Files
ReSkate/Extension/Profile/local_profile_world.cpp
AyeZeeBB b7936a36c6 Steam IDs in the player list for the team; custom scripts switched off
Player list. A player on the developer, staff or homie list sees each
player's Steam ID in the overlay's PLAYERS list for the session they are
in, after "Host" / "Guest" and the distance, on their own row too. A click
copies it. The lists are the backend's, the same ones the tags come from;
every client in a session already knew the IDs, so this changes who is
shown them and nothing else.

Custom scripts. Nothing in scripts/Custom is read or run any more, whatever
the launcher's "Loose files" setting says: nothing used them, and a mod was
using the folder to load a DLL into the game (package.loadlib from its
init.lua). The loader is kept behind one switch (custom_scripts_enabled in
custom_script_loader.cpp) for the day they come back.

For that day, the loader now also takes Lua's ways of running native code
away before the first custom script runs: package.loadlib, require through
the two C searchers and package.cpath, os.execute and io.popen. If that
does not hold, no custom script runs.

The overlay's Mods page no longer has the INSTALLED / SCRIPTS tabs; it is
the installed mods. Editing the game's own Lua and config through loose
files is unchanged.
2026-10-10 10:52:59 -05:00

325 lines
17 KiB
C++

#include "Engine/Core/Log/logging.h"
#include "runtime_internal.h"
#include "Extension/Rendering/local_graphics_controls.h"
#include "Extension/World/local_park_rotation.h"
#include "Extension/World/local_population_controls.h"
#include "Extension/World/local_world_controls.h"
#include "Extension/World/local_world_layers.h"
namespace dingosdk {
using namespace profile_runtime;
GraphicsControlsModel local_profile_graphics_controls() {
auto& r = graphics_runtime(); std::lock_guard lock(r.mutex);
auto result = r.model; result.available = r.active && local_runtime().active;
return result;
}
void update_local_graphics_controls() noexcept {
if (!local_runtime().active.load(std::memory_order_acquire)) return;
PreserveError preserve;
try { update_graphics_controls(); }
catch (...) { dingosdk::logging::event(dingosdk::logging::Channel::profile, "{\"event\":\"graphics_controls_update_failed\"}"); }
}
bool set_local_graphics_control(std::string_view key, int value) {
auto& s = local_runtime(); auto& r = graphics_runtime();
std::lock_guard native(s.native_mutex); std::lock_guard lock(r.mutex);
if (!s.active || !r.active || value < -1 || value > 1) return false;
auto choices = r.model.choices;
if (key == "reset") choices = {};
else {
const auto it = std::find(graphics_keys.begin(), graphics_keys.end(), key);
if (it == graphics_keys.end()) return false;
choices.effects[it - graphics_keys.begin()] = value;
}
try {
s.store->save_graphics_controls(choices);
r.model.choices = choices; r.last_update = 0;
r.disabled_video_effects.store(graphics_video_mask(choices), std::memory_order_release);
r.model.status = "Saved. Applying graphics controls.";
dingosdk::logging::event(dingosdk::logging::Channel::profile, dingosdk::Json{{"event","graphics_control_saved"},{"key",key},{"value",value}}.dump().c_str());
return true;
} catch (...) { r.model.status = "Graphics controls could not be saved."; return false; }
}
WorldControlsModel local_profile_world_controls() {
auto& r = world_control_runtime(); std::lock_guard lock(r.mutex);
auto result = r.model;
result.environment_available = r.environment_active && local_runtime().active;
result.population_available = r.population_active && local_runtime().active;
for (unsigned i = 0; i < 2; ++i)
result.population_ready[i] = result.population_ready[i] && GetTickCount64() - population_runtime().observed_at[i] < 3000;
return result;
}
void update_local_world_controls(WorldMap map) noexcept {
if (!local_runtime().active.load(std::memory_order_acquire)) return;
PreserveError preserve;
try { update_environment_controls(map); update_population_controls(); }
catch (...) { dingosdk::logging::event(dingosdk::logging::Channel::profile, "{\"event\":\"world_controls_update_failed\"}"); }
}
bool set_local_atmosphere_control(std::string_view key, std::string_view value) {
auto& s=local_runtime(); auto& r=world_control_runtime();
std::lock_guard native(s.native_mutex); std::lock_guard lock(r.mutex);
const auto* control=find_atmosphere_control(key);
if (!s.active || !r.environment_active || !control) return false;
const bool reset=value=="default";
AtmosphereValue v;
if (!reset && !parse_atmosphere_value(*control,value,v)) return false;
if (!reset && control->type==AtmosphereType::texture) {
const auto assets=r.textures.find(control->property);
if (assets==r.textures.end() || !assets->second.contains(v.texture)) return false;
}
auto c=r.model.choices;
if (reset) c.atmosphere.erase(std::string(key)); else c.atmosphere[std::string(key)]=std::move(v);
try {
s.store->save_world_controls(c);r.model.choices=std::move(c);r.last_update=0;
r.model.status="Saved. Applying environment controls.";
return true;
} catch (...) { r.model.status="World controls could not be saved.";return false; }
}
bool reset_local_atmosphere_controls(unsigned kind) {
auto& s=local_runtime();auto& r=world_control_runtime();
std::lock_guard native(s.native_mutex);std::lock_guard lock(r.mutex);
if (!s.active || !r.environment_active || kind>2) return false;
auto c=r.model.choices;
for (const auto& control : atmosphere_controls)
if (atmosphere_kind(control.property)==kind) c.atmosphere.erase(control.key);
if (kind==0) { c.fog=-1;c.fog_distance=-1; }
if (kind==1) { c.sky_brightness=-1;c.clouds=-1; }
if (kind==2) { c.wind_strength=-1;c.wind_direction=-1; }
try {
s.store->save_world_controls(c);r.model.choices=std::move(c);r.last_update=0;
r.model.status="Saved. Restoring environment defaults.";
return true;
} catch (...) { r.model.status="World controls could not be saved.";return false; }
}
bool set_local_world_control(std::string_view key, float value) {
auto& s = local_runtime(); auto& r = world_control_runtime();
std::lock_guard native(s.native_mutex); std::lock_guard lock(r.mutex);
if (!s.active || !std::isfinite(value) || value < -1 || value > 360) return false;
auto c = r.model.choices;
if (key == "traffic" || key == "pedestrians" || key == "fog") {
if (std::floor(value) != value) return false;
if (key == "traffic") c.traffic = static_cast<int>(value);
if (key == "pedestrians") c.pedestrians = static_cast<int>(value);
if (key == "fog") c.fog = static_cast<int>(value);
} else if (key == "fog_distance") c.fog_distance = value;
else if (key == "sky_brightness") c.sky_brightness = value;
else if (key == "clouds") c.clouds = value;
else if (key == "wind_strength") c.wind_strength = value;
else if (key == "wind_direction") c.wind_direction = value;
else if (key == "reset_environment") { const auto traffic = c.traffic, pedestrians = c.pedestrians; c = {}; c.traffic = traffic; c.pedestrians = pedestrians; }
else return false;
if (!valid_world_controls(c)) return false;
const bool population = key == "traffic" || key == "pedestrians";
if (population ? !r.population_active : !r.environment_active) return false;
try {
s.store->save_world_controls(c); r.model.choices = c; r.last_update = 0; r.diagnostic_due = GetTickCount64() + 6000;
r.model.status = "Saved. Applying world controls.";
dingosdk::logging::event(dingosdk::logging::Channel::profile, dingosdk::Json{{"event","world_control_saved"},{"key",key},{"value",value}}.dump().c_str());
return true;
} catch (...) { r.model.status = "World controls could not be saved."; return false; }
}
WorldLayersModel local_profile_world_layers() {
auto& s = local_runtime(); auto& r = world_layers_runtime();
std::lock_guard lock(s.native_mutex);
auto result = r.model;
result.available = s.active.load(std::memory_order_acquire) && r.active.load(std::memory_order_acquire) &&
park_runtime().active.load(std::memory_order_acquire);
return result;
}
void set_local_world_layer_map_hint(WorldMap map) noexcept {
world_layers_runtime().map_hint.store(map, std::memory_order_release);
}
void apply_host_world_layers(bool forced, const WorldLayerChoices& choices) {
auto& s = local_runtime(); auto& r = world_layers_runtime();
std::lock_guard lock(s.native_mutex);
if (r.model.controlled_by_host == forced && (!forced || r.model.choices == choices)) return;
if (!r.lobby_override.apply(r.model, forced, choices)) return;
r.next_poll = 0;
r.model.feedback = forced ? "World layers follow the host's choices for this session."
: "Restoring your local world layer choices.";
}
bool set_local_world_layer(std::string_view layer, std::string_view mode) {
auto& s = local_runtime(); auto& r = world_layers_runtime();
std::lock_guard lock(s.native_mutex);
if (r.model.controlled_by_host) {
r.model.feedback = "The session host is forcing world layer sync.";
return false;
}
if (!s.active.load(std::memory_order_acquire) || !r.active.load(std::memory_order_acquire) ||
!park_runtime().active.load(std::memory_order_acquire)) return false;
try {
const auto it = std::find_if(world_layers().begin(), world_layers().end(), [&](const auto& row) { return row.key == layer; });
if (it == world_layers().end() || !valid_world_layer_mode(mode) || world_layer_kept_off(it->key)) {
r.model.feedback = "Choose a valid world layer and mode."; return false;
}
const auto index = static_cast<unsigned>(it - world_layers().begin());
if (!r.model.ready || r.model.map != it->map || !r.model.supported[index]) {
r.model.feedback = "That world layer is not available in the active level.";
return false;
}
s.store->save_world_layer_choice(index, mode);
r.model.choices[index] = mode;
std::lock_guard lifetime(r.lifetime_mutex);
for (auto& [entity, node] : r.nodes) {
(void)entity;
if (world_layer_uses_slot(index, node.slot))
if (node.pending && GetTickCount64() - node.requested_at > 30000) node.pending.reset();
}
r.next_poll = 0;
r.model.feedback = "World choice saved. Applies when its map is ready.";
return true;
} catch (...) { r.model.feedback = "World choice could not be saved."; return false; }
}
bool restore_local_world_layers(std::string_view map) {
auto& s = local_runtime(); auto& r = world_layers_runtime();
std::lock_guard lock(s.native_mutex);
if (r.model.controlled_by_host) {
r.model.feedback = "The session host is forcing world layer sync.";
return false;
}
if (!s.active.load(std::memory_order_acquire) || !r.active.load(std::memory_order_acquire) ||
!park_runtime().active.load(std::memory_order_acquire)) return false;
WorldMap selected = WorldMap::none;
for (const auto slot : world_map_anchors())
if (world_map_key(world_layer_nodes()[slot].map) == map) selected = world_layer_nodes()[slot].map;
if (selected == WorldMap::none) { r.model.feedback = "Choose a valid world map."; return false; }
try {
// One durable transaction; never publish a partially reset map.
s.store->restore_world_layers(selected);
for (unsigned i = 0; i < world_layers().size(); ++i)
if (world_layers()[i].map == selected) r.model.choices[i] = "default";
std::lock_guard lifetime(r.lifetime_mutex);
for (auto& [entity, node] : r.nodes) {
(void)entity;
if (world_layer_nodes()[node.slot].map == selected && node.pending &&
GetTickCount64() - node.requested_at > 30000) node.pending.reset();
}
r.next_poll = 0;
r.model.feedback = std::string(world_map_label(selected)) + " defaults restored. Layers return to game control.";
return true;
} catch (...) { r.model.feedback = "World defaults could not be saved."; return false; }
}
ParksModel local_profile_parks() {
auto& s = local_runtime();
std::lock_guard lock(s.native_mutex);
auto result = park_runtime().model;
result.available = s.active.load(std::memory_order_acquire) && park_runtime().active.load(std::memory_order_acquire);
return result;
}
bool set_local_park(std::string_view lot, std::string_view layout) {
auto& s = local_runtime(); auto& r = park_runtime();
std::lock_guard lock(s.native_mutex);
if (!s.active.load(std::memory_order_acquire) || !r.active.load(std::memory_order_acquire)) return false;
if (r.model.controlled_by_host) {
r.model.feedback = "The lobby host controls community park layouts.";
return false;
}
try {
const auto it = std::find_if(park_lots.begin(), park_lots.end(), [&](const auto& row) { return row.key == lot; });
if (it == park_lots.end() || layout.empty()) {
r.model.feedback = "Choose a valid park location and layout.";
return false;
}
const auto index = static_cast<unsigned>(it - park_lots.begin());
s.store->save_park_choice(index, layout);
r.launch_randomization.pending = false;
r.model.choices[index] = layout;
r.sent[index].clear();
r.model.feedback = "Park choice saved. It loads when BAM's park controller is ready.";
return true;
} catch (...) {
r.model.feedback = "Park choice could not be saved. Check the profile and layout.";
return false;
}
}
bool load_random_local_parks() {
auto& s = local_runtime(); auto& r = park_runtime();
std::lock_guard lock(s.native_mutex);
if (!s.active || !r.active || !s.store) return false;
if (r.model.controlled_by_host) {
r.model.feedback = "The lobby host controls community park layouts.";
return false;
}
try {
auto choices = random_park_choices();
// Publish only after all three choices are durably saved together.
s.store->save_park_choices(choices);
r.model.choices = std::move(choices);
r.sent = {}; r.clear_unset = {};
r.launch_randomization.pending = false;
// Respect an in-flight clear/load delay; the pending load uses the new choice.
if (r.pending_lot >= park_lots.size()) r.next_request = 0;
r.model.feedback = "Random park choices saved. Loading when the park controller is ready.";
return true;
} catch (...) {
r.model.feedback = "Random park choices could not be saved.";
return false;
}
}
bool set_local_park_randomize_on_launch(bool enabled) {
auto& s = local_runtime(); auto& r = park_runtime();
std::lock_guard lock(s.native_mutex);
if (!s.active || !r.active || !s.store) return false;
try {
s.store->set_user_value("ReSkate.RandomizeParksOnLaunch", enabled);
r.model.randomize_on_launch = enabled;
// Enabling schedules the next launch, never an unexpected mid-session load.
if (!enabled) r.launch_randomization.pending = false;
r.model.feedback = enabled ? "Parks will randomize on the next game launch when you control the layouts." :
"Randomize on Launch is off. Your saved layouts will be used.";
return true;
} catch (...) {
r.model.feedback = "Randomize on Launch could not be saved.";
return false;
}
}
void set_lobby_park_mode(bool active, bool guest) {
auto& s = local_runtime(); auto& r = park_runtime();
std::lock_guard lock(s.native_mutex);
if (r.lobby_active == active && r.model.controlled_by_host == (active && guest)) return;
if (!s.active.load(std::memory_order_acquire) || !r.active.load(std::memory_order_acquire)) return;
// Joining a host cancels this launch's roll even before a park controller
// can tick. Leaving the lobby must restore saved choices without rerolling.
if (active && guest) r.launch_randomization.pending = false;
auto choices = r.model.choices;
if (!active) choices = profile::park_choices(*s.store->shared_snapshot());
else if (!guest) {
// An unselected host slot means an empty lot for this session. Apply it
// locally too, so guests cannot retain a different saved layout.
for (auto& choice : choices) if (choice.empty()) choice = "empty";
}
for (unsigned lot = 0; lot < choices.size(); ++lot)
if (choices[lot] != r.model.choices[lot]) {
r.sent[lot].clear();
r.clear_unset[lot] = choices[lot].empty();
}
r.model.choices = std::move(choices);
r.lobby_active = active;
r.model.controlled_by_host = active && guest;
if (r.pending_lot >= park_lots.size()) r.next_request = 0;
r.model.feedback = active ? (guest ? "Waiting for the host's park settings." :
"Park choices are shared with everyone in this lobby.") : "Restoring your local park choices.";
}
void apply_host_park_choices(const ParkChoices& choices) {
auto& s = local_runtime(); auto& r = park_runtime();
std::lock_guard lock(s.native_mutex);
if (!r.lobby_active || !r.model.controlled_by_host) return;
for (unsigned lot = 0; lot < choices.size(); ++lot)
if (choices[lot].empty() || !valid_park(lot, choices[lot]))
throw std::invalid_argument("Invalid host park selection");
if (choices == r.model.choices) {
if (r.model.feedback == "Waiting for the host's park settings.")
r.model.feedback = "Park layouts follow the lobby host.";
return;
}
for (unsigned lot = 0; lot < choices.size(); ++lot)
if (choices[lot] != r.model.choices[lot]) {
r.sent[lot].clear(); r.clear_unset[lot] = false;
}
r.model.choices = choices;
if (r.pending_lot >= park_lots.size()) r.next_request = 0;
r.model.feedback = "Applying the host's park choices when BAM is ready...";
}
}