mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-08-05 00:59:14 -04:00
initial stage service ported from encounter's original impl
This commit is contained in:
@@ -1504,6 +1504,8 @@ set(DUSK_FILES
|
||||
src/dusk/mods/svc/window.hpp
|
||||
src/dusk/mods/svc/save.cpp
|
||||
src/dusk/mods/svc/save.hpp
|
||||
src/dusk/mods/svc/stage.cpp
|
||||
src/dusk/mods/svc/stage.hpp
|
||||
src/dusk/mouse.cpp
|
||||
src/dusk/scope_guard.hpp
|
||||
src/dusk/settings.cpp
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <mods/api.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <mods/service.hpp>
|
||||
#endif
|
||||
|
||||
#define STAGE_SERVICE_ID "dev.twilitrealm.dusklight.stage"
|
||||
#define STAGE_SERVICE_MAJOR 1u
|
||||
#define STAGE_SERVICE_MINOR 0u
|
||||
|
||||
typedef uint64_t StageActorHandle;
|
||||
|
||||
typedef struct StageService {
|
||||
ServiceHeader header;
|
||||
|
||||
/* Replace actor record that matches record_crc with given record */
|
||||
ModResult (*patch_actor)(ModContext* ctx, const char* stage, uint8_t room, int8_t layer,
|
||||
uint32_t record_crc, const void* record, size_t record_size, StageActorHandle* out_handle);
|
||||
|
||||
/* Remove actor record matching record_crc */
|
||||
ModResult (*delete_actor)(ModContext* ctx, const char* stage, uint8_t room, int8_t layer,
|
||||
uint32_t record_crc, StageActorHandle* out_handle);
|
||||
|
||||
/* Add new actor record */
|
||||
ModResult (*add_actor)(ModContext* ctx, const char* stage, uint8_t room, int8_t layer,
|
||||
const void* record, size_t record_size, StageActorHandle* out_handle);
|
||||
|
||||
/* Remove an edit previously registered by the calling mod */
|
||||
ModResult (*remove_actor_edit)(ModContext* ctx, StageActorHandle handle);
|
||||
} StageService;
|
||||
|
||||
MOD_DECLARE_SERVICE(
|
||||
StageService, svc_stage, STAGE_SERVICE_ID, STAGE_SERVICE_MAJOR, STAGE_SERVICE_MINOR);
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "dusk/logging.h"
|
||||
#include "helpers/string.hpp"
|
||||
#if TARGET_PC
|
||||
#include "dusk/mods/svc/stage.hpp"
|
||||
#include <format>
|
||||
#include <fmt/ranges.h>
|
||||
#endif
|
||||
@@ -1593,6 +1594,13 @@ u8 dStage_roomControl_c::mNoArcBank;
|
||||
#endif
|
||||
|
||||
static void dStage_actorCreate(stage_actor_data_class* i_actorData, fopAcM_prm_class* i_actorPrm) {
|
||||
#if TARGET_PC
|
||||
if (!dusk::mods::svc::stage_apply_actor_edits(i_actorData, i_actorPrm, i_actorPrm->room_no)) {
|
||||
DuskLog.error("Failed to apply stage actor edits!");
|
||||
JKRFree(i_actorPrm);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
dStage_objectNameInf* actorInf = dStage_searchName(i_actorData->name);
|
||||
|
||||
if (actorInf == NULL) {
|
||||
@@ -2511,6 +2519,29 @@ static void dKankyo_create() {
|
||||
fopKyM_fastCreate(fpcNm_ENVSE_e, 0, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
static void duskStageSvc_newActorCreate(dStage_dt_c* i_stage) {
|
||||
dusk::mods::svc::stage_create_new_actors(i_stage->getRoomNo(),
|
||||
[](void* user, const void* record, size_t size) {
|
||||
auto* stage = static_cast<dStage_dt_c*>(user);
|
||||
stage_tgsc_data_class object{};
|
||||
std::memcpy(&object, record, size);
|
||||
|
||||
fopAcM_prm_class* appen = fopAcM_CreateAppend();
|
||||
if (appen != nullptr) {
|
||||
appen->base = object.base;
|
||||
appen->room_no = static_cast<int>(stage->getRoomNo());
|
||||
// if size is greater than 0x20, must be a TGSC actor
|
||||
if (size > 0x20) {
|
||||
appen->scale = object.scale;
|
||||
}
|
||||
dStage_actorCreate(reinterpret_cast<stage_actor_data_class*>(&object), appen);
|
||||
}
|
||||
},
|
||||
i_stage);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void layerMemoryInfoLoader(void* i_data, dStage_dt_c* i_stage, int param_2) {
|
||||
UNUSED(param_2);
|
||||
static FuncTable l_layerFuncTable[] = {
|
||||
@@ -2707,6 +2738,9 @@ void dStage_dt_c_roomReLoader(void* i_data, dStage_dt_c* i_stage, int param_2) {
|
||||
};
|
||||
|
||||
dStage_dt_c_decode(i_data, i_stage, l_funcTable, ARRAY_SIZEU(l_funcTable));
|
||||
#if TARGET_PC
|
||||
duskStageSvc_newActorCreate(i_stage);
|
||||
#endif
|
||||
layerActorLoader(i_data, i_stage, param_2);
|
||||
}
|
||||
|
||||
|
||||
@@ -212,6 +212,7 @@ void ModLoader::init_services() {
|
||||
&svc::g_windowModule,
|
||||
&svc::g_gfxModule,
|
||||
&svc::g_saveModule,
|
||||
&svc::g_stageModule,
|
||||
})
|
||||
{
|
||||
svc::register_module(*module);
|
||||
|
||||
@@ -74,5 +74,6 @@ extern const ServiceModule g_cameraModule;
|
||||
extern const ServiceModule g_windowModule;
|
||||
extern const ServiceModule g_gfxModule;
|
||||
extern const ServiceModule g_saveModule;
|
||||
extern const ServiceModule g_stageModule;
|
||||
|
||||
} // namespace dusk::mods::svc
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
#include "stage.hpp"
|
||||
|
||||
#include "registry.hpp"
|
||||
|
||||
#include "aurora/lib/logging.hpp"
|
||||
#include "dusk/mods/loader/loader.hpp"
|
||||
#include "mods/svc/stage.h"
|
||||
|
||||
#include "d/d_com_inf_game.h"
|
||||
#include "dusk/utilities.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include <string_view>
|
||||
|
||||
namespace dusk::mods::svc {
|
||||
namespace {
|
||||
|
||||
aurora::Module Log("dusk::mods::stage");
|
||||
|
||||
constexpr size_t kACTREntrySize = 0x20;
|
||||
constexpr size_t kTGSCEntrySize = 0x23;
|
||||
constexpr uint8_t kRoomDefault = 0xFF;
|
||||
constexpr int8_t kLayerDefault = -1;
|
||||
|
||||
enum class EditKind : uint8_t { Patch, Delete, Add };
|
||||
|
||||
struct ActorEditRecord {
|
||||
uint64_t handle = 0;
|
||||
LoadedMod* mod = nullptr;
|
||||
uint64_t seq = 0;
|
||||
uint8_t room = 0;
|
||||
int8_t layer = kLayerDefault;
|
||||
EditKind kind = EditKind::Patch;
|
||||
uint32_t crc = 0; // Patch/Delete
|
||||
std::vector<uint8_t> record; // Patch/Add
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, std::vector<ActorEditRecord>> s_edits;
|
||||
uint64_t s_nextHandle = 1;
|
||||
uint64_t s_nextSeq = 1;
|
||||
std::unordered_set<uint64_t> s_warnedRecords; // (crc | seq-of-stage-hash) collision warnings
|
||||
|
||||
// Position in m_mods (dependency-sorted load order) + 1; later-loaded mods win.
|
||||
int32_t compute_mod_priority(const LoadedMod& mod) {
|
||||
int32_t index = 0;
|
||||
for (const auto& other : ModLoader::instance().mods()) {
|
||||
++index;
|
||||
if (&other == &mod) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
const std::vector<ActorEditRecord>* current_stage_edits() {
|
||||
if (s_edits.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
const char* stageName = dComIfGp_getStartStageName();
|
||||
if (stageName == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto it = s_edits.find(stageName);
|
||||
return it != s_edits.end() ? &it->second : nullptr;
|
||||
}
|
||||
|
||||
bool room_matches(const ActorEditRecord& record, uint8_t roomNo) {
|
||||
return record.room == roomNo || record.room == kRoomDefault;
|
||||
}
|
||||
|
||||
bool layer_matches(const ActorEditRecord& record, int8_t layer) {
|
||||
return record.layer == layer || record.layer == kLayerDefault;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool stage_apply_actor_edits(void* actorData, void* actorPrm, int8_t roomNo) {
|
||||
const auto* edits = current_stage_edits();
|
||||
if (edits == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// PLYR spawns use room -1 to signify using the start room ID
|
||||
uint8_t room = static_cast<uint8_t>(roomNo);
|
||||
if (roomNo == -1) {
|
||||
room = static_cast<uint8_t>(dComIfGp_getStartStageRoomNo());
|
||||
}
|
||||
|
||||
const auto layer = static_cast<int8_t>(dComIfG_play_c::getLayerNo(0));
|
||||
const auto crcActr = utils::crc32(actorData, kACTREntrySize);
|
||||
const auto crcTgsc = utils::crc32(actorData, kTGSCEntrySize);
|
||||
|
||||
const ActorEditRecord* winner = nullptr;
|
||||
int32_t winnerPriority = 0;
|
||||
const LoadedMod* firstOwner = nullptr;
|
||||
for (const auto& record : *edits) {
|
||||
if (record.kind == EditKind::Add || !record.mod->active || !layer_matches(record, layer) ||
|
||||
!room_matches(record, room))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// check record type based on size
|
||||
const bool matches = record.kind == EditKind::Delete
|
||||
? record.crc == crcActr || record.crc == crcTgsc
|
||||
: record.crc == (record.record.size() == kTGSCEntrySize ? crcTgsc : crcActr);
|
||||
if (!matches) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (firstOwner == nullptr) {
|
||||
firstOwner = record.mod;
|
||||
} else if (firstOwner != record.mod && s_warnedRecords.insert(record.crc).second) {
|
||||
Log.warn("actor record {:#010x} edited by multiple mods; the later-loaded one wins",
|
||||
record.crc);
|
||||
}
|
||||
|
||||
const auto priority = compute_mod_priority(*record.mod);
|
||||
if (winner == nullptr || priority > winnerPriority ||
|
||||
(priority == winnerPriority && record.seq > winner->seq))
|
||||
{
|
||||
winner = &record;
|
||||
winnerPriority = priority;
|
||||
}
|
||||
}
|
||||
|
||||
if (winner == nullptr) {
|
||||
return true;
|
||||
}
|
||||
if (winner->kind == EditKind::Delete) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(actorData, winner->record.data(), winner->record.size());
|
||||
std::memcpy(actorPrm, winner->record.data() + 8, winner->record.size() - 8);
|
||||
return true;
|
||||
}
|
||||
|
||||
void stage_create_new_actors(
|
||||
int8_t roomNo, void (*createFn)(void* user, const void* record, size_t size), void* user) {
|
||||
const auto* edits = current_stage_edits();
|
||||
if (edits == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto layer = static_cast<int8_t>(dComIfG_play_c::getLayerNo(0));
|
||||
for (const auto& record : *edits) {
|
||||
if (record.kind == EditKind::Add && record.mod->active &&
|
||||
record.room == static_cast<uint8_t>(roomNo) && layer_matches(record, layer))
|
||||
{
|
||||
createFn(user, record.record.data(), record.record.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
ModResult register_edit(LoadedMod& mod, const char* stage, ActorEditRecord&& record,
|
||||
uint64_t& outHandle) {
|
||||
record.handle = s_nextHandle++;
|
||||
record.mod = &mod;
|
||||
record.seq = s_nextSeq++;
|
||||
outHandle = record.handle;
|
||||
s_edits[stage].push_back(std::move(record));
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ModResult stage_patch_actor(LoadedMod& mod, const char* stage, uint8_t room, int8_t layer,
|
||||
uint32_t crc, const void* record, size_t recordSize, uint64_t& outHandle) {
|
||||
const auto* bytes = static_cast<const uint8_t*>(record);
|
||||
return register_edit(mod, stage,
|
||||
ActorEditRecord{.room = room,
|
||||
.layer = layer,
|
||||
.kind = EditKind::Patch,
|
||||
.crc = crc,
|
||||
.record = {bytes, bytes + recordSize}},
|
||||
outHandle);
|
||||
}
|
||||
|
||||
ModResult stage_delete_actor(LoadedMod& mod, const char* stage, uint8_t room, int8_t layer,
|
||||
uint32_t crc, uint64_t& outHandle) {
|
||||
return register_edit(mod, stage,
|
||||
ActorEditRecord{.room = room, .layer = layer, .kind = EditKind::Delete, .crc = crc},
|
||||
outHandle);
|
||||
}
|
||||
|
||||
ModResult stage_add_actor(LoadedMod& mod, const char* stage, uint8_t room, int8_t layer,
|
||||
const void* record, size_t recordSize, uint64_t& outHandle) {
|
||||
const auto* bytes = static_cast<const uint8_t*>(record);
|
||||
return register_edit(mod, stage,
|
||||
ActorEditRecord{.room = room,
|
||||
.layer = layer,
|
||||
.kind = EditKind::Add,
|
||||
.record = {bytes, bytes + recordSize}},
|
||||
outHandle);
|
||||
}
|
||||
|
||||
ModResult stage_remove_actor_edit(LoadedMod& mod, uint64_t handle) {
|
||||
for (auto it = s_edits.begin(); it != s_edits.end(); ++it) {
|
||||
const auto removed = std::erase_if(it->second, [&](const auto& record) {
|
||||
return record.handle == handle && record.mod == &mod;
|
||||
});
|
||||
if (removed != 0) {
|
||||
if (it->second.empty()) {
|
||||
s_edits.erase(it);
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
}
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
void stage_remove_mod(LoadedMod& mod) {
|
||||
for (auto it = s_edits.begin(); it != s_edits.end();) {
|
||||
std::erase_if(it->second, [&](const auto& record) { return record.mod == &mod; });
|
||||
it = it->second.empty() ? s_edits.erase(it) : std::next(it);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr size_t kMaxStageNameLength = 8;
|
||||
|
||||
bool is_valid_stage_name(const char* stage) {
|
||||
if (stage == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const std::string_view view{stage};
|
||||
return !view.empty() && view.size() <= kMaxStageNameLength;
|
||||
}
|
||||
|
||||
bool is_valid_record_size(size_t size) {
|
||||
return size == kACTREntrySize || size == kTGSCEntrySize;
|
||||
}
|
||||
|
||||
ModResult stage_patch_actor_(ModContext* context, const char* stage, uint8_t room, int8_t layer,
|
||||
uint32_t recordCrc, const void* record, size_t recordSize, StageActorHandle* outHandle) {
|
||||
if (outHandle != nullptr) {
|
||||
*outHandle = 0;
|
||||
}
|
||||
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || !is_valid_stage_name(stage) || record == nullptr ||
|
||||
!is_valid_record_size(recordSize))
|
||||
{
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
uint64_t handle = 0;
|
||||
const auto result =
|
||||
stage_patch_actor(*mod, stage, room, layer, recordCrc, record, recordSize, handle);
|
||||
if (outHandle != nullptr) {
|
||||
*outHandle = handle;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ModResult stage_delete_actor_(ModContext* context, const char* stage, uint8_t room, int8_t layer,
|
||||
uint32_t recordCrc, StageActorHandle* outHandle) {
|
||||
if (outHandle != nullptr) {
|
||||
*outHandle = 0;
|
||||
}
|
||||
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || !is_valid_stage_name(stage)) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
uint64_t handle = 0;
|
||||
const auto result = stage_delete_actor(*mod, stage, room, layer, recordCrc, handle);
|
||||
if (outHandle != nullptr) {
|
||||
*outHandle = handle;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ModResult stage_add_actor_(ModContext* context, const char* stage, uint8_t room, int8_t layer,
|
||||
const void* record, size_t recordSize, StageActorHandle* outHandle) {
|
||||
if (outHandle != nullptr) {
|
||||
*outHandle = 0;
|
||||
}
|
||||
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || !is_valid_stage_name(stage) || room == kRoomDefault ||
|
||||
record == nullptr || !is_valid_record_size(recordSize))
|
||||
{
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
uint64_t handle = 0;
|
||||
const auto result = stage_add_actor(*mod, stage, room, layer, record, recordSize, handle);
|
||||
if (outHandle != nullptr) {
|
||||
*outHandle = handle;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ModResult stage_remove_actor_edit_(ModContext* context, StageActorHandle handle) {
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || handle == 0) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
return stage_remove_actor_edit(*mod, handle);
|
||||
}
|
||||
|
||||
constexpr StageService s_stageService{
|
||||
.header = SERVICE_HEADER(StageService, STAGE_SERVICE_MAJOR, STAGE_SERVICE_MINOR),
|
||||
.patch_actor = stage_patch_actor_,
|
||||
.delete_actor = stage_delete_actor_,
|
||||
.add_actor = stage_add_actor_,
|
||||
.remove_actor_edit = stage_remove_actor_edit_,
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
constinit const ServiceModule g_stageModule{
|
||||
.id = STAGE_SERVICE_ID,
|
||||
.majorVersion = STAGE_SERVICE_MAJOR,
|
||||
.minorVersion = STAGE_SERVICE_MINOR,
|
||||
.service = &s_stageService,
|
||||
.modDetached = stage_remove_mod,
|
||||
};
|
||||
|
||||
} // namespace dusk::mods::svc
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace dusk::mods::svc {
|
||||
|
||||
// Apply registered patch/delete edits for the current stage/layer to the record about to spawn
|
||||
bool stage_apply_actor_edits(void* actorData, void* actorPrm, int8_t roomNo);
|
||||
|
||||
// Creates registered new actors from their record's on room load
|
||||
void stage_create_new_actors(
|
||||
int8_t roomNo, void (*createFn)(void* user, const void* record, size_t size), void* user);
|
||||
|
||||
} // namespace dusk::mods
|
||||
Reference in New Issue
Block a user