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

polserver / polserver / 25990848974

17 May 2026 12:27PM UTC coverage: 60.822% (-0.08%) from 60.903%
25990848974

Pull #884

github

turleypol
listhostiles and attack_once accept now Attackable
Pull Request #884: Attackable item

268 of 575 new or added lines in 26 files covered. (46.61%)

17 existing lines in 7 files now uncovered.

44719 of 73524 relevant lines covered (60.82%)

513395.8 hits per line

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

63.42
/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,291✔
424
{
425
  return mob_flags_.get( MOB_FLAGS::LOGGED_IN );
1,291✔
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,873✔
444
{
445
  return ( client != nullptr && client->isActive() );
50,873✔
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
107✔
465
{
466
  return ( client != nullptr && client->gd != nullptr && client->gd->custom_house_serial != 0 );
107✔
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
96✔
531
{
532
  unsigned int wt = 10 + wornitems->weight();
96✔
533
  if ( has_gotten_item() )
96✔
534
    wt += gotten_item().item()->weight();
26✔
535
  if ( trading_cont.get() )
96✔
536
    wt += trading_cont->weight();
×
537
  return wt;
96✔
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
13✔
545
{
546
  return static_cast<u16>( floor( ( 40 + strength() * 7 / 2 + carrying_capacity_mod() ) *
13✔
547
                                  Core::settingsManager.ssopt.carrying_capacity_mod ) );
13✔
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

NEW
869
  movement_cost( Core::MovementCostMod{
×
870
      .walk = elem.remove_double( "MovementWalkMod", Core::MovementCostMod::DEFAULT.walk ),
87✔
871
      .run = elem.remove_double( "MovementRunMod", Core::MovementCostMod::DEFAULT.run ),
87✔
872
      .walk_mounted = elem.remove_double( "MovementWalkMountedMod",
174✔
873
                                          Core::MovementCostMod::DEFAULT.walk_mounted ),
87✔
874
      .run_mounted = elem.remove_double( "MovementRunMountedMod",
87✔
875
                                         Core::MovementCostMod::DEFAULT.run_mounted ) } );
87✔
876

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

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

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

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

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

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

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

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

922
  privs.readfrom( elem.remove_string( "Privs", "" ) );
87✔
923
  settings.readfrom( elem.remove_string( "Settings", "" ) );
87✔
924
  refresh_cached_settings();
87✔
925
}
87✔
926

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

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

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

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

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

976
            cap = cap_ones * 10 + cap_tenths;
2✔
977
          }
978

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

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

994

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

999
        av.lock( lock );
10✔
1000

1001
        break;
10✔
1002
      }
1003
    }
597✔
1004
  }
1005

1006
  calc_vital_stuff();
11✔
1007

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

1030
void Character::readProperties( Clib::ConfigElem& elem )
11✔
1031
{
1032
  readCommonProperties( elem );
11✔
1033
  readAttributesAndVitals( elem );
11✔
1034

1035
  last_corpse = elem.remove_ulong( "LastCorpse", 0 );
11✔
1036
}
11✔
1037

1038

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

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

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

1056
  cached_settings.reset();
192✔
1057

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

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

1113
  refresh_cached_settings();
5✔
1114
}
5✔
1115

1116
std::string Character::all_settings() const
×
1117
{
1118
  return settings.extract();
×
1119
}
1120

1121
std::string Character::all_privs() const
1✔
1122
{
1123
  return privs.extract();
1✔
1124
}
1125

1126
void Character::set_privs( const std::string& privlist )
×
1127
{
1128
  set_dirty();
×
1129
  privs.readfrom( privlist );
×
1130
}
×
1131

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

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

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

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

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

1164
  return false;
×
1165
}
1166

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

1174
  return false;
32✔
1175
}
1176

1177
bool Character::can_be_renamed_by( const Character* /*chr*/ ) const
×
1178
{
1179
  // consider command levels?
1180
  return false;
×
1181
}
1182

1183
bool Character::can_be_clothed_by( const Character* chr ) const
×
1184
{
1185
  return chr == this;
×
1186
}
1187

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

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

1198
bool Character::can_hearghosts() const
×
1199
{
1200
  return cached_settings.get( PRIV_FLAGS::HEAR_GHOSTS );
×
1201
}
1202

1203
bool Character::can_be_heard_as_ghost() const
×
1204
{
1205
  return cached_settings.get( PRIV_FLAGS::CAN_BE_HEARD_AS_GHOST );
×
1206
}
1207

1208
bool Character::can_moveanydist() const
17✔
1209
{
1210
  return cached_settings.get( PRIV_FLAGS::MOVE_ANY_DIST );
17✔
1211
}
1212

1213
bool Character::can_plogany() const
×
1214
{
1215
  return cached_settings.get( PRIV_FLAGS::PLOG_ANY );
×
1216
}
1217

1218
bool Character::can_speedhack() const
×
1219
{
1220
  return cached_settings.get( PRIV_FLAGS::SPEEDHACK );
×
1221
}
1222

1223
bool Character::can_freemove() const
50✔
1224
{
1225
  return cached_settings.get( PRIV_FLAGS::FREEMOVE );
50✔
1226
}
1227

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

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

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

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

1267
// TODO: This could be more efficient, by inlining 'spend' logic
1268
// in a recursive function
1269

1270
void Character::spend_gold( unsigned int amount )
×
1271
{
1272
  passert( gold_carried() >= amount );
×
1273

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

1281
Items::Item* Character::wornitem( int layer ) const
6,116✔
1282
{
1283
  return wornitems->GetItemOnLayer( layer );
6,116✔
1284
}
1285

1286
bool Character::layer_is_equipped( int layer ) const
111✔
1287
{
1288
  return ( wornitems->GetItemOnLayer( layer ) != nullptr );
111✔
1289
}
1290

1291
bool Character::is_equipped( const Items::Item* item ) const
90✔
1292
{
1293
  if ( !Items::valid_equip_layer( item ) )
90✔
1294
    return false;
2✔
1295

1296
  return ( wornitems->GetItemOnLayer( item->tile_layer ) == item );
88✔
1297
}
1298

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

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

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

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

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

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

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

1387
  return true;
64✔
1388
}
1389

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

1395
  item->setposition( pos() );  // TODO POS realm should be nullptr
32✔
1396
  wornitems->PutItemOnLayer( item );
32✔
1397

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

1417
Items::UWeapon* Character::intrinsic_weapon()
×
1418
{
1419
  return Core::gamestate.wrestling_weapon;
×
1420
}
1421

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

1429
  wornitems->RemoveItemFromLayer( item );
25✔
1430

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

1448
bool Character::on_mount() const
46✔
1449
{
1450
  if ( race == Plib::RACE_GARGOYLE )
46✔
1451
    return ( movemode & Plib::MOVEMODE_FLY ) == 0 ? false : true;
×
1452

1453
  return layer_is_equipped( Core::LAYER_MOUNT );
46✔
1454
}
1455

1456

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

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

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

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

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

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

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

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

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

1569
  int start_ones = vv.current_ones();
267✔
1570
  int start_max = vv.maximum_ones();
267✔
1571

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

1575
  if ( mv < static_cast<int>( Core::VITAL_LOWEST_MAX_HUNDREDTHS ) )
267✔
1576
    mv = Core::VITAL_LOWEST_MAX_HUNDREDTHS;
25✔
1577
  if ( mv > static_cast<int>( Core::VITAL_HIGHEST_MAX_HUNDREDTHS ) )
267✔
1578
    mv = Core::VITAL_HIGHEST_MAX_HUNDREDTHS;
×
1579

1580
  vv.maximum( mv );
267✔
1581

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

1584
  if ( rr < Core::VITAL_LOWEST_REGENRATE )
267✔
1585
    rr = Core::VITAL_LOWEST_REGENRATE;
×
1586
  if ( rr > Core::VITAL_HIGHEST_REGENRATE )
267✔
1587
    rr = Core::VITAL_HIGHEST_REGENRATE;
×
1588

1589
  vv.regenrate( rr );
267✔
1590

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

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

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

1603
    if ( im < ATTRIBUTE_MIN_INTRINSIC_MOD )
×
1604
      im = ATTRIBUTE_MIN_INTRINSIC_MOD;
×
1605
    if ( im > ATTRIBUTE_MAX_INTRINSIC_MOD )
×
1606
      im = ATTRIBUTE_MAX_INTRINSIC_MOD;
×
1607

1608
    av.intrinsic_mod( static_cast<short>( im ) );
×
1609
  }
1610
}
4,628✔
1611

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

1620
    Network::ClientInterface::tell_vital_changed( this, Core::gamestate.vitals[vi] );
228✔
1621
  }
1622
}
76✔
1623

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

1629
  set_dirty();
×
1630
  graphic = newgraphic;
×
1631
  send_remove_character_to_nearby_cansee( this );
×
1632
  if ( client )
×
1633
    send_goxyz( client, client->chr );
×
1634
  send_create_mobile_to_nearby_cansee( this );
×
1635

1636
  return true;
×
1637
}
1638

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

1647
void Character::on_poison_changed()
×
1648
{
1649
  send_move_mobile_to_nearby_cansee( this, true );
×
1650
}
×
1651

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

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

1683
void Character::on_cmdlevel_changed()
1✔
1684
{
1685
  send_remove_character_to_nearby_cantsee( this );
1✔
1686
  send_create_mobile_to_nearby_cansee( this );
1✔
1687
}
1✔
1688

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

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

1707
void Character::setfacing( u8 newfacing )
99✔
1708
{
1709
  facing = newfacing & 7;
99✔
1710
}
99✔
1711

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

1732
  return flag1;
203✔
1733
}
1734

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

1745
  if ( ( source ) && ( userepsys ) )
×
1746
    source->repsys_on_attack( this );
×
1747

1748
  if ( ( amount == 0 ) || cached_settings.get( PRIV_FLAGS::INVUL ) )
×
1749
    return;
×
1750

1751
  set_dirty();
×
1752
  if ( hidden() )
×
1753
    unhide();
×
1754

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

1765
  if ( paralyzed() )
×
1766
    mob_flags_.remove( MOB_FLAGS::PARALYZED );
×
1767

1768
  disable_regeneration_for( 2 );  // FIXME depend on amount?
×
1769

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

1776
  if ( ( source ) && ( userepsys ) )
×
1777
    source->repsys_on_damage( this );
×
1778

1779
  send_update_hits_to_inrange( this );
×
1780

1781
  if ( vv.current() == 0 )
×
1782
    die();
×
1783
}
1784

1785
// keep this in sync with NPC::armor_absorb_damage
1786

1787

1788
unsigned short calc_thru_damage( double damage, unsigned short ar )
×
1789
{
1790
  int blocked = ar;
×
1791
  if ( blocked < 0 )
×
1792
    blocked = 0;
×
1793

1794
  int absorbed = blocked / 2;
×
1795

1796
  blocked -= absorbed;
×
1797
  absorbed += Clib::random_int( blocked );
×
1798
  damage -= absorbed;
×
1799

1800
  if ( damage >= 2.0 )
×
NEW
1801
    return Clib::clamp_convert<unsigned short>( damage * 0.5 );
×
NEW
1802
  return Clib::clamp_convert<unsigned short>( damage );
×
1803
}
1804

1805

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

1813
    armor->reduce_hp_from_hit();
×
1814
  }
1815
  return damage;
×
1816
}
1817

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

1828
  return damage;
×
1829
}
1830

1831
void Character::get_hitscript_params( double damage, Items::UArmor** parmor,
8✔
1832
                                      unsigned short* rawdamage )
1833
{
1834
  Items::UArmor* armor = choose_armor();
8✔
1835
  if ( armor )
8✔
UNCOV
1836
    *rawdamage = calc_thru_damage( damage, armor->ar() + ar_mod() );
×
1837
  else
1838
    *rawdamage = Clib::clamp_convert<unsigned short>( damage );
8✔
1839
  *parmor = armor;
8✔
1840
}
8✔
1841

1842
void Character::run_hit_script( const Attackable& defender, double damage )
8✔
1843
{
1844
  ref_ptr<Bscript::EScriptProgram> prog = find_script2(
1845
      weapon->hit_script(), true, Plib::systemstate.config.cache_interactive_scripts );
8✔
1846
  if ( prog.get() == nullptr )
8✔
1847
    return;
×
1848

1849
  std::unique_ptr<Core::UOExecutor> ex( Core::create_script_executor() );
8✔
1850
  auto uoemod = new Module::UOExecutorModule( *ex );
8✔
1851
  ex->addModule( uoemod );
8✔
1852

1853
  unsigned short rawdamage = 0;
8✔
1854
  unsigned short basedamage = Clib::clamp_convert<unsigned short>( damage );
8✔
1855

1856
  Items::UArmor* armor = nullptr;
8✔
1857

1858
  if ( auto* mob = defender.mobile() )
8✔
1859
    mob->get_hitscript_params( damage, &armor, &rawdamage );
8✔
1860
  else
NEW
1861
    rawdamage = Clib::clamp_convert<u16>( damage );
×
1862

1863

1864
  ex->pushArg( new Bscript::BLong( rawdamage ) );
8✔
1865
  ex->pushArg( new Bscript::BLong( basedamage ) );
8✔
1866
  if ( armor )
8✔
1867
    ex->pushArg( new Module::EItemRefObjImp( armor ) );
×
1868
  else
1869
    ex->pushArg( new Bscript::BLong( 0 ) );
8✔
1870
  ex->pushArg( new Module::EItemRefObjImp( weapon ) );
8✔
1871
  ex->pushArg( defender.object()->make_ref() );
8✔
1872
  ex->pushArg( new Module::ECharacterRefObjImp( this ) );
8✔
1873

1874
  ex->priority( 100 );
8✔
1875

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

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

1901

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

1918
  if ( amount == 0 )
×
1919
    return;
×
1920

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

1923
  check_undamaged();
×
1924

1925
  send_update_hits_to_inrange( this );
×
1926
}
1927

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

1947

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

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

1979
  mob_flags_.remove( MOB_FLAGS::DEAD );
×
1980
  mob_flags_.remove( MOB_FLAGS::WARMODE );
×
1981
  mob_flags_.remove( MOB_FLAGS::FROZEN );
×
1982
  mob_flags_.remove( MOB_FLAGS::PARALYZED );
×
1983

1984
  color = truecolor;
×
1985

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

1996
  if ( !Core::gamestate.pVitalMana->regen_while_dead )
×
1997
    set_current_ones( Core::gamestate.pVitalMana, vital( Core::gamestate.pVitalMana->vitalid ), 0,
×
1998
                      VitalDepletedReason::RESURRECT );
1999

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

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

2028
  // equip( create_backpack() );
2029

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

2046
  // Tell other connected players, if in range, about this character.
2047
  send_remove_character_to_nearby_cansee( this );
×
2048
  send_create_mobile_to_nearby_cansee( this );
×
2049
  realm()->notify_resurrected( *this );
×
2050
}
2051

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

2066
  if ( client != nullptr )
1✔
2067
  {
2068
    if ( opponent_ )
×
NEW
2069
      opponent_.inform_disengaged( Attackable{ this } );
×
2070

2071
    client->pause();
×
2072
    send_warmode();
×
2073

2074
    // Sends the complete corpse to the client itself, so he knows where his
2075
    // items went.
2076
    send_full_corpse( client, corpse );
×
2077

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

2087
    client->restart();
×
2088
  }
2089

2090
  // change self to ghost for ghosts, remove self for living
2091
  send_remove_character_to_nearby_cantsee( this );
1✔
2092

2093
  send_create_mobile_to_nearby_cansee( this );
1✔
2094

2095
  if ( Clib::FileExists( "scripts/misc/chrdeath.ecl" ) )
1✔
2096
    Core::start_script( "misc/chrdeath", new Module::EItemRefObjImp( corpse ),
×
2097
                        new Module::ECharacterRefObjImp( this ) );
×
2098
}
1✔
2099

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

2119
void Character::remove_opponent_of( const Attackable& other )
4✔
2120
{
2121
  opponent_of.erase( other );
4✔
2122
}
4✔
2123
void Character::add_opponent_of( Attackable other )
4✔
2124
{
2125
  opponent_of.insert( std::move( other ) );
4✔
2126
}
4✔
2127

2128
void Character::die()
74✔
2129
{
2130
  if ( Core::gamestate.system_hooks.can_die )
74✔
2131
  {
2132
    if ( !Core::gamestate.system_hooks.can_die->call( make_mobileref( this ) ) )
×
2133
      return;
×
2134
  }
2135

2136
  set_current_ones( Core::gamestate.pVitalLife, vital( Core::gamestate.pVitalLife->vitalid ), 0,
74✔
2137
                    VitalDepletedReason::DEATH );
2138
  clear_my_aggressors();
74✔
2139
  clear_my_lawful_damagers();
74✔
2140
  commit_to_reportables();
74✔
2141

2142

2143
  u16 save_graphic = graphic;
74✔
2144

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

2165
  mob_flags_.set( MOB_FLAGS::DEAD );
74✔
2166
  mob_flags_.remove( MOB_FLAGS::WARMODE );
74✔
2167
  mob_flags_.remove( MOB_FLAGS::FROZEN );
74✔
2168
  mob_flags_.remove( MOB_FLAGS::PARALYZED );
74✔
2169

2170
  UPDATE_CHECKPOINT();
74✔
2171
  /* FIXME: corpse container difficulties.
2172

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

2185
     The current solution is to put the backpack
2186
     on the corpse.
2187
     */
2188

2189
  Core::UCorpse* corpse = static_cast<Core::UCorpse*>( Items::Item::create( UOBJ_CORPSE ) );
74✔
2190
  this->last_corpse = corpse->serial;
74✔
2191

2192
  corpse->ownerserial = this->serial;
74✔
2193
  corpse->setname( "A corpse of " + name_.get() );
74✔
2194
  corpse->take_contents_to_grave( acct == nullptr );
74✔
2195

2196
  UPDATE_CHECKPOINT();
74✔
2197

2198
  corpse->color = truecolor;
74✔
2199
  corpse->setposition( pos() );
74✔
2200
  corpse->facing = facing;
74✔
2201
  corpse->corpsetype = save_graphic;
74✔
2202
  // corpse->dir = dir;
2203
  UPDATE_CHECKPOINT();
74✔
2204

2205
  register_with_supporting_multi( corpse );
74✔
2206
  if ( is_trading() )
74✔
2207
    Core::cancel_trade( this );
×
2208
  clear_gotten_item();
74✔
2209
  corpse->copyprops( *this );
74✔
2210
  UPDATE_CHECKPOINT();
74✔
2211

2212
  // Change the character's color to grey
2213
  color = 0;
74✔
2214
  UPDATE_CHECKPOINT();
74✔
2215

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

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

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

2262
    if ( item->layer == Core::LAYER_MOUNT &&
2✔
2263
         item->objtype_ == Core::settingsManager.extobj.boatmount )
×
2264
    {
2265
      Multi::UMulti* multi = realm()->find_supporting_multi( pos3d() );
×
2266

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

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

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

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

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

2372
    UPDATE_CHECKPOINT();
2✔
2373

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

2420

2421
  UPDATE_CHECKPOINT();
74✔
2422
  send_death_message( this, corpse );
74✔
2423

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

2437
  UPDATE_CHECKPOINT();
74✔
2438

2439
  clear_opponent_of();
74✔
2440

2441
  set_opponent( {} );
74✔
2442

2443
  UPDATE_CHECKPOINT();
74✔
2444

2445
  on_death( corpse );
74✔
2446

2447
  CLEAR_CHECKPOINT();
74✔
2448
}
2449

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

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

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

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

2501
  new_ar += ar_mod();
164✔
2502

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

2505
  if ( client )
164✔
2506
  {  // CHECKME consider sending less frequently
2507
    send_full_statmsg( client, this );
6✔
2508
  }
2509
}
164✔
2510

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

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

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

2575
  // calc defence increase if lower than cap
2576
  if ( item->has_defence_increase() )
157✔
2577
    defence_increase( defence_increase().addToValue( item->defence_increase() ) );
34✔
2578
}
157✔
2579

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

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

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

2618
  // reset others
2619
  if ( has_lower_reagent_cost() )
164✔
2620
    lower_reagent_cost( lower_reagent_cost().setAsValue( 0 ) );
61✔
2621
  if ( has_spell_damage_increase() )
164✔
2622
    spell_damage_increase( spell_damage_increase().setAsValue( 0 ) );
59✔
2623
  if ( has_faster_casting() )
164✔
2624
    faster_casting( faster_casting().setAsValue( 0 ) );
64✔
2625
  if ( has_faster_cast_recovery() )
164✔
2626
    faster_cast_recovery( faster_cast_recovery().setAsValue( 0 ) );
65✔
2627

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

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

2663
/* check skill: test skill, advance, reset atrophy timers, blah blah..
2664
   obviously, needs work, and more parameters.
2665
   */
2666

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

2681
  return false;
×
2682
}
2683

2684
void Character::check_concealment_level()
×
2685
{
2686
  if ( concealed() > cmdlevel() )
×
2687
    concealed( cmdlevel() );
×
2688
}
×
2689

2690
// you can only be concealed from
2691
// those of lower stature
2692
bool Character::is_concealed_from_me( const Character* chr ) const
187✔
2693
{
2694
  return ( chr->concealed() > cmdlevel() );
187✔
2695
}
2696

2697
bool Character::is_visible_to_me( const Character* chr, bool check_range ) const
316✔
2698
{
2699
  if ( chr == nullptr )
316✔
2700
    return false;
×
2701
  if ( chr == this )
316✔
2702
    return true;  // I can always see myself (?)
145✔
2703
  if ( is_concealed_from_me( chr ) )
171✔
2704
    return false;
×
2705
  if ( !chr->logged_in() )
171✔
2706
    return false;
×
2707
  if ( chr->hidden() && !cached_settings.get( PRIV_FLAGS::SEE_HIDDEN ) )
171✔
2708
    return false;  // noone can see anyone hidden.
×
2709
  if ( check_range )
171✔
2710
  {
2711
    if ( !in_visual_range( chr ) )
148✔
2712
      return false;
52✔
2713
  }
2714
  else if ( chr->realm() != this->realm() )
23✔
2715
    return false;
1✔
2716

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

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

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

2763
            if ( chr->invul() )  // if invul send 0x17 for newer clients
×
2764
              msginvul.Send( client );
×
2765
            return;
×
2766
          }
2767

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

2800
  // iter over all old in range players and send remove
2801
  Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
164✔
2802
      chr->lastpos,
164✔
2803
      [&]( Character* zonechr )
164✔
2804
      {
2805
        Client* client = zonechr->client;
123✔
2806
        if ( !zonechr->in_visual_range( nullptr, chr->lastpos ) )
123✔
2807
          return;
32✔
2808
        if ( !zonechr->is_visible_to_me( chr, /*check_range*/ false ) )
91✔
2809
          return;
1✔
2810

2811
        if ( zonechr->in_visual_range( chr ) )  // already handled
90✔
2812
          return;
81✔
2813
        // if we just walked out of range of this character, send its
2814
        // client a remove object, or else a ghost character will remain.
2815
        send_remove_character( client, chr, msgremove );
9✔
2816
      } );
2817
}
164✔
2818

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

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

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

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

2855
      INFO_PRINTLN_TRACE( 19 )
×
2856
      ( "clocks[delay] = (({}+{}={})*{})/1000", weapon_delay, delay_mod(), delay_sum,
×
2857
        Core::POLCLOCKS_PER_SEC );
2858

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

2868
    if ( clocks < ( Core::POLCLOCKS_PER_SEC / 5 ) )
17✔
2869
    {
2870
      INFO_PRINTLN_TRACE( 20 )( "{} attack timer: {}", name(), clocks );
6✔
2871
    }
2872
    INFO_PRINTLN_TRACE( 19 )( "clocks={}", clocks );
17✔
2873

2874
    new Core::OneShotTaskInst<Character*>( &swing_task, swing_timer_start_clock_ + clocks + 1,
17✔
2875
                                           swing_task_func, this );
17✔
2876
  }
2877
}
25✔
2878

2879
void Character::reset_swing_timer()
105✔
2880
{
2881
  INFO_PRINTLN_TRACE( 15 )( "reset_swing_timer({:#x})", this->serial );
105✔
2882
  mob_flags_.remove( MOB_FLAGS::READY_TO_SWING );
105✔
2883

2884
  swing_timer_start_clock_ = Core::polclock();
105✔
2885
  if ( swing_task )
105✔
2886
    swing_task->cancel();
6✔
2887

2888
  if ( opponent_ || !opponent_of.empty() )
105✔
2889
  {
2890
    schedule_attack();
13✔
2891
  }
2892
}
105✔
2893

2894
bool Character::manual_set_swing_timer( Core::polclock_t clocks )
×
2895
{
2896
  INFO_PRINTLN_TRACE( 15 )
×
2897
  ( "manual_set_swing_timer({:#x}) delay: {}", this->serial, clocks );
×
2898
  mob_flags_.remove( MOB_FLAGS::READY_TO_SWING );
×
2899

2900
  swing_timer_start_clock_ = Core::polclock();
×
2901
  if ( swing_task )
×
2902
    swing_task->cancel();
×
2903

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

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

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

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

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

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

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

2990
  return {};
168✔
2991
}
2992

2993
void Character::send_highlight() const
91✔
2994
{
2995
  if ( client != nullptr && has_active_client() )
91✔
2996
  {
2997
    auto opponent = get_opponent();
4✔
2998

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

3008
void Character::on_swing_failure( Character* /*attacker*/ )
×
3009
{
3010
  // do nothing
3011
}
×
3012

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

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

3029
void Character::inform_criminal( Character* /*moved*/ )
×
3030
{
3031
  // virtual that does nothing at character level, but fires event for NPCs
3032
}
×
3033

3034
void Character::inform_leftarea( Character* /*wholeft*/ )
1✔
3035
{
3036
  // virtual that does nothing at character level, but fires event for NPCs
3037
}
1✔
3038

3039
void Character::inform_enteredarea( Character* /*whoentered*/ )
22✔
3040
{
3041
  // virtual that does nothing at character level, but fires event for NPCs
3042
}
22✔
3043

3044
void Character::inform_moved( Character* /*moved*/ )
25✔
3045
{
3046
  // consider moving PropagateMove here!
3047
}
25✔
3048
void Character::inform_imoved( Character* /*chr*/ ) {}
29✔
3049

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

3059
    if ( !warmode() && ( script_isa( Core::POLCLASS_NPC ) || has_active_client() ) )
4✔
3060
      set_warmode( true );
1✔
3061
  }
3062

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

3075
  opponent_ = std::move( new_opponent );
88✔
3076

3077

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

3084
    if ( opponent_ )
88✔
3085
    {
3086
      auto* mob = opponent_.mobile();
4✔
3087
      if ( mob )
4✔
3088
      {
3089
        repsys_on_attack( mob );
4✔
3090
        if ( !mob->get_opponent() )
4✔
3091
          mob->reset_swing_timer();
4✔
3092
      }
3093

3094
      opponent_.add_opponent_of( this_att );
4✔
3095
      opponent_.inform_engaged( this_att );
4✔
3096
      if ( mob )
4✔
3097
        mob->schedule_attack();
4✔
3098
    }
3099
  }
3100

3101
  send_highlight();
88✔
3102
}
3103

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

3116
void Character::disable_regeneration_for( int seconds )
4✔
3117
{
3118
  time_t new_disable_time = Core::poltime() + seconds;
4✔
3119
  if ( new_disable_time > disable_regeneration_until )
4✔
3120
    disable_regeneration_until = new_disable_time;
4✔
3121
}
4✔
3122

3123
bool Character::warmode() const
263✔
3124
{
3125
  return mob_flags_.get( MOB_FLAGS::WARMODE );
263✔
3126
}
3127

3128
void Character::set_warmode( bool i_warmode )
4✔
3129
{
3130
  if ( Core::gamestate.system_hooks.warmode_change )
4✔
3131
    Core::gamestate.system_hooks.warmode_change->call( new Module::ECharacterRefObjImp( this ),
×
3132
                                                       new Bscript::BLong( i_warmode ) );
×
3133

3134
  if ( warmode() != i_warmode )
4✔
3135
  {
3136
    disable_regeneration_for( 2 );
4✔
3137
  }
3138

3139
  mob_flags_.change( MOB_FLAGS::WARMODE, i_warmode );
4✔
3140
  if ( i_warmode == false )
4✔
3141
  {
3142
    set_opponent( {} );
1✔
3143
  }
3144
  reset_swing_timer();
4✔
3145

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

3169
          msgmove.Send( chr->client );
2✔
3170
        } );
3171
  }
4✔
3172
}
4✔
3173

3174
const AttributeValue& Character::weapon_attribute() const
16✔
3175
{
3176
  return attribute( weapon->attribute().attrid );
16✔
3177
}
3178

3179
unsigned short Character::random_weapon_damage() const
8✔
3180
{
3181
  return weapon->get_random_damage();
8✔
3182
}
3183

3184
unsigned short Character::min_weapon_damage() const
81✔
3185
{
3186
  return weapon->min_weapon_damage();
81✔
3187
}
3188

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

3194
void Character::damage_weapon()
8✔
3195
{
3196
  if ( !weapon->is_intrinsic() && !weapon->is_projectile() )
8✔
3197
  {
3198
    weapon->reduce_hp_from_hit();
×
3199
  }
3200
}
8✔
3201

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

3216
Core::UACTION Character::weapon_anim() const
8✔
3217
{
3218
  if ( on_mount() )
8✔
3219
    return weapon->mounted_anim();
×
3220
  return weapon->anim();
8✔
3221
}
3222

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

3252
void Character::do_hit_success_effects()
8✔
3253
{
3254
  unsigned short sound = weapon->hit_sound();
8✔
3255
  if ( sound )
8✔
3256
    play_sound_effect( this, sound );
×
3257
}
8✔
3258

3259
void Character::do_hit_failure_effects()
×
3260
{
3261
  unsigned short sound = weapon->miss_sound();
×
3262
  if ( sound )
×
3263
    play_sound_effect( this, sound );
×
3264
}
×
3265

3266
u16 Character::get_damaged_sound() const
×
3267
{
3268
  if ( gender == Plib::GENDER_MALE )
×
3269
    return SOUND_EFFECT_MALE_DEFENSE;
×
3270
  return SOUND_EFFECT_FEMALE_DEFENSE;
×
3271
}
3272

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

3283

3284
void Character::attack( const Attackable& opponent )
8✔
3285
{
3286
  auto calc_damage = [&]()
8✔
3287
  {
3288
    double damage = random_weapon_damage();
8✔
3289
    damage_weapon();
8✔
3290

3291
    if ( Core::settingsManager.watch.combat )
8✔
NEW
3292
      INFO_PRINTLN( "Base damage: {}", damage );
×
3293

3294
    double damage_multiplier = attribute( Core::gamestate.pAttrTactics->attrid ).effective() + 50;
8✔
3295
    damage_multiplier += strength() * 0.20f;
8✔
3296
    damage_multiplier *= 0.01f;
8✔
3297

3298
    damage *= damage_multiplier;
8✔
3299

3300
    if ( Core::settingsManager.watch.combat )
8✔
NEW
3301
      INFO_PRINTLN( "Damage multiplier due to tactics/STR: {} Result: {}", damage_multiplier,
×
3302
                    damage );
3303
    return damage;
8✔
3304
  };
8✔
3305

3306
  INC_PROFILEVAR( combat_operations );
8✔
3307

3308
  if ( Core::gamestate.system_hooks.attack_hook )
8✔
3309
  {
NEW
3310
    if ( Core::gamestate.system_hooks.attack_hook->call( new Module::ECharacterRefObjImp( this ),
×
NEW
3311
                                                         opponent.object()->make_ref() ) )
×
UNCOV
3312
      return;
×
3313
  }
3314

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

3318
  if ( weapon->is_projectile() )
8✔
3319
  {
3320
    Core::UContainer* bp = backpack();
×
3321
    int check_consume_hook = -1;
×
3322
    if ( Core::gamestate.system_hooks.consume_ammunition_hook )
×
3323
    {
3324
      check_consume_hook = Core::gamestate.system_hooks.consume_ammunition_hook->call_long(
×
3325
          new Module::ECharacterRefObjImp( this ), new Module::EItemRefObjImp( weapon ) );
×
3326
      if ( check_consume_hook == 0 )
×
3327
      {
3328
        return;
×
3329
      }
3330
    }
3331

3332
    if ( ( check_consume_hook == -1 ) &&
×
3333
         ( ( bp == nullptr ) || ( weapon->consume_projectile( bp ) == false ) ) )
×
3334
    {
3335
      // 04/2007 - MuadDib
3336
      // Range through wornitems to find containers and check
3337
      // here also if backpack fails. Use the mainpack first this way.
3338
      bool projectile_check = false;
×
3339
      for ( unsigned layer = Core::LAYER_EQUIP__LOWEST; layer <= Core::LAYER_EQUIP__HIGHEST;
×
3340
            layer++ )
3341
      {
3342
        Items::Item* item = wornitems->GetItemOnLayer( layer );
×
3343
        if ( item )
×
3344
        {
3345
          if ( item != nullptr && item->script_isa( Core::POLCLASS_CONTAINER ) )
×
3346
          {
3347
            if ( layer != Core::LAYER_HAIR && layer != Core::LAYER_FACE &&
×
3348
                 layer != Core::LAYER_BEARD && layer != Core::LAYER_BACKPACK &&
×
3349
                 layer != Core::LAYER_MOUNT )
3350
            {
3351
              Core::UContainer* cont = static_cast<Core::UContainer*>( item );
×
3352

3353
              if ( weapon->consume_projectile( cont ) == true )
×
3354
              {
3355
                projectile_check = true;
×
3356
                break;
×
3357
              }
3358
            }
3359
          }
3360
        }
3361
      }
3362
      // I'm out of projectiles.
3363
      if ( projectile_check == false )
×
3364
        return;
×
3365
    }
3366
  }
3367

3368
  auto* opponent_mobile = opponent.mobile();
8✔
3369
  if ( opponent_mobile )
8✔
3370
  {
3371
    repsys_on_attack( opponent_mobile );
8✔
3372
    repsys_on_damage( opponent_mobile );
8✔
3373
  }
3374

3375
  do_attack_effects( opponent );
8✔
3376

3377
  if ( Core::gamestate.system_hooks.combat_advancement_hook )
8✔
3378
  {
3379
    Core::gamestate.system_hooks.combat_advancement_hook->call(
×
3380
        new Module::ECharacterRefObjImp( this ), new Module::EItemRefObjImp( weapon ),
×
NEW
3381
        opponent.object()->make_ref() );
×
3382
  }
3383

3384
  // special handling when attacking items
3385
  // they always hit
3386
  if ( auto* item = opponent.item() )
8✔
3387
  {
NEW
3388
    do_hit_success_effects();
×
3389

NEW
3390
    double damage = calc_damage();
×
NEW
3391
    if ( weapon->hit_script().empty() )
×
NEW
3392
      item->apply_damage( Clib::clamp_convert<u16>( damage ), this,
×
NEW
3393
                          Core::settingsManager.combat_config.send_damage_packet );
×
3394
    else
NEW
3395
      run_hit_script( opponent, damage );
×
3396

NEW
3397
    return;
×
3398
  }
3399

3400

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

3413
    double damage = calc_damage();
8✔
3414

3415
    if ( opponent_mobile->shield != nullptr )
8✔
3416
    {
3417
      if ( Core::gamestate.system_hooks.parry_advancement_hook )
×
3418
      {
3419
        Core::gamestate.system_hooks.parry_advancement_hook->call(
×
3420
            new Module::ECharacterRefObjImp( this ), new Module::EItemRefObjImp( weapon ),
×
NEW
3421
            new Module::ECharacterRefObjImp( opponent_mobile ),
×
NEW
3422
            new Module::EItemRefObjImp( opponent_mobile->shield ) );
×
3423
      }
3424

3425
      double parry_chance =
NEW
3426
          opponent_mobile->attribute( Core::gamestate.pAttrParry->attrid ).effective() / 200.0;
×
NEW
3427
      parry_chance += opponent_mobile->parrychance_mod() * 0.001f;
×
3428
      if ( Core::settingsManager.watch.combat )
×
3429
        INFO_PRINT( "Parry Chance: {}: ", parry_chance );
×
3430
      if ( Clib::random_double( 1.0 ) < parry_chance )
×
3431
      {
3432
        if ( Core::settingsManager.watch.combat )
×
NEW
3433
          INFO_PRINTLN( "{} hits deflected", opponent_mobile->shield->ar() );
×
3434
        if ( Core::settingsManager.combat_config.display_parry_success_messages &&
×
NEW
3435
             opponent_mobile->client )
×
NEW
3436
          Core::send_sysmessage( opponent_mobile->client, "You successfully parried the attack!" );
×
3437

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

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

3493
      // we don't want attack() to recursively cause new attacks
3494
      mob_flags_.remove( MOB_FLAGS::READY_TO_SWING );
8✔
3495
      attack( opponent );
8✔
3496
      FUNCTION_CHECKPOINT( check_attack_after_move, 5 );
8✔
3497
      reset_swing_timer();
8✔
3498
      FUNCTION_CHECKPOINT( check_attack_after_move, 6 );
8✔
3499
    }
3500
    else
3501
    {
3502
      FUNCTION_CHECKPOINT( check_attack_after_move, 7 );
8✔
3503
      INFO_PRINTLN_TRACE( 20 )( "not ready to swing" );
8✔
3504
      schedule_attack();
8✔
3505
      FUNCTION_CHECKPOINT( check_attack_after_move, 8 );
8✔
3506
    }
3507
  }
3508
  FUNCTION_CHECKPOINT( check_attack_after_move, 0 );
184✔
3509

3510
  if ( check_opponents_after_check )
184✔
3511
  {
3512
    if ( auto* mob = opponent_.mobile() )
167✔
3513
      mob->check_attack_after_move( false );
3✔
3514

3515
    // attacking can change the opponent_of array drastically.
3516
    AttackableSet tmp( opponent_of );
167✔
3517
    for ( auto& att : tmp )
170✔
3518
    {
3519
      if ( auto* mob = att.mobile() )
3✔
3520
        mob->check_attack_after_move( false );
3✔
3521
    }
3522
  }
167✔
3523
}
184✔
3524

3525

3526
void Character::check_light_region_change()
166✔
3527
{
3528
  auto light_unil = lightoverride_until();
166✔
3529
  if ( light_unil < Core::read_gameclock() && light_unil != ~0u )
166✔
3530
  {
3531
    lightoverride_until( 0 );
166✔
3532
    lightoverride( -1 );
166✔
3533
  }
3534
  if ( client->gd->weather_region && client->gd->weather_region->lightoverride != -1 &&
166✔
3535
       !has_lightoverride() )
×
3536
    return;
×
3537

3538
  int newlightlevel;
3539
  if ( has_lightoverride() )
166✔
3540
    newlightlevel = lightoverride();
×
3541
  else
3542
  {
3543
    // dave 12-22 check for no regions
3544
    Core::LightRegion* light_region = Core::gamestate.lightdef->getregion( pos() );
166✔
3545
    if ( light_region != nullptr )
166✔
3546
      newlightlevel = light_region->lightlevel;
×
3547
    else
3548
      newlightlevel = Core::settingsManager.ssopt.default_light_level;
166✔
3549
  }
3550

3551
  if ( newlightlevel != client->gd->lightlevel )
166✔
3552
  {
3553
    Core::send_light( client, newlightlevel );
2✔
3554
    client->gd->lightlevel = newlightlevel;
2✔
3555
  }
3556
}
3557

3558
void Character::check_justice_region_change()
103✔
3559
{
3560
  Core::JusticeRegion* cur_justice_region = client->gd->justice_region;
103✔
3561
  Core::JusticeRegion* new_justice_region = Core::gamestate.justicedef->getregion( pos() );
103✔
3562

3563
  if ( cur_justice_region != new_justice_region )
103✔
3564
  {
3565
    if ( cur_justice_region != nullptr )
×
3566
      cur_justice_region->RunLeaveScript( client->chr );
×
3567
    if ( new_justice_region != nullptr )
×
3568
      new_justice_region->RunEnterScript( client->chr );
×
3569

3570
    // print 'leaving' message
3571
    bool printmsgs;
3572
    if ( cur_justice_region != nullptr && new_justice_region != nullptr &&
×
3573
         cur_justice_region->entertext() == new_justice_region->entertext() &&
×
3574
         cur_justice_region->leavetext() == new_justice_region->leavetext() )
×
3575
    {
3576
      printmsgs = false;
×
3577
    }
3578
    else
3579
    {
3580
      printmsgs = true;
×
3581
    }
3582

3583
    if ( printmsgs && cur_justice_region )
×
3584
    {
3585
      const std::string& leavetext = cur_justice_region->leavetext();
×
3586
      if ( !leavetext.empty() )
×
3587
      {
3588
        Core::send_sysmessage( client, leavetext );
×
3589
      }
3590
    }
3591

3592
    client->gd->justice_region = new_justice_region;
×
3593

3594
    if ( new_justice_region && new_justice_region->RunNoCombatCheck( client ) == true )
×
3595
    {
NEW
3596
      get_opponent().remove_opponent_of( Attackable{ client->chr } );
×
NEW
3597
      if ( auto* opp2 = get_opponent().mobile(); opp2 && opp2->client )
×
3598
      {
3599
        // TODO Attackable
NEW
3600
        opp2->set_opponent( {}, true );
×
3601
        opp2->schedule_attack();
×
NEW
3602
        opp2->opponent_.clear();
×
3603
        opp2->clear_opponent_of();
×
NEW
3604
        set_opponent( {}, true );
×
3605
        if ( swing_task != nullptr )
×
3606
          swing_task->cancel();
×
3607
      }
3608
    }
3609

3610

3611
    // print 'entering' message
3612
    // handle nocombat while we have entered.
3613
    if ( printmsgs && new_justice_region )
×
3614
    {
3615
      const std::string& entertext = new_justice_region->entertext();
×
3616
      if ( !entertext.empty() )
×
3617
      {
3618
        Core::send_sysmessage( client, entertext );
×
3619
      }
3620
    }
3621
  }
3622
}
103✔
3623

3624
void Character::check_music_region_change()
103✔
3625
{
3626
  Core::MusicRegion* cur_music_region = client->gd->music_region;
103✔
3627
  Core::MusicRegion* new_music_region = Core::gamestate.musicdef->getregion( pos() );
103✔
3628

3629
  // may want to consider changing every n minutes, too, even if region didn't change
3630
  if ( cur_music_region != new_music_region )
103✔
3631
  {
3632
    client->gd->music_region = new_music_region;
×
3633
    if ( new_music_region != nullptr )
×
3634
    {
3635
      Core::send_midi( client, new_music_region->getmidi() );
×
3636
    }
3637
    else
3638
    {
3639
      Core::send_midi( client, 0 );
×
3640
    }
3641
  }
3642
}
103✔
3643

3644
void Character::check_weather_region_change( bool force )  // dave changed 5/26/03 - use force
105✔
3645
                                                           // boolean if current weather region
3646
                                                           // changed type/intensity
3647
{
3648
  Core::WeatherRegion* cur_weather_region = client->gd->weather_region;
105✔
3649
  Core::WeatherRegion* new_weather_region = Core::gamestate.weatherdef->getregion( pos() );
105✔
3650

3651
  // eric 5/31/03: I don't think this is right.  it's possible to go from somewhere that has
3652
  // no weather region, and to walk to somewhere that doesn't have a weather region.
3653
  //
3654
  if ( force || ( cur_weather_region != new_weather_region ) )
105✔
3655
  {
3656
    if ( new_weather_region != nullptr && new_weather_region->lightoverride != -1 &&
2✔
3657
         !has_lightoverride() )
×
3658
    {
3659
      Core::send_light( client, new_weather_region->lightoverride );
×
3660
      client->gd->lightlevel = new_weather_region->lightoverride;
×
3661
    }
3662

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

3670
    if ( new_weather_region )
2✔
3671
    {
3672
      Core::send_weather( client, new_weather_region->weathertype, new_weather_region->severity,
×
3673
                          new_weather_region->aux );
×
3674
    }
3675
    else
3676
    {
3677
      Core::send_weather( client, 0xff, 0, 0 );  // turn off
2✔
3678
    }
3679
    client->gd->weather_region = new_weather_region;
2✔
3680
  }
3681
}
105✔
3682

3683
void Character::check_region_changes()
166✔
3684
{
3685
  if ( client != nullptr )
166✔
3686
  {
3687
    check_weather_region_change();
103✔
3688

3689
    check_light_region_change();
103✔
3690

3691
    check_justice_region_change();
103✔
3692

3693
    check_music_region_change();
103✔
3694
  }
3695
}
166✔
3696

3697
void Character::position_changed()
256✔
3698
{
3699
  wornitems->setposition( pos() );
256✔
3700
  position_changed_at_ = Core::polclock();
256✔
3701
}
256✔
3702

3703
void Character::unhide()
3✔
3704
{
3705
  if ( Core::gamestate.system_hooks.un_hide )
3✔
3706
  {
3707
    if ( !Core::gamestate.system_hooks.un_hide->call( make_mobileref( this ) ) )
×
3708
      return;
×
3709
  }
3710

3711
  hidden( false );
3✔
3712
  if ( is_visible() )
3✔
3713
  {
3714
    if ( client != nullptr )
3✔
3715
      send_owncreate( client, this );
×
3716

3717
    Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
3✔
3718
        this,
3719
        [&]( Character* chr )
3✔
3720
        {
3721
          if ( chr == this )
×
3722
            return;
×
3723
          if ( !chr->is_visible_to_me( this ) )
×
3724
            return;
×
3725
          send_owncreate( chr->client, this );
×
3726
        } );
3727

3728
    realm()->notify_unhid( *this );
3✔
3729

3730
    if ( !Clib::exit_signalled )
3✔
3731
    {
3732
      check_attack_after_move( true );
3✔
3733
    }
3734
  }
3735
}
3736

3737
void Character::set_stealthsteps( unsigned short newval )
8✔
3738
{
3739
  stealthsteps_ = newval;
8✔
3740
}
8✔
3741

3742
bool Character::doors_block() const
98✔
3743
{
3744
  return !( graphic == UOBJ_GAMEMASTER || graphic == UOBJ_HUMAN_MALE_GHOST ||
196✔
3745
            graphic == UOBJ_HUMAN_FEMALE_GHOST || graphic == UOBJ_ELF_MALE_GHOST ||
98✔
3746
            graphic == UOBJ_ELF_FEMALE_GHOST || graphic == UOBJ_GARGOYLE_MALE_GHOST ||
98✔
3747
            graphic == UOBJ_GARGOYLE_FEMALE_GHOST ||
98✔
3748
            cached_settings.get( PRIV_FLAGS::IGNORE_DOORS ) );
196✔
3749
}
3750

3751
/*
3752
    This isn't quite right, for hidden characters.
3753
    What normally happens:
3754
    1) Client sends "use skill hiding"
3755
    2) Server sends "77 move" with the hidden flag set.
3756
    3) client sends move-request
3757
    4) server sends move-approve
3758
    5) server sense "78 create" message with hidden flag clear, at new posn.
3759

3760
    We're sending the "78 create" _before_ the move-approve.
3761
    */
3762

3763
bool Character::can_face( Core::UFACING /*i_facing*/ )
24✔
3764
{
3765
  if ( can_freemove() )
24✔
3766
    return true;
×
3767

3768
  if ( frozen() || paralyzed() )
24✔
3769
  {
3770
    if ( client != nullptr )
×
3771
    {
3772
      if ( frozen() )
×
3773
        private_say_above( this, this, "I am frozen and cannot move." );
×
3774
      else if ( paralyzed() )
×
3775
        private_say_above( this, this, "I am paralyzed and cannot move." );
×
3776
    }
3777
    return false;
×
3778
  }
3779

3780
  if ( Core::settingsManager.ssopt.movement_uses_stamina &&
72✔
3781
       vital( Core::gamestate.pVitalStamina->vitalid ).current_ones() == 0 && !dead() )
24✔
3782
  {
3783
    private_say_above( this, this, "You are too fatigued to move." );
×
3784
    return false;
×
3785
  }
3786

3787
  return true;
24✔
3788
}
3789

3790

3791
bool Character::face( Core::UFACING i_facing, int flags )
24✔
3792
{
3793
  if ( ( flags & 1 ) == 0 )
24✔
3794
  {
3795
    if ( !can_face( i_facing ) )
24✔
3796
      return false;
×
3797
  }
3798

3799
  if ( i_facing != facing )
24✔
3800
  {
3801
    setfacing( static_cast<u8>( i_facing ) );
8✔
3802

3803
    if ( Core::settingsManager.combat_config.reset_swing_onturn &&
16✔
3804
         !cached_settings.get( PRIV_FLAGS::FIRE_WHILE_MOVING ) && weapon->is_projectile() )
8✔
3805
      reset_swing_timer();
×
3806
    if ( hidden() && ( Core::settingsManager.ssopt.hidden_turns_count ) )
8✔
3807
    {
3808
      if ( stealthsteps_ == 0 )
×
3809
        unhide();
×
3810
      else
3811
        stealthsteps_--;
×
3812
    }
3813
  }
3814

3815
  return true;
24✔
3816
}
3817

3818

3819
bool Character::CustomHousingMove( unsigned char i_dir )
×
3820
{
3821
  passert( facing < 8 );
×
3822

3823
  Multi::UMulti* multi = Core::system_find_multi( client->gd->custom_house_serial );
×
3824
  if ( multi != nullptr )
×
3825
  {
3826
    Multi::UHouse* house = multi->as_house();
×
3827
    if ( house != nullptr )
×
3828
    {
3829
      Core::UFACING i_facing = static_cast<Core::UFACING>( i_dir & PKTIN_02_FACING_MASK );
×
3830
      if ( i_facing != facing )
×
3831
      {
3832
        setfacing( static_cast<u8>( i_facing ) );
×
3833
        set_dirty();
×
3834
        dir = i_dir;
×
3835
        return true;
×
3836
      }
3837

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

3852
        position_changed();
×
3853
        set_dirty();
×
3854
        return true;
×
3855
      }
3856
    }
3857
  }
3858
  return false;
×
3859
}
3860

3861
bool Character::is_piloting_boat() const
138✔
3862
{
3863
  auto* mountpiece = wornitem( Core::LAYER_MOUNT );
138✔
3864
  return mountpiece != nullptr && mountpiece->objtype_ == Core::settingsManager.extobj.boatmount;
138✔
3865
}
3866

3867
//************************************
3868
// Method:    move
3869
// FullName:  Character::move
3870
// Access:    public
3871
// Returns:   bool
3872
// Qualifier:
3873
// Parameter: unsigned char i_dir
3874
//************************************
3875
bool Character::move( unsigned char i_dir )
20✔
3876
{
3877
  if ( is_piloting_boat() )
20✔
3878
  {
3879
    return false;
1✔
3880
  }
3881

3882
  lastpos = pos();
19✔
3883

3884
  // if currently building a house chr can move free inside the multi
3885
  if ( is_house_editing() )
19✔
3886
    return CustomHousingMove( i_dir );
×
3887

3888
  u8 oldFacing = facing;
19✔
3889

3890
  Core::UFACING i_facing = static_cast<Core::UFACING>( i_dir & PKTIN_02_FACING_MASK );
19✔
3891
  if ( !face( i_facing ) )
19✔
3892
    return false;
×
3893

3894
  // If we're a player, and we used our "move" command to turn,
3895
  //  we want to skip the meat of this function
3896
  if ( ( script_isa( Core::POLCLASS_NPC ) ) || ( facing == oldFacing ) )
19✔
3897
  {
3898
    if ( facing & 1 )  // check if diagonal movement is allowed -- Nando (2009-02-26)
14✔
3899
    {
3900
      short new_z;
3901
      u8 tmp_facing = ( facing + 1 ) & 0x7;
1✔
3902
      auto tmp_pos = pos().move( static_cast<Core::UFACING>( tmp_facing ) );
1✔
3903

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

3908
      tmp_facing = ( facing - 1 ) & 0x7;
1✔
3909
      tmp_pos = pos().move( static_cast<Core::UFACING>( tmp_facing ) );
1✔
3910

3911
      if ( !walk1 && !realm()->walkheight( this, tmp_pos.xy(), tmp_pos.z(), &new_z, nullptr,
1✔
3912
                                           nullptr, nullptr ) )
3913
        return false;
×
3914
    }
3915
    auto new_pos = pos().move( static_cast<Core::UFACING>( facing ) );
14✔
3916

3917
    // FIXME consider consolidating with similar code in UOEMOD.CPP
3918
    short newz;
3919
    Multi::UMulti* supporting_multi;
3920
    Items::Item* walkon_item;
3921

3922
    short current_boost = gradual_boost;
14✔
3923
    if ( !realm()->walkheight( this, new_pos.xy(), new_pos.z(), &newz, &supporting_multi,
14✔
3924
                               &walkon_item, &current_boost ) )
3925
      return false;
1✔
3926
    new_pos.z( static_cast<s8>( newz ) );
13✔
3927
    remote_containers_.clear();
13✔
3928

3929
    if ( !CheckPushthrough() )
13✔
3930
      return false;
×
3931

3932
    if ( !can_freemove() && Core::settingsManager.ssopt.movement_uses_stamina && !dead() )
13✔
3933
    {
3934
      int carry_perc = weight() * 100 / carrying_capacity();
13✔
3935
      unsigned short tmv = movecost(
26✔
3936
          this, carry_perc, ( i_dir & PKTIN_02_DIR_RUNNING_BIT ) ? true : false, on_mount() );
13✔
3937
      VitalValue& stamina = vital( Core::gamestate.pVitalStamina->vitalid );
13✔
3938
      if ( !consume( Core::gamestate.pVitalStamina, stamina, tmv, VitalDepletedReason::MOVEMENT ) )
13✔
3939
      {
3940
        private_say_above( this, this, "You are too fatigued to move." );
×
3941
        return false;
×
3942
      }
3943
    }
3944

3945
    // Maybe have a flag for this in servspecopt?
3946
    if ( !cached_settings.get( PRIV_FLAGS::FIRE_WHILE_MOVING ) && weapon->is_projectile() )
13✔
3947
      reset_swing_timer();
×
3948

3949
    Core::Pos4d oldpos = pos();
13✔
3950
    setposition( new_pos );
13✔
3951
    moved_at_ = Core::polclock();
13✔
3952

3953
    if ( on_mount() && !script_isa( Core::POLCLASS_NPC ) )
13✔
3954
    {
3955
      mountedsteps_++;
×
3956
    }
3957
    if ( supporting_multi != nullptr )
13✔
3958
    {
3959
      supporting_multi->register_object( this );
×
3960

3961
      if ( this->registered_multi == 0 )
×
3962
      {
3963
        this->registered_multi = supporting_multi->serial;
×
3964
        supporting_multi->walk_on( this );
×
3965
      }
3966
    }
3967
    else
3968
    {
3969
      if ( registered_multi > 0 )
13✔
3970
      {
3971
        Multi::UMulti* multi = Core::system_find_multi( registered_multi );
×
3972
        if ( multi != nullptr )
×
3973
        {
3974
          multi->unregister_object( (UObject*)this );
×
3975
        }
3976
        registered_multi = 0;
×
3977
      }
3978
    }
3979

3980
    gradual_boost = current_boost;
13✔
3981
    MoveCharacterWorldPosition( oldpos, this );
13✔
3982

3983
    position_changed();
13✔
3984
    if ( walkon_item != nullptr )
13✔
3985
    {
3986
      walkon_item->walk_on( this );
×
3987
    }
3988

3989
    if ( hidden() )
13✔
3990
    {
3991
      if ( ( ( i_dir & PKTIN_02_DIR_RUNNING_BIT ) &&
×
3992
             !cached_settings.get( PRIV_FLAGS::RUN_WHILE_STEALTH ) ) ||
×
3993
           ( stealthsteps_ == 0 ) )
×
3994
        unhide();
×
3995
      else if ( stealthsteps_ )
×
3996
        --stealthsteps_;
×
3997
    }
3998

3999
    if ( Core::gamestate.system_hooks.ouch_hook )
13✔
4000
    {
4001
      if ( ( lastpos.z() - z() ) > 21 )
×
4002
        Core::gamestate.system_hooks.ouch_hook->call(
×
4003
            make_mobileref( this ), new Bscript::BLong( lastpos.x() ),
×
4004
            new Bscript::BLong( lastpos.y() ), new Bscript::BLong( lastpos.z() ) );
×
4005
    }
4006
  }
4007

4008
  set_dirty();
18✔
4009
  dir = i_dir;
18✔
4010

4011
  return true;
18✔
4012

4013

4014
  // this would be a great place for tellmove(), except that
4015
  // we want to send the move acknowledge to the client before
4016
  // sending the move/create messages to the neighboring clients.
4017

4018
  // Why? Maybe to give the best response time to the client.
4019

4020
  // may want to rethink this, since it's starting to complicate
4021
  // things.
4022
}
4023

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

4050
  if ( has_active_client() )
18✔
4051
  {
4052
    // these are important to keep here in this order
4053
    Core::send_realm_change( client, realm() );
14✔
4054
    Core::send_map_difs( client );
14✔
4055
    if ( Core::settingsManager.ssopt.core_sends_season )
14✔
4056
      Core::send_season_info( client );
14✔
4057
    Core::send_full_statmsg( client, this );
14✔
4058
    Core::send_feature_enable( client );
14✔
4059
  }
4060
}
18✔
4061

4062
bool Character::CheckPushthrough()
13✔
4063
{
4064
  if ( !can_freemove() && Core::gamestate.system_hooks.pushthrough_hook )
13✔
4065
  {
4066
    auto newpos = pos().move( static_cast<Core::UFACING>( facing ) );
×
4067
    auto mobs = std::unique_ptr<Bscript::ObjArray>();
×
4068

4069
    Core::WorldIterator<Core::MobileFilter>::InRange(
×
4070
        newpos, 0,
4071
        [&]( Mobile::Character* _chr )
×
4072
        {
4073
          if ( _chr->z() >= z() - 10 && _chr->z() <= z() + 10 && !_chr->dead() &&
×
4074
               ( is_visible_to_me( _chr ) ||
×
4075
                 _chr->hidden() ) )  // add hidden mobs even if they're not visible to me
×
4076
          {
4077
            if ( !mobs )
×
4078
              mobs = std::make_unique<Bscript::ObjArray>();
×
4079
            mobs->addElement( make_mobileref( _chr ) );
×
4080
          }
4081
        } );
×
4082

4083
    if ( mobs )
×
4084
    {
4085
      return Core::gamestate.system_hooks.pushthrough_hook->call( make_mobileref( this ),
×
4086
                                                                  mobs.release() );
×
4087
    }
4088
    return true;
×
4089
  }
×
4090
  return true;
13✔
4091
}
4092

4093
void Character::tellmove()
164✔
4094
{
4095
  check_region_changes();
164✔
4096
  PropagateMove( this );
164✔
4097

4098
  // notify npcs and items (maybe the PropagateMove should also go there eventually? - Nando
4099
  // 2018-06-16)
4100
  realm()->notify_moved( *this );
164✔
4101

4102
  check_attack_after_move( true );
164✔
4103

4104
  move_reason = OTHER;
164✔
4105
}
164✔
4106

4107
void Character::add_remote_container( Items::Item* item )
×
4108
{
4109
  remote_containers_.emplace_back( item );
×
4110
}
×
4111

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

4141
bool Character::mightsee( const Items::Item* item ) const
76✔
4142
{
4143
  const auto* owner = item->toplevel_owner();
76✔
4144
  for ( const auto& elem : remote_containers_ )
76✔
4145
  {
4146
    Items::Item* additional_item = elem.get();
×
4147
    if ( additional_item == owner )
×
4148
      return true;
×
4149
  }
4150

4151
  return in_visual_range( owner );
76✔
4152
}
4153

4154
bool Character::squelched() const
37✔
4155
{
4156
  Core::gameclock_t squelched = squelched_until();
37✔
4157
  if ( squelched == 0 )
37✔
4158
    return false;
36✔
4159
  if ( squelched == ~0u )
1✔
4160
    return true;
×
4161

4162
  if ( Core::read_gameclock() < squelched )
1✔
4163
  {
4164
    return true;
1✔
4165
  }
4166

4167
  const_cast<Character*>( this )->squelched_until( 0 );
×
4168
  return false;
×
4169
}
4170

4171
bool Character::deafened() const
38✔
4172
{
4173
  Core::gameclock_t deafened = deafened_until();
38✔
4174
  if ( deafened == 0 )
38✔
4175
    return false;
37✔
4176
  if ( deafened == ~0u )
1✔
4177
    return true;
×
4178

4179
  if ( Core::read_gameclock() < deafened )
1✔
4180
  {
4181
    return true;
1✔
4182
  }
4183

4184
  const_cast<Character*>( this )->deafened_until( 0 );
×
4185
  return false;
×
4186
}
4187

4188
bool Character::invul() const
462✔
4189
{
4190
  return cached_settings.get( PRIV_FLAGS::INVUL );
462✔
4191
}
4192

4193
u16 Character::strength() const
21✔
4194
{
4195
  return static_cast<u16>( attribute( Core::gamestate.pAttrStrength->attrid ).effective() );
21✔
4196
}
4197
u16 Character::dexterity() const
177✔
4198
{
4199
  return static_cast<u16>( attribute( Core::gamestate.pAttrDexterity->attrid ).effective() );
177✔
4200
}
4201
u16 Character::intelligence() const
×
4202
{
4203
  return static_cast<u16>( attribute( Core::gamestate.pAttrIntelligence->attrid ).effective() );
×
4204
}
4205

4206
bool Character::target_cursor_busy() const
8✔
4207
{
4208
  if ( client && client->gd && client->gd->tcursor2 && client->gd->target_cursor_uoemod != nullptr )
8✔
4209
    return true;
×
4210
  return false;
8✔
4211
}
4212

4213
void Character::cancel_menu()
×
4214
{
4215
  if ( client )
×
4216
  {
4217
    client->gd->menu.clear();
×
4218
    if ( client->gd->on_menu_selection != nullptr )
×
4219
      client->gd->on_menu_selection( client, nullptr, nullptr );
×
4220
    client->gd->on_menu_selection = nullptr;
×
4221
  }
4222
}
×
4223

4224
bool Character::is_trading() const
194✔
4225
{
4226
  return ( trading_with.get() != nullptr );
194✔
4227
}
4228

4229
bool Character::trade_accepted() const
×
4230
{
4231
  return mob_flags_.get( MOB_FLAGS::TRADE_ACCEPTED );
×
4232
}
4233

4234
void Character::trade_accepted( bool newvalue )
×
4235
{
4236
  mob_flags_.change( MOB_FLAGS::TRADE_ACCEPTED, newvalue );
×
4237
}
×
4238

4239
void Character::create_trade_container()
×
4240
{
4241
  if ( trading_cont.get() == nullptr )  // FIXME hardcoded
×
4242
  {
4243
    Items::Item* cont = Items::Item::create( Core::settingsManager.extobj.secure_trade_container );
×
4244
    // TODO Pos: no realm
4245
    cont->setposition( pos() );
×
4246
    trading_cont.set( static_cast<Core::UContainer*>( cont ) );
×
4247
  }
4248
}
×
4249

4250
Core::UContainer* Character::trade_container()
×
4251
{
4252
  return trading_cont.get();
×
4253
}
4254

4255
// SkillValue removed for no use - MuadDib
4256

4257
const char* Character::target_tag() const
×
4258
{
4259
  return "mobile";
×
4260
}
4261

4262
void Character::set_caps_to_default()
99✔
4263
{
4264
  for ( unsigned ai = 0; ai < Core::gamestate.numAttributes; ++ai )
5,247✔
4265
  {
4266
    Attribute* pAttr = Core::gamestate.attributes[ai];
5,148✔
4267
    AttributeValue& av = attribute( ai );
5,148✔
4268

4269
    av = attribute( ai );
5,148✔
4270
    av.cap( pAttr->default_cap );
5,148✔
4271
  }
4272
}
99✔
4273

4274
u16 Character::last_textcolor() const
×
4275
{
4276
  return _last_textcolor;
×
4277
}
4278

4279
void Character::last_textcolor( u16 new_color )
3✔
4280
{
4281
  _last_textcolor = new_color;
3✔
4282
}
3✔
4283

4284
unsigned int Character::guildid() const
2✔
4285
{
4286
  auto g = guild();
2✔
4287
  return ( g != nullptr ) ? g->guildid() : 0;
2✔
4288
}
4289

4290
/**
4291
 * Adds a new buff or overwrites an existing one for the character
4292
 * Sends packets to the client accordingly
4293
 * @author Bodom
4294
 */
4295
void Character::addBuff( u16 icon, u16 duration, u32 cl_name, const std::string& name_arguments,
2✔
4296
                         u32 cl_descr, const std::string& desc_arguments )
4297
{
4298
  // Icon is already present, must send a remove packet first or client will not update
4299
  delBuff( icon );
2✔
4300

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

4304
  if ( client != nullptr )
2✔
4305
    send_buff_message( this, icon, true, duration, cl_name, name_arguments, cl_descr,
×
4306
                       desc_arguments );
4307
}
4✔
4308

4309
/**
4310
 * Removes a buff for the character
4311
 * Sends packets to the client accordingly
4312
 * @author Bodom
4313
 * @return True when the buff has been found and removed, False when the buff was not present
4314
 */
4315
bool Character::delBuff( u16 icon )
4✔
4316
{
4317
  auto b = buffs_.find( icon );
4✔
4318

4319
  if ( b == buffs_.end() )
4✔
4320
    return false;
3✔
4321

4322
  buffs_.erase( b );
1✔
4323
  if ( client != nullptr )
1✔
4324
    send_buff_message( this, icon, false );
×
4325
  return true;
1✔
4326
}
4327

4328
/**
4329
 * Removes al buffs for the character
4330
 * Sends packets to the client accordingly
4331
 * @author Bodom
4332
 */
4333
void Character::clearBuffs()
2✔
4334
{
4335
  if ( client != nullptr )
2✔
4336
  {
4337
    for ( const auto& buf : buffs_ )
×
4338
    {
4339
      send_buff_message( this, buf.first, false );
×
4340
    }
4341
  }
4342
  buffs_.clear();
2✔
4343
}
2✔
4344

4345
/**
4346
 * Resends all buffs (with updated duration), usually called at (re)login
4347
 * @author Bodom
4348
 */
4349
void Character::send_buffs()
2✔
4350
{
4351
  if ( client == nullptr )
2✔
4352
    return;
×
4353

4354
  for ( const auto& [icon, buf] : buffs_ )
2✔
4355
  {
4356
    u16 duration = Clib::clamp_convert<u16>( buf.end - Core::read_gameclock() );
×
4357

4358
    send_buff_message( this, icon, true, duration, buf.cl_name, buf.name_arguments, buf.cl_descr,
×
4359
                       buf.desc_arguments );
×
4360
  }
4361
}
4362

4363
u8 Character::los_size() const
62,974✔
4364
{
4365
  return client ? client->update_range() : Core::settingsManager.ssopt.default_visual_range;
62,974✔
4366
}
4367

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

4408
  size += Clib::memsize( attributes ) + Clib::memsize( vitals ) + Clib::memsize( armor_ ) +
2✔
4409
          Clib::memsize( remote_containers_ ) + Clib::memsize( opponent_of ) +
2✔
4410
          Clib::memsize( aggressor_to_ ) + Clib::memsize( lawfully_damaged_ ) +
2✔
4411
          Clib::memsize( to_be_reportable_ ) + Clib::memsize( reportable_ ) +
2✔
4412
          Clib::memsize( buffs_ );
2✔
4413
  return size;
2✔
4414
}
4415

4416
void Character::on_delete_from_account()
3✔
4417
{
4418
  if ( realm() )
3✔
4419
    realm()->remove_mobile( *this, Realms::WorldChangeReason::PlayerDeleted );
3✔
4420
}
3✔
4421

4422
bool Character::has_paperdoll() const
14✔
4423
{
4424
  switch ( graphic )
14✔
4425
  {
4426
  case UOBJ_HUMAN_MALE:
14✔
4427
  case UOBJ_HUMAN_FEMALE:
4428
  case UOBJ_HUMAN_MALE_GHOST:
4429
  case UOBJ_HUMAN_FEMALE_GHOST:
4430
  case UOBJ_ELF_MALE:
4431
  case UOBJ_ELF_FEMALE:
4432
  case UOBJ_ELF_MALE_GHOST:
4433
  case UOBJ_ELF_FEMALE_GHOST:
4434
  case UOBJ_GARGOYLE_MALE:
4435
  case UOBJ_GARGOYLE_FEMALE:
4436
  case UOBJ_GARGOYLE_MALE_GHOST:
4437
  case UOBJ_GARGOYLE_FEMALE_GHOST:
4438
  case UOBJ_GAMEMASTER:
4439
  case UOBJ_LORD_BRITISH:
4440
  case UOBJ_BLACKTHORN:
4441
  case UOBJ_DUPRE:
4442
    return true;
14✔
4443
  default:
×
4444
    return false;
×
4445
  }
4446
}
4447

4448
bool Character::get_method_hook( const char* methodname, Bscript::Executor* ex,
×
4449
                                 Core::ExportScript** hook, unsigned int* PC ) const
4450
{
4451
  if ( Core::gamestate.system_hooks.get_method_hook(
×
4452
           Core::gamestate.system_hooks.character_method_script.get(), methodname, ex, hook, PC ) )
4453
    return true;
×
4454
  return base::get_method_hook( methodname, ex, hook, PC );
×
4455
}
4456

4457
void Character::update_objects_on_range_change( u8 newrange )
2✔
4458
{
4459
  Network::RemoveObjectPkt msgremove( serial_ext );
2✔
4460

4461
  Core::WorldIterator<Core::MobileFilter>::InRange(
2✔
4462
      this, std::max( newrange, los_size() ),
2✔
4463
      [&]( Mobile::Character* zonechr )
2✔
4464
      {
4465
        if ( this == zonechr || !is_visible_to_me( zonechr, false /*rangecheck*/ ) )
2✔
4466
          return;
2✔
4467
        bool was_inrange = in_range( zonechr, los_size() + zonechr->visible_size() );
×
4468
        bool is_inrange = in_range( zonechr, newrange + zonechr->visible_size() );
×
4469
        if ( was_inrange && is_inrange )
×
4470
          return;
×
4471
        if ( !was_inrange && is_inrange )
×
4472
          Core::send_owncreate( client, zonechr );
×
4473
        else if ( was_inrange && !is_inrange )
×
4474
          Core::send_remove_character( client, zonechr, msgremove );
×
4475
      } );
4476

4477
  Core::WorldIterator<Core::ItemFilter>::InRange(
2✔
4478
      this, Core::gamestate.max_update_range_multi_only() + std::max( newrange, los_size() ),
2✔
4479
      [&]( Items::Item* zoneitem )
2✔
4480
      {
4481
        bool was_inrange = in_range( zoneitem, los_size() + zoneitem->visible_size() );
8✔
4482
        bool is_inrange = in_range( zoneitem, newrange + zoneitem->visible_size() );
8✔
4483
        if ( was_inrange && is_inrange )
8✔
4484
          return;
×
4485
        if ( !was_inrange && is_inrange )
8✔
4486
          Core::send_item( client, zoneitem );
3✔
4487
        else if ( was_inrange && !is_inrange )
5✔
4488
          Core::send_remove_object( client, zoneitem, msgremove );
3✔
4489
      } );
4490

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

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

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

4645
}  // namespace Mobile
4646
}  // 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