mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-20 19:46:34 +02:00
253 lines
11 KiB
C++
253 lines
11 KiB
C++
#include "park_editor_runtime.h"
|
|
#include "park_editor_internal.h"
|
|
#include "Extension/Objects/local_placements_runtime.h"
|
|
|
|
namespace dingosdk::profile_runtime::park_editor_detail {
|
|
void update_preview_target(Transaction &tx) {
|
|
if (tx.steps.empty())
|
|
return;
|
|
auto &preview = *tx.preview;
|
|
auto &step = tx.steps.front();
|
|
if (step.kind == Kind::create) {
|
|
if (!tx.sent) {
|
|
if (preview.cancelled)
|
|
tx.steps.clear();
|
|
else
|
|
step.object = preview.target;
|
|
}
|
|
return; // A sent create must acknowledge its original pose first.
|
|
}
|
|
if (preview.cancelled && preview.placing) {
|
|
if (step.kind != Kind::erase) {
|
|
step.kind = Kind::erase;
|
|
step.acknowledged = false;
|
|
tx.sent = false;
|
|
tx.waiting_since = GetTickCount64();
|
|
}
|
|
} else {
|
|
for (auto &move : tx.steps) {
|
|
const auto *target = find_object(tx.change.after, move.object.id);
|
|
if (target && !placement_same_pose(move.object, *target)) {
|
|
move.object = *target;
|
|
move.acknowledged = false;
|
|
move.live_dirty = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
void cancel_preview(Transaction &tx) {
|
|
auto &preview = *tx.preview;
|
|
preview.ending = preview.cancelled = true;
|
|
tx.change.after = tx.change.before;
|
|
if (!preview.placing)
|
|
preview.target = *find_object(tx.change.before, preview.target.id);
|
|
update_preview_target(tx);
|
|
editor_state().status = "Restoring the park after cancelling the preview...";
|
|
}
|
|
void expire_preview() {
|
|
auto &e = editor_state();
|
|
if (e.transaction && e.transaction->preview && !e.transaction->preview->ending &&
|
|
GetTickCount64() > e.transaction->preview->last_sample + 2000)
|
|
cancel_preview(*e.transaction);
|
|
}
|
|
} // namespace dingosdk::profile_runtime::park_editor_detail
|
|
namespace dingosdk {
|
|
using namespace profile_runtime;
|
|
using namespace profile_runtime::park_editor_detail;
|
|
bool queue_local_park_paste(const EditorPasteRequest &request) {
|
|
std::lock_guard lock(local_runtime().native_mutex);
|
|
auto &e = editor_state();
|
|
auto &r = placements_runtime();
|
|
try {
|
|
if (!local_runtime().active || !placement_session_ready() || !idle() ||
|
|
request.generation != e.generation || request.revision != e.revision || request.objects.empty() ||
|
|
request.objects.size() > 1024 || !e.assets)
|
|
return false;
|
|
auto target = r.document.maps[r.map];
|
|
if (target.size() + request.objects.size() > 1024 || r.next_id > UINT64_MAX - request.objects.size())
|
|
return false;
|
|
auto next_id = r.next_id;
|
|
for (const auto © : request.objects) {
|
|
if (std::none_of(e.assets->begin(), e.assets->end(),
|
|
[&](const auto &asset) { return asset.key == copy.item; }))
|
|
return false;
|
|
profile::PlacedObject object{next_id++, copy.item, copy.position, copy.rotation, copy.scale};
|
|
if (!profile::valid_placed_object(object))
|
|
return false;
|
|
target.push_back(std::move(object));
|
|
}
|
|
// One validated layout change gives the entire pasted group one undo step.
|
|
if (!begin(std::move(target), 0))
|
|
return false;
|
|
r.next_id = next_id;
|
|
return true;
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
bool queue_local_park_preview(const EditorPreviewRequest &request) {
|
|
std::lock_guard lock(local_runtime().native_mutex);
|
|
auto &e = editor_state();
|
|
auto &r = placements_runtime();
|
|
try {
|
|
if (!lobby_object_placement_allowed() && request.action != EditorPreviewAction::cancel) return false;
|
|
if (!local_runtime().active || !placement_session_ready() || request.generation != e.generation ||
|
|
!request.token || !request.sequence)
|
|
return false;
|
|
const bool placing = request.action == EditorPreviewAction::begin_place;
|
|
const bool starting = placing || request.action == EditorPreviewAction::begin_move;
|
|
if (starting) {
|
|
if (!idle() || request.revision != e.revision || request.token <= e.last_preview_token)
|
|
return false;
|
|
const auto &layout = r.document.maps[r.map];
|
|
profile::PlacedObject object;
|
|
std::uint64_t entity{};
|
|
if (placing) {
|
|
if (layout.size() >= 1024 || r.next_id == UINT64_MAX || !e.assets ||
|
|
std::none_of(e.assets->begin(), e.assets->end(),
|
|
[&](const auto &asset) { return asset.key == request.item; }))
|
|
return false;
|
|
object.id = r.next_id;
|
|
object.item = request.item;
|
|
} else {
|
|
const auto *saved = find_object(layout, request.object);
|
|
const auto row = std::find_if(r.rows.begin(), r.rows.end(), [&](const auto &candidate) {
|
|
return candidate.saved_id == request.object && candidate.spawned;
|
|
});
|
|
if (!saved || row == r.rows.end())
|
|
return false;
|
|
object = *saved;
|
|
entity = row->entity;
|
|
}
|
|
object.position = request.position;
|
|
object.rotation = request.rotation;
|
|
object.scale = request.scale;
|
|
if (!profile::valid_placed_object(object))
|
|
return false;
|
|
Transaction tx;
|
|
tx.change = {layout, layout};
|
|
if (placing) {
|
|
tx.change.after.push_back(object);
|
|
++r.next_id;
|
|
} else {
|
|
for (auto &row : tx.change.after)
|
|
if (row.id == object.id)
|
|
row = object;
|
|
}
|
|
tx.steps.push_back({placing ? Kind::create : Kind::move, object, entity});
|
|
if (!placing && !request.transforms.empty()) {
|
|
if (request.transforms.size() > 1024)
|
|
return false;
|
|
tx.steps.clear();
|
|
tx.change.after = layout;
|
|
std::set<std::uint64_t> seen;
|
|
for (const auto &transform : request.transforms) {
|
|
const auto *saved = find_object(layout, transform.id);
|
|
const auto row = std::find_if(r.rows.begin(), r.rows.end(), [&](const auto &candidate) {
|
|
return candidate.saved_id == transform.id && candidate.spawned;
|
|
});
|
|
if (!saved || row == r.rows.end() || !seen.insert(transform.id).second)
|
|
return false;
|
|
auto next = *saved;
|
|
next.position = transform.position;
|
|
next.rotation = transform.rotation;
|
|
next.scale = transform.scale;
|
|
if (!profile::valid_placed_object(next))
|
|
return false;
|
|
for (auto &value : tx.change.after)
|
|
if (value.id == next.id)
|
|
value = next;
|
|
tx.steps.push_back({Kind::move, next, row->entity});
|
|
}
|
|
if (!seen.contains(request.object))
|
|
return false;
|
|
}
|
|
tx.waiting_since = GetTickCount64();
|
|
tx.preview =
|
|
Preview{request.token, request.revision, request.sequence, GetTickCount64(), object, placing};
|
|
for (const auto &step : tx.steps)
|
|
tx.preview->selected.push_back(step.object.id);
|
|
e.transaction = std::move(tx);
|
|
e.last_preview_token = request.token;
|
|
e.status = placing ? "Placing object. Release or click to keep; Escape cancels."
|
|
: "Moving object. Release to keep; Escape restores.";
|
|
++e.revision;
|
|
return true;
|
|
}
|
|
if (!e.transaction || !e.transaction->preview)
|
|
return false;
|
|
auto &tx = *e.transaction;
|
|
auto &preview = *tx.preview;
|
|
if (preview.token != request.token || preview.revision != request.revision || preview.ending ||
|
|
request.sequence <= preview.sequence)
|
|
return false;
|
|
if (request.action == EditorPreviewAction::cancel)
|
|
cancel_preview(tx);
|
|
else if (request.action == EditorPreviewAction::update ||
|
|
request.action == EditorPreviewAction::commit) {
|
|
auto after = tx.change.after;
|
|
auto target = preview.target;
|
|
if (!preview.placing && !request.transforms.empty()) {
|
|
if (request.transforms.size() != preview.selected.size())
|
|
return false;
|
|
std::set<std::uint64_t> seen;
|
|
for (const auto &transform : request.transforms) {
|
|
if (std::find(preview.selected.begin(), preview.selected.end(), transform.id) ==
|
|
preview.selected.end() ||
|
|
!seen.insert(transform.id).second)
|
|
return false;
|
|
for (auto &object : after) {
|
|
if (object.id != transform.id)
|
|
continue;
|
|
object.position = transform.position;
|
|
object.rotation = transform.rotation;
|
|
object.scale = transform.scale;
|
|
if (!profile::valid_placed_object(object))
|
|
return false;
|
|
}
|
|
}
|
|
target = *find_object(after, target.id);
|
|
} else {
|
|
if (preview.selected.size() != 1)
|
|
return false;
|
|
target.position = request.position;
|
|
target.rotation = request.rotation;
|
|
target.scale = request.scale;
|
|
if (!profile::valid_placed_object(target))
|
|
return false;
|
|
for (auto &object : after)
|
|
if (object.id == target.id)
|
|
object = target;
|
|
}
|
|
preview.target = target;
|
|
preview.ending = request.action == EditorPreviewAction::commit;
|
|
tx.change.after = std::move(after);
|
|
update_preview_target(tx);
|
|
} else
|
|
return false;
|
|
preview.sequence = request.sequence;
|
|
preview.last_sample = GetTickCount64();
|
|
return true;
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
bool queue_local_park_selection(const EditorSelectionRequest &request) {
|
|
std::lock_guard lock(local_runtime().native_mutex);
|
|
auto &e = editor_state();
|
|
if ((!lobby_object_placement_allowed() && !request.objects.empty()) || !local_runtime().active || request.generation != e.generation || request.objects.size() > 1024)
|
|
return false;
|
|
e.selection = request.objects;
|
|
e.selection_time = GetTickCount64();
|
|
return true;
|
|
}
|
|
bool queue_local_park_surface(const EditorSurfaceRequest &request) {
|
|
std::lock_guard lock(local_runtime().native_mutex);
|
|
auto &e = editor_state();
|
|
if (!lobby_object_placement_allowed() || !local_runtime().active || request.generation != e.generation || !request.id)
|
|
return false;
|
|
e.probe = request; // Latest cursor sample replaces older, unprocessed samples.
|
|
return true;
|
|
}
|
|
} // namespace dingosdk
|