mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-25 08:24:45 +01:00
layer scan fix
This commit is contained in:
1 parent
d895c9e915
commit
1664c0a855
11 files changed
+352
-3
No files matched your search
@@ -17,6 +17,15 @@ inline constexpr std::uintptr_t publish_materials = 0x1331420;
|
||||
inline constexpr std::array<unsigned char, 24> publish_materials_prefix{
|
||||
0x48,0x89,0x5c,0x24,0x08,0x89,0x54,0x24,0x10,0x55,0x56,0x57,0x41,0x54,0x41,0x55,
|
||||
0x41,0x56,0x41,0x57,0x48,0x83,0xec,0x20};
|
||||
// Typed per-instance shader setter reached by MaterialInstance:addEsVector.
|
||||
// (material, ParamDbKey*, NativeValue*, priority) -> success. Inserts with the
|
||||
// engine allocator and preserves the existing priority rules.
|
||||
inline constexpr std::uintptr_t set_shader_parameter = 0x1312d00;
|
||||
inline constexpr std::array<unsigned char, 24> set_shader_parameter_prefix{
|
||||
0x48,0x89,0x5c,0x24,0x20,0x48,0x89,0x4c,0x24,0x08,0x55,0x56,0x57,0x41,0x54,0x41,
|
||||
0x55,0x48,0x8d,0x6c,0x24,0xc9,0x48,0x81};
|
||||
// Initialized ColorRgb type descriptor; validate its type hash, size and index.
|
||||
inline constexpr std::uintptr_t color_rgb_type = 0x7780c50;
|
||||
// Finds a named entry in a loaded table by the FNV-1a hash of its name:
|
||||
// (out, name, table, flag, context) -> out, the fifth passed on the stack.
|
||||
// The skater loader's preset lookups reach it through a Lua binding that
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
namespace dingosdk::world_layer_scan {
|
||||
namespace fs = std::filesystem;
|
||||
@@ -211,12 +212,23 @@ WorldLayerCatalog from_json(const Json& document) {
|
||||
WorldLayerCatalog scan(const fs::path& gameRoot) {
|
||||
struct Result { std::vector<Reference> references; std::set<std::string> shipped; };
|
||||
std::vector<std::future<Result>> pending;
|
||||
for (const auto& source : maps)
|
||||
pending.push_back(std::async(std::launch::async, [&gameRoot, toc = source.toc] {
|
||||
std::vector<std::jthread> workers;
|
||||
pending.reserve(maps.size());
|
||||
workers.reserve(maps.size());
|
||||
// MSVC std::async schedules onto Windows' existing thread pool. During
|
||||
// pre-entrypoint initialization the launcher gates those existing threads,
|
||||
// so waiting for a pool job deadlocks on a cold/stale layer cache. Dedicated
|
||||
// threads are created after the gate and can finish the scan independently.
|
||||
// jthread also joins all started jobs if creation or a scan throws.
|
||||
for (const auto& source : maps) {
|
||||
std::packaged_task<Result()> task([&gameRoot, toc = source.toc] {
|
||||
Result result;
|
||||
result.references = read_references(gameRoot, toc, result.shipped);
|
||||
return result;
|
||||
}));
|
||||
});
|
||||
pending.push_back(task.get_future());
|
||||
workers.emplace_back(std::move(task));
|
||||
}
|
||||
WorldLayerCatalog catalog;
|
||||
for (std::size_t i = 0; i < maps.size(); ++i) {
|
||||
const auto result = pending[i].get();
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
#include "developer_board.h"
|
||||
#include "Engine/Core/Platform/memory.h"
|
||||
#include "Engine/Core/Log/logging.h"
|
||||
#include "Extension/Multiplayer/Steam/steam_social.h"
|
||||
#include "Extension/Multiplayer/Remote/native_skater.h"
|
||||
#include <Windows.h>
|
||||
#include <cstring>
|
||||
|
||||
namespace dingosdk {
|
||||
namespace {
|
||||
void report_board(std::uintptr_t entity, std::uint64_t steam_id, std::string_view message, logging::Level level) noexcept {
|
||||
struct Report { std::uintptr_t entity{}; std::uint64_t at{}; std::uint32_t message{}; };
|
||||
static std::array<Report, 33> reports{};
|
||||
const auto now = GetTickCount64();
|
||||
const auto hash = developer_board_detail::material_hash(message);
|
||||
auto *entry = &reports.front();
|
||||
for (auto &candidate : reports) {
|
||||
if (candidate.entity == entity) { entry = &candidate; break; }
|
||||
if (candidate.at < entry->at) entry = &candidate;
|
||||
}
|
||||
if (entry->entity == entity && (entry->message == hash || now - entry->at < 5000)) return;
|
||||
*entry = {entity, now, hash};
|
||||
logging::log(level, logging::Channel::customization, "Developer board (Steam {}, actor {:#x}): {}", steam_id, entity, message);
|
||||
}
|
||||
bool write_color(std::uintptr_t base, const developer_board_detail::Binding &binding, const developer_board_detail::Color &color) noexcept {
|
||||
__try {
|
||||
if (!binding.node) {
|
||||
const developer_board_detail::NativeColor value{color, 0, binding.type};
|
||||
// Let the engine allocate/copy/hash a missing typed parameter. Priority
|
||||
// zero allows later native appearance presets to replace this override.
|
||||
return reinterpret_cast<std::uint64_t (*)(std::uintptr_t, const developer_board_detail::ParameterKey *,
|
||||
const developer_board_detail::NativeColor *, std::uint8_t)>(
|
||||
base + addr::native_cosmetics::set_shader_parameter)(binding.material, &binding.key, &value, 0) != 0;
|
||||
}
|
||||
std::memcpy(reinterpret_cast<void *>(binding.node + 0x10), color.data(), sizeof(color));
|
||||
*reinterpret_cast<std::uint8_t *>(binding.material + 0x2f6) = 1;
|
||||
return true;
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) { return false; }
|
||||
}
|
||||
bool publish_materials(std::uintptr_t base, std::uintptr_t item) noexcept {
|
||||
__try {
|
||||
reinterpret_cast<void (*)(std::uintptr_t, int)>(base + addr::native_cosmetics::publish_materials)(item, 0);
|
||||
return true;
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) { return false; }
|
||||
}
|
||||
} // namespace
|
||||
void update_developer_board(std::uintptr_t base, std::uintptr_t board, std::uint64_t steam_id,
|
||||
std::uint64_t generation, DeveloperBoardState &state) noexcept {
|
||||
const bool developer = multiplayer::reskate_developer(steam_id);
|
||||
if (!board) { state = {}; return; }
|
||||
if (!developer && !state.count) return;
|
||||
try {
|
||||
auto read = [](std::uintptr_t address, void *out, std::size_t size) { return memory::peek_bytes(address, out, size); };
|
||||
std::array<unsigned char, addr::native_cosmetics::publish_materials_prefix.size()> publisher{};
|
||||
std::array<unsigned char, addr::native_cosmetics::set_shader_parameter_prefix.size()> setter{};
|
||||
if (!read(base + addr::native_cosmetics::publish_materials, publisher.data(), publisher.size()) ||
|
||||
publisher != addr::native_cosmetics::publish_materials_prefix ||
|
||||
!read(base + addr::native_cosmetics::set_shader_parameter, setter.data(), setter.size()) ||
|
||||
setter != addr::native_cosmetics::set_shader_parameter_prefix) {
|
||||
report_board(board, steam_id, "material functions differ from the supported build", logging::Level::warning);
|
||||
return;
|
||||
}
|
||||
const auto live = developer_board_detail::materials(read, base, board);
|
||||
bool published = true;
|
||||
auto write = [base](const auto &binding, const auto &color) { return write_color(base, binding, color); };
|
||||
auto publish = [base, &published](std::uintptr_t item) { if (!publish_materials(base, item)) published = false; };
|
||||
developer_board_detail::animate(state, live, board, generation, developer, GetTickCount64(), write, publish);
|
||||
const auto wanted = static_cast<std::size_t>(std::count_if(live.bindings.begin(), live.bindings.begin() + live.count,
|
||||
[](const auto &binding) { return binding.eligible; }));
|
||||
if (!published) report_board(board, steam_id, "native material publication failed", logging::Level::warning);
|
||||
else if (developer && !live.pending && state.count < wanted)
|
||||
report_board(board, steam_id, "waiting for the remaining board color overrides", logging::Level::warning);
|
||||
else if (developer && state.count)
|
||||
report_board(board, steam_id, "RGB active on the owned pink board cosmetics", logging::Level::info);
|
||||
} catch (const std::exception &error) {
|
||||
report_board(board, steam_id, error.what(), logging::Level::warning);
|
||||
} catch (...) {}
|
||||
}
|
||||
void tick_local_developer_board(std::uintptr_t base, std::uintptr_t client, bool ready) noexcept {
|
||||
static DeveloperBoardState state;
|
||||
if (!ready) return;
|
||||
try {
|
||||
const auto social = multiplayer::steam_social_snapshot();
|
||||
const auto id = social ? social->local.id : 0;
|
||||
if (!multiplayer::reskate_developer(id) && !state.count) return;
|
||||
const auto local = multiplayer::capture_local(base, client, false);
|
||||
if (!local.ready) return;
|
||||
auto read = [](std::uintptr_t address, void *out, std::size_t size) { return memory::peek_bytes(address, out, size); };
|
||||
// Cosmetic changes can replace the board while retaining the skater.
|
||||
// Re-resolve the actual local association, rather than capture's cached board.
|
||||
const auto board = multiplayer::read_native_board(read, base, local.entity);
|
||||
update_developer_board(base, board.entity, id, local.context, state);
|
||||
} catch (...) {}
|
||||
}
|
||||
} // namespace dingosdk
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include "developer_board_material.h"
|
||||
|
||||
namespace dingosdk {
|
||||
void update_developer_board(std::uintptr_t base, std::uintptr_t board, std::uint64_t steam_id,
|
||||
std::uint64_t generation, DeveloperBoardState &state) noexcept;
|
||||
void tick_local_developer_board(std::uintptr_t base, std::uintptr_t client, bool ready) noexcept;
|
||||
} // namespace dingosdk
|
||||
@@ -0,0 +1,213 @@
|
||||
#pragma once
|
||||
#include "developer_hoodie_material.h"
|
||||
#include <cstddef>
|
||||
|
||||
namespace dingosdk {
|
||||
namespace developer_board_detail {
|
||||
using developer_hoodie_detail::Color;
|
||||
using developer_hoodie_detail::material_hash;
|
||||
using developer_hoodie_detail::rainbow;
|
||||
inline constexpr std::uint64_t graphic_color = 0x873f48af36e50d59ULL;
|
||||
inline constexpr std::uint64_t base_color = 0x8c46cd354b18a9e2ULL;
|
||||
inline constexpr Color shader_default{.5f, .5f, .5f};
|
||||
inline constexpr std::array<std::string_view, 3> slots{
|
||||
"Characters/Customization/Board/Slots/Skateboard_Deck_Slot",
|
||||
"Characters/Customization/Board/Slots/Skateboard_Trucks_Slot",
|
||||
"Characters/Customization/Board/Slots/Skateboard_Wheels_Slot"};
|
||||
struct Target {
|
||||
std::string_view asset, material;
|
||||
std::uint32_t recipe_slot;
|
||||
std::size_t controller;
|
||||
std::uint64_t key;
|
||||
};
|
||||
// The board loader registers Deck_Mat / DeckTop_Mat for the compiled sections
|
||||
// Skateboard_base_mat / mat_skateboard_top. Use the live named map identities.
|
||||
// Recipe identities, assigned board controllers and serialized shader keys from
|
||||
// the four pink cosmetics. The deck's wood follows its Solid Pink graphic.
|
||||
inline constexpr std::array<Target, 5> targets{{
|
||||
{"Own_DeckGraphic_Gen_Popsicle_00021", "Deck_Mat", 0x66071628, 0, graphic_color},
|
||||
{"Own_DeckGraphic_Gen_Popsicle_00021", "Deck_Mat", 0x66071628, 0, base_color},
|
||||
{"Own_DeckGripColor_Gen_Popsicle_00006", "DeckTop_Mat", 0x755d5ee0, 0, graphic_color},
|
||||
{"Own_Truck_Gen_Default_00002", "Truck_Mat", 0x0add98be, 1, base_color},
|
||||
{"Own_WheelColor_Gen_Classic_00005", "Wheel_mat", 0xc83bb94e, 2, base_color},
|
||||
}};
|
||||
// Native ParamDbKey and inline NativeValue layout used by addEsVector's setter.
|
||||
// The key's first word is the final two serialized GUID bytes. Its type index
|
||||
// comes from the live ColorRgb descriptor, not a serialized Vec3 or boxed value.
|
||||
struct ParameterKey {
|
||||
std::uint16_t identity{}, size{}, type{}, count{};
|
||||
std::uint64_t hash{};
|
||||
};
|
||||
struct NativeColor {
|
||||
Color color{};
|
||||
std::uint32_t padding{};
|
||||
std::uintptr_t type{};
|
||||
std::uint32_t flags{3}, reserved{};
|
||||
};
|
||||
static_assert(sizeof(ParameterKey) == 16 && offsetof(ParameterKey, hash) == 8);
|
||||
static_assert(sizeof(NativeColor) == 32 && offsetof(NativeColor, type) == 16 && offsetof(NativeColor, flags) == 24);
|
||||
inline ParameterKey parameter_key(std::uint64_t hash, std::uint16_t type) noexcept {
|
||||
return {static_cast<std::uint16_t>(hash == graphic_color ? 0xd442 : 0x8c54), sizeof(Color), type, 1, hash};
|
||||
}
|
||||
struct Binding {
|
||||
std::uintptr_t item{}, material{}, node{}, type{};
|
||||
ParameterKey key{};
|
||||
Color color{};
|
||||
bool eligible{};
|
||||
};
|
||||
struct SavedColor {
|
||||
Binding binding;
|
||||
Color original{}, last{};
|
||||
};
|
||||
inline constexpr std::size_t max_bindings = targets.size() * 4;
|
||||
struct Materials {
|
||||
std::uintptr_t component{}, appearance{};
|
||||
std::array<Binding, max_bindings> bindings{};
|
||||
std::size_t count{};
|
||||
bool pending{};
|
||||
};
|
||||
template<class Memory>
|
||||
std::uintptr_t parameter(Memory &m, std::uintptr_t material, std::uint64_t key) {
|
||||
const auto buckets = m.ptr(material, 0x268);
|
||||
const auto size = m.template get<std::uint32_t>(material, 0x270);
|
||||
const auto total = m.template get<std::uint32_t>(material, 0x274);
|
||||
// The native setter also uses these buckets when inserting a missing color.
|
||||
m.check(buckets && size && size <= 1024 && total <= 512, "Board parameter map exceeds bounds.");
|
||||
const auto sentinel = m.ptr(buckets, size * 8ULL);
|
||||
auto node = m.ptr(buckets, (key % size) * 8ULL);
|
||||
std::uint32_t visited{};
|
||||
for (; node && node != sentinel && visited < total; ++visited, node = m.ptr(node, 0x40))
|
||||
if (m.template get<std::uint64_t>(node, 8) == key) return node;
|
||||
m.check(!node || node == sentinel, "Board parameter chain exceeds bounds.");
|
||||
return 0;
|
||||
}
|
||||
template<class Read> Materials materials(Read &read, std::uintptr_t base, std::uintptr_t entity) {
|
||||
using namespace multiplayer;
|
||||
CosmeticMemory<Read &> m{read, base};
|
||||
Materials out;
|
||||
m.check(m.ptr(entity) == base + addr::engine::board_entity_vtable, "RGB board actor type differs.");
|
||||
out.component = m.component(entity);
|
||||
out.pending = m.template get<std::uint8_t>(out.component, 0x10a) || m.template get<std::uint8_t>(out.component, 0x10d);
|
||||
const auto recipe = m.ptr(out.component, 0x158);
|
||||
const auto count = m.count(recipe, sizeof(NativeCosmeticItem), max_cosmetic_slots);
|
||||
std::array<bool, targets.size()> eligible{};
|
||||
for (std::size_t i = 0; i < count; ++i) {
|
||||
const auto item = m.template get<NativeCosmeticItem>(recipe, i * sizeof(NativeCosmeticItem));
|
||||
for (std::size_t t = 0; t < targets.size(); ++t) {
|
||||
const auto &target = targets[t];
|
||||
if (item.slot != target.recipe_slot || item.hash != cosmetic_asset_hash(target.asset)) continue;
|
||||
const auto name = item.asset ? m.text(reinterpret_cast<std::uintptr_t>(item.asset)) : std::string{};
|
||||
eligible[t] = name.empty() || name == target.asset;
|
||||
}
|
||||
}
|
||||
const auto type = base + addr::native_cosmetics::color_rgb_type;
|
||||
const auto info = m.ptr(type);
|
||||
m.check(info >= base && info < base + 0x09144000 && m.template get<std::uint32_t>(info) == 0x885eff59 &&
|
||||
m.template get<std::uint16_t>(info, 6) == sizeof(Color), "Board ColorRgb descriptor differs.");
|
||||
const auto type_index = m.template get<std::uint16_t>(type, 8);
|
||||
out.appearance = read_native_component(read, entity, base + addr::engine::skater_appearance_vtable);
|
||||
const auto begin = m.ptr(out.appearance, 0x90), end = m.ptr(out.appearance, 0x98), capacity = m.ptr(out.appearance, 0xa0);
|
||||
m.check(begin <= end && end <= capacity && (end - begin) % 8 == 0 && (capacity - begin) % 8 == 0 &&
|
||||
(capacity - begin) / 8 <= 64 && (begin || !capacity), "Board controller array exceeds bounds.");
|
||||
std::array<bool, slots.size()> seen{};
|
||||
for (auto entry = begin; entry < end; entry += 8) {
|
||||
const auto item = m.ptr(entry);
|
||||
if (!item) continue;
|
||||
m.check(m.ptr(item, 0x48) == out.appearance + 0x40, "Board controller ownership differs.");
|
||||
const auto name = m.text(m.ptr(item, 0x30));
|
||||
const auto assigned = std::find(slots.begin(), slots.end(), name);
|
||||
if (assigned == slots.end()) continue;
|
||||
const auto slot = static_cast<std::size_t>(assigned - slots.begin());
|
||||
m.check(!seen[slot], "Board has duplicate slot controllers.");
|
||||
seen[slot] = true;
|
||||
if (!(m.template get<std::uint32_t>(item, 0xb0) & 2)) { out.pending = true; continue; }
|
||||
const auto buckets = m.ptr(item, 0x1f0);
|
||||
const auto size = m.template get<std::uint32_t>(item, 0x1f8), total = m.template get<std::uint32_t>(item, 0x1fc);
|
||||
if (!total) { out.pending = true; continue; }
|
||||
m.check(buckets && size && size <= 256 && total <= 128, "Board material map exceeds bounds.");
|
||||
const auto sentinel = m.ptr(buckets, size * 8ULL);
|
||||
for (std::size_t t = 0; t < targets.size(); ++t) {
|
||||
const auto &target = targets[t];
|
||||
if (target.controller != slot) continue;
|
||||
const auto hash = material_hash(target.material);
|
||||
auto node = m.ptr(buckets, (hash % size) * 8ULL);
|
||||
std::uint32_t visited{};
|
||||
for (; node && node != sentinel && visited < total; ++visited, node = m.ptr(node, 0x28))
|
||||
if (m.template get<std::uint32_t>(node) == hash) break;
|
||||
m.check(!node || node == sentinel || visited < total, "Board material chain exceeds bounds.");
|
||||
if (!node || node == sentinel) continue;
|
||||
std::array<std::uintptr_t, 4> variants{};
|
||||
for (std::size_t v = 0; v < variants.size(); ++v) {
|
||||
const auto material = m.ptr(node, 8 + v * 8);
|
||||
if (!material || std::find(variants.begin(), variants.begin() + v, material) != variants.begin() + v) continue;
|
||||
variants[v] = material;
|
||||
const auto p = parameter(m, material, target.key);
|
||||
const auto key = parameter_key(target.key, type_index);
|
||||
Color color = shader_default;
|
||||
if (p) {
|
||||
const auto native_key = m.template get<ParameterKey>(p);
|
||||
m.check(native_key.identity == key.identity && native_key.size == key.size && native_key.type == key.type &&
|
||||
native_key.count == key.count && m.ptr(p, 0x20) == type &&
|
||||
(m.template get<std::uint32_t>(p, 0x28) & 1), "Board color parameter type differs.");
|
||||
color = m.template get<Color>(p, 0x10);
|
||||
m.check(std::all_of(color.begin(), color.end(), [](float c) { return std::isfinite(c); }), "Board color is invalid.");
|
||||
}
|
||||
m.check(out.count < out.bindings.size(), "Board colors exceed bounds.");
|
||||
out.bindings[out.count++] = {item, material, p, type, key, color, eligible[t]};
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
inline bool same_binding(const Binding &live, const Binding &saved) noexcept {
|
||||
// A successful native insertion acquires its node on the next fresh walk.
|
||||
return live.item == saved.item && live.material == saved.material && live.type == saved.type && live.key.hash == saved.key.hash &&
|
||||
(saved.node ? live.node == saved.node : live.node != 0);
|
||||
}
|
||||
} // namespace developer_board_detail
|
||||
struct DeveloperBoardState {
|
||||
std::uintptr_t entity{}, component{}, appearance{};
|
||||
std::uint64_t generation{};
|
||||
std::array<developer_board_detail::SavedColor, developer_board_detail::max_bindings> colors{};
|
||||
std::size_t count{};
|
||||
};
|
||||
namespace developer_board_detail {
|
||||
// Re-discover ownership before every write. Changing the recipe restores only
|
||||
// colors that still equal our last write; native preset updates take precedence.
|
||||
// Missing keys inherit .5 from these compiled board shaders. Keep inserted
|
||||
// overrides at native preset priority and restore that default on deactivation.
|
||||
// Never clear a NativeValue: the renderer still copies the key's full payload.
|
||||
template<class Write, class Publish>
|
||||
void animate(DeveloperBoardState &state, const Materials &live, std::uintptr_t entity, std::uint64_t generation,
|
||||
bool developer, std::uint64_t milliseconds, Write &write, Publish &publish) {
|
||||
if (state.entity != entity || state.generation != generation || state.component != live.component || state.appearance != live.appearance)
|
||||
state = {};
|
||||
if (live.pending) return;
|
||||
DeveloperBoardState next;
|
||||
next.entity = entity; next.generation = generation; next.component = live.component; next.appearance = live.appearance;
|
||||
const auto color = rainbow(milliseconds);
|
||||
std::array<std::uintptr_t, max_bindings> changed_items{};
|
||||
std::size_t changed{};
|
||||
for (std::size_t i = 0; i < live.count; ++i) {
|
||||
const auto &binding = live.bindings[i];
|
||||
const SavedColor *saved{};
|
||||
for (std::size_t j = 0; j < state.count; ++j)
|
||||
if (same_binding(binding, state.colors[j].binding)) { saved = &state.colors[j]; break; }
|
||||
bool wrote{};
|
||||
if (developer && binding.eligible) {
|
||||
const auto original = saved && binding.color == saved->last ? saved->original : binding.color;
|
||||
if ((binding.node && binding.color == color) || (wrote = write(binding, color)))
|
||||
next.colors[next.count++] = {binding, original, color};
|
||||
else if (saved) next.colors[next.count++] = *saved;
|
||||
} else if (saved && binding.color == saved->last && binding.color != saved->original) {
|
||||
wrote = write(binding, saved->original);
|
||||
if (!wrote) next.colors[next.count++] = *saved;
|
||||
}
|
||||
if (wrote && std::find(changed_items.begin(), changed_items.begin() + changed, binding.item) == changed_items.begin() + changed)
|
||||
changed_items[changed++] = binding.item;
|
||||
}
|
||||
state = next;
|
||||
for (std::size_t i = 0; i < changed; ++i) publish(changed_items[i]);
|
||||
}
|
||||
} // namespace developer_board_detail
|
||||
} // namespace dingosdk
|
||||
@@ -565,6 +565,9 @@ NativeAnimationStats remote_animation_stats() noexcept {
|
||||
std::uintptr_t remote_skater_entity() noexcept {
|
||||
return watched().entity[peer_slot].load(std::memory_order_acquire);
|
||||
}
|
||||
std::uintptr_t remote_board_entity() noexcept {
|
||||
return watched().board[peer_slot].load(std::memory_order_acquire);
|
||||
}
|
||||
std::uint64_t remote_skater_generation() noexcept {
|
||||
return remote().generation.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ std::uint64_t remote_pose_updates() noexcept;
|
||||
std::uint64_t remote_board_pose_updates() noexcept;
|
||||
NativeAnimationStats remote_animation_stats() noexcept;
|
||||
std::uintptr_t remote_skater_entity() noexcept;
|
||||
std::uintptr_t remote_board_entity() noexcept;
|
||||
// Changes whenever the current slot's actor is created, removed or destroyed.
|
||||
std::uint64_t remote_skater_generation() noexcept;
|
||||
// Client thread. Installs the shared animation/destruction hooks without
|
||||
|
||||
@@ -445,6 +445,7 @@ void render(Session &s, std::uintptr_t client, const NativeFrame &local, std::ui
|
||||
if (was_visible && local.ready && !p.render_failed && p.far_interval && now < p.next_far_sample &&
|
||||
!(p.far_for_view && !out_of_view(p, view))) {
|
||||
update_developer_hoodie(s.base, remote_skater_entity(), p.member.id, remote_skater_generation(), p.developer_hoodie);
|
||||
update_developer_board(s.base, remote_board_entity(), p.member.id, remote_skater_generation(), p.developer_board);
|
||||
present_audio(p);
|
||||
update_party_position(&p.render_pose);
|
||||
if (labels) label(p);
|
||||
@@ -521,6 +522,8 @@ void render(Session &s, std::uintptr_t client, const NativeFrame &local, std::ui
|
||||
}
|
||||
update_developer_hoodie(s.base, p.visible ? remote_skater_entity() : 0, p.member.id,
|
||||
remote_skater_generation(), p.developer_hoodie);
|
||||
update_developer_board(s.base, p.visible ? remote_board_entity() : 0, p.member.id,
|
||||
remote_skater_generation(), p.developer_board);
|
||||
if (!p.visible) {
|
||||
stop_remote_audio();
|
||||
p.presented_audio.reset();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "Engine/Game/Multiplayer/chat_rate.h"
|
||||
#include "session.h"
|
||||
#include "Extension/Customization/developer_hoodie.h"
|
||||
#include "Extension/Customization/developer_board.h"
|
||||
#include "Extension/Multiplayer/Remote/native_skater.h"
|
||||
#include "Extension/Multiplayer/Voice/voice_chat.h"
|
||||
#include "Extension/Multiplayer/Steam/steam_transport.h"
|
||||
@@ -98,6 +99,7 @@ struct Peer {
|
||||
// (0 after a spawn) to spread native recipe applies over frames.
|
||||
std::uint64_t cosmetic_revision{}, applied_cosmetics{};
|
||||
DeveloperHoodieState developer_hoodie;
|
||||
DeveloperBoardState developer_board;
|
||||
AudioBuffer audio;
|
||||
std::uint32_t voice_sequence{};
|
||||
bool received_voice{};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "runtime_internal.h"
|
||||
#include "Extension/Customization/developer_hoodie.h"
|
||||
#include "Extension/Customization/developer_board.h"
|
||||
#include "Extension/Assets/live_mods.h"
|
||||
#include "Engine/Core/Log/logging.h"
|
||||
#include "Engine/Core/Profiling/profiler.h"
|
||||
@@ -985,6 +986,7 @@ void tick(std::uintptr_t client, std::uintptr_t update) {
|
||||
dingosdk::multiplayer::tick(r.base,client,multiplayer_ready,r.multiplayer_map,load_multiplayer_map);
|
||||
}
|
||||
dingosdk::tick_local_developer_hoodie(r.base, client, multiplayer_ready);
|
||||
dingosdk::tick_local_developer_board(r.base, client, multiplayer_ready);
|
||||
// The session spawns and places skaters and can teleport: check the camera again.
|
||||
tick_state.camera_issue.reset();
|
||||
// The spectate camera follows a moving skater, so it runs every client tick, after the
|
||||
|
||||
@@ -93,6 +93,7 @@ add_library(dingosdk_runtime SHARED
|
||||
Extension/World/loading_screen.cpp
|
||||
Extension/Customization/preset_lookup_guard.cpp
|
||||
Extension/Customization/developer_hoodie.cpp
|
||||
Extension/Customization/developer_board.cpp
|
||||
Extension/Skater/skater_model.cpp
|
||||
Extension/Skater/skater_observer.cpp
|
||||
Extension/Skater/client_source_spawn.cpp
|
||||
|
||||
Reference in new issue
Block a user