232 lines
11 KiB
Python
232 lines
11 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 UndertaleOptions
|
|
from worlds.AutoWorld import World, WebWorld
|
|
from worlds.LauncherComponents import Component, components
|
|
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"
|
|
options_dataclass = UndertaleOptions
|
|
options: UndertaleOptions
|
|
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()}
|
|
|
|
def _get_undertale_data(self):
|
|
return {
|
|
"world_seed": self.random.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.options.route_required.current_key,
|
|
"starting_area": self.options.starting_area.current_key,
|
|
"temy_armor_include": bool(self.options.temy_include.value),
|
|
"only_flakes": bool(self.options.only_flakes.value),
|
|
"no_equips": bool(self.options.no_equips.value),
|
|
"key_hunt": bool(self.options.key_hunt.value),
|
|
"key_pieces": int(self.options.key_pieces.value),
|
|
"rando_love": bool(self.options.rando_love and (self.options.route_required == "genocide" or self.options.route_required == "all_routes")),
|
|
"rando_stats": bool(self.options.rando_stats and (self.options.route_required == "genocide" or self.options.route_required == "all_routes")),
|
|
"prog_armor": bool(self.options.prog_armor.value),
|
|
"prog_weapons": bool(self.options.prog_weapons.value),
|
|
"rando_item_button": bool(self.options.rando_item_button.value),
|
|
"route_required": int(self.options.route_required.value),
|
|
"temy_include": int(self.options.temy_include.value)
|
|
|
|
}
|
|
|
|
def get_filler_item_name(self):
|
|
if self.options.route_required == "all_routes":
|
|
junk_pool = junk_weights_all
|
|
elif self.options.route_required == "genocide":
|
|
junk_pool = junk_weights_genocide
|
|
elif self.options.route_required == "neutral":
|
|
junk_pool = junk_weights_neutral
|
|
elif self.options.route_required == "pacifist":
|
|
junk_pool = junk_weights_pacifist
|
|
else:
|
|
junk_pool = junk_weights_all
|
|
if not self.options.only_flakes:
|
|
return self.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()))[0]
|
|
else:
|
|
return "Temmie Flakes"
|
|
|
|
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.options.route_required == "all_routes":
|
|
junk_pool = junk_weights_all.copy()
|
|
elif self.options.route_required == "genocide":
|
|
junk_pool = junk_weights_genocide.copy()
|
|
elif self.options.route_required == "neutral":
|
|
junk_pool = junk_weights_neutral.copy()
|
|
elif self.options.route_required == "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.options.rando_item_button:
|
|
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.options.route_required == "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.options.route_required == "neutral":
|
|
itempool = [item for item in itempool if item != "Popato Chisps" and item != "Hot Cat" and
|
|
item != "Hot Dog...?"]
|
|
if self.options.route_required == "pacifist" or self.options.route_required == "all_routes":
|
|
itempool += ["Undyne Letter EX"]
|
|
else:
|
|
itempool.remove("Complete Skeleton")
|
|
itempool.remove("Fish")
|
|
itempool.remove("DT Extractor")
|
|
itempool.remove("Hush Puppy")
|
|
if self.options.key_hunt:
|
|
itempool += ["Key Piece"] * self.options.key_pieces.value
|
|
else:
|
|
itempool += ["Left Home Key"]
|
|
itempool += ["Right Home Key"]
|
|
if not self.options.rando_love or \
|
|
(self.options.route_required != "genocide" and self.options.route_required != "all_routes"):
|
|
itempool = [item for item in itempool if not item == "LOVE"]
|
|
if not self.options.rando_stats or \
|
|
(self.options.route_required != "genocide" and self.options.route_required != "all_routes"):
|
|
itempool = [item for item in itempool if not (item == "ATK Up" or item == "DEF Up" or item == "HP Up")]
|
|
if self.options.temy_include:
|
|
itempool += ["temy armor"]
|
|
if self.options.no_equips:
|
|
itempool = [item for item in itempool if item not in required_armor and item not in required_weapons]
|
|
else:
|
|
if self.options.prog_armor:
|
|
itempool = [item if (item not in required_armor and not item == "temy armor") else
|
|
"Progressive Armor" for item in itempool]
|
|
if self.options.prog_weapons:
|
|
itempool = [item if item not in required_weapons else "Progressive Weapons" for item in itempool]
|
|
if self.options.route_required == "genocide" or \
|
|
self.options.route_required == "all_routes":
|
|
if not self.options.only_flakes:
|
|
itempool += ["Snowman Piece"] * 2
|
|
if not self.options.no_equips:
|
|
itempool = ["Real Knife" if item == "Worn Dagger" else "The Locket"
|
|
if item == "Heart Locket" else item for item in itempool]
|
|
if self.options.only_flakes:
|
|
itempool = [item for item in itempool if item not in non_key_items]
|
|
|
|
starting_key = self.options.starting_area.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.options.route_required.current_key])
|
|
if not self.options.rando_love or \
|
|
(self.options.route_required != "genocide" and self.options.route_required != "all_routes"):
|
|
exclusion_pool.update(exclusion_table["NoLove"])
|
|
if not self.options.rando_stats or \
|
|
(self.options.route_required != "genocide" and self.options.route_required != "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)
|
|
|
|
# Convert itempool into real items
|
|
itempool = [item for item in map(lambda name: self.create_item(name), itempool)]
|
|
# Fill remaining items with randomly generated junk or Temmie Flakes
|
|
while len(itempool) < len(self.multiworld.get_unfilled_locations(self.player)):
|
|
itempool.append(self.create_filler())
|
|
|
|
self.multiworld.itempool += itempool
|
|
|
|
def set_rules(self):
|
|
set_rules(self)
|
|
set_completion_rules(self)
|
|
|
|
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.options.rando_stats and
|
|
(self.options.route_required == "genocide" or
|
|
self.options.route_required == "all_routes"))) and
|
|
(loc_name not in exclusion_table["NoLove"] or
|
|
(self.options.rando_love and
|
|
(self.options.route_required == "genocide" or
|
|
self.options.route_required == "all_routes"))) and
|
|
loc_name not in exclusion_table[self.options.route_required.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):
|
|
return self._get_undertale_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
|