uox3/source/effect.cpp
charles e2cfe011c6 Stream replacement
This changes all routine calls that wanted a specific type of stream ifstream/ofstream to the generic istream/ostream.  So you can use stringstream, or other derived types.
2023-08-27 21:42:47 -04:00

1991 lines
62 KiB
C++

#include "uox3.h"
#include "skills.h"
#include "magic.h"
#include "cMagic.h"
#include "CJSMapping.h"
#include "mapstuff.h"
#include "cScript.h"
#include "cEffects.h"
#include "teffect.h"
#include "CPacketSend.h"
#include "classes.h"
#include "regions.h"
#include "combat.h"
#include "CJSMapping.h"
#include "townregion.h"
#include "StringUtility.hpp"
#include <algorithm>
//o------------------------------------------------------------------------------------------------o
//| Function - cEffects::DeathAction()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Plays a characters death animation and sends it to nearby players
//o------------------------------------------------------------------------------------------------o
void cEffects::DeathAction( CChar *s, CItem *x, UI08 fallDirection )
{
CPDeathAction toSend(( *s ), ( *x ));
toSend.FallDirection( fallDirection );
for( auto &mSock : FindNearbyPlayers( s ))
{
if( mSock->CurrcharObj() != s ) // Death action screws up corpse display for the person who died.
{
mSock->Send( &toSend );
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - cEffects::SpawnBloodEffect()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Spawns a blood effect item and returns the newly created object to caller
//o------------------------------------------------------------------------------------------------o
CItem * cEffects::SpawnBloodEffect( UI08 worldNum, UI16 instanceId, UI16 bloodColour, BloodTypes bloodType )
{
// Use default blood decay timer from ini setting
R32 bloodDecayTimer = static_cast<R32>( cwmWorldState->ServerData()->SystemTimer( tSERVER_BLOODDECAY ));
// Blood effects, sorted by size of effect from small to large
std::vector<UI16> bloodIds{ 0x1645, 0x122C, 0x122E, 0x122B, 0x122D, 0x122A, 0x122F };
UI16 bloodEffectId = 0x122c;
switch( bloodType )
{
case BLOOD_DEATH:
// Use corpse-specific blood decay timer instead of default one
bloodDecayTimer = static_cast<R32>( cwmWorldState->ServerData()->SystemTimer( tSERVER_BLOODDECAYCORPSE ));
// Randomize between large blood effects
bloodEffectId = bloodIds[RandomNum( static_cast<UI16>( 3 ), static_cast<UI16>( 6 ))];
break;
case BLOOD_BLEED:
// Randomize between small blood effects
bloodEffectId = bloodIds[RandomNum( static_cast<UI16>( 0 ), static_cast<UI16>( 3 ))];
break;
case BLOOD_CRITICAL:
// Randomize between medium to large blood effects
bloodEffectId = bloodIds[RandomNum( static_cast<UI16>( 2 ), static_cast<UI16>( 5 ))];
// More blood, so increase the time it takes to decay
bloodDecayTimer *= 1.5;
break;
default:
break;
}
// Spawn the blood effect item
CItem *blood = Items->CreateBaseItem( worldNum, OT_ITEM, instanceId, false );
if( ValidateObject( blood ))
{
blood->SetId( bloodEffectId );
blood->SetColour( bloodColour );
blood->SetDecayable( true );
blood->SetDecayTime( BuildTimeValue( bloodDecayTimer ));
return blood;
}
return nullptr;
}
//o------------------------------------------------------------------------------------------------o
//| Function - cEffects::PlayMovingAnimation( CBaseObject *source, CBaseObject *dest, UI16 effect,
//| UI08 speed, UI08 loop, bool explode, UI32 hue, UI32 renderMode )
//o------------------------------------------------------------------------------------------------o
//| Purpose - Sends a message to client to display a moving animation from source object to target object
//o------------------------------------------------------------------------------------------------o
void cEffects::PlayMovingAnimation( CBaseObject *source, CBaseObject *dest, UI16 effect, UI08 speed, UI08 loop, bool explode, UI32 hue, UI32 renderMode, bool playLocalMoveFX )
{ //0x0f 0x42 = arrow 0x1b 0xfe=bolt
if( !ValidateObject( source ) || !ValidateObject( dest ))
return;
// TODO - if enhanced client/3d client, send packet 0xC7 (CP3DGraphicalEffect) instead
// CP3DGraphicalEffect toSend( 0, ( *source ), ( *dest ));
CPGraphicalEffect2 toSend( 0, ( *source ), ( *dest ));
toSend.Model( effect );
toSend.SourceLocation(( *source ));
toSend.TargetLocation(( *dest ));
toSend.Z( dest->GetZ() + 14 );
toSend.ZTrg( dest->GetZ() + 11 );
toSend.Speed( speed );
toSend.Duration( loop );
toSend.ExplodeOnImpact( explode );
toSend.Hue( hue );
toSend.RenderMode( renderMode ); // 0x00000004
// Edge case, in case someone want to play the moving FX locally on source object
if( source == dest && playLocalMoveFX )
{
toSend.Effect( 0x03 );
if( loop == 0 )
{
// Make sure effect has a fixed duration, or it will stick around for ever!
toSend.Duration( 0x05 );
}
toSend.SourceSerial( 0x0000 );
toSend.TargetSerial( 0x0000 );
toSend.TargetLocation( 0, 0, 0 );
}
// TODO: Used with 3D graphical effect only
/*toSend.EffectId3D( 0x0000 );
toSend.ExplodeEffectId( 0x0001 );
toSend.MovingEffectId( 0xFFFF );
toSend.TargetObjSerial( dest->GetSerial() );
toSend.LayerId( 0x01 );
toSend.Unknown( 0x0000 );*/
for( auto &tSock : Network->connClients )
{
if( ObjInRange( tSock, source, DIST_SAMESCREEN ) && ObjInRange( tSock, dest, DIST_SAMESCREEN ))
{
tSock->Send( &toSend );
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - cEffects::PlayMovingAnimation( CBaseObject *source, SI16 x, SI16 y, SI08 z, UI16 effect,
//| UI08 speed, UI08 loop, bool explode, UI32 hue, UI32 renderMode )
//o------------------------------------------------------------------------------------------------o
//| Purpose - Sends a message to client to display a moving animation from source object to target location
//o------------------------------------------------------------------------------------------------o
void cEffects::PlayMovingAnimation( CBaseObject *source, SI16 x, SI16 y, SI08 z, UI16 effect, UI08 speed, UI08 loop, bool explode, UI32 hue, UI32 renderMode )
{ //0x0f 0x42 = arrow 0x1b 0xfe=bolt
if( !ValidateObject( source ))
return;
CPGraphicalEffect2 toSend( 0, ( *source ));
toSend.TargetSerial( INVALIDSERIAL );
toSend.Model( effect );
toSend.SourceLocation(( *source ));
toSend.TargetLocation( x, y, z );
toSend.Speed( speed );
toSend.Duration( loop );
toSend.ExplodeOnImpact( explode );
toSend.Hue( hue );
toSend.RenderMode( renderMode );
for( auto &mSock : FindNearbyPlayers( source, DIST_SAMESCREEN ))
{
mSock->Send( &toSend );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - cEffects::PlayMovingAnimation( SI16 srcX, SI16 srcY, SI08 srcZ, SI16 x, SI16 y, SI08 z, UI16 effect,
//| UI08 speed, UI08 loop, bool explode, UI32 hue, UI32 renderMode )
//o------------------------------------------------------------------------------------------------o
//| Purpose - Sends a message to client to display a moving animation from source object to target location
//o------------------------------------------------------------------------------------------------o
void cEffects::PlayMovingAnimation( SI16 srcX, SI16 srcY, SI08 srcZ, SI16 x, SI16 y, SI08 z, UI16 effect, UI08 speed, UI08 loop, bool explode, UI32 hue, UI32 renderMode )
{ //0x0f 0x42 = arrow 0x1b 0xfe=bolt
CPGraphicalEffect2 toSend( 0 );
toSend.TargetSerial( INVALIDSERIAL );
toSend.Model( effect );
toSend.SourceLocation( srcX, srcY, srcZ );
toSend.TargetLocation( x, y, z );
toSend.Speed( speed );
toSend.Duration( loop );
toSend.ExplodeOnImpact( explode );
toSend.Hue( hue );
toSend.RenderMode( renderMode );
for( auto &mSock : FindNearbyPlayers( srcX, srcY, srcZ, DIST_SAMESCREEN ))
{
mSock->Send( &toSend );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - cEffects::PlayCharacterAnimation()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Sends message to client to make character perform specified action/anim
//o------------------------------------------------------------------------------------------------o
void cEffects::PlayCharacterAnimation( CChar *mChar, UI16 actionId, UI08 frameDelay, UI08 frameCount, bool playBackwards )
{
CPCharacterAnimation toSend = ( *mChar );
toSend.Action( actionId );
toSend.FrameDelay( frameDelay );
toSend.FrameCount( frameCount );
toSend.DoBackwards( playBackwards );
for( auto &mSock : FindNearbyPlayers( mChar ))
{
mSock->Send( &toSend );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - cEffects::PlayNewCharacterAnimation()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Sends message to client to make character perform specified action/anim
//| in client versions above 7.0.0.0
//o------------------------------------------------------------------------------------------------o
void cEffects::PlayNewCharacterAnimation( CChar *mChar, UI16 actionId, UI16 subActionId, UI08 subSubActionId )
{
CPNewCharacterAnimation toSend = ( *mChar );
toSend.Action( actionId );
toSend.SubAction( subActionId );
toSend.SubSubAction( subSubActionId );
for( auto &mSock : FindNearbyPlayers( mChar ))
{
mSock->Send( &toSend );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - cEffects::PlaySpellCastingAnimation()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Handles spellcasting action
//o------------------------------------------------------------------------------------------------o
void cEffects::PlaySpellCastingAnimation( CChar *mChar, UI16 actionId, bool monsterCast, bool monsterAreaCastAnim )
{
if( !monsterCast && ( mChar->GetBodyType() == BT_GARGOYLE || cwmWorldState->ServerData()->ForceNewAnimationPacket() ))
{
if( mChar->GetBodyType() == BT_GARGOYLE )
{
if( actionId == 0x10 )
{
PlayNewCharacterAnimation( mChar, N_ACT_SPELL, S_ACT_SPELL_AREA ); // Action: 0x0b, SubAction: spell variation 0x01
}
else if( actionId == 0x11 )
{
PlayNewCharacterAnimation( mChar, N_ACT_SPELL, S_ACT_SPELL_TARGET ); // Action: 0x0b, SubAction: spell variation 0x00
}
}
else
{
// Sub actions are in reverse order for humans, compared to gargoyles
if( actionId == 0x10 )
{
PlayNewCharacterAnimation( mChar, N_ACT_SPELL, S_ACT_SPELL_TARGET ); // Action: 0x0b, SubAction: spell variation 0x00
}
else if( actionId == 0x11 )
{
PlayNewCharacterAnimation( mChar, N_ACT_SPELL, S_ACT_SPELL_AREA ); // Action: 0x0b, SubAction: spell variation 0x01
}
}
return;
}
else if( mChar->IsOnHorse() || mChar->GetMounted() ) // Players and NPC are mounted differently...
{
if( actionId == 0x10 )
{
PlayCharacterAnimation( mChar, ACT_MOUNT_ATT_1H, 0, 5 ); // 0x1A
}
else if( actionId == 0x11 )
{
PlayCharacterAnimation( mChar, ACT_MOUNT_ATT_BOW, 0, 5 ); // 0x1B
}
return;
}
else
{
if( !monsterCast )
{
// Human old animation packet
PlayCharacterAnimation( mChar, actionId, 0, 7 );
}
else
{
// Non-human old animation packet
if( actionId == 0x22 )
return;
UI16 castAnim = 0;
UI08 castAnimLength = 0;
if( monsterAreaCastAnim )
{
castAnim = static_cast<UI16>( cwmWorldState->creatures[mChar->GetId()].CastAnimAreaId() );
castAnimLength = cwmWorldState->creatures[mChar->GetId()].CastAnimAreaLength();
}
else
{
castAnim = static_cast<UI16>( cwmWorldState->creatures[mChar->GetId()].CastAnimTargetId() );
castAnimLength = cwmWorldState->creatures[mChar->GetId()].CastAnimTargetLength();
}
// Play cast anim, but fallback to default attack anim (0x04) with anim length of 4 frames if no cast anim was defined in creatures.dfn
PlayCharacterAnimation( mChar, ( castAnim != 0 ? castAnim : 0x04 ), 0, ( castAnimLength != 0 ? castAnimLength : 4 ));
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - cEffects::PlayStaticAnimation( CBaseObject *target, UI16 effect, UI08 speed, UI08 loop, bool explode )
//o------------------------------------------------------------------------------------------------o
//| Purpose - Sends message to client to play a static animation effect on an object
//o------------------------------------------------------------------------------------------------o
void cEffects::PlayStaticAnimation( CBaseObject *target, UI16 effect, UI08 speed, UI08 loop, bool explode )
{
if( !ValidateObject( target ))
return;
CPGraphicalEffect toSend( 3, ( *target ));
if( target->GetObjType() != OT_CHAR )
{
toSend.TargetSerial(( *target ));
}
else
{
toSend.TargetSerial( INVALIDSERIAL );
}
toSend.Model( effect );
toSend.SourceLocation(( *target ));
if( target->GetObjType() != OT_CHAR )
{
toSend.TargetLocation(( *target ));
}
else
{
toSend.ZTrg( 15 );
}
toSend.Speed( speed );
toSend.Duration( loop );
toSend.AdjustDir( false );
toSend.ExplodeOnImpact( explode );
for( auto &mSock : FindNearbyPlayers( target, DIST_SAMESCREEN ))
{
mSock->Send( &toSend );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - cEffects::PlayStaticAnimation( SI16 x, SI16 y, SI08 z, UI16 effect, UI08 speed, UI08 loop, bool explode )
//o------------------------------------------------------------------------------------------------o
//| Purpose - Sends message to client to play a static animation effect at a location
//o------------------------------------------------------------------------------------------------o
void cEffects::PlayStaticAnimation( SI16 x, SI16 y, SI08 z, UI16 effect, UI08 speed, UI08 loop, bool explode )
{
CPGraphicalEffect toSend( 2 );
toSend.Model( effect );
toSend.SourceLocation( x, y, z );
toSend.TargetLocation( x, y, z );
toSend.Speed( speed );
toSend.Duration( loop );
toSend.AdjustDir( false );
toSend.ExplodeOnImpact( explode );
std::for_each( Network->connClients.begin(), Network->connClients.end(), [&toSend]( CSocket *tSock )
{
tSock->Send( &toSend );
});
}
//o------------------------------------------------------------------------------------------------o
//| Function - cEffects::Bolteffect()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Sends message to client to play a lightning bolt effect on a specific player
//o------------------------------------------------------------------------------------------------o
void cEffects::Bolteffect( CChar *player )
{
if( !ValidateObject( player ))
return;
CPGraphicalEffect toSend( 1, ( *player ));
toSend.SourceLocation(( *player ));
toSend.ExplodeOnImpact( false );
toSend.AdjustDir( false );
for( auto &mSock : FindNearbyPlayers( player ))
{
mSock->Send( &toSend );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - ExplodeItem()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Explode an item, dealing damage to nearby characters and deleting the item
//o------------------------------------------------------------------------------------------------o
auto ExplodeItem( CSocket *mSock, CItem *nItem, UI32 damage = 0, [[maybe_unused]] UI08 damageType = 0, bool explodeNearby = true ) -> void
{
auto c = mSock->CurrcharObj();
UI32 dx, dy, dz;
// - send the effect (visual and sound)
if( nItem->GetCont() )
{
Effects->PlayStaticAnimation( c, 0x36B0, 0x00, 0x09 );
nItem->SetCont( nullptr );
nItem->SetLocation( c );
Effects->PlaySound( c, 0x0207 );
}
else
{
Effects->PlayStaticAnimation( nItem, 0x36B0, 0x00, 0x09, 0x00 );
Effects->PlaySound( nItem, 0x0207 );
}
UI32 len = nItem->GetTempVar( CITV_MOREX ) / 250; //4 square max damage at 100 alchemy
if( damage == 0 )
{
damage = RandomNum( nItem->GetTempVar( CITV_MOREZ ) * 5, nItem->GetTempVar( CITV_MOREZ ) * 10 );
}
if( damage < 5 )
{
damage = RandomNum( 5, 10 ); // 5 points minimum damage
}
if( len < 2 )
{
len = 2; // 2 square min damage range
}
if( explodeNearby )
{
// Explode for characters nearby
for( auto &Cell : MapRegion->PopulateList( nItem ))
{
bool chain = false;
auto regChars = Cell->GetCharList();
for( const auto &tempChar : regChars->collection() )
{
if( !ValidateObject( tempChar ) || tempChar->GetInstanceId() != nItem->GetInstanceId() )
continue;
if( !tempChar->GetRegion()->IsSafeZone() )
{
// Character is in not safe zone, count damage
dx = abs( tempChar->GetX() - nItem->GetX() );
dy = abs( tempChar->GetY() - nItem->GetY() );
dz = abs( tempChar->GetZ() - nItem->GetZ() );
if( dx <= len && dy <= len && dz <= len )
{
if( !tempChar->IsGM() && !tempChar->IsInvulnerable() && ( tempChar->IsNpc() || IsOnline(( *tempChar ))))
{
//UI08 hitLoc = Combat->CalculateHitLoc();
damage = Combat->ApplyDefenseModifiers( HEAT, c, tempChar, ALCHEMY, 0, ( static_cast<SI32>( damage ) + ( 2 - std::min( dx, dy ))), true );
[[maybe_unused]] bool retVal = tempChar->Damage( static_cast<SI16>( damage ), HEAT, c, true );
}
}
}
}
auto regItems = Cell->GetItemList();
for( const auto &tempItem : regItems->collection() )
{
if( !ValidateObject( tempItem ) || tempItem->GetInstanceId() != nItem->GetInstanceId() )
continue;
if( tempItem->GetId() == 0x0F0D && tempItem->GetType() == IT_POTION )
{
dx = abs( nItem->GetX() - tempItem->GetX() );
dy = abs( nItem->GetY() - tempItem->GetY() );
dz = abs( nItem->GetZ() - tempItem->GetZ() );
if( dx <= 2 && dy <= 2 && dz <= 2 && !chain ) // only trigger if in a 2*2*2 cube
{
if( !( dx == 0 && dy == 0 && dz == 0 ))
{
if( RandomNum( 0, 1 ) == 1 )
{
chain = true;
}
Effects->TempEffect( c, tempItem, 17, 0, 1, 0 );
}
}
}
}
}
}
else
{
// Only affect character associated with item
if( !c->IsGM() && !c->IsInvulnerable() && ( c->IsNpc() || IsOnline(( *c ))))
{
damage = Combat->ApplyDefenseModifiers( HEAT, c, c, ALCHEMY, 0, static_cast<SI32>( damage ), true );
[[maybe_unused]] bool retVal = c->Damage( static_cast<SI16>( damage ), HEAT, c, true );
}
}
nItem->Delete();
}
//o------------------------------------------------------------------------------------------------o
//| Function - cEffects::HandleMakeItemEffect()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies maker's mark to crafted items and plays sound FX
//o------------------------------------------------------------------------------------------------o
void cEffects::HandleMakeItemEffect( CTEffect *tMake )
{
if( tMake == nullptr )
return;
CChar *src = CalcCharObjFromSer( tMake->Source() );
UI16 iMaking = tMake->More2();
CreateEntry_st *toMake = Skills->FindItem( iMaking );
if( toMake == nullptr )
return;
CSocket *sock = src->GetSocket();
std::string addItem = toMake->addItem;
UI16 amount = 1;
auto csecs = oldstrutil::sections( addItem, "," );
if( csecs.size() > 1 )
{
amount = oldstrutil::value<UI16>( oldstrutil::extractSection( addItem, ",", 1, 1 ));
addItem = oldstrutil::extractSection( addItem, ",", 0, 0 );
}
UI16 iColour = 0;
if( ValidateObject( src ))
{
// Get colour of the resource targeted by player (if any), so it can be applied to the item being made
TAGMAPOBJECT tempTagObj = src->GetTempTag( "craftItemColor" );
if( tempTagObj.m_ObjectType == TAGMAP_TYPE_INT && tempTagObj.m_IntValue > 0 )
{
iColour = static_cast<UI16>( tempTagObj.m_IntValue );
}
}
// Create the actual item
CItem *targItem = Items->CreateScriptItem( sock, src, addItem, amount, OT_ITEM, true, iColour );
for( size_t skCounter = 0; skCounter < toMake->skillReqs.size(); ++skCounter )
{
src->SkillUsed( false, toMake->skillReqs[skCounter].skillNumber );
}
if( targItem == nullptr )
{
Console.Error( oldstrutil::format( "CSkills::MakeItem() bad script item # %s, made by player 0x%X", addItem.c_str(), src->GetSerial() ));
return;
}
else
{
// targItem->SetName2( targItem->GetName().c_str() ); // Why was this here?
SI32 rank = Skills->CalcRankAvg( src, ( *toMake ));
SI32 maxrank = toMake->maxRank;
Skills->ApplyRank( sock, targItem, static_cast<UI08>( rank ), static_cast<UI08>( maxrank ));
// if we're not a GameMaster, see if we should store our creator
if( !src->IsGM() && !toMake->skillReqs.empty() )
{
// Store serial of crafter
targItem->SetCreator( src->GetSerial() );
// Store skill number that item was crafted with
targItem->SetMadeWith( toMake->skillReqs[0].skillNumber + 1 );
// Set Makers Mark on item if primary skill used to craft item is at 100.0 or higher
SI32 primarySkill = src->GetSkill( toMake->skillReqs[0].skillNumber );
if( primarySkill >= 1000 )
{
targItem->SetMakersMark( true );
}
}
else
{
// Crafted by a GM, don't apply maker's marks or give any additional information
targItem->SetCreator( INVALIDSERIAL );
targItem->SetMadeWith( 0 );
}
// Store the entry number from create DFNs that item was made from
targItem->EntryMadeFrom( iMaking );
}
// Make sure it's movable
targItem->SetMovable( 1 );
if( toMake->soundPlayed )
{
PlaySound( sock, toMake->soundPlayed, true );
}
if( FindItemOwner( targItem ) == src )
{
sock->SysMessage( 985 ); // You create the item and place it in your backpack.
}
// Trigger onMakeItem() JS event for character who crafted the item
std::vector<UI16> scriptTriggers = src->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
toExecute->OnMakeItem( sock, src, targItem, iMaking );
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - cEffects::CheckTempeffects()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Checks timer-controlled temporary effects
//o------------------------------------------------------------------------------------------------o
auto cEffects::CheckTempeffects() -> void
{
CItem *i = nullptr;
CChar *s = nullptr, *src = nullptr;
CSocket *tSock = nullptr;
CBaseObject *myObj = nullptr;
const UI32 j = cwmWorldState->GetUICurrentTime();
std::vector<CTEffect*> removeEffects;
auto collection = cwmWorldState->tempEffects.collection();
for( auto &Effect : collection )
{
if( Effect == nullptr )
{
//removeEffects.push_back( Effect );
continue;
}
else if( Effect->Destination() == INVALIDSERIAL )
{
removeEffects.push_back( Effect );
continue;
}
if( Effect->ExpireTime() > j )
continue;
if( Effect->Destination() < BASEITEMSERIAL )
{
s = CalcCharObjFromSer( Effect->Destination() );
if( !ValidateObject( s ))
{
removeEffects.push_back( Effect );
//cwmWorldState->tempEffects.Remove( Effect, true );
continue;
}
tSock = s->GetSocket();
}
bool equipCheckNeeded = false;
bool validChar = ValidateObject( s );
switch( Effect->Number() )
{
case 1: // Paralysis / Paralysis Field Spells
if( validChar && s->IsFrozen() )
{
s->SetFrozen( false );
if( tSock != nullptr )
{
// Don't show system message if we don't want it to
if( Effect->More1() == 0 )
{
tSock->SysMessage( 700 ); // You are no longer frozen.
}
s->Update();
}
}
break;
case 2: // Nightsight Potion (JS) and Spell (code)
if( validChar )
{
s->SetFixedLight( 255 );
DoLight( tSock, cwmWorldState->ServerData()->WorldLightCurrentLevel() );
}
break;
case 3: // Clumsy spell (JS)
if( validChar )
{
s->IncDexterity2( Effect->More1() );
equipCheckNeeded = true;
}
break;
case 4: // Feeblemind spell (JS)
if( validChar )
{
s->IncIntelligence2( Effect->More1() );
equipCheckNeeded = true;
}
break;
case 5: // Weaken Spell
if( validChar )
{
s->IncStrength2( Effect->More1() );
equipCheckNeeded = true;
}
break;
case 6: // Agility Potion (JS) and Spell (code)
if( validChar )
{
s->IncDexterity2( -Effect->More1() );
s->SetStamina( std::min( s->GetStamina(), s->GetMaxStam() ));
equipCheckNeeded = true;
}
break;
case 7: // Cunning Spell
if( validChar )
{
s->IncIntelligence2( -Effect->More1() );
s->SetMana( std::min( s->GetMana(), s->GetMaxMana() ));
equipCheckNeeded = true;
}
break;
case 8: // Strength Potion (JS) and Spell (code)
if( validChar )
{
s->IncStrength2( -Effect->More1() );
s->SetHP( std::min( s->GetHP(), static_cast<SI16>( s->GetMaxHP() )));
equipCheckNeeded = true;
}
break;
case 9: // Grind potion (also used for necro stuff)
if( validChar )
{
switch( Effect->More1() )
{
case 0:
if( Effect->More2() != 0 )
{
s->TextMessage( nullptr, 1270, EMOTE, 1, s->GetName().c_str() ); // *%s continues grinding*
}
PlaySound( s, 0x0242 );
break;
default:
break;
}
}
break;
case 11: // Bless Spell
if( validChar )
{
s->IncStrength2( -Effect->More1() );
s->SetHP( std::min( s->GetHP(), static_cast<SI16>( s->GetMaxHP() )));
s->IncDexterity2( -Effect->More2() );
s->SetStamina( std::min( s->GetStamina(), s->GetMaxStam() ));
s->IncIntelligence2( -Effect->More3() );
s->SetMana( std::min( s->GetMana(), s->GetMaxMana() ));
equipCheckNeeded = true;
}
break;
case 12: // Curse Spell
equipCheckNeeded = false;
if( validChar )
{
s->IncStrength2( Effect->More1() );
s->IncDexterity2( Effect->More2() );
s->IncIntelligence2( Effect->More3() );
equipCheckNeeded = true;
}
break;
case 15: // Reactive Armor Spell
if( validChar )
{
s->SetReactiveArmour( false );
}
break;
case 16: // Explosion potion messages (JS)
src = CalcCharObjFromSer( Effect->Source() );
if( src->GetTimer( tCHAR_ANTISPAM ) < cwmWorldState->GetUICurrentTime() )
{
src->SetTimer( tCHAR_ANTISPAM, BuildTimeValue( 1 ));
std::string mTemp = oldstrutil::number( Effect->More3() );
if( tSock )
{
tSock->SysMessage( mTemp.c_str() );
}
}
break;
case 17: // Explosion effect (JS and code)
src = CalcCharObjFromSer( Effect->Source() );
ExplodeItem( src->GetSocket(), static_cast<CItem *>( Effect->ObjPtr() )); //explode this item
break;
case 18: // Polymorph Spell
if( validChar )
{
s->SetId( s->GetOrgId() );
s->IsPolymorphed( false );
}
break;
case 19: // Incognito Spell
if( validChar )
{
s->SetId( s->GetOrgId() );
// ------ NAME -----
s->SetName( s->GetOrgName() );
i = s->GetItemAtLayer( IL_HAIR );
if( ValidateObject( i ))
{
i->SetColour( s->GetHairColour() );
i->SetId( s->GetHairStyle() );
}
i = s->GetItemAtLayer( IL_FACIALHAIR );
if( ValidateObject( i ) && s->GetId( 2 ) == 0x90 )
{
i->SetVisible( VT_VISIBLE );
i->SetColour( s->GetBeardColour() );
i->SetId( s->GetBeardStyle() );
}
if( tSock )
{
s->SendWornItems( tSock );
}
s->IsIncognito( false );
}
break;
case 21: // Protection Spell
if( validChar )
{
UI16 toDrop;
toDrop = Effect->More1();
if(( s->GetBaseSkill( PARRYING ) - toDrop ) < 0 )
{
s->SetBaseSkill( 0, PARRYING );
}
else
{
s->SetBaseSkill( s->GetBaseSkill( PARRYING ) - toDrop, PARRYING );
}
equipCheckNeeded = true;
}
break;
case 25: // Temporarily set item as disabled
Effect->ObjPtr()->SetDisabled( false );
break;
case 26: // Disallow immediately using another potion (JS)
if( validChar )
{
s->SetUsingPotion( false );
}
break;
case 27: // Explosion Spell
src = CalcCharObjFromSer( Effect->Source() );
if( ValidateObject( src ) && ValidateObject( s ))
{
Magic->PlaySound( src, 43 );
Magic->DoMoveEffect( 43, s, src );
Magic->DoStaticEffect( s, 43 );
Magic->MagicDamage( s, Effect->More1(), src, HEAT );
equipCheckNeeded = true;
}
break;
case 28: // Magic Arrow Spell
src = CalcCharObjFromSer( Effect->Source() );
if( ValidateObject( src ) && ValidateObject( s ))
{
Magic->PlaySound( src, 5 );
Magic->DoMoveEffect( 5, s, src );
// Caster.MovingParticles(d, 0x36E4, 5, 0, false, false, 3006, 0, 0);
Magic->DoStaticEffect( s, 5 );
Magic->MagicDamage( s, Effect->More1(), src, HEAT );
equipCheckNeeded = true;
}
break;
case 29: // Harm Spell
src = CalcCharObjFromSer( Effect->Source() );
if( ValidateObject( src ) && ValidateObject( s ))
{
Magic->PlaySound( src, 12 );
Magic->DoMoveEffect( 12, s, src );
Magic->DoStaticEffect( s, 12 );
Magic->MagicDamage( s, Effect->More1(), src, COLD );
equipCheckNeeded = true;
}
break;
case 30: // Fireball Spell
src = CalcCharObjFromSer( Effect->Source() );
if( ValidateObject( src ) && ValidateObject( s ))
{
Magic->PlaySound( src, 18 );
Magic->DoMoveEffect( 18, s, src );
Magic->DoStaticEffect( s, 18 );
Magic->MagicDamage( s, Effect->More1(), src, HEAT );
equipCheckNeeded = true;
}
break;
case 31: // Lightning Spell
src = CalcCharObjFromSer( Effect->Source() );
if( ValidateObject( src ) && ValidateObject( s ))
{
Effects->Bolteffect( s );
Magic->PlaySound( src, 30 );
Magic->DoMoveEffect( 30, s, src );
Magic->DoStaticEffect( s, 30 );
Magic->MagicDamage( s, Effect->More1(), src, LIGHTNING );
equipCheckNeeded = true;
}
break;
case 32: // Mind Blast
src = CalcCharObjFromSer( Effect->Source() );
if( ValidateObject( src ) && ValidateObject( s ))
{
Magic->PlaySound( src, 37 );
Magic->DoMoveEffect( 37, s, src );
Magic->DoStaticEffect( s, 37 );
Magic->MagicDamage( s, Effect->More1(), src, COLD );
equipCheckNeeded = true;
}
break;
case 33: // Energy Bolt
src = CalcCharObjFromSer( Effect->Source() );
if( ValidateObject( src ) && ValidateObject( s ))
{
Magic->PlaySound( s, 42 );
Magic->DoMoveEffect( 42, s, src );
Magic->DoStaticEffect( s, 42 );
Magic->MagicDamage( s, Effect->More1(), src, LIGHTNING );
equipCheckNeeded = true;
}
break;
case 34: // Chain Lightning
src = CalcCharObjFromSer( Effect->Source() );
if( ValidateObject( src ) && ValidateObject( s ))
{
Effects->Bolteffect( s );
Magic->PlaySound( s, 49 );
Magic->DoMoveEffect( 49, s, src );
Magic->DoStaticEffect( s, 49 );
Magic->MagicDamage( s, Effect->More1(), src, LIGHTNING );
equipCheckNeeded = true;
}
break;
case 35: // Flame Strike
src = CalcCharObjFromSer( Effect->Source() );
if( ValidateObject( src ) && ValidateObject( s ))
{
Magic->PlaySound( s, 51 );
Magic->DoMoveEffect( 51, s, src );
Magic->DoStaticEffect( s, 51 );
Magic->MagicDamage( s, Effect->More1(), src, HEAT );
equipCheckNeeded = true;
}
break;
case 36: // Meteor Swarm
src = CalcCharObjFromSer( Effect->Source() );
if( ValidateObject( src ) && ValidateObject( s ))
{
Magic->PlaySound( src, 55 );
if( s != src )
{
Magic->DoMoveEffect( 55, s, src );
}
Magic->DoStaticEffect( s, 55 );
//Effects->PlaySound( target, 0x160 );
//Effects->PlayMovingAnimation( caster, target, 0x36D5, 0x07, 0x00, 0x01 );
Magic->MagicDamage( s, Effect->More1(), src, HEAT );
equipCheckNeeded = true;
}
break;
case 40: // Used by JS timers
{
// Default/Global script ID
UI16 scpNum = 0xFFFF;
// Get script associated with effect, if any
cScript *tScript = JSMapping->GetScript( Effect->AssocScript() );
// items have serials of 0x40000000 and above, and we already know it's not INVALIDSERIAL
if( Effect->Source() >= BASEITEMSERIAL )
{
myObj = CalcItemObjFromSer( Effect->Source() );
equipCheckNeeded = false;
}
else
{
myObj = CalcCharObjFromSer( Effect->Source() );
equipCheckNeeded = true;
}
// Check that object effect was running on still exists
if( ValidateObject( myObj ))
{
// No associated script, so it must be another callback variety
if( tScript == nullptr )
{
if( Effect->More2() != 0xFFFF ) // A scriptId other than default one was found
{
scpNum = Effect->More2();
tScript = JSMapping->GetScript( scpNum );
}
}
// Make sure to check for a specific script via envoke system when the previous checks ended in the global script.
if(( tScript == nullptr || scpNum == 0 ) && Effect->Source() >= BASEITEMSERIAL )
{
if( JSMapping->GetEnvokeByType()->Check( static_cast<UI16>(( static_cast<CItem *>( myObj ))->GetType() )))
{
scpNum = JSMapping->GetEnvokeByType()->GetScript( static_cast<UI16>(( static_cast<CItem *>( myObj ))->GetType() ));
tScript = JSMapping->GetScript( scpNum );
}
else if( JSMapping->GetEnvokeById()->Check( myObj->GetId() ))
{
scpNum = JSMapping->GetEnvokeById()->GetScript( myObj->GetId() );
tScript = JSMapping->GetScript( scpNum );
}
}
// Callback to onTimer event in script
if( tScript )
{
tScript->OnTimer( myObj, static_cast<UI16>( Effect->More1() ));
}
}
break;
}
case 41: // Start of item crafting
HandleMakeItemEffect( Effect );
break;
case 42: // End of item crafting
src = CalcCharObjFromSer( Effect->Source() );
PlaySound( src, Effect->More2() );
break;
case 43: // Turn a wooly sheep into a sheared sheep
src = CalcCharObjFromSer( Effect->Source() );
if( !ValidateObject( src )) // char doesn't exist!
{
break;
}
else if( src->GetId() == 0xCF )
{
break;
}
src->SetId( 0xCF ); // Thats all we need to do
break;
case 50: // Unlock a magically locked object
{
myObj = CalcItemObjFromSer( Effect->Destination() );
if( ValidateObject( myObj ))
{
auto lockedItem = static_cast<CItem *>( myObj );
// Only continue if door is actually still locked
auto lockType = lockedItem->GetType();
if( lockType == IT_LOCKEDDOOR || lockType == IT_LOCKEDCONTAINER || lockType == IT_LOCKEDSPAWNCONT )
{
switch( lockType )
{
case IT_LOCKEDDOOR: lockedItem->SetType( IT_DOOR ); break;
case IT_LOCKEDCONTAINER: lockedItem->SetType( IT_CONTAINER ); break;
case IT_LOCKEDSPAWNCONT: lockedItem->SetType( IT_SPAWNCONT ); break;
default: break;
}
lockedItem->RemoveFromSight();
lockedItem->Update();
if( Magic->spells[23].Effect() != INVALIDID )
{
Effects->PlaySound( lockedItem, Magic->spells[23].Effect(), true );
}
CMagicStat temp = Magic->spells[23].StaticEffect();
if( temp.Effect() != INVALIDID )
{
auto iCont = lockedItem->GetCont();
if( ValidateObject( iCont ) && iCont->CanBeObjType( OT_CHAR ))
{
// Play unlock FX on character holding object
Effects->PlayStaticAnimation( iCont, temp.Effect(), temp.Speed(), temp.Loop() );
}
else
{
// Play unlock FX directly on object
Effects->PlayStaticAnimation( lockedItem, temp.Effect(), temp.Speed(), temp.Loop() );
}
}
}
}
break;
}
default:
Console.Error( oldstrutil::format( " Fallout of switch statement without default (%i). checktempeffects()", Effect->Number() ));
break;
}
if( validChar && equipCheckNeeded )
{
Items->CheckEquipment( s ); // checks equipments for stat requirements
}
removeEffects.push_back( Effect );
}
std::for_each( removeEffects.begin(), removeEffects.end(), []( CTEffect *effect )
{
cwmWorldState->tempEffects.Remove( effect, true );
});
}
//o------------------------------------------------------------------------------------------------o
//| Function - ReverseEffect()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Reverse a temp effect
//o------------------------------------------------------------------------------------------------o
void ReverseEffect( CTEffect *Effect )
{
CChar *s = CalcCharObjFromSer( Effect->Destination() );
if( ValidateObject( s ))
{
switch( Effect->Number() )
{
case 1: // Paralysis Spell
s->SetFrozen( false );
break;
case 2: // Nightsight Potion (JS) and Spell (code)
s->SetFixedLight( 255 );
break;
case 3: s->IncDexterity2( Effect->More1() ); break; // Clumsy Spell (JS)
case 4: s->IncIntelligence2( Effect->More1() ); break; // Feeblemind Spell (JS)
case 5: s->IncStrength2( Effect->More1() ); break; // Weaken Spell
case 6: s->IncDexterity2( -Effect->More1() ); break; // Agility Potion (JS) and Spell (code)
case 7: s->IncIntelligence2( -Effect->More1() ); break; // Cunning Spell
case 8: s->IncStrength2( -Effect->More1() ); break; // Strength Potion (JS) and Spell (code)
case 11: // Bless Spell
s->IncStrength2( -Effect->More1() );
s->IncDexterity2( -Effect->More2() );
s->IncIntelligence2( -Effect->More3() );
break;
case 12: // Curse Spell
s->IncStrength2( Effect->More1() );
s->IncDexterity2( Effect->More2() );
s->IncIntelligence2( Effect->More3() );
break;
case 18: // Polymorph Spell
s->SetId( s->GetOrgId() );
s->IsPolymorphed( false );
break;
case 19: // Incognito Spell
CItem *j;
// ------ SEX ------
s->SetId( s->GetOrgId() );
s->SetName( s->GetOrgName() );
j = s->GetItemAtLayer( IL_HAIR );
if( ValidateObject( j ))
{
j->SetColour( s->GetHairColour() );
j->SetId( s->GetHairStyle() );
}
j = s->GetItemAtLayer( IL_FACIALHAIR );
if( ValidateObject( j ) && s->GetId( 2 ) == 0x90 )
{
j->SetColour( s->GetBeardColour() );
j->SetId( s->GetBeardStyle() );
}
// only refresh once
CSocket *tSock;
tSock = s->GetSocket();
s->SendWornItems( tSock );
s->IsIncognito( false );
break;
case 21: // Protection Spell
SI32 toDrop;
toDrop = Effect->More1();
if(( s->GetBaseSkill( PARRYING ) - toDrop ) < 0 )
{
s->SetBaseSkill( 0, PARRYING );
}
else
{
s->SetBaseSkill( s->GetBaseSkill( PARRYING ) - toDrop, PARRYING );
}
break;
case 26: // Re-enable the usage of potions
s->SetUsingPotion( false );
break;
default:
Console.Error( " Fallout of switch statement without default. uox3.cpp, reverseEffect()");
break;
}
}
Items->CheckEquipment( s );
}
//o------------------------------------------------------------------------------------------------o
//| Function - cEffects::TempEffect()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Adds a temp effect to a character
//o------------------------------------------------------------------------------------------------o
void cEffects::TempEffect( CChar *source, CChar *dest, UI08 num, UI16 more1, UI16 more2, UI16 more3, [[maybe_unused]] CItem *targItemPtr )
{
//if( !ValidateObject( source ) || !ValidateObject( dest ))
if( !ValidateObject( dest ))
return;
bool spellResisted = false;
CTEffect *toAdd = new CTEffect;
SERIAL sourceSerial = 0;
if( source != nullptr )
{
sourceSerial = source->GetSerial();
}
SERIAL targSer = dest->GetSerial();
toAdd->Source( sourceSerial );
toAdd->Destination( targSer );
std::vector<CTEffect *> removeEffect;
for( const auto &Effect : cwmWorldState->tempEffects.collection() )
{
if( Effect->Destination() == targSer )
{
if( Effect->Number() == num )
{
switch( Effect->Number() )
{
case 3: // Clumsy Spell (JS)
case 4: // Feeblemind Spell (JS)
case 5: // Weaken Spell
case 6: // Agility Potion (JS) and Spell (code)
case 7: // Cunning Spell
case 8: // Strength Potion (JS) and Spell (code)
case 11: // Bless Spell
case 12: // Curse Spell
case 18: // Polymorph Spell
case 19: // Incognito Spell
case 21: // Protection Spell
ReverseEffect( Effect );
removeEffect.push_back( Effect );
break;
default:
break;
}
}
}
}
std::for_each( removeEffect.begin(), removeEffect.end(), []( CTEffect *effect )
{
cwmWorldState->tempEffects.Remove( effect, true );
});
CSocket *tSock = dest->GetSocket();
toAdd->Number( num );
switch( num )
{
case 1: // Paralyse Spell
{
dest->SetFrozen( true );
R32 effectDuration = 0;
if( source == nullptr )
{
effectDuration = static_cast<R32>( more2 );
if( more3 != 0 )
{
// Divide duration by more3 value
effectDuration /= static_cast<R32>( more3 );
}
// Keep a reference to more1, if different than 0
if( more1 != 0 )
{
toAdd->More1( more1 );
}
}
else
{
effectDuration = static_cast<R32>( source->GetSkill( MAGERY )) / 100.0f;
}
toAdd->ExpireTime( BuildTimeValue( effectDuration ));
toAdd->Dispellable( true );
break;
}
case 2: // Nightsight Potion (JS) and Spell (code)
{
SI16 worldbrightlevel;
worldbrightlevel = cwmWorldState->ServerData()->WorldLightBrightLevel();
dest->SetFixedLight( static_cast<UI08>( worldbrightlevel ));
DoLight( tSock, static_cast<SI08>( worldbrightlevel ));
R32 effectDuration = 0;
if( source == nullptr )
{
effectDuration = static_cast<R32>( more2 );
}
else
{
effectDuration = static_cast<R32>( source->GetSkill( MAGERY )) / 2.0f;
}
toAdd->ExpireTime( BuildTimeValue( effectDuration ));
toAdd->Dispellable( true );
break;
}
case 3: // Clumsy Spell (JS)
{
if( dest->GetDexterity() < more1 )
{
more1 = dest->GetDexterity();
}
dest->IncDexterity2( -more1 );
dest->SetStamina( std::min( dest->GetStamina(), dest->GetMaxStam() ));
R32 effectDuration = 0;
if( source == nullptr )
{
// Use duration provided with effect call
effectDuration = static_cast<R32>( more2 );
// Halve effect-duration on resist
spellResisted = Magic->CheckResist( more3, dest, 1 );
}
else
{
effectDuration = static_cast<R32>( source->GetSkill( MAGERY ) / 10.0f );
//Halve effect-timer on resist
spellResisted = Magic->CheckResist( source, dest, 1 );
}
if( spellResisted )
{
toAdd->ExpireTime( BuildTimeValue( effectDuration / 2.0f ));
}
else
{
toAdd->ExpireTime( BuildTimeValue( effectDuration ));
}
toAdd->More1( more1 );
toAdd->Dispellable( true );
break;
}
case 4: // Feeblemind Spell (JS)
{
if( dest->GetIntelligence() < more1 )
{
more1 = dest->GetIntelligence();
}
dest->IncIntelligence2( -more1 );
dest->SetMana( std::min( dest->GetMana(), dest->GetMaxMana() ));
R32 effectDuration = 0;
if( source == nullptr )
{
// Use duration provided with effect call
effectDuration = static_cast<R32>( more2 );
// Halve effect-duration on resist
spellResisted = Magic->CheckResist( more3, dest, 3 );
}
else
{
effectDuration = static_cast<R32>( source->GetSkill( MAGERY ) / 10.0f );
//Halve effect-timer on resist
spellResisted = Magic->CheckResist( source, dest, 1 );
}
if( spellResisted )
{
toAdd->ExpireTime( BuildTimeValue( effectDuration / 2.0f ));
}
else
{
toAdd->ExpireTime( BuildTimeValue( effectDuration ));
}
toAdd->More1( more1 );
toAdd->Dispellable( true );
break;
}
case 5: // Weaken Spell
{
if( dest->GetStrength() < more1 )
{
more1 = dest->GetStrength();
}
dest->IncStrength2( -more1 );
dest->SetHP( std::min( dest->GetHP(), static_cast<SI16>( dest->GetMaxHP() )));
R32 effectDuration = 0;
if( source == nullptr )
{
// Use duration provided with effect call
effectDuration = static_cast<R32>( more2 );
// Halve effect-duration on resist
spellResisted = Magic->CheckResist( more3, dest, 1 );
}
else
{
effectDuration = static_cast<R32>( source->GetSkill( MAGERY ) / 10.0f );
//Halve effect-timer on resist
spellResisted = Magic->CheckResist( source, dest, 1 );
}
//Halve effect-timer on resist
if( spellResisted )
{
toAdd->ExpireTime( BuildTimeValue( effectDuration / 2.0f ));
}
else
{
toAdd->ExpireTime( BuildTimeValue( effectDuration ));
}
toAdd->More1( more1 );
toAdd->Dispellable( true );
break;
}
case 6: // Agility Potion (JS) and Spell (code)
{
dest->IncDexterity( more1 );
if( source == nullptr )
{
// Use duration provided with effect call
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>( more2 )));
}
else
{
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>( source->GetSkill( MAGERY )) / 10.0f ));
}
toAdd->More1( more1 );
toAdd->Dispellable( true );
break;
}
case 7: // Cunning Spell
dest->IncIntelligence2( more1 );
if( source == nullptr )
{
// Use duration provided with effect call
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>(more2) ));
}
else
{
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>( source->GetSkill( MAGERY )) / 10.0f ));
}
toAdd->More1( more1 );
toAdd->Dispellable( true );
break;
case 8: // Strength Potion (JS) and Spell (code)
dest->IncStrength2( more1 );
if( source == nullptr )
{
// Use duration provided with effect call
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>( more2 )));
}
else
{
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>( source->GetSkill( MAGERY )) / 10.0f ));
}
toAdd->More1( more1 );
toAdd->Dispellable( true );
break;
case 9: // Grind Necro Reagent
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>( more2 )));
toAdd->More1( more1 );
toAdd->More2( more2 );
toAdd->Dispellable( false );
break;
case 10: // ???
toAdd->ExpireTime( BuildTimeValue( 12.0f ));
toAdd->Dispellable( false );
toAdd->More1( more1 );
toAdd->More2( more2 );
break;
case 11: // Bless Spell
if( source == nullptr )
{
// No source! Effect gotten from equipping an item?
// Apply same bonus to all stats
dest->IncStrength2( more1 );
dest->IncDexterity2( more1 );
dest->IncIntelligence2( more1 );
toAdd->ExpireTime( BuildTimeValue( more2 ));
toAdd->More1( more1 );
toAdd->More2( more1 );
toAdd->More3( more1 );
}
else
{
// Effect gotten from a spell cast by another character
dest->IncStrength2( more1 );
dest->IncDexterity2( more2 );
dest->IncIntelligence2( more3 );
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>( source->GetSkill( MAGERY )) / 10.0f ));
toAdd->More1( more1 );
toAdd->More2( more2 );
toAdd->More3( more3 );
}
toAdd->Dispellable( true );
break;
case 12: // Curse Spell
{
bool spellResisted = false;
R32 effectDuration = 0;
if( source == nullptr )
{
auto effectStrength = more1;
if( dest->GetStrength() < more1 )
{
effectStrength = dest->GetStrength();
}
dest->IncStrength2( -effectStrength );
if( dest->GetDexterity() < more1 )
{
effectStrength = dest->GetDexterity();
}
dest->IncDexterity2( -effectStrength );
if( dest->GetIntelligence() < more1 )
{
effectStrength = dest->GetIntelligence();
}
dest->IncIntelligence2( -effectStrength );
toAdd->More1( more1 );
toAdd->More2( more1 );
toAdd->More3( more1 );
// Use duration provided with effect call
effectDuration = static_cast<R32>( more2 );
// Halve effect-duration on resist
spellResisted = Magic->CheckResist( more3, dest, 4 );
}
else
{
if( dest->GetStrength() < more1 )
{
more1 = dest->GetStrength();
}
if( dest->GetDexterity() < more2 )
{
more2 = dest->GetDexterity();
}
if( dest->GetIntelligence() < more3 )
{
more3 = dest->GetIntelligence();
}
dest->IncStrength2( -more1 );
dest->IncDexterity2( -more2 );
dest->IncIntelligence2( -more3 );
toAdd->More1( more1 );
toAdd->More2( more2 );
toAdd->More3( more3 );
effectDuration = static_cast<R32>( source->GetSkill( MAGERY )) / 10.0f;
//Halve effect-timer on resist
spellResisted = Magic->CheckResist( source, dest, 4 );
}
if( spellResisted )
{
toAdd->ExpireTime( BuildTimeValue( effectDuration / 2.0f ));
}
else
{
toAdd->ExpireTime( BuildTimeValue( effectDuration ));
}
toAdd->Dispellable( true );
break;
}
case 15: // Reactive Armor Spell
if( source != nullptr )
{
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>( source->GetSkill( MAGERY )) / 10.0f ));
}
else
{
toAdd->ExpireTime( BuildTimeValue( 6.0f ));
}
toAdd->Dispellable( true );
break;
case 16: // Explosion potion messages (JS)
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>( more2 )));
toAdd->More1( more1 ); //item/potion
toAdd->More2( more2 ); //seconds
toAdd->More3( more3 ); //countdown#
toAdd->Dispellable( false );
break;
case 18: // Polymorph Spell
toAdd->ExpireTime( cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_POLYMORPH ));
toAdd->Dispellable( false );
UI16 k;
// Grey flag when polymorphed
dest->SetTimer( tCHAR_CRIMFLAG, cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_POLYMORPH ));
if( dest->IsOnHorse() )
{
DismountCreature( dest );
}
else if( dest->IsFlying() )
{
dest->ToggleFlying();
}
k = ( more1<<8 ) + more2;
if( k <= 0x03e2 ) // body-values >0x3e1 crash the client
{
dest->SetId( k );
}
break;
case 19: // Incognito Spell
toAdd->ExpireTime( BuildTimeValue( 90.0f )); // 90 seconds
toAdd->Dispellable( false );
break;
case 21: // Protection Spell
{
R32 effectDuration = 0;
if( source == nullptr )
{
effectDuration = static_cast<R32>( more2 );
}
else
{
effectDuration = 120.0f;
}
toAdd->ExpireTime( BuildTimeValue( effectDuration ));
toAdd->Dispellable( true );
toAdd->More1( more1 );
dest->SetBaseSkill( dest->GetBaseSkill( PARRYING ) + more1, PARRYING );
break;
}
case 25: // Temporarily set item as disabled
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>( more1 )));
toAdd->ObjPtr( dest );
dest->SetDisabled( true );
toAdd->More2( 1 );
break;
case 26: // Temporarily disable the usage of potions
toAdd->ExpireTime( cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_POTION ));
dest->SetUsingPotion( true );
break;
case 27: // Explosion Spell
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>( Magic->spells[43].DamageDelay() )));
toAdd->More1( more1 );
break;
case 28: // Magic Arrow Spell
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>(Magic->spells[5].DamageDelay() )));
toAdd->More1( more1 );
break;
case 29: // Harm Spell
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>(Magic->spells[12].DamageDelay() )));
toAdd->More1( more1 );
break;
case 30: // Fireball Spell
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>(Magic->spells[18].DamageDelay() )));
toAdd->More1( more1 );
break;
case 31: // Lightning Spell
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>(Magic->spells[30].DamageDelay() )));
toAdd->More1( more1 );
break;
case 32: // Mind Blast Spell
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>(Magic->spells[37].DamageDelay() )));
toAdd->More1( more1 );
break;
case 33: // Energy Bolt Spell
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>(Magic->spells[42].DamageDelay() )));
toAdd->More1( more1 );
break;
case 34: // Chain Lightning Spell
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>(Magic->spells[49].DamageDelay() )));
toAdd->More1( more1 );
break;
case 35: // Flamestrike Spell
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>(Magic->spells[51].DamageDelay() )));
toAdd->More1( more1 );
break;
case 36: // Meteor Swarm
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>(Magic->spells[55].DamageDelay() )));
toAdd->More1( more1 );
break;
case 40: // Used by JS timers
toAdd->ExpireTime( BuildTimeValue(( static_cast<R32>( more1 ) + static_cast<R32>( more2 ) / 1000.0f )));
toAdd->More1( more3 );
break;
case 41: // creating item
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>( more1 ) / 100.0f ));
toAdd->More2( more2 );
break;
case 42: // delayed sound effect for crafting
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>( more1 ) / 100.0f ));
toAdd->More2( more2 );
break;
case 43: // regain wool for sheeps (JS)
toAdd->ExpireTime( BuildTimeValue( static_cast<R32>( more1 )));
break;
default:
Console.Error( oldstrutil::format( " Fallout of switch statement (%d) without default. uox3.cpp, tempeffect()", num ));
delete toAdd;
return;
}
cwmWorldState->tempEffects.Add( toAdd );
}
//o------------------------------------------------------------------------------------------------o
//| Function - cEffects::TempEffect( CChar *source, CItem *dest, UI08 num, UI16 more1, UI16 more2, UI16 more3 )
//o------------------------------------------------------------------------------------------------o
//| Purpose - Adds a temp effect to an item
//o------------------------------------------------------------------------------------------------o
void cEffects::TempEffect( CChar *source, CItem *dest, UI08 num, UI16 more1, UI16 more2, [[maybe_unused]] UI16 more3 )
{
if( !ValidateObject( dest ))
return;
CTEffect *toAdd = new CTEffect;
if( ValidateObject( source ))
{
toAdd->Source( source->GetSerial() );
}
else
{
toAdd->Source( INVALIDSERIAL );
}
toAdd->Destination( dest->GetSerial() );
toAdd->Number( num );
switch( num )
{
case 10: // Crafting Potion??
toAdd->ExpireTime( BuildTimeValue( 12 ));
toAdd->Dispellable( false );
toAdd->More1( more1 );
toAdd->More2( more2 );
source->SkillUsed( true, ALCHEMY );
break;
case 13: // Timer for door (gangplank on boats?) to autoclose?
if( !dest->IsDoorOpen() )
{
dest->SetDoorOpen( true );
}
toAdd->ExpireTime( BuildTimeValue( 10 ));
toAdd->Dispellable( false );
break;
case 17: // Explosion potion (explosion) Tauriel (explode in 4 seconds)
toAdd->ExpireTime( BuildTimeValue( 4 ));
toAdd->More1( more1 );
toAdd->More2( more2 );
toAdd->ObjPtr( dest );
toAdd->Dispellable( false );
break;
case 25: // // Temporarily set item as disabled
toAdd->ExpireTime( BuildTimeValue( more1 ));
toAdd->ObjPtr( dest );
dest->SetDisabled( true );
toAdd->More2( 0 );
break;
case 50: // Magically locked, expires in X seconds (more1, based on caster's Magery skill)
toAdd->ExpireTime( BuildTimeValue( more1 ));
toAdd->ObjPtr( dest );
toAdd->Dispellable( false );
break;
default:
Console.Error( " Fallout of switch statement without default. uox3.cpp, tempeffect2()" );
delete toAdd;
return;
}
cwmWorldState->tempEffects.Add( toAdd );
}
//o------------------------------------------------------------------------------------------------o
//| Function - cEffects::SaveEffects()
//| Date - 16 March, 2002
//o------------------------------------------------------------------------------------------------o
//| Purpose - Saves out the TempEffects to effects.wsc
//o------------------------------------------------------------------------------------------------o
void cEffects::SaveEffects( void )
{
std::ofstream effectDestination;
const char blockDiscriminator[] = "\n\n---EFFECT---\n\n";
SI32 s_t = GetClock();
Console << "Saving Effects... ";
Console.TurnYellow();
std::string filename = cwmWorldState->ServerData()->Directory( CSDDP_SHARED ) + "effects.wsc";
effectDestination.open( filename.c_str() );
if( !effectDestination )
{
Console.Error( oldstrutil::format( "Failed to open %s for writing", filename.c_str() ));
return;
}
for( const auto &currEffect : cwmWorldState->tempEffects.collection() )
{
if( currEffect )
{
currEffect->Save( effectDestination );
effectDestination << blockDiscriminator;
}
}
effectDestination.close();
Console << "\b\b\b\b";
Console.PrintDone();
SI32 e_t = GetClock();
Console.Print( oldstrutil::format("Effects saved in %.02fsec\n", ( static_cast<R32>( e_t-s_t )) / 1000.0f ));
}
void ReadWorldTagData( std::istream &inStream, std::string &tag, std::string &data );
//o------------------------------------------------------------------------------------------------o
//| Function - cEffects::LoadEffects()
//| Date - 16 March, 2002
//o------------------------------------------------------------------------------------------------o
//| Purpose - Loads in Effects from disk
//o------------------------------------------------------------------------------------------------o
void cEffects::LoadEffects( void )
{
std::ifstream input;
std::string filename = cwmWorldState->ServerData()->Directory( CSDDP_SHARED ) + "effects.wsc";
input.open( filename.c_str(), std::ios_base::in );
input.seekg( 0, std::ios::beg );
std::string tag;
std::string data;
std::string UTag;
if( input.is_open() )
{
CTEffect *toLoad;
char line[1024];
while( !input.eof() && !input.fail() )
{
input.getline(line, 1023);
line[input.gcount()] = 0;
std::string sLine( line );
sLine = oldstrutil::trim( oldstrutil::removeTrailing( sLine, "//" ));
auto usLine = oldstrutil::upper( sLine );
if( !sLine.empty() )
{
if( usLine == "[EFFECT]" )
{
toLoad = new CTEffect;
while( tag != "o---o" )
{
ReadWorldTagData( input, tag, data );
if( tag != "o---o" )
{
UTag = oldstrutil::upper( tag );
switch(( UTag.data()[0] ))
{
case 'A':
if( UTag == "ASSOCSCRIPT" )
{
toLoad->AssocScript( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )));
}
break;
case 'D':
if( UTag == "DEST" )
{
toLoad->Destination( static_cast<UI32>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )));
}
if( UTag == "DISPEL" )
{
toLoad->Dispellable((( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )) == 0 ) ? false : true ));
}
break;
case 'E':
if( UTag == "EXPIRE" )
{
toLoad->ExpireTime( static_cast<UI32>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )) + cwmWorldState->GetUICurrentTime() );
}
break;
case 'I':
if( UTag == "ITEMPTR" )
{
SERIAL objSer = static_cast<UI32>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 ));
if( objSer != INVALIDSERIAL )
{
if( objSer < BASEITEMSERIAL )
{
toLoad->ObjPtr( CalcCharObjFromSer( objSer ));
}
else
{
toLoad->ObjPtr( CalcItemObjFromSer( objSer ));
}
}
else
{
toLoad->ObjPtr( nullptr );
}
}
break;
case 'M':
if( UTag == "MORE1" )
{
toLoad->More1( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )));
}
if( UTag == "MORE2" )
{
toLoad->More2( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )));
}
if( UTag == "MORE3" )
{
toLoad->More3( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )));
}
break;
case 'N':
if( UTag == "NUMBER" )
{
toLoad->Number( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )));
}
break;
case 'O':
if( UTag == "OBJPTR" )
{
SERIAL objSer = static_cast<UI32>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 ));
if( objSer != INVALIDSERIAL )
{
if( objSer < BASEITEMSERIAL )
{
toLoad->ObjPtr( CalcCharObjFromSer( objSer ));
}
else
{
toLoad->ObjPtr( CalcItemObjFromSer( objSer ));
}
}
else
{
toLoad->ObjPtr( nullptr );
}
}
break;
case 'S':
if( UTag == "SOURCE" )
{
toLoad->Source( static_cast<UI32>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )));
}
break;
default:
Console.Error( oldstrutil::format( "Unknown effects tag %s with contents of %s", tag.c_str(), data.c_str() ));
break;
}
}
}
cwmWorldState->tempEffects.Add( toLoad );
tag = "";
}
}
}
input.close();
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CTEffect::Save()
//| Date - March 16, 2002
//o------------------------------------------------------------------------------------------------o
//| Purpose - Output the effect data to the effect destination. Returns true/false
//| indicating the success of the write operation
//o------------------------------------------------------------------------------------------------o
bool CTEffect::Save( std::ostream &effectDestination ) const
{
CBaseObject *getPtr = nullptr;
const char newLine = '\n';
effectDestination << "[EFFECT]" << newLine;
// Hexadecimal Values
effectDestination << std::hex;
effectDestination << "Source=0x" << Source() << newLine;
effectDestination << "Dest=0x" << Destination() << newLine;
getPtr = ObjPtr();
effectDestination << "ObjPtr=0x";
if( ValidateObject( getPtr ))
{
effectDestination << getPtr->GetSerial() << newLine;
}
else
{
effectDestination << INVALIDSERIAL << newLine;
}
// Decimal / String Values
effectDestination << std::dec;
effectDestination << "Expire=" + std::to_string( ExpireTime() - cwmWorldState->GetUICurrentTime() ) + newLine;
effectDestination << "Number=" + std::to_string( Number() ) + newLine;
effectDestination << "More1=" + std::to_string( More1() ) + newLine;
effectDestination << "More2=" + std::to_string( More2() ) + newLine;
effectDestination << "More3=" + std::to_string( More3() ) + newLine;
effectDestination << "Dispel=" + std::to_string( Dispellable() ? 1 : 0 ) + newLine;
effectDestination << "AssocScript=" + std::to_string( AssocScript() ) + newLine;
effectDestination << "\no---o\n\n";
return true;
}