diff --git a/include/d/actor/d_a_e_hp.h b/include/d/actor/d_a_e_hp.h index 594c0de01f..86b092f915 100644 --- a/include/d/actor/d_a_e_hp.h +++ b/include/d/actor/d_a_e_hp.h @@ -97,7 +97,7 @@ public: /* 0xDEC */ u8 field_0xdec[0xdf9 - 0xdec]; /* 0xDF9 */ u8 field_0xdf9; #if TARGET_PC - bool mItemCheckOverridden; + bool mItemCheckHandled; #endif }; diff --git a/include/d/d_a_itembase_static.h b/include/d/d_a_itembase_static.h index 36a0f55899..38ecb323eb 100644 --- a/include/d/d_a_itembase_static.h +++ b/include/d/d_a_itembase_static.h @@ -1,10 +1,15 @@ #ifndef D_A_D_A_ITEMBASE_STATIC_H #define D_A_D_A_ITEMBASE_STATIC_H +#include "dolphin/types.h" class fopAc_ac_c; int CheckFieldItemCreateHeap(fopAc_ac_c* actor); int CheckItemCreateHeap(fopAc_ac_c* i_this); +#if TARGET_PC +const char* dItem_fieldModelArc(u8 itemNo); +#endif + #endif /* D_A_D_A_ITEMBASE_STATIC_H */ diff --git a/include/d/d_item_data.h b/include/d/d_item_data.h index 0044573976..7672174c87 100644 --- a/include/d/d_item_data.h +++ b/include/d/d_item_data.h @@ -142,7 +142,12 @@ enum { /* 0x2F */ dItemNo_WEAR_KOKIRI_e, /* 0x30 */ dItemNo_ARMOR_e, /* 0x31 */ dItemNo_WEAR_ZORA_e, +#if TARGET_PC /* 0x32 */ dItemNo_MAGIC_LV1_e, + dItemNo_SHADOW_CRYSTAL_e = dItemNo_MAGIC_LV1_e, +#else + /* 0x32 */ dItemNo_MAGIC_LV1_e, +#endif /* 0x33 */ dItemNo_DUNGEON_EXIT_2_e, /* 0x34 */ dItemNo_WALLET_LV1_e, /* 0x35 */ dItemNo_WALLET_LV2_e, diff --git a/include/global.h b/include/global.h index 4a86f0678f..725fd03874 100644 --- a/include/global.h +++ b/include/global.h @@ -259,13 +259,19 @@ using std::isnan; #if TARGET_PC && defined(DUSK_BUILDING_GAME) #include "dusk/mods/item.hpp" #define DUSK_ITEM_CHECK(name, item_no, giver) \ - (item_no) = ::dusk::mods::item_check(name, (item_no), giver) + (item_no) = ::dusk::mods::item_check_commit(name, (item_no), giver).itemNo #define DUSK_ITEM_CHECK_EXPR(name, item_no, giver) \ + (::dusk::mods::item_check_commit(name, (item_no), giver).itemNo) +#define DUSK_ITEM_CHECK_PREVIEW(name, item_no, giver) \ + (item_no) = ::dusk::mods::item_check(name, (item_no), giver) +#define DUSK_ITEM_CHECK_PREVIEW_EXPR(name, item_no, giver) \ (::dusk::mods::item_check(name, (item_no), giver)) #define DUSK_GIVE_TAG(name) IF_DUSK_ARG(::dusk::mods::item_give_tag(name)) #else #define DUSK_ITEM_CHECK(name, item_no, giver) #define DUSK_ITEM_CHECK_EXPR(name, item_no, giver) (item_no) +#define DUSK_ITEM_CHECK_PREVIEW(name, item_no, giver) +#define DUSK_ITEM_CHECK_PREVIEW_EXPR(name, item_no, giver) (item_no) #define DUSK_GIVE_TAG(name) #endif diff --git a/mods/window_demo/src/mod.cpp b/mods/window_demo/src/mod.cpp index 00d6f1240b..0a1e9333ff 100644 --- a/mods/window_demo/src/mod.cpp +++ b/mods/window_demo/src/mod.cpp @@ -188,8 +188,6 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) { g_stageHook = 0; return mods::set_error(error, MOD_ERROR, "failed to register mod panel"); } - - mods::log::info("auxiliary WebGPU window ready"); return MOD_OK; } diff --git a/sdk/include/mods/items.h b/sdk/include/mods/items.h new file mode 100644 index 0000000000..e10b6df639 --- /dev/null +++ b/sdk/include/mods/items.h @@ -0,0 +1,62 @@ +#pragma once + +/* + * ItemService check names. + */ + +#define ITEM_CHECK_BOSS_PREFIX "boss:" /* */ +#define ITEM_CHECK_BUG_PREFIX "bug:" /* */ +#define ITEM_CHECK_CHEST_PREFIX "chest:" /* : */ +#define ITEM_CHECK_FREESTANDING_PREFIX "freestanding:" /* : */ +#define ITEM_CHECK_POE_PREFIX "poe:" /* : */ +#define ITEM_CHECK_SHOP_PREFIX "shop:" /* : */ +#define ITEM_CHECK_SKY_PREFIX "sky:" /* : */ +#define ITEM_CHECK_DUNGEON_REWARD_PREFIX "dungeon_reward:" /* */ + +#define ITEM_CHECK_ARCHERY_REWARD "archery_reward:F_SP109" +#define ITEM_CHECK_ASHEI_SKETCH "ashei_sketch" +#define ITEM_CHECK_AURU_MEMO "auru_memo" +#define ITEM_CHECK_BALL_AND_CHAIN "ball_and_chain:D_MN11" +#define ITEM_CHECK_BULBLIN_KEY "bulblin_key:D_MN09" +#define ITEM_CHECK_CORAL_EARRING "coral_earring" +#define ITEM_CHECK_CORO_BOTTLE "coro_bottle" +#define ITEM_CHECK_DUNGEON_MAP_SNOWPEAK "dungeon_map:D_MN11" +#define ITEM_CHECK_FAIRY_REWARD "fairy_reward:D_SB01" +#define ITEM_CHECK_FISHING_BOTTLE "fishing_bottle" +#define ITEM_CHECK_FISHING_HEART_PIECE "fishing_heart_piece" +#define ITEM_CHECK_GOATS_REWARD "goats_reward" +#define ITEM_CHECK_GORON_REWARD "goron_reward:F_SP113" +#define ITEM_CHECK_ILIA_CHARM "ilia_charm" +#define ITEM_CHECK_ILIA_MEMORY "ilia_memory" +#define ITEM_CHECK_IZA_REWARD_1 "iza_reward_1" +#define ITEM_CHECK_IZA_REWARD_2 "iza_reward_2" +#define ITEM_CHECK_JOVANI_REWARD_1 "jovani_reward_1" +#define ITEM_CHECK_JOVANI_REWARD_2 "jovani_reward_2" +#define ITEM_CHECK_KEY_SHARD_1 "key_shard_1:D_MN04" +#define ITEM_CHECK_KEY_SHARD_2 "key_shard_2:D_MN04" +#define ITEM_CHECK_KEY_SHARD_3 "key_shard_3:D_MN04" +#define ITEM_CHECK_ORDON_SHIELD "ordon_shield" +#define ITEM_CHECK_ORDON_SWORD "ordon_sword" +#define ITEM_CHECK_PLUMM_REWARD "plumm_minigame_reward" +#define ITEM_CHECK_PRAYER_REWARD "prayer_reward" +#define ITEM_CHECK_RENADO_LETTER "renado_letter" +#define ITEM_CHECK_SERA_REWARD "sera_reward" +#define ITEM_CHECK_SKYBOOK "skybook" +#define ITEM_CHECK_SNOWBOARD_REWARD "snowboard_race_reward" +#define ITEM_CHECK_STAR_REWARD_1 "star_reward_1" +#define ITEM_CHECK_STAR_REWARD_2 "star_reward_2" +#define ITEM_CHECK_TELMA_INVOICE "telma_invoice" +#define ITEM_CHECK_ULI_CRADLE_REWARD "uli_cradle_reward" +#define ITEM_CHECK_WOOD_STATUE "wood_statue" +#define ITEM_CHECK_ZORA_ARMOR "zora_armor" + +#define ITEM_CHECK_DUNGEON_REWARD_FOREST "dungeon_reward:D_MN05" +#define ITEM_CHECK_DUNGEON_REWARD_GORON "dungeon_reward:D_MN04" +#define ITEM_CHECK_DUNGEON_REWARD_LAKEBED "dungeon_reward:D_MN01" +#define ITEM_CHECK_DUNGEON_REWARD_ARBITERS "dungeon_reward:D_MN10" +#define ITEM_CHECK_DUNGEON_REWARD_SNOWPEAK "dungeon_reward:D_MN11" +#define ITEM_CHECK_DUNGEON_REWARD_TIME "dungeon_reward:D_MN06" +#define ITEM_CHECK_DUNGEON_REWARD_CITY "dungeon_reward:D_MN07" + +#define ITEM_CHECK_MASTER_SWORD "master_sword" +#define ITEM_CHECK_SHADOW_CRYSTAL "shadow_crystal" diff --git a/sdk/include/mods/svc/item.h b/sdk/include/mods/svc/item.h index 81a2589f6d..1f2c3d3b43 100644 --- a/sdk/include/mods/svc/item.h +++ b/sdk/include/mods/svc/item.h @@ -17,10 +17,12 @@ typedef uint64_t ItemGiveHandle; /* * Item check resolution and inventory grants. * - * Check names are case-sensitive. Resolvers must be free of side effects because the game may - * resolve a check more than once, including once for display and again when granting the item. - * Registrations and pending grants are removed when the calling mod is detached. Callbacks run - * on the game thread. + * Check names are case-sensitive. Resolvers must be free of side effects because previews may be + * resolved repeatedly. For checks representing a non-item side effect, vanilla_item is 0xFF: + * resolving to 0xFF preserves that side effect, while resolving to another item replaces it. + * + * Registrations and pending grants are removed when the calling mod is detached. Callbacks run on + * the game thread. Check names are listed in . */ /* Host-owned callback data, valid only for the duration of the callback. */ @@ -72,14 +74,14 @@ typedef struct ItemService { ModResult (*clear_check_resolver)(ModContext* ctx, ItemCheckHandle handle); - /* Resolve without granting an item or notifying give observers. */ + /* Resolve a live preview without granting an item or notifying give observers. */ ModResult (*resolve_check)( ModContext* ctx, const char* name, uint8_t vanilla_item, uint8_t* out_item); /* * Add a grant to the global FIFO. check_name may be NULL unless ITEM_GIVE_RESOLVE is set. - * Returns MOD_UNAVAILABLE when the queue is full. Entries wait until gameplay is in a safe - * state and are cleared when the active save slot changes. + * Entries wait until gameplay is in a safe state and are cleared when the active save slot + * changes. */ ModResult (*give_item)( ModContext* ctx, const char* check_name, uint8_t item_no, uint32_t flags); diff --git a/src/d/actor/d_a_alink_demo.inc b/src/d/actor/d_a_alink_demo.inc index e8812c6e41..f16c8a6887 100644 --- a/src/d/actor/d_a_alink_demo.inc +++ b/src/d/actor/d_a_alink_demo.inc @@ -1027,6 +1027,9 @@ void daAlink_c::setGetItemFace(u16 i_itemNo) { || i_itemNo == dItemNo_ANCIENT_DOCUMENT2_e || i_itemNo == dItemNo_DROP_CONTAINER_e || i_itemNo == dItemNo_DROP_CONTAINER02_e +#if TARGET_PC + || i_itemNo == dItemNo_SHADOW_CRYSTAL_e +#endif || i_itemNo == dItemNo_DROP_CONTAINER03_e) { setFaceBasicBck(dRes_ID_ALANM_BCK_FI_e); @@ -2009,7 +2012,11 @@ void daAlink_c::setGetSubBgm(int i_itemNo) { /* dItemNo_WEAR_KOKIRI_e */ SETYPE_NONE, /* dItemNo_ARMOR_e */ SETYPE_ITEM_GET, /* dItemNo_WEAR_ZORA_e */ SETYPE_ITEM_GET, +#if TARGET_PC + /* dItemNo_SHADOW_CRYSTAL_e */ SETYPE_ITEM_GET, +#else /* dItemNo_MAGIC_LV1_e */ SETYPE_NONE, +#endif /* dItemNo_DUNGEON_EXIT_2_e */ SETYPE_ITEM_GET_MINI, /* dItemNo_WALLET_LV1_e */ SETYPE_NONE, /* dItemNo_WALLET_LV2_e */ SETYPE_ITEM_GET, @@ -2307,8 +2314,13 @@ int daAlink_c::procCoGetItemInit() { item_no = dComIfGp_event_getGtItm(); } +#if TARGET_PC + const u32 giveTag = mDemo.getParam0() != 0x100 && mDemo.getParam1() != 0 ? + static_cast(mDemo.getParam1()) : + dusk::mods::item_give_queue_take_tag(); +#endif fpc_ProcID item_partner_id = fopAcM_createItemForPresentDemo(¤t.pos, item_no, 0, -1, - fopAcM_GetRoomNo(this), NULL, NULL IF_DUSK_ARG(dusk::mods::item_give_queue_take_tag())); + fopAcM_GetRoomNo(this), NULL, NULL IF_DUSK_ARG(giveTag)); if (item_partner_id != fpcM_ERROR_PROCESS_ID_e) { dComIfGp_event_setItemPartnerId(item_partner_id); } @@ -2533,6 +2545,16 @@ int daAlink_c::procCoGetItem() { } else if (mProcVar2.field_0x300c == 0x23 && checkStageName("D_MN11")) { field_0x32cc = 0x5C0; } else if (mProcVar2.field_0x300c == 0xE0) { +#if TARGET_PC + // Dusklight increments the Poe count when the displayed item is granted. + if (dComIfGs_getPohSpiritNum() + 1 == 20) { + field_0x32cc = 0x4CF; + } else if (dComIfGs_getPohSpiritNum() + 1 == 60) { + field_0x32cc = 0x4D0; + } else { + field_0x32cc = mProcVar2.field_0x300c + 0x65; + } +#else if (dComIfGs_getPohSpiritNum() == 20) { field_0x32cc = 0x4CF; } else if (dComIfGs_getPohSpiritNum() == 60) { @@ -2540,6 +2562,7 @@ int daAlink_c::procCoGetItem() { } else { field_0x32cc = mProcVar2.field_0x300c + 0x65; } +#endif } else if (mProcVar2.field_0x300c == 0x21) { static u32 const heartPieceMessage[5] = {0x86, 0x9C, 0x9D, 0x9E, 0x9F}; field_0x32cc = heartPieceMessage[dComIfGs_getMaxLife() % 5]; diff --git a/src/d/actor/d_a_b_ds.cpp b/src/d/actor/d_a_b_ds.cpp index e0623acaba..efa1c289a9 100644 --- a/src/d/actor/d_a_b_ds.cpp +++ b/src/d/actor/d_a_b_ds.cpp @@ -19,6 +19,10 @@ #include "f_op/f_op_actor_enemy.h" #include "Z2AudioLib/Z2Instances.h" +#if TARGET_PC +#include "mods/items.h" +#endif + enum daB_DS_Joint { DS_JNT_BACKBONE1, DS_JNT_BACKBONE2, @@ -4086,7 +4090,8 @@ void daB_DS_c::executeBattle2Dead() { dComIfGs_onStageBossEnemy(0x13); #if TARGET_PC // This reward has no original grant at this point in the cutscene. - dusk::mods::item_check_enqueue("Arbiters Grounds Dungeon Reward", dItemNo_NONE_e); + dusk::mods::item_check_enqueue_deferred( + ITEM_CHECK_DUNGEON_REWARD_ARBITERS, dItemNo_NONE_e); #endif /* dSv_event_flag_c::F_0265 - Arbiter's Grounds - Arbiter's Grounds clear */ dComIfGs_onEventBit(0x2010); diff --git a/src/d/actor/d_a_demo00.cpp b/src/d/actor/d_a_demo00.cpp index b737083598..60a99d4d54 100644 --- a/src/d/actor/d_a_demo00.cpp +++ b/src/d/actor/d_a_demo00.cpp @@ -1090,6 +1090,13 @@ inline int daDemo00_c::execute() { break; } case 1: +#if TARGET_PC + // Suppress demo22_01's M_077 write. On Dusklight, the transform flag + // is set by the Shadow Crystal item. + if (dSv_event_flag_c::saveBitLabels[sp0E] == dSv_event_flag_c::M_077) { + break; + } +#endif dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[sp0E]); break; diff --git a/src/d/actor/d_a_e_hp.cpp b/src/d/actor/d_a_e_hp.cpp index 8c9d9398ea..a7729d81f6 100644 --- a/src/d/actor/d_a_e_hp.cpp +++ b/src/d/actor/d_a_e_hp.cpp @@ -727,19 +727,25 @@ void daE_HP_c::executeDead() { } #if TARGET_PC - mItemCheckOverridden = - dusk::mods::item_check_poe(bitSw, dItemNo_POU_SPIRIT_e, this) != dItemNo_POU_SPIRIT_e; - if (mItemCheckOverridden) { - dusk::mods::item_check_enqueue_poe(bitSw, dItemNo_POU_SPIRIT_e); - } else + const auto itemCheck = dusk::mods::item_check_commit( + dusk::mods::item_give_tag_poe(bitSw), dItemNo_POU_SPIRIT_e, this); + mItemCheckHandled = true; + if (itemCheck.itemNo == dItemNo_NONE_e) { + dusk::mods::item_check_complete(itemCheck, this); + } else { + dusk::mods::item_check_enqueue(itemCheck, dusk::mods::ItemGiveMode::ForcedDemo); + } +#else + dComIfGs_addPohSpiritNum(); #endif - dComIfGs_addPohSpiritNum(); field_0x784 = -1; - if (dComIfGs_getPohSpiritNum() == 20 IF_DUSK(&&!mItemCheckOverridden)) { +#if !TARGET_PC + if (dComIfGs_getPohSpiritNum() == 20) { dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[0x1c9]); } +#endif movemode++; } @@ -759,7 +765,7 @@ void daE_HP_c::executeDead() { field_0x788 = 1; } } - } else if (field_0x788 != 0 IF_DUSK(|| mItemCheckOverridden)) { + } else if (field_0x788 != 0 IF_DUSK(|| mItemCheckHandled)) { fopAcM_createDisappear(this, ¤t.pos, 8, 3, 0xff); fopAcM_delete(this); } else { diff --git a/src/d/actor/d_a_e_po.cpp b/src/d/actor/d_a_e_po.cpp index f2971f23a3..3e1bcb8073 100644 --- a/src/d/actor/d_a_e_po.cpp +++ b/src/d/actor/d_a_e_po.cpp @@ -1133,21 +1133,24 @@ static void e_po_dead(e_po_class* i_this) { camera_player->mCamera.SetTrimSize(0); dComIfGp_event_reset(); #if TARGET_PC - if (dusk::mods::item_check_poe(i_this->BitSW, dItemNo_POU_SPIRIT_e, a_this) == - dItemNo_POU_SPIRIT_e) - { -#endif - dComIfGs_addPohSpiritNum(); + const auto itemCheck = dusk::mods::item_check_commit( + dusk::mods::item_give_tag_poe(i_this->BitSW), dItemNo_POU_SPIRIT_e, a_this); + daPy_getPlayerActorClass()->cancelOriginalDemo(); + if (itemCheck.itemNo == dItemNo_NONE_e) { + dusk::mods::item_check_complete(itemCheck, a_this); + } else { + dusk::mods::item_check_enqueue(itemCheck, dusk::mods::ItemGiveMode::ForcedDemo); + } +#else + dComIfGs_addPohSpiritNum(); #if !PLATFORM_SHIELD if (dComIfGs_getPohSpiritNum() == 0x14) { /* dSv_event_flag_c::F_0457 - Castle Town - Revived cat */ dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[457]); } -#endif -#if TARGET_PC - } #endif daPy_getPlayerActorClass()->cancelOriginalDemo(); +#endif } else if (mArg0Check(i_this, 0) != 0) { if (!fopAcM_isSwitch(a_this, 0x22)) { if (fopAcM_SearchByID(i_this->field_0x5B8, &local_1b0_actor) != 0 && @@ -1274,10 +1277,16 @@ static void e_po_dead(e_po_class* i_this) { } else { if (i_this->field_0x75C == -1) { #if TARGET_PC - const u8 itemNo = - dusk::mods::item_check_poe(i_this->BitSW, dItemNo_POU_SPIRIT_e, a_this); - i_this->field_0x75C = fopAcM_createItemForPresentDemo(&a_this->current.pos, itemNo, - 0, -1, -1, NULL, NULL, dusk::mods::item_give_tag_poe(i_this->BitSW)); + const auto itemCheck = dusk::mods::item_check_commit( + dusk::mods::item_give_tag_poe(i_this->BitSW), dItemNo_POU_SPIRIT_e, a_this); + if (itemCheck.itemNo == dItemNo_NONE_e) { + dusk::mods::item_check_complete(itemCheck, a_this); + i_this->field_0x75B = 1; + } else { + i_this->field_0x75C = fopAcM_createItemForPresentDemo( + &a_this->current.pos, itemCheck.itemNo, 0, -1, -1, NULL, NULL, + itemCheck.tag); + } #else i_this->field_0x75C = fopAcM_createItemForPresentDemo(&a_this->current.pos, 0xE0, 0, -1, -1, NULL, NULL); diff --git a/src/d/actor/d_a_e_rdb.cpp b/src/d/actor/d_a_e_rdb.cpp index c467f38fd6..0e6fbbdafd 100644 --- a/src/d/actor/d_a_e_rdb.cpp +++ b/src/d/actor/d_a_e_rdb.cpp @@ -16,6 +16,10 @@ #include "f_op/f_op_camera_mng.h" #include +#if TARGET_PC +#include "mods/items.h" +#endif + class daE_RDB_HIO_c : public JORReflexible { public: daE_RDB_HIO_c(); @@ -1250,9 +1254,18 @@ static void demo_camera(e_rdb_class* i_this) { } if (iVar1 != 0) { - daPy_getPlayerActorClass()->changeDemoMode(11, - DUSK_ITEM_CHECK_EXPR("bulblin_key:D_MN09", dItemNo_SMALL_KEY_e, &i_this->enemy), 0, - 0); +#if TARGET_PC + const auto itemCheck = dusk::mods::item_check_commit( + ITEM_CHECK_BULBLIN_KEY, dItemNo_SMALL_KEY_e, &i_this->enemy); + if (itemCheck.itemNo == dItemNo_NONE_e) { + dusk::mods::item_check_complete(itemCheck, &i_this->enemy); + } + daPy_getPlayerActorClass()->changeDemoMode( + 11, itemCheck.itemNo == dItemNo_NONE_e ? 0 : itemCheck.itemNo, + itemCheck.itemNo == dItemNo_NONE_e ? 0 : static_cast(itemCheck.tag), 0); +#else + daPy_getPlayerActorClass()->changeDemoMode(11, dItemNo_SMALL_KEY_e, 0, 0); +#endif i_this->mDemoMode = 12; i_this->field_0x10aa = 0; i_this->field_0xfe5 = 1; diff --git a/src/d/actor/d_a_mg_rod.cpp b/src/d/actor/d_a_mg_rod.cpp index 545b6c3069..6cf1e76024 100644 --- a/src/d/actor/d_a_mg_rod.cpp +++ b/src/d/actor/d_a_mg_rod.cpp @@ -27,8 +27,10 @@ #if TARGET_PC #include "dusk/frame_interpolation.h" +#include "dusk/mods/item.hpp" #include "dusk/settings.h" #include "dusk/version.hpp" +#include "mods/items.h" #endif class dmg_rod_HIO_c : public JORReflexible { @@ -2955,7 +2957,17 @@ static void lure_heart(dmg_rod_class* i_this) { if (obj_life != NULL) { fopAcM_delete(obj_life); fopAcM_onItem(obj_life, 0x80); +#if TARGET_PC + const auto itemCheck = dusk::mods::item_check_commit( + ITEM_CHECK_FISHING_HEART_PIECE, dItemNo_KAKERA_HEART_e, actor); + if (itemCheck.itemNo == dItemNo_KAKERA_HEART_e) { + execItemGet(dItemNo_KAKERA_HEART_e, itemCheck.tag, actor); + } else if (itemCheck.itemNo == dItemNo_NONE_e) { + dusk::mods::item_check_complete(itemCheck, actor); + } +#else execItemGet(dItemNo_KAKERA_HEART_e); +#endif u8 eventReg = dComIfGs_getEventReg(0xECFF); eventReg |= (u8)0x40; dComIfGs_setEventReg(0xECFF, eventReg); @@ -4090,7 +4102,15 @@ static void uki_catch(dmg_rod_class* i_this) { } else if (mgfish->mCaughtType == MG_CATCH_BIN) { i_this->msgflow.init(actor, 0x139A, 0, NULL); dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[468]); +#if TARGET_PC + const auto itemCheck = dusk::mods::item_check_commit( + ITEM_CHECK_FISHING_BOTTLE, dItemNo_EMPTY_BOTTLE_e, actor); + if (itemCheck.itemNo == dItemNo_EMPTY_BOTTLE_e) { + dComIfGs_setEmptyBottle(); + } +#else dComIfGs_setEmptyBottle(); +#endif } else if (mgfish->mCaughtType == MG_CATCH_KN) { i_this->msgflow.init(actor, 0x139C, 0, NULL); } else if (mgfish->mCaughtType == MG_CATCH_ED) { @@ -4167,6 +4187,18 @@ static void uki_catch(dmg_rod_class* i_this) { if (mgfish->mCaughtType == MG_CATCH_LH) { dComIfGp_setItemRupeeCount(10.0f + cM_rndF(40.9f)); } +#if TARGET_PC + else if (mgfish->mCaughtType == MG_CATCH_BIN) + { + const auto itemCheck = dusk::mods::item_check_commit( + ITEM_CHECK_FISHING_BOTTLE, dItemNo_EMPTY_BOTTLE_e, actor); + if (itemCheck.itemNo == dItemNo_EMPTY_BOTTLE_e || + itemCheck.itemNo == dItemNo_NONE_e) + { + dusk::mods::item_check_complete(itemCheck, actor); + } + } +#endif } else { dComIfGs_addFishNum(fish_kind); if (i_this->field_0x14c0 != 0) { diff --git a/src/d/actor/d_a_npc_chin.cpp b/src/d/actor/d_a_npc_chin.cpp index fb91bd79d8..ae629e5d34 100644 --- a/src/d/actor/d_a_npc_chin.cpp +++ b/src/d/actor/d_a_npc_chin.cpp @@ -1806,17 +1806,21 @@ int daNpcChin_c::_Evt_GameSucceed_CutMain(const int& param_0) { #if TARGET_PC const char* itemCheckName = nullptr; + u32 itemGiveTag = 0; if (itemId1 == dItemNo_ARROW_LV2_e) { itemCheckName = "star_reward_1"; } else if (itemId1 == dItemNo_ARROW_LV3_e) { itemCheckName = "star_reward_2"; } if (itemCheckName != nullptr) { - itemId1 = dusk::mods::item_check(itemCheckName, itemId1, this); + const auto itemCheck = + dusk::mods::item_check_commit(itemCheckName, itemId1, this); + itemId1 = itemCheck.itemNo; + itemGiveTag = itemCheck.tag; } #endif fpc_ProcID itemId2 = fopAcM_createItemForPresentDemo(¤t.pos, itemId1, 0, -1, -1, - 0, 0 IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName))); + 0, 0 IF_DUSK_ARG(itemGiveTag)); if (itemId2 != -1) { dComIfGp_event_setItemPartnerId(itemId2); } diff --git a/src/d/actor/d_a_npc_gra.cpp b/src/d/actor/d_a_npc_gra.cpp index 4300348ff9..13f3215fae 100644 --- a/src/d/actor/d_a_npc_gra.cpp +++ b/src/d/actor/d_a_npc_gra.cpp @@ -4019,14 +4019,16 @@ BOOL daNpc_grA_c::talk(void*) { if (r26 && talkProc(NULL, TRUE, NULL)) { if (mFlow.getEventId(&sp8) == 1) { #if TARGET_PC - const char* itemCheckName = nullptr; + u32 itemGiveTag = 0; if (sp8 == dItemNo_BOMB_IN_BAG_e) { - itemCheckName = "goron_reward:F_SP113"; - sp8 = dusk::mods::item_check(itemCheckName, sp8, this); + const auto itemCheck = + dusk::mods::item_check_commit("goron_reward:F_SP113", sp8, this); + sp8 = itemCheck.itemNo; + itemGiveTag = itemCheck.tag; } #endif field_0x1480 = fopAcM_createItemForPresentDemo(¤t.pos, sp8, 0, -1, -1, NULL, - NULL IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName))); + NULL IF_DUSK_ARG(itemGiveTag)); if (field_0x1480 != fpcM_ERROR_PROCESS_ID_e) { s16 r25 = dComIfGp_getEventManager().getEventIdx(this, "DEFAULT_GETITEM", 0xff); dComIfGp_getEvent()->reset(this); diff --git a/src/d/actor/d_a_npc_ins.cpp b/src/d/actor/d_a_npc_ins.cpp index ede3f53664..7dfdec325e 100644 --- a/src/d/actor/d_a_npc_ins.cpp +++ b/src/d/actor/d_a_npc_ins.cpp @@ -1495,9 +1495,11 @@ int daNpcIns_c::talk(void* param_1) { #if TARGET_PC u32 itemGiveTag = 0; if (s_givenInsectId != dItemNo_NONE_e) { - itemNo = dusk::mods::item_check_bug( - s_givenInsectId, itemNo & 0xFF, this); - itemGiveTag = dusk::mods::item_give_tag_bug(s_givenInsectId); + const auto itemCheck = dusk::mods::item_check_commit( + dusk::mods::item_give_tag_bug(s_givenInsectId), itemNo & 0xFF, + this); + itemNo = itemCheck.itemNo; + itemGiveTag = itemCheck.tag; s_givenInsectId = dItemNo_NONE_e; } #endif diff --git a/src/d/actor/d_a_npc_kkri.cpp b/src/d/actor/d_a_npc_kkri.cpp index 4d9c882d03..755257bff7 100644 --- a/src/d/actor/d_a_npc_kkri.cpp +++ b/src/d/actor/d_a_npc_kkri.cpp @@ -1182,15 +1182,17 @@ int daNpc_Kkri_c::talk(void*) { case 1: if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) { #if TARGET_PC - const char* itemCheckName = nullptr; + u32 itemGiveTag = 0; if (item_no == dItemNo_OIL_BOTTLE3_e) { - itemCheckName = "coro_bottle"; - item_no = dusk::mods::item_check(itemCheckName, item_no, this); + const auto itemCheck = + dusk::mods::item_check_commit("coro_bottle", item_no, this); + item_no = itemCheck.itemNo; + itemGiveTag = itemCheck.tag; } #endif mItemPartnerId = fopAcM_createItemForPresentDemo(¤t.pos, item_no, 0, -1, -1, NULL, - NULL IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName))); + NULL IF_DUSK_ARG(itemGiveTag)); } if (fopAcM_IsExecuting(mItemPartnerId)) { diff --git a/src/d/actor/d_a_npc_myna2.cpp b/src/d/actor/d_a_npc_myna2.cpp index faa5301bb8..9b7bc973fc 100644 --- a/src/d/actor/d_a_npc_myna2.cpp +++ b/src/d/actor/d_a_npc_myna2.cpp @@ -1192,14 +1192,16 @@ int daNpc_myna2_c::ECut_gameGoalSuccess(int i_staffId) { int itemNo = 0; if (mFlow.getEventId(&itemNo) == 1) { #if TARGET_PC - const char* itemCheckName = nullptr; + u32 itemGiveTag = 0; if (itemNo == dItemNo_KAKERA_HEART_e) { - itemCheckName = "plumm_minigame_reward"; - itemNo = dusk::mods::item_check(itemCheckName, itemNo, this); + const auto itemCheck = + dusk::mods::item_check_commit("plumm_minigame_reward", itemNo, this); + itemNo = itemCheck.itemNo; + itemGiveTag = itemCheck.tag; } #endif mItemPid = fopAcM_createItemForPresentDemo(¤t.pos, itemNo, 0, -1, -1, NULL, - NULL IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName))); + NULL IF_DUSK_ARG(itemGiveTag)); } break; } diff --git a/src/d/actor/d_a_npc_pouya.cpp b/src/d/actor/d_a_npc_pouya.cpp index 6101b64a93..5b9ad82755 100644 --- a/src/d/actor/d_a_npc_pouya.cpp +++ b/src/d/actor/d_a_npc_pouya.cpp @@ -960,17 +960,21 @@ int daNpc_Pouya_c::cutHaveFavorToAsk(int param_0) { if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) { #if TARGET_PC const char* itemCheckName = nullptr; + u32 itemGiveTag = 0; if (local_64 == dItemNo_DROP_BOTTLE_e) { itemCheckName = "jovani_reward_1"; } else if (local_64 == dItemNo_SILVER_RUPEE_e) { itemCheckName = "jovani_reward_2"; } if (itemCheckName != nullptr) { - local_64 = dusk::mods::item_check(itemCheckName, local_64, this); + const auto itemCheck = + dusk::mods::item_check_commit(itemCheckName, local_64, this); + local_64 = itemCheck.itemNo; + itemGiveTag = itemCheck.tag; } #endif mItemPartnerId = fopAcM_createItemForPresentDemo(¤t.pos, local_64, 0, - -1, -1, 0, 0 IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName))); + -1, -1, 0, 0 IF_DUSK_ARG(itemGiveTag)); } if (fopAcM_IsExecuting(mItemPartnerId)) { field_0xfce = 1; diff --git a/src/d/actor/d_a_npc_zra.inc b/src/d/actor/d_a_npc_zra.inc index 70426d5f99..5cdb380423 100644 --- a/src/d/actor/d_a_npc_zra.inc +++ b/src/d/actor/d_a_npc_zra.inc @@ -2611,17 +2611,21 @@ BOOL daNpc_zrA_c::ECut_thanksBlast(int i_staffID) { if (mFlow.getEventId(&item_id) == 1) { #if TARGET_PC const char* itemCheckName = nullptr; + u32 itemGiveTag = 0; if (item_id == dItemNo_BOMB_IN_BAG_e) { itemCheckName = "iza_reward_1"; } else if (item_id == dItemNo_BOMB_BAG_LV2_e) { itemCheckName = "iza_reward_2"; } if (itemCheckName != nullptr) { - item_id = dusk::mods::item_check(itemCheckName, item_id, this); + const auto itemCheck = + dusk::mods::item_check_commit(itemCheckName, item_id, this); + item_id = itemCheck.itemNo; + itemGiveTag = itemCheck.tag; } #endif mItemID = fopAcM_createItemForPresentDemo(¤t.pos, item_id, 0, -1, -1, NULL, - NULL IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName))); + NULL IF_DUSK_ARG(itemGiveTag)); } field_0x9eb = true; break; diff --git a/src/d/actor/d_a_obj_bosswarp.cpp b/src/d/actor/d_a_obj_bosswarp.cpp index e286ce1640..5bf510d04a 100644 --- a/src/d/actor/d_a_obj_bosswarp.cpp +++ b/src/d/actor/d_a_obj_bosswarp.cpp @@ -14,6 +14,11 @@ #include "d/actor/d_a_obj_ystone.h" #include +#if TARGET_PC +#include "dusk/mods/item.hpp" +#include "mods/items.h" +#endif + static DUSK_CONST char* l_arcName = "ef_Portal"; static char const* l_clearEvName[9] = { @@ -77,6 +82,27 @@ static int getNowLevel() { return -1; } +#if TARGET_PC +static const char* dungeon_reward_check_name(int level) { + switch (level) { + case 0: + return ITEM_CHECK_DUNGEON_REWARD_FOREST; + case 1: + return ITEM_CHECK_DUNGEON_REWARD_GORON; + case 2: + return ITEM_CHECK_DUNGEON_REWARD_LAKEBED; + case 4: + return ITEM_CHECK_DUNGEON_REWARD_SNOWPEAK; + case 5: + return ITEM_CHECK_DUNGEON_REWARD_TIME; + case 6: + return ITEM_CHECK_DUNGEON_REWARD_CITY; + default: + return nullptr; + } +} +#endif + void daObjBossWarp_c::initBaseMtx() { scale.y = 0.15f; mpModel->setBaseScale(scale); @@ -485,6 +511,9 @@ int daObjBossWarp_c::demoProc() { player->onSceneChangeArea(scene, 0xff, 0); break; case 4: // STONE_FALL +#if TARGET_PC + { +#endif if (ystone != NULL) { mYstonePos = ystone->current.pos; if (getNowLevel() < 3) { @@ -506,28 +535,52 @@ int daObjBossWarp_c::demoProc() { mDoMtx_stack_c::multVec(&mYstoneTargetPos, &mYstoneTargetPos); mYstonePos.x = mYstoneTargetPos.x; mYstonePos.z = mYstoneTargetPos.z; - switch (getNowLevel()) { - case 0: - dComIfGs_onCollectCrystal(0); - break; - case 1: - dComIfGs_onCollectCrystal(1); - break; - case 2: - dComIfGs_onCollectCrystal(2); - break; - case 4: - dComIfGs_onCollectMirror(1); - break; - case 5: - dComIfGs_onCollectMirror(2); - break; - case 6: - dComIfGs_onCollectMirror(3); - break; +#if TARGET_PC + int level = getNowLevel(); + const char* checkName = dungeon_reward_check_name(level); + dusk::mods::ItemCheckResult itemCheck{}; + bool applyVanillaReward = true; + if (checkName != nullptr) { + itemCheck = dusk::mods::item_check_commit(checkName, dItemNo_NONE_e, this); + applyVanillaReward = itemCheck.itemNo == dItemNo_NONE_e; } + if (applyVanillaReward) { + switch (level) { +#else + switch (getNowLevel()) { +#endif + case 0: + dComIfGs_onCollectCrystal(0); + break; + case 1: + dComIfGs_onCollectCrystal(1); + break; + case 2: + dComIfGs_onCollectCrystal(2); + break; + case 4: + dComIfGs_onCollectMirror(1); + break; + case 5: + dComIfGs_onCollectMirror(2); + break; + case 6: + dComIfGs_onCollectMirror(3); + break; + } +#if TARGET_PC + if (checkName != nullptr) { + dusk::mods::item_check_complete(itemCheck, this); + } + } else { + dusk::mods::item_check_enqueue(itemCheck, dusk::mods::ItemGiveMode::Silent); + } +#endif mCounter = 0; break; +#if TARGET_PC + } +#endif case 5: // STONE_MIDNA mCounter = 0; break; diff --git a/src/d/actor/d_a_obj_item.cpp b/src/d/actor/d_a_obj_item.cpp index 1176f9aab3..a67e780286 100644 --- a/src/d/actor/d_a_obj_item.cpp +++ b/src/d/actor/d_a_obj_item.cpp @@ -287,7 +287,11 @@ int daItem_c::_daItem_create() { BOOL flag = dItem_data::chkFlag(m_itemNo, 2); #if DEBUG +#if TARGET_PC + if (dItem_fieldModelArc(m_itemNo) == NULL) { +#else if (dItem_data::getFieldArc(m_itemNo) == NULL) { +#endif // "Item Num<%d>: No Resource Archive specified!!\n" OS_REPORT_ERROR("アイテム番号<%d>:リソースのアーカイブ指定がありません!!\n", m_itemNo); return cPhs_ERROR_e; @@ -319,6 +323,20 @@ int daItem_c::_daItem_create() { if (flag) { CreateInit(); } else { +#if TARGET_PC + // Resolved items may have no field model; display their get-demo model instead. + if (dItem_data::getFieldArc(m_itemNo) == NULL) { + phase_state = dComIfG_resLoad(&mPhase, dItem_data::getArcName(m_itemNo)); + if (phase_state == cPhs_COMPLEATE_e) { + if (!fopAcM_entrySolidHeap(this, CheckItemCreateHeap, 0x4000)) { + return cPhs_ERROR_e; + } + + CreateInit(); + } + return phase_state; + } +#endif phase_state = dComIfG_resLoad(&mPhase, dItem_data::getFieldArc(m_itemNo)); if (phase_state == cPhs_COMPLEATE_e) { if (!fopAcM_entrySolidHeap(this, CheckFieldItemCreateHeap, @@ -412,7 +430,11 @@ int daItem_c::_daItem_delete() { mSparkleEmtr.remove(); } +#if TARGET_PC + DeleteBase(dItem_fieldModelArc(m_itemNo)); +#else DeleteBase(dItem_data::getFieldArc(m_itemNo)); +#endif return 1; } @@ -520,9 +542,10 @@ void daItem_c::procInitGetDemoEvent() { #if TARGET_PC const u8 displayItemNo = m_itemNo; - if (mItemOverridden) { - m_itemNo = dusk::mods::item_check_tagged(mItemGiveTag, mOriginalItemNo, this); - } + const auto itemCheck = dusk::mods::item_check_commit(mItemGiveTag, mOriginalItemNo, this); + m_itemNo = itemCheck.itemNo; + mItemGiveTag = itemCheck.tag; + mItemOverridden = m_itemNo != mOriginalItemNo; #endif m_item_id = fopAcM_createItemForTrBoxDemo( ¤t.pos, m_itemNo, -1, fopAcM_GetRoomNo(this), NULL, NULL IF_DUSK_ARG(mItemGiveTag)); @@ -907,9 +930,10 @@ void daItem_c::itemGetNextExecute() { void daItem_c::itemGet() { #if TARGET_PC const u8 displayItemNo = m_itemNo; - if (mItemOverridden) { - m_itemNo = dusk::mods::item_check_tagged(mItemGiveTag, mOriginalItemNo, this); - } + const auto itemCheck = dusk::mods::item_check_commit(mItemGiveTag, mOriginalItemNo, this); + m_itemNo = itemCheck.itemNo; + mItemGiveTag = itemCheck.tag; + mItemOverridden = m_itemNo != mOriginalItemNo; #endif switch (m_itemNo) { #if TARGET_PC diff --git a/src/d/actor/d_a_obj_life_container.cpp b/src/d/actor/d_a_obj_life_container.cpp index 3c800e946f..cd58c828dc 100644 --- a/src/d/actor/d_a_obj_life_container.cpp +++ b/src/d/actor/d_a_obj_life_container.cpp @@ -6,6 +6,7 @@ #include "d/dolzel_rel.h" // IWYU pragma: keep #include "d/actor/d_a_obj_life_container.h" +#include "d/d_a_itembase_static.h" #include "d/d_com_inf_game.h" #include "d/actor/d_a_player.h" #include "d/d_item_data.h" @@ -184,11 +185,23 @@ int daObjLife_c::create() { return cPhs_ERROR_e; } +#if TARGET_PC + // Resolved items may have no field model; display their get-demo model instead. + const bool useGetModel = dItem_data::getFieldArc(m_itemNo) == NULL; + int phase_state = dComIfG_resLoad(&mPhase, dItem_fieldModelArc(m_itemNo)); + if (phase_state == cPhs_COMPLEATE_e) { + if (!fopAcM_entrySolidHeap( + this, useGetModel ? CheckItemCreateHeap : CheckFieldItemCreateHeap, 0x4000)) + { + return cPhs_ERROR_e; + } +#else int phase_state = dComIfG_resLoad(&mPhase, dItem_data::getFieldArc(m_itemNo)); if (phase_state == cPhs_COMPLEATE_e) { if (!fopAcM_entrySolidHeap(this, CheckFieldItemCreateHeap, 0x4000)) { return cPhs_ERROR_e; } +#endif if (!Create()) { return cPhs_ERROR_e; @@ -325,9 +338,10 @@ int daObjLife_c::initActionOrderGetDemo() { #if TARGET_PC const u8 displayItemNo = m_itemNo; - if (mItemOverridden) { - m_itemNo = dusk::mods::item_check_tagged(mItemGiveTag, mOriginalItemNo, this); - } + const auto itemCheck = dusk::mods::item_check_commit(mItemGiveTag, mOriginalItemNo, this); + m_itemNo = itemCheck.itemNo; + mItemGiveTag = itemCheck.tag; + mItemOverridden = m_itemNo != mOriginalItemNo; #endif mItemId = fopAcM_createItemForTrBoxDemo( ¤t.pos, m_itemNo, -1, fopAcM_GetRoomNo(this), NULL, NULL IF_DUSK_ARG(mItemGiveTag)); @@ -519,7 +533,11 @@ int daObjLife_c::_delete() { endEffect00(); endEffect02(); +#if TARGET_PC + DeleteBase(dItem_fieldModelArc(m_itemNo)); +#else DeleteBase(dItem_data::getFieldArc(m_itemNo)); +#endif return 1; } diff --git a/src/d/actor/d_a_obj_master_sword.cpp b/src/d/actor/d_a_obj_master_sword.cpp index 820f603363..36fa786808 100644 --- a/src/d/actor/d_a_obj_master_sword.cpp +++ b/src/d/actor/d_a_obj_master_sword.cpp @@ -1,6 +1,6 @@ /** * @file d_a_obj_master_sword.cpp - * + * */ #include "d/dolzel_rel.h" // IWYU pragma: keep @@ -10,6 +10,12 @@ #include "d/d_com_inf_game.h" #include "d/d_meter2_info.h" +#if TARGET_PC +#include "d/d_item.h" +#include "dusk/mods/item.hpp" +#include "mods/items.h" +#endif + DUSK_GAME_DATA daObjMasterSword_Attr_c const daObjMasterSword_c::mAttr = {1.0f}; void daObjMasterSword_c::initBaseMtx() { @@ -145,7 +151,11 @@ void daObjMasterSword_c::create_init() { int daObjMasterSword_c::create() { fopAcM_ct(this, daObjMasterSword_c); +#if TARGET_PC + if (dComIfGs_isEventBit(dSv_event_flag_c::F_0264)) { +#else if (dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[getFlagNo()])) { +#endif return cPhs_ERROR_e; } @@ -186,12 +196,41 @@ int daObjMasterSword_c::execute() { mBrk.play(); if (dComIfGs_isTmpBit(dSv_event_tmp_flag_c::tempBitLabels[73])) { +#if TARGET_PC + const auto masterSword = dusk::mods::item_check_commit( + ITEM_CHECK_MASTER_SWORD, dItemNo_MASTER_SWORD_e, this); + if (masterSword.itemNo == dItemNo_MASTER_SWORD_e) { + dComIfGs_onItemFirstBit(dItemNo_MASTER_SWORD_e); + dMeter2Info_setSword(dItemNo_MASTER_SWORD_e, false); + dComIfGs_setSelectEquipSword(dItemNo_MASTER_SWORD_e); + dusk::mods::item_check_complete(masterSword, this); + } else if (masterSword.itemNo == dItemNo_NONE_e) { + dusk::mods::item_check_complete(masterSword, this); + } else { + dusk::mods::item_check_enqueue(masterSword, dusk::mods::ItemGiveMode::Demo); + } + + dComIfGp_setItemLifeCount(dComIfGs_getMaxLife(), 0); + + const auto shadowCrystal = dusk::mods::item_check_commit( + ITEM_CHECK_SHADOW_CRYSTAL, dItemNo_SHADOW_CRYSTAL_e, this); + if (shadowCrystal.itemNo == dItemNo_SHADOW_CRYSTAL_e) { + execItemGet(shadowCrystal.itemNo, shadowCrystal.tag, this); + } else if (shadowCrystal.itemNo == dItemNo_NONE_e) { + dusk::mods::item_check_complete(shadowCrystal, this); + } else { + dusk::mods::item_check_enqueue(shadowCrystal, dusk::mods::ItemGiveMode::Demo); + } + + dComIfGs_onEventBit(dSv_event_flag_c::F_0264); +#else dComIfGs_onItemFirstBit(dItemNo_MASTER_SWORD_e); dMeter2Info_setSword(dItemNo_MASTER_SWORD_e, false); dComIfGs_setSelectEquipSword(dItemNo_MASTER_SWORD_e); dComIfGp_setItemLifeCount(dComIfGs_getMaxLife(), 0); dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[getFlagNo()]); +#endif fopAcM_delete(this); } diff --git a/src/d/actor/d_a_obj_shield.cpp b/src/d/actor/d_a_obj_shield.cpp index abe56ad6c8..5cc63c9a89 100644 --- a/src/d/actor/d_a_obj_shield.cpp +++ b/src/d/actor/d_a_obj_shield.cpp @@ -109,7 +109,7 @@ int daItemShield_c::__CreateHeap() { int daItemShield_c::create() { fopAcM_ct(this, daItemShield_c); m_itemNo = dItemNo_WOOD_SHIELD_e; - DUSK_ITEM_CHECK("ordon_shield", m_itemNo, this); + DUSK_ITEM_CHECK_PREVIEW("ordon_shield", m_itemNo, this); if (fopAcM_isSwitch(this, getSwBit2())) { OS_REPORT("木の盾:もう取ったので出ません\n"); return cPhs_ERROR_e; diff --git a/src/d/actor/d_a_obj_sword.cpp b/src/d/actor/d_a_obj_sword.cpp index d7bf0cfaab..f87d078512 100644 --- a/src/d/actor/d_a_obj_sword.cpp +++ b/src/d/actor/d_a_obj_sword.cpp @@ -37,7 +37,7 @@ int daObjSword_c::Create() { cPhs_Step daObjSword_c::create() { fopAcM_ct(this, daObjSword_c); m_itemNo = 0x28; - DUSK_ITEM_CHECK("ordon_sword", m_itemNo, this); + DUSK_ITEM_CHECK_PREVIEW("ordon_sword", m_itemNo, this); if (fopAcM_isItem(this, getItemBit())) { return cPhs_ERROR_e; } diff --git a/src/d/actor/d_a_obj_zcloth.cpp b/src/d/actor/d_a_obj_zcloth.cpp index 7d9ca8fc51..74a957fa8d 100644 --- a/src/d/actor/d_a_obj_zcloth.cpp +++ b/src/d/actor/d_a_obj_zcloth.cpp @@ -32,7 +32,7 @@ int daObjZCloth_c::Create() { int daObjZCloth_c::create() { fopAcM_ct(this, daObjZCloth_c); m_itemNo = 0x31; - DUSK_ITEM_CHECK("zora_armor", m_itemNo, this); + DUSK_ITEM_CHECK_PREVIEW("zora_armor", m_itemNo, this); int phase = dComIfG_resLoad(&mPhase, dItem_data::getFieldArc(m_itemNo)); if (phase == cPhs_COMPLEATE_e) { if (!fopAcM_entrySolidHeap(this, (heapCallbackFunc)CheckFieldItemCreateHeap, 0x2fb0)) { diff --git a/src/d/actor/d_a_tag_statue_evt.cpp b/src/d/actor/d_a_tag_statue_evt.cpp index de5666aef9..cdd0db19ad 100644 --- a/src/d/actor/d_a_tag_statue_evt.cpp +++ b/src/d/actor/d_a_tag_statue_evt.cpp @@ -363,10 +363,13 @@ int daTagStatue_c::demoProc() { } #if TARGET_PC - item = dusk::mods::item_check_sky_character(item & 0xFF, this); + const auto itemCheck = dusk::mods::item_check_commit( + dusk::mods::item_give_tag_sky_character(), item & 0xFF, this); + item = itemCheck.itemNo; #endif - mItemId = fopAcM_createItemForTrBoxDemo(¤t.pos, item, -1, - fopAcM_GetRoomNo(this), 0, 0 IF_DUSK_ARG(dusk::mods::item_give_tag_sky_character())); + mItemId = + fopAcM_createItemForTrBoxDemo(¤t.pos, item, -1, fopAcM_GetRoomNo(this), 0, + 0 IF_DUSK_ARG(itemCheck.tag)); JUT_ASSERT(580, mItemId != fpcM_ERROR_PROCESS_ID_e); diff --git a/src/d/actor/d_a_tbox.cpp b/src/d/actor/d_a_tbox.cpp index c77478da91..f8048478ee 100644 --- a/src/d/actor/d_a_tbox.cpp +++ b/src/d/actor/d_a_tbox.cpp @@ -1391,8 +1391,9 @@ u8 daTbox_c::getBombItemNoMain(u8 i_itemNo) { int daTbox_c::setGetDemoItem() { u8 item_no = getItemNo(); #if TARGET_PC - const u32 giveTag = dusk::mods::item_give_tag_chest(getTboxNo()); - item_no = dusk::mods::item_check_tagged(giveTag, mOriginalItemNo, this); + const auto itemCheck = dusk::mods::item_check_commit( + dusk::mods::item_give_tag_chest(getTboxNo()), mOriginalItemNo, this); + item_no = itemCheck.itemNo; #endif if (item_no == dItemNo_BOMB_5_e || item_no == dItemNo_BOMB_10_e || item_no == dItemNo_BOMB_20_e || item_no == dItemNo_BOMB_30_e || item_no == dItemNo_WATER_BOMB_5_e || item_no == dItemNo_WATER_BOMB_10_e || item_no == dItemNo_WATER_BOMB_20_e || item_no == dItemNo_WATER_BOMB_30_e || @@ -1404,10 +1405,10 @@ int daTbox_c::setGetDemoItem() { fpc_ProcID item_id; if (field_0x718) { item_id = fopAcM_createItemForPresentDemo( - ¤t.pos, item_no, 1, -1, -1, NULL, NULL IF_DUSK_ARG(giveTag)); + ¤t.pos, item_no, 1, -1, -1, NULL, NULL IF_DUSK_ARG(itemCheck.tag)); } else { item_id = fopAcM_createItemForTrBoxDemo( - ¤t.pos, item_no, -1, -1, NULL, NULL IF_DUSK_ARG(giveTag)); + ¤t.pos, item_no, -1, -1, NULL, NULL IF_DUSK_ARG(itemCheck.tag)); } if (item_id != fpcM_ERROR_PROCESS_ID_e) { diff --git a/src/d/actor/d_a_tbox2.cpp b/src/d/actor/d_a_tbox2.cpp index 54c3e3135e..2d35e8285f 100644 --- a/src/d/actor/d_a_tbox2.cpp +++ b/src/d/actor/d_a_tbox2.cpp @@ -383,15 +383,18 @@ int daTbox2_c::setGetDemoItem() { u8 item_no = getItemNo(); #if TARGET_PC int tboxNo = fopAcM_GetParamBit(this, 16, 8); - const u32 giveTag = dusk::mods::item_give_tag_chest(tboxNo); - item_no = dusk::mods::item_check_tagged(giveTag, mOriginalItemNo, this); + const auto itemCheck = dusk::mods::item_check_commit( + dusk::mods::item_give_tag_chest(tboxNo), mOriginalItemNo, this); + item_no = itemCheck.itemNo; #endif u32 partner_id; if (mReturnRupee) { - partner_id = fopAcM_createItemForPresentDemo(¤t.pos, item_no, 1, -1, -1, NULL, NULL IF_DUSK_ARG(giveTag)); + partner_id = fopAcM_createItemForPresentDemo( + ¤t.pos, item_no, 1, -1, -1, NULL, NULL IF_DUSK_ARG(itemCheck.tag)); } else { - partner_id = fopAcM_createItemForTrBoxDemo(¤t.pos, item_no, -1, -1, NULL, NULL IF_DUSK_ARG(giveTag)); + partner_id = fopAcM_createItemForTrBoxDemo( + ¤t.pos, item_no, -1, -1, NULL, NULL IF_DUSK_ARG(itemCheck.tag)); } if (partner_id != -1) { diff --git a/src/d/d_a_itembase_static.cpp b/src/d/d_a_itembase_static.cpp index 5e98a20409..9bdd933f5f 100644 --- a/src/d/d_a_itembase_static.cpp +++ b/src/d/d_a_itembase_static.cpp @@ -57,6 +57,13 @@ int CheckItemCreateHeap(fopAc_ac_c* i_this) { dItem_data::getBrkName(item_no), dItem_data::getBtpName(item_no)); } +#if TARGET_PC +const char* dItem_fieldModelArc(u8 itemNo) { + const char* arcName = dItem_data::getFieldArc(itemNo); + return arcName != NULL ? arcName : dItem_data::getArcName(itemNo); +} +#endif + int CheckFieldItemCreateHeap(fopAc_ac_c* i_this) { daItemBase_c* a_this = static_cast(i_this); diff --git a/src/d/d_item.cpp b/src/d/d_item.cpp index 5484bd584a..a388ef155c 100644 --- a/src/d/d_item.cpp +++ b/src/d/d_item.cpp @@ -741,8 +741,12 @@ void item_func_ARMOR() {} void item_func_WEAR_ZORA() {} void item_func_MAGIC_LV1() { +#if TARGET_PC + dComIfGs_onEventBit(0xD04); +#else dComIfGp_setItemMagicCount(16); dComIfGp_setItemMaxMagicCount(16); +#endif } void item_func_DUNGEON_EXIT_2() { @@ -1152,7 +1156,15 @@ void item_func_M_MAYFLY() {} void item_func_F_MAYFLY() {} -void item_func_POU_SPIRIT() {} +void item_func_POU_SPIRIT() { +#if TARGET_PC + dComIfGs_addPohSpiritNum(); + if (dComIfGs_getPohSpiritNum() == 20) { + /* dSv_event_flag_c::F_0457 - Castle Town - Revived cat */ + dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[457]); + } +#endif +} void item_func_ANCIENT_DOCUMENT() { dComIfGs_setItem(SLOT_22, dItemNo_ANCIENT_DOCUMENT_e); @@ -1411,7 +1423,11 @@ int item_getcheck_func_WEAR_ZORA() { } int item_getcheck_func_MAGIC_LV1() { +#if TARGET_PC + return dComIfGs_isEventBit(0xD04); +#else return -1; +#endif } int item_getcheck_func_DUNGEON_EXIT_2() { diff --git a/src/d/d_item_data.cpp b/src/d/d_item_data.cpp index 9ff4321aa5..a9aaab12d3 100644 --- a/src/d/d_item_data.cpp +++ b/src/d/d_item_data.cpp @@ -53,7 +53,11 @@ DUSK_GAME_DATA dItem_itemResource dItem_data::item_resource[] = { {"F_gD_rupy", 0x0004,-0x0001,-0x0001, 0x0007,-0x0001, 0x0, -0x1, 0x006B, 0x64, 0x0000}, {"O_gD_marm", 0x0003,-0x0001,-0x0001,-0x0001,-0x0001, -0x1, -0x1, 0x0041, 0x64, 0x01A9}, {"O_gD_zora", 0x0003,-0x0001,-0x0001,-0x0001,-0x0001, -0x1, -0x1, 0x004D, 0x64, 0x01AA}, +#if TARGET_PC + {"O_gD_Injy", 0x0003,-0x0001,-0x0001,-0x0001,-0x0001, -0x1, -0x1, 0x002E, 0x64, 0x0000}, +#else {"O_gD_Injy", 0x0003,-0x0001,-0x0001,-0x0001,-0x0001, -0x1, -0x1, 0x002D, 0x64, 0x0000}, +#endif {"O_gD_TKS", 0x0008,-0x0001, 0x0005,-0x0001,-0x0001, -0x1, -0x1, 0x0028, 0x64, 0x0000}, {"O_gD_puL2", 0x0003,-0x0001,-0x0001,-0x0001,-0x0001, -0x1, -0x1, 0x0047, 0x64, 0x0000}, {"O_gD_puL2", 0x0003,-0x0001,-0x0001,-0x0001,-0x0001, -0x1, -0x1, 0x0048, 0x64, 0x0000}, diff --git a/src/d/d_msg_object.cpp b/src/d/d_msg_object.cpp index 3dbaa02dec..e9e62ce635 100644 --- a/src/d/d_msg_object.cpp +++ b/src/d/d_msg_object.cpp @@ -36,6 +36,7 @@ #include "dusk/language.hpp" #include "dusk/logging.h" #include "dusk/menu_pointer.h" +#include "dusk/mods/item.hpp" #include "dusk/mods/svc/flow.hpp" #include "dusk/settings.h" #include "dusk/version.hpp" @@ -832,6 +833,9 @@ u32 dMsgObject_c::getMessageIndex(u32 param_0) { u32 dMsgObject_c::getRevoMessageIndex(u32 param_1) { #if TARGET_PC + const u16 sourceGroup = param_1 > 5000 ? static_cast(s_groupID) : 0; + param_1 = dusk::mods::item_check_message(sourceGroup, param_1); + u16 customGroup = 0; if (param_1 <= 0xffff && dusk::flow::custom_message_group(static_cast(param_1), customGroup)) diff --git a/src/d/d_s_play.cpp b/src/d/d_s_play.cpp index ab82ca1b9b..433e9065b1 100644 --- a/src/d/d_s_play.cpp +++ b/src/d/d_s_play.cpp @@ -42,7 +42,9 @@ #if TARGET_PC #include "dusk/autosave.h" #include "dusk/memory.h" +#include "dusk/mods/item.hpp" #include "dusk/ui/ui.hpp" +#include "mods/items.h" #endif #if DEBUG @@ -1192,11 +1194,35 @@ static int phase_1(dScnPly_c* i_this) { } // Stage: Ordon Spring, Room: Ordon Spring +#if TARGET_PC + const bool iliaMemoryArrival = + !strcmp(dComIfGp_getStartStageName(), "F_SP104") && dComIfGp_getStartStageRoomNo() == 1 && + dComIfGp_getStartStagePoint() == 23 && dComIfGp_getStartStageLayer() == 12; + const u32 iliaMemoryTag = dusk::mods::item_give_tag(ITEM_CHECK_ILIA_MEMORY); + if (!iliaMemoryArrival) { + dusk::mods::item_check_cancel(iliaMemoryTag); + } + if (iliaMemoryArrival) +#else if (!strcmp(dComIfGp_getStartStageName(), "F_SP104") && dComIfGp_getStartStageRoomNo() == 1 && dComIfGp_getStartStagePoint() == 23 && dComIfGp_getStartStageLayer() == 12) +#endif { - dComIfGs_onItemFirstBit(dItemNo_HORSE_FLUTE_e); - dComIfGs_setItem(SLOT_21, dItemNo_HORSE_FLUTE_e); +#if TARGET_PC + const auto itemCheck = + dusk::mods::item_check_commit(iliaMemoryTag, dItemNo_HORSE_FLUTE_e, nullptr); + if (itemCheck.itemNo == dItemNo_HORSE_FLUTE_e) { +#endif + dComIfGs_onItemFirstBit(dItemNo_HORSE_FLUTE_e); + dComIfGs_setItem(SLOT_21, dItemNo_HORSE_FLUTE_e); +#if TARGET_PC + dusk::mods::item_check_complete(itemCheck, nullptr); + } else if (itemCheck.itemNo == dItemNo_NONE_e) { + dusk::mods::item_check_complete(itemCheck, nullptr); + } else { + dusk::mods::item_check_enqueue(itemCheck, dusk::mods::ItemGiveMode::Silent); + } +#endif } if ((u8)dKy_darkworld_stage_check(dComIfGp_getStartStageName(), diff --git a/src/d/d_shop_system.cpp b/src/d/d_shop_system.cpp index bf2d3650af..1d7fed2406 100644 --- a/src/d/d_shop_system.cpp +++ b/src/d/d_shop_system.cpp @@ -906,15 +906,17 @@ int dShopSystem_c::seq_start(fopAc_ac_c* actor, dMsgFlow_c* i_flow) { if (mFlow.getEventId(&itemNo) == 1) { if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) { #if TARGET_PC - const char* itemCheckName = nullptr; + u32 itemGiveTag = 0; if (itemNo == dItemNo_HALF_MILK_BOTTLE_e) { - itemCheckName = "sera_reward"; - itemNo = dusk::mods::item_check(itemCheckName, itemNo, actor); + const auto itemCheck = + dusk::mods::item_check_commit("sera_reward", itemNo, actor); + itemNo = itemCheck.itemNo; + itemGiveTag = itemCheck.tag; } #endif mItemPartnerId = fopAcM_createItemForPresentDemo(¤t.pos, itemNo, 0, -1, -1, NULL, - NULL IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName))); + NULL IF_DUSK_ARG(itemGiveTag)); } if (fpcEx_IsExist(mItemPartnerId)) { @@ -1205,7 +1207,9 @@ int dShopSystem_c::seq_decide_yes(fopAc_ac_c* actor, dMsgFlow_c* i_flow) { if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) { #if TARGET_PC const u32 itemGiveTag = dusk::mods::item_give_tag_shop(itemNo & 0xFF); - itemNo = dusk::mods::item_check_shop(itemNo & 0xFF, actor); + const auto itemCheck = + dusk::mods::item_check_commit(itemGiveTag, itemNo & 0xFF, actor); + itemNo = itemCheck.itemNo; #endif mItemPartnerId = fopAcM_createItemForPresentDemo( ¤t.pos, itemNo, 0, -1, -1, NULL, NULL IF_DUSK_ARG(itemGiveTag)); diff --git a/src/dusk/imgui/ImGuiSaveEditor.cpp b/src/dusk/imgui/ImGuiSaveEditor.cpp index ec2802de44..244b26e200 100644 --- a/src/dusk/imgui/ImGuiSaveEditor.cpp +++ b/src/dusk/imgui/ImGuiSaveEditor.cpp @@ -76,7 +76,7 @@ namespace dusk { { dItemNo_WEAR_KOKIRI_e, {"Hero's Clothes"} }, { dItemNo_ARMOR_e, {"Magic Armor"} }, { dItemNo_WEAR_ZORA_e, {"Zora Armor"} }, - { dItemNo_MAGIC_LV1_e, {"Magic Level 1"} }, + { dItemNo_SHADOW_CRYSTAL_e, {"Shadow Crystal"} }, { dItemNo_DUNGEON_EXIT_2_e, {"Ooccoo Sr.", ITEMTYPE_EQUIP_e} }, { dItemNo_WALLET_LV1_e, {"Wallet"} }, { dItemNo_WALLET_LV2_e, {"Big Wallet"} }, diff --git a/src/dusk/mods/item.hpp b/src/dusk/mods/item.hpp index ab98b899e3..5577358f38 100644 --- a/src/dusk/mods/item.hpp +++ b/src/dusk/mods/item.hpp @@ -1,21 +1,31 @@ #pragma once #include +#include class fopAc_ac_c; namespace dusk::mods { +enum class ItemGiveMode : uint8_t { + Demo, + Silent, + ForcedDemo, +}; + +struct ItemCheckResult { + uint32_t tag = 0; + uint8_t itemNo = 0; +}; + uint8_t item_check(const char* name, uint8_t itemNo, fopAc_ac_c* giver); -uint8_t item_check_tagged(uint32_t giveTag, uint8_t itemNo, fopAc_ac_c* giver); +ItemCheckResult item_check_commit(const char* name, uint8_t itemNo, fopAc_ac_c* giver); +ItemCheckResult item_check_commit(uint32_t giveTag, uint8_t itemNo, fopAc_ac_c* giver); uint8_t item_check_chest(uint8_t boxNo, uint8_t itemNo, fopAc_ac_c* chest); uint8_t item_check_boss(uint8_t itemNo, fopAc_ac_c* boss); uint8_t item_check_freestanding(uint8_t bitNo, uint8_t itemNo, fopAc_ac_c* item); -uint8_t item_check_poe(uint8_t bitNo, uint8_t itemNo, fopAc_ac_c* poe); uint8_t item_check_shop(uint8_t itemNo, fopAc_ac_c* giver); -uint8_t item_check_bug(uint8_t insectId, uint8_t itemNo, fopAc_ac_c* agitha); -uint8_t item_check_sky_character(uint8_t itemNo, fopAc_ac_c* statue); uint32_t item_give_tag(const char* name); uint32_t item_give_tag_chest(uint8_t boxNo); @@ -26,12 +36,63 @@ uint32_t item_give_tag_shop(uint8_t itemNo); uint32_t item_give_tag_bug(uint8_t insectId); uint32_t item_give_tag_sky_character(); -void item_check_enqueue(const char* name, uint8_t itemNo); -void item_check_enqueue_poe(uint8_t bitNo, uint8_t itemNo); +void item_check_enqueue_deferred(const char* name, uint8_t itemNo); +bool item_check_enqueue(ItemCheckResult check, ItemGiveMode mode); +void item_check_complete(ItemCheckResult check, fopAc_ac_c* giver); +void item_check_cancel(uint32_t giveTag); +void item_check_clear_committed(); void item_granted(uint8_t itemNo, uint32_t giveTag, fopAc_ac_c* giver); +uint32_t item_check_message(uint16_t group, uint32_t messageId); + bool item_give_queue_dispatching(); uint32_t item_give_queue_take_tag(); +namespace detail { +struct CommittedCheck { + uint8_t vanillaItem = 0; + uint8_t resolvedItem = 0; + bool queued = false; +}; + +class ItemCommitStore { +public: + CommittedCheck* find(uint32_t giveTag) { + const auto it = mChecks.find(giveTag); + return it != mChecks.end() ? &it->second : nullptr; + } + + const CommittedCheck* find(uint32_t giveTag) const { + const auto it = mChecks.find(giveTag); + return it != mChecks.end() ? &it->second : nullptr; + } + + template + CommittedCheck& commit(uint32_t giveTag, uint8_t vanillaItem, ResolveFn&& resolve) { + const auto existing = mChecks.find(giveTag); + if (existing != mChecks.end()) { + return existing->second; + } + + const uint8_t resolvedItem = std::forward(resolve)(); + return mChecks + .emplace( + giveTag, CommittedCheck{.vanillaItem = vanillaItem, .resolvedItem = resolvedItem}) + .first->second; + } + + bool contains(uint32_t giveTag) const { return mChecks.contains(giveTag); } + + void erase(uint32_t giveTag) { mChecks.erase(giveTag); } + + void clear() { mChecks.clear(); } + + bool empty() const { return mChecks.empty(); } + +private: + std::unordered_map mChecks; +}; +} // namespace detail + } // namespace dusk::mods diff --git a/src/dusk/mods/item_checks.cpp b/src/dusk/mods/item_checks.cpp index 79a6c993b9..1684310c42 100644 --- a/src/dusk/mods/item_checks.cpp +++ b/src/dusk/mods/item_checks.cpp @@ -6,9 +6,14 @@ #include "aurora/lib/logging.hpp" #include "d/d_com_inf_game.h" +#include "d/d_item_data.h" +#include "mods/items.h" #include +#include +#include +#include #include #include #include @@ -45,6 +50,78 @@ struct PendingResolve { void* userData = nullptr; }; +struct MessageCheck { + uint16_t group = 0; + uint16_t messageId = 0; + const char* name = nullptr; + uint8_t vanillaItem = 0; + bool enqueueAtDisplay = true; +}; + +constexpr std::array kMessageChecks{ + MessageCheck{ + .group = 0, + .messageId = 1822, + .name = ITEM_CHECK_FISHING_BOTTLE, + .vanillaItem = dItemNo_EMPTY_BOTTLE_e, + }, + MessageCheck{ + .group = 7, + .messageId = 7564, + .name = ITEM_CHECK_FISHING_HEART_PIECE, + .vanillaItem = dItemNo_KAKERA_HEART_e, + }, + MessageCheck{ + .group = 7, + .messageId = 7578, + .name = ITEM_CHECK_FISHING_HEART_PIECE, + .vanillaItem = dItemNo_KAKERA_HEART_e, + }, + MessageCheck{ + .group = 5, + .messageId = 5001, + .name = ITEM_CHECK_DUNGEON_REWARD_FOREST, + .vanillaItem = dItemNo_NONE_e, + }, + MessageCheck{ + .group = 5, + .messageId = 6011, + .name = ITEM_CHECK_DUNGEON_REWARD_GORON, + .vanillaItem = dItemNo_NONE_e, + }, + MessageCheck{ + .group = 5, + .messageId = 7001, + .name = ITEM_CHECK_DUNGEON_REWARD_LAKEBED, + .vanillaItem = dItemNo_NONE_e, + }, + MessageCheck{ + .group = 5, + .messageId = 9301, + .name = ITEM_CHECK_DUNGEON_REWARD_SNOWPEAK, + .vanillaItem = dItemNo_NONE_e, + }, + MessageCheck{ + .group = 5, + .messageId = 9401, + .name = ITEM_CHECK_DUNGEON_REWARD_TIME, + .vanillaItem = dItemNo_NONE_e, + }, + MessageCheck{ + .group = 5, + .messageId = 11001, + .name = ITEM_CHECK_DUNGEON_REWARD_CITY, + .vanillaItem = dItemNo_NONE_e, + }, + MessageCheck{ + .group = 0, + .messageId = 233, + .name = ITEM_CHECK_ILIA_MEMORY, + .vanillaItem = dItemNo_HORSE_FLUTE_e, + .enqueueAtDisplay = false, + }, +}; + std::unordered_map s_modChecks; std::unordered_set s_warnedCollisions; ItemCheckHandle s_nextCheckHandle = 1; @@ -79,7 +156,28 @@ std::string bug_check_name(uint8_t insectId) { } std::string sky_character_check_name() { - return fmt::format("skychar:{}:{}", current_stage_name(), dStage_roomControl_c::getStayNo()); + return fmt::format("sky:{}:{}", current_stage_name(), dStage_roomControl_c::getStayNo()); +} + +bool ilia_memory_context() { + return std::strcmp(current_stage_name(), "R_SP109") == 0 && + dComIfGp_roomControl_getStayNo() == 0 && dComIfG_play_c::getLayerNo(0) == 9; +} + +uint16_t item_get_message(uint8_t itemNo) { + if (itemNo == dItemNo_KAKERA_HEART_e) { + constexpr std::array heartPieceMessages{0x86, 0x9C, 0x9D, 0x9E, 0x9F}; + return heartPieceMessages[dComIfGs_getMaxLife() % heartPieceMessages.size()]; + } + return static_cast(itemNo + 0x65); +} + +void invalidate_committed_check(const std::string& name) { + if (name.empty()) { + item_check_clear_committed(); + } else { + item_check_cancel(item_give_tag(name.c_str())); + } } } // namespace @@ -176,14 +274,6 @@ uint8_t item_check_freestanding(uint8_t bitNo, uint8_t itemNo, fopAc_ac_c* item) return item_check(name.c_str(), itemNo, item); } -uint8_t item_check_poe(uint8_t bitNo, uint8_t itemNo, fopAc_ac_c* poe) { - if (s_modChecks.empty()) { - return itemNo; - } - const auto name = poe_check_name(bitNo); - return item_check(name.c_str(), itemNo, poe); -} - uint8_t item_check_shop(uint8_t itemNo, fopAc_ac_c* giver) { if (s_modChecks.empty()) { return itemNo; @@ -192,20 +282,24 @@ uint8_t item_check_shop(uint8_t itemNo, fopAc_ac_c* giver) { return item_check(name.c_str(), itemNo, giver); } -uint8_t item_check_bug(uint8_t insectId, uint8_t itemNo, fopAc_ac_c* agitha) { - if (s_modChecks.empty()) { - return itemNo; - } - const auto name = bug_check_name(insectId); - return item_check(name.c_str(), itemNo, agitha); -} +uint32_t item_check_message(uint16_t group, uint32_t messageId) { + for (const auto& check : kMessageChecks) { + if (check.group != group || check.messageId != messageId || + (check.group == 0 && check.messageId == 233 && !ilia_memory_context())) + { + continue; + } -uint8_t item_check_sky_character(uint8_t itemNo, fopAc_ac_c* statue) { - if (s_modChecks.empty()) { - return itemNo; + const ItemCheckResult result = item_check_commit(check.name, check.vanillaItem, nullptr); + if (result.itemNo == check.vanillaItem || result.itemNo == dItemNo_NONE_e) { + return messageId; + } + if (check.enqueueAtDisplay) { + item_check_enqueue(result, ItemGiveMode::Silent); + } + return item_get_message(result.itemNo); } - const auto name = sky_character_check_name(); - return item_check(name.c_str(), itemNo, statue); + return messageId; } uint32_t item_give_tag_chest(uint8_t boxNo) { @@ -236,10 +330,6 @@ uint32_t item_give_tag_sky_character() { return item_give_tag(sky_character_check_name().c_str()); } -void item_check_enqueue_poe(uint8_t bitNo, uint8_t itemNo) { - item_check_enqueue(poe_check_name(bitNo).c_str(), itemNo); -} - namespace svc { ModResult item_check_set_override(LoadedMod& mod, const char* name, uint8_t itemNo) { @@ -247,10 +337,12 @@ ModResult item_check_set_override(LoadedMod& mod, const char* name, uint8_t item for (auto& checkOverride : checks.overrides) { if (checkOverride.name == name) { checkOverride.itemNo = itemNo; + invalidate_committed_check(checkOverride.name); return MOD_OK; } } checks.overrides.push_back({.name = name, .itemNo = itemNo}); + invalidate_committed_check(checks.overrides.back().name); return MOD_OK; } @@ -261,6 +353,9 @@ ModResult item_check_clear_override(LoadedMod& mod, const char* name) { } const auto removed = std::erase_if(modIt->second.overrides, [&](const auto& checkOverride) { return checkOverride.name == name; }); + if (removed != 0) { + invalidate_committed_check(name); + } return removed != 0 ? MOD_OK : MOD_INVALID_ARGUMENT; } @@ -272,6 +367,7 @@ ModResult item_check_add_resolver(LoadedMod& mod, const char* name, ItemCheckRes resolver.fn = fn; resolver.userData = userData; outHandle = resolver.handle; + invalidate_committed_check(resolver.name); return MOD_OK; } @@ -280,12 +376,31 @@ ModResult item_check_remove_resolver(LoadedMod& mod, ItemCheckHandle handle) { if (modIt == s_modChecks.end()) { return MOD_INVALID_ARGUMENT; } - const auto removed = std::erase_if( - modIt->second.resolvers, [&](const auto& resolver) { return resolver.handle == handle; }); - return removed != 0 ? MOD_OK : MOD_INVALID_ARGUMENT; + const auto resolverIt = + std::find_if(modIt->second.resolvers.begin(), modIt->second.resolvers.end(), + [&](const auto& resolver) { return resolver.handle == handle; }); + if (resolverIt == modIt->second.resolvers.end()) { + return MOD_INVALID_ARGUMENT; + } + const std::string name = resolverIt->name; + modIt->second.resolvers.erase(resolverIt); + invalidate_committed_check(name); + return MOD_OK; } void item_checks_remove_mod(LoadedMod& mod) { + if (const auto modIt = s_modChecks.find(&mod); modIt != s_modChecks.end()) { + for (const auto& resolver : modIt->second.resolvers) { + if (resolver.name.empty()) { + item_check_clear_committed(); + break; + } + invalidate_committed_check(resolver.name); + } + for (const auto& checkOverride : modIt->second.overrides) { + invalidate_committed_check(checkOverride.name); + } + } s_modChecks.erase(&mod); s_warnedCollisions.clear(); } diff --git a/src/dusk/mods/item_gives.cpp b/src/dusk/mods/item_gives.cpp index bfe92d3cff..739a5ac5ee 100644 --- a/src/dusk/mods/item_gives.cpp +++ b/src/dusk/mods/item_gives.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include namespace dusk::mods { @@ -28,6 +29,8 @@ aurora::Module Log{"dusk::mods::item_gives"}; std::deque s_giveNames; std::unordered_map s_giveNameIds; +detail::ItemCommitStore s_committedChecks; + const char* item_give_name(uint32_t tag) { if (tag == 0 || tag > s_giveNames.size()) { return nullptr; @@ -92,7 +95,18 @@ void notify_gives(const char* checkName, uint8_t itemNo, fopAc_ac_c* giver, Item } } -constexpr size_t kGiveQueueLimit = 64; +void complete_check(uint8_t itemNo, uint32_t giveTag, fopAc_ac_c* giver, ItemGiveOrigin origin) { + if (const auto* committed = s_committedChecks.find(giveTag); + committed != nullptr && committed->resolvedItem != itemNo) + { + Log.error("committed check '{}' completed with item {:#x} instead of {:#x}", + item_give_name(giveTag) != nullptr ? item_give_name(giveTag) : "", itemNo, + committed->resolvedItem); + } + s_committedChecks.erase(giveTag); + notify_gives(item_give_name(giveTag), itemNo, giver, origin); +} + constexpr int kGiveMaxRetries = 5; struct QueuedGive { @@ -101,6 +115,7 @@ struct QueuedGive { uint8_t itemNo = 0; bool silent = false; bool resolveAtDispatch = false; + bool forced = false; }; std::deque s_giveQueue; @@ -111,6 +126,11 @@ bool s_inFlight = false; bool s_inFlightSpawned = false; bool s_dispatchingSilent = false; +bool has_forced_give() { + return std::any_of( + s_giveQueue.begin(), s_giveQueue.end(), [](const QueuedGive& give) { return give.forced; }); +} + bool safe_to_dispatch() { daAlink_c* link = daAlink_getAlinkActorClass(); if (link == nullptr) { @@ -142,13 +162,13 @@ bool safe_to_dispatch() { bool resolve_queued_give(const QueuedGive& give, ItemGiveOrigin origin, uint8_t& outItem) { outItem = give.itemNo; if (give.resolveAtDispatch) { - outItem = item_check(item_give_name(give.tag), give.itemNo, nullptr); + outItem = item_check_commit(give.tag, give.itemNo, nullptr).itemNo; } if (outItem != dItemNo_NONE_e) { return true; } - notify_gives(item_give_name(give.tag), dItemNo_NONE_e, nullptr, origin); + complete_check(dItemNo_NONE_e, give.tag, nullptr, origin); return false; } @@ -171,11 +191,40 @@ void dispatch_demo_give() { daAlink_c* link = daAlink_getAlinkActorClass(); dComIfGp_getEvent()->setGtItm(s_inFlightItem); - link->procCoGetItemInit(); + link->mProcID = daAlink_c::PROC_GET_ITEM; const s16 eventIndex = dComIfGp_getEventManager().getEventIdx(link, "DEFAULT_GETITEM", 0xFF); fopAcM_orderChangeEventId(link, eventIndex, 1, 0xFFFF); } +void dispatch_next_give() { + while (!s_giveQueue.empty()) { + while (!s_giveQueue.empty() && s_giveQueue.front().silent) { + const QueuedGive give = s_giveQueue.front(); + s_giveQueue.pop_front(); + dispatch_silent_give(give); + } + if (s_giveQueue.empty()) { + return; + } + + const QueuedGive give = s_giveQueue.front(); + s_giveQueue.pop_front(); + + uint8_t itemNo = 0; + if (!resolve_queued_give(give, ITEM_GIVE_ORIGIN_QUEUE, itemNo)) { + continue; + } + + s_inFlightGive = give; + s_inFlightItem = itemNo; + s_inFlightRetries = 0; + s_inFlight = true; + s_inFlightSpawned = false; + dispatch_demo_give(); + return; + } +} + } // namespace uint32_t item_give_tag(const char* name) { @@ -192,16 +241,30 @@ uint32_t item_give_tag(const char* name) { return tag; } -uint8_t item_check_tagged(uint32_t giveTag, uint8_t itemNo, fopAc_ac_c* giver) { +ItemCheckResult item_check_commit(uint32_t giveTag, uint8_t itemNo, fopAc_ac_c* giver) { const char* name = item_give_name(giveTag); - return name != nullptr ? item_check(name, itemNo, giver) : itemNo; + if (name == nullptr) { + return {.tag = giveTag, .itemNo = itemNo}; + } + + if (const auto* committed = s_committedChecks.find(giveTag); committed != nullptr) { + if (committed->vanillaItem != itemNo) { + Log.error("committed check '{}' changed vanilla item from {:#x} to {:#x}", name, + committed->vanillaItem, itemNo); + } + return {.tag = giveTag, .itemNo = committed->resolvedItem}; + } + + const auto& committed = + s_committedChecks.commit(giveTag, itemNo, [&] { return item_check(name, itemNo, giver); }); + return {.tag = giveTag, .itemNo = committed.resolvedItem}; } -void item_check_enqueue(const char* name, uint8_t itemNo) { - if (s_giveQueue.size() >= kGiveQueueLimit) { - Log.warn("item give queue is full; dropping check '{}'", name != nullptr ? name : ""); - return; - } +ItemCheckResult item_check_commit(const char* name, uint8_t itemNo, fopAc_ac_c* giver) { + return item_check_commit(item_give_tag(name), itemNo, giver); +} + +void item_check_enqueue_deferred(const char* name, uint8_t itemNo) { s_giveQueue.push_back({ .tag = item_give_tag(name), .itemNo = itemNo, @@ -209,6 +272,53 @@ void item_check_enqueue(const char* name, uint8_t itemNo) { }); } +bool item_check_enqueue(ItemCheckResult check, ItemGiveMode mode) { + auto* committed = s_committedChecks.find(check.tag); + if (committed == nullptr) { + Log.error("cannot enqueue uncommitted check '{}'", + item_give_name(check.tag) != nullptr ? item_give_name(check.tag) : ""); + return false; + } + if (committed->resolvedItem != check.itemNo) { + Log.error("committed check '{}' changed item from {:#x} to {:#x}", + item_give_name(check.tag) != nullptr ? item_give_name(check.tag) : "", check.itemNo, + committed->resolvedItem); + return false; + } + if (committed->queued) { + return false; + } + + committed->queued = true; + s_giveQueue.push_back({ + .tag = check.tag, + .itemNo = committed->resolvedItem, + .silent = mode == ItemGiveMode::Silent, + .forced = mode == ItemGiveMode::ForcedDemo, + }); + if (mode == ItemGiveMode::ForcedDemo && !s_inFlight) { + dispatch_next_give(); + } + return true; +} + +void item_check_complete(ItemCheckResult check, fopAc_ac_c* giver) { + complete_check(check.itemNo, check.tag, giver, ITEM_GIVE_ORIGIN_GAME); +} + +void item_check_cancel(uint32_t giveTag) { + s_committedChecks.erase(giveTag); + std::erase_if(s_giveQueue, + [&](const QueuedGive& give) { return give.owner == nullptr && give.tag == giveTag; }); +} + +void item_check_clear_committed() { + std::erase_if(s_giveQueue, [&](const QueuedGive& give) { + return give.owner == nullptr && s_committedChecks.contains(give.tag); + }); + s_committedChecks.clear(); +} + void item_granted(uint8_t itemNo, uint32_t giveTag, fopAc_ac_c* giver) { ItemGiveOrigin origin = ITEM_GIVE_ORIGIN_GAME; if (s_dispatchingSilent) { @@ -218,7 +328,7 @@ void item_granted(uint8_t itemNo, uint32_t giveTag, fopAc_ac_c* giver) { s_inFlight = false; s_inFlightSpawned = false; } - notify_gives(item_give_name(giveTag), itemNo, giver, origin); + complete_check(itemNo, giveTag, giver, origin); } bool item_give_queue_dispatching() { @@ -236,17 +346,21 @@ uint32_t item_give_queue_take_tag() { namespace svc { void item_gives_tick() { - if ((!s_inFlight && s_giveQueue.empty()) || !safe_to_dispatch()) { + if (!s_inFlight && s_giveQueue.empty()) { return; } if (s_inFlight) { + if (!safe_to_dispatch()) { + return; + } if (++s_inFlightRetries > kGiveMaxRetries) { Log.error("item {:#x} for '{}' did not complete after {} attempts; dropping it", s_inFlightItem, item_give_name(s_inFlightGive.tag) != nullptr ? item_give_name(s_inFlightGive.tag) : "", kGiveMaxRetries); + item_check_cancel(s_inFlightGive.tag); s_inFlight = false; s_inFlightSpawned = false; return; @@ -256,29 +370,10 @@ void item_gives_tick() { return; } - while (!s_giveQueue.empty() && s_giveQueue.front().silent) { - const QueuedGive give = s_giveQueue.front(); - s_giveQueue.pop_front(); - dispatch_silent_give(give); - } - if (s_giveQueue.empty()) { + if (!has_forced_give() && !safe_to_dispatch()) { return; } - - const QueuedGive give = s_giveQueue.front(); - uint8_t itemNo = 0; - if (!resolve_queued_give(give, ITEM_GIVE_ORIGIN_QUEUE, itemNo)) { - s_giveQueue.pop_front(); - return; - } - - s_giveQueue.pop_front(); - s_inFlightGive = give; - s_inFlightItem = itemNo; - s_inFlightRetries = 0; - s_inFlight = true; - s_inFlightSpawned = false; - dispatch_demo_give(); + dispatch_next_give(); } void item_gives_clear() { @@ -289,12 +384,10 @@ void item_gives_clear() { s_giveQueue.clear(); s_inFlight = false; s_inFlightSpawned = false; + s_committedChecks.clear(); } ModResult item_give_enqueue(LoadedMod& mod, const char* checkName, uint8_t itemNo, uint32_t flags) { - if (s_giveQueue.size() >= kGiveQueueLimit) { - return MOD_UNAVAILABLE; - } s_giveQueue.push_back({ .owner = &mod, .tag = item_give_tag(checkName), diff --git a/src/dusk/ui/editor.cpp b/src/dusk/ui/editor.cpp index 8aa3c3894d..d4373e059a 100644 --- a/src/dusk/ui/editor.cpp +++ b/src/dusk/ui/editor.cpp @@ -275,7 +275,7 @@ std::map itemMap = { {dItemNo_WEAR_KOKIRI_e, {"Hero's Clothes"}}, {dItemNo_ARMOR_e, {"Magic Armor"}}, {dItemNo_WEAR_ZORA_e, {"Zora Armor"}}, - {dItemNo_MAGIC_LV1_e, {"Magic Level 1"}}, + {dItemNo_SHADOW_CRYSTAL_e, {"Shadow Crystal"}}, {dItemNo_DUNGEON_EXIT_2_e, {"Ooccoo Sr.", ITEMTYPE_EQUIP_e}}, {dItemNo_WALLET_LV1_e, {"Wallet"}}, {dItemNo_WALLET_LV2_e, {"Big Wallet"}}, diff --git a/src/f_op/f_op_actor_mng.cpp b/src/f_op/f_op_actor_mng.cpp index e02bec33fe..5ac24d534e 100644 --- a/src/f_op/f_op_actor_mng.cpp +++ b/src/f_op/f_op_actor_mng.cpp @@ -1592,6 +1592,12 @@ fpc_ProcID fopAcM_createItemForBoss(const cXyz* i_pos, int i_itemNo, int i_roomN int _ = -1; #if TARGET_PC const u8 originalItemNo = i_itemNo & 0xFF; + // Overridden item checks do not set StageLife, so boss containers + // also need a persistent local item bit. + constexpr u8 kBossHeartContainerFlag = 0x9F; + if (originalItemNo == dItemNo_UTAWA_HEART_e && param_8 == -1) { + param_8 = kBossHeartContainerFlag; + } u32 giveTag; if (i_itemCheckName != NULL) { i_itemNo = dusk::mods::item_check(i_itemCheckName, originalItemNo, NULL);