Scripting guide
From TremFusion
/!\ /!\ /!\ UNFINISHED DOCUMENT /!\ /!\ /!\
Contents |
Bases
Scripting organisation
Files organisation:
- game.qvm: lua enabled by default, python not available
- game.so: lua enabled by default, python enabled by default
- ui.qvm: lua enabled by default, python not available
- ui.so: lua enabled by default, python not available
Source code structure:
- src/lua/* src/lua/lua.h -- LUA interpreter
- src/scripting/* src/scripting/scripts.h -- Generic scripting layer for python and lua
- src/game/g_script_*.c src/game/g_script.h -- Script access to game data & functions
- src/ui/ui_script_*.c src/ui/ui_script.h -- Script access to ui data & functions
- src/python : python includes and some python specific code
Game/ui call scripts by calling a function in src/scripting/scripts.h, which can then access game/ui using the*_script_*.c files. All of the above are included in a qvm/so.
What the scripter see
Vocabulary
- event: instant in the game, caused by a specific action.
- hook: a function linked to an event, ordered in the hook list.
- hooks chain: ordered list, containing all hooks defined in an event. Hook chain is readed during event's call.
- group: groupement of adjoining hooks.
- tag: name given to a hook or to a hooks group.
- module: script containing a list of hooks on different actions. hooks are linked during the module load, and are unlinked during the module unload.
- event data: Data structure created during the begining of each hook chain reading.
Hooks
A hook is a function called during a specific event. (For exemple, to run a code when a player die, add a hook on "playerDie" event)
It define a part of code used to process the event.
Hook make be linked by:
- C langage
- Scripting langages (Lua, Python)
A hook can be:
- A check to continue or stop the event reading (on "onEvolve", game have to check if you have enough evo to evolve)
- An action (on "onPlayerDie" event, game have to increase killer's money)
- An action modifier (on "onDamage" event, before making damages, game have to decrease damages if players have an armor)
Hook chain
A hook chain is an ordered list containing all hooks linked to an event.
So, for a game only (without any scripts) "onEvolve" event, the hook chain has there hooks:
- Check if you are alien
- Check if you are alive
- Check if overmind up
- Check if have enough evo
- Check if class is available this stage
- Initialize numeric data about evolution (how many evo cost the class ?)
- Take evo to player
- Set new class to player
- Add a log entry
So, we can define a hook chain containing each hooks. Each hooks will be called one by one.
Event data
The event data is a hash-table forwarded readable by each hooks.
Each hooks are able to read and write to the event data.
For exemple, a "onDamage" has a "damage" entry in its event data. Hooks are able to modify damages before the damage action to increase or decrease damages deals during the event issue.
Event data also contain all data about event triggering (exemple, during a "onEvolve" event, contains entity who try to evolve)
Groups
To add a hook in the chain, you must be able to locate different kind of hooks in the chain : in you want to double the cost to evolve, you need to be sure to not execute your "double cost" hook before the hook 7. where you take evos to player, or your new hook will be useless.
So, hooks are organized. In the exemple, 5 first hooks are checks. Hook 6 initialize data used by following actions. Hooks 7 and 8 are main actions. Hook 9 is a post-action hook.
Let's add, in the hook chain, some groups to organize rules.
- A "check" group
- A "modifier" group (with also initialization)
- An "action" group
Each group has a "begin" entry and "end" entry. Hooks located between begin entry and end entry are member of this group.
So, new chain:
- Begin "check" group
- Check if you are alien
- Check if you are alive
- Check if overmind up
- Check if have enough evo
- Check if class is available this stage
- End "check" group
- Begin "modifier" group
- Initialize numeric data about evolution
- End "modifier" group
- Begin "action" group
- Set new class to player
- End "action" group
- Add a log entry
Tags
To locate hooks and groups, you need to name it during their creation. A "tag" is a name, it can reference a hook, or a group. A tag must be unique, or generate a conflict.
Localisation in hooks chain
To add a new hook, you need to locate the location for your new hook in the chain.
To locate it, you need to use groups, and other hooks. Then, you will be able to locate your new hook. So, there is three kind of localisation :
- after: add the new hook after the hook or group referenced by given tag.
- before: add the new hook before the hook or group referenced by given tag.
- in: add the new hook somewhere in the group referenced by given tag.
So, 'after' and 'before' can be used for a hook tag or a group tag. 'in' can be used only for a group tag (a hook is a singleton, it can't be splitted)
Ignore some hooks
During a hook execution, you are able to ignore a hook. Give simply one or more "ignore" instruction with a hook tag or a group tag, and hook (or hooks in the group) will be simply ignored and will not executed.
Modules
A module is a hook set. A module can be loaded or unloaded with a simple command. When a script is loaded, it can register new commands, link new hooks, etc... When a script is unloaded, it must drop all registeration or linked it did during loading.
So, a module make it easy to add features to a server and remove it.
Scripting langages concepts
Basics
Basic concepts used between C API and Scripting langages:
Basic (atomic) types:
- Integer
- Float
- String
Compounded types:
- Scalar:
- Definition: an Integer, or a Float, or a String
- Array:
- Definition: ordered and Integer-referenced list of Scalar
- Hash table:
- Definition: non-ordered and String-referenced list of Scalar
- Value:
- Definition: a Scalar, or an Array, or a Hash
- Type:
- Definition: enum of String containing: "Integer", "Float", "String", "Array", "Hash", "Scalar", "Value"
- Namespace:
- Definition: a String Array
- Function:
- Definition: defined by a language (a String), a name (a Namespace and a String), and arguments (a Type Array). containing code
- Global variable
- Definition: defined by a name (a Namespace and a String). containing a Value
- TODO: Module definition, need something ?
Scripting langages needs at C-side:
- Functions:
- Register/Call a C-side definded function in scripting langage
- Register/Call to C-side a scripting langage definded function
- Global variable:
- Get the global variable
- Set the global variable
- Namespace:
- Get list of entry in a namespace
- Go up/down in namespace hierarchy
- Get a function/global variable from a namespace
Implementation (C side)
Events
Data struture
Event
An event is implemented with a Tree with groups as nodes and hooks as leafs.
Node
A node of event tree can be definded by a group, or a hook, definded by a tag. There is two kind of nodes:
- Nodes associated with a group.
- Nodes associated to a hook (it's leaf).
A node has three entry. Each entry has a contains new sub-tree.
- Hooks to run before instant.
- Hooks to run during instant.
- Hooks to run after instant.
Group
A group is only a node of event tree
Hook
A hook is a leaf of event tree. A hook is definded by a name, and is associated with a function.
Tag, placement
According to API, it define tag:
- If hooks linked with a tag.after function, hook is added in 'before' node entry
- If hooks linked with a tag.in function, hook is added in 'in' node entry
- If hooks linked with a tag.before function, hook is added in 'after' node entry
Skip event
A script can ask to skip all hooks located in current group (not 'after' and 'before', only 'in')
Temporary Hooks
It possible to add temporary hooks : this hook will be automaticly deleted at the end of event processing.
Ignore Hooks
To ignore hooks, must add a temporary hooks in node you want to ignore. This hook is an unconditional skip event. So, node will be skipped.
Full schema of data struct
The scripting API
/!\ Scripting API outdated /!\
So, in scripting API, both scripting langages and C will be able to add rules to chain. At the begining, chain is allready empty.
Then, it's initialized : we add the two tags
chain.NewTag("check", chain.head);
chain.NewTag("action", chain.after("check"));
Then, we can add rules:
chain.NewRule(isAlien, chain.in("check"));
chain.NewRule(isAlive, chain.in("check"));
chain.NewRule(isOvermindUp, chain.in("check"));
chain.NewRule(haveEnoughEvo, chain.in("check"));
chain.NewRule(classIsAvailable, chain.in("check"));
chain.NewRule(setNewClass, chain.in("act"));
chain.NewRule(addLogEntry, chain.after("act"));
Basically, that's all. Now.... Is it really enough to make a real scripting engine ? If I want add a "freefunds" rule, (can evolve/devolve without money), can I do it ? Probably not So, we need maybe split "check" tag in two tags, or make subtags. it can be something like:
chain.NewTag("check", chain.head);
chain.NewTag("alwaysCheck", chain.in("check"));
chain.NewTag("gameCheck", chain.after("alwaysCheck"));
chain.NewTag("action", chain.after("check"));
chain.NewRule(isAlien, chain.in("alwaysCheck"));
chain.NewRule(isAlive, chain.in("alwaysCheck"));
chain.NewRule(isOvermindUp, chain.in("gameCheck"));
chain.NewRule(haveEnoughEvo, chain.in("gameCheck"));
chain.NewRule(classIsAvailable, chain.in("gameCheck"));
chain.NewRule(setNewClass, chain.in("act"));
chain.NewRule(addLogEntry, chain.after("act"));
Ignore rules, miss tags
Now, to add our "freefunds" rule (you can evolve/devolve without money), we have only to ignore all rules in "gameCheck" tag. So, we will able to call a function:
ignoreTags(chain, "gameCheck");
Now, to add a "freefunds" rule, we can make a script like this:
chain.NewRule(function () ignoreTags(chain, "gameCheck") end, chain.after("alwaysCheck"));
Then, new freefunds rule is before "gameCheck" rules, and it have to instruction to ignore gameCheck rules, so... you will always evolve when "alwaysCheck" rules are ok.
So, the reponsability rules is a powerfull scripting engine, I think it's possible to make most part of scripts with it. A problem is following : it's maybe a little hard to understand. So, next challenge is to make simple API using responsability rules system to script game easly !
Make new events
It must be possible to make new events.
For exemple, we want implement a sudden death. (remember, sudden death must not is part of tremulous kernel !) We will add an event in game initialisation rules.
game.init.NewRule(initSuddenDeath, game.init.in("act"));
Now, we must create a new event : suddenDeath event. So, scripters will be able to make new events during suddenDeath.
initSuddenDeath source code may be following:
function initSuddenDeath ()
newEvent("think", game.suddendeath);
newEvent("start", game.suddendeath);
game.suddenDeath.think.NewTag("check", game.suddenDeath.think.head);
game.suddenDeath.think.NewTag("act", game.suddenDeath.think.after("check");
game.suddenDeath.start.NewTag("check", game.suddenDeath.start.head);
game.suddenDeath.start.NewTag("act", game.suddenDeath.start.after("check");
game.suddenDeath.start.NewRule(checkSuddenDeathBegin, game.suddenDeath.start.in("check"));
game.suddenDeath.start.NewRule(doSuddenDeath, game.suddenDeath.start.in("act"));
-- if g_suddenDeath is 0, return false, so it's break the chain
game.suddenDeath.think.NewRule(function () return g_suddenDeath end, game.suddenDeath.think.in("check"));
-- currently, it's useless to add act rules on think event on suddendeath, but scripters may need it, so we may make it for us :)
end
function checkSuddenDeathBegin ()
if game.suddenDeath.isSuddenDeath == 1 then
return false
else
return g_suddenDeath == 1 or g_suddenDeathTime <= game.time
end
end
function doSuddenDeath ()
game.suddenDeath.isSuddenDeath = 1;
-- Always refuse to build when it's SD
game.buildable.build.NewRule(function () return false end , game.buildable.build.in("gameCheck"))
end
