#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 //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 R64 bloodDecayTimer = static_cast( cwmWorldState->ServerData()->SystemTimer( tSERVER_BLOODDECAY )); // Blood effects, sorted by size of effect from small to large std::vector 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( cwmWorldState->ServerData()->SystemTimer( tSERVER_BLOODDECAYCORPSE )); // Randomize between large blood effects bloodEffectId = bloodIds[RandomNum( static_cast( 3 ), static_cast( 6 ))]; break; case BLOOD_BLEED: // Randomize between small blood effects bloodEffectId = bloodIds[RandomNum( static_cast( 0 ), static_cast( 3 ))]; break; case BLOOD_CRITICAL: // Randomize between medium to large blood effects bloodEffectId = bloodIds[RandomNum( static_cast( 2 ), static_cast( 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( cwmWorldState->creatures[mChar->GetId()].CastAnimAreaId() ); castAnimLength = cwmWorldState->creatures[mChar->GetId()].CastAnimAreaLength(); } else { castAnim = static_cast( 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( damage ) + ( 2 - std::min( dx, dy ))), true ); [[maybe_unused]] bool retVal = tempChar->Damage( static_cast( 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( damage ), true ); [[maybe_unused]] bool retVal = c->Damage( static_cast( 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( 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( 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( rank ), static_cast( 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 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 TIMERVAL j = cwmWorldState->GetUICurrentTime(); std::vector 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 || Effect->PauseTime() > 0 ) 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( 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( 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.0 )); 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( 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(( static_cast( myObj ))->GetType() ))) { scpNum = JSMapping->GetEnvokeByType()->GetScript( static_cast(( static_cast( 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( 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( 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 - PauseEffect() //o------------------------------------------------------------------------------------------------o //| Purpose - Pause a temp effect //o------------------------------------------------------------------------------------------------o void PauseEffect( CTEffect *Effect ) { // Store timestamp for when effect was paused // We'll use this later when we resume the effect // That's all! Effect->PauseTime( BuildTimeValue( 0.0 )); } //o------------------------------------------------------------------------------------------------o //| Function - ResumeEffect() //o------------------------------------------------------------------------------------------------o //| Purpose - Resume a temp effect that has been paused //o------------------------------------------------------------------------------------------------o void ResumeEffect( CTEffect *Effect ) { // Let's resume the effect - if it's paused auto pauseTime = Effect->PauseTime(); if( pauseTime > 0 ) { // Effect IS paused, let's resume it and prolong the original timer by the amount of time it's been paused const TIMERVAL currTime = cwmWorldState->GetUICurrentTime(); auto expireTime = Effect->ExpireTime(); expireTime = expireTime + ( currTime - pauseTime ); Effect->ExpireTime( expireTime ); Effect->PauseTime( 0 ); } } //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 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 ); dest->Dirty( UT_UPDATE ); R64 effectDuration = 0; if( source == nullptr ) { effectDuration = static_cast( more2 ); if( more3 != 0 ) { // Divide duration by more3 value effectDuration /= static_cast( more3 ); } // Keep a reference to more1, if different than 0 if( more1 != 0 ) { toAdd->More1( more1 ); } } else { effectDuration = static_cast( source->GetSkill( MAGERY )) / 100.0; } 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( worldbrightlevel )); DoLight( tSock, static_cast( worldbrightlevel )); R64 effectDuration = 0; if( source == nullptr ) { effectDuration = static_cast( more2 ); } else { effectDuration = static_cast( source->GetSkill( MAGERY )) / 2.0; } 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() )); R64 effectDuration = 0; if( source == nullptr ) { // Use duration provided with effect call effectDuration = static_cast( more2 ); // Halve effect-duration on resist spellResisted = Magic->CheckResist( more3, dest, 1 ); } else { effectDuration = static_cast( source->GetSkill( MAGERY ) / 10.0 ); //Halve effect-timer on resist spellResisted = Magic->CheckResist( source, dest, 1 ); } if( spellResisted ) { toAdd->ExpireTime( BuildTimeValue( effectDuration / 2.0 )); } 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() )); R64 effectDuration = 0; if( source == nullptr ) { // Use duration provided with effect call effectDuration = static_cast( more2 ); // Halve effect-duration on resist spellResisted = Magic->CheckResist( more3, dest, 3 ); } else { effectDuration = static_cast( source->GetSkill( MAGERY ) / 10.0 ); //Halve effect-timer on resist spellResisted = Magic->CheckResist( source, dest, 1 ); } if( spellResisted ) { toAdd->ExpireTime( BuildTimeValue( effectDuration / 2.0 )); } 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( dest->GetMaxHP() ))); R64 effectDuration = 0; if( source == nullptr ) { // Use duration provided with effect call effectDuration = static_cast( more2 ); // Halve effect-duration on resist spellResisted = Magic->CheckResist( more3, dest, 1 ); } else { effectDuration = static_cast( source->GetSkill( MAGERY ) / 10.0 ); //Halve effect-timer on resist spellResisted = Magic->CheckResist( source, dest, 1 ); } //Halve effect-timer on resist if( spellResisted ) { toAdd->ExpireTime( BuildTimeValue( effectDuration / 2.0 )); } 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( more2 ))); } else { toAdd->ExpireTime( BuildTimeValue( static_cast( source->GetSkill( MAGERY )) / 10.0 )); } 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( more2 ))); } else { toAdd->ExpireTime( BuildTimeValue( static_cast( source->GetSkill( MAGERY )) / 10.0 )); } 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( more2 ))); } else { toAdd->ExpireTime( BuildTimeValue( static_cast( source->GetSkill( MAGERY )) / 10.0 )); } toAdd->More1( more1 ); toAdd->Dispellable( true ); break; case 9: // Grind Necro Reagent toAdd->ExpireTime( BuildTimeValue( static_cast( more2 ))); toAdd->More1( more1 ); toAdd->More2( more2 ); toAdd->Dispellable( false ); break; case 10: // ??? toAdd->ExpireTime( BuildTimeValue( 12.0 )); 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( source->GetSkill( MAGERY )) / 10.0 )); toAdd->More1( more1 ); toAdd->More2( more2 ); toAdd->More3( more3 ); } toAdd->Dispellable( true ); break; case 12: // Curse Spell { bool spellResisted = false; R64 effectDuration = 0.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( 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( source->GetSkill( MAGERY )) / 10.0; //Halve effect-timer on resist spellResisted = Magic->CheckResist( source, dest, 4 ); } if( spellResisted ) { toAdd->ExpireTime( BuildTimeValue( effectDuration / 2.0 )); } else { toAdd->ExpireTime( BuildTimeValue( effectDuration )); } toAdd->Dispellable( true ); break; } case 15: // Reactive Armor Spell if( source != nullptr ) { toAdd->ExpireTime( BuildTimeValue( static_cast( source->GetSkill( MAGERY )) / 10.0 )); } else { toAdd->ExpireTime( BuildTimeValue( 6.0 )); } toAdd->Dispellable( true ); break; case 16: // Explosion potion messages (JS) toAdd->ExpireTime( BuildTimeValue( static_cast( 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.0 )); // 90 seconds toAdd->Dispellable( false ); break; case 21: // Protection Spell { R64 effectDuration = 0.0; if( source == nullptr ) { effectDuration = static_cast( more2 ); } else { effectDuration = 120.0; } 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( 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( Magic->spells[43].DamageDelay() ))); toAdd->More1( more1 ); break; case 28: // Magic Arrow Spell toAdd->ExpireTime( BuildTimeValue( static_cast( Magic->spells[5].DamageDelay() ))); toAdd->More1( more1 ); break; case 29: // Harm Spell toAdd->ExpireTime( BuildTimeValue( static_cast( Magic->spells[12].DamageDelay() ))); toAdd->More1( more1 ); break; case 30: // Fireball Spell toAdd->ExpireTime( BuildTimeValue( static_cast( Magic->spells[18].DamageDelay() ))); toAdd->More1( more1 ); break; case 31: // Lightning Spell toAdd->ExpireTime( BuildTimeValue( static_cast( Magic->spells[30].DamageDelay() ))); toAdd->More1( more1 ); break; case 32: // Mind Blast Spell toAdd->ExpireTime( BuildTimeValue( static_cast( Magic->spells[37].DamageDelay() ))); toAdd->More1( more1 ); break; case 33: // Energy Bolt Spell toAdd->ExpireTime( BuildTimeValue( static_cast( Magic->spells[42].DamageDelay() ))); toAdd->More1( more1 ); break; case 34: // Chain Lightning Spell toAdd->ExpireTime( BuildTimeValue( static_cast( Magic->spells[49].DamageDelay() ))); toAdd->More1( more1 ); break; case 35: // Flamestrike Spell toAdd->ExpireTime( BuildTimeValue( static_cast( Magic->spells[51].DamageDelay() ))); toAdd->More1( more1 ); break; case 36: // Meteor Swarm toAdd->ExpireTime( BuildTimeValue( static_cast( Magic->spells[55].DamageDelay() ))); toAdd->More1( more1 ); break; case 40: // Used by JS timers toAdd->ExpireTime( BuildTimeValue(( static_cast( more1 ) + static_cast( more2 )) / 1000.0 )); toAdd->More1( more3 ); break; case 41: // creating item toAdd->ExpireTime( BuildTimeValue( static_cast( more1 ) / 100.0 )); toAdd->More2( more2 ); break; case 42: // delayed sound effect for crafting toAdd->ExpireTime( BuildTimeValue( static_cast( more1 ) / 100.0 )); toAdd->More2( more2 ); break; case 43: // regain wool for sheeps (JS) toAdd->ExpireTime( BuildTimeValue( static_cast( 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.0 )); 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.0 )); toAdd->Dispellable( false ); break; case 17: // Explosion potion (explosion) Tauriel (explode in 4 seconds) toAdd->ExpireTime( BuildTimeValue( 4.0 )); toAdd->More1( more1 ); toAdd->More2( more2 ); toAdd->ObjPtr( dest ); toAdd->Dispellable( false ); break; case 25: // // Temporarily set item as disabled toAdd->ExpireTime( BuildTimeValue( static_cast( 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( static_cast( 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"; TIMERVAL 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(); TIMERVAL e_t = GetClock(); Console.Print( oldstrutil::format("Effects saved in %.02fsec\n", ( static_cast( 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( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 ))); } break; case 'D': if( UTag == "DEST" ) { toLoad->Destination( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 ))); } if( UTag == "DISPEL" ) { toLoad->Dispellable((( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )) == 0 ) ? false : true )); } break; case 'E': if( UTag == "EXPIRE" ) { toLoad->ExpireTime( static_cast( std::stoull( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )) + cwmWorldState->GetUICurrentTime() ); } break; case 'I': if( UTag == "ITEMPTR" ) { SERIAL objSer = static_cast( 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( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 ))); } if( UTag == "MORE2" ) { toLoad->More2( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 ))); } if( UTag == "MORE3" ) { toLoad->More3( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 ))); } break; case 'N': if( UTag == "NUMBER" ) { toLoad->Number( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 ))); } break; case 'O': if( UTag == "OBJPTR" ) { SERIAL objSer = static_cast( 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 'P': if( UTag == "PAUSE" ) { auto pauseTime = static_cast( std::stoull( oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0 )); toLoad->PauseTime( pauseTime > 0 ? pauseTime + cwmWorldState->GetUICurrentTime() : 0 ); } break; case 'S': if( UTag == "SOURCE" ) { toLoad->Source( static_cast( 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 << "Pause=" + std::to_string( PauseTime() > 0 ? PauseTime() - cwmWorldState->GetUICurrentTime() : 0 ) + 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; }