Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

3D Game Programming All In One (2004)

.pdf
Скачиваний:
139
Добавлен:
17.08.2013
Размер:
17.91 Mб
Скачать

188 Chapter 5 Game Play

for (%i = 0; %i < 8; %i++)

{

if ((%image = %obj.GetMountedImage(%i)) > 0)

if (IsObject(%image.ammo) && %image.ammo.GetId() == %this.GetId()) %obj.SetImageAmmo(%i,%amount != 0);

}

}

function RadiusDamage(%sourceObject, %position, %radius, %damage, %damageType, %impulse)

{

InitContainerRadiusSearch(%position, %radius, $TypeMasks::ShapeBaseObjectType);

%halfRadius = %radius / 2;

while ((%targetObject = ContainerSearchNext()) != 0) { %coverage = CalcExplosionCoverage(%position, %targetObject,

$TypeMasks::InteriorObjectType | $TypeMasks::TerrainObjectType | $TypeMasks::ForceFieldObjectType | $TypeMasks::VehicleObjectType);

if (%coverage == 0) continue;

%dist = ContainerSearchCurrRadiusDist(); %distScale = (%dist < %halfRadius)? 1.0:

1.0 - ((%dist - %halfRadius) / %halfRadius); %targetObject.Damage(%sourceObject, %position,

%damage * %coverage * %distScale, %damageType); if (%impulse) {

%impulseVec = VectorSub(%targetObject.GetWorldBoxCenter(), %position);

%impulseVec = VectorNormalize(%impulseVec);

%impulseVec = VectorScale(%impulseVec, %impulse * %distScale); %targetObject.ApplyImpulse(%position, %impulseVec);

}

}

}

The weapon management system contained in this module assumes all primary weapons are mounted into the slot specified by the $WeaponSlot variable.

The first method defined, Weapon::onUse, describes the default behavior for all weapons when used: mount it into the object's $WeaponSlot weapon slot, which is currently set to slot 0. A message is sent to the client indicating that the mounting action was successful. Picture this: You are carrying a holstered pistol. When the Use command is sent to the server after being initiated by some key binding, the pistol is removed from the holster, figuratively speaking, and placed in image slot 0, where it becomes visible in the player's hand. That's what takes place when you "use" a weapon.

Team LRN

Server Control Modules

189

The next method, Weapon::onPickup, is the weapon's version of what happens when you collide with a weapon, and the onCollision method of the MaleAvatar decides you need to pick this weapon up. First, the parent Item method performs the actual pickup, which involves the act of including the weapon in our inventory. After that has been handled, we get control of the process here. What we do is automatically use the weapon if the player does not already have one in hand.

When the Item inventory code detects a change in the inventory status, the Weapon::onInventory method is called in order to check if we are holding an instance of the weapon in a mount slot, in case there are none showing in inventory. When the weapon inventory has changed, make sure there are no weapons of this type mounted if there are none left in inventory.

The method WeaponImage::onMount is called when a weapon is mounted (used). We use it to set the state according to the current inventory.

If there are any special effects we want to invoke when we pick up a weapon, we would put them in the Ammo::onPickup method. The parent Item method performs the actual pickup, and then we take a crack at it. If we had booby-trapped weapons, this would be a good place to put the code.

Generally, ammunition is treated as an item in its own right. The Ammo::onInventory method is called when ammo inventory levels change. Then we can update any mounted images using this ammo to reflect the new state. In the method we cycle through all the mounted weapons to examine each mounted weapon's ammo status.

RadiusDamage is a pretty nifty function that we use to apply explosion effects to objects within a certain distance from where the explosion occurred and to impart an impulse force on each object to move it if called for.

The first statement in the function uses InitContainerRadiusSearch to prepare the container system for use. It basically indicates that the engine is going to search for all objects of the type $TypeMasks::ShapeBaseObjectType located within %radius distance from the location specified by %position. See Table A.1 in Appendix A for a list of available type masks. Once the container radius search has been set up, we then will make successive calls to ContainerSearchNext. Each call will return the handle of the objects found that match the mask we supplied. If the handle is returned as 0, then the search has finished.

So we enter a nicely sized while loop that will continue as long as ContainerSearchNext returns a valid object handle (nonzero) in %targetObject. With each object found, we calculate how much of the object is affected by the explosion but only apply this calculation based on how much of the explosion is blocked by certain types of objects. If an object of one of these types has completely blocked the explosion, then the explosion coverage will be 0.

Team LRN

190Chapter 5 Game Play

Then we use the ContainerSearchCurrRadiusDist to find the approximate radius of the affected object and subtract that value from the center-of-explosion to center-of-object distance to get the distance to the nearest surface of the object. Next, damage is applied that is proportional to this distance. If the nearest surface of the object is less than half the radius of the explosion away, then full damage is applied.

Finally, a proportional impulse force vector, if appropriate, is applied using modified distance scale. This has the effect of pushing the object away from the center of the blast.

control/server/weapons/crossbow.cs

For each weapon in our game, we need a definition module that contains the specifics for that weapon—its data blocks, methods, particle definitions (if they are going to be unique to the weapon), and other useful stuff.

There is a lot of material here, so if you want to exclude some stuff to cut back on typing, then leave out all of the particle and explosion data blocks. You won't get any cool-look- ing explosions or smoke trails, and you will get some error warnings in your console log file, but the weapon will still work.

The crossbow is a somewhat stylized and fantasy-based crossbow—rather medieval in flavor. It fires a burning bolt projectile that explodes like a grenade on impact. It's cool.

Type in the following code and save it as C:\Emaga5\control\server\weapons\crossbow.cs.

//============================================================================

//control/server/weapons/crossbow.cs

//Copyright (c) 2003 Kenneth C. Finney

//Portions Copyright (c) 2001 GarageGames.com

//Portions Copyright (c) 2001 by Sierra Online, Inc. //============================================================================ datablock ParticleData(CrossbowBoltParticle)

{

textureName

 

= "~/data/particles/smoke";

dragCoefficient

= 0.0;

 

gravityCoefficient

= -0.2;

// rises slowly

inheritedVelFactor

= 0.00;

 

lifetimeMS

 

= 500; // lasts 0.7 second

lifetimeVarianceMS

= 150;

// ...more or less

useInvAlpha = false;

 

 

spinRandomMin = -30.0;

 

spinRandomMax = 30.0;

 

colors[0]

= "0.56 0.36 0.26 1.0";

colors[1]

= "0.56 0.36 0.26 1.0";

colors[2]

= "0 0 0 0";

 

Team LRN

Server Control Modules

191

sizes[0]

= 0.25;

sizes[1]

= 0.5;

sizes[2]

= 1.0;

times[0]

= 0.0;

times[1]

= 0.3;

times[2]

= 1.0;

};

datablock ParticleEmitterData(CrossbowBoltEmitter)

{

ejectionPeriodMS = 10; periodVarianceMS = 5; ejectionVelocity = 0.25; velocityVariance = 0.10;

thetaMin

= 0.0;

thetaMax

= 90.0;

particles = CrossbowBoltParticle;

};

 

 

datablock ParticleData(CrossbowExplosionParticle)

{

 

 

textureName

 

= "~/data/particles/smoke";

dragCoefficient

= 2;

gravityCoefficient

= 0.2;

inheritedVelFactor

= 0.2;

constantAcceleration = 0.0;

lifetimeMS

 

= 1000;

lifetimeVarianceMS

= 150;

colors[0]

= "0.56 0.36 0.26 1.0";

colors[1]

= "0.56 0.36 0.26 0.0";

sizes[0]

= 0.5;

 

sizes[1]

= 1.0;

 

};

datablock ParticleEmitterData(CrossbowExplosionEmitter)

{

ejectionPeriodMS = 7; periodVarianceMS = 0; ejectionVelocity = 2; velocityVariance = 1.0;

ejectionOffset

= 0.0;

thetaMin

= 0;

thetaMax

= 60;

phiReferenceVel

= 0;

phiVariance

= 360;

Team LRN

192 Chapter 5 Game Play

particles = "CrossbowExplosionParticle";

};

datablock ParticleData(CrossbowExplosionSmoke)

{

textureName

 

= "~/data/particles/smoke";

dragCoefficient

= 100.0;

gravityCoefficient

= 0;

inheritedVelFactor

= 0.25;

constantAcceleration = -0.80;

lifetimeMS

 

= 1200;

lifetimeVarianceMS

= 300;

useInvAlpha =

true;

 

spinRandomMin = -80.0;

spinRandomMax = 80.0;

colors[0]

= "0.56 0.36 0.26 1.0";

colors[1]

= "0.2 0.2 0.2 1.0";

colors[2]

= "0.0 0.0 0.0 0.0";

sizes[0]

= 1.0;

sizes[1]

= 1.5;

sizes[2]

= 2.0;

times[0]

= 0.0;

times[1]

= 0.5;

times[2]

= 1.0;

};

datablock ParticleEmitterData(CrossbowExplosionSmokeEmitter)

{

ejectionPeriodMS = 10; periodVarianceMS = 0; ejectionVelocity = 4; velocityVariance = 0.5;

thetaMin

= 0.0;

thetaMax

= 180.0;

lifetimeMS

= 250;

particles = "CrossbowExplosionSmoke";

};

datablock ParticleData(CrossbowExplosionSparks)

{

 

textureName

= "~/data/particles/spark";

Team LRN

Server Control Modules

193

dragCoefficient

= 1;

gravityCoefficient

= 0.0;

inheritedVelFactor

= 0.2;

constantAcceleration = 0.0;

lifetimeMS

 

= 500;

lifetimeVarianceMS

= 350;

colors[0]

= "0.60 0.40 0.30 1.0";

colors[1]

= "0.60 0.40 0.30 1.0";

colors[2]

= "1.0 0.40 0.30 0.0";

sizes[0]

= 0.5;

 

sizes[1]

= 0.25;

sizes[2]

= 0.25;

times[0]

= 0.0;

 

times[1]

= 0.5;

 

times[2]

= 1.0;

 

};

datablock ParticleEmitterData(CrossbowExplosionSparkEmitter)

{

ejectionPeriodMS = 3; periodVarianceMS = 0; ejectionVelocity = 13; velocityVariance = 6.75;

ejectionOffset

= 0.0;

thetaMin

= 0;

thetaMax

= 180;

phiReferenceVel

= 0;

phiVariance

= 360;

overrideAdvances = false;

orientParticles

= true;

lifetimeMS

= 100;

particles = "CrossbowExplosionSparks";

};

datablock ExplosionData(CrossbowSubExplosion1)

{

offset = 1.0;

emitter[0] = CrossbowExplosionSmokeEmitter; emitter[1] = CrossbowExplosionSparkEmitter;

};

datablock ExplosionData(CrossbowSubExplosion2)

{

offset = 1.0;

Team LRN

194 Chapter 5 Game Play

emitter[0] = CrossbowExplosionSmokeEmitter; emitter[1] = CrossbowExplosionSparkEmitter;

};

datablock ExplosionData(CrossbowExplosion)

{

lifeTimeMS = 1200;

particleEmitter = CrossbowExplosionEmitter; // Volume particles particleDensity = 80;

particleRadius = 1;

 

emitter[0]

=

CrossbowExplosionSmokeEmitter;

// Point emission

emitter[1]

=

CrossbowExplosionSparkEmitter;

 

subExplosion[0] = CrossbowSubExplosion1; // Sub explosion objects subExplosion[1] = CrossbowSubExplosion2;

shakeCamera = true;

// Camera Shaking

camShakeFreq = "10.0 11.0 10.0";

 

camShakeAmp = "1.0 1.0 1.0";

 

camShakeDuration = 0.5;

 

camShakeRadius = 10.0;

 

lightStartRadius = 6;

// Dynamic light

lightEndRadius = 3;

 

lightStartColor = "0.5 0.5 0";

 

lightEndColor = "0 0 0";

 

};

datablock ProjectileData(CrossbowProjectile)

{

projectileShapeName = "~/data/models/weapons/bolt.dts";

directDamage

 

= 20;

radiusDamage

 

= 20;

damageRadius

 

= 1.5;

explosion

 

= CrossbowExplosion;

particleEmitter

= CrossbowBoltEmitter;

muzzleVelocity

= 100;

velInheritFactor

= 0.3;

armingDelay

 

= 0;

lifetime

 

= 5000;

fadeDelay

 

= 5000;

bounceElasticity

= 0;

bounceFriction

= 0;

isBallistic

 

= true;

gravityMod = 0.80;

 

hasLight

= true;

 

lightRadius = 4.0;

Team LRN

Server Control Modules

195

lightColor = "0.5 0.5 0";

};

function CrossbowProjectile::OnCollision(%this,%obj,%col,%fade,%pos,%normal)

{

if (%col.getType() & $TypeMasks::ShapeBaseObjectType) %col.damage(%obj,%pos,%this.directDamage,"CrossbowBolt");

RadiusDamage(%obj,%pos,%this.damageRadius,%this.radiusDamage,"CrossbowBolt",0);

}

datablock ItemData(CrossbowAmmo)

{

category = "Ammo"; className = "Ammo";

shapeFile = "~/data/models/weapons/boltclip.dts"; mass = 1;

elasticity = 0.2; friction = 0.6;

// Dynamic properties defined by the scripts pickUpName = "crossbow bolts";

maxInventory = 20;

};

datablock ItemData(Crossbow)

{

category = "Weapon"; className = "Weapon";

shapeFile = "~/data/models/weapons/crossbow.dts"; mass = 1;

elasticity = 0.2; friction = 0.6; emap = true;

pickUpName = "a crossbow"; image = CrossbowImage;

};

datablock ShapeBaseImageData(CrossbowImage)

{

shapeFile = "~/data/models/weapons/crossbow.dts"; emap = true;

mountPoint = 0;

eyeOffset = "0.1 0.4 -0.6"; correctMuzzleVector = false; className = "WeaponImage"; item = Crossbow;

Team LRN

196

Chapter 5 Game Play

 

 

ammo = CrossbowAmmo;

 

 

projectile = CrossbowProjectile;

 

 

projectileType = Projectile;

 

 

stateName[0]

= "Preactivate";

 

stateTransitionOnLoaded[0]

= "Activate";

 

stateTransitionOnNoAmmo[0]

= "NoAmmo";

 

stateName[1]

= "Activate";

 

stateTransitionOnTimeout[1]

= "Ready";

 

stateTimeoutValue[1]

= 0.6;

 

stateSequence[1]

= "Activate";

 

stateName[2]

= "Ready";

 

stateTransitionOnNoAmmo[2]

= "NoAmmo";

 

stateTransitionOnTriggerDown[2]

= "Fire";

 

stateName[3]

= "Fire";

 

stateTransitionOnTimeout[3]

= "Reload";

 

stateTimeoutValue[3]

= 0.2;

 

stateFire[3]

= true;

 

stateRecoil[3]

= LightRecoil;

 

stateAllowImageChange[3]

= false;

 

stateSequence[3]

= "Fire";

 

stateScript[3]

= "onFire";

 

stateName[4]

= "Reload";

 

stateTransitionOnNoAmmo[4]

= "NoAmmo";

 

stateTransitionOnTimeout[4]

= "Ready";

 

stateTimeoutValue[4]

= 0.8;

 

stateAllowImageChange[4]

= false;

 

stateSequence[4]

= "Reload";

 

stateEjectShell[4]

= true;

 

stateName[5]

= "NoAmmo";

 

stateTransitionOnAmmo[5]

= "Reload";

 

stateSequence[5]

= "NoAmmo";

 

stateTransitionOnTriggerDown[5]

= "DryFire";

 

stateName[6]

= "DryFire";

 

stateTimeoutValue[6]

= 1.0;

 

stateTransitionOnTimeout[6]

= "NoAmmo";

 

};

 

 

function CrossbowImage::onFire(%this, %obj, %slot)

 

{

 

%projectile = %this.projectile; %obj.decInventory(%this.ammo,1); %muzzleVector = %obj.getMuzzleVector(%slot);

Team LRN

Server Control Modules

197

%objectVelocity = %obj.getVelocity(); %muzzleVelocity = VectorAdd(

VectorScale(%muzzleVector, %projectile.muzzleVelocity),

VectorScale(%objectVelocity, %projectile.velInheritFactor));

%p = new (%this.projectileType)() {

dataBlock

= %projectile;

initialVelocity

= %muzzleVelocity;

initialPosition

= %obj.getMuzzlePoint(%slot);

sourceObject

= %obj;

sourceSlot

= %slot;

client

= %obj.client;

};

MissionCleanup.add(%p); return %p;

}

We will cover the contents of the particle, explosion, and weapon datablocks in detail in later chapters when we start creating our own weapons. Therefore we will skip discussion of these elements for now and focus on the data block's methods.

The first method, and one of the most critical, is the CrossbowProjectile::OnCollision method. When called, it looks first to see if the projectile has collided with the right kind of object. If so, then the projectile's damage value is applied directly to the struck object. The method then calls the RadiusDamage function to apply damage to surrounding objects, if applicable.

When shooting the crossbow, the CrossbowImage::onFire method is used to handle the aspects of firing the weapon that cause the projectile to be created and launched. First, the projectile is removed from inventory, and then a vector is calculated based upon which way the muzzle is facing. This vector is scaled by the specified muzzle velocity of the projectile and the velocity inherited from the movement of the crossbow (which gets that velocity from the movement of the player).

Finally, a new projectile object is spawned into the game world at the location of the weapon's muzzle—the projectile possesses all of the velocity information at the time of spawning, so when added, it immediately begins coursing toward its target.

The projectile is added to the MissionCleanup group before the method exits.

control/server/misc/item.cs

This module contains the code needed to pick up and create items, as well as definitions of specific items and their methods. Type in the following code and save it as C:\Emaga5\control\server\misc\item.cs.

Team LRN