diff --git a/Engine/Game/World/world_layers.h b/Engine/Game/World/world_layers.h index 88886f8..ab833a8 100644 --- a/Engine/Game/World/world_layers.h +++ b/Engine/Game/World/world_layers.h @@ -169,6 +169,29 @@ inline WorldLayerMapSelection select_world_layer_map( return result; } +// A TOD__ layer (or a lighting tier's _TOD__): one time of day among its siblings. +inline bool world_layer_time_of_day(std::size_t slot) { + const auto& bundle = world_layer_nodes()[slot].bundle; + auto leaf = bundle.substr(bundle.find_last_of('/') + 1); + for (auto& c : leaf) if (c >= 'A' && c <= 'Z') c = static_cast(c + ('a' - 'A')); + const auto mid = leaf.find("_tod_"); + const auto at = leaf.starts_with("tod_") ? 4 : mid == std::string::npos ? leaf.size() : mid + 5; + return at < leaf.size() && leaf[at] >= '1' && leaf[at] <= '9'; +} + +// A sibling time of day that is to be off but not fully unloaded yet, which `slot` must wait for; +// world_layer_nodes().size() when there is none. +template +std::size_t world_layer_old_time_of_day(std::size_t slot, const std::vector>& desired, FullyOff&& fully_off) { + const auto& nodes = world_layer_nodes(); + if (!world_layer_time_of_day(slot)) return nodes.size(); + for (std::size_t at = 0; at < nodes.size(); ++at) + if (at != slot && nodes[at].parent == nodes[slot].parent && nodes[at].map == nodes[slot].map && + desired[at] == false && world_layer_time_of_day(at) && !fully_off(at)) + return at; + return nodes.size(); +} + inline bool world_layer_supported( std::size_t choice, const std::vector& present) noexcept { diff --git a/Extension/World/local_world_layers.cpp b/Extension/World/local_world_layers.cpp index d1b9374..cef713c 100644 --- a/Extension/World/local_world_layers.cpp +++ b/Extension/World/local_world_layers.cpp @@ -171,8 +171,9 @@ bool ensure_seasonal_state(std::uintptr_t entity, bool wanted, std::uint64_t now } if (reached(wanted)) return true; // An engine-initiated auto-load is still in flight. Wait for it rather than - // claiming success from a non-null request handle or repeatedly reloading. - if (wanted && state.requested) { status = "Loading..."; return false; } + // claiming success from a non-null request handle, repeatedly reloading, or + // unloading it midway (the engine ignores that unload and finishes the load). + if (state.requested && !state.loaded) { status = "Loading..."; return false; } node.pending = wanted; node.requested_at = now; const auto slot = node.slot; SeasonalEvent event{local_runtime().base + addr::world_layers::subworld_event_vtable, wanted ? 0xd27bc17eu : 0x21f3f677u}; @@ -309,6 +310,10 @@ void update_world_layers(std::uintptr_t manager, std::uint64_t now) { std::vector done(targets.size()), inactive(targets.size()); std::vector status(targets.size(), "Waiting for layer"); bool issued{}; + const auto fully_off = [&](std::size_t at) { + SeasonalState live; + return !targets[at] || (seasonal_state(r.nodes.at(targets[at]), live) && !live.requested && !live.loaded); + }; for (unsigned slot = 0; slot < targets.size(); ++slot) { if (world_layer_nodes()[slot].map != map) continue; const auto parent = world_parent(static_cast(slot)); @@ -323,7 +328,12 @@ void update_world_layers(std::uintptr_t manager, std::uint64_t now) { if (!targets[slot]) continue; SeasonalState state; if (!seasonal_state(r.nodes.at(targets[slot]), state)) continue; - if (!desired[slot]) { + // Two times of day loaded at once can overflow the engine's light tables: the old one goes first. + const auto old = desired[slot] == true && !state.requested ? world_layer_old_time_of_day(slot, desired, fully_off) : targets.size(); + if (old < targets.size()) { + status[slot] = "Waiting for the old time of day"; + (void)ensure_seasonal_state(targets[old], false, now, issued, status[old]); + } else if (!desired[slot]) { done[slot] = state.requested == state.loaded; inactive[slot] = done[slot] && !state.loaded; status[slot] = done[slot] ? "Default" : "Loading...";