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

polserver / polserver / 25972182398

16 May 2026 08:30PM UTC coverage: 60.836% (-0.07%) from 60.903%
25972182398

Pull #884

github

turleypol
updated dynproperties
Pull Request #884: Attackable item

241 of 527 new or added lines in 26 files covered. (45.73%)

17 existing lines in 7 files now uncovered.

44729 of 73524 relevant lines covered (60.84%)

446850.64 hits per line

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

63.3
/pol-core/pol/mobile/charactr.cpp
1
/** @file
2
 *
3
 * @par History
4
 * - 2003/12/24 Dave:      on_poison_changed() changed to keep life bars from disappearing when you
5
 * poison someone.
6
 * - 2005/03/09 Shinigami: Added Prop Delay_Mod [ms] for WeaponDelay (see schedule_attack too)
7
 * - 2005/06/01 Shinigami: Added Walking_Mounted and Running_Mounted movecosts
8
 * - 2005/09/14 Shinigami: Character::resurrect() - Vital.regen_while_dead implemented
9
 * - 2005/10/14 Shinigami: fixed missing init of Character::dblclick_wait
10
 * - 2005/11/23 MuadDib:    Added warmode_wait object for characters.
11
 * - 2005/11/25 MuadDib:   Added realm check to is_visible_to_me.
12
 * - 2005/12/06 MuadDib:   Added uclang member for storing UC language from client.
13
 * - 2006/03/10 MuadDib:   Added NoCombat support to checking of justice region.
14
 * - 2006/05/04 MuadDib:   Removed get_legal_item for no use.
15
 * - 2006/05/04 MuadDib:   SkillValue() removed for no use.
16
 * - 2006/05/16 Shinigami: UOBJ_*MALE_GHOST renamed to UOBJ_HUMAN_*MALE_GHOST
17
 *                         added Prop Race (RACE_* Constants) to support Elfs
18
 *                         Character::die(), Character::doors_block() and Character::resurrect()
19
 * updated
20
 * - 2008/07/08 Turley:    get_flag1() changed to show WarMode of other player again
21
 *                         get_flag1_aos() removed
22
 * - 2009/01/14 Nando:     setgraphic() changed to allow graphics up to 2048 (0x800).
23
 * - 2009/02/01 MuadDib:   Resistance storage added.
24
 * - 2009/02/25 MuadDib:   on_poison_changed() added UOKR Status bar update for poisoned. Booyah!
25
 * - 2009/07/20 MuadDib:   Slot checks added to Character::Die()
26
 * - 2009/07/25 MuadDib:   equippable() now checks if a twohanded is intrinsic or not also.
27
 * Intrinsic gets ignored
28
 * - 2009/07/31 Turley:    added check for cmbtcfg::send_swing_packet & reset_swing_onturn
29
 * - 2009/08/04 MuadDib:   calc_vital_stuff() now checks to see if a vital changed, before using
30
 * tell_vital_changed()
31
 * - 2009/08/06 MuadDib:   Addeed gotten_by code for items.
32
 * - 2009/08/07 MuadDib:   Added new Corpse Layer code to character Die() to put equipped items on
33
 * correct layer with
34
 *                         corpse. Ignores items from pack, because in death we only want the items
35
 * it had equipped in
36
 *                         life, showing up on the corpse when dead.
37
 * - 2009/08/09 MuadDib:   on_poison_changed() rewritten for better KR poison support.
38
 * - 2009/08/16 MuadDib:   fix for die() where checking corpse and not item for slot_index().
39
 * - 2009/08/25 Shinigami: STLport-5.2.1 fix: corpseSlot not used
40
 *                         STLport-5.2.1 fix: init order changed of party_can_loot_,
41
 * party_decline_timeout_ and skillcap_
42
 * - 2009/08/28 Turley:    Crashfix for Character::on_poison_changed()
43
 * - 2009/09/03 MuadDib:   Changed combat related ssopt stuff to combat_config.
44
 *                         Changes for account related source file relocation
45
 *                         Changes for multi related source file relocation
46
 * - 2009/09/09 Turley:    ServSpecOpt CarryingCapacityMod as * modifier for
47
 * Character::carrying_capacity()
48
 * - 2009/09/15 MuadDib:   Cleanup from registered houses on destroy
49
 *                         u32 registered_house added to store serial of registered multi.
50
 *                         Multi registration/unregistration support added.
51
 * - 2009/09/06 Turley:    Changed Version checks to bitfield client->ClientType
52
 * - 2009/09/18 MuadDib:   Adding save/load of registered house serial
53
 * - 2009/09/22 MuadDib:   Rewrite for Character/NPC to use ar(), ar_mod(), ar_mod(newvalue)
54
 * virtuals.
55
 * - 2009/09/22 Turley:    Added DamagePacket support & repsys param to applydamage
56
 * - 2009/10/14 Turley:    new priv canbeheardasghost
57
 * - 2009/10/14 Turley:    Added char.deaf() methods & char.deafened member
58
 * - 2009/10/17 Turley:    PrivUpdater for "seehidden", "seeghosts", "seeinvisitems" and "invul" -
59
 * Tomi
60
 *                         fixed "all" priv
61
 *                         PrivUpdater class cleanup, removed duplicate stuff
62
 * - 2009/10/22 Turley:    added OuchHook call if lastz-z>21 (clientside value)
63
 * - 2009/11/16 Turley:    added NpcPropagateEnteredArea()/inform_enteredarea() for event on
64
 * resurrection
65
 * - 2009/11/19 Turley:    lightlevel now supports endless duration - Tomi
66
 * - 2009/11/20 Turley:    RecalcVitals can update single Attributes/Vitals - based on Tomi
67
 * - 2009/11/26 Turley:    Syshook CanDie(mobile)
68
 * - 2009/11/30 Turley:    fixed calc_single_vital doesnt check changed maximum value
69
 * - 2009/12/02 Turley:    added gargoyle & face support
70
 * - 2009/12/03 Turley:    fixed client>=7 poison/flying flag, basic flying support
71
 * - 2010/01/14 Turley:    AttackWhileFrozen check
72
 * - 2010/01/15 Turley:    (Tomi) priv runwhilestealth
73
 * - 2010/01/22 Turley:    Speedhack Prevention System
74
 * - 2011/11/12 Tomi:      added extobj.mount and extobj.secure_trade_container
75
 * - 2012/02/06 MuadDib:   Added serial check at root of Character::get_from_ground to make sure not
76
 * trying to move orphaned items.
77
 *                         In loop, if an found is an orphan it logs it and skips rest of individual
78
 * itr to ensure movement not attempted.
79
 */
80

81

82
#include "mobile/attack.h"
83
#include "pol_global_config.h"
84

85
#include "charactr.h"
86

87
#include <iterator>
88
#include <list>
89
#include <memory>
90
#include <stdlib.h>
91
#include <string>
92

93
#include "../../bscript/executor.h"
94
#include "../../clib/cfgelem.h"
95
#include "../../clib/cfgfile.h"
96
#include "../../clib/clib.h"
97
#include "../../clib/clib_endian.h"
98
#include "../../clib/esignal.h"
99
#include "../../clib/fileutil.h"
100
#include "../../clib/logfacility.h"
101
#include "../../clib/passert.h"
102
#include "../../clib/random.h"
103
#include "../../clib/stlutil.h"
104
#include "../../clib/streamsaver.h"
105
#include "../../plib/clidata.h"
106
#include "../../plib/mapcell.h"
107
#include "../../plib/objtype.h"
108
#include "../../plib/systemstate.h"
109
#include "../accounts/account.h"
110
#include "../accounts/accounts.h"
111
#include "../checkpnt.h"
112
#include "../cmbtcfg.h"
113
#include "../cmdlevel.h"
114
#include "../containr.h"
115
#include "../dice.h"
116
#include "../extobj.h"
117
#include "../fnsearch.h"
118
#include "../globals/settings.h"
119
#include "../globals/state.h"
120
#include "../globals/uvars.h"
121
#include "../guilds.h"
122
#include "../item/armor.h"
123
#include "../item/item.h"
124
#include "../item/itemdesc.h"
125
#include "../item/weapon.h"
126
#include "../item/wepntmpl.h"
127
#include "../layers.h"
128
#include "../mkscrobj.h"
129
#include "../module/uomod.h"
130
#include "../movecost.h"
131
#include "../multi/customhouses.h"
132
#include "../multi/house.h"
133
#include "../multi/multi.h"
134
#include "../multi/multidef.h"
135
#include "../network/cgdata.h"
136
#include "../network/client.h"
137
#include "../network/cliface.h"
138
#include "../network/packetdefs.h"
139
#include "../network/packethelper.h"
140
#include "../network/packets.h"
141
#include "../network/pktdef.h"
142
#include "../party.h"
143
#include "../polclass.h"
144
#include "../polsig.h"
145
#include "../polvar.h"
146
#include "../profile.h"
147
#include "../realms/WorldChangeReasons.h"
148
#include "../realms/realm.h"
149
#include "../schedule.h"
150
#include "../scrdef.h"
151
#include "../scrsched.h"
152
#include "../scrstore.h"
153
#include "../sfx.h"
154
#include "../skilladv.h"
155
#include "../spelbook.h"
156
#include "../statmsg.h"
157
#include "../syshook.h"
158
#include "../syshookscript.h"
159
#include "../ufunc.h"
160
#include "../ufuncstd.h"
161
#include "../uobjcnt.h"
162
#include "../uoexec.h"
163
#include "../uoscrobj.h"
164
#include "../uworld.h"
165
#include "../vital.h"
166
#include "attribute.h"
167
#include "corpse.h"
168
#include "privupdater.h"
169
#include "regions/guardrgn.h"
170
#include "regions/miscrgn.h"
171
#include "regions/musicrgn.h"
172
#include "wornitems.h"
173

174
#ifdef _MSC_VER
175
#pragma warning( disable : 4505 )  // unreferenced local function has been removed
176
#endif
177

178
namespace Pol
179
{
180
namespace Core
181
{
182
void cancel_trade( Mobile::Character* chr1 );
183
}
184
namespace Mobile
185
{
186
unsigned short layer_to_zone( unsigned short layer )
3✔
187
{
188
  for ( unsigned short zone = 0; zone < Core::gamestate.armorzones.size(); ++zone )
3✔
189
  {
190
    for ( unsigned i = 0; i < Core::gamestate.armorzones[zone].layers.size(); ++i )
12✔
191
    {
192
      if ( Core::gamestate.armorzones[zone].layers[i] == layer )
12✔
193
        return zone;
3✔
194
    }
195
  }
196
  ERROR_PRINTLN( "Couldn't find an Armor Zone in armrzone.cfg for layer {}", layer );
×
197
  throw std::runtime_error( "Configuration file error" );
×
198
}
199

200
const char* zone_to_zone_name( unsigned short zone )
×
201
{
202
  return Core::gamestate.armorzones[zone].name.c_str();
×
203
}
204

205
unsigned short zone_name_to_zone( const char* zname )
3✔
206
{
207
  for ( unsigned short zone = 0; zone < Core::gamestate.armorzones.size(); ++zone )
3✔
208
  {
209
    if ( stricmp( Core::gamestate.armorzones[zone].name.c_str(), zname ) == 0 )
3✔
210
    {
211
      return zone;
3✔
212
    }
213
  }
214
  ERROR_PRINTLN( "Couldn't find an armrzone.cfg config elem named '{}'", zname );
×
215

216
  throw std::runtime_error( "Configuration file error" );
×
217
}
218
void load_armor_zones()
3✔
219
{
220
  if ( !Clib::FileExists( "config/armrzone.cfg" ) )
3✔
221
  {
222
    if ( Plib::systemstate.config.loglevel > 1 )
×
223
      INFO_PRINTLN( "File config/armrzone.cfg not found, skipping." );
×
224
    return;
×
225
  }
226
  Clib::ConfigFile cf( "config/armrzone.cfg" );
3✔
227
  Clib::ConfigElem elem;
3✔
228

229
  Core::gamestate.armor_zone_chance_sum = 0;
3✔
230
  Core::gamestate.armorzones.clear();
3✔
231

232
  while ( cf.read( elem ) )
21✔
233
  {
234
    // armorzones.push_back( ArmorZone( elem ) );
235
    Core::ArmorZone az;
18✔
236
    az.name = elem.remove_string( "NAME" );
18✔
237
    az.chance = static_cast<double>( elem.remove_ushort( "CHANCE" ) ) / 100.0;
18✔
238
    unsigned short in_layer;
239
    while ( elem.remove_prop( "LAYER", &in_layer ) )
54✔
240
    {
241
      if ( in_layer < Core::LOWEST_LAYER || in_layer > Core::HIGHEST_LAYER )
36✔
242
      {
243
        ERROR_PRINTLN(
×
244
            "ArmorZone {}: Layer {} is out of range.\n"
245
            "Valid range is {} to {}",
246
            az.name, in_layer, Core::LOWEST_LAYER, Core::HIGHEST_LAYER );
×
247
        throw std::runtime_error( "Configuration file error" );
×
248
      }
249
      az.layers.push_back( in_layer );
36✔
250
    }
251

252
    Core::gamestate.armorzones.push_back( az );
18✔
253
    Core::gamestate.armor_zone_chance_sum += az.chance;
18✔
254
  }
18✔
255
}
3✔
256

257
void unload_armor_zones()
3✔
258
{
259
  Core::gamestate.armorzones.clear();
3✔
260
  Core::gamestate.armor_zone_chance_sum = 0;
3✔
261
}
3✔
262

263

264
Character::Character( u32 objtype, Core::UOBJ_CLASS uobj_class )
99✔
265
    : UObject( objtype, uobj_class ),
266
      // NPC
267
      // EQUIPMENT / ITEMS
268
      weapon( Core::gamestate.wrestling_weapon ),
99✔
269
      shield( nullptr ),
99✔
270
      armor_( Core::gamestate.armorzones.size() ),
99✔
271
      wornitems( new Core::WornItemsContainer ),  // default objtype is in containr.cpp,
99✔
272
                                                  // WornItemsContainer class
273
      remote_containers_(),
99✔
274
      // MOVEMENT
275
      dir( 0 ),
99✔
276
      gradual_boost( 0 ),
99✔
277
      lastpos( 0, 0, 0, nullptr ),
99✔
278
      move_reason( OTHER ),
99✔
279
      movemode( Plib::MOVEMODE_LAND ),
99✔
280
      // COMBAT
281
      warmode_wait( 0 ),
99✔
282
      ar_( 0 ),
99✔
283
      opponent_(),
99✔
284
      opponent_of(),
99✔
285
      swing_timer_start_clock_( 0 ),
99✔
286
      swing_task( nullptr ),
99✔
287
      // ATTRIBUTES / VITALS
288
      disable_regeneration_until( 0 ),
99✔
289
      attributes( Core::gamestate.numAttributes ),
99✔
290
      vitals( Core::gamestate.numVitals ),
99✔
291
      // REPUTATION
292
      aggressor_to_(),
99✔
293
      lawfully_damaged_(),
99✔
294
      criminal_until_( 0 ),
99✔
295
      repsys_task_( nullptr ),
99✔
296
      to_be_reportable_(),
99✔
297
      reportable_(),
99✔
298
      // GUILD
299
      // PARTY
300
      party_decline_timeout_( nullptr ),
99✔
301
      // SECURE TRADING
302
      trading_cont(),
99✔
303
      trading_with( nullptr ),
99✔
304
      // SCRIPT
305
      script_ex( nullptr ),
99✔
306
      spell_task( nullptr ),
99✔
307
      // CLIENT
308
      client( nullptr ),
99✔
309
      uclang( "enu" ),
99✔
310
      _last_textcolor( 0 ),
99✔
311
      // PRIVS SETTINGS STATUS
312
      cmdlevel_( 0 ),
99✔
313
      concealed_( 0 ),
99✔
314
      stealthsteps_( 0 ),
99✔
315
      mountedsteps_( 0 ),
99✔
316
      privs(),
99✔
317
      settings(),
99✔
318
      cached_settings(),
99✔
319
      mob_flags_(),
99✔
320
      // SERIALIZATION
321
      // CREATION
322
      created_at( 0 ),
99✔
323
      // BUFFS
324
      buffs_(),
99✔
325
      // MISC
326
      acct( nullptr ),
99✔
327
      registered_multi( 0 ),
99✔
328
      last_corpse( 0 ),
99✔
329
      trueobjtype( 0 ),
99✔
330
      truecolor( 0 ),
99✔
331
      // Note, Item uses the named constructor idiom, but here, it is not used.
332
      // this is probably okay, but something to keep in mind.
333
      gender( Plib::GENDER_MALE ),
99✔
334
      race( Plib::RACE_HUMAN ),
99✔
335
      position_changed_at_( 0 ),
99✔
336
      moved_at_( 0 )
99✔
337
{
338
  logged_in( true );  // so initialization scripts etc can see
99✔
339

340
  height = Core::settingsManager.ssopt
99✔
341
               .default_character_height;  // this gets overwritten in UObject::readProperties!
99✔
342
  wornitems->chr_owner = this;             // FIXME, dangerous.
99✔
343

344
  set_caps_to_default();
99✔
345

346
  // vector
347
  refresh_cached_settings( false );
99✔
348
  ++Core::stateManager.uobjcount.ucharacter_count;
99✔
349
}
99✔
350

351
Character::~Character()
120✔
352
{
353
  if ( acct.get() )
99✔
354
  {
355
    for ( int i = 0; i < Plib::systemstate.config.character_slots; i++ )
×
356
    {
357
      if ( acct->get_character( i ) == this )
×
358
      {
359
        acct->clear_character( i );
×
360
      }
361
    }
362
  }
363
  acct.clear();
99✔
364

365
  if ( client && ( client->chr == this ) )
99✔
366
    client->chr = nullptr;
×
367
  client = nullptr;
99✔
368

369
  // It might be nice to do this only when the system isn't shutting down...
370
  // if (!opponent_of.empty())
371
  //{
372
  //  Clib::Log( "Destroying character with nonempty opponent_of! (But cleaning up..)\n" );
373
  //}
374

375
  removal_cleanup();
99✔
376

377
  // clean up wornitems, so it can be reaped by the objecthash later
378
  wornitems->destroy();
99✔
379

380
  // clean up trade container if it exists
381
  if ( trading_cont != nullptr )
99✔
382
    trading_cont->destroy();
×
383

384
  if ( repsys_task_ != nullptr )
99✔
385
    repsys_task_->cancel();
2✔
386

387
  if ( party_decline_timeout_ != nullptr )
99✔
388
    party_decline_timeout_->cancel();
×
389

390
  --Core::stateManager.uobjcount.ucharacter_count;
99✔
391
}
120✔
392

393
void Character::removal_cleanup()
101✔
394
{
395
  clear_opponent_of();
101✔
396

397
  /* This used to be a call to set_opponent(nullptr),
398
     which was slick,
399
     but that was sending disengage events, which were
400
     trying to resurrect this object. (C++)
401
     */
402
  if ( opponent_ )
101✔
403
  {
NEW
404
    opponent_.remove_opponent_of( Attackable{ this } );
×
NEW
405
    opponent_.clear();
×
406
  }
407

408
  if ( swing_task != nullptr )
101✔
409
    swing_task->cancel();
×
410

411
  disconnect_cleanup();
101✔
412
}
101✔
413

414
void Character::disconnect_cleanup()
103✔
415
{
416
  if ( is_trading() )
103✔
417
    Core::cancel_trade( this );
×
418

419
  stop_skill_script();
103✔
420
  on_loggoff_party( this );
103✔
421
}
103✔
422

423
bool Character::logged_in() const
1,301✔
424
{
425
  return mob_flags_.get( MOB_FLAGS::LOGGED_IN );
1,301✔
426
}
427

428
void Character::logged_in( bool newvalue )
202✔
429
{
430
  mob_flags_.change( MOB_FLAGS::LOGGED_IN, newvalue );
202✔
431
}
202✔
432

433
bool Character::connected() const
×
434
{
435
  return mob_flags_.get( MOB_FLAGS::CONNECTED );
×
436
}
437

438
void Character::connected( bool newvalue )
82✔
439
{
440
  mob_flags_.change( MOB_FLAGS::CONNECTED, newvalue );
82✔
441
}
82✔
442

443
bool Character::has_active_client() const
50,888✔
444
{
445
  return ( client != nullptr && client->isActive() );
50,888✔
446
}
447

448
bool Character::has_active_prompt() const
×
449
{
450
  return ( client != nullptr && client->gd != nullptr &&
×
451
           ( client->gd->prompt_uniemod || client->gd->prompt_uoemod ) );
×
452
}
453

454
bool Character::has_active_gump() const
×
455
{
456
  return ( client != nullptr && client->gd != nullptr && !client->gd->gumpmods.empty() );
×
457
}
458

459
bool Character::has_active_textentry() const
×
460
{
461
  return ( client != nullptr && client->gd != nullptr && client->gd->textentry_uoemod != nullptr );
×
462
}
463

464
bool Character::is_house_editing() const
115✔
465
{
466
  return ( client != nullptr && client->gd != nullptr && client->gd->custom_house_serial != 0 );
115✔
467
}
468

469
void Character::clear_gotten_item()
76✔
470
{
471
  if ( !has_gotten_item() )
76✔
472
    return;
76✔
473
  auto info = gotten_item();
×
474
  if ( info.item() != nullptr )
×
475
  {
476
    gotten_item( {} );
×
477
    info.item()->inuse( false );
×
478
    if ( connected() )
×
479
      Core::send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN );
×
480
    info.undo( this );
×
481
  }
482
}
×
483

484
void Character::destroy()
99✔
485
{
486
  stop_skill_script();
99✔
487
  if ( registered_multi > 0 )
99✔
488
  {
489
    Multi::UMulti* multi = Core::system_find_multi( registered_multi );
×
490
    if ( multi != nullptr )
×
491
    {
492
      multi->unregister_object( (UObject*)this );
×
493
    }
494
    registered_multi = 0;
×
495
  }
496
  base::destroy();
99✔
497
}
99✔
498

499
void Character::stop_skill_script()
202✔
500
{
501
  if ( script_ex != nullptr )
202✔
502
  {
503
    if ( script_ex->survive_attached_disconnect )
×
504
    {
505
      /*
506
      We only want to resume those scripts that are paused indefinitely (eg. Target()).
507
      Scripts that will eventually resume (eg. Sleep()) or those that are in debugging
508
      state remain untouched.
509
      */
510
      if ( script_ex->in_hold_list() == Core::HoldListType::NOTIMEOUT_LIST )
×
511
      {
512
        script_ex->revive();
×
513
      }
514
    }
515
    else
516
    {
517
      // this will force the execution engine to stop running this script immediately
518
      // dont delete the executor here, since it could currently run
519
      script_ex->seterror( true );
×
520
      script_ex->revive();
×
521
      if ( script_ex->in_debugger_holdlist() )
×
522
        script_ex->revive_debugged();
×
523
    }
524
  }
525
}
202✔
526

527
///
528
/// A Mobile's weight is 10 stones + the weight of their equipment.
529
///
530
unsigned int Character::weight() const
100✔
531
{
532
  unsigned int wt = 10 + wornitems->weight();
100✔
533
  if ( has_gotten_item() )
100✔
534
    wt += gotten_item().item()->weight();
26✔
535
  if ( trading_cont.get() )
100✔
536
    wt += trading_cont->weight();
×
537
  return wt;
100✔
538
}
539

540
///
541
/// A Mobile's carrying capacity is (40 + 3.5*STR +
542
/// chr.carrying_capacity_mod)*ssopt.carrying_capacity_mod stones.
543
///
544
unsigned short Character::carrying_capacity() const
17✔
545
{
546
  return static_cast<u16>( floor( ( 40 + strength() * 7 / 2 + carrying_capacity_mod() ) *
17✔
547
                                  Core::settingsManager.ssopt.carrying_capacity_mod ) );
17✔
548
}
549

550
int Character::charindex() const
18✔
551
{
552
  if ( acct == nullptr )
18✔
553
    return -1;
×
554

555
  for ( int i = 0; i < Plib::systemstate.config.character_slots; i++ )
18✔
556
  {
557
    if ( acct->get_character( i ) == this )
18✔
558
      return i;
18✔
559
  }
560

561
  POLLOG_INFOLN( "Warning: Can't find charidx for Character {:#x}", serial );
×
562
  return -1;
×
563
}
564

565

566
void Character::printProperties( Clib::StreamWriter& sw ) const
22✔
567
{
568
  using namespace fmt;
569

570
  if ( acct != nullptr )
22✔
571
  {
572
    sw.add( "Account", acct->name() );
18✔
573
    sw.add( "CharIdx", charindex() );
18✔
574
  }
575

576
  base::printProperties( sw );
22✔
577

578
  if ( cmdlevel_ )
22✔
579
  {
580
    sw.add( "CmdLevel", Core::gamestate.cmdlevels[cmdlevel_].name );
2✔
581
  }
582
  if ( concealed_ )
22✔
583
  {
584
    sw.add( "Concealed", int( concealed_ ) );
2✔
585
  }
586
  sw.add( "TrueColor", Clib::hexintv( truecolor ) );
22✔
587
  sw.add( "TrueObjtype", Clib::hexintv( trueobjtype ) );
22✔
588

589
  if ( registered_multi )
22✔
590
    sw.add( "RegisteredMulti", Clib::hexintv( registered_multi ) );
2✔
591

592
  sw.add( "Gender", static_cast<int>( gender ) );
22✔
593
  sw.add( "Race", static_cast<int>( race ) );
22✔
594

595
  if ( dead() )
22✔
596
    sw.add( "Dead", dead() );
2✔
597

598
  if ( mountedsteps_ )
22✔
599
    sw.add( "MountedSteps", mountedsteps_ );
2✔
600

601
  if ( hidden() )
22✔
602
    sw.add( "Hidden", hidden() );
2✔
603

604
  if ( frozen() )
22✔
605
    sw.add( "Frozen", frozen() );
2✔
606

607
  if ( has_movement_cost() )
22✔
608
  {
609
    auto movecost_value = movement_cost();
2✔
610
    if ( movecost_value.walk != Core::MovementCostMod::DEFAULT.walk )
2✔
611
      sw.add( "MovementWalkMod", movecost_value.walk );
2✔
612
    if ( movecost_value.run != Core::MovementCostMod::DEFAULT.run )
2✔
613
      sw.add( "MovementRunMod", movecost_value.run );
2✔
614
    if ( movecost_value.walk_mounted != Core::MovementCostMod::DEFAULT.walk_mounted )
2✔
615
      sw.add( "MovementWalkMountedMod", movecost_value.walk_mounted );
2✔
616
    if ( movecost_value.run_mounted != Core::MovementCostMod::DEFAULT.run_mounted )
2✔
617
      sw.add( "MovementRunMountedMod", movecost_value.run_mounted );
2✔
618
  }
619
  if ( has_carrying_capacity_mod() )
22✔
620
    sw.add( "CarryingCapacityMod", carrying_capacity_mod() );
2✔
621

622

623
  // output Attributes
624
  for ( Attribute* pAttr : Core::gamestate.attributes )
1,166✔
625
  {
626
    const AttributeValue& av = attribute( pAttr->attrid );
1,144✔
627
    short lock = av.lock();
1,144✔
628
    unsigned cap = av.cap();
1,144✔
629

630
    if ( av.base() || lock ||
2,268✔
631
         cap != pAttr->default_cap )  // it kind of floods the file... but... :/ (Nando)
1,124✔
632
    {
633
      unsigned ones = av.base() / 10;
20✔
634
      unsigned tenths = av.base() % 10;
20✔
635

636
      auto val = fmt::format( FMT_COMPILE( "{}" ), ones );
60✔
637
      if ( tenths )
20✔
638
        fmt::format_to( std::back_inserter( val ), FMT_COMPILE( ".{}" ), tenths );
6✔
639
      if ( cap != pAttr->default_cap )
20✔
640
      {
641
        unsigned cap_ones = cap / 10;
4✔
642
        unsigned cap_tenths = cap % 10;
4✔
643
        fmt::format_to( std::back_inserter( val ), FMT_COMPILE( ":{}" ), cap_ones );
12✔
644
        if ( cap_tenths )
4✔
645
          fmt::format_to( std::back_inserter( val ), FMT_COMPILE( ".{}" ), cap_tenths );
6✔
646
      }
647
      if ( lock )
20✔
648
        fmt::format_to( std::back_inserter( val ), FMT_COMPILE( ";{}" ), lock );
12✔
649

650
      sw.add( pAttr->name, val );
20✔
651
    }
20✔
652
  }
653

654
  // output Vitals
655
  for ( Core::Vital* pVital : Core::gamestate.vitals )
88✔
656
  {
657
    const VitalValue& vv = vital( pVital->vitalid );
66✔
658
    if ( vv.current_ones() )
66✔
659
    {
660
      sw.add( pVital->name, vv.current_ones() );
12✔
661
    }
662
  }
663

664
  if ( has_skillstatcap() )
22✔
665
  {
666
    auto cap_value = skillstatcap();
2✔
667
    if ( cap_value.statcap != Core::SkillStatCap::DEFAULT.statcap )
2✔
668
      sw.add( "Statcap", cap_value.statcap );
2✔
669
    if ( cap_value.skillcap != Core::SkillStatCap::DEFAULT.skillcap )
2✔
670
      sw.add( "Skillcap", cap_value.skillcap );
2✔
671
  }
672

673
  if ( has_followers() )
22✔
674
  {
675
    auto followers_value = followers();
2✔
676
    if ( followers_value.followers_max != Core::ExtStatBarFollowers::DEFAULT.followers_max )
2✔
677
      sw.add( "FollowersMax", static_cast<int>( followers_value.followers_max ) );
2✔
678
    if ( followers_value.followers != Core::ExtStatBarFollowers::DEFAULT.followers )
2✔
679
      sw.add( "Followers", static_cast<int>( followers_value.followers ) );
2✔
680
  }
681
  if ( has_tithing() )
22✔
682
    sw.add( "Tithing", tithing() );
2✔
683

684

685
  if ( movemode != Plib::MOVEMODE_LAND )
22✔
686
    sw.add( "MoveMode", encode_movemode( movemode ) );
2✔
687

688
  if ( !privs.empty() )
22✔
689
  {
690
    sw.add( "Privs", privs.extract() );
2✔
691
  }
692
  if ( !settings.empty() )
22✔
693
  {
694
    sw.add( "Settings", settings.extract() );
2✔
695
  }
696

697
  sw.add( "CreatedAt", created_at );
22✔
698

699
  if ( has_squelched_until() )
22✔
700
    sw.add( "SquelchedUntil", squelched_until() );
2✔
701
  if ( has_deafened_until() )
22✔
702
    sw.add( "DeafenedUntil", deafened_until() );
2✔
703

704
  if ( has_title_prefix() )
22✔
705
    sw.add( "TitlePrefix", Clib::getencodedquotedstring( title_prefix() ) );
2✔
706
  if ( has_title_suffix() )
22✔
707
    sw.add( "TitleSuffix", Clib::getencodedquotedstring( title_suffix() ) );
2✔
708
  if ( has_title_guild() )
22✔
709
    sw.add( "TitleGuild", Clib::getencodedquotedstring( title_guild() ) );
2✔
710
  if ( has_title_race() )
22✔
711
    sw.add( "TitleRace", Clib::getencodedquotedstring( title_race() ) );
2✔
712

713
  if ( is_murderer() )
22✔
714
    sw.add( "Murderer", is_murderer() );
2✔
715
  if ( party_can_loot() )
22✔
716
    sw.add( "PartyCanLoot", party_can_loot() );
×
717
  for ( const auto& rt : reportable_ )
24✔
718
  {
719
    sw.add( "Reportable", fmt::format( FMT_COMPILE( "{:#x} {}" ), rt.serial, rt.polclock ) );
8✔
720
  }
721

722
  Core::UCorpse* corpse_obj = static_cast<Core::UCorpse*>( Core::system_find_item( last_corpse ) );
22✔
723
  if ( corpse_obj != nullptr && !corpse_obj->orphan() )
22✔
724
    sw.add( "LastCorpse", last_corpse );
2✔
725
}
22✔
726

727
void Character::printDebugProperties( Clib::StreamWriter& sw ) const
×
728
{
729
  base::printDebugProperties( sw );
×
730
}
×
731

732
const char* Character::classname() const
18✔
733
{
734
  return "Character";
18✔
735
}
736

737
void Character::printSelfOn( Clib::StreamWriter& sw ) const
×
738
{
739
  base::printOn( sw );
×
740
}
×
741

742
void Character::printOn( Clib::StreamWriter& sw ) const
18✔
743
{
744
  base::printOn( sw );
18✔
745
}
18✔
746
void Character::printWornItems( Clib::StreamWriter& sw_pc, Clib::StreamWriter& sw_equip ) const
22✔
747
{
748
  wornitems->print( sw_pc, sw_equip );
22✔
749
}
22✔
750

751
Plib::MOVEMODE Character::decode_movemode( const std::string& str )
171✔
752
{
753
  Plib::MOVEMODE mm = Plib::MOVEMODE_NONE;
171✔
754

755
  const auto not_found = std::string::npos;
171✔
756
  if ( str.find( 'L' ) != not_found )
171✔
757
    mm = static_cast<Plib::MOVEMODE>( mm + Plib::MOVEMODE_LAND );
168✔
758
  if ( str.find( 'S' ) != not_found )
171✔
759
    mm = static_cast<Plib::MOVEMODE>( mm + Plib::MOVEMODE_SEA );
3✔
760
  if ( str.find( 'A' ) != not_found )
171✔
761
    mm = static_cast<Plib::MOVEMODE>( mm + Plib::MOVEMODE_AIR );
2✔
762
  if ( str.find( 'F' ) != not_found )
171✔
763
    mm = static_cast<Plib::MOVEMODE>( mm + Plib::MOVEMODE_FLY );
×
764
  return mm;
171✔
765
}
766

767
std::string Character::encode_movemode( Plib::MOVEMODE mm )
2✔
768
{
769
  std::string res;
2✔
770
  if ( mm & Plib::MOVEMODE_LAND )
2✔
771
    res += "L";
×
772
  if ( mm & Plib::MOVEMODE_SEA )
2✔
773
    res += "S";
×
774
  if ( mm & Plib::MOVEMODE_AIR )
2✔
775
    res += "A";
2✔
776
  if ( mm & Plib::MOVEMODE_FLY )
2✔
777
    res += "F";
×
778
  return res;
2✔
779
}
×
780

781
void Character::readCommonProperties( Clib::ConfigElem& elem )
87✔
782
{
783
  serial = elem.remove_ulong( "SERIAL" );
87✔
784

785
  if ( Plib::systemstate.config.check_integrity )
87✔
786
  {
787
    if ( Core::system_find_mobile( serial ) )
87✔
788
    {
789
      ERROR_PRINTLN( "Character {:#x} defined more than once.", serial );
×
790
      throw std::runtime_error( "Data integrity error" );
×
791
    }
792
  }
793
  serial_ext = ctBEu32( serial );
87✔
794
  Core::UseCharSerialNumber( serial );
87✔
795

796
  std::string acctname;
87✔
797
  if ( elem.remove_prop( "ACCOUNT", &acctname ) )
87✔
798
  {
799
    unsigned short charindex;
800
    charindex = elem.remove_ushort( "CHARIDX" );
9✔
801

802
    if ( charindex >= Plib::systemstate.config.character_slots )
9✔
803
    {
804
      ERROR_PRINTLN( "Account {}: CHARIDX of {} is too high for character serial ({:#x})", acctname,
×
805
                     charindex, serial );
×
806

807
      throw std::runtime_error( "Data integrity error" );
×
808
    }
809
    Accounts::Account* search_acct = Accounts::find_account( acctname.c_str() );
9✔
810
    if ( search_acct == nullptr )
9✔
811
    {
812
      ERROR_PRINTLN( "Character '{}': Account '{}' doesn't exist.", name(), acctname );
×
813
      throw std::runtime_error( "Data integrity error" );
×
814
    }
815
    if ( search_acct->get_character( charindex ) != nullptr )
9✔
816
    {
817
      ERROR_PRINTLN( "Account {} has two characters with CHARIDX of {}", acctname, charindex );
×
818
      throw std::runtime_error( "Data integrity error" );
×
819
    }
820

821
    acct.set( search_acct );
9✔
822
    acct->set_character( charindex, this );
9✔
823
  }
824

825
  trueobjtype = elem.remove_unsigned( "TRUEOBJTYPE", objtype_ );  // dave 1/30/3
87✔
826
  graphic = static_cast<u16>( objtype_ );
87✔
827

828
  registered_multi = elem.remove_ulong( "REGISTEREDMULTI", 0 );
87✔
829
  if ( registered_multi == 0 )
87✔
830
    registered_multi = elem.remove_ulong( "REGISTEREDHOUSE", 0 );
86✔
831

832
  base::readProperties( elem );
87✔
833

834
  if ( name_ == "" )
87✔
835
  {
836
    ERROR_PRINTLN( "Character '{:#x}' has no name!", serial );
×
837
    throw std::runtime_error( "Data integrity error" );
×
838
  }
839
  wornitems->serial = serial;
87✔
840
  wornitems->serial_ext = serial_ext;
87✔
841
  position_changed();
87✔
842

843
  std::string cmdaccstr = elem.remove_string( "CMDLEVEL", "player" );
87✔
844
  Core::CmdLevel* cmdlevel_search = Core::find_cmdlevel( cmdaccstr.c_str() );
87✔
845
  if ( cmdlevel_search == nullptr )
87✔
846
    elem.throw_error( "Didn't understand cmdlevel of '" + cmdaccstr + "'" );
×
847
  cmdlevel_ = cmdlevel_search->cmdlevel;
87✔
848

849
  movemode = decode_movemode( elem.remove_string( "MOVEMODE", "L" ) );
87✔
850
  concealed_ = static_cast<unsigned char>( elem.remove_ushort(
87✔
851
      "CONCEALED", 0 ) );  // DAVE changed from remove_bool 11/25. concealed is a char, not a bool!
852
  //  if (concealed_ > cmdlevel)
853
  //    concealed_ = cmdlevel;
854

855
  truecolor = elem.remove_ushort( "TRUECOLOR" );
87✔
856

857
  mountedsteps_ = elem.remove_ulong( "MOUNTEDSTEPS", 0 );
87✔
858

859
  gender = static_cast<Plib::UGENDER>( elem.remove_ushort( "GENDER" ) );
87✔
860
  race = static_cast<Plib::URACE>( elem.remove_ushort( "RACE", Plib::RACE_HUMAN ) );
87✔
861

862
  if ( elem.remove_bool( "DEAD", false ) )
87✔
863
    mob_flags_.set( MOB_FLAGS::DEAD );
1✔
864
  if ( elem.remove_bool( "HIDDEN", false ) )
87✔
865
    mob_flags_.set( MOB_FLAGS::HIDDEN );
1✔
866
  if ( elem.remove_bool( "FROZEN", false ) )
87✔
867
    mob_flags_.set( MOB_FLAGS::FROZEN );
1✔
868

869
  movement_cost( Core::MovementCostMod(
87✔
870
      elem.remove_double( "MovementWalkMod", Core::MovementCostMod::DEFAULT.walk ),
87✔
871
      elem.remove_double( "MovementRunMod", Core::MovementCostMod::DEFAULT.run ),
87✔
872
      elem.remove_double( "MovementWalkMountedMod", Core::MovementCostMod::DEFAULT.walk_mounted ),
87✔
873
      elem.remove_double( "MovementRunMountedMod", Core::MovementCostMod::DEFAULT.run_mounted ) ) );
87✔
874

875
  carrying_capacity_mod( static_cast<s16>( elem.remove_int( "CarryingCapacityMod", 0 ) ) );
87✔
876

877
  height = Core::settingsManager.ssopt.default_character_height;  // no really, height is 9
87✔
878

879
  created_at = elem.remove_ulong( "CreatedAt", 0 );
87✔
880
  squelched_until( elem.remove_ulong( "SquelchedUntil", 0 ) );
87✔
881
  deafened_until( elem.remove_ulong( "DeafenedUntil", 0 ) );
87✔
882

883
  title_prefix( elem.remove_string( "TITLEPREFIX", "" ) );
87✔
884
  title_suffix( elem.remove_string( "TITLESUFFIX", "" ) );
87✔
885
  title_guild( elem.remove_string( "TITLEGUILD", "" ) );
87✔
886
  title_race( elem.remove_string( "TITLERACE", "" ) );
87✔
887

888
  unsigned int tmp_guildid;
889
  if ( elem.remove_prop( "GUILDID", &tmp_guildid ) )
87✔
890
    guild( Core::Guild::FindOrCreateGuild( tmp_guildid, serial ) );
×
891

892
  if ( elem.remove_bool( "MURDERER", false ) )
87✔
893
    mob_flags_.set( MOB_FLAGS::MURDERER );
1✔
894
  if ( elem.remove_bool( "PARTYCANLOOT", false ) )
87✔
895
    mob_flags_.set( MOB_FLAGS::PARTY_CAN_LOOT );
×
896

897
  std::string rt;
87✔
898
  while ( elem.remove_prop( "REPORTABLE", &rt ) )
88✔
899
  {
900
    ISTRINGSTREAM is( rt );
1✔
901
    reportable_t rt_t;
902
    if ( is >> std::hex >> rt_t.serial >> std::dec >> rt_t.polclock )
1✔
903
    {
904
      reportable_.insert( rt_t );
1✔
905
    }
906
  }
1✔
907

908
  uclang = elem.remove_string( "UCLang", "enu" );
87✔
909
  skillstatcap( Core::SkillStatCap(
87✔
910
      static_cast<s16>( elem.remove_int( "STATCAP", Core::SkillStatCap::DEFAULT.statcap ) ),
87✔
911
      static_cast<u16>( elem.remove_int( "SKILLCAP", Core::SkillStatCap::DEFAULT.skillcap ) ) ) );
87✔
912
  followers( Core::ExtStatBarFollowers(
87✔
913
      static_cast<s8>(
914
          elem.remove_int( "FOLLOWERS", Core::ExtStatBarFollowers::DEFAULT.followers ) ),
87✔
915
      static_cast<s8>(
916
          elem.remove_int( "FOLLOWERSMAX", Core::ExtStatBarFollowers::DEFAULT.followers_max ) ) ) );
87✔
917
  tithing( elem.remove_int( "TITHING", 0 ) );
87✔
918

919
  privs.readfrom( elem.remove_string( "Privs", "" ) );
87✔
920
  settings.readfrom( elem.remove_string( "Settings", "" ) );
87✔
921
  refresh_cached_settings();
87✔
922
}
87✔
923

924
// these are read here to allow NPCs to have "die-roll" type values
925
void Character::readAttributesAndVitals( Clib::ConfigElem& elem )
11✔
926
{
927
  // read Attributes
928
  for ( Attribute* pAttr : Core::gamestate.attributes )
583✔
929
  {
930
    AttributeValue& av = attribute( pAttr->attrid );
572✔
931

932
    for ( unsigned i = 0; i < pAttr->aliases.size(); ++i )
1,159✔
933
    {
934
      std::string temp;
597✔
935
      if ( elem.remove_prop( pAttr->aliases[i].c_str(), &temp ) )
597✔
936
      {
937
        // read
938
        unsigned int base;
939
        unsigned int cap = pAttr->default_cap;
10✔
940
        unsigned char lock = 0;
10✔
941
        if ( Core::settingsManager.polvar.DataWrittenBy93 &&
10✔
942
             Core::stateManager.gflag_in_system_load )
×
943
        {
944
          unsigned int raw = strtoul( temp.c_str(), nullptr, 10 );
×
945
          base = Core::raw_to_base( raw );
×
946
        }
×
947
        else
948
        {
949
          if ( Core::settingsManager.polvar.DataWrittenBy.empty() &&
10✔
950
               Core::stateManager.gflag_in_system_load )
×
951
          {
952
            elem.throw_error( "Pol.txt 'System' element needs to specify CoreVersion" );
×
953
          }
954

955
          const char* pval = temp.c_str();
10✔
956
          char* pdot = nullptr;
10✔
957
          unsigned int ones = strtoul( pval, &pdot, 10 );
10✔
958
          unsigned int tenths = 0;
10✔
959
          if ( pdot && *pdot == '.' )
10✔
960
            tenths = strtoul( pdot + 1, &pdot, 10 );
1✔
961
          base = ones * 10 + tenths;
10✔
962

963
          // Do we have caps?
964
          if ( pdot && *pdot == ':' )
10✔
965
          {
966
            unsigned int cap_ones = strtoul( pdot + 1, &pdot, 10 );
2✔
967
            unsigned int cap_tenths = 0;
2✔
968

969
            // Tenths in cap?
970
            if ( pdot && *pdot == '.' )
2✔
971
              cap_tenths = strtoul( pdot + 1, &pdot, 10 );
1✔
972

973
            cap = cap_ones * 10 + cap_tenths;
2✔
974
          }
975

976
          if ( pdot && *pdot == ';' )
10✔
977
            lock = (unsigned char)strtoul( pdot + 1, nullptr, 10 );
2✔
978
        }
979
        if ( /*base < ATTRIBUTE_MIN_BASE ||*/  // ATTRIBUTE_MIN_BASE is currently 0
10✔
980
             base > ATTRIBUTE_MAX_BASE )
981
          elem.throw_error( "Character " + Clib::hexint( serial ) + " attribute " +
×
982
                            pAttr->aliases[i] + " is out of range" );
×
983
        av.base( static_cast<unsigned short>( base ) );
10✔
984

985
        if ( /*cap < ATTRIBUTE_MIN_BASE ||*/  // ATTRIBUTE_MIN_BASE is currently 0
10✔
986
             cap > ATTRIBUTE_MAX_BASE )
987
          elem.throw_error( "Character " + Clib::hexint( serial ) + " attribute cap from " +
×
988
                            pAttr->aliases[i] + " is out of range" );
×
989
        av.cap( static_cast<unsigned short>( cap ) );
10✔
990

991

992
        if ( lock > 2 )  // FIXME: HardCoded Value
10✔
993
          elem.throw_error( "Character " + Clib::hexint( serial ) + " attribute " +
×
994
                            pAttr->aliases[i] + " has illegal lock state!" );
×
995

996
        av.lock( lock );
10✔
997

998
        break;
10✔
999
      }
1000
    }
597✔
1001
  }
1002

1003
  calc_vital_stuff();
11✔
1004

1005
  // read Vitals
1006
  for ( Core::Vital* pVital : Core::gamestate.vitals )
44✔
1007
  {
1008
    VitalValue& vv = vital( pVital->vitalid );
33✔
1009
    for ( const auto& _i : pVital->aliases )
69✔
1010
    {
1011
      unsigned int temp;
1012
      if ( elem.remove_prop( _i.c_str(), &temp ) )
42✔
1013
      {
1014
        // read
1015
        // these are always just stored as points
1016
        if ( /*temp < Core::VITAL_MIN_VALUE ||*/  // VITAL_MIN_VALUE is currently 0
6✔
1017
             temp > Core::VITAL_MAX_VALUE )
6✔
1018
          elem.throw_error( "Character " + Clib::hexint( serial ) + " vital " + _i +
×
1019
                            " is out of range" );
1020
        vv.current_ones( temp );
6✔
1021
        break;
6✔
1022
      }
1023
    }
1024
  }
1025
}
11✔
1026

1027
void Character::readProperties( Clib::ConfigElem& elem )
11✔
1028
{
1029
  readCommonProperties( elem );
11✔
1030
  readAttributesAndVitals( elem );
11✔
1031

1032
  last_corpse = elem.remove_ulong( "LastCorpse", 0 );
11✔
1033
}
11✔
1034

1035

1036
bool Character::has_privilege( const char* priv ) const
6✔
1037
{
1038
  return privs.contains( priv ) || privs.contains( "all" );
6✔
1039
}
1040

1041
bool Character::setting_enabled( const char* setting ) const
4,036✔
1042
{
1043
  return cached_settings.get( PRIV_FLAGS::ALL ) || settings.contains( setting );
4,036✔
1044
}
1045

1046
void Character::refresh_cached_settings( bool update )
192✔
1047
{
1048
  PrivUpdater invulwatch( update ? this : nullptr, PRIV_FLAGS::INVUL );
192✔
1049
  PrivUpdater seeghostswatch( update ? this : nullptr, PRIV_FLAGS::SEE_GHOSTS );
192✔
1050
  PrivUpdater seehiddenwatch( update ? this : nullptr, PRIV_FLAGS::SEE_HIDDEN );
192✔
1051
  PrivUpdater seeinvisitemswatch( update ? this : nullptr, PRIV_FLAGS::SEE_INVIS_ITEMS );
192✔
1052

1053
  cached_settings.reset();
192✔
1054

1055
  if ( setting_enabled( "all" ) )
192✔
1056
  {
1057
    cached_settings.set( PRIV_FLAGS::ALL );
×
1058
    return;
×
1059
  }
1060
  if ( setting_enabled( "clotheany" ) )
192✔
1061
    cached_settings.set( PRIV_FLAGS::CLOTHE_ANY );
1✔
1062
  if ( setting_enabled( "dblclickany" ) )
192✔
1063
    cached_settings.set( PRIV_FLAGS::DBLCLICK_ANY );
×
1064
  if ( setting_enabled( "hearghosts" ) )
192✔
1065
    cached_settings.set( PRIV_FLAGS::HEAR_GHOSTS );
×
1066
  if ( setting_enabled( "invul" ) )
192✔
1067
    cached_settings.set( PRIV_FLAGS::INVUL );
72✔
1068
  if ( setting_enabled( "losany" ) )
192✔
1069
    cached_settings.set( PRIV_FLAGS::LOS_ANY );
×
1070
  if ( setting_enabled( "moveany" ) )
192✔
1071
    cached_settings.set( PRIV_FLAGS::MOVE_ANY );
×
1072
  if ( setting_enabled( "renameany" ) )
192✔
1073
    cached_settings.set( PRIV_FLAGS::RENAME_ANY );
×
1074
  if ( setting_enabled( "seeghosts" ) )
192✔
1075
    cached_settings.set( PRIV_FLAGS::SEE_GHOSTS );
×
1076
  if ( setting_enabled( "seehidden" ) )
192✔
1077
    cached_settings.set( PRIV_FLAGS::SEE_HIDDEN );
×
1078
  if ( setting_enabled( "seeinvisitems" ) )
192✔
1079
    cached_settings.set( PRIV_FLAGS::SEE_INVIS_ITEMS );
1✔
1080
  if ( setting_enabled( "ignoredoors" ) )
192✔
1081
    cached_settings.set( PRIV_FLAGS::IGNORE_DOORS );
×
1082
  if ( setting_enabled( "freemove" ) )
192✔
1083
    cached_settings.set( PRIV_FLAGS::FREEMOVE );
×
1084
  if ( setting_enabled( "firewhilemoving" ) )
192✔
1085
    cached_settings.set( PRIV_FLAGS::FIRE_WHILE_MOVING );
×
1086
  if ( setting_enabled( "attackhidden" ) )
192✔
1087
    cached_settings.set( PRIV_FLAGS::ATTACK_HIDDEN );
×
1088
  if ( setting_enabled( "hiddenattack" ) )
192✔
1089
    cached_settings.set( PRIV_FLAGS::HIDDEN_ATTACK );
2✔
1090
  if ( setting_enabled( "plogany" ) )
192✔
1091
    cached_settings.set( PRIV_FLAGS::PLOG_ANY );
×
1092
  if ( setting_enabled( "moveanydist" ) )
192✔
1093
    cached_settings.set( PRIV_FLAGS::MOVE_ANY_DIST );
×
1094
  if ( setting_enabled( "canbeheardasghost" ) )
192✔
1095
    cached_settings.set( PRIV_FLAGS::CAN_BE_HEARD_AS_GHOST );
×
1096
  if ( setting_enabled( "runwhilestealth" ) )
192✔
1097
    cached_settings.set( PRIV_FLAGS::RUN_WHILE_STEALTH );
×
1098
  if ( setting_enabled( "speedhack" ) )
192✔
1099
    cached_settings.set( PRIV_FLAGS::SPEEDHACK );
×
1100
}
192✔
1101

1102
void Character::set_setting( const char* setting, bool value )
5✔
1103
{
1104
  set_dirty();
5✔
1105
  if ( value == false )
5✔
1106
    settings.remove( setting );
2✔
1107
  else if ( has_privilege( setting ) )
3✔
1108
    settings.add( setting );
3✔
1109

1110
  refresh_cached_settings();
5✔
1111
}
5✔
1112

1113
std::string Character::all_settings() const
×
1114
{
1115
  return settings.extract();
×
1116
}
1117

1118
std::string Character::all_privs() const
1✔
1119
{
1120
  return privs.extract();
1✔
1121
}
1122

1123
void Character::set_privs( const std::string& privlist )
×
1124
{
1125
  set_dirty();
×
1126
  privs.readfrom( privlist );
×
1127
}
×
1128

1129
void Character::grant_privilege( const char* priv )
4✔
1130
{
1131
  if ( !privs.contains( priv ) )
4✔
1132
  {
1133
    set_dirty();
4✔
1134
    privs.add( priv );
4✔
1135
  }
1136
}
4✔
1137

1138
void Character::revoke_privilege( const char* priv )
1✔
1139
{
1140
  set_dirty();
1✔
1141
  privs.remove( priv );
1✔
1142
  settings.remove( priv );
1✔
1143
  refresh_cached_settings();
1✔
1144
}
1✔
1145

1146
bool Character::can_access( const Items::Item* item, int range ) const
×
1147
{
1148
  // TODO: find_legal_item() is awful, we should just check the item
1149
  //       properties directly instead of going around searching for a given serial in the world
1150

1151
  // Range < 0 has special meaning. -1 is the default accessible range,
1152
  // anything smaller ignores the range check.
1153
  if ( range == -1 )
×
1154
    range = Core::settingsManager.ssopt.default_accessible_range;
×
1155

1156
  const bool within_range =
1157
      ( range < -1 ) || item->in_range( this, Clib::clamp_convert<u16>( range ) );
×
1158
  if ( within_range && ( find_legal_item( this, item->serial ) != nullptr ) )
×
1159
    return true;
×
1160

1161
  return false;
×
1162
}
1163

1164
bool Character::can_move( const Items::Item* item ) const
6,143✔
1165
{
1166
  if ( item->objtype_ != UOBJ_CORPSE )
6,143✔
1167
  {
1168
    return cached_settings.get( PRIV_FLAGS::MOVE_ANY ) || item->movable();
6,111✔
1169
  }
1170

1171
  return false;
32✔
1172
}
1173

1174
bool Character::can_be_renamed_by( const Character* /*chr*/ ) const
×
1175
{
1176
  // consider command levels?
1177
  return false;
×
1178
}
1179

1180
bool Character::can_be_clothed_by( const Character* chr ) const
×
1181
{
1182
  return chr == this;
×
1183
}
1184

1185
bool Character::can_rename( const Character* chr ) const
×
1186
{
1187
  return cached_settings.get( PRIV_FLAGS::RENAME_ANY ) || chr->can_be_renamed_by( this );
×
1188
}
1189

1190
bool Character::can_clothe( const Character* chr ) const
10✔
1191
{
1192
  return cached_settings.get( PRIV_FLAGS::CLOTHE_ANY ) || chr->can_be_clothed_by( this );
10✔
1193
}
1194

1195
bool Character::can_hearghosts() const
×
1196
{
1197
  return cached_settings.get( PRIV_FLAGS::HEAR_GHOSTS );
×
1198
}
1199

1200
bool Character::can_be_heard_as_ghost() const
×
1201
{
1202
  return cached_settings.get( PRIV_FLAGS::CAN_BE_HEARD_AS_GHOST );
×
1203
}
1204

1205
bool Character::can_moveanydist() const
17✔
1206
{
1207
  return cached_settings.get( PRIV_FLAGS::MOVE_ANY_DIST );
17✔
1208
}
1209

1210
bool Character::can_plogany() const
×
1211
{
1212
  return cached_settings.get( PRIV_FLAGS::PLOG_ANY );
×
1213
}
1214

1215
bool Character::can_speedhack() const
×
1216
{
1217
  return cached_settings.get( PRIV_FLAGS::SPEEDHACK );
×
1218
}
1219

1220
bool Character::can_freemove() const
62✔
1221
{
1222
  return cached_settings.get( PRIV_FLAGS::FREEMOVE );
62✔
1223
}
1224

1225
Core::UContainer* Character::backpack() const
240✔
1226
{
1227
  return static_cast<Core::UContainer*>( wornitems->GetItemOnLayer( Core::LAYER_BACKPACK ) );
240✔
1228
}
1229

1230
Core::Spellbook* Character::spellbook( u8 school ) const
×
1231
{
1232
  Items::Item* _item = wornitem( Core::LAYER_HAND1 );
×
1233
  if ( _item != nullptr && _item->script_isa( Core::POLCLASS_SPELLBOOK ) )
×
1234
  {
1235
    Core::Spellbook* book = static_cast<Core::Spellbook*>( _item );
×
1236
    if ( book->spell_school == school )
×
1237
      return book;
×
1238
  }
1239

1240
  Core::UContainer* cont = backpack();
×
1241
  if ( cont != nullptr )
×
1242
  {
1243
    for ( auto item : *cont )
×
1244
    {
1245
      if ( item != nullptr && item->script_isa( Core::POLCLASS_SPELLBOOK ) )
×
1246
      {
1247
        const Core::Spellbook* book = static_cast<const Core::Spellbook*>( item );
×
1248
        if ( book->spell_school == school )
×
1249
          return const_cast<Core::Spellbook*>( book );
×
1250
      }
1251
    }
1252
  }
1253
  return nullptr;
×
1254
}
1255

1256
unsigned int Character::gold_carried() const
81✔
1257
{
1258
  Core::UContainer* bp = backpack();
81✔
1259
  if ( bp != nullptr )
81✔
1260
    return bp->find_sumof_objtype_noninuse( UOBJ_GOLD_COIN );
81✔
1261
  return 0;
×
1262
}
1263

1264
// TODO: This could be more efficient, by inlining 'spend' logic
1265
// in a recursive function
1266

1267
void Character::spend_gold( unsigned int amount )
×
1268
{
1269
  passert( gold_carried() >= amount );
×
1270

1271
  Core::UContainer* bp = backpack();
×
1272
  if ( bp != nullptr )
×
1273
    bp->consume_sumof_objtype_noninuse( UOBJ_GOLD_COIN, amount );
×
1274
  if ( client != nullptr )
×
1275
    send_full_statmsg( client, this );
×
1276
}
×
1277

1278
Items::Item* Character::wornitem( int layer ) const
6,120✔
1279
{
1280
  return wornitems->GetItemOnLayer( layer );
6,120✔
1281
}
1282

1283
bool Character::layer_is_equipped( int layer ) const
123✔
1284
{
1285
  return ( wornitems->GetItemOnLayer( layer ) != nullptr );
123✔
1286
}
1287

1288
bool Character::is_equipped( const Items::Item* item ) const
90✔
1289
{
1290
  if ( !Items::valid_equip_layer( item ) )
90✔
1291
    return false;
2✔
1292

1293
  return ( wornitems->GetItemOnLayer( item->tile_layer ) == item );
88✔
1294
}
1295

1296
bool Character::strong_enough_to_equip( const Items::Item* item ) const
1✔
1297
{
1298
  const Items::ItemDesc& desc = item->itemdesc();
1✔
1299
  return ( attribute( Core::gamestate.pAttrStrength->attrid ).base() >= desc.base_str_req );
1✔
1300
}
1301

1302
bool Character::equippable( const Items::Item* item ) const
65✔
1303
{
1304
  if ( !Items::valid_equip_layer( item ) )
65✔
1305
  {
1306
    if ( item->objtype_ == Core::settingsManager.extobj.mount )
1✔
1307
    {
1308
      POLLOG_INFOLN(
×
1309
          "\n"
1310
          "Warning: Character {:#x} tried to mount Item {:#x}, but it doesn't have a mount "
1311
          "graphic (current graphic: {:#x}). Check that the list of mounts in uoconvert.cfg is "
1312
          "correct and re-run uoconvert if necessary.",
1313
          this->serial, item->serial, item->graphic );
×
1314
    }
1315

1316
    if ( item->objtype_ != Core::settingsManager.extobj.boatmount )
1✔
1317
    {
1318
      return false;
1✔
1319
    }
1320
  }
1321
  if ( layer_is_equipped( item->tile_layer ) )
64✔
1322
  {
1323
    return false;
×
1324
  }
1325
  if ( ( item->tile_layer == Core::LAYER_BACKPACK ) &&
104✔
1326
       !item->isa( Core::UOBJ_CLASS::CLASS_CONTAINER ) )
40✔
1327
  {
1328
    return false;
×
1329
  }
1330

1331
  // Only allow mounts if they have the mount objtype as defined in extobj.cfg
1332
  if ( item->tile_layer == Core::LAYER_MOUNT && Plib::systemstate.config.enforce_mount_objtype &&
64✔
1333
       item->objtype_ != Core::settingsManager.extobj.mount )
×
1334
  {
1335
    POLLOG_INFOLN(
×
1336
        "\n"
1337
        "Warning: Character {:#x} tried to mount Item {:#x}, but it doesn't have the mount "
1338
        "objtype (as defined in extobj.cfg) and EnforceMountObjtype in pol.cfg is true.",
1339
        this->serial, item->serial );
×
1340
    return false;
×
1341
  }
1342

1343
  if ( item->objtype_ != Core::settingsManager.extobj.boatmount )
64✔
1344
  {
1345
    if ( ~Plib::tile_flags( item->graphic ) & Plib::FLAG::EQUIPPABLE )
58✔
1346
    {
1347
      return false;
×
1348
    }
1349
    // redundant sanity check
1350
    if ( Plib::tilelayer( item->graphic ) != item->tile_layer )
58✔
1351
    {
1352
      return false;
×
1353
    }
1354
  }
1355

1356
  const Items::ItemDesc& desc = item->itemdesc();
64✔
1357
  if ( attribute( Core::gamestate.pAttrStrength->attrid ).base() < desc.base_str_req )
64✔
1358
  {
1359
    return false;
×
1360
  }
1361

1362
  if ( item->tile_layer == Core::LAYER_HAND1 || item->tile_layer == Core::LAYER_HAND2 )
64✔
1363
  {
1364
    if ( weapon != nullptr )
×
1365
    {
1366
      if ( weapon->descriptor().two_handed && !weapon->is_intrinsic() )
×
1367
      {
1368
        return false;
×
1369
      }
1370
    }
1371
    if ( item->isa( Core::UOBJ_CLASS::CLASS_WEAPON ) )
×
1372
    {
1373
      const Items::UWeapon* wpn_item = static_cast<const Items::UWeapon*>( item );
×
1374
      if ( wpn_item->descriptor().two_handed )
×
1375
      {
1376
        if ( layer_is_equipped( Core::LAYER_HAND1 ) || layer_is_equipped( Core::LAYER_HAND2 ) )
×
1377
        {
1378
          return false;
×
1379
        }
1380
      }
1381
    }
1382
  }
1383

1384
  return true;
64✔
1385
}
1386

1387
void Character::equip( Items::Item* item )
32✔
1388
{
1389
  passert_r( equippable( item ),
32✔
1390
             "It is impossible to equip Item with ObjType " + Clib::hexint( item->objtype_ ) );
1391

1392
  item->setposition( pos() );  // TODO POS realm should be nullptr
32✔
1393
  wornitems->PutItemOnLayer( item );
32✔
1394

1395
  // PutItemOnLayer sets the layer, so we can go on now
1396
  // checking item->layer instead of item->tile_layer
1397
  if ( item->isa( Core::UOBJ_CLASS::CLASS_WEAPON ) &&
32✔
1398
       ( item->layer == Core::LAYER_HAND1 || item->layer == Core::LAYER_HAND2 ) )
×
1399
  {
1400
    weapon = static_cast<Items::UWeapon*>( item );
×
1401
    reset_swing_timer();
×
1402
  }
1403
  else if ( item->isa( Core::UOBJ_CLASS::CLASS_ARMOR ) )
32✔
1404
  {
1405
    if ( item->layer == Core::LAYER_HAND1 || item->layer == Core::LAYER_HAND2 )
1✔
1406
    {
1407
      shield = static_cast<Items::UArmor*>( item );
×
1408
    }
1409
    reset_swing_timer();
1✔
1410
  }
1411
  refresh_ar();
32✔
1412
}
32✔
1413

1414
Items::UWeapon* Character::intrinsic_weapon()
×
1415
{
1416
  return Core::gamestate.wrestling_weapon;
×
1417
}
1418

1419
void Character::unequip( Items::Item* item )
25✔
1420
{
1421
  passert( Items::valid_equip_layer( item ) );
25✔
1422
  // assume any item being de-equipped is in fact being worn.
1423
  passert( item->container == wornitems.get() );
25✔
1424
  passert( is_equipped( item ) );
25✔
1425

1426
  wornitems->RemoveItemFromLayer( item );
25✔
1427

1428
  if ( item == weapon )
25✔
1429
  {
1430
    weapon = intrinsic_weapon();
×
1431
    /* we don't reset the swing timer here, 'cause
1432
       you can switch from weapon to H2H quickly.
1433
       Note, this _could_ let you use a faster weapon
1434
       to wrestle faster, hmm.
1435
       */
1436
  }
1437
  else if ( item == shield )
25✔
1438
  {
1439
    shield = nullptr;
×
1440
    reset_swing_timer();
×
1441
  }
1442
  refresh_ar();
25✔
1443
}
25✔
1444

1445
bool Character::on_mount() const
58✔
1446
{
1447
  if ( race == Plib::RACE_GARGOYLE )
58✔
1448
    return ( movemode & Plib::MOVEMODE_FLY ) == 0 ? false : true;
×
1449

1450
  return layer_is_equipped( Core::LAYER_MOUNT );
58✔
1451
}
1452

1453

1454
Items::Item* Character::find_wornitem( u32 find_serial ) const
44✔
1455
{
1456
  for ( unsigned layer = Core::LAYER_EQUIP__LOWEST; layer <= Core::LAYER_EQUIP__HIGHEST; layer++ )
1,144✔
1457
  {
1458
    Items::Item* item = wornitems->GetItemOnLayer( layer );
1,100✔
1459
    if ( item )
1,100✔
1460
    {
1461
      if ( item->serial == find_serial )
44✔
1462
        return item;
×
1463
      // 4/2007 - MuadDib
1464
      // Added cont check and using cont->find to check here
1465
      // for equipped cont items like the ML added Quivers.
1466
      // Using redundant null check.
1467
      if ( item != nullptr && item->script_isa( Core::POLCLASS_CONTAINER ) )
44✔
1468
      {
1469
        if ( layer != Core::LAYER_HAIR && layer != Core::LAYER_FACE && layer != Core::LAYER_BEARD &&
44✔
1470
             layer != Core::LAYER_BACKPACK && layer != Core::LAYER_MOUNT )
×
1471
        {
1472
          Core::UContainer* cont = static_cast<Core::UContainer*>( item );
×
1473
          item = nullptr;
×
1474
          item = cont->find( find_serial );
×
1475
          if ( item != nullptr )
×
1476
            return item;
×
1477
        }
1478
      }
1479
    }
1480
  }
1481
  return nullptr;
44✔
1482
}
1483

1484
void Character::produce( const Core::Vital* pVital, VitalValue& vv, unsigned int amt )
105✔
1485
{
1486
  int start_ones = vv.current_ones();
105✔
1487
  set_dirty();
105✔
1488
  vv.produce( amt );
105✔
1489
  if ( start_ones != vv.current_ones() )
105✔
1490
    Network::ClientInterface::tell_vital_changed( this, pVital );
1✔
1491
}
105✔
1492

1493
bool Character::consume( const Core::Vital* pVital, VitalValue& vv, unsigned int amt,
17✔
1494
                         VitalDepletedReason reason )
1495
{
1496
  int start_ones = vv.current_ones();
17✔
1497
  set_dirty();
17✔
1498
  bool res = vv.consume( amt );
17✔
1499
  if ( start_ones != vv.current_ones() )
17✔
1500
  {
1501
    Network::ClientInterface::tell_vital_changed( this, pVital );
×
1502
    if ( start_ones != 0 && vv.current_ones() == 0 && pVital->depleted_func != nullptr )
×
1503
      pVital->depleted_func->call( new Module::ECharacterRefObjImp( this ),
×
1504
                                   new Bscript::BLong( static_cast<int>( reason ) ) );
×
1505
  }
1506
  return res;
17✔
1507
}
1508

1509
void Character::set_current_ones( const Core::Vital* pVital, VitalValue& vv, unsigned int ones,
74✔
1510
                                  VitalDepletedReason reason )
1511
{
1512
  int start_ones = vv.current_ones();
74✔
1513
  set_dirty();
74✔
1514
  vv.current_ones( ones );
74✔
1515
  Network::ClientInterface::tell_vital_changed( this, pVital );
74✔
1516
  if ( start_ones != 0 && vv.current_ones() == 0 && pVital->depleted_func != nullptr )
74✔
1517
    pVital->depleted_func->call( new Module::ECharacterRefObjImp( this ),
×
1518
                                 new Bscript::BLong( static_cast<int>( reason ) ) );
×
1519
}
74✔
1520

1521
void Character::set_current( const Core::Vital* pVital, VitalValue& vv, unsigned int ones,
9✔
1522
                             VitalDepletedReason reason )
1523
{
1524
  int start_ones = vv.current_ones();
9✔
1525
  set_dirty();
9✔
1526
  vv.current( ones );
9✔
1527
  Network::ClientInterface::tell_vital_changed( this, pVital );
9✔
1528
  if ( start_ones != 0 && vv.current_ones() == 0 && pVital->depleted_func != nullptr )
9✔
1529
    pVital->depleted_func->call( new Module::ECharacterRefObjImp( this ),
×
1530
                                 new Bscript::BLong( static_cast<int>( reason ) ) );
×
1531
}
9✔
1532

1533
void Character::regen_vital( const Core::Vital* pVital )
105✔
1534
{
1535
  VitalValue& vv = vital( pVital->vitalid );
105✔
1536
  int rr = vv.regenrate();
105✔
1537
  if ( rr > 0 )
105✔
1538
    produce( pVital, vv, rr / 12 );
105✔
1539
  else if ( rr < 0 )
×
1540
    consume( pVital, vv, -rr / 12, VitalDepletedReason::REGENERATE );
×
1541
}
105✔
1542

1543
void Character::calc_vital_stuff( bool i_mod, bool v_mod )
89✔
1544
{
1545
  if ( i_mod )
89✔
1546
  {
1547
    for ( unsigned ai = 0; ai < Core::gamestate.numAttributes; ++ai )
4,717✔
1548
    {
1549
      calc_single_attribute( Core::gamestate.attributes[ai] );
4,628✔
1550
    }
1551
  }
1552

1553
  if ( v_mod )
89✔
1554
  {
1555
    for ( unsigned vi = 0; vi < Core::gamestate.numVitals; ++vi )
356✔
1556
    {
1557
      calc_single_vital( Core::gamestate.vitals[vi] );
267✔
1558
    }
1559
  }
1560
}
89✔
1561

1562
void Character::calc_single_vital( const Core::Vital* pVital )
267✔
1563
{
1564
  VitalValue& vv = vital( pVital->vitalid );
267✔
1565

1566
  int start_ones = vv.current_ones();
267✔
1567
  int start_max = vv.maximum_ones();
267✔
1568

1569
  // dave change the order of maximum and regen function 3/19/3
1570
  int mv = pVital->get_maximum_func->call_long( new Module::ECharacterRefObjImp( this ) );
267✔
1571

1572
  if ( mv < static_cast<int>( Core::VITAL_LOWEST_MAX_HUNDREDTHS ) )
267✔
1573
    mv = Core::VITAL_LOWEST_MAX_HUNDREDTHS;
25✔
1574
  if ( mv > static_cast<int>( Core::VITAL_HIGHEST_MAX_HUNDREDTHS ) )
267✔
1575
    mv = Core::VITAL_HIGHEST_MAX_HUNDREDTHS;
×
1576

1577
  vv.maximum( mv );
267✔
1578

1579
  int rr = pVital->get_regenrate_func->call_long( new Module::ECharacterRefObjImp( this ) );
267✔
1580

1581
  if ( rr < Core::VITAL_LOWEST_REGENRATE )
267✔
1582
    rr = Core::VITAL_LOWEST_REGENRATE;
×
1583
  if ( rr > Core::VITAL_HIGHEST_REGENRATE )
267✔
1584
    rr = Core::VITAL_HIGHEST_REGENRATE;
×
1585

1586
  vv.regenrate( rr );
267✔
1587

1588
  if ( ( start_ones != vv.current_ones() ) || ( start_max != vv.maximum_ones() ) )
267✔
1589
    Network::ClientInterface::tell_vital_changed( this, pVital );
267✔
1590
}
267✔
1591

1592
void Character::calc_single_attribute( const Attribute* pAttr )
4,628✔
1593
{
1594
  AttributeValue& av = attribute( pAttr->attrid );
4,628✔
1595

1596
  if ( pAttr->getintrinsicmod_func )
4,628✔
1597
  {
1598
    int im = pAttr->getintrinsicmod_func->call_long( new Module::ECharacterRefObjImp( this ) );
×
1599

1600
    if ( im < ATTRIBUTE_MIN_INTRINSIC_MOD )
×
1601
      im = ATTRIBUTE_MIN_INTRINSIC_MOD;
×
1602
    if ( im > ATTRIBUTE_MAX_INTRINSIC_MOD )
×
1603
      im = ATTRIBUTE_MAX_INTRINSIC_MOD;
×
1604

1605
    av.intrinsic_mod( static_cast<short>( im ) );
×
1606
  }
1607
}
4,628✔
1608

1609
void Character::set_vitals_to_maximum()  // throw()
76✔
1610
{
1611
  set_dirty();
76✔
1612
  for ( unsigned vi = 0; vi < Core::gamestate.numVitals; ++vi )
304✔
1613
  {
1614
    VitalValue& vv = vital( vi );
228✔
1615
    vv.current( vv.maximum() );
228✔
1616

1617
    Network::ClientInterface::tell_vital_changed( this, Core::gamestate.vitals[vi] );
228✔
1618
  }
1619
}
76✔
1620

1621
bool Character::setgraphic( u16 newgraphic )
×
1622
{
1623
  if ( newgraphic < 1 || newgraphic > Plib::systemstate.config.max_anim_id )
×
1624
    return false;
×
1625

1626
  set_dirty();
×
1627
  graphic = newgraphic;
×
1628
  send_remove_character_to_nearby_cansee( this );
×
1629
  if ( client )
×
1630
    send_goxyz( client, client->chr );
×
1631
  send_create_mobile_to_nearby_cansee( this );
×
1632

1633
  return true;
×
1634
}
1635

1636
void Character::on_color_changed()
×
1637
{
1638
  send_remove_character_to_nearby_cansee( this );
×
1639
  if ( client )
×
1640
    send_goxyz( client, client->chr );
×
1641
  send_create_mobile_to_nearby_cansee( this );
×
1642
}
×
1643

1644
void Character::on_poison_changed()
×
1645
{
1646
  send_move_mobile_to_nearby_cansee( this, true );
×
1647
}
×
1648

1649
void Character::on_hidden_changed()
7✔
1650
{
1651
  if ( hidden() )
7✔
1652
  {
1653
    set_stealthsteps( 0 );
4✔
1654
    if ( client )
4✔
1655
      send_move( client, this );
×
1656
    send_remove_character_to_nearby_cantsee( this );
4✔
1657
    send_create_mobile_to_nearby_cansee( this );
4✔
1658
  }
1659
  else
1660
  {
1661
    unhide();
3✔
1662
    set_stealthsteps( 0 );
3✔
1663
  }
1664
}
7✔
1665

1666
void Character::on_concealed_changed()
1✔
1667
{
1668
  if ( concealed() )
1✔
1669
  {
1670
    if ( client )
1✔
1671
      send_move( client, this );
×
1672
    send_remove_character_to_nearby_cantsee( this );
1✔
1673
    send_create_mobile_to_nearby_cansee( this );
1✔
1674
  }
1675
  else if ( is_visible() )
×
1676
    unhide();
×
1677
  set_stealthsteps( 0 );
1✔
1678
}
1✔
1679

1680
void Character::on_cmdlevel_changed()
1✔
1681
{
1682
  send_remove_character_to_nearby_cantsee( this );
1✔
1683
  send_create_mobile_to_nearby_cansee( this );
1✔
1684
}
1✔
1685

1686
void Character::on_facing_changed()
5✔
1687
{
1688
  if ( client )
5✔
1689
    send_goxyz( client, client->chr );
5✔
1690
  send_move_mobile_to_nearby_cansee( this );
5✔
1691
}
5✔
1692

1693
void Character::on_aos_ext_stat_changed()
4✔
1694
{
1695
  if ( client )
4✔
1696
  {
1697
    if ( client->acctSupports( Plib::ExpansionVersion::AOS ) && has_active_client() )
×
1698
    {
1699
      send_full_statmsg( client, client->chr );
×
1700
    }
1701
  }
1702
}
4✔
1703

1704
void Character::setfacing( u8 newfacing )
99✔
1705
{
1706
  facing = newfacing & 7;
99✔
1707
}
99✔
1708

1709
u8 Character::get_flag1( Network::Client* other_client ) const
205✔
1710
{
1711
  // Breaks paperdoll
1712
  u8 flag1 = 0;
205✔
1713
  if ( gender )
205✔
1714
    flag1 |= Core::CHAR_FLAG1_GENDER;
×
1715
  if ( ( poisoned() ) &&
205✔
1716
       ( ~other_client->ClientType &
×
1717
         Network::CLIENTTYPE_7000 ) )  // client >=7 receive the poisonflag with 0x17
1718
    flag1 |= Core::CHAR_FLAG1_POISONED;
×
1719
  if ( ( movemode & Plib::MOVEMODE_FLY ) &&
205✔
1720
       ( other_client->ClientType & Network::CLIENTTYPE_7000 ) )
×
1721
    flag1 |= Core::CHAR_FLAG1_FLYING;
×
1722
  if ( ( Core::settingsManager.ssopt.invul_tag == 2 ) && ( invul() ) )
205✔
1723
    flag1 |= Core::CHAR_FLAG1_YELLOWHEALTH;
×
1724
  if ( warmode() )
205✔
1725
    flag1 |= Core::CHAR_FLAG1_WARMODE;
1✔
1726
  if ( !is_visible() )
205✔
1727
    flag1 |= Core::CHAR_FLAG1_INVISIBLE;
×
1728

1729
  return flag1;
205✔
1730
}
1731

1732
void Character::apply_raw_damage_hundredths( unsigned int amount, Character* source, bool userepsys,
×
1733
                                             bool send_damage_packet )
1734
{
1735
  if ( dead() )
×
1736
  {
1737
    // cerr << "Waah! " << name() << " " << hexint(serial) << " is dead, but taking damage?" <<
1738
    // endl;
1739
    return;
×
1740
  }
1741

1742
  if ( ( source ) && ( userepsys ) )
×
1743
    source->repsys_on_attack( this );
×
1744

1745
  if ( ( amount == 0 ) || cached_settings.get( PRIV_FLAGS::INVUL ) )
×
1746
    return;
×
1747

1748
  set_dirty();
×
1749
  if ( hidden() )
×
1750
    unhide();
×
1751

1752
  if ( send_damage_packet && source )
×
1753
  {
1754
    u16 showdmg;
1755
    if ( amount > 6553500 )  // 0xFFFF*100
×
1756
      showdmg = 0xFFFF;
×
1757
    else
1758
      showdmg = static_cast<u16>( amount / 100 );
×
1759
    send_damage( source, this, showdmg );
×
1760
  }
1761

1762
  if ( paralyzed() )
×
1763
    mob_flags_.remove( MOB_FLAGS::PARALYZED );
×
1764

1765
  disable_regeneration_for( 2 );  // FIXME depend on amount?
×
1766

1767
  // 0.xx is still 0
1768
  VitalValue& vv = vital( Core::gamestate.pVitalLife->vitalid );
×
1769
  if ( vv.current() - amount <= 99 )
×
1770
    amount = vv.current();  // be greedy with that last point
×
1771
  consume( Core::gamestate.pVitalLife, vv, amount, VitalDepletedReason::DAMAGE );
×
1772

1773
  if ( ( source ) && ( userepsys ) )
×
1774
    source->repsys_on_damage( this );
×
1775

1776
  send_update_hits_to_inrange( this );
×
1777

1778
  if ( vv.current() == 0 )
×
1779
    die();
×
1780
}
1781

1782
// keep this in sync with NPC::armor_absorb_damage
1783

1784

1785
unsigned short calc_thru_damage( double damage, unsigned short ar )
×
1786
{
1787
  int blocked = ar;
×
1788
  if ( blocked < 0 )
×
1789
    blocked = 0;
×
1790

1791
  int absorbed = blocked / 2;
×
1792

1793
  blocked -= absorbed;
×
1794
  absorbed += Clib::random_int( blocked );
×
1795
  damage -= absorbed;
×
1796

1797
  if ( damage >= 2.0 )
×
1798
  {
1799
    return static_cast<unsigned short>( damage * 0.5 );
×
1800
  }
1801

1802
  int dmg = static_cast<int>( damage );
×
1803
  if ( dmg >= 0 )
×
1804
    return static_cast<unsigned short>( dmg );
×
1805
  return 0;
×
1806
}
1807

1808

1809
double Character::armor_absorb_damage( double damage )
×
1810
{
1811
  Items::UArmor* armor = choose_armor();
×
1812
  if ( armor != nullptr )
×
1813
  {
1814
    damage = calc_thru_damage( damage, armor->ar() + ar_mod() );
×
1815

1816
    armor->reduce_hp_from_hit();
×
1817
  }
1818
  return damage;
×
1819
}
1820

1821
double Character::apply_damage( double damage, Character* source, bool userepsys,
×
1822
                                bool send_damage_packet )
1823
{
1824
  damage = armor_absorb_damage( damage );
×
1825
  if ( Core::settingsManager.watch.combat )
×
1826
    INFO_PRINTLN( "Final damage: {}", damage );
×
1827
  do_imhit_effects();
×
1828
  apply_raw_damage_hundredths( static_cast<unsigned int>( damage * 100 ), source, userepsys,
×
1829
                               send_damage_packet );
1830

1831
  return damage;
×
1832
}
1833

1834
void Character::get_hitscript_params( double damage, Items::UArmor** parmor,
8✔
1835
                                      unsigned short* rawdamage )
1836
{
1837
  Items::UArmor* armor = choose_armor();
8✔
1838
  if ( armor )
8✔
1839
  {
1840
    *rawdamage = calc_thru_damage( damage, armor->ar() + ar_mod() );
×
1841
  }
1842
  else
1843
  {
1844
    *rawdamage = static_cast<unsigned short>( damage );
8✔
1845
  }
1846
  *parmor = armor;
8✔
1847
}
8✔
1848

1849
void Character::run_hit_script( Character* defender, double damage )
8✔
1850
{
1851
  ref_ptr<Bscript::EScriptProgram> prog = find_script2(
1852
      weapon->hit_script(), true, Plib::systemstate.config.cache_interactive_scripts );
8✔
1853
  if ( prog.get() == nullptr )
8✔
1854
    return;
×
1855

1856
  std::unique_ptr<Core::UOExecutor> ex( Core::create_script_executor() );
8✔
1857
  auto uoemod = new Module::UOExecutorModule( *ex );
8✔
1858
  ex->addModule( uoemod );
8✔
1859

1860
  unsigned short rawdamage = 0;
8✔
1861
  unsigned short basedamage = static_cast<unsigned short>( damage );
8✔
1862

1863
  Items::UArmor* armor = nullptr;
8✔
1864

1865
  defender->get_hitscript_params( damage, &armor, &rawdamage );
8✔
1866

1867

1868
  ex->pushArg( new Bscript::BLong( rawdamage ) );
8✔
1869
  ex->pushArg( new Bscript::BLong( basedamage ) );
8✔
1870
  if ( armor )
8✔
1871
    ex->pushArg( new Module::EItemRefObjImp( armor ) );
×
1872
  else
1873
    ex->pushArg( new Bscript::BLong( 0 ) );
8✔
1874
  ex->pushArg( new Module::EItemRefObjImp( weapon ) );
8✔
1875
  ex->pushArg( new Module::ECharacterRefObjImp( defender ) );
8✔
1876
  ex->pushArg( new Module::ECharacterRefObjImp( this ) );
8✔
1877

1878
  ex->priority( 100 );
8✔
1879

1880
  if ( ex->setProgram( prog.get() ) )
8✔
1881
  {
1882
    uoemod->controller_.set( this );
8✔
1883
    schedule_executor( ex.release() );
8✔
1884
  }
1885
  else
1886
  {
1887
    POLLOGLN( "Blech, couldn't start hitscript {}", weapon->hit_script().name() );
×
1888
  }
1889
}
8✔
1890

1891
///
1892
/// Clear a Mobile's ToBeReportable list when all of the following are true:
1893
///   1) hits are at maximum
1894
///   2) mobile is not poisoned
1895
///   3) mobile is not paralyzed
1896
///
1897
void Character::check_undamaged()
35✔
1898
{
1899
  if ( vital( Core::gamestate.pVitalLife->vitalid ).is_at_maximum() && !poisoned() && !paralyzed() )
35✔
1900
  {
1901
    clear_to_be_reportables();
34✔
1902
  }
1903
}
35✔
1904

1905

1906
///
1907
/// When a Mobile is Healed
1908
///
1909
///   if Amy's hits are at maximum,
1910
///     Clear Amy's ToBeReportable list
1911
///
1912
///   (note, poisoned and paralyzed flags are not checked)
1913
///
1914
void Character::heal_damage_hundredths( unsigned int amount )
×
1915
{
1916
  if ( dead() )
×
1917
  {
1918
    // cerr << "Waah! " << name() << " is dead, but healing damage?" << endl;
1919
    return;
×
1920
  }
1921

1922
  if ( amount == 0 )
×
1923
    return;
×
1924

1925
  produce( Core::gamestate.pVitalLife, vital( Core::gamestate.pVitalLife->vitalid ), amount );
×
1926

1927
  check_undamaged();
×
1928

1929
  send_update_hits_to_inrange( this );
×
1930
}
1931

1932
Items::Item* create_death_shroud()
1✔
1933
{
1934
  Items::Item* item = Items::Item::create( UOBJ_DEATH_SHROUD );
1✔
1935
  item->layer = Core::LAYER_ROBE_DRESS;
1✔
1936
  return item;
1✔
1937
}
1938
Items::Item* create_death_robe()
×
1939
{
1940
  Items::Item* item = Items::Item::create( UOBJ_DEATH_ROBE );
×
1941
  item->layer = Core::LAYER_ROBE_DRESS;
×
1942
  return item;
×
1943
}
1944
Items::Item* create_backpack()
×
1945
{
1946
  Items::Item* item = Items::Item::create( UOBJ_BACKPACK );
×
1947
  item->layer = Core::LAYER_BACKPACK;
×
1948
  return item;
×
1949
}
1950

1951

1952
void Character::send_warmode()
4✔
1953
{
1954
  Network::PktHelper::PacketOut<Network::PktOut_72> msg;
4✔
1955
  msg->Write<u8>( warmode() ? 1u : 0u );
4✔
1956
  msg->offset++;  // u8 unk2
4✔
1957
  msg->Write<u8>( 0x32u );
4✔
1958
  msg->offset++;  // u8 unk4
4✔
1959
  msg.Send( client );
4✔
1960
}
4✔
1961

1962
void Character::resurrect()
×
1963
{
1964
  if ( !dead() )
×
1965
  {
1966
    ERROR_PRINTLN( "uh, trying to resurrect {}, who isn't dead.", name() );
×
1967
    return;
×
1968
  }
1969
  set_dirty();
×
1970
  if ( graphic == UOBJ_HUMAN_MALE_GHOST )
×
1971
    graphic = UOBJ_HUMAN_MALE;
×
1972
  else if ( graphic == UOBJ_HUMAN_FEMALE_GHOST )
×
1973
    graphic = UOBJ_HUMAN_FEMALE;
×
1974
  else if ( graphic == UOBJ_ELF_MALE_GHOST )
×
1975
    graphic = UOBJ_ELF_MALE;
×
1976
  else if ( graphic == UOBJ_ELF_FEMALE_GHOST )
×
1977
    graphic = UOBJ_ELF_FEMALE;
×
1978
  else if ( graphic == UOBJ_GARGOYLE_MALE_GHOST )
×
1979
    graphic = UOBJ_GARGOYLE_MALE;
×
1980
  else if ( graphic == UOBJ_GARGOYLE_FEMALE_GHOST )
×
1981
    graphic = UOBJ_GARGOYLE_FEMALE;
×
1982

1983
  mob_flags_.remove( MOB_FLAGS::DEAD );
×
1984
  mob_flags_.remove( MOB_FLAGS::WARMODE );
×
1985
  mob_flags_.remove( MOB_FLAGS::FROZEN );
×
1986
  mob_flags_.remove( MOB_FLAGS::PARALYZED );
×
1987

1988
  color = truecolor;
×
1989

1990
  if ( Core::gamestate.pVitalLife->regen_while_dead )
×
1991
  {
1992
    VitalValue& vv = vital( Core::gamestate.pVitalLife->vitalid );
×
1993
    if ( vv._current == 0 )  // set in die()
×
1994
      set_current_ones( Core::gamestate.pVitalLife, vv, 1, VitalDepletedReason::RESURRECT );
×
1995
  }
1996
  else
1997
    set_current_ones( Core::gamestate.pVitalLife, vital( Core::gamestate.pVitalLife->vitalid ), 1,
×
1998
                      VitalDepletedReason::RESURRECT );
1999

2000
  if ( !Core::gamestate.pVitalMana->regen_while_dead )
×
2001
    set_current_ones( Core::gamestate.pVitalMana, vital( Core::gamestate.pVitalMana->vitalid ), 0,
×
2002
                      VitalDepletedReason::RESURRECT );
2003

2004
  if ( !Core::gamestate.pVitalStamina->regen_while_dead )
×
2005
    set_current_ones( Core::gamestate.pVitalStamina,
×
2006
                      vital( Core::gamestate.pVitalStamina->vitalid ), 1,
×
2007
                      VitalDepletedReason::RESURRECT );
2008

2009
  // Replace the death shroud with a death robe
2010
  bool equip_death_robe = true;
×
2011
  if ( layer_is_equipped( Core::LAYER_ROBE_DRESS ) )
×
2012
  {
2013
    Items::Item* death_shroud = wornitems->GetItemOnLayer( Core::LAYER_ROBE_DRESS );
×
2014
    if ( death_shroud->objtype_ == UOBJ_DEATH_SHROUD )
×
2015
    {
2016
      unequip( death_shroud );
×
2017
      death_shroud->destroy();
×
2018
      death_shroud = nullptr;
×
2019
    }
2020
    else
2021
    {
2022
      // Do not destroy and replace the already equipped robe
2023
      equip_death_robe = false;
×
2024
    }
2025
  }
2026
  if ( equip_death_robe )
×
2027
  {
2028
    Items::Item* death_robe = create_death_robe();
×
2029
    equip( death_robe );
×
2030
  }
2031

2032
  // equip( create_backpack() );
2033

2034
  // if this has a connected client, tell him his new position.
2035
  if ( client )
×
2036
  {
2037
    client->pause();
×
2038
    send_warmode();
×
2039
    send_goxyz( client, this );
×
2040
    send_owncreate( client, this );
×
2041
    Core::WorldIterator<Core::MobileFilter>::InRange( pos(), los_size(),
×
2042
                                                      [&]( Character* zonechr )
×
2043
                                                      {
2044
                                                        if ( !is_visible_to_me( zonechr ) )
×
2045
                                                          send_remove_character( client, zonechr );
×
2046
                                                      } );
×
2047
    client->restart();
×
2048
  }
2049

2050
  // Tell other connected players, if in range, about this character.
2051
  send_remove_character_to_nearby_cansee( this );
×
2052
  send_create_mobile_to_nearby_cansee( this );
×
2053
  realm()->notify_resurrected( *this );
×
2054
}
2055

2056
void Character::on_death( Items::Item* corpse )
1✔
2057
{
2058
  Items::Item* death_shroud = create_death_shroud();
1✔
2059
  if ( equippable( death_shroud ) )  // check it or passert will trigger
1✔
2060
  {
2061
    equip( death_shroud );
×
2062
    send_wornitem_to_inrange( this, death_shroud );
×
2063
  }
2064
  else
2065
  {
2066
    ERROR_PRINTLN( "Create Character: Failed to equip death shroud {:#x}", death_shroud->graphic );
1✔
2067
    death_shroud->destroy();
1✔
2068
  }
2069

2070
  if ( client != nullptr )
1✔
2071
  {
2072
    if ( opponent_ )
×
NEW
2073
      opponent_.inform_disengaged( Attackable{ this } );
×
2074

2075
    client->pause();
×
2076
    send_warmode();
×
2077

2078
    // Sends the complete corpse to the client itself, so he knows where his
2079
    // items went.
2080
    send_full_corpse( client, corpse );
×
2081

2082
    send_goxyz( client, this );
×
2083
    Core::WorldIterator<Core::MobileFilter>::InRange(
×
2084
        pos(), los_size(),
×
2085
        [&]( Character* zonechr )
×
2086
        {
2087
          if ( zonechr->dead() && is_visible_to_me( zonechr ) )
×
2088
            send_owncreate( client, zonechr );
×
2089
        } );
×
2090

2091
    client->restart();
×
2092
  }
2093

2094
  // change self to ghost for ghosts, remove self for living
2095
  send_remove_character_to_nearby_cantsee( this );
1✔
2096

2097
  send_create_mobile_to_nearby_cansee( this );
1✔
2098

2099
  if ( Clib::FileExists( "scripts/misc/chrdeath.ecl" ) )
1✔
2100
    Core::start_script( "misc/chrdeath", new Module::EItemRefObjImp( corpse ),
×
2101
                        new Module::ECharacterRefObjImp( this ) );
×
2102
}
1✔
2103

2104
void Character::clear_opponent_of()
175✔
2105
{
2106
  while ( !opponent_of.empty() )
175✔
2107
  {
NEW
2108
    auto attitr = opponent_of.begin();
×
2109
    // note that chr->set_opponent is going to remove
2110
    // its entry from our opponent_of collection,
2111
    // so eventually this loop will exit.
NEW
2112
    if ( auto* mob = attitr->mobile() )
×
2113
    {
NEW
2114
      mob->set_opponent( {}, false );
×
NEW
2115
      continue;
×
2116
    }
NEW
2117
    if ( auto* item = attitr->item() )
×
NEW
2118
      item->remove_opponent_of( Attackable{ this } );
×
NEW
2119
    opponent_of.erase( attitr );
×
2120
  }
2121
}
175✔
2122

2123
void Character::remove_opponent_of( const Attackable& other )
4✔
2124
{
2125
  opponent_of.erase( other );
4✔
2126
}
4✔
2127
void Character::add_opponent_of( Attackable other )
4✔
2128
{
2129
  opponent_of.insert( std::move( other ) );
4✔
2130
}
4✔
2131

2132
void Character::die()
74✔
2133
{
2134
  if ( Core::gamestate.system_hooks.can_die )
74✔
2135
  {
2136
    if ( !Core::gamestate.system_hooks.can_die->call( make_mobileref( this ) ) )
×
2137
      return;
×
2138
  }
2139

2140
  set_current_ones( Core::gamestate.pVitalLife, vital( Core::gamestate.pVitalLife->vitalid ), 0,
74✔
2141
                    VitalDepletedReason::DEATH );
2142
  clear_my_aggressors();
74✔
2143
  clear_my_lawful_damagers();
74✔
2144
  commit_to_reportables();
74✔
2145

2146

2147
  u16 save_graphic = graphic;
74✔
2148

2149
  if ( graphic == UOBJ_HUMAN_MALE || graphic == UOBJ_HUMAN_FEMALE || graphic == UOBJ_ELF_MALE ||
74✔
2150
       graphic == UOBJ_ELF_FEMALE || graphic == UOBJ_GARGOYLE_MALE ||
×
2151
       graphic == UOBJ_GARGOYLE_FEMALE )
×
2152
  {
2153
    switch ( race )
74✔
2154
    {
2155
    case Plib::RACE_HUMAN:
74✔
2156
      graphic = ( gender == Plib::GENDER_MALE ) ? UOBJ_HUMAN_MALE_GHOST : UOBJ_HUMAN_FEMALE_GHOST;
74✔
2157
      break;
74✔
2158
    case Plib::RACE_ELF:
×
2159
      graphic = ( gender == Plib::GENDER_MALE ) ? UOBJ_ELF_MALE_GHOST : UOBJ_ELF_FEMALE_GHOST;
×
2160
      break;
×
2161
    case Plib::RACE_GARGOYLE:
×
2162
      graphic =
×
2163
          ( gender == Plib::GENDER_MALE ) ? UOBJ_GARGOYLE_MALE_GHOST : UOBJ_GARGOYLE_FEMALE_GHOST;
×
2164
      break;
×
2165
    }
2166
  }
2167
  DECLARE_CHECKPOINT;
148✔
2168

2169
  mob_flags_.set( MOB_FLAGS::DEAD );
74✔
2170
  mob_flags_.remove( MOB_FLAGS::WARMODE );
74✔
2171
  mob_flags_.remove( MOB_FLAGS::FROZEN );
74✔
2172
  mob_flags_.remove( MOB_FLAGS::PARALYZED );
74✔
2173

2174
  UPDATE_CHECKPOINT();
74✔
2175
  /* FIXME: corpse container difficulties.
2176

2177
     What am I gonna do about the corpse.  I've said that
2178
     MAX_CONTAINER_ITEMS can be in a container, but if I put
2179
     worn items, plus what's in the backpack, in the
2180
     corpse 'container' I'm going to violate this.  If
2181
     I put everything in a backpack in the corpse,
2182
     it's too easy to grab everything.
2183
     Maybe I have to limit the backpack to MAX_CONTAINER_ITEM
2184
     - NUM_LAYERS or something, or grow the "send container"
2185
     message to be able to hold a full backpack, plus
2186
     all worn items.  That doesn't work, tho, 'cause it
2187
     overfills the corpse container. ick.
2188

2189
     The current solution is to put the backpack
2190
     on the corpse.
2191
     */
2192

2193
  Core::UCorpse* corpse = static_cast<Core::UCorpse*>( Items::Item::create( UOBJ_CORPSE ) );
74✔
2194
  this->last_corpse = corpse->serial;
74✔
2195

2196
  corpse->ownerserial = this->serial;
74✔
2197
  corpse->setname( "A corpse of " + name_.get() );
74✔
2198
  corpse->take_contents_to_grave( acct == nullptr );
74✔
2199

2200
  UPDATE_CHECKPOINT();
74✔
2201

2202
  corpse->color = truecolor;
74✔
2203
  corpse->setposition( pos() );
74✔
2204
  corpse->facing = facing;
74✔
2205
  corpse->corpsetype = save_graphic;
74✔
2206
  // corpse->dir = dir;
2207
  UPDATE_CHECKPOINT();
74✔
2208

2209
  register_with_supporting_multi( corpse );
74✔
2210
  if ( is_trading() )
74✔
2211
    Core::cancel_trade( this );
×
2212
  clear_gotten_item();
74✔
2213
  corpse->copyprops( *this );
74✔
2214
  UPDATE_CHECKPOINT();
74✔
2215

2216
  // Change the character's color to grey
2217
  color = 0;
74✔
2218
  UPDATE_CHECKPOINT();
74✔
2219

2220
  // Keep a list of copied items to set movable = false _after_ the corpse has
2221
  // been sent, since changing the movable property does an
2222
  // `send_put_in_container_to_inrange`. The client would ignore this item, as
2223
  // the container does not exist yet.
2224
  std::list<Items::Item*> copied_items;
148✔
2225

2226
  // small lambdas to reduce the mess inside the loops
2227
  auto _copy_item = [&]( Items::Item* _item ) {  // copy a item into the corpse
2✔
2228
    Items::Item* copy = _item->clone();
2✔
2229
    copied_items.push_back( copy );
2✔
2230
    corpse->equip_and_add( copy, copy->layer );
2✔
2231
  };
2✔
2232
  auto _drop_item_to_world = [&]( Items::Item* _item ) {  // places the item onto the corpse coords
×
2233
    _item->setposition( corpse->pos() );
×
2234
    add_item_to_world( _item );
×
2235
    register_with_supporting_multi( _item );
×
2236
    move_item( _item, corpse->pos() );
×
2237
  };
74✔
2238

2239
  // WARNING: never ever touch or be 10000% sure what you are doing!!!!
2240
  for ( unsigned layer = Core::LAYER_EQUIP__LOWEST; layer <= Core::LAYER_EQUIP__HIGHEST; ++layer )
1,924✔
2241
  {
2242
    Items::Item* item = wornitems->GetItemOnLayer( layer );
1,850✔
2243
    if ( item == nullptr )
1,850✔
2244
      continue;
1,848✔
2245
    if ( item->layer == Core::LAYER_BACKPACK )  // These needs to be the first!!!!
6✔
2246
      continue;
2✔
2247
    // never ever touch this order
2248
    // first only copy the hair layers and only these!
2249
    // then check for newbie and then I dont care
2250
    if ( item->layer == Core::LAYER_BEARD || item->layer == Core::LAYER_HAIR ||
4✔
2251
         item->layer == Core::LAYER_FACE )
2✔
2252
    {
2253
      // Copies hair items onto the corpse
2254
      _copy_item( item );
2✔
2255
      continue;
2✔
2256
    }
2257
    if ( item->newbie() || item->insured() || item->no_drop() )
2✔
2258
      continue;
×
2259
    if ( item->layer != Core::LAYER_MOUNT && item->layer != Core::LAYER_ROBE_DRESS &&
4✔
2260
         !item->movable() )  // dress layer needs to be unequipped for deathrobe
2✔
2261
    {
2262
      _copy_item( item );
×
2263
      continue;
×
2264
    }
2265

2266
    if ( item->layer == Core::LAYER_MOUNT &&
2✔
2267
         item->objtype_ == Core::settingsManager.extobj.boatmount )
×
2268
    {
2269
      Multi::UMulti* multi = realm()->find_supporting_multi( pos3d() );
×
2270

2271
      // Clear the pilot from the boat
2272
      if ( multi != nullptr && multi->script_isa( Core::POLCLASS_BOAT ) )
×
2273
      {
2274
        Multi::UBoat* boat = static_cast<Multi::UBoat*>( multi );
×
2275
        if ( boat->pilot() == this )
×
2276
        {
2277
          boat->clear_pilot();
×
2278
          continue;
×
2279
        }
2280
      }
2281

2282
      // If for some reason there was a mismatch between chr multi and boat pilot, just destroy the
2283
      // boatmount. Destroying the boatmount item will leave an orphaned itemref on the boat multi.
2284
      // The itemref will be re-set on next set_pilot call, as an orphaned boatmount behaves as if
2285
      // there is no boatmount at all.
2286
      destroy_item( item );
×
2287
      continue;
×
2288
    }
×
2289

2290
    ///
2291
    /// Unequip scripts aren't honored when moving a dead mobile's equipment
2292
    /// onto a corpse if honor_unequip_script_on_death is disabled.
2293
    ///
2294
    UPDATE_CHECKPOINT();
2✔
2295
    if ( Core::settingsManager.ssopt.honor_unequip_script_on_death )
2✔
2296
    {
2297
      if ( !item->check_unequiptest_scripts() )
×
2298
        continue;
×
2299
      if ( !item->check_unequip_script() )
×
2300
        continue;
×
2301
    }
2302
    else
2303
    {
2304
      item->check_unequip_script();
2✔
2305
    }
2306
    UPDATE_CHECKPOINT();
2✔
2307
    unequip( item );
2✔
2308
    UPDATE_CHECKPOINT();
2✔
2309

2310
    u8 newSlot = 1;
2✔
2311
    if ( !corpse->can_add_to_slot( newSlot ) || !item->slot_index( newSlot ) )
2✔
2312
    {
2313
      _drop_item_to_world( item );
×
2314
    }
2315
    else
2316
    {
2317
      corpse->equip_and_add( item, layer );
2✔
2318
    }
2319
    UPDATE_CHECKPOINT();
2✔
2320
  }
2321
  UPDATE_CHECKPOINT();
74✔
2322

2323
  // For some reason, the backpack shows up as a small child.. So change its layer.
2324
  Core::UContainer* bp = backpack();
74✔
2325
  if ( bp )
74✔
2326
  {
2327
    Core::UContainer::Contents tmp;
2✔
2328
    UPDATE_CHECKPOINT();
2✔
2329
    bp->extract( tmp );
2✔
2330
    UPDATE_CHECKPOINT();
2✔
2331
    // We set slot to 1 outside the loop. As it cycles through, this will continue
2332
    // to increase. This will reduce the amount of checks to find next available
2333
    // slots
2334
    u8 packSlot = 1;
2✔
2335
    // u8 corpseSlot = 1;
2336
    while ( !tmp.empty() )
4✔
2337
    {
2338
      Items::Item* bp_item = tmp.back();
2✔
2339
      tmp.pop_back();
2✔
2340
      bp_item->container = nullptr;
2✔
2341
      bp_item->layer = 0;
2✔
2342
      UPDATE_CHECKPOINT();
2✔
2343
      if ( ( bp_item->newbie() || bp_item->no_drop() || bp_item->use_insurance() ) &&
2✔
2344
           bp->can_add( *bp_item ) )
×
2345
      {
2346
        if ( !bp->can_add_to_slot( packSlot ) || !bp_item->slot_index( packSlot ) )
×
2347
        {
2348
          _drop_item_to_world( bp_item );
×
2349
        }
2350
        else
2351
        {
2352
          bp->add( bp_item, bp_item->pos2d() );
×
2353
        }
2354
        UPDATE_CHECKPOINT();
×
2355
      }
2356
      else if ( corpse->can_add( *bp_item ) )
2✔
2357
      {
2358
        if ( !corpse->can_add_to_slot( packSlot ) || !bp_item->slot_index( packSlot ) )
2✔
2359
        {
2360
          _drop_item_to_world( bp_item );
×
2361
        }
2362
        else
2363
        {
2364
          corpse->add_at_random_location( bp_item );
2✔
2365
        }
2366
        UPDATE_CHECKPOINT();
2✔
2367
      }
2368
      else
2369
      {
2370
        UPDATE_CHECKPOINT();
×
2371
        _drop_item_to_world( bp_item );
×
2372
      }
2373
      UPDATE_CHECKPOINT();
2✔
2374
    }
2375

2376
    UPDATE_CHECKPOINT();
2✔
2377

2378
    for ( unsigned layer = Core::LAYER_EQUIP__LOWEST; layer <= Core::LAYER_EQUIP__HIGHEST; ++layer )
52✔
2379
    {
2380
      Items::Item* item = wornitems->GetItemOnLayer( layer );
50✔
2381
      if ( item == nullptr )
50✔
2382
        continue;
47✔
2383
      if ( item->layer == Core::LAYER_BACKPACK )  // These needs to be the first!!!!
3✔
2384
        continue;
2✔
2385
      if ( item->layer == Core::LAYER_BEARD || item->layer == Core::LAYER_HAIR ||
1✔
2386
           item->layer == Core::LAYER_FACE )
×
2387
        continue;
1✔
2388
      if ( item->layer != Core::LAYER_MOUNT && item->layer != Core::LAYER_ROBE_DRESS &&
×
2389
           !item->movable() )
×
2390
        continue;
×
2391
      if ( ( item->newbie() || item->no_drop() || item->use_insurance() ) && bp->can_add( *item ) )
×
2392
      {
2393
        UPDATE_CHECKPOINT();
×
2394
        if ( Core::settingsManager.ssopt.honor_unequip_script_on_death )
×
2395
        {
2396
          if ( !item->check_unequiptest_scripts() )
×
2397
            continue;
×
2398
          if ( !item->check_unequip_script() )
×
2399
            continue;
×
2400
        }
2401
        else
2402
        {
2403
          item->check_unequip_script();
×
2404
        }
2405
        UPDATE_CHECKPOINT();
×
2406
        unequip( item );
×
2407
        item->layer = 0;
×
2408
        UPDATE_CHECKPOINT();
×
2409
        if ( !bp->can_add_to_slot( packSlot ) || !item->slot_index( packSlot ) )
×
2410
        {
2411
          _drop_item_to_world( item );
×
2412
        }
2413
        else
2414
        {
2415
          bp->add_at_random_location( item );
×
2416
          update_item_to_inrange( item );
×
2417
        }
2418
        UPDATE_CHECKPOINT();
×
2419
      }
2420
    }
2421
    UPDATE_CHECKPOINT();
2✔
2422
  }
2✔
2423

2424

2425
  UPDATE_CHECKPOINT();
74✔
2426
  send_death_message( this, corpse );
74✔
2427

2428
  UPDATE_CHECKPOINT();
74✔
2429
  corpse->restart_decay_timer();
74✔
2430
  UPDATE_CHECKPOINT();
74✔
2431
  add_item_to_world( corpse );
74✔
2432
  UPDATE_CHECKPOINT();
74✔
2433
  send_item_to_inrange( corpse );
74✔
2434
  UPDATE_CHECKPOINT();
74✔
2435
  // Set the items to unmovable, now that the client knows about the corpse container.
2436
  for ( auto* copied_item : copied_items )
76✔
2437
  {
2438
    copied_item->movable( false );
2✔
2439
  }
2440

2441
  UPDATE_CHECKPOINT();
74✔
2442

2443
  clear_opponent_of();
74✔
2444

2445
  set_opponent( {} );
74✔
2446

2447
  UPDATE_CHECKPOINT();
74✔
2448

2449
  on_death( corpse );
74✔
2450

2451
  CLEAR_CHECKPOINT();
74✔
2452
}
2453

2454
void Character::refresh_ar()
164✔
2455
{
2456
  //  find_armor(); <-- MuadDib commented out, put code inside here to cut down on iter.
2457
  // Figure out what's in each zone
2458
  //   FIXME? NZONES * NLAYERS (5 * 24 = 124) iterations.
2459
  // okay, reverse, for each wornitem, for each coverage area, upgrade.
2460
  // Turley: should be fixed now only iterators over armor's coverage zones instead of all zones
2461
  for ( auto& entry : armor_ )
1,148✔
2462
    entry = nullptr;
984✔
2463
  // we need to reset each resist to 0, then add the base back using calc.
2464
  resetEquipableProperties();
164✔
2465

2466
  for ( unsigned layer = Core::LAYER_EQUIP__LOWEST; layer <= Core::LAYER_EQUIP__HIGHEST; ++layer )
4,264✔
2467
  {
2468
    Items::Item* item = wornitems->GetItemOnLayer( layer );
4,100✔
2469
    if ( !item )
4,100✔
2470
      continue;
3,943✔
2471
    // Let's check all items as base, and handle their element_resists.
2472
    updateEquipableProperties( item );
157✔
2473
    if ( item->isa( Core::UOBJ_CLASS::CLASS_ARMOR ) )
157✔
2474
    {
2475
      auto* armor = static_cast<Items::UArmor*>( item );
1✔
2476
      for ( auto zone : armor->tmplzones() )
2✔
2477
      {
2478
        if ( !armor_[zone] || armor->ar() > armor_[zone]->ar() )
1✔
2479
          armor_[zone] = armor;
1✔
2480
      }
2481
    }
2482
  }
2483

2484
  //  calculate_ar();  <-- MuadDib Commented out, mixed code within ported find_armor to reduce
2485
  // iter.
2486
  double new_ar = 0.0;
164✔
2487
  for ( unsigned zone = 0; zone < Core::gamestate.armorzones.size(); ++zone )
1,148✔
2488
  {
2489
    Items::UArmor* armor = armor_[zone];
984✔
2490
    if ( armor )
984✔
2491
    {
2492
      new_ar += armor->ar() * Core::gamestate.armorzones[zone].chance;
1✔
2493
    }
2494
  }
2495

2496
  /* add AR due to shield : parry skill / 2 is percent of AR */
2497
  // FIXME: Should we allow this to be adjustable via a prop? Hrmmmmm
2498
  if ( shield )
164✔
2499
  {
2500
    new_ar += std::max(
×
2501
        1.0,  //
×
2502
        0.5 * 0.01 * shield->ar() * attribute( Core::gamestate.pAttrParry->attrid ).effective() );
×
2503
  }
2504

2505
  new_ar += ar_mod();
164✔
2506

2507
  ar_ = std::max( 0_s16, Clib::clamp_convert<short>( new_ar ) );
164✔
2508

2509
  if ( client )
164✔
2510
  {  // CHECKME consider sending less frequently
2511
    send_full_statmsg( client, this );
6✔
2512
  }
2513
}
164✔
2514

2515
void Character::updateEquipableProperties( Items::Item* item )
157✔
2516
{
2517
  // calc caps
2518
  if ( item->has_defence_increase_cap() )
157✔
2519
    defence_increase_cap( defence_increase_cap().addToValue( item->defence_increase_cap() ) );
36✔
2520
  // calc resist caps
2521
  if ( item->has_fire_resist_cap() )
157✔
2522
    fire_resist_cap( fire_resist_cap().addToValue( item->fire_resist_cap() ) );
21✔
2523
  if ( item->has_cold_resist_cap() )
157✔
2524
    cold_resist_cap( cold_resist_cap().addToValue( item->cold_resist_cap() ) );
29✔
2525
  if ( item->has_energy_resist_cap() )
157✔
2526
    energy_resist_cap( energy_resist_cap().addToValue( item->energy_resist_cap() ) );
25✔
2527
  if ( item->has_poison_resist_cap() )
157✔
2528
    poison_resist_cap( poison_resist_cap().addToValue( item->poison_resist_cap() ) );
13✔
2529
  if ( item->has_physical_resist_cap() )
157✔
2530
    physical_resist_cap( physical_resist_cap().addToValue( item->physical_resist_cap() ) );
17✔
2531
  // calc resists
2532
  if ( item->has_fire_resist() )
157✔
2533
    fire_resist( fire_resist().addToValue( item->fire_resist() ) );
9✔
2534
  if ( item->has_cold_resist() )
157✔
2535
    cold_resist( cold_resist().addToValue( item->cold_resist() ) );
11✔
2536
  if ( item->has_energy_resist() )
157✔
2537
    energy_resist( energy_resist().addToValue( item->energy_resist() ) );
10✔
2538
  if ( item->has_poison_resist() )
157✔
2539
    poison_resist( poison_resist().addToValue( item->poison_resist() ) );
7✔
2540
  if ( item->has_physical_resist() )
157✔
2541
    physical_resist( physical_resist().addToValue( item->physical_resist() ) );
8✔
2542

2543
  // calc damages
2544
  if ( item->has_fire_damage() )
157✔
2545
    fire_damage( fire_damage().addToValue( item->fire_damage() ) );
88✔
2546
  if ( item->has_cold_damage() )
157✔
2547
    cold_damage( cold_damage().addToValue( item->cold_damage() ) );
90✔
2548
  if ( item->has_energy_damage() )
157✔
2549
    energy_damage( energy_damage().addToValue( item->energy_damage() ) );
89✔
2550
  if ( item->has_poison_damage() )
157✔
2551
    poison_damage( poison_damage().addToValue( item->poison_damage() ) );
86✔
2552
  if ( item->has_physical_damage() )
157✔
2553
    physical_damage( physical_damage().addToValue( item->physical_damage() ) );
87✔
2554

2555
  // calc others
2556
  if ( item->has_lower_reagent_cost() )
157✔
2557
    lower_reagent_cost( lower_reagent_cost().addToValue( item->lower_reagent_cost() ) );
55✔
2558
  if ( item->has_spell_damage_increase() )
157✔
2559
    spell_damage_increase( spell_damage_increase().addToValue( item->spell_damage_increase() ) );
53✔
2560
  if ( item->has_faster_casting() )
157✔
2561
    faster_casting( faster_casting().addToValue( item->faster_casting() ) );
58✔
2562
  if ( item->has_faster_cast_recovery() )
157✔
2563
    faster_cast_recovery( faster_cast_recovery().addToValue( item->faster_cast_recovery() ) );
59✔
2564
  if ( item->has_lower_mana_cost() )
157✔
2565
    lower_mana_cost( lower_mana_cost().addToValue( item->lower_mana_cost() ) );
56✔
2566
  if ( item->has_hit_chance() )
157✔
2567
    hit_chance( hit_chance().addToValue( item->hit_chance() ) );
57✔
2568
  if ( item->has_luck() )
157✔
2569
    luck( luck().addToValue( item->luck() ) );
54✔
2570
  if ( item->has_swing_speed_increase() )
157✔
2571
    swing_speed_increase( swing_speed_increase().addToValue( item->swing_speed_increase() ) );
52✔
2572
  if ( item->has_min_attack_range_increase() )
157✔
2573
    min_attack_range_increase(
102✔
2574
        min_attack_range_increase().addToValue( item->min_attack_range_increase() ) );
51✔
2575
  if ( item->has_max_attack_range_increase() )
157✔
2576
    max_attack_range_increase(
100✔
2577
        max_attack_range_increase().addToValue( item->max_attack_range_increase() ) );
50✔
2578

2579
  // calc defence increase if lower than cap
2580
  if ( item->has_defence_increase() )
157✔
2581
    defence_increase( defence_increase().addToValue( item->defence_increase() ) );
34✔
2582
}
157✔
2583

2584
void Character::resetEquipableProperties()
164✔
2585
{
2586
  // reset resists
2587
  if ( has_fire_resist() )
164✔
2588
    fire_resist( fire_resist().setAsValue( 0 ) );
13✔
2589
  if ( has_cold_resist() )
164✔
2590
    cold_resist( cold_resist().setAsValue( 0 ) );
15✔
2591
  if ( has_energy_resist() )
164✔
2592
    energy_resist( energy_resist().setAsValue( 0 ) );
14✔
2593
  if ( has_poison_resist() )
164✔
2594
    poison_resist( poison_resist().setAsValue( 0 ) );
11✔
2595
  if ( has_physical_resist() )
164✔
2596
    physical_resist( physical_resist().setAsValue( 0 ) );
12✔
2597

2598
  // reset caps
2599
  if ( has_fire_resist_cap() )
164✔
2600
    fire_resist_cap( fire_resist_cap().setAsValue( 0 ) );
27✔
2601
  if ( has_cold_resist_cap() )
164✔
2602
    cold_resist_cap( cold_resist_cap().setAsValue( 0 ) );
35✔
2603
  if ( has_energy_resist_cap() )
164✔
2604
    energy_resist_cap( energy_resist_cap().setAsValue( 0 ) );
31✔
2605
  if ( has_poison_resist_cap() )
164✔
2606
    poison_resist_cap( poison_resist_cap().setAsValue( 0 ) );
19✔
2607
  if ( has_physical_resist_cap() )
164✔
2608
    physical_resist_cap( physical_resist_cap().setAsValue( 0 ) );
23✔
2609

2610
  // reset damages
2611
  if ( has_fire_damage() )
164✔
2612
    fire_damage( fire_damage().setAsValue( 0 ) );
95✔
2613
  if ( has_cold_damage() )
164✔
2614
    cold_damage( cold_damage().setAsValue( 0 ) );
97✔
2615
  if ( has_energy_damage() )
164✔
2616
    energy_damage( energy_damage().setAsValue( 0 ) );
96✔
2617
  if ( has_poison_damage() )
164✔
2618
    poison_damage( poison_damage().setAsValue( 0 ) );
93✔
2619
  if ( has_physical_damage() )
164✔
2620
    physical_damage( physical_damage().setAsValue( 0 ) );
94✔
2621

2622
  // reset others
2623
  if ( has_lower_reagent_cost() )
164✔
2624
    lower_reagent_cost( lower_reagent_cost().setAsValue( 0 ) );
61✔
2625
  if ( has_spell_damage_increase() )
164✔
2626
    spell_damage_increase( spell_damage_increase().setAsValue( 0 ) );
59✔
2627
  if ( has_faster_casting() )
164✔
2628
    faster_casting( faster_casting().setAsValue( 0 ) );
64✔
2629
  if ( has_faster_cast_recovery() )
164✔
2630
    faster_cast_recovery( faster_cast_recovery().setAsValue( 0 ) );
65✔
2631

2632
  if ( has_defence_increase() )
164✔
2633
    defence_increase( defence_increase().setAsValue( 0 ) );
39✔
2634
  if ( has_defence_increase_cap() )
164✔
2635
    defence_increase_cap( defence_increase_cap().setAsValue( 0 ) );
43✔
2636
  if ( has_lower_mana_cost() )
164✔
2637
    lower_mana_cost( lower_mana_cost().setAsValue( 0 ) );
62✔
2638
  if ( has_hit_chance() )
164✔
2639
    hit_chance( hit_chance().setAsValue( 0 ) );
63✔
2640
  if ( has_luck() )
164✔
2641
    luck( luck().setAsValue( 0 ) );
60✔
2642
  if ( has_swing_speed_increase() )
164✔
2643
    swing_speed_increase( swing_speed_increase().setAsValue( 0 ) );
61✔
2644
  if ( has_min_attack_range_increase() )
164✔
2645
    min_attack_range_increase( min_attack_range_increase().setAsValue( 0 ) );
57✔
2646
  if ( has_max_attack_range_increase() )
164✔
2647
    max_attack_range_increase( max_attack_range_increase().setAsValue( 0 ) );
57✔
2648
}
164✔
2649

2650
void Character::showarmor() const
×
2651
{
2652
  if ( client != nullptr )
×
2653
  {
2654
    Core::send_sysmessage( client, "Your armor coverage:" );
×
2655
    for ( unsigned i = 0; i < armor_.size(); ++i )
×
2656
    {
2657
      std::string text = Core::gamestate.armorzones[i].name + ": ";
×
2658
      if ( armor_[i] == nullptr )
×
2659
        text += "Nothing";
×
2660
      else
2661
        text += armor_[i]->name();
×
2662
      Core::send_sysmessage( client, text );
×
2663
    }
×
2664
  }
2665
}
×
2666

2667
/* check skill: test skill, advance, reset atrophy timers, blah blah..
2668
   obviously, needs work, and more parameters.
2669
   */
2670

2671
bool Character::check_skill( Core::USKILLID skillid, int difficulty, unsigned short pointvalue )
×
2672
{
2673
  INC_PROFILEVAR( skill_checks );
×
2674
  static bool in_here = false;
2675
  if ( !in_here && Core::gamestate.system_hooks.check_skill_hook )
×
2676
  {
2677
    in_here = true;
×
2678
    bool res = Core::gamestate.system_hooks.check_skill_hook->call(
×
2679
        new Module::ECharacterRefObjImp( this ), new Bscript::BLong( skillid ),
×
2680
        new Bscript::BLong( difficulty ), new Bscript::BLong( pointvalue ) );
×
2681
    in_here = false;
×
2682
    return res;
×
2683
  }
2684

2685
  return false;
×
2686
}
2687

2688
void Character::check_concealment_level()
×
2689
{
2690
  if ( concealed() > cmdlevel() )
×
2691
    concealed( cmdlevel() );
×
2692
}
×
2693

2694
// you can only be concealed from
2695
// those of lower stature
2696
bool Character::is_concealed_from_me( const Character* chr ) const
193✔
2697
{
2698
  return ( chr->concealed() > cmdlevel() );
193✔
2699
}
2700

2701
bool Character::is_visible_to_me( const Character* chr, bool check_range ) const
326✔
2702
{
2703
  if ( chr == nullptr )
326✔
2704
    return false;
×
2705
  if ( chr == this )
326✔
2706
    return true;  // I can always see myself (?)
149✔
2707
  if ( is_concealed_from_me( chr ) )
177✔
2708
    return false;
×
2709
  if ( !chr->logged_in() )
177✔
2710
    return false;
×
2711
  if ( chr->hidden() && !cached_settings.get( PRIV_FLAGS::SEE_HIDDEN ) )
177✔
2712
    return false;  // noone can see anyone hidden.
×
2713
  if ( check_range )
177✔
2714
  {
2715
    if ( !in_visual_range( chr ) )
152✔
2716
      return false;
54✔
2717
  }
2718
  else if ( chr->realm() != this->realm() )
25✔
2719
    return false;
1✔
2720

2721
  if ( dead() )
122✔
2722
    return true;  // If I'm dead, I can see anything
×
2723
  if ( !chr->dead() || cached_settings.get( PRIV_FLAGS::SEE_GHOSTS ) )
122✔
2724
    return true;  // Anyone can see the living
122✔
2725
  if ( chr->warmode() )
×
2726
    return true;  // Anyone can see someone in warmode
×
2727
  return false;
×
2728
};
2729

2730
// NOTE: chr is at new position, lastpos have old position.
2731
void PropagateMove( /*Client *client,*/ Character* chr )
168✔
2732
{
2733
  using namespace Network;
2734
  if ( chr == nullptr )
168✔
2735
    return;
×
2736
  RemoveObjectPkt msgremove( chr->serial_ext );
168✔
2737
  HealthBarStatusUpdate msgpoison( chr->serial_ext, HealthBarStatusUpdate::Color::GREEN,
2738
                                   chr->poisoned() );
168✔
2739
  HealthBarStatusUpdate msginvul( chr->serial_ext, HealthBarStatusUpdate::Color::YELLOW,
2740
                                  chr->invul() );
168✔
2741
  PktHelper::PacketOut<PktOut_78> msgcreate;
168✔
2742
  MoveChrPkt msgmove( chr );
168✔
2743
  build_owncreate( chr, msgcreate.Get() );
168✔
2744

2745
  Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
168✔
2746
      chr,
2747
      [&]( Character* zonechr )
168✔
2748
      {
2749
        Client* client = zonechr->client;
153✔
2750
        if ( zonechr == chr )
153✔
2751
          return;
105✔
2752
        if ( !zonechr->is_visible_to_me( chr ) )
48✔
2753
          return;
32✔
2754
        /* The two characters exist, and are in range of each other.
2755
        Character 'chr''s lastpos coordinates are valid.
2756
        SO, if lastpos are out of range of client->chr, we
2757
        should send a 'create' type message.  If they are in range,
2758
        we should just send a move.
2759
        */
2760
        if ( chr->move_reason == Character::MULTIMOVE )
16✔
2761
        {
2762
          if ( client->ClientType & Network::CLIENTTYPE_7090 )
×
2763
          {
2764
            if ( chr->poisoned() )  // if poisoned send 0x17 for newer clients
×
2765
              msgpoison.Send( client );
×
2766

2767
            if ( chr->invul() )  // if invul send 0x17 for newer clients
×
2768
              msginvul.Send( client );
×
2769
            return;
×
2770
          }
2771

2772
      // NOTE: uncomment this line to make movement smoother (no stepping anims)
2773
// but basically makes it very difficult to talk while the ship
2774
// is moving.
2775
#ifdef PERGON
2776
          send_remove_character( client, chr, msgremove );
2777
#else
2778
// send_remove_character( client, chr );
2779
#endif
2780
          send_owncreate( client, chr, msgcreate.Get() );
×
2781
          if ( chr->poisoned() )
×
2782
            msgpoison.Send( client );
×
2783
          if ( chr->invul() )
×
2784
            msginvul.Send( client );
×
2785
        }
2786
        else if ( zonechr->in_visual_range( nullptr, chr->lastpos ) )
16✔
2787
        {
2788
          msgmove.Send( client );
10✔
2789
          if ( chr->poisoned() )
10✔
2790
            msgpoison.Send( client );
×
2791
          if ( chr->invul() )
10✔
2792
            msginvul.Send( client );
2✔
2793
        }
2794
        else
2795
        {
2796
          send_owncreate( client, chr, msgcreate.Get() );
6✔
2797
          if ( chr->poisoned() )
6✔
2798
            msgpoison.Send( client );
×
2799
          if ( chr->invul() )
6✔
2800
            msginvul.Send( client );
1✔
2801
        }
2802
      } );
2803

2804
  // iter over all old in range players and send remove
2805
  Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
168✔
2806
      chr->lastpos,
168✔
2807
      [&]( Character* zonechr )
168✔
2808
      {
2809
        Client* client = zonechr->client;
131✔
2810
        if ( !zonechr->in_visual_range( nullptr, chr->lastpos ) )
131✔
2811
          return;
34✔
2812
        if ( !zonechr->is_visible_to_me( chr, /*check_range*/ false ) )
97✔
2813
          return;
1✔
2814

2815
        if ( zonechr->in_visual_range( chr ) )  // already handled
96✔
2816
          return;
87✔
2817
        // if we just walked out of range of this character, send its
2818
        // client a remove object, or else a ghost character will remain.
2819
        send_remove_character( client, chr, msgremove );
9✔
2820
      } );
2821
}
168✔
2822

2823
void Character::swing_task_func( Character* chr )
11✔
2824
{
2825
  THREAD_CHECKPOINT( tasks, 800 );
11✔
2826
  INFO_PRINTLN_TRACE( 20 )( "swing_task_func({:#x})", chr->serial );
11✔
2827
  chr->mob_flags_.set( MOB_FLAGS::READY_TO_SWING );
11✔
2828
  chr->check_attack_after_move( false );
11✔
2829
  THREAD_CHECKPOINT( tasks, 899 );
11✔
2830
}
11✔
2831

2832
void Character::schedule_attack()
25✔
2833
{
2834
  INFO_PRINTLN_TRACE( 18 )( "schedule_attack({:#x})", this->serial );
25✔
2835
  // we'll get here with a swing_task already set, if
2836
  // while in an adjacent cell to your opponent, you turn/move
2837
  // while waiting for your timeout.
2838
  if ( swing_task == nullptr )
25✔
2839
  {
2840
    unsigned int weapon_speed = weapon->speed();
17✔
2841
    unsigned int weapon_delay = weapon->delay();
17✔
2842
    Core::polclock_t clocks;
2843

2844
    if ( !weapon_delay )
17✔
2845
    {
2846
      INFO_PRINTLN_TRACE( 19 )
17✔
2847
      ( "clocks[speed] = ({}*15000)/(({}+100)*{})", Core::POLCLOCKS_PER_SEC, dexterity(),
×
2848
        weapon_speed );
2849

2850
      clocks = Core::POLCLOCKS_PER_SEC * static_cast<Core::polclock_t>( 15000L );
17✔
2851
      clocks /= ( dexterity() + 100 ) * weapon_speed;
17✔
2852
    }
2853
    else
2854
    {
2855
      int delay_sum = weapon_delay + delay_mod();
×
2856
      if ( delay_sum < 0 )
×
2857
        delay_sum = 0;
×
2858

2859
      INFO_PRINTLN_TRACE( 19 )
×
2860
      ( "clocks[delay] = (({}+{}={})*{})/1000", weapon_delay, delay_mod(), delay_sum,
×
2861
        Core::POLCLOCKS_PER_SEC );
2862

2863
      clocks = ( delay_sum * Core::POLCLOCKS_PER_SEC ) / 1000;
×
2864
    }
2865
    // Swing speed modifier can't be less than -1 otherwise it would give a negitive swing speed and
2866
    // thats not possible.
2867
    double speed_modifier = ( swing_speed_increase().sum() / 100.0 );
17✔
2868
    if ( speed_modifier < -0.99 )
17✔
2869
      speed_modifier = -0.99;
×
2870
    clocks = static_cast<Core::polclock_t>( round( clocks / ( 1 + speed_modifier ) ) );
17✔
2871

2872
    if ( clocks < ( Core::POLCLOCKS_PER_SEC / 5 ) )
17✔
2873
    {
2874
      INFO_PRINTLN_TRACE( 20 )( "{} attack timer: {}", name(), clocks );
6✔
2875
    }
2876
    INFO_PRINTLN_TRACE( 19 )( "clocks={}", clocks );
17✔
2877

2878
    new Core::OneShotTaskInst<Character*>( &swing_task, swing_timer_start_clock_ + clocks + 1,
17✔
2879
                                           swing_task_func, this );
17✔
2880
  }
2881
}
25✔
2882

2883
void Character::reset_swing_timer()
105✔
2884
{
2885
  INFO_PRINTLN_TRACE( 15 )( "reset_swing_timer({:#x})", this->serial );
105✔
2886
  mob_flags_.remove( MOB_FLAGS::READY_TO_SWING );
105✔
2887

2888
  swing_timer_start_clock_ = Core::polclock();
105✔
2889
  if ( swing_task )
105✔
2890
    swing_task->cancel();
6✔
2891

2892
  if ( opponent_ || !opponent_of.empty() )
105✔
2893
  {
2894
    schedule_attack();
13✔
2895
  }
2896
}
105✔
2897

2898
bool Character::manual_set_swing_timer( Core::polclock_t clocks )
×
2899
{
2900
  INFO_PRINTLN_TRACE( 15 )
×
2901
  ( "manual_set_swing_timer({:#x}) delay: {}", this->serial, clocks );
×
2902
  mob_flags_.remove( MOB_FLAGS::READY_TO_SWING );
×
2903

2904
  swing_timer_start_clock_ = Core::polclock();
×
2905
  if ( swing_task )
×
2906
    swing_task->cancel();
×
2907

2908
  if ( opponent_ || !opponent_of.empty() )
×
2909
  {
2910
    new Core::OneShotTaskInst<Character*>( &swing_task, swing_timer_start_clock_ + clocks + 1,
×
2911
                                           swing_task_func, this );
×
2912
    return true;
×
2913
  }
2914
  return false;
×
2915
}
2916

2917
/* The highlighted character is:
2918
     Your selected_opponent, if you have one.
2919
     If not, then the first char that has you as their opponent.
2920
     Or, noone.
2921
     */
2922
Attackable Character::get_opponent() const
10✔
2923
{
2924
  if ( opponent_ )
10✔
2925
    return opponent_;
×
2926
  if ( !opponent_of.empty() )
10✔
2927
    return *opponent_of.begin();
×
2928
  return {};
10✔
2929
}
2930

2931
bool Character::is_attackable( const Attackable& attackable ) const
23✔
2932
{
2933
  passert( attackable.object() != nullptr );
23✔
2934
  const auto* obj = attackable.object();
23✔
2935
  if ( Core::settingsManager.combat_config.scripted_attack_checks )
23✔
2936
  {
2937
    INFO_PRINTLN_TRACE( 21 )
×
NEW
2938
    ( "is_attackable({:#x},{:#x}): will be handled by combat hook.", this->serial, obj->serial );
×
2939
    return true;
×
2940
  }
2941

2942
  const auto* mob = attackable.mobile();
23✔
2943
  INFO_PRINTLN_TRACE( 21 )
23✔
2944
  ( "is_attackable({:#x},{:#x}):\n"
×
2945
    "  who->dead:  {}\n"
2946
    "  wpn->inrange: {}\n"
2947
    "  hidden:     {}\n"
2948
    "  who->hidden:  {}\n"
2949
    "  concealed:  {}",
NEW
2950
    this->serial, obj->serial, mob ? mob->dead() : false, weapon->in_range( this, attackable ),
×
NEW
2951
    hidden(), mob ? mob->hidden() : false, mob ? is_concealed_from_me( mob ) : false );
×
2952

2953
  if ( mob && mob->dead() )
23✔
UNCOV
2954
    return false;
×
2955
  if ( !weapon->in_range( this, attackable ) )
23✔
2956
    return false;
3✔
2957
  if ( hidden() && !cached_settings.get( PRIV_FLAGS::HIDDEN_ATTACK ) )
20✔
2958
    return false;
2✔
2959
  if ( mob )
18✔
2960
  {
2961
    if ( mob->hidden() && !cached_settings.get( PRIV_FLAGS::ATTACK_HIDDEN ) )
18✔
2962
      return false;
2✔
2963
    if ( is_concealed_from_me( mob ) )
16✔
NEW
2964
      return false;
×
2965
  }
2966
  if ( !realm()->has_los( *this, *obj ) )
16✔
UNCOV
2967
    return false;
×
2968
  return true;
16✔
2969
}
2970

2971
Attackable Character::get_attackable_opponent() const
188✔
2972
{
2973
  if ( opponent_ )
188✔
2974
  {
2975
    INFO_PRINTLN_TRACE( 20 )
12✔
NEW
2976
    ( "get_attackable_opponent({:#x}): checking opponent {:#x}", this->serial,
×
NEW
2977
      opponent_.object()->serial );
×
2978
    if ( is_attackable( opponent_ ) )
12✔
2979
      return opponent_;
9✔
2980
  }
2981

2982
  if ( !opponent_of.empty() )
179✔
2983
  {
2984
    for ( auto& who : opponent_of )
15✔
2985
    {
2986
      INFO_PRINTLN_TRACE( 20 )
11✔
NEW
2987
      ( "get_attackable_opponent({:#x}): checking opponent_of {:#x}", this->serial,
×
NEW
2988
        who.object()->serial );
×
2989
      if ( is_attackable( who ) )
11✔
2990
        return who;
7✔
2991
    }
2992
  }
2993

2994
  return {};
172✔
2995
}
2996

2997
void Character::send_highlight() const
91✔
2998
{
2999
  if ( client != nullptr && has_active_client() )
91✔
3000
  {
3001
    auto opponent = get_opponent();
4✔
3002

3003
    Network::PktHelper::PacketOut<Network::PktOut_AA> msg;
4✔
3004
    if ( opponent )
4✔
NEW
3005
      msg->Write<u32>( opponent.object()->serial_ext );
×
3006
    else
3007
      msg->offset += 4;
4✔
3008
    msg.Send( client );
4✔
3009
  }
4✔
3010
}
91✔
3011

3012
void Character::on_swing_failure( Character* /*attacker*/ )
×
3013
{
3014
  // do nothing
3015
}
×
3016

NEW
3017
void Character::inform_disengaged( const Attackable& /*disengaged*/ )
×
3018
{
3019
  // someone has just disengaged. If we don't have an explicit opponent,
3020
  // pick one of those that has us targetted as the highlight character.
NEW
3021
  if ( !opponent_ )
×
3022
    send_highlight();
×
3023
}
×
3024

3025
void Character::inform_engaged( const Attackable& /*engaged*/ )
1✔
3026
{
3027
  // someone has targetted us.  If we don't have an explicit opponent,
3028
  // pick one of those that has us targetted as the highlight character.
3029
  if ( !opponent_ )
1✔
3030
    send_highlight();
1✔
3031
}
1✔
3032

3033
void Character::inform_criminal( Character* /*moved*/ )
×
3034
{
3035
  // virtual that does nothing at character level, but fires event for NPCs
3036
}
×
3037

3038
void Character::inform_leftarea( Character* /*wholeft*/ )
1✔
3039
{
3040
  // virtual that does nothing at character level, but fires event for NPCs
3041
}
1✔
3042

3043
void Character::inform_enteredarea( Character* /*whoentered*/ )
22✔
3044
{
3045
  // virtual that does nothing at character level, but fires event for NPCs
3046
}
22✔
3047

3048
void Character::inform_moved( Character* /*moved*/ )
29✔
3049
{
3050
  // consider moving PropagateMove here!
3051
}
29✔
3052
void Character::inform_imoved( Character* /*chr*/ ) {}
33✔
3053

3054
void Character::set_opponent( Attackable new_opponent, bool inform_old_opponent )
88✔
3055
{
3056
  INFO_PRINTLN_TRACE( 12 )
88✔
NEW
3057
  ( "set_opponent({:#x},{:#x})", this->serial, new_opponent ? new_opponent.object()->serial : 0 );
×
3058
  if ( new_opponent )
88✔
3059
  {
3060
    if ( auto* mob = new_opponent.mobile(); mob && mob->dead() )
4✔
3061
      return;
×
3062

3063
    if ( !warmode() && ( script_isa( Core::POLCLASS_NPC ) || has_active_client() ) )
4✔
3064
      set_warmode( true );
1✔
3065
  }
3066

3067
  Attackable this_att{ this };
88✔
3068
  if ( opponent_ )
88✔
3069
  {
3070
    opponent_.remove_opponent_of( this_att );
4✔
3071
    // Turley 05/26/09 no need to send disengaged event on shutdown
3072
    if ( !Clib::exit_signalled )
4✔
3073
    {
3074
      if ( inform_old_opponent )
4✔
3075
        opponent_.inform_disengaged( this_att );
4✔
3076
    }
3077
  }
3078

3079
  opponent_ = std::move( new_opponent );
88✔
3080

3081

3082
  // Turley 05/26/09 possible shutdown crashfix during cleanup
3083
  // (inside schedule_attack() the rest is also senseless on shutdowncleanup)
3084
  if ( !Clib::exit_signalled )
88✔
3085
  {
3086
    reset_swing_timer();
88✔
3087

3088
    if ( opponent_ )
88✔
3089
    {
3090
      auto* mob = opponent_.mobile();
4✔
3091
      if ( mob )
4✔
3092
      {
3093
        repsys_on_attack( mob );
4✔
3094
        if ( !mob->get_opponent() )
4✔
3095
          mob->reset_swing_timer();
4✔
3096
      }
3097

3098
      opponent_.add_opponent_of( this_att );
4✔
3099
      opponent_.inform_engaged( this_att );
4✔
3100
      if ( mob )
4✔
3101
        mob->schedule_attack();
4✔
3102
    }
3103
  }
3104

3105
  send_highlight();
88✔
3106
}
3107

NEW
3108
void Character::select_opponent( Attackable opponent )
×
3109
{
3110
  // test for setting to same so swing timer doesn't reset
3111
  // if you double-click the same guy over and over
NEW
3112
  if ( !opponent_ || opponent_.object()->serial != opponent.object()->serial )
×
3113
  {
NEW
3114
    if ( realm() != opponent.object()->realm() )
×
NEW
3115
      return;
×
NEW
3116
    set_opponent( std::move( opponent ) );
×
3117
  }
3118
}
3119

3120
void Character::disable_regeneration_for( int seconds )
4✔
3121
{
3122
  time_t new_disable_time = Core::poltime() + seconds;
4✔
3123
  if ( new_disable_time > disable_regeneration_until )
4✔
3124
    disable_regeneration_until = new_disable_time;
4✔
3125
}
4✔
3126

3127
bool Character::warmode() const
263✔
3128
{
3129
  return mob_flags_.get( MOB_FLAGS::WARMODE );
263✔
3130
}
3131

3132
void Character::set_warmode( bool i_warmode )
4✔
3133
{
3134
  if ( Core::gamestate.system_hooks.warmode_change )
4✔
3135
    Core::gamestate.system_hooks.warmode_change->call( new Module::ECharacterRefObjImp( this ),
×
3136
                                                       new Bscript::BLong( i_warmode ) );
×
3137

3138
  if ( warmode() != i_warmode )
4✔
3139
  {
3140
    disable_regeneration_for( 2 );
4✔
3141
  }
3142

3143
  mob_flags_.change( MOB_FLAGS::WARMODE, i_warmode );
4✔
3144
  if ( i_warmode == false )
4✔
3145
  {
3146
    set_opponent( {} );
1✔
3147
  }
3148
  reset_swing_timer();
4✔
3149

3150
  if ( dead() )
4✔
3151
  {
3152
    if ( warmode() )  // if entered warmode, display me now
×
3153
    {
3154
      send_create_mobile_to_nearby_cansee( this );
×
3155
    }
3156
    else  // if leaving warmode, I go invisible.
3157
    {
3158
      send_remove_character_to_nearby_cantsee( this );
×
3159
    }
3160
  }
3161
  else
3162
  {
3163
    Network::MoveChrPkt msgmove( this );
4✔
3164
    Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
4✔
3165
        this,
3166
        [&]( Character* chr )
4✔
3167
        {
3168
          if ( chr == this )
4✔
3169
            return;
2✔
3170
          if ( !chr->is_visible_to_me( this ) )
2✔
3171
            return;
×
3172

3173
          msgmove.Send( chr->client );
2✔
3174
        } );
3175
  }
4✔
3176
}
4✔
3177

3178
const AttributeValue& Character::weapon_attribute() const
16✔
3179
{
3180
  return attribute( weapon->attribute().attrid );
16✔
3181
}
3182

3183
unsigned short Character::random_weapon_damage() const
8✔
3184
{
3185
  return weapon->get_random_damage();
8✔
3186
}
3187

3188
unsigned short Character::min_weapon_damage() const
81✔
3189
{
3190
  return weapon->min_weapon_damage();
81✔
3191
}
3192

3193
unsigned short Character::max_weapon_damage() const
81✔
3194
{
3195
  return weapon->max_weapon_damage();
81✔
3196
}
3197

3198
void Character::damage_weapon()
8✔
3199
{
3200
  if ( !weapon->is_intrinsic() && !weapon->is_projectile() )
8✔
3201
  {
3202
    weapon->reduce_hp_from_hit();
×
3203
  }
3204
}
8✔
3205

3206
Items::UArmor* Character::choose_armor() const
8✔
3207
{
3208
  double f = Clib::random_double( Core::gamestate.armor_zone_chance_sum );
8✔
3209
  for ( unsigned zone = 0; zone < Core::gamestate.armorzones.size(); ++zone )
15✔
3210
  {
3211
    f -= Core::gamestate.armorzones[zone].chance;
15✔
3212
    if ( f <= 0.0 )
15✔
3213
    {
3214
      return armor_[zone];
8✔
3215
    }
3216
  }
3217
  return nullptr;
×
3218
}
3219

3220
Core::UACTION Character::weapon_anim() const
8✔
3221
{
3222
  if ( on_mount() )
8✔
3223
    return weapon->mounted_anim();
×
3224
  return weapon->anim();
8✔
3225
}
3226

3227
void Character::do_attack_effects( const Attackable& target )
8✔
3228
{
3229
  if ( weapon->is_projectile() )
8✔
3230
  {
3231
    // 234 is hit, 238 is miss??
3232
    play_sound_effect( this, weapon->projectile_sound() );
×
NEW
3233
    play_moving_effect( this, target.object(), weapon->projectile_anim(),
×
3234
                        9,    // Speed (??)
3235
                        0,    // Loop
3236
                        0 );  // Explode
3237
    if ( graphic >= UOBJ_HUMAN_MALE )
×
3238
    {
3239
      send_action_to_inrange( this, weapon_anim() );
×
3240
    }
3241
    else
3242
    {
3243
      send_action_to_inrange( this, Core::ACTION_LOOK_AROUND );
×
3244
    }
3245
  }
3246
  else if ( graphic >= UOBJ_HUMAN_MALE )
8✔
3247
  {
3248
    send_action_to_inrange( this, weapon_anim() );
8✔
3249
  }
3250
  else
3251
  {
3252
    send_action_to_inrange( this, Core::ACTION_LOOK_AROUND );
×
3253
  }
3254
}
8✔
3255

3256
void Character::do_hit_success_effects()
8✔
3257
{
3258
  unsigned short sound = weapon->hit_sound();
8✔
3259
  if ( sound )
8✔
3260
    play_sound_effect( this, sound );
×
3261
}
8✔
3262

3263
void Character::do_hit_failure_effects()
×
3264
{
3265
  unsigned short sound = weapon->miss_sound();
×
3266
  if ( sound )
×
3267
    play_sound_effect( this, sound );
×
3268
}
×
3269

3270
u16 Character::get_damaged_sound() const
×
3271
{
3272
  if ( gender == Plib::GENDER_MALE )
×
3273
    return SOUND_EFFECT_MALE_DEFENSE;
×
3274
  return SOUND_EFFECT_FEMALE_DEFENSE;
×
3275
}
3276

3277
void Character::do_imhit_effects()
×
3278
{
3279
  if ( Core::settingsManager.combat_config.core_hit_sounds )
×
3280
  {
3281
    play_sound_effect( this, get_damaged_sound() );
×
3282
  }
3283
  if ( objtype_ >= UOBJ_HUMAN_MALE )
×
3284
    send_action_to_inrange( this, Core::ACTION_GOT_HIT );
×
3285
}
×
3286

3287

3288
void Character::attack( const Attackable& opponent )
8✔
3289
{
3290
  INC_PROFILEVAR( combat_operations );
8✔
3291

3292
  if ( Core::gamestate.system_hooks.attack_hook )
8✔
3293
  {
NEW
3294
    if ( Core::gamestate.system_hooks.attack_hook->call( new Module::ECharacterRefObjImp( this ),
×
NEW
3295
                                                         opponent.object()->make_ref() ) )
×
UNCOV
3296
      return;
×
3297
  }
3298

3299
  if ( Core::settingsManager.watch.combat )
8✔
NEW
3300
    INFO_PRINTLN( "{} attacks {}", name(), opponent.object()->name() );
×
3301

3302
  if ( weapon->is_projectile() )
8✔
3303
  {
3304
    Core::UContainer* bp = backpack();
×
3305
    int check_consume_hook = -1;
×
3306
    if ( Core::gamestate.system_hooks.consume_ammunition_hook )
×
3307
    {
3308
      check_consume_hook = Core::gamestate.system_hooks.consume_ammunition_hook->call_long(
×
3309
          new Module::ECharacterRefObjImp( this ), new Module::EItemRefObjImp( weapon ) );
×
3310
      if ( check_consume_hook == 0 )
×
3311
      {
3312
        return;
×
3313
      }
3314
    }
3315

3316
    if ( ( check_consume_hook == -1 ) &&
×
3317
         ( ( bp == nullptr ) || ( weapon->consume_projectile( bp ) == false ) ) )
×
3318
    {
3319
      // 04/2007 - MuadDib
3320
      // Range through wornitems to find containers and check
3321
      // here also if backpack fails. Use the mainpack first this way.
3322
      bool projectile_check = false;
×
3323
      for ( unsigned layer = Core::LAYER_EQUIP__LOWEST; layer <= Core::LAYER_EQUIP__HIGHEST;
×
3324
            layer++ )
3325
      {
3326
        Items::Item* item = wornitems->GetItemOnLayer( layer );
×
3327
        if ( item )
×
3328
        {
3329
          if ( item != nullptr && item->script_isa( Core::POLCLASS_CONTAINER ) )
×
3330
          {
3331
            if ( layer != Core::LAYER_HAIR && layer != Core::LAYER_FACE &&
×
3332
                 layer != Core::LAYER_BEARD && layer != Core::LAYER_BACKPACK &&
×
3333
                 layer != Core::LAYER_MOUNT )
3334
            {
3335
              Core::UContainer* cont = static_cast<Core::UContainer*>( item );
×
3336

3337
              if ( weapon->consume_projectile( cont ) == true )
×
3338
              {
3339
                projectile_check = true;
×
3340
                break;
×
3341
              }
3342
            }
3343
          }
3344
        }
3345
      }
3346
      // I'm out of projectiles.
3347
      if ( projectile_check == false )
×
3348
        return;
×
3349
    }
3350
  }
3351

3352
  auto* opponent_mobile = opponent.mobile();
8✔
3353
  if ( opponent_mobile )
8✔
3354
  {
3355
    repsys_on_attack( opponent_mobile );
8✔
3356
    repsys_on_damage( opponent_mobile );
8✔
3357
  }
3358

3359
  do_attack_effects( opponent );
8✔
3360

3361
  if ( auto* item = opponent.item() )
8✔
3362
  {
3363
    // always hit?
3364
    // TODO Attackable cleanup, runhitscript maybe?
NEW
3365
    do_hit_success_effects();
×
3366

NEW
3367
    double damage = random_weapon_damage();
×
NEW
3368
    damage_weapon();
×
3369

NEW
3370
    double damage_multiplier = attribute( Core::gamestate.pAttrTactics->attrid ).effective() + 50;
×
NEW
3371
    damage_multiplier += strength() * 0.20f;
×
NEW
3372
    damage_multiplier *= 0.01f;
×
3373

NEW
3374
    damage *= damage_multiplier;
×
3375
    //    if ( weapon->hit_script().empty() )
3376
    //    {
NEW
3377
    item->apply_damage( Clib::clamp_convert<u16>( damage ), this,
×
NEW
3378
                        Core::settingsManager.combat_config.send_damage_packet );
×
3379
    //    }
3380
    //    else
3381
    //    {
3382
    //      run_hit_script( opponent_mobile, damage );
3383
    //    }
3384

NEW
3385
    return;
×
3386
  }
3387

3388
  if ( Core::gamestate.system_hooks.combat_advancement_hook )
8✔
3389
  {
3390
    Core::gamestate.system_hooks.combat_advancement_hook->call(
×
3391
        new Module::ECharacterRefObjImp( this ), new Module::EItemRefObjImp( weapon ),
×
NEW
3392
        new Module::ECharacterRefObjImp( opponent_mobile ) );
×
3393
  }
3394

3395
  double hit_chance = ( weapon_attribute().effective() + 50.0 ) /
8✔
3396
                      ( 2.0 * ( opponent_mobile->weapon_attribute().effective() + 50.0 ) );
8✔
3397
  hit_chance += hitchance_mod() * 0.001f;
8✔
3398
  hit_chance -= opponent_mobile->evasionchance_mod() * 0.001f;
8✔
3399
  if ( Core::settingsManager.watch.combat )
8✔
3400
    INFO_PRINT( "Chance to hit: {}: ", hit_chance );
×
3401
  if ( Clib::random_double( 1.0 ) < hit_chance )
8✔
3402
  {
3403
    if ( Core::settingsManager.watch.combat )
8✔
3404
      INFO_PRINTLN( "Hit!" );
×
3405
    do_hit_success_effects();
8✔
3406

3407
    double damage = random_weapon_damage();
8✔
3408
    damage_weapon();
8✔
3409

3410
    if ( Core::settingsManager.watch.combat )
8✔
3411
      INFO_PRINTLN( "Base damage: {}", damage );
×
3412

3413
    double damage_multiplier = attribute( Core::gamestate.pAttrTactics->attrid ).effective() + 50;
8✔
3414
    damage_multiplier += strength() * 0.20f;
8✔
3415
    damage_multiplier *= 0.01f;
8✔
3416

3417
    damage *= damage_multiplier;
8✔
3418

3419
    if ( Core::settingsManager.watch.combat )
8✔
3420
      INFO_PRINTLN( "Damage multiplier due to tactics/STR: {} Result: {}", damage_multiplier,
×
3421
                    damage );
3422

3423
    if ( opponent_mobile->shield != nullptr )
8✔
3424
    {
3425
      if ( Core::gamestate.system_hooks.parry_advancement_hook )
×
3426
      {
3427
        Core::gamestate.system_hooks.parry_advancement_hook->call(
×
3428
            new Module::ECharacterRefObjImp( this ), new Module::EItemRefObjImp( weapon ),
×
NEW
3429
            new Module::ECharacterRefObjImp( opponent_mobile ),
×
NEW
3430
            new Module::EItemRefObjImp( opponent_mobile->shield ) );
×
3431
      }
3432

3433
      double parry_chance =
NEW
3434
          opponent_mobile->attribute( Core::gamestate.pAttrParry->attrid ).effective() / 200.0;
×
NEW
3435
      parry_chance += opponent_mobile->parrychance_mod() * 0.001f;
×
3436
      if ( Core::settingsManager.watch.combat )
×
3437
        INFO_PRINT( "Parry Chance: {}: ", parry_chance );
×
3438
      if ( Clib::random_double( 1.0 ) < parry_chance )
×
3439
      {
3440
        if ( Core::settingsManager.watch.combat )
×
NEW
3441
          INFO_PRINTLN( "{} hits deflected", opponent_mobile->shield->ar() );
×
3442
        if ( Core::settingsManager.combat_config.display_parry_success_messages &&
×
NEW
3443
             opponent_mobile->client )
×
NEW
3444
          Core::send_sysmessage( opponent_mobile->client, "You successfully parried the attack!" );
×
3445

NEW
3446
        damage -= opponent_mobile->shield->ar();
×
3447
        if ( damage < 0 )
×
3448
          damage = 0;
×
3449
      }
3450
      else
3451
      {
3452
        if ( Core::settingsManager.watch.combat )
×
3453
          INFO_PRINTLN( "failed." );
×
3454
      }
3455
    }
3456
    if ( weapon->hit_script().empty() )
8✔
3457
    {
NEW
3458
      opponent_mobile->apply_damage( damage, this, true,
×
NEW
3459
                                     Core::settingsManager.combat_config.send_damage_packet );
×
3460
    }
3461
    else
3462
    {
3463
      run_hit_script( opponent_mobile, damage );
8✔
3464
    }
3465
  }
3466
  else
3467
  {
3468
    if ( Core::settingsManager.watch.combat )
×
3469
      INFO_PRINTLN( "Miss!" );
×
NEW
3470
    opponent_mobile->on_swing_failure( this );
×
3471
    do_hit_failure_effects();
×
3472
    if ( Core::gamestate.system_hooks.hitmiss_hook )
×
3473
    {
3474
      Core::gamestate.system_hooks.hitmiss_hook->call(
×
NEW
3475
          new Module::ECharacterRefObjImp( this ),
×
NEW
3476
          new Module::ECharacterRefObjImp( opponent_mobile ) );
×
3477
    }
3478
  }
3479
}
3480

3481
void Character::check_attack_after_move( bool check_opponents_after_check )
188✔
3482
{
3483
  FUNCTION_CHECKPOINT( check_attack_after_move, 1 );
188✔
3484
  auto opponent = get_attackable_opponent();
188✔
3485
  FUNCTION_CHECKPOINT( check_attack_after_move, 2 );
188✔
3486
  INFO_PRINTLN_TRACE( 20 )
188✔
3487
  ( "check_attack_after_move({:#x}): opponent is {:#x}", this->serial,
×
NEW
3488
    opponent ? opponent.object()->serial : 0 );
×
3489
  if ( opponent &&  // and I have an opponent
204✔
3490
       !dead() &&   // If I'm not dead
204✔
3491
       ( Core::settingsManager.combat_config.attack_while_frozen ||
16✔
3492
         ( !paralyzed() && !frozen() ) ) )
×
3493
  {
3494
    FUNCTION_CHECKPOINT( check_attack_after_move, 3 );
16✔
3495
    if ( mob_flags_.get( MOB_FLAGS::READY_TO_SWING ) )  // and I can swing now,
16✔
3496
    {                                                   // do so.
3497
      FUNCTION_CHECKPOINT( check_attack_after_move, 4 );
8✔
3498
      if ( Core::settingsManager.combat_config.send_swing_packet && client != nullptr )
8✔
NEW
3499
        send_fight_occuring( client, opponent.object() );
×
3500

3501
      // we don't want attack() to recursively cause new attacks
3502
      mob_flags_.remove( MOB_FLAGS::READY_TO_SWING );
8✔
3503
      attack( opponent );
8✔
3504
      FUNCTION_CHECKPOINT( check_attack_after_move, 5 );
8✔
3505
      reset_swing_timer();
8✔
3506
      FUNCTION_CHECKPOINT( check_attack_after_move, 6 );
8✔
3507
    }
3508
    else
3509
    {
3510
      FUNCTION_CHECKPOINT( check_attack_after_move, 7 );
8✔
3511
      INFO_PRINTLN_TRACE( 20 )( "not ready to swing" );
8✔
3512
      schedule_attack();
8✔
3513
      FUNCTION_CHECKPOINT( check_attack_after_move, 8 );
8✔
3514
    }
3515
  }
3516
  FUNCTION_CHECKPOINT( check_attack_after_move, 0 );
188✔
3517

3518
  if ( check_opponents_after_check )
188✔
3519
  {
3520
    if ( auto* mob = opponent_.mobile() )
171✔
3521
      mob->check_attack_after_move( false );
3✔
3522

3523
    // attacking can change the opponent_of array drastically.
3524
    AttackableSet tmp( opponent_of );
171✔
3525
    for ( auto& att : tmp )
174✔
3526
    {
3527
      if ( auto* mob = att.mobile() )
3✔
3528
        mob->check_attack_after_move( false );
3✔
3529
    }
3530
  }
171✔
3531
}
188✔
3532

3533

3534
void Character::check_light_region_change()
170✔
3535
{
3536
  auto light_unil = lightoverride_until();
170✔
3537
  if ( light_unil < Core::read_gameclock() && light_unil != ~0u )
170✔
3538
  {
3539
    lightoverride_until( 0 );
170✔
3540
    lightoverride( -1 );
170✔
3541
  }
3542
  if ( client->gd->weather_region && client->gd->weather_region->lightoverride != -1 &&
170✔
3543
       !has_lightoverride() )
×
3544
    return;
×
3545

3546
  int newlightlevel;
3547
  if ( has_lightoverride() )
170✔
3548
    newlightlevel = lightoverride();
×
3549
  else
3550
  {
3551
    // dave 12-22 check for no regions
3552
    Core::LightRegion* light_region = Core::gamestate.lightdef->getregion( pos() );
170✔
3553
    if ( light_region != nullptr )
170✔
3554
      newlightlevel = light_region->lightlevel;
×
3555
    else
3556
      newlightlevel = Core::settingsManager.ssopt.default_light_level;
170✔
3557
  }
3558

3559
  if ( newlightlevel != client->gd->lightlevel )
170✔
3560
  {
3561
    Core::send_light( client, newlightlevel );
2✔
3562
    client->gd->lightlevel = newlightlevel;
2✔
3563
  }
3564
}
3565

3566
void Character::check_justice_region_change()
107✔
3567
{
3568
  Core::JusticeRegion* cur_justice_region = client->gd->justice_region;
107✔
3569
  Core::JusticeRegion* new_justice_region = Core::gamestate.justicedef->getregion( pos() );
107✔
3570

3571
  if ( cur_justice_region != new_justice_region )
107✔
3572
  {
3573
    if ( cur_justice_region != nullptr )
×
3574
      cur_justice_region->RunLeaveScript( client->chr );
×
3575
    if ( new_justice_region != nullptr )
×
3576
      new_justice_region->RunEnterScript( client->chr );
×
3577

3578
    // print 'leaving' message
3579
    bool printmsgs;
3580
    if ( cur_justice_region != nullptr && new_justice_region != nullptr &&
×
3581
         cur_justice_region->entertext() == new_justice_region->entertext() &&
×
3582
         cur_justice_region->leavetext() == new_justice_region->leavetext() )
×
3583
    {
3584
      printmsgs = false;
×
3585
    }
3586
    else
3587
    {
3588
      printmsgs = true;
×
3589
    }
3590

3591
    if ( printmsgs && cur_justice_region )
×
3592
    {
3593
      const std::string& leavetext = cur_justice_region->leavetext();
×
3594
      if ( !leavetext.empty() )
×
3595
      {
3596
        Core::send_sysmessage( client, leavetext );
×
3597
      }
3598
    }
3599

3600
    client->gd->justice_region = new_justice_region;
×
3601

3602
    if ( new_justice_region && new_justice_region->RunNoCombatCheck( client ) == true )
×
3603
    {
NEW
3604
      get_opponent().remove_opponent_of( Attackable{ client->chr } );
×
NEW
3605
      if ( auto* opp2 = get_opponent().mobile(); opp2 && opp2->client )
×
3606
      {
3607
        // TODO Attackable
NEW
3608
        opp2->set_opponent( {}, true );
×
3609
        opp2->schedule_attack();
×
NEW
3610
        opp2->opponent_.clear();
×
3611
        opp2->clear_opponent_of();
×
NEW
3612
        set_opponent( {}, true );
×
3613
        if ( swing_task != nullptr )
×
3614
          swing_task->cancel();
×
3615
      }
3616
    }
3617

3618

3619
    // print 'entering' message
3620
    // handle nocombat while we have entered.
3621
    if ( printmsgs && new_justice_region )
×
3622
    {
3623
      const std::string& entertext = new_justice_region->entertext();
×
3624
      if ( !entertext.empty() )
×
3625
      {
3626
        Core::send_sysmessage( client, entertext );
×
3627
      }
3628
    }
3629
  }
3630
}
107✔
3631

3632
void Character::check_music_region_change()
107✔
3633
{
3634
  Core::MusicRegion* cur_music_region = client->gd->music_region;
107✔
3635
  Core::MusicRegion* new_music_region = Core::gamestate.musicdef->getregion( pos() );
107✔
3636

3637
  // may want to consider changing every n minutes, too, even if region didn't change
3638
  if ( cur_music_region != new_music_region )
107✔
3639
  {
3640
    client->gd->music_region = new_music_region;
×
3641
    if ( new_music_region != nullptr )
×
3642
    {
3643
      Core::send_midi( client, new_music_region->getmidi() );
×
3644
    }
3645
    else
3646
    {
3647
      Core::send_midi( client, 0 );
×
3648
    }
3649
  }
3650
}
107✔
3651

3652
void Character::check_weather_region_change( bool force )  // dave changed 5/26/03 - use force
109✔
3653
                                                           // boolean if current weather region
3654
                                                           // changed type/intensity
3655
{
3656
  Core::WeatherRegion* cur_weather_region = client->gd->weather_region;
109✔
3657
  Core::WeatherRegion* new_weather_region = Core::gamestate.weatherdef->getregion( pos() );
109✔
3658

3659
  // eric 5/31/03: I don't think this is right.  it's possible to go from somewhere that has
3660
  // no weather region, and to walk to somewhere that doesn't have a weather region.
3661
  //
3662
  if ( force || ( cur_weather_region != new_weather_region ) )
109✔
3663
  {
3664
    if ( new_weather_region != nullptr && new_weather_region->lightoverride != -1 &&
2✔
3665
         !has_lightoverride() )
×
3666
    {
3667
      Core::send_light( client, new_weather_region->lightoverride );
×
3668
      client->gd->lightlevel = new_weather_region->lightoverride;
×
3669
    }
3670

3671
    // eric removed this 5/31/03, it's calling itself recursively:
3672
    // move_character_to -> tellmove -> check_region_changes -> check_weather_region_change
3673
    // (here, doh) if you need to send the client something special, just do it.
3674
    // move_character_to(this,x,y,z,0); //dave added 5/26/03: client doesn't refresh properly
3675
    // without a teleport :| and send_goxyz causes weather effects to stop if character is
3676
    // walking/running too
3677

3678
    if ( new_weather_region )
2✔
3679
    {
3680
      Core::send_weather( client, new_weather_region->weathertype, new_weather_region->severity,
×
3681
                          new_weather_region->aux );
×
3682
    }
3683
    else
3684
    {
3685
      Core::send_weather( client, 0xff, 0, 0 );  // turn off
2✔
3686
    }
3687
    client->gd->weather_region = new_weather_region;
2✔
3688
  }
3689
}
109✔
3690

3691
void Character::check_region_changes()
170✔
3692
{
3693
  if ( client != nullptr )
170✔
3694
  {
3695
    check_weather_region_change();
107✔
3696

3697
    check_light_region_change();
107✔
3698

3699
    check_justice_region_change();
107✔
3700

3701
    check_music_region_change();
107✔
3702
  }
3703
}
170✔
3704

3705
void Character::position_changed()
260✔
3706
{
3707
  wornitems->setposition( pos() );
260✔
3708
  position_changed_at_ = Core::polclock();
260✔
3709
}
260✔
3710

3711
void Character::unhide()
3✔
3712
{
3713
  if ( Core::gamestate.system_hooks.un_hide )
3✔
3714
  {
3715
    if ( !Core::gamestate.system_hooks.un_hide->call( make_mobileref( this ) ) )
×
3716
      return;
×
3717
  }
3718

3719
  hidden( false );
3✔
3720
  if ( is_visible() )
3✔
3721
  {
3722
    if ( client != nullptr )
3✔
3723
      send_owncreate( client, this );
×
3724

3725
    Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
3✔
3726
        this,
3727
        [&]( Character* chr )
3✔
3728
        {
3729
          if ( chr == this )
×
3730
            return;
×
3731
          if ( !chr->is_visible_to_me( this ) )
×
3732
            return;
×
3733
          send_owncreate( chr->client, this );
×
3734
        } );
3735

3736
    realm()->notify_unhid( *this );
3✔
3737

3738
    if ( !Clib::exit_signalled )
3✔
3739
    {
3740
      check_attack_after_move( true );
3✔
3741
    }
3742
  }
3743
}
3744

3745
void Character::set_stealthsteps( unsigned short newval )
8✔
3746
{
3747
  stealthsteps_ = newval;
8✔
3748
}
8✔
3749

3750
bool Character::doors_block() const
102✔
3751
{
3752
  return !( graphic == UOBJ_GAMEMASTER || graphic == UOBJ_HUMAN_MALE_GHOST ||
204✔
3753
            graphic == UOBJ_HUMAN_FEMALE_GHOST || graphic == UOBJ_ELF_MALE_GHOST ||
102✔
3754
            graphic == UOBJ_ELF_FEMALE_GHOST || graphic == UOBJ_GARGOYLE_MALE_GHOST ||
102✔
3755
            graphic == UOBJ_GARGOYLE_FEMALE_GHOST ||
102✔
3756
            cached_settings.get( PRIV_FLAGS::IGNORE_DOORS ) );
204✔
3757
}
3758

3759
/*
3760
    This isn't quite right, for hidden characters.
3761
    What normally happens:
3762
    1) Client sends "use skill hiding"
3763
    2) Server sends "77 move" with the hidden flag set.
3764
    3) client sends move-request
3765
    4) server sends move-approve
3766
    5) server sense "78 create" message with hidden flag clear, at new posn.
3767

3768
    We're sending the "78 create" _before_ the move-approve.
3769
    */
3770

3771
bool Character::can_face( Core::UFACING /*i_facing*/ )
28✔
3772
{
3773
  if ( can_freemove() )
28✔
3774
    return true;
×
3775

3776
  if ( frozen() || paralyzed() )
28✔
3777
  {
3778
    if ( client != nullptr )
×
3779
    {
3780
      if ( frozen() )
×
3781
        private_say_above( this, this, "I am frozen and cannot move." );
×
3782
      else if ( paralyzed() )
×
3783
        private_say_above( this, this, "I am paralyzed and cannot move." );
×
3784
    }
3785
    return false;
×
3786
  }
3787

3788
  if ( Core::settingsManager.ssopt.movement_uses_stamina &&
84✔
3789
       vital( Core::gamestate.pVitalStamina->vitalid ).current_ones() == 0 && !dead() )
28✔
3790
  {
3791
    private_say_above( this, this, "You are too fatigued to move." );
×
3792
    return false;
×
3793
  }
3794

3795
  return true;
28✔
3796
}
3797

3798

3799
bool Character::face( Core::UFACING i_facing, int flags )
28✔
3800
{
3801
  if ( ( flags & 1 ) == 0 )
28✔
3802
  {
3803
    if ( !can_face( i_facing ) )
28✔
3804
      return false;
×
3805
  }
3806

3807
  if ( i_facing != facing )
28✔
3808
  {
3809
    setfacing( static_cast<u8>( i_facing ) );
8✔
3810

3811
    if ( Core::settingsManager.combat_config.reset_swing_onturn &&
16✔
3812
         !cached_settings.get( PRIV_FLAGS::FIRE_WHILE_MOVING ) && weapon->is_projectile() )
8✔
3813
      reset_swing_timer();
×
3814
    if ( hidden() && ( Core::settingsManager.ssopt.hidden_turns_count ) )
8✔
3815
    {
3816
      if ( stealthsteps_ == 0 )
×
3817
        unhide();
×
3818
      else
3819
        stealthsteps_--;
×
3820
    }
3821
  }
3822

3823
  return true;
28✔
3824
}
3825

3826

3827
bool Character::CustomHousingMove( unsigned char i_dir )
×
3828
{
3829
  passert( facing < 8 );
×
3830

3831
  Multi::UMulti* multi = Core::system_find_multi( client->gd->custom_house_serial );
×
3832
  if ( multi != nullptr )
×
3833
  {
3834
    Multi::UHouse* house = multi->as_house();
×
3835
    if ( house != nullptr )
×
3836
    {
3837
      Core::UFACING i_facing = static_cast<Core::UFACING>( i_dir & PKTIN_02_FACING_MASK );
×
3838
      if ( i_facing != facing )
×
3839
      {
3840
        setfacing( static_cast<u8>( i_facing ) );
×
3841
        set_dirty();
×
3842
        dir = i_dir;
×
3843
        return true;
×
3844
      }
3845

3846
      auto newpos = pos().move( static_cast<Core::UFACING>( facing ) );
×
3847
      newpos.z( house->z() +
×
3848
                Multi::CustomHouseDesign::custom_house_z_xlate_table[house->editing_floor_num] );
×
3849
      const Multi::MultiDef& def = house->multidef();
×
3850
      auto relpos = newpos - house->pos().xy();
×
3851
      // minx and y are wall elements and z is 7
3852
      // mobile will look like flying when allowing the min coords
3853
      if ( def.within_multi( relpos ) && relpos.x() != def.minrxyz.x() &&
×
3854
           relpos.y() != def.minrxyz.y() )
×
3855
      {
3856
        Core::Pos4d oldpos = pos();
×
3857
        setposition( newpos );
×
3858
        MoveCharacterWorldPosition( oldpos, this );
×
3859

3860
        position_changed();
×
3861
        set_dirty();
×
3862
        return true;
×
3863
      }
3864
    }
3865
  }
3866
  return false;
×
3867
}
3868

3869
bool Character::is_piloting_boat() const
142✔
3870
{
3871
  auto* mountpiece = wornitem( Core::LAYER_MOUNT );
142✔
3872
  return mountpiece != nullptr && mountpiece->objtype_ == Core::settingsManager.extobj.boatmount;
142✔
3873
}
3874

3875
//************************************
3876
// Method:    move
3877
// FullName:  Character::move
3878
// Access:    public
3879
// Returns:   bool
3880
// Qualifier:
3881
// Parameter: unsigned char i_dir
3882
//************************************
3883
bool Character::move( unsigned char i_dir )
24✔
3884
{
3885
  if ( is_piloting_boat() )
24✔
3886
  {
3887
    return false;
1✔
3888
  }
3889

3890
  lastpos = pos();
23✔
3891

3892
  // if currently building a house chr can move free inside the multi
3893
  if ( is_house_editing() )
23✔
3894
    return CustomHousingMove( i_dir );
×
3895

3896
  u8 oldFacing = facing;
23✔
3897

3898
  Core::UFACING i_facing = static_cast<Core::UFACING>( i_dir & PKTIN_02_FACING_MASK );
23✔
3899
  if ( !face( i_facing ) )
23✔
3900
    return false;
×
3901

3902
  // If we're a player, and we used our "move" command to turn,
3903
  //  we want to skip the meat of this function
3904
  if ( ( script_isa( Core::POLCLASS_NPC ) ) || ( facing == oldFacing ) )
23✔
3905
  {
3906
    if ( facing & 1 )  // check if diagonal movement is allowed -- Nando (2009-02-26)
18✔
3907
    {
3908
      short new_z;
3909
      u8 tmp_facing = ( facing + 1 ) & 0x7;
1✔
3910
      auto tmp_pos = pos().move( static_cast<Core::UFACING>( tmp_facing ) );
1✔
3911

3912
      // needs to save because if only one direction is blocked, it shouldn't block ;)
3913
      bool walk1 =
3914
          realm()->walkheight( this, tmp_pos.xy(), tmp_pos.z(), &new_z, nullptr, nullptr, nullptr );
1✔
3915

3916
      tmp_facing = ( facing - 1 ) & 0x7;
1✔
3917
      tmp_pos = pos().move( static_cast<Core::UFACING>( tmp_facing ) );
1✔
3918

3919
      if ( !walk1 && !realm()->walkheight( this, tmp_pos.xy(), tmp_pos.z(), &new_z, nullptr,
1✔
3920
                                           nullptr, nullptr ) )
3921
        return false;
×
3922
    }
3923
    auto new_pos = pos().move( static_cast<Core::UFACING>( facing ) );
18✔
3924

3925
    // FIXME consider consolidating with similar code in UOEMOD.CPP
3926
    short newz;
3927
    Multi::UMulti* supporting_multi;
3928
    Items::Item* walkon_item;
3929

3930
    short current_boost = gradual_boost;
18✔
3931
    if ( !realm()->walkheight( this, new_pos.xy(), new_pos.z(), &newz, &supporting_multi,
18✔
3932
                               &walkon_item, &current_boost ) )
3933
      return false;
1✔
3934
    new_pos.z( static_cast<s8>( newz ) );
17✔
3935
    remote_containers_.clear();
17✔
3936

3937
    if ( !CheckPushthrough() )
17✔
3938
      return false;
×
3939

3940
    if ( !can_freemove() && Core::settingsManager.ssopt.movement_uses_stamina && !dead() )
17✔
3941
    {
3942
      int carry_perc = weight() * 100 / carrying_capacity();
17✔
3943
      unsigned short tmv = movecost(
34✔
3944
          this, carry_perc, ( i_dir & PKTIN_02_DIR_RUNNING_BIT ) ? true : false, on_mount() );
17✔
3945
      VitalValue& stamina = vital( Core::gamestate.pVitalStamina->vitalid );
17✔
3946
      if ( !consume( Core::gamestate.pVitalStamina, stamina, tmv, VitalDepletedReason::MOVEMENT ) )
17✔
3947
      {
3948
        private_say_above( this, this, "You are too fatigued to move." );
×
3949
        return false;
×
3950
      }
3951
    }
3952

3953
    // Maybe have a flag for this in servspecopt?
3954
    if ( !cached_settings.get( PRIV_FLAGS::FIRE_WHILE_MOVING ) && weapon->is_projectile() )
17✔
3955
      reset_swing_timer();
×
3956

3957
    Core::Pos4d oldpos = pos();
17✔
3958
    setposition( new_pos );
17✔
3959
    moved_at_ = Core::polclock();
17✔
3960

3961
    if ( on_mount() && !script_isa( Core::POLCLASS_NPC ) )
17✔
3962
    {
3963
      mountedsteps_++;
×
3964
    }
3965
    if ( supporting_multi != nullptr )
17✔
3966
    {
3967
      supporting_multi->register_object( this );
×
3968

3969
      if ( this->registered_multi == 0 )
×
3970
      {
3971
        this->registered_multi = supporting_multi->serial;
×
3972
        supporting_multi->walk_on( this );
×
3973
      }
3974
    }
3975
    else
3976
    {
3977
      if ( registered_multi > 0 )
17✔
3978
      {
3979
        Multi::UMulti* multi = Core::system_find_multi( registered_multi );
×
3980
        if ( multi != nullptr )
×
3981
        {
3982
          multi->unregister_object( (UObject*)this );
×
3983
        }
3984
        registered_multi = 0;
×
3985
      }
3986
    }
3987

3988
    gradual_boost = current_boost;
17✔
3989
    MoveCharacterWorldPosition( oldpos, this );
17✔
3990

3991
    position_changed();
17✔
3992
    if ( walkon_item != nullptr )
17✔
3993
    {
3994
      walkon_item->walk_on( this );
×
3995
    }
3996

3997
    if ( hidden() )
17✔
3998
    {
3999
      if ( ( ( i_dir & PKTIN_02_DIR_RUNNING_BIT ) &&
×
4000
             !cached_settings.get( PRIV_FLAGS::RUN_WHILE_STEALTH ) ) ||
×
4001
           ( stealthsteps_ == 0 ) )
×
4002
        unhide();
×
4003
      else if ( stealthsteps_ )
×
4004
        --stealthsteps_;
×
4005
    }
4006

4007
    if ( Core::gamestate.system_hooks.ouch_hook )
17✔
4008
    {
4009
      if ( ( lastpos.z() - z() ) > 21 )
×
4010
        Core::gamestate.system_hooks.ouch_hook->call(
×
4011
            make_mobileref( this ), new Bscript::BLong( lastpos.x() ),
×
4012
            new Bscript::BLong( lastpos.y() ), new Bscript::BLong( lastpos.z() ) );
×
4013
    }
4014
  }
4015

4016
  set_dirty();
22✔
4017
  dir = i_dir;
22✔
4018

4019
  return true;
22✔
4020

4021

4022
  // this would be a great place for tellmove(), except that
4023
  // we want to send the move acknowledge to the client before
4024
  // sending the move/create messages to the neighboring clients.
4025

4026
  // Why? Maybe to give the best response time to the client.
4027

4028
  // may want to rethink this, since it's starting to complicate
4029
  // things.
4030
}
4031

4032
void Character::realm_changed()
18✔
4033
{
4034
  // Commented out the explicit backpack handling, should be handled
4035
  // automagically by wormitems realm handling.  There is a slim
4036
  // possibility that backpacks might be assigned to a character but
4037
  // not be a worn item?  If this is the case, that will be broken.
4038
  //  backpack()->realm = realm;
4039
  //  backpack()->for_each_item(setrealm, (void*)realm);
4040
  wornitems->for_each_item( Core::setrealm, (void*)realm() );
18✔
4041
  // TODO Pos: realm should be all the time nullptr for these items
4042
  if ( has_gotten_item() )
18✔
4043
  {
4044
    auto* item = gotten_item().item();
3✔
4045
    item->setposition( Core::Pos4d( item->pos().xyz(), realm() ) );
3✔
4046
    if ( item->isa( Core::UOBJ_CLASS::CLASS_CONTAINER ) )
3✔
4047
    {
4048
      Core::UContainer* cont = static_cast<Core::UContainer*>( item );
3✔
4049
      cont->for_each_item( Core::setrealm, (void*)realm() );
3✔
4050
    }
4051
  }
4052
  if ( trading_cont.get() )
18✔
4053
  {
4054
    trading_cont->setposition( Core::Pos4d( trading_cont->pos().xyz(), realm() ) );
×
4055
    trading_cont->for_each_item( Core::setrealm, (void*)realm() );
×
4056
  }
4057

4058
  if ( has_active_client() )
18✔
4059
  {
4060
    // these are important to keep here in this order
4061
    Core::send_realm_change( client, realm() );
14✔
4062
    Core::send_map_difs( client );
14✔
4063
    if ( Core::settingsManager.ssopt.core_sends_season )
14✔
4064
      Core::send_season_info( client );
14✔
4065
    Core::send_full_statmsg( client, this );
14✔
4066
    Core::send_feature_enable( client );
14✔
4067
  }
4068
}
18✔
4069

4070
bool Character::CheckPushthrough()
17✔
4071
{
4072
  if ( !can_freemove() && Core::gamestate.system_hooks.pushthrough_hook )
17✔
4073
  {
4074
    auto newpos = pos().move( static_cast<Core::UFACING>( facing ) );
×
4075
    auto mobs = std::unique_ptr<Bscript::ObjArray>();
×
4076

4077
    Core::WorldIterator<Core::MobileFilter>::InRange(
×
4078
        newpos, 0,
4079
        [&]( Mobile::Character* _chr )
×
4080
        {
4081
          if ( _chr->z() >= z() - 10 && _chr->z() <= z() + 10 && !_chr->dead() &&
×
4082
               ( is_visible_to_me( _chr ) ||
×
4083
                 _chr->hidden() ) )  // add hidden mobs even if they're not visible to me
×
4084
          {
4085
            if ( !mobs )
×
4086
              mobs = std::make_unique<Bscript::ObjArray>();
×
4087
            mobs->addElement( make_mobileref( _chr ) );
×
4088
          }
4089
        } );
×
4090

4091
    if ( mobs )
×
4092
    {
4093
      return Core::gamestate.system_hooks.pushthrough_hook->call( make_mobileref( this ),
×
4094
                                                                  mobs.release() );
×
4095
    }
4096
    return true;
×
4097
  }
×
4098
  return true;
17✔
4099
}
4100

4101
void Character::tellmove()
168✔
4102
{
4103
  check_region_changes();
168✔
4104
  PropagateMove( this );
168✔
4105

4106
  // notify npcs and items (maybe the PropagateMove should also go there eventually? - Nando
4107
  // 2018-06-16)
4108
  realm()->notify_moved( *this );
168✔
4109

4110
  check_attack_after_move( true );
168✔
4111

4112
  move_reason = OTHER;
168✔
4113
}
168✔
4114

4115
void Character::add_remote_container( Items::Item* item )
×
4116
{
4117
  remote_containers_.emplace_back( item );
×
4118
}
×
4119

4120
Items::Item* Character::search_remote_containers( u32 find_serial, bool* isRemoteContainer ) const
45✔
4121
{
4122
  if ( isRemoteContainer )
45✔
4123
    *isRemoteContainer = false;
40✔
4124
  for ( const auto& elem : remote_containers_ )
45✔
4125
  {
4126
    Items::Item* item = elem.get();
×
4127
    if ( item->orphan() )
×
4128
      continue;  // it'll be cleaned up when they move
×
4129
    if ( item->serial == find_serial )
×
4130
    {
4131
      if ( isRemoteContainer )
×
4132
        *isRemoteContainer = true;
×
4133
      return item;
×
4134
    }
4135
    if ( item->isa( Core::UOBJ_CLASS::CLASS_CONTAINER ) )
×
4136
    {
4137
      item = ( (Core::UContainer*)item )->find( find_serial );
×
4138
      if ( item )
×
4139
      {
4140
        if ( isRemoteContainer )
×
4141
          *isRemoteContainer = false;
×
4142
        return item;
×
4143
      }
4144
    }
4145
  }
4146
  return nullptr;
45✔
4147
}
4148

4149
bool Character::mightsee( const Items::Item* item ) const
76✔
4150
{
4151
  const auto* owner = item->toplevel_owner();
76✔
4152
  for ( const auto& elem : remote_containers_ )
76✔
4153
  {
4154
    Items::Item* additional_item = elem.get();
×
4155
    if ( additional_item == owner )
×
4156
      return true;
×
4157
  }
4158

4159
  return in_visual_range( owner );
76✔
4160
}
4161

4162
bool Character::squelched() const
37✔
4163
{
4164
  Core::gameclock_t squelched = squelched_until();
37✔
4165
  if ( squelched == 0 )
37✔
4166
    return false;
36✔
4167
  if ( squelched == ~0u )
1✔
4168
    return true;
×
4169

4170
  if ( Core::read_gameclock() < squelched )
1✔
4171
  {
4172
    return true;
1✔
4173
  }
4174

4175
  const_cast<Character*>( this )->squelched_until( 0 );
×
4176
  return false;
×
4177
}
4178

4179
bool Character::deafened() const
38✔
4180
{
4181
  Core::gameclock_t deafened = deafened_until();
38✔
4182
  if ( deafened == 0 )
38✔
4183
    return false;
37✔
4184
  if ( deafened == ~0u )
1✔
4185
    return true;
×
4186

4187
  if ( Core::read_gameclock() < deafened )
1✔
4188
  {
4189
    return true;
1✔
4190
  }
4191

4192
  const_cast<Character*>( this )->deafened_until( 0 );
×
4193
  return false;
×
4194
}
4195

4196
bool Character::invul() const
468✔
4197
{
4198
  return cached_settings.get( PRIV_FLAGS::INVUL );
468✔
4199
}
4200

4201
u16 Character::strength() const
25✔
4202
{
4203
  return static_cast<u16>( attribute( Core::gamestate.pAttrStrength->attrid ).effective() );
25✔
4204
}
4205
u16 Character::dexterity() const
177✔
4206
{
4207
  return static_cast<u16>( attribute( Core::gamestate.pAttrDexterity->attrid ).effective() );
177✔
4208
}
4209
u16 Character::intelligence() const
×
4210
{
4211
  return static_cast<u16>( attribute( Core::gamestate.pAttrIntelligence->attrid ).effective() );
×
4212
}
4213

4214
bool Character::target_cursor_busy() const
8✔
4215
{
4216
  if ( client && client->gd && client->gd->tcursor2 && client->gd->target_cursor_uoemod != nullptr )
8✔
4217
    return true;
×
4218
  return false;
8✔
4219
}
4220

4221
void Character::cancel_menu()
×
4222
{
4223
  if ( client )
×
4224
  {
4225
    client->gd->menu.clear();
×
4226
    if ( client->gd->on_menu_selection != nullptr )
×
4227
      client->gd->on_menu_selection( client, nullptr, nullptr );
×
4228
    client->gd->on_menu_selection = nullptr;
×
4229
  }
4230
}
×
4231

4232
bool Character::is_trading() const
198✔
4233
{
4234
  return ( trading_with.get() != nullptr );
198✔
4235
}
4236

4237
bool Character::trade_accepted() const
×
4238
{
4239
  return mob_flags_.get( MOB_FLAGS::TRADE_ACCEPTED );
×
4240
}
4241

4242
void Character::trade_accepted( bool newvalue )
×
4243
{
4244
  mob_flags_.change( MOB_FLAGS::TRADE_ACCEPTED, newvalue );
×
4245
}
×
4246

4247
void Character::create_trade_container()
×
4248
{
4249
  if ( trading_cont.get() == nullptr )  // FIXME hardcoded
×
4250
  {
4251
    Items::Item* cont = Items::Item::create( Core::settingsManager.extobj.secure_trade_container );
×
4252
    // TODO Pos: no realm
4253
    cont->setposition( pos() );
×
4254
    trading_cont.set( static_cast<Core::UContainer*>( cont ) );
×
4255
  }
4256
}
×
4257

4258
Core::UContainer* Character::trade_container()
×
4259
{
4260
  return trading_cont.get();
×
4261
}
4262

4263
// SkillValue removed for no use - MuadDib
4264

4265
const char* Character::target_tag() const
×
4266
{
4267
  return "mobile";
×
4268
}
4269

4270
void Character::set_caps_to_default()
99✔
4271
{
4272
  for ( unsigned ai = 0; ai < Core::gamestate.numAttributes; ++ai )
5,247✔
4273
  {
4274
    Attribute* pAttr = Core::gamestate.attributes[ai];
5,148✔
4275
    AttributeValue& av = attribute( ai );
5,148✔
4276

4277
    av = attribute( ai );
5,148✔
4278
    av.cap( pAttr->default_cap );
5,148✔
4279
  }
4280
}
99✔
4281

4282
u16 Character::last_textcolor() const
×
4283
{
4284
  return _last_textcolor;
×
4285
}
4286

4287
void Character::last_textcolor( u16 new_color )
3✔
4288
{
4289
  _last_textcolor = new_color;
3✔
4290
}
3✔
4291

4292
unsigned int Character::guildid() const
2✔
4293
{
4294
  auto g = guild();
2✔
4295
  return ( g != nullptr ) ? g->guildid() : 0;
2✔
4296
}
4297

4298
/**
4299
 * Adds a new buff or overwrites an existing one for the character
4300
 * Sends packets to the client accordingly
4301
 * @author Bodom
4302
 */
4303
void Character::addBuff( u16 icon, u16 duration, u32 cl_name, const std::string& name_arguments,
2✔
4304
                         u32 cl_descr, const std::string& desc_arguments )
4305
{
4306
  // Icon is already present, must send a remove packet first or client will not update
4307
  delBuff( icon );
2✔
4308

4309
  Core::gameclock_t end = Core::read_gameclock() + duration;
2✔
4310
  buffs_[icon] = { end, cl_name, cl_descr, name_arguments, desc_arguments };
2✔
4311

4312
  if ( client != nullptr )
2✔
4313
    send_buff_message( this, icon, true, duration, cl_name, name_arguments, cl_descr,
×
4314
                       desc_arguments );
4315
}
4✔
4316

4317
/**
4318
 * Removes a buff for the character
4319
 * Sends packets to the client accordingly
4320
 * @author Bodom
4321
 * @return True when the buff has been found and removed, False when the buff was not present
4322
 */
4323
bool Character::delBuff( u16 icon )
4✔
4324
{
4325
  auto b = buffs_.find( icon );
4✔
4326

4327
  if ( b == buffs_.end() )
4✔
4328
    return false;
3✔
4329

4330
  buffs_.erase( b );
1✔
4331
  if ( client != nullptr )
1✔
4332
    send_buff_message( this, icon, false );
×
4333
  return true;
1✔
4334
}
4335

4336
/**
4337
 * Removes al buffs for the character
4338
 * Sends packets to the client accordingly
4339
 * @author Bodom
4340
 */
4341
void Character::clearBuffs()
2✔
4342
{
4343
  if ( client != nullptr )
2✔
4344
  {
4345
    for ( const auto& buf : buffs_ )
×
4346
    {
4347
      send_buff_message( this, buf.first, false );
×
4348
    }
4349
  }
4350
  buffs_.clear();
2✔
4351
}
2✔
4352

4353
/**
4354
 * Resends all buffs (with updated duration), usually called at (re)login
4355
 * @author Bodom
4356
 */
4357
void Character::send_buffs()
2✔
4358
{
4359
  if ( client == nullptr )
2✔
4360
    return;
×
4361

4362
  for ( const auto& [icon, buf] : buffs_ )
2✔
4363
  {
4364
    u16 duration = Clib::clamp_convert<u16>( buf.end - Core::read_gameclock() );
×
4365

4366
    send_buff_message( this, icon, true, duration, buf.cl_name, buf.name_arguments, buf.cl_descr,
×
4367
                       buf.desc_arguments );
×
4368
  }
4369
}
4370

4371
u8 Character::los_size() const
63,012✔
4372
{
4373
  return client ? client->update_range() : Core::settingsManager.ssopt.default_visual_range;
63,012✔
4374
}
4375

4376
size_t Character::estimatedSize() const
2✔
4377
{
4378
  size_t size = base::estimatedSize() + uclang.capacity() + privs.estimatedSize() +
2✔
4379
                settings.estimatedSize() + sizeof( Core::AccountRef ) /*acct*/
2✔
4380
                + sizeof( Network::Client* )                          /*client*/
4381
                + sizeof( u32 )                                       /*registered_multi*/
4382
                + sizeof( unsigned char )                             /*cmdlevel_*/
4383
                + sizeof( u8 )                                        /*dir*/
4384
                + sizeof( Core::Pos4d )                               /*lastpos*/
4385
                + sizeof( MOVEREASON )                                /*move_reason*/
4386
                + sizeof( Plib::MOVEMODE )                            /*movemode*/
4387
                + sizeof( time_t )                                    /*disable_regeneration_until*/
4388
                + sizeof( u16 )                                       /*truecolor*/
4389
                + sizeof( u32 )                                       /*trueobjtype*/
4390
                + sizeof( Plib::UGENDER )                             /*gender*/
4391
                + sizeof( Plib::URACE )                               /*race*/
4392
                + sizeof( short )                                     /*gradual_boost*/
4393
                + sizeof( u32 )                                       /*last_corpse*/
4394
                + sizeof( u16 )                                       /*_last_textcolor*/
4395
                + sizeof( ref_ptr<Core::WornItemsContainer> )         /*wornitems_ref*/
4396
                + sizeof( unsigned short )                            /*ar_*/
4397
                + sizeof( Items::UWeapon* )                           /*weapon*/
4398
                + sizeof( Items::UArmor* )                            /*shield*/
4399
                + sizeof( unsigned char )                             /*concealed_*/
4400
                + sizeof( unsigned short )                            /*stealthsteps_*/
4401
                + sizeof( unsigned int )                              /*mountedsteps_*/
4402
                + privs.estimatedSize() + settings.estimatedSize() +
2✔
4403
                sizeof( Core::UOExecutor* )                  /*script_ex*/
4404
                + sizeof( Attackable )                       /*opponent_*/
4405
                + sizeof( Core::polclock_t )                 /*swing_timer_start_clock_*/
4406
                + sizeof( Core::OneShotTask* )               /*swing_task*/
4407
                + sizeof( Core::OneShotTask* )               /*spell_task*/
4408
                + sizeof( Core::gameclock_t )                /*created_at*/
4409
                + sizeof( Core::polclock_t )                 /*criminal_until_*/
4410
                + sizeof( Core::OneShotTask* )               /*repsys_task_*/
4411
                + sizeof( Core::OneShotTask* )               /*party_decline_timeout_*/
4412
                + sizeof( Core::AttributeFlags<PRIV_FLAGS> ) /*cached_settings*/
4413
                + sizeof( Core::AttributeFlags<MOB_FLAGS> )  /*mob_flags_*/
2✔
4414
      ;
4415

4416
  size += Clib::memsize( attributes ) + Clib::memsize( vitals ) + Clib::memsize( armor_ ) +
2✔
4417
          Clib::memsize( remote_containers_ ) + Clib::memsize( opponent_of ) +
2✔
4418
          Clib::memsize( aggressor_to_ ) + Clib::memsize( lawfully_damaged_ ) +
2✔
4419
          Clib::memsize( to_be_reportable_ ) + Clib::memsize( reportable_ ) +
2✔
4420
          Clib::memsize( buffs_ );
2✔
4421
  return size;
2✔
4422
}
4423

4424
void Character::on_delete_from_account()
3✔
4425
{
4426
  if ( realm() )
3✔
4427
    realm()->remove_mobile( *this, Realms::WorldChangeReason::PlayerDeleted );
3✔
4428
}
3✔
4429

4430
bool Character::has_paperdoll() const
14✔
4431
{
4432
  switch ( graphic )
14✔
4433
  {
4434
  case UOBJ_HUMAN_MALE:
14✔
4435
  case UOBJ_HUMAN_FEMALE:
4436
  case UOBJ_HUMAN_MALE_GHOST:
4437
  case UOBJ_HUMAN_FEMALE_GHOST:
4438
  case UOBJ_ELF_MALE:
4439
  case UOBJ_ELF_FEMALE:
4440
  case UOBJ_ELF_MALE_GHOST:
4441
  case UOBJ_ELF_FEMALE_GHOST:
4442
  case UOBJ_GARGOYLE_MALE:
4443
  case UOBJ_GARGOYLE_FEMALE:
4444
  case UOBJ_GARGOYLE_MALE_GHOST:
4445
  case UOBJ_GARGOYLE_FEMALE_GHOST:
4446
  case UOBJ_GAMEMASTER:
4447
  case UOBJ_LORD_BRITISH:
4448
  case UOBJ_BLACKTHORN:
4449
  case UOBJ_DUPRE:
4450
    return true;
14✔
4451
  default:
×
4452
    return false;
×
4453
  }
4454
}
4455

4456
bool Character::get_method_hook( const char* methodname, Bscript::Executor* ex,
×
4457
                                 Core::ExportScript** hook, unsigned int* PC ) const
4458
{
4459
  if ( Core::gamestate.system_hooks.get_method_hook(
×
4460
           Core::gamestate.system_hooks.character_method_script.get(), methodname, ex, hook, PC ) )
4461
    return true;
×
4462
  return base::get_method_hook( methodname, ex, hook, PC );
×
4463
}
4464

4465
void Character::update_objects_on_range_change( u8 newrange )
2✔
4466
{
4467
  Network::RemoveObjectPkt msgremove( serial_ext );
2✔
4468

4469
  Core::WorldIterator<Core::MobileFilter>::InRange(
2✔
4470
      this, std::max( newrange, los_size() ),
2✔
4471
      [&]( Mobile::Character* zonechr )
2✔
4472
      {
4473
        if ( this == zonechr || !is_visible_to_me( zonechr, false /*rangecheck*/ ) )
2✔
4474
          return;
2✔
4475
        bool was_inrange = in_range( zonechr, los_size() + zonechr->visible_size() );
×
4476
        bool is_inrange = in_range( zonechr, newrange + zonechr->visible_size() );
×
4477
        if ( was_inrange && is_inrange )
×
4478
          return;
×
4479
        if ( !was_inrange && is_inrange )
×
4480
          Core::send_owncreate( client, zonechr );
×
4481
        else if ( was_inrange && !is_inrange )
×
4482
          Core::send_remove_character( client, zonechr, msgremove );
×
4483
      } );
4484

4485
  Core::WorldIterator<Core::ItemFilter>::InRange(
2✔
4486
      this, Core::gamestate.max_update_range_multi_only() + std::max( newrange, los_size() ),
2✔
4487
      [&]( Items::Item* zoneitem )
2✔
4488
      {
4489
        bool was_inrange = in_range( zoneitem, los_size() + zoneitem->visible_size() );
8✔
4490
        bool is_inrange = in_range( zoneitem, newrange + zoneitem->visible_size() );
8✔
4491
        if ( was_inrange && is_inrange )
8✔
4492
          return;
×
4493
        if ( !was_inrange && is_inrange )
8✔
4494
          Core::send_item( client, zoneitem );
3✔
4495
        else if ( was_inrange && !is_inrange )
5✔
4496
          Core::send_remove_object( client, zoneitem, msgremove );
3✔
4497
      } );
4498

4499
  Core::WorldIterator<Core::MultiFilter>::InRange(
2✔
4500
      this, Core::gamestate.max_update_range_multi_only() + std::max( newrange, los_size() ),
2✔
4501
      [&]( Multi::UMulti* zonemulti )
2✔
4502
      {
4503
        bool was_inrange = in_range( zonemulti, los_size() + zonemulti->visible_size() );
×
4504
        bool is_inrange = in_range( zonemulti, newrange + zonemulti->visible_size() );
×
4505
        if ( was_inrange && is_inrange )
×
4506
          return;
×
4507
        if ( !was_inrange && is_inrange )
×
4508
        {
4509
          Core::send_multi( client, zonemulti );
×
4510
          Multi::UHouse* house = zonemulti->as_house();
×
4511
          if ( client->acctSupports( Plib::ExpansionVersion::AOS ) && house != nullptr &&
×
4512
               house->IsCustom() )
×
4513
            Multi::CustomHousesSendShort( house, client );
×
4514
        }
×
4515
        else if ( was_inrange && !is_inrange )
×
4516
          Core::send_remove_object( client, zonemulti, msgremove );
×
4517
      } );
4518
}
2✔
4519

4520
AttributeValue::AttributeValue()
5,148✔
4521
    : _base( 0 ), _temp( 0 ), _intrinsic( 0 ), _lockstate( 0 ), _cap( 0 )
5,148✔
4522
{
4523
}
5,148✔
4524
int AttributeValue::effective() const
736✔
4525
{
4526
  int v = _base;
736✔
4527
  v += _temp;
736✔
4528
  v += _intrinsic;
736✔
4529
  return ( v > 0 ) ? ( v / 10 ) : 0;
736✔
4530
}
4531
int AttributeValue::effective_tenths() const
110✔
4532
{
4533
  int v = _base;
110✔
4534
  v += _temp;
110✔
4535
  v += _intrinsic;
110✔
4536
  return ( v > 0 ) ? v : 0;
110✔
4537
}
4538
int AttributeValue::base() const
1,345✔
4539
{
4540
  return _base;
1,345✔
4541
}
4542
void AttributeValue::base( unsigned short base )
322✔
4543
{
4544
  passert( base <= ATTRIBUTE_MAX_BASE );
322✔
4545
  _base = base;
322✔
4546
}
322✔
4547
int AttributeValue::temp_mod() const
×
4548
{
4549
  return _temp;
×
4550
}
4551
void AttributeValue::temp_mod( short temp )
×
4552
{
4553
  _temp = temp;
×
4554
}
×
4555
int AttributeValue::intrinsic_mod() const
×
4556
{
4557
  return _intrinsic;
×
4558
}
4559
void AttributeValue::intrinsic_mod( short val )
×
4560
{
4561
  _intrinsic = val;
×
4562
}
×
4563
unsigned char AttributeValue::lock() const
1,242✔
4564
{
4565
  return _lockstate;
1,242✔
4566
}
4567
void AttributeValue::lock( unsigned char lockstate )
12✔
4568
{
4569
  _lockstate = lockstate;
12✔
4570
}
12✔
4571
unsigned short AttributeValue::cap() const
1,148✔
4572
{
4573
  return _cap;
1,148✔
4574
}
4575
void AttributeValue::cap( unsigned short cap )
5,160✔
4576
{
4577
  _cap = cap;
5,160✔
4578
}
5,160✔
4579

4580
VitalValue::VitalValue() : _current( 0 ), _maximum( 0 ), _regenrate( 0 ) {}
297✔
4581
int VitalValue::current() const
11✔
4582
{
4583
  return _current;
11✔
4584
}
4585
int VitalValue::current_ones() const
1,301✔
4586
{
4587
  return _current / 100;
1,301✔
4588
}
4589
int VitalValue::current_thousands() const
67✔
4590
{
4591
  // use division to prevent overflow
4592
  return ( _current / 100 ) * 1000 / ( _maximum / 100 );
67✔
4593
}
4594
int VitalValue::maximum() const
234✔
4595
{
4596
  return _maximum;
234✔
4597
}
4598
int VitalValue::maximum_ones() const
792✔
4599
{
4600
  return _maximum / 100;
792✔
4601
}
4602
bool VitalValue::is_at_maximum() const
35✔
4603
{
4604
  return ( _current >= _maximum );
35✔
4605
}
4606
int VitalValue::regenrate() const
105✔
4607
{
4608
  return _regenrate;
105✔
4609
}
4610
void VitalValue::current( int cur )
317✔
4611
{
4612
  _current = cur;
317✔
4613
  if ( _current > _maximum )
317✔
4614
    _current = _maximum;
×
4615
}
317✔
4616
void VitalValue::current_ones( int ones )
80✔
4617
{
4618
  current( ones * 100 );
80✔
4619
}
80✔
4620
void VitalValue::maximum( int val )
267✔
4621
{
4622
  _maximum = val;
267✔
4623
  if ( _current > _maximum )
267✔
4624
    current( _maximum );
×
4625
}
267✔
4626
void VitalValue::regenrate( int rate )
267✔
4627
{
4628
  _regenrate = rate;
267✔
4629
}
267✔
4630
bool VitalValue::consume( unsigned int hamt )
17✔
4631
{
4632
  if ( _current > hamt )
17✔
4633
  {
4634
    _current -= hamt;
17✔
4635
    return true;
17✔
4636
  }
4637
  _current = 0;
×
4638
  return false;
×
4639
}
4640
void VitalValue::produce( unsigned int hamt )
105✔
4641
{
4642
  unsigned newcur = _current + hamt;
105✔
4643
  if ( newcur > _maximum || newcur < _current )
105✔
4644
  {
4645
    _current = _maximum;
104✔
4646
  }
4647
  else
4648
  {
4649
    _current = newcur;
1✔
4650
  }
4651
}
105✔
4652

4653
}  // namespace Mobile
4654
}  // namespace Pol
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