mirror of
https://github.com/UOX3DevTeam/UOX3
synced 2026-08-13 12:27:04 -04:00
Fixed bug where player might get stuck as frozen in ghost form and unable to move if dying in the midst of casting a spell Fixed bug where reagents could not be dropped directly from ground and into newbie reagents bag, as the bag didn't have the proper setup for a container. Now inherits its setup from a regular bag (dfndata/items/magic/reagents.dfn) Fixed two broken timer references in JS: Timer.SPELLRECOVERYTIME and Timer.LOYALTYTIME Fixed a bug where players could gain points in Evaluating Intelligence skill by targeting themselves (js/skills/evaluateintel.js) Updated Evaluating Intelligence skill to show more detailed information about targets (js/skills/evaluateintel.js) Fixed some issues with sound effects in JS based spells UOX3 console should now show 100% when done saving, regardless of the amount of data it has to save Earthquake spell can no longer harm invulnerable characters Fixed issue where NPC would evade instead of change to a different nearby target if unable to pathfind to current target Damage dealt by Fire Field spell is now determined by BASEDMG tag (defaults to 2, if resisted, half of that) in spells.dfn The rate at which the effects of field spells (Fire Field, Poison Field and Paralyze Field) are applied is now determined via the DAMAGEDELAY tag in spells.dfn, which defaults to 1.0 seconds
2819 lines
96 KiB
C++
2819 lines
96 KiB
C++
#include "uox3.h"
|
|
#include "cGuild.h"
|
|
#include "combat.h"
|
|
#include "townregion.h"
|
|
#include "cRaces.h"
|
|
#include "skills.h"
|
|
#include "cMagic.h"
|
|
#include "cEffects.h"
|
|
#include "CPacketSend.h"
|
|
#include "classes.h"
|
|
#include "regions.h"
|
|
#include "magic.h"
|
|
#include "Dictionary.h"
|
|
#include "movement.h"
|
|
#include "cScript.h"
|
|
#include "CJSMapping.h"
|
|
#include <algorithm>
|
|
|
|
#undef DBGFILE
|
|
#define DBGFILE "combat.cpp"
|
|
#define DEBUG_COMBAT 0
|
|
|
|
CHandleCombat *Combat = nullptr;
|
|
|
|
#define SWINGAT (UI32)1.75 * 1000
|
|
|
|
const UI08 BODYPERCENT = 0;
|
|
//const UI08 ARMSPERCENT = 1;
|
|
//const UI08 HEADPERCENT = 2;
|
|
//const UI08 LEGSPERCENT = 3;
|
|
//const UI08 NECKPERCENT = 4;
|
|
//const UI08 OTHERPERCENT = 5;
|
|
const UI08 TOTALTARGETSPOTS = 6; // Wonder if that should be one?
|
|
|
|
const UI08 LOCPERCENTAGES[TOTALTARGETSPOTS] = { 44, 14, 14, 14, 7, 7 };
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - bool StartAttack( CChar *cAttack, CChar *cTarget )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Handle characters initiating combat with other characters
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
bool CHandleCombat::StartAttack( CChar *cAttack, CChar *cTarget )
|
|
{
|
|
if( !ValidateObject( cAttack ) || !ValidateObject( cTarget ) || cAttack == cTarget )
|
|
return false;
|
|
if( cAttack->IsDead() || cTarget->IsDead() )
|
|
return false;
|
|
if( cAttack->GetTarg() == cTarget )
|
|
return false;
|
|
if( cTarget->GetVisible() > 2 )
|
|
return false;
|
|
if( !objInRange( cAttack, cTarget, DIST_NEXTTILE ) && !LineOfSight( nullptr, cAttack, cTarget->GetX(), cTarget->GetY(), ( cTarget->GetZ() + 15 ), WALLS_CHIMNEYS + DOORS + FLOORS_FLAT_ROOFING, false ) )
|
|
return false;
|
|
|
|
if( !cAttack->GetCanAttack() || cAttack->IsEvading() ) // Is the char allowed to attack?
|
|
return false;
|
|
if( cAttack->GetNPCAiType() == AI_DUMMY ) // If passive, don't allow attack
|
|
return false;
|
|
|
|
// If two NPCs try to fight, do some extra checks to make sure they're allowed to (ignore if both are pets)
|
|
if( cAttack->IsNpc() && cTarget->IsNpc() && ( cAttack->GetOwner() == INVALIDSERIAL && cTarget->GetOwner() == INVALIDSERIAL ))
|
|
{
|
|
// Are both the NPCs non-human?
|
|
if( !cwmWorldState->creatures[cAttack->GetID()].IsHuman() && !cwmWorldState->creatures[cTarget->GetID()].IsHuman() )
|
|
{
|
|
// Don't allow if the NPCs belong to same race
|
|
if( cAttack->GetRace() == cTarget->GetRace() )
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Don't allow if they both have same NPC Flag (evil, innocent, neutral)
|
|
if( cAttack->GetNPCFlag() == cTarget->GetNPCFlag() )
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Check if combat is allowed in attacker AND target's regions, but allow it if it's a guard
|
|
if( cAttack->GetNPCAiType() != AI_GUARD && ( cAttack->GetRegion()->IsSafeZone() || cTarget->GetRegion()->IsSafeZone() ))
|
|
{
|
|
// Either attacker or target is in a safe zone where all aggressive actions are forbidden, disallow
|
|
CSocket *cSocket = cAttack->GetSocket();
|
|
if( cSocket )
|
|
cSocket->sysmessage( 1799 );
|
|
return false;
|
|
}
|
|
|
|
bool returningAttack = false;
|
|
|
|
cAttack->SetTarg( cTarget );
|
|
cAttack->SetAttacker( cTarget );
|
|
cAttack->SetAttackFirst( ( cTarget->GetTarg() != cAttack ) );
|
|
if( !cTarget->IsInvulnerable() && (!ValidateObject( cTarget->GetTarg() ) || !objInRange( cTarget, cTarget->GetTarg(), DIST_INRANGE )) ) // Only invuln don't fight back
|
|
{
|
|
if( cTarget->GetNPCAiType() != AI_DUMMY )
|
|
{
|
|
cTarget->SetTarg( cAttack );
|
|
cTarget->SetAttacker( cAttack );
|
|
cTarget->SetAttackFirst( false );
|
|
returningAttack = true;
|
|
}
|
|
|
|
if( cTarget->GetSocket() != nullptr )
|
|
{
|
|
CPAttackOK tSend = (*cAttack);
|
|
cTarget->GetSocket()->Send( &tSend );
|
|
}
|
|
}
|
|
|
|
if( WillResultInCriminal( cAttack, cTarget ) ) //REPSYS
|
|
{
|
|
criminal( cAttack );
|
|
bool regionGuarded = ( cTarget->GetRegion()->IsGuarded() );
|
|
if( cwmWorldState->ServerData()->GuardsStatus() && regionGuarded && cTarget->IsNpc() && cTarget->GetNPCAiType() != AI_GUARD && cwmWorldState->creatures[cTarget->GetID()].IsHuman() )
|
|
{
|
|
cTarget->TextMessage( nullptr, 335, TALK, true );
|
|
callGuards( cTarget, cAttack );
|
|
}
|
|
}
|
|
|
|
if( cAttack->GetVisible() == VT_TEMPHIDDEN || cAttack->GetVisible() == VT_INVISIBLE )
|
|
cAttack->ExposeToView();
|
|
|
|
if( !cAttack->IsNpc() )
|
|
cAttack->BreakConcentration( cAttack->GetSocket() );
|
|
else
|
|
{
|
|
UI16 toPlay = cwmWorldState->creatures[cAttack->GetID()].GetSound( SND_STARTATTACK );
|
|
if( toPlay != 0x00 )
|
|
Effects->PlaySound( cAttack, toPlay );
|
|
|
|
// if the source is an npc, make sure they're in war mode and reset their movement time
|
|
if( !cAttack->IsAtWar() )
|
|
cAttack->ToggleCombat();
|
|
if( cAttack->GetMounted() )
|
|
cAttack->SetTimer( tNPC_MOVETIME, BuildTimeValue( cAttack->GetMountedWalkingSpeed() ) );
|
|
else
|
|
cAttack->SetTimer( tNPC_MOVETIME, BuildTimeValue( cAttack->GetWalkingSpeed() ) );
|
|
}
|
|
|
|
// Only unhide the defender, if they're going to return the attack (otherwise they're doing nothing!)
|
|
if( returningAttack )
|
|
{
|
|
if( cTarget->GetVisible() == VT_TEMPHIDDEN || cAttack->GetVisible() == VT_INVISIBLE )
|
|
cTarget->ExposeToView();
|
|
|
|
if( !cTarget->IsNpc() )
|
|
cTarget->BreakConcentration( cTarget->GetSocket() );
|
|
|
|
// if the target is an npc, and not a guard, make sure they're in war mode and update their movement time
|
|
// ONLY IF THEY'VE CHANGED ATTACKER
|
|
if( cTarget->IsNpc() && cTarget->GetNPCAiType() != AI_GUARD )
|
|
{
|
|
if( !cTarget->IsAtWar() )
|
|
cTarget->ToggleCombat();
|
|
if( cTarget->GetMounted() )
|
|
cTarget->SetTimer( tNPC_MOVETIME, BuildTimeValue( cTarget->GetMountedWalkingSpeed() ) );
|
|
else
|
|
cTarget->SetTimer( tNPC_MOVETIME, BuildTimeValue( cTarget->GetWalkingSpeed() ) );
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void callGuards( CChar *mChar, CChar *targChar );
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void PlayerAttack( CSocket *s )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Handle player attacking (Double-clicking whilst in war mode)
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CHandleCombat::PlayerAttack( CSocket *s )
|
|
{
|
|
if( s == nullptr )
|
|
return;
|
|
|
|
CChar *ourChar = s->CurrcharObj();
|
|
if( !ValidateObject( ourChar ) )
|
|
return;
|
|
|
|
// Don't allow attacking other characters if invulnerable
|
|
if( ourChar->IsInvulnerable() )
|
|
{
|
|
s->sysmessage( 1973 ); // You cannot engage in combat while invulnerable!
|
|
return;
|
|
}
|
|
|
|
SERIAL serial = s->GetDWord( 1 );
|
|
if( serial == INVALIDSERIAL )
|
|
{
|
|
ourChar->SetTarg( nullptr );
|
|
return;
|
|
}
|
|
CChar *i = calcCharObjFromSer( serial );
|
|
if( !ValidateObject( i ) )
|
|
{
|
|
ourChar->SetTarg( nullptr );
|
|
return;
|
|
}
|
|
if( ourChar->IsDead() )
|
|
{
|
|
if( i->IsNpc() )
|
|
{
|
|
switch( i->GetNPCAiType() )
|
|
{
|
|
case AI_HEALER_G: // Good Healer
|
|
if( !ourChar->IsCriminal() && !ourChar->IsMurderer() )
|
|
{ // Character is innocent
|
|
if( objInRange( i, ourChar, DIST_NEARBY ) )
|
|
{
|
|
CMultiObj *multiObj = ourChar->GetMultiObj();
|
|
if( !ValidateObject( multiObj ) || multiObj->GetOwner() == ourChar->GetSerial() )
|
|
{
|
|
if( LineOfSight( s, ourChar, i->GetX(), i->GetY(), ( i->GetZ() + 15 ), WALLS_CHIMNEYS + DOORS + FLOORS_FLAT_ROOFING, false ) )
|
|
{
|
|
//let's resurrect him!
|
|
Effects->PlayCharacterAnimation( i, 0x10, 0, 7 );
|
|
NpcResurrectTarget( ourChar );
|
|
Effects->PlayStaticAnimation( ourChar, 0x376A, 0x09, 0x06 );
|
|
i->TextMessage( nullptr, ( 316 + RandomNum( 0, 4 ) ), TALK, false );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
i->TextMessage( nullptr, 321, TALK, true );
|
|
}
|
|
else // Character is criminal or murderer
|
|
i->TextMessage( nullptr, 322, TALK, true );
|
|
break;
|
|
case AI_HEALER_E: // Evil Healer
|
|
if( ourChar->IsMurderer() )
|
|
{
|
|
if( objInRange( i, ourChar, DIST_NEARBY ) ) // let's resurrect him
|
|
{
|
|
CMultiObj *multiObj = ourChar->GetMultiObj();
|
|
if( !ValidateObject( multiObj ) || multiObj->GetOwner() == ourChar->GetSerial() )
|
|
{
|
|
if( LineOfSight( s, ourChar, i->GetX(), i->GetY(), ( i->GetZ() + 15 ), WALLS_CHIMNEYS + DOORS + FLOORS_FLAT_ROOFING, false ) )
|
|
{
|
|
if( i->GetBodyType() == BT_GARGOYLE
|
|
|| ( cwmWorldState->ServerData()->ForceNewAnimationPacket()
|
|
&& ( i->GetBodyType() == BT_HUMAN || i->GetBodyType() == BT_ELF )))
|
|
Effects->PlayNewCharacterAnimation( i, N_ACT_SPELL, S_ACT_SPELL_TARGET ); // Action 0x0b, subAction 0x00
|
|
else
|
|
Effects->PlayCharacterAnimation( i, ACT_SPELL_TARGET, 0, 5 ); // 0x10
|
|
NpcResurrectTarget( ourChar );
|
|
Effects->PlayStaticAnimation( ourChar, 0x3709, 0x09, 0x19 ); //Flamestrike effect
|
|
i->TextMessage( nullptr, ( 323 + RandomNum( 0, 4 ) ), TALK, false );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
i->TextMessage( nullptr, 328, TALK, true );
|
|
}
|
|
else
|
|
i->TextMessage( nullptr, 329, TALK, true );
|
|
break;
|
|
default:
|
|
s->sysmessage( 330 );
|
|
break;
|
|
}
|
|
return;
|
|
} //if isNpc
|
|
else
|
|
{
|
|
if( cwmWorldState->ServerData()->PlayerPersecutionStatus() )
|
|
{
|
|
ourChar->SetTarg( i );
|
|
Skills->Persecute( s );
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
s->sysmessage( 330 );
|
|
return;
|
|
}
|
|
}//if isNpc
|
|
}//if isDead
|
|
else if( ourChar->GetTarg() != i )
|
|
{
|
|
std::vector<UI16> scriptTriggers = ourChar->GetScriptTriggers();
|
|
for( auto scriptTrig : scriptTriggers )
|
|
{
|
|
cScript *toExecute = JSMapping->GetScript( scriptTrig );
|
|
if( toExecute != nullptr )
|
|
{
|
|
// -1 == script doesn't exist, or returned -1
|
|
// 0 == script returned false, 0, or nothing - don't execute hard code
|
|
// 1 == script returned true or 1
|
|
if( toExecute->OnCombatStart( ourChar, i ) == 0 )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !ourChar->GetCanAttack() ) //Is our char allowed to attack
|
|
{
|
|
s->sysmessage( 1778 );
|
|
return;
|
|
}
|
|
|
|
// Check if combat is allowed in attacker AND target's regions
|
|
if( ourChar->GetRegion()->IsSafeZone() || i->GetRegion()->IsSafeZone() )
|
|
{
|
|
// Either attacker or target is in a safe zone where all aggressive actions are forbidden, disallow
|
|
s->sysmessage( 1799 );
|
|
return;
|
|
}
|
|
|
|
ourChar->SetTarg( i );
|
|
if( ourChar->GetVisible() == VT_TEMPHIDDEN || ourChar->GetVisible() == VT_INVISIBLE )
|
|
ourChar->ExposeToView();
|
|
if( i->IsDead() || i->GetHP() <= 0 )
|
|
{
|
|
s->sysmessage( 331 );
|
|
return;
|
|
}
|
|
if( i->GetNPCAiType() == AI_PLAYERVENDOR ) // PlayerVendors
|
|
{
|
|
s->sysmessage( 332, i->GetName().c_str() );
|
|
return;
|
|
}
|
|
if( i->IsInvulnerable() )
|
|
{
|
|
s->sysmessage( 333 );
|
|
return;
|
|
}
|
|
if( i->IsEvading() )
|
|
{
|
|
s->sysmessage( 1792 );
|
|
return;
|
|
}
|
|
|
|
// Deal with players attacking NPCs they're escorting!
|
|
if( i->IsNpc() && i->GetQuestType() == 0xFF && i->GetOwnerObj() == ourChar )
|
|
{
|
|
i->SetNpcWander( WT_FREE ); // Wander freely
|
|
i->SetFTarg( nullptr ); // Reset follow target
|
|
i->SetQuestType( 0 ); // Reset quest type
|
|
i->SetQuestDestRegion( 0 ); // Reset quest destination region
|
|
// Set a timer to automatically delete the NPC
|
|
i->SetTimer( tNPC_SUMMONTIME, cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_ESCORTDONE ) );
|
|
i->SetOwner( nullptr );
|
|
i->TextMessage( nullptr, 1797, TALK, false ); // Send out the rant about being abandoned
|
|
}
|
|
|
|
//flag them FIRST so that anything attacking them as a result of this is not flagged.
|
|
if( WillResultInCriminal( ourChar, i ) ) //REPSYS
|
|
{
|
|
criminal( ourChar );
|
|
bool regionGuarded = ( i->GetRegion()->IsGuarded() );
|
|
if( cwmWorldState->ServerData()->GuardsStatus() && regionGuarded && i->IsNpc() && i->GetNPCAiType() != AI_GUARD && cwmWorldState->creatures[i->GetID()].IsHuman() )
|
|
{
|
|
i->TextMessage( nullptr, 335, TALK, true );
|
|
callGuards( i, ourChar );
|
|
}
|
|
}
|
|
|
|
if( i->IsGuarded() )
|
|
petGuardAttack( ourChar, i, i );
|
|
|
|
std::string attackerName = getNpcDictName( ourChar );
|
|
std::string targetName = getNpcDictName( i );
|
|
|
|
ourChar->TextMessage( nullptr, 334, EMOTE, 1, attackerName.c_str(), targetName.c_str() ); // Attacker emotes "You see attacker attacking target" to all nearby
|
|
|
|
if( !i->IsNpc() )
|
|
{
|
|
CSocket *iSock = i->GetSocket();
|
|
if( iSock != nullptr )
|
|
{
|
|
std::string attackerName = getNpcDictName( ourChar, iSock );
|
|
i->TextMessage( iSock, 1281, EMOTE, 1, attackerName.c_str() ); // "Attacker is attacking you!" sent to target socket only
|
|
}
|
|
}
|
|
|
|
// keep the target highlighted
|
|
CPAttackOK toSend = (*i);
|
|
s->Send( &toSend );
|
|
|
|
if( i->GetNPCAiType() != AI_GUARD && !ValidateObject( i->GetTarg() ) )
|
|
{
|
|
i->SetAttacker( ourChar );
|
|
i->SetAttackFirst( false );
|
|
}
|
|
ourChar->SetAttackFirst( true );
|
|
ourChar->SetAttacker( i );
|
|
AttackTarget( i, ourChar );
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void AttackTarget( CChar *cAttack, CChar *cTarget )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Handle the attacking action in combat
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CHandleCombat::AttackTarget( CChar *cAttack, CChar *cTarget )
|
|
{
|
|
// Check if OnCombatStart event exists, necessary here to make event run for NPCs attacking
|
|
std::vector<UI16> scriptTriggers = cAttack->GetScriptTriggers();
|
|
for( auto scriptTrig : scriptTriggers )
|
|
{
|
|
cScript *toExecute = JSMapping->GetScript( scriptTrig );
|
|
if( toExecute != nullptr )
|
|
{
|
|
// -1 == script doesn't exist, or returned -1
|
|
// 0 == script returned false, 0, or nothing - don't execute hard code
|
|
// 1 == script returned true or 1
|
|
if( toExecute->OnCombatStart( cAttack, cTarget ) == 0 )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !cAttack->GetCanAttack() || cAttack->IsEvading() ) // Is the char allowed to attack?
|
|
return;
|
|
|
|
if( !StartAttack( cAttack, cTarget ) )
|
|
return;
|
|
|
|
// If the target is an innocent, not a racial or guild ally/enemy, then flag them as criminal
|
|
// and, of course, call the guards ;>
|
|
if( WillResultInCriminal( cAttack, cTarget ) )
|
|
{
|
|
criminal( cAttack );
|
|
bool regionGuarded = ( cTarget->GetRegion()->IsGuarded() );
|
|
if( cwmWorldState->ServerData()->GuardsStatus() && regionGuarded )
|
|
{
|
|
if( cTarget->IsNpc() && cTarget->GetNPCAiType() != AI_GUARD && cwmWorldState->creatures[cTarget->GetID()].IsHuman() )
|
|
{
|
|
cTarget->TextMessage( nullptr, 1282, TALK, true );
|
|
callGuards( cTarget, cAttack );
|
|
}
|
|
}
|
|
}
|
|
if( cAttack->DidAttackFirst() )
|
|
{
|
|
std::string attackerName = getNpcDictName( cAttack );
|
|
std::string targetName = getNpcDictName( cTarget );
|
|
|
|
cAttack->TextMessage( nullptr, 334, EMOTE, 1, attackerName.c_str(), targetName.c_str() ); // NPC should emote "Source is attacking Target" to all nearby
|
|
if( !cTarget->IsNpc() )
|
|
{
|
|
CSocket *iSock = cTarget->GetSocket();
|
|
if( iSock != nullptr )
|
|
{
|
|
attackerName = getNpcDictName( cAttack, iSock );
|
|
cTarget->TextMessage( iSock, 1281, EMOTE, 1, attackerName.c_str() ); // Target should get an emote only to his socket "Target is attacking you!"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - CItem * getWeapon( CChar *i )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Find what weapon (if any) character is holding
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
CItem * CHandleCombat::getWeapon( CChar *i )
|
|
{
|
|
if( !ValidateObject( i ) )
|
|
return nullptr;
|
|
|
|
CItem *j = i->GetItemAtLayer( IL_RIGHTHAND );
|
|
if( ValidateObject( j ) )
|
|
{
|
|
if( j->GetType() == IT_SPELLBOOK ) // spell books aren't weapons
|
|
return nullptr;
|
|
return j;
|
|
}
|
|
j = i->GetItemAtLayer( IL_LEFTHAND );
|
|
if( ValidateObject( j ) && !j->IsShieldType() )
|
|
return j;
|
|
return nullptr;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - CItem * getShield( CChar *i )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Check players hands for a shield
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
CItem * CHandleCombat::getShield( CChar *i )
|
|
{
|
|
if( ValidateObject( i ) )
|
|
{
|
|
CItem *shield = i->GetItemAtLayer( IL_LEFTHAND );
|
|
if( ValidateObject( shield ) && shield->IsShieldType() )
|
|
return shield;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - UI08 getWeaponType( CItem *i )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Find what weapon type a character is holding (based on its ID)
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
UI08 CHandleCombat::getWeaponType( CItem *i )
|
|
{
|
|
if( !ValidateObject( i ) )
|
|
return WRESTLING;
|
|
|
|
switch( i->GetID() )
|
|
{
|
|
// Slashing Swords
|
|
case 0x0EC4: //skinning knife
|
|
case 0x0EC5: //skinning knife
|
|
case 0x0F60: //longsword
|
|
case 0x0F61: //longsword
|
|
case 0x13B7: //long sword
|
|
case 0x13B8: //long sword
|
|
case 0x1440: //cutlass
|
|
case 0x1441: //cutlass
|
|
case 0x26BB: //bone harvester - AoS
|
|
case 0x26C5: //bone harvester - AoS
|
|
case 0x2573: //magic sword - LBR
|
|
case 0x2574: //magic sword - LBR
|
|
case 0x2575: //magic sword - LBR
|
|
case 0x2576: //magic sword - LBR
|
|
case 0x257E: //short sword - LBR
|
|
case 0x27A4: //wakizashi - SE
|
|
case 0x27EF: //wakizashi - SE
|
|
case 0x0907: //gargish shortblade - SA
|
|
case 0x4076: //gargish shortblade - SA
|
|
case 0x48C6: //gargish bone harvester - SA
|
|
case 0x48C7: //gargish bone harvester - SA
|
|
return DEF_SWORDS;
|
|
// Default Swords
|
|
case 0x13F6: //butcher knife
|
|
case 0x13F7: //butcher knife
|
|
case 0x13FE: //katana
|
|
case 0x13FF: //katana
|
|
case 0x13B5: //scimitar
|
|
case 0x13B6: //scimitar
|
|
case 0x13B9: //viking sword
|
|
case 0x13BA: //viking sword
|
|
case 0x255E: //ratman sword - LBR
|
|
case 0x2560: //skeleton scimitar - LBR
|
|
case 0x090C: //gargish glass sword - SA
|
|
case 0x4073: //gargish glass sword - SA
|
|
case 0x0900: //gargish stone war sword - SA
|
|
case 0x4071: //gargish stone war sword - SA
|
|
case 0x48B6: //gargish butcherknife - SA
|
|
case 0x48B7: //gargish butcherknife - SA
|
|
case 0x48BA: //gargish katana - SA
|
|
case 0x48BB: //gargish katana - SA
|
|
case 0xA28B: //bladed whip - ToL
|
|
case 0xA293: //bladed whip - ToL
|
|
return SLASH_SWORDS;
|
|
// One-Handed Lg. Swords
|
|
case 0x0F5E: //broadsword
|
|
case 0x0F5F: //broadsword
|
|
case 0x257D: //longsword - LBR
|
|
case 0x26CE: //paladin sword - AoS
|
|
case 0x26CF: //paladin sword - AoS
|
|
case 0x2554: //daemon sword - LBR
|
|
case 0x2D27: //radiant scimitar - ML
|
|
case 0x2D33: //radiant scimitar - ML
|
|
case 0x2D29: //elven machete - ML
|
|
case 0x2D35: //elven machete - ML
|
|
case 0x090B: //gargish dread sword - SA
|
|
case 0x4074: //gargish dread sword - SA
|
|
case 0xA33B: //antique sword 1 south
|
|
case 0xA33C: //antique sword 1 east
|
|
case 0xA33D: //antique sword 2 south
|
|
case 0xA33E: //antique sword 2 east
|
|
case 0xA33F: //antique sword 3 south
|
|
case 0xA340: //antique sword 3 east
|
|
case 0xA341: //skull sword south
|
|
case 0xA342: //skull sword east
|
|
case 0xA345: //gargoyle skull sword south
|
|
case 0xA346: //gargoyle skull sword east
|
|
return ONEHND_LG_SWORDS;
|
|
// Two-Handed Lg. Swords
|
|
case 0x143E: //halberd
|
|
case 0x143F: //halberd
|
|
case 0x26BD: //bladed staff - AoS
|
|
case 0x26C7: //bladed staff - AoS
|
|
case 0x26C1: //crescent blade - AoS
|
|
case 0x26CB: //crescent blade - AoS
|
|
case 0x2578: //no-dachi - LBR
|
|
case 0x27A2: //no-dachi - SE
|
|
case 0x27ED: //no-dachi - SE
|
|
case 0x27A8: //bokuto - SE
|
|
case 0x27F3: //bokuto - SE
|
|
case 0x2D26: //rune blade - ML
|
|
case 0x2D32: //rune blade - ML
|
|
case 0x0908: //gargish talwar - SA
|
|
case 0x4075: //gargish talwar - SA
|
|
return TWOHND_LG_SWORDS;
|
|
// Bardiche
|
|
case 0x0F4D: //bardiche
|
|
case 0x0F4E: //bardiche
|
|
case 0x26BA: //scythe - AoS
|
|
case 0x26C4: //scythe - AoS
|
|
case 0x255B: //ophidian bardiche - LBR
|
|
case 0x2577: //naginata - LBR
|
|
case 0x48B4: //gargish bardiche - SA
|
|
case 0x48B5: //gargish bardiche - SA
|
|
case 0x48C4: //gargish scythe - SA
|
|
case 0x48C5: //gargish scythe - SA
|
|
return BARDICHE;
|
|
// One-Handed Axes
|
|
case 0x0EC2: //cleaver
|
|
case 0x0EC3: //cleaver
|
|
case 0x0E85: //pickaxe
|
|
case 0x0E86: //pickaxe
|
|
case 0x2567: //orc lord battleaxe - LBR
|
|
case 0x2579: //pick - LBR
|
|
case 0x48AE: //gargish cleaver
|
|
case 0x48AF: //gargish cleaver
|
|
return ONEHND_AXES;
|
|
// Two-Handed Axes
|
|
case 0x13FA: //large battle axe
|
|
case 0x13FB: //large battle axe
|
|
case 0x1442: //two handed axe
|
|
case 0x1443: //two handed axe
|
|
case 0x0F43: //hatchet
|
|
case 0x0F44: //hatchet
|
|
case 0x0F45: //executioner's axe
|
|
case 0x0F46: //executioner's axe
|
|
case 0x0F47: //battle axe
|
|
case 0x0F48: //battle axe
|
|
case 0x0F49: //axe
|
|
case 0x0F4A: //axe
|
|
case 0x0F4B: //double axe
|
|
case 0x0F4C: //double axe
|
|
case 0x255F: //skeleton axe - LBR
|
|
case 0x255D: //ratman axe - LBR
|
|
case 0x2564: //troll axe - LBR
|
|
case 0x2570: //hatchet - LBR
|
|
case 0x2D28: //ornate axe - ML
|
|
case 0x2D34: //ornate axe - ML
|
|
case 0x48B0: //gargish battleaxe - SA
|
|
case 0x48B1: //gargish battleaxe - SA
|
|
case 0x48B2: //gargish axe - SA
|
|
case 0x48B3: //gargish axe - SA
|
|
return TWOHND_AXES;
|
|
// Default Maces
|
|
case 0x13E3: //smith's hammer
|
|
case 0x13E4: //smith's hammer
|
|
case 0x13B3: //club
|
|
case 0x13B4: //club
|
|
case 0x0FB4: //sledge hammer
|
|
case 0x0FB5: //sledge hammer
|
|
case 0x0F5C: //mace
|
|
case 0x0F5D: //mace
|
|
case 0x1406: //war mace
|
|
case 0x1407: //war mace
|
|
case 0x143C: //hammer pick
|
|
case 0x143D: //hammer pick
|
|
case 0x13AF: //war axe
|
|
case 0x13B0: //war axe
|
|
case 0x143A: //maul
|
|
case 0x143B: //maul
|
|
case 0x26BC: //scepter - AoS
|
|
case 0x26C6: //scepter - AoS
|
|
case 0x2557: //lizardman's mace - LBR
|
|
case 0x255C: //orc club - LBR
|
|
case 0x256F: //smyth's hammer - LBR
|
|
case 0x257F: //war mace - LBR
|
|
case 0x2D24: //diamond mace - ML
|
|
case 0x2D30: //diamond mace - ML
|
|
case 0x2D25: //wild staff - ML
|
|
case 0x2D31: //wild staff - ML
|
|
case 0x0903: //gargish disc mace - SA
|
|
case 0x406E: //gargish disc mace - SA
|
|
case 0x48C2: //gargish maul - SA
|
|
case 0x48C3: //gargish maul - SA
|
|
case 0xA289: //barbed whip - ToL
|
|
case 0xA291: //barbed whip - ToL
|
|
return DEF_MACES;
|
|
// Large Maces
|
|
case 0x13F4: //crook
|
|
case 0x13F5: //crook
|
|
case 0x13F8: //gnarled staff
|
|
case 0x13F9: //gnarled staff
|
|
case 0x0DF0: //black staff
|
|
case 0x0DF1: //black staff
|
|
case 0x0E81: //shepherd's crook
|
|
case 0x0E82: //shepherd's crook
|
|
case 0x0E89: //quarter staff
|
|
case 0x0E8A: //quarter staff
|
|
case 0x1438: //war hammer
|
|
case 0x1439: //war hammer
|
|
case 0x2555: //ettin hammer - LBR
|
|
case 0x2556: //liche's staff - LBR
|
|
case 0x2559: //ogre's club - LBR
|
|
case 0x255A: //ophidian staff - LBR
|
|
case 0x2561: //terathan staff - LBR
|
|
case 0x2565: //troll maul - LBR
|
|
case 0x2566: //frost troll club - LBR
|
|
case 0x2568: //orc mage staff - LBR
|
|
case 0x2569: //bone mage staff - LBR
|
|
case 0x256B: //magic staff - LBR
|
|
case 0x256C: //magic staff - LBR
|
|
case 0x256D: //magic staff - LBR
|
|
case 0x256E: //magic staff - LBR
|
|
case 0x27A3: //tessen - SE
|
|
case 0x27EE: //tessen - SE
|
|
case 0x27A6: //tetsubo - SE
|
|
case 0x27F1: //tetsubo - SE
|
|
case 0x27AE: //nunchako - SE
|
|
case 0x27F9: //nunchako - SE
|
|
case 0x48C0: //gargish war hammer - SA
|
|
case 0x48C1: //gargish war hammer - SA
|
|
case 0x48CC: //gargish tessen - SA
|
|
case 0x48CD: //gargish tessen - SA
|
|
case 0x0905: //gargish glass staff - SA
|
|
case 0x4070: //gargish glass staff - SA
|
|
case 0x0906: //gargish serpentstone staff - SA
|
|
case 0x406F: //gargish serpentstone staff - SA
|
|
case 0xA343: //skull staff south
|
|
case 0xA344: //skull staff east
|
|
case 0xA347: //gargoyle skull staff south
|
|
case 0xA348: //gargoyle skull staff east
|
|
return LG_MACES;
|
|
// Bows
|
|
case 0x13B1: //bow
|
|
case 0x13B2: //bow
|
|
case 0x26C2: //composite bow - AoS
|
|
case 0x26CC: //composite bow - AoS
|
|
case 0x2571: //horseman's bow - LBR
|
|
case 0x27A5: //yumi - SE
|
|
case 0x27F0: //yumi - SE
|
|
case 0x2D1E: //elven composite longbow - ML
|
|
case 0x2D2A: //elven composite longbow - ML
|
|
case 0x2D1F: //magical shortbow - ML
|
|
case 0x2D2B: //magical shortbow - ML
|
|
return BOWS;
|
|
// Crossbows
|
|
case 0x0F4F: //crossbow
|
|
case 0x0F50: //crossbow
|
|
case 0x13FC: //heavy crossbow
|
|
case 0x13FD: //heavy crossbow
|
|
case 0x26C3: //repeating crossbow - AoS
|
|
case 0x26CD: //repeating crossbow - AoS
|
|
return XBOWS;
|
|
case 0x27AA: //fukiya - SE - Blowgun, uses Dart ammo (0x2806 or 0x2804)
|
|
case 0x27F5: //fukiya - SE - Blowgun, uses Dart ammo (0x2806 or 0x2804)
|
|
return BLOWGUNS;
|
|
// Normal Fencing Weapons
|
|
case 0x0F51: //dagger
|
|
case 0x0F52: //dagger
|
|
case 0x1400: //kryss
|
|
case 0x1401: //kryss
|
|
case 0x1402: //short spear
|
|
case 0x1403: //short spear
|
|
case 0x1404: //war fork
|
|
case 0x1405: //war fork
|
|
case 0x257C: //sword(rapier) - LBR
|
|
case 0x2D20: //elven spellblade - ML
|
|
case 0x2D2C: //elven spellblade - ML
|
|
case 0x2D21: //assassin spike - ML
|
|
case 0x2D2D: //assassin spike - ML
|
|
case 0x2D22: //leafblade - ML
|
|
case 0x2D2E: //leafblade - ML
|
|
case 0x2D23: //war cleaver - ML
|
|
case 0x2D2F: //war cleaver - ML
|
|
case 0x0902: //gargish dagger - SA
|
|
case 0x406A: //gargish dagger
|
|
case 0x08FE: //gargish bloodblade - SA
|
|
case 0x4072: //gargish bloodblade - SA
|
|
case 0x48BC: //gargish kryss - SA
|
|
case 0x48BD: //gargish kryss - SA
|
|
case 0xA28A: //spiked whip - ToL
|
|
case 0xA292: //spiked whip - ToL
|
|
return DEF_FENCING;
|
|
// Stabbing Fencing Weapons
|
|
case 0x0E87: //pitchfork
|
|
case 0x0E88: //pitchfork
|
|
case 0x0F62: //spear
|
|
case 0x0F63: //spear
|
|
case 0x26BE: //pike - AoS
|
|
case 0x26C8: //pike - AoS
|
|
case 0x26BF: //double bladed staff - AoS
|
|
case 0x26C9: //double bladed staff - AoS
|
|
case 0x26C0: //lance - AoS
|
|
case 0x26CA: //lance - AoS
|
|
case 0x2558: //lizardman's spear - LBR
|
|
case 0x2562: //terathan spear - LBR
|
|
case 0x2572: //horseman's javelin - LBR
|
|
case 0x257A: //spear - LBR
|
|
case 0x257B: //spear - LBR
|
|
case 0x27A7: //lajatang - SE
|
|
case 0x27F2: //lajatang - SE
|
|
case 0x0904: //gargish dual pointed spear - SA
|
|
case 0x406D: //gargish dual pointed spear - SA
|
|
case 0x48C8: //gargish pike - SA
|
|
case 0x48C9: //gargish pike - SA
|
|
case 0x48CA: //gargish lance - SA
|
|
case 0x48CB: //gargish lance - SA
|
|
return TWOHND_FENCING;
|
|
case 0x27AF: //sai - SE
|
|
case 0x27FA: //sai - SE
|
|
return DUAL_FENCING_STAB;
|
|
case 0x27AB: //tekagi - SE
|
|
case 0x27F6: //tekagi - SE
|
|
case 0x27AD: //kama - SE
|
|
case 0x27F8: //kama - SE
|
|
case 0x48CE: //gargish tekagi - SA
|
|
case 0x48CF: //gargish tekagi - SA
|
|
return DUAL_FENCING_SLASH;
|
|
case 0x27A9: //daisho - SE
|
|
case 0x27F4: //daisho - SE
|
|
case 0x08FD: //gargish dual short axes - SA
|
|
case 0x4068: //gargish dual short axes - SA
|
|
case 0x48D0: //gargish daisho - SA
|
|
case 0x48D1: //gargish daisho - SA
|
|
return DUAL_SWORD;
|
|
case 0x08FF: //gargish boomerang - SA
|
|
case 0x4067: //gargish boomerang - SA
|
|
case 0x0901: //gargish cyclone - SA
|
|
case 0x406C: //gargish cyclone - SA
|
|
case 0x090A: //gargish soul glaive - SA
|
|
case 0x406B: //gargish soul glaive - SA
|
|
return THROWN;
|
|
default: // Wrestling
|
|
return WRESTLING;
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - UI08 getBowType( CItem *bItem )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Find if character is holding a Bow or XBow
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
UI08 CHandleCombat::getBowType( CItem *bItem )
|
|
{
|
|
if( !ValidateObject( bItem ) )
|
|
return 0;
|
|
|
|
switch( getWeaponType( bItem ) )
|
|
{
|
|
case BOWS:
|
|
return BOWS;
|
|
case XBOWS:
|
|
return XBOWS;
|
|
case BLOWGUNS:
|
|
return BLOWGUNS;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - UI08 getCombatSkill( CItem *wItem )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Gets your combat skill based on the weapon in your hand (if any)
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
UI08 CHandleCombat::getCombatSkill( CItem *wItem )
|
|
{
|
|
if( !ValidateObject( wItem ) )
|
|
return WRESTLING;
|
|
|
|
switch( getWeaponType( wItem ) )
|
|
{
|
|
case DEF_SWORDS:
|
|
case SLASH_SWORDS:
|
|
case ONEHND_LG_SWORDS:
|
|
case TWOHND_LG_SWORDS:
|
|
case ONEHND_AXES:
|
|
case TWOHND_AXES:
|
|
case BARDICHE:
|
|
case DUAL_SWORD:
|
|
return SWORDSMANSHIP;
|
|
case DEF_MACES:
|
|
case LG_MACES:
|
|
return MACEFIGHTING;
|
|
case DEF_FENCING:
|
|
case TWOHND_FENCING:
|
|
case DUAL_FENCING_SLASH:
|
|
case DUAL_FENCING_STAB:
|
|
return FENCING;
|
|
case BOWS:
|
|
case XBOWS:
|
|
case BLOWGUNS:
|
|
return ARCHERY;
|
|
case THROWN:
|
|
return THROWING;
|
|
case WRESTLING:
|
|
default:
|
|
return WRESTLING;
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - SI16 calcAtt( CChar *p, bool doDamage )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Calculate total attack power and do damage to the weapon
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
SI16 CHandleCombat::calcAtt( CChar *p, bool doDamage )
|
|
{
|
|
if( !ValidateObject( p ) )
|
|
return 0;
|
|
|
|
SI16 getDamage = 0;
|
|
|
|
CItem *weapon = getWeapon( p );
|
|
if( ValidateObject( weapon ) )
|
|
{
|
|
if( weapon->GetLoDamage() > 0 && weapon->GetHiDamage() > 0 )
|
|
{
|
|
// Calculate random damage dealt by weapon based on lo/hi damage from dfns
|
|
if( weapon->GetLoDamage() >= weapon->GetHiDamage() ) {
|
|
getDamage += weapon->GetLoDamage();
|
|
}
|
|
else {
|
|
getDamage += RandomNum( weapon->GetLoDamage(), weapon->GetHiDamage() );
|
|
}
|
|
|
|
// Chance to apply damage to (player's) weapon based on ini setting
|
|
if( doDamage && !p->IsNpc() && ( cwmWorldState->ServerData()->CombatWeaponDamageChance() >= RandomNum( 1, 100 )))
|
|
{
|
|
SI08 weaponDamage = 0;
|
|
UI08 weaponDamageMin = 0;
|
|
UI08 weaponDamageMax = 0;
|
|
|
|
// Fetch minimum and maximum weapon damage from ini
|
|
weaponDamageMin = cwmWorldState->ServerData()->CombatWeaponDamageMin();
|
|
weaponDamageMax = cwmWorldState->ServerData()->CombatWeaponDamageMax();
|
|
|
|
weaponDamage -= static_cast<std::uint8_t>( RandomNum( static_cast<std::uint16_t>(weaponDamageMin), static_cast<std::uint16_t>(weaponDamageMax) ));
|
|
weapon->IncHP( weaponDamage );
|
|
|
|
|
|
// If weapon hp has reached 0, destroy it
|
|
if( weapon->GetHP() <= 0 )
|
|
{
|
|
CSocket *mSock = p->GetSocket();
|
|
if( mSock != nullptr )
|
|
{
|
|
std::string name;
|
|
getTileName( (*weapon), name );
|
|
mSock->sysmessage( 311, name.c_str() ); // Your %s has been destroyed.
|
|
}
|
|
weapon->Delete();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if( p->IsNpc() )
|
|
{
|
|
if( p->GetLoDamage() >= p->GetHiDamage() ){
|
|
getDamage = p->GetLoDamage();
|
|
}
|
|
else if( p->GetHiDamage() > 2 ){
|
|
getDamage = RandomNum( p->GetLoDamage(), p->GetHiDamage() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UI16 getWrestSkill = ( p->GetSkill( WRESTLING ) / 65 );
|
|
if( getWrestSkill > 0 ) {
|
|
getDamage = HalfRandomNum( getWrestSkill );
|
|
}
|
|
else{
|
|
getDamage = RandomNum( 1, 2 );
|
|
}
|
|
}
|
|
if( getDamage < 1 )
|
|
getDamage = 1;
|
|
return getDamage;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - SI16 calcLowDamage( CChar *p )
|
|
//| Date - 11. Mar, 2006
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Calculate low damage value of the char
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
SI16 CHandleCombat::calcLowDamage( CChar *p )
|
|
{
|
|
if( !ValidateObject( p ) )
|
|
return 0;
|
|
|
|
SI16 getDamage = 0;
|
|
|
|
CItem *weapon = getWeapon( p );
|
|
if( ValidateObject( weapon ) )
|
|
{
|
|
if( weapon->GetLoDamage() > 0 && weapon->GetHiDamage() > 0 )
|
|
{
|
|
if( weapon->GetLoDamage() >= weapon->GetHiDamage() )
|
|
getDamage = weapon->GetHiDamage();
|
|
else
|
|
getDamage = weapon->GetLoDamage();
|
|
}
|
|
}
|
|
else if( p->IsNpc() )
|
|
{
|
|
if( p->GetLoDamage() >= p->GetHiDamage() )
|
|
getDamage = p->GetHiDamage();
|
|
else if( p->GetHiDamage() > 2 )
|
|
getDamage = p->GetLoDamage();
|
|
}
|
|
else
|
|
{
|
|
UI16 getWrestSkill = ( p->GetSkill( WRESTLING ) / 65 );
|
|
if( getWrestSkill > 0 )
|
|
getDamage = ( getWrestSkill / 2);
|
|
else
|
|
getDamage = 1;
|
|
}
|
|
if( getDamage < 1 )
|
|
getDamage = 1;
|
|
return getDamage;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - SI16 calcHighDamage( CChar *p )
|
|
//| Date - 11. Mar, 2006
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Calculate high damage value of the char
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
SI16 CHandleCombat::calcHighDamage( CChar *p )
|
|
{
|
|
if( !ValidateObject( p ) )
|
|
return 0;
|
|
|
|
SI16 getDamage = 0;
|
|
|
|
CItem *weapon = getWeapon( p );
|
|
if( ValidateObject( weapon ) )
|
|
{
|
|
if( weapon->GetLoDamage() > 0 && weapon->GetHiDamage() > 0 )
|
|
{
|
|
if( weapon->GetLoDamage() >= weapon->GetHiDamage() )
|
|
getDamage = weapon->GetLoDamage();
|
|
else
|
|
getDamage = weapon->GetHiDamage();
|
|
|
|
}
|
|
}
|
|
else if( p->IsNpc() )
|
|
{
|
|
if( p->GetLoDamage() >= p->GetHiDamage() )
|
|
getDamage = p->GetLoDamage();
|
|
else if( p->GetHiDamage() > 2 )
|
|
getDamage = p->GetHiDamage();
|
|
}
|
|
else
|
|
{
|
|
UI16 getWrestSkill = ( p->GetSkill( WRESTLING ) / 65 );
|
|
if( getWrestSkill > 0 )
|
|
getDamage = getWrestSkill;
|
|
else
|
|
getDamage = 2;
|
|
}
|
|
if( getDamage < 1 )
|
|
getDamage = 1;
|
|
return getDamage;
|
|
}
|
|
|
|
/*
|
|
New Defense Calculations
|
|
Date: 3/03/2003
|
|
|
|
Purpose:
|
|
To emulate OSI's armor calculation methods for specific hit locations and total AR level
|
|
|
|
How we do this:
|
|
We need to first determine what we are calculating, total AR (hitLoc 0) or the armor level of a specific locations (hitLoc 1-6).
|
|
Next we need to loop through the items a player has equipped searching for items that match the following criteria:
|
|
Defence > 0
|
|
On a specific layer (depending on the hit location)
|
|
The item with the most AR covering that location
|
|
Lastly we have to return this information. If we are returning the AR of a specific hit location, then we return the actual Def value of the item
|
|
if we are returning the total AR of a character, we need to find the AR / the divisor used for that specific location.
|
|
|
|
Divisors based upon location / Armor (as best I can match it to OSI)
|
|
Torso (hitLoc 1): AR / 2.8
|
|
Arms (hitLoc 2): AR / 6.8
|
|
Head (hitLoc 3): AR / 7.3
|
|
Legs (hitLoc 4): AR / 4.5
|
|
Neck (hitLoc 5): AR / 14.5
|
|
Hands (hitLoc 6): AR / 14.5
|
|
|
|
Functions:
|
|
checkDef( CItem *checkItem, CItem& currItem, SI32 &currDef )
|
|
getArmorDef( CChar *mChar, CItem& defItem, UI08 bodyLoc, bool findTotal )
|
|
calcDef( CChar *mChar, UI08 hitLoc, bool doDamage )
|
|
|
|
All information on armor defence values, divisors, and combat calculations gleaned from uo.stratics.com
|
|
http://uo.stratics.com/content/arms-armor/armor.shtml
|
|
http://uo.stratics.com/content/arms-armor/combat.shtml
|
|
http://uo.stratics.com/content/arms-armor/clothes.shtml
|
|
http://uo.stratics.com/content/aos/combatchanges.shtml
|
|
*/
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - CItem *checkDef( CItem *checkItem, CItem *currItem, SI32 &currDef, WeatherType resistType )
|
|
//| Date - 3/03/2003
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Checks the defense of checkItem vs the defense of currItem and returns
|
|
//| the item with the greater Def and its def value
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
CItem *CHandleCombat::checkDef( CItem *checkItem, CItem *currItem, SI32 &currDef, WeatherType resistType )
|
|
{
|
|
if( ValidateObject( checkItem ) && checkItem->GetResist( resistType ) > currDef )
|
|
{
|
|
currDef = checkItem->GetResist( resistType );
|
|
return checkItem;
|
|
}
|
|
return currItem;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - CItem * getArmorDef( CChar *mChar, SI32 &totalDef, UI08 bodyLoc, bool findTotal, WeatherType resistType )
|
|
//| Date - 3/03/2003
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Finds the item covering the location bodyLoc with the greatest AR and
|
|
//| returns it along with its def value
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
CItem * CHandleCombat::getArmorDef( CChar *mChar, SI32 &totalDef, UI08 bodyLoc, bool findTotal, WeatherType resistType )
|
|
{
|
|
SI32 armorDef = 0;
|
|
CItem *currItem = nullptr;
|
|
switch( bodyLoc )
|
|
{
|
|
case 1: // Torso
|
|
currItem = checkDef( mChar->GetItemAtLayer( IL_INNERSHIRT ), currItem, armorDef, resistType ); // Shirt
|
|
currItem = checkDef( mChar->GetItemAtLayer( IL_TUNIC ), currItem, armorDef, resistType ); // Torso (Inner - Chest Armor)
|
|
currItem = checkDef( mChar->GetItemAtLayer( IL_OUTERSHIRT ), currItem, armorDef, resistType ); // Torso (Middle - Tunic, etc)
|
|
currItem = checkDef( mChar->GetItemAtLayer( IL_CLOAK ), currItem, armorDef, resistType ); // Back (Cloak)
|
|
currItem = checkDef( mChar->GetItemAtLayer( IL_ROBE ), currItem, armorDef, resistType ); // Torso (Outer - Robe)
|
|
if( findTotal )
|
|
armorDef = (SI32)(100 * (R32)( armorDef / 2.8 ));
|
|
break;
|
|
case 2: // Arms
|
|
currItem = checkDef( mChar->GetItemAtLayer( IL_ARMS ), currItem, armorDef, resistType ); // Arms
|
|
if( findTotal )
|
|
armorDef = (SI32)(100 * (R32)( armorDef / 6.8 ));
|
|
break;
|
|
case 3: // Head
|
|
currItem = checkDef( mChar->GetItemAtLayer( IL_HELM ), currItem, armorDef, resistType ); // Head
|
|
if( findTotal )
|
|
armorDef = (SI32)(100 * (R32)( armorDef / 7.3 ));
|
|
break;
|
|
case 4: // Legs
|
|
currItem = checkDef( mChar->GetItemAtLayer( IL_FOOTWEAR ), currItem, armorDef, resistType ); // Shoes
|
|
currItem = checkDef( mChar->GetItemAtLayer( IL_PANTS ), currItem, armorDef, resistType ); // Pants
|
|
currItem = checkDef( mChar->GetItemAtLayer( IL_WAIST ), currItem, armorDef, resistType ); // Waist (Half Apron)
|
|
currItem = checkDef( mChar->GetItemAtLayer( IL_OUTERLEGGINGS ), currItem, armorDef, resistType ); // Legs (Outer - Skirt, Kilt)
|
|
currItem = checkDef( mChar->GetItemAtLayer( IL_INNERLEGGINGS ), currItem, armorDef, resistType ); // Legs (Inner - Leg Armor)
|
|
if( findTotal )
|
|
armorDef = (SI32)(100 * (R32)( armorDef / 4.5 ));
|
|
break;
|
|
case 5: // Neck
|
|
currItem = checkDef( mChar->GetItemAtLayer( IL_NECK ), currItem, armorDef, resistType ); // Neck
|
|
if( findTotal )
|
|
armorDef = (SI32)(100 * (R32)( armorDef / 14.5 ));
|
|
break;
|
|
case 6: // Hands
|
|
currItem = checkDef( mChar->GetItemAtLayer( IL_GLOVES ), currItem, armorDef, resistType ); // Gloves
|
|
if( findTotal )
|
|
armorDef = (SI32)(100 * (R32)( armorDef / 14.5 ));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if( findTotal )
|
|
totalDef += armorDef;
|
|
else
|
|
totalDef = armorDef;
|
|
|
|
return currItem;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - UI16 calcDef( CChar *mChar, UI08 hitLoc, bool doDamage, WeatherType resistType )
|
|
//| Date - 3/03/2003
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Finds the defense value of a specific location or the entire character based on hitLoc
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
UI16 CHandleCombat::calcDef( CChar *mChar, UI08 hitLoc, bool doDamage, WeatherType resistType )
|
|
{
|
|
if( !ValidateObject( mChar ) )
|
|
return 0;
|
|
|
|
SI32 total = mChar->GetResist( resistType );
|
|
|
|
if( !mChar->IsNpc() || cwmWorldState->creatures[mChar->GetID()].IsHuman() ) // Polymorphed Characters and GM's can still wear armor
|
|
{
|
|
CItem *defendItem = nullptr;
|
|
if( hitLoc == 0 )
|
|
{
|
|
for( UI08 getLoc = 1; getLoc < 7; ++getLoc )
|
|
getArmorDef( mChar, total, getLoc, true, resistType );
|
|
total = (total / 100);
|
|
}
|
|
else
|
|
defendItem = getArmorDef( mChar, total, hitLoc, false, resistType );
|
|
|
|
// Deal damage to armor on hit, if enabled
|
|
if( total > 0 && doDamage && ValidateObject( defendItem ) && !mChar->IsNpc() && ( cwmWorldState->ServerData()->CombatArmorDamageChance() >= RandomNum( 1, 100 )))
|
|
{
|
|
SI08 armorDamage = 0; // Based on OSI standards, each successful hit does 0 to 2 damage to armor hit
|
|
UI08 armorDamageMin = 0;
|
|
UI08 armorDamageMax = 0;
|
|
|
|
armorDamageMin = cwmWorldState->ServerData()->CombatArmorDamageMin();
|
|
armorDamageMax = cwmWorldState->ServerData()->CombatArmorDamageMax();
|
|
|
|
armorDamage -= static_cast<std::uint8_t>(RandomNum( static_cast<std::uint16_t>(armorDamageMin), static_cast<std::uint16_t>(armorDamageMax )));
|
|
defendItem->IncHP( armorDamage );
|
|
|
|
if( defendItem->GetHP() <= 0 )
|
|
{
|
|
CSocket *mSock = mChar->GetSocket();
|
|
if( mSock != nullptr )
|
|
{
|
|
std::string name;
|
|
getTileName( (*defendItem), name );
|
|
mSock->sysmessage( 311, name.c_str() );
|
|
}
|
|
defendItem->Delete();
|
|
}
|
|
}
|
|
}
|
|
|
|
//if( total < 2 && hitLoc != 0 )
|
|
// total = 2;
|
|
return (UI16)total;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void CombatAnimsNew( CChar *i )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Play combat animations for characters (including gargoyles) in clients v7.0.0.0+
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CHandleCombat::CombatAnimsNew( CChar *i )
|
|
{
|
|
if( !ValidateObject( i ) )
|
|
return;
|
|
|
|
UI16 animToPlay = N_ACT_ATT; // 0x00
|
|
UI16 subAnimToPlay = S_ACT_WRESTLE; // 0x00
|
|
CItem *j = getWeapon( i );
|
|
|
|
if( i->IsFlying() || i->IsOnHorse() )
|
|
{
|
|
switch( getWeaponType( j ) )
|
|
{
|
|
case BOWS:
|
|
case XBOWS:
|
|
case DEF_SWORDS:
|
|
case SLASH_SWORDS:
|
|
case ONEHND_LG_SWORDS:
|
|
case ONEHND_AXES:
|
|
case DEF_MACES:
|
|
case DEF_FENCING:
|
|
case WRESTLING:
|
|
case BLOWGUNS: subAnimToPlay = S_ACT_2H_SLASH; break; // 0x07
|
|
case DUAL_SWORD:
|
|
case DUAL_FENCING_SLASH:
|
|
case DUAL_FENCING_STAB:
|
|
case TWOHND_FENCING:
|
|
case TWOHND_LG_SWORDS:
|
|
case BARDICHE:
|
|
case TWOHND_AXES:
|
|
case LG_MACES:
|
|
case THROWN:
|
|
default: subAnimToPlay = S_ACT_WRESTLE; break; // 0x00
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch( getWeaponType( j ) )
|
|
{
|
|
case BOWS: subAnimToPlay = S_ACT_BOW; break; // 0x01
|
|
case XBOWS:
|
|
case BLOWGUNS: subAnimToPlay = S_ACT_XBOW; break; // 0x02
|
|
case THROWN: subAnimToPlay = S_ACT_THROW;break; // 0x09
|
|
case DEF_SWORDS:
|
|
case SLASH_SWORDS:
|
|
case ONEHND_LG_SWORDS:
|
|
subAnimToPlay = ( RandomNum( 0, 2 ) > 1 ? (RandomNum( 0, 3 ) > 2 ? S_ACT_1H_PIERCE : S_ACT_1H_BASH ) : S_ACT_1H_SLASH ); // 0x05, 0x03 and 0x04
|
|
break;
|
|
case ONEHND_AXES:
|
|
case DEF_MACES: subAnimToPlay = ( RandomNum( 0, 3 ) > 2 ? S_ACT_1H_SLASH : S_ACT_1H_BASH ); break; // 0x04 and 0x03
|
|
case DUAL_FENCING_STAB:
|
|
case DEF_FENCING: subAnimToPlay = ( RandomNum( 0, 3 ) > 2 ? S_ACT_1H_SLASH : S_ACT_1H_PIERCE ); break; // 0x04 and 0x05
|
|
case TWOHND_FENCING: subAnimToPlay = S_ACT_2H_PIERCE; break; // 0x08
|
|
case TWOHND_LG_SWORDS:
|
|
case DUAL_SWORD:
|
|
case DUAL_FENCING_SLASH:
|
|
case BARDICHE:
|
|
subAnimToPlay = ( RandomNum( 0, 2 ) > 1 ? (RandomNum( 0, 3 ) > 2 ? S_ACT_2H_PIERCE : S_ACT_2H_BASH ) : S_ACT_2H_SLASH ); // 0x08, 0x06 and 0x07
|
|
break;
|
|
case TWOHND_AXES:
|
|
case LG_MACES: subAnimToPlay = ( RandomNum( 0, 3 ) > 2 ? S_ACT_2H_BASH : S_ACT_2H_SLASH ); break; // 0x06 and 0x07
|
|
case WRESTLING:
|
|
default: subAnimToPlay = S_ACT_WRESTLE; break; // 0x00
|
|
}
|
|
}
|
|
Effects->PlayNewCharacterAnimation( i, animToPlay, subAnimToPlay );
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void CombatOnHorse( CChar *i )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Play combat animations for mounted characters in clients v6.x and below
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CHandleCombat::CombatOnHorse( CChar *i )
|
|
{
|
|
if( !ValidateObject( i ) )
|
|
return;
|
|
|
|
UI16 animToPlay = ACT_MOUNT_ATT_1H; // 0x1A;
|
|
CItem *j = getWeapon( i );
|
|
UI08 frameCount = 5;
|
|
|
|
// Normal mounted characters
|
|
switch( getWeaponType( j ) )
|
|
{
|
|
case BOWS: animToPlay = ACT_MOUNT_ATT_BOW; break; // 0x1B
|
|
case XBOWS:
|
|
case BLOWGUNS:
|
|
animToPlay = ACT_MOUNT_ATT_XBOW;
|
|
frameCount = 7;
|
|
break; // 0x1C
|
|
case THROWN:
|
|
case DEF_SWORDS:
|
|
case SLASH_SWORDS:
|
|
case DUAL_SWORD:
|
|
case ONEHND_LG_SWORDS:
|
|
case ONEHND_AXES:
|
|
case DEF_MACES:
|
|
case DUAL_FENCING_SLASH:
|
|
case DUAL_FENCING_STAB:
|
|
case DEF_FENCING: animToPlay = ACT_MOUNT_ATT_1H; break; // 0x1A
|
|
case TWOHND_FENCING:
|
|
case TWOHND_LG_SWORDS:
|
|
case BARDICHE:
|
|
case TWOHND_AXES:
|
|
case LG_MACES: animToPlay = ACT_MOUNT_ATT_2H; break; // 0x1D
|
|
case WRESTLING:
|
|
default: animToPlay = ACT_MOUNT_ATT_1H; break; // 0x1A
|
|
}
|
|
Effects->PlayCharacterAnimation( i, animToPlay, 0, frameCount );
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void CombatOnFoot( CChar *i )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Play combat animations for characters on foot in clients v6.x and below
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CHandleCombat::CombatOnFoot( CChar *i )
|
|
{
|
|
if( !ValidateObject( i ) )
|
|
return;
|
|
|
|
UI16 animToPlay = 0x00;
|
|
CItem *j = getWeapon( i );
|
|
switch( getWeaponType( j ) )
|
|
{
|
|
case BOWS: animToPlay = ACT_ATT_BOW; break; // 0x12
|
|
case XBOWS:
|
|
case BLOWGUNS: animToPlay = ACT_ATT_XBOW; break; // 0x13
|
|
case DEF_SWORDS:
|
|
case SLASH_SWORDS:
|
|
case ONEHND_LG_SWORDS:
|
|
case DUAL_SWORD:
|
|
case DUAL_FENCING_SLASH:
|
|
case ONEHND_AXES: animToPlay = (RandomNum( 0, 2 ) < 1 ? ACT_ATT_1H_SLASH : ACT_ATT_1H_PIERCE ); break; // 0x09 and 0x0A
|
|
case DEF_MACES: animToPlay = (RandomNum( 0, 2 ) < 2 ? ACT_ATT_1H_SLASH : ACT_ATT_1H_BASH ); break; // 0x09 and 0x0B
|
|
case LG_MACES:
|
|
case TWOHND_LG_SWORDS:
|
|
case BARDICHE:
|
|
case TWOHND_AXES: animToPlay = (RandomNum( 0, 1 ) ? ACT_ATT_2H_BASH : ACT_ATT_2H_SLASH ); break; // 0x0C and 0x0D
|
|
case DUAL_FENCING_STAB:
|
|
case DEF_FENCING: animToPlay = ACT_ATT_1H_PIERCE; break; // 0x0A
|
|
case TWOHND_FENCING: animToPlay = ACT_ATT_2H_PIERCE; break; // 0x0E
|
|
case WRESTLING:
|
|
default:
|
|
switch( RandomNum( 0, 2 ) )
|
|
{
|
|
case 0: animToPlay = ACT_ATT_1H_BASH; break; // 0x0B
|
|
case 1: animToPlay = ACT_ATT_1H_SLASH; break; // 0x09
|
|
default: animToPlay = ACT_ATT_WRESTLE; break; // 0x1F
|
|
}
|
|
break;
|
|
}
|
|
Effects->PlayCharacterAnimation( i, animToPlay, 0, 7 );
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void PlaySwingAnimations( CChar *mChar )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Plays the attack/swing-animation for specified character
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CHandleCombat::PlaySwingAnimations( CChar *mChar )
|
|
{
|
|
UI16 charID = mChar->GetID();
|
|
if( !cwmWorldState->creatures[charID].IsHuman() )
|
|
{
|
|
UI08 aa = 0;
|
|
if( cwmWorldState->creatures[charID].AntiBlink() )
|
|
aa = RandomNum( 5, 6 ); // some creatures dont have animation #4, or it's not an attack
|
|
else
|
|
aa = RandomNum( 4, 6 );
|
|
if( charID == 5 ) // eagles need special treatment
|
|
{
|
|
switch( RandomNum( 0, 2 ) )
|
|
{
|
|
case 0: aa = 0x1; break;
|
|
case 1: aa = 0x14; break;
|
|
case 2: aa = 0x4; break;
|
|
}
|
|
}
|
|
Effects->PlayCharacterAnimation( mChar, aa, 0, 4 ); // Creature attack anims are only 4 frames long
|
|
if( RandomNum( 0, 4 ) )
|
|
{
|
|
UI16 toPlay = cwmWorldState->creatures[charID].GetSound( SND_ATTACK );
|
|
if( toPlay != 0x00 )
|
|
Effects->PlaySound( mChar, toPlay );
|
|
}
|
|
}
|
|
else if( mChar->GetBodyType() == BT_GARGOYLE
|
|
|| ( cwmWorldState->ServerData()->ForceNewAnimationPacket() && ( mChar->GetSocket() == nullptr || mChar->GetSocket()->ClientType() >= CV_SA2D ))) //mChar->GetSocket()->ClientVerShort() >= CVS_7000 )))
|
|
CombatAnimsNew( mChar );
|
|
else if( mChar->IsOnHorse() )
|
|
CombatOnHorse( mChar );
|
|
else
|
|
CombatOnFoot( mChar );
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void PlayMissedSoundEffect( CChar *p )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Do the "Missed" Sound Effect
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CHandleCombat::PlayMissedSoundEffect( CChar *p )
|
|
{
|
|
CItem *weapon = getWeapon( p );
|
|
|
|
if( !ValidateObject( p ) )
|
|
return;
|
|
|
|
switch( getWeaponType( weapon ) )
|
|
{
|
|
case BOWS:
|
|
case XBOWS: Effects->PlaySound( p, RandomNum( 0x04c8, 0x04c9 )); break;
|
|
case BLOWGUNS: Effects->PlaySound( p, 0x052F ); break;
|
|
default:
|
|
switch( RandomNum( 0, 2 ) )
|
|
{
|
|
case 0:
|
|
Effects->PlaySound( p, 0x0238 );
|
|
break;
|
|
case 1:
|
|
Effects->PlaySound( p, 0x0239 );
|
|
break;
|
|
default:
|
|
Effects->PlaySound( p, 0x023A );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void PlayHitSoundEffect( CChar *p, CItem *weapon )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Do the "Hit" Sound Effect
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CHandleCombat::PlayHitSoundEffect( CChar *p, CItem *weapon )
|
|
{
|
|
if( !ValidateObject( p ) || !ValidateObject( weapon ) )
|
|
return;
|
|
|
|
switch( getWeaponType( weapon ) )
|
|
{
|
|
case ONEHND_AXES:
|
|
case TWOHND_AXES:
|
|
case DEF_MACES:
|
|
case LG_MACES:
|
|
Effects->PlaySound( p, RandomNum( 0x0232, 0x0233 )); // Whoosh Weapons
|
|
break;
|
|
case DEF_SWORDS:
|
|
case DEF_FENCING:
|
|
case TWOHND_FENCING:
|
|
case DUAL_FENCING_STAB:
|
|
Effects->PlaySound( p, RandomNum( 0x023B, 0x023C )); // Stabbing Weapons
|
|
break;
|
|
case BARDICHE:
|
|
Effects->PlaySound( p, RandomNum( 0x0236, 0x0237 )); // Bardiche
|
|
break;
|
|
case SLASH_SWORDS:
|
|
case DUAL_SWORD:
|
|
case DUAL_FENCING_SLASH:
|
|
case ONEHND_LG_SWORDS:
|
|
Effects->PlaySound( p, RandomNum( 0x023B, 0x023C )); // Slashing Weapons
|
|
break;
|
|
case TWOHND_LG_SWORDS:
|
|
Effects->PlaySound( p, RandomNum( 0x0236, 0x0237 )); // Large Swords
|
|
break;
|
|
case BOWS:
|
|
case XBOWS:
|
|
Effects->PlaySound( p, 0x0234 ); // Bows
|
|
break;
|
|
case BLOWGUNS:
|
|
Effects->PlaySound( p, RandomNum( 0x0223, 0x0224 )); //Darts
|
|
break;
|
|
case WRESTLING:
|
|
default:
|
|
switch( RandomNum( 0, 3 ) ) // Wrestling
|
|
{
|
|
case 0: Effects->PlaySound( p, 0x0135 ); break;
|
|
case 1: Effects->PlaySound( p, 0x0137 ); break;
|
|
case 2: Effects->PlaySound( p, 0x013D ); break;
|
|
default: Effects->PlaySound( p, 0x013B ); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - SI16 AdjustRaceDamage( CChar *attack, CChar *defend, CItem *weapon, SI16 bDamage, UI08 hitLoc, UI08 getFightSkill )
|
|
//| Date - 3rd July, 2001
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Adjusts the damage dealt to defend by weapon based on
|
|
//| race and weather weaknesses
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
SI16 CHandleCombat::AdjustRaceDamage( CChar *attack, CChar *defend, CItem *weapon, SI16 bDamage, UI08 hitLoc, UI08 getFightSkill )
|
|
{
|
|
SI16 amount = 0;
|
|
|
|
if( !ValidateObject( defend ) || !ValidateObject( weapon ) )
|
|
return bDamage;
|
|
|
|
if( weapon->GetRace() == defend->GetRace() )
|
|
amount = bDamage;
|
|
CRace *rPtr = Races->Race( defend->GetRace() );
|
|
if( rPtr != nullptr )
|
|
{
|
|
for( SI32 i = LIGHT; i < WEATHNUM; ++i )
|
|
{
|
|
if( weapon->GetWeatherDamage( (WeatherType)i ) && rPtr->AffectedBy( (WeatherType)i ) )
|
|
{
|
|
amount += ApplyDefenseModifiers( (WeatherType)i, attack, defend, getFightSkill, hitLoc, bDamage, false );
|
|
}
|
|
}
|
|
}
|
|
return (bDamage + amount);
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - SI16 AdjustArmorClassDamage( CChar *attack, CChar *defend, CItem *weapon, SI16 bDamage, UI08 hitLoc )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Adjusts the damage dealt to defender based on potential armour class weakness
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
SI16 CHandleCombat::AdjustArmorClassDamage( CChar *attacker, CChar *defender, CItem *attackerWeapon, SI16 baseDamage, UI08 hitLoc )
|
|
{
|
|
SI32 bonusDamage = 0;
|
|
|
|
if( !ValidateObject( defender ) || !ValidateObject( attackerWeapon ) )
|
|
return baseDamage;
|
|
|
|
CItem *defenderItem = NULL;
|
|
if( hitLoc != 0 )
|
|
{
|
|
// If hit location system is enabled, check armor class of item at specified hit location
|
|
defenderItem = getArmorDef( attacker, bonusDamage, hitLoc, false, PHYSICAL );
|
|
|
|
if( ValidateObject( defenderItem ) && attackerWeapon->GetArmourClass() == defenderItem->GetArmourClass() )
|
|
{
|
|
// Add bonus damage equal to half of the armor's physical resist/armor rating
|
|
bonusDamage = bonusDamage / 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Hit location system not enabled. Rely on average AC/physical resist instead
|
|
SI32 bonusPotential = 0;
|
|
for( UI08 getLoc = 1; getLoc < 7; ++getLoc )
|
|
{
|
|
defenderItem = getArmorDef( attacker, bonusPotential, getLoc, false, PHYSICAL );
|
|
|
|
if( ValidateObject( defenderItem ) && attackerWeapon->GetArmourClass() == defenderItem->GetArmourClass() )
|
|
{
|
|
bonusDamage += bonusPotential;
|
|
}
|
|
}
|
|
|
|
bonusDamage /= 7; // get averaage of all 7 armor locations
|
|
bonusDamage /= 2; // then split damage in half.
|
|
}
|
|
|
|
return (baseDamage + static_cast<SI16>(bonusDamage));
|
|
}
|
|
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void DoHitMessage( CChar *mChar, CChar *ourTarg, SI08 hitLoc, SI16 damage )
|
|
//| Date - 3rd July, 2001
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Prints out the hit message, if enabled
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CHandleCombat::DoHitMessage( CChar *mChar, CChar *ourTarg, SI08 hitLoc, SI16 damage )
|
|
{
|
|
if( !ValidateObject( ourTarg ) || !ValidateObject( mChar ) || ourTarg->IsNpc() )
|
|
return;
|
|
|
|
CSocket *targSock = ourTarg->GetSocket();
|
|
|
|
if( cwmWorldState->ServerData()->CombatDisplayHitMessage() && targSock != nullptr )
|
|
{
|
|
std::string attackerName = getNpcDictName( mChar, targSock );
|
|
|
|
UI08 randHit = RandomNum( 0, 2 );
|
|
switch( hitLoc )
|
|
{
|
|
case 1: // Body
|
|
switch ( randHit )
|
|
{
|
|
case 1:
|
|
if( damage < 10 )
|
|
targSock->sysmessage( 284, attackerName.c_str() ); // %s hits you in your Chest!
|
|
else
|
|
targSock->sysmessage( 285, attackerName.c_str() ); // %s lands a terrible blow to your Chest!
|
|
break;
|
|
case 2:
|
|
if( damage < 10 )
|
|
targSock->sysmessage( 286, attackerName.c_str() ); // %s lands a blow to your Stomach!
|
|
else
|
|
targSock->sysmessage( 287, attackerName.c_str() ); // %s knocks the wind out of you!
|
|
break;
|
|
default:
|
|
if( damage < 10 )
|
|
targSock->sysmessage( 288, attackerName.c_str() ); // %s hits you in your Ribs!
|
|
else
|
|
targSock->sysmessage( 289, attackerName.c_str() ); // %s broke your Rib!
|
|
break;
|
|
}
|
|
break;
|
|
case 2: // Arms
|
|
switch( randHit )
|
|
{
|
|
case 1: targSock->sysmessage( 290, attackerName.c_str() ); break; // %s hits you in your Left Arm!
|
|
case 2: targSock->sysmessage( 291, attackerName.c_str() ); break; // %s hits you in your Right Arm!
|
|
default: targSock->sysmessage( 292, attackerName.c_str() ); break; // %s hits you in your Right Arm!
|
|
}
|
|
break;
|
|
case 3: // Head
|
|
switch( randHit )
|
|
{
|
|
case 1:
|
|
if( damage < 10 )
|
|
targSock->sysmessage( 293, attackerName.c_str() ); // %s hits you you straight in the Face!
|
|
else
|
|
targSock->sysmessage( 294, attackerName.c_str() ); // %s lands a stunning blow to your Head!
|
|
break;
|
|
case 2:
|
|
if( damage < 10 )
|
|
targSock->sysmessage( 295, attackerName.c_str() ); // %s hits you to your Head!
|
|
else
|
|
targSock->sysmessage( 296, attackerName.c_str() ); // %s smashed a blow across your Face!
|
|
break;
|
|
default:
|
|
if( damage < 10 )
|
|
targSock->sysmessage( 297, attackerName.c_str() ); // %s hits you you square in the Jaw!
|
|
else
|
|
targSock->sysmessage( 298, attackerName.c_str() ); // %s lands a terrible hit to your Temple!
|
|
break;
|
|
}
|
|
break;
|
|
case 4: // Legs
|
|
switch( randHit )
|
|
{
|
|
case 1: targSock->sysmessage( 299, attackerName.c_str() ); break; // %s hits you in the Left Thigh!
|
|
case 2: targSock->sysmessage( 300, attackerName.c_str() ); break; // %s hits you in the Right Thigh!
|
|
default: targSock->sysmessage( 301, attackerName.c_str() ); break; // %s hits you in the Groin!
|
|
}
|
|
break;
|
|
case 5: // Neck
|
|
targSock->sysmessage( 302, attackerName.c_str() ); // %s hits you to your Throat!
|
|
break;
|
|
case 6: // Hands
|
|
switch( randHit )
|
|
{
|
|
case 1: targSock->sysmessage( 303, attackerName.c_str() ); break; // %s hits you in the Left Hand!
|
|
case 2: targSock->sysmessage( 304, attackerName.c_str() ); break; // s hits you in the Right Hand!
|
|
default: targSock->sysmessage( 305, attackerName.c_str() ); break; // %s hits you in the Right Hand!
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - SI08 CalculateHitLoc( void )
|
|
//| Date - 3rd July, 2001
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - calculates where on the body the person was hit and returns that
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
SI08 CHandleCombat::CalculateHitLoc( void )
|
|
{
|
|
SI08 hitLoc = RandomNum( 0, 99 ); // Determine area of Body Hit
|
|
for( UI08 t = BODYPERCENT; t < TOTALTARGETSPOTS; ++t )
|
|
{
|
|
hitLoc -= LOCPERCENTAGES[t];
|
|
if( hitLoc < 0 )
|
|
{
|
|
hitLoc = t + 1;
|
|
break;
|
|
}
|
|
}
|
|
return hitLoc;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - SI16 ApplyDamageBonuses( WeatherType damageType, CChar *mChar, CChar *ourTarg, UI08 getFightSkill, UI08 hitLoc, SI16 baseDamage )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Applies damage bonuses based on race/weather weakness and character skills
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
SI16 CHandleCombat::ApplyDamageBonuses( WeatherType damageType, CChar *mChar, CChar *ourTarg, UI08 getFightSkill, UI08 hitLoc, SI16 baseDamage )
|
|
{
|
|
if( !ValidateObject( ourTarg ) || !ValidateObject( mChar ) )
|
|
return baseDamage;
|
|
|
|
R32 multiplier = 1;
|
|
R32 damage = 0;
|
|
SI32 RaceDamage = 0;
|
|
CItem *mWeapon = getWeapon( mChar );
|
|
CRace *rPtr = Races->Race( ourTarg->GetRace() );
|
|
|
|
switch( damageType )
|
|
{
|
|
case NONE:
|
|
damage = (R32)baseDamage;
|
|
break;
|
|
case PHYSICAL:
|
|
// Race Dmg Modification: Bonus percentage.
|
|
RaceDamage = Races->DamageFromSkill( getFightSkill, mChar->GetRace() );
|
|
if( RaceDamage != 0 )
|
|
baseDamage += (SI16)( (R32)baseDamage * ( (R32)RaceDamage / 1000 ) );
|
|
|
|
// Adjust race and weather weakness
|
|
baseDamage = AdjustRaceDamage( mChar, ourTarg, mWeapon, baseDamage, hitLoc, getFightSkill );
|
|
|
|
// Adjust for armour class weakness
|
|
if( cwmWorldState->ServerData()->CombatArmorClassDamageBonus() )
|
|
baseDamage = AdjustArmorClassDamage( mChar, ourTarg, mWeapon, baseDamage, hitLoc );
|
|
|
|
// Strength Damage Bonus, +20% Damage
|
|
if( mChar->GetStrength() >= 100 )
|
|
multiplier = static_cast<R32>( mChar->GetStrength() * 0.3) + 5;
|
|
else
|
|
multiplier = static_cast<R32>( mChar->GetStrength() * 0.3);
|
|
|
|
// Tactics Damage Bonus, % = ( Tactics + 50 )
|
|
if( mChar->GetSkill( TACTICS ) >= 1000 )
|
|
multiplier += static_cast<R32>((( mChar->GetSkill( TACTICS ) / 10 ) / 1.6 ) + 6.25 );
|
|
else
|
|
multiplier += static_cast<R32>(( mChar->GetSkill( TACTICS ) / 10 ) / 1.6 );
|
|
|
|
// Anatomy Damage Bonus
|
|
if( mChar->GetSkill( ANATOMY ) >= 1000 )
|
|
multiplier += 30; // 30% Damage at GM Skill or above,
|
|
else
|
|
multiplier += static_cast<R32>( ( ( mChar->GetSkill( ANATOMY ) / 10 ) / 5 ) ); // Up to 20% Damage at 99.9 skillpoints or below
|
|
|
|
// Lumberjacking Bonus ( 30% Damage at GM Skill )
|
|
if( mChar->GetSkill( LUMBERJACKING ) >= 1000 )
|
|
multiplier += 30; // 30% Damage at GM Skill or above,
|
|
else
|
|
multiplier += static_cast<R32>( ( ( mChar->GetSkill( LUMBERJACKING ) / 10 ) / 5 ) ); // up to 20% Damage at 99.9 skillpoints or below
|
|
|
|
// Where does defender tactics damage modifier come from? Which era, if any?
|
|
// Defender Tactics Damage Modifier, -20% Damage
|
|
// multiplier = static_cast<R32>(1.0 - ( ( ( ourTarg->GetSkill( TACTICS ) * 20 ) / 1000 ) / 100 ));
|
|
multiplier /= 100;
|
|
damage = baseDamage + static_cast<R32>(baseDamage * multiplier);
|
|
break;
|
|
default:
|
|
damage = static_cast<R32>(baseDamage);
|
|
|
|
// If the attack is magic and the target a NPC but not a human, double the damage
|
|
if( getFightSkill == MAGERY && ourTarg->IsNpc() && !cwmWorldState->creatures[ourTarg->GetID()].IsHuman() )
|
|
damage *= 2;
|
|
break;
|
|
}
|
|
|
|
// If the race is weak to this element double the damage
|
|
if( rPtr != nullptr )
|
|
{
|
|
if( rPtr->AffectedBy( damageType ) )
|
|
damage *= 2;
|
|
}
|
|
return static_cast<SI16>( roundNumber( damage ));
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - SI16 ApplyDefenseModifiers( WeatherType damageType, CChar *mChar, CChar *ourTarg, UI08 getFightSkill, UI08 hitLoc, SI16 baseDamage, bool doArmorDamage )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Applies defense modifiers based on shields/parrying, armor values and elemental damage
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
SI16 CHandleCombat::ApplyDefenseModifiers( WeatherType damageType, CChar *mChar, CChar *ourTarg, UI08 getFightSkill, UI08 hitLoc, SI16 baseDamage, bool doArmorDamage )
|
|
{
|
|
if( !ValidateObject( ourTarg ) )
|
|
return baseDamage;
|
|
|
|
UI16 getDef = 0, attSkill = 1000;
|
|
R32 damageModifier = 0;
|
|
R32 damage = (R32)baseDamage;
|
|
|
|
if( ValidateObject( mChar ) )
|
|
attSkill = mChar->GetSkill( getFightSkill );
|
|
|
|
CSocket *targSock = ourTarg->GetSocket();
|
|
CItem *shield = getShield( ourTarg );
|
|
|
|
switch( damageType )
|
|
{
|
|
case NONE: break; // No Armor protection
|
|
case PHYSICAL: // Physical damage
|
|
{
|
|
// Check Shield Defense
|
|
if( ValidateObject( shield ) )
|
|
{
|
|
Skills->CheckSkill( ourTarg, PARRYING, 0, 1000 );
|
|
|
|
// Renaissance Publish (UOR) - https://uo.com/wiki/ultima-online-wiki/technical/previous-publishes/2000-2/2000-publish-05-27th-april/
|
|
// The higher a shield's AR, the lower the chance to block, but the more damage is absorbed upon blocking
|
|
// The lower a shield's AR, the higher the chance to block, but the less damage is absorbed upon blocking
|
|
// parryChance = parrySkill - (shield AR * 2)
|
|
const UI16 defendParry = ourTarg->GetSkill( PARRYING );
|
|
|
|
R32 parryChance = (defendParry / 10 ) - ( shield->GetResist( PHYSICAL ) * 2 ); // or is it 1.33?
|
|
if( RandomNum( 0, 100 ) < parryChance )
|
|
{
|
|
// Play shield parrying FX
|
|
Effects->PlayStaticAnimation( ourTarg, 0x37b9, 10, 16 );
|
|
|
|
auto loShieldDamage = cwmWorldState->ServerData()->CombatParryDamageMin();
|
|
auto hiShieldDamage = cwmWorldState->ServerData()->CombatParryDamageMax();
|
|
SI16 shieldDamage = -(RandomNum( static_cast<SI16>(loShieldDamage), static_cast<SI16>(hiShieldDamage) ));
|
|
|
|
if( cwmWorldState->ServerData()->CombatDisplayHitMessage() )
|
|
{
|
|
if( targSock != nullptr )
|
|
targSock->sysmessage( 1805 ); // You block the attack!
|
|
else if( ValidateObject( mChar ) && mChar->GetSocket() != nullptr )
|
|
mChar->GetSocket()->sysmessage( 2060 ); // Your attack was blocked!
|
|
}
|
|
|
|
// Pre-AoS/Pub15/UOR
|
|
// FORMULA: Melee Damage Absorbed = ( AR of Shield ) / 2 | Archery Damage Absorbed = AR of Shield
|
|
if( getFightSkill == ARCHERY )
|
|
damage -= static_cast<R32>( shield->GetResist( PHYSICAL ) );
|
|
else
|
|
damage -= static_cast<R32>( shield->GetResist( PHYSICAL ) / 2 );
|
|
|
|
// Calculate defense given by armor
|
|
getDef = HalfRandomNum( calcDef( ourTarg, hitLoc, doArmorDamage, PHYSICAL ) );
|
|
|
|
// Apply damage to shield from parrying action?
|
|
if( cwmWorldState->ServerData()->CombatParryDamageChance() >= RandomNum( 0, 100 ) ) // 20% chance by default
|
|
shield->IncHP( shieldDamage );
|
|
|
|
if( shield->GetHP() <= 0 )
|
|
{
|
|
if( targSock != nullptr )
|
|
targSock->sysmessage( 283 ); // Your shield has been destroyed!
|
|
shield->Delete();
|
|
}
|
|
}
|
|
}
|
|
|
|
// No shield, no weapon parry, no wrestling parry - armor needs to take the brunt of damage!
|
|
if( damage > 0 && getDef == 0 )
|
|
{
|
|
getDef = HalfRandomNum( calcDef( ourTarg, hitLoc, doArmorDamage, PHYSICAL ) );
|
|
}
|
|
break;
|
|
}
|
|
case POISON: // POISON Damage
|
|
damageModifier = ( calcDef( ourTarg, hitLoc, doArmorDamage, damageType ) / 100 );
|
|
damage = (SI16)roundNumber( ((R32)baseDamage - ( (R32)baseDamage * damageModifier )) );
|
|
break;
|
|
default: // Elemental damage
|
|
getDef = HalfRandomNum( calcDef( ourTarg, hitLoc, doArmorDamage, damageType ) );
|
|
break;
|
|
}
|
|
|
|
if( getDef > 0 )
|
|
damage -= (R32)( ( (R32)getDef * (R32)attSkill ) / 750 );
|
|
|
|
return (SI16)roundNumber( damage );
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - SI16 calcDamage( CChar *mChar, CChar *ourTarg, UI08 getFightSkill, UI08 hitLoc )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Calculate damage based on hit location, damage bonuses, defense modifiers
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
SI16 CHandleCombat::calcDamage( CChar *mChar, CChar *ourTarg, UI08 getFightSkill, UI08 hitLoc )
|
|
{
|
|
SI16 damage = -1;
|
|
|
|
std::vector<UI16> scriptTriggers = mChar->GetScriptTriggers();
|
|
for( auto scriptTrig : scriptTriggers )
|
|
{
|
|
cScript *toExecute = JSMapping->GetScript( scriptTrig );
|
|
if( toExecute != nullptr )
|
|
{
|
|
// -1 == event doesn't exist, default to hard code
|
|
// Other value = calculated damage from script
|
|
damage = toExecute->OnCombatDamageCalc( mChar, ourTarg, getFightSkill, hitLoc );
|
|
}
|
|
}
|
|
|
|
if( damage >= 0 )
|
|
return damage;
|
|
else
|
|
damage = 0;
|
|
|
|
const SI16 baseDamage = calcAtt( mChar, true );
|
|
|
|
if( baseDamage == -1 ) // No damage if weapon breaks
|
|
return 0;
|
|
|
|
damage = ApplyDamageBonuses( PHYSICAL, mChar, ourTarg, getFightSkill, hitLoc, baseDamage );
|
|
|
|
if( damage < 1 )
|
|
return 0;
|
|
|
|
damage = ApplyDefenseModifiers( PHYSICAL, mChar, ourTarg, getFightSkill, hitLoc, damage, true );
|
|
|
|
if( damage <= 0 )
|
|
damage = RandomNum( 0, 4 );
|
|
|
|
// Half remaining damage by 2 if PUB15 or earlier
|
|
damage /= 2;
|
|
|
|
if( !ourTarg->IsNpc() )
|
|
damage /= cwmWorldState->ServerData()->CombatNPCDamageRate(); // Rate damage against other players
|
|
|
|
return damage;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void HandleCombat( CSocket *mSock, CChar& mChar, CChar *ourTarg )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Handles combat related stuff during combat loop
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CHandleCombat::HandleCombat( CSocket *mSock, CChar& mChar, CChar *ourTarg )
|
|
{
|
|
const UI16 ourDist = getDist( &mChar, ourTarg );
|
|
//Attacker Skill values
|
|
CItem *mWeapon = getWeapon( &mChar );
|
|
const UI08 getFightSkill = getCombatSkill( mWeapon );
|
|
const UI16 attackSkill = std::min( 1000, (SI32)mChar.GetSkill( getFightSkill ) );
|
|
|
|
//Defender Skill values
|
|
CItem *defWeapon = getWeapon( ourTarg );
|
|
const UI08 getTargetSkill = getCombatSkill( defWeapon );
|
|
const UI16 defendSkill = std::min( 1000, (SI32)ourTarg->GetSkill( getTargetSkill ) );
|
|
|
|
bool checkDist = (ourDist <= 1 && abs( mChar.GetZ() - ourTarg->GetZ() ) <= 15 );
|
|
|
|
// Trigger onSwing for scripts attached to attacker
|
|
std::vector<UI16> scriptTriggers = mChar.GetScriptTriggers();
|
|
for( auto scriptTrig : scriptTriggers )
|
|
{
|
|
cScript *toExecute = JSMapping->GetScript( scriptTrig );
|
|
if( toExecute != nullptr )
|
|
{
|
|
if( toExecute->OnSwing( mWeapon, &mChar, ourTarg ) == 0 )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Trigger onSwing for scripts attached to attacker's weapon
|
|
if( mWeapon )
|
|
{
|
|
std::vector<UI16> weaponScriptTriggers = mWeapon->GetScriptTriggers();
|
|
for( auto scriptTrig : weaponScriptTriggers )
|
|
{
|
|
cScript *toExecute = JSMapping->GetScript( scriptTrig );
|
|
if( toExecute != nullptr )
|
|
{
|
|
if( toExecute->OnSwing( mWeapon, &mChar, ourTarg ) == 0 )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !checkDist && getFightSkill == ARCHERY )
|
|
checkDist = LineOfSight( mSock, &mChar, ourTarg->GetX(), ourTarg->GetY(), ( ourTarg->GetZ() + 15 ), WALLS_CHIMNEYS + DOORS + FLOORS_FLAT_ROOFING, false );
|
|
|
|
if( checkDist )
|
|
{
|
|
CPFightOccurring tSend( mChar, (*ourTarg) );
|
|
SOCKLIST nearbyChars = FindNearbyPlayers( &mChar );
|
|
for( SOCKLIST_ITERATOR cIter = nearbyChars.begin(); cIter != nearbyChars.end(); ++cIter )
|
|
{
|
|
if( (*cIter) != nullptr )
|
|
(*cIter)->Send( &tSend );
|
|
}
|
|
|
|
if( mChar.IsNpc() )
|
|
{
|
|
if( mChar.GetNpcWander() != WT_FLEE )
|
|
{
|
|
UI08 charDir = Movement->Direction( &mChar, ourTarg->GetX(), ourTarg->GetY() );
|
|
if( mChar.GetDir() != charDir && charDir < 8 )
|
|
mChar.SetDir( charDir );
|
|
}
|
|
}
|
|
|
|
if( getFightSkill == ARCHERY && mWeapon != nullptr )
|
|
{
|
|
// If amount of time since character last moved is less than the minimum delay for shooting after coming to a halt, return
|
|
if(( cwmWorldState->GetUICurrentTime() - mChar.LastMoveTime() ) < static_cast<UI32>( cwmWorldState->ServerData()->CombatArcheryShootDelay() * 10 ))
|
|
return;
|
|
|
|
UI16 ammoID = mWeapon->GetAmmoID();
|
|
UI16 ammoHue = mWeapon->GetAmmoHue();
|
|
UI16 ammoFX = mWeapon->GetAmmoFX();
|
|
UI16 ammoFXHue = mWeapon->GetAmmoFXHue();
|
|
UI16 ammoFXRender = mWeapon->GetAmmoFXRender();
|
|
|
|
if( mChar.IsNpc() || ( ammoID != 0 && DeleteItemAmount( &mChar, 1, ammoID, ammoHue ) == 1 ))
|
|
{
|
|
PlaySwingAnimations( &mChar );
|
|
Effects->PlayMovingAnimation( mChar.GetX(), mChar.GetY(), mChar.GetZ() + 5, ourTarg->GetX(), ourTarg->GetY(), ourTarg->GetZ(), ammoFX, 0x08, 0x00, 0x00, static_cast<UI32>( ammoFXHue ), static_cast<UI32>( ammoFXRender ));
|
|
}
|
|
else
|
|
{
|
|
if( mSock != nullptr )
|
|
mSock->sysmessage( 309 );
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
PlaySwingAnimations( &mChar );
|
|
|
|
SI16 staminaToLose = cwmWorldState->ServerData()->CombatAttackStamina();
|
|
if( staminaToLose && ( !mChar.IsGM() && !mChar.IsCounselor() ) )
|
|
mChar.IncStamina( staminaToLose );
|
|
|
|
const UI16 getDefTactics = ourTarg->GetSkill( TACTICS );
|
|
bool skillPassed = false;
|
|
|
|
// Do a skill check so the fight skill is increased
|
|
Skills->CheckSkill( &mChar, getFightSkill, 0, std::min( 1000, (SI32)((getDefTactics * 1.25) + 100) ) );
|
|
|
|
R32 hitChance = ( ( ( (R32)attackSkill + 500.0 ) / ( ( (R32)defendSkill + 500.0 ) * 2.0) ) * 100.0 );
|
|
if( hitChance < 0 )
|
|
hitChance = 0;
|
|
else if( hitChance > 100 )
|
|
hitChance = 100;
|
|
|
|
// Bonus to hit chance for archery skill since Pub 5/UOR
|
|
if( getFightSkill == ARCHERY )
|
|
{
|
|
hitChance += cwmWorldState->ServerData()->CombatArcheryHitBonus();
|
|
}
|
|
|
|
skillPassed = ( RandomNum( 0, 100 ) <= hitChance );
|
|
|
|
if( !skillPassed )
|
|
{
|
|
// It's a miss!
|
|
if( getFightSkill == ARCHERY && mWeapon->GetAmmoID() != 0 && !RandomNum( 0, 2 ) )
|
|
{
|
|
UI16 ammoID = mWeapon->GetAmmoID();
|
|
UI16 ammoHue = mWeapon->GetAmmoHue();
|
|
Items->CreateItem( nullptr, ourTarg, ammoID, 1, ammoHue, OT_ITEM, false );
|
|
}
|
|
|
|
PlayMissedSoundEffect( &mChar );
|
|
}
|
|
else
|
|
{
|
|
// It's a hit!
|
|
CSocket *targSock = ourTarg->GetSocket();
|
|
|
|
Skills->CheckSkill( &mChar, TACTICS, 0, 1000 );
|
|
Skills->CheckSkill( ourTarg, TACTICS, 0, 1000 );
|
|
|
|
switch( ourTarg->GetID() )
|
|
{
|
|
case 0x025E: // elf/human/garg female
|
|
case 0x0191:
|
|
case 0x029B: Effects->PlaySound( ourTarg, RandomNum( 0x014B, 0x014F )); break;
|
|
case 0x025D: // elf/human/garg male
|
|
case 0x0190:
|
|
case 0x029A: Effects->PlaySound( ourTarg, RandomNum( 0x0155, 0x0158 )); break;
|
|
default:
|
|
{
|
|
UI16 toPlay = cwmWorldState->creatures[ourTarg->GetID()].GetSound( SND_DEFEND );
|
|
if( toPlay != 0x00 )
|
|
Effects->PlaySound( ourTarg, toPlay );
|
|
break;
|
|
}
|
|
}
|
|
|
|
UI08 poisonStrength = mChar.GetPoisonStrength();
|
|
if( poisonStrength && ourTarg->GetPoisoned() < poisonStrength )
|
|
{
|
|
if( (( getFightSkill == FENCING || getFightSkill == SWORDSMANSHIP ) && !RandomNum( 0, 2 )) || mChar.IsNpc() )
|
|
{
|
|
// Apply poison on target
|
|
ourTarg->SetPoisoned( poisonStrength );
|
|
|
|
// Set time until next time poison "ticks"
|
|
ourTarg->SetTimer( tCHAR_POISONTIME, BuildTimeValue(static_cast<R32>(getPoisonTickTime( poisonStrength ))));
|
|
|
|
// Set time until poison wears off completely
|
|
ourTarg->SetTimer( tCHAR_POISONWEAROFF, ourTarg->GetTimer( tCHAR_POISONTIME ) + ( 1000 * getPoisonDuration( poisonStrength ))); //wear off starts after poison takes effect
|
|
|
|
|
|
if( targSock != nullptr )
|
|
targSock->sysmessage( 282 );
|
|
}
|
|
}
|
|
|
|
// Calculate damage
|
|
const UI08 hitLoc = CalculateHitLoc();
|
|
UI16 ourDamage = calcDamage( &mChar, ourTarg, getFightSkill, hitLoc );
|
|
if( ourDamage > 0 )
|
|
{
|
|
// Show hit messages, if enabled
|
|
DoHitMessage( &mChar, ourTarg, hitLoc, ourDamage );
|
|
|
|
// Interrupt Spellcasting
|
|
if( !ourTarg->IsNpc() && targSock != nullptr )
|
|
{
|
|
if( ourTarg->IsCasting() && targSock->CurrentSpellType() == 0 )
|
|
{
|
|
ourTarg->StopSpell();
|
|
ourTarg->SetFrozen( false );
|
|
targSock->sysmessage( 306 );
|
|
}
|
|
}
|
|
// Reactive Armor
|
|
if( ourTarg->GetReactiveArmour() )
|
|
{
|
|
SI32 retDamage = (SI32)( ourDamage * ( ourTarg->GetSkill( MAGERY ) / 2000.0 ) );
|
|
ourTarg->Damage( ourDamage - retDamage, PHYSICAL, &mChar );
|
|
if( ourTarg->IsNpc() )
|
|
retDamage *= cwmWorldState->ServerData()->CombatNPCDamageRate();
|
|
mChar.Damage( retDamage, PHYSICAL, &mChar );
|
|
Effects->PlayStaticAnimation( ourTarg, 0x374A, 0, 15 );
|
|
}
|
|
else
|
|
{
|
|
ourTarg->Damage( ourDamage, PHYSICAL, &mChar, true );
|
|
}
|
|
}
|
|
if( cwmWorldState->creatures[mChar.GetID()].IsHuman() )
|
|
PlayHitSoundEffect( &mChar, mWeapon );
|
|
|
|
for( auto scriptTrig : scriptTriggers )
|
|
{
|
|
cScript *toExecute = JSMapping->GetScript( scriptTrig );
|
|
if( toExecute != nullptr )
|
|
{
|
|
toExecute->OnAttack( &mChar, ourTarg );
|
|
}
|
|
}
|
|
|
|
std::vector<UI16> defScriptTriggers = ourTarg->GetScriptTriggers();
|
|
for( auto scriptTrig : defScriptTriggers )
|
|
{
|
|
cScript *toExecute = JSMapping->GetScript( scriptTrig );
|
|
if( toExecute != nullptr )
|
|
{
|
|
toExecute->OnDefense( &mChar, ourTarg );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - inline void QuickSwitch( CChar *mChar, CChar *ourTarg, SI08 spellNum )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Allows beneficial spells to be cast on self during combat
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
inline void QuickSwitch( CChar *mChar, CChar *ourTarg, SI08 spellNum )
|
|
{
|
|
if( !ValidateObject( mChar ) || !ValidateObject( ourTarg ) || mChar == ourTarg )
|
|
return;
|
|
|
|
mChar->SetSpellCast( spellNum );
|
|
mChar->SetTarg( mChar );
|
|
Magic->CastSpell( nullptr, mChar );
|
|
mChar->StopSpell();
|
|
mChar->SetTarg( ourTarg );
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - bool CastSpell( CChar *mChar, CChar *ourTarg, SI08 spellNum )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Handles spellcasting during combat
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
bool CHandleCombat::CastSpell( CChar *mChar, CChar *ourTarg, SI08 spellNum )
|
|
{
|
|
if( !ValidateObject( mChar ) || !ValidateObject( ourTarg ) || mChar == ourTarg )
|
|
return false;
|
|
|
|
if( mChar->GetMana() <= Magic->spells[spellNum].Mana() )
|
|
return false;
|
|
switch( spellNum )
|
|
{
|
|
case 7:
|
|
if( !mChar->GetReactiveArmour() )
|
|
mChar->SetSpellCast( spellNum );
|
|
else
|
|
return false;
|
|
break;
|
|
case 11:
|
|
if( mChar->GetPoisoned() > 0 )
|
|
QuickSwitch( mChar, ourTarg, spellNum );
|
|
else
|
|
return false;
|
|
break;
|
|
case 29:
|
|
if( mChar->GetMaxHP() > mChar->GetHP() )
|
|
QuickSwitch( mChar, ourTarg, spellNum );
|
|
else
|
|
return false;
|
|
break;
|
|
case 36:
|
|
if( !mChar->IsTempReflected() && !mChar->IsPermReflected() )
|
|
mChar->SetSpellCast( spellNum );
|
|
else
|
|
return false;
|
|
break;
|
|
case 1:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
case 12:
|
|
case 18:
|
|
case 20:
|
|
case 27:
|
|
case 30:
|
|
case 31:
|
|
case 33:
|
|
case 37:
|
|
case 38:
|
|
case 41:
|
|
case 42:
|
|
case 43:
|
|
case 46:
|
|
case 49:
|
|
case 51:
|
|
case 53:
|
|
case 55:
|
|
case 57:
|
|
case 58:
|
|
mChar->SetSpellCast( spellNum );
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void HandleNPCSpellAttack( CChar *npcAttack, CChar *cDefend, UI16 playerDistance )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Handles the casting of spells by NPCs during combat
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CHandleCombat::HandleNPCSpellAttack( CChar *npcAttack, CChar *cDefend, UI16 playerDistance )
|
|
{
|
|
if( !npcAttack->GetCanAttack() || npcAttack->IsEvading() )
|
|
return;
|
|
|
|
if( npcAttack->GetTimer( tNPC_SPATIMER ) <= cwmWorldState->GetUICurrentTime() )
|
|
{
|
|
if( playerDistance < cwmWorldState->ServerData()->CombatMaxSpellRange() )
|
|
{
|
|
SI16 spattacks = npcAttack->GetSpAttack();
|
|
if( spattacks <= 0 )
|
|
return;
|
|
if( spattacks > 8 )
|
|
spattacks = 8;
|
|
|
|
npcAttack->StopSpell();
|
|
|
|
UI16 offensiveWeight = 0;
|
|
|
|
// Establish a simple baseline for letting NPC determine whether to cast offensive or defensive spells
|
|
// The higher the NPC's current health percentage, the more likely they will be to cast offensive spells
|
|
// The lower their health percentage, the more likely they will be to cast defensive spells
|
|
offensiveWeight = static_cast<UI16>(std::round(( npcAttack->GetHP() * 100 ) / npcAttack->GetMaxHP() ));
|
|
|
|
UI16 rndNum = RandomNum( 1, 100 );
|
|
|
|
// Cut weighting towards offensive spells in half if NPC is poisoned; we want him to cast cures!
|
|
if( npcAttack->GetPoisoned() > 0 )
|
|
offensiveWeight /= 2;
|
|
|
|
if( offensiveWeight > rndNum )
|
|
{
|
|
// Cast offensive spells
|
|
switch(( RandomNum( static_cast<SI16>(0), spattacks ) + 1 ))
|
|
{
|
|
case 1: // Spell Circle 1 - Offensive Spells
|
|
switch( RandomNum( 1, 4 ) )
|
|
{
|
|
case 1: CastSpell( npcAttack, cDefend, 1 ); break; // Clumsy
|
|
case 2: CastSpell( npcAttack, cDefend, 3 ); break; // Feeble Mind
|
|
case 3: CastSpell( npcAttack, cDefend, 5 ); break; // Magic Arrow
|
|
case 4: CastSpell( npcAttack, cDefend, 8 ); break; // weaken
|
|
}
|
|
break;
|
|
case 2:
|
|
CastSpell( npcAttack, cDefend, 12 ); // Harm
|
|
break;
|
|
case 3:
|
|
CastSpell( npcAttack, cDefend, 18 ); // Fireball
|
|
break;
|
|
case 4:
|
|
CastSpell( npcAttack, cDefend, 30 ); // Lightning
|
|
break;
|
|
case 5:
|
|
switch( RandomNum( 1, 2 ) )
|
|
{
|
|
case 1: CastSpell( npcAttack, cDefend, 37 ); break; // Mind Blast
|
|
case 2: CastSpell( npcAttack, cDefend, 38 ); break; // Paralyze
|
|
//case 3: CastSpell( npcAttack, cDefend, 33 ); break; // Blade Spirits
|
|
}
|
|
break;
|
|
case 6:
|
|
if( cDefend->IsNpc() )
|
|
{
|
|
if( cDefend->GetTimer( tNPC_SUMMONTIME ) > 0 && cDefend->IsDispellable() && cDefend->GetNPCAiType() != AI_GUARD )
|
|
{
|
|
if( npcAttack->GetSkill( MAGERY ) > RandomNum( 1, 1000 ) )
|
|
{
|
|
CastSpell( npcAttack, cDefend, 41 );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
switch( RandomNum( 1, 2 ) )
|
|
{
|
|
case 1: CastSpell( npcAttack, cDefend, 42 ); break; // Energy Bolt
|
|
case 2: CastSpell( npcAttack, cDefend, 43 ); break; // Explosion
|
|
}
|
|
break;
|
|
case 7:
|
|
switch( RandomNum( 1, 3 ) )
|
|
{
|
|
case 1: CastSpell( npcAttack, cDefend, 49 ); break; // Chain Lightning
|
|
case 2: CastSpell( npcAttack, cDefend, 51 ); break; // Flamestrike
|
|
case 3: CastSpell( npcAttack, cDefend, 55 ); break; // Meteor Swarm
|
|
}
|
|
break;
|
|
case 8:
|
|
CastSpell( npcAttack, cDefend, 57 ); // Earthquake
|
|
// case 2: break; //CastSpell( npcAttack, cDefend, 58 ); break; // Energy Vortex
|
|
// This is where dragon attacks go eventually when the NPC DFNs are fixed...*/
|
|
/*
|
|
case 9:
|
|
case 10:
|
|
case 11:
|
|
*/
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If situation not too bad yet, let's go for some buffs/debuffs
|
|
if( offensiveWeight > rndNum / 1.5 )
|
|
{
|
|
// Cast buffs/debuffs
|
|
switch(( RandomNum( static_cast<SI16>(0), spattacks ) + 1 ))
|
|
{
|
|
case 1:
|
|
[[fallthrough]];
|
|
case 2:
|
|
switch( RandomNum( 1, 4 ) )
|
|
{
|
|
case 1: CastSpell( npcAttack, cDefend, 1 ); break; // Clumsy
|
|
case 2: CastSpell( npcAttack, cDefend, 3 ); break; // Feeble Mind
|
|
case 3: CastSpell( npcAttack, cDefend, 7 ); break; // Reactive Armor (personal...switch target temporarily to self and then back after casting)
|
|
case 4: CastSpell( npcAttack, cDefend, 8 ); break; // weaken
|
|
}
|
|
break;
|
|
case 3:
|
|
if( cDefend->GetPoisoned() == 0 )
|
|
{
|
|
CastSpell( npcAttack, cDefend, 20 ); // Poison
|
|
break;
|
|
}
|
|
if( spattacks + 1 == 3 )
|
|
break;
|
|
[[fallthrough]]; // Fallthrough if target is poisoned already and if NPC can cast spells from next circle
|
|
case 4:
|
|
switch( RandomNum( 1, 2 ) )
|
|
{
|
|
case 1: CastSpell( npcAttack, cDefend, 27 ); break; // Curse
|
|
case 2: CastSpell( npcAttack, cDefend, 31 ); break; // Mana Drain
|
|
}
|
|
break;
|
|
case 5:
|
|
CastSpell( npcAttack, cDefend, 36 ); // Magic Reflection (personal...no target switching required)
|
|
break;
|
|
case 6:
|
|
CastSpell( npcAttack, cDefend, 46 ); // Mass Curse
|
|
break;
|
|
case 7:
|
|
if( spattacks + 1 == 7 )
|
|
break; // Don't fallthrough if NPC cannot cast spells from next circle!
|
|
[[fallthrough]];
|
|
case 8:
|
|
CastSpell( npcAttack, cDefend, 53 ); // Mana Vampire
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Critical health levels! Let's focus on heals and cures
|
|
switch(( RandomNum( static_cast<SI16>(0), spattacks ) + 1 ))
|
|
{
|
|
case 1:
|
|
if(( spattacks + 1 >= 2 ) && !CastSpell( npcAttack, cDefend, 11 )) // Cast cure if we're poisoned, else move on to Heal
|
|
CastSpell( npcAttack, cDefend, 4 ); // Heal (personal...switch target temporarily to self and then back after casting)
|
|
break;
|
|
case 2:
|
|
[[fallthrough]];
|
|
case 3:
|
|
if( !CastSpell( npcAttack, cDefend, 11 )) // Cast cure if we're poisoned, else move on to Heal
|
|
CastSpell( npcAttack, cDefend, 4 ); // Heal (personal...switch target temporarily to self and then back after casting)
|
|
break;
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
case 8:
|
|
CastSpell( npcAttack, cDefend, 29 ); // Greater Healing (personal...switch target temporarily to self and then back after casting)
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( npcAttack->GetSpellCast() != -1 )
|
|
{
|
|
if( npcAttack->GetBodyType() == BT_HUMAN || npcAttack->GetBodyType() == BT_ELF || npcAttack->GetBodyType() == BT_GARGOYLE )
|
|
{
|
|
SpellInfo curSpellCasting = Magic->spells[npcAttack->GetSpellCast()];
|
|
Effects->PlaySpellCastingAnimation( npcAttack, curSpellCasting.Action() ); // do the action
|
|
npcAttack->SetTimer( tNPC_MOVETIME, BuildTimeValue( 0.75 ) );
|
|
}
|
|
Magic->CastSpell( nullptr, npcAttack );
|
|
}
|
|
|
|
//Adjust spellDelay based on UOX.INI setting:
|
|
SI08 spellDelay = floor( ( npcAttack->GetSpDelay() / cwmWorldState->ServerData()->NPCSpellCastSpeed() ) + 0.5 );
|
|
|
|
npcAttack->SetTimer( tNPC_SPATIMER, BuildTimeValue( spellDelay ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - R32 GetCombatTimeout( CChar *mChar )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Calculate delay between attacks in combat
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
R32 CHandleCombat::GetCombatTimeout( CChar *mChar )
|
|
{
|
|
SI16 statOffset = 0;
|
|
if( cwmWorldState->ServerData()->CombatAttackSpeedFromStamina() )
|
|
statOffset = mChar->GetStamina();
|
|
else
|
|
statOffset = mChar->GetDexterity();
|
|
|
|
R32 getDelay = 0;
|
|
if( mChar->IsNpc() ) // Allow NPCs more speed based off of available stamina than players
|
|
getDelay = (R32)( (R32)std::min( statOffset, static_cast<SI16>(300) ) + 100 );
|
|
else
|
|
getDelay = (R32)( (R32)std::min( statOffset, static_cast<SI16>(100) ) + 100 );
|
|
|
|
SI32 getOffset = 0;
|
|
SI32 baseValue = 15000;
|
|
|
|
CChar *ourTarg = mChar->GetTarg();
|
|
|
|
CItem *mWeapon = getWeapon( mChar );
|
|
if( ValidateObject( mWeapon ) )
|
|
{
|
|
if( mWeapon->GetSpeed() == 0 )
|
|
mWeapon->SetSpeed( 35 );
|
|
getOffset = mWeapon->GetSpeed();
|
|
}
|
|
else
|
|
{
|
|
if( mChar->GetSkill( WRESTLING ) < 800 )
|
|
getOffset = (((SI32)(mChar->GetSkill( WRESTLING ) / 200 )) * 5) + 30;
|
|
else
|
|
getOffset = 50;
|
|
}
|
|
|
|
//Allow faster strikes on fleeing targets
|
|
if( ValidateObject(ourTarg) )
|
|
if( ourTarg->GetNpcWander() == WT_FLEE)
|
|
baseValue = 10000;
|
|
|
|
R32 globalAttackSpeed = cwmWorldState->ServerData()->GlobalAttackSpeed(); //Defaults to 1.0
|
|
|
|
getDelay = (baseValue / (getDelay * getOffset)) / globalAttackSpeed;
|
|
return getDelay;
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void InvalidateAttacker( CChar *mChar )
|
|
//| Date - 3rd July, 2001
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Resets the attacker attack so that it cancels attack setup.
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CHandleCombat::InvalidateAttacker( CChar *mChar )
|
|
{
|
|
CChar *ourTarg = mChar->GetTarg();
|
|
|
|
// Check if OnCombatEnd event exists.
|
|
std::vector<UI16> scriptTriggers = mChar->GetScriptTriggers();
|
|
for( auto scriptTrig : scriptTriggers )
|
|
{
|
|
cScript *toExecute = JSMapping->GetScript( scriptTrig );
|
|
if( toExecute != nullptr )
|
|
{
|
|
//Check if ourTarg validates as another character. If not, don't use
|
|
if( !ValidateObject( ourTarg ))
|
|
ourTarg = nullptr;
|
|
|
|
// -1 == event doesn't exist, or returned -1
|
|
// 0 == script returned false, 0, or nothing - don't execute hard code
|
|
// 1 == script returned true or 1
|
|
if( toExecute->OnCombatEnd( mChar, ourTarg ) == 0 ) // if it exists and we don't want hard code, return
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( mChar->IsNpc() && mChar->GetNPCAiType() == AI_GUARD )
|
|
{
|
|
mChar->SetTimer( tNPC_SUMMONTIME, BuildTimeValue( 20 ) );
|
|
mChar->SetNpcWander( WT_FREE );
|
|
if( mChar->GetMounted() )
|
|
mChar->SetTimer( tNPC_MOVETIME, BuildTimeValue( mChar->GetMountedWalkingSpeed() ) );
|
|
else
|
|
mChar->SetTimer( tNPC_MOVETIME, BuildTimeValue( mChar->GetWalkingSpeed() ) );
|
|
mChar->TextMessage( nullptr, 281, TALK, false );
|
|
}
|
|
|
|
if( ValidateObject( ourTarg ) && ourTarg->GetTarg() == mChar )
|
|
{
|
|
ourTarg->SetTarg( nullptr );
|
|
ourTarg->SetAttacker( nullptr );
|
|
ourTarg->SetAttackFirst( false );
|
|
if( ourTarg->IsAtWar() )
|
|
ourTarg->ToggleCombat();
|
|
}
|
|
mChar->SetTarg( nullptr );
|
|
CChar *attAttacker = mChar->GetAttacker();
|
|
if( ValidateObject( attAttacker ) )
|
|
{
|
|
attAttacker->SetAttackFirst( false );
|
|
attAttacker->SetAttacker( nullptr );
|
|
}
|
|
mChar->SetAttacker( nullptr );
|
|
mChar->SetAttackFirst( false );
|
|
if( mChar->IsAtWar() && mChar->IsNpc() )
|
|
mChar->ToggleCombat();
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void Kill( CChar *mChar, CChar *ourTarg )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Handle death during combat
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CHandleCombat::Kill( CChar *mChar, CChar *ourTarg )
|
|
{
|
|
if( ValidateObject( mChar ) )
|
|
{
|
|
if( mChar->GetNPCAiType() == AI_GUARD && ourTarg->IsNpc() )
|
|
{
|
|
Effects->PlayCharacterAnimation( ourTarg, ( RandomNum( 0, 1 ) ? ACT_DIE_BACKWARD : ACT_DIE_FORWARD ), 0, 6 ); // 0x15 or 0x16, either is 6 frames long
|
|
Effects->playDeathSound( ourTarg );
|
|
|
|
ourTarg->Delete(); // NPC was killed by a Guard, don't leave a corpse behind
|
|
if( mChar->IsAtWar() )
|
|
mChar->ToggleCombat();
|
|
return;
|
|
}
|
|
|
|
if( mChar->GetNPCAiType() == AI_ANIMAL && !mChar->IsTamed() )
|
|
{
|
|
mChar->SetHunger( 6 );
|
|
|
|
if( ourTarg->IsNpc() )
|
|
{
|
|
// Note: What if ourTarg is a non-human NPC which doesn't have death animation 0x15?
|
|
Effects->PlayCharacterAnimation( ourTarg, 0x15 );
|
|
Effects->playDeathSound( ourTarg );
|
|
|
|
ourTarg->Delete(); // eating animals, don't give body
|
|
if( mChar->IsAtWar() )
|
|
mChar->ToggleCombat();
|
|
return;
|
|
}
|
|
}
|
|
InvalidateAttacker( mChar );
|
|
}
|
|
HandleDeath( ourTarg, mChar );
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void CombatLoop( CSocket *mSock, CChar& mChar )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Handles the main combat loop for characters
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CHandleCombat::CombatLoop( CSocket *mSock, CChar& mChar )
|
|
{
|
|
CChar *ourTarg = mChar.GetTarg();
|
|
if( ourTarg == nullptr )
|
|
return;
|
|
|
|
if( mChar.IsNpc() && mChar.GetTimer( tNPC_EVADETIME ) > cwmWorldState->GetUICurrentTime() )
|
|
{
|
|
#if defined( UOX_DEBUG_MODE ) && defined( DEBUG_COMBAT )
|
|
Console.warning( "DEBUG: Exited CombatLoop for NPC due to EvadeTimer.\n" );
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
if( mChar.GetTimer( tCHAR_TIMEOUT ) <= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() )
|
|
{
|
|
bool validTarg = false;
|
|
if( !mChar.IsDead() && ValidateObject( ourTarg ) && !ourTarg->isFree() && ( ourTarg->IsNpc() || isOnline( (*ourTarg) ) ) )
|
|
{
|
|
if( !ourTarg->IsInvulnerable() && !ourTarg->IsDead() && ourTarg->GetNPCAiType() != AI_PLAYERVENDOR && ourTarg->GetVisible() == VT_VISIBLE )
|
|
{
|
|
if( charInRange( &mChar, ourTarg ) )
|
|
{
|
|
CItem *equippedWeapon = getWeapon( &mChar );
|
|
if( getCombatSkill( equippedWeapon ) == ARCHERY || getCombatSkill( equippedWeapon ) == THROWING )
|
|
{
|
|
if( getDist( &mChar, ourTarg ) > equippedWeapon->GetMaxRange() )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
validTarg = true;
|
|
if( mChar.IsNpc() && mChar.GetSpAttack() > 0 && mChar.GetMana() > 0 && !RandomNum( 0, 2 ) )
|
|
HandleNPCSpellAttack( &mChar, ourTarg, getDist( &mChar, ourTarg ) );
|
|
else
|
|
HandleCombat( mSock, mChar, ourTarg );
|
|
|
|
if( !ValidateObject( ourTarg->GetTarg() ) || !objInRange( ourTarg, ourTarg->GetTarg(), DIST_INRANGE ) ) //if the defender is swung at, and they don't have a target already, set this as their target
|
|
StartAttack( ourTarg, &mChar );
|
|
}
|
|
else if( mChar.IsNpc() && mChar.GetNPCAiType() == AI_GUARD && mChar.GetRegion()->IsGuarded() && cwmWorldState->ServerData()->GuardsStatus() )
|
|
{
|
|
validTarg = true;
|
|
mChar.SetLocation( ourTarg );
|
|
Effects->PlaySound( &mChar, 0x01FE );
|
|
Effects->PlayStaticAnimation( &mChar, 0x372A, 0x09, 0x06 );
|
|
mChar.TextMessage( nullptr, 1616, TALK, true );
|
|
}
|
|
else
|
|
InvalidateAttacker( &mChar );
|
|
}
|
|
}
|
|
if( !validTarg )
|
|
{
|
|
mChar.SetTarg( nullptr );
|
|
mChar.SetAttacker( nullptr );
|
|
mChar.SetAttackFirst( false );
|
|
if( mChar.IsAtWar() )
|
|
mChar.ToggleCombat();
|
|
}
|
|
else
|
|
mChar.SetTimer( tCHAR_TIMEOUT, BuildTimeValue( GetCombatTimeout( &mChar ) ) );
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void SpawnGuard( CChar *mChar, CChar *targChar, SI16 x, SI16 y, SI08 z )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Handle spawning a guard in guarded areas
|
|
//| NEED TO REWORK FOR REGIONAL GUARD STUFF
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CHandleCombat::SpawnGuard( CChar *mChar, CChar *targChar, SI16 x, SI16 y, SI08 z )
|
|
{
|
|
if( !ValidateObject( mChar ) || !ValidateObject( targChar ) )
|
|
return;
|
|
|
|
if( targChar->IsDead() || targChar->IsInvulnerable() )
|
|
return;
|
|
|
|
CTownRegion *targRegion = mChar->GetRegion();
|
|
|
|
if( !targRegion->IsGuarded() || !cwmWorldState->ServerData()->GuardsStatus() )
|
|
return;
|
|
|
|
bool reUseGuard = false;
|
|
CChar *getGuard = nullptr;
|
|
CMapRegion *toCheck = MapRegion->GetMapRegion( mChar );
|
|
|
|
if( toCheck != nullptr )
|
|
{
|
|
GenericList< CChar * > *regChars = toCheck->GetCharList();
|
|
regChars->Push();
|
|
for( getGuard = regChars->First(); !regChars->Finished(); getGuard = regChars->Next() )
|
|
{
|
|
if( !ValidateObject( getGuard ) )
|
|
continue;
|
|
|
|
if( !getGuard->IsNpc() || getGuard->GetNPCAiType() != AI_GUARD )
|
|
continue;
|
|
|
|
if( !ValidateObject( getGuard->GetTarg() ) || getGuard->GetTarg() == targChar )
|
|
{
|
|
if( charInRange( getGuard, targChar ) )
|
|
reUseGuard = true;
|
|
}
|
|
else if( getGuard->GetTarg() == targChar )
|
|
{
|
|
regChars->Pop();
|
|
return;
|
|
}
|
|
}
|
|
regChars->Pop();
|
|
}
|
|
// 1/13/2003 - Fix for JSE NocSpawner
|
|
if( !reUseGuard )
|
|
{
|
|
getGuard = targRegion->GetRandomGuard();
|
|
if( ValidateObject( getGuard ) )
|
|
{
|
|
getGuard->SetLocation( x, y, z, mChar->WorldNumber(), mChar->GetInstanceID() );
|
|
Npcs->PostSpawnUpdate( getGuard );
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
//
|
|
if( ValidateObject( getGuard ) )
|
|
{
|
|
getGuard->SetAttackFirst( true );
|
|
getGuard->SetAttacker( targChar );
|
|
getGuard->SetTarg( targChar );
|
|
getGuard->SetNpcWander( WT_FREE );
|
|
if( !getGuard->IsAtWar() )
|
|
getGuard->ToggleCombat();
|
|
|
|
if( reUseGuard )
|
|
getGuard->SetLocation( targChar );
|
|
else
|
|
{
|
|
if( getGuard->GetMounted() )
|
|
getGuard->SetTimer( tNPC_MOVETIME, BuildTimeValue( getGuard->GetMountedWalkingSpeed() ) );
|
|
else
|
|
getGuard->SetTimer( tNPC_MOVETIME, BuildTimeValue( getGuard->GetWalkingSpeed() ) );
|
|
getGuard->SetTimer( tNPC_SUMMONTIME, BuildTimeValue( 25 ) );
|
|
|
|
Effects->PlaySound( getGuard, 0x01FE );
|
|
Effects->PlayStaticAnimation( getGuard, 0x372A, 0x09, 0x06 );
|
|
|
|
getGuard->TextMessage( nullptr, 313, TALK, true );
|
|
}
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void petGuardAttack( CChar *mChar, CChar *owner, CBaseObject *guarded )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Get the pet guarding an item / character and have him attack
|
|
//| the person using / attacking the item / character
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void CHandleCombat::petGuardAttack( CChar *mChar, CChar *owner, CBaseObject *guarded, CChar *petGuard )
|
|
{
|
|
if( !ValidateObject( mChar ) || !ValidateObject( guarded ) )
|
|
return;
|
|
|
|
if( mChar->IsCounselor() || mChar->IsGM() )
|
|
return;
|
|
|
|
if( !ValidateObject( petGuard ))
|
|
{
|
|
// No pet guard was passed into function, so let's look for one ourself
|
|
petGuard = Npcs->getGuardingPet( owner, guarded );
|
|
}
|
|
|
|
if( ValidateObject( petGuard ) && objInRange( mChar, petGuard, cwmWorldState->ServerData()->CombatMaxRange() ) )
|
|
{
|
|
if( mChar->GetSerial() == petGuard->GetOwner() )
|
|
return;
|
|
|
|
if( !Npcs->checkPetFriend( mChar, petGuard ) )
|
|
AttackTarget( petGuard, mChar );
|
|
else
|
|
{
|
|
CSocket *oSock = owner->GetSocket();
|
|
if( oSock != nullptr )
|
|
oSock->sysmessage( 1629 );
|
|
}
|
|
}
|
|
}
|