Jump to content

Event-driven variants


Recommended Posts

Yeah. It's too complex. Adding new actions and defining it would really be great... However... What about we create something modular? We could have a basic template.variable that would let us copy and tweak it without the need of going too deep in the code. Cutting the hardcoded part would be awesome.

Link to comment
Share on other sites

http://trac.wildfire...com/wiki/Entity This? I have yet to completely figure out how 'entities' and 'actors' relate.

However, I am currently pondering whether it would be feasible to link each variant in an actor to a script somehow and then that script would define the relevant 'behavior' for when the variant should be activated... so you would attach a 'behavior' to a variant, sort of how you attach a material to an actor.

Edited by zoot
Link to comment
Share on other sites

Entities are basically, If I understood right, the attributes of the actor.

Actor is just the visual representation. An Entity is actually the game_object of the actor.

Sounds about right. I think all actors are entities, but not all entities are actors.

Link to comment
Share on other sites

However, I am currently pondering whether it would be feasible to link each variant in an actor to a script somehow and then that script would define the relevant 'behavior' for when the variant should be activated... so you would attach a 'behavior' to a variant, sort of how you attach a material to an actor.

historic_bruno (or some other highly informed individual), do you have any comments on the above? Would it make sense to have an actor file with 'groups' looking something like this:

<group behavior="UnitActivities.js">
<variant name="Idle">
<props>
...
</props>
</variant>
<variant name="Melee">
<props>
...
</props>
</variant>
<variant name="gather_tree">
<props>
...
</props>
</variant>
<variant name="gather_grain">
<props>
...
</props>
</variant>
<variant name="gather_fruit">
<props>
...
</props>
</variant>
</group>

Basically, the script 'UnitActivities.js' would define under which circumstances a new variant would be chosen from the group. The script would be able to hook into the message passing system, just like other simulation scripts, and subscribe to relevant messages like those broadcast when a unit begins gathering or building something. When it receives such a message, it can then pick the right variant from the group, and update the actor accordingly.

Edited by zoot
Link to comment
Share on other sites

Entities are basically, If I understood right, the attributes of the actor.

Actor is just the visual representation. An Entity is actually the game_object of the actor.

I believe the definitions at the top of these pages are still valid:

http://trac.wildfire...wiki/XML.Entity

http://trac.wildfire.../wiki/XML.Actor

IMHO, Groups and variants are much easier to conceptualize (at least for me) in Actor Editor rather than raw code... because it is difficult to wrap your brain around the subsets of various combinations of groups and variants. Check it out if you haven't already done so.

Link to comment
Share on other sites

IMHO, Groups and variants are much easier to conceptualize (at least for me) in Actor Editor rather than raw code... because it is difficult to wrap your brain around the subsets of various combinations of groups and variants. Check it out if you haven't already done so.

I've done a small amount of work on the Actor Editor and like to think that I know what groups and variants are - but I still find that they are too limited in terms of the amount of control the artist has over which variant is chosen when.

Link to comment
Share on other sites

Apparently, it has once been possible to attach scripted event handlers to entity "templates" quite like described above. However, looking through the current templates (e.g. this one), I see no trace of these <Script> and <Event> tags described in the old XML.Entity page.

Does anyone know what has happened with this functionality? Obviously, there must still be some way to specify the behavior of the various objects in the game (like buildings and units).

Edited by zoot
Link to comment
Share on other sites

I believe that is how we originally set up the plan for the architecture of how entities would be structured. That page your looking at was all prior to the simulation rewrite that Philip made several years ago. Most everything that was in the old simulation engine is present today, but some of it hasn't been restored yet. I don't believe that the events you see at the bottom of that "example" at the top of the page was ever fully implemented. It was conceived of by a former instrumental programming lead - Stuart Walpole (aka Acumen). Here is his original spec:

INTRODUCTION

We want to hardcode the bare minimum of game logic into the engine. In designing these XML attributes, we strive to keep it flexible so that designers can easily adjust the nature and behaviour of the game's units and other objects with minimal bugging of overworked programmers.

However, trying to create an XML attribute for every contingency that could ever be required ("BelchesGreenFire=true") is an exercise in futility.

The event/action model uses the flexibility of JavaScript to embed script commands/functions/calls directly into the XML of a game object (entity, cliff, water, terrain, tech, actor, etc), which is then executed when a specific event occurs for an instance of this entity.

This allows custom logic to be written for an entity and encapsulated with its data, instead of having a special XML attribute that flags some logic which we assume to be "handled elsewhere".

Like the GUI, these commands could also call a function written in another file (good for reuse of code) or an engine function.

DANGER, WILL ROBINSON!

Before we describe how events can be used, a couple of warnings:

- Allowing script-based game logic gives modders and developers great power.. But that same power could be used to make 0 A.D. a hacker's paradise. We need to ensure that each player's data in a multiplayer game is sufficiently validated so that a player can't alter the script of his local version to give himself an unfair advantage (eg give himself resources every tick). Client/server architecture, out-of-sync checks, hashing, checksums, we need to use any means necessary to ensure modders have the freedom to adapt game data, while ensuring that players must have the same data version in order to play together.

- Script code isn't as fast as engine code, so lots of resource-intensive script logic could slow the game down. Time will tell exactly how much we can get away with it. But initially, at least, we want to script almost everything. JavaScript at least makes a token effort to efficiency; if the same script is used multiple times, it only has to be parsed and compiled to bytecode once. There are two ways that we can take advantage of this feature to reduce the overhead:


    a] Wrap logic in a function wherever possible and call that, so that it can be called by other entities if needed.
    b] Entities can inherit attributes from each other, including event logic. For example, the behaviour for unit upgrading could be written once, in the generic-citizen-soldier entity. All Citizen Soldiers could then inherit from this entity, gaining the upgrade script automatically. See the XML Header Overview for more info.

- Events should generally be used for one-offs (eg raising/lowering of sails) or complicated exceptions (eg auras). For example, it isn't worth scripting the upgrade system into each Citizen Soldier's XML because so many units make use of it ... that would probably double the size of the entity files. Even wrapped in a function, it means more repetition of the same commands, which all have to be parsed and processed.

CODE SYNTAX

The syntax for an entity event is almost exactly the same as that for a GUI event (eg if this button object is clicked, perform this sequence of instructions):


<event on=<EventName> ![CDATA[
<JS Commands>
]]></event>

CODE EXAMPLE


// When the player "kills" the chest,
<event on="Death" ![CDATA[
// Do a special death animation of the chest opening and coins spiralling into the air.
this.SetAnimation("open_chest",10);
// Transfer the resources it is carrying to the player's Resource Pool.
GiveResources(player, this.supply.type, this.supply.value);
]]></event>

EVENTS

Events are the conditions under which effects occur. An event is true when an entity enters a certain state. Generally the event jumps in just before the actual event takes place. For example, logic written for the Death event occurs just before the unit is about to die.

This list will grow as we implement and hammer down the details of game logic (eg formations, repairing, town bell, AI) ... It's easy for programmers to add additional events, but remember to minimise the amount of script that needs to be executed every frame. For example, it's less costly to check XYZBegin and XYZCancel than check XYZ every frame.

All the relevant data for an event will be copied to a special 'ev' object, allowing easy passing of event information to a script (i.e., for TakesDamage, there'd be ev.inflictor, ev.damage.crush, ev.damage.pierce, ev.range and so on).

Currently planned events are:

  • AuraComplete: When an entity has remained in aura radius for the length of time specified by Aura.Time.
  • AuraEnter: When an entity enters the aura radius of this entity.
  • AuraLeave: When an entity leaves the aura radius of this entity.
  • CreateBegin: Occurs when creation (training/building/researching) of this entity has started.
  • CreateCancel: Occurs when creation (training/building/researching) of this entity has been cancelled by the user.
  • CreateComplete: Occurs when creation (training/building/researching) of this entity has finished.
  • Death: Occurs immediately before the entity is destroyed.
  • GarrisonEmpty: Occurs when all garrison slots are empty.
  • GarrisonEnter: Occurs when an entity is garrisoned in this entity.
  • GarrisonExit: Occurs when an entity is ungarrisoned from this entity.
  • GarrisonFull: Occurs when all garrison slots are occupied.
  • SocketEnter: Occurs when an entity occupies a socket (prop point) in this entity.
  • SocketEmpty: Occurs when all sockets are empty.
  • SocketExit: Occurs when an entity occupies a socket (prop point) in this entity.
  • SocketFull: Occurs when all sockets are occupied.
  • SupplyEmpty: Occurs when all Supply has been gathered from an entity.
  • SupplyFull: Occurs when an entity has reached its maximum Supply.
  • HoverStart: Occurs when the player places the cursor over the entity.
  • HoverStop: Occurs when the player moves the cursor off of the entity.
  • MouseDown: Occurs when the player clicks on the entity.
  • MouseUp: Occurs when the player releases the mouse button, or moves the cursor off the entity.
  • Initialise: Occurs when the entity first enters the world.
  • LOSEnter: When an entity enters the sight radius of this entity.
  • LOSLeave: When an entity leaves the sight radius of this entity.
  • OrderBegin: Occurs when the entity has been given a command and is about to start carrying it out.
  • OrderCancel: Occurs when the user cancels the order.
  • OrderComplete: Occurs when the entity has finished an order.
  • Tick: Occurs every simulation frame; currently 10 Hz unless this framerate cannot be achieved. JavaScript isn't particularly efficient, so while this is a very powerful feature, it's almost always better to add an additional event to this list than constantly scan for a condition.
  • WalkOver: Occurs when an entity moves over the surface of it. Typically only used by terrains.

Here is something Philip wrote in the archives a long time ago - so maybe they did exist?:

As a (probably inaccurate) rough overview of what I think needs to be done:

template_entity_script.js:

Add a function entity_event_heal, similar to entity_event_gather but without the resources or destruction - probably just something like "evt.target.traits.health.curr += this.actions.heal.amount". (Or do a 'console.write("It works!")' first, so you can tell when everything else is working.)

((The entity documentation says resources should be deducted when healing; but I think it'd be best to ignore that until the basics are working.))

template_entity_full.xml:

Add <Event On="Heal" Function="entity_event_heal"/> line so it knows what function to call in response to the 'heal' event.

EventHandlers.h/cpp:

Add a CEventHeal, similar to CEventGather.

EntityStateProcessing.cpp:

Add CEntity::processHeal and CEntity::processHealNoPathing, similar to CEntity::processGather[NoPathing].

Plus associated bits - ORDER_HEAL in EntityOrders.h, and a "SEntityAction m_heal" in Entity.h. And the "actions.heal.amount" property (as used by the JS) needs to be loaded from the XML - add an AddProperty in CEntity::CEntity (similar to where it loads gather/attack speeds, though healing only needs a single 'amount' (er... 'speed'?) property).

Entity.cpp:

(around line 330) Call processHeal[NoPathing], similar to processGather[NoPathing].

I can't currently see anything else that needs to be done, although I imagine I've missed some things and lots of details (like how a player actually selects the 'heal' action)... Anyway, I think it's very similar to the existing code for 'gather' and also partly 'attack', since they are all one-entity-walks-up-to-other-entity-and-alters-its-stats-periodically kinds of things, so much of the code can be copied from there.

Incidentally, the above description has a worringly low ratio of English words to code identifier names and acronyms. Hopefully it's a little less confusing after looking at the relevant bits of code, but I'll be happy to offer any possible assistance in trying to work out what it all means (or at least make up something that sounds convincing, since I don't myself know how it all works tongue.gif)

More stuff from Andrew (aka pyrolink)"

Feel free to add to or correct this.

EventHandlers.h/.cpp

Entity.h - SEntityAction, processor

Entity.cpp - update() switch statement processor, addproperty, order()

switch statement

EntityOrders.h

EventTypes.h

template_entity_full.XML

template_entity_script.js

AllNetMessages.h

NetMessage.cpp

GameSetup.cpp

Simulation.cpp

BaseEntity.h/.cpp

1. Create the network message class

* in AllNetMessages.h, add an enum value just before

NMT_COMMAND_LAST

* add a message class - use one of the existing command

messages as a template, and change the fields as appropriate

2. Export the message ID to Javascript by adding a line in

CNetMessage::ScriptingInit

3. Add a message parser in CNetMessage::CommandFromJSArgs. If the

message is like the other message types listed there, i.e. it only has a target entity or a target position as the message data, just use the PositionMessage or EntityMessage macros.

4. Add a switch case in CSimulation::TranslateMessage to map the

network message (NMT_Heal) to an entity order (ORDER_HEAL).

This routine also uses macros similar to the CommandFromJSArgs macros.

An entity is essentially any interactive being in the game. For example, resources, units, buildings, are all entities. Entity.h contains the CEntity, which is made up of the things that constitute a particular entity. The first thing you'll need to know is that SEntityAction is a structure which defines the parameters of an action, such as speed, minimum range, and maximum range. These are loaded through an AddProperty() call in the constructor in Entity.cpp. EntityOrders.h contains a list of orders for an entity to process. No Pathing means that the unit has reached its destination and no longer needs to find a path. I'm not certain where these orders are given from. The entity's update function (defined in Entity.cpp) finds the correct processing function depending on the order type. If the function returns false, the update function returns and does not process any more orders. A processing function (prototype) needs to be added to Entity.h. These are defined in EntityStateProcessing.cpp.

The basic processing function will call processContactAction(). This function will attempt to find a path (if needed) to the target (which would be what the player right clicked, e.g. for gathering it would be the resource). One of the parameters to the processContactAction is an order, or more precisely transistion order. This is the order that will be processed when the entity reaches the position. If you'll recall, "NoPathing" means that the unit has reached its destination and no longer needs to pathfind. When this message is processed, it will call process<whatever action>NoPathing(). In this function, processContactActionNoPathing() is called, but first, an event must be defined.

Events are linked with the JS (JavaScript) interface, which I'm not exactly sure how it works. EventTypes.h contains the types of events, as well as the strings corresponding to them. EventHandlers.h / .cpp are the objects created in the NoPathing processing functions. You'll notice that in the constructor for each event, it calls CScriptEvent() with an event type, and a string. CScriptEvent() seems to create an event that is part of the JS code. Inside the EventHandler, it assigns the relevant variables, and then calls AddLocalProperty(), which apparently makes it visible to that JS code. So now you have an event sitting around, somewhere. When processContactActionNoPathing() gets called, if the entity is in range (that is, it hasn't moved since it was), the event that we created will be sent to the JS handling code through dispatch event().

Now where is this JS handling done? It takes place in template_entity_script.js. How does it know what to call? In template_entity_full.xml, there are statements like: <Event On="PrepareOrder" Function="entity_event_prepareorder" />. The function defined in the Function = part of which ever statement like the above that has the On equal to the first argument to the CScriptEvent() call (the string of text) will be called. That sounded complex, but it isn't. Basically if On= CScriptEvent() 's first parameter, it calls the corresponding function.

For information on entity traits such as health, player resources, and so on, go here: http://www.wildfiregames.com/users/code/wi...itle=XML.Entity

Link to comment
Share on other sites

Thanks. I wonder if the simulation rewrite introduced the message passing system? (This 3 years old edit would suggest that it did.) That might explain why events were dropped - the message passing system superseded the old events system.

However, I'm still clueless as to how entities are coupled to game logic (like "if an arrow hits you, lose health") or even where that logic is defined/coded.

Edit: This is vaguely beginning to make sense ;) At least part of the logic is defined here.

Edited by zoot
Link to comment
Share on other sites

All of the javascript components are where you linked to. There are some C++ components in source/simulation2/components/. So if an entity has <Footprint>...</Footprint> in the xml if will have the Footprint component loaded for it. The C++ simulation system handles all of this so you can pretty much ignore how it works in detail for most purposes.

Link to comment
Share on other sites

All of the javascript components are where you linked to. There are some C++ components in source/simulation2/components/. So if an entity has <Footprint>...</Footprint> in the xml if will have the Footprint component loaded for it. The C++ simulation system handles all of this so you can pretty much ignore how it works in detail for most purposes.

Excellent, that was the main piece of the puzzle I seemed to be missing. Thanks.

Link to comment
Share on other sites

Again, http://svn.wildfireg...components.html mentions pretty much everything being asked and unlike half the stuff mentioned here, it's actually up-to-date. It's not the "friendliest" document to read, but it works.

I don't actually see any explicit mention of the mapping between XML elements and component names that quantumstate described, though I suppose someone smart enough might infer the relation from the way the name 'Example' is carried through the document.

Edited by zoot
Link to comment
Share on other sites

How about this:

I define a new component interface for "variant events". Any component (e.g. UnitAI) may implement this interface. It has only one simple method that allows other components to subscribe to "variant events" emitted by it. Components that wish to subscribe to such events must in turn implement a counterpart callback interface that I will also define.

The <group> element in actor XML will take a 'controller' attribute. The value of the controller attribute must be a valid name of a component on the entity which the actor is associated with (e.g. "UnitAI"). When the actor is loaded/created/instantiated, it will invoke, on the component named in the 'controller' attribute, the method to subscribe to variant events emitted by the component.

Variant events are simple string messages like "GatherFruit". When the actor receives an emitted variant event, it searches child <variant> elements of the <group> element for ones that have the variant event message in their 'name' attribute. If a match is found, that variant will be chosen for the actor and its rendering updated accordingly.

Would that make sense?

The main improvement over the old approach is that it is cleaner - the 'name' attribute of variants become unambiguous. That will allow me to implement new "variant events" for civilization phases and destruction levels in good conscience :)

Edit: Instead of defining a new interface, I could also use the message passing system.

Edited by zoot
Link to comment
Share on other sites

Edit: Instead of defining a new interface, I could also use the message passing system.

Yeah, why not create one or two new message types and have CmpVisualActor subscribe to them. The message data could possibly be a list of variant strings, this is assuming the variant system gets "upgraded" to support applying only one variant at a time instead of replacing the entire list. The only thing to avoid is using model data to affect the simulation but it doesn't sound like you're wanting to do that.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...