This is basically an IP for archiving. I'm working on a pretty big scripting project, one that I hope will for lack of a better word revolutionize the way we view and use script. If you are unfamiliar with the concept of Object Oriented Programming, it's basically a method for using real world "nouns" and "verbs" and structuring the program code around them.
For example, if you have a bicycle, and you want to write a program for it, the standard style (called procedural) would have you write functions for steering, peddling, and son. But with OOP, those functions are hidden behind a noun "bike", and then you use methods "verbs" to do things. Unless you are actually building the functions that make the bike work, you never see them. All you care about is the bike and what you can do.
In reality, it means there is a set of "private" functions that do the work, and a set of public functions you interact with.
Private functions -> Public Functions -> You
So back to our bike, you have public functions for going, stopping, etc, but all these really do is activate private functions you don't see.
bike.go
bike.stop
bike.park
And so on...
The main advantages are that it's simple. Once you have the object established it's easy to make it do things. It's also easily expandable. Because the nuts and bolts are hidden behind an abstraction layer, updates can be made or functions added and nothing breaks.
If you have a procedure function called bike({go}, {stop}, {park}) and you later want to add speed, you have to tack on more parameters that might break older function calls. If you need to change the code that makes stopping work? Too bad, everyone who has an old copy must update or get broken. With OOP you just add the speed option, and that's it. Old calls don't break, new calls get new options.
Now technically speaking, OpenBOR Script does not support real object oriented programing. We can't create true class files, hide functions or initialize them into objects. But after some experimenting I've found a way to come pretty darn close.
It basically involves storing a named array with an index cadence into a script level variable, using a second script variable to track the index. A series of small and simple "public" functions are then provided to interface with the larger "private" functions to manipulate and act upon said array to achieve results. This allows multiple instances of an "object" to be active at once. Multiple bindings, multiple damage objects, and so on.
Scriptvars are ideal because they are available across all functions within the same script, but not the global scope, and they are destroyed when the script is. Arrays never go away until specifically freed, so we can also turned right around and get our object in another script. It sounds complicated but in practice you should find it much easier than current procedural methods.
Best of all, these objects can be copied at will and interact seamlessly with each other while still being entirely self contained. One of the reasons I haven't released my modules or many of my scripts is they are all part of an interconnected system. Binding can't work without damage system, damage system can't work without sound system, sound system can't work without random system, random needs a global set of utility functions, all of it depends on a mile long list of constants... you get the idea. The OOP concept will fix all that straight away. The damage system below, once complete will work on its own without any support scripts. Binding will be next, and though it will need to use Damage for grappling purposes, it will do so by interacting through object transfer without sharing any code at all.
But wait, there's more!
For you, scripters and non scripters, actually using these functions will be insanely simple. Function calls have one parameter - that's it and that's all. Remember the actual meat and potatoes are encapsulated. This means you have a lot of one line commands, but each command is concise and easy to understand.
So instead of something like this to damage someone on a one time basis:
You get this:
See how it works? You set up the object, then go with it. Only include the parameters you want. Change any or all of them at will, the others remain persistent - then activate the action as many times as you like. Simple, right? And remember the very first line. You can set up multiple instances labeled by index... so then something like this is possible:
Here is the IP code. I've got a ton of work to do obviously, but you can see it's starting to take shape. Finished product (and more like it) coming soon!
interface
main
For example, if you have a bicycle, and you want to write a program for it, the standard style (called procedural) would have you write functions for steering, peddling, and son. But with OOP, those functions are hidden behind a noun "bike", and then you use methods "verbs" to do things. Unless you are actually building the functions that make the bike work, you never see them. All you care about is the bike and what you can do.
In reality, it means there is a set of "private" functions that do the work, and a set of public functions you interact with.
Private functions -> Public Functions -> You
So back to our bike, you have public functions for going, stopping, etc, but all these really do is activate private functions you don't see.
bike.go
bike.stop
bike.park
And so on...
The main advantages are that it's simple. Once you have the object established it's easy to make it do things. It's also easily expandable. Because the nuts and bolts are hidden behind an abstraction layer, updates can be made or functions added and nothing breaks.
If you have a procedure function called bike({go}, {stop}, {park}) and you later want to add speed, you have to tack on more parameters that might break older function calls. If you need to change the code that makes stopping work? Too bad, everyone who has an old copy must update or get broken. With OOP you just add the speed option, and that's it. Old calls don't break, new calls get new options.
Now technically speaking, OpenBOR Script does not support real object oriented programing. We can't create true class files, hide functions or initialize them into objects. But after some experimenting I've found a way to come pretty darn close.
It basically involves storing a named array with an index cadence into a script level variable, using a second script variable to track the index. A series of small and simple "public" functions are then provided to interface with the larger "private" functions to manipulate and act upon said array to achieve results. This allows multiple instances of an "object" to be active at once. Multiple bindings, multiple damage objects, and so on.
Scriptvars are ideal because they are available across all functions within the same script, but not the global scope, and they are destroyed when the script is. Arrays never go away until specifically freed, so we can also turned right around and get our object in another script. It sounds complicated but in practice you should find it much easier than current procedural methods.
Best of all, these objects can be copied at will and interact seamlessly with each other while still being entirely self contained. One of the reasons I haven't released my modules or many of my scripts is they are all part of an interconnected system. Binding can't work without damage system, damage system can't work without sound system, sound system can't work without random system, random needs a global set of utility functions, all of it depends on a mile long list of constants... you get the idea. The OOP concept will fix all that straight away. The damage system below, once complete will work on its own without any support scripts. Binding will be next, and though it will need to use Damage for grappling purposes, it will do so by interacting through object transfer without sharing any code at all.
But wait, there's more!
For you, scripters and non scripters, actually using these functions will be insanely simple. Function calls have one parameter - that's it and that's all. Remember the actual meat and potatoes are encapsulated. This means you have a lot of one line commands, but each command is concise and easy to understand.
So instead of something like this to damage someone on a one time basis:
Code:
@cmd damage({target}, {force}, {knockdown}, {velocity X}, {velocity Y}, {velocity Z} ....
frame blah.... #Do damage on this frame
@cmd damage({target}, {force}, {knockdown}, {velocity X}, {velocity Y}, {velocity Z} ....
frame blah.... #Do damage on this frame
@cmd damage({target}, {force}, {knockdown}, {velocity X}, {velocity Y}, {velocity Z}....
frame blah.... #Do damage on this frame
You get this:
Code:
@cmd damage_create_object {index} #Sets up a damage object and current instance we'll modify.
@cmd damage_set_target {entity}
@cmd damage_set_force {int}
@cmd damage_set_knockdown {int}
@cmd damage_set_velocity_x {float}
@cmd damage_set_velocity_y {float}
@cmd damage_set_velocity_z {float}
frame blah...
@cmd damage_execute
frame blah... #Do damage on this frame.
@cmd damage_set_velocity_x {float} #Set a new X velocity.
@cmd damage_execute
frame blah... #Do damage on this frame using new X velocity.
See how it works? You set up the object, then go with it. Only include the parameters you want. Change any or all of them at will, the others remain persistent - then activate the action as many times as you like. Simple, right? And remember the very first line. You can set up multiple instances labeled by index... so then something like this is possible:
Code:
# For this instance, let's just use the defaults for everything but target and force.
@cmd damage_create_object 1 #Sets up a damage object and current instance we'll modify.
@cmd damage_set_target {entity}
@cmd damage_set_force {int}
#Now lets set up a second instance. We'll give it a few more parameters.
@cmd damage_create_object 2 #Sets up a damage object and current instance we'll modify.
@cmd damage_set_target {entity}
@cmd damage_set_force {int}
@cmd damage_set_knockdown {int}
@cmd damage_set_velocity_x {float}
@cmd damage_set_velocity_y {float}
@cmd damage_set_velocity_z {float}
frame blah...
@cmd damage_set_index 1 #Let's work with instance 1.
@cmd damage_execute
@cmd damage_set_index 2 #Let's work with instance 2.
@cmd damage_execute
frame blah... #Do two types of damage on this frame.
@cmd damage_set_index 1 #Let's work with index 1.
@cmd damge_set_velocity_x {float} #Set a new X velocity.
@cmd damage_execute
frame blah... #Do damage on this frame.
Here is the IP code. I've got a ton of work to do obviously, but you can see it's starting to take shape. Finished product (and more like it) coming soon!
interface
Code:
// Direction adjustments.
#define DAMAGE_DIR_ADJ_LEFT -2 // Force target to face left.
#define DAMAGE_DIR_ADJ_OPP -1 // Force target to face opposite direction as base.
#define DAMAGE_DIR_ADJ_NONE 0 // Do not change target facing.
#define DAMAGE_DIR_ADJ_SAME 1 // Force target to face same direction as base.
#define DAMAGE_DIR_ADJ_RIGHT 2 // Force target to face right.
// Generic On/Off flag settings.
#define DAMAGE_TRUE 1
#define DAMAGE_FALSE 0
// Default settings.
#define DAMAGE_DEF_TYPE openborconstant("ATK_NORMAL") // Type of damage.
#define DAMAGE_DEF_DROP 1 // Damage droping (knockdown) power.
#define DAMAGE_DEF_FORCE 0 // Hitpoints taken (i.e. damage amount).
#define DAMAGE_DEF_VEL_X 1.0 // X (Horizontal).
#define DAMAGE_DEF_VEL_Y 2.0 // Y (Vertical).
#define DAMAGE_DEF_VEL_Z 0.0 // Z (Lateral).
#define DAMAGE_DEF_DIR_ADJ DAMAGE_DIR_ADJ_OPP // Force direction setting.
#define DAMAGE_DEF_MIN 0 // Minimum remaining after damage is applied.
#define DAMAGE_DEF_DOL_FORCE 0 // Damage on landing hitpoints taken.
#define DAMAGE_DEF_ATTACKING DAMAGE_FALSE // Attacking flag (for hitting others during fall).
#define DAMAGE_DEF_PROJECTILE DAMAGE_FALSE // Projectile flag (thrown/blasted).
// Operations - There's usually no reason to mess with these.
// Object settings
#define DAMAGE_OBJECT_NAME "DAMAGE" // Array name used to store object data.
#define DAMAGE_OBJECT_INDEX "DAMAGE.i" // Script var used to store instance index of object data.
// Object array
#define DAMAGE_ARRAY_KEY_TYPE 0 // Type of damage.
#define DAMAGE_ARRAY_KEY_DROP 1 // Damage droping (knockdown) power.
#define DAMAGE_ARRAY_KEY_FORCE 2 // Hitpoints taken (i.e. damage amount).
#define DAMAGE_ARRAY_KEY_VEL_X 3 // X (Horizontal).
#define DAMAGE_ARRAY_KEY_VEL_Y 4 // Y (Vertical).
#define DAMAGE_ARRAY_KEY_VEL_Z 5 // Z (Lateral).
#define DAMAGE_ARRAY_KEY_MIN 6 // Minimum remaining after damage is applied.
#define DAMAGE_ARRAY_KEY_DIR_ADJ 7 // Force direction setting.
#define DAMAGE_ARRAY_KEY_DOL_FORCE 8 // Damage on landing hitpoints taken.
#define DAMAGE_ARRAY_KEY_ATTACKING 9 // Attacking flag (for hitting others during fall).
#define DAMAGE_ARRAY_KEY_PROJECTILE 10 // Projectile flag (thrown/blasted).
#define DAMAGE_ARRAY_SIZE 11 // Array size of array holding object data.
main
Code:
#include "data/scripts/com/damage/interface.h"
/* --PUBLIC--
Use these functions freely in model text, other scripts, etc.
*/
// Initialize object.
void damage_set_object(int index)
{
// Data array
void object;
// Create array.
object = array(DAMAGE_ARRAY_SIZE);
// Set index.
damage_set_index(index);
// Store array into a scriptvar labeled by index.
setscriptvar(DAMAGE_OBJECT_NAME + index, object);
//Set default values.
damage_set_type(DAMAGE_DEF_TYPE);
damage_set_force(DAMAGE_DEF_FORCE);
damage_set_drop(DAMAGE_DEF_DROP);
damage_set_direction(DAMAGE_DEF_DIR_ADJ);
damage_set_velocity_x(DAMAGE_DEF_VEL_X);
damage_set_velocity_y(DAMAGE_DEF_VEL_Y);
damage_set_velocity_z(DAMAGE_DEF_VEL_Z);
damage_set_dol_force(DAMAGE_DEF_DOL_FORCE);
}
// Set current index.
void damage_set_index(int index)
{
// Set index.
setscriptvar(DAMAGE_OBJECT_INDEX, index);
}
// Set damage type.
void damage_set_type(int value)
{
// Call object property setting function with appropriate key.
damage_set_object_property(DAMAGE_ARRAY_KEY_TYPE, value);
}
// Set damage force (hp taken).
void damage_set_force(int value)
{
int index; // Instance index.
void object; // damage data array.
// First thing we need to do is get the current index. This is how we know
// which instance of our object we'll be working on.
index = getscriptvar(DAMAGE_OBJECT_INDEX);
// Verify index, and alert user if it's missing. We can't do anything else
// without it.
if(!index)
{
log("\n damage_set_force: No index found! Make sure to set an instance index.");
}
else
{
// Now we'll use our index to get the array from its script variable.
// This is our "object". Then we can do work on it as needed.
// Get the object array.
object = getscriptvar(DAMAGE_OBJECT_NAME + index);
// Set array element value.
set(object, DAMAGE_ARRAY_KEY_FORCE, value);
// Once we're done with our object array, let's put it back into a
// script var for later use.
setscriptvar(DAMAGE_OBJECT_NAME + index, object);
}
}
// Set damage drop.
void damage_set_drop(int value)
{
// Call object property setting function with appropriate key.
damage_set_object_property(DAMAGE_ARRAY_KEY_DROP, value);
}
// Set force direction.
void damage_set_direction(int value)
{
// Call object property setting function with appropriate key.
damage_set_object_property(DAMAGE_ARRAY_KEY_DIR_ADJ, value);
}
// Set X axis velocity.
void damage_set_velocity_x(float value)
{
// Call object property setting function with appropriate key.
damage_set_object_property(DAMAGE_ARRAY_KEY_VEL_X, value);
}
// Set y axis velocity.
void damage_set_velocity_y(float value)
{
// Call object property setting function with appropriate key.
damage_set_object_property(DAMAGE_ARRAY_KEY_VEL_Y, value);
}
// Set z axis velocity.
void damage_set_velocity_z(float value)
{
// Call object property setting function with appropriate key.
damage_set_object_property(DAMAGE_ARRAY_KEY_VEL_Z, value);
}
// Set minimum HP target will have after damage is applied.
void damage_set_min(int value)
{
// Call object property setting function with appropriate key.
damage_set_object_property(DAMAGE_ARRAY_KEY_MIN, value);
}
// Set damage on landing force (damage).
void damage_set_dol_force(int value)
{
// Call object property setting function with appropriate key.
damage_set_object_property(DAMAGE_ARRAY_KEY_DOL_FORCE, value);
}
// Set damage attacking flag (can hit others during fall).
void damage_set_attacking(int value)
{
// Call object property setting function with appropriate key.
damage_set_object_property(DAMAGE_ARRAY_KEY_ATTACKING, value);
}
// Set damage projectile flag (can hit others during fall).
void damage_set_projectile(int value)
{
// Call object property setting function with appropriate key.
damage_set_object_property(DAMAGE_ARRAY_KEY_PROJECTILE, value);
}
/* -- PRIVATE --
These functions are for internal use and should never be called directly.
Keeping them separated allows for (among many other things) future
updates that retain backward compatibility.
*/
// Handle object value changes.
void damage_set_object_property(int key, float value)
{
int index; // Instance index.
void object; // damage data array.
// First thing we need to do is get the current index. This is how we know
// which instance of our object we'll be working on.
index = getscriptvar(DAMAGE_OBJECT_INDEX);
// Verify index, and alert user if it's missing. We can't do anything else
// without it.
if(!index)
{
log("\n damage_set_<property>: No index found! Make sure to set an instance index before attempting to set object properties.");
}
else
{
// Now we'll combine out index with the object name to get the array from a
// script variable that we first set up during object creation.
// This array is our "object". Then we can do work on it as needed.
// Get the object array.
object = getscriptvar(DAMAGE_OBJECT_NAME + index);
// Set array element value.
set(object, key, value);
// Once we're done with our object array, let's put it back into a
// script var for later use.
setscriptvar(DAMAGE_OBJECT_NAME + index, object);
}
}