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

polserver / polserver / 13163655424

05 Feb 2025 06:02PM UTC coverage: 58.476% (+0.4%) from 58.057%
13163655424

push

github

web-flow
Expansion helper classes for central handling of features (#756)

* use uoexpansion enums for A9, B9 flags
added TOL to uoexpansion
currently unsure where this will end

simplified account initialisation, added errorcheck

* Expansion classes for server and account
fixed flags

* use of AccountExpansion,ServerExpansion
extended tests
splitted unittest file
added missing test for Min/MaxAttackRange

* more micro performance for world saving
use all 4 cores in CI builds
fix compiler flag warning on windows

* save test for weight_multiplier

* enable gothic,rustictiles when using HSA expansion

* removed ancient unused files

* addressed comments
renamed ServerExpansion to ServerFeatures
added method for expansion name

* got rid of client UOExpansionFlag which is the same as the accout
expansion

* updated core-changes and docs

* updated generated ssopt

* test for tooltips
changed method names
removed/clarified comments

* npc hitchance was never saved
added missing npc save tests

* hitchance bug exists also for items...
added missing test for movemode

465 of 525 new or added lines in 30 files covered. (88.57%)

16 existing lines in 7 files now uncovered.

41768 of 71427 relevant lines covered (58.48%)

388521.09 hits per line

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

61.59
/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 <stdlib.h>
88
#include <string>
89

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

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

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

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

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

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

226
  Core::gamestate.armor_zone_chance_sum = 0;
3✔
227
  Core::gamestate.armorzones.clear();
3✔
228

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

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

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

260

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

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

341
  set_caps_to_default();
77✔
342

343
  // vector
344
  refresh_cached_settings( false );
77✔
345
  ++Core::stateManager.uobjcount.ucharacter_count;
77✔
346
}
77✔
347

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

362
  if ( client && ( client->chr == this ) )
77✔
363
    client->chr = nullptr;
×
364
  client = nullptr;
77✔
365

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

372
  removal_cleanup();
77✔
373

374
  // clean up wornitems, so it can be reaped by the objecthash later
375
  wornitems->destroy();
77✔
376

377
  // clean up trade container if it exists
378
  if ( trading_cont != nullptr )
77✔
379
    trading_cont->destroy();
×
380

381
  if ( repsys_task_ != nullptr )
77✔
382
    repsys_task_->cancel();
2✔
383

384
  if ( party_decline_timeout_ != nullptr )
77✔
385
    party_decline_timeout_->cancel();
×
386

387
  --Core::stateManager.uobjcount.ucharacter_count;
77✔
388
}
98✔
389

390
void Character::removal_cleanup()
79✔
391
{
392
  clear_opponent_of();
79✔
393

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

565

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

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

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

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

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

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

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

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

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

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

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

622

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

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

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

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

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

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

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

684

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

991

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

996
        av.lock( lock );
10✔
997

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

1003
  calc_vital_stuff();
11✔
1004

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

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

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

1035

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

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

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

1053
  cached_settings.reset();
146✔
1054

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

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

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

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

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

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

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

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

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

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

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

1161
  return false;
×
1162
}
1163

1164
bool Character::can_move( const Items::Item* item ) const
6,078✔
1165
{
1166
  if ( item->objtype_ != UOBJ_CORPSE )
6,078✔
1167
  {
1168
    return cached_settings.get( PRIV_FLAGS::MOVE_ANY ) || item->movable();
6,063✔
1169
  }
1170
  else
1171
  {
1172
    return false;
15✔
1173
  }
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_rename( const Character* chr ) const
×
1183
{
1184
  return cached_settings.get( PRIV_FLAGS::RENAME_ANY ) || chr->can_be_renamed_by( this );
×
1185
}
1186

1187
bool Character::can_clothe( const Character* chr ) const
×
1188
{
1189
  return ( ( chr == this ) || cached_settings.get( PRIV_FLAGS::CLOTHE_ANY ) );
×
1190
}
1191

1192
bool Character::can_hearghosts() const
×
1193
{
1194
  return cached_settings.get( PRIV_FLAGS::HEAR_GHOSTS );
×
1195
}
1196

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

1202
bool Character::can_moveanydist() const
9✔
1203
{
1204
  return cached_settings.get( PRIV_FLAGS::MOVE_ANY_DIST );
9✔
1205
}
1206

1207
bool Character::can_plogany() const
×
1208
{
1209
  return cached_settings.get( PRIV_FLAGS::PLOG_ANY );
×
1210
}
1211

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

1217
bool Character::can_freemove() const
62✔
1218
{
1219
  return cached_settings.get( PRIV_FLAGS::FREEMOVE );
62✔
1220
}
1221

1222
Core::UContainer* Character::backpack() const
174✔
1223
{
1224
  return static_cast<Core::UContainer*>( wornitems->GetItemOnLayer( Core::LAYER_BACKPACK ) );
174✔
1225
}
1226

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

1237
  Core::UContainer* cont = backpack();
×
1238
  if ( cont != nullptr )
×
1239
  {
1240
    for ( Core::UContainer::const_iterator itr = cont->begin(); itr != cont->end(); ++itr )
×
1241
    {
1242
      const Items::Item* item = *itr;
×
1243

1244
      if ( item != nullptr && item->script_isa( Core::POLCLASS_SPELLBOOK ) )
×
1245
      {
1246
        const Core::Spellbook* book = static_cast<const Core::Spellbook*>( item );
×
1247
        if ( book->spell_school == school )
×
1248
          return const_cast<Core::Spellbook*>( book );
×
1249
      }
1250
    }
1251
  }
1252
  return nullptr;
×
1253
}
1254

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1384
  return true;
32✔
1385
}
1386

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

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

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

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

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

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

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

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

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

1453

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

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

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

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

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

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

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

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

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

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

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

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

1577
  vv.maximum( mv );
201✔
1578

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

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

1586
  vv.regenrate( rr );
201✔
1587

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

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

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

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

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

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

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

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

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

1633
  return true;
×
1634
}
1635

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

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

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

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

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

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

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

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

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

1729
  return flag1;
122✔
1730
}
1731

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

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

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

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

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

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

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

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

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

1776
  send_update_hits_to_inrange( this );
×
1777

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

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

1784

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

1791
  int absorbed = blocked / 2;
×
1792

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

1797
  if ( damage >= 2.0 )
×
1798
  {
1799
    return static_cast<unsigned short>( damage * 0.5 );
×
1800
  }
1801
  else
1802
  {
1803
    int dmg = static_cast<int>( damage );
×
1804
    if ( dmg >= 0 )
×
1805
      return static_cast<unsigned short>( dmg );
×
1806
    else
1807
      return 0;
×
1808
  }
1809
}
1810

1811

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

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

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

1834
  return damage;
×
1835
}
1836

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

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

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

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

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

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

1870

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

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

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

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

1908

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

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

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

1930
  check_undamaged();
×
1931

1932
  send_update_hits_to_inrange( this );
×
1933
}
1934

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

1954

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

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

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

1991
  color = truecolor;
×
1992

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

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

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

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

2035
  // equip( create_backpack() );
2036

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

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

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

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

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

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

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

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

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

2100
  send_create_mobile_to_nearby_cansee( this );
1✔
2101

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

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

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

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

2133

2134
  u16 save_graphic = graphic;
52✔
2135

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

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

2161
  UPDATE_CHECKPOINT();
52✔
2162
  /* FIXME: corpse container difficulties.
2163

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

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

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

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

2187
  UPDATE_CHECKPOINT();
52✔
2188

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

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

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

2207
  // small lambdas to reduce the mess inside the loops
2208
  auto _copy_item = [&]( Items::Item* _item ) {  // copy a item into the corpse
1✔
2209
    Items::Item* copy = _item->clone();
1✔
2210
    copy->movable( false );
1✔
2211
    corpse->equip_and_add( copy, copy->layer );
1✔
2212
  };
53✔
2213
  auto _drop_item_to_world = [&]( Items::Item* _item ) {  // places the item onto the corpse coords
×
2214
    _item->setposition( corpse->pos() );
×
2215
    add_item_to_world( _item );
×
2216
    register_with_supporting_multi( _item );
×
2217
    move_item( _item, corpse->pos() );
×
2218
  };
52✔
2219

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

2247
    if ( item->layer == Core::LAYER_MOUNT &&
2✔
2248
         item->objtype_ == Core::settingsManager.extobj.boatmount )
×
2249
    {
2250
      Multi::UMulti* multi = realm()->find_supporting_multi( pos3d() );
×
2251

2252
      // Clear the pilot from the boat
2253
      if ( multi != nullptr && multi->script_isa( Core::POLCLASS_BOAT ) )
×
2254
      {
2255
        Multi::UBoat* boat = static_cast<Multi::UBoat*>( multi );
×
2256
        if ( boat->pilot() == this )
×
2257
        {
2258
          boat->clear_pilot();
×
2259
          continue;
×
2260
        }
2261
      }
2262

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

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

2291
    u8 newSlot = 1;
2✔
2292
    if ( !corpse->can_add_to_slot( newSlot ) || !item->slot_index( newSlot ) )
2✔
2293
    {
2294
      _drop_item_to_world( item );
×
2295
    }
2296
    else
2297
    {
2298
      corpse->equip_and_add( item, layer );
2✔
2299
    }
2300
    UPDATE_CHECKPOINT();
2✔
2301
  }
2302
  UPDATE_CHECKPOINT();
52✔
2303

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

2357
    UPDATE_CHECKPOINT();
2✔
2358

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

2405

2406
  UPDATE_CHECKPOINT();
52✔
2407
  send_death_message( this, corpse );
52✔
2408

2409
  UPDATE_CHECKPOINT();
52✔
2410
  corpse->restart_decay_timer();
52✔
2411
  UPDATE_CHECKPOINT();
52✔
2412
  add_item_to_world( corpse );
52✔
2413
  UPDATE_CHECKPOINT();
52✔
2414
  send_item_to_inrange( corpse );
52✔
2415

2416
  UPDATE_CHECKPOINT();
52✔
2417

2418
  clear_opponent_of();
52✔
2419

2420
  set_opponent( nullptr );
52✔
2421

2422
  UPDATE_CHECKPOINT();
52✔
2423

2424
  on_death( corpse );
52✔
2425

2426
  CLEAR_CHECKPOINT();
52✔
2427
}
2428

2429
void Character::refresh_ar()
131✔
2430
{
2431
  //  find_armor(); <-- MuadDib commented out, put code inside here to cut down on iter.
2432
  // Figure out what's in each zone
2433
  //   FIXME? NZONES * NLAYERS (5 * 24 = 124) iterations.
2434
  // okay, reverse, for each wornitem, for each coverage area, upgrade.
2435
  // Turley: should be fixed now only iterators over armor's coverage zones instead of all zones
2436
  for ( unsigned zone = 0; zone < Core::gamestate.armorzones.size(); ++zone )
917✔
2437
    armor_[zone] = nullptr;
786✔
2438
  // we need to reset each resist to 0, then add the base back using calc.
2439
  resetEquipableProperties();
131✔
2440

2441
  for ( unsigned layer = Core::LAYER_EQUIP__LOWEST; layer <= Core::LAYER_EQUIP__HIGHEST; ++layer )
3,406✔
2442
  {
2443
    Items::Item* item = wornitems->GetItemOnLayer( layer );
3,275✔
2444
    if ( item == nullptr )
3,275✔
2445
      continue;
3,146✔
2446
    // Let's check all items as base, and handle their element_resists.
2447
    updateEquipableProperties( item );
129✔
2448
    if ( item->isa( Core::UOBJ_CLASS::CLASS_ARMOR ) )
129✔
2449
    {
2450
      Items::UArmor* armor = static_cast<Items::UArmor*>( item );
×
2451
      std::set<unsigned short> tmplzones = armor->tmplzones();
×
2452
      std::set<unsigned short>::iterator itr;
×
2453
      for ( itr = tmplzones.begin(); itr != tmplzones.end(); ++itr )
×
2454
      {
2455
        if ( ( armor_[*itr] == nullptr ) || ( armor->ar() > armor_[*itr]->ar() ) )
×
2456
          armor_[*itr] = armor;
×
2457
      }
2458
    }
×
2459
  }
2460

2461
  //  calculate_ar();  <-- MuadDib Commented out, mixed code within ported find_armor to reduce
2462
  // iter.
2463
  double new_ar = 0.0;
131✔
2464
  for ( unsigned zone = 0; zone < Core::gamestate.armorzones.size(); ++zone )
917✔
2465
  {
2466
    Items::UArmor* armor = armor_[zone];
786✔
2467
    if ( armor != nullptr )
786✔
2468
    {
2469
      new_ar += armor->ar() * Core::gamestate.armorzones[zone].chance;
×
2470
    }
2471
  }
2472

2473
  /* add AR due to shield : parry skill / 2 is percent of AR */
2474
  // FIXME: Should we allow this to be adjustable via a prop? Hrmmmmm
2475
  if ( shield != nullptr )
131✔
2476
  {
2477
    double add =
2478
        0.5 * 0.01 * shield->ar() * attribute( Core::gamestate.pAttrParry->attrid ).effective();
×
2479
    if ( add > 1.0 )
×
2480
      new_ar += add;
×
2481
    else
2482
      new_ar += 1.0;
×
2483
  }
2484

2485
  new_ar += ar_mod();
131✔
2486

2487
  short s_new_ar = static_cast<short>( new_ar );
131✔
2488
  if ( s_new_ar >= 0 )
131✔
2489
    ar_ = s_new_ar;
131✔
2490
  else
2491
    ar_ = 0;
×
2492

2493
  if ( client != nullptr )
131✔
2494
  {  // CHECKME consider sending less frequently
2495
    send_full_statmsg( client, this );
6✔
2496
  }
2497
}
131✔
2498

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

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

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

2563
  // calc defence increase if lower than cap
2564
  if ( item->has_defence_increase() )
129✔
2565
    defence_increase( defence_increase().addToValue( item->defence_increase() ) );
34✔
2566

2567
  if ( client != nullptr )
129✔
2568
  {  // CHECKME consider sending less frequently
2569
    send_full_statmsg( client, this );
9✔
2570
  }
2571
}
129✔
2572

2573
void Character::resetEquipableProperties()
131✔
2574
{
2575
  // reset resists
2576
  if ( has_fire_resist() )
131✔
2577
    fire_resist( fire_resist().setAsValue( 0 ) );
11✔
2578
  if ( has_cold_resist() )
131✔
2579
    cold_resist( cold_resist().setAsValue( 0 ) );
13✔
2580
  if ( has_energy_resist() )
131✔
2581
    energy_resist( energy_resist().setAsValue( 0 ) );
12✔
2582
  if ( has_poison_resist() )
131✔
2583
    poison_resist( poison_resist().setAsValue( 0 ) );
9✔
2584
  if ( has_physical_resist() )
131✔
2585
    physical_resist( physical_resist().setAsValue( 0 ) );
10✔
2586

2587
  // reset caps
2588
  if ( has_fire_resist_cap() )
131✔
2589
    fire_resist_cap( fire_resist_cap().setAsValue( 0 ) );
25✔
2590
  if ( has_cold_resist_cap() )
131✔
2591
    cold_resist_cap( cold_resist_cap().setAsValue( 0 ) );
33✔
2592
  if ( has_energy_resist_cap() )
131✔
2593
    energy_resist_cap( energy_resist_cap().setAsValue( 0 ) );
29✔
2594
  if ( has_poison_resist_cap() )
131✔
2595
    poison_resist_cap( poison_resist_cap().setAsValue( 0 ) );
17✔
2596
  if ( has_physical_resist_cap() )
131✔
2597
    physical_resist_cap( physical_resist_cap().setAsValue( 0 ) );
21✔
2598

2599
  // reset damages
2600
  if ( has_fire_damage() )
131✔
2601
    fire_damage( fire_damage().setAsValue( 0 ) );
93✔
2602
  if ( has_cold_damage() )
131✔
2603
    cold_damage( cold_damage().setAsValue( 0 ) );
95✔
2604
  if ( has_energy_damage() )
131✔
2605
    energy_damage( energy_damage().setAsValue( 0 ) );
94✔
2606
  if ( has_poison_damage() )
131✔
2607
    poison_damage( poison_damage().setAsValue( 0 ) );
91✔
2608
  if ( has_physical_damage() )
131✔
2609
    physical_damage( physical_damage().setAsValue( 0 ) );
92✔
2610

2611
  // reset others
2612
  if ( has_lower_reagent_cost() )
131✔
2613
    lower_reagent_cost( lower_reagent_cost().setAsValue( 0 ) );
59✔
2614
  if ( has_spell_damage_increase() )
131✔
2615
    spell_damage_increase( spell_damage_increase().setAsValue( 0 ) );
57✔
2616
  if ( has_faster_casting() )
131✔
2617
    faster_casting( faster_casting().setAsValue( 0 ) );
62✔
2618
  if ( has_faster_cast_recovery() )
131✔
2619
    faster_cast_recovery( faster_cast_recovery().setAsValue( 0 ) );
63✔
2620

2621
  if ( has_defence_increase() )
131✔
2622
    defence_increase( defence_increase().setAsValue( 0 ) );
37✔
2623
  if ( has_defence_increase_cap() )
131✔
2624
    defence_increase_cap( defence_increase_cap().setAsValue( 0 ) );
41✔
2625
  if ( has_lower_mana_cost() )
131✔
2626
    lower_mana_cost( lower_mana_cost().setAsValue( 0 ) );
60✔
2627
  if ( has_hit_chance() )
131✔
2628
    hit_chance( hit_chance().setAsValue( 0 ) );
61✔
2629
  if ( has_luck() )
131✔
2630
    luck( luck().setAsValue( 0 ) );
58✔
2631
  if ( has_swing_speed_increase() )
131✔
2632
    swing_speed_increase( swing_speed_increase().setAsValue( 0 ) );
59✔
2633
  if ( has_min_attack_range_increase() )
131✔
2634
    min_attack_range_increase( min_attack_range_increase().setAsValue( 0 ) );
55✔
2635
  if ( has_max_attack_range_increase() )
131✔
2636
    max_attack_range_increase( max_attack_range_increase().setAsValue( 0 ) );
55✔
2637
}
131✔
2638

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

2656
/* check skill: test skill, advance, reset atrophy timers, blah blah..
2657
   obviously, needs work, and more parameters.
2658
   */
2659

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

2679
void Character::check_concealment_level()
×
2680
{
2681
  if ( concealed() > cmdlevel() )
×
2682
    concealed( cmdlevel() );
×
2683
}
×
2684

2685
// you can only be concealed from
2686
// those of lower stature
2687
bool Character::is_concealed_from_me( const Character* chr ) const
107✔
2688
{
2689
  return ( chr->concealed() > cmdlevel() );
107✔
2690
}
2691

2692
bool Character::is_visible_to_me( const Character* chr, bool check_range ) const
169✔
2693
{
2694
  if ( chr == nullptr )
169✔
2695
    return false;
×
2696
  if ( chr == this )
169✔
2697
    return true;  // I can always see myself (?)
78✔
2698
  if ( is_concealed_from_me( chr ) )
91✔
2699
    return false;
×
2700
  if ( !chr->logged_in() )
91✔
2701
    return false;
×
2702
  if ( chr->hidden() && !cached_settings.get( PRIV_FLAGS::SEE_HIDDEN ) )
91✔
2703
    return false;  // noone can see anyone hidden.
×
2704
  if ( check_range )
91✔
2705
  {
2706
    if ( !in_visual_range( chr ) )
65✔
2707
      return false;
18✔
2708
  }
2709
  else if ( chr->realm() != this->realm() )
26✔
2710
    return false;
×
2711

2712
  if ( dead() )
73✔
2713
    return true;  // If I'm dead, I can see anything
×
2714
  if ( !chr->dead() || cached_settings.get( PRIV_FLAGS::SEE_GHOSTS ) )
73✔
2715
    return true;  // Anyone can see the living
73✔
2716
  if ( chr->warmode() )
×
2717
    return true;  // Anyone can see someone in warmode
×
2718
  return false;
×
2719
};
2720

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

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

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

2797
  // iter over all old in range players and send remove
2798
  Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
129✔
2799
      chr->lastpos,
129✔
2800
      [&]( Character* zonechr )
129✔
2801
      {
2802
        Client* client = zonechr->client;
89✔
2803
        if ( !zonechr->in_visual_range( nullptr, chr->lastpos ) )
89✔
2804
          return;
22✔
2805
        if ( !zonechr->is_visible_to_me( chr, /*check_range*/ false ) )
67✔
2806
          return;
×
2807

2808
        if ( zonechr->in_visual_range( chr ) )  // already handled
67✔
2809
          return;
56✔
2810
        // if we just walked out of range of this character, send its
2811
        // client a remove object, or else a ghost character will remain.
2812
        send_remove_character( client, chr, msgremove );
11✔
2813
      } );
2814
}
129✔
2815

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

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

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

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

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

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

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

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

2876
void Character::reset_swing_timer()
82✔
2877
{
2878
  INFO_PRINTLN_TRACE( 15 )( "reset_swing_timer({:#x})", this->serial );
82✔
2879
  mob_flags_.remove( MOB_FLAGS::READY_TO_SWING );
82✔
2880

2881
  swing_timer_start_clock_ = Core::polclock();
82✔
2882
  if ( swing_task )
82✔
2883
    swing_task->cancel();
6✔
2884

2885
  if ( opponent_ || !opponent_of.empty() )
82✔
2886
  {
2887
    schedule_attack();
13✔
2888
  }
2889
}
82✔
2890

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

2897
  swing_timer_start_clock_ = Core::polclock();
×
2898
  if ( swing_task )
×
2899
    swing_task->cancel();
×
2900

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

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

2926
bool Character::is_attackable( Character* who ) const
23✔
2927
{
2928
  passert( who != nullptr );
23✔
2929
  if ( Core::settingsManager.combat_config.scripted_attack_checks )
23✔
2930
  {
2931
    INFO_PRINTLN_TRACE( 21 )
×
2932
    ( "is_attackable({:#x},{:#x}): will be handled by combat hook.", this->serial, who->serial );
×
2933
    return true;
×
2934
  }
2935
  else
2936
  {
2937
    INFO_PRINTLN_TRACE( 21 )
23✔
2938
    ( "is_attackable({:#x},{:#x}):\n"
×
2939
      "  who->dead:  {}\n"
2940
      "  wpn->inrange: {}\n"
2941
      "  hidden:     {}\n"
2942
      "  who->hidden:  {}\n"
2943
      "  concealed:  {}",
2944
      this->serial, who->serial, who->dead(), weapon->in_range( this, who ), hidden(),
×
2945
      who->hidden(), is_concealed_from_me( who ) );
×
2946
    if ( who->dead() )
23✔
2947
      return false;
×
2948
    else if ( !weapon->in_range( this, who ) )
23✔
2949
      return false;
3✔
2950
    else if ( hidden() && !cached_settings.get( PRIV_FLAGS::HIDDEN_ATTACK ) )
20✔
2951
      return false;
2✔
2952
    else if ( who->hidden() && !cached_settings.get( PRIV_FLAGS::ATTACK_HIDDEN ) )
18✔
2953
      return false;
2✔
2954
    else if ( is_concealed_from_me( who ) )
16✔
2955
      return false;
×
2956
    else if ( !realm()->has_los( *this, *who ) )
16✔
2957
      return false;
×
2958
    else
2959
      return true;
16✔
2960
  }
2961
}
2962

2963
Character* Character::get_attackable_opponent() const
149✔
2964
{
2965
  if ( opponent_ != nullptr )
149✔
2966
  {
2967
    INFO_PRINTLN_TRACE( 20 )
12✔
2968
    ( "get_attackable_opponent({:#x}): checking opponent {:#x}", this->serial, opponent_->serial );
×
2969
    if ( is_attackable( opponent_ ) )
12✔
2970
      return opponent_;
9✔
2971
  }
2972

2973
  if ( !opponent_of.empty() )
140✔
2974
  {
2975
    for ( auto& who : opponent_of )
15✔
2976
    {
2977
      INFO_PRINTLN_TRACE( 20 )
11✔
2978
      ( "get_attackable_opponent({:#x}): checking opponent_of {:#x}", this->serial, who->serial );
×
2979
      if ( is_attackable( who ) )
11✔
2980
        return who;
7✔
2981
    }
2982
  }
2983

2984
  return nullptr;
133✔
2985
}
2986

2987
void Character::send_highlight() const
69✔
2988
{
2989
  if ( client != nullptr && has_active_client() )
69✔
2990
  {
2991
    Character* opponent = get_opponent();
4✔
2992

2993
    Network::PktHelper::PacketOut<Network::PktOut_AA> msg;
4✔
2994
    if ( opponent != nullptr )
4✔
2995
      msg->Write<u32>( opponent->serial_ext );
×
2996
    else
2997
      msg->offset += 4;
4✔
2998
    msg.Send( client );
4✔
2999
  }
4✔
3000
}
69✔
3001

3002
void Character::on_swing_failure( Character* /*attacker*/ )
×
3003
{
3004
  // do nothing
3005
}
×
3006

3007
void Character::inform_disengaged( Character* /*disengaged*/ )
×
3008
{
3009
  // someone has just disengaged. If we don't have an explicit opponent,
3010
  // pick one of those that has us targetted as the highlight character.
3011
  if ( opponent_ == nullptr )
×
3012
    send_highlight();
×
3013
}
×
3014

3015
void Character::inform_engaged( Character* /*engaged*/ )
1✔
3016
{
3017
  // someone has targetted us.  If we don't have an explicit opponent,
3018
  // pick one of those that has us targetted as the highlight character.
3019
  if ( opponent_ == nullptr )
1✔
3020
    send_highlight();
1✔
3021
}
1✔
3022

3023
void Character::inform_criminal( Character* /*moved*/ )
×
3024
{
3025
  // virtual that does nothing at character level, but fires event for NPCs
3026
}
×
3027

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

3033
void Character::inform_enteredarea( Character* /*whoentered*/ )
5✔
3034
{
3035
  // virtual that does nothing at character level, but fires event for NPCs
3036
}
5✔
3037

3038
void Character::inform_moved( Character* /*moved*/ )
31✔
3039
{
3040
  // consider moving PropagateMove here!
3041
}
31✔
3042
void Character::inform_imoved( Character* /*chr*/ ) {}
23✔
3043

3044
void Character::set_opponent( Character* new_opponent, bool inform_old_opponent )
66✔
3045
{
3046
  INFO_PRINTLN_TRACE( 12 )
66✔
3047
  ( "set_opponent({:#x},{:#x})", this->serial, new_opponent != nullptr ? new_opponent->serial : 0 );
×
3048
  if ( new_opponent != nullptr )
66✔
3049
  {
3050
    if ( new_opponent->dead() )
4✔
3051
      return;
×
3052

3053
    if ( !warmode() && ( script_isa( Core::POLCLASS_NPC ) || has_active_client() ) )
4✔
3054
      set_warmode( true );
1✔
3055
  }
3056

3057
  if ( opponent_ != nullptr )
66✔
3058
  {
3059
    opponent_->opponent_of.erase( this );
4✔
3060
    // Turley 05/26/09 no need to send disengaged event on shutdown
3061
    if ( !Clib::exit_signalled )
4✔
3062
    {
3063
      if ( inform_old_opponent && opponent_ != nullptr )
4✔
3064
        opponent_->inform_disengaged( this );
4✔
3065
    }
3066
  }
3067

3068
  opponent_ = new_opponent;
66✔
3069

3070

3071
  // Turley 05/26/09 possible shutdown crashfix during cleanup
3072
  // (inside schedule_attack() the rest is also senseless on shutdowncleanup)
3073
  if ( !Clib::exit_signalled )
66✔
3074
  {
3075
    reset_swing_timer();
66✔
3076

3077
    if ( opponent_ != nullptr )
66✔
3078
    {
3079
      repsys_on_attack( opponent_ );
4✔
3080
      if ( opponent_->get_opponent() == nullptr )
4✔
3081
        opponent_->reset_swing_timer();
4✔
3082

3083
      opponent_->opponent_of.insert( this );
4✔
3084

3085
      opponent_->inform_engaged( this );
4✔
3086

3087
      opponent_->schedule_attack();
4✔
3088
    }
3089

3090
    send_highlight();
66✔
3091
  }
3092
}
3093

3094
void Character::select_opponent( u32 opp_serial )
×
3095
{
3096
  // test for setting to same so swing timer doesn't reset
3097
  // if you double-click the same guy over and over
3098
  if ( opponent_ == nullptr || opponent_->serial != opp_serial )
×
3099
  {
3100
    Character* new_opponent = Core::find_character( opp_serial );
×
3101
    if ( new_opponent != nullptr )
×
3102
    {
3103
      if ( realm() != new_opponent->realm() )
×
3104
        return;
×
3105
      set_opponent( new_opponent );
×
3106
    }
3107
  }
3108
}
3109

3110
void Character::disable_regeneration_for( int seconds )
4✔
3111
{
3112
  time_t new_disable_time = Core::poltime() + seconds;
4✔
3113
  if ( new_disable_time > disable_regeneration_until )
4✔
3114
    disable_regeneration_until = new_disable_time;
4✔
3115
}
4✔
3116

3117
bool Character::warmode() const
156✔
3118
{
3119
  return mob_flags_.get( MOB_FLAGS::WARMODE );
156✔
3120
}
3121

3122
void Character::set_warmode( bool i_warmode )
4✔
3123
{
3124
  if ( Core::gamestate.system_hooks.warmode_change )
4✔
3125
    Core::gamestate.system_hooks.warmode_change->call( new Module::ECharacterRefObjImp( this ),
×
3126
                                                       new Bscript::BLong( i_warmode ) );
×
3127

3128
  if ( warmode() != i_warmode )
4✔
3129
  {
3130
    disable_regeneration_for( 2 );
4✔
3131
  }
3132

3133
  mob_flags_.change( MOB_FLAGS::WARMODE, i_warmode );
4✔
3134
  if ( i_warmode == false )
4✔
3135
  {
3136
    set_opponent( nullptr );
1✔
3137
  }
3138
  reset_swing_timer();
4✔
3139

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

3167
const AttributeValue& Character::weapon_attribute() const
16✔
3168
{
3169
  return attribute( weapon->attribute().attrid );
16✔
3170
}
3171

3172
unsigned short Character::random_weapon_damage() const
8✔
3173
{
3174
  return weapon->get_random_damage();
8✔
3175
}
3176

3177
unsigned short Character::min_weapon_damage() const
59✔
3178
{
3179
  return weapon->min_weapon_damage();
59✔
3180
}
3181

3182
unsigned short Character::max_weapon_damage() const
59✔
3183
{
3184
  return weapon->max_weapon_damage();
59✔
3185
}
3186

3187
void Character::damage_weapon()
8✔
3188
{
3189
  if ( !weapon->is_intrinsic() && !weapon->is_projectile() )
8✔
3190
  {
3191
    weapon->reduce_hp_from_hit();
×
3192
  }
3193
}
8✔
3194

3195
Items::UArmor* Character::choose_armor() const
8✔
3196
{
3197
  double f = Clib::random_double( Core::gamestate.armor_zone_chance_sum );
8✔
3198
  for ( unsigned zone = 0; zone < Core::gamestate.armorzones.size(); ++zone )
23✔
3199
  {
3200
    f -= Core::gamestate.armorzones[zone].chance;
23✔
3201
    if ( f <= 0.0 )
23✔
3202
    {
3203
      return armor_[zone];
8✔
3204
    }
3205
  }
3206
  return nullptr;
×
3207
}
3208

3209
Core::UACTION Character::weapon_anim() const
8✔
3210
{
3211
  if ( on_mount() )
8✔
3212
    return weapon->mounted_anim();
×
3213
  else
3214
    return weapon->anim();
8✔
3215
}
3216

3217
void Character::do_attack_effects( Character* target )
8✔
3218
{
3219
  if ( weapon->is_projectile() )
8✔
3220
  {
3221
    // 234 is hit, 238 is miss??
3222
    play_sound_effect( this, weapon->projectile_sound() );
×
3223
    play_moving_effect( this, target, weapon->projectile_anim(),
×
3224
                        9,    // Speed (??)
3225
                        0,    // Loop
3226
                        0 );  // Explode
3227
    if ( graphic >= UOBJ_HUMAN_MALE )
×
3228
    {
3229
      send_action_to_inrange( this, weapon_anim() );
×
3230
    }
3231
    else
3232
    {
3233
      send_action_to_inrange( this, Core::ACTION_LOOK_AROUND );
×
3234
    }
3235
  }
3236
  else if ( graphic >= UOBJ_HUMAN_MALE )
8✔
3237
  {
3238
    send_action_to_inrange( this, weapon_anim() );
8✔
3239
  }
3240
  else
3241
  {
3242
    send_action_to_inrange( this, Core::ACTION_LOOK_AROUND );
×
3243
  }
3244
}
8✔
3245

3246
void Character::do_hit_success_effects()
8✔
3247
{
3248
  unsigned short sound = weapon->hit_sound();
8✔
3249
  if ( sound )
8✔
3250
    play_sound_effect( this, sound );
×
3251
}
8✔
3252

3253
void Character::do_hit_failure_effects()
×
3254
{
3255
  unsigned short sound = weapon->miss_sound();
×
3256
  if ( sound )
×
3257
    play_sound_effect( this, sound );
×
3258
}
×
3259

3260
u16 Character::get_damaged_sound() const
×
3261
{
3262
  if ( gender == Plib::GENDER_MALE )
×
3263
    return SOUND_EFFECT_MALE_DEFENSE;
×
3264
  return SOUND_EFFECT_FEMALE_DEFENSE;
×
3265
}
3266

3267
void Character::do_imhit_effects()
×
3268
{
3269
  if ( Core::settingsManager.combat_config.core_hit_sounds )
×
3270
  {
3271
    play_sound_effect( this, get_damaged_sound() );
×
3272
  }
3273
  if ( objtype_ >= UOBJ_HUMAN_MALE )
×
3274
    send_action_to_inrange( this, Core::ACTION_GOT_HIT );
×
3275
}
×
3276

3277

3278
void Character::attack( Character* opponent )
8✔
3279
{
3280
  INC_PROFILEVAR( combat_operations );
8✔
3281

3282
  if ( Core::gamestate.system_hooks.attack_hook )
8✔
3283
  {
3284
    if ( Core::gamestate.system_hooks.attack_hook->call(
×
3285
             new Module::ECharacterRefObjImp( this ),
×
3286
             new Module::ECharacterRefObjImp( opponent ) ) )
×
3287
      return;
×
3288
  }
3289

3290
  if ( Core::settingsManager.watch.combat )
8✔
3291
    INFO_PRINTLN( "{} attacks {}", name(), opponent->name() );
×
3292

3293
  if ( weapon->is_projectile() )
8✔
3294
  {
3295
    Core::UContainer* bp = backpack();
×
3296
    int check_consume_hook = -1;
×
3297
    if ( Core::gamestate.system_hooks.consume_ammunition_hook )
×
3298
    {
3299
      check_consume_hook = Core::gamestate.system_hooks.consume_ammunition_hook->call_long(
×
3300
          new Module::ECharacterRefObjImp( this ), new Module::EItemRefObjImp( weapon ) );
×
3301
      if ( check_consume_hook == 0 )
×
3302
      {
3303
        return;
×
3304
      }
3305
    }
3306

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

3328
              if ( weapon->consume_projectile( cont ) == true )
×
3329
              {
3330
                projectile_check = true;
×
3331
                break;
×
3332
              }
3333
            }
3334
          }
3335
        }
3336
      }
3337
      // I'm out of projectiles.
3338
      if ( projectile_check == false )
×
3339
        return;
×
3340
    }
3341
  }
3342

3343
  repsys_on_attack( opponent );
8✔
3344
  repsys_on_damage( opponent );
8✔
3345

3346
  do_attack_effects( opponent );
8✔
3347

3348
  if ( Core::gamestate.system_hooks.combat_advancement_hook )
8✔
3349
  {
3350
    Core::gamestate.system_hooks.combat_advancement_hook->call(
×
3351
        new Module::ECharacterRefObjImp( this ), new Module::EItemRefObjImp( weapon ),
×
3352
        new Module::ECharacterRefObjImp( opponent ) );
×
3353
  }
3354

3355
  double hit_chance = ( weapon_attribute().effective() + 50.0 ) /
8✔
3356
                      ( 2.0 * ( opponent->weapon_attribute().effective() + 50.0 ) );
8✔
3357
  hit_chance += hitchance_mod() * 0.001f;
8✔
3358
  hit_chance -= opponent->evasionchance_mod() * 0.001f;
8✔
3359
  if ( Core::settingsManager.watch.combat )
8✔
3360
    INFO_PRINT( "Chance to hit: {}: ", hit_chance );
×
3361
  if ( Clib::random_double( 1.0 ) < hit_chance )
8✔
3362
  {
3363
    if ( Core::settingsManager.watch.combat )
8✔
3364
      INFO_PRINTLN( "Hit!" );
×
3365
    do_hit_success_effects();
8✔
3366

3367
    double damage = random_weapon_damage();
8✔
3368
    damage_weapon();
8✔
3369

3370
    if ( Core::settingsManager.watch.combat )
8✔
3371
      INFO_PRINTLN( "Base damage: {}", damage );
×
3372

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

3377
    damage *= damage_multiplier;
8✔
3378

3379
    if ( Core::settingsManager.watch.combat )
8✔
3380
      INFO_PRINTLN( "Damage multiplier due to tactics/STR: {} Result: {}", damage_multiplier,
×
3381
                    damage );
3382

3383
    if ( opponent->shield != nullptr )
8✔
3384
    {
3385
      if ( Core::gamestate.system_hooks.parry_advancement_hook )
×
3386
      {
3387
        Core::gamestate.system_hooks.parry_advancement_hook->call(
×
3388
            new Module::ECharacterRefObjImp( this ), new Module::EItemRefObjImp( weapon ),
×
3389
            new Module::ECharacterRefObjImp( opponent ),
×
3390
            new Module::EItemRefObjImp( opponent->shield ) );
×
3391
      }
3392

3393
      double parry_chance =
3394
          opponent->attribute( Core::gamestate.pAttrParry->attrid ).effective() / 200.0;
×
3395
      parry_chance += opponent->parrychance_mod() * 0.001f;
×
3396
      if ( Core::settingsManager.watch.combat )
×
3397
        INFO_PRINT( "Parry Chance: {}: ", parry_chance );
×
3398
      if ( Clib::random_double( 1.0 ) < parry_chance )
×
3399
      {
3400
        if ( Core::settingsManager.watch.combat )
×
3401
          INFO_PRINTLN( "{} hits deflected", opponent->shield->ar() );
×
3402
        if ( Core::settingsManager.combat_config.display_parry_success_messages &&
×
3403
             opponent->client )
×
3404
          Core::send_sysmessage( opponent->client, "You successfully parried the attack!" );
×
3405

3406
        damage -= opponent->shield->ar();
×
3407
        if ( damage < 0 )
×
3408
          damage = 0;
×
3409
      }
3410
      else
3411
      {
3412
        if ( Core::settingsManager.watch.combat )
×
3413
          INFO_PRINTLN( "failed." );
×
3414
      }
3415
    }
3416
    if ( weapon->hit_script().empty() )
8✔
3417
    {
3418
      opponent->apply_damage( damage, this, true,
×
3419
                              Core::settingsManager.combat_config.send_damage_packet );
×
3420
    }
3421
    else
3422
    {
3423
      run_hit_script( opponent, damage );
8✔
3424
    }
3425
  }
3426
  else
3427
  {
3428
    if ( Core::settingsManager.watch.combat )
×
3429
      INFO_PRINTLN( "Miss!" );
×
3430
    opponent->on_swing_failure( this );
×
3431
    do_hit_failure_effects();
×
3432
    if ( Core::gamestate.system_hooks.hitmiss_hook )
×
3433
    {
3434
      Core::gamestate.system_hooks.hitmiss_hook->call(
×
3435
          new Module::ECharacterRefObjImp( this ), new Module::ECharacterRefObjImp( opponent ) );
×
3436
    }
3437
  }
3438
}
3439

3440
void Character::check_attack_after_move( bool check_opponents_after_check )
149✔
3441
{
3442
  FUNCTION_CHECKPOINT( check_attack_after_move, 1 );
149✔
3443
  Character* opponent = get_attackable_opponent();
149✔
3444
  FUNCTION_CHECKPOINT( check_attack_after_move, 2 );
149✔
3445
  INFO_PRINTLN_TRACE( 20 )
149✔
3446
  ( "check_attack_after_move({:#x}): opponent is {:#x}", this->serial,
×
3447
    opponent != nullptr ? opponent->serial : 0 );
×
3448
  if ( opponent != nullptr &&  // and I have an opponent
165✔
3449
       !dead() &&              // If I'm not dead
165✔
3450
       ( Core::settingsManager.combat_config.attack_while_frozen ||
16✔
3451
         ( !paralyzed() && !frozen() ) ) )
×
3452
  {
3453
    FUNCTION_CHECKPOINT( check_attack_after_move, 3 );
16✔
3454
    if ( mob_flags_.get( MOB_FLAGS::READY_TO_SWING ) )  // and I can swing now,
16✔
3455
    {                                                   // do so.
3456
      FUNCTION_CHECKPOINT( check_attack_after_move, 4 );
8✔
3457
      if ( Core::settingsManager.combat_config.send_swing_packet && client != nullptr )
8✔
3458
        send_fight_occuring( client, opponent );
×
3459

3460
      // we don't want attack() to recursively cause new attacks
3461
      mob_flags_.remove( MOB_FLAGS::READY_TO_SWING );
8✔
3462
      attack( opponent );
8✔
3463
      FUNCTION_CHECKPOINT( check_attack_after_move, 5 );
8✔
3464
      reset_swing_timer();
8✔
3465
      FUNCTION_CHECKPOINT( check_attack_after_move, 6 );
8✔
3466
    }
3467
    else
3468
    {
3469
      FUNCTION_CHECKPOINT( check_attack_after_move, 7 );
8✔
3470
      INFO_PRINTLN_TRACE( 20 )( "not ready to swing" );
8✔
3471
      schedule_attack();
8✔
3472
      FUNCTION_CHECKPOINT( check_attack_after_move, 8 );
8✔
3473
    }
3474
  }
3475
  FUNCTION_CHECKPOINT( check_attack_after_move, 0 );
149✔
3476

3477
  if ( check_opponents_after_check )
149✔
3478
  {
3479
    if ( opponent_ != nullptr )
132✔
3480
      opponent_->check_attack_after_move( false );
3✔
3481

3482
    // attacking can change the opponent_of array drastically.
3483
    std::set<Character*> tmp( opponent_of );
132✔
3484
    for ( auto& chr : tmp )
135✔
3485
    {
3486
      chr->check_attack_after_move( false );
3✔
3487
    }
3488
  }
132✔
3489
}
149✔
3490

3491

3492
void Character::check_light_region_change()
105✔
3493
{
3494
  auto light_unil = lightoverride_until();
105✔
3495
  if ( light_unil < Core::read_gameclock() && light_unil != ~0u )
105✔
3496
  {
3497
    lightoverride_until( 0 );
105✔
3498
    lightoverride( -1 );
105✔
3499
  }
3500
  if ( client->gd->weather_region && client->gd->weather_region->lightoverride != -1 &&
105✔
3501
       !has_lightoverride() )
×
3502
    return;
×
3503

3504
  int newlightlevel;
3505
  if ( has_lightoverride() )
105✔
3506
    newlightlevel = lightoverride();
×
3507
  else
3508
  {
3509
    // dave 12-22 check for no regions
3510
    Core::LightRegion* light_region = Core::gamestate.lightdef->getregion( pos() );
105✔
3511
    if ( light_region != nullptr )
105✔
3512
      newlightlevel = light_region->lightlevel;
×
3513
    else
3514
      newlightlevel = Core::settingsManager.ssopt.default_light_level;
105✔
3515
  }
3516

3517
  if ( newlightlevel != client->gd->lightlevel )
105✔
3518
  {
3519
    Core::send_light( client, newlightlevel );
2✔
3520
    client->gd->lightlevel = newlightlevel;
2✔
3521
  }
3522
}
3523

3524
void Character::check_justice_region_change()
68✔
3525
{
3526
  Core::JusticeRegion* cur_justice_region = client->gd->justice_region;
68✔
3527
  Core::JusticeRegion* new_justice_region = Core::gamestate.justicedef->getregion( pos() );
68✔
3528

3529
  if ( cur_justice_region != new_justice_region )
68✔
3530
  {
3531
    if ( cur_justice_region != nullptr )
×
3532
      cur_justice_region->RunLeaveScript( client->chr );
×
3533
    if ( new_justice_region != nullptr )
×
3534
      new_justice_region->RunEnterScript( client->chr );
×
3535

3536
    // print 'leaving' message
3537
    bool printmsgs;
3538
    if ( cur_justice_region != nullptr && new_justice_region != nullptr &&
×
3539
         cur_justice_region->entertext() == new_justice_region->entertext() &&
×
3540
         cur_justice_region->leavetext() == new_justice_region->leavetext() )
×
3541
    {
3542
      printmsgs = false;
×
3543
    }
3544
    else
3545
    {
3546
      printmsgs = true;
×
3547
    }
3548

3549
    if ( printmsgs && cur_justice_region )
×
3550
    {
3551
      const std::string& leavetext = cur_justice_region->leavetext();
×
3552
      if ( !leavetext.empty() )
×
3553
      {
3554
        Core::send_sysmessage( client, leavetext );
×
3555
      }
3556
    }
3557

3558
    client->gd->justice_region = new_justice_region;
×
3559

3560
    if ( new_justice_region && new_justice_region->RunNoCombatCheck( client ) == true )
×
3561
    {
3562
      Character* opp2 = get_opponent();
×
3563
      if ( ( opp2 != nullptr && opp2->client ) )
×
3564
      {
3565
        opp2->opponent_of.erase( client->chr );
×
3566
        opp2->set_opponent( nullptr, true );
×
3567
        opp2->schedule_attack();
×
3568
        opp2->opponent_ = nullptr;
×
3569
        opp2->clear_opponent_of();
×
3570
        set_opponent( nullptr, true );
×
3571
        if ( swing_task != nullptr )
×
3572
          swing_task->cancel();
×
3573
      }
3574
    }
3575

3576

3577
    // print 'entering' message
3578
    // handle nocombat while we have entered.
3579
    if ( printmsgs && new_justice_region )
×
3580
    {
3581
      const std::string& entertext = new_justice_region->entertext();
×
3582
      if ( !entertext.empty() )
×
3583
      {
3584
        Core::send_sysmessage( client, entertext );
×
3585
      }
3586
    }
3587
  }
3588
}
68✔
3589

3590
void Character::check_music_region_change()
68✔
3591
{
3592
  Core::MusicRegion* cur_music_region = client->gd->music_region;
68✔
3593
  Core::MusicRegion* new_music_region = Core::gamestate.musicdef->getregion( pos() );
68✔
3594

3595
  // may want to consider changing every n minutes, too, even if region didn't change
3596
  if ( cur_music_region != new_music_region )
68✔
3597
  {
3598
    client->gd->music_region = new_music_region;
×
3599
    if ( new_music_region != nullptr )
×
3600
    {
3601
      Core::send_midi( client, new_music_region->getmidi() );
×
3602
    }
3603
    else
3604
    {
3605
      Core::send_midi( client, 0 );
×
3606
    }
3607
  }
3608
}
68✔
3609

3610
void Character::check_weather_region_change( bool force )  // dave changed 5/26/03 - use force
70✔
3611
                                                           // boolean if current weather region
3612
                                                           // changed type/intensity
3613
{
3614
  Core::WeatherRegion* cur_weather_region = client->gd->weather_region;
70✔
3615
  Core::WeatherRegion* new_weather_region = Core::gamestate.weatherdef->getregion( pos() );
70✔
3616

3617
  // eric 5/31/03: I don't think this is right.  it's possible to go from somewhere that has no
3618
  // weather region,
3619
  // and to walk to somewhere that doesn't have a weather region.
3620
  //
3621
  if ( force || ( cur_weather_region != new_weather_region ) )
70✔
3622
  {
3623
    if ( new_weather_region != nullptr && new_weather_region->lightoverride != -1 &&
2✔
3624
         !has_lightoverride() )
×
3625
    {
3626
      Core::send_light( client, new_weather_region->lightoverride );
×
3627
      client->gd->lightlevel = new_weather_region->lightoverride;
×
3628
    }
3629

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

3638
    if ( new_weather_region )
2✔
3639
    {
3640
      Core::send_weather( client, new_weather_region->weathertype, new_weather_region->severity,
×
3641
                          new_weather_region->aux );
×
3642
    }
3643
    else
3644
    {
3645
      Core::send_weather( client, 0xff, 0, 0 );  // turn off
2✔
3646
    }
3647
    client->gd->weather_region = new_weather_region;
2✔
3648
  }
3649
}
70✔
3650

3651
void Character::check_region_changes()
131✔
3652
{
3653
  if ( client != nullptr )
131✔
3654
  {
3655
    check_weather_region_change();
68✔
3656

3657
    check_light_region_change();
68✔
3658

3659
    check_justice_region_change();
68✔
3660

3661
    check_music_region_change();
68✔
3662
  }
3663
}
131✔
3664

3665
void Character::position_changed()
199✔
3666
{
3667
  wornitems->setposition( pos() );
199✔
3668
  position_changed_at_ = Core::polclock();
199✔
3669
}
199✔
3670

3671
void Character::unhide()
3✔
3672
{
3673
  if ( Core::gamestate.system_hooks.un_hide )
3✔
3674
  {
3675
    if ( !Core::gamestate.system_hooks.un_hide->call( make_mobileref( this ) ) )
×
3676
      return;
×
3677
  }
3678

3679
  hidden( false );
3✔
3680
  if ( is_visible() )
3✔
3681
  {
3682
    if ( client != nullptr )
3✔
3683
      send_owncreate( client, this );
×
3684

3685
    Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
3✔
3686
        this,
3687
        [&]( Character* chr )
3✔
3688
        {
3689
          if ( chr == this )
×
3690
            return;
×
3691
          if ( !chr->is_visible_to_me( this ) )
×
3692
            return;
×
3693
          send_owncreate( chr->client, this );
×
3694
        } );
3695

3696
    realm()->notify_unhid( *this );
3✔
3697

3698
    if ( !Clib::exit_signalled )
3✔
3699
    {
3700
      check_attack_after_move( true );
3✔
3701
    }
3702
  }
3703
}
3704

3705
void Character::set_stealthsteps( unsigned short newval )
8✔
3706
{
3707
  stealthsteps_ = newval;
8✔
3708
}
8✔
3709

3710
bool Character::doors_block() const
80✔
3711
{
3712
  return !( graphic == UOBJ_GAMEMASTER || graphic == UOBJ_HUMAN_MALE_GHOST ||
160✔
3713
            graphic == UOBJ_HUMAN_FEMALE_GHOST || graphic == UOBJ_ELF_MALE_GHOST ||
80✔
3714
            graphic == UOBJ_ELF_FEMALE_GHOST || graphic == UOBJ_GARGOYLE_MALE_GHOST ||
80✔
3715
            graphic == UOBJ_GARGOYLE_FEMALE_GHOST ||
80✔
3716
            cached_settings.get( PRIV_FLAGS::IGNORE_DOORS ) );
160✔
3717
}
3718

3719
/*
3720
    This isn't quite right, for hidden characters.
3721
    What normally happens:
3722
    1) Client sends "use skill hiding"
3723
    2) Server sends "77 move" with the hidden flag set.
3724
    3) client sends move-request
3725
    4) server sends move-approve
3726
    5) server sense "78 create" message with hidden flag clear, at new posn.
3727

3728
    We're sending the "78 create" _before_ the move-approve.
3729
    */
3730

3731
bool Character::can_face( Core::UFACING /*i_facing*/ )
28✔
3732
{
3733
  if ( can_freemove() )
28✔
3734
    return true;
×
3735

3736
  if ( frozen() || paralyzed() )
28✔
3737
  {
3738
    if ( client != nullptr )
×
3739
    {
3740
      if ( frozen() )
×
3741
        private_say_above( this, this, "I am frozen and cannot move." );
×
3742
      else if ( paralyzed() )
×
3743
        private_say_above( this, this, "I am paralyzed and cannot move." );
×
3744
    }
3745
    return false;
×
3746
  }
3747

3748
  if ( Core::settingsManager.ssopt.movement_uses_stamina &&
84✔
3749
       vital( Core::gamestate.pVitalStamina->vitalid ).current_ones() == 0 && !dead() )
28✔
3750
  {
3751
    private_say_above( this, this, "You are too fatigued to move." );
×
3752
    return false;
×
3753
  }
3754

3755
  return true;
28✔
3756
}
3757

3758

3759
bool Character::face( Core::UFACING i_facing, int flags )
28✔
3760
{
3761
  if ( ( flags & 1 ) == 0 )
28✔
3762
  {
3763
    if ( !can_face( i_facing ) )
28✔
3764
      return false;
×
3765
  }
3766

3767
  if ( i_facing != facing )
28✔
3768
  {
3769
    setfacing( static_cast<u8>( i_facing ) );
8✔
3770

3771
    if ( Core::settingsManager.combat_config.reset_swing_onturn &&
16✔
3772
         !cached_settings.get( PRIV_FLAGS::FIRE_WHILE_MOVING ) && weapon->is_projectile() )
8✔
3773
      reset_swing_timer();
×
3774
    if ( hidden() && ( Core::settingsManager.ssopt.hidden_turns_count ) )
8✔
3775
    {
3776
      if ( stealthsteps_ == 0 )
×
3777
        unhide();
×
3778
      else
3779
        stealthsteps_--;
×
3780
    }
3781
  }
3782

3783
  return true;
28✔
3784
}
3785

3786

3787
bool Character::CustomHousingMove( unsigned char i_dir )
×
3788
{
3789
  passert( facing < 8 );
×
3790

3791
  Multi::UMulti* multi = Core::system_find_multi( client->gd->custom_house_serial );
×
3792
  if ( multi != nullptr )
×
3793
  {
3794
    Multi::UHouse* house = multi->as_house();
×
3795
    if ( house != nullptr )
×
3796
    {
3797
      Core::UFACING i_facing = static_cast<Core::UFACING>( i_dir & PKTIN_02_FACING_MASK );
×
3798
      if ( i_facing != facing )
×
3799
      {
3800
        setfacing( static_cast<u8>( i_facing ) );
×
3801
        set_dirty();
×
3802
        dir = i_dir;
×
3803
        return true;
×
3804
      }
3805
      else
3806
      {
3807
        auto newpos = pos().move( static_cast<Core::UFACING>( facing ) );
×
3808
        newpos.z( house->z() +
×
3809
                  Multi::CustomHouseDesign::custom_house_z_xlate_table[house->editing_floor_num] );
×
3810
        const Multi::MultiDef& def = house->multidef();
×
3811
        auto relpos = newpos - house->pos().xy();
×
3812
        // minx and y are wall elements and z is 7
3813
        // mobile will look like flying when allowing the min coords
3814
        if ( def.within_multi( relpos ) && relpos.x() != def.minrxyz.x() &&
×
3815
             relpos.y() != def.minrxyz.y() )
×
3816
        {
3817
          Core::Pos4d oldpos = pos();
×
3818
          setposition( newpos );
×
3819
          MoveCharacterWorldPosition( oldpos, this );
×
3820

3821
          position_changed();
×
3822
          set_dirty();
×
3823
          return true;
×
3824
        }
3825
      }
3826
    }
3827
  }
3828
  return false;
×
3829
}
3830

3831
bool Character::is_piloting_boat() const
116✔
3832
{
3833
  auto* mountpiece = wornitem( Core::LAYER_MOUNT );
116✔
3834
  return mountpiece != nullptr && mountpiece->objtype_ == Core::settingsManager.extobj.boatmount;
116✔
3835
}
3836

3837
//************************************
3838
// Method:    move
3839
// FullName:  Character::move
3840
// Access:    public
3841
// Returns:   bool
3842
// Qualifier:
3843
// Parameter: unsigned char i_dir
3844
//************************************
3845
bool Character::move( unsigned char i_dir )
24✔
3846
{
3847
  if ( is_piloting_boat() )
24✔
3848
  {
3849
    return false;
1✔
3850
  }
3851

3852
  lastpos = pos();
23✔
3853

3854
  // if currently building a house chr can move free inside the multi
3855
  if ( is_house_editing() )
23✔
3856
    return CustomHousingMove( i_dir );
×
3857

3858
  u8 oldFacing = facing;
23✔
3859

3860
  Core::UFACING i_facing = static_cast<Core::UFACING>( i_dir & PKTIN_02_FACING_MASK );
23✔
3861
  if ( !face( i_facing ) )
23✔
3862
    return false;
×
3863

3864
  // If we're a player, and we used our "move" command to turn,
3865
  //  we want to skip the meat of this function
3866
  if ( ( script_isa( Core::POLCLASS_NPC ) ) || ( facing == oldFacing ) )
23✔
3867
  {
3868
    if ( facing & 1 )  // check if diagonal movement is allowed -- Nando (2009-02-26)
18✔
3869
    {
3870
      short new_z;
3871
      u8 tmp_facing = ( facing + 1 ) & 0x7;
1✔
3872
      auto tmp_pos = pos().move( static_cast<Core::UFACING>( tmp_facing ) );
1✔
3873

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

3878
      tmp_facing = ( facing - 1 ) & 0x7;
1✔
3879
      tmp_pos = pos().move( static_cast<Core::UFACING>( tmp_facing ) );
1✔
3880

3881
      if ( !walk1 && !realm()->walkheight( this, tmp_pos.xy(), tmp_pos.z(), &new_z, nullptr,
1✔
3882
                                           nullptr, nullptr ) )
3883
        return false;
×
3884
    }
3885
    auto new_pos = pos().move( static_cast<Core::UFACING>( facing ) );
18✔
3886

3887
    // FIXME consider consolidating with similar code in UOEMOD.CPP
3888
    short newz;
3889
    Multi::UMulti* supporting_multi;
3890
    Items::Item* walkon_item;
3891

3892
    short current_boost = gradual_boost;
18✔
3893
    if ( !realm()->walkheight( this, new_pos.xy(), new_pos.z(), &newz, &supporting_multi,
18✔
3894
                               &walkon_item, &current_boost ) )
3895
      return false;
1✔
3896
    new_pos.z( static_cast<s8>( newz ) );
17✔
3897
    remote_containers_.clear();
17✔
3898

3899
    if ( !CheckPushthrough() )
17✔
3900
      return false;
×
3901

3902
    if ( !can_freemove() && Core::settingsManager.ssopt.movement_uses_stamina && !dead() )
17✔
3903
    {
3904
      int carry_perc = weight() * 100 / carrying_capacity();
17✔
3905
      unsigned short tmv = movecost(
34✔
3906
          this, carry_perc, ( i_dir & PKTIN_02_DIR_RUNNING_BIT ) ? true : false, on_mount() );
17✔
3907
      VitalValue& stamina = vital( Core::gamestate.pVitalStamina->vitalid );
17✔
3908
      if ( !consume( Core::gamestate.pVitalStamina, stamina, tmv, VitalDepletedReason::MOVEMENT ) )
17✔
3909
      {
3910
        private_say_above( this, this, "You are too fatigued to move." );
×
3911
        return false;
×
3912
      }
3913
    }
3914

3915
    // Maybe have a flag for this in servspecopt?
3916
    if ( !cached_settings.get( PRIV_FLAGS::FIRE_WHILE_MOVING ) && weapon->is_projectile() )
17✔
3917
      reset_swing_timer();
×
3918

3919
    Core::Pos4d oldpos = pos();
17✔
3920
    setposition( new_pos );
17✔
3921
    moved_at_ = Core::polclock();
17✔
3922

3923
    if ( on_mount() && !script_isa( Core::POLCLASS_NPC ) )
17✔
3924
    {
3925
      mountedsteps_++;
×
3926
    }
3927
    if ( supporting_multi != nullptr )
17✔
3928
    {
3929
      supporting_multi->register_object( this );
×
3930

3931
      if ( this->registered_multi == 0 )
×
3932
      {
3933
        this->registered_multi = supporting_multi->serial;
×
3934
        supporting_multi->walk_on( this );
×
3935
      }
3936
    }
3937
    else
3938
    {
3939
      if ( registered_multi > 0 )
17✔
3940
      {
3941
        Multi::UMulti* multi = Core::system_find_multi( registered_multi );
×
3942
        if ( multi != nullptr )
×
3943
        {
3944
          multi->unregister_object( (UObject*)this );
×
3945
        }
3946
        registered_multi = 0;
×
3947
      }
3948
    }
3949

3950
    gradual_boost = current_boost;
17✔
3951
    MoveCharacterWorldPosition( oldpos, this );
17✔
3952

3953
    position_changed();
17✔
3954
    if ( walkon_item != nullptr )
17✔
3955
    {
3956
      walkon_item->walk_on( this );
×
3957
    }
3958

3959
    if ( hidden() )
17✔
3960
    {
3961
      if ( ( ( i_dir & PKTIN_02_DIR_RUNNING_BIT ) &&
×
3962
             !cached_settings.get( PRIV_FLAGS::RUN_WHILE_STEALTH ) ) ||
×
3963
           ( stealthsteps_ == 0 ) )
×
3964
        unhide();
×
3965
      else if ( stealthsteps_ )
×
3966
        --stealthsteps_;
×
3967
    }
3968

3969
    if ( Core::gamestate.system_hooks.ouch_hook )
17✔
3970
    {
3971
      if ( ( lastpos.z() - z() ) > 21 )
×
3972
        Core::gamestate.system_hooks.ouch_hook->call(
×
3973
            make_mobileref( this ), new Bscript::BLong( lastpos.x() ),
×
3974
            new Bscript::BLong( lastpos.y() ), new Bscript::BLong( lastpos.z() ) );
×
3975
    }
3976
  }
3977

3978
  set_dirty();
22✔
3979
  dir = i_dir;
22✔
3980

3981
  return true;
22✔
3982

3983

3984
  // this would be a great place for tellmove(), except that
3985
  // we want to send the move acknowledge to the client before
3986
  // sending the move/create messages to the neighboring clients.
3987

3988
  // Why? Maybe to give the best response time to the client.
3989

3990
  // may want to rethink this, since it's starting to complicate
3991
  // things.
3992
}
3993

3994
void Character::realm_changed()
8✔
3995
{
3996
  // Commented out the explicit backpack handling, should be handled
3997
  // automagically by wormitems realm handling.  There is a slim
3998
  // possibility that backpacks might be assigned to a character but
3999
  // not be a worn item?  If this is the case, that will be broken.
4000
  //  backpack()->realm = realm;
4001
  //  backpack()->for_each_item(setrealm, (void*)realm);
4002
  wornitems->for_each_item( Core::setrealm, (void*)realm() );
8✔
4003
  // TODO Pos: realm should be all the time nullptr for these items
4004
  if ( has_gotten_item() )
8✔
4005
  {
4006
    auto gotten = gotten_item();
×
4007
    gotten.item()->setposition( Core::Pos4d( gotten.item()->pos().xyz(), realm() ) );
×
4008
  }
4009
  if ( trading_cont.get() )
8✔
4010
    trading_cont->setposition( Core::Pos4d( trading_cont->pos().xyz(), realm() ) );
×
4011

4012
  if ( has_active_client() )
8✔
4013
  {
4014
    // these are important to keep here in this order
4015
    Core::send_realm_change( client, realm() );
4✔
4016
    Core::send_map_difs( client );
4✔
4017
    if ( Core::settingsManager.ssopt.core_sends_season )
4✔
4018
      Core::send_season_info( client );
4✔
4019
    Core::send_full_statmsg( client, this );
4✔
4020
    Core::send_feature_enable( client );
4✔
4021
  }
4022
}
8✔
4023

4024
bool Character::CheckPushthrough()
17✔
4025
{
4026
  if ( !can_freemove() && Core::gamestate.system_hooks.pushthrough_hook )
17✔
4027
  {
4028
    auto newpos = pos().move( static_cast<Core::UFACING>( facing ) );
×
4029
    auto mobs = std::unique_ptr<Bscript::ObjArray>();
×
4030

4031
    Core::WorldIterator<Core::MobileFilter>::InRange(
×
4032
        newpos, 0,
4033
        [&]( Mobile::Character* _chr )
×
4034
        {
4035
          if ( _chr->z() >= z() - 10 && _chr->z() <= z() + 10 && !_chr->dead() &&
×
4036
               ( is_visible_to_me( _chr ) ||
×
4037
                 _chr->hidden() ) )  // add hidden mobs even if they're not visible to me
×
4038
          {
4039
            if ( !mobs )
×
4040
              mobs.reset( new Bscript::ObjArray );
×
4041
            mobs->addElement( make_mobileref( _chr ) );
×
4042
          }
4043
        } );
×
4044

4045
    if ( mobs )
×
4046
    {
4047
      return Core::gamestate.system_hooks.pushthrough_hook->call( make_mobileref( this ),
×
4048
                                                                  mobs.release() );
×
4049
    }
4050
    return true;
×
4051
  }
×
4052
  return true;
17✔
4053
}
4054

4055
void Character::tellmove()
129✔
4056
{
4057
  check_region_changes();
129✔
4058
  PropagateMove( this );
129✔
4059

4060
  // notify npcs and items (maybe the PropagateMove should also go there eventually? - Nando
4061
  // 2018-06-16)
4062
  realm()->notify_moved( *this );
129✔
4063

4064
  check_attack_after_move( true );
129✔
4065

4066
  move_reason = OTHER;
129✔
4067
}
129✔
4068

4069
void Character::add_remote_container( Items::Item* item )
×
4070
{
4071
  remote_containers_.push_back( Core::ItemRef( item ) );
×
4072
}
×
4073

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

4103
bool Character::mightsee( const Items::Item* item ) const
50✔
4104
{
4105
  const auto* owner = item->toplevel_owner();
50✔
4106
  for ( const auto& elem : remote_containers_ )
50✔
4107
  {
4108
    Items::Item* additional_item = elem.get();
×
4109
    if ( additional_item == owner )
×
4110
      return true;
×
4111
  }
4112

4113
  return in_visual_range( owner );
50✔
4114
}
4115

4116
bool Character::squelched() const
19✔
4117
{
4118
  Core::gameclock_t squelched = squelched_until();
19✔
4119
  if ( squelched == 0 )
19✔
4120
    return false;
18✔
4121
  else if ( squelched == ~0u )
1✔
4122
    return true;
×
4123

4124
  if ( Core::read_gameclock() < squelched )
1✔
4125
  {
4126
    return true;
1✔
4127
  }
4128
  else
4129
  {
4130
    const_cast<Character*>( this )->squelched_until( 0 );
×
4131
    return false;
×
4132
  }
4133
}
4134

4135
bool Character::deafened() const
20✔
4136
{
4137
  Core::gameclock_t deafened = deafened_until();
20✔
4138
  if ( deafened == 0 )
20✔
4139
    return false;
19✔
4140
  else if ( deafened == ~0u )
1✔
4141
    return true;
×
4142

4143
  if ( Core::read_gameclock() < deafened )
1✔
4144
  {
4145
    return true;
1✔
4146
  }
4147
  else
4148
  {
4149
    const_cast<Character*>( this )->deafened_until( 0 );
×
4150
    return false;
×
4151
  }
4152
}
4153

4154
bool Character::invul() const
282✔
4155
{
4156
  return cached_settings.get( PRIV_FLAGS::INVUL );
282✔
4157
}
4158

4159
u16 Character::strength() const
25✔
4160
{
4161
  return static_cast<u16>( attribute( Core::gamestate.pAttrStrength->attrid ).effective() );
25✔
4162
}
4163
u16 Character::dexterity() const
133✔
4164
{
4165
  return static_cast<u16>( attribute( Core::gamestate.pAttrDexterity->attrid ).effective() );
133✔
4166
}
4167
u16 Character::intelligence() const
×
4168
{
4169
  return static_cast<u16>( attribute( Core::gamestate.pAttrIntelligence->attrid ).effective() );
×
4170
}
4171

4172
bool Character::target_cursor_busy() const
8✔
4173
{
4174
  if ( client && client->gd && client->gd->tcursor2 && client->gd->target_cursor_uoemod != nullptr )
8✔
4175
    return true;
×
4176
  return false;
8✔
4177
}
4178

4179
void Character::cancel_menu()
×
4180
{
4181
  if ( client )
×
4182
  {
4183
    client->gd->menu.clear();
×
4184
    if ( client->gd->on_menu_selection != nullptr )
×
4185
      client->gd->on_menu_selection( client, nullptr, nullptr );
×
4186
    client->gd->on_menu_selection = nullptr;
×
4187
  }
4188
}
×
4189

4190
bool Character::is_trading() const
154✔
4191
{
4192
  return ( trading_with.get() != nullptr );
154✔
4193
}
4194

4195
bool Character::trade_accepted() const
×
4196
{
4197
  return mob_flags_.get( MOB_FLAGS::TRADE_ACCEPTED );
×
4198
}
4199

4200
void Character::trade_accepted( bool newvalue )
×
4201
{
4202
  mob_flags_.change( MOB_FLAGS::TRADE_ACCEPTED, newvalue );
×
4203
}
×
4204

4205
void Character::create_trade_container()
×
4206
{
4207
  if ( trading_cont.get() == nullptr )  // FIXME hardcoded
×
4208
  {
4209
    Items::Item* cont = Items::Item::create( Core::settingsManager.extobj.secure_trade_container );
×
4210
    // TODO Pos: no realm
4211
    cont->setposition( pos() );
×
4212
    trading_cont.set( static_cast<Core::UContainer*>( cont ) );
×
4213
  }
4214
}
×
4215

4216
Core::UContainer* Character::trade_container()
×
4217
{
4218
  return trading_cont.get();
×
4219
}
4220

4221
// SkillValue removed for no use - MuadDib
4222

4223
const char* Character::target_tag() const
×
4224
{
4225
  return "mobile";
×
4226
}
4227

4228
void Character::set_caps_to_default()
77✔
4229
{
4230
  for ( unsigned ai = 0; ai < Core::gamestate.numAttributes; ++ai )
4,081✔
4231
  {
4232
    Attribute* pAttr = Core::gamestate.attributes[ai];
4,004✔
4233
    AttributeValue& av = attribute( ai );
4,004✔
4234

4235
    av = attribute( ai );
4,004✔
4236
    av.cap( pAttr->default_cap );
4,004✔
4237
  }
4238
}
77✔
4239

4240
u16 Character::last_textcolor() const
×
4241
{
4242
  return _last_textcolor;
×
4243
}
4244

4245
void Character::last_textcolor( u16 new_color )
3✔
4246
{
4247
  _last_textcolor = new_color;
3✔
4248
}
3✔
4249

4250
unsigned int Character::guildid() const
2✔
4251
{
4252
  auto g = guild();
2✔
4253
  return ( g != nullptr ) ? g->guildid() : 0;
2✔
4254
}
4255

4256
/**
4257
 * Adds a new buff or overwrites an existing one for the character
4258
 * Sends packets to the client accordingly
4259
 * @author Bodom
4260
 */
4261
void Character::addBuff( u16 icon, u16 duration, u32 cl_name, const std::string& name_arguments,
2✔
4262
                         u32 cl_descr, const std::string& desc_arguments )
4263
{
4264
  // Icon is already present, must send a remove packet first or client will not update
4265
  delBuff( icon );
2✔
4266

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

4270
  if ( client != nullptr )
2✔
4271
    send_buff_message( this, icon, true, duration, cl_name, name_arguments, cl_descr,
×
4272
                       desc_arguments );
4273
}
4✔
4274

4275
/**
4276
 * Removes a buff for the character
4277
 * Sends packets to the client accordingly
4278
 * @author Bodom
4279
 * @return True when the buff has been found and removed, False when the buff was not present
4280
 */
4281
bool Character::delBuff( u16 icon )
4✔
4282
{
4283
  auto b = buffs_.find( icon );
4✔
4284

4285
  if ( b == buffs_.end() )
4✔
4286
    return false;
3✔
4287

4288
  buffs_.erase( b );
1✔
4289
  if ( client != nullptr )
1✔
4290
    send_buff_message( this, icon, false );
×
4291
  return true;
1✔
4292
}
4293

4294
/**
4295
 * Removes al buffs for the character
4296
 * Sends packets to the client accordingly
4297
 * @author Bodom
4298
 */
4299
void Character::clearBuffs()
2✔
4300
{
4301
  if ( client != nullptr )
2✔
4302
  {
4303
    for ( const auto& buf : buffs_ )
×
4304
    {
4305
      send_buff_message( this, buf.first, false );
×
4306
    }
4307
  }
4308
  buffs_.clear();
2✔
4309
}
2✔
4310

4311
/**
4312
 * Resends all buffs (with updated duration), usually called at (re)login
4313
 * @author Bodom
4314
 */
4315
void Character::send_buffs()
2✔
4316
{
4317
  if ( client == nullptr )
2✔
4318
    return;
×
4319

4320
  for ( const auto& [icon, buf] : buffs_ )
2✔
4321
  {
4322
    u16 duration = Clib::clamp_convert<u16>( buf.end - Core::read_gameclock() );
×
4323

4324
    send_buff_message( this, icon, true, duration, buf.cl_name, buf.name_arguments, buf.cl_descr,
×
4325
                       buf.desc_arguments );
×
4326
  }
4327
}
4328

4329
u8 Character::los_size() const
55,614✔
4330
{
4331
  return client ? client->update_range() : Core::settingsManager.ssopt.default_visual_range;
55,614✔
4332
}
4333

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

4374
  size += Clib::memsize( attributes ) + Clib::memsize( vitals ) + Clib::memsize( armor_ ) +
×
4375
          Clib::memsize( remote_containers_ ) + Clib::memsize( opponent_of ) +
×
4376
          Clib::memsize( aggressor_to_ ) + Clib::memsize( lawfully_damaged_ ) +
×
4377
          Clib::memsize( to_be_reportable_ ) + Clib::memsize( reportable_ ) +
×
4378
          Clib::memsize( buffs_ );
×
4379
  return size;
×
4380
}
4381

4382
void Character::on_delete_from_account()
3✔
4383
{
4384
  if ( realm() )
3✔
4385
    realm()->remove_mobile( *this, Realms::WorldChangeReason::PlayerDeleted );
3✔
4386
}
3✔
4387

4388
bool Character::get_method_hook( const char* methodname, Bscript::Executor* ex,
×
4389
                                 Core::ExportScript** hook, unsigned int* PC ) const
4390
{
4391
  if ( Core::gamestate.system_hooks.get_method_hook(
×
4392
           Core::gamestate.system_hooks.character_method_script.get(), methodname, ex, hook, PC ) )
4393
    return true;
×
4394
  return base::get_method_hook( methodname, ex, hook, PC );
×
4395
}
4396

4397
void Character::update_objects_on_range_change( u8 newrange )
2✔
4398
{
4399
  Network::RemoveObjectPkt msgremove( serial_ext );
2✔
4400

4401
  Core::WorldIterator<Core::MobileFilter>::InRange(
2✔
4402
      this, std::max( newrange, los_size() ),
2✔
4403
      [&]( Mobile::Character* zonechr )
2✔
4404
      {
4405
        if ( this == zonechr || !is_visible_to_me( zonechr, false /*rangecheck*/ ) )
2✔
4406
          return;
2✔
4407
        bool was_inrange = in_range( zonechr, los_size() + zonechr->visible_size() );
×
4408
        bool is_inrange = in_range( zonechr, newrange + zonechr->visible_size() );
×
4409
        if ( was_inrange && is_inrange )
×
4410
          return;
×
4411
        if ( !was_inrange && is_inrange )
×
4412
          Core::send_owncreate( client, zonechr );
×
4413
        else if ( was_inrange && !is_inrange )
×
4414
          Core::send_remove_character( client, zonechr, msgremove );
×
4415
      } );
4416

4417
  Core::WorldIterator<Core::ItemFilter>::InRange(
2✔
4418
      this, Core::gamestate.max_update_range_multi_only() + std::max( newrange, los_size() ),
2✔
4419
      [&]( Items::Item* zoneitem )
2✔
4420
      {
4421
        bool was_inrange = in_range( zoneitem, los_size() + zoneitem->visible_size() );
6✔
4422
        bool is_inrange = in_range( zoneitem, newrange + zoneitem->visible_size() );
6✔
4423
        if ( was_inrange && is_inrange )
6✔
4424
          return;
×
4425
        if ( !was_inrange && is_inrange )
6✔
4426
          Core::send_item( client, zoneitem );
3✔
4427
        else if ( was_inrange && !is_inrange )
3✔
4428
          Core::send_remove_object( client, zoneitem, msgremove );
3✔
4429
      } );
4430

4431
  Core::WorldIterator<Core::MultiFilter>::InRange(
2✔
4432
      this, Core::gamestate.max_update_range_multi_only() + std::max( newrange, los_size() ),
2✔
4433
      [&]( Multi::UMulti* zonemulti )
2✔
4434
      {
4435
        bool was_inrange = in_range( zonemulti, los_size() + zonemulti->visible_size() );
×
4436
        bool is_inrange = in_range( zonemulti, newrange + zonemulti->visible_size() );
×
4437
        if ( was_inrange && is_inrange )
×
4438
          return;
×
4439
        if ( !was_inrange && is_inrange )
×
4440
        {
4441
          Core::send_multi( client, zonemulti );
×
4442
          Multi::UHouse* house = zonemulti->as_house();
×
NEW
4443
          if ( client->acctSupports( Plib::ExpansionVersion::AOS ) && house != nullptr &&
×
NEW
4444
               house->IsCustom() )
×
4445
            Multi::CustomHousesSendShort( house, client );
×
4446
        }
×
4447
        else if ( was_inrange && !is_inrange )
×
4448
          Core::send_remove_object( client, zonemulti, msgremove );
×
4449
      } );
4450
}
2✔
4451

4452
AttributeValue::AttributeValue()
4,004✔
4453
    : _base( 0 ), _temp( 0 ), _intrinsic( 0 ), _lockstate( 0 ), _cap( 0 )
4,004✔
4454
{
4455
}
4,004✔
4456
int AttributeValue::effective() const
560✔
4457
{
4458
  int v = _base;
560✔
4459
  v += _temp;
560✔
4460
  v += _intrinsic;
560✔
4461
  return ( v > 0 ) ? ( v / 10 ) : 0;
560✔
4462
}
4463
int AttributeValue::effective_tenths() const
110✔
4464
{
4465
  int v = _base;
110✔
4466
  v += _temp;
110✔
4467
  v += _intrinsic;
110✔
4468
  return ( v > 0 ) ? v : 0;
110✔
4469
}
4470
int AttributeValue::base() const
1,312✔
4471
{
4472
  return _base;
1,312✔
4473
}
4474
void AttributeValue::base( unsigned short base )
234✔
4475
{
4476
  passert( base <= ATTRIBUTE_MAX_BASE );
234✔
4477
  _base = base;
234✔
4478
}
234✔
4479
int AttributeValue::temp_mod() const
×
4480
{
4481
  return _temp;
×
4482
}
4483
void AttributeValue::temp_mod( short temp )
×
4484
{
4485
  _temp = temp;
×
4486
}
×
4487
int AttributeValue::intrinsic_mod() const
×
4488
{
4489
  return _intrinsic;
×
4490
}
4491
void AttributeValue::intrinsic_mod( short val )
×
4492
{
4493
  _intrinsic = val;
×
4494
}
×
4495
unsigned char AttributeValue::lock() const
1,242✔
4496
{
4497
  return _lockstate;
1,242✔
4498
}
4499
void AttributeValue::lock( unsigned char lockstate )
12✔
4500
{
4501
  _lockstate = lockstate;
12✔
4502
}
12✔
4503
unsigned short AttributeValue::cap() const
1,148✔
4504
{
4505
  return _cap;
1,148✔
4506
}
4507
void AttributeValue::cap( unsigned short cap )
4,016✔
4508
{
4509
  _cap = cap;
4,016✔
4510
}
4,016✔
4511

4512
VitalValue::VitalValue() : _current( 0 ), _maximum( 0 ), _regenrate( 0 ) {}
231✔
4513
int VitalValue::current() const
10✔
4514
{
4515
  return _current;
10✔
4516
}
4517
int VitalValue::current_ones() const
955✔
4518
{
4519
  return _current / 100;
955✔
4520
}
4521
int VitalValue::current_thousands() const
18✔
4522
{
4523
  // use division to prevent overflow
4524
  return ( _current / 100 ) * 1000 / ( _maximum / 100 );
18✔
4525
}
4526
int VitalValue::maximum() const
168✔
4527
{
4528
  return _maximum;
168✔
4529
}
4530
int VitalValue::maximum_ones() const
600✔
4531
{
4532
  return _maximum / 100;
600✔
4533
}
4534
bool VitalValue::is_at_maximum() const
17✔
4535
{
4536
  return ( _current >= _maximum );
17✔
4537
}
4538
int VitalValue::regenrate() const
51✔
4539
{
4540
  return _regenrate;
51✔
4541
}
4542
void VitalValue::current( int cur )
228✔
4543
{
4544
  _current = cur;
228✔
4545
  if ( _current > _maximum )
228✔
4546
    _current = _maximum;
×
4547
}
228✔
4548
void VitalValue::current_ones( int ones )
58✔
4549
{
4550
  current( ones * 100 );
58✔
4551
}
58✔
4552
void VitalValue::maximum( int val )
201✔
4553
{
4554
  _maximum = val;
201✔
4555
  if ( _current > _maximum )
201✔
4556
    current( _maximum );
×
4557
}
201✔
4558
void VitalValue::regenrate( int rate )
201✔
4559
{
4560
  _regenrate = rate;
201✔
4561
}
201✔
4562
bool VitalValue::consume( unsigned int hamt )
17✔
4563
{
4564
  if ( _current > hamt )
17✔
4565
  {
4566
    _current -= hamt;
17✔
4567
    return true;
17✔
4568
  }
4569
  _current = 0;
×
4570
  return false;
×
4571
}
4572
void VitalValue::produce( unsigned int hamt )
51✔
4573
{
4574
  unsigned newcur = _current + hamt;
51✔
4575
  if ( newcur > _maximum || newcur < _current )
51✔
4576
  {
4577
    _current = _maximum;
43✔
4578
  }
4579
  else
4580
  {
4581
    _current = newcur;
8✔
4582
  }
4583
}
51✔
4584

4585
}  // namespace Mobile
4586
}  // 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