Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Noita: Update to use new Options API #2370

Merged
merged 17 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions worlds/noita/Events.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,20 @@ def create_location(player: int, name: str, region: Region) -> Location:
return Locations.NoitaLocation(player, name, None, region)


def create_locked_location_event(multiworld: MultiWorld, player: int, region_name: str, item: str) -> Location:
region = multiworld.get_region(region_name, player)

def create_locked_location_event(player: int, region: Region, item: str) -> Location:
new_location = create_location(player, item, region)
new_location.place_locked_item(create_event(player, item))

region.locations.append(new_location)
return new_location


def create_all_events(multiworld: MultiWorld, player: int) -> None:
for region, event in event_locks.items():
create_locked_location_event(multiworld, player, region, event)
def create_all_events(world, created_regions: Dict[str, Region]) -> None:
ScipioWright marked this conversation as resolved.
Show resolved Hide resolved
for region_name, event in event_locks.items():
region = created_regions[region_name]
create_locked_location_event(world.player, region, event)

multiworld.completion_condition[player] = lambda state: state.has("Victory", player)
world.multiworld.completion_condition[world.player] = lambda state: state.has("Victory", world.player)


# Maps region names to event names
Expand Down
29 changes: 17 additions & 12 deletions worlds/noita/Items.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import itertools
from collections import Counter
from typing import Dict, List, NamedTuple, Set
from typing import Dict, List, NamedTuple, Set, TYPE_CHECKING

from BaseClasses import Item, ItemClassification, MultiWorld
from BaseClasses import Item, ItemClassification
from .Options import BossesAsChecks, VictoryCondition, ExtraOrbs

if TYPE_CHECKING:
from . import NoitaWorld
else:
NoitaWorld = object


class ItemData(NamedTuple):
code: int
Expand Down Expand Up @@ -44,32 +49,32 @@ def create_kantele(victory_condition: VictoryCondition) -> List[str]:
return ["Kantele"] if victory_condition.value >= VictoryCondition.option_pure_ending else []


def create_random_items(multiworld: MultiWorld, player: int, random_count: int) -> List[str]:
def create_random_items(world: NoitaWorld, random_count: int) -> List[str]:
filler_pool = filler_weights.copy()
if multiworld.bad_effects[player].value == 0:
if world.options.bad_effects.value == 0:
ScipioWright marked this conversation as resolved.
Show resolved Hide resolved
del filler_pool["Trap"]

return multiworld.random.choices(
return world.random.choices(
population=list(filler_pool.keys()),
weights=list(filler_pool.values()),
k=random_count
)


def create_all_items(multiworld: MultiWorld, player: int) -> None:
sum_locations = len(multiworld.get_unfilled_locations(player))
def create_all_items(world: NoitaWorld) -> None:
sum_locations = len(world.multiworld.get_unfilled_locations(world.player))

itempool = (
create_fixed_item_pool()
+ create_orb_items(multiworld.victory_condition[player], multiworld.extra_orbs[player])
+ create_spatial_awareness_item(multiworld.bosses_as_checks[player])
+ create_kantele(multiworld.victory_condition[player])
+ create_orb_items(world.options.victory_condition, world.options.extra_orbs)
+ create_spatial_awareness_item(world.options.bosses_as_checks)
+ create_kantele(world.options.victory_condition)
)

random_count = sum_locations - len(itempool)
itempool += create_random_items(multiworld, player, random_count)
itempool += create_random_items(world, random_count)

multiworld.itempool += [create_item(player, name) for name in itempool]
world.multiworld.itempool += [create_item(world.player, name) for name in itempool]


# 110000 - 110032
Expand Down
30 changes: 15 additions & 15 deletions worlds/noita/Options.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import Dict
from Options import AssembleOptions, Choice, DeathLink, DefaultOnToggle, Range, StartInventoryPool
from Options import Choice, DeathLink, DefaultOnToggle, Range, StartInventoryPool, PerGameCommonOptions
from dataclasses import dataclass


class PathOption(Choice):
Expand Down Expand Up @@ -99,16 +99,16 @@ class ShopPrice(Choice):
default = 100


noita_options: Dict[str, AssembleOptions] = {
"start_inventory_from_pool": StartInventoryPool,
"death_link": DeathLink,
"bad_effects": Traps,
"victory_condition": VictoryCondition,
"path_option": PathOption,
"hidden_chests": HiddenChests,
"pedestal_checks": PedestalChecks,
"orbs_as_checks": OrbsAsChecks,
"bosses_as_checks": BossesAsChecks,
"extra_orbs": ExtraOrbs,
"shop_price": ShopPrice,
}
@dataclass
class NoitaOptions(PerGameCommonOptions):
start_inventory_from_pool: StartInventoryPool
death_link: DeathLink
bad_effects: Traps
victory_condition: VictoryCondition
path_option: PathOption
hidden_chests: HiddenChests
pedestal_checks: PedestalChecks
orbs_as_checks: OrbsAsChecks
bosses_as_checks: BossesAsChecks
extra_orbs: ExtraOrbs
shop_price: ShopPrice
46 changes: 27 additions & 19 deletions worlds/noita/Regions.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
# Regions are areas in your game that you travel to.
from typing import Dict, Set
from typing import Dict, Set, TYPE_CHECKING

from BaseClasses import Entrance, MultiWorld, Region
from BaseClasses import Entrance, Region
from . import Locations
from .Events import create_all_events

if TYPE_CHECKING:
from . import NoitaWorld
else:
NoitaWorld = object

def add_location(player: int, loc_name: str, id: int, region: Region) -> None:
location = Locations.NoitaLocation(player, loc_name, id, region)

def add_location(player: int, loc_name: str, loc_id: int, region: Region) -> None:
location = Locations.NoitaLocation(player, loc_name, loc_id, region)
region.locations.append(location)
ScipioWright marked this conversation as resolved.
Show resolved Hide resolved


def add_locations(multiworld: MultiWorld, player: int, region: Region) -> None:
def add_locations(world: NoitaWorld, region: Region) -> None:
player = world.player
locations = Locations.location_region_mapping.get(region.name, {})
for location_name, location_data in locations.items():
location_type = location_data.ltype
flag = location_data.flag

opt_orbs = multiworld.orbs_as_checks[player].value
opt_bosses = multiworld.bosses_as_checks[player].value
opt_paths = multiworld.path_option[player].value
opt_num_chests = multiworld.hidden_chests[player].value
opt_num_pedestals = multiworld.pedestal_checks[player].value
opt_orbs = world.options.orbs_as_checks.value
ScipioWright marked this conversation as resolved.
Show resolved Hide resolved
opt_bosses = world.options.bosses_as_checks.value
opt_paths = world.options.path_option.value
opt_num_chests = world.options.hidden_chests.value
opt_num_pedestals = world.options.pedestal_checks.value

is_orb_allowed = location_type == "orb" and flag <= opt_orbs
is_boss_allowed = location_type == "boss" and flag <= opt_bosses
Expand All @@ -35,14 +42,14 @@ def add_locations(multiworld: MultiWorld, player: int, region: Region) -> None:


# Creates a new Region with the locations found in `location_region_mapping` and adds them to the world.
def create_region(multiworld: MultiWorld, player: int, region_name: str) -> Region:
new_region = Region(region_name, player, multiworld)
add_locations(multiworld, player, new_region)
def create_region(world: NoitaWorld, region_name: str) -> Region:
new_region = Region(region_name, world.player, world.multiworld)
add_locations(world, new_region)
ScipioWright marked this conversation as resolved.
Show resolved Hide resolved
return new_region


def create_regions(multiworld: MultiWorld, player: int) -> Dict[str, Region]:
return {name: create_region(multiworld, player, name) for name in noita_regions}
def create_regions(world: NoitaWorld) -> Dict[str, Region]:
ScipioWright marked this conversation as resolved.
Show resolved Hide resolved
return {name: create_region(world, name) for name in noita_regions}


# An "Entrance" is really just a connection between two regions
Expand All @@ -60,11 +67,12 @@ def create_connections(player: int, regions: Dict[str, Region]) -> None:


# Creates all regions and connections. Called from NoitaWorld.
def create_all_regions_and_connections(multiworld: MultiWorld, player: int) -> None:
created_regions = create_regions(multiworld, player)
create_connections(player, created_regions)
def create_all_regions_and_connections(world: NoitaWorld) -> None:
created_regions = create_regions(world)
create_connections(world.player, created_regions)
create_all_events(world, created_regions)

multiworld.regions += created_regions.values()
world.multiworld.regions += created_regions.values()


# Oh, what a tangled web we weave
Expand Down
91 changes: 48 additions & 43 deletions worlds/noita/Rules.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
from typing import List, NamedTuple, Set
from typing import List, NamedTuple, Set, Optional, TYPE_CHECKING

from BaseClasses import CollectionState, MultiWorld
from BaseClasses import CollectionState
from . import Items, Locations
from .Options import BossesAsChecks, VictoryCondition
from worlds.generic import Rules as GenericRules

if TYPE_CHECKING:
from . import NoitaWorld
else:
NoitaWorld = object


class EntranceLock(NamedTuple):
source: str
Expand Down Expand Up @@ -66,9 +71,9 @@ def has_orb_count(state: CollectionState, player: int, amount: int) -> bool:
return state.item_count("Orb", player) >= amount


def forbid_items_at_location(multiworld: MultiWorld, location_name: str, items: Set[str], player: int):
location = multiworld.get_location(location_name, player)
GenericRules.forbid_items_for_player(location, items, player)
def forbid_items_at_location(world: NoitaWorld, location_name: str, items: Optional[Set[str] | List[str]]):
location = world.multiworld.get_location(location_name, world.player)
ScipioWright marked this conversation as resolved.
Show resolved Hide resolved
GenericRules.forbid_items_for_player(location, items, world.player)


# ----------------
Expand All @@ -77,91 +82,91 @@ def forbid_items_at_location(multiworld: MultiWorld, location_name: str, items:


# Prevent gold and potions from appearing as purchasable items in shops (because physics will destroy them)
def ban_items_from_shops(multiworld: MultiWorld, player: int) -> None:
def ban_items_from_shops(world: NoitaWorld) -> None:
for location_name in Locations.location_name_to_id.keys():
if "Shop Item" in location_name:
forbid_items_at_location(multiworld, location_name, items_hidden_from_shops, player)
forbid_items_at_location(world, location_name, items_hidden_from_shops)


# Prevent high tier wands from appearing in early Holy Mountain shops
def ban_early_high_tier_wands(multiworld: MultiWorld, player: int) -> None:
def ban_early_high_tier_wands(world: NoitaWorld) -> None:
for i, region_name in enumerate(holy_mountain_regions):
wands_to_forbid = wand_tiers[i+1:]

locations_in_region = Locations.location_region_mapping[region_name].keys()
for location_name in locations_in_region:
forbid_items_at_location(multiworld, location_name, wands_to_forbid, player)
forbid_items_at_location(world, location_name, wands_to_forbid)

# Prevent high tier wands from appearing in the Secret shop
wands_to_forbid = wand_tiers[3:]
locations_in_region = Locations.location_region_mapping["Secret Shop"].keys()
for location_name in locations_in_region:
forbid_items_at_location(multiworld, location_name, wands_to_forbid, player)
forbid_items_at_location(world, location_name, wands_to_forbid)


def lock_holy_mountains_into_spheres(multiworld: MultiWorld, player: int) -> None:
def lock_holy_mountains_into_spheres(world: NoitaWorld) -> None:
for lock in entrance_locks:
location = multiworld.get_entrance(f"From {lock.source} To {lock.destination}", player)
GenericRules.set_rule(location, lambda state, evt=lock.event: state.has(evt, player))
location = world.multiworld.get_entrance(f"From {lock.source} To {lock.destination}", world.player)
GenericRules.set_rule(location, lambda state, evt=lock.event: state.has(evt, world.player))


def holy_mountain_unlock_conditions(multiworld: MultiWorld, player: int) -> None:
victory_condition = multiworld.victory_condition[player].value
def holy_mountain_unlock_conditions(world: NoitaWorld) -> None:
victory_condition = world.options.victory_condition.value
for lock in entrance_locks:
location = multiworld.get_location(lock.event, player)
location = world.multiworld.get_location(lock.event, world.player)

if victory_condition == VictoryCondition.option_greed_ending:
location.access_rule = lambda state, items_needed=lock.items_needed: (
has_perk_count(state, player, items_needed//2)
has_perk_count(state, world.player, items_needed//2)
)
elif victory_condition == VictoryCondition.option_pure_ending:
location.access_rule = lambda state, items_needed=lock.items_needed: (
has_perk_count(state, player, items_needed//2) and
has_orb_count(state, player, items_needed)
has_perk_count(state, world.player, items_needed//2) and
has_orb_count(state, world.player, items_needed)
)
elif victory_condition == VictoryCondition.option_peaceful_ending:
location.access_rule = lambda state, items_needed=lock.items_needed: (
has_perk_count(state, player, items_needed//2) and
has_orb_count(state, player, items_needed * 3)
has_perk_count(state, world.player, items_needed//2) and
has_orb_count(state, world.player, items_needed * 3)
)


def biome_unlock_conditions(multiworld: MultiWorld, player: int):
lukki_entrances = multiworld.get_region("Lukki Lair", player).entrances
magical_entrances = multiworld.get_region("Magical Temple", player).entrances
wizard_entrances = multiworld.get_region("Wizards' Den", player).entrances
def biome_unlock_conditions(world: NoitaWorld):
lukki_entrances = world.multiworld.get_region("Lukki Lair", world.player).entrances
magical_entrances = world.multiworld.get_region("Magical Temple", world.player).entrances
wizard_entrances = world.multiworld.get_region("Wizards' Den", world.player).entrances
for entrance in lukki_entrances:
entrance.access_rule = lambda state: state.has("Melee Immunity Perk", player) and\
state.has("All-Seeing Eye Perk", player)
entrance.access_rule = lambda state: state.has("Melee Immunity Perk", world.player) and\
state.has("All-Seeing Eye Perk", world.player)
for entrance in magical_entrances:
entrance.access_rule = lambda state: state.has("All-Seeing Eye Perk", player)
entrance.access_rule = lambda state: state.has("All-Seeing Eye Perk", world.player)
for entrance in wizard_entrances:
entrance.access_rule = lambda state: state.has("All-Seeing Eye Perk", player)
entrance.access_rule = lambda state: state.has("All-Seeing Eye Perk", world.player)


def victory_unlock_conditions(multiworld: MultiWorld, player: int) -> None:
victory_condition = multiworld.victory_condition[player].value
victory_location = multiworld.get_location("Victory", player)
def victory_unlock_conditions(world: NoitaWorld) -> None:
victory_condition = world.options.victory_condition.value
victory_location = world.multiworld.get_location("Victory", world.player)

if victory_condition == VictoryCondition.option_pure_ending:
victory_location.access_rule = lambda state: has_orb_count(state, player, 11)
victory_location.access_rule = lambda state: has_orb_count(state, world.player, 11)
elif victory_condition == VictoryCondition.option_peaceful_ending:
victory_location.access_rule = lambda state: has_orb_count(state, player, 33)
victory_location.access_rule = lambda state: has_orb_count(state, world.player, 33)


# ----------------
# Main Function
# ----------------


def create_all_rules(multiworld: MultiWorld, player: int) -> None:
ban_items_from_shops(multiworld, player)
ban_early_high_tier_wands(multiworld, player)
lock_holy_mountains_into_spheres(multiworld, player)
holy_mountain_unlock_conditions(multiworld, player)
biome_unlock_conditions(multiworld, player)
victory_unlock_conditions(multiworld, player)
def create_all_rules(world: NoitaWorld) -> None:
ban_items_from_shops(world)
ban_early_high_tier_wands(world)
lock_holy_mountains_into_spheres(world)
holy_mountain_unlock_conditions(world)
biome_unlock_conditions(world)
victory_unlock_conditions(world)

# Prevent the Map perk (used to find Toveri) from being on Toveri (boss)
if multiworld.bosses_as_checks[player].value >= BossesAsChecks.option_all_bosses:
forbid_items_at_location(multiworld, "Toveri", {"Spatial Awareness Perk"}, player)
if world.options.bosses_as_checks.value >= BossesAsChecks.option_all_bosses:
forbid_items_at_location(world, "Toveri", {"Spatial Awareness Perk"})
Loading
Loading