adventuregamestudio / ags

AGS editor and engine source code
Other
707 stars 159 forks source link

Support running interaction callbacks in any script module #2534

Closed ivan-mogilko closed 1 month ago

ivan-mogilko commented 2 months ago

Fix #485

When running a global object's interaction function, the engine will search in all modules in their order, until it finds a matching function. Only the first found callback is run, the others (if any exist) are skipped.

There's still a question of how to handle this in the Editor. I can try to make Editor jump to the correct place when user presses "..." button in events table with existing function. But all the new functions will be still generated in GlobalScript. I cannot see any easy way to reconfigure this.

EDIT: Hmm, here's what I may try:

  1. Add a new field to the types, related to events, which caches the script file where event functions are located. Since nothing prevents user from moving these around, I suppose this may store the location of a first filled event. After all, this is not a restriction, but is for a convenience. This field will not be saved to game, and not used at runtime.
  2. When adding for a new event, Editor will insert a function in the script, referenced by this script name cache field. If that script does not exist, then it will recache the name based on which events exists, searching for them in all scripts, or using global script if there's none.
  3. When opening a connected event function for editing, Editor will first try to zoom into this cached script name. If that script does not exist, or function not found, it then will recache and search further, as explained above.
ivan-mogilko commented 2 months ago

I stopped liking my initial idea. Perhaps saving script's name along with the function name for event would be a smarter approach. This may be done both in the Editor and compiled game.

ericoporto commented 2 months ago

Uhm, I know we are currently limited to 128 modules but if we readjust the assembly it's easy to go for something like 32768, so if the Character or GUI or whatever had a property to set the script they link to, they could even have their own dedicated scripts - like say a save/load GUI that you would export with the script.

I am imagining here how it works in game maker where each element gets it's own script file - not sure on going that far, as this seems more useful for something like prefabs that you instantiate in multiple places, so this is why I thought about a property. I imagine people would maybe have a script for handling all their items or perhaps they would divide them by game area - would this make more sense for adventure games?

Perhaps asking in the forums about this, someone could give additional ideas.

ivan-mogilko commented 2 months ago

Okay, I managed to do something interesting. I put the property, but not in the object itself, but inside a Interactions class, and made it displayed on Events tab, closer to events themselves, for convenience.

Furthermore, this property displays a automatically refreshed selection of script modules for global objects, and a fixed room script for room contents.

This is how it looks like: select-script-module-for-events

There are still few things not working as expected, for instance, I found that GUIs use different tab class to display their events, which unfortunately does not share any common base class with object interactions... so they do not work yet. Another thing is that this module selection is used only by the Editor right now. I was thinking that it's possible to avoid saving this property in the compiled game, and instead prepend module name as a prefix to the function name (i.e. something like module.asc::functioname), then it could all be written into existing fields; the downside being be duplicate prefix for all interactions. But this is still something to think about.

ericoporto commented 2 months ago

Your solution to the UI looks great, and it does look better having the field right there in the events tab! It feels intuitive. :)

The project upgrade approach here would be to fill all of these with GlobalScript.asc, right?

I was thinking that it's possible to avoid saving this property in the compiled game

Uhm, right, going this route only thing I think would be to confirm that these event function name entries support variable length strings as entries - thinking that the function name may be slightly bigger with the added file name.

ivan-mogilko commented 2 months ago

Added ScriptModule property for GUIs. Controls use their parent GUI's setting.

ivan-mogilko commented 2 months ago

The PR is complete for testing.

Midway I changed my mind about saving module's name in compiled game. At first I wanted to concatenate it with the event function names for simplicity, but then realized that this may lead to extensive data duplication for no real gain. Plus, this is not required for room interactions. So I added "ScriptModule" field to InteractionEvents list and - separately - GUI class. This mirrors the changes in the Editor and project data, so is also consistent in my view.

There's another thing: previously I thought that this structure may come handy if we support delegates, but later realized that is not likely to be a thing, because for the "function pointers" assigned at runtime we will need a different kind of data for quicker calls. In fact, we may do the same to these events too. The existing "InteractionEvents" lists may serve as a loaded data, which may be resolved to script function addresses after everything is loaded and scripts initialized. Well, this is all a future matter, but the point is that there's probably no need to plan existing structs for use as "function pointers" at the moment...

Anyway, this rambling aside, current PR now amends the compiled game format with a new extension, but it does not amend compiled room format, since room interactions don't need any new data.