forked from ArchipelagoMW/Archipelago
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Noita: implement new game (ArchipelagoMW#1676)
* Noita: implement new game (ArchipelagoMW#1676) --------- Co-authored-by: DaftBrit <[email protected]> Co-authored-by: [email protected] <[email protected]> Co-authored-by: Fabian Dill <[email protected]> Co-authored-by: Scipio Wright <[email protected]> Co-authored-by: Scipio Wright <[email protected]> Co-authored-by: Zach Parks <[email protected]>
- Loading branch information
1 parent
722757e
commit 4dc9347
Showing
10 changed files
with
962 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
from typing import Dict | ||
|
||
from BaseClasses import Item, ItemClassification, Location, MultiWorld, Region | ||
from . import Items, Locations | ||
|
||
|
||
def create_event(player: int, name: str) -> Item: | ||
return Items.NoitaItem(name, ItemClassification.progression, None, player) | ||
|
||
|
||
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) | ||
|
||
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) | ||
|
||
multiworld.completion_condition[player] = lambda state: state.has("Victory", player) | ||
|
||
|
||
# Maps region names to event names | ||
event_locks: Dict[str, str] = { | ||
"The Work": "Victory", | ||
"Mines": "Portal to Holy Mountain 1", | ||
"Coal Pits": "Portal to Holy Mountain 2", | ||
"Snowy Depths": "Portal to Holy Mountain 3", | ||
"Hiisi Base": "Portal to Holy Mountain 4", | ||
"Underground Jungle": "Portal to Holy Mountain 5", | ||
"The Vault": "Portal to Holy Mountain 6", | ||
"Temple of the Art": "Portal to Holy Mountain 7", | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
import itertools | ||
from collections import Counter | ||
from typing import Dict, List, NamedTuple, Optional, Set | ||
|
||
from BaseClasses import Item, ItemClassification, MultiWorld | ||
from .Options import BossesAsChecks, VictoryCondition | ||
|
||
|
||
class ItemData(NamedTuple): | ||
code: Optional[int] | ||
group: str | ||
classification: ItemClassification = ItemClassification.progression | ||
required_num: int = 0 | ||
|
||
|
||
class NoitaItem(Item): | ||
game: str = "Noita" | ||
|
||
|
||
def create_item(player: int, name: str) -> Item: | ||
item_data = item_table[name] | ||
return NoitaItem(name, item_data.classification, item_data.code, player) | ||
|
||
|
||
def create_fixed_item_pool() -> List[str]: | ||
required_items: Dict[str, int] = {name: data.required_num for name, data in item_table.items()} | ||
return list(Counter(required_items).elements()) | ||
|
||
|
||
def create_orb_items(victory_condition: VictoryCondition) -> List[str]: | ||
orb_count = 0 | ||
if victory_condition == VictoryCondition.option_pure_ending: | ||
orb_count = 11 | ||
elif victory_condition == VictoryCondition.option_peaceful_ending: | ||
orb_count = 33 | ||
return ["Orb" for _ in range(orb_count)] | ||
|
||
|
||
def create_spatial_awareness_item(bosses_as_checks: BossesAsChecks) -> List[str]: | ||
return ["Spatial Awareness Perk"] if bosses_as_checks.value >= BossesAsChecks.option_all_bosses else [] | ||
|
||
|
||
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]: | ||
filler_pool = filler_weights.copy() | ||
if multiworld.bad_effects[player].value == 0: | ||
del filler_pool["Trap"] | ||
|
||
return multiworld.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)) | ||
|
||
itempool = ( | ||
create_fixed_item_pool() | ||
+ create_orb_items(multiworld.victory_condition[player]) | ||
+ create_spatial_awareness_item(multiworld.bosses_as_checks[player]) | ||
+ create_kantele(multiworld.victory_condition[player]) | ||
) | ||
|
||
random_count = sum_locations - len(itempool) | ||
itempool += create_random_items(multiworld, player, random_count) | ||
|
||
multiworld.itempool += [create_item(player, name) for name in itempool] | ||
|
||
|
||
# 110000 - 110032 | ||
item_table: Dict[str, ItemData] = { | ||
"Trap": ItemData(110000, "Traps", ItemClassification.trap), | ||
"Extra Max HP": ItemData(110001, "Pickups", ItemClassification.useful), | ||
"Spell Refresher": ItemData(110002, "Pickups", ItemClassification.filler), | ||
"Potion": ItemData(110003, "Items", ItemClassification.filler), | ||
"Gold (200)": ItemData(110004, "Gold", ItemClassification.filler), | ||
"Gold (1000)": ItemData(110005, "Gold", ItemClassification.filler), | ||
"Wand (Tier 1)": ItemData(110006, "Wands", ItemClassification.useful), | ||
"Wand (Tier 2)": ItemData(110007, "Wands", ItemClassification.useful), | ||
"Wand (Tier 3)": ItemData(110008, "Wands", ItemClassification.useful), | ||
"Wand (Tier 4)": ItemData(110009, "Wands", ItemClassification.useful), | ||
"Wand (Tier 5)": ItemData(110010, "Wands", ItemClassification.useful), | ||
"Wand (Tier 6)": ItemData(110011, "Wands", ItemClassification.useful), | ||
"Kantele": ItemData(110012, "Wands", ItemClassification.useful), | ||
"Fire Immunity Perk": ItemData(110013, "Perks", ItemClassification.progression, 1), | ||
"Toxic Immunity Perk": ItemData(110014, "Perks", ItemClassification.progression, 1), | ||
"Explosion Immunity Perk": ItemData(110015, "Perks", ItemClassification.progression, 1), | ||
"Melee Immunity Perk": ItemData(110016, "Perks", ItemClassification.progression, 1), | ||
"Electricity Immunity Perk": ItemData(110017, "Perks", ItemClassification.progression, 1), | ||
"Tinker with Wands Everywhere Perk": ItemData(110018, "Perks", ItemClassification.progression, 1), | ||
"All-Seeing Eye Perk": ItemData(110019, "Perks", ItemClassification.progression, 1), | ||
"Spatial Awareness Perk": ItemData(110020, "Perks", ItemClassification.progression), | ||
"Extra Life Perk": ItemData(110021, "Repeatable Perks", ItemClassification.useful), | ||
"Orb": ItemData(110022, "Orbs", ItemClassification.progression_skip_balancing), | ||
"Random Potion": ItemData(110023, "Items", ItemClassification.filler), | ||
"Secret Potion": ItemData(110024, "Items", ItemClassification.filler), | ||
"Powder Pouch": ItemData(110025, "Items", ItemClassification.filler), | ||
"Chaos Die": ItemData(110026, "Items", ItemClassification.filler), | ||
"Greed Die": ItemData(110027, "Items", ItemClassification.filler), | ||
"Kammi": ItemData(110028, "Items", ItemClassification.filler), | ||
"Refreshing Gourd": ItemData(110029, "Items", ItemClassification.filler), | ||
"Sädekivi": ItemData(110030, "Items", ItemClassification.filler), | ||
"Broken Wand": ItemData(110031, "Items", ItemClassification.filler), | ||
|
||
} | ||
|
||
filler_weights: Dict[str, int] = { | ||
"Trap": 15, | ||
"Extra Max HP": 25, | ||
"Spell Refresher": 20, | ||
"Potion": 40, | ||
"Gold (200)": 15, | ||
"Gold (1000)": 6, | ||
"Wand (Tier 1)": 10, | ||
"Wand (Tier 2)": 8, | ||
"Wand (Tier 3)": 7, | ||
"Wand (Tier 4)": 6, | ||
"Wand (Tier 5)": 5, | ||
"Wand (Tier 6)": 4, | ||
"Extra Life Perk": 10, | ||
"Random Potion": 9, | ||
"Secret Potion": 10, | ||
"Powder Pouch": 10, | ||
"Chaos Die": 4, | ||
"Greed Die": 4, | ||
"Kammi": 4, | ||
"Refreshing Gourd": 4, | ||
"Sädekivi": 3, | ||
"Broken Wand": 10, | ||
} | ||
|
||
|
||
# These helper functions make the comprehensions below more readable | ||
def get_item_group(item_name: str) -> str: | ||
return item_table[item_name].group | ||
|
||
|
||
def item_is_filler(item_name: str) -> bool: | ||
return item_table[item_name].classification == ItemClassification.filler | ||
|
||
|
||
def item_is_perk(item_name: str) -> bool: | ||
return item_table[item_name].group == "Perks" | ||
|
||
|
||
filler_items: List[str] = list(filter(item_is_filler, item_table.keys())) | ||
item_name_to_id: Dict[str, int] = {name: data.code for name, data in item_table.items()} | ||
|
||
item_name_groups: Dict[str, Set[str]] = { | ||
group: set(item_names) for group, item_names in itertools.groupby(item_table, get_item_group) | ||
} |
Oops, something went wrong.