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

polserver / polserver / 25958346975

16 May 2026 09:21AM UTC coverage: 60.867% (+0.004%) from 60.863%
25958346975

push

github

turleypol
adapted is_attackable and do attack_effects

20 of 28 new or added lines in 2 files covered. (71.43%)

2 existing lines in 1 file now uncovered.

44702 of 73442 relevant lines covered (60.87%)

494146.38 hits per line

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

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

81

82
#include "mobile/attack.h"
83
#include "pol_global_config.h"
84

85
#include "charactr.h"
86

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

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

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

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

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

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

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

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

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

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

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

263

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

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

344
  set_caps_to_default();
99✔
345

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

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

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

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

375
  removal_cleanup();
99✔
376

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

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

384
  if ( repsys_task_ != nullptr )
99✔
385
    repsys_task_->cancel();
2✔
386

387
  if ( party_decline_timeout_ != nullptr )
99✔
388
    party_decline_timeout_->cancel();
×
389

390
  --Core::stateManager.uobjcount.ucharacter_count;
99✔
391
}
120✔
392

393
void Character::removal_cleanup()
101✔
394
{
395
  clear_opponent_of();
101✔
396

397
  /* This used to be a call to set_opponent(nullptr),
398
     which was slick,
399
     but that was sending disengage events, which were
400
     trying to resurrect this object. (C++)
401
     */
402
  if ( opponent_ )
101✔
403
  {
404
    opponent_.remove_opponent_of( Attackable{ this } );
×
405
    opponent_.clear();
×
406
  }
407

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

565

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

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

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

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

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

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

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

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

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

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

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

622

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

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

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

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

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

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

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

684

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

991

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

996
        av.lock( lock );
10✔
997

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

1003
  calc_vital_stuff();
11✔
1004

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

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

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

1035

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

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

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

1053
  cached_settings.reset();
192✔
1054

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

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

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

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

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

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

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

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

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

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

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

1161
  return false;
×
1162
}
1163

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

1171
  return false;
32✔
1172
}
1173

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1384
  return true;
64✔
1385
}
1386

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

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

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

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

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

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

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

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

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

1453

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

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

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

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

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

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

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

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

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

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

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

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

1577
  vv.maximum( mv );
267✔
1578

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

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

1586
  vv.regenrate( rr );
267✔
1587

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

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

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

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

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

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

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

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

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

1633
  return true;
×
1634
}
1635

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

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

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

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

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

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

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

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

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

1729
  return flag1;
203✔
1730
}
1731

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

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

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

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

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

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

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

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

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

1776
  send_update_hits_to_inrange( this );
×
1777

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

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

1784

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

1791
  int absorbed = blocked / 2;
×
1792

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

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

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

1808

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

1816
    armor->reduce_hp_from_hit();
×
1817
  }
1818
  return damage;
×
1819
}
1820

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

1831
  return damage;
×
1832
}
1833

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

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

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

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

1863
  Items::UArmor* armor = nullptr;
8✔
1864

1865
  defender->get_hitscript_params( damage, &armor, &rawdamage );
8✔
1866

1867

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

1878
  ex->priority( 100 );
8✔
1879

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

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

1905

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

1922
  if ( amount == 0 )
×
1923
    return;
×
1924

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

1927
  check_undamaged();
×
1928

1929
  send_update_hits_to_inrange( this );
×
1930
}
1931

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

1951

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

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

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

1988
  color = truecolor;
×
1989

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

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

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

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

2032
  // equip( create_backpack() );
2033

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

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

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

2070
  if ( client != nullptr )
1✔
2071
  {
2072
    if ( opponent_ )
×
2073
      opponent_.inform_disengaged( Attackable{ this } );
×
2074

2075
    client->pause();
×
2076
    send_warmode();
×
2077

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

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

2091
    client->restart();
×
2092
  }
2093

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

2097
  send_create_mobile_to_nearby_cansee( this );
1✔
2098

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

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

2117
void Character::remove_opponent_of( const Attackable& other )
4✔
2118
{
2119
  opponent_of.erase( other );
4✔
2120
}
4✔
2121
void Character::add_opponent_of( Attackable other )
4✔
2122
{
2123
  opponent_of.insert( std::move( other ) );
4✔
2124
}
4✔
2125

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

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

2140

2141
  u16 save_graphic = graphic;
74✔
2142

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

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

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

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

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

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

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

2194
  UPDATE_CHECKPOINT();
74✔
2195

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

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

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

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

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

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

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

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

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

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

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

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

2370
    UPDATE_CHECKPOINT();
2✔
2371

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

2418

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

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

2435
  UPDATE_CHECKPOINT();
74✔
2436

2437
  clear_opponent_of();
74✔
2438

2439
  set_opponent( {} );
74✔
2440

2441
  UPDATE_CHECKPOINT();
74✔
2442

2443
  on_death( corpse );
74✔
2444

2445
  CLEAR_CHECKPOINT();
74✔
2446
}
2447

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

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

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

2490
  /* add AR due to shield : parry skill / 2 is percent of AR */
2491
  // FIXME: Should we allow this to be adjustable via a prop? Hrmmmmm
2492
  if ( shield )
164✔
2493
  {
2494
    new_ar += std::max(
×
2495
        1.0,  //
×
2496
        0.5 * 0.01 * shield->ar() * attribute( Core::gamestate.pAttrParry->attrid ).effective() );
×
2497
  }
2498

2499
  new_ar += ar_mod();
164✔
2500

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

2503
  if ( client )
164✔
2504
  {  // CHECKME consider sending less frequently
2505
    send_full_statmsg( client, this );
6✔
2506
  }
2507
}
164✔
2508

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

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

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

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

2578
void Character::resetEquipableProperties()
164✔
2579
{
2580
  // reset resists
2581
  if ( has_fire_resist() )
164✔
2582
    fire_resist( fire_resist().setAsValue( 0 ) );
13✔
2583
  if ( has_cold_resist() )
164✔
2584
    cold_resist( cold_resist().setAsValue( 0 ) );
15✔
2585
  if ( has_energy_resist() )
164✔
2586
    energy_resist( energy_resist().setAsValue( 0 ) );
14✔
2587
  if ( has_poison_resist() )
164✔
2588
    poison_resist( poison_resist().setAsValue( 0 ) );
11✔
2589
  if ( has_physical_resist() )
164✔
2590
    physical_resist( physical_resist().setAsValue( 0 ) );
12✔
2591

2592
  // reset caps
2593
  if ( has_fire_resist_cap() )
164✔
2594
    fire_resist_cap( fire_resist_cap().setAsValue( 0 ) );
27✔
2595
  if ( has_cold_resist_cap() )
164✔
2596
    cold_resist_cap( cold_resist_cap().setAsValue( 0 ) );
35✔
2597
  if ( has_energy_resist_cap() )
164✔
2598
    energy_resist_cap( energy_resist_cap().setAsValue( 0 ) );
31✔
2599
  if ( has_poison_resist_cap() )
164✔
2600
    poison_resist_cap( poison_resist_cap().setAsValue( 0 ) );
19✔
2601
  if ( has_physical_resist_cap() )
164✔
2602
    physical_resist_cap( physical_resist_cap().setAsValue( 0 ) );
23✔
2603

2604
  // reset damages
2605
  if ( has_fire_damage() )
164✔
2606
    fire_damage( fire_damage().setAsValue( 0 ) );
95✔
2607
  if ( has_cold_damage() )
164✔
2608
    cold_damage( cold_damage().setAsValue( 0 ) );
97✔
2609
  if ( has_energy_damage() )
164✔
2610
    energy_damage( energy_damage().setAsValue( 0 ) );
96✔
2611
  if ( has_poison_damage() )
164✔
2612
    poison_damage( poison_damage().setAsValue( 0 ) );
93✔
2613
  if ( has_physical_damage() )
164✔
2614
    physical_damage( physical_damage().setAsValue( 0 ) );
94✔
2615

2616
  // reset others
2617
  if ( has_lower_reagent_cost() )
164✔
2618
    lower_reagent_cost( lower_reagent_cost().setAsValue( 0 ) );
61✔
2619
  if ( has_spell_damage_increase() )
164✔
2620
    spell_damage_increase( spell_damage_increase().setAsValue( 0 ) );
59✔
2621
  if ( has_faster_casting() )
164✔
2622
    faster_casting( faster_casting().setAsValue( 0 ) );
64✔
2623
  if ( has_faster_cast_recovery() )
164✔
2624
    faster_cast_recovery( faster_cast_recovery().setAsValue( 0 ) );
65✔
2625

2626
  if ( has_defence_increase() )
164✔
2627
    defence_increase( defence_increase().setAsValue( 0 ) );
39✔
2628
  if ( has_defence_increase_cap() )
164✔
2629
    defence_increase_cap( defence_increase_cap().setAsValue( 0 ) );
43✔
2630
  if ( has_lower_mana_cost() )
164✔
2631
    lower_mana_cost( lower_mana_cost().setAsValue( 0 ) );
62✔
2632
  if ( has_hit_chance() )
164✔
2633
    hit_chance( hit_chance().setAsValue( 0 ) );
63✔
2634
  if ( has_luck() )
164✔
2635
    luck( luck().setAsValue( 0 ) );
60✔
2636
  if ( has_swing_speed_increase() )
164✔
2637
    swing_speed_increase( swing_speed_increase().setAsValue( 0 ) );
61✔
2638
  if ( has_min_attack_range_increase() )
164✔
2639
    min_attack_range_increase( min_attack_range_increase().setAsValue( 0 ) );
57✔
2640
  if ( has_max_attack_range_increase() )
164✔
2641
    max_attack_range_increase( max_attack_range_increase().setAsValue( 0 ) );
57✔
2642
}
164✔
2643

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

2661
/* check skill: test skill, advance, reset atrophy timers, blah blah..
2662
   obviously, needs work, and more parameters.
2663
   */
2664

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

2679
  return false;
×
2680
}
2681

2682
void Character::check_concealment_level()
×
2683
{
2684
  if ( concealed() > cmdlevel() )
×
2685
    concealed( cmdlevel() );
×
2686
}
×
2687

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

2695
bool Character::is_visible_to_me( const Character* chr, bool check_range ) const
320✔
2696
{
2697
  if ( chr == nullptr )
320✔
2698
    return false;
×
2699
  if ( chr == this )
320✔
2700
    return true;  // I can always see myself (?)
147✔
2701
  if ( is_concealed_from_me( chr ) )
173✔
2702
    return false;
×
2703
  if ( !chr->logged_in() )
173✔
2704
    return false;
×
2705
  if ( chr->hidden() && !cached_settings.get( PRIV_FLAGS::SEE_HIDDEN ) )
173✔
2706
    return false;  // noone can see anyone hidden.
×
2707
  if ( check_range )
173✔
2708
  {
2709
    if ( !in_visual_range( chr ) )
150✔
2710
      return false;
54✔
2711
  }
2712
  else if ( chr->realm() != this->realm() )
23✔
2713
    return false;
1✔
2714

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

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

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

2761
            if ( chr->invul() )  // if invul send 0x17 for newer clients
×
2762
              msginvul.Send( client );
×
2763
            return;
×
2764
          }
2765

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2925
bool Character::is_attackable( const Attackable& attackable ) const
23✔
2926
{
2927
  passert( attackable.object() != nullptr );
23✔
2928
  const auto* obj = attackable.object();
23✔
2929
  if ( Core::settingsManager.combat_config.scripted_attack_checks )
23✔
2930
  {
2931
    INFO_PRINTLN_TRACE( 21 )
×
NEW
2932
    ( "is_attackable({:#x},{:#x}): will be handled by combat hook.", this->serial, obj->serial );
×
2933
    return true;
×
2934
  }
2935

2936
  const auto* mob = attackable.mobile();
23✔
2937
  INFO_PRINTLN_TRACE( 21 )
23✔
2938
  ( "is_attackable({:#x},{:#x}):\n"
×
2939
    "  who->dead:  {}\n"
2940
    "  wpn->inrange: {}\n"
2941
    "  hidden:     {}\n"
2942
    "  who->hidden:  {}\n"
2943
    "  concealed:  {}",
NEW
2944
    this->serial, obj->serial, mob ? mob->dead() : false, weapon->in_range( this, attackable ),
×
NEW
2945
    hidden(), mob ? mob->hidden() : false, mob ? is_concealed_from_me( mob ) : false );
×
2946

2947
  if ( mob && mob->dead() )
23✔
UNCOV
2948
    return false;
×
2949
  if ( !weapon->in_range( this, attackable ) )
23✔
2950
    return false;
3✔
2951
  if ( hidden() && !cached_settings.get( PRIV_FLAGS::HIDDEN_ATTACK ) )
20✔
2952
    return false;
2✔
2953
  if ( mob )
18✔
2954
  {
2955
    if ( mob->hidden() && !cached_settings.get( PRIV_FLAGS::ATTACK_HIDDEN ) )
18✔
2956
      return false;
2✔
2957
    if ( is_concealed_from_me( mob ) )
16✔
NEW
2958
      return false;
×
2959
  }
2960
  if ( !realm()->has_los( *this, *obj ) )
16✔
UNCOV
2961
    return false;
×
2962
  return true;
16✔
2963
}
2964

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

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

2988
  return {};
170✔
2989
}
2990

2991
void Character::send_highlight() const
91✔
2992
{
2993
  if ( client != nullptr && has_active_client() )
91✔
2994
  {
2995
    auto opponent = get_opponent();
4✔
2996

2997
    Network::PktHelper::PacketOut<Network::PktOut_AA> msg;
4✔
2998
    if ( opponent )
4✔
2999
      msg->Write<u32>( opponent.object()->serial_ext );
×
3000
    else
3001
      msg->offset += 4;
4✔
3002
    msg.Send( client );
4✔
3003
  }
4✔
3004
}
91✔
3005

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

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

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

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

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

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

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

3048
void Character::set_opponent( Attackable new_opponent, bool inform_old_opponent )
88✔
3049
{
3050
  INFO_PRINTLN_TRACE( 12 )
88✔
3051
  ( "set_opponent({:#x},{:#x})", this->serial, new_opponent ? new_opponent.object()->serial : 0 );
×
3052
  if ( new_opponent )
88✔
3053
  {
3054
    if ( auto* mob = new_opponent.mobile(); mob && mob->dead() )
4✔
3055
      return;
×
3056

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

3061
  if ( opponent_ )  // TODO Attackable
88✔
3062
  {
3063
    opponent_.remove_opponent_of( Attackable{ this } );
4✔
3064
    // Turley 05/26/09 no need to send disengaged event on shutdown
3065
    if ( !Clib::exit_signalled )
4✔
3066
    {
3067
      if ( inform_old_opponent )
4✔
3068
        opponent_.inform_disengaged( Attackable{ this } );
4✔
3069
    }
3070
  }
3071

3072
  opponent_ = std::move( new_opponent );
88✔
3073

3074

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

3081
    if ( opponent_ )
88✔
3082
    {
3083
      auto* mob = opponent_.mobile();
4✔
3084
      if ( mob )
4✔
3085
      {
3086
        repsys_on_attack( mob );
4✔
3087
        if ( !mob->get_opponent() )
4✔
3088
          mob->reset_swing_timer();
4✔
3089
      }
3090

3091
      opponent_.add_opponent_of( Attackable{ this } );
4✔
3092
      opponent_.inform_engaged( Attackable{ this } );
4✔
3093
      if ( mob )
4✔
3094
        mob->schedule_attack();
4✔
3095
    }
3096
  }
3097

3098
  send_highlight();
88✔
3099
}
3100

3101
void Character::select_opponent( u32 opp_serial )
×
3102
{
3103
  // test for setting to same so swing timer doesn't reset
3104
  // if you double-click the same guy over and over
3105
  if ( !opponent_ || opponent_.object()->serial != opp_serial )
×
3106
  {
3107
    auto* obj = Core::find_toplevel_object( opp_serial );
×
3108
    if ( !obj )
×
3109
      return;
×
3110
    if ( realm() != obj->realm() )
×
3111
      return;
×
3112
    set_opponent( Attackable{ obj } );
×
3113
  }
3114
}
3115

3116
void Character::disable_regeneration_for( int seconds )
4✔
3117
{
3118
  time_t new_disable_time = Core::poltime() + seconds;
4✔
3119
  if ( new_disable_time > disable_regeneration_until )
4✔
3120
    disable_regeneration_until = new_disable_time;
4✔
3121
}
4✔
3122

3123
bool Character::warmode() const
263✔
3124
{
3125
  return mob_flags_.get( MOB_FLAGS::WARMODE );
263✔
3126
}
3127

3128
void Character::set_warmode( bool i_warmode )
4✔
3129
{
3130
  if ( Core::gamestate.system_hooks.warmode_change )
4✔
3131
    Core::gamestate.system_hooks.warmode_change->call( new Module::ECharacterRefObjImp( this ),
×
3132
                                                       new Bscript::BLong( i_warmode ) );
×
3133

3134
  if ( warmode() != i_warmode )
4✔
3135
  {
3136
    disable_regeneration_for( 2 );
4✔
3137
  }
3138

3139
  mob_flags_.change( MOB_FLAGS::WARMODE, i_warmode );
4✔
3140
  if ( i_warmode == false )
4✔
3141
  {
3142
    set_opponent( {} );
1✔
3143
  }
3144
  reset_swing_timer();
4✔
3145

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

3169
          msgmove.Send( chr->client );
2✔
3170
        } );
3171
  }
4✔
3172
}
4✔
3173

3174
const AttributeValue& Character::weapon_attribute() const
16✔
3175
{
3176
  return attribute( weapon->attribute().attrid );
16✔
3177
}
3178

3179
unsigned short Character::random_weapon_damage() const
8✔
3180
{
3181
  return weapon->get_random_damage();
8✔
3182
}
3183

3184
unsigned short Character::min_weapon_damage() const
81✔
3185
{
3186
  return weapon->min_weapon_damage();
81✔
3187
}
3188

3189
unsigned short Character::max_weapon_damage() const
81✔
3190
{
3191
  return weapon->max_weapon_damage();
81✔
3192
}
3193

3194
void Character::damage_weapon()
8✔
3195
{
3196
  if ( !weapon->is_intrinsic() && !weapon->is_projectile() )
8✔
3197
  {
3198
    weapon->reduce_hp_from_hit();
×
3199
  }
3200
}
8✔
3201

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

3216
Core::UACTION Character::weapon_anim() const
8✔
3217
{
3218
  if ( on_mount() )
8✔
3219
    return weapon->mounted_anim();
×
3220
  return weapon->anim();
8✔
3221
}
3222

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

3252
void Character::do_hit_success_effects()
8✔
3253
{
3254
  unsigned short sound = weapon->hit_sound();
8✔
3255
  if ( sound )
8✔
3256
    play_sound_effect( this, sound );
×
3257
}
8✔
3258

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

3266
u16 Character::get_damaged_sound() const
×
3267
{
3268
  if ( gender == Plib::GENDER_MALE )
×
3269
    return SOUND_EFFECT_MALE_DEFENSE;
×
3270
  return SOUND_EFFECT_FEMALE_DEFENSE;
×
3271
}
3272

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

3283

3284
void Character::attack( const Attackable& opponent )
8✔
3285
{
3286
  INC_PROFILEVAR( combat_operations );
8✔
3287

3288
  if ( Core::gamestate.system_hooks.attack_hook )
8✔
3289
  {
3290
    if ( Core::gamestate.system_hooks.attack_hook->call( new Module::ECharacterRefObjImp( this ),
×
3291
                                                         opponent.object()->make_ref() ) )
×
3292
      return;
×
3293
  }
3294

3295
  if ( Core::settingsManager.watch.combat )
8✔
3296
    INFO_PRINTLN( "{} attacks {}", name(), opponent.object()->name() );
×
3297

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

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

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

3348
  auto* opponent_mobile = opponent.mobile();
8✔
3349
  if ( opponent_mobile )
8✔
3350
  {
3351
    repsys_on_attack( opponent_mobile );
8✔
3352
    repsys_on_damage( opponent_mobile );
8✔
3353
  }
3354

3355
  do_attack_effects( opponent );
8✔
3356
  if ( opponent.item() )
8✔
NEW
3357
    return;  // TODO Attackable
×
3358

3359
  if ( Core::gamestate.system_hooks.combat_advancement_hook )
8✔
3360
  {
3361
    Core::gamestate.system_hooks.combat_advancement_hook->call(
×
3362
        new Module::ECharacterRefObjImp( this ), new Module::EItemRefObjImp( weapon ),
×
3363
        new Module::ECharacterRefObjImp( opponent_mobile ) );
×
3364
  }
3365

3366
  double hit_chance = ( weapon_attribute().effective() + 50.0 ) /
8✔
3367
                      ( 2.0 * ( opponent_mobile->weapon_attribute().effective() + 50.0 ) );
8✔
3368
  hit_chance += hitchance_mod() * 0.001f;
8✔
3369
  hit_chance -= opponent_mobile->evasionchance_mod() * 0.001f;
8✔
3370
  if ( Core::settingsManager.watch.combat )
8✔
3371
    INFO_PRINT( "Chance to hit: {}: ", hit_chance );
×
3372
  if ( Clib::random_double( 1.0 ) < hit_chance )
8✔
3373
  {
3374
    if ( Core::settingsManager.watch.combat )
8✔
3375
      INFO_PRINTLN( "Hit!" );
×
3376
    do_hit_success_effects();
8✔
3377

3378
    double damage = random_weapon_damage();
8✔
3379
    damage_weapon();
8✔
3380

3381
    if ( Core::settingsManager.watch.combat )
8✔
3382
      INFO_PRINTLN( "Base damage: {}", damage );
×
3383

3384
    double damage_multiplier = attribute( Core::gamestate.pAttrTactics->attrid ).effective() + 50;
8✔
3385
    damage_multiplier += strength() * 0.20f;
8✔
3386
    damage_multiplier *= 0.01f;
8✔
3387

3388
    damage *= damage_multiplier;
8✔
3389

3390
    if ( Core::settingsManager.watch.combat )
8✔
3391
      INFO_PRINTLN( "Damage multiplier due to tactics/STR: {} Result: {}", damage_multiplier,
×
3392
                    damage );
3393

3394
    if ( opponent_mobile->shield != nullptr )
8✔
3395
    {
3396
      if ( Core::gamestate.system_hooks.parry_advancement_hook )
×
3397
      {
3398
        Core::gamestate.system_hooks.parry_advancement_hook->call(
×
3399
            new Module::ECharacterRefObjImp( this ), new Module::EItemRefObjImp( weapon ),
×
3400
            new Module::ECharacterRefObjImp( opponent_mobile ),
×
3401
            new Module::EItemRefObjImp( opponent_mobile->shield ) );
×
3402
      }
3403

3404
      double parry_chance =
3405
          opponent_mobile->attribute( Core::gamestate.pAttrParry->attrid ).effective() / 200.0;
×
3406
      parry_chance += opponent_mobile->parrychance_mod() * 0.001f;
×
3407
      if ( Core::settingsManager.watch.combat )
×
3408
        INFO_PRINT( "Parry Chance: {}: ", parry_chance );
×
3409
      if ( Clib::random_double( 1.0 ) < parry_chance )
×
3410
      {
3411
        if ( Core::settingsManager.watch.combat )
×
3412
          INFO_PRINTLN( "{} hits deflected", opponent_mobile->shield->ar() );
×
3413
        if ( Core::settingsManager.combat_config.display_parry_success_messages &&
×
3414
             opponent_mobile->client )
×
3415
          Core::send_sysmessage( opponent_mobile->client, "You successfully parried the attack!" );
×
3416

3417
        damage -= opponent_mobile->shield->ar();
×
3418
        if ( damage < 0 )
×
3419
          damage = 0;
×
3420
      }
3421
      else
3422
      {
3423
        if ( Core::settingsManager.watch.combat )
×
3424
          INFO_PRINTLN( "failed." );
×
3425
      }
3426
    }
3427
    if ( weapon->hit_script().empty() )
8✔
3428
    {
3429
      opponent_mobile->apply_damage( damage, this, true,
×
3430
                                     Core::settingsManager.combat_config.send_damage_packet );
×
3431
    }
3432
    else
3433
    {
3434
      run_hit_script( opponent_mobile, damage );
8✔
3435
    }
3436
  }
3437
  else
3438
  {
3439
    if ( Core::settingsManager.watch.combat )
×
3440
      INFO_PRINTLN( "Miss!" );
×
3441
    opponent_mobile->on_swing_failure( this );
×
3442
    do_hit_failure_effects();
×
3443
    if ( Core::gamestate.system_hooks.hitmiss_hook )
×
3444
    {
3445
      Core::gamestate.system_hooks.hitmiss_hook->call(
×
3446
          new Module::ECharacterRefObjImp( this ),
×
3447
          new Module::ECharacterRefObjImp( opponent_mobile ) );
×
3448
    }
3449
  }
3450
}
3451

3452
void Character::check_attack_after_move( bool check_opponents_after_check )
186✔
3453
{
3454
  FUNCTION_CHECKPOINT( check_attack_after_move, 1 );
186✔
3455
  auto opponent = get_attackable_opponent();
186✔
3456
  FUNCTION_CHECKPOINT( check_attack_after_move, 2 );
186✔
3457
  INFO_PRINTLN_TRACE( 20 )
186✔
3458
  ( "check_attack_after_move({:#x}): opponent is {:#x}", this->serial,
×
3459
    opponent ? opponent.object()->serial : 0 );
×
3460
  if ( opponent &&  // and I have an opponent
202✔
3461
       !dead() &&   // If I'm not dead
202✔
3462
       ( Core::settingsManager.combat_config.attack_while_frozen ||
16✔
3463
         ( !paralyzed() && !frozen() ) ) )
×
3464
  {
3465
    FUNCTION_CHECKPOINT( check_attack_after_move, 3 );
16✔
3466
    if ( mob_flags_.get( MOB_FLAGS::READY_TO_SWING ) )  // and I can swing now,
16✔
3467
    {                                                   // do so.
3468
      FUNCTION_CHECKPOINT( check_attack_after_move, 4 );
8✔
3469
      if ( Core::settingsManager.combat_config.send_swing_packet && client != nullptr )
8✔
3470
        send_fight_occuring( client, opponent.object() );
×
3471

3472
      // we don't want attack() to recursively cause new attacks
3473
      mob_flags_.remove( MOB_FLAGS::READY_TO_SWING );
8✔
3474
      attack( opponent );
8✔
3475
      FUNCTION_CHECKPOINT( check_attack_after_move, 5 );
8✔
3476
      reset_swing_timer();
8✔
3477
      FUNCTION_CHECKPOINT( check_attack_after_move, 6 );
8✔
3478
    }
3479
    else
3480
    {
3481
      FUNCTION_CHECKPOINT( check_attack_after_move, 7 );
8✔
3482
      INFO_PRINTLN_TRACE( 20 )( "not ready to swing" );
8✔
3483
      schedule_attack();
8✔
3484
      FUNCTION_CHECKPOINT( check_attack_after_move, 8 );
8✔
3485
    }
3486
  }
3487
  FUNCTION_CHECKPOINT( check_attack_after_move, 0 );
186✔
3488

3489
  if ( check_opponents_after_check )
186✔
3490
  {
3491
    if ( auto* mob = opponent_.mobile() )
169✔
3492
      mob->check_attack_after_move( false );
3✔
3493

3494
    // attacking can change the opponent_of array drastically.
3495
    AttackableSet tmp( opponent_of );
169✔
3496
    for ( auto& att : tmp )
172✔
3497
    {
3498
      if ( auto* mob = att.mobile() )
3✔
3499
        mob->check_attack_after_move( false );
3✔
3500
    }
3501
  }
169✔
3502
}
186✔
3503

3504

3505
void Character::check_light_region_change()
168✔
3506
{
3507
  auto light_unil = lightoverride_until();
168✔
3508
  if ( light_unil < Core::read_gameclock() && light_unil != ~0u )
168✔
3509
  {
3510
    lightoverride_until( 0 );
168✔
3511
    lightoverride( -1 );
168✔
3512
  }
3513
  if ( client->gd->weather_region && client->gd->weather_region->lightoverride != -1 &&
168✔
3514
       !has_lightoverride() )
×
3515
    return;
×
3516

3517
  int newlightlevel;
3518
  if ( has_lightoverride() )
168✔
3519
    newlightlevel = lightoverride();
×
3520
  else
3521
  {
3522
    // dave 12-22 check for no regions
3523
    Core::LightRegion* light_region = Core::gamestate.lightdef->getregion( pos() );
168✔
3524
    if ( light_region != nullptr )
168✔
3525
      newlightlevel = light_region->lightlevel;
×
3526
    else
3527
      newlightlevel = Core::settingsManager.ssopt.default_light_level;
168✔
3528
  }
3529

3530
  if ( newlightlevel != client->gd->lightlevel )
168✔
3531
  {
3532
    Core::send_light( client, newlightlevel );
2✔
3533
    client->gd->lightlevel = newlightlevel;
2✔
3534
  }
3535
}
3536

3537
void Character::check_justice_region_change()
105✔
3538
{
3539
  Core::JusticeRegion* cur_justice_region = client->gd->justice_region;
105✔
3540
  Core::JusticeRegion* new_justice_region = Core::gamestate.justicedef->getregion( pos() );
105✔
3541

3542
  if ( cur_justice_region != new_justice_region )
105✔
3543
  {
3544
    if ( cur_justice_region != nullptr )
×
3545
      cur_justice_region->RunLeaveScript( client->chr );
×
3546
    if ( new_justice_region != nullptr )
×
3547
      new_justice_region->RunEnterScript( client->chr );
×
3548

3549
    // print 'leaving' message
3550
    bool printmsgs;
3551
    if ( cur_justice_region != nullptr && new_justice_region != nullptr &&
×
3552
         cur_justice_region->entertext() == new_justice_region->entertext() &&
×
3553
         cur_justice_region->leavetext() == new_justice_region->leavetext() )
×
3554
    {
3555
      printmsgs = false;
×
3556
    }
3557
    else
3558
    {
3559
      printmsgs = true;
×
3560
    }
3561

3562
    if ( printmsgs && cur_justice_region )
×
3563
    {
3564
      const std::string& leavetext = cur_justice_region->leavetext();
×
3565
      if ( !leavetext.empty() )
×
3566
      {
3567
        Core::send_sysmessage( client, leavetext );
×
3568
      }
3569
    }
3570

3571
    client->gd->justice_region = new_justice_region;
×
3572

3573
    if ( new_justice_region && new_justice_region->RunNoCombatCheck( client ) == true )
×
3574
    {
3575
      get_opponent().remove_opponent_of( Attackable{ client->chr } );
×
3576
      if ( auto* opp2 = get_opponent().mobile(); opp2 && opp2->client )
×
3577
      {
3578
        // TODO Attackable
3579
        opp2->set_opponent( {}, true );
×
3580
        opp2->schedule_attack();
×
3581
        opp2->opponent_.clear();
×
3582
        opp2->clear_opponent_of();
×
3583
        set_opponent( {}, true );
×
3584
        if ( swing_task != nullptr )
×
3585
          swing_task->cancel();
×
3586
      }
3587
    }
3588

3589

3590
    // print 'entering' message
3591
    // handle nocombat while we have entered.
3592
    if ( printmsgs && new_justice_region )
×
3593
    {
3594
      const std::string& entertext = new_justice_region->entertext();
×
3595
      if ( !entertext.empty() )
×
3596
      {
3597
        Core::send_sysmessage( client, entertext );
×
3598
      }
3599
    }
3600
  }
3601
}
105✔
3602

3603
void Character::check_music_region_change()
105✔
3604
{
3605
  Core::MusicRegion* cur_music_region = client->gd->music_region;
105✔
3606
  Core::MusicRegion* new_music_region = Core::gamestate.musicdef->getregion( pos() );
105✔
3607

3608
  // may want to consider changing every n minutes, too, even if region didn't change
3609
  if ( cur_music_region != new_music_region )
105✔
3610
  {
3611
    client->gd->music_region = new_music_region;
×
3612
    if ( new_music_region != nullptr )
×
3613
    {
3614
      Core::send_midi( client, new_music_region->getmidi() );
×
3615
    }
3616
    else
3617
    {
3618
      Core::send_midi( client, 0 );
×
3619
    }
3620
  }
3621
}
105✔
3622

3623
void Character::check_weather_region_change( bool force )  // dave changed 5/26/03 - use force
107✔
3624
                                                           // boolean if current weather region
3625
                                                           // changed type/intensity
3626
{
3627
  Core::WeatherRegion* cur_weather_region = client->gd->weather_region;
107✔
3628
  Core::WeatherRegion* new_weather_region = Core::gamestate.weatherdef->getregion( pos() );
107✔
3629

3630
  // eric 5/31/03: I don't think this is right.  it's possible to go from somewhere that has
3631
  // no weather region, and to walk to somewhere that doesn't have a weather region.
3632
  //
3633
  if ( force || ( cur_weather_region != new_weather_region ) )
107✔
3634
  {
3635
    if ( new_weather_region != nullptr && new_weather_region->lightoverride != -1 &&
2✔
3636
         !has_lightoverride() )
×
3637
    {
3638
      Core::send_light( client, new_weather_region->lightoverride );
×
3639
      client->gd->lightlevel = new_weather_region->lightoverride;
×
3640
    }
3641

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

3649
    if ( new_weather_region )
2✔
3650
    {
3651
      Core::send_weather( client, new_weather_region->weathertype, new_weather_region->severity,
×
3652
                          new_weather_region->aux );
×
3653
    }
3654
    else
3655
    {
3656
      Core::send_weather( client, 0xff, 0, 0 );  // turn off
2✔
3657
    }
3658
    client->gd->weather_region = new_weather_region;
2✔
3659
  }
3660
}
107✔
3661

3662
void Character::check_region_changes()
168✔
3663
{
3664
  if ( client != nullptr )
168✔
3665
  {
3666
    check_weather_region_change();
105✔
3667

3668
    check_light_region_change();
105✔
3669

3670
    check_justice_region_change();
105✔
3671

3672
    check_music_region_change();
105✔
3673
  }
3674
}
168✔
3675

3676
void Character::position_changed()
258✔
3677
{
3678
  wornitems->setposition( pos() );
258✔
3679
  position_changed_at_ = Core::polclock();
258✔
3680
}
258✔
3681

3682
void Character::unhide()
3✔
3683
{
3684
  if ( Core::gamestate.system_hooks.un_hide )
3✔
3685
  {
3686
    if ( !Core::gamestate.system_hooks.un_hide->call( make_mobileref( this ) ) )
×
3687
      return;
×
3688
  }
3689

3690
  hidden( false );
3✔
3691
  if ( is_visible() )
3✔
3692
  {
3693
    if ( client != nullptr )
3✔
3694
      send_owncreate( client, this );
×
3695

3696
    Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
3✔
3697
        this,
3698
        [&]( Character* chr )
3✔
3699
        {
3700
          if ( chr == this )
×
3701
            return;
×
3702
          if ( !chr->is_visible_to_me( this ) )
×
3703
            return;
×
3704
          send_owncreate( chr->client, this );
×
3705
        } );
3706

3707
    realm()->notify_unhid( *this );
3✔
3708

3709
    if ( !Clib::exit_signalled )
3✔
3710
    {
3711
      check_attack_after_move( true );
3✔
3712
    }
3713
  }
3714
}
3715

3716
void Character::set_stealthsteps( unsigned short newval )
8✔
3717
{
3718
  stealthsteps_ = newval;
8✔
3719
}
8✔
3720

3721
bool Character::doors_block() const
100✔
3722
{
3723
  return !( graphic == UOBJ_GAMEMASTER || graphic == UOBJ_HUMAN_MALE_GHOST ||
200✔
3724
            graphic == UOBJ_HUMAN_FEMALE_GHOST || graphic == UOBJ_ELF_MALE_GHOST ||
100✔
3725
            graphic == UOBJ_ELF_FEMALE_GHOST || graphic == UOBJ_GARGOYLE_MALE_GHOST ||
100✔
3726
            graphic == UOBJ_GARGOYLE_FEMALE_GHOST ||
100✔
3727
            cached_settings.get( PRIV_FLAGS::IGNORE_DOORS ) );
200✔
3728
}
3729

3730
/*
3731
    This isn't quite right, for hidden characters.
3732
    What normally happens:
3733
    1) Client sends "use skill hiding"
3734
    2) Server sends "77 move" with the hidden flag set.
3735
    3) client sends move-request
3736
    4) server sends move-approve
3737
    5) server sense "78 create" message with hidden flag clear, at new posn.
3738

3739
    We're sending the "78 create" _before_ the move-approve.
3740
    */
3741

3742
bool Character::can_face( Core::UFACING /*i_facing*/ )
26✔
3743
{
3744
  if ( can_freemove() )
26✔
3745
    return true;
×
3746

3747
  if ( frozen() || paralyzed() )
26✔
3748
  {
3749
    if ( client != nullptr )
×
3750
    {
3751
      if ( frozen() )
×
3752
        private_say_above( this, this, "I am frozen and cannot move." );
×
3753
      else if ( paralyzed() )
×
3754
        private_say_above( this, this, "I am paralyzed and cannot move." );
×
3755
    }
3756
    return false;
×
3757
  }
3758

3759
  if ( Core::settingsManager.ssopt.movement_uses_stamina &&
78✔
3760
       vital( Core::gamestate.pVitalStamina->vitalid ).current_ones() == 0 && !dead() )
26✔
3761
  {
3762
    private_say_above( this, this, "You are too fatigued to move." );
×
3763
    return false;
×
3764
  }
3765

3766
  return true;
26✔
3767
}
3768

3769

3770
bool Character::face( Core::UFACING i_facing, int flags )
26✔
3771
{
3772
  if ( ( flags & 1 ) == 0 )
26✔
3773
  {
3774
    if ( !can_face( i_facing ) )
26✔
3775
      return false;
×
3776
  }
3777

3778
  if ( i_facing != facing )
26✔
3779
  {
3780
    setfacing( static_cast<u8>( i_facing ) );
8✔
3781

3782
    if ( Core::settingsManager.combat_config.reset_swing_onturn &&
16✔
3783
         !cached_settings.get( PRIV_FLAGS::FIRE_WHILE_MOVING ) && weapon->is_projectile() )
8✔
3784
      reset_swing_timer();
×
3785
    if ( hidden() && ( Core::settingsManager.ssopt.hidden_turns_count ) )
8✔
3786
    {
3787
      if ( stealthsteps_ == 0 )
×
3788
        unhide();
×
3789
      else
3790
        stealthsteps_--;
×
3791
    }
3792
  }
3793

3794
  return true;
26✔
3795
}
3796

3797

3798
bool Character::CustomHousingMove( unsigned char i_dir )
×
3799
{
3800
  passert( facing < 8 );
×
3801

3802
  Multi::UMulti* multi = Core::system_find_multi( client->gd->custom_house_serial );
×
3803
  if ( multi != nullptr )
×
3804
  {
3805
    Multi::UHouse* house = multi->as_house();
×
3806
    if ( house != nullptr )
×
3807
    {
3808
      Core::UFACING i_facing = static_cast<Core::UFACING>( i_dir & PKTIN_02_FACING_MASK );
×
3809
      if ( i_facing != facing )
×
3810
      {
3811
        setfacing( static_cast<u8>( i_facing ) );
×
3812
        set_dirty();
×
3813
        dir = i_dir;
×
3814
        return true;
×
3815
      }
3816

3817
      auto newpos = pos().move( static_cast<Core::UFACING>( facing ) );
×
3818
      newpos.z( house->z() +
×
3819
                Multi::CustomHouseDesign::custom_house_z_xlate_table[house->editing_floor_num] );
×
3820
      const Multi::MultiDef& def = house->multidef();
×
3821
      auto relpos = newpos - house->pos().xy();
×
3822
      // minx and y are wall elements and z is 7
3823
      // mobile will look like flying when allowing the min coords
3824
      if ( def.within_multi( relpos ) && relpos.x() != def.minrxyz.x() &&
×
3825
           relpos.y() != def.minrxyz.y() )
×
3826
      {
3827
        Core::Pos4d oldpos = pos();
×
3828
        setposition( newpos );
×
3829
        MoveCharacterWorldPosition( oldpos, this );
×
3830

3831
        position_changed();
×
3832
        set_dirty();
×
3833
        return true;
×
3834
      }
3835
    }
3836
  }
3837
  return false;
×
3838
}
3839

3840
bool Character::is_piloting_boat() const
140✔
3841
{
3842
  auto* mountpiece = wornitem( Core::LAYER_MOUNT );
140✔
3843
  return mountpiece != nullptr && mountpiece->objtype_ == Core::settingsManager.extobj.boatmount;
140✔
3844
}
3845

3846
//************************************
3847
// Method:    move
3848
// FullName:  Character::move
3849
// Access:    public
3850
// Returns:   bool
3851
// Qualifier:
3852
// Parameter: unsigned char i_dir
3853
//************************************
3854
bool Character::move( unsigned char i_dir )
22✔
3855
{
3856
  if ( is_piloting_boat() )
22✔
3857
  {
3858
    return false;
1✔
3859
  }
3860

3861
  lastpos = pos();
21✔
3862

3863
  // if currently building a house chr can move free inside the multi
3864
  if ( is_house_editing() )
21✔
3865
    return CustomHousingMove( i_dir );
×
3866

3867
  u8 oldFacing = facing;
21✔
3868

3869
  Core::UFACING i_facing = static_cast<Core::UFACING>( i_dir & PKTIN_02_FACING_MASK );
21✔
3870
  if ( !face( i_facing ) )
21✔
3871
    return false;
×
3872

3873
  // If we're a player, and we used our "move" command to turn,
3874
  //  we want to skip the meat of this function
3875
  if ( ( script_isa( Core::POLCLASS_NPC ) ) || ( facing == oldFacing ) )
21✔
3876
  {
3877
    if ( facing & 1 )  // check if diagonal movement is allowed -- Nando (2009-02-26)
16✔
3878
    {
3879
      short new_z;
3880
      u8 tmp_facing = ( facing + 1 ) & 0x7;
1✔
3881
      auto tmp_pos = pos().move( static_cast<Core::UFACING>( tmp_facing ) );
1✔
3882

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

3887
      tmp_facing = ( facing - 1 ) & 0x7;
1✔
3888
      tmp_pos = pos().move( static_cast<Core::UFACING>( tmp_facing ) );
1✔
3889

3890
      if ( !walk1 && !realm()->walkheight( this, tmp_pos.xy(), tmp_pos.z(), &new_z, nullptr,
1✔
3891
                                           nullptr, nullptr ) )
3892
        return false;
×
3893
    }
3894
    auto new_pos = pos().move( static_cast<Core::UFACING>( facing ) );
16✔
3895

3896
    // FIXME consider consolidating with similar code in UOEMOD.CPP
3897
    short newz;
3898
    Multi::UMulti* supporting_multi;
3899
    Items::Item* walkon_item;
3900

3901
    short current_boost = gradual_boost;
16✔
3902
    if ( !realm()->walkheight( this, new_pos.xy(), new_pos.z(), &newz, &supporting_multi,
16✔
3903
                               &walkon_item, &current_boost ) )
3904
      return false;
1✔
3905
    new_pos.z( static_cast<s8>( newz ) );
15✔
3906
    remote_containers_.clear();
15✔
3907

3908
    if ( !CheckPushthrough() )
15✔
3909
      return false;
×
3910

3911
    if ( !can_freemove() && Core::settingsManager.ssopt.movement_uses_stamina && !dead() )
15✔
3912
    {
3913
      int carry_perc = weight() * 100 / carrying_capacity();
15✔
3914
      unsigned short tmv = movecost(
30✔
3915
          this, carry_perc, ( i_dir & PKTIN_02_DIR_RUNNING_BIT ) ? true : false, on_mount() );
15✔
3916
      VitalValue& stamina = vital( Core::gamestate.pVitalStamina->vitalid );
15✔
3917
      if ( !consume( Core::gamestate.pVitalStamina, stamina, tmv, VitalDepletedReason::MOVEMENT ) )
15✔
3918
      {
3919
        private_say_above( this, this, "You are too fatigued to move." );
×
3920
        return false;
×
3921
      }
3922
    }
3923

3924
    // Maybe have a flag for this in servspecopt?
3925
    if ( !cached_settings.get( PRIV_FLAGS::FIRE_WHILE_MOVING ) && weapon->is_projectile() )
15✔
3926
      reset_swing_timer();
×
3927

3928
    Core::Pos4d oldpos = pos();
15✔
3929
    setposition( new_pos );
15✔
3930
    moved_at_ = Core::polclock();
15✔
3931

3932
    if ( on_mount() && !script_isa( Core::POLCLASS_NPC ) )
15✔
3933
    {
3934
      mountedsteps_++;
×
3935
    }
3936
    if ( supporting_multi != nullptr )
15✔
3937
    {
3938
      supporting_multi->register_object( this );
×
3939

3940
      if ( this->registered_multi == 0 )
×
3941
      {
3942
        this->registered_multi = supporting_multi->serial;
×
3943
        supporting_multi->walk_on( this );
×
3944
      }
3945
    }
3946
    else
3947
    {
3948
      if ( registered_multi > 0 )
15✔
3949
      {
3950
        Multi::UMulti* multi = Core::system_find_multi( registered_multi );
×
3951
        if ( multi != nullptr )
×
3952
        {
3953
          multi->unregister_object( (UObject*)this );
×
3954
        }
3955
        registered_multi = 0;
×
3956
      }
3957
    }
3958

3959
    gradual_boost = current_boost;
15✔
3960
    MoveCharacterWorldPosition( oldpos, this );
15✔
3961

3962
    position_changed();
15✔
3963
    if ( walkon_item != nullptr )
15✔
3964
    {
3965
      walkon_item->walk_on( this );
×
3966
    }
3967

3968
    if ( hidden() )
15✔
3969
    {
3970
      if ( ( ( i_dir & PKTIN_02_DIR_RUNNING_BIT ) &&
×
3971
             !cached_settings.get( PRIV_FLAGS::RUN_WHILE_STEALTH ) ) ||
×
3972
           ( stealthsteps_ == 0 ) )
×
3973
        unhide();
×
3974
      else if ( stealthsteps_ )
×
3975
        --stealthsteps_;
×
3976
    }
3977

3978
    if ( Core::gamestate.system_hooks.ouch_hook )
15✔
3979
    {
3980
      if ( ( lastpos.z() - z() ) > 21 )
×
3981
        Core::gamestate.system_hooks.ouch_hook->call(
×
3982
            make_mobileref( this ), new Bscript::BLong( lastpos.x() ),
×
3983
            new Bscript::BLong( lastpos.y() ), new Bscript::BLong( lastpos.z() ) );
×
3984
    }
3985
  }
3986

3987
  set_dirty();
20✔
3988
  dir = i_dir;
20✔
3989

3990
  return true;
20✔
3991

3992

3993
  // this would be a great place for tellmove(), except that
3994
  // we want to send the move acknowledge to the client before
3995
  // sending the move/create messages to the neighboring clients.
3996

3997
  // Why? Maybe to give the best response time to the client.
3998

3999
  // may want to rethink this, since it's starting to complicate
4000
  // things.
4001
}
4002

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

4029
  if ( has_active_client() )
18✔
4030
  {
4031
    // these are important to keep here in this order
4032
    Core::send_realm_change( client, realm() );
14✔
4033
    Core::send_map_difs( client );
14✔
4034
    if ( Core::settingsManager.ssopt.core_sends_season )
14✔
4035
      Core::send_season_info( client );
14✔
4036
    Core::send_full_statmsg( client, this );
14✔
4037
    Core::send_feature_enable( client );
14✔
4038
  }
4039
}
18✔
4040

4041
bool Character::CheckPushthrough()
15✔
4042
{
4043
  if ( !can_freemove() && Core::gamestate.system_hooks.pushthrough_hook )
15✔
4044
  {
4045
    auto newpos = pos().move( static_cast<Core::UFACING>( facing ) );
×
4046
    auto mobs = std::unique_ptr<Bscript::ObjArray>();
×
4047

4048
    Core::WorldIterator<Core::MobileFilter>::InRange(
×
4049
        newpos, 0,
4050
        [&]( Mobile::Character* _chr )
×
4051
        {
4052
          if ( _chr->z() >= z() - 10 && _chr->z() <= z() + 10 && !_chr->dead() &&
×
4053
               ( is_visible_to_me( _chr ) ||
×
4054
                 _chr->hidden() ) )  // add hidden mobs even if they're not visible to me
×
4055
          {
4056
            if ( !mobs )
×
4057
              mobs = std::make_unique<Bscript::ObjArray>();
×
4058
            mobs->addElement( make_mobileref( _chr ) );
×
4059
          }
4060
        } );
×
4061

4062
    if ( mobs )
×
4063
    {
4064
      return Core::gamestate.system_hooks.pushthrough_hook->call( make_mobileref( this ),
×
4065
                                                                  mobs.release() );
×
4066
    }
4067
    return true;
×
4068
  }
×
4069
  return true;
15✔
4070
}
4071

4072
void Character::tellmove()
166✔
4073
{
4074
  check_region_changes();
166✔
4075
  PropagateMove( this );
166✔
4076

4077
  // notify npcs and items (maybe the PropagateMove should also go there eventually? - Nando
4078
  // 2018-06-16)
4079
  realm()->notify_moved( *this );
166✔
4080

4081
  check_attack_after_move( true );
166✔
4082

4083
  move_reason = OTHER;
166✔
4084
}
166✔
4085

4086
void Character::add_remote_container( Items::Item* item )
×
4087
{
4088
  remote_containers_.emplace_back( item );
×
4089
}
×
4090

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

4120
bool Character::mightsee( const Items::Item* item ) const
76✔
4121
{
4122
  const auto* owner = item->toplevel_owner();
76✔
4123
  for ( const auto& elem : remote_containers_ )
76✔
4124
  {
4125
    Items::Item* additional_item = elem.get();
×
4126
    if ( additional_item == owner )
×
4127
      return true;
×
4128
  }
4129

4130
  return in_visual_range( owner );
76✔
4131
}
4132

4133
bool Character::squelched() const
37✔
4134
{
4135
  Core::gameclock_t squelched = squelched_until();
37✔
4136
  if ( squelched == 0 )
37✔
4137
    return false;
36✔
4138
  if ( squelched == ~0u )
1✔
4139
    return true;
×
4140

4141
  if ( Core::read_gameclock() < squelched )
1✔
4142
  {
4143
    return true;
1✔
4144
  }
4145

4146
  const_cast<Character*>( this )->squelched_until( 0 );
×
4147
  return false;
×
4148
}
4149

4150
bool Character::deafened() const
38✔
4151
{
4152
  Core::gameclock_t deafened = deafened_until();
38✔
4153
  if ( deafened == 0 )
38✔
4154
    return false;
37✔
4155
  if ( deafened == ~0u )
1✔
4156
    return true;
×
4157

4158
  if ( Core::read_gameclock() < deafened )
1✔
4159
  {
4160
    return true;
1✔
4161
  }
4162

4163
  const_cast<Character*>( this )->deafened_until( 0 );
×
4164
  return false;
×
4165
}
4166

4167
bool Character::invul() const
464✔
4168
{
4169
  return cached_settings.get( PRIV_FLAGS::INVUL );
464✔
4170
}
4171

4172
u16 Character::strength() const
23✔
4173
{
4174
  return static_cast<u16>( attribute( Core::gamestate.pAttrStrength->attrid ).effective() );
23✔
4175
}
4176
u16 Character::dexterity() const
177✔
4177
{
4178
  return static_cast<u16>( attribute( Core::gamestate.pAttrDexterity->attrid ).effective() );
177✔
4179
}
4180
u16 Character::intelligence() const
×
4181
{
4182
  return static_cast<u16>( attribute( Core::gamestate.pAttrIntelligence->attrid ).effective() );
×
4183
}
4184

4185
bool Character::target_cursor_busy() const
8✔
4186
{
4187
  if ( client && client->gd && client->gd->tcursor2 && client->gd->target_cursor_uoemod != nullptr )
8✔
4188
    return true;
×
4189
  return false;
8✔
4190
}
4191

4192
void Character::cancel_menu()
×
4193
{
4194
  if ( client )
×
4195
  {
4196
    client->gd->menu.clear();
×
4197
    if ( client->gd->on_menu_selection != nullptr )
×
4198
      client->gd->on_menu_selection( client, nullptr, nullptr );
×
4199
    client->gd->on_menu_selection = nullptr;
×
4200
  }
4201
}
×
4202

4203
bool Character::is_trading() const
196✔
4204
{
4205
  return ( trading_with.get() != nullptr );
196✔
4206
}
4207

4208
bool Character::trade_accepted() const
×
4209
{
4210
  return mob_flags_.get( MOB_FLAGS::TRADE_ACCEPTED );
×
4211
}
4212

4213
void Character::trade_accepted( bool newvalue )
×
4214
{
4215
  mob_flags_.change( MOB_FLAGS::TRADE_ACCEPTED, newvalue );
×
4216
}
×
4217

4218
void Character::create_trade_container()
×
4219
{
4220
  if ( trading_cont.get() == nullptr )  // FIXME hardcoded
×
4221
  {
4222
    Items::Item* cont = Items::Item::create( Core::settingsManager.extobj.secure_trade_container );
×
4223
    // TODO Pos: no realm
4224
    cont->setposition( pos() );
×
4225
    trading_cont.set( static_cast<Core::UContainer*>( cont ) );
×
4226
  }
4227
}
×
4228

4229
Core::UContainer* Character::trade_container()
×
4230
{
4231
  return trading_cont.get();
×
4232
}
4233

4234
// SkillValue removed for no use - MuadDib
4235

4236
const char* Character::target_tag() const
×
4237
{
4238
  return "mobile";
×
4239
}
4240

4241
void Character::set_caps_to_default()
99✔
4242
{
4243
  for ( unsigned ai = 0; ai < Core::gamestate.numAttributes; ++ai )
5,247✔
4244
  {
4245
    Attribute* pAttr = Core::gamestate.attributes[ai];
5,148✔
4246
    AttributeValue& av = attribute( ai );
5,148✔
4247

4248
    av = attribute( ai );
5,148✔
4249
    av.cap( pAttr->default_cap );
5,148✔
4250
  }
4251
}
99✔
4252

4253
u16 Character::last_textcolor() const
×
4254
{
4255
  return _last_textcolor;
×
4256
}
4257

4258
void Character::last_textcolor( u16 new_color )
3✔
4259
{
4260
  _last_textcolor = new_color;
3✔
4261
}
3✔
4262

4263
unsigned int Character::guildid() const
2✔
4264
{
4265
  auto g = guild();
2✔
4266
  return ( g != nullptr ) ? g->guildid() : 0;
2✔
4267
}
4268

4269
/**
4270
 * Adds a new buff or overwrites an existing one for the character
4271
 * Sends packets to the client accordingly
4272
 * @author Bodom
4273
 */
4274
void Character::addBuff( u16 icon, u16 duration, u32 cl_name, const std::string& name_arguments,
2✔
4275
                         u32 cl_descr, const std::string& desc_arguments )
4276
{
4277
  // Icon is already present, must send a remove packet first or client will not update
4278
  delBuff( icon );
2✔
4279

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

4283
  if ( client != nullptr )
2✔
4284
    send_buff_message( this, icon, true, duration, cl_name, name_arguments, cl_descr,
×
4285
                       desc_arguments );
4286
}
4✔
4287

4288
/**
4289
 * Removes a buff for the character
4290
 * Sends packets to the client accordingly
4291
 * @author Bodom
4292
 * @return True when the buff has been found and removed, False when the buff was not present
4293
 */
4294
bool Character::delBuff( u16 icon )
4✔
4295
{
4296
  auto b = buffs_.find( icon );
4✔
4297

4298
  if ( b == buffs_.end() )
4✔
4299
    return false;
3✔
4300

4301
  buffs_.erase( b );
1✔
4302
  if ( client != nullptr )
1✔
4303
    send_buff_message( this, icon, false );
×
4304
  return true;
1✔
4305
}
4306

4307
/**
4308
 * Removes al buffs for the character
4309
 * Sends packets to the client accordingly
4310
 * @author Bodom
4311
 */
4312
void Character::clearBuffs()
2✔
4313
{
4314
  if ( client != nullptr )
2✔
4315
  {
4316
    for ( const auto& buf : buffs_ )
×
4317
    {
4318
      send_buff_message( this, buf.first, false );
×
4319
    }
4320
  }
4321
  buffs_.clear();
2✔
4322
}
2✔
4323

4324
/**
4325
 * Resends all buffs (with updated duration), usually called at (re)login
4326
 * @author Bodom
4327
 */
4328
void Character::send_buffs()
2✔
4329
{
4330
  if ( client == nullptr )
2✔
4331
    return;
×
4332

4333
  for ( const auto& [icon, buf] : buffs_ )
2✔
4334
  {
4335
    u16 duration = Clib::clamp_convert<u16>( buf.end - Core::read_gameclock() );
×
4336

4337
    send_buff_message( this, icon, true, duration, buf.cl_name, buf.name_arguments, buf.cl_descr,
×
4338
                       buf.desc_arguments );
×
4339
  }
4340
}
4341

4342
u8 Character::los_size() const
62,990✔
4343
{
4344
  return client ? client->update_range() : Core::settingsManager.ssopt.default_visual_range;
62,990✔
4345
}
4346

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

4387
  size += Clib::memsize( attributes ) + Clib::memsize( vitals ) + Clib::memsize( armor_ ) +
2✔
4388
          Clib::memsize( remote_containers_ ) + Clib::memsize( opponent_of ) +
2✔
4389
          Clib::memsize( aggressor_to_ ) + Clib::memsize( lawfully_damaged_ ) +
2✔
4390
          Clib::memsize( to_be_reportable_ ) + Clib::memsize( reportable_ ) +
2✔
4391
          Clib::memsize( buffs_ );
2✔
4392
  return size;
2✔
4393
}
4394

4395
void Character::on_delete_from_account()
3✔
4396
{
4397
  if ( realm() )
3✔
4398
    realm()->remove_mobile( *this, Realms::WorldChangeReason::PlayerDeleted );
3✔
4399
}
3✔
4400

4401
bool Character::has_paperdoll() const
14✔
4402
{
4403
  switch ( graphic )
14✔
4404
  {
4405
  case UOBJ_HUMAN_MALE:
14✔
4406
  case UOBJ_HUMAN_FEMALE:
4407
  case UOBJ_HUMAN_MALE_GHOST:
4408
  case UOBJ_HUMAN_FEMALE_GHOST:
4409
  case UOBJ_ELF_MALE:
4410
  case UOBJ_ELF_FEMALE:
4411
  case UOBJ_ELF_MALE_GHOST:
4412
  case UOBJ_ELF_FEMALE_GHOST:
4413
  case UOBJ_GARGOYLE_MALE:
4414
  case UOBJ_GARGOYLE_FEMALE:
4415
  case UOBJ_GARGOYLE_MALE_GHOST:
4416
  case UOBJ_GARGOYLE_FEMALE_GHOST:
4417
  case UOBJ_GAMEMASTER:
4418
  case UOBJ_LORD_BRITISH:
4419
  case UOBJ_BLACKTHORN:
4420
  case UOBJ_DUPRE:
4421
    return true;
14✔
4422
  default:
×
4423
    return false;
×
4424
  }
4425
}
4426

4427
bool Character::get_method_hook( const char* methodname, Bscript::Executor* ex,
×
4428
                                 Core::ExportScript** hook, unsigned int* PC ) const
4429
{
4430
  if ( Core::gamestate.system_hooks.get_method_hook(
×
4431
           Core::gamestate.system_hooks.character_method_script.get(), methodname, ex, hook, PC ) )
4432
    return true;
×
4433
  return base::get_method_hook( methodname, ex, hook, PC );
×
4434
}
4435

4436
void Character::update_objects_on_range_change( u8 newrange )
2✔
4437
{
4438
  Network::RemoveObjectPkt msgremove( serial_ext );
2✔
4439

4440
  Core::WorldIterator<Core::MobileFilter>::InRange(
2✔
4441
      this, std::max( newrange, los_size() ),
2✔
4442
      [&]( Mobile::Character* zonechr )
2✔
4443
      {
4444
        if ( this == zonechr || !is_visible_to_me( zonechr, false /*rangecheck*/ ) )
2✔
4445
          return;
2✔
4446
        bool was_inrange = in_range( zonechr, los_size() + zonechr->visible_size() );
×
4447
        bool is_inrange = in_range( zonechr, newrange + zonechr->visible_size() );
×
4448
        if ( was_inrange && is_inrange )
×
4449
          return;
×
4450
        if ( !was_inrange && is_inrange )
×
4451
          Core::send_owncreate( client, zonechr );
×
4452
        else if ( was_inrange && !is_inrange )
×
4453
          Core::send_remove_character( client, zonechr, msgremove );
×
4454
      } );
4455

4456
  Core::WorldIterator<Core::ItemFilter>::InRange(
2✔
4457
      this, Core::gamestate.max_update_range_multi_only() + std::max( newrange, los_size() ),
2✔
4458
      [&]( Items::Item* zoneitem )
2✔
4459
      {
4460
        bool was_inrange = in_range( zoneitem, los_size() + zoneitem->visible_size() );
8✔
4461
        bool is_inrange = in_range( zoneitem, newrange + zoneitem->visible_size() );
8✔
4462
        if ( was_inrange && is_inrange )
8✔
4463
          return;
×
4464
        if ( !was_inrange && is_inrange )
8✔
4465
          Core::send_item( client, zoneitem );
3✔
4466
        else if ( was_inrange && !is_inrange )
5✔
4467
          Core::send_remove_object( client, zoneitem, msgremove );
3✔
4468
      } );
4469

4470
  Core::WorldIterator<Core::MultiFilter>::InRange(
2✔
4471
      this, Core::gamestate.max_update_range_multi_only() + std::max( newrange, los_size() ),
2✔
4472
      [&]( Multi::UMulti* zonemulti )
2✔
4473
      {
4474
        bool was_inrange = in_range( zonemulti, los_size() + zonemulti->visible_size() );
×
4475
        bool is_inrange = in_range( zonemulti, newrange + zonemulti->visible_size() );
×
4476
        if ( was_inrange && is_inrange )
×
4477
          return;
×
4478
        if ( !was_inrange && is_inrange )
×
4479
        {
4480
          Core::send_multi( client, zonemulti );
×
4481
          Multi::UHouse* house = zonemulti->as_house();
×
4482
          if ( client->acctSupports( Plib::ExpansionVersion::AOS ) && house != nullptr &&
×
4483
               house->IsCustom() )
×
4484
            Multi::CustomHousesSendShort( house, client );
×
4485
        }
×
4486
        else if ( was_inrange && !is_inrange )
×
4487
          Core::send_remove_object( client, zonemulti, msgremove );
×
4488
      } );
4489
}
2✔
4490

4491
AttributeValue::AttributeValue()
5,148✔
4492
    : _base( 0 ), _temp( 0 ), _intrinsic( 0 ), _lockstate( 0 ), _cap( 0 )
5,148✔
4493
{
4494
}
5,148✔
4495
int AttributeValue::effective() const
734✔
4496
{
4497
  int v = _base;
734✔
4498
  v += _temp;
734✔
4499
  v += _intrinsic;
734✔
4500
  return ( v > 0 ) ? ( v / 10 ) : 0;
734✔
4501
}
4502
int AttributeValue::effective_tenths() const
110✔
4503
{
4504
  int v = _base;
110✔
4505
  v += _temp;
110✔
4506
  v += _intrinsic;
110✔
4507
  return ( v > 0 ) ? v : 0;
110✔
4508
}
4509
int AttributeValue::base() const
1,345✔
4510
{
4511
  return _base;
1,345✔
4512
}
4513
void AttributeValue::base( unsigned short base )
322✔
4514
{
4515
  passert( base <= ATTRIBUTE_MAX_BASE );
322✔
4516
  _base = base;
322✔
4517
}
322✔
4518
int AttributeValue::temp_mod() const
×
4519
{
4520
  return _temp;
×
4521
}
4522
void AttributeValue::temp_mod( short temp )
×
4523
{
4524
  _temp = temp;
×
4525
}
×
4526
int AttributeValue::intrinsic_mod() const
×
4527
{
4528
  return _intrinsic;
×
4529
}
4530
void AttributeValue::intrinsic_mod( short val )
×
4531
{
4532
  _intrinsic = val;
×
4533
}
×
4534
unsigned char AttributeValue::lock() const
1,242✔
4535
{
4536
  return _lockstate;
1,242✔
4537
}
4538
void AttributeValue::lock( unsigned char lockstate )
12✔
4539
{
4540
  _lockstate = lockstate;
12✔
4541
}
12✔
4542
unsigned short AttributeValue::cap() const
1,148✔
4543
{
4544
  return _cap;
1,148✔
4545
}
4546
void AttributeValue::cap( unsigned short cap )
5,160✔
4547
{
4548
  _cap = cap;
5,160✔
4549
}
5,160✔
4550

4551
VitalValue::VitalValue() : _current( 0 ), _maximum( 0 ), _regenrate( 0 ) {}
297✔
4552
int VitalValue::current() const
11✔
4553
{
4554
  return _current;
11✔
4555
}
4556
int VitalValue::current_ones() const
1,313✔
4557
{
4558
  return _current / 100;
1,313✔
4559
}
4560
int VitalValue::current_thousands() const
67✔
4561
{
4562
  // use division to prevent overflow
4563
  return ( _current / 100 ) * 1000 / ( _maximum / 100 );
67✔
4564
}
4565
int VitalValue::maximum() const
234✔
4566
{
4567
  return _maximum;
234✔
4568
}
4569
int VitalValue::maximum_ones() const
792✔
4570
{
4571
  return _maximum / 100;
792✔
4572
}
4573
bool VitalValue::is_at_maximum() const
38✔
4574
{
4575
  return ( _current >= _maximum );
38✔
4576
}
4577
int VitalValue::regenrate() const
114✔
4578
{
4579
  return _regenrate;
114✔
4580
}
4581
void VitalValue::current( int cur )
317✔
4582
{
4583
  _current = cur;
317✔
4584
  if ( _current > _maximum )
317✔
4585
    _current = _maximum;
×
4586
}
317✔
4587
void VitalValue::current_ones( int ones )
80✔
4588
{
4589
  current( ones * 100 );
80✔
4590
}
80✔
4591
void VitalValue::maximum( int val )
267✔
4592
{
4593
  _maximum = val;
267✔
4594
  if ( _current > _maximum )
267✔
4595
    current( _maximum );
×
4596
}
267✔
4597
void VitalValue::regenrate( int rate )
267✔
4598
{
4599
  _regenrate = rate;
267✔
4600
}
267✔
4601
bool VitalValue::consume( unsigned int hamt )
15✔
4602
{
4603
  if ( _current > hamt )
15✔
4604
  {
4605
    _current -= hamt;
15✔
4606
    return true;
15✔
4607
  }
4608
  _current = 0;
×
4609
  return false;
×
4610
}
4611
void VitalValue::produce( unsigned int hamt )
114✔
4612
{
4613
  unsigned newcur = _current + hamt;
114✔
4614
  if ( newcur > _maximum || newcur < _current )
114✔
4615
  {
4616
    _current = _maximum;
113✔
4617
  }
4618
  else
4619
  {
4620
    _current = newcur;
1✔
4621
  }
4622
}
114✔
4623

4624
}  // namespace Mobile
4625
}  // 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