Archipelago/worlds/blasphemous/__init__.py

413 lines
16 KiB
Python

from typing import Dict, Set, Any
from collections import Counter
from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification
from ..AutoWorld import World, WebWorld
from .Items import base_id, item_table, group_table, tears_set, reliquary_set, skill_set
from .Locations import location_table, shop_set
from .Exits import region_exit_table, exit_lookup_table
from .Rules import rules
from worlds.generic.Rules import set_rule
from .Options import blasphemous_options
from . import Vanilla
class BlasphemousWeb(WebWorld):
theme = "stone"
tutorials = [Tutorial(
"Multiworld Setup Guide",
"A guide to setting up the Blasphemous randomizer connected to an Archipelago Multiworld",
"English",
"setup_en.md",
"setup/en",
["TRPG"]
)]
class BlasphemousWorld(World):
"""
Blasphemous is a challenging Metroidvania set in the cursed land of Cvstodia. Play as the Penitent One, trapped
in an endless cycle of death and rebirth, and free the world from it's terrible fate in your quest to break
your eternal damnation!
"""
game: str = "Blasphemous"
web = BlasphemousWeb()
data_version: 1
item_name_to_id = {item["name"]: (base_id + index) for index, item in enumerate(item_table)}
location_name_to_id = {loc["name"]: (base_id + index) for index, loc in enumerate(location_table)}
location_name_to_game_id = {loc["name"]: loc["game_id"] for loc in location_table}
item_name_groups = group_table
option_definitions = blasphemous_options
def set_rules(self):
rules(self)
def create_item(self, name: str) -> "BlasphemousItem":
item_id: int = self.item_name_to_id[name]
id = item_id - base_id
return BlasphemousItem(name, item_table[id]["classification"], item_id, player=self.player)
def create_event(self, event: str):
return BlasphemousItem(event, ItemClassification.progression_skip_balancing, None, self.player)
def get_filler_item_name(self) -> str:
return self.multiworld.random.choice(tears_set)
def generate_basic(self):
placed_items = []
placed_items.extend(Vanilla.unrandomized_dict.values())
if not self.multiworld.reliquary_shuffle[self.player]:
placed_items.extend(reliquary_set)
elif self.multiworld.reliquary_shuffle[self.player]:
placed_items.append("Tears of Atonement (250)")
placed_items.append("Tears of Atonement (300)")
placed_items.append("Tears of Atonement (500)")
if not self.multiworld.cherub_shuffle[self.player]:
for i in range(38):
placed_items.append("Child of Moonlight")
if not self.multiworld.life_shuffle[self.player]:
for i in range(6):
placed_items.append("Life Upgrade")
if not self.multiworld.fervour_shuffle[self.player]:
for i in range(6):
placed_items.append("Fervour Upgrade")
if not self.multiworld.sword_shuffle[self.player]:
for i in range(7):
placed_items.append("Mea Culpa Upgrade")
if not self.multiworld.blessing_shuffle[self.player]:
placed_items.extend(Vanilla.blessing_dict.values())
if not self.multiworld.dungeon_shuffle[self.player]:
placed_items.extend(Vanilla.dungeon_dict.values())
if not self.multiworld.tirso_shuffle[self.player]:
placed_items.extend(Vanilla.tirso_dict.values())
if not self.multiworld.miriam_shuffle[self.player]:
placed_items.append("Cantina of the Blue Rose")
if not self.multiworld.redento_shuffle[self.player]:
placed_items.extend(Vanilla.redento_dict.values())
if not self.multiworld.jocinero_shuffle[self.player]:
placed_items.extend(Vanilla.jocinero_dict.values())
if not self.multiworld.altasgracias_shuffle[self.player]:
placed_items.extend(Vanilla.altasgracias_dict.values())
if not self.multiworld.tentudia_shuffle[self.player]:
placed_items.extend(Vanilla.tentudia_dict.values())
if not self.multiworld.gemino_shuffle[self.player]:
placed_items.extend(Vanilla.gemino_dict.values())
if not self.multiworld.guilt_shuffle[self.player]:
placed_items.append("Weight of True Guilt")
if not self.multiworld.ossuary_shuffle[self.player]:
placed_items.extend(Vanilla.ossuary_dict.values())
if not self.multiworld.boss_shuffle[self.player]:
placed_items.extend(Vanilla.boss_dict.values())
if not self.multiworld.wound_shuffle[self.player]:
placed_items.extend(Vanilla.wound_dict.values())
if not self.multiworld.mask_shuffle[self.player]:
placed_items.extend(Vanilla.mask_dict.values())
if not self.multiworld.eye_shuffle[self.player]:
placed_items.extend(Vanilla.eye_dict.values())
if not self.multiworld.herb_shuffle[self.player]:
placed_items.extend(Vanilla.herb_dict.values())
if not self.multiworld.church_shuffle[self.player]:
placed_items.extend(Vanilla.church_dict.values())
if not self.multiworld.shop_shuffle[self.player]:
placed_items.extend(Vanilla.shop_dict.values())
if self.multiworld.thorn_shuffle[self.player] == 2:
for i in range(8):
placed_items.append("Thorn Upgrade")
if not self.multiworld.candle_shuffle[self.player]:
placed_items.extend(Vanilla.candle_dict.values())
if self.multiworld.start_wheel[self.player]:
placed_items.append("The Young Mason's Wheel")
if not self.multiworld.skill_randomizer[self.player]:
placed_items.extend(Vanilla.skill_dict.values())
counter = Counter(placed_items)
pool = []
for item in item_table:
count = item["count"] - counter[item["name"]]
if count <= 0:
continue
else:
for i in range(count):
pool.append(self.create_item(item["name"]))
self.multiworld.itempool += pool
def pre_fill(self):
self.place_items_from_dict(Vanilla.unrandomized_dict)
if not self.multiworld.cherub_shuffle[self.player]:
self.place_items_from_set(Vanilla.cherub_set, "Child of Moonlight")
if not self.multiworld.life_shuffle[self.player]:
self.place_items_from_set(Vanilla.life_set, "Life Upgrade")
if not self.multiworld.fervour_shuffle[self.player]:
self.place_items_from_set(Vanilla.fervour_set, "Fervour Upgrade")
if not self.multiworld.sword_shuffle[self.player]:
self.place_items_from_set(Vanilla.sword_set, "Mea Culpa Upgrade")
if not self.multiworld.blessing_shuffle[self.player]:
self.place_items_from_dict(Vanilla.blessing_dict)
if not self.multiworld.dungeon_shuffle[self.player]:
self.place_items_from_dict(Vanilla.dungeon_dict)
if not self.multiworld.tirso_shuffle[self.player]:
self.place_items_from_dict(Vanilla.tirso_dict)
if not self.multiworld.miriam_shuffle[self.player]:
self.multiworld.get_location("AtTotS: Miriam's gift", self.player)\
.place_locked_item(self.create_item("Cantina of the Blue Rose"))
if not self.multiworld.redento_shuffle[self.player]:
self.place_items_from_dict(Vanilla.redento_dict)
if not self.multiworld.jocinero_shuffle[self.player]:
self.place_items_from_dict(Vanilla.jocinero_dict)
if not self.multiworld.altasgracias_shuffle[self.player]:
self.place_items_from_dict(Vanilla.altasgracias_dict)
if not self.multiworld.tentudia_shuffle[self.player]:
self.place_items_from_dict(Vanilla.tentudia_dict)
if not self.multiworld.gemino_shuffle[self.player]:
self.place_items_from_dict(Vanilla.gemino_dict)
if not self.multiworld.guilt_shuffle[self.player]:
self.multiworld.get_location("GotP: Confessor Dungeon room", self.player)\
.place_locked_item(self.create_item("Weight of True Guilt"))
if not self.multiworld.ossuary_shuffle[self.player]:
self.place_items_from_dict(Vanilla.ossuary_dict)
if not self.multiworld.boss_shuffle[self.player]:
self.place_items_from_dict(Vanilla.boss_dict)
if not self.multiworld.wound_shuffle[self.player]:
self.place_items_from_dict(Vanilla.wound_dict)
if not self.multiworld.mask_shuffle[self.player]:
self.place_items_from_dict(Vanilla.mask_dict)
if not self.multiworld.eye_shuffle[self.player]:
self.place_items_from_dict(Vanilla.eye_dict)
if not self.multiworld.herb_shuffle[self.player]:
self.place_items_from_dict(Vanilla.herb_dict)
if not self.multiworld.church_shuffle[self.player]:
self.place_items_from_dict(Vanilla.church_dict)
if not self.multiworld.shop_shuffle[self.player]:
self.place_items_from_dict(Vanilla.shop_dict)
if self.multiworld.thorn_shuffle[self.player] == 2:
self.place_items_from_set(Vanilla.thorn_set, "Thorn Upgrade")
if not self.multiworld.candle_shuffle[self.player]:
self.place_items_from_dict(Vanilla.candle_dict)
if self.multiworld.start_wheel[self.player]:
self.multiworld.get_location("BotSS: Beginning gift", self.player)\
.place_locked_item(self.create_item("The Young Mason's Wheel"))
if not self.multiworld.skill_randomizer[self.player]:
self.place_items_from_dict(Vanilla.skill_dict)
if self.multiworld.thorn_shuffle[self.player] == 1:
self.multiworld.local_items[self.player].value.add("Thorn Upgrade")
def place_items_from_set(self, location_set: Set[str], name: str):
for loc in location_set:
self.multiworld.get_location(loc, self.player)\
.place_locked_item(self.create_item(name))
def place_items_from_dict(self, option_dict: Dict[str, str]):
for loc, item in option_dict.items():
self.multiworld.get_location(loc, self.player)\
.place_locked_item(self.create_item(item))
def create_regions(self) -> None:
player = self.player
world = self.multiworld
region_table: Dict[str, Region] = {
"menu" : Region("Menu", player, world),
"albero" : Region("Albero", player, world),
"attots" : Region("All the Tears of the Sea", player, world),
"ar" : Region("Archcathedral Rooftops", player, world),
"bottc" : Region("Bridge of the Three Cavalries", player, world),
"botss" : Region("Brotherhood of the Silent Sorrow", player, world),
"coolotcv": Region("Convent of Our Lady of the Charred Visage", player, world),
"dohh" : Region("Deambulatory of His Holiness", player, world),
"dc" : Region("Desecrated Cistern", player, world),
"eos" : Region("Echoes of Salt", player, world),
"ft" : Region("Ferrous Tree", player, world),
"gotp" : Region("Graveyard of the Peaks", player, world),
"ga" : Region("Grievance Ascends", player, world),
"hotd" : Region("Hall of the Dawning", player, world),
"jondo" : Region("Jondo", player, world),
"kottw" : Region("Knot of the Three Words", player, world),
"lotnw" : Region("Library of the Negated Words", player, world),
"md" : Region("Mercy Dreams", player, world),
"mom" : Region("Mother of Mothers", player, world),
"moted" : Region("Mountains of the Endless Dusk", player, world),
"mah" : Region("Mourning and Havoc", player, world),
"potss" : Region("Patio of the Silent Steps", player, world),
"petrous" : Region("Petrous", player, world),
"thl" : Region("The Holy Line", player, world),
"trpots" : Region("The Resting Place of the Sister", player, world),
"tsc" : Region("The Sleeping Canvases", player, world),
"wothp" : Region("Wall of the Holy Prohibitions", player, world),
"wotbc" : Region("Wasteland of the Buried Churches", player, world),
"wotw" : Region("Where Olive Trees Wither", player, world),
"dungeon" : Region("Dungeons", player, world)
}
for rname, reg in region_table.items():
world.regions.append(reg)
for ename, exits in region_exit_table.items():
if ename == rname:
for i in exits:
ent = Entrance(player, i, reg)
reg.exits.append(ent)
for e, r in exit_lookup_table.items():
if i == e:
ent.connect(region_table[r])
for loc in location_table:
id = base_id + location_table.index(loc)
region_table[loc["region"]].locations\
.append(BlasphemousLocation(self.player, loc["name"], id, region_table[loc["region"]]))
victory = Location(self.player, "His Holiness Escribar", None, self.multiworld.get_region("Deambulatory of His Holiness", self.player))
victory.place_locked_item(self.create_event("Victory"))
self.multiworld.get_region("Deambulatory of His Holiness", self.player).locations.append(victory)
if self.multiworld.ending[self.player].value == 1:
set_rule(victory, lambda state: state.has("Thorn Upgrade", player, 8))
elif self.multiworld.ending[self.player].value == 2:
set_rule(victory, lambda state: state.has("Thorn Upgrade", player, 8) and \
state.has("Holy Wound of Abnegation", player))
self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
def fill_slot_data(self) -> Dict[str, Any]:
slot_data: Dict[str, Any] = {}
locations = []
for loc in self.multiworld.get_filled_locations(self.player):
if loc.name == "His Holiness Escribar":
continue
else:
data = {
"id": self.location_name_to_game_id[loc.name],
"ap_id": loc.address,
"name": loc.item.name,
"player_name": self.multiworld.player_name[loc.item.player]
}
if loc.name in shop_set:
data["type"] = loc.item.classification.name
locations.append(data)
config = {
"versionCreated": "AP",
"general": {
"teleportationAlwaysUnlocked": bool(self.multiworld.prie_dieu_warp[self.player].value),
"skipCutscenes": bool(self.multiworld.skip_cutscenes[self.player].value),
"enablePenitence": bool(self.multiworld.penitence[self.player].value),
"hardMode": False,
"customSeed": 0,
"allowHints": bool(self.multiworld.corpse_hints[self.player].value)
},
"items": {
"type": 1,
"lungDamage": False,
"disableNPCDeath": True,
"startWithWheel": bool(self.multiworld.start_wheel[self.player].value),
"shuffleReliquaries": bool(self.multiworld.reliquary_shuffle[self.player].value)
},
"enemies": {
"type": self.multiworld.enemy_randomizer[self.player].value,
"maintainClass": bool(self.multiworld.enemy_groups[self.player].value),
"areaScaling": bool(self.multiworld.enemy_scaling[self.player].value)
},
"prayers": {
"type": 0,
"removeMirabis": False
},
"doors": {
"type": 0
},
"debug": {
"type": 0
}
}
slot_data = {
"locations": locations,
"cfg": config,
"ending": self.multiworld.ending[self.player].value,
"death_link": bool(self.multiworld.death_link[self.player].value)
}
return slot_data
class BlasphemousItem(Item):
game: str = "Blasphemous"
class BlasphemousLocation(Location):
game: str = "Blasphemous"