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

polserver / polserver / 14466730254

15 Apr 2025 10:03AM UTC coverage: 58.75% (-0.08%) from 58.833%
14466730254

push

github

web-flow
uoconvert improvements for supporting MultiCollections.uop (#776)

- Add uoconvert support for MultiCollection.uop
- Allow multiple elements in MultiTypes and Mounts in uoconvert.cfg
- Simplify entries in uoconvert.cfg to remove House and Stairs properties in the MultiTypes element

57 of 153 new or added lines in 5 files covered. (37.25%)

11 existing lines in 3 files now uncovered.

42332 of 72054 relevant lines covered (58.75%)

373498.58 hits per line

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

70.26
/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
39
{
40
namespace UoConvert
41
{
42
using namespace std;
43
using namespace Pol::Core;
44
using namespace Pol::Plib;
45

46
///////////////////////////////////////////////////////////////////////////////
47

48

49
///////////////////////////////////////////////////////////////////////////////
50

51
UoConvertMain::UoConvertMain()
11✔
52
    : Pol::Clib::ProgramMain(), cfg_use_no_shoot( false ), cfg_LOS_through_windows( false )
11✔
53
{
54
}
11✔
55
UoConvertMain::~UoConvertMain() {}
×
56
///////////////////////////////////////////////////////////////////////////////
57

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

75
using namespace Core;
76
using namespace Plib;
77

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

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

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

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

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

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

148
          short z;
149
          USTRUCT_MAPINFO mi;
150

151
          safe_getmapinfo( x, y, &z, &mi );
2,596,864✔
152

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

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

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

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

183

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

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

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

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

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

244
  mapwriter->WriteConfigFile();
4✔
245
  delete mapwriter;
4✔
246

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

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

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

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

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

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

296
    if ( is_cave_shadow( mi ) || is_cave_exit( mi ) )
8,350,852✔
297
      z0 = z;
×
298

299
    if ( is_no_draw( mi ) )
8,350,852✔
300
      cave_override = true;
×
301

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

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

312
    if ( is_cave_shadow( mi ) || is_cave_exit( mi ) )
8,354,048✔
313
      z0 = z;
×
314

315
    if ( is_no_draw( mi ) )
8,354,048✔
316
      cave_override = true;
×
317

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

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

328
    if ( is_cave_shadow( mi ) || is_cave_exit( mi ) )
8,350,852✔
329
      z0 = z;
×
330

331
    if ( is_no_draw( mi ) )
8,350,852✔
332
      cave_override = true;
×
333

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

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

344
    if ( is_cave_shadow( mi ) || is_cave_exit( mi ) )
8,360,832✔
345
      z0 = z;
×
346

347
    if ( is_no_draw( mi ) )
8,360,832✔
348
      cave_override = true;
×
349

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

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

360
    if ( is_cave_shadow( mi ) || is_cave_exit( mi ) )
8,350,852✔
361
      z0 = z;
×
362

363
    if ( is_no_draw( mi ) )
8,350,852✔
364
      cave_override = true;
×
365

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

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

376
    if ( is_cave_shadow( mi ) || is_cave_exit( mi ) )
8,354,048✔
377
      z0 = z;
×
378

379
    if ( is_no_draw( mi ) )
8,354,048✔
380
      cave_override = true;
×
381

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

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

392
    if ( is_cave_shadow( mi ) || is_cave_exit( mi ) )
8,350,852✔
393
      z0 = z;
×
394

395
    if ( is_no_draw( mi ) )
8,350,852✔
396
      cave_override = true;
×
397

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

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

408
    if ( is_cave_shadow( mi ) || is_cave_exit( mi ) )
8,360,832✔
409
      z0 = z;
×
410

411
    if ( is_no_draw( mi ) )
8,360,832✔
412
      cave_override = true;
×
413

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

420
  if ( cave_override )
8,364,032✔
421
    return z;
×
422
  else
423
    return lowest_z;
8,364,032✔
424
}
425

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

434
  unsigned short x_add_max = SOLIDX_X_SIZE, y_add_max = SOLIDX_Y_SIZE;
130,688✔
435
  if ( x_base + x_add_max > uo_map_width )
130,688✔
436
    x_add_max = uo_map_width - x_base;
×
437
  if ( y_base + y_add_max > uo_map_height )
130,688✔
438
    y_add_max = uo_map_height - y_base;
×
439

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

447
      StaticList statics;
8,364,032✔
448

449
      // read the map, and treat it like a static.
450
      short z;
451
      USTRUCT_MAPINFO mi;
452

453
      safe_getmapinfo( x, y, &z, &mi );
8,364,032✔
454

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

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

463
      short lt_height = z - low_z;
8,364,032✔
464
      z = low_z;
8,364,032✔
465

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

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

481
      if ( lt_flags & USTRUCT_TILE::FLAG_WALL )
8,364,032✔
482
        lt_height = 20;
×
483

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

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

495
        unsigned int polflags = polflags_from_tileflags( srec.graphic, srec.flags, cfg_use_no_shoot,
704✔
496
                                                         cfg_LOS_through_windows );
352✔
497

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

519
      bool addMap = true;
8,364,032✔
520

521
      for ( const auto& srec : statics )
8,364,384✔
522
      {
523
        // Look for water tiles.  If there are any, discard the map (which is usually at -15 anyway)
524
        if ( z + lt_height <= srec.z &&
352✔
525
             ( ( srec.z - ( z + lt_height ) ) <=
352✔
526
               10 ) &&  // only where the map is below or same Z as the static
340✔
527
             srec.graphic >= 0x1796 &&
340✔
528
             srec.graphic <= 0x17B2 )  // FIXME hardcoded
×
529
        {
530
          // arr, there be water here
531
          addMap = false;
×
532
        }
533

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

547

548
      // If the map is a NODRAW tile, and there are statics, discard the map tile
549
      if ( mi.landtile == 2 && !statics.empty() )
8,364,032✔
550
        addMap = false;
×
551

552
      if ( addMap )
8,364,032✔
553
        statics.push_back( StaticRec( 0, static_cast<signed char>( z ), lt_flags,
8,364,032✔
554
                                      static_cast<char>( lt_height ) ) );
555

556
      sort( statics.begin(), statics.end(), StaticsByZ() );
8,364,032✔
557
      reverse( statics.begin(), statics.end() );
8,364,032✔
558

559
      std::vector<MapShape> shapes;
8,364,032✔
560

561
      // try to consolidate like shapes, and discard ones we don't care about.
562
      while ( !statics.empty() )
16,728,416✔
563
      {
564
        StaticRec srec = statics.back();
8,364,384✔
565
        statics.pop_back();
8,364,384✔
566

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

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

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

612
        MapShape& prev = shapes.back();
352✔
613
        // we're adding it.
614
        MapShape shape;
615
        shape.z = srec.z;
352✔
616
        shape.height = srec.height;
352✔
617
        shape.flags = polflags;
352✔
618

619
        // always add the map shape seperately
620
        if ( shapes.size() == 1 )
352✔
621
        {
622
          shapes.push_back( shape );
208✔
623
          continue;
208✔
624
        }
625

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

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

660
        // string prevflags_s = flagstr(prev.flags);
661
        // const char* prevflags = prevflags_s.c_str();
662
        // string shapeflags_s = flagstr(shape.flags);
663
        // const char* shapeflags = shapeflags_s.c_str();
664

665
        if ( shape.z > prev.z + prev.height )
144✔
666
        {
667
          //
668
          // elevated above what's below, must include separately
669
          //
670

671
          shapes.push_back( shape );
96✔
672
          continue;
96✔
673
        }
674

675
        passert_always( shape.z == prev.z + prev.height );
48✔
676

677
        if ( shape.z == prev.z + prev.height )
48✔
678
        {
679
          //
680
          // sitting right on top of the previous solid
681
          //
682

683
          // standable atop non-standable: standable
684
          // nonstandable atop standable: nonstandable
685
          // etc
686
          bool can_combine =
687
              flags_match( prev.flags, shape.flags, FLAG::BLOCKSIGHT | FLAG::BLOCKING );
48✔
688
          if ( prev.flags & FLAG::MOVELAND && ~shape.flags & FLAG::BLOCKING &&
48✔
689
               ~shape.flags & FLAG::MOVELAND )
×
690
          {
691
            can_combine = false;
×
692
          }
693

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

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

718
      mapwriter.SetMapCell( x, y, cell );
8,364,032✔
719

720
      if ( !shapes.empty() )
8,364,032✔
721
      {
722
        ++with_more_solids;
63,784✔
723
        total_statics += static_cast<unsigned int>( shapes.size() );
63,784✔
724
        if ( idx2_offset == 0 )
63,784✔
725
          idx2_offset = mapwriter.NextSolidx2Offset();
3,200✔
726

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

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

768
std::string UoConvertMain::resolve_type_from_id( unsigned id ) const
20✔
769
{
770
  if ( BoatTypes.count( id ) )
20✔
771
    return "Boat";
12✔
772
  else
773
    return "Multi";
8✔
774
}
775

776
void UoConvertMain::write_multi_element( FILE* multis_cfg, const USTRUCT_MULTI_ELEMENT& elem,
820✔
777
                                         const std::string& mytype, bool& first )
778
{
779
  if ( elem.graphic == GRAPHIC_NODRAW )
820✔
NEW
780
    return;
×
781

782
  std::string type = elem.flags ? "static" : "dynamic";
820✔
783

784
  if ( mytype == "Boat" && first && elem.graphic != 1 )
820✔
785
    type = "static";
12✔
786

787
  std::string comment;
820✔
788
  if ( cfg_use_new_hsa_format )
820✔
789
  {
790
    USTRUCT_TILE_HSA tile;
NEW
791
    readtile( elem.graphic, &tile );
×
NEW
792
    comment.assign( tile.name, sizeof( tile.name ) );
×
793
  }
794
  else
795
  {
796
    USTRUCT_TILE tile;
797
    readtile( elem.graphic, &tile );
820✔
798
    comment.assign( tile.name, sizeof( tile.name ) );
820✔
799
  }
800

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

804
  first = false;
820✔
805
}
820✔
806

NEW
807
void UoConvertMain::write_multi( FILE* multis_cfg, unsigned id,
×
808
                                 std::vector<Plib::USTRUCT_MULTI_ELEMENT>& multi_elems )
809
{
NEW
810
  std::string mytype = resolve_type_from_id( id );
×
811

NEW
812
  fprintf( multis_cfg, "%s 0x%x\n", mytype.c_str(), id );
×
NEW
813
  fprintf( multis_cfg, "{\n" );
×
814

NEW
815
  bool first = true;
×
NEW
816
  for ( const auto& elem : multi_elems )
×
817
  {
NEW
818
    write_multi_element( multis_cfg, elem, mytype, first );
×
819
  }
820

NEW
821
  fprintf( multis_cfg, "}\n\n" );
×
NEW
822
}
×
823

824
void UoConvertMain::write_multi( FILE* multis_cfg, unsigned id, FILE* multi_mul,
20✔
825
                                 unsigned int offset, unsigned int length )
826
{
827
  USTRUCT_MULTI_ELEMENT elem;
828
  unsigned int count =
20✔
829
      cfg_use_new_hsa_format ? length / sizeof( USTRUCT_MULTI_ELEMENT_HSA ) : length / sizeof elem;
830

831
  std::string mytype = resolve_type_from_id( id );
20✔
832

833
  fprintf( multis_cfg, "%s 0x%x\n", mytype.c_str(), id );
20✔
834
  fprintf( multis_cfg, "{\n" );
20✔
835

836
  if ( fseek( multi_mul, offset, SEEK_SET ) != 0 )
20✔
837
  {
838
    throw std::runtime_error( "write_multi(): fseek() failed" );
×
839
  }
840

841

842
  bool first = true;
20✔
843
  while ( count-- )
840✔
844
  {
845
    if ( fread( &elem, sizeof elem, 1, multi_mul ) != 1 )
820✔
846
    {
847
      throw std::runtime_error( "write_multi(): fread() failed" );
×
848
    }
849

850
    if ( cfg_use_new_hsa_format )
820✔
851
    {
852
      if ( fseek( multi_mul, 4, SEEK_CUR ) != 0 )
×
853
        throw std::runtime_error( "write_multi(): fseek() failed" );
×
854
    }
855

856
    write_multi_element( multis_cfg, elem, mytype, first );
820✔
857
  }
858

859
  fprintf( multis_cfg, "}\n\n" );
20✔
860
}
20✔
861

862
void UoConvertMain::create_multis_cfg( FILE* multi_idx, FILE* multi_mul, FILE* multis_cfg )
1✔
863
{
864
  if ( fseek( multi_idx, 0, SEEK_SET ) != 0 )
1✔
865
    throw std::runtime_error( "create_multis_cfg: fseek failed" );
×
866
  unsigned count = 0;
1✔
867
  USTRUCT_IDX idxrec;
868
  for ( int i = 0; fread( &idxrec, sizeof idxrec, 1, multi_idx ) == 1; ++i )
16,384✔
869
  {
870
    const USTRUCT_VERSION* vrec = nullptr;
16,383✔
871

872
    if ( check_verdata( VERFILE_MULTI_MUL, i, vrec ) )
16,383✔
873
    {
874
      write_multi( multis_cfg, i, verfile, vrec->filepos, vrec->length );
×
875
      ++count;
×
876
    }
877
    else
878
    {
879
      if ( idxrec.offset == 0xFFffFFffLu )
16,383✔
880
        continue;
16,363✔
881

882
      write_multi( multis_cfg, i, multi_mul, idxrec.offset, idxrec.length );
20✔
883
      ++count;
20✔
884
    }
885
  }
886
  INFO_PRINTLN( "{} multi definitions written to multis.cfg", count );
1✔
887
}
1✔
888

889
void UoConvertMain::create_multis_cfg()
1✔
890
{
891
  std::map<unsigned int, std::vector<USTRUCT_MULTI_ELEMENT>> multi_map;
1✔
892

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

896
  if ( open_uopmulti_file( multi_map ) )
1✔
897
  {
NEW
898
    for ( auto& [id, elems] : multi_map )
×
899
    {
NEW
900
      write_multi( multis_cfg, id, elems );
×
901
    }
902

NEW
903
    INFO_PRINTLN( "{} multi definitions written to multis.cfg", multi_map.size() );
×
904

NEW
905
    return;
×
906
  }
907

908
  FILE* multi_idx = open_uo_file( "multi.idx" );
1✔
909
  FILE* multi_mul = open_uo_file( "multi.mul" );
1✔
910

911
  create_multis_cfg( multi_idx, multi_mul, multis_cfg );
1✔
912

913
  fclose( multis_cfg );
1✔
914

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

946
void UoConvertMain::create_tiles_cfg()
1✔
947
{
948
  std::string outdir = programArgsFindEquals( "outdir=", "." );
1✔
949
  FILE* fp = fopen( ( outdir + "/tiles.cfg" ).c_str(), "wt" );
1✔
950
  int mountCount;
951
  char name[21];
952

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

979
    if ( tile.name[0] == '\0' && tile.flags == 0 && tile.layer == 0 && tile.height == 0 &&
16,384✔
980
         mountCount == 0 )
981
    {
982
      continue;
16,269✔
983
    }
984
    unsigned int flags =
985
        polflags_from_tileflags( graphic, tile.flags, cfg_use_no_shoot, cfg_LOS_through_windows );
115✔
986
    if ( mountCount != 0 )
115✔
987
    {
988
      tile.layer = 25;
×
989
      flags |= FLAG::EQUIPPABLE;
×
990
    }
991

992
    memset( name, 0, sizeof name );
115✔
993
    memcpy( name, tile.name, sizeof tile.name );
115✔
994

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

1014
  INFO_PRINTLN( "{} tile definitions written to tiles.cfg", count );
1✔
1015
}
1✔
1016

1017
void UoConvertMain::create_landtiles_cfg()
1✔
1018
{
1019
  std::string outdir = programArgsFindEquals( "outdir=", "." );
1✔
1020
  FILE* fp = fopen( ( outdir + "/landtiles.cfg" ).c_str(), "wt" );
1✔
1021
  unsigned count = 0;
1✔
1022

1023
  for ( u16 i = 0; i <= 0x3FFF; ++i )
16,385✔
1024
  {
1025
    USTRUCT_LAND_TILE landtile;
1026
    if ( cfg_use_new_hsa_format )
16,384✔
1027
    {
1028
      USTRUCT_LAND_TILE_HSA newlandtile;
1029
      readlandtile( i, &newlandtile );
×
1030
      landtile.flags = newlandtile.flags;
×
1031
      landtile.unk = newlandtile.unk;
×
1032
      memcpy( landtile.name, newlandtile.name, sizeof landtile.name );
×
1033
    }
1034
    else
1035
      readlandtile( i, &landtile );
16,384✔
1036

1037
    if ( landtile.name[0] || landtile.flags )
16,384✔
1038
    {
1039
      fprintf( fp, "landtile 0x%x\n", i );
2✔
1040
      fprintf( fp, "{\n" );
2✔
1041
      fprintf( fp, "    Name %s\n", landtile.name );
2✔
1042
      fprintf( fp, "    UoFlags 0x%08lx\n", static_cast<unsigned long>( landtile.flags ) );
2✔
1043

1044
      unsigned int flags = polflags_from_landtileflags( i, landtile.flags );
2✔
1045
      flags &= ~FLAG::MOVABLE;  // movable makes no sense for landtiles
2✔
1046
      write_flags( fp, flags );
2✔
1047
      fprintf( fp, "}\n" );
2✔
1048
      fprintf( fp, "\n" );
2✔
1049
      ++count;
2✔
1050
    }
1051
  }
1052
  fclose( fp );
1✔
1053

1054
  INFO_PRINTLN( "{} landtile definitions written to landtiles.cfg", count );
1✔
1055
}
1✔
1056

1057
int UoConvertMain::main()
11✔
1058
{
1059
  const std::vector<std::string>& binArgs = programArgs();
11✔
1060

1061
  /**********************************************
1062
   * show help
1063
   **********************************************/
1064
  if ( binArgs.size() == 1 )
11✔
1065
  {
1066
    showHelp();
×
1067
    return 0;  // return "okay"
×
1068
  }
1069

1070
  // Setups uoconvert by finding the path of uo files and max tiles from pol.cfg or command
1071
  // line arguments. Also loads parameters from uoconvert.cfg.
1072
  setup_uoconvert();
11✔
1073

1074
  std::string command = binArgs[1];
11✔
1075
  if ( command == "uoptomul" )
11✔
1076
  {
1077
    if ( !convert_uop_to_mul() )
×
1078
      return 1;
×
1079
  }
1080
  else if ( command == "map" )
11✔
1081
  {
1082
    UoConvert::uo_mapid = programArgsFindEquals( "mapid=", 0, false );
4✔
1083
    UoConvert::uo_usedif = programArgsFindEquals( "usedif=", 1, false );
4✔
1084
    UoConvert::uo_readuop = (bool)programArgsFindEquals( "readuop=", 1, false );
4✔
1085

1086
    std::string realm = programArgsFindEquals( "realm=", "britannia" );
4✔
1087

1088
    UoConvert::open_uo_data_files();
4✔
1089
    UoConvert::read_uo_data();
4✔
1090

1091
    // Auto-detects defaults for mapid=0 or 1 based on the map size. All other sizes are fixed based
1092
    // on the mapid.
1093
    Pol::Plib::MUL::MapInfo mapinfo( UoConvert::uo_mapid, UoConvert::uo_map_size );
4✔
1094
    int default_width = mapinfo.width();
4✔
1095
    int default_height = mapinfo.height();
4✔
1096

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

1100
    uo_map_width =
4✔
1101
        static_cast<unsigned short>( programArgsFindEquals( "width=", default_width, false ) );
4✔
1102
    uo_map_height =
4✔
1103
        static_cast<unsigned short>( programArgsFindEquals( "height=", default_height, false ) );
4✔
1104

1105
    check_for_errors_in_map_parameters();
4✔
1106

1107
    int x = programArgsFindEquals( "x=", -1, false );
4✔
1108
    int y = programArgsFindEquals( "y=", -1, false );
4✔
1109

1110
    // britannia: realm=main mapid=0 width=6144 height=4096
1111
    // ilshenar: realm=ilshenar mapid=2 width=2304 height=1600
1112
    // malas: realm=malas mapid=3 width=2560 height=2048
1113
    // tokuno: realm=tokuno mapid=4 width=1448 height=1448
1114
    // termur: realm=termur mapid=5 width=1280 height=4096
1115

1116
    if ( x >= 0 && y >= 0 )
4✔
1117
    {
1118
      UoConvertMain::update_map( realm, static_cast<u16>( x ), static_cast<u16>( y ) );
×
1119
    }
1120
    else
1121
    {
1122
      UoConvertMain::create_map( realm, static_cast<unsigned short>( uo_map_width ),
4✔
1123
                                 static_cast<unsigned short>( uo_map_height ) );
1124
    }
1125
  }
4✔
1126
  else if ( command == "statics" )
7✔
1127
  {
1128
    std::string realm = programArgsFindEquals( "realm=", "britannia" );
2✔
1129
    Plib::RealmDescriptor descriptor = Plib::RealmDescriptor::Load( realm );
2✔
1130

1131
    UoConvert::uo_mapid = descriptor.uomapid;
2✔
1132
    UoConvert::uo_usedif = descriptor.uodif;
2✔
1133
    UoConvert::uo_map_width = static_cast<unsigned short>( descriptor.width );
2✔
1134
    UoConvert::uo_map_height = static_cast<unsigned short>( descriptor.height );
2✔
1135

1136
    UoConvert::open_uo_data_files();
2✔
1137
    UoConvert::read_uo_data();
2✔
1138

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

1164
    UoConvert::uo_mapid = descriptor.uomapid;
2✔
1165
    UoConvert::uo_usedif = descriptor.uodif;
2✔
1166
    UoConvert::uo_map_width = static_cast<unsigned short>( descriptor.width );
2✔
1167
    UoConvert::uo_map_height = static_cast<unsigned short>( descriptor.height );
2✔
1168

1169
    UoConvert::open_uo_data_files();
2✔
1170
    UoConvert::read_uo_data();
2✔
1171

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

1193
    INFO_PRINTLN( "\nWarning: Width and height do not match the map size ({} bytes, expected {})",
×
1194
                  UoConvert::uo_map_size, expected_size );
1195

1196
    if ( uo_map_width == 0 || uo_map_height == 0 )
×
1197
      throw std::runtime_error(
×
1198
          "Width and height were not identified automatically. Please specify them manually." );
×
1199

1200

1201
    if ( ( uo_map_width % MUL::Map::blockWidth != 0 ) ||
×
1202
         ( uo_map_height % MUL::Map::blockHeight != 0 ) )
×
1203
      throw std::runtime_error( "Width and height must be divisible by 8" );
×
1204

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

1214
  std::string mul_mapfile = "map" + to_string( uo_mapid ) + ".mul";
×
1215
  std::string uop_mapfile = "map" + to_string( uo_mapid ) + "LegacyMUL.uop";
×
1216

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

1220
  std::ifstream ifs( uop_mapfile, std::ifstream::binary );
×
1221
  if ( !ifs )
×
1222
  {
1223
    ERROR_PRINTLN( "Error when opening mapfile: {}", uop_mapfile );
×
1224
    return false;
×
1225
  }
1226

1227
  kaitai::kstream ks( &ifs );
×
1228
  uop_t uopfile( &ks );
×
1229

1230
  // TODO: read all blocks
1231
  std::map<uint64_t, uop_t::file_t*> filemap;
×
1232
  uop_t::block_addr_t* currentblock = uopfile.header()->firstblock();
×
1233
  for ( auto file : *currentblock->block_body()->files() )
×
1234
  {
1235
    if ( file == nullptr )
×
1236
      continue;
×
1237
    if ( file->decompressed_size() == 0 )
×
1238
      continue;
×
1239
    filemap[file->filehash()] = file;
×
1240
  }
1241

1242
  if ( uopfile.header()->nfiles() != filemap.size() )
×
1243
    INFO_PRINTLN( "Warning: not all chunks read ({}/{})", filemap.size(),
×
1244
                  uopfile.header()->nfiles() );
×
1245

1246
  std::ofstream ofs( mul_mapfile, std::ofstream::binary );
×
1247
  for ( size_t i = 0; i < filemap.size(); i++ )
×
1248
  {
1249
    auto fileitr = filemap.find( maphash( uo_mapid, i ) );
×
1250
    if ( fileitr == filemap.end() )
×
1251
    {
1252
      INFO_PRINTLN( "Couldn't find file hash: {}", maphash( uo_mapid, i ) );
×
1253
      continue;
×
1254
    }
1255

1256
    auto file = fileitr->second;
×
1257
    ofs << file->data()->filebytes();
×
1258
    INFO_PRINTLN( "Wrote: {}/{}", i + 1, filemap.size() );
×
1259
  }
1260
  INFO_PRINTLN( "Done converting." );
×
1261

1262
  return true;
×
1263
}
×
1264
void UoConvertMain::setup_uoconvert()
11✔
1265
{
1266
  std::string uodata_root = programArgsFindEquals( "uodata=", "" );
11✔
1267
  unsigned short max_tile =
1268
      static_cast<unsigned short>( programArgsFindEquals( "maxtileid=", 0x0, true ) );
11✔
1269

1270
  if ( max_tile )
11✔
1271
  {
1272
    INFO_PRINTLN( "Warning: maxtileid will be ignored and detected from tiledata.mul instead." );
×
1273
  }
1274

1275
  // read required parameters from pol.cfg
1276
  if ( uodata_root.empty() )
11✔
1277
  {
1278
    INFO_PRINTLN( "Reading pol.cfg." );
×
1279
    Clib::ConfigFile cf( "pol.cfg" );
×
1280
    Clib::ConfigElem elem;
×
1281

1282
    cf.readraw( elem );
×
1283

1284
    if ( uodata_root.empty() )
×
1285
      uodata_root = Plib::UOInstallFinder::remove_elem( elem );
×
1286
  }
×
1287

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

1291
  // Load parameters from uoconvert.cfg (multi types, mounts, etc)
1292
  load_uoconvert_cfg();
11✔
1293
}
11✔
1294

1295
void parse_graphics_properties( Clib::ConfigElem& elem, const std::string& prop_name,
16✔
1296
                                std::set<unsigned int>& dest )
1297
{
1298
  std::string prop_value;
16✔
1299
  std::string graphicnum;
16✔
1300

1301
  if ( !elem.has_prop( prop_name.c_str() ) )
16✔
1302
  {
NEW
1303
    elem.throw_prop_not_found( prop_name );
×
1304
  }
1305

1306
  while ( elem.remove_prop( prop_name.c_str(), &prop_value ) )
32✔
1307
  {
1308
    ISTRINGSTREAM is( prop_value );
16✔
1309
    while ( is >> graphicnum )
120✔
1310
    {
1311
      dest.insert( strtoul( graphicnum.c_str(), nullptr, 0 ) );
104✔
1312
    }
1313
  }
16✔
1314
}
16✔
1315

1316
void notice_deprecated( Clib::ConfigElem& elem, const std::string& prop_name )
16✔
1317
{
1318
  if ( elem.has_prop( prop_name.c_str() ) )
16✔
1319
  {
1320
    INFO_PRINTLN( "Note: specifying {} in MultiTypes is no longer needed.", prop_name );
16✔
1321
  }
1322
}
16✔
1323

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

1345
        if ( elem.has_prop( "LOSThroughWindows" ) )
8✔
1346
          UoConvertMain::cfg_LOS_through_windows = elem.remove_bool( "LOSThroughWindows" );
8✔
1347
      }
1348
      else if ( elem.type_is( "Mounts" ) )
24✔
1349
      {
1350
        parse_graphics_properties( elem, "Tiles", MountTypes );
8✔
1351
      }
1352
      else if ( elem.type_is( "StaticOptions" ) )
16✔
1353
      {
1354
        if ( elem.has_prop( "MaxStaticsPerBlock" ) )
8✔
1355
        {
1356
          UoConvert::cfg_max_statics_per_block = elem.remove_int( "MaxStaticsPerBlock" );
8✔
1357

1358
          if ( UoConvert::cfg_max_statics_per_block > MAX_STATICS_PER_BLOCK )
8✔
1359
          {
1360
            UoConvert::cfg_max_statics_per_block = MAX_STATICS_PER_BLOCK;
×
1361
            INFO_PRINTLN( "max. Statics per Block limited to {} Items",
×
1362
                          UoConvert::cfg_max_statics_per_block );
1363
          }
1364
          else if ( UoConvert::cfg_max_statics_per_block < 0 )
8✔
1365
            UoConvert::cfg_max_statics_per_block = 1000;
×
1366
        }
1367

1368
        if ( elem.has_prop( "WarningStaticsPerBlock" ) )
8✔
1369
        {
1370
          UoConvert::cfg_warning_statics_per_block = elem.remove_int( "WarningStaticsPerBlock" );
8✔
1371

1372
          if ( UoConvert::cfg_warning_statics_per_block > MAX_STATICS_PER_BLOCK )
8✔
1373
          {
1374
            UoConvert::cfg_warning_statics_per_block = MAX_STATICS_PER_BLOCK;
×
1375
            INFO_PRINTLN( "max. Statics per Block for Warning limited to {} Items",
×
1376
                          UoConvert::cfg_warning_statics_per_block );
1377
          }
1378
          else if ( UoConvert::cfg_warning_statics_per_block < 0 )
8✔
1379
            UoConvert::cfg_warning_statics_per_block = 1000;
×
1380
        }
1381

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

1402
///////////////////////////////////////////////////////////////////////////////
1403
///////////////////////////////////////////////////////////////////////////////
1404
///////////////////////////////////////////////////////////////////////////////
1405

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