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

polserver / polserver / 21108840797

18 Jan 2026 08:35AM UTC coverage: 60.508% (+0.02%) from 60.492%
21108840797

push

github

web-flow
ClangTidy readability-else-after-return (#857)

* trigger tidy

* Automated clang-tidy change: readability-else-after-return

* compile test

* rerun

* Automated clang-tidy change: readability-else-after-return

* trigger..

* Automated clang-tidy change: readability-else-after-return

* manually removed a few

* Automated clang-tidy change: readability-else-after-return

* removed duplicate code

* fix remaining warnings

* fixed scope

---------

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

837 of 1874 new or added lines in 151 files covered. (44.66%)

46 existing lines in 25 files now uncovered.

44448 of 73458 relevant lines covered (60.51%)

525066.38 hits per line

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

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

81

82
#include "pol_global_config.h"
83

84
#include "charactr.h"
85

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

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

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

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

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

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

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

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

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

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

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

262

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

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

343
  set_caps_to_default();
97✔
344

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

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

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

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

374
  removal_cleanup();
97✔
375

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

567

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

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

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

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

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

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

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

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

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

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

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

624

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

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

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

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

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

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

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

686

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

993

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

998
        av.lock( lock );
10✔
999

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

1005
  calc_vital_stuff();
11✔
1006

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

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

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

1037

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

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

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

1055
  cached_settings.reset();
188✔
1056

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

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

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

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

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

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

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

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

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

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

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

1163
  return false;
×
1164
}
1165

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

1173
  return false;
32✔
1174
}
1175

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1297
  return ( wornitems->GetItemOnLayer( item->tile_layer ) == item );
84✔
1298
}
1299

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

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

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

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

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

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

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

1388
  return true;
62✔
1389
}
1390

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

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

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

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

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

1430
  wornitems->RemoveItemFromLayer( item );
24✔
1431

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

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

1454
  return layer_is_equipped( Core::LAYER_MOUNT );
52✔
1455
}
1456

1457

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

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

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

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

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

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

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

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

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

1570
  int start_ones = vv.current_ones();
261✔
1571
  int start_max = vv.maximum_ones();
261✔
1572

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

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

1581
  vv.maximum( mv );
261✔
1582

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

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

1590
  vv.regenrate( rr );
261✔
1591

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

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

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

1604
    if ( im < ATTRIBUTE_MIN_INTRINSIC_MOD )
×
1605
      im = ATTRIBUTE_MIN_INTRINSIC_MOD;
×
1606
    if ( im > ATTRIBUTE_MAX_INTRINSIC_MOD )
×
1607
      im = ATTRIBUTE_MAX_INTRINSIC_MOD;
×
1608

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

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

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

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

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

1637
  return true;
×
1638
}
1639

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

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

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

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

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

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

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

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

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

1733
  return flag1;
203✔
1734
}
1735

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

1746
  if ( ( source ) && ( userepsys ) )
×
1747
    source->repsys_on_attack( this );
×
1748

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

1752
  set_dirty();
×
1753
  if ( hidden() )
×
1754
    unhide();
×
1755

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

1766
  if ( paralyzed() )
×
1767
    mob_flags_.remove( MOB_FLAGS::PARALYZED );
×
1768

1769
  disable_regeneration_for( 2 );  // FIXME depend on amount?
×
1770

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

1777
  if ( ( source ) && ( userepsys ) )
×
1778
    source->repsys_on_damage( this );
×
1779

1780
  send_update_hits_to_inrange( this );
×
1781

1782
  if ( vv.current() == 0 )
×
1783
    die();
×
1784
}
1785

1786
// keep this in sync with NPC::armor_absorb_damage
1787

1788

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

1795
  int absorbed = blocked / 2;
×
1796

1797
  blocked -= absorbed;
×
1798
  absorbed += Clib::random_int( blocked );
×
1799
  damage -= absorbed;
×
1800

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

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

1812

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

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

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

1835
  return damage;
×
1836
}
1837

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

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

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

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

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

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

1871

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

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

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

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

1909

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

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

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

1931
  check_undamaged();
×
1932

1933
  send_update_hits_to_inrange( this );
×
1934
}
1935

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

1955

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

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

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

1992
  color = truecolor;
×
1993

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

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

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

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

2036
  // equip( create_backpack() );
2037

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

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

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

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

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

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

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

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

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

2101
  send_create_mobile_to_nearby_cansee( this );
1✔
2102

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

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

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

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

2134

2135
  u16 save_graphic = graphic;
72✔
2136

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

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

2162
  UPDATE_CHECKPOINT();
72✔
2163
  /* FIXME: corpse container difficulties.
2164

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

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

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

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

2188
  UPDATE_CHECKPOINT();
72✔
2189

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

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

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

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

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

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

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

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

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

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

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

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

2364
    UPDATE_CHECKPOINT();
2✔
2365

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

2412

2413
  UPDATE_CHECKPOINT();
72✔
2414
  send_death_message( this, corpse );
72✔
2415

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

2429
  UPDATE_CHECKPOINT();
72✔
2430

2431
  clear_opponent_of();
72✔
2432

2433
  set_opponent( nullptr );
72✔
2434

2435
  UPDATE_CHECKPOINT();
72✔
2436

2437
  on_death( corpse );
72✔
2438

2439
  CLEAR_CHECKPOINT();
72✔
2440
}
2441

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

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

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

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

2498
  new_ar += ar_mod();
162✔
2499

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

NEW
2687
  return false;
×
2688
}
2689

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2988
  return nullptr;
170✔
2989
}
2990

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

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

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

3011
void Character::inform_disengaged( Character* /*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_ == nullptr )
×
3016
    send_highlight();
×
3017
}
×
3018

3019
void Character::inform_engaged( Character* /*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_ == nullptr )
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( Character* new_opponent, bool inform_old_opponent )
86✔
3049
{
3050
  INFO_PRINTLN_TRACE( 12 )
86✔
3051
  ( "set_opponent({:#x},{:#x})", this->serial, new_opponent != nullptr ? new_opponent->serial : 0 );
×
3052
  if ( new_opponent != nullptr )
86✔
3053
  {
3054
    if ( new_opponent->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_ != nullptr )
86✔
3062
  {
3063
    opponent_->opponent_of.erase( 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 && opponent_ != nullptr )
4✔
3068
        opponent_->inform_disengaged( this );
4✔
3069
    }
3070
  }
3071

3072
  opponent_ = new_opponent;
86✔
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 )
86✔
3078
  {
3079
    reset_swing_timer();
86✔
3080

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

3087
      opponent_->opponent_of.insert( this );
4✔
3088

3089
      opponent_->inform_engaged( this );
4✔
3090

3091
      opponent_->schedule_attack();
4✔
3092
    }
3093

3094
    send_highlight();
86✔
3095
  }
3096
}
3097

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3281

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

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

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

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

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

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

3347
  repsys_on_attack( opponent );
8✔
3348
  repsys_on_damage( opponent );
8✔
3349

3350
  do_attack_effects( opponent );
8✔
3351

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

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

3371
    double damage = random_weapon_damage();
8✔
3372
    damage_weapon();
8✔
3373

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

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

3381
    damage *= damage_multiplier;
8✔
3382

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

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

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

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

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

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

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

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

3495

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

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

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

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

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

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

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

3562
    client->gd->justice_region = new_justice_region;
×
3563

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

3580

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

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

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

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

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

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

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

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

3661
    check_light_region_change();
105✔
3662

3663
    check_justice_region_change();
105✔
3664

3665
    check_music_region_change();
105✔
3666
  }
3667
}
168✔
3668

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

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

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

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

3700
    realm()->notify_unhid( *this );
3✔
3701

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

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

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

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

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

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

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

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

3759
  return true;
26✔
3760
}
3761

3762

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

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

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

3787
  return true;
26✔
3788
}
3789

3790

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

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

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

NEW
3824
        position_changed();
×
NEW
3825
        set_dirty();
×
NEW
3826
        return true;
×
3827
      }
3828
    }
3829
  }
3830
  return false;
×
3831
}
3832

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

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

3854
  lastpos = pos();
21✔
3855

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

3860
  u8 oldFacing = facing;
21✔
3861

3862
  Core::UFACING i_facing = static_cast<Core::UFACING>( i_dir & PKTIN_02_FACING_MASK );
21✔
3863
  if ( !face( i_facing ) )
21✔
3864
    return false;
×
3865

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

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

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

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

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

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

3901
    if ( !CheckPushthrough() )
15✔
3902
      return false;
×
3903

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

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

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

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

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

3952
    gradual_boost = current_boost;
15✔
3953
    MoveCharacterWorldPosition( oldpos, this );
15✔
3954

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

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

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

3980
  set_dirty();
20✔
3981
  dir = i_dir;
20✔
3982

3983
  return true;
20✔
3984

3985

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

3990
  // Why? Maybe to give the best response time to the client.
3991

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

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

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

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

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

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

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

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

4074
  check_attack_after_move( true );
166✔
4075

4076
  move_reason = OTHER;
166✔
4077
}
166✔
4078

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

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

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

4123
  return in_visual_range( owner );
76✔
4124
}
4125

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

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

NEW
4139
  const_cast<Character*>( this )->squelched_until( 0 );
×
NEW
4140
  return false;
×
4141
}
4142

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

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

NEW
4156
  const_cast<Character*>( this )->deafened_until( 0 );
×
NEW
4157
  return false;
×
4158
}
4159

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

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

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

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

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

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

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

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

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

4227
// SkillValue removed for no use - MuadDib
4228

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

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

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

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

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

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

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

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

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

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

4291
  if ( b == buffs_.end() )
4✔
4292
    return false;
3✔
4293

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4617
}  // namespace Mobile
4618
}  // 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