diff --git a/include/d/d_com_inf_game.h b/include/d/d_com_inf_game.h index 307dbcbf75..40bc63d7ed 100644 --- a/include/d/d_com_inf_game.h +++ b/include/d/d_com_inf_game.h @@ -2492,6 +2492,12 @@ inline s8 dComIfGp_getStartStageRoomNo() { return g_dComIfG_gameInfo.play.getStartStageRoomNo(); } +#if TARGET_PC +inline s8 dComIfGp_getLayerNo() { + return g_dComIfG_gameInfo.play.getLayerNo(0); +} +#endif + inline s8 dComIfGp_getStartStageLayer() { return g_dComIfG_gameInfo.play.getStartStageLayer(); } diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index e6943baf96..1eaea7728a 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -11469,6 +11469,14 @@ int daAlink_c::orderTalk(int i_checkZTalk) { static void* daAlink_searchBouDoor(fopAc_ac_c* i_actor, void* i_data) { UNUSED(i_data); +#if TARGET_PC + // In randomizer, we don't want Bo preventing us from entering his house on Day 2 + if (randomizer_IsActive() && daAlink_c::checkStageName("F_SP103")) + { + return NULL; + } +#endif + if (fopAcM_GetName(i_actor) == fpcNm_NPC_BOU_e && ((daNpc_Bou_c*)i_actor)->speakTo()) { return i_actor; } diff --git a/src/d/actor/d_a_npc_zrc.cpp b/src/d/actor/d_a_npc_zrc.cpp index d7f2ebc372..c169c15ad9 100644 --- a/src/d/actor/d_a_npc_zrc.cpp +++ b/src/d/actor/d_a_npc_zrc.cpp @@ -882,7 +882,11 @@ u8 daNpc_zrC_c::getTypeFromParam() { int daNpc_zrC_c::isDelete() { if (mType == 4 || mType == 0 || mType == 1 || (mType == 2 && daNpcF_chkEvtBit(0x108) +#if TARGET_PC + && (!daNpcF_chkEvtBit(0x10A) || randomizer_IsActive())) || mType == 3) +#else && !daNpcF_chkEvtBit(0x10A)) || mType == 3) +#endif { return false; } diff --git a/src/d/d_msg_flow.cpp b/src/d/d_msg_flow.cpp index c461b179e1..51d80dcfbf 100644 --- a/src/d/d_msg_flow.cpp +++ b/src/d/d_msg_flow.cpp @@ -15,6 +15,10 @@ #include "SSystem/SComponent/c_math.h" #include +#if TARGET_PC +#include "dusk/randomizer/game/stages.h" +#endif + dMsgFlow_c::dMsgFlow_c() { mNonStopJunpFlowFlag = 0; setInitValue(1); @@ -1134,6 +1138,20 @@ u16 dMsgFlow_c::query022(mesg_flow_node_branch* i_flowNode_p, fopAc_ac_c* i_spea const u8 prm0 = i_flowNode_p->param; u16 ret = checkItemGet(prm0 & 0xFF, 1) ? 0 : 1; +#if TARGET_PC + // Check to see if we're currently in one of the Ordon interiors + if (randomizer_IsActive() && daAlink_c::checkStageName(allStages[Ordon_Village_Interiors])) + { + // Check to see if checking for the Iron Boots + if (prm0 == dItemNo_Randomizer_HVY_BOOTS_e) + { + // Return false so that the door in Bo's house can be opened without having the + // Iron Boots + return 0; + } + } +#endif + if (param_2 != 0) { // "Get Check" OS_REPORT("\x1B[44;33m所持チェック            \x1B[m|:"); diff --git a/src/d/d_stage.cpp b/src/d/d_stage.cpp index 3904697598..b5d5a97054 100644 --- a/src/d/d_stage.cpp +++ b/src/d/d_stage.cpp @@ -22,9 +22,11 @@ #include #include + +#if TARGET_PC #include "dusk/logging.h" #include "dusk/string.hpp" -#if TARGET_PC +#include "dusk/randomizer/game/randomizer_context.hpp" #include #include #endif @@ -1595,6 +1597,22 @@ u8 dStage_roomControl_c::mNoArcBank; static void dStage_actorCreate(stage_actor_data_class* i_actorData, fopAcM_prm_class* i_actorPrm) { dStage_objectNameInf* actorInf = dStage_searchName(i_actorData->name); + +#if TARGET_PC + // If randomizer is active, override the data for this actor if it's in the actorPatches + if (randomizer_IsActive()) { + auto currentStageKey = getActorPatchesCurrentStageKey(); + if (randomizer_GetContext().mActorPatches.contains(currentStageKey)) { + const auto& patches = randomizer_GetContext().mActorPatches.at(currentStageKey); + auto actorKey = getActorCRC32(i_actorData); + if (patches.contains(actorKey)) { + const auto& patchedActorData = patches.at(actorKey); + std::memcpy(i_actorPrm, patchedActorData.data() + 8, RandomizerContext::ACTOR_CRC_SIZE - 8); + } + } + } +#endif + if (actorInf == NULL) { OS_REPORT("\x1B""[43;30mStage Actor Name Nothing !! <%s>\n\x1B[m", i_actorData->name); JKRFree(i_actorPrm); diff --git a/src/dusk/imgui/ImGuiMenuRandomizer.cpp b/src/dusk/imgui/ImGuiMenuRandomizer.cpp index 25b36d2067..ad13ea4b2d 100644 --- a/src/dusk/imgui/ImGuiMenuRandomizer.cpp +++ b/src/dusk/imgui/ImGuiMenuRandomizer.cpp @@ -1,10 +1,10 @@ #include "imgui.h" +#include "d/d_stage.h" + #include "ImGuiConsole.hpp" #include "ImGuiMenuRandomizer.hpp" -#include - #include "dusk/app_info.hpp" #include "dusk/logging.h" #include "dusk/randomizer/game/randomizer_context.hpp" @@ -13,9 +13,13 @@ #include "SDL3/SDL_filesystem.h" +#include #include #include +#include "dusk/randomizer/generator/utility/endian.hpp" +#include "dusk/randomizer/generator/utility/string.hpp" + namespace dusk { static bool generatingSeed = false; @@ -126,6 +130,81 @@ namespace dusk { else if (startTimeSetting == "Night") randoData.mStartHour = 24; + // Actor Patches + auto actorPatches = LoadYAML(RANDO_DATA_PATH "actor_patches.yaml"); + for (const auto& stageNode : actorPatches) { + const auto& stageName = stageNode.first.as(); + for (const auto& roomNode : stageNode.second) { + const auto& roomStr = roomNode.first.as(); + u8 roomNo = roomStr.back() - '0'; + for (const auto& layerNode : roomNode.second) { + const auto& layerStr = layerNode.first.as(); + u8 layerNo = layerStr.back() - '0'; + // Create key based off of stage index, room, and layer + u32 stageRoomLayerKey{}; + stageRoomLayerKey |= getStageID(stageName.c_str()) << 16; + stageRoomLayerKey |= roomNo << 8; + stageRoomLayerKey |= layerNo; + for (const auto& actorNode : layerNode.second) { + using namespace Utility::Endian; + // Get all the data for the actor (with endian shenanigans) + stage_actor_data_class actor{}; + const auto& actorName = actorNode["name"].as(); + strncpy(actor.name, actorName.c_str(), 8); + actor.base.parameters = toPlatform(target, actorNode["parameters"].as()); + actor.base.position.x = toPlatform(target, actorNode["position"]["x"].as()); + actor.base.position.y = toPlatform(target, actorNode["position"]["y"].as()); + actor.base.position.z = toPlatform(target, actorNode["position"]["z"].as()); + // Have to retrieve as u16 and then cast as s16 because otherwise yaml-cpp + // complains about values over 32767 not fitting in s16 + actor.base.angle.x = toPlatform(target, static_cast(actorNode["angle"]["x"].as())); + actor.base.angle.y = toPlatform(target, static_cast(actorNode["angle"]["y"].as())); + actor.base.angle.z = toPlatform(target, static_cast(actorNode["angle"]["z"].as())); + + + // Create unique hash based off of actor data + u32 actorCRC32 = getActorCRC32(&actor); + + // Then override the actor with whatever parts are being patched + const auto& patchNode = actorNode["patch"]; + if (patchNode["parameters"]) { + actor.base.parameters = toPlatform(target, patchNode["parameters"].as()); + } + if (auto patchPosition = patchNode["position"]) { + if (patchPosition["x"]) { + actor.base.position.x = toPlatform(target, patchPosition["x"].as()); + } + if (patchPosition["y"]) { + actor.base.position.y = toPlatform(target, patchPosition["y"].as()); + } + if (patchPosition["z"]) { + actor.base.position.z = toPlatform(target, patchPosition["z"].as()); + } + } + if (auto patchAngle = patchNode["angle"]) { + // Have to retrieve as u16 and then cast as s16 because otherwise yaml-cpp + // complains about values over 32767 not fitting in s16 + if (patchAngle["x"]) { + actor.base.angle.x = toPlatform(target, static_cast(patchAngle["x"].as())); + } + if (patchAngle["y"]) { + actor.base.angle.y = toPlatform(target, static_cast(patchAngle["y"].as())); + } + if (patchAngle["z"]) { + actor.base.angle.z = toPlatform(target, static_cast(patchAngle["z"].as())); + } + } + + // Insert the actor patch into the context with our crc32 as the key and the + // raw actor patch data as the value + std::array patchedActorData{}; + std::memcpy(patchedActorData.data(), &actor, RandomizerContext::ACTOR_CRC_SIZE); + randoData.mActorPatches[stageRoomLayerKey][actorCRC32] = patchedActorData; + } + } + } + } + randoData.mHash = r.GetConfig().GetHash(); auto writeToFileResult = randoData.WriteToFile(); if (writeToFileResult.has_value()) { diff --git a/src/dusk/randomizer/game/randomizer_context.cpp b/src/dusk/randomizer/game/randomizer_context.cpp index 681b996615..f579e2058b 100644 --- a/src/dusk/randomizer/game/randomizer_context.cpp +++ b/src/dusk/randomizer/game/randomizer_context.cpp @@ -6,11 +6,15 @@ #include "dusk/logging.h" #include "dusk/main.h" #include "dusk/randomizer/game/tools.h" +#include "dusk/randomizer/generator/utility/endian.hpp" #include "SDL3/SDL_filesystem.h" +#include #include +#include "d/actor/d_a_alink.h" + RandomizerContext& randomizer_GetContext() { static RandomizerContext instance; return instance; @@ -54,6 +58,12 @@ std::optional RandomizerContext::WriteToFile() { out["mStartHour"] = static_cast(this->mStartHour); out["mMapBits"] = static_cast(this->mMapBits); + for (const auto& [stageRoomLayer, actorPatches] : this->mActorPatches) { + for (const auto& [actorCRC, actorPatch] : actorPatches) { + out["mActorPatches"][stageRoomLayer][actorCRC] = ContainerToHexString(actorPatch); + } + } + seedData << YAML::Dump(out); seedData.close(); @@ -109,6 +119,17 @@ std::optional RandomizerContext::LoadFromHash(const std::string& ha // Starting map bits this->mMapBits = in["mMapBits"].as(); + // Actor Patches + for (const auto& stageRoomLayerNode: in["mActorPatches"]) { + u32 stageRoomLayer = stageRoomLayerNode.first.as(); + for (const auto& actorPatchNode : stageRoomLayerNode.second) { + u32 actorCRC = actorPatchNode.first.as(); + auto actorBytes = HexToBytes(actorPatchNode.second.as()); + auto& patchedActor = this->mActorPatches[stageRoomLayer][actorCRC]; + std::copy_n(actorBytes.begin(), actorBytes.size(), patchedActor.begin()); + } + } + DuskLog.debug("Loaded Randomizer Seed {}", this->mHash); return std::nullopt; @@ -118,3 +139,27 @@ std::string RandomizerContext::GetSeedDataPath() const { return std::string(SDL_GetPrefPath(dusk::OrgName, dusk::AppName)) + "randomizer/seeds/" + this->mHash + "/seed.dat"; } +std::vector HexToBytes(std::string hex) { + std::vector bytes; + // Strip "0x" if present + if (hex.substr(0, 2) == "0x") hex = hex.substr(2); + + for (size_t i = 0; i < hex.length(); i += 2) { + std::string byteString = hex.substr(i, 2); + u8 byte = static_cast(strtol(byteString.c_str(), nullptr, 16)); + bytes.push_back(byte); + } + return bytes; +} + +u32 getActorPatchesCurrentStageKey() { + u32 actorPatchesStageKey{}; + actorPatchesStageKey |= getStageID(dComIfGp_getStartStageName()) << 16; + actorPatchesStageKey |= dComIfGp_getStartStageRoomNo() << 8; + actorPatchesStageKey |= dComIfGp_getLayerNo(); + return actorPatchesStageKey; +} + +u32 getActorCRC32(stage_actor_data_class* actor) { + return zng_crc32(0, reinterpret_cast(actor), RandomizerContext::ACTOR_CRC_SIZE); +} \ No newline at end of file diff --git a/src/dusk/randomizer/game/randomizer_context.hpp b/src/dusk/randomizer/game/randomizer_context.hpp index 8737b6bb2e..9760d4d35a 100644 --- a/src/dusk/randomizer/game/randomizer_context.hpp +++ b/src/dusk/randomizer/game/randomizer_context.hpp @@ -2,13 +2,17 @@ #define DUSK_RANDOMIZER_CONTEXT_HPP #include -#include +#include +#include #include +#include #include class RandomizerContext { public: + static constexpr size_t ACTOR_CRC_SIZE = 30; + RandomizerContext() = default; bool mActive{false}; @@ -26,6 +30,8 @@ public: u8 mStartHour{0}; u8 mMapBits{}; + std::unordered_map>> mActorPatches{}; + std::optional WriteToFile(); std::optional LoadFromHash(const std::string& hash); std::string GetSeedDataPath() const; @@ -35,4 +41,47 @@ RandomizerContext& randomizer_GetContext(); bool randomizer_IsActive(); +/** + * Helper function to convert raw bytes of a container to a hex string + */ +template +std::string ContainerToHexString(const T& container, bool includePrefix = true) { + std::ostringstream oss; + + if (includePrefix) { + oss << "0x"; + } + + // Get the raw byte pointer to the start of the data + const auto* rawBytes = reinterpret_cast(container.data()); + + // Calculate total byte size (number of elements * size of each element) + size_t totalBytes = container.size() * sizeof(typename T::value_type); + + oss << std::hex << std::setfill('0') << std::uppercase; + + for (size_t i = 0; i < totalBytes; ++i) { + // static_cast to not u8 is necessary so oss treats it as a number, not a char + oss << std::setw(2) << static_cast(rawBytes[i]); + } + + return oss.str(); +} + +/** + * Helper function to convert hex string to raw bytes + */ +std::vector HexToBytes(std::string hex); + +/* + * Gets the current stage id, room no, and layer no in the format for a key in mActorPatches + */ +u32 getActorPatchesCurrentStageKey(); + +class stage_actor_data_class; +/* + * Gets the CRC32 hash of an actors name, parameters, position, and angle + */ +u32 getActorCRC32(stage_actor_data_class*); + #endif //DUSK_RANDOMIZER_CONTEXT_HPP diff --git a/src/dusk/randomizer/game/tools.cpp b/src/dusk/randomizer/game/tools.cpp index bd1a7c6d25..403e8e2b53 100644 --- a/src/dusk/randomizer/game/tools.cpp +++ b/src/dusk/randomizer/game/tools.cpp @@ -99,8 +99,10 @@ int getStageID(const char* stage) int loopCount = sizeof(allStages) / sizeof(allStages[0]); for (int i = 0; i < loopCount; i++) { - if (daAlink_c::checkStageName(allStages[i])) - { + // If stage is NULL, check for current stage + if (stage == NULL) { + if (daAlink_c::checkStageName(allStages[i])) return i; + } else if (strcmp(stage, allStages[i]) == 0) { return i; } } diff --git a/src/dusk/randomizer/game/tools.h b/src/dusk/randomizer/game/tools.h index 2da0af66aa..fc43ba88c3 100644 --- a/src/dusk/randomizer/game/tools.h +++ b/src/dusk/randomizer/game/tools.h @@ -7,5 +7,9 @@ void checkTransformFromWolf(); u8 setNextWarashibeItem(); void offWarashibeItem(u8 item); int initCreatePlayerItem(u32 item, u32 flag, const cXyz* pos, int roomNo, const csXyz* angle, const cXyz* scale); -int getStageID(const char* stage); +/* + * Returns the ID of the passed in stage name. If no stage name is passed in, the id of the current + * stage is returned + */ +int getStageID(const char* stage = NULL); bool playerIsOnTitleScreen(); diff --git a/src/dusk/randomizer/generator/data/actor_patches.yaml b/src/dusk/randomizer/generator/data/actor_patches.yaml new file mode 100644 index 0000000000..6d8100f23d --- /dev/null +++ b/src/dusk/randomizer/generator/data/actor_patches.yaml @@ -0,0 +1,38 @@ +# Patches for actors that already exist on a given map + +# NOTE: Data is expressed in little endian format + +# Ordon Village +F_SP103: + Room 0: + Layer 0: + # Bo's House left Door + - name: kdoor + parameters: 0x88000627 + position: + x: 470.48294 + y: 500.19 + z: 5323.129 + angle: + x: 0x0191 + y: 0x3777 + z: 0xFFFF + patch: + # Set angle.x to -1 so the door isn't locked + angle: + x: 0xFFFF + # Bo's House right door + - name: kdoor + parameters: 0x94000627 + position: + x: 500.71118 + y: 500.19 + z: 5172.267 + angle: + x: 0x0191 + y: 0xB778 + z: 0xFFFF + patch: + # Set angle.x to -1 so the door isn't locked + angle: + x: 0xFFFF \ No newline at end of file