Skip to content

Commit

Permalink
Merge 'Adjust blue warp item behavior' (#2251)
Browse files Browse the repository at this point in the history
# Conflicts:
#	ASM/build/asm_symbols.txt
#	ASM/build/bundle.o
#	ASM/build/c_symbols.txt
#	ASM/src/config.asm
#	data/generated/rom_patch.txt
#	data/generated/symbols.json
  • Loading branch information
fenhl committed Aug 9, 2024
2 parents d8f281b + 7c3d874 commit 046ca6c
Show file tree
Hide file tree
Showing 14 changed files with 32,886 additions and 32,879 deletions.
1,288 changes: 645 additions & 643 deletions ASM/build/asm_symbols.txt

Large diffs are not rendered by default.

Binary file modified ASM/build/bundle.o
Binary file not shown.
1,311 changes: 656 additions & 655 deletions ASM/build/c_symbols.txt

Large diffs are not rendered by default.

47 changes: 21 additions & 26 deletions ASM/c/blue_warp.c
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
#include <stdbool.h>

#include "blue_warp.h"
#include "item_table.h"
#include "save.h"
#include "z64.h"

#define TEXT_STATE_CLOSING 2

extern uint8_t PLAYER_ID;
extern uint8_t PLAYER_NAME_ID;
extern bool REWARDS_AS_ITEMS;

// Original function copied over
int32_t DoorWarp1_PlayerInRange(z64_actor_t* actor, z64_game_t* game) {
if (actor->xzdist_from_link < 60.0f) {
Expand All @@ -18,38 +23,28 @@ int32_t DoorWarp1_PlayerInRange(z64_actor_t* actor, z64_game_t* game) {
return false;
};

// Routine taken from Majora's Mask blue warps
int32_t DoorWarp1_PlayerInRange_Overwrite(z64_actor_t* actor, z64_game_t* game) {
// Check vanilla range
if (DoorWarp1_PlayerInRange(actor, game)) {

// Check if dungeon reward has already been collected
if (extended_savectx.collected_dungeon_rewards[game->scene_index - 0x0011]) {
return true;
}

// Null out blue warp parent if it is still the dungeon boss
if (z64_ActorHasParent(actor, game) && (actor->parent != &z64_link.common)) {
actor->parent = NULL;
}

// Link will attach as the parent actor once the GetItem is accepted. Until then, offer the dungeon reward for Link.
if (!z64_ActorHasParent(actor, game)) {
// Put a dummy item value on the blue warp, which will be overwritten by the medallions
z64_ActorOfferGetItem(actor, game, 0x65, 60.0f, 20.0f);
return false;
}

// Wait until Link closes the textbox displaying the getItem reward
if (z64_MessageGetState(((uint8_t*)(&z64_game)) + 0x20D8) == TEXT_STATE_CLOSING) {
if ((game->scene_index != 0x17) && (game->scene_index != 0x18)) {
extended_savectx.collected_dungeon_rewards[game->scene_index - 0x0011] = true;
uint8_t boss_idx = game->scene_index - 0x0011;
// queue the item if not already collected
if (!extended_savectx.collected_dungeon_rewards[boss_idx]) {
if (REWARDS_AS_ITEMS) {
push_delayed_item(0x05 + boss_idx);
} else {
override_key_t override_key = { .scene = 0xFF, .type = OVR_DELAYED, .flag = 0x05 + boss_idx };
override_t override = lookup_override_by_key(override_key);
uint16_t resolved_item_id = resolve_upgrades(override);
item_row_t* item_row = get_item_row(resolved_item_id);
call_effect_function(item_row);
PLAYER_NAME_ID = override.value.base.player;
z64_DisplayTextbox(&z64_game, resolve_item_text_id(item_row, PLAYER_NAME_ID != PLAYER_ID), 0);
}
return true;
extended_savectx.collected_dungeon_rewards[boss_idx] = true;
}
// immediately activate the blue warp. Queued item will be given after the warp
return true;
}


return false;
}

Expand Down
2 changes: 2 additions & 0 deletions ASM/src/config.asm
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ CFG_BIGOCTO_OVERRIDE_KEY:
.area 6, 0x00
PASSWORD:
.endarea
REWARDS_AS_ITEMS:
.byte 0x00
.align 4

; These configuration values are given fixed addresses to aid auto-trackers.
Expand Down
2 changes: 1 addition & 1 deletion Goals.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def replace_goal_names(worlds: list[World]) -> None:
location
for location in world.get_filled_locations()
if location.type == 'Boss'
or (location.name == 'ToT Reward from Rauru' and not world.settings.skip_reward_from_rauru)
and (location.name != 'ToT Reward from Rauru' or not world.settings.skip_reward_from_rauru)
]
for category in world.goal_categories.values():
for goal in category.goals:
Expand Down
2 changes: 1 addition & 1 deletion Hints.py
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ def get_area_from_name(check: str) -> HintArea | str:
except Exception:
return check
# Don't consider dungeons as already hinted from the reward hint on the Temple of Time altar
if (location.type == 'Boss' or location.name == 'ToT Reward from Rauru') and world.settings.shuffle_dungeon_rewards in ('vanilla', 'reward'):
if location.type == 'Boss' and world.settings.shuffle_dungeon_rewards in ('vanilla', 'reward'):
return None
return HintArea.at(location)

Expand Down
22 changes: 12 additions & 10 deletions LocationList.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,20 +57,22 @@ def shop_address(shop_id: int, shelf_id: int) -> int:
# Actor ID - The position of the actor in the actor table.
# The default variable can also be a list of such tuples in the case that multiple scene setups contain the same locations to be shuffled together.

# For cutscene/song/boss locations, the Scene is set to 0xFF. This matches the behavior of the push_delayed_item C function.

# Note: for ActorOverride locations, the "Addresses" variable is in the form ([addresses], [bytes]) where addresses is a list of memory locations in ROM to be updated, and bytes is the data that will be written to that location

# Location: Type Scene Default Addresses Vanilla Item Categories
location_table: dict[str, tuple[str, Optional[int], LocationDefault, LocationAddresses, Optional[str], LocationFilterTags]] = OrderedDict([
## Dungeon Rewards
("ToT Reward from Rauru", ("Cutscene", 0xFF, 0x04, None, 'Light Medallion', ("Temple of Time", "NPCs", "Dungeon Rewards",))),
("Queen Gohma", ("Boss", 0x11, 0x65, None, 'Kokiri Emerald', ("Deku Tree", "Deku Tree MQ", "Vanilla Dungeons", "Master Quest", "Dungeon Rewards",))),
("King Dodongo", ("Boss", 0x12, 0x65, None, 'Goron Ruby', ("Dodongo's Cavern", "Dodongo's Cavern MQ", "Vanilla Dungeons", "Master Quest", "Dungeon Rewards",))),
("Barinade", ("Boss", 0x13, 0x65, None, 'Zora Sapphire', ("Jabu Jabu's Belly", "Jabu Jabu's Belly MQ", "Vanilla Dungeons", "Master Quest", "Dungeon Rewards",))),
("Phantom Ganon", ("Boss", 0x14, 0x65, None, 'Forest Medallion', ("Forest Temple", "Forest Temple MQ", "Vanilla Dungeons", "Master Quest", "Dungeon Rewards",))),
("Volvagia", ("Boss", 0x15, 0x65, None, 'Fire Medallion', ("Fire Temple", "Fire Temple MQ", "Vanilla Dungeons", "Master Quest", "Dungeon Rewards",))),
("Morpha", ("Boss", 0x16, 0x65, None, 'Water Medallion', ("Water Temple", "Water Temple MQ", "Vanilla Dungeons", "Master Quest", "Dungeon Rewards",))),
("Bongo Bongo", ("Boss", 0x18, 0x65, None, 'Shadow Medallion', ("Shadow Temple", "Shadow Temple MQ", "Vanilla Dungeons", "Master Quest", "Dungeon Rewards",))),
("Twinrova", ("Boss", 0x17, 0x65, None, 'Spirit Medallion', ("Spirit Temple", "Spirit Temple MQ", "Vanilla Dungeons", "Master Quest", "Dungeon Rewards",))),
("ToT Reward from Rauru", ("Boss", 0xFF, 0x04, None, 'Light Medallion', ("Temple of Time", "NPCs", "Dungeon Rewards",))),
("Queen Gohma", ("Boss", 0xFF, 0x05, None, 'Kokiri Emerald', ("Deku Tree", "Deku Tree MQ", "Vanilla Dungeons", "Master Quest", "Dungeon Rewards",))),
("King Dodongo", ("Boss", 0xFF, 0x06, None, 'Goron Ruby', ("Dodongo's Cavern", "Dodongo's Cavern MQ", "Vanilla Dungeons", "Master Quest", "Dungeon Rewards",))),
("Barinade", ("Boss", 0xFF, 0x07, None, 'Zora Sapphire', ("Jabu Jabu's Belly", "Jabu Jabu's Belly MQ", "Vanilla Dungeons", "Master Quest", "Dungeon Rewards",))),
("Phantom Ganon", ("Boss", 0xFF, 0x08, None, 'Forest Medallion', ("Forest Temple", "Forest Temple MQ", "Vanilla Dungeons", "Master Quest", "Dungeon Rewards",))),
("Volvagia", ("Boss", 0xFF, 0x09, None, 'Fire Medallion', ("Fire Temple", "Fire Temple MQ", "Vanilla Dungeons", "Master Quest", "Dungeon Rewards",))),
("Morpha", ("Boss", 0xFF, 0x0A, None, 'Water Medallion', ("Water Temple", "Water Temple MQ", "Vanilla Dungeons", "Master Quest", "Dungeon Rewards",))),
("Bongo Bongo", ("Boss", 0xFF, 0x0C, None, 'Shadow Medallion', ("Shadow Temple", "Shadow Temple MQ", "Vanilla Dungeons", "Master Quest", "Dungeon Rewards",))),
("Twinrova", ("Boss", 0xFF, 0x0B, None, 'Spirit Medallion', ("Spirit Temple", "Spirit Temple MQ", "Vanilla Dungeons", "Master Quest", "Dungeon Rewards",))),
("Ganon", ("Event", None, None, None, 'Triforce', None)),
("Gift from Sages", ("Cutscene", 0xFF, 0x03, None, None, None)),

Expand Down Expand Up @@ -2627,7 +2629,7 @@ def shop_address(shop_id: int, shelf_id: int) -> int:
'Song': [name for (name, data) in location_table.items() if data[0] == 'Song'],
'Chest': [name for (name, data) in location_table.items() if data[0] == 'Chest'],
'Collectable': [name for (name, data) in location_table.items() if data[0] == 'Collectable'],
'Boss': [name for (name, data) in location_table.items() if data[0] == 'Boss' or name == 'ToT Reward from Rauru'],
'Boss': [name for (name, data) in location_table.items() if data[0] == 'Boss'],
'ActorOverride': [name for (name, data) in location_table.items() if data[0] == 'ActorOverride'],
'BossHeart': [name for (name, data) in location_table.items() if data[0] == 'BossHeart'],
'CollectableLike': [name for (name, data) in location_table.items() if data[0] in ('Collectable', 'BossHeart', 'GS Token', 'SilverRupee')],
Expand Down
29 changes: 17 additions & 12 deletions Patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
build_ganon_text, build_misc_item_hints, build_misc_location_hints, get_simple_hint_no_prefix, get_item_generic_name
from Item import Item
from ItemList import REWARD_COLORS
from ItemPool import song_list, trade_items, child_trade_items
from ItemPool import reward_list, song_list, trade_items, child_trade_items
from Location import Location, DisableType
from LocationList import business_scrubs
from Messages import read_messages, update_message_by_id, read_shop_items, update_warp_song_text, \
Expand Down Expand Up @@ -425,23 +425,28 @@ def make_bytes(txt: str, size: int) -> list[int]:
rom.write_bytes(0xCD5E76, [0x0E, 0xDC])
rom.write_bytes(0xCD5E12, [0x0E, 0xDC])

# songs as items flag
# Some types of locations (boss rewards, songs, and the fairy ocarina) have special behavior,
# but need to use the normal Get Item mechanism if shuffled into the main item pool.
rewards_as_items = (
world.settings.shuffle_dungeon_rewards not in ('vanilla', 'reward')
or world.distribution.rewards_as_items
or any(name in reward_list and record.count for name, record in world.settings.starting_items.items())
)
if rewards_as_items:
rom.write_byte(rom.sym('REWARDS_AS_ITEMS'), 1)
songs_as_items = (
world.settings.shuffle_song_items != 'song'
or world.distribution.song_as_items
or world.distribution.songs_as_items
or any(name in song_list and record.count for name, record in world.settings.starting_items.items())
or world.settings.shuffle_individual_ocarina_notes
)

if songs_as_items:
rom.write_byte(rom.sym('SONGS_AS_ITEMS'), 1)
if world.settings.shuffle_ocarinas:
rom.write_byte(rom.sym('OCARINAS_SHUFFLED'), 0x01)

patch_cutscenes(rom, songs_as_items)

if world.settings.shuffle_ocarinas:
symbol = rom.sym('OCARINAS_SHUFFLED')
rom.write_byte(symbol, 0x01)

# Speed Pushing of All Pushable Objects (other than armos statues, which are handled in ASM)
rom.write_bytes(0xDD2B86, [0x40, 0x80]) # block speed
rom.write_bytes(0xDD2D26, [0x00, 0x01]) # block delay
Expand Down Expand Up @@ -2316,7 +2321,7 @@ def get_override_entry(location: Location) -> Optional[OverrideEntry]:
return None

# Don't add freestanding items, pots/crates, beehives to the override table if they're disabled. We use this check to determine how to draw and interact with them
if location.type in ["ActorOverride", "Freestanding", "RupeeTower", "Pot", "Crate", "FlyingPot", "SmallCrate", "Beehive", "Wonderitem"] and location.disabled != DisableType.ENABLED:
if location.type in ('ActorOverride', 'Freestanding', 'RupeeTower', 'Pot', 'Crate', 'FlyingPot', 'SmallCrate', 'Beehive', 'Wonderitem') and location.disabled != DisableType.ENABLED:
return None

player_id = location.item.world.id + 1
Expand All @@ -2325,12 +2330,12 @@ def get_override_entry(location: Location) -> Optional[OverrideEntry]:
else:
looks_like_item_id = 0

if location.type in ('NPC', 'Scrub', 'BossHeart', 'Boss'):
if location.type in ('NPC', 'Scrub', 'BossHeart'):
type = 0
elif location.type == 'Chest':
type = 1
default &= 0x1F
elif location.type in ['Freestanding', 'Pot', 'Crate', 'FlyingPot', 'SmallCrate', 'RupeeTower', 'Beehive', 'SilverRupee', 'Wonderitem']:
elif location.type in ('Freestanding', 'Pot', 'Crate', 'FlyingPot', 'SmallCrate', 'RupeeTower', 'Beehive', 'SilverRupee', 'Wonderitem'):
type = 6
if not (isinstance(location.default, list) or isinstance(location.default, tuple)):
raise Exception("Not right")
Expand All @@ -2357,7 +2362,7 @@ def get_override_entry(location: Location) -> Optional[OverrideEntry]:
type = 0
elif location.type == 'GrottoScrub' and location.item.type != 'Shop':
type = 4
elif location.type in ('Song', 'Cutscene'):
elif location.type in ('Song', 'Cutscene', 'Boss'):
type = 5
else:
return None
Expand Down
13 changes: 9 additions & 4 deletions Plandomizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,8 @@ def __init__(self, distribution: Distribution, id: int, src_dict: Optional[dict[
self.id: int = id
self.base_pool: list[str] = []
self.major_group: list[str] = []
self.song_as_items: bool = False
self.rewards_as_items: bool = False
self.songs_as_items: bool = False
self.skipped_locations: list[Location] = []
self.effective_starting_items: dict[str, StarterRecord] = {}

Expand Down Expand Up @@ -607,8 +608,10 @@ def alter_pool(self, world: World, pool: list[str]) -> list[str]:
self.pool_remove_item([pool], item_name, record.count)
except KeyError:
pass
if item_name in item_groups["DungeonReward"]:
self.rewards_as_items = True
if item_name in item_groups["Song"]:
self.song_as_items = True
self.songs_as_items = True

junk_to_add = pool_size - len(pool)
if junk_to_add > 0:
Expand Down Expand Up @@ -907,8 +910,10 @@ def fill(self, worlds: list[World], location_pools: list[list[Location]], item_p

item = self.get_item(ignore_pools, item_pools, location, player_id, record, worlds)

if location.type == 'Boss' and location.name != 'ToT Reward from Rauru' and item.type != 'DungeonReward':
self.rewards_as_items = True
if location.type == 'Song' and item.type != 'Song':
self.song_as_items = True
self.songs_as_items = True
location.world.push_item(location, item, True)

if item.advancement:
Expand Down Expand Up @@ -1084,7 +1089,7 @@ def configure_effective_starting_items(self, worlds: list[World], world: World)
if iter_world.settings.empty_dungeons_mode != 'none':
skipped_locations_from_dungeons: list[Location] = []
if iter_world.settings.shuffle_dungeon_rewards in ('vanilla', 'reward'):
skipped_locations_from_dungeons += [world.get_location(loc_name) for loc_name in location_groups['Boss']]
skipped_locations_from_dungeons += [world.get_location(loc_name) for loc_name in location_groups['Boss'] if loc_name != 'ToT Reward from Rauru']
elif world.settings.shuffle_dungeon_rewards == 'dungeon':
skipped_locations_from_dungeons += [location for location in iter_world.get_filled_locations() if location.item.type == 'DungeonReward']
if world.settings.shuffle_song_items == 'song':
Expand Down
2 changes: 1 addition & 1 deletion Rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def set_rules(world: World) -> None:
if location.type == 'Song':
# allow junk items, but songs must still have matching world
add_item_rule(location, lambda location, item:
((location.world.distribution.song_as_items or any(name in song_list and record.count for name, record in world.settings.starting_items.items()))
((location.world.distribution.songs_as_items or any(name in song_list and record.count for name, record in world.settings.starting_items.items()))
and item.type != 'Song')
or (item.type == 'Song' and item.world.id == location.world.id))
else:
Expand Down
Loading

0 comments on commit 046ca6c

Please sign in to comment.