Tremfusion.net

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:

  1. Check if you are alien
  2. Check if you are alive
  3. Check if overmind up
  4. Check if have enough evo
  5. Check if class is available this stage
  6. Initialize numeric data about evolution (how many evo cost the class ?)
  7. Take evo to player
  8. Set new class to player
  9. 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:

  1. Begin "check" group
  2. Check if you are alien
  3. Check if you are alive
  4. Check if overmind up
  5. Check if have enough evo
  6. Check if class is available this stage
  7. End "check" group
  8. Begin "modifier" group
  9. Initialize numeric data about evolution
  10. End "modifier" group
  11. Begin "action" group
  12. Set new class to player
  13. End "action" group
  14. 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

Schema.png

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