Torque3D/Engine/source/afx/afxMagicSpell.cpp

2714 lines
76 KiB
C++
Raw Normal View History

2017-07-26 08:35:44 +00:00
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
// Copyright (C) 2015 Faust Logic, Inc.
//
// 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.
//
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
#include "afx/arcaneFX.h"
#include "console/engineAPI.h"
#include "T3D/gameBase/gameConnection.h"
#include "sfx/sfxSystem.h"
#include "math/mathIO.h"
#include "T3D/containerQuery.h"
#include "afx/afxChoreographer.h"
#include "afx/afxPhrase.h"
#include "afx/afxMagicSpell.h"
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// afxMagicSpellData::ewValidator
//
// When any of the effect list fields (addCastingEffect, etc.) are set, this validator
// intercepts the value and adds it to the appropriate effects list. One validator is
// created for each effect list and an id is used to identify which list to add the effect
// to.
//
void afxMagicSpellData::ewValidator::validateType(SimObject* object, void* typePtr)
{
afxMagicSpellData* spelldata = dynamic_cast<afxMagicSpellData*>(object);
afxEffectBaseData** ew = (afxEffectBaseData**)(typePtr);
if (spelldata && ew)
{
switch (id)
{
case CASTING_PHRASE:
spelldata->mCasting_fx_list.push_back(*ew);
2017-07-26 08:35:44 +00:00
break;
case LAUNCH_PHRASE:
spelldata->mLaunch_fx_list.push_back(*ew);
2017-07-26 08:35:44 +00:00
break;
case DELIVERY_PHRASE:
spelldata->mDelivery_fx_list.push_back(*ew);
2017-07-26 08:35:44 +00:00
break;
case IMPACT_PHRASE:
spelldata->mImpact_fx_list.push_back(*ew);
2017-07-26 08:35:44 +00:00
break;
case LINGER_PHRASE:
spelldata->mLinger_fx_list.push_back(*ew);
2017-07-26 08:35:44 +00:00
break;
}
*ew = 0;
}
}
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
class SpellFinishStartupEvent : public SimEvent
{
public:
void process(SimObject* obj) override
2017-07-26 08:35:44 +00:00
{
afxMagicSpell* spell = dynamic_cast<afxMagicSpell*>(obj);
if (spell)
spell->finish_startup();
}
};
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// afxMagicSpellData
IMPLEMENT_CO_DATABLOCK_V1(afxMagicSpellData);
ConsoleDocClass( afxMagicSpellData,
"@brief Defines the properties of an afxMagicSpell.\n\n"
"@ingroup afxChoreographers\n"
"@ingroup AFX\n"
"@ingroup Datablocks\n"
);
IMPLEMENT_CALLBACK( afxMagicSpellData, onDamage, void,
(afxMagicSpell* spell, const char* label, const char* flaver, U32 target_id, F32 amount, U8 n, Point3F pos, F32 ad_amount, F32 radius, F32 impulse),
(spell, label, flaver, target_id, amount, n, pos, ad_amount, radius, impulse),
"Called when the spell deals damage.\n"
"@param spell the spell object\n" );
IMPLEMENT_CALLBACK( afxMagicSpellData, onDeactivate, void, (afxMagicSpell* spell), (spell),
"Called when the spell ends naturally.\n"
"@param spell the spell object\n" );
IMPLEMENT_CALLBACK( afxMagicSpellData, onInterrupt, void, (afxMagicSpell* spell, ShapeBase* caster), (spell, caster),
"Called when the spell ends unnaturally due to an interruption.\n"
"@param spell the spell object\n" );
IMPLEMENT_CALLBACK( afxMagicSpellData, onLaunch, void,
(afxMagicSpell* spell, ShapeBase* caster, SceneObject* target, afxMagicMissile* missile),
(spell, caster, target, missile),
"Called when the spell's casting stage ends and the delivery stage begins.\n"
"@param spell the spell object\n" );
IMPLEMENT_CALLBACK( afxMagicSpellData, onImpact, void,
(afxMagicSpell* spell, ShapeBase* caster, SceneObject* impacted, Point3F pos, Point3F normal),
(spell, caster, impacted, pos, normal),
"Called at the spell's missile impact marking the end of the deliver stage and the start of the linger stage.\n"
"@param spell the spell object\n" );
IMPLEMENT_CALLBACK( afxMagicSpellData, onPreactivate, bool,
(SimObject* param_holder, ShapeBase* caster, SceneObject* target, SimObject* extra),
(param_holder, caster, target, extra),
"Called during spell casting before spell instance is fully created.\n");
IMPLEMENT_CALLBACK( afxMagicSpellData, onActivate, void,
(afxMagicSpell* spell, ShapeBase* caster, SceneObject* target),
(spell, caster, target),
"Called when the spell starts.\n"
"@param spell the spell object\n" );
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
afxMagicSpellData::afxMagicSpellData()
{
mCasting_dur = 0.0f;
mDelivery_dur = 0.0f;
mLinger_dur = 0.0f;
2017-07-26 08:35:44 +00:00
mNum_casting_loops = 1;
mNum_delivery_loops = 1;
mNum_linger_loops = 1;
2017-07-26 08:35:44 +00:00
mExtra_casting_time = 0.0f;
mExtra_delivery_time = 0.0f;
mExtra_linger_time = 0.0f;
2017-07-26 08:35:44 +00:00
// interrupt flags
mDo_move_interrupts = true;
mMove_interrupt_speed = 2.0f;
2017-07-26 08:35:44 +00:00
// delivers projectile spells
mMissile_db = 0;
mLaunch_on_server_signal = false;
mPrimary_target_types = PlayerObjectType;
2017-07-26 08:35:44 +00:00
// dummy entry holds effect-wrapper pointer while a special validator
// grabs it and adds it to an appropriate effects list
mDummy_fx_entry = NULL;
2017-07-26 08:35:44 +00:00
// marked true if datablock ids need to
// be converted into pointers
mDo_id_convert = false;
2017-07-26 08:35:44 +00:00
}
afxMagicSpellData::afxMagicSpellData(const afxMagicSpellData& other, bool temp_clone) : afxChoreographerData(other, temp_clone)
{
mCasting_dur = other.mCasting_dur;
mDelivery_dur = other.mDelivery_dur;
mLinger_dur = other.mLinger_dur;
mNum_casting_loops = other.mNum_casting_loops;
mNum_delivery_loops = other.mNum_delivery_loops;
mNum_linger_loops = other.mNum_linger_loops;
mExtra_casting_time = other.mExtra_casting_time;
mExtra_delivery_time = other.mExtra_delivery_time;
mExtra_linger_time = other.mExtra_linger_time;
mDo_move_interrupts = other.mDo_move_interrupts;
mMove_interrupt_speed = other.mMove_interrupt_speed;
mMissile_db = other.mMissile_db;
mLaunch_on_server_signal = other.mLaunch_on_server_signal;
mPrimary_target_types = other.mPrimary_target_types;
mDummy_fx_entry = other.mDummy_fx_entry;
mDo_id_convert = other.mDo_id_convert;
mCasting_fx_list = other.mCasting_fx_list;
mLaunch_fx_list = other.mLaunch_fx_list;
mDelivery_fx_list = other.mDelivery_fx_list;
mImpact_fx_list = other.mImpact_fx_list;
mLinger_fx_list = other.mLinger_fx_list;
2017-07-26 08:35:44 +00:00
}
void afxMagicSpellData::reloadReset()
{
mCasting_fx_list.clear();
mLaunch_fx_list.clear();
mDelivery_fx_list.clear();
mImpact_fx_list.clear();
mLinger_fx_list.clear();
2017-07-26 08:35:44 +00:00
}
#define myOffset(field) Offset(field, afxMagicSpellData)
void afxMagicSpellData::initPersistFields()
{
docsURL;
2017-07-26 08:35:44 +00:00
static ewValidator _castingPhrase(CASTING_PHRASE);
static ewValidator _launchPhrase(LAUNCH_PHRASE);
static ewValidator _deliveryPhrase(DELIVERY_PHRASE);
static ewValidator _impactPhrase(IMPACT_PHRASE);
static ewValidator _lingerPhrase(LINGER_PHRASE);
// for each effect list, dummy_fx_entry is set and then a validator adds it to the appropriate effects list
addGroup("Casting Stage");
addField("castingDur", TypeF32, myOffset(mCasting_dur),
2017-07-26 08:35:44 +00:00
"...");
addField("numCastingLoops", TypeS32, myOffset(mNum_casting_loops),
2017-07-26 08:35:44 +00:00
"...");
addField("extraCastingTime", TypeF32, myOffset(mExtra_casting_time),
2017-07-26 08:35:44 +00:00
"...");
addFieldV("addCastingEffect", TYPEID<afxEffectBaseData>(), Offset(mDummy_fx_entry, afxMagicSpellData), &_castingPhrase,
2017-07-26 08:35:44 +00:00
"...");
endGroup("Casting Stage");
addGroup("Delivery Stage");
addField("deliveryDur", TypeF32, myOffset(mDelivery_dur),
2017-07-26 08:35:44 +00:00
"...");
addField("numDeliveryLoops", TypeS32, myOffset(mNum_delivery_loops),
2017-07-26 08:35:44 +00:00
"...");
addField("extraDeliveryTime", TypeF32, myOffset(mExtra_delivery_time),
2017-07-26 08:35:44 +00:00
"...");
addFieldV("addLaunchEffect", TYPEID<afxEffectBaseData>(), Offset(mDummy_fx_entry, afxMagicSpellData), &_launchPhrase,
2017-07-26 08:35:44 +00:00
"...");
addFieldV("addDeliveryEffect", TYPEID<afxEffectBaseData>(), Offset(mDummy_fx_entry, afxMagicSpellData), &_deliveryPhrase,
2017-07-26 08:35:44 +00:00
"...");
endGroup("Delivery Stage");
addGroup("Linger Stage");
addField("lingerDur", TypeF32, myOffset(mLinger_dur),
2017-07-26 08:35:44 +00:00
"...");
addField("numLingerLoops", TypeS32, myOffset(mNum_linger_loops),
2017-07-26 08:35:44 +00:00
"...");
addField("extraLingerTime", TypeF32, myOffset(mExtra_linger_time),
2017-07-26 08:35:44 +00:00
"...");
addFieldV("addImpactEffect", TYPEID<afxEffectBaseData>(), Offset(mDummy_fx_entry, afxMagicSpellData), &_impactPhrase,
2017-07-26 08:35:44 +00:00
"...");
addFieldV("addLingerEffect", TYPEID<afxEffectBaseData>(), Offset(mDummy_fx_entry, afxMagicSpellData), &_lingerPhrase,
2017-07-26 08:35:44 +00:00
"...");
endGroup("Linger Stage");
// interrupt flags
addField("allowMovementInterrupts", TypeBool, myOffset(mDo_move_interrupts),
2017-07-26 08:35:44 +00:00
"...");
addField("movementInterruptSpeed", TypeF32, myOffset(mMove_interrupt_speed),
2017-07-26 08:35:44 +00:00
"...");
// delivers projectile spells
addField("missile", TYPEID<afxMagicMissileData>(), myOffset(mMissile_db),
2017-07-26 08:35:44 +00:00
"...");
addField("launchOnServerSignal", TypeBool, myOffset(mLaunch_on_server_signal),
2017-07-26 08:35:44 +00:00
"...");
addField("primaryTargetTypes", TypeS32, myOffset(mPrimary_target_types),
2017-07-26 08:35:44 +00:00
"...");
Parent::initPersistFields();
// disallow some field substitutions
onlyKeepClearSubstitutions("missile"); // subs resolving to "~~", or "~0" are OK
disableFieldSubstitutions("addCastingEffect");
disableFieldSubstitutions("addLaunchEffect");
disableFieldSubstitutions("addDeliveryEffect");
disableFieldSubstitutions("addImpactEffect");
disableFieldSubstitutions("addLingerEffect");
}
bool afxMagicSpellData::onAdd()
{
if (Parent::onAdd() == false)
return false;
if (mMissile_db != NULL && mDelivery_dur == 0.0)
mDelivery_dur = -1;
2017-07-26 08:35:44 +00:00
return true;
}
void afxMagicSpellData::pack_fx(BitStream* stream, const afxEffectList& fx, bool packed)
{
stream->writeInt(fx.size(), EFFECTS_PER_PHRASE_BITS);
for (int i = 0; i < fx.size(); i++)
writeDatablockID(stream, fx[i], packed);
}
void afxMagicSpellData::unpack_fx(BitStream* stream, afxEffectList& fx)
{
fx.clear();
S32 n_fx = stream->readInt(EFFECTS_PER_PHRASE_BITS);
for (int i = 0; i < n_fx; i++)
fx.push_back((afxEffectWrapperData*)(uintptr_t)readDatablockID(stream));
2017-07-26 08:35:44 +00:00
}
void afxMagicSpellData::packData(BitStream* stream)
{
Parent::packData(stream);
stream->write(mCasting_dur);
stream->write(mDelivery_dur);
stream->write(mLinger_dur);
2017-07-26 08:35:44 +00:00
//
stream->write(mNum_casting_loops);
stream->write(mNum_delivery_loops);
stream->write(mNum_linger_loops);
2017-07-26 08:35:44 +00:00
//
stream->write(mExtra_casting_time);
stream->write(mExtra_delivery_time);
stream->write(mExtra_linger_time);
2017-07-26 08:35:44 +00:00
stream->writeFlag(mDo_move_interrupts);
stream->write(mMove_interrupt_speed);
2017-07-26 08:35:44 +00:00
writeDatablockID(stream, mMissile_db, mPacked);
stream->write(mLaunch_on_server_signal);
stream->write(mPrimary_target_types);
2017-07-26 08:35:44 +00:00
pack_fx(stream, mCasting_fx_list, mPacked);
pack_fx(stream, mLaunch_fx_list, mPacked);
pack_fx(stream, mDelivery_fx_list, mPacked);
pack_fx(stream, mImpact_fx_list, mPacked);
pack_fx(stream, mLinger_fx_list, mPacked);
2017-07-26 08:35:44 +00:00
}
void afxMagicSpellData::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
stream->read(&mCasting_dur);
stream->read(&mDelivery_dur);
stream->read(&mLinger_dur);
2017-07-26 08:35:44 +00:00
//
stream->read(&mNum_casting_loops);
stream->read(&mNum_delivery_loops);
stream->read(&mNum_linger_loops);
2017-07-26 08:35:44 +00:00
//
stream->read(&mExtra_casting_time);
stream->read(&mExtra_delivery_time);
stream->read(&mExtra_linger_time);
2017-07-26 08:35:44 +00:00
mDo_move_interrupts = stream->readFlag();
stream->read(&mMove_interrupt_speed);
2017-07-26 08:35:44 +00:00
mMissile_db = (afxMagicMissileData*)(uintptr_t)readDatablockID(stream);
stream->read(&mLaunch_on_server_signal);
stream->read(&mPrimary_target_types);
2017-07-26 08:35:44 +00:00
mDo_id_convert = true;
unpack_fx(stream, mCasting_fx_list);
unpack_fx(stream, mLaunch_fx_list);
unpack_fx(stream, mDelivery_fx_list);
unpack_fx(stream, mImpact_fx_list);
unpack_fx(stream, mLinger_fx_list);
2017-07-26 08:35:44 +00:00
}
bool afxMagicSpellData::writeField(StringTableEntry fieldname, const char* value)
{
if (!Parent::writeField(fieldname, value))
return false;
// don't write the dynamic array fields
if( fieldname == StringTable->insert("addCastingEffect") )
return false;
if( fieldname == StringTable->insert("addLaunchEffect") )
return false;
if( fieldname == StringTable->insert("addDeliveryEffect") )
return false;
if( fieldname == StringTable->insert("addImpactEffect") )
return false;
if( fieldname == StringTable->insert("addLingerEffect") )
return false;
return true;
}
inline void expand_fx_list(afxEffectList& fx_list, const char* tag)
{
for (S32 i = 0; i < fx_list.size(); i++)
{
SimObjectId db_id = SimObjectId((uintptr_t)fx_list[i]);
if (db_id != 0)
{
// try to convert id to pointer
if (!Sim::findObject(db_id, fx_list[i]))
{
Con::errorf(ConsoleLogEntry::General,
"afxMagicSpellData::preload() -- bad datablockId: 0x%x (%s)",
db_id, tag);
}
}
}
}
bool afxMagicSpellData::preload(bool server, String &errorStr)
{
if (!Parent::preload(server, errorStr))
return false;
// Resolve objects transmitted from server
if (!server)
{
if (mDo_id_convert)
2017-07-26 08:35:44 +00:00
{
SimObjectId missile_id = SimObjectId((uintptr_t)mMissile_db);
2017-07-26 08:35:44 +00:00
if (missile_id != 0)
{
// try to convert id to pointer
if (!Sim::findObject(missile_id, mMissile_db))
2017-07-26 08:35:44 +00:00
{
Con::errorf(ConsoleLogEntry::General,
"afxMagicSpellData::preload() -- bad datablockId: 0x%x (missile)",
missile_id);
}
}
expand_fx_list(mCasting_fx_list, "casting");
expand_fx_list(mLaunch_fx_list, "launch");
expand_fx_list(mDelivery_fx_list, "delivery");
expand_fx_list(mImpact_fx_list, "impact");
expand_fx_list(mLinger_fx_list, "linger");
mDo_id_convert = false;
2017-07-26 08:35:44 +00:00
}
}
return true;
}
void afxMagicSpellData::gatherConstraintDefs(Vector<afxConstraintDef>& defs)
{
afxConstraintDef::gather_cons_defs(defs, mCasting_fx_list);
afxConstraintDef::gather_cons_defs(defs, mLaunch_fx_list);
afxConstraintDef::gather_cons_defs(defs, mDelivery_fx_list);
afxConstraintDef::gather_cons_defs(defs, mImpact_fx_list);
afxConstraintDef::gather_cons_defs(defs, mLinger_fx_list);
2017-07-26 08:35:44 +00:00
if (mMissile_db)
mMissile_db->gather_cons_defs(defs);
2017-07-26 08:35:44 +00:00
}
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
DefineEngineMethod(afxMagicSpellData, reset, void, (),,
"Resets a spell datablock during reload.\n\n"
"@ingroup AFX")
{
object->reloadReset();
}
2020-10-04 01:18:54 +00:00
DefineEngineMethod(afxMagicSpellData, pushCastingEffect, void, (afxEffectBaseData* effect),,
2017-07-26 08:35:44 +00:00
"Adds an effect (wrapper or group) to a spell's casting phase.\n\n"
"@ingroup AFX")
{
if (!effect)
{
Con::errorf(ConsoleLogEntry::General,
"afxMagicSpellData::addCastingEffect() -- "
"missing afxEffectWrapperData.");
return;
}
object->mCasting_fx_list.push_back(effect);
2017-07-26 08:35:44 +00:00
}
2020-10-04 01:18:54 +00:00
DefineEngineMethod(afxMagicSpellData, pushLaunchEffect, void, (afxEffectBaseData* effect),,
2017-07-26 08:35:44 +00:00
"Adds an effect (wrapper or group) to a spell's launch phase.\n\n"
"@ingroup AFX")
{
if (!effect)
{
Con::errorf(ConsoleLogEntry::General,
"afxMagicSpellData::addLaunchEffect() -- "
"failed to find afxEffectWrapperData.");
return;
}
object->mLaunch_fx_list.push_back(effect);
2017-07-26 08:35:44 +00:00
}
2020-10-04 01:18:54 +00:00
DefineEngineMethod(afxMagicSpellData, pushDeliveryEffect, void, (afxEffectBaseData* effect),,
2017-07-26 08:35:44 +00:00
"Adds an effect (wrapper or group) to a spell's delivery phase.\n\n"
"@ingroup AFX")
{
if (!effect)
{
Con::errorf(ConsoleLogEntry::General,
"afxMagicSpellData::addDeliveryEffect() -- "
"missing afxEffectWrapperData.");
return;
}
object->mDelivery_fx_list.push_back(effect);
2017-07-26 08:35:44 +00:00
}
2020-10-04 01:18:54 +00:00
DefineEngineMethod(afxMagicSpellData, pushImpactEffect, void, (afxEffectBaseData* effect),,
2017-07-26 08:35:44 +00:00
"Adds an effect (wrapper or group) to a spell's impact phase.\n\n"
"@ingroup AFX")
{
if (!effect)
{
Con::errorf(ConsoleLogEntry::General,
"afxMagicSpellData::addImpactEffect() -- "
"missing afxEffectWrapperData.");
return;
}
object->mImpact_fx_list.push_back(effect);
2017-07-26 08:35:44 +00:00
}
2020-10-04 01:18:54 +00:00
DefineEngineMethod(afxMagicSpellData, pushLingerEffect, void, (afxEffectBaseData* effect),,
2017-07-26 08:35:44 +00:00
"Adds an effect (wrapper or group) to a spell's linger phase.\n\n"
"@ingroup AFX")
{
if (!effect)
{
Con::errorf(ConsoleLogEntry::General,
"afxMagicSpellData::addLingerEffect() -- "
"missing afxEffectWrapperData.");
return;
}
object->mLinger_fx_list.push_back(effect);
2017-07-26 08:35:44 +00:00
}
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// afxMagicSpell
IMPLEMENT_GLOBAL_CALLBACK( onCastingStart, void, (), (),
"A callout called on clients by spells when the casting stage begins.\n"
"@ingroup AFX\n" );
IMPLEMENT_GLOBAL_CALLBACK( onCastingProgressUpdate, void, (F32 frac), (frac),
"A callout called periodically on clients by spells to indicate casting progress.\n"
"@ingroup AFX\n" );
IMPLEMENT_GLOBAL_CALLBACK( onCastingEnd, void, (), (),
"A callout called on clients by spells when the casting stage ends.\n"
"@ingroup AFX\n" );
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// CastingPhrase_C
// Subclass of afxPhrase for the client casting phrase.
// This subclass adds handling of the casting progress
// bar in cases where the caster is the client's control
// object.
//
class CastingPhrase_C : public afxPhrase
{
typedef afxPhrase Parent;
ShapeBase* mCaster;
bool mNotify_castbar;
F32 mCastbar_progress;
2017-07-26 08:35:44 +00:00
public:
/*C*/ CastingPhrase_C(ShapeBase* caster, bool notify_castbar);
void start(F32 startstamp, F32 timestamp) override;
void update(F32 dt, F32 timestamp) override;
void stop(F32 timestamp) override;
void interrupt(F32 timestamp) override;
2017-07-26 08:35:44 +00:00
};
CastingPhrase_C::CastingPhrase_C(ShapeBase* c, bool notify)
: afxPhrase(false, true)
{
mCaster = c;
mNotify_castbar = notify;
mCastbar_progress = 0.0f;
2017-07-26 08:35:44 +00:00
}
void CastingPhrase_C::start(F32 startstamp, F32 timestamp)
{
Parent::start(startstamp, timestamp); //START
if (mNotify_castbar)
2017-07-26 08:35:44 +00:00
{
mCastbar_progress = 0.0f;
2017-07-26 08:35:44 +00:00
onCastingStart_callback();
}
}
void CastingPhrase_C::update(F32 dt, F32 timestamp)
{
Parent::update(dt, timestamp);
if (!mNotify_castbar)
2017-07-26 08:35:44 +00:00
return;
if (mDur > 0 && mNum_loops > 0)
2017-07-26 08:35:44 +00:00
{
F32 nfrac = (timestamp - mStartTime)/(mDur*mNum_loops);
if (nfrac - mCastbar_progress > 1.0f/200.0f)
2017-07-26 08:35:44 +00:00
{
mCastbar_progress = (nfrac < 1.0f) ? nfrac : 1.0f;
onCastingProgressUpdate_callback(mCastbar_progress);
2017-07-26 08:35:44 +00:00
}
}
}
void CastingPhrase_C::stop(F32 timestamp)
{
Parent::stop(timestamp);
if (mCastbar_progress)
2017-07-26 08:35:44 +00:00
{
onCastingEnd_callback();
mNotify_castbar = false;
2017-07-26 08:35:44 +00:00
}
}
void CastingPhrase_C::interrupt(F32 timestamp)
{
Parent::interrupt(timestamp);
if (mNotify_castbar)
2017-07-26 08:35:44 +00:00
{
onCastingEnd_callback();
mNotify_castbar = false;
2017-07-26 08:35:44 +00:00
}
}
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// some enum to name converters for debugging purposes
#ifdef USE_FOR_DEBUG_MESSAGES
static char* name_from_state(U8 s)
{
switch (s)
{
case afxMagicSpell::INACTIVE_STATE:
return "inactive";
case afxMagicSpell::CASTING_STATE:
return "casting";
case afxMagicSpell::DELIVERY_STATE:
return "delivery";
case afxMagicSpell::LINGER_STATE:
return "linger";
case afxMagicSpell::CLEANUP_STATE:
return "cleanup";
case afxMagicSpell::DONE_STATE:
return "done";
}
return "unknown";
}
static char* name_from_event(U8 e)
{
switch (e)
{
case afxMagicSpell::NULL_EVENT:
return "null";
case afxMagicSpell::ACTIVATE_EVENT:
return "activate";
case afxMagicSpell::LAUNCH_EVENT:
return "launch";
case afxMagicSpell::IMPACT_EVENT:
return "impact";
case afxMagicSpell::SHUTDOWN_EVENT:
return "shutdown";
case afxMagicSpell::DEACTIVATE_EVENT:
return "deactivate";
case afxMagicSpell::INTERRUPT_PHASE_EVENT:
return "interrupt_phase";
case afxMagicSpell::INTERRUPT_SPELL_EVENT:
return "interrupt_spell";
}
return "unknown";
}
#endif
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// afxMagicSpell
IMPLEMENT_CO_NETOBJECT_V1(afxMagicSpell);
ConsoleDocClass( afxMagicSpell,
"@brief A magic spell effects choreographer.\n\n"
"@ingroup afxChoreographers\n"
"@ingroup AFX\n"
);
// static
StringTableEntry afxMagicSpell::CASTER_CONS;
StringTableEntry afxMagicSpell::TARGET_CONS;
StringTableEntry afxMagicSpell::MISSILE_CONS;
StringTableEntry afxMagicSpell::CAMERA_CONS;
StringTableEntry afxMagicSpell::LISTENER_CONS;
StringTableEntry afxMagicSpell::IMPACT_POINT_CONS;
StringTableEntry afxMagicSpell::IMPACTED_OBJECT_CONS;
void afxMagicSpell::init()
{
// setup static predefined constraint names
if (CASTER_CONS == 0)
{
CASTER_CONS = StringTable->insert("caster");
TARGET_CONS = StringTable->insert("target");
MISSILE_CONS = StringTable->insert("missile");
CAMERA_CONS = StringTable->insert("camera");
LISTENER_CONS = StringTable->insert("listener");
IMPACT_POINT_CONS = StringTable->insert("impactPoint");
IMPACTED_OBJECT_CONS = StringTable->insert("impactedObject");
}
// afxMagicSpell is always in scope, however effects
// do their own scoping in that they will shut off if
// their position constraint leaves scope.
//
// note -- ghosting is delayed until constraint
// initialization is done.
//
//mNetFlags.set(Ghostable | ScopeAlways);
mNetFlags.clear(Ghostable | ScopeAlways);
mDatablock = NULL;
mExeblock = NULL;
mMissile_db = NULL;
2017-07-26 08:35:44 +00:00
mCaster = NULL;
mTarget = NULL;
2017-07-26 08:35:44 +00:00
mCaster_field = NULL;
mTarget_field = NULL;
2017-07-26 08:35:44 +00:00
mCaster_scope_id = 0;
mTarget_scope_id = 0;
mTarget_is_shape = false;
2017-07-26 08:35:44 +00:00
mConstraints_initialized = false;
mScoping_initialized = false;
2017-07-26 08:35:44 +00:00
mSpell_state = (U8) INACTIVE_STATE;
mSpell_elapsed = 0;
2017-07-26 08:35:44 +00:00
// define named constraints
constraint_mgr->defineConstraint(OBJECT_CONSTRAINT, CASTER_CONS);
constraint_mgr->defineConstraint(OBJECT_CONSTRAINT, TARGET_CONS);
constraint_mgr->defineConstraint(OBJECT_CONSTRAINT, MISSILE_CONS);
constraint_mgr->defineConstraint(CAMERA_CONSTRAINT, CAMERA_CONS);
constraint_mgr->defineConstraint(POINT_CONSTRAINT, LISTENER_CONS);
constraint_mgr->defineConstraint(POINT_CONSTRAINT, IMPACT_POINT_CONS);
constraint_mgr->defineConstraint(OBJECT_CONSTRAINT, IMPACTED_OBJECT_CONS);
for (S32 i = 0; i < NUM_PHRASES; i++)
{
mPhrases[i] = NULL;
mTfactors[i] = 1.0f;
2017-07-26 08:35:44 +00:00
}
mNotify_castbar = false;
mOverall_time_factor = 1.0f;
2017-07-26 08:35:44 +00:00
mCamera_cons_obj = 0;
2017-07-26 08:35:44 +00:00
mMarks_mask = 0;
2017-07-26 08:35:44 +00:00
mMissile = NULL;
mMissile_is_armed = false;
mImpacted_obj = NULL;
mImpact_pos.zero();
mImpact_norm.set(0,0,1);
mImpacted_scope_id = 0;
mImpacted_is_shape = false;
2017-07-26 08:35:44 +00:00
}
afxMagicSpell::afxMagicSpell()
{
started_with_newop = true;
init();
}
afxMagicSpell::afxMagicSpell(ShapeBase* caster, SceneObject* target)
{
started_with_newop = false;
init();
mCaster = caster;
2017-07-26 08:35:44 +00:00
if (caster)
{
mCaster_field = caster;
2017-07-26 08:35:44 +00:00
deleteNotify(caster);
processAfter(caster);
}
mTarget = target;
2017-07-26 08:35:44 +00:00
if (target)
{
mTarget_field = target;
2017-07-26 08:35:44 +00:00
deleteNotify(target);
}
}
afxMagicSpell::~afxMagicSpell()
{
for (S32 i = 0; i < NUM_PHRASES; i++)
{
if (mPhrases[i])
2017-07-26 08:35:44 +00:00
{
mPhrases[i]->interrupt(mSpell_elapsed);
delete mPhrases[i];
2017-07-26 08:35:44 +00:00
}
}
if (mMissile)
mMissile->deleteObject();
2017-07-26 08:35:44 +00:00
if (mMissile_db && mMissile_db->isTempClone())
2017-07-26 08:35:44 +00:00
{
delete mMissile_db;
mMissile_db = 0;
2017-07-26 08:35:44 +00:00
}
if (mDatablock && mDatablock->isTempClone())
2017-07-26 08:35:44 +00:00
{
delete mDatablock;
mDatablock = 0;
2017-07-26 08:35:44 +00:00
}
}
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// STANDARD OVERLOADED METHODS //
bool afxMagicSpell::onNewDataBlock(GameBaseData* dptr, bool reload)
{
mDatablock = dynamic_cast<afxMagicSpellData*>(dptr);
if (!mDatablock || !Parent::onNewDataBlock(dptr, reload))
2017-07-26 08:35:44 +00:00
return false;
if (isServerObject() && started_with_newop)
{
// copy dynamic fields from the datablock but
// don't replace fields with a value
assignDynamicFieldsFrom(dptr, arcaneFX::sParameterFieldPrefix, true);
}
mExeblock = mDatablock;
mMissile_db = mDatablock->mMissile_db;
2017-07-26 08:35:44 +00:00
if (isClientObject())
{
// make a temp datablock clone if there are substitutions
if (mDatablock->getSubstitutionCount() > 0)
2017-07-26 08:35:44 +00:00
{
afxMagicSpellData* orig_db = mDatablock;
mDatablock = new afxMagicSpellData(*orig_db, true);
mExeblock = orig_db;
mMissile_db = mDatablock->mMissile_db;
2017-07-26 08:35:44 +00:00
// Don't perform substitutions yet, the spell's dynamic fields haven't
// arrived yet and the substitutions may refer to them. Hold off and do
// in in the onAdd() method.
}
}
else if (started_with_newop)
{
// make a temp datablock clone if there are substitutions
if (mDatablock->getSubstitutionCount() > 0)
2017-07-26 08:35:44 +00:00
{
afxMagicSpellData* orig_db = mDatablock;
mDatablock = new afxMagicSpellData(*orig_db, true);
mExeblock = orig_db;
orig_db->performSubstitutions(mDatablock, this, ranking);
mMissile_db = mDatablock->mMissile_db;
2017-07-26 08:35:44 +00:00
}
}
return true;
}
void afxMagicSpell::processTick(const Move* m)
{
Parent::processTick(m);
// don't process moves or client ticks
if (m != 0 || isClientObject())
return;
process_server();
}
void afxMagicSpell::advanceTime(F32 dt)
{
Parent::advanceTime(dt);
process_client(dt);
}
bool afxMagicSpell::onAdd()
{
if (!Parent::onAdd())
return false ;
if (isClientObject())
{
if (mDatablock->isTempClone())
2017-07-26 08:35:44 +00:00
{
afxMagicSpellData* orig_db = (afxMagicSpellData*)mExeblock;
orig_db->performSubstitutions(mDatablock, this, ranking);
mMissile_db = mDatablock->mMissile_db;
mNotify_castbar = (mNotify_castbar && (mDatablock->mCasting_dur > 0.0f));
2017-07-26 08:35:44 +00:00
}
}
else if (started_with_newop && !postpone_activation)
{
if (!activationCallInit())
return false;
activate();
}
return true ;
}
void afxMagicSpell::onRemove()
{
Parent::onRemove();
}
void afxMagicSpell::onDeleteNotify(SimObject* obj)
{
// caster deleted?
ShapeBase* shape = dynamic_cast<ShapeBase*>(obj);
if (shape == mCaster)
2017-07-26 08:35:44 +00:00
{
clearProcessAfter();
mCaster = NULL;
mCaster_field = NULL;
mCaster_scope_id = 0;
2017-07-26 08:35:44 +00:00
}
// target deleted?
SceneObject* scene_obj = dynamic_cast<SceneObject*>(obj);
if (scene_obj == mTarget)
2017-07-26 08:35:44 +00:00
{
mTarget = NULL;
mTarget_field = NULL;
mTarget_scope_id = 0;
mTarget_is_shape = false;
2017-07-26 08:35:44 +00:00
}
// impacted_obj deleted?
if (scene_obj == mImpacted_obj)
2017-07-26 08:35:44 +00:00
{
mImpacted_obj = NULL;
mImpacted_scope_id = 0;
mImpacted_is_shape = false;
2017-07-26 08:35:44 +00:00
}
// missile deleted?
afxMagicMissile* missile = dynamic_cast<afxMagicMissile*>(obj);
if (missile != NULL && missile == mMissile)
2017-07-26 08:35:44 +00:00
{
mMissile = NULL;
2017-07-26 08:35:44 +00:00
}
// something else
Parent::onDeleteNotify(obj);
}
// static
void afxMagicSpell::initPersistFields()
{
docsURL;
addField("caster", TYPEID<SimObject>(), Offset(mCaster_field, afxMagicSpell),
2017-07-26 08:35:44 +00:00
"...");
addField("target", TYPEID<SimObject>(), Offset(mTarget_field, afxMagicSpell),
2017-07-26 08:35:44 +00:00
"...");
Parent::initPersistFields();
}
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
void afxMagicSpell::pack_constraint_info(NetConnection* conn, BitStream* stream)
{
// pack caster's ghost index or scope id if not yet ghosted
if (stream->writeFlag(mCaster != NULL))
2017-07-26 08:35:44 +00:00
{
S32 ghost_idx = conn->getGhostIndex(mCaster);
2017-07-26 08:35:44 +00:00
if (stream->writeFlag(ghost_idx != -1))
stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount);
else
{
bool bit = (mCaster) ? (mCaster->getScopeId() > 0) : false;
2017-07-26 08:35:44 +00:00
if (stream->writeFlag(bit))
stream->writeInt(mCaster->getScopeId(), NetObject::SCOPE_ID_BITS);
2017-07-26 08:35:44 +00:00
}
}
// pack target's ghost index or scope id if not yet ghosted
if (stream->writeFlag(mTarget != NULL))
2017-07-26 08:35:44 +00:00
{
S32 ghost_idx = conn->getGhostIndex(mTarget);
2017-07-26 08:35:44 +00:00
if (stream->writeFlag(ghost_idx != -1))
stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount);
else
{
if (stream->writeFlag(mTarget->getScopeId() > 0))
2017-07-26 08:35:44 +00:00
{
stream->writeInt(mTarget->getScopeId(), NetObject::SCOPE_ID_BITS);
stream->writeFlag(dynamic_cast<ShapeBase*>(mTarget) != NULL); // is shape?
2017-07-26 08:35:44 +00:00
}
}
}
Parent::pack_constraint_info(conn, stream);
}
void afxMagicSpell::unpack_constraint_info(NetConnection* conn, BitStream* stream)
{
mCaster = NULL;
mCaster_field = NULL;
mCaster_scope_id = 0;
2017-07-26 08:35:44 +00:00
if (stream->readFlag()) // has caster
{
if (stream->readFlag()) // has ghost_idx
{
S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount);
mCaster = dynamic_cast<ShapeBase*>(conn->resolveGhost(ghost_idx));
if (mCaster)
2017-07-26 08:35:44 +00:00
{
mCaster_field = mCaster;
deleteNotify(mCaster);
processAfter(mCaster);
2017-07-26 08:35:44 +00:00
}
}
else
{
if (stream->readFlag()) // has scope_id (is always a shape)
mCaster_scope_id = stream->readInt(NetObject::SCOPE_ID_BITS);
2017-07-26 08:35:44 +00:00
}
}
mTarget = NULL;
mTarget_field = NULL;
mTarget_scope_id = 0;
mTarget_is_shape = false;
2017-07-26 08:35:44 +00:00
if (stream->readFlag()) // has target
{
if (stream->readFlag()) // has ghost_idx
{
S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount);
mTarget = dynamic_cast<SceneObject*>(conn->resolveGhost(ghost_idx));
if (mTarget)
2017-07-26 08:35:44 +00:00
{
mTarget_field = mTarget;
deleteNotify(mTarget);
2017-07-26 08:35:44 +00:00
}
}
else
{
if (stream->readFlag()) // has scope_id
{
mTarget_scope_id = stream->readInt(NetObject::SCOPE_ID_BITS);
mTarget_is_shape = stream->readFlag(); // is shape?
2017-07-26 08:35:44 +00:00
}
}
}
Parent::unpack_constraint_info(conn, stream);
}
U32 afxMagicSpell::packUpdate(NetConnection* conn, U32 mask, BitStream* stream)
{
S32 mark_stream_pos = stream->getCurPos();
U32 retMask = Parent::packUpdate(conn, mask, stream);
// InitialUpdate
if (stream->writeFlag(mask & InitialUpdateMask))
{
// pack extra object's ghost index or scope id if not yet ghosted
2018-03-12 22:41:22 +00:00
if (stream->writeFlag(dynamic_cast<NetObject*>(mExtra) != 0))
2017-07-26 08:35:44 +00:00
{
2018-03-12 22:41:22 +00:00
NetObject* net_extra = (NetObject*)mExtra;
2017-07-26 08:35:44 +00:00
S32 ghost_idx = conn->getGhostIndex(net_extra);
if (stream->writeFlag(ghost_idx != -1))
stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount);
else
{
if (stream->writeFlag(net_extra->getScopeId() > 0))
{
stream->writeInt(net_extra->getScopeId(), NetObject::SCOPE_ID_BITS);
}
}
}
// pack initial exec conditions
stream->write(exec_conds_mask);
// flag if this client owns the spellcaster
bool client_owns_caster = is_caster_client(mCaster, dynamic_cast<GameConnection*>(conn));
2017-07-26 08:35:44 +00:00
stream->writeFlag(client_owns_caster);
// pack per-phrase time-factor values
for (S32 i = 0; i < NUM_PHRASES; i++)
stream->write(mTfactors[i]);
2017-07-26 08:35:44 +00:00
// flag if this conn is zoned-in yet
bool zoned_in = client_owns_caster;
if (!zoned_in)
{
GameConnection* gconn = dynamic_cast<GameConnection*>(conn);
zoned_in = (gconn) ? gconn->isZonedIn() : false;
}
if (stream->writeFlag(zoned_in))
pack_constraint_info(conn, stream);
}
// StateEvent or SyncEvent
if (stream->writeFlag((mask & StateEventMask) || (mask & SyncEventMask)))
{
stream->write(mMarks_mask);
stream->write(mSpell_state);
2017-07-26 08:35:44 +00:00
stream->write(state_elapsed());
stream->write(mSpell_elapsed);
2017-07-26 08:35:44 +00:00
}
// SyncEvent
if (stream->writeFlag((mask & SyncEventMask) && !(mask & InitialUpdateMask)))
{
pack_constraint_info(conn, stream);
}
// LaunchEvent
if (stream->writeFlag((mask & LaunchEventMask) && (mMarks_mask & MARK_LAUNCH) && mMissile))
2017-07-26 08:35:44 +00:00
{
F32 vel; Point3F vel_vec;
mMissile->getStartingVelocityValues(vel, vel_vec);
2017-07-26 08:35:44 +00:00
// pack launch vector and velocity
stream->write(vel);
mathWrite(*stream, vel_vec);
}
// ImpactEvent
if (stream->writeFlag(((mask & ImpactEventMask) || (mask & SyncEventMask)) && (mMarks_mask & MARK_IMPACT)))
2017-07-26 08:35:44 +00:00
{
// pack impact objects's ghost index or scope id if not yet ghosted
if (stream->writeFlag(mImpacted_obj != NULL))
2017-07-26 08:35:44 +00:00
{
S32 ghost_idx = conn->getGhostIndex(mImpacted_obj);
2017-07-26 08:35:44 +00:00
if (stream->writeFlag(ghost_idx != -1))
stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount);
else
{
if (stream->writeFlag(mImpacted_obj->getScopeId() > 0))
2017-07-26 08:35:44 +00:00
{
stream->writeInt(mImpacted_obj->getScopeId(), NetObject::SCOPE_ID_BITS);
stream->writeFlag(dynamic_cast<ShapeBase*>(mImpacted_obj) != NULL);
2017-07-26 08:35:44 +00:00
}
}
}
// pack impact position and normal
mathWrite(*stream, mImpact_pos);
mathWrite(*stream, mImpact_norm);
2017-07-26 08:35:44 +00:00
stream->write(exec_conds_mask);
ShapeBase* temp_shape;
stream->writeFlag(mCaster != 0 && mCaster->getDamageState() == ShapeBase::Enabled);
temp_shape = dynamic_cast<ShapeBase*>(mTarget);
2017-07-26 08:35:44 +00:00
stream->writeFlag(temp_shape != 0 && temp_shape->getDamageState() == ShapeBase::Enabled);
temp_shape = dynamic_cast<ShapeBase*>(mImpacted_obj);
2017-07-26 08:35:44 +00:00
stream->writeFlag(temp_shape != 0 && temp_shape->getDamageState() == ShapeBase::Enabled);
}
check_packet_usage(conn, stream, mark_stream_pos, "afxMagicSpell:");
AssertISV(stream->isValid(), "afxMagicSpell::packUpdate(): write failure occurred, possibly caused by packet-size overrun.");
return retMask;
}
//~~~~~~~~~~~~~~~~~~~~//
// CONSTRAINT REMAPPING <<
bool afxMagicSpell::remap_builtin_constraint(SceneObject* obj, const char* cons_name)
{
StringTableEntry cons_name_ste = StringTable->insert(cons_name);
if (cons_name_ste == CASTER_CONS)
return true;
if (cons_name_ste == TARGET_CONS)
{
if (obj && mTarget && obj != mTarget && !mTarget_cons_id.undefined())
2017-07-26 08:35:44 +00:00
{
mTarget = obj;
constraint_mgr->setReferenceObject(mTarget_cons_id, mTarget);
2017-07-26 08:35:44 +00:00
if (isServerObject())
{
if (mTarget->isScopeable())
constraint_mgr->addScopeableObject(mTarget);
2017-07-26 08:35:44 +00:00
}
}
return true;
}
if (cons_name_ste == MISSILE_CONS)
return true;
if (cons_name_ste == CAMERA_CONS)
return true;
if (cons_name_ste == LISTENER_CONS)
return true;
if (cons_name_ste == IMPACT_POINT_CONS)
return true;
if (cons_name_ste == IMPACTED_OBJECT_CONS)
return true;
return false;
}
// CONSTRAINT REMAPPING >>
void afxMagicSpell::unpackUpdate(NetConnection * conn, BitStream * stream)
{
Parent::unpackUpdate(conn, stream);
bool initial_update = false;
bool zoned_in = true;
bool do_sync_event = false;
U16 new_marks_mask = 0;
U8 new_spell_state = INACTIVE_STATE;
F32 new_state_elapsed = 0;
F32 new_spell_elapsed = 0;;
// InitialUpdate
if (stream->readFlag())
{
initial_update = true;
// extra sent
if (stream->readFlag())
{
// cleanup?
if (stream->readFlag()) // is ghost_idx
{
S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount);
2018-03-12 22:41:22 +00:00
mExtra = dynamic_cast<SimObject*>(conn->resolveGhost(ghost_idx));
2017-07-26 08:35:44 +00:00
}
else
{
if (stream->readFlag()) // has scope_id
{
// JTF NOTE: U16 extra_scope_id = stream->readInt(NetObject::SCOPE_ID_BITS);
stream->readInt(NetObject::SCOPE_ID_BITS);
}
}
}
// unpack initial exec conditions
stream->read(&exec_conds_mask);
// if this is controlling client for the caster,
// enable castbar updates
bool client_owns_caster = stream->readFlag();
if (client_owns_caster)
mNotify_castbar = Con::isFunction("onCastingStart");
2017-07-26 08:35:44 +00:00
// unpack per-phrase time-factor values
for (S32 i = 0; i < NUM_PHRASES; i++)
stream->read(&mTfactors[i]);
2017-07-26 08:35:44 +00:00
// if client is marked as fully zoned in
if ((zoned_in = stream->readFlag()) == true)
{
unpack_constraint_info(conn, stream);
init_constraints();
}
}
// StateEvent or SyncEvent
// this state data is sent for both state-events and
// sync-events
if (stream->readFlag())
{
stream->read(&new_marks_mask);
stream->read(&new_spell_state);
stream->read(&new_state_elapsed);
stream->read(&new_spell_elapsed);
mMarks_mask = new_marks_mask;
2017-07-26 08:35:44 +00:00
}
// SyncEvent
if ((do_sync_event = stream->readFlag()) == true)
{
unpack_constraint_info(conn, stream);
init_constraints();
}
// LaunchEvent
if (stream->readFlag())
{
F32 vel; Point3F vel_vec;
stream->read(&vel);
mathRead(*stream, &vel_vec);
if (mMissile)
2017-07-26 08:35:44 +00:00
{
mMissile->setStartingVelocity(vel);
mMissile->setStartingVelocityVector(vel_vec);
2017-07-26 08:35:44 +00:00
}
}
// ImpactEvent
if (stream->readFlag())
{
if (mImpacted_obj)
clearNotify(mImpacted_obj);
mImpacted_obj = NULL;
mImpacted_scope_id = 0;
mImpacted_is_shape = false;
2017-07-26 08:35:44 +00:00
if (stream->readFlag()) // is impacted_obj
{
if (stream->readFlag()) // is ghost_idx
{
S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount);
mImpacted_obj = dynamic_cast<SceneObject*>(conn->resolveGhost(ghost_idx));
if (mImpacted_obj)
deleteNotify(mImpacted_obj);
2017-07-26 08:35:44 +00:00
}
else
{
if (stream->readFlag()) // has scope_id
{
mImpacted_scope_id = stream->readInt(NetObject::SCOPE_ID_BITS);
mImpacted_is_shape = stream->readFlag(); // is shape?
2017-07-26 08:35:44 +00:00
}
}
}
mathRead(*stream, &mImpact_pos);
mathRead(*stream, &mImpact_norm);
2017-07-26 08:35:44 +00:00
stream->read(&exec_conds_mask);
bool caster_alive = stream->readFlag();
bool target_alive = stream->readFlag();
bool impacted_alive = stream->readFlag();
afxConstraint* cons;
if ((cons = constraint_mgr->getConstraint(mCaster_cons_id)) != 0)
2017-07-26 08:35:44 +00:00
cons->setLivingState(caster_alive);
if ((cons = constraint_mgr->getConstraint(mTarget_cons_id)) != 0)
2017-07-26 08:35:44 +00:00
cons->setLivingState(target_alive);
if ((cons = constraint_mgr->getConstraint(mImpacted_cons_id)) != 0)
2017-07-26 08:35:44 +00:00
cons->setLivingState(impacted_alive);
}
//~~~~~~~~~~~~~~~~~~~~//
if (!zoned_in)
mSpell_state = LATE_STATE;
2017-07-26 08:35:44 +00:00
// need to adjust state info to get all synced up with spell on server
if (do_sync_event && !initial_update)
sync_client(new_marks_mask, new_spell_state, new_state_elapsed, new_spell_elapsed);
}
void afxMagicSpell::sync_with_clients()
{
setMaskBits(SyncEventMask);
}
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// private
bool afxMagicSpell::state_expired()
{
afxPhrase* phrase = NULL;
switch (mSpell_state)
2017-07-26 08:35:44 +00:00
{
case CASTING_STATE:
phrase = mPhrases[CASTING_PHRASE];
2017-07-26 08:35:44 +00:00
break;
case DELIVERY_STATE:
phrase = mPhrases[DELIVERY_PHRASE];
2017-07-26 08:35:44 +00:00
break;
case LINGER_STATE:
phrase = mPhrases[LINGER_PHRASE];
2017-07-26 08:35:44 +00:00
break;
}
if (phrase)
{
if (phrase->expired(mSpell_elapsed))
return (!phrase->recycle(mSpell_elapsed));
2017-07-26 08:35:44 +00:00
return false;
}
return true;
}
F32 afxMagicSpell::state_elapsed()
{
afxPhrase* phrase = NULL;
switch (mSpell_state)
2017-07-26 08:35:44 +00:00
{
case CASTING_STATE:
phrase = mPhrases[CASTING_PHRASE];
2017-07-26 08:35:44 +00:00
break;
case DELIVERY_STATE:
phrase = mPhrases[DELIVERY_PHRASE];
2017-07-26 08:35:44 +00:00
break;
case LINGER_STATE:
phrase = mPhrases[LINGER_PHRASE];
2017-07-26 08:35:44 +00:00
break;
}
return (phrase) ? phrase->elapsed(mSpell_elapsed) : 0.0f;
2017-07-26 08:35:44 +00:00
}
void afxMagicSpell::init_constraints()
{
if (mConstraints_initialized)
2017-07-26 08:35:44 +00:00
{
//Con::printf("CONSTRAINTS ALREADY INITIALIZED");
return;
}
Vector<afxConstraintDef> defs;
mDatablock->gatherConstraintDefs(defs);
2017-07-26 08:35:44 +00:00
constraint_mgr->initConstraintDefs(defs, isServerObject());
if (isServerObject())
{
mCaster_cons_id = constraint_mgr->setReferenceObject(CASTER_CONS, mCaster);
mTarget_cons_id = constraint_mgr->setReferenceObject(TARGET_CONS, mTarget);
2017-07-26 08:35:44 +00:00
#if defined(AFX_CAP_SCOPE_TRACKING)
if (mCaster && mCaster->isScopeable())
constraint_mgr->addScopeableObject(mCaster);
2017-07-26 08:35:44 +00:00
if (mTarget && mTarget->isScopeable())
constraint_mgr->addScopeableObject(mTarget);
2017-07-26 08:35:44 +00:00
#endif
// find local camera
mCamera_cons_obj = get_camera();
if (mCamera_cons_obj)
mCamera_cons_id = constraint_mgr->setReferenceObject(CAMERA_CONS, mCamera_cons_obj);
2017-07-26 08:35:44 +00:00
}
else // if (isClientObject())
{
if (mCaster)
mCaster_cons_id = constraint_mgr->setReferenceObject(CASTER_CONS, mCaster);
else if (mCaster_scope_id > 0)
mCaster_cons_id = constraint_mgr->setReferenceObjectByScopeId(CASTER_CONS, mCaster_scope_id, true);
2017-07-26 08:35:44 +00:00
if (mTarget)
mTarget_cons_id = constraint_mgr->setReferenceObject(TARGET_CONS, mTarget);
else if (mTarget_scope_id > 0)
mTarget_cons_id = constraint_mgr->setReferenceObjectByScopeId(TARGET_CONS, mTarget_scope_id, mTarget_is_shape);
2017-07-26 08:35:44 +00:00
// find local camera
mCamera_cons_obj = get_camera();
if (mCamera_cons_obj)
mCamera_cons_id = constraint_mgr->setReferenceObject(CAMERA_CONS, mCamera_cons_obj);
2017-07-26 08:35:44 +00:00
// find local listener
Point3F listener_pos;
listener_pos = SFX->getListener().getTransform().getPosition();
mListener_cons_id = constraint_mgr->setReferencePoint(LISTENER_CONS, listener_pos);
2017-07-26 08:35:44 +00:00
}
constraint_mgr->adjustProcessOrdering(this);
mConstraints_initialized = true;
2017-07-26 08:35:44 +00:00
}
void afxMagicSpell::init_scoping()
{
if (mScoping_initialized)
2017-07-26 08:35:44 +00:00
{
//Con::printf("SCOPING ALREADY INITIALIZED");
return;
}
if (isServerObject())
{
if (explicit_clients.size() > 0)
{
for (U32 i = 0; i < explicit_clients.size(); i++)
explicit_clients[i]->objectLocalScopeAlways(this);
}
else
{
mNetFlags.set(Ghostable);
setScopeAlways();
}
mScoping_initialized = true;
2017-07-26 08:35:44 +00:00
}
}
void afxMagicSpell::setup_casting_fx()
{
if (isServerObject())
mPhrases[CASTING_PHRASE] = new afxPhrase(isServerObject(), true);
2017-07-26 08:35:44 +00:00
else
mPhrases[CASTING_PHRASE] = new CastingPhrase_C(mCaster, mNotify_castbar);
2017-07-26 08:35:44 +00:00
if (mPhrases[CASTING_PHRASE])
mPhrases[CASTING_PHRASE]->init(mDatablock->mCasting_fx_list, mDatablock->mCasting_dur, this,
mTfactors[CASTING_PHRASE], mDatablock->mNum_casting_loops, 0,
mDatablock->mExtra_casting_time);
2017-07-26 08:35:44 +00:00
}
void afxMagicSpell::setup_launch_fx()
{
mPhrases[LAUNCH_PHRASE] = new afxPhrase(isServerObject(), false);
if (mPhrases[LAUNCH_PHRASE])
mPhrases[LAUNCH_PHRASE]->init(mDatablock->mLaunch_fx_list, -1, this,
mTfactors[LAUNCH_PHRASE], 1);
2017-07-26 08:35:44 +00:00
}
void afxMagicSpell::setup_delivery_fx()
{
mPhrases[DELIVERY_PHRASE] = new afxPhrase(isServerObject(), true);
if (mPhrases[DELIVERY_PHRASE])
2017-07-26 08:35:44 +00:00
{
mPhrases[DELIVERY_PHRASE]->init(mDatablock->mDelivery_fx_list, mDatablock->mDelivery_dur, this,
mTfactors[DELIVERY_PHRASE], mDatablock->mNum_delivery_loops, 0,
mDatablock->mExtra_delivery_time);
2017-07-26 08:35:44 +00:00
}
}
void afxMagicSpell::setup_impact_fx()
{
mPhrases[IMPACT_PHRASE] = new afxPhrase(isServerObject(), false);
if (mPhrases[IMPACT_PHRASE])
2017-07-26 08:35:44 +00:00
{
mPhrases[IMPACT_PHRASE]->init(mDatablock->mImpact_fx_list, -1, this,
mTfactors[IMPACT_PHRASE], 1);
2017-07-26 08:35:44 +00:00
}
}
void afxMagicSpell::setup_linger_fx()
{
mPhrases[LINGER_PHRASE] = new afxPhrase(isServerObject(), true);
if (mPhrases[LINGER_PHRASE])
mPhrases[LINGER_PHRASE]->init(mDatablock->mLinger_fx_list, mDatablock->mLinger_dur, this,
mTfactors[LINGER_PHRASE], mDatablock->mNum_linger_loops, 0,
mDatablock->mExtra_linger_time);
2017-07-26 08:35:44 +00:00
}
bool afxMagicSpell::cleanup_over()
{
for (S32 i = 0; i < NUM_PHRASES; i++)
if (mPhrases[i] && !mPhrases[i]->isEmpty())
2017-07-26 08:35:44 +00:00
return false;
return true;
}
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// private
//
// MISSILE STUFF
//
void afxMagicSpell::init_missile_s(afxMagicMissileData* mm_db)
{
if (mMissile)
clearNotify(mMissile);
2017-07-26 08:35:44 +00:00
// create the missile
mMissile = new afxMagicMissile(true, false);
mMissile->setSubstitutionData(this, ranking);
mMissile->setDataBlock(mm_db);
mMissile->setChoreographer(this);
if (!mMissile->registerObject())
2017-07-26 08:35:44 +00:00
{
Con::errorf("afxMagicSpell: failed to register missile instance.");
delete mMissile;
mMissile = NULL;
2017-07-26 08:35:44 +00:00
}
if (mMissile)
2017-07-26 08:35:44 +00:00
{
deleteNotify(mMissile);
registerForCleanup(mMissile);
2017-07-26 08:35:44 +00:00
}
}
void afxMagicSpell::launch_missile_s()
{
if (mMissile)
2017-07-26 08:35:44 +00:00
{
mMissile->launch();
constraint_mgr->setReferenceObject(MISSILE_CONS, mMissile);
2017-07-26 08:35:44 +00:00
}
}
void afxMagicSpell::init_missile_c(afxMagicMissileData* mm_db)
{
if (mMissile)
clearNotify(mMissile);
2017-07-26 08:35:44 +00:00
// create the missile
mMissile = new afxMagicMissile(false, true);
mMissile->setSubstitutionData(this, ranking);
mMissile->setDataBlock(mm_db);
mMissile->setChoreographer(this);
if (!mMissile->registerObject())
2017-07-26 08:35:44 +00:00
{
Con::errorf("afxMagicSpell: failed to register missile instance.");
delete mMissile;
mMissile = NULL;
2017-07-26 08:35:44 +00:00
}
if (mMissile)
2017-07-26 08:35:44 +00:00
{
deleteNotify(mMissile);
registerForCleanup(mMissile);
2017-07-26 08:35:44 +00:00
}
}
void afxMagicSpell::launch_missile_c()
{
if (mMissile)
2017-07-26 08:35:44 +00:00
{
mMissile->launch();
constraint_mgr->setReferenceObject(MISSILE_CONS, mMissile);
2017-07-26 08:35:44 +00:00
}
}
bool afxMagicSpell::is_impact_in_water(SceneObject* obj, const Point3F& p)
{
// AFX_T3D_BROKEN -- water impact detection is disabled. Look at projectile.
return false;
}
void afxMagicSpell::impactNotify(const Point3F& p, const Point3F& n, SceneObject* obj)
{
if (isClientObject())
return;
///impact_time_ms = spell_elapsed_ms;
if (mImpacted_obj)
clearNotify(mImpacted_obj);
mImpacted_obj = obj;
mImpact_pos = p;
mImpact_norm = n;
2017-07-26 08:35:44 +00:00
if (mImpacted_obj != NULL)
2017-07-26 08:35:44 +00:00
{
deleteNotify(mImpacted_obj);
2017-07-26 08:35:44 +00:00
exec_conds_mask |= IMPACTED_SOMETHING;
if (mImpacted_obj == mTarget)
2017-07-26 08:35:44 +00:00
exec_conds_mask |= IMPACTED_TARGET;
if (mImpacted_obj->getTypeMask() & mDatablock->mPrimary_target_types)
2017-07-26 08:35:44 +00:00
exec_conds_mask |= IMPACTED_PRIMARY;
}
if (is_impact_in_water(obj, p))
exec_conds_mask |= IMPACT_IN_WATER;
postSpellEvent(IMPACT_EVENT);
if (mMissile)
clearNotify(mMissile);
mMissile = NULL;
2017-07-26 08:35:44 +00:00
}
void afxMagicSpell::executeScriptEvent(const char* method, afxConstraint* cons,
const MatrixF& xfm, const char* data)
{
SceneObject* cons_obj = (cons) ? cons->getSceneObject() : NULL;
char *arg_buf = Con::getArgBuffer(256);
Point3F pos;
xfm.getColumn(3,&pos);
AngAxisF aa(xfm);
dSprintf(arg_buf,256,"%g %g %g %g %g %g %g",
pos.x, pos.y, pos.z,
aa.axis.x, aa.axis.y, aa.axis.z, aa.angle);
// CALL SCRIPT afxChoreographerData::method(%spell, %caster, %constraint, %transform, %data)
Con::executef(mExeblock, method,
2017-07-26 08:35:44 +00:00
getIdString(),
(mCaster) ? mCaster->getIdString() : "",
2017-07-26 08:35:44 +00:00
(cons_obj) ? cons_obj->getIdString() : "",
arg_buf,
data);
}
void afxMagicSpell::inflictDamage(const char * label, const char* flavor, SimObjectId target_id,
F32 amount, U8 n, F32 ad_amount, F32 radius, Point3F pos, F32 impulse)
{
// Con::printf("INFLICT-DAMAGE label=%s flav=%s id=%d amt=%g n=%d rad=%g pos=(%g %g %g) imp=%g",
// label, flavor, target_id, amount, n, radius, pos.x, pos.y, pos.z, impulse);
// CALL SCRIPT afxMagicSpellData::onDamage()
// onDamage(%spell, %label, %type, %damaged_obj, %amount, %count, %pos, %ad_amount,
// %radius, %impulse)
mDatablock->onDamage_callback(this, label, flavor, target_id, amount, n, pos, ad_amount, radius, impulse);
2017-07-26 08:35:44 +00:00
}
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// private
void afxMagicSpell::process_server()
{
if (mSpell_state != INACTIVE_STATE)
mSpell_elapsed += TickSec;
2017-07-26 08:35:44 +00:00
U8 pending_state = mSpell_state;
2017-07-26 08:35:44 +00:00
// check for state changes
switch (mSpell_state)
2017-07-26 08:35:44 +00:00
{
case INACTIVE_STATE:
if (mMarks_mask & MARK_ACTIVATE)
2017-07-26 08:35:44 +00:00
pending_state = CASTING_STATE;
break;
case CASTING_STATE:
if (mDatablock->mCasting_dur > 0.0f && mDatablock->mDo_move_interrupts && is_caster_moving())
2017-07-26 08:35:44 +00:00
{
displayScreenMessage(mCaster, "SPELL INTERRUPTED.");
2017-07-26 08:35:44 +00:00
postSpellEvent(INTERRUPT_SPELL_EVENT);
}
if (mMarks_mask & MARK_INTERRUPT_CASTING)
2017-07-26 08:35:44 +00:00
pending_state = CLEANUP_STATE;
else if (mMarks_mask & MARK_END_CASTING)
2017-07-26 08:35:44 +00:00
pending_state = DELIVERY_STATE;
else if (mMarks_mask & MARK_LAUNCH)
2017-07-26 08:35:44 +00:00
pending_state = DELIVERY_STATE;
else if (state_expired())
pending_state = DELIVERY_STATE;
break;
case DELIVERY_STATE:
if (mMarks_mask & MARK_INTERRUPT_DELIVERY)
2017-07-26 08:35:44 +00:00
pending_state = CLEANUP_STATE;
else if (mMarks_mask & MARK_END_DELIVERY)
2017-07-26 08:35:44 +00:00
pending_state = LINGER_STATE;
else if (mMarks_mask & MARK_IMPACT)
2017-07-26 08:35:44 +00:00
pending_state = LINGER_STATE;
else if (state_expired())
pending_state = LINGER_STATE;
break;
case LINGER_STATE:
if (mMarks_mask & MARK_INTERRUPT_LINGER)
2017-07-26 08:35:44 +00:00
pending_state = CLEANUP_STATE;
else if (mMarks_mask & MARK_END_LINGER)
2017-07-26 08:35:44 +00:00
pending_state = CLEANUP_STATE;
else if (mMarks_mask & MARK_SHUTDOWN)
2017-07-26 08:35:44 +00:00
pending_state = CLEANUP_STATE;
else if (state_expired())
pending_state = CLEANUP_STATE;
break;
case CLEANUP_STATE:
if ((mMarks_mask & MARK_INTERRUPT_CLEANUP) || cleanup_over())
2017-07-26 08:35:44 +00:00
pending_state = DONE_STATE;
break;
}
if (mSpell_state != pending_state)
2017-07-26 08:35:44 +00:00
change_state_s(pending_state);
if (mSpell_state == INACTIVE_STATE)
2017-07-26 08:35:44 +00:00
return;
//--------------------------//
// sample the constraints
constraint_mgr->sample(TickSec, Platform::getVirtualMilliseconds());
for (S32 i = 0; i < NUM_PHRASES; i++)
if (mPhrases[i])
mPhrases[i]->update(TickSec, mSpell_elapsed);
2017-07-26 08:35:44 +00:00
if (mMissile_is_armed)
2017-07-26 08:35:44 +00:00
{
launch_missile_s();
mMissile_is_armed = false;
2017-07-26 08:35:44 +00:00
}
}
void afxMagicSpell::change_state_s(U8 pending_state)
{
if (mSpell_state == pending_state)
2017-07-26 08:35:44 +00:00
return;
// LEAVING THIS STATE
switch (mSpell_state)
2017-07-26 08:35:44 +00:00
{
case INACTIVE_STATE:
break;
case CASTING_STATE:
leave_casting_state_s();
break;
case DELIVERY_STATE:
leave_delivery_state_s();
break;
case LINGER_STATE:
leave_linger_state_s();
break;
case CLEANUP_STATE:
break;
case DONE_STATE:
break;
}
mSpell_state = pending_state;
2017-07-26 08:35:44 +00:00
// ENTERING THIS STATE
switch (pending_state)
{
case INACTIVE_STATE:
break;
case CASTING_STATE:
enter_casting_state_s();
break;
case DELIVERY_STATE:
enter_delivery_state_s();
break;
case LINGER_STATE:
enter_linger_state_s();
break;
case CLEANUP_STATE:
break;
case DONE_STATE:
enter_done_state_s();
break;
}
}
void afxMagicSpell::enter_done_state_s()
{
postSpellEvent(DEACTIVATE_EVENT);
if (mMarks_mask & MARK_INTERRUPTS)
2017-07-26 08:35:44 +00:00
{
Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + 500);
}
else
{
F32 done_time = mSpell_elapsed;
2017-07-26 08:35:44 +00:00
for (S32 i = 0; i < NUM_PHRASES; i++)
{
if (mPhrases[i])
2017-07-26 08:35:44 +00:00
{
F32 phrase_done;
if (mPhrases[i]->willStop() && mPhrases[i]->isInfinite())
phrase_done = mSpell_elapsed + mPhrases[i]->calcAfterLife();
2017-07-26 08:35:44 +00:00
else
phrase_done = mPhrases[i]->calcDoneTime();
2017-07-26 08:35:44 +00:00
if (phrase_done > done_time)
done_time = phrase_done;
}
}
F32 time_left = done_time - mSpell_elapsed;
2017-07-26 08:35:44 +00:00
if (time_left < 0)
time_left = 0;
Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + time_left*1000 + 500);
}
// CALL SCRIPT afxMagicSpellData::onDeactivate(%spell)
mDatablock->onDeactivate_callback(this);
2017-07-26 08:35:44 +00:00
}
void afxMagicSpell::enter_casting_state_s()
{
// note - onActivate() is called in cast_spell() instead of here to make sure any
// new time-factor settings resolve before they are sent off to the clients.
// stamp constraint-mgr starting time and reset spell timer
constraint_mgr->setStartTime(Platform::getVirtualMilliseconds());
mSpell_elapsed = 0;
2017-07-26 08:35:44 +00:00
setup_dynamic_constraints();
// start casting effects
setup_casting_fx();
if (mPhrases[CASTING_PHRASE])
mPhrases[CASTING_PHRASE]->start(mSpell_elapsed, mSpell_elapsed);
2017-07-26 08:35:44 +00:00
// initialize missile
if (mMissile_db)
2017-07-26 08:35:44 +00:00
{
mMissile_db = mMissile_db->cloneAndPerformSubstitutions(this, ranking);
init_missile_s(mMissile_db);
2017-07-26 08:35:44 +00:00
}
}
void afxMagicSpell::leave_casting_state_s()
{
if (mPhrases[CASTING_PHRASE])
2017-07-26 08:35:44 +00:00
{
if (mMarks_mask & MARK_INTERRUPT_CASTING)
2017-07-26 08:35:44 +00:00
{
//Con::printf("INTERRUPT CASTING (S)");
mPhrases[CASTING_PHRASE]->interrupt(mSpell_elapsed);
2017-07-26 08:35:44 +00:00
}
else
{
//Con::printf("LEAVING CASTING (S)");
mPhrases[CASTING_PHRASE]->stop(mSpell_elapsed);
2017-07-26 08:35:44 +00:00
}
}
if (mMarks_mask & MARK_INTERRUPT_CASTING)
2017-07-26 08:35:44 +00:00
{
// CALL SCRIPT afxMagicSpellData::onInterrupt(%spell, %caster)
mDatablock->onInterrupt_callback(this, mCaster);
2017-07-26 08:35:44 +00:00
}
}
void afxMagicSpell::enter_delivery_state_s()
{
// CALL SCRIPT afxMagicSpellData::onLaunch(%spell, %caster, %target, %missile)
mDatablock->onLaunch_callback(this, mCaster, mTarget, mMissile);
2017-07-26 08:35:44 +00:00
if (mDatablock->mLaunch_on_server_signal)
2017-07-26 08:35:44 +00:00
postSpellEvent(LAUNCH_EVENT);
mMissile_is_armed = true;
2017-07-26 08:35:44 +00:00
// start launch effects
setup_launch_fx();
if (mPhrases[LAUNCH_PHRASE])
mPhrases[LAUNCH_PHRASE]->start(mSpell_elapsed, mSpell_elapsed); //START
2017-07-26 08:35:44 +00:00
// start delivery effects
setup_delivery_fx();
if (mPhrases[DELIVERY_PHRASE])
mPhrases[DELIVERY_PHRASE]->start(mSpell_elapsed, mSpell_elapsed); //START
2017-07-26 08:35:44 +00:00
}
void afxMagicSpell::leave_delivery_state_s()
{
if (mPhrases[DELIVERY_PHRASE])
2017-07-26 08:35:44 +00:00
{
if (mMarks_mask & MARK_INTERRUPT_DELIVERY)
2017-07-26 08:35:44 +00:00
{
//Con::printf("INTERRUPT DELIVERY (S)");
mPhrases[DELIVERY_PHRASE]->interrupt(mSpell_elapsed);
2017-07-26 08:35:44 +00:00
}
else
{
//Con::printf("LEAVING DELIVERY (S)");
mPhrases[DELIVERY_PHRASE]->stop(mSpell_elapsed);
2017-07-26 08:35:44 +00:00
}
}
if (!mMissile && !(mMarks_mask & MARK_IMPACT))
2017-07-26 08:35:44 +00:00
{
if (mTarget)
2017-07-26 08:35:44 +00:00
{
Point3F p = afxMagicSpell::getShapeImpactPos(mTarget);
2017-07-26 08:35:44 +00:00
Point3F n = Point3F(0,0,1);
impactNotify(p, n, mTarget);
2017-07-26 08:35:44 +00:00
}
else
{
Point3F p = Point3F(0,0,0);
Point3F n = Point3F(0,0,1);
impactNotify(p, n, 0);
}
}
}
void afxMagicSpell::enter_linger_state_s()
{
if (mImpacted_obj)
2017-07-26 08:35:44 +00:00
{
mImpacted_cons_id = constraint_mgr->setReferenceObject(IMPACTED_OBJECT_CONS, mImpacted_obj);
2017-07-26 08:35:44 +00:00
#if defined(AFX_CAP_SCOPE_TRACKING)
if (mImpacted_obj->isScopeable())
constraint_mgr->addScopeableObject(mImpacted_obj);
2017-07-26 08:35:44 +00:00
#endif
}
else
constraint_mgr->setReferencePoint(IMPACTED_OBJECT_CONS, mImpact_pos, mImpact_norm);
constraint_mgr->setReferencePoint(IMPACT_POINT_CONS, mImpact_pos, mImpact_norm);
2017-07-26 08:35:44 +00:00
constraint_mgr->setReferenceObject(MISSILE_CONS, 0);
// start impact effects
setup_impact_fx();
if (mPhrases[IMPACT_PHRASE])
mPhrases[IMPACT_PHRASE]->start(mSpell_elapsed, mSpell_elapsed); //START
2017-07-26 08:35:44 +00:00
// start linger effects
setup_linger_fx();
if (mPhrases[LINGER_PHRASE])
mPhrases[LINGER_PHRASE]->start(mSpell_elapsed, mSpell_elapsed); //START
2017-07-26 08:35:44 +00:00
#if 0 // code temporarily replaced with old callback technique in order to avoid engine bug.
// CALL SCRIPT afxMagicSpellData::onImpact(%spell, %caster, %impactedObj, %impactedPos, %impactedNorm)
mDatablock->onImpact_callback(this, mCaster, mImpacted_obj, mImpact_pos, mImpact_norm);
2017-07-26 08:35:44 +00:00
#else
char pos_buf[128];
dSprintf(pos_buf, sizeof(pos_buf), "%g %g %g", mImpact_pos.x, mImpact_pos.y, mImpact_pos.z);
2017-07-26 08:35:44 +00:00
char norm_buf[128];
dSprintf(norm_buf, sizeof(norm_buf), "%g %g %g", mImpact_norm.x, mImpact_norm.y, mImpact_norm.z);
Con::executef(mExeblock, "onImpact", getIdString(),
(mCaster) ? mCaster->getIdString(): "",
(mImpacted_obj) ? mImpacted_obj->getIdString(): "",
2017-07-26 08:35:44 +00:00
pos_buf, norm_buf);
#endif
}
void afxMagicSpell::leave_linger_state_s()
{
if (mPhrases[LINGER_PHRASE])
2017-07-26 08:35:44 +00:00
{
if (mMarks_mask & MARK_INTERRUPT_LINGER)
2017-07-26 08:35:44 +00:00
{
//Con::printf("INTERRUPT LINGER (S)");
mPhrases[LINGER_PHRASE]->interrupt(mSpell_elapsed);
2017-07-26 08:35:44 +00:00
}
else
{
//Con::printf("LEAVING LINGER (S)");
mPhrases[LINGER_PHRASE]->stop(mSpell_elapsed);
2017-07-26 08:35:44 +00:00
}
}
}
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// private
void afxMagicSpell::process_client(F32 dt)
{
mSpell_elapsed += dt; //SPELL_ELAPSED
2017-07-26 08:35:44 +00:00
U8 pending_state = mSpell_state;
2017-07-26 08:35:44 +00:00
// check for state changes
switch (mSpell_state)
2017-07-26 08:35:44 +00:00
{
case INACTIVE_STATE:
if (mMarks_mask & MARK_ACTIVATE)
2017-07-26 08:35:44 +00:00
pending_state = CASTING_STATE;
break;
case CASTING_STATE:
if (mMarks_mask & MARK_INTERRUPT_CASTING)
2017-07-26 08:35:44 +00:00
pending_state = CLEANUP_STATE;
else if (mMarks_mask & MARK_END_CASTING)
2017-07-26 08:35:44 +00:00
pending_state = DELIVERY_STATE;
else if (mDatablock->mLaunch_on_server_signal)
2017-07-26 08:35:44 +00:00
{
if (mMarks_mask & MARK_LAUNCH)
2017-07-26 08:35:44 +00:00
pending_state = DELIVERY_STATE;
}
else
{
if (state_expired())
pending_state = DELIVERY_STATE;
}
break;
case DELIVERY_STATE:
if (mMarks_mask & MARK_INTERRUPT_DELIVERY)
2017-07-26 08:35:44 +00:00
pending_state = CLEANUP_STATE;
else if (mMarks_mask & MARK_END_DELIVERY)
2017-07-26 08:35:44 +00:00
pending_state = LINGER_STATE;
else if (mMarks_mask & MARK_IMPACT)
2017-07-26 08:35:44 +00:00
pending_state = LINGER_STATE;
else
state_expired();
break;
case LINGER_STATE:
if (mMarks_mask & MARK_INTERRUPT_LINGER)
2017-07-26 08:35:44 +00:00
pending_state = CLEANUP_STATE;
else if (mMarks_mask & MARK_END_LINGER)
2017-07-26 08:35:44 +00:00
pending_state = CLEANUP_STATE;
else if (mMarks_mask & MARK_SHUTDOWN)
2017-07-26 08:35:44 +00:00
pending_state = CLEANUP_STATE;
else if (state_expired())
pending_state = CLEANUP_STATE;
break;
case CLEANUP_STATE:
if ((mMarks_mask & MARK_INTERRUPT_CLEANUP) || cleanup_over())
2017-07-26 08:35:44 +00:00
pending_state = DONE_STATE;
break;
}
if (mSpell_state != pending_state)
2017-07-26 08:35:44 +00:00
change_state_c(pending_state);
if (mSpell_state == INACTIVE_STATE)
2017-07-26 08:35:44 +00:00
return;
//--------------------------//
// update the listener constraint position
if (!mListener_cons_id.undefined())
2017-07-26 08:35:44 +00:00
{
Point3F listener_pos;
listener_pos = SFX->getListener().getTransform().getPosition();
constraint_mgr->setReferencePoint(mListener_cons_id, listener_pos);
2017-07-26 08:35:44 +00:00
}
// find local camera position
Point3F cam_pos;
SceneObject* current_cam = get_camera(&cam_pos);
// detect camera changes
if (!mCamera_cons_id.undefined() && current_cam != mCamera_cons_obj)
2017-07-26 08:35:44 +00:00
{
constraint_mgr->setReferenceObject(mCamera_cons_id, current_cam);
mCamera_cons_obj = current_cam;
2017-07-26 08:35:44 +00:00
}
// sample the constraints
constraint_mgr->sample(dt, Platform::getVirtualMilliseconds(), (current_cam) ? &cam_pos : 0);
// update active effects lists
for (S32 i = 0; i < NUM_PHRASES; i++)
if (mPhrases[i])
mPhrases[i]->update(dt, mSpell_elapsed);
2017-07-26 08:35:44 +00:00
if (mMissile_is_armed)
2017-07-26 08:35:44 +00:00
{
launch_missile_c();
mMissile_is_armed = false;
2017-07-26 08:35:44 +00:00
}
}
void afxMagicSpell::change_state_c(U8 pending_state)
{
if (mSpell_state == pending_state)
2017-07-26 08:35:44 +00:00
return;
// LEAVING THIS STATE
switch (mSpell_state)
2017-07-26 08:35:44 +00:00
{
case INACTIVE_STATE:
break;
case CASTING_STATE:
leave_casting_state_c();
break;
case DELIVERY_STATE:
leave_delivery_state_c();
break;
case LINGER_STATE:
leave_linger_state_c();
break;
case CLEANUP_STATE:
break;
case DONE_STATE:
break;
}
mSpell_state = pending_state;
2017-07-26 08:35:44 +00:00
// ENTERING THIS STATE
switch (pending_state)
{
case INACTIVE_STATE:
break;
case CASTING_STATE:
enter_casting_state_c(mSpell_elapsed);
2017-07-26 08:35:44 +00:00
break;
case DELIVERY_STATE:
enter_delivery_state_c(mSpell_elapsed);
2017-07-26 08:35:44 +00:00
break;
case LINGER_STATE:
enter_linger_state_c(mSpell_elapsed);
2017-07-26 08:35:44 +00:00
break;
case CLEANUP_STATE:
break;
case DONE_STATE:
break;
}
}
void afxMagicSpell::enter_casting_state_c(F32 starttime)
{
// stamp constraint-mgr starting time
constraint_mgr->setStartTime(Platform::getVirtualMilliseconds() - (U32)(mSpell_elapsed *1000));
2017-07-26 08:35:44 +00:00
//spell_elapsed = 0; //SPELL_ELAPSED
setup_dynamic_constraints();
// start casting effects and castbar
setup_casting_fx();
if (mPhrases[CASTING_PHRASE])
mPhrases[CASTING_PHRASE]->start(starttime, mSpell_elapsed); //START
2017-07-26 08:35:44 +00:00
// initialize missile
if (mMissile_db)
2017-07-26 08:35:44 +00:00
{
mMissile_db = mMissile_db->cloneAndPerformSubstitutions(this, ranking);
init_missile_c(mMissile_db);
2017-07-26 08:35:44 +00:00
}
}
void afxMagicSpell::leave_casting_state_c()
{
if (mPhrases[CASTING_PHRASE])
2017-07-26 08:35:44 +00:00
{
if (mMarks_mask & MARK_INTERRUPT_CASTING)
2017-07-26 08:35:44 +00:00
{
//Con::printf("INTERRUPT CASTING (C)");
mPhrases[CASTING_PHRASE]->interrupt(mSpell_elapsed);
2017-07-26 08:35:44 +00:00
}
else
{
//Con::printf("LEAVING CASTING (C)");
mPhrases[CASTING_PHRASE]->stop(mSpell_elapsed);
2017-07-26 08:35:44 +00:00
}
}
}
void afxMagicSpell::enter_delivery_state_c(F32 starttime)
{
mMissile_is_armed = true;
2017-07-26 08:35:44 +00:00
setup_launch_fx();
if (mPhrases[LAUNCH_PHRASE])
mPhrases[LAUNCH_PHRASE]->start(starttime, mSpell_elapsed); //START
2017-07-26 08:35:44 +00:00
setup_delivery_fx();
if (mPhrases[DELIVERY_PHRASE])
mPhrases[DELIVERY_PHRASE]->start(starttime, mSpell_elapsed); //START
2017-07-26 08:35:44 +00:00
}
void afxMagicSpell::leave_delivery_state_c()
{
if (mMissile)
2017-07-26 08:35:44 +00:00
{
clearNotify(mMissile);
mMissile->deleteObject();
mMissile = NULL;
2017-07-26 08:35:44 +00:00
}
if (mPhrases[DELIVERY_PHRASE])
2017-07-26 08:35:44 +00:00
{
if (mMarks_mask & MARK_INTERRUPT_DELIVERY)
2017-07-26 08:35:44 +00:00
{
//Con::printf("INTERRUPT DELIVERY (C)");
mPhrases[DELIVERY_PHRASE]->interrupt(mSpell_elapsed);
2017-07-26 08:35:44 +00:00
}
else
{
//Con::printf("LEAVING DELIVERY (C)");
mPhrases[DELIVERY_PHRASE]->stop(mSpell_elapsed);
2017-07-26 08:35:44 +00:00
}
}
}
void afxMagicSpell::enter_linger_state_c(F32 starttime)
{
if (mImpacted_obj)
mImpacted_cons_id = constraint_mgr->setReferenceObject(IMPACTED_OBJECT_CONS, mImpacted_obj);
else if (mImpacted_scope_id > 0)
mImpacted_cons_id = constraint_mgr->setReferenceObjectByScopeId(IMPACTED_OBJECT_CONS, mImpacted_scope_id, mImpacted_is_shape);
2017-07-26 08:35:44 +00:00
else
constraint_mgr->setReferencePoint(IMPACTED_OBJECT_CONS, mImpact_pos, mImpact_norm);
constraint_mgr->setReferencePoint(IMPACT_POINT_CONS, mImpact_pos, mImpact_norm);
2017-07-26 08:35:44 +00:00
constraint_mgr->setReferenceObject(MISSILE_CONS, 0);
setup_impact_fx();
if (mPhrases[IMPACT_PHRASE])
mPhrases[IMPACT_PHRASE]->start(starttime, mSpell_elapsed); //START
2017-07-26 08:35:44 +00:00
setup_linger_fx();
if (mPhrases[LINGER_PHRASE])
2017-07-26 08:35:44 +00:00
{
mPhrases[LINGER_PHRASE]->start(starttime, mSpell_elapsed); //START
2017-07-26 08:35:44 +00:00
}
}
void afxMagicSpell::leave_linger_state_c()
{
if (mPhrases[LINGER_PHRASE])
2017-07-26 08:35:44 +00:00
{
if (mMarks_mask & MARK_INTERRUPT_LINGER)
2017-07-26 08:35:44 +00:00
{
//Con::printf("INTERRUPT LINGER (C)");
mPhrases[LINGER_PHRASE]->interrupt(mSpell_elapsed);
2017-07-26 08:35:44 +00:00
}
else
{
//Con::printf("LEAVING LINGER (C)");
mPhrases[LINGER_PHRASE]->stop(mSpell_elapsed);
2017-07-26 08:35:44 +00:00
}
}
}
void afxMagicSpell::sync_client(U16 marks, U8 state, F32 elapsed, F32 spell_elapsed)
{
//Con::printf("SYNC marks=%d old_state=%s state=%s elapsed=%g spell_elapsed=%g",
// marks, name_from_state(spell_state), name_from_state(state), elapsed,
// spell_elapsed);
if (mSpell_state != LATE_STATE)
2017-07-26 08:35:44 +00:00
return;
mMarks_mask = marks;
2017-07-26 08:35:44 +00:00
// don't want to be started on late zoning clients
if (!mDatablock->exec_on_new_clients)
2017-07-26 08:35:44 +00:00
{
mSpell_state = DONE_STATE;
2017-07-26 08:35:44 +00:00
}
// it looks like we're ghosting pretty late and
// should just return to the inactive state.
else if ((marks & (MARK_INTERRUPTS | MARK_DEACTIVATE | MARK_SHUTDOWN)) ||
(((marks & MARK_IMPACT) || (marks & MARK_END_DELIVERY)) && (marks & MARK_END_LINGER)))
{
mSpell_state = DONE_STATE;
2017-07-26 08:35:44 +00:00
}
// it looks like we should be in the linger state.
else if ((marks & MARK_IMPACT) ||
(((marks & MARK_LAUNCH) || (marks & MARK_END_CASTING)) && (marks & MARK_END_DELIVERY)))
{
mSpell_state = LINGER_STATE;
mSpell_elapsed = spell_elapsed;
2017-07-26 08:35:44 +00:00
enter_linger_state_c(spell_elapsed-elapsed);
}
// it looks like we should be in the delivery state.
else if ((marks & MARK_LAUNCH) || (marks & MARK_END_CASTING))
{
mSpell_state = DELIVERY_STATE;
mSpell_elapsed = spell_elapsed;
2017-07-26 08:35:44 +00:00
enter_delivery_state_c(spell_elapsed-elapsed);
}
// it looks like we should be in the casting state.
else if (marks & MARK_ACTIVATE)
{
mSpell_state = CASTING_STATE; //SPELL_STATE
mSpell_elapsed = spell_elapsed;
2017-07-26 08:35:44 +00:00
enter_casting_state_c(spell_elapsed-elapsed);
}
}
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// public:
void afxMagicSpell::postSpellEvent(U8 event)
{
setMaskBits(StateEventMask);
switch (event)
{
case ACTIVATE_EVENT:
mMarks_mask |= MARK_ACTIVATE;
2017-07-26 08:35:44 +00:00
break;
case LAUNCH_EVENT:
mMarks_mask |= MARK_LAUNCH;
2017-07-26 08:35:44 +00:00
setMaskBits(LaunchEventMask);
break;
case IMPACT_EVENT:
mMarks_mask |= MARK_IMPACT;
2017-07-26 08:35:44 +00:00
setMaskBits(ImpactEventMask);
break;
case SHUTDOWN_EVENT:
mMarks_mask |= MARK_SHUTDOWN;
2017-07-26 08:35:44 +00:00
break;
case DEACTIVATE_EVENT:
mMarks_mask |= MARK_DEACTIVATE;
2017-07-26 08:35:44 +00:00
break;
case INTERRUPT_PHASE_EVENT:
if (mSpell_state == CASTING_STATE)
mMarks_mask |= MARK_END_CASTING;
else if (mSpell_state == DELIVERY_STATE)
mMarks_mask |= MARK_END_DELIVERY;
else if (mSpell_state == LINGER_STATE)
mMarks_mask |= MARK_END_LINGER;
2017-07-26 08:35:44 +00:00
break;
case INTERRUPT_SPELL_EVENT:
if (mSpell_state == CASTING_STATE)
mMarks_mask |= MARK_INTERRUPT_CASTING;
else if (mSpell_state == DELIVERY_STATE)
mMarks_mask |= MARK_INTERRUPT_DELIVERY;
else if (mSpell_state == LINGER_STATE)
mMarks_mask |= MARK_INTERRUPT_LINGER;
else if (mSpell_state == CLEANUP_STATE)
mMarks_mask |= MARK_INTERRUPT_CLEANUP;
2017-07-26 08:35:44 +00:00
break;
}
}
void afxMagicSpell::resolveTimeFactors()
{
for (S32 i = 0; i < NUM_PHRASES; i++)
mTfactors[i] *= mOverall_time_factor;
2017-07-26 08:35:44 +00:00
}
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
void afxMagicSpell::finish_startup()
{
#if !defined(BROKEN_POINT_IN_WATER)
// test if caster is in water
if (mCaster)
2017-07-26 08:35:44 +00:00
{
Point3F pos = mCaster->getPosition();
if (mCaster->pointInWater(pos))
2017-07-26 08:35:44 +00:00
exec_conds_mask |= CASTER_IN_WATER;
}
#endif
resolveTimeFactors();
init_constraints();
init_scoping();
postSpellEvent(afxMagicSpell::ACTIVATE_EVENT);
}
// static
afxMagicSpell*
afxMagicSpell::cast_spell(afxMagicSpellData* datablock, ShapeBase* caster, SceneObject* target, SimObject* extra)
{
AssertFatal(datablock != NULL, "Datablock is missing.");
AssertFatal(caster != NULL, "Caster is missing.");
afxMagicSpellData* exeblock = datablock;
SimObject* param_holder = new SimObject();
if (!param_holder->registerObject())
{
Con::errorf("afxMagicSpell: failed to register parameter object.");
delete param_holder;
return 0;
}
param_holder->assignDynamicFieldsFrom(datablock, arcaneFX::sParameterFieldPrefix);
if (extra)
{
// copy dynamic fields from the extra object to the param holder
param_holder->assignDynamicFieldsFrom(extra, arcaneFX::sParameterFieldPrefix);
}
if (datablock->isMethod("onPreactivate"))
{
// CALL SCRIPT afxMagicSpellData::onPreactivate(%params, %caster, %target, %extra)
bool result = datablock->onPreactivate_callback(param_holder, caster, target, extra);
if (!result)
{
#if defined(TORQUE_DEBUG)
Con::warnf("afxMagicSpell: onPreactivate() returned false, spell aborted.");
#endif
Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime());
return 0;
}
}
// make a temp datablock clone if there are substitutions
if (datablock->getSubstitutionCount() > 0)
{
datablock = new afxMagicSpellData(*exeblock, true);
exeblock->performSubstitutions(datablock, param_holder);
}
// create a new spell instance
afxMagicSpell* spell = new afxMagicSpell(caster, target);
spell->setDataBlock(datablock);
spell->mExeblock = exeblock;
2017-07-26 08:35:44 +00:00
spell->setExtra(extra);
// copy dynamic fields from the param holder to the spell
spell->assignDynamicFieldsFrom(param_holder, arcaneFX::sParameterFieldPrefix);
Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime());
// register
if (!spell->registerObject())
{
Con::errorf("afxMagicSpell: failed to register spell instance.");
Sim::postEvent(spell, new ObjectDeleteEvent, Sim::getCurrentTime());
return 0;
}
registerForCleanup(spell);
spell->activate();
return spell;
}
IMPLEMENT_GLOBAL_CALLBACK( DisplayScreenMessage, void, (GameConnection* client, const char* message), (client, message),
"Called to display a screen message.\n"
"@ingroup AFX\n" );
void afxMagicSpell::displayScreenMessage(ShapeBase* caster, const char* msg)
{
if (!caster)
return;
GameConnection* client = caster->getControllingClient();
if (client)
DisplayScreenMessage_callback(client, msg);
}
Point3F afxMagicSpell::getShapeImpactPos(SceneObject* obj)
{
Point3F pos = obj->getRenderPosition();
if (obj->getTypeMask() & CorpseObjectType)
pos.z += 0.5f;
else
pos.z += (obj->getObjBox().len_z()/2);
return pos;
}
void afxMagicSpell::restoreObject(SceneObject* obj)
{
if (obj->getScopeId() == mCaster_scope_id && dynamic_cast<ShapeBase*>(obj) != NULL)
2017-07-26 08:35:44 +00:00
{
mCaster_scope_id = 0;
mCaster = (ShapeBase*)obj;
mCaster_field = mCaster;
deleteNotify(mCaster);
processAfter(mCaster);
2017-07-26 08:35:44 +00:00
}
if (obj->getScopeId() == mTarget_scope_id)
2017-07-26 08:35:44 +00:00
{
mTarget_scope_id = 0;
mTarget = obj;
mTarget_field = mTarget;
deleteNotify(mTarget);
2017-07-26 08:35:44 +00:00
}
if (obj->getScopeId() == mImpacted_scope_id)
2017-07-26 08:35:44 +00:00
{
mImpacted_scope_id = 0;
mImpacted_obj = obj;
deleteNotify(mImpacted_obj);
2017-07-26 08:35:44 +00:00
}
}
bool afxMagicSpell::activationCallInit(bool postponed)
{
if (postponed && (!started_with_newop || !postpone_activation))
{
Con::errorf("afxMagicSpell::activate() -- activate() is only required when creating a spell with the \"new\" operator "
"and the postponeActivation field is set to \"true\".");
return false;
}
if (!mCaster_field)
2017-07-26 08:35:44 +00:00
{
Con::errorf("afxMagicSpell::activate() -- no spellcaster specified.");
return false;
}
mCaster = dynamic_cast<ShapeBase*>(mCaster_field);
if (!mCaster)
2017-07-26 08:35:44 +00:00
{
Con::errorf("afxMagicSpell::activate() -- spellcaster is not a ShapeBase derived object.");
return false;
}
if (mTarget_field)
2017-07-26 08:35:44 +00:00
{
mTarget = dynamic_cast<SceneObject*>(mTarget_field);
if (!mTarget)
2017-07-26 08:35:44 +00:00
Con::warnf("afxMagicSpell::activate() -- target is not a SceneObject derived object.");
}
return true;
}
void afxMagicSpell::activate()
{
// separating the final part of startup allows the calling script
// to make certain types of calls on the returned spell that need
// to happen prior to object registration.
Sim::postEvent(this, new SpellFinishStartupEvent, Sim::getCurrentTime());
mCaster_field = mCaster;
mTarget_field = mTarget;
2017-07-26 08:35:44 +00:00
// CALL SCRIPT afxMagicSpellData::onActivate(%spell, %caster, %target)
mDatablock->onActivate_callback(this, mCaster, mTarget);
2017-07-26 08:35:44 +00:00
}
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// console methods/functions
DefineEngineMethod(afxMagicSpell, getCaster, S32, (),,
"Returns ID of the spell's caster object.\n\n"
"@ingroup AFX")
{
ShapeBase* caster = object->getCaster();
return (caster) ? caster->getId() : -1;
}
DefineEngineMethod(afxMagicSpell, getTarget, S32, (),,
"Returns ID of the spell's target object.\n\n"
"@ingroup AFX")
{
SceneObject* target = object->getTarget();
return (target) ? target->getId() : -1;
}
DefineEngineMethod(afxMagicSpell, getMissile, S32, (),,
"Returns ID of the spell's magic-missile object.\n\n"
"@ingroup AFX")
{
afxMagicMissile* missile = object->getMissile();
return (missile) ? missile->getId() : -1;
}
DefineEngineMethod(afxMagicSpell, getImpactedObject, S32, (),,
"Returns ID of impacted-object for the spell.\n\n"
"@ingroup AFX")
{
SceneObject* imp_obj = object->getImpactedObject();
return (imp_obj) ? imp_obj->getId() : -1;
}
DefineEngineStringlyVariadicMethod(afxMagicSpell, setTimeFactor, void, 3, 4, "(F32 factor) or (string phase, F32 factor)"
"Sets the time-factor for the spell, either overall or for a specific phrase.\n\n"
"@ingroup AFX")
2017-07-26 08:35:44 +00:00
{
if (argc == 3)
2021-04-01 02:10:55 +00:00
object->setTimeFactor(argv[2].getFloat());
else
{
2021-04-01 02:10:55 +00:00
F32 value = argv[3].getFloat();
if (dStricmp(argv[2], "overall") == 0)
object->setTimeFactor(dAtof(argv[3]));
else if (dStricmp(argv[2], "casting") == 0)
2021-04-01 02:10:55 +00:00
object->setTimeFactor(afxMagicSpell::CASTING_PHRASE, value);
else if (dStricmp(argv[2], "launch") == 0)
2021-04-01 02:10:55 +00:00
object->setTimeFactor(afxMagicSpell::LAUNCH_PHRASE, value);
else if (dStricmp(argv[2], "delivery") == 0)
2021-04-01 02:10:55 +00:00
object->setTimeFactor(afxMagicSpell::DELIVERY_PHRASE, value);
else if (dStricmp(argv[2], "impact") == 0)
2021-04-01 02:10:55 +00:00
object->setTimeFactor(afxMagicSpell::IMPACT_PHRASE, value);
else if (dStricmp(argv[2], "linger") == 0)
2021-04-01 02:10:55 +00:00
object->setTimeFactor(afxMagicSpell::LINGER_PHRASE, value);
else
2021-04-01 02:10:55 +00:00
Con::errorf("afxMagicSpell::setTimeFactor() -- unknown spell phrase [%s].", argv[2].getString());
}
2017-07-26 08:35:44 +00:00
}
DefineEngineMethod(afxMagicSpell, interruptStage, void, (),,
"Interrupts the current stage of a magic spell causing it to move onto the next one.\n\n"
"@ingroup AFX")
{
object->postSpellEvent(afxMagicSpell::INTERRUPT_PHASE_EVENT);
}
DefineEngineMethod(afxMagicSpell, interrupt, void, (),,
"Interrupts and deletes a running magic spell.\n\n"
"@ingroup AFX")
{
object->postSpellEvent(afxMagicSpell::INTERRUPT_SPELL_EVENT);
}
DefineEngineMethod(afxMagicSpell, activate, void, (),,
"Activates a magic spell that was started with postponeActivation=true.\n\n"
"@ingroup AFX")
{
if (object->activationCallInit(true))
object->activate();
}
DefineEngineFunction(castSpell, S32, (afxMagicSpellData* datablock, ShapeBase* caster, SceneObject* target, SimObject* extra),
(nullAsType<afxMagicSpellData*>(), nullAsType<ShapeBase*>(), nullAsType<SceneObject*>(), nullAsType<SimObject*>()),
"Instantiates the magic spell defined by datablock and cast by caster.\n\n"
"@ingroup AFX")
{
if (!datablock)
{
Con::errorf("castSpell() -- missing valid spell datablock.");
return 0;
}
if (!caster)
{
Con::errorf("castSpell() -- missing valid spellcaster.");
return 0;
}
// target is optional (depends on spell)
// note -- we must examine all arguments prior to calling cast_spell because
// it calls Con::executef() which will overwrite the argument array.
afxMagicSpell* spell = afxMagicSpell::cast_spell(datablock, caster, target, extra);
return (spell) ? spell->getId() : 0;
}
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//