Lua scripting

Lua scripting
The Lua language itself is documented at www.lua.org. The existing Lua code is quite simple and
would be a good introduction (ignore 'bindings.lua' initially).
I'm hoping to implement much of the "game logic" in Lua eventually: things like score keeping,
weapons, the damage system. All the "fun stuff", and everything which would benefit from being
"easy to edit" to create good gameplay. In part, I'm hoping to build on the success of things like
QuakeC and Unreal script for doing these kinds of things.
How Lua relates to other languages
Lua is a very simple, yet extensible, scripting language. It is garbage collected (a good thing). It has
only one data structure, a "table" (an associative array -- a hash table). Everything is a table. Classes
are tables, objects are tables, namespaces are tables.
One confusing aspect is the use of '.' and ':'. Mostly you use dot for everything EXCEPT when you
call or define a method (code that is considered "part of" an object). If in doubt, try it both ways and
see what works.
I wont repeat material from the Lua tutorials here. Prehaps others are more likely to see what's easy
to understand and what isn't so I will try to stick to Vesta specific technicalities for the moment.
Basics
All scripts in the lua/ subdirectory (NOT src/Lua) are loaded during engine startup. Any errors will
appear in the log (and the console) between the lines "--- Lua starting ---" and "--- Lua started ---".
Currently error handling is rather primitive so it may well crash if there's a syntax error in the Lua
scripts. Check the log for details.
Currently there are 3 lua source files:



bindings.lua: internal things such as Objects, Vectors and the Lua-side interface to the
engine itself. It's heavy going. You probably won't need to edit it unless you're editing on the
C++ side aswell.
weapons.lua: this module replaces the setup_weapon constructs with a scripting
interface. Although slightly more wordy, it is much more flexible than the old system
already, and will only get moreso. The only thing we've (temporarily) lost is the "delay"
parameter.
new_game.lua: this module replaces new_game.script, it handles creating the planet,
asteroid field and random other bits we're testing. It also helps with player creation.

Items.lua: this is a script that randomly places a number of health and ammo crates around
the asteroid belt.

AI.lua: this is a script that governs bot AI, called whenever a bot is created.
You access all the console variables e.g. max_throttle using syntax like var.max_throttle
though you can't assign to them at the moment.
Primitive vector support is included. Create a new vector with Vector(x,y,z) eg:
obj.velocity = Vector(12, -47, 63). Addition, subtraction and scaling by a number
can be done with + - * as expected.
You can print debugging info using print, for example: print("time: ", engine.time)
or equivalently print("time: " .. engine.time) This output will end up in the console
and the logs.
Engine bindings




engine.frame - the current frame number (since the engine started)
engine.time - the current game time (in seconds) since the engine started
engine.sphere_query(centre, radius, solid_only, non_static_only, players_only) - returns
a list of all the objects in the given sphere, subject to the conditions specified. centre must
be a vector. The last three aruments are true/false (setting true restricts the list in the
specified way).
engine.global_query(solid_only, non_static_only, players_only) - like sphere query but
always test ALL the objects in the world. Expensive, so only use it if you need it. Using
engine.global_query(false, false, false) is the same as just looping over
the objects table.
Objects
Objects can be accessed by ID number: objects[23] or by name, if they have one:
objects["Planet"] or objects.Planet. You can use them explicitly like that, or create a
shorter name using local p = objects.Planet for example. New objects can be created by
calling Object("name"). The name need NOT be unique (anymore), if you re-use an existing name
then it will get a numbered suffix, so it will go like: rocket rocket2 rocket3 .... You
shouldn't use numbers directly at the end of object names yourself to avoid interracting badly with
this system. Also all names must now be valid Lua identifiers, which means case sensitive mix of
uppercase, lowercase and underscores, numbers discouraged. Existing objects can be removed using
something like objects.Planet:delete() or p:delete().
Objects have lots of properties which can be read or set directly. Simply access them using the '.'
notation: p.velocity = Vector(0, 0, 0). Current properties are:

id (read-only integer) - The ID number assigned to this object. Mostly for internal use.

name (read-only string) - The name of the object, if any. Unnamed objects give "". Note that
this may not be exactly the name you passed to Object() as it may have a number added
to it to distinguish it from other objects created by the same bit of code.

is_solid (read-only) - You can't set this directly anymore, use geom instead.

is_static (true/FALSE) - If true, the object will not move from it's current positon (but may
still rotate). Static objects are much cheaper for the engine to work with. All the asteroids are
created with this flag on initially.

is_heavy (true/FALSE) - Only used for the planet, marks object as source of gravity.

is_destroyable (TRUE/false) - Destroyable objects will get deleted when they stray outside
the world space. This is used to protect Player objects and the planet.

is_player (read-only true/FALSE) - Is the object a player?

health (integer) - Simple hit point system, not fully functional yet.

pos (Vector) - Current position in world space. (alias: position)

vel (Vector) - Current velocity (alias: velocity)

orientation (Quaternion) - Can't do much with Quaternions in Lua yet, except copy them
between objects: a.orientation = b.orientation. Often this is enough. You can
also set the orientation indirectly (and more intuatively) using the look_at method
documented below. rotate can also be used to change the orientation. All Objects start
with an "identity" orientation, that is they appear with the model's axes aligned with the
world axes.

spin (Vector) - The direction this points in defines an axis to spin around, and the length
gives the rate of spin. (alias: angular_velocity).

scale (Vector) - Gives the amount to scale the mesh associated with this Object (if any). As a
special feature you can specify a single value rather than a vector, so: obj.scale = 42
is a shorthand for obj.scale = Vector(42, 42, 42). This may also affect some
aspects of the particle system. scale is '1' by default.

right_dir, up_dir, forward_dir (read-only Vectors) - These give the directions specified
corresponding to the x, y and -z axis as they are in the model. This is another (more useful)
way to look at the orientation.

mesh (string) - Pass the filename of a mesh to load (in quotes). Specify a .lodmesh if you
want LOD support. Set to nil or "" to remove current mesh.

geom (nil/Sphere/Capsule/Box/Cylinder) - The size and shape of the object, used for
collision detection. If you don't set this the object will be non-solid. Example: obj.geom
= Sphere(50) where 50 is the radius. Similarly we have: Box(xsize, ysize,
zsize), Capsule(radius, length) and Cylinder(radius, length). A
"capsule" is a cylinder with rounded ends. Both cylinders and capsules are always oriented
along the local Z axis. Set geom to nil to disable collision detection. You must also set
collision if you want to be able to react to the collisions in a special way. Setting geom
also implicitly alters the distribution of mass to match the same geometry.

mass - Alters the total mass of the object. The distribution of mass (moment of intertia) is
determined by the geom property.

ps (string) - Pass the Ogre name of a particle system (the name after 'particle' inside the
.particle file, rather than the filename itself).

are_emitters_enabled (TRUE/false) - Resets to true each time you load a new particle
system. Set this to false and the system wont create any new particles, but the old ones wont
suddenly disappear either (which would happen if you set ps = nil. Could also be used
for controlling a beam-type weapon on/off.

anim (read-only string) - Use animate method (see below) to assign a new animation.
This lets you find out what animation is currently playing (if any).

is_anim_looped (read-only true/false) - Determines if the animation is looped.

collision (method) - Set this to an Object method and you will get a callback when the object
collides with something else. E.g.: obj.collision = Object.rocket_hit. (NB:
unlike declaring or calling methods, you use '.' not ':' for this.) Set nil to disable again. The
method will be passed the other object involved in the collision as its only argument.
Method calls
Method calls need to use the colon syntax: obj:method(arg). Within all methods self is a
special pointer to the object itself (equivalent to this in Java, *this in C++).

delete() - Removes the Object completely. You should call this on any Objects you're
finished with.

animate(name, looped) - Pass the name of an animation to play (in quotes) and true or false
to indicate if the animation should be looped.

nextthink(delay, func) - Asks the engine to call another Object method after the given
number of seconds (or fractions of a second) have elapsed. Calling nextthink again replaces
the original request. func should not take any arguments.

look_at(pos, up): turns self until it's facing (has it's -z axis) in the direction of the given
position. Most useful for players where this literally means that pos is in the centre of the
screen. The up vector controls which direction becomes "up" for the object (the +y axis).
NB: this only sets the orientation at the time it is called, the camera will not automatically
"follow" the given position.

rotate(axis, angle): rotates the Object by angle (in degrees) around given axis. Example:
obj:rotate(Vector(1, 0, 0), 60) rotates 60 degrees around x-axis. I'm not sure if it's clockwise
or anti-clockwise. Compare with the 'spin' property which controls dynamically how fast it
spins, whereas rotate just does a one-off rotation.

add_impulse(impulse, point) - an impules (unlike a force) is a short sharp kick to the
object which happens instantaneously. impulse is a vector giving the direction and
magnitude of the impulse, point is the point at which the force acts (if in doubt just pass
the position of the object).

play_sound(filename, gain, loop) - starts a sound playing originating from self. Gain is
volume between 0 and 1. Stereo samples (like music) aren't working, cockpit/global sounds
not accessible yet. Duplicate calls will restart sound. Only one sound per Object currently.
Example: obj:play_sound("data/sounds/explosn.wav", 0.8, false)

stop_sound() - stops any sound currently playing on self
Extending
You can add new attributes to Objects just by using them eg Object.colour or Object.dangerousness
and so on. You can also add new methods using the following declaration: function
Object:method() ..stuff.. end Add arguments if you need them. Normal (non-method)
functions can be declared simply as function name(arg1, arg2) etc.
Entry points

new_game() - Called to create a world for a new game e.g. the planet, asteroids etc.

before_physics() - Called once each frame before physics is run

after_physics() - Called once each frame after physics is run

Object:spawn_player() - Called each time a new player is spawned to select start position,
ship mesh, etc. The player object itself is created by the engine, so you don't need to do a
call like Player("name").

Object:fire(num) - Called on a Player object to get it to fire a particular weapon.

Object:func(other), via obj.collision = func (for various values of 'func') - If you set these
you will get callbacks when your objects collide. Note that if you set a collision function for
both objects they you will get two callbacks: a:func(b) and b:func(a) when they collide.
Inside each func self will be the object which registered the callback (obj above) and
'other' will be the other object.

Object:func(), via obj:nextthink(time, func) (for various values of 'func') - If you set up a
think function via nextthink you will get a callback at the specified time.
Each frame, the calls happen in the following order: before_physics(), any collision
functions, any think function, after_physics(). The actual frame is rendered at the end.
Gotchas
(to be detailed)
= vs ==
.. vs + vs ,
: vs .
[""] vs .
0 "" "0" are all true