mirror of
https://github.com/polserver/polserver
synced 2026-08-13 08:23:08 -04:00
* refactored npcmod,decay to use Pos classes get rid of some realm methods used to wrap Pos * all wrappers in Realm are now gone * review comments
1137 lines
37 KiB
C++
1137 lines
37 KiB
C++
/** @file
|
|
*
|
|
* @par History
|
|
* - 2005/06/15 Shinigami: added CanMove - checks if an NPC can move in given direction
|
|
* (IsLegalMove works in a different way and is used for bounding boxes
|
|
* only)
|
|
* - 2006/01/18 Shinigami: set Master first and then start Script in NPC::readNpcProperties
|
|
* - 2006/01/18 Shinigami: added attached_npc_ - to get attached NPC from AI-Script-Process Obj
|
|
* - 2006/09/17 Shinigami: send_event() will return error "Event queue is full, discarding event"
|
|
* - 2009/03/27 MuadDib: NPC::inform_moved() && NPC::inform_imoved()
|
|
* split the left/entered area to fix bug where one would trigger when not
|
|
* enabled.
|
|
* - 2009/07/23 MuadDib: updates for new Enum::Packet Out ID
|
|
* - 2009/08/25 Shinigami: STLport-5.2.1 fix: params not used
|
|
* STLport-5.2.1 fix: init order changed of damaged_sound
|
|
* - 2009/09/15 MuadDib: Cleanup from registered houses on destroy
|
|
* - 2009/09/18 MuadDib: Adding save/load of registered house serial
|
|
* - 2009/09/22 MuadDib: Rewrite for Character/NPC to use ar(), ar_mod(), ar_mod(newvalue)
|
|
* virtuals.
|
|
* - 2009/10/14 Turley: Added char.deaf() methods & char.deafened member
|
|
* - 2009/10/23 Turley: fixed OPPONENT_MOVED,LEFTAREA,ENTEREDAREA
|
|
* - 2009/11/16 Turley: added NpcPropagateEnteredArea()/inform_enteredarea() for event on
|
|
* resurrection
|
|
* - 2010/01/15 Turley: (Tomi) SaveOnExit as npcdesc entry
|
|
*/
|
|
|
|
#include "npc.h"
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "../../bscript/berror.h"
|
|
#include "../../bscript/executor.h"
|
|
#include "../../clib/cfgelem.h"
|
|
#include "../../clib/fileutil.h"
|
|
#include "../../clib/logfacility.h"
|
|
#include "../../clib/passert.h"
|
|
#include "../../clib/random.h"
|
|
#include "../../clib/refptr.h"
|
|
#include "../../clib/streamsaver.h"
|
|
#include "../baseobject.h"
|
|
#include "../dice.h"
|
|
#include "../fnsearch.h"
|
|
#include "../globals/state.h"
|
|
#include "../globals/uvars.h"
|
|
#include "../item/armor.h"
|
|
#include "../item/weapon.h"
|
|
#include "../listenpt.h"
|
|
#include "../module/npcmod.h"
|
|
#include "../module/uomod.h"
|
|
#include "../multi/multi.h"
|
|
#include "../npctmpl.h"
|
|
#include "../scrdef.h"
|
|
#include "../scrsched.h"
|
|
#include "../scrstore.h"
|
|
#include "../syshookscript.h"
|
|
#include "../ufunc.h"
|
|
#include "../uobjcnt.h"
|
|
#include "../uobject.h"
|
|
#include "../uoexec.h"
|
|
#include "../uoscrobj.h"
|
|
#include "../uworld.h"
|
|
#include "attribute.h"
|
|
#include "charactr.h"
|
|
#include "wornitems.h"
|
|
|
|
|
|
/* An area definition is as follows:
|
|
pt: (x,y)
|
|
rect: [pt-pt]
|
|
area: rect,rect,...
|
|
So, format is: [(x1,y1)-(x2,y2)],[],[],...
|
|
Well, right now, the format is x1 y1 x2 y2 ... (ick)
|
|
*/
|
|
namespace Pol
|
|
{
|
|
namespace Mobile
|
|
{
|
|
unsigned short calc_thru_damage( double damage, unsigned short ar );
|
|
|
|
NPC::NPC( u32 objtype, const Clib::ConfigElem& elem )
|
|
: Character( objtype, Core::UOBJ_CLASS::CLASS_NPC ),
|
|
// UOBJECT INTERFACE
|
|
// NPC INTERFACE
|
|
npc_ar_( 0 ),
|
|
// MOVEMENT
|
|
run_speed( dexterity() ),
|
|
anchor(),
|
|
// EVENTS
|
|
// SCRIPT
|
|
script( "" ),
|
|
ex( nullptr ),
|
|
// MISC
|
|
damaged_sound( 0 ),
|
|
template_name(),
|
|
master_( nullptr ),
|
|
template_( Core::find_npc_template( elem ) )
|
|
{
|
|
connected( true );
|
|
logged_in( true );
|
|
use_adjustments( true );
|
|
++Core::stateManager.uobjcount.npc_count;
|
|
}
|
|
|
|
NPC::~NPC()
|
|
{
|
|
stop_scripts();
|
|
--Core::stateManager.uobjcount.npc_count;
|
|
}
|
|
|
|
void NPC::stop_scripts()
|
|
{
|
|
if ( ex != nullptr )
|
|
{
|
|
// this will force the execution engine to stop running this script immediately
|
|
// dont delete the executor here, since it could currently run
|
|
ex->seterror( true );
|
|
ex->revive();
|
|
if ( ex->in_debugger_holdlist() )
|
|
ex->revive_debugged();
|
|
}
|
|
}
|
|
|
|
void NPC::destroy()
|
|
{
|
|
// stop_scripts();
|
|
wornitems->destroy_contents();
|
|
if ( registered_multi > 0 )
|
|
{
|
|
Multi::UMulti* multi = Core::system_find_multi( registered_multi );
|
|
if ( multi != nullptr )
|
|
{
|
|
multi->unregister_object( (UObject*)this );
|
|
}
|
|
registered_multi = 0;
|
|
}
|
|
base::destroy();
|
|
}
|
|
|
|
const char* NPC::classname() const
|
|
{
|
|
return "NPC";
|
|
}
|
|
|
|
bool NPC::anchor_allows_move( Core::UFACING fdir ) const
|
|
{
|
|
auto newpos = pos().move( fdir );
|
|
|
|
if ( anchor.enabled && !warmode() )
|
|
{
|
|
unsigned short curdist = distance_to( anchor.pos );
|
|
unsigned short newdist = newpos.xy().pol_distance( anchor.pos );
|
|
if ( newdist > curdist ) // if we're moving further away, see if we can
|
|
{
|
|
if ( newdist > anchor.dstart )
|
|
{
|
|
int perc = 100 - ( newdist - anchor.dstart ) * anchor.psub;
|
|
if ( perc < 5 )
|
|
perc = 5;
|
|
if ( Clib::random_int( 99 ) > perc )
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool NPC::could_move( Core::UFACING fdir ) const
|
|
{
|
|
short newz;
|
|
Multi::UMulti* supporting_multi;
|
|
Items::Item* walkon_item;
|
|
// Check for diagonal move - use Nandos change from charactr.cpp -- OWHorus (2011-04-26)
|
|
if ( fdir & 1 ) // check if diagonal movement is allowed -- Nando (2009-02-26)
|
|
{
|
|
u8 tmp_facing = ( fdir + 1 ) & 0x7;
|
|
auto tmp_pos = pos().move( static_cast<Core::UFACING>( tmp_facing ) );
|
|
|
|
// needs to save because if only one direction is blocked, it shouldn't block ;)
|
|
short current_boost = gradual_boost;
|
|
bool walk1 = realm()->walkheight( this, tmp_pos.xy(), tmp_pos.z(), &newz, &supporting_multi,
|
|
&walkon_item, ¤t_boost );
|
|
|
|
tmp_facing = ( fdir - 1 ) & 0x7;
|
|
tmp_pos = pos().move( static_cast<Core::UFACING>( tmp_facing ) );
|
|
current_boost = gradual_boost;
|
|
if ( !walk1 && !realm()->walkheight( this, tmp_pos.xy(), tmp_pos.z(), &newz, &supporting_multi,
|
|
&walkon_item, ¤t_boost ) )
|
|
return false;
|
|
}
|
|
auto new_pos = pos().move( fdir );
|
|
short current_boost = gradual_boost;
|
|
return realm()->walkheight( this, new_pos.xy(), new_pos.z(), &newz, &supporting_multi,
|
|
&walkon_item, ¤t_boost ) &&
|
|
!npc_path_blocked( fdir ) && anchor_allows_move( fdir );
|
|
}
|
|
|
|
bool NPC::npc_path_blocked( Core::UFACING fdir ) const
|
|
{
|
|
if ( can_freemove() ||
|
|
( !this->master() && !Core::settingsManager.ssopt.mobiles_block_npc_movement ) )
|
|
return false;
|
|
|
|
auto new_pos = pos().move( fdir );
|
|
|
|
Core::Pos2d gridp = Core::zone_convert( new_pos );
|
|
|
|
if ( Core::settingsManager.ssopt.mobiles_block_npc_movement )
|
|
{
|
|
for ( const auto& chr : realm()->getzone_grid( gridp ).characters )
|
|
{
|
|
// First check if there really is a character blocking
|
|
if ( chr->pos2d() == new_pos.xy() && chr->z() >= z() - 10 && chr->z() <= z() + 10 )
|
|
{
|
|
if ( !chr->dead() && is_visible_to_me( chr, /*check_range*/ false ) )
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
for ( const auto& chr : realm()->getzone_grid( gridp ).npcs )
|
|
{
|
|
// First check if there really is a character blocking
|
|
if ( chr->pos2d() == new_pos.xy() && chr->z() >= z() - 10 && chr->z() <= z() + 10 )
|
|
{
|
|
// Check first with the ssopt false to now allow npcs of same master running on top of
|
|
// each other
|
|
if ( !Core::settingsManager.ssopt.mobiles_block_npc_movement )
|
|
{
|
|
NPC* npc = static_cast<NPC*>( chr );
|
|
if ( npc->master() && this->master() == npc->master() && !npc->dead() &&
|
|
is_visible_to_me( npc, /*check_range*/ false ) )
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if ( !chr->dead() && is_visible_to_me( chr, /*check_range*/ false ) )
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void NPC::printOn( Clib::StreamWriter& sw ) const
|
|
{
|
|
sw.begin( classname(), template_name.get() );
|
|
printProperties( sw );
|
|
sw.end();
|
|
}
|
|
|
|
void NPC::printSelfOn( Clib::StreamWriter& sw ) const
|
|
{
|
|
printOn( sw );
|
|
}
|
|
|
|
void NPC::printProperties( Clib::StreamWriter& sw ) const
|
|
{
|
|
base::printProperties( sw );
|
|
|
|
if ( npc_ar_ )
|
|
sw.add( "AR", npc_ar_ );
|
|
|
|
if ( !script.get().empty() )
|
|
sw.add( "script", script.get() );
|
|
|
|
if ( master_.get() != nullptr )
|
|
sw.add( "master", master_->serial );
|
|
|
|
if ( has_speech_color() )
|
|
sw.add( "SpeechColor", speech_color() );
|
|
|
|
if ( has_speech_font() )
|
|
sw.add( "SpeechFont", speech_font() );
|
|
|
|
if ( run_speed != dexterity() )
|
|
sw.add( "RunSpeed", run_speed );
|
|
|
|
if ( use_adjustments() != true )
|
|
sw.add( "UseAdjustments", use_adjustments() );
|
|
|
|
|
|
if ( has_orig_fire_resist() )
|
|
sw.add( "FireResist", orig_fire_resist() );
|
|
if ( has_orig_cold_resist() )
|
|
sw.add( "ColdResist", orig_cold_resist() );
|
|
if ( has_orig_energy_resist() )
|
|
sw.add( "EnergyResist", orig_energy_resist() );
|
|
if ( has_orig_poison_resist() )
|
|
sw.add( "PoisonResist", orig_poison_resist() );
|
|
if ( has_orig_physical_resist() )
|
|
sw.add( "PhysicalResist", orig_physical_resist() );
|
|
|
|
if ( has_orig_fire_damage() )
|
|
sw.add( "FireDamage", orig_fire_damage() );
|
|
if ( has_orig_cold_damage() )
|
|
sw.add( "ColdDamage", orig_cold_damage() );
|
|
if ( has_orig_energy_damage() )
|
|
sw.add( "EnergyDamage", orig_energy_damage() );
|
|
if ( has_orig_poison_damage() )
|
|
sw.add( "PoisonDamage", orig_poison_damage() );
|
|
if ( has_orig_physical_damage() )
|
|
sw.add( "PhysicalDamage", orig_physical_damage() );
|
|
if ( has_orig_lower_reagent_cost() )
|
|
sw.add( "LowerReagentCost", orig_lower_reagent_cost() );
|
|
if ( has_orig_spell_damage_increase() )
|
|
sw.add( "SpellDamageIncrease", orig_spell_damage_increase() );
|
|
if ( has_orig_faster_casting() )
|
|
sw.add( "FasterCasting", orig_faster_casting() );
|
|
if ( has_orig_faster_cast_recovery() )
|
|
sw.add( "FasterCastRecovery", orig_faster_cast_recovery() );
|
|
if ( has_orig_defence_increase() )
|
|
sw.add( "DefenceIncrease", orig_defence_increase() );
|
|
if ( has_orig_defence_increase_cap() )
|
|
sw.add( "DefenceIncreaseCap", orig_defence_increase_cap() );
|
|
if ( has_orig_lower_mana_cost() )
|
|
sw.add( "LowerManaCost", orig_lower_mana_cost() );
|
|
if ( has_orig_fire_resist_cap() )
|
|
sw.add( "FireResistCap", orig_fire_resist_cap() );
|
|
if ( has_orig_cold_resist_cap() )
|
|
sw.add( "ColdResistCap", orig_cold_resist_cap() );
|
|
if ( has_orig_energy_resist_cap() )
|
|
sw.add( "EnergyResistCap", orig_energy_resist_cap() );
|
|
if ( has_orig_physical_resist_cap() )
|
|
sw.add( "PhysicalResistCap", orig_physical_resist_cap() );
|
|
if ( has_orig_poison_resist_cap() )
|
|
sw.add( "PoisonResistCap", orig_poison_resist_cap() );
|
|
if ( has_orig_luck() )
|
|
sw.add( "Luck", orig_luck() );
|
|
if ( has_swing_speed_increase() )
|
|
sw.add( "SwingSpeedIncrease", orig_swing_speed_increase() );
|
|
if ( no_drop_exception() )
|
|
sw.add( "NoDropException", no_drop_exception() );
|
|
}
|
|
|
|
void NPC::printDebugProperties( Clib::StreamWriter& sw ) const
|
|
{
|
|
base::printDebugProperties( sw );
|
|
sw.comment( "template: {}", template_->name );
|
|
if ( anchor.enabled )
|
|
{
|
|
sw.comment( "anchor: x={} y={} dstart={} psub={}", anchor.pos.x(), anchor.pos.y(),
|
|
anchor.dstart, anchor.psub );
|
|
}
|
|
}
|
|
|
|
void NPC::readNpcProperties( Clib::ConfigElem& elem )
|
|
{
|
|
Items::UWeapon* wpn = static_cast<Items::UWeapon*>(
|
|
Items::find_intrinsic_equipment( elem.rest(), Core::LAYER_HAND1 ) );
|
|
if ( wpn == nullptr )
|
|
wpn = Items::create_intrinsic_weapon_from_npctemplate( elem, template_->pkg );
|
|
if ( wpn != nullptr )
|
|
weapon = wpn;
|
|
|
|
Items::UArmor* sld = static_cast<Items::UArmor*>(
|
|
Items::find_intrinsic_equipment( elem.rest(), Core::LAYER_HAND2 ) );
|
|
if ( sld == nullptr )
|
|
sld = Items::create_intrinsic_shield_from_npctemplate( elem, template_->pkg );
|
|
if ( sld != nullptr )
|
|
shield = sld;
|
|
|
|
// Load the base, equiping items etc will refresh_ar() to update for reals.
|
|
loadEquipablePropertiesNPC( elem );
|
|
|
|
// dave 3/19/3, read templatename only if empty
|
|
if ( template_name.get().empty() )
|
|
{
|
|
template_name = elem.rest();
|
|
|
|
if ( template_name.get().empty() )
|
|
{
|
|
std::string tmp;
|
|
if ( getprop( "template", tmp ) )
|
|
{
|
|
template_name = tmp.c_str() + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
unsigned int master_serial;
|
|
if ( elem.remove_prop( "MASTER", &master_serial ) )
|
|
{
|
|
Character* chr = Core::system_find_mobile( master_serial );
|
|
if ( chr != nullptr )
|
|
master_.set( chr );
|
|
}
|
|
|
|
script = elem.remove_string( "script", "" );
|
|
if ( !script.get().empty() )
|
|
start_script();
|
|
|
|
speech_color( elem.remove_ushort( "SpeechColor", Plib::DEFAULT_TEXT_COLOR ) );
|
|
speech_font( elem.remove_ushort( "SpeechFont", Plib::DEFAULT_TEXT_FONT ) );
|
|
saveonexit( elem.remove_bool( "SaveOnExit", true ) );
|
|
|
|
mob_flags_.change( MOB_FLAGS::USE_ADJUSTMENTS, elem.remove_bool( "UseAdjustments", true ) );
|
|
run_speed = elem.remove_ushort( "RunSpeed", dexterity() );
|
|
|
|
damaged_sound = elem.remove_ushort( "DamagedSound", 0 );
|
|
no_drop_exception( elem.remove_bool( "NoDropException", false ) );
|
|
}
|
|
|
|
void NPC::loadEquipablePropertiesNPC( Clib::ConfigElem& elem )
|
|
{
|
|
// for ar and elemental damage/resist the mod values are loaded before in character code!
|
|
auto diceValue = []( const std::string& dicestr, int* value ) -> bool
|
|
{
|
|
Core::Dice dice;
|
|
std::string errmsg;
|
|
if ( !dice.load( dicestr.c_str(), &errmsg ) )
|
|
*value = atoi( dicestr.c_str() );
|
|
else
|
|
*value = dice.roll();
|
|
return *value != 0;
|
|
};
|
|
auto apply = []( Core::ValueModPack v, int value ) -> Core::ValueModPack
|
|
{ return v.addToValue( static_cast<s16>( value ) ); };
|
|
|
|
std::string tmp;
|
|
int value;
|
|
if ( elem.remove_prop( "AR", &tmp ) && diceValue( tmp, &value ) )
|
|
npc_ar_ = static_cast<u16>( value );
|
|
if ( elem.remove_prop( "LOWERREAGENTCOST", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
lower_reagent_cost( apply( lower_reagent_cost(), value ) );
|
|
orig_lower_reagent_cost( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "SPELLDAMAGEINCREASE", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
spell_damage_increase( apply( spell_damage_increase(), value ) );
|
|
orig_spell_damage_increase( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "FASTERCASTING", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
faster_casting( apply( faster_casting(), value ) );
|
|
orig_faster_casting( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "FASTERCASTRECOVERY", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
faster_cast_recovery( apply( faster_cast_recovery(), value ) );
|
|
orig_faster_cast_recovery( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "DEFENCEINCREASE", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
defence_increase( apply( defence_increase(), value ) );
|
|
orig_defence_increase( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "DEFENCEINCREASECAP", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
defence_increase_cap( apply( defence_increase_cap(), value ) );
|
|
orig_defence_increase_cap( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "LOWERMANACOST", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
lower_mana_cost( apply( lower_mana_cost(), value ) );
|
|
orig_lower_mana_cost( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "HITCHANCE", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
hit_chance( apply( hit_chance(), value ) );
|
|
orig_hit_chance( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "FIRERESISTCAP", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
fire_resist_cap( apply( fire_resist_cap(), value ) );
|
|
orig_fire_resist_cap( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "COLDRESISTCAP", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
cold_resist_cap( apply( cold_resist_cap(), value ) );
|
|
orig_cold_resist_cap( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "ENERGYRESISTCAP", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
energy_resist_cap( apply( energy_resist_cap(), value ) );
|
|
orig_energy_resist_cap( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "PHYSICALRESISTCAP", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
physical_resist_cap( apply( physical_resist_cap(), value ) );
|
|
orig_physical_resist_cap( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "POISONRESISTCAP", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
poison_resist_cap( apply( poison_resist_cap(), value ) );
|
|
orig_poison_resist_cap( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "LUCK", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
luck( apply( luck(), value ) );
|
|
orig_luck( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "SWINGSPEEDINCREASE", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
swing_speed_increase( apply( swing_speed_increase(), value ) );
|
|
orig_swing_speed_increase( static_cast<s16>( value ) );
|
|
}
|
|
|
|
// elemental start
|
|
// first apply template value as value and if mod or value exist sum them
|
|
if ( elem.remove_prop( "FIRERESIST", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
fire_resist( apply( fire_resist(), value ) );
|
|
orig_fire_resist( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "COLDRESIST", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
cold_resist( apply( cold_resist(), value ) );
|
|
orig_cold_resist( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "ENERGYRESIST", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
energy_resist( apply( energy_resist(), value ) );
|
|
orig_energy_resist( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "POISONRESIST", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
poison_resist( apply( poison_resist(), value ) );
|
|
orig_poison_resist( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "PHYSICALRESIST", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
physical_resist( apply( physical_resist(), value ) );
|
|
orig_physical_resist( static_cast<s16>( value ) );
|
|
}
|
|
|
|
if ( elem.remove_prop( "FIREDAMAGE", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
fire_damage( apply( fire_damage(), value ) );
|
|
orig_fire_damage( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "COLDDAMAGE", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
cold_damage( apply( cold_damage(), value ) );
|
|
orig_cold_damage( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "ENERGYDAMAGE", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
energy_damage( apply( energy_damage(), value ) );
|
|
orig_energy_damage( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "POISONDAMAGE", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
poison_damage( apply( poison_damage(), value ) );
|
|
orig_poison_damage( static_cast<s16>( value ) );
|
|
}
|
|
if ( elem.remove_prop( "PHYSICALDAMAGE", &tmp ) && diceValue( tmp, &value ) )
|
|
{
|
|
physical_damage( apply( physical_damage(), value ) );
|
|
orig_physical_damage( static_cast<s16>( value ) );
|
|
}
|
|
}
|
|
|
|
void NPC::readProperties( Clib::ConfigElem& elem )
|
|
{
|
|
// 3/18/3 dave copied this npctemplate code from readNpcProperties, because base::readProperties
|
|
// will call the exported vital functions before npctemplate is set (distro uses npctemplate in
|
|
// the exported funcs).
|
|
template_name = elem.rest();
|
|
|
|
if ( template_name.get().empty() )
|
|
{
|
|
std::string tmp;
|
|
if ( getprop( "template", tmp ) )
|
|
{
|
|
template_name = tmp.c_str() + 1;
|
|
}
|
|
}
|
|
base::readProperties( elem );
|
|
readNpcProperties( elem );
|
|
}
|
|
|
|
void NPC::readNewNpcAttributes( Clib::ConfigElem& elem )
|
|
{
|
|
std::string diestring;
|
|
Core::Dice dice;
|
|
std::string errmsg;
|
|
|
|
for ( Attribute* pAttr : Core::gamestate.attributes )
|
|
{
|
|
AttributeValue& av = attribute( pAttr->attrid );
|
|
for ( unsigned i = 0; i < pAttr->aliases.size(); ++i )
|
|
{
|
|
if ( elem.remove_prop( pAttr->aliases[i].c_str(), &diestring ) )
|
|
{
|
|
if ( !dice.load( diestring.c_str(), &errmsg ) )
|
|
{
|
|
elem.throw_error( "Error reading Attribute " + pAttr->name + ": " + errmsg );
|
|
}
|
|
int base = dice.roll() * 10;
|
|
if ( base > static_cast<int>( ATTRIBUTE_MAX_BASE ) )
|
|
base = ATTRIBUTE_MAX_BASE;
|
|
|
|
av.base( static_cast<unsigned short>( base ) );
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void NPC::readPropertiesForNewNPC( Clib::ConfigElem& elem )
|
|
{
|
|
readCommonProperties( elem );
|
|
readNewNpcAttributes( elem );
|
|
readNpcProperties( elem );
|
|
calc_vital_stuff();
|
|
set_vitals_to_maximum();
|
|
|
|
// readNpcProperties( elem );
|
|
}
|
|
|
|
void NPC::restart_script()
|
|
{
|
|
if ( ex != nullptr )
|
|
{
|
|
ex->seterror( true );
|
|
// A Sleeping script would otherwise sit and wait until it wakes up to be killed.
|
|
ex->revive();
|
|
if ( ex->in_debugger_holdlist() )
|
|
ex->revive_debugged();
|
|
ex = nullptr;
|
|
// when the NPC executor module destructs, it checks this NPC to see if it points
|
|
// back at it. If not, it leaves us alone.
|
|
}
|
|
if ( !script.get().empty() )
|
|
start_script();
|
|
}
|
|
|
|
void NPC::on_death( Items::Item* corpse )
|
|
{
|
|
// base::on_death intentionally not called
|
|
send_remove_character_to_nearby( this );
|
|
|
|
|
|
corpse->setprop( "npctemplate", "s" + template_name.get() );
|
|
if ( Clib::FileExists( "scripts/misc/death.ecl" ) )
|
|
Core::start_script( "misc/death", new Module::EItemRefObjImp( corpse ) );
|
|
|
|
ClrCharacterWorldPosition( this, Realms::WorldChangeReason::NpcDeath );
|
|
if ( ex != nullptr )
|
|
{
|
|
// this will force the execution engine to stop running this script immediately
|
|
ex->seterror( true );
|
|
ex->revive();
|
|
if ( ex->in_debugger_holdlist() )
|
|
ex->revive_debugged();
|
|
}
|
|
|
|
destroy();
|
|
}
|
|
|
|
|
|
void NPC::start_script()
|
|
{
|
|
passert( ex == nullptr );
|
|
passert( !script.get().empty() );
|
|
Core::ScriptDef sd( script, template_->pkg, "scripts/ai/" );
|
|
// Log( "NPC script starting: %s\n", sd.name().c_str() );
|
|
|
|
ref_ptr<Bscript::EScriptProgram> prog = Core::find_script2( sd );
|
|
// find_script( "ai/" + script );
|
|
|
|
if ( prog.get() == nullptr )
|
|
{
|
|
ERROR_PRINTLN( "Unable to read script {} for NPC {}({:#x})", sd.name(), name(), serial );
|
|
throw std::runtime_error( "Error loading NPCs" );
|
|
}
|
|
|
|
ex = Core::create_script_executor();
|
|
ex->addModule( new Module::NPCExecutorModule( *ex, *this ) );
|
|
Module::UOExecutorModule* uoemod = new Module::UOExecutorModule( *ex );
|
|
ex->addModule( uoemod );
|
|
if ( ex->setProgram( prog.get() ) == false )
|
|
{
|
|
ERROR_PRINTLN( "There was an error running script {} for NPC {}({:#x})", script.get(), name(),
|
|
serial );
|
|
throw std::runtime_error( "Error loading NPCs" );
|
|
}
|
|
|
|
uoemod->attached_npc_ = this;
|
|
|
|
schedule_executor( ex );
|
|
}
|
|
|
|
|
|
bool NPC::can_be_renamed_by( const Character* chr ) const
|
|
{
|
|
return ( master_.get() == chr );
|
|
}
|
|
|
|
void NPC::on_pc_spoke( Character* src_chr, const std::string& speech, u8 texttype,
|
|
const std::string& lang, Bscript::ObjArray* speechtokens )
|
|
{
|
|
if ( ex == nullptr )
|
|
return;
|
|
|
|
if ( Core::settingsManager.ssopt.seperate_speechtoken )
|
|
{
|
|
if ( speechtokens != nullptr && ( ( ex->eventmask & Core::EVID_TOKEN_SPOKE ) == 0 ) )
|
|
return;
|
|
else if ( speechtokens == nullptr && ( ( ex->eventmask & Core::EVID_SPOKE ) == 0 ) )
|
|
return;
|
|
}
|
|
if ( ( ( ex->eventmask & Core::EVID_SPOKE ) || ( ex->eventmask & Core::EVID_TOKEN_SPOKE ) ) &&
|
|
in_range( src_chr, ex->speech_size ) && !deafened() )
|
|
{
|
|
if ( ( !Core::settingsManager.ssopt.event_visibility_core_checks ) ||
|
|
is_visible_to_me( src_chr, /*check_range*/ false ) )
|
|
{
|
|
ex->signal_event( new Module::SpeechEvent(
|
|
src_chr, speech, Core::ListenPoint::TextTypeToString( texttype ), lang, speechtokens ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
void NPC::on_ghost_pc_spoke( Character* src_chr, const std::string& speech, u8 texttype,
|
|
const std::string& lang, Bscript::ObjArray* speechtokens )
|
|
{
|
|
if ( ex == nullptr )
|
|
return;
|
|
|
|
if ( Core::settingsManager.ssopt.seperate_speechtoken )
|
|
{
|
|
if ( speechtokens != nullptr && ( ( ex->eventmask & Core::EVID_TOKEN_GHOST_SPOKE ) == 0 ) )
|
|
return;
|
|
else if ( speechtokens == nullptr && ( ( ex->eventmask & Core::EVID_GHOST_SPEECH ) == 0 ) )
|
|
return;
|
|
}
|
|
if ( ( ( ex->eventmask & Core::EVID_GHOST_SPEECH ) ||
|
|
( ex->eventmask & Core::EVID_TOKEN_GHOST_SPOKE ) ) &&
|
|
in_range( src_chr, ex->speech_size ) && !deafened() )
|
|
{
|
|
if ( ( !Core::settingsManager.ssopt.event_visibility_core_checks ) ||
|
|
is_visible_to_me( src_chr, /*check_range*/ false ) )
|
|
{
|
|
ex->signal_event( new Module::SpeechEvent(
|
|
src_chr, speech, Core::ListenPoint::TextTypeToString( texttype ), lang, speechtokens ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
void NPC::inform_engaged( Character* engaged )
|
|
{
|
|
// someone has targetted us. Create an event if appropriate.
|
|
if ( ex != nullptr )
|
|
{
|
|
if ( ex->eventmask & Core::EVID_ENGAGED )
|
|
{
|
|
ex->signal_event( new Module::EngageEvent( engaged ) );
|
|
}
|
|
}
|
|
// Note, we don't do the base class thing, 'cause we have no client.
|
|
}
|
|
|
|
void NPC::inform_disengaged( Character* disengaged )
|
|
{
|
|
// someone has targetted us. Create an event if appropriate.
|
|
if ( ex != nullptr )
|
|
{
|
|
if ( ex->eventmask & Core::EVID_DISENGAGED )
|
|
{
|
|
ex->signal_event( new Module::DisengageEvent( disengaged ) );
|
|
}
|
|
}
|
|
// Note, we don't do the base class thing, 'cause we have no client.
|
|
}
|
|
|
|
void NPC::inform_criminal( Character* thecriminal )
|
|
{
|
|
if ( ex != nullptr )
|
|
{
|
|
if ( ( ex->eventmask & ( Core::EVID_GONE_CRIMINAL ) ) &&
|
|
in_range( thecriminal, ex->area_size ) )
|
|
{
|
|
if ( ( !Core::settingsManager.ssopt.event_visibility_core_checks ) ||
|
|
is_visible_to_me( thecriminal, /*check_range*/ false ) )
|
|
ex->signal_event( new Module::SourcedEvent( Core::EVID_GONE_CRIMINAL, thecriminal ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
void NPC::inform_leftarea( Character* wholeft )
|
|
{
|
|
if ( ex != nullptr )
|
|
{
|
|
if ( ( ex->eventmask & ( Core::EVID_LEFTAREA ) ) && can_accept_area_event_by( wholeft ) )
|
|
{
|
|
if ( in_range( wholeft, ex->area_size ) )
|
|
{
|
|
if ( ( !Core::settingsManager.ssopt.event_visibility_core_checks ) ||
|
|
is_visible_to_me( wholeft, /*check_range*/ false ) )
|
|
ex->signal_event( new Module::SourcedEvent( Core::EVID_LEFTAREA, wholeft ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void NPC::inform_enteredarea( Character* whoentered )
|
|
{
|
|
if ( ex != nullptr )
|
|
{
|
|
if ( ( ex->eventmask & ( Core::EVID_ENTEREDAREA ) ) && can_accept_area_event_by( whoentered ) )
|
|
{
|
|
if ( in_range( whoentered, ex->area_size ) )
|
|
{
|
|
if ( ( !Core::settingsManager.ssopt.event_visibility_core_checks ) ||
|
|
is_visible_to_me( whoentered, /*check_range*/ false ) )
|
|
ex->signal_event( new Module::SourcedEvent( Core::EVID_ENTEREDAREA, whoentered ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void NPC::inform_moved( Character* moved )
|
|
{
|
|
// 8-26-05 Austin
|
|
// Note: This does not look at realms at all, just X Y coords.
|
|
// ^is_visible_to_me checks realm - Turley
|
|
|
|
if ( ex != nullptr )
|
|
{
|
|
bool signaled = false;
|
|
passert( moved != nullptr );
|
|
if ( ( ex->eventmask & ( Core::EVID_ENTEREDAREA | Core::EVID_LEFTAREA ) ) &&
|
|
can_accept_area_event_by( moved ) )
|
|
{
|
|
bool are_inrange = in_range( moved, ex->area_size );
|
|
bool were_inrange = in_range( moved->lastpos, ex->area_size );
|
|
|
|
if ( ( !Core::settingsManager.ssopt.event_visibility_core_checks ) ||
|
|
is_visible_to_me( moved ) )
|
|
{
|
|
if ( are_inrange && !were_inrange && ( ex->eventmask & ( Core::EVID_ENTEREDAREA ) ) )
|
|
{
|
|
ex->signal_event( new Module::SourcedEvent( Core::EVID_ENTEREDAREA, moved ) );
|
|
signaled = true;
|
|
}
|
|
else if ( !are_inrange && were_inrange && ( ex->eventmask & ( Core::EVID_LEFTAREA ) ) )
|
|
{
|
|
ex->signal_event( new Module::SourcedEvent( Core::EVID_LEFTAREA, moved ) );
|
|
signaled = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !signaled ) // only send moved event if left/enteredarea wasnt send
|
|
{
|
|
if ( ( moved == opponent_ ) && ( ex->eventmask & ( Core::EVID_OPPONENT_MOVED ) ) )
|
|
{
|
|
if ( ( !Core::settingsManager.ssopt.event_visibility_core_checks ) ||
|
|
is_visible_to_me( moved ) )
|
|
ex->signal_event( new Module::SourcedEvent( Core::EVID_OPPONENT_MOVED, moved ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// This NPC moved. Tell him about other mobiles that have "entered" his area
|
|
// (through his own movement)
|
|
//
|
|
|
|
void NPC::inform_imoved( Character* chr )
|
|
{
|
|
if ( ex != nullptr )
|
|
{
|
|
passert( chr != nullptr );
|
|
if ( ex->eventmask & ( Core::EVID_ENTEREDAREA | Core::EVID_LEFTAREA ) &&
|
|
can_accept_area_event_by( chr ) )
|
|
{
|
|
bool are_inrange = in_range( chr, ex->area_size );
|
|
bool were_inrange = lastpos.in_range( chr->pos(), ex->area_size );
|
|
|
|
if ( ( !Core::settingsManager.ssopt.event_visibility_core_checks ) ||
|
|
is_visible_to_me( chr ) )
|
|
{
|
|
if ( are_inrange && !were_inrange && ( ex->eventmask & ( Core::EVID_ENTEREDAREA ) ) )
|
|
ex->signal_event( new Module::SourcedEvent( Core::EVID_ENTEREDAREA, chr ) );
|
|
else if ( !are_inrange && were_inrange && ( ex->eventmask & ( Core::EVID_LEFTAREA ) ) )
|
|
ex->signal_event( new Module::SourcedEvent( Core::EVID_LEFTAREA, chr ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool NPC::can_accept_area_event_by( const Character* who ) const
|
|
{
|
|
if ( !ex->area_mask )
|
|
return true;
|
|
|
|
const bool isNPC = who->isa( Core::UOBJ_CLASS::CLASS_NPC );
|
|
if ( ( ex->area_mask & Core::EVMASK_ONLY_PC ) && isNPC )
|
|
return false;
|
|
|
|
if ( ( ex->area_mask & Core::EVMASK_ONLY_NPC ) && !isNPC )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool NPC::can_accept_event( Core::EVENTID eventid )
|
|
{
|
|
if ( ex == nullptr )
|
|
return false;
|
|
if ( ex->eventmask & eventid )
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool NPC::send_event( Bscript::BObjectImp* event )
|
|
{
|
|
if ( ex != nullptr )
|
|
{
|
|
if ( ex->signal_event( event ) )
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// There's no executor, so we must delete it ourselves.
|
|
Bscript::BObject bo( event );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Bscript::BObjectImp* NPC::send_event_script( Bscript::BObjectImp* event )
|
|
{
|
|
if ( ex != nullptr )
|
|
{
|
|
if ( ex->signal_event( event ) )
|
|
return new Bscript::BLong( 1 );
|
|
else
|
|
{
|
|
return new Bscript::BError( "Event queue is full, discarding event" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Because there is no control script, we must delete it ourselves.
|
|
Bscript::BObject bo( event );
|
|
return new Bscript::BError( "That NPC doesn't have a control script" );
|
|
}
|
|
}
|
|
|
|
void NPC::apply_raw_damage_hundredths( unsigned int damage, Character* source, bool userepsys,
|
|
bool send_damage_packet )
|
|
{
|
|
if ( ex != nullptr )
|
|
{
|
|
if ( ex->eventmask & Core::EVID_DAMAGED )
|
|
{
|
|
ex->signal_event(
|
|
new Module::DamageEvent( source, static_cast<unsigned short>( damage / 100 ) ) );
|
|
}
|
|
}
|
|
|
|
base::apply_raw_damage_hundredths( damage, source, userepsys, send_damage_packet );
|
|
}
|
|
|
|
// keep this in sync with Character::armor_absorb_damage
|
|
double NPC::armor_absorb_damage( double damage )
|
|
{
|
|
if ( !npc_ar_ )
|
|
{
|
|
return base::armor_absorb_damage( damage );
|
|
}
|
|
else
|
|
{
|
|
int blocked = npc_ar_ + ar_mod();
|
|
if ( blocked < 0 )
|
|
blocked = 0;
|
|
int absorbed = blocked / 2;
|
|
|
|
blocked -= absorbed;
|
|
absorbed += Clib::random_int( blocked );
|
|
if ( Core::settingsManager.watch.combat )
|
|
INFO_PRINTLN( "{} hits absorbed by NPC armor.", absorbed );
|
|
damage -= absorbed;
|
|
if ( damage < 0 )
|
|
damage = 0;
|
|
}
|
|
return damage;
|
|
}
|
|
|
|
void NPC::get_hitscript_params( double damage, Items::UArmor** parmor, unsigned short* rawdamage )
|
|
{
|
|
if ( !npc_ar_ )
|
|
{
|
|
base::get_hitscript_params( damage, parmor, rawdamage );
|
|
}
|
|
else
|
|
{
|
|
*rawdamage = static_cast<unsigned short>( calc_thru_damage( damage, npc_ar_ + ar_mod() ) );
|
|
}
|
|
}
|
|
|
|
Items::UWeapon* NPC::intrinsic_weapon()
|
|
{
|
|
if ( template_->intrinsic_weapon )
|
|
return template_->intrinsic_weapon;
|
|
else
|
|
return Core::gamestate.wrestling_weapon;
|
|
}
|
|
|
|
void NPC::refresh_ar()
|
|
{
|
|
// Check if the NPC is using intrinsic armor. If yes, use it and set armor to 0
|
|
// regardless of what is equipped. If not, just calculate like a PC.
|
|
// TODO: intrinsic and equipped armor should sum, but many code will need to be
|
|
// modified for this to work.
|
|
// NOTE: Keep in mind an armor could have just been destroyed when calling this
|
|
// and it could've been the last piece of armor equipped, so checking for
|
|
// an equipped armor will not be wise. Intrinsic armor is assumed to never
|
|
// change instead.
|
|
if ( npc_ar_ )
|
|
{
|
|
for ( unsigned zone = 0; zone < Core::gamestate.armorzones.size(); ++zone )
|
|
armor_[zone] = nullptr;
|
|
ar_ = 0;
|
|
resetEquipablePropertiesNPC();
|
|
}
|
|
else
|
|
{
|
|
base::refresh_ar();
|
|
}
|
|
}
|
|
|
|
void NPC::resetEquipablePropertiesNPC()
|
|
{
|
|
if ( has_fire_resist() || has_orig_fire_resist() )
|
|
fire_resist( fire_resist().setAsValue( orig_fire_resist() ) );
|
|
if ( has_cold_resist() || has_orig_cold_resist() )
|
|
cold_resist( cold_resist().setAsValue( orig_cold_resist() ) );
|
|
if ( has_energy_resist() || has_orig_energy_resist() )
|
|
energy_resist( energy_resist().setAsValue( orig_energy_resist() ) );
|
|
if ( has_poison_resist() || has_orig_poison_resist() )
|
|
poison_resist( poison_resist().setAsValue( orig_poison_resist() ) );
|
|
if ( has_physical_resist() || has_orig_physical_resist() )
|
|
physical_resist( physical_resist().setAsValue( orig_physical_resist() ) );
|
|
|
|
if ( has_fire_damage() || has_orig_fire_damage() )
|
|
fire_damage( fire_damage().setAsValue( orig_fire_damage() ) );
|
|
if ( has_cold_damage() || has_orig_cold_damage() )
|
|
cold_damage( cold_damage().setAsValue( orig_cold_damage() ) );
|
|
if ( has_energy_damage() || has_orig_energy_damage() )
|
|
energy_damage( energy_damage().setAsValue( orig_energy_damage() ) );
|
|
if ( has_poison_damage() || has_orig_poison_damage() )
|
|
poison_damage( poison_damage().setAsValue( orig_poison_damage() ) );
|
|
if ( has_physical_damage() || has_orig_physical_damage() )
|
|
physical_damage( physical_damage().setAsValue( orig_physical_damage() ) );
|
|
|
|
if ( has_lower_reagent_cost() || has_orig_lower_reagent_cost() )
|
|
lower_reagent_cost( lower_reagent_cost().setAsValue( orig_lower_reagent_cost() ) );
|
|
if ( has_spell_damage_increase() || has_orig_spell_damage_increase() )
|
|
spell_damage_increase( spell_damage_increase().setAsValue( orig_spell_damage_increase() ) );
|
|
if ( has_faster_casting() || has_orig_faster_casting() )
|
|
faster_casting( faster_casting().setAsValue( orig_faster_casting() ) );
|
|
if ( has_faster_cast_recovery() || has_orig_faster_cast_recovery() )
|
|
faster_cast_recovery( faster_cast_recovery().setAsValue( orig_faster_cast_recovery() ) );
|
|
if ( has_defence_increase() || has_orig_defence_increase() )
|
|
defence_increase( defence_increase().setAsValue( orig_defence_increase() ) );
|
|
if ( has_defence_increase_cap() || has_orig_defence_increase_cap() )
|
|
defence_increase_cap( defence_increase_cap().setAsValue( orig_defence_increase_cap() ) );
|
|
if ( has_lower_mana_cost() || has_orig_lower_mana_cost() )
|
|
lower_mana_cost( lower_mana_cost().setAsValue( orig_lower_mana_cost() ) );
|
|
if ( has_hit_chance() || has_orig_hit_chance() )
|
|
hit_chance( hit_chance().setAsValue( orig_hit_chance() ) );
|
|
if ( has_fire_resist_cap() || has_orig_fire_resist_cap() )
|
|
fire_resist_cap( fire_resist_cap().setAsValue( orig_fire_resist_cap() ) );
|
|
if ( has_cold_resist_cap() || has_orig_cold_resist_cap() )
|
|
cold_resist_cap( cold_resist_cap().setAsValue( orig_energy_resist_cap() ) );
|
|
if ( has_energy_resist_cap() || has_orig_energy_resist_cap() )
|
|
energy_resist_cap( energy_resist_cap().setAsValue( orig_energy_resist_cap() ) );
|
|
if ( has_physical_resist_cap() || has_orig_physical_resist_cap() )
|
|
physical_resist_cap( physical_resist_cap().setAsValue( orig_physical_resist_cap() ) );
|
|
if ( has_poison_resist_cap() || has_orig_poison_resist_cap() )
|
|
poison_resist_cap( poison_resist_cap().setAsValue( orig_poison_resist_cap() ) );
|
|
if ( has_luck() || has_orig_luck() )
|
|
luck( luck().setAsValue( orig_luck() ) );
|
|
if ( has_swing_speed_increase() || has_orig_swing_speed_increase() )
|
|
swing_speed_increase( swing_speed_increase().setAsValue( orig_swing_speed_increase() ) );
|
|
}
|
|
|
|
size_t NPC::estimatedSize() const
|
|
{
|
|
return base::estimatedSize() + sizeof( unsigned short ) /*damaged_sound*/
|
|
+ sizeof( unsigned short ) /*run_speed*/
|
|
+ sizeof( Core::UOExecutor* ) /*ex*/
|
|
+ sizeof( unsigned short ) /*npc_ar_*/
|
|
+ sizeof( Core::CharacterRef ) /*master_*/
|
|
+ sizeof( anchor ) /*anchor*/
|
|
+ sizeof( boost_utils::script_name_flystring ) /*script*/
|
|
+ sizeof( boost_utils::npctemplate_name_flystring ); /*template_name*/
|
|
}
|
|
|
|
u16 NPC::get_damaged_sound() const
|
|
{
|
|
if ( damaged_sound != 0 )
|
|
return damaged_sound;
|
|
return base::get_damaged_sound();
|
|
}
|
|
|
|
bool NPC::use_adjustments() const
|
|
{
|
|
return mob_flags_.get( MOB_FLAGS::USE_ADJUSTMENTS );
|
|
}
|
|
|
|
void NPC::use_adjustments( bool newvalue )
|
|
{
|
|
mob_flags_.change( MOB_FLAGS::USE_ADJUSTMENTS, newvalue );
|
|
}
|
|
|
|
bool NPC::no_drop_exception() const
|
|
{
|
|
return flags_.get( Core::OBJ_FLAGS::NO_DROP_EXCEPTION );
|
|
}
|
|
|
|
void NPC::no_drop_exception( bool newvalue )
|
|
{
|
|
flags_.change( Core::OBJ_FLAGS::NO_DROP_EXCEPTION, newvalue );
|
|
}
|
|
|
|
std::string NPC::templatename() const
|
|
{
|
|
return template_name;
|
|
}
|
|
|
|
bool NPC::get_method_hook( const char* methodname, Bscript::Executor* executor,
|
|
Core::ExportScript** hook, unsigned int* PC ) const
|
|
{
|
|
if ( Core::gamestate.system_hooks.get_method_hook(
|
|
Core::gamestate.system_hooks.npc_method_script.get(), methodname, executor, hook, PC ) )
|
|
return true;
|
|
return base::get_method_hook( methodname, executor, hook, PC );
|
|
}
|
|
} // namespace Mobile
|
|
} // namespace Pol
|