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

polserver / polserver / 21541532363

31 Jan 2026 08:14AM UTC coverage: 60.532% (+0.03%) from 60.507%
21541532363

push

github

web-flow
Tidy modernize for loops (#862)

* trigger loop convert

* Automated clang-tidy change: modernize-loop-convert

* fixed refactor

* Automated clang-tidy change: modernize-loop-convert

* compile

* first look through

* fixes and start to use a few ranges

* revert autogenerated file

* compilation fix

* second pass

* renamed loop variable

---------

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

164 of 447 new or added lines in 61 files covered. (36.69%)

6 existing lines in 5 files now uncovered.

44377 of 73312 relevant lines covered (60.53%)

499857.83 hits per line

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

62.85
/pol-core/pol/mobile/charactr.cpp
1
/** @file
2
 *
3
 * @par History
4
 * - 2003/12/24 Dave:      on_poison_changed() changed to keep life bars from disappearing when you
5
 * poison someone.
6
 * - 2005/03/09 Shinigami: Added Prop Delay_Mod [ms] for WeaponDelay (see schedule_attack too)
7
 * - 2005/06/01 Shinigami: Added Walking_Mounted and Running_Mounted movecosts
8
 * - 2005/09/14 Shinigami: Character::resurrect() - Vital.regen_while_dead implemented
9
 * - 2005/10/14 Shinigami: fixed missing init of Character::dblclick_wait
10
 * - 2005/11/23 MuadDib:    Added warmode_wait object for characters.
11
 * - 2005/11/25 MuadDib:   Added realm check to is_visible_to_me.
12
 * - 2005/12/06 MuadDib:   Added uclang member for storing UC language from client.
13
 * - 2006/03/10 MuadDib:   Added NoCombat support to checking of justice region.
14
 * - 2006/05/04 MuadDib:   Removed get_legal_item for no use.
15
 * - 2006/05/04 MuadDib:   SkillValue() removed for no use.
16
 * - 2006/05/16 Shinigami: UOBJ_*MALE_GHOST renamed to UOBJ_HUMAN_*MALE_GHOST
17
 *                         added Prop Race (RACE_* Constants) to support Elfs
18
 *                         Character::die(), Character::doors_block() and Character::resurrect()
19
 * updated
20
 * - 2008/07/08 Turley:    get_flag1() changed to show WarMode of other player again
21
 *                         get_flag1_aos() removed
22
 * - 2009/01/14 Nando:     setgraphic() changed to allow graphics up to 2048 (0x800).
23
 * - 2009/02/01 MuadDib:   Resistance storage added.
24
 * - 2009/02/25 MuadDib:   on_poison_changed() added UOKR Status bar update for poisoned. Booyah!
25
 * - 2009/07/20 MuadDib:   Slot checks added to Character::Die()
26
 * - 2009/07/25 MuadDib:   equippable() now checks if a twohanded is intrinsic or not also.
27
 * Intrinsic gets ignored
28
 * - 2009/07/31 Turley:    added check for cmbtcfg::send_swing_packet & reset_swing_onturn
29
 * - 2009/08/04 MuadDib:   calc_vital_stuff() now checks to see if a vital changed, before using
30
 * tell_vital_changed()
31
 * - 2009/08/06 MuadDib:   Addeed gotten_by code for items.
32
 * - 2009/08/07 MuadDib:   Added new Corpse Layer code to character Die() to put equipped items on
33
 * correct layer with
34
 *                         corpse. Ignores items from pack, because in death we only want the items
35
 * it had equipped in
36
 *                         life, showing up on the corpse when dead.
37
 * - 2009/08/09 MuadDib:   on_poison_changed() rewritten for better KR poison support.
38
 * - 2009/08/16 MuadDib:   fix for die() where checking corpse and not item for slot_index().
39
 * - 2009/08/25 Shinigami: STLport-5.2.1 fix: corpseSlot not used
40
 *                         STLport-5.2.1 fix: init order changed of party_can_loot_,
41
 * party_decline_timeout_ and skillcap_
42
 * - 2009/08/28 Turley:    Crashfix for Character::on_poison_changed()
43
 * - 2009/09/03 MuadDib:   Changed combat related ssopt stuff to combat_config.
44
 *                         Changes for account related source file relocation
45
 *                         Changes for multi related source file relocation
46
 * - 2009/09/09 Turley:    ServSpecOpt CarryingCapacityMod as * modifier for
47
 * Character::carrying_capacity()
48
 * - 2009/09/15 MuadDib:   Cleanup from registered houses on destroy
49
 *                         u32 registered_house added to store serial of registered multi.
50
 *                         Multi registration/unregistration support added.
51
 * - 2009/09/06 Turley:    Changed Version checks to bitfield client->ClientType
52
 * - 2009/09/18 MuadDib:   Adding save/load of registered house serial
53
 * - 2009/09/22 MuadDib:   Rewrite for Character/NPC to use ar(), ar_mod(), ar_mod(newvalue)
54
 * virtuals.
55
 * - 2009/09/22 Turley:    Added DamagePacket support & repsys param to applydamage
56
 * - 2009/10/14 Turley:    new priv canbeheardasghost
57
 * - 2009/10/14 Turley:    Added char.deaf() methods & char.deafened member
58
 * - 2009/10/17 Turley:    PrivUpdater for "seehidden", "seeghosts", "seeinvisitems" and "invul" -
59
 * Tomi
60
 *                         fixed "all" priv
61
 *                         PrivUpdater class cleanup, removed duplicate stuff
62
 * - 2009/10/22 Turley:    added OuchHook call if lastz-z>21 (clientside value)
63
 * - 2009/11/16 Turley:    added NpcPropagateEnteredArea()/inform_enteredarea() for event on
64
 * resurrection
65
 * - 2009/11/19 Turley:    lightlevel now supports endless duration - Tomi
66
 * - 2009/11/20 Turley:    RecalcVitals can update single Attributes/Vitals - based on Tomi
67
 * - 2009/11/26 Turley:    Syshook CanDie(mobile)
68
 * - 2009/11/30 Turley:    fixed calc_single_vital doesnt check changed maximum value
69
 * - 2009/12/02 Turley:    added gargoyle & face support
70
 * - 2009/12/03 Turley:    fixed client>=7 poison/flying flag, basic flying support
71
 * - 2010/01/14 Turley:    AttackWhileFrozen check
72
 * - 2010/01/15 Turley:    (Tomi) priv runwhilestealth
73
 * - 2010/01/22 Turley:    Speedhack Prevention System
74
 * - 2011/11/12 Tomi:      added extobj.mount and extobj.secure_trade_container
75
 * - 2012/02/06 MuadDib:   Added serial check at root of Character::get_from_ground to make sure not
76
 * trying to move orphaned items.
77
 *                         In loop, if an found is an orphan it logs it and skips rest of individual
78
 * itr to ensure movement not attempted.
79
 */
80

81

82
#include "pol_global_config.h"
83

84
#include "charactr.h"
85

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

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

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

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

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

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

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

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

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

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

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

262

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

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

343
  set_caps_to_default();
97✔
344

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

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

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

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

374
  removal_cleanup();
97✔
375

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

567

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

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

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

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

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

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

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

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

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

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

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

624

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

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

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

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

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

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

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

686

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

993

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

998
        av.lock( lock );
10✔
999

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

1005
  calc_vital_stuff();
11✔
1006

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

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

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

1037

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

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

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

1055
  cached_settings.reset();
188✔
1056

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

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

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

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

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

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

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

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

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

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

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

1163
  return false;
×
1164
}
1165

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

1173
  return false;
32✔
1174
}
1175

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1386
  return true;
62✔
1387
}
1388

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

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

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

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

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

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

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

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

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

1455

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

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

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

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

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

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

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

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

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

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

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

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

1579
  vv.maximum( mv );
261✔
1580

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

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

1588
  vv.regenrate( rr );
261✔
1589

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

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

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

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

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

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

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

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

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

1635
  return true;
×
1636
}
1637

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

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

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

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

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

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

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

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

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

1731
  return flag1;
203✔
1732
}
1733

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

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

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

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

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

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

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

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

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

1778
  send_update_hits_to_inrange( this );
×
1779

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

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

1786

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

1793
  int absorbed = blocked / 2;
×
1794

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

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

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

1810

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

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

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

1833
  return damage;
×
1834
}
1835

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

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

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

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

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

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

1869

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

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

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

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

1907

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

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

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

1929
  check_undamaged();
×
1930

1931
  send_update_hits_to_inrange( this );
×
1932
}
1933

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

1953

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

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

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

1990
  color = truecolor;
×
1991

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

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

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

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

2034
  // equip( create_backpack() );
2035

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

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

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

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

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

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

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

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

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

2099
  send_create_mobile_to_nearby_cansee( this );
1✔
2100

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

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

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

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

2132

2133
  u16 save_graphic = graphic;
72✔
2134

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

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

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

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

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

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

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

2186
  UPDATE_CHECKPOINT();
72✔
2187

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

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

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

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

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

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

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

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

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

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

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

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

2362
    UPDATE_CHECKPOINT();
2✔
2363

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

2410

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

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

2427
  UPDATE_CHECKPOINT();
72✔
2428

2429
  clear_opponent_of();
72✔
2430

2431
  set_opponent( nullptr );
72✔
2432

2433
  UPDATE_CHECKPOINT();
72✔
2434

2435
  on_death( corpse );
72✔
2436

2437
  CLEAR_CHECKPOINT();
72✔
2438
}
2439

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

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

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

2484
  /* add AR due to shield : parry skill / 2 is percent of AR */
2485
  // FIXME: Should we allow this to be adjustable via a prop? Hrmmmmm
2486
  if ( shield != nullptr )
162✔
2487
  {
2488
    double add =
2489
        0.5 * 0.01 * shield->ar() * attribute( Core::gamestate.pAttrParry->attrid ).effective();
×
2490
    if ( add > 1.0 )
×
2491
      new_ar += add;
×
2492
    else
2493
      new_ar += 1.0;
×
2494
  }
2495

2496
  new_ar += ar_mod();
162✔
2497

2498
  short s_new_ar = static_cast<short>( new_ar );
162✔
2499
  if ( s_new_ar >= 0 )
162✔
2500
    ar_ = s_new_ar;
162✔
2501
  else
2502
    ar_ = 0;
×
2503

2504
  if ( client != nullptr )
162✔
2505
  {  // CHECKME consider sending less frequently
2506
    send_full_statmsg( client, this );
6✔
2507
  }
2508
}
162✔
2509

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

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

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

2574
  // calc defence increase if lower than cap
2575
  if ( item->has_defence_increase() )
154✔
2576
    defence_increase( defence_increase().addToValue( item->defence_increase() ) );
34✔
2577

2578
  if ( client != nullptr )
154✔
2579
  {  // CHECKME consider sending less frequently
2580
    send_full_statmsg( client, this );
9✔
2581
  }
2582
}
154✔
2583

2584
void Character::resetEquipableProperties()
162✔
2585
{
2586
  // reset resists
2587
  if ( has_fire_resist() )
162✔
2588
    fire_resist( fire_resist().setAsValue( 0 ) );
11✔
2589
  if ( has_cold_resist() )
162✔
2590
    cold_resist( cold_resist().setAsValue( 0 ) );
13✔
2591
  if ( has_energy_resist() )
162✔
2592
    energy_resist( energy_resist().setAsValue( 0 ) );
12✔
2593
  if ( has_poison_resist() )
162✔
2594
    poison_resist( poison_resist().setAsValue( 0 ) );
9✔
2595
  if ( has_physical_resist() )
162✔
2596
    physical_resist( physical_resist().setAsValue( 0 ) );
10✔
2597

2598
  // reset caps
2599
  if ( has_fire_resist_cap() )
162✔
2600
    fire_resist_cap( fire_resist_cap().setAsValue( 0 ) );
25✔
2601
  if ( has_cold_resist_cap() )
162✔
2602
    cold_resist_cap( cold_resist_cap().setAsValue( 0 ) );
33✔
2603
  if ( has_energy_resist_cap() )
162✔
2604
    energy_resist_cap( energy_resist_cap().setAsValue( 0 ) );
29✔
2605
  if ( has_poison_resist_cap() )
162✔
2606
    poison_resist_cap( poison_resist_cap().setAsValue( 0 ) );
17✔
2607
  if ( has_physical_resist_cap() )
162✔
2608
    physical_resist_cap( physical_resist_cap().setAsValue( 0 ) );
21✔
2609

2610
  // reset damages
2611
  if ( has_fire_damage() )
162✔
2612
    fire_damage( fire_damage().setAsValue( 0 ) );
93✔
2613
  if ( has_cold_damage() )
162✔
2614
    cold_damage( cold_damage().setAsValue( 0 ) );
95✔
2615
  if ( has_energy_damage() )
162✔
2616
    energy_damage( energy_damage().setAsValue( 0 ) );
94✔
2617
  if ( has_poison_damage() )
162✔
2618
    poison_damage( poison_damage().setAsValue( 0 ) );
91✔
2619
  if ( has_physical_damage() )
162✔
2620
    physical_damage( physical_damage().setAsValue( 0 ) );
92✔
2621

2622
  // reset others
2623
  if ( has_lower_reagent_cost() )
162✔
2624
    lower_reagent_cost( lower_reagent_cost().setAsValue( 0 ) );
59✔
2625
  if ( has_spell_damage_increase() )
162✔
2626
    spell_damage_increase( spell_damage_increase().setAsValue( 0 ) );
57✔
2627
  if ( has_faster_casting() )
162✔
2628
    faster_casting( faster_casting().setAsValue( 0 ) );
62✔
2629
  if ( has_faster_cast_recovery() )
162✔
2630
    faster_cast_recovery( faster_cast_recovery().setAsValue( 0 ) );
63✔
2631

2632
  if ( has_defence_increase() )
162✔
2633
    defence_increase( defence_increase().setAsValue( 0 ) );
37✔
2634
  if ( has_defence_increase_cap() )
162✔
2635
    defence_increase_cap( defence_increase_cap().setAsValue( 0 ) );
41✔
2636
  if ( has_lower_mana_cost() )
162✔
2637
    lower_mana_cost( lower_mana_cost().setAsValue( 0 ) );
60✔
2638
  if ( has_hit_chance() )
162✔
2639
    hit_chance( hit_chance().setAsValue( 0 ) );
61✔
2640
  if ( has_luck() )
162✔
2641
    luck( luck().setAsValue( 0 ) );
58✔
2642
  if ( has_swing_speed_increase() )
162✔
2643
    swing_speed_increase( swing_speed_increase().setAsValue( 0 ) );
59✔
2644
  if ( has_min_attack_range_increase() )
162✔
2645
    min_attack_range_increase( min_attack_range_increase().setAsValue( 0 ) );
55✔
2646
  if ( has_max_attack_range_increase() )
162✔
2647
    max_attack_range_increase( max_attack_range_increase().setAsValue( 0 ) );
55✔
2648
}
162✔
2649

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

2667
/* check skill: test skill, advance, reset atrophy timers, blah blah..
2668
   obviously, needs work, and more parameters.
2669
   */
2670

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

2685
  return false;
×
2686
}
2687

2688
void Character::check_concealment_level()
×
2689
{
2690
  if ( concealed() > cmdlevel() )
×
2691
    concealed( cmdlevel() );
×
2692
}
×
2693

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2883
void Character::reset_swing_timer()
102✔
2884
{
2885
  INFO_PRINTLN_TRACE( 15 )( "reset_swing_timer({:#x})", this->serial );
102✔
2886
  mob_flags_.remove( MOB_FLAGS::READY_TO_SWING );
102✔
2887

2888
  swing_timer_start_clock_ = Core::polclock();
102✔
2889
  if ( swing_task )
102✔
2890
    swing_task->cancel();
6✔
2891

2892
  if ( opponent_ || !opponent_of.empty() )
102✔
2893
  {
2894
    schedule_attack();
13✔
2895
  }
2896
}
102✔
2897

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

2904
  swing_timer_start_clock_ = Core::polclock();
×
2905
  if ( swing_task )
×
2906
    swing_task->cancel();
×
2907

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

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

2931
bool Character::is_attackable( Character* who ) const
23✔
2932
{
2933
  passert( who != nullptr );
23✔
2934
  if ( Core::settingsManager.combat_config.scripted_attack_checks )
23✔
2935
  {
2936
    INFO_PRINTLN_TRACE( 21 )
×
2937
    ( "is_attackable({:#x},{:#x}): will be handled by combat hook.", this->serial, who->serial );
×
2938
    return true;
×
2939
  }
2940

2941
  INFO_PRINTLN_TRACE( 21 )
23✔
2942
  ( "is_attackable({:#x},{:#x}):\n"
×
2943
    "  who->dead:  {}\n"
2944
    "  wpn->inrange: {}\n"
2945
    "  hidden:     {}\n"
2946
    "  who->hidden:  {}\n"
2947
    "  concealed:  {}",
2948
    this->serial, who->serial, who->dead(), weapon->in_range( this, who ), hidden(), who->hidden(),
×
2949
    is_concealed_from_me( who ) );
×
2950
  if ( who->dead() )
23✔
2951
    return false;
×
2952
  if ( !weapon->in_range( this, who ) )
23✔
2953
    return false;
3✔
2954
  if ( hidden() && !cached_settings.get( PRIV_FLAGS::HIDDEN_ATTACK ) )
20✔
2955
    return false;
2✔
2956
  if ( who->hidden() && !cached_settings.get( PRIV_FLAGS::ATTACK_HIDDEN ) )
18✔
2957
    return false;
2✔
2958
  if ( is_concealed_from_me( who ) )
16✔
2959
    return false;
×
2960
  if ( !realm()->has_los( *this, *who ) )
16✔
2961
    return false;
×
2962
  return true;
16✔
2963
}
2964

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

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

2986
  return nullptr;
170✔
2987
}
2988

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

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

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

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

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

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

3030
void Character::inform_leftarea( Character* /*wholeft*/ )
1✔
3031
{
3032
  // virtual that does nothing at character level, but fires event for NPCs
3033
}
1✔
3034

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

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

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

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

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

3070
  opponent_ = new_opponent;
86✔
3071

3072

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

3079
    if ( opponent_ != nullptr )
86✔
3080
    {
3081
      repsys_on_attack( opponent_ );
4✔
3082
      if ( opponent_->get_opponent() == nullptr )
4✔
3083
        opponent_->reset_swing_timer();
4✔
3084

3085
      opponent_->opponent_of.insert( this );
4✔
3086

3087
      opponent_->inform_engaged( this );
4✔
3088

3089
      opponent_->schedule_attack();
4✔
3090
    }
3091

3092
    send_highlight();
86✔
3093
  }
3094
}
3095

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

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

3119
bool Character::warmode() const
265✔
3120
{
3121
  return mob_flags_.get( MOB_FLAGS::WARMODE );
265✔
3122
}
3123

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

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

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

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

3165
          msgmove.Send( chr->client );
2✔
3166
        } );
3167
  }
4✔
3168
}
4✔
3169

3170
const AttributeValue& Character::weapon_attribute() const
16✔
3171
{
3172
  return attribute( weapon->attribute().attrid );
16✔
3173
}
3174

3175
unsigned short Character::random_weapon_damage() const
8✔
3176
{
3177
  return weapon->get_random_damage();
8✔
3178
}
3179

3180
unsigned short Character::min_weapon_damage() const
90✔
3181
{
3182
  return weapon->min_weapon_damage();
90✔
3183
}
3184

3185
unsigned short Character::max_weapon_damage() const
90✔
3186
{
3187
  return weapon->max_weapon_damage();
90✔
3188
}
3189

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

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

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

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

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

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

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

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

3279

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

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

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

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

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

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

3345
  repsys_on_attack( opponent );
8✔
3346
  repsys_on_damage( opponent );
8✔
3347

3348
  do_attack_effects( opponent );
8✔
3349

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

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

3369
    double damage = random_weapon_damage();
8✔
3370
    damage_weapon();
8✔
3371

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

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

3379
    damage *= damage_multiplier;
8✔
3380

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

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

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

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

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

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

3479
  if ( check_opponents_after_check )
186✔
3480
  {
3481
    if ( opponent_ != nullptr )
169✔
3482
      opponent_->check_attack_after_move( false );
3✔
3483

3484
    // attacking can change the opponent_of array drastically.
3485
    std::set<Character*> tmp( opponent_of );
169✔
3486
    for ( auto& chr : tmp )
172✔
3487
    {
3488
      chr->check_attack_after_move( false );
3✔
3489
    }
3490
  }
169✔
3491
}
186✔
3492

3493

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

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

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

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

3531
  if ( cur_justice_region != new_justice_region )
105✔
3532
  {
3533
    if ( cur_justice_region != nullptr )
×
3534
      cur_justice_region->RunLeaveScript( client->chr );
×
3535
    if ( new_justice_region != nullptr )
×
3536
      new_justice_region->RunEnterScript( client->chr );
×
3537

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

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

3560
    client->gd->justice_region = new_justice_region;
×
3561

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

3578

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

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

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

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

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

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

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

3653
void Character::check_region_changes()
168✔
3654
{
3655
  if ( client != nullptr )
168✔
3656
  {
3657
    check_weather_region_change();
105✔
3658

3659
    check_light_region_change();
105✔
3660

3661
    check_justice_region_change();
105✔
3662

3663
    check_music_region_change();
105✔
3664
  }
3665
}
168✔
3666

3667
void Character::position_changed()
256✔
3668
{
3669
  wornitems->setposition( pos() );
256✔
3670
  position_changed_at_ = Core::polclock();
256✔
3671
}
256✔
3672

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

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

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

3698
    realm()->notify_unhid( *this );
3✔
3699

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

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

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

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

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

3733
bool Character::can_face( Core::UFACING /*i_facing*/ )
26✔
3734
{
3735
  if ( can_freemove() )
26✔
3736
    return true;
×
3737

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

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

3757
  return true;
26✔
3758
}
3759

3760

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

3769
  if ( i_facing != facing )
26✔
3770
  {
3771
    setfacing( static_cast<u8>( i_facing ) );
8✔
3772

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

3785
  return true;
26✔
3786
}
3787

3788

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

3793
  Multi::UMulti* multi = Core::system_find_multi( client->gd->custom_house_serial );
×
3794
  if ( multi != nullptr )
×
3795
  {
3796
    Multi::UHouse* house = multi->as_house();
×
3797
    if ( house != nullptr )
×
3798
    {
3799
      Core::UFACING i_facing = static_cast<Core::UFACING>( i_dir & PKTIN_02_FACING_MASK );
×
3800
      if ( i_facing != facing )
×
3801
      {
3802
        setfacing( static_cast<u8>( i_facing ) );
×
3803
        set_dirty();
×
3804
        dir = i_dir;
×
3805
        return true;
×
3806
      }
3807

3808
      auto newpos = pos().move( static_cast<Core::UFACING>( facing ) );
×
3809
      newpos.z( house->z() +
×
3810
                Multi::CustomHouseDesign::custom_house_z_xlate_table[house->editing_floor_num] );
×
3811
      const Multi::MultiDef& def = house->multidef();
×
3812
      auto relpos = newpos - house->pos().xy();
×
3813
      // minx and y are wall elements and z is 7
3814
      // mobile will look like flying when allowing the min coords
3815
      if ( def.within_multi( relpos ) && relpos.x() != def.minrxyz.x() &&
×
3816
           relpos.y() != def.minrxyz.y() )
×
3817
      {
3818
        Core::Pos4d oldpos = pos();
×
3819
        setposition( newpos );
×
3820
        MoveCharacterWorldPosition( oldpos, this );
×
3821

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

3831
bool Character::is_piloting_boat() const
140✔
3832
{
3833
  auto* mountpiece = wornitem( Core::LAYER_MOUNT );
140✔
3834
  return mountpiece != nullptr && mountpiece->objtype_ == Core::settingsManager.extobj.boatmount;
140✔
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 )
22✔
3846
{
3847
  if ( is_piloting_boat() )
22✔
3848
  {
3849
    return false;
1✔
3850
  }
3851

3852
  lastpos = pos();
21✔
3853

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

3858
  u8 oldFacing = facing;
21✔
3859

3860
  Core::UFACING i_facing = static_cast<Core::UFACING>( i_dir & PKTIN_02_FACING_MASK );
21✔
3861
  if ( !face( i_facing ) )
21✔
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 ) )
21✔
3867
  {
3868
    if ( facing & 1 )  // check if diagonal movement is allowed -- Nando (2009-02-26)
16✔
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 ) );
16✔
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;
16✔
3893
    if ( !realm()->walkheight( this, new_pos.xy(), new_pos.z(), &newz, &supporting_multi,
16✔
3894
                               &walkon_item, &current_boost ) )
3895
      return false;
1✔
3896
    new_pos.z( static_cast<s8>( newz ) );
15✔
3897
    remote_containers_.clear();
15✔
3898

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

3902
    if ( !can_freemove() && Core::settingsManager.ssopt.movement_uses_stamina && !dead() )
15✔
3903
    {
3904
      int carry_perc = weight() * 100 / carrying_capacity();
15✔
3905
      unsigned short tmv = movecost(
30✔
3906
          this, carry_perc, ( i_dir & PKTIN_02_DIR_RUNNING_BIT ) ? true : false, on_mount() );
15✔
3907
      VitalValue& stamina = vital( Core::gamestate.pVitalStamina->vitalid );
15✔
3908
      if ( !consume( Core::gamestate.pVitalStamina, stamina, tmv, VitalDepletedReason::MOVEMENT ) )
15✔
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() )
15✔
3917
      reset_swing_timer();
×
3918

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

3923
    if ( on_mount() && !script_isa( Core::POLCLASS_NPC ) )
15✔
3924
    {
3925
      mountedsteps_++;
×
3926
    }
3927
    if ( supporting_multi != nullptr )
15✔
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 )
15✔
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;
15✔
3951
    MoveCharacterWorldPosition( oldpos, this );
15✔
3952

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

3959
    if ( hidden() )
15✔
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 )
15✔
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();
20✔
3979
  dir = i_dir;
20✔
3980

3981
  return true;
20✔
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()
18✔
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() );
18✔
4003
  // TODO Pos: realm should be all the time nullptr for these items
4004
  if ( has_gotten_item() )
18✔
4005
  {
4006
    auto* item = gotten_item().item();
3✔
4007
    item->setposition( Core::Pos4d( item->pos().xyz(), realm() ) );
3✔
4008
    if ( item->isa( Core::UOBJ_CLASS::CLASS_CONTAINER ) )
3✔
4009
    {
4010
      Core::UContainer* cont = static_cast<Core::UContainer*>( item );
3✔
4011
      cont->for_each_item( Core::setrealm, (void*)realm() );
3✔
4012
    }
4013
  }
4014
  if ( trading_cont.get() )
18✔
4015
  {
4016
    trading_cont->setposition( Core::Pos4d( trading_cont->pos().xyz(), realm() ) );
×
4017
    trading_cont->for_each_item( Core::setrealm, (void*)realm() );
×
4018
  }
4019

4020
  if ( has_active_client() )
18✔
4021
  {
4022
    // these are important to keep here in this order
4023
    Core::send_realm_change( client, realm() );
14✔
4024
    Core::send_map_difs( client );
14✔
4025
    if ( Core::settingsManager.ssopt.core_sends_season )
14✔
4026
      Core::send_season_info( client );
14✔
4027
    Core::send_full_statmsg( client, this );
14✔
4028
    Core::send_feature_enable( client );
14✔
4029
  }
4030
}
18✔
4031

4032
bool Character::CheckPushthrough()
15✔
4033
{
4034
  if ( !can_freemove() && Core::gamestate.system_hooks.pushthrough_hook )
15✔
4035
  {
4036
    auto newpos = pos().move( static_cast<Core::UFACING>( facing ) );
×
4037
    auto mobs = std::unique_ptr<Bscript::ObjArray>();
×
4038

4039
    Core::WorldIterator<Core::MobileFilter>::InRange(
×
4040
        newpos, 0,
4041
        [&]( Mobile::Character* _chr )
×
4042
        {
4043
          if ( _chr->z() >= z() - 10 && _chr->z() <= z() + 10 && !_chr->dead() &&
×
4044
               ( is_visible_to_me( _chr ) ||
×
4045
                 _chr->hidden() ) )  // add hidden mobs even if they're not visible to me
×
4046
          {
4047
            if ( !mobs )
×
4048
              mobs = std::make_unique<Bscript::ObjArray>();
×
4049
            mobs->addElement( make_mobileref( _chr ) );
×
4050
          }
4051
        } );
×
4052

4053
    if ( mobs )
×
4054
    {
4055
      return Core::gamestate.system_hooks.pushthrough_hook->call( make_mobileref( this ),
×
4056
                                                                  mobs.release() );
×
4057
    }
4058
    return true;
×
4059
  }
×
4060
  return true;
15✔
4061
}
4062

4063
void Character::tellmove()
166✔
4064
{
4065
  check_region_changes();
166✔
4066
  PropagateMove( this );
166✔
4067

4068
  // notify npcs and items (maybe the PropagateMove should also go there eventually? - Nando
4069
  // 2018-06-16)
4070
  realm()->notify_moved( *this );
166✔
4071

4072
  check_attack_after_move( true );
166✔
4073

4074
  move_reason = OTHER;
166✔
4075
}
166✔
4076

4077
void Character::add_remote_container( Items::Item* item )
×
4078
{
4079
  remote_containers_.emplace_back( item );
×
4080
}
×
4081

4082
Items::Item* Character::search_remote_containers( u32 find_serial, bool* isRemoteContainer ) const
45✔
4083
{
4084
  if ( isRemoteContainer )
45✔
4085
    *isRemoteContainer = false;
40✔
4086
  for ( const auto& elem : remote_containers_ )
45✔
4087
  {
4088
    Items::Item* item = elem.get();
×
4089
    if ( item->orphan() )
×
4090
      continue;  // it'll be cleaned up when they move
×
4091
    if ( item->serial == find_serial )
×
4092
    {
4093
      if ( isRemoteContainer )
×
4094
        *isRemoteContainer = true;
×
4095
      return item;
×
4096
    }
4097
    if ( item->isa( Core::UOBJ_CLASS::CLASS_CONTAINER ) )
×
4098
    {
4099
      item = ( (Core::UContainer*)item )->find( find_serial );
×
4100
      if ( item )
×
4101
      {
4102
        if ( isRemoteContainer )
×
4103
          *isRemoteContainer = false;
×
4104
        return item;
×
4105
      }
4106
    }
4107
  }
4108
  return nullptr;
45✔
4109
}
4110

4111
bool Character::mightsee( const Items::Item* item ) const
76✔
4112
{
4113
  const auto* owner = item->toplevel_owner();
76✔
4114
  for ( const auto& elem : remote_containers_ )
76✔
4115
  {
4116
    Items::Item* additional_item = elem.get();
×
4117
    if ( additional_item == owner )
×
4118
      return true;
×
4119
  }
4120

4121
  return in_visual_range( owner );
76✔
4122
}
4123

4124
bool Character::squelched() const
37✔
4125
{
4126
  Core::gameclock_t squelched = squelched_until();
37✔
4127
  if ( squelched == 0 )
37✔
4128
    return false;
36✔
4129
  if ( squelched == ~0u )
1✔
4130
    return true;
×
4131

4132
  if ( Core::read_gameclock() < squelched )
1✔
4133
  {
4134
    return true;
1✔
4135
  }
4136

4137
  const_cast<Character*>( this )->squelched_until( 0 );
×
4138
  return false;
×
4139
}
4140

4141
bool Character::deafened() const
38✔
4142
{
4143
  Core::gameclock_t deafened = deafened_until();
38✔
4144
  if ( deafened == 0 )
38✔
4145
    return false;
37✔
4146
  if ( deafened == ~0u )
1✔
4147
    return true;
×
4148

4149
  if ( Core::read_gameclock() < deafened )
1✔
4150
  {
4151
    return true;
1✔
4152
  }
4153

4154
  const_cast<Character*>( this )->deafened_until( 0 );
×
4155
  return false;
×
4156
}
4157

4158
bool Character::invul() const
464✔
4159
{
4160
  return cached_settings.get( PRIV_FLAGS::INVUL );
464✔
4161
}
4162

4163
u16 Character::strength() const
23✔
4164
{
4165
  return static_cast<u16>( attribute( Core::gamestate.pAttrStrength->attrid ).effective() );
23✔
4166
}
4167
u16 Character::dexterity() const
173✔
4168
{
4169
  return static_cast<u16>( attribute( Core::gamestate.pAttrDexterity->attrid ).effective() );
173✔
4170
}
4171
u16 Character::intelligence() const
×
4172
{
4173
  return static_cast<u16>( attribute( Core::gamestate.pAttrIntelligence->attrid ).effective() );
×
4174
}
4175

4176
bool Character::target_cursor_busy() const
8✔
4177
{
4178
  if ( client && client->gd && client->gd->tcursor2 && client->gd->target_cursor_uoemod != nullptr )
8✔
4179
    return true;
×
4180
  return false;
8✔
4181
}
4182

4183
void Character::cancel_menu()
×
4184
{
4185
  if ( client )
×
4186
  {
4187
    client->gd->menu.clear();
×
4188
    if ( client->gd->on_menu_selection != nullptr )
×
4189
      client->gd->on_menu_selection( client, nullptr, nullptr );
×
4190
    client->gd->on_menu_selection = nullptr;
×
4191
  }
4192
}
×
4193

4194
bool Character::is_trading() const
192✔
4195
{
4196
  return ( trading_with.get() != nullptr );
192✔
4197
}
4198

4199
bool Character::trade_accepted() const
×
4200
{
4201
  return mob_flags_.get( MOB_FLAGS::TRADE_ACCEPTED );
×
4202
}
4203

4204
void Character::trade_accepted( bool newvalue )
×
4205
{
4206
  mob_flags_.change( MOB_FLAGS::TRADE_ACCEPTED, newvalue );
×
4207
}
×
4208

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

4220
Core::UContainer* Character::trade_container()
×
4221
{
4222
  return trading_cont.get();
×
4223
}
4224

4225
// SkillValue removed for no use - MuadDib
4226

4227
const char* Character::target_tag() const
×
4228
{
4229
  return "mobile";
×
4230
}
4231

4232
void Character::set_caps_to_default()
97✔
4233
{
4234
  for ( unsigned ai = 0; ai < Core::gamestate.numAttributes; ++ai )
5,141✔
4235
  {
4236
    Attribute* pAttr = Core::gamestate.attributes[ai];
5,044✔
4237
    AttributeValue& av = attribute( ai );
5,044✔
4238

4239
    av = attribute( ai );
5,044✔
4240
    av.cap( pAttr->default_cap );
5,044✔
4241
  }
4242
}
97✔
4243

4244
u16 Character::last_textcolor() const
×
4245
{
4246
  return _last_textcolor;
×
4247
}
4248

4249
void Character::last_textcolor( u16 new_color )
3✔
4250
{
4251
  _last_textcolor = new_color;
3✔
4252
}
3✔
4253

4254
unsigned int Character::guildid() const
2✔
4255
{
4256
  auto g = guild();
2✔
4257
  return ( g != nullptr ) ? g->guildid() : 0;
2✔
4258
}
4259

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

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

4274
  if ( client != nullptr )
2✔
4275
    send_buff_message( this, icon, true, duration, cl_name, name_arguments, cl_descr,
×
4276
                       desc_arguments );
4277
}
4✔
4278

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

4289
  if ( b == buffs_.end() )
4✔
4290
    return false;
3✔
4291

4292
  buffs_.erase( b );
1✔
4293
  if ( client != nullptr )
1✔
4294
    send_buff_message( this, icon, false );
×
4295
  return true;
1✔
4296
}
4297

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

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

4324
  for ( const auto& [icon, buf] : buffs_ )
2✔
4325
  {
4326
    u16 duration = Clib::clamp_convert<u16>( buf.end - Core::read_gameclock() );
×
4327

4328
    send_buff_message( this, icon, true, duration, buf.cl_name, buf.name_arguments, buf.cl_descr,
×
4329
                       buf.desc_arguments );
×
4330
  }
4331
}
4332

4333
u8 Character::los_size() const
62,978✔
4334
{
4335
  return client ? client->update_range() : Core::settingsManager.ssopt.default_visual_range;
62,978✔
4336
}
4337

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

4378
  size += Clib::memsize( attributes ) + Clib::memsize( vitals ) + Clib::memsize( armor_ ) +
2✔
4379
          Clib::memsize( remote_containers_ ) + Clib::memsize( opponent_of ) +
2✔
4380
          Clib::memsize( aggressor_to_ ) + Clib::memsize( lawfully_damaged_ ) +
2✔
4381
          Clib::memsize( to_be_reportable_ ) + Clib::memsize( reportable_ ) +
2✔
4382
          Clib::memsize( buffs_ );
2✔
4383
  return size;
2✔
4384
}
4385

4386
void Character::on_delete_from_account()
3✔
4387
{
4388
  if ( realm() )
3✔
4389
    realm()->remove_mobile( *this, Realms::WorldChangeReason::PlayerDeleted );
3✔
4390
}
3✔
4391

4392
bool Character::has_paperdoll() const
14✔
4393
{
4394
  switch ( graphic )
14✔
4395
  {
4396
  case UOBJ_HUMAN_MALE:
14✔
4397
  case UOBJ_HUMAN_FEMALE:
4398
  case UOBJ_HUMAN_MALE_GHOST:
4399
  case UOBJ_HUMAN_FEMALE_GHOST:
4400
  case UOBJ_ELF_MALE:
4401
  case UOBJ_ELF_FEMALE:
4402
  case UOBJ_ELF_MALE_GHOST:
4403
  case UOBJ_ELF_FEMALE_GHOST:
4404
  case UOBJ_GARGOYLE_MALE:
4405
  case UOBJ_GARGOYLE_FEMALE:
4406
  case UOBJ_GARGOYLE_MALE_GHOST:
4407
  case UOBJ_GARGOYLE_FEMALE_GHOST:
4408
  case UOBJ_GAMEMASTER:
4409
  case UOBJ_LORD_BRITISH:
4410
  case UOBJ_BLACKTHORN:
4411
  case UOBJ_DUPRE:
4412
    return true;
14✔
4413
  default:
×
4414
    return false;
×
4415
  }
4416
}
4417

4418
bool Character::get_method_hook( const char* methodname, Bscript::Executor* ex,
×
4419
                                 Core::ExportScript** hook, unsigned int* PC ) const
4420
{
4421
  if ( Core::gamestate.system_hooks.get_method_hook(
×
4422
           Core::gamestate.system_hooks.character_method_script.get(), methodname, ex, hook, PC ) )
4423
    return true;
×
4424
  return base::get_method_hook( methodname, ex, hook, PC );
×
4425
}
4426

4427
void Character::update_objects_on_range_change( u8 newrange )
2✔
4428
{
4429
  Network::RemoveObjectPkt msgremove( serial_ext );
2✔
4430

4431
  Core::WorldIterator<Core::MobileFilter>::InRange(
2✔
4432
      this, std::max( newrange, los_size() ),
2✔
4433
      [&]( Mobile::Character* zonechr )
2✔
4434
      {
4435
        if ( this == zonechr || !is_visible_to_me( zonechr, false /*rangecheck*/ ) )
2✔
4436
          return;
2✔
4437
        bool was_inrange = in_range( zonechr, los_size() + zonechr->visible_size() );
×
4438
        bool is_inrange = in_range( zonechr, newrange + zonechr->visible_size() );
×
4439
        if ( was_inrange && is_inrange )
×
4440
          return;
×
4441
        if ( !was_inrange && is_inrange )
×
4442
          Core::send_owncreate( client, zonechr );
×
4443
        else if ( was_inrange && !is_inrange )
×
4444
          Core::send_remove_character( client, zonechr, msgremove );
×
4445
      } );
4446

4447
  Core::WorldIterator<Core::ItemFilter>::InRange(
2✔
4448
      this, Core::gamestate.max_update_range_multi_only() + std::max( newrange, los_size() ),
2✔
4449
      [&]( Items::Item* zoneitem )
2✔
4450
      {
4451
        bool was_inrange = in_range( zoneitem, los_size() + zoneitem->visible_size() );
8✔
4452
        bool is_inrange = in_range( zoneitem, newrange + zoneitem->visible_size() );
8✔
4453
        if ( was_inrange && is_inrange )
8✔
4454
          return;
×
4455
        if ( !was_inrange && is_inrange )
8✔
4456
          Core::send_item( client, zoneitem );
3✔
4457
        else if ( was_inrange && !is_inrange )
5✔
4458
          Core::send_remove_object( client, zoneitem, msgremove );
3✔
4459
      } );
4460

4461
  Core::WorldIterator<Core::MultiFilter>::InRange(
2✔
4462
      this, Core::gamestate.max_update_range_multi_only() + std::max( newrange, los_size() ),
2✔
4463
      [&]( Multi::UMulti* zonemulti )
2✔
4464
      {
4465
        bool was_inrange = in_range( zonemulti, los_size() + zonemulti->visible_size() );
×
4466
        bool is_inrange = in_range( zonemulti, newrange + zonemulti->visible_size() );
×
4467
        if ( was_inrange && is_inrange )
×
4468
          return;
×
4469
        if ( !was_inrange && is_inrange )
×
4470
        {
4471
          Core::send_multi( client, zonemulti );
×
4472
          Multi::UHouse* house = zonemulti->as_house();
×
4473
          if ( client->acctSupports( Plib::ExpansionVersion::AOS ) && house != nullptr &&
×
4474
               house->IsCustom() )
×
4475
            Multi::CustomHousesSendShort( house, client );
×
4476
        }
×
4477
        else if ( was_inrange && !is_inrange )
×
4478
          Core::send_remove_object( client, zonemulti, msgremove );
×
4479
      } );
4480
}
2✔
4481

4482
AttributeValue::AttributeValue()
5,044✔
4483
    : _base( 0 ), _temp( 0 ), _intrinsic( 0 ), _lockstate( 0 ), _cap( 0 )
5,044✔
4484
{
4485
}
5,044✔
4486
int AttributeValue::effective() const
751✔
4487
{
4488
  int v = _base;
751✔
4489
  v += _temp;
751✔
4490
  v += _intrinsic;
751✔
4491
  return ( v > 0 ) ? ( v / 10 ) : 0;
751✔
4492
}
4493
int AttributeValue::effective_tenths() const
110✔
4494
{
4495
  int v = _base;
110✔
4496
  v += _temp;
110✔
4497
  v += _intrinsic;
110✔
4498
  return ( v > 0 ) ? v : 0;
110✔
4499
}
4500
int AttributeValue::base() const
1,343✔
4501
{
4502
  return _base;
1,343✔
4503
}
4504
void AttributeValue::base( unsigned short base )
314✔
4505
{
4506
  passert( base <= ATTRIBUTE_MAX_BASE );
314✔
4507
  _base = base;
314✔
4508
}
314✔
4509
int AttributeValue::temp_mod() const
×
4510
{
4511
  return _temp;
×
4512
}
4513
void AttributeValue::temp_mod( short temp )
×
4514
{
4515
  _temp = temp;
×
4516
}
×
4517
int AttributeValue::intrinsic_mod() const
×
4518
{
4519
  return _intrinsic;
×
4520
}
4521
void AttributeValue::intrinsic_mod( short val )
×
4522
{
4523
  _intrinsic = val;
×
4524
}
×
4525
unsigned char AttributeValue::lock() const
1,242✔
4526
{
4527
  return _lockstate;
1,242✔
4528
}
4529
void AttributeValue::lock( unsigned char lockstate )
12✔
4530
{
4531
  _lockstate = lockstate;
12✔
4532
}
12✔
4533
unsigned short AttributeValue::cap() const
1,148✔
4534
{
4535
  return _cap;
1,148✔
4536
}
4537
void AttributeValue::cap( unsigned short cap )
5,056✔
4538
{
4539
  _cap = cap;
5,056✔
4540
}
5,056✔
4541

4542
VitalValue::VitalValue() : _current( 0 ), _maximum( 0 ), _regenrate( 0 ) {}
291✔
4543
int VitalValue::current() const
11✔
4544
{
4545
  return _current;
11✔
4546
}
4547
int VitalValue::current_ones() const
1,336✔
4548
{
4549
  return _current / 100;
1,336✔
4550
}
4551
int VitalValue::current_thousands() const
67✔
4552
{
4553
  // use division to prevent overflow
4554
  return ( _current / 100 ) * 1000 / ( _maximum / 100 );
67✔
4555
}
4556
int VitalValue::maximum() const
228✔
4557
{
4558
  return _maximum;
228✔
4559
}
4560
int VitalValue::maximum_ones() const
807✔
4561
{
4562
  return _maximum / 100;
807✔
4563
}
4564
bool VitalValue::is_at_maximum() const
40✔
4565
{
4566
  return ( _current >= _maximum );
40✔
4567
}
4568
int VitalValue::regenrate() const
120✔
4569
{
4570
  return _regenrate;
120✔
4571
}
4572
void VitalValue::current( int cur )
309✔
4573
{
4574
  _current = cur;
309✔
4575
  if ( _current > _maximum )
309✔
4576
    _current = _maximum;
×
4577
}
309✔
4578
void VitalValue::current_ones( int ones )
78✔
4579
{
4580
  current( ones * 100 );
78✔
4581
}
78✔
4582
void VitalValue::maximum( int val )
261✔
4583
{
4584
  _maximum = val;
261✔
4585
  if ( _current > _maximum )
261✔
4586
    current( _maximum );
×
4587
}
261✔
4588
void VitalValue::regenrate( int rate )
261✔
4589
{
4590
  _regenrate = rate;
261✔
4591
}
261✔
4592
bool VitalValue::consume( unsigned int hamt )
15✔
4593
{
4594
  if ( _current > hamt )
15✔
4595
  {
4596
    _current -= hamt;
15✔
4597
    return true;
15✔
4598
  }
4599
  _current = 0;
×
4600
  return false;
×
4601
}
4602
void VitalValue::produce( unsigned int hamt )
120✔
4603
{
4604
  unsigned newcur = _current + hamt;
120✔
4605
  if ( newcur > _maximum || newcur < _current )
120✔
4606
  {
4607
    _current = _maximum;
119✔
4608
  }
4609
  else
4610
  {
4611
    _current = newcur;
1✔
4612
  }
4613
}
120✔
4614

4615
}  // namespace Mobile
4616
}  // 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