Game Development Community

Developing a Battle System (discussion)

by Infinitum3D · in Torque Game Engine · 05/06/2009 (9:45 am) · 67 replies

I have TGE 1.5.2

The way I see it, there are three types of battles;
1. melee
2. ranged
3. magic

Torque has a basic 'ranged' battle system in effect.

I'm looking at player.cs and crossbow.cs first.

player.cs has datablock PlayerData(PlayerBody)
with the definition "maxDamage = 100;" which is commonly referred to as 'hit points'.

further down;
function Armor::onImpact applies damage %obj.damage()

and function Armor::damage applies damage
%obj.applyDamage(%damage);

and function Armor::onDamage does 3 things:
1.checks to see if player is dead
2.'flashes' the screen to show damage being done
3. plays an audio (pain) file

There are also the onEnterLiquid damage switch for Lava and finally the function Player::kill which looks like it assigns 10000 points damage to the player.


Crossbow.cs uses datablock ProjectileData(CrossbowProjectile)
which has the definitions
directDamage=20;
radiusDamage=20;

and further down
function CrossbowProjectile::onCollision()
which applies damage to ShapeBaseObjectTypes

So, what is my question?

Obviously this all applies to a 'ranged' battle system.

Would anyone care to comment on how they've adapted this to fit their game?

Are modifiers added through script, or are engine changes used? For example, if I wanted a more powerful (or less powerful) crossbow projectile, I could simple increase (or decrease) tthe directDamage variable.

But what I'm looking for is a discussion on various ways to modify this inherent battle system in TGE.

I know a 'melee' system resource exists (I've already added it), but for now, maybe we can just discuss 'ranged' modifications?

Any comments?

Tony
#21
05/07/2009 (10:04 am)
I would shy away from global variables - they're inelegant. If you're mounting the sword as a weapon image, you can give it a damage amount. So yes, what Thomas suggested - you'd store 'strength' as a property of each Player object, and use it to modify the basic weapon damage.

Quote:Since datablocks are static, how does a changing value happen? Where do I keep track of the change?
Like I mentioned, store it in the Player object. If you want to be able to save your level and come back to it after closing the engine, you'll need to look at writing save files with the relevant character data.

Quote:If I want 10 different crossbow projectile types, its not so bad. But if I want a hundred different types, it gets kinda messy.
Do you really need a hundred types? I can see what you're getting at, though. The amount of stuff you need to write can be minimised by using the syntax to make the datablock a copy of another one, like so:
datablock ProjectileData(CrossbowProjectile)
{
   shapeFile = "shape";
   mass = 5;
   something = 1;
   somethingElse = 3;
   damage = 10;
};

datablock ProjectileData(SuperCrossbowProjectile : CrossbowProjectile)
{
   //All the data from CrossbowProjectile is put in here automatically
   //so we don't need to add it
   //But we want different damage, so overwrite it
   damage = 20;
};
If you really do need lots of projectiles that vary slightly, it might be good to look at implementing asolution specific to your game, maybe to break some ProjectileData members into the Projectile class itself so they are allocated for each Projectile, not each datablock.
#22
05/07/2009 (10:39 am)
Quote:
Like I mentioned, store it in the Player object. If you want to be able to save your level and come back to it after closing the engine, you'll need to look at writing save files with the relevant character data.

That's the reason why i am thinking about a general data structure in a DLL (could be also done in the Engine itself). Before a level is loaded you have to fill the data structure with default values or specific values from a previous saved savegame.
A possible solution could be to give each object in a level (each object in the Missiongroup) a unique ID. Not to be confused with the ID of the objects in the Engine itself.
Now you have the connection between the object and the stored values in the data structure.

In addition to that you have all necessary values in one place (the data structure itself ). So you dont need to find these values from here and from there when you want to create a savefile.
#23
05/07/2009 (11:22 am)
I really don't need a hundred different projectiles, but what I would like is to have a generic projectile that deals damage based on the player's attributes and experience.

If you can't alter a datablock, I still don't see how you affect the projectile without creating a datablock for every possible permutation of attribute and experience value.

Its seems to me like there should be a way to say:

%directDamage = (%playerExperience / %playerLevel);

but to do this, there has to be somewhere to define the three arguments. If $GlobalVariables are inelegant, I'm still confused. Considering my options...

Thanks for your comments!!!

Please continue to discuss :)

Tony
#24
05/07/2009 (11:39 am)
Quote:If you can't alter a datablock, I still don't see how you affect the projectile without creating a datablock for every possible permutation of attribute and experience value.
You can set the values of the Projectile object directly:
function CrossbowImage::onFire(%this, %obj, %slot)
//Note: %this is the ShapeBaseImageData, %obj is the Player
{
   %projectile = %this.projectile;

   //blah blah...

   // Create the projectile object
   %p = new Projectile() {
      dataBlock        = %projectile;
      initialVelocity  = %muzzleVelocity;
      initialPosition  = %obj.getMuzzlePoint(%slot);
      sourceObject     = %obj;
      sourceSlot       = %slot;
      client           = %obj.client;
      //// **********
      dynamicDamage = %obj.experience; //Add this!!
      //// **********
   };
   MissionCleanup.add(%p);
   return %p;
}

function CrossbowProjectile::onCollision(%this,%obj,%col,%fade,%pos,%normal)
//%this is the ProjectileData, %obj is the Projectile
{
   // Apply damage to the object all shape base objects
   //*******
   //Apply dynamic damage
   %damage = %this.directDamage + %obj.dynamicDamage;
   if (%col.getType() & $TypeMasks::ShapeBaseObjectType)
      %col.damage(%obj,%pos,%damage,"CrossbowBolt");
   //*******

   // Radius damage is a support scripts defined in radiusDamage.cs
   // Push the contact point away from the contact surface slightly
   // along the contact normal to derive the explosion center. -dbs
   radiusDamage(%obj, %pos, %this.damageRadius, %this.radiusDamage, "Radius", %this.areaImpulse);
}

Remember whenever you create a new object, you have the opportunity to set members that aren't defined explicitly in code (in method initPersistFields). You can also do this at any time by simply saying something like:
%objID.myNewMember = 2;
Now you can read myNewMember from that object, and that object only.
#25
05/07/2009 (12:47 pm)
@Infinitum3D:
Daniel ist talking about dynamic fields. So you can enhance your object with a lot of additional properties. Important: Dynamic fields aren't networked. No additional traffic.
#26
05/07/2009 (1:09 pm)
Quote:Important: Dynamic fields aren't networked. No additional traffic.
Whoops, didn't realise that :P. I guess it makes sense - you just have to remember not to try to access these members in client-side script.
#27
05/07/2009 (2:25 pm)
So all Dynamic fields are ServerSide? Does that mean it requires a commandToServer call from a function? That would increase network traffic, right?

I'm sure I misunderstand this.

@Daniel, I looked at the crossbow onFire function, where it creates a new object for the projectile... Would a Case/Switch here for various projectile types work?

Case 1: regular projectile
Case 2: fire projectile
Case 3: ice projectile
etc.
etc.?
#28
05/07/2009 (2:36 pm)
You dont need onFire.

new ActionMap(myActionMap);
myActionMap.bind(mouse0, button0, Weapfire );

function Weapfire(%val)
{
   if (%val)
   {
      echo("Mouse Button Press");
      commandToServer('StartFiring');
   } 
   else
   {
      echo("Mouse Button Release");
      commandToServer('StopFiring');
   }
}

function serverCmdStartFiring(%clientConn)
{
   Fire(%clientConn);
}

function Fire(%clientConn)
{
   echo("Starting: Create Projectile");
   %projectile = DBprojectile; // the datablock of the projectile
   %obj = %clientConn.player;
   %muzzleVector = %obj.getMuzzleVector(0);
   %objectVelocity = %obj.getVelocity();
   
   %muzzleVelocity = VectorAdd(
      VectorScale(%muzzleVector, %projectile.muzzleVelocity),
      VectorScale(%objectVelocity, %projectile.velInheritFactor));
      
   %p = new Projectile() 
   {
      dataBlock        = %projectile;
      initialVelocity  = %muzzleVelocity;   
      initialPosition  = %obj.getMuzzlePoint(0);
      sourceObject     = %obj;
      sourceSlot = 0;
   };
   
   MissionGroup.add(%p);
   $schedID_WeaponFire = schedule(100,0,Fire,%clientConn); 
   // shooting frequency = 100 ms ( 10 projectiles / second )
}

function serverCmdStopFiring(%clientConn)
{
   echo("Stop: Create Projectile");
   cancel($schedID_WeaponFire);
}
#29
05/07/2009 (2:46 pm)
That's cool, but can you explain why/how this is better than using onFire?
#30
05/07/2009 (2:55 pm)
I dont know. I had only the idea to do it this way.
I thought about to press and hold down the left mouse key to create projectiles every xyz milliseconds until i release the left mouse button.

You can modify the code to fire projectiles dependent of the current weapon.

Insert a switch-statement in the function serverCmdStartFiring(%clientConn) to execute a function for weapon1, for weapon2 and so on...

When you combine the modified code with different weapon damages... ;-)
#31
05/07/2009 (4:13 pm)
Nice! It can be used for a new type of crossbow - a Repeater.

Getting back to my original (?) question, it looks like there are basically two ways of defining variables;

1. as a global variable $var
2. as a local variable in a function specifically written for that variable

Tony
#32
05/07/2009 (11:22 pm)
Yes... and ...

3. As property (known as dynamic field) of a object.
This is another kind of global variable.
#33
05/08/2009 (4:10 am)
You can achieve repeating fire using the existing image system, without having to go through server commands. You just need to set up the image states correctly. One thing you can't do *efficiently* with this sytem is burst fire - though I'm working to remedy that.

Tony - yes, a case switch in onFire would be appropriate. Unless you always want one kind of weapon to fire one type of projectile - in which case, you specify the projectile the usual way.

Quote:Does that mean it requires a commandToServer call from a function? That would increase network traffic, right?
Some functions are run on the server, and some are run on the client. The two script folders 'server' and 'client' show you the differences between what server and client scripts handle.
Basically, UI events and input events are handled on the client. Events generated by the game like onAdd, onFire and onCollision are called on the server.
#34
05/08/2009 (5:10 am)
Quote:3. As property (known as dynamic field) of a object.
This is another kind of global variable.

As property of an object... a single specific object, or ALL instances of that object?

If I use new in a function to create a new SuperCrossbow with a certain dynamic variable, but some AI spawn with a SuperCrossbow that has different dynamic variables, does the creation of a NEW SuperCrossbow override the existing... Nevermind. If they have different dynamic variables, then they would be SuperCrossbow1, SuperCrossbow2,... Different types, so different datablocks to begin with...

I think I'm starting to get it.

Quote:The two script folders 'server' and 'client' show you the differences between what server and client scripts handle.

Well, that makes sense. Guess I just never thought about that.
#35
05/08/2009 (7:18 am)
Dynamic fields exist only in the object itself.

Quote:
If I use new in a function to create a new SuperCrossbow with a certain dynamic variable, but some AI spawn with a SuperCrossbow that has different dynamic variables, does the creation of a NEW SuperCrossbow override the existing... Nevermind. If they have different dynamic variables, then they would be SuperCrossbow1, SuperCrossbow2,... Different types, so different datablocks to begin with...

Each object has it's own set of dynamic fields. They are NOT overwritten by another new instance.

Example:

function CreateProjectile(%x, %y, %z)
{
   %p = new Projectile()
   {
      datablock = blub_blub;
      damage_OnAI_Soldier = %x;
      damage_OnAI_Alien = %y;
      damage_OnAI_Mummy = %z;
   }
   MissionGroup.add(%p);
}

CreateProjectile(30,50,40);
CreateProjectile(10,30,80);
CreateProjectile(70,20,10);

Now you have three different projectile-objects(instances) each with different values.
#36
05/08/2009 (7:31 am)
Yes, dynamic fields are individual to each object. An object's position is an example of a field which is stored on a per-object basis: every single object will probably have a different position. Just like every single projectile, if you have a complex damage system, might have different damage amounts.

Datablocks are for values that are supposed to be the same for all instances of an object type.
#37
05/08/2009 (7:36 am)
@Infinitum3D: Daniel ist completely right. That's the reason why datablocks exist. They store read-only values to save bandwidth.

If you have additional read-only values you can also create dynamic fields in datablocks. This is also possible.
#38
05/08/2009 (8:00 am)
So using the previous example script;

function CreateProjectile(%x, %y, %z)
{
   %p = new Projectile()
   {
      datablock = blub_blub;
      damage_OnAI_Soldier = %x;
      damage_OnAI_Alien = %y;
      damage_OnAI_Mummy = %z;
   }
   MissionGroup.add(%p);
}

CreateProjectile(30,50,40);   
CreateProjectile(10,30,80);   
CreateProjectile(70,20,10);

This projectile CreateProjectile(70,20,10); would apply 70 damage to AI_Soldier, 20 to AI_Alien and 10 to AI_Mummy, correct?

Torque automatically follows the pattern CreateProjectile(x, y, z); because
that's the order of the arguments in function CreateProjectile(%x, %y, %z)?
#39
05/08/2009 (8:20 am)
Correct.

%x, %y and %z are only local variables. The same way like in C/C++, Delphi, Visual Basic and so on...
#40
05/08/2009 (9:40 am)
OK, so say I have a 'standard' crossbow that can fire any type of 'bolt'.

normal (the default explosive projectile)
magical flaming bolt
magical ice bolt
globalThermoNuclear bolt
whatever bolt

First, each type needs a datablock, so that we can place them in our world.

Second, when a player collects the items, they get added to the player inventory.

Third, when the player selects the bolt from the inventory screen, the actual bolt is created by a function call, which defines all the specific parameters for that specific bolt, based on player skill level, experience score, bonuses, penalties, and whatever other %arguments we define in the function.
%skill, %experience, %foo, %bar, etc.

Correct so far? This doesn't seem to use Dynamic Fields, though.
Quote:3. As property (known as dynamic field) of a object.
This is another kind of global variable.

A "dynamic field of an object" is created when/how? In a function, since it can't be in a datablock?

Wait, I see...

%p = new Projectile() {   
      dataBlock        = %projectile;   
      initialVelocity  = %muzzleVelocity;   
      initialPosition  = %obj.getMuzzlePoint(%slot);   
      sourceObject     = %obj;   
      sourceSlot       = %slot;   
      client           = %obj.client;   
      //// **********   
      dynamicDamage = %obj.experience; //Add this!!   
      //// **********   
   };

Tony