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

Return-To-The-Roots / s25client / 5735031298

pending completion
5735031298

Pull #1617

github

web-flow
Merge d291cd187 into a397539c1
Pull Request #1617: Show Popup on missing game files

11 of 11 new or added lines in 3 files covered. (100.0%)

21762 of 43119 relevant lines covered (50.47%)

32710.67 hits per line

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

26.0
/libs/s25main/Loader.cpp
1
// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org)
2
//
3
// SPDX-License-Identifier: GPL-2.0-or-later
4

5
///////////////////////////////////////////////////////////////////////////////
6

7
#include "Loader.h"
8
#include "ListDir.h"
9
#include "RttrConfig.h"
10
#include "Settings.h"
11
#include "Timer.h"
12
#include "addons/const_addons.h"
13
#include "commonDefines.h"
14
#include "convertSounds.h"
15
#include "files.h"
16
#include "helpers/EnumRange.h"
17
#include "helpers/containerUtils.h"
18
#include "ogl/MusicItem.h"
19
#include "ogl/SoundEffectItem.h"
20
#include "ogl/glArchivItem_Bitmap_Player.h"
21
#include "ogl/glArchivItem_Bitmap_RLE.h"
22
#include "ogl/glArchivItem_Bitmap_Raw.h"
23
#include "ogl/glArchivItem_Bob.h"
24
#include "ogl/glArchivItem_Sound_Wave.h"
25
#include "ogl/glFont.h"
26
#include "ogl/glSmartBitmap.h"
27
#include "ogl/glTexturePacker.h"
28
#include "resources/ArchiveLoader.h"
29
#include "resources/ArchiveLocator.h"
30
#include "resources/ResolvedFile.h"
31
#include "gameTypes/Direction.h"
32
#include "gameTypes/DirectionToImgDir.h"
33
#include "gameData/JobConsts.h"
34
#include "gameData/MilitaryConsts.h"
35
#include "gameData/NationConsts.h"
36
#include "libsiedler2/ArchivItem_Font.h"
37
#include "libsiedler2/ArchivItem_Palette.h"
38
#include "libsiedler2/ArchivItem_PaletteAnimation.h"
39
#include "libsiedler2/ArchivItem_Text.h"
40
#include "libsiedler2/ErrorCodes.h"
41
#include "libsiedler2/PixelBufferBGRA.h"
42
#include "libsiedler2/PixelBufferPaletted.h"
43
#include "libsiedler2/libsiedler2.h"
44
#include "s25util/Log.h"
45
#include "s25util/StringConversion.h"
46
#include "s25util/System.h"
47
#include "s25util/strAlgos.h"
48
#include <boost/filesystem.hpp>
49
#include <boost/pointer_cast.hpp>
50
#include <boost/range/adaptor/map.hpp>
51
#include <algorithm>
52
#include <chrono>
53
#include <cstdio>
54
#include <iomanip>
55
#include <memory>
56
#include <sstream>
57
#include <stdexcept>
58

59
struct Loader::FileEntry
60
{
61
    libsiedler2::Archiv archive;
62
    /// List of files used to build this archive
63
    ResolvedFile resolvedFile;
64
};
65

66
template<typename T>
67
static T convertChecked(libsiedler2::ArchivItem* item)
953✔
68
{
69
    T res = dynamic_cast<T>(item);
953✔
70
    RTTR_Assert(!item || res);
953✔
71
    return res;
953✔
72
}
73

74
Loader::Loader(Log& logger, const RttrConfig& config)
5✔
75
    : logger_(logger), config_(config), archiveLocator_(std::make_unique<ArchiveLocator>(logger)),
76
      archiveLoader_(std::make_unique<ArchiveLoader>(logger)), isWinterGFX_(false), nation_gfx(), nationIcons_(),
77
      map_gfx(nullptr), stp(nullptr)
45✔
78
{}
5✔
79

80
Loader::~Loader() = default;
5✔
81

82
void Loader::initResourceFolders(const std::vector<Nation>& usedNations, const std::vector<AddonId>& enabledAddons)
1✔
83
{
84
    addDefaultResourceFolders(config_, *archiveLocator_, usedNations, enabledAddons);
1✔
85
}
1✔
86

87
glArchivItem_Bitmap* Loader::GetImageN(const ResourceId& file, unsigned nr)
933✔
88
{
89
    return convertChecked<glArchivItem_Bitmap*>(files_[file].archive[nr]);
933✔
90
}
91

92
ITexture* Loader::GetTextureN(const ResourceId& file, unsigned nr)
12✔
93
{
94
    return convertChecked<ITexture*>(files_[file].archive[nr]);
12✔
95
}
96

97
glArchivItem_Bitmap* Loader::GetImage(const ResourceId& file, const std::string& name)
×
98
{
99
    return convertChecked<glArchivItem_Bitmap*>(files_[file].archive.find(name));
×
100
}
101

102
glArchivItem_Bitmap_Player* Loader::GetPlayerImage(const ResourceId& file, unsigned nr)
×
103
{
104
    return convertChecked<glArchivItem_Bitmap_Player*>(files_[file].archive[nr]);
×
105
}
106

107
glFont* Loader::GetFont(FontSize size)
380✔
108
{
109
    return fonts.empty() ? nullptr : &fonts[static_cast<unsigned>(size)];
380✔
110
}
111

112
libsiedler2::ArchivItem_Palette* Loader::GetPaletteN(const ResourceId& file, unsigned nr)
51✔
113
{
114
    return dynamic_cast<libsiedler2::ArchivItem_Palette*>(files_[file].archive[nr]);
51✔
115
}
116

117
SoundEffectItem* Loader::GetSoundN(const ResourceId& file, unsigned nr)
131✔
118
{
119
    return dynamic_cast<SoundEffectItem*>(files_[file].archive[nr]);
131✔
120
}
121

122
std::string Loader::GetTextN(const ResourceId& file, unsigned nr)
×
123
{
124
    auto* archive = dynamic_cast<libsiedler2::ArchivItem_Text*>(files_[file].archive[nr]);
×
125
    return archive ? archive->getText() : "text missing";
×
126
}
127

128
libsiedler2::Archiv& Loader::GetArchive(const ResourceId& file)
12✔
129
{
130
    RTTR_Assert(helpers::contains(files_, file));
12✔
131
    return files_[file].archive;
12✔
132
}
133

134
glArchivItem_Bob* Loader::GetBob(const ResourceId& file)
×
135
{
136
    return dynamic_cast<glArchivItem_Bob*>(files_[file].archive.get(0));
×
137
}
138

139
glArchivItem_BitmapBase* Loader::GetNationImageN(Nation nation, unsigned nr)
×
140
{
141
    return dynamic_cast<glArchivItem_BitmapBase*>(nation_gfx[nation]->get(nr));
×
142
}
143

144
glArchivItem_Bitmap* Loader::GetNationImage(Nation nation, unsigned nr)
×
145
{
146
    return checkedCast<glArchivItem_Bitmap*>(GetNationImageN(nation, nr));
×
147
}
148

149
glArchivItem_Bitmap* Loader::GetNationIcon(Nation nation, BuildingType bld)
×
150
{
151
    if(bld == BuildingType::Charburner)
×
152
        return LOADER.GetImageN("charburner", rttr::enum_cast(nation) * 8 + 8);
×
153
    else
154
        return convertChecked<glArchivItem_Bitmap*>(nationIcons_[nation]->get(rttr::enum_cast(bld)));
×
155
}
156

157
ITexture* Loader::GetNationTex(Nation nation, unsigned nr)
×
158
{
159
    return checkedCast<ITexture*>(GetNationImage(nation, nr));
×
160
}
161

162
glArchivItem_Bitmap_Player* Loader::GetNationPlayerImage(Nation nation, unsigned nr)
×
163
{
164
    return checkedCast<glArchivItem_Bitmap_Player*>(GetNationImageN(nation, nr));
×
165
}
166

167
glArchivItem_Bitmap* Loader::GetMapImage(unsigned nr)
×
168
{
169
    return convertChecked<glArchivItem_Bitmap*>(map_gfx->get(nr));
×
170
}
171

172
ITexture* Loader::GetMapTexture(unsigned nr)
8✔
173
{
174
    return convertChecked<ITexture*>(map_gfx->get(nr));
8✔
175
}
176

177
glArchivItem_Bitmap_Player* Loader::GetMapPlayerImage(unsigned nr)
×
178
{
179
    return convertChecked<glArchivItem_Bitmap_Player*>(map_gfx->get(nr));
×
180
}
181

182
/**
183
 *  Load general files required also outside of games
184
 *
185
 *  @return @p true on success, @p false on error.
186
 */
187
bool Loader::LoadFilesAtStart()
×
188
{
189
    namespace res = s25::resources;
190
    // Palettes
191
    if(!LoadFiles({res::pal5, res::pal6, res::pal7, res::paletti0, res::paletti1, res::paletti8})
×
192
       || !Load(ResourceId("colors")))
×
193
        return false;
×
194

195
    if(!LoadFonts())
×
196
        return false;
×
197

198
    std::vector<std::string> files = {res::resource,
199
                                      res::io,                       // Menu graphics
200
                                      res::setup013, res::setup015}; // Backgrounds for options and free play
×
201

202
    const std::array<bfs::path, 2> loadScreenFolders{config_.ExpandPath(s25::folders::loadScreens),
×
203
                                                     config_.ExpandPath(s25::folders::loadScreensMissions)};
×
204
    for(const std::string& loadScreenId : LOAD_SCREENS)
×
205
    {
206
        const std::string filename = s25util::toUpper(loadScreenId) + ".LBM";
×
207
        if(exists(loadScreenFolders[0] / filename))
×
208
            files.push_back((loadScreenFolders[0] / filename).string());
×
209
        else
210
            files.push_back((loadScreenFolders[1] / filename).string());
×
211
    }
212

213
    if(!LoadFiles(files))
×
214
        return false;
×
215

216
    if(!LoadSounds())
×
217
        return false;
×
218

219
    return LoadResources({"io_new", "client", "languages", "logo", "menu", "rttr"});
×
220
}
221

222
bool Loader::LoadSounds()
×
223
{
224
    if(!Load(config_.ExpandPath(s25::files::soundOrig)))
×
225
        return false;
×
226
    const Timer timer(true);
×
227
    logger_.write(_("Starting sound conversion: "));
×
228
    try
229
    {
230
        convertSounds(GetArchive("sound"), config_.ExpandPath(s25::files::soundScript));
×
231
    } catch(const std::runtime_error& e)
×
232
    {
233
        logger_.write(_("failed: %1%\n")) % e.what();
×
234
        return false;
×
235
    }
236
    using namespace std::chrono;
237
    logger_.write(_("done in %ums\n")) % duration_cast<milliseconds>(timer.getElapsed()).count();
×
238

239
    const bfs::path oggPath = config_.ExpandPath(s25::folders::sng);
×
240
    std::vector<bfs::path> oggFiles = ListDir(oggPath, "ogg");
×
241

242
    sng_lst.reserve(oggFiles.size());
×
243
    for(const auto& oggFile : oggFiles)
×
244
    {
245
        try
246
        {
247
            libsiedler2::Archiv sng = archiveLoader_->loadFileOrDir(oggFile);
×
248
            auto music = boost::dynamic_pointer_cast<MusicItem>(sng.release(0));
×
249
            if(music)
×
250
                sng_lst.emplace_back(std::move(music));
×
251
            else
252
                logger_.write(_("WARNING: Found invalid music item for %1%\n")) % oggFile;
×
253
        } catch(const LoadError&)
×
254
        {
255
            return false;
×
256
        }
257
    }
258

259
    if(sng_lst.empty())
×
260
    {
261
        logger_.write(_("WARNING: Did not find the music files.\n\tYou have to run the updater once or copy the .ogg "
×
262
                        "files manually to %1% or you won't be able to hear the music.\n"))
263
          % oggPath;
×
264
    }
265

266
    return true;
×
267
}
268

269
bool Loader::LoadFonts()
1✔
270
{
271
    if(!Load(ResourceId("fonts"), GetPaletteN("pal5")))
2✔
272
        return false;
×
273
    fonts.clear();
1✔
274
    const auto& loadedFonts = GetArchive("fonts");
1✔
275
    for(unsigned i = 0; i <= helpers::MaxEnumValue_v<FontSize>; i++)
4✔
276
    {
277
        const auto* curFont = dynamic_cast<const libsiedler2::ArchivItem_Font*>(loadedFonts[i]);
3✔
278
        if(!curFont)
3✔
279
        {
280
            logger_.write(_("Unable to load font at index %1%\n")) % i;
×
281
            return false;
×
282
        }
283
        fonts.emplace_back(*curFont);
3✔
284
    }
285
    return true;
1✔
286
}
287

288
void Loader::LoadDummyGUIFiles()
3✔
289
{
290
    // Palettes
291
    {
292
        auto palette = std::make_unique<libsiedler2::ArchivItem_Palette>();
3✔
293
        for(int i = 0; i < 256; i++)
771✔
294
            palette->set(i, libsiedler2::ColorRGB(42, 137, i));
768✔
295
        files_["pal5"].archive.pushC(*palette);
3✔
296
        // Player color palette
297
        for(int i = 128; i < 128 + libsiedler2::ArchivItem_Bitmap_Player::numPlayerClrs; i++)
15✔
298
            palette->set(i, libsiedler2::ColorRGB(i, i, i));
12✔
299
        files_["colors"].archive.push(std::move(palette));
3✔
300
    }
301
    // GUI elements
302
    libsiedler2::Archiv& resource = files_["resource"].archive;
3✔
303
    resource.alloc(57);
3✔
304
    for(unsigned id = 4; id < 36; id++)
99✔
305
    {
306
        auto bmp = std::make_unique<glArchivItem_Bitmap_RLE>();
192✔
307
        libsiedler2::PixelBufferBGRA buffer(1, 1);
384✔
308
        bmp->create(buffer);
96✔
309
        resource.set(id, std::move(bmp));
96✔
310
    }
311
    for(unsigned id = 36; id < 57; id++)
66✔
312
    {
313
        auto bmp = std::make_unique<glArchivItem_Bitmap_Raw>();
126✔
314
        libsiedler2::PixelBufferBGRA buffer(1, 1);
252✔
315
        bmp->create(buffer);
63✔
316
        resource.set(id, std::move(bmp));
63✔
317
    }
318
    libsiedler2::Archiv& io = files_["io"].archive;
3✔
319
    for(unsigned id = 0; id < 264; id++)
795✔
320
    {
321
        auto bmp = std::make_unique<glArchivItem_Bitmap_Raw>();
1,584✔
322
        libsiedler2::PixelBufferBGRA buffer(1, 1);
3,168✔
323
        bmp->create(buffer);
792✔
324
        io.push(std::move(bmp));
792✔
325
    }
326
    // Fonts
327
    auto* palette = GetPaletteN("colors");
3✔
328
    libsiedler2::PixelBufferBGRA buffer(15, 16);
15✔
329
    for(unsigned i = 0; i <= helpers::MaxEnumValue_v<FontSize>; i++)
12✔
330
    {
331
        auto font = std::make_unique<libsiedler2::ArchivItem_Font>();
18✔
332
        const unsigned dx = 9 + i * (helpers::MaxEnumValue_v<FontSize> + 1);
9✔
333
        const unsigned dy = 10 + i * (helpers::MaxEnumValue_v<FontSize> + 1);
9✔
334
        font->setDx(dx);
9✔
335
        font->setDy(dy);
9✔
336
        font->alloc(255);
9✔
337
        for(unsigned id = 0x20; id < 255; id++)
2,016✔
338
        {
339
            auto bmp = std::make_unique<glArchivItem_Bitmap_Player>();
2,007✔
340
            bmp->create(dx, dy, buffer, palette, 0);
2,007✔
341
            font->set(id, std::move(bmp));
2,007✔
342
        }
343
        fonts.emplace_back(*font);
9✔
344
    }
345
}
3✔
346

347
void Loader::LoadDummyMapFiles()
1✔
348
{
349
    libsiedler2::Archiv& map = files_["map_0_z"].archive;
1✔
350
    if(!map.empty())
1✔
351
        return;
×
352
    const auto pushRange = [&map](unsigned from, unsigned to) {
322✔
353
        map.alloc_inc(to - map.size() + 1);
10✔
354
        libsiedler2::PixelBufferBGRA buffer(1, 1);
50✔
355
        for(unsigned i = from; i <= to; i++)
322✔
356
        {
357
            auto bmp = std::make_unique<glArchivItem_Bitmap_Raw>();
312✔
358
            bmp->create(buffer);
312✔
359
            map.set(i, std::move(bmp));
312✔
360
        };
361
    };
10✔
362
    map_gfx = &map;
1✔
363

364
    // Some ID ranges as found in map_0_z.lst
365
    pushRange(20, 23);
1✔
366
    pushRange(40, 46);
1✔
367
    pushRange(50, 55);
1✔
368
    pushRange(59, 67);
1✔
369
    pushRange(200, 282);
1✔
370
    pushRange(290, 334);
1✔
371
    pushRange(350, 432);
1✔
372
    pushRange(440, 484);
1✔
373
    pushRange(500, 527);
1✔
374
    pushRange(560, 561);
1✔
375

376
    for(int j = 0; j <= 5; j++)
7✔
377
    {
378
        libsiedler2::Archiv& bobs = files_[ResourceId("mis" + std::to_string(j) + "bobs")].archive;
12✔
379
        libsiedler2::PixelBufferBGRA buffer(1, 1);
30✔
380
        for(unsigned i = 0; i <= 10; i++)
72✔
381
        {
382
            auto bmp = std::make_unique<glArchivItem_Bitmap_Raw>();
66✔
383
            bmp->create(buffer);
66✔
384
            bobs.push(std::move(bmp));
66✔
385
        }
386
    }
387
}
388

389
void Loader::LoadDummySoundFiles()
3✔
390
{
391
    libsiedler2::Archiv& archive = files_["sound"].archive;
3✔
392
    archive.alloc(116);
3✔
393
    for(unsigned id = 51; id < archive.size(); id++)
198✔
394
    {
395
        auto snd = std::make_unique<glArchivItem_Sound_Wave>();
195✔
396
        archive.set(id, std::move(snd));
195✔
397
    }
398
}
3✔
399

400
namespace {
401
struct NationResourcesSource
402
{
403
    bfs::path buildingsFilePath, iconsFilePath;
404
};
405

406
NationResourcesSource getNationResourcesSource(const Nation nation, bool isWinter, const RttrConfig& config)
×
407
{
408
    const auto shortName = std::string(NationNames[nation], 0, 3);
×
409
    auto buildingsFilename = shortName + "_Z.LST";
×
410
    auto iconsFilename = shortName + "_ICON.LST";
×
411
    if(isWinter)
×
412
        buildingsFilename.insert(buildingsFilename.begin(), 'W');
×
413
    bfs::path nationFolder;
×
414
    // The original ("native") nations use uppercase file names while we use lowercase names for the new ones
415
    // Especially relevant for case sensitive file systems
416
    if(rttr::enum_cast(nation) < NUM_NATIVE_NATIONS)
×
417
    {
418
        nationFolder = config.ExpandPath(s25::folders::mbob);
×
419
        buildingsFilename = s25util::toUpper(buildingsFilename);
×
420
        iconsFilename = s25util::toUpper(iconsFilename);
×
421
    } else
422
    {
423
        nationFolder = config.ExpandPath(s25::folders::assetsNations) / NationNames[nation];
×
424
        buildingsFilename = s25util::toLower(buildingsFilename);
×
425
        iconsFilename = s25util::toLower(iconsFilename);
×
426
    }
427
    NationResourcesSource result;
×
428
    result.buildingsFilePath = nationFolder / buildingsFilename;
×
429
    result.iconsFilePath = nationFolder / iconsFilename;
×
430
    return result;
×
431
}
432
} // namespace
433

434
/**
435
 *  Load files required during a game
436
 *
437
 *  @param[in] mapGfxPath Path to map gfx files
438
 *  @param[in] isWinterGFX True iff winter nation files should be loaded
439
 *  @param[in] nations True entry for each nation to load
440
 *
441
 *  @return @p true on success
442
 */
443
bool Loader::LoadFilesAtGame(const std::string& mapGfxPath, bool isWinterGFX, const std::vector<Nation>& nations,
×
444
                             const std::vector<AddonId>& enabledAddons)
445
{
446
    initResourceFolders(nations, enabledAddons);
×
447

448
    namespace res = s25::resources;
449
    std::vector<std::string> files = {res::rom_bobs, res::carrier,  res::jobs,     res::boat,
450
                                      res::boot_z,   res::mis0bobs, res::mis1bobs, res::mis2bobs,
451
                                      res::mis3bobs, res::mis4bobs, res::mis5bobs};
×
452

453
    const libsiedler2::ArchivItem_Palette* pal5 = GetPaletteN("pal5");
×
454

455
    if(!LoadFiles(files) || !Load(ResourceId("map_new"), pal5))
×
456
        return false;
×
457

458
    // Load nation building and icon graphics
459
    nation_gfx = nationIcons_ = {};
×
460
    for(Nation nation : nations)
×
461
    {
462
        const auto resourceSource = getNationResourcesSource(nation, isWinterGFX, config_);
×
463
        if(!Load(resourceSource.buildingsFilePath, pal5) || !Load(resourceSource.iconsFilePath, pal5))
×
464
            return false;
×
465
        nation_gfx[nation] = &files_[ResourceId::make(resourceSource.buildingsFilePath)].archive;
×
466
        nationIcons_[nation] = &files_[ResourceId::make(resourceSource.iconsFilePath)].archive;
×
467
    }
468

469
    // TODO: Move to addon folder and make it overwrite existing file
470
    if(!LoadResources({"charburner", "charburner_bobs"}))
×
471
        return false;
×
472

473
    const bfs::path mapGFXFile = config_.ExpandPath(mapGfxPath);
×
474
    if(!Load(mapGFXFile, pal5))
×
475
        return false;
×
476
    map_gfx = &GetArchive(ResourceId::make(mapGFXFile));
×
477

478
    isWinterGFX_ = isWinterGFX;
×
479

480
    return true;
×
481
}
482

483
bool Loader::LoadFiles(const std::vector<std::string>& files)
×
484
{
485
    const libsiedler2::ArchivItem_Palette* pal5 = GetPaletteN("pal5");
×
486
    // load the files
487
    for(const std::string& curFile : files)
×
488
    {
489
        const bfs::path filePath = config_.ExpandPath(curFile);
×
490
        if(!Load(filePath, pal5))
×
491
        {
492
            logger_.write(_("Failed to load %s\n")) % filePath;
×
493
            return false;
×
494
        }
495
    }
496

497
    return true;
×
498
}
499

500
bool Loader::LoadResources(const std::vector<ResourceId>& resources)
×
501
{
502
    const libsiedler2::ArchivItem_Palette* pal5 = GetPaletteN("pal5");
×
503
    for(const ResourceId& curResource : resources)
×
504
    {
505
        if(!Load(curResource, pal5))
×
506
        {
507
            logger_.write(_("Failed to load %s\n")) % curResource;
×
508
            return false;
×
509
        }
510
    }
511

512
    return true;
×
513
}
514

515
void Loader::fillCaches()
×
516
{
517
    stp = std::make_unique<glTexturePacker>();
×
518

519
    // Animals
520
    for(const auto species : helpers::EnumRange<Species>{})
×
521
    {
522
        for(const auto dir : helpers::EnumRange<Direction>{})
×
523
        {
524
            for(unsigned ani_step = 0; ani_step < ANIMALCONSTS[species].animation_steps; ++ani_step)
×
525
            {
526
                glSmartBitmap& bmp = getAnimalSprite(Species(species), dir, ani_step);
×
527

528
                bmp.reset();
×
529

530
                bmp.add(GetMapImage(ANIMALCONSTS[species].walking_id
×
531
                                    + ANIMALCONSTS[species].animation_steps * rttr::enum_cast(dir + 3u) + ani_step));
×
532

533
                if(ANIMALCONSTS[species].shadow_id)
×
534
                {
535
                    if(species == Species::Duck)
×
536
                        // Ente Sonderfall, da gibts nur einen Schatten für jede Richtung!
537
                        bmp.addShadow(GetMapImage(ANIMALCONSTS[species].shadow_id));
×
538
                    else
539
                        // ansonsten immer pro Richtung einen Schatten
540
                        bmp.addShadow(GetMapImage(ANIMALCONSTS[species].shadow_id + rttr::enum_cast(dir + 3u)));
×
541
                }
542

543
                stp->add(bmp);
×
544
            }
545
        }
546

547
        glSmartBitmap& bmp = getDeadAnimalSprite(species);
×
548

549
        bmp.reset();
×
550

551
        if(ANIMALCONSTS[species].dead_id)
×
552
        {
553
            bmp.add(GetMapImage(ANIMALCONSTS[species].dead_id));
×
554

555
            if(ANIMALCONSTS[species].shadow_dead_id)
×
556
            {
557
                bmp.addShadow(GetMapImage(ANIMALCONSTS[species].shadow_dead_id));
×
558
            }
559

560
            stp->add(bmp);
×
561
        }
562
    }
563

564
    glArchivItem_Bob* bob_jobs = GetBob("jobs");
×
565
    if(!bob_jobs)
×
566
        throw std::runtime_error("jobs not found");
×
567

568
    for(const auto nation : helpers::enumRange<Nation>())
×
569
    {
570
        if(!nation_gfx[nation])
×
571
            continue;
×
572
        // BUILDINGS
573
        for(const auto type : helpers::enumRange<BuildingType>())
×
574
        {
575
            BuildingSprites& sprites = building_cache[nation][type];
×
576

577
            sprites.building.reset();
×
578
            sprites.skeleton.reset();
×
579
            sprites.door.reset();
×
580

581
            if(type == BuildingType::Charburner)
×
582
            {
583
                unsigned id = rttr::enum_cast(nation) * 8;
×
584

585
                sprites.building.add(GetImageN("charburner", id + (isWinterGFX_ ? 6 : 1)));
×
586
                sprites.building.addShadow(GetImageN("charburner", id + 2));
×
587

588
                sprites.skeleton.add(GetImageN("charburner", id + 3));
×
589
                sprites.skeleton.addShadow(GetImageN("charburner", id + 4));
×
590

591
                sprites.door.add(GetImageN("charburner", id + (isWinterGFX_ ? 7 : 5)));
×
592
            } else
593
            {
594
                sprites.building.add(GetNationImage(nation, 250 + 5 * rttr::enum_cast(type)));
×
595
                sprites.building.addShadow(GetNationImage(nation, 250 + 5 * rttr::enum_cast(type) + 1));
×
596
                if(type == BuildingType::Headquarters)
×
597
                {
598
                    // HQ has no skeleton, but we have a tent that can act as an HQ
599
                    sprites.skeleton.add(GetImageN("mis0bobs", 6));
×
600
                    sprites.skeleton.addShadow(GetImageN("mis0bobs", 7));
×
601
                } else
602
                {
603
                    sprites.skeleton.add(GetNationImage(nation, 250 + 5 * rttr::enum_cast(type) + 2));
×
604
                    sprites.skeleton.addShadow(GetNationImage(nation, 250 + 5 * rttr::enum_cast(type) + 3));
×
605
                }
606
                sprites.door.add(GetNationImage(nation, 250 + 5 * rttr::enum_cast(type) + 4));
×
607
            }
608

609
            stp->add(sprites.building);
×
610
            stp->add(sprites.skeleton);
×
611
            stp->add(sprites.door);
×
612
        }
613

614
        // FLAGS
615
        for(const auto type : helpers::enumRange<FlagType>())
×
616
        {
617
            for(unsigned ani_step = 0; ani_step < 8; ++ani_step)
×
618
            {
619
                // Flaggentyp berücksichtigen
620
                int nr = ani_step + 100 + 20 * rttr::enum_cast(type);
×
621

622
                glSmartBitmap& bmp = flag_cache[nation][type][ani_step];
×
623

624
                bmp.reset();
×
625

626
                bmp.add(GetNationPlayerImage(nation, nr));
×
627
                bmp.addShadow(GetNationImage(nation, nr + 10));
×
628

629
                stp->add(bmp);
×
630
            }
631
        }
632

633
        // Bobs from jobs.bob.
634
        for(const auto job : helpers::enumRange<Job>())
×
635
        {
636
            for(Direction dir : helpers::EnumRange<Direction>{})
×
637
            {
638
                for(unsigned ani_step = 0; ani_step < 8; ++ani_step)
×
639
                {
640
                    glSmartBitmap& bmp = bob_jobs_cache[nation][job][dir][ani_step];
×
641
                    bmp.reset();
×
642

643
                    const auto& spriteData = JOB_SPRITE_CONSTS[Job(job)];
×
644
                    const libsiedler2::ImgDir imgDir = toImgDir(dir);
×
645

646
                    bmp.add(dynamic_cast<glArchivItem_Bitmap_Player*>(
×
647
                      bob_jobs->getBody(spriteData.isFat(), imgDir, ani_step)));
×
648
                    bmp.add(dynamic_cast<glArchivItem_Bitmap_Player*>(
×
649
                      bob_jobs->getOverlay(spriteData.getBobId(Nation(nation)), spriteData.isFat(), imgDir, ani_step)));
×
650
                    bmp.addShadow(GetMapImage(900 + static_cast<unsigned>(imgDir) * 8 + ani_step));
×
651

652
                    stp->add(bmp);
×
653
                }
654
            }
655
        }
656
        // Fat carrier
657
        for(Direction dir : helpers::EnumRange<Direction>{})
×
658
        {
659
            for(unsigned ani_step = 0; ani_step < 8; ++ani_step)
×
660
            {
661
                glSmartBitmap& bmp = fat_carrier_cache[nation][dir][ani_step];
×
662
                bmp.reset();
×
663

664
                const libsiedler2::ImgDir imgDir = toImgDir(dir);
×
665

666
                bmp.add(dynamic_cast<glArchivItem_Bitmap_Player*>(bob_jobs->getBody(true, imgDir, ani_step)));
×
667
                bmp.add(dynamic_cast<glArchivItem_Bitmap_Player*>(bob_jobs->getOverlay(0, true, imgDir, ani_step)));
×
668
                bmp.addShadow(GetMapImage(900 + static_cast<unsigned>(imgDir) * 8 + ani_step));
×
669

670
                stp->add(bmp);
×
671
            }
672
        }
673

674
        {
675
            glSmartBitmap& bmp = boundary_stone_cache[nation];
×
676
            bmp.reset();
×
677

678
            bmp.add(GetNationPlayerImage(nation, 0));
×
679
            bmp.addShadow(GetNationImage(nation, 1));
×
680

681
            stp->add(bmp);
×
682
        }
683

684
        libsiedler2::Archiv& romBobs = GetArchive("rom_bobs");
×
685
        const auto getAlternative = [&romBobs](unsigned id, unsigned altId) {
×
686
            auto* bmp = convertChecked<glArchivItem_Bitmap_Player*>(romBobs[id]);
×
687
            return bmp ? bmp : convertChecked<glArchivItem_Bitmap_Player*>(romBobs[altId]);
×
688
        };
×
689
        // Special handling for non-native nations: Use roman animations if own are missing
690
        const Nation fallbackNation = rttr::enum_cast(nation) < NUM_NATIVE_NATIONS ? nation : Nation::Romans;
×
691
        const auto& natFightAnimIds = FIGHT_ANIMATIONS[nation];
×
692
        const auto& altNatFightAnimIds = FIGHT_ANIMATIONS[fallbackNation];
×
693
        const auto& natHitIds = HIT_SOLDIERS[nation];
×
694
        const auto& altNatHitIds = HIT_SOLDIERS[fallbackNation];
×
695
        for(unsigned rank = 0; rank < NUM_SOLDIER_RANKS; ++rank)
×
696
        {
697
            for(unsigned dir = 0; dir < 2; ++dir)
×
698
            {
699
                FightSprites& sprites = fight_cache[nation][rank][dir];
×
700
                const auto& fightAnimIds = natFightAnimIds[rank][dir];
×
701
                const auto& altFightAnimIds = altNatFightAnimIds[rank][dir];
×
702
                for(unsigned ani_step = 0; ani_step < 8; ++ani_step)
×
703
                {
704
                    glSmartBitmap& bmp = sprites.attacking[ani_step];
×
705
                    bmp.reset();
×
706
                    bmp.add(getAlternative(fightAnimIds.attacking[ani_step], altFightAnimIds.attacking[ani_step]));
×
707
                    stp->add(bmp);
×
708
                }
709

710
                for(unsigned i = 0; i < sprites.defending.size(); i++)
×
711
                {
712
                    for(unsigned ani_step = 0; ani_step < 8; ++ani_step)
×
713
                    {
714
                        glSmartBitmap& bmp = sprites.defending[i][ani_step];
×
715
                        bmp.reset();
×
716
                        bmp.add(
×
717
                          getAlternative(fightAnimIds.defending[i][ani_step], altFightAnimIds.defending[i][ani_step]));
×
718
                        stp->add(bmp);
×
719
                    }
720
                }
721
                glSmartBitmap& bmp = sprites.hit;
×
722
                bmp.reset();
×
723
                // Hit sprites for left/right are consecutive
724
                bmp.add(getAlternative(natHitIds[rank] + dir, altNatHitIds[rank] + dir));
×
725
                stp->add(bmp);
×
726
            }
727
        }
728
    }
729

730
    // BUILDING FLAG ANIMATION (for military buildings)
731
    /*
732
        for (unsigned ani_step = 0; ani_step < 8; ++ani_step)
733
        {
734
            glSmartBitmap &bmp = building_flag_cache[ani_step];
735

736
            bmp.reset();
737

738
            bmp.add(static_cast<glArchivItem_Bitmap_Player *>(GetMapTexture(3162+ani_step)));
739

740
            int a, b, c, d;
741
            static_cast<glArchivItem_Bitmap_Player *>(GetMapTexture(3162+ani_step))->getVisibleArea(a, b, c, d);
742
            fprintf(stderr, "%i,%i (%ix%i)\n", a, b, c, d);
743

744

745
            stp->add(bmp);
746
        }
747
    */
748
    // Trees
749
    for(unsigned type = 0; type < 9; ++type)
×
750
    {
751
        for(unsigned ani_step = 0; ani_step < 15; ++ani_step)
×
752
        {
753
            glSmartBitmap& bmp = tree_cache[type][ani_step];
×
754

755
            bmp.reset();
×
756

757
            bmp.add(GetMapImage(200 + type * 15 + ani_step));
×
758
            bmp.addShadow(GetMapImage(350 + type * 15 + ani_step));
×
759

760
            stp->add(bmp);
×
761
        }
762
    }
763

764
    // Granite
765
    for(const auto type : helpers::enumRange<GraniteType>())
×
766
    {
767
        for(unsigned size = 0; size < 6; ++size)
×
768
        {
769
            glSmartBitmap& bmp = granite_cache[type][size];
×
770

771
            bmp.reset();
×
772

773
            bmp.add(GetMapImage(516 + rttr::enum_cast(type) * 6 + size));
×
774
            bmp.addShadow(GetMapImage(616 + rttr::enum_cast(type) * 6 + size));
×
775

776
            stp->add(bmp);
×
777
        }
778
    }
779

780
    // Grainfields
781
    for(unsigned type = 0; type < 2; ++type)
×
782
    {
783
        for(unsigned size = 0; size < 4; ++size)
×
784
        {
785
            glSmartBitmap& bmp = grainfield_cache[type][size];
×
786

787
            bmp.reset();
×
788

789
            bmp.add(GetMapImage(532 + type * 5 + size));
×
790
            bmp.addShadow(GetMapImage(632 + type * 5 + size));
×
791

792
            stp->add(bmp);
×
793
        }
794
    }
795

796
    // Donkeys
797
    for(const auto dir : helpers::EnumRange<Direction>{})
×
798
    {
799
        for(unsigned ani_step = 0; ani_step < 8; ++ani_step)
×
800
        {
801
            glSmartBitmap& bmp = getDonkeySprite(dir, ani_step);
×
802

803
            bmp.reset();
×
804

805
            bmp.add(GetMapImage(2000 + rttr::enum_cast(dir + 3u) * 8 + ani_step));
×
806
            bmp.addShadow(GetMapImage(2048 + rttr::enum_cast(dir) % 3));
×
807

808
            stp->add(bmp);
×
809
        }
810
    }
811

812
    // Boats
813
    for(const auto dir : helpers::EnumRange<Direction>{})
×
814
    {
815
        for(unsigned ani_step = 0; ani_step < 8; ++ani_step)
×
816
        {
817
            glSmartBitmap& bmp = getBoatCarrierSprite(dir, ani_step);
×
818

819
            bmp.reset();
×
820

821
            bmp.add(GetPlayerImage("boat", rttr::enum_cast(dir + 3u) * 8 + ani_step));
×
822
            bmp.addShadow(GetMapImage(2048 + rttr::enum_cast(dir) % 3));
×
823

824
            stp->add(bmp);
×
825
        }
826
    }
827

828
    // carrier_cache[ware][direction][animation_step][fat]
829
    glArchivItem_Bob* bob_carrier = GetBob("carrier");
×
830
    if(!bob_carrier)
×
831
        throw std::runtime_error("carrier not found");
×
832

833
    for(bool fat : {true, false})
×
834
    {
835
        for(const auto ware : helpers::EnumRange<GoodType>{})
×
836
        {
837
            for(Direction dir : helpers::EnumRange<Direction>{})
×
838
            {
839
                for(unsigned ani_step = 0; ani_step < 8; ++ani_step)
×
840
                {
841
                    glSmartBitmap& bmp = getCarrierSprite(ware, fat, dir, ani_step);
×
842
                    bmp.reset();
×
843

844
                    // Japanese shield is missing
845
                    const unsigned id =
846
                      rttr::enum_cast((ware == GoodType::ShieldJapanese) ? GoodType::ShieldRomans : ware);
×
847

848
                    const libsiedler2::ImgDir imgDir = toImgDir(dir);
×
849

850
                    bmp.add(dynamic_cast<glArchivItem_Bitmap_Player*>(bob_carrier->getBody(fat, imgDir, ani_step)));
×
851
                    bmp.add(
×
852
                      dynamic_cast<glArchivItem_Bitmap_Player*>(bob_carrier->getOverlay(id, fat, imgDir, ani_step)));
×
853
                    bmp.addShadow(GetMapImage(900 + static_cast<unsigned>(imgDir) * 8 + ani_step));
×
854

855
                    stp->add(bmp);
×
856
                }
857
            }
858
        }
859
    }
860

861
    // gateway animation :)
862
    {
863
        const unsigned char start_index = 248;
×
864
        const unsigned char color_count = 4;
×
865

866
        libsiedler2::ArchivItem_Palette* palette = GetPaletteN("pal5");
×
867
        auto* image = GetMapImage(561);
×
868
        auto* shadow = GetMapImage(661);
×
869

870
        if((image) && (shadow) && (palette))
×
871
        {
872
            unsigned short width = image->getWidth();
×
873
            unsigned short height = image->getHeight();
×
874

875
            std::vector<unsigned char> buffer(width * height, 254);
×
876

877
            image->print(&buffer.front(), width, height, libsiedler2::TextureFormat::Paletted, palette, 0, 0, 0, 0,
×
878
                         width, height);
879

880
            for(unsigned char i = 0; i < color_count; ++i)
×
881
            {
882
                glSmartBitmap& bmp = gateway_cache[i + 1];
×
883

884
                bmp.reset();
×
885

886
                for(unsigned x = 0; x < width; ++x)
×
887
                {
888
                    for(unsigned y = 0; y < height; ++y)
×
889
                    {
890
                        if(buffer[y * width + x] >= start_index && buffer[y * width + x] < start_index + color_count)
×
891
                        {
892
                            if(++buffer[y * width + x] >= start_index + color_count)
×
893
                                buffer[y * width + x] = start_index;
×
894
                        }
895
                    }
896
                }
897

898
                auto bitmap = std::make_unique<glArchivItem_Bitmap_Raw>();
×
899
                bitmap->create(width, height, &buffer.front(), width, height, libsiedler2::TextureFormat::Paletted,
×
900
                               palette);
901
                bitmap->setNx(image->getNx());
×
902
                bitmap->setNy(image->getNy());
×
903

904
                bmp.add(std::move(bitmap));
×
905
                bmp.addShadow(shadow);
×
906

907
                stp->add(bmp);
×
908
            }
×
909
        } else
910
        {
911
            for(unsigned char i = 0; i < color_count; ++i)
×
912
            {
913
                glSmartBitmap& bmp = gateway_cache[i + 1];
×
914

915
                bmp.reset();
×
916
            }
917
        }
918
    }
919

920
    if(SETTINGS.video.shared_textures)
×
921
    {
922
        // generate mega texture
923
        stp->pack();
×
924
    } else
925
        stp.reset();
×
926
}
×
927

928
/**
929
 *  Extrahiert eine Textur aus den Daten.
930
 */
931
std::unique_ptr<glArchivItem_Bitmap> Loader::ExtractTexture(const glArchivItem_Bitmap& srcImg, const Rect& rect)
×
932
{
933
    Extent texSize = rect.getSize();
×
934
    if(texSize.x == 0 && rect.right < srcImg.getWidth())
×
935
        texSize.x = srcImg.getWidth() - rect.right;
×
936
    if(texSize.y == 0 && rect.bottom < srcImg.getHeight())
×
937
        texSize.y = srcImg.getHeight() - rect.bottom;
×
938
    libsiedler2::PixelBufferPaletted buffer(texSize.x, texSize.y);
×
939

940
    if(int ec = srcImg.print(buffer, nullptr, 0, 0, rect.left, rect.top))
×
941
        throw std::runtime_error(std::string("Error loading texture: ") + libsiedler2::getErrorString(ec));
×
942

943
    std::unique_ptr<glArchivItem_Bitmap> bitmap = std::make_unique<glArchivItem_Bitmap_Raw>();
×
944
    if(int ec = bitmap->create(buffer, srcImg.getPalette()))
×
945
    {
946
        throw std::runtime_error(std::string("Error loading texture: ") + libsiedler2::getErrorString(ec));
×
947
    }
948
    return bitmap;
×
949
}
950

951
/**
952
 *  Extrahiert mehrere (animierte) Texturen aus den Daten.
953
 */
954
std::unique_ptr<libsiedler2::Archiv> Loader::ExtractAnimatedTexture(const glArchivItem_Bitmap& srcImg, const Rect& rect,
×
955
                                                                    uint8_t start_index, uint8_t color_count)
956
{
957
    Extent texSize = rect.getSize();
×
958
    if(texSize.x == 0 && rect.right < srcImg.getWidth())
×
959
        texSize.x = srcImg.getWidth() - rect.right;
×
960
    if(texSize.y == 0 && rect.bottom < srcImg.getHeight())
×
961
        texSize.y = srcImg.getHeight() - rect.bottom;
×
962
    libsiedler2::PixelBufferPaletted buffer(texSize.x, texSize.y);
×
963

964
    srcImg.print(buffer, nullptr, 0, 0, rect.left, rect.top);
×
965

966
    auto destination = std::make_unique<libsiedler2::Archiv>();
×
967
    libsiedler2::ArchivItem_PaletteAnimation anim;
×
968
    anim.isActive = true;
×
969
    anim.moveUp = false;
×
970
    anim.firstClr = start_index;
×
971
    anim.lastClr = start_index + color_count - 1u;
×
972
    const libsiedler2::ArchivItem_Palette* curPal = nullptr;
×
973
    for(unsigned i = 0; i < color_count; ++i)
×
974
    {
975
        auto newPal = (i == 0) ? clone(srcImg.getPalette()) : anim.apply(*curPal);
×
976
        auto bitmap = std::make_unique<glArchivItem_Bitmap_Raw>();
×
977
        bitmap->setPalette(std::move(newPal));
×
978
        if(int ec = bitmap->create(buffer))
×
979
            throw std::runtime_error("Error extracting animated texture: " + libsiedler2::getErrorString(ec));
×
980
        curPal = bitmap->getPalette();
×
981
        destination->push(std::move(bitmap));
×
982
    }
983
    return destination;
×
984
}
985

986
bool Loader::Load(libsiedler2::Archiv& archive, const bfs::path& path, const libsiedler2::ArchivItem_Palette* palette)
×
987
{
988
    try
989
    {
990
        archive = archiveLoader_->load(archiveLocator_->resolve(path), palette);
×
991
        return true;
×
992
    } catch(const LoadError&)
×
993
    {
994
        return false;
×
995
    }
996
}
997

998
bool Loader::Load(libsiedler2::Archiv& archive, const ResourceId& resId, const libsiedler2::ArchivItem_Palette* palette)
×
999
{
1000
    const ResolvedFile resolvedFile = archiveLocator_->resolve(resId);
×
1001
    if(!resolvedFile)
×
1002
    {
1003
        logger_.write(_("Failed to resolve resource %1%\n")) % resId;
×
1004
        return false;
×
1005
    }
1006
    try
1007
    {
1008
        archive = archiveLoader_->load(resolvedFile, palette);
×
1009
        return true;
×
1010
    } catch(const LoadError&)
×
1011
    {
1012
        return false;
×
1013
    }
1014
}
1015

1016
template<typename T>
1017
bool Loader::LoadImpl(const T& resIdOrPath, const libsiedler2::ArchivItem_Palette* palette)
9✔
1018
{
1019
    const auto resolvedFile = archiveLocator_->resolve(resIdOrPath);
18✔
1020
    if(!resolvedFile)
9✔
1021
    {
1022
        logger_.write(_("Failed to resolve resource %1%\n")) % resIdOrPath;
×
1023
        return false;
×
1024
    }
1025
    FileEntry& entry = files_[ResourceId::make(resIdOrPath)];
9✔
1026
    // Do we really need to reload or can we reused the loaded version?
1027
    if(entry.resolvedFile != resolvedFile)
9✔
1028
    {
1029
        try
1030
        {
1031
            entry.archive = archiveLoader_->load(resolvedFile, palette);
7✔
1032
        } catch(const LoadError&)
×
1033
        {
1034
            return false;
×
1035
        }
1036
        // Update how we loaded this
1037
        entry.resolvedFile = resolvedFile;
7✔
1038
    }
1039
    RTTR_Assert(!entry.archive.empty());
9✔
1040
    return true;
9✔
1041
}
1042

1043
bool Loader::Load(const bfs::path& path, const libsiedler2::ArchivItem_Palette* palette)
8✔
1044
{
1045
    return LoadImpl(path, palette);
8✔
1046
}
1047

1048
bool Loader::Load(const ResourceId& resId, const libsiedler2::ArchivItem_Palette* palette)
1✔
1049
{
1050
    return LoadImpl(resId, palette);
1✔
1051
}
1052

1053
void addDefaultResourceFolders(const RttrConfig& config, ArchiveLocator& locator,
1✔
1054
                               const std::vector<Nation>& usedNations, const std::vector<AddonId>& enabledAddons)
1055
{
1056
    locator.clear();
1✔
1057
    locator.addAssetFolder(config.ExpandPath(s25::folders::assetsBase));
1✔
1058
    for(Nation nation : usedNations)
1✔
1059
    {
1060
        const auto overrideFolder = config.ExpandPath(s25::folders::assetsNations) / NationNames[nation];
×
1061
        if(bfs::exists(overrideFolder))
×
1062
            locator.addOverrideFolder(overrideFolder);
×
1063
    }
1064
    locator.addOverrideFolder(config.ExpandPath(s25::folders::assetsOverrides));
1✔
1065
    for(AddonId addonId : enabledAddons)
1✔
1066
    {
1067
        const auto overrideFolder = config.ExpandPath(s25::folders::assetsAddons)
×
1068
                                    / s25util::toStringClassic(static_cast<uint32_t>(addonId), true);
×
1069
        if(bfs::exists(overrideFolder))
×
1070
            locator.addOverrideFolder(overrideFolder);
×
1071
    }
1072
    const bfs::path userOverrides = config.ExpandPath(s25::folders::assetsUserOverrides);
3✔
1073
    if(exists(userOverrides))
1✔
1074
        locator.addOverrideFolder(userOverrides);
×
1075
}
1✔
1076

1077
Loader& getGlobalLoader()
1,540✔
1078
{
1079
    static Loader loader{LOG, RTTRCONFIG};
1,540✔
1080
    return loader;
1,540✔
1081
}
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