Skip to content

Commit

Permalink
Noita: implement new game (ArchipelagoMW#1676)
Browse files Browse the repository at this point in the history
* 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
7 people authored Apr 20, 2023
1 parent 722757e commit 4dc9347
Show file tree
Hide file tree
Showing 10 changed files with 962 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Currently, the following games are supported:
* Clique
* Adventure
* DLC Quest
* Noita

For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled
Expand Down
42 changes: 42 additions & 0 deletions worlds/noita/Events.py
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",
}
156 changes: 156 additions & 0 deletions worlds/noita/Items.py
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)
}
Loading

0 comments on commit 4dc9347

Please sign in to comment.