Files
dockipelago/worlds/oribf/__init__.py
Jonathan Tinney 7971961166
Some checks failed
Analyze modified files / flake8 (push) Failing after 2m28s
Build / build-win (push) Has been cancelled
Build / build-ubuntu2204 (push) Has been cancelled
ctest / Test C++ ubuntu-latest (push) Has been cancelled
ctest / Test C++ windows-latest (push) Has been cancelled
Analyze modified files / mypy (push) Has been cancelled
Build and Publish Docker Images / Push Docker image to Docker Hub (push) Successful in 5m4s
Native Code Static Analysis / scan-build (push) Failing after 5m2s
type check / pyright (push) Successful in 1m7s
unittests / Test Python 3.11.2 ubuntu-latest (push) Failing after 16m23s
unittests / Test Python 3.12 ubuntu-latest (push) Failing after 28m19s
unittests / Test Python 3.13 ubuntu-latest (push) Failing after 14m49s
unittests / Test hosting with 3.13 on ubuntu-latest (push) Successful in 5m0s
unittests / Test Python 3.13 macos-latest (push) Has been cancelled
unittests / Test Python 3.11 windows-latest (push) Has been cancelled
unittests / Test Python 3.13 windows-latest (push) Has been cancelled
add schedule I, sonic 1/frontiers/heroes, spirit island
2026-04-02 23:46:36 -07:00

207 lines
9.9 KiB
Python

from typing import Dict, Any
from BaseClasses import ItemClassification, Region, Tutorial
from worlds.AutoWorld import World, WebWorld
from .Items import OriBlindForestItem, base_items, keystone_items, mapstone_items, filler_items, item_dict, item_alias_list
from .Locations import location_dict, tagged_locations_dict, area_tags, event_location_list
from .Options import OriBlindForestOptions, LogicDifficulty, KeystoneLogic, MapstoneLogic, Goal, slot_data_options
from .Rules import apply_location_rules, apply_connection_rules, create_progressive_maps
from .Regions import region_list
from ..generic.Rules import add_item_rule
class OriBFWeb(WebWorld):
theme = "ocean"
tutorials = [Tutorial(
"Multiworld Setup Guide",
"A guide to setting up the Ori and the Blind Forest software on your computer. This guide covers single-player, "
"multiworld, and related software.",
"English",
"oribf_en.md",
"oribf/en",
["c-ostic"]
)]
class OriBlindForestWorld(World):
# TODO: Make description
"""Insert description of the world/game here."""
game = "Ori and the Blind Forest"
options_dataclass = OriBlindForestOptions
options: OriBlindForestOptions
topology_present = True
web = OriBFWeb()
# TODO: Determine if this is a good number to use
base_id = 262144
item_name_to_id = {name: id for id, name in enumerate(item_dict, base_id)}
location_name_to_id = {name: id for id, name in enumerate(location_dict, base_id)}
item_name_groups = item_alias_list
location_name_groups = {location: set(tags) for location, tags in tagged_locations_dict.items()}
def generate_early(self):
self.logic_sets: set[str] = {"casual"} # always include at least casual
self.location_exclusion_list: list[str] = []
self.world_tour_areas: list[str] = []
self.world_tour_areas_unused: list[str] = area_tags.copy()
if self.options.logic_difficulty == LogicDifficulty.option_glitched:
self.logic_sets.add("glitched")
self.logic_sets.add("expert")
self.logic_sets.add("standard")
if self.options.logic_difficulty == LogicDifficulty.option_master:
self.logic_sets.add("master")
self.logic_sets.add("expert")
self.logic_sets.add("standard")
if self.options.logic_difficulty == LogicDifficulty.option_expert:
self.logic_sets.add("expert")
self.logic_sets.add("standard")
if self.options.logic_difficulty == LogicDifficulty.option_standard:
self.logic_sets.add("standard")
if self.options.mapstone_logic == MapstoneLogic.option_progressive:
self.location_exclusion_list.extend(tagged_locations_dict["Map"])
else:
self.location_exclusion_list.extend(event_location_list)
def create_region(self, name: str):
return Region(name, self.player, self.multiworld)
def create_regions(self) -> None:
menu = self.create_region("Menu")
self.multiworld.regions.append(menu)
for region_name in region_list:
region = self.create_region(region_name)
self.multiworld.regions.append(region)
# set the starting region
if region_name == "SunkenGladesRunaway":
menu.connect(region)
apply_connection_rules(self)
apply_location_rules(self)
if self.options.mapstone_logic == MapstoneLogic.option_progressive:
create_progressive_maps(self)
if self.options.restrict_dungeon_keys == True:
self.restrict_item("Ginso", "GinsoKey")
self.restrict_item("Forlorn", "ForlornKey")
self.restrict_item("Horu", "HoruKey")
def restrict_item(self, tag: str, item_name: str):
for location in tagged_locations_dict[tag]:
if location not in self.location_exclusion_list:
add_item_rule(self.get_location(location), lambda item: item.name != item_name)
def create_item(self, name: str) -> OriBlindForestItem:
return OriBlindForestItem(name, item_dict[name][0], self.item_name_to_id[name], self.player)
def create_items(self) -> None:
placed_first_energy_cell = False
item_list = dict(base_items)
item_count = 0
if self.options.keystone_logic == KeystoneLogic.option_area_specific:
item_list = { **item_list, **keystone_items["AreaSpecific"] }
else:
item_list = { **item_list, **keystone_items["Anywhere"] }
if self.options.mapstone_logic == MapstoneLogic.option_area_specific:
item_list = { **item_list, **mapstone_items["AreaSpecific"] }
else:
item_list = { **item_list, **mapstone_items["Anywhere"] }
for item_key, item_value in item_list.items():
# override the item values for counts that come from options
if item_key == "WarmthFragment" and self.options.goal == Goal.option_warmth_fragments:
item_value = (item_value[0], self.options.warmth_fragments_available.value)
if item_key == "Relic" and self.options.goal == Goal.option_world_tour:
item_value = (item_value[0], self.options.relic_count.value)
if item_key == "MapStone":
item_value = (item_value[0], item_value[1] + self.options.extra_mapstones.value)
for count in range(item_value[1]):
item = self.create_item(item_key)
# place the first energy cell at its normal location so the player can save right away
if item_key == "EnergyCell" and not placed_first_energy_cell:
self.get_location("FirstEnergyCell").place_locked_item(item)
placed_first_energy_cell = True
# place relics in a random location from a random area
# track which areas have been used in order to not have multiple relics in an area
elif item_key == "Relic":
random_area: str = self.random.choice(self.world_tour_areas_unused)
self.world_tour_areas_unused.remove(random_area)
self.world_tour_areas.append(random_area)
random_location: str = self.random.choice(tagged_locations_dict[random_area])
# the random location can't be an excluded location or "FirstEnergyCell" (since this was locked above)
while random_location in self.location_exclusion_list or random_location == "FirstEnergyCell":
random_location = self.random.choice(tagged_locations_dict[random_area])
self.get_location(random_location).place_locked_item(item)
# otherwise add the item normally
else:
self.multiworld.itempool.append(item)
item_count += 1
unfilled_locations = len(self.multiworld.get_unfilled_locations(self.player))
self.multiworld.itempool += [self.create_filler() for _ in range(unfilled_locations - item_count)]
def create_event(self, event: str) -> OriBlindForestItem:
return OriBlindForestItem(event, ItemClassification.progression, None, self.player)
def set_rules(self) -> None:
# most of the rules are set above in create_regions
# set the goal condition
if self.options.goal == Goal.option_all_skill_trees:
self.multiworld.completion_condition[self.player] = lambda state: \
state.can_reach_region("HoruEscapeInnerDoor", self.player) and \
all(state.can_reach_location(skill_tree, self.player) for skill_tree in tagged_locations_dict["Skill"])
elif self.options.goal == Goal.option_all_maps:
location_tag: str = ""
if self.options.mapstone_logic == MapstoneLogic.option_progressive:
location_tag = "ProgressiveMap"
else:
location_tag = "Map"
self.multiworld.completion_condition[self.player] = lambda state: \
state.can_reach_region("HoruEscapeInnerDoor", self.player) and \
all(state.can_reach_location(area_map, self.player) for area_map in tagged_locations_dict[location_tag])
elif self.options.goal == Goal.option_warmth_fragments:
# in case the required value is larger than the available, make the actual amount required equal to the available
fragments_required: int = min(self.options.warmth_fragments_available.value, self.options.warmth_fragments_required.value)
self.multiworld.completion_condition[self.player] = lambda state: \
state.can_reach_region("HoruEscapeInnerDoor", self.player) and \
state.has("WarmthFragment", self.player, fragments_required)
elif self.options.goal == Goal.option_world_tour:
self.multiworld.completion_condition[self.player] = lambda state: \
state.can_reach_region("HoruEscapeInnerDoor", self.player) and \
state.has("Relic", self.player, self.options.relic_count.value)
else: # self.options.goal == Goal.option_none
self.multiworld.completion_condition[self.player] = lambda state: \
state.can_reach_region("HoruEscapeInnerDoor", self.player)
def fill_slot_data(self) -> Dict[str, Any]:
slot_data: Dict[str, Any] = {}
for option_name, option_value in self.options.as_dict(*slot_data_options).items():
slot_data[option_name] = option_value
slot_data["world_tour_areas"] = self.world_tour_areas
return slot_data
def get_filler_item_name(self) -> str:
total = sum([item[1] for item in filler_items.values()])
weights = [item[1] / total for item in filler_items.values()]
return self.random.choices(list(filler_items.keys()), weights)[0]