Documentation

1: Introduction

This page contains some work-in-progres documentation for the game and how to modify it.

Lemmingball Z was originaly designed as just a simple game, but I do have a new goal with it. I want to make the technology behind LBZ available for other game projects. Right now, very little about the technology is written down. These pages server to make a start. The technology behind LBZ is called the "Cellblock 9 engine" or CB9 for short. This is because i am a huge fan of the bluesbrothers movie and blues music in general.

Features of the engine include:

  • Built-in level editor
  • Built-in script/xml editor with syntax highlighting
  • Built-in model viewer/importer
  • Destructable terrain
  • Use XML to define your own moves, with very little programming
  • A networking system that "just works" like "magic", no need to think about synchronizing characters/moves over the internet.

2: Setting up a development environment

If you want to build a mod to LBZ or if you just want to edit the game a bit you will need a development version of the game.
In this document i will detail how to set up a current development environment for modding LBZ.

2.1: Cellblock9's 2 data folders

  • The installation folder: that is the folder where you extracted the ZIP file. Usually something like "c:\games\downloaded\Lemmingball Z"
  • The profile folder: this is created by the LBZ updater to store settings. Usually "C:\Users\[your username]\Documents\LBZ Alpha Game Data"

2.2: Copy original data

To create a development environment for LBZ copy the data folder from the profile folder to the installation folder and name it dev-data.
Do not overwrite the data folder that is already there... in your installation folder should now be a data folder and a folder named dev-data.

2.3: Create development shortcut

  • In the installation folder create a new shortcut to lbzwin.exe.
  • Right-click the new shortcut and select properties.
  • A screen should open with all properties of the shortcut: The first field should be target and should contain a full path to lbzwin.exe.
  • To this field add --debug without the quotes.
  • The field should read something like: "C:\games\downloaded\LBZ\lbzwin.exe" --debug
  • Click OK to save the shortcut.

You now have a working development install of LBZ! When you start LBZ with the newly created shortcut: all data will be loaded from the dev-data folder instead of the regular data. You can verify this by checking the console and scrolling up: you should see a message: "Found 'dev-data' directory in developer mode!!!" The updater will never tough this folder. if the game gets updated and you want/need the new files, manually copy over the profile/data folder to dev-data Now you can mod/hack/edit as much as you want.

2.4: Associate the internal editor with scripts

To open .lgs script files with LBZ's internal script editor, navigate to the dev-data folder, find an LGS scriptfile, right-click and select open with. Click the browse button and navigate to the shortcut. Select the shortcut and click ok. From this moment on clicking an LGS file in the dev-data folder will open up LBZ's internal editor with syntax highlighting.

 

3: File formats and their uses

LBZ makes use of a lot of different files, almost all files can be edited with a simple text editor, or with the ingame script editor.

3.1: .png files for textures

As a general rule: the engine does not put size limits on textures.
Textures are stored as .png files (portable network graphics) .png have loss-less compression and can be easily loaded without big image libraries.

Textures can have specular maps, normal maps and "inner textures". (inner textures are used when destroying/tunneling inside objects carrying this texture)

Say you have a nice grass texture (1024x1024) called grass.png

  • Specular map: the A channel of the base texture can be used as a specular map (reversed specularity... to 255 == no specularity, 0 == full specularity)
  • To use a normal map with the texture: Create a png called grass.nmap.png , the RGB channels are for normal map data
  • To use an inner material with the grass texture: Create a png file grass.destroy.png , of course this can have its own nmap... grass.destroy.nmap.png

 

3.2: .ogm files for sounds

Ogm files are simply used for sound. They are referenced from the move files. A good sound editor that can export ogm files is audacity.

(TO BE EXPANDED)

3.3: .xml files

XML stands for EXtensible Markup Language. XML was designed to store and transport data. XML was designed to be both human- and machine-readable.

The CB9 engine uses XML files for many things: this is an example XML file:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 
<gamedefaults> 
 <name>Lemmingball Z</name> <!--The full name of the game--> 
 <shortname>LBZ</shortname> <!--The short name of the game--> 
 <newsurl>http://www.lbz3d.com/?rml</newsurl>
</gamedefaults>

CB9 has 2 features that can be used in ANY XML file it reads:

  • Including of other XML files:
    <lxi:include src="someotherxml.xml">path/to/stuff</lxi:include>
  • Inline scripts: Any tag that is supposed to contain a filename that points to a script can use the lxi:inlinelgs option to inline the script...
    <script id="addgamescript">data/moves/myscript.lgs</script>
    Can be inlined like:
    <script id="addgamescript" lxi:inlinelgs="1">
      <![CDATA[
        /*CONTENT OF SCRIPT*/
      ]]>
    </script>
    
    

3.3.1: character xml

Character XML is used to define selectable characters for the game. (TO BE EXPANDED)

3.3.2: move xml

Move XML is used to define attacks (moves). (TO BE EXPANDED)

3.3.3: cb9enginedefaults.xml

This XML file contains the minimal information the engine requires to start and function. (TO BE EXPANDED)

3.4: .lgs files

The Cellblock9 engine comes with its own scripting language called LGS. LGS stands for "Lemmingball Game Script", it is used for make game rules and decisions like who gets points, who lives, who dies. It is also fast enough to be used for graphics. LGS is a simple language and it's design is based on C++. In LGS you do not have to manage memory, making it safe to play around in.

3.4.1: Goals for LGS:

  • Fast enough to be used in graphics and in gameplay
  • Display errors fast and early to help you write good code
  • Easy to understand
  • Includes advanced features such as object oriented programming, event based programming etc etc

3.4.2: Programming styles officially supported by LGS:

3.4.2.1: Event based programming:

React to a specific event generated by the gameengine, or another script For example:

//When a player dies
catchallevents(_SYS_PLAYER_DEATH) {
	//I want to write to console:
	print(Player_name+" died!");
}
	
3.4.2.2: C-ish functions:

Easy to understand flow of the program For example:

function isPlayerDead(int player) return int{
	//
	return 1;
}
//Check if player 2 is dead
if(isPlayerDead(2)) {
	print("Hah! player 2 is dead!");
}
3.4.2.3: object oriented programming C++-style:

Can organize your code/data in a better way. (Good for communication) For example:

//Define a base class for mammals
class _mammal {
	float health=100;
	function kill() {
		health = 0;
	}
}

//Define a base class for greed
class _greedy {
	int cashInCents=100000;

	function withdraw(int amount) return int {
		cashInCents -= amount;
		return amount;
	}
}

//Define a human as a mammal with greed and a name:
class _human extends _mammal,_greedy {
	string name;	
}

//Create a human:
object _human myself;
myself->name = "Menne Kamminga";
//I am rich:
myself->cashInCents = 100;
//Now i am broke:
myself->withdraw(100);
//Dont want to life as a poor man:
myself->kill();

//Print my current state:
print("My health is:"+cast_ftos(myself->health,2));

3.4.3: Control logic:

3.4.3.1: If/else:

Basic control logic, like a question:

if(health < 0) {
	//i am dead
} elseif(health < 10){
	//i am alive, but almost dead
} else {
	//i am alilve!
}
3.4.3.2: foreach

Loop trough a list of things:

//Have a list of names
string names[]={"AnalyticaL","LSDuck"};
//Loop trough all names:
foreach(names index a) {
	//Print out the names:
	print(names[a]);
}
3.4.3.3: for

A custom loop:

//Countdown from 10:
for(int c = 10;c>0;c--) {
	//Prints 10, then 9, then 8 etc etc
	print(cast_itos(c));
}
3.4.3.4: while

Loop for as long as a condition is true:

//For as long as i am alive:
while(Player_health > 1) {
	//Eat cookies:
}

3.4.4: Data types & declaring variables:

3.4.4.1: LGS Supports the following basic data types:
  • float - representes numbers with an imaginary part like -1000.50 or 3.14750
  • int - Represents whole (integer) number like -1, 1000 or 777
  • string - Represents text string line = "this is a line of text";
  • resource - Represents a system resource, like a texture or a sound. Also used to store things like "events"
  • object - Magic store, can store a whole bunch of things together. can optionally have a class with its own functions.
    Example: 
    object myStore;
    myStore["name"] = "My first magick store object";
    myStore["number"] = 10;
3.4.4.2: You can declare your own variables like this:

{1 or more type selectors} (Optional class name) {name}([Optional list size]) (=Optional initializer). Available type selectors are:

  • float: sets primary type to float
  • int: sets primary type to int
  • string: sets primary type to string
  • resource: sets primary type to resource
  • object: sets primary type to object. objects can store anything, can optionally have a class name
  • var: sets primary type to "unknown"
  • global: changes the memory location of this variable to "global" AKA "this variable is the same in every script"
  • moveglobal: changes the memory location of this variable to "moveglobal" AKA "this variable is unique for every move and the same in every script for that move"
  • playerglobal: changes the memory location of this variable to "playerglobal" AKA "this variable is unique for every player and the same in every script for that player, including all the players moves"
  • ref: turns the variable into a "reference": this means the variable "points" to another variable instead of having memory of its own. (for strings or objects)
  • auto: Guess the variable type from it's initializer (requires initializer).

A couple of examples:

  • int age=42; //Declare an integer number named "age" and set it to 42
  • int ages[4] = {15,16,17,18}; //Declare a list of 4 integers named ages and fill it with 15,16,17,18
  • string name="LSDuck"; //Declare a bit of text named "name" and set contents to LSDuck
  • string ref namePoint = name; //Declare a reference to a bit of text and point it to "name"
  • object dork; //Declare an object that can store anything.
  • object _human dork2; //Declare an object that stores everything defined in class "_human"
  • auto dork3 = dork2; //Make a copy of the dork2 object... note the use of auto to guess the type.
3.4.4.3: Variable scopes:

By default, each variable you declare is only available inside the scope you declare it in. You can spot scopes easily. Each scope starts with { and ends with } For example:

float myhealth = 100.1;
if(1) { // Watch it, starting new scope:
	float myhealth = 0;//New variable also called myhealth, but in different scope!
}
//myhealth is still 100.1

3.4.5: Declaring functions:

Most (almost ALL) of the time, when you write a piece code, you want to reuse it in a different spot. For example: you might want to damage your enemy, then tell the world about what you did:

	
Enemy_health -= 10;
print("Hah! i damaged "+Enemy_name+" for 10 points!");

You probably want to do this multiple times so you can write it as a function:

//Declare a function:
function damageEnemy(float points) return float {
	Enemy_health -= points;
	print("Hah! i damaged "+Enemy_name+" for "+cast_ftos(points,0)+" points!");
	
	return Enemy_health;
}

Now you can just write:

float health_remaining = damageEnemy(10);

3.4.6: Custom datatypes (classes):

With the magic "object" datatype you can also declare your own "custom" datatypes... These are called classes. A class is defined as follows:

class {name} (Optional: extends {other class},{another class}) {
	{variable declarations}
	
	(Optional function declarations)
}

For example:

	
class myFirstClass extends someBaseClass {
	int myFirstVariableInAClass;
	
	function increase() return int {
		myFirstVariableInAClass++;
		return myFirstVariableInAClass;
	}
}
3.4.6.1: Function overriding in classes: (polymorphism)

Consider these classes:

//Define base class for color:
class color {		
	function use() {
		//Nothing to do here
		print("ERROR! base color used");
	}
}

//Define "red" color:
final class red extends color{
	function use() {
		glColor4f(1,0,0,1);
	}
}

//Define "blue" color:
final class blue extends color{
	function use() {
		glColor4f(0,0,1,1);
	}
}

//If i have an object of class color:
object color myTeamColor;

//And some color objects:
object blue myBlue;
object red myRed;

//You can do this: (make the team color blue)
myTeamColor = myBlue;
myTeamColor->use();//Even though myTeamColor is of class "color", this will call the use() from the "blue" class!
myTeamColor = myRed;
myTeamColor->use();//Even though myTeamColor is of class "color", This will call the use() from the "red" class!

3.4.7: List of all keywords:

Keywords are words that have special meaning in the LGS language: this is a list of all such keywords. If you ever need to have a variable with the same name as a keyword, you can use $. ($construct means variable with name construct, just using construct will not work as it has special meaning)

  • #uctexture
    	Define an uncompressed texture: #uctexture "myfile.png" as myTex
    	This defines a resource variable myTex holding texture data from file "myfile.png"
    
  •  	
    #texture
    	Define a regular texture: #texture "myfile.png" as myTex
    
  •  
    #sound
    	Define a sound: #sound "myname.ogm" as mySound
    	Defines a resource variable holding a sound
    
  •  	
    #htmlfile
    	Define a html file: #htmlfile "myname.html" as myDocument
    	Defines a resource variable holding a html document
    
  •  		
    #htmlelement
    	Define a html element: #htmlelement "_myelementid_" as myElement
    	Defines a resource variable holding an element retrieved by getElementById from last loaded document.
    
  •  		
    #model
    	Define a model: #model "myname.xml" as myModel
    	Defines a resource variable holding a model
    
  •  
    #modelanim
    	Define an animation: #modelanim "myanimationname" as myAnim
    	Defines a resource variable holding an animation loaded from last loaded #model
    
  •  	
    #warning
    	Give out a warning: #warning "This file should not be used anymore"
    	Will print the warning when the script is used. Usefull for detecting scripts that use old includes for example.
    
  •  	
    #event
    	Declare an event: #event _MY_FIRST_EVENT
    	This will define a global event.
    	You can trigger an event with fireEvent(_MY_FIRST_EVENT);
    	Then another script, (or same script) can "catch" that event: catchevent(_MY_FIRST_EVENT) {/*Some code to execute on event*/}
    
  •  		
    #timer
    	Declare a timer: #timer _MYTIME
    	use with timeMark function and timeSince to get high-resolution time data.
    
  •  		
    #include
    	Include a file into your script: #include "someotherfile.inc"
    	All the stuff defined in someotherfile.inc becomes available in your current script.
    
  •  	
    for
    	The custom for loop (See control logic)
    
  •  		
    foreach
    	The foreach for loop (See control logic)
    
  •  		
    as
    	Used while declaring resources: #texture "blah" as ...
    
  •  		
    return
    	Returns from the current function, if this function is "main()" then exit script.
    	Can optionally return a variable from a function: 
    		function test() return float {
    			float myFloat = 7.0;
    			return myFloat;
    		}
    
  •  
    index
    	Used in the foreach loop: foreach(myArray index a) 
    	Names the index... in this example, if myArray has 10 elements, then "a" would count from 0 to 9
    
  •  		
    while
    	A while loop: while(CONDITION) {/*code*/}
    	Basicly: for a long as CONDITION is true: execute code inside the brackets.
    
  •  		
    catchevent
    	Catches an event exactly once: catchEvent(_SYS_PLAYER_DEATH) { /*Do something the next time the player dies...*/ }
    	Can catch multiple events at once: catchEvent(_SYS_PLAYER_DEATH,_SOME_OTHER_EVENT) {}
    
  •  		
    catchallevents
    	Permanently catches all events: catchAllEvents(_SYS_PLAYER_DEATH) { /*Do something every time the player dies...*/ }
    	Can catch multiple events at once: catchEvent(_SYS_PLAYER_DEATH,_SOME_OTHER_EVENT) {}
    
  •  		
    function
    	Starts the declaration of a new function: function myName() {}
    	Code between the {} gets called each time you call the function with "myname()"
    
  •  		
    if
    	(See control logic)
    	if(CONDITION) {/*CODE*/} //Execute CODE if CONDITION == true
    
  •  		
    elseif
    	(See control logic)
    	Can be appended to an if or another elseif code block to put multiple conditions together
    
  •  		
    else
    	(See control logic)
    	Can be appended to an if or an elseif, if the previous condition is NOT true, execute this.
    
  •  		
    class
    	Start defining a class: see: Custom datatypes (classes)
    	class myClassName { /*Some variables and/or functions */ }
    
  •  		
    extends
    	For classes, declares that this class is an extension of an existing class:
    	class myOtherClass extends myBaseClass,myOtherBaseClass { /*Some more variables*/ }
    	extended classes inherit all the variables&function from the base class it extends, and can then add it's own stuff...
    	You can extend multiple classes at once...
    	You can override functions from a base class. (see: Custom datatypes (classes))
    
  •  			
    _event
    	Define an inline event: _event{/*Code to execute on event*/}
    	This is usefull for functions that take an event as parameter.
    	For example: webMessage("http://url","My message to send",_event{ /*Code to execute when the webserver has replied to message */} ,1.0);
    	Basicly triggerEvent(_event{/*code*/}); is the same as:
    		#event _MY_EVENT
    		catchAllEvents(_MY_EVENT) {/*code*/}
    		triggerEvent(_MY_EVENT);
    	So in some cases, this is just less typing...
    
  •  				
    break
    	Break away from a while, for, or foreach loop. (stops the loop.)
    	For example: 
    	foreach(arrayOfColors index i) {
    		if(arrayOfColors[i] == "blue") {
    			print("i found the color blue");
    			break;//Can stop the loop now.
    		}
    	}
    
  •  	
    typename
    	Returns the typename of an argument as a string: print(typename(MYVARIABLE));
    	For example: if the type of MYVARIABLE is "float", this would print "float"
    
  •  		
    deref
    	De-reference a reference to an object or a string: (See: Data types & declaring variables)
    	Consider 2 strings and a reference to a string:
    		string A="Menne"; 
    		string B=" Kamminga";
    		string ref C;
    		
    		//This will make C point to A: (C does not have memory of its own, it is now pointed to A)
    		C = A;
    		
    		//Now this will overwrite A: (After all, C = pointed to A)
    		deref(C) = "David";
    		
    		//This will point C to B:
    		C = B;
    		
    		//This will print David Kamminga
    		print(A+B);
    	References, and dereferences can be used to gain speed boosts by NOT copying objects or strings around.
    	It can also simplyfy some algorithms. They can be tough to understand, so only use it if you understand it!
    
  •  			
    construct
    	Force construction of an unconstructed object: construct(A);
    	object storage is only actually created when the object gets used. This forces an to be created.
    	Also, references to objects with classes are also not created.
    	Consider this slightly complecated example:
    		class _tree{
    			//These children do NOT get constructed, if they would be, this would mean an endless loop since they are of the same type (_tree) as the parent.
    			object _tree children[2];
    			function getChild(int numChild) return object _tree{
    				//Here we force construction of a specific child and then return it.
    				return construct(children[numChild]);
    			}
    		}
    
  •  	
    __construct
    	For classes: Special function inside a class that gets called when object of that class gets constructed.
    		class myClass{
    			function __construct() {
    				print("Hi! I get called each time an object of class myClass is created...");
    			}
    		}
    
  •  		
    private:
    	For classes: class members (variables or functions) that follow this are private and cannot be called outside of the class.
    		class myClass {
    			private:
    			int i;
    			public:
    			int c;
    		}
    		object myClass A;
    		A->i = 0; //Error: "i" is private
    		A->c = 0; //Correct: "c" is public
    
  •  			
    public:
    	The opposite of private: (see private:)
    
  •  		
    final
    	The final keyword marks classes or class members as final:
    	final class myClass{} // Other classes cannot extend this class (see "extends") since it is marked final.
    	class myNotSoFinalClass {// Other class CAN extend this class:
    		final function myFunc() {// But this myFunc, cannot be overriden in any class that extends myNotSoFinalClass.
    		
    		}
    	}
    

3.4.8: List of all operators:

Operators are composed of 1 or 2 characters and have special meaning in the LGS language. This is a list of all operator and their meaning and priority (from lowest to highest priority)

  • operator =
    	The assignment operator. A = B;
    	//Copy content from B to A
    	A = B; 
    	//(If A is a reference, point A to B instead of performing a copy)
    
  •  
    operator +=
    	Add B to A: A += B; //(same as A = A + B)
    
  •  
    operator -=
    	Substract B from A:	A -= B; //(Same as A = A - B)
    
  •  
    operator *=
    	Multiply A by B: A *= B; //(Same as A = A * B)
    
  •  
    operator /=
    	Divide A by B: A /= B; //(Same as A = A / B)
    
  •  
    operator %=
    	Set A  to the modulus of A / B: A %= B; //(Same as A = A % B)
    	A modulus is the leftover value from a division. (6/4 == 1, 6%4 == 2)
    
  •  
    operator &=
    	Binary AND operation on A & B: A = A & B 
    	If A in binary is 0001 and B is 1001, then the result is 0001
    
  •  
    operator |=
    	Binary OR operation on A | B: A = A | B 
    	If A in binary is 0001 and B is 1001, then the result is 1001
    
  •  
    operator ^=
    	Binary EXCLUSIVE OR operation on A ^ B: A = A ^ B 
    	If A in binary is 0001 and B is 1001, then the result is 1000
    
  •  
    operator ||
    	Logical OR operation. A || B will be true when either A OR B is true.
    	primary use: if(A || B || C) { doSomthing() }
    
  •  
    operator &&
    	Logical AND operation. A && B will be true when both A AND B are true.
    	primary use: if(A && B && C) { doSomthing() }
    
  •  
    operator |
    	Binary OR operator: C = A | B; 1001 = 1000 | 0001;
    
  •  
    operator ^
    	Binary EXCUSIVE OR operator: C = A ^ B; 1110 = 0001 ^ 1111;
    
  •  
    operator &
    	Binary AND operator: C = A & B; 0001 = 1111 & 0001;
    
  •  
    operator !=
    	Does not equal: A != B  is true when A is different from B
    
  •  
    operator ==
    	Equal: A == B is true when A is the same as B
    
  •  
    operator >
    	Greater then: A > B is true when A is bigger then B
    
  •  
    operator >=
    	Greater then or equal: A >= B is true when A is bigger then B or A == B
    
  •  
    operator <=
    	Less then or equal: A <= B is true when A is smaller then B or A == B
    
  •  
    operator <
    	Less then: A < B is true when A is smaller then B
    
  •  
    operator +
    	Add: C = A + B;
    
  •  
    operator -
    	Minus: C = A - B;
    
  •  
    operator *
    	Multiply: C = A * B;
    
  •  
    operator /
    	Divide: C = A / B;
    
  •  
    operator %
    	Modulus: C = A % B;
    
  •  
    operator !
    	NOT operator: !A is true when A is false
    
  •  
    operator ++
    	Increment: A++ means: A = A + 1
    
  •  
    operator --
    	Decrement: A-- means: A = A - 1
    
  •  
    operator []
    	Subscript... A[N] extract N'th from A.
    	If A is an array, then N is the index of the element you want.
    	If A is an object, then N is the string name of the property you want.
    
  •  
    operator {}
    	Extract property: A{N} extract property with name N from object A
    
  •  
    operator ->
    	Extractor operator A->B extracts member "B" from object A.
    	member B can be a function: A->B();
    	member B can be a variable: A->B = 0.1;
    

 

4: The internal script editor

CB9 comes equiped with a syntax highlighting editor. To open the editor press CTRL+q while inside the game. The game needs to be started with the --debug paramater. (see here). (TO BE EXPANDED)

 

How to create a normal map: https://developer.valvesoftware.com/wiki/Normal_Map_Creation_in_The_GIMP

Leave a Reply

Your email address will not be published. Required fields are marked *