mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 04:34:48 +00:00
2119 lines
65 KiB
C++
2119 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()
|
|
{
|
|
projectileShapeName = ST_NULLSTRING;
|
|
|
|
sound = NULL;
|
|
|
|
/* 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)
|
|
{
|
|
projectileShapeName = other.projectileShapeName;
|
|
projectileShape = other.projectileShape; // -- TSShape loads using projectileShapeName
|
|
sound = other.sound;
|
|
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));
|
|
|
|
addField("projectileShapeName", TypeFilename, Offset(projectileShapeName, afxMagicMissileData));
|
|
addField("scale", TypePoint3F, Offset(scale, afxMagicMissileData));
|
|
|
|
addField("sound", TypeSFXTrackName, Offset(sound, afxMagicMissileData));
|
|
|
|
/* 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<char*> 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);
|
|
*/
|
|
|
|
String sfxErrorStr;
|
|
if( !sfxResolve( &sound, sfxErrorStr) )
|
|
Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet: %s", sfxErrorStr.c_str());
|
|
|
|
if (!lightDesc && lightDescId != 0)
|
|
if (Sim::findObject(lightDescId, lightDesc) == false)
|
|
Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockid(lightDesc): %d", lightDescId);
|
|
}
|
|
|
|
if (projectileShapeName != ST_NULLSTRING)
|
|
{
|
|
projectileShape = ResourceManager::get().load(projectileShapeName);
|
|
if (bool(projectileShape) == false)
|
|
{
|
|
errorStr = String::ToString("afxMagicMissileData::load: Couldn't load shape \"%s\"", projectileShapeName);
|
|
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);
|
|
|
|
stream->writeString(projectileShapeName);
|
|
/* 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);
|
|
*/
|
|
|
|
sfxWrite( stream, sound );
|
|
|
|
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);
|
|
|
|
projectileShapeName = stream->readSTString();
|
|
/* 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);
|
|
*/
|
|
|
|
sfxRead( stream, &sound );
|
|
|
|
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->sound )
|
|
mSound = SFX->createSource( mDataBlock->sound );
|
|
}
|
|
|
|
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->sound)
|
|
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);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|