PDA

View Full Version : Message system discussion



DarkDragon
01-24-2017, 12:25 AM
AngelScript will use a messaging system, and ZScript may switch to using one as well, depending on if it seems like the effort is worth it (i.e., if it looks like AngelScript will take a while to get up and running).

This thread is to discuss how scripts will work in the messaging system, and to hash out the details of this system.

First, a quick overview. Instead of scripts with run() methods, in the future scripts will have a collection of message handlers/callbacks. Here is an initial, very minimal set of proposed messages that more or less replicate current ZScript functionality. Ultimately, many more message types could be supported (feel free to suggest):

Every script type
OnCreate -- called when the object to which the FFC is associated is first created (at the beginning of the game for global scripts; when Link enters the screen for screen and ffc scripts, etc)
OnDestroy -- called when the object to which the FFC is associated is destroyed (when Link leaves the screen for screen and ffc scripts, etc)
EveryFrame -- called at the beginning of each frame (immediately preceding when current scripts that called WaitFrame() execute)
OnDraw -- called at the end of each frame (immediately preceding when current scripts that called WaitDraw() execute)

Item scripts
OnPickedUp -- called when an instance of the item is picked up
OnUsed -- called when the item is used from the inventory

...

Once a callback starts to run, its execution context exists independently of the execution of any other of the script's callbacks. Waitframe() suspends execution until the start of the next frame, WaitDraw() until the end of the current frame, etc. So, for example, if a candle script has an OnUsed callback that creates fire and animates it for several iterations, if Link uses the candle item a second time, a second and completely independent instance of the OnUsed callback starts to run (and can spawn and control its own fire weapon, etc.)

It seems like every script should have control over what messages it is listening for, and which ones it is ignoring (so for instance, a blue candle script could disable OnUsed until Link leaves the screen after the first time it is used, etc.)

Instead of using Misc[] for storing local state, each script will have its own set of local variables, declared within the script, and shared by all instances of all running callbacks of that script.

Old Script Compatibility

If messages are implemented into ZScript, ensuring compatibility of old scripts seems straightforward. Each old global and ffc script (script that includes a Run() method and no callbacks) gets a default OnCreate callback that calls Run(), and a default OnDestroy callback that calls Quit(). Each old item pickup script gets an OnPickedUp that calls Run(), and item use scripts gets OnUsed() callbacks that call Run().

ZoriaRPG
01-24-2017, 02:03 AM
AngelScript will use a messaging system, and ZScript may switch to using one as well, depending on if it seems like the effort is worth it (i.e., if it looks like AngelScript will take a while to get up and running).

This thread is to discuss how scripts will work in the messaging system, and to hash out the details of this system.

First, a quick overview. Instead of scripts with run() methods, in the future scripts will have a collection of message handlers/callbacks. Here is an initial, very minimal set of proposed messages that more or less replicate current ZScript functionality. Ultimately, many more message types could be supported (feel free to suggest):

Every script type
OnCreate -- called when the object to which the FFC is associated is first created (at the beginning of the game for global scripts; when Link enters the screen for screen and ffc scripts, etc)
OnDestroy -- called when the object to which the FFC is associated is destroyed (when Link leaves the screen for screen and ffc scripts, etc)
EveryFrame -- called at the beginning of each frame (immediately preceding when current scripts that called WaitFrame() execute)
OnDraw -- called at the end of each frame (immediately preceding when current scripts that called WaitDraw() execute)

Item scripts
OnPickedUp -- called when an instance of the item is picked up
OnUsed -- called when the item is used from the inventory

...

Once a callback starts to run, its execution context exists independently of the execution of any other of the script's callbacks. Waitframe() suspends execution until the start of the next frame, WaitDraw() until the end of the current frame, etc. So, for example, if a candle script has an OnUsed callback that creates fire and animates it for several iterations, if Link uses the candle item a second time, a second and completely independent instance of the OnUsed callback starts to run (and can spawn and control its own fire weapon, etc.)

It seems like every script should have control over what messages it is listening for, and which ones it is ignoring (so for instance, a blue candle script could disable OnUsed until Link leaves the screen after the first time it is used, etc.)

Instead of using Misc[] for storing local state, each script will have its own set of local variables, declared within the script, and shared by all instances of all running callbacks of that script.

Old Script Compatibility

If messages are implemented into ZScript, ensuring compatibility of old scripts seems straightforward. Each old global and ffc script (script that includes a Run() method and no callbacks) gets a default OnCreate callback that calls Run(), and a default OnDestroy callback that calls Quit(). Each old item pickup script gets an OnPickedUp that calls Run(), and item use scripts gets OnUsed() callbacks that call Run().

->Misc[] support does need to continue, so that mixing scripts does not pose problems. That is, a newer script needs to know how to read/write to ->Misc[] of an object generated by an older script.

Or do you not mean the Misc[] array for the pointer objects? Really, it's wise to retain that, as part of the object class, as the object can remain extant after the script exits, which is where those values have always been useful. Cutting that is going to be another 'huge problem', and I don't see how it would be a technical issue, given that it is a variable of the object class and not inherent tot he script that generated it.

Did you mean something else?

Another issue, is that if the same local vars are shared across all running instances, that this would not seem to work properly given the present types of implementation. For example, an ffc script that controls an npc, may have ten (or 32) running instances, each [I]needing[I] its own local variables and/or arrays. If this is not what you meant here, please clarify.

DarkDragon
01-24-2017, 02:28 AM
Misc will still work. But there will also be script local variables you can use instead, for data you want to share between callbacks.


Another issue, is that if the same local vars are shared across all running instances

Hold on, I'm not sure what you mean by "running instances" here.

If you have multiple FFCs on the screen, and all have been assigned the same script, each script behaves completely independently. Other than the fact that they happen to have the same code, the only thing they share are the global variables.

Each script can have script member variables that act exactly like C++ member variables. For one FFC, each callback can read and write member variables that are also visible to other callbacks (of the same FFC only).

Then of course each callback has its function local variables, which are unique to each execution of the callback.

Gleeok
01-24-2017, 03:10 AM
Hold on ZoriaRPG, let me translate the OP really quick using the state of the art tool transintercommunicomputron-2000:

**working**
...

BZZTFFTT....
...

Computron-2000 ready to translate this is my robot voice BZZT WHIRR.



Greetings primitive human scum. No one use the beta-suggestion forum to help improve scripting in ZC. Why you all do this? We like to improve scripting it make easy things to do that are hard as current. Please help suggest ways to improve our base turret firing system to protect from Pigeon raids. Pigeons eat our milkshakes, this is bad. We can send message to you so you can intercept via script to stop future bird poop attacks. What system do you like to use? We can transmit coordinates directly to your scripts. In future, we poop on birds from below. Is great justice for ZC in 2.6 to poop on birds from below.


Err.. Looks like it's got some kinks in it still... I can never get the blasted thing to work right. :/

ZoriaRPG
01-24-2017, 07:15 AM
Misc will still work. But there will also be script local variables you can use instead, for data you want to share between callbacks.



Hold on, I'm not sure what you mean by "running instances" here.

If you have multiple FFCs on the screen, and all have been assigned the same script, each script behaves completely independently. Other than the fact that they happen to have the same code, the only thing they share are the global variables.

Each script can have script member variables that act exactly like C++ member variables. For one FFC, each callback can read and write member variables that are also visible to other callbacks (of the same FFC only).

Then of course each callback has its function local variables, which are unique to each execution of the callback.

I primarily wanted to clarify, and ensure that was true. Your post sounded like you want to deprecate ptr->Misc[], which doesn't make sense because those values are independent of any script that sets them.


Hold on ZoriaRPG, let me translate the OP really quick using the state of the art tool transintercommunicomputron-2000:

**working**
...

BZZTFFTT....
...

Computron-2000 ready to translate this is my robot voice BZZT WHIRR.



Err.. Looks like it's got some kinks in it still... I can never get the blasted thing to work right. :/

[/choking to death laughing]

You win Gleeok. The AGN 'Funnies Bastard' award, goes to you. :)

Gleeok
01-27-2017, 10:23 AM
I guess no one has any concrete ideas?

For the time being I'll just do these:

~OnUpdate()
~OnDraw()
~OnPigeonRaid()

The tricky one is going to get Link right at some point. Perhaps we might try and come up with a spec for how Link would work if scripters want to have control over various Link stuff.

DarkDragon
01-27-2017, 02:27 PM
This is a good idea. When I get some spare time I'll take a look at the code and come up with a proposal.

For items, weapons, enemies, etc I think it's best to add a very minimal set of callbacks to the existing hard-coded enemies, and then add in a special "scriptable enemy" etc that has a full and large set of callbacks.

Gleeok
01-28-2017, 09:17 AM
Callbacks are in for stateful scripts (ie.; scripts with persistent stack space). For global callbacks these should be declared global procedures by scripts. I can add any procedure to call these on the script side easily, just need to spec the desired arguments and return values and then add them to ZC first.

There's a slight annoyance with classes though: Script classes cannot inherit from application classes, so for example a scripted npc with an npc property can't do 'this.X = 0', but instead '(this.)npc.X = 0', which is weird. This requires an extra layer of a scripted base class to define these properties and get them from the engine... I *think*. The problem is we are short of people, especially with Saffith leaving. I wouldn't be surprised if npcs have to wait one version... :/ Unlimited screen scripts, map scripts, global scripts, and various callbacks might be enough for an initial release, which isn't so bad I guess.

One nice thing is the memory usage isn't too bad. Compiling and updating 2048 global scripts -each script uses only a few kb.

ZoriaRPG
01-28-2017, 05:17 PM
I guess no one has any concrete ideas?

For the time being I'll just do these:

~OnUpdate()
~OnDraw()
~OnPigeonRaid()

The tricky one is going to get Link right at some point. Perhaps we might try and come up with a spec for how Link would work if scripters want to have control over various Link stuff.

I missed this one. The first thing that we need to do in this regard, is refactor how some aspects of the Link class work. If you don;t want to allow writing to sprites, then we need to rework sprites,a nd read them from an ffscript class of some kind, as a default, and if those are not set, use the engine sprites. Likewise, the detection of Link and map flags, combo solidity, eweapons, and npcs, need to work in the same way, calculating his hitbox from his x, y, hitsize, and hitoffsets. The present implementation is scattered around, uses some dfunctons that are very hard to read, and is just generally terrible.

I'm a bit tired of making adjustments back and forth between files. A general, hitbox function per class would be best, for calculating hits. Perhaps even a hitbox class that can be applied to any game object? That would solve many issues.

Are we good to go for messages in the zscript parser for now?

Saffith
01-28-2017, 07:40 PM
There's a slight annoyance with classes though: Script classes cannot inherit from application classes, so for example a scripted npc with an npc property can't do 'this.X = 0', but instead '(this.)npc.X = 0', which is weird.
You can use virtual properties to work around that.

fix X
{
get const { return npc.X; }
set { npc.X=value; }
}
That also has the advantage that the handle can be made private in the base class, so you can't accidentally point it to another enemy.
One minor issue what that is that you can't use increment and decrement operators on virtual properties. I think it should be possible to add them, though, if you don't mind messing around with AS's code a bit. They were deliberately removed, and I forget the reason, but I think it had to do with multithreading issues that aren't relevant to ZC.

Tamamo
01-30-2017, 10:27 AM
Global Scripts
onDeath --called when dying
onWin --called when saving zelda
onPassiveSubscreen --called everyframe but only if subscreen is enabled
onActiveSubscreen --called when opening subscreen, would allow for some awesome stuff
onMap --called when opening maps

Gleeok
01-31-2017, 09:10 AM
Thank you @Tamano for actually suggesting things. :) I think I'll start a list so people can start adding them in at a later time.

I started on global callbacks with the aim to make them dead simple for you guys to add to ZC. Currently you have these to work with:

void CallGlobalCallbackFunction(int id);
bool CallGlobalCallbackFunctionWithArgs(u32 id, ScriptVariant* args, u32 argCount, ScriptVariant* returnValue = NULL);

-Step 1: add an enum value to a supported callback list. ex: CB_ON_LINK_DEATH,
-Step 2: in the code that checks for link death and add: "CallGlobalCallbackFunction(CB_ON_LINK_DEATH);"

In general, once the AS engine is hashed out better, any additions to the script engine generally requires modifying a single function.

Next I have to figure out the best way to allowscripts to set callbacks dynamically. This might require reserving space in the save file.

Tamamo
01-31-2017, 10:18 AM
Gleeok
you're welcome. :)

ZoriaRPG
02-01-2017, 01:54 AM
Thank you @Tamano for actually suggesting things. :) I think I'll start a list so people can start adding them in at a later time.

I started on global callbacks with the aim to make them dead simple for you guys to add to ZC. Currently you have these to work with:

void CallGlobalCallbackFunction(int id);
bool CallGlobalCallbackFunctionWithArgs(u32 id, ScriptVariant* args, u32 argCount, ScriptVariant* returnValue = NULL);

-Step 1: add an enum value to a supported callback list. ex: CB_ON_LINK_DEATH,
-Step 2: in the code that checks for link death and add: "CallGlobalCallbackFunction(CB_ON_LINK_DEATH);"

In general, once the AS engine is hashed out better, any additions to the script engine generally requires modifying a single function.

Next I have to figure out the best way to allowscripts to set callbacks dynamically. This might require reserving space in the save file.

Why are you hardcoding messages like this? AFAIK, DarkDragon was planning to allow the user to define messages in the ZScript parser.

I would just allow defining a message and linking it to a series of events that are easy to expand.

OnUseItem(int itm) and such are good, if you can;t do it this way.
OnScriptRunning(type, id)

OnEngineEvent(event) would be a prudent generic.
OnSpawnNPCs()
OnGenerateDrop()
OnGenerateDrop(dropset)
OnGenerateItem(item id)
OnTriggerSecret()
OnTriggerSecret(specific_secret)
OnUseWeapon
OnUseEWeapon
OnTriggerMapFlag(flag)
OnLowHP()

There are too many single-purpose message points to list...

Gleeok
02-01-2017, 02:56 AM
Why are you hardcoding messages like this? AFAIK, DarkDragon was planning to allow the user to define messages in the ZScript parser.

I would just allow defining a message and linking it to a series of events that are easy to expand.
.

I don't understand what you are saying. You have to "hardcode" them otherwise how in the hell is ZC going to call the script function at the appropriate place in the code? Hint: It can't.

Scripts can also at any time - even in the callback itself!! - change the callback dynamically in the script. Ex:



void OnDeathWithFairy()
{
//revive Link
// then....
if(--bottled_fairy_count == 0)
Game.SetCallback(CB_DEATH, @OnLinkDeath);
}

void OnLinkDeath()
{
//dies ..poor guy. R.I.P.
}


For all the writing you do I feel like you don'tread what I am saying... like ever. You don't NEED TO SET EVERYTHING IN ZQUEST BECAUSE WITH THE NEW SCRIPT ENGINE YOU DONT NEED TO EVER OPEN ZQUEST. You can compile scripts directly in ZC as you are playing, minus setting up enemies in the editor and shit like that. At least that's the plan.

ZoriaRPG
02-03-2017, 06:26 AM
I don't understand what you are saying. You have to "hardcode" them otherwise how in the hell is ZC going to call the script function at the appropriate place in the code? Hint: It can't.

Scripts can also at any time - even in the callback itself!! - change the callback dynamically in the script. Ex:



void OnDeathWithFairy()
{
//revive Link
// then....
if(--bottled_fairy_count == 0)
Game.SetCallback(CB_DEATH, @OnLinkDeath);
}

void OnLinkDeath()
{
//dies ..poor guy. R.I.P.
}


For all the writing you do I feel like you don'tread what I am saying... like ever. You don't NEED TO SET EVERYTHING IN ZQUEST BECAUSE WITH THE NEW SCRIPT ENGINE YOU DONT NEED TO EVER OPEN ZQUEST. You can compile scripts directly in ZC as you are playing, minus setting up enemies in the editor and shit like that. At least that's the plan.

What did I write, at all, in this thread, that is even remotely related to requiring the user to open ZQuest?

'I would just allow defining a message and linking it to a series of events that are easy to expand. '

So, that feeling is mutual.

By hardcoding, I mean creating individual callback identifiers, versus OnEvent(system event), which would IMO, be easier to later expand, by expanding the possible events. That is, OnEvent(EVENT_DEATH, ...). What is the point of:

OnDeath(EVENT_DEATH, ...) ??

You want a series of enums for the events, and you want to separately list every possible event, too? Please, what is the purpose? I legitimately do not comprehend it. Please, if I;m missing something here, let me know. I see a handle in the syntax, and a defined value. Is there a need for individual message IDs, or is that just a list of examples for internal use, and not for user-use?

Perhaps that is what I am missing here.
That, and of course, setting user-defined events, would be nice. If that isn't possible, that's fine.

Gleeok
02-03-2017, 06:35 AM
You're going to have to expand that idea and clarify what you mean with some sort of implementation detail then, because I don't understand how what you are suggesting is even possible.

ZoriaRPG
02-03-2017, 07:36 AM
You're going to have to expand that idea and clarify what you mean with some sort of implementation detail then, because I don't understand how what you are suggesting is even possible.

You mean user-defined callbacks?

I was thinking that a user could set them up, sort of like a jump. Put an insertion point in a piece of code, and if a callback references the 'jump point', it executes that code starting at the specified point. So, you would use the @ Handle to reference a function, and the defined value for the jump point:



void foo(){
//instructions
//instructions
$jump_identifier
//other instructions
}

user_callback(jump_identifier, @foo)


Not sure if it is feasible, but I thought it would be interesting. There may be more idyllic ways to do it. In fact, it may be possible to allow setting some kinds of special engine conditions in the programme, labeling them, and using them as callbacks; which would, probably require setting them up in ZQuest. I would think though, that dmap IDs, items in use, eweapons in use, and the lot that I posted above, would all be useful events for this sort of thing, and you didn't even comment on those.

This would be mostly useful in a script series where a defined series of events was logical. Taking ywkls Metroid thing as an example, you would want an onSamusNoHP() callback, that was linked to the custom HP system. For one of my quests, I may want OnIsInSpace() as a callback. Who knows. The point, is that if it could exist as an option, may as well make it possible.

I was mistakenly reading the labels for some of these things as specific callback instructions though. That was my fault. I though that OnLinkDeath(ON_DEATH, @function_to_run) was how you were doing it, which was silly. This was probably because DD and Gray were discussing it two weeks ago, and the message OnDeath() was one of the proposed things from that chat.

I don;t think that this kind of callback is actually something that I have used to date. C/Cpp don;t support them. I don;t recall if Ruby does, but I've never used them. I avoid Java/J++ like the plague. I see how useful they would be, don;t mistake me. I just want to ensure that the implementation is sane and streamlined.

I'm not sure how these would be written so that adding callback types internally wouldn't be a gigantic pain. By this, I mean, writing the source-code side of things to define the event and wrap it in something, because our present 'events' are strewn through classes in a wherever-fashion. I would think that would all need fixing.

I will add however,

onLinkDrawing ...
onLinkWeapon(weapon)

and similar.

onCollision(type) is also good, if I missed that above. Simplify the hell out of those.

Other suggestions:

onWarp(type)
onRolLCredits
onIntroScreen
onTitleScreen
onNameEntry

Howzat?

Gleeok
02-03-2017, 08:00 AM
An enum is a simple and convenient way of declaring to ZC what specific engine behavior you want to change. Overrides are handled internally.

You can already do all that stuff you mentioned in AS. You can call any callback yourself, or you can store an array of funcdefs or delegates and call them whenever. http://www.angelcode.com/angelscript/sdk/docs/manual/doc_datatypes_funcptr.html

DarkDragon
02-11-2017, 01:41 AM
void CallGlobalCallbackFunction(int id);
bool CallGlobalCallbackFunctionWithArgs(u32 id, ScriptVariant* args, u32 argCount, ScriptVariant* returnValue = NULL);



Gleeok, can we do something like this:

There is a list of game objects (which I will cause Objects, for lack of any creativity) that can have scripts attached to them. An initial list of Objects are
1. the game itself;
2. the screen;
3. Link;
4. npcs;
5. items;
6. weapons;
7. combos (freeform or otherwise).

Each Object maintains
- any number of scripts, each of which can contain any number of callbacks;
- local variables associated to that Object

The game engine, and scripts, can send a message to either a specific Object (via a pointer to that Object) or broadcast a message to all active Objects.
If multiple Objects would receive a message, they receive them in a well-defined order (for example, in the order listed above, with scripts associated to the game getting first dibs, then screen scripts, etc). Each callback returns whether to continue processing the message, or to stop. Each callback runs in its own execution context as described in my first post.

You suggestion to start with just CB_ON_UPDATE and CB_ON_DRAW sounds fine to me. We can work out the details of WaitFrame() and WaitDraw() within callbacks, Object pointers and variables, execution contexts, etc. for just these two basic messages, and build up from there.

Gleeok
02-11-2017, 05:06 AM
Gleeok, can we do something like this:

There is a list of game objects (which I will cause Objects, for lack of any creativity) that can have scripts attached to them. An initial list of Objects are
1. the game itself;
2. the screen;
3. Link;
4. npcs;
5. items;
6. weapons;
7. combos (freeform or otherwise).

Each Object maintains
- any number of scripts, each of which can contain any number of callbacks;
- local variables associated to that Object

The game engine, and scripts, can send a message to either a specific Object (via a pointer to that Object) or broadcast a message to all active Objects.
If multiple Objects would receive a message, they receive them in a well-defined order (for example, in the order listed above, with scripts associated to the game getting first dibs, then screen scripts, etc). Each callback returns whether to continue processing the message, or to stop. Each callback runs in its own execution context as described in my first post.

You suggestion to start with just CB_ON_UPDATE and CB_ON_DRAW sounds fine to me. We can work out the details of WaitFrame() and WaitDraw() within callbacks, Object pointers and variables, execution contexts, etc. for just these two basic messages, and build up from there.

As a good jumping off point I was planning on Screen and Global scripts, or "Objects," first then going from there. The first script system I wrote was over-engineered and as a result was crap to work with and use; the second time I learned my lesson and it was much simpler. Forcing scripters to use objects (read: classes, member ptrs, virtual, inheritance, etc.) for everything is not a good start I don't think. Tell me if you think I'm wrong here, but whenever we can do a simple procedural style interop I'd go with that.

The simplest things to add (and immediately support) right now are global callbacks--Link is just a namespace so he falls into that category as well--everything else object related needs a bit of integration into ZC, and npcs need a slight refactoring first. Also doing the bindings and all that junk for them so I'll call that phase 2.

The way I'm doing it right now is this: If a script object defines an instance callback then it gets called, if not then the engine will ignore it. For example:


class MyGlobalScript
{
int myTime = 420;

void OnDestroy() { //this callback is defined, so it gets called by the engine.
//last call for alcohol
}
}

In this example maybe the scripter doesn't care about it being updated, or drawn, or anything else besides what happens when it is destroyed, so that is fine. It still exists and other scripts can access it the same, or whatever else. Did you have something better in mind?

I don't like the idea that any object can have any number of scripts though, that sounds very complicated and bad. Can you give an example?

Also Waitframes(), contexts, and callbacks are already working. I do need to implement an intermediate pass over preprocessed script code (hopefully without any major lexing) as a next thing I do though. Modifying angelscript is not so easy for certain things, and I wasted a bunch of time in that when I should of just done it separate... :/

DarkDragon
02-11-2017, 02:41 PM
As a good jumping off point I was planning on Screen and Global scripts, or "Objects," first then going from there.

Sounds good to me. In fact it may make sense to implement AS for Screen and Global scripts, and then release a public beta letting users play around with it, and gather feedback we can use to flesh out other scriptable things.


Forcing scripters to use objects (read: classes, member ptrs, virtual, inheritance, etc.) for everything is not a good start I don't think. Tell me if you think I'm wrong here, but whenever we can do a simple procedural style interop I'd go with that. I'm not sure what you mean? Under the hood, the game engine will need to keep track of the "list of scriptable things" and have a way for scripts to reference them (whether this is handles, pointers, integer IDs, or whatever). I don't care much about the details of how this is done, though having a class for e.g. items, with built-in member variables that are common to all items, does make sense to me.


In this example maybe the scripter doesn't care about it being updated, or drawn, or anything else besides what happens when it is destroyed, so that is fine. It still exists and other scripts can access it the same, or whatever else. Did you have something better in mind?
Sounds good to me.


I don't like the idea that any object can have any number of scripts though, that sounds very complicated and bad. Can you give an example?
Sure, supposed you have a screen for a dungeon boss. You have one screen script in charge of controlling the boss, one that is in charge of some environmental effects (lava that hurts link and teleports him when he falls in), and one that handles some ancillary graphics (creating clouds of steam that change shape and wander around the screen). Instead of writing a Franken-script that tries to do all of these things, you split up the unrelated stuff into independent scripts each with their own independent callbacks.

This has several advantages:
1. Smaller self-contained scripts are easier to work with than giant scripts that try to do a lot of unrelated things;
2. Smaller self-contained scripts are easier to mix and match (you can reuse the vapor cloud script on a different screen that doesn't have the boss);
3. On a related note, smaller self-contained scripts can be independently packaged and shared (i.e in the QDB).

Even for e.g. items it is useful to be able to split up callbacks between several scripts. For instance you might want one script that handles animating an items, and another that is in charge of the item use behavior, since it seems commonplace for users to want to be able to mix-and-match the two parts of an item without having them tied to each other.

Grayswandir
02-11-2017, 03:48 PM
Are the local variables associated with each object going to be like the ->Misc[] arrays currently in use, or will they be different for each object class, or will it be more like a self.anyNameYouWant = 30; lua/javascript kinda deal?

DarkDragon
02-11-2017, 04:59 PM
I imagine it will be a mix of pre-defined variables with game engine meaning (like Tile, etc) and user-defined variables. I hope Misc[] goes away, it's a crude workaround from the days where all data associated with an FFC had to be defined at compile time.

There is an important but subtle distinction between (a) variables associated to an object (the Game, an item, etc) and (b) variables associated to a script attached to the object. I suppose we need support for both.

Gleeok
02-11-2017, 07:20 PM
Sure, supposed you have a screen for a dungeon boss. You have one screen script in charge of controlling the boss, one that is in charge of some environmental effects (lava that hurts link and teleports him when he falls in), and one that handles some ancillary graphics (creating clouds of steam that change shape and wander around the screen). Instead of writing a Franken-script that tries to do all of these things, you split up the unrelated stuff into independent scripts each with their own independent callbacks.

This has several advantages:
1. Smaller self-contained scripts are easier to work with than giant scripts that try to do a lot of unrelated things;
2. Smaller self-contained scripts are easier to mix and match (you can reuse the vapor cloud script on a different screen that doesn't have the boss);
3. On a related note, smaller self-contained scripts can be independently packaged and shared (i.e in the QDB).

Even for e.g. items it is useful to be able to split up callbacks between several scripts. For instance you might want one script that handles animating an items, and another that is in charge of the item use behavior, since it seems commonplace for users to want to be able to mix-and-match the two parts of an item without having them tied to each other.

Ah, I see what you are saying. I did think the same thing except our solutions were different. Instead of multiple object script contexts I was opting for single object context and general way for scripts to create other contexts if they want to. ..I guess they both are OK but there is downside:



void Callback_or_Whatever()
{
//get script attached to object
IScript@ obj = [email protected];
if(obj !is null) {
// access script object here
}

// Or... multiple scripts

for(int i=0; i < ffc.NumScripts; ++i) {
IScript@ obj = [email protected][i]; // check for specific script type here... or what?
if(cast<MyScript@>(obj) !is null) {
// it's the battering ram script part so ignore it..
}
if(cast<AnimationController@>(obj) !is null) {
// change the animation..
}
}
}


I don't know. AS supports composition and virtual inheritence already so I just did the simplest thing. We'll play around with it.

Gleeok
02-12-2017, 04:51 AM
Okay, I'm looking at it right now. I need to add two major components and three minor ones and then it should be ready for you to mess around with. You'll only have access to Global functions, Logging, and Link variables right now, but hey. Kind of busy this week, but I'll see what I can do.

DarkDragon
02-12-2017, 06:46 AM
I guess they both are OK but there is downside:

Is there a need to check for "script types"? If scripts are just a collection of callbacks, presumably you just need to check scripts for the callback you're looking for, and invoke the method if it's found.


Okay, I'm looking at it right now.
Awesome, looking forward to it!

Gleeok
02-12-2017, 07:59 AM
Maybe I'm still misunderstanding... ..If objects have arrays of scripts, then you'd have multiple callbacks even though it's the same object. If a script needs access to variables from another script it's the end user that has to sort through them, which is why I think it's needlessly complex. Does composition do what you are suggesting? Eg;



class Foo
{
Bar@ bar; // thing a) subscript of this object.
Baz baz; // thing b) Similar to bar, but we'll just handle it ourselves without the overhead of another script context.

Foo() {
bar = Game.CreateSomeScriptContext(..);
bar.parent = this;
}
}

DarkDragon
02-13-2017, 12:57 AM
I understand what you mean: if scripts are classes independent of the objects they are attached to, it is awkward to access or create variables on the objects themselves, rather than on the scripts.

This is also a problem currently with ZScript, where users make use of the Misc array to store data at the object level.

I'm not sure there's a clean way around it, though. One solution is to have objects (the game, screen, Link, items, etc.) maintain a key-value store that scripts attached to these objects can read and write from (a la JavaScript), and as you say it's then the user's responsibility to make sure that different scripts do not trample on each other when trying to share use of the same variables. If I understand correctly, you are suggesting another: there is a "master" script for each object, which can contain subscripts. Only the master script can create variables attached to the object (i.e., accessible from other scripts not attached to that object, and persisting after scripts finish executing and/or are unattached)? In ZQuest how do I specify which script is the master?

Maybe it is helpful to discuss a concrete example. Let's say I want three scripts attached to a certain screen:
- a miniboss script;
- a lava script (checks if Link steps on lava, and punishes him when he does);
- a purely cosmetic "vapor" script.

1. All three want to listen and respond to the OnUpdate() callback, and do independent things inside that callback.
2. The lava script doesn't need write access to any of the Screen's variables. It passively listens for messages and checks Link's position against the Screen's Combo array each frame.
3. The miniboss script and vapor script both want to write persistent state to the screen: the miniboss wants to keep track whether the boss has been killed or not (once killed it stays dead) and the vapor script wants to store the vapor cloud's current position, so that it persists if Link leaves and reenters the Screen.
4. The miniboss script wants to be able to read the vapor script's stored position (maybe the boss runs towards and hides in the vapor when it gets too hurt).
5. Once the boss dies, the vapor on the screen stops (the vapor script detaches from the Screen and no longer receives any new messages). Maybe this is done by the miniboss script sending the vapor script a Kill message, or by directly telling the Screen to detach the vapor script... I'm not sure.
6. Ideally, it is possible for the user to achieve all of this by just going to Screen->Scripts in ZQuest, and clicking "add script" three times and pointing to three different script files written by Moosh, without needing to do any additional coding.

What's the simplest setup that would allow for this kind of interaction?

Grayswandir
02-13-2017, 04:01 PM
Here's how I'm imagining the system:

Global:


We have a global persistent key/value store for miscellaneous data. This can be used to store data between instantiations of the same object if needed.

Objects:


Every object can be interacted with by its class. So a screen object has methods and variables that make sense for Screen, etc. (Like the Screen-> we have now).
Every object exposes a scripts array/list/whatever listing all the scripts it has attached to it. Scripts can be added/removed dynamically, but the added/removed scripts aren't saved persistently across instantiations of the object.
Every object has a publicly accessible key/value store. Again, this isn't saved persistently across instantiations of the object. This bit is nice to have, but it isn't particularly important that it's done right away, or at all.
The Screen object, and others that make sense to, may optionally have a persistent data store.
Every object has a void receiveMessage(string message, args...) method. The object just passes the message on to all of its scripts.

Scripts:

Each script has a public pointer to its attached object and its own name.
Scripts can be moved between objects, but can't live on unattached. (Though there will be a global Game object for global scripts.) Every script can have a destructor message for when its attached object dies (possibly letting it move to another object before it disappears.)
Every script has a vod receiveMessage(string message, args...) method which adds a pair<Script, Message> to the global queue. The queue is run through several times a frame - during Waitdraw(), Waitframe(), etc. Basically, any time the main program stops to send the built in messages, we run through the queue first. Messages can be appended to the queue by messages started from the queue, and they will still execute during this time step.
Every script has a public bool hasMessageHandler(string name).
Scripts don't need a key/value store, since they can just make variables as needed. Though it might be useful for insterscript communication?


So, for your scenario:

The lava script is attached to the screen, and otherwise doesn't interact with anything.

The vapor script:

On instantiation, the script checks if it should be run. This is either by checking the global data store ("noVapor:" + dmapid + ":" + dscreenid), or the persistent screen data store if we go that route ("noVapor").
The script has a custom kill() message, which detaches it and marks the noVapor variable as above.
In the update message, it stores "vaporX" and "vaporY" in the screen's temporary data store.

The boss script:

Creates the boss when you enter the room. It can make use of the engine's existing boss death flags and such to manage this.
When it detects the boss's death (through the destructor, probably), it'll send the kill message to the vapor script.
It reads the screen's temporary data store for "vaporX" and "vaporY" to move to. If they're not present, it just doesn't execute that attack pattern and moves on to the next.

Gleeok
02-14-2017, 06:01 AM
I think the problem is actually simpler than some of that. Of course, this may just be previous experience with AS talking. We should probably just simply write those scripts as a first test case to get a better top-down view instead of an inside-out one, in any case.

There's an interesting detail you've brought up here though: Conditional scripts. A script shouldn't even be created if it's dead or doesn't meet the necessary requirements. Of course, these would have to be set up in the editor first unless we do one of the following:
1) Expand script metadata so that users can make script objects themselves conditional,
2) Expose conditional variables to scripts.

...Maybe something else?

At any rate you guys can have fun with it. There's probably lots of cool stuff that can be done as it progresses, I'm sure.

DarkDragon
02-18-2017, 01:07 PM
Here's how I'm imagining the system:

Global:


We have a global persistent key/value store for miscellaneous data. This can be used to store data between instantiations of the same object if needed.



Is this really needed as a separate data structure? Or is this functionality already covered by the Game's key/value store?



Objects:


Every object can be interacted with by its class. So a screen object has methods and variables that make sense for Screen, etc. (Like the Screen-> we have now).



Are these fixed by the game engine? Or can the user modify/subclass the existing classes? The former is simpler, of course; are there sufficiently compelling reasons why users may want to define their own object classes (as opposed to script classes, which obviously they want to be able to write)?



Every object has a publicly accessible key/value store. Again, this isn't saved persistently across instantiations of the object. This bit is nice to have, but it isn't particularly important that it's done right away, or at all.



The Screen object, and others that make sense to, may optionally have a persistent data store.



I would think that either the store should always persist, or never persist? Is there a need for this to be case-by-case depending on the object type?
Actually, this raises a confusion I've had for quite a while. For some types of objects, e.g. items, npcs, weapons, etc, there are two types of data associated to that object: there is the "default"/template data that is set globally for all instances of the object, for example in the item or enemy editor, and then there is the data that is *individual* to one particular instance of that object. A enemy script, for example, might want to 1) change the cset of one specific darknut on the current screen (leaving all other darknuts unaffected), or 2) change the cset of all future darknuts that are created in the game (i.e. by changing the default cset of all darknut objects). I assume there needs to be a mechanism for both.
Items have the yet additional wrinkle that instances of the same item can appear on the screen (via a drop or item chest combo, etc), on the active subscreen, on the passive subscreen, and in random other places (all of treasures), and that scripts presumably want control over the cosmetic variables of the item in all of these places.






Each script has a public pointer to its attached object and its own name


Why does it need a name? Can't it store its name in its own variable, if needed?



Scripts can be moved between objects, but can't live on unattached. (Though there will be a global Game object for global scripts.) Every script can have a destructor message for when its attached object dies (possibly letting it move to another object before it disappears.)

Why not let them live unattached? Once could imagine, for example, implementing an item use script for the candle which creates the flame sprite and controls it for a few frames (until the flame dies out). If the candle is removed from the player's inventory during that time (thereby leaving the item use script orphaned) I don't see why the use script shouldn't be allowed to finish its thing, until its use message handler finishes.






Every script has a vod receiveMessage(string message, args...) method which adds a pair<Script, Message> to the global queue. The queue is run through several times a frame - during Waitdraw(), Waitframe(), etc. Basically, any time the main program stops to send the built in messages, we run through the queue first. Messages can be appended to the queue by messages started from the queue, and they will still execute during this time step.
Every script has a public bool hasMessageHandler(string name).
Scripts don't need a key/value store, since they can just make variables as needed.


All makes sense to me.