17 return std::unexpected(
"Player selection rule wrap width must be positive");
24 (
static_cast<unsigned char>(
text[
offset]) & 0xC0U) == 0x80U)
29 std::vector<std::string>
lines;
30 std::string remaining(
value);
31 while (!remaining.empty() &&
lines.size() < maxLines)
33 const auto whole =
font.measure(remaining);
34 if (!
whole)
return std::unexpected(
whole.error().detail);
37 lines.push_back(std::move(remaining));
41 std::size_t
cut = std::string::npos;
42 std::size_t
search = remaining.size();
45 const auto space = remaining.rfind(
' ',
search - 1);
46 if (
space == std::string::npos)
break;
48 if (!
prefix)
return std::unexpected(
prefix.error().detail);
57 if (
cut != std::string::npos)
59 lines.push_back(remaining.substr(0,
cut));
60 remaining.erase(0,
cut + 1);
66 auto wordEnd = remaining.find(
' ');
72 const auto prefix =
font.measure(std::string_view(remaining).
substr(0, end));
73 if (!
prefix)
return std::unexpected(
prefix.error().detail);
82 if (
next == end)
break;
86 lines.push_back(remaining.substr(0,
best));
87 remaining.erase(0,
best);
97 using Spec = detail::ObjectSpec;
98 constexpr std::array<int, 8> CardX{35,141,241,344,36,141,242,345};
99 constexpr std::array<int, 8>
CardY{253,253,253,253,370,371,371,372};
100 constexpr std::array<std::array<int,4>,16>
Values{{
101 {{4,5,0,0}}, {{12,32,60,88}}, {{4,12,16,22}}, {{0,250,500,750}},
102 {{500,1000,1500,2000}}, {{0,100,200,400}}, {{0,5,10,15}},
103 {{0,100,200,400}}, {{0,75,150,300}}, {{1,2,3,4}}, {{0,50,100,200}},
104 {{0,1,6,12}}, {{0,1,3,6}}, {{0,5,10,20}}, {{3,4,5,10}}, {{0,2,4,28}}
107 template<
class Char> std::string
utf8(std::basic_string_view<Char>
value)
109 const char*
encoding =
sizeof(
Char) == 2 ?
"UTF-16LE" :
"UTF-32LE";
119 std::expected<std::string,std::string>
text(
120 const engine::SequencePlayback&
playback, std::uint32_t
id)
124 return std::unexpected(
"Player selection language catalog unavailable");
125 auto result =
resources->language()->catalog->message(
id);
126 if (!result)
return std::unexpected(result.error().detail);
136 bool enabled(
O object,
const RenderState&
s)
140 case O::ENTERNAME_BUTTON_NEXT:
return s.setup.name.size()>1;
141 case O::SELECTPLAYER_BUTTON_MORE:
return s.setup.playerLogCount>8;
142 case O::START_BUTTON_ADDHUMAN:
144 case O::START_BUTTON_REMOVEPLAYER:
return s.localPlayers>0;
145 case O::START_BUTTON_STARTGAME:
146 return s.setup.serverMode &&
s.game.numberOfPlayers>=2 &&
147 std::any_of(
s.game.players.begin(),
s.game.players.begin()+
149 [](
const auto&
p){return p.aiPlayerLevel==0;});
150 case O::RULES_BUTTON_OKAY:
return s.setup.serverMode;
153 constexpr std::array<O,11>
buttons{O::PICKTOKEN_BUTTON_CANNON,
154 O::PICKTOKEN_BUTTON_RACECAR,O::PICKTOKEN_BUTTON_DOG,O::PICKTOKEN_BUTTON_TOPHAT,
155 O::PICKTOKEN_BUTTON_IRON,O::PICKTOKEN_BUTTON_HORSE,O::PICKTOKEN_BUTTON_BATTLESHIP,
156 O::PICKTOKEN_BUTTON_SHOE,O::PICKTOKEN_BUTTON_THIMBLE,
157 O::PICKTOKEN_BUTTON_WHEELBARROW,O::PICKTOKEN_BUTTON_SACKOFMONEY};
158 constexpr std::array<O,11>
rotating{O::PICKTOKEN_ROTATING_CANNON,
159 O::PICKTOKEN_ROTATING_RACECAR,O::PICKTOKEN_ROTATING_DOG,O::PICKTOKEN_ROTATING_TOPHAT,
160 O::PICKTOKEN_ROTATING_IRON,O::PICKTOKEN_ROTATING_HORSE,O::PICKTOKEN_ROTATING_BATTLESHIP,
161 O::PICKTOKEN_ROTATING_SHOE,O::PICKTOKEN_ROTATING_THIMBLE,
162 O::PICKTOKEN_ROTATING_WHEELBARROW,O::PICKTOKEN_ROTATING_SACKOFMONEY};
163 for (std::uint8_t
i=0;
i<11;++
i)
177 const P target = visible ?
s.setup.phase : P::None;
180 std::vector<sequence::SequenceCommand> commands;
183 if (
object.
id==
id)
return {};
184 std::shared_ptr<const sequence::SequenceProgram> program;
190 if (!
loaded)
return std::unexpected(
loaded.error().detail);
209 if (
auto r=
change(
object,0);!
r)
return r;
212 if (!
object.animating)
continue;
214 if (!info || info->sequenceClock<info->endTime)
continue;
215 if(
auto r=
change(
object,
object.leaving ? 0 :
object.idle);!
r)
return r;
216 object.animating=
false;
218 std::erase_if(
next,[](
const auto& item){
return item.second.id==0;});
224 if(
object.leaving)
continue;
227 object.animating=
object.id!=0;
229 std::erase_if(
next,[](
const auto& item){
return item.second.id==0;});
240 [](
const auto& item){return item.second.animating;});
243 if(spec.phase!=
target || !spec.idle || !
enabled(spec.object,
s))
continue;
244 if(spec.object>=O::PICKTOKEN_ROTATING_CANNON && spec.object<=O::PICKTOKEN_ROTATING_SHOE &&
246 if(
europe && (spec.object==O::CITY_STATIC || spec.object==O::CITY_BUTTON_LEFT || spec.object==O::CITY_BUTTON_RIGHT))
continue;
247 if(!
europe && (spec.object==O::COUNTRY_STATIC || spec.object==O::COUNTRY_BUTTON_LEFT || spec.object==O::COUNTRY_BUTTON_RIGHT || spec.object==O::CURRENCY_STATIC || spec.object==O::CURRENCY_BUTTON_LEFT || spec.object==O::CURRENCY_BUTTON_RIGHT))
continue;
248 desired.emplace(
static_cast<int>(spec.object),spec);
253 return std::unexpected(
"Player selection font runtime unavailable");
255 *textCacheFont_==
font->settings() && textCacheResources_==
playback.resources() &&
256 textCacheBoard_==
s.setup.boardEdition && textCachePage_==
s.setup.playerLogPageStart &&
257 textCacheCount_==
s.setup.playerLogCount;
258 if(
target==P::SelectPlayer)
261 for(std::size_t
i=0;
i<8 &&
s.setup.playerLogPageStart+
i<
s.setup.playerLogCount;++
i)
264 const int key=220+
static_cast<int>(
i);
265 const auto cached=textCache_.find(
key);
266 const auto surface=surfaces_.find(
key);
269 cached->second.text==
s.setup.playerLog[
s.setup.playerLogPageStart+
i] &&
270 surface!=surfaces_.end() &&
live!=
next.end() && !
live->second.leaving &&
271 live->second.id==surface->second &&
272 playback.runtimeBitmaps().asset(surface->second)==cached->second.asset;
273 const auto priority=
static_cast<std::uint16_t
>(1500+
i);
280 const int key=220+
static_cast<int>(
i);
282 static_cast<std::uint16_t
>(1501+
i)});
288 if(
auto r=
font->saveSettings(9);!
r)
return std::unexpected(
r.error().detail);
290 auto setFont=[&](
int size,
int weight)->std::expected<void,std::string>
292 auto r=
font->setSize(size);
if(!
r)
return std::unexpected(
r.error().detail);
293 font->setWeight(weight);
return {};
297 if(
value.empty())
return {};
308 if(
found==surfaces_.end())
311 if(!
id)
return std::unexpected(
id.
error());
312 found=surfaces_.emplace(
key,*
id).first;
334 if(
auto r=setFont(16,700);!
r)
return r;
338 const auto&
entry=
s.highScores[
i];
340 const auto wins=std::to_string(
entry.wins);
342 if(!
worth)
return std::unexpected(
worth.error());
343 const int y=100+
static_cast<int>(
i)*21;
352 if(
auto r=surface(202,std::move(
image),9,170,1500);!
r)
return r;
358 if(
auto r=surface(200,std::move(
image),153,361,1500);!
r)
return r;
360 if(
target==P::RemovePlayer)
362 if(
auto r=setFont(16,700);!
r)
return r;
370 if(
auto r=surface(201,std::move(
image),100,281,1500);!
r)
return r;
372 if(
target==P::SelectPlayer)
374 for(std::size_t
i=0;
i<8 &&
s.setup.playerLogPageStart+
i<
s.setup.playerLogCount;++
i)
377 ++playerNameRasterizations_;
378 if(
auto r=setFont(8,700);!
r)
return r;
379 for(
int size=8;size>1;--size)
383 if(
auto r=setFont(size-1,700);!
r)
return r;
387 const auto priority=
static_cast<std::uint16_t
>(1500+
i);
391 const int key=220+
static_cast<int>(
i);
393 s.setup.playerLog[
s.setup.playerLogPageStart+
i],
394 playback.runtimeBitmaps().asset(surfaces_.at(
key))});
400 constexpr std::array<const char*,11>
Cities{
"Classic",
"Atlanta",
"Boston",
"Chicago",
"Dallas",
"Los Angeles",
"New York",
"San Francisco",
"Seattle",
"Toronto",
"Washington"};
401 if(
auto r=setFont(8,400);!
r)
return r;
404 const int selected=
field==0?
s.setup.citySelected:
s.setup.currencySelection[std::clamp(
s.setup.currencySelectionIndex,0,2)];
407 if(!
value)
return std::unexpected(
value.error());
409 if(
auto r=setFont(8,400);!
r)
return r;
418 if(
target==P::CustomizeRules)
423 const auto&
o=
s.game.options;
424 const std::array<int,21> selected{
o.housesPerHotel,
o.maximumHouses,
o.maximumHotels,
o.freeParkingSeed,
o.initialCash,
o.passingGoAmount,
o.taxRate,
o.flatTaxFee,
o.luxuryTaxAmount,
o.maximumTurnsInJail,
o.getOutOfJailFee,
o.houseShortageLevel,
o.hotelShortageLevel,
o.interestRate,
o.auctionGoingTimeDelay,
o.dealNPropertiesAtStartup,
o.evenBuildRule,
o.doubleSalaryOnGo,
o.freeParkingPot,
o.futureRentTradingAllowed&&
o.immunitiesTradingAllowed,
o.dealFreePropertiesAtStartup};
425 constexpr std::array<int,16>
DefaultChoice{1,3,3,2,2,2,2,2,1,2,1,2,2,2,2,0};
429 if(
auto r=setFont(8,400);!
r)
return r;
432 if(
auto r=setFont(8,700);!
r)
return r;
434 for(std::size_t line=0;line<
wrapped->size();++line)
437 for(
int choice=0;choice<
count;++choice)
439 const int bx=
x+(
row==0?choice+2:
row<16?choice:3)*43;
441 const int key=300+
static_cast<int>(
nextHits.size());
450 std::expected<std::string,std::string>
value=std::to_string(
Values[
row][choice]);
453 if(!
value)
return std::unexpected(
value.error());
458 if(
y>450){
x=407;
y=100;}
461 const int y1=
y+(450-
y-72)/3,
y2=
y+2*(450-
y-72)/3+36;
463 desired[
static_cast<int>(O::RULES_BUTTON_RESTORESTANDARD)].
x=
bx;
464 desired[
static_cast<int>(O::RULES_BUTTON_RESTORESTANDARD)].
y=
y1;
465 desired[
static_cast<int>(O::RULES_BUTTON_SHORTGAME)].
x=
bx;
466 desired[
static_cast<int>(O::RULES_BUTTON_SHORTGAME)].
y=
y2;
467 if(!
s.setup.serverMode)
472 if(
auto r=surface(240,std::move(
image),7,100,
europe?1658:1654);!
r)
return r;
479 const bool pressed=
s.pressSerial!=pressSerial_ &&
480 ((spec.object==O::CITY_BUTTON_LEFT &&
s.pressedButton==B::CityLeft) ||
481 (spec.object==O::CITY_BUTTON_RIGHT &&
s.pressedButton==B::CityRight) ||
482 (spec.object==O::COUNTRY_BUTTON_LEFT &&
s.pressedButton==B::CountryLeft) ||
483 (spec.object==O::COUNTRY_BUTTON_RIGHT &&
s.pressedButton==B::CountryRight) ||
484 (spec.object==O::CURRENCY_BUTTON_LEFT &&
s.pressedButton==B::CurrencyLeft) ||
485 (spec.object==O::CURRENCY_BUTTON_RIGHT &&
s.pressedButton==B::CurrencyRight));
488 object.priority=spec.priority;
object.x=spec.x;
object.y=spec.y;
489 object.idle=spec.idle;
object.out=spec.out;
object.leaving=
false;
490 object.loop=spec.object>=O::PICKTOKEN_ROTATING_CANNON && spec.object<=O::PICKTOKEN_ROTATING_SHOE;
491 object.animating=spec.in!=0;
492 if(
auto r=
change(
object,spec.in?spec.in:spec.idle);!
r)
return r;
497 if(
desired.contains(
key)||
object.leaving)
continue;
499 object.animating=
object.id!=0;
object.leaving=
true;
502 std::erase_if(
next,[](
const auto& item){
return item.second.id==0;});
504 return std::unexpected(
"Sequence queue cannot fit player selection transition");
505 for(
auto& command:commands)
506 if(!std::visit([&](
auto item){
return playback.commands().enqueue(std::move(item));},std::move(command)))
507 return std::unexpected(
"Validated player selection command rejected");
508 live_=std::move(
next);
513 textCacheResources_=
playback.resources();
514 textCacheBoard_=
s.setup.boardEdition;
515 textCachePage_=
s.setup.playerLogPageStart;
516 textCacheCount_=
s.setup.playerLogCount;
518 pressSerial_=
s.pressSerial;
524 if (
target == P::None) startedPhase_.reset();
526 ready_=phase_==
target && std::none_of(live_.begin(),live_.end(),[](
const auto& item){return item.second.animating;});
533 live_.clear();surfaces_.clear();textCache_.clear();ruleHits_.clear();
534 textCacheFont_.reset();textCacheResources_.reset();
535 textCachePage_=textCacheCount_=0;playerNameRasterizations_=0;
536 restoreRect_={};shortRect_={};phase_=P::None;ready_=
false;
537 pressSerial_=0;interactable_=
false;startedPhase_.reset();
std::shared_ptr< const sequence::SequenceProgram > on
std::expected< void, std::string > sync(const RenderState &, fonts::Runtime *, engine::SequencePlayback &)
static constexpr std::size_t Capacity
static std::expected< std::shared_ptr< const SequenceProgram >, RuntimeError > rawBitmap(data::DataId id, data::LegacyDataType sourceType, DescriptionLimits limits={})
static std::expected< std::shared_ptr< const SequenceProgram >, RuntimeError > load(const data::DataSource &source, data::DataId id, std::size_t offset=0, DescriptionLimits limits={})
bool isRuntimeBitmapDataId(DataId id) noexcept
std::expected< std::string, std::string > format(std::int64_t defaultValue, int monetarySystem, bool printSymbol, data::BoardEdition edition)
std::expected< std::vector< std::string >, std::string > wrapRuleDescription(fonts::Runtime &font, std::string_view value, int maxPixelWidth, std::size_t maxLines)
std::vector< ObjectSpec > catalog(bool europe)
constexpr PlayerNumber MaxPlayers
SequenceTransform moveXYTransform(std::int32_t x, std::int32_t y) noexcept
std::shared_ptr< const data::ResourceSnapshot > resources() noexcept
bool tokenAvailable(const GameState &uiState, std::uint8_t token)
std::vector< std::uint8_t > pixels
std::uint8_t endingAction