Non-Units. Towards freeform scripting.

Non-Units. Towards freeform scripting.

Discuss the source code and development of Spring Engine in general from a technical point of view. Patches go here too.

Moderator: Moderators

Post Reply
User avatar
Argh
Posts: 10920
Joined: 21 Feb 2005, 03:38

Non-Units. Towards freeform scripting.

Post by Argh »

This topic is meant to describe a conceptual / logical framework for developing a newer, more general-purpose and flexible design for the game engine's weapon / special effects system. I have laid this out as an attempt to simplify things and give us a more logical path for development efforts going forward.

This is not really a "wish list", in the classic sense. I have tried to carefully define the logic used, so that we have a practical system that meets modders' needs.

Before I begin, I will be the first to say that I do not know Spring's sourcecode backwards and forwards at this time. I am still reading over sections, and as I am at work at the moment, I will not be able to provide any definative quotes from the source in this initial article. However, I will be citing the source as I gain more knowledge.

Section One: What is the goal?

In a word: "flexibility". The entire system can be summarized in one sentence:

"Weapons are nothing but particle systems with additional gameplay effects."

The system I have laid out, below, lays out a variable system that is very flexible- i.e., it has as few interlocking conditions as possible. Fewer interlocking (or mutually-exclusive) conditions means that modders can develop things with relative ease. It also means that coders can keep more things seperated, and call them through includes, which should help make the coding of this area of Spring more straightforward.

Section Two: Non-Units, Scripted Events, Particle Systems

Sub-Sections:

1. Non-Units
1a. A quick example.
1b. Definitions (incomplete, but that's why this a public document)

2. Events
2a. Definitions (variable states, booleans, logical operators, etc.)
2b. Examples of scripted events


1. What are Non-Units?

Non-Units are any game objects that are not maps and are not units. These are interactive objects that are called by game code to do a wide variety of things, from explosion special effects called by an Event to weapons that may have associated special effects, sounds, and other properties. Maps and Units may interact with Non-Units by calling them through Event Scripting, which may be hardcoded parameters within Spring or "soft coded" scripted parameters that become parts of the Unit/Map mix.

If this sounds vague, it is because this is a very general system, which is broken into sub-parts for speed and efficiency, but kept very abstract in order to be flexible and easily modified by coders working on the Spring project. Instead of tying large numbers of things together in one piece of code to get a particular desired goal met, the design here calls for modular pieces that can be altered without having to re-write the whole, and allowing for new things to be added without disrupting the whole.

1a. A Quick Example.

Here is a very simple Non-Unit. This is a weapon that fires from the named PieceNum in the COB script during a FIRE event. It fires a Model, which may have Events tied to *it*. The beauty of the system I'm laying out here (and I admit heavily drawing on my Freelancer experience for this) is that it is referring to other Non-Units to create an entire event (so far as players are concerned, at any rate), instead of being its own special class. Therefore the number of hard-coded things is kept as small as possible- we simply add on more Non-Units with different parameters to create a whole. We can have layers and layers of inter-connected things this way, allowing us to re-use scripts, combine them into new, interesting groups without having to create new ones, etc.

Code: Select all

[MediumCannon]
{
IsWeapon=1;
MyGravity=0;
Velocity=500;
Acceleration=0;
MaxRange=1000;
AreaEffect=16;
Type=SimpleModel;
Model=MediumCannonShell.s3o;
ShadowMe=1;
PlaySound(create)=MedCannon.wav;
PlaySound(death)=MedCannonExplosion.wav;

CallNonUnit(create)=Particle.MediumCannonSmokePuff;
CallNonUnit(create)=Particle.MediumCannonFlare;
CallNonUnit(death)=Particle.MediumCannonExplosion;
CallNonUnit(death)=Particle.MediumCannonExplosionSpawnParticle;

AffectsStat.MaxDamage
{
default=-1000;
SpireRook=-300;
}
}
User avatar
Argh
Posts: 10920
Joined: 21 Feb 2005, 03:38

Post by Argh »

1b. Definitions

Non-Units are defined the following way:

Non-Units have a source point. This is a point in a S3O or 3DO model. It might also be advantageous to make Non-Unit source points be definable in Maps, and allow future Map formats to allow for Non-Unit scripts. These source points would be created by the map compiler, or created by the Spring engine after reading it from the SMD file. As a quick example of how cool and powerful what I am proposing could be, in the context of map-design... we could do semi-random "weather" with various effects through this. Among other things.

Non-Units have one or more attributes. These are TDF-like simple declarations of a boolean/float type, along with more complicated scripting-language calls.

Here is a preliminary list of attributes. Note that it's a pretty long list, compared to the current TDF "tags" used by Spring. That said, this includes all of the attributes needed for particle systems called by any event. With stronger support for calling outside events in BOS/COB, we could do incredible things with this system.

BASE ATTRIBUTES

These are all things that should be set for every NonUnit, regardless of use, because they will cause problems with spawned NonUnits and event scripting if they aren't defined. I may have not picked all of the vital ones out, and will edit if logical flaws are found.

[Name] (Integer). Every NonUnit must have a Name, so that it can be referred to by other NonUnits. In theory, an endless chain of NonUnits could call each other forever. But, without Names, they can't find each other.
IsWeapon (Boolean). If IsWeapon == True, then this NonUnit can be called by a Unit's FBI definition as a weapon, and this NonUnit will get called when the Weapon returns a FIRE state from the COBhandler. Technically, you could call every single NonUnit a Weapon, without causing any harm, but there's really no need, and that state would have to be stored, so it would be best not to. If IsWeapon is undefined, it defaults to 0.
Velocity (Float). The velocity of the NonUnit when it is created, along the Z axis. It is assumed throughout this document that NonUnits use the Z axis by default, and travel "forward". This may mean that they travel at any number of crazy angles, of course- we achieve that by spinning the parent source point (which may be another NonUnit's centerpoint). If undefined, then it defaults to 0. Velocity < 0 = 0.
Acceleration (Float). Acceleration, like Velocity, is defined in OTA "pixels", which we now know are really a fraction of an "elmo". Elmos are 1/8th of a "footprint". Confused yet? So am I ;) If undefined, it defaults to 0. Acceleration may be negative!
MyGravity (Float). Every NonUnit should declare to what extent (if any) it is affected by the universal game constant, gravity. This can be a negative number, indicating that the NonUnit will gradually accelerate upwards, in direct proportion to the amount! If MyGravity is undefined, then it defaults to 0.
Wait (Float). This is the amount of time from the CallNonUnit Event (more about this in Events, below) before the NonUnit is created. If undefined, then it defaults to 0.
User avatar
Argh
Posts: 10920
Joined: 21 Feb 2005, 03:38

Post by Argh »

Optional Attributes

These are... optional. As you will (hopefully) agree, very few of them are redundant, and very few of them require each other to operate. Which is an important goal, assuming what I'm laying out here has any point at all, and gets turned into actual code.

The Type Attribute. Basic visuals.

Type(Integer). This is the drawing method used. Obviously this immediately gets into some of the biggest areas of hard-coded bits- we want, no we need drawing methods to be fast C++ code. Therefore, we have to pick and choose our Types carefully. If no Type is named, then the NonUnit defaults to SimplePoint.

Here are my initial proposals for Types:
SimplePoint (Boolean, exclusive). SimplePoints are just that- they're just points in space, with no graphical abilities whatsoever. Assigning a bitmap to a SimplePoint should be ignored. Why have SimplePoints? Because we could use them to create/control/move other particle systems around, without being obvious. SimplePoints should rotate around the X axis if fired on a ballistic path, so that things continue to look good, but can make use of "DoNotRotate", which is described below.
SimpleModel (Boolean, exclusive). SimpleModels are just like SimplePoints, but they use a S3O/3DO model instead of just a point. They can make use of DoNotRotate.
LaserBlast (Boolean, exclusive). LaserBlasts use the current Spring code for beamweapons, but with two important exceptions: users can assign standard RGB values to them, and assign lengths to them, instead of having Spring decide that based on velocity. Other than these things which are specific to LaserBlast, these are just SimplePoints, really, including the DoNotRotate tag. You cannot name a model if using LaserBeam, but you may name a bitmap, otherwise it uses the default bitmap.
LaserBeam (Boolean, exclusive). Just like the beamlasers in Spring, pretty much. These cannot make use of DoNotRotate, but they can be rotated. You cannot name a model or a bitmap if using LaserBeam.
GlobeCluster (Boolean, exclusive). GlobeClusters are one or more alpha-channeled bitmaps, drawn so that they are always perpindicular to the POV. They can be 32-bit TGA or DDS DXT5. Each bitmap then can move, change color, change size, etc., etc., somewhat independently, according to the additional instructions in the NonUnit's script. These are ideal for everything from smoke to clouds to modeled bits and pieces that would fly out randomly from a destroyed object (which might be a good alternative, in some mods, to using the current ExplosionHandler stuff). GlobeClusters cannot be rotated, and ignore rotational commands.
Flare (Boolean, exclusive). Flares are one or more alpha-channeled bitmaps, drawn along the 0 Z axis by default, with the bottom pixels being the "bottom" of the resulting bitmap, are are rendered so that they are always facing the viewer, but obey proper perspective (OpenGL programmers, please don't laugh at me, but I don't know what that's called). Obvious uses include energy shells, gunflares, etc., etc., etc. They can not make use of DoNotRotate, as they are drawn along the 0 Z axis unless rotated.
Last edited by Argh on 30 Mar 2006, 13:21, edited 1 time in total.
User avatar
Argh
Posts: 10920
Joined: 21 Feb 2005, 03:38

Post by Argh »

Fun with Bitmaps
These variables/operations are for working with particles that are bitmaps. They will not work with models.

ParticleBitmap (Integer). ParticleBitmaps are either 32-bit TGAs (RLE is OK, use the DeVil libraries as we have been) or DDS DTX5 files, with inherent alpha channels. Each NonUnit may have multiple named ParticleBitmaps, which would be chosen at random as Particles are created.
MixBitmap (Integer). MixBitmaps are (usually larger) bitmaps that are randomly "mixed" with ParticleBitmaps, using the ParticleBitmap's alpha channel to determine their border. This is another way of providing huge variety to particles without adding a great deal of suck in terms of performance, and Spring already makes use of this trick for Smoke, to great effect.
MixStrength (Float, requires MixBitmap to be defined or is ignored). If a MixBitmap is defined, this is how much alpha to apply to the MixBitmap before applying it to the ParticleBitmap. A value of 0.5, for example, would fade the MixBitmap 50%. Ignore this variable if MixBitmap is not defined.
ColorShift (Boolean, Exclusive). ColorShift will allow a particle to change colors over time, shifting the bit values RGB towards the ColorShiftTowards value. If the next three variables are not set, but ColorShift==1, then the next three variables are assumed to have values of 0, 1.0 and 255 255 255, respectively.
ColorShiftStartTime (Float). ColorShiftStartTime, like Wait, can delay the onset of the ColorShift sub-event. It is based off of the NonUnit create event.
ColorShiftTime (Float). This is the amount of time it will take to finish the color shift. Some sort've ramping code will need to be developed to make the color transition smooth, obviously.
ColorShiftTowards (Special). ColorShiftTowards is the RGB value that the ColorShift is going to arrive at. It is defined as three numbers with spaces between, between the values 000 and 255. So it would look like this: ColorShiftTowards=255 255 255;
Last edited by Argh on 30 Mar 2006, 14:08, edited 2 times in total.
User avatar
NOiZE
Balanced Annihilation Developer
Posts: 3984
Joined: 28 Apr 2005, 19:29

Post by NOiZE »

Do you got programming skills Argh?
User avatar
Argh
Posts: 10920
Joined: 21 Feb 2005, 03:38

Post by Argh »

Fun with Particles

Min/MaxParticles (Integer). This is the mininum/max number of particles to be drawn. If users turn their "max particles" sliders down all the way, this the bare minimum that will be shown is defined by MinParticles, so modders will have to work between Min and Max with some care, so as not to set MinParticles so high that it bogs down lower-end systems, but not so low that particle systems occasionally spawn that look like complete crap even on the most uber-high-end systems. If MaxParticles has been reached, and a particle is calling to be spawned, but no existing particles have yet been destroyed, the game engine should not spawn additional particles. That will better than suddenly culling particles that are already doing something dynamic, which would be lame-looking.

Making Particles change size dynamically over time.

InitialSize (Float, must be non-zero, non-negative). InitialSize is how large/small the particle is. I do not know if Spring has any code for doing size transforms with models, but it does for bitmaps. If this variable is not defined, then it is always assumed to be 1.0.
MaxSize (Float, must be non-zero, non-negative). MaxSize is how large/small the particle can become over time, subject to some random variables, below. If not defined, this is assumed to be 1.0. MaxSize may be smaller than InitialSize!
GrowthRateMax/Min (Float, can be negative). GrowthRateMax/Min represents the maximum/minimum growth rate for a particle. As this can be a negative number, particles can shrink over time, but will never go smaller than their MaxSize.

Killing/Fading Particles

DeathRate (Float, from 0 to 1.0). DeathRate is how often a particle dies. A randomizer picks a number between 0.000001 and 1.0 for each particle every few ticks (how many should be determined by performance reviews) and then kills any particles that come up "unlucky". If undefined, this is set at 0.
DeathTime (Float). DeathTime is the maximum life of a particle. If it is reached, but the particle has not been killed already, the particle is killed. If undefined, the particle is alive unless an Event or other condition kills it.
FadeRate (Float, from 0 to 1.0) FadeRate is how quickly the particle alpha-fades away. Particles reaching 0 are killed. If undefined, this is 0.
FadeTime (Float). Like DeathTime, this is the amount of time to apply FadeRate. After this time is reached, the particle will fade no further. Useful for effects you want to travel long distances without completely disappearing.

Moving Particles around

DriftRate assumes that both DriftRateMax and DriftRateMin are non-zero. If one or the other is undefined, it gets the value of the variable that is defined.
DriftRateMax_XYZ (Float Float Float) This is the amount of random XYZ drift to apply to particles. Proper syntax is DriftRateMax=1.0 1.0 1.0, which would produce a random drift XYZ between DriftWaits of 1.0.
DriftRateMin_XYZ (Float Float Float) This is the minimum amount of random XYZ drift to apply between DriftWaits.
DriftWait (Float) this is the amount of time, in seconds, before the next DriftRate is applied.
User avatar
Argh
Posts: 10920
Joined: 21 Feb 2005, 03:38

Post by Argh »

Particles creating new Particles

As a practical (and fast) way around doing this through Events, I feel that we should have something called a Caster. This is just a Particle that can call another NonUnit. Nothing more, nothing less... but very, very powerful. Using this, we can layer NonUnit on one another very easily. Going with my initial precept, that means that if we build this, then we can have Weapons that "cast" other Weapons from themselves, among other things. Or "rainclouds" that pour down "rain" that slows Units passing below it (the "rain" would be a weapon that affects MaxVelocity).

Caster (Boolean) Caster defines a Particle as a Caster.
CastNonUnit (Integer) The particle to be cast.
... and that's all. The rest is controlled by the Particle being cast. So if you have a "raincloud" NonUnit that is set up to drift over a battlefield, and it casts a "raindrop" NonUnit, you will have to design it with a fan-like drift and MyGravity=1;, so that the "rain" drops.

Really Specific Things That Don't Fit Anywhere Else

This is the catch-all, for everything that is really odd or for very specific uses.

Cone (Boolean) Cone should probably be defined as a Type, but I am not quite sure. Very simply, particles generated by a NonUnit with Cone defined will travel along the cone. If they Drift against the sides of the Cone, they will go no further.
ConeAngle (Float) The larger the ConeAngle, the larger the distance particles will be able to Drift.

ShadowMe (Boolean). This is a bit of complicated drawing mess. Luckily, all of the code to do that has already been done. If ShadowMe==1, and the particle is a bitmap, then the areas where the alpha channel are black will not be drawn when the shadowmask is created. If ShadowMe==1, and a Model is defined, then the Model casts a shadow. If ShadowMe<>1, and a Model or bitmap is defined, then the model/bitmap casts no shadow. Potentially, this is a very big performance helper/buster, depending how it is used.
User avatar
Argh
Posts: 10920
Joined: 21 Feb 2005, 03:38

Post by Argh »

And, finally... weapon-like effects.

I put the weapon-like definitions at the end, to kind've drive home my point- weapons are nothing more than particle effect systems with some extra junk added. So here's the extra junk. Even if everybody on the Spring team ignores the main thrust of this 10+ hours of my free time, I hope that you will look at this part. Just this one set of things could totally change the Spring developing environment, for the better.

These things are only active if IsWeapon==1.

ReloadRate (Float) Just like it is, with one change- Bursts start at the end of ReloadTime. No more fussing about with WeaponTimer.

Things I would keep from OTA, and see no reason to change at all. If they're not on here, they're not defined any more. Many things, like SprayAngle, were totally redundant, or like RandomDecay, are already defined in the NonUnit definitions above. Keep in mind that each Burst is a series of NonUnits, which may be spawning particles that can do damage, etc., etc... performance could really go in the toilet if people get crazy piling things on, very easily, but that's for modders to figure out.
Burst
BurstRate
Accuracy
AccuracyWhileMoving
FireStarter
Tolerance
NoExplode
GroundBounce
AreaOfEffect


ImpulseFactor (Float) Would be its own, seperate variable, not tied to Damage.
Crater (Boolean) Need to set this to have the Weapon make Craters.
If Crater==1, then these two must be defined, or they are both defined as 1.0;
CraterDepth(Float) The amount to dent the ground downwards.
CraterWidth(Float) The amount to dent the ground outwards.



TurnRate (Float). This is a value, in degrees, of the amount of turn the NonUnit can make, seeking the Target. "Guided, self-propelled", etc., from OTA is assumed. I never understood why they did things the way they did, and I see no point in having redundancies.

CanIntercept (Special). This is a series of named Weapon that this Weapon can Intercept. If the Unit is not targeting something more important with this Weapon, then the Weapon can fire at the named Weapons.

Syntax:

CanIntercept.Enemy
{
Nuke
ReallyHugeNuke
TotallyGiganticNuke
}

Repulses (Special) This is the PlasmaRepulsor, without the current limitations. Again, syntax is like CanIntercept:

Repulses.Enemy
{
SmallPlasma
SmallLaser
SmallRocket
}

It would probably be best to assign it some other variables, as we seem to be going towards, but I'm getting too tired to think that through at the moment.

AffectsStat (Special) Here's the biggest item on this wishlist, folks... Weapons that can affect stats other than Paralyze and MaxDamage. This would use the ARMOR definitions, just like Damage currently does, for simplicity's sake. You can assign more than one AffectsStat to a Weapon, too, for endless tweaking- truely, this would be worth its weight in gold, all by itself.

Syntax:

Here's a Weapon that slows down enemy Units if hit. Just one teensy example. This could RULE.

AffectsStat.Enemy.MaxVelocity
{
default=-1;
SpireRook=0;
}

AffectsStatField (Special) And here's yet another giant wishlist item. It's a simple spherical "field" that projects around the Unit, that has an affect on a stat. Depending on whether Enemy/Friend/Neutral was selected, you could have awesome gameplay, where, for example, a "healer" could occasionally buff the health of all units (up to MaxDamage, of course)... a "wizard" could cast a "spell" that would speed up the movement rates of everybody around him, etc., etc., etc. The uses of this stuff are nearly endless!

Syntax:

AffectsStatField.Friend.Health
{
default=100;
}


... and now I need to get some sleep. I realise that this bit at the end will seem a bit ragged, and probably has some mistakes, but here are 3 of the 4 named NonUnits from the example Weapon that is at the very beginning of the article.

Code: Select all

//A small puff of semi-random smokepuffs that will appear at the weapon's source point, floating upwards and 

outwards in a semi-random fashion.  
Particle.MediumCannonSmokePuff
{
Type=GlobeCluster;
ShadowMe=1;
MyGravity=-0.100;
MinParticles=1;
MaxParticles=10;
ParticleBitmap1=SmokeCloud1.tga;
MixBitmap=SmokeBackGround.tga;
MixStrength=1;
InitialSize=0.1;
MaxSize=1.0;
GrowthRateMax=0.25;
GrowthRateMin=0.1;
DeathRate=0.5;
FadeRate=4.0;
}

//A bright white flare that fades to a dark red over 0.75 seconds.  
//Flares are always aligned with the z-axis of the source point, but can make use of other ParticleSystem 

properties.
Particle.MediumCannonFlare
{
CalledFromWeaponSource=1;
Type=Flare;
ShadowMe=0;
MyGravity=0;
MinParticles=1;
MaxParticles=1;
Graphic=GunFlare1.tga;
InitialSize=0.1;
MaxSize=1;
GrowthRateMax=0.75;
GrowthRateMin=0.45;
FadeTime=0.75;
ColorShift=1;
ColorShiftStartTime=0;
ColorShiftTime=0.75;
ColorShiftTowards=000 100 000;
}

//A big, fancy smoke cloud called when the weapon impacts something, and casts another particle from itself.
Particle.MediumCannonExplosion
{
CalledWhenWeaponHits=1;
Type=GlobeCluster;
Caster=1;
CastType=Random;
CastParticle=Particle.MediumCannonExplosionSpawnParticle;
CastMax=10;
CastMin=4;
CastStrength=10;
ShadowMe=1;
MyGravity=-0.250;
MinParticles=10;
MaxParticles=20;
Graphic=SmokeCloud1.tga;
}
All right, now you can start flaming me for the Longest Wishlist Ever.
User avatar
jcnossen
Former Engine Dev
Posts: 2440
Joined: 05 Jun 2005, 19:13

Post by jcnossen »

Still this is not a very unified approach I think. You have specified a lot of tags just to allow weapons be handled like particles. It's probably better to specify weapons seperately from particles, so each system gets less tags/complexity.

Too big to comment more for now ;) I have to study...
User avatar
Argh
Posts: 10920
Joined: 21 Feb 2005, 03:38

Post by Argh »

I can see handling weapons seperately, and then calling the particle effects through the weapon, say "FireEffect=particle.FireMediumCannon, ExplosionEffect=particle.ExplosionMedium", etc., etc..

But I don't see how we can reduce the tags a great deal, without losing features. Somewhere along the line, we have to deal with the fact that a real, configurable particle system has to have a lot of control variables to be useful. I cut the number of tags down to the smallest number that I thought would still be useful- there were a lot've things I thought up that just seemed to be only semi-useful, or just were too specific.

Almost everything named in here is in Spring now- drifting, resizing, use of bitmaps, color-shifting, etc., is all in there already, we just need the tags to be able to specify how they work. The tags are a way to access and give parameters to things we already have, really- the existing code for these things would need minor changes. The main really new features are in the ability to filter weapon affects through FBI variables, instead of doing it through one-time kludges. Such an approach, all by itself, could open up a lot of possiblities in terms of game design.
User avatar
AF
AI Developer
Posts: 20687
Joined: 14 Sep 2004, 11:32

Post by AF »

I think for the moment unifying the weapons classes into one big weapons class should do a lot fo what this is needed for, then we can move onwards from that towards somethign similair t this.

But I doubt even this could be done without merging all the weapons classes together first.......
User avatar
jcnossen
Former Engine Dev
Posts: 2440
Joined: 05 Jun 2005, 19:13

Post by jcnossen »

Ok further generalization of tags doesn't seem possible as you said. Except if you would think of more dynamic tags with an expression system of some sort. But that is moving towards full scripting, in which case you can better do full scripting directly.
A scripting language specially for scripting weapons and projectiles (both need a lot of speed and math support, and less general scripting language features) is something that could be considered though.
Instead of tags you would have functions to handle creation/update/drawing/collision and simple expressions to handle position and drawing sprites or lines (lasers) or models....

Simply unifiying all existing weapons code into one class is the opposite of abstraction and seperation, so it's something that really should be avoided (You would be creating a class that is far too complex to handle and extend easily).
User avatar
Dragon45
Posts: 2883
Joined: 16 Aug 2004, 04:36

Post by Dragon45 »

I thought the next system was primarily about abstracting + encapsulating units/weapons/features *away* from the main engine, hooking LUA into this somehow?


In any case i think it would be cool if weapons/units/features *were* seperate DLLs and what have you from the main engine so that it woudl be easier for coders to create new weapon types and behaviors without having to distribute their engine hacks or asking to have thier hacks distributed i nthe main release.
Post Reply

Return to “Engine”