 COB Overview
What is COB?

COB is the Spring engine's scripting system. Each unit has a .cob bitfile which defines many of its major behaviors, including animation and weapon aiming. Of course, COB is also capable of doing a lot more if you wish. When Spring is actually running, it interprets the bitfile for each unit as necessary. This interpreter runs in a very straightforward way, without any fancy optimizations, so don't expect great performance from it.
BOS

The code that gets compiled into a .cob is written in a .bos file. A program called Scriptor generally does this compilation. BOS uses a C-like syntax, although it has many Spring-specific features and isn't as powerful as C.
BOS versus C

Similarities:

    * Function syntax. A function is defined by FunctionName(args).
    * Block delimiters. Like C, a statement block is delimited by curly braces ({}).
    * General syntax. For example, to add two variables and assign the result to a third, you say x = y + z; 

Differences:

    * Not case sensitive.
    * Only one data type. There is only one type of data in COB, which is basically a 32-bit integer.
    * Variable declaration. 

You can declare a static variable, whose scope encompasses the entire script, using

static-var x;

You can declare multiple variables at once by separating them with commas.

Alternately, you can declare a local variable:

var x;

The scope is limited to the function that it is in.

    * No pointers or arrays.
    * No internal return values. When the script receives a callin, it may return a value to the "outside"; however, there is no way to return a value directly to another function within the script. You will have to use a static variable.
    * Function calls. 

Functions are called using

call-script Function(args);

or

start-script Function(args);

See COB_Threads14 for details.

    * Multiple threads. This isn't as exciting as it sounds. Again, see COB_Threads15 for details.
    * Spring-specific features. There are too many here to name; please consult the other COB articles. 

Retrieved from "http://spring.clan-sy.com/wiki/COB_Overview16"

This page has been accessed 141 times. This page was last modified 05:49, 28 May 2008.



 Pieces

Each unit has a root piece and a tree of other pieces attached to the root piece. At the top of each file you'll need to put the following line:

piece [names of pieces of the model separated by commas];

As usual, capitalization does not matter; neither does the order of the pieces. You do not need to name every piece that the model actually has, although you should do so, and you cannot do anything to a piece that you have not named. If you try to do something to a piece that doesn't exist, Spring will throw an error. Generally it will not crash but you'll probably get lots of chat spam, so avoid this.

Every piece starts in the position as it appears in any modeling program, such as Upspring. This is defined to be zero position and zero rotation.

Pieces are really numbers, although Scriptor usually throws a fit if you try to use them as numbers (it will not accept a number in places where it expects a piece name, although for functions that involve a piecenum you can use the piece's number instead of the piece name if you so desire). The first piece you list is 0, and it counts from there.
Linear Units

Everything linear really happens in a unit I'll call Spring distance (sd). This is a tiny distance--there are 216 = 65536 sd in an elmo, which is the unit that is most commonly used. For example, weapon ranges are specified in elmos. Fortunately, Scriptor can automatically convert elmos to sd: just put the number of elmos you want in brackets ([]). Sometimes you'll see scripts that use 2.5 * 216 = 163840 instead. I recommend not doing this since it makes conversion more difficult.
Angular Units

Likewise, everything angular really happens in a unit I'll call Spring angle (sa). There are 216 sa in a full circle. Generally you'll be working in degrees. There are 182 and 2/45 sa in a degree. Again, Scriptor can automatically convert from degrees to sa; just put the number of degrees you want in angles (<>).
Axes

All COB animation statements specify an axis. There are three axes, "x-axis", "y-axis", and "z-axis". Positive x is to the unit's left (the right if you are looking at the unit from the front). Positive y is to the unit's top. Positive z is to the unit's front.

The position and rotation of each piece defines the coordinate system for all of its child pieces. So, if you move or rotate a piece, all of its child pieces will move and rotate with it.

A positive angle is counterclockwise looking at the piece from the positive end of the axis toward the negative end of the axis.
move

move [piece] to [axis] [distance] now;

This moves the piece immediately. The piece will be in its new position along the axis before the next statement is executed.

move [piece] to [axis] [distance] speed [speed];

This moves the piece at some speed until it reaches its destination, or the piece is told to move to a different distance along the same axis, whichever comes first. After this statement, the piece moves independent of the script; indeed, the script proceeds immediately to the next statement after it sets the piece moving.

Speed is always positive, even if you are moving the piece to a negative distance. It is measured in Spring distance/second (sd/s).

wait-for-move [piece] along [axis];

Makes the thread sleep until the piece stops moving along the axis.
turn

These are similar to movement in behavior, except they rotate the piece.

turn [piece] to [axis] [angle] now;

Turns the piece to the specified angle around the axis immediately. The piece will be in its new rotation around the axis before the next statement is executed. Pieces rotate around their own center, not their parent's (although if the parent rotates, they do as well). If the piece moves, the center moves with it, so a piece's movement occurs "before" its rotation in determining its final position.

turn [piece] to [axis] [angle] speed [angular speed];

This turns the piece at some angular speed until it reaches its destination, or the piece is told to turn to a different distance around the same axis, whichever comes first. Angular speed is always positive, even if the angle is negative. The speed is in Spring angles/second (sa/s).

wait-for-turn [piece] around [axis];

Makes the thread sleep until the piece stops turning around the axis.
spin

spin [piece] around [axis] speed [angular speed];

This starts the piece spinning at a constant speed immediately. Again, the speed is in Spring angles/second (sa/s); however, it can be negative if you want it to spin in that direction.

spin [piece] around [axis] speed [angular speed] accelerate [angular acceleration];

The angular acceleration should always be positive, even if you are spinning the piece at a negative angular speed. It is measured in Spring angles per frame-second (sa/f*s); in other words, the speed of the piece will increase by that amount every frame, not every second. There are 30 frames per second.

stop-spin [piece] around [axis];

Stops the piece from spinning immediately, regardless of its orientation.

stop-spin [piece] around [axis] decelerate [angular deceleration];

Slows the piece's spinning down until it stops. As with angular acceleration, the angular deceleration should always be positive and is measured in Spring angles per frame-second.





Some (but not all) are listed near the end of this file: https://spring.clan-sy.com/svn/spring/trunk/rts/Sim/Units/COB/CobFile.cpp24
Purpose

Callins have two purposes:

First, they allow a script to be notified when an event happens, such as the unit being created or destroyed.

Second, they allow the engine to get information from the script. Sometimes this is accomplished through a return value, but usually this is done by setting one of the arguments to a function to the requested value.
Callins
General

    * Create(): Called when the unit is created. "Created" means when the unit's nanoframe is created; if you want to wait until the unit is actually finished, you'll have to use something like 

while(GET BUILD_PERCENT_LEFT) sleep 250; 

    * Activate()/Deactivate(): Called when the unit is turned on/off.
    * Killed(severity, corpsetype): Called when the unit is killed. Severity indicates how many percent of the unit's hit points were dealt by the final blow. Corpsetype basically indicates the number of times the unit and its corpses are destroyed; for example, corpsetype = 3 acts as if the unit is destroyed, the corpse is destroyed, and the corpse's corpse is destroyed. If it runs out of corpses nothing is left behind. Set corpsetype to the desired value. 

Movement

    * StartMoving()/StopMoving(): Called when the unit starts/stops moving. This includes rotation, so don't be surprised if StartMoving() sometimes gets called when a unit starts aiming at a target.
    * SetSFXOccupy(terraintype): Supposedly called when the unit changes terrain type, although it didn't work last time I tried it? Maybe I was using it wrong. terraintype: 0 = transported, 1 = sea, 2 = deep sea (greater than 5 water depth and unit is upright), 4 = land.
    * MoveRate[0...3](): Appears to be the aircraft equivalent of SetSFXOccupy. MoveRate3 doesn't appear to actually be used. MoveRate1 is called when the aircraft takes off or lands. If the aircraft is not taking off or landing, MoveRate0 is called when the aircraft drops below 1/3 maximum speed, MoveRate2 is called when the aircraft exceeds 2/3 maximum speed, and MoveRate1 is called when the aircraft enters within 1/3-2/3 maximum speed. I don't know if this actually works.
    * SetDirection(heading): Used only for wind generators. Gives the direction of the wind.
    * SetSpeed(speed): Used for wind generators, tidals, and mexes. Basically more resources = higher speed. 

Weapons

For weapons, X means either the number of the weapon, or "Primary", "Secondary", or "Tertiary" (don't put "Weapon" for these). I recommend using numbers, since it's cleaner and you can use more than three weapons that way.

    * AimWeaponX(heading, pitch): Called every ~0.5s when the unit is aiming the weapon at a target. This is used to turn a unit's turrets toward the target. Return 1 when the weapon is ready to fire. Alternately, return 0 if you don't want the weapon to fire (you can use this to disable weapons).
    * AimFromWeaponX(piecenum): You should set piecenum to a piece of the model; LOS is traced from the here to the target to see if the weapon can fire.
    * FireWeaponX(): Called whenever the trigger is pulled on a weapon. This occurs before the first shot in a burst/projectile salvo (only).
    * ShotX(): Called before every shot a weapon fires. This includes burst and projectile tags. This happens after FireWeaponX(), even for the first shot.
    * QueryWeaponX(piecenum): You should set piecenum to a piece of the model. The shot starts from this piece of the model; this is called for this purpose after FireWeaponX() and ShotX(). LOS is also traced from here to the target to see if a weapon can fire. Make sure this is a piece on the weapon's turret. Not doing this right can cause headaches; example: http://trac.caspring.org/wiki/GunsJammingOnShips25
    * EndBurstX(): Called after the last shot in a burst.
    * BlockShotX(unitID, block)
    * TargetWeightX(unitID, weightMod)
    * RockUnit(anglex, anglez): Called when the unit fires. Use it to rock the unit on firing, although there are other ways to do this.
    * HitByWeaponID(damage, weaponid?, unitid?): Called when the unit is hit by a weapon.
    * SetMaxReloadTime(time): Called on unit creation. time is the time in ms that it takes the slowest-firing weapon to reload. 

Construction

    * StartBuilding(): Called when the unit starts building.
    * StopBuilding(): Called when the unit stops building.
    * QueryNanoPiece(piecenum): You should set piecenum to the piece you want the nanospray to come from.
    * QueryBuildInfo(piecenum): For factories. You should set piecenum to the piece you want the unit to be built on. 

Transportation

    * Falling(): Used for airdropping units?
    * Landed(): Used for airdropping units?
    * QueryTransport(piecenum): You should set piecenum to the piece you want the transported unit to be attached to.
    * BeginTransport(): Called when an air transport picks up a unit.
    * EndTransport(): Called when an air transport becomes empty.
    * TransportPickup(): Called when a land transport picks up a unit.
    * TransportDrop(): Called when a land transport becomes empty.
    * StartUnload(): Called when the transport starts the unloading process. 

Landing Pads

    * QueryLandingPadCount(count): You should set count to the number of landing pads. However, it doesn't seem to be actually necessary to do this unless you want more than 16 pads.
    * QueryLandingPad(piecenum1, piecenum2, piecenum3...): Set each piecenum to a different landing pad piece. 





 Source

This has a list of all COB SET/GET keys: https://spring.clan-sy.com/svn/spring/trunk/rts/Sim/Units/COB/CobInstance.cpp21

You'll want to include a file with these constants in your script.
SET/GET
Use

Use GET [tag] to get a value. Some GETs refer to functions; in this case, use GET [tag](arg1, arg2, arg3, arg4). Use SET [tag] to [value] to change a value.
Tags

There's a lot of GET/SET tags; I'll cover some of the more useful but less intuitive ones.

    * ACTIVATION: Whether the unit is on (true) or off (false).
    * ARMORED: When true, any damage dealt to the unit will be multiplied by its damageModifier.
    * PRINT: You can use GET PRINT to send up to 4 arguments to chat. Useful for debugging.
    * VETERAN_LEVEL: The unit's experience times 100. Note that you can SET this via script, although the unit won't gain any extra health from doing so. 

emit-sfx

emit-sfx [sfxtype] from [piece];

The least significant byte of sfxtype is treated as an unsigned integer; the remaining bits are a bitmask. The bitmask indicates what type of sfx to emit, while the number indicates which sfx of that type to emit. The bits are as follows:

    * 256: Point-based fx. Used for white smoke (1), black smoke (2), and bubbles (3).
    * 1024: Unit sfx. Use with the unit's sfxtypes.explosionGenerators. The first one is 0, the second one is 1, etc.
    * 2048: Fire weapon. Fires a weapon from the piece (no aiming involved). Again, the first weapon is 0, the second weapon is 1, etc.
    * 4096: Explode weapon. Detonates a weapon at the piece. Again, the first weapon is 0, the second weapon is 1, etc. 

explode

Causes the piece to explode and possibly go flying. This actually gives the appearance of making a copy of the piece (henceforth referred to as "debris") so if you're using it outside of Killed() you may want to hide the piece in question.

explode [piece] type [exptype];

exptype is a bitmask. The bits are as follows:

    * SHATTER: Explodes the debris into tiny triangles. Looks like confetti.
    * EXPLODE_ON_HIT: Debris will explode on impact, causing about 40 damage.
    * FALL: Debris is affected by gravity.
    * SMOKE
    * FIRE
    * BITMAPONLY: No debris, just an explosion graphic.
    * NOCEGTRAIL
    * BITMAP[1...5]: Doesn't actually appear to do anything. 




 Source

https://spring.clan-sy.com/svn/spring/trunk/rts/Sim/Units/COB/CobThread.cpp23
Introduction

Much of the time you can expect your script to execute sequentially; that is, looking through your code, the system progresses from statement to statement without anything "strange" (variables being changed, threads being started, etc.) between them. HOWEVER, "strange" things do happen, and in fact they happen quite regularly in scripts. We say that the script system has "concurrency" when things can happen between statements.
Why Concurrency?

So why go to all this trouble with concurrency? The script system has concurrency because we never know when things are going to happen. The player could give a command to the unit, the unit could run into a wall, rocks could fall out of the sky and kill the unit, and so forth. Without concurrency we would have to check very often whether which of all possible events had happened recently in order to determine what to do next. This would suck.
Dealing With Concurrency

Every time a callin is called or a start-script statement is executed, a "thread" is created. At any one time, only one thread is being executed at a time, but sometimes a thread will stop executing for a while and continue later, while several other threads will execute in the meantime. Most of the time your script is guaranteed to execute sequentially. If you say x = 1;, it will stay 1 unless you change it again. The exceptions are as follows:

    * Before a script starts.
    * After a wait-for-move or wait-for-turn statement.
    * After a sleep statement. 

During these times, any other thread has the ability to come in and change all your static variables, tell your pieces to move, turn, and spin, and so forth. Therefore, you cannot always assume these things will be the way you left them when execution of the thread resumes. If it is possible that another thread will change something that you need to know about, you need to check it after any of these times.
Sleeping

You can have a thread sleep by using a sleep statement:

sleep [time in ms];

This is useful for delaying events and in making loops execute every so often. If you want to wait until a piece moves into position, use wait-for-move or wait-for-turn instead.
Signals

Signals provide a way to kill threads in their sleep. Signals are bitmasks. There are two statement types dealing with signals: signal and set-signal-mask.

signal [bitmask];

kills all sleeping threads that overlap the bitmask.

set-signal-mask [bitmask];

sets what signals kill the current thread.

This has several applications. For functions that are called regularly, such as AimWeaponX(), this prevents multiple copies of the same thread from running at the same time. It is also a way (but not the only or necessarily best way) to interrupt another thread.
call-script

Use as follows:

call-script [function]([args]);

This does not start a new thread! The calling function will not continue until the called function returns.
start-script

At first glance, this is similar to call-script:

start-script [function]([args]);

However, start-script does create a new thread, and the calling function doesn't wait until the started function returns before continuing. By default, the new thread shares the same signal mask as its parent, although you can set it manually if you like. Apart from this, the two threads execute completely independently. 





HitByWeaponId(z,x,weapon_id,shot_power)

The z and x angles are not returned unless the impulse of the weapon is high enough to move the unit.

The weapon_id is the "id=xxx" from the weapon information in the weapon tdf.

Always return(100); or the weapon won't deal full damage. 




NOTE LuaCob will merged with LuaRules in 76b2
Contents
[hide14]

    * 1 Introduction15
    * 2 Interfacing COB->Lua16
          o 2.1 Calling Lua from BOS17
                + 2.1.1 Getting output18
                + 2.1.2 LUA0..919
          o 2.2 Summary20
                + 2.2.1 Examples of code21
    * 3 Interfacing Lua->COB22
    * 4 References23
    * 5 Thanks24

Introduction

To use Lua code in your BOS/COB files, you can either include the gadgetHandler (include it in the mod, see the CA mod or include it from the springcontent.sdz) or you create your own enviroment:

    * create a mod-level directory LuaCOB/,
    * make a LuaCOB/main.lua file. 

Any additional files need to be included with a

include("LuaCOB/additional_file.lua" [, fenvTable])

If you don't know what a fenvTable is, don't worry, you probably don't need it anyway. More information can be found on the Lua site25. It it highly recommended to read the manual and the tutorials found there. Note that Spring 76b1 used Lua version is 5.1.2, so please take note which version of the manual you're reading.

Also note that the usual Lua functions for executing code in other files (dofiles(), require()) are disabled for technical reasons.
Interfacing COB->Lua

All Lua functions that are called by COB are given three parameters:

    * unitID - ID of the unit calling the function,
    * unitDefID - ID of the unitDef,
    * teamID - self-explanatory. 

You can call scripts with any (enough for all practical purpouses) amount of additional arguments.

Example functions:

 function GetNearestEnemy(unitID, unitDefID, teamID, radius)
   local px, py, pz = Spring.GetUnitPosition(unitID)
   if (px == nil) then
     return
   end
      
   local nearDist = 1.0e20
   local nearUnit
   local units = Spring.GetUnitsInCylinder(px, pz, radius)
   for _,enemyID in ipairs(units) do
     if (not Sim.IsUnitAllied(enemyID)) then             
       local ex, ey, ez = Spring.GetUnitPosition(enemyID)
       if (ex ~= nil) then
         local dx, dy, dz = (px - ex), (py - ey), (pz - ez)
         local dist = (dx * dx) + (dy * dy) + (dz + dz)
         if (dist < nearDist) then
           nearUnit = enemyID
           nearDist = dist
         end
       end 
     end   
   end   

   if (nearUnit == nil) then
     return -1
   else
     return nearUnit
   end
 end

Please note that returning tables doesn't currently work as one could expect. (Description what it does here) If you want to return multiple values from a table, you need to do it like this:

function ReturnSomeValues(u, ud, t, ...)
    return arg[1], arg[2], arg[3]  -- instead of arg
end

Calling Lua from BOS

To call a Lua function from BOS code, you need to declare it first:

// BOS
lua_MyFunction() { return 0; }

Then just call this script. The lua_ prefix does its magic and routes the call to Lua code instead of calling the usual BOS code. Note that arguments don't matter, they are passed anyway:

call-script lua_MyFunction(arg1, arg2);

Getting output

If you wish to print out some debugging info, please note that print and io.write don't quite work in Windows versions of Spring, since stdout isn't availible (or is well-hidden.) Use Spring.Echo("a string"), this will print a message to the Spring console and into the infolog.
LUA0..9

// the GET/SET constants
#define LUA0 110
#define LUA1 111
#define LUA2 112
#define LUA3 113
#define LUA4 114
#define LUA5 115
#define LUA6 116
#define LUA7 117
#define LUA8 118
#define LUA9 119

get LUA0 is used to check whether call succeeded (1 if true, 0 otherwise.) LUA1..9 store return values, e.g. lua_ReturnSomeValues above would put its first argument into get LUA1, second into get LUA2, and so on.
Summary

Steps needed to call a Lua function in a BOS call-script call:

    * define a Lua function MyFunction(unitID, unitDefID, teamID, ...) in LuaCOB/main.lua or a file included by it
    * define a BOS/COB script lua_MyFunction() { return 0; } so the compiler knows about it
    * call your function with call-script lua_MyFunction()
    * return values can be found in get LUA1..9
    * get LUA0 returns 1 when call was successful (regardless of its return values) and 0 in case of failure. 

Examples of code

    * Inter-unit communication26 (shameless plug ;p) - a library that allows passing messages between units and reading/writing of public variables exposed by them.Included is a tech demo involving modified Nanoblobs27 0.64. Patch, start the game, make a sheep and see for yourself.
    * Your own! 

Interfacing Lua->COB

Lua:

Spring.CallCOBScript(unitID, "CustomScript", retArgCount, arg1, ..., argN)
Spring.CallCOBScriptCB(unitID, "CustomScript", retArgCount, callbackData, arg1, ..., argN)

retArgCount:  the number of arguments expected to be returned
callbackData: number passed to CobCallback() when the cob call completes

COB:

CustomScript(arg1, ..., argN) { ... }

One particularly useful COB script, since Lua doesn't seem to have a way of making get SOMETHING(args) requests (but it may provide other ways of getting the same information):

LuaGet(r, cmd, p1, p2, p3, p4)
{
 if (r == 0) { r = get cmd;                 return r }
 if (r == 1) { r = get cmd(p1,  0,  0,  0); return r }
 if (r == 2) { r = get cmd(p1, p2,  0,  0); return r }
 if (r == 3) { r = get cmd(p1, p2, p3,  0); return r }
 if (r == 4) { r = get cmd(p1, p2, p3, p4); return r }

 return 1234;
}

References

   1. Spring Lua Scripts forum28
   2. trepan's description of Lua-COB interface29 - a little bit outdated, but still worth a read 

Thanks

trepan for doing the hard work of integrating Lua into Spring!


NOTE from trepan:

 Thanks, but there were lua startscripts before I started doing
 any Spring coding. The benefits of the new LuaCob, LuaGaia, and
 LuaRules scripts are that they can be loaded from map/mod archives,
 and that they all use the same base libraries (as does LuaUI).
 They also have significantly more features then the current
 startscript code (ex: more call-ins and call-outs, the ability to
 render graphics in several different modes, play sounds, etc...)






CEG For Units

Creating Custom Explosion Generators (CEGs) and using them with Units in Spring, via COB, is quite easy. This tutorial will show you how to do so.
Step 1: Link your CEGs to your Unit.

CEGs need to be linked to a Unit's data to be generated via COB.

To do so, create a section in your Unit's TDF like this:

[SFXTypes]
{
   ExplosionGenerator0=custom:MyNiftyExplosionGenerator;
}

This tells Spring that the CEG called MyNiftyExplosionGenerator is attached to this Unit, and allows COB to call it. If you have more than one, then the next one uses a 1 instead of a 0, e.g. ExplosionGenerator1=custom:MyNiftyExplosionGenerator; ... and so forth.
Step 2: Calling the CEG via COB

The easiest way to do this is to use a #DEFINE statement, using the appropriate number for the type of Explosion you want to invoke. So, what I typically do is put in lines at the start of the COB, like this:

#DEFINE MyNiftyExplosionGenerator 1024+0

This means that, wherever in the COB I call MyNiftyExplosionGenerator, Spring will call the appropriate CEG.

To actually call the CEG, just use the following:

emit-sfx MyNiftyExplosionGenerator from PieceName;

It's just that simple :-) Just make darn sure you've gotten everything right before you use this, or you will cause to Spring to crash












