Add Meritous (#278)

This commit is contained in:
Felix R 2022-03-18 00:30:47 -03:00 committed by GitHub
parent ce6966a823
commit b02a710bc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 726 additions and 0 deletions

View File

@ -0,0 +1,25 @@
# Meritous
## Where is the settings page?
The [player settings page for Meritous](../player-settings) contains all the options you need to configure and export a config file.
## What does randomization do to this game?
The PSI Enhancement Tiles have become general-purpose Item Caches, and all upgrades and artifacts are added to the multiworld item pool. Optionally, the progression-critical PSI Keys can also be added to the pool, as well as monster evolution traps which (in vanilla) trigger when bosses are defeated.
## What is the goal of Meritous when randomized?
At minimum, you will need to get the PSI Keys, defeat the three bosses, retrieve the Cursed Seal, and return it to the entrance. Depending on your selected goal, you may also have to defeat the final boss, or you may also need to explore every last room of the Atlas Dome and retrieve the Agate Knife before getting the Cursed Seal and defeating the final boss' true form.
## Which items can be in another player's world?
Every item added to the multiworld pool (as outlined above) can be distributed to other players' worlds.
## What is considered a location check in Meritous?
The Alpha, Beta, and Gamma item caches each have 24 checks to buy, increasing in cost each time. Reward chests obtained from clearing ambush rooms will contain up to 24 location checks, thereafter always awarding a cache of PSI Crystals. If enabled, PSI Key Pedestals will contain checks, which must be unlocked by eliminating a certain percentage of monsters. If enabled, defeating bosses will result in an automatic check.
## Which notable items are not randomized?
The Cursed Seal and Agate Knife will always be in the farthest-away room from the Entrance and the final room explored, respectively.
## What does another world's item look like in Meritous?
There is no visual representation of other players' items in Meritous. You will be buying checks from item caches and opening chests in ambush rooms blindly.
## When the player receives an item, what happens?
A sound will play, and a notification will briefly appear on the lower half of the screen informing you of what you have received.

View File

@ -0,0 +1,64 @@
# Meritous Randomizer Setup Guide
## Required Software
Download the game from the [Meritous Gaiden GitHub releases page](https://github.com/FelicitusNeko/meritous-ap/releases)
## Installation Procedures
Simply download the latest version of Meritous Gaiden from the link above, and extract it wherever you like.
- ⚠️ Do not extract Meritous Gaiden to Program Files, as this will cause file access issues.
## Joining a Multiworld Game
1. Modify the `meritous-ap.json` file with your server details, as outlined in the next section.
2. Run `meritous.exe`. If the AP settings file is detected, you will see "AP Enabled" show up in the bottom left of the menu screen.
3. Start a new game. If it is able to successfully connect to the AP server, "Connected" will show up in the bottom left of the game screen for a few seconds.
## AP Settings File
The format of `meritous-ap.json` should be as follows:
```json
{
"ap-enable": true,
"server": "archipelago.gg",
"port": 38281,
"password": null,
"slotname": "YourName"
}
```
- `ap-enable`: Enables the game to connect to the Archipelago server. If this is `false` or missing, it will generate a local item randomizer.
- `server`: The server to which to connect. This can be a domain name (such as archipelago.gg) or an IP address (such as 127.0.0.1). If this is missing, the game will assume archipelago.gg.
- `port`: The port number to which to connect. By default, Archipelago will use port 38281 to host, unless the game is hosted on the Archipelago webhost. If this is missing, the game will assume 38281.
- `password`: The password to use for this game, if any. This can be omitted or set to `null` if there is no password.
- `slotname`: The slot name to use for this game. This is required, and must match the name provided on your YAML file.
Eventually, this process will be moved to in-game menus for better ease of use.
## Finishing the Game
Your initial goal is to find all three PSI Keys. Depending on your YAML settings, these may be located on pedestals in special rooms in the Atlas Dome, or they may be scattered across other players' worlds. These PSI Keys are then brought to their respective locations in the Dome, where you will be subjected to a boss battle. Once all three bosses are defeated, this unlocks the Cursed Seal, hidden in the farthest-away location from the Entrance. The Compass tiles can help you find your way to these locations.
At minimum, every seed will require you to find the Cursed Seal and bring it back to the Entrance. The goal can then vary based on your `goal` YAML setting:
- `return_the_cursed_seal`: You will fight the final boss, but win or lose, a victory will be posted.
- `any_ending`: You must defeat the final boss.
- `true_ending`: You must first explore all 3000 rooms of the Atlas Dome and find the Agate Knife, then fight the final boss' true form.
Once the goal has been completed, you may press F to send a forfeit, sending out all of your world's remaining items to their respective players, and C to send a collect, which gathers up all of your world's items from their shuffled locations in other player's worlds. You may also press S to view your statistics, if you're a fan of numbers.
More in-depth information about the game can be found in the game's help file, accessed by pressing H while playing.
## Game Troubleshooting
### An error message shows up at the bottom-left
- `Disconnected`: If the game does not reconnect automatically, you may need to save, quit, and reload the game to reconnect. Keep in mind that the game does not auto-save, and it is only possible to save the game at Save Tiles.
- `InvalidSlot`, `InvalidGame`: Make sure the `slotname` in `meritous-ap.json` matches the name provided in your Meritous YAML file.
- `SlotAlreadyTaken`: Make sure Meritous Gaiden is not already running and connected to the server.
- `IncompatibleVersion`: Make sure Meritous Gaiden has been updated to the latest version.
- `InvalidPassword`: Make sure the `password` in `meritous-ap.json` matches the password for your game. If there is no password, either set this to `null` (no quotes) or omit/remove it completely.
- `InvalidItemsHandling`: This is a bug and shouldn't happen if you downloaded a precompiled copy of the game. If you downloaded a precompiled copy, please let KewlioMZX know over GitHub or the AP Discord.

View File

@ -225,6 +225,25 @@
}
]
},
{
"gameTitle": "Meritous",
"tutorials": [
{
"name": "Meritous Setup Tutorial",
"description": "A guide to setting up the Archipelago Meritous software on your computer.",
"files": [
{
"language": "English",
"filename": "meritous/setup_en.md",
"link": "meritous/setup/en",
"authors": [
"KewlioMZX"
]
}
]
}
]
},
{
"gameTitle": "Minecraft",
"tutorials": [

214
worlds/meritous/Items.py Normal file
View File

@ -0,0 +1,214 @@
# Copyright (c) 2022 FelicitusNeko
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
import typing
from BaseClasses import Item
# pedestal_credit_text: str = "and the Unknown Item"
# sickkid_credit_text: Optional[str] = None
# magicshop_credit_text: Optional[str] = None
# zora_credit_text: Optional[str] = None
# fluteboy_credit_text: Optional[str] = None
class MeritousLttPText(typing.NamedTuple):
pedestal: typing.Optional[str]
sickkid: typing.Optional[str]
magicshop: typing.Optional[str]
zora: typing.Optional[str]
fluteboy: typing.Optional[str]
LttPCreditsText = {
"Nothing": MeritousLttPText("lack of presence",
"Forgot to get you anything",
"Thanks for the shroom, sucker",
"Bucket o' Nothing for 9999.99",
"I can't hear anything"),
"Reflect Shield upgrade": MeritousLttPText("Protective Aura",
"Safe under the covers",
"Cast a magic circle",
"Psionic aura for sale",
"This tune makes you feel safe"),
"Circuit Charge upgrade": MeritousLttPText("Psionic Charge",
"This kid's so ready now",
"Expand your mind",
"Psionic energy for sale",
"Synthwave? From a flute?"),
"Circuit Refill upgrade": MeritousLttPText("Psionic Cleanse",
"All rested up",
"Shrooms for mental floss",
"Psionic refreshment for sale",
"Peaceful little tune"),
"Map": MeritousLttPText("Twisted Chart",
"Abstract artist kid",
"Shrooms for pictograms",
"Strange imagery for sale",
"Just follow the rhythm"),
"Shield Boost": MeritousLttPText("Heavy Aura",
"Blanket fort kid",
"Shrooms for protection",
"Bigger circles for sale",
"Don't touch the music man"),
"Crystal Efficiency": MeritousLttPText("Expensive Trinket",
"Investment kid",
"Make your own crystals",
"Invest in someone's future",
"A rich melody"),
"Circuit Booster": MeritousLttPText("Mental Focus",
"Far-reaching kid",
"I can see through time",
"Finglonger for sale",
"Can you please keep it down"),
"Metabolism": MeritousLttPText("Energy Drink",
"Zoom-Zoom kid",
"Shrooms for Zooms",
"Speed for sale",
"How does he play so fast"),
"Dodge Enhancer": MeritousLttPText("Insignificant Dot",
"Evasive kid",
"Still at large",
"Take the money and run",
"Gonna rock and go"),
"Ethereal Monocle": MeritousLttPText("Weird Glass",
"He can see you coming",
"Okay now I'm seeing things",
"Precognition for sale",
"Like deja vu all over again"),
"Crystal Gatherer": MeritousLttPText("Attractive Aura",
"Magnetic kid",
"I swear it attracts money",
"Big magnet for sale",
"Works for tips"),
"Portable Compass": MeritousLttPText("Way Forward",
"Forward-thinking kid",
"Shrooms for Life Advice",
"Moving Needle for sale",
"Sing a tale of adventure"),
"PSI Key 1": MeritousLttPText("Familiar Artifact",
"Messenger kid",
"The Black Market",
"I've got something good",
"An otherworldly tune"),
"PSI Key 2": MeritousLttPText("Familiar Artifact",
"Messenger kid",
"The Black Market",
"I've got something good",
"An otherworldly tune"),
"PSI Key 3": MeritousLttPText("Familiar Artifact",
"Messenger kid",
"The Black Market",
"I've got something good",
"An otherworldly tune"),
"Cursed Seal": MeritousLttPText("Psionic Anomaly",
"What's this doing here",
"What's this doing here",
"What's this doing here",
"What's this doing here"),
"Agate Knife": MeritousLttPText("Psionic Anomaly",
"What's this doing here",
"What's this doing here",
"What's this doing here",
"What's this doing here"),
"Evolution Trap": MeritousLttPText("Awful Curse",
"Dennis the Menace",
"I can make it harder for 'em",
"Pranks for sale",
"This tune sucks, I'm angry now"),
"Crystals x500": MeritousLttPText("Pile of Rocks",
"Shiny collector kid",
"A backroom exchange",
"Currency conversion here",
"Quarter-full tip jar"),
"Crystals x1000": MeritousLttPText("Pile of Rocks",
"Shiny collector kid",
"A backroom exchange",
"Currency conversion here",
"Half-full tip jar"),
"Crystals x2000": MeritousLttPText("Pile of Rocks",
"Shiny collector kid",
"A backroom exchange",
"Currency conversion here",
"This was a real good gig"),
"Extra Life": MeritousLttPText("Lifesaver",
"Sick kid feels alive again",
"A life-saving concoction",
"Second chance for sale",
"A life-saving melody")
}
class MeritousItem(Item):
game: str = "Meritous"
def __init__(self, name, advancement, code, player):
super(MeritousItem, self).__init__(name, advancement, code, player)
if code is None:
self.type = "Event"
elif "Trap" in name:
self.type = "Trap"
self.trap = True
elif "PSI Key" in name:
self.type = "PSI Key"
elif "upgrade" in name:
self.type = "Enhancement"
elif "Crystals x" in name:
self.type = "Crystals"
elif name == "Nothing":
self.type = "Nothing"
elif name == "Cursed Seal" or name == "Agate Knife":
self.type = name
elif name == "Extra Life":
self.type = "Other"
else:
self.type = "Artifact"
self.never_exclude = True
if name in LttPCreditsText:
lttp = LttPCreditsText[name]
self.pedestal_credit_text = f"and the {lttp.pedestal}"
self.sickkid_credit_text = lttp.sickkid
self.magicshop_credit_text = lttp.magicshop
self.zora_credit_text = lttp.zora
self.fluteboy_credit_text = lttp.fluteboy
offset = 593_000
item_table = {
"Nothing": offset + 0,
"Reflect Shield upgrade": offset + 1,
"Circuit Charge upgrade": offset + 2,
"Circuit Refill upgrade": offset + 3,
"Map": offset + 4,
"Shield Boost": offset + 5,
"Crystal Efficiency": offset + 6,
"Circuit Booster": offset + 7,
"Metabolism": offset + 8,
"Dodge Enhancer": offset + 9,
"Ethereal Monocle": offset + 10,
"Crystal Gatherer": offset + 11,
"Portable Compass": offset + 12,
"PSI Key 1": offset + 13,
"PSI Key 2": offset + 14,
"PSI Key 3": offset + 15,
"Cursed Seal": offset + 16,
"Agate Knife": offset + 17,
"Evolution Trap": offset + 18,
"Crystals x500": offset + 19,
"Crystals x1000": offset + 20,
"Crystals x2000": offset + 21,
"Extra Life": offset + 22
}
item_groups = {
"PSI Keys": [f"PSI Key {x}" for x in range(1, 4)],
"Upgrades": ["Reflect Shield upgrade", "Circuit Charge upgrade", "Circuit Refill upgrade"],
"Artifacts": ["Map", "Shield Boost", "Crystal Efficiency", "Circuit Booster",
"Metabolism", "Dodge Enhancer", "Ethereal Monocle", "Crystal Gatherer",
"Portable Compass"],
"Crystals": ["Crystals x500", "Crystals x1000", "Crystals x2000"]
}

View File

@ -0,0 +1,53 @@
# Copyright (c) 2022 FelicitusNeko
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
from BaseClasses import Location
class MeritousLocation(Location):
game: str = "Meritous"
def __init__(self, player: int, name: str = '', address: int = None, parent=None):
super(MeritousLocation, self).__init__(player, name, address, parent)
if "Wervyn Anixil" in name or "Defeat" in name:
self.event = True
offset = 593_000
alpha_store = {
f"Alpha Cache {i + 1}": offset + i for i in range(0, 24)
}
beta_store = {
f"Beta Cache {i + 1}": offset + i + 24 for i in range(0, 24)
}
gamma_store = {
f"Gamma Cache {i + 1}": offset + i + 48 for i in range(0, 24)
}
chest_store = {
f"Reward Chest {i + 1}": offset + i + 72 for i in range(0, 24)
}
special_store = {
"PSI Key Storage 1": offset + 96,
"PSI Key Storage 2": offset + 97,
"PSI Key Storage 3": offset + 98,
"Meridian": offset + 99,
"Ataraxia": offset + 100,
"Merodach": offset + 101,
"Place of Power": offset + 102,
"The Last Place You'll Look": offset + 103
}
location_table = {
**alpha_store,
**beta_store,
**gamma_store,
**chest_store,
**special_store
}

View File

@ -0,0 +1,60 @@
# Copyright (c) 2022 FelicitusNeko
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
import typing
from Options import Option, DeathLink, Toggle, DefaultOnToggle, Choice
cost_scales = {
0: [80, 5, 4],
1: [60, 5, 3],
2: [50, 4, 3]
}
class Goal(Choice):
"""Which goal must be achieved to trigger completion."""
display_name = "Goal"
option_return_the_cursed_seal = 0
option_any_ending = 1
option_true_ending = 2
alias_normal_ending = 1
alias_agate_knife = 2
default = 0
class IncludePSIKeys(DefaultOnToggle):
"""Whether PSI Keys should be included in the multiworld pool. If not, they will be in their vanilla locations."""
display_name = "Include PSI Keys"
class IncludeEvolutionTraps(Toggle):
"""
Whether evolution traps should be included in the multiworld pool.
If not, they will be activated by bosses, as in vanilla.
"""
display_name = "Include Evolution Traps"
class ItemCacheCost(Choice):
"""
Determines how the cost for Alpha, Beta, and Gamma caches will scale.
Vanilla has a total cost of about 1B crystals on Normal difficulty;
Reduced has about 750M; and Heavily Reduced has about 600M.
"""
display_name = "Item cache cost scaling"
option_vanilla = 0
option_reduced = 1
option_heavily_reduced = 2
default = 0
meritous_options: typing.Dict[str, type(Option)] = {
"goal": Goal,
"include_psi_keys": IncludePSIKeys,
"include_evolution_traps": IncludeEvolutionTraps,
"item_cache_cost": ItemCacheCost,
"death_link": DeathLink
}

107
worlds/meritous/Regions.py Normal file
View File

@ -0,0 +1,107 @@
# Copyright (c) 2022 FelicitusNeko
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
from BaseClasses import MultiWorld, Region, Entrance, RegionType
from .Locations import MeritousLocation, location_table
meritous_regions = ["Meridian", "Ataraxia", "Merodach", "Endgame"]
def _generate_entrances(player: int, entrance_list: [str], parent: Region):
return [Entrance(player, entrance, parent) for entrance in entrance_list]
def create_regions(world: MultiWorld, player: int):
regions = ["First", "Second", "Third", "Last"]
bosses = ["Meridian", "Ataraxia", "Merodach"]
for x, name in enumerate(regions):
fullname = f"{name} Quarter"
insidename = fullname
if x == 0:
insidename = "Menu"
region = Region(insidename, RegionType.Generic, fullname, player, world)
for store in ["Alpha Cache", "Beta Cache", "Gamma Cache", "Reward Chest"]:
for y in range(1, 7):
loc_name = f"{store} {(x * 6) + y}"
region.locations += [MeritousLocation(player, loc_name, location_table[loc_name], region)]
if x < 3:
storage_loc = f"PSI Key Storage {x + 1}"
region.locations += [MeritousLocation(player, storage_loc, location_table[storage_loc], region)]
region.exits += _generate_entrances(player, [f"To {bosses[x]}"], region)
else:
locations_end_game = ["Place of Power", "The Last Place You'll Look"]
region.locations += [
MeritousLocation(player, loc_name, location_table[loc_name], region)
for loc_name in locations_end_game]
region.exits += _generate_entrances(player, ["Back to the entrance",
"Back to the entrance with the Knife"],
region)
world.regions += [region]
for x, boss in enumerate(bosses):
boss_region = Region(boss, RegionType.Generic, boss, player, world)
boss_region.locations += [
MeritousLocation(player, boss, location_table[boss], boss_region),
MeritousLocation(player, f"{boss} Defeat", None, boss_region)
]
boss_region.exits = _generate_entrances(player, [f"To {regions[x + 1]} Quarter"], boss_region)
world.regions.append(boss_region)
region_final_boss = Region(
"Final Boss", RegionType.Generic, "Final Boss", player, world)
region_final_boss.locations = [MeritousLocation(
player, "Wervyn Anixil", None, region_final_boss)]
world.regions.append(region_final_boss)
region_tfb = Region("True Final Boss", RegionType.Generic,
"True Final Boss", player, world)
region_tfb.locations = [MeritousLocation(
player, "Wervyn Anixil?", None, region_tfb)]
world.regions.append(region_tfb)
entrance_map = {
"To Meridian": {
"to": "Meridian",
"rule": lambda state: state.has_group("PSI Keys", player, 1)
},
"To Second Quarter": {
"to": "Second Quarter",
"rule": lambda state: state.has("Meridian Defeated", player)
},
"To Ataraxia": {
"to": "Ataraxia",
"rule": lambda state: state.has_group("PSI Keys", player, 2)
},
"To Third Quarter": {
"to": "Third Quarter",
"rule": lambda state: state.has("Ataraxia Defeated", player)
},
"To Merodach": {
"to": "Merodach",
"rule": lambda state: state.has_group("PSI Keys", player, 3)
},
"To Last Quarter": {
"to": "Last Quarter",
"rule": lambda state: state.has("Merodach Defeated", player)
},
"Back to the entrance": {
"to": "Final Boss",
"rule": lambda state: state.has("Cursed Seal", player)
},
"Back to the entrance with the Knife": {
"to": "True Final Boss",
"rule": lambda state: state.has_all(["Cursed Seal", "Agate Knife"], player)
}
}
for entrance in entrance_map:
connection_data = entrance_map[entrance]
connection = world.get_entrance(entrance, player)
connection.access_rule = connection_data["rule"]
connection.connect(world.get_region(connection_data["to"], player))

22
worlds/meritous/Rules.py Normal file
View File

@ -0,0 +1,22 @@
# Copyright (c) 2022 FelicitusNeko
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
from ..generic.Rules import forbid_item, exclusion_rules
def set_rules(world, player):
# Prevent PSI keys from showing up in any boss' room
# This is to prevent softlock from ending up having to fight a boss in the wrong boss room
for boss in ["Meridian", "Ataraxia", "Merodach"]:
for key in range(1, 4):
forbid_item(world.get_location(boss, player), f"PSI Key {key}", player)
# Prevent progression from showing up in last six checks per store
# This is to prevent softlock from high prices or low chest drop
default_exclude_locations = set()
for store in ["Alpha Cache", "Beta Cache", "Gamma Cache", "Reward Chest"]:
for check_number in range(19, 25):
default_exclude_locations.add(f"{store} {check_number}")
exclusion_rules(world, player, default_exclude_locations)

162
worlds/meritous/__init__.py Normal file
View File

@ -0,0 +1,162 @@
# Copyright (c) 2022 FelicitusNeko
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
from BaseClasses import Item, MultiWorld
from Fill import fill_restrictive
from .Items import item_table, item_groups, MeritousItem
from .Locations import location_table, MeritousLocation
from .Options import meritous_options, cost_scales
from .Regions import create_regions
from .Rules import set_rules
from ..AutoWorld import World
client_version = 1
class MeritousWorld(World):
"""
Meritous Gaiden is a procedurally generated bullet-hell dungeon crawl game.
Five generations after the Orcus Dome incident, strange experiments conducted in a new
structure on the moon are tearing at the very fabric of reality...
"""
game: str = "Meritous"
topology_present: False
item_name_to_id = item_table
location_name_to_id = location_table
item_name_groups = item_groups
data_version = 2
forced_auto_forfeit = False
options = meritous_options
def __init__(self, world: MultiWorld, player: int):
super(MeritousWorld, self).__init__(world, player)
self.goal = 0
self.include_evolution_traps = False
self.include_psi_keys = False
self.item_cache_cost = 0
self.death_link = False
@staticmethod
def _is_progression(name):
return "PSI Key" in name or name in ["Cursed Seal", "Agate Knife"]
def create_item(self, name: str) -> Item:
return MeritousItem(name, self._is_progression(
name), item_table[name], self.player)
def create_event(self, event: str):
event = MeritousItem(event, True, None, self.player)
event.type = "Victory"
return event
def _create_item_in_quantities(self, name: str, qty: int) -> [Item]:
return [self.create_item(name) for _ in range(0, qty)]
def _make_crystals(self, qty: int) -> [MeritousItem]:
crystal_pool = []
for _ in range(0, qty):
rand_crystals = self.world.random.randrange(0, 32)
if rand_crystals < 16:
crystal_pool += [self.create_item("Crystals x500")]
elif rand_crystals < 28:
crystal_pool += [self.create_item("Crystals x1000")]
else:
crystal_pool += [self.create_item("Crystals x2000")]
return crystal_pool
def generate_early(self):
self.goal = self.world.goal[self.player].value
self.include_evolution_traps = self.world.include_evolution_traps[self.player].value
self.include_psi_keys = self.world.include_psi_keys[self.player].value
self.item_cache_cost = self.world.item_cache_cost[self.player].value
self.death_link = self.world.death_link[self.player].value
def create_regions(self):
create_regions(self.world, self.player)
def create_items(self):
frequencies = [0, # Nothing [0]
25, 23, 22, # PSI Enhancements [1-3]
1, 1, 1, 1, 1, 1, 1, 1, 1, # Artifacts [4-12]
1, 1, 1, # PSI Keys [13-15]
0, 0, # Seal & Knife [16-17]
3] # Traps [18]
location_count = len(location_table) - 2
item_pool = []
if not self.include_psi_keys:
location_count -= 3
for i in range(3):
frequencies[i - 6] = 0
if not self.include_evolution_traps:
frequencies[-1] = 0
location_count -= 3
for i, name in enumerate(item_table):
if i < len(frequencies):
item_pool += self._create_item_in_quantities(
name, frequencies[i])
if len(item_pool) < location_count:
item_pool += self._make_crystals(location_count - len(item_pool))
self.world.itempool += item_pool
def set_rules(self):
set_rules(self.world, self.player)
def generate_basic(self):
self.world.get_location("Place of Power", self.player).place_locked_item(
self.create_item("Cursed Seal"))
self.world.get_location("The Last Place You'll Look", self.player).place_locked_item(
self.create_item("Agate Knife"))
self.world.get_location("Wervyn Anixil", self.player).place_locked_item(
self.create_event("Victory"))
self.world.get_location("Wervyn Anixil?", self.player).place_locked_item(
self.create_event("Full Victory"))
for boss in ["Meridian", "Ataraxia", "Merodach"]:
self.world.get_location(f"{boss} Defeat", self.player).place_locked_item(
self.create_event(f"{boss} Defeated"))
if not self.include_psi_keys:
psi_keys = []
psi_key_storage = []
for i in range(0, 3):
psi_keys += [self.create_item(f"PSI Key {i + 1}")]
psi_key_storage += [self.world.get_location(
f"PSI Key Storage {i + 1}", self.player)]
fill_restrictive(self.world, self.world.get_all_state(
False), psi_key_storage, psi_keys)
if not self.include_evolution_traps:
for boss in ["Meridian", "Ataraxia", "Merodach"]:
self.world.get_location(boss, self.player).place_locked_item(
self.create_item("Evolution Trap"))
if self.goal == 0:
self.world.completion_condition[self.player] = lambda state: state.has_any(
["Victory", "Full Victory"], self.player)
else:
self.world.completion_condition[self.player] = lambda state: state.has(
"Full Victory", self.player)
def get_required_client_version(self) -> tuple:
# NOTE: Remember to change this before this game goes live
return max((0, 2, 4), super(MeritousWorld, self).get_required_client_version())
def fill_slot_data(self) -> dict:
return {
"goal": self.goal,
"cost_scale": cost_scales[self.item_cache_cost],
"death_link": self.death_link
}