Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
TradePropertyPlayback.cpp
Go to the documentation of this file.
2
3#include "IBarLayout.hpp"
5
6#include <algorithm>
7
8#include <memory>
9#include <utility>
10#include <variant>
11#include <vector>
12
13namespace monopoly::tradeui
14{
15 namespace
16 {
17 using DesiredObjects =
18 std::array<PropertyPlayback::ObjectState, TradePropertyPlaybackObjectCount>;
19
20 [[nodiscard]] std::pair<PropertyMask, PropertyMask> boxMasks(
21 const PropertyProjection& projection,
22 std::size_t box) noexcept
23 {
24 switch (box)
25 {
26 case 0: return {projection.before[0], projection.beforeMortgaged[0]};
27 case 1: return {projection.before[1], projection.beforeMortgaged[1]};
28 case 2: return {projection.offered[0], projection.offeredMortgaged[0]};
29 case 3: return {projection.offered[1], projection.offeredMortgaged[1]};
30 default: return {};
31 }
32 }
33
35 std::uint8_t fromBox,
36 std::uint8_t toBox) noexcept
37 {
38 return (fromBox == 0 && toBox == 2) ||
39 (fromBox == 1 && toBox == 3) ||
40 (fromBox == 2 && toBox == 0) ||
41 (fromBox == 3 && toBox == 1);
42 }
43
44 [[nodiscard]] std::expected<DesiredObjects, std::string> desiredObjects(
45 const State& state,
46 const rules::GameState& gameState,
48 const PropertyMoveRequest* moving)
49 {
52 return desired;
53
54 const auto projection = projectProperties(state, gameState);
55 for (std::size_t box = 0; box < TradePropertyBasePriorities.size(); ++box)
56 {
57 const auto [normal, mortgaged] = boxMasks(projection, box);
58 const auto visible = normal | mortgaged;
59 for (int square = 0; square < static_cast<int>(rules::SquareCount); ++square)
60 {
61 if (moving && moving->toBox == box && moving->square == square)
62 continue;
63
64 const auto bit = ibar::layout::propertyBit(square);
65 if (bit == 0 || (visible & bit) == 0)
66 continue;
67
68 const bool isMortgaged = (mortgaged & bit) != 0;
69 const auto id = tradePropertyDataId(square, isMortgaged);
70 const auto& rect = projection.hitRects[box][static_cast<std::size_t>(square)];
71 if (id == data::EmptyDataId || rect.right <= rect.left || rect.bottom <= rect.top)
72 return std::unexpected("trade deed projection contains invalid property geometry");
73
74 const auto offset = projection.priorities[box][static_cast<std::size_t>(square)];
75 if (offset < 0)
76 return std::unexpected("trade deed projection contains invalid priority");
77 const auto index = box * static_cast<std::size_t>(rules::SquareCount) +
78 static_cast<std::size_t>(square);
79 desired[index] = {
80 id,
81 static_cast<std::uint16_t>(
82 TradePropertyBasePriorities[box] + static_cast<std::uint16_t>(offset)),
83 rect.left,
84 rect.top};
85 }
86 }
87 return desired;
88 }
89 }
90
91 data::DataId tradePropertyDataId(int square, bool mortgaged) noexcept
92 {
93 const auto property = ibar::layout::propertyIndex(square);
94 if (property < 0)
95 return data::EmptyDataId;
96 const auto base = mortgaged
99 return data::packDataId(
101 static_cast<data::DataTag>(base + property));
102 }
103
104 std::expected<void, std::string> PropertyPlayback::sync(
105 State& state,
108 std::uint64_t nowMs,
110 const mouse::State& pointer,
111 std::uint64_t hoverTick,
112 int city)
113 {
114 std::optional<MovingState> nextMoving = moving_;
115 bool startMoving = false;
116 bool stepMoving = false;
117 bool finishMoving = false;
118 bool clearMoveRequest = false;
119
121 {
122 finishMoving = nextMoving.has_value();
123 nextMoving.reset();
124 clearMoveRequest = state.propertyMove.has_value();
125 }
126 else if (!nextMoving && state.propertyMove)
127 {
128 const auto& request = *state.propertyMove;
129 if (request.square < 0 ||
130 request.square >= static_cast<int>(rules::SquareCount) ||
131 !validMoveBoxes(request.fromBox, request.toBox) ||
132 request.from.right <= request.from.left ||
133 request.from.bottom <= request.from.top ||
134 request.to.right <= request.to.left ||
135 request.to.bottom <= request.to.top)
136 {
137 return std::unexpected("trade deed move request is invalid");
138 }
139
140 const auto id = tradePropertyDataId(request.square, request.mortgaged);
141 if (id == data::EmptyDataId)
142 return std::unexpected("trade deed move request references non-property square");
143 if (gameState.squares[static_cast<std::size_t>(request.square)].mortgaged !=
144 request.mortgaged)
145 {
146 return std::unexpected("trade deed move request mortgage state changed");
147 }
148
149 MovingState move{};
150 move.request = request;
151 move.id = id;
152 move.priority = TradePropertyMovingPriorities[request.toBox];
153 move.deltaX = (request.to.left - request.from.left) / TradePropertyMoveSteps;
154 move.deltaY = (request.to.top - request.from.top) / TradePropertyMoveSteps;
155 move.x = request.from.left + move.deltaX;
156 move.y = request.from.top + move.deltaY;
157 move.count = 2;
158 move.lastMoveMs = nowMs;
159 nextMoving = move;
160 startMoving = true;
161 }
162 else if (nextMoving)
163 {
164 if (!state.propertyMove || *state.propertyMove != nextMoving->request)
165 return std::unexpected("trade deed move request changed during playback");
166
167 const auto elapsed = nowMs >= nextMoving->lastMoveMs
168 ? nowMs - nextMoving->lastMoveMs
169 : 0U;
171 {
172 nextMoving->lastMoveMs = nowMs;
174 {
175 finishMoving = true;
176 nextMoving.reset();
177 clearMoveRequest = true;
178 }
179 else
180 {
181 nextMoving->x += nextMoving->deltaX;
182 nextMoving->y += nextMoving->deltaY;
183 ++nextMoving->count;
184 stepMoving = true;
185 }
186 }
187 }
188
190 nextMoving ? &nextMoving->request : nullptr;
191 const auto desired = desiredObjects(
193 if (!desired)
194 return std::unexpected(desired.error());
195
196 std::array<std::shared_ptr<const sequence::SequenceProgram>,
198 for (std::size_t index = 0; index < desired->size(); ++index)
199 {
200 const auto& before = current_[index];
201 const auto& after = (*desired)[index];
202 const bool needsStart = after.id != data::EmptyDataId &&
203 (before.id != after.id || before.priority != after.priority);
204 if (!needsStart)
205 continue;
206
207 auto loaded = sequence::SequenceProgram::load(playback.resources(), after.id);
208 if (!loaded)
209 return std::unexpected(loaded.error().detail);
210 programs[index] = std::move(*loaded);
211 }
212
213 std::shared_ptr<const sequence::SequenceProgram> movingProgram;
214 if (startMoving)
215 {
217 playback.resources(), nextMoving->id);
218 if (!loaded)
219 return std::unexpected(loaded.error().detail);
220 movingProgram = std::move(*loaded);
221 }
222
223 // UDTrade refreshes the hit from the live pointer even without a new
224 // input event: offers and modal dialogs can change beneath the mouse.
225 int hover = -1;
227 !state.cashDialogVisible)
228 {
230 pointer.x, pointer.y))
231 hover = *hit + (state.contractDialogVisible ? 0 : 1000);
232 }
233 int nextCheckedHover = checkedHover_;
234 auto nextHoverStartTick = hoverStartTick_;
236 if (hover < 0)
237 nextCheckedHover = -1;
238 else if (hover == checkedHover_)
239 {
240 if (hoverTick >= hoverStartTick_ &&
241 hoverTick - hoverStartTick_ > TradeDeedHoverDelayTicks)
242 {
243 const int square = hover % 100;
244 const bool mortgaged = gameState.squares[
245 static_cast<std::size_t>(square)].mortgaged;
246 const auto base = mortgaged ? ibar::PropertyHoverMortgagedBaseTag
248 const auto fallback = data::packDataId(
250 static_cast<data::DataTag>(base + ibar::layout::propertyIndex(square)
251 + 28 * std::max(city, 0)));
252 nextHoverDeed = playback.deedDataId(square, !mortgaged, fallback);
253 }
254 }
255 else
256 {
257 // Moving straight between deeds retains the original delay, but
258 // changes the check for one frame exactly as UDIBar does.
259 if (checkedHover_ == -1)
262 }
263 std::shared_ptr<const sequence::SequenceProgram> hoverProgram;
264 if (nextHoverDeed != hoverDeed_ && nextHoverDeed != data::EmptyDataId)
265 {
266 auto loaded = playback.loadProgram(nextHoverDeed);
267 if (!loaded)
268 return std::unexpected(loaded.error());
269 hoverProgram = std::move(*loaded);
270 }
271
272 std::vector<sequence::SequenceCommand> commands;
273 if (finishMoving && moving_)
274 {
275 commands.push_back(sequence::StopSequenceCommand{
276 moving_->id, moving_->priority, false});
277 }
278
279 for (std::size_t index = 0; index < desired->size(); ++index)
280 {
281 const auto& before = current_[index];
282 const auto& after = (*desired)[index];
283 const bool identityChanged =
284 before.id != after.id || before.priority != after.priority;
285
286 if (identityChanged && before.id != data::EmptyDataId)
287 {
288 commands.push_back(sequence::StopSequenceCommand{
289 before.id, before.priority, false});
290 }
291 if (identityChanged && after.id != data::EmptyDataId)
292 {
293 commands.push_back(sequence::StartSequenceCommand{
294 programs[index], after.priority, {}});
295 commands.push_back(sequence::makeMoveXY(
296 after.id, after.priority, after.x, after.y));
297 }
298 else if (!identityChanged && after.id != data::EmptyDataId &&
299 (before.x != after.x || before.y != after.y))
300 {
301 commands.push_back(sequence::makeMoveXY(
302 after.id, after.priority, after.x, after.y));
303 }
304 }
305
306 if (startMoving)
307 {
308 commands.push_back(sequence::StartSequenceCommand{
310 commands.push_back(sequence::makeMoveXY(
311 nextMoving->id, nextMoving->priority,
312 nextMoving->x, nextMoving->y));
313 }
314 else if (stepMoving)
315 {
316 commands.push_back(sequence::makeMoveXY(
317 nextMoving->id, nextMoving->priority,
318 nextMoving->x, nextMoving->y));
319 }
320
321 if (nextHoverDeed != hoverDeed_)
322 {
323 if (hoverDeed_ != data::EmptyDataId)
324 commands.push_back(sequence::StopSequenceCommand{
325 hoverDeed_, TradeDeedHoverPriority, false});
327 {
328 commands.push_back(sequence::StartSequenceCommand{
329 std::move(hoverProgram), TradeDeedHoverPriority, {}});
330 commands.push_back(sequence::makeMoveXY(nextHoverDeed,
331 TradeDeedHoverPriority, hover >= 1000 ? 600 : 0, -2));
332 }
333 }
334
335 if (commands.size() > sequence::SequenceCommandQueue::Capacity -
336 playback.commands().pendingCount())
337 {
338 return std::unexpected(
339 "sequence command queue cannot fit trade deed transition");
340 }
341
342 for (auto& command : commands)
343 {
344 const auto queued = std::visit(
345 [&](auto value)
346 {
347 return playback.commands().enqueue(std::move(value));
348 },
349 std::move(command));
350 if (!queued)
351 return std::unexpected("validated trade deed command rejected");
352 }
353
354 checkedHover_ = nextCheckedHover;
355 hoverStartTick_ = nextHoverStartTick;
356 // Update only from the successfully queued placement transition, never
357 // from a newer pointer/dialog state while the old deed remains active.
358 if(nextHoverDeed != hoverDeed_)
359 hoverDeedRightPanel_ = nextHoverDeed != data::EmptyDataId && hover >= 1000;
360 hoverDeed_ = nextHoverDeed;
361 current_ = *desired;
362 moving_ = std::move(nextMoving);
364 state.propertyMove.reset();
365 return {};
366 }
367
369 {
370 current_ = {};
371 moving_.reset();
372 checkedHover_ = -1;
373 hoverStartTick_ = 0;
374 hoverDeed_ = data::EmptyDataId;
375 hoverDeedRightPanel_ = false;
376 }
377}
std::size_t offset
data::DataId id
unsigned state
unsigned index
Rect rect
std::uint16_t value
Definition Timers.cpp:16
static std::expected< std::shared_ptr< const SequenceProgram >, RuntimeError > load(const data::DataSource &source, data::DataId id, std::size_t offset=0, DescriptionLimits limits={})
std::expected< void, std::string > sync(State &state, const rules::GameState &gameState, display::Screen2D desiredView, std::uint64_t nowMs, engine::SequencePlayback &playback, const mouse::State &pointer={}, std::uint64_t hoverTick=0, int city=0)
std::uint32_t DataId
Definition DataBanks.hpp:10
constexpr DataId EmptyDataId
Definition DataBanks.hpp:13
std::uint16_t DataTag
Definition DataBanks.hpp:11
constexpr DataId packDataId(std::uint16_t group, DataTag tag) noexcept
Definition DataBanks.hpp:39
int propertyIndex(int square) noexcept
PropertyMask propertyBit(int square) noexcept
constexpr data::DataTag PropertyHoverNormalBaseTag
constexpr data::DataTag PropertyHoverMortgagedBaseTag
constexpr std::size_t SquareCount
Definition RuleTypes.hpp:24
MoveSequenceCommand makeMoveXY(data::DataId dataId, std::uint16_t priority, std::int32_t x, std::int32_t y) noexcept
PropertyProjection projectProperties(const State &state, const rules::GameState &gameState) noexcept
Definition TradeUI.cpp:811
constexpr std::uint64_t TradeDeedHoverDelayTicks
constexpr std::uint16_t TradeDeedHoverPriority
std::optional< int > propertyHit(const PropertyProjection &projection, int x, int y) noexcept
Definition TradeUI.cpp:988
constexpr std::array< std::uint16_t, 4 > TradePropertyBasePriorities
constexpr data::DataTag TradePropertyNormalBaseTag
constexpr int TradePropertyMoveSteps
constexpr std::uint64_t TradePropertyMoveStepMs
constexpr data::DataTag TradePropertyMortgagedBaseTag
constexpr std::array< std::uint16_t, 4 > TradePropertyMovingPriorities
constexpr std::size_t TradePropertyPlaybackObjectCount
data::DataId tradePropertyDataId(int square, bool mortgaged) noexcept