def patch_resource_database( self, db: ResourceDatabase, configuration: BaseConfiguration) -> ResourceDatabase: base_damage_reduction = db.base_damage_reduction damage_reductions = copy.copy(db.damage_reductions) requirement_template = copy.copy(db.requirement_template) suits = [db.get_item_by_name("Varia Suit")] if configuration.heat_protection_only_varia: requirement_template["Heat-Resisting Suit"] = ResourceRequirement( db.get_item_by_name("Varia Suit"), 1, False) else: suits.extend([ db.get_item_by_name("Gravity Suit"), db.get_item_by_name("Phazon Suit") ]) reductions = [DamageReduction(None, configuration.heat_damage / 10.0)] reductions.extend([DamageReduction(suit, 0) for suit in suits]) damage_reductions[db.get_by_type_and_index(ResourceType.DAMAGE, "HeatDamage1")] = reductions if configuration.progressive_damage_reduction: base_damage_reduction = self.prime1_progressive_damage_reduction else: base_damage_reduction = self.prime1_absolute_damage_reduction return dataclasses.replace(db, damage_reductions=damage_reductions, base_damage_reduction=base_damage_reduction, requirement_template=requirement_template)
def create_stk_hints( all_patches: Dict[int, GamePatches], players_config: PlayersConfiguration, resource_database: ResourceDatabase, namer: HintNamer, hide_area: bool, ) -> list: """ Creates the string patches entries that changes the Sky Temple Gateway hint scans with hints for where the STK actually are. :param all_patches: :param players_config: :param resource_database: :param namer: :param hide_area: Should the hint include only the world? :return: """ resulting_hints = guaranteed_item_hint.create_guaranteed_hints_for_resources( all_patches, players_config, namer, hide_area, [ resource_database.get_item(index) for index in echoes_items.SKY_TEMPLE_KEY_ITEMS ], True, ) return [ create_simple_logbook_hint( _SKY_TEMPLE_KEY_SCAN_ASSETS[key_number], resulting_hints[resource_database.get_item(key_index)], ) for key_number, key_index in enumerate( echoes_items.SKY_TEMPLE_KEY_ITEMS) ]
def create_energy_cell( cell_index: int, resource_database: ResourceDatabase, ) -> PickupEntry: ENERGY_CELL_CATEGORY = ItemCategory(name="energy_cell", long_name="", hint_details=("an ", "energy cell"), is_major=False, is_key=True) return PickupEntry( name=f"Energy Cell {cell_index + 1}", progression=((resource_database.get_item( corruption_items.ENERGY_CELL_ITEMS[cell_index]), 1), ), extra_resources=( (resource_database.get_item( corruption_items.ENERGY_CELL_TOTAL_ITEM), 1), (resource_database.item_percentage, 1), ), model=PickupModel( game=resource_database.game_enum, name=corruption_items.ENERGY_CELL_MODEL, ), item_category=ENERGY_CELL_CATEGORY, broad_category=GENERIC_KEY_CATEGORY, probability_offset=0.25, )
def create_resource_lock(self, resource_database: ResourceDatabase) -> Optional[ResourceLock]: if self.unlocked_by is not None: return ResourceLock( locked_by=resource_database.get_item(self.unlocked_by), item_to_lock=resource_database.get_item(self.items[0]), temporary_item=resource_database.get_item(self.temporary), ) return None
def prime1_absolute_damage_reduction(self, db: ResourceDatabase, current_resources: ResourceCollection): if current_resources[db.get_item_by_name("Phazon Suit")] > 0: return 0.5 elif current_resources[db.get_item_by_name("Gravity Suit")] > 0: return 0.8 elif current_resources[db.get_item_by_name("Varia Suit")] > 0: return 0.9 else: return 1
def create_game_specific(self, resource_database: ResourceDatabase) -> EchoesBeamConfiguration: return EchoesBeamConfiguration( item=resource_database.get_item(self.item_index), ammo_a=resource_database.get_item(self.ammo_a) if self.ammo_a >= 0 else None, ammo_b=resource_database.get_item(self.ammo_b) if self.ammo_b >= 0 else None, uncharged_cost=self.uncharged_cost, charged_cost=self.charged_cost, combo_missile_cost=self.combo_missile_cost, combo_ammo_cost=self.combo_ammo_cost, )
def wrap(db: ResourceDatabase, data): if isinstance(data, dict): return { db.get_item(key): value for key, value in data.items() } else: return [ (db.get_item(key), value) for key, value in data ]
def read_beam_configuration(data: Dict, resource_database: ResourceDatabase, ) -> EchoesBeamConfiguration: return EchoesBeamConfiguration( item=resource_database.get_item(data["item_index"]), ammo_a=resource_database.get_item(data["ammo_a"]) if data["ammo_a"] is not None else None, ammo_b=resource_database.get_item(data["ammo_b"]) if data["ammo_b"] is not None else None, uncharged_cost=data["uncharged_cost"], charged_cost=data["charged_cost"], combo_missile_cost=data["combo_missile_cost"], combo_ammo_cost=data["combo_ammo_cost"], )
def patch_resource_database( self, db: ResourceDatabase, configuration: BaseConfiguration) -> ResourceDatabase: damage_reductions = copy.copy(db.damage_reductions) damage_reductions[db.get_by_type_and_index( ResourceType.DAMAGE, "DarkWorld1")] = [ DamageReduction(None, configuration.varia_suit_damage / 6.0), DamageReduction(db.get_item_by_name("Dark Suit"), configuration.dark_suit_damage / 6.0), DamageReduction(db.get_item_by_name("Light Suit"), 0.0), ] return dataclasses.replace(db, damage_reductions=damage_reductions)
def event_resources_for_configuration(self, configuration: BaseConfiguration, resource_database: ResourceDatabase, ) -> ResourceGain: assert isinstance(configuration, DreadConfiguration) if configuration.hanubia_shortcut_no_grapple: for name in ["s080_shipyard:default:grapplepulloff1x2_000", "s080_shipyard:default:grapplepulloff1x2"]: yield resource_database.get_event(name), 1 if configuration.hanubia_easier_path_to_itorash: yield resource_database.get_event("s080_shipyard:default:grapplepulloff1x2_001"), 1 if configuration.x_starts_released: yield resource_database.get_event("ElunReleaseX"), 1
def read_resource_database(game: RandovaniaGame, data: Dict) -> ResourceDatabase: reader = ResourceReader() item = read_dict(data["items"], reader.read_item_resource_info) db = ResourceDatabase( game_enum=game, item=item, event=reader.read_resource_info_array(data["events"], ResourceType.EVENT), trick=read_dict(data["tricks"], reader.read_trick_resource_info), damage=reader.read_resource_info_array(data["damage"], ResourceType.DAMAGE), version=reader.read_resource_info_array(data["versions"], ResourceType.VERSION), misc=reader.read_resource_info_array(data["misc"], ResourceType.MISC), requirement_template={}, damage_reductions={}, energy_tank_item_index=data["energy_tank_item_index"], item_percentage_index=data["item_percentage_index"], multiworld_magic_item_index=data["multiworld_magic_item_index"]) db.requirement_template.update( read_requirement_templates(data["requirement_template"], db)) db.damage_reductions.update( read_resource_reductions_dict(data["damage_reductions"], db)) return db
def gate_assignment_for_configuration(configuration: EchoesConfiguration, resource_database: ResourceDatabase, rng: Random, ) -> GateAssignment: """ :param configuration: :param resource_database: :param rng: :return: """ all_choices = list(LayoutTranslatorRequirement) all_choices.remove(LayoutTranslatorRequirement.RANDOM) all_choices.remove(LayoutTranslatorRequirement.RANDOM_WITH_REMOVED) without_removed = copy.copy(all_choices) without_removed.remove(LayoutTranslatorRequirement.REMOVED) random_requirements = {LayoutTranslatorRequirement.RANDOM, LayoutTranslatorRequirement.RANDOM_WITH_REMOVED} result = {} for gate, requirement in configuration.translator_configuration.translator_requirement.items(): if requirement in random_requirements: if rng is None: raise MissingRng("Translator") requirement = rng.choice(all_choices if requirement == LayoutTranslatorRequirement.RANDOM_WITH_REMOVED else without_removed) result[gate] = resource_database.get_by_type_and_index(ResourceType.ITEM, requirement.item_index) return result
def create_artifact(artifact_index: int, minimum_progression: int, resource_database: ResourceDatabase, ) -> PickupEntry: ARTIFACT_CATEGORY = ItemCategory( name="artifact", long_name="", hint_details=("an ", "artifact"), is_major=False, is_key=True ) return PickupEntry( name=prime_items.ARTIFACT_NAMES[artifact_index], progression=( (resource_database.get_item(prime_items.ARTIFACT_ITEMS[artifact_index]), 1), ), model=PickupModel( game=resource_database.game_enum, name=prime_items.ARTIFACT_MODEL[artifact_index], ), item_category=ARTIFACT_CATEGORY, broad_category=GENERIC_KEY_CATEGORY, probability_offset=0.25, required_progression=minimum_progression, )
def _create_resource_type_combo( current_resource_type: ResourceType, parent: QWidget, resource_database: ResourceDatabase) -> QComboBox: """ :param current_resource_type: :param parent: :return: """ resource_type_combo = QComboBox(parent) for resource_type in iterate_enum(ResourceType): try: count_elements = len(resource_database.get_by_type(resource_type)) except ValueError: count_elements = 0 if count_elements == 0: continue resource_type_combo.addItem(resource_type.name, resource_type) if resource_type is current_resource_type: resource_type_combo.setCurrentIndex(resource_type_combo.count() - 1) return resource_type_combo
def test_area_with_invalid_connections(): # Setup db = ResourceDatabase(RandovaniaGame.METROID_PRIME_ECHOES, [], [], [], [], [], [], {}, {}, 0, 0, 0) reader = WorldReader(db, None) reader.current_world_name = "World" with pytest.raises(MissingResource) as e: reader.read_area("Broken Area", { "extra": {}, "nodes": { "A": { "heal": True, "coordinates": None, "node_type": "generic", "connections": {}, "extra": {}, "layers": ["default"], "description": "", }, "Broken": { "heal": True, "coordinates": None, "node_type": "generic", "layers": ["default"], "extra": {}, "description": "", "connections": { "A": { "type": "resource", "data": { "type": "items", "name": "Dark", "amount": 1, "negate": False } } } }, } }) assert str(e.value) == ("In area Broken Area, connection from Broken to A got error: " "items Resource with short_name 'Dark' not found in 0 resources")
def test_area_with_invalid_connections(): # Setup db = ResourceDatabase([], [], [], [], [], [], [], {}) reader = WorldReader(db, None) with pytest.raises(MissingResource) as e: reader.read_area({ "name": "Broken Area", "nodes": [ {"name": "A", "heal": True, "node_type": "generic", "connections": {}}, {"name": "Broken", "heal": True, "node_type": "generic", "connections": { "A": { "type": "resource", "data": { "type": 0, "index": 1, "amount": 1, "negate": False } } }}, ] }) assert str(e.value) == ("In area Broken Area, connection from Broken to A got error: " "ResourceType.ITEM Resource with index 1 not found in []")
def gate_assignment_for_configuration( configuration: LayoutConfiguration, resource_database: ResourceDatabase, rng: Random, ) -> GateAssignment: """ :param configuration: :param resource_database: :param rng: :return: """ choices = list(LayoutTranslatorRequirement) choices.remove(LayoutTranslatorRequirement.RANDOM) result = {} for gate, requirement in configuration.translator_configuration.translator_requirement.items( ): if requirement == LayoutTranslatorRequirement.RANDOM: if rng is None: raise MissingRng("Translator") requirement = rng.choice(choices) result[gate] = resource_database.get_by_type_and_index( ResourceType.ITEM, requirement.item_index) return result
def create_ammo_expansion( ammo: Ammo, ammo_count: Sequence[int], requires_major_item: bool, resource_database: ResourceDatabase, ) -> PickupEntry: """ Creates a Pickup for an expansion of the given ammo. :param ammo: :param ammo_count: :param requires_major_item: :param resource_database: :return: """ resources = [(resource_database.get_item(item), count) for item, count in zip(ammo.items, ammo_count)] if resource_database.item_percentage is not None: resources.append((resource_database.item_percentage, 1)) return PickupEntry( name=ammo.name, progression=(), extra_resources=tuple(resources), model=PickupModel( game=resource_database.game_enum, name=ammo.model_name, ), item_category=ammo.item_category, broad_category=ammo.broad_category, respects_lock=requires_major_item, resource_lock=ammo.create_resource_lock(resource_database), probability_multiplier=2, )
def create_sky_temple_key( key_number: int, resource_database: ResourceDatabase, ) -> PickupEntry: """ :param key_number: :param resource_database: :return: """ SKY_TEMPLE_KEY_CATEGORY = ItemCategory(name="sky_temple_key", long_name="", hint_details=("a ", "Sky Temple Key"), is_major=False, is_key=True) return PickupEntry( name="Sky Temple Key {}".format(key_number + 1), progression=((resource_database.get_item( echoes_items.SKY_TEMPLE_KEY_ITEMS[key_number]), 1), ), model=PickupModel( game=resource_database.game_enum, name=echoes_items.SKY_TEMPLE_KEY_MODEL, ), item_category=SKY_TEMPLE_KEY_CATEGORY, broad_category=GENERIC_KEY_CATEGORY, probability_offset=3, )
def create_dark_temple_key( key_number: int, temple_index: int, resource_database: ResourceDatabase, ) -> PickupEntry: """ Creates a Dark Temple Key :param key_number: :param temple_index: The index of the temple: Dark Agon, Dark Torvus, Hive Temple :param resource_database: :return: """ TEMPLE_KEY_CATEGORY = ItemCategory(name="temple_key", long_name="", hint_details=("a ", "red Temple Key"), is_major=False, is_key=True) return PickupEntry( name=echoes_items.DARK_TEMPLE_KEY_NAMES[temple_index].format( key_number + 1), progression=((resource_database.get_item( echoes_items.DARK_TEMPLE_KEY_ITEMS[temple_index][key_number]), 1), ), model=PickupModel( game=resource_database.game_enum, name=echoes_items.DARK_TEMPLE_KEY_MODEL, ), item_category=TEMPLE_KEY_CATEGORY, broad_category=GENERIC_KEY_CATEGORY, probability_offset=3, )
def with_data(cls, database: ResourceDatabase, resource_type: ResourceType, requirement_name: str, amount: int, negate: bool) -> ResourceRequirement: return cls.create( database.get_by_type_and_index(resource_type, requirement_name), amount, negate, )
def damage(self, current_resources: CurrentResources, database: ResourceDatabase) -> int: if self.resource.resource_type == ResourceType.DAMAGE: return ceil( database.get_damage_reduction(self.resource, current_resources) * self.amount) else: return 0
def _starting_items_value_for(resource_database: ResourceDatabase, starting_items: CurrentResources, index: str) -> Union[bool, int]: item = resource_database.get_item(index) value = starting_items.get(item, 0) if item.max_capacity > 1: return value else: return value > 0
def __init__(self, parent: QWidget, item: MajorItem, starting_state: MajorItemState, resources_database: ResourceDatabase): super().__init__(parent) self.setupUi(self) set_default_window_icon(self) self._item = item self.setWindowTitle(f"Item: {item.name}") self.item_name_label.setText(item.name) # connect self.excluded_radio.toggled.connect(self._on_select_excluded) self.vanilla_radio.toggled.connect(self._on_select_vanilla) self.starting_radio.toggled.connect(self._on_select_starting) self.shuffled_radio.toggled.connect(self._on_select_shuffled) self.shuffled_spinbox.valueChanged.connect(self._on_shuffled_value) self.provided_ammo_spinbox.valueChanged.connect(self._on_shuffled_value) # Update self.vanilla_radio.setEnabled(item.original_index is not None) self.shuffled_radio.setEnabled(item.model_index is not None) if not self.vanilla_radio.isEnabled(): self.vanilla_radio.setToolTip( "This item does not exist in the original game, so there's no vanilla location.") if not self.shuffled_radio.isEnabled(): self.shuffled_radio.setToolTip( "There's currently no available model and/or logic for this item to be shuffled.") # At least one radio should be selected for radio in (self.shuffled_radio, self.starting_radio, self.vanilla_radio): if radio.isEnabled(): radio.setChecked(True) break if item.ammo_index: self.provided_ammo_label.setText( "<html><head/><body><p>Provided Ammo ({})</p></body></html>".format( " and ".join( resources_database.get_item(ammo_index).long_name for ammo_index in item.ammo_index ) ) ) else: self.provided_ammo_label.hide() self.provided_ammo_spinbox.hide() if self._item.required: self.item_name_label.setToolTip( "This item is necessary for the game to function properly and can't be removed.") self.setEnabled(False) self.state = self._create_state(num_included_in_starting_items=1) else: if self._item.warning is not None: self.item_name_label.setToolTip(self._item.warning) self.state = starting_state
def _starting_items_value_for(resource_database: ResourceDatabase, starting_items: ResourceCollection, index: str) -> Union[bool, int]: item = resource_database.get_item(index) value = starting_items[item] if item.max_capacity > 1: return value else: return value > 0
def read_resource_reductions_dict( data: List[Dict], db: ResourceDatabase, ) -> Dict[SimpleResourceInfo, List[DamageReduction]]: return { db.get_by_type_and_index(ResourceType.DAMAGE, item["name"]): read_damage_reductions(item["reductions"], db.item) for item in data }
def with_data(cls, database: ResourceDatabase, resource_type: ResourceType, requirement_index: int, amount: int, negate: bool) -> "IndividualRequirement": return cls( database.get_by_type_and_index(resource_type, requirement_index), amount, negate)
def create_ammo_expansion( ammo: Ammo, ammo_count: List[int], requires_major_item: bool, resource_database: ResourceDatabase, ) -> PickupEntry: """ Creates a Pickup for an expansion of the given ammo. :param ammo: :param ammo_count: :param requires_major_item: :param resource_database: :return: """ resources = [(resource_database.get_item(item), count) for item, count in zip(ammo.items, ammo_count)] resources.append((resource_database.item_percentage, 1)) if ammo.unlocked_by is not None and requires_major_item: temporary_resources = [ (resource_database.get_item(item), count) for item, count in zip(ammo.temporaries, ammo_count) ] temporary_resources.append((resource_database.item_percentage, 1)) conditional_resources = ( ConditionalResources(temporary_resources[0][0].long_name, None, tuple(temporary_resources)), ConditionalResources(ammo.name, resource_database.get_item(ammo.unlocked_by), tuple(resources)), ) else: conditional_resources = (ConditionalResources(None, None, tuple(resources)), ) return PickupEntry( name=ammo.name, resources=conditional_resources, model_index=ammo.models[0], # TODO: use a random model item_category=ItemCategory.EXPANSION, broad_category=ammo.broad_category, )
def prime1_progressive_damage_reduction(self, db: ResourceDatabase, current_resources: ResourceCollection): num_suits = sum(current_resources[db.get_item_by_name(suit)] for suit in ["Varia Suit", "Gravity Suit", "Phazon Suit"]) if num_suits >= 3: return 0.5 elif num_suits == 2: return 0.8 elif num_suits == 1: return 0.9 else: return 1
def read_resource_database(data: Dict) -> ResourceDatabase: item = read_resource_info_array(data["items"], ResourceType.ITEM) return ResourceDatabase( item=item, event=read_resource_info_array(data["events"], ResourceType.EVENT), trick=read_resource_info_array(data["tricks"], ResourceType.TRICK), damage=read_damage_resource_info_array(data["damage"], item), version=read_resource_info_array(data["versions"], ResourceType.VERSION), misc=read_resource_info_array(data["misc"], ResourceType.MISC), difficulty=read_resource_info_array(data["difficulty"], ResourceType.DIFFICULTY), )