• 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

76.79
/pol-core/pol/multi/boat.cpp
1
/** @file
2
 *
3
 * @par History
4
 * - 2005/04/02 Shinigami: move_offline_mobiles - added realm param
5
 * - 2005/08/22 Shinigami: do_tellmoves - bugfix - sometimes we've destroyed objects because of
6
 * control scripts
7
 * - 2009/07/23 MuadDib:   updates for new Enum::Packet Out ID
8
 * - 2009/08/25 Shinigami: STLport-5.2.1 fix: cassert removed
9
 *                         STLport-5.2.1 fix: boat_components changed little bit
10
 * - 2009/09/03 MuadDib:   Changes for account related source file relocation
11
 *                         Changes for multi related source file relocation
12
 * - 2009/12/02 Turley:    added 0xf3 packet - Tomi
13
 * - 2011/11/12 Tomi:      added extobj.tillerman, extobj.port_plank, extobj.starboard_plank and
14
 * extobj.hold
15
 * - 2011/12/13 Tomi:      added support for new boats
16
 */
17

18
#include "boat.h"
19

20
#include <exception>
21
#include <string>
22

23
#include "../../bscript/berror.h"
24
#include "../../bscript/executor.h"
25
#include "../../clib/cfgelem.h"
26
#include "../../clib/cfgfile.h"
27
#include "../../clib/clib.h"
28
#include "../../clib/clib_endian.h"
29
#include "../../clib/logfacility.h"
30
#include "../../clib/passert.h"
31
#include "../../clib/stlutil.h"
32
#include "../../clib/streamsaver.h"
33
#include "../../plib/systemstate.h"
34
#include "../../plib/tiles.h"
35
#include "../../plib/uconst.h"
36
#include "../../plib/ustruct.h"
37
#include "../containr.h"
38
#include "../extobj.h"
39
#include "../fnsearch.h"
40
#include "../globals/object_storage.h"
41
#include "../globals/settings.h"
42
#include "../globals/uvars.h"
43
#include "../item/item.h"
44
#include "../item/itemdesc.h"
45
#include "../mkscrobj.h"
46
#include "../mobile/charactr.h"
47
#include "../module/uomod.h"
48
#include "../network/client.h"
49
#include "../network/packethelper.h"
50
#include "../network/packets.h"
51
#include "../network/pktdef.h"
52
#include "../polvar.h"
53
#include "../realms/realm.h"
54
#include "../scrsched.h"
55
#include "../scrstore.h"
56
#include "../syshookscript.h"
57
#include "../ufunc.h"
58
#include "../uobject.h"
59
#include "../uworld.h"
60
#include "boatcomp.h"
61
#include "multi.h"
62
#include "multidef.h"
63

64

65
namespace Pol::Multi
66
{
67
// #define DEBUG_BOATS
68

69
std::vector<Network::Client*> boat_sent_to;
70

71
BoatShape::ComponentShape::ComponentShape( const std::string& str, unsigned char type )
144✔
72
{
73
  altgraphic = 0;
144✔
74
  objtype = get_component_objtype( type );
144✔
75
  ISTRINGSTREAM is( str );
144✔
76
  std::string tmp;
144✔
77
  if ( is >> tmp )
144✔
78
  {
79
    graphic = static_cast<unsigned short>( strtoul( tmp.c_str(), nullptr, 0 ) );
144✔
80
    if ( graphic )
144✔
81
    {
82
      s16 xd, yd;
83
      if ( is >> xd >> yd )
144✔
84
      {
85
        delta = Core::Vec3d( xd, yd, 0 );
144✔
86
        s16 zd;
87
        if ( is >> zd )
144✔
88
          delta.z( zd );
144✔
89
        return;
144✔
90
      }
91
    }
92
  }
93

94
  ERROR_PRINTLN( "Boat component definition '{}' is poorly formed.", str );
×
95
  throw std::runtime_error( "Poorly formed boat.cfg component definition" );
×
96
}
144✔
97

98
BoatShape::ComponentShape::ComponentShape( const std::string& str, const std::string& altstr,
72✔
99
                                           unsigned char type )
72✔
100
    : ComponentShape( str, type )
72✔
101
{
102
  ISTRINGSTREAM altis( altstr );
72✔
103
  std::string alttmp;
72✔
104
  if ( altis >> alttmp )
72✔
105
  {
106
    altgraphic = static_cast<unsigned short>( strtoul( alttmp.c_str(), nullptr, 0 ) );
72✔
107
    return;
72✔
108
  }
109
  ERROR_PRINTLN( "Boat component definition '{}' is poorly formed.", str );
×
110
  throw std::runtime_error( "Poorly formed boat.cfg component definition" );
×
111
}
72✔
112

113

114
BoatShape::BoatShape( Clib::ConfigElem& elem )
48✔
115
{
116
  std::string tmp_str;
48✔
117

118
  while ( elem.remove_prop( "Tillerman", &tmp_str ) )
84✔
119
    Componentshapes.emplace_back( tmp_str, COMPONENT_TILLERMAN );
36✔
120
  while ( elem.remove_prop( "PortGangplankRetracted", &tmp_str ) )
84✔
121
    Componentshapes.emplace_back( tmp_str, elem.remove_string( "PortGangplankExtended" ),
36✔
122
                                  COMPONENT_PORT_PLANK );
72✔
123
  while ( elem.remove_prop( "StarboardGangplankRetracted", &tmp_str ) )
84✔
124
    Componentshapes.emplace_back( tmp_str, elem.remove_string( "StarboardGangplankExtended" ),
36✔
125
                                  COMPONENT_STARBOARD_PLANK );
72✔
126
  while ( elem.remove_prop( "Hold", &tmp_str ) )
84✔
127
    Componentshapes.emplace_back( tmp_str, COMPONENT_HOLD );
36✔
128
  while ( elem.remove_prop( "Rope", &tmp_str ) )
48✔
129
    Componentshapes.emplace_back( tmp_str, COMPONENT_ROPE );
×
130
  while ( elem.remove_prop( "Wheel", &tmp_str ) )
48✔
131
    Componentshapes.emplace_back( tmp_str, COMPONENT_WHEEL );
×
132
  while ( elem.remove_prop( "Hull", &tmp_str ) )
48✔
133
    Componentshapes.emplace_back( tmp_str, COMPONENT_HULL );
×
134
  while ( elem.remove_prop( "Tiller", &tmp_str ) )
48✔
135
    Componentshapes.emplace_back( tmp_str, COMPONENT_TILLER );
×
136
  while ( elem.remove_prop( "Rudder", &tmp_str ) )
48✔
137
    Componentshapes.emplace_back( tmp_str, COMPONENT_RUDDER );
×
138
  while ( elem.remove_prop( "Sails", &tmp_str ) )
48✔
139
    Componentshapes.emplace_back( tmp_str, COMPONENT_SAILS );
×
140
  while ( elem.remove_prop( "Storage", &tmp_str ) )
48✔
141
    Componentshapes.emplace_back( tmp_str, COMPONENT_STORAGE );
×
142
  while ( elem.remove_prop( "Weaponslot", &tmp_str ) )
48✔
143
    Componentshapes.emplace_back( tmp_str, COMPONENT_WEAPONSLOT );
×
144
}
48✔
145

146
bool BoatShape::objtype_is_component( unsigned int objtype )
4✔
147
{
148
  if ( objtype == Core::settingsManager.extobj.tillerman )
4✔
149
    return true;
1✔
150
  if ( objtype == Core::settingsManager.extobj.port_plank )
3✔
151
    return true;
1✔
152
  if ( objtype == Core::settingsManager.extobj.starboard_plank )
2✔
153
    return true;
1✔
154
  if ( objtype == Core::settingsManager.extobj.hold )
1✔
155
    return true;
1✔
NEW
156
  if ( objtype == Core::settingsManager.extobj.rope )
×
157
    return true;
×
NEW
158
  if ( objtype == Core::settingsManager.extobj.wheel )
×
159
    return true;
×
NEW
160
  if ( objtype == Core::settingsManager.extobj.hull )
×
161
    return true;
×
NEW
162
  if ( objtype == Core::settingsManager.extobj.tiller )
×
163
    return true;
×
NEW
164
  if ( objtype == Core::settingsManager.extobj.rudder )
×
165
    return true;
×
NEW
166
  if ( objtype == Core::settingsManager.extobj.sails )
×
167
    return true;
×
NEW
168
  if ( objtype == Core::settingsManager.extobj.storage )
×
169
    return true;
×
NEW
170
  if ( objtype == Core::settingsManager.extobj.weaponslot )
×
171
    return true;
×
NEW
172
  return false;
×
173
}
174

175
size_t BoatShape::estimateSize() const
16✔
176
{
177
  return Clib::memsize( Componentshapes );
16✔
178
}
179

180
unsigned int get_component_objtype( unsigned char type )
144✔
181
{
182
  switch ( type )
144✔
183
  {
184
  case COMPONENT_TILLERMAN:
36✔
185
    return Core::settingsManager.extobj.tillerman;
36✔
186
  case COMPONENT_PORT_PLANK:
36✔
187
    return Core::settingsManager.extobj.port_plank;
36✔
188
  case COMPONENT_STARBOARD_PLANK:
36✔
189
    return Core::settingsManager.extobj.starboard_plank;
36✔
190
  case COMPONENT_HOLD:
36✔
191
    return Core::settingsManager.extobj.hold;
36✔
192
  case COMPONENT_ROPE:
×
193
    return Core::settingsManager.extobj.rope;
×
194
  case COMPONENT_WHEEL:
×
195
    return Core::settingsManager.extobj.wheel;
×
196
  case COMPONENT_HULL:
×
197
    return Core::settingsManager.extobj.hull;
×
198
  case COMPONENT_TILLER:
×
199
    return Core::settingsManager.extobj.tiller;
×
200
  case COMPONENT_RUDDER:
×
201
    return Core::settingsManager.extobj.rudder;
×
202
  case COMPONENT_SAILS:
×
203
    return Core::settingsManager.extobj.sails;
×
204
  case COMPONENT_STORAGE:
×
205
    return Core::settingsManager.extobj.storage;
×
206
  case COMPONENT_WEAPONSLOT:
×
207
    return Core::settingsManager.extobj.weaponslot;
×
208
  default:
×
209
    return 0;
×
210
  }
211
}
212

213
void read_boat_cfg()
3✔
214
{
215
  Clib::ConfigFile cf( "config/boats.cfg", "Boat" );
3✔
216
  Clib::ConfigElem elem;
3✔
217
  while ( cf.read( elem ) )
51✔
218
  {
219
    unsigned short multiid = elem.remove_ushort( "MultiID" );
48✔
220
    try
221
    {
222
      Core::gamestate.boatshapes[multiid] = new BoatShape( elem );
48✔
223
    }
224
    catch ( std::exception& )
×
225
    {
226
      ERROR_PRINTLN( "Error occurred reading definition for boat {:#x}", multiid );
×
227
      throw;
×
228
    }
×
229
  }
230
}
3✔
231

232
void clean_boatshapes()
3✔
233
{
234
  Core::BoatShapes::iterator iter = Core::gamestate.boatshapes.begin();
3✔
235
  for ( ; iter != Core::gamestate.boatshapes.end(); ++iter )
51✔
236
  {
237
    if ( iter->second != nullptr )
48✔
238
      delete iter->second;
48✔
239
    iter->second = nullptr;
48✔
240
  }
241
  Core::gamestate.boatshapes.clear();
3✔
242
}
3✔
243

244
bool BoatShapeExists( u16 multiid )
36✔
245
{
246
  return Core::gamestate.boatshapes.count( multiid ) != 0;
36✔
247
}
248

249
void UBoat::send_smooth_move( Network::Client* client, Core::UFACING move_dir, u8 speed,
24✔
250
                              bool relative ) const
251
{
252
  Network::PktHelper::PacketOut<Network::PktOut_F6> msg;
24✔
253

254
  Core::UFACING b_facing = boat_facing();
24✔
255
  if ( relative == false )
24✔
256
    move_dir = static_cast<Core::UFACING>( ( b_facing + move_dir ) * 7 );
24✔
257

258
  msg->offset += 2;  // Length
24✔
259
  msg->Write<u32>( serial_ext );
24✔
260
  msg->Write<u8>( speed );
24✔
261

262
  msg->Write<u8>( move_dir );
24✔
263
  msg->Write<u8>( b_facing );
24✔
264

265
  msg->WriteFlipped<u16>( x() );
24✔
266
  msg->WriteFlipped<u16>( y() );
24✔
267
  msg->WriteFlipped<s16>( z() );
24✔
268

269
  // 0xe000 encoding room, huffman can take more space then the maximum of 0xffff (10% + extra
270
  // margin)
271
  const u16 max_count = ( 0xe000 - 18 ) / 10;
24✔
272
  u16 object_count = 0;
24✔
273
  u16 len_offset = msg->offset;
24✔
274
  msg->offset += 2;  // Length
24✔
275
  for ( auto& component : Components )
120✔
276
  {
277
    if ( object_count >= max_count )
96✔
278
    {
279
      POLLOG_INFOLN( "Boat {:#x} at {} with {} items is too full - truncating movement packet",
×
280
                     serial, pos(), travellers_.size() );
×
281
      break;
×
282
    }
283
    if ( component == nullptr || component->orphan() )
96✔
284
      continue;
×
285

286
    msg->Write<u32>( component->serial_ext );
96✔
287
    msg->WriteFlipped<u16>( component->x() );
96✔
288
    msg->WriteFlipped<u16>( component->y() );
96✔
289
    msg->WriteFlipped<s16>( component->z() );
96✔
290
    ++object_count;
96✔
291
  }
292
  for ( auto& travellerRef : travellers_ )
5,766✔
293
  {
294
    if ( object_count >= max_count )
5,743✔
295
    {
296
      POLLOG_INFOLN( "Boat {:#x} at {} with {} items is too full - truncating movement packet",
2✔
297
                     serial, pos(), travellers_.size() );
1✔
298
      break;
1✔
299
    }
300
    UObject* obj = travellerRef.get();
5,742✔
301

302
    if ( obj->orphan() )
5,742✔
303
      continue;
×
304
    if ( obj->ismobile() )
5,742✔
305
    {
306
      auto* chr = static_cast<Mobile::Character*>( obj );
14✔
307
      if ( !client->chr->is_visible_to_me( chr, /*check_range*/ false ) )
14✔
308
        continue;
×
309
    }
310
    else
311
    {
312
      auto* item = static_cast<Items::Item*>( obj );
5,728✔
313
      if ( item->invisible() && !client->chr->can_seeinvisitems() )
5,728✔
314
        continue;
×
315
    }
316
    msg->Write<u32>( obj->serial_ext );
5,742✔
317
    msg->WriteFlipped<u16>( obj->x() );
5,742✔
318
    msg->WriteFlipped<u16>( obj->y() );
5,742✔
319
    msg->WriteFlipped<s16>( obj->z() );
5,742✔
320
    ++object_count;
5,742✔
321
  }
322
  u16 len = msg->offset;
24✔
323
  msg->offset = len_offset;
24✔
324
  msg->WriteFlipped<u16>( object_count );
24✔
325
  msg->offset = 1;
24✔
326
  msg->WriteFlipped<u16>( len );
24✔
327

328
  msg.Send( client, len );
24✔
329
}
24✔
330

331
void UBoat::send_display_boat( Network::Client* client )
10✔
332
{
333
  Network::PktHelper::PacketOut<Network::PktOut_F7> msg;
10✔
334

335
  msg->offset += 2;  // Length
10✔
336

337
  // Send_display_boat is only called for CLIENTTYPE_7090, so each 0xF3 is 26 bytes here
338
  // 0xe000 encoding room, huffman can take more space then the maximum of 0xffff (10% + extra
339
  // margin)
340
  const u16 max_count = ( 0xe000 - 5 ) / 26;
10✔
341
  u16 object_count = 1;  // Add 1 for the boat aswell
10✔
342
  u16 len_offset = msg->offset;
10✔
343
  msg->offset += 2;  // Length
10✔
344

345
  // Build boat part
346
  msg->Write<u8>( 0xF3u );
10✔
347
  msg->WriteFlipped<u16>( 0x1u );
10✔
348
  msg->Write<u8>( 0x2u );  // MultiData flag
10✔
349
  msg->Write<u32>( this->serial_ext );
10✔
350
  msg->WriteFlipped<u16>( this->multidef().multiid );
10✔
351
  msg->offset++;                   // ID offset, TODO CHECK IF NEED THESE
10✔
352
  msg->WriteFlipped<u16>( 0x1u );  // Amount
10✔
353
  msg->WriteFlipped<u16>( 0x1u );  // Amount
10✔
354
  msg->WriteFlipped<u16>( this->x() );
10✔
355
  msg->WriteFlipped<u16>( this->y() );
10✔
356
  msg->Write<s8>( this->z() );
10✔
357
  msg->offset++;  // facing 0 for multis
10✔
358
  msg->WriteFlipped<u16>( this->color );
10✔
359
  msg->offset++;     // flags 0 for multis
10✔
360
  msg->offset += 2;  // HSA access flags, TODO find out what these are for and implement it
10✔
361

362
  for ( auto& component : Components )
42✔
363
  {
364
    if ( object_count >= max_count )
32✔
365
    {
366
      POLLOG_INFOLN( "Boat {:#x} at {} with {} items is too full - truncating display boat packet",
×
367
                     serial, pos(), travellers_.size() );
×
368
      break;
×
369
    }
370
    if ( component == nullptr || component->orphan() )
32✔
371
      continue;
×
372
    msg->Write<u8>( 0xF3u );
32✔
373
    msg->WriteFlipped<u16>( 0x1u );
32✔
374
    msg->Write<u8>( 0x0u );  // ItemData flag
32✔
375
    msg->Write<u32>( component->serial_ext );
32✔
376
    msg->WriteFlipped<u16>( component->graphic );
32✔
377
    msg->offset++;  // ID offset, TODO CHECK IF NEED THESE
32✔
378
    msg->WriteFlipped<u16>( component->get_senditem_amount() );  // Amount
32✔
379
    msg->WriteFlipped<u16>( component->get_senditem_amount() );  // Amount
32✔
380
    msg->WriteFlipped<u16>( component->x() );
32✔
381
    msg->WriteFlipped<u16>( component->y() );
32✔
382
    msg->Write<s8>( component->z() );
32✔
383
    msg->Write<u8>( component->facing );
32✔
384
    msg->WriteFlipped<u16>( component->color );
32✔
385
    msg->offset++;     // FLAGS, no flags for components
32✔
386
    msg->offset += 2;  // HSA access flags, TODO find out what these are for and implement it
32✔
387
    ++object_count;
32✔
388
  }
389
  for ( auto& travellerRef : travellers_ )
18✔
390
  {
391
    UObject* obj = travellerRef.get();
8✔
392

393
    if ( obj->orphan() )
8✔
394
      continue;
×
395
    if ( object_count >= max_count )
8✔
396
    {
397
      POLLOG_INFOLN( "Boat {:#x} at {} with {} items is too full - truncating display boat packet",
×
398
                     serial, pos(), travellers_.size() );
×
399
      break;
×
400
    }
401
    u8 flags = 0;
8✔
402
    if ( obj->ismobile() )
8✔
403
    {
404
      auto* chr = static_cast<Mobile::Character*>( obj );
8✔
405
      if ( !client->chr->is_visible_to_me( chr, /*check_range*/ false ) )
8✔
406
        continue;
×
407
    }
408
    else
409
    {
410
      auto* item = static_cast<Items::Item*>( obj );
×
411
      if ( item->invisible() && !client->chr->can_seeinvisitems() )
×
412
      {
413
        send_remove_object( client, item );
×
414
        continue;
×
415
      }
416
      if ( client->chr->can_move( item ) )
×
417
        flags |= ITEM_FLAG_FORCE_MOVABLE;
×
418
    }
419

420
    msg->Write<u8>( 0xF3u );
8✔
421
    msg->WriteFlipped<u16>( 0x1u );
8✔
422

423
    if ( obj->ismobile() )
8✔
424
      msg->Write<u8>( 0x1u );  // CharData flag
8✔
425
    else
426
      msg->Write<u8>( 0x0u );  // ItemData flag
×
427

428
    msg->Write<u32>( obj->serial_ext );
8✔
429
    msg->WriteFlipped<u16>( obj->graphic );
8✔
430
    msg->offset++;  // ID offset, TODO CHECK IF NEED THESE
8✔
431

432
    if ( obj->ismobile() )
8✔
433
    {
434
      msg->WriteFlipped<u16>( 0x1u );  // Amount
8✔
435
      msg->WriteFlipped<u16>( 0x1u );  // Amount
8✔
436
    }
437
    else
438
    {
439
      Items::Item* item = static_cast<Items::Item*>( obj );
×
440
      msg->WriteFlipped<u16>( item->get_senditem_amount() );  // Amount
×
441
      msg->WriteFlipped<u16>( item->get_senditem_amount() );  // Amount
×
442
    }
443

444
    msg->WriteFlipped<u16>( obj->x() );
8✔
445
    msg->WriteFlipped<u16>( obj->y() );
8✔
446
    msg->Write<s8>( obj->z() );
8✔
447
    msg->Write<u8>( obj->facing );
8✔
448
    msg->WriteFlipped<u16>( obj->color );
8✔
449

450
    msg->Write<u8>( flags );  // FLAGS
8✔
451
    msg->offset += 2;         // HSA access flags, TODO find out what these are for and implement it
8✔
452
    ++object_count;
8✔
453
  }
454
  u16 len = msg->offset;
10✔
455
  msg->offset = len_offset;
10✔
456
  msg->WriteFlipped<u16>( object_count );
10✔
457
  msg->offset = 1;
10✔
458
  msg->WriteFlipped<u16>( len );
10✔
459

460
  msg.Send( client, len );
10✔
461
}
10✔
462

463
void UBoat::send_boat_newly_inrange( Network::Client* client )
1✔
464
{
465
  Network::PktHelper::PacketOut<Network::PktOut_F3> msg;
1✔
466
  msg->WriteFlipped<u16>( 0x1u );
1✔
467
  msg->Write<u8>( 0x02u );
1✔
468
  msg->Write<u32>( serial_ext );
1✔
469
  msg->WriteFlipped<u16>( multidef().multiid );
1✔
470
  msg->offset++;                   // 0;
1✔
471
  msg->WriteFlipped<u16>( 0x1u );  // amount
1✔
472
  msg->WriteFlipped<u16>( 0x1u );  // amount2
1✔
473
  msg->WriteFlipped<u16>( x() );
1✔
474
  msg->WriteFlipped<u16>( y() );
1✔
475
  msg->Write<s8>( z() );
1✔
476
  msg->offset++;                    // u8 facing
1✔
477
  msg->WriteFlipped<u16>( color );  // u16 color
1✔
478
  msg->offset += 3;                 // u8 flags + u16 HSA access flags
1✔
479

480
  msg.Send( client );
1✔
481

482
  for ( auto& travellerRef : travellers_ )
1✔
483
  {
484
    UObject* obj = travellerRef.get();
×
485

486
    if ( !obj->orphan() )
×
487
    {
488
      if ( obj->ismobile() )
×
489
      {
490
        Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
×
491
        send_owncreate( client, chr );
×
492
      }
493
      else
494
      {
495
        Items::Item* item = static_cast<Items::Item*>( obj );
×
496
        send_item( client, item );
×
497
      }
498
    }
499
  }
500

501
  for ( auto& component : Components )
5✔
502
  {
503
    if ( component != nullptr && !component->orphan() )
4✔
504
      send_item( client, component.get() );
4✔
505
  }
506
}
1✔
507

508
void UBoat::send_display_boat_to_inrange( const std::optional<Core::Pos4d>& oldpos )
35✔
509
{
510
  Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
35✔
511
      this,
512
      [&]( Mobile::Character* zonechr )
35✔
513
      {
514
        Network::Client* client = zonechr->client;
28✔
515
        if ( !zonechr->in_visual_range( this ) )
28✔
516
          return;
18✔
517

518
        if ( client->ClientType & Network::CLIENTTYPE_7090 )
10✔
519
          send_display_boat( client );
10✔
520
        else if ( client->ClientType & Network::CLIENTTYPE_7000 )
×
521
          send_boat( client );
×
522
        else
523
          send_boat_old( client );
×
524
      } );
525

526
  if ( oldpos )
35✔
527
  {
528
    Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
11✔
529
        oldpos.value(),
530
        [&]( Mobile::Character* zonechr )
22✔
531
        {
532
          if ( !zonechr->in_visual_range( this, oldpos.value() ) )
16✔
533
            return;
11✔
534
          if ( !zonechr->in_visual_range( this ) )  // send remove to chrs only seeing the old loc
5✔
535
            send_remove_boat( zonechr->client );
×
536
        } );
537
  }
538
}
35✔
539

540
void UBoat::send_boat( Network::Client* client )
×
541
{
542
  // Client >= 7.0.0.0 ( SA )
543
  Network::PktHelper::PacketOut<Network::PktOut_F3> msg2;
×
544
  msg2->WriteFlipped<u16>( 0x1u );
×
545
  msg2->Write<u8>( 0x02u );
×
546
  msg2->Write<u32>( this->serial_ext );
×
547
  msg2->WriteFlipped<u16>( this->multidef().multiid );
×
548
  msg2->offset++;                   // 0;
×
549
  msg2->WriteFlipped<u16>( 0x1u );  // amount
×
550
  msg2->WriteFlipped<u16>( 0x1u );  // amount2
×
551
  msg2->WriteFlipped<u16>( this->x() );
×
552
  msg2->WriteFlipped<u16>( this->y() );
×
553
  msg2->Write<s8>( this->z() );
×
554
  msg2->offset++;                          // u8 facing
×
555
  msg2->WriteFlipped<u16>( this->color );  // u16 color
×
556
  msg2->offset++;                          // u8 flags
×
557

558
  msg2.Send( client );
×
559
}
×
560

561
void UBoat::send_boat_old( Network::Client* client )
×
562
{
563
  Network::PktHelper::PacketOut<Network::PktOut_1A> msg;
×
564
  msg->offset += 2;
×
565
  u16 b_graphic = this->multidef().multiid | 0x4000;
×
566
  msg->Write<u32>( this->serial_ext );
×
567
  msg->WriteFlipped<u16>( b_graphic );
×
568
  msg->WriteFlipped<u16>( this->x() );
×
569
  msg->WriteFlipped<u16>( this->y() );
×
570
  msg->Write<s8>( this->z() );
×
571
  u16 len1A = msg->offset;
×
572
  msg->offset = 1;
×
573
  msg->WriteFlipped<u16>( len1A );
×
574

575
  client->pause();
×
576
  msg.Send( client, len1A );
×
577
  boat_sent_to.push_back( client );
×
578
}
×
579

580
void UBoat::send_remove_boat( Network::Client* client )
1✔
581
{
582
  send_remove_object( client, this );
1✔
583
}
1✔
584

585
void unpause_paused()
83✔
586
{
587
  for ( Network::Client* client : boat_sent_to )
83✔
588
  {
589
    client->restart();
×
590
  }
591
  boat_sent_to.clear();
83✔
592
}
83✔
593

594

595
UBoat::UBoat( const Items::ItemDesc& descriptor )
18✔
596
    : UMulti( descriptor ),
597
      tillerman( nullptr ),
18✔
598
      portplank( nullptr ),
18✔
599
      starboardplank( nullptr ),
18✔
600
      hold( nullptr ),
18✔
601
      mountpiece( nullptr )
18✔
602
{
603
  passert( Core::gamestate.boatshapes.count( multiid_ ) != 0 );
18✔
604
}
18✔
605

606
UBoat* UBoat::as_boat()
17✔
607
{
608
  return this;
17✔
609
}
610

611
bool UBoat::objtype_passable( unsigned short objtype )
5,341✔
612
{
613
  if ( Core::settingsManager.ssopt.boat_sails_collide )
5,341✔
614
    return false;
8✔
615

616
  auto uoflags = Plib::systemstate.tile[objtype].uoflags;
5,333✔
617
  bool passable = ( uoflags & Plib::USTRUCT_TILE::FLAG_FOLIAGE ) &&
5,911✔
618
                  ( ~uoflags & Plib::USTRUCT_TILE::FLAG_BLOCKING );
578✔
619
  return passable;
5,333✔
620
}
621

622
/* boats are a bit strange - they can move from side to side, forwards and
623
   backwards, without turning.
624
   */
625
void UBoat::regself()
94✔
626
{
627
  const MultiDef& md = multidef();
94✔
628
  for ( const auto& ele : md.hull )
1,970✔
629
  {
630
    if ( objtype_passable( ele->objtype ) )
1,876✔
631
      continue;
202✔
632

633
    Core::Pos2d hullpos = pos2d() + ele->relpos.xy();
1,674✔
634

635
    unsigned int gh = realm()->encode_global_hull( hullpos );
1,674✔
636
    realm()->global_hulls.insert( gh );
1,674✔
637
  }
638
}
94✔
639

640
void UBoat::unregself()
92✔
641
{
642
  const MultiDef& md = multidef();
92✔
643
  for ( const auto& ele : md.hull )
1,928✔
644
  {
645
    if ( objtype_passable( ele->objtype ) )
1,836✔
646
      continue;
198✔
647

648
    Core::Pos2d hullpos = pos2d() + ele->relpos.xy();
1,638✔
649

650
    unsigned int gh = realm()->encode_global_hull( hullpos );
1,638✔
651
    realm()->global_hulls.erase( gh );
1,638✔
652
  }
653
}
92✔
654

655
bool UBoat::can_fit_at_location( const MultiDef& md, const Core::Pos4d& desired_pos )
92✔
656
{
657
  if ( !desired_pos.can_move_to( md.minrxyz.xy() ) || !desired_pos.can_move_to( md.maxrxyz.xy() ) )
92✔
658
  {
659
#ifdef DEBUG_BOATS
660
    INFO_PRINTLN( "Location {} impassable, location is off the map", desired_pos );
661
#endif
662
    return false;
3✔
663
  }
664
  return true;
89✔
665
}
666

667
// navigable: Can the ship sit here?  ie is every point on the hull on water,and not blocked?
668
bool UBoat::navigable( const MultiDef& md, const Core::Pos4d& desired_pos )
89✔
669
{
670
  if ( !can_fit_at_location( md, desired_pos ) )
89✔
671
    return false;
3✔
672

673
  /* Test the external hull to make sure it's on water */
674

675
  for ( const auto& ele : md.hull )
1,709✔
676
  {
677
    Core::Pos3d hullpos = desired_pos.xyz() + ele->relpos;
1,629✔
678
#ifdef DEBUG_BOATS
679
    INFO_PRINT( "[{}]", hullpos );
680
#endif
681

682
    if ( objtype_passable( ele->objtype ) )
1,629✔
683
      continue;
178✔
684

685
    /*
686
     * See if any other ship hulls occupy this space
687
     */
688
    unsigned int gh = desired_pos.realm()->encode_global_hull( hullpos.xy() );
1,451✔
689
    if ( desired_pos.realm()->global_hulls.count( gh ) )  // already a boat there
1,451✔
690
    {
691
#ifdef DEBUG_BOATS
692
      INFO_PRINTLN( "Location {} {} already has a ship hull present", desired_pos.realm()->name(),
693
                    hullpos );
694
#endif
695
      return false;
6✔
696
    }
697

698
    if ( !desired_pos.realm()->navigable( hullpos, Plib::systemstate.tile[ele->objtype].height ) )
1,451✔
699
      return false;
6✔
700
  }
701

702
  return true;
80✔
703
}
704

705
// ugh, we've moved the ship already - but we're comparing using the character's coords
706
// which used to be on the ship.
707
// let's hope it's always illegal to stand on the edges. !!
708
bool UBoat::on_ship( const BoatContext& bc, const UObject* obj )
18,050✔
709
{
710
  if ( Core::IsItem( obj->serial ) )
18,050✔
711
  {
712
    const Item* item = static_cast<const Item*>( obj );
18,010✔
713
    if ( item->container != nullptr )
18,010✔
714
      return false;
×
715
  }
716
  Core::Vec2d rxy = obj->pos2d() - bc.oldpos.xy();
18,050✔
717

718
  return bc.mdef.body_contains( rxy );
18,050✔
719
}
720

721
void UBoat::move_travellers( const BoatContext& oldlocation )
62✔
722
{
723
  bool any_orphans = false;
62✔
724

725
  for ( auto& travellerRef : travellers_ )
6,090✔
726
  {
727
    UObject* obj = travellerRef.get();
6,028✔
728

729
    // consider: occasional sweeps of all boats to reap orphans
730
    if ( obj->orphan() || !on_ship( oldlocation, obj ) )
6,028✔
731
    {
732
      any_orphans = true;
×
733
      travellerRef.clear();
×
734
      continue;
×
735
    }
736

737
    obj->set_dirty();
6,028✔
738
    // keeps relative distance from boat mast
739
    auto delta = obj->pos3d() - oldlocation.oldpos.xyz();
6,028✔
740
    auto newtravellerpos = pos() + delta;
6,028✔
741
    if ( obj->ismobile() )
6,028✔
742
    {
743
      auto* chr = static_cast<Mobile::Character*>( obj );
23✔
744
      move_boat_mobile( chr, newtravellerpos );
23✔
745
    }
746
    else
747
    {
748
      auto* item = static_cast<Items::Item*>( obj );
6,005✔
749
      move_boat_item( item, newtravellerpos );
6,005✔
750

751
      if ( Core::settingsManager.ssopt.refresh_decay_after_boat_moves )
6,005✔
752
        item->restart_decay_timer();
6,005✔
753
    }
754
  }
755

756
  if ( any_orphans )
62✔
757
    remove_orphans();
×
758
}
62✔
759

760
void UBoat::move_boat_item( Items::Item* item, const Core::Pos4d& newpos )
6,285✔
761
{
762
  item->set_dirty();
6,285✔
763
  const Core::Pos4d oldpos = item->pos();
6,285✔
764
  item->setposition( newpos );
6,285✔
765
  MoveItemWorldPosition( oldpos, item );
6,285✔
766
  // TODO POS should be removed
767
  if ( oldpos.realm() != newpos.realm() && item->isa( Core::UOBJ_CLASS::CLASS_CONTAINER ) )
6,285✔
768
  {
769
    auto* cont = static_cast<Core::UContainer*>( item );
2✔
770
    cont->for_each_item( Core::setrealm, (void*)realm() );
2✔
771
  }
772

773
  Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
6,285✔
774
      item,
775
      [&]( Mobile::Character* zonechr )
6,285✔
776
      {
777
        if ( !zonechr->in_visual_range( item ) )
12,378✔
778
          return;
6,278✔
779
        Network::Client* client = zonechr->client;
6,100✔
780

781
        if ( !( client->ClientType & Network::CLIENTTYPE_7090 ) )
6,100✔
782
          send_item( client, item );
×
783
        else if ( !client->chr->in_visual_range(
6,100✔
784
                      item, oldpos ) )  // multis are visible before a client accepts items, we need
6,100✔
785
                                        // to resend them
786
          send_item( client, item );
3✔
787
      } );
788

789
  Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
6,285✔
790
      oldpos,
791
      [&]( Mobile::Character* zonechr )
6,285✔
792
      {
793
        if ( !zonechr->in_visual_range( item, oldpos ) )
12,378✔
794
          return;
6,278✔
795
        if ( !zonechr->in_visual_range(
6,100✔
796
                 item ) )  // not in range.  If old loc was in range, send a delete.
797
          send_remove_object( zonechr->client, item );
3✔
798
      } );
799
}
6,285✔
800

801
void UBoat::move_boat_mobile( Mobile::Character* chr, const Core::Pos4d& newpos )
27✔
802
{
803
  const auto oldpos = chr->pos();
27✔
804
  // TODO from core.cpp more refactoring... can move_character_to() be used here?
805
  if ( oldpos.realm() != newpos.realm() )
27✔
806
  {
807
    oldpos.realm()->notify_left( *chr );
2✔
808
    send_remove_character_to_nearby( chr );
2✔
809
    if ( chr->client != nullptr )
2✔
810
      Core::remove_objects_inrange( chr->client );
×
811
  }
812

813
  chr->setposition( newpos );
27✔
814
  chr->lastpos = oldpos;
27✔
815
  if ( oldpos.realm() != newpos.realm() )
27✔
816
    chr->realm_changed();
2✔
817

818
  MoveCharacterWorldPosition( oldpos, chr );
27✔
819
  chr->position_changed();
27✔
820
  if ( chr->has_active_client() )
27✔
821
  {
822
    if ( oldpos.realm() != chr->realm() )
19✔
823
    {
824
      Core::send_new_subserver( chr->client );
×
825
      Core::send_owncreate( chr->client, chr );
×
826
    }
827

828
    if ( chr->client->ClientType & Network::CLIENTTYPE_7090 )
19✔
829
    {
830
      if ( chr->poisoned() )  // if poisoned send 0x17 for newer clients
19✔
831
        send_poisonhealthbar( chr->client, chr );
×
832

833
      if ( chr->invul() )  // if invul send 0x17 for newer clients
19✔
834
        send_invulhealthbar( chr->client, chr );
×
835
    }
836
    else
837
    {
838
      Core::send_goxyz( chr->client, chr );
×
839
      // lastpos are set above so these two calls will work right.
840
      // FIXME these are also called, in this order, in MOVEMENT.CPP.
841
      // should be consolidated.
842
      Core::send_objects_newly_inrange_on_boat( chr->client, this->serial );
×
843
    }
844
  }
845
  chr->move_reason = Mobile::Character::MULTIMOVE;
27✔
846
  // multis are visible before a client accepts objects, we need to resend them
847
  Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
27✔
848
      chr,
849
      [&]( Mobile::Character* zonechr )
27✔
850
      {
851
        if ( !( zonechr->client->ClientType & Network::CLIENTTYPE_7090 ) )
38✔
852
          return;
×
853
        if ( !zonechr->is_visible_to_me( chr, /*check_range*/ true ) )
38✔
854
          return;
19✔
855
        if ( !zonechr->in_visual_range( chr, oldpos ) )
19✔
856
          send_owncreate( zonechr->client, chr );
×
857
      } );
858
}
27✔
859

860
Core::Pos4d UBoat::turn_coords( const Core::Pos4d& oldpos, RELATIVE_DIR dir ) const
8✔
861
{
862
  Core::Vec3d delta = oldpos.xyz() - pos3d();
8✔
863
  switch ( dir )
8✔
864
  {
865
  case LEFT:
2✔
866
    delta = Core::Vec3d( delta.y(), -delta.x(), delta.z() );
2✔
867
    break;
2✔
868
  case AROUND:
2✔
869
    delta.x( -delta.x() ).y( -delta.y() );
2✔
870
    break;
2✔
871
  case RIGHT:
2✔
872
    delta = Core::Vec3d( -delta.y(), delta.x(), delta.z() );
2✔
873
    break;
2✔
874
  case NO_TURN:
2✔
875
    return oldpos;
2✔
876
  }
877
  return pos() + delta;
6✔
878
}
879

880
u8 UBoat::turn_facing( u8 oldfacing, RELATIVE_DIR dir ) const
8✔
881
{
882
  return ( ( dir * 2 ) + oldfacing ) & 7;
8✔
883
}
884

885
void UBoat::turn_travellers( RELATIVE_DIR dir, const BoatContext& oldlocation )
4✔
886
{
887
  bool any_orphans = false;
4✔
888

889
  for ( auto& travellerRef : travellers_ )
12✔
890
  {
891
    UObject* obj = travellerRef.get();
8✔
892

893
    // consider: occasional sweeps of all boats to reap orphans
894
    if ( obj->orphan() || !on_ship( oldlocation, obj ) )
8✔
895
    {
896
      any_orphans = true;
×
897
      travellerRef.clear();
×
898
      continue;
×
899
    }
900

901
    obj->set_dirty();
8✔
902
    auto newpos = turn_coords( obj->pos(), dir );
8✔
903
    if ( obj->ismobile() )
8✔
904
    {
905
      Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
4✔
906
      chr->setfacing( turn_facing( chr->facing, dir ) );
4✔
907
      move_boat_mobile( chr, newpos );
4✔
908
    }
909
    else
910
    {
911
      Items::Item* item = static_cast<Items::Item*>( obj );
4✔
912

913
      move_boat_item( item, newpos );
4✔
914
      if ( Core::settingsManager.ssopt.refresh_decay_after_boat_moves )
4✔
915
        item->restart_decay_timer();
4✔
916
    }
917
  }
918

919
  if ( any_orphans )
4✔
920
    remove_orphans();
×
921
}
4✔
922

923
void UBoat::remove_orphans()
16✔
924
{
925
  bool any_orphan_travellers;
926
  do
16✔
927
  {
928
    any_orphan_travellers = false;
16✔
929

930
    for ( Travellers::iterator itr = travellers_.begin(), end = travellers_.end(); itr != end;
16✔
931
          ++itr )
×
932
    {
933
      UObject* obj = ( *itr ).get();
×
934
      if ( obj == nullptr )
×
935
      {
936
        set_dirty();
×
937
        travellers_.erase( itr );
×
938
        any_orphan_travellers = true;
×
939
        break;
×
940
      }
941
    }
942
  } while ( any_orphan_travellers );
943
}
16✔
944

945
void UBoat::cleanup_deck()
16✔
946
{
947
  BoatContext bc( *this );
16✔
948

949
  for ( auto& travellerRef : travellers_ )
16✔
950
  {
951
    UObject* obj = travellerRef.get();
×
952

953
    if ( obj->orphan() || !on_ship( bc, obj ) )
×
954
    {
955
      set_dirty();
×
956
      travellerRef.clear();
×
957
    }
958
  }
959
  remove_orphans();
16✔
960
}
16✔
961

962
bool UBoat::has_offline_mobiles() const
16✔
963
{
964
  BoatContext bc( *this );
16✔
965

966
  for ( const auto& travellerRef : travellers_ )
16✔
967
  {
968
    UObject* obj = travellerRef.get();
×
969

970
    if ( !obj->orphan() && on_ship( bc, obj ) && obj->ismobile() )
×
971
    {
972
      Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
×
973

974
      if ( !chr->logged_in() )
×
975
      {
976
        return true;
×
977
      }
978
    }
979
  }
980
  return false;
16✔
981
}
982

983
void UBoat::move_offline_mobiles( const Core::Pos4d& newpos )
×
984
{
985
  BoatContext bc( *this );
×
986

987
  for ( auto& travellerRef : travellers_ )
×
988
  {
989
    UObject* obj = travellerRef.get();
×
990

991
    if ( !obj->orphan() && on_ship( bc, obj ) && obj->ismobile() )
×
992
    {
993
      Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
×
994

995
      if ( !chr->logged_in() )
×
996
      {
997
        chr->set_dirty();
×
998
        chr->setposition( newpos );
×
999
        chr->realm_changed();  // not sure if neccessary...
×
1000
        travellerRef.clear();
×
1001
      }
1002
    }
1003
  }
1004
  remove_orphans();
×
1005
}
×
1006

1007
void UBoat::on_color_changed()
×
1008
{
1009
  send_display_boat_to_inrange( {} );
×
1010
}
×
1011

1012
bool UBoat::deck_empty() const
16✔
1013
{
1014
  return travellers_.empty();
16✔
1015
}
1016

1017
bool UBoat::hold_empty() const
16✔
1018
{
1019
  Items::Item* it = this->hold;
16✔
1020
  if ( it == nullptr )
16✔
1021
  {
1022
    return true;
1✔
1023
  }
1024
  Core::UContainer* cont = static_cast<Core::UContainer*>( it );
15✔
1025
  return ( cont->count() == 0 );
15✔
1026
}
1027

1028
void UBoat::do_tellmoves()
66✔
1029
{
1030
  // we only do tellmove here because tellmove also checks attacks.
1031
  // this way, we move everyone, then check for attacks.
1032

1033
  for ( auto& travellerRef : travellers_ )
6,102✔
1034
  {
1035
    UObject* obj = travellerRef.get();
6,036✔
1036

1037
    if ( !obj || !obj->ismobile() )
6,036✔
1038
      continue;
6,009✔
1039
    auto* chr = static_cast<Mobile::Character*>( obj );
27✔
1040
    if ( chr->isa( Core::UOBJ_CLASS::CLASS_NPC ) )
27✔
1041
    {
1042
      chr->tellmove();
8✔
1043
      continue;
8✔
1044
    }
1045
    if ( !chr->has_active_client() )
19✔
1046
      continue;
×
1047
    chr->tellmove();
19✔
1048
    auto* client = chr->client;
19✔
1049
    // with smooth movement the position of travellers is known when the displayboat pkt is send,
1050
    // new objects have to be send afterwards
1051
    if ( client->ClientType & Network::CLIENTTYPE_7090 )
19✔
1052
      Core::send_objects_newly_inrange_on_boat( client, serial );
19✔
1053
  }
1054
}
66✔
1055

1056
bool UBoat::move_to( const Core::Pos4d& newpos, int flags )
11✔
1057
{
1058
  BoatMoveGuard registerguard( this );
11✔
1059
  if ( ( flags & Core::MOVEITEM_FORCELOCATION ) || navigable( multidef(), newpos ) )
11✔
1060
  {
1061
    BoatContext bc( *this );
11✔
1062

1063
    set_dirty();
11✔
1064

1065
    setposition( newpos );
11✔
1066
    move_multi_in_world( this, bc.oldpos );
11✔
1067

1068
    move_travellers( bc );
11✔
1069
    move_components();
11✔
1070
    send_display_boat_to_inrange( bc.oldpos );
11✔
1071
    do_tellmoves();
11✔
1072
    unpause_paused();
11✔
1073

1074
    return true;
11✔
1075
  }
1076
  return false;
×
1077
}
11✔
1078

1079
bool UBoat::move( Core::UFACING dir, u8 speed, bool relative )
58✔
1080
{
1081
  BoatMoveGuard registerguard( this );
58✔
1082

1083
  Core::UFACING move_dir;
1084

1085
  if ( relative == false )
58✔
1086
    move_dir = dir;
51✔
1087
  else
1088
    move_dir = static_cast<Core::UFACING>( ( dir + boat_facing() ) & 7 );
7✔
1089

1090
  auto newpos = pos().move( move_dir );
58✔
1091

1092
  if ( !navigable( multidef(), newpos ) )
58✔
1093
    return false;
7✔
1094

1095
  BoatContext bc( *this );
51✔
1096

1097
  set_dirty();
51✔
1098

1099
  setposition( newpos );
51✔
1100
  move_multi_in_world( this, bc.oldpos );
51✔
1101
  move_travellers( bc );
51✔
1102
  move_components();
51✔
1103

1104
  Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
51✔
1105
      this,
1106
      [&]( Mobile::Character* zonechr )
51✔
1107
      {
1108
        Network::Client* client = zonechr->client;
86✔
1109
        if ( !zonechr->in_visual_range( this ) )
86✔
1110
          return;
61✔
1111
        if ( client->ClientType & Network::CLIENTTYPE_7090 )
25✔
1112
        {
1113
          if ( zonechr->in_visual_range( this, bc.oldpos ) )
25✔
1114
            send_smooth_move( client, move_dir, speed, relative );
24✔
1115
          else
1116
            send_boat_newly_inrange( client );  // send HSA packet only for newly inrange
1✔
1117
        }
1118
        else
1119
        {
1120
          if ( client->ClientType & Network::CLIENTTYPE_7000 )
×
1121
            send_boat( client );  // Send
×
1122
          else
1123
            send_boat_old( client );
×
1124
        }
1125
      } );
1126

1127
  Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
51✔
1128
      bc.oldpos,
1129
      [&]( Mobile::Character* zonechr )
51✔
1130
      {
1131
        if ( zonechr->in_visual_range( this, bc.oldpos ) &&
111✔
1132
             !zonechr->in_visual_range( this ) )  // send remove to chrs only seeing the old loc
25✔
1133
          send_remove_boat( zonechr->client );
1✔
1134
      } );
86✔
1135

1136
  do_tellmoves();
51✔
1137
  unpause_paused();
51✔
1138
  return true;
51✔
1139
}
58✔
1140

1141
inline unsigned short UBoat::multiid_ifturn( RELATIVE_DIR dir )
8✔
1142
{
1143
  return ( multiid_ & ~3u ) | ( ( multiid_ + dir ) & 3 );
8✔
1144
}
1145

1146
const MultiDef& UBoat::multi_ifturn( RELATIVE_DIR dir )
4✔
1147
{
1148
  unsigned short multiid_dir = multiid_ifturn( dir );
4✔
1149
  passert( MultiDefByMultiIDExists( multiid_dir ) );
4✔
1150
  return *MultiDefByMultiID( multiid_dir );
4✔
1151
}
1152

1153
Core::UFACING UBoat::boat_facing() const
34✔
1154
{
1155
  return static_cast<Core::UFACING>( ( multiid_ & 3 ) * 2 );
34✔
1156
}
1157

1158
const BoatShape& UBoat::boatshape() const
93✔
1159
{
1160
  passert( Core::gamestate.boatshapes.count( multiid_ ) != 0 );
93✔
1161
  return *Core::gamestate.boatshapes[multiid_];
93✔
1162
}
1163

1164

1165
void UBoat::transform_components( const BoatShape& old_boatshape )
7✔
1166
{
1167
  const BoatShape& bshape = boatshape();
7✔
1168
  auto end = Components.end();
7✔
1169
  auto end2 = bshape.Componentshapes.end();
7✔
1170
  auto old_end = old_boatshape.Componentshapes.end();
7✔
1171

1172
  auto itr = Components.begin();
7✔
1173
  auto itr2 = bshape.Componentshapes.begin();
7✔
1174
  auto old_itr = old_boatshape.Componentshapes.begin();
7✔
1175

1176
  for ( ; itr != end && itr2 != end2 && old_itr != old_end; ++itr, ++itr2, ++old_itr )
35✔
1177
  {
1178
    Items::Item* item = itr->get();
28✔
1179
    if ( item != nullptr )
28✔
1180
    {
1181
      if ( item->orphan() )
28✔
1182
        continue;
×
1183

1184
      // This should be rare enough for a simple log to be the solution. We don't want POL to
1185
      // crash in MoveItemWorldPosition() because the item was not in the world to start with, so
1186
      // we skip it.
1187
      if ( item->container != nullptr || item->has_gotten_by() )
28✔
1188
      {
1189
        u32 containerSerial = ( item->container != nullptr ) ? item->container->serial : 0;
×
1190
        POLLOG_ERRORLN(
×
1191
            "Boat component is gotten or in a container and couldn't be moved together with the "
1192
            "boat: serial {:#x}\n, graphic: {:#x}, container: {:#x}.",
1193
            item->serial, item->graphic, containerSerial );
×
1194
        continue;
×
1195
      }
×
1196

1197
      if ( item->objtype_ == Core::settingsManager.extobj.port_plank &&
35✔
1198
           item->graphic == old_itr->altgraphic )
7✔
1199
        item->graphic = itr2->altgraphic;
×
1200
      else if ( item->objtype_ == Core::settingsManager.extobj.starboard_plank &&
35✔
1201
                item->graphic == old_itr->altgraphic )
7✔
1202
        item->graphic = itr2->altgraphic;
×
1203
      else
1204
        item->graphic = itr2->graphic;
28✔
1205

1206
      move_boat_item( item, pos() + itr2->delta );
28✔
1207
    }
1208
  }
1209
}
7✔
1210

1211
void UBoat::move_components()
62✔
1212
{
1213
  const BoatShape& bshape = boatshape();
62✔
1214
  auto itr = Components.begin();
62✔
1215
  auto end = Components.end();
62✔
1216
  auto itr2 = bshape.Componentshapes.begin();
62✔
1217
  auto end2 = bshape.Componentshapes.end();
62✔
1218
  for ( ; itr != end && itr2 != end2; ++itr, ++itr2 )
310✔
1219
  {
1220
    Items::Item* item = itr->get();
248✔
1221
    if ( item != nullptr )
248✔
1222
    {
1223
      if ( item->orphan() )
248✔
1224
      {
1225
        continue;
×
1226
      }
1227

1228
      // This should be rare enough for a simple log to be the solution. We don't want POL to
1229
      // crash in MoveItemWorldPosition() because the item was not in the world to start with, so
1230
      // we skip it.
1231
      if ( item->container != nullptr || item->has_gotten_by() )
248✔
1232
      {
1233
        u32 containerSerial = ( item->container != nullptr ) ? item->container->serial : 0;
×
1234
        POLLOG_INFOLN(
×
1235
            "Boat component is gotten or in a container and couldn't be moved together with the "
1236
            "boat: serial {:#x}\n"
1237
            ", graphic: {:#x}, container: {:#x}.",
1238
            item->serial, item->graphic, containerSerial );
×
1239
        continue;
×
1240
      }
×
1241
      move_boat_item( item, pos() + itr2->delta );
248✔
1242
    }
1243
  }
1244
}
62✔
1245

1246
bool UBoat::turn( RELATIVE_DIR dir )
4✔
1247
{
1248
  BoatMoveGuard registerguard( this );
4✔
1249

1250
  const MultiDef& newmd = multi_ifturn( dir );
4✔
1251

1252
  if ( !navigable( newmd, pos() ) )
4✔
1253
    return false;
×
1254
  BoatContext bc( *this );
4✔
1255
  const BoatShape& old_boatshape = boatshape();
4✔
1256

1257
  set_dirty();
4✔
1258
  multiid_ = multiid_ifturn( dir );
4✔
1259

1260
  turn_travellers( dir, bc );
4✔
1261
  transform_components( old_boatshape );
4✔
1262
  send_display_boat_to_inrange( {} );
4✔
1263
  do_tellmoves();
4✔
1264
  unpause_paused();
4✔
1265
  facing = turn_facing( facing, dir );
4✔
1266
  return true;
4✔
1267
}
4✔
1268

1269
void UBoat::register_object( UObject* obj )
6,019✔
1270
{
1271
  if ( find( travellers_.begin(), travellers_.end(), obj ) == travellers_.end() )
6,019✔
1272
  {
1273
    set_dirty();
6,015✔
1274
    travellers_.emplace_back( obj );
6,015✔
1275
  }
1276
}
6,019✔
1277

1278
void UBoat::unregister_object( UObject* obj )
6,076✔
1279
{
1280
  Travellers::iterator this_traveller = find( travellers_.begin(), travellers_.end(), obj );
6,076✔
1281

1282
  if ( this_traveller != travellers_.end() )
6,076✔
1283
  {
1284
    set_dirty();
6,016✔
1285
    travellers_.erase( this_traveller );
6,016✔
1286
  }
1287
}
6,076✔
1288

1289
void UBoat::rescan_components()
18✔
1290
{
1291
  UPlank* plank;
1292

1293
  if ( portplank != nullptr && !portplank->orphan() )
18✔
1294
  {
1295
    plank = static_cast<UPlank*>( portplank );
17✔
1296
    plank->setboat( this );
17✔
1297
  }
1298

1299
  if ( starboardplank != nullptr && !starboardplank->orphan() )
18✔
1300
  {
1301
    plank = static_cast<UPlank*>( starboardplank );
17✔
1302
    plank->setboat( this );
17✔
1303
  }
1304
}
18✔
1305

1306
void UBoat::reread_components()
1✔
1307
{
1308
  for ( auto& component : Components )
5✔
1309
  {
1310
    if ( component == nullptr )
4✔
1311
      continue;
×
1312
    // check boat members here
1313
    if ( component->objtype_ == Core::settingsManager.extobj.tillerman && tillerman == nullptr )
4✔
1314
      tillerman = component.get();
1✔
1315
    if ( component->objtype_ == Core::settingsManager.extobj.port_plank && portplank == nullptr )
4✔
1316
      portplank = component.get();
1✔
1317
    if ( component->objtype_ == Core::settingsManager.extobj.starboard_plank &&
5✔
1318
         starboardplank == nullptr )
1✔
1319
      starboardplank = component.get();
1✔
1320
    if ( component->objtype_ == Core::settingsManager.extobj.hold && hold == nullptr )
4✔
1321
      hold = component.get();
1✔
1322
  }
1323
}
1✔
1324

1325
/// POL098 and earlier were using graphic to store MultiID, this should not be lost
1326
/// to avoid screwing up boats during conversion
1327
void UBoat::fixInvalidGraphic()
1✔
1328
{
1329
  if ( !Core::settingsManager.polvar.DataWrittenBy99OrLater )
1✔
1330
  {
1331
    passert_always_r( graphic >= 0x4000, "Unexpected boat graphic < 0x4000 in POL < 099 data" );
×
1332
    multiid_ = graphic - 0x4000;
×
1333
  }
1334
  base::fixInvalidGraphic();
1✔
1335
}
1✔
1336

1337
void UBoat::readProperties( Clib::ConfigElem& elem )
1✔
1338
{
1339
  base::readProperties( elem );
1✔
1340

1341
  BoatContext bc( *this );
1✔
1342
  u32 tmp_serial;
1343
  while ( elem.remove_prop( "Traveller", &tmp_serial ) )
2✔
1344
  {
1345
    if ( Core::IsItem( tmp_serial ) )
1✔
1346
    {
1347
      Items::Item* item = Core::find_toplevel_item( tmp_serial );
×
1348
      if ( item != nullptr )
×
1349
      {
1350
        if ( BoatShape::objtype_is_component( item->objtype_ ) )
×
1351
        {
1352
          Components.emplace_back( item );
×
1353
        }
1354
        else if ( on_ship( bc, item ) )
×
1355
        {
1356
          travellers_.emplace_back( item );
×
1357
        }
1358
      }
1359
    }
1360
    else
1361
    {
1362
      Mobile::Character* chr = Core::system_find_mobile( tmp_serial );
1✔
1363

1364
      if ( chr != nullptr )
1✔
1365
      {
1366
        if ( on_ship( bc, chr ) )
1✔
1367
          travellers_.emplace_back( chr );
1✔
1368
      }
1369
    }
1370
  }
1371
  while ( elem.remove_prop( "Component", &tmp_serial ) )
5✔
1372
  {
1373
    Items::Item* item = Core::system_find_item( tmp_serial );
4✔
1374
    if ( item != nullptr )
4✔
1375
    {
1376
      if ( BoatShape::objtype_is_component( item->objtype_ ) )
4✔
1377
      {
1378
        Components.emplace_back( item );
4✔
1379
      }
1380
    }
1381
  }
1382
  reread_components();
1✔
1383
  rescan_components();
1✔
1384

1385
  regself();  // do this after our x,y are known.
1✔
1386
  // consider throwing if starting position isn't passable.
1387

1388
  auto control_script = itemdesc().control_script.empty() ? Core::ScriptDef( "misc/boat", nullptr )
3✔
1389
                                                          : itemdesc().control_script;
2✔
1390

1391
  auto prog = Core::find_script2( control_script );
1✔
1392

1393
  if ( prog.get() == nullptr )
1✔
1394
  {
1395
    POLLOG_ERRORLN( "Could not start script {}, boat: serial {:#x}", control_script.c_str(),
×
1396
                    this->serial );
×
1397
    return;
×
1398
  }
1399

1400
  Module::UOExecutorModule* script = Core::start_script( prog, make_boatref( this ) );
1✔
1401

1402
  if ( script == nullptr )
1✔
1403
  {
1404
    POLLOG_ERRORLN( "Could not start script {}, boat: serial {:#x}", control_script.c_str(),
×
1405
                    this->serial );
×
1406
  }
1407
  else
1408
  {
1409
    this->process( script );
1✔
1410
    this->process()->attached_item_.set( this );
1✔
1411
  }
1412
}
1✔
1413

1414
void UBoat::printProperties( Clib::StreamWriter& sw ) const
2✔
1415
{
1416
  base::printProperties( sw );
2✔
1417

1418
  sw.add( "MultiID", multiid_ );
2✔
1419

1420
  BoatContext bc( *this );
2✔
1421

1422
  for ( auto& travellerRef : travellers_ )
4✔
1423
  {
1424
    UObject* obj = travellerRef.get();
2✔
1425
    if ( !obj->orphan() && on_ship( bc, obj ) )
2✔
1426
    {
1427
      sw.add( "Traveller", Clib::hexintv( obj->serial ) );
2✔
1428
    }
1429
  }
1430
  for ( auto& component : Components )
10✔
1431
  {
1432
    if ( component != nullptr && !component->orphan() )
8✔
1433
    {
1434
      sw.add( "Component", Clib::hexintv( component->serial ) );
8✔
1435
    }
1436
  }
1437
}
2✔
1438

1439
Bscript::BObjectImp* UBoat::scripted_create( const Items::ItemDesc& descriptor,
19✔
1440
                                             const Core::Pos4d& pos, int flags )
1441
{
1442
  unsigned short multiid = descriptor.multiid;
19✔
1443
  unsigned short multiid_offset =
19✔
1444
      static_cast<unsigned short>( ( flags & CRMULTI_FACING_MASK ) >> CRMULTI_FACING_SHIFT );
19✔
1445
  unsigned char facing = static_cast<unsigned char>( multiid_offset * 2 );
19✔
1446
  multiid += multiid_offset;
19✔
1447

1448
  const MultiDef* md = MultiDefByMultiID( multiid );
19✔
1449
  if ( md == nullptr )
19✔
1450
  {
1451
    return new Bscript::BError(
1452
        "Multi definition not found for Boat, objtype=" + Clib::hexint( descriptor.objtype ) +
×
1453
        ", multiid=" + Clib::hexint( multiid ) );
×
1454
  }
1455
  if ( !Core::gamestate.boatshapes.count( descriptor.multiid ) )
19✔
1456
  {
1457
    return new Bscript::BError(
1458
        "No boatshape for Boat in boats.cfg, objtype=" + Clib::hexint( descriptor.objtype ) +
×
1459
        ", multiid=" + Clib::hexint( multiid ) );
×
1460
  }
1461

1462
  if ( !navigable( *md, pos ) )
19✔
1463
  {
1464
    return new Bscript::BError( "Position indicated is impassable" );
2✔
1465
  }
1466

1467
  UBoat* boat = new UBoat( descriptor );
17✔
1468
  boat->multiid_ = multiid;
17✔
1469
  boat->serial = Core::GetNewItemSerialNumber();
17✔
1470
  boat->serial_ext = ctBEu32( boat->serial );
17✔
1471
  boat->setposition( pos );
17✔
1472
  boat->facing = facing;
17✔
1473
  add_multi_to_world( boat );
17✔
1474
  boat->send_display_boat_to_inrange( {} );
17✔
1475
  boat->create_components();
17✔
1476
  boat->rescan_components();
17✔
1477
  unpause_paused();
17✔
1478
  boat->regself();
17✔
1479

1480
  ////hash
1481
  Core::objStorageManager.objecthash.Insert( boat );
17✔
1482
  ////
1483

1484
  Bscript::BObjectImp* boatref = make_boatref( boat );
17✔
1485

1486
  auto control_script = descriptor.control_script.empty() ? Core::ScriptDef( "misc/boat", nullptr )
47✔
1487
                                                          : descriptor.control_script;
32✔
1488

1489
  auto prog = Core::find_script2( control_script );
17✔
1490

1491
  if ( prog.get() == nullptr )
17✔
1492
  {
1493
    POLLOG_ERRORLN( "Could not start script {}, boat: serial {:#x}", control_script.c_str(),
1✔
1494
                    boat->serial );
1✔
1495

1496
    return boatref;
1✔
1497
  }
1498

1499
  Module::UOExecutorModule* script = Core::start_script( prog, boatref );
16✔
1500

1501
  if ( script == nullptr )
16✔
1502
  {
1503
    POLLOG_ERRORLN( "Could not start script {}, boat: serial {:#x}", control_script.c_str(),
×
1504
                    boat->serial );
×
1505
  }
1506
  else
1507
  {
1508
    boat->process( script );
16✔
1509
    boat->process()->attached_item_.set( boat );
16✔
1510
  }
1511

1512
  return boatref;
16✔
1513
}
17✔
1514

1515
void UBoat::create_components()
17✔
1516
{
1517
  const BoatShape& bshape = boatshape();
17✔
1518
  for ( std::vector<BoatShape::ComponentShape>::const_iterator itr = bshape.Componentshapes.begin(),
34✔
1519
                                                               end = bshape.Componentshapes.end();
17✔
1520
        itr != end; ++itr )
81✔
1521
  {
1522
    Items::Item* component;
1523
    try
1524
    {
1525
      component = Items::Item::create( itr->objtype );
64✔
1526
    }
1527
    catch ( ... )
×
1528
    {
1529
      continue;
×
1530
    }
×
1531
    if ( component == nullptr )
64✔
1532
      continue;
×
1533
    // check boat members here
1534
    if ( component->objtype_ == Core::settingsManager.extobj.tillerman && tillerman == nullptr )
64✔
1535
      tillerman = component;
16✔
1536
    if ( component->objtype_ == Core::settingsManager.extobj.port_plank && portplank == nullptr )
64✔
1537
      portplank = component;
16✔
1538
    if ( component->objtype_ == Core::settingsManager.extobj.starboard_plank &&
64✔
1539
         starboardplank == nullptr )
16✔
1540
      starboardplank = component;
16✔
1541
    if ( component->objtype_ == Core::settingsManager.extobj.hold && hold == nullptr )
64✔
1542
      hold = component;
16✔
1543

1544
    component->graphic = itr->graphic;
64✔
1545
    // component itemdesc entries generally have graphic=1, so they don't get their height set.
1546
    component->height = Plib::tileheight( component->graphic );
64✔
1547
    component->setposition( pos() + itr->delta );
64✔
1548
    component->disable_decay();
64✔
1549
    component->movable( false );
64✔
1550
    add_item_to_world( component );
64✔
1551
    update_item_to_inrange( component );
64✔
1552
    Components.emplace_back( component );
64✔
1553
  }
1554
}
17✔
1555

1556
Bscript::BObjectImp* UBoat::items_list() const
3✔
1557
{
1558
  BoatContext bc( *this );
3✔
1559
  Bscript::ObjArray* arr = new Bscript::ObjArray;
3✔
1560

1561
  for ( const auto& travellerRef : travellers_ )
12,007✔
1562
  {
1563
    UObject* obj = travellerRef.get();
12,004✔
1564
    if ( !obj->orphan() && on_ship( bc, obj ) && Core::IsItem( obj->serial ) )
12,004✔
1565
    {
1566
      Item* item = static_cast<Item*>( obj );
12,001✔
1567
      arr->addElement( make_itemref( item ) );
12,001✔
1568
    }
1569
  }
1570
  return arr;
3✔
1571
}
1572

1573
Bscript::BObjectImp* UBoat::mobiles_list() const
4✔
1574
{
1575
  BoatContext bc( *this );
4✔
1576
  Bscript::ObjArray* arr = new Bscript::ObjArray;
4✔
1577
  for ( const auto& travellerRef : travellers_ )
8✔
1578
  {
1579
    UObject* obj = travellerRef.get();
4✔
1580
    if ( !obj->orphan() && on_ship( bc, obj ) && obj->ismobile() )
4✔
1581
    {
1582
      Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
4✔
1583
      if ( chr->logged_in() )
4✔
1584
        arr->addElement( make_mobileref( chr ) );
4✔
1585
    }
1586
  }
1587
  return arr;
4✔
1588
}
1589

1590
Bscript::BObjectImp* UBoat::component_list( unsigned char type ) const
1✔
1591
{
1592
  Bscript::ObjArray* arr = new Bscript::ObjArray;
1✔
1593
  for ( const auto& component : Components )
5✔
1594
  {
1595
    if ( component != nullptr && !component->orphan() )
4✔
1596
    {
1597
      if ( type == COMPONENT_ALL )
4✔
1598
      {
1599
        arr->addElement( component->make_ref() );
4✔
1600
      }
1601
      else
1602
      {
1603
        if ( component->objtype_ == get_component_objtype( type ) )
×
1604
          arr->addElement( component->make_ref() );
×
1605
      }
1606
    }
1607
  }
1608
  return arr;
1✔
1609
}
1610

1611
void UBoat::destroy_components()
16✔
1612
{
1613
  for ( auto& component : Components )
76✔
1614
  {
1615
    if ( component != nullptr && !component->orphan() )
60✔
1616
    {
1617
      Core::destroy_item( component.get() );
60✔
1618
    }
1619
  }
1620
  Components.clear();
16✔
1621
}
16✔
1622

1623
size_t UBoat::estimatedSize() const
×
1624
{
1625
  size_t size = base::estimatedSize() + sizeof( Items::Item* ) /*tillerman*/
×
1626
                + sizeof( Items::Item* )                       /*portplank*/
1627
                + sizeof( Items::Item* )                       /*starboardplank*/
1628
                + sizeof( Items::Item* )                       /*hold*/
1629
                // no estimateSize here element is in objhash
1630
                + Clib::memsize( travellers_ ) + Clib::memsize( Components );
×
1631
  return size;
×
1632
}
1633

1634
bool UBoat::get_method_hook( const char* methodname, Bscript::Executor* ex,
1✔
1635
                             Core::ExportScript** hook, unsigned int* PC ) const
1636
{
1637
  if ( Core::gamestate.system_hooks.get_method_hook(
1✔
1638
           Core::gamestate.system_hooks.boat_method_script.get(), methodname, ex, hook, PC ) )
1639
    return true;
1✔
1640
  return base::get_method_hook( methodname, ex, hook, PC );
×
1641
}
1642

1643
Bscript::BObjectImp* destroy_boat( UBoat* boat )
16✔
1644
{
1645
  boat->cleanup_deck();
16✔
1646

1647
  if ( !boat->hold_empty() )
16✔
1648
    return new Bscript::BError( "There is cargo in the ship's hold" );
×
1649
  if ( boat->has_offline_mobiles() )
16✔
1650
    return new Bscript::BError( "There are logged-out characters on the deck" );
×
1651
  if ( !boat->deck_empty() )
16✔
1652
    return new Bscript::BError( "The deck is not empty" );
×
1653

1654
  boat->destroy_components();
16✔
1655
  boat->unregself();
16✔
1656

1657
  Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
16✔
1658
      boat,
1659
      [&]( Mobile::Character* zonechr )
16✔
1660
      {
1661
        if ( zonechr->in_visual_range( boat ) )
5✔
1662
          Core::send_remove_object( zonechr->client, boat );
2✔
1663
      } );
5✔
1664
  remove_multi_from_world( boat );
16✔
1665
  boat->destroy();
16✔
1666
  return new Bscript::BLong( 1 );
16✔
1667
}
1668

1669
Mobile::Character* UBoat::pilot() const
6✔
1670
{
1671
  if ( mountpiece != nullptr && !mountpiece->orphan() )
6✔
1672
  {
1673
    return mountpiece->GetCharacterOwner();
5✔
1674
  }
1675
  return nullptr;
1✔
1676
}
1677

1678
Bscript::BObjectImp* UBoat::set_pilot( Mobile::Character* chr )
10✔
1679
{
1680
  if ( chr == nullptr )
10✔
1681
  {
1682
    clear_pilot();
6✔
1683
    return new Bscript::BLong( 1 );
6✔
1684
  }
1685

1686
  if ( mountpiece != nullptr && !mountpiece->orphan() )
4✔
1687
  {
1688
    return new Bscript::BError( "The boat is already being piloted." );
1✔
1689
  }
1690

1691
  if ( !has_process() )
3✔
1692
  {
NEW
1693
    return new Bscript::BError( "The boat does not have a running process." );
×
1694
  }
1695

1696
  if ( !chr->client )
3✔
1697
  {
NEW
1698
    return new Bscript::BError( "That character is not connected." );
×
1699
  }
1700

1701
  if ( !( chr->client->ClientType & Network::CLIENTTYPE_7090 ) )
3✔
1702
  {
1703
    return new Bscript::BError(
NEW
1704
        "The client for that character does not support High Seas Adventure." );
×
1705
  }
1706

1707
  BoatContext bc( *this );
3✔
1708
  bool pilot_on_ship = false;
3✔
1709
  for ( const auto& travellerRef : travellers_ )
3✔
1710
  {
1711
    UObject* obj = travellerRef.get();
3✔
1712
    if ( !obj->orphan() && on_ship( bc, obj ) && obj == chr )
3✔
1713
    {
1714
      pilot_on_ship = true;
3✔
1715
      break;
3✔
1716
    }
1717
  }
1718

1719
  if ( !pilot_on_ship )
3✔
1720
  {
NEW
1721
    return new Bscript::BError( "The boat does not have that character on it." );
×
1722
  }
1723

1724
  Items::Item* item = Items::Item::create( Core::settingsManager.extobj.boatmount );
3✔
1725
  if ( !chr->equippable( item ) )
3✔
1726
  {
NEW
1727
    item->destroy();
×
NEW
1728
    return new Bscript::BError( "The boat mount piece is not equippable by that character." );
×
1729
  }
1730
  chr->equip( item );
3✔
1731
  send_wornitem_to_inrange( chr, item );
3✔
1732
  mountpiece = Core::ItemRef( item );
3✔
1733

1734
  // Mark the item as 'in-use' to prevent moving by client or scripts.
1735
  item->inuse( true );
3✔
1736

1737
  return new Bscript::BLong( 1 );
3✔
1738
}
1739

1740
void UBoat::clear_pilot()
6✔
1741
{
1742
  if ( mountpiece != nullptr )
6✔
1743
  {
1744
    if ( !mountpiece->orphan() )
3✔
1745
    {
1746
      destroy_item( mountpiece.get() );
3✔
1747
    }
1748
    mountpiece.clear();
3✔
1749
  }
1750
}
6✔
1751
}  // 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