• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

polserver / polserver / 25939934106

15 May 2026 08:30PM UTC coverage: 60.882% (-0.05%) from 60.929%
25939934106

push

github

turleypol
apply damage for items + evid_damaged

0 of 36 new or added lines in 2 files covered. (0.0%)

1083 existing lines in 14 files now uncovered.

44698 of 73417 relevant lines covered (60.88%)

524562.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.03
/pol-core/pol/mobile/npc.cpp
1
/** @file
2
 *
3
 * @par History
4
 * - 2005/06/15 Shinigami: added CanMove - checks if an NPC can move in given direction
5
 *                         (IsLegalMove works in a different way and is used for bounding boxes
6
 * only)
7
 * - 2006/01/18 Shinigami: set Master first and then start Script in NPC::readNpcProperties
8
 * - 2006/01/18 Shinigami: added attached_npc_ - to get attached NPC from AI-Script-Process Obj
9
 * - 2006/09/17 Shinigami: send_event() will return error "Event queue is full, discarding event"
10
 * - 2009/03/27 MuadDib:   NPC::inform_moved() && NPC::inform_imoved()
11
 *                         split the left/entered area to fix bug where one would trigger when not
12
 * enabled.
13
 * - 2009/07/23 MuadDib:   updates for new Enum::Packet Out ID
14
 * - 2009/08/25 Shinigami: STLport-5.2.1 fix: params not used
15
 *                         STLport-5.2.1 fix: init order changed of damaged_sound
16
 * - 2009/09/15 MuadDib:   Cleanup from registered houses on destroy
17
 * - 2009/09/18 MuadDib:   Adding save/load of registered house serial
18
 * - 2009/09/22 MuadDib:   Rewrite for Character/NPC to use ar(), ar_mod(), ar_mod(newvalue)
19
 * virtuals.
20
 * - 2009/10/14 Turley:    Added char.deaf() methods & char.deafened member
21
 * - 2009/10/23 Turley:    fixed OPPONENT_MOVED,LEFTAREA,ENTEREDAREA
22
 * - 2009/11/16 Turley:    added NpcPropagateEnteredArea()/inform_enteredarea() for event on
23
 * resurrection
24
 * - 2010/01/15 Turley:    (Tomi) SaveOnExit as npcdesc entry
25
 */
26

27
#include "npc.h"
28

29
#include <stdlib.h>
30

31
#include "../../bscript/berror.h"
32
#include "../../bscript/executor.h"
33
#include "../../clib/cfgelem.h"
34
#include "../../clib/fileutil.h"
35
#include "../../clib/logfacility.h"
36
#include "../../clib/passert.h"
37
#include "../../clib/random.h"
38
#include "../../clib/refptr.h"
39
#include "../../clib/streamsaver.h"
40
#include "../baseobject.h"
41
#include "../dice.h"
42
#include "../fnsearch.h"
43
#include "../globals/state.h"
44
#include "../globals/uvars.h"
45
#include "../item/armor.h"
46
#include "../item/weapon.h"
47
#include "../listenpt.h"
48
#include "../module/npcmod.h"
49
#include "../module/uomod.h"
50
#include "../multi/multi.h"
51
#include "../npctmpl.h"
52
#include "../scrdef.h"
53
#include "../scrsched.h"
54
#include "../scrstore.h"
55
#include "../syshookscript.h"
56
#include "../ufunc.h"
57
#include "../uobjcnt.h"
58
#include "../uobject.h"
59
#include "../uoexec.h"
60
#include "../uoscrobj.h"
61
#include "../uworld.h"
62
#include "attribute.h"
63
#include "charactr.h"
64
#include "wornitems.h"
65

66

67
/* An area definition is as follows:
68
   pt: (x,y)
69
   rect: [pt-pt]
70
   area: rect,rect,...
71
   So, format is: [(x1,y1)-(x2,y2)],[],[],...
72
   Well, right now, the format is x1 y1 x2 y2 ... (ick)
73
*/
74

75
namespace Pol::Mobile
76
{
77
unsigned short calc_thru_damage( double damage, unsigned short ar );
78

79
NPC::NPC( u32 objtype, const Clib::ConfigElem& elem )
78✔
80
    : Character( objtype, Core::UOBJ_CLASS::CLASS_NPC ),
81
      // UOBJECT INTERFACE
82
      // NPC INTERFACE
83
      npc_ar_( 0 ),
78✔
84
      // MOVEMENT
85
      run_speed( dexterity() ),
156✔
86
      anchor(),
78✔
87
      // EVENTS
88
      // SCRIPT
89
      script( "" ),
78✔
90
      ex( nullptr ),
78✔
91
      // MISC
92
      damaged_sound( 0 ),
78✔
93
      template_name(),
78✔
94
      master_( nullptr ),
78✔
95
      template_( Core::find_npc_template( elem ) )
156✔
96
{
97
  connected( true );
78✔
98
  logged_in( true );
78✔
99
  use_adjustments( true );
78✔
100
  ++Core::stateManager.uobjcount.npc_count;
78✔
101
}
78✔
102

103
NPC::~NPC()
156✔
104
{
105
  stop_scripts();
78✔
106
  --Core::stateManager.uobjcount.npc_count;
78✔
107
}
156✔
108

109
void NPC::stop_scripts()
78✔
110
{
111
  if ( ex != nullptr )
78✔
112
  {
113
    // this will force the execution engine to stop running this script immediately
114
    // dont delete the executor here, since it could currently run
115
    ex->seterror( true );
×
116
    ex->revive();
×
117
    if ( ex->in_debugger_holdlist() )
×
118
      ex->revive_debugged();
×
119
  }
120
}
78✔
121

122
void NPC::destroy()
78✔
123
{
124
  // stop_scripts();
125
  wornitems->destroy_contents();
78✔
126
  if ( registered_multi > 0 )
78✔
127
  {
128
    Multi::UMulti* multi = Core::system_find_multi( registered_multi );
6✔
129
    if ( multi != nullptr )
6✔
130
    {
131
      multi->unregister_object( (UObject*)this );
6✔
132
    }
133
    registered_multi = 0;
6✔
134
  }
135
  base::destroy();
78✔
136
}
78✔
137

138
const char* NPC::classname() const
4✔
139
{
140
  return "NPC";
4✔
141
}
142

143
bool NPC::anchor_allows_move( Core::UFACING fdir ) const
1✔
144
{
145
  auto newpos = pos().move( fdir );
1✔
146

147
  if ( anchor.enabled && !warmode() )
1✔
148
  {
149
    unsigned short curdist = distance_to( anchor.pos );
×
150
    unsigned short newdist = newpos.xy().pol_distance( anchor.pos );
×
151
    if ( newdist > curdist )  // if we're moving further away, see if we can
×
152
    {
153
      if ( newdist > anchor.dstart )
×
154
      {
155
        int perc = 100 - ( newdist - anchor.dstart ) * anchor.psub;
×
156
        if ( perc < 5 )
×
157
          perc = 5;
×
158
        if ( Clib::random_int( 99 ) > perc )
×
159
          return false;
×
160
      }
161
    }
162
  }
163
  return true;
1✔
164
}
165

166
bool NPC::could_move( Core::UFACING fdir ) const
×
167
{
168
  short newz;
169
  Multi::UMulti* supporting_multi;
170
  Items::Item* walkon_item;
171
  // Check for diagonal move - use Nandos change from charactr.cpp -- OWHorus (2011-04-26)
172
  if ( fdir & 1 )  // check if diagonal movement is allowed -- Nando (2009-02-26)
×
173
  {
174
    u8 tmp_facing = ( fdir + 1 ) & 0x7;
×
175
    auto tmp_pos = pos().move( static_cast<Core::UFACING>( tmp_facing ) );
×
176

177
    // needs to save because if only one direction is blocked, it shouldn't block ;)
178
    short current_boost = gradual_boost;
×
179
    bool walk1 = realm()->walkheight( this, tmp_pos.xy(), tmp_pos.z(), &newz, &supporting_multi,
×
180
                                      &walkon_item, &current_boost );
181

182
    tmp_facing = ( fdir - 1 ) & 0x7;
×
183
    tmp_pos = pos().move( static_cast<Core::UFACING>( tmp_facing ) );
×
184
    current_boost = gradual_boost;
×
185
    if ( !walk1 && !realm()->walkheight( this, tmp_pos.xy(), tmp_pos.z(), &newz, &supporting_multi,
×
186
                                         &walkon_item, &current_boost ) )
187
      return false;
×
188
  }
189
  auto new_pos = pos().move( fdir );
×
190
  short current_boost = gradual_boost;
×
191
  return realm()->walkheight( this, new_pos.xy(), new_pos.z(), &newz, &supporting_multi,
×
192
                              &walkon_item, &current_boost ) &&
×
193
         !npc_path_blocked( fdir ) && anchor_allows_move( fdir );
×
194
}
195

196
bool NPC::npc_path_blocked( Core::UFACING fdir ) const
×
197
{
198
  if ( can_freemove() )
×
199
    return false;
×
200

201
  Core::MOVEBLOCKMODE moveBlockMode = Core::settingsManager.ssopt.mobiles_block_npc_movement;
×
202

203
  if ( moveBlockMode == Core::MOVEBLOCKMODE_NONE )
×
204
    return false;
×
205
  if ( !this->master() && moveBlockMode == Core::MOVEBLOCKMODE_SAME_MASTER )
×
206
    return false;
×
207

208
  auto new_pos = pos().move( fdir );
×
209

210
  Core::Pos2d gridp = Core::zone_convert( new_pos );
×
211

212
  if ( moveBlockMode == Core::MOVEBLOCKMODE_ALL )
×
213
  {
214
    for ( const auto& chr : realm()->getzone_grid( gridp ).characters )
×
215
    {
216
      // First check if there really is a character blocking
217
      if ( chr->pos2d() == new_pos.xy() && chr->z() >= z() - 10 && chr->z() <= z() + 10 )
×
218
      {
219
        if ( !chr->dead() && is_visible_to_me( chr, /*check_range*/ false ) )
×
220
          return true;
×
221
      }
222
    }
223
  }
224

225
  for ( const auto& chr : realm()->getzone_grid( gridp ).npcs )
×
226
  {
227
    // First check if there really is a character blocking
228
    if ( chr->pos2d() == new_pos.xy() && chr->z() >= z() - 10 && chr->z() <= z() + 10 )
×
229
    {
230
      // Do not allow npcs of same master running on top of each other
231
      if ( moveBlockMode == Core::MOVEBLOCKMODE_SAME_MASTER )
×
232
      {
233
        NPC* npc = static_cast<NPC*>( chr );
×
234
        if ( npc->master() && this->master() == npc->master() && !npc->dead() &&
×
235
             is_visible_to_me( npc, /*check_range*/ false ) )
×
236
          return true;
×
237
      }
238
      else
239
      {
240
        if ( !chr->dead() && is_visible_to_me( chr, /*check_range*/ false ) )
×
241
          return true;
×
242
      }
243
    }
244
  }
245
  return false;
×
246
}
247

248
void NPC::printOn( Clib::StreamWriter& sw ) const
4✔
249
{
250
  sw.begin( classname(), template_name.get() );
4✔
251
  printProperties( sw );
4✔
252
  sw.end();
4✔
253
}
4✔
254

255
void NPC::printSelfOn( Clib::StreamWriter& sw ) const
×
256
{
257
  printOn( sw );
×
258
}
×
259

260
void NPC::printProperties( Clib::StreamWriter& sw ) const
4✔
261
{
262
  base::printProperties( sw );
4✔
263

264
  if ( npc_ar_ )
4✔
265
    sw.add( "AR", npc_ar_ );
2✔
266

267
  if ( !script.get().empty() )
4✔
268
    sw.add( "script", script.get() );
2✔
269

270
  if ( master_.get() != nullptr )
4✔
271
    sw.add( "master", master_->serial );
2✔
272

273
  if ( has_speech_color() )
4✔
274
    sw.add( "SpeechColor", speech_color() );
2✔
275

276
  if ( has_speech_font() )
4✔
277
    sw.add( "SpeechFont", speech_font() );
2✔
278

279
  if ( run_speed != dexterity() )
4✔
280
    sw.add( "RunSpeed", run_speed );
2✔
281

282
  if ( use_adjustments() != true )
4✔
283
    sw.add( "UseAdjustments", use_adjustments() );
2✔
284

285

286
  if ( has_orig_fire_resist() )
4✔
287
    sw.add( "FireResist", orig_fire_resist() );
2✔
288
  if ( has_orig_cold_resist() )
4✔
289
    sw.add( "ColdResist", orig_cold_resist() );
2✔
290
  if ( has_orig_energy_resist() )
4✔
291
    sw.add( "EnergyResist", orig_energy_resist() );
2✔
292
  if ( has_orig_poison_resist() )
4✔
293
    sw.add( "PoisonResist", orig_poison_resist() );
2✔
294
  if ( has_orig_physical_resist() )
4✔
295
    sw.add( "PhysicalResist", orig_physical_resist() );
2✔
296

297
  if ( has_orig_fire_damage() )
4✔
298
    sw.add( "FireDamage", orig_fire_damage() );
2✔
299
  if ( has_orig_cold_damage() )
4✔
300
    sw.add( "ColdDamage", orig_cold_damage() );
2✔
301
  if ( has_orig_energy_damage() )
4✔
302
    sw.add( "EnergyDamage", orig_energy_damage() );
2✔
303
  if ( has_orig_poison_damage() )
4✔
304
    sw.add( "PoisonDamage", orig_poison_damage() );
2✔
305
  if ( has_orig_physical_damage() )
4✔
306
    sw.add( "PhysicalDamage", orig_physical_damage() );
2✔
307
  if ( has_orig_lower_reagent_cost() )
4✔
308
    sw.add( "LowerReagentCost", orig_lower_reagent_cost() );
2✔
309
  if ( has_orig_spell_damage_increase() )
4✔
310
    sw.add( "SpellDamageIncrease", orig_spell_damage_increase() );
2✔
311
  if ( has_orig_faster_casting() )
4✔
312
    sw.add( "FasterCasting", orig_faster_casting() );
2✔
313
  if ( has_orig_faster_cast_recovery() )
4✔
314
    sw.add( "FasterCastRecovery", orig_faster_cast_recovery() );
2✔
315
  if ( has_orig_defence_increase() )
4✔
316
    sw.add( "DefenceIncrease", orig_defence_increase() );
2✔
317
  if ( has_orig_defence_increase_cap() )
4✔
318
    sw.add( "DefenceIncreaseCap", orig_defence_increase_cap() );
2✔
319
  if ( has_orig_lower_mana_cost() )
4✔
320
    sw.add( "LowerManaCost", orig_lower_mana_cost() );
2✔
321
  if ( has_orig_hit_chance() )
4✔
322
    sw.add( "HitChance", orig_hit_chance() );
2✔
323
  if ( has_orig_fire_resist_cap() )
4✔
324
    sw.add( "FireResistCap", orig_fire_resist_cap() );
2✔
325
  if ( has_orig_cold_resist_cap() )
4✔
326
    sw.add( "ColdResistCap", orig_cold_resist_cap() );
2✔
327
  if ( has_orig_energy_resist_cap() )
4✔
328
    sw.add( "EnergyResistCap", orig_energy_resist_cap() );
2✔
329
  if ( has_orig_physical_resist_cap() )
4✔
330
    sw.add( "PhysicalResistCap", orig_physical_resist_cap() );
2✔
331
  if ( has_orig_poison_resist_cap() )
4✔
332
    sw.add( "PoisonResistCap", orig_poison_resist_cap() );
2✔
333
  if ( has_orig_luck() )
4✔
334
    sw.add( "Luck", orig_luck() );
2✔
335
  if ( has_swing_speed_increase() )
4✔
336
    sw.add( "SwingSpeedIncrease", orig_swing_speed_increase() );
2✔
337
  if ( has_min_attack_range_increase() )
4✔
338
    sw.add( "MinAttackRangeIncrease", orig_min_attack_range_increase() );
2✔
339
  if ( has_max_attack_range_increase() )
4✔
340
    sw.add( "MaxAttackRangeIncrease", orig_max_attack_range_increase() );
2✔
341
  if ( no_drop_exception() )
4✔
342
    sw.add( "NoDropException", no_drop_exception() );
2✔
343
}
4✔
344

345
void NPC::printDebugProperties( Clib::StreamWriter& sw ) const
×
346
{
347
  base::printDebugProperties( sw );
×
348
  sw.comment( "template: {}", template_->name );
×
349
  if ( anchor.enabled )
×
350
  {
351
    sw.comment( "anchor: x={} y={} dstart={} psub={}", anchor.pos.x(), anchor.pos.y(),
×
352
                anchor.dstart, anchor.psub );
×
353
  }
354
}
×
355

356
void NPC::readNpcProperties( Clib::ConfigElem& elem )
78✔
357
{
358
  Items::UWeapon* wpn = static_cast<Items::UWeapon*>(
359
      Items::find_intrinsic_equipment( elem.rest(), Core::LAYER_HAND1 ) );
78✔
360
  if ( wpn == nullptr )
78✔
361
    wpn = Items::create_intrinsic_weapon_from_npctemplate( elem, template_->pkg );
×
362
  if ( wpn != nullptr )
78✔
363
    weapon = wpn;
78✔
364

365
  Items::UArmor* sld = static_cast<Items::UArmor*>(
366
      Items::find_intrinsic_equipment( elem.rest(), Core::LAYER_HAND2 ) );
78✔
367
  if ( sld == nullptr )
78✔
368
    sld = Items::create_intrinsic_shield_from_npctemplate( elem, template_->pkg );
73✔
369
  if ( sld != nullptr )
78✔
370
    shield = sld;
5✔
371

372
  // Load the base, equiping items etc will refresh_ar() to update for reals.
373
  loadEquipablePropertiesNPC( elem );
78✔
374

375
  // dave 3/19/3, read templatename only if empty
376
  if ( template_name.get().empty() )
78✔
377
  {
378
    template_name = elem.rest();
76✔
379

380
    if ( template_name.get().empty() )
76✔
381
    {
382
      std::string tmp;
×
383
      if ( getprop( "template", tmp ) )
×
384
      {
385
        template_name = tmp.c_str() + 1;
×
386
      }
387
    }
×
388
  }
389

390
  unsigned int master_serial;
391
  if ( elem.remove_prop( "MASTER", &master_serial ) )
78✔
392
  {
393
    Character* chr = Core::system_find_mobile( master_serial );
1✔
394
    if ( chr != nullptr )
1✔
395
      master_.set( chr );
1✔
396
  }
397

398
  script = elem.remove_string( "script", "" );
78✔
399
  if ( !script.get().empty() )
78✔
400
    start_script();
10✔
401

402
  speech_color( elem.remove_ushort( "SpeechColor", Plib::DEFAULT_TEXT_COLOR ) );
78✔
403
  speech_font( elem.remove_ushort( "SpeechFont", Plib::DEFAULT_TEXT_FONT ) );
78✔
404
  saveonexit( elem.remove_bool( "SaveOnExit", true ) );
78✔
405

406
  mob_flags_.change( MOB_FLAGS::USE_ADJUSTMENTS, elem.remove_bool( "UseAdjustments", true ) );
78✔
407
  run_speed = elem.remove_ushort( "RunSpeed", dexterity() );
78✔
408

409
  damaged_sound = elem.remove_ushort( "DamagedSound", 0 );
78✔
410
  no_drop_exception( elem.remove_bool( "NoDropException", false ) );
78✔
411
}
78✔
412

413
void NPC::loadEquipablePropertiesNPC( Clib::ConfigElem& elem )
78✔
414
{
415
  // for ar and elemental damage/resist the mod values are loaded before in character code!
416
  auto diceValue = []( const std::string& dicestr, s16* value ) -> bool
84✔
417
  {
418
    Core::Dice dice;
84✔
419
    std::string errmsg;
84✔
420
    if ( !dice.load( dicestr.c_str(), &errmsg ) )
84✔
421
      *value = Clib::clamp_convert<s16>( atoi( dicestr.c_str() ) );
×
422
    else
423
      *value = Clib::clamp_convert<s16>( dice.roll() );
84✔
424
    return *value != 0;
84✔
425
  };
84✔
426
  auto apply = []( Core::ValueModPack v, s16 value ) -> Core::ValueModPack
81✔
427
  { return v.addToValue( value ); };
81✔
428

429
  std::string tmp;
78✔
430
  s16 value;
431
  if ( elem.remove_prop( "AR", &tmp ) && diceValue( tmp, &value ) )
78✔
432
    npc_ar_ = value;
3✔
433
  if ( elem.remove_prop( "LOWERREAGENTCOST", &tmp ) && diceValue( tmp, &value ) )
78✔
434
  {
435
    lower_reagent_cost( apply( lower_reagent_cost(), value ) );
3✔
436
    orig_lower_reagent_cost( value );
3✔
437
  }
438
  if ( elem.remove_prop( "SPELLDAMAGEINCREASE", &tmp ) && diceValue( tmp, &value ) )
78✔
439
  {
440
    spell_damage_increase( apply( spell_damage_increase(), value ) );
3✔
441
    orig_spell_damage_increase( value );
3✔
442
  }
443
  if ( elem.remove_prop( "FASTERCASTING", &tmp ) && diceValue( tmp, &value ) )
78✔
444
  {
445
    faster_casting( apply( faster_casting(), value ) );
3✔
446
    orig_faster_casting( value );
3✔
447
  }
448
  if ( elem.remove_prop( "FASTERCASTRECOVERY", &tmp ) && diceValue( tmp, &value ) )
78✔
449
  {
450
    faster_cast_recovery( apply( faster_cast_recovery(), value ) );
3✔
451
    orig_faster_cast_recovery( value );
3✔
452
  }
453
  if ( elem.remove_prop( "DEFENCEINCREASE", &tmp ) && diceValue( tmp, &value ) )
78✔
454
  {
455
    defence_increase( apply( defence_increase(), value ) );
3✔
456
    orig_defence_increase( value );
3✔
457
  }
458
  if ( elem.remove_prop( "DEFENCEINCREASECAP", &tmp ) && diceValue( tmp, &value ) )
78✔
459
  {
460
    defence_increase_cap( apply( defence_increase_cap(), value ) );
3✔
461
    orig_defence_increase_cap( value );
3✔
462
  }
463
  if ( elem.remove_prop( "LOWERMANACOST", &tmp ) && diceValue( tmp, &value ) )
78✔
464
  {
465
    lower_mana_cost( apply( lower_mana_cost(), value ) );
3✔
466
    orig_lower_mana_cost( value );
3✔
467
  }
468
  if ( elem.remove_prop( "HITCHANCE", &tmp ) && diceValue( tmp, &value ) )
78✔
469
  {
470
    hit_chance( apply( hit_chance(), value ) );
3✔
471
    orig_hit_chance( value );
3✔
472
  }
473
  if ( elem.remove_prop( "FIRERESISTCAP", &tmp ) && diceValue( tmp, &value ) )
78✔
474
  {
475
    fire_resist_cap( apply( fire_resist_cap(), value ) );
3✔
476
    orig_fire_resist_cap( value );
3✔
477
  }
478
  if ( elem.remove_prop( "COLDRESISTCAP", &tmp ) && diceValue( tmp, &value ) )
78✔
479
  {
480
    cold_resist_cap( apply( cold_resist_cap(), value ) );
3✔
481
    orig_cold_resist_cap( value );
3✔
482
  }
483
  if ( elem.remove_prop( "ENERGYRESISTCAP", &tmp ) && diceValue( tmp, &value ) )
78✔
484
  {
485
    energy_resist_cap( apply( energy_resist_cap(), value ) );
3✔
486
    orig_energy_resist_cap( value );
3✔
487
  }
488
  if ( elem.remove_prop( "PHYSICALRESISTCAP", &tmp ) && diceValue( tmp, &value ) )
78✔
489
  {
490
    physical_resist_cap( apply( physical_resist_cap(), value ) );
3✔
491
    orig_physical_resist_cap( value );
3✔
492
  }
493
  if ( elem.remove_prop( "POISONRESISTCAP", &tmp ) && diceValue( tmp, &value ) )
78✔
494
  {
495
    poison_resist_cap( apply( poison_resist_cap(), value ) );
3✔
496
    orig_poison_resist_cap( value );
3✔
497
  }
498
  if ( elem.remove_prop( "LUCK", &tmp ) && diceValue( tmp, &value ) )
78✔
499
  {
500
    luck( apply( luck(), value ) );
3✔
501
    orig_luck( value );
3✔
502
  }
503
  if ( elem.remove_prop( "SWINGSPEEDINCREASE", &tmp ) && diceValue( tmp, &value ) )
78✔
504
  {
505
    swing_speed_increase( apply( swing_speed_increase(), value ) );
3✔
506
    orig_swing_speed_increase( value );
3✔
507
  }
508
  if ( elem.remove_prop( "MINATTACKRANGEINCREASE", &tmp ) && diceValue( tmp, &value ) )
78✔
509
  {
510
    min_attack_range_increase( apply( min_attack_range_increase(), value ) );
3✔
511
    orig_min_attack_range_increase( value );
3✔
512
  }
513
  if ( elem.remove_prop( "MAXATTACKRANGEINCREASE", &tmp ) && diceValue( tmp, &value ) )
78✔
514
  {
515
    max_attack_range_increase( apply( max_attack_range_increase(), value ) );
3✔
516
    orig_max_attack_range_increase( value );
3✔
517
  }
518

519
  // elemental start
520
  // first apply template value as value and if mod or value exist sum them
521
  if ( elem.remove_prop( "FIRERESIST", &tmp ) && diceValue( tmp, &value ) )
78✔
522
  {
523
    fire_resist( apply( fire_resist(), value ) );
3✔
524
    orig_fire_resist( value );
3✔
525
  }
526
  if ( elem.remove_prop( "COLDRESIST", &tmp ) && diceValue( tmp, &value ) )
78✔
527
  {
528
    cold_resist( apply( cold_resist(), value ) );
3✔
529
    orig_cold_resist( value );
3✔
530
  }
531
  if ( elem.remove_prop( "ENERGYRESIST", &tmp ) && diceValue( tmp, &value ) )
78✔
532
  {
533
    energy_resist( apply( energy_resist(), value ) );
3✔
534
    orig_energy_resist( value );
3✔
535
  }
536
  if ( elem.remove_prop( "POISONRESIST", &tmp ) && diceValue( tmp, &value ) )
78✔
537
  {
538
    poison_resist( apply( poison_resist(), value ) );
3✔
539
    orig_poison_resist( value );
3✔
540
  }
541
  if ( elem.remove_prop( "PHYSICALRESIST", &tmp ) && diceValue( tmp, &value ) )
78✔
542
  {
543
    physical_resist( apply( physical_resist(), value ) );
3✔
544
    orig_physical_resist( value );
3✔
545
  }
546

547
  if ( elem.remove_prop( "FIREDAMAGE", &tmp ) && diceValue( tmp, &value ) )
78✔
548
  {
549
    fire_damage( apply( fire_damage(), value ) );
3✔
550
    orig_fire_damage( value );
3✔
551
  }
552
  if ( elem.remove_prop( "COLDDAMAGE", &tmp ) && diceValue( tmp, &value ) )
78✔
553
  {
554
    cold_damage( apply( cold_damage(), value ) );
3✔
555
    orig_cold_damage( value );
3✔
556
  }
557
  if ( elem.remove_prop( "ENERGYDAMAGE", &tmp ) && diceValue( tmp, &value ) )
78✔
558
  {
559
    energy_damage( apply( energy_damage(), value ) );
3✔
560
    orig_energy_damage( value );
3✔
561
  }
562
  if ( elem.remove_prop( "POISONDAMAGE", &tmp ) && diceValue( tmp, &value ) )
78✔
563
  {
564
    poison_damage( apply( poison_damage(), value ) );
3✔
565
    orig_poison_damage( value );
3✔
566
  }
567
  if ( elem.remove_prop( "PHYSICALDAMAGE", &tmp ) && diceValue( tmp, &value ) )
78✔
568
  {
569
    physical_damage( apply( physical_damage(), value ) );
3✔
570
    orig_physical_damage( value );
3✔
571
  }
572
}
78✔
573

574
void NPC::readProperties( Clib::ConfigElem& elem )
2✔
575
{
576
  // 3/18/3 dave copied this npctemplate code from readNpcProperties, because base::readProperties
577
  // will call the exported vital functions before npctemplate is set (distro uses npctemplate in
578
  // the exported funcs).
579
  template_name = elem.rest();
2✔
580

581
  if ( template_name.get().empty() )
2✔
582
  {
583
    std::string tmp;
×
584
    if ( getprop( "template", tmp ) )
×
585
    {
586
      template_name = tmp.c_str() + 1;
×
587
    }
588
  }
×
589
  base::readProperties( elem );
2✔
590
  readNpcProperties( elem );
2✔
591
}
2✔
592

593
void NPC::readNewNpcAttributes( Clib::ConfigElem& elem )
76✔
594
{
595
  std::string diestring;
76✔
596
  Core::Dice dice;
76✔
597
  std::string errmsg;
76✔
598

599
  for ( Attribute* pAttr : Core::gamestate.attributes )
4,028✔
600
  {
601
    AttributeValue& av = attribute( pAttr->attrid );
3,952✔
602
    for ( unsigned i = 0; i < pAttr->aliases.size(); ++i )
7,828✔
603
    {
604
      if ( elem.remove_prop( pAttr->aliases[i].c_str(), &diestring ) )
4,180✔
605
      {
606
        if ( !dice.load( diestring.c_str(), &errmsg ) )
304✔
607
        {
608
          elem.throw_error( "Error reading Attribute " + pAttr->name + ": " + errmsg );
×
609
        }
610
        int base = dice.roll() * 10;
304✔
611
        if ( base > static_cast<int>( ATTRIBUTE_MAX_BASE ) )
304✔
612
          base = ATTRIBUTE_MAX_BASE;
×
613

614
        av.base( static_cast<unsigned short>( base ) );
304✔
615

616
        break;
304✔
617
      }
618
    }
619
  }
620
}
76✔
621

622
void NPC::readPropertiesForNewNPC( Clib::ConfigElem& elem )
76✔
623
{
624
  readCommonProperties( elem );
76✔
625
  readNewNpcAttributes( elem );
76✔
626
  readNpcProperties( elem );
76✔
627
  calc_vital_stuff();
76✔
628
  set_vitals_to_maximum();
76✔
629

630
  //    readNpcProperties( elem );
631
}
76✔
632

633
void NPC::restart_script()
×
634
{
635
  if ( ex != nullptr )
×
636
  {
637
    ex->seterror( true );
×
638
    // A Sleeping script would otherwise sit and wait until it wakes up to be killed.
639
    ex->revive();
×
640
    if ( ex->in_debugger_holdlist() )
×
641
      ex->revive_debugged();
×
642
    ex = nullptr;
×
643
    // when the NPC executor module destructs, it checks this NPC to see if it points
644
    // back at it.  If not, it leaves us alone.
645
  }
646
  if ( !script.get().empty() )
×
647
    start_script();
×
648
}
×
649

650
void NPC::on_death( Items::Item* corpse )
73✔
651
{
652
  // base::on_death intentionally not called
653
  send_remove_character_to_nearby( this );
73✔
654

655

656
  corpse->setprop( "npctemplate", "s" + template_name.get() );
73✔
657
  if ( Clib::FileExists( "scripts/misc/death.ecl" ) )
73✔
658
    Core::start_script( "misc/death", new Module::EItemRefObjImp( corpse ) );
×
659

660
  ClrCharacterWorldPosition( this, Realms::WorldChangeReason::NpcDeath );
73✔
661
  if ( ex != nullptr )
73✔
662
  {
663
    // this will force the execution engine to stop running this script immediately
664
    ex->seterror( true );
8✔
665
    ex->revive();
8✔
666
    if ( ex->in_debugger_holdlist() )
8✔
667
      ex->revive_debugged();
×
668
  }
669

670
  destroy();
73✔
671
}
73✔
672

673

674
void NPC::start_script()
10✔
675
{
676
  passert( ex == nullptr );
10✔
677
  passert( !script.get().empty() );
10✔
678
  Core::ScriptDef sd( script, template_->pkg, "scripts/ai/" );
10✔
679
  // Log( "NPC script starting: %s\n", sd.name().c_str() );
680

681
  ref_ptr<Bscript::EScriptProgram> prog = Core::find_script2( sd );
10✔
682
  // find_script( "ai/" + script );
683

684
  if ( prog.get() == nullptr )
10✔
685
  {
686
    ERROR_PRINTLN( "Unable to read script {} for NPC {}({:#x})", sd.name(), name(), serial );
×
687
    throw std::runtime_error( "Error loading NPCs" );
×
688
  }
689

690
  ex = Core::create_script_executor();
10✔
691
  ex->addModule( new Module::NPCExecutorModule( *ex, *this ) );
10✔
692
  Module::UOExecutorModule* uoemod = new Module::UOExecutorModule( *ex );
10✔
693
  ex->addModule( uoemod );
10✔
694
  if ( ex->setProgram( prog.get() ) == false )
10✔
695
  {
696
    ERROR_PRINTLN( "There was an error running script {} for NPC {}({:#x})", script.get(), name(),
×
697
                   serial );
×
698
    throw std::runtime_error( "Error loading NPCs" );
×
699
  }
700

701
  uoemod->attached_npc_ = this;
10✔
702

703
  schedule_executor( ex );
10✔
704
}
10✔
705

706

707
bool NPC::can_be_renamed_by( const Character* chr ) const
×
708
{
709
  return ( master_.get() == chr );
×
710
}
711

712
bool NPC::can_be_clothed_by( const Character* chr ) const
9✔
713
{
714
  return has_paperdoll() && Core::settingsManager.ssopt.master_can_clothe_npcs &&
15✔
715
         master_.get() == chr;
15✔
716
}
717

718
void NPC::on_pc_spoke( Character* src_chr, const std::string& speech, u8 texttype,
2✔
719
                       const std::string& lang, Bscript::ObjArray* speechtokens )
720
{
721
  if ( ex == nullptr )
2✔
722
    return;
×
723

724
  if ( Core::settingsManager.ssopt.seperate_speechtoken )
2✔
725
  {
726
    if ( speechtokens != nullptr && ( ( ex->eventmask & Core::EVID_TOKEN_SPOKE ) == 0 ) )
×
727
      return;
×
728
    if ( speechtokens == nullptr && ( ( ex->eventmask & Core::EVID_SPOKE ) == 0 ) )
×
729
      return;
×
730
  }
731
  if ( ( ( ex->eventmask & Core::EVID_SPOKE ) || ( ex->eventmask & Core::EVID_TOKEN_SPOKE ) ) &&
×
732
       in_range( src_chr, ex->speech_size ) && !deafened() )
2✔
733
  {
734
    if ( ( !Core::settingsManager.ssopt.event_visibility_core_checks ) ||
2✔
735
         is_visible_to_me( src_chr, /*check_range*/ false ) )
×
736
    {
737
      ex->signal_event( new Module::SpeechEvent(
2✔
738
          src_chr, speech, Core::ListenPoint::TextTypeToString( texttype ), lang, speechtokens ) );
4✔
739
    }
740
  }
741
}
742

743
void NPC::on_ghost_pc_spoke( Character* src_chr, const std::string& speech, u8 texttype,
×
744
                             const std::string& lang, Bscript::ObjArray* speechtokens )
745
{
746
  if ( ex == nullptr )
×
747
    return;
×
748

749
  if ( Core::settingsManager.ssopt.seperate_speechtoken )
×
750
  {
751
    if ( speechtokens != nullptr && ( ( ex->eventmask & Core::EVID_TOKEN_GHOST_SPOKE ) == 0 ) )
×
752
      return;
×
753
    if ( speechtokens == nullptr && ( ( ex->eventmask & Core::EVID_GHOST_SPEECH ) == 0 ) )
×
754
      return;
×
755
  }
756
  if ( ( ( ex->eventmask & Core::EVID_GHOST_SPEECH ) ||
×
757
         ( ex->eventmask & Core::EVID_TOKEN_GHOST_SPOKE ) ) &&
×
758
       in_range( src_chr, ex->speech_size ) && !deafened() )
×
759
  {
760
    if ( ( !Core::settingsManager.ssopt.event_visibility_core_checks ) ||
×
761
         is_visible_to_me( src_chr, /*check_range*/ false ) )
×
762
    {
763
      ex->signal_event( new Module::SpeechEvent(
×
764
          src_chr, speech, Core::ListenPoint::TextTypeToString( texttype ), lang, speechtokens ) );
×
765
    }
766
  }
767
}
768

769
void NPC::inform_engaged( const Attackable& engaged )
4✔
770
{
771
  // someone has targetted us. Create an event if appropriate.
772
  if ( !ex || !engaged )
4✔
UNCOV
773
    return;
×
774
  if ( ex->eventmask & Core::EVID_ENGAGED )
4✔
775
  {
776
    ex->signal_event( new Module::EngageEvent( engaged.object() ) );
×
777
  }
778
  // Note, we don't do the base class thing, 'cause we have no client.
779
}
780

781
void NPC::inform_disengaged( const Attackable& disengaged )
4✔
782
{
783
  // someone has targetted us. Create an event if appropriate.
784
  if ( !ex || !disengaged )
4✔
UNCOV
785
    return;
×
786

787
  if ( ex->eventmask & Core::EVID_DISENGAGED )
4✔
788
  {
789
    ex->signal_event( new Module::DisengageEvent( disengaged.object() ) );
×
790
  }
791
  // Note, we don't do the base class thing, 'cause we have no client.
792
}
793

UNCOV
794
void NPC::inform_criminal( Character* thecriminal )
×
795
{
UNCOV
796
  if ( ex != nullptr )
×
797
  {
UNCOV
798
    if ( ( ex->eventmask & ( Core::EVID_GONE_CRIMINAL ) ) &&
×
799
         in_range( thecriminal, ex->area_size ) )
×
800
    {
UNCOV
801
      if ( ( !Core::settingsManager.ssopt.event_visibility_core_checks ) ||
×
802
           is_visible_to_me( thecriminal, /*check_range*/ false ) )
×
803
        ex->signal_event( new Module::SourcedEvent( Core::EVID_GONE_CRIMINAL, thecriminal ) );
×
804
    }
805
  }
UNCOV
806
}
×
807

808
void NPC::inform_leftarea( Character* wholeft )
1✔
809
{
810
  if ( ex != nullptr )
1✔
811
  {
UNCOV
812
    if ( ( ex->eventmask & ( Core::EVID_LEFTAREA ) ) && can_accept_area_event_by( wholeft ) )
×
813
    {
UNCOV
814
      if ( in_range( wholeft, ex->area_size ) )
×
815
      {
UNCOV
816
        if ( ( !Core::settingsManager.ssopt.event_visibility_core_checks ) ||
×
817
             is_visible_to_me( wholeft, /*check_range*/ false ) )
×
818
          ex->signal_event( new Module::SourcedEvent( Core::EVID_LEFTAREA, wholeft ) );
×
819
      }
820
    }
821
  }
822
}
1✔
823

824
void NPC::inform_enteredarea( Character* whoentered )
299✔
825
{
826
  if ( ex != nullptr )
299✔
827
  {
828
    if ( ( ex->eventmask & ( Core::EVID_ENTEREDAREA ) ) && can_accept_area_event_by( whoentered ) )
36✔
829
    {
830
      if ( in_range( whoentered, ex->area_size ) )
18✔
831
      {
832
        if ( ( !Core::settingsManager.ssopt.event_visibility_core_checks ) ||
8✔
UNCOV
833
             is_visible_to_me( whoentered, /*check_range*/ false ) )
×
834
          ex->signal_event( new Module::SourcedEvent( Core::EVID_ENTEREDAREA, whoentered ) );
8✔
835
      }
836
    }
837
  }
838
}
299✔
839

840
void NPC::inform_moved( Character* moved )
245✔
841
{
842
  if ( ex == nullptr )
245✔
843
    return;
207✔
844
  passert( moved != nullptr );
38✔
845
  if ( ( ex->eventmask & ( Core::EVID_ENTEREDAREA | Core::EVID_LEFTAREA ) ) &&
68✔
846
       can_accept_area_event_by( moved ) )
30✔
847
  {
848
    bool are_inrange = in_range( moved, ex->area_size );
20✔
849
    bool were_inrange = in_range( moved->lastpos, ex->area_size );
20✔
850

851
    if ( ( !Core::settingsManager.ssopt.event_visibility_core_checks ) ||
20✔
UNCOV
852
         is_visible_to_me( moved ) )
×
853
    {
854
      if ( are_inrange && !were_inrange && ( ex->eventmask & ( Core::EVID_ENTEREDAREA ) ) )
20✔
855
      {
856
        ex->signal_event( new Module::SourcedEvent( Core::EVID_ENTEREDAREA, moved ) );
2✔
857
        return;
2✔
858
      }
859
      if ( !are_inrange && were_inrange && ( ex->eventmask & ( Core::EVID_LEFTAREA ) ) )
18✔
860
      {
861
        ex->signal_event( new Module::SourcedEvent( Core::EVID_LEFTAREA, moved ) );
10✔
862
        return;
10✔
863
      }
864
    }
865
  }
866

867
  // only send moved event if left/enteredarea wasnt send
868
  if ( ( moved == opponent_.object() ) && ( ex->eventmask & ( Core::EVID_OPPONENT_MOVED ) ) )
26✔
869
  {
UNCOV
870
    if ( ( !Core::settingsManager.ssopt.event_visibility_core_checks ) ||
×
871
         is_visible_to_me( moved ) )
×
872
      ex->signal_event( new Module::SourcedEvent( Core::EVID_OPPONENT_MOVED, moved ) );
×
873
  }
874
}
875

876
//
877
// This NPC moved.  Tell him about other mobiles that have "entered" his area
878
// (through his own movement)
879
//
880
void NPC::inform_imoved( Character* chr )
241✔
881
{
882
  if ( ex == nullptr )
241✔
883
    return;
229✔
884
  passert( chr != nullptr );
12✔
885
  if ( ex->eventmask & ( Core::EVID_ENTEREDAREA | Core::EVID_LEFTAREA ) &&
12✔
UNCOV
886
       can_accept_area_event_by( chr ) )
×
887
  {
UNCOV
888
    bool are_inrange = in_range( chr, ex->area_size );
×
889
    bool were_inrange = lastpos.in_range( chr->pos(), ex->area_size );
×
890

UNCOV
891
    if ( ( !Core::settingsManager.ssopt.event_visibility_core_checks ) || is_visible_to_me( chr ) )
×
892
    {
UNCOV
893
      if ( are_inrange && !were_inrange && ( ex->eventmask & ( Core::EVID_ENTEREDAREA ) ) )
×
894
        ex->signal_event( new Module::SourcedEvent( Core::EVID_ENTEREDAREA, chr ) );
×
895
      else if ( !are_inrange && were_inrange && ( ex->eventmask & ( Core::EVID_LEFTAREA ) ) )
×
896
        ex->signal_event( new Module::SourcedEvent( Core::EVID_LEFTAREA, chr ) );
×
897
    }
898
  }
899
}
900

901
bool NPC::can_accept_area_event_by( const Character* who ) const
57✔
902
{
903
  if ( !ex->area_mask )
57✔
904
    return true;
19✔
905

906
  const bool isNPC = who->isa( Core::UOBJ_CLASS::CLASS_NPC );
38✔
907
  if ( ( ex->area_mask & Core::EVMASK_ONLY_PC ) && isNPC )
38✔
908
    return false;
19✔
909

910
  if ( ( ex->area_mask & Core::EVMASK_ONLY_NPC ) && !isNPC )
19✔
UNCOV
911
    return false;
×
912

913
  return true;
19✔
914
}
915

916
bool NPC::can_accept_event( Core::EVENTID eventid )
5✔
917
{
918
  if ( ex == nullptr )
5✔
919
    return false;
5✔
UNCOV
920
  if ( ex->eventmask & eventid )
×
921
    return true;
×
922
  return false;
×
923
}
924

UNCOV
925
bool NPC::send_event( Bscript::BObjectImp* event )
×
926
{
UNCOV
927
  if ( ex != nullptr )
×
928
  {
UNCOV
929
    if ( ex->signal_event( event ) )
×
930
      return true;
×
931
  }
932
  else
933
  {
934
    // There's no executor, so we must delete it ourselves.
UNCOV
935
    Bscript::BObject bo( event );
×
936
  }
×
937
  return false;
×
938
}
939

UNCOV
940
Bscript::BObjectImp* NPC::send_event_script( Bscript::BObjectImp* event )
×
941
{
UNCOV
942
  if ( ex != nullptr )
×
943
  {
UNCOV
944
    if ( ex->signal_event( event ) )
×
945
      return new Bscript::BLong( 1 );
×
946

UNCOV
947
    return new Bscript::BError( "Event queue is full, discarding event" );
×
948
  }
949

950
  // Because there is no control script, we must delete it ourselves.
UNCOV
951
  Bscript::BObject bo( event );
×
952
  return new Bscript::BError( "That NPC doesn't have a control script" );
×
953
}
×
954

UNCOV
955
void NPC::apply_raw_damage_hundredths( unsigned int damage, Character* source, bool userepsys,
×
956
                                       bool send_damage_packet )
957
{
UNCOV
958
  if ( ex != nullptr )
×
959
  {
UNCOV
960
    if ( ex->eventmask & Core::EVID_DAMAGED )
×
961
    {
UNCOV
962
      ex->signal_event(
×
963
          new Module::DamageEvent( source, static_cast<unsigned short>( damage / 100 ) ) );
×
964
    }
965
  }
966

UNCOV
967
  base::apply_raw_damage_hundredths( damage, source, userepsys, send_damage_packet );
×
968
}
×
969

970
// keep this in sync with Character::armor_absorb_damage
UNCOV
971
double NPC::armor_absorb_damage( double damage )
×
972
{
UNCOV
973
  if ( !npc_ar_ )
×
974
  {
UNCOV
975
    return base::armor_absorb_damage( damage );
×
976
  }
UNCOV
977
  int blocked = npc_ar_ + ar_mod();
×
978
  if ( blocked < 0 )
×
979
    blocked = 0;
×
980
  int absorbed = blocked / 2;
×
981

UNCOV
982
  blocked -= absorbed;
×
983
  absorbed += Clib::random_int( blocked );
×
984
  if ( Core::settingsManager.watch.combat )
×
985
    INFO_PRINTLN( "{} hits absorbed by NPC armor.", absorbed );
×
986
  damage -= absorbed;
×
987
  if ( damage < 0 )
×
988
    damage = 0;
×
989
  return damage;
×
990
}
991

992
void NPC::get_hitscript_params( double damage, Items::UArmor** parmor, unsigned short* rawdamage )
8✔
993
{
994
  if ( !npc_ar_ )
8✔
995
  {
996
    base::get_hitscript_params( damage, parmor, rawdamage );
8✔
997
  }
998
  else
999
  {
UNCOV
1000
    *rawdamage = static_cast<unsigned short>( calc_thru_damage( damage, npc_ar_ + ar_mod() ) );
×
1001
  }
1002
}
8✔
1003

UNCOV
1004
Items::UWeapon* NPC::intrinsic_weapon()
×
1005
{
UNCOV
1006
  if ( template_->intrinsic_weapon )
×
1007
    return template_->intrinsic_weapon;
×
1008
  return Core::gamestate.wrestling_weapon;
×
1009
}
1010

1011
void NPC::refresh_ar()
48✔
1012
{
1013
  // Check if the NPC is using intrinsic armor. If yes, use it and set armor to 0
1014
  // regardless of what is equipped. If not, just calculate like a PC.
1015
  // TODO: intrinsic and equipped armor should sum, but many code will need to be
1016
  //       modified for this to work.
1017
  // NOTE: Keep in mind an armor could have just been destroyed when calling this
1018
  //       and it could've been the last piece of armor equipped, so checking for
1019
  //       an equipped armor will not be wise. Intrinsic armor is assumed to never
1020
  //       change instead.
1021
  if ( npc_ar_ )
48✔
1022
  {
1023
    for ( unsigned zone = 0; zone < Core::gamestate.armorzones.size(); ++zone )
7✔
1024
      armor_[zone] = nullptr;
6✔
1025
    ar_ = 0;
1✔
1026
    resetEquipablePropertiesNPC();
1✔
1027
  }
1028
  else
1029
  {
1030
    base::refresh_ar();
47✔
1031
  }
1032
}
48✔
1033

1034
void NPC::resetEquipablePropertiesNPC()
1✔
1035
{
1036
  if ( has_fire_resist() || has_orig_fire_resist() )
1✔
1037
    fire_resist( fire_resist().setAsValue( orig_fire_resist() ) );
1✔
1038
  if ( has_cold_resist() || has_orig_cold_resist() )
1✔
1039
    cold_resist( cold_resist().setAsValue( orig_cold_resist() ) );
1✔
1040
  if ( has_energy_resist() || has_orig_energy_resist() )
1✔
1041
    energy_resist( energy_resist().setAsValue( orig_energy_resist() ) );
1✔
1042
  if ( has_poison_resist() || has_orig_poison_resist() )
1✔
1043
    poison_resist( poison_resist().setAsValue( orig_poison_resist() ) );
1✔
1044
  if ( has_physical_resist() || has_orig_physical_resist() )
1✔
1045
    physical_resist( physical_resist().setAsValue( orig_physical_resist() ) );
1✔
1046

1047
  if ( has_fire_damage() || has_orig_fire_damage() )
1✔
1048
    fire_damage( fire_damage().setAsValue( orig_fire_damage() ) );
1✔
1049
  if ( has_cold_damage() || has_orig_cold_damage() )
1✔
1050
    cold_damage( cold_damage().setAsValue( orig_cold_damage() ) );
1✔
1051
  if ( has_energy_damage() || has_orig_energy_damage() )
1✔
1052
    energy_damage( energy_damage().setAsValue( orig_energy_damage() ) );
1✔
1053
  if ( has_poison_damage() || has_orig_poison_damage() )
1✔
1054
    poison_damage( poison_damage().setAsValue( orig_poison_damage() ) );
1✔
1055
  if ( has_physical_damage() || has_orig_physical_damage() )
1✔
1056
    physical_damage( physical_damage().setAsValue( orig_physical_damage() ) );
1✔
1057

1058
  if ( has_lower_reagent_cost() || has_orig_lower_reagent_cost() )
1✔
1059
    lower_reagent_cost( lower_reagent_cost().setAsValue( orig_lower_reagent_cost() ) );
1✔
1060
  if ( has_spell_damage_increase() || has_orig_spell_damage_increase() )
1✔
1061
    spell_damage_increase( spell_damage_increase().setAsValue( orig_spell_damage_increase() ) );
1✔
1062
  if ( has_faster_casting() || has_orig_faster_casting() )
1✔
1063
    faster_casting( faster_casting().setAsValue( orig_faster_casting() ) );
1✔
1064
  if ( has_faster_cast_recovery() || has_orig_faster_cast_recovery() )
1✔
1065
    faster_cast_recovery( faster_cast_recovery().setAsValue( orig_faster_cast_recovery() ) );
1✔
1066
  if ( has_defence_increase() || has_orig_defence_increase() )
1✔
1067
    defence_increase( defence_increase().setAsValue( orig_defence_increase() ) );
1✔
1068
  if ( has_defence_increase_cap() || has_orig_defence_increase_cap() )
1✔
1069
    defence_increase_cap( defence_increase_cap().setAsValue( orig_defence_increase_cap() ) );
1✔
1070
  if ( has_lower_mana_cost() || has_orig_lower_mana_cost() )
1✔
1071
    lower_mana_cost( lower_mana_cost().setAsValue( orig_lower_mana_cost() ) );
1✔
1072
  if ( has_hit_chance() || has_orig_hit_chance() )
1✔
1073
    hit_chance( hit_chance().setAsValue( orig_hit_chance() ) );
1✔
1074
  if ( has_fire_resist_cap() || has_orig_fire_resist_cap() )
1✔
1075
    fire_resist_cap( fire_resist_cap().setAsValue( orig_fire_resist_cap() ) );
1✔
1076
  if ( has_cold_resist_cap() || has_orig_cold_resist_cap() )
1✔
1077
    cold_resist_cap( cold_resist_cap().setAsValue( orig_cold_resist_cap() ) );
1✔
1078
  if ( has_energy_resist_cap() || has_orig_energy_resist_cap() )
1✔
1079
    energy_resist_cap( energy_resist_cap().setAsValue( orig_energy_resist_cap() ) );
1✔
1080
  if ( has_physical_resist_cap() || has_orig_physical_resist_cap() )
1✔
1081
    physical_resist_cap( physical_resist_cap().setAsValue( orig_physical_resist_cap() ) );
1✔
1082
  if ( has_poison_resist_cap() || has_orig_poison_resist_cap() )
1✔
1083
    poison_resist_cap( poison_resist_cap().setAsValue( orig_poison_resist_cap() ) );
1✔
1084
  if ( has_luck() || has_orig_luck() )
1✔
1085
    luck( luck().setAsValue( orig_luck() ) );
1✔
1086
  if ( has_swing_speed_increase() || has_orig_swing_speed_increase() )
1✔
1087
    swing_speed_increase( swing_speed_increase().setAsValue( orig_swing_speed_increase() ) );
1✔
1088
  if ( has_min_attack_range_increase() || has_orig_min_attack_range_increase() )
1✔
1089
    min_attack_range_increase(
2✔
1090
        min_attack_range_increase().setAsValue( orig_min_attack_range_increase() ) );
1✔
1091
  if ( has_max_attack_range_increase() || has_orig_max_attack_range_increase() )
1✔
1092
    max_attack_range_increase(
2✔
1093
        max_attack_range_increase().setAsValue( orig_max_attack_range_increase() ) );
1✔
1094
}
1✔
1095

1096
size_t NPC::estimatedSize() const
2✔
1097
{
1098
  return base::estimatedSize() + sizeof( unsigned short )     /*damaged_sound*/
2✔
1099
         + sizeof( unsigned short )                           /*run_speed*/
1100
         + sizeof( Core::UOExecutor* )                        /*ex*/
1101
         + sizeof( unsigned short )                           /*npc_ar_*/
1102
         + sizeof( Core::CharacterRef )                       /*master_*/
1103
         + sizeof( anchor )                                   /*anchor*/
1104
         + sizeof( boost_utils::script_name_flystring )       /*script*/
1105
         + sizeof( boost_utils::npctemplate_name_flystring ); /*template_name*/
2✔
1106
}
1107

UNCOV
1108
u16 NPC::get_damaged_sound() const
×
1109
{
UNCOV
1110
  if ( damaged_sound != 0 )
×
1111
    return damaged_sound;
×
1112
  return base::get_damaged_sound();
×
1113
}
1114

1115
bool NPC::use_adjustments() const
7✔
1116
{
1117
  return mob_flags_.get( MOB_FLAGS::USE_ADJUSTMENTS );
7✔
1118
}
1119

1120
void NPC::use_adjustments( bool newvalue )
78✔
1121
{
1122
  mob_flags_.change( MOB_FLAGS::USE_ADJUSTMENTS, newvalue );
78✔
1123
}
78✔
1124

1125
bool NPC::no_drop_exception() const
8✔
1126
{
1127
  return flags_.get( Core::OBJ_FLAGS::NO_DROP_EXCEPTION );
8✔
1128
}
1129

1130
void NPC::no_drop_exception( bool newvalue )
78✔
1131
{
1132
  flags_.change( Core::OBJ_FLAGS::NO_DROP_EXCEPTION, newvalue );
78✔
1133
}
78✔
1134

UNCOV
1135
std::string NPC::templatename() const
×
1136
{
UNCOV
1137
  return template_name;
×
1138
}
1139

UNCOV
1140
bool NPC::get_method_hook( const char* methodname, Bscript::Executor* executor,
×
1141
                           Core::ExportScript** hook, unsigned int* PC ) const
1142
{
UNCOV
1143
  if ( Core::gamestate.system_hooks.get_method_hook(
×
1144
           Core::gamestate.system_hooks.npc_method_script.get(), methodname, executor, hook, PC ) )
UNCOV
1145
    return true;
×
1146
  return base::get_method_hook( methodname, executor, hook, PC );
×
1147
}
1148
}  // namespace Pol::Mobile
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc