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

polserver / polserver / 24779328085

22 Apr 2026 12:52PM UTC coverage: 60.815% (+0.06%) from 60.76%
24779328085

push

github

web-flow
Don't send statmsg pkt on every equipped item when refreshing ar (#875)

* do not send full_statmsg for each equipped item while refreshing armor

* added test

* docs

12 of 15 new or added lines in 2 files covered. (80.0%)

1 existing line in 1 file now uncovered.

44538 of 73235 relevant lines covered (60.82%)

534551.11 hits per line

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

63.41
/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 )
3✔
186
{
187
  for ( unsigned short zone = 0; zone < Core::gamestate.armorzones.size(); ++zone )
3✔
188
  {
189
    for ( unsigned i = 0; i < Core::gamestate.armorzones[zone].layers.size(); ++i )
12✔
190
    {
191
      if ( Core::gamestate.armorzones[zone].layers[i] == layer )
12✔
192
        return zone;
3✔
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 )
98✔
264
    : UObject( objtype, uobj_class ),
265
      // NPC
266
      // EQUIPMENT / ITEMS
267
      weapon( Core::gamestate.wrestling_weapon ),
98✔
268
      shield( nullptr ),
98✔
269
      armor_( Core::gamestate.armorzones.size() ),
98✔
270
      wornitems( new Core::WornItemsContainer ),  // default objtype is in containr.cpp,
98✔
271
                                                  // WornItemsContainer class
272
      remote_containers_(),
98✔
273
      // MOVEMENT
274
      dir( 0 ),
98✔
275
      gradual_boost( 0 ),
98✔
276
      lastpos( 0, 0, 0, nullptr ),
98✔
277
      move_reason( OTHER ),
98✔
278
      movemode( Plib::MOVEMODE_LAND ),
98✔
279
      // COMBAT
280
      warmode_wait( 0 ),
98✔
281
      ar_( 0 ),
98✔
282
      opponent_( nullptr ),
98✔
283
      opponent_of(),
98✔
284
      swing_timer_start_clock_( 0 ),
98✔
285
      swing_task( nullptr ),
98✔
286
      // ATTRIBUTES / VITALS
287
      disable_regeneration_until( 0 ),
98✔
288
      attributes( Core::gamestate.numAttributes ),
98✔
289
      vitals( Core::gamestate.numVitals ),
98✔
290
      // REPUTATION
291
      aggressor_to_(),
98✔
292
      lawfully_damaged_(),
98✔
293
      criminal_until_( 0 ),
98✔
294
      repsys_task_( nullptr ),
98✔
295
      to_be_reportable_(),
98✔
296
      reportable_(),
98✔
297
      // GUILD
298
      // PARTY
299
      party_decline_timeout_( nullptr ),
98✔
300
      // SECURE TRADING
301
      trading_cont(),
98✔
302
      trading_with( nullptr ),
98✔
303
      // SCRIPT
304
      script_ex( nullptr ),
98✔
305
      spell_task( nullptr ),
98✔
306
      // CLIENT
307
      client( nullptr ),
98✔
308
      uclang( "enu" ),
98✔
309
      _last_textcolor( 0 ),
98✔
310
      // PRIVS SETTINGS STATUS
311
      cmdlevel_( 0 ),
98✔
312
      concealed_( 0 ),
98✔
313
      stealthsteps_( 0 ),
98✔
314
      mountedsteps_( 0 ),
98✔
315
      privs(),
98✔
316
      settings(),
98✔
317
      cached_settings(),
98✔
318
      mob_flags_(),
98✔
319
      // SERIALIZATION
320
      // CREATION
321
      created_at( 0 ),
98✔
322
      // BUFFS
323
      buffs_(),
98✔
324
      // MISC
325
      acct( nullptr ),
98✔
326
      registered_multi( 0 ),
98✔
327
      last_corpse( 0 ),
98✔
328
      trueobjtype( 0 ),
98✔
329
      truecolor( 0 ),
98✔
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 ),
98✔
333
      race( Plib::RACE_HUMAN ),
98✔
334
      position_changed_at_( 0 ),
98✔
335
      moved_at_( 0 )
98✔
336
{
337
  logged_in( true );  // so initialization scripts etc can see
98✔
338

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

343
  set_caps_to_default();
98✔
344

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

350
Character::~Character()
119✔
351
{
352
  if ( acct.get() )
98✔
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();
98✔
363

364
  if ( client && ( client->chr == this ) )
98✔
365
    client->chr = nullptr;
×
366
  client = nullptr;
98✔
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();
98✔
375

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

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

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

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

389
  --Core::stateManager.uobjcount.ucharacter_count;
98✔
390
}
119✔
391

392
void Character::removal_cleanup()
100✔
393
{
394
  clear_opponent_of();
100✔
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 )
100✔
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 )
100✔
411
    swing_task->cancel();
×
412

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

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

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

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

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

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

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

445
bool Character::has_active_client() const
50,872✔
446
{
447
  return ( client != nullptr && client->isActive() );
50,872✔
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()
75✔
472
{
473
  if ( !has_gotten_item() )
75✔
474
    return;
75✔
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()
98✔
487
{
488
  stop_skill_script();
98✔
489
  if ( registered_multi > 0 )
98✔
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();
98✔
499
}
98✔
500

501
void Character::stop_skill_script()
200✔
502
{
503
  if ( script_ex != nullptr )
200✔
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
}
200✔
528

529
///
530
/// A Mobile's weight is 10 stones + the weight of their equipment.
531
///
532
unsigned int Character::weight() const
96✔
533
{
534
  unsigned int wt = 10 + wornitems->weight();
96✔
535
  if ( has_gotten_item() )
96✔
536
    wt += gotten_item().item()->weight();
26✔
537
  if ( trading_cont.get() )
96✔
538
    wt += trading_cont->weight();
×
539
  return wt;
96✔
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 )
169✔
754
{
755
  Plib::MOVEMODE mm = Plib::MOVEMODE_NONE;
169✔
756

757
  const auto not_found = std::string::npos;
169✔
758
  if ( str.find( 'L' ) != not_found )
169✔
759
    mm = static_cast<Plib::MOVEMODE>( mm + Plib::MOVEMODE_LAND );
166✔
760
  if ( str.find( 'S' ) != not_found )
169✔
761
    mm = static_cast<Plib::MOVEMODE>( mm + Plib::MOVEMODE_SEA );
3✔
762
  if ( str.find( 'A' ) != not_found )
169✔
763
    mm = static_cast<Plib::MOVEMODE>( mm + Plib::MOVEMODE_AIR );
2✔
764
  if ( str.find( 'F' ) != not_found )
169✔
765
    mm = static_cast<Plib::MOVEMODE>( mm + Plib::MOVEMODE_FLY );
×
766
  return mm;
169✔
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 )
86✔
784
{
785
  serial = elem.remove_ulong( "SERIAL" );
86✔
786

787
  if ( Plib::systemstate.config.check_integrity )
86✔
788
  {
789
    if ( Core::system_find_mobile( serial ) )
86✔
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 );
86✔
796
  Core::UseCharSerialNumber( serial );
86✔
797

798
  std::string acctname;
86✔
799
  if ( elem.remove_prop( "ACCOUNT", &acctname ) )
86✔
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
86✔
828
  graphic = static_cast<u16>( objtype_ );
86✔
829

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

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

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

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

851
  movemode = decode_movemode( elem.remove_string( "MOVEMODE", "L" ) );
86✔
852
  concealed_ = static_cast<unsigned char>( elem.remove_ushort(
86✔
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" );
86✔
858

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

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

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

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

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

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

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

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

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

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

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

921
  privs.readfrom( elem.remove_string( "Privs", "" ) );
86✔
922
  settings.readfrom( elem.remove_string( "Settings", "" ) );
86✔
923
  refresh_cached_settings();
86✔
924
}
86✔
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,994✔
1044
{
1045
  return cached_settings.get( PRIV_FLAGS::ALL ) || settings.contains( setting );
3,994✔
1046
}
1047

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

1055
  cached_settings.reset();
190✔
1056

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

1173
  return false;
32✔
1174
}
1175

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1386
  return true;
64✔
1387
}
1388

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

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

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

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

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

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

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

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

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

1455

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

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

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

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

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

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

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

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

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

1568
  int start_ones = vv.current_ones();
264✔
1569
  int start_max = vv.maximum_ones();
264✔
1570

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

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

1579
  vv.maximum( mv );
264✔
1580

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

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

1588
  vv.regenrate( rr );
264✔
1589

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

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

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

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

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

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

1619
    Network::ClientInterface::tell_vital_changed( this, Core::gamestate.vitals[vi] );
225✔
1620
  }
1621
}
75✔
1622

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

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

1635
  return true;
×
1636
}
1637

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

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

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

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

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

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

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

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

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

1731
  return flag1;
203✔
1732
}
1733

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

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

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

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

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

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

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

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

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

1778
  send_update_hits_to_inrange( this );
×
1779

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

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

1786

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

1793
  int absorbed = blocked / 2;
×
1794

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

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

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

1810

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

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

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

1833
  return damage;
×
1834
}
1835

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

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

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

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

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

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

1869

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

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

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

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

1907

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

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

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

1929
  check_undamaged();
×
1930

1931
  send_update_hits_to_inrange( this );
×
1932
}
1933

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

1953

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

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

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

1990
  color = truecolor;
×
1991

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

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

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

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

2034
  // equip( create_backpack() );
2035

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

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

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

2072
  if ( client != nullptr )
1✔
2073
  {
2074
    if ( opponent_ )
×
2075
      opponent_->inform_disengaged( this );
×
2076

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

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

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

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

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

2099
  send_create_mobile_to_nearby_cansee( this );
1✔
2100

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

2106
void Character::clear_opponent_of()
173✔
2107
{
2108
  while ( !opponent_of.empty() )
173✔
2109
  {
2110
    Character* chr = *opponent_of.begin();
×
2111
    // note that chr->set_opponent is going to remove
2112
    // its entry from our opponent_of collection,
2113
    // so eventually this loop will exit.
2114
    chr->set_opponent( nullptr, false );
×
2115
  }
2116
}
173✔
2117

2118
void Character::die()
73✔
2119
{
2120
  if ( Core::gamestate.system_hooks.can_die )
73✔
2121
  {
2122
    if ( !Core::gamestate.system_hooks.can_die->call( make_mobileref( this ) ) )
×
2123
      return;
×
2124
  }
2125

2126
  set_current_ones( Core::gamestate.pVitalLife, vital( Core::gamestate.pVitalLife->vitalid ), 0,
73✔
2127
                    VitalDepletedReason::DEATH );
2128
  clear_my_aggressors();
73✔
2129
  clear_my_lawful_damagers();
73✔
2130
  commit_to_reportables();
73✔
2131

2132

2133
  u16 save_graphic = graphic;
73✔
2134

2135
  if ( graphic == UOBJ_HUMAN_MALE || graphic == UOBJ_HUMAN_FEMALE || graphic == UOBJ_ELF_MALE ||
73✔
2136
       graphic == UOBJ_ELF_FEMALE || graphic == UOBJ_GARGOYLE_MALE ||
×
2137
       graphic == UOBJ_GARGOYLE_FEMALE )
×
2138
  {
2139
    switch ( race )
73✔
2140
    {
2141
    case Plib::RACE_HUMAN:
73✔
2142
      graphic = ( gender == Plib::GENDER_MALE ) ? UOBJ_HUMAN_MALE_GHOST : UOBJ_HUMAN_FEMALE_GHOST;
73✔
2143
      break;
73✔
2144
    case Plib::RACE_ELF:
×
2145
      graphic = ( gender == Plib::GENDER_MALE ) ? UOBJ_ELF_MALE_GHOST : UOBJ_ELF_FEMALE_GHOST;
×
2146
      break;
×
2147
    case Plib::RACE_GARGOYLE:
×
2148
      graphic =
×
2149
          ( gender == Plib::GENDER_MALE ) ? UOBJ_GARGOYLE_MALE_GHOST : UOBJ_GARGOYLE_FEMALE_GHOST;
×
2150
      break;
×
2151
    }
2152
  }
2153
  DECLARE_CHECKPOINT;
146✔
2154

2155
  mob_flags_.set( MOB_FLAGS::DEAD );
73✔
2156
  mob_flags_.remove( MOB_FLAGS::WARMODE );
73✔
2157
  mob_flags_.remove( MOB_FLAGS::FROZEN );
73✔
2158
  mob_flags_.remove( MOB_FLAGS::PARALYZED );
73✔
2159

2160
  UPDATE_CHECKPOINT();
73✔
2161
  /* FIXME: corpse container difficulties.
2162

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

2175
     The current solution is to put the backpack
2176
     on the corpse.
2177
     */
2178

2179
  Core::UCorpse* corpse = static_cast<Core::UCorpse*>( Items::Item::create( UOBJ_CORPSE ) );
73✔
2180
  this->last_corpse = corpse->serial;
73✔
2181

2182
  corpse->ownerserial = this->serial;
73✔
2183
  corpse->setname( "A corpse of " + name_.get() );
73✔
2184
  corpse->take_contents_to_grave( acct == nullptr );
73✔
2185

2186
  UPDATE_CHECKPOINT();
73✔
2187

2188
  corpse->color = truecolor;
73✔
2189
  corpse->setposition( pos() );
73✔
2190
  corpse->facing = facing;
73✔
2191
  corpse->corpsetype = save_graphic;
73✔
2192
  // corpse->dir = dir;
2193
  UPDATE_CHECKPOINT();
73✔
2194

2195
  register_with_supporting_multi( corpse );
73✔
2196
  if ( is_trading() )
73✔
2197
    Core::cancel_trade( this );
×
2198
  clear_gotten_item();
73✔
2199
  corpse->copyprops( *this );
73✔
2200
  UPDATE_CHECKPOINT();
73✔
2201

2202
  // Change the character's color to grey
2203
  color = 0;
73✔
2204
  UPDATE_CHECKPOINT();
73✔
2205

2206
  // Keep a list of copied items to set movable = false _after_ the corpse has
2207
  // been sent, since changing the movable property does an
2208
  // `send_put_in_container_to_inrange`. The client would ignore this item, as
2209
  // the container does not exist yet.
2210
  std::list<Items::Item*> copied_items;
146✔
2211

2212
  // small lambdas to reduce the mess inside the loops
2213
  auto _copy_item = [&]( Items::Item* _item ) {  // copy a item into the corpse
2✔
2214
    Items::Item* copy = _item->clone();
2✔
2215
    copied_items.push_back( copy );
2✔
2216
    corpse->equip_and_add( copy, copy->layer );
2✔
2217
  };
2✔
2218
  auto _drop_item_to_world = [&]( Items::Item* _item ) {  // places the item onto the corpse coords
×
2219
    _item->setposition( corpse->pos() );
×
2220
    add_item_to_world( _item );
×
2221
    register_with_supporting_multi( _item );
×
2222
    move_item( _item, corpse->pos() );
×
2223
  };
73✔
2224

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

2252
    if ( item->layer == Core::LAYER_MOUNT &&
2✔
2253
         item->objtype_ == Core::settingsManager.extobj.boatmount )
×
2254
    {
2255
      Multi::UMulti* multi = realm()->find_supporting_multi( pos3d() );
×
2256

2257
      // Clear the pilot from the boat
2258
      if ( multi != nullptr && multi->script_isa( Core::POLCLASS_BOAT ) )
×
2259
      {
2260
        Multi::UBoat* boat = static_cast<Multi::UBoat*>( multi );
×
2261
        if ( boat->pilot() == this )
×
2262
        {
2263
          boat->clear_pilot();
×
2264
          continue;
×
2265
        }
2266
      }
2267

2268
      // If for some reason there was a mismatch between chr multi and boat pilot, just destroy the
2269
      // boatmount. Destroying the boatmount item will leave an orphaned itemref on the boat multi.
2270
      // The itemref will be re-set on next set_pilot call, as an orphaned boatmount behaves as if
2271
      // there is no boatmount at all.
2272
      destroy_item( item );
×
2273
      continue;
×
2274
    }
×
2275

2276
    ///
2277
    /// Unequip scripts aren't honored when moving a dead mobile's equipment
2278
    /// onto a corpse if honor_unequip_script_on_death is disabled.
2279
    ///
2280
    UPDATE_CHECKPOINT();
2✔
2281
    if ( Core::settingsManager.ssopt.honor_unequip_script_on_death )
2✔
2282
    {
2283
      if ( !item->check_unequiptest_scripts() )
×
2284
        continue;
×
2285
      if ( !item->check_unequip_script() )
×
2286
        continue;
×
2287
    }
2288
    else
2289
    {
2290
      item->check_unequip_script();
2✔
2291
    }
2292
    UPDATE_CHECKPOINT();
2✔
2293
    unequip( item );
2✔
2294
    UPDATE_CHECKPOINT();
2✔
2295

2296
    u8 newSlot = 1;
2✔
2297
    if ( !corpse->can_add_to_slot( newSlot ) || !item->slot_index( newSlot ) )
2✔
2298
    {
2299
      _drop_item_to_world( item );
×
2300
    }
2301
    else
2302
    {
2303
      corpse->equip_and_add( item, layer );
2✔
2304
    }
2305
    UPDATE_CHECKPOINT();
2✔
2306
  }
2307
  UPDATE_CHECKPOINT();
73✔
2308

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

2362
    UPDATE_CHECKPOINT();
2✔
2363

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

2410

2411
  UPDATE_CHECKPOINT();
73✔
2412
  send_death_message( this, corpse );
73✔
2413

2414
  UPDATE_CHECKPOINT();
73✔
2415
  corpse->restart_decay_timer();
73✔
2416
  UPDATE_CHECKPOINT();
73✔
2417
  add_item_to_world( corpse );
73✔
2418
  UPDATE_CHECKPOINT();
73✔
2419
  send_item_to_inrange( corpse );
73✔
2420
  UPDATE_CHECKPOINT();
73✔
2421
  // Set the items to unmovable, now that the client knows about the corpse container.
2422
  for ( auto* copied_item : copied_items )
75✔
2423
  {
2424
    copied_item->movable( false );
2✔
2425
  }
2426

2427
  UPDATE_CHECKPOINT();
73✔
2428

2429
  clear_opponent_of();
73✔
2430

2431
  set_opponent( nullptr );
73✔
2432

2433
  UPDATE_CHECKPOINT();
73✔
2434

2435
  on_death( corpse );
73✔
2436

2437
  CLEAR_CHECKPOINT();
73✔
2438
}
2439

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

2452
  for ( unsigned layer = Core::LAYER_EQUIP__LOWEST; layer <= Core::LAYER_EQUIP__HIGHEST; ++layer )
4,264✔
2453
  {
2454
    Items::Item* item = wornitems->GetItemOnLayer( layer );
4,100✔
2455
    if ( !item )
4,100✔
2456
      continue;
3,943✔
2457
    // Let's check all items as base, and handle their element_resists.
2458
    updateEquipableProperties( item );
157✔
2459
    if ( item->isa( Core::UOBJ_CLASS::CLASS_ARMOR ) )
157✔
2460
    {
2461
      auto* armor = static_cast<Items::UArmor*>( item );
1✔
2462
      for ( auto zone : armor->tmplzones() )
2✔
2463
      {
2464
        if ( !armor_[zone] || armor->ar() > armor_[zone]->ar() )
1✔
2465
          armor_[zone] = armor;
1✔
2466
      }
2467
    }
2468
  }
2469

2470
  //  calculate_ar();  <-- MuadDib Commented out, mixed code within ported find_armor to reduce
2471
  // iter.
2472
  double new_ar = 0.0;
164✔
2473
  for ( unsigned zone = 0; zone < Core::gamestate.armorzones.size(); ++zone )
1,148✔
2474
  {
2475
    Items::UArmor* armor = armor_[zone];
984✔
2476
    if ( armor )
984✔
2477
    {
2478
      new_ar += armor->ar() * Core::gamestate.armorzones[zone].chance;
1✔
2479
    }
2480
  }
2481

2482
  /* add AR due to shield : parry skill / 2 is percent of AR */
2483
  // FIXME: Should we allow this to be adjustable via a prop? Hrmmmmm
2484
  if ( shield )
164✔
2485
  {
NEW
2486
    new_ar += std::max(
×
NEW
2487
        1.0,  //
×
NEW
2488
        0.5 * 0.01 * shield->ar() * attribute( Core::gamestate.pAttrParry->attrid ).effective() );
×
2489
  }
2490

2491
  new_ar += ar_mod();
164✔
2492

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

2495
  if ( client )
164✔
2496
  {  // CHECKME consider sending less frequently
2497
    send_full_statmsg( client, this );
6✔
2498
  }
2499
}
164✔
2500

2501
void Character::updateEquipableProperties( Items::Item* item )
157✔
2502
{
2503
  // calc caps
2504
  if ( item->has_defence_increase_cap() )
157✔
2505
    defence_increase_cap( defence_increase_cap().addToValue( item->defence_increase_cap() ) );
36✔
2506
  // calc resist caps
2507
  if ( item->has_fire_resist_cap() )
157✔
2508
    fire_resist_cap( fire_resist_cap().addToValue( item->fire_resist_cap() ) );
21✔
2509
  if ( item->has_cold_resist_cap() )
157✔
2510
    cold_resist_cap( cold_resist_cap().addToValue( item->cold_resist_cap() ) );
29✔
2511
  if ( item->has_energy_resist_cap() )
157✔
2512
    energy_resist_cap( energy_resist_cap().addToValue( item->energy_resist_cap() ) );
25✔
2513
  if ( item->has_poison_resist_cap() )
157✔
2514
    poison_resist_cap( poison_resist_cap().addToValue( item->poison_resist_cap() ) );
13✔
2515
  if ( item->has_physical_resist_cap() )
157✔
2516
    physical_resist_cap( physical_resist_cap().addToValue( item->physical_resist_cap() ) );
17✔
2517
  // calc resists
2518
  if ( item->has_fire_resist() )
157✔
2519
    fire_resist( fire_resist().addToValue( item->fire_resist() ) );
9✔
2520
  if ( item->has_cold_resist() )
157✔
2521
    cold_resist( cold_resist().addToValue( item->cold_resist() ) );
11✔
2522
  if ( item->has_energy_resist() )
157✔
2523
    energy_resist( energy_resist().addToValue( item->energy_resist() ) );
10✔
2524
  if ( item->has_poison_resist() )
157✔
2525
    poison_resist( poison_resist().addToValue( item->poison_resist() ) );
7✔
2526
  if ( item->has_physical_resist() )
157✔
2527
    physical_resist( physical_resist().addToValue( item->physical_resist() ) );
8✔
2528

2529
  // calc damages
2530
  if ( item->has_fire_damage() )
157✔
2531
    fire_damage( fire_damage().addToValue( item->fire_damage() ) );
88✔
2532
  if ( item->has_cold_damage() )
157✔
2533
    cold_damage( cold_damage().addToValue( item->cold_damage() ) );
90✔
2534
  if ( item->has_energy_damage() )
157✔
2535
    energy_damage( energy_damage().addToValue( item->energy_damage() ) );
89✔
2536
  if ( item->has_poison_damage() )
157✔
2537
    poison_damage( poison_damage().addToValue( item->poison_damage() ) );
86✔
2538
  if ( item->has_physical_damage() )
157✔
2539
    physical_damage( physical_damage().addToValue( item->physical_damage() ) );
87✔
2540

2541
  // calc others
2542
  if ( item->has_lower_reagent_cost() )
157✔
2543
    lower_reagent_cost( lower_reagent_cost().addToValue( item->lower_reagent_cost() ) );
55✔
2544
  if ( item->has_spell_damage_increase() )
157✔
2545
    spell_damage_increase( spell_damage_increase().addToValue( item->spell_damage_increase() ) );
53✔
2546
  if ( item->has_faster_casting() )
157✔
2547
    faster_casting( faster_casting().addToValue( item->faster_casting() ) );
58✔
2548
  if ( item->has_faster_cast_recovery() )
157✔
2549
    faster_cast_recovery( faster_cast_recovery().addToValue( item->faster_cast_recovery() ) );
59✔
2550
  if ( item->has_lower_mana_cost() )
157✔
2551
    lower_mana_cost( lower_mana_cost().addToValue( item->lower_mana_cost() ) );
56✔
2552
  if ( item->has_hit_chance() )
157✔
2553
    hit_chance( hit_chance().addToValue( item->hit_chance() ) );
57✔
2554
  if ( item->has_luck() )
157✔
2555
    luck( luck().addToValue( item->luck() ) );
54✔
2556
  if ( item->has_swing_speed_increase() )
157✔
2557
    swing_speed_increase( swing_speed_increase().addToValue( item->swing_speed_increase() ) );
52✔
2558
  if ( item->has_min_attack_range_increase() )
157✔
2559
    min_attack_range_increase(
102✔
2560
        min_attack_range_increase().addToValue( item->min_attack_range_increase() ) );
51✔
2561
  if ( item->has_max_attack_range_increase() )
157✔
2562
    max_attack_range_increase(
100✔
2563
        max_attack_range_increase().addToValue( item->max_attack_range_increase() ) );
50✔
2564

2565
  // calc defence increase if lower than cap
2566
  if ( item->has_defence_increase() )
157✔
2567
    defence_increase( defence_increase().addToValue( item->defence_increase() ) );
34✔
2568
}
157✔
2569

2570
void Character::resetEquipableProperties()
164✔
2571
{
2572
  // reset resists
2573
  if ( has_fire_resist() )
164✔
2574
    fire_resist( fire_resist().setAsValue( 0 ) );
13✔
2575
  if ( has_cold_resist() )
164✔
2576
    cold_resist( cold_resist().setAsValue( 0 ) );
15✔
2577
  if ( has_energy_resist() )
164✔
2578
    energy_resist( energy_resist().setAsValue( 0 ) );
14✔
2579
  if ( has_poison_resist() )
164✔
2580
    poison_resist( poison_resist().setAsValue( 0 ) );
11✔
2581
  if ( has_physical_resist() )
164✔
2582
    physical_resist( physical_resist().setAsValue( 0 ) );
12✔
2583

2584
  // reset caps
2585
  if ( has_fire_resist_cap() )
164✔
2586
    fire_resist_cap( fire_resist_cap().setAsValue( 0 ) );
27✔
2587
  if ( has_cold_resist_cap() )
164✔
2588
    cold_resist_cap( cold_resist_cap().setAsValue( 0 ) );
35✔
2589
  if ( has_energy_resist_cap() )
164✔
2590
    energy_resist_cap( energy_resist_cap().setAsValue( 0 ) );
31✔
2591
  if ( has_poison_resist_cap() )
164✔
2592
    poison_resist_cap( poison_resist_cap().setAsValue( 0 ) );
19✔
2593
  if ( has_physical_resist_cap() )
164✔
2594
    physical_resist_cap( physical_resist_cap().setAsValue( 0 ) );
23✔
2595

2596
  // reset damages
2597
  if ( has_fire_damage() )
164✔
2598
    fire_damage( fire_damage().setAsValue( 0 ) );
95✔
2599
  if ( has_cold_damage() )
164✔
2600
    cold_damage( cold_damage().setAsValue( 0 ) );
97✔
2601
  if ( has_energy_damage() )
164✔
2602
    energy_damage( energy_damage().setAsValue( 0 ) );
96✔
2603
  if ( has_poison_damage() )
164✔
2604
    poison_damage( poison_damage().setAsValue( 0 ) );
93✔
2605
  if ( has_physical_damage() )
164✔
2606
    physical_damage( physical_damage().setAsValue( 0 ) );
94✔
2607

2608
  // reset others
2609
  if ( has_lower_reagent_cost() )
164✔
2610
    lower_reagent_cost( lower_reagent_cost().setAsValue( 0 ) );
61✔
2611
  if ( has_spell_damage_increase() )
164✔
2612
    spell_damage_increase( spell_damage_increase().setAsValue( 0 ) );
59✔
2613
  if ( has_faster_casting() )
164✔
2614
    faster_casting( faster_casting().setAsValue( 0 ) );
64✔
2615
  if ( has_faster_cast_recovery() )
164✔
2616
    faster_cast_recovery( faster_cast_recovery().setAsValue( 0 ) );
65✔
2617

2618
  if ( has_defence_increase() )
164✔
2619
    defence_increase( defence_increase().setAsValue( 0 ) );
39✔
2620
  if ( has_defence_increase_cap() )
164✔
2621
    defence_increase_cap( defence_increase_cap().setAsValue( 0 ) );
43✔
2622
  if ( has_lower_mana_cost() )
164✔
2623
    lower_mana_cost( lower_mana_cost().setAsValue( 0 ) );
62✔
2624
  if ( has_hit_chance() )
164✔
2625
    hit_chance( hit_chance().setAsValue( 0 ) );
63✔
2626
  if ( has_luck() )
164✔
2627
    luck( luck().setAsValue( 0 ) );
60✔
2628
  if ( has_swing_speed_increase() )
164✔
2629
    swing_speed_increase( swing_speed_increase().setAsValue( 0 ) );
61✔
2630
  if ( has_min_attack_range_increase() )
164✔
2631
    min_attack_range_increase( min_attack_range_increase().setAsValue( 0 ) );
57✔
2632
  if ( has_max_attack_range_increase() )
164✔
2633
    max_attack_range_increase( max_attack_range_increase().setAsValue( 0 ) );
57✔
2634
}
164✔
2635

2636
void Character::showarmor() const
×
2637
{
2638
  if ( client != nullptr )
×
2639
  {
2640
    Core::send_sysmessage( client, "Your armor coverage:" );
×
2641
    for ( unsigned i = 0; i < armor_.size(); ++i )
×
2642
    {
2643
      std::string text = Core::gamestate.armorzones[i].name + ": ";
×
2644
      if ( armor_[i] == nullptr )
×
2645
        text += "Nothing";
×
2646
      else
2647
        text += armor_[i]->name();
×
2648
      Core::send_sysmessage( client, text );
×
2649
    }
×
2650
  }
2651
}
×
2652

2653
/* check skill: test skill, advance, reset atrophy timers, blah blah..
2654
   obviously, needs work, and more parameters.
2655
   */
2656

2657
bool Character::check_skill( Core::USKILLID skillid, int difficulty, unsigned short pointvalue )
×
2658
{
2659
  INC_PROFILEVAR( skill_checks );
×
2660
  static bool in_here = false;
2661
  if ( !in_here && Core::gamestate.system_hooks.check_skill_hook )
×
2662
  {
2663
    in_here = true;
×
2664
    bool res = Core::gamestate.system_hooks.check_skill_hook->call(
×
2665
        new Module::ECharacterRefObjImp( this ), new Bscript::BLong( skillid ),
×
2666
        new Bscript::BLong( difficulty ), new Bscript::BLong( pointvalue ) );
×
2667
    in_here = false;
×
2668
    return res;
×
2669
  }
2670

2671
  return false;
×
2672
}
2673

2674
void Character::check_concealment_level()
×
2675
{
2676
  if ( concealed() > cmdlevel() )
×
2677
    concealed( cmdlevel() );
×
2678
}
×
2679

2680
// you can only be concealed from
2681
// those of lower stature
2682
bool Character::is_concealed_from_me( const Character* chr ) const
187✔
2683
{
2684
  return ( chr->concealed() > cmdlevel() );
187✔
2685
}
2686

2687
bool Character::is_visible_to_me( const Character* chr, bool check_range ) const
316✔
2688
{
2689
  if ( chr == nullptr )
316✔
2690
    return false;
×
2691
  if ( chr == this )
316✔
2692
    return true;  // I can always see myself (?)
145✔
2693
  if ( is_concealed_from_me( chr ) )
171✔
2694
    return false;
×
2695
  if ( !chr->logged_in() )
171✔
2696
    return false;
×
2697
  if ( chr->hidden() && !cached_settings.get( PRIV_FLAGS::SEE_HIDDEN ) )
171✔
2698
    return false;  // noone can see anyone hidden.
×
2699
  if ( check_range )
171✔
2700
  {
2701
    if ( !in_visual_range( chr ) )
148✔
2702
      return false;
52✔
2703
  }
2704
  else if ( chr->realm() != this->realm() )
23✔
2705
    return false;
1✔
2706

2707
  if ( dead() )
118✔
2708
    return true;  // If I'm dead, I can see anything
×
2709
  if ( !chr->dead() || cached_settings.get( PRIV_FLAGS::SEE_GHOSTS ) )
118✔
2710
    return true;  // Anyone can see the living
118✔
2711
  if ( chr->warmode() )
×
2712
    return true;  // Anyone can see someone in warmode
×
2713
  return false;
×
2714
};
2715

2716
// NOTE: chr is at new position, lastpos have old position.
2717
void PropagateMove( /*Client *client,*/ Character* chr )
164✔
2718
{
2719
  using namespace Network;
2720
  if ( chr == nullptr )
164✔
2721
    return;
×
2722
  RemoveObjectPkt msgremove( chr->serial_ext );
164✔
2723
  HealthBarStatusUpdate msgpoison( chr->serial_ext, HealthBarStatusUpdate::Color::GREEN,
2724
                                   chr->poisoned() );
164✔
2725
  HealthBarStatusUpdate msginvul( chr->serial_ext, HealthBarStatusUpdate::Color::YELLOW,
2726
                                  chr->invul() );
164✔
2727
  PktHelper::PacketOut<PktOut_78> msgcreate;
164✔
2728
  MoveChrPkt msgmove( chr );
164✔
2729
  build_owncreate( chr, msgcreate.Get() );
164✔
2730

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

2753
            if ( chr->invul() )  // if invul send 0x17 for newer clients
×
2754
              msginvul.Send( client );
×
2755
            return;
×
2756
          }
2757

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

2790
  // iter over all old in range players and send remove
2791
  Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
164✔
2792
      chr->lastpos,
164✔
2793
      [&]( Character* zonechr )
164✔
2794
      {
2795
        Client* client = zonechr->client;
123✔
2796
        if ( !zonechr->in_visual_range( nullptr, chr->lastpos ) )
123✔
2797
          return;
32✔
2798
        if ( !zonechr->is_visible_to_me( chr, /*check_range*/ false ) )
91✔
2799
          return;
1✔
2800

2801
        if ( zonechr->in_visual_range( chr ) )  // already handled
90✔
2802
          return;
81✔
2803
        // if we just walked out of range of this character, send its
2804
        // client a remove object, or else a ghost character will remain.
2805
        send_remove_character( client, chr, msgremove );
9✔
2806
      } );
2807
}
164✔
2808

2809
void Character::swing_task_func( Character* chr )
11✔
2810
{
2811
  THREAD_CHECKPOINT( tasks, 800 );
11✔
2812
  INFO_PRINTLN_TRACE( 20 )( "swing_task_func({:#x})", chr->serial );
11✔
2813
  chr->mob_flags_.set( MOB_FLAGS::READY_TO_SWING );
11✔
2814
  chr->check_attack_after_move( false );
11✔
2815
  THREAD_CHECKPOINT( tasks, 899 );
11✔
2816
}
11✔
2817

2818
void Character::schedule_attack()
25✔
2819
{
2820
  INFO_PRINTLN_TRACE( 18 )( "schedule_attack({:#x})", this->serial );
25✔
2821
  // we'll get here with a swing_task already set, if
2822
  // while in an adjacent cell to your opponent, you turn/move
2823
  // while waiting for your timeout.
2824
  if ( swing_task == nullptr )
25✔
2825
  {
2826
    unsigned int weapon_speed = weapon->speed();
17✔
2827
    unsigned int weapon_delay = weapon->delay();
17✔
2828
    Core::polclock_t clocks;
2829

2830
    if ( !weapon_delay )
17✔
2831
    {
2832
      INFO_PRINTLN_TRACE( 19 )
17✔
2833
      ( "clocks[speed] = ({}*15000)/(({}+100)*{})", Core::POLCLOCKS_PER_SEC, dexterity(),
×
2834
        weapon_speed );
2835

2836
      clocks = Core::POLCLOCKS_PER_SEC * static_cast<Core::polclock_t>( 15000L );
17✔
2837
      clocks /= ( dexterity() + 100 ) * weapon_speed;
17✔
2838
    }
2839
    else
2840
    {
2841
      int delay_sum = weapon_delay + delay_mod();
×
2842
      if ( delay_sum < 0 )
×
2843
        delay_sum = 0;
×
2844

2845
      INFO_PRINTLN_TRACE( 19 )
×
2846
      ( "clocks[delay] = (({}+{}={})*{})/1000", weapon_delay, delay_mod(), delay_sum,
×
2847
        Core::POLCLOCKS_PER_SEC );
2848

2849
      clocks = ( delay_sum * Core::POLCLOCKS_PER_SEC ) / 1000;
×
2850
    }
2851
    // Swing speed modifier can't be less than -1 otherwise it would give a negitive swing speed and
2852
    // thats not possible.
2853
    double speed_modifier = ( swing_speed_increase().sum() / 100.0 );
17✔
2854
    if ( speed_modifier < -0.99 )
17✔
2855
      speed_modifier = -0.99;
×
2856
    clocks = static_cast<Core::polclock_t>( round( clocks / ( 1 + speed_modifier ) ) );
17✔
2857

2858
    if ( clocks < ( Core::POLCLOCKS_PER_SEC / 5 ) )
17✔
2859
    {
2860
      INFO_PRINTLN_TRACE( 20 )( "{} attack timer: {}", name(), clocks );
6✔
2861
    }
2862
    INFO_PRINTLN_TRACE( 19 )( "clocks={}", clocks );
17✔
2863

2864
    new Core::OneShotTaskInst<Character*>( &swing_task, swing_timer_start_clock_ + clocks + 1,
17✔
2865
                                           swing_task_func, this );
17✔
2866
  }
2867
}
25✔
2868

2869
void Character::reset_swing_timer()
104✔
2870
{
2871
  INFO_PRINTLN_TRACE( 15 )( "reset_swing_timer({:#x})", this->serial );
104✔
2872
  mob_flags_.remove( MOB_FLAGS::READY_TO_SWING );
104✔
2873

2874
  swing_timer_start_clock_ = Core::polclock();
104✔
2875
  if ( swing_task )
104✔
2876
    swing_task->cancel();
6✔
2877

2878
  if ( opponent_ || !opponent_of.empty() )
104✔
2879
  {
2880
    schedule_attack();
13✔
2881
  }
2882
}
104✔
2883

2884
bool Character::manual_set_swing_timer( Core::polclock_t clocks )
×
2885
{
2886
  INFO_PRINTLN_TRACE( 15 )
×
2887
  ( "manual_set_swing_timer({:#x}) delay: {}", this->serial, clocks );
×
2888
  mob_flags_.remove( MOB_FLAGS::READY_TO_SWING );
×
2889

2890
  swing_timer_start_clock_ = Core::polclock();
×
2891
  if ( swing_task )
×
2892
    swing_task->cancel();
×
2893

2894
  if ( opponent_ || !opponent_of.empty() )
×
2895
  {
2896
    new Core::OneShotTaskInst<Character*>( &swing_task, swing_timer_start_clock_ + clocks + 1,
×
2897
                                           swing_task_func, this );
×
2898
    return true;
×
2899
  }
2900
  return false;
×
2901
}
2902

2903
/* The highlighted character is:
2904
     Your selected_opponent, if you have one.
2905
     If not, then the first char that has you as their opponent.
2906
     Or, noone.
2907
     */
2908
Character* Character::get_opponent() const
10✔
2909
{
2910
  if ( opponent_ != nullptr )
10✔
2911
    return opponent_;
×
2912
  if ( !opponent_of.empty() )
10✔
2913
    return *opponent_of.begin();
×
2914
  return nullptr;
10✔
2915
}
2916

2917
bool Character::is_attackable( Character* who ) const
23✔
2918
{
2919
  passert( who != nullptr );
23✔
2920
  if ( Core::settingsManager.combat_config.scripted_attack_checks )
23✔
2921
  {
2922
    INFO_PRINTLN_TRACE( 21 )
×
2923
    ( "is_attackable({:#x},{:#x}): will be handled by combat hook.", this->serial, who->serial );
×
2924
    return true;
×
2925
  }
2926

2927
  INFO_PRINTLN_TRACE( 21 )
23✔
2928
  ( "is_attackable({:#x},{:#x}):\n"
×
2929
    "  who->dead:  {}\n"
2930
    "  wpn->inrange: {}\n"
2931
    "  hidden:     {}\n"
2932
    "  who->hidden:  {}\n"
2933
    "  concealed:  {}",
2934
    this->serial, who->serial, who->dead(), weapon->in_range( this, who ), hidden(), who->hidden(),
×
2935
    is_concealed_from_me( who ) );
×
2936
  if ( who->dead() )
23✔
2937
    return false;
×
2938
  if ( !weapon->in_range( this, who ) )
23✔
2939
    return false;
3✔
2940
  if ( hidden() && !cached_settings.get( PRIV_FLAGS::HIDDEN_ATTACK ) )
20✔
2941
    return false;
2✔
2942
  if ( who->hidden() && !cached_settings.get( PRIV_FLAGS::ATTACK_HIDDEN ) )
18✔
2943
    return false;
2✔
2944
  if ( is_concealed_from_me( who ) )
16✔
2945
    return false;
×
2946
  if ( !realm()->has_los( *this, *who ) )
16✔
2947
    return false;
×
2948
  return true;
16✔
2949
}
2950

2951
Character* Character::get_attackable_opponent() const
184✔
2952
{
2953
  if ( opponent_ != nullptr )
184✔
2954
  {
2955
    INFO_PRINTLN_TRACE( 20 )
12✔
2956
    ( "get_attackable_opponent({:#x}): checking opponent {:#x}", this->serial, opponent_->serial );
×
2957
    if ( is_attackable( opponent_ ) )
12✔
2958
      return opponent_;
9✔
2959
  }
2960

2961
  if ( !opponent_of.empty() )
175✔
2962
  {
2963
    for ( auto& who : opponent_of )
15✔
2964
    {
2965
      INFO_PRINTLN_TRACE( 20 )
11✔
2966
      ( "get_attackable_opponent({:#x}): checking opponent_of {:#x}", this->serial, who->serial );
×
2967
      if ( is_attackable( who ) )
11✔
2968
        return who;
7✔
2969
    }
2970
  }
2971

2972
  return nullptr;
168✔
2973
}
2974

2975
void Character::send_highlight() const
90✔
2976
{
2977
  if ( client != nullptr && has_active_client() )
90✔
2978
  {
2979
    Character* opponent = get_opponent();
4✔
2980

2981
    Network::PktHelper::PacketOut<Network::PktOut_AA> msg;
4✔
2982
    if ( opponent != nullptr )
4✔
2983
      msg->Write<u32>( opponent->serial_ext );
×
2984
    else
2985
      msg->offset += 4;
4✔
2986
    msg.Send( client );
4✔
2987
  }
4✔
2988
}
90✔
2989

2990
void Character::on_swing_failure( Character* /*attacker*/ )
×
2991
{
2992
  // do nothing
2993
}
×
2994

2995
void Character::inform_disengaged( Character* /*disengaged*/ )
×
2996
{
2997
  // someone has just disengaged. If we don't have an explicit opponent,
2998
  // pick one of those that has us targetted as the highlight character.
2999
  if ( opponent_ == nullptr )
×
3000
    send_highlight();
×
3001
}
×
3002

3003
void Character::inform_engaged( Character* /*engaged*/ )
1✔
3004
{
3005
  // someone has targetted us.  If we don't have an explicit opponent,
3006
  // pick one of those that has us targetted as the highlight character.
3007
  if ( opponent_ == nullptr )
1✔
3008
    send_highlight();
1✔
3009
}
1✔
3010

3011
void Character::inform_criminal( Character* /*moved*/ )
×
3012
{
3013
  // virtual that does nothing at character level, but fires event for NPCs
3014
}
×
3015

3016
void Character::inform_leftarea( Character* /*wholeft*/ )
1✔
3017
{
3018
  // virtual that does nothing at character level, but fires event for NPCs
3019
}
1✔
3020

3021
void Character::inform_enteredarea( Character* /*whoentered*/ )
22✔
3022
{
3023
  // virtual that does nothing at character level, but fires event for NPCs
3024
}
22✔
3025

3026
void Character::inform_moved( Character* /*moved*/ )
25✔
3027
{
3028
  // consider moving PropagateMove here!
3029
}
25✔
3030
void Character::inform_imoved( Character* /*chr*/ ) {}
29✔
3031

3032
void Character::set_opponent( Character* new_opponent, bool inform_old_opponent )
87✔
3033
{
3034
  INFO_PRINTLN_TRACE( 12 )
87✔
3035
  ( "set_opponent({:#x},{:#x})", this->serial, new_opponent != nullptr ? new_opponent->serial : 0 );
×
3036
  if ( new_opponent != nullptr )
87✔
3037
  {
3038
    if ( new_opponent->dead() )
4✔
3039
      return;
×
3040

3041
    if ( !warmode() && ( script_isa( Core::POLCLASS_NPC ) || has_active_client() ) )
4✔
3042
      set_warmode( true );
1✔
3043
  }
3044

3045
  if ( opponent_ != nullptr )
87✔
3046
  {
3047
    opponent_->opponent_of.erase( this );
4✔
3048
    // Turley 05/26/09 no need to send disengaged event on shutdown
3049
    if ( !Clib::exit_signalled )
4✔
3050
    {
3051
      if ( inform_old_opponent && opponent_ != nullptr )
4✔
3052
        opponent_->inform_disengaged( this );
4✔
3053
    }
3054
  }
3055

3056
  opponent_ = new_opponent;
87✔
3057

3058

3059
  // Turley 05/26/09 possible shutdown crashfix during cleanup
3060
  // (inside schedule_attack() the rest is also senseless on shutdowncleanup)
3061
  if ( !Clib::exit_signalled )
87✔
3062
  {
3063
    reset_swing_timer();
87✔
3064

3065
    if ( opponent_ != nullptr )
87✔
3066
    {
3067
      repsys_on_attack( opponent_ );
4✔
3068
      if ( opponent_->get_opponent() == nullptr )
4✔
3069
        opponent_->reset_swing_timer();
4✔
3070

3071
      opponent_->opponent_of.insert( this );
4✔
3072

3073
      opponent_->inform_engaged( this );
4✔
3074

3075
      opponent_->schedule_attack();
4✔
3076
    }
3077

3078
    send_highlight();
87✔
3079
  }
3080
}
3081

3082
void Character::select_opponent( u32 opp_serial )
×
3083
{
3084
  // test for setting to same so swing timer doesn't reset
3085
  // if you double-click the same guy over and over
3086
  if ( opponent_ == nullptr || opponent_->serial != opp_serial )
×
3087
  {
3088
    Character* new_opponent = Core::find_character( opp_serial );
×
3089
    if ( new_opponent != nullptr )
×
3090
    {
3091
      if ( realm() != new_opponent->realm() )
×
3092
        return;
×
3093
      set_opponent( new_opponent );
×
3094
    }
3095
  }
3096
}
3097

3098
void Character::disable_regeneration_for( int seconds )
4✔
3099
{
3100
  time_t new_disable_time = Core::poltime() + seconds;
4✔
3101
  if ( new_disable_time > disable_regeneration_until )
4✔
3102
    disable_regeneration_until = new_disable_time;
4✔
3103
}
4✔
3104

3105
bool Character::warmode() const
263✔
3106
{
3107
  return mob_flags_.get( MOB_FLAGS::WARMODE );
263✔
3108
}
3109

3110
void Character::set_warmode( bool i_warmode )
4✔
3111
{
3112
  if ( Core::gamestate.system_hooks.warmode_change )
4✔
3113
    Core::gamestate.system_hooks.warmode_change->call( new Module::ECharacterRefObjImp( this ),
×
3114
                                                       new Bscript::BLong( i_warmode ) );
×
3115

3116
  if ( warmode() != i_warmode )
4✔
3117
  {
3118
    disable_regeneration_for( 2 );
4✔
3119
  }
3120

3121
  mob_flags_.change( MOB_FLAGS::WARMODE, i_warmode );
4✔
3122
  if ( i_warmode == false )
4✔
3123
  {
3124
    set_opponent( nullptr );
1✔
3125
  }
3126
  reset_swing_timer();
4✔
3127

3128
  if ( dead() )
4✔
3129
  {
3130
    if ( warmode() )  // if entered warmode, display me now
×
3131
    {
3132
      send_create_mobile_to_nearby_cansee( this );
×
3133
    }
3134
    else  // if leaving warmode, I go invisible.
3135
    {
3136
      send_remove_character_to_nearby_cantsee( this );
×
3137
    }
3138
  }
3139
  else
3140
  {
3141
    Network::MoveChrPkt msgmove( this );
4✔
3142
    Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
4✔
3143
        this,
3144
        [&]( Character* chr )
4✔
3145
        {
3146
          if ( chr == this )
4✔
3147
            return;
2✔
3148
          if ( !chr->is_visible_to_me( this ) )
2✔
3149
            return;
×
3150

3151
          msgmove.Send( chr->client );
2✔
3152
        } );
3153
  }
4✔
3154
}
4✔
3155

3156
const AttributeValue& Character::weapon_attribute() const
16✔
3157
{
3158
  return attribute( weapon->attribute().attrid );
16✔
3159
}
3160

3161
unsigned short Character::random_weapon_damage() const
8✔
3162
{
3163
  return weapon->get_random_damage();
8✔
3164
}
3165

3166
unsigned short Character::min_weapon_damage() const
81✔
3167
{
3168
  return weapon->min_weapon_damage();
81✔
3169
}
3170

3171
unsigned short Character::max_weapon_damage() const
81✔
3172
{
3173
  return weapon->max_weapon_damage();
81✔
3174
}
3175

3176
void Character::damage_weapon()
8✔
3177
{
3178
  if ( !weapon->is_intrinsic() && !weapon->is_projectile() )
8✔
3179
  {
3180
    weapon->reduce_hp_from_hit();
×
3181
  }
3182
}
8✔
3183

3184
Items::UArmor* Character::choose_armor() const
8✔
3185
{
3186
  double f = Clib::random_double( Core::gamestate.armor_zone_chance_sum );
8✔
3187
  for ( unsigned zone = 0; zone < Core::gamestate.armorzones.size(); ++zone )
13✔
3188
  {
3189
    f -= Core::gamestate.armorzones[zone].chance;
13✔
3190
    if ( f <= 0.0 )
13✔
3191
    {
3192
      return armor_[zone];
8✔
3193
    }
3194
  }
3195
  return nullptr;
×
3196
}
3197

3198
Core::UACTION Character::weapon_anim() const
8✔
3199
{
3200
  if ( on_mount() )
8✔
3201
    return weapon->mounted_anim();
×
3202
  return weapon->anim();
8✔
3203
}
3204

3205
void Character::do_attack_effects( Character* target )
8✔
3206
{
3207
  if ( weapon->is_projectile() )
8✔
3208
  {
3209
    // 234 is hit, 238 is miss??
3210
    play_sound_effect( this, weapon->projectile_sound() );
×
3211
    play_moving_effect( this, target, weapon->projectile_anim(),
×
3212
                        9,    // Speed (??)
3213
                        0,    // Loop
3214
                        0 );  // Explode
3215
    if ( graphic >= UOBJ_HUMAN_MALE )
×
3216
    {
3217
      send_action_to_inrange( this, weapon_anim() );
×
3218
    }
3219
    else
3220
    {
3221
      send_action_to_inrange( this, Core::ACTION_LOOK_AROUND );
×
3222
    }
3223
  }
3224
  else if ( graphic >= UOBJ_HUMAN_MALE )
8✔
3225
  {
3226
    send_action_to_inrange( this, weapon_anim() );
8✔
3227
  }
3228
  else
3229
  {
3230
    send_action_to_inrange( this, Core::ACTION_LOOK_AROUND );
×
3231
  }
3232
}
8✔
3233

3234
void Character::do_hit_success_effects()
8✔
3235
{
3236
  unsigned short sound = weapon->hit_sound();
8✔
3237
  if ( sound )
8✔
3238
    play_sound_effect( this, sound );
×
3239
}
8✔
3240

3241
void Character::do_hit_failure_effects()
×
3242
{
3243
  unsigned short sound = weapon->miss_sound();
×
3244
  if ( sound )
×
3245
    play_sound_effect( this, sound );
×
3246
}
×
3247

3248
u16 Character::get_damaged_sound() const
×
3249
{
3250
  if ( gender == Plib::GENDER_MALE )
×
3251
    return SOUND_EFFECT_MALE_DEFENSE;
×
3252
  return SOUND_EFFECT_FEMALE_DEFENSE;
×
3253
}
3254

3255
void Character::do_imhit_effects()
×
3256
{
3257
  if ( Core::settingsManager.combat_config.core_hit_sounds )
×
3258
  {
3259
    play_sound_effect( this, get_damaged_sound() );
×
3260
  }
3261
  if ( objtype_ >= UOBJ_HUMAN_MALE )
×
3262
    send_action_to_inrange( this, Core::ACTION_GOT_HIT );
×
3263
}
×
3264

3265

3266
void Character::attack( Character* opponent )
8✔
3267
{
3268
  INC_PROFILEVAR( combat_operations );
8✔
3269

3270
  if ( Core::gamestate.system_hooks.attack_hook )
8✔
3271
  {
3272
    if ( Core::gamestate.system_hooks.attack_hook->call(
×
3273
             new Module::ECharacterRefObjImp( this ),
×
3274
             new Module::ECharacterRefObjImp( opponent ) ) )
×
3275
      return;
×
3276
  }
3277

3278
  if ( Core::settingsManager.watch.combat )
8✔
3279
    INFO_PRINTLN( "{} attacks {}", name(), opponent->name() );
×
3280

3281
  if ( weapon->is_projectile() )
8✔
3282
  {
3283
    Core::UContainer* bp = backpack();
×
3284
    int check_consume_hook = -1;
×
3285
    if ( Core::gamestate.system_hooks.consume_ammunition_hook )
×
3286
    {
3287
      check_consume_hook = Core::gamestate.system_hooks.consume_ammunition_hook->call_long(
×
3288
          new Module::ECharacterRefObjImp( this ), new Module::EItemRefObjImp( weapon ) );
×
3289
      if ( check_consume_hook == 0 )
×
3290
      {
3291
        return;
×
3292
      }
3293
    }
3294

3295
    if ( ( check_consume_hook == -1 ) &&
×
3296
         ( ( bp == nullptr ) || ( weapon->consume_projectile( bp ) == false ) ) )
×
3297
    {
3298
      // 04/2007 - MuadDib
3299
      // Range through wornitems to find containers and check
3300
      // here also if backpack fails. Use the mainpack first this way.
3301
      bool projectile_check = false;
×
3302
      for ( unsigned layer = Core::LAYER_EQUIP__LOWEST; layer <= Core::LAYER_EQUIP__HIGHEST;
×
3303
            layer++ )
3304
      {
3305
        Items::Item* item = wornitems->GetItemOnLayer( layer );
×
3306
        if ( item )
×
3307
        {
3308
          if ( item != nullptr && item->script_isa( Core::POLCLASS_CONTAINER ) )
×
3309
          {
3310
            if ( layer != Core::LAYER_HAIR && layer != Core::LAYER_FACE &&
×
3311
                 layer != Core::LAYER_BEARD && layer != Core::LAYER_BACKPACK &&
×
3312
                 layer != Core::LAYER_MOUNT )
3313
            {
3314
              Core::UContainer* cont = static_cast<Core::UContainer*>( item );
×
3315

3316
              if ( weapon->consume_projectile( cont ) == true )
×
3317
              {
3318
                projectile_check = true;
×
3319
                break;
×
3320
              }
3321
            }
3322
          }
3323
        }
3324
      }
3325
      // I'm out of projectiles.
3326
      if ( projectile_check == false )
×
3327
        return;
×
3328
    }
3329
  }
3330

3331
  repsys_on_attack( opponent );
8✔
3332
  repsys_on_damage( opponent );
8✔
3333

3334
  do_attack_effects( opponent );
8✔
3335

3336
  if ( Core::gamestate.system_hooks.combat_advancement_hook )
8✔
3337
  {
3338
    Core::gamestate.system_hooks.combat_advancement_hook->call(
×
3339
        new Module::ECharacterRefObjImp( this ), new Module::EItemRefObjImp( weapon ),
×
3340
        new Module::ECharacterRefObjImp( opponent ) );
×
3341
  }
3342

3343
  double hit_chance = ( weapon_attribute().effective() + 50.0 ) /
8✔
3344
                      ( 2.0 * ( opponent->weapon_attribute().effective() + 50.0 ) );
8✔
3345
  hit_chance += hitchance_mod() * 0.001f;
8✔
3346
  hit_chance -= opponent->evasionchance_mod() * 0.001f;
8✔
3347
  if ( Core::settingsManager.watch.combat )
8✔
3348
    INFO_PRINT( "Chance to hit: {}: ", hit_chance );
×
3349
  if ( Clib::random_double( 1.0 ) < hit_chance )
8✔
3350
  {
3351
    if ( Core::settingsManager.watch.combat )
8✔
3352
      INFO_PRINTLN( "Hit!" );
×
3353
    do_hit_success_effects();
8✔
3354

3355
    double damage = random_weapon_damage();
8✔
3356
    damage_weapon();
8✔
3357

3358
    if ( Core::settingsManager.watch.combat )
8✔
3359
      INFO_PRINTLN( "Base damage: {}", damage );
×
3360

3361
    double damage_multiplier = attribute( Core::gamestate.pAttrTactics->attrid ).effective() + 50;
8✔
3362
    damage_multiplier += strength() * 0.20f;
8✔
3363
    damage_multiplier *= 0.01f;
8✔
3364

3365
    damage *= damage_multiplier;
8✔
3366

3367
    if ( Core::settingsManager.watch.combat )
8✔
3368
      INFO_PRINTLN( "Damage multiplier due to tactics/STR: {} Result: {}", damage_multiplier,
×
3369
                    damage );
3370

3371
    if ( opponent->shield != nullptr )
8✔
3372
    {
3373
      if ( Core::gamestate.system_hooks.parry_advancement_hook )
×
3374
      {
3375
        Core::gamestate.system_hooks.parry_advancement_hook->call(
×
3376
            new Module::ECharacterRefObjImp( this ), new Module::EItemRefObjImp( weapon ),
×
3377
            new Module::ECharacterRefObjImp( opponent ),
×
3378
            new Module::EItemRefObjImp( opponent->shield ) );
×
3379
      }
3380

3381
      double parry_chance =
3382
          opponent->attribute( Core::gamestate.pAttrParry->attrid ).effective() / 200.0;
×
3383
      parry_chance += opponent->parrychance_mod() * 0.001f;
×
3384
      if ( Core::settingsManager.watch.combat )
×
3385
        INFO_PRINT( "Parry Chance: {}: ", parry_chance );
×
3386
      if ( Clib::random_double( 1.0 ) < parry_chance )
×
3387
      {
3388
        if ( Core::settingsManager.watch.combat )
×
3389
          INFO_PRINTLN( "{} hits deflected", opponent->shield->ar() );
×
3390
        if ( Core::settingsManager.combat_config.display_parry_success_messages &&
×
3391
             opponent->client )
×
3392
          Core::send_sysmessage( opponent->client, "You successfully parried the attack!" );
×
3393

3394
        damage -= opponent->shield->ar();
×
3395
        if ( damage < 0 )
×
3396
          damage = 0;
×
3397
      }
3398
      else
3399
      {
3400
        if ( Core::settingsManager.watch.combat )
×
3401
          INFO_PRINTLN( "failed." );
×
3402
      }
3403
    }
3404
    if ( weapon->hit_script().empty() )
8✔
3405
    {
3406
      opponent->apply_damage( damage, this, true,
×
3407
                              Core::settingsManager.combat_config.send_damage_packet );
×
3408
    }
3409
    else
3410
    {
3411
      run_hit_script( opponent, damage );
8✔
3412
    }
3413
  }
3414
  else
3415
  {
3416
    if ( Core::settingsManager.watch.combat )
×
3417
      INFO_PRINTLN( "Miss!" );
×
3418
    opponent->on_swing_failure( this );
×
3419
    do_hit_failure_effects();
×
3420
    if ( Core::gamestate.system_hooks.hitmiss_hook )
×
3421
    {
3422
      Core::gamestate.system_hooks.hitmiss_hook->call(
×
3423
          new Module::ECharacterRefObjImp( this ), new Module::ECharacterRefObjImp( opponent ) );
×
3424
    }
3425
  }
3426
}
3427

3428
void Character::check_attack_after_move( bool check_opponents_after_check )
184✔
3429
{
3430
  FUNCTION_CHECKPOINT( check_attack_after_move, 1 );
184✔
3431
  Character* opponent = get_attackable_opponent();
184✔
3432
  FUNCTION_CHECKPOINT( check_attack_after_move, 2 );
184✔
3433
  INFO_PRINTLN_TRACE( 20 )
184✔
3434
  ( "check_attack_after_move({:#x}): opponent is {:#x}", this->serial,
×
3435
    opponent != nullptr ? opponent->serial : 0 );
×
3436
  if ( opponent != nullptr &&  // and I have an opponent
200✔
3437
       !dead() &&              // If I'm not dead
200✔
3438
       ( Core::settingsManager.combat_config.attack_while_frozen ||
16✔
3439
         ( !paralyzed() && !frozen() ) ) )
×
3440
  {
3441
    FUNCTION_CHECKPOINT( check_attack_after_move, 3 );
16✔
3442
    if ( mob_flags_.get( MOB_FLAGS::READY_TO_SWING ) )  // and I can swing now,
16✔
3443
    {                                                   // do so.
3444
      FUNCTION_CHECKPOINT( check_attack_after_move, 4 );
8✔
3445
      if ( Core::settingsManager.combat_config.send_swing_packet && client != nullptr )
8✔
3446
        send_fight_occuring( client, opponent );
×
3447

3448
      // we don't want attack() to recursively cause new attacks
3449
      mob_flags_.remove( MOB_FLAGS::READY_TO_SWING );
8✔
3450
      attack( opponent );
8✔
3451
      FUNCTION_CHECKPOINT( check_attack_after_move, 5 );
8✔
3452
      reset_swing_timer();
8✔
3453
      FUNCTION_CHECKPOINT( check_attack_after_move, 6 );
8✔
3454
    }
3455
    else
3456
    {
3457
      FUNCTION_CHECKPOINT( check_attack_after_move, 7 );
8✔
3458
      INFO_PRINTLN_TRACE( 20 )( "not ready to swing" );
8✔
3459
      schedule_attack();
8✔
3460
      FUNCTION_CHECKPOINT( check_attack_after_move, 8 );
8✔
3461
    }
3462
  }
3463
  FUNCTION_CHECKPOINT( check_attack_after_move, 0 );
184✔
3464

3465
  if ( check_opponents_after_check )
184✔
3466
  {
3467
    if ( opponent_ != nullptr )
167✔
3468
      opponent_->check_attack_after_move( false );
3✔
3469

3470
    // attacking can change the opponent_of array drastically.
3471
    std::set<Character*> tmp( opponent_of );
167✔
3472
    for ( auto& chr : tmp )
170✔
3473
    {
3474
      chr->check_attack_after_move( false );
3✔
3475
    }
3476
  }
167✔
3477
}
184✔
3478

3479

3480
void Character::check_light_region_change()
166✔
3481
{
3482
  auto light_unil = lightoverride_until();
166✔
3483
  if ( light_unil < Core::read_gameclock() && light_unil != ~0u )
166✔
3484
  {
3485
    lightoverride_until( 0 );
166✔
3486
    lightoverride( -1 );
166✔
3487
  }
3488
  if ( client->gd->weather_region && client->gd->weather_region->lightoverride != -1 &&
166✔
3489
       !has_lightoverride() )
×
3490
    return;
×
3491

3492
  int newlightlevel;
3493
  if ( has_lightoverride() )
166✔
3494
    newlightlevel = lightoverride();
×
3495
  else
3496
  {
3497
    // dave 12-22 check for no regions
3498
    Core::LightRegion* light_region = Core::gamestate.lightdef->getregion( pos() );
166✔
3499
    if ( light_region != nullptr )
166✔
3500
      newlightlevel = light_region->lightlevel;
×
3501
    else
3502
      newlightlevel = Core::settingsManager.ssopt.default_light_level;
166✔
3503
  }
3504

3505
  if ( newlightlevel != client->gd->lightlevel )
166✔
3506
  {
3507
    Core::send_light( client, newlightlevel );
2✔
3508
    client->gd->lightlevel = newlightlevel;
2✔
3509
  }
3510
}
3511

3512
void Character::check_justice_region_change()
103✔
3513
{
3514
  Core::JusticeRegion* cur_justice_region = client->gd->justice_region;
103✔
3515
  Core::JusticeRegion* new_justice_region = Core::gamestate.justicedef->getregion( pos() );
103✔
3516

3517
  if ( cur_justice_region != new_justice_region )
103✔
3518
  {
3519
    if ( cur_justice_region != nullptr )
×
3520
      cur_justice_region->RunLeaveScript( client->chr );
×
3521
    if ( new_justice_region != nullptr )
×
3522
      new_justice_region->RunEnterScript( client->chr );
×
3523

3524
    // print 'leaving' message
3525
    bool printmsgs;
3526
    if ( cur_justice_region != nullptr && new_justice_region != nullptr &&
×
3527
         cur_justice_region->entertext() == new_justice_region->entertext() &&
×
3528
         cur_justice_region->leavetext() == new_justice_region->leavetext() )
×
3529
    {
3530
      printmsgs = false;
×
3531
    }
3532
    else
3533
    {
3534
      printmsgs = true;
×
3535
    }
3536

3537
    if ( printmsgs && cur_justice_region )
×
3538
    {
3539
      const std::string& leavetext = cur_justice_region->leavetext();
×
3540
      if ( !leavetext.empty() )
×
3541
      {
3542
        Core::send_sysmessage( client, leavetext );
×
3543
      }
3544
    }
3545

3546
    client->gd->justice_region = new_justice_region;
×
3547

3548
    if ( new_justice_region && new_justice_region->RunNoCombatCheck( client ) == true )
×
3549
    {
3550
      Character* opp2 = get_opponent();
×
3551
      if ( ( opp2 != nullptr && opp2->client ) )
×
3552
      {
3553
        opp2->opponent_of.erase( client->chr );
×
3554
        opp2->set_opponent( nullptr, true );
×
3555
        opp2->schedule_attack();
×
3556
        opp2->opponent_ = nullptr;
×
3557
        opp2->clear_opponent_of();
×
3558
        set_opponent( nullptr, true );
×
3559
        if ( swing_task != nullptr )
×
3560
          swing_task->cancel();
×
3561
      }
3562
    }
3563

3564

3565
    // print 'entering' message
3566
    // handle nocombat while we have entered.
3567
    if ( printmsgs && new_justice_region )
×
3568
    {
3569
      const std::string& entertext = new_justice_region->entertext();
×
3570
      if ( !entertext.empty() )
×
3571
      {
3572
        Core::send_sysmessage( client, entertext );
×
3573
      }
3574
    }
3575
  }
3576
}
103✔
3577

3578
void Character::check_music_region_change()
103✔
3579
{
3580
  Core::MusicRegion* cur_music_region = client->gd->music_region;
103✔
3581
  Core::MusicRegion* new_music_region = Core::gamestate.musicdef->getregion( pos() );
103✔
3582

3583
  // may want to consider changing every n minutes, too, even if region didn't change
3584
  if ( cur_music_region != new_music_region )
103✔
3585
  {
3586
    client->gd->music_region = new_music_region;
×
3587
    if ( new_music_region != nullptr )
×
3588
    {
3589
      Core::send_midi( client, new_music_region->getmidi() );
×
3590
    }
3591
    else
3592
    {
3593
      Core::send_midi( client, 0 );
×
3594
    }
3595
  }
3596
}
103✔
3597

3598
void Character::check_weather_region_change( bool force )  // dave changed 5/26/03 - use force
105✔
3599
                                                           // boolean if current weather region
3600
                                                           // changed type/intensity
3601
{
3602
  Core::WeatherRegion* cur_weather_region = client->gd->weather_region;
105✔
3603
  Core::WeatherRegion* new_weather_region = Core::gamestate.weatherdef->getregion( pos() );
105✔
3604

3605
  // eric 5/31/03: I don't think this is right.  it's possible to go from somewhere that has no
3606
  // weather region,
3607
  // and to walk to somewhere that doesn't have a weather region.
3608
  //
3609
  if ( force || ( cur_weather_region != new_weather_region ) )
105✔
3610
  {
3611
    if ( new_weather_region != nullptr && new_weather_region->lightoverride != -1 &&
2✔
3612
         !has_lightoverride() )
×
3613
    {
3614
      Core::send_light( client, new_weather_region->lightoverride );
×
3615
      client->gd->lightlevel = new_weather_region->lightoverride;
×
3616
    }
3617

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

3626
    if ( new_weather_region )
2✔
3627
    {
3628
      Core::send_weather( client, new_weather_region->weathertype, new_weather_region->severity,
×
3629
                          new_weather_region->aux );
×
3630
    }
3631
    else
3632
    {
3633
      Core::send_weather( client, 0xff, 0, 0 );  // turn off
2✔
3634
    }
3635
    client->gd->weather_region = new_weather_region;
2✔
3636
  }
3637
}
105✔
3638

3639
void Character::check_region_changes()
166✔
3640
{
3641
  if ( client != nullptr )
166✔
3642
  {
3643
    check_weather_region_change();
103✔
3644

3645
    check_light_region_change();
103✔
3646

3647
    check_justice_region_change();
103✔
3648

3649
    check_music_region_change();
103✔
3650
  }
3651
}
166✔
3652

3653
void Character::position_changed()
255✔
3654
{
3655
  wornitems->setposition( pos() );
255✔
3656
  position_changed_at_ = Core::polclock();
255✔
3657
}
255✔
3658

3659
void Character::unhide()
3✔
3660
{
3661
  if ( Core::gamestate.system_hooks.un_hide )
3✔
3662
  {
3663
    if ( !Core::gamestate.system_hooks.un_hide->call( make_mobileref( this ) ) )
×
3664
      return;
×
3665
  }
3666

3667
  hidden( false );
3✔
3668
  if ( is_visible() )
3✔
3669
  {
3670
    if ( client != nullptr )
3✔
3671
      send_owncreate( client, this );
×
3672

3673
    Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
3✔
3674
        this,
3675
        [&]( Character* chr )
3✔
3676
        {
3677
          if ( chr == this )
×
3678
            return;
×
3679
          if ( !chr->is_visible_to_me( this ) )
×
3680
            return;
×
3681
          send_owncreate( chr->client, this );
×
3682
        } );
3683

3684
    realm()->notify_unhid( *this );
3✔
3685

3686
    if ( !Clib::exit_signalled )
3✔
3687
    {
3688
      check_attack_after_move( true );
3✔
3689
    }
3690
  }
3691
}
3692

3693
void Character::set_stealthsteps( unsigned short newval )
8✔
3694
{
3695
  stealthsteps_ = newval;
8✔
3696
}
8✔
3697

3698
bool Character::doors_block() const
98✔
3699
{
3700
  return !( graphic == UOBJ_GAMEMASTER || graphic == UOBJ_HUMAN_MALE_GHOST ||
196✔
3701
            graphic == UOBJ_HUMAN_FEMALE_GHOST || graphic == UOBJ_ELF_MALE_GHOST ||
98✔
3702
            graphic == UOBJ_ELF_FEMALE_GHOST || graphic == UOBJ_GARGOYLE_MALE_GHOST ||
98✔
3703
            graphic == UOBJ_GARGOYLE_FEMALE_GHOST ||
98✔
3704
            cached_settings.get( PRIV_FLAGS::IGNORE_DOORS ) );
196✔
3705
}
3706

3707
/*
3708
    This isn't quite right, for hidden characters.
3709
    What normally happens:
3710
    1) Client sends "use skill hiding"
3711
    2) Server sends "77 move" with the hidden flag set.
3712
    3) client sends move-request
3713
    4) server sends move-approve
3714
    5) server sense "78 create" message with hidden flag clear, at new posn.
3715

3716
    We're sending the "78 create" _before_ the move-approve.
3717
    */
3718

3719
bool Character::can_face( Core::UFACING /*i_facing*/ )
24✔
3720
{
3721
  if ( can_freemove() )
24✔
3722
    return true;
×
3723

3724
  if ( frozen() || paralyzed() )
24✔
3725
  {
3726
    if ( client != nullptr )
×
3727
    {
3728
      if ( frozen() )
×
3729
        private_say_above( this, this, "I am frozen and cannot move." );
×
3730
      else if ( paralyzed() )
×
3731
        private_say_above( this, this, "I am paralyzed and cannot move." );
×
3732
    }
3733
    return false;
×
3734
  }
3735

3736
  if ( Core::settingsManager.ssopt.movement_uses_stamina &&
72✔
3737
       vital( Core::gamestate.pVitalStamina->vitalid ).current_ones() == 0 && !dead() )
24✔
3738
  {
3739
    private_say_above( this, this, "You are too fatigued to move." );
×
3740
    return false;
×
3741
  }
3742

3743
  return true;
24✔
3744
}
3745

3746

3747
bool Character::face( Core::UFACING i_facing, int flags )
24✔
3748
{
3749
  if ( ( flags & 1 ) == 0 )
24✔
3750
  {
3751
    if ( !can_face( i_facing ) )
24✔
3752
      return false;
×
3753
  }
3754

3755
  if ( i_facing != facing )
24✔
3756
  {
3757
    setfacing( static_cast<u8>( i_facing ) );
8✔
3758

3759
    if ( Core::settingsManager.combat_config.reset_swing_onturn &&
16✔
3760
         !cached_settings.get( PRIV_FLAGS::FIRE_WHILE_MOVING ) && weapon->is_projectile() )
8✔
3761
      reset_swing_timer();
×
3762
    if ( hidden() && ( Core::settingsManager.ssopt.hidden_turns_count ) )
8✔
3763
    {
3764
      if ( stealthsteps_ == 0 )
×
3765
        unhide();
×
3766
      else
3767
        stealthsteps_--;
×
3768
    }
3769
  }
3770

3771
  return true;
24✔
3772
}
3773

3774

3775
bool Character::CustomHousingMove( unsigned char i_dir )
×
3776
{
3777
  passert( facing < 8 );
×
3778

3779
  Multi::UMulti* multi = Core::system_find_multi( client->gd->custom_house_serial );
×
3780
  if ( multi != nullptr )
×
3781
  {
3782
    Multi::UHouse* house = multi->as_house();
×
3783
    if ( house != nullptr )
×
3784
    {
3785
      Core::UFACING i_facing = static_cast<Core::UFACING>( i_dir & PKTIN_02_FACING_MASK );
×
3786
      if ( i_facing != facing )
×
3787
      {
3788
        setfacing( static_cast<u8>( i_facing ) );
×
3789
        set_dirty();
×
3790
        dir = i_dir;
×
3791
        return true;
×
3792
      }
3793

3794
      auto newpos = pos().move( static_cast<Core::UFACING>( facing ) );
×
3795
      newpos.z( house->z() +
×
3796
                Multi::CustomHouseDesign::custom_house_z_xlate_table[house->editing_floor_num] );
×
3797
      const Multi::MultiDef& def = house->multidef();
×
3798
      auto relpos = newpos - house->pos().xy();
×
3799
      // minx and y are wall elements and z is 7
3800
      // mobile will look like flying when allowing the min coords
3801
      if ( def.within_multi( relpos ) && relpos.x() != def.minrxyz.x() &&
×
3802
           relpos.y() != def.minrxyz.y() )
×
3803
      {
3804
        Core::Pos4d oldpos = pos();
×
3805
        setposition( newpos );
×
3806
        MoveCharacterWorldPosition( oldpos, this );
×
3807

3808
        position_changed();
×
3809
        set_dirty();
×
3810
        return true;
×
3811
      }
3812
    }
3813
  }
3814
  return false;
×
3815
}
3816

3817
bool Character::is_piloting_boat() const
138✔
3818
{
3819
  auto* mountpiece = wornitem( Core::LAYER_MOUNT );
138✔
3820
  return mountpiece != nullptr && mountpiece->objtype_ == Core::settingsManager.extobj.boatmount;
138✔
3821
}
3822

3823
//************************************
3824
// Method:    move
3825
// FullName:  Character::move
3826
// Access:    public
3827
// Returns:   bool
3828
// Qualifier:
3829
// Parameter: unsigned char i_dir
3830
//************************************
3831
bool Character::move( unsigned char i_dir )
20✔
3832
{
3833
  if ( is_piloting_boat() )
20✔
3834
  {
3835
    return false;
1✔
3836
  }
3837

3838
  lastpos = pos();
19✔
3839

3840
  // if currently building a house chr can move free inside the multi
3841
  if ( is_house_editing() )
19✔
3842
    return CustomHousingMove( i_dir );
×
3843

3844
  u8 oldFacing = facing;
19✔
3845

3846
  Core::UFACING i_facing = static_cast<Core::UFACING>( i_dir & PKTIN_02_FACING_MASK );
19✔
3847
  if ( !face( i_facing ) )
19✔
3848
    return false;
×
3849

3850
  // If we're a player, and we used our "move" command to turn,
3851
  //  we want to skip the meat of this function
3852
  if ( ( script_isa( Core::POLCLASS_NPC ) ) || ( facing == oldFacing ) )
19✔
3853
  {
3854
    if ( facing & 1 )  // check if diagonal movement is allowed -- Nando (2009-02-26)
14✔
3855
    {
3856
      short new_z;
3857
      u8 tmp_facing = ( facing + 1 ) & 0x7;
1✔
3858
      auto tmp_pos = pos().move( static_cast<Core::UFACING>( tmp_facing ) );
1✔
3859

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

3864
      tmp_facing = ( facing - 1 ) & 0x7;
1✔
3865
      tmp_pos = pos().move( static_cast<Core::UFACING>( tmp_facing ) );
1✔
3866

3867
      if ( !walk1 && !realm()->walkheight( this, tmp_pos.xy(), tmp_pos.z(), &new_z, nullptr,
1✔
3868
                                           nullptr, nullptr ) )
3869
        return false;
×
3870
    }
3871
    auto new_pos = pos().move( static_cast<Core::UFACING>( facing ) );
14✔
3872

3873
    // FIXME consider consolidating with similar code in UOEMOD.CPP
3874
    short newz;
3875
    Multi::UMulti* supporting_multi;
3876
    Items::Item* walkon_item;
3877

3878
    short current_boost = gradual_boost;
14✔
3879
    if ( !realm()->walkheight( this, new_pos.xy(), new_pos.z(), &newz, &supporting_multi,
14✔
3880
                               &walkon_item, &current_boost ) )
3881
      return false;
1✔
3882
    new_pos.z( static_cast<s8>( newz ) );
13✔
3883
    remote_containers_.clear();
13✔
3884

3885
    if ( !CheckPushthrough() )
13✔
3886
      return false;
×
3887

3888
    if ( !can_freemove() && Core::settingsManager.ssopt.movement_uses_stamina && !dead() )
13✔
3889
    {
3890
      int carry_perc = weight() * 100 / carrying_capacity();
13✔
3891
      unsigned short tmv = movecost(
26✔
3892
          this, carry_perc, ( i_dir & PKTIN_02_DIR_RUNNING_BIT ) ? true : false, on_mount() );
13✔
3893
      VitalValue& stamina = vital( Core::gamestate.pVitalStamina->vitalid );
13✔
3894
      if ( !consume( Core::gamestate.pVitalStamina, stamina, tmv, VitalDepletedReason::MOVEMENT ) )
13✔
3895
      {
3896
        private_say_above( this, this, "You are too fatigued to move." );
×
3897
        return false;
×
3898
      }
3899
    }
3900

3901
    // Maybe have a flag for this in servspecopt?
3902
    if ( !cached_settings.get( PRIV_FLAGS::FIRE_WHILE_MOVING ) && weapon->is_projectile() )
13✔
3903
      reset_swing_timer();
×
3904

3905
    Core::Pos4d oldpos = pos();
13✔
3906
    setposition( new_pos );
13✔
3907
    moved_at_ = Core::polclock();
13✔
3908

3909
    if ( on_mount() && !script_isa( Core::POLCLASS_NPC ) )
13✔
3910
    {
3911
      mountedsteps_++;
×
3912
    }
3913
    if ( supporting_multi != nullptr )
13✔
3914
    {
3915
      supporting_multi->register_object( this );
×
3916

3917
      if ( this->registered_multi == 0 )
×
3918
      {
3919
        this->registered_multi = supporting_multi->serial;
×
3920
        supporting_multi->walk_on( this );
×
3921
      }
3922
    }
3923
    else
3924
    {
3925
      if ( registered_multi > 0 )
13✔
3926
      {
3927
        Multi::UMulti* multi = Core::system_find_multi( registered_multi );
×
3928
        if ( multi != nullptr )
×
3929
        {
3930
          multi->unregister_object( (UObject*)this );
×
3931
        }
3932
        registered_multi = 0;
×
3933
      }
3934
    }
3935

3936
    gradual_boost = current_boost;
13✔
3937
    MoveCharacterWorldPosition( oldpos, this );
13✔
3938

3939
    position_changed();
13✔
3940
    if ( walkon_item != nullptr )
13✔
3941
    {
3942
      walkon_item->walk_on( this );
×
3943
    }
3944

3945
    if ( hidden() )
13✔
3946
    {
3947
      if ( ( ( i_dir & PKTIN_02_DIR_RUNNING_BIT ) &&
×
3948
             !cached_settings.get( PRIV_FLAGS::RUN_WHILE_STEALTH ) ) ||
×
3949
           ( stealthsteps_ == 0 ) )
×
3950
        unhide();
×
3951
      else if ( stealthsteps_ )
×
3952
        --stealthsteps_;
×
3953
    }
3954

3955
    if ( Core::gamestate.system_hooks.ouch_hook )
13✔
3956
    {
3957
      if ( ( lastpos.z() - z() ) > 21 )
×
3958
        Core::gamestate.system_hooks.ouch_hook->call(
×
3959
            make_mobileref( this ), new Bscript::BLong( lastpos.x() ),
×
3960
            new Bscript::BLong( lastpos.y() ), new Bscript::BLong( lastpos.z() ) );
×
3961
    }
3962
  }
3963

3964
  set_dirty();
18✔
3965
  dir = i_dir;
18✔
3966

3967
  return true;
18✔
3968

3969

3970
  // this would be a great place for tellmove(), except that
3971
  // we want to send the move acknowledge to the client before
3972
  // sending the move/create messages to the neighboring clients.
3973

3974
  // Why? Maybe to give the best response time to the client.
3975

3976
  // may want to rethink this, since it's starting to complicate
3977
  // things.
3978
}
3979

3980
void Character::realm_changed()
18✔
3981
{
3982
  // Commented out the explicit backpack handling, should be handled
3983
  // automagically by wormitems realm handling.  There is a slim
3984
  // possibility that backpacks might be assigned to a character but
3985
  // not be a worn item?  If this is the case, that will be broken.
3986
  //  backpack()->realm = realm;
3987
  //  backpack()->for_each_item(setrealm, (void*)realm);
3988
  wornitems->for_each_item( Core::setrealm, (void*)realm() );
18✔
3989
  // TODO Pos: realm should be all the time nullptr for these items
3990
  if ( has_gotten_item() )
18✔
3991
  {
3992
    auto* item = gotten_item().item();
3✔
3993
    item->setposition( Core::Pos4d( item->pos().xyz(), realm() ) );
3✔
3994
    if ( item->isa( Core::UOBJ_CLASS::CLASS_CONTAINER ) )
3✔
3995
    {
3996
      Core::UContainer* cont = static_cast<Core::UContainer*>( item );
3✔
3997
      cont->for_each_item( Core::setrealm, (void*)realm() );
3✔
3998
    }
3999
  }
4000
  if ( trading_cont.get() )
18✔
4001
  {
4002
    trading_cont->setposition( Core::Pos4d( trading_cont->pos().xyz(), realm() ) );
×
4003
    trading_cont->for_each_item( Core::setrealm, (void*)realm() );
×
4004
  }
4005

4006
  if ( has_active_client() )
18✔
4007
  {
4008
    // these are important to keep here in this order
4009
    Core::send_realm_change( client, realm() );
14✔
4010
    Core::send_map_difs( client );
14✔
4011
    if ( Core::settingsManager.ssopt.core_sends_season )
14✔
4012
      Core::send_season_info( client );
14✔
4013
    Core::send_full_statmsg( client, this );
14✔
4014
    Core::send_feature_enable( client );
14✔
4015
  }
4016
}
18✔
4017

4018
bool Character::CheckPushthrough()
13✔
4019
{
4020
  if ( !can_freemove() && Core::gamestate.system_hooks.pushthrough_hook )
13✔
4021
  {
4022
    auto newpos = pos().move( static_cast<Core::UFACING>( facing ) );
×
4023
    auto mobs = std::unique_ptr<Bscript::ObjArray>();
×
4024

4025
    Core::WorldIterator<Core::MobileFilter>::InRange(
×
4026
        newpos, 0,
4027
        [&]( Mobile::Character* _chr )
×
4028
        {
4029
          if ( _chr->z() >= z() - 10 && _chr->z() <= z() + 10 && !_chr->dead() &&
×
4030
               ( is_visible_to_me( _chr ) ||
×
4031
                 _chr->hidden() ) )  // add hidden mobs even if they're not visible to me
×
4032
          {
4033
            if ( !mobs )
×
4034
              mobs = std::make_unique<Bscript::ObjArray>();
×
4035
            mobs->addElement( make_mobileref( _chr ) );
×
4036
          }
4037
        } );
×
4038

4039
    if ( mobs )
×
4040
    {
4041
      return Core::gamestate.system_hooks.pushthrough_hook->call( make_mobileref( this ),
×
4042
                                                                  mobs.release() );
×
4043
    }
4044
    return true;
×
4045
  }
×
4046
  return true;
13✔
4047
}
4048

4049
void Character::tellmove()
164✔
4050
{
4051
  check_region_changes();
164✔
4052
  PropagateMove( this );
164✔
4053

4054
  // notify npcs and items (maybe the PropagateMove should also go there eventually? - Nando
4055
  // 2018-06-16)
4056
  realm()->notify_moved( *this );
164✔
4057

4058
  check_attack_after_move( true );
164✔
4059

4060
  move_reason = OTHER;
164✔
4061
}
164✔
4062

4063
void Character::add_remote_container( Items::Item* item )
×
4064
{
4065
  remote_containers_.emplace_back( item );
×
4066
}
×
4067

4068
Items::Item* Character::search_remote_containers( u32 find_serial, bool* isRemoteContainer ) const
45✔
4069
{
4070
  if ( isRemoteContainer )
45✔
4071
    *isRemoteContainer = false;
40✔
4072
  for ( const auto& elem : remote_containers_ )
45✔
4073
  {
4074
    Items::Item* item = elem.get();
×
4075
    if ( item->orphan() )
×
4076
      continue;  // it'll be cleaned up when they move
×
4077
    if ( item->serial == find_serial )
×
4078
    {
4079
      if ( isRemoteContainer )
×
4080
        *isRemoteContainer = true;
×
4081
      return item;
×
4082
    }
4083
    if ( item->isa( Core::UOBJ_CLASS::CLASS_CONTAINER ) )
×
4084
    {
4085
      item = ( (Core::UContainer*)item )->find( find_serial );
×
4086
      if ( item )
×
4087
      {
4088
        if ( isRemoteContainer )
×
4089
          *isRemoteContainer = false;
×
4090
        return item;
×
4091
      }
4092
    }
4093
  }
4094
  return nullptr;
45✔
4095
}
4096

4097
bool Character::mightsee( const Items::Item* item ) const
76✔
4098
{
4099
  const auto* owner = item->toplevel_owner();
76✔
4100
  for ( const auto& elem : remote_containers_ )
76✔
4101
  {
4102
    Items::Item* additional_item = elem.get();
×
4103
    if ( additional_item == owner )
×
4104
      return true;
×
4105
  }
4106

4107
  return in_visual_range( owner );
76✔
4108
}
4109

4110
bool Character::squelched() const
37✔
4111
{
4112
  Core::gameclock_t squelched = squelched_until();
37✔
4113
  if ( squelched == 0 )
37✔
4114
    return false;
36✔
4115
  if ( squelched == ~0u )
1✔
4116
    return true;
×
4117

4118
  if ( Core::read_gameclock() < squelched )
1✔
4119
  {
4120
    return true;
1✔
4121
  }
4122

4123
  const_cast<Character*>( this )->squelched_until( 0 );
×
4124
  return false;
×
4125
}
4126

4127
bool Character::deafened() const
38✔
4128
{
4129
  Core::gameclock_t deafened = deafened_until();
38✔
4130
  if ( deafened == 0 )
38✔
4131
    return false;
37✔
4132
  if ( deafened == ~0u )
1✔
4133
    return true;
×
4134

4135
  if ( Core::read_gameclock() < deafened )
1✔
4136
  {
4137
    return true;
1✔
4138
  }
4139

4140
  const_cast<Character*>( this )->deafened_until( 0 );
×
4141
  return false;
×
4142
}
4143

4144
bool Character::invul() const
462✔
4145
{
4146
  return cached_settings.get( PRIV_FLAGS::INVUL );
462✔
4147
}
4148

4149
u16 Character::strength() const
21✔
4150
{
4151
  return static_cast<u16>( attribute( Core::gamestate.pAttrStrength->attrid ).effective() );
21✔
4152
}
4153
u16 Character::dexterity() const
175✔
4154
{
4155
  return static_cast<u16>( attribute( Core::gamestate.pAttrDexterity->attrid ).effective() );
175✔
4156
}
4157
u16 Character::intelligence() const
×
4158
{
4159
  return static_cast<u16>( attribute( Core::gamestate.pAttrIntelligence->attrid ).effective() );
×
4160
}
4161

4162
bool Character::target_cursor_busy() const
8✔
4163
{
4164
  if ( client && client->gd && client->gd->tcursor2 && client->gd->target_cursor_uoemod != nullptr )
8✔
4165
    return true;
×
4166
  return false;
8✔
4167
}
4168

4169
void Character::cancel_menu()
×
4170
{
4171
  if ( client )
×
4172
  {
4173
    client->gd->menu.clear();
×
4174
    if ( client->gd->on_menu_selection != nullptr )
×
4175
      client->gd->on_menu_selection( client, nullptr, nullptr );
×
4176
    client->gd->on_menu_selection = nullptr;
×
4177
  }
4178
}
×
4179

4180
bool Character::is_trading() const
192✔
4181
{
4182
  return ( trading_with.get() != nullptr );
192✔
4183
}
4184

4185
bool Character::trade_accepted() const
×
4186
{
4187
  return mob_flags_.get( MOB_FLAGS::TRADE_ACCEPTED );
×
4188
}
4189

4190
void Character::trade_accepted( bool newvalue )
×
4191
{
4192
  mob_flags_.change( MOB_FLAGS::TRADE_ACCEPTED, newvalue );
×
4193
}
×
4194

4195
void Character::create_trade_container()
×
4196
{
4197
  if ( trading_cont.get() == nullptr )  // FIXME hardcoded
×
4198
  {
4199
    Items::Item* cont = Items::Item::create( Core::settingsManager.extobj.secure_trade_container );
×
4200
    // TODO Pos: no realm
4201
    cont->setposition( pos() );
×
4202
    trading_cont.set( static_cast<Core::UContainer*>( cont ) );
×
4203
  }
4204
}
×
4205

4206
Core::UContainer* Character::trade_container()
×
4207
{
4208
  return trading_cont.get();
×
4209
}
4210

4211
// SkillValue removed for no use - MuadDib
4212

4213
const char* Character::target_tag() const
×
4214
{
4215
  return "mobile";
×
4216
}
4217

4218
void Character::set_caps_to_default()
98✔
4219
{
4220
  for ( unsigned ai = 0; ai < Core::gamestate.numAttributes; ++ai )
5,194✔
4221
  {
4222
    Attribute* pAttr = Core::gamestate.attributes[ai];
5,096✔
4223
    AttributeValue& av = attribute( ai );
5,096✔
4224

4225
    av = attribute( ai );
5,096✔
4226
    av.cap( pAttr->default_cap );
5,096✔
4227
  }
4228
}
98✔
4229

4230
u16 Character::last_textcolor() const
×
4231
{
4232
  return _last_textcolor;
×
4233
}
4234

4235
void Character::last_textcolor( u16 new_color )
3✔
4236
{
4237
  _last_textcolor = new_color;
3✔
4238
}
3✔
4239

4240
unsigned int Character::guildid() const
2✔
4241
{
4242
  auto g = guild();
2✔
4243
  return ( g != nullptr ) ? g->guildid() : 0;
2✔
4244
}
4245

4246
/**
4247
 * Adds a new buff or overwrites an existing one for the character
4248
 * Sends packets to the client accordingly
4249
 * @author Bodom
4250
 */
4251
void Character::addBuff( u16 icon, u16 duration, u32 cl_name, const std::string& name_arguments,
2✔
4252
                         u32 cl_descr, const std::string& desc_arguments )
4253
{
4254
  // Icon is already present, must send a remove packet first or client will not update
4255
  delBuff( icon );
2✔
4256

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

4260
  if ( client != nullptr )
2✔
4261
    send_buff_message( this, icon, true, duration, cl_name, name_arguments, cl_descr,
×
4262
                       desc_arguments );
4263
}
4✔
4264

4265
/**
4266
 * Removes a buff for the character
4267
 * Sends packets to the client accordingly
4268
 * @author Bodom
4269
 * @return True when the buff has been found and removed, False when the buff was not present
4270
 */
4271
bool Character::delBuff( u16 icon )
4✔
4272
{
4273
  auto b = buffs_.find( icon );
4✔
4274

4275
  if ( b == buffs_.end() )
4✔
4276
    return false;
3✔
4277

4278
  buffs_.erase( b );
1✔
4279
  if ( client != nullptr )
1✔
4280
    send_buff_message( this, icon, false );
×
4281
  return true;
1✔
4282
}
4283

4284
/**
4285
 * Removes al buffs for the character
4286
 * Sends packets to the client accordingly
4287
 * @author Bodom
4288
 */
4289
void Character::clearBuffs()
2✔
4290
{
4291
  if ( client != nullptr )
2✔
4292
  {
4293
    for ( const auto& buf : buffs_ )
×
4294
    {
4295
      send_buff_message( this, buf.first, false );
×
4296
    }
4297
  }
4298
  buffs_.clear();
2✔
4299
}
2✔
4300

4301
/**
4302
 * Resends all buffs (with updated duration), usually called at (re)login
4303
 * @author Bodom
4304
 */
4305
void Character::send_buffs()
2✔
4306
{
4307
  if ( client == nullptr )
2✔
4308
    return;
×
4309

4310
  for ( const auto& [icon, buf] : buffs_ )
2✔
4311
  {
4312
    u16 duration = Clib::clamp_convert<u16>( buf.end - Core::read_gameclock() );
×
4313

4314
    send_buff_message( this, icon, true, duration, buf.cl_name, buf.name_arguments, buf.cl_descr,
×
4315
                       buf.desc_arguments );
×
4316
  }
4317
}
4318

4319
u8 Character::los_size() const
62,975✔
4320
{
4321
  return client ? client->update_range() : Core::settingsManager.ssopt.default_visual_range;
62,975✔
4322
}
4323

4324
size_t Character::estimatedSize() const
2✔
4325
{
4326
  size_t size = base::estimatedSize() + uclang.capacity() + privs.estimatedSize() +
2✔
4327
                settings.estimatedSize() + sizeof( Core::AccountRef ) /*acct*/
2✔
4328
                + sizeof( Network::Client* )                          /*client*/
4329
                + sizeof( u32 )                                       /*registered_multi*/
4330
                + sizeof( unsigned char )                             /*cmdlevel_*/
4331
                + sizeof( u8 )                                        /*dir*/
4332
                + sizeof( Core::Pos4d )                               /*lastpos*/
4333
                + sizeof( MOVEREASON )                                /*move_reason*/
4334
                + sizeof( Plib::MOVEMODE )                            /*movemode*/
4335
                + sizeof( time_t )                                    /*disable_regeneration_until*/
4336
                + sizeof( u16 )                                       /*truecolor*/
4337
                + sizeof( u32 )                                       /*trueobjtype*/
4338
                + sizeof( Plib::UGENDER )                             /*gender*/
4339
                + sizeof( Plib::URACE )                               /*race*/
4340
                + sizeof( short )                                     /*gradual_boost*/
4341
                + sizeof( u32 )                                       /*last_corpse*/
4342
                + sizeof( u16 )                                       /*_last_textcolor*/
4343
                + sizeof( ref_ptr<Core::WornItemsContainer> )         /*wornitems_ref*/
4344
                + sizeof( unsigned short )                            /*ar_*/
4345
                + sizeof( Items::UWeapon* )                           /*weapon*/
4346
                + sizeof( Items::UArmor* )                            /*shield*/
4347
                + sizeof( unsigned char )                             /*concealed_*/
4348
                + sizeof( unsigned short )                            /*stealthsteps_*/
4349
                + sizeof( unsigned int )                              /*mountedsteps_*/
4350
                + privs.estimatedSize() + settings.estimatedSize() +
2✔
4351
                sizeof( Core::UOExecutor* )                  /*script_ex*/
4352
                + sizeof( Character* )                       /*opponent_*/
4353
                + sizeof( Core::polclock_t )                 /*swing_timer_start_clock_*/
4354
                + sizeof( Core::OneShotTask* )               /*swing_task*/
4355
                + sizeof( Core::OneShotTask* )               /*spell_task*/
4356
                + sizeof( Core::gameclock_t )                /*created_at*/
4357
                + sizeof( Core::polclock_t )                 /*criminal_until_*/
4358
                + sizeof( Core::OneShotTask* )               /*repsys_task_*/
4359
                + sizeof( Core::OneShotTask* )               /*party_decline_timeout_*/
4360
                + sizeof( Core::AttributeFlags<PRIV_FLAGS> ) /*cached_settings*/
4361
                + sizeof( Core::AttributeFlags<MOB_FLAGS> )  /*mob_flags_*/
2✔
4362
      ;
4363

4364
  size += Clib::memsize( attributes ) + Clib::memsize( vitals ) + Clib::memsize( armor_ ) +
2✔
4365
          Clib::memsize( remote_containers_ ) + Clib::memsize( opponent_of ) +
2✔
4366
          Clib::memsize( aggressor_to_ ) + Clib::memsize( lawfully_damaged_ ) +
2✔
4367
          Clib::memsize( to_be_reportable_ ) + Clib::memsize( reportable_ ) +
2✔
4368
          Clib::memsize( buffs_ );
2✔
4369
  return size;
2✔
4370
}
4371

4372
void Character::on_delete_from_account()
3✔
4373
{
4374
  if ( realm() )
3✔
4375
    realm()->remove_mobile( *this, Realms::WorldChangeReason::PlayerDeleted );
3✔
4376
}
3✔
4377

4378
bool Character::has_paperdoll() const
14✔
4379
{
4380
  switch ( graphic )
14✔
4381
  {
4382
  case UOBJ_HUMAN_MALE:
14✔
4383
  case UOBJ_HUMAN_FEMALE:
4384
  case UOBJ_HUMAN_MALE_GHOST:
4385
  case UOBJ_HUMAN_FEMALE_GHOST:
4386
  case UOBJ_ELF_MALE:
4387
  case UOBJ_ELF_FEMALE:
4388
  case UOBJ_ELF_MALE_GHOST:
4389
  case UOBJ_ELF_FEMALE_GHOST:
4390
  case UOBJ_GARGOYLE_MALE:
4391
  case UOBJ_GARGOYLE_FEMALE:
4392
  case UOBJ_GARGOYLE_MALE_GHOST:
4393
  case UOBJ_GARGOYLE_FEMALE_GHOST:
4394
  case UOBJ_GAMEMASTER:
4395
  case UOBJ_LORD_BRITISH:
4396
  case UOBJ_BLACKTHORN:
4397
  case UOBJ_DUPRE:
4398
    return true;
14✔
4399
  default:
×
4400
    return false;
×
4401
  }
4402
}
4403

4404
bool Character::get_method_hook( const char* methodname, Bscript::Executor* ex,
×
4405
                                 Core::ExportScript** hook, unsigned int* PC ) const
4406
{
4407
  if ( Core::gamestate.system_hooks.get_method_hook(
×
4408
           Core::gamestate.system_hooks.character_method_script.get(), methodname, ex, hook, PC ) )
4409
    return true;
×
4410
  return base::get_method_hook( methodname, ex, hook, PC );
×
4411
}
4412

4413
void Character::update_objects_on_range_change( u8 newrange )
2✔
4414
{
4415
  Network::RemoveObjectPkt msgremove( serial_ext );
2✔
4416

4417
  Core::WorldIterator<Core::MobileFilter>::InRange(
2✔
4418
      this, std::max( newrange, los_size() ),
2✔
4419
      [&]( Mobile::Character* zonechr )
2✔
4420
      {
4421
        if ( this == zonechr || !is_visible_to_me( zonechr, false /*rangecheck*/ ) )
2✔
4422
          return;
2✔
4423
        bool was_inrange = in_range( zonechr, los_size() + zonechr->visible_size() );
×
4424
        bool is_inrange = in_range( zonechr, newrange + zonechr->visible_size() );
×
4425
        if ( was_inrange && is_inrange )
×
4426
          return;
×
4427
        if ( !was_inrange && is_inrange )
×
4428
          Core::send_owncreate( client, zonechr );
×
4429
        else if ( was_inrange && !is_inrange )
×
4430
          Core::send_remove_character( client, zonechr, msgremove );
×
4431
      } );
4432

4433
  Core::WorldIterator<Core::ItemFilter>::InRange(
2✔
4434
      this, Core::gamestate.max_update_range_multi_only() + std::max( newrange, los_size() ),
2✔
4435
      [&]( Items::Item* zoneitem )
2✔
4436
      {
4437
        bool was_inrange = in_range( zoneitem, los_size() + zoneitem->visible_size() );
8✔
4438
        bool is_inrange = in_range( zoneitem, newrange + zoneitem->visible_size() );
8✔
4439
        if ( was_inrange && is_inrange )
8✔
4440
          return;
×
4441
        if ( !was_inrange && is_inrange )
8✔
4442
          Core::send_item( client, zoneitem );
3✔
4443
        else if ( was_inrange && !is_inrange )
5✔
4444
          Core::send_remove_object( client, zoneitem, msgremove );
3✔
4445
      } );
4446

4447
  Core::WorldIterator<Core::MultiFilter>::InRange(
2✔
4448
      this, Core::gamestate.max_update_range_multi_only() + std::max( newrange, los_size() ),
2✔
4449
      [&]( Multi::UMulti* zonemulti )
2✔
4450
      {
4451
        bool was_inrange = in_range( zonemulti, los_size() + zonemulti->visible_size() );
×
4452
        bool is_inrange = in_range( zonemulti, newrange + zonemulti->visible_size() );
×
4453
        if ( was_inrange && is_inrange )
×
4454
          return;
×
4455
        if ( !was_inrange && is_inrange )
×
4456
        {
4457
          Core::send_multi( client, zonemulti );
×
4458
          Multi::UHouse* house = zonemulti->as_house();
×
4459
          if ( client->acctSupports( Plib::ExpansionVersion::AOS ) && house != nullptr &&
×
4460
               house->IsCustom() )
×
4461
            Multi::CustomHousesSendShort( house, client );
×
4462
        }
×
4463
        else if ( was_inrange && !is_inrange )
×
4464
          Core::send_remove_object( client, zonemulti, msgremove );
×
4465
      } );
4466
}
2✔
4467

4468
AttributeValue::AttributeValue()
5,096✔
4469
    : _base( 0 ), _temp( 0 ), _intrinsic( 0 ), _lockstate( 0 ), _cap( 0 )
5,096✔
4470
{
4471
}
5,096✔
4472
int AttributeValue::effective() const
727✔
4473
{
4474
  int v = _base;
727✔
4475
  v += _temp;
727✔
4476
  v += _intrinsic;
727✔
4477
  return ( v > 0 ) ? ( v / 10 ) : 0;
727✔
4478
}
4479
int AttributeValue::effective_tenths() const
110✔
4480
{
4481
  int v = _base;
110✔
4482
  v += _temp;
110✔
4483
  v += _intrinsic;
110✔
4484
  return ( v > 0 ) ? v : 0;
110✔
4485
}
4486
int AttributeValue::base() const
1,345✔
4487
{
4488
  return _base;
1,345✔
4489
}
4490
void AttributeValue::base( unsigned short base )
318✔
4491
{
4492
  passert( base <= ATTRIBUTE_MAX_BASE );
318✔
4493
  _base = base;
318✔
4494
}
318✔
4495
int AttributeValue::temp_mod() const
×
4496
{
4497
  return _temp;
×
4498
}
4499
void AttributeValue::temp_mod( short temp )
×
4500
{
4501
  _temp = temp;
×
4502
}
×
4503
int AttributeValue::intrinsic_mod() const
×
4504
{
4505
  return _intrinsic;
×
4506
}
4507
void AttributeValue::intrinsic_mod( short val )
×
4508
{
4509
  _intrinsic = val;
×
4510
}
×
4511
unsigned char AttributeValue::lock() const
1,242✔
4512
{
4513
  return _lockstate;
1,242✔
4514
}
4515
void AttributeValue::lock( unsigned char lockstate )
12✔
4516
{
4517
  _lockstate = lockstate;
12✔
4518
}
12✔
4519
unsigned short AttributeValue::cap() const
1,148✔
4520
{
4521
  return _cap;
1,148✔
4522
}
4523
void AttributeValue::cap( unsigned short cap )
5,108✔
4524
{
4525
  _cap = cap;
5,108✔
4526
}
5,108✔
4527

4528
VitalValue::VitalValue() : _current( 0 ), _maximum( 0 ), _regenrate( 0 ) {}
294✔
4529
int VitalValue::current() const
11✔
4530
{
4531
  return _current;
11✔
4532
}
4533
int VitalValue::current_ones() const
1,299✔
4534
{
4535
  return _current / 100;
1,299✔
4536
}
4537
int VitalValue::current_thousands() const
67✔
4538
{
4539
  // use division to prevent overflow
4540
  return ( _current / 100 ) * 1000 / ( _maximum / 100 );
67✔
4541
}
4542
int VitalValue::maximum() const
231✔
4543
{
4544
  return _maximum;
231✔
4545
}
4546
int VitalValue::maximum_ones() const
786✔
4547
{
4548
  return _maximum / 100;
786✔
4549
}
4550
bool VitalValue::is_at_maximum() const
38✔
4551
{
4552
  return ( _current >= _maximum );
38✔
4553
}
4554
int VitalValue::regenrate() const
114✔
4555
{
4556
  return _regenrate;
114✔
4557
}
4558
void VitalValue::current( int cur )
313✔
4559
{
4560
  _current = cur;
313✔
4561
  if ( _current > _maximum )
313✔
4562
    _current = _maximum;
×
4563
}
313✔
4564
void VitalValue::current_ones( int ones )
79✔
4565
{
4566
  current( ones * 100 );
79✔
4567
}
79✔
4568
void VitalValue::maximum( int val )
264✔
4569
{
4570
  _maximum = val;
264✔
4571
  if ( _current > _maximum )
264✔
4572
    current( _maximum );
×
4573
}
264✔
4574
void VitalValue::regenrate( int rate )
264✔
4575
{
4576
  _regenrate = rate;
264✔
4577
}
264✔
4578
bool VitalValue::consume( unsigned int hamt )
13✔
4579
{
4580
  if ( _current > hamt )
13✔
4581
  {
4582
    _current -= hamt;
13✔
4583
    return true;
13✔
4584
  }
4585
  _current = 0;
×
4586
  return false;
×
4587
}
4588
void VitalValue::produce( unsigned int hamt )
114✔
4589
{
4590
  unsigned newcur = _current + hamt;
114✔
4591
  if ( newcur > _maximum || newcur < _current )
114✔
4592
  {
4593
    _current = _maximum;
113✔
4594
  }
4595
  else
4596
  {
4597
    _current = newcur;
1✔
4598
  }
4599
}
114✔
4600

4601
}  // namespace Mobile
4602
}  // 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