uox3/source/magic.cpp
Xoduz a48d9ee299 UOX3 0.99.6-RC6
Removed arbitrary and hidden restrictions on refreshing list of online players shown with 'wholist command
Fixed an issue where player ghosts that get teleported would not see themselves get updated to new location
Fixed a server crash from clients disconnecting while running server in debug mode
Improved how container updates are sent to players; now only sends to clients that have actually opened the container, and who are still within range
Added more information about UO data files being loaded during server startup
Enhanced the GM 'add menu to allow clicking directly on images of items/npcs to add them, and adjusted size of menus to accommodate this
Fixed an issue with fleeing NPCs that could cause them to attempt to flee across time and space, regardless of distance to target/attacker in combat
Added NPC AI that runs away from players if they get too close (even outside combat). Applied to hind, rabbits, squirrels, ferrets and various birds by default:
	AI_ANIMAL_SCARED (aitype 12)
Fixed issues with NPCs fleeing forever, or getting stuck in flee/don't flee loop, by introducing max fleeing distance (50 tiles) and cooldown (30 sec) on fleeing
Updated onNameRequest JS Event to include third parameter with origin of name request, so script responses can be tailored appropriately:
	onNameRequest( myObj, requestedBy, requestSource )
		Potential values for requestSource:
			0 - Speech/System Messages
			1 - Guild Menus
			2 - Stat Window (self)
			3 - Stat Window (other)
			4 - Tooltip
			5 - Paperdoll Journal
			6 - Paperdoll
			7 - Single Click / All-Names
			8 - System
			9 - Secure Trade Window
Added Adaptive Performance System (APS) that dynamically adjust how often NPC AI/movement is checked based on overall shard performance. If performance drops below defined threshold, UOX3 gradually slows down checks for NPC AI/movement to prioritize player movement/speech/command responsiveness. If performance climbs back up above threshold, slowdowns are gradually removed. The following UOX.INI options have been added under [system] category to support this system:
	APSPERFTHRESHOLD=50 	// Performance threshold (measured in simulation cycles/sec) below which the APS kicks in
	APSINTERVAL=100 		// How often (in milliseconds) the APS checks performance and makes adjustments (if needed) to balance out shard performance
	APSDELAYSTEP=50 		// How much the delay timer is modified by (in milliseconds) each time APS makes adjustments
	APSDELAYMAXCAP=2000 	// Max amount of of delay APS can introduce for NPC AI/movement handling when attempting to restore shard performance
Added new JS Methods for Region objects:
	.GetOrePrefs( oreType )	// Get ore preference data for ore type found in town region. Returned as an array containing the following data:
		orePrefData -> [
			oreName,	// name of ore
			color,		// color of ore
			minskill,	// min skill to mine ore
			ingotName,	// name of ingot created from ore
			makemenu,	// makemenu entry for crafting something from ingot
			oreChance,	// default global chance of finding this ore type
			scriptID 	// script attached to mined ore
		],
		orePrefChance 	// Chance of finding this ore type in given town region
	.GetOreChance() 	// Get base chance of finding any ore in town region
Moved Mining skill plus gravedigging feature from hard code to scripts (js/skill/mining.js) to make it easier to maintain and/or customize, and removed hard coded variants
Gravedigging now relies on the ore resource system behind the scenes to restrict how often graves in a given area can be dug up
Fixed some incorrect references to .worldNumber Character property in misc scripts (should be .worldnumber)
Made some "hard-scripted" system messages in misc scripts use dictionary system instead
Added spawn region for banker NPCs in Serpent's Hold (dfndata/spawn/felucca/spawn_town_serpents_hold.dfn, dfndata/spawn/trammel/spawn_town_serpents_hold.dfn)
Fixed region definition of Ocllo to actually cover the entire town (dfndata/regions/regions.dfn)
Added numerous additional locations accessible with 'goplace # command, for key locations in Ilshenar, Malas, Tokuno Islands and Ter Mur (dfndata/location/location.dfn)
Revised travel-menu portion of GM menu (shortcut: 'travel) to include more travel options based on new locations (dfndata/items/travelmenu.dfn and travelmenu.bulk.dfn)
Fixed an issue where NPCs could attempt to attack targets in other worlds/instances
Added new JS Event that triggers for characters who are about to deal damage in combat. Complimentary to onDamage, which triggers for chars receiving damage:
	onDamageDeal( dmgDealer, dmgReceiver, damageValue, damageType )
Added new JS Event that can trigger in global script upon creation of new player chars. Note that this will trigger in place of onCreateDFN event for player characters:
	onCreatePlayer( pChar )
Added new JS Event that triggers for characters selecting a target with a spell. Complimentary to onSpellTarget, which triggers for targets selected with a spell:
	onSpellTargetSelect( caster, target, spellNum )
Updated onCombatStart and onCombatEnd JS Events to also trigger for the other party in combat
Updated onPickup JS Event to include a third parameter - the potential container item was picked up from. Event now also triggers event in scripts attached to said containers:
	onPickup( iPickedUp, pChar, iCont )
Added tracking of total playtime per individual character, and per account across all characters, and exposed these properties to JS engine:
	.totalPlayTime		// Account property, total playtime across all chars
	.playTime 			// Character property, total playtime on given character only
Added new player-accessible command ('playtime) to spit out the playtime of current character/account as a whole (js/commands/playtime.js)
Implemented first version of Young Player System:
	Replaced the UNUSED9 account flag with YOUNG, to be used by Young/New Player System
	Added new JS Account property that gets/sets whether player account is considered Young:
		.isYoung
	Added new Char timers:
		TIMER_YOUNGHEAL		// Restricts how often Young players are healed by NPC healers
		TIMER_YOUNGMESSAGE	// Restricts how often Young players are warned about dangerous looking monsters in overworld
	Updated GM commands 'get and 'set to get/set .isYoung property of player's account (js/commands/targeting/get.js and set.js)
	Added new UOX.INI setting to enable/disable Young Player System (enabled by default):
		YOUNGPLAYERSYSTEM=1
	If Young Player System is enabled, all newly created player accounts are automatically marked with Young flag
	Added new script to handle various restrictions and functions related to Young characters (js/player/young_player.js):
		Young characters will have [Young] displayed over their head
		Young characters will have their Young status checked and verified on every login + every stat/skill gain, to revoke the Young status if any of the following is true:
			Account has a total playtime of more than 40 hours
			Any character has more than 350 total (base) skill points
			Any character has more than 70 skill points in a single skill
			Any character has more than 150 total stat points
			Any character has more than 80 stat points in a single stat
		Young characters can renounce their Young status manually by saying the words "I renounce my young player status"
		Young characters get two additional items upon creation:
			a new player ticket (can be combined with any other players new player ticket for both players to get a reward)
			a sextant (shows Young players directions to nearest moongate/bank regardless of where they are, as long as outdoors)
		Young characters cannot target other player characters with hostile spells or skills
		Young characters cannot be the target of other player characters' hostile spells or skills
		Young characters can only cast beneficial spells upon themselves or other Young characters
		Young characters can only be the target of beneficial spells from other Young players
		Young players (and their pets/followers) cannot attack or harm any other players (or their pets/followers), nor themselves be attacked or harmed by any other players (or their pets/followers), whether from combat, spells, skills or items
		Young players don't lose any of their items on death, and are teleported to the nearest healer upon death with all their items intact
		Young players receive a warning upon entering dungeons that monsters there will be hostile
	Updated global script (js/server/global.js) to:
		Check/Verify Young status of players upon login
		Attach young player script on login/creation for players on Young accounts
		Give specific items to Young players upon creation
	Added new script (js/item/corpse.js) that is assigned to all freshly created corpses. This script handles restrictions related to Young players interactions with corpses, and the interactions of other players with corpses of Young players
	Updated JS Method .Carve() to return true/false depending on whether carving a corpse was successful
	Updated Healer AI in code to heal nearby injured Young players
	Updated Evil AI and Evil Caster AI in code to avoid selecting Young players as targets in combat outside of dungeons
	Added a new section to UOX.INI - [young player starting locations] - that can be used to define starting locations for Young players. If only one such location is provided, all players start there. Otherwise, it matches the same starting location setup as the regular one - with one entry per town in Britannia.
	Restricted transferring and/or friending of pets (code) and hirelings (hirelings.js) between Young and non-Young players
	Restricted Young players from recalling or gating to Felucca facet
	Young players can instantly logout from anywhere, at any time
2023-07-01 20:02:40 +08:00

5425 lines
185 KiB
C++

#include "uox3.h"
#include "magic.h"
#include "power.h"
#include "weight.h"
#include "cGuild.h"
#include "townregion.h"
#include "cRaces.h"
#include "cServerDefinitions.h"
#include "cMagic.h"
#include "skills.h"
#include "ssection.h"
#include "CJSMapping.h"
#include "cScript.h"
#include "cEffects.h"
#include "CPacketSend.h"
#include "classes.h"
#include "regions.h"
#include "combat.h"
#include "Dictionary.h"
#include "movement.h"
#include "scriptc.h"
#include "StringUtility.hpp"
#include "ObjectFactory.h"
#include <algorithm>
#include "osunique.hpp"
CMagic *Magic = nullptr;
#define SPELL_MAX 68 // use define for now; can make autocount later
// Look up spell-names from dictionary-files
const MagicTable_st magic_table[] = {
{ 593, (MAGIC_DEFN)&splClumsy },
{ 594, (MAGIC_DEFN)&splCreateFood },
{ 595, (MAGIC_DEFN)&splFeeblemind },
{ 596, (MAGIC_DEFN)&splHeal },
{ 597, (MAGIC_DEFN)&splMagicArrow },
{ 598, (MAGIC_DEFN)&splNightSight },
{ 599, (MAGIC_DEFN)&splReactiveArmor },
{ 600, (MAGIC_DEFN)&splWeaken },
{ 601, (MAGIC_DEFN)&splAgility },
{ 602, (MAGIC_DEFN)&splCunning },
{ 603, (MAGIC_DEFN)&splCure },
{ 604, (MAGIC_DEFN)&splHarm },
{ 605, (MAGIC_DEFN)&splMagicTrap },
{ 606, (MAGIC_DEFN)&splMagicUntrap },
{ 607, (MAGIC_DEFN)&splProtection },
{ 608, (MAGIC_DEFN)&splStrength },
{ 609, (MAGIC_DEFN)&splBless },
{ 610, (MAGIC_DEFN)&splFireball },
{ 611, (MAGIC_DEFN)&splMagicLock },
{ 612, (MAGIC_DEFN)&splPoison },
{ 613, (MAGIC_DEFN)&splTelekinesis },
{ 614, (MAGIC_DEFN)&splTeleport },
{ 615, (MAGIC_DEFN)&splUnlock },
{ 616, (MAGIC_DEFN)&splWallOfStone },
{ 617, (MAGIC_DEFN)&splArchCure },
{ 618, (MAGIC_DEFN)&splArchProtection },
{ 619, (MAGIC_DEFN)&splCurse },
{ 620, (MAGIC_DEFN)&splFireField },
{ 621, (MAGIC_DEFN)&splGreaterHeal },
{ 622, (MAGIC_DEFN)&splLightning },
{ 623, (MAGIC_DEFN)&splManaDrain },
{ 624, (MAGIC_DEFN)&splRecall },
{ 625, (MAGIC_DEFN)&splBladeSpirits },
{ 626, (MAGIC_DEFN)&splDispelField },
{ 627, (MAGIC_DEFN)&splIncognito },
{ 628, (MAGIC_DEFN)&splMagicReflection },
{ 629, (MAGIC_DEFN)&splMindBlast },
{ 630, (MAGIC_DEFN)&splParalyze },
{ 631, (MAGIC_DEFN)&splPoisonField },
{ 632, (MAGIC_DEFN)&splSummonCreature },
{ 633, (MAGIC_DEFN)&splDispel },
{ 634, (MAGIC_DEFN)&splEnergyBolt },
{ 635, (MAGIC_DEFN)&splExplosion },
{ 636, (MAGIC_DEFN)&splInvisibility },
{ 637, (MAGIC_DEFN)&splMark },
{ 638, (MAGIC_DEFN)&splMassCurse },
{ 639, (MAGIC_DEFN)&splParalyzeField },
{ 640, (MAGIC_DEFN)&splReveal },
{ 641, (MAGIC_DEFN)&splChainLightning },
{ 642, (MAGIC_DEFN)&splEnergyField },
{ 643, (MAGIC_DEFN)&splFlameStrike },
{ 644, (MAGIC_DEFN)&splGateTravel },
{ 645, (MAGIC_DEFN)&splManaVampire },
{ 646, (MAGIC_DEFN)&splMassDispel },
{ 647, (MAGIC_DEFN)&splMeteorSwarm },
{ 648, (MAGIC_DEFN)&splPolymorph },
{ 649, (MAGIC_DEFN)&splEarthquake },
{ 650, (MAGIC_DEFN)&splEnergyVortex },
{ 651, (MAGIC_DEFN)&splResurrection },
{ 652, (MAGIC_DEFN)&splSummonAir },
{ 653, (MAGIC_DEFN)&splSummonDaemon },
{ 654, (MAGIC_DEFN)&splSummonEarth },
{ 655, (MAGIC_DEFN)&splSummonFire },
{ 656, (MAGIC_DEFN)&splSummonWater },
{ 657, (MAGIC_DEFN)&splRandom },
{ 658, (MAGIC_DEFN)&splNecro1 },
{ 659, (MAGIC_DEFN)&splNecro2 },
{ 660, (MAGIC_DEFN)&splNecro3 },
{ 661, (MAGIC_DEFN)&splNecro4 },
{ 662, (MAGIC_DEFN)&splNecro5 },
{ 0, nullptr }
};
//o------------------------------------------------------------------------------------------------o
//| Function - SpawnGate()
//| Date - 17th September, 2001
//o------------------------------------------------------------------------------------------------o
//| Purpose - It will construct 2 linked gates, one at srcX / srcY / srcZ and another at trgX / trgY / trgZ
//o------------------------------------------------------------------------------------------------o
void SpawnGate( CChar *caster, SI16 srcX, SI16 srcY, SI08 srcZ, UI08 srcWorld, SI16 trgX, SI16 trgY, SI08 trgZ, UI08 trgWorld, UI16 trgInstanceId )
{
CItem *g1 = Items->CreateItem( nullptr, caster, 0x0F6C, 1, 0, OT_ITEM );
if( ValidateObject( g1 ))
{
g1->SetDecayable( true );
g1->SetType( IT_GATE );
g1->SetLocation( srcX, srcY, srcZ, srcWorld, g1->GetInstanceId() );
g1->SetDecayTime( cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_GATE ));
g1->SetDir( 1 );
CItem *g2 = Items->CreateItem( nullptr, caster, 0x0F6C, 1, 0, OT_ITEM );
if( ValidateObject( g2 ))
{
g2->SetDecayable( true );
g2->SetType( IT_GATE );
g2->SetLocation( trgX, trgY, trgZ, trgWorld, trgInstanceId );
g2->SetDecayTime( cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_GATE ));
g2->SetDir( 1 );
g2->SetTempVar( CITV_MOREX, g1->GetSerial() );
g1->SetTempVar( CITV_MOREX, g2->GetSerial() );
}
else
{
g1->Delete();
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - FieldSpell()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Creates rows of items for field spells like firewalls, wall of stone, etc
//o------------------------------------------------------------------------------------------------o
bool FieldSpell( CChar *caster, UI16 id, SI16 x, SI16 y, SI08 z, UI08 fieldDir, SI08 spellNum )
{
SI16 fx[5], fy[5];
if( fieldDir )
{
fx[0] = fx[1] = fx[2] = fx[3] = fx[4] = x;
fy[0] = y - 1;
fy[1] = y + 1;
fy[2] = y - 2;
fy[3] = y + 2;
fy[4] = y;
}
else
{
fy[0] = fy[1] = fy[2] = fy[3] = fy[4] = y;
fx[0] = x - 1;
fx[1] = x + 1;
fx[2] = x - 2;
fx[3] = x + 2;
fx[4] = x;
}
CItem *i = nullptr;
UI08 worldNumber = caster->WorldNumber();
UI16 instanceId = caster->GetInstanceId();
for( UI08 j = 0; j < 5; ++j ) // how about we make this 5, huh? missing part of the field
{
if( id != 0x0080 || ( id == 0x0080 && !Movement->CheckForCharacterAtXYZ( caster, fx[j], fy[j], z )))
{
i = Items->CreateItem( nullptr, caster, id, 1, 0, OT_ITEM );
if( i != nullptr )
{
i->SetDispellable( true );
i->SetDecayable( true );
// Duration of field item based on caster's Magery skill
i->SetDecayTime( BuildTimeValue( static_cast<R32>( caster->GetSkill( MAGERY ) / 15 )));
// If Poison Field, use average of caster's Magery and Poisoning skills
if( spellNum == 39 ) // Poison Field
{
i->SetTempVar( CITV_MOREX, static_cast<UI16>(( caster->GetSkill( MAGERY ) + caster->GetSkill( POISONING )) / 2 ));
}
else
{
i->SetTempVar( CITV_MOREX, caster->GetSkill( MAGERY )); // remember casters magery skill for damage
}
// Store caster's serial in field item
i->SetTempVar( CITV_MOREY, caster->GetSerial() );
i->SetLocation( fx[j], fy[j], Map->Height( fx[j], fy[j], z, worldNumber, instanceId ));
i->SetDir( 29 );
i->SetMovable( 2 );
// Don't save these temporary effect items!
i->ShouldSave( false );
CMagicStat temp = Magic->spells[spellNum].StaticEffect();
if( temp.Effect() != INVALIDID )
{
Effects->PlayStaticAnimation( i, temp.Effect(), temp.Speed(), temp.Loop() );
}
}
}
}
return true;
}
bool splClumsy( [[maybe_unused]] CChar *caster, [[maybe_unused]] CChar *target, [[maybe_unused]] CChar *src, [[maybe_unused]] SI08 curSpell )
{
// This spell is now handled in JS/MAGIC/level1targ.js
return false;
}
bool splCreateFood( [[maybe_unused]] CSocket *sock, [[maybe_unused]] CChar *caster, [[maybe_unused]] SI08 curSpell )
{
// This spell is now handled in JS/MAGIC/createfood.js
return false;
}
bool splFeeblemind( [[maybe_unused]] CChar *caster, [[maybe_unused]] CChar *target, [[maybe_unused]] CChar *src, [[maybe_unused]] SI08 curSpell )
{
// This spell is now handled in JS/MAGIC/level1targ.js
return false;
}
bool splHeal( [[maybe_unused]] CChar *caster, [[maybe_unused]] CChar *target, [[maybe_unused]] CChar *src, [[maybe_unused]] SI08 curSpell )
{
// This spell is now handled in JS/MAGIC/level1targ.js
return false;
}
bool splMagicArrow( [[maybe_unused]] CChar *caster, [[maybe_unused]] CChar *target, [[maybe_unused]] CChar *src, [[maybe_unused]] SI08 curSpell )
{
// This spell is now handled in JS/MAGIC/level1targ.js
return false;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splNightSight()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Night Sight spell
//o------------------------------------------------------------------------------------------------o
bool splNightSight( CChar *caster, CChar *target, CChar *src, [[maybe_unused]] SI08 curSpell )
{
Effects->TempEffect( src, target, 2, 0, 0, 0 );
if( target->IsMurderer() )
{
MakeCriminal( caster );
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splReactiveArmor()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Reactive Armor spell
//o------------------------------------------------------------------------------------------------o
bool splReactiveArmor( CChar *caster, CChar *target, CChar *src, [[maybe_unused]] SI08 curSpell )
{
Effects->TempEffect( src, target, 15, caster->GetSkill( MAGERY ) / 100, 0, 0 );
target->SetReactiveArmour( true );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splWeaken()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Weaken spell
//o------------------------------------------------------------------------------------------------o
bool splWeaken( CChar *caster, CChar *target, CChar *src, [[maybe_unused]] SI08 curSpell )
{
Effects->TempEffect( src, target, 5, caster->GetSkill( MAGERY ) / 100, 0, 0 );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splAgility()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Agility spell
//o------------------------------------------------------------------------------------------------o
bool splAgility( CChar *caster, CChar *target, CChar *src, [[maybe_unused]] SI08 curSpell )
{
Effects->TempEffect( src, target, 6, caster->GetSkill( MAGERY ) / 100, 0, 0 );
if( target->IsMurderer() )
{
MakeCriminal( caster );
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splCunning()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Cunning spell
//o------------------------------------------------------------------------------------------------o
bool splCunning( CChar *caster, CChar *target, CChar *src, [[maybe_unused]] SI08 curSpell )
{
Effects->TempEffect( src, target, 7, caster->GetSkill( MAGERY ) / 100, 0, 0 );
if( target->IsMurderer() )
{
MakeCriminal( caster );
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splCure()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Cure spell
//o------------------------------------------------------------------------------------------------o
bool splCure( CChar *caster, CChar *target, [[maybe_unused]] CChar *src, [[maybe_unused]] SI08 curSpell )
{
target->SetPoisoned( 0 );
target->SetTimer( tCHAR_POISONWEAROFF, cwmWorldState->GetUICurrentTime() );
if( target->IsMurderer() )
{
MakeCriminal( caster );
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splHarm()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Harm spell
//o------------------------------------------------------------------------------------------------o
bool splHarm( CChar *caster, CChar *target, CChar *src, SI08 curSpell )
{
SI16 spellDamage = 0;
bool spellResisted = Magic->CheckResist( caster, target, Magic->spells[curSpell].Circle() );
spellDamage = Magic->spells[curSpell].BaseDmg();
spellDamage = CalcSpellDamageMod( caster, target, spellDamage, spellResisted );
Effects->TempEffect( src, target, 29, spellDamage, 0, 0 );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splMagicTrap()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Magic Trap spell
//o------------------------------------------------------------------------------------------------o
bool splMagicTrap( CSocket *sock, CChar *caster, CItem *target, [[maybe_unused]] SI08 curSpell )
{
if( target->IsContType() && target->GetId() != 0x0E75 )
{
target->SetTempVar( CITV_MOREZ, CalcSerial( 1, caster->GetSkill( MAGERY ) / 200, caster->GetSkill( MAGERY ) / 100, 0 ));
if( Magic->spells[13].Effect() != INVALIDID )
{
Effects->PlaySound( caster, Magic->spells[13].Effect() );
}
CMagicStat temp = Magic->spells[13].StaticEffect();
if( temp.Effect() != INVALIDID )
{
Effects->PlayStaticAnimation( target, temp.Effect(), temp.Speed(), temp.Loop() );
}
}
else
{
sock->SysMessage( 663 ); // You cannot trap this!
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splMagicUntrap()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Magic Untrap spell
//o------------------------------------------------------------------------------------------------o
bool splMagicUntrap( CSocket *sock, CChar *caster, CItem *target, [[maybe_unused]] SI08 curSpell )
{
if( target->IsContType() )
{
if( target->GetTempVar( CITV_MOREZ, 1 ) == 1 )
{
if( RandomNum( 1, 100 ) <= 50 + ( caster->GetSkill( MAGERY ) / 10 ) - target->GetTempVar( CITV_MOREZ, 3 ))
{
target->SetTempVar( CITV_MOREZ, 1, 0 );
if( Magic->spells[14].Effect() != INVALIDID )
{
Effects->PlaySound( caster, Magic->spells[14].Effect() );
}
CMagicStat temp = Magic->spells[14].StaticEffect();
if( temp.Effect() != INVALIDID )
{
Effects->PlayStaticAnimation( target, temp.Effect(), temp.Speed(), temp.Loop() );
}
sock->SysMessage( 664 ); // You successful untrap this item!
}
else
{
sock->SysMessage( 665 ); // You miss to untrap this item!
}
}
else
{
sock->SysMessage( 667 ); // This items doesn't seem to be trapped!
}
}
else
{
sock->SysMessage( 668 ); // This items cannot be trapped!
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splProtection()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Protection spell
//o------------------------------------------------------------------------------------------------o
bool splProtection( CChar *caster, CChar *target, CChar *src, [[maybe_unused]] SI08 curSpell )
{
Effects->TempEffect( src, target, 21, caster->GetSkill( MAGERY ) / 10, 0, 0 );
if( target->IsMurderer() )
{
MakeCriminal( caster );
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splStrength()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Strength spell
//o------------------------------------------------------------------------------------------------o
bool splStrength( CChar *caster, CChar *target, CChar *src, [[maybe_unused]] SI08 curSpell )
{
Effects->TempEffect( src, target, 8, caster->GetSkill( MAGERY ) / 100, 0, 0 );
if( target->IsMurderer() )
{
MakeCriminal( caster );
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splBless()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Bless spell
//o------------------------------------------------------------------------------------------------o
bool splBless( CChar *caster, CChar *target, CChar *src, [[maybe_unused]] SI08 curSpell )
{
SI32 j = caster->GetSkill( MAGERY ) / 100;
Effects->TempEffect( src, target, 11, j, j, j);
if( target->IsMurderer() )
{
MakeCriminal( caster );
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splFireball()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Fireball spell
//o------------------------------------------------------------------------------------------------o
bool splFireball( CChar *caster, CChar *target, CChar *src, SI08 curSpell )
{
SI16 spellDamage = 0;
bool spellResisted = Magic->CheckResist( caster, target, Magic->spells[curSpell].Circle() );
spellDamage = Magic->spells[curSpell].BaseDmg();
spellDamage = CalcSpellDamageMod( caster, target, spellDamage, spellResisted );
Effects->TempEffect( src, target, 30, spellDamage, 0, 0 );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splMagicLock()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Magic Lock spell
//o------------------------------------------------------------------------------------------------o
bool splMagicLock( CSocket *sock, CChar *caster, CItem *target, [[maybe_unused]] SI08 curSpell )
{
ItemTypes type = target->GetType();
if(( type == IT_CONTAINER || type == IT_DOOR || type == IT_SPAWNCONT ) && ( target->GetId( 1 ) != 0x0E || target->GetId( 2 ) != 0x75 ))
{
// Don't allow casting this on locks inside multis
if( ValidateObject( target->GetMultiObj() ))
{
sock->SysMessage( 669 ); // You cannot lock this!
return true;
}
switch( type )
{
case IT_CONTAINER: target->SetType( IT_LOCKEDCONTAINER ); break;
case IT_DOOR: target->SetType( IT_LOCKEDDOOR ); break;
case IT_SPAWNCONT: target->SetType( IT_LOCKEDSPAWNCONT ); break;
default:
Console.Error( "Fallout of switch statement without default. magic.cpp, magiclocktarget()" );
break;
}
// Mark lock as magically locked
target->SetTempVar( CITV_MOREY, 2, static_cast<UI08>( 0x01 ));
// Store min/max skill to unlock with magery based on player's magery skill
target->SetTempVar( CITV_MOREY, 3, static_cast<UI08>(( caster->GetSkill( MAGERY ) / 10 ) / 2 ));
target->SetTempVar( CITV_MOREY, 4, static_cast<UI08>( caster->GetSkill( MAGERY ) / 10 ));
target->RemoveFromSight();
target->Update();
if( Magic->spells[19].Effect() != INVALIDID )
{
Effects->PlaySound( caster, Magic->spells[19].Effect() );
}
CMagicStat temp = Magic->spells[19].StaticEffect();
if( temp.Effect() != INVALIDID )
{
auto iCont = target->GetCont();
if( ValidateObject( iCont ) && iCont->CanBeObjType( OT_CHAR ))
{
// If container is a character, play lock FX on character
Effects->PlayStaticAnimation( iCont, temp.Effect(), temp.Speed(), temp.Loop() );
}
else
{
// Otherwise play FX directly on object
Effects->PlayStaticAnimation( target, temp.Effect(), temp.Speed(), temp.Loop() );
}
}
// Make it an effect that expires in between 7 to 50 seconds, depending on caster's Magery skill
auto magicLockDuration = static_cast<UI16>( floor( caster->GetSkill( MAGERY ) / 100 ) + floor( caster->GetSkill( MAGERY ) / 25 ));
Effects->TempEffect( caster, target, 50, magicLockDuration, 0, 0 );
}
else
{
sock->SysMessage( 669 ); // You cannot lock this!
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CalculateMagicPoisonStrength()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Calculate strength of magic poison based on caster and target
//o------------------------------------------------------------------------------------------------o
UI08 CalculateMagicPoisonStrength( CChar *caster, CChar *target, UI16 skillVal, bool checkDistance, UI08 spellCircle )
{
// Default to Lesser Poison if caster is further than 2 tiles away from target
UI08 poisonStrength = 1;
// Check if caster is within 2 tiles of target
if( !checkDistance || GetDist( caster, target ) <= 2 )
{
// Calculate poison strength based on average of caster's Magery and Poisoning skills
if( skillVal == 0 )
{
skillVal = static_cast<UI16>(( caster->GetSkill( MAGERY ) + caster->GetSkill( POISONING )) / 2 );
}
if( skillVal >= 1000 ) // GM in both skills
{
// 5% chance of Deadly Poison if spellCircle is lower than 5, or always Deadly if spellCircle is 5
if( spellCircle == 5 || RandomNum( 1, 100 ) <= 5 )
{
poisonStrength = 4; // Deadly Poison
}
else
{
poisonStrength = 3; // Greater Poison
}
}
else if( skillVal > 701 )
{
poisonStrength = 3; // Greater Poison
}
else if( skillVal > 301 )
{
poisonStrength = 2; // Regular Poison
}
}
// If target resists spell, reduce strength of poison
if( Magic->CheckResist( caster, target, 3 ))
{
if( poisonStrength > 1 )
{
poisonStrength--;
}
}
return poisonStrength;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splPoison()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Poison spell
//o------------------------------------------------------------------------------------------------o
bool splPoison( CChar *caster, CChar *target, [[maybe_unused]] CChar *src, [[maybe_unused]] SI08 curSpell )
{
// Calculate poison strength of spell based on caster's skills, range to target and whether target resists
UI08 poisonStrength = CalculateMagicPoisonStrength( caster, target, 0, true, 3 );
// Apply poison on target
target->SetPoisoned( poisonStrength );
// Set time until poison wears off completely
target->SetTimer( tCHAR_POISONWEAROFF, BuildTimeValue( GetPoisonDuration( poisonStrength )));
// Handle criminal flagging
if( ValidateObject( caster ) && target->IsInnocent() )
{
if( WillResultInCriminal( caster, target ) && ValidateObject( caster ) && !caster->IsGM() && !caster->IsCounselor() )
{
MakeCriminal( caster );
}
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splTelekinesis()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Telekinesis spell
//o------------------------------------------------------------------------------------------------o
bool splTelekinesis( CSocket *sock, CChar *caster, CItem *target, [[maybe_unused]] SI08 curSpell )
{
if( target->IsContType() )
{
if( target->GetTempVar( CITV_MOREZ, 1 ) == 1 )
{
target->SetTempVar( CITV_MOREZ, 1, 0 );
if( Magic->spells[21].Effect() != INVALIDID )
{
Effects->PlaySound( caster, Magic->spells[21].Effect() );
}
CMagicStat temp = Magic->spells[21].StaticEffect();
if( temp.Effect() != INVALIDID )
{
Effects->PlayStaticAnimation( target, temp.Effect(), temp.Speed(), temp.Loop() );
}
Magic->MagicTrap( caster, target );
}
else
{
sock->SysMessage( 667 ); // This item doesn't seem to be trapped!
}
}
else
{
sock->SysMessage( 668 ); // This item cannot be trapped!
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splTeleport()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Teleport spell
//o------------------------------------------------------------------------------------------------o
bool splTeleport( CSocket *sock, CChar *caster, SI16 x, SI16 y, SI08 z, [[maybe_unused]] SI08 curSpell )
{
CMultiObj *m = FindMulti( x, y, z, caster->WorldNumber(), caster->GetInstanceId() );
if( ValidateObject( m ) && m->GetOwnerObj() != caster && !caster->IsNpc() )
{
sock->SysMessage( 670 ); // You can't teleport here!
return false;
}
if( !caster->IsNpc() )
{
CTile& tile = Map->SeekTile( sock->GetWord( 0x11 ));
if(( tile.Name() == "water" ) || tile.CheckFlag( TF_WET ))
{
sock->SysMessage( 671 ); //You can't teleport there!
return false;
}
if( tile.CheckFlag( TF_ROOF )) // slanted roof tile!!! naughty naughty
{
sock->SysMessage( 672 ); // You cannot teleport there!
return false;
}
}
if( Weight->IsOverloaded( caster ))
{
if( caster->GetMana() >= 20 )
{
caster->SetMana( static_cast<SI16>( caster->GetMana() - 20 ));
}
else
{
sock->SysMessage( 1386 ); // You are overloaded! You can't hold all this weight.
return false;
}
}
CMagicStat temp = Magic->spells[22].StaticEffect();
if( temp.Effect() != INVALIDID )
{
Effects->PlayStaticAnimation( caster->GetX(), caster->GetY(), caster->GetZ(), temp.Effect(), temp.Speed(), temp.Loop(), false );
caster->SetLocation( x, y, z );
Magic->DoStaticEffect( caster, 22 );
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splUnlock()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Unlock spell
//o------------------------------------------------------------------------------------------------o
bool splUnlock( CSocket *sock, CChar *caster, CItem *target, [[maybe_unused]] SI08 curSpell )
{
if( target->IsDivineLocked() )
{
sock->SysMessage( 673 ); // This item has been protected with divine magic. You cannot unlock this item.
return false;
}
if( target->GetType() == IT_LOCKEDCONTAINER )
{
bool canMagicUnlock = true;
if( target->GetTempVar( CITV_MOREY ) > 0 )
{
canMagicUnlock = static_cast<bool>( target->GetTempVar( CITV_MOREY, 2 ));
if( !canMagicUnlock )
{
sock->SysMessage( 9102 ); // That lock cannot be unlocked with magic!
return false;
}
}
auto minSkill = target->GetTempVar( CITV_MOREY, 3 ) * 10;
auto maxSkill = target->GetTempVar( CITV_MOREY, 4 ) * 10;
if( !Skills->CheckSkill( caster, MAGERY, minSkill, ( maxSkill > 0 ? maxSkill : 1000 )))
{
sock->SysMessage( 6092 ); // You are not skilled enough to do that.
return false;
}
target->SetType( IT_CONTAINER );
sock->SysMessage( 674 ); // You unlock the item.
target->RemoveFromSight();
target->Update();
if( Magic->spells[23].Effect() != INVALIDID )
{
Effects->PlaySound( caster, Magic->spells[23].Effect() );
}
CMagicStat temp = Magic->spells[23].StaticEffect();
if( temp.Effect() != INVALIDID )
{
auto iCont = target->GetCont();
if( ValidateObject( iCont ) && iCont->CanBeObjType( OT_CHAR ))
{
// If container is a character, play lock FX on character
Effects->PlayStaticAnimation( iCont, temp.Effect(), temp.Speed(), temp.Loop() );
}
else
{
Effects->PlayStaticAnimation( target, temp.Effect(), temp.Speed(), temp.Loop() );
}
}
}
else if( target->GetType() == IT_LOCKEDSPAWNCONT )
{
bool canMagicUnlock = true;
if( target->GetTempVar( CITV_MOREY ) > 0 )
{
canMagicUnlock = static_cast<bool>(target->GetTempVar( CITV_MOREY, 2 ));
if( !canMagicUnlock )
{
sock->SysMessage( 9102 ); // That lock cannot be unlocked with magic!
return false;
}
}
auto minSkill = target->GetTempVar( CITV_MOREY, 3 ) * 10;
auto maxSkill = target->GetTempVar( CITV_MOREY, 4 ) * 10;
if( !Skills->CheckSkill( caster, MAGERY, minSkill, ( maxSkill > 0 ? maxSkill : 1000 )))
{
sock->SysMessage( 6092 ); // You are not skilled enough to do that.
return false;
}
target->SetType( IT_SPAWNCONT );
sock->SysMessage( 675 ); // You unlock the item.
target->RemoveFromSight();
target->Update();
if( Magic->spells[23].Effect() != INVALIDID )
{
Effects->PlaySound( caster, Magic->spells[23].Effect() );
}
CMagicStat temp = Magic->spells[23].StaticEffect();
if( temp.Effect() != INVALIDID )
{
Effects->PlayStaticAnimation( target, temp.Effect(), temp.Speed(), temp.Loop() );
}
}
else if( target->GetType() == IT_CONTAINER || target->GetType() == IT_SPAWNCONT || target->GetType() == IT_LOCKEDSPAWNCONT || target->GetType() == IT_TRASHCONT )
{
sock->SysMessage( 676 ); // That is not locked.
}
else if( target->GetType() == IT_DOOR )
{
sock->SysMessage( 937 ); // That cannot be unlocked without a key!
}
else if( target->GetType() == IT_LOCKEDDOOR )
{
bool canMagicUnlock = true;
if( target->GetTempVar( CITV_MOREY ) > 0 )
{
canMagicUnlock = static_cast<bool>(target->GetTempVar( CITV_MOREY, 2 ));
if( !canMagicUnlock )
{
sock->SysMessage( 9102 ); // That lock cannot be unlocked with magic!
return false;
}
}
auto minSkill = target->GetTempVar( CITV_MOREY, 3 ) * 10;
auto maxSkill = target->GetTempVar( CITV_MOREY, 4 ) * 10;
if( !Skills->CheckSkill( caster, MAGERY, minSkill, ( maxSkill > 0 ? maxSkill : 1000 )))
{
sock->SysMessage( 6092 ); // You are not skilled enough to do that.
return false;
}
target->SetType( IT_DOOR );
sock->SysMessage( 675 ); // You unlock the item.
target->RemoveFromSight();
target->Update();
if( Magic->spells[23].Effect() != INVALIDID )
{
Effects->PlaySound( caster, Magic->spells[23].Effect() );
}
CMagicStat temp = Magic->spells[23].StaticEffect();
if( temp.Effect() != INVALIDID )
{
Effects->PlayStaticAnimation( target, temp.Effect(), temp.Speed(), temp.Loop() );
}
}
else
{
sock->SysMessage( 678 ); // That does not have a lock.
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splWallOfStone()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Wall of Stone spell
//o------------------------------------------------------------------------------------------------o
bool splWallOfStone( [[maybe_unused]] CSocket *sock, CChar *caster, UI08 fieldDir, SI16 x, SI16 y, SI08 z, SI08 curSpell )
{
return FieldSpell( caster, 0x0080, x, y, z, fieldDir, curSpell );
}
//o------------------------------------------------------------------------------------------------o
//| Function - ArchCureStub()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Arch Cure spell to an individual target
//o------------------------------------------------------------------------------------------------o
void ArchCureStub( [[maybe_unused]] CChar *caster, CChar *target, [[maybe_unused]] SI08 curSpell, [[maybe_unused]] SI08 targCount )
{
target->SetPoisoned( 0 );
target->SetTimer( tCHAR_POISONWEAROFF, cwmWorldState->GetUICurrentTime() );
}
//o------------------------------------------------------------------------------------------------o
//| Function - splArchCure()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Casts Arch Cure as an area affect spell
//o------------------------------------------------------------------------------------------------o
bool splArchCure( CSocket *sock, CChar *caster, [[maybe_unused]] CChar *target, [[maybe_unused]] CChar *src, SI08 curSpell )
{
AreaAffectSpell( sock, caster, &ArchCureStub, curSpell );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - ArchProtectionStub()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Arch Protection spell to an individual target
//o------------------------------------------------------------------------------------------------o
void ArchProtectionStub( CChar *caster, CChar *target, [[maybe_unused]] SI08 curSpell, [[maybe_unused]] SI08 targCount )
{
Magic->PlaySound( target, 26 );
Magic->DoStaticEffect( target, 15 ); // protection
Effects->TempEffect( caster, target, 21, caster->GetSkill( MAGERY ) / 10, 0, 0 );
}
//o------------------------------------------------------------------------------------------------o
//| Function - splArchProtection()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Casts Arch Protection as an area affect spell
//o------------------------------------------------------------------------------------------------o
bool splArchProtection( CSocket *sock, CChar *caster, [[maybe_unused]] CChar *target, [[maybe_unused]] CChar *src, SI08 curSpell )
{
AreaAffectSpell( sock, caster, &ArchProtectionStub, curSpell );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splCurse()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Curse spell
//o------------------------------------------------------------------------------------------------o
bool splCurse( CChar *caster, CChar *target, [[maybe_unused]] CChar *src, [[maybe_unused]] SI08 curSpell )
{
SI32 j = caster->GetSkill( MAGERY ) / 100;
Effects->TempEffect( caster, target, 12, j, j, j);
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splFireField()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Casts Fire Field as a field spell
//o------------------------------------------------------------------------------------------------o
bool splFireField( [[maybe_unused]] CSocket *sock, CChar *caster, UI08 fieldDir, SI16 x, SI16 y, SI08 z, SI08 curSpell )
{
if( fieldDir == 0 )
{
return FieldSpell( caster, 0x398C, x, y, z, fieldDir, curSpell );
}
else
{
return FieldSpell( caster, 0x3996, x, y, z, fieldDir, curSpell );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - splGreaterHeal()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Greater Heal spell
//o------------------------------------------------------------------------------------------------o
bool splGreaterHeal( CChar *caster, CChar *target, [[maybe_unused]] CChar *src, SI08 curSpell )
{
SI32 srcHealth = target->GetHP();
SI32 baseHealing = Magic->spells[curSpell].BaseDmg();
SI32 j = caster->GetSkill( MAGERY ) / 30 + HalfRandomNum( baseHealing );
target->Heal( j, caster );
Magic->SubtractHealth( caster, std::min( target->GetStrength(), static_cast<SI16>( srcHealth + j )), 29 );
if( target->IsMurderer() )
{
MakeCriminal( caster );
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splLightning()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Lightning spell
//o------------------------------------------------------------------------------------------------o
bool splLightning( CChar *caster, CChar *target, CChar *src, SI08 curSpell )
{
SI16 spellDamage = 0;
bool spellResisted = Magic->CheckResist( caster, target, Magic->spells[curSpell].Circle() );
spellDamage = Magic->spells[curSpell].BaseDmg();
spellDamage = CalcSpellDamageMod( caster, target, spellDamage, spellResisted );
Effects->TempEffect( src, target, 31, spellDamage, 0, 0 );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splManaDrain()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Mana Drain spell
//o------------------------------------------------------------------------------------------------o
bool splManaDrain( CChar *caster, CChar *target, [[maybe_unused]] CChar *src, [[maybe_unused]] SI08 curSpell )
{
if( !Magic->CheckResist( caster, target, 4 ))
{
target->SetMana( target->GetMana() - caster->GetSkill( MAGERY )/35 );
if( target->GetMana() < 0 )
{
target->SetMana( 0 );
}
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splRecall()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Recall spell
//o------------------------------------------------------------------------------------------------o
auto splRecall( CSocket *sock, CChar *caster, CItem *i, [[maybe_unused]] SI08 curSpell ) -> bool
{
// No recall if too heavy, GMs excempt
if( Weight->IsOverloaded( caster ) && !cwmWorldState->ServerData()->TravelSpellsWhileOverweight() && ( !caster->IsCounselor() && !caster->IsGM() ))
{
sock->SysMessage( 680 ); // You are too heavy to do that!
sock->SysMessage( 681 ); // You feel drained from the attempt.
return false;
}
else if( i->GetType() == 7 )
{
// Player recalled off a key
CMultiObj *shipMulti = CalcMultiFromSer( i->GetTempVar( CITV_MORE ));
if( ValidateObject( shipMulti ) && shipMulti->CanBeObjType( OT_BOAT ))
{
if(( shipMulti->WorldNumber() == caster->WorldNumber() || cwmWorldState->ServerData()->TravelSpellsBetweenWorlds() ) && shipMulti->GetInstanceId() == caster->GetInstanceId() )
{
if( shipMulti->WorldNumber() == 0 && caster->WorldNumber() != 0 && cwmWorldState->ServerData()->YoungPlayerSystem() && caster->GetAccount().wFlags.test( AB_FLAGS_YOUNG ) )
{
sock->SysMessage( 18733 ); // You decide against traveling to Felucca while you are still young.
return false;
}
// Teleport player's followers too
auto myFollowers = caster->GetFollowerList();
for( const auto &myFollower : myFollowers->collection() )
{
if( ValidateObject( myFollower ))
{
if( !myFollower->GetMounted() && myFollower->GetOwnerObj() == caster )
{
// Only teleport followers if they are set to actually following owner & are within range
if( myFollower->GetNpcWander() == WT_FOLLOW && ObjInOldRange( caster, myFollower, DIST_CMDRANGE ))
{
myFollower->SetLocation( shipMulti->GetX() + 1, shipMulti->GetY(), shipMulti->GetZ() + 3, shipMulti->WorldNumber(), shipMulti->GetInstanceId() );
}
}
}
}
// Teleport player
caster->SetLocation( shipMulti->GetX() + 1, shipMulti->GetY(), shipMulti->GetZ() + 3, shipMulti->WorldNumber(), shipMulti->GetInstanceId() );
return true;
}
else
{
sock->SysMessage( 2063 ); // You are unable to recall to your ship - it might be in another world!
return false;
}
}
else
{
sock->SysMessage( 2065 ); // You can only recall off of recall runes or valid ship keys!
return false;
}
}
else
{
// Check if rune was marked in a multi - if so, try to take user directly there
TAGMAPOBJECT runeMore = i->GetTag( "multiSerial" );
if( runeMore.m_StringValue != "" )
{
SERIAL mSerial = oldstrutil::value<SERIAL>( runeMore.m_StringValue );
if( mSerial != 0 && mSerial != INVALIDSERIAL )
{
CMultiObj *shipMulti = CalcMultiFromSer( mSerial );
if( ValidateObject( shipMulti ) && shipMulti->CanBeObjType( OT_BOAT ))
{
if(( shipMulti->WorldNumber() == caster->WorldNumber() || cwmWorldState->ServerData()->TravelSpellsBetweenWorlds() ) && shipMulti->GetInstanceId() == caster->GetInstanceId() )
{
if( shipMulti->WorldNumber() == 0 && caster->WorldNumber() != 0 && cwmWorldState->ServerData()->YoungPlayerSystem() && caster->GetAccount().wFlags.test( AB_FLAGS_YOUNG ) )
{
sock->SysMessage( 18733 ); // You decide against traveling to Felucca while you are still young.
return false;
}
// Teleport player's followers too
auto myFollowers = caster->GetFollowerList();
for( const auto &myFollower : myFollowers->collection() )
{
if( ValidateObject( myFollower ))
{
if( !myFollower->GetMounted() && myFollower->GetOwnerObj() == caster )
{
if( myFollower->GetNpcWander() == WT_FOLLOW && ObjInOldRange( caster, myFollower, DIST_CMDRANGE ))
{
myFollower->SetLocation( shipMulti->GetX() + 1, shipMulti->GetY(), shipMulti->GetZ() + 3, shipMulti->WorldNumber(), shipMulti->GetInstanceId() );
}
}
}
}
caster->SetLocation( shipMulti->GetX() + 1, shipMulti->GetY(), shipMulti->GetZ() + 3, shipMulti->WorldNumber(), shipMulti->GetInstanceId() );
return true;
}
else
{
sock->SysMessage( 2063 ); // You are unable to recall to your ship - it might be in another world!
return false;
}
}
}
sock->SysMessage( 2062 ); // Unable to locate ship - it might have been dry-docked... or sunk!
return false;
}
else if( i->GetTempVar( CITV_MOREX ) == 0 && i->GetTempVar( CITV_MOREY ) == 0 )
{
sock->SysMessage( 679 ); // That rune has not been marked yet!
return false;
}
else
{
UI08 worldNum = static_cast<UI08>( i->GetTempVar( CITV_MORE ));
if( !Map->MapExists( worldNum ))
{
worldNum = caster->WorldNumber();
}
SI16 targLocX = static_cast<SI16>( i->GetTempVar( CITV_MOREX ));
SI16 targLocY = static_cast<SI16>( i->GetTempVar( CITV_MOREY ));
SI08 targLocZ = static_cast<SI08>( i->GetTempVar( CITV_MOREZ ));
UI16 instanceId = static_cast<UI16>( i->GetTempVar( CITV_MORE0 ));
if( worldNum != caster->WorldNumber() )
{
if( cwmWorldState->ServerData()->TravelSpellsBetweenWorlds() )
{
if( worldNum == 0 && caster->WorldNumber() != 0 && cwmWorldState->ServerData()->YoungPlayerSystem() && caster->GetAccount().wFlags.test( AB_FLAGS_YOUNG ) )
{
sock->SysMessage( 18733 ); // You decide against traveling to Felucca while you are still young.
return false;
}
// Teleport player's followers too
auto myFollowers = caster->GetFollowerList();
for( const auto &myFollower : myFollowers->collection() )
{
if( ValidateObject( myFollower ))
{
if( !myFollower->GetMounted() && myFollower->GetOwnerObj() == caster )
{
if( myFollower->GetNpcWander() == WT_FOLLOW && ObjInOldRange( caster, myFollower, DIST_CMDRANGE ))
{
myFollower->SetLocation( targLocX, targLocY, targLocZ, worldNum, instanceId );
}
}
}
}
// Teleport the player
caster->SetLocation( targLocX, targLocY, targLocZ, worldNum, instanceId );
SendMapChange( caster->WorldNumber(), sock, false );
}
else
{
sock->SysMessage( 2061 ); // Travelling between worlds using Recall or Gate spells is not possible.
return false;
}
}
else
{
// Teleport player's followers too
auto myFollowers = caster->GetFollowerList();
for( const auto &myFollower : myFollowers->collection() )
{
if( ValidateObject( myFollower ))
{
if( !myFollower->GetMounted() && myFollower->GetOwnerObj() == caster )
{
if( myFollower->GetNpcWander() == WT_FOLLOW && ObjInOldRange( caster, myFollower, DIST_CMDRANGE ))
{
myFollower->SetLocation( targLocX, targLocY, targLocZ, worldNum, instanceId );
}
}
}
}
// Teleport the player
caster->SetLocation( targLocX, targLocY, targLocZ, worldNum, instanceId );
}
sock->SysMessage( 682 ); // You have recalled from the rune.
return true;
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - splBladeSpirits()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Blade Spirits spell (summons a Blade Spirit)
//o------------------------------------------------------------------------------------------------o
auto splBladeSpirits( CSocket *sock, CChar *caster, SI16 x, SI16 y, SI08 z, [[maybe_unused]] SI08 curSpell ) -> bool
{
Magic->SummonMonster( sock, caster, 6, x, y, z );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splDispelField()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Dispel Field spell
//o------------------------------------------------------------------------------------------------o
bool splDispelField( CSocket *sock, CChar *caster, [[maybe_unused]] SI08 curSpell )
{
CItem *i = CalcItemObjFromSer( sock->GetDWord( 7 ));
if( ValidateObject( i ))
{
if( LineOfSight( sock, caster, i->GetX(), i->GetY(), ( i->GetZ() + 15 ), WALLS_CHIMNEYS + DOORS + FLOORS_FLAT_ROOFING, false ) || caster->IsGM() )
{
if( i->IsDecayable() && i->IsDispellable() )
{
std::vector<UI16> scriptTriggers = i->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
if( toExecute->OnDispel( i ) == 1 ) // if it exists and we don't want hard code, return
return false;
}
}
i->Delete();
}
Effects->PlaySound( caster, 0x0201 );
}
else
{
sock->SysMessage( 683 ); // There seems to be something in the way.
}
}
return true;
}
void SetRandomName( CChar *s, const std::string& namelist );
//o------------------------------------------------------------------------------------------------o
//| Function - splIncognito()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Incognito spell
//o------------------------------------------------------------------------------------------------o
bool splIncognito( CSocket *sock, CChar *caster, [[maybe_unused]] SI08 curSpell )
{
if( caster->IsIncognito() )
{
sock->SysMessage( 1636 ); // You cannot go incognito while already incognito
return false;
}
if( caster->IsPolymorphed() )
{
sock->SysMessage( 1674 ); // You can't do that while polymorphed.
return false;
}
if( caster->IsDisguised() )
{
sock->SysMessage( 9232 ); // You can't do that while disguised.
return false;
}
if( caster->GetId() == 0x00b8 || caster->GetId() == 0x00b7 ) // Savage Kin Paint transformation
{
sock->SysMessage( 1672 ); // You cannot use incognito while wearing body paint
return false;
}
// ------ SEX ------
caster->SetOrgId( caster->GetId() );
UI08 randomGender = RandomNum( 0, 3 );
switch( randomGender )
{
case 0: caster->SetId( 0x0190 ); break; // male, human
case 1: caster->SetId( 0x0191 ); break; // female, human
case 2: caster->SetId( 0x025D ); break; // male, elf
case 3: caster->SetId( 0x025E ); break; // female, elf
}
// ------ NAME -----
caster->SetOrgName( caster->GetName() );
switch( randomGender )
{
case 0: SetRandomName( caster, "1" ); break; // get a name from human male list
case 1: SetRandomName( caster, "2" ); break; // get a name from human female list
case 2: SetRandomName( caster, "3" ); break; // get a name from elf male list
case 3: SetRandomName( caster, "4" ); break; // get a name from elf female list
}
SI32 color = RandomNum( 0x044E, 0x047D );
CItem *j = caster->GetItemAtLayer( IL_HAIR );
if( ValidateObject( j ))
{
caster->SetHairColour( j->GetColour() );
caster->SetHairStyle( j->GetId() );
SI32 rNum = RandomNum( 0, 9 );
switch( rNum )
{
case 0:
case 1:
case 2:
j->SetId( 0x3B + rNum, 2 );
break;
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
j->SetId( 0x44 + ( rNum - 3 ), 2 );
break;
}
j->SetColour( color );
}
// Handle facial hair
j = caster->GetItemAtLayer( IL_FACIALHAIR );
if( ValidateObject( j ))
{
if( randomGender == 0 || randomGender == 2 )
{
caster->SetBeardColour( j->GetColour() );
caster->SetBeardStyle( j->GetId() );
SI32 rNum2 = RandomNum( 0, 6 );
switch( rNum2 )
{
case 0:
case 1:
case 2:
case 3:
j->SetId( 0x3E + rNum2, 2 );
break;
case 4:
case 5:
case 6:
j->SetId( 0x4B + ( rNum2 - 4 ), 2 );
break;
}
j->SetColour( color );
}
else
{
// Hide the facial hair for female characters
j->SetVisible( VT_PERMHIDDEN );
}
}
//only refresh once
caster->SendWornItems( sock );
Effects->PlaySound( caster, 0x0203 );
Effects->TempEffect( caster, caster, 19, 0, 0, 0 );
caster->IsIncognito( true );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splMagicReflection()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Magic Reflection spell
//o------------------------------------------------------------------------------------------------o
bool splMagicReflection( [[maybe_unused]] CSocket *sock, CChar *caster, [[maybe_unused]] SI08 curSpell )
{
caster->SetTempReflected( true );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splMindBlast()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Mind Blast spell
//o------------------------------------------------------------------------------------------------o
bool splMindBlast( CChar *caster, CChar *target, CChar *src, SI08 curSpell )
{
SI16 spellDamage = 0;
SI16 baseDamage = 0;
SI16 targetStats = target->GetIntelligence() + target->GetStrength() + target->GetDexterity();
SI16 casterStats = caster->GetIntelligence() + caster->GetStrength() + caster->GetDexterity();
//If target's int makes out a larger percentage of the total stats than the caster's, backfire spell onto caster
if(( target->GetIntelligence() * 100 ) / targetStats > ( caster->GetIntelligence() * 100 ) / casterStats )
{
target = src;
}
bool spellResisted = Magic->CheckResist( caster, target, Magic->spells[curSpell].Circle() );
baseDamage = Magic->spells[curSpell].BaseDmg();
spellDamage = baseDamage;
spellDamage = CalcSpellDamageMod( caster, target, spellDamage, spellResisted );
//Damage should not exceed 60% of target maxHp
if( spellDamage > ( target->GetMaxHP() * 0.6 ))
{
spellDamage = static_cast<SI16>( RoundNumber( target->GetMaxHP() * 0.6 ));
}
//Damage should not exceed basedamage from DFN + 20%
if( spellDamage > baseDamage * 1.20 )
{
spellDamage = baseDamage * 1.20;
}
Effects->TempEffect( src, target, 32, spellDamage, 0, 0 );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splParalyze()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Paralyze spell
//o------------------------------------------------------------------------------------------------o
bool splParalyze( CChar *caster, CChar *target, [[maybe_unused]] CChar *src, [[maybe_unused]] SI08 curSpell )
{
if( !Magic->CheckResist( caster, target, 7 ))
{
Effects->TempEffect( caster, target, 1, 0, 0, 0 );
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splPoisonField()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Casts Poison Field as a field spell
//o------------------------------------------------------------------------------------------------o
bool splPoisonField( [[maybe_unused]] CSocket *sock, CChar *caster, UI08 fieldDir, SI16 x, SI16 y, SI08 z, SI08 curSpell )
{
if( fieldDir == 0 )
{
return FieldSpell( caster, 0x3915, x, y, z, fieldDir, curSpell );
}
else
{
return FieldSpell( caster, 0x3921, x, y, z, fieldDir, curSpell );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - splSummonCreature()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Summon Creature spell (summons a creature)
//o------------------------------------------------------------------------------------------------o
bool splSummonCreature( CSocket *sock, CChar *caster, SI16 x, SI16 y, SI08 z, [[maybe_unused]] SI08 curSpell )
{
if( caster->GetSkill( MAGERY ) <= 380 )
{
return false;
}
else
{
Magic->SummonMonster( sock, caster, 0, x, y, z );
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splDispel()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Dispel spell
//o------------------------------------------------------------------------------------------------o
bool splDispel( CChar *caster, CChar *target, [[maybe_unused]] CChar *src, [[maybe_unused]] SI08 curSpell )
{
if( target->IsDispellable() && !target->IsGM() )
{
//Effects->PlayStaticAnimation( target, 0x372A, 0x09, 0x06 ); // why the specifics here?
if( target->IsNpc() )
{
std::vector<UI16> scriptTriggers = target->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
if( toExecute->OnDispel( target ) == 1 ) // if it exists and we don't want hard code, return
return false;
}
}
// Base chance of dispelling on Dispel caster's Magery skill vs summoner's magicresistance
UI16 casterMagery = caster->GetSkill( MAGERY );
UI16 targetResist = 0;
targetResist = target->GetSkill( MAGICRESISTANCE );
// If creature was summoned, use highest of creature's resist and summoner's resist
if( target->GetOwner() != INVALIDSERIAL )
{
targetResist = std::max( targetResist, target->GetOwnerObj()->GetSkill( MAGICRESISTANCE ));
}
UI16 dispelChance = std::max( static_cast<UI16>( 950 ), ( static_cast<UI16>( std::max( 0.0, static_cast<double>( std::ceil( static_cast<double>( 500 - ( targetResist - casterMagery )) / 1.5 ))))));
if( dispelChance > RandomNum( 0, 1000 ))
{
// Dispel succeeded!
if( Magic->spells[41].Effect() != INVALIDID )
{
Effects->PlaySound( target, Magic->spells[41].Effect() );
}
CMagicStat temp = Magic->spells[41].StaticEffect();
if( temp.Effect() != INVALIDID )
{
// Play static effect at target's location, since target gets destroyed
Effects->PlayStaticAnimation( target->GetX(), target->GetY(), target->GetZ(), temp.Effect(), temp.Speed(), temp.Loop(), false );
}
target->Delete();
}
else if( caster->GetSocket() != nullptr )
{
// Dispel resisted!
caster->GetSocket()->SysMessage( 2761 ); // The creature resisted the attempt to dispel it!
}
}
else
{
HandleDeath( target, nullptr );
}
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splEnergyBolt()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Energy Bolt spell
//o------------------------------------------------------------------------------------------------o
bool splEnergyBolt( CChar *caster, CChar *target, CChar *src, SI08 curSpell )
{
SI16 spellDamage = 0;
bool spellResisted = Magic->CheckResist( caster, target, Magic->spells[curSpell].Circle() );
spellDamage = Magic->spells[curSpell].BaseDmg();
spellDamage = CalcSpellDamageMod( caster, target, spellDamage, spellResisted );
Effects->TempEffect( src, target, 33, spellDamage, 0, 0 );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splExplosion()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Explosion spell. Delay configurable in UOX.INI
//o------------------------------------------------------------------------------------------------o
bool splExplosion( CChar *caster, CChar *target, CChar *src, SI08 curSpell )
{
SI16 spellDamage = 0;
bool spellResisted = Magic->CheckResist( caster, target, Magic->spells[curSpell].Circle() );
spellDamage = Magic->spells[curSpell].BaseDmg();
spellDamage = CalcSpellDamageMod( caster, target, spellDamage, spellResisted );
Effects->TempEffect( src, target, 27, spellDamage, 0, 0 );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splInvisibility()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Invisibility spell
//o------------------------------------------------------------------------------------------------o
bool splInvisibility( CChar *caster, CChar *target, [[maybe_unused]] CChar *src, [[maybe_unused]] SI08 curSpell )
{
target->SetVisible( VT_INVISIBLE );
target->SetTimer( tCHAR_INVIS, cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_INVISIBILITY ));
if( target->IsMurderer() )
{
MakeCriminal( caster );
}
//Stop autodefending against targets in combat once you turn invisible
if( !target->IsNpc() )
{
Combat->InvalidateAttacker( target );
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splMark()
//| Date - 09/22/2002
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Mark spell
//|
//| Changes - 09/22/2002 - Fixed marking of locked down runes..
//| (shouldn't be possible)
//o------------------------------------------------------------------------------------------------o
bool splMark( CSocket *sock, CChar *caster, CItem *i, [[maybe_unused]] SI08 curSpell )
{
if( i->IsLockedDown() )
{
sock->SysMessage( 774 ); // That is locked down and you cannot use it.
return false;
}
if( i->GetType() == IT_RUNEBOOK )
{
sock->SysMessage( 710 ); // That item is not a recall rune.
return false;
}
bool markedInMulti = false;
CMultiObj *multi = caster->GetMultiObj();
if( ValidateObject( multi ))
{
if( !multi->IsOnOwnerList( caster ))
{
sock->SysMessage( 9034 ); // Marking a rune in property you don't own is not possible.
return false;
}
else if( !cwmWorldState->ServerData()->MarkRunesInMultis() )
{
sock->SysMessage( 9035 ); // "Marking a rune inside one's property is not permitted.
return false;
}
else
{
// Let's allow marking the rune in the multi, and store multi's serial in a tag
auto mSerial = multi->GetSerial();
TAGMAPOBJECT tagObject;
tagObject.m_Destroy = false;
tagObject.m_StringValue = std::to_string( mSerial );
tagObject.m_IntValue = static_cast<SI32>( tagObject.m_StringValue.size() );
tagObject.m_ObjectType = TAGMAP_TYPE_STRING;
i->SetTag( "multiSerial", tagObject );
markedInMulti = true;
std::string tempRuneName = oldstrutil::format( Dictionary->GetEntry( 684 ), multi->GetNameRequest( caster, NRS_SYSTEM ).c_str() ); // A recall rune for %s.
if( tempRuneName.length() > 0 )
{
i->SetName( tempRuneName );
}
}
}
if( !markedInMulti )
{
i->SetTempVar( CITV_MOREX, caster->GetX() );
i->SetTempVar( CITV_MOREY, caster->GetY() );
i->SetTempVar( CITV_MOREZ, caster->GetZ() );
i->SetTempVar( CITV_MORE, caster->WorldNumber() );
i->SetTempVar( CITV_MORE0, caster->GetInstanceId() );
std::string tempitemname;
if( caster->GetRegion()->GetName()[0] != 0 )
{
tempitemname = oldstrutil::format( Dictionary->GetEntry( 684 ), caster->GetRegion()->GetName().c_str() ); // A recall rune for %s.
}
else
{
tempitemname = Dictionary->GetEntry( 685 ); // A recall rune for An Unknown Location.
}
i->SetName( tempitemname );
// Change ID of rune from a blank, unmarked rune (0x1f17) to a marked one (0x1f14)
i->SetId( 0x1f14 );
}
sock->SysMessage( 686 ); // Recall rune marked.
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - MassCurseStub()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Mass Curse spell to an individual target
//o------------------------------------------------------------------------------------------------o
void MassCurseStub( CChar *caster, CChar *target, [[maybe_unused]] SI08 curSpell, [[maybe_unused]] SI08 targCount )
{
if( target->IsNpc() && target->GetNpcAiType() == AI_PLAYERVENDOR )
return; // Player Vendors can't be killed
if( target->IsGM() || target->IsInvulnerable() )
return; // GMs/Invuls can't be killed
if( target->IsNpc() && target == caster )
return; // Don't let NPC curse themselves
if( target->IsNpc() && caster->IsNpc() )
{
if( target->GetNPCFlag() == caster->GetNPCFlag() )
return; // Don't let NPCs curse other NPCs with same NPC Flag (innocent, evil, neutral)
switch( caster->GetNpcAiType() )
{
case 2: // AI_EVIL
case 11: // AI_EVIL_CASTER
{
if( target->GetNpcAiType() == AI_EVIL || target->GetNpcAiType() == AI_EVIL_CASTER )
return; // Don't curse NPCs with similar AI types
break;
}
case 5: // AI_FIGHTER
case 10: // AI_CASTER
{
if( target->GetNpcAiType() == AI_FIGHTER || target->GetNpcAiType() == AI_CASTER )
return; // Don't curse NPCs with similar AI types
break;
}
default:
break;
}
}
SI32 j;
if( target->IsNpc() )
{
Combat->AttackTarget( caster, target );
}
Effects->PlayStaticAnimation( target, 0x374A, 0, 15 );
Effects->PlaySound( target, 0x01FC );
if( Magic->CheckResist( caster, target, 6 ))
{
j = caster->GetSkill( MAGERY ) / 200;
}
else
{
j = caster->GetSkill( MAGERY ) / 75;
}
Effects->TempEffect( caster, target, 12, j, j, j );
}
//o------------------------------------------------------------------------------------------------o
//| Function - splMassCurse()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Casts Mass Curse as an area affect spell
//o------------------------------------------------------------------------------------------------o
bool splMassCurse( CSocket *sock, CChar *caster, [[maybe_unused]] CChar *target, [[maybe_unused]] CChar *src, SI08 curSpell )
{
AreaAffectSpell( sock, caster, &MassCurseStub, curSpell );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splParalyzeField()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Casts Paralyze Field as a field spell
//o------------------------------------------------------------------------------------------------o
bool splParalyzeField( [[maybe_unused]] CSocket *sock, CChar *caster, UI08 fieldDir, SI16 x, SI16 y, SI08 z, SI08 curSpell )
{
if( fieldDir == 0 )
{
return FieldSpell( caster, 0x3967, x, y, z, fieldDir, curSpell );
}
else
{
return FieldSpell( caster, 0x3979, x, y, z, fieldDir, curSpell );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - splReveal()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Reveal spell
//o------------------------------------------------------------------------------------------------o
auto splReveal( CSocket *sock, CChar *caster, SI16 x, SI16 y, SI08 z, [[maybe_unused]] SI08 curSpell ) -> bool
{
if( LineOfSight( sock, caster, x, y, ( z + 15 ), WALLS_CHIMNEYS + DOORS + FLOORS_FLAT_ROOFING, false ) || caster->IsGM() )
{
UI16 j = caster->GetSkill( MAGERY );
SI32 revealRange = ((( j - 261 ) * ( 15 )) / 739 ) + 5;
//If the caster has a Magery of 26.1 (min to cast reveal w/ scroll), revealRange radius is
//5 tiles, if magery is maxed out at 100.0 (except for gms I suppose), revealRange is 20
for( auto &MapArea : MapRegion->PopulateList( caster ))
{
if( MapArea )
{
auto regChars = MapArea->GetCharList();
for( const auto &tempChar : regChars->collection() )
{
if( ValidateObject( tempChar ) && tempChar->GetInstanceId() == caster->GetInstanceId() )
{
if( tempChar->GetVisible() == VT_TEMPHIDDEN || tempChar->GetVisible() == VT_INVISIBLE )
{
Point3_st difference = ( tempChar->GetLocation() - Point3_st( x, y, z ));
if( difference.MagSquared() <= ( revealRange * revealRange )) // char to reveal is within radius or range
{
tempChar->ExposeToView();
CMagicStat temp = Magic->spells[48].StaticEffect();
if( temp.Effect() != INVALIDID )
{
Effects->PlayStaticAnimation( tempChar, temp.Effect(), temp.Speed(), temp.Loop() );
}
}
}
}
}
}
}
if( Magic->spells[48].Effect() != INVALIDID )
{
Effects->PlaySound( sock, Magic->spells[48].Effect(), true );
}
}
else
{
sock->SysMessage( 687 ); // You would like to see if anything was there, but there is too much stuff in the way.
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - ChainLightningStub()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Chain Lightning spell to an individual target
//o------------------------------------------------------------------------------------------------o
void ChainLightningStub( CChar *caster, CChar *target, SI08 curSpell, SI08 targCount )
{
if( target->IsNpc() && target->GetNpcAiType() == AI_PLAYERVENDOR )
return; // Player Vendors can't be killed
if( target->IsGM() || target->IsInvulnerable() )
return; // GMs/Invuls can't be killed
if( target->IsNpc() && target == caster )
return; // Don't let NPC hit themselves with damaging spell
if( target->IsNpc() && caster->IsNpc() )
{
if( target->GetNPCFlag() == caster->GetNPCFlag() )
return; // Don't let NPCs damage other NPCs with same NPC Flag (innocent, evil, neutral)
switch( caster->GetNpcAiType() )
{
case 2: // AI_EVIL
case 11: // AI_EVIL_CASTER
{
if( target->GetNpcAiType() == AI_EVIL || target->GetNpcAiType() == AI_EVIL_CASTER )
return; // Don't damage NPCs with similar AI types
break;
}
case 5: // AI_FIGHTER
case 10: // AI_CASTER
{
if( target->GetNpcAiType() == AI_FIGHTER || target->GetNpcAiType() == AI_CASTER )
return; // Don't damage NPCs with similar AI types
break;
}
default:
break;
}
}
if( target->IsNpc() )
{
Combat->AttackTarget( target, caster );
}
/*Effects->PlaySound( caster, 0x0029 );*/
[[maybe_unused]] CChar *def = nullptr;
if( Magic->CheckMagicReflect( target ))
{
def = caster;
}
else
{
def = target;
}
SI16 spellDamage = 0;
bool spellResisted = Magic->CheckResist( caster, target, Magic->spells[curSpell].Circle() );
spellDamage = Magic->spells[curSpell].BaseDmg();
spellDamage = CalcSpellDamageMod( caster, target, spellDamage, spellResisted );
//If targetCOunt is > 1, do equal damage to two targets, split damage on more targets
if( targCount > 1 )
{
spellDamage = ( spellDamage * 2 ) / targCount;
}
Effects->TempEffect( caster, target, 34, spellDamage, 0, 0 );
}
//o------------------------------------------------------------------------------------------------o
//| Function - splChainLightning()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Casts Chain Lightning as an area affect spell
//o------------------------------------------------------------------------------------------------o
bool splChainLightning( CSocket *sock, CChar *caster, [[maybe_unused]] CChar *target, [[maybe_unused]] CChar *src, SI08 curSpell )
{
AreaAffectSpell( sock, caster, &ChainLightningStub, curSpell );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splEnergyField()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Casts Energy Field spell as a field spell
//o------------------------------------------------------------------------------------------------o
bool splEnergyField( [[maybe_unused]] CSocket *sock, CChar *caster, UI08 fieldDir, SI16 x, SI16 y, SI08 z, SI08 curSpell )
{
if( fieldDir == 0 )
{
return FieldSpell( caster, 0x3946, x, y, z, fieldDir, curSpell );
}
else
{
return FieldSpell( caster, 0x3956, x, y, z, fieldDir, curSpell );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - splFlameStrike()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Flame Strike spell on target
//o------------------------------------------------------------------------------------------------o
bool splFlameStrike( CChar *caster, CChar *target, [[maybe_unused]] CChar *src, SI08 curSpell )
{
SI16 spellDamage = 0;
bool spellResisted = Magic->CheckResist( caster, target, Magic->spells[curSpell].Circle() );
spellDamage = Magic->spells[curSpell].BaseDmg();
spellDamage = CalcSpellDamageMod( caster, target, spellDamage, spellResisted );
Effects->TempEffect( caster, target, 35, spellDamage, 0, 0 );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splGateTravel()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Gate Travel spell (opens gate from recall rune)
//o------------------------------------------------------------------------------------------------o
bool splGateTravel( CSocket *sock, CChar *caster, CItem *i, [[maybe_unused]] SI08 curSpell )
{
// No recall if too heavy, GM's excempt
if( Weight->IsOverloaded( caster ) && !cwmWorldState->ServerData()->TravelSpellsWhileOverweight() && ( !caster->IsCounselor() && !caster->IsGM() ))
{
sock->SysMessage( 680 ); // You are too heavy to do that!
sock->SysMessage( 681 ); // You feel drained from the attempt.
return false;
}
else
{
// Check if rune was marked in a multi - if so, try to take user directly there
TAGMAPOBJECT runeMore = i->GetTag( "multiSerial" );
if( runeMore.m_StringValue != "" )
{
SERIAL mSerial = oldstrutil::value<SERIAL>( runeMore.m_StringValue );
if( mSerial != 0 && mSerial != INVALIDSERIAL )
{
CMultiObj *shipMulti = CalcMultiFromSer( mSerial );
if( ValidateObject( shipMulti ) && shipMulti->CanBeObjType( OT_BOAT ))
{
if(( shipMulti->WorldNumber() == caster->WorldNumber() || cwmWorldState->ServerData()->TravelSpellsBetweenWorlds() ) && shipMulti->GetInstanceId() == caster->GetInstanceId() )
{
if( shipMulti->WorldNumber() == 0 && caster->WorldNumber() != 0 && cwmWorldState->ServerData()->YoungPlayerSystem() && caster->GetAccount().wFlags.test( AB_FLAGS_YOUNG ) )
{
sock->SysMessage( 18733 ); // You decide against traveling to Felucca while you are still young.
return false;
}
caster->SetLocation( shipMulti->GetX() + 1, shipMulti->GetY(), shipMulti->GetZ() + 3 );
SpawnGate( caster, caster->GetX() + 1, caster->GetY() + 1, caster->GetZ(), caster->WorldNumber(), shipMulti->GetX() + 1, shipMulti->GetY(), shipMulti->GetZ() + 3, shipMulti->WorldNumber(), shipMulti->GetInstanceId() );
return true;
}
else
{
sock->SysMessage( 2064 ); // You are unable to open a gate to your ship - it might be in another world!
return false;
}
}
}
sock->SysMessage( 2062 ); // Unable to locate ship - it might have been dry-docked... or sunk!
return false;
}
else if( i->GetTempVar( CITV_MOREX ) == 0 && i->GetTempVar( CITV_MOREY ) == 0 )
{
sock->SysMessage( 679 ); // That rune has not been marked yet!
return false;
}
else
{
UI08 worldNum = static_cast<UI08>( i->GetTempVar( CITV_MORE ));
if( !Map->MapExists( worldNum ))
{
worldNum = caster->WorldNumber();
}
if( caster->WorldNumber() != worldNum && !cwmWorldState->ServerData()->TravelSpellsBetweenWorlds() )
{
sock->SysMessage( 2061 ); // Travelling between worlds using Recall or Gate spells is not possible.
return false;
}
if( worldNum == 0 && caster->WorldNumber() != 0 && cwmWorldState->ServerData()->YoungPlayerSystem() && caster->GetAccount().wFlags.test( AB_FLAGS_YOUNG ) )
{
sock->SysMessage( 18733 ); // You decide against traveling to Felucca while you are still young.
return false;
}
SI16 targLocX = static_cast<SI16>( i->GetTempVar( CITV_MOREX ));
SI16 targLocY = static_cast<SI16>( i->GetTempVar( CITV_MOREY ));
SI08 targLocZ = static_cast<SI08>( i->GetTempVar( CITV_MOREZ ));
UI16 instanceId = static_cast<UI16>( i->GetTempVar( CITV_MORE0 ));
SpawnGate( caster, caster->GetX() + 1, caster->GetY() + 1, caster->GetZ(), caster->WorldNumber(), targLocX, targLocY, targLocZ, worldNum, instanceId );
return true;
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - splManaVampire()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Mana Vampire spell to target
//o------------------------------------------------------------------------------------------------o
bool splManaVampire( CChar *caster, CChar *target, [[maybe_unused]] CChar *src, [[maybe_unused]] SI08 curSpell )
{
if( !Magic->CheckResist( caster, target, 7 ))
{
if( target->GetMana() < 40 )
{
caster->SetMana( caster->GetMana() + target->GetMana() );
target->SetMana( 0 );
}
else
{
target->SetMana( target->GetMana() - 40 );
caster->SetMana( caster->GetMana() + 40 );
}
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - MassDispelStub()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Mass Dispel spell to an individual target
//o------------------------------------------------------------------------------------------------o
void MassDispelStub( CChar *caster, CChar *target, [[maybe_unused]] SI08 curSpell, [[maybe_unused]] SI08 targCount )
{
if( target->IsDispellable() )
{
//Effects->PlayStaticAnimation( target, 0x372A, 0x09, 0x06 ); // why the specifics here?
if( target->IsNpc() )
{
std::vector<UI16> scriptTriggers = target->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
if( toExecute->OnDispel( target ) == 1 ) // if it exists and we don't want hard code, return
return;
}
}
// Base chance of dispelling on Dispel caster's Magery skill vs summoner's magicresistance
UI16 casterMagery = caster->GetSkill( MAGERY );
UI16 targetResist = 0;
targetResist = target->GetSkill( MAGICRESISTANCE );
// If creature was summoned, use highest of creature's resist and summoner's resist
if( target->GetOwner() != INVALIDSERIAL )
{
targetResist = std::max( targetResist, target->GetOwnerObj()->GetSkill( MAGICRESISTANCE ));
}
UI16 dispelChance = static_cast<UI16>( std::max( static_cast<UI16>( 950 ), static_cast<UI16>( std::ceil(( 500 - ( targetResist - casterMagery )) / 1.5 ))));
if( dispelChance > RandomNum( 0, 1000 ))
{
// Dispel succeeded!
if( Magic->spells[41].Effect() != INVALIDID )
{
Effects->PlaySound( target, Magic->spells[41].Effect() );
}
CMagicStat temp = Magic->spells[41].StaticEffect();
if( temp.Effect() != INVALIDID )
{
// Play static effect at target's location, since target gets destroyed
Effects->PlayStaticAnimation( target->GetX(), target->GetY(), target->GetZ(), temp.Effect(), temp.Speed(), temp.Loop(), false );
}
target->Delete();
}
else if( caster->GetSocket() != nullptr )
{
// Dispel resisted!
caster->GetSocket()->SysMessage( 2761 ); // The creature resisted the attempt to dispel it!
}
}
else
{
HandleDeath( target, nullptr );
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - splMassDispel()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Casts Mass Dispel spell as an area affect spell
//o------------------------------------------------------------------------------------------------o
bool splMassDispel( CSocket *sock, CChar *caster, [[maybe_unused]] CChar *target, [[maybe_unused]] CChar *src, SI08 curSpell )
{
AreaAffectSpell( sock, caster, &MassDispelStub, curSpell );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - MeteorSwarmStub()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Meteor Swarm spell to an individual target
//o------------------------------------------------------------------------------------------------o
void MeteorSwarmStub( CChar *caster, CChar *target, SI08 curSpell, SI08 targCount )
{
if( target->IsNpc() && target->GetNpcAiType() == AI_PLAYERVENDOR )
return; // Player Vendors can't be killed
if( target->IsInvulnerable() )
return; // GMs/Invuls can't be killed
if( target->IsNpc() && target == caster )
return; // Don't let NPC hit themselves with damaging spell
if( target->IsNpc() && caster->IsNpc() )
{
if( target->GetNPCFlag() == caster->GetNPCFlag() )
return; // Don't let NPCs damage other NPCs with same NPC Flag (innocent, evil, neutral)
switch( caster->GetNpcAiType() )
{
case 2: // AI_EVIL
case 11: // AI_EVIL_CASTER
{
if( target->GetNpcAiType() == AI_EVIL || target->GetNpcAiType() == AI_EVIL_CASTER )
return; // Don't damage NPCs with similar AI types
break;
}
case 5: // AI_FIGHTER
case 10: // AI_CASTER
{
if( target->GetNpcAiType() == AI_FIGHTER || target->GetNpcAiType() == AI_CASTER )
return; // Don't damage NPCs with similar AI types
break;
}
default:
break;
}
}
if( target->IsNpc() )
{
Combat->AttackTarget( target, caster );
}
SI16 spellDamage = 0;
bool spellResisted = Magic->CheckResist( caster, target, Magic->spells[curSpell].Circle() );
spellDamage = Magic->spells[curSpell].BaseDmg();
spellDamage = CalcSpellDamageMod( caster, target, spellDamage, spellResisted );
//If targetCOunt is > 1, do equal damage to two targets, split damage on more targets
if( targCount > 1 )
{
spellDamage = ( spellDamage * 2 ) / targCount;
}
Effects->TempEffect( caster, target, 36, spellDamage, 0, 0 );
}
//o------------------------------------------------------------------------------------------------o
//| Function - AreaAffectSpell()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Provides shared functionality for all area affect spells
//o------------------------------------------------------------------------------------------------o
auto AreaAffectSpell( CSocket *sock, CChar *caster, void (*trgFunc)( MAGIC_AREA_STUB_LIST ), SI08 curSpell ) -> bool
{
SI16 x1, x2, y1, y2;
SI08 z1, z2;
x1 = x2 = y1 = y2 = z1 = z2 = 0;
SI08 targCount = 0;
UI16 i;
std::vector<CChar *> targetList;
Magic->BoxSpell( sock, caster, x1, x2, y1, y2, z1, z2 );
for( auto &MapArea : MapRegion->PopulateList( caster ))
{
if( MapArea )
{
auto regChars = MapArea->GetCharList();
for( const auto &tempChar : regChars->collection() )
{
if( ValidateObject( tempChar ) && tempChar->GetInstanceId() == caster->GetInstanceId() )
{
if( tempChar->GetX() >= x1 && tempChar->GetX() <= x2 && tempChar->GetY() >= y1 && tempChar->GetY() <= y2 &&
tempChar->GetZ() >= z1 && tempChar->GetZ() <= z2 && ( IsOnline(( *tempChar )) || tempChar->IsNpc() ))
{
if( tempChar == caster || LineOfSight( sock, caster, tempChar->GetX(), tempChar->GetY(), ( tempChar->GetZ() + 15 ), WALLS_CHIMNEYS + DOORS + FLOORS_FLAT_ROOFING, false ) || caster->IsGM() )
{
//Store target candidates in targetList
targetList.push_back( tempChar );
//trgFunc( caster, tempChar, curSpell, targCount );
}
else if( sock )
{
sock->SysMessage( 688 ); // You cannot see the target well.
}
}
}
}
}
}
// Iterate through list of valid targets, do damage
targCount = static_cast<SI08>( targetList.size() );
for( i = 0; i < targetList.size(); ++i )
{
trgFunc( caster, targetList[i], curSpell, targCount );
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splMeteorSwarm()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Casts Meteor Swarm spell as an area affect spell
//o------------------------------------------------------------------------------------------------o
bool splMeteorSwarm( CSocket *sock, CChar *caster, [[maybe_unused]] CChar *target, [[maybe_unused]] CChar *src, SI08 curSpell )
{
AreaAffectSpell( sock, caster, &MeteorSwarmStub, curSpell );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splPolymorph()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Polymorph spell
//o------------------------------------------------------------------------------------------------o
bool splPolymorph( CSocket *sock, CChar *caster, [[maybe_unused]] SI08 curSpell )
{
if( caster->IsPolymorphed() )
{
sock->SysMessage( 1637 ); // You cannot polymorph while already in another form
}
else if( caster->IsIncognito() )
{
sock->SysMessage( 1673 ); // You can't do that while incognito.
}
else if( caster->IsDisguised() )
{
sock->SysMessage( 9233 ); // You cannot polymorph while disguised.
}
else if( caster->GetId() == 0x00b8 || caster->GetId() == 0x00b7 ) // Savage Kin Paint transformation
{
sock->SysMessage( 1671 ); // You cannot polymorph while wearing body paint
}
else
{
Magic->PolymorphMenu( sock, POLYMORPHMENUOFFSET ); // Polymorph
return true;
}
return false;
}
//o------------------------------------------------------------------------------------------------o
//| Function - EarthquakeStub()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Earthquake spell to an individual target
//o------------------------------------------------------------------------------------------------o
void EarthquakeStub( CChar *caster, CChar *target, SI08 curSpell, [[maybe_unused]] SI08 targCount )
{
// Check if target is in a safe zone, or invulnerable
if( target->GetRegion()->IsSafeZone() || target->IsInvulnerable() )
{
// Target is in a safe zone, or invulnerable ignore spell effect
return;
}
if( target->IsNpc() && target == caster )
return; // Don't let NPC hit themselves with damaging spell
if( target->IsNpc() && caster->IsNpc() )
{
if( target->GetNPCFlag() == caster->GetNPCFlag() )
return; // Don't let NPCs damage other NPCs with same NPC Flag (innocent, evil, neutral)
switch( caster->GetNpcAiType() )
{
case 2: // AI_EVIL
case 11: // AI_EVIL_CASTER
{
if( target->GetNpcAiType() == AI_EVIL || target->GetNpcAiType() == AI_EVIL_CASTER )
return; // Don't damage NPCs with similar AI types
break;
}
case 5: // AI_FIGHTER
case 10: // AI_CASTER
{
if( target->GetNpcAiType() == AI_FIGHTER || target->GetNpcAiType() == AI_CASTER )
return; // Don't damage NPCs with similar AI types
break;
}
default:
break;
}
}
SI32 distx = abs( target->GetX() - caster->GetX() );
SI32 disty = abs( target->GetY() - caster->GetY() );
SI32 dmgmod = std::min( distx, disty );
dmgmod = -( dmgmod - 7 );
SI16 spellDamage = 0;
bool spellResisted = Magic->CheckResist( caster, target, Magic->spells[curSpell].Circle() );
spellDamage = Magic->spells[curSpell].BaseDmg();
spellDamage = CalcSpellDamageMod( caster, target, spellDamage, spellResisted );
//If targetCOunt is > 1, do equal damage to two targets, split damage on more targets
//if( targCount > 1 )
// spellDamage = ( spellDamage * 2 ) / targCount;
//Apply bonus damage based on target distance from center of earthquake
spellDamage += dmgmod;
if( target->Damage( spellDamage, LIGHTNING, caster, true ))
{
// If this killed the target, don't continue
if( target->IsDead() )
return;
}
target->SetStamina( target->GetStamina() - ( RandomNum( 0, 9 ) + 5 ));
if( target->GetStamina() == -1 )
{
target->SetStamina( 0 );
}
if(( !target->IsNpc() && IsOnline(( *target ))) || ( target->IsNpc() && cwmWorldState->creatures[target->GetId()].IsHuman() ))
{
if( !target->IsOnHorse() && !target->IsFlying() )
{
if( target->GetBodyType() == BT_GARGOYLE )
{
Effects->PlayNewCharacterAnimation( target, N_ACT_IMPACT ); // Impact anim (0x04) - can't seem to trigger death anim manually with new animation packet
}
else // BT_HUMAN and BT_ELF
{
if( RandomNum( 0, 1 ))
{
Effects->PlayCharacterAnimation( target, ACT_DIE_BACKWARD, 0, 6 ); // Death anim variation 1 - 0x15, forward
}
else
{
Effects->PlayCharacterAnimation( target, ACT_DIE_FORWARD, 0, 6 ); // Death anim variation 2 - 0x16, backward
}
}
}
}
else // Monsters, animals
{
if( target->GetHP() > 0 )
{
Effects->PlayCharacterAnimation( target, 0x2, 0, 4 );
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - splEarthquake()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Casts Earthquake spell as an area affect spell
//o------------------------------------------------------------------------------------------------o
bool splEarthquake( CSocket *sock, CChar *caster, SI08 curSpell )
{
MakeCriminal( caster );
if( sock != nullptr )
{
sock->SetWord( 11, caster->GetX() );
sock->SetWord( 13, caster->GetY() );
sock->SetByte( 16, caster->GetZ() );
}
AreaAffectSpell( sock, caster, &EarthquakeStub, curSpell );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splEnergyVortex()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Energy Vortex spell (summons an Energy Vortex)
//o------------------------------------------------------------------------------------------------o
bool splEnergyVortex( CSocket *sock, CChar *caster, SI16 x, SI16 y, SI08 z, [[maybe_unused]] SI08 curSpell )
{
if( caster->GetSkill( MAGERY ) <= 800 )
return false;
Magic->SummonMonster( sock, caster, 1, x, y, z );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splResurrection()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Resurrection spell to a target
//o------------------------------------------------------------------------------------------------o
bool splResurrection( [[maybe_unused]] CChar *caster, CChar *target, [[maybe_unused]] CChar *src, [[maybe_unused]] SI08 curSpell )
{
if( target->IsDead() )
{
NpcResurrectTarget( target );
return true;
}
return false;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splSummonAir()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Summon Air Elemental spell (Summons Air Elemental)
//o------------------------------------------------------------------------------------------------o
bool splSummonAir( CSocket *sock, CChar *caster, [[maybe_unused]] SI08 curSpell )
{
if( caster->GetSkill( MAGERY ) <= 800 )
return false;
Magic->SummonMonster( sock, caster, 2, caster->GetX() + 1, caster->GetY() + 1, caster->GetZ() );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splSummonDaemon()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Summon Daemon spell (Summons Daemon)
//o------------------------------------------------------------------------------------------------o
bool splSummonDaemon( CSocket *sock, CChar *caster, [[maybe_unused]] SI08 curSpell )
{
if( caster->GetSkill( MAGERY ) <= 800 )
return false;
Magic->SummonMonster( sock, caster, 7, caster->GetX() + 1, caster->GetY() + 1, caster->GetZ() );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splSummonEarth()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Summon Earth Elemental spell (Summons Earth Elemental)
//o------------------------------------------------------------------------------------------------o
bool splSummonEarth( CSocket *sock, CChar *caster, [[maybe_unused]] SI08 curSpell )
{
if( caster->GetSkill( MAGERY ) <= 800 )
return false;
Magic->SummonMonster( sock, caster, 3, caster->GetX() + 1, caster->GetY() + 1, caster->GetZ() );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splSummonFire()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Summon Fire Elemental spell (Summons Fire Elemental)
//o------------------------------------------------------------------------------------------------o
bool splSummonFire( CSocket *sock, CChar *caster, [[maybe_unused]] SI08 curSpell )
{
if( caster->GetSkill( MAGERY ) <= 800 )
return false;
Magic->SummonMonster( sock, caster, 4, caster->GetX() + 1, caster->GetY() + 1, caster->GetZ() );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splSummonWater()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Summon Water Elemental spell (Summons Water Elemental)
//o------------------------------------------------------------------------------------------------o
bool splSummonWater( CSocket *sock, CChar *caster, [[maybe_unused]] SI08 curSpell )
{
if( caster->GetSkill( MAGERY ) <= 800 )
return false;
Magic->SummonMonster( sock, caster, 5, caster->GetX() + 1, caster->GetY() + 1, caster->GetZ() );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splRandom()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Summon Monster spell (Summons random monster)
//o------------------------------------------------------------------------------------------------o
bool splRandom( CSocket *sock, CChar *caster, [[maybe_unused]] SI08 curSpell )
{
if( caster->GetSkill( MAGERY ) <= 800 )
return false;
Magic->SummonMonster( sock, caster, 8, caster->GetX() + 1, caster->GetY() + 1, caster->GetZ() );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splNecro1()
//o------------------------------------------------------------------------------------------------o
//| Purpose - UNUSED
//o------------------------------------------------------------------------------------------------o
bool splNecro1( [[maybe_unused]] CChar *caster, [[maybe_unused]] CChar *target, [[maybe_unused]] CChar *src, [[maybe_unused]] SI08 curSpell )
{
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splNecro2()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effect of Necromantic summon spell (summons monster)
//o------------------------------------------------------------------------------------------------o
bool splNecro2( CSocket *sock, CChar *caster, [[maybe_unused]] SI08 curSpell )
{
Magic->SummonMonster( sock, caster, 9, caster->GetX() +1, caster->GetY() +1, caster->GetZ() );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splNecro3()
//o------------------------------------------------------------------------------------------------o
//| Purpose - UNUSED
//o------------------------------------------------------------------------------------------------o
bool splNecro3( [[maybe_unused]] CChar *caster, [[maybe_unused]] CChar *target, [[maybe_unused]] CChar *src, [[maybe_unused]] SI08 curSpell )
{
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splNecro4()
//o------------------------------------------------------------------------------------------------o
//| Purpose - UNUSED
//o------------------------------------------------------------------------------------------------o
bool splNecro4( [[maybe_unused]] CChar *caster, [[maybe_unused]] CChar *target, [[maybe_unused]] CChar *src, [[maybe_unused]] SI08 curSpell )
{
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - splNecro5()
//o------------------------------------------------------------------------------------------------o
//| Purpose - UNUSED
//o------------------------------------------------------------------------------------------------o
bool splNecro5( [[maybe_unused]] CChar *caster, [[maybe_unused]] CChar *target, [[maybe_unused]] CChar *src, [[maybe_unused]] SI08 curSpell )
{
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - DiamondSpell()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Casts field spell in the shape of a diamond
//o------------------------------------------------------------------------------------------------o
bool DiamondSpell( [[maybe_unused]] CSocket *sock, CChar *caster, UI16 id, SI16 x, SI16 y, SI08 z, UI08 length )
{
SI32 j;
SI16 fx[5], fy[5];
SI16 yOffset = length;
SI16 xOffset = length;
fx[0] = -xOffset; fy[0] = 0;
fx[1] = 0; fy[1] = yOffset;
fx[2] = 0; fy[2] = -yOffset;
fx[3] = xOffset; fy[3] = 0;
CItem *i = nullptr;
UI08 worldNumber = caster->WorldNumber();
UI16 instanceId = caster->GetInstanceId();
for( j = 0; j < 4; ++j ) // Draw the corners of our diamond
{
i = Items->CreateItem( nullptr, caster, id, 1, 0, OT_ITEM );
if( i != nullptr )
{
i->SetDispellable( true );
i->SetDecayable( true );
i->SetDecayTime( BuildTimeValue( static_cast<R32>( caster->GetSkill( MAGERY ) / 15 )));
i->SetTempVar( CITV_MOREX, caster->GetSkill( MAGERY )); // remember casters magery skill for damage
i->SetTempVar( CITV_MOREY, caster->GetSerial() );
i->SetLocation( x + fx[j], y + fy[j], Map->Height( x + fx[j], y + fy[j], z, worldNumber, instanceId ));
i->SetDir( 29 );
i->SetMovable( 2 );
}
}
for( j = -1; j < 2; j = j + 2 )
{
for( SI08 counter2 = -1; counter2 < 2; counter2 += 2 )
{
for( SI32 counter3 = 1; counter3 < yOffset; ++counter3 )
{
i = Items->CreateItem( nullptr, caster, id, 1, 0, OT_ITEM );
if( i != nullptr )
{
i->SetDispellable( true );
i->SetDecayable( true );
i->SetDecayTime( BuildTimeValue(static_cast<R32>( caster->GetSkill( MAGERY ) / 15 )));
i->SetTempVar( CITV_MOREX, caster->GetSkill( MAGERY )); // remember casters magery skill for damage
i->SetTempVar( CITV_MOREY, caster->GetSerial() );
const SI16 newX = x + counter2 * counter3;
const SI16 newY = y + j * ( yOffset - counter3 );
i->SetLocation( newX, newY, Map->Height( newX, newY, z, worldNumber, instanceId ));
i->SetDir( 29 );
i->SetMovable( 2 );
}
}
}
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - SquareSpell()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Casts field spell in the shape of a square
//o------------------------------------------------------------------------------------------------o
bool SquareSpell( [[maybe_unused]] CSocket *sock, CChar *caster, UI16 id, SI16 x, SI16 y, SI08 z, UI08 length )
{
SI16 fx[5], fy[5];
SI16 yOffset = length;
SI16 xOffset = length;
fx[0] = -xOffset; fy[0] = xOffset;
fx[1] = -yOffset; fy[1] = yOffset;
fx[2] = -xOffset; fy[2] = xOffset;
fx[3] = -yOffset; fy[3] = yOffset;
CItem *i = nullptr;
UI08 worldNumber = caster->WorldNumber();
UI16 instanceId = caster->GetInstanceId();
for( UI08 j = 0; j < 4; ++j ) // Draw the corners of our diamond
{
for( SI32 counter = fx[j]; counter < fy[j]; ++counter )
{
i = Items->CreateItem( nullptr, caster, id, 1, 0, OT_ITEM );
if( i != nullptr )
{
i->SetDispellable( true );
i->SetDecayable( true );
i->SetDecayTime( BuildTimeValue( static_cast<R32>( caster->GetSkill( MAGERY ) / 15 )));
i->SetTempVar( CITV_MOREX, caster->GetSkill( MAGERY )); // remember casters magery skill for damage
i->SetTempVar( CITV_MOREY, caster->GetSerial() );
switch( j )
{
case 0:
case 2:
i->SetLocation( x + counter, y + (j - 1) * yOffset, Map->Height( x + counter, y + (j-1) * yOffset, z, worldNumber, instanceId ));
break;
case 1:
case 3:
i->SetLocation( x + (j-2 ) * xOffset, y + counter, Map->Height( x + (j-2 ) * xOffset, y + counter, z, worldNumber, instanceId ));
break;
}
i->SetDir( 29 );
i->SetMovable( 2 );
}
}
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - FloodSpell()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Casts field spell in the shape of a filled square
//o------------------------------------------------------------------------------------------------o
bool FloodSpell( [[maybe_unused]] CSocket *sock, CChar *caster, UI16 id, SI16 x, SI16 y, SI08 z, UI08 length )
{
SI16 yOffset = length;
SI16 xOffset = length;
SI16 maxX = x + xOffset;
SI16 maxY = y + yOffset;
SI16 minX = x - xOffset;
SI16 minY = y - yOffset;
UI08 worldNumber = caster->WorldNumber();
UI16 instanceId = caster->GetInstanceId();
for( SI32 counter1 = minX; counter1 <= maxX; ++counter1 )
{
for( SI32 counter2 = minY; counter2 <= maxY; ++counter2 )
{
CItem *i = Items->CreateItem( nullptr, caster, id, 1, 0, OT_ITEM );
if( i != nullptr )
{
i->SetDispellable( true );
i->SetDecayable( true );
i->SetDecayTime( BuildTimeValue( static_cast<R32>( caster->GetSkill( MAGERY ) / 15 )));
i->SetTempVar( CITV_MOREX, caster->GetSkill( MAGERY )); // remember casters magery skill for damage
i->SetTempVar( CITV_MOREY, caster->GetSerial() );
i->SetLocation( counter1, counter2, Map->Height( counter1, counter2, z, worldNumber, instanceId ));
i->SetDir( 29 );
i->SetMovable( 2 );
}
}
}
return true;
}
/////////////////////////////////////////////////////////////////
/// INDEX:
// - misc magic functions
// - NPCs casting spells related functions
// - ITEMs magic powers related functions
// - PCs casting spells related functions
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
/////// MISC MAGIC FUNCTIONS ////////////////////////////////////
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
// added constructor/destructor
CMagic::CMagic() : spellCount( 1 )
{
spells.resize( 0 );
}
CMagic::~CMagic()
{
// delete any allocations
spells.clear();
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::HasSpell()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Checks if given spellbook contains specific spell
//o------------------------------------------------------------------------------------------------o
bool CMagic::HasSpell( CItem *book, SI32 spellNum )
{
if( !ValidateObject( book ))
return false;
UI32 wordNum = spellNum / 32;
UI32 bitNum = ( spellNum % 32 );
UI32 flagToCheck = power( 2, bitNum );
UI32 sourceAmount = 0;
if( wordNum < 3 )
{
sourceAmount = book->GetSpell( static_cast<UI08>( wordNum ));
}
return (( sourceAmount & flagToCheck ) == flagToCheck);
}
//o------------------------------------------------------------------------------------------------o
//| Function - AddSpell()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Adds specific spell to given spellbook
//o------------------------------------------------------------------------------------------------o
void CMagic::AddSpell( CItem *book, SI32 spellNum )
{
std::vector<UI16> scriptTriggers = book->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
// If script returns false/0, prevent addition of spell to spellbook
if( toExecute->OnSpellGain( book, spellNum ) == 0 )
{
return;
}
}
}
if( !ValidateObject( book ))
return;
UI32 wordNum = spellNum / 32;
if( wordNum < 3 )
{
UI32 bitNum = ( spellNum % 32 );
UI32 flagToSet = power( 2, bitNum );
UI32 targAmount;
targAmount = ( book->GetSpell( static_cast<UI08>( wordNum )) | flagToSet );
book->SetSpell( static_cast<UI08>( wordNum ), targAmount );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::RemoveSpell()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Removes specific spell from given spellbook
//o------------------------------------------------------------------------------------------------o
void CMagic::RemoveSpell( CItem *book, SI32 spellNum )
{
std::vector<UI16> scriptTriggers = book->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
// If script returns false/0, prevent removal of spell from spellbook
if( toExecute->OnSpellLoss( book, spellNum ) == 0 )
{
return;
}
}
}
if( !ValidateObject( book ))
return;
UI32 wordNum = spellNum / 32;
if( wordNum < 3 )
{
UI32 bitNum = ( spellNum % 32 );
UI32 flagToSet = power( 2, bitNum );
UI32 flagMask = 0xFFFFFFFF;
UI32 targAmount;
flagMask ^= flagToSet;
targAmount = ( book->GetSpell( static_cast<UI08>( wordNum )) & flagMask );
book->SetSpell( static_cast<UI08>( wordNum ), targAmount );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::SpellBook()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Opens the spellbook and displays all spells a character has in his book
//o------------------------------------------------------------------------------------------------o
void CMagic::SpellBook( CSocket *mSock )
{
UI08 spellsList[70];
SERIAL serial = ( mSock->GetDWord( 1 ) & 0x7FFFFFFF );
CChar *mChar = mSock->CurrcharObj();
CItem *spellBook = CalcItemObjFromSer( serial );
// Here's the kicker. We NEVER needed to search through our pack for our spellbook!!! Why, you might ask???
// Because we are able to calculate the serial of the spellbook, because it's targetted for us!!!!
// Hence, we can remove all searches from here, and just use it directly
// The only time we need to search is when they use the macro, and that's easily done (item == -1)
if( !ValidateObject( spellBook )) // used a macro!!!
{
spellBook = FindItemOfType( mChar, IT_SPELLBOOK );
}
if( !ValidateObject( spellBook )) // we STILL have no spellbook!!!
{
mSock->SysMessage( 692 ); // You have no spellbook upon you!
return;
}
CItem *x = mChar->GetPackItem();
if( spellBook->GetCont() != mChar && spellBook->GetCont() != x )
{
mSock->SysMessage( 403 ); // If you wish to open a spellbook, it must be equipped or in your main backpack.
return;
}
CPDrawContainer sbStart(( *spellBook ));
sbStart.Model( 0xFFFF );
if( mSock->ClientType() >= CV_HS2D && mSock->ClientVerShort() >= CVS_7090 )
{
sbStart.ContType( 0x7D );
}
mSock->Send( &sbStart );
// Support for new Client Spellbook
CPNewSpellBook ourBook(( *spellBook ));
if( ourBook.ClientCanReceive( mSock ))
{
mSock->Send( &ourBook );
return;
}
else
{
memset( spellsList, 0, sizeof( UI08 ) * 70 );
for( UI08 j = 0; j < 65; ++j )
{
if( HasSpell( spellBook, j ))
{
spellsList[j] = 1;
}
}
UI08 i;
i = spellsList[0];
spellsList[0] = spellsList[1];
spellsList[1] = spellsList[2];
spellsList[2] = spellsList[3];
spellsList[3] = spellsList[4];
spellsList[4] = spellsList[5];
spellsList[5] = spellsList[6];
spellsList[6] = i;
if( spellsList[64] )
{
for( i = 0; i < 65; ++i )
{
spellsList[i] = 1;
}
spellsList[64] = 0;
}
spellsList[64] = spellsList[65];
spellsList[65] = 0;
UI08 scount = 0;
for( i = 0; i < 65; ++i )
{
if( spellsList[i] )
{
++scount;
}
}
if( scount > 0 )
{
CPItemsInContainer mItems;
if( mSock->ClientVerShort() >= CVS_6017 )
{
mItems.UOKRFlag( true );
}
mItems.NumberOfItems( scount );
UI16 runningCounter = 0;
const SERIAL CONTSER = spellBook->GetSerial();
for( i = 0; i < 65; ++i )
{
if( spellsList[i] )
{
mItems.Add( runningCounter++, 0x41000000 + i, CONTSER, i + 1 );
}
}
mSock->Send( &mItems );
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::GateCollision()
//| Changes - collision dir
//o------------------------------------------------------------------------------------------------o
//| Purpose - Used when a PLAYER passes through a gate. Takes the player
//| to the other side of the gate-link.
//o------------------------------------------------------------------------------------------------o
auto CMagic::GateCollision( CSocket *mSock, CChar *mChar, CItem *itemCheck, ItemTypes type ) -> void
{
if( type == IT_GATE )
{
if( !mChar->IsNpc() )
{
auto otherGate = CalcItemObjFromSer( itemCheck->GetTempVar( CITV_MOREX ));
if( ValidateObject( otherGate ))
{
SI08 dirOffset = 0;
if( mChar->GetDir() < SOUTH )
{
dirOffset = 1;
}
else
{
dirOffset = -1;
}
if( otherGate->WorldNumber() != mChar->WorldNumber() )
{
mChar->SetLocation( otherGate->GetX() + dirOffset, otherGate->GetY(), otherGate->GetZ(), otherGate->WorldNumber(), otherGate->GetInstanceId() );
SendMapChange( mChar->WorldNumber(), mSock, false );
}
else
{
mChar->SetLocation( otherGate->GetX() + dirOffset, otherGate->GetY(), otherGate->GetZ(), otherGate->WorldNumber(), otherGate->GetInstanceId() );
}
// Teleport player's followers too
auto myFollowers = mChar->GetFollowerList();
for( const auto &myFollower : myFollowers->collection() )
{
if( ValidateObject( myFollower ))
{
if( !myFollower->GetMounted() && myFollower->GetOwnerObj() == mChar )
{
if( myFollower->GetNpcWander() == WT_FOLLOW && ObjInOldRange( mChar, myFollower, DIST_CMDRANGE ))
{
myFollower->SetLocation( mChar );
}
}
}
}
Effects->PlaySound( mSock, 0x01FE, true );
Effects->PlayStaticAnimation( mChar, 0x372A, 0x09, 0x06 );
}
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::SummonMonster()
//| Changes - May 4th, 2000 - made function general words PC's and NPC's
//o------------------------------------------------------------------------------------------------o
//| Purpose - Summon a monster (dispellable with DISPEL).
//o------------------------------------------------------------------------------------------------o
void CMagic::SummonMonster( CSocket *s, CChar *caster, UI16 id, SI16 x, SI16 y, SI08 z )
{
bool validSocket = ( s != nullptr );
if( validSocket )
{
caster = s->CurrcharObj();
}
else
{ // Since caster will determine who is casting the spell... get a socket for players to see animations and hear effects
if( caster->IsNpc() && !caster->GetTarg()->IsNpc() )
{
s = caster->GetTarg()->GetSocket();
}
}
UI08 maxControlSlots = cwmWorldState->ServerData()->MaxControlSlots();
UI08 maxFollowers = cwmWorldState->ServerData()->MaxFollowers();
UI08 controlSlotsUsed = caster->GetControlSlotsUsed();
UI08 followerCount = static_cast<UI08>( caster->GetFollowerList()->Num() );
CChar *newChar=nullptr;
switch( id )
{
case 0: // summon monster
{
newChar = Npcs->CreateRandomNPC( "10000" );
if( !ValidateObject( newChar ))
{
if( validSocket )
{
s->SysMessage( 694 ); // Contact your shard op to setup the summon list!
}
return;
}
if( maxControlSlots > 0 )
{
if( controlSlotsUsed + newChar->GetControlSlots() > maxControlSlots )
{
newChar->Delete();
if( validSocket )
{
s->SysMessage( 2390 ); // That would exceed your maximum pet control slots.
}
return;
}
}
else if( maxFollowers > 0 && followerCount >= maxFollowers )
{
newChar->Delete();
if( validSocket )
{
s->SysMessage( 2346, maxFollowers ); // You can maximum have %i pets / followers active at the same time.
}
return;
}
if( Magic->spells[40].Effect() != INVALIDID )
{
Effects->PlaySound( newChar, Magic->spells[40].Effect() );
}
CMagicStat temp = Magic->spells[40].StaticEffect();
if( temp.Effect() != INVALIDID )
{
Magic->DoStaticEffect( newChar, 40 );
}
newChar->SetOwner( caster );
caster->SetControlSlotsUsed( std::clamp( controlSlotsUsed + newChar->GetControlSlots(), 0, 255 ));
newChar->SetTimer( tNPC_SUMMONTIME, BuildTimeValue( static_cast<R32>( caster->GetSkill( MAGERY ) / 5 )));
newChar->SetLocation( caster );
Effects->PlayCharacterAnimation( newChar, ACT_SPELL_AREA, 0, 7 ); // 0x11, used to be 0x0C
newChar->SetFTarg( caster );
newChar->SetNpcWander( WT_FOLLOW );
newChar->SetDispellable( true );
if( validSocket )
{
s->SysMessage( 695 ); // Your pet beings are following you.
}
return;
}
case 1: // Energy Vortex
Effects->PlaySound( s, 0x0216, true ); // EV
newChar = Npcs->CreateNPCxyz( "energyvortex-summon", 0, 0, 0, caster->WorldNumber(), caster->GetInstanceId() );
break;
case 2: // Air Elemental
Effects->PlaySound( s, 0x0217, true ); // AE
newChar = Npcs->CreateNPCxyz( "airele-summon", 0, 0, 0, caster->WorldNumber(), caster->GetInstanceId() );
break;
case 3: //Earth Elemental
Effects->PlaySound( s, 0x0217, true );
newChar = Npcs->CreateNPCxyz( "earthele-summon", 0, 0, 0, caster->WorldNumber(), caster->GetInstanceId() );
break;
case 4: //Fire Elemental
Effects->PlaySound( s, 0x0217, true );
newChar = Npcs->CreateNPCxyz( "firele-summon", 0, 0, 0, caster->WorldNumber(), caster->GetInstanceId() );
break;
case 5: //Water Elemental
Effects->PlaySound( s, 0x0217, true );
newChar = Npcs->CreateNPCxyz( "waterele-summon", 0, 0, 0, caster->WorldNumber(), caster->GetInstanceId() );
break;
case 6: //Blade Spirits
Effects->PlaySound( s, 0x0212, true ); // I don't know if this is the right effect...
newChar = Npcs->CreateNPCxyz( "bladespirit-summon", 0, 0, 0, caster->WorldNumber(), caster->GetInstanceId() );
break;
case 7: // Daemon
Effects->PlaySound( s, 0x0216, true );
newChar = Npcs->CreateNPCxyz( "daemon-summon", 0, 0, 0, caster->WorldNumber(), caster->GetInstanceId() );
break;
case 8: // Dupre The Hero
Effects->PlaySound( s, 0x0246, true );
newChar = Npcs->CreateNPCxyz( "dupre-summon", 0, 0, 0, caster->WorldNumber(), caster->GetInstanceId() );
break;
case 9: // Black Night
Effects->PlaySound( s, 0x0216, true );
newChar = Npcs->CreateNPCxyz( "blacknight-summon", 0, 0, 0, caster->WorldNumber(), caster->GetInstanceId() );
break;
default:
Effects->PlaySound( s, 0x0215, true );
}
if( !ValidateObject( newChar ))
return;
if( maxControlSlots > 0 )
{
if( controlSlotsUsed + newChar->GetControlSlots() > maxControlSlots )
{
newChar->Delete();
if( validSocket )
{
s->SysMessage( 2390 ); // That would exceed your maximum pet control slots.
}
return;
}
}
else if( maxFollowers > 0 && followerCount >= maxFollowers )
{
newChar->Delete();
if( validSocket )
{
s->SysMessage( 2346, maxFollowers ); // You can maximum have %i pets / followers active at the same time.
}
return;
}
newChar->SetDispellable( true );
// pc's don't own BS/EV, NPCs do
if( caster->IsNpc() || ( id != 1 && id != 6 ))
{
newChar->SetOwner( caster );
if( !caster->IsNpc() )
{
// Update control slots used
caster->SetControlSlotsUsed( std::clamp(controlSlotsUsed + newChar->GetControlSlots(), 0, 255) );
}
}
if( x == 0 )
{
newChar->SetLocation( caster->GetX()-1, caster->GetY(), caster->GetZ() );
}
else
{
if( Map->ValidSpawnLocation( x, y, z, caster->WorldNumber(), caster->GetInstanceId() ))
{
newChar->SetLocation( x, y, z );
}
else
{
newChar->SetLocation( caster->GetX()-1, caster->GetY(), caster->GetZ() );
}
}
newChar->SetSpDelay( 10 );
newChar->SetTimer( tNPC_SUMMONTIME, BuildTimeValue( static_cast<R32>( caster->GetSkill( MAGERY ) / 5 )));
Effects->PlayCharacterAnimation( newChar, ACT_SPELL_AREA, 0, 7 ); // 0x11, used to be 0x0C
// (9/99) - added the chance to make the monster attack
// the person you targeted ( if you targeted a char, naturally :) )
CChar *i = nullptr;
if( !caster->IsNpc() && s != nullptr )
{
if( s->GetDWord( 7 ) != INVALIDSERIAL )
{
i = CalcCharObjFromSer( s->GetDWord( 7 ));
if( !ValidateObject( i ))
return;
}
}
else
{
i = caster->GetTarg();
}
Combat->AttackTarget( newChar, i );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::CheckBook()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Check if the spell is memorized into the spellbook.
//o------------------------------------------------------------------------------------------------o
bool CMagic::CheckBook( SI32 circle, SI32 spell, CItem *i )
{
SI32 spellnum = spell + ( circle - 1 ) * 8;
return HasSpell( i, spellnum );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::CheckMana()
//| Changes - to use spells[] array.
//o------------------------------------------------------------------------------------------------o
//| Purpose - Check if character has enough mana to cast spell
//o------------------------------------------------------------------------------------------------o
bool CMagic::CheckMana( CChar *s, SI32 num )
{
if( !ValidateObject( s ))
return true;
if( s->NoNeedMana() )
return true;
if( s->GetMana() >= spells[num].Mana() )
{
return true;
}
else
{
CSocket *p = s->GetSocket();
if( p != nullptr )
{
p->SysMessage( 696 ); // You have insufficient mana to cast that spell.
}
}
return false;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::CheckStamina()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Check if character has enough stamina to cast spell
//o------------------------------------------------------------------------------------------------o
bool CMagic::CheckStamina( CChar *s, SI32 num )
{
if( !ValidateObject( s ))
return true;
if( s->NoNeedMana() )
return true;
if( s->GetStamina() >= spells[num].Stamina() )
{
return true;
}
else
{
CSocket *p = s->GetSocket();
if( p != nullptr )
{
p->SysMessage( 697 ); // You have insufficient stamina to cast that spell.
}
}
return false;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::CheckHealth()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Check if character has enough health to cast spell
//o------------------------------------------------------------------------------------------------o
bool CMagic::CheckHealth( CChar *s, SI32 num )
{
if( !ValidateObject( s ))
return true;
if( s->NoNeedMana() )
return true;
if( spells[num].Health() == 0 )
return true;
if( spells[num].Health() > 0 )
{
if( s->GetHP() >= spells[num].Health() )
return true;
}
else
{
return true;
}
CSocket *p = s->GetSocket();
if( p != nullptr )
{
p->SysMessage( 698 ); // You have insufficient health to cast that spell.
}
return false;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::SubtractMana()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Subtract the required mana from character's mana reserve
//o------------------------------------------------------------------------------------------------o
void CMagic::SubtractMana( CChar *s, SI32 mana)
{
if( s->NoNeedMana() )
return;
s->SetMana( s->GetMana() - mana );
if( s->GetMana() < 0 )
{
s->SetMana( 0 );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::SubtractStamina()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Subtract the required stmina from character's stamina reserve
//o------------------------------------------------------------------------------------------------o
void CMagic::SubtractStamina( CChar *s, SI32 stamina )
{
if( s->NoNeedMana() )
return;
s->SetStamina( s->GetStamina() - stamina );
if( s->GetStamina() == -1 )
{
s->SetStamina( 0 );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::SubtractHealth()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Subtract the required health from character's stamina reserve
//o------------------------------------------------------------------------------------------------o
void CMagic::SubtractHealth( CChar *s, SI32 health, SI32 spellNum )
{
if( s->NoNeedMana() || spells[spellNum].Health() == 0 )
return;
if( spells[spellNum].Health() < 0 )
{
if( abs( spells[spellNum].Health() * health ) > s->GetHP() )
{
s->SetHP( 0 );
}
else
{
s->IncHP( spells[spellNum].Health() * health );
}
}
else
{
s->IncHP( -health );
}
if( s->GetHP() < 0 )
{
s->SetHP( 0 );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::CheckMagicReflect()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Check if character is protected by MagicReflect.
//| If yes, remove the protection and do visual effect.
//| However, don't touch the protection if character is permanently protected
//o------------------------------------------------------------------------------------------------o
bool CMagic::CheckMagicReflect( CChar *i )
{
if( i->IsTempReflected() )
{
// Is character temporarily protected by a magic reflect spell?
i->SetTempReflected( false );
Effects->PlayStaticAnimation( i, 0x373A, 0, 15 );
return true;
}
else if( i->IsPermReflected() )
{
// Is character permanently protected by a magic reflect spell?
Effects->PlayStaticAnimation( i, 0x373A, 0, 15 );
return true;
}
return false;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::CheckResist()
//| Changes - to add EV.INT. check
//o------------------------------------------------------------------------------------------------o
//| Purpose - Check character's magic resistance.
//o------------------------------------------------------------------------------------------------o
bool CMagic::CheckResist( CChar *attacker, CChar *defender, SI32 circle )
{
bool i = Skills->CheckSkill( defender, MAGICRESISTANCE, 80 * circle, 800 + ( 80 * circle ));
CSocket *s = nullptr;
if( ValidateObject( attacker ))
{
// Check what is higher between user's normal resistchance and a fallback value
// To ensure user always has a chance of resisting, however small (except at resist-skill 0 )
SI16 defaultChance = defender->GetSkill( MAGICRESISTANCE ) / 5;
SI16 resistChance = defender->GetSkill( MAGICRESISTANCE ) - ((( attacker->GetSkill( MAGERY ) - 20 ) / 5 ) + ( circle * 5 ));
if( defaultChance > resistChance )
{
resistChance = defaultChance;
}
if( RandomNum( 1, 100 ) < resistChance / 10 )
{
s = defender->GetSocket();
if( s != nullptr )
{
s->SysMessage( 699 ); // You feel yourself resisting magical energy!
}
i = true;
}
else
{
i = false;
}
}
else
{
if( i )
{
s = defender->GetSocket();
if( s != nullptr )
{
s->SysMessage( 699 ); // You feel yourself resisting magical energy!
}
}
}
return i;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::CheckResist()
//| Changes - to add EV.INT. check
//o------------------------------------------------------------------------------------------------o
//| Purpose - Check character's magic resistance.
//o------------------------------------------------------------------------------------------------o
bool CMagic::CheckResist( SI16 resistDifficulty, CChar *defender, SI32 circle )
{
bool i = Skills->CheckSkill( defender, MAGICRESISTANCE, 80 * circle, 800 + ( 80 * circle ));
CSocket *s = nullptr;
// Calculate chance of resisting the magic effect based on defender's magic resistance vs the resistDifficulty
SI16 defaultChance = defender->GetSkill( MAGICRESISTANCE ) / 5;
SI16 resistChance = defender->GetSkill( MAGICRESISTANCE ) - ((( resistDifficulty - 20 ) / 5 ) + ( circle * 5 ));
if( defaultChance > resistChance )
{
resistChance = defaultChance;
}
if( RandomNum( 1, 100 ) < resistChance / 10 )
{
s = defender->GetSocket();
if( s != nullptr )
{
s->SysMessage( 699 ); // You feel yourself resisting magical energy!
}
i = true;
}
else
{
i = false;
}
return i;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CalcSpellDamageMod()
//| Date - November 7th 2011
//o------------------------------------------------------------------------------------------------o
//| Purpose - Calculate Spell Damage after skill-modifiers
//o------------------------------------------------------------------------------------------------o
SI16 CalcSpellDamageMod( CChar *caster, CChar *target, SI16 spellDamage, bool spellResisted )
{
// Randomize in upper-half of damage range for some variety
spellDamage = HalfRandomNum( spellDamage );
// If spell was resisted, halve damage
if( spellResisted )
{
spellDamage = static_cast<SI16>( RoundNumber( spellDamage / 2 ));
}
// Add damage bonus/penalty based on attacker's EVALINT vs defender's MAGICRESISTANCE
UI16 casterEval = caster->GetSkill( EVALUATINGINTEL ) / 10;
UI16 targetResist = target->GetSkill( MAGICRESISTANCE ) / 10;
if( targetResist > casterEval )
{
spellDamage *= ((( casterEval - targetResist ) / 200.0f ) + 1 );
}
else
{
spellDamage *= ((( casterEval - targetResist ) / 500.0f ) + 1 );
}
// Randomize some more to get broader min/max damage values
SI32 i = RandomNum( 0, 4 );
if( i <= 2 )
{
spellDamage = RoundNumber( RandomNum( static_cast<SI16>( HalfRandomNum( spellDamage ) / 2 ), spellDamage ));
}
else if( i == 3 )
{
spellDamage = RoundNumber( HalfRandomNum( spellDamage ));
}
else //keep current spellDamage
{
spellDamage = RoundNumber( spellDamage );
}
return spellDamage;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::MagicDamage()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Calculate and inflict magic damage.
//o------------------------------------------------------------------------------------------------o
void CMagic::MagicDamage( CChar *p, SI16 amount, CChar *attacker, WeatherType element )
{
if( !ValidateObject( p ))
return;
if( p->IsDead() || p->GetHP() <= 0 ) // extra condition check, to see if deathstuff hasn't been hit yet
return;
CSocket *mSock = p->GetSocket();
if( p->IsFrozen() && p->GetDexterity() > 0 )
{
p->SetFrozen( false );
if( mSock != nullptr )
{
mSock->SysMessage( 700 ); // You are no longer frozen.
}
}
// Allow damage if target is not invulnerable or in a zone safe from aggressive magic
if( !p->IsInvulnerable() && !p->GetRegion()->IsSafeZone() && p->GetRegion()->CanCastAggressive() )
{
UI08 hitLoc = Combat->CalculateHitLoc();
SI16 damage = Combat->ApplyDamageBonuses( element, attacker, p, MAGERY, hitLoc, amount);
damage = Combat->ApplyDefenseModifiers( element, attacker, p, MAGERY, hitLoc, damage, true);
if( damage <= 0 )
{
damage = 1;
}
[[maybe_unused]] bool retVal = p->Damage( damage, element, attacker, true );
p->ReactOnDamage( element, attacker );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::PoisonDamage()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Apply the poison to the character.
//o------------------------------------------------------------------------------------------------o
void CMagic::PoisonDamage( CChar *p, SI32 poison )
{
if( p->IsFrozen() )
{
p->SetFrozen( false );
CSocket *s = p->GetSocket();
if( s != nullptr )
{
s->SysMessage( 700 ); // You are no longer frozen.
}
}
// Allow poison damage if target is not invunlnerable or in a zone safe from aggressive magic
if( !p->IsInvulnerable() && !p->GetRegion()->IsSafeZone() && p->GetRegion()->CanCastAggressive() )
{
if( poison > 5 )
{
poison = 5;
}
else if( poison < 0 )
{
poison = 1;
}
// Apply poison on target
p->SetPoisoned( poison );
// Set time until poison wears off completely
p->SetTimer( tCHAR_POISONWEAROFF, BuildTimeValue( GetPoisonDuration( poison )));
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::CheckFieldEffects()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Check if character stands on a magic-field and apply effects.
//o------------------------------------------------------------------------------------------------o
auto CMagic::CheckFieldEffects( CChar& mChar ) -> void
{
auto toCheck = MapRegion->GetMapRegion( &mChar );
if( toCheck )
{
auto regItems = toCheck->GetItemList();
for( const auto &inItemList :regItems->collection() )
{
if( ValidateObject( inItemList ) && inItemList->GetInstanceId() == mChar.GetInstanceId() )
{
if( inItemList->GetX() == mChar.GetX() && inItemList->GetY() == mChar.GetY() )
{
HandleFieldEffects(( &mChar ), inItemList, inItemList->GetId() );
}
}
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::HandleFieldEffects()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Handle the effects of characters entering field spells
//o------------------------------------------------------------------------------------------------o
bool CMagic::HandleFieldEffects( CChar *mChar, CItem *fieldItem, UI16 id )
{
CChar *caster;
if( id >= 0x398C && id <= 0x399F ) //fire field
{
caster = CalcCharObjFromSer( fieldItem->GetTempVar( CITV_MOREY )); // store caster in morey
if( ValidateObject( caster) )
{
if( mChar->GetTimer( tCHAR_FIREFIELDTICK ) < cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() )
{
// Set a timer, so another field spell cannot "tick" for this character for a short while
mChar->SetTimer( tCHAR_FIREFIELDTICK, BuildTimeValue( static_cast<R32>( Magic->spells[28].DamageDelay() )));
// Fetch spell damage from Fire Field spell
auto spellDamage = Magic->spells[28].BaseDmg();
// Apply magic damage
if( !CheckResist( nullptr, mChar, 4 ))
{
MagicDamage( mChar, spellDamage, caster, HEAT );
}
else
{
MagicDamage( mChar, spellDamage / 2, caster, HEAT );
}
Effects->PlaySound( mChar, 520 );
}
}
return true;
}
else if( id >= 0x3914 && id <= 0x3929 ) //poison field
{
caster = CalcCharObjFromSer( fieldItem->GetTempVar( CITV_MOREY )); // caster is stored in MOREY
if( ValidateObject( caster ) && mChar->IsInnocent() )
{
if( WillResultInCriminal( caster, mChar ) && ValidateObject( caster ) && !caster->IsGM() && !caster->IsCounselor() )
{
MakeCriminal( caster );
}
}
if( mChar->GetTimer( tCHAR_POISONFIELDTICK ) < cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() )
{
// Set a timer, so another field spell cannot "tick" for this character for a short while
mChar->SetTimer( tCHAR_POISONFIELDTICK, BuildTimeValue( static_cast<R32>( Magic->spells[39].DamageDelay() )));
// Calculate strength of poison, but disregard range check
UI08 poisonStrength = 1;
// Fetch average skill value of poison field caster's Magery and Poisoning skills
UI16 skillVal = fieldItem->GetTempVar( CITV_MOREX );
// Calculate strength of poison
poisonStrength = CalculateMagicPoisonStrength( caster, mChar, skillVal, false, 5 );
// Apply poison on character
PoisonDamage( mChar, poisonStrength );
Effects->PlaySound( mChar, 520 );
}
return true;
}
else if( id >= 0x3967 && id <= 0x398A ) //paralysis field
{
caster = CalcCharObjFromSer( fieldItem->GetTempVar( CITV_MOREY )); // store caster in morey
if( mChar->IsInnocent() )
{
if( WillResultInCriminal( caster, mChar ) && ValidateObject( caster ) && !caster->IsGM() && !caster->IsCounselor() )
{
MakeCriminal( caster );
}
}
if( mChar->GetTimer( tCHAR_PARAFIELDTICK ) < cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() )
{
// Set a timer, so another field spell cannot "tick" for this character for a short while
mChar->SetTimer( tCHAR_PARAFIELDTICK, BuildTimeValue( static_cast<R32>( Magic->spells[39].DamageDelay() )));
if( !CheckResist( nullptr, mChar, 6 ))
{
Effects->TempEffect( caster, mChar, 1, 0, 0, 0 );
Effects->PlaySound( mChar, 520 );
}
}
return true;
}
else if( id >= 0x3946 && id <= 0x3964 ) //energy field
{
caster = CalcCharObjFromSer( fieldItem->GetTempVar( CITV_MOREY )); // store caster in morey
if( ValidateObject( caster) )
{
if( RandomNum( 0, 2 ) == 1 )
{
if( !CheckResist( nullptr, mChar, 4 ))
{
MagicDamage( mChar, fieldItem->GetTempVar( CITV_MOREX ) / 50, caster, LIGHTNING );
}
else
{
MagicDamage( mChar, fieldItem->GetTempVar( CITV_MOREX ) / 100, caster, LIGHTNING );
}
Effects->PlaySound( mChar, 520 );
}
}
return true;
}
return false;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::BoxSpell()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Calculate the spell box effect, depending on character's magery skill
//o------------------------------------------------------------------------------------------------o
void CMagic::BoxSpell( CSocket *s, CChar *caster, SI16& x1, SI16& x2, SI16& y1, SI16& y2, SI08& z1, SI08& z2 )
{
SI16 x, y;
SI08 z;
SI16 length;
if( s != nullptr )
{
x = s->GetWord( 11 );
y = s->GetWord( 13 );
z = s->GetByte( 16 );
}
else
{
x = caster->GetTarg()->GetX();
y = caster->GetTarg()->GetY();
z = caster->GetTarg()->GetZ();
}
length = std::max( caster->GetSkill( MAGERY ) / 130, 1 );
x1 = x - length;
x2 = x + length;
y1 = y - length;
y2 = y + length;
z1 = z;
z2 = z + 3;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::MagicTrap()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Do the visual effect and apply damage when a player opens a trapped container
//o------------------------------------------------------------------------------------------------o
auto CMagic::MagicTrap( CChar *s, CItem *i ) -> void
{
if( ValidateObject( s ) && ValidateObject( i ))
{
if( FindItemOwner( i ) == s || ObjInRange( s, i, DIST_NEARBY ))
{
Effects->PlayStaticAnimation( i, 0x36B0, 0x09, 0x09 );
Effects->PlayStaticAnimation( s, 0x36B0, 0x09, 0x09 );
Effects->PlaySound( s, 0x0207 );
if( CheckResist( nullptr, s, 4 ))
{
MagicDamage( s, i->GetTempVar( CITV_MOREZ, 2 ) / 2, nullptr, HEAT );
}
else
{
MagicDamage( s, i->GetTempVar( CITV_MOREZ, 2 ), nullptr, HEAT );
}
}
else
{
// Player is out of range, possibly opened the trapped container using Telekinesis from a safe distance
Effects->PlayStaticAnimation( i, 0x36B0, 0x09, 0x09 );
Effects->PlaySound( i, 0x0207 );
// Find nearby players and apply damage to them?
for( auto &MapArea : MapRegion->PopulateList( s ))
{
if( MapArea == nullptr )
continue;
auto regChars = MapArea->GetCharList();
for( const auto &tempChar :regChars->collection() )
{
if( !ValidateObject( tempChar ) || tempChar->GetInstanceId() != s->GetInstanceId() || tempChar->GetSerial() == s->GetSerial() )
continue;
if( ObjInRange( tempChar, i, DIST_NEARBY ))
{
if( CheckResist( nullptr, tempChar, 4 ))
{
MagicDamage( tempChar, i->GetTempVar( CITV_MOREZ, 2 ) / 2, s, HEAT );
}
else
{
MagicDamage( tempChar, i->GetTempVar( CITV_MOREZ, 2 ), s, HEAT );
}
// Reveal hidden players impacted by the explosion
if( tempChar->GetVisible() == VT_TEMPHIDDEN || tempChar->GetVisible() == VT_INVISIBLE )
{
tempChar->ExposeToView();
}
}
}
}
}
// Set first part of MOREZ to 0 to disable trap, but leave other parts intact
i->SetTempVar( CITV_MOREZ, 1, 0 );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::CheckReagents()
//| Changes - to use reag-st
//o------------------------------------------------------------------------------------------------o
//| Purpose - Check for required reagents in player's backpack.
//o------------------------------------------------------------------------------------------------o
bool CMagic::CheckReagents( CChar *s, const Reag_st *reagents )
{
Reag_st failmsg;
if( s->NoNeedReags() )
return true;
if( reagents->ash != 0 && GetItemAmount( s, 0x0F8C ) < reagents->ash )
{
failmsg.ash = 1;
}
if( reagents->drake!=0 && GetItemAmount( s, 0x0F86 ) < reagents->drake )
{
failmsg.drake = 1;
}
if( reagents->garlic != 0 && GetItemAmount( s, 0x0F84 ) < reagents->garlic )
{
failmsg.garlic = 1;
}
if( reagents->ginseng != 0 && GetItemAmount( s, 0x0F85 ) < reagents->ginseng )
{
failmsg.ginseng = 1;
}
if( reagents->moss != 0 && GetItemAmount( s, 0x0F7B ) < reagents->moss )
{
failmsg.moss = 1;
}
if( reagents->pearl != 0 && GetItemAmount( s, 0x0F7A ) < reagents->pearl )
{
failmsg.pearl = 1;
}
if( reagents->shade != 0 && GetItemAmount( s, 0x0F88 ) < reagents->shade )
{
failmsg.shade = 1;
}
if( reagents->silk != 0 && GetItemAmount( s, 0x0F8D ) < reagents->silk )
{
failmsg.silk = 1;
}
return RegMsg( s, failmsg );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::RegMsg()
//| Changes - display missing reagents types
//o------------------------------------------------------------------------------------------------o
//| Purpose - Display an error message if character has not enough regs
//o------------------------------------------------------------------------------------------------o
bool CMagic::RegMsg( CChar *s, Reag_st failmsg )
{
if( !ValidateObject( s ))
return true;
bool display = false;
char message[100] = { 0, };
// Copy dictionary message into char array
strcopy( message, 100, Dictionary->GetEntry( 702 ).c_str() ); // You do not have enough reagents to cast that spell.
// Create temporary string to hold info on our missing reagents
std::string tempString;
tempString = " [";
if( failmsg.ash )
{
display = true; tempString += "Sa, ";
}
if( failmsg.drake )
{
display = true; tempString += "Mr, ";
}
if( failmsg.garlic )
{
display = true; tempString += "Ga, ";
}
if( failmsg.ginseng )
{
display = true; tempString += "Gi, ";
}
if( failmsg.moss )
{
display = true; tempString += "Bm, ";
}
if( failmsg.pearl )
{
display = true; tempString += "Bp, ";
}
if( failmsg.shade )
{
display = true; tempString += "Ns, ";
}
if( failmsg.silk )
{
display = true; tempString += "Ss, ";
}
// Append our temporary string to the end of the char array and add an end-bracket
mstrcat( message, 100, tempString.c_str() );
message[strlen( message ) - 2] = ']';
if( display )
{
CSocket *i = s->GetSocket();
if( i != nullptr )
{
i->SysMessage( message );
}
return false;
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::SpellFail()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Do visual and sound effects when a player fails to cast a spell.
//o------------------------------------------------------------------------------------------------o
void CMagic::SpellFail( CSocket *s )
{
CChar *mChar = s->CurrcharObj();
Effects->PlayStaticAnimation( mChar, 0x3735, 0, 30 );
Effects->PlaySound( mChar, 0x005C );
mChar->TextMessage( s, 771, EMOTE, false ); // The spell fizzles.
}
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
/////// PCs CASTING SPELLS RELATED FUNCTIONS ////////////////////
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::SelectSpell()
//| Date - 28 August 1999 / 10 September 1999
//o------------------------------------------------------------------------------------------------o
//| Purpose - Execute the selected spell to cast.
//|
//| Changes - Use spells (memory version of script info )
//| Made memory optionable
//| Perma cached
//| Added JS handling
//o------------------------------------------------------------------------------------------------o
bool CMagic::SelectSpell( CSocket *mSock, SI32 num )
{
if( num < 1 )
{
Console.Error( "Invalid spell ID passed to CMagic::SelectSpell() - aborting spellcast attempt!" );
return false;
}
/*SI16 lowSkill = 0, highSkill = 0;*/
CChar *mChar = mSock->CurrcharObj();
cScript *jsScript = JSMapping->GetScript( spells[num].JSScript() );
if( jsScript != nullptr )
{
// It is the responsibility of the JS script to call the proper events (ie onSpellSuccess and so forth)
// They deal with their own mana subtraction and all sorts of things
// They also deal with valid regions and what not. s is *not* guaranteed to be valid (ie it could be an NPC)
// Suggested script workflow:
// * Check if already casting, if so, stop and dump out
// * Check if frozen, if so, can't cast spell
// * Check if enabled
// * Check if scroll, wand or normal cast (scroll reduce requirements, wand do straight away)
// * Check requirements (min int, dex, str)
// * Check for reagants
// * Check if can cast in this area
// * Determine if the cast "succeeded"
// * If so, fire onSpellSuccess, otherwise fire onSpellFailure. If failed, speak and return
// * Set the player as "casting" and freeze
// * Create timer for when the spell will be complete
// * Wait for timer to elapse
// * Upon timer elapsing,
// * Unfreeze player
// * Send target identifier if any (based on timer) and wait for callback
// * If not target, do action (if any)
// * If target, create onCallback function
// * On callback, do stuff
// * NOTE * For area affect spells, use an AreaCharacterFunction
if( jsScript->MagicSpellCast( mSock, mChar, false, num ))
{
return true;
}
}
if( !mChar->GetCanAttack() )
{
mSock->SysMessage( 1778 ); // You are currently under the effect of peace and can not attack!
return false;
}
if( mSock->GetCursorItem() != nullptr )
{
// Player is holding an item, disallow casting while holding items on the cursor
mSock->SysMessage( Dictionary->GetEntry( 2862, mSock->Language() )); // Your hands must be free to cast spells or meditate.
return false;
}
SI32 type = mSock->CurrentSpellType();
CSpellInfo curSpellCasting = spells[num];
// Spell recovery time active? This timer stars as soon as targeting cursor is ready, and defaults to 1.0 seconds unless set otherwise in spells.dfn
if( mChar->GetTimer( tCHAR_SPELLRECOVERYTIME ) != 0 )
{
if( mChar->GetTimer( tCHAR_SPELLRECOVERYTIME ) > cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() )
{
mSock->SysMessage( 1638 ); // You must wait a little while before casting
return false;
}
}
// Player already casting a spell?
if( mChar->GetTimer( tCHAR_SPELLTIME ) != 0 )
{
if( mChar->IsCasting() )
{
mSock->SysMessage( 762 ); // You are already casting a spell.
return false;
}
else if( mChar->GetTimer( tCHAR_SPELLTIME ) > cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() )
{
mSock->SysMessage( 1638 ); // You must wait a little while before casting
return false;
}
}
mChar->SetSpellCast( static_cast<SI08>( num ));
if( num > 63 && num <= static_cast<SI32>( spellCount ) && spellCount <= 70 )
{
LogSpell( Dictionary->GetEntry( magic_table[num].spell_name ), mChar, nullptr, "(Attempted)");
}
if( mChar->IsJailed() && !mChar->IsGM() )
{
mSock->SysMessage( 704 ); // You are in jail and cannot cast spells!
mChar->StopSpell();
return false;
}
// Region checks
CTownRegion *ourReg = mChar->GetRegion();
if(( num == 45 && !ourReg->CanMark() ) || ( num == 52 && !ourReg->CanGate() ) ||
( num == 32 && !ourReg->CanRecall() ) || ( num == 22 && !ourReg->CanTeleport() ))
{
mSock->SysMessage( 705 ); // This is not allowed here.
mChar->StopSpell();
return false;
}
// Check if caster can cast aggressive spells in the region they're in
if( spells[mChar->GetSpellCast()].AggressiveSpell() )
{
bool allowCasting = true;
if( mChar->GetRegion()->IsSafeZone() )
{
mSock->SysMessage( 1799 ); // Hostile actions are not permitted in this safe area.
allowCasting = false;
}
else if( !mChar->GetRegion()->CanCastAggressive() )
{
mSock->SysMessage( 706 ); // This is not allowed in town.
allowCasting = false;
}
if( !allowCasting )
{
// Not allowed to cast selected spell in this region, abort
mChar->StopSpell();
return false;
}
}
if( !curSpellCasting.Enabled() )
{
mSock->SysMessage( 707 ); // That spell is currently not enabled.
mChar->StopSpell();
return false;
}
// The following loop checks to see if any item is currently equipped (if not a GM)
if( !mChar->IsGM() )
{
if( type != 2 )
{
CItem *itemRHand = mChar->GetItemAtLayer( IL_RIGHTHAND );
CItem *itemLHand = mChar->GetItemAtLayer( IL_LEFTHAND );
if( itemLHand != nullptr || ( itemRHand != nullptr && itemRHand->GetType() != IT_SPELLBOOK ))
{
mSock->SysMessage( 708 ); // You cannot cast with a weapon equipped.
mChar->StopSpell();
return false;
}
}
}
if( mChar->GetVisible() == VT_TEMPHIDDEN || mChar->GetVisible() == VT_INVISIBLE )
{
mChar->ExposeToView();
}
mChar->BreakConcentration( mSock );
// What the JavaScript event has to handle:
// - Reag consumption
// - Health consumption
// - Mana consumption
// - Stamina consumption
// - Delay measurement
SI16 castDelay = -2;
// If more than one script contains the onSpellCast/onScrollCast events, whichever script runs last will
// determine the final delay value
std::vector<UI16> scriptTriggers = mChar->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
// Events:
// type 0: onSpellCast (SpellBook)
// type 1: onScrollCast (Scroll)
// type 2: onScrolllCast (Wand)
if( type == 0 )
{
castDelay = toExecute->OnSpellCast( mChar, num );
}
else
{
castDelay = toExecute->OnScrollCast( mChar, num );
}
}
}
// If Delay is -1 the spell should be canceled
if( castDelay == -1 )
{
mChar->StopSpell();
return false;
}
if(( castDelay == -2 ) && !mChar->IsGM() )
{
// type == 2 - Wands
if( type != 2 )
{
if( !CheckMana( mChar, num ))
{
mSock->SysMessage( 696 ); // You have insufficient mana to cast that spell.
mChar->StopSpell();
return false;
}
if( !CheckStamina( mChar, num ))
{
mSock->SysMessage( 697 ); // You have insufficient stamina to cast that spell.
mChar->StopSpell();
return false;
}
if( !CheckHealth( mChar, num ))
{
mSock->SysMessage( 698 ); // You have insufficient health to cast that spell.
mChar->StopSpell();
return false;
}
}
}
mChar->SetNextAct( 75 ); // why 75?
// Delay measurement...
if( castDelay >= 0 )
{
mChar->SetTimer( tCHAR_SPELLTIME, BuildTimeValue(static_cast<R32>( castDelay / 1000 )));
if( !cwmWorldState->ServerData()->CastSpellsWhileMoving() )
{
mChar->SetFrozen( true );
mChar->Dirty( UT_UPDATE );
}
}
else if( type == 0 && mChar->GetCommandLevel() < 2 ) // if they are a gm they don't have a delay :-)
{
mChar->SetTimer( tCHAR_SPELLTIME, BuildTimeValue( static_cast<R32>( curSpellCasting.Delay() )));
if( !cwmWorldState->ServerData()->CastSpellsWhileMoving() )
{
mChar->SetFrozen( true );
mChar->Dirty( UT_UPDATE );
}
}
else
{
// Inform GM why there's no spellcast delay
mSock->SysMessage( 6280 ); // Spellcast delay ignored due to elevated command level!
mChar->SetTimer( tCHAR_SPELLTIME, 0 );
}
// Delay measurement end
//if( !mChar->IsOnHorse() )
Effects->PlaySpellCastingAnimation( mChar, curSpellCasting.Action(), false, false ); // do the action
std::string temp;
if( spells[num].FieldSpell() )
{
if( mChar->GetSkill( MAGERY ) > 600 )
{
temp = "Vas " + curSpellCasting.Mantra();
}
else
{
temp = curSpellCasting.Mantra();
}
}
else
{
temp = curSpellCasting.Mantra();
}
mChar->TextMessage( nullptr, temp, TALK, false );
mChar->SetCasting( true );
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::GetFieldDir()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Gets field direction
//o------------------------------------------------------------------------------------------------o
UI08 CMagic::GetFieldDir( CChar *s, SI16 x, SI16 y )
{
UI08 fieldDir = 0;
switch( Movement->Direction( s, x, y ))
{
case NORTH:
case SOUTH:
break;
case EAST:
case WEST:
fieldDir = 1;
break;
case NORTHEAST:
case SOUTHEAST:
case SOUTHWEST:
case NORTHWEST:
case UNKNOWNDIR:
switch( s->GetDir() )
{
case NORTH:
case SOUTH:
break;
case NORTHEAST:
case EAST:
case SOUTHEAST:
case SOUTHWEST:
case WEST:
case NORTHWEST:
fieldDir = 1;
break;
default:
Console.Error( " Fallout of switch statement without default. uox3.cpp, GetFieldDir()" );
break;
}
break;
default:
Console.Error( " Fallout of switch statement without default. uox3.cpp, GetFieldDir()" );
break;
}
return fieldDir;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::CastSpell()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Handles a character's attempt to cast spells
//o------------------------------------------------------------------------------------------------o
void CMagic::CastSpell( CSocket *s, CChar *caster )
{
// for LocationTarget spell like ArchCure, ArchProtection etc...
SI08 curSpell;
curSpell = caster->GetSpellCast();
// No spell! Abort
if( curSpell == -1 )
return;
cScript *jsScript = JSMapping->GetScript( spells[curSpell].JSScript() );
if( jsScript != nullptr )
{
if( jsScript->MagicSpellCast( s, caster, true, curSpell ))
{
return;
}
}
if( caster->IsCasting() )
return; // interrupted
caster->StopSpell();
std::vector<UI16> scriptTriggers = caster->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
// If script returns true/1, prevent other scripts with event from running
if( toExecute->OnSpellSuccess( caster, curSpell ) == 1 )
{
break;
}
}
}
CChar *src = caster;
bool validSocket = ( s != nullptr );
// Check if caster can cast aggressive spells in the region they're in
if( spells[curSpell].AggressiveSpell() )
{
bool allowCasting = true;
if( caster->GetRegion()->IsSafeZone() )
{
// Caster is in a safe zone, where all aggressive actions are forbidden
if( validSocket )
{
s->SysMessage( 1799 ); // Hostile actions are not permitted in this safe area.
}
allowCasting = false;
}
else if( !caster->GetRegion()->CanCastAggressive() )
{
// Caster is in a region where casting aggressive spells is forbidden
if( validSocket )
{
s->SysMessage( 709 ); // You can't cast in town!
}
allowCasting = false;
}
if( !allowCasting )
return;
}
if( curSpell == 32 || curSpell == 52 )
{
if( !cwmWorldState->ServerData()->TravelSpellsWhileAggressor() )
{
// Aggressor flag only matters against other players, not against NPCs
if(( caster->IsAggressor( true ) && ValidateObject( caster->GetTarg() ) && caster->GetTarg()->IsInnocent() ))
{
if( validSocket )
{
s->SysMessage( 2066 ); // You are not allowed to use Recall or Gate spells while being the aggressor in a fight!
}
return;
}
}
if( caster->IsCriminal() || caster->HasStolen() )
{
if( validSocket )
{
s->SysMessage( 9278 ); // You cannot use Recall or Gate spells while you have an active criminal flag, or if you have stolen recently!
}
return;
}
}
//Check for enough reagents
// type == 0 -> SpellBook
if( validSocket && s->CurrentSpellType() == 0 && !CheckReagents( caster, spells[curSpell].ReagantsPtr() ))
{
caster->StopSpell();
return;
}
// Let human NPC casters say the magic words as they cast spells
if( caster->IsNpc() && cwmWorldState->creatures[caster->GetId()].IsHuman() )
{
std::string temp;
if( spells[curSpell].FieldSpell() )
{
if( caster->GetSkill( MAGERY ) > 600 )
{
temp = "Vas " + spells[curSpell].Mantra();
}
else
{
temp = spells[curSpell].Mantra();
}
}
else
{
temp = spells[curSpell].Mantra();
}
caster->TextMessage( nullptr, temp, TALK, false );
}
// Consume mana/health/stamina for the spellcast attempt, if
// caster is an NPC or spelltype is spellbook or scrolls
if( caster->IsNpc() || ( validSocket && ( s->CurrentSpellType() != 2 )))
{
SubtractMana( caster, spells[curSpell].Mana() );
if( spells[curSpell].Health() > 0 )
{
SubtractHealth( caster, spells[curSpell].Health(), curSpell );
}
SubtractStamina( caster, spells[curSpell].Stamina() );
}
// Consume reagents for the spellcast attempt
if( validSocket && s->CurrentSpellType() == 0 && !caster->IsNpc() )
{
DelReagents( caster, spells[curSpell].Reagants() );
}
// Cut the casting requirement on scrolls
SI16 lowSkill = 0, highSkill = 0;
if( validSocket && s->CurrentSpellType() == 1 && cwmWorldState->ServerData()->CutScrollRequirementStatus() ) // only if enabled
{
lowSkill = spells[curSpell].ScrollLow();
highSkill = spells[curSpell].ScrollHigh();
}
else
{
lowSkill = spells[curSpell].LowSkill();
highSkill = spells[curSpell].HighSkill();
}
// Do the skill check, and fizzle if player fails
if( !caster->IsNpc() && !caster->IsGM() && ( !Skills->CheckSkill( caster, MAGERY, lowSkill, highSkill )))
{
if( validSocket )
{
SpellFail( s );
}
caster->StopSpell();
return;
}
if( curSpell > 63 && static_cast<UI32>(curSpell) <= spellCount && spellCount <= 70 )
{
LogSpell( Dictionary->GetEntry( magic_table[curSpell].spell_name ), caster, nullptr, "(Succeeded)");
}
if( spells[curSpell].RequireTarget() ) // target spells if true
{
CItem *i = nullptr;
if( validSocket && spells[curSpell].TravelSpell() ) // travel spells.... mark, recall and gate
{
// Region checks
CTownRegion *ourReg = caster->GetRegion();
if(( curSpell == 45 && !ourReg->CanMark() ) || ( curSpell == 52 && !ourReg->CanGate() ) ||
( curSpell == 32 && !ourReg->CanRecall() ) || ( curSpell == 22 && !ourReg->CanTeleport() ))
{
if( validSocket )
{
s->SysMessage( 705 ); // This is not allowed here.
}
return;
}
// mark, recall and gate go here
if( !caster->IsNpc() )
{
i = CalcItemObjFromSer( s->GetDWord( 7 ));
}
if( ValidateObject( i ))
{
if( i->GetCont() != nullptr || LineOfSight( s, caster, i->GetX(), i->GetY(), i->GetZ(), WALLS_CHIMNEYS + DOORS + FLOORS_FLAT_ROOFING, false ) || caster->IsGM() )
{
if(( i->GetType() == IT_RECALLRUNE || (( curSpell == 32 || curSpell == 52 ) && i->GetType() == IT_RUNEBOOK )) || ( curSpell != 45 && i->GetType() == IT_KEY && cwmWorldState->ServerData()->TravelSpellsFromBoatKeys() ))
{
PlaySound( src, curSpell );
DoMoveEffect( curSpell, i, src );
DoStaticEffect( src, curSpell );
switch( curSpell )
{
case 32: //////////// (32) RECALL ////////////////
case 45: //////////// (45) MARK //////////////////
case 52: //////////// (52) GATE //////////////////
{
// Check for onSpellTargetSelect on spell caster
scriptTriggers.clear();
scriptTriggers.shrink_to_fit();
scriptTriggers = caster->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
auto retVal = toExecute->OnSpellTargetSelect( caster, i, curSpell );
if( retVal == 1 )
{
break;
}
else if( retVal == 2 )
{
return;
}
}
}
// Check for onSpellTarget on spell target
scriptTriggers.clear();
scriptTriggers.shrink_to_fit();
scriptTriggers = i->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
// If script returns true/1, prevent other scripts with event from running
// If script returns 2, spell being cast on this target was rejected
auto retVal = toExecute->OnSpellTarget( i, caster, curSpell );
if( retVal == 1 )
{
break;
}
else if( retVal == 2 )
{
return;
}
}
}
(*(( MAGIC_ITEMFUNC )magic_table[curSpell-1].mag_extra ))( s, caster, i, curSpell );
break;
}
default:
Console.Error( oldstrutil::format( " Unknown Travel spell %i, magic.cpp", curSpell ));
break;
}
}
else if( validSocket )
{
s->SysMessage( 710 ); // That item is not a recall rune.
}
}
else if( validSocket )
{
s->SysMessage( 683 ); // There seems to be something in the way.
}
}
else if( validSocket )
{
s->SysMessage( 710 ); // That item is not a recall rune.
}
return;
}
if( spells[curSpell].RequireCharTarget() )
{
// TARGET CALC HERE
CChar *c = nullptr;
if( validSocket && !caster->IsNpc() )
{
c = CalcCharObjFromSer( s->GetDWord( 7 ));
}
else
{
c = caster->GetTarg();
}
if( ValidateObject( c )) // we have to have targetted a person to kill them :)
{
if( !ObjInRange( c, caster, cwmWorldState->ServerData()->CombatMaxSpellRange() ))
{
if( validSocket )
{
s->SysMessage( 712 ); // You can't cast on someone that far away!
}
return;
}
if( LineOfSight( s, caster, c->GetX(), c->GetY(), ( c->GetZ() + 15 ), WALLS_CHIMNEYS + DOORS + FLOORS_FLAT_ROOFING, false ) || caster->IsGM() )
{
if( spells[curSpell].AggressiveSpell() )
{
if( c->GetRegion()->IsSafeZone() )
{
// Target is in a safe zone, where all aggressive actions are forbidden
if( validSocket )
{
s->SysMessage( 1799 ); // Hostile actions are not permitted in this safe area.
}
return;
}
else if( !c->GetRegion()->CanCastAggressive() )
{
// Target is in a region where casting aggressive spells is forbidden
if( validSocket )
{
s->SysMessage( 709 ); // You can't cast in town!
}
return;
}
if( c->GetNpcAiType() == AI_PLAYERVENDOR || c->IsInvulnerable() )
{
if( validSocket )
{
s->SysMessage( 713 ); // They are invulnerable merchants!
}
return;
}
Combat->AttackTarget( caster, c );
}
if( spells[curSpell].SpellReflectable() )
{
if( CheckMagicReflect( c ))
{
src = c;
c = caster;
}
}
// Don't play sound and effects for these direct damagespells - it's handled
// in effect.cpp after their damage delay timer has expired. Dispel is also a special case
if( curSpell != 5 && curSpell != 12 && curSpell != 18 && curSpell != 30 && curSpell != 41
&& curSpell != 42 && curSpell != 43 && curSpell != 49 && curSpell != 51 && curSpell != 55 )
{
if( caster->IsNpc() )
{
PlaySound( c, curSpell );
}
else
{
PlaySound( src, curSpell );
}
DoMoveEffect( curSpell, c, src );
DoStaticEffect( c, curSpell );
}
switch( curSpell )
{
case 1: // Clumsy
case 3: // Feeblemind
case 4: // Heal 2-26-00 used OSI values from UO book
case 5: // Magic Arrow
case 6: // Night Sight
case 7: // Reactive Armor
case 8: // Weaken
case 9: // Agility
case 10: // Cunning
case 11: // Cure
case 12: // Harm
case 15: // Protection
case 16: // Strength
case 17: // Bless
case 18: // Fireball
case 20: // Poison
case 27: // Curse
case 29: // Greater Heal
case 30: // Lightning
case 31: // Mana drain
case 37: // Mind Blast
case 38: // Paralyze
case 41: // Dispel
case 42: // Energy Bolt
case 43: // Explosion
case 44: // Invisibility
case 51: // Flamestrike
case 53: // Mana Vampire
case 59: // Resurrection
{
// Check for onSpellTargetSelect on spell caster
scriptTriggers.clear();
scriptTriggers.shrink_to_fit();
scriptTriggers = caster->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
auto retVal = toExecute->OnSpellTargetSelect( caster, c, curSpell );
if( retVal == 1 )
{
break;
}
else if( retVal == 2 )
{
return;
}
}
}
// Check for onSpellTarget event on spell target
scriptTriggers.clear();
scriptTriggers.shrink_to_fit();
scriptTriggers = c->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
auto retVal = toExecute->OnSpellTarget( c, caster, curSpell );
if( retVal == 1 )
{
break;
}
else if( retVal == 2 )
{
return;
}
}
}
// All ok, continue casting spell
(*(( MAGIC_CHARFUNC )magic_table[curSpell-1].mag_extra ))( caster, c, src, curSpell );
break;
}
case 46: // Mass cure
case 25: // Arch Cure
case 26: // Arch Protection
case 49: // Chain Lightning
case 54: // Mass Dispel
case 55: // Meteor Swarm
{
// Check for onSpellTargetSelect on spell caster
scriptTriggers.clear();
scriptTriggers.shrink_to_fit();
scriptTriggers = caster->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
auto retVal = toExecute->OnSpellTargetSelect( caster, c, curSpell );
if( retVal == 1 )
{
break;
}
else if( retVal == 2 )
{
return;
}
}
}
// Check for onSpellTarget on spell target
scriptTriggers.clear();
scriptTriggers.shrink_to_fit();
scriptTriggers = c->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
auto retVal = toExecute->OnSpellTarget( c, caster, curSpell );
if( retVal == 1 )
{
break;
}
else if( retVal == 2 )
{
return;
}
}
}
(*(( MAGIC_TESTFUNC )magic_table[curSpell-1].mag_extra ))( s, caster, c, src, curSpell );
break;
}
default:
Console.Error( oldstrutil::format( " Unknown CharacterTarget spell %i, magic.cpp", curSpell ));
break;
}
}
else if( validSocket )
{
s->SysMessage( 683 ); // There seems to be something in the way.
}
}
else if( validSocket )
{
s->SysMessage( 715 ); // That is not a person.
}
}
else if( spells[curSpell].RequireLocTarget() )
{
SI16 x = 0;
SI16 y = 0;
SI08 z=0;
CBaseObject *getTarg = nullptr;
if( !caster->IsNpc() )
{
if( s->GetDWord( 7 ) != INVALIDSERIAL )
{
SERIAL targSer = s->GetDWord( 7 );
if( targSer >= BASEITEMSERIAL )
{
getTarg = CalcItemObjFromSer( targSer );
}
else
{
getTarg = CalcCharObjFromSer( targSer );
}
}
else
{
x = s->GetWord( 11 );
y = s->GetWord( 13 );
z = s->GetByte( 16 ) + Map->TileHeight( s->GetWord( 17 ));
}
}
else
{
getTarg = caster->GetTarg();
}
if( ValidateObject( getTarg ))
{
if( !getTarg->CanBeObjType( OT_ITEM ) || (static_cast<CItem *>(getTarg))->GetCont() == nullptr )
{
x = getTarg->GetX();
y = getTarg->GetY();
z = getTarg->GetZ();
}
else if( validSocket )
{
s->SysMessage( 717 ); // That is not a valid location.
return;
}
}
if( spells[curSpell].AggressiveSpell() )
{
CTownRegion *targetRegion = CalcRegionFromXY( x, y, caster->WorldNumber(), caster->GetInstanceId() );
if( targetRegion->IsSafeZone() )
{
// Target location is in a region where hostile actions are forbidden, disallow
if( validSocket )
{
s->SysMessage( 1799 ); // Hostile actions are not permitted in this safe area.
}
return;
}
else if( !targetRegion->CanCastAggressive() )
{
// Target location is in a region where casting aggressive spells is forbidden, disallow
if( validSocket )
{
s->SysMessage( 709 ); // You can't cast in town!
}
return;
}
}
// Check distance to target location vs CombatMaxSpellRange
UI16 targDist;
Point3_st targLocation = Point3_st( x, y, z );
Point3_st difference = caster->GetLocation() - targLocation;
targDist = static_cast<UI16>( difference.Mag() );
if( targDist > cwmWorldState->ServerData()->CombatMaxSpellRange() )
{
if( validSocket )
{
s->SysMessage( 393 ); // That is too far away.
}
return;
}
if( LineOfSight( s, caster, x, y, ( z + 15 ), WALLS_CHIMNEYS + DOORS + FLOORS_FLAT_ROOFING, false ))
{
if( spells[curSpell].FieldSpell() )
{
UI08 j = GetFieldDir( caster, x, y );
PlaySound( src, curSpell );
DoStaticEffect( src, curSpell );
(*(( MAGIC_FIELDFUNC )magic_table[curSpell-1].mag_extra ))( s, caster, j, x, y, z, curSpell );
}
else if( spells[curSpell].RequireNoTarget() )
{
if( curSpell != 48 )
{
PlaySound( src, curSpell );
DoStaticEffect( src, curSpell );
}
switch( curSpell )
{
case 2: //create food
case 22:// Teleport
case 25:// Arch Cure
case 26:// Arch Protection
case 33:// Blade Spirits
case 34:// Dispel Field
case 40:// Summon Creature
case 46:// Mass curse
case 48:// Reveal
case 49:// Chain Lightning
case 54:// Mass Dispel
case 55:// Meteor Swarm
case 60:// Summon Air Elemental
case 61:// Summon Daemon
case 62:// Summon Earth Elemental
case 63:// Summon Fire Elemental
case 64:// Summon water Elemental
case 65:// Summon Hero
case 58:// Energy Vortex
(*(( MAGIC_LOCFUNC )magic_table[curSpell-1].mag_extra ))( s, caster, x, y, z, curSpell );
break;
default:
Console.Error(oldstrutil::format( " Unknown LocationTarget spell %i", curSpell ));
break;
}
}
}
else if( validSocket )
{
s->SysMessage( 717 ); // That is not a valid location.
}
}
else if( spells[curSpell].RequireItemTarget() && validSocket )
{
i = CalcItemObjFromSer( s->GetDWord( 7 ));
if( ValidateObject( i ))
{
auto iCont = i->GetCont();
if( iCont != nullptr && FindItemOwner( i ) != caster )
{
s->SysMessage( 718 ); // You can't cast on an item that far away!
return;
}
if( iCont == nullptr && !ObjInRange( caster, i, cwmWorldState->ServerData()->CombatMaxSpellRange() ))
{
s->SysMessage( 718 ); // You can't cast on an item that far away!
return;
}
if( iCont == nullptr && !LineOfSight( s, caster, i->GetX(), i->GetY(), i->GetZ(), WALLS_CHIMNEYS + DOORS + FLOORS_FLAT_ROOFING, false ) && !caster->IsGM() )
{
s->SysMessage( 683 ); // There seems to be something in the way.
return;
}
switch( curSpell )
{
case 13: // Magic Trap
case 14: // Magic Untrap
case 19: // Magic Lock
case 23: // Magic Unlock
case 21: // Telekinesis
(*(( MAGIC_ITEMFUNC )magic_table[curSpell-1].mag_extra))( s, caster, i, curSpell );
break;
default:
Console.Error( oldstrutil::format(" Unknown ItemTarget spell %i, magic.cpp", curSpell ));
break;
}
}
else
{
s->SysMessage( 711 ); // That is not a valid item.
}
}
else if( validSocket )
{
s->SysMessage( 720 ); // Can't cope with this spell, requires a target but it doesn't specify what type.
}
return;
}
else if( spells[curSpell].RequireNoTarget() )
{
// non targetted spells
PlaySound( src, curSpell );
DoStaticEffect( src, curSpell );
switch( curSpell )
{
case 2: // Create food
case 40: // Summon Monster // summon monster is 40, not 33
case 35:
case 36: // Magic Reflection
case 56: // Poly Morph
case 57: // Earthquake
case 60: // Summon Air Elemental
case 61: // Summon Daemon
case 62: // Summon Earth Elemental
case 63: // Summon Fire Elemental
case 64: // Summon Water Elemental
case 65: // Summon Hero
case 67:
(*(( MAGIC_NOFUNC )magic_table[curSpell-1].mag_extra ))( s, caster, curSpell );
break;
default:
Console.Error( oldstrutil::format( " Unknown NonTarget spell %i, magic.cpp", curSpell ));
break;
}
return;
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::LoadScript()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Loads spell data from spell DFNs
//|
//| Notes - Avoid multiple reading of the spell script every time a spell is
//| cast to avoid crippling the server when a mage enters combat
//o------------------------------------------------------------------------------------------------o
void CMagic::LoadScript( void )
{
spells.resize( 0 );
// for some strange reason, spells go from index 1 to SPELL_MAX and
// apparently index 0 is left unused
spells.resize( SPELL_MAX + 1 );
std::string spEntry;
std::string tag, data, UTag;
UI08 i = 0;
for( auto &spellScp : FileLookup->ScriptListings[spells_def] )
{
if( spellScp == nullptr )
continue;
for( const auto &[spEntry, SpellLoad]:spellScp->collection() )
{
if( SpellLoad == nullptr )
continue;
auto ssecs = oldstrutil::sections( spEntry, " " );
if( ssecs[0] == "SPELL" )
{
i = static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 0 ));
if( i <= SPELL_MAX )
{
++spellCount;
spells[i].Enabled( false );
Reag_st *mRegs = spells[i].ReagantsPtr();
//Console.Log( "Spell number: %i", "spell.log", i ); // Disabled for performance reasons
for( const auto &sec : SpellLoad->collection() )
{
tag = sec->tag;
data = sec->data;
UTag = oldstrutil::upper( tag );
data = oldstrutil::trim( oldstrutil::removeTrailing( data, "//" ));
//Console.Log( "Tag: %s\tData: %s", "spell.log", UTag.c_str(), data.c_str() ); // Disabled for performance reasons
switch(( UTag.data()[0] ))
{
case 'A':
if( UTag == "ACTION" )
{
spells[i].Action( static_cast<UI16>( std::stoul( data, nullptr, 0 )));
}
else if( UTag == "ASH" )
{
mRegs->ash =static_cast<UI08>( std::stoul( data, nullptr, 0 ));
}
break;
case 'B':
if( UTag == "BASEDMG" )
{
spells[i].BaseDmg( static_cast<SI16>( std::stoi( data, nullptr, 0 )));
}
break;
case 'C':
if( UTag == "CIRCLE" )
{
spells[i].Circle( static_cast<UI08>( std::stoul( data, nullptr, 0 )));
}
break;
case 'D':
if( UTag == "DAMAGEDELAY" )
{
spells[i].DamageDelay( static_cast<R32>( std::stof( data )));
}
else if( UTag == "DELAY" )
{
spells[i].Delay( static_cast<R32>( std::stof( data )));
}
else if( UTag == "DRAKE" )
{
mRegs->drake = static_cast<UI08>( std::stoul( data, nullptr, 0 ));
}
break;
case 'E':
if( UTag == "ENABLE" ) // presence of enable is enough to enable it
{
spells[i].Enabled( std::stoul( data, nullptr, 0 ) != 0 );
}
break;
case 'F':
if( UTag == "FLAGS" )
{
auto ssecs = oldstrutil::sections( data, " " );
if( ssecs.size() > 1 )
{
// This is used to load flags from old-style spells.dfn, where flags are written as FLAGS=## ## instead of FLAGS=0x####
spells[i].Flags((( static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 16 ))) << 8 ) |
static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 16 )));
}
else
{
spells[i].Flags( static_cast<UI16>( std::stoul( data, nullptr, 0 )));
}
}
break;
case 'G':
if( UTag == "GARLIC" )
{
mRegs->garlic = static_cast<UI08>( std::stoul( data, nullptr, 0 ));
}
else if( UTag == "GINSENG" )
{
mRegs->ginseng = static_cast<UI08>( std::stoul( data, nullptr, 0 ));
}
break;
case 'H':
if( UTag == "HISKILL" )
{
spells[i].HighSkill( static_cast<SI16>( std::stoi( data, nullptr, 0 )));
}
else if( UTag == "HEALTH" )
{
spells[i].Health( static_cast<SI16>( std::stoi( data, nullptr, 0 )));
}
break;
case 'L':
if( UTag == "LOSKILL" )
{
spells[i].LowSkill( static_cast<SI16>( std::stoi( data, nullptr, 0 )));
}
break;
case 'M':
if( UTag == "MANA" )
{
spells[i].Mana( static_cast<SI16>( std::stoi( data, nullptr, 0 )));
}
else if( UTag == "MANTRA" )
{
spells[i].Mantra( data );
}
else if( UTag == "MOSS" )
{
mRegs->moss = static_cast<UI08>( std::stoul( data, nullptr, 0 ));
}
else if( UTag == "MOVEFX" )
{
auto ssecs = oldstrutil::sections( data, " " );
if( ssecs.size() > 1 )
{
CMagicMove *mv = spells[i].MoveEffectPtr();
mv->Effect( static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 16 )), static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 16 )));
mv->Speed( static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[2], "//" )), nullptr, 16 )));
mv->Loop( static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[3], "//" )), nullptr, 16 )));
mv->Explode( static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[4], "//" )), nullptr, 16 )));
}
}
break;
case 'P':
if( UTag == "PEARL" )
{
mRegs->pearl = static_cast<UI08>( std::stoul( data, nullptr, 0 ));
}
break;
case 'R':
if( UTag == "RECOVERYDELAY" )
{
spells[i].RecoveryDelay( static_cast<R32>( std::stof( data )));
}
break;
case 'S':
if( UTag == "SHADE" )
{
mRegs->shade = static_cast<UI08>( std::stoul( data, nullptr, 0 ));
}
else if( UTag == "SILK" )
{
mRegs->silk = static_cast<UI08>( std::stoul( data, nullptr, 0 ));
}
else if( UTag == "SOUNDFX" )
{
auto ssecs = oldstrutil::sections( data, " " );
if( ssecs.size() > 1 )
{
// This is used to load sounds from old-style spells.dfn, where soundfx are written as SOUNDFX=## ## instead of SOUNDFX=0x####
spells[i].Effect(((static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 16 )) << 8 ) |
static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 16 ))));
}
else
{
spells[i].Effect( static_cast<UI16>( std::stoul( data, nullptr, 0 )));
}
}
else if( UTag == "STATFX" )
{
auto ssecs = oldstrutil::sections( data, " " );
if( ssecs.size() > 1 )
{
CMagicStat *stat = spells[i].StaticEffectPtr();
stat->Effect( static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 16 )), static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 16 )));
stat->Speed( static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[2], "//" )), nullptr, 16 )));
stat->Loop( static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[3], "//" )), nullptr, 16 )));
}
}
else if( UTag == "SCLO" )
{
spells[i].ScrollLow( static_cast<SI16>( std::stoi( data, nullptr, 0 )));
}
else if( UTag == "SCHI" )
{
spells[i].ScrollHigh( static_cast<SI16>( std::stoi( data, nullptr, 0 )));
}
else if( UTag == "STAMINA" )
{
spells[i].Stamina( static_cast<SI16>( std::stoi( data, nullptr, 0 )));
}
break;
case 'T':
if( UTag == "TARG" )
{
spells[i].StringToSay( data );
}
break;
}
}
}
}
}
}
#if defined( UOX_DEBUG_MODE )
Console.Print( "Registering spells\n" );
#endif
CJSMappingSection *spellSection = JSMapping->GetSection( SCPT_MAGIC );
for( cScript *ourScript = spellSection->First(); !spellSection->Finished(); ourScript = spellSection->Next() )
{
if( ourScript )
{
ourScript->ScriptRegistration( "Spell" );
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::DelReagents()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Deletes the specified amount of reagents when a spell is cast
//o------------------------------------------------------------------------------------------------o
void CMagic::DelReagents( CChar *s, Reag_st reags )
{
if( s->NoNeedReags() )
return;
DeleteItemAmount( s, reags.pearl, 0x0F7A );
DeleteItemAmount( s, reags.moss, 0x0F7B );
DeleteItemAmount( s, reags.garlic, 0x0F84 );
DeleteItemAmount( s, reags.ginseng, 0x0F85 );
DeleteItemAmount( s, reags.drake, 0x0F86 );
DeleteItemAmount( s, reags.shade, 0x0F88 );
DeleteItemAmount( s, reags.ash, 0x0F8C );
DeleteItemAmount( s, reags.silk, 0x0F8D );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::PlaySound()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Plays the SFX associated with a spell that is being cast
//o------------------------------------------------------------------------------------------------o
void CMagic::PlaySound( CChar *source, SI32 num )
{
if( spells[num].Effect() != INVALIDID )
{
Effects->PlaySound( source, spells[num].Effect() );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::DoStaticEffect()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Plays the static FX associated with a spell that is being cast
//o------------------------------------------------------------------------------------------------o
void CMagic::DoStaticEffect( CChar *source, SI32 num )
{
CMagicStat temp = spells[num].StaticEffect();
if( temp.Effect() != INVALIDID )
{
Effects->PlayStaticAnimation( source, temp.Effect(), temp.Speed(), temp.Loop() );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::DoMoveEffect()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Plays the moving FX associated with a spell that is being cast
//o------------------------------------------------------------------------------------------------o
void CMagic::DoMoveEffect( SI32 num, CBaseObject *target, CChar *source )
{
CMagicMove temp = spells[num].MoveEffect();
if( temp.Effect() != INVALIDID )
{
Effects->PlayMovingAnimation( source, target, temp.Effect(), temp.Speed(), temp.Loop(), ( temp.Explode() == 1 ));
}
}
void HandleCommonGump( CSocket *mSock, CScriptSection *gumpScript, UI16 gumpIndex );
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::PolymorphMenu()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Opens the polymorph menu to allow player to choose which creature to poly into
//o------------------------------------------------------------------------------------------------o
void CMagic::PolymorphMenu( CSocket *s, UI16 gmindex )
{
std::string sect = "POLYMORPHMENU " + oldstrutil::number( gmindex );
CScriptSection *polyStuff = FileLookup->FindEntry( sect, menus_def );
if( polyStuff == nullptr )
return;
HandleCommonGump( s, polyStuff, gmindex );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::Polymorph()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Applies effects of Polymorph spell to caster
//o------------------------------------------------------------------------------------------------o
void CMagic::Polymorph( CSocket *s, UI16 polyId )
{
UI08 id1 = static_cast<UI08>( polyId >> 8 );
UI08 id2 = static_cast<UI08>( polyId % 256 );
CChar *mChar = s->CurrcharObj();
// store our original ID
mChar->SetOrgId( mChar->GetId() );
Effects->PlaySound( mChar, 0x020F );
// Temp effect will actually switch our body for us
Effects->TempEffect( mChar, mChar, 18, id1, id2, 0 );
mChar->IsPolymorphed( true );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::LogSpell()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Writes out spell casting details to spell.log file
//o------------------------------------------------------------------------------------------------o
void CMagic::LogSpell( std::string spell, CChar *player1, CChar *player2, const std::string &extraInfo )
{
if( spell.empty() || !ValidateObject( player1 ))
return;
std::string logName = cwmWorldState->ServerData()->Directory( CSDDP_LOGS ) + "spell.log";
std::ofstream logDestination;
logDestination.open( logName.c_str(), std::ios::out | std::ios::app );
if( !logDestination.is_open() )
{
Console.Error(oldstrutil::format( "Unable to open spell log file %s!", logName.c_str() ));
return;
}
char dateTime[1024];
RealTime( dateTime );
logDestination << "[" << dateTime << "] ";
std::string casterName = GetNpcDictName( player1, nullptr, NRS_SYSTEM );
logDestination << casterName << " (serial: " << std::hex << player1->GetSerial() << " ) ";
logDestination << "cast spell <" << spell << "> ";
if( ValidateObject( player2 ))
{
std::string targetName = GetNpcDictName( player2, nullptr, NRS_SYSTEM );
logDestination << "on player " << targetName << " (serial: " << player2->GetSerial() << " ) ";
}
logDestination << "Extra Info: " << extraInfo << std::endl;
logDestination.close();
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::Register()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Registers a spell in the JS Engine
//o------------------------------------------------------------------------------------------------o
void CMagic::Register( cScript *toRegister, SI32 spellNumber, bool isEnabled )
{
#if defined( UOX_DEBUG_MODE )
Console.Print( oldstrutil::format( "Registering spell number %i\n", spellNumber ));
#endif
if( spellNumber < 0 || static_cast<size_t>( spellNumber ) >= spells.size() )
return;
if( toRegister == nullptr )
return;
spells[spellNumber].JSScript( JSMapping->GetScriptId( toRegister->Object() ));
spells[spellNumber].Enabled( isEnabled );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMagic::SetSpellStatus()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Sets the enabled state of a given spell
//o------------------------------------------------------------------------------------------------o
void CMagic::SetSpellStatus( SI32 spellNumber, bool isEnabled )
{
if( spellNumber < 0 || static_cast<size_t>( spellNumber ) >= spells.size() )
return;
spells[spellNumber].Enabled( isEnabled );
}