Skip to content

Commit

Permalink
adds manual mp9 version 4
Browse files Browse the repository at this point in the history
  • Loading branch information
Joethepic authored May 29, 2023
1 parent 294e324 commit 04208e9
Show file tree
Hide file tree
Showing 21 changed files with 1,228 additions and 0 deletions.
55 changes: 55 additions & 0 deletions worlds/manual_mp9v4_squidy/Data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import json
import os
import pkgutil
import sys

from .DataValidation import DataValidation, ValidationError

# blatantly copied from the minecraft ap world because why not
def load_data_file(*args) -> dict:
fname = os.path.join("data", *args)

try:
filedata = json.loads(pkgutil.get_data(__name__, fname).decode())
except:
filedata = []

return filedata

game_table = load_data_file('game.json')
item_table = load_data_file('items.json')
#progressive_item_table = load_data_file('progressive_items.json')
progressive_item_table = {}
location_table = load_data_file('locations.json')
region_table = load_data_file('regions.json')

# seed all of the tables for validation
DataValidation.game_table = game_table
DataValidation.item_table = item_table
DataValidation.location_table = location_table
DataValidation.region_table = region_table

try:
# check that requires have correct item names in locations and regions
DataValidation.checkItemNamesInLocationRequires()
DataValidation.checkItemNamesInRegionRequires()

# check that region names are correct in locations
DataValidation.checkRegionNamesInLocations()

# check that items that are required by locations and regions are also marked required
DataValidation.checkItemsThatShouldBeRequired()

# check that regions that are connected to are correct
DataValidation.checkRegionsConnectingToOtherRegions()

# check that the apworld creator didn't specify multiple victory conditions
DataValidation.checkForMultipleVictoryLocations()
except ValidationError as e:
print("\nValidationError: %s\n\n" % (e))
print("You can close this window.\n")
keeping_terminal_open = input("If you are running from a terminal, press Ctrl-C followed by ENTER to break execution.")

# show the apworld creator what their game name and filename should be
# commenting this out for now because it mainly just causes confusion for users
# DataValidation.outputExpectedGameNames()
205 changes: 205 additions & 0 deletions worlds/manual_mp9v4_squidy/DataValidation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import logging
import re
import json

class ValidationError(Exception):
pass

class DataValidation():
game_table = {}
item_table = []
location_table = []
region_table = {}

@staticmethod
def outputExpectedGameNames():
logging.warning("With the information in your game.json, ")
logging.warning(" - The expected game name in your YAML is: Manual_%s_%s" % (DataValidation.game_table["game"], DataValidation.game_table["player"]))
logging.warning(" - The expected apworld filename and directory is: manual_%s_%s" % (DataValidation.game_table["game"].lower(), DataValidation.game_table["player"].lower()))
logging.warning("")
logging.warning("")

@staticmethod
def checkItemNamesInLocationRequires():
for location in DataValidation.location_table:
if "requires" not in location:
continue

if isinstance(location["requires"], str):
# parse user written statement into list of each item
reqires_raw = re.split('(\AND|\)|\(|OR|\|)', location["requires"])
remove_spaces = [x.strip() for x in reqires_raw]
remove_empty = [x for x in remove_spaces if x != '']
requires_list = [x for x in remove_empty if x != '|']

for i, item in enumerate(requires_list):
if item.lower() == "or" or item.lower() == "and" or item == ")" or item == "(":
continue
else:
item_parts = item.split(":")
item_name = item

if len(item_parts) > 1:
item_name = item_parts[0]

item_exists = len([item["name"] for item in DataValidation.item_table if item["name"] == item_name]) > 0

if not item_exists:
raise ValidationError("Item %s is required by location %s but is misspelled or does not exist." % (item_name, location["name"]))

else: # item access is in dict form
for item in location["requires"]:
# if the require entry is an object with "or" or a list of items, treat it as a standalone require of its own
if (isinstance(item, dict) and "or" in item and isinstance(item["or"], list)) or (isinstance(item, list)):
or_items = item

if isinstance(item, dict):
or_items = item["or"]

for or_item in or_items:
or_item_parts = or_item.split(":")
or_item_name = or_item

if len(or_item_parts) > 1:
or_item_name = or_item_parts[0]

item_exists = len([item["name"] for item in DataValidation.item_table if item["name"] == or_item_name]) > 0

if not item_exists:
raise ValidationError("Item %s is required by location %s but is misspelled or does not exist." % (or_item_name, location["name"]))
else:
item_parts = item.split(":")
item_name = item

if len(item_parts) > 1:
item_name = item_parts[0]

item_exists = len([item["name"] for item in DataValidation.item_table if item["name"] == item_name]) > 0

if not item_exists:
raise ValidationError("Item %s is required by location %s but is misspelled or does not exist." % (item_name, location["name"]))

@staticmethod
def checkItemNamesInRegionRequires():
for region_name in DataValidation.region_table:
region = DataValidation.region_table[region_name]

if "requires" not in region:
continue

if isinstance(region["requires"], str):
# parse user written statement into list of each item
reqires_raw = re.split('(\AND|\)|\(|OR|\|)', region["requires"])
remove_spaces = [x.strip() for x in reqires_raw]
remove_empty = [x for x in remove_spaces if x != '']
requires_list = [x for x in remove_empty if x != '|']

for i, item in enumerate(requires_list):
if item.lower() == "or" or item.lower() == "and" or item == ")" or item == "(":
continue
else:
item_parts = item.split(":")
item_name = item

if len(item_parts) > 1:
item_name = item_parts[0]

item_exists = len([item["name"] for item in DataValidation.item_table if item["name"] == item_name]) > 0

if not item_exists:
raise ValidationError("Item %s is required by region %s but is misspelled or does not exist." % (item_name, region_name))

else: # item access is in dict form
for item in region["requires"]:
# if the require entry is an object with "or" or a list of items, treat it as a standalone require of its own
if (isinstance(item, dict) and "or" in item and isinstance(item["or"], list)) or (isinstance(item, list)):
or_items = item

if isinstance(item, dict):
or_items = item["or"]

for or_item in or_items:
or_item_parts = or_item.split(":")
or_item_name = or_item

if len(or_item_parts) > 1:
or_item_name = or_item_parts[0]

item_exists = len([item["name"] for item in DataValidation.item_table if item["name"] == or_item_name]) > 0

if not item_exists:
raise ValidationError("Item %s is required by region %s but is misspelled or does not exist." % (or_item_name, region_name))
else:
item_parts = item.split(":")
item_name = item

if len(item_parts) > 1:
item_name = item_parts[0]

item_exists = len([item["name"] for item in DataValidation.item_table if item["name"] == item_name]) > 0

if not item_exists:
raise ValidationError("Item %s is required by region %s but is misspelled or does not exist." % (item_name, region_name))

@staticmethod
def checkRegionNamesInLocations():
for location in DataValidation.location_table:
if "region" not in location:
continue

region_exists = len([name for name in DataValidation.region_table if name == location["region"]]) > 0

if not region_exists:
raise ValidationError("Region %s is set for location %s, but the region is misspelled or does not exist." % (location["region"], location["name"]))

@staticmethod
def checkItemsThatShouldBeRequired():
for item in DataValidation.item_table:
# if the item is already progression, no need to check
if "progression" in item and item["progression"]:
continue

# check location requires for the presence of item name
for location in DataValidation.location_table:
if "requires" not in location:
continue

# convert to json so we don't have to guess the data type
location_requires = json.dumps(location["requires"])

if item["name"] in location_requires:
raise ValidationError("Item %s is required by location %s, but the item is not marked as progression." % (item["name"], location["name"]))

# check region requires for the presence of item name
for region_name in DataValidation.region_table:
region = DataValidation.region_table[region_name]

if "requires" not in region:
continue

# convert to json so we don't have to guess the data type
region_requires = json.dumps(region["requires"])

if item["name"] in region_requires:
raise ValidationError("Item %s is required by region %s, but the item is not marked as progression." % (item["name"], key))

@staticmethod
def checkRegionsConnectingToOtherRegions():
for region_name in DataValidation.region_table:
region = DataValidation.region_table[region_name]

if "connects_to" not in region:
continue

for connecting_region in region["connects_to"]:
region_exists = len([name for name in DataValidation.region_table if name == connecting_region]) > 0

if not region_exists:
raise ValidationError("Region %s connects to a region %s, which is misspelled or does not exist." % (region_name, connecting_region))

@staticmethod
def checkForMultipleVictoryLocations():
victory_count = len([location["name"] for location in DataValidation.location_table if "victory" in location and location["victory"]])

if victory_count > 1:
raise ValidationError("There are %s victory locations defined, but there should only be 1." % (str(victory_count)))
23 changes: 23 additions & 0 deletions worlds/manual_mp9v4_squidy/Game.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from .Data import game_table

game_name = "Manual_%s_%s" % (game_table["game"], game_table["player"])
filler_item_name = game_table["filler_item_name"]

# Programmatically generate starting indexes for items and locations based upon the game name and player name to aim for non-colliding indexes
# Additionally, make this use as many characters of the game name as possible to avoid accidental id pool collisions
# - It's assumed that the first two characters of a game and the last character *should* be fairly unique, but we use the remaining characters anyways to move the pool
# - The player name is meant to be a small differentiator, so we just apply a flat multiplier for that

# 100m + 70m + 10m, which should put all Manual games comfortably in the billions
starting_index = (ord(game_table["game"][:1]) * 100000000) + \
(ord(game_table["game"][1:2]) * 70000000) + \
(ord(game_table["game"][-1:]) * 10000000)

if len(game_table["game"]) > 3:
for index in range(2, len(game_table["game"]) - 1):
multiplier = 100000

starting_index += (ord(game_table["game"][index:index+1]) * multiplier)

for index in range(0, len(game_table["player"])):
starting_index += (ord(game_table["player"][index:index+1]) * 1000)
63 changes: 63 additions & 0 deletions worlds/manual_mp9v4_squidy/Items.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from BaseClasses import Item
from .Data import item_table, progressive_item_table
from .Game import filler_item_name, starting_index

######################
# Generate item lookups
######################

item_id_to_name = {}
item_name_to_item = {}
advancement_item_names = set()
lastItemId = -1

count = starting_index

# add the filler item to the list of items for lookup
item_table.append({
"name": filler_item_name
})

# add sequential generated ids to the lists
for key, val in enumerate(item_table):
item_table[key]["id"] = count
item_table[key]["progression"] = val["progression"] if "progression" in val else False
count += 1

for item in item_table:
item_name = item["name"]
item_id_to_name[item["id"]] = item_name
item_name_to_item[item_name] = item
if item["progression"]:
advancement_item_names.add(item_name)

if item["id"] != None:
lastItemId = max(lastItemId, item["id"])

progressive_item_list = {}

for item in progressive_item_table:
progressiveName = progressive_item_table[item]
if progressiveName not in progressive_item_list:
progressive_item_list[progressiveName] = []
progressive_item_list[progressiveName].append(item)

for progressiveItemName in progressive_item_list.keys():
lastItemId += 1
generatedItem = {}
generatedItem["id"] = lastItemId
generatedItem["name"] = progressiveItemName
generatedItem["progression"] = item_name_to_item[progressive_item_list[progressiveItemName][0]]["progression"]
item_name_to_item[progressiveItemName] = generatedItem
item_id_to_name[lastItemId] = progressiveItemName

item_id_to_name[None] = "__Victory__"
item_name_to_id = {name: id for id, name in item_id_to_name.items()}

######################
# Item classes
######################


class ManualItem(Item):
game = "Manual"
Loading

0 comments on commit 04208e9

Please sign in to comment.