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

polserver / polserver / 13484236498

23 Feb 2025 03:06PM UTC coverage: 58.838% (+0.06%) from 58.783%
13484236498

push

github

web-flow
Modulus operations with doubles (#769)

* implemented modulus operator % and %= for doubles
int%dbl=dbl
dbl%int=dbl
dbl%dbl=dbl
...

* added missing test for ins_set_member_id_consume_divideequal

* more tests
docs

26 of 26 new or added lines in 2 files covered. (100.0%)

10 existing lines in 3 files now uncovered.

42342 of 71964 relevant lines covered (58.84%)

385815.91 hits per line

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

76.72
/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

19
#include "boat.h"
20

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

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

65

66
namespace Pol
67
{
68
namespace Multi
69
{
70
// #define DEBUG_BOATS
71

72
std::vector<Network::Client*> boat_sent_to;
73

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

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

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

116

117
BoatShape::BoatShape( Clib::ConfigElem& elem )
48✔
118
{
119
  std::string tmp_str;
48✔
120

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

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

179
size_t BoatShape::estimateSize() const
16✔
180
{
181
  return Clib::memsize( Componentshapes );
16✔
182
}
183

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

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

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

248
bool BoatShapeExists( u16 multiid )
36✔
249
{
250
  return Core::gamestate.boatshapes.count( multiid ) != 0;
36✔
251
}
252

253
void UBoat::send_smooth_move( Network::Client* client, Core::UFACING move_dir, u8 speed,
6✔
254
                              bool relative ) const
255
{
256
  Network::PktHelper::PacketOut<Network::PktOut_F6> msg;
6✔
257

258
  Core::UFACING b_facing = boat_facing();
6✔
259
  if ( relative == false )
6✔
260
    move_dir = static_cast<Core::UFACING>( ( b_facing + move_dir ) * 7 );
6✔
261

262
  msg->offset += 2;  // Length
6✔
263
  msg->Write<u32>( serial_ext );
6✔
264
  msg->Write<u8>( speed );
6✔
265

266
  msg->Write<u8>( move_dir );
6✔
267
  msg->Write<u8>( b_facing );
6✔
268

269
  msg->WriteFlipped<u16>( x() );
6✔
270
  msg->WriteFlipped<u16>( y() );
6✔
271
  msg->WriteFlipped<s16>( z() );
6✔
272

273
  // 0xe000 encoding room, huffman can take more space then the maximum of 0xffff (10% + extra
274
  // margin)
275
  const u16 max_count = ( 0xe000 - 18 ) / 10;
6✔
276
  u16 object_count = 0;
6✔
277
  u16 len_offset = msg->offset;
6✔
278
  msg->offset += 2;  // Length
6✔
279
  for ( auto& component : Components )
30✔
280
  {
281
    if ( object_count >= max_count )
24✔
282
    {
283
      POLLOG_INFOLN( "Boat {:#x} at {} with {} items is too full - truncating movement packet",
×
284
                     serial, pos(), travellers_.size() );
×
285
      break;
×
286
    }
287
    if ( component == nullptr || component->orphan() )
24✔
288
      continue;
×
289
    msg->Write<u32>( component->serial_ext );
24✔
290
    msg->WriteFlipped<u16>( component->x() );
24✔
291
    msg->WriteFlipped<u16>( component->y() );
24✔
292
    msg->WriteFlipped<s16>( component->z() );
24✔
293
    ++object_count;
24✔
294
  }
295
  for ( auto& travellerRef : travellers_ )
5,736✔
296
  {
297
    if ( object_count >= max_count )
5,731✔
298
    {
299
      POLLOG_INFOLN( "Boat {:#x} at {} with {} items is too full - truncating movement packet",
2✔
300
                     serial, pos(), travellers_.size() );
1✔
301
      break;
1✔
302
    }
303
    UObject* obj = travellerRef.get();
5,730✔
304

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

331
  msg.Send( client, len );
6✔
332
}
6✔
333

334
void UBoat::send_display_boat( Network::Client* client )
9✔
335
{
336
  Network::PktHelper::PacketOut<Network::PktOut_F7> msg;
9✔
337

338
  msg->offset += 2;  // Length
9✔
339

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

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

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

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

423
    msg->Write<u8>( 0xF3u );
7✔
424
    msg->WriteFlipped<u16>( 0x1u );
7✔
425

426
    if ( obj->ismobile() )
7✔
427
      msg->Write<u8>( 0x1u );  // CharData flag
7✔
428
    else
429
      msg->Write<u8>( 0x0u );  // ItemData flag
×
430

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

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

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

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

463
  msg.Send( client, len );
9✔
464
}
9✔
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
  {
487
    UObject* obj = travellerRef.get();
×
488

489
    if ( !obj->orphan() )
×
490
    {
491
      if ( obj->ismobile() )
×
492
      {
493
        Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
×
494
        send_owncreate( client, chr );
×
495
      }
496
      else
497
      {
498
        Items::Item* item = static_cast<Items::Item*>( obj );
×
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 )
34✔
512
{
513
  Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
34✔
514
      this,
515
      [&]( Mobile::Character* zonechr )
34✔
516
      {
517
        Network::Client* client = zonechr->client;
26✔
518
        if ( !zonechr->in_visual_range( this ) )
26✔
519
          return;
17✔
520

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

529
  if ( oldpos )
34✔
530
  {
531
    Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
10✔
532
        oldpos.value(),
533
        [&]( Mobile::Character* zonechr )
20✔
534
        {
535
          if ( !zonechr->in_visual_range( this, oldpos.value() ) )
14✔
536
            return;
10✔
537
          if ( !zonechr->in_visual_range( this ) )  // send remove to chrs only seeing the old loc
4✔
538
            send_remove_boat( zonechr->client );
×
539
        } );
540
  }
541
}
34✔
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() );
×
557
  msg2->offset++;                          // u8 facing
×
558
  msg2->WriteFlipped<u16>( this->color );  // u16 color
×
559
  msg2->offset++;                          // u8 flags
×
560

561
  msg2.Send( client );
×
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() );
×
574
  u16 len1A = msg->offset;
×
575
  msg->offset = 1;
×
576
  msg->WriteFlipped<u16>( len1A );
×
577

578
  client->pause();
×
579
  msg.Send( client, len1A );
×
580
  boat_sent_to.push_back( client );
×
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()
59✔
589
{
590
  for ( Network::Client* client : boat_sent_to )
59✔
591
  {
592
    client->restart();
×
593
  }
594
  boat_sent_to.clear();
59✔
595
}
59✔
596

597

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

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

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

619
  auto uoflags = Plib::systemstate.tile[objtype].uoflags;
3,629✔
620
  bool passable = ( uoflags & Plib::USTRUCT_TILE::FLAG_FOLIAGE ) &&
4,035✔
621
                  ( ~uoflags & Plib::USTRUCT_TILE::FLAG_BLOCKING );
406✔
622
  return passable;
3,629✔
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()
64✔
629
{
630
  const MultiDef& md = multidef();
64✔
631
  for ( const auto& ele : md.hull )
1,340✔
632
  {
633
    if ( objtype_passable( ele->objtype ) )
1,276✔
634
      continue;
142✔
635

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

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

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

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

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

658
bool UBoat::can_fit_at_location( const MultiDef& md, const Core::Pos4d& desired_pos )
62✔
659
{
660
  if ( !desired_pos.can_move_to( md.minrxyz.xy() ) || !desired_pos.can_move_to( md.maxrxyz.xy() ) )
62✔
661
  {
662
#ifdef DEBUG_BOATS
663
    INFO_PRINTLN( "Location {} impassable, location is off the map", desired_pos );
664
#endif
665
    return false;
1✔
666
  }
667
  return true;
61✔
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 )
59✔
672
{
673
  if ( !can_fit_at_location( md, desired_pos ) )
59✔
674
    return false;
1✔
675

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

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

685
    if ( objtype_passable( ele->objtype ) )
1,125✔
686
      continue;
126✔
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() );
999✔
692
    if ( desired_pos.realm()->global_hulls.count( gh ) )  // already a boat there
999✔
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;
2✔
699
    }
700

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

705
  return true;
56✔
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,037✔
712
{
713
  if ( Core::IsItem( obj->serial ) )
18,037✔
714
  {
715
    const Item* item = static_cast<const Item*>( obj );
18,010✔
716
    if ( item->container != nullptr )
18,010✔
717
      return false;
×
718
  }
719
  Core::Vec2d rxy = obj->pos2d() - bc.oldpos.xy();
18,037✔
720

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

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

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

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

740
    obj->set_dirty();
6,015✔
741
    // keeps relative distance from boat mast
742
    auto delta = obj->pos3d() - oldlocation.oldpos.xyz();
6,015✔
743
    auto newtravellerpos = pos() + delta;
6,015✔
744
    if ( obj->ismobile() )
6,015✔
745
    {
746
      auto* chr = static_cast<Mobile::Character*>( obj );
10✔
747
      move_boat_mobile( chr, newtravellerpos );
10✔
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 )
38✔
760
    remove_orphans();
×
761
}
38✔
762

763
void UBoat::move_boat_item( Items::Item* item, const Core::Pos4d& newpos )
6,189✔
764
{
765
  item->set_dirty();
6,189✔
766
  const Core::Pos4d oldpos = item->pos();
6,189✔
767
  item->setposition( newpos );
6,189✔
768
  MoveItemWorldPosition( oldpos, item );
6,189✔
769
  // TODO POS should be removed
770
  if ( oldpos.realm() != newpos.realm() && item->isa( Core::UOBJ_CLASS::CLASS_CONTAINER ) )
6,189✔
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,189✔
777
      item,
778
      [&]( Mobile::Character* zonechr )
6,189✔
779
      {
780
        if ( !zonechr->in_visual_range( item ) )
12,210✔
781
          return;
6,170✔
782
        Network::Client* client = zonechr->client;
6,040✔
783

784
        if ( !( client->ClientType & Network::CLIENTTYPE_7090 ) )
6,040✔
785
          send_item( client, item );
×
786
      } );
787

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

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

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

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

827
    if ( chr->client->ClientType & Network::CLIENTTYPE_7090 )
6✔
828
    {
829
      Core::send_objects_newly_inrange_on_boat( chr->client, this->serial );
6✔
830

831
      if ( chr->poisoned() )  // if poisoned send 0x17 for newer clients
6✔
832
        send_poisonhealthbar( chr->client, chr );
×
833

834
      if ( chr->invul() )  // if invul send 0x17 for newer clients
6✔
835
        send_invulhealthbar( chr->client, chr );
×
836
    }
837
    else
838
    {
839
      Core::send_goxyz( chr->client, chr );
×
840
      // lastpos are set above so these two calls will work right.
841
      // FIXME these are also called, in this order, in MOVEMENT.CPP.
842
      // should be consolidated.
843
      Core::send_objects_newly_inrange_on_boat( chr->client, this->serial );
×
844
    }
845
  }
846
  chr->move_reason = Mobile::Character::MULTIMOVE;
14✔
847
}
14✔
848

849
Core::Pos4d UBoat::turn_coords( const Core::Pos4d& oldpos, RELATIVE_DIR dir ) const
8✔
850
{
851
  Core::Vec3d delta = oldpos.xyz() - pos3d();
8✔
852
  switch ( dir )
8✔
853
  {
854
  case LEFT:
2✔
855
    delta = Core::Vec3d( delta.y(), -delta.x(), delta.z() );
2✔
856
    break;
2✔
857
  case AROUND:
2✔
858
    delta.x( -delta.x() ).y( -delta.y() );
2✔
859
    break;
2✔
860
  case RIGHT:
2✔
861
    delta = Core::Vec3d( -delta.y(), delta.x(), delta.z() );
2✔
862
    break;
2✔
863
  case NO_TURN:
2✔
864
    return oldpos;
2✔
865
  }
866
  return pos() + delta;
6✔
867
}
868

869
u8 UBoat::turn_facing( u8 oldfacing, RELATIVE_DIR dir ) const
8✔
870
{
871
  return ( ( dir * 2 ) + oldfacing ) & 7;
8✔
872
}
873

874
void UBoat::turn_travellers( RELATIVE_DIR dir, const BoatContext& oldlocation )
4✔
875
{
876
  bool any_orphans = false;
4✔
877

878
  for ( auto& travellerRef : travellers_ )
12✔
879
  {
880
    UObject* obj = travellerRef.get();
8✔
881

882
    // consider: occasional sweeps of all boats to reap orphans
883
    if ( obj->orphan() || !on_ship( oldlocation, obj ) )
8✔
884
    {
885
      any_orphans = true;
×
886
      travellerRef.clear();
×
887
      continue;
×
888
    }
889

890
    obj->set_dirty();
8✔
891
    auto newpos = turn_coords( obj->pos(), dir );
8✔
892
    if ( obj->ismobile() )
8✔
893
    {
894
      Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
4✔
895
      chr->setfacing( turn_facing( chr->facing, dir ) );
4✔
896
      move_boat_mobile( chr, newpos );
4✔
897
    }
898
    else
899
    {
900
      Items::Item* item = static_cast<Items::Item*>( obj );
4✔
901

902
      move_boat_item( item, newpos );
4✔
903
      if ( Core::settingsManager.ssopt.refresh_decay_after_boat_moves )
4✔
904
        item->restart_decay_timer();
4✔
905
    }
906
  }
907

908
  if ( any_orphans )
4✔
909
    remove_orphans();
×
910
}
4✔
911

912
void UBoat::remove_orphans()
16✔
913
{
914
  bool any_orphan_travellers;
915
  do
16✔
916
  {
917
    any_orphan_travellers = false;
16✔
918

919
    for ( Travellers::iterator itr = travellers_.begin(), end = travellers_.end(); itr != end;
16✔
920
          ++itr )
×
921
    {
922
      UObject* obj = ( *itr ).get();
×
923
      if ( obj == nullptr )
×
924
      {
925
        set_dirty();
×
926
        travellers_.erase( itr );
×
927
        any_orphan_travellers = true;
×
928
        break;
×
929
      }
930
    }
931
  } while ( any_orphan_travellers );
932
}
16✔
933

934
void UBoat::cleanup_deck()
16✔
935
{
936
  BoatContext bc( *this );
16✔
937

938
  for ( auto& travellerRef : travellers_ )
16✔
939
  {
940
    UObject* obj = travellerRef.get();
×
941

942
    if ( obj->orphan() || !on_ship( bc, obj ) )
×
943
    {
944
      set_dirty();
×
945
      travellerRef.clear();
×
946
    }
947
  }
948
  remove_orphans();
16✔
949
}
16✔
950

951
bool UBoat::has_offline_mobiles() const
16✔
952
{
953
  BoatContext bc( *this );
16✔
954

955
  for ( const auto& travellerRef : travellers_ )
16✔
956
  {
957
    UObject* obj = travellerRef.get();
×
958

959
    if ( !obj->orphan() && on_ship( bc, obj ) && obj->ismobile() )
×
960
    {
961
      Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
×
962

963
      if ( !chr->logged_in() )
×
964
      {
965
        return true;
×
966
      }
967
    }
968
  }
969
  return false;
16✔
970
}
971

972
void UBoat::move_offline_mobiles( const Core::Pos4d& newpos )
×
973
{
974
  BoatContext bc( *this );
×
975

976
  for ( auto& travellerRef : travellers_ )
×
977
  {
978
    UObject* obj = travellerRef.get();
×
979

980
    if ( !obj->orphan() && on_ship( bc, obj ) && obj->ismobile() )
×
981
    {
982
      Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
×
983

984
      if ( !chr->logged_in() )
×
985
      {
986
        chr->set_dirty();
×
987
        chr->setposition( newpos );
×
988
        chr->realm_changed();  // not sure if neccessary...
×
989
        travellerRef.clear();
×
990
      }
991
    }
992
  }
993
  remove_orphans();
×
994
}
×
995

996
void UBoat::on_color_changed()
×
997
{
998
  send_display_boat_to_inrange( {} );
×
999
}
×
1000

1001
bool UBoat::deck_empty() const
16✔
1002
{
1003
  return travellers_.empty();
16✔
1004
}
1005

1006
bool UBoat::hold_empty() const
16✔
1007
{
1008
  Items::Item* it = this->hold;
16✔
1009
  if ( it == nullptr )
16✔
1010
  {
1011
    return true;
1✔
1012
  }
1013
  Core::UContainer* cont = static_cast<Core::UContainer*>( it );
15✔
1014
  return ( cont->count() == 0 );
15✔
1015
}
1016

1017
void UBoat::do_tellmoves()
42✔
1018
{
1019
  // we only do tellmove here because tellmove also checks attacks.
1020
  // this way, we move everyone, then check for attacks.
1021

1022
  for ( auto& travellerRef : travellers_ )
6,065✔
1023
  {
1024
    UObject* obj = travellerRef.get();
6,023✔
1025

1026
    if ( obj != nullptr )  // sometimes we've destroyed objects because of control scripts
6,023✔
1027
    {
1028
      if ( obj->ismobile() )
6,023✔
1029
      {
1030
        Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
14✔
1031
        if ( chr->isa( Core::UOBJ_CLASS::CLASS_NPC ) ||
20✔
1032
             chr->has_active_client() )  // dave 3/27/3, dont tell moves of offline PCs
6✔
1033
          chr->tellmove();
14✔
1034
      }
1035
    }
1036
  }
1037
}
42✔
1038

1039
bool UBoat::move_to( const Core::Pos4d& newpos, int flags )
10✔
1040
{
1041
  BoatMoveGuard registerguard( this );
10✔
1042
  if ( ( flags & Core::MOVEITEM_FORCELOCATION ) || navigable( multidef(), newpos ) )
10✔
1043
  {
1044
    BoatContext bc( *this );
10✔
1045

1046
    set_dirty();
10✔
1047

1048
    setposition( newpos );
10✔
1049
    move_multi_in_world( this, bc.oldpos );
10✔
1050

1051
    move_travellers( bc );
10✔
1052
    move_components();
10✔
1053
    send_display_boat_to_inrange( bc.oldpos );
10✔
1054
    do_tellmoves();
10✔
1055
    unpause_paused();
10✔
1056

1057
    return true;
10✔
1058
  }
1059
  return false;
×
1060
}
10✔
1061

1062
bool UBoat::move( Core::UFACING dir, u8 speed, bool relative )
29✔
1063
{
1064
  BoatMoveGuard registerguard( this );
29✔
1065

1066
  Core::UFACING move_dir;
1067

1068
  if ( relative == false )
29✔
1069
    move_dir = dir;
22✔
1070
  else
1071
    move_dir = static_cast<Core::UFACING>( ( dir + boat_facing() ) & 7 );
7✔
1072

1073
  auto newpos = pos().move( move_dir );
29✔
1074

1075
  if ( !navigable( multidef(), newpos ) )
29✔
1076
    return false;
1✔
1077

1078
  BoatContext bc( *this );
28✔
1079

1080
  set_dirty();
28✔
1081

1082
  setposition( newpos );
28✔
1083
  move_multi_in_world( this, bc.oldpos );
28✔
1084
  move_travellers( bc );
28✔
1085
  move_components();
28✔
1086

1087
  Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
28✔
1088
      this,
1089
      [&]( Mobile::Character* zonechr )
28✔
1090
      {
1091
        Network::Client* client = zonechr->client;
40✔
1092
        if ( !zonechr->in_visual_range( this ) )
40✔
1093
          return;
33✔
1094
        if ( client->ClientType & Network::CLIENTTYPE_7090 )
7✔
1095
        {
1096
          if ( zonechr->in_visual_range( this, bc.oldpos ) )
7✔
1097
            send_smooth_move( client, move_dir, speed, relative );
6✔
1098
          else
1099
            send_boat_newly_inrange( client );  // send HSA packet only for newly inrange
1✔
1100
        }
1101
        else
1102
        {
1103
          if ( client->ClientType & Network::CLIENTTYPE_7000 )
×
1104
            send_boat( client );  // Send
×
1105
          else
1106
            send_boat_old( client );
×
1107
        }
1108
      } );
1109

1110
  Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
28✔
1111
      bc.oldpos,
1112
      [&]( Mobile::Character* zonechr )
28✔
1113
      {
1114
        if ( zonechr->in_visual_range( this, bc.oldpos ) &&
47✔
1115
             !zonechr->in_visual_range( this ) )  // send remove to chrs only seeing the old loc
7✔
1116
          send_remove_boat( zonechr->client );
1✔
1117
      } );
40✔
1118

1119
  do_tellmoves();
28✔
1120
  unpause_paused();
28✔
1121
  return true;
28✔
1122
}
29✔
1123

1124
inline unsigned short UBoat::multiid_ifturn( RELATIVE_DIR dir )
8✔
1125
{
1126
  return ( multiid_ & ~3u ) | ( ( multiid_ + dir ) & 3 );
8✔
1127
}
1128

1129
const MultiDef& UBoat::multi_ifturn( RELATIVE_DIR dir )
4✔
1130
{
1131
  unsigned short multiid_dir = multiid_ifturn( dir );
4✔
1132
  passert( MultiDefByMultiIDExists( multiid_dir ) );
4✔
1133
  return *MultiDefByMultiID( multiid_dir );
4✔
1134
}
1135

1136
Core::UFACING UBoat::boat_facing() const
16✔
1137
{
1138
  return static_cast<Core::UFACING>( ( multiid_ & 3 ) * 2 );
16✔
1139
}
1140

1141
const BoatShape& UBoat::boatshape() const
69✔
1142
{
1143
  passert( Core::gamestate.boatshapes.count( multiid_ ) != 0 );
69✔
1144
  return *Core::gamestate.boatshapes[multiid_];
69✔
1145
}
1146

1147

1148
void UBoat::transform_components( const BoatShape& old_boatshape )
7✔
1149
{
1150
  const BoatShape& bshape = boatshape();
7✔
1151
  auto end = Components.end();
7✔
1152
  auto end2 = bshape.Componentshapes.end();
7✔
1153
  auto old_end = old_boatshape.Componentshapes.end();
7✔
1154

1155
  auto itr = Components.begin();
7✔
1156
  auto itr2 = bshape.Componentshapes.begin();
7✔
1157
  auto old_itr = old_boatshape.Componentshapes.begin();
7✔
1158

1159
  for ( ; itr != end && itr2 != end2 && old_itr != old_end; ++itr, ++itr2, ++old_itr )
35✔
1160
  {
1161
    Items::Item* item = itr->get();
28✔
1162
    if ( item != nullptr )
28✔
1163
    {
1164
      if ( item->orphan() )
28✔
1165
        continue;
×
1166

1167
      // This should be rare enough for a simple log to be the solution. We don't want POL to
1168
      // crash in MoveItemWorldPosition() because the item was not in the world to start with, so
1169
      // we skip it.
1170
      if ( item->container != nullptr || item->has_gotten_by() )
28✔
1171
      {
1172
        u32 containerSerial = ( item->container != nullptr ) ? item->container->serial : 0;
×
1173
        POLLOG_ERRORLN(
×
1174
            "Boat component is gotten or in a container and couldn't be moved together with the "
1175
            "boat: serial {:#x}\n, graphic: {:#x}, container: {:#x}.",
1176
            item->serial, item->graphic, containerSerial );
×
1177
        continue;
×
1178
      }
×
1179

1180
      if ( item->objtype_ == Core::settingsManager.extobj.port_plank &&
35✔
1181
           item->graphic == old_itr->altgraphic )
7✔
1182
        item->graphic = itr2->altgraphic;
×
1183
      else if ( item->objtype_ == Core::settingsManager.extobj.starboard_plank &&
35✔
1184
                item->graphic == old_itr->altgraphic )
7✔
1185
        item->graphic = itr2->altgraphic;
×
1186
      else
1187
        item->graphic = itr2->graphic;
28✔
1188

1189
      move_boat_item( item, pos() + itr2->delta );
28✔
1190
    }
1191
  }
1192
}
7✔
1193

1194
void UBoat::move_components()
38✔
1195
{
1196
  const BoatShape& bshape = boatshape();
38✔
1197
  auto itr = Components.begin();
38✔
1198
  auto end = Components.end();
38✔
1199
  auto itr2 = bshape.Componentshapes.begin();
38✔
1200
  auto end2 = bshape.Componentshapes.end();
38✔
1201
  for ( ; itr != end && itr2 != end2; ++itr, ++itr2 )
190✔
1202
  {
1203
    Items::Item* item = itr->get();
152✔
1204
    if ( item != nullptr )
152✔
1205
    {
1206
      if ( item->orphan() )
152✔
1207
      {
1208
        continue;
×
1209
      }
1210

1211
      // This should be rare enough for a simple log to be the solution. We don't want POL to
1212
      // crash in MoveItemWorldPosition() because the item was not in the world to start with, so
1213
      // we skip it.
1214
      if ( item->container != nullptr || item->has_gotten_by() )
152✔
1215
      {
1216
        u32 containerSerial = ( item->container != nullptr ) ? item->container->serial : 0;
×
1217
        POLLOG_INFOLN(
×
1218
            "Boat component is gotten or in a container and couldn't be moved together with the "
1219
            "boat: serial {:#x}\n"
1220
            ", graphic: {:#x}, container: {:#x}.",
1221
            item->serial, item->graphic, containerSerial );
×
1222
        continue;
×
1223
      }
×
1224
      move_boat_item( item, pos() + itr2->delta );
152✔
1225
    }
1226
  }
1227
}
38✔
1228

1229
bool UBoat::turn( RELATIVE_DIR dir )
4✔
1230
{
1231
  BoatMoveGuard registerguard( this );
4✔
1232

1233
  const MultiDef& newmd = multi_ifturn( dir );
4✔
1234

1235
  if ( !navigable( newmd, pos() ) )
4✔
1236
    return false;
×
1237
  BoatContext bc( *this );
4✔
1238
  const BoatShape& old_boatshape = boatshape();
4✔
1239

1240
  set_dirty();
4✔
1241
  multiid_ = multiid_ifturn( dir );
4✔
1242

1243
  turn_travellers( dir, bc );
4✔
1244
  transform_components( old_boatshape );
4✔
1245
  send_display_boat_to_inrange( {} );
4✔
1246
  do_tellmoves();
4✔
1247
  unpause_paused();
4✔
1248
  facing = turn_facing( facing, dir );
4✔
1249
  return true;
4✔
1250
}
4✔
1251

1252
void UBoat::register_object( UObject* obj )
6,018✔
1253
{
1254
  if ( find( travellers_.begin(), travellers_.end(), obj ) == travellers_.end() )
6,018✔
1255
  {
1256
    set_dirty();
6,015✔
1257
    travellers_.push_back( Traveller( obj ) );
6,015✔
1258
  }
1259
}
6,018✔
1260

1261
void UBoat::unregister_object( UObject* obj )
6,076✔
1262
{
1263
  Travellers::iterator this_traveller = find( travellers_.begin(), travellers_.end(), obj );
6,076✔
1264

1265
  if ( this_traveller != travellers_.end() )
6,076✔
1266
  {
1267
    set_dirty();
6,016✔
1268
    travellers_.erase( this_traveller );
6,016✔
1269
  }
1270
}
6,076✔
1271

1272
void UBoat::rescan_components()
18✔
1273
{
1274
  UPlank* plank;
1275

1276
  if ( portplank != nullptr && !portplank->orphan() )
18✔
1277
  {
1278
    plank = static_cast<UPlank*>( portplank );
17✔
1279
    plank->setboat( this );
17✔
1280
  }
1281

1282
  if ( starboardplank != nullptr && !starboardplank->orphan() )
18✔
1283
  {
1284
    plank = static_cast<UPlank*>( starboardplank );
17✔
1285
    plank->setboat( this );
17✔
1286
  }
1287
}
18✔
1288

1289
void UBoat::reread_components()
1✔
1290
{
1291
  for ( auto& component : Components )
5✔
1292
  {
1293
    if ( component == nullptr )
4✔
1294
      continue;
×
1295
    // check boat members here
1296
    if ( component->objtype_ == Core::settingsManager.extobj.tillerman && tillerman == nullptr )
4✔
1297
      tillerman = component.get();
1✔
1298
    if ( component->objtype_ == Core::settingsManager.extobj.port_plank && portplank == nullptr )
4✔
1299
      portplank = component.get();
1✔
1300
    if ( component->objtype_ == Core::settingsManager.extobj.starboard_plank &&
5✔
1301
         starboardplank == nullptr )
1✔
1302
      starboardplank = component.get();
1✔
1303
    if ( component->objtype_ == Core::settingsManager.extobj.hold && hold == nullptr )
4✔
1304
      hold = component.get();
1✔
1305
  }
1306
}
1✔
1307

1308
/// POL098 and earlier were using graphic to store MultiID, this should not be lost
1309
/// to avoid screwing up boats during conversion
1310
void UBoat::fixInvalidGraphic()
1✔
1311
{
1312
  if ( !Core::settingsManager.polvar.DataWrittenBy99OrLater )
1✔
1313
  {
1314
    passert_always_r( graphic >= 0x4000, "Unexpected boat graphic < 0x4000 in POL < 099 data" );
×
1315
    multiid_ = graphic - 0x4000;
×
1316
  }
1317
  base::fixInvalidGraphic();
1✔
1318
}
1✔
1319

1320
void UBoat::readProperties( Clib::ConfigElem& elem )
1✔
1321
{
1322
  base::readProperties( elem );
1✔
1323

1324
  BoatContext bc( *this );
1✔
1325
  u32 tmp_serial;
1326
  while ( elem.remove_prop( "Traveller", &tmp_serial ) )
2✔
1327
  {
1328
    if ( Core::IsItem( tmp_serial ) )
1✔
1329
    {
1330
      Items::Item* item = Core::find_toplevel_item( tmp_serial );
×
1331
      if ( item != nullptr )
×
1332
      {
1333
        if ( BoatShape::objtype_is_component( item->objtype_ ) )
×
1334
        {
1335
          Components.push_back( Component( item ) );
×
1336
        }
1337
        else if ( on_ship( bc, item ) )
×
1338
        {
1339
          travellers_.push_back( Traveller( item ) );
×
1340
        }
1341
      }
1342
    }
1343
    else
1344
    {
1345
      Mobile::Character* chr = Core::system_find_mobile( tmp_serial );
1✔
1346

1347
      if ( chr != nullptr )
1✔
1348
      {
1349
        if ( on_ship( bc, chr ) )
1✔
1350
          travellers_.push_back( Traveller( chr ) );
1✔
1351
      }
1352
    }
1353
  }
1354
  while ( elem.remove_prop( "Component", &tmp_serial ) )
6✔
1355
  {
1356
    Items::Item* item = Core::system_find_item( tmp_serial );
4✔
1357
    if ( item != nullptr )
4✔
1358
    {
1359
      if ( BoatShape::objtype_is_component( item->objtype_ ) )
4✔
1360
      {
1361
        Components.push_back( Component( item ) );
4✔
1362
      }
1363
    }
1364
  }
1365
  reread_components();
1✔
1366
  rescan_components();
1✔
1367

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

1371
  auto control_script = itemdesc().control_script.empty() ? Core::ScriptDef( "misc/boat", nullptr )
1✔
1372
                                                          : itemdesc().control_script;
1✔
1373

1374
  auto prog = Core::find_script2( control_script );
1✔
1375

1376
  if ( prog.get() == nullptr )
1✔
1377
  {
1378
    POLLOG_ERRORLN( "Could not start script {}, boat: serial {:#x}", control_script.c_str(),
×
1379
                    this->serial );
×
1380
    return;
×
1381
  }
1382

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

1385
  if ( script == nullptr )
1✔
1386
  {
1387
    POLLOG_ERRORLN( "Could not start script {}, boat: serial {:#x}", control_script.c_str(),
×
1388
                    this->serial );
×
1389
  }
1390
  else
1391
  {
1392
    this->process( script );
1✔
1393
    this->process()->attached_item_.set( this );
1✔
1394
  }
1395
}
1✔
1396

1397
void UBoat::printProperties( Clib::StreamWriter& sw ) const
2✔
1398
{
1399
  base::printProperties( sw );
2✔
1400

1401
  sw.add( "MultiID", multiid_ );
2✔
1402

1403
  BoatContext bc( *this );
2✔
1404

1405
  for ( auto& travellerRef : travellers_ )
4✔
1406
  {
1407
    UObject* obj = travellerRef.get();
2✔
1408
    if ( !obj->orphan() && on_ship( bc, obj ) )
2✔
1409
    {
1410
      sw.add( "Traveller", Clib::hexintv( obj->serial ) );
2✔
1411
    }
1412
  }
1413
  for ( auto& component : Components )
10✔
1414
  {
1415
    if ( component != nullptr && !component->orphan() )
8✔
1416
    {
1417
      sw.add( "Component", Clib::hexintv( component->serial ) );
8✔
1418
    }
1419
  }
1420
}
2✔
1421

1422
Bscript::BObjectImp* UBoat::scripted_create( const Items::ItemDesc& descriptor,
19✔
1423
                                             const Core::Pos4d& pos, int flags )
1424
{
1425
  unsigned short multiid = descriptor.multiid;
19✔
1426
  unsigned short multiid_offset =
19✔
1427
      static_cast<unsigned short>( ( flags & CRMULTI_FACING_MASK ) >> CRMULTI_FACING_SHIFT );
19✔
1428
  unsigned char facing = static_cast<unsigned char>( multiid_offset * 2 );
19✔
1429
  multiid += multiid_offset;
19✔
1430

1431
  const MultiDef* md = MultiDefByMultiID( multiid );
19✔
1432
  if ( md == nullptr )
19✔
1433
  {
1434
    return new Bscript::BError(
1435
        "Multi definition not found for Boat, objtype=" + Clib::hexint( descriptor.objtype ) +
×
1436
        ", multiid=" + Clib::hexint( multiid ) );
×
1437
  }
1438
  if ( !Core::gamestate.boatshapes.count( descriptor.multiid ) )
19✔
1439
  {
1440
    return new Bscript::BError(
1441
        "No boatshape for Boat in boats.cfg, objtype=" + Clib::hexint( descriptor.objtype ) +
×
1442
        ", multiid=" + Clib::hexint( multiid ) );
×
1443
  }
1444

1445
  if ( !navigable( *md, pos ) )
19✔
1446
  {
1447
    return new Bscript::BError( "Position indicated is impassable" );
2✔
1448
  }
1449

1450
  UBoat* boat = new UBoat( descriptor );
17✔
1451
  boat->multiid_ = multiid;
17✔
1452
  boat->serial = Core::GetNewItemSerialNumber();
17✔
1453
  boat->serial_ext = ctBEu32( boat->serial );
17✔
1454
  boat->setposition( pos );
17✔
1455
  boat->facing = facing;
17✔
1456
  add_multi_to_world( boat );
17✔
1457
  boat->send_display_boat_to_inrange( {} );
17✔
1458
  boat->create_components();
17✔
1459
  boat->rescan_components();
17✔
1460
  unpause_paused();
17✔
1461
  boat->regself();
17✔
1462

1463
  ////hash
1464
  Core::objStorageManager.objecthash.Insert( boat );
17✔
1465
  ////
1466

1467
  Bscript::BObjectImp* boatref = make_boatref( boat );
17✔
1468

1469
  auto control_script = descriptor.control_script.empty() ? Core::ScriptDef( "misc/boat", nullptr )
17✔
1470
                                                          : descriptor.control_script;
17✔
1471

1472
  auto prog = Core::find_script2( control_script );
17✔
1473

1474
  if ( prog.get() == nullptr )
17✔
1475
  {
1476
    POLLOG_ERRORLN( "Could not start script {}, boat: serial {:#x}", control_script.c_str(),
1✔
1477
                    boat->serial );
1✔
1478

1479
    return boatref;
1✔
1480
  }
1481

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

1484
  if ( script == nullptr )
16✔
1485
  {
1486
    POLLOG_ERRORLN( "Could not start script {}, boat: serial {:#x}", control_script.c_str(),
×
1487
                    boat->serial );
×
1488
  }
1489
  else
1490
  {
1491
    boat->process( script );
16✔
1492
    boat->process()->attached_item_.set( boat );
16✔
1493
  }
1494

1495
  return boatref;
16✔
1496
}
17✔
1497

1498
void UBoat::create_components()
17✔
1499
{
1500
  const BoatShape& bshape = boatshape();
17✔
1501
  for ( std::vector<BoatShape::ComponentShape>::const_iterator itr = bshape.Componentshapes.begin(),
34✔
1502
                                                               end = bshape.Componentshapes.end();
17✔
1503
        itr != end; ++itr )
81✔
1504
  {
1505
    Items::Item* component;
1506
    try
1507
    {
1508
      component = Items::Item::create( itr->objtype );
64✔
1509
    }
1510
    catch ( ... )
×
1511
    {
1512
      continue;
×
1513
    }
×
1514
    if ( component == nullptr )
64✔
1515
      continue;
×
1516
    // check boat members here
1517
    if ( component->objtype_ == Core::settingsManager.extobj.tillerman && tillerman == nullptr )
64✔
1518
      tillerman = component;
16✔
1519
    if ( component->objtype_ == Core::settingsManager.extobj.port_plank && portplank == nullptr )
64✔
1520
      portplank = component;
16✔
1521
    if ( component->objtype_ == Core::settingsManager.extobj.starboard_plank &&
64✔
1522
         starboardplank == nullptr )
16✔
1523
      starboardplank = component;
16✔
1524
    if ( component->objtype_ == Core::settingsManager.extobj.hold && hold == nullptr )
64✔
1525
      hold = component;
16✔
1526

1527
    component->graphic = itr->graphic;
64✔
1528
    // component itemdesc entries generally have graphic=1, so they don't get their height set.
1529
    component->height = Plib::tileheight( component->graphic );
64✔
1530
    component->setposition( pos() + itr->delta );
64✔
1531
    component->disable_decay();
64✔
1532
    component->movable( false );
64✔
1533
    add_item_to_world( component );
64✔
1534
    update_item_to_inrange( component );
64✔
1535
    Components.push_back( Component( component ) );
64✔
1536
  }
1537
}
17✔
1538

1539
Bscript::BObjectImp* UBoat::items_list() const
3✔
1540
{
1541
  BoatContext bc( *this );
3✔
1542
  Bscript::ObjArray* arr = new Bscript::ObjArray;
3✔
1543

1544
  for ( const auto& travellerRef : travellers_ )
12,007✔
1545
  {
1546
    UObject* obj = travellerRef.get();
12,004✔
1547
    if ( !obj->orphan() && on_ship( bc, obj ) && Core::IsItem( obj->serial ) )
12,004✔
1548
    {
1549
      Item* item = static_cast<Item*>( obj );
12,001✔
1550
      arr->addElement( make_itemref( item ) );
12,001✔
1551
    }
1552
  }
1553
  return arr;
3✔
1554
}
1555

1556
Bscript::BObjectImp* UBoat::mobiles_list() const
4✔
1557
{
1558
  BoatContext bc( *this );
4✔
1559
  Bscript::ObjArray* arr = new Bscript::ObjArray;
4✔
1560
  for ( const auto& travellerRef : travellers_ )
8✔
1561
  {
1562
    UObject* obj = travellerRef.get();
4✔
1563
    if ( !obj->orphan() && on_ship( bc, obj ) && obj->ismobile() )
4✔
1564
    {
1565
      Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
4✔
1566
      if ( chr->logged_in() )
4✔
1567
        arr->addElement( make_mobileref( chr ) );
4✔
1568
    }
1569
  }
1570
  return arr;
4✔
1571
}
1572

1573
Bscript::BObjectImp* UBoat::component_list( unsigned char type ) const
1✔
1574
{
1575
  Bscript::ObjArray* arr = new Bscript::ObjArray;
1✔
1576
  for ( const auto& component : Components )
5✔
1577
  {
1578
    if ( component != nullptr && !component->orphan() )
4✔
1579
    {
1580
      if ( type == COMPONENT_ALL )
4✔
1581
      {
1582
        arr->addElement( component->make_ref() );
4✔
1583
      }
1584
      else
1585
      {
1586
        if ( component->objtype_ == get_component_objtype( type ) )
×
1587
          arr->addElement( component->make_ref() );
×
1588
      }
1589
    }
1590
  }
1591
  return arr;
1✔
1592
}
1593

1594
void UBoat::destroy_components()
16✔
1595
{
1596
  for ( auto& component : Components )
76✔
1597
  {
1598
    if ( component != nullptr && !component->orphan() )
60✔
1599
    {
1600
      Core::destroy_item( component.get() );
60✔
1601
    }
1602
  }
1603
  Components.clear();
16✔
1604
}
16✔
1605

UNCOV
1606
size_t UBoat::estimatedSize() const
×
1607
{
UNCOV
1608
  size_t size = base::estimatedSize() + sizeof( Items::Item* ) /*tillerman*/
×
1609
                + sizeof( Items::Item* )                       /*portplank*/
1610
                + sizeof( Items::Item* )                       /*starboardplank*/
1611
                + sizeof( Items::Item* )                       /*hold*/
1612
                // no estimateSize here element is in objhash
UNCOV
1613
                + Clib::memsize( travellers_ ) + Clib::memsize( Components );
×
UNCOV
1614
  return size;
×
1615
}
1616

1617
bool UBoat::get_method_hook( const char* methodname, Bscript::Executor* ex,
1✔
1618
                             Core::ExportScript** hook, unsigned int* PC ) const
1619
{
1620
  if ( Core::gamestate.system_hooks.get_method_hook(
1✔
1621
           Core::gamestate.system_hooks.boat_method_script.get(), methodname, ex, hook, PC ) )
1622
    return true;
1✔
1623
  return base::get_method_hook( methodname, ex, hook, PC );
×
1624
}
1625

1626
Bscript::BObjectImp* destroy_boat( UBoat* boat )
16✔
1627
{
1628
  boat->cleanup_deck();
16✔
1629

1630
  if ( !boat->hold_empty() )
16✔
1631
    return new Bscript::BError( "There is cargo in the ship's hold" );
×
1632
  if ( boat->has_offline_mobiles() )
16✔
1633
    return new Bscript::BError( "There are logged-out characters on the deck" );
×
1634
  if ( !boat->deck_empty() )
16✔
1635
    return new Bscript::BError( "The deck is not empty" );
×
1636

1637
  boat->destroy_components();
16✔
1638
  boat->unregself();
16✔
1639

1640
  Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
16✔
1641
      boat,
1642
      [&]( Mobile::Character* zonechr )
16✔
1643
      {
1644
        if ( zonechr->in_visual_range( boat ) )
5✔
1645
          Core::send_remove_object( zonechr->client, boat );
2✔
1646
      } );
5✔
1647
  remove_multi_from_world( boat );
16✔
1648
  boat->destroy();
16✔
1649
  return new Bscript::BLong( 1 );
16✔
1650
}
1651

1652
Mobile::Character* UBoat::pilot() const
6✔
1653
{
1654
  if ( mountpiece != nullptr && !mountpiece->orphan() )
6✔
1655
  {
1656
    return mountpiece->GetCharacterOwner();
5✔
1657
  }
1658
  return nullptr;
1✔
1659
}
1660

1661
Bscript::BObjectImp* UBoat::set_pilot( Mobile::Character* chr )
10✔
1662
{
1663
  if ( chr == nullptr )
10✔
1664
  {
1665
    clear_pilot();
6✔
1666
    return new Bscript::BLong( 1 );
6✔
1667
  }
1668
  else
1669
  {
1670
    if ( mountpiece != nullptr && !mountpiece->orphan() )
4✔
1671
    {
1672
      return new Bscript::BError( "The boat is already being piloted." );
1✔
1673
    }
1674

1675
    if ( !has_process() )
3✔
1676
    {
1677
      return new Bscript::BError( "The boat does not have a running process." );
×
1678
    }
1679

1680
    if ( !chr->client )
3✔
1681
    {
1682
      return new Bscript::BError( "That character is not connected." );
×
1683
    }
1684

1685
    if ( !( chr->client->ClientType & Network::CLIENTTYPE_7090 ) )
3✔
1686
    {
1687
      return new Bscript::BError(
1688
          "The client for that character does not support High Seas Adventure." );
×
1689
    }
1690

1691
    BoatContext bc( *this );
3✔
1692
    bool pilot_on_ship = false;
3✔
1693
    for ( const auto& travellerRef : travellers_ )
3✔
1694
    {
1695
      UObject* obj = travellerRef.get();
3✔
1696
      if ( !obj->orphan() && on_ship( bc, obj ) && obj == chr )
3✔
1697
      {
1698
        pilot_on_ship = true;
3✔
1699
        break;
3✔
1700
      }
1701
    }
1702

1703
    if ( !pilot_on_ship )
3✔
1704
    {
1705
      return new Bscript::BError( "The boat does not have that character on it." );
×
1706
    }
1707

1708
    Items::Item* item = Items::Item::create( Core::settingsManager.extobj.boatmount );
3✔
1709
    if ( !chr->equippable( item ) )
3✔
1710
    {
1711
      item->destroy();
×
1712
      return new Bscript::BError( "The boat mount piece is not equippable by that character." );
×
1713
    }
1714
    chr->equip( item );
3✔
1715
    send_wornitem_to_inrange( chr, item );
3✔
1716
    mountpiece = Core::ItemRef( item );
3✔
1717

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

1721
    return new Bscript::BLong( 1 );
3✔
1722
  }
1723
}
1724

1725
void UBoat::clear_pilot()
6✔
1726
{
1727
  if ( mountpiece != nullptr )
6✔
1728
  {
1729
    if ( !mountpiece->orphan() )
3✔
1730
    {
1731
      destroy_item( mountpiece.get() );
3✔
1732
    }
1733
    mountpiece.clear();
3✔
1734
  }
1735
}
6✔
1736
}  // namespace Multi
1737
}  // namespace Pol
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc