Skip to content

Commit 96f0f8c

Browse files
committed
Simplify BOB interface
Modify links so the offset to the images is represented there. Hence overlay indices from links array can be directly used Also some refactoring: - Use ImgDir to avoid bulk arguments or misplaced argument order - Use named constants - Translate most comments - Use better variable and function names
1 parent f9c7eec commit 96f0f8c

File tree

4 files changed

+147
-65
lines changed

4 files changed

+147
-65
lines changed

include/libsiedler2/ArchivItem_Bob.h

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,42 +21,50 @@
2121

2222
#include "Archiv.h"
2323
#include "ArchivItem.h"
24+
#include "ImgDir.h"
2425
#include <cstdint>
2526
#include <iosfwd>
2627
#include <map>
2728
#include <vector>
2829

2930
namespace libsiedler2 {
3031
class ArchivItem_Palette;
31-
}
32+
class ArchivItem_Bitmap_Player;
33+
} // namespace libsiedler2
3234

3335
namespace libsiedler2 {
34-
/// Klasse für Bobfiles.
36+
3537
class ArchivItem_Bob : public ArchivItem, public Archiv
3638
{
3739
public:
3840
ArchivItem_Bob();
39-
4041
~ArchivItem_Bob() override;
4142
RTTR_CLONEABLE(ArchivItem_Bob)
4243

43-
/// lädt die Bobdaten aus einer Datei.
44+
/// Load BOB data from file
4445
int load(std::istream& file, const ArchivItem_Palette* palette);
4546

46-
/// schreibt die Bobdaten in eine Datei.
47+
/// Write BOB data to file. TODO: Implement
4748
static int write(std::ostream& file, const ArchivItem_Palette* palette);
4849

49-
uint32_t getNumGoodImgs() const { return numGoodImgs; }
50-
uint32_t getNumItems() const { return uint32_t(links.size()); }
51-
uint16_t getLink(uint32_t idx) const { return links[idx]; };
50+
uint32_t getNumOverlayImgs() const { return numOverlayImgs; }
51+
uint16_t getNumLinks() const { return static_cast<uint16_t>(links.size()); }
52+
uint16_t getOverlayIdx(unsigned idx) const { return links[idx]; };
53+
uint16_t getOverlayIdx(unsigned overlayIdx, bool fat, ImgDir direction, unsigned animationstep) const
54+
{
55+
// Array [overlayIdx][animStep][fat][direction]: [35][8][2][6]
56+
return getOverlayIdx(((overlayIdx * 8 + animationstep) * 2 + fat) * 6 + static_cast<unsigned>(direction));
57+
}
58+
ArchivItem_Bitmap_Player* getBody(bool fat, ImgDir direction, unsigned animationstep);
59+
ArchivItem_Bitmap_Player* getOverlay(unsigned overlayIdx, bool fat, ImgDir direction, unsigned animationstep);
5260

5361
/// Write the links in mapping format (TAB separated entries with # comments)
5462
void writeLinks(std::ostream& file) const;
55-
static std::map<unsigned, uint16_t> readLinks(std::istream& file);
63+
static std::map<uint16_t, uint16_t> readLinks(std::istream& file);
5664

5765
protected:
58-
uint16_t numGoodImgs; /// Number of pictures for wares
59-
std::vector<uint16_t> links; /// "Links" (Zugehörigkeiten der Bilder)
66+
uint16_t numOverlayImgs; /// Number of actual overlay pictures (e.g. carried wares)
67+
std::vector<uint16_t> links; /// Array [overlayId][animStep=8][fat=2][direction=6] mapping to an overlay picture
6068
};
6169
} // namespace libsiedler2
6270

include/libsiedler2/ImgDir.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) 2005 - 2020 Settlers Freaks (sf-team at siedler25.org)
2+
//
3+
// This file is part of Return To The Roots.
4+
//
5+
// Return To The Roots is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 2 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// Return To The Roots is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU General Public License
16+
// along with Return To The Roots. If not, see <http://www.gnu.org/licenses/>.
17+
18+
#ifndef libsiedler2_imgdir_h__
19+
#define libsiedler2_imgdir_h__
20+
21+
namespace libsiedler2 {
22+
/// Direction that image figures are facing/walking
23+
enum class ImgDir
24+
{
25+
// Order as in S2 arrays: Right first, then clockwise
26+
E,
27+
SE,
28+
SW,
29+
W,
30+
NW,
31+
NE
32+
};
33+
} // namespace libsiedler2
34+
35+
#endif // libsiedler2_imgdir_h__

src/ArchivItem_Bob.cpp

Lines changed: 63 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@
2626
#include <boost/range/adaptor/indexed.hpp>
2727
#include <iostream>
2828

29+
namespace {
30+
constexpr uint16_t COLOR_BLOCK_HEADER = 0x01F5;
31+
constexpr uint16_t IMAGE_DATA_HEADER = 0x01F4;
32+
/// 8 Animation Steps, 6 directions, 2 types (Fat, Non-Fat) --> Array [fat][direction][animStep]
33+
constexpr unsigned NUM_BODY_IMAGES = 2 * 6 * 8;
34+
/// Same for links but Array [animStep][fat][direction]
35+
constexpr unsigned NUM_LINKS_PER_OVERLAY = 8 * 2 * 6;
36+
constexpr uint16_t SPRITE_WIDTH = 32;
37+
/// Draw offset in X
38+
constexpr uint16_t X_OFFSET = 16;
39+
} // namespace
40+
2941
namespace libsiedler2 {
3042
/// Read a block of colors used later
3143
static int readColorBlock(libendian::EndianIStreamAdapter<false, std::istream&>& fs, std::vector<uint8_t>& pixels)
@@ -34,7 +46,7 @@ static int readColorBlock(libendian::EndianIStreamAdapter<false, std::istream&>&
3446
if(!(fs >> id >> size))
3547
return ErrorCode::UNEXPECTED_EOF;
3648

37-
if(id != 0x01F5)
49+
if(id != COLOR_BLOCK_HEADER)
3850
return ErrorCode::WRONG_FORMAT;
3951

4052
pixels.resize(size);
@@ -50,7 +62,7 @@ static int readImageData(libendian::EndianIStreamAdapter<false, std::istream&>&
5062
if(!(fs >> id >> height))
5163
return ErrorCode::UNEXPECTED_EOF;
5264

53-
if(id != 0x01F4)
65+
if(id != IMAGE_DATA_HEADER)
5466
return ErrorCode::WRONG_FORMAT;
5567

5668
starts.resize(height);
@@ -59,12 +71,7 @@ static int readImageData(libendian::EndianIStreamAdapter<false, std::istream&>&
5971
return ErrorCode::NONE;
6072
}
6173

62-
/** @class ArchivItem_Bob
63-
*
64-
* Klasse für Bobfiles.
65-
*/
66-
67-
ArchivItem_Bob::ArchivItem_Bob() : ArchivItem(BobType::Bob), numGoodImgs(0) {}
74+
ArchivItem_Bob::ArchivItem_Bob() : ArchivItem(BobType::Bob), numOverlayImgs(0) {}
6875

6976
ArchivItem_Bob::~ArchivItem_Bob() = default;
7077

@@ -90,9 +97,10 @@ int ArchivItem_Bob::load(std::istream& file, const ArchivItem_Palette* palette)
9097
if(int ec = readColorBlock(fs, raw_base))
9198
return ec;
9299

93-
// Einzelner Bilder auslesen ( untere Körper ): 8 Animation Steps, 6 directions, 2 types (Fat, Non-Fat) = 96
94-
alloc(96);
95-
for(uint32_t i = 0; i < 96; ++i)
100+
// Read body images (to get full figure draw this then draw the item image (below) over it)
101+
// They form an array [fat][direction][animStep]
102+
alloc(NUM_BODY_IMAGES);
103+
for(uint32_t i = 0; i < NUM_BODY_IMAGES; ++i)
96104
{
97105
std::vector<uint16_t> starts;
98106
uint8_t ny;
@@ -101,17 +109,17 @@ int ArchivItem_Bob::load(std::istream& file, const ArchivItem_Palette* palette)
101109

102110
auto image = getAllocator().create<ArchivItem_Bitmap_Player>(BobType::BitmapPlayer);
103111
assert(image);
104-
image->setNx(16); //-V522
112+
image->setNx(X_OFFSET);
105113
image->setNy(ny);
106114

107-
int ec = image->load(32, raw_base, starts, true, palette);
115+
int ec = image->load(SPRITE_WIDTH, raw_base, starts, true, palette);
108116
if(ec)
109117
return ec;
110118

111119
set(i, std::move(image));
112120
}
113121

114-
// erstmal die 6 Farbblöcke fr die 6 Richtungen
122+
// Color blocks for each direction
115123
std::array<std::vector<uint8_t>, 6> raw;
116124

117125
for(auto& i : raw)
@@ -120,55 +128,58 @@ int ArchivItem_Bob::load(std::istream& file, const ArchivItem_Palette* palette)
120128
return ec;
121129
}
122130

123-
// Anzahl Warenbilder
124-
fs >> numGoodImgs;
131+
// Number of overlay images (e.g. goods of carriers)
132+
fs >> numOverlayImgs;
125133

126-
alloc_inc(numGoodImgs);
134+
alloc_inc(numOverlayImgs);
127135

128-
std::vector<std::vector<uint16_t>> starts(numGoodImgs);
129-
std::vector<uint8_t> ny(numGoodImgs);
136+
std::vector<std::vector<uint16_t>> starts(numOverlayImgs);
137+
std::vector<uint8_t> ny(numOverlayImgs);
130138

131-
for(uint16_t i = 0; i < numGoodImgs; ++i)
139+
for(uint16_t i = 0; i < numOverlayImgs; ++i)
132140
{
133141
if(int ec = readImageData(fs, starts[i], ny[i]))
134142
return ec;
135143
}
136144

137145
// Number of complete pictures.
138-
// Links form an array: [ware][animStep][fat][direction]: [][8][2][6]
139-
// the item at position 96 + link[ware][animStep][fat][direction] shall be combined
146+
// Links form an array: [overlay][animStep][fat][direction]: [][8][2][6]
147+
// the item at position NUM_BODY_IMAGES + link[overlay][animStep][fat][direction] shall be combined
140148
// with the appropriate body
141-
uint16_t item_count;
142-
if(!(fs >> item_count))
149+
uint16_t numLinks;
150+
if(!(fs >> numLinks))
143151
return ErrorCode::UNEXPECTED_EOF;
144152

145-
links.resize(item_count);
146-
std::vector<bool> loaded(numGoodImgs, false);
153+
links.resize(numLinks);
154+
std::vector<bool> loaded(numOverlayImgs, false);
147155

148-
for(uint32_t i = 0; i < item_count; ++i)
156+
for(uint32_t i = 0; i < numLinks; ++i)
149157
{
150158
uint16_t unknown;
151159
if(!(fs >> links[i] >> unknown))
152160
return ErrorCode::UNEXPECTED_EOF;
153161

154-
if(links[i] >= numGoodImgs)
162+
if(links[i] >= numOverlayImgs)
155163
return ErrorCode::WRONG_FORMAT;
156164

157165
if(loaded[links[i]])
158166
continue;
159167

160168
auto image = getAllocator().create<ArchivItem_Bitmap_Player>(BobType::BitmapPlayer);
161169
assert(image);
162-
image->setNx(16); //-V522
170+
image->setNx(X_OFFSET);
163171
image->setNy(ny[links[i]]);
164172

165-
int ec = image->load(32, raw[i % 6], starts[links[i]], true, palette);
173+
int ec = image->load(SPRITE_WIDTH, raw[i % 6], starts[links[i]], true, palette);
166174
if(ec)
167175
return ec;
168176

169-
set(96 + links[i], std::move(image));
177+
set(NUM_BODY_IMAGES + links[i], std::move(image));
170178
loaded[links[i]] = true;
171179
}
180+
// Adjust links so they point to actual indices in the archive as we moved them by NUM_BODY_IMAGES
181+
for(auto& link : links)
182+
link += NUM_BODY_IMAGES;
172183

173184
return (!fs) ? ErrorCode::UNEXPECTED_EOF : ErrorCode::NONE;
174185
}
@@ -186,21 +197,37 @@ int ArchivItem_Bob::write(std::ostream&, const ArchivItem_Palette*)
186197
return ErrorCode::UNSUPPORTED_FORMAT;
187198
}
188199

200+
ArchivItem_Bitmap_Player* ArchivItem_Bob::getBody(bool fat, ImgDir direction, unsigned animationstep)
201+
{
202+
// Array: [fat][direction][animStep]: [2][6][8]
203+
const unsigned bodyIdx = (fat * 6 + static_cast<unsigned>(direction)) * 8 + animationstep;
204+
return dynamic_cast<ArchivItem_Bitmap_Player*>(get(bodyIdx));
205+
}
206+
207+
ArchivItem_Bitmap_Player* ArchivItem_Bob::getOverlay(unsigned overlayIdx, bool fat, ImgDir direction, unsigned animationstep)
208+
{
209+
return dynamic_cast<ArchivItem_Bitmap_Player*>(get(getOverlayIdx(overlayIdx, fat, direction, animationstep)));
210+
}
211+
189212
void ArchivItem_Bob::writeLinks(std::ostream& file) const
190213
{
191214
// links[][8][2][6]
192215
for(const auto it : links | boost::adaptors::indexed())
193216
{
194-
if(it.index() % (8 * 2 * 6) == 0)
195-
file << "# Job ID " << it.index() / (8 * 2 * 6) << "\n";
217+
if(it.index() % NUM_LINKS_PER_OVERLAY == 0)
218+
file << "# Job ID " << it.index() / NUM_LINKS_PER_OVERLAY << "\n";
196219
file << s25util::toStringClassic(it.index()) << "\t" << s25util::toStringClassic(it.value()) << "\n";
197220
}
198221
}
199222

200-
std::map<unsigned, uint16_t> ArchivItem_Bob::readLinks(std::istream& file)
223+
std::map<uint16_t, uint16_t> ArchivItem_Bob::readLinks(std::istream& file)
201224
{
202-
std::map<unsigned, uint16_t> result;
203-
loadMapping(file, [&result](unsigned idx, const std::string& value) { result[idx] = s25util::fromStringClassic<uint16_t>(value); });
225+
std::map<uint16_t, uint16_t> result;
226+
loadMapping(file, [&result](unsigned idx, const std::string& value) {
227+
if(idx > std::numeric_limits<uint16_t>::max())
228+
throw std::range_error("Index " + std::to_string(idx) + " is to large");
229+
result[idx] = s25util::fromStringClassic<uint16_t>(value);
230+
});
204231
return result;
205232
}
206233

tests/testBob.cpp

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,31 +34,43 @@ BOOST_AUTO_TEST_CASE(LoadBobFile)
3434
return;
3535
const std::string inPath = libsiedler2::test::s2Path + "/DATA/BOBS/CARRIER.BOB";
3636
libsiedler2::Archiv archiv;
37-
BOOST_REQUIRE_EQUAL(libsiedler2::Load(inPath, archiv, palette), 0);
38-
BOOST_REQUIRE_EQUAL(archiv.size(), 1u);
37+
BOOST_TEST_REQUIRE(libsiedler2::Load(inPath, archiv, palette) == 0);
38+
BOOST_TEST_REQUIRE(archiv.size() == 1u);
3939
BOOST_TEST_REQUIRE(dynamic_cast<const libsiedler2::ArchivItem_Bob*>(archiv[0]));
40-
const libsiedler2::ArchivItem_Bob& bob = *static_cast<const libsiedler2::ArchivItem_Bob*>(archiv[0]);
41-
BOOST_REQUIRE_EQUAL(bob.getNumGoodImgs(), 602u);
42-
BOOST_REQUIRE_EQUAL(bob.getNumItems(), 3264u);
43-
BOOST_REQUIRE_EQUAL(bob.size(), 698u);
40+
libsiedler2::ArchivItem_Bob& bob = *static_cast<libsiedler2::ArchivItem_Bob*>(archiv[0]);
41+
BOOST_TEST(bob.getNumOverlayImgs() == 602u);
42+
BOOST_TEST(bob.getNumLinks() == 3264u);
43+
BOOST_TEST(bob.size() == 698u);
4444
for(unsigned i = 0; i < bob.size(); i++)
4545
{
4646
const auto* bmp = dynamic_cast<const libsiedler2::ArchivItem_Bitmap_Player*>(bob[i]);
47-
BOOST_REQUIRE(bmp);
48-
BOOST_REQUIRE_EQUAL(bmp->getNx(), 16);
49-
BOOST_REQUIRE_EQUAL(bmp->getWidth(), 32u);
50-
BOOST_REQUIRE_GT(bmp->getHeight(), 0u);
47+
BOOST_TEST_REQUIRE(bmp);
48+
BOOST_TEST(bmp->getNx() == 16);
49+
BOOST_TEST(bmp->getWidth() == 32u);
50+
BOOST_TEST(bmp->getHeight() > 0u);
5151
}
5252
// Some fixed tests
5353
const auto* bmp = dynamic_cast<const libsiedler2::ArchivItem_Bitmap_Player*>(bob[0]);
54-
BOOST_REQUIRE_EQUAL(bmp->getNy(), 12);
55-
BOOST_REQUIRE_EQUAL(bmp->getHeight(), 13u);
54+
BOOST_TEST(bmp->getNy() == 12);
55+
BOOST_TEST(bmp->getHeight() == 13u);
5656
bmp = dynamic_cast<const libsiedler2::ArchivItem_Bitmap_Player*>(bob[1]);
57-
BOOST_REQUIRE_EQUAL(bmp->getNy(), 13);
58-
BOOST_REQUIRE_EQUAL(bmp->getHeight(), 14u);
57+
BOOST_TEST(bmp->getNy() == 13);
58+
BOOST_TEST(bmp->getHeight() == 14u);
5959
bmp = dynamic_cast<const libsiedler2::ArchivItem_Bitmap_Player*>(bob[697]);
60-
BOOST_REQUIRE_EQUAL(bmp->getNy(), 18);
61-
BOOST_REQUIRE_EQUAL(bmp->getHeight(), 12u);
60+
BOOST_TEST(bmp->getNy() == 18);
61+
BOOST_TEST(bmp->getHeight() == 12u);
62+
63+
// Bodies are an array: array [fat][direction][animStep]: [2][6][8]
64+
using libsiedler2::ImgDir;
65+
BOOST_TEST(bob.getBody(true, ImgDir::W, 5));
66+
BOOST_TEST(bob.getBody(true, ImgDir::W, 5) == bob[1 * 48 + 3 * 8 + 5]);
67+
BOOST_TEST(bob.getBody(false, ImgDir::NW, 3));
68+
BOOST_TEST(bob.getBody(false, ImgDir::NW, 3) == bob[0 * 48 + 4 * 8 + 3]);
69+
// Overlay links are an array: [overlay][animStep][fat][direction]: [][8][2][6]
70+
BOOST_TEST(bob.getOverlay(21, true, ImgDir::W, 5));
71+
BOOST_TEST(bob.getOverlay(21, true, ImgDir::W, 5) == bob[bob.getOverlayIdx(21 * 96 + 5 * 12 + 3 + 1 * 6)]);
72+
BOOST_TEST(bob.getOverlay(13, false, ImgDir::NW, 7));
73+
BOOST_TEST(bob.getOverlay(13, false, ImgDir::NW, 7) == bob[bob.getOverlayIdx(13 * 96 + 7 * 12 + 4 + 0 * 6)]);
6274
}
6375

6476
BOOST_AUTO_TEST_CASE(WriteReadLinks)
@@ -74,11 +86,11 @@ BOOST_AUTO_TEST_CASE(WriteReadLinks)
7486
bob->writeLinks(s);
7587
s.seekg(0);
7688
const auto links = libsiedler2::ArchivItem_Bob::readLinks(s);
77-
for(unsigned i = 0; i < bob->getNumItems(); i++)
89+
for(unsigned i = 0; i < bob->getNumLinks(); i++)
7890
{
7991
const auto it = links.find(i);
8092
BOOST_TEST_REQUIRE((it != links.end()));
81-
BOOST_TEST_REQUIRE(it->second == bob->getLink(i));
93+
BOOST_TEST_REQUIRE(it->second == bob->getOverlayIdx(i));
8294
}
8395
}
8496

0 commit comments

Comments
 (0)