• 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

51.08
/pol-core/pol/multi/house.cpp
1
/** @file
2
 *
3
 * @par History
4
 * - 2005/06/06 Shinigami: added readobjects - to get a list of statics
5
 * - 2005/11/26 Shinigami: changed "strcmp" into "stricmp" to suppress Script Errors
6
 * - 2009/09/03 MuadDib:   Relocation of multi related cpp/h
7
 * - 2009/09/14 MuadDib:   Squatters code added to register.unregister mobs.
8
 * - 2009/09/15 MuadDib:   Better cleanup handling on house destroy. Alos clears registered_house
9
 * off character.
10
 *                         Houses now only allow mobiles to be registered. May add items later for
11
 * other storage.
12
 * - 2012/02/02 Tomi:      Added boat member MBR_MULTIID
13
 */
14

15
#include "house.h"
16

17
#include <boost/numeric/conversion/cast.hpp>
18
#include <iterator>
19
#include <stdlib.h>
20

21
#include "../../bscript/berror.h"
22
#include "../../bscript/executor.h"
23
#include "../../bscript/objmembers.h"
24
#include "../../bscript/objmethods.h"
25
#include "../../clib/cfgelem.h"
26
#include "../../clib/clib_endian.h"
27
#include "../../clib/logfacility.h"
28
#include "../../clib/passert.h"
29
#include "../../clib/refptr.h"
30
#include "../../clib/stlutil.h"
31
#include "../../clib/streamsaver.h"
32
#include "../../plib/mapcell.h"
33
#include "../../plib/mapshape.h"
34
#include "../../plib/systemstate.h"
35
#include "../../plib/uconst.h"
36
#include "../core.h"
37
#include "../fnsearch.h"
38
#include "../globals/object_storage.h"
39
#include "../globals/uvars.h"
40
#include "../item/itemdesc.h"
41
#include "../mobile/charactr.h"
42
#include "../module/uomod.h"
43
#include "../network/cgdata.h"
44
#include "../network/client.h"
45
#include "../realms/realm.h"
46
#include "../syshookscript.h"
47
#include "../ufunc.h"
48
#include "../uobject.h"
49
#include "../uoexec.h"
50
#include "../uoscrobj.h"
51
#include "../uworld.h"
52
#include "base/range.h"
53
#include "customhouses.h"
54
#include "multi.h"
55
#include "multidef.h"
56

57

58
namespace Pol::Multi
59
{
60
Core::Range3d UHouse::current_box() const
48✔
61
{
62
  if ( IsCustom() )
48✔
63
  {
64
    const MultiDef& md = multidef();
×
65
    return Core::Range3d( pos() + md.minrxyz, pos() + md.maxrxyz + Core::Vec2d( 0, 1 ) );
×
66
  }
67

68
  return base::current_box();
48✔
69
}
70

71
ItemList UHouse::get_working_design_items( UHouse* house )
×
72
{
73
  ItemList itemlist;
×
74
  MobileList moblist;
×
75
  bool was_editing = house->editing;
×
76
  house->editing = false;
×
77
  UHouse::list_contents( house, itemlist, moblist );
×
78
  house->editing = was_editing;
×
79
  return itemlist;
×
80
}
×
81

82
void UHouse::list_contents( const UHouse* house, ItemList& items_in, MobileList& chrs_in )
26✔
83
{
84
  auto box = house->current_box();
26✔
85
  Core::WorldIterator<Core::MobileFilter>::InBox(
26✔
86
      box.range(), house->realm(),
26✔
87
      [&]( Mobile::Character* chr )
26✔
88
      {
89
        UMulti* multi = house->realm()->find_supporting_multi( chr->pos3d() );
6✔
90
        if ( const_cast<const UMulti*>( multi ) == house )
6✔
91
          chrs_in.push_back( chr );
4✔
92
      } );
6✔
93
  Core::WorldIterator<Core::ItemFilter>::InBox(
26✔
94
      box.range(), house->realm(),
26✔
95
      [&]( Items::Item* item )
26✔
96
      {
97
        UMulti* multi = house->realm()->find_supporting_multi( item->pos3d() );
7✔
98
        if ( const_cast<const UMulti*>( multi ) == house )
7✔
99
        {
100
          if ( Plib::tile_flags( item->graphic ) & Plib::FLAG::WALKBLOCK )
7✔
101
            items_in.push_front( item );
4✔
102
          else
103
            items_in.push_back( item );
3✔
104
        }
105
      } );
7✔
106
}
26✔
107

108
UHouse::UHouse( const Items::ItemDesc& itemdesc )
19✔
109
    : UMulti( itemdesc ),
110
      editing( false ),
19✔
111
      waiting_for_accept( false ),
19✔
112
      editing_floor_num( 1 ),
19✔
113
      revision( 0 ),
19✔
114
      custom( false )
19✔
115
{
116
}
19✔
117

118
size_t UHouse::estimatedSize() const
×
119
{
120
  size_t size = base::estimatedSize() + CurrentDesign.estimatedSize() +
×
121
                WorkingDesign.estimatedSize() + BackupDesign.estimatedSize() +
×
122
                Clib::memsize( CurrentCompressed ) + Clib::memsize( WorkingCompressed ) +
×
123
                sizeof( bool )   /*editing*/
124
                + sizeof( bool ) /*waiting_for_accept*/
125
                + sizeof( int )  /*editing_floor_num*/
126
                + sizeof( u32 )  /*revision*/
127
                + sizeof( bool ) /*custom*/
128
                // no estimateSize here element is in objhash
129
                + Clib::memsize( squatters_ ) + Clib::memsize( components_ );
×
130
  return size;
×
131
}
132

133
/**
134
 * Creates dynamic house components from MultiDef (multis.cfg at the time of writing)
135
 */
136
void UHouse::create_components()
15✔
137
{
138
  const MultiDef& md = multidef();
15✔
139
  for ( unsigned i = 0; i < md.elems.size(); ++i )
1,171✔
140
  {
141
    const MULTI_ELEM& elem = md.elems[i];
1,156✔
142
    if ( !elem.is_static )
1,156✔
143
    {
144
      Items::Item* item = Items::Item::create( elem.objtype );
12✔
145
      bool res = add_component( item, elem.relpos.x(), elem.relpos.y(), elem.relpos.z() );
12✔
146
      passert_always_r( res,
12✔
147
                        "Couldn't add newly created item as house component. Please report this "
148
                        "bug on the forums." );
149
    }
150
  }
151
}
15✔
152

153
/**
154
 * Moves the item into the house, adding it as house component
155
 * (change item coordinates, set it unmovable, etc...)
156
 *
157
 * @param item Pointer to the item to be added
158
 * @param xoff The X offset inside the house
159
 * @param yoff The Y offset inside the house
160
 * @param zoff The Z offset inside the house
161
 * @return true on success, false when the item can't be added
162
 */
163
bool UHouse::add_component( Items::Item* item, s32 xoff, s32 yoff, s16 zoff )
12✔
164
{
165
  if ( !can_add_component( item ) )
12✔
166
    return false;
×
167

168
  u16 newx, newy;
169
  s8 newz;
170
  try
171
  {
172
    // These casts should be safe, but better check them - 2015-01-25 Bodom
173
    newx = boost::numeric_cast<u16>( x() + xoff );
12✔
174
    newy = boost::numeric_cast<u16>( y() + yoff );
12✔
175
    newz = boost::numeric_cast<s8>( z() + zoff );
12✔
176
  }
177
  catch ( boost::bad_numeric_cast& )
×
178
  {
179
    // Printing an error because this is supposed to not happen,
180
    // so it's probably a bug.
181
    POLLOG_ERRORLN( "Out-of-range coordinates while trying to add Item {:#x} to House {:#x}",
×
182
                    item->serial, serial );
×
183
    return false;
×
184
  }
×
185
  item->setposition( Core::Pos4d( newx, newy, newz, realm() ) );
12✔
186
  item->disable_decay();
12✔
187
  item->movable( false );
12✔
188
  update_item_to_inrange( item );
12✔
189
  add_item_to_world( item );
12✔
190
  add_component_no_check( Component( item ) );
12✔
191
  return true;
12✔
192
}
193

194
/**
195
 * (Re-)Adds a component to the House, without modifying it:
196
 * the item must already be inside the house, it just gets added to the components list
197
 *
198
 * @param item Reference to the item being added
199
 * @return true on success, false when the item can't be added
200
 */
201
bool UHouse::add_component( Component item )
4✔
202
{
203
  if ( !can_add_component( item.get() ) )
4✔
204
    return false;
×
205

206
  add_component_no_check( item );
4✔
207
  return true;
4✔
208
}
209

210
/**
211
 * Returns list of house components by allocating a new Array to be used by scripts
212
 *
213
 * @return ObjArray* pointer to the newly allocated array
214
 */
215
Bscript::ObjArray* UHouse::component_list() const
7✔
216
{
217
  std::unique_ptr<Bscript::ObjArray> arr( new Bscript::ObjArray );
7✔
218
  for ( Components::const_iterator itr = components_.begin(), end = components_.end(); itr != end;
21✔
219
        ++itr )
14✔
220
  {
221
    Items::Item* item = ( *itr ).get();
14✔
222
    if ( item != nullptr && !item->orphan() )
14✔
223
    {
224
      arr->addElement( new Module::EItemRefObjImp( item ) );
14✔
225
    }
226
  }
227
  return arr.release();
14✔
228
}
7✔
229

230
Bscript::ObjArray* UHouse::items_list() const
1✔
231
{
232
  ItemList itemlist;
1✔
233
  MobileList moblist;
1✔
234
  list_contents( this, itemlist, moblist );
1✔
235
  std::unique_ptr<Bscript::ObjArray> arr( new Bscript::ObjArray );
1✔
236
  for ( auto& item : itemlist )
4✔
237
  {
238
    if ( std::find( components_.cbegin(), components_.cend(), Component( item ) ) ==
3✔
239
         components_.cend() )
6✔
240
    {
241
      arr->addElement( new Module::EItemRefObjImp( item ) );
1✔
242
    }
243
  }
244
  return arr.release();
2✔
245
}
1✔
246

247
Bscript::ObjArray* UHouse::mobiles_list() const
×
248
{
249
  ItemList itemlist;
×
250
  MobileList moblist;
×
251
  list_contents( this, itemlist, moblist );
×
252
  std::unique_ptr<Bscript::ObjArray> arr( new Bscript::ObjArray );
×
253
  for ( auto& chr : moblist )
×
254
  {
255
    arr->addElement( new Module::ECharacterRefObjImp( chr ) );
×
256
  }
257
  return arr.release();
×
258
}
×
259

260
UHouse* UHouse::as_house()
5,154✔
261
{
262
  return this;
5,154✔
263
}
264

265
Bscript::BObjectImp* UHouse::get_script_member_id( const int id ) const  /// id test
58✔
266
{
267
  using namespace Bscript;
268
  BObjectImp* imp = base::get_script_member_id( id );
58✔
269
  if ( imp )
58✔
270
    return imp;
42✔
271

272
  switch ( id )
16✔
273
  {
274
  case MBR_COMPONENTS:
7✔
275
    return component_list();
7✔
276
    break;
277
  case MBR_ITEMS:
1✔
278
    return items_list();
1✔
279
    break;
280
  case MBR_MOBILES:
×
281
    return mobiles_list();
×
282
    break;
283
  case MBR_CUSTOM:
1✔
284
    return new BLong( IsCustom() );
1✔
285
    break;
286
  case MBR_EDITING:
×
287
    return new BLong( IsEditing() );
×
288
    break;
289
  case MBR_MULTIID:
6✔
290
    return new BLong( multiid_ );
6✔
291
    break;
292
  case MBR_HOUSEPARTS:
1✔
293
    if ( !IsCustom() )
1✔
294
      return new BError( "House is not custom" );
×
295
    else if ( IsEditing() )
1✔
296
      return WorkingDesign.list_parts();
×
297
    else
298
      return CurrentDesign.list_parts();
1✔
299
    break;
300
  default:
×
301
    return nullptr;
×
302
  }
303
}
304

305
Bscript::BObjectImp* UHouse::get_script_member( const char* membername ) const
3✔
306
{
307
  Bscript::ObjMember* objmember = Bscript::getKnownObjMember( membername );
3✔
308
  if ( objmember != nullptr )
3✔
309
    return this->get_script_member_id( objmember->id );
3✔
NEW
310
  return nullptr;
×
311
}
312

313
Bscript::BObjectImp* UHouse::script_method_id( const int id, Core::UOExecutor& ex )
14✔
314
{
315
  using namespace Bscript;
316
  BObjectImp* imp = base::script_method_id( id, ex );
14✔
317
  if ( imp != nullptr )
14✔
318
    return imp;
3✔
319

320
  switch ( id )
11✔
321
  {
322
  case MTH_SETCUSTOM:
1✔
323
  {
324
    int _custom;
325
    if ( ex.getParam( 0, _custom ) )
1✔
326
    {
327
      SetCustom( _custom ? true : false );
1✔
328
      return new BLong( 1 );
1✔
329
    }
330
    break;
×
331
  }
332
  case MTH_ADD_COMPONENT:
×
333
  {
334
    BApplicObjBase* aob;
335
    if ( ex.hasParams( 0 ) )
×
336
    {
337
      aob = ex.getApplicObjParam( 0, &Module::eitemrefobjimp_type );
×
338
      if ( aob )
×
339
      {
340
        Module::EItemRefObjImp* ir = static_cast<Module::EItemRefObjImp*>( aob );
×
341
        Core::ItemRef iref = ir->value();
×
342

343
        if ( add_component( iref ) )
×
344
          return new BLong( 1 );
×
345

346
        if ( iref->house() )
×
347
          return new BError( "Item is already an house component" );
×
NEW
348
        return new BError( "Couldn't add component" );
×
349
      }
×
350
    }
351
    break;
×
352
  }
353
  case MTH_ERASE_COMPONENT:
×
354
  {
355
    BApplicObjBase* aob;
356
    if ( ex.hasParams( 0 ) )
×
357
    {
358
      aob = ex.getApplicObjParam( 0, &Module::eitemrefobjimp_type );
×
359
      if ( aob )
×
360
      {
361
        Module::EItemRefObjImp* ir = static_cast<Module::EItemRefObjImp*>( aob );
×
362
        Core::ItemRef iref = ir->value();
×
363
        Components::iterator pos;
×
364
        pos = find( components_.begin(), components_.end(), iref );
×
365
        if ( pos != components_.end() )
×
366
        {
367
          iref->house( nullptr );
×
368
          components_.erase( pos );
×
369
        }
370
        else
371
          return new BError( "Component not found" );
×
372
        return new BLong( 1 );
×
373
      }
×
374
    }
375
    break;
×
376
  }
377
  case MTH_ADD_HOUSE_PART:
×
378
  {
379
    if ( !IsCustom() )
×
380
      return new BError( "House is not custom" );
×
NEW
381
    if ( IsEditing() )
×
382
      return new BError( "House is currently been edited" );
×
NEW
383
    if ( !ex.hasParams( 4 ) )
×
384
      return new BError( "Not enough parameters" );
×
385
    u16 item_graphic;
386
    int xoff, yoff, item_z;
387
    if ( ex.getParam( 0, item_graphic ) && ex.getParam( 1, xoff ) && ex.getParam( 2, yoff ) &&
×
388
         ex.getParam( 3, item_z ) )
×
389
    {
390
      CUSTOM_HOUSE_ELEMENT elem;
391
      elem.graphic = item_graphic;
×
392
      elem.xoffset = xoff;
×
393
      elem.yoffset = yoff;
×
394
      elem.z = static_cast<u8>( item_z );
×
395
      CurrentDesign.Add( elem );
×
396
      // invalidate
397
      // invalidate
398
      WorkingDesign = CurrentDesign;
×
399
      std::vector<u8> newvec;
×
400
      WorkingCompressed.swap( newvec );
×
401
      std::vector<u8> newvec2;
×
402
      CurrentCompressed.swap( newvec2 );
×
403
      revision++;
×
404
      CustomHousesSendFullToInRange( this, HOUSE_DESIGN_CURRENT );
×
405
      return new BLong( 1 );
×
406
    }
×
407
    break;
×
408
  }
409
  case MTH_ERASE_HOUSE_PART:
×
410
  {
411
    if ( !IsCustom() )
×
412
      return new BError( "House is not custom" );
×
NEW
413
    if ( IsEditing() )
×
414
      return new BError( "House is currently been edited" );
×
NEW
415
    if ( !ex.hasParams( 4 ) )
×
416
      return new BError( "Not enough parameters" );
×
417
    int item_graphic, xoff, yoff, item_z;
418
    if ( ex.getParam( 0, item_graphic ) && ex.getParam( 1, xoff ) && ex.getParam( 2, yoff ) &&
×
419
         ex.getParam( 3, item_z ) )
×
420
    {
421
      bool ret =
422
          CurrentDesign.EraseGraphicAt( static_cast<u16>( item_graphic ), static_cast<u32>( xoff ),
×
423
                                        static_cast<u32>( yoff ), static_cast<u8>( item_z ) );
424
      if ( ret )
×
425
      {
426
        // invalidate
427
        WorkingDesign = CurrentDesign;
×
428
        std::vector<u8> newvec;
×
429
        WorkingCompressed.swap( newvec );
×
430
        std::vector<u8> newvec2;
×
431
        CurrentCompressed.swap( newvec2 );
×
432
        CustomHousesSendFullToInRange( this, HOUSE_DESIGN_CURRENT );
×
433
      }
×
434
      return new BLong( ret ? 1 : 0 );
×
435
    }
436
    break;
×
437
  }
438
  case MTH_ACCEPT_COMMIT:
×
439
  {
440
    if ( !IsCustom() )
×
441
      return new BError( "House is not custom" );
×
442
    // else if (!IsEditing())
443
    //  return new BError( "House is currently not been edited" );
NEW
444
    if ( !IsWaitingForAccept() )
×
445
      return new BError( "House is currently not waiting for a commit" );
×
NEW
446
    if ( !ex.hasParams( 2 ) )
×
447
      return new BError( "Not enough parameters" );
×
448
    int accept;
449
    Mobile::Character* chr;
450
    if ( ex.getParam( 1, accept ) && ex.getCharacterParam( 0, chr ) )
×
451
    {
452
      AcceptHouseCommit( chr, accept ? true : false );
×
453
      return new BLong( 1 );
×
454
    }
455
    break;
×
456
  }
457
  case MTH_CANCEL_EDITING:
×
458
  {
459
    if ( !IsCustom() )
×
460
      return new BError( "House is not custom" );
×
NEW
461
    if ( !IsEditing() )
×
462
      return new BError( "House is currently not been edited" );
×
NEW
463
    if ( !ex.hasParams( 2 ) )
×
464
      return new BError( "Not enough parameters" );
×
465
    Mobile::Character* chr;
466
    int drop_changes;
467
    if ( ex.getCharacterParam( 0, chr ) && ex.getParam( 1, drop_changes ) )
×
468
    {
469
      if ( chr->client->gd->custom_house_serial == serial )
×
470
        CustomHousesQuit( chr, drop_changes ? true : false );
×
471
      else
472
        return new BError( "Character is not editing this house" );
×
473
      return new BLong( 1 );
×
474
    }
475
    break;
×
476
  }
477
  case MTH_SET_MULTIID:
10✔
478
  {
479
    if ( IsEditing() )
10✔
480
      return new BError( "House is currently been edited" );
10✔
481
    if ( !ex.hasParams( 2 ) )
10✔
482
      return new BError( "Not enough parameters" );
×
483

484
    u16 multiid;
485
    BObjectImp* multiid_imp = ex.getParamImp( 0 );
10✔
486

487
    // If passing an integer, it's a multiid and NOT an objtype id
488
    if ( multiid_imp->isa( BObjectImp::OTLong ) )
10✔
489
    {
490
      multiid = Clib::clamp_convert<u16>( static_cast<BLong*>( multiid_imp )->value() );
8✔
491
    }
492
    // If a string, get the multiid of the itemdesc of the name passed.
493
    else if ( multiid_imp->isa( BObjectImp::OTString ) )
2✔
494
    {
495
      const Items::ItemDesc* itemdesc_out;
496
      if ( !ex.getObjtypeParam( 0, itemdesc_out ) || itemdesc_out->multiid == 0 )
2✔
497
      {
498
        return new BError( "Invalid parameter type" );
1✔
499
      }
500
      multiid = itemdesc_out->multiid;
1✔
501
    }
502
    else
503
    {
504
      break;
×
505
    }
506

507
    int flags;
508
    if ( !ex.getParam( 1, flags ) )
9✔
509
      break;
×
510

511
    if ( !MultiDefByMultiIDExists( multiid ) )
9✔
512
    {
513
      return new Bscript::BError(
514
          fmt::format( "Multi definition not found for House, multiid=0x{:x}", multiid ) );
2✔
515
    }
516

517
    const MultiDef* md = MultiDefByMultiID( multiid );
8✔
518
    std::string why_not;
8✔
519

520
    if ( !is_valid_position( md, pos(), flags, why_not, this ) )
8✔
521
    {
522
      return new BError( why_not );
2✔
523
    }
524

525
    change_multiid( multiid, flags & CRMULTI_RECREATE_COMPONENTS );
6✔
526

527
    return new BLong( 1 );
6✔
528
  }
8✔
529

530
  default:
×
531
    return nullptr;
×
532
  }
533
  return new BError( "Invalid parameter type" );
×
534
}
535

536
Bscript::BObjectImp* UHouse::script_method( const char* methodname, Core::UOExecutor& ex )
×
537
{
538
  Bscript::ObjMethod* objmethod = Bscript::getKnownObjMethod( methodname );
×
539
  if ( objmethod != nullptr )
×
540
    return this->script_method_id( objmethod->id, ex );
×
NEW
541
  return nullptr;
×
542
}
543

544
void UHouse::readProperties( Clib::ConfigElem& elem )
2✔
545
{
546
  base::readProperties( elem );
2✔
547
  u32 tmp_serial;
548
  while ( elem.remove_prop( "Component", &tmp_serial ) )
8✔
549
  {
550
    Items::Item* item = Core::find_toplevel_item( tmp_serial );
4✔
551
    if ( item != nullptr )
4✔
552
    {
553
      if ( !add_component( Component( item ) ) )
4✔
554
      {
555
        std::string os =
556
            fmt::format( "Couldn't add component {:#x} to house {:#x}.\n", item->serial, serial );
×
557
        UHouse* contHouse = item->house();
×
558
        if ( contHouse == nullptr )
×
559
        {
560
          os += "This is probably a core bug. Please report it on the forums.";
×
561
        }
562
        else
563
        {
564
          fmt::format_to( std::back_inserter( os ),
×
565
                          "This item is already part of house {:#x}.\n"
566
                          "Allowing an item to be a component in two different houses was a bug,\n"
567
                          "please also fix your save data.",
568
                          contHouse->serial );
×
569
        }
570
        throw std::runtime_error( os );
×
571
      }
×
572
    }
573
  }
574
  custom = elem.remove_bool( "Custom", false );
2✔
575
  if ( custom )
2✔
576
  {
577
    const MultiDef& def = multidef();
1✔
578
    auto size =
579
        Core::Pos2d( 1, 2 ) +
580
        ( def.maxrxyz.xy() - def.minrxyz.xy() );  //+1 to include offset 0 in -3..3, additional y+1
1✔
581
                                                  // for front steps outside of multidef footprint
582
    Core::Vec2d xybase( (short)std::abs( def.minrxyz.x() ), (short)std::abs( def.minrxyz.y() ) );
1✔
583

584

585
    CurrentDesign.InitDesign( size.y(), size.x(), xybase.x(), xybase.y() );
1✔
586
    WorkingDesign.InitDesign( size.y(), size.x(), xybase.x(), xybase.y() );
1✔
587
    BackupDesign.InitDesign( size.y(), size.x(), xybase.x(), xybase.y() );
1✔
588
    CurrentDesign.readProperties( elem, "Current" );
1✔
589
    WorkingDesign.readProperties( elem, "Working" );
1✔
590
    BackupDesign.readProperties( elem, "Backup" );
1✔
591

592
    elem.remove_prop( "DesignRevision", &revision );
1✔
593
  }
594
}
2✔
595

596
void UHouse::printProperties( Clib::StreamWriter& sw ) const
4✔
597
{
598
  base::printProperties( sw );
4✔
599

600
  for ( Components::const_iterator itr = components_.begin(), end = components_.end(); itr != end;
12✔
601
        ++itr )
8✔
602
  {
603
    Items::Item* item = ( *itr ).get();
8✔
604
    if ( item != nullptr && !item->orphan() )
8✔
605
    {
606
      sw.add( "Component", Clib::hexintv( item->serial ) );
8✔
607
    }
608
  }
609
  sw.add( "Custom", custom );
4✔
610
  if ( custom )
4✔
611
  {
612
    CurrentDesign.printProperties( sw, "Current" );
2✔
613
    WorkingDesign.printProperties( sw, "Working" );
2✔
614
    BackupDesign.printProperties( sw, "Backup" );
2✔
615
    sw.add( "DesignRevision", revision );
2✔
616
  }
617
}
4✔
618

619
void UHouse::destroy_components()
13✔
620
{
621
  while ( !components_.empty() )
21✔
622
  {
623
    Items::Item* item = components_.back().get();
8✔
624
    if ( Plib::systemstate.config.loglevel >= 5 )
8✔
625
      POLLOGLN( "Destroying component {:#x}, serial={:#x}", item->objtype_, item->serial );
8✔
626
    if ( !item->orphan() )
8✔
627
      Core::destroy_item( item );
8✔
628
    if ( Plib::systemstate.config.loglevel >= 5 )
8✔
629
      POLLOGLN( "Component destroyed" );
8✔
630
    components_.pop_back();
8✔
631
  }
632
}
13✔
633

634
void UHouse::change_multiid( u16 multiid, bool recreate_components )
6✔
635
{
636
  // Used for sorting and difference calculation
637
  auto comparator = []( Core::UObject* a, Core::UObject* b ) { return a->serial < b->serial; };
2✔
638

639
  ItemList itemlist;
6✔
640
  MobileList old_moblist;
6✔
641
  list_contents( this, itemlist, old_moblist );
6✔
642
  old_moblist.sort( comparator );
6✔
643

644
  multiid_ = multiid;
6✔
645
  send_multi_to_inrange( this );
6✔
646

647
  MobileList new_moblist;
6✔
648
  list_contents( this, itemlist, new_moblist );
6✔
649
  new_moblist.sort( comparator );
6✔
650

651
  auto on_mob_added = [&]( Mobile::Character* chr )
1✔
652
  {
653
    register_object( chr );
1✔
654

655
    if ( chr->registered_multi == 0 )
1✔
656
    {
657
      chr->registered_multi = this->serial;
1✔
658
      this->walk_on( chr );
1✔
659
    }
660
  };
7✔
661

662
  auto on_mob_removed = [&]( Mobile::Character* chr ) { unregister_object( chr ); };
1✔
663

664
  // Traverse both vectors
665
  auto old_itr = old_moblist.begin();
6✔
666
  auto new_itr = new_moblist.begin();
6✔
667

668
  while ( old_itr != old_moblist.end() && new_itr != new_moblist.end() )
7✔
669
  {
670
    if ( comparator( *old_itr, *new_itr ) )
1✔
671
    {
672
      on_mob_removed( *old_itr );
×
673
      ++old_itr;
×
674
    }
675
    else if ( comparator( *new_itr, *old_itr ) )
1✔
676
    {
677
      on_mob_added( *new_itr );
×
678
      ++new_itr;
×
679
    }
680
    else
681
    {
682
      ++old_itr;
1✔
683
      ++new_itr;
1✔
684
    }
685
  }
686

687
  // Remaining elements in old are removed
688
  while ( old_itr != old_moblist.end() )
7✔
689
  {
690
    on_mob_removed( *old_itr );
1✔
691
    ++old_itr;
1✔
692
  }
693

694
  // Remaining elements in new are added
695
  while ( new_itr != new_moblist.end() )
7✔
696
  {
697
    on_mob_added( *new_itr );
1✔
698
    ++new_itr;
1✔
699
  }
700

701
  // Recreate components (if specified)
702
  if ( recreate_components )
6✔
703
  {
704
    for ( auto& component : components_ )
×
705
    {
706
      if ( !component->orphan() )
×
707
        Core::destroy_item( component.get() );
×
708
    }
709

710
    components_.clear();
×
711

712
    create_components();
×
713
  }
714
}
6✔
715

716
bool UHouse::readshapes( Plib::MapShapeList& vec, short shape_x, short shape_y, short zbase )
3✔
717
{
718
  if ( !custom )
3✔
719
    return false;
×
720

721
  bool result = false;
3✔
722
  HouseFloorZColumn* elems;
723
  HouseFloorZColumn::iterator itr;
3✔
724
  CustomHouseDesign* design;
725
  design =
3✔
726
      editing
3✔
727
          ? &WorkingDesign
3✔
728
          : &CurrentDesign;  // consider having a list of players that should use the working set
729

730
  if ( shape_x + design->xoff < 0 || shape_x + design->xoff >= static_cast<s32>( design->width ) ||
3✔
731
       shape_y + design->yoff < 0 || shape_y + design->yoff >= static_cast<s32>( design->height ) )
×
732
    return false;
3✔
733
  for ( int i = 0; i < CUSTOM_HOUSE_NUM_PLANES; i++ )
×
734
  {
735
    elems = design->Elements[i].GetElementsAt( shape_x, shape_y );
×
736
    for ( itr = elems->begin(); itr != elems->end(); ++itr )
×
737
    {
738
      Plib::MapShape shape;
739
      shape.z = itr->z + zbase;
×
740
      shape.height = Plib::tileheight( itr->graphic );
×
741
      shape.flags = Plib::tile_flags( itr->graphic );
×
742
      if ( !shape.height )
×
743
      {
744
        ++shape.height;
×
745
        --shape.z;
×
746
      }
747
      vec.push_back( shape );
×
748
      result = true;
×
749
    }
750
  }
751
  // house teleporters are components and replace floors
752
  for ( const auto& c : components_ )
×
753
  {
754
    Items::Item* item = c.get();
×
755
    if ( item == nullptr || item->orphan() )
×
756
      continue;
×
757
    if ( item->graphic >= TELEPORTER_START && item->graphic <= TELEPORTER_END )
×
758
    {
759
      Plib::MapShape shape;
760
      shape.z = item->z();
×
761
      shape.height = Plib::tileheight( item->graphic );
×
762
      shape.flags = Plib::tile_flags( item->graphic );
×
763
      if ( !shape.height )
×
764
      {
765
        ++shape.height;
×
766
        --shape.z;
×
767
      }
768
      vec.push_back( shape );
×
769
      result = true;
×
770
    }
771
  }
772
  return result;
×
773
}
774

775
bool UHouse::readobjects( Plib::StaticList& vec, short obj_x, short obj_y, short zbase )
×
776
{
777
  if ( !custom )
×
778
    return false;
×
779

780
  bool result = false;
×
781
  HouseFloorZColumn* elems;
782
  HouseFloorZColumn::iterator itr;
×
783
  CustomHouseDesign* design;
784
  design =
×
785
      editing
×
786
          ? &WorkingDesign
×
787
          : &CurrentDesign;  // consider having a list of players that should use the working set
788

789
  if ( obj_x + design->xoff < 0 || obj_x + design->xoff >= static_cast<s32>( design->width ) ||
×
790
       obj_y + design->yoff < 0 || obj_y + design->yoff >= static_cast<s32>( design->height ) )
×
791
    return false;
×
792
  for ( int i = 0; i < CUSTOM_HOUSE_NUM_PLANES; i++ )
×
793
  {
794
    elems = design->Elements[i].GetElementsAt( obj_x, obj_y );
×
795
    for ( itr = elems->begin(); itr != elems->end(); ++itr )
×
796
    {
797
      Plib::StaticRec rec( itr->graphic, static_cast<signed char>( itr->z + zbase ),
×
798
                           Plib::tile_flags( itr->graphic ), Plib::tileheight( itr->graphic ) );
×
799
      if ( !rec.height )
×
800
      {
801
        ++rec.height;
×
802
        --rec.z;
×
803
      }
804
      vec.push_back( rec );
×
805
      result = true;
×
806
    }
807
  }
808
  return result;
×
809
}
810

811
// consider: storing editing char serial list on the house, to validate who should see the working
812
// set
813
UHouse* UHouse::FindWorkingHouse( u32 chrserial )
×
814
{
815
  Mobile::Character* chr = Core::find_character( chrserial );
×
816
  if ( chr == nullptr )
×
817
    return nullptr;
×
818
  if ( chr->client == nullptr )
×
819
    return nullptr;
×
820

821
  u32 house_serial = chr->client->gd->custom_house_serial;
×
822

823
  UMulti* multi = Core::system_find_multi( house_serial );
×
824
  if ( multi == nullptr )
×
825
    return nullptr;
×
826

827
  UHouse* house = multi->as_house();
×
828
  if ( house == nullptr )
×
829
    return nullptr;
×
830

831
  return house;
×
832
}
833

834
bool multis_exist_in( const Core::Pos4d& minpos, const Core::Pos4d& maxpos,
22✔
835
                      Multi::UMulti* ignore_existing )
836
{
837
  // TODO Pos maximum multi footprint, gamestate.update_range?
838
  Core::Range2d mybox( minpos, maxpos );
22✔
839
  Core::Range2d gridarea( Core::zone_convert( minpos - Core::Vec2d( 100, 100 ) ),
22✔
840
                          Core::zone_convert( maxpos + Core::Vec2d( 100, 100 ) ), nullptr );
44✔
841
  for ( const auto& gpos : gridarea )
418✔
842
  {
843
    for ( const auto& multi : minpos.realm()->getzone_grid( gpos ).multis )
227✔
844
    {
845
      if ( ignore_existing == multi )
29✔
846
        continue;
7✔
847

848
      // find out if any of our walls would fall within its footprint.
849
      auto otherbox = multi->current_box().range();
22✔
850
      if ( mybox.intersect( otherbox ) )
22✔
851
        return true;
×
852
    }
853
  }
854
  return false;
22✔
855
}
856

857
bool objects_exist_in( const Core::Pos4d& p1, const Core::Pos4d& p2,
22✔
858
                       Multi::UMulti* ignore_existing )
859
{
860
  Core::Range2d gridarea( Core::zone_convert( p1 ), Core::zone_convert( p2 ), nullptr );
22✔
861
  Core::Range2d worldarea( p1, p2 );
22✔
862
  for ( const auto& gpos : gridarea )
76✔
863
  {
864
    for ( const auto& chr : p1.realm()->getzone_grid( gpos ).characters )
30✔
865
    {
866
      if ( ignore_existing != nullptr && chr->registered_multi == ignore_existing->serial )
1✔
867
        continue;
×
868

869
      if ( worldarea.contains( chr->pos2d() ) )
1✔
870
        return true;
×
871
    }
872
    for ( const auto& chr : p1.realm()->getzone_grid( gpos ).npcs )
33✔
873
    {
874
      if ( ignore_existing != nullptr && chr->registered_multi == ignore_existing->serial )
5✔
875
        continue;
2✔
876

877
      if ( worldarea.contains( chr->pos2d() ) )
3✔
878
        return true;
1✔
879
    }
880
    for ( const auto& item : p1.realm()->getzone_grid( gpos ).items )
84✔
881
    {
882
      if ( ignore_existing != nullptr &&
74✔
883
           ( item->house() == ignore_existing ||
17✔
884
             p1.realm()->find_supporting_multi( item->pos3d() ) == ignore_existing ) )
13✔
885
        continue;
4✔
886

887
      if ( worldarea.contains( item->pos2d() ) )
53✔
888
        return true;
1✔
889
    }
890
  }
891
  return false;
20✔
892
}
893

894
bool statics_cause_problems( const Core::Pos4d& p1, const Core::Pos4d& p2,
20✔
895
                             Multi::UMulti* ignore_existing )
896
{
897
  Core::Range2d area( p1, p2 );
20✔
898
  for ( const auto& p : area )
3,744✔
899
  {
900
    short newz;
901
    UMulti* multi;
902
    Items::Item* item;
903
    if ( !p1.realm()->walkheight( p, p1.z(), &newz, &multi, &item, true, Plib::MOVEMODE_LAND,
1,862✔
904
                                  nullptr, ignore_existing ) )
905
    {
906
      if ( ignore_existing != nullptr && multi != ignore_existing )
×
907
      {
908
        POLLOGLN( "Refusing to place house at {},{}: can't stand there", p, p1.z() );
×
909
        return true;
×
910
      }
911
    }
912
    if ( labs( p1.z() - newz ) > 2 )
1,862✔
913
    {
914
      POLLOGLN( "Refusing to place house at {},{}: result Z ({}) is too far afield", p, p1.z(),
×
915
                newz );
916
      return true;
×
917
    }
918
  }
919
  return false;
20✔
920
}
921

922
bool UHouse::is_valid_position( const MultiDef* md, const Core::Pos4d& pos, int flags,
23✔
923
                                std::string& why_not, UMulti* existing )
924
{
925
  using namespace Bscript;
926

927
  if ( !pos.can_move_to( md->minrxyz.xy() ) || !pos.can_move_to( md->maxrxyz.xy() ) )
23✔
928
    return why_not = "That location is out of bounds", false;
×
929

930
  auto corner_ne = pos + md->minrxyz;
23✔
931
  auto corner_sw = pos + md->maxrxyz;
23✔
932
  if ( ~flags & CRMULTI_IGNORE_MULTIS )
23✔
933
  {
934
    // TODO: it seems to be more restrictive then original server
935
    // see https://uo.stratics.com/homes/betterhomes/bhc_placing.shtml
936
    // should we change it or keep it that way?
937
    const auto additional_gap = Core::Vec2d( 1, 5 );
22✔
938
    if ( multis_exist_in( corner_ne - additional_gap, corner_sw + additional_gap, existing ) )
22✔
939
      return why_not = "Location intersects with another structure", false;
×
940
  }
941

942
  if ( ~flags & CRMULTI_IGNORE_OBJECTS )
23✔
943
  {
944
    if ( objects_exist_in( corner_ne, corner_sw, existing ) )
22✔
945
      return why_not = "Something is blocking that location", false;
2✔
946
  }
947
  if ( ~flags & CRMULTI_IGNORE_FLATNESS )
21✔
948
  {
949
    const auto additional_gap = Core::Vec2d( 1, 1 );
20✔
950
    if ( statics_cause_problems( corner_ne - additional_gap, corner_sw + additional_gap,
20✔
951
                                 existing ) )
952

953
      return why_not = "That location is not suitable", false;
×
954
  }
955

956
  return true;
21✔
957
}
958

959
Bscript::BObjectImp* UHouse::scripted_create( const Items::ItemDesc& descriptor,
15✔
960
                                              const Core::Pos4d& pos, int flags )
961
{
962
  if ( !MultiDefByMultiIDExists( descriptor.multiid ) )
15✔
963
  {
964
    return new Bscript::BError(
965
        "Multi definition not found for House, objtype=" + Clib::hexint( descriptor.objtype ) +
×
966
        ", multiid=" + Clib::hexint( descriptor.multiid ) );
×
967
  }
968
  const MultiDef* md = MultiDefByMultiID( descriptor.multiid );
15✔
969
  std::string why_not;
15✔
970

971
  if ( !is_valid_position( md, pos, flags, why_not, nullptr ) )
15✔
972
  {
973
    return new Bscript::BError( why_not );
×
974
  }
975

976
  UHouse* house = new UHouse( descriptor );
15✔
977
  house->serial = Core::GetNewItemSerialNumber();
15✔
978
  house->serial_ext = ctBEu32( house->serial );
15✔
979
  house->setposition( pos );
15✔
980
  send_multi_to_inrange( house );
15✔
981
  // update_item_to_inrange( house );
982
  add_multi_to_world( house );
15✔
983
  house->create_components();
15✔
984

985
  ////Hash
986
  Core::objStorageManager.objecthash.Insert( house );
15✔
987
  ////
988

989
  return house->make_ref();
15✔
990
}
15✔
991

992

993
void move_to_ground( Items::Item* item )
×
994
{
995
  const Core::Pos4d oldpos = item->toplevel_pos();
×
996
  item->set_dirty();
×
997
  item->movable( true );
×
998
  item->set_decay_after( 60 );  // just a dummy in case decay=0
×
999
  item->restart_decay_timer();
×
1000
  for ( unsigned short xd = 0; xd < 5; ++xd )  // TODO POS Range2d for Vectors?
×
1001
  {
1002
    for ( unsigned short yd = 0; yd < 5; ++yd )
×
1003
    {
1004
      Items::Item* walkon;
1005
      UMulti* multi;
1006
      short newz;
1007
      item->setposition( Core::Pos4d( item->pos() ).x( 0 ).y( 0 ) );
×
1008
      auto newpos = oldpos + Core::Vec2d( xd, yd );
×
1009
      // move 'self' a bit so it doesn't interfere with itself
1010
      bool res = item->realm()->walkheight( newpos.xy(), item->z(), &newz, &multi, &walkon, true,
×
1011
                                            Plib::MOVEMODE_LAND );
1012
      if ( res )
×
1013
      {
1014
        item->setposition( newpos.z( static_cast<signed char>( newz ) ) );
×
1015
        move_item( item, oldpos );
×
1016
        return;
×
1017
      }
NEW
1018
      item->setposition( oldpos );
×
1019
    }
1020
  }
1021
  short newz;
1022
  if ( item->realm()->groundheight( item->pos2d(), &newz ) )
×
1023
  {
1024
    item->setposition( Core::Pos4d( item->pos() ).z( static_cast<signed char>( newz ) ) );
×
1025
    move_item( item, oldpos );
×
1026
    return;
×
1027
  }
1028
}
1029

1030

1031
void move_to_ground( Mobile::Character* chr )
×
1032
{
1033
  move_character_to( chr, chr->pos(), Core::MOVEITEM_FORCELOCATION );
×
1034
}
×
1035

1036
// void send_remove_object_if_inrange( Client *client, const UObject *item );
1037

1038
Bscript::BObjectImp* destroy_house( UHouse* house )
13✔
1039
{
1040
  if ( house->IsEditing() )
13✔
1041
    return new Bscript::BError( "House currently being customized." );
×
1042

1043
  house->destroy_components();
13✔
1044

1045
  ItemList item_contents;
13✔
1046
  MobileList chr_contents;
13✔
1047
  UHouse::list_contents( house, item_contents, chr_contents );
13✔
1048

1049
  send_remove_object_to_inrange( house );
13✔
1050
  remove_multi_from_world( house );
13✔
1051

1052
  while ( !item_contents.empty() )
13✔
1053
  {
1054
    Items::Item* item = item_contents.front();
×
1055
    move_to_ground( item );
×
1056

1057
    item_contents.pop_front();
×
1058
  }
1059

1060
  while ( !chr_contents.empty() )
13✔
1061
  {
1062
    Mobile::Character* chr = chr_contents.back();
×
1063
    move_to_ground( chr );
×
1064
    chr->registered_multi = 0;
×
1065
    chr_contents.pop_back();
×
1066
  }
1067

1068
  house->ClearSquatters();
13✔
1069

1070
  house->destroy();
13✔
1071

1072
  return new Bscript::BLong( 1 );
13✔
1073
}
13✔
1074

1075
void UHouse::register_object( UObject* obj )
7✔
1076
{
1077
  if ( !obj->ismobile() )
7✔
1078
    return;
3✔
1079
  if ( find( squatters_.begin(), squatters_.end(), obj ) == squatters_.end() )
4✔
1080
  {
1081
    set_dirty();
4✔
1082
    squatters_.emplace_back( obj );
4✔
1083
  }
1084
}
1085

1086
void UHouse::unregister_object( UObject* obj )
12✔
1087
{
1088
  Squatters::iterator this_squatter = find( squatters_.begin(), squatters_.end(), obj );
12✔
1089

1090
  if ( this_squatter != squatters_.end() )
12✔
1091
  {
1092
    set_dirty();
4✔
1093
    squatters_.erase( this_squatter );
4✔
1094
  }
1095
}
12✔
1096

1097
void UHouse::ClearSquatters()
13✔
1098
{
1099
  squatters_.clear();
13✔
1100
}
13✔
1101

1102
void UHouse::AcceptHouseCommit( Mobile::Character* chr, bool accept )
×
1103
{
1104
  waiting_for_accept = false;
×
1105
  if ( accept )
×
1106
  {
1107
    auto itemlist = get_working_design_items( this );
×
1108

1109
    revision++;
×
1110

1111
    // commit working design to current design
1112
    CurrentDesign = WorkingDesign;
×
1113

1114
    // invalidate old packet
1115
    std::vector<u8> newvec;
×
1116
    CurrentCompressed.swap( newvec );
×
1117

1118
    CustomHouseStopEditing( chr, this, itemlist );
×
1119

1120
    // send full house
1121
    CustomHousesSendFullToInRange( this, HOUSE_DESIGN_CURRENT );
×
1122
  }
×
1123
  else
1124
  {
1125
    WorkingDesign.AddComponents( this );
×
1126
    WorkingDesign.ClearComponents( this );
×
1127
    if ( chr && chr->client )
×
1128
      CustomHousesSendFull( this, chr->client, HOUSE_DESIGN_WORKING );
×
1129
  }
1130
}
×
1131

1132
bool UHouse::get_method_hook( const char* methodname, Bscript::Executor* ex,
×
1133
                              Core::ExportScript** hook, unsigned int* PC ) const
1134
{
1135
  if ( Core::gamestate.system_hooks.get_method_hook(
×
1136
           Core::gamestate.system_hooks.house_method_script.get(), methodname, ex, hook, PC ) )
1137
    return true;
×
1138
  return base::get_method_hook( methodname, ex, hook, PC );
×
1139
}
1140
}  // namespace Pol::Multi
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