Features suggestion: autocomplete custom properties + disabling them

Started by Lord Vetinari, Tue 05/12/2017 10:21:24

Previous topic - Next topic

Lord Vetinari

The title is kinda cryptic due to title length limitations, I'll try to explain more here.

Baically, I've been using custom properties a lot recently, mostly to consolidate stuff and track/set custom statuses. So, for example, I have the inventory items custom properties "batteryCharge" and "powerSetting" (think Star Trek phasers with stun and kill settings for this one, ans ammo for the other one), which allows me to have a single laser gun that behaves differently depending on those properties without the need to create multiple inventory items and swap them to represent the different statuses of the gun. I don't know if this was the intended use for custom properties, anyway it works really well and I find it easier to wrap my mind around design this way for this kind of purpose.

I have two tiny gripes with the custom properties used this way, though: the first is that the editor does not autocomplete or indeed recognize if what you're writing is a thing until you compile, which means that I have to either write down their names on paper or keep opening the custom properties window to check the correct spelling, and I'm missing the easiness of use that comes with the ability to choose one item from the autocomplete list rather than manually typing their names.

The second is that you can only create custom properties per broader categories (i.e, all objects, all inventory items, all rooms, etc), which means that when I use this approach a lot, I end up with a long list of mostly useless properties that are meant to be relevant for one or two objects/rooms/chracters/etc and instead pop up in every single same-type-thingie I create in the rest of the game (the iBucket doesn't need batteryCharge nor powerSetting, but it may need the fullnessThresholds one that is however useless for the iGun, and so on. This would become an even bigger problem if autocomplete will ever be added). Not a big issue per se (even though I don't know how big an impact a large amount of custom properties have on the game's performance and memory usage, if any), but sometimes tracking down the right property in the middle of the other useles ones becomes more tedious that it should be.
Would it be possible to add the option to enable/disable any property from any specific item/object/etc (or at least hide it from it's properties list)?

Snarky

The lack of autocomplete is a consequence of the fact that custom property names are arbitrary strings. There's really no feasible way to make autocomplete happen while that is the case: they'd have to be fundamentally reimplemented some other way.

However, you can get access to autocomplete by using #define statements to make constants for the strings you need:

Code: ags
#define ipBatteryCharge "batteryCharge"  // "ip" for "inventory property"

function someFunction()
{
  // ...
  int charge = iGun.GetProperty(ipBatteryCharge); // Should autocomplete
}


You can put the #define statement in your GlobalScript header to make the constant available in room scripts.

(By convention, constants are usually all uppercase, but I think it's worth making an exception in this case.)

For the other issue, that probably means you're using custom properties in a way they're not meant to be used. If you have some property that only applies to one particular instance of a type (like an inventory item), you should just use a variable to store it. For example, if you just have a single gun in the game, there's no need to add batteryCharge and powerSetting as custom properties to all inventory items: You can just have them as global variables.

If you have a whole bunch of related variables that describe some type of object, and particularly if you can have more than one instance of that object, that's when you want to use a struct. For example, let's say you have a bunch of space ships with different stats:

Code: ags
struct SpaceShip
{
  String Name;
  int View; // The view number for this ship
  int CargoCapacity;
  int PassengerCapacity;
  int MaxSpeed;
};


Now you can create different space ships. For example:

Code: ags
  SpaceShip millenniumFalcon;
  millenniumFalcon.Name = "Millennium Falcon";
  millenniumFalcon.View = VMILLENNIUM; // The name of a view
  millenniumFalcon.CargoCapacity = 120;
  millenniumFalcon.PassengerCapacity = 6;
  millenniumFalcon.MaxSpeed = 16;


And if you then have a Character called cShip that represents your current space ship, you might set it to the Millennium Falcon like so:

Code: ags
  cShip.Name = millenniumFalcon.Name;
  cShip.ChangeView(millenniumFalcon.View);

Lord Vetinari

Didn't know about #define, that'll be useful, thank you.

As for the structs, going with your example I'll have to then check either via if-else or switch cases which SpaceShip is currently assigned to cShip if I want to get any use out of those nice extra properties like CargoCapacity, and the more SpaceShip instances there are, the messier the function becomes (unless I'm missing something relevant). Also less easier to expand in case later down the line I decide to add new ship classes.
The code becomes much more straightforward and easy to read (especially if you check it back some time later) if I can simply do something like
Code: AGS

void LoadingCargo(int TransferredCargoSize, int CargoID) {
  if ((TransferredCargoSize <= cShip.GetProperty("CargoCapacity") && (cShip.GetProperty("IsCargoHoldFull") == false)) {
    cShip.SetProperty("IsCargoHoldFull", true);
    cShip.SetProperty("TransportedCargo", CargoID);
  }
  else {
    Display ("sorry, we have no room for that");
  }
}


Nine lines, done, and it'll work with any CargoCapacity value I'll ever assign to cShip. But this on the other hand means that I'll find all my non-ships characters with CargoCapacity and all the other stuff that they don't need attached.
How can you do the same as easily with structs?

Snarky

Quote from: Lord Vetinari on Tue 05/12/2017 14:42:28
As for the structs, going with your example I'll have to then check either via if-else or switch cases which SpaceShip is currently assigned to cShip if I want to get any use out of those nice extra properties like CargoCapacity, and the more SpaceShip instances there are, the messier the function becomes (unless I'm missing something relevant). Also less easier to expand in case later down the line I decide to add new ship classes.

No, that shouldn't be necessary. There are a couple of ways to it, depending on your exact needs.

One is to use a managed struct with a pointer (I've added a couple of bells and whistles to the code to show you what's possible; you don't strictly need all of this):

Code: ags
// SPACESHIP MODULE HEADER
managed struct SpaceShip // We've just added the tag "managed"
{
  String Name;
  int View; // The view number for this ship
  int CargoCapacity;
  int PassengerCapacity;
  int MaxSpeed;

  // And let's add some more properties for the current ship status:
  int CurrentCargo;
  int CurrentPassengers;
  int CurrentSpeed;

  // We can also add functions:

  /// Make this SpaceShip your current ship
  import void Select();

  /// Load cargoAmount. Returns whether the cargo could be loaded.
  import bool LoadCargo(int cargoAmount);
};

import SpaceShip* myShip;
import SpaceShip* millenniumFalcon;

// SPACESHIP MODULE SCRIPT

// This is a variable that should always refer to your current ship
// (the same way "player" always refers to the current player Character)
SpaceShip* myShip;
export myShip;

SpaceShip* millenniumFalcon; // If you have a lot of different ships you'd probably store them in an array instead
export millenniumFalcon;

// You need to make sure this function is called, ideally on game start
function InitializeSpaceShips()
{
  millenniumFalcon = new SpaceShip;
  // ... Otherwise same as before
}

void SpaceShip::Select()
{
  myShip = this;
  cShip.Name = this.Name;
  cShip.ChangeView(this.View);
}

bool SpaceShip::LoadCargo(int cargoAmount)
{
  if(this.CurrentCargo + cargoAmount > this.CargoCapacity)
    return false;
  this.CurrentCargo += cargoAmount;
  return true;
}


Now you can call millenniumFalcon.Select(); to set your ship to the Millennium Falcon, and instead of cShip.GetProperty("CargoCapacity") you would just use myShip.CargoCapacity, or even the custom function for loading cargo we just made:

Code: ags
void LoadingCargo(int transferredCargoSize) {
  if(!myShip.LoadCargo(transferredCargoSize))
    Display ("Sorry, we have no room for that");
}


And then you could do it in 4 lines of code! :-D
(You're not really saving much code, just moving it into SpaceShip.LoadCargo() instead.)

If you don't want to use managed structs (or you're using an older AGS version that doesn't support them), you would have to work with arrays and copy functions and so on. That's a good deal more tedious.

As you see, you're essentially doing just the same thing as with custom properties, except that instead of adding the properties to (in this case) the Character type, you're making a type called SpaceShip and adding them to that. With just a few lines of code to link the Character to the SpaceShip, you get all the benefit without adding any clutter to other Characters.

Edit: Fixed typo in code

Lord Vetinari

I'm using 3.4.0, the latest official release (since 3.4.1 isn't officially out yet).

Wait, so there's been managed structs in AGS for this whole time? The wiki says that there weren't and has even a lengthy and rather scary article on how to fake custom struct pointers.
I've been bashing my head against the lack of custom types pointers for such a long time! Admittedly I'm doing something that probably is not meant to be done in AGS, that is a game with a significant survival/simulation component in which you start from scratch, update your tools, have to manage the character's stats, etc, and I've been doing all of that with custom properties because I thought I couldn't use structs.

I'm not that obsessed with the overall lenght of the code, I just don't want to have overly long functions that you don't understand anymore a week after you wrote them no matter how many comments there are. "Encapsulating" stuff (ok, I know it's not proper encapsulation as it's all public, you get what I mean) in structs is perfectly fine.

Given this, I'll probably have to rewrite half my game, but that's a huge relief. Thanks!

Crimson Wizard

Quote from: Lord Vetinari on Tue 05/12/2017 15:45:54
Wait, so there's been managed structs in AGS for this whole time? The wiki says that there weren't and has even a lenghty and rather scary article on how to fake custom struct pointers.

User managed structs were introduced in 3.4.0, but there is a technical limitation that you cannot put another managed pointer in such struct. I know that's silly, but there is a serious reason for that that was not yet solved.

Wiki is like 10 years old compared to recent release, it is not even updated to AGS 3.2.1 (last version made by Chris Jones).
(I feel like repeating this for the 10th time in past couple of years, because people keep getting into similar situation :().

I strongly advise to check for "Upgrading to AGS X.X" topics in the manual you have with the AGS Editor (you can call it with F1). These articles note most important additions and changes.

Lord Vetinari

Thank you. No pointers in structs shouldn't be a huge issue in my case (after all, I already wrote a significant part of the "survival" logic using properties, which do not allow pointers either).
I'm not too keen on starting from scratch, at least for now I'll probably just move all that stuff to structs and update the global script functions to reference the structs instances' members rather than the custom properties.

Thank you all!

SMF spam blocked by CleanTalk