forked from mirror/Archipelago
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
207 lines
9.9 KiB
Python
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] |