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

polserver / polserver / 21066490217

16 Jan 2026 12:22PM UTC coverage: 60.507%. Remained the same
21066490217

push

github

web-flow
misc clang-tidy (#853)

* trigger tidy

* Automated clang-tidy change: modernize-use-equals-delete,modernize-make-shared,modernize-make-unique,modernize-use-constraints,readability-container-size-empty,modernize-redundant-void-arg,modernize-use-emplace

* removed non needed macros

* missed to disable tidy build

---------

Co-authored-by: Clang Tidy <clang-tidy@users.noreply.github.com>

174 of 248 new or added lines in 74 files covered. (70.16%)

2 existing lines in 2 files now uncovered.

44459 of 73477 relevant lines covered (60.51%)

515895.79 hits per line

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

62.82
/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 "pol_global_config.h"
83

84
#include "charactr.h"
85

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

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

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

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

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

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

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

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

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

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

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

262

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

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

343
  set_caps_to_default();
97✔
344

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

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

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

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

374
  removal_cleanup();
97✔
375

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

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

383
  if ( repsys_task_ != nullptr )
97✔
384
    repsys_task_->cancel();
2✔
385

386
  if ( party_decline_timeout_ != nullptr )
97✔
387
    party_decline_timeout_->cancel();
×
388

389
  --Core::stateManager.uobjcount.ucharacter_count;
97✔
390
}
118✔
391

392
void Character::removal_cleanup()
99✔
393
{
394
  clear_opponent_of();
99✔
395

396
  /* This used to be a call to set_opponent(nullptr),
397
     which was slick,
398
     but that was sending disengage events, which were
399
     trying to resurrect this object. (C++)
400
     */
401
  if ( opponent_ != nullptr )
99✔
402
  {
403
    opponent_->opponent_of.erase( this );
×
404
    //    This is cleanup, wtf we doing trying to send highlights?!
405
    //    opponent_->send_highlight();
406
    //    opponent_->schedule_attack();
407
    opponent_ = nullptr;
×
408
  }
409

410
  if ( swing_task != nullptr )
99✔
411
    swing_task->cancel();
×
412

413
  disconnect_cleanup();
99✔
414
}
99✔
415

416
void Character::disconnect_cleanup()
101✔
417
{
418
  if ( is_trading() )
101✔
419
    Core::cancel_trade( this );
×
420

421
  stop_skill_script();
101✔
422
  on_loggoff_party( this );
101✔
423
}
101✔
424

425
bool Character::logged_in() const
1,262✔
426
{
427
  return mob_flags_.get( MOB_FLAGS::LOGGED_IN );
1,262✔
428
}
429

430
void Character::logged_in( bool newvalue )
198✔
431
{
432
  mob_flags_.change( MOB_FLAGS::LOGGED_IN, newvalue );
198✔
433
}
198✔
434

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

440
void Character::connected( bool newvalue )
80✔
441
{
442
  mob_flags_.change( MOB_FLAGS::CONNECTED, newvalue );
80✔
443
}
80✔
444

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

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

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

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

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

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

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

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

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

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

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

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

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

567

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

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

578
  base::printProperties( sw );
22✔
579

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

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

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

597
  if ( dead() )
22✔
598
    sw.add( "Dead", dead() );
2✔
599

600
  if ( mountedsteps_ )
22✔
601
    sw.add( "MountedSteps", mountedsteps_ );
2✔
602

603
  if ( hidden() )
22✔
604
    sw.add( "Hidden", hidden() );
2✔
605

606
  if ( frozen() )
22✔
607
    sw.add( "Frozen", frozen() );
2✔
608

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

624

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

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

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

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

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

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

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

686

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

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

699
  sw.add( "CreatedAt", created_at );
22✔
700

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

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

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

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

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

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

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

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

753
Plib::MOVEMODE Character::decode_movemode( const std::string& str )
167✔
754
{
755
  Plib::MOVEMODE mm = Plib::MOVEMODE_NONE;
167✔
756

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

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

783
void Character::readCommonProperties( Clib::ConfigElem& elem )
85✔
784
{
785
  serial = elem.remove_ulong( "SERIAL" );
85✔
786

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

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

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

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

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

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

830
  registered_multi = elem.remove_ulong( "REGISTEREDMULTI", 0 );
85✔
831
  if ( registered_multi == 0 )
85✔
832
    registered_multi = elem.remove_ulong( "REGISTEREDHOUSE", 0 );
84✔
833

834
  base::readProperties( elem );
85✔
835

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

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

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

857
  truecolor = elem.remove_ushort( "TRUECOLOR" );
85✔
858

859
  mountedsteps_ = elem.remove_ulong( "MOUNTEDSTEPS", 0 );
85✔
860

861
  gender = static_cast<Plib::UGENDER>( elem.remove_ushort( "GENDER" ) );
85✔
862
  race = static_cast<Plib::URACE>( elem.remove_ushort( "RACE", Plib::RACE_HUMAN ) );
85✔
863

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

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

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

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

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

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

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

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

899
  std::string rt;
85✔
900
  while ( elem.remove_prop( "REPORTABLE", &rt ) )
86✔
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" );
85✔
911
  skillstatcap( Core::SkillStatCap(
85✔
912
      static_cast<s16>( elem.remove_int( "STATCAP", Core::SkillStatCap::DEFAULT.statcap ) ),
85✔
913
      static_cast<u16>( elem.remove_int( "SKILLCAP", Core::SkillStatCap::DEFAULT.skillcap ) ) ) );
85✔
914
  followers( Core::ExtStatBarFollowers(
85✔
915
      static_cast<s8>(
916
          elem.remove_int( "FOLLOWERS", Core::ExtStatBarFollowers::DEFAULT.followers ) ),
85✔
917
      static_cast<s8>(
918
          elem.remove_int( "FOLLOWERSMAX", Core::ExtStatBarFollowers::DEFAULT.followers_max ) ) ) );
85✔
919
  tithing( elem.remove_int( "TITHING", 0 ) );
85✔
920

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

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

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

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

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

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

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

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

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

993

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

998
        av.lock( lock );
10✔
999

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

1005
  calc_vital_stuff();
11✔
1006

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

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

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

1037

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

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

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

1055
  cached_settings.reset();
188✔
1056

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

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

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

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

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

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

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

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

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

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

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

1163
  return false;
×
1164
}
1165

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

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

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

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

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

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

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

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

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

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

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

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

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

1244
  Core::UContainer* cont = backpack();
×
1245
  if ( cont != nullptr )
×
1246
  {
1247
    for ( Core::UContainer::const_iterator itr = cont->begin(); itr != cont->end(); ++itr )
×
1248
    {
1249
      const Items::Item* item = *itr;
×
1250

1251
      if ( item != nullptr && item->script_isa( Core::POLCLASS_SPELLBOOK ) )
×
1252
      {
1253
        const Core::Spellbook* book = static_cast<const Core::Spellbook*>( item );
×
1254
        if ( book->spell_school == school )
×
1255
          return const_cast<Core::Spellbook*>( book );
×
1256
      }
1257
    }
1258
  }
1259
  return nullptr;
×
1260
}
1261

1262
unsigned int Character::gold_carried() const
90✔
1263
{
1264
  Core::UContainer* bp = backpack();
90✔
1265
  if ( bp != nullptr )
90✔
1266
    return bp->find_sumof_objtype_noninuse( UOBJ_GOLD_COIN );
90✔
1267
  else
1268
    return 0;
×
1269
}
1270

1271
// TODO: This could be more efficient, by inlining 'spend' logic
1272
// in a recursive function
1273

1274
void Character::spend_gold( unsigned int amount )
×
1275
{
1276
  passert( gold_carried() >= amount );
×
1277

1278
  Core::UContainer* bp = backpack();
×
1279
  if ( bp != nullptr )
×
1280
    bp->consume_sumof_objtype_noninuse( UOBJ_GOLD_COIN, amount );
×
1281
  if ( client != nullptr )
×
1282
    send_full_statmsg( client, this );
×
1283
}
×
1284

1285
Items::Item* Character::wornitem( int layer ) const
6,091✔
1286
{
1287
  return wornitems->GetItemOnLayer( layer );
6,091✔
1288
}
1289

1290
bool Character::layer_is_equipped( int layer ) const
109✔
1291
{
1292
  return ( wornitems->GetItemOnLayer( layer ) != nullptr );
109✔
1293
}
1294

1295
bool Character::is_equipped( const Items::Item* item ) const
86✔
1296
{
1297
  if ( !Items::valid_equip_layer( item ) )
86✔
1298
    return false;
2✔
1299

1300
  return ( wornitems->GetItemOnLayer( item->tile_layer ) == item );
84✔
1301
}
1302

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

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

1323
    if ( item->objtype_ != Core::settingsManager.extobj.boatmount )
1✔
1324
    {
1325
      return false;
1✔
1326
    }
1327
  }
1328
  if ( layer_is_equipped( item->tile_layer ) )
62✔
1329
  {
1330
    return false;
×
1331
  }
1332
  if ( ( item->tile_layer == Core::LAYER_BACKPACK ) &&
102✔
1333
       !item->isa( Core::UOBJ_CLASS::CLASS_CONTAINER ) )
40✔
1334
  {
1335
    return false;
×
1336
  }
1337

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

1350
  if ( item->objtype_ != Core::settingsManager.extobj.boatmount )
62✔
1351
  {
1352
    if ( ~Plib::tile_flags( item->graphic ) & Plib::FLAG::EQUIPPABLE )
56✔
1353
    {
1354
      return false;
×
1355
    }
1356
    // redundant sanity check
1357
    if ( Plib::tilelayer( item->graphic ) != item->tile_layer )
56✔
1358
    {
1359
      return false;
×
1360
    }
1361
  }
1362

1363
  const Items::ItemDesc& desc = item->itemdesc();
62✔
1364
  if ( attribute( Core::gamestate.pAttrStrength->attrid ).base() < desc.base_str_req )
62✔
1365
  {
1366
    return false;
×
1367
  }
1368

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

1391
  return true;
62✔
1392
}
1393

1394
void Character::equip( Items::Item* item )
31✔
1395
{
1396
  passert_r( equippable( item ),
31✔
1397
             "It is impossible to equip Item with ObjType " + Clib::hexint( item->objtype_ ) );
1398

1399
  item->setposition( pos() );  // TODO POS realm should be nullptr
31✔
1400
  wornitems->PutItemOnLayer( item );
31✔
1401

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

1421
Items::UWeapon* Character::intrinsic_weapon()
×
1422
{
1423
  return Core::gamestate.wrestling_weapon;
×
1424
}
1425

1426
void Character::unequip( Items::Item* item )
24✔
1427
{
1428
  passert( Items::valid_equip_layer( item ) );
24✔
1429
  // assume any item being de-equipped is in fact being worn.
1430
  passert( item->container == wornitems.get() );
24✔
1431
  passert( is_equipped( item ) );
24✔
1432

1433
  wornitems->RemoveItemFromLayer( item );
24✔
1434

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

1452
bool Character::on_mount() const
46✔
1453
{
1454
  if ( race == Plib::RACE_GARGOYLE )
46✔
1455
    return ( movemode & Plib::MOVEMODE_FLY ) == 0 ? false : true;
×
1456

1457
  return layer_is_equipped( Core::LAYER_MOUNT );
46✔
1458
}
1459

1460

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

1491
void Character::produce( const Core::Vital* pVital, VitalValue& vv, unsigned int amt )
120✔
1492
{
1493
  int start_ones = vv.current_ones();
120✔
1494
  set_dirty();
120✔
1495
  vv.produce( amt );
120✔
1496
  if ( start_ones != vv.current_ones() )
120✔
1497
    Network::ClientInterface::tell_vital_changed( this, pVital );
1✔
1498
}
120✔
1499

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

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

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

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

1550
void Character::calc_vital_stuff( bool i_mod, bool v_mod )
87✔
1551
{
1552
  if ( i_mod )
87✔
1553
  {
1554
    for ( unsigned ai = 0; ai < Core::gamestate.numAttributes; ++ai )
4,611✔
1555
    {
1556
      calc_single_attribute( Core::gamestate.attributes[ai] );
4,524✔
1557
    }
1558
  }
1559

1560
  if ( v_mod )
87✔
1561
  {
1562
    for ( unsigned vi = 0; vi < Core::gamestate.numVitals; ++vi )
348✔
1563
    {
1564
      calc_single_vital( Core::gamestate.vitals[vi] );
261✔
1565
    }
1566
  }
1567
}
87✔
1568

1569
void Character::calc_single_vital( const Core::Vital* pVital )
261✔
1570
{
1571
  VitalValue& vv = vital( pVital->vitalid );
261✔
1572

1573
  int start_ones = vv.current_ones();
261✔
1574
  int start_max = vv.maximum_ones();
261✔
1575

1576
  // dave change the order of maximum and regen function 3/19/3
1577
  int mv = pVital->get_maximum_func->call_long( new Module::ECharacterRefObjImp( this ) );
261✔
1578

1579
  if ( mv < static_cast<int>( Core::VITAL_LOWEST_MAX_HUNDREDTHS ) )
261✔
1580
    mv = Core::VITAL_LOWEST_MAX_HUNDREDTHS;
25✔
1581
  if ( mv > static_cast<int>( Core::VITAL_HIGHEST_MAX_HUNDREDTHS ) )
261✔
1582
    mv = Core::VITAL_HIGHEST_MAX_HUNDREDTHS;
×
1583

1584
  vv.maximum( mv );
261✔
1585

1586
  int rr = pVital->get_regenrate_func->call_long( new Module::ECharacterRefObjImp( this ) );
261✔
1587

1588
  if ( rr < Core::VITAL_LOWEST_REGENRATE )
261✔
1589
    rr = Core::VITAL_LOWEST_REGENRATE;
×
1590
  if ( rr > Core::VITAL_HIGHEST_REGENRATE )
261✔
1591
    rr = Core::VITAL_HIGHEST_REGENRATE;
×
1592

1593
  vv.regenrate( rr );
261✔
1594

1595
  if ( ( start_ones != vv.current_ones() ) || ( start_max != vv.maximum_ones() ) )
261✔
1596
    Network::ClientInterface::tell_vital_changed( this, pVital );
261✔
1597
}
261✔
1598

1599
void Character::calc_single_attribute( const Attribute* pAttr )
4,524✔
1600
{
1601
  AttributeValue& av = attribute( pAttr->attrid );
4,524✔
1602

1603
  if ( pAttr->getintrinsicmod_func )
4,524✔
1604
  {
1605
    int im = pAttr->getintrinsicmod_func->call_long( new Module::ECharacterRefObjImp( this ) );
×
1606

1607
    if ( im < ATTRIBUTE_MIN_INTRINSIC_MOD )
×
1608
      im = ATTRIBUTE_MIN_INTRINSIC_MOD;
×
1609
    if ( im > ATTRIBUTE_MAX_INTRINSIC_MOD )
×
1610
      im = ATTRIBUTE_MAX_INTRINSIC_MOD;
×
1611

1612
    av.intrinsic_mod( static_cast<short>( im ) );
×
1613
  }
1614
}
4,524✔
1615

1616
void Character::set_vitals_to_maximum()  // throw()
74✔
1617
{
1618
  set_dirty();
74✔
1619
  for ( unsigned vi = 0; vi < Core::gamestate.numVitals; ++vi )
296✔
1620
  {
1621
    VitalValue& vv = vital( vi );
222✔
1622
    vv.current( vv.maximum() );
222✔
1623

1624
    Network::ClientInterface::tell_vital_changed( this, Core::gamestate.vitals[vi] );
222✔
1625
  }
1626
}
74✔
1627

1628
bool Character::setgraphic( u16 newgraphic )
×
1629
{
1630
  if ( newgraphic < 1 || newgraphic > Plib::systemstate.config.max_anim_id )
×
1631
    return false;
×
1632

1633
  set_dirty();
×
1634
  graphic = newgraphic;
×
1635
  send_remove_character_to_nearby_cansee( this );
×
1636
  if ( client )
×
1637
    send_goxyz( client, client->chr );
×
1638
  send_create_mobile_to_nearby_cansee( this );
×
1639

1640
  return true;
×
1641
}
1642

1643
void Character::on_color_changed()
×
1644
{
1645
  send_remove_character_to_nearby_cansee( this );
×
1646
  if ( client )
×
1647
    send_goxyz( client, client->chr );
×
1648
  send_create_mobile_to_nearby_cansee( this );
×
1649
}
×
1650

1651
void Character::on_poison_changed()
×
1652
{
1653
  send_move_mobile_to_nearby_cansee( this, true );
×
1654
}
×
1655

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

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

1687
void Character::on_cmdlevel_changed()
1✔
1688
{
1689
  send_remove_character_to_nearby_cantsee( this );
1✔
1690
  send_create_mobile_to_nearby_cansee( this );
1✔
1691
}
1✔
1692

1693
void Character::on_facing_changed()
5✔
1694
{
1695
  if ( client )
5✔
1696
    send_goxyz( client, client->chr );
5✔
1697
  send_move_mobile_to_nearby_cansee( this );
5✔
1698
}
5✔
1699

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

1711
void Character::setfacing( u8 newfacing )
97✔
1712
{
1713
  facing = newfacing & 7;
97✔
1714
}
97✔
1715

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

1736
  return flag1;
203✔
1737
}
1738

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

1749
  if ( ( source ) && ( userepsys ) )
×
1750
    source->repsys_on_attack( this );
×
1751

1752
  if ( ( amount == 0 ) || cached_settings.get( PRIV_FLAGS::INVUL ) )
×
1753
    return;
×
1754

1755
  set_dirty();
×
1756
  if ( hidden() )
×
1757
    unhide();
×
1758

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

1769
  if ( paralyzed() )
×
1770
    mob_flags_.remove( MOB_FLAGS::PARALYZED );
×
1771

1772
  disable_regeneration_for( 2 );  // FIXME depend on amount?
×
1773

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

1780
  if ( ( source ) && ( userepsys ) )
×
1781
    source->repsys_on_damage( this );
×
1782

1783
  send_update_hits_to_inrange( this );
×
1784

1785
  if ( vv.current() == 0 )
×
1786
    die();
×
1787
}
1788

1789
// keep this in sync with NPC::armor_absorb_damage
1790

1791

1792
unsigned short calc_thru_damage( double damage, unsigned short ar )
×
1793
{
1794
  int blocked = ar;
×
1795
  if ( blocked < 0 )
×
1796
    blocked = 0;
×
1797

1798
  int absorbed = blocked / 2;
×
1799

1800
  blocked -= absorbed;
×
1801
  absorbed += Clib::random_int( blocked );
×
1802
  damage -= absorbed;
×
1803

1804
  if ( damage >= 2.0 )
×
1805
  {
1806
    return static_cast<unsigned short>( damage * 0.5 );
×
1807
  }
1808
  else
1809
  {
1810
    int dmg = static_cast<int>( damage );
×
1811
    if ( dmg >= 0 )
×
1812
      return static_cast<unsigned short>( dmg );
×
1813
    else
1814
      return 0;
×
1815
  }
1816
}
1817

1818

1819
double Character::armor_absorb_damage( double damage )
×
1820
{
1821
  Items::UArmor* armor = choose_armor();
×
1822
  if ( armor != nullptr )
×
1823
  {
1824
    damage = calc_thru_damage( damage, armor->ar() + ar_mod() );
×
1825

1826
    armor->reduce_hp_from_hit();
×
1827
  }
1828
  return damage;
×
1829
}
1830

1831
double Character::apply_damage( double damage, Character* source, bool userepsys,
×
1832
                                bool send_damage_packet )
1833
{
1834
  damage = armor_absorb_damage( damage );
×
1835
  if ( Core::settingsManager.watch.combat )
×
1836
    INFO_PRINTLN( "Final damage: {}", damage );
×
1837
  do_imhit_effects();
×
1838
  apply_raw_damage_hundredths( static_cast<unsigned int>( damage * 100 ), source, userepsys,
×
1839
                               send_damage_packet );
1840

1841
  return damage;
×
1842
}
1843

1844
void Character::get_hitscript_params( double damage, Items::UArmor** parmor,
8✔
1845
                                      unsigned short* rawdamage )
1846
{
1847
  Items::UArmor* armor = choose_armor();
8✔
1848
  if ( armor )
8✔
1849
  {
1850
    *rawdamage = calc_thru_damage( damage, armor->ar() + ar_mod() );
×
1851
  }
1852
  else
1853
  {
1854
    *rawdamage = static_cast<unsigned short>( damage );
8✔
1855
  }
1856
  *parmor = armor;
8✔
1857
}
8✔
1858

1859
void Character::run_hit_script( Character* defender, double damage )
8✔
1860
{
1861
  ref_ptr<Bscript::EScriptProgram> prog = find_script2(
1862
      weapon->hit_script(), true, Plib::systemstate.config.cache_interactive_scripts );
8✔
1863
  if ( prog.get() == nullptr )
8✔
1864
    return;
×
1865

1866
  std::unique_ptr<Core::UOExecutor> ex( Core::create_script_executor() );
8✔
1867
  auto uoemod = new Module::UOExecutorModule( *ex );
8✔
1868
  ex->addModule( uoemod );
8✔
1869

1870
  unsigned short rawdamage = 0;
8✔
1871
  unsigned short basedamage = static_cast<unsigned short>( damage );
8✔
1872

1873
  Items::UArmor* armor = nullptr;
8✔
1874

1875
  defender->get_hitscript_params( damage, &armor, &rawdamage );
8✔
1876

1877

1878
  ex->pushArg( new Bscript::BLong( rawdamage ) );
8✔
1879
  ex->pushArg( new Bscript::BLong( basedamage ) );
8✔
1880
  if ( armor )
8✔
1881
    ex->pushArg( new Module::EItemRefObjImp( armor ) );
×
1882
  else
1883
    ex->pushArg( new Bscript::BLong( 0 ) );
8✔
1884
  ex->pushArg( new Module::EItemRefObjImp( weapon ) );
8✔
1885
  ex->pushArg( new Module::ECharacterRefObjImp( defender ) );
8✔
1886
  ex->pushArg( new Module::ECharacterRefObjImp( this ) );
8✔
1887

1888
  ex->priority( 100 );
8✔
1889

1890
  if ( ex->setProgram( prog.get() ) )
8✔
1891
  {
1892
    uoemod->controller_.set( this );
8✔
1893
    schedule_executor( ex.release() );
8✔
1894
  }
1895
  else
1896
  {
1897
    POLLOGLN( "Blech, couldn't start hitscript {}", weapon->hit_script().name() );
×
1898
  }
1899
}
8✔
1900

1901
///
1902
/// Clear a Mobile's ToBeReportable list when all of the following are true:
1903
///   1) hits are at maximum
1904
///   2) mobile is not poisoned
1905
///   3) mobile is not paralyzed
1906
///
1907
void Character::check_undamaged()
40✔
1908
{
1909
  if ( vital( Core::gamestate.pVitalLife->vitalid ).is_at_maximum() && !poisoned() && !paralyzed() )
40✔
1910
  {
1911
    clear_to_be_reportables();
39✔
1912
  }
1913
}
40✔
1914

1915

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

1932
  if ( amount == 0 )
×
1933
    return;
×
1934

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

1937
  check_undamaged();
×
1938

1939
  send_update_hits_to_inrange( this );
×
1940
}
1941

1942
Items::Item* create_death_shroud()
1✔
1943
{
1944
  Items::Item* item = Items::Item::create( UOBJ_DEATH_SHROUD );
1✔
1945
  item->layer = Core::LAYER_ROBE_DRESS;
1✔
1946
  return item;
1✔
1947
}
1948
Items::Item* create_death_robe()
×
1949
{
1950
  Items::Item* item = Items::Item::create( UOBJ_DEATH_ROBE );
×
1951
  item->layer = Core::LAYER_ROBE_DRESS;
×
1952
  return item;
×
1953
}
1954
Items::Item* create_backpack()
×
1955
{
1956
  Items::Item* item = Items::Item::create( UOBJ_BACKPACK );
×
1957
  item->layer = Core::LAYER_BACKPACK;
×
1958
  return item;
×
1959
}
1960

1961

1962
void Character::send_warmode()
4✔
1963
{
1964
  Network::PktHelper::PacketOut<Network::PktOut_72> msg;
4✔
1965
  msg->Write<u8>( warmode() ? 1u : 0u );
4✔
1966
  msg->offset++;  // u8 unk2
4✔
1967
  msg->Write<u8>( 0x32u );
4✔
1968
  msg->offset++;  // u8 unk4
4✔
1969
  msg.Send( client );
4✔
1970
}
4✔
1971

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

1993
  mob_flags_.remove( MOB_FLAGS::DEAD );
×
1994
  mob_flags_.remove( MOB_FLAGS::WARMODE );
×
1995
  mob_flags_.remove( MOB_FLAGS::FROZEN );
×
1996
  mob_flags_.remove( MOB_FLAGS::PARALYZED );
×
1997

1998
  color = truecolor;
×
1999

2000
  if ( Core::gamestate.pVitalLife->regen_while_dead )
×
2001
  {
2002
    VitalValue& vv = vital( Core::gamestate.pVitalLife->vitalid );
×
2003
    if ( vv._current == 0 )  // set in die()
×
2004
      set_current_ones( Core::gamestate.pVitalLife, vv, 1, VitalDepletedReason::RESURRECT );
×
2005
  }
2006
  else
2007
    set_current_ones( Core::gamestate.pVitalLife, vital( Core::gamestate.pVitalLife->vitalid ), 1,
×
2008
                      VitalDepletedReason::RESURRECT );
2009

2010
  if ( !Core::gamestate.pVitalMana->regen_while_dead )
×
2011
    set_current_ones( Core::gamestate.pVitalMana, vital( Core::gamestate.pVitalMana->vitalid ), 0,
×
2012
                      VitalDepletedReason::RESURRECT );
2013

2014
  if ( !Core::gamestate.pVitalStamina->regen_while_dead )
×
2015
    set_current_ones( Core::gamestate.pVitalStamina,
×
2016
                      vital( Core::gamestate.pVitalStamina->vitalid ), 1,
×
2017
                      VitalDepletedReason::RESURRECT );
2018

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

2042
  // equip( create_backpack() );
2043

2044
  // if this has a connected client, tell him his new position.
2045
  if ( client )
×
2046
  {
2047
    client->pause();
×
2048
    send_warmode();
×
2049
    send_goxyz( client, this );
×
2050
    send_owncreate( client, this );
×
2051
    Core::WorldIterator<Core::MobileFilter>::InRange( pos(), los_size(),
×
2052
                                                      [&]( Character* zonechr )
×
2053
                                                      {
2054
                                                        if ( !is_visible_to_me( zonechr ) )
×
2055
                                                          send_remove_character( client, zonechr );
×
2056
                                                      } );
×
2057
    client->restart();
×
2058
  }
2059

2060
  // Tell other connected players, if in range, about this character.
2061
  send_remove_character_to_nearby_cansee( this );
×
2062
  send_create_mobile_to_nearby_cansee( this );
×
2063
  realm()->notify_resurrected( *this );
×
2064
}
2065

2066
void Character::on_death( Items::Item* corpse )
1✔
2067
{
2068
  Items::Item* death_shroud = create_death_shroud();
1✔
2069
  if ( equippable( death_shroud ) )  // check it or passert will trigger
1✔
2070
  {
2071
    equip( death_shroud );
×
2072
    send_wornitem_to_inrange( this, death_shroud );
×
2073
  }
2074
  else
2075
  {
2076
    ERROR_PRINTLN( "Create Character: Failed to equip death shroud {:#x}", death_shroud->graphic );
1✔
2077
    death_shroud->destroy();
1✔
2078
  }
2079

2080
  if ( client != nullptr )
1✔
2081
  {
2082
    if ( opponent_ )
×
2083
      opponent_->inform_disengaged( this );
×
2084

2085
    client->pause();
×
2086
    send_warmode();
×
2087

2088
    // Sends the complete corpse to the client itself, so he knows where his
2089
    // items went.
2090
    send_full_corpse( client, corpse );
×
2091

2092
    send_goxyz( client, this );
×
2093
    Core::WorldIterator<Core::MobileFilter>::InRange(
×
2094
        pos(), los_size(),
×
2095
        [&]( Character* zonechr )
×
2096
        {
2097
          if ( zonechr->dead() && is_visible_to_me( zonechr ) )
×
2098
            send_owncreate( client, zonechr );
×
2099
        } );
×
2100

2101
    client->restart();
×
2102
  }
2103

2104
  // change self to ghost for ghosts, remove self for living
2105
  send_remove_character_to_nearby_cantsee( this );
1✔
2106

2107
  send_create_mobile_to_nearby_cansee( this );
1✔
2108

2109
  if ( Clib::FileExists( "scripts/misc/chrdeath.ecl" ) )
1✔
2110
    Core::start_script( "misc/chrdeath", new Module::EItemRefObjImp( corpse ),
×
2111
                        new Module::ECharacterRefObjImp( this ) );
×
2112
}
1✔
2113

2114
void Character::clear_opponent_of()
171✔
2115
{
2116
  while ( !opponent_of.empty() )
171✔
2117
  {
2118
    Character* chr = *opponent_of.begin();
×
2119
    // note that chr->set_opponent is going to remove
2120
    // its entry from our opponent_of collection,
2121
    // so eventually this loop will exit.
2122
    chr->set_opponent( nullptr, false );
×
2123
  }
2124
}
171✔
2125

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

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

2140

2141
  u16 save_graphic = graphic;
72✔
2142

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

2163
  mob_flags_.set( MOB_FLAGS::DEAD );
72✔
2164
  mob_flags_.remove( MOB_FLAGS::WARMODE );
72✔
2165
  mob_flags_.remove( MOB_FLAGS::FROZEN );
72✔
2166
  mob_flags_.remove( MOB_FLAGS::PARALYZED );
72✔
2167

2168
  UPDATE_CHECKPOINT();
72✔
2169
  /* FIXME: corpse container difficulties.
2170

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

2183
     The current solution is to put the backpack
2184
     on the corpse.
2185
     */
2186

2187
  Core::UCorpse* corpse = static_cast<Core::UCorpse*>( Items::Item::create( UOBJ_CORPSE ) );
72✔
2188
  this->last_corpse = corpse->serial;
72✔
2189

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

2194
  UPDATE_CHECKPOINT();
72✔
2195

2196
  corpse->color = truecolor;
72✔
2197
  corpse->setposition( pos() );
72✔
2198
  corpse->facing = facing;
72✔
2199
  corpse->corpsetype = save_graphic;
72✔
2200
  // corpse->dir = dir;
2201
  UPDATE_CHECKPOINT();
72✔
2202

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

2210
  // Change the character's color to grey
2211
  color = 0;
72✔
2212
  UPDATE_CHECKPOINT();
72✔
2213

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

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

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

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

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

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

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

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

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

2370
    UPDATE_CHECKPOINT();
2✔
2371

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

2418

2419
  UPDATE_CHECKPOINT();
72✔
2420
  send_death_message( this, corpse );
72✔
2421

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

2435
  UPDATE_CHECKPOINT();
72✔
2436

2437
  clear_opponent_of();
72✔
2438

2439
  set_opponent( nullptr );
72✔
2440

2441
  UPDATE_CHECKPOINT();
72✔
2442

2443
  on_death( corpse );
72✔
2444

2445
  CLEAR_CHECKPOINT();
72✔
2446
}
2447

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

2460
  for ( unsigned layer = Core::LAYER_EQUIP__LOWEST; layer <= Core::LAYER_EQUIP__HIGHEST; ++layer )
4,212✔
2461
  {
2462
    Items::Item* item = wornitems->GetItemOnLayer( layer );
4,050✔
2463
    if ( item == nullptr )
4,050✔
2464
      continue;
3,896✔
2465
    // Let's check all items as base, and handle their element_resists.
2466
    updateEquipableProperties( item );
154✔
2467
    if ( item->isa( Core::UOBJ_CLASS::CLASS_ARMOR ) )
154✔
2468
    {
2469
      Items::UArmor* armor = static_cast<Items::UArmor*>( item );
×
2470
      std::set<unsigned short> tmplzones = armor->tmplzones();
×
2471
      std::set<unsigned short>::iterator itr;
×
2472
      for ( itr = tmplzones.begin(); itr != tmplzones.end(); ++itr )
×
2473
      {
2474
        if ( ( armor_[*itr] == nullptr ) || ( armor->ar() > armor_[*itr]->ar() ) )
×
2475
          armor_[*itr] = armor;
×
2476
      }
2477
    }
×
2478
  }
2479

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

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

2504
  new_ar += ar_mod();
162✔
2505

2506
  short s_new_ar = static_cast<short>( new_ar );
162✔
2507
  if ( s_new_ar >= 0 )
162✔
2508
    ar_ = s_new_ar;
162✔
2509
  else
2510
    ar_ = 0;
×
2511

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

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

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

2558
  // calc others
2559
  if ( item->has_lower_reagent_cost() )
154✔
2560
    lower_reagent_cost( lower_reagent_cost().addToValue( item->lower_reagent_cost() ) );
55✔
2561
  if ( item->has_spell_damage_increase() )
154✔
2562
    spell_damage_increase( spell_damage_increase().addToValue( item->spell_damage_increase() ) );
53✔
2563
  if ( item->has_faster_casting() )
154✔
2564
    faster_casting( faster_casting().addToValue( item->faster_casting() ) );
58✔
2565
  if ( item->has_faster_cast_recovery() )
154✔
2566
    faster_cast_recovery( faster_cast_recovery().addToValue( item->faster_cast_recovery() ) );
59✔
2567
  if ( item->has_lower_mana_cost() )
154✔
2568
    lower_mana_cost( lower_mana_cost().addToValue( item->lower_mana_cost() ) );
56✔
2569
  if ( item->has_hit_chance() )
154✔
2570
    hit_chance( hit_chance().addToValue( item->hit_chance() ) );
57✔
2571
  if ( item->has_luck() )
154✔
2572
    luck( luck().addToValue( item->luck() ) );
54✔
2573
  if ( item->has_swing_speed_increase() )
154✔
2574
    swing_speed_increase( swing_speed_increase().addToValue( item->swing_speed_increase() ) );
52✔
2575
  if ( item->has_min_attack_range_increase() )
154✔
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() )
154✔
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() )
154✔
2584
    defence_increase( defence_increase().addToValue( item->defence_increase() ) );
34✔
2585

2586
  if ( client != nullptr )
154✔
2587
  {  // CHECKME consider sending less frequently
2588
    send_full_statmsg( client, this );
9✔
2589
  }
2590
}
154✔
2591

2592
void Character::resetEquipableProperties()
162✔
2593
{
2594
  // reset resists
2595
  if ( has_fire_resist() )
162✔
2596
    fire_resist( fire_resist().setAsValue( 0 ) );
11✔
2597
  if ( has_cold_resist() )
162✔
2598
    cold_resist( cold_resist().setAsValue( 0 ) );
13✔
2599
  if ( has_energy_resist() )
162✔
2600
    energy_resist( energy_resist().setAsValue( 0 ) );
12✔
2601
  if ( has_poison_resist() )
162✔
2602
    poison_resist( poison_resist().setAsValue( 0 ) );
9✔
2603
  if ( has_physical_resist() )
162✔
2604
    physical_resist( physical_resist().setAsValue( 0 ) );
10✔
2605

2606
  // reset caps
2607
  if ( has_fire_resist_cap() )
162✔
2608
    fire_resist_cap( fire_resist_cap().setAsValue( 0 ) );
25✔
2609
  if ( has_cold_resist_cap() )
162✔
2610
    cold_resist_cap( cold_resist_cap().setAsValue( 0 ) );
33✔
2611
  if ( has_energy_resist_cap() )
162✔
2612
    energy_resist_cap( energy_resist_cap().setAsValue( 0 ) );
29✔
2613
  if ( has_poison_resist_cap() )
162✔
2614
    poison_resist_cap( poison_resist_cap().setAsValue( 0 ) );
17✔
2615
  if ( has_physical_resist_cap() )
162✔
2616
    physical_resist_cap( physical_resist_cap().setAsValue( 0 ) );
21✔
2617

2618
  // reset damages
2619
  if ( has_fire_damage() )
162✔
2620
    fire_damage( fire_damage().setAsValue( 0 ) );
93✔
2621
  if ( has_cold_damage() )
162✔
2622
    cold_damage( cold_damage().setAsValue( 0 ) );
95✔
2623
  if ( has_energy_damage() )
162✔
2624
    energy_damage( energy_damage().setAsValue( 0 ) );
94✔
2625
  if ( has_poison_damage() )
162✔
2626
    poison_damage( poison_damage().setAsValue( 0 ) );
91✔
2627
  if ( has_physical_damage() )
162✔
2628
    physical_damage( physical_damage().setAsValue( 0 ) );
92✔
2629

2630
  // reset others
2631
  if ( has_lower_reagent_cost() )
162✔
2632
    lower_reagent_cost( lower_reagent_cost().setAsValue( 0 ) );
59✔
2633
  if ( has_spell_damage_increase() )
162✔
2634
    spell_damage_increase( spell_damage_increase().setAsValue( 0 ) );
57✔
2635
  if ( has_faster_casting() )
162✔
2636
    faster_casting( faster_casting().setAsValue( 0 ) );
62✔
2637
  if ( has_faster_cast_recovery() )
162✔
2638
    faster_cast_recovery( faster_cast_recovery().setAsValue( 0 ) );
63✔
2639

2640
  if ( has_defence_increase() )
162✔
2641
    defence_increase( defence_increase().setAsValue( 0 ) );
37✔
2642
  if ( has_defence_increase_cap() )
162✔
2643
    defence_increase_cap( defence_increase_cap().setAsValue( 0 ) );
41✔
2644
  if ( has_lower_mana_cost() )
162✔
2645
    lower_mana_cost( lower_mana_cost().setAsValue( 0 ) );
60✔
2646
  if ( has_hit_chance() )
162✔
2647
    hit_chance( hit_chance().setAsValue( 0 ) );
61✔
2648
  if ( has_luck() )
162✔
2649
    luck( luck().setAsValue( 0 ) );
58✔
2650
  if ( has_swing_speed_increase() )
162✔
2651
    swing_speed_increase( swing_speed_increase().setAsValue( 0 ) );
59✔
2652
  if ( has_min_attack_range_increase() )
162✔
2653
    min_attack_range_increase( min_attack_range_increase().setAsValue( 0 ) );
55✔
2654
  if ( has_max_attack_range_increase() )
162✔
2655
    max_attack_range_increase( max_attack_range_increase().setAsValue( 0 ) );
55✔
2656
}
162✔
2657

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

2675
/* check skill: test skill, advance, reset atrophy timers, blah blah..
2676
   obviously, needs work, and more parameters.
2677
   */
2678

2679
bool Character::check_skill( Core::USKILLID skillid, int difficulty, unsigned short pointvalue )
×
2680
{
2681
  INC_PROFILEVAR( skill_checks );
×
2682
  static bool in_here = false;
2683
  if ( !in_here && Core::gamestate.system_hooks.check_skill_hook )
×
2684
  {
2685
    in_here = true;
×
2686
    bool res = Core::gamestate.system_hooks.check_skill_hook->call(
×
2687
        new Module::ECharacterRefObjImp( this ), new Bscript::BLong( skillid ),
×
2688
        new Bscript::BLong( difficulty ), new Bscript::BLong( pointvalue ) );
×
2689
    in_here = false;
×
2690
    return res;
×
2691
  }
2692
  else
2693
  {
2694
    return false;
×
2695
  }
2696
}
2697

2698
void Character::check_concealment_level()
×
2699
{
2700
  if ( concealed() > cmdlevel() )
×
2701
    concealed( cmdlevel() );
×
2702
}
×
2703

2704
// you can only be concealed from
2705
// those of lower stature
2706
bool Character::is_concealed_from_me( const Character* chr ) const
186✔
2707
{
2708
  return ( chr->concealed() > cmdlevel() );
186✔
2709
}
2710

2711
bool Character::is_visible_to_me( const Character* chr, bool check_range ) const
315✔
2712
{
2713
  if ( chr == nullptr )
315✔
2714
    return false;
×
2715
  if ( chr == this )
315✔
2716
    return true;  // I can always see myself (?)
145✔
2717
  if ( is_concealed_from_me( chr ) )
170✔
2718
    return false;
×
2719
  if ( !chr->logged_in() )
170✔
2720
    return false;
×
2721
  if ( chr->hidden() && !cached_settings.get( PRIV_FLAGS::SEE_HIDDEN ) )
170✔
2722
    return false;  // noone can see anyone hidden.
×
2723
  if ( check_range )
170✔
2724
  {
2725
    if ( !in_visual_range( chr ) )
147✔
2726
      return false;
51✔
2727
  }
2728
  else if ( chr->realm() != this->realm() )
23✔
2729
    return false;
1✔
2730

2731
  if ( dead() )
118✔
2732
    return true;  // If I'm dead, I can see anything
×
2733
  if ( !chr->dead() || cached_settings.get( PRIV_FLAGS::SEE_GHOSTS ) )
118✔
2734
    return true;  // Anyone can see the living
118✔
2735
  if ( chr->warmode() )
×
2736
    return true;  // Anyone can see someone in warmode
×
2737
  return false;
×
2738
};
2739

2740
// NOTE: chr is at new position, lastpos have old position.
2741
void PropagateMove( /*Client *client,*/ Character* chr )
164✔
2742
{
2743
  using namespace Network;
2744
  if ( chr == nullptr )
164✔
2745
    return;
×
2746
  RemoveObjectPkt msgremove( chr->serial_ext );
164✔
2747
  HealthBarStatusUpdate msgpoison( chr->serial_ext, HealthBarStatusUpdate::Color::GREEN,
2748
                                   chr->poisoned() );
164✔
2749
  HealthBarStatusUpdate msginvul( chr->serial_ext, HealthBarStatusUpdate::Color::YELLOW,
2750
                                  chr->invul() );
164✔
2751
  PktHelper::PacketOut<PktOut_78> msgcreate;
164✔
2752
  MoveChrPkt msgmove( chr );
164✔
2753
  build_owncreate( chr, msgcreate.Get() );
164✔
2754

2755
  Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
164✔
2756
      chr,
2757
      [&]( Character* zonechr )
164✔
2758
      {
2759
        Client* client = zonechr->client;
145✔
2760
        if ( zonechr == chr )
145✔
2761
          return;
101✔
2762
        if ( !zonechr->is_visible_to_me( chr ) )
44✔
2763
          return;
30✔
2764
        /* The two characters exist, and are in range of each other.
2765
        Character 'chr''s lastpos coordinates are valid.
2766
        SO, if lastpos are out of range of client->chr, we
2767
        should send a 'create' type message.  If they are in range,
2768
        we should just send a move.
2769
        */
2770
        if ( chr->move_reason == Character::MULTIMOVE )
14✔
2771
        {
2772
          if ( client->ClientType & Network::CLIENTTYPE_7090 )
×
2773
          {
2774
            if ( chr->poisoned() )  // if poisoned send 0x17 for newer clients
×
2775
              msgpoison.Send( client );
×
2776

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

2816
  // iter over all old in range players and send remove
2817
  Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
164✔
2818
      chr->lastpos,
164✔
2819
      [&]( Character* zonechr )
164✔
2820
      {
2821
        Client* client = zonechr->client;
123✔
2822
        if ( !zonechr->in_visual_range( nullptr, chr->lastpos ) )
123✔
2823
          return;
32✔
2824
        if ( !zonechr->is_visible_to_me( chr, /*check_range*/ false ) )
91✔
2825
          return;
1✔
2826

2827
        if ( zonechr->in_visual_range( chr ) )  // already handled
90✔
2828
          return;
81✔
2829
        // if we just walked out of range of this character, send its
2830
        // client a remove object, or else a ghost character will remain.
2831
        send_remove_character( client, chr, msgremove );
9✔
2832
      } );
2833
}
164✔
2834

2835
void Character::swing_task_func( Character* chr )
11✔
2836
{
2837
  THREAD_CHECKPOINT( tasks, 800 );
11✔
2838
  INFO_PRINTLN_TRACE( 20 )( "swing_task_func({:#x})", chr->serial );
11✔
2839
  chr->mob_flags_.set( MOB_FLAGS::READY_TO_SWING );
11✔
2840
  chr->check_attack_after_move( false );
11✔
2841
  THREAD_CHECKPOINT( tasks, 899 );
11✔
2842
}
11✔
2843

2844
void Character::schedule_attack()
25✔
2845
{
2846
  INFO_PRINTLN_TRACE( 18 )( "schedule_attack({:#x})", this->serial );
25✔
2847
  // we'll get here with a swing_task already set, if
2848
  // while in an adjacent cell to your opponent, you turn/move
2849
  // while waiting for your timeout.
2850
  if ( swing_task == nullptr )
25✔
2851
  {
2852
    unsigned int weapon_speed = weapon->speed();
17✔
2853
    unsigned int weapon_delay = weapon->delay();
17✔
2854
    Core::polclock_t clocks;
2855

2856
    if ( !weapon_delay )
17✔
2857
    {
2858
      INFO_PRINTLN_TRACE( 19 )
17✔
2859
      ( "clocks[speed] = ({}*15000)/(({}+100)*{})", Core::POLCLOCKS_PER_SEC, dexterity(),
×
2860
        weapon_speed );
2861

2862
      clocks = Core::POLCLOCKS_PER_SEC * static_cast<Core::polclock_t>( 15000L );
17✔
2863
      clocks /= ( dexterity() + 100 ) * weapon_speed;
17✔
2864
    }
2865
    else
2866
    {
2867
      int delay_sum = weapon_delay + delay_mod();
×
2868
      if ( delay_sum < 0 )
×
2869
        delay_sum = 0;
×
2870

2871
      INFO_PRINTLN_TRACE( 19 )
×
2872
      ( "clocks[delay] = (({}+{}={})*{})/1000", weapon_delay, delay_mod(), delay_sum,
×
2873
        Core::POLCLOCKS_PER_SEC );
2874

2875
      clocks = ( delay_sum * Core::POLCLOCKS_PER_SEC ) / 1000;
×
2876
    }
2877
    // Swing speed modifier can't be less than -1 otherwise it would give a negitive swing speed and
2878
    // thats not possible.
2879
    double speed_modifier = ( swing_speed_increase().sum() / 100.0 );
17✔
2880
    if ( speed_modifier < -0.99 )
17✔
2881
      speed_modifier = -0.99;
×
2882
    clocks = static_cast<Core::polclock_t>( round( clocks / ( 1 + speed_modifier ) ) );
17✔
2883

2884
    if ( clocks < ( Core::POLCLOCKS_PER_SEC / 5 ) )
17✔
2885
    {
2886
      INFO_PRINTLN_TRACE( 20 )( "{} attack timer: {}", name(), clocks );
6✔
2887
    }
2888
    INFO_PRINTLN_TRACE( 19 )( "clocks={}", clocks );
17✔
2889

2890
    new Core::OneShotTaskInst<Character*>( &swing_task, swing_timer_start_clock_ + clocks + 1,
17✔
2891
                                           swing_task_func, this );
17✔
2892
  }
2893
}
25✔
2894

2895
void Character::reset_swing_timer()
102✔
2896
{
2897
  INFO_PRINTLN_TRACE( 15 )( "reset_swing_timer({:#x})", this->serial );
102✔
2898
  mob_flags_.remove( MOB_FLAGS::READY_TO_SWING );
102✔
2899

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

2904
  if ( opponent_ || !opponent_of.empty() )
102✔
2905
  {
2906
    schedule_attack();
13✔
2907
  }
2908
}
102✔
2909

2910
bool Character::manual_set_swing_timer( Core::polclock_t clocks )
×
2911
{
2912
  INFO_PRINTLN_TRACE( 15 )
×
2913
  ( "manual_set_swing_timer({:#x}) delay: {}", this->serial, clocks );
×
2914
  mob_flags_.remove( MOB_FLAGS::READY_TO_SWING );
×
2915

2916
  swing_timer_start_clock_ = Core::polclock();
×
2917
  if ( swing_task )
×
2918
    swing_task->cancel();
×
2919

2920
  if ( opponent_ || !opponent_of.empty() )
×
2921
  {
2922
    new Core::OneShotTaskInst<Character*>( &swing_task, swing_timer_start_clock_ + clocks + 1,
×
2923
                                           swing_task_func, this );
×
2924
    return true;
×
2925
  }
2926
  else
2927
    return false;
×
2928
}
2929

2930
/* The highlighted character is:
2931
     Your selected_opponent, if you have one.
2932
     If not, then the first char that has you as their opponent.
2933
     Or, noone.
2934
     */
2935
Character* Character::get_opponent() const
10✔
2936
{
2937
  if ( opponent_ != nullptr )
10✔
2938
    return opponent_;
×
2939
  else if ( !opponent_of.empty() )
10✔
2940
    return *opponent_of.begin();
×
2941
  else
2942
    return nullptr;
10✔
2943
}
2944

2945
bool Character::is_attackable( Character* who ) const
23✔
2946
{
2947
  passert( who != nullptr );
23✔
2948
  if ( Core::settingsManager.combat_config.scripted_attack_checks )
23✔
2949
  {
2950
    INFO_PRINTLN_TRACE( 21 )
×
2951
    ( "is_attackable({:#x},{:#x}): will be handled by combat hook.", this->serial, who->serial );
×
2952
    return true;
×
2953
  }
2954
  else
2955
  {
2956
    INFO_PRINTLN_TRACE( 21 )
23✔
2957
    ( "is_attackable({:#x},{:#x}):\n"
×
2958
      "  who->dead:  {}\n"
2959
      "  wpn->inrange: {}\n"
2960
      "  hidden:     {}\n"
2961
      "  who->hidden:  {}\n"
2962
      "  concealed:  {}",
2963
      this->serial, who->serial, who->dead(), weapon->in_range( this, who ), hidden(),
×
2964
      who->hidden(), is_concealed_from_me( who ) );
×
2965
    if ( who->dead() )
23✔
2966
      return false;
×
2967
    else if ( !weapon->in_range( this, who ) )
23✔
2968
      return false;
3✔
2969
    else if ( hidden() && !cached_settings.get( PRIV_FLAGS::HIDDEN_ATTACK ) )
20✔
2970
      return false;
2✔
2971
    else if ( who->hidden() && !cached_settings.get( PRIV_FLAGS::ATTACK_HIDDEN ) )
18✔
2972
      return false;
2✔
2973
    else if ( is_concealed_from_me( who ) )
16✔
2974
      return false;
×
2975
    else if ( !realm()->has_los( *this, *who ) )
16✔
2976
      return false;
×
2977
    else
2978
      return true;
16✔
2979
  }
2980
}
2981

2982
Character* Character::get_attackable_opponent() const
184✔
2983
{
2984
  if ( opponent_ != nullptr )
184✔
2985
  {
2986
    INFO_PRINTLN_TRACE( 20 )
12✔
2987
    ( "get_attackable_opponent({:#x}): checking opponent {:#x}", this->serial, opponent_->serial );
×
2988
    if ( is_attackable( opponent_ ) )
12✔
2989
      return opponent_;
9✔
2990
  }
2991

2992
  if ( !opponent_of.empty() )
175✔
2993
  {
2994
    for ( auto& who : opponent_of )
15✔
2995
    {
2996
      INFO_PRINTLN_TRACE( 20 )
11✔
2997
      ( "get_attackable_opponent({:#x}): checking opponent_of {:#x}", this->serial, who->serial );
×
2998
      if ( is_attackable( who ) )
11✔
2999
        return who;
7✔
3000
    }
3001
  }
3002

3003
  return nullptr;
168✔
3004
}
3005

3006
void Character::send_highlight() const
89✔
3007
{
3008
  if ( client != nullptr && has_active_client() )
89✔
3009
  {
3010
    Character* opponent = get_opponent();
4✔
3011

3012
    Network::PktHelper::PacketOut<Network::PktOut_AA> msg;
4✔
3013
    if ( opponent != nullptr )
4✔
3014
      msg->Write<u32>( opponent->serial_ext );
×
3015
    else
3016
      msg->offset += 4;
4✔
3017
    msg.Send( client );
4✔
3018
  }
4✔
3019
}
89✔
3020

3021
void Character::on_swing_failure( Character* /*attacker*/ )
×
3022
{
3023
  // do nothing
3024
}
×
3025

3026
void Character::inform_disengaged( Character* /*disengaged*/ )
×
3027
{
3028
  // someone has just disengaged. If we don't have an explicit opponent,
3029
  // pick one of those that has us targetted as the highlight character.
3030
  if ( opponent_ == nullptr )
×
3031
    send_highlight();
×
3032
}
×
3033

3034
void Character::inform_engaged( Character* /*engaged*/ )
1✔
3035
{
3036
  // someone has targetted us.  If we don't have an explicit opponent,
3037
  // pick one of those that has us targetted as the highlight character.
3038
  if ( opponent_ == nullptr )
1✔
3039
    send_highlight();
1✔
3040
}
1✔
3041

3042
void Character::inform_criminal( Character* /*moved*/ )
×
3043
{
3044
  // virtual that does nothing at character level, but fires event for NPCs
3045
}
×
3046

3047
void Character::inform_leftarea( Character* /*wholeft*/ )
1✔
3048
{
3049
  // virtual that does nothing at character level, but fires event for NPCs
3050
}
1✔
3051

3052
void Character::inform_enteredarea( Character* /*whoentered*/ )
22✔
3053
{
3054
  // virtual that does nothing at character level, but fires event for NPCs
3055
}
22✔
3056

3057
void Character::inform_moved( Character* /*moved*/ )
25✔
3058
{
3059
  // consider moving PropagateMove here!
3060
}
25✔
3061
void Character::inform_imoved( Character* /*chr*/ ) {}
29✔
3062

3063
void Character::set_opponent( Character* new_opponent, bool inform_old_opponent )
86✔
3064
{
3065
  INFO_PRINTLN_TRACE( 12 )
86✔
3066
  ( "set_opponent({:#x},{:#x})", this->serial, new_opponent != nullptr ? new_opponent->serial : 0 );
×
3067
  if ( new_opponent != nullptr )
86✔
3068
  {
3069
    if ( new_opponent->dead() )
4✔
3070
      return;
×
3071

3072
    if ( !warmode() && ( script_isa( Core::POLCLASS_NPC ) || has_active_client() ) )
4✔
3073
      set_warmode( true );
1✔
3074
  }
3075

3076
  if ( opponent_ != nullptr )
86✔
3077
  {
3078
    opponent_->opponent_of.erase( this );
4✔
3079
    // Turley 05/26/09 no need to send disengaged event on shutdown
3080
    if ( !Clib::exit_signalled )
4✔
3081
    {
3082
      if ( inform_old_opponent && opponent_ != nullptr )
4✔
3083
        opponent_->inform_disengaged( this );
4✔
3084
    }
3085
  }
3086

3087
  opponent_ = new_opponent;
86✔
3088

3089

3090
  // Turley 05/26/09 possible shutdown crashfix during cleanup
3091
  // (inside schedule_attack() the rest is also senseless on shutdowncleanup)
3092
  if ( !Clib::exit_signalled )
86✔
3093
  {
3094
    reset_swing_timer();
86✔
3095

3096
    if ( opponent_ != nullptr )
86✔
3097
    {
3098
      repsys_on_attack( opponent_ );
4✔
3099
      if ( opponent_->get_opponent() == nullptr )
4✔
3100
        opponent_->reset_swing_timer();
4✔
3101

3102
      opponent_->opponent_of.insert( this );
4✔
3103

3104
      opponent_->inform_engaged( this );
4✔
3105

3106
      opponent_->schedule_attack();
4✔
3107
    }
3108

3109
    send_highlight();
86✔
3110
  }
3111
}
3112

3113
void Character::select_opponent( u32 opp_serial )
×
3114
{
3115
  // test for setting to same so swing timer doesn't reset
3116
  // if you double-click the same guy over and over
3117
  if ( opponent_ == nullptr || opponent_->serial != opp_serial )
×
3118
  {
3119
    Character* new_opponent = Core::find_character( opp_serial );
×
3120
    if ( new_opponent != nullptr )
×
3121
    {
3122
      if ( realm() != new_opponent->realm() )
×
3123
        return;
×
3124
      set_opponent( new_opponent );
×
3125
    }
3126
  }
3127
}
3128

3129
void Character::disable_regeneration_for( int seconds )
4✔
3130
{
3131
  time_t new_disable_time = Core::poltime() + seconds;
4✔
3132
  if ( new_disable_time > disable_regeneration_until )
4✔
3133
    disable_regeneration_until = new_disable_time;
4✔
3134
}
4✔
3135

3136
bool Character::warmode() const
265✔
3137
{
3138
  return mob_flags_.get( MOB_FLAGS::WARMODE );
265✔
3139
}
3140

3141
void Character::set_warmode( bool i_warmode )
4✔
3142
{
3143
  if ( Core::gamestate.system_hooks.warmode_change )
4✔
3144
    Core::gamestate.system_hooks.warmode_change->call( new Module::ECharacterRefObjImp( this ),
×
3145
                                                       new Bscript::BLong( i_warmode ) );
×
3146

3147
  if ( warmode() != i_warmode )
4✔
3148
  {
3149
    disable_regeneration_for( 2 );
4✔
3150
  }
3151

3152
  mob_flags_.change( MOB_FLAGS::WARMODE, i_warmode );
4✔
3153
  if ( i_warmode == false )
4✔
3154
  {
3155
    set_opponent( nullptr );
1✔
3156
  }
3157
  reset_swing_timer();
4✔
3158

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

3182
          msgmove.Send( chr->client );
2✔
3183
        } );
3184
  }
4✔
3185
}
4✔
3186

3187
const AttributeValue& Character::weapon_attribute() const
16✔
3188
{
3189
  return attribute( weapon->attribute().attrid );
16✔
3190
}
3191

3192
unsigned short Character::random_weapon_damage() const
8✔
3193
{
3194
  return weapon->get_random_damage();
8✔
3195
}
3196

3197
unsigned short Character::min_weapon_damage() const
90✔
3198
{
3199
  return weapon->min_weapon_damage();
90✔
3200
}
3201

3202
unsigned short Character::max_weapon_damage() const
90✔
3203
{
3204
  return weapon->max_weapon_damage();
90✔
3205
}
3206

3207
void Character::damage_weapon()
8✔
3208
{
3209
  if ( !weapon->is_intrinsic() && !weapon->is_projectile() )
8✔
3210
  {
3211
    weapon->reduce_hp_from_hit();
×
3212
  }
3213
}
8✔
3214

3215
Items::UArmor* Character::choose_armor() const
8✔
3216
{
3217
  double f = Clib::random_double( Core::gamestate.armor_zone_chance_sum );
8✔
3218
  for ( unsigned zone = 0; zone < Core::gamestate.armorzones.size(); ++zone )
19✔
3219
  {
3220
    f -= Core::gamestate.armorzones[zone].chance;
19✔
3221
    if ( f <= 0.0 )
19✔
3222
    {
3223
      return armor_[zone];
8✔
3224
    }
3225
  }
3226
  return nullptr;
×
3227
}
3228

3229
Core::UACTION Character::weapon_anim() const
8✔
3230
{
3231
  if ( on_mount() )
8✔
3232
    return weapon->mounted_anim();
×
3233
  else
3234
    return weapon->anim();
8✔
3235
}
3236

3237
void Character::do_attack_effects( Character* target )
8✔
3238
{
3239
  if ( weapon->is_projectile() )
8✔
3240
  {
3241
    // 234 is hit, 238 is miss??
3242
    play_sound_effect( this, weapon->projectile_sound() );
×
3243
    play_moving_effect( this, target, weapon->projectile_anim(),
×
3244
                        9,    // Speed (??)
3245
                        0,    // Loop
3246
                        0 );  // Explode
3247
    if ( graphic >= UOBJ_HUMAN_MALE )
×
3248
    {
3249
      send_action_to_inrange( this, weapon_anim() );
×
3250
    }
3251
    else
3252
    {
3253
      send_action_to_inrange( this, Core::ACTION_LOOK_AROUND );
×
3254
    }
3255
  }
3256
  else if ( graphic >= UOBJ_HUMAN_MALE )
8✔
3257
  {
3258
    send_action_to_inrange( this, weapon_anim() );
8✔
3259
  }
3260
  else
3261
  {
3262
    send_action_to_inrange( this, Core::ACTION_LOOK_AROUND );
×
3263
  }
3264
}
8✔
3265

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

3273
void Character::do_hit_failure_effects()
×
3274
{
3275
  unsigned short sound = weapon->miss_sound();
×
3276
  if ( sound )
×
3277
    play_sound_effect( this, sound );
×
3278
}
×
3279

3280
u16 Character::get_damaged_sound() const
×
3281
{
3282
  if ( gender == Plib::GENDER_MALE )
×
3283
    return SOUND_EFFECT_MALE_DEFENSE;
×
3284
  return SOUND_EFFECT_FEMALE_DEFENSE;
×
3285
}
3286

3287
void Character::do_imhit_effects()
×
3288
{
3289
  if ( Core::settingsManager.combat_config.core_hit_sounds )
×
3290
  {
3291
    play_sound_effect( this, get_damaged_sound() );
×
3292
  }
3293
  if ( objtype_ >= UOBJ_HUMAN_MALE )
×
3294
    send_action_to_inrange( this, Core::ACTION_GOT_HIT );
×
3295
}
×
3296

3297

3298
void Character::attack( Character* opponent )
8✔
3299
{
3300
  INC_PROFILEVAR( combat_operations );
8✔
3301

3302
  if ( Core::gamestate.system_hooks.attack_hook )
8✔
3303
  {
3304
    if ( Core::gamestate.system_hooks.attack_hook->call(
×
3305
             new Module::ECharacterRefObjImp( this ),
×
3306
             new Module::ECharacterRefObjImp( opponent ) ) )
×
3307
      return;
×
3308
  }
3309

3310
  if ( Core::settingsManager.watch.combat )
8✔
3311
    INFO_PRINTLN( "{} attacks {}", name(), opponent->name() );
×
3312

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

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

3348
              if ( weapon->consume_projectile( cont ) == true )
×
3349
              {
3350
                projectile_check = true;
×
3351
                break;
×
3352
              }
3353
            }
3354
          }
3355
        }
3356
      }
3357
      // I'm out of projectiles.
3358
      if ( projectile_check == false )
×
3359
        return;
×
3360
    }
3361
  }
3362

3363
  repsys_on_attack( opponent );
8✔
3364
  repsys_on_damage( opponent );
8✔
3365

3366
  do_attack_effects( opponent );
8✔
3367

3368
  if ( Core::gamestate.system_hooks.combat_advancement_hook )
8✔
3369
  {
3370
    Core::gamestate.system_hooks.combat_advancement_hook->call(
×
3371
        new Module::ECharacterRefObjImp( this ), new Module::EItemRefObjImp( weapon ),
×
3372
        new Module::ECharacterRefObjImp( opponent ) );
×
3373
  }
3374

3375
  double hit_chance = ( weapon_attribute().effective() + 50.0 ) /
8✔
3376
                      ( 2.0 * ( opponent->weapon_attribute().effective() + 50.0 ) );
8✔
3377
  hit_chance += hitchance_mod() * 0.001f;
8✔
3378
  hit_chance -= opponent->evasionchance_mod() * 0.001f;
8✔
3379
  if ( Core::settingsManager.watch.combat )
8✔
3380
    INFO_PRINT( "Chance to hit: {}: ", hit_chance );
×
3381
  if ( Clib::random_double( 1.0 ) < hit_chance )
8✔
3382
  {
3383
    if ( Core::settingsManager.watch.combat )
8✔
3384
      INFO_PRINTLN( "Hit!" );
×
3385
    do_hit_success_effects();
8✔
3386

3387
    double damage = random_weapon_damage();
8✔
3388
    damage_weapon();
8✔
3389

3390
    if ( Core::settingsManager.watch.combat )
8✔
3391
      INFO_PRINTLN( "Base damage: {}", damage );
×
3392

3393
    double damage_multiplier = attribute( Core::gamestate.pAttrTactics->attrid ).effective() + 50;
8✔
3394
    damage_multiplier += strength() * 0.20f;
8✔
3395
    damage_multiplier *= 0.01f;
8✔
3396

3397
    damage *= damage_multiplier;
8✔
3398

3399
    if ( Core::settingsManager.watch.combat )
8✔
3400
      INFO_PRINTLN( "Damage multiplier due to tactics/STR: {} Result: {}", damage_multiplier,
×
3401
                    damage );
3402

3403
    if ( opponent->shield != nullptr )
8✔
3404
    {
3405
      if ( Core::gamestate.system_hooks.parry_advancement_hook )
×
3406
      {
3407
        Core::gamestate.system_hooks.parry_advancement_hook->call(
×
3408
            new Module::ECharacterRefObjImp( this ), new Module::EItemRefObjImp( weapon ),
×
3409
            new Module::ECharacterRefObjImp( opponent ),
×
3410
            new Module::EItemRefObjImp( opponent->shield ) );
×
3411
      }
3412

3413
      double parry_chance =
3414
          opponent->attribute( Core::gamestate.pAttrParry->attrid ).effective() / 200.0;
×
3415
      parry_chance += opponent->parrychance_mod() * 0.001f;
×
3416
      if ( Core::settingsManager.watch.combat )
×
3417
        INFO_PRINT( "Parry Chance: {}: ", parry_chance );
×
3418
      if ( Clib::random_double( 1.0 ) < parry_chance )
×
3419
      {
3420
        if ( Core::settingsManager.watch.combat )
×
3421
          INFO_PRINTLN( "{} hits deflected", opponent->shield->ar() );
×
3422
        if ( Core::settingsManager.combat_config.display_parry_success_messages &&
×
3423
             opponent->client )
×
3424
          Core::send_sysmessage( opponent->client, "You successfully parried the attack!" );
×
3425

3426
        damage -= opponent->shield->ar();
×
3427
        if ( damage < 0 )
×
3428
          damage = 0;
×
3429
      }
3430
      else
3431
      {
3432
        if ( Core::settingsManager.watch.combat )
×
3433
          INFO_PRINTLN( "failed." );
×
3434
      }
3435
    }
3436
    if ( weapon->hit_script().empty() )
8✔
3437
    {
3438
      opponent->apply_damage( damage, this, true,
×
3439
                              Core::settingsManager.combat_config.send_damage_packet );
×
3440
    }
3441
    else
3442
    {
3443
      run_hit_script( opponent, damage );
8✔
3444
    }
3445
  }
3446
  else
3447
  {
3448
    if ( Core::settingsManager.watch.combat )
×
3449
      INFO_PRINTLN( "Miss!" );
×
3450
    opponent->on_swing_failure( this );
×
3451
    do_hit_failure_effects();
×
3452
    if ( Core::gamestate.system_hooks.hitmiss_hook )
×
3453
    {
3454
      Core::gamestate.system_hooks.hitmiss_hook->call(
×
3455
          new Module::ECharacterRefObjImp( this ), new Module::ECharacterRefObjImp( opponent ) );
×
3456
    }
3457
  }
3458
}
3459

3460
void Character::check_attack_after_move( bool check_opponents_after_check )
184✔
3461
{
3462
  FUNCTION_CHECKPOINT( check_attack_after_move, 1 );
184✔
3463
  Character* opponent = get_attackable_opponent();
184✔
3464
  FUNCTION_CHECKPOINT( check_attack_after_move, 2 );
184✔
3465
  INFO_PRINTLN_TRACE( 20 )
184✔
3466
  ( "check_attack_after_move({:#x}): opponent is {:#x}", this->serial,
×
3467
    opponent != nullptr ? opponent->serial : 0 );
×
3468
  if ( opponent != nullptr &&  // and I have an opponent
200✔
3469
       !dead() &&              // If I'm not dead
200✔
3470
       ( Core::settingsManager.combat_config.attack_while_frozen ||
16✔
3471
         ( !paralyzed() && !frozen() ) ) )
×
3472
  {
3473
    FUNCTION_CHECKPOINT( check_attack_after_move, 3 );
16✔
3474
    if ( mob_flags_.get( MOB_FLAGS::READY_TO_SWING ) )  // and I can swing now,
16✔
3475
    {                                                   // do so.
3476
      FUNCTION_CHECKPOINT( check_attack_after_move, 4 );
8✔
3477
      if ( Core::settingsManager.combat_config.send_swing_packet && client != nullptr )
8✔
3478
        send_fight_occuring( client, opponent );
×
3479

3480
      // we don't want attack() to recursively cause new attacks
3481
      mob_flags_.remove( MOB_FLAGS::READY_TO_SWING );
8✔
3482
      attack( opponent );
8✔
3483
      FUNCTION_CHECKPOINT( check_attack_after_move, 5 );
8✔
3484
      reset_swing_timer();
8✔
3485
      FUNCTION_CHECKPOINT( check_attack_after_move, 6 );
8✔
3486
    }
3487
    else
3488
    {
3489
      FUNCTION_CHECKPOINT( check_attack_after_move, 7 );
8✔
3490
      INFO_PRINTLN_TRACE( 20 )( "not ready to swing" );
8✔
3491
      schedule_attack();
8✔
3492
      FUNCTION_CHECKPOINT( check_attack_after_move, 8 );
8✔
3493
    }
3494
  }
3495
  FUNCTION_CHECKPOINT( check_attack_after_move, 0 );
184✔
3496

3497
  if ( check_opponents_after_check )
184✔
3498
  {
3499
    if ( opponent_ != nullptr )
167✔
3500
      opponent_->check_attack_after_move( false );
3✔
3501

3502
    // attacking can change the opponent_of array drastically.
3503
    std::set<Character*> tmp( opponent_of );
167✔
3504
    for ( auto& chr : tmp )
170✔
3505
    {
3506
      chr->check_attack_after_move( false );
3✔
3507
    }
3508
  }
167✔
3509
}
184✔
3510

3511

3512
void Character::check_light_region_change()
166✔
3513
{
3514
  auto light_unil = lightoverride_until();
166✔
3515
  if ( light_unil < Core::read_gameclock() && light_unil != ~0u )
166✔
3516
  {
3517
    lightoverride_until( 0 );
166✔
3518
    lightoverride( -1 );
166✔
3519
  }
3520
  if ( client->gd->weather_region && client->gd->weather_region->lightoverride != -1 &&
166✔
3521
       !has_lightoverride() )
×
3522
    return;
×
3523

3524
  int newlightlevel;
3525
  if ( has_lightoverride() )
166✔
3526
    newlightlevel = lightoverride();
×
3527
  else
3528
  {
3529
    // dave 12-22 check for no regions
3530
    Core::LightRegion* light_region = Core::gamestate.lightdef->getregion( pos() );
166✔
3531
    if ( light_region != nullptr )
166✔
3532
      newlightlevel = light_region->lightlevel;
×
3533
    else
3534
      newlightlevel = Core::settingsManager.ssopt.default_light_level;
166✔
3535
  }
3536

3537
  if ( newlightlevel != client->gd->lightlevel )
166✔
3538
  {
3539
    Core::send_light( client, newlightlevel );
2✔
3540
    client->gd->lightlevel = newlightlevel;
2✔
3541
  }
3542
}
3543

3544
void Character::check_justice_region_change()
103✔
3545
{
3546
  Core::JusticeRegion* cur_justice_region = client->gd->justice_region;
103✔
3547
  Core::JusticeRegion* new_justice_region = Core::gamestate.justicedef->getregion( pos() );
103✔
3548

3549
  if ( cur_justice_region != new_justice_region )
103✔
3550
  {
3551
    if ( cur_justice_region != nullptr )
×
3552
      cur_justice_region->RunLeaveScript( client->chr );
×
3553
    if ( new_justice_region != nullptr )
×
3554
      new_justice_region->RunEnterScript( client->chr );
×
3555

3556
    // print 'leaving' message
3557
    bool printmsgs;
3558
    if ( cur_justice_region != nullptr && new_justice_region != nullptr &&
×
3559
         cur_justice_region->entertext() == new_justice_region->entertext() &&
×
3560
         cur_justice_region->leavetext() == new_justice_region->leavetext() )
×
3561
    {
3562
      printmsgs = false;
×
3563
    }
3564
    else
3565
    {
3566
      printmsgs = true;
×
3567
    }
3568

3569
    if ( printmsgs && cur_justice_region )
×
3570
    {
3571
      const std::string& leavetext = cur_justice_region->leavetext();
×
3572
      if ( !leavetext.empty() )
×
3573
      {
3574
        Core::send_sysmessage( client, leavetext );
×
3575
      }
3576
    }
3577

3578
    client->gd->justice_region = new_justice_region;
×
3579

3580
    if ( new_justice_region && new_justice_region->RunNoCombatCheck( client ) == true )
×
3581
    {
3582
      Character* opp2 = get_opponent();
×
3583
      if ( ( opp2 != nullptr && opp2->client ) )
×
3584
      {
3585
        opp2->opponent_of.erase( client->chr );
×
3586
        opp2->set_opponent( nullptr, true );
×
3587
        opp2->schedule_attack();
×
3588
        opp2->opponent_ = nullptr;
×
3589
        opp2->clear_opponent_of();
×
3590
        set_opponent( nullptr, true );
×
3591
        if ( swing_task != nullptr )
×
3592
          swing_task->cancel();
×
3593
      }
3594
    }
3595

3596

3597
    // print 'entering' message
3598
    // handle nocombat while we have entered.
3599
    if ( printmsgs && new_justice_region )
×
3600
    {
3601
      const std::string& entertext = new_justice_region->entertext();
×
3602
      if ( !entertext.empty() )
×
3603
      {
3604
        Core::send_sysmessage( client, entertext );
×
3605
      }
3606
    }
3607
  }
3608
}
103✔
3609

3610
void Character::check_music_region_change()
103✔
3611
{
3612
  Core::MusicRegion* cur_music_region = client->gd->music_region;
103✔
3613
  Core::MusicRegion* new_music_region = Core::gamestate.musicdef->getregion( pos() );
103✔
3614

3615
  // may want to consider changing every n minutes, too, even if region didn't change
3616
  if ( cur_music_region != new_music_region )
103✔
3617
  {
3618
    client->gd->music_region = new_music_region;
×
3619
    if ( new_music_region != nullptr )
×
3620
    {
3621
      Core::send_midi( client, new_music_region->getmidi() );
×
3622
    }
3623
    else
3624
    {
3625
      Core::send_midi( client, 0 );
×
3626
    }
3627
  }
3628
}
103✔
3629

3630
void Character::check_weather_region_change( bool force )  // dave changed 5/26/03 - use force
105✔
3631
                                                           // boolean if current weather region
3632
                                                           // changed type/intensity
3633
{
3634
  Core::WeatherRegion* cur_weather_region = client->gd->weather_region;
105✔
3635
  Core::WeatherRegion* new_weather_region = Core::gamestate.weatherdef->getregion( pos() );
105✔
3636

3637
  // eric 5/31/03: I don't think this is right.  it's possible to go from somewhere that has no
3638
  // weather region,
3639
  // and to walk to somewhere that doesn't have a weather region.
3640
  //
3641
  if ( force || ( cur_weather_region != new_weather_region ) )
105✔
3642
  {
3643
    if ( new_weather_region != nullptr && new_weather_region->lightoverride != -1 &&
2✔
3644
         !has_lightoverride() )
×
3645
    {
3646
      Core::send_light( client, new_weather_region->lightoverride );
×
3647
      client->gd->lightlevel = new_weather_region->lightoverride;
×
3648
    }
3649

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

3658
    if ( new_weather_region )
2✔
3659
    {
3660
      Core::send_weather( client, new_weather_region->weathertype, new_weather_region->severity,
×
3661
                          new_weather_region->aux );
×
3662
    }
3663
    else
3664
    {
3665
      Core::send_weather( client, 0xff, 0, 0 );  // turn off
2✔
3666
    }
3667
    client->gd->weather_region = new_weather_region;
2✔
3668
  }
3669
}
105✔
3670

3671
void Character::check_region_changes()
166✔
3672
{
3673
  if ( client != nullptr )
166✔
3674
  {
3675
    check_weather_region_change();
103✔
3676

3677
    check_light_region_change();
103✔
3678

3679
    check_justice_region_change();
103✔
3680

3681
    check_music_region_change();
103✔
3682
  }
3683
}
166✔
3684

3685
void Character::position_changed()
254✔
3686
{
3687
  wornitems->setposition( pos() );
254✔
3688
  position_changed_at_ = Core::polclock();
254✔
3689
}
254✔
3690

3691
void Character::unhide()
3✔
3692
{
3693
  if ( Core::gamestate.system_hooks.un_hide )
3✔
3694
  {
3695
    if ( !Core::gamestate.system_hooks.un_hide->call( make_mobileref( this ) ) )
×
3696
      return;
×
3697
  }
3698

3699
  hidden( false );
3✔
3700
  if ( is_visible() )
3✔
3701
  {
3702
    if ( client != nullptr )
3✔
3703
      send_owncreate( client, this );
×
3704

3705
    Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
3✔
3706
        this,
3707
        [&]( Character* chr )
3✔
3708
        {
3709
          if ( chr == this )
×
3710
            return;
×
3711
          if ( !chr->is_visible_to_me( this ) )
×
3712
            return;
×
3713
          send_owncreate( chr->client, this );
×
3714
        } );
3715

3716
    realm()->notify_unhid( *this );
3✔
3717

3718
    if ( !Clib::exit_signalled )
3✔
3719
    {
3720
      check_attack_after_move( true );
3✔
3721
    }
3722
  }
3723
}
3724

3725
void Character::set_stealthsteps( unsigned short newval )
8✔
3726
{
3727
  stealthsteps_ = newval;
8✔
3728
}
8✔
3729

3730
bool Character::doors_block() const
98✔
3731
{
3732
  return !( graphic == UOBJ_GAMEMASTER || graphic == UOBJ_HUMAN_MALE_GHOST ||
196✔
3733
            graphic == UOBJ_HUMAN_FEMALE_GHOST || graphic == UOBJ_ELF_MALE_GHOST ||
98✔
3734
            graphic == UOBJ_ELF_FEMALE_GHOST || graphic == UOBJ_GARGOYLE_MALE_GHOST ||
98✔
3735
            graphic == UOBJ_GARGOYLE_FEMALE_GHOST ||
98✔
3736
            cached_settings.get( PRIV_FLAGS::IGNORE_DOORS ) );
196✔
3737
}
3738

3739
/*
3740
    This isn't quite right, for hidden characters.
3741
    What normally happens:
3742
    1) Client sends "use skill hiding"
3743
    2) Server sends "77 move" with the hidden flag set.
3744
    3) client sends move-request
3745
    4) server sends move-approve
3746
    5) server sense "78 create" message with hidden flag clear, at new posn.
3747

3748
    We're sending the "78 create" _before_ the move-approve.
3749
    */
3750

3751
bool Character::can_face( Core::UFACING /*i_facing*/ )
24✔
3752
{
3753
  if ( can_freemove() )
24✔
3754
    return true;
×
3755

3756
  if ( frozen() || paralyzed() )
24✔
3757
  {
3758
    if ( client != nullptr )
×
3759
    {
3760
      if ( frozen() )
×
3761
        private_say_above( this, this, "I am frozen and cannot move." );
×
3762
      else if ( paralyzed() )
×
3763
        private_say_above( this, this, "I am paralyzed and cannot move." );
×
3764
    }
3765
    return false;
×
3766
  }
3767

3768
  if ( Core::settingsManager.ssopt.movement_uses_stamina &&
72✔
3769
       vital( Core::gamestate.pVitalStamina->vitalid ).current_ones() == 0 && !dead() )
24✔
3770
  {
3771
    private_say_above( this, this, "You are too fatigued to move." );
×
3772
    return false;
×
3773
  }
3774

3775
  return true;
24✔
3776
}
3777

3778

3779
bool Character::face( Core::UFACING i_facing, int flags )
24✔
3780
{
3781
  if ( ( flags & 1 ) == 0 )
24✔
3782
  {
3783
    if ( !can_face( i_facing ) )
24✔
3784
      return false;
×
3785
  }
3786

3787
  if ( i_facing != facing )
24✔
3788
  {
3789
    setfacing( static_cast<u8>( i_facing ) );
8✔
3790

3791
    if ( Core::settingsManager.combat_config.reset_swing_onturn &&
16✔
3792
         !cached_settings.get( PRIV_FLAGS::FIRE_WHILE_MOVING ) && weapon->is_projectile() )
8✔
3793
      reset_swing_timer();
×
3794
    if ( hidden() && ( Core::settingsManager.ssopt.hidden_turns_count ) )
8✔
3795
    {
3796
      if ( stealthsteps_ == 0 )
×
3797
        unhide();
×
3798
      else
3799
        stealthsteps_--;
×
3800
    }
3801
  }
3802

3803
  return true;
24✔
3804
}
3805

3806

3807
bool Character::CustomHousingMove( unsigned char i_dir )
×
3808
{
3809
  passert( facing < 8 );
×
3810

3811
  Multi::UMulti* multi = Core::system_find_multi( client->gd->custom_house_serial );
×
3812
  if ( multi != nullptr )
×
3813
  {
3814
    Multi::UHouse* house = multi->as_house();
×
3815
    if ( house != nullptr )
×
3816
    {
3817
      Core::UFACING i_facing = static_cast<Core::UFACING>( i_dir & PKTIN_02_FACING_MASK );
×
3818
      if ( i_facing != facing )
×
3819
      {
3820
        setfacing( static_cast<u8>( i_facing ) );
×
3821
        set_dirty();
×
3822
        dir = i_dir;
×
3823
        return true;
×
3824
      }
3825
      else
3826
      {
3827
        auto newpos = pos().move( static_cast<Core::UFACING>( facing ) );
×
3828
        newpos.z( house->z() +
×
3829
                  Multi::CustomHouseDesign::custom_house_z_xlate_table[house->editing_floor_num] );
×
3830
        const Multi::MultiDef& def = house->multidef();
×
3831
        auto relpos = newpos - house->pos().xy();
×
3832
        // minx and y are wall elements and z is 7
3833
        // mobile will look like flying when allowing the min coords
3834
        if ( def.within_multi( relpos ) && relpos.x() != def.minrxyz.x() &&
×
3835
             relpos.y() != def.minrxyz.y() )
×
3836
        {
3837
          Core::Pos4d oldpos = pos();
×
3838
          setposition( newpos );
×
3839
          MoveCharacterWorldPosition( oldpos, this );
×
3840

3841
          position_changed();
×
3842
          set_dirty();
×
3843
          return true;
×
3844
        }
3845
      }
3846
    }
3847
  }
3848
  return false;
×
3849
}
3850

3851
bool Character::is_piloting_boat() const
138✔
3852
{
3853
  auto* mountpiece = wornitem( Core::LAYER_MOUNT );
138✔
3854
  return mountpiece != nullptr && mountpiece->objtype_ == Core::settingsManager.extobj.boatmount;
138✔
3855
}
3856

3857
//************************************
3858
// Method:    move
3859
// FullName:  Character::move
3860
// Access:    public
3861
// Returns:   bool
3862
// Qualifier:
3863
// Parameter: unsigned char i_dir
3864
//************************************
3865
bool Character::move( unsigned char i_dir )
20✔
3866
{
3867
  if ( is_piloting_boat() )
20✔
3868
  {
3869
    return false;
1✔
3870
  }
3871

3872
  lastpos = pos();
19✔
3873

3874
  // if currently building a house chr can move free inside the multi
3875
  if ( is_house_editing() )
19✔
3876
    return CustomHousingMove( i_dir );
×
3877

3878
  u8 oldFacing = facing;
19✔
3879

3880
  Core::UFACING i_facing = static_cast<Core::UFACING>( i_dir & PKTIN_02_FACING_MASK );
19✔
3881
  if ( !face( i_facing ) )
19✔
3882
    return false;
×
3883

3884
  // If we're a player, and we used our "move" command to turn,
3885
  //  we want to skip the meat of this function
3886
  if ( ( script_isa( Core::POLCLASS_NPC ) ) || ( facing == oldFacing ) )
19✔
3887
  {
3888
    if ( facing & 1 )  // check if diagonal movement is allowed -- Nando (2009-02-26)
14✔
3889
    {
3890
      short new_z;
3891
      u8 tmp_facing = ( facing + 1 ) & 0x7;
1✔
3892
      auto tmp_pos = pos().move( static_cast<Core::UFACING>( tmp_facing ) );
1✔
3893

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

3898
      tmp_facing = ( facing - 1 ) & 0x7;
1✔
3899
      tmp_pos = pos().move( static_cast<Core::UFACING>( tmp_facing ) );
1✔
3900

3901
      if ( !walk1 && !realm()->walkheight( this, tmp_pos.xy(), tmp_pos.z(), &new_z, nullptr,
1✔
3902
                                           nullptr, nullptr ) )
3903
        return false;
×
3904
    }
3905
    auto new_pos = pos().move( static_cast<Core::UFACING>( facing ) );
14✔
3906

3907
    // FIXME consider consolidating with similar code in UOEMOD.CPP
3908
    short newz;
3909
    Multi::UMulti* supporting_multi;
3910
    Items::Item* walkon_item;
3911

3912
    short current_boost = gradual_boost;
14✔
3913
    if ( !realm()->walkheight( this, new_pos.xy(), new_pos.z(), &newz, &supporting_multi,
14✔
3914
                               &walkon_item, &current_boost ) )
3915
      return false;
1✔
3916
    new_pos.z( static_cast<s8>( newz ) );
13✔
3917
    remote_containers_.clear();
13✔
3918

3919
    if ( !CheckPushthrough() )
13✔
3920
      return false;
×
3921

3922
    if ( !can_freemove() && Core::settingsManager.ssopt.movement_uses_stamina && !dead() )
13✔
3923
    {
3924
      int carry_perc = weight() * 100 / carrying_capacity();
13✔
3925
      unsigned short tmv = movecost(
26✔
3926
          this, carry_perc, ( i_dir & PKTIN_02_DIR_RUNNING_BIT ) ? true : false, on_mount() );
13✔
3927
      VitalValue& stamina = vital( Core::gamestate.pVitalStamina->vitalid );
13✔
3928
      if ( !consume( Core::gamestate.pVitalStamina, stamina, tmv, VitalDepletedReason::MOVEMENT ) )
13✔
3929
      {
3930
        private_say_above( this, this, "You are too fatigued to move." );
×
3931
        return false;
×
3932
      }
3933
    }
3934

3935
    // Maybe have a flag for this in servspecopt?
3936
    if ( !cached_settings.get( PRIV_FLAGS::FIRE_WHILE_MOVING ) && weapon->is_projectile() )
13✔
3937
      reset_swing_timer();
×
3938

3939
    Core::Pos4d oldpos = pos();
13✔
3940
    setposition( new_pos );
13✔
3941
    moved_at_ = Core::polclock();
13✔
3942

3943
    if ( on_mount() && !script_isa( Core::POLCLASS_NPC ) )
13✔
3944
    {
3945
      mountedsteps_++;
×
3946
    }
3947
    if ( supporting_multi != nullptr )
13✔
3948
    {
3949
      supporting_multi->register_object( this );
×
3950

3951
      if ( this->registered_multi == 0 )
×
3952
      {
3953
        this->registered_multi = supporting_multi->serial;
×
3954
        supporting_multi->walk_on( this );
×
3955
      }
3956
    }
3957
    else
3958
    {
3959
      if ( registered_multi > 0 )
13✔
3960
      {
3961
        Multi::UMulti* multi = Core::system_find_multi( registered_multi );
×
3962
        if ( multi != nullptr )
×
3963
        {
3964
          multi->unregister_object( (UObject*)this );
×
3965
        }
3966
        registered_multi = 0;
×
3967
      }
3968
    }
3969

3970
    gradual_boost = current_boost;
13✔
3971
    MoveCharacterWorldPosition( oldpos, this );
13✔
3972

3973
    position_changed();
13✔
3974
    if ( walkon_item != nullptr )
13✔
3975
    {
3976
      walkon_item->walk_on( this );
×
3977
    }
3978

3979
    if ( hidden() )
13✔
3980
    {
3981
      if ( ( ( i_dir & PKTIN_02_DIR_RUNNING_BIT ) &&
×
3982
             !cached_settings.get( PRIV_FLAGS::RUN_WHILE_STEALTH ) ) ||
×
3983
           ( stealthsteps_ == 0 ) )
×
3984
        unhide();
×
3985
      else if ( stealthsteps_ )
×
3986
        --stealthsteps_;
×
3987
    }
3988

3989
    if ( Core::gamestate.system_hooks.ouch_hook )
13✔
3990
    {
3991
      if ( ( lastpos.z() - z() ) > 21 )
×
3992
        Core::gamestate.system_hooks.ouch_hook->call(
×
3993
            make_mobileref( this ), new Bscript::BLong( lastpos.x() ),
×
3994
            new Bscript::BLong( lastpos.y() ), new Bscript::BLong( lastpos.z() ) );
×
3995
    }
3996
  }
3997

3998
  set_dirty();
18✔
3999
  dir = i_dir;
18✔
4000

4001
  return true;
18✔
4002

4003

4004
  // this would be a great place for tellmove(), except that
4005
  // we want to send the move acknowledge to the client before
4006
  // sending the move/create messages to the neighboring clients.
4007

4008
  // Why? Maybe to give the best response time to the client.
4009

4010
  // may want to rethink this, since it's starting to complicate
4011
  // things.
4012
}
4013

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

4040
  if ( has_active_client() )
18✔
4041
  {
4042
    // these are important to keep here in this order
4043
    Core::send_realm_change( client, realm() );
14✔
4044
    Core::send_map_difs( client );
14✔
4045
    if ( Core::settingsManager.ssopt.core_sends_season )
14✔
4046
      Core::send_season_info( client );
14✔
4047
    Core::send_full_statmsg( client, this );
14✔
4048
    Core::send_feature_enable( client );
14✔
4049
  }
4050
}
18✔
4051

4052
bool Character::CheckPushthrough()
13✔
4053
{
4054
  if ( !can_freemove() && Core::gamestate.system_hooks.pushthrough_hook )
13✔
4055
  {
4056
    auto newpos = pos().move( static_cast<Core::UFACING>( facing ) );
×
4057
    auto mobs = std::unique_ptr<Bscript::ObjArray>();
×
4058

4059
    Core::WorldIterator<Core::MobileFilter>::InRange(
×
4060
        newpos, 0,
4061
        [&]( Mobile::Character* _chr )
×
4062
        {
4063
          if ( _chr->z() >= z() - 10 && _chr->z() <= z() + 10 && !_chr->dead() &&
×
4064
               ( is_visible_to_me( _chr ) ||
×
4065
                 _chr->hidden() ) )  // add hidden mobs even if they're not visible to me
×
4066
          {
4067
            if ( !mobs )
×
NEW
4068
              mobs = std::make_unique<Bscript::ObjArray>();
×
4069
            mobs->addElement( make_mobileref( _chr ) );
×
4070
          }
4071
        } );
×
4072

4073
    if ( mobs )
×
4074
    {
4075
      return Core::gamestate.system_hooks.pushthrough_hook->call( make_mobileref( this ),
×
4076
                                                                  mobs.release() );
×
4077
    }
4078
    return true;
×
4079
  }
×
4080
  return true;
13✔
4081
}
4082

4083
void Character::tellmove()
164✔
4084
{
4085
  check_region_changes();
164✔
4086
  PropagateMove( this );
164✔
4087

4088
  // notify npcs and items (maybe the PropagateMove should also go there eventually? - Nando
4089
  // 2018-06-16)
4090
  realm()->notify_moved( *this );
164✔
4091

4092
  check_attack_after_move( true );
164✔
4093

4094
  move_reason = OTHER;
164✔
4095
}
164✔
4096

4097
void Character::add_remote_container( Items::Item* item )
×
4098
{
NEW
4099
  remote_containers_.emplace_back( item );
×
4100
}
×
4101

4102
Items::Item* Character::search_remote_containers( u32 find_serial, bool* isRemoteContainer ) const
45✔
4103
{
4104
  if ( isRemoteContainer )
45✔
4105
    *isRemoteContainer = false;
40✔
4106
  for ( const auto& elem : remote_containers_ )
45✔
4107
  {
4108
    Items::Item* item = elem.get();
×
4109
    if ( item->orphan() )
×
4110
      continue;  // it'll be cleaned up when they move
×
4111
    if ( item->serial == find_serial )
×
4112
    {
4113
      if ( isRemoteContainer )
×
4114
        *isRemoteContainer = true;
×
4115
      return item;
×
4116
    }
4117
    if ( item->isa( Core::UOBJ_CLASS::CLASS_CONTAINER ) )
×
4118
    {
4119
      item = ( (Core::UContainer*)item )->find( find_serial );
×
4120
      if ( item )
×
4121
      {
4122
        if ( isRemoteContainer )
×
4123
          *isRemoteContainer = false;
×
4124
        return item;
×
4125
      }
4126
    }
4127
  }
4128
  return nullptr;
45✔
4129
}
4130

4131
bool Character::mightsee( const Items::Item* item ) const
76✔
4132
{
4133
  const auto* owner = item->toplevel_owner();
76✔
4134
  for ( const auto& elem : remote_containers_ )
76✔
4135
  {
4136
    Items::Item* additional_item = elem.get();
×
4137
    if ( additional_item == owner )
×
4138
      return true;
×
4139
  }
4140

4141
  return in_visual_range( owner );
76✔
4142
}
4143

4144
bool Character::squelched() const
37✔
4145
{
4146
  Core::gameclock_t squelched = squelched_until();
37✔
4147
  if ( squelched == 0 )
37✔
4148
    return false;
36✔
4149
  else if ( squelched == ~0u )
1✔
4150
    return true;
×
4151

4152
  if ( Core::read_gameclock() < squelched )
1✔
4153
  {
4154
    return true;
1✔
4155
  }
4156
  else
4157
  {
4158
    const_cast<Character*>( this )->squelched_until( 0 );
×
4159
    return false;
×
4160
  }
4161
}
4162

4163
bool Character::deafened() const
38✔
4164
{
4165
  Core::gameclock_t deafened = deafened_until();
38✔
4166
  if ( deafened == 0 )
38✔
4167
    return false;
37✔
4168
  else if ( deafened == ~0u )
1✔
4169
    return true;
×
4170

4171
  if ( Core::read_gameclock() < deafened )
1✔
4172
  {
4173
    return true;
1✔
4174
  }
4175
  else
4176
  {
4177
    const_cast<Character*>( this )->deafened_until( 0 );
×
4178
    return false;
×
4179
  }
4180
}
4181

4182
bool Character::invul() const
462✔
4183
{
4184
  return cached_settings.get( PRIV_FLAGS::INVUL );
462✔
4185
}
4186

4187
u16 Character::strength() const
21✔
4188
{
4189
  return static_cast<u16>( attribute( Core::gamestate.pAttrStrength->attrid ).effective() );
21✔
4190
}
4191
u16 Character::dexterity() const
173✔
4192
{
4193
  return static_cast<u16>( attribute( Core::gamestate.pAttrDexterity->attrid ).effective() );
173✔
4194
}
4195
u16 Character::intelligence() const
×
4196
{
4197
  return static_cast<u16>( attribute( Core::gamestate.pAttrIntelligence->attrid ).effective() );
×
4198
}
4199

4200
bool Character::target_cursor_busy() const
8✔
4201
{
4202
  if ( client && client->gd && client->gd->tcursor2 && client->gd->target_cursor_uoemod != nullptr )
8✔
4203
    return true;
×
4204
  return false;
8✔
4205
}
4206

4207
void Character::cancel_menu()
×
4208
{
4209
  if ( client )
×
4210
  {
4211
    client->gd->menu.clear();
×
4212
    if ( client->gd->on_menu_selection != nullptr )
×
4213
      client->gd->on_menu_selection( client, nullptr, nullptr );
×
4214
    client->gd->on_menu_selection = nullptr;
×
4215
  }
4216
}
×
4217

4218
bool Character::is_trading() const
190✔
4219
{
4220
  return ( trading_with.get() != nullptr );
190✔
4221
}
4222

4223
bool Character::trade_accepted() const
×
4224
{
4225
  return mob_flags_.get( MOB_FLAGS::TRADE_ACCEPTED );
×
4226
}
4227

4228
void Character::trade_accepted( bool newvalue )
×
4229
{
4230
  mob_flags_.change( MOB_FLAGS::TRADE_ACCEPTED, newvalue );
×
4231
}
×
4232

4233
void Character::create_trade_container()
×
4234
{
4235
  if ( trading_cont.get() == nullptr )  // FIXME hardcoded
×
4236
  {
4237
    Items::Item* cont = Items::Item::create( Core::settingsManager.extobj.secure_trade_container );
×
4238
    // TODO Pos: no realm
4239
    cont->setposition( pos() );
×
4240
    trading_cont.set( static_cast<Core::UContainer*>( cont ) );
×
4241
  }
4242
}
×
4243

4244
Core::UContainer* Character::trade_container()
×
4245
{
4246
  return trading_cont.get();
×
4247
}
4248

4249
// SkillValue removed for no use - MuadDib
4250

4251
const char* Character::target_tag() const
×
4252
{
4253
  return "mobile";
×
4254
}
4255

4256
void Character::set_caps_to_default()
97✔
4257
{
4258
  for ( unsigned ai = 0; ai < Core::gamestate.numAttributes; ++ai )
5,141✔
4259
  {
4260
    Attribute* pAttr = Core::gamestate.attributes[ai];
5,044✔
4261
    AttributeValue& av = attribute( ai );
5,044✔
4262

4263
    av = attribute( ai );
5,044✔
4264
    av.cap( pAttr->default_cap );
5,044✔
4265
  }
4266
}
97✔
4267

4268
u16 Character::last_textcolor() const
×
4269
{
4270
  return _last_textcolor;
×
4271
}
4272

4273
void Character::last_textcolor( u16 new_color )
3✔
4274
{
4275
  _last_textcolor = new_color;
3✔
4276
}
3✔
4277

4278
unsigned int Character::guildid() const
2✔
4279
{
4280
  auto g = guild();
2✔
4281
  return ( g != nullptr ) ? g->guildid() : 0;
2✔
4282
}
4283

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

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

4298
  if ( client != nullptr )
2✔
4299
    send_buff_message( this, icon, true, duration, cl_name, name_arguments, cl_descr,
×
4300
                       desc_arguments );
4301
}
4✔
4302

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

4313
  if ( b == buffs_.end() )
4✔
4314
    return false;
3✔
4315

4316
  buffs_.erase( b );
1✔
4317
  if ( client != nullptr )
1✔
4318
    send_buff_message( this, icon, false );
×
4319
  return true;
1✔
4320
}
4321

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

4339
/**
4340
 * Resends all buffs (with updated duration), usually called at (re)login
4341
 * @author Bodom
4342
 */
4343
void Character::send_buffs()
2✔
4344
{
4345
  if ( client == nullptr )
2✔
4346
    return;
×
4347

4348
  for ( const auto& [icon, buf] : buffs_ )
2✔
4349
  {
4350
    u16 duration = Clib::clamp_convert<u16>( buf.end - Core::read_gameclock() );
×
4351

4352
    send_buff_message( this, icon, true, duration, buf.cl_name, buf.name_arguments, buf.cl_descr,
×
4353
                       buf.desc_arguments );
×
4354
  }
4355
}
4356

4357
u8 Character::los_size() const
62,962✔
4358
{
4359
  return client ? client->update_range() : Core::settingsManager.ssopt.default_visual_range;
62,962✔
4360
}
4361

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

4402
  size += Clib::memsize( attributes ) + Clib::memsize( vitals ) + Clib::memsize( armor_ ) +
2✔
4403
          Clib::memsize( remote_containers_ ) + Clib::memsize( opponent_of ) +
2✔
4404
          Clib::memsize( aggressor_to_ ) + Clib::memsize( lawfully_damaged_ ) +
2✔
4405
          Clib::memsize( to_be_reportable_ ) + Clib::memsize( reportable_ ) +
2✔
4406
          Clib::memsize( buffs_ );
2✔
4407
  return size;
2✔
4408
}
4409

4410
void Character::on_delete_from_account()
3✔
4411
{
4412
  if ( realm() )
3✔
4413
    realm()->remove_mobile( *this, Realms::WorldChangeReason::PlayerDeleted );
3✔
4414
}
3✔
4415

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

4442
bool Character::get_method_hook( const char* methodname, Bscript::Executor* ex,
×
4443
                                 Core::ExportScript** hook, unsigned int* PC ) const
4444
{
4445
  if ( Core::gamestate.system_hooks.get_method_hook(
×
4446
           Core::gamestate.system_hooks.character_method_script.get(), methodname, ex, hook, PC ) )
4447
    return true;
×
4448
  return base::get_method_hook( methodname, ex, hook, PC );
×
4449
}
4450

4451
void Character::update_objects_on_range_change( u8 newrange )
2✔
4452
{
4453
  Network::RemoveObjectPkt msgremove( serial_ext );
2✔
4454

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

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

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

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

4566
VitalValue::VitalValue() : _current( 0 ), _maximum( 0 ), _regenrate( 0 ) {}
291✔
4567
int VitalValue::current() const
11✔
4568
{
4569
  return _current;
11✔
4570
}
4571
int VitalValue::current_ones() const
1,330✔
4572
{
4573
  return _current / 100;
1,330✔
4574
}
4575
int VitalValue::current_thousands() const
67✔
4576
{
4577
  // use division to prevent overflow
4578
  return ( _current / 100 ) * 1000 / ( _maximum / 100 );
67✔
4579
}
4580
int VitalValue::maximum() const
228✔
4581
{
4582
  return _maximum;
228✔
4583
}
4584
int VitalValue::maximum_ones() const
807✔
4585
{
4586
  return _maximum / 100;
807✔
4587
}
4588
bool VitalValue::is_at_maximum() const
40✔
4589
{
4590
  return ( _current >= _maximum );
40✔
4591
}
4592
int VitalValue::regenrate() const
120✔
4593
{
4594
  return _regenrate;
120✔
4595
}
4596
void VitalValue::current( int cur )
309✔
4597
{
4598
  _current = cur;
309✔
4599
  if ( _current > _maximum )
309✔
4600
    _current = _maximum;
×
4601
}
309✔
4602
void VitalValue::current_ones( int ones )
78✔
4603
{
4604
  current( ones * 100 );
78✔
4605
}
78✔
4606
void VitalValue::maximum( int val )
261✔
4607
{
4608
  _maximum = val;
261✔
4609
  if ( _current > _maximum )
261✔
4610
    current( _maximum );
×
4611
}
261✔
4612
void VitalValue::regenrate( int rate )
261✔
4613
{
4614
  _regenrate = rate;
261✔
4615
}
261✔
4616
bool VitalValue::consume( unsigned int hamt )
13✔
4617
{
4618
  if ( _current > hamt )
13✔
4619
  {
4620
    _current -= hamt;
13✔
4621
    return true;
13✔
4622
  }
4623
  _current = 0;
×
4624
  return false;
×
4625
}
4626
void VitalValue::produce( unsigned int hamt )
120✔
4627
{
4628
  unsigned newcur = _current + hamt;
120✔
4629
  if ( newcur > _maximum || newcur < _current )
120✔
4630
  {
4631
    _current = _maximum;
119✔
4632
  }
4633
  else
4634
  {
4635
    _current = newcur;
1✔
4636
  }
4637
}
120✔
4638

4639
}  // namespace Mobile
4640
}  // 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