From 9dc352f82967c2dd74f2cd57be8d3503b8471696 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Sun, 4 Aug 2024 17:51:19 -0400 Subject: [PATCH] Swap to using logicmixin instead of prog_items (thanks Vi) --- worlds/tunic/__init__.py | 14 +++++- worlds/tunic/combat_logic.py | 83 +++++++++++++++++------------------- 2 files changed, 51 insertions(+), 46 deletions(-) diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py index cbca847c8a..4eeb1a5cf4 100644 --- a/worlds/tunic/__init__.py +++ b/worlds/tunic/__init__.py @@ -11,6 +11,7 @@ from .er_scripts import create_er_regions from .er_data import portal_mapping from .options import (TunicOptions, EntranceRando, tunic_option_groups, tunic_option_presets, TunicPlandoConnections, LaurelsLocation, LogicRules, LaurelsZips, IceGrappling, LadderStorage) +from .combat_logic import area_data, CombatState from worlds.AutoWorld import WebWorld, World from Options import PlandoConnection from decimal import Decimal, ROUND_HALF_UP @@ -127,6 +128,15 @@ class TunicWorld(World): def stage_generate_early(cls, multiworld: MultiWorld) -> None: tunic_worlds: Tuple[TunicWorld] = multiworld.get_game_worlds("TUNIC") for tunic in tunic_worlds: + # setting up state combat logic stuff, see has_combat_reqs for its use + # and this is magic so pycharm doesn't like it, unfortunately + if tunic.options.combat_logic: + multiworld.state.tunic_need_to_reset_combat_from_collect[tunic.player] = False + multiworld.state.tunic_need_to_reset_combat_from_remove[tunic.player] = False + multiworld.state.tunic_area_combat_state[tunic.player] = {} + for area_name in area_data.keys(): + multiworld.state.tunic_area_combat_state[tunic.player][area_name] = CombatState.unchecked + # if it's one of the options, then it isn't a custom seed group if tunic.options.entrance_rando.value in EntranceRando.options.values(): continue @@ -352,13 +362,13 @@ class TunicWorld(World): def collect(self, state: CollectionState, item: Item) -> bool: change = super().collect(state, item) if change and self.options.combat_logic and item.name in combat_items: - state.prog_items[self.player]["need_to_reset_combat_state_from_collect"] = 1 + state.tunic_need_to_reset_combat_from_collect[self.player] = True return change def remove(self, state: CollectionState, item: Item) -> bool: change = super().remove(state, item) if change and self.options.combat_logic and item.name in combat_items: - state.prog_items[self.player]["need_to_reset_combat_state_from_remove"] = 1 + state.tunic_need_to_reset_combat_from_remove[self.player] = True return change def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]) -> None: diff --git a/worlds/tunic/combat_logic.py b/worlds/tunic/combat_logic.py index d9d4dd7a7f..b322274d6a 100644 --- a/worlds/tunic/combat_logic.py +++ b/worlds/tunic/combat_logic.py @@ -2,6 +2,7 @@ from typing import Dict, List, NamedTuple, Tuple, Optional from enum import IntEnum from BaseClasses import CollectionState from .rules import has_sword, has_melee +from worlds.AutoWorld import LogicMixin # the vanilla stats you are expected to have to get through an area, based on where they are in vanilla @@ -59,34 +60,23 @@ class CombatState(IntEnum): def has_combat_reqs(area_name: str, state: CollectionState, player: int) -> bool: # we're caching whether you've met the combat reqs before if the state didn't change first - player_state = state.prog_items[player] # if the combat state is stale, mark each area's combat state as stale - if player_state["need_to_reset_combat_state_from_collect"]: - player_state["need_to_reset_combat_state_from_collect"] = 0 - for name in boss_areas: - if player_state["combat_reqs_met_for_" + name] == CombatState.failed: - player_state["combat_reqs_met_for_" + name] = CombatState.unchecked - for name in non_boss_areas: - if player_state["combat_reqs_met_for_" + name] == CombatState.failed: - player_state["combat_reqs_met_for_" + name] = CombatState.unchecked - if player_state["combat_reqs_met_for_Gauntlet"] == CombatState.failed: - player_state["combat_reqs_met_for_Gauntlet"] = CombatState.unchecked + if state.tunic_need_to_reset_combat_from_collect[player]: + state.tunic_need_to_reset_combat_from_collect[player] = 0 + for name in area_data.keys(): + if state.tunic_area_combat_state[player][name] == CombatState.failed: + state.tunic_area_combat_state[player][name] = CombatState.unchecked - if player_state["need_to_reset_combat_state_from_remove"]: - player_state["need_to_reset_combat_state_from_remove"] = 0 - for name in boss_areas: - if player_state["combat_reqs_met_for_" + name] == CombatState.succeeded: - player_state["combat_reqs_met_for_" + name] = CombatState.unchecked - for name in non_boss_areas: - if player_state["combat_reqs_met_for_" + name] == CombatState.succeeded: - player_state["combat_reqs_met_for_" + name] = CombatState.unchecked - if player_state["combat_reqs_met_for_Gauntlet"] == CombatState.succeeded: - player_state["combat_reqs_met_for_Gauntlet"] = CombatState.unchecked + if state.tunic_need_to_reset_combat_from_remove[player]: + state.tunic_need_to_reset_combat_from_remove[player] = 0 + for name in area_data.keys(): + if state.tunic_area_combat_state[player][name] == CombatState.succeeded: + state.tunic_area_combat_state[player][name] = CombatState.unchecked + + if state.tunic_area_combat_state[player][area_name] > CombatState.unchecked: + return state.tunic_area_combat_state[player][area_name] == CombatState.succeeded met_combat_reqs = check_combat_reqs(area_name, state, player) - if player_state["combat_reqs_met_for_" + area_name] > CombatState.unchecked: - return met_combat_reqs - # loop through the lists and set the easier/harder area states accordingly if area_name in boss_areas: area_list = boss_areas @@ -95,30 +85,25 @@ def has_combat_reqs(area_name: str, state: CollectionState, player: int) -> bool else: area_list = [area_name] - if area_name in area_list: - if met_combat_reqs: - # set the state as true for each area until you get to the area we're looking at - for name in area_list: - player_state["combat_reqs_met_for_" + name] = CombatState.succeeded - if name == area_name: - break - else: - # set the state as false for the area we're looking at and each area after that - reached_name = False - for name in area_list: - if name == area_name: - reached_name = True - if reached_name: - player_state["combat_reqs_met_for_" + name] = CombatState.failed + if met_combat_reqs: + # set the state as true for each area until you get to the area we're looking at + for name in area_list: + state.tunic_area_combat_state[player][name] = CombatState.succeeded + if name == area_name: + break + else: + # set the state as false for the area we're looking at and each area after that + reached_name = False + for name in area_list: + if name == area_name: + reached_name = True + if reached_name: + state.tunic_area_combat_state[player][name] = CombatState.failed - return check_combat_reqs(area_name, state, player) + return met_combat_reqs def check_combat_reqs(area_name: str, state: CollectionState, player: int, alt_data: Optional[AreaStats] = None) -> bool: - # if our cache says we've already calced this, we don't need to go through these calculations again - if state.prog_items[player]["combat_reqs_met_for_" + area_name] > CombatState.unchecked: - return state.prog_items[player]["combat_reqs_met_for_" + area_name] > CombatState.failed - data = alt_data or area_data[area_name] extra_att_needed = 0 extra_def_needed = 0 @@ -413,3 +398,13 @@ def get_money_count(state: CollectionState, player: int) -> int: money += money_per_break money_per_break = min(512, money_per_break * 2) return money + + +class TunicState(LogicMixin): + # the per-player need to reset the combat state when collecting a combat item + tunic_need_to_reset_combat_from_collect: Dict[int, bool] = {} + # the per-player need to reset the combat state when removing a combat item + tunic_need_to_reset_combat_from_remove: Dict[int, bool] = {} + # the per-player, per-area state of combat checking -- unchecked, failed, or succeeded + tunic_area_combat_state: Dict[int, Dict[str, int]] = {} +