diff --git a/soh/include/variables.h b/soh/include/variables.h index 2bad8335c99..ed64357f7f1 100644 --- a/soh/include/variables.h +++ b/soh/include/variables.h @@ -99,7 +99,7 @@ extern "C" extern u16 gUpgradeCapacities[8][4]; extern u32 gGsFlagsMasks[4]; extern u32 gGsFlagsShifts[4]; - extern void* gItemIcons[0x82]; + extern void* gItemIcons[0x83]; extern u8 gItemAgeReqs[]; extern u8 gSlotAgeReqs[]; extern u8 gItemSlots[56]; diff --git a/soh/include/z64item.h b/soh/include/z64item.h index 1fc919a45f6..5aa02469afa 100644 --- a/soh/include/z64item.h +++ b/soh/include/z64item.h @@ -221,91 +221,92 @@ typedef enum { /* 0x44 */ ITEM_BOOTS_KOKIRI, /* 0x45 */ ITEM_BOOTS_IRON, /* 0x46 */ ITEM_BOOTS_HOVER, - /* 0x47 */ ITEM_BULLET_BAG_30, - /* 0x48 */ ITEM_BULLET_BAG_40, - /* 0x49 */ ITEM_BULLET_BAG_50, - /* 0x4A */ ITEM_QUIVER_30, - /* 0x4B */ ITEM_QUIVER_40, - /* 0x4C */ ITEM_QUIVER_50, - /* 0x4D */ ITEM_BOMB_BAG_20, - /* 0x4E */ ITEM_BOMB_BAG_30, - /* 0x4F */ ITEM_BOMB_BAG_40, - /* 0x50 */ ITEM_BRACELET, - /* 0x51 */ ITEM_GAUNTLETS_SILVER, - /* 0x52 */ ITEM_GAUNTLETS_GOLD, - /* 0x53 */ ITEM_SCALE_SILVER, - /* 0x54 */ ITEM_SCALE_GOLDEN, - /* 0x55 */ ITEM_SWORD_KNIFE, - /* 0x56 */ ITEM_WALLET_ADULT, - /* 0x57 */ ITEM_WALLET_GIANT, - /* 0x58 */ ITEM_SEEDS, - /* 0x59 */ ITEM_FISHING_POLE, - /* 0x5A */ ITEM_SONG_MINUET, - /* 0x5B */ ITEM_SONG_BOLERO, - /* 0x5C */ ITEM_SONG_SERENADE, - /* 0x5D */ ITEM_SONG_REQUIEM, - /* 0x5E */ ITEM_SONG_NOCTURNE, - /* 0x5F */ ITEM_SONG_PRELUDE, - /* 0x60 */ ITEM_SONG_LULLABY, - /* 0x61 */ ITEM_SONG_EPONA, - /* 0x62 */ ITEM_SONG_SARIA, - /* 0x63 */ ITEM_SONG_SUN, - /* 0x64 */ ITEM_SONG_TIME, - /* 0x65 */ ITEM_SONG_STORMS, - /* 0x66 */ ITEM_MEDALLION_FOREST, - /* 0x67 */ ITEM_MEDALLION_FIRE, - /* 0x68 */ ITEM_MEDALLION_WATER, - /* 0x69 */ ITEM_MEDALLION_SPIRIT, - /* 0x6A */ ITEM_MEDALLION_SHADOW, - /* 0x6B */ ITEM_MEDALLION_LIGHT, - /* 0x6C */ ITEM_KOKIRI_EMERALD, - /* 0x6D */ ITEM_GORON_RUBY, - /* 0x6E */ ITEM_ZORA_SAPPHIRE, - /* 0x6F */ ITEM_STONE_OF_AGONY, - /* 0x70 */ ITEM_GERUDO_CARD, - /* 0x71 */ ITEM_SKULL_TOKEN, - /* 0x72 */ ITEM_HEART_CONTAINER, - /* 0x73 */ ITEM_HEART_PIECE, - /* 0x74 */ ITEM_KEY_BOSS, - /* 0x75 */ ITEM_COMPASS, - /* 0x76 */ ITEM_DUNGEON_MAP, - /* 0x77 */ ITEM_KEY_SMALL, - /* 0x78 */ ITEM_MAGIC_SMALL, - /* 0x79 */ ITEM_MAGIC_LARGE, - /* 0x7A */ ITEM_HEART_PIECE_2, - /* 0x7B */ ITEM_SINGLE_MAGIC, - /* 0x7C */ ITEM_DOUBLE_MAGIC, - /* 0x7D */ ITEM_DOUBLE_DEFENSE, - /* 0x7E */ ITEM_INVALID_4, - /* 0x7F */ ITEM_INVALID_5, - /* 0x80 */ ITEM_INVALID_6, - /* 0x81 */ ITEM_INVALID_7, - /* 0x82 */ ITEM_MILK, - /* 0x83 */ ITEM_HEART, - /* 0x84 */ ITEM_RUPEE_GREEN, - /* 0x85 */ ITEM_RUPEE_BLUE, - /* 0x86 */ ITEM_RUPEE_RED, - /* 0x87 */ ITEM_RUPEE_PURPLE, - /* 0x88 */ ITEM_RUPEE_GOLD, - /* 0x89 */ ITEM_INVALID_8, - /* 0x8A */ ITEM_STICKS_5, - /* 0x8B */ ITEM_STICKS_10, - /* 0x8C */ ITEM_NUTS_5, - /* 0x8D */ ITEM_NUTS_10, - /* 0x8E */ ITEM_BOMBS_5, - /* 0x8F */ ITEM_BOMBS_10, - /* 0x90 */ ITEM_BOMBS_20, - /* 0x91 */ ITEM_BOMBS_30, - /* 0x92 */ ITEM_ARROWS_SMALL, - /* 0x93 */ ITEM_ARROWS_MEDIUM, - /* 0x94 */ ITEM_ARROWS_LARGE, - /* 0x95 */ ITEM_SEEDS_30, - /* 0x96 */ ITEM_BOMBCHUS_5, - /* 0x97 */ ITEM_BOMBCHUS_20, - /* 0x98 */ ITEM_STICK_UPGRADE_20, - /* 0x99 */ ITEM_STICK_UPGRADE_30, - /* 0x9A */ ITEM_NUT_UPGRADE_30, - /* 0x9B */ ITEM_NUT_UPGRADE_40, + ITEM_BOW_ARROW_BOMB, + /* 0x48 */ ITEM_BULLET_BAG_30, + /* 0x49 */ ITEM_BULLET_BAG_40, + /* 0x4a */ ITEM_BULLET_BAG_50, + /* 0x4B */ ITEM_QUIVER_30, + /* 0x4C */ ITEM_QUIVER_40, + /* 0x4D */ ITEM_QUIVER_50, + /* 0x4E */ ITEM_BOMB_BAG_20, + /* 0x4F */ ITEM_BOMB_BAG_30, + /* 0x50 */ ITEM_BOMB_BAG_40, + /* 0x51 */ ITEM_BRACELET, + /* 0x52 */ ITEM_GAUNTLETS_SILVER, + /* 0x53 */ ITEM_GAUNTLETS_GOLD, + /* 0x54 */ ITEM_SCALE_SILVER, + /* 0x55 */ ITEM_SCALE_GOLDEN, + /* 0x56 */ ITEM_SWORD_KNIFE, + /* 0x57 */ ITEM_WALLET_ADULT, + /* 0x58 */ ITEM_WALLET_GIANT, + /* 0x59 */ ITEM_SEEDS, + /* 0x5a */ ITEM_FISHING_POLE, + /* 0x5B */ ITEM_SONG_MINUET, + /* 0x5C */ ITEM_SONG_BOLERO, + /* 0x5D */ ITEM_SONG_SERENADE, + /* 0x5E */ ITEM_SONG_REQUIEM, + /* 0x5F */ ITEM_SONG_NOCTURNE, + /* 0x60 */ ITEM_SONG_PRELUDE, + /* 0x61 */ ITEM_SONG_LULLABY, + /* 0x62 */ ITEM_SONG_EPONA, + /* 0x63 */ ITEM_SONG_SARIA, + /* 0x64 */ ITEM_SONG_SUN, + /* 0x65 */ ITEM_SONG_TIME, + /* 0x66 */ ITEM_SONG_STORMS, + /* 0x67 */ ITEM_MEDALLION_FOREST, + /* 0x68 */ ITEM_MEDALLION_FIRE, + /* 0x69 */ ITEM_MEDALLION_WATER, + /* 0x6a */ ITEM_MEDALLION_SPIRIT, + /* 0x6B */ ITEM_MEDALLION_SHADOW, + /* 0x6C */ ITEM_MEDALLION_LIGHT, + /* 0x6D */ ITEM_KOKIRI_EMERALD, + /* 0x6E */ ITEM_GORON_RUBY, + /* 0x6F */ ITEM_ZORA_SAPPHIRE, + /* 0x70 */ ITEM_STONE_OF_AGONY, + /* 0x71 */ ITEM_GERUDO_CARD, + /* 0x72 */ ITEM_SKULL_TOKEN, + /* 0x73 */ ITEM_HEART_CONTAINER, + /* 0x74 */ ITEM_HEART_PIECE, + /* 0x75 */ ITEM_KEY_BOSS, + /* 0x76 */ ITEM_COMPASS, + /* 0x77 */ ITEM_DUNGEON_MAP, + /* 0x78 */ ITEM_KEY_SMALL, + /* 0x79 */ ITEM_MAGIC_SMALL, + /* 0x7a */ ITEM_MAGIC_LARGE, + /* 0x7B */ ITEM_HEART_PIECE_2, + /* 0x7C */ ITEM_SINGLE_MAGIC, + /* 0x7D */ ITEM_DOUBLE_MAGIC, + /* 0x7E */ ITEM_DOUBLE_DEFENSE, + /* 0x7F */ ITEM_INVALID_4, + /* 0x80 */ ITEM_INVALID_5, + /* 0x81 */ ITEM_INVALID_6, + /* 0x82 */ ITEM_INVALID_7, + /* 0x83 */ ITEM_MILK, + /* 0x84 */ ITEM_HEART, + /* 0x85 */ ITEM_RUPEE_GREEN, + /* 0x86 */ ITEM_RUPEE_BLUE, + /* 0x87 */ ITEM_RUPEE_RED, + /* 0x88 */ ITEM_RUPEE_PURPLE, + /* 0x89 */ ITEM_RUPEE_GOLD, + /* 0x8a */ ITEM_INVALID_8, + /* 0x8B */ ITEM_STICKS_5, + /* 0x8C */ ITEM_STICKS_10, + /* 0x8D */ ITEM_NUTS_5, + /* 0x8E */ ITEM_NUTS_10, + /* 0x8F */ ITEM_BOMBS_5, + /* 0x90 */ ITEM_BOMBS_10, + /* 0x91 */ ITEM_BOMBS_20, + /* 0x92 */ ITEM_BOMBS_30, + /* 0x93 */ ITEM_ARROWS_SMALL, + /* 0x94 */ ITEM_ARROWS_MEDIUM, + /* 0x95 */ ITEM_ARROWS_LARGE, + /* 0x96 */ ITEM_SEEDS_30, + /* 0x97 */ ITEM_BOMBCHUS_5, + /* 0x98 */ ITEM_BOMBCHUS_20, + /* 0x99 */ ITEM_STICK_UPGRADE_20, + /* 0x9a */ ITEM_STICK_UPGRADE_30, + /* 0x9B */ ITEM_NUT_UPGRADE_30, + /* 0x9C */ ITEM_NUT_UPGRADE_40, /* 0xFC */ ITEM_LAST_USED = 0xFC, /* 0xFE */ ITEM_NONE_FE = 0xFE, /* 0xFF */ ITEM_NONE = 0xFF diff --git a/soh/include/z64player.h b/soh/include/z64player.h index ff30cc17094..968c3ca0058 100644 --- a/soh/include/z64player.h +++ b/soh/include/z64player.h @@ -77,7 +77,7 @@ typedef enum { /* 0x09 */ PLAYER_IA_BOW_FIRE, /* 0x0A */ PLAYER_IA_BOW_ICE, /* 0x0B */ PLAYER_IA_BOW_LIGHT, - /* 0x0C */ PLAYER_IA_BOW_0C, + /* 0x0C */ PLAYER_IA_BOW_BOMB, /* 0x0D */ PLAYER_IA_BOW_0D, /* 0x0E */ PLAYER_IA_BOW_0E, /* 0x0F */ PLAYER_IA_SLINGSHOT, @@ -426,14 +426,14 @@ typedef struct { #define PLAYER_STATE1_ENEMY_TARGET (1 << 4) #define PLAYER_STATE1_INPUT_DISABLED (1 << 5) #define PLAYER_STATE1_TEXT_ON_SCREEN (1 << 6) -#define PLAYER_STATE1_DEAD (1 << 7) +#define PLAYER_STATE1_DEAD (1 << 7) #define PLAYER_STATE1_START_PUTAWAY (1 << 8) #define PLAYER_STATE1_READY_TO_FIRE (1 << 9) #define PLAYER_STATE1_GETTING_ITEM (1 << 10) #define PLAYER_STATE1_ITEM_OVER_HEAD (1 << 11) #define PLAYER_STATE1_CHARGING_SPIN_ATTACK (1 << 12) #define PLAYER_STATE1_HANGING_OFF_LEDGE (1 << 13) -#define PLAYER_STATE1_CLIMBING_LEDGE (1 << 14) +#define PLAYER_STATE1_CLIMBING_LEDGE (1 << 14) #define PLAYER_STATE1_TARGETING (1 << 15) #define PLAYER_STATE1_TARGET_LOCKED (1 << 16) #define PLAYER_STATE1_TARGET_NOTHING (1 << 17) @@ -490,7 +490,7 @@ typedef struct { #define PLAYER_STATE3_PAUSE_ACTION_FUNC (1 << 2) #define PLAYER_STATE3_FINISHED_ATTACKING (1 << 3) #define PLAYER_STATE3_CHECK_FLOOR_WATER_COLLISION (1 << 4) -#define PLAYER_STATE3_FORCE_PULL_OCARINA (1 << 5) +#define PLAYER_STATE3_FORCE_PULL_OCARINA (1 << 5) #define PLAYER_STATE3_RESTORE_NAYRUS_LOVE (1 << 6) // Set by ocarina effects actors when destroyed to signal Nayru's Love may be restored (see `ACTOROVL_ALLOC_ABSOLUTE`) #define PLAYER_STATE3_HOOKSHOT_TRAVELLING (1 << 7) //Travelling to target diff --git a/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp b/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp index e552a9672d9..5309f7d6e41 100644 --- a/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp +++ b/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp @@ -105,7 +105,8 @@ void CustomMessage::Replace(std::string&& oldStr, std::string&& newEnglish, std: void CustomMessage::Format(ItemID iid) { for (std::string* str : { &english, &french, &german }) { - str->insert(0, ITEM_OBTAINED(iid)); + // HACK: this is so messed up + str->insert(0, ITEM_OBTAINED(iid > ITEM_BOW_ARROW_BOMB ? iid - 1 : iid)); size_t start_pos = 0; std::replace(str->begin(), str->end(), '&', NEWLINE()[0]); while ((start_pos = str->find('^', start_pos)) != std::string::npos) { @@ -253,7 +254,7 @@ bool CustomMessageManager::ClearMessageTable(std::string tableID) { return true; } -bool CustomMessageManager::AddCustomMessageTable(std::string tableID) { +bool CustomMessageManager::AddCustomMessageTable(std::string tableID) { CustomMessageTable newMessageTable; return messageTables.emplace(tableID, newMessageTable).second; } diff --git a/soh/soh/Enhancements/presets.h b/soh/soh/Enhancements/presets.h index 4f01f6ebf76..7d7239f9259 100644 --- a/soh/soh/Enhancements/presets.h +++ b/soh/soh/Enhancements/presets.h @@ -248,6 +248,7 @@ const std::vector enhancementsCvars = { "gAddTraps.Speed", "gAddTraps.Tele", "gAddTraps.Void", + "gBombArrows", }; const std::vector cheatCvars = { diff --git a/soh/soh/SohMenuBar.cpp b/soh/soh/SohMenuBar.cpp index 10dca75cfc2..acb6a872e87 100644 --- a/soh/soh/SohMenuBar.cpp +++ b/soh/soh/SohMenuBar.cpp @@ -608,6 +608,8 @@ void DrawEnhancementsMenu() { UIWidgets::Tooltip("Catch Poes by swinging an empty bottle at them instead of from a text box like you can in Majora's Mask."); UIWidgets::PaddedEnhancementCheckbox("Nuts explode bombs", "gNutsExplodeBombs", true, false); UIWidgets::Tooltip("Makes nuts explode bombs, similar to how they interact with bombchus. This does not affect bombflowers."); + UIWidgets::PaddedEnhancementCheckbox("Bomb Arrows", "gBombArrows", true, false); + UIWidgets::Tooltip("Equip bombs onto the same button as your bow to shoot arrows that explode on impact"); UIWidgets::PaddedEnhancementCheckbox("Equip Multiple Arrows at Once", "gSeparateArrows", true, false); UIWidgets::Tooltip("Allow the bow and magic arrows to be equipped at the same time on different slots. (Note this will disable the behaviour of the 'Equip Dupe' glitch)"); UIWidgets::PaddedEnhancementCheckbox("Switch Arrow Types", "gArrowSwitching", true, false); diff --git a/soh/src/code/code_80097A00.c b/soh/src/code/code_80097A00.c index eb83738e360..49eab232998 100644 --- a/soh/src/code/code_80097A00.c +++ b/soh/src/code/code_80097A00.c @@ -108,6 +108,7 @@ void* gItemIcons[] = { gItemIconBootsKokiriTex, gItemIconBootsIronTex, gItemIconBootsHoverTex, + gItemIconBowFireTex, gItemIconBulletBag30Tex, gItemIconBulletBag40Tex, gItemIconBulletBag50Tex, diff --git a/soh/src/code/z_message_PAL.c b/soh/src/code/z_message_PAL.c index 7bb3803fcc6..96660c34161 100644 --- a/soh/src/code/z_message_PAL.c +++ b/soh/src/code/z_message_PAL.c @@ -470,8 +470,8 @@ void Message_DrawTextboxIcon(PlayState* play, Gfx** p, s16 x, s16 y) { sIconEnvColors[1] = color; } else if (CVarGetInteger("gCosmetics.DefaultColorScheme", COLORSCHEME_N64) == COLORSCHEME_GAMECUBE) { sIconPrimColors[0] = (Color_RGB8){ 0, 200, 80 }; - sIconPrimColors[1] = (Color_RGB8){ 50, 255, 130 }; - sIconEnvColors[1] = (Color_RGB8){ 50, 255, 130 }; + sIconPrimColors[1] = (Color_RGB8){ 50, 255, 130 }; + sIconEnvColors[1] = (Color_RGB8){ 50, 255, 130 }; } static Color_RGB8 sIconPrim = { 0, 80, 200 }; static s16 sIconFlashTimer = 12; @@ -1132,6 +1132,9 @@ void Message_LoadItemIcon(PlayState* play, u16 itemId, s16 y) { interfaceCtx->mapPalette[30] = 0xFF; interfaceCtx->mapPalette[31] = 0xFF; } + if (itemId > ITEM_BOW_ARROW_BOMB) { + itemId += 1; + } if (itemId < ITEM_MEDALLION_FOREST) { R_TEXTBOX_ICON_XPOS = R_TEXT_INIT_XPOS - sIconItem32XOffsets[gSaveContext.language]; R_TEXTBOX_ICON_YPOS = y + 6; @@ -3139,7 +3142,7 @@ void Message_Update(PlayState* play) { if (msgCtx->msgLength == 0) { return; } - + GameInteractor_ExecuteOnDialogMessage(); bool isB_Held = CVarGetInteger("gSkipText", 0) != 0 ? CHECK_BTN_ALL(input->cur.button, BTN_B) && !sTextboxSkipped diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index 6941531c085..785b93651db 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -4863,14 +4863,18 @@ void Interface_DrawAmmoCount(PlayState* play, s16 button, s16 alpha) { i = gSaveContext.equips.buttonItems[button]; if ((i == ITEM_STICK) || (i == ITEM_NUT) || (i == ITEM_BOMB) || (i == ITEM_BOW) || - ((i >= ITEM_BOW_ARROW_FIRE) && (i <= ITEM_BOW_ARROW_LIGHT)) || (i == ITEM_SLINGSHOT) || (i == ITEM_BOMBCHU) || - (i == ITEM_BEAN)) { + ((i >= ITEM_BOW_ARROW_FIRE) && (i <= ITEM_BOW_ARROW_LIGHT)) || (i == ITEM_BOW_ARROW_BOMB) || (i == ITEM_SLINGSHOT) || + (i == ITEM_BOMBCHU) || (i == ITEM_BEAN)) { if ((i >= ITEM_BOW_ARROW_FIRE) && (i <= ITEM_BOW_ARROW_LIGHT)) { i = ITEM_BOW; } - ammo = AMMO(i); + if (i == ITEM_BOW_ARROW_BOMB) { + ammo = MIN(AMMO(ITEM_BOW), AMMO(ITEM_BOMB)); + } else { + ammo = AMMO(i); + } gDPPipeSync(OVERLAY_DISP++); @@ -4885,6 +4889,7 @@ void Interface_DrawAmmoCount(PlayState* play, s16 button, s16 alpha) { } } else if (((i == ITEM_BOW) && (AMMO(i) == CUR_CAPACITY(UPG_QUIVER))) || ((i == ITEM_BOMB) && (AMMO(i) == CUR_CAPACITY(UPG_BOMB_BAG))) || + ((i == ITEM_BOW_ARROW_BOMB) && MIN(AMMO(ITEM_BOW), AMMO(ITEM_BOMB)) == MIN(CUR_CAPACITY(UPG_QUIVER), CUR_CAPACITY(UPG_BOMB_BAG))) || ((i == ITEM_SLINGSHOT) && (AMMO(i) == CUR_CAPACITY(UPG_BULLET_BAG))) || ((i == ITEM_STICK) && (AMMO(i) == CUR_CAPACITY(UPG_STICKS))) || ((i == ITEM_NUT) && (AMMO(i) == CUR_CAPACITY(UPG_NUTS))) || ((i == ITEM_BOMBCHU) && (ammo == 50)) || diff --git a/soh/src/code/z_player_lib.c b/soh/src/code/z_player_lib.c index 820f2c67267..d579ffcb12e 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -626,7 +626,7 @@ void Player_SetModels(Player* this, s32 modelGroup) { // Left hand this->leftHandType = gPlayerModelTypes[modelGroup][PLAYER_MODELGROUPENTRY_LEFT_HAND]; this->leftHandDLists = &sPlayerDListGroups[this->leftHandType][gSaveContext.linkAge]; - + // Right hand this->rightHandType = gPlayerModelTypes[modelGroup][PLAYER_MODELGROUPENTRY_RIGHT_HAND]; this->rightHandDLists = &sPlayerDListGroups[this->rightHandType][gSaveContext.linkAge]; @@ -798,6 +798,7 @@ s32 Player_HoldsBow(Player* this) { case PLAYER_IA_BOW_FIRE: case PLAYER_IA_BOW_ICE: case PLAYER_IA_BOW_LIGHT: + case PLAYER_IA_BOW_BOMB: return true; default: return false; @@ -1274,7 +1275,7 @@ s32 Player_OverrideLimbDrawGameplayCommon(PlayState* play, s32 limbIndex, Gfx** s32 Player_OverrideLimbDrawGameplayDefault(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, void* thisx) { Player* this = (Player*)thisx; - if (!Player_OverrideLimbDrawGameplayCommon(play, limbIndex, dList, pos, rot, thisx)) + if (!Player_OverrideLimbDrawGameplayCommon(play, limbIndex, dList, pos, rot, thisx)) { if (limbIndex == PLAYER_LIMB_L_HAND) { Gfx** dLists = this->leftHandDLists; @@ -1323,7 +1324,7 @@ s32 Player_OverrideLimbDrawGameplayDefault(PlayState* play, s32 limbIndex, Gfx** } else if (limbIndex == PLAYER_LIMB_WAIST) { - + if (!Player_IsCustomLinkModel()) { *dList = ResourceMgr_LoadGfxByName( this->waistDLists[sDListsLodOffset]); // NOTE: This needs to be disabled when using custom @@ -1833,6 +1834,7 @@ void Player_PostLimbDrawGameplay(PlayState* play, s32 limbIndex, Gfx** dList, Ve (this->heldItemAction == PLAYER_IA_BOW_FIRE) || (this->heldItemAction == PLAYER_IA_BOW_ICE) || (this->heldItemAction == PLAYER_IA_BOW_LIGHT) || + (this->heldItemAction == PLAYER_IA_BOW_BOMB) || (this->heldItemAction == PLAYER_IA_BOW) || (this->heldItemAction == PLAYER_IA_SLINGSHOT))) { if (heldActor != NULL) { @@ -1907,7 +1909,7 @@ u32 func_80091738(PlayState* play, u8* segment, SkelAnime* skelAnime) { return size + 0x8800 + 0x90; } -u8 sPauseModelGroupBySword[] = { +u8 sPauseModelGroupBySword[] = { PLAYER_MODELGROUP_SWORD, // PLAYER_SWORD_KOKIRI PLAYER_MODELGROUP_SWORD, // PLAYER_SWORD_MASTER PLAYER_MODELGROUP_BGS, // PLAYER_SWORD_BIGGORON @@ -1931,7 +1933,7 @@ s32 Player_OverrideLimbDrawPause(PlayState* play, s32 limbIndex, Gfx** dList, Ve type = gPlayerModelTypes[modelGroup][PLAYER_MODELGROUPENTRY_LEFT_HAND]; sLeftHandType = type; - // SOH: Handle unexpected swordless case. Previously OOB array access is avoided, but we want the + // SOH: Handle unexpected swordless case. Previously OOB array access is avoided, but we want the // hand model-type to be set to open (otherwise it is set to holding sword model-type) if (playerSwordAndShield[0] == PLAYER_SWORD_NONE) { type = PLAYER_MODELTYPE_LH_OPEN; @@ -1956,7 +1958,7 @@ s32 Player_OverrideLimbDrawPause(PlayState* play, s32 limbIndex, Gfx** dList, Ve } } else if (limbIndex == PLAYER_LIMB_WAIST) { type = gPlayerModelTypes[modelGroup][PLAYER_MODELGROUPENTRY_WAIST]; - + if (Player_IsCustomLinkModel()) { return 0; } @@ -2225,7 +2227,7 @@ void Player_DrawPause(PlayState* play, u8* segment, SkelAnime* skelAnime, Vec3f* } else { SelectedAnim=randval; } - } + } } else if ((CUR_EQUIP_VALUE(EQUIP_TYPE_SWORD) == EQUIP_VALUE_SWORD_NONE) && (CUR_EQUIP_VALUE(EQUIP_TYPE_SHIELD) == EQUIP_VALUE_SHIELD_NONE)) { // if the player has no sword or shield equipped s16 randval = (rand() % (4 - 2 + 1)) + 2; // 3 animations if (randval==4) { //if its the shield anim diff --git a/soh/src/overlays/actors/ovl_En_Arrow/z_en_arrow.c b/soh/src/overlays/actors/ovl_En_Arrow/z_en_arrow.c index 8b1d191a618..f50448348ec 100644 --- a/soh/src/overlays/actors/ovl_En_Arrow/z_en_arrow.c +++ b/soh/src/overlays/actors/ovl_En_Arrow/z_en_arrow.c @@ -5,6 +5,7 @@ */ #include "z_en_arrow.h" +#include "overlays/actors/ovl_En_Bom/z_en_bom.h" #include "objects/gameplay_keep/gameplay_keep.h" #include "objects/object_gi_nuts/object_gi_nuts.h" @@ -214,6 +215,7 @@ void EnArrow_Shoot(EnArrow* this, PlayState* play) { case ARROW_NORMAL_LIT: case ARROW_NORMAL_HORSE: case ARROW_NORMAL: + case ARROW_BOMB: Player_PlaySfx(&player->actor, NA_SE_IT_ARROW_SHOT); break; @@ -441,11 +443,12 @@ void EnArrow_Update(Actor* thisx, PlayState* play) { if ((this->actor.params >= ARROW_FIRE) && (this->actor.params <= ARROW_0E)) { s16 elementalActorIds[] = { ACTOR_ARROW_FIRE, ACTOR_ARROW_ICE, ACTOR_ARROW_LIGHT, - ACTOR_ARROW_FIRE, ACTOR_ARROW_FIRE, ACTOR_ARROW_FIRE }; + ACTOR_EN_BOM, ACTOR_ARROW_FIRE, ACTOR_ARROW_FIRE }; if (this->actor.child == NULL) { Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, elementalActorIds[this->actor.params - 3], - this->actor.world.pos.x, this->actor.world.pos.y, this->actor.world.pos.z, 0, 0, 0, 0); + this->actor.world.pos.x, this->actor.world.pos.y, this->actor.world.pos.z, 0, 0, 0, + this->actor.params == ARROW_BOMB ? BOMB_ARROW : 0); } } else if (this->actor.params == ARROW_NORMAL_LIT) { static Vec3f velocity = { 0.0f, 0.5f, 0.0f }; diff --git a/soh/src/overlays/actors/ovl_En_Arrow/z_en_arrow.h b/soh/src/overlays/actors/ovl_En_Arrow/z_en_arrow.h index 4ee84d3315b..7e9b9c1967e 100644 --- a/soh/src/overlays/actors/ovl_En_Arrow/z_en_arrow.h +++ b/soh/src/overlays/actors/ovl_En_Arrow/z_en_arrow.h @@ -34,7 +34,7 @@ typedef enum { /* 3 */ ARROW_FIRE, /* 4 */ ARROW_ICE, /* 5 */ ARROW_LIGHT, - /* 6 */ ARROW_0C, + /* 6 */ ARROW_BOMB, /* 7 */ ARROW_0D, /* 8 */ ARROW_0E, /* 9 */ ARROW_SEED, diff --git a/soh/src/overlays/actors/ovl_En_Bom/z_en_bom.c b/soh/src/overlays/actors/ovl_En_Bom/z_en_bom.c index 40adc1a6f1a..a34ca45ddd2 100644 --- a/soh/src/overlays/actors/ovl_En_Bom/z_en_bom.c +++ b/soh/src/overlays/actors/ovl_En_Bom/z_en_bom.c @@ -20,6 +20,8 @@ void EnBom_Draw(Actor* thisx, PlayState* play); void EnBom_Move(EnBom* this, PlayState* play); void EnBom_WaitForRelease(EnBom* this, PlayState* play); +void ArrowBomb_Init(EnBom* this, PlayState* play); + const ActorInit En_Bom_InitVars = { ACTOR_EN_BOM, ACTORCAT_EXPLOSIVE, @@ -93,6 +95,11 @@ void EnBom_SetupAction(EnBom* this, EnBomActionFunc actionFunc) { void EnBom_Init(Actor* thisx, PlayState* play) { EnBom* this = (EnBom*)thisx; + if (this->actor.params == BOMB_ARROW) { + ArrowBomb_Init(this, play); + return; + } + Actor_ProcessInitChain(thisx, sInitChain); ActorShape_Init(&thisx->shape, 700.0f, ActorShadow_DrawCircle, 16.0f); thisx->colChkInfo.mass = 200; @@ -205,7 +212,7 @@ void EnBom_Explode(EnBom* this, PlayState* play) { } else { this->explosionCollider.elements[0].dim.worldSphere.radius += this->actor.shape.rot.z + 8; } - + if (this->actor.params == BOMB_EXPLOSION) { CollisionCheck_SetAT(play, &play->colChkCtx, &this->explosionCollider.base); @@ -246,6 +253,9 @@ void EnBom_Explode(EnBom* this, PlayState* play) { } Actor_Kill(&this->actor); + if (this->actor.parent && this->actor.parent->id == ACTOR_EN_ARROW) { + Actor_Kill(this->actor.parent); + } } } @@ -348,6 +358,52 @@ void EnBom_Update(Actor* thisx, PlayState* play2) { Audio_PlayActorSound2(thisx, NA_SE_IT_BOMB_EXPLOSION); + play->envCtx.adjLight1Color[0] = play->envCtx.adjLight1Color[1] = + play->envCtx.adjLight1Color[2] = 250; + + play->envCtx.adjAmbientColor[0] = play->envCtx.adjAmbientColor[1] = + play->envCtx.adjAmbientColor[2] = 250; + + Camera_AddQuake(&play->mainCamera, 2, 0xB, 8); + thisx->params = BOMB_EXPLOSION; + this->timer = 10; + thisx->flags |= ACTOR_FLAG_DRAW_WHILE_CULLED; + EnBom_SetupAction(this, EnBom_Explode); + } + } else if (thisx->params == BOMB_ARROW) { + dustAccel.y = 0.2f; + + // spawn spark effect on even frames + effPos = thisx->world.pos; + effPos.y += 5.0f; + if ((play->gameplayFrames % 2) == 0) { + EffectSsGSpk_SpawnFuse(play, thisx, &effPos, &effVelocity, &effAccel); + } + + Audio_PlayActorSound2(thisx, NA_SE_IT_BOMB_IGNIT - SFX_FLAG); + + effPos.y += 1.0f; + func_8002829C(play, &effPos, &effVelocity, &dustAccel, &dustColor, &dustColor, 50, 5); + + dustAccel.y = 0.2f; + effPos = thisx->world.pos; + effPos.y += 1.0f; + + if (this->timer == 0) { + effPos = thisx->world.pos; + + effPos.y += 10.0f; + + EffectSsBomb2_SpawnLayered(play, &effPos, &effVelocity, &bomb2Accel, 100, + (thisx->shape.rot.z * 6) + 19); + + effPos.y = thisx->floorHeight; + if (thisx->floorHeight > BGCHECK_Y_MIN) { + EffectSsBlast_SpawnWhiteShockwave(play, &effPos, &effVelocity, &effAccel); + } + + Audio_PlayActorSound2(thisx, NA_SE_IT_BOMB_EXPLOSION); + play->envCtx.adjLight1Color[0] = play->envCtx.adjLight1Color[1] = play->envCtx.adjLight1Color[2] = 250; @@ -364,7 +420,7 @@ void EnBom_Update(Actor* thisx, PlayState* play2) { Actor_SetFocus(thisx, 20.0f); - if (thisx->params <= BOMB_BODY) { + if (thisx->params != BOMB_EXPLOSION) { Collider_UpdateCylinder(thisx, &this->bombCollider); // if link is not holding the bomb anymore and bump conditions are met, subscribe to OC @@ -395,7 +451,7 @@ void EnBom_Draw(Actor* thisx, PlayState* play) { OPEN_DISPS(play->state.gfxCtx); - if (thisx->params == BOMB_BODY) { + if (thisx->params != BOMB_EXPLOSION) { Gfx_SetupDL_25Opa(play->state.gfxCtx); if (!CVarGetInteger("gDisableBombBillboarding", 0)) { Matrix_ReplaceRotation(&play->billboardMtxF); @@ -415,3 +471,92 @@ void EnBom_Draw(Actor* thisx, PlayState* play) { CLOSE_DISPS(play->state.gfxCtx); } + + +#include "overlays/actors/ovl_En_Arrow/z_en_arrow.h" + +void ArrowBomb_Charge(EnBom* this, PlayState* play); +void ArrowBomb_Fly(EnBom* this, PlayState* play); + +void ArrowBomb_Init(EnBom* this, PlayState* play) { + Actor_ProcessInitChain(&this->actor, sInitChain); + ActorShape_Init(&this->actor.shape, 700.0f, ActorShadow_DrawCircle, 16.0f); + + this->flashSpeedScale = 7; + Collider_InitCylinder(play, &this->bombCollider); + Collider_InitJntSph(play, &this->explosionCollider); + Collider_SetCylinder(play, &this->bombCollider, &this->actor, &sCylinderInit); + Collider_SetJntSph(play, &this->explosionCollider, &this->actor, &sJntSphInit, &this->explosionColliderItems[0]); + this->explosionColliderItems[0].info.toucher.damage += (this->actor.shape.rot.z & 0xFF00) >> 8; + + this->actor.shape.rot.z &= 0xFF; + if (this->actor.shape.rot.z & 0x80) { + this->actor.shape.rot.z |= 0xFF00; + } + + Actor_SetScale(&this->actor, 0.003f); + + EnBom_SetupAction(this, ArrowBomb_Charge); +} + +void ArrowBomb_SetPosition(EnBom* this, EnArrow* arrow) { + // copy position and rotation from arrow + this->actor.world.pos = arrow->actor.world.pos; + f32 r = 8.0f; + f32 xrot = arrow->actor.world.rot.x; + f32 yrot = arrow->actor.world.rot.y; + this->actor.world.pos.x += r * Math_CosS(xrot) * Math_SinS(yrot); + this->actor.world.pos.y -= r * Math_SinS(xrot) + 2.0f; + this->actor.world.pos.z += r * Math_CosS(xrot) * Math_CosS(yrot); +} + +void ArrowBomb_Charge(EnBom* this, PlayState* play) { + EnArrow* arrow = (EnArrow*)this->actor.parent; + if ((arrow == NULL) || (arrow->actor.update == NULL)) { + Actor_Kill(&this->actor); + return; + } + + ArrowBomb_SetPosition(this, arrow); + + this->timer = 10; + + // if arrow has no parent, player has fired the arrow + if (arrow->actor.parent == NULL) { + this->unkPos = this->actor.world.pos; + EnBom_SetupAction(this, ArrowBomb_Fly); + } +} + +void ArrowBomb_80865ECC(Vec3f* unkPos, Vec3f* firePos, f32 scale) { + unkPos->x += ((firePos->x - unkPos->x) * scale); + unkPos->y += ((firePos->y - unkPos->y) * scale); + unkPos->z += ((firePos->z - unkPos->z) * scale); +} + +void ArrowBomb_Fly(EnBom* this, PlayState* play) { + EnArrow* arrow; + f32 distanceScaled; + s32 pad; + + arrow = (EnArrow*)this->actor.parent; + if ((arrow == NULL) || (arrow->actor.update == NULL)) { + Actor_Kill(&this->actor); + return; + } + ArrowBomb_SetPosition(this, arrow); + + distanceScaled = Math_Vec3f_DistXYZ(&this->unkPos, &this->actor.world.pos) * (1.0f / 24.0f); + this->unk_158 = distanceScaled; + if (distanceScaled < 1.0f) { + this->unk_158 = 1.0f; + } + ArrowBomb_80865ECC(&this->unkPos, &this->actor.world.pos, 0.05f); + + if (arrow->hitFlags & 1) { + this->timer = 0; + this->actor.shape.rot.z = 0; + } else { + this->timer = 10; + } +} diff --git a/soh/src/overlays/actors/ovl_En_Bom/z_en_bom.h b/soh/src/overlays/actors/ovl_En_Bom/z_en_bom.h index 15e391226d8..90d7465f5f4 100644 --- a/soh/src/overlays/actors/ovl_En_Bom/z_en_bom.h +++ b/soh/src/overlays/actors/ovl_En_Bom/z_en_bom.h @@ -18,11 +18,14 @@ typedef struct EnBom { /* 0x01FC */ f32 flashIntensity; /* 0x0200 */ u8 bumpOn; /* 0x0204 */ EnBomActionFunc actionFunc; + /* 0x014C */ Vec3f unkPos; + /* 0x0158 */ f32 unk_158; } EnBom; // size = 0x0208 typedef enum { /* 0x00 */ BOMB_BODY, - /* 0x01 */ BOMB_EXPLOSION + /* 0x01 */ BOMB_EXPLOSION, + /* 0x02 */ BOMB_ARROW, } EnBomType; #endif diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 0cabab9c0f7..cfa3807d829 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -1062,6 +1062,7 @@ static s8 sItemActionParams[] = { PLAYER_IA_BOOTS_KOKIRI, PLAYER_IA_BOOTS_IRON, PLAYER_IA_BOOTS_HOVER, + PLAYER_IA_BOW_BOMB, }; static u8 sMaskMemory; @@ -2213,6 +2214,14 @@ s32 func_80834380(PlayState* play, Player* this, s32* itemPtr, s32* typePtr) { return play->interfaceCtx.hbaAmmo; } else if (play->shootingGalleryStatus != 0) { return play->shootingGalleryStatus; + } else if (this->heldItemAction == PLAYER_IA_BOW_BOMB) { + if (AMMO(*itemPtr) == 0) { + return 0; + } else if (AMMO(ITEM_BOMB) == 0) { + *typePtr = ARROW_NORMAL; + } else { + return MIN(AMMO(*itemPtr), AMMO(ITEM_BOMB)); + } } else { return AMMO(*itemPtr); } @@ -2316,7 +2325,7 @@ s32 func_8083442C(Player* this, PlayState* play) { s32 arrowType; s32 magicArrowType; - if ((this->heldItemAction >= PLAYER_IA_BOW_FIRE) && (this->heldItemAction <= PLAYER_IA_BOW_0E) && + if ((this->heldItemAction >= PLAYER_IA_BOW_FIRE) && (this->heldItemAction < PLAYER_IA_BOW_BOMB) && (gSaveContext.magicState != MAGIC_STATE_IDLE)) { func_80078884(NA_SE_SY_ERROR); } else { @@ -2642,6 +2651,9 @@ s32 func_808350A4(PlayState* play, Player* this) { if (!CVarGetInteger("gInfiniteAmmo", 0)) { play->shootingGalleryStatus--; } + } else if (item == ITEM_BOW && this->heldItemAction == PLAYER_IA_BOW_BOMB) { + Inventory_ChangeAmmo(ITEM_BOW, -1); + Inventory_ChangeAmmo(ITEM_BOMB, -1); } else { Inventory_ChangeAmmo(item, -1); if (CVarGetInteger("gArrowSwitching", 0) && diff --git a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_item.c b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_item.c index 665c95b81af..071ea3423fa 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_item.c +++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_item.c @@ -813,8 +813,8 @@ void KaleidoScope_UpdateItemEquip(PlayState* play) { { C_DOWN_BUTTON_X+X_Margins_CD, C_DOWN_BUTTON_Y+Y_Margins_CD }, { C_RIGHT_BUTTON_X+X_Margins_CR, C_RIGHT_BUTTON_Y+Y_Margins_CR }, { DPAD_UP_X+X_Margins_DPad_Items, DPAD_UP_Y+Y_Margins_DPad_Items }, - { DPAD_DOWN_X+X_Margins_DPad_Items, DPAD_DOWN_Y+Y_Margins_DPad_Items }, - { DPAD_LEFT_X+X_Margins_DPad_Items, DPAD_LEFT_Y+Y_Margins_DPad_Items }, + { DPAD_DOWN_X+X_Margins_DPad_Items, DPAD_DOWN_Y+Y_Margins_DPad_Items }, + { DPAD_LEFT_X+X_Margins_DPad_Items, DPAD_LEFT_Y+Y_Margins_DPad_Items }, { DPAD_RIGHT_X+X_Margins_DPad_Items, DPAD_RIGHT_Y+Y_Margins_DPad_Items } }; s16 DPad_ItemsOffset[4][2] = { @@ -1027,11 +1027,21 @@ void KaleidoScope_UpdateItemEquip(PlayState* play) { pauseCtx->equipTargetSlot = SLOT_BOW; } } - + // If the item is on another button already, swap the two uint16_t targetButtonIndex = pauseCtx->equipTargetCBtn + 1; for (uint16_t otherSlotIndex = 0; otherSlotIndex < ARRAY_COUNT(gSaveContext.equips.cButtonSlots); otherSlotIndex++) { + int slot_item = gSaveContext.equips.buttonItems[targetButtonIndex]; + // Don't check for bomb arrows so you can replace bomb arrow equip with just bombs + if (CVarGetInteger("gBombArrows", 0) && + (slot_item == ITEM_BOW || slot_item == ITEM_BOW_ARROW_FIRE || + slot_item == ITEM_BOW_ARROW_ICE || slot_item == ITEM_BOW_ARROW_LIGHT) && + pauseCtx->equipTargetItem == ITEM_BOMB) { + pauseCtx->equipTargetItem = ITEM_BOW_ARROW_BOMB; + pauseCtx->equipTargetSlot = SLOT_BOW; + } + uint16_t otherButtonIndex = otherSlotIndex + 1; if (otherSlotIndex == pauseCtx->equipTargetCBtn) { continue;