Torque3D/Engine/source/afx/afxMagicMissile.cpp
AzaezelX f07c8745b2 inspector cleanups
clustering work for datablocks for both consistent scanning for object parameters, as well as an eye towards orgainizing things to make reviewing what variations of components we'll be needing down the line clearer
2023-01-24 17:12:23 -06:00

2126 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);
addGroup("Particle Effects");
addField("particleEmitter", TYPEID<ParticleEmitterData>(), Offset(particleEmitter, afxMagicMissileData));
addField("particleWaterEmitter", TYPEID<ParticleEmitterData>(), Offset(particleWaterEmitter, afxMagicMissileData));
endGroup("Particle Effects");
addGroup("Shapes");
INITPERSISTFIELD_SHAPEASSET(ProjectileShape, afxMagicMissileData, "Shape for the projectile");
addField("scale", TypePoint3F, Offset(scale, afxMagicMissileData));
endGroup("Shapes");
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);
}
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//