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

polserver / polserver / 25918451630

15 May 2026 12:43PM UTC coverage: 60.929% (+2.1%) from 58.859%
25918451630

push

github

turleypol
added dynamic property which returns a pointer of the object instead of
a copy like the current imp.
needed to be able to eg store a vector

43 of 61 new or added lines in 2 files covered. (70.49%)

14455 existing lines in 345 files now uncovered.

44695 of 73356 relevant lines covered (60.93%)

449621.59 hits per line

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

70.2
/pol-core/uoconvert/UoConvertMain.cpp
1
#include "UoConvertMain.h"
2

3
#include <stdio.h>
4
#include <string.h>
5
#include <string>
6

7
#include "clib/Program/ProgramMain.h"
8
#include "clib/cfgelem.h"
9
#include "clib/cfgfile.h"
10
#include "clib/fileutil.h"
11
#include "clib/logfacility.h"
12
#include "clib/passert.h"
13
#include "clib/rawtypes.h"
14
#include "clib/stlutil.h"
15
#include "clib/timer.h"
16
#include "plib/clidata.h"
17
#include "plib/mapcell.h"
18
#include "plib/mapfunc.h"
19
#include "plib/mapshape.h"
20
#include "plib/mapsolid.h"
21
#include "plib/maptile.h"
22
#include "plib/mapwriter.h"
23
#include "plib/mul/map.h"
24
#include "plib/objtype.h"
25
#include "plib/polfile.h"
26
#include "plib/realmdescriptor.h"
27
#include "plib/systemstate.h"
28
#include "plib/udatfile.h"
29
#include "plib/uofile.h"
30
#include "plib/uofilei.h"
31
#include "plib/uoinstallfinder.h"
32
#include "plib/uopreader/uop.h"
33
#include "plib/uopreader/uophash.h"
34
#include "plib/ustruct.h"
35
#include "pol/landtile.h"
36

37

38
namespace Pol::UoConvert
39
{
40
using namespace std;
41
using namespace Pol::Core;
42
using namespace Pol::Plib;
43

44
///////////////////////////////////////////////////////////////////////////////
45

46

47
///////////////////////////////////////////////////////////////////////////////
48

49
UoConvertMain::UoConvertMain()
11✔
50
    : Pol::Clib::ProgramMain(), cfg_use_no_shoot( false ), cfg_LOS_through_windows( false )
11✔
51
{
52
}
11✔
UNCOV
53
UoConvertMain::~UoConvertMain() = default;
×
54
///////////////////////////////////////////////////////////////////////////////
55

UNCOV
56
void UoConvertMain::showHelp()
×
57
{
58
  ERROR_PRINTLN( std::string{
×
59
      "Usage:\n"
60
      "    \n"
61
      "  UOCONVERT command [options ...]\n"
62
      "    \n"
63
      "  Commands: \n"
64
      "    map {uodata=Dir} {realm=realmname} {width=Width}        {height=Height} {mapid=0} "
65
      "{readuop=1} {x=X} {y=Y}\n"
66
      "    statics {uodata=Dir} {realm=realmname}\n"
67
      "    maptile {uodata=Dir} {realm=realmname}\n"
68
      "    multis {uodata=Dir} {outdir=dir}\n"
69
      "    tiles {uodata=Dir} {outdir=dir}\n"
70
      "    landtiles {uodata=Dir} {outdir=dir}" } );
UNCOV
71
}
×
72

73
using namespace Core;
74
using namespace Plib;
75

UNCOV
76
void UoConvertMain::display_flags()
×
77
{
78
  for ( unsigned blocking = 0; blocking <= 1; ++blocking )
×
79
  {
80
    for ( unsigned platform = 0; platform <= 1; ++platform )
×
81
    {
82
      for ( unsigned walk = 0; walk <= 1; ++walk )
×
83
      {
84
        for ( unsigned wall = 0; wall <= 1; ++wall )
×
85
        {
86
          for ( unsigned half = 0; half <= 1; ++half )
×
87
          {
88
            for ( unsigned floor = 0; floor <= 1; ++floor )
×
89
            {
90
              unsigned flags = 0;
×
UNCOV
91
              if ( blocking )
×
92
                flags |= USTRUCT_TILE::FLAG_BLOCKING;
×
93
              if ( platform )
×
94
                flags |= USTRUCT_TILE::FLAG_PLATFORM;
×
95
              if ( walk )
×
96
                flags |= USTRUCT_TILE::FLAG__WALK;
×
97
              if ( wall )
×
98
                flags |= USTRUCT_TILE::FLAG_WALL;
×
99
              if ( half )
×
100
                flags |= USTRUCT_TILE::FLAG_HALF_HEIGHT;
×
101
              if ( floor )
×
102
                flags |= USTRUCT_TILE::FLAG_FLOOR;
×
103

104
              unsigned int polflags = Plib::polflags_from_tileflags(
×
UNCOV
105
                  0x4000, flags, cfg_use_no_shoot, cfg_LOS_through_windows );
×
106
              unsigned moveland = ( polflags & Plib::FLAG::MOVELAND ) ? 1 : 0;
×
107
              INFO_PRINTLN( "{} {} {} {} {} {}: {}", blocking, platform, walk, wall, half, floor,
×
108
                            moveland );
109
            }
110
          }
111
        }
112
      }
113
    }
114
  }
UNCOV
115
}
×
116

117
void UoConvertMain::create_maptile( const std::string& realmname )
2✔
118
{
119
  Plib::RealmDescriptor descriptor = Plib::RealmDescriptor::Load( realmname );
2✔
120
  uo_map_height = static_cast<unsigned short>( descriptor.height );
2✔
121
  uo_map_width = static_cast<unsigned short>( descriptor.width );
2✔
122

123
  INFO_PRINTLN(
2✔
124
      "Creating maptile file.\n"
125
      "  Realm: {}\n"
126
      "  Map ID: {}\n"
127
      "  Use Dif files: {}\n"
128
      "  Size: {}x{}",
129
      realmname, descriptor.uomapid, ( descriptor.uodif ? "Yes" : "No" ), uo_map_width,
2✔
130
      uo_map_height );
131

132
  auto writer = new Plib::MapWriter();
2✔
133
  writer->OpenExistingFiles( realmname );
2✔
134

135
  for ( unsigned short y_base = 0; y_base < uo_map_height; y_base += Plib::MAPTILE_CHUNK )
30✔
136
  {
137
    for ( unsigned short x_base = 0; x_base < uo_map_width; x_base += Plib::MAPTILE_CHUNK )
662✔
138
    {
139
      for ( unsigned short x_add = 0; x_add < Plib::MAPTILE_CHUNK; ++x_add )
41,210✔
140
      {
141
        for ( unsigned short y_add = 0; y_add < Plib::MAPTILE_CHUNK; ++y_add )
2,637,440✔
142
        {
143
          unsigned short x = x_base + x_add;
2,596,864✔
144
          unsigned short y = y_base + y_add;
2,596,864✔
145

146
          short z;
147
          USTRUCT_MAPINFO mi;
148

149
          safe_getmapinfo( x, y, &z, &mi );
2,596,864✔
150

151
          if ( mi.landtile > 0x3FFF )
2,596,864✔
UNCOV
152
            INFO_PRINTLN( "Tile {:#x} at ({},{},{}) is an invalid ID!", mi.landtile, x, y, z );
×
153

154
          // for water, don't average with surrounding tiles.
155
          if ( Plib::landtile_uoflags_read( mi.landtile ) & Plib::USTRUCT_TILE::FLAG_LIQUID )
2,596,864✔
156
            z = mi.z;
194,432✔
157

158
          Plib::MAPTILE_CELL cell;
159
          cell.landtile = mi.landtile;
2,596,864✔
160
          cell.z = static_cast<signed char>( z );
2,596,864✔
161
          writer->SetMapTile( x, y, cell );
2,596,864✔
162
        }
163
      }
164
    }
165
    INFO_PRINT( "\rConverting: {}%", y_base * 100 / uo_map_height );
28✔
166
  }
167
  writer->Flush();
2✔
168
  delete writer;
2✔
169
  INFO_PRINTLN( "\rConversion complete." );
2✔
170
}
2✔
171

172
class StaticsByZ
173
{
174
public:
175
  bool operator()( const StaticRec& a, const StaticRec& b )
496✔
176
  {
177
    return ( a.z < b.z ) || ( ( a.z == b.z && a.height < b.height ) );
496✔
178
  }
179
};
180

181

182
bool flags_match( unsigned int f1, unsigned int f2, unsigned char bits_compare )
48✔
183
{
184
  return ( f1 & bits_compare ) == ( f2 & bits_compare );
48✔
185
}
186

187
/*
188
bool otherflags_match( unsigned char f1, unsigned char f2, unsigned char bits_exclude )
189
{
190
  return ( f1 & ~bits_exclude ) == ( f2 & ~bits_exclude );
191
}
192
bool differby_exactly( unsigned char f1, unsigned char f2, unsigned char bits )
193
{
194
  return ( ( f1 ^ f2 ) == bits );
195
}*/
196

UNCOV
197
void UoConvertMain::update_map( const std::string& realm, unsigned short x, unsigned short y )
×
198
{
199
  auto mapwriter = new MapWriter();
×
UNCOV
200
  mapwriter->OpenExistingFiles( realm );
×
201
  rawmapfullread();
×
202
  rawstaticfullread();
×
203
  unsigned short x_base = x / SOLIDX_X_SIZE * SOLIDX_X_SIZE;
×
204
  unsigned short y_base = y / SOLIDX_Y_SIZE * SOLIDX_Y_SIZE;
×
205

206
  ProcessSolidBlock( x_base, y_base, *mapwriter );
×
UNCOV
207
  delete mapwriter;
×
208
  INFO_PRINTLN( "empty={}, nonempty={}\nwith more_solids: {}\ntotal statics={}", empty, nonempty,
×
209
                with_more_solids, total_statics );
×
210
}
×
211

212
void UoConvertMain::create_map( const std::string& realm, unsigned short width,
4✔
213
                                unsigned short height )
214
{
215
  auto mapwriter = new MapWriter();
4✔
216
  INFO_PRINT(
4✔
217
      "Creating map base and solids files.\n"
218
      "  Realm: {}\n"
219
      "  Map ID: {}\n"
220
      "  Reading UOP file: {}\n"
221
      "  Use Dif files: {}\n"
222
      "  Size: {}x{}\n"
223
      "Initializing files: ",
224
      realm, uo_mapid, ( uo_readuop ? "Yes" : "No" ), ( uo_usedif ? "Yes" : "No" ), uo_map_width,
4✔
225
      uo_map_height );
226
  mapwriter->CreateNewFiles( realm, width, height );
4✔
227
  INFO_PRINTLN( "Done." );
4✔
228
  Tools::Timer<> timer;
4✔
229
  rawmapfullread();
4✔
230
  rawstaticfullread();
4✔
231
  INFO_PRINTLN( "  Reading mapfiles time: {} ms.", timer.ellapsed() );
4✔
232
  for ( unsigned short y_base = 0; y_base < height; y_base += SOLIDX_Y_SIZE )
1,252✔
233
  {
234
    for ( unsigned short x_base = 0; x_base < width; x_base += SOLIDX_X_SIZE )
131,936✔
235
    {
236
      ProcessSolidBlock( x_base, y_base, *mapwriter );
130,688✔
237
    }
238
    INFO_PRINT( "\rConverting: {}%", y_base * 100 / height );
1,248✔
239
  }
240
  timer.stop();
4✔
241

242
  mapwriter->WriteConfigFile();
4✔
243
  delete mapwriter;
4✔
244

245
  INFO_PRINTLN(
4✔
246
      "\rConversion complete.\n"
247
      "Conversion details:\n"
248
      "  Total blocks: {}\n"
249
      "  Blocks with solids: {} ({}%)\n"
250
      "  Blocks without solids: {} ({}%)\n"
251
      "  Locations with solids: {}\n"
252
      "  Total number of solids: {}\n"
253
      "  Elapsed time: {} ms.",
UNCOV
254
      empty + nonempty, nonempty, ( nonempty * 100 / ( empty + nonempty ) ), empty,
×
255
      ( empty * 100 / ( empty + nonempty ) ), with_more_solids, total_statics, timer.ellapsed() );
4✔
256
}
4✔
257

258
static bool is_no_draw( USTRUCT_MAPINFO& mi )
66,833,168✔
259
{
260
  return ( mi.landtile == 0x2 );
66,833,168✔
261
}
262

263
static bool is_cave_exit( USTRUCT_MAPINFO& mi )
66,833,168✔
264
{
265
  return ( mi.landtile == 0x7ec || mi.landtile == 0x7ed || mi.landtile == 0x7ee ||
66,833,168✔
266
           mi.landtile == 0x7ef || mi.landtile == 0x7f0 || mi.landtile == 0x7f1 ||
66,833,168✔
267
           mi.landtile == 0x834 || mi.landtile == 0x835 || mi.landtile == 0x836 ||
66,833,168✔
268
           mi.landtile == 0x837 || mi.landtile == 0x838 || mi.landtile == 0x839 ||
66,833,168✔
269
           mi.landtile == 0x1d3 || mi.landtile == 0x1d4 || mi.landtile == 0x1d5 ||
66,833,168✔
270
           mi.landtile == 0x1d6 || mi.landtile == 0x1d7 || mi.landtile == 0x1d8 ||
66,833,168✔
271
           mi.landtile == 0x1d9 || mi.landtile == 0x1da );
133,666,336✔
272
}
273

274
static bool is_cave_shadow( USTRUCT_MAPINFO& mi )
75,197,200✔
275
{
276
  return ( mi.landtile == 0x1db ||  // shadows above caves
150,394,400✔
277
           mi.landtile == 0x1ae ||  // more shadows above caves
75,197,200✔
278
           mi.landtile == 0x1af || mi.landtile == 0x1b0 || mi.landtile == 0x1b1 ||
75,197,200✔
279
           mi.landtile == 0x1b2 || mi.landtile == 0x1b3 || mi.landtile == 0x1b4 ||
225,591,600✔
280
           mi.landtile == 0x1b5 );
150,394,400✔
281
}
282

283
short get_lowestadjacentz( unsigned short x, unsigned short y, short z )
8,364,032✔
284
{
285
  USTRUCT_MAPINFO mi;
286
  short z0;
287
  short lowest_z = z;
8,364,032✔
288
  bool cave_override = false;
8,364,032✔
289

290
  if ( ( x - 1 >= 0 ) && ( y - 1 >= 0 ) )
8,364,032✔
291
  {
292
    safe_getmapinfo( x - 1, y - 1, &z0, &mi );
8,350,852✔
293

294
    if ( is_cave_shadow( mi ) || is_cave_exit( mi ) )
8,350,852✔
UNCOV
295
      z0 = z;
×
296

297
    if ( is_no_draw( mi ) )
8,350,852✔
UNCOV
298
      cave_override = true;
×
299

300
    if ( z0 < lowest_z )
8,350,852✔
301
    {
302
      lowest_z = z0;
25,432✔
303
    }
304
  }
305

306
  if ( x - 1 >= 0 )
8,364,032✔
307
  {
308
    safe_getmapinfo( x - 1, y, &z0, &mi );
8,354,048✔
309

310
    if ( is_cave_shadow( mi ) || is_cave_exit( mi ) )
8,354,048✔
UNCOV
311
      z0 = z;
×
312

313
    if ( is_no_draw( mi ) )
8,354,048✔
UNCOV
314
      cave_override = true;
×
315

316
    if ( z0 < lowest_z )
8,354,048✔
317
    {
318
      lowest_z = z0;
8✔
319
    }
320
  }
321

322
  if ( ( x - 1 >= 0 ) && ( y + 1 < uo_map_height ) )
8,364,032✔
323
  {
324
    safe_getmapinfo( x - 1, y + 1, &z0, &mi );
8,350,852✔
325

326
    if ( is_cave_shadow( mi ) || is_cave_exit( mi ) )
8,350,852✔
UNCOV
327
      z0 = z;
×
328

329
    if ( is_no_draw( mi ) )
8,350,852✔
UNCOV
330
      cave_override = true;
×
331

332
    if ( z0 < lowest_z )
8,350,852✔
333
    {
334
      lowest_z = z0;
8,904✔
335
    }
336
  }
337

338
  if ( y - 1 >= 0 )
8,364,032✔
339
  {
340
    safe_getmapinfo( x, y - 1, &z0, &mi );
8,360,832✔
341

342
    if ( is_cave_shadow( mi ) || is_cave_exit( mi ) )
8,360,832✔
UNCOV
343
      z0 = z;
×
344

345
    if ( is_no_draw( mi ) )
8,360,832✔
UNCOV
346
      cave_override = true;
×
347

348
    if ( z0 < lowest_z )
8,360,832✔
349
    {
350
      lowest_z = z0;
8✔
351
    }
352
  }
353

354
  if ( ( y - 1 >= 0 ) && ( x + 1 < uo_map_width ) )
8,364,032✔
355
  {
356
    safe_getmapinfo( x + 1, y - 1, &z0, &mi );
8,350,852✔
357

358
    if ( is_cave_shadow( mi ) || is_cave_exit( mi ) )
8,350,852✔
UNCOV
359
      z0 = z;
×
360

361
    if ( is_no_draw( mi ) )
8,350,852✔
UNCOV
362
      cave_override = true;
×
363

364
    if ( z0 < lowest_z )
8,350,852✔
365
    {
366
      lowest_z = z0;
29,248✔
367
    }
368
  }
369

370
  if ( x + 1 < uo_map_width )
8,364,032✔
371
  {
372
    safe_getmapinfo( x + 1, y, &z0, &mi );
8,354,048✔
373

374
    if ( is_cave_shadow( mi ) || is_cave_exit( mi ) )
8,354,048✔
UNCOV
375
      z0 = z;
×
376

377
    if ( is_no_draw( mi ) )
8,354,048✔
UNCOV
378
      cave_override = true;
×
379

380
    if ( z0 < lowest_z )
8,354,048✔
381
    {
UNCOV
382
      lowest_z = z0;
×
383
    }
384
  }
385

386
  if ( ( x + 1 < uo_map_width ) && ( y + 1 < uo_map_height ) )
8,364,032✔
387
  {
388
    safe_getmapinfo( x + 1, y + 1, &z0, &mi );
8,350,852✔
389

390
    if ( is_cave_shadow( mi ) || is_cave_exit( mi ) )
8,350,852✔
UNCOV
391
      z0 = z;
×
392

393
    if ( is_no_draw( mi ) )
8,350,852✔
UNCOV
394
      cave_override = true;
×
395

396
    if ( z0 < lowest_z )
8,350,852✔
397
    {
398
      lowest_z = z0;
8✔
399
    }
400
  }
401

402
  if ( y + 1 < uo_map_height )
8,364,032✔
403
  {
404
    safe_getmapinfo( x, y + 1, &z0, &mi );
8,360,832✔
405

406
    if ( is_cave_shadow( mi ) || is_cave_exit( mi ) )
8,360,832✔
UNCOV
407
      z0 = z;
×
408

409
    if ( is_no_draw( mi ) )
8,360,832✔
UNCOV
410
      cave_override = true;
×
411

412
    if ( z0 < lowest_z )
8,360,832✔
413
    {
UNCOV
414
      lowest_z = z0;
×
415
    }
416
  }
417

418
  if ( cave_override )
8,364,032✔
UNCOV
419
    return z;
×
420
  return lowest_z;
8,364,032✔
421
}
422

423
void UoConvertMain::ProcessSolidBlock( unsigned short x_base, unsigned short y_base,
130,688✔
424
                                       MapWriter& mapwriter )
425
{
426
  unsigned int idx2_offset = 0;
130,688✔
427
  SOLIDX2_ELEM idx2_elem;
428
  memset( &idx2_elem, 0, sizeof idx2_elem );
130,688✔
429
  idx2_elem.baseindex = mapwriter.NextSolidIndex();
130,688✔
430

431
  unsigned short x_add_max = SOLIDX_X_SIZE, y_add_max = SOLIDX_Y_SIZE;
130,688✔
432
  if ( x_base + x_add_max > uo_map_width )
130,688✔
UNCOV
433
    x_add_max = uo_map_width - x_base;
×
434
  if ( y_base + y_add_max > uo_map_height )
130,688✔
UNCOV
435
    y_add_max = uo_map_height - y_base;
×
436

437
  for ( unsigned short x_add = 0; x_add < x_add_max; ++x_add )
1,176,192✔
438
  {
439
    for ( unsigned short y_add = 0; y_add < y_add_max; ++y_add )
9,409,536✔
440
    {
441
      unsigned short x = x_base + x_add;
8,364,032✔
442
      unsigned short y = y_base + y_add;
8,364,032✔
443

444
      StaticList statics;
8,364,032✔
445

446
      // read the map, and treat it like a static.
447
      short z;
448
      USTRUCT_MAPINFO mi;
449

450
      safe_getmapinfo( x, y, &z, &mi );
8,364,032✔
451

452
      if ( mi.landtile > 0x3FFF )
8,364,032✔
UNCOV
453
        INFO_PRINTLN( "Tile {:#x} at ({},{},{}) is an invalid ID!", mi.landtile, x, y, z );
×
454

455
      // for water, don't average with surrounding tiles.
456
      if ( Plib::landtile_uoflags_read( mi.landtile ) & USTRUCT_TILE::FLAG_LIQUID )
8,364,032✔
457
        z = mi.z;
725,760✔
458
      short low_z = get_lowestadjacentz( x, y, z );
8,364,032✔
459

460
      short lt_height = z - low_z;
8,364,032✔
461
      z = low_z;
8,364,032✔
462

463
      if ( mi.landtile > 0x3FFF )
8,364,032✔
UNCOV
464
        INFO_PRINTLN( "Tile {:#x} at ({},{},{}) is an invalid ID!", mi.landtile, x, y, z );
×
465

466
      unsigned int lt_flags = Plib::landtile_uoflags_read( mi.landtile );
8,364,032✔
467
      if ( ~lt_flags & USTRUCT_TILE::FLAG_BLOCKING )
8,364,032✔
468
      {  // this seems to be the default.
469
        lt_flags |= USTRUCT_TILE::FLAG_PLATFORM;
7,638,272✔
470
      }
471
      lt_flags |=
8,364,032✔
472
          USTRUCT_TILE::FLAG_NO_SHOOT;  // added to make sure people using noshoot will have shapes
473
      // generated by this tile in future block LOS, shouldn't
474
      // affect people using old LOS method one way or another.
475
      lt_flags |= USTRUCT_TILE::FLAG_FLOOR;
8,364,032✔
476
      lt_flags |= USTRUCT_TILE::FLAG_HALF_HEIGHT;  // the entire map is this way
8,364,032✔
477

478
      if ( lt_flags & USTRUCT_TILE::FLAG_WALL )
8,364,032✔
UNCOV
479
        lt_height = 20;
×
480

481
      readstatics( statics, x, y,
8,364,032✔
482
                   USTRUCT_TILE::FLAG_BLOCKING | USTRUCT_TILE::FLAG_PLATFORM |
483
                       USTRUCT_TILE::FLAG_HALF_HEIGHT | USTRUCT_TILE::FLAG_LIQUID |
484
                       USTRUCT_TILE::FLAG_HOVEROVER
485
                   // USTRUCT_TILE::FLAG__WALK
486
      );
487

488
      for ( unsigned i = 0; i < statics.size(); ++i )
8,364,384✔
489
      {
490
        StaticRec srec = statics[i];
352✔
491

492
        unsigned int polflags = polflags_from_tileflags( srec.graphic, srec.flags, cfg_use_no_shoot,
704✔
493
                                                         cfg_LOS_through_windows );
352✔
494

495
        if ( ( ~polflags & FLAG::MOVELAND ) && ( ~polflags & FLAG::MOVESEA ) &&
352✔
496
             ( ~polflags & FLAG::BLOCKSIGHT ) && ( ~polflags & FLAG::BLOCKING ) &&
196✔
UNCOV
497
             ( ~polflags & FLAG::OVERFLIGHT ) )
×
498
        {
499
          // remove it.  we'll re-sort later.
500
          statics.erase( statics.begin() + i );
×
UNCOV
501
          --i;  // do-over
×
502
        }
503
        if ( ( ~srec.flags & USTRUCT_TILE::FLAG_BLOCKING ) &&
352✔
504
             ( ~srec.flags & USTRUCT_TILE::FLAG_PLATFORM ) &&
156✔
UNCOV
505
             ( ~srec.flags & USTRUCT_TILE::FLAG_HALF_HEIGHT ) &&
×
UNCOV
506
             ( ~srec.flags & USTRUCT_TILE::FLAG_LIQUID ) &&
×
UNCOV
507
             ( ~srec.flags & USTRUCT_TILE::FLAG_HOVEROVER ) )
×
508
        /*(~srec.flags & USTRUCT_TILE::FLAG__WALK)*/
509
        {
510
          // remove it.  we'll re-sort later.
UNCOV
511
          statics.erase( statics.begin() + i );
×
UNCOV
512
          --i;  // do-over
×
513
        }
514
      }
515

516
      bool addMap = true;
8,364,032✔
517

518
      for ( const auto& srec : statics )
8,364,384✔
519
      {
520
        // Look for water tiles. If there are any, discard the map (which is usually at -15 anyway)
521
        if ( z + lt_height <= srec.z &&
1,056✔
522
             // only where the map is below or same Z as the static
523
             ( ( srec.z - ( z + lt_height ) ) <= 10 ) && DiscardedWaterTypes.count( srec.graphic ) )
352✔
524
        {
525
          // arr, there be water here
UNCOV
526
          addMap = false;
×
527
        }
528

529
        // if there's a static on top of one of these "wall" landtiles, make it override.
530
        if ( ( lt_flags & USTRUCT_TILE::FLAG_WALL ) &&  // wall?
352✔
531
             z <= srec.z && srec.z - z <= lt_height )
×
532
        {
UNCOV
533
          lt_height = srec.z - z;
×
534
        }
535
      }
536
      // shadows above caves
537
      if ( is_cave_shadow( mi ) && !statics.empty() )
8,364,032✔
538
      {
UNCOV
539
        addMap = false;
×
540
      }
541

542

543
      // If the map is a NODRAW tile, and there are statics, discard the map tile
544
      if ( mi.landtile == 2 && !statics.empty() )
8,364,032✔
UNCOV
545
        addMap = false;
×
546

547
      if ( addMap )
8,364,032✔
UNCOV
548
        statics.emplace_back( 0, static_cast<signed char>( z ), lt_flags,
×
549
                              static_cast<char>( lt_height ) );
8,364,032✔
550

551
      sort( statics.begin(), statics.end(), StaticsByZ() );
8,364,032✔
552
      reverse( statics.begin(), statics.end() );
8,364,032✔
553

554
      std::vector<MapShape> shapes;
8,364,032✔
555

556
      // try to consolidate like shapes, and discard ones we don't care about.
557
      while ( !statics.empty() )
16,728,416✔
558
      {
559
        StaticRec srec = statics.back();
8,364,384✔
560
        statics.pop_back();
8,364,384✔
561

562
        unsigned int polflags = polflags_from_tileflags( srec.graphic, srec.flags, cfg_use_no_shoot,
16,728,768✔
563
                                                         cfg_LOS_through_windows );
8,364,384✔
564
        if ( ( ~polflags & FLAG::MOVELAND ) && ( ~polflags & FLAG::MOVESEA ) &&
8,364,384✔
565
             ( ~polflags & FLAG::BLOCKSIGHT ) && ( ~polflags & FLAG::BLOCKING ) &&
196✔
UNCOV
566
             ( ~polflags & FLAG::OVERFLIGHT ) )
×
567
        {
UNCOV
568
          passert_always( 0 );
×
569
          continue;
8,364,376✔
570
        }
571
        if ( ( ~srec.flags & USTRUCT_TILE::FLAG_BLOCKING ) &&
8,364,384✔
572
             ( ~srec.flags & USTRUCT_TILE::FLAG_PLATFORM ) &&
7,638,428✔
573
             ( ~srec.flags & USTRUCT_TILE::FLAG_HALF_HEIGHT ) &&
×
UNCOV
574
             ( ~srec.flags & USTRUCT_TILE::FLAG_LIQUID ) &&
×
UNCOV
575
             ( ~srec.flags & USTRUCT_TILE::FLAG_HOVEROVER ) )
×
576
        /*(~srec.flags & USTRUCT_TILE::FLAG__WALK)*/
577
        {
578
          passert_always( 0 );
×
579
          continue;
580
        }
581

582
        if ( shapes.empty() )
8,364,384✔
583
        {
584
          // this, whatever it is, is the map base.
585
          // TODO: look for water statics and use THOSE as the map.
586
          MapShape shape;
587
          shape.z = srec.z;  // these will be converted below to
8,364,032✔
588
          shape.height = 0;  // make the map "solid"
8,364,032✔
589
          shape.flags = static_cast<unsigned char>( polflags );
8,364,032✔
590
          // no matter what, the lowest level is gradual
591
          shape.flags |= FLAG::GRADUAL;
8,364,032✔
592
          shapes.push_back( shape );
8,364,032✔
593

594
          // for wall flag - map tile always height 0, at bottom. if map tile has height, add it as
595
          // a static
596
          if ( srec.height != 0 )
8,364,032✔
597
          {
598
            MapShape _shape;
599
            _shape.z = srec.z;
63,576✔
600
            _shape.height = srec.height;
63,576✔
601
            _shape.flags = polflags;
63,576✔
602
            shapes.push_back( _shape );
63,576✔
603
          }
604
          continue;
8,364,032✔
605
        }
8,364,032✔
606

607
        MapShape& prev = shapes.back();
352✔
608
        // we're adding it.
609
        MapShape shape;
610
        shape.z = srec.z;
352✔
611
        shape.height = srec.height;
352✔
612
        shape.flags = polflags;
352✔
613

614
        // always add the map shape seperately
615
        if ( shapes.size() == 1 )
352✔
616
        {
617
          shapes.push_back( shape );
208✔
618
          continue;
208✔
619
        }
620

621
        if ( shape.z < prev.z + prev.height )
144✔
622
        {
623
          // things can't exist in the same place.
624
          // shrink the bottom part of this shape.
625
          // if that would give it negative height, then skip it.
UNCOV
626
          short height_remove = prev.z + prev.height - shape.z;
×
UNCOV
627
          if ( height_remove <= shape.height )
×
628
          {
UNCOV
629
            shape.z += height_remove;
×
UNCOV
630
            shape.height -= height_remove;
×
631
          }
632
          else
633
          {  // example: 5530, 14
634
            continue;
×
635
          }
636
        }
637

638
        // sometimes water has "sand" a couple z-coords above it.
639
        // We'll try to detect this (really, anything that is up to 4 dist from water)
640
        // and extend the thing above downward.
641
        if ( ( prev.flags & FLAG::MOVESEA ) && ( shape.z > prev.z + prev.height ) &&
144✔
UNCOV
642
             ( shape.z <= prev.z + prev.height + 4 ) )
×
643
        {
UNCOV
644
          short height_add = shape.z - prev.z - prev.height;
×
UNCOV
645
          shape.z -= height_add;
×
UNCOV
646
          shape.height += height_add;
×
647
        }
648
        if ( ( prev.flags & FLAG::MOVESEA ) && ( prev.z + prev.height == -5 ) &&
144✔
649
             ( shape.flags & FLAG::MOVESEA ) && ( shape.z == 25 ) )
×
650
        {
651
          // oddly, there are some water tiles at z=25 in some places...I don't get it
UNCOV
652
          continue;
×
653
        }
654

655
        // string prevflags_s = flagstr(prev.flags);
656
        // const char* prevflags = prevflags_s.c_str();
657
        // string shapeflags_s = flagstr(shape.flags);
658
        // const char* shapeflags = shapeflags_s.c_str();
659

660
        if ( shape.z > prev.z + prev.height )
144✔
661
        {
662
          //
663
          // elevated above what's below, must include separately
664
          //
665

666
          shapes.push_back( shape );
96✔
667
          continue;
96✔
668
        }
669

670
        passert_always( shape.z == prev.z + prev.height );
48✔
671

672
        if ( shape.z == prev.z + prev.height )
48✔
673
        {
674
          //
675
          // sitting right on top of the previous solid
676
          //
677

678
          // standable atop non-standable: standable
679
          // nonstandable atop standable: nonstandable
680
          // etc
681
          bool can_combine =
682
              flags_match( prev.flags, shape.flags, FLAG::BLOCKSIGHT | FLAG::BLOCKING );
48✔
683
          if ( prev.flags & FLAG::MOVELAND && ~shape.flags & FLAG::BLOCKING &&
48✔
UNCOV
684
               ~shape.flags & FLAG::MOVELAND )
×
685
          {
UNCOV
686
            can_combine = false;
×
687
          }
688

689
          if ( can_combine )
48✔
690
          {
691
            prev.flags = shape.flags;
8✔
692
            prev.height += shape.height;
8✔
693
          }
694
          else  // if one blocks LOS, but not the other, they can't be combined this way.
695
          {
696
            shapes.push_back( shape );
40✔
697
            continue;
40✔
698
          }
699
        }
700
      }
701

702
      // the first StaticShape is the map base.
703
      MapShape base = shapes[0];
8,364,032✔
704
      shapes.erase( shapes.begin() );
8,364,032✔
705
      MAPCELL cell;
706
      passert_always( base.height == 0 );
8,364,032✔
707
      cell.z = static_cast<signed char>(
8,364,032✔
708
          base.z );  // assume now map has height=1. a static was already added if it was >0
8,364,032✔
709
      cell.flags = static_cast<u8>( base.flags );
8,364,032✔
710
      if ( !shapes.empty() )
8,364,032✔
711
        cell.flags |= FLAG::MORE_SOLIDS;
63,784✔
712

713
      mapwriter.SetMapCell( x, y, cell );
8,364,032✔
714

715
      if ( !shapes.empty() )
8,364,032✔
716
      {
717
        ++with_more_solids;
63,784✔
718
        total_statics += static_cast<unsigned int>( shapes.size() );
63,784✔
719
        if ( idx2_offset == 0 )
63,784✔
720
          idx2_offset = mapwriter.NextSolidx2Offset();
3,200✔
721

722
        unsigned int addindex = mapwriter.NextSolidIndex() - idx2_elem.baseindex;
63,784✔
723
        if ( addindex > std::numeric_limits<unsigned short>::max() )
63,784✔
UNCOV
724
          throw std::runtime_error( "addoffset overflow" );
×
725
        idx2_elem.addindex[x_add][y_add] = static_cast<unsigned short>( addindex );
63,784✔
726
        int count = static_cast<int>( shapes.size() );
63,784✔
727
        for ( int j = 0; j < count; ++j )
127,704✔
728
        {
729
          MapShape shape = shapes[j];
63,920✔
730
          char _z, height, flags;
731
          _z = static_cast<char>( shapes[j].z );
63,920✔
732
          height = static_cast<char>( shape.height );
63,920✔
733
          flags = static_cast<u8>( shape.flags );
63,920✔
734
          if ( !height )  // make 0 height solid
63,920✔
735
          {
736
            --_z;
144✔
737
            ++height;
144✔
738
          }
739

740
          if ( j != count - 1 )
63,920✔
741
            flags |= FLAG::MORE_SOLIDS;
136✔
742
          SOLIDS_ELEM solid;
743
          solid.z = _z;
63,920✔
744
          solid.height = height;
63,920✔
745
          solid.flags = flags;
63,920✔
746
          mapwriter.AppendSolid( solid );
63,920✔
747
        }
748
      }
749
    }
8,364,032✔
750
  }
751
  if ( idx2_offset )
130,688✔
752
  {
753
    ++nonempty;
3,200✔
754
    mapwriter.AppendSolidx2Elem( idx2_elem );
3,200✔
755
  }
756
  else
757
  {
758
    ++empty;
127,488✔
759
  }
760
  mapwriter.SetSolidx2Offset( x_base, y_base, idx2_offset );
130,688✔
761
}
130,688✔
762

763
std::string UoConvertMain::resolve_type_from_id( unsigned id ) const
20✔
764
{
765
  if ( BoatTypes.count( id ) )
20✔
766
    return "Boat";
12✔
767
  return "Multi";
8✔
768
}
769

770
void UoConvertMain::write_multi_element( FILE* multis_cfg, const USTRUCT_MULTI_ELEMENT& elem,
820✔
771
                                         const std::string& mytype, bool& first )
772
{
773
  if ( elem.graphic == GRAPHIC_NODRAW )
820✔
774
    return;
×
775

776
  std::string type = elem.flags ? "static" : "dynamic";
820✔
777

778
  if ( mytype == "Boat" && first && elem.graphic != 1 )
820✔
779
    type = "static";
12✔
780

781
  std::string comment;
820✔
782
  if ( cfg_use_new_hsa_format )
820✔
783
  {
784
    USTRUCT_TILE_HSA tile;
UNCOV
785
    readtile( elem.graphic, &tile );
×
UNCOV
786
    comment.assign( tile.name, sizeof( tile.name ) );
×
787
  }
788
  else
789
  {
790
    USTRUCT_TILE tile;
791
    readtile( elem.graphic, &tile );
820✔
792
    comment.assign( tile.name, sizeof( tile.name ) );
820✔
793
  }
794

795
  fprintf( multis_cfg, "    %-7s 0x%04x %4d %4d %4d   // %s\n", type.c_str(), elem.graphic, elem.x,
1,640✔
796
           elem.y, elem.z, comment.c_str() );
820✔
797

798
  first = false;
820✔
799
}
820✔
800

UNCOV
801
void UoConvertMain::write_multi( FILE* multis_cfg, unsigned id,
×
802
                                 std::vector<Plib::USTRUCT_MULTI_ELEMENT>& multi_elems )
803
{
UNCOV
804
  std::string mytype = resolve_type_from_id( id );
×
805

806
  fprintf( multis_cfg, "%s 0x%x\n", mytype.c_str(), id );
×
UNCOV
807
  fprintf( multis_cfg, "{\n" );
×
808

UNCOV
809
  bool first = true;
×
UNCOV
810
  for ( const auto& elem : multi_elems )
×
811
  {
812
    write_multi_element( multis_cfg, elem, mytype, first );
×
813
  }
814

UNCOV
815
  fprintf( multis_cfg, "}\n\n" );
×
816
}
×
817

818
void UoConvertMain::write_multi( FILE* multis_cfg, unsigned id, FILE* multi_mul,
20✔
819
                                 unsigned int offset, unsigned int length )
820
{
821
  USTRUCT_MULTI_ELEMENT elem;
822
  unsigned int count =
20✔
823
      cfg_use_new_hsa_format ? length / sizeof( USTRUCT_MULTI_ELEMENT_HSA ) : length / sizeof elem;
824

825
  std::string mytype = resolve_type_from_id( id );
20✔
826

827
  fprintf( multis_cfg, "%s 0x%x\n", mytype.c_str(), id );
20✔
828
  fprintf( multis_cfg, "{\n" );
20✔
829

830
  if ( fseek( multi_mul, offset, SEEK_SET ) != 0 )
20✔
831
  {
UNCOV
832
    throw std::runtime_error( "write_multi(): fseek() failed" );
×
833
  }
834

835

836
  bool first = true;
20✔
837
  while ( count-- )
840✔
838
  {
839
    if ( fread( &elem, sizeof elem, 1, multi_mul ) != 1 )
820✔
840
    {
UNCOV
841
      throw std::runtime_error( "write_multi(): fread() failed" );
×
842
    }
843

844
    if ( cfg_use_new_hsa_format )
820✔
845
    {
UNCOV
846
      if ( fseek( multi_mul, 4, SEEK_CUR ) != 0 )
×
UNCOV
847
        throw std::runtime_error( "write_multi(): fseek() failed" );
×
848
    }
849

850
    write_multi_element( multis_cfg, elem, mytype, first );
820✔
851
  }
852

853
  fprintf( multis_cfg, "}\n\n" );
20✔
854
}
20✔
855

856
void UoConvertMain::create_multis_cfg( FILE* multi_idx, FILE* multi_mul, FILE* multis_cfg )
1✔
857
{
858
  if ( fseek( multi_idx, 0, SEEK_SET ) != 0 )
1✔
UNCOV
859
    throw std::runtime_error( "create_multis_cfg: fseek failed" );
×
860
  unsigned count = 0;
1✔
861
  USTRUCT_IDX idxrec;
862
  for ( int i = 0; fread( &idxrec, sizeof idxrec, 1, multi_idx ) == 1; ++i )
16,384✔
863
  {
864
    const USTRUCT_VERSION* vrec = nullptr;
16,383✔
865

866
    if ( check_verdata( VERFILE_MULTI_MUL, i, vrec ) )
16,383✔
867
    {
UNCOV
868
      write_multi( multis_cfg, i, verfile, vrec->filepos, vrec->length );
×
UNCOV
869
      ++count;
×
870
    }
871
    else
872
    {
873
      if ( idxrec.offset == 0xFFffFFffLu )
16,383✔
874
        continue;
16,363✔
875

876
      write_multi( multis_cfg, i, multi_mul, idxrec.offset, idxrec.length );
20✔
877
      ++count;
20✔
878
    }
879
  }
880
  INFO_PRINTLN( "{} multi definitions written to multis.cfg", count );
1✔
881
}
1✔
882

883
void UoConvertMain::create_multis_cfg()
1✔
884
{
885
  std::map<unsigned int, std::vector<USTRUCT_MULTI_ELEMENT>> multi_map;
1✔
886

887
  std::string outdir = programArgsFindEquals( "outdir=", "." );
1✔
888
  FILE* multis_cfg = fopen( ( outdir + "/multis.cfg" ).c_str(), "wt" );
1✔
889

890
  if ( open_uopmulti_file( multi_map ) )
1✔
891
  {
UNCOV
892
    for ( auto& [id, elems] : multi_map )
×
893
    {
UNCOV
894
      write_multi( multis_cfg, id, elems );
×
895
    }
896

UNCOV
897
    INFO_PRINTLN( "{} multi definitions written to multis.cfg", multi_map.size() );
×
898

UNCOV
899
    return;
×
900
  }
901

902
  FILE* multi_idx = open_uo_file( "multi.idx" );
1✔
903
  FILE* multi_mul = open_uo_file( "multi.mul" );
1✔
904

905
  create_multis_cfg( multi_idx, multi_mul, multis_cfg );
1✔
906

907
  fclose( multis_cfg );
1✔
908

909
  fclose( multi_idx );
1✔
910
  fclose( multi_mul );
1✔
911
}
1✔
912
void UoConvertMain::write_flags( FILE* fp, unsigned int flags )
117✔
913
{
914
  if ( flags & FLAG::MOVELAND )
117✔
915
    fprintf( fp, "    MoveLand 1\n" );
27✔
916
  if ( flags & FLAG::MOVESEA )
117✔
917
    fprintf( fp, "    MoveSea 1\n" );
1✔
918
  if ( flags & FLAG::BLOCKSIGHT )
117✔
919
    fprintf( fp, "    BlockSight 1\n" );
92✔
920
  if ( ~flags & FLAG::OVERFLIGHT )
117✔
921
    fprintf( fp, "    OverFlight 0\n" );
117✔
922
  if ( flags & FLAG::ALLOWDROPON )
117✔
923
    fprintf( fp, "    AllowDropOn 1\n" );
27✔
924
  if ( flags & FLAG::GRADUAL )
117✔
925
    fprintf( fp, "    Gradual 1\n" );
4✔
926
  if ( flags & FLAG::STACKABLE )
117✔
927
    fprintf( fp, "    Stackable 1\n" );
2✔
928
  if ( flags & FLAG::BLOCKING )
117✔
929
    fprintf( fp, "    Blocking 1\n" );
67✔
930
  if ( flags & FLAG::MOVABLE )
117✔
931
    fprintf( fp, "    Movable 1\n" );
48✔
932
  if ( flags & FLAG::EQUIPPABLE )
117✔
933
    fprintf( fp, "    Equippable 1\n" );
4✔
934
  if ( flags & FLAG::DESC_PREPEND_A )
117✔
935
    fprintf( fp, "    DescPrependA 1\n" );
2✔
936
  if ( flags & FLAG::DESC_PREPEND_AN )
117✔
937
    fprintf( fp, "    DescPrependAn 1\n" );
×
938
}
117✔
939

940
void UoConvertMain::create_tiles_cfg()
1✔
941
{
942
  std::string outdir = programArgsFindEquals( "outdir=", "." );
1✔
943
  FILE* fp = fopen( ( outdir + "/tiles.cfg" ).c_str(), "wt" );
1✔
944
  int mountCount;
945
  char name[21];
946

947
  unsigned count = 0;
1✔
948
  for ( unsigned int graphic_i = 0; graphic_i <= Plib::systemstate.config.max_tile_id; ++graphic_i )
16,385✔
949
  {
950
    u16 graphic = static_cast<u16>( graphic_i );
16,384✔
951
    USTRUCT_TILE tile;
952
    if ( cfg_use_new_hsa_format )
16,384✔
953
    {
954
      USTRUCT_TILE_HSA newtile;
UNCOV
955
      read_objinfo( graphic, newtile );
×
UNCOV
956
      tile.anim = newtile.anim;
×
UNCOV
957
      tile.flags = newtile.flags;
×
UNCOV
958
      tile.height = newtile.height;
×
UNCOV
959
      tile.layer = newtile.layer;
×
UNCOV
960
      memcpy( tile.name, newtile.name, sizeof tile.name );
×
UNCOV
961
      tile.unk14 = newtile.unk14;
×
962
      tile.unk15 = newtile.unk15;
×
963
      tile.unk6 = newtile.unk6;
×
UNCOV
964
      tile.unk7 = newtile.unk7;
×
UNCOV
965
      tile.unk8 = newtile.unk8;
×
UNCOV
966
      tile.unk9 = newtile.unk9;
×
UNCOV
967
      tile.weight = newtile.weight;
×
968
    }
969
    else
970
      read_objinfo( graphic, tile );
16,384✔
971
    mountCount = static_cast<int>( MountTypes.count( graphic ) );
16,384✔
972

973
    if ( tile.name[0] == '\0' && tile.flags == 0 && tile.layer == 0 && tile.height == 0 &&
16,384✔
974
         mountCount == 0 )
975
    {
976
      continue;
16,269✔
977
    }
978
    unsigned int flags =
979
        polflags_from_tileflags( graphic, tile.flags, cfg_use_no_shoot, cfg_LOS_through_windows );
115✔
980
    if ( mountCount != 0 )
115✔
981
    {
UNCOV
982
      tile.layer = 25;
×
UNCOV
983
      flags |= FLAG::EQUIPPABLE;
×
984
    }
985

986
    memset( name, 0, sizeof name );
115✔
987
    memcpy( name, tile.name, sizeof tile.name );
115✔
988

989
    fprintf( fp, "tile 0x%x\n", graphic );
115✔
990
    fprintf( fp, "{\n" );
115✔
991
    fprintf( fp, "    Desc %s\n", name );
115✔
992
    fprintf( fp, "    UoFlags 0x%08lx\n", static_cast<unsigned long>( tile.flags ) );
115✔
993
    if ( tile.layer )
115✔
994
      fprintf( fp, "    Layer %u\n", tile.layer );
31✔
995
    if ( flags & FLAG::EQUIPPABLE )
115✔
996
      fprintf( fp, "    AnimID %u\n", tile.anim );
4✔
997
    if ( static_cast<unsigned long>( tile.flags ) & USTRUCT_TILE::FLAG_PARTIAL_HUE )
115✔
UNCOV
998
      fprintf( fp, "    PartialHue 1\n" );
×
999
    fprintf( fp, "    Height %u\n", tile.height );
115✔
1000
    fprintf( fp, "    Weight %u\n", tile.weight );
115✔
1001
    write_flags( fp, flags );
115✔
1002
    fprintf( fp, "}\n" );
115✔
1003
    fprintf( fp, "\n" );
115✔
1004
    ++count;
115✔
1005
  }
1006
  fclose( fp );
1✔
1007

1008
  INFO_PRINTLN( "{} tile definitions written to tiles.cfg", count );
1✔
1009
}
1✔
1010

1011
void UoConvertMain::create_landtiles_cfg()
1✔
1012
{
1013
  std::string outdir = programArgsFindEquals( "outdir=", "." );
1✔
1014
  FILE* fp = fopen( ( outdir + "/landtiles.cfg" ).c_str(), "wt" );
1✔
1015
  unsigned count = 0;
1✔
1016

1017
  for ( u16 i = 0; i <= 0x3FFF; ++i )
16,385✔
1018
  {
1019
    USTRUCT_LAND_TILE landtile;
1020
    if ( cfg_use_new_hsa_format )
16,384✔
1021
    {
1022
      USTRUCT_LAND_TILE_HSA newlandtile;
UNCOV
1023
      readlandtile( i, &newlandtile );
×
UNCOV
1024
      landtile.flags = newlandtile.flags;
×
UNCOV
1025
      landtile.unk = newlandtile.unk;
×
UNCOV
1026
      memcpy( landtile.name, newlandtile.name, sizeof landtile.name );
×
1027
    }
1028
    else
1029
      readlandtile( i, &landtile );
16,384✔
1030

1031
    if ( landtile.name[0] || landtile.flags )
16,384✔
1032
    {
1033
      fprintf( fp, "landtile 0x%x\n", i );
2✔
1034
      fprintf( fp, "{\n" );
2✔
1035
      fprintf( fp, "    Name %s\n", landtile.name );
2✔
1036
      fprintf( fp, "    UoFlags 0x%08lx\n", static_cast<unsigned long>( landtile.flags ) );
2✔
1037

1038
      unsigned int flags = polflags_from_landtileflags( i, landtile.flags );
2✔
1039
      flags &= ~FLAG::MOVABLE;  // movable makes no sense for landtiles
2✔
1040
      write_flags( fp, flags );
2✔
1041
      fprintf( fp, "}\n" );
2✔
1042
      fprintf( fp, "\n" );
2✔
1043
      ++count;
2✔
1044
    }
1045
  }
1046
  fclose( fp );
1✔
1047

1048
  INFO_PRINTLN( "{} landtile definitions written to landtiles.cfg", count );
1✔
1049
}
1✔
1050

1051
int UoConvertMain::main()
11✔
1052
{
1053
  const std::vector<std::string>& binArgs = programArgs();
11✔
1054

1055
  /**********************************************
1056
   * show help
1057
   **********************************************/
1058
  if ( binArgs.size() == 1 )
11✔
1059
  {
UNCOV
1060
    showHelp();
×
UNCOV
1061
    return 0;  // return "okay"
×
1062
  }
1063

1064
  // Setups uoconvert by finding the path of uo files and max tiles from pol.cfg or command
1065
  // line arguments. Also loads parameters from uoconvert.cfg.
1066
  setup_uoconvert();
11✔
1067

1068
  std::string command = binArgs[1];
11✔
1069
  if ( command == "uoptomul" )
11✔
1070
  {
UNCOV
1071
    if ( !convert_uop_to_mul() )
×
UNCOV
1072
      return 1;
×
1073
  }
1074
  else if ( command == "map" )
11✔
1075
  {
1076
    UoConvert::uo_mapid = programArgsFindEquals( "mapid=", 0, false );
4✔
1077
    UoConvert::uo_usedif = programArgsFindEquals( "usedif=", 1, false );
4✔
1078
    UoConvert::uo_readuop = (bool)programArgsFindEquals( "readuop=", 1, false );
4✔
1079

1080
    std::string realm = programArgsFindEquals( "realm=", "britannia" );
4✔
1081

1082
    UoConvert::open_uo_data_files();
4✔
1083
    UoConvert::read_uo_data();
4✔
1084

1085
    // Auto-detects defaults for mapid=0 or 1 based on the map size. All other sizes are fixed based
1086
    // on the mapid.
1087
    Pol::Plib::MUL::MapInfo mapinfo( UoConvert::uo_mapid, UoConvert::uo_map_size );
4✔
1088
    int default_width = mapinfo.width();
4✔
1089
    int default_height = mapinfo.height();
4✔
1090

1091
    if ( mapinfo.guessed() )
4✔
1092
      INFO_PRINTLN( "Auto-detected map dimensions: {}x{}", default_width, default_height );
2✔
1093

1094
    uo_map_width =
4✔
1095
        static_cast<unsigned short>( programArgsFindEquals( "width=", default_width, false ) );
4✔
1096
    uo_map_height =
4✔
1097
        static_cast<unsigned short>( programArgsFindEquals( "height=", default_height, false ) );
4✔
1098

1099
    check_for_errors_in_map_parameters();
4✔
1100

1101
    int x = programArgsFindEquals( "x=", -1, false );
4✔
1102
    int y = programArgsFindEquals( "y=", -1, false );
4✔
1103

1104
    // britannia: realm=main mapid=0 width=6144 height=4096
1105
    // ilshenar: realm=ilshenar mapid=2 width=2304 height=1600
1106
    // malas: realm=malas mapid=3 width=2560 height=2048
1107
    // tokuno: realm=tokuno mapid=4 width=1448 height=1448
1108
    // termur: realm=termur mapid=5 width=1280 height=4096
1109

1110
    if ( x >= 0 && y >= 0 )
4✔
1111
    {
UNCOV
1112
      UoConvertMain::update_map( realm, static_cast<u16>( x ), static_cast<u16>( y ) );
×
1113
    }
1114
    else
1115
    {
1116
      UoConvertMain::create_map( realm, static_cast<unsigned short>( uo_map_width ),
4✔
1117
                                 static_cast<unsigned short>( uo_map_height ) );
1118
    }
1119
  }
4✔
1120
  else if ( command == "statics" )
7✔
1121
  {
1122
    std::string realm = programArgsFindEquals( "realm=", "britannia" );
2✔
1123
    Plib::RealmDescriptor descriptor = Plib::RealmDescriptor::Load( realm );
2✔
1124

1125
    UoConvert::uo_mapid = descriptor.uomapid;
2✔
1126
    UoConvert::uo_usedif = descriptor.uodif;
2✔
1127
    UoConvert::uo_map_width = static_cast<unsigned short>( descriptor.width );
2✔
1128
    UoConvert::uo_map_height = static_cast<unsigned short>( descriptor.height );
2✔
1129

1130
    UoConvert::open_uo_data_files();
2✔
1131
    UoConvert::read_uo_data();
2✔
1132

1133
    write_pol_static_files( realm );
2✔
1134
  }
2✔
1135
  else if ( command == "multis" )
5✔
1136
  {
1137
    UoConvert::open_uo_data_files();
1✔
1138
    UoConvert::read_uo_data();
1✔
1139
    UoConvertMain::create_multis_cfg();
1✔
1140
  }
1141
  else if ( command == "tiles" )
4✔
1142
  {
1143
    UoConvert::open_uo_data_files();
1✔
1144
    UoConvert::read_uo_data();
1✔
1145
    UoConvertMain::create_tiles_cfg();
1✔
1146
  }
1147
  else if ( command == "landtiles" )
3✔
1148
  {
1149
    UoConvert::open_uo_data_files();
1✔
1150
    UoConvert::read_uo_data();
1✔
1151
    UoConvertMain::create_landtiles_cfg();
1✔
1152
  }
1153
  else if ( command == "maptile" )
2✔
1154
  {
1155
    std::string realm = programArgsFindEquals( "realm=", "britannia" );
2✔
1156
    Plib::RealmDescriptor descriptor = Plib::RealmDescriptor::Load( realm );
2✔
1157

1158
    UoConvert::uo_mapid = descriptor.uomapid;
2✔
1159
    UoConvert::uo_usedif = descriptor.uodif;
2✔
1160
    UoConvert::uo_map_width = static_cast<unsigned short>( descriptor.width );
2✔
1161
    UoConvert::uo_map_height = static_cast<unsigned short>( descriptor.height );
2✔
1162

1163
    UoConvert::open_uo_data_files();
2✔
1164
    UoConvert::read_uo_data();
2✔
1165

1166
    UoConvertMain::create_maptile( realm );
2✔
1167
  }
2✔
UNCOV
1168
  else if ( command == "flags" )
×
1169
  {
1170
    UoConvertMain::display_flags();
×
1171
  }
1172
  else  // unknown option
1173
  {
UNCOV
1174
    showHelp();
×
1175
    return 1;
×
1176
  }
1177
  UoConvert::clear_tiledata();
11✔
1178
  return 0;
11✔
1179
}
11✔
1180
void UoConvertMain::check_for_errors_in_map_parameters()
4✔
1181
{
1182
  if ( !MUL::Map::valid_size( UoConvert::uo_map_size, uo_map_width, uo_map_height ) )
4✔
1183
  {
1184
    size_t expected_size =
UNCOV
1185
        MUL::Map::blockSize * MUL::Map::expected_blocks( uo_map_width, uo_map_height );
×
1186

UNCOV
1187
    INFO_PRINTLN( "\nWarning: Width and height do not match the map size ({} bytes, expected {})",
×
1188
                  UoConvert::uo_map_size, expected_size );
1189

UNCOV
1190
    if ( uo_map_width == 0 || uo_map_height == 0 )
×
1191
      throw std::runtime_error(
×
1192
          "Width and height were not identified automatically. Please specify them manually." );
×
1193

1194

1195
    if ( ( uo_map_width % MUL::Map::blockWidth != 0 ) ||
×
UNCOV
1196
         ( uo_map_height % MUL::Map::blockHeight != 0 ) )
×
1197
      throw std::runtime_error( "Width and height must be divisible by 8" );
×
1198

UNCOV
1199
    if ( uo_map_size < expected_size )
×
UNCOV
1200
      throw std::runtime_error( "Map size is smaller than the given width and height" );
×
1201
  }
1202
}
4✔
UNCOV
1203
bool UoConvertMain::convert_uop_to_mul()
×
1204
{
1205
  // this is kludgy and doesn't take into account the UODataPath. Mostly a proof of concept now.
1206
  UoConvert::uo_mapid = programArgsFindEquals( "mapid=", 0, false );
×
1207

UNCOV
1208
  std::string mul_mapfile = "map" + to_string( uo_mapid ) + ".mul";
×
1209
  std::string uop_mapfile = "map" + to_string( uo_mapid ) + "LegacyMUL.uop";
×
1210

1211
  auto maphash = []( int mapid, size_t chunkidx )
×
1212
  { return HashLittle2( fmt::format( "build/map{}legacymul/{:08d}.dat", mapid, chunkidx ) ); };
×
1213

UNCOV
1214
  std::ifstream ifs( uop_mapfile, std::ifstream::binary );
×
UNCOV
1215
  if ( !ifs )
×
1216
  {
1217
    ERROR_PRINTLN( "Error when opening mapfile: {}", uop_mapfile );
×
1218
    return false;
×
1219
  }
1220

1221
  kaitai::kstream ks( &ifs );
×
UNCOV
1222
  uop_t uopfile( &ks );
×
1223

1224
  // TODO: read all blocks
UNCOV
1225
  std::map<uint64_t, uop_t::file_t*> filemap;
×
1226
  uop_t::block_addr_t* currentblock = uopfile.header()->firstblock();
×
1227
  for ( auto file : *currentblock->block_body()->files() )
×
1228
  {
UNCOV
1229
    if ( file == nullptr )
×
1230
      continue;
×
1231
    if ( file->decompressed_size() == 0 )
×
1232
      continue;
×
UNCOV
1233
    filemap[file->filehash()] = file;
×
1234
  }
1235

1236
  if ( uopfile.header()->nfiles() != filemap.size() )
×
1237
    INFO_PRINTLN( "Warning: not all chunks read ({}/{})", filemap.size(),
×
UNCOV
1238
                  uopfile.header()->nfiles() );
×
1239

UNCOV
1240
  std::ofstream ofs( mul_mapfile, std::ofstream::binary );
×
UNCOV
1241
  for ( size_t i = 0; i < filemap.size(); i++ )
×
1242
  {
UNCOV
1243
    auto fileitr = filemap.find( maphash( uo_mapid, i ) );
×
UNCOV
1244
    if ( fileitr == filemap.end() )
×
1245
    {
1246
      INFO_PRINTLN( "Couldn't find file hash: {}", maphash( uo_mapid, i ) );
×
UNCOV
1247
      continue;
×
1248
    }
1249

UNCOV
1250
    auto file = fileitr->second;
×
UNCOV
1251
    ofs << file->data()->filebytes();
×
1252
    INFO_PRINTLN( "Wrote: {}/{}", i + 1, filemap.size() );
×
1253
  }
1254
  INFO_PRINTLN( "Done converting." );
×
1255

1256
  return true;
×
UNCOV
1257
}
×
1258
void UoConvertMain::setup_uoconvert()
11✔
1259
{
1260
  std::string uodata_root = programArgsFindEquals( "uodata=", "" );
11✔
1261
  unsigned short max_tile =
1262
      static_cast<unsigned short>( programArgsFindEquals( "maxtileid=", 0x0, true ) );
11✔
1263

1264
  if ( max_tile )
11✔
1265
  {
UNCOV
1266
    INFO_PRINTLN( "Warning: maxtileid will be ignored and detected from tiledata.mul instead." );
×
1267
  }
1268

1269
  // read required parameters from pol.cfg
1270
  if ( uodata_root.empty() )
11✔
1271
  {
UNCOV
1272
    INFO_PRINTLN( "Reading pol.cfg." );
×
UNCOV
1273
    Clib::ConfigFile cf( "pol.cfg" );
×
UNCOV
1274
    Clib::ConfigElem elem;
×
1275

UNCOV
1276
    cf.readraw( elem );
×
1277

UNCOV
1278
    if ( uodata_root.empty() )
×
UNCOV
1279
      uodata_root = Plib::UOInstallFinder::remove_elem( elem );
×
UNCOV
1280
  }
×
1281

1282
  // Save the parameters into this ugly global state we have
1283
  Plib::systemstate.config.uo_datafile_root = Clib::normalized_dir_form( uodata_root );
11✔
1284

1285
  // Load parameters from uoconvert.cfg (multi types, mounts, etc)
1286
  load_uoconvert_cfg();
11✔
1287
}
11✔
1288

1289
void parse_graphics_properties( Clib::ConfigElem& elem, const std::string& prop_name,
16✔
1290
                                std::set<unsigned int>& dest )
1291
{
1292
  std::string prop_value;
16✔
1293
  std::string graphicnum;
16✔
1294

1295
  if ( !elem.has_prop( prop_name.c_str() ) )
16✔
1296
  {
UNCOV
1297
    elem.throw_prop_not_found( prop_name );
×
1298
  }
1299

1300
  while ( elem.remove_prop( prop_name.c_str(), &prop_value ) )
32✔
1301
  {
1302
    ISTRINGSTREAM is( prop_value );
16✔
1303
    while ( is >> graphicnum )
120✔
1304
    {
1305
      dest.insert( strtoul( graphicnum.c_str(), nullptr, 0 ) );
104✔
1306
    }
1307
  }
16✔
1308
}
16✔
1309

1310
void notice_deprecated( Clib::ConfigElem& elem, const std::string& prop_name )
16✔
1311
{
1312
  if ( elem.has_prop( prop_name.c_str() ) )
16✔
1313
  {
1314
    INFO_PRINTLN( "Note: specifying {} in MultiTypes is no longer needed.", prop_name );
16✔
1315
  }
1316
}
16✔
1317

1318
void UoConvertMain::load_uoconvert_cfg()
11✔
1319
{
1320
  std::string main_cfg = "uoconvert.cfg";
11✔
1321
  if ( Clib::FileExists( main_cfg ) )
11✔
1322
  {
1323
    Clib::ConfigElem elem;
8✔
1324
    INFO_PRINTLN( "Reading uoconvert.cfg." );
8✔
1325
    Clib::ConfigFile cf_main( main_cfg );
8✔
1326
    while ( cf_main.read( elem ) )
48✔
1327
    {
1328
      if ( elem.type_is( "MultiTypes" ) )
40✔
1329
      {
1330
        parse_graphics_properties( elem, "Boats", BoatTypes );
8✔
1331
        notice_deprecated( elem, "Houses" );
8✔
1332
        notice_deprecated( elem, "Stairs" );
8✔
1333
      }
1334
      else if ( elem.type_is( "LOSOptions" ) )
32✔
1335
      {
1336
        if ( elem.has_prop( "UseNoShoot" ) )
8✔
1337
          UoConvertMain::cfg_use_no_shoot = elem.remove_bool( "UseNoShoot" );
8✔
1338

1339
        if ( elem.has_prop( "LOSThroughWindows" ) )
8✔
1340
          UoConvertMain::cfg_LOS_through_windows = elem.remove_bool( "LOSThroughWindows" );
8✔
1341
      }
1342
      else if ( elem.type_is( "Mounts" ) )
24✔
1343
      {
1344
        parse_graphics_properties( elem, "Tiles", MountTypes );
8✔
1345
      }
1346
      else if ( elem.type_is( "StaticOptions" ) )
16✔
1347
      {
1348
        if ( elem.has_prop( "MaxStaticsPerBlock" ) )
8✔
1349
        {
1350
          UoConvert::cfg_max_statics_per_block = elem.remove_int( "MaxStaticsPerBlock" );
8✔
1351

1352
          if ( UoConvert::cfg_max_statics_per_block > MAX_STATICS_PER_BLOCK )
8✔
1353
          {
1354
            UoConvert::cfg_max_statics_per_block = MAX_STATICS_PER_BLOCK;
×
UNCOV
1355
            INFO_PRINTLN( "max. Statics per Block limited to {} Items",
×
1356
                          UoConvert::cfg_max_statics_per_block );
1357
          }
1358
          else if ( UoConvert::cfg_max_statics_per_block < 0 )
8✔
UNCOV
1359
            UoConvert::cfg_max_statics_per_block = 1000;
×
1360
        }
1361

1362
        if ( elem.has_prop( "WarningStaticsPerBlock" ) )
8✔
1363
        {
1364
          UoConvert::cfg_warning_statics_per_block = elem.remove_int( "WarningStaticsPerBlock" );
8✔
1365

1366
          if ( UoConvert::cfg_warning_statics_per_block > MAX_STATICS_PER_BLOCK )
8✔
1367
          {
UNCOV
1368
            UoConvert::cfg_warning_statics_per_block = MAX_STATICS_PER_BLOCK;
×
UNCOV
1369
            INFO_PRINTLN( "max. Statics per Block for Warning limited to {} Items",
×
1370
                          UoConvert::cfg_warning_statics_per_block );
1371
          }
1372
          else if ( UoConvert::cfg_warning_statics_per_block < 0 )
8✔
1373
            UoConvert::cfg_warning_statics_per_block = 1000;
×
1374
        }
1375

1376
        if ( elem.has_prop( "DiscardedWaterTiles" ) )
8✔
1377
        {
UNCOV
1378
          parse_graphics_properties( elem, "DiscardedWaterTiles", DiscardedWaterTypes );
×
1379
        }
1380
        else
1381
          for ( int i = 0x1796; i <= 0x17B2; ++i )
240✔
1382
            DiscardedWaterTypes.insert( i );
232✔
1383

1384
        if ( elem.has_prop( "ShowIllegalGraphicWarning" ) )
8✔
1385
          UoConvert::cfg_show_illegal_graphic_warning =
8✔
1386
              elem.remove_bool( "ShowIllegalGraphicWarning" );
8✔
1387
      }
1388
      else if ( elem.type_is( "TileOptions" ) )
8✔
1389
      {
1390
        if ( elem.has_prop( "ShowRoofAndPlatformWarning" ) )
8✔
1391
          cfg_show_roof_and_platform_warning = elem.remove_bool( "ShowRoofAndPlatformWarning" );
8✔
1392
      }
UNCOV
1393
      else if ( elem.type_is( "ClientOptions" ) )
×
1394
      {
UNCOV
1395
        if ( elem.has_prop( "UseNewHSAFormat" ) )
×
UNCOV
1396
          INFO_PRINTLN( "Warning: UseNewHSAFormat in uoconvert.cfg is no longer needed." );
×
1397
      }
1398
    }
1399
  }
8✔
1400
}
11✔
1401
}  // namespace Pol::UoConvert
1402

1403

1404
///////////////////////////////////////////////////////////////////////////////
1405
///////////////////////////////////////////////////////////////////////////////
1406
///////////////////////////////////////////////////////////////////////////////
1407

1408
int main( int argc, char* argv[] )
11✔
1409
{
1410
  Pol::UoConvert::UoConvertMain* UoConvertMain = new Pol::UoConvert::UoConvertMain();
11✔
1411
  UoConvertMain->start( argc, argv );
11✔
UNCOV
1412
}
×
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