Torque3D/Engine/source/afx/afxMagicMissile.cpp
2022-11-28 21:39:06 -05:00

2122 lines
65 KiB
C++

//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
// Copyright (C) 2015 Faust Logic, Inc.
//
// afxMagicMissile is a heavily modified variation of the stock Projectile class. In
// addition to numerous AFX customizations, it also incorporates functionality based on
// the following TGE resources:
//
// Guided or Seeker Projectiles by Derk Adams
// http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=6778
//
// Projectile Ballistic Coefficients (drag factors) by Mark Owen
// http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=5128
//
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
#include "afx/arcaneFX.h"
#include "scene/sceneRenderState.h"
#include "scene/sceneManager.h"
#include "core/resourceManager.h"
#include "ts/tsShapeInstance.h"
#include "sfx/sfxTrack.h"
#include "sfx/sfxSource.h"
#include "sfx/sfxSystem.h"
#include "sfx/sfxTypes.h"
#include "math/mathUtils.h"
#include "math/mathIO.h"
#include "sim/netConnection.h"
#include "T3D/fx/particleEmitter.h"
#include "T3D/fx/splash.h"
#include "T3D/physics/physicsPlugin.h"
#include "T3D/physics/physicsWorld.h"
#include "gfx/gfxTransformSaver.h"
#include "T3D/containerQuery.h"
#include "T3D/lightDescription.h"
#include "console/engineAPI.h"
#include "lighting/lightManager.h"
#include "afx/util/afxEase.h"
#include "afx/afxMagicMissile.h"
#include "afx/afxMagicSpell.h"
#include "afx/afxChoreographer.h"
class ObjectDeleteEvent : public SimEvent
{
public:
void process(SimObject *object)
{
object->deleteObject();
}
};
IMPLEMENT_CO_DATABLOCK_V1(afxMagicMissileData);
ConsoleDocClass( afxMagicMissileData,
"@brief Defines a particular magic-missile type. (Use with afxMagicSpellData.)\n"
"@tsexample\n"
"datablock afxMagicMissileData(Fireball_MM)\n"
"{\n"
" muzzleVelocity = 50;\n"
" velInheritFactor = 0;\n"
" lifetime = 20000;\n"
" isBallistic = true;\n"
" ballisticCoefficient = 0.85;\n"
" gravityMod = 0.05;\n"
" isGuided = true;\n"
" precision = 30;\n"
" trackDelay = 7;\n"
" launchOffset = \"0 0 43.7965\";\n"
" launchOnServerSignal = true;\n"
"};\n"
"@endtsexample\n"
"@ingroup AFX\n"
);
IMPLEMENT_CO_NETOBJECT_V1(afxMagicMissile);
ConsoleDocClass( afxMagicMissile,
"@brief Magic-missile class used internally by afxMagicSpell. Properties of individual missile types are defined using afxMagicMissileData.\n"
"@ingroup AFX\n"
);
/* From stock Projectile code...
IMPLEMENT_CALLBACK( ProjectileData, onExplode, void, ( Projectile* proj, Point3F pos, F32 fade ),
( proj, pos, fade ),
"Called when a projectile explodes.\n"
"@param proj The projectile exploding.\n"
"@param pos The position of the explosion.\n"
"@param fade The currently fadeValue of the projectile, affects its visibility.\n"
"@see Projectile, ProjectileData\n"
);
IMPLEMENT_CALLBACK( ProjectileData, onCollision, void, ( Projectile* proj, SceneObject* col, F32 fade, Point3F pos, Point3F normal ),
( proj, col, fade, pos, normal ),
"Called when a projectile collides with another object.\n"
"@param proj The projectile colliding.\n"
"@param col The object hit by the projectile.\n"
"@param fade The current fadeValue of the projectile, affects its visibility.\n"
"@param pos The collision position.\n"
"@param normal The collision normal.\n"
"@see Projectile, ProjectileData\n"
);
const U32 Projectile::csmStaticCollisionMask = TerrainObjectType |
InteriorObjectType |
StaticObjectType;
const U32 Projectile::csmDynamicCollisionMask = PlayerObjectType |
VehicleObjectType |
DamagableItemObjectType;
const U32 Projectile::csmDamageableMask = Projectile::csmDynamicCollisionMask;
U32 Projectile::smProjectileWarpTicks = 5;
*/
//--------------------------------------------------------------------------
//
afxMagicMissileData::afxMagicMissileData()
{
INIT_ASSET(ProjectileShape);
INIT_ASSET(ProjectileSound);
/* From stock Projectile code...
explosion = NULL;
explosionId = 0;
waterExplosion = NULL;
waterExplosionId = 0;
*/
/* From stock Projectile code...
faceViewer = false;
*/
scale.set( 1.0f, 1.0f, 1.0f );
isBallistic = false;
/* From stock Projectile code...
velInheritFactor = 1.0f;
*/
muzzleVelocity = 50;
/* From stock Projectile code...
impactForce = 0.0f;
armingDelay = 0;
activateSeq = -1;
maintainSeq = -1;
*/
lifetime = 20000 / 32;
fadeDelay = 20000 / 32;
gravityMod = 1.0;
/* From stock Projectile code...
bounceElasticity = 0.999f;
bounceFriction = 0.3f;
*/
particleEmitter = NULL;
particleEmitterId = 0;
particleWaterEmitter = NULL;
particleWaterEmitterId = 0;
splash = NULL;
splashId = 0;
/* From stock Projectile code...
decal = NULL;
decalId = 0;
*/
lightDesc = NULL;
lightDescId = 0;
starting_vel_vec.zero();
isGuided = false;
precision = 0;
trackDelay = 0;
ballisticCoefficient = 1.0f;
followTerrain = false;
followTerrainHeight = 0.1f;
followTerrainAdjustRate = 20.0f;
followTerrainAdjustDelay = 0;
lifetime = MaxLifetimeTicks;
collision_mask = arcaneFX::sMissileCollisionMask;
acceleration = 0;
accelDelay = 0;
accelLifetime = 0;
launch_node = ST_NULLSTRING;
launch_offset.zero();
launch_offset_server.zero();
launch_offset_client.zero();
launch_node_offset.zero();
launch_pitch = 0;
launch_pan = 0;
launch_cons_s_spec = ST_NULLSTRING;
launch_cons_c_spec = ST_NULLSTRING;
echo_launch_offset = false;
wiggle_axis_string = ST_NULLSTRING;
wiggle_num_axis = 0;
wiggle_axis = 0;
hover_altitude = 0;
hover_attack_distance = 0;
hover_attack_gradient = 0;
hover_time = 0;
reverse_targeting = false;
caster_safety_time = U32_MAX;
}
afxMagicMissileData::afxMagicMissileData(const afxMagicMissileData& other, bool temp_clone) : GameBaseData(other, temp_clone)
{
CLONE_ASSET(ProjectileShape);
projectileShape = other.projectileShape; // -- TSShape loads using projectileShapeName
CLONE_ASSET(ProjectileSound);
splash = other.splash;
splashId = other.splashId; // -- for pack/unpack of splash ptr
lightDesc = other.lightDesc;
lightDescId = other.lightDescId; // -- for pack/unpack of lightDesc ptr
scale = other.scale;
isBallistic = other.isBallistic;
muzzleVelocity = other.muzzleVelocity;
gravityMod = other.gravityMod;
particleEmitter = other.particleEmitter;
particleEmitterId = other.particleEmitterId; // -- for pack/unpack of particleEmitter ptr
particleWaterEmitter = other.particleWaterEmitter;
particleWaterEmitterId = other.particleWaterEmitterId; // -- for pack/unpack of particleWaterEmitter ptr
collision_mask = other.collision_mask;
starting_vel_vec = other.starting_vel_vec;
isGuided = other.isGuided;
precision = other.precision;
trackDelay = other.trackDelay;
ballisticCoefficient = other.ballisticCoefficient;
followTerrain = other.followTerrain;
followTerrainHeight = other.followTerrainHeight;
followTerrainAdjustRate = other.followTerrainAdjustRate;
followTerrainAdjustDelay = other.followTerrainAdjustDelay;
lifetime = other.lifetime;
fadeDelay = other.fadeDelay;
acceleration = other.acceleration;
accelDelay = other.accelDelay;
accelLifetime = other.accelLifetime;
launch_node = other.launch_node;
launch_offset = other.launch_offset;
launch_offset_server = other.launch_offset_server;
launch_offset_client = other.launch_offset_client;
launch_node_offset = other.launch_node_offset;
launch_pitch = other.launch_pitch;
launch_pan = other.launch_pan;
launch_cons_s_spec = other.launch_cons_s_spec;
launch_cons_c_spec = other.launch_cons_c_spec;
launch_cons_s_def = other.launch_cons_s_def;
launch_cons_c_def = other.launch_cons_c_def;
echo_launch_offset = other.echo_launch_offset;
wiggle_magnitudes = other.wiggle_magnitudes;
wiggle_speeds = other.wiggle_speeds;
wiggle_axis_string = other.wiggle_axis_string;
wiggle_num_axis = other.wiggle_num_axis;
wiggle_axis = other.wiggle_axis;
hover_altitude = other.hover_altitude;
hover_attack_distance = other.hover_attack_distance;
hover_attack_gradient = other.hover_attack_gradient;
hover_time = other.hover_time;
reverse_targeting = other.reverse_targeting;
caster_safety_time = other.caster_safety_time;
}
afxMagicMissileData::~afxMagicMissileData()
{
if (wiggle_axis)
delete [] wiggle_axis;
}
afxMagicMissileData* afxMagicMissileData::cloneAndPerformSubstitutions(const SimObject* owner, S32 index)
{
if (!owner || getSubstitutionCount() == 0)
return this;
afxMagicMissileData* sub_missile_db = new afxMagicMissileData(*this, true);
performSubstitutions(sub_missile_db, owner, index);
return sub_missile_db;
}
//--------------------------------------------------------------------------
#define myOffset(field) Offset(field, afxMagicMissileData)
FRangeValidator muzzleVelocityValidator(0, 10000);
FRangeValidator missilePrecisionValidator(0.f, 100.f);
FRangeValidator missileTrackDelayValidator(0, 100000);
FRangeValidator missileBallisticCoefficientValidator(0, 1);
void afxMagicMissileData::initPersistFields()
{
static IRangeValidatorScaled ticksFromMS(TickMs, 0, MaxLifetimeTicks);
addField("particleEmitter", TYPEID<ParticleEmitterData>(), Offset(particleEmitter, afxMagicMissileData));
addField("particleWaterEmitter", TYPEID<ParticleEmitterData>(), Offset(particleWaterEmitter, afxMagicMissileData));
INITPERSISTFIELD_SHAPEASSET(ProjectileShape, afxMagicMissileData, "Shape for the projectile");
addField("scale", TypePoint3F, Offset(scale, afxMagicMissileData));
INITPERSISTFIELD_SOUNDASSET(ProjectileSound, afxMagicMissileData, "sound for the projectile");
/* From stock Projectile code...
addField("explosion", TYPEID< ExplosionData >(), Offset(explosion, ProjectileData));
addField("waterExplosion", TYPEID< ExplosionData >(), Offset(waterExplosion, ProjectileData));
*/
addField("splash", TYPEID<SplashData>(), Offset(splash, afxMagicMissileData));
/* From stock Projectile code...
addField("decal", TYPEID< DecalData >(), Offset(decal, ProjectileData));
*/
addField("lightDesc", TYPEID< LightDescription >(), Offset(lightDesc, afxMagicMissileData));
addField("isBallistic", TypeBool, Offset(isBallistic, afxMagicMissileData));
/* From stock Projectile code...
addField("velInheritFactor", TypeF32, Offset(velInheritFactor, ProjectileData));
*/
addNamedFieldV(muzzleVelocity, TypeF32, afxMagicMissileData, &muzzleVelocityValidator);
/* From stock Projectile code...
addField("impactForce", TypeF32, Offset(impactForce, ProjectileData));
*/
addNamedFieldV(lifetime, TypeS32, afxMagicMissileData, &ticksFromMS);
/* From stock Projectile code...
addProtectedField("armingDelay", TypeS32, Offset(armingDelay, ProjectileData), &setArmingDelay, &getScaledValue,
"The time in milliseconds before the projectile is armed and will cause damage or explode on impact." );
addProtectedField("fadeDelay", TypeS32, Offset(fadeDelay, ProjectileData), &setFadeDelay, &getScaledValue,
"The time in milliseconds when the projectile begins to fade out. Must be less than the lifetime to have an effect." );
addField("bounceElasticity", TypeF32, Offset(bounceElasticity, ProjectileData));
addField("bounceFriction", TypeF32, Offset(bounceFriction, ProjectileData));
*/
addField("gravityMod", TypeF32, Offset(gravityMod, afxMagicMissileData));
// FIELDS ADDED BY MAGIC-MISSILE
//addField("missileShapeName", TypeFilename, myOffset(projectileShapeName));
addField("missileShapeScale", TypePoint3F, myOffset(scale));
addField("startingVelocityVector",TypePoint3F, myOffset(starting_vel_vec));
addNamedField(isGuided, TypeBool, afxMagicMissileData);
addNamedFieldV(precision, TypeF32, afxMagicMissileData, &missilePrecisionValidator);
addNamedFieldV(trackDelay, TypeS32, afxMagicMissileData, &missileTrackDelayValidator);
addNamedFieldV(ballisticCoefficient, TypeF32, afxMagicMissileData, &missileBallisticCoefficientValidator);
addField("collisionMask", TypeS32, myOffset(collision_mask));
addField("followTerrain", TypeBool, myOffset(followTerrain));
addField("followTerrainHeight", TypeF32, myOffset(followTerrainHeight));
addField("followTerrainAdjustRate", TypeF32, myOffset(followTerrainAdjustRate));
addFieldV("followTerrainAdjustDelay", TypeS32, myOffset(followTerrainAdjustDelay), &ticksFromMS);
addNamedField(acceleration, TypeF32, afxMagicMissileData);
addNamedFieldV(accelDelay, TypeS32, afxMagicMissileData, &ticksFromMS);
addNamedFieldV(accelLifetime, TypeS32, afxMagicMissileData, &ticksFromMS);
addField("launchNode", TypeString, myOffset(launch_node));
addField("launchOffset", TypePoint3F, myOffset(launch_offset));
addField("launchOffsetServer",TypePoint3F, myOffset(launch_offset_server));
addField("launchOffsetClient",TypePoint3F, myOffset(launch_offset_client));
addField("launchNodeOffset", TypePoint3F, myOffset(launch_node_offset));
addField("launchAimPitch", TypeF32, myOffset(launch_pitch));
addField("launchAimPan", TypeF32, myOffset(launch_pan));
addField("launchConstraintServer", TypeString, myOffset(launch_cons_s_spec));
addField("launchConstraintClient", TypeString, myOffset(launch_cons_c_spec));
//
addField("echoLaunchOffset", TypeBool, myOffset(echo_launch_offset));
addField("wiggleMagnitudes", TypeF32Vector, myOffset(wiggle_magnitudes));
addField("wiggleSpeeds", TypeF32Vector, myOffset(wiggle_speeds));
addField("wiggleAxis", TypeString, myOffset(wiggle_axis_string));
addField("hoverAltitude", TypeF32, myOffset(hover_altitude));
addField("hoverAttackDistance", TypeF32, myOffset(hover_attack_distance));
addField("hoverAttackGradient", TypeF32, myOffset(hover_attack_gradient));
addFieldV("hoverTime", TypeS32, myOffset(hover_time), &ticksFromMS);
addField("reverseTargeting", TypeBool, myOffset(reverse_targeting));
addFieldV("casterSafetyTime", TypeS32, myOffset(caster_safety_time), &ticksFromMS);
Parent::initPersistFields();
// disallow some field substitutions
onlyKeepClearSubstitutions("particleEmitter"); // subs resolving to "~~", or "~0" are OK
onlyKeepClearSubstitutions("particleWaterEmitter");
onlyKeepClearSubstitutions("sound");
onlyKeepClearSubstitutions("splash");
}
//--------------------------------------------------------------------------
bool afxMagicMissileData::onAdd()
{
if(!Parent::onAdd())
return false;
// ADDED BY MAGIC-MISSILE
// Wiggle axes ////////////////////////////////////////////////////////////
if (wiggle_axis_string != ST_NULLSTRING && wiggle_num_axis == 0)
{
// Tokenize input string and convert to Point3F array
//
Vector<String> dataBlocks(__FILE__, __LINE__);
// make a copy of points_string
dsize_t tokCopyLen = dStrlen(wiggle_axis_string) + 1;
char* tokCopy = new char[tokCopyLen];
dStrcpy(tokCopy, wiggle_axis_string, tokCopyLen);
// extract tokens one by one, adding them to dataBlocks
char* currTok = dStrtok(tokCopy, " \t");
while (currTok != NULL)
{
dataBlocks.push_back(currTok);
currTok = dStrtok(NULL, " \t");
}
// bail if there were no tokens in the string
if (dataBlocks.size() == 0)
{
Con::warnf(ConsoleLogEntry::General, "afxMagicMissileData(%s) invalid wiggle axis string. No tokens found", getName());
delete [] tokCopy;
return false;
}
// Find wiggle_num_axis (round up to multiple of 3) // WARNING here if not multiple of 3?
for (U32 i = 0; i < dataBlocks.size()%3; i++)
{
dataBlocks.push_back("0.0");
}
wiggle_num_axis = dataBlocks.size()/3;
wiggle_axis = new Point3F[wiggle_num_axis];
U32 p_i = 0;
for (U32 i = 0; i < dataBlocks.size(); i+=3, p_i++)
{
F32 x,y,z;
x = dAtof(dataBlocks[i]); // What about overflow?
y = dAtof(dataBlocks[i+1]);
z = dAtof(dataBlocks[i+2]);
wiggle_axis[p_i].set(x,y,z);
wiggle_axis[p_i].normalizeSafe(); // sufficient????
}
delete [] tokCopy;
}
launch_cons_s_def.parseSpec(launch_cons_s_spec, true, false);
launch_cons_c_def.parseSpec(launch_cons_c_spec, false, true);
return true;
}
bool afxMagicMissileData::preload(bool server, String &errorStr)
{
if (Parent::preload(server, errorStr) == false)
return false;
if( !server )
{
if (!particleEmitter && particleEmitterId != 0)
if (Sim::findObject(particleEmitterId, particleEmitter) == false)
Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockId(particleEmitter): %d", particleEmitterId);
if (!particleWaterEmitter && particleWaterEmitterId != 0)
if (Sim::findObject(particleWaterEmitterId, particleWaterEmitter) == false)
Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockId(particleWaterEmitter): %d", particleWaterEmitterId);
/* From stock Projectile code...
if (!explosion && explosionId != 0)
if (Sim::findObject(explosionId, explosion) == false)
Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(explosion): %d", explosionId);
if (!waterExplosion && waterExplosionId != 0)
if (Sim::findObject(waterExplosionId, waterExplosion) == false)
Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(waterExplosion): %d", waterExplosionId);
*/
if (!splash && splashId != 0)
if (Sim::findObject(splashId, splash) == false)
Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockId(splash): %d", splashId);
/* From stock Projectile code...
if (!decal && decalId != 0)
if (Sim::findObject(decalId, decal) == false)
Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(decal): %d", decalId);
*/
_setProjectileSound(getProjectileSound());
if (getProjectileSound() != StringTable->EmptyString())
{
if (!getProjectileSoundProfile())
Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Cant get an sfxProfile for afxMagicMissileData.");
}
if (!lightDesc && lightDescId != 0)
if (Sim::findObject(lightDescId, lightDesc) == false)
Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockid(lightDesc): %d", lightDescId);
}
if (!mProjectileShapeAsset.isNull())
{
projectileShape = mProjectileShapeAsset->getShapeResource();
if (bool(projectileShape) == false)
{
errorStr = String::ToString("afxMagicMissileData::preload: Couldn't load shape \"%s\"", mProjectileShapeAssetId);
return false;
}
/* From stock Projectile code...
activateSeq = projectileShape->findSequence("activate");
maintainSeq = projectileShape->findSequence("maintain");
*/
}
if (bool(projectileShape)) // create an instance to preload shape data
{
TSShapeInstance* pDummy = new TSShapeInstance(projectileShape, !server);
delete pDummy;
}
return true;
}
//--------------------------------------------------------------------------
// Modified from floorPlanRes.cc
// Read a vector of items
template <class T>
bool readVector(Vector<T> & vec, Stream & stream, const char * msg)
{
U32 num, i;
bool Ok = true;
stream.read( & num );
vec.setSize( num );
for( i = 0; i < num && Ok; i++ ){
Ok = stream.read(& vec[i]);
AssertISV( Ok, avar("math vec read error (%s) on elem %d", msg, i) );
}
return Ok;
}
// Write a vector of items
template <class T>
bool writeVector(const Vector<T> & vec, Stream & stream, const char * msg)
{
bool Ok = true;
stream.write( vec.size() );
for( U32 i = 0; i < vec.size() && Ok; i++ ) {
Ok = stream.write(vec[i]);
AssertISV( Ok, avar("vec write error (%s) on elem %d", msg, i) );
}
return Ok;
}
//--------------------------------------------------------------------------
void afxMagicMissileData::packData(BitStream* stream)
{
Parent::packData(stream);
PACKDATA_ASSET(ProjectileShape);
/* From stock Projectile code...
stream->writeFlag(faceViewer);
*/
if(stream->writeFlag(scale.x != 1 || scale.y != 1 || scale.z != 1))
{
stream->write(scale.x);
stream->write(scale.y);
stream->write(scale.z);
}
stream->write(collision_mask);
if (stream->writeFlag(particleEmitter != NULL))
stream->writeRangedU32(particleEmitter->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);
if (stream->writeFlag(particleWaterEmitter != NULL))
stream->writeRangedU32(particleWaterEmitter->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);
/* From stock Projectile code...
if (stream->writeFlag(explosion != NULL))
stream->writeRangedU32(explosion->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);
if (stream->writeFlag(waterExplosion != NULL))
stream->writeRangedU32(waterExplosion->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);
*/
if (stream->writeFlag(splash != NULL))
stream->writeRangedU32(splash->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);
/* From stock Projectile code...
if (stream->writeFlag(decal != NULL))
stream->writeRangedU32(decal->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);
*/
PACKDATA_ASSET(ProjectileSound);
if ( stream->writeFlag(lightDesc != NULL))
stream->writeRangedU32(lightDesc->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);
/* From stock Projectile code...
stream->write(impactForce);
*/
stream->write(lifetime);
/* From stock Projectile code...
stream->write(armingDelay);
stream->write(fadeDelay);
*/
if(stream->writeFlag(isBallistic))
{
stream->write(gravityMod);
/* From stock Projectile code...
stream->write(bounceElasticity);
stream->write(bounceFriction);
*/
stream->write(ballisticCoefficient);
}
if(stream->writeFlag(isGuided))
{
stream->write(precision);
stream->write(trackDelay);
}
stream->write(muzzleVelocity);
mathWrite(*stream, starting_vel_vec);
stream->write(acceleration);
stream->write(accelDelay);
stream->write(accelLifetime);
stream->writeString(launch_node);
mathWrite(*stream, launch_offset);
mathWrite(*stream, launch_offset_server);
mathWrite(*stream, launch_offset_client);
mathWrite(*stream, launch_node_offset);
stream->write(launch_pitch);
stream->write(launch_pan);
stream->writeString(launch_cons_c_spec);
stream->writeFlag(echo_launch_offset);
writeVector(wiggle_magnitudes, *stream, "afxMagicMissile: wiggle_magnitudes");
writeVector(wiggle_speeds, *stream, "afxMagicMissile: wiggle_speeds");
stream->write(wiggle_num_axis);
for (U32 i = 0; i < wiggle_num_axis; i++)
mathWrite(*stream, wiggle_axis[i]);
stream->write(hover_altitude);
stream->write(hover_attack_distance);
stream->write(hover_attack_gradient);
stream->writeRangedU32(hover_time, 0, MaxLifetimeTicks);
stream->writeFlag(reverse_targeting);
stream->write(caster_safety_time);
}
void afxMagicMissileData::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
UNPACKDATA_ASSET(ProjectileShape);
/* From stock Projectile code...
faceViewer = stream->readFlag();
*/
if(stream->readFlag())
{
stream->read(&scale.x);
stream->read(&scale.y);
stream->read(&scale.z);
}
else
scale.set(1,1,1);
stream->read(&collision_mask);
if (stream->readFlag())
particleEmitterId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
if (stream->readFlag())
particleWaterEmitterId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
/* From stock Projectile code...
if (stream->readFlag())
explosionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
if (stream->readFlag())
waterExplosionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
*/
if (stream->readFlag())
splashId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
/* From stock Projectile code...
if (stream->readFlag())
decalId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
*/
UNPACKDATA_ASSET(ProjectileSound);
if (stream->readFlag())
lightDescId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
/* From stock Projectile code...
stream->read(&impactForce);
*/
stream->read(&lifetime);
/* From stock Projectile code...
stream->read(&armingDelay);
stream->read(&fadeDelay);
*/
isBallistic = stream->readFlag();
if(isBallistic)
{
stream->read(&gravityMod);
/* From stock Projectile code...
stream->read(&bounceElasticity);
stream->read(&bounceFriction);
*/
stream->read(&ballisticCoefficient);
}
isGuided = stream->readFlag();
if(isGuided)
{
stream->read(&precision);
stream->read(&trackDelay);
}
stream->read(&muzzleVelocity);
mathRead(*stream, &starting_vel_vec);
stream->read(&acceleration);
stream->read(&accelDelay);
stream->read(&accelLifetime);
launch_node = stream->readSTString();
mathRead(*stream, &launch_offset);
mathRead(*stream, &launch_offset_server);
mathRead(*stream, &launch_offset_client);
mathRead(*stream, &launch_node_offset);
stream->read(&launch_pitch);
stream->read(&launch_pan);
launch_cons_c_spec = stream->readSTString();
echo_launch_offset = stream->readFlag();
readVector(wiggle_magnitudes, *stream, "afxMagicMissile: wiggle_magnitudes");
readVector(wiggle_speeds, *stream, "afxMagicMissile: wiggle_speeds");
if (wiggle_axis)
delete [] wiggle_axis;
wiggle_axis = 0;
wiggle_num_axis = 0;
stream->read(&wiggle_num_axis);
if (wiggle_num_axis > 0)
{
wiggle_axis = new Point3F[wiggle_num_axis];
for (U32 i = 0; i < wiggle_num_axis; i++)
mathRead(*stream, &wiggle_axis[i]);
}
stream->read(&hover_altitude);
stream->read(&hover_attack_distance);
stream->read(&hover_attack_gradient);
hover_time = stream->readRangedU32(0, MaxLifetimeTicks);
reverse_targeting = stream->readFlag();
stream->read(&caster_safety_time);
}
/* From stock Projectile code...
bool ProjectileData::setLifetime( void *obj, const char *index, const char *data )
{
S32 value = dAtoi(data);
value = scaleValue(value);
ProjectileData *object = static_cast<ProjectileData*>(obj);
object->lifetime = value;
return false;
}
bool ProjectileData::setArmingDelay( void *obj, const char *index, const char *data )
{
S32 value = dAtoi(data);
value = scaleValue(value);
ProjectileData *object = static_cast<ProjectileData*>(obj);
object->armingDelay = value;
return false;
}
bool ProjectileData::setFadeDelay( void *obj, const char *index, const char *data )
{
S32 value = dAtoi(data);
value = scaleValue(value);
ProjectileData *object = static_cast<ProjectileData*>(obj);
object->fadeDelay = value;
return false;
}
const char *ProjectileData::getScaledValue( void *obj, const char *data)
{
S32 value = dAtoi(data);
value = scaleValue(value, false);
String stringData = String::ToString(value);
char *strBuffer = Con::getReturnBuffer(stringData.size());
dMemcpy( strBuffer, stringData, stringData.size() );
return strBuffer;
}
S32 ProjectileData::scaleValue( S32 value, bool down )
{
S32 minV = 0;
S32 maxV = Projectile::MaxLivingTicks;
// scale down to ticks before we validate
if( down )
value /= TickMs;
if(value < minV || value > maxV)
{
Con::errorf("ProjectileData::scaleValue(S32 value = %d, bool down = %b) - Scaled value must be between %d and %d", value, down, minV, maxV);
if(value < minV)
value = minV;
else if(value > maxV)
value = maxV;
}
// scale up from ticks after we validate
if( !down )
value *= TickMs;
return value;
}
*/
void afxMagicMissileData::gather_cons_defs(Vector<afxConstraintDef>& defs)
{
if (launch_cons_s_def.isDefined())
defs.push_back(launch_cons_s_def);
if (launch_cons_c_def.isDefined())
defs.push_back(launch_cons_c_def);
};
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// afxMagicMissile
afxMagicMissile::afxMagicMissile()
{
init(true, true);
}
afxMagicMissile::afxMagicMissile(bool on_server, bool on_client)
{
init(on_server, on_client);
}
//--------------------------------------------------------------------------
//--------------------------------------
//
void afxMagicMissile::init(bool on_server, bool on_client)
{
mPhysicsWorld = NULL;
mTypeMask |= ProjectileObjectType | LightObjectType;
mLight = LightManager::createLightInfo();
mLight->setType( LightInfo::Point );
mLightState.clear();
mLightState.setLightInfo( mLight );
mCurrPosition.zero();
mCurrVelocity.set(0, 0, 1);
mCurrTick = 0;
mParticleEmitter = NULL;
mParticleWaterEmitter = NULL;
mSound = 0;
mProjectileShape = NULL;
mDataBlock = NULL;
choreographer = NULL;
if (on_server != on_client)
{
client_only = on_client;
server_only = on_server;
mNetFlags.clear(Ghostable | ScopeAlways);
if (client_only)
mNetFlags.set(IsGhost);
}
else
{
// note -- setting neither server or client makes no sense so we
// treat as if both are set.
mNetFlags.set(Ghostable | ScopeAlways);
client_only = server_only = false;
}
mCurrDeltaBase.zero();
mCurrBackDelta.zero();
collision_mask = 0;
prec_inc = 0.0f;
did_launch = false;
did_impact = false;
missile_target = NULL;
collide_exempt = NULL;
use_accel = false;
hover_attack_go = false;
hover_attack_tick = 0;
starting_velocity = 0.0;
starting_vel_vec.zero();
ss_object = 0;
ss_index = 0;
}
afxMagicMissile::~afxMagicMissile()
{
SAFE_DELETE(mLight);
delete mProjectileShape;
mProjectileShape = NULL;
}
//--------------------------------------------------------------------------
void afxMagicMissile::initPersistFields()
{
addGroup("Physics");
addField("initialPosition", TypePoint3F, Offset(mCurrPosition, afxMagicMissile) ,
"Initial starting position for this missile.");
addField("initialVelocity", TypePoint3F, Offset(mCurrVelocity, afxMagicMissile) ,
"Initial starting velocity for this missile.");
endGroup("Physics");
/* From stock Projectile code...
addGroup("Source");
addField("sourceObject", TypeS32, Offset(mSourceObjectId, Projectile) ,"The object that fires this projectile. If this projectile was fired by a WeaponImage, it will be the object that owns the WeaponImage, usually the player.");
addField("sourceSlot", TypeS32, Offset(mSourceObjectSlot, Projectile) ,"Which weapon slot on the sourceObject that this projectile originates from.");
endGroup("Source");
*/
Parent::initPersistFields();
}
/* From stock Projectile code...
bool Projectile::calculateImpact(float,
Point3F& pointOfImpact,
float& impactTime)
{
Con::warnf(ConsoleLogEntry::General, "Projectile::calculateImpact: Should never be called");
impactTime = 0;
pointOfImpact.set(0, 0, 0);
return false;
}
//--------------------------------------------------------------------------
F32 Projectile::getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 updateSkips)
{
F32 ret = Parent::getUpdatePriority(camInfo, updateMask, updateSkips);
// if the camera "owns" this object, it should have a slightly higher priority
if(mSourceObject == camInfo->camera)
return ret + 0.2;
return ret;
}
*/
bool afxMagicMissile::onAdd()
{
if(!Parent::onAdd())
return false;
if (isServerObject())
{
/* From stock Projectile code...
ShapeBase* ptr;
if (Sim::findObject(mSourceObjectId, ptr))
{
mSourceObject = ptr;
// Since we later do processAfter( mSourceObject ) we must clearProcessAfter
// if it is deleted. SceneObject already handles this in onDeleteNotify so
// all we need to do is register for the notification.
deleteNotify( ptr );
}
else
{
if (mSourceObjectId != -1)
Con::errorf(ConsoleLogEntry::General, "Projectile::onAdd: mSourceObjectId is invalid");
mSourceObject = NULL;
}
// If we're on the server, we need to inherit some of our parent's velocity
//
mCurrTick = 0;
*/
}
else
{
if (bool(mDataBlock->projectileShape))
{
mProjectileShape = new TSShapeInstance(mDataBlock->projectileShape, true);
/* From stock Projectile code...
if (mDataBlock->activateSeq != -1)
{
mActivateThread = mProjectileShape->addThread();
mProjectileShape->setTimeScale(mActivateThread, 1);
mProjectileShape->setSequence(mActivateThread, mDataBlock->activateSeq, 0);
}
*/
}
if (mDataBlock->particleEmitter != NULL)
{
ParticleEmitter* pEmitter = new ParticleEmitter;
//pEmitter->setDataBlock(mDataBlock->particleEmitter->cloneAndPerformSubstitutions(ss_object, ss_index));
pEmitter->onNewDataBlock(mDataBlock->particleEmitter->cloneAndPerformSubstitutions(ss_object, ss_index), false);
if (pEmitter->registerObject() == false)
{
Con::warnf(ConsoleLogEntry::General, "Could not register particle emitter for particle of class: %s", mDataBlock->getName());
delete pEmitter;
pEmitter = NULL;
}
mParticleEmitter = pEmitter;
}
if (mDataBlock->particleWaterEmitter != NULL)
{
ParticleEmitter* pEmitter = new ParticleEmitter;
pEmitter->onNewDataBlock(mDataBlock->particleWaterEmitter->cloneAndPerformSubstitutions(ss_object, ss_index), false);
if (pEmitter->registerObject() == false)
{
Con::warnf(ConsoleLogEntry::General, "Could not register particle emitter for particle of class: %s", mDataBlock->getName());
delete pEmitter;
pEmitter = NULL;
}
mParticleWaterEmitter = pEmitter;
}
}
/* From stock Projectile code...
if (mSourceObject.isValid())
processAfter(mSourceObject);
*/
// detect for acceleration
use_accel = (mDataBlock->acceleration != 0 && mDataBlock->accelLifetime > 0);
// Setup our bounding box
if (bool(mDataBlock->projectileShape) == true)
mObjBox = mDataBlock->projectileShape->mBounds;
else
mObjBox = Box3F(Point3F(0, 0, 0), Point3F(0, 0, 0));
resetWorldBox();
addToScene();
if ( PHYSICSMGR )
mPhysicsWorld = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
return true;
}
void afxMagicMissile::onRemove()
{
if(mParticleEmitter)
{
mParticleEmitter->deleteWhenEmpty();
mParticleEmitter = NULL;
}
if(mParticleWaterEmitter)
{
mParticleWaterEmitter->deleteWhenEmpty();
mParticleWaterEmitter = NULL;
}
SFX_DELETE( mSound );
removeFromScene();
Parent::onRemove();
}
bool afxMagicMissile::onNewDataBlock(GameBaseData* dptr, bool reload)
{
mDataBlock = dynamic_cast<afxMagicMissileData*>(dptr);
if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
return false;
starting_velocity = mDataBlock->muzzleVelocity;
starting_vel_vec = mDataBlock->starting_vel_vec;
collision_mask = mDataBlock->collision_mask;
if ( isGhost() )
{
// Create the sound ahead of time. This reduces runtime
// costs and makes the system easier to understand.
SFX_DELETE( mSound );
if (mDataBlock->getProjectileSound())
mSound = SFX->createSource(mDataBlock->getProjectileSoundProfile());
}
return true;
}
//--------------------------------------------------------------------------
void afxMagicMissile::submitLights( LightManager *lm, bool staticLighting )
{
if ( staticLighting || isHidden() || !mDataBlock->lightDesc )
return;
mDataBlock->lightDesc->submitLight( &mLightState, getRenderTransform(), lm, this );
}
bool afxMagicMissile::pointInWater(const Point3F &point)
{
// This is pretty much a hack so we can use the existing ContainerQueryInfo
// and findObject router.
// We only care if we intersect with water at all
// so build a box at the point that has only 1 z extent.
// And test if water coverage is anything other than zero.
Box3F boundsBox( point, point );
boundsBox.maxExtents.z += 1.0f;
ContainerQueryInfo info;
info.box = boundsBox;
info.mass = 0.0f;
// Find and retreive physics info from intersecting WaterObject(s)
if(mContainer != NULL)
{
mContainer->findObjects( boundsBox, WaterObjectType, findRouter, &info );
}
else
{
// Handle special case where the projectile has exploded prior to having
// called onAdd() on the client. This occurs when the projectile on the
// server is created and then explodes in the same network update tick.
// On the client end in NetConnection::ghostReadPacket() the ghost is
// created and then Projectile::unpackUpdate() is called prior to the
// projectile being registered. Within unpackUpdate() the explosion
// is triggered, but without being registered onAdd() isn't called and
// the container is not set. As all we're doing is checking if the
// given explosion point is within water, we should be able to use the
// global container here. We could likely always get away with this,
// but using the actual defined container when possible is the right
// thing to do. DAW
AssertFatal(isClientObject(), "Server projectile has not been properly added");
gClientContainer.findObjects( boundsBox, WaterObjectType, findRouter, &info );
}
return ( info.waterCoverage > 0.0f );
}
//----------------------------------------------------------------------------
void afxMagicMissile::emitParticles(const Point3F& from, const Point3F& to, const Point3F& vel, const U32 ms)
{
/* From stock Projectile code...
if ( isHidden() )
return;
*/
Point3F axis = -vel;
if( axis.isZero() )
axis.set( 0.0, 0.0, 1.0 );
else
axis.normalize();
bool fromWater = pointInWater(from);
bool toWater = pointInWater(to);
if (!fromWater && !toWater && bool(mParticleEmitter)) // not in water
mParticleEmitter->emitParticles(from, to, axis, vel, ms);
else if (fromWater && toWater && bool(mParticleWaterEmitter)) // in water
mParticleWaterEmitter->emitParticles(from, to, axis, vel, ms);
else if (!fromWater && toWater) // entering water
{
// cast the ray to get the surface point of the water
RayInfo rInfo;
if (gClientContainer.castRay(from, to, WaterObjectType, &rInfo))
{
create_splash(rInfo.point);
// create an emitter for the particles out of water and the particles in water
if (mParticleEmitter)
mParticleEmitter->emitParticles(from, rInfo.point, axis, vel, ms);
if (mParticleWaterEmitter)
mParticleWaterEmitter->emitParticles(rInfo.point, to, axis, vel, ms);
}
}
else if (fromWater && !toWater) // leaving water
{
// cast the ray in the opposite direction since that point is out of the water, otherwise
// we hit water immediately and wont get the appropriate surface point
RayInfo rInfo;
if (gClientContainer.castRay(to, from, WaterObjectType, &rInfo))
{
create_splash(rInfo.point);
// create an emitter for the particles out of water and the particles in water
if (mParticleEmitter)
mParticleEmitter->emitParticles(rInfo.point, to, axis, vel, ms);
if (mParticleWaterEmitter)
mParticleWaterEmitter->emitParticles(from, rInfo.point, axis, vel, ms);
}
}
}
void afxMagicMissile::processTick(const Move* move)
{
Parent::processTick( move );
// only active from launch to impact
if (!is_active())
return;
mCurrTick++;
// missile fizzles out by exceeding lifetime
if ((isServerObject() || client_only) && mCurrTick >= mDataBlock->lifetime)
{
did_impact = true;
setMaskBits(ImpactMask);
if (choreographer)
{
Point3F n = mCurrVelocity; n.normalizeSafe();
choreographer->impactNotify(mCurrPosition, n, 0);
}
Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + 500);
return;
}
static F32 dT = F32(TickMs)*0.001f;
Point3F old_pos = mCurrPosition;
// adjust missile velocity from gravity and drag influences
if (mDataBlock->isBallistic)
{
F32 dV = (1 - mDataBlock->ballisticCoefficient)*dT;
Point3F d(mCurrVelocity.x*dV, mCurrVelocity.y*dV, 9.81f*mDataBlock->gravityMod*dT);
mCurrVelocity -= d;
}
// adjust missile velocity from acceleration
if (use_accel)
{
if (mCurrTick > mDataBlock->accelDelay &&
mCurrTick <= mDataBlock->accelDelay + mDataBlock->accelLifetime)
{
Point3F d = mCurrVelocity; d.normalizeSafe();
mCurrVelocity += d*mDataBlock->acceleration*dT;
}
}
// adjust mCurrVelocity from guidance system influences
if (mDataBlock->isGuided && missile_target && mCurrTick > mDataBlock->trackDelay)
{
// get the position tracked by the guidance system
Point3F target_pos = missile_target->getBoxCenter();
Point3F target_vec = target_pos - mCurrPosition;
F32 target_dist_sq = target_vec.lenSquared();
if (target_dist_sq < 4.0f)
prec_inc += 1.0f;
// hover
if (mDataBlock->hover_altitude > 0.0f)
{
Point3F target_vec_xy(target_vec.x, target_vec.y, 0);
F32 xy_dist = target_vec_xy.len();
if (xy_dist > mDataBlock->hover_attack_distance)
{
hover_attack_go = false;
if (xy_dist > mDataBlock->hover_attack_distance + mDataBlock->hover_attack_gradient)
{
target_pos.z += mDataBlock->hover_altitude;
}
else
{
target_pos.z += afxEase::eq( (xy_dist-mDataBlock->hover_attack_distance)/mDataBlock->hover_attack_gradient,
0.0f, mDataBlock->hover_altitude,
0.25f, 0.75f);
}
target_vec = target_pos - mCurrPosition;
}
else
{
if (!hover_attack_go)
{
hover_attack_go = true;
hover_attack_tick = 0;
}
hover_attack_tick++;
if (hover_attack_tick < mDataBlock->hover_time)
{
target_pos.z += mDataBlock->hover_altitude;
target_vec = target_pos - mCurrPosition;
}
}
}
// apply precision
// extract speed
F32 speed = mCurrVelocity.len();
// normalize vectors
target_vec.normalizeSafe();
mCurrVelocity.normalize();
F32 prec = mDataBlock->precision;
// fade in precision gradually to avoid sudden turn
if (mCurrTick < mDataBlock->trackDelay + 16)
prec *= (mCurrTick - mDataBlock->trackDelay)/16.0f;
prec += prec_inc;
if (prec > 100)
prec = 100;
// apply precision weighting
target_vec *= prec;
mCurrVelocity *= (100 - prec);
mCurrVelocity += target_vec;
mCurrVelocity.normalize();
mCurrVelocity *= speed;
}
// wiggle
for (U32 i = 0; i < mDataBlock->wiggle_num_axis; i++)
{
if (i >= mDataBlock->wiggle_magnitudes.size() || i >= mDataBlock->wiggle_speeds.size())
break;
F32 wiggle_mag = mDataBlock->wiggle_magnitudes[i];
F32 wiggle_speed = mDataBlock->wiggle_speeds[i];
Point3F wiggle_axis = mDataBlock->wiggle_axis[i];
//wiggle_axis.normalizeSafe(); // sufficient????
F32 theta = wiggle_mag * mSin(wiggle_speed*(mCurrTick*TickSec));
//Con::printf( "theta: %f", theta );
AngAxisF thetaRot(wiggle_axis, theta);
MatrixF temp(true);
thetaRot.setMatrix(&temp);
temp.mulP(mCurrVelocity);
}
Point3F new_pos = old_pos + mCurrVelocity*dT;
// conform to terrain
if (mDataBlock->followTerrain && mCurrTick >= mDataBlock->followTerrainAdjustDelay)
{
U32 mask = TerrainObjectType | TerrainLikeObjectType; // | InteriorObjectType;
F32 ht = mDataBlock->followTerrainHeight;
F32 ht_rate = mDataBlock->followTerrainAdjustRate;
F32 ht_min = 0.05f;
if (ht < ht_min)
ht = ht_min;
SceneContainer* container = (isServerObject()) ? &gServerContainer : &gClientContainer;
Point3F above_pos = new_pos; above_pos.z += 10000;
Point3F below_pos = new_pos; below_pos.z -= 10000;
RayInfo rInfo;
if (container && container->castRay(above_pos, below_pos, mask, &rInfo))
{
F32 terrain_z = rInfo.point.z;
F32 seek_z = terrain_z + ht;
if (new_pos.z < seek_z)
{
new_pos.z += ht_rate*dT;
if (new_pos.z > seek_z)
new_pos.z = seek_z;
}
else if (new_pos.z > seek_z)
{
new_pos.z -= ht_rate*dT;
if (new_pos.z < seek_z)
new_pos.z = seek_z;
}
if (new_pos.z < terrain_z + ht_min)
new_pos.z = terrain_z + ht_min;
}
}
// only check for impacts on server
if (isServerObject())
{
// avoid collision with the spellcaster
if (collide_exempt && mCurrTick <= mDataBlock->caster_safety_time)
collide_exempt->disableCollision();
// check for collision along ray from old to new position
RayInfo rInfo;
bool did_hit = false;
if (mPhysicsWorld)
{
// did_hit = mPhysicsWorld->castRay(old_pos, new_pos, &rInfo, Point3F(new_pos - old_pos) * mDataBlock->impactForce );
// Impulse currently hardcoded for testing purposes
did_hit = mPhysicsWorld->castRay(old_pos, new_pos, &rInfo, Point3F(new_pos - old_pos) * 1000.0f );
}
else
{
did_hit = getContainer()->castRay(old_pos, new_pos, collision_mask, &rInfo);
}
// restore collisions on spellcaster
if (collide_exempt && mCurrTick <= mDataBlock->caster_safety_time)
collide_exempt->enableCollision();
// process impact
if (did_hit)
{
MatrixF xform(true);
xform.setColumn(3, rInfo.point);
setTransform(xform);
mCurrPosition = rInfo.point;
mCurrVelocity = Point3F(0, 0, 0);
did_impact = true;
setMaskBits(ImpactMask);
if (choreographer)
{
choreographer->impactNotify(rInfo.point, rInfo.normal, rInfo.object);
Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + 500);
}
}
}
else // if (isClientObject())
{
emitParticles(mCurrPosition, new_pos, mCurrVelocity, TickMs);
updateSound();
}
// interp values used in interpolateTick()
mCurrDeltaBase = new_pos;
mCurrBackDelta = mCurrPosition - new_pos;
mCurrPosition = new_pos;
MatrixF xform(true);
xform.setColumn(3, mCurrPosition);
setTransform(xform);
}
void afxMagicMissile::interpolateTick(F32 delta)
{
Parent::interpolateTick(delta);
// only active from launch to impact
if (!is_active())
return;
Point3F interpPos = mCurrDeltaBase + mCurrBackDelta * delta;
Point3F dir = mCurrVelocity;
if(dir.isZero())
dir.set(0,0,1);
else
dir.normalize();
MatrixF xform(true);
xform = MathUtils::createOrientFromDir(dir);
xform.setPosition(interpPos);
setRenderTransform(xform);
updateSound();
}
//--------------------------------------------------------------------------
U32 afxMagicMissile::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
{
U32 retMask = Parent::packUpdate( con, mask, stream );
const bool isInitalUpdate = mask & GameBase::InitialUpdateMask;
// InitialUpdateMask
if ( stream->writeFlag( isInitalUpdate ) )
{
Point3F pos;
getTransform().getColumn( 3, &pos );
stream->writeCompressedPoint( pos );
F32 len = mCurrVelocity.len();
if ( stream->writeFlag( len > 0.02 ) )
{
Point3F outVel = mCurrVelocity;
outVel *= 1 / len;
stream->writeNormalVector( outVel, 10 );
len *= 32.0; // 5 bits for fraction
if ( len > 8191 )
len = 8191;
stream->writeInt( (S32)len, 13 );
}
stream->writeRangedU32( mCurrTick, 0, afxMagicMissileData::MaxLifetimeTicks );
if (choreographer)
{
S32 ghostIndex = con->getGhostIndex( choreographer );
if ( stream->writeFlag( ghostIndex != -1 ) )
{
stream->writeRangedU32( U32(ghostIndex),
0,
NetConnection::MaxGhostCount );
}
else
// have not recieved the ghost for the source object yet, try again later
retMask |= GameBase::InitialUpdateMask;
}
else
stream->writeFlag( false );
}
// impact update
if (stream->writeFlag(mask & ImpactMask))
{
mathWrite(*stream, mCurrPosition);
mathWrite(*stream, mCurrVelocity);
stream->writeFlag(did_impact);
}
// guided update
if (stream->writeFlag(mask & GuideMask))
{
mathWrite(*stream, mCurrPosition);
mathWrite(*stream, mCurrVelocity);
}
return retMask;
}
void afxMagicMissile::unpackUpdate(NetConnection* con, BitStream* stream)
{
Parent::unpackUpdate(con, stream);
if ( stream->readFlag() ) // InitialUpdateMask
{
Point3F pos;
stream->readCompressedPoint( &pos );
if ( stream->readFlag() )
{
stream->readNormalVector( &mCurrVelocity, 10 );
mCurrVelocity *= stream->readInt( 13 ) / 32.0f;
}
else
mCurrVelocity.zero();
mCurrDeltaBase = pos;
mCurrBackDelta = mCurrPosition - pos;
mCurrPosition = pos;
setPosition( mCurrPosition );
mCurrTick = stream->readRangedU32(0, afxMagicMissileData::MaxLifetimeTicks);
if ( stream->readFlag() )
{
U32 id = stream->readRangedU32(0, NetConnection::MaxGhostCount);
choreographer = dynamic_cast<afxChoreographer*>(con->resolveGhost(id));
if (choreographer)
{
deleteNotify(choreographer);
}
}
else
{
if (choreographer)
clearNotify(choreographer);
choreographer = 0;
}
}
// impact update
if (stream->readFlag())
{
mathRead(*stream, &mCurrPosition);
mathRead(*stream, &mCurrVelocity);
did_impact = stream->readFlag();
}
if (stream->readFlag()) // guided update
{
mathRead( *stream, &mCurrPosition );
mathRead( *stream, &mCurrVelocity );
}
}
//--------------------------------------------------------------------------
void afxMagicMissile::prepRenderImage(SceneRenderState* state)
{
if (!is_active())
return;
/*
if (isHidden() || mFadeValue <= (1.0/255.0))
return;
*/
if ( mDataBlock->lightDesc )
{
mDataBlock->lightDesc->prepRender( state, &mLightState, getRenderTransform() );
}
/*
if ( mFlareData )
{
mFlareState.fullBrightness = mDataBlock->lightDesc->mBrightness;
mFlareState.scale = mFlareScale;
mFlareState.lightInfo = mLight;
mFlareState.lightMat = getTransform();
mFlareData->prepRender( state, &mFlareState );
}
*/
prepBatchRender( state );
}
void afxMagicMissile::prepBatchRender(SceneRenderState* state)
{
if ( !mProjectileShape )
return;
GFXTransformSaver saver;
// Set up our TS render state.
TSRenderState rdata;
rdata.setSceneState( state );
// We might have some forward lit materials
// so pass down a query to gather lights.
LightQuery query;
query.init( getWorldSphere() );
rdata.setLightQuery( &query );
MatrixF mat = getRenderTransform();
mat.scale( mObjScale );
mat.scale( mDataBlock->scale );
GFX->setWorldMatrix( mat );
mProjectileShape->setDetailFromPosAndScale( state, mat.getPosition(), mObjScale );
mProjectileShape->animate();
mProjectileShape->render( rdata );
}
void afxMagicMissile::onDeleteNotify(SimObject* obj)
{
ShapeBase* shape_test = dynamic_cast<ShapeBase*>(obj);
if (shape_test == collide_exempt)
{
collide_exempt = NULL;
Parent::onDeleteNotify(obj);
return;
}
SceneObject* target_test = dynamic_cast<SceneObject*>(obj);
if (target_test == missile_target)
{
missile_target = NULL;
Parent::onDeleteNotify(obj);
return;
}
afxChoreographer* ch = dynamic_cast<afxChoreographer*>(obj);
if (ch == choreographer)
{
choreographer = NULL;
Parent::onDeleteNotify(obj);
return;
}
Parent::onDeleteNotify(obj);
}
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// private:
void afxMagicMissile::create_splash(const Point3F& pos)
{
if (!mDataBlock || !mDataBlock->splash)
return;
MatrixF xfm = getTransform();
xfm.setPosition(pos);
Splash* splash = new Splash();
splash->onNewDataBlock(mDataBlock->splash, false);
splash->setTransform(xfm);
splash->setInitialState(xfm.getPosition(), Point3F(0.0, 0.0, 1.0));
if (!splash->registerObject())
{
delete splash;
splash = NULL;
}
}
void afxMagicMissile::get_launch_constraint_data(Point3F& pos, Point3F& vel)
{
// need a choreographer
if (!choreographer)
{
Con::errorf("afxMagicMissile::get_launch_constraint_data(): missing choreographer.");
return;
}
// need a constraint manager
afxConstraintMgr* cons_mgr = choreographer->getConstraintMgr();
if (!cons_mgr)
{
Con::errorf("afxMagicMissile::get_launch_constraint_data(): missing constriant manager.");
return;
}
// need a valid constraint
afxConstraintID launch_cons_id;
if (isServerObject())
launch_cons_id = cons_mgr->getConstraintId(mDataBlock->launch_cons_s_def);
else
launch_cons_id = cons_mgr->getConstraintId(mDataBlock->launch_cons_c_def);
afxConstraint* launch_cons = cons_mgr->getConstraint(launch_cons_id);
if (!launch_cons)
{
Con::errorf("afxMagicMissile::get_launch_constraint_data(): constraint undefined.");
return;
}
MatrixF launch_xfm;
launch_cons->getTransform(launch_xfm);
Point3F launch_pos;
launch_cons->getPosition(launch_pos);
pos = launch_pos;
// echo the actual launch position to the console
if (mDataBlock->echo_launch_offset)
{
SceneObject* default_launcher = get_default_launcher();
if (default_launcher)
{
MatrixF launcher_xfm_inv = default_launcher->getWorldTransform();
VectorF offset = pos - default_launcher->getRenderPosition();
launcher_xfm_inv.mulV(offset);
if (isServerObject())
Con::printf("launchOffsetServer = \"%g %g %g\";", offset.x, offset.y, offset.z);
else
Con::printf("launchOffsetClient = \"%g %g %g\";", offset.x, offset.y, offset.z);
}
}
// setup aiming matrix to straight forward and level
MatrixF aim_mtx;
AngAxisF aim_aa(Point3F(0,1,0),0);
aim_aa.setMatrix(&aim_mtx);
// calculate final aiming vector
MatrixF aim2_mtx;
aim2_mtx.mul(launch_xfm, aim_mtx);
VectorF aim_vec;
aim2_mtx.getColumn(1,&aim_vec);
aim_vec.normalizeSafe();
// give velocity vector a magnitude
vel = aim_vec*mDataBlock->muzzleVelocity;
}
// resolve the launch constraint object. normally it's the caster, but for
// reverse_targeting the target object us used.
SceneObject* afxMagicMissile::get_default_launcher() const
{
SceneObject* launch_cons_obj = 0;
if (mDataBlock->reverse_targeting)
{
if (dynamic_cast<afxMagicSpell*>(choreographer))
launch_cons_obj = ((afxMagicSpell*)choreographer)->mTarget;
if (!launch_cons_obj)
{
Con::errorf("afxMagicMissile::get_launch_data(): missing target constraint object for reverse targeted missile.");
return 0;
}
}
else
{
if (dynamic_cast<afxMagicSpell*>(choreographer))
launch_cons_obj = ((afxMagicSpell*)choreographer)->mCaster;
if (!launch_cons_obj)
{
Con::errorf("afxMagicMissile::get_launch_data(): missing launch constraint object missile.");
return 0;
}
}
return launch_cons_obj;
}
void afxMagicMissile::get_launch_data(Point3F& pos, Point3F& vel)
{
bool use_constraint = (isServerObject()) ? mDataBlock->launch_cons_s_def.isDefined() : mDataBlock->launch_cons_c_def.isDefined();
if (use_constraint)
{
get_launch_constraint_data(pos, vel);
return;
}
// a choreographer pointer is required
if (!choreographer)
{
Con::errorf("afxMagicMissile::get_launch_data(): missing choreographer.");
return;
}
SceneObject* launch_cons_obj = get_default_launcher();
if (!launch_cons_obj)
return;
MatrixF launch_xfm = launch_cons_obj->getRenderTransform();
// calculate launch position
Point3F offset_override = (isClientObject()) ? mDataBlock->launch_offset_client :
mDataBlock->launch_offset_server;
// override
if (!offset_override.isZero())
{
launch_xfm.mulV(offset_override);
pos = launch_cons_obj->getRenderPosition() + offset_override;
}
// no override
else
{
// get transformed launch offset
VectorF launch_offset = mDataBlock->launch_offset;
launch_xfm.mulV(launch_offset);
StringTableEntry launch_node = mDataBlock->launch_node;
// calculate position of missile at launch
if (launch_node != ST_NULLSTRING)
{
ShapeBase* launch_cons_shape = dynamic_cast<ShapeBase*>(launch_cons_obj);
TSShapeInstance* shape_inst = (launch_cons_shape) ? launch_cons_shape->getShapeInstance() : 0;
if (!shape_inst || !shape_inst->getShape())
launch_node = ST_NULLSTRING;
else
{
S32 node_ID = shape_inst->getShape()->findNode(launch_node);
MatrixF node_xfm = launch_cons_obj->getRenderTransform();
node_xfm.scale(launch_cons_obj->getScale());
if (node_ID >= 0)
node_xfm.mul(shape_inst->mNodeTransforms[node_ID]);
VectorF node_offset = mDataBlock->launch_node_offset;
node_xfm.mulV(node_offset);
pos = node_xfm.getPosition() + launch_offset + node_offset;
}
}
// calculate launch position without launch node
else
pos = launch_cons_obj->getRenderPosition() + launch_offset;
}
// echo the actual launch position to the console
if (mDataBlock->echo_launch_offset)
{
VectorF offset = pos - launch_cons_obj->getRenderPosition();
MatrixF caster_xfm_inv = launch_xfm;
caster_xfm_inv.affineInverse();
caster_xfm_inv.mulV(offset);
if (isServerObject())
Con::printf("launchOffsetServer = \"%g %g %g\";", offset.x, offset.y, offset.z);
else
Con::printf("launchOffsetClient = \"%g %g %g\";", offset.x, offset.y, offset.z);
}
// calculate launch velocity vector
if (starting_vel_vec.isZero())
{
// setup aiming matrix to straight forward and level
MatrixF aim_mtx;
AngAxisF aim_aa(Point3F(0,1,0),0);
aim_aa.setMatrix(&aim_mtx);
// setup pitch matrix
MatrixF pitch_mtx;
AngAxisF pitch_aa(Point3F(1,0,0),mDegToRad(mDataBlock->launch_pitch));
pitch_aa.setMatrix(&pitch_mtx);
// setup pan matrix
MatrixF pan_mtx;
AngAxisF pan_aa(Point3F(0,0,1),mDegToRad(mDataBlock->launch_pan));
pan_aa.setMatrix(&pan_mtx);
// calculate adjusted aiming matrix
aim_mtx.mul(pitch_mtx);
aim_mtx.mul(pan_mtx);
// calculate final aiming vector
MatrixF aim2_mtx;
aim2_mtx.mul(launch_xfm, aim_mtx);
VectorF aim_vec;
aim2_mtx.getColumn(1,&aim_vec);
aim_vec.normalizeSafe();
// give velocity vector a magnitude
vel = aim_vec*mDataBlock->muzzleVelocity;
}
else
{
vel = starting_vel_vec*starting_velocity;
}
}
void afxMagicMissile::updateSound()
{
if (!mDataBlock->isProjectileSoundValid())
return;
if ( mSound )
{
if ( !mSound->isPlaying() )
mSound->play();
mSound->setVelocity( getVelocity() );
mSound->setTransform( getRenderTransform() );
}
}
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// public:
void afxMagicMissile::launch()
{
get_launch_data(mCurrPosition, mCurrVelocity);
mCurrDeltaBase = mCurrPosition;
mCurrBackDelta.zero();
did_launch = true;
setPosition(mCurrPosition);
afxMagicSpell* spell = dynamic_cast<afxMagicSpell*>(choreographer);
if (spell)
{
if (mDataBlock->reverse_targeting)
{
missile_target = spell->mCaster;
collide_exempt = spell->mTarget;
}
else
{
missile_target = spell->mTarget;
collide_exempt = spell->mCaster;
}
if (spell->mCaster)
processAfter(spell->mCaster);
if (missile_target)
deleteNotify(missile_target);
if (collide_exempt)
deleteNotify(collide_exempt);
}
else
{
missile_target = 0;
collide_exempt = 0;
}
}
void afxMagicMissile::setChoreographer(afxChoreographer* chor)
{
if (choreographer)
clearNotify(choreographer);
choreographer = chor;
if (choreographer)
deleteNotify(choreographer);
}
void afxMagicMissile::setStartingVelocityVector(const Point3F& vel_vec)
{
starting_vel_vec = vel_vec;
}
void afxMagicMissile::setStartingVelocity(const F32 vel)
{
starting_velocity = vel;
}
void afxMagicMissile::getStartingVelocityValues(F32& vel, Point3F& vel_vec)
{
vel = starting_velocity;
vel_vec = starting_vel_vec;
}
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
/* From stock Projectile code...
DefineEngineMethod(Projectile, presimulate, void, (F32 seconds), (1.0f), "Updates velocity and position, and performs collision testing.\n"
"@param seconds Amount of time, in seconds, since the simulation began, to start the simulation at.\n"
"@tsexample\n"
"// Tell the projectile object to process a simulation event, and provide the amount of time\n"
" in seconds that has passed since the simulation began.\n"
"%seconds = 2000;\n"
"%projectile.presimulate(%seconds);\n"
"@endtsexample\n")
{
object->simulate( seconds );
}
*/
DefineEngineMethod(afxMagicMissile, setStartingVelocityVector, void, (Point3F velocityVec),,
"Set the starting velocity-vector for a magic-missile.\n\n"
"@ingroup AFX")
{
object->setStartingVelocityVector(velocityVec);
}
DefineEngineMethod(afxMagicMissile, setStartingVelocity, void, (float velocity),,
"Set the starting velocity for a magic-missile.\n\n"
"@ingroup AFX")
{
object->setStartingVelocity(velocity);
}
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//