mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-24 18:16:27 +02:00
Throwdowns: a plugin refusing its card no longer blocks every later flag, and joins say what they did
A plugin that calls flag.leave() on `open` (Tag and Infected outside a session) sent the game an exit from a setup that never opened. The game never took it, and every later flag's setup waited behind it. A registration left before its setup was asked for now just ends, from flag.leave() and from the waiting card's Leave alike (leave_unopened). The join card says which flag it read and how it was branded, or which buttons it has when it has no Enroll; its branding errors were swallowed. Joining another player's plugin flag says whether its plugin heard join, or why the player was taken out of its queue. A navigation that waits on the game's says so every 3 s, and a setup that does not open in 15 s says so.
This commit is contained in:
1 parent
a598ce602b
commit
77542e4db9
4 files changed
+73
-8
No files matched your search
@@ -89,6 +89,16 @@ void to_cleanup(Placement& p, std::uint64_t now) {
|
||||
if (p.step != Step::cleanup) p.sent = 0;
|
||||
p.cancelling = true; p.step = Step::cleanup; p.until = now + 15000;
|
||||
}
|
||||
// Left before its setup was asked for (a plugin refusing its card at `open`): nothing of the game's
|
||||
// is open to back out of, so the registration just ends. An exit the game never takes stays
|
||||
// pending, and every later flag's setup waits behind it (2026-10-21, hosting with a player).
|
||||
bool leave_unopened(Placement& p) {
|
||||
if (p.step != Step::open || p.created || p.setup_seen) return false;
|
||||
p.owner_left = true;
|
||||
retire(p);
|
||||
p.status.clear();
|
||||
return true;
|
||||
}
|
||||
void open_request(Placement& p, const Request& r, std::uint64_t now) {
|
||||
reset(p);
|
||||
p.step = Step::open; p.mode = r.mode; p.game = r.game; p.options = r.options; p.world = r.world;
|
||||
@@ -117,7 +127,17 @@ bool navigate(const menu_data::Context& c, std::string_view name, std::uint32_t
|
||||
const auto current = c.field(roots.front().model, 0xf3ba9cf8);
|
||||
const auto metadata = c.field(roots.front().model, 0xf437b255);
|
||||
const auto pending = read<std::uint32_t>(c.address(current));
|
||||
if (pending && pending != consumed) return false;
|
||||
if (pending && pending != consumed) {
|
||||
// The game's own navigation is still on its way: tried again at the next tick. Said every
|
||||
// few seconds, with what it is, for a setup that never opens.
|
||||
static std::uint64_t next_note{};
|
||||
if (const auto now = GetTickCount64(); now >= next_note) {
|
||||
next_note = now + 3000;
|
||||
logging::log(logging::Level::info, logging::Channel::ui, "Native flag: navigation {} waits; the game's navigation 0x{:08x} is pending.",
|
||||
name, pending);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
c.set(current, game::native_name_hash(name));
|
||||
c.array(metadata, std::vector<std::int32_t>{-1});
|
||||
logging::log(logging::Level::info, logging::Channel::ui, "Native flag: navigation {}.", name);
|
||||
@@ -210,7 +230,7 @@ bool leave() noexcept {
|
||||
if (p.step == Step::idle) return false;
|
||||
note(p, "leave", {std::to_string(p.game)});
|
||||
p.pending.reset(); // Quit/Cancel must not reopen a queued replacement
|
||||
to_cleanup(p, GetTickCount64());
|
||||
if (!leave_unopened(p)) to_cleanup(p, GetTickCount64());
|
||||
}
|
||||
flush();
|
||||
return true;
|
||||
@@ -381,6 +401,8 @@ void tick(const menu_data::Context& c) {
|
||||
return;
|
||||
}
|
||||
if ((p.step == Step::open || p.step == Step::setup) && !p.created && !p.setup_seen && now >= p.until) {
|
||||
logging::log(logging::Level::warning, logging::Channel::ui, "Native flag: the setup for {} did not open in 15 s (step {}).", p.mode,
|
||||
static_cast<unsigned>(p.step));
|
||||
retire(p); p.status = "The setup did not open. Select the mode to try again.";
|
||||
return; // no native queue was created; do not close another menu
|
||||
}
|
||||
@@ -480,7 +502,7 @@ void leave() noexcept {
|
||||
using namespace multiplayer::native_flag;
|
||||
auto& p = placement(); std::lock_guard lock(p.mutex);
|
||||
p.pending.reset();
|
||||
if (p.step == Step::idle) return;
|
||||
if (p.step == Step::idle || leave_unopened(p)) return;
|
||||
p.owner_left = true;
|
||||
to_cleanup(p, GetTickCount64());
|
||||
}
|
||||
|
||||
@@ -543,8 +543,13 @@ void on_local(Relay &r, ThrowdownLocalAction &a, std::uint64_t now) {
|
||||
}
|
||||
if(joined) {
|
||||
close_hosted(r);leave_mirrors(r,a.mmid);flag.claimed=true;
|
||||
note("you joined another player's {} (MMID {:#x}); its plugin hears join.",flag.offer.mode,a.mmid);
|
||||
}
|
||||
else {
|
||||
note("you joined another player's {} (MMID {:#x}), but {}; you were taken out of its queue.",flag.offer.mode,a.mmid,
|
||||
!mode?"no plugin here registers that mode":!flag.offer.open?"it is closed":"another throwdown of yours is still open");
|
||||
if(const auto local=local_native_player_id())queue_throwdown_remove(local);
|
||||
}
|
||||
else if(const auto local=local_native_player_id())queue_throwdown_remove(local);
|
||||
return;
|
||||
}
|
||||
// Joining a queue removes the player from any other one and destroys a drop it hosts.
|
||||
|
||||
@@ -131,17 +131,37 @@ void restore_join_branding(const Context& c) {
|
||||
for(auto& [handle,card]:state().join_cards)restore_branding_presenters(c,card.presenters);
|
||||
state().join_cards.clear();
|
||||
}
|
||||
// What the join card of the flag the game last filled details for came to, said when it changes:
|
||||
// the only record of a card that stays the donor's.
|
||||
void join_card_report(std::string text) {
|
||||
auto& s=state();
|
||||
if(text==s.join_report)return;
|
||||
s.join_report=std::move(text);
|
||||
if(!s.join_report.empty())logging::log(logging::Level::info,logging::Channel::ui,"Join card: {}",s.join_report);
|
||||
}
|
||||
void custom_join_details(const Context& c) {
|
||||
auto& s=state();const auto mmid=s.join_mmid.load(std::memory_order_acquire);
|
||||
if(!mmid) {join_card_report({});restore_join_branding(c);return;}
|
||||
const auto definition=flag_brand(mmid);
|
||||
if(!definition || (!s.in_menu && !s.join_cards.empty())) {
|
||||
if(!definition) {
|
||||
const auto remote=custom_throwdown_flag_info(mmid);
|
||||
join_card_report(std::format("MMID {:#x} {}; the card stays the game's.",mmid,
|
||||
flag_plumbing::owns(mmid)?"is this game's own flag, whose mode has no definition":
|
||||
remote?"is "+remote->key+"'s flag from another player, whose mode is not registered here":"is not a plugin mode's flag"));
|
||||
restore_join_branding(c);s.join_mmid.store(0);return;
|
||||
}
|
||||
if(!s.in_menu && !s.join_cards.empty()) {restore_join_branding(c);s.join_mmid.store(0);return;}
|
||||
std::vector<std::string> seen;
|
||||
bool branded{};
|
||||
try {
|
||||
for(const auto& root:c.roots({panel_schema.hash})) {
|
||||
const auto buttons=ui_models(c,c.field(root.model,0xbfc64535),button.hash);
|
||||
const bool enroll=std::any_of(buttons.begin(),buttons.end(),[&](const auto b){
|
||||
const auto text=c.text(c.path(b,{0x58c9d354,text_field}));return text=="ID_ENROLL_LABEL" || text=="ENROLL";
|
||||
});
|
||||
bool enroll{};
|
||||
for(const auto b:buttons) {
|
||||
const auto text=c.text(c.path(b,{0x58c9d354,text_field}));
|
||||
if(text=="ID_ENROLL_LABEL" || text=="ENROLL")enroll=true;
|
||||
else if(seen.size()<8)seen.push_back(text);
|
||||
}
|
||||
if(!enroll)continue;
|
||||
auto& card=s.join_cards[root.model.handle];
|
||||
if(std::any_of(card.presenters.begin(),card.presenters.end(),[&](const auto& p){return !branding_presenter_attached(c,p);}))
|
||||
@@ -170,7 +190,17 @@ void custom_join_details(const Context& c) {
|
||||
}
|
||||
mode_art(c,std::array{c.field(card.badge,0x6e469d2a)},definition->logo);
|
||||
c.set_if_changed(c.field(header,content),Widget{asset(c,"UI/Foundations/Components/Media/Icons/Texture_Widget"),{0,card.badge.handle}});
|
||||
branded=true;
|
||||
}
|
||||
} catch(const std::exception& e) {
|
||||
join_card_report(std::format("MMID {:#x} ({}): branding failed: {}",mmid,definition->name,e.what()));
|
||||
throw;
|
||||
}
|
||||
if(branded) {join_card_report(std::format("MMID {:#x} shows {}.",mmid,definition->name));return;}
|
||||
std::string buttons;
|
||||
for(const auto& text:seen)buttons+=(buttons.empty()?"":", ")+(text.empty()?std::string("(no text)"):text);
|
||||
join_card_report(std::format("MMID {:#x} ({}): no card with an Enroll button is up{}.",mmid,definition->name,
|
||||
buttons.empty()?std::string{}:"; its buttons: "+buttons));
|
||||
}
|
||||
void throwdown_map_identity(const Context& c) {
|
||||
auto& b=state().map_branding;
|
||||
|
||||
@@ -202,6 +202,7 @@ struct State {
|
||||
MapBranding map_branding;
|
||||
std::atomic<std::uint32_t> join_mmid{};
|
||||
std::map<Handle,JoinCard> join_cards;
|
||||
std::string join_report; // join_card_report's last line
|
||||
MatchPanel match_panel;
|
||||
std::atomic<bool> match_panel_open{};
|
||||
std::mutex panel_input_mutex;
|
||||
@@ -2232,6 +2233,13 @@ void observe_native_custom_join_details(std::uintptr_t vm) noexcept {
|
||||
try {
|
||||
const auto mmid=menu_data::native_join_mmid(vm,[](Address at,void* out,std::size_t count){return memory::peek_bytes(at,out,count);});
|
||||
if(mmid)state().join_mmid.store(*mmid,std::memory_order_release);
|
||||
// Said when it changes: a card whose MMID cannot be read is never branded.
|
||||
static std::atomic<std::uint64_t> last{~0ULL};
|
||||
const std::uint64_t value=mmid?*mmid:1ULL<<32;
|
||||
if(last.exchange(value)!=value) {
|
||||
if(mmid)logging::log(logging::Level::info,logging::Channel::ui,"Join card: the game filled the details of MMID {:#x}.",*mmid);
|
||||
else logging::log(logging::Level::info,logging::Channel::ui,"Join card: the game filled a throwdown's details; its MMID could not be read.");
|
||||
}
|
||||
} catch(...) {}
|
||||
}
|
||||
void observe_native_flag_interaction(std::uintptr_t vm) noexcept {
|
||||
|
||||
Reference in new issue
Block a user