diff --git a/worlds/tunic/combat_logic.py b/worlds/tunic/combat_logic.py index 2e490d1d..2e9f19db 100644 --- a/worlds/tunic/combat_logic.py +++ b/worlds/tunic/combat_logic.py @@ -140,24 +140,14 @@ def check_combat_reqs(area_name: str, state: CollectionState, player: int, alt_d # need sword for bosses if data.is_boss: return False - equipment.remove("Sword") - if has_magic: - if "Magic" not in equipment: - equipment.append("Magic") - # +4 mp pretty much makes up for the lack of sword, at least in Quarry - extra_mp_needed += 4 - if stick_bool: - # stick is a backup plan, and doesn't scale well, so let's require a little less - equipment.append("Stick") - extra_att_needed -= 2 - else: - extra_mp_needed += 2 - extra_att_needed -= 32 - elif stick_bool: + if stick_bool: + equipment.remove("Sword") equipment.append("Stick") # may revise this later based on feedback extra_att_needed += 3 extra_def_needed += 2 + # this is for when it changes over to the magic-only state if it needs to later + extra_mp_needed += 4 else: return False @@ -204,7 +194,7 @@ def check_combat_reqs(area_name: str, state: CollectionState, player: int, alt_d equip_list.append("Magic") more_modified_stats = AreaStats(modified_stats.att_level - 32, modified_stats.def_level, modified_stats.potion_level, modified_stats.hp_level, - modified_stats.sp_level, modified_stats.mp_level + 4, + modified_stats.sp_level, modified_stats.mp_level + 2, modified_stats.potion_count, equip_list, data.is_boss) if check_combat_reqs("none", state, player, more_modified_stats): return True @@ -222,7 +212,7 @@ def has_required_stats(data: AreaStats, state: CollectionState, player: int) -> player_att, att_offerings = get_att_level(state, player) # if you have 2 more attack than needed, we can forego needing mp - if data.mp_level > 1: + if data.mp_level > 1 and "Magic" in data.equipment: if player_att < data.att_level + 2: player_mp, mp_offerings = get_mp_level(state, player) if player_mp < data.mp_level: diff --git a/worlds/tunic/er_rules.py b/worlds/tunic/er_rules.py index 4d0a462c..f111fed8 100644 --- a/worlds/tunic/er_rules.py +++ b/worlds/tunic/er_rules.py @@ -1832,7 +1832,7 @@ def set_er_location_rules(world: "TunicWorld") -> None: if world.options.combat_logic == CombatLogic.option_on: combat_logic_to_loc("Overworld - [Northeast] Flowers Holy Cross", "Garden Knight") combat_logic_to_loc("Overworld - [Northwest] Chest Near Quarry Gate", "Before Well", dagger=True) - combat_logic_to_loc("Overworld - [Northeast] Chest Above Patrol Cave", "Garden Knight", dagger=True) + combat_logic_to_loc("Overworld - [Northeast] Chest Above Patrol Cave", "West Garden", dagger=True) combat_logic_to_loc("Overworld - [Southwest] West Beach Guarded By Turret", "Overworld", dagger=True) combat_logic_to_loc("Overworld - [Southwest] West Beach Guarded By Turret 2", "Overworld") combat_logic_to_loc("Overworld - [Southwest] Bombable Wall Near Fountain", "Before Well", dagger=True) diff --git a/worlds/tunic/items.py b/worlds/tunic/items.py index 846650c6..20696eb5 100644 --- a/worlds/tunic/items.py +++ b/worlds/tunic/items.py @@ -212,7 +212,7 @@ slot_data_item_names = [ combat_items: List[str] = [name for name, data in item_table.items() if data.combat_ic and IC.progression in data.combat_ic] -combat_items.extend(["Stick", "Sword", "Sword Upgrade", "Magic Wand", "Hero's Laurels"]) +combat_items.extend(["Stick", "Sword", "Sword Upgrade", "Magic Wand", "Hero's Laurels", "Gun"]) item_name_to_id: Dict[str, int] = {name: item_base_id + data.item_id_offset for name, data in item_table.items()} diff --git a/worlds/tunic/test/test_combat.py b/worlds/tunic/test/test_combat.py index 866dc5f8..c0e76ef9 100644 --- a/worlds/tunic/test/test_combat.py +++ b/worlds/tunic/test/test_combat.py @@ -4,7 +4,7 @@ from collections import Counter from . import TunicTestBase from .. import options from ..combat_logic import (check_combat_reqs, area_data, get_money_count, calc_effective_hp, get_potion_level, - get_hp_level, get_def_level, get_sp_level) + get_hp_level, get_def_level, get_sp_level, has_combat_reqs) from ..items import item_table from .. import TunicWorld @@ -81,3 +81,39 @@ class TestCombat(TunicTestBase): f"Free Def and Offerings: {player_def - def_offerings}, {def_offerings}\n" f"Free SP and Offerings: {player_sp - sp_offerings}, {sp_offerings}") prev_statuses[area] = curr_statuses[area] + + # the issue was that a direct check of the logic and the cache had different results + # it was actually due to the combat_items in items.py not having the Gun in it + # but this test is still helpful for verifying the cache + def test_combat_magic_weapons(self): + combat_items = self.combat_items.copy() + combat_items.remove("Magic Wand") + combat_items.remove("Gun") + area_names = list(area_data.keys()) + self.multiworld.worlds[1].random.shuffle(combat_items) + self.multiworld.worlds[1].random.shuffle(area_names) + current_items = Counter() + state = self.multiworld.state.copy() + player = self.player + gun = TunicWorld.create_item(self.world, "Gun") + + for current_item_name in combat_items: + current_item = TunicWorld.create_item(self.world, current_item_name) + state.collect(current_item) + current_items[current_item_name] += 1 + for area in area_names: + if check_combat_reqs(area, state, player) != has_combat_reqs(area, state, player): + raise Exception(f"Cache for {area} does not match a direct check " + f"after collecting {current_item_name}.\n" + f"Current items: {current_items}.\n" + f"Cache {'succeeded' if has_combat_reqs(area, state, player) else 'failed'}\n" + f"Direct {'succeeded' if check_combat_reqs(area, state, player) else 'failed'}") + state.collect(gun) + for area in area_names: + if check_combat_reqs(area, state, player) != has_combat_reqs(area, state, player): + raise Exception(f"Cache for {area} does not match a direct check " + f"after collecting the Gun.\n" + f"Current items: {current_items}.\n" + f"Cache {'succeeded' if has_combat_reqs(area, state, player) else 'failed'}\n" + f"Direct {'succeeded' if check_combat_reqs(area, state, player) else 'failed'}") + state.remove(gun)