Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Messages - Lord Vetinari

#21
Thank you! I never thought I could use views that way.
I understand most of your code, correct me if I'm wrong: create a pointer, assing to it the frame stored in a specific position in a view, assign the pointer's value to the GUI element, then "recycle" the same pointer for the next GUI element and so on, right? One thing I don't understand, though (forgive me, but as a self-taught programmer my knowledge and understanding of code and AGS specifically is very erratic): why did you put +6 on the view argument on line 3 and a -1 on the frame argument on line 6? I mean specifically the + and the -.
#22
Hello, I once again need your help figuring out how to update a system in my game in a sane way.

Context: in the bottom right corner of my game's screen there's a GUI that dispalys the avatar of the protagonist's starship's AI, Stantok (think Holly from Red Dwarf). It is made of a floating head, torso and hands. It is not actually animated, however it does change it's pose depending on it's “mood”, let's say; this “mood” is tracked by an integer variable whose values are not incremental, they just indicate a specific state (so 0 means neutral, 1 happy, 2 surprised, 3 sarcastic, etc.). Then there are add-ons that represent the ship's systems and are meant to be displayed on top of each part as if they were weared gadgets; they can be activated and deactivated by the player and are handled graphics-wise using the gStantok GUI buttons graphics. On top of all this, there's a ShipPowerLevel variable that tracks how much of the ship's power grid is restored and how much stuff is activated (so, once again, it's not a linear progression) and, from the graphics point of view, means that the add-ons look a bit different depending on that variable's value.
So, to recap, in terms of graphics there are a GUI background that is used to display the generic figure of the avatar and is updated every time AI's mood changes, and a series of GUI buttons that display some gadgets and they too are updated every time the mood changes because they need to match the base figure's pose, plus updated every time the player fixes some bits of the ship AND every time the player chooses to activate or deactivate something on the ship's computer.
In terms of parameters, we have the mood (which changes mostly on it's own), activated gadgets (which are activated manually so depend entirely on the player) and power level (which is only indirectly controlled by the player)

Now, the gameplay logic for all of this is in place and working properly and it's good enough (at least for me), what I'm really unhappy with is the logic that handles the graphics of this GUI elements.
Right now it works with a very brute force and dumb approach, which means a maze of if-else clauses that checks all possible combinations and is really unwieldy and basically impossible to upgrade/expand (not to mention read and debug) without major headaches; what I'd like to do is to make it more flexible and modular and possibly use less game resources to boot.

The current function is something like this:
Code: ags

void UpdateStantokGUI() {
	if (StantokMood == 1) {
		gStantok.BackgroundGraphic = 15;  //or whatever the sprite ID number is
		if (HandsActiveSystem == 1) {
			if (ShipPowerLevel == 1) {
				btnStantokHandsGear.NormalGraphics = 25;
			}
			else if (ShipPowerLevel == 2) {
				btnStantokHandsGear.NormalGraphics = 26;
			}
			//repeat the above for each power level
		}
		//repeat the above for each system and each “body” part.
	}
	else if (StantokMood ==2) {
	//repeat all the above for each mood state.
}


What I'd like to do is simplify that function to something like this:

Code: ags

if (StantokMood == 1) {
	gStantok.BackgroundGraphic = 15;
	btnStantokHandsGear.NormalGraphic = HandsGraphicsID; // by which I mean something like a pointer or variable containing the proper graphics ID depending on type, pose and upgrade, all defined and selected elsewhere.
	btnStantokHeadGear. NormalGraphic = HeadGraphicsID; //same
	btnStantokTorsoGear.NormalGraphic = TorsoGraphicsID;//same
}
else if (StantokMood = 2) {
//repeat
}
//and so on for the other mood values.


and use another function, hopefully less convoluted than the one I have now, to figure out which are the proper graphics at any given moment. The final goal is to make this logic completely indipendent from the actual sprite IDs so that adding new add-ons or removing/replacing exhisting ones becomes as easy and painless as possible, without messing too much (if any) with the functions that handle this GUI or the scripts that call these functions.

Therefore I started to create a struct that contains the list as int variables of all the sprite IDs correlated to any specific add-on, then created an array of these structs, so that each array entry represents one add-on. The (admittedly pretty vague) idea for this second function was some kind of “sorting engine” that could tell the game “this array entry and this struct entry are the ones we're currently using”.
Then I discovered why you need good plans, because the whole thing started to go down the wrong track and finally derailed when I discovered that I can't create a pointer to a custom array or struct (which was the way I thought I could use to set the right array entries as the currently used ones), so I had to once again use the if-else nest.

Help, please? Am I missing something or is this approach completely wrong for what I'd like to do? What should I look at in that case? Thanks in advance
#23
Ok, thanks everybody.

I managed to streamline things a bit with Snarky's example. The important thing here was understanding if my approach was wrong or not; it's good to know that even if the code was rough, I was on the right track.
#24
Quote from: Vincent on Sun 30/04/2017 10:32:03
It doesn't work if you do something like this ?

Code: ags

// main global script file

Hotspot *activeInteractionPoint;
Object *activeInteractionPoint;
Character *activeInteractionPoint;
int lt;

function repeatedly_execute() 
{
   lt = GetLocationType(mouse.x, mouse.y);
   
   if (lt == eLocationCharacter)
   {
     
   }
   
   else if (lt == eLocationHotspot)
   {
     
   }
   
   else if (lt == eLocationObject)
   {
     
   }
   
   else if (lt == eLocationNothing)
   {
     
   }
}

Maybe I'm misreading it, but it doesn't seem to help with the duplicated code issue.
It seems your code tests what kind of activeInteractionPoint I clicked on, then let me do different things with each type. I need the exact opposite, a code that does the same thing irregardless of the type of object the pointer points to.

If you need more context, it's a custom verb menu that grabs the list of available verbs/interactions from the object's properties (list that of course varies depending on the particular object; I managed to get that working in the old version of the code that is more or less what you see in the OP. That version though worked only for hotspots. I extended it to objects and characters by simply replicating the same code three times and changing the pointers, but that feels like a kludge. What I'm looking for is a way to achieve that without the duplication, if possible).
#25
@RickJ: I wouldn't know where to start with that. I'm a self taught programmer, my confidence in my coding skill is rather low, and I can assure you that if anything I write seems somewhat advanced, it's pure luck  :-D
I'm trying to expand my boundaries with this project, so I'll look into that, but I'd rather proceed with baby steps.  :-[

@ Snarky That kinda helped, thank you.
Except that now instead of duplicating the code that creates the gui, I'm duplicating the code that grabs the data and I'm using a couple more variables to boot (specifically, an array of strings where I store all the interaction names to retrieve them later when I create the GUI and another INT counter to know how many elements of the array are actually used). Overall it's a couple of lines shorter than the previous version, still I don't think I've achieved that much. A couple more hints, please?

The issue with the way I'm approaching it is that I can't figure out where I should define the pointer, because if I nest that inside a couple of if clauses like

Code: AGS

 if (GetLocationType(mouse.x, mouse.y) == eLocationHotspot) {
      Hotspot *activeInteractionPoint;
 }
 else if (GetLocationType(mouse.x, mouse.y) == eLocationObject) {
      Object *activeInteractionPoint;
 }
 else if (GetLocationType(mouse.x, mouse.y) == eLocationCharacter) {
      Character *activeInteractionPoint;
 }

then the compiler won't accept activeInteractionPoint outside that series of clauses, same if I do that inside another function and then call it inside on_mouse_click. I guess I need to define the pointer outside the if/else clauses to be able to use by anything outside them, but if I don't use if/else clauses I can't test what kind of object I have under my cursor. It does work if I put inside each if clause the code that grabs the value, but that leads to duplicating it three times.
Therefore I guess my approach is completely wrong, but I can't think at anything different.
#26
I thought about it, but I feel it would still need a lot of unnecessary checks to replace the pointers inside the function.
#27
This is a follow-up to my old thread about a custom interaction menu: https://www.adventuregamestudio.co.uk/forums/index.php?topic=54624.0

I'm still usign the code you see in post 3, with updated variables names to fix the silly quasi-hungarian notation. I'll just quote the post for simplicity's sake:
Quote from: Lord Vetinari on Sun 26/03/2017 21:52:14
Code: ags

function on_mouse_click(MouseButton button) {
  // called when a mouse button is clicked. button is either LEFT or RIGHT
  Hotspot *activeHotspot;
  if (IsGamePaused() == 1) {
    // Game is paused, so do nothing (ie. don't allow mouse click)
  }
  else if (button == eMouseRight)  {
    //Here I put all the menu code.

    //Begin by resetting everything before a fresh start on a new hotspot
    gInteractionsMenu.Visible = false;  //This one is required because I had some fringe cases when a previous menu remained visible but with no options in the listbox, no idea why.
    ListInteractions.Clear();
    gInteractionsMenu.Height = 38;
    ListInteractions.Height = 0;
    
    //set the required control variables
    int AreThereInteractions = 0;
    vIntMouseActionX = mouse.x;
    vIntMouseActionY = mouse.y;
    activeHotspot = Hotspot.GetAtScreenXY(mouse.x, mouse.y);
    
    if(activeHotspot != null) //safety check: pointers can be "null", and if so you can't call methods on them (Thanks Snarky!)
    {
      //We add the options with a loop that automatically ends when there are no more options to be added
      vIntActionNumber = activeHotspot.GetProperty("Interaction_Number");
      String CurrentOption;
      int vActionCounter = 1;
      while (vIntActionNumber > 0) {
        CurrentOption = String.Format("Action_%d", vActionCounter);
        ListInteractions.AddItem (String.Format("%s", activeHotspot.GetTextProperty(String.Format("%s", CurrentOption))));  
        //The GUI is resized depending on how many items there are in the listbox
        gInteractionsMenu.Height += 23;
        ListInteractions.Height += 23;
        vIntActionNumber -=1;
        AreThereInteractions += 1;
        vActionCounter +=1;
      }
        if (AreThereInteractions > 0) {
        //If there are no options, the menu is not displayed.
        //I know that it would be enough to set this to 1 and call it a day, but I thought that adding and additional if clause in the loop to check it was superfluous
        gInteractionsMenu.X = mouse.x;
        gInteractionsMenu.Y = mouse.y;
        ListInteractions.SelectedIndex = -1;

        //Now we title the menu with the name of the hotspot
        if (activeHotspot.ID != 0) {
          lbMenuTitle.Text = String.Format("%s", activeHotspot.Name);
        }
        else {
          lbMenuTitle.Text = "Room";
          //This is here because sometimes even hotspot[0] has special interactions that are meant
          //to be available everywhere in the room, not only on actual interactive items.
          //For all the room this is not the case, the menu won't even appear if you don't click on an actual hotspot because AreThereInteractions is 0.
        }
        gInteractionsMenu.Visible = true;
      }
    }
  }
  else if (button == eMouseLeft) {
  cEgo.Walk(mouse.x, mouse.y eWalkableAreas);
  }
}

Now, that menu is supposed to work not only for hotspots, but for objects and characters as well.
Currently it does, but again I feel I did it in the least smart way possible: I set three pointers (Hotspot *activeHotspot, Object *activeObject, Character *activeCharacter), check each one at a time if it's not null (plus an additional safety check to exclude hotspot 0 when it comes to objects and characters), and copy/paste under each check the same code, just replacing the proper pointer each time.

I don't know much about coding, but I know that if you're copypasting the same lines of code several times, you're doing it the silly way and there's usually a better way of doing it. This has been bothering me for a while and today I feel in the right mood to try and fix it, but I can't seem to find a way. What should I look to understand what I should do? I thought enums, but, other than not knowing if I can make an enum with pointers, I still have to check which option of the enum is the right one, so that's not it.
#28
Regarding money-for-engine/editor development, what about a Patreon? I know that a lot of people are turning to it and many fails, but given the size of this community I think it could work. And the beauty of Patreon is that if it succeeds it can raise a substantial amount of money wile leaving the patrons free to choose how much they feel like contributing.

I mean, AGS is doing huge things for me. I'm mostly a self taught programmer, I actually don't know anything, I come up with solutions that I think are smart and then turn out to be 30 years old outdated practices that are now shun upon by real programmers, etc. I'm also not making a penny from my project, not because they are that bad, but because I don't feel like asking money for them (yet? who knows). I don't want to sound ungrateful, and being a freelancer artist as my daytime job I know well that people should earn money for their work, but this being my usage of AGS, I don't know if I'd buy AGS to basically toy around a bit with it. I know about Unity's Adventure Creator and, beyon Unity not being as user friendly as AGS, I know that 70$ is a bit too much for what I'll be doing with it in the foreseable future (which basically means forever, because if there isn't something that lets me monkey around with coding untill I become a bit less crap, I'll never think that paying for a development tool will be a wise move for me).
I'd gladly put a few bucks on it's Pateron page, though.
#29
Quote from: Snarky on Mon 27/03/2017 15:20:31
Quote from: Lord Vetinari on Sun 26/03/2017 21:52:14
So every hotspot can have up to 8 different interactions, but not always the same eight interactions? If they're always using a subset of the same eight, I think there are simpler/better ways to do it.

No, they change. In the current state of the project, the amount ranges from 0 (almost every hotspot[0] except two specific cases) to at maximum 8, with an average of 4/5.
They change with each hotspot for a couple of reasons:
- since you can't talk to a fridge, for example, I think there's no sense in adding a "talk" option, so some interactions may not be available. This is what makes it hard to figure out what is in a specific spot on the listbox. It is slightly confusing to have a series of Action_n properties, but since I code the interactions as I set up the hotspot and therefore the local values of each custom property, hopefully it's easy to remember which one is which.
- some of them indeed have custom names. Rather than a generic "use", it may be "turn on" for an electric appliance, "eat a snack" for the fridge, etc. In the case of the TV I put in the example post, it has two separate options for "watch movie" and "watch TV show", for example.

Quote from: Snarky on Mon 27/03/2017 15:20:31
The main thing I would change if this were my code is the series of vBoolActionN flags. Assuming only one of them is true at a time, you shouldn't have 8 different variables to represent one thing. This a typical case for using an enum. An enum is a variable that can have a set of predefined values:

Code: ags
enum PlayerAction
{
  eActionInvalid = -1,
  eAction1,  // If you have a constant set of 8 interactions, I would use a descriptive action name instead of a number
  eAction2,
  eAction3,
  eAction4,
  eAction5,
  eAction6,
  eAction7,
  eAction8
};


And then to set the current action (I've also fixed your indenting):

Code: ags
PlayerAction currentAction;

function ListInteractions_OnSelectionCh(GUIControl *control)
{
  if (ListInteractions.SelectedIndex == 0) {
    currentAction = eAction1;  // <--- NOTE: Changed!
    gInteractionsMenu.Visible = false; //Turn off the GUI
    Room.ProcessClick (vIntMouseActionX, vIntMouseActionY, eModeUsermode1); //process the interaction
    //reset the "fake mouse pointer" to a place where there is the unclickable statusbar, so it won't find anything to interact with for safety reasons.
    vIntMouseActionX = 0;
    vIntMouseActionY = 0;
    mouse.Mode = 1; //set the mouse to one of the mouse mode I'm not going to use
  }
  // ...
}


And finally:

Code: ags
function hTV_Mode8()
{
  if (currentAction == eAction1) {
    //do stuff
  }
  else if (currentAction == eAction2) {
    //do stuff
  }
  // (other alternatives: you don't need entries for the actions not supported by this hotspot
  else {
    //default/fallback: in case you missed something
  }
  currentAction = eActionInvalid;
}

I'll look into enums, thanks. But can I set them as global variables? Can I set it up in the global scrip and then read them in the room hotspot function?

Quote from: Snarky on Mon 27/03/2017 15:20:31
In fact, in AGS 3.4 you can now use the switch-case construct instead of the series of if-else-if statements:

Code: ags
function hTV_Mode8()
{
  switch(currentAction) {
    case Action1:
      //do stuff
      break;
    case Action2:
      //do stuff
      break;
    // ... more cases...
    default:
      //do stuff
      break;
  }
  currentAction = eActionInvalid;
}


The drawback here is that you can't tell what action the user has chosen without looking up the custom property. Unless the set of interactions is entirely open, I would prefer meaningful names and some other way to define which ones are supported by each hotspot.

This is very interesting! Indeed the interaction set is open, that's why I didn't use proper names. I actually considered using a completely different set of custom boolean properties like IsTalkAllowed for the most common ones, but I decided the version I went with required less properties and was more customizable.

Quote from: Snarky on Mon 27/03/2017 15:20:31
It's not a bad idea to read the manual beginning-to-end (skimming over the scripting documentation, just getting a sense of the classes that exist): it's not that long. But there are definitely aspects of it that could be improved, and getting feedback from beginners is important input for that work. I'm hoping I'll have a chance to take a pass at an update soonish (meaning sometime this year).

I didn't know it was you who maintained it, sorry. Didn't want to offend. I did read the whole manual before I started a year ago, but something got filed in my brain under "advanced coding, be careful" and in the end you kinda know they are an option but can't think about it on the fly. That's why I said that references would be useful.
On a side note, there's one thing left to do with this code, which is making the menu the standard interaction with objects and characters too, but I guess I just have to expand the code in on_mouse_click to include a character and an object pointer. All the custom Action_n properties are already set up to be available to objects and characters too.

Quote from: Snarky on Mon 27/03/2017 15:41:17
Oh, one more concern: I'm curious about your variable naming scheme...

Code: ags
vBoolAction2
vIntMouseActionX


You're using some weird version of Hungarian notation, with two prefixes: v- (not sure what this means) and the variable type (Int- or Bool-). In my opinion this is not good practice. Most coding style guides recommend against Hungarian notation (particularly system-Hungarian, where it's used to indicate the variable type). It's used in AGS for a few things (mostly built-in struct types, and enum options), but I definitely wouldn't use it for ints and bools.

I didn't even know it was called Hungarian notations  :-[
If I'm allowed a bit of rambling, all my coding education is a few lessons on Turbo Pascal at school in the 90s on a prehistoric Commodore that still used 5 1/4 inches floppy disks (yes, in the 90s). Everything else (which you may as well say everything at all, because it taught me some really basic concepts like if-else checks, for/while loops and declaring variables and functions, but not much else; besides, who uses Pascal these days?) is basically self taught via occasional spurs of curiosity and creativity, like the current one.
I named variables this way because it seemed easier to remember for me what is what, especially for global variables that may be called all over the places. Why is it discouraged? What are the drawbacks?
#30
Nevermind, I think I solved everything myself and I'm really, really happy right now! I removed all the default code in the on_mouse_click function and added this:

Code: ags

function on_mouse_click(MouseButton button) {
  // called when a mouse button is clicked. button is either LEFT or RIGHT
  Hotspot *activeHotspot;
  if (IsGamePaused() == 1) {
    // Game is paused, so do nothing (ie. don't allow mouse click)
  }
  else if (button == eMouseRight)  {
    //Here I put all the menu code.

    //Begin by resetting everything before a fresh start on a new hotspot
    gInteractionsMenu.Visible = false;  //This one is required because I had some fringe cases when a previous menu remained visible but with no options in the listbox, no idea why.
    ListInteractions.Clear();
    gInteractionsMenu.Height = 38;
    ListInteractions.Height = 0;
    
    //set the required control variables
    int AreThereInteractions = 0;
    vIntMouseActionX = mouse.x;
    vIntMouseActionY = mouse.y;
    activeHotspot = Hotspot.GetAtScreenXY(mouse.x, mouse.y);
    
    if(activeHotspot != null) //safety check: pointers can be "null", and if so you can't call methods on them (Thanks Snarky!)
    {
      //We add the options with a loop that automatically ends when there are no more options to be added
      vIntActionNumber = activeHotspot.GetProperty("Interaction_Number");
      String CurrentOption;
      int vActionCounter = 1;
      while (vIntActionNumber > 0) {
        CurrentOption = String.Format("Action_%d", vActionCounter);
        ListInteractions.AddItem (String.Format("%s", activeHotspot.GetTextProperty(String.Format("%s", CurrentOption))));  
        //The GUI is resized depending on how many items there are in the listbox
        gInteractionsMenu.Height += 23;
        ListInteractions.Height += 23;
        vIntActionNumber -=1;
        AreThereInteractions += 1;
        vActionCounter +=1;
      }
        if (AreThereInteractions > 0) {
        //If there are no options, the menu is not displayed.
        //I know that it would be enough to set this to 1 and call it a day, but I thought that adding and additional if clause in the loop to check it was superfluous
        gInteractionsMenu.X = mouse.x;
        gInteractionsMenu.Y = mouse.y;
        ListInteractions.SelectedIndex = -1;

        //Now we title the menu with the name of the hotspot
        if (activeHotspot.ID != 0) {
          lbMenuTitle.Text = String.Format("%s", activeHotspot.Name);
        }
        else {
          lbMenuTitle.Text = "Room";
          //This is here because sometimes even hotspot[0] has special interactions that are meant
          //to be available everywhere in the room, not only on actual interactive items.
          //For all the room this is not the case, the menu won't even appear if you don't click on an actual hotspot because AreThereInteractions is 0.
        }
        gInteractionsMenu.Visible = true;
      }
    }
  }
  else if (button == eMouseLeft) {
  cEgo.Walk(mouse.x, mouse.y eWalkableAreas);
  }
}


Then, in the function that handles listbox selections:

Code: ags

function ListInteractions_OnSelectionCh(GUIControl *control)
{
  if (ListInteractions.SelectedIndex == 0) {
  vBoolAction1 = true;  //This variable is used in the hotspot code to choose what action needs to be performed.
  gInteractionsMenu.Visible = false; //Turn off the GUI
  Room.ProcessClick (vIntMouseActionX, vIntMouseActionY, eModeUsermode1); //process the interaction
  //reset the "fake mouse pointer" to a place where there is the unclickable statusbar, so it won't find anything to interact with for safety reasons.
  vIntMouseActionX = 0;
  vIntMouseActionY = 0;
  mouse.Mode = 1; //set the mouse to one of the mouse mode I'm not going to use
  }
  else if (ListInteractions.SelectedIndex == 1) {
  vBoolAction2 = true;
  gInteractionsMenu.Visible = false;
  Room.ProcessClick (vIntMouseActionX, vIntMouseActionY, eModeUsermode1);
  vIntMouseActionX = 0;
  vIntMouseActionY = 0;
  mouse.Mode = 1;
  }
  // Repeat the same bits for the maximum listbox choices possible (currently 8 because I never have more options than that, so up to ListInteractions.SelectedIndex == 7 and vBoolAction8
  else
  mouse.Mode = 1; //set the mouse to a mode that is not walking, so that it doesn't do anything as there is no code associated with any of the default actions
}


And finally, in each hotspot/room script (this is a single example, for a TV in a living room):

Code: ags

function hTV_Mode8()
{
  if (vBoolAction1 == true) {
    //do stuff
    vBoolAction1 = false;
  }
  if (vBoolAction2 == true) {
    //do stuff
    vBoolAction2 = false;
  }
}

//etc.


As you can see I bypassed all predefined mouse modes and interactions and used the first of the two custom ones because, even if I try to be constant regarding at which place certain actions should appear, it turns out that, since some interactions are not available on certain hotspots, there are so many exceptions that it's easier to manually put in the hotspot code the interactions you need.
I'm really happy, it works like a charm. It slightly bothers me that I have to use a "close" button to close it if you con't choose any option, maybe I should add a function in repetedly_execute that checks if the mouse is moved away from the GUI and deactivate it automatically.

What do you think of it? See any part that can be improved? I tested it in all the conditions I could think of and it seemed very reliable (at least in my game).
#31
Thank you! I can be a rambly guy, I'll try to keep it at minimum, sorry.  :P

Bonus question: the brute force way I tried had all the code in the various AnyClick() functions; the current plan is to keep it that way and start replacing every bit that can be generalized with a global function. When I've done that, is there a better place to put the interactions code or it's fine where it is?

[ramble, avoid if not interested]
Can I say that AGS's manual and wiki lack a bit of cross referencing and sometimes assume that the users are a bit more knoweledgable that what's probably the average level?
I mean, unless you read and memorize the whole thing from top to bottom newbies like me will miss obvious stuff like this one. I searched before asking here (probably with the wrong wording), and everything pointed to the hotspost function and properties page. However there isn't on that page a line as simple as "all these functions require IDs, if you want to generalize them you need to use pointers", which would at least give me a clue on what to look after that.
#32
A bit of context: I made a game last year, a quite crappy one if I say so myself, by playing it VERY safe, using the most basic scripting and interactions and the least amount of custom made stuff based on examples, code monkey style. Now I'm trying to spice things up and customize things and generally trying to be more daring by upgrading/remaking it with some neater features (like the clock I asked about last time).

What I'm trying to replicate at the moment is something like Leisure Suit Larry 7's interactions menu. If you are not familiar with it, LSL7 used left click to move the character and possibly to execute the default interaction with something (but I'm not sure; I haven't replayed it recently and anyway I'm not really interested in this second functionality), and right clicks opened a small drop-down menu at the cursor's position with a list of potential interactions with that object/hotspot that changed depending on what you clicked on. It was also used to access the inventory and the ship's map and possibly some other generic options I can't remember when you right-clicked anywhere outside an interactive element (but again I'm not interested in that as much - which is kinda ironic, because it seems it's the only bit I can replicate as I know that no hotspot in AGS script is hotspot[0]).
I like this kind of interface, it's somewhat of a mix between the complexity of having multiple modes/verbs and the simplicity of the more modern one-click-does-everything systems. It also allowed to sneak in the occasional nice joke interactions (well, actually coarse joke interactions since we're speaking of LSL, but that's beyond the point).
I know, I'm trying this despite tha fact that a couple of days ago I wasn't able to properly format a string containing a couple of variables (later I found that my mistake is explained in the manual too - really sorry for that -, but for some reason I find it hard to navigate the manual and all those legacy references to very old versions of AGS make it hard for me to understand how you are supposed to properly spell things now in 3.4); It does seem a bit excessive for my skills, but as Ron Gilbert said, "sometimes it pays to be so stupid that you don't understand you can't do what you're trying to do" (ok, I'll probably never be as good a coder as Ron Gilbert and I'm definitely not making something as groundbreaking as Maniac Mansion, but hopefully even I can manage to achieve something).

I kinda managed to have something that resembles what I described using a custom GUI with a listbox and resizing both depending on the amount of options available, and basically bruteforcing it by replicating the same code (or at least variations of the same given the different actions allowed on each object/hotspot) in every single interactive element in one room, which I know is very bad programming and a good hint you need a generic function.
So I created a bunch of custom text properties for objects, characters and hotspots called Action1, Action2, etc, and tried to create a generic function in the general script that reads those and use them to fill the listbox.
The problem is that I can't find a way to read them if I don't specify the ID of the hotspot. I tried to use
Code: ags
Hotspot.GetProperty(Action1)
but it doesn't work, the compiler returns an error and, since every example I can find of it's use on the manual and the wiki use
Code: ags
hotspot[somenumber].GetProperty(action1)
I figured that you need to know what specific hotspot you are clicking on.
I may work around that by starting with a series of checks like
Code: ags
if (Hotspot.GetAtScreenXY(mouse.x, mouse.y) == hotspot[1])
which I know it works because it comes straight from the manual (and I used it to check that hotspot[0] case I talked about), but that seems blunt and bruteforceish too.
Is there a way to get the values of those properties without knowing the hotspot ID in advance? Or am I approaching the matter from a completely wrong angle?
If I'm doing everything wrong I'd like you to just point me in the right direction rather than giving me actual working code, I'd like to understand and figure it out myself, otherwise I'll never stop opening threads on the help forums :P
#33
Quote from: Cassiebsg on Tue 21/03/2017 15:16:27
In that case, it doesn't even need to be in rep_exec. Just call it after each interaction, no need to check it 40 times a sec.  ;)

Well, you are absolutely right now that I think about it. Thank you for pointing that out!
For some reason I had this fixation that, being a GUI, it had to be constantly updated.
#34
Thank you.
Thank you. I knew that about the repeatedly_execute_always, however I don't think it is needed because the clock doesn't update in the background, it updates when you interact, in theory after every other interaction-related code is executed; that's why I said it shouldn't matter, any blocking action should be endend by the time I call the clock function.
#35
Thank you! I knew I was missing something.

Regarding putting it in repeatedly_execute_always, I can see it as a safety precaution against my clumsiness (I can easily see myself calling the functions in the wrong order, for example), but is it really necessary? There isn't an actual timer, the function that updates the clock is called only if the player interacts with something. I'm asking to understand better how things work.

EDIT: one quick question while I'm at it: I never thought of that, but of course if the minutes are less than 10 this code displays them as a single digit rather than 6:05, for example. I fixed it rather bluntly by checking if vIntMinutes is less than 10 and using two different messages, one with a 0 added in the string and one without it. It seems a rather blunt solution, is there something more elegant than this?
#36
Apparently I don't know how to do anything, please send help  :P
The very basic idea is this: there's a player controlled clock in game (I mean that time advances only if the player interacts with anything) and I'm trying to display it in a label on temporary GUI for testing purposes and I ended up immediately stuck.
I created three global variables in the global variables editor, vIntDays, vIntMinutes and vIntHours, created the gui and the label, then put in the repetedly_execute function of the global script this very simple line:
Code: ags

    lbClock.Text = ("Day %d", vIntDays, ", %d", vIntHours, ":%d", vIntMinutes);


The goal is to have it display something like "Day 1, 6:30")
I compile it and it says GlobalScript.asc(66): Error (line 66): Parse error in expr near '"Day %d"'
I dug an old question from the forums, stuff that dates back to more than 10 years ago, and the solution was to convert the int variables into strings before displaying them, so I did that:

Code: ags

    vStrDays = ("%d", vIntDays);
    vStrMinutes = ("%d", vIntMinutes);
    vStrHours = ("%d", vIntHours);
    lbClock.Text = ("Day ", vStrDays, " ", vStrHours, ":", vStrMinutes);


(of course I created the three new string global variables beforehand the same way).

Same results, except that the error now point to the very first line, Error (line 66): Parse error in expr near '"%d"'. So obviously either %d doesn't work (but the forums says it does) or the game doesn't recognize the variables (but I can find them in the autocomplete), or repetedly_execute is not the right place (but where else could I put it to keep the clock updated?) What am I doing wrong? The manual says that if you create a variable in the global variable editor you don't have to declare and/or import it the old way.
I haven't coded the clock, yet (this was the preparatory bit before delving into more complex scripting. I'm off to a great start!), but it shouldn't be an issue because I gave all the int variable the base value 0 in the editor.
#37
Thanks for the answer, I'll look into that.
I'm a bit wary about performances because the manual, wiki and some tutorials keep talking about that, it's hard for a newbie to understand to what extent you can push the engine.
#38
Thanks for the answers!
I think I'll be having at most 4 characters in a room, up to six if I split the AI in three. Is that too muck? I'm not planning to have any scrolling room, I imagined that it won't be easy to keep the watch in the right place.
The head is definetly animated (the AI talks), ideally the arms too. I could use a GUI for the torso, I didn't think about that.
What's the workaround to animate a GUI?
#39
After quite a few unreleased test games, I'm trying to make something more complex. In this game, the main character's sidekick is an AI thingy that lives in the protagonist's wrist watch. I'm planning to make a closeup of the watch in the bottom right corner. The appearence of the AI's head, arms and torso will change a few times throughout the game depending on the solution of certain puzzles. I'd like to give the player relative freedom to choose which goal to tackle first, which means that I can't draw a linear progression of the AI's looks.
I think I know how to do this, my question is: what is better from the coding, performance and memory usage point of view? Three overlapping AGS characters (head, torso, arms) so that I can change each one's view individually, or a single AGS character with an exponentially larger amount of views and loops to cover each potential combination?
#40
Hello, Lord Vetinari here. I've been lurking the site and forums since AGS 2.7 or something like that. I made a couple of small test games, now I'd like to try my first publicly released game, so I registered.

In real life I'm a professional graphic and comic book author, so I think I can manage graphics and writing; unfortunatly I have a weird relationship with programming, let's call it unreturned love. I like it but it doesn't like me, so bad stuff happen.
SMF spam blocked by CleanTalk