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

polserver / polserver / 25987404945

17 May 2026 09:41AM UTC coverage: 60.852% (-0.05%) from 60.903%
25987404945

Pull #884

github

turleypol
updated dynprops a bit more.
made the structs trivial copyable
updated concept to not repeat the types
Pull Request #884: Attackable item

251 of 540 new or added lines in 26 files covered. (46.48%)

17 existing lines in 7 files now uncovered.

44733 of 73511 relevant lines covered (60.85%)

498686.16 hits per line

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

63.63
/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,297✔
424
{
425
  return mob_flags_.get( MOB_FLAGS::LOGGED_IN );
1,297✔
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
1✔
434
{
435
  return mob_flags_.get( MOB_FLAGS::CONNECTED );
1✔
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,878✔
444
{
445
  return ( client != nullptr && client->isActive() );
50,878✔
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
111✔
465
{
466
  return ( client != nullptr && client->gd != nullptr && client->gd->custom_house_serial != 0 );
111✔
467
}
468

469
void Character::clear_gotten_item()
77✔
470
{
471
  if ( !has_gotten_item() )
77✔
472
    return;
76✔
473
  auto info = gotten_item();
1✔
474
  if ( info.item() != nullptr )
1✔
475
  {
476
    gotten_item( {} );
1✔
477
    info.item()->inuse( false );
1✔
478
    if ( connected() )
1✔
479
      Core::send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN );
1✔
480
    info.undo( this );
1✔
481
  }
482
}
1✔
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
99✔
531
{
532
  unsigned int wt = 10 + wornitems->weight();
99✔
533
  if ( has_gotten_item() )
99✔
534
    wt += gotten_item().item()->weight();
26✔
535
  if ( trading_cont.get() )
99✔
536
    wt += trading_cont->weight();
×
537
  return wt;
99✔
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
15✔
545
{
546
  return static_cast<u16>( floor( ( 40 + strength() * 7 / 2 + carrying_capacity_mod() ) *
15✔
547
                                  Core::settingsManager.ssopt.carrying_capacity_mod ) );
15✔
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
56✔
1224
{
1225
  return cached_settings.get( PRIV_FLAGS::FREEMOVE );
56✔
1226
}
1227

1228
Core::UContainer* Character::backpack() const
241✔
1229
{
1230
  return static_cast<Core::UContainer*>( wornitems->GetItemOnLayer( Core::LAYER_BACKPACK ) );
241✔
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
82✔
1260
{
1261
  Core::UContainer* bp = backpack();
82✔
1262
  if ( bp != nullptr )
82✔
1263
    return bp->find_sumof_objtype_noninuse( UOBJ_GOLD_COIN );
82✔
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,118✔
1282
{
1283
  return wornitems->GetItemOnLayer( layer );
6,118✔
1284
}
1285

1286
bool Character::layer_is_equipped( int layer ) const
117✔
1287
{
1288
  return ( wornitems->GetItemOnLayer( layer ) != nullptr );
117✔
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
52✔
1449
{
1450
  if ( race == Plib::RACE_GARGOYLE )
52✔
1451
    return ( movemode & Plib::MOVEMODE_FLY ) == 0 ? false : true;
×
1452

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

1456

1457
Items::Item* Character::find_wornitem( u32 find_serial ) const
43✔
1458
{
1459
  for ( unsigned layer = Core::LAYER_EQUIP__LOWEST; layer <= Core::LAYER_EQUIP__HIGHEST; layer++ )
1,118✔
1460
  {
1461
    Items::Item* item = wornitems->GetItemOnLayer( layer );
1,075✔
1462
    if ( item )
1,075✔
1463
    {
1464
      if ( item->serial == find_serial )
43✔
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 ) )
43✔
1471
      {
1472
        if ( layer != Core::LAYER_HAIR && layer != Core::LAYER_FACE && layer != Core::LAYER_BEARD &&
43✔
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;
43✔
1485
}
1486

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

1496
bool Character::consume( const Core::Vital* pVital, VitalValue& vv, unsigned int amt,
15✔
1497
                         VitalDepletedReason reason )
1498
{
1499
  int start_ones = vv.current_ones();
15✔
1500
  set_dirty();
15✔
1501
  bool res = vv.consume( amt );
15✔
1502
  if ( start_ones != vv.current_ones() )
15✔
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;
15✔
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 )
117✔
1537
{
1538
  VitalValue& vv = vital( pVital->vitalid );
117✔
1539
  int rr = vv.regenrate();
117✔
1540
  if ( rr > 0 )
117✔
1541
    produce( pVital, vv, rr / 12 );
117✔
1542
  else if ( rr < 0 )
×
1543
    consume( pVital, vv, -rr / 12, VitalDepletedReason::REGENERATE );
×
1544
}
117✔
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
205✔
1713
{
1714
  // Breaks paperdoll
1715
  u8 flag1 = 0;
205✔
1716
  if ( gender )
205✔
1717
    flag1 |= Core::CHAR_FLAG1_GENDER;
×
1718
  if ( ( poisoned() ) &&
205✔
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 ) &&
205✔
1723
       ( other_client->ClientType & Network::CLIENTTYPE_7000 ) )
×
1724
    flag1 |= Core::CHAR_FLAG1_FLYING;
×
1725
  if ( ( Core::settingsManager.ssopt.invul_tag == 2 ) && ( invul() ) )
205✔
1726
    flag1 |= Core::CHAR_FLAG1_YELLOWHEALTH;
×
1727
  if ( warmode() )
205✔
1728
    flag1 |= Core::CHAR_FLAG1_WARMODE;
1✔
1729
  if ( !is_visible() )
205✔
1730
    flag1 |= Core::CHAR_FLAG1_INVISIBLE;
×
1731

1732
  return flag1;
205✔
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 )
×
1801
  {
1802
    return static_cast<unsigned short>( damage * 0.5 );
×
1803
  }
1804

1805
  int dmg = static_cast<int>( damage );
×
1806
  if ( dmg >= 0 )
×
1807
    return static_cast<unsigned short>( dmg );
×
1808
  return 0;
×
1809
}
1810

1811

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

1819
    armor->reduce_hp_from_hit();
×
1820
  }
1821
  return damage;
×
1822
}
1823

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

1834
  return damage;
×
1835
}
1836

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

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

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

1863
  unsigned short rawdamage = 0;
8✔
1864
  unsigned short basedamage = static_cast<unsigned short>( damage );
8✔
1865

1866
  Items::UArmor* armor = nullptr;
8✔
1867

1868
  defender->get_hitscript_params( damage, &armor, &rawdamage );
8✔
1869

1870

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

1881
  ex->priority( 100 );
8✔
1882

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

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

1908

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

1925
  if ( amount == 0 )
×
1926
    return;
×
1927

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

1930
  check_undamaged();
×
1931

1932
  send_update_hits_to_inrange( this );
×
1933
}
1934

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

1954

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

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

1986
  mob_flags_.remove( MOB_FLAGS::DEAD );
×
1987
  mob_flags_.remove( MOB_FLAGS::WARMODE );
×
1988
  mob_flags_.remove( MOB_FLAGS::FROZEN );
×
1989
  mob_flags_.remove( MOB_FLAGS::PARALYZED );
×
1990

1991
  color = truecolor;
×
1992

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

2003
  if ( !Core::gamestate.pVitalMana->regen_while_dead )
×
2004
    set_current_ones( Core::gamestate.pVitalMana, vital( Core::gamestate.pVitalMana->vitalid ), 0,
×
2005
                      VitalDepletedReason::RESURRECT );
2006

2007
  if ( !Core::gamestate.pVitalStamina->regen_while_dead )
×
2008
    set_current_ones( Core::gamestate.pVitalStamina,
×
2009
                      vital( Core::gamestate.pVitalStamina->vitalid ), 1,
×
2010
                      VitalDepletedReason::RESURRECT );
2011

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

2035
  // equip( create_backpack() );
2036

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

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

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

2073
  if ( client != nullptr )
1✔
2074
  {
2075
    if ( opponent_ )
×
NEW
2076
      opponent_.inform_disengaged( Attackable{ this } );
×
2077

2078
    client->pause();
×
2079
    send_warmode();
×
2080

2081
    // Sends the complete corpse to the client itself, so he knows where his
2082
    // items went.
2083
    send_full_corpse( client, corpse );
×
2084

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

2094
    client->restart();
×
2095
  }
2096

2097
  // change self to ghost for ghosts, remove self for living
2098
  send_remove_character_to_nearby_cantsee( this );
1✔
2099

2100
  send_create_mobile_to_nearby_cansee( this );
1✔
2101

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

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

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

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

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

2149

2150
  u16 save_graphic = graphic;
74✔
2151

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

2172
  mob_flags_.set( MOB_FLAGS::DEAD );
74✔
2173
  mob_flags_.remove( MOB_FLAGS::WARMODE );
74✔
2174
  mob_flags_.remove( MOB_FLAGS::FROZEN );
74✔
2175
  mob_flags_.remove( MOB_FLAGS::PARALYZED );
74✔
2176

2177
  UPDATE_CHECKPOINT();
74✔
2178
  /* FIXME: corpse container difficulties.
2179

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

2192
     The current solution is to put the backpack
2193
     on the corpse.
2194
     */
2195

2196
  Core::UCorpse* corpse = static_cast<Core::UCorpse*>( Items::Item::create( UOBJ_CORPSE ) );
74✔
2197
  this->last_corpse = corpse->serial;
74✔
2198

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

2203
  UPDATE_CHECKPOINT();
74✔
2204

2205
  corpse->color = truecolor;
74✔
2206
  corpse->setposition( pos() );
74✔
2207
  corpse->facing = facing;
74✔
2208
  corpse->corpsetype = save_graphic;
74✔
2209
  // corpse->dir = dir;
2210
  UPDATE_CHECKPOINT();
74✔
2211

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

2219
  // Change the character's color to grey
2220
  color = 0;
74✔
2221
  UPDATE_CHECKPOINT();
74✔
2222

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

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

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

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

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

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

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

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

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

2379
    UPDATE_CHECKPOINT();
2✔
2380

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

2427

2428
  UPDATE_CHECKPOINT();
74✔
2429
  send_death_message( this, corpse );
74✔
2430

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

2444
  UPDATE_CHECKPOINT();
74✔
2445

2446
  clear_opponent_of();
74✔
2447

2448
  set_opponent( {} );
74✔
2449

2450
  UPDATE_CHECKPOINT();
74✔
2451

2452
  on_death( corpse );
74✔
2453

2454
  CLEAR_CHECKPOINT();
74✔
2455
}
2456

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

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

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

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

2508
  new_ar += ar_mod();
164✔
2509

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

2512
  if ( client )
164✔
2513
  {  // CHECKME consider sending less frequently
2514
    send_full_statmsg( client, this );
6✔
2515
  }
2516
}
164✔
2517

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

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

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

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

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

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

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

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

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

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

2670
/* check skill: test skill, advance, reset atrophy timers, blah blah..
2671
   obviously, needs work, and more parameters.
2672
   */
2673

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

2688
  return false;
×
2689
}
2690

2691
void Character::check_concealment_level()
×
2692
{
2693
  if ( concealed() > cmdlevel() )
×
2694
    concealed( cmdlevel() );
×
2695
}
×
2696

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

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

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

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

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

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

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

2807
  // iter over all old in range players and send remove
2808
  Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
166✔
2809
      chr->lastpos,
166✔
2810
      [&]( Character* zonechr )
166✔
2811
      {
2812
        Client* client = zonechr->client;
127✔
2813
        if ( !zonechr->in_visual_range( nullptr, chr->lastpos ) )
127✔
2814
          return;
32✔
2815
        if ( !zonechr->is_visible_to_me( chr, /*check_range*/ false ) )
95✔
2816
          return;
1✔
2817

2818
        if ( zonechr->in_visual_range( chr ) )  // already handled
94✔
2819
          return;
85✔
2820
        // if we just walked out of range of this character, send its
2821
        // client a remove object, or else a ghost character will remain.
2822
        send_remove_character( client, chr, msgremove );
9✔
2823
      } );
2824
}
166✔
2825

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

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

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

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

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

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

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

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

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

2891
  swing_timer_start_clock_ = Core::polclock();
105✔
2892
  if ( swing_task )
105✔
2893
    swing_task->cancel();
6✔
2894

2895
  if ( opponent_ || !opponent_of.empty() )
105✔
2896
  {
2897
    schedule_attack();
13✔
2898
  }
2899
}
105✔
2900

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

2907
  swing_timer_start_clock_ = Core::polclock();
×
2908
  if ( swing_task )
×
2909
    swing_task->cancel();
×
2910

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

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

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

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

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

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

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

2997
  return {};
170✔
2998
}
2999

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

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

3015
void Character::on_swing_failure( Character* /*attacker*/ )
×
3016
{
3017
  // do nothing
3018
}
×
3019

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

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

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

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

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

3051
void Character::inform_moved( Character* /*moved*/ )
27✔
3052
{
3053
  // consider moving PropagateMove here!
3054
}
27✔
3055
void Character::inform_imoved( Character* /*chr*/ ) {}
31✔
3056

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

3066
    if ( !warmode() && ( script_isa( Core::POLCLASS_NPC ) || has_active_client() ) )
4✔
3067
      set_warmode( true );
1✔
3068
  }
3069

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

3082
  opponent_ = std::move( new_opponent );
88✔
3083

3084

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

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

3101
      opponent_.add_opponent_of( this_att );
4✔
3102
      opponent_.inform_engaged( this_att );
4✔
3103
      if ( mob )
4✔
3104
        mob->schedule_attack();
4✔
3105
    }
3106
  }
3107

3108
  send_highlight();
88✔
3109
}
3110

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

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

3130
bool Character::warmode() const
266✔
3131
{
3132
  return mob_flags_.get( MOB_FLAGS::WARMODE );
266✔
3133
}
3134

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

3141
  if ( warmode() != i_warmode )
4✔
3142
  {
3143
    disable_regeneration_for( 2 );
4✔
3144
  }
3145

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

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

3176
          msgmove.Send( chr->client );
2✔
3177
        } );
3178
  }
4✔
3179
}
4✔
3180

3181
const AttributeValue& Character::weapon_attribute() const
16✔
3182
{
3183
  return attribute( weapon->attribute().attrid );
16✔
3184
}
3185

3186
unsigned short Character::random_weapon_damage() const
8✔
3187
{
3188
  return weapon->get_random_damage();
8✔
3189
}
3190

3191
unsigned short Character::min_weapon_damage() const
82✔
3192
{
3193
  return weapon->min_weapon_damage();
82✔
3194
}
3195

3196
unsigned short Character::max_weapon_damage() const
82✔
3197
{
3198
  return weapon->max_weapon_damage();
82✔
3199
}
3200

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

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

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

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

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

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

3273
u16 Character::get_damaged_sound() const
×
3274
{
3275
  if ( gender == Plib::GENDER_MALE )
×
3276
    return SOUND_EFFECT_MALE_DEFENSE;
×
3277
  return SOUND_EFFECT_FEMALE_DEFENSE;
×
3278
}
3279

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

3290

3291
void Character::attack( const Attackable& opponent )
8✔
3292
{
3293
  INC_PROFILEVAR( combat_operations );
8✔
3294

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

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

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

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

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

3355
  auto* opponent_mobile = opponent.mobile();
8✔
3356
  if ( opponent_mobile )
8✔
3357
  {
3358
    repsys_on_attack( opponent_mobile );
8✔
3359
    repsys_on_damage( opponent_mobile );
8✔
3360
  }
3361

3362
  do_attack_effects( opponent );
8✔
3363

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

NEW
3370
    double damage = random_weapon_damage();
×
NEW
3371
    damage_weapon();
×
3372

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

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

NEW
3388
    return;
×
3389
  }
3390

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

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

3410
    double damage = random_weapon_damage();
8✔
3411
    damage_weapon();
8✔
3412

3413
    if ( Core::settingsManager.watch.combat )
8✔
3414
      INFO_PRINTLN( "Base damage: {}", damage );
×
3415

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

3420
    damage *= damage_multiplier;
8✔
3421

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

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

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

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

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

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

3521
  if ( check_opponents_after_check )
186✔
3522
  {
3523
    if ( auto* mob = opponent_.mobile() )
169✔
3524
      mob->check_attack_after_move( false );
3✔
3525

3526
    // attacking can change the opponent_of array drastically.
3527
    AttackableSet tmp( opponent_of );
169✔
3528
    for ( auto& att : tmp )
172✔
3529
    {
3530
      if ( auto* mob = att.mobile() )
3✔
3531
        mob->check_attack_after_move( false );
3✔
3532
    }
3533
  }
169✔
3534
}
186✔
3535

3536

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

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

3562
  if ( newlightlevel != client->gd->lightlevel )
168✔
3563
  {
3564
    Core::send_light( client, newlightlevel );
2✔
3565
    client->gd->lightlevel = newlightlevel;
2✔
3566
  }
3567
}
3568

3569
void Character::check_justice_region_change()
105✔
3570
{
3571
  Core::JusticeRegion* cur_justice_region = client->gd->justice_region;
105✔
3572
  Core::JusticeRegion* new_justice_region = Core::gamestate.justicedef->getregion( pos() );
105✔
3573

3574
  if ( cur_justice_region != new_justice_region )
105✔
3575
  {
3576
    if ( cur_justice_region != nullptr )
×
3577
      cur_justice_region->RunLeaveScript( client->chr );
×
3578
    if ( new_justice_region != nullptr )
×
3579
      new_justice_region->RunEnterScript( client->chr );
×
3580

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

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

3603
    client->gd->justice_region = new_justice_region;
×
3604

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

3621

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

3635
void Character::check_music_region_change()
105✔
3636
{
3637
  Core::MusicRegion* cur_music_region = client->gd->music_region;
105✔
3638
  Core::MusicRegion* new_music_region = Core::gamestate.musicdef->getregion( pos() );
105✔
3639

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

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

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

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

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

3694
void Character::check_region_changes()
168✔
3695
{
3696
  if ( client != nullptr )
168✔
3697
  {
3698
    check_weather_region_change();
105✔
3699

3700
    check_light_region_change();
105✔
3701

3702
    check_justice_region_change();
105✔
3703

3704
    check_music_region_change();
105✔
3705
  }
3706
}
168✔
3707

3708
void Character::position_changed()
258✔
3709
{
3710
  wornitems->setposition( pos() );
258✔
3711
  position_changed_at_ = Core::polclock();
258✔
3712
}
258✔
3713

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

3722
  hidden( false );
3✔
3723
  if ( is_visible() )
3✔
3724
  {
3725
    if ( client != nullptr )
3✔
3726
      send_owncreate( client, this );
×
3727

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

3739
    realm()->notify_unhid( *this );
3✔
3740

3741
    if ( !Clib::exit_signalled )
3✔
3742
    {
3743
      check_attack_after_move( true );
3✔
3744
    }
3745
  }
3746
}
3747

3748
void Character::set_stealthsteps( unsigned short newval )
8✔
3749
{
3750
  stealthsteps_ = newval;
8✔
3751
}
8✔
3752

3753
bool Character::doors_block() const
100✔
3754
{
3755
  return !( graphic == UOBJ_GAMEMASTER || graphic == UOBJ_HUMAN_MALE_GHOST ||
200✔
3756
            graphic == UOBJ_HUMAN_FEMALE_GHOST || graphic == UOBJ_ELF_MALE_GHOST ||
100✔
3757
            graphic == UOBJ_ELF_FEMALE_GHOST || graphic == UOBJ_GARGOYLE_MALE_GHOST ||
100✔
3758
            graphic == UOBJ_GARGOYLE_FEMALE_GHOST ||
100✔
3759
            cached_settings.get( PRIV_FLAGS::IGNORE_DOORS ) );
200✔
3760
}
3761

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

3771
    We're sending the "78 create" _before_ the move-approve.
3772
    */
3773

3774
bool Character::can_face( Core::UFACING /*i_facing*/ )
26✔
3775
{
3776
  if ( can_freemove() )
26✔
3777
    return true;
×
3778

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

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

3798
  return true;
26✔
3799
}
3800

3801

3802
bool Character::face( Core::UFACING i_facing, int flags )
26✔
3803
{
3804
  if ( ( flags & 1 ) == 0 )
26✔
3805
  {
3806
    if ( !can_face( i_facing ) )
26✔
3807
      return false;
×
3808
  }
3809

3810
  if ( i_facing != facing )
26✔
3811
  {
3812
    setfacing( static_cast<u8>( i_facing ) );
8✔
3813

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

3826
  return true;
26✔
3827
}
3828

3829

3830
bool Character::CustomHousingMove( unsigned char i_dir )
×
3831
{
3832
  passert( facing < 8 );
×
3833

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

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

3863
        position_changed();
×
3864
        set_dirty();
×
3865
        return true;
×
3866
      }
3867
    }
3868
  }
3869
  return false;
×
3870
}
3871

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

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

3893
  lastpos = pos();
21✔
3894

3895
  // if currently building a house chr can move free inside the multi
3896
  if ( is_house_editing() )
21✔
3897
    return CustomHousingMove( i_dir );
×
3898

3899
  u8 oldFacing = facing;
21✔
3900

3901
  Core::UFACING i_facing = static_cast<Core::UFACING>( i_dir & PKTIN_02_FACING_MASK );
21✔
3902
  if ( !face( i_facing ) )
21✔
3903
    return false;
×
3904

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

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

3919
      tmp_facing = ( facing - 1 ) & 0x7;
1✔
3920
      tmp_pos = pos().move( static_cast<Core::UFACING>( tmp_facing ) );
1✔
3921

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

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

3933
    short current_boost = gradual_boost;
16✔
3934
    if ( !realm()->walkheight( this, new_pos.xy(), new_pos.z(), &newz, &supporting_multi,
16✔
3935
                               &walkon_item, &current_boost ) )
3936
      return false;
1✔
3937
    new_pos.z( static_cast<s8>( newz ) );
15✔
3938
    remote_containers_.clear();
15✔
3939

3940
    if ( !CheckPushthrough() )
15✔
3941
      return false;
×
3942

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

3956
    // Maybe have a flag for this in servspecopt?
3957
    if ( !cached_settings.get( PRIV_FLAGS::FIRE_WHILE_MOVING ) && weapon->is_projectile() )
15✔
3958
      reset_swing_timer();
×
3959

3960
    Core::Pos4d oldpos = pos();
15✔
3961
    setposition( new_pos );
15✔
3962
    moved_at_ = Core::polclock();
15✔
3963

3964
    if ( on_mount() && !script_isa( Core::POLCLASS_NPC ) )
15✔
3965
    {
3966
      mountedsteps_++;
×
3967
    }
3968
    if ( supporting_multi != nullptr )
15✔
3969
    {
3970
      supporting_multi->register_object( this );
×
3971

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

3991
    gradual_boost = current_boost;
15✔
3992
    MoveCharacterWorldPosition( oldpos, this );
15✔
3993

3994
    position_changed();
15✔
3995
    if ( walkon_item != nullptr )
15✔
3996
    {
3997
      walkon_item->walk_on( this );
×
3998
    }
3999

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

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

4019
  set_dirty();
20✔
4020
  dir = i_dir;
20✔
4021

4022
  return true;
20✔
4023

4024

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

4029
  // Why? Maybe to give the best response time to the client.
4030

4031
  // may want to rethink this, since it's starting to complicate
4032
  // things.
4033
}
4034

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

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

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

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

4094
    if ( mobs )
×
4095
    {
4096
      return Core::gamestate.system_hooks.pushthrough_hook->call( make_mobileref( this ),
×
4097
                                                                  mobs.release() );
×
4098
    }
4099
    return true;
×
4100
  }
×
4101
  return true;
15✔
4102
}
4103

4104
void Character::tellmove()
166✔
4105
{
4106
  check_region_changes();
166✔
4107
  PropagateMove( this );
166✔
4108

4109
  // notify npcs and items (maybe the PropagateMove should also go there eventually? - Nando
4110
  // 2018-06-16)
4111
  realm()->notify_moved( *this );
166✔
4112

4113
  check_attack_after_move( true );
166✔
4114

4115
  move_reason = OTHER;
166✔
4116
}
166✔
4117

4118
void Character::add_remote_container( Items::Item* item )
×
4119
{
4120
  remote_containers_.emplace_back( item );
×
4121
}
×
4122

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

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

4162
  return in_visual_range( owner );
76✔
4163
}
4164

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

4173
  if ( Core::read_gameclock() < squelched )
1✔
4174
  {
4175
    return true;
1✔
4176
  }
4177

4178
  const_cast<Character*>( this )->squelched_until( 0 );
×
4179
  return false;
×
4180
}
4181

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

4190
  if ( Core::read_gameclock() < deafened )
1✔
4191
  {
4192
    return true;
1✔
4193
  }
4194

4195
  const_cast<Character*>( this )->deafened_until( 0 );
×
4196
  return false;
×
4197
}
4198

4199
bool Character::invul() const
466✔
4200
{
4201
  return cached_settings.get( PRIV_FLAGS::INVUL );
466✔
4202
}
4203

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

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

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

4235
bool Character::is_trading() const
196✔
4236
{
4237
  return ( trading_with.get() != nullptr );
196✔
4238
}
4239

4240
bool Character::trade_accepted() const
×
4241
{
4242
  return mob_flags_.get( MOB_FLAGS::TRADE_ACCEPTED );
×
4243
}
4244

4245
void Character::trade_accepted( bool newvalue )
×
4246
{
4247
  mob_flags_.change( MOB_FLAGS::TRADE_ACCEPTED, newvalue );
×
4248
}
×
4249

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

4261
Core::UContainer* Character::trade_container()
×
4262
{
4263
  return trading_cont.get();
×
4264
}
4265

4266
// SkillValue removed for no use - MuadDib
4267

4268
const char* Character::target_tag() const
×
4269
{
4270
  return "mobile";
×
4271
}
4272

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

4280
    av = attribute( ai );
5,148✔
4281
    av.cap( pAttr->default_cap );
5,148✔
4282
  }
4283
}
99✔
4284

4285
u16 Character::last_textcolor() const
×
4286
{
4287
  return _last_textcolor;
×
4288
}
4289

4290
void Character::last_textcolor( u16 new_color )
3✔
4291
{
4292
  _last_textcolor = new_color;
3✔
4293
}
3✔
4294

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

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

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

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

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

4330
  if ( b == buffs_.end() )
4✔
4331
    return false;
3✔
4332

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

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

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

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

4369
    send_buff_message( this, icon, true, duration, buf.cl_name, buf.name_arguments, buf.cl_descr,
×
4370
                       buf.desc_arguments );
×
4371
  }
4372
}
4373

4374
u8 Character::los_size() const
62,994✔
4375
{
4376
  return client ? client->update_range() : Core::settingsManager.ssopt.default_visual_range;
62,994✔
4377
}
4378

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

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

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

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

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

4468
void Character::update_objects_on_range_change( u8 newrange )
2✔
4469
{
4470
  Network::RemoveObjectPkt msgremove( serial_ext );
2✔
4471

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

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

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

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

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

4656
}  // namespace Mobile
4657
}  // 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