Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
BoardBackdropPlayback.cpp
Go to the documentation of this file.
2
3#include "LegacyBitmap.hpp"
4
5#include <algorithm>
6#include <fstream>
7#include <utility>
8#include <vector>
9
11{
12 namespace
13 {
14 [[nodiscard]] std::expected<std::uint32_t, std::string> cameraIndex(
16 {
17 const auto value = static_cast<std::uint32_t>(camera);
19 return std::unexpected("UDBoard backdrop camera is outside 0..38");
20 return value;
21 }
22
23 [[nodiscard]] std::expected<std::uint32_t, std::string> cityIndex(
24 int city)
25 {
26 if (city < 0 || city >= static_cast<int>(UsaCityCount))
27 return std::unexpected("UDBoard backdrop city is outside 0..10");
28 return static_cast<std::uint32_t>(city);
29 }
30
32 data::DataTag base, std::uint32_t city, std::uint32_t camera) noexcept
33 {
34 const auto boardIndex = city * BoardsPerCity + camera;
36 static_cast<data::DataTag>(base + boardIndex));
37 }
38
39 [[nodiscard]] std::pair<std::int32_t, std::int32_t> backdropPosition(
40 display::Screen2D view) noexcept
41 {
42 switch (view)
43 {
44 case display::Screen2D::Trade: return {200, 0};
46 case display::Screen2D::Portfolio: return {0, 0};
47 default: return {0, 0};
48 }
49 }
50 }
51
52 std::expected<void, std::string> BoardBackdropPlayback::ensureSurfaces(
54 {
55 if (surfacesReady_) return {};
56
57 std::vector<data::DataId> allocated;
59 for (auto& buffer : mainBuffers_)
60 {
61 const auto created = playback.runtimeBitmaps().create(
63 if (!created)
64 {
65 for (const auto id : allocated) (void)playback.runtimeBitmaps().remove(id);
66 return std::unexpected(created.error());
67 }
68 buffer.surface = *created;
69 buffer.cityLoaded = -1;
70 buffer.viewLoaded = -1;
71 buffer.timeLoaded = 0;
72 buffer.customRootLoaded.clear();
73 allocated.push_back(*created);
74 }
75
76 const auto trade = playback.runtimeBitmaps().create(
78 if (!trade)
79 {
80 for (const auto id : allocated) (void)playback.runtimeBitmaps().remove(id);
81 mainBuffers_ = {};
82 return std::unexpected(trade.error());
83 }
84 tradeSurface_ = *trade;
85 surfacesReady_ = true;
86 return {};
87 }
88
89 std::expected<data::LegacyBitmapRGBA8, std::string>
90 BoardBackdropPlayback::loadBoardBitmap(
92 {
93 const auto resources = playback.resources();
94 if (!resources) return std::unexpected("UDBoard backdrop has no resource snapshot");
95 const auto metadata = resources->data().metadata(id);
96 if (!metadata) return std::unexpected(metadata.error().detail);
98 return std::unexpected("UDBoard backdrop source is not DataBMP");
99
100 const auto bytes = resources->data().load(id);
101 if (!bytes) return std::unexpected(bytes.error().detail);
103 if (!decoded) return std::unexpected(decoded.error().detail);
104 return *decoded;
105 }
106 std::expected<data::LegacyBitmapRGBA8, std::string>
107 BoardBackdropPlayback::loadCustomBoardBitmap(
108 std::uint32_t camera, const std::filesystem::path& customRoot,
110 {
111 const auto resources = playback.resources();
112 if (!resources) return std::unexpected("custom UDBoard backdrop has no resource snapshot");
113 const auto names = data::twoDimensionalBoardTextureNames();
114 if (camera >= names.size())
115 return std::unexpected("custom UDBoard backdrop camera is outside 0..38");
116 const data::BoardTextureContext context{
117 resources->context().board, resources->context().language,
118 -1, 0, customRoot};
119 const auto resolved = data::resolveBoardTexturePath(resources->paths(),
121 names[camera], context);
122 if (!resolved) return std::unexpected(resolved.error());
123
124 constexpr std::uint64_t MaximumFileBytes = 4U * 1024U * 1024U;
125 std::ifstream input(*resolved, std::ios::binary | std::ios::ate);
126 if (!input) return std::unexpected("cannot open custom 2D board bitmap");
127 const auto size = input.tellg();
128 if (size < 0 || static_cast<std::uint64_t>(size) > MaximumFileBytes)
129 return std::unexpected("custom 2D board bitmap exceeds file budget");
130 std::vector<std::byte> bytes(static_cast<std::size_t>(size));
131 input.seekg(0, std::ios::beg);
132 if (!bytes.empty())
133 input.read(reinterpret_cast<char*>(bytes.data()),
134 static_cast<std::streamsize>(bytes.size()));
135 if (!input) return std::unexpected("cannot read complete custom 2D board bitmap");
137 bytes, static_cast<std::size_t>(MainBoardWidth) * MainBoardHeight);
138 if (!decoded) return std::unexpected(decoded.error().detail);
139 return *decoded;
140 }
141
142 std::expected<void, std::string> BoardBackdropPlayback::validateCustomBoardSet(
143 const std::filesystem::path& customRoot,
145 {
146 if (customRoot.empty() || !customRoot.is_absolute())
147 return std::unexpected("custom UDBoard backdrop requires an absolute asset root");
148 const auto resources = playback.resources();
149 if (!resources) return std::unexpected("custom UDBoard backdrop has no resource snapshot");
150 const data::BoardTextureContext context{
151 resources->context().board, resources->context().language,
152 -1, 0, customRoot};
153 std::error_code error;
155 {
156 const auto resolved = data::resolveBoardTexturePath(resources->paths(),
158 name, context);
159 if (!resolved) return std::unexpected(resolved.error());
160 if (!std::filesystem::is_regular_file(*resolved, error) || error)
161 return std::unexpected("custom 2D board set is incomplete");
162 }
163 return {};
164 }
165
166 std::expected<void, std::string> BoardBackdropPlayback::compileImageInto(
167 data::DataId surface, const data::LegacyBitmapRGBA8& sourceImage,
169 {
170 auto& bitmaps = playback.runtimeBitmaps();
171 const auto extent = bitmaps.extent(surface);
172 if (!extent)
173 return std::unexpected("UDBoard backdrop runtime surface is missing");
174
175 // L_Grafix equivalent: reset the native object to opaque black via
176 // ColorArea, then ShowObject/ShowTCB into the same fixed surface.
177 if (const auto cleared = bitmaps.fill(surface, 0, 0,
178 static_cast<std::int32_t>(extent->width),
179 static_cast<std::int32_t>(extent->height), 0x00000000U);
180 !cleared)
181 return cleared;
182
183 return bitmaps.blit(surface, sourceImage, 0, 0,
185 }
186
187 std::expected<void, std::string> BoardBackdropPlayback::compileInto(
190 {
191 const auto sourceImage = loadBoardBitmap(source, playback);
192 if (!sourceImage) return std::unexpected(sourceImage.error());
193 return compileImageInto(surface, *sourceImage, playback);
194 }
195
196 std::expected<data::DataId, std::string> BoardBackdropPlayback::selectBackdrop(
197 display::Screen2D view, int city, pieces::BoardCameraView camera,
198 std::uint32_t tick, const std::filesystem::path& customRoot,
200 {
201 const auto index = cameraIndex(camera);
202 if (!index) return std::unexpected(index.error());
203 const bool custom = city == -1;
204 std::optional<std::uint32_t> cityValue;
205 if (!custom)
206 {
207 const auto stockCity = cityIndex(city);
208 if (!stockCity) return std::unexpected(stockCity.error());
210 }
211
212 if (view == display::Screen2D::Main)
213 {
214 std::size_t oldestIndex = 0;
215 std::uint32_t oldest = mainBuffers_[0].timeLoaded;
216 std::optional<std::size_t> found;
217 for (std::size_t i = 0; i < mainBuffers_.size(); ++i)
218 {
219 if (oldest > mainBuffers_[i].timeLoaded)
220 {
221 oldest = mainBuffers_[i].timeLoaded;
222 oldestIndex = i;
223 }
224 if (mainBuffers_[i].cityLoaded == city &&
225 mainBuffers_[i].viewLoaded == static_cast<int>(*index) &&
226 (!custom || mainBuffers_[i].customRootLoaded == customRoot))
227 found = i;
228 }
229
230 if (found)
231 {
232 currentMainBuffer_ = *found;
233 return mainBuffers_[*found].surface;
234 }
235
236 std::expected<void, std::string> compiled;
237 if (custom)
238 {
239 if (validatedCustomRoot_ != customRoot)
240 {
241 const auto validated = validateCustomBoardSet(customRoot, playback);
242 if (!validated) return std::unexpected(validated.error());
243 validatedCustomRoot_ = customRoot;
244 }
245 const auto sourceImage = loadCustomBoardBitmap(*index, customRoot, playback);
246 if (!sourceImage) return std::unexpected(sourceImage.error());
247 compiled = compileImageInto(
248 mainBuffers_[oldestIndex].surface, *sourceImage, playback);
249 }
250 else
251 {
252 const auto source = boardBitmapId(
254 compiled = compileInto(
255 mainBuffers_[oldestIndex].surface, source, playback);
256 }
257 if (!compiled) return std::unexpected(compiled.error());
258 mainBuffers_[oldestIndex].cityLoaded = city;
259 mainBuffers_[oldestIndex].viewLoaded = static_cast<int>(*index);
260 mainBuffers_[oldestIndex].timeLoaded = tick;
261 mainBuffers_[oldestIndex].customRootLoaded = custom
262 ? customRoot : std::filesystem::path{};
263 currentMainBuffer_ = oldestIndex;
264 return mainBuffers_[oldestIndex].surface;
265 }
266
267 if (view == display::Screen2D::Portfolio ||
269 {
270 // Legacy custom boards replace the full-size Main camera set only.
271 // Trade/Portfolio continue to use the stock classic-small board.
272 const auto source = boardBitmapId(
274 const auto compiled = compileInto(tradeSurface_, source, playback);
275 if (!compiled) return std::unexpected(compiled.error());
276 return tradeSurface_;
277 }
278
279 return std::unexpected("UDBoard backdrop requested for non-board view");
280 }
281
282 std::expected<void, std::string> BoardBackdropPlayback::sync(
284 {
285 // UDBoard.cpp selects these full-screen backgrounds independently of
286 // the board's 2D/3D mode, city and camera (including custom boards).
288 switch (inputs.view)
289 {
292 break;
296 break;
299 break;
300 default: break;
301 }
303 {
304 if (activeBackdrop_ == staticBackdrop && currentView_ == inputs.view)
305 return {};
306 const auto previous = activeBackdrop_ == data::EmptyDataId
307 ? std::optional<data::DataId>{} : std::optional{activeBackdrop_};
308 const auto transitioned = playback.transitionXY(
310 if (!transitioned) return transitioned;
311 activeBackdrop_ = staticBackdrop;
312 currentView_ = inputs.view;
313 currentCity_.reset();
314 currentCamera_.reset();
315 currentCustomRoot_.clear();
316 return {};
317 }
318 const bool shouldRun = display::isBoardVisible(inputs.view) && !inputs.game3DOn;
319 if (!shouldRun)
320 {
321 if (activeBackdrop_ == data::EmptyDataId) return {};
322 if (playback.commands().pendingCount() >=
324 return std::unexpected("sequence command queue capacity exceeded");
325 const auto stopped = playback.stop(activeBackdrop_, BoardBackdropPriority);
326 if (!stopped) return stopped;
327 activeBackdrop_ = data::EmptyDataId;
328 currentView_ = display::Screen2D::Invalid;
329 currentCity_.reset();
330 currentCamera_.reset();
331 currentCustomRoot_.clear();
332 return {};
333 }
334
335 const bool custom = inputs.city == -1;
336 if (custom)
337 {
338 if (inputs.customRoot.empty() || !inputs.customRoot.is_absolute())
339 return std::unexpected("custom UDBoard backdrop requires an absolute asset root");
340 if (inputs.view == display::Screen2D::Main &&
341 validatedCustomRoot_ != inputs.customRoot)
342 {
343 const auto validated = validateCustomBoardSet(inputs.customRoot, playback);
344 if (!validated) return std::unexpected(validated.error());
345 validatedCustomRoot_ = inputs.customRoot;
346 }
347 }
348 else
349 {
350 const auto cityValue = cityIndex(inputs.city);
351 if (!cityValue) return std::unexpected(cityValue.error());
352 }
353 const auto requestedCustomRoot = custom
354 ? inputs.customRoot : std::filesystem::path{};
355
356 if (activeBackdrop_ != data::EmptyDataId &&
357 currentView_ == inputs.view &&
358 currentCity_ == inputs.city &&
359 currentCamera_ == inputs.camera &&
360 currentCustomRoot_ == requestedCustomRoot)
361 return {};
362
363 const std::size_t required =
364 activeBackdrop_ == data::EmptyDataId ? 2U : 3U;
365 if (playback.commands().pendingCount() >
367 return std::unexpected("sequence command queue capacity exceeded");
368
369 const auto surfaces = ensureSurfaces(playback);
370 if (!surfaces) return surfaces;
371 const auto selected = selectBackdrop(
372 inputs.view, inputs.city, inputs.camera, inputs.tick,
374 if (!selected) return std::unexpected(selected.error());
375
376 const auto [x, y] = backdropPosition(inputs.view);
377 const std::optional<data::DataId> previous =
378 activeBackdrop_ == data::EmptyDataId
379 ? std::nullopt : std::optional<data::DataId>{activeBackdrop_};
380 const auto transitioned = playback.transitionXY(
381 previous, *selected, BoardBackdropPriority, x, y, false);
382 if (!transitioned) return transitioned;
383
384 activeBackdrop_ = *selected;
385 currentView_ = inputs.view;
386 currentCity_ = inputs.city;
387 currentCamera_ = inputs.camera;
388 currentCustomRoot_ = requestedCustomRoot;
389 return {};
390 }
391
393 {
394 mainBuffers_ = {};
395 tradeSurface_ = data::EmptyDataId;
396 currentMainBuffer_.reset();
397 activeBackdrop_ = data::EmptyDataId;
398 currentView_ = display::Screen2D::Invalid;
399 currentCity_.reset();
400 currentCamera_.reset();
401 currentCustomRoot_.clear();
402 validatedCustomRoot_.clear();
403 surfacesReady_ = false;
404 }
405}
const char * name
LegacyBitmapMetadata metadata
std::vector< std::byte > bytes
std::shared_ptr< const data::HmdTextureImage > source
unsigned index
std::string error
Bytes input
std::uint16_t value
Definition Timers.cpp:16
std::expected< void, std::string > sync(const BoardBackdropInputs &inputs, engine::SequencePlayback &playback)
constexpr std::uint32_t TradeBoardWidth
constexpr data::DataTag TradeBoardBitmapBaseTag
constexpr std::uint16_t BoardBackdropPriority
constexpr std::uint32_t BoardsPerCity
constexpr std::uint32_t BoardCameraCount
constexpr std::uint32_t TradeBoardHeight
constexpr std::uint32_t UsaCityCount
constexpr data::DataTag MainBoardBitmapBaseTag
constexpr std::size_t MainBoardBufferCount
constexpr std::uint32_t MainBoardWidth
constexpr std::uint32_t MainBoardHeight
std::uint32_t DataId
Definition DataBanks.hpp:10
constexpr DataId EmptyDataId
Definition DataBanks.hpp:13
std::uint16_t DataTag
Definition DataBanks.hpp:11
std::span< const std::string_view > twoDimensionalBoardTextureNames() noexcept
std::expected< LegacyBitmapRGBA8, BitmapError > decodeLegacyBitmapRGBA8(std::span< const std::byte > bytes, std::size_t maxPixels)
std::expected< std::filesystem::path, std::string > resolveBoardTexturePath(const ResourcePaths &stockPaths, BoardMeshKind mesh, TextureLocation location, std::string_view fileName, const BoardTextureContext &context)
constexpr DataId packDataId(std::uint16_t group, DataTag tag) noexcept
Definition DataBanks.hpp:39
constexpr bool isBoardVisible(Screen2D screen) noexcept
Definition Display.hpp:41