Archipelago/worlds/undertale/__init__.py

231 lines
12 KiB
Python

from .Items import UndertaleItem, item_table, required_armor, required_weapons, non_key_items, key_items, \
junk_weights_all, plot_items, junk_weights_neutral, junk_weights_pacifist, junk_weights_genocide
from .Locations import UndertaleAdvancement, advancement_table, exclusion_table
from .Regions import undertale_regions, link_undertale_areas
from .Rules import set_rules, set_completion_rules
from worlds.generic.Rules import exclusion_rules
from BaseClasses import Region, Entrance, Tutorial, Item
from .Options import undertale_options
from worlds.AutoWorld import World, WebWorld
from worlds.LauncherComponents import Component, components, Type
from multiprocessing import Process
def run_client():
print('running undertale client')
from .UndertaleClient import main # lazy import
p = Process(target=main)
p.start()
components.append(Component("Undertale Client", "UndertaleClient"))
# components.append(Component("Undertale Client", func=run_client))
def data_path(file_name: str):
import pkgutil
return pkgutil.get_data(__name__, "data/" + file_name)
class UndertaleWeb(WebWorld):
tutorials = [Tutorial(
"Multiworld Setup Guide",
"A guide to setting up the Archipelago Undertale software on your computer. This guide covers "
"single-player, multiworld, and related software.",
"English",
"setup_en.md",
"setup/en",
["Mewlif"]
)]
class UndertaleWorld(World):
"""
Undertale is an RPG where every choice you make matters. You could choose to hurt all the enemies, eventually
causing genocide of the monster species. Or you can spare all the enemies, befriending them and freeing them
from their underground prison.
"""
game = "Undertale"
option_definitions = undertale_options
web = UndertaleWeb()
item_name_to_id = {name: data.code for name, data in item_table.items()}
location_name_to_id = {name: data.id for name, data in advancement_table.items()}
data_version = 7
def _get_undertale_data(self):
return {
"world_seed": self.multiworld.per_slot_randoms[self.player].getrandbits(32),
"seed_name": self.multiworld.seed_name,
"player_name": self.multiworld.get_player_name(self.player),
"player_id": self.player,
"client_version": self.required_client_version,
"race": self.multiworld.is_race,
"route": self.multiworld.route_required[self.player].current_key,
"starting_area": self.multiworld.starting_area[self.player].current_key,
"temy_armor_include": bool(self.multiworld.temy_include[self.player].value),
"only_flakes": bool(self.multiworld.only_flakes[self.player].value),
"no_equips": bool(self.multiworld.no_equips[self.player].value),
"key_hunt": bool(self.multiworld.key_hunt[self.player].value),
"key_pieces": self.multiworld.key_pieces[self.player].value,
"rando_love": bool(self.multiworld.rando_love[self.player].value),
"rando_stats": bool(self.multiworld.rando_stats[self.player].value),
"prog_armor": bool(self.multiworld.prog_armor[self.player].value),
"prog_weapons": bool(self.multiworld.prog_weapons[self.player].value),
"rando_item_button": bool(self.multiworld.rando_item_button[self.player].value)
}
def create_items(self):
self.multiworld.get_location("Undyne Date", self.player).place_locked_item(self.create_item("Undyne Date"))
self.multiworld.get_location("Alphys Date", self.player).place_locked_item(self.create_item("Alphys Date"))
self.multiworld.get_location("Papyrus Date", self.player).place_locked_item(self.create_item("Papyrus Date"))
# Generate item pool
itempool = []
if self.multiworld.route_required[self.player] == "all_routes":
junk_pool = junk_weights_all.copy()
elif self.multiworld.route_required[self.player] == "genocide":
junk_pool = junk_weights_genocide.copy()
elif self.multiworld.route_required[self.player] == "neutral":
junk_pool = junk_weights_neutral.copy()
elif self.multiworld.route_required[self.player] == "pacifist":
junk_pool = junk_weights_pacifist.copy()
else:
junk_pool = junk_weights_all.copy()
# Add all required progression items
for name, num in key_items.items():
itempool += [name] * num
for name, num in required_armor.items():
itempool += [name] * num
for name, num in required_weapons.items():
itempool += [name] * num
for name, num in non_key_items.items():
itempool += [name] * num
if self.multiworld.rando_item_button[self.player]:
itempool += ["ITEM"]
else:
self.multiworld.push_precollected(self.create_item("ITEM"))
self.multiworld.push_precollected(self.create_item("FIGHT"))
self.multiworld.push_precollected(self.create_item("ACT"))
self.multiworld.push_precollected(self.create_item("MERCY"))
if self.multiworld.route_required[self.player] == "genocide":
itempool = [item for item in itempool if item != "Popato Chisps" and item != "Stained Apron" and
item != "Nice Cream" and item != "Hot Cat" and item != "Hot Dog...?" and item != "Punch Card"]
elif self.multiworld.route_required[self.player] == "neutral":
itempool = [item for item in itempool if item != "Popato Chisps" and item != "Hot Cat" and
item != "Hot Dog...?"]
if self.multiworld.route_required[self.player] == "pacifist" or \
self.multiworld.route_required[self.player] == "all_routes":
itempool += ["Undyne Letter EX"]
else:
itempool.remove("Complete Skeleton")
itempool.remove("Fish")
itempool.remove("DT Extractor")
itempool.remove("Hush Puppy")
if self.multiworld.key_hunt[self.player]:
itempool += ["Key Piece"] * self.multiworld.key_pieces[self.player].value
else:
itempool += ["Left Home Key"]
itempool += ["Right Home Key"]
if not self.multiworld.rando_love[self.player] or \
(self.multiworld.route_required[self.player] != "genocide" and
self.multiworld.route_required[self.player] != "all_routes"):
itempool = [item for item in itempool if not item == "LOVE"]
if not self.multiworld.rando_stats[self.player] or \
(self.multiworld.route_required[self.player] != "genocide" and
self.multiworld.route_required[self.player] != "all_routes"):
itempool = [item for item in itempool if not (item == "ATK Up" or item == "DEF Up" or item == "HP Up")]
if self.multiworld.temy_include[self.player]:
itempool += ["temy armor"]
if self.multiworld.no_equips[self.player]:
itempool = [item for item in itempool if item not in required_armor and item not in required_weapons]
else:
if self.multiworld.prog_armor[self.player]:
itempool = [item if (item not in required_armor and not item == "temy armor") else
"Progressive Armor" for item in itempool]
if self.multiworld.prog_weapons[self.player]:
itempool = [item if item not in required_weapons else "Progressive Weapons" for item in itempool]
if self.multiworld.route_required[self.player] == "genocide" or \
self.multiworld.route_required[self.player] == "all_routes":
if not self.multiworld.only_flakes[self.player]:
itempool += ["Snowman Piece"] * 2
if not self.multiworld.no_equips[self.player]:
itempool = ["Real Knife" if item == "Worn Dagger" else "The Locket"
if item == "Heart Locket" else item for item in itempool]
if self.multiworld.only_flakes[self.player]:
itempool = [item for item in itempool if item not in non_key_items]
starting_key = self.multiworld.starting_area[self.player].current_key.title() + " Key"
itempool.remove(starting_key)
self.multiworld.push_precollected(self.create_item(starting_key))
# Choose locations to automatically exclude based on settings
exclusion_pool = set()
exclusion_pool.update(exclusion_table[self.multiworld.route_required[self.player].current_key])
if not self.multiworld.rando_love[self.player] or \
(self.multiworld.route_required[self.player] != "genocide" and
self.multiworld.route_required[self.player] != "all_routes"):
exclusion_pool.update(exclusion_table["NoLove"])
if not self.multiworld.rando_stats[self.player] or \
(self.multiworld.route_required[self.player] != "genocide" and
self.multiworld.route_required[self.player] != "all_routes"):
exclusion_pool.update(exclusion_table["NoStats"])
# Choose locations to automatically exclude based on settings
exclusion_checks = set()
exclusion_checks.update(["Nicecream Punch Card", "Hush Trade"])
exclusion_rules(self.multiworld, self.player, exclusion_checks)
# Fill remaining items with randomly generated junk or Temmie Flakes
if not self.multiworld.only_flakes[self.player]:
itempool += self.multiworld.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()),
k=len(self.location_names)-len(itempool)-len(exclusion_pool))
else:
itempool += ["Temmie Flakes"] * (len(self.location_names) - len(itempool) - len(exclusion_pool))
# Convert itempool into real items
itempool = [item for item in map(lambda name: self.create_item(name), itempool)]
self.multiworld.itempool += itempool
def set_rules(self):
set_rules(self.multiworld, self.player)
set_completion_rules(self.multiworld, self.player)
def create_regions(self):
def UndertaleRegion(region_name: str, exits=[]):
ret = Region(region_name, self.player, self.multiworld)
ret.locations += [UndertaleAdvancement(self.player, loc_name, loc_data.id, ret)
for loc_name, loc_data in advancement_table.items()
if loc_data.region == region_name and
(loc_name not in exclusion_table["NoStats"] or
(self.multiworld.rando_stats[self.player] and
(self.multiworld.route_required[self.player] == "genocide" or
self.multiworld.route_required[self.player] == "all_routes"))) and
(loc_name not in exclusion_table["NoLove"] or
(self.multiworld.rando_love[self.player] and
(self.multiworld.route_required[self.player] == "genocide" or
self.multiworld.route_required[self.player] == "all_routes"))) and
loc_name not in exclusion_table[self.multiworld.route_required[self.player].current_key]]
for exit in exits:
ret.exits.append(Entrance(self.player, exit, ret))
return ret
self.multiworld.regions += [UndertaleRegion(*r) for r in undertale_regions]
link_undertale_areas(self.multiworld, self.player)
def fill_slot_data(self):
slot_data = self._get_undertale_data()
for option_name in undertale_options:
option = getattr(self.multiworld, option_name)[self.player]
if (option_name == "rando_love" or option_name == "rando_stats") and \
self.multiworld.route_required[self.player] != "genocide" and \
self.multiworld.route_required[self.player] != "all_routes":
option.value = False
if slot_data.get(option_name, None) is None and type(option.value) in {str, int}:
slot_data[option_name] = int(option.value)
return slot_data
def create_item(self, name: str) -> Item:
item_data = item_table[name]
item = UndertaleItem(name, item_data.classification, item_data.code, self.player)
return item