//----------------------------------------------------------------------------- // 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() { docsURL; static IRangeValidatorScaled ticksFromMS(TickMs, 0, MaxLifetimeTicks); addGroup("Shapes"); INITPERSISTFIELD_SHAPEASSET(ProjectileShape, afxMagicMissileData, "Shape for the projectile"); addField("scale", TypePoint3F, Offset(scale, afxMagicMissileData)); addField("missileShapeScale", TypePoint3F, myOffset(scale)); endGroup("Shapes"); addGroup("Particle Effects"); addField("particleEmitter", TYPEID(), Offset(particleEmitter, afxMagicMissileData)); addField("particleWaterEmitter", TYPEID(), Offset(particleWaterEmitter, afxMagicMissileData)); addField("splash", TYPEID(), Offset(splash, afxMagicMissileData)); endGroup("Particle Effects"); addGroup("Sounds"); INITPERSISTFIELD_SOUNDASSET(ProjectileSound, afxMagicMissileData, "sound for the projectile"); endGroup("Sounds"); addGroup("Light Emitter"); addField("lightDesc", TYPEID< LightDescription >(), Offset(lightDesc, afxMagicMissileData)); endGroup("Light Emitter"); addGroup("Physics"); addNamedFieldV(lifetime, TypeS32, afxMagicMissileData, &ticksFromMS); addFieldV("casterSafetyTime", TypeS32, myOffset(caster_safety_time), &ticksFromMS); addField("isBallistic", TypeBool, Offset(isBallistic, afxMagicMissileData)); addNamedFieldV(muzzleVelocity, TypeF32, afxMagicMissileData, &muzzleVelocityValidator); addNamedFieldV(ballisticCoefficient, TypeF32, afxMagicMissileData, &missileBallisticCoefficientValidator); addField("gravityMod", TypeF32, Offset(gravityMod, afxMagicMissileData)); addField("collisionMask", TypeS32, myOffset(collision_mask)); addField("startingVelocityVector",TypePoint3F, myOffset(starting_vel_vec)); addNamedField(acceleration, TypeF32, afxMagicMissileData); addNamedFieldV(accelDelay, TypeS32, afxMagicMissileData, &ticksFromMS); addNamedFieldV(accelLifetime, TypeS32, afxMagicMissileData, &ticksFromMS); addField("reverseTargeting", TypeBool, myOffset(reverse_targeting)); endGroup("Physics"); addGroup("Physics-Tracking"); addNamedField(isGuided, TypeBool, afxMagicMissileData); addNamedFieldV(precision, TypeF32, afxMagicMissileData, &missilePrecisionValidator); addNamedFieldV(trackDelay, TypeS32, afxMagicMissileData, &missileTrackDelayValidator); endGroup("Physics-Tracking"); addGroup("Physics-Avoidance"); addField("followTerrain", TypeBool, myOffset(followTerrain)); addField("followTerrainHeight", TypeF32, myOffset(followTerrainHeight)); addField("followTerrainAdjustRate", TypeF32, myOffset(followTerrainAdjustRate)); addFieldV("followTerrainAdjustDelay", TypeS32, myOffset(followTerrainAdjustDelay), &ticksFromMS); 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); endGroup("Physics-Avoidance"); addGroup("Physics-Launch"); 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)); endGroup("Physics-Launch"); addGroup("Physics-Wiggle"); addField("wiggleMagnitudes", TypeF32Vector, myOffset(wiggle_magnitudes)); addField("wiggleSpeeds", TypeF32Vector, myOffset(wiggle_speeds)); addField("wiggleAxis", TypeString, myOffset(wiggle_axis_string)); endGroup("Physics-Wiggle"); 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 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 bool readVector(Vector & 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 bool writeVector(const Vector & 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(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(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(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& 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() { docsURL; 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(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(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(obj); if (shape_test == collide_exempt) { collide_exempt = NULL; Parent::onDeleteNotify(obj); return; } SceneObject* target_test = dynamic_cast(obj); if (target_test == missile_target) { missile_target = NULL; Parent::onDeleteNotify(obj); return; } afxChoreographer* ch = dynamic_cast(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(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(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(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(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); } //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//