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

polserver / polserver / 25939934106

15 May 2026 08:30PM UTC coverage: 60.882% (-0.05%) from 60.929%
25939934106

push

github

turleypol
apply damage for items + evid_damaged

0 of 36 new or added lines in 2 files covered. (0.0%)

1083 existing lines in 14 files now uncovered.

44698 of 73417 relevant lines covered (60.88%)

524562.92 hits per line

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

76.67
/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✔
156
  if ( objtype == Core::settingsManager.extobj.rope )
×
157
    return true;
×
158
  if ( objtype == Core::settingsManager.extobj.wheel )
×
159
    return true;
×
160
  if ( objtype == Core::settingsManager.extobj.hull )
×
161
    return true;
×
162
  if ( objtype == Core::settingsManager.extobj.tiller )
×
163
    return true;
×
164
  if ( objtype == Core::settingsManager.extobj.rudder )
×
165
    return true;
×
166
  if ( objtype == Core::settingsManager.extobj.sails )
×
167
    return true;
×
168
  if ( objtype == Core::settingsManager.extobj.storage )
×
169
    return true;
×
170
  if ( objtype == Core::settingsManager.extobj.weaponslot )
×
171
    return true;
×
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>( 0xF3_u8 );
32✔
373
    msg->WriteFlipped<u16>( 0x1_u16 );
32✔
374
    msg->Write<u8>( component->is_attackable() ? 0x3_u8 : 0x0_u8 );  // 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>( 0xF3_u8 );
8✔
421
    msg->WriteFlipped<u16>( 0x1_u16 );
8✔
422

423
    if ( obj->ismobile() )
8✔
424
      msg->Write<u8>( 0x1_u8 );  // CharData flag
8✔
425
    else
426
    {
UNCOV
427
      auto* item = static_cast<Items::Item*>( obj );
×
UNCOV
428
      msg->Write<u8>( item->is_attackable() ? 0x3_u8 : 0x0_u8 );  // ItemData flag
×
429
    }
430

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

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

447
    msg->WriteFlipped<u16>( obj->x() );
8✔
448
    msg->WriteFlipped<u16>( obj->y() );
8✔
449
    msg->Write<s8>( obj->z() );
8✔
450
    msg->Write<u8>( obj->facing );
8✔
451
    msg->WriteFlipped<u16>( obj->color );
8✔
452

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

463
  msg.Send( client, len );
10✔
464
}
10✔
465

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

483
  msg.Send( client );
1✔
484

485
  for ( auto& travellerRef : travellers_ )
1✔
486
  {
UNCOV
487
    UObject* obj = travellerRef.get();
×
488

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

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

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

521
        if ( client->ClientType & Network::CLIENTTYPE_7090 )
10✔
522
          send_display_boat( client );
10✔
523
        else if ( client->ClientType & Network::CLIENTTYPE_7000 )
×
UNCOV
524
          send_boat( client );
×
525
        else
UNCOV
526
          send_boat_old( client );
×
527
      } );
528

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

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

561
  msg2.Send( client );
×
UNCOV
562
}
×
563

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

578
  client->pause();
×
UNCOV
579
  msg.Send( client, len1A );
×
UNCOV
580
  boat_sent_to.push_back( client );
×
UNCOV
581
}
×
582

583
void UBoat::send_remove_boat( Network::Client* client )
1✔
584
{
585
  send_remove_object( client, this );
1✔
586
}
1✔
587

588
void unpause_paused()
86✔
589
{
590
  for ( Network::Client* client : boat_sent_to )
86✔
591
  {
UNCOV
592
    client->restart();
×
593
  }
594
  boat_sent_to.clear();
86✔
595
}
86✔
596

597

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

609
UBoat* UBoat::as_boat()
20✔
610
{
611
  return this;
20✔
612
}
613

614
bool UBoat::objtype_passable( unsigned short objtype )
5,521✔
615
{
616
  if ( Core::settingsManager.ssopt.boat_sails_collide )
5,521✔
617
    return false;
8✔
618

619
  auto uoflags = Plib::systemstate.tile[objtype].uoflags;
5,513✔
620
  bool passable = ( uoflags & Plib::USTRUCT_TILE::FLAG_FOLIAGE ) &&
6,109✔
621
                  ( ~uoflags & Plib::USTRUCT_TILE::FLAG_BLOCKING );
596✔
622
  return passable;
5,513✔
623
}
624

625
/* boats are a bit strange - they can move from side to side, forwards and
626
   backwards, without turning.
627
   */
628
void UBoat::regself()
97✔
629
{
630
  const MultiDef& md = multidef();
97✔
631
  for ( const auto& ele : md.hull )
2,033✔
632
  {
633
    if ( objtype_passable( ele->objtype ) )
1,936✔
634
      continue;
208✔
635

636
    Core::Pos2d hullpos = pos2d() + ele->relpos.xy();
1,728✔
637

638
    unsigned int gh = realm()->encode_global_hull( hullpos );
1,728✔
639
    realm()->global_hulls.insert( gh );
1,728✔
640
  }
641
}
97✔
642

643
void UBoat::unregself()
95✔
644
{
645
  const MultiDef& md = multidef();
95✔
646
  for ( const auto& ele : md.hull )
1,991✔
647
  {
648
    if ( objtype_passable( ele->objtype ) )
1,896✔
649
      continue;
204✔
650

651
    Core::Pos2d hullpos = pos2d() + ele->relpos.xy();
1,692✔
652

653
    unsigned int gh = realm()->encode_global_hull( hullpos );
1,692✔
654
    realm()->global_hulls.erase( gh );
1,692✔
655
  }
656
}
95✔
657

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

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

676
  /* Test the external hull to make sure it's on water */
677

678
  for ( const auto& ele : md.hull )
1,772✔
679
  {
680
    Core::Pos3d hullpos = desired_pos.xyz() + ele->relpos;
1,689✔
681
#ifdef DEBUG_BOATS
682
    INFO_PRINT( "[{}]", hullpos );
683
#endif
684

685
    if ( objtype_passable( ele->objtype ) )
1,689✔
686
      continue;
184✔
687

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

701
    if ( !desired_pos.realm()->navigable( hullpos, Plib::systemstate.tile[ele->objtype].height ) )
1,505✔
702
      return false;
6✔
703
  }
704

705
  return true;
83✔
706
}
707

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

721
  return bc.mdef.body_contains( rxy );
18,050✔
722
}
723

724
void UBoat::move_travellers( const BoatContext& oldlocation )
62✔
725
{
726
  bool any_orphans = false;
62✔
727

728
  for ( auto& travellerRef : travellers_ )
6,090✔
729
  {
730
    UObject* obj = travellerRef.get();
6,028✔
731

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

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

754
      if ( Core::settingsManager.ssopt.refresh_decay_after_boat_moves )
6,005✔
755
        item->restart_decay_timer();
6,005✔
756
    }
757
  }
758

759
  if ( any_orphans )
62✔
UNCOV
760
    remove_orphans();
×
761
}
62✔
762

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

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

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

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

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

816
  chr->setposition( newpos );
27✔
817
  chr->lastpos = oldpos;
27✔
818
  if ( oldpos.realm() != newpos.realm() )
27✔
819
    chr->realm_changed();
2✔
820

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

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

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

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

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

888
void UBoat::turn_travellers( RELATIVE_DIR dir, const BoatContext& oldlocation )
4✔
889
{
890
  bool any_orphans = false;
4✔
891

892
  for ( auto& travellerRef : travellers_ )
12✔
893
  {
894
    UObject* obj = travellerRef.get();
8✔
895

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

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

916
      move_boat_item( item, newpos );
4✔
917
      if ( Core::settingsManager.ssopt.refresh_decay_after_boat_moves )
4✔
918
        item->restart_decay_timer();
4✔
919
    }
920
  }
921

922
  if ( any_orphans )
4✔
UNCOV
923
    remove_orphans();
×
924
}
4✔
925

926
void UBoat::remove_orphans()
19✔
927
{
928
  bool any_orphan_travellers;
929
  do
19✔
930
  {
931
    any_orphan_travellers = false;
19✔
932

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

948
void UBoat::cleanup_deck()
19✔
949
{
950
  BoatContext bc( *this );
19✔
951

952
  for ( auto& travellerRef : travellers_ )
19✔
953
  {
UNCOV
954
    UObject* obj = travellerRef.get();
×
955

956
    if ( obj->orphan() || !on_ship( bc, obj ) )
×
957
    {
UNCOV
958
      set_dirty();
×
UNCOV
959
      travellerRef.clear();
×
960
    }
961
  }
962
  remove_orphans();
19✔
963
}
19✔
964

965
bool UBoat::has_offline_mobiles() const
19✔
966
{
967
  BoatContext bc( *this );
19✔
968

969
  for ( const auto& travellerRef : travellers_ )
19✔
970
  {
UNCOV
971
    UObject* obj = travellerRef.get();
×
972

UNCOV
973
    if ( !obj->orphan() && on_ship( bc, obj ) && obj->ismobile() )
×
974
    {
UNCOV
975
      Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
×
976

UNCOV
977
      if ( !chr->logged_in() )
×
978
      {
UNCOV
979
        return true;
×
980
      }
981
    }
982
  }
983
  return false;
19✔
984
}
985

UNCOV
986
void UBoat::move_offline_mobiles( const Core::Pos4d& newpos )
×
987
{
UNCOV
988
  BoatContext bc( *this );
×
989

UNCOV
990
  for ( auto& travellerRef : travellers_ )
×
991
  {
UNCOV
992
    UObject* obj = travellerRef.get();
×
993

UNCOV
994
    if ( !obj->orphan() && on_ship( bc, obj ) && obj->ismobile() )
×
995
    {
UNCOV
996
      Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
×
997

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

1010
void UBoat::on_color_changed()
×
1011
{
UNCOV
1012
  send_display_boat_to_inrange( {} );
×
UNCOV
1013
}
×
1014

1015
bool UBoat::deck_empty() const
19✔
1016
{
1017
  return travellers_.empty();
19✔
1018
}
1019

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

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

1036
  for ( auto& travellerRef : travellers_ )
6,102✔
1037
  {
1038
    UObject* obj = travellerRef.get();
6,036✔
1039

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

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

1066
    set_dirty();
11✔
1067

1068
    setposition( newpos );
11✔
1069
    move_multi_in_world( this, bc.oldpos );
11✔
1070

1071
    move_travellers( bc );
11✔
1072
    move_components();
11✔
1073
    send_display_boat_to_inrange( bc.oldpos );
11✔
1074
    do_tellmoves();
11✔
1075
    unpause_paused();
11✔
1076

1077
    return true;
11✔
1078
  }
UNCOV
1079
  return false;
×
1080
}
11✔
1081

1082
bool UBoat::move( Core::UFACING dir, u8 speed, bool relative )
58✔
1083
{
1084
  BoatMoveGuard registerguard( this );
58✔
1085

1086
  Core::UFACING move_dir;
1087

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

1093
  auto newpos = pos().move( move_dir );
58✔
1094

1095
  if ( !navigable( multidef(), newpos ) )
58✔
1096
    return false;
7✔
1097

1098
  BoatContext bc( *this );
51✔
1099

1100
  set_dirty();
51✔
1101

1102
  setposition( newpos );
51✔
1103
  move_multi_in_world( this, bc.oldpos );
51✔
1104
  move_travellers( bc );
51✔
1105
  move_components();
51✔
1106

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

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

1139
  do_tellmoves();
51✔
1140
  unpause_paused();
51✔
1141
  return true;
51✔
1142
}
58✔
1143

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

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

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

1161
const BoatShape& UBoat::boatshape() const
96✔
1162
{
1163
  passert( Core::gamestate.boatshapes.count( multiid_ ) != 0 );
96✔
1164
  return *Core::gamestate.boatshapes[multiid_];
96✔
1165
}
1166

1167

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

1175
  auto itr = Components.begin();
7✔
1176
  auto itr2 = bshape.Componentshapes.begin();
7✔
1177
  auto old_itr = old_boatshape.Componentshapes.begin();
7✔
1178

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

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

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

1209
      move_boat_item( item, pos() + itr2->delta );
28✔
1210
    }
1211
  }
1212
}
7✔
1213

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

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

1249
bool UBoat::turn( RELATIVE_DIR dir )
4✔
1250
{
1251
  BoatMoveGuard registerguard( this );
4✔
1252

1253
  const MultiDef& newmd = multi_ifturn( dir );
4✔
1254

1255
  if ( !navigable( newmd, pos() ) )
4✔
UNCOV
1256
    return false;
×
1257
  BoatContext bc( *this );
4✔
1258
  const BoatShape& old_boatshape = boatshape();
4✔
1259

1260
  set_dirty();
4✔
1261
  multiid_ = multiid_ifturn( dir );
4✔
1262

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

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

1281
void UBoat::unregister_object( UObject* obj )
6,088✔
1282
{
1283
  Travellers::iterator this_traveller = find( travellers_.begin(), travellers_.end(), obj );
6,088✔
1284

1285
  if ( this_traveller != travellers_.end() )
6,088✔
1286
  {
1287
    set_dirty();
6,016✔
1288
    travellers_.erase( this_traveller );
6,016✔
1289
  }
1290
}
6,088✔
1291

1292
void UBoat::rescan_components()
21✔
1293
{
1294
  UPlank* plank;
1295

1296
  if ( portplank != nullptr && !portplank->orphan() )
21✔
1297
  {
1298
    plank = static_cast<UPlank*>( portplank );
20✔
1299
    plank->setboat( this );
20✔
1300
  }
1301

1302
  if ( starboardplank != nullptr && !starboardplank->orphan() )
21✔
1303
  {
1304
    plank = static_cast<UPlank*>( starboardplank );
20✔
1305
    plank->setboat( this );
20✔
1306
  }
1307
}
21✔
1308

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

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

1340
void UBoat::readProperties( Clib::ConfigElem& elem )
1✔
1341
{
1342
  base::readProperties( elem );
1✔
1343

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

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

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

1391
  auto control_script = itemdesc().control_script.empty() ? Core::ScriptDef( "misc/boat", nullptr )
3✔
1392
                                                          : itemdesc().control_script;
2✔
1393

1394
  auto prog = Core::find_script2( control_script );
1✔
1395

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

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

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

1417
void UBoat::printProperties( Clib::StreamWriter& sw ) const
2✔
1418
{
1419
  base::printProperties( sw );
2✔
1420

1421
  sw.add( "MultiID", multiid_ );
2✔
1422

1423
  BoatContext bc( *this );
2✔
1424

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

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

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

1465
  if ( !navigable( *md, pos ) )
22✔
1466
  {
1467
    return new Bscript::BError( "Position indicated is impassable" );
2✔
1468
  }
1469

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

1483
  ////hash
1484
  Core::objStorageManager.objecthash.Insert( boat );
20✔
1485
  ////
1486

1487
  Bscript::BObjectImp* boatref = make_boatref( boat );
20✔
1488

1489
  auto control_script = descriptor.control_script.empty() ? Core::ScriptDef( "misc/boat", nullptr )
56✔
1490
                                                          : descriptor.control_script;
38✔
1491

1492
  auto prog = Core::find_script2( control_script );
20✔
1493

1494
  if ( prog.get() == nullptr )
20✔
1495
  {
1496
    POLLOG_ERRORLN( "Could not start script {}, boat: serial {:#x}", control_script.c_str(),
1✔
1497
                    boat->serial );
1✔
1498

1499
    return boatref;
1✔
1500
  }
1501

1502
  Module::UOExecutorModule* script = Core::start_script( prog, boatref );
19✔
1503

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

1515
  return boatref;
19✔
1516
}
20✔
1517

1518
void UBoat::create_components()
20✔
1519
{
1520
  const BoatShape& bshape = boatshape();
20✔
1521
  for ( const auto& componentshape : bshape.Componentshapes )
96✔
1522
  {
1523
    Items::Item* component;
1524
    try
1525
    {
1526
      component = Items::Item::create( componentshape.objtype );
76✔
1527
    }
1528
    catch ( ... )
×
1529
    {
1530
      continue;
×
UNCOV
1531
    }
×
1532
    if ( component == nullptr )
76✔
UNCOV
1533
      continue;
×
1534
    // check boat members here
1535
    if ( component->objtype_ == Core::settingsManager.extobj.tillerman && tillerman == nullptr )
76✔
1536
      tillerman = component;
19✔
1537
    if ( component->objtype_ == Core::settingsManager.extobj.port_plank && portplank == nullptr )
76✔
1538
      portplank = component;
19✔
1539
    if ( component->objtype_ == Core::settingsManager.extobj.starboard_plank &&
76✔
1540
         starboardplank == nullptr )
19✔
1541
      starboardplank = component;
19✔
1542
    if ( component->objtype_ == Core::settingsManager.extobj.hold && hold == nullptr )
76✔
1543
      hold = component;
19✔
1544

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

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

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

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

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

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

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

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

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

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

1655
  boat->destroy_components();
19✔
1656
  boat->unregself();
19✔
1657

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

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

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

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

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

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

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

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

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

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

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

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

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