implement item queue

This commit is contained in:
gymnast86
2026-04-25 03:07:07 -07:00
parent c7c9b664e8
commit 80f4284e82
11 changed files with 452 additions and 2 deletions
+4
View File
@@ -3908,6 +3908,10 @@ public:
u16 getReadyItem() { return dComIfGp_getSelectItem(mSelectItemId); }
static u32 getOtherHeapSize() { return 0xF0A60; }
#if TARGET_PC
u16 getEventId() { return mMsgFlow.getEventId(); }
#endif
static daAlink_BckData const m_mainBckShield[20];
static daAlink_BckData const m_mainBckSword[5];
+9
View File
@@ -470,6 +470,9 @@ public:
s8 getNextStageRoomNo() { return mNextStage.getRoomNo(); }
s8 getNextStageLayer() { return mNextStage.getLayer(); }
BOOL isEnableNextStage() { return mNextStage.isEnable(); }
#if TARGET_PC
void setEnableNextStage() { return mNextStage.setEnable(); }
#endif
void offEnableNextStage() { mNextStage.offEnable(); }
s8 getNextStageWipe() { return mNextStage.getWipe(); }
u8 getNextStageWipeSpeed() { return mNextStage.getWipeSpeed(); }
@@ -2542,6 +2545,12 @@ inline void dComIfGp_offEnableNextStage() {
g_dComIfG_gameInfo.play.offEnableNextStage();
}
#if TARGET_PC
inline void dComIfGp_setEnableNextStage() {
g_dComIfG_gameInfo.play.setEnableNextStage();
}
#endif
inline s8 dComIfGp_getNextStageWipe() {
return g_dComIfG_gameInfo.play.getNextStageWipe();
}
+3 -1
View File
@@ -129,7 +129,9 @@ public:
bool getPlayerSubject();
bool isBButtonShow(bool);
s16 getButtonTimer();
#if TARGET_PC
f32 getZButtonAlpha() { return mButtonZAlpha;}
#endif
virtual ~dMeter2Draw_c();
J2DScreen* getMainScreenPtr() { return mpScreen; }
+3
View File
@@ -58,6 +58,9 @@ public:
int checkEventRender(int*, int*, int*, int*);
void remove();
u16 getEventId(int*);
#if TARGET_PC
u16 getEventId();
#endif
u32 getMsgNo();
u32 getNowMsgNo();
msg_class* getMsg();
+3
View File
@@ -1292,6 +1292,9 @@ public:
void set(const char*, s8, s16, s8, s8, u8);
void offEnable() { enabled = 0; }
BOOL isEnable() const { return enabled; }
#if TARGET_PC
void setEnable() { enabled |= 0x1; }
#endif
s8 getWipe() const { return wipe; }
u8 getWipeSpeed() const { return wipe_speed; }
dStage_startStage_c* getStartStage() { return this; }
+17
View File
@@ -23,7 +23,10 @@
#include "d/actor/d_a_npc_tkc.h"
#include <cstring>
#if TARGET_PC
#include "dusk/settings.h"
#include "dusk/randomizer/game/randomizer_context.hpp"
#endif
BOOL daAlink_c::checkEventRun() const {
return dComIfGp_event_runCheck() || checkPlayerDemoMode();
@@ -2535,6 +2538,15 @@ int daAlink_c::procCoGetItemInit() {
s16 var_r22 = 0;
BOOL var_r31 = FALSE;
BOOL var_r30 = FALSE;
#if TARGET_PC
if (randomizer_IsActive()) {
// If we are giving a custom item, we want to set mParam0 to 0x100 so that instead of trying to search for an item
// actor that doesnt exist we want the game to create one using the item id in mGtItm.
if (g_randomizerState.getGiveItemToPlayerStatus() == RandomizerState::ITEM_IN_QUEUE) {
mDemo.setParam0(0x100);
}
}
#endif
if (mProcID == PROC_GET_ITEM || mProcID == PROC_INSECT_CATCH ||
(mProcID == PROC_PREACTION_UNEQUIP && !checkNoUpperAnime()))
{
@@ -2572,6 +2584,11 @@ int daAlink_c::procCoGetItemInit() {
} else {
item_no = dComIfGp_event_getGtItm();
}
#if TARGET_PC
if (randomizer_IsActive() && g_randomizerState.getGiveItemToPlayerStatus() == RandomizerState::ITEM_IN_QUEUE) {
g_randomizerState.setGiveItemToPlayerStatus(RandomizerState::CLEAR_QUEUE);
}
#endif
fpc_ProcID item_partner_id = fopAcM_createItemForPresentDemo(&current.pos, item_no, 0, -1,
fopAcM_GetRoomNo(this), NULL, NULL);
+7
View File
@@ -4084,6 +4084,13 @@ void daB_DS_c::executeBattle2Dead() {
camera->mCamera.SetTrimSize(0);
dComIfGp_event_reset();
dComIfGs_onStageBossEnemy(0x13);
#if TARGET_PC
if (randomizer_IsActive()) {
// Give the boss item
u8 agDungeonReward = randomizer_getItemAtLocation("Arbiters Grounds Dungeon Reward");
g_randomizerState.addItemToEventQueue(agDungeonReward);
}
#endif
/* dSv_event_flag_c::F_0265 - Arbiter's Grounds - Arbiter's Grounds clear */
dComIfGs_onEventBit(0x2010);
fopAcM_delete(this);
+6
View File
@@ -260,6 +260,12 @@ u16 dMsgFlow_c::getEventId(int* oItemId_p) {
return mEventId;
}
#if TARGET_PC
u16 dMsgFlow_c::getEventId() {
return mEventId;
}
#endif
u32 dMsgFlow_c::getMsgNo() {
return mMsgNo;
}
@@ -4,6 +4,8 @@
#include "dusk/logging.h"
#include "dusk/main.h"
#include "dusk/randomizer/game/tools.h"
#include "dusk/randomizer/game/stages.h"
#include "dusk/randomizer/game/verify_item_functions.h"
#include "dusk/randomizer/generator/utility/endian.hpp"
#include "dusk/randomizer/generator/utility/yaml.hpp"
#include "dusk/randomizer/generator/randomizer.hpp"
@@ -14,6 +16,10 @@
#include <fstream>
#include "d/actor/d_a_alink.h"
#include "d/d_com_inf_game.h"
#include "d/d_meter2_info.h"
#include "d/d_meter2.h"
#include "d/d_meter2_draw.h"
std::optional<std::string> RandomizerContext::WriteToFile() {
@@ -46,6 +52,9 @@ std::optional<std::string> RandomizerContext::WriteToFile() {
}
}
const std::unordered_map<u16, u16> u16PoeOverrides(this->mPoeOverrides.begin(), this->mPoeOverrides.end());
out["mPoeOverrides"] = u16PoeOverrides;
for (const auto& [stageIdx, itemOverride] : this->mFreestandingItemOverrides) {
for (const auto& [flag, itemId] : itemOverride) {
out["mFreestandingItemOverrides"][static_cast<u16>(stageIdx)][static_cast<u16>(flag)] = static_cast<u16>(itemId);
@@ -119,6 +128,13 @@ std::optional<std::string> RandomizerContext::LoadFromHash(const std::string& ha
}
}
// Poe Overrides
for (const auto& poeNode : in["mPoeOverrides"]) {
u16 key = poeNode.first.as<u16>();
u8 itemId = poeNode.second.as<u8>();
this->mPoeOverrides[key] = itemId;
}
// Freestanding overrides
for (const auto& stageNode : in["mFreestandingItemOverrides"]) {
const auto& stageIdx = stageNode.first.as<u8>();
@@ -180,6 +196,296 @@ std::string RandomizerContext::GetSeedDataPath() const {
return std::string(SDL_GetPrefPath(dusk::OrgName, dusk::AppName)) + "randomizer/seeds/" + this->mHash + "/seed.dat";
}
RandomizerState g_randomizerState;
int RandomizerState::_create() {
mInitialized = true;
mEventItemStatus = QUEUE_EMPTY;
mHasPendingToDChange = false;
// g_customMenuRing._initialize();
return 1;
}
int RandomizerState::_delete() {
mInitialized = false;
return 1;
}
int RandomizerState::execute() {
if (!mInitialized) {
return 0;
}
// Always check for and handle time of day changes
if (getTimeChange() != NO_CHANGE) {
handleTimeSpeed();
}
bool currentReloadingState;
// Any custom functionality that relies on Link's actor being on a stage
if (daAlink_getAlinkActorClass()) {
currentReloadingState = daAlink_getAlinkActorClass()->checkRestartRoom();
// Handle giving item to the player at any time.
initGiveItemToPlayer();
}
else {
currentReloadingState = true;
}
bool prevReloadingState = getRoomReloadingState();
if (!currentReloadingState) {
if (prevReloadingState) {
offLoad();
}
}
setRoomReloadingState(currentReloadingState);
// COpypasta old rando code until I build the framework out.
/*if (!libtp::tp::d_a_alink::checkStageName(libtp::data::stage::allStages[libtp::data::stage::StageIDs::Title_Screen]))
{
handleFoolishItem(randoPtr);
}*/
return 1;
}
int RandomizerState::draw() {
return 1;
}
void RandomizerState::handlePoeItem(u8 bitSw)
{
u16 key = getStageID() << 8 | bitSw;
u8 item = randomizer_GetContext().mPoeOverrides[key];
addItemToEventQueue(item);
daAlink_getAlinkActorClass()->procWolfAtnActorMoveInit();
}
void RandomizerState::addItemToEventQueue(u8 item)
{
for (int i = 0; i < EVENT_ITEM_QUEUE_SIZE; i++)
{
if (mEventItemQueue[i] == 0)
{
mEventItemQueue[i] = item;
break;
}
}
}
void RandomizerState::initGiveItemToPlayer()
{
switch (daAlink_getAlinkActorClass()->mProcID)
{
case daAlink_c::PROC_WAIT:
case daAlink_c::PROC_TIRED_WAIT:
case daAlink_c::PROC_MOVE:
case daAlink_c::PROC_WOLF_WAIT:
case daAlink_c::PROC_WOLF_TIRED_WAIT:
case daAlink_c::PROC_WOLF_MOVE:
case daAlink_c::PROC_ATN_MOVE:
case daAlink_c::PROC_WOLF_ATN_AC_MOVE:
{
// Check if link is currently in a cutscene
if (daAlink_getAlinkActorClass()->checkEventRun())
{
break;
}
// Ensure that link is not currently in a message-based event.
if (daAlink_getAlinkActorClass()->getEventId() != 0)
{
break;
}
u8 itemToGive = 0xFF;
for (int i = 0; i < EVENT_ITEM_QUEUE_SIZE; i++)
{
const u8 storedItem = mEventItemQueue[i];
if (storedItem)
{
const u8 giveItemToPlayerStatus = getGiveItemToPlayerStatus();
// If we have the call to clear the queue, then we want to clear the item and break out.
if (giveItemToPlayerStatus == CLEAR_QUEUE)
{
mEventItemQueue[i] = 0;
setGiveItemToPlayerStatus(QUEUE_EMPTY);
break;
}
// If the queue is empty and we have an item to give, update the queue state.
else if (giveItemToPlayerStatus == QUEUE_EMPTY)
{
setGiveItemToPlayerStatus(ITEM_IN_QUEUE);
}
itemToGive = verifyProgressiveItem(storedItem);
break;
}
}
// if there is no item to give, break out of the case.
if (itemToGive == 0xFF)
{
break;
}
g_dComIfG_gameInfo.play.getEvent()->setGtItm(itemToGive);
// Set the process value for getting an item to start the "get item" cutscene when next available.
daAlink_getAlinkActorClass()->mProcID = daAlink_c::PROC_GET_ITEM;
// Get the event index for the "Get Item" event.
const s16 eventIdx = dComIfGp_getEventManager().getEventIdx((fopAc_ac_c*)daAlink_getAlinkActorClass(),"DEFAULT_GETITEM",0xFF);
// Finally we want to modify the event stack to prioritize our custom event so that it happens next.
fopAcM_orderChangeEventId(daAlink_getAlinkActorClass(), eventIdx, 1, 0xFFFF);
}
default:
{
break;
}
}
}
void RandomizerState::handleTimeOfDayChange()
{
if (dComIfGp_roomControl_getTimePass())
{
// No point in changing values if we are already changing the time.
if (getTimeChange() == NO_CHANGE)
{
if (!dKy_daynight_check()) // Day time
{
setTimeChange(CHANGE_TO_NIGHT);
}
else
{
setTimeChange(CHANGE_TO_DAY);
}
g_env_light.time_change_rate = 1.f; // Increase time speed
}
}
else
{
if (!dKy_daynight_check()) // Day time
{
dComIfGs_setTime(285.f);
}
else
{
dComIfGs_setTime(105.f);
}
dComIfGp_setEnableNextStage();
}
}
void RandomizerState::handleTimeSpeed()
{
if (!dKy_daynight_check()) // Day time
{
if (getTimeChange() == CHANGE_TO_DAY)
{
g_env_light.time_change_rate = 0.012f; // Set time speed to normal
setTimeChange(NO_CHANGE);
}
}
else if (getTimeChange() == CHANGE_TO_NIGHT)
{
g_env_light.time_change_rate = 0.012f; // Set time speed to normal
setTimeChange(NO_CHANGE);
}
}
void RandomizerState::offLoad()
{
if ((getStageID() == City_in_the_Sky) && (dStage_roomControl_c::mStayNo == 0) && (dComIfGp_getStartStagePoint() == 3))
{
// Fan in the main room active
dComIfGs_offSaveSwitch(0xA);
// Main Room 1F explored
dComIfGs_offSaveSwitch(0xF);
}
if (playerIsInRoomStage(1, allStages[Sacred_Grove]))
{
// If the portal in SG isn't active then we want to spawn the shadow beasts.
if (!dComIfGs_isSaveSwitch(0x64))
{
dComIfGs_onSvOneZoneSwitch(0, 0xE);
}
}
if ((getStageID() == Ordon_Ranch) && (dComIfGp_getStartStagePoint() == 1))
{
// Clear the danBit that starts a conversation when entering the ranch so the player can do goats as needed.
dComIfGs_offSaveDunSwitch(0x1);
}
}
bool checkFoolishItemEffectReady()
{
// Verify Link is loaded on the map.
if (!daAlink_getAlinkActorClass())
{
return false;
}
// Ensure Link is not in a cutscene
if (daAlink_getAlinkActorClass()->checkEventRun())
{
return false;
}
// Make sure Link isn't riding anything
if (daAlink_getAlinkActorClass()->checkRide())
{
return false;
}
// Ensure there are pointers to the mMeterClass and mpMeterDraw structs
if (!dMeter2Info_getMeterClass())
{
return false;
}
if (!dMeter2Info_getMeterClass()->getMeterDrawPtr())
{
return false;
}
// Make sure Z button isn't dimmed
if (dMeter2Info_getMeterClass()->getMeterDrawPtr()->getZButtonAlpha() != 1.f)
{
return false;
}
switch (daAlink_getAlinkActorClass()->mProcID)
{
case daAlink_c::PROC_TALK:
case daAlink_c::PROC_WOLF_SWIM_MOVE:
case daAlink_c::PROC_SWIM_MOVE:
case daAlink_c::PROC_SWIM_WAIT:
case daAlink_c::PROC_WOLF_SWIM_WAIT:
case daAlink_c::PROC_SWIM_UP:
case daAlink_c::PROC_SWIM_DIVE:
{
return false;
}
default:
{
break;
}
}
return true;
}
RandomizerContext& randomizer_GetContext() {
static RandomizerContext instance;
return instance;
@@ -218,6 +524,9 @@ u32 getActorCRC32(stage_actor_data_class* actor) {
return zng_crc32(0, reinterpret_cast<u8*>(actor), RandomizerContext::ACTOR_CRC_SIZE);
}
/*
* Generates a seed and writes the necessary seed files to the players seed directory
*/
void GenerateAndWriteSeed(std::string& generationStatusMsg) {
const auto result = SDL_GetPrefPath(dusk::OrgName, dusk::AppName);
if (!result) {
@@ -254,6 +563,16 @@ void GenerateAndWriteSeed(std::string& generationStatusMsg) {
randoData.mTreasureChestOverrides[stage][tboxId] = itemId;
}
// Poe Overrides
// Keyed by u16 of 0xFF00 (stage index) and 0x00FF (collectible flag)
if (location->HasCategories("Poe")) {
const auto& stage = metaData[0]["Stage"].as<u8>();
const auto& flag = metaData[0]["Flag"].as<u8>();
u8 itemId = location->GetCurrentItem()->GetID();
u16 key = (stage << 8) | flag;
randoData.mPoeOverrides[key] = itemId;
}
// Freestanding Overrides
// Keyed by the stage index and collectible flag of the item
if (location->HasCategories("Freestanding Item")) {
@@ -9,6 +9,10 @@
#include <sstream>
#include <unordered_map>
/*
* Class holding all the information necessary for playing
* the current randomizer seed
*/
class RandomizerContext {
public:
static constexpr size_t ACTOR_CRC_SIZE = 30;
@@ -25,7 +29,7 @@ public:
std::unordered_map<u8, std::list<u8>> mStartRegionFlags{};
std::list<u8> mStartingInventory{};
std::unordered_map<std::string, std::unordered_map<u8, u8>> mTreasureChestOverrides{};
std::unordered_map<std::string, std::unordered_map<u8, u8>> mPoeOverrides{};
std::unordered_map<u16, u8> mPoeOverrides{};
std::unordered_map<u8, std::unordered_map<u8, u8>> mFreestandingItemOverrides{};
std::unordered_map<u8, u8> mBugRewardOverrides{};
std::unordered_map<u16, u8> mSkyCharacterOverrides{};
@@ -40,6 +44,61 @@ public:
std::string GetSeedDataPath() const;
};
/*
* Class holding seed-agnostic dynamic information about current randomizer play.
* This gets reset when reseting to the title screen.
*/
class RandomizerState {
public:
enum TimeChange {
NO_CHANGE = 0,
CHANGE_TO_NIGHT,
CHANGE_TO_DAY,
};
enum EventItemStatus {
QUEUE_EMPTY,
ITEM_IN_QUEUE,
CLEAR_QUEUE,
};
static constexpr u8 EVENT_ITEM_QUEUE_SIZE = 10;
RandomizerState() {mInitialized = false;}
int _create();
int _delete();
int execute();
int draw();
void addItemToEventQueue(u8 item);
void initGiveItemToPlayer();
//void handleBonkDamage();
void handleTimeOfDayChange();
void handleTimeSpeed();
void offLoad();
void handlePoeItem(u8 bitSw);
u8 getGiveItemToPlayerStatus() const { return mEventItemStatus;}
u8 getTimeChange() const { return mTimeChange; }
bool getRoomReloadingState() const { return mRoomReloadingState; }
bool getHasPendingToDChange() const { return mHasPendingToDChange; }
void setGiveItemToPlayerStatus(u8 status) { mEventItemStatus = status;}
void setHasPendingToDChange(bool hasPending) { mHasPendingToDChange = hasPending; }
void setTimeChange(u8 newTimeChange) { mTimeChange = newTimeChange; }
void setRoomReloadingState(bool newState) { mRoomReloadingState = newState; }
bool mInitialized{false};
u8 mEventItemStatus{};
bool mHasPendingToDChange{false};
u8 mTimeChange{};
u8 mEventItemQueue[EVENT_ITEM_QUEUE_SIZE];
bool mRoomReloadingState{false};
};
extern RandomizerState g_randomizerState;
RandomizerContext& randomizer_GetContext();
bool randomizer_IsActive();
+21
View File
@@ -23,6 +23,11 @@
#include "tracy/Tracy.hpp"
#if TARGET_PC
#include "dusk/randomizer/game/randomizer_context.hpp"
#endif
void fpcM_Draw(void* i_proc) {
fpcDw_Execute((base_process_class*)i_proc);
}
@@ -89,6 +94,16 @@ void fpcM_Management(fpcM_ManagementFunc i_preExecuteFn, fpcM_ManagementFunc i_p
i_preExecuteFn();
}
#if TARGET_PC
if (randomizer_IsActive()) {
if (!g_randomizerState.mInitialized) {
g_randomizerState._create();
}
g_randomizerState.execute();
} else if (g_randomizerState.mInitialized) {
g_randomizerState = RandomizerState{};
}
#endif
if (!fapGm_HIO_c::isCaptureScreen()) {
fpcEx_Handler((fpcLnIt_QueueFunc)fpcM_Execute);
}
@@ -97,6 +112,12 @@ void fpcM_Management(fpcM_ManagementFunc i_preExecuteFn, fpcM_ManagementFunc i_p
fpcDw_Handler((fpcDw_HandlerFuncFunc)fpcM_DrawIterater, (fpcDw_HandlerFunc)fpcM_Draw);
}
#if TARGET_PC
if (randomizer_IsActive()) {
g_randomizerState.draw();
}
#endif
if (i_postExecuteFn != NULL) {
i_postExecuteFn();
}