uox3/source/effect.cpp
Xoduz 68e57b0756 UOX3 0.99.5b-6
Increased world load speed by almost 20% by reducing how often loading progress is written to the console
Increased world save speed by almost 50% through the use of binary mode and buffer tweaks for ofstream combined with a cached newline char and std::to_string instead of static_cast
Fixed a server crash involving killing NPCs of a specific spawn region using the 'spawnkill # command
Fixed a server crash involving moving items from decaying corpses to the ground
Fixed a server crash involving carving up human corpses
Fixed a server crash related to AreaCharacterFunction JS function, which could trigger for any script that potentially kills any NPC this function iterates over (like explosion potions)
Fixed a server crash related to GMs attempting to add items from add-menu while having no backpack
Fixed a client crash issue caused by attempting to sell house deeds back to architect NPCs
Fixed an issue where players would get incorrect amounts of gold for selling house deeds back to architect NPCs
Fixed an issue with characters dying onboard boats which prevented players from packing up the boats later
Added new item property in code (via bools bitset) that can be used to enable/disable maker's mark on crafted items. This property can be set to true for items crafted at exceptional quality by GM craftsmen
Exposed the new item property to JS engine:
	.isMarkedByMaker	 	// If true, maker's mark will be displayed
Updated default item tooltip to display " of exceptional quality" for items crafted with exceptional quality
Updated default item tooltip to display "[Crafted] by [Crafter's Name]" for exceptional quality items crafted by GM craftsmen. The [Crafted] text comes from the MADEWORD defined in dfndata/skills/skills.dfn for the primary skill used to craft the item
Updated old-school single-click names for items (when AoS bit is disabled in client/server features) to display exceptional status and/or maker's mark for appropriate items
Fixed an issue with the display of titles for NPCs that used dictionary lookups for their title
Added new UOX.INI settings under the [settings] section:
	DISPLAYMAKERSMARK=1/0			// Controls if maker's marks on crafted items are shown on a global level
	SHOWNPCTITLESOVERHEAD=1/0 		// Controls whether NPC titles are shown over their heads
	SHOWINVULNERABLETAGOVERHEAD=0/1 // Controls whether invulnerable tags are shown overhead
	GLOBALRESTOCKMULTIPLIER=1.0 	// Global multiplier applied to RESTOCK property of items when loaded from DFNs
Added new UOX.INI settings under the [combat] section:
	PETCOMBATTRAINING=1/0 			// Controls whether pets can gain skills/stats from combat
	HIRELINGCOMBATTRAINING=1/0 		// Controls whether hirelings can gain skills/stats from combat
	NPCCOMBATTRAINING=0/1 			// Controls whether NPCs in general can gain skills/stats from combat
Fixed an issue where a player meeting the exact minimum requirements for crafting an item would always fail
Fixed a bug with teleporting off boats to valid nearby locations when double-clicking boat planks while standing on it
Updated behaviour of planks on boats:
	Boat key can unlock plank, but won't automatically open it
	Boat key used on an open, unlocked plank will lock and automatically close the plank
	Boat key used on an open, locked plank will unlock the plank and leave it open
	Player using an unlocked plank now opens it
	Player using an open plank will disembark (if onboard) or embark (if not) the boat
	Player can always open a locked plank if they're on the boat, even with no key, to allow disembarking the boat.
	Planks that are open, but locked cannot be accessed from outside the boat, and will close automatically after five of seconds
Updated behaviour of Magic Lock/Unlock spells:
	Magic Lock cannot be used on objects inside a multi
	Magic Lock applies difficulty to lock based on caster's Magery skill
	Magic Lock effect will last between 7 to 50 seconds (depending on caster's Magery skill)
	Any object that can be locked via Magic Lock spell can also be unlocked via Magic Unlock spell, if caster has high enough Magery skill
Fixed a bug where certain JS Functions, Methods and Properties would occasionally pass incorrect values to scripts that called upon them. Scripts that rely on the GetCurrentClock() Function, the .GetTimer() Method, .decayTime, .logTime, .oreTime and .fishTime Properties might need to be updated to account for the fix, which causes larger values to be passed in than before.
Fixed a bug related to GetCurrentClock() that caused decay and respawning of dungeon chests from working properly
Fixed a bug related to GetCurrentClock() and .logTime/.fishTime that prevented consumption and regeneration of log/fish resources from working properly
Added a new JS Function:
	GetStartTime()	// Returns a timestamp for when server started up
Fixed a bug related to GetCurrentClock() where server uptime stats in help menu was showing up incorrectly
Fixed a bug related to GetCurrentClock() and purging of registrations of visitors in public houses so their visits can be counted again later
Fixed a bug related to GetCurrentClock() that caused monster speech to not work properly
Fixed a Z-related issue that sometimes prevented players from fishing when standing on the shoreline next to the sea
Fixed an issue with Animal Lore skill that would always return a pet's loyalty level as "Wild"
Increased max range pets can be from their owner in order to still be included in moongate/teleporter travel from 12 to 24 tiles
Fixed an issue where the alchemy bonus damage for explosion potions was off by an order of magnitude
Fixed an issue where food IDs from foodlists referenced by other foodlists was not detected as valid food for tamed pets or NPCs
Fixed an issue where Alchemy crafting skill did not properly check for the existence of the crafting tool before proceeding with the crafting process
Fixed an issue that prevented the Teleport spell from working as intended
Fixed an issue where players would still get 1 gold even if STARTGOLD in uox.ini was set to 0
Fixed an issue with mount restrictions script that prevented players from mounting Unicorns, Ki-rins and Cu Sidhes
Fixed an issue where aggressive creatures wouldn't stop attacking player after being tamed (js/skill/taming.js)
Added a system message to inform the player when they've dropped out of stealth due to taking too many steps
Fixed an issue where hunger level of NPCs instantly dropped upon spawning because hunger timer was not active yet
Fixed a bug where character corpses would appear with a [unidentified] tag attached
Fixed an issue where the [unidentified] tag did not show up for unidentified magic items
Added ANIMAL tag to a small bunch of creature definitions (dfndata/creatures/creatures.dfn)
Fixed an issue with Bowcraft skill where player could not gain enough skill from crafting bows to reach min requirement for crossbows, and similiarly could not reach min requirement for heavy crossbows from crossbows
Updated default value of MURDERDECAYTIMER INI setting from 60 seconds to 28800 seconds (8 hours), to match the amount of time it would take for one (short term) murder count to decay on LBR-era OSI shards.
2022-08-25 02:13:00 +08:00

1742 lines
60 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 - void deathAction( CChar *s, CItem *x, UI08 fallDirection )
//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 - void SpawnBloodEffect( UI16 bloodColour )
//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 - void 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 )
{ //0x0f 0x42 = arrow 0x1b 0xfe=bolt
if( !ValidateObject( source ) || !ValidateObject( dest ) )
return;
CPGraphicalEffect2 toSend( 0, (*source), (*dest) );
if( source == dest )
toSend.Effect( 0x03 );
toSend.Model( effect );
toSend.SourceLocation( (*source) );
toSend.TargetLocation( (*dest) );
toSend.Speed( speed );
toSend.Duration( loop );
toSend.ExplodeOnImpact( explode );
toSend.Hue( hue );
toSend.RenderMode( renderMode );//0x00000004
for(auto &tSock : Network->connClients){
if( objInRange( tSock, source, DIST_SAMESCREEN ) && objInRange( tSock, dest, DIST_SAMESCREEN ) )
tSock->Send( &toSend );
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void 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 - void 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 - void PlayCharacterAnimation( CChar *mChar, UI16 actionID, UI08 frameDelay, bool playBackwards )
//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 - void PlayNewCharacterAnimation( CChar *mChar, UI16 actionID, UI16 subActionID, UI08 subSubActionID )
//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 - void PlaySpellCastingAnimation( CChar *mChar, UI16 actionID )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Handles spellcasting action
//o-----------------------------------------------------------------------------------------------o
void cEffects::PlaySpellCastingAnimation( CChar *mChar, UI16 actionID )
{
if( mChar->GetBodyType() == BT_GARGOYLE || ( cwmWorldState->ServerData()->ForceNewAnimationPacket()
&& ( mChar->GetSocket() == nullptr || mChar->GetSocket()->ClientType() >= CV_SA2D )))
{
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!
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() )
{
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;
}
if( !cwmWorldState->creatures[mChar->GetID()].IsHuman() && actionID == 0x22 )
return;
PlayCharacterAnimation( mChar, actionID, 0, 7 );
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void 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 - void 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 - void bolteffect( CChar *player )
//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 - void explodeItem( CSocket *mSock, CItem *nItem, UI32 damage, UI08 damageType )
//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, 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() ){
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( tempItem->GetInstanceID() == nItem->GetInstanceID() ) {
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 - void HandleMakeItemEffect( CTEffect *tMake )
//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 *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<std::uint16_t>( 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 GM, 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 - void checktempeffects( void )
//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 ){
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() );
PlaySound( s, 0x0242 );
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 );
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<UI08>(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(),[this](CTEffect *effect) {
cwmWorldState->tempEffects.Remove(effect,true);
});
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void reverseEffect( CTEffect *Effect )
//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 - void tempeffect( CChar *source, CChar *dest, UI08 num, UI16 more1, UI16 more2, UI16 more3, CItem *targItemPtr )
//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, 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(), [this](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( (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( (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( (R32)source->GetSkill( MAGERY ) / 10.0f ) );
}
toAdd->More1( more1 );
toAdd->Dispellable( true );
break;
case 9: // Grind Necro Reagent
toAdd->ExpireTime( BuildTimeValue( (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( (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( (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( (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( (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( ( (R32)more1 + (R32)more2 / 1000.0f ) ) );
toAdd->More1( more3 );
break;
case 41: // creating item
toAdd->ExpireTime( BuildTimeValue( (R32)(more1) / 100.0f ) );
toAdd->More2( more2 );
break;
case 42: // delayed sound effect for crafting
toAdd->ExpireTime( BuildTimeValue( (R32)(more1) / 100.0f ) );
toAdd->More2( more2 );
break;
case 43: // regain wool for sheeps (JS)
toAdd->ExpireTime( BuildTimeValue( (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 - void 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, 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 - void SaveEffects( void )
//| 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", ((R32)(e_t-s_t))/1000.0f) );
}
void ReadWorldTagData( std::ifstream &inStream, std::string &tag, std::string &data );
//o-----------------------------------------------------------------------------------------------o
//| Function - void LoadEffects( void )
//| 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 - bool Save( std::ofstream &effectDestination ) const
//| 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::ofstream &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;
}