KH2: Version 2 (#2009)

Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com>
Co-authored-by: Joe Prochaska <prochaska.joseph@gmail.com>
This commit is contained in:
JaredWeakStrike 2023-11-25 09:46:00 -05:00 committed by GitHub
parent c138918400
commit 2ccf11f3d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 6341 additions and 4882 deletions

View File

@ -1,894 +1,8 @@
import os
import asyncio
import ModuleUpdate
import json
import Utils
from pymem import pymem
from worlds.kh2.Items import exclusionItem_table, CheckDupingItems
from worlds.kh2 import all_locations, item_dictionary_table, exclusion_table
from worlds.kh2.WorldLocations import *
from worlds import network_data_package
if __name__ == "__main__":
Utils.init_logging("KH2Client", exception_logger="Client")
from NetUtils import ClientStatus
from CommonClient import gui_enabled, logger, get_base_parser, ClientCommandProcessor, \
CommonContext, server_loop
from worlds.kh2.Client import launch
ModuleUpdate.update()
kh2_loc_name_to_id = network_data_package["games"]["Kingdom Hearts 2"]["location_name_to_id"]
# class KH2CommandProcessor(ClientCommandProcessor):
class KH2Context(CommonContext):
# command_processor: int = KH2CommandProcessor
game = "Kingdom Hearts 2"
items_handling = 0b101 # Indicates you get items sent from other worlds.
def __init__(self, server_address, password):
super(KH2Context, self).__init__(server_address, password)
self.kh2LocalItems = None
self.ability = None
self.growthlevel = None
self.KH2_sync_task = None
self.syncing = False
self.kh2connected = False
self.serverconneced = False
self.item_name_to_data = {name: data for name, data, in item_dictionary_table.items()}
self.location_name_to_data = {name: data for name, data, in all_locations.items()}
self.lookup_id_to_item: typing.Dict[int, str] = {data.code: item_name for item_name, data in
item_dictionary_table.items() if data.code}
self.lookup_id_to_Location: typing.Dict[int, str] = {data.code: item_name for item_name, data in
all_locations.items() if data.code}
self.location_name_to_worlddata = {name: data for name, data, in all_world_locations.items()}
self.location_table = {}
self.collectible_table = {}
self.collectible_override_flags_address = 0
self.collectible_offsets = {}
self.sending = []
# list used to keep track of locations+items player has. Used for disoneccting
self.kh2seedsave = None
self.slotDataProgressionNames = {}
self.kh2seedname = None
self.kh2slotdata = None
self.itemamount = {}
# sora equipped, valor equipped, master equipped, final equipped
self.keybladeAnchorList = (0x24F0, 0x32F4, 0x339C, 0x33D4)
if "localappdata" in os.environ:
self.game_communication_path = os.path.expandvars(r"%localappdata%\KH2AP")
self.amountOfPieces = 0
# hooked object
self.kh2 = None
self.ItemIsSafe = False
self.game_connected = False
self.finalxemnas = False
self.worldid = {
# 1: {}, # world of darkness (story cutscenes)
2: TT_Checks,
# 3: {}, # destiny island doesn't have checks to ima put tt checks here
4: HB_Checks,
5: BC_Checks,
6: Oc_Checks,
7: AG_Checks,
8: LoD_Checks,
9: HundredAcreChecks,
10: PL_Checks,
11: DC_Checks, # atlantica isn't a supported world. if you go in atlantica it will check dc
12: DC_Checks,
13: TR_Checks,
14: HT_Checks,
15: HB_Checks, # world map, but you only go to the world map while on the way to goa so checking hb
16: PR_Checks,
17: SP_Checks,
18: TWTNW_Checks,
# 255: {}, # starting screen
}
# 0x2A09C00+0x40 is the sve anchor. +1 is the last saved room
self.sveroom = 0x2A09C00 + 0x41
# 0 not in battle 1 in yellow battle 2 red battle #short
self.inBattle = 0x2A0EAC4 + 0x40
self.onDeath = 0xAB9078
# PC Address anchors
self.Now = 0x0714DB8
self.Save = 0x09A70B0
self.Sys3 = 0x2A59DF0
self.Bt10 = 0x2A74880
self.BtlEnd = 0x2A0D3E0
self.Slot1 = 0x2A20C98
self.chest_set = set(exclusion_table["Chests"])
self.keyblade_set = set(CheckDupingItems["Weapons"]["Keyblades"])
self.staff_set = set(CheckDupingItems["Weapons"]["Staffs"])
self.shield_set = set(CheckDupingItems["Weapons"]["Shields"])
self.all_weapons = self.keyblade_set.union(self.staff_set).union(self.shield_set)
self.equipment_categories = CheckDupingItems["Equipment"]
self.armor_set = set(self.equipment_categories["Armor"])
self.accessories_set = set(self.equipment_categories["Accessories"])
self.all_equipment = self.armor_set.union(self.accessories_set)
self.Equipment_Anchor_Dict = {
"Armor": [0x2504, 0x2506, 0x2508, 0x250A],
"Accessories": [0x2514, 0x2516, 0x2518, 0x251A]}
self.AbilityQuantityDict = {}
self.ability_categories = CheckDupingItems["Abilities"]
self.sora_ability_set = set(self.ability_categories["Sora"])
self.donald_ability_set = set(self.ability_categories["Donald"])
self.goofy_ability_set = set(self.ability_categories["Goofy"])
self.all_abilities = self.sora_ability_set.union(self.donald_ability_set).union(self.goofy_ability_set)
self.boost_set = set(CheckDupingItems["Boosts"])
self.stat_increase_set = set(CheckDupingItems["Stat Increases"])
self.AbilityQuantityDict = {item: self.item_name_to_data[item].quantity for item in self.all_abilities}
# Growth:[level 1,level 4,slot]
self.growth_values_dict = {"High Jump": [0x05E, 0x061, 0x25DA],
"Quick Run": [0x62, 0x65, 0x25DC],
"Dodge Roll": [0x234, 0x237, 0x25DE],
"Aerial Dodge": [0x066, 0x069, 0x25E0],
"Glide": [0x6A, 0x6D, 0x25E2]}
self.boost_to_anchor_dict = {
"Power Boost": 0x24F9,
"Magic Boost": 0x24FA,
"Defense Boost": 0x24FB,
"AP Boost": 0x24F8}
self.AbilityCodeList = [self.item_name_to_data[item].code for item in exclusionItem_table["Ability"]]
self.master_growth = {"High Jump", "Quick Run", "Dodge Roll", "Aerial Dodge", "Glide"}
self.bitmask_item_code = [
0x130000, 0x130001, 0x130002, 0x130003, 0x130004, 0x130005, 0x130006, 0x130007
, 0x130008, 0x130009, 0x13000A, 0x13000B, 0x13000C
, 0x13001F, 0x130020, 0x130021, 0x130022, 0x130023
, 0x13002A, 0x13002B, 0x13002C, 0x13002D]
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(KH2Context, self).server_auth(password_requested)
await self.get_username()
await self.send_connect()
async def connection_closed(self):
self.kh2connected = False
self.serverconneced = False
if self.kh2seedname is not None and self.auth is not None:
with open(os.path.join(self.game_communication_path, f"kh2save{self.kh2seedname}{self.auth}.json"),
'w') as f:
f.write(json.dumps(self.kh2seedsave, indent=4))
await super(KH2Context, self).connection_closed()
async def disconnect(self, allow_autoreconnect: bool = False):
self.kh2connected = False
self.serverconneced = False
if self.kh2seedname not in {None} and self.auth not in {None}:
with open(os.path.join(self.game_communication_path, f"kh2save{self.kh2seedname}{self.auth}.json"),
'w') as f:
f.write(json.dumps(self.kh2seedsave, indent=4))
await super(KH2Context, self).disconnect()
@property
def endpoints(self):
if self.server:
return [self.server]
else:
return []
async def shutdown(self):
if self.kh2seedname not in {None} and self.auth not in {None}:
with open(os.path.join(self.game_communication_path, f"kh2save{self.kh2seedname}{self.auth}.json"),
'w') as f:
f.write(json.dumps(self.kh2seedsave, indent=4))
await super(KH2Context, self).shutdown()
def on_package(self, cmd: str, args: dict):
if cmd in {"RoomInfo"}:
self.kh2seedname = args['seed_name']
if not os.path.exists(self.game_communication_path):
os.makedirs(self.game_communication_path)
if not os.path.exists(self.game_communication_path + f"\kh2save{self.kh2seedname}{self.auth}.json"):
self.kh2seedsave = {"itemIndex": -1,
# back of soras invo is 0x25E2. Growth should be moved there
# Character: [back of invo, front of invo]
"SoraInvo": [0x25D8, 0x2546],
"DonaldInvo": [0x26F4, 0x2658],
"GoofyInvo": [0x280A, 0x276C],
"AmountInvo": {
"ServerItems": {
"Ability": {},
"Amount": {},
"Growth": {"High Jump": 0, "Quick Run": 0, "Dodge Roll": 0,
"Aerial Dodge": 0,
"Glide": 0},
"Bitmask": [],
"Weapon": {"Sora": [], "Donald": [], "Goofy": []},
"Equipment": [],
"Magic": {},
"StatIncrease": {},
"Boost": {},
},
"LocalItems": {
"Ability": {},
"Amount": {},
"Growth": {"High Jump": 0, "Quick Run": 0, "Dodge Roll": 0,
"Aerial Dodge": 0, "Glide": 0},
"Bitmask": [],
"Weapon": {"Sora": [], "Donald": [], "Goofy": []},
"Equipment": [],
"Magic": {},
"StatIncrease": {},
"Boost": {},
}},
# 1,3,255 are in this list in case the player gets locations in those "worlds" and I need to still have them checked
"LocationsChecked": [],
"Levels": {
"SoraLevel": 0,
"ValorLevel": 0,
"WisdomLevel": 0,
"LimitLevel": 0,
"MasterLevel": 0,
"FinalLevel": 0,
},
"SoldEquipment": [],
"SoldBoosts": {"Power Boost": 0,
"Magic Boost": 0,
"Defense Boost": 0,
"AP Boost": 0}
}
with open(os.path.join(self.game_communication_path, f"kh2save{self.kh2seedname}{self.auth}.json"),
'wt') as f:
pass
self.locations_checked = set()
elif os.path.exists(self.game_communication_path + f"\kh2save{self.kh2seedname}{self.auth}.json"):
with open(self.game_communication_path + f"\kh2save{self.kh2seedname}{self.auth}.json", 'r') as f:
self.kh2seedsave = json.load(f)
self.locations_checked = set(self.kh2seedsave["LocationsChecked"])
self.serverconneced = True
if cmd in {"Connected"}:
self.kh2slotdata = args['slot_data']
self.kh2LocalItems = {int(location): item for location, item in self.kh2slotdata["LocalItems"].items()}
try:
self.kh2 = pymem.Pymem(process_name="KINGDOM HEARTS II FINAL MIX")
logger.info("You are now auto-tracking")
self.kh2connected = True
except Exception as e:
logger.info("Line 247")
if self.kh2connected:
logger.info("Connection Lost")
self.kh2connected = False
logger.info(e)
if cmd in {"ReceivedItems"}:
start_index = args["index"]
if start_index == 0:
# resetting everything that were sent from the server
self.kh2seedsave["SoraInvo"][0] = 0x25D8
self.kh2seedsave["DonaldInvo"][0] = 0x26F4
self.kh2seedsave["GoofyInvo"][0] = 0x280A
self.kh2seedsave["itemIndex"] = - 1
self.kh2seedsave["AmountInvo"]["ServerItems"] = {
"Ability": {},
"Amount": {},
"Growth": {"High Jump": 0, "Quick Run": 0, "Dodge Roll": 0,
"Aerial Dodge": 0,
"Glide": 0},
"Bitmask": [],
"Weapon": {"Sora": [], "Donald": [], "Goofy": []},
"Equipment": [],
"Magic": {},
"StatIncrease": {},
"Boost": {},
}
if start_index > self.kh2seedsave["itemIndex"]:
self.kh2seedsave["itemIndex"] = start_index
for item in args['items']:
asyncio.create_task(self.give_item(item.item))
if cmd in {"RoomUpdate"}:
if "checked_locations" in args:
new_locations = set(args["checked_locations"])
# TODO: make this take locations from other players on the same slot so proper coop happens
# items_to_give = [self.kh2slotdata["LocalItems"][str(location_id)] for location_id in new_locations if
# location_id in self.kh2LocalItems.keys()]
self.checked_locations |= new_locations
async def checkWorldLocations(self):
try:
currentworldint = int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + 0x0714DB8, 1), "big")
if currentworldint in self.worldid:
curworldid = self.worldid[currentworldint]
for location, data in curworldid.items():
locationId = kh2_loc_name_to_id[location]
if locationId not in self.locations_checked \
and (int.from_bytes(
self.kh2.read_bytes(self.kh2.base_address + self.Save + data.addrObtained, 1),
"big") & 0x1 << data.bitIndex) > 0:
self.sending = self.sending + [(int(locationId))]
except Exception as e:
logger.info("Line 285")
if self.kh2connected:
logger.info("Connection Lost.")
self.kh2connected = False
logger.info(e)
async def checkLevels(self):
try:
for location, data in SoraLevels.items():
currentLevel = int.from_bytes(
self.kh2.read_bytes(self.kh2.base_address + self.Save + 0x24FF, 1), "big")
locationId = kh2_loc_name_to_id[location]
if locationId not in self.locations_checked \
and currentLevel >= data.bitIndex:
if self.kh2seedsave["Levels"]["SoraLevel"] < currentLevel:
self.kh2seedsave["Levels"]["SoraLevel"] = currentLevel
self.sending = self.sending + [(int(locationId))]
formDict = {
0: ["ValorLevel", ValorLevels], 1: ["WisdomLevel", WisdomLevels], 2: ["LimitLevel", LimitLevels],
3: ["MasterLevel", MasterLevels], 4: ["FinalLevel", FinalLevels]}
for i in range(5):
for location, data in formDict[i][1].items():
formlevel = int.from_bytes(
self.kh2.read_bytes(self.kh2.base_address + self.Save + data.addrObtained, 1), "big")
locationId = kh2_loc_name_to_id[location]
if locationId not in self.locations_checked \
and formlevel >= data.bitIndex:
if formlevel > self.kh2seedsave["Levels"][formDict[i][0]]:
self.kh2seedsave["Levels"][formDict[i][0]] = formlevel
self.sending = self.sending + [(int(locationId))]
except Exception as e:
logger.info("Line 312")
if self.kh2connected:
logger.info("Connection Lost.")
self.kh2connected = False
logger.info(e)
async def checkSlots(self):
try:
for location, data in weaponSlots.items():
locationId = kh2_loc_name_to_id[location]
if locationId not in self.locations_checked:
if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + data.addrObtained, 1),
"big") > 0:
self.sending = self.sending + [(int(locationId))]
for location, data in formSlots.items():
locationId = kh2_loc_name_to_id[location]
if locationId not in self.locations_checked:
if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + data.addrObtained, 1),
"big") & 0x1 << data.bitIndex > 0:
# self.locations_checked
self.sending = self.sending + [(int(locationId))]
except Exception as e:
if self.kh2connected:
logger.info("Line 333")
logger.info("Connection Lost.")
self.kh2connected = False
logger.info(e)
async def verifyChests(self):
try:
for location in self.locations_checked:
locationName = self.lookup_id_to_Location[location]
if locationName in self.chest_set:
if locationName in self.location_name_to_worlddata.keys():
locationData = self.location_name_to_worlddata[locationName]
if int.from_bytes(
self.kh2.read_bytes(self.kh2.base_address + self.Save + locationData.addrObtained, 1),
"big") & 0x1 << locationData.bitIndex == 0:
roomData = int.from_bytes(
self.kh2.read_bytes(self.kh2.base_address + self.Save + locationData.addrObtained,
1), "big")
self.kh2.write_bytes(self.kh2.base_address + self.Save + locationData.addrObtained,
(roomData | 0x01 << locationData.bitIndex).to_bytes(1, 'big'), 1)
except Exception as e:
if self.kh2connected:
logger.info("Line 350")
logger.info("Connection Lost.")
self.kh2connected = False
logger.info(e)
async def verifyLevel(self):
for leveltype, anchor in {"SoraLevel": 0x24FF,
"ValorLevel": 0x32F6,
"WisdomLevel": 0x332E,
"LimitLevel": 0x3366,
"MasterLevel": 0x339E,
"FinalLevel": 0x33D6}.items():
if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + anchor, 1), "big") < \
self.kh2seedsave["Levels"][leveltype]:
self.kh2.write_bytes(self.kh2.base_address + self.Save + anchor,
(self.kh2seedsave["Levels"][leveltype]).to_bytes(1, 'big'), 1)
async def give_item(self, item, ItemType="ServerItems"):
try:
itemname = self.lookup_id_to_item[item]
itemcode = self.item_name_to_data[itemname]
if itemcode.ability:
abilityInvoType = 0
TwilightZone = 2
if ItemType == "LocalItems":
abilityInvoType = 1
TwilightZone = -2
if itemname in {"High Jump", "Quick Run", "Dodge Roll", "Aerial Dodge", "Glide"}:
self.kh2seedsave["AmountInvo"][ItemType]["Growth"][itemname] += 1
return
if itemname not in self.kh2seedsave["AmountInvo"][ItemType]["Ability"]:
self.kh2seedsave["AmountInvo"][ItemType]["Ability"][itemname] = []
# appending the slot that the ability should be in
if len(self.kh2seedsave["AmountInvo"][ItemType]["Ability"][itemname]) < \
self.AbilityQuantityDict[itemname]:
if itemname in self.sora_ability_set:
self.kh2seedsave["AmountInvo"][ItemType]["Ability"][itemname].append(
self.kh2seedsave["SoraInvo"][abilityInvoType])
self.kh2seedsave["SoraInvo"][abilityInvoType] -= TwilightZone
elif itemname in self.donald_ability_set:
self.kh2seedsave["AmountInvo"][ItemType]["Ability"][itemname].append(
self.kh2seedsave["DonaldInvo"][abilityInvoType])
self.kh2seedsave["DonaldInvo"][abilityInvoType] -= TwilightZone
else:
self.kh2seedsave["AmountInvo"][ItemType]["Ability"][itemname].append(
self.kh2seedsave["GoofyInvo"][abilityInvoType])
self.kh2seedsave["GoofyInvo"][abilityInvoType] -= TwilightZone
elif itemcode.code in self.bitmask_item_code:
if itemname not in self.kh2seedsave["AmountInvo"][ItemType]["Bitmask"]:
self.kh2seedsave["AmountInvo"][ItemType]["Bitmask"].append(itemname)
elif itemcode.memaddr in {0x3594, 0x3595, 0x3596, 0x3597, 0x35CF, 0x35D0}:
if itemname in self.kh2seedsave["AmountInvo"][ItemType]["Magic"]:
self.kh2seedsave["AmountInvo"][ItemType]["Magic"][itemname] += 1
else:
self.kh2seedsave["AmountInvo"][ItemType]["Magic"][itemname] = 1
elif itemname in self.all_equipment:
self.kh2seedsave["AmountInvo"][ItemType]["Equipment"].append(itemname)
elif itemname in self.all_weapons:
if itemname in self.keyblade_set:
self.kh2seedsave["AmountInvo"][ItemType]["Weapon"]["Sora"].append(itemname)
elif itemname in self.staff_set:
self.kh2seedsave["AmountInvo"][ItemType]["Weapon"]["Donald"].append(itemname)
else:
self.kh2seedsave["AmountInvo"][ItemType]["Weapon"]["Goofy"].append(itemname)
elif itemname in self.boost_set:
if itemname in self.kh2seedsave["AmountInvo"][ItemType]["Boost"]:
self.kh2seedsave["AmountInvo"][ItemType]["Boost"][itemname] += 1
else:
self.kh2seedsave["AmountInvo"][ItemType]["Boost"][itemname] = 1
elif itemname in self.stat_increase_set:
if itemname in self.kh2seedsave["AmountInvo"][ItemType]["StatIncrease"]:
self.kh2seedsave["AmountInvo"][ItemType]["StatIncrease"][itemname] += 1
else:
self.kh2seedsave["AmountInvo"][ItemType]["StatIncrease"][itemname] = 1
else:
if itemname in self.kh2seedsave["AmountInvo"][ItemType]["Amount"]:
self.kh2seedsave["AmountInvo"][ItemType]["Amount"][itemname] += 1
else:
self.kh2seedsave["AmountInvo"][ItemType]["Amount"][itemname] = 1
except Exception as e:
if self.kh2connected:
logger.info("Line 398")
logger.info("Connection Lost.")
self.kh2connected = False
logger.info(e)
def run_gui(self):
"""Import kivy UI system and start running it as self.ui_task."""
from kvui import GameManager
class KH2Manager(GameManager):
logging_pairs = [
("Client", "Archipelago")
]
base_title = "Archipelago KH2 Client"
self.ui = KH2Manager(self)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
async def IsInShop(self, sellable, master_boost):
# journal = 0x741230 shop = 0x741320
# if journal=-1 and shop = 5 then in shop
# if journam !=-1 and shop = 10 then journal
journal = self.kh2.read_short(self.kh2.base_address + 0x741230)
shop = self.kh2.read_short(self.kh2.base_address + 0x741320)
if (journal == -1 and shop == 5) or (journal != -1 and shop == 10):
# print("your in the shop")
sellable_dict = {}
for itemName in sellable:
itemdata = self.item_name_to_data[itemName]
amount = int.from_bytes(
self.kh2.read_bytes(self.kh2.base_address + self.Save + itemdata.memaddr, 1), "big")
sellable_dict[itemName] = amount
while (journal == -1 and shop == 5) or (journal != -1 and shop == 10):
journal = self.kh2.read_short(self.kh2.base_address + 0x741230)
shop = self.kh2.read_short(self.kh2.base_address + 0x741320)
await asyncio.sleep(0.5)
for item, amount in sellable_dict.items():
itemdata = self.item_name_to_data[item]
afterShop = int.from_bytes(
self.kh2.read_bytes(self.kh2.base_address + self.Save + itemdata.memaddr, 1), "big")
if afterShop < amount:
if item in master_boost:
self.kh2seedsave["SoldBoosts"][item] += (amount - afterShop)
else:
self.kh2seedsave["SoldEquipment"].append(item)
async def verifyItems(self):
try:
local_amount = set(self.kh2seedsave["AmountInvo"]["LocalItems"]["Amount"].keys())
server_amount = set(self.kh2seedsave["AmountInvo"]["ServerItems"]["Amount"].keys())
master_amount = local_amount | server_amount
local_ability = set(self.kh2seedsave["AmountInvo"]["LocalItems"]["Ability"].keys())
server_ability = set(self.kh2seedsave["AmountInvo"]["ServerItems"]["Ability"].keys())
master_ability = local_ability | server_ability
local_bitmask = set(self.kh2seedsave["AmountInvo"]["LocalItems"]["Bitmask"])
server_bitmask = set(self.kh2seedsave["AmountInvo"]["ServerItems"]["Bitmask"])
master_bitmask = local_bitmask | server_bitmask
local_keyblade = set(self.kh2seedsave["AmountInvo"]["LocalItems"]["Weapon"]["Sora"])
local_staff = set(self.kh2seedsave["AmountInvo"]["LocalItems"]["Weapon"]["Donald"])
local_shield = set(self.kh2seedsave["AmountInvo"]["LocalItems"]["Weapon"]["Goofy"])
server_keyblade = set(self.kh2seedsave["AmountInvo"]["ServerItems"]["Weapon"]["Sora"])
server_staff = set(self.kh2seedsave["AmountInvo"]["ServerItems"]["Weapon"]["Donald"])
server_shield = set(self.kh2seedsave["AmountInvo"]["ServerItems"]["Weapon"]["Goofy"])
master_keyblade = local_keyblade | server_keyblade
master_staff = local_staff | server_staff
master_shield = local_shield | server_shield
local_equipment = set(self.kh2seedsave["AmountInvo"]["LocalItems"]["Equipment"])
server_equipment = set(self.kh2seedsave["AmountInvo"]["ServerItems"]["Equipment"])
master_equipment = local_equipment | server_equipment
local_magic = set(self.kh2seedsave["AmountInvo"]["LocalItems"]["Magic"].keys())
server_magic = set(self.kh2seedsave["AmountInvo"]["ServerItems"]["Magic"].keys())
master_magic = local_magic | server_magic
local_stat = set(self.kh2seedsave["AmountInvo"]["LocalItems"]["StatIncrease"].keys())
server_stat = set(self.kh2seedsave["AmountInvo"]["ServerItems"]["StatIncrease"].keys())
master_stat = local_stat | server_stat
local_boost = set(self.kh2seedsave["AmountInvo"]["LocalItems"]["Boost"].keys())
server_boost = set(self.kh2seedsave["AmountInvo"]["ServerItems"]["Boost"].keys())
master_boost = local_boost | server_boost
master_sell = master_equipment | master_staff | master_shield | master_boost
await asyncio.create_task(self.IsInShop(master_sell, master_boost))
for itemName in master_amount:
itemData = self.item_name_to_data[itemName]
amountOfItems = 0
if itemName in local_amount:
amountOfItems += self.kh2seedsave["AmountInvo"]["LocalItems"]["Amount"][itemName]
if itemName in server_amount:
amountOfItems += self.kh2seedsave["AmountInvo"]["ServerItems"]["Amount"][itemName]
if itemName == "Torn Page":
# Torn Pages are handled differently because they can be consumed.
# Will check the progression in 100 acre and - the amount of visits
# amountofitems-amount of visits done
for location, data in tornPageLocks.items():
if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + data.addrObtained, 1),
"big") & 0x1 << data.bitIndex > 0:
amountOfItems -= 1
if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + itemData.memaddr, 1),
"big") != amountOfItems and amountOfItems >= 0:
self.kh2.write_bytes(self.kh2.base_address + self.Save + itemData.memaddr,
amountOfItems.to_bytes(1, 'big'), 1)
for itemName in master_keyblade:
itemData = self.item_name_to_data[itemName]
# if the inventory slot for that keyblade is less than the amount they should have
if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + itemData.memaddr, 1),
"big") != 1 and int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + 0x1CFF, 1),
"big") != 13:
# Checking form anchors for the keyblade
if self.kh2.read_short(self.kh2.base_address + self.Save + 0x24F0) == itemData.kh2id \
or self.kh2.read_short(self.kh2.base_address + self.Save + 0x32F4) == itemData.kh2id \
or self.kh2.read_short(self.kh2.base_address + self.Save + 0x339C) == itemData.kh2id \
or self.kh2.read_short(self.kh2.base_address + self.Save + 0x33D4) == itemData.kh2id:
self.kh2.write_bytes(self.kh2.base_address + self.Save + itemData.memaddr,
(0).to_bytes(1, 'big'), 1)
else:
self.kh2.write_bytes(self.kh2.base_address + self.Save + itemData.memaddr,
(1).to_bytes(1, 'big'), 1)
for itemName in master_staff:
itemData = self.item_name_to_data[itemName]
if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + itemData.memaddr, 1),
"big") != 1 \
and self.kh2.read_short(self.kh2.base_address + self.Save + 0x2604) != itemData.kh2id \
and itemName not in self.kh2seedsave["SoldEquipment"]:
self.kh2.write_bytes(self.kh2.base_address + self.Save + itemData.memaddr,
(1).to_bytes(1, 'big'), 1)
for itemName in master_shield:
itemData = self.item_name_to_data[itemName]
if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + itemData.memaddr, 1),
"big") != 1 \
and self.kh2.read_short(self.kh2.base_address + self.Save + 0x2718) != itemData.kh2id \
and itemName not in self.kh2seedsave["SoldEquipment"]:
self.kh2.write_bytes(self.kh2.base_address + self.Save + itemData.memaddr,
(1).to_bytes(1, 'big'), 1)
for itemName in master_ability:
itemData = self.item_name_to_data[itemName]
ability_slot = []
if itemName in local_ability:
ability_slot += self.kh2seedsave["AmountInvo"]["LocalItems"]["Ability"][itemName]
if itemName in server_ability:
ability_slot += self.kh2seedsave["AmountInvo"]["ServerItems"]["Ability"][itemName]
for slot in ability_slot:
current = self.kh2.read_short(self.kh2.base_address + self.Save + slot)
ability = current & 0x0FFF
if ability | 0x8000 != (0x8000 + itemData.memaddr):
if current - 0x8000 > 0:
self.kh2.write_short(self.kh2.base_address + self.Save + slot, (0x8000 + itemData.memaddr))
else:
self.kh2.write_short(self.kh2.base_address + self.Save + slot, itemData.memaddr)
# removes the duped ability if client gave faster than the game.
for charInvo in {"SoraInvo", "DonaldInvo", "GoofyInvo"}:
if self.kh2.read_short(self.kh2.base_address + self.Save + self.kh2seedsave[charInvo][1]) != 0 and \
self.kh2seedsave[charInvo][1] + 2 < self.kh2seedsave[charInvo][0]:
self.kh2.write_short(self.kh2.base_address + self.Save + self.kh2seedsave[charInvo][1], 0)
# remove the dummy level 1 growths if they are in these invo slots.
for inventorySlot in {0x25CE, 0x25D0, 0x25D2, 0x25D4, 0x25D6, 0x25D8}:
current = self.kh2.read_short(self.kh2.base_address + self.Save + inventorySlot)
ability = current & 0x0FFF
if 0x05E <= ability <= 0x06D:
self.kh2.write_short(self.kh2.base_address + self.Save + inventorySlot, 0)
for itemName in self.master_growth:
growthLevel = self.kh2seedsave["AmountInvo"]["ServerItems"]["Growth"][itemName] \
+ self.kh2seedsave["AmountInvo"]["LocalItems"]["Growth"][itemName]
if growthLevel > 0:
slot = self.growth_values_dict[itemName][2]
min_growth = self.growth_values_dict[itemName][0]
max_growth = self.growth_values_dict[itemName][1]
if growthLevel > 4:
growthLevel = 4
current_growth_level = self.kh2.read_short(self.kh2.base_address + self.Save + slot)
ability = current_growth_level & 0x0FFF
# if the player should be getting a growth ability
if ability | 0x8000 != 0x8000 + min_growth - 1 + growthLevel:
# if it should be level one of that growth
if 0x8000 + min_growth - 1 + growthLevel <= 0x8000 + min_growth or ability < min_growth:
self.kh2.write_short(self.kh2.base_address + self.Save + slot, min_growth)
# if it is already in the inventory
elif ability | 0x8000 < (0x8000 + max_growth):
self.kh2.write_short(self.kh2.base_address + self.Save + slot, current_growth_level + 1)
for itemName in master_bitmask:
itemData = self.item_name_to_data[itemName]
itemMemory = int.from_bytes(
self.kh2.read_bytes(self.kh2.base_address + self.Save + itemData.memaddr, 1), "big")
if (int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + itemData.memaddr, 1),
"big") & 0x1 << itemData.bitmask) == 0:
# when getting a form anti points should be reset to 0 but bit-shift doesn't trigger the game.
if itemName in {"Valor Form", "Wisdom Form", "Limit Form", "Master Form", "Final Form"}:
self.kh2.write_bytes(self.kh2.base_address + self.Save + 0x3410,
(0).to_bytes(1, 'big'), 1)
self.kh2.write_bytes(self.kh2.base_address + self.Save + itemData.memaddr,
(itemMemory | 0x01 << itemData.bitmask).to_bytes(1, 'big'), 1)
for itemName in master_equipment:
itemData = self.item_name_to_data[itemName]
isThere = False
if itemName in self.accessories_set:
Equipment_Anchor_List = self.Equipment_Anchor_Dict["Accessories"]
else:
Equipment_Anchor_List = self.Equipment_Anchor_Dict["Armor"]
# Checking form anchors for the equipment
for slot in Equipment_Anchor_List:
if self.kh2.read_short(self.kh2.base_address + self.Save + slot) == itemData.kh2id:
isThere = True
if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + itemData.memaddr, 1),
"big") != 0:
self.kh2.write_bytes(self.kh2.base_address + self.Save + itemData.memaddr,
(0).to_bytes(1, 'big'), 1)
break
if not isThere and itemName not in self.kh2seedsave["SoldEquipment"]:
if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + itemData.memaddr, 1),
"big") != 1:
self.kh2.write_bytes(self.kh2.base_address + self.Save + itemData.memaddr,
(1).to_bytes(1, 'big'), 1)
for itemName in master_magic:
itemData = self.item_name_to_data[itemName]
amountOfItems = 0
if itemName in local_magic:
amountOfItems += self.kh2seedsave["AmountInvo"]["LocalItems"]["Magic"][itemName]
if itemName in server_magic:
amountOfItems += self.kh2seedsave["AmountInvo"]["ServerItems"]["Magic"][itemName]
if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + itemData.memaddr, 1),
"big") != amountOfItems \
and int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + 0x741320, 1), "big") in {10, 8}:
self.kh2.write_bytes(self.kh2.base_address + self.Save + itemData.memaddr,
amountOfItems.to_bytes(1, 'big'), 1)
for itemName in master_stat:
itemData = self.item_name_to_data[itemName]
amountOfItems = 0
if itemName in local_stat:
amountOfItems += self.kh2seedsave["AmountInvo"]["LocalItems"]["StatIncrease"][itemName]
if itemName in server_stat:
amountOfItems += self.kh2seedsave["AmountInvo"]["ServerItems"]["StatIncrease"][itemName]
# 0x130293 is Crit_1's location id for touching the computer
if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + itemData.memaddr, 1),
"big") != amountOfItems \
and int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Slot1 + 0x1B2, 1),
"big") >= 5 and int.from_bytes(
self.kh2.read_bytes(self.kh2.base_address + self.Save + 0x23DF, 1),
"big") > 0:
self.kh2.write_bytes(self.kh2.base_address + self.Save + itemData.memaddr,
amountOfItems.to_bytes(1, 'big'), 1)
for itemName in master_boost:
itemData = self.item_name_to_data[itemName]
amountOfItems = 0
if itemName in local_boost:
amountOfItems += self.kh2seedsave["AmountInvo"]["LocalItems"]["Boost"][itemName]
if itemName in server_boost:
amountOfItems += self.kh2seedsave["AmountInvo"]["ServerItems"]["Boost"][itemName]
amountOfBoostsInInvo = int.from_bytes(
self.kh2.read_bytes(self.kh2.base_address + self.Save + itemData.memaddr, 1),
"big")
amountOfUsedBoosts = int.from_bytes(
self.kh2.read_bytes(self.kh2.base_address + self.Save + self.boost_to_anchor_dict[itemName], 1),
"big")
# Ap Boots start at +50 for some reason
if itemName == "AP Boost":
amountOfUsedBoosts -= 50
totalBoosts = (amountOfBoostsInInvo + amountOfUsedBoosts)
if totalBoosts <= amountOfItems - self.kh2seedsave["SoldBoosts"][
itemName] and amountOfBoostsInInvo < 255:
self.kh2.write_bytes(self.kh2.base_address + self.Save + itemData.memaddr,
(amountOfBoostsInInvo + 1).to_bytes(1, 'big'), 1)
except Exception as e:
logger.info("Line 573")
if self.kh2connected:
logger.info("Connection Lost.")
self.kh2connected = False
logger.info(e)
def finishedGame(ctx: KH2Context, message):
if ctx.kh2slotdata['FinalXemnas'] == 1:
if 0x1301ED in message[0]["locations"]:
ctx.finalxemnas = True
# three proofs
if ctx.kh2slotdata['Goal'] == 0:
if int.from_bytes(ctx.kh2.read_bytes(ctx.kh2.base_address + ctx.Save + 0x36B2, 1), "big") > 0 \
and int.from_bytes(ctx.kh2.read_bytes(ctx.kh2.base_address + ctx.Save + 0x36B3, 1), "big") > 0 \
and int.from_bytes(ctx.kh2.read_bytes(ctx.kh2.base_address + ctx.Save + 0x36B4, 1), "big") > 0:
if ctx.kh2slotdata['FinalXemnas'] == 1:
if ctx.finalxemnas:
return True
else:
return False
else:
return True
else:
return False
elif ctx.kh2slotdata['Goal'] == 1:
if int.from_bytes(ctx.kh2.read_bytes(ctx.kh2.base_address + ctx.Save + 0x3641, 1), "big") >= \
ctx.kh2slotdata['LuckyEmblemsRequired']:
ctx.kh2.write_bytes(ctx.kh2.base_address + ctx.Save + 0x36B2, (1).to_bytes(1, 'big'), 1)
ctx.kh2.write_bytes(ctx.kh2.base_address + ctx.Save + 0x36B3, (1).to_bytes(1, 'big'), 1)
ctx.kh2.write_bytes(ctx.kh2.base_address + ctx.Save + 0x36B4, (1).to_bytes(1, 'big'), 1)
if ctx.kh2slotdata['FinalXemnas'] == 1:
if ctx.finalxemnas:
return True
else:
return False
else:
return True
else:
return False
elif ctx.kh2slotdata['Goal'] == 2:
for boss in ctx.kh2slotdata["hitlist"]:
if boss in message[0]["locations"]:
ctx.amountOfPieces += 1
if ctx.amountOfPieces >= ctx.kh2slotdata["BountyRequired"]:
ctx.kh2.write_bytes(ctx.kh2.base_address + ctx.Save + 0x36B2, (1).to_bytes(1, 'big'), 1)
ctx.kh2.write_bytes(ctx.kh2.base_address + ctx.Save + 0x36B3, (1).to_bytes(1, 'big'), 1)
ctx.kh2.write_bytes(ctx.kh2.base_address + ctx.Save + 0x36B4, (1).to_bytes(1, 'big'), 1)
if ctx.kh2slotdata['FinalXemnas'] == 1:
if ctx.finalxemnas:
return True
else:
return False
else:
return True
else:
return False
async def kh2_watcher(ctx: KH2Context):
while not ctx.exit_event.is_set():
try:
if ctx.kh2connected and ctx.serverconneced:
ctx.sending = []
await asyncio.create_task(ctx.checkWorldLocations())
await asyncio.create_task(ctx.checkLevels())
await asyncio.create_task(ctx.checkSlots())
await asyncio.create_task(ctx.verifyChests())
await asyncio.create_task(ctx.verifyItems())
await asyncio.create_task(ctx.verifyLevel())
message = [{"cmd": 'LocationChecks', "locations": ctx.sending}]
if finishedGame(ctx, message):
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
ctx.finished_game = True
location_ids = []
location_ids = [location for location in message[0]["locations"] if location not in location_ids]
for location in location_ids:
if location not in ctx.locations_checked:
ctx.locations_checked.add(location)
ctx.kh2seedsave["LocationsChecked"].append(location)
if location in ctx.kh2LocalItems:
item = ctx.kh2slotdata["LocalItems"][str(location)]
await asyncio.create_task(ctx.give_item(item, "LocalItems"))
await ctx.send_msgs(message)
elif not ctx.kh2connected and ctx.serverconneced:
logger.info("Game is not open. Disconnecting from Server.")
await ctx.disconnect()
except Exception as e:
logger.info("Line 661")
if ctx.kh2connected:
logger.info("Connection Lost.")
ctx.kh2connected = False
logger.info(e)
await asyncio.sleep(0.5)
if __name__ == '__main__':
async def main(args):
ctx = KH2Context(args.connect, args.password)
ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop")
if gui_enabled:
ctx.run_gui()
ctx.run_cli()
progression_watcher = asyncio.create_task(
kh2_watcher(ctx), name="KH2ProgressionWatcher")
await ctx.exit_event.wait()
ctx.server_address = None
await progression_watcher
await ctx.shutdown()
import colorama
parser = get_base_parser(description="KH2 Client, for text interfacing.")
args, rest = parser.parse_known_args()
colorama.init()
asyncio.run(main(args))
colorama.deinit()
Utils.init_logging("KH2Client", exception_logger="Client")
launch()

View File

@ -71,7 +71,6 @@ non_apworlds: set = {
"Clique",
"DLCQuest",
"Final Fantasy",
"Kingdom Hearts 2",
"Lufia II Ancient Cave",
"Meritous",
"Ocarina of Time",

View File

@ -112,8 +112,6 @@ components: List[Component] = [
# Zillion
Component('Zillion Client', 'ZillionClient',
file_identifier=SuffixIdentifier('.apzl')),
# Kingdom Hearts 2
Component('KH2 Client', "KH2Client"),
#MegaMan Battle Network 3
Component('MMBN3 Client', 'MMBN3Client', file_identifier=SuffixIdentifier('.apbn3'))

881
worlds/kh2/Client.py Normal file
View File

@ -0,0 +1,881 @@
import ModuleUpdate
ModuleUpdate.update()
import os
import asyncio
import json
from pymem import pymem
from . import item_dictionary_table, exclusion_item_table, CheckDupingItems, all_locations, exclusion_table, SupportAbility_Table, ActionAbility_Table, all_weapon_slot
from .Names import ItemName
from .WorldLocations import *
from NetUtils import ClientStatus
from CommonClient import gui_enabled, logger, get_base_parser, CommonContext, server_loop
class KH2Context(CommonContext):
# command_processor: int = KH2CommandProcessor
game = "Kingdom Hearts 2"
items_handling = 0b111 # Indicates you get items sent from other worlds.
def __init__(self, server_address, password):
super(KH2Context, self).__init__(server_address, password)
self.goofy_ability_to_slot = dict()
self.donald_ability_to_slot = dict()
self.all_weapon_location_id = None
self.sora_ability_to_slot = dict()
self.kh2_seed_save = None
self.kh2_local_items = None
self.growthlevel = None
self.kh2connected = False
self.serverconneced = False
self.item_name_to_data = {name: data for name, data, in item_dictionary_table.items()}
self.location_name_to_data = {name: data for name, data, in all_locations.items()}
self.kh2_loc_name_to_id = None
self.kh2_item_name_to_id = None
self.lookup_id_to_item = None
self.lookup_id_to_location = None
self.sora_ability_dict = {k: v.quantity for dic in [SupportAbility_Table, ActionAbility_Table] for k, v in
dic.items()}
self.location_name_to_worlddata = {name: data for name, data, in all_world_locations.items()}
self.sending = []
# list used to keep track of locations+items player has. Used for disoneccting
self.kh2_seed_save_cache = {
"itemIndex": -1,
# back of soras invo is 0x25E2. Growth should be moved there
# Character: [back of invo, front of invo]
"SoraInvo": [0x25D8, 0x2546],
"DonaldInvo": [0x26F4, 0x2658],
"GoofyInvo": [0x2808, 0x276C],
"AmountInvo": {
"Ability": {},
"Amount": {
"Bounty": 0,
},
"Growth": {
"High Jump": 0, "Quick Run": 0, "Dodge Roll": 0,
"Aerial Dodge": 0, "Glide": 0
},
"Bitmask": [],
"Weapon": {"Sora": [], "Donald": [], "Goofy": []},
"Equipment": [],
"Magic": {
"Fire Element": 0,
"Blizzard Element": 0,
"Thunder Element": 0,
"Cure Element": 0,
"Magnet Element": 0,
"Reflect Element": 0
},
"StatIncrease": {
ItemName.MaxHPUp: 0,
ItemName.MaxMPUp: 0,
ItemName.DriveGaugeUp: 0,
ItemName.ArmorSlotUp: 0,
ItemName.AccessorySlotUp: 0,
ItemName.ItemSlotUp: 0,
},
},
}
self.front_of_inventory = {
"Sora": 0x2546,
"Donald": 0x2658,
"Goofy": 0x276C,
}
self.kh2seedname = None
self.kh2slotdata = None
self.itemamount = {}
if "localappdata" in os.environ:
self.game_communication_path = os.path.expandvars(r"%localappdata%\KH2AP")
self.hitlist_bounties = 0
# hooked object
self.kh2 = None
self.final_xemnas = False
self.worldid_to_locations = {
# 1: {}, # world of darkness (story cutscenes)
2: TT_Checks,
# 3: {}, # destiny island doesn't have checks
4: HB_Checks,
5: BC_Checks,
6: Oc_Checks,
7: AG_Checks,
8: LoD_Checks,
9: HundredAcreChecks,
10: PL_Checks,
11: Atlantica_Checks,
12: DC_Checks,
13: TR_Checks,
14: HT_Checks,
15: HB_Checks, # world map, but you only go to the world map while on the way to goa so checking hb
16: PR_Checks,
17: SP_Checks,
18: TWTNW_Checks,
# 255: {}, # starting screen
}
# 0x2A09C00+0x40 is the sve anchor. +1 is the last saved room
# self.sveroom = 0x2A09C00 + 0x41
# 0 not in battle 1 in yellow battle 2 red battle #short
# self.inBattle = 0x2A0EAC4 + 0x40
# self.onDeath = 0xAB9078
# PC Address anchors
self.Now = 0x0714DB8
self.Save = 0x09A70B0
# self.Sys3 = 0x2A59DF0
# self.Bt10 = 0x2A74880
# self.BtlEnd = 0x2A0D3E0
self.Slot1 = 0x2A20C98
self.chest_set = set(exclusion_table["Chests"])
self.keyblade_set = set(CheckDupingItems["Weapons"]["Keyblades"])
self.staff_set = set(CheckDupingItems["Weapons"]["Staffs"])
self.shield_set = set(CheckDupingItems["Weapons"]["Shields"])
self.all_weapons = self.keyblade_set.union(self.staff_set).union(self.shield_set)
self.equipment_categories = CheckDupingItems["Equipment"]
self.armor_set = set(self.equipment_categories["Armor"])
self.accessories_set = set(self.equipment_categories["Accessories"])
self.all_equipment = self.armor_set.union(self.accessories_set)
self.Equipment_Anchor_Dict = {
"Armor": [0x2504, 0x2506, 0x2508, 0x250A],
"Accessories": [0x2514, 0x2516, 0x2518, 0x251A]
}
self.AbilityQuantityDict = {}
self.ability_categories = CheckDupingItems["Abilities"]
self.sora_ability_set = set(self.ability_categories["Sora"])
self.donald_ability_set = set(self.ability_categories["Donald"])
self.goofy_ability_set = set(self.ability_categories["Goofy"])
self.all_abilities = self.sora_ability_set.union(self.donald_ability_set).union(self.goofy_ability_set)
self.stat_increase_set = set(CheckDupingItems["Stat Increases"])
self.AbilityQuantityDict = {item: self.item_name_to_data[item].quantity for item in self.all_abilities}
# Growth:[level 1,level 4,slot]
self.growth_values_dict = {
"High Jump": [0x05E, 0x061, 0x25DA],
"Quick Run": [0x62, 0x65, 0x25DC],
"Dodge Roll": [0x234, 0x237, 0x25DE],
"Aerial Dodge": [0x66, 0x069, 0x25E0],
"Glide": [0x6A, 0x6D, 0x25E2]
}
self.ability_code_list = None
self.master_growth = {"High Jump", "Quick Run", "Dodge Roll", "Aerial Dodge", "Glide"}
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(KH2Context, self).server_auth(password_requested)
await self.get_username()
await self.send_connect()
async def connection_closed(self):
self.kh2connected = False
self.serverconneced = False
if self.kh2seedname is not None and self.auth is not None:
with open(os.path.join(self.game_communication_path, f"kh2save2{self.kh2seedname}{self.auth}.json"),
'w') as f:
f.write(json.dumps(self.kh2_seed_save, indent=4))
await super(KH2Context, self).connection_closed()
async def disconnect(self, allow_autoreconnect: bool = False):
self.kh2connected = False
self.serverconneced = False
if self.kh2seedname not in {None} and self.auth not in {None}:
with open(os.path.join(self.game_communication_path, f"kh2save2{self.kh2seedname}{self.auth}.json"),
'w') as f:
f.write(json.dumps(self.kh2_seed_save, indent=4))
await super(KH2Context, self).disconnect()
@property
def endpoints(self):
if self.server:
return [self.server]
else:
return []
async def shutdown(self):
if self.kh2seedname not in {None} and self.auth not in {None}:
with open(os.path.join(self.game_communication_path, f"kh2save2{self.kh2seedname}{self.auth}.json"),
'w') as f:
f.write(json.dumps(self.kh2_seed_save, indent=4))
await super(KH2Context, self).shutdown()
def kh2_read_short(self, address):
return self.kh2.read_short(self.kh2.base_address + address)
def kh2_write_short(self, address, value):
return self.kh2.write_short(self.kh2.base_address + address, value)
def kh2_write_byte(self, address, value):
return self.kh2.write_bytes(self.kh2.base_address + address, value.to_bytes(1, 'big'), 1)
def kh2_read_byte(self, address):
return int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + address, 1), "big")
def on_package(self, cmd: str, args: dict):
if cmd in {"RoomInfo"}:
self.kh2seedname = args['seed_name']
if not os.path.exists(self.game_communication_path):
os.makedirs(self.game_communication_path)
if not os.path.exists(self.game_communication_path + f"\kh2save2{self.kh2seedname}{self.auth}.json"):
self.kh2_seed_save = {
"Levels": {
"SoraLevel": 0,
"ValorLevel": 0,
"WisdomLevel": 0,
"LimitLevel": 0,
"MasterLevel": 0,
"FinalLevel": 0,
"SummonLevel": 0,
},
"SoldEquipment": [],
}
with open(os.path.join(self.game_communication_path, f"kh2save2{self.kh2seedname}{self.auth}.json"),
'wt') as f:
pass
# self.locations_checked = set()
elif os.path.exists(self.game_communication_path + f"\kh2save2{self.kh2seedname}{self.auth}.json"):
with open(self.game_communication_path + f"\kh2save2{self.kh2seedname}{self.auth}.json", 'r') as f:
self.kh2_seed_save = json.load(f)
if self.kh2_seed_save is None:
self.kh2_seed_save = {
"Levels": {
"SoraLevel": 0,
"ValorLevel": 0,
"WisdomLevel": 0,
"LimitLevel": 0,
"MasterLevel": 0,
"FinalLevel": 0,
"SummonLevel": 0,
},
"SoldEquipment": [],
}
# self.locations_checked = set(self.kh2_seed_save_cache["LocationsChecked"])
# self.serverconneced = True
if cmd in {"Connected"}:
asyncio.create_task(self.send_msgs([{"cmd": "GetDataPackage", "games": ["Kingdom Hearts 2"]}]))
self.kh2slotdata = args['slot_data']
# self.kh2_local_items = {int(location): item for location, item in self.kh2slotdata["LocalItems"].items()}
self.locations_checked = set(args["checked_locations"])
if cmd in {"ReceivedItems"}:
# 0x2546
# 0x2658
# 0x276A
start_index = args["index"]
if start_index == 0:
self.kh2_seed_save_cache = {
"itemIndex": -1,
# back of soras invo is 0x25E2. Growth should be moved there
# Character: [back of invo, front of invo]
"SoraInvo": [0x25D8, 0x2546],
"DonaldInvo": [0x26F4, 0x2658],
"GoofyInvo": [0x2808, 0x276C],
"AmountInvo": {
"Ability": {},
"Amount": {
"Bounty": 0,
},
"Growth": {
"High Jump": 0, "Quick Run": 0, "Dodge Roll": 0,
"Aerial Dodge": 0, "Glide": 0
},
"Bitmask": [],
"Weapon": {"Sora": [], "Donald": [], "Goofy": []},
"Equipment": [],
"Magic": {
"Fire Element": 0,
"Blizzard Element": 0,
"Thunder Element": 0,
"Cure Element": 0,
"Magnet Element": 0,
"Reflect Element": 0
},
"StatIncrease": {
ItemName.MaxHPUp: 0,
ItemName.MaxMPUp: 0,
ItemName.DriveGaugeUp: 0,
ItemName.ArmorSlotUp: 0,
ItemName.AccessorySlotUp: 0,
ItemName.ItemSlotUp: 0,
},
},
}
if start_index > self.kh2_seed_save_cache["itemIndex"] and self.serverconneced:
self.kh2_seed_save_cache["itemIndex"] = start_index
for item in args['items']:
asyncio.create_task(self.give_item(item.item, item.location))
if cmd in {"RoomUpdate"}:
if "checked_locations" in args:
new_locations = set(args["checked_locations"])
self.locations_checked |= new_locations
if cmd in {"DataPackage"}:
self.kh2_loc_name_to_id = args["data"]["games"]["Kingdom Hearts 2"]["location_name_to_id"]
self.lookup_id_to_location = {v: k for k, v in self.kh2_loc_name_to_id.items()}
self.kh2_item_name_to_id = args["data"]["games"]["Kingdom Hearts 2"]["item_name_to_id"]
self.lookup_id_to_item = {v: k for k, v in self.kh2_item_name_to_id.items()}
self.ability_code_list = [self.kh2_item_name_to_id[item] for item in exclusion_item_table["Ability"]]
if "keyblade_abilities" in self.kh2slotdata.keys():
sora_ability_dict = self.kh2slotdata["KeybladeAbilities"]
# sora ability to slot
# itemid:[slots that are available for that item]
for k, v in sora_ability_dict.items():
if v >= 1:
if k not in self.sora_ability_to_slot.keys():
self.sora_ability_to_slot[k] = []
for _ in range(sora_ability_dict[k]):
self.sora_ability_to_slot[k].append(self.kh2_seed_save_cache["SoraInvo"][0])
self.kh2_seed_save_cache["SoraInvo"][0] -= 2
donald_ability_dict = self.kh2slotdata["StaffAbilities"]
for k, v in donald_ability_dict.items():
if v >= 1:
if k not in self.donald_ability_to_slot.keys():
self.donald_ability_to_slot[k] = []
for _ in range(donald_ability_dict[k]):
self.donald_ability_to_slot[k].append(self.kh2_seed_save_cache["DonaldInvo"][0])
self.kh2_seed_save_cache["DonaldInvo"][0] -= 2
goofy_ability_dict = self.kh2slotdata["ShieldAbilities"]
for k, v in goofy_ability_dict.items():
if v >= 1:
if k not in self.goofy_ability_to_slot.keys():
self.goofy_ability_to_slot[k] = []
for _ in range(goofy_ability_dict[k]):
self.goofy_ability_to_slot[k].append(self.kh2_seed_save_cache["GoofyInvo"][0])
self.kh2_seed_save_cache["GoofyInvo"][0] -= 2
all_weapon_location_id = []
for weapon_location in all_weapon_slot:
all_weapon_location_id.append(self.kh2_loc_name_to_id[weapon_location])
self.all_weapon_location_id = set(all_weapon_location_id)
try:
self.kh2 = pymem.Pymem(process_name="KINGDOM HEARTS II FINAL MIX")
logger.info("You are now auto-tracking")
self.kh2connected = True
except Exception as e:
if self.kh2connected:
self.kh2connected = False
logger.info("Game is not open.")
self.serverconneced = True
asyncio.create_task(self.send_msgs([{'cmd': 'Sync'}]))
async def checkWorldLocations(self):
try:
currentworldint = self.kh2_read_byte(self.Now)
await self.send_msgs([{
"cmd": "Set", "key": "Slot: " + str(self.slot) + " :CurrentWorld",
"default": 0, "want_reply": True, "operations": [{
"operation": "replace",
"value": currentworldint
}]
}])
if currentworldint in self.worldid_to_locations:
curworldid = self.worldid_to_locations[currentworldint]
for location, data in curworldid.items():
if location in self.kh2_loc_name_to_id.keys():
locationId = self.kh2_loc_name_to_id[location]
if locationId not in self.locations_checked \
and self.kh2_read_byte(self.Save + data.addrObtained) & 0x1 << data.bitIndex > 0:
self.sending = self.sending + [(int(locationId))]
except Exception as e:
if self.kh2connected:
self.kh2connected = False
logger.info(e)
logger.info("line 425")
async def checkLevels(self):
try:
for location, data in SoraLevels.items():
currentLevel = self.kh2_read_byte(self.Save + 0x24FF)
locationId = self.kh2_loc_name_to_id[location]
if locationId not in self.locations_checked \
and currentLevel >= data.bitIndex:
if self.kh2_seed_save["Levels"]["SoraLevel"] < currentLevel:
self.kh2_seed_save["Levels"]["SoraLevel"] = currentLevel
self.sending = self.sending + [(int(locationId))]
formDict = {
0: ["ValorLevel", ValorLevels], 1: ["WisdomLevel", WisdomLevels], 2: ["LimitLevel", LimitLevels],
3: ["MasterLevel", MasterLevels], 4: ["FinalLevel", FinalLevels], 5: ["SummonLevel", SummonLevels]
}
# TODO: remove formDict[i][0] in self.kh2_seed_save_cache["Levels"].keys() after 4.3
for i in range(6):
for location, data in formDict[i][1].items():
formlevel = self.kh2_read_byte(self.Save + data.addrObtained)
if location in self.kh2_loc_name_to_id.keys():
# if current form level is above other form level
locationId = self.kh2_loc_name_to_id[location]
if locationId not in self.locations_checked \
and formlevel >= data.bitIndex:
if formlevel > self.kh2_seed_save["Levels"][formDict[i][0]]:
self.kh2_seed_save["Levels"][formDict[i][0]] = formlevel
self.sending = self.sending + [(int(locationId))]
except Exception as e:
if self.kh2connected:
self.kh2connected = False
logger.info(e)
logger.info("line 456")
async def checkSlots(self):
try:
for location, data in weaponSlots.items():
locationId = self.kh2_loc_name_to_id[location]
if locationId not in self.locations_checked:
if self.kh2_read_byte(self.Save + data.addrObtained) > 0:
self.sending = self.sending + [(int(locationId))]
for location, data in formSlots.items():
locationId = self.kh2_loc_name_to_id[location]
if locationId not in self.locations_checked and self.kh2_read_byte(self.Save + 0x06B2) == 0:
if self.kh2_read_byte(self.Save + data.addrObtained) & 0x1 << data.bitIndex > 0:
self.sending = self.sending + [(int(locationId))]
except Exception as e:
if self.kh2connected:
self.kh2connected = False
logger.info(e)
logger.info("line 475")
async def verifyChests(self):
try:
for location in self.locations_checked:
locationName = self.lookup_id_to_location[location]
if locationName in self.chest_set:
if locationName in self.location_name_to_worlddata.keys():
locationData = self.location_name_to_worlddata[locationName]
if self.kh2_read_byte(self.Save + locationData.addrObtained) & 0x1 << locationData.bitIndex == 0:
roomData = self.kh2_read_byte(self.Save + locationData.addrObtained)
self.kh2_write_byte(self.Save + locationData.addrObtained, roomData | 0x01 << locationData.bitIndex)
except Exception as e:
if self.kh2connected:
self.kh2connected = False
logger.info(e)
logger.info("line 491")
async def verifyLevel(self):
for leveltype, anchor in {
"SoraLevel": 0x24FF,
"ValorLevel": 0x32F6,
"WisdomLevel": 0x332E,
"LimitLevel": 0x3366,
"MasterLevel": 0x339E,
"FinalLevel": 0x33D6
}.items():
if self.kh2_read_byte(self.Save + anchor) < self.kh2_seed_save["Levels"][leveltype]:
self.kh2_write_byte(self.Save + anchor, self.kh2_seed_save["Levels"][leveltype])
async def give_item(self, item, location):
try:
# todo: ripout all the itemtype stuff and just have one dictionary. the only thing that needs to be tracked from the server/local is abilites
itemname = self.lookup_id_to_item[item]
itemdata = self.item_name_to_data[itemname]
# itemcode = self.kh2_item_name_to_id[itemname]
if itemdata.ability:
if location in self.all_weapon_location_id:
return
if itemname in {"High Jump", "Quick Run", "Dodge Roll", "Aerial Dodge", "Glide"}:
self.kh2_seed_save_cache["AmountInvo"]["Growth"][itemname] += 1
return
if itemname not in self.kh2_seed_save_cache["AmountInvo"]["Ability"]:
self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname] = []
# appending the slot that the ability should be in
# for non beta. remove after 4.3
if "PoptrackerVersion" in self.kh2slotdata:
if self.kh2slotdata["PoptrackerVersionCheck"] < 4.3:
if (itemname in self.sora_ability_set
and len(self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname]) < self.item_name_to_data[itemname].quantity) \
and self.kh2_seed_save_cache["SoraInvo"][1] > 0x254C:
ability_slot = self.kh2_seed_save_cache["SoraInvo"][1]
self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname].append(ability_slot)
self.kh2_seed_save_cache["SoraInvo"][1] -= 2
elif itemname in self.donald_ability_set:
ability_slot = self.kh2_seed_save_cache["DonaldInvo"][1]
self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname].append(ability_slot)
self.kh2_seed_save_cache["DonaldInvo"][1] -= 2
else:
ability_slot = self.kh2_seed_save_cache["GoofyInvo"][1]
self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname].append(ability_slot)
self.kh2_seed_save_cache["GoofyInvo"][1] -= 2
elif len(self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname]) < \
self.AbilityQuantityDict[itemname]:
if itemname in self.sora_ability_set:
ability_slot = self.kh2_seed_save_cache["SoraInvo"][0]
self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname].append(ability_slot)
self.kh2_seed_save_cache["SoraInvo"][0] -= 2
elif itemname in self.donald_ability_set:
ability_slot = self.kh2_seed_save_cache["DonaldInvo"][0]
self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname].append(ability_slot)
self.kh2_seed_save_cache["DonaldInvo"][0] -= 2
elif itemname in self.goofy_ability_set:
ability_slot = self.kh2_seed_save_cache["GoofyInvo"][0]
self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname].append(ability_slot)
self.kh2_seed_save_cache["GoofyInvo"][0] -= 2
elif itemdata.memaddr in {0x36C4, 0x36C5, 0x36C6, 0x36C0, 0x36CA}:
# if memaddr is in a bitmask location in memory
if itemname not in self.kh2_seed_save_cache["AmountInvo"]["Bitmask"]:
self.kh2_seed_save_cache["AmountInvo"]["Bitmask"].append(itemname)
elif itemdata.memaddr in {0x3594, 0x3595, 0x3596, 0x3597, 0x35CF, 0x35D0}:
# if memaddr is in magic addresses
self.kh2_seed_save_cache["AmountInvo"]["Magic"][itemname] += 1
elif itemname in self.all_equipment:
self.kh2_seed_save_cache["AmountInvo"]["Equipment"].append(itemname)
elif itemname in self.all_weapons:
if itemname in self.keyblade_set:
self.kh2_seed_save_cache["AmountInvo"]["Weapon"]["Sora"].append(itemname)
elif itemname in self.staff_set:
self.kh2_seed_save_cache["AmountInvo"]["Weapon"]["Donald"].append(itemname)
else:
self.kh2_seed_save_cache["AmountInvo"]["Weapon"]["Goofy"].append(itemname)
elif itemname in self.stat_increase_set:
self.kh2_seed_save_cache["AmountInvo"]["StatIncrease"][itemname] += 1
else:
if itemname in self.kh2_seed_save_cache["AmountInvo"]["Amount"]:
self.kh2_seed_save_cache["AmountInvo"]["Amount"][itemname] += 1
else:
self.kh2_seed_save_cache["AmountInvo"]["Amount"][itemname] = 1
except Exception as e:
if self.kh2connected:
self.kh2connected = False
logger.info(e)
logger.info("line 582")
def run_gui(self):
"""Import kivy UI system and start running it as self.ui_task."""
from kvui import GameManager
class KH2Manager(GameManager):
logging_pairs = [
("Client", "Archipelago")
]
base_title = "Archipelago KH2 Client"
self.ui = KH2Manager(self)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
async def IsInShop(self, sellable):
# journal = 0x741230 shop = 0x741320
# if journal=-1 and shop = 5 then in shop
# if journal !=-1 and shop = 10 then journal
journal = self.kh2_read_short(0x741230)
shop = self.kh2_read_short(0x741320)
if (journal == -1 and shop == 5) or (journal != -1 and shop == 10):
# print("your in the shop")
sellable_dict = {}
for itemName in sellable:
itemdata = self.item_name_to_data[itemName]
amount = self.kh2_read_byte(self.Save + itemdata.memaddr)
sellable_dict[itemName] = amount
while (journal == -1 and shop == 5) or (journal != -1 and shop == 10):
journal = self.kh2_read_short(0x741230)
shop = self.kh2_read_short(0x741320)
await asyncio.sleep(0.5)
for item, amount in sellable_dict.items():
itemdata = self.item_name_to_data[item]
afterShop = self.kh2_read_byte(self.Save + itemdata.memaddr)
if afterShop < amount:
self.kh2_seed_save["SoldEquipment"].append(item)
async def verifyItems(self):
try:
master_amount = set(self.kh2_seed_save_cache["AmountInvo"]["Amount"].keys())
master_ability = set(self.kh2_seed_save_cache["AmountInvo"]["Ability"].keys())
master_bitmask = set(self.kh2_seed_save_cache["AmountInvo"]["Bitmask"])
master_keyblade = set(self.kh2_seed_save_cache["AmountInvo"]["Weapon"]["Sora"])
master_staff = set(self.kh2_seed_save_cache["AmountInvo"]["Weapon"]["Donald"])
master_shield = set(self.kh2_seed_save_cache["AmountInvo"]["Weapon"]["Goofy"])
master_equipment = set(self.kh2_seed_save_cache["AmountInvo"]["Equipment"])
master_magic = set(self.kh2_seed_save_cache["AmountInvo"]["Magic"].keys())
master_stat = set(self.kh2_seed_save_cache["AmountInvo"]["StatIncrease"].keys())
master_sell = master_equipment | master_staff | master_shield
await asyncio.create_task(self.IsInShop(master_sell))
for item_name in master_amount:
item_data = self.item_name_to_data[item_name]
amount_of_items = 0
amount_of_items += self.kh2_seed_save_cache["AmountInvo"]["Amount"][item_name]
if item_name == "Torn Page":
# Torn Pages are handled differently because they can be consumed.
# Will check the progression in 100 acre and - the amount of visits
# amountofitems-amount of visits done
for location, data in tornPageLocks.items():
if self.kh2_read_byte(self.Save + data.addrObtained) & 0x1 << data.bitIndex > 0:
amount_of_items -= 1
if self.kh2_read_byte(self.Save + item_data.memaddr) != amount_of_items and amount_of_items >= 0:
self.kh2_write_byte(self.Save + item_data.memaddr, amount_of_items)
for item_name in master_keyblade:
item_data = self.item_name_to_data[item_name]
# if the inventory slot for that keyblade is less than the amount they should have,
# and they are not in stt
if self.kh2_read_byte(self.Save + item_data.memaddr) != 1 and self.kh2_read_byte(self.Save + 0x1CFF) != 13:
# Checking form anchors for the keyblade to remove extra keyblades
if self.kh2_read_short(self.Save + 0x24F0) == item_data.kh2id \
or self.kh2_read_short(self.Save + 0x32F4) == item_data.kh2id \
or self.kh2_read_short(self.Save + 0x339C) == item_data.kh2id \
or self.kh2_read_short(self.Save + 0x33D4) == item_data.kh2id:
self.kh2_write_byte(self.Save + item_data.memaddr, 0)
else:
self.kh2_write_byte(self.Save + item_data.memaddr, 1)
for item_name in master_staff:
item_data = self.item_name_to_data[item_name]
if self.kh2_read_byte(self.Save + item_data.memaddr) != 1 \
and self.kh2_read_short(self.Save + 0x2604) != item_data.kh2id \
and item_name not in self.kh2_seed_save["SoldEquipment"]:
self.kh2_write_byte(self.Save + item_data.memaddr, 1)
for item_name in master_shield:
item_data = self.item_name_to_data[item_name]
if self.kh2_read_byte(self.Save + item_data.memaddr) != 1 \
and self.kh2_read_short(self.Save + 0x2718) != item_data.kh2id \
and item_name not in self.kh2_seed_save["SoldEquipment"]:
self.kh2_write_byte(self.Save + item_data.memaddr, 1)
for item_name in master_ability:
item_data = self.item_name_to_data[item_name]
ability_slot = []
ability_slot += self.kh2_seed_save_cache["AmountInvo"]["Ability"][item_name]
for slot in ability_slot:
current = self.kh2_read_short(self.Save + slot)
ability = current & 0x0FFF
if ability | 0x8000 != (0x8000 + item_data.memaddr):
if current - 0x8000 > 0:
self.kh2_write_short(self.Save + slot, 0x8000 + item_data.memaddr)
else:
self.kh2_write_short(self.Save + slot, item_data.memaddr)
# removes the duped ability if client gave faster than the game.
for charInvo in {"Sora", "Donald", "Goofy"}:
if self.kh2_read_short(self.Save + self.front_of_inventory[charInvo]) != 0:
print(f"removed {self.Save + self.front_of_inventory[charInvo]} from {charInvo}")
self.kh2_write_short(self.Save + self.front_of_inventory[charInvo], 0)
# remove the dummy level 1 growths if they are in these invo slots.
for inventorySlot in {0x25CE, 0x25D0, 0x25D2, 0x25D4, 0x25D6, 0x25D8}:
current = self.kh2_read_short(self.Save + inventorySlot)
ability = current & 0x0FFF
if 0x05E <= ability <= 0x06D:
self.kh2_write_short(self.Save + inventorySlot, 0)
for item_name in self.master_growth:
growthLevel = self.kh2_seed_save_cache["AmountInvo"]["Growth"][item_name]
if growthLevel > 0:
slot = self.growth_values_dict[item_name][2]
min_growth = self.growth_values_dict[item_name][0]
max_growth = self.growth_values_dict[item_name][1]
if growthLevel > 4:
growthLevel = 4
current_growth_level = self.kh2_read_short(self.Save + slot)
ability = current_growth_level & 0x0FFF
# if the player should be getting a growth ability
if ability | 0x8000 != 0x8000 + min_growth - 1 + growthLevel:
# if it should be level one of that growth
if 0x8000 + min_growth - 1 + growthLevel <= 0x8000 + min_growth or ability < min_growth:
self.kh2_write_short(self.Save + slot, min_growth)
# if it is already in the inventory
elif ability | 0x8000 < (0x8000 + max_growth):
self.kh2_write_short(self.Save + slot, current_growth_level + 1)
for item_name in master_bitmask:
item_data = self.item_name_to_data[item_name]
itemMemory = self.kh2_read_byte(self.Save + item_data.memaddr)
if self.kh2_read_byte(self.Save + item_data.memaddr) & 0x1 << item_data.bitmask == 0:
# when getting a form anti points should be reset to 0 but bit-shift doesn't trigger the game.
if item_name in {"Valor Form", "Wisdom Form", "Limit Form", "Master Form", "Final Form"}:
self.kh2_write_byte(self.Save + 0x3410, 0)
self.kh2_write_byte(self.Save + item_data.memaddr, itemMemory | 0x01 << item_data.bitmask)
for item_name in master_equipment:
item_data = self.item_name_to_data[item_name]
is_there = False
if item_name in self.accessories_set:
Equipment_Anchor_List = self.Equipment_Anchor_Dict["Accessories"]
else:
Equipment_Anchor_List = self.Equipment_Anchor_Dict["Armor"]
# Checking form anchors for the equipment
for slot in Equipment_Anchor_List:
if self.kh2_read_short(self.Save + slot) == item_data.kh2id:
is_there = True
if self.kh2_read_byte(self.Save + item_data.memaddr) != 0:
self.kh2_write_byte(self.Save + item_data.memaddr, 0)
break
if not is_there and item_name not in self.kh2_seed_save["SoldEquipment"]:
if self.kh2_read_byte(self.Save + item_data.memaddr) != 1:
self.kh2_write_byte(self.Save + item_data.memaddr, 1)
for item_name in master_magic:
item_data = self.item_name_to_data[item_name]
amount_of_items = 0
amount_of_items += self.kh2_seed_save_cache["AmountInvo"]["Magic"][item_name]
if self.kh2_read_byte(self.Save + item_data.memaddr) != amount_of_items and self.kh2_read_byte(0x741320) in {10, 8}:
self.kh2_write_byte(self.Save + item_data.memaddr, amount_of_items)
for item_name in master_stat:
item_data = self.item_name_to_data[item_name]
amount_of_items = 0
amount_of_items += self.kh2_seed_save_cache["AmountInvo"]["StatIncrease"][item_name]
# if slot1 has 5 drive gauge and goa lost illusion is checked and they are not in a cutscene
if self.kh2_read_byte(self.Save + item_data.memaddr) != amount_of_items \
and self.kh2_read_byte(self.Slot1 + 0x1B2) >= 5 and \
self.kh2_read_byte(self.Save + 0x23DF) & 0x1 << 3 > 0 and self.kh2_read_byte(0x741320) in {10, 8}:
self.kh2_write_byte(self.Save + item_data.memaddr, amount_of_items)
if "PoptrackerVersionCheck" in self.kh2slotdata:
if self.kh2slotdata["PoptrackerVersionCheck"] > 4.2 and self.kh2_read_byte(self.Save + 0x3607) != 1: # telling the goa they are on version 4.3
self.kh2_write_byte(self.Save + 0x3607, 1)
except Exception as e:
if self.kh2connected:
self.kh2connected = False
logger.info(e)
logger.info("line 840")
def finishedGame(ctx: KH2Context, message):
if ctx.kh2slotdata['FinalXemnas'] == 1:
if not ctx.final_xemnas and ctx.kh2_loc_name_to_id[LocationName.FinalXemnas] in ctx.locations_checked:
ctx.final_xemnas = True
# three proofs
if ctx.kh2slotdata['Goal'] == 0:
if ctx.kh2_read_byte(ctx.Save + 0x36B2) > 0 \
and ctx.kh2_read_byte(ctx.Save + 0x36B3) > 0 \
and ctx.kh2_read_byte(ctx.Save + 0x36B4) > 0:
if ctx.kh2slotdata['FinalXemnas'] == 1:
if ctx.final_xemnas:
return True
return False
return True
return False
elif ctx.kh2slotdata['Goal'] == 1:
if ctx.kh2_read_byte(ctx.Save + 0x3641) >= ctx.kh2slotdata['LuckyEmblemsRequired']:
if ctx.kh2_read_byte(ctx.Save + 0x36B3) < 1:
ctx.kh2_write_byte(ctx.Save + 0x36B2, 1)
ctx.kh2_write_byte(ctx.Save + 0x36B3, 1)
ctx.kh2_write_byte(ctx.Save + 0x36B4, 1)
logger.info("The Final Door is now Open")
if ctx.kh2slotdata['FinalXemnas'] == 1:
if ctx.final_xemnas:
return True
return False
return True
return False
elif ctx.kh2slotdata['Goal'] == 2:
# for backwards compat
if "hitlist" in ctx.kh2slotdata:
for boss in ctx.kh2slotdata["hitlist"]:
if boss in message[0]["locations"]:
ctx.hitlist_bounties += 1
if ctx.hitlist_bounties >= ctx.kh2slotdata["BountyRequired"] or ctx.kh2_seed_save_cache["AmountInvo"]["Amount"]["Bounty"] >= ctx.kh2slotdata["BountyRequired"]:
if ctx.kh2_read_byte(ctx.Save + 0x36B3) < 1:
ctx.kh2_write_byte(ctx.Save + 0x36B2, 1)
ctx.kh2_write_byte(ctx.Save + 0x36B3, 1)
ctx.kh2_write_byte(ctx.Save + 0x36B4, 1)
logger.info("The Final Door is now Open")
if ctx.kh2slotdata['FinalXemnas'] == 1:
if ctx.final_xemnas:
return True
return False
return True
return False
elif ctx.kh2slotdata["Goal"] == 3:
if ctx.kh2_seed_save_cache["AmountInvo"]["Amount"]["Bounty"] >= ctx.kh2slotdata["BountyRequired"] and \
ctx.kh2_read_byte(ctx.Save + 0x3641) >= ctx.kh2slotdata['LuckyEmblemsRequired']:
if ctx.kh2_read_byte(ctx.Save + 0x36B3) < 1:
ctx.kh2_write_byte(ctx.Save + 0x36B2, 1)
ctx.kh2_write_byte(ctx.Save + 0x36B3, 1)
ctx.kh2_write_byte(ctx.Save + 0x36B4, 1)
logger.info("The Final Door is now Open")
if ctx.kh2slotdata['FinalXemnas'] == 1:
if ctx.final_xemnas:
return True
return False
return True
return False
async def kh2_watcher(ctx: KH2Context):
while not ctx.exit_event.is_set():
try:
if ctx.kh2connected and ctx.serverconneced:
ctx.sending = []
await asyncio.create_task(ctx.checkWorldLocations())
await asyncio.create_task(ctx.checkLevels())
await asyncio.create_task(ctx.checkSlots())
await asyncio.create_task(ctx.verifyChests())
await asyncio.create_task(ctx.verifyItems())
await asyncio.create_task(ctx.verifyLevel())
message = [{"cmd": 'LocationChecks', "locations": ctx.sending}]
if finishedGame(ctx, message):
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
ctx.finished_game = True
await ctx.send_msgs(message)
elif not ctx.kh2connected and ctx.serverconneced:
logger.info("Game Connection lost. waiting 15 seconds until trying to reconnect.")
ctx.kh2 = None
while not ctx.kh2connected and ctx.serverconneced:
await asyncio.sleep(15)
ctx.kh2 = pymem.Pymem(process_name="KINGDOM HEARTS II FINAL MIX")
if ctx.kh2 is not None:
logger.info("You are now auto-tracking")
ctx.kh2connected = True
except Exception as e:
if ctx.kh2connected:
ctx.kh2connected = False
logger.info(e)
logger.info("line 940")
await asyncio.sleep(0.5)
def launch():
async def main(args):
ctx = KH2Context(args.connect, args.password)
ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop")
if gui_enabled:
ctx.run_gui()
ctx.run_cli()
progression_watcher = asyncio.create_task(
kh2_watcher(ctx), name="KH2ProgressionWatcher")
await ctx.exit_event.wait()
ctx.server_address = None
await progression_watcher
await ctx.shutdown()
import colorama
parser = get_base_parser(description="KH2 Client, for text interfacing.")
args, rest = parser.parse_known_args()
colorama.init()
asyncio.run(main(args))
colorama.deinit()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

642
worlds/kh2/Logic.py Normal file
View File

@ -0,0 +1,642 @@
from .Names import ItemName, RegionName, LocationName
# this file contains the dicts,lists and sets used for making rules in rules.py
base_tools = [
ItemName.FinishingPlus,
ItemName.Guard,
ItemName.AerialRecovery
]
gap_closer = [
ItemName.SlideDash,
ItemName.FlashStep
]
defensive_tool = [
ItemName.ReflectElement,
ItemName.Guard
]
form_list = [
ItemName.ValorForm,
ItemName.WisdomForm,
ItemName.LimitForm,
ItemName.MasterForm,
ItemName.FinalForm
]
form_list_without_final = [
ItemName.ValorForm,
ItemName.WisdomForm,
ItemName.LimitForm,
ItemName.MasterForm
]
ground_finisher = [
ItemName.GuardBreak,
ItemName.Explosion,
ItemName.FinishingLeap
]
party_limit = [
ItemName.Fantasia,
ItemName.FlareForce,
ItemName.Teamwork,
ItemName.TornadoFusion
]
donald_limit = [
ItemName.Fantasia,
ItemName.FlareForce
]
aerial_move = [
ItemName.AerialDive,
ItemName.AerialSpiral,
ItemName.HorizontalSlash,
ItemName.AerialSweep,
ItemName.AerialFinish
]
level_3_form_loc = [
LocationName.Valorlvl3,
LocationName.Wisdomlvl3,
LocationName.Limitlvl3,
LocationName.Masterlvl3,
LocationName.Finallvl3
]
black_magic = [
ItemName.FireElement,
ItemName.BlizzardElement,
ItemName.ThunderElement
]
magic = [
ItemName.FireElement,
ItemName.BlizzardElement,
ItemName.ThunderElement,
ItemName.ReflectElement,
ItemName.CureElement,
ItemName.MagnetElement
]
summons = [
ItemName.ChickenLittle,
ItemName.Stitch,
ItemName.Genie,
ItemName.PeterPan
]
three_proofs = [
ItemName.ProofofConnection,
ItemName.ProofofPeace,
ItemName.ProofofNonexistence
]
auto_form_dict = {
ItemName.FinalForm: ItemName.AutoFinal,
ItemName.MasterForm: ItemName.AutoMaster,
ItemName.LimitForm: ItemName.AutoLimit,
ItemName.WisdomForm: ItemName.AutoWisdom,
ItemName.ValorForm: ItemName.AutoValor,
}
# could use comprehension for getting a list of the region objects but eh I like this more
drive_form_list = [RegionName.Valor, RegionName.Wisdom, RegionName.Limit, RegionName.Master, RegionName.Final, RegionName.Summon]
easy_data_xigbar_tools = {
ItemName.FinishingPlus: 1,
ItemName.Guard: 1,
ItemName.AerialDive: 1,
ItemName.HorizontalSlash: 1,
ItemName.AirComboPlus: 2,
ItemName.FireElement: 3,
ItemName.ReflectElement: 3,
}
normal_data_xigbar_tools = {
ItemName.FinishingPlus: 1,
ItemName.Guard: 1,
ItemName.HorizontalSlash: 1,
ItemName.FireElement: 3,
ItemName.ReflectElement: 3,
}
easy_data_lex_tools = {
ItemName.Guard: 1,
ItemName.FireElement: 3,
ItemName.ReflectElement: 2,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1
}
normal_data_lex_tools = {
ItemName.Guard: 1,
ItemName.FireElement: 3,
ItemName.ReflectElement: 1,
}
easy_data_marluxia_tools = {
ItemName.Guard: 1,
ItemName.FireElement: 3,
ItemName.ReflectElement: 2,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.AerialRecovery: 1,
}
normal_data_marluxia_tools = {
ItemName.Guard: 1,
ItemName.FireElement: 3,
ItemName.ReflectElement: 1,
ItemName.AerialRecovery: 1,
}
easy_terra_tools = {
ItemName.SecondChance: 1,
ItemName.OnceMore: 1,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.Explosion: 1,
ItemName.ComboPlus: 2,
ItemName.FireElement: 3,
ItemName.Fantasia: 1,
ItemName.FlareForce: 1,
ItemName.ReflectElement: 1,
ItemName.Guard: 1,
ItemName.DodgeRoll: 3,
ItemName.AerialDodge: 3,
ItemName.Glide: 3
}
normal_terra_tools = {
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.Explosion: 1,
ItemName.ComboPlus: 2,
ItemName.Guard: 1,
ItemName.DodgeRoll: 2,
ItemName.AerialDodge: 2,
ItemName.Glide: 2
}
hard_terra_tools = {
ItemName.Explosion: 1,
ItemName.ComboPlus: 2,
ItemName.DodgeRoll: 2,
ItemName.AerialDodge: 2,
ItemName.Glide: 2,
ItemName.Guard: 1
}
easy_data_luxord_tools = {
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.AerialDodge: 2,
ItemName.Glide: 2,
ItemName.ReflectElement: 3,
ItemName.Guard: 1,
}
easy_data_zexion = {
ItemName.FireElement: 3,
ItemName.SecondChance: 1,
ItemName.OnceMore: 1,
ItemName.Fantasia: 1,
ItemName.FlareForce: 1,
ItemName.ReflectElement: 3,
ItemName.Guard: 1,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.QuickRun: 3,
}
normal_data_zexion = {
ItemName.FireElement: 3,
ItemName.ReflectElement: 3,
ItemName.Guard: 1,
ItemName.QuickRun: 3
}
hard_data_zexion = {
ItemName.FireElement: 2,
ItemName.ReflectElement: 1,
ItemName.QuickRun: 2,
}
easy_data_xaldin = {
ItemName.FireElement: 3,
ItemName.AirComboPlus: 2,
ItemName.FinishingPlus: 1,
ItemName.Guard: 1,
ItemName.ReflectElement: 3,
ItemName.FlareForce: 1,
ItemName.Fantasia: 1,
ItemName.HighJump: 3,
ItemName.AerialDodge: 3,
ItemName.Glide: 3,
ItemName.MagnetElement: 1,
ItemName.HorizontalSlash: 1,
ItemName.AerialDive: 1,
ItemName.AerialSpiral: 1,
ItemName.BerserkCharge: 1
}
normal_data_xaldin = {
ItemName.FireElement: 3,
ItemName.FinishingPlus: 1,
ItemName.Guard: 1,
ItemName.ReflectElement: 3,
ItemName.FlareForce: 1,
ItemName.Fantasia: 1,
ItemName.HighJump: 3,
ItemName.AerialDodge: 3,
ItemName.Glide: 3,
ItemName.MagnetElement: 1,
ItemName.HorizontalSlash: 1,
ItemName.AerialDive: 1,
ItemName.AerialSpiral: 1,
}
hard_data_xaldin = {
ItemName.FireElement: 2,
ItemName.FinishingPlus: 1,
ItemName.Guard: 1,
ItemName.HighJump: 2,
ItemName.AerialDodge: 2,
ItemName.Glide: 2,
ItemName.MagnetElement: 1,
ItemName.AerialDive: 1
}
easy_data_larxene = {
ItemName.FireElement: 3,
ItemName.SecondChance: 1,
ItemName.OnceMore: 1,
ItemName.Fantasia: 1,
ItemName.FlareForce: 1,
ItemName.ReflectElement: 3,
ItemName.Guard: 1,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.AerialDodge: 3,
ItemName.Glide: 3,
ItemName.GuardBreak: 1,
ItemName.Explosion: 1
}
normal_data_larxene = {
ItemName.FireElement: 3,
ItemName.ReflectElement: 3,
ItemName.Guard: 1,
ItemName.AerialDodge: 3,
ItemName.Glide: 3,
}
hard_data_larxene = {
ItemName.FireElement: 2,
ItemName.ReflectElement: 1,
ItemName.Guard: 1,
ItemName.AerialDodge: 2,
ItemName.Glide: 2,
}
easy_data_vexen = {
ItemName.FireElement: 3,
ItemName.SecondChance: 1,
ItemName.OnceMore: 1,
ItemName.Fantasia: 1,
ItemName.FlareForce: 1,
ItemName.ReflectElement: 3,
ItemName.Guard: 1,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.AerialDodge: 3,
ItemName.Glide: 3,
ItemName.GuardBreak: 1,
ItemName.Explosion: 1,
ItemName.DodgeRoll: 3,
ItemName.QuickRun: 3,
}
normal_data_vexen = {
ItemName.FireElement: 3,
ItemName.ReflectElement: 3,
ItemName.Guard: 1,
ItemName.AerialDodge: 3,
ItemName.Glide: 3,
ItemName.DodgeRoll: 3,
ItemName.QuickRun: 3,
}
hard_data_vexen = {
ItemName.FireElement: 2,
ItemName.ReflectElement: 1,
ItemName.Guard: 1,
ItemName.AerialDodge: 2,
ItemName.Glide: 2,
ItemName.DodgeRoll: 3,
ItemName.QuickRun: 3,
}
easy_thousand_heartless_rules = {
ItemName.SecondChance: 1,
ItemName.OnceMore: 1,
ItemName.Guard: 1,
ItemName.MagnetElement: 2,
}
normal_thousand_heartless_rules = {
ItemName.LimitForm: 1,
ItemName.Guard: 1,
}
easy_data_demyx = {
ItemName.FormBoost: 1,
ItemName.ReflectElement: 2,
ItemName.FireElement: 3,
ItemName.FlareForce: 1,
ItemName.Guard: 1,
ItemName.SecondChance: 1,
ItemName.OnceMore: 1,
ItemName.FinishingPlus: 1,
}
normal_data_demyx = {
ItemName.ReflectElement: 2,
ItemName.FireElement: 3,
ItemName.FlareForce: 1,
ItemName.Guard: 1,
ItemName.FinishingPlus: 1,
}
hard_data_demyx = {
ItemName.ReflectElement: 1,
ItemName.FireElement: 2,
ItemName.FlareForce: 1,
ItemName.Guard: 1,
ItemName.FinishingPlus: 1,
}
easy_sephiroth_tools = {
ItemName.Guard: 1,
ItemName.ReflectElement: 3,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.GuardBreak: 1,
ItemName.Explosion: 1,
ItemName.DodgeRoll: 3,
ItemName.FinishingPlus: 1,
ItemName.SecondChance: 1,
ItemName.OnceMore: 1,
}
normal_sephiroth_tools = {
ItemName.Guard: 1,
ItemName.ReflectElement: 2,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.GuardBreak: 1,
ItemName.Explosion: 1,
ItemName.DodgeRoll: 3,
ItemName.FinishingPlus: 1,
}
hard_sephiroth_tools = {
ItemName.Guard: 1,
ItemName.ReflectElement: 1,
ItemName.DodgeRoll: 2,
ItemName.FinishingPlus: 1,
}
not_hard_cor_tools_dict = {
ItemName.ReflectElement: 3,
ItemName.Stitch: 1,
ItemName.ChickenLittle: 1,
ItemName.MagnetElement: 2,
ItemName.Explosion: 1,
ItemName.FinishingLeap: 1,
ItemName.ThunderElement: 2,
}
transport_tools_dict = {
ItemName.ReflectElement: 3,
ItemName.Stitch: 1,
ItemName.ChickenLittle: 1,
ItemName.MagnetElement: 2,
ItemName.Explosion: 1,
ItemName.FinishingLeap: 1,
ItemName.ThunderElement: 3,
ItemName.Fantasia: 1,
ItemName.FlareForce: 1,
ItemName.Genie: 1,
}
easy_data_saix = {
ItemName.Guard: 1,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.ThunderElement: 1,
ItemName.BlizzardElement: 1,
ItemName.FlareForce: 1,
ItemName.Fantasia: 1,
ItemName.FireElement: 3,
ItemName.ReflectElement: 3,
ItemName.GuardBreak: 1,
ItemName.Explosion: 1,
ItemName.AerialDodge: 3,
ItemName.Glide: 3,
ItemName.SecondChance: 1,
ItemName.OnceMore: 1
}
normal_data_saix = {
ItemName.Guard: 1,
ItemName.ThunderElement: 1,
ItemName.BlizzardElement: 1,
ItemName.FireElement: 3,
ItemName.ReflectElement: 3,
ItemName.AerialDodge: 3,
ItemName.Glide: 3,
}
hard_data_saix = {
ItemName.Guard: 1,
ItemName.BlizzardElement: 1,
ItemName.ReflectElement: 1,
ItemName.AerialDodge: 3,
ItemName.Glide: 3,
}
easy_data_roxas_tools = {
ItemName.Guard: 1,
ItemName.ReflectElement: 3,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.GuardBreak: 1,
ItemName.Explosion: 1,
ItemName.DodgeRoll: 3,
ItemName.FinishingPlus: 1,
ItemName.SecondChance: 1,
ItemName.OnceMore: 1,
}
normal_data_roxas_tools = {
ItemName.Guard: 1,
ItemName.ReflectElement: 2,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.GuardBreak: 1,
ItemName.Explosion: 1,
ItemName.DodgeRoll: 3,
ItemName.FinishingPlus: 1,
}
hard_data_roxas_tools = {
ItemName.Guard: 1,
ItemName.ReflectElement: 1,
ItemName.DodgeRoll: 2,
ItemName.FinishingPlus: 1,
}
easy_data_axel_tools = {
ItemName.Guard: 1,
ItemName.ReflectElement: 3,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.GuardBreak: 1,
ItemName.Explosion: 1,
ItemName.DodgeRoll: 3,
ItemName.FinishingPlus: 1,
ItemName.SecondChance: 1,
ItemName.OnceMore: 1,
ItemName.BlizzardElement: 3,
}
normal_data_axel_tools = {
ItemName.Guard: 1,
ItemName.ReflectElement: 2,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.GuardBreak: 1,
ItemName.Explosion: 1,
ItemName.DodgeRoll: 3,
ItemName.FinishingPlus: 1,
ItemName.BlizzardElement: 3,
}
hard_data_axel_tools = {
ItemName.Guard: 1,
ItemName.ReflectElement: 1,
ItemName.DodgeRoll: 2,
ItemName.FinishingPlus: 1,
ItemName.BlizzardElement: 2,
}
easy_roxas_tools = {
ItemName.AerialDodge: 1,
ItemName.Glide: 1,
ItemName.LimitForm: 1,
ItemName.ThunderElement: 1,
ItemName.ReflectElement: 2,
ItemName.GuardBreak: 1,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.FinishingPlus: 1,
ItemName.BlizzardElement: 1
}
normal_roxas_tools = {
ItemName.ThunderElement: 1,
ItemName.ReflectElement: 2,
ItemName.GuardBreak: 1,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.FinishingPlus: 1,
ItemName.BlizzardElement: 1
}
easy_xigbar_tools = {
ItemName.HorizontalSlash: 1,
ItemName.FireElement: 2,
ItemName.FinishingPlus: 1,
ItemName.Glide: 2,
ItemName.AerialDodge: 2,
ItemName.QuickRun: 2,
ItemName.ReflectElement: 1,
ItemName.Guard: 1,
}
normal_xigbar_tools = {
ItemName.FireElement: 2,
ItemName.FinishingPlus: 1,
ItemName.Glide: 2,
ItemName.AerialDodge: 2,
ItemName.QuickRun: 2,
ItemName.ReflectElement: 1,
ItemName.Guard: 1
}
easy_luxord_tools = {
ItemName.AerialDodge: 1,
ItemName.Glide: 1,
ItemName.QuickRun: 2,
ItemName.Guard: 1,
ItemName.ReflectElement: 2,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.LimitForm: 1,
}
normal_luxord_tools = {
ItemName.AerialDodge: 1,
ItemName.Glide: 1,
ItemName.QuickRun: 2,
ItemName.Guard: 1,
ItemName.ReflectElement: 2,
}
easy_saix_tools = {
ItemName.AerialDodge: 1,
ItemName.Glide: 1,
ItemName.QuickRun: 2,
ItemName.Guard: 1,
ItemName.ReflectElement: 2,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.LimitForm: 1,
}
normal_saix_tools = {
ItemName.AerialDodge: 1,
ItemName.Glide: 1,
ItemName.QuickRun: 2,
ItemName.Guard: 1,
ItemName.ReflectElement: 2,
}
easy_xemnas_tools = {
ItemName.AerialDodge: 1,
ItemName.Glide: 1,
ItemName.QuickRun: 2,
ItemName.Guard: 1,
ItemName.ReflectElement: 2,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.LimitForm: 1,
}
normal_xemnas_tools = {
ItemName.AerialDodge: 1,
ItemName.Glide: 1,
ItemName.QuickRun: 2,
ItemName.Guard: 1,
ItemName.ReflectElement: 2,
}
easy_data_xemnas = {
ItemName.ComboMaster: 1,
ItemName.Slapshot: 1,
ItemName.ReflectElement: 3,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.FinishingPlus: 1,
ItemName.Guard: 1,
ItemName.TrinityLimit: 1,
ItemName.SecondChance: 1,
ItemName.OnceMore: 1,
ItemName.LimitForm: 1,
}
normal_data_xemnas = {
ItemName.ComboMaster: 1,
ItemName.Slapshot: 1,
ItemName.ReflectElement: 3,
ItemName.SlideDash: 1,
ItemName.FlashStep: 1,
ItemName.FinishingPlus: 1,
ItemName.Guard: 1,
ItemName.LimitForm: 1,
}
hard_data_xemnas = {
ItemName.ComboMaster: 1,
ItemName.Slapshot: 1,
ItemName.ReflectElement: 2,
ItemName.FinishingPlus: 1,
ItemName.Guard: 1,
ItemName.LimitForm: 1,
}
final_leveling_access = {
LocationName.MemorysSkyscaperMythrilCrystal,
LocationName.GrimReaper2,
LocationName.Xaldin,
LocationName.StormRider,
LocationName.SunsetTerraceAbilityRing
}
multi_form_region_access = {
ItemName.CastleKey,
ItemName.BattlefieldsofWar,
ItemName.SwordoftheAncestor,
ItemName.BeastsClaw,
ItemName.BoneFist,
ItemName.SkillandCrossbones,
ItemName.Scimitar,
ItemName.MembershipCard,
ItemName.IceCream,
ItemName.WaytotheDawn,
ItemName.IdentityDisk,
}
limit_form_region_access = {
ItemName.CastleKey,
ItemName.BattlefieldsofWar,
ItemName.SwordoftheAncestor,
ItemName.BeastsClaw,
ItemName.BoneFist,
ItemName.SkillandCrossbones,
ItemName.Scimitar,
ItemName.MembershipCard,
ItemName.IceCream,
ItemName.WaytotheDawn,
ItemName.IdentityDisk,
ItemName.NamineSketches
}

View File

@ -12,8 +12,7 @@ SecretAnsemsReport10 = "Secret Ansem's Report 10"
SecretAnsemsReport11 = "Secret Ansem's Report 11"
SecretAnsemsReport12 = "Secret Ansem's Report 12"
SecretAnsemsReport13 = "Secret Ansem's Report 13"
# progression
# proofs, visit unlocks and forms
ProofofConnection = "Proof of Connection"
ProofofNonexistence = "Proof of Nonexistence"
ProofofPeace = "Proof of Peace"
@ -32,51 +31,33 @@ IdentityDisk = "Identity Disk"
NamineSketches = "Namine Sketches"
CastleKey = "Disney Castle Key"
TornPages = "Torn Page"
TornPages = "Torn Page"
TornPages = "Torn Page"
TornPages = "Torn Page"
TornPages = "Torn Page"
ValorForm = "Valor Form"
WisdomForm = "Wisdom Form"
LimitForm = "Limit Form"
MasterForm = "Master Form"
FinalForm = "Final Form"
AntiForm = "Anti Form"
# magic and summons
FireElement = "Fire Element"
BlizzardElement = "Blizzard Element"
ThunderElement = "Thunder Element"
CureElement = "Cure Element"
MagnetElement = "Magnet Element"
ReflectElement = "Reflect Element"
FireElement = "Fire Element"
BlizzardElement = "Blizzard Element"
ThunderElement = "Thunder Element"
CureElement = "Cure Element"
MagnetElement = "Magnet Element"
ReflectElement = "Reflect Element"
Genie = "Genie"
PeterPan = "Peter Pan"
Stitch = "Stitch"
ChickenLittle = "Chicken Little"
#movement
# movement
HighJump = "High Jump"
QuickRun = "Quick Run"
AerialDodge = "Aerial Dodge"
Glide = "Glide"
DodgeRoll = "Dodge Roll"
#keyblades
# keyblades
Oathkeeper = "Oathkeeper"
Oblivion = "Oblivion"
StarSeeker = "Star Seeker"
@ -109,7 +90,6 @@ MagesStaff = "Mages Staff"
MeteorStaff = "Meteor Staff"
CometStaff = "Comet Staff"
Centurion2 = "Centurion+"
MeteorStaff = "Meteor Staff"
NobodyLance = "Nobody Lance"
PreciousMushroom = "Precious Mushroom"
PreciousMushroom2 = "Precious Mushroom+"
@ -203,7 +183,7 @@ Ribbon = "Ribbon"
GrandRibbon = "Grand Ribbon"
# usefull and stat incre
MickyMunnyPouch = "Mickey Munny Pouch"
MickeyMunnyPouch = "Mickey Munny Pouch"
OletteMunnyPouch = "Olette Munny Pouch"
HadesCupTrophy = "Hades Cup Trophy"
UnknownDisk = "Unknown Disk"
@ -253,7 +233,6 @@ LightDarkness = "Light & Darkness"
MagicLock = "Magic Lock-On"
LeafBracer = "Leaf Bracer"
CombinationBoost = "Combination Boost"
DamageDrive = "Damage Drive"
OnceMore = "Once More"
SecondChance = "Second Chance"
@ -313,10 +292,6 @@ DonaldLuckyLucky = "Donald Lucky Lucky"
DonaldFireBoost = "Donald Fire Boost"
DonaldBlizzardBoost = "Donald Blizzard Boost"
DonaldThunderBoost = "Donald Thunder Boost"
DonaldFireBoost = "Donald Fire Boost"
DonaldBlizzardBoost = "Donald Blizzard Boost"
DonaldThunderBoost = "Donald Thunder Boost"
DonaldMPRage = "Donald MP Rage"
DonaldMPHastera = "Donald MP Hastera"
DonaldAutoLimit = "Donald Auto Limit"
DonaldHyperHealing = "Donald Hyper Healing"
@ -324,14 +299,7 @@ DonaldAutoHealing = "Donald Auto Healing"
DonaldMPHastega = "Donald MP Hastega"
DonaldItemBoost = "Donald Item Boost"
DonaldDamageControl = "Donald Damage Control"
DonaldHyperHealing = "Donald Hyper Healing"
DonaldMPRage = "Donald MP Rage"
DonaldMPHaste = "Donald MP Haste"
DonaldMPHastera = "Donald MP Hastera"
DonaldMPHastega = "Donald MP Hastega"
DonaldMPHaste = "Donald MP Haste"
DonaldDamageControl = "Donald Damage Control"
DonaldMPHastera = "Donald MP Hastera"
DonaldDraw = "Donald Draw"
# goofy abili
@ -353,27 +321,18 @@ GoofyOnceMore = "Goofy Once More"
GoofyAutoChange = "Goofy Auto Change"
GoofyHyperHealing = "Goofy Hyper Healing"
GoofyAutoHealing = "Goofy Auto Healing"
GoofyDefender = "Goofy Defender"
GoofyHyperHealing = "Goofy Hyper Healing"
GoofyMPHaste = "Goofy MP Haste"
GoofyMPHastera = "Goofy MP Hastera"
GoofyMPRage = "Goofy MP Rage"
GoofyMPHastega = "Goofy MP Hastega"
GoofyItemBoost = "Goofy Item Boost"
GoofyDamageControl = "Goofy Damage Control"
GoofyProtect = "Goofy Protect"
GoofyProtera = "Goofy Protera"
GoofyProtega = "Goofy Protega"
GoofyDamageControl = "Goofy Damage Control"
GoofyProtect = "Goofy Protect"
GoofyProtera = "Goofy Protera"
GoofyProtega = "Goofy Protega"
Victory = "Victory"
LuckyEmblem = "Lucky Emblem"
Bounty="Bounty"
Bounty = "Bounty"
UniversalKey="Universal Key"
# UniversalKey = "Universal Key"
# Keyblade Slots
FAKESlot = "FAKE (Slot)"
DetectionSaberSlot = "Detection Saber (Slot)"
@ -402,3 +361,73 @@ FatalCrestSlot = "Fatal Crest (Slot)"
FenrirSlot = "Fenrir (Slot)"
UltimaWeaponSlot = "Ultima Weapon (Slot)"
WinnersProofSlot = "Winner's Proof (Slot)"
# events
HostileProgramEvent = "Hostile Program Event"
McpEvent = "Master Control Program Event"
ASLarxeneEvent = "AS Larxene Event"
DataLarxeneEvent = "Data Larxene Event"
BarbosaEvent = "Barbosa Event"
GrimReaper1Event = "Grim Reaper 1 Event"
GrimReaper2Event = "Grim Reaper 2 Event"
DataLuxordEvent = "Data Luxord Event"
DataAxelEvent = "Data Axel Event"
CerberusEvent = "Cerberus Event"
OlympusPeteEvent = "Olympus Pete Event"
HydraEvent = "Hydra Event"
OcPainAndPanicCupEvent = "Pain and Panic Cup Event"
OcCerberusCupEvent = "Cerberus Cup Event"
HadesEvent = "Hades Event"
ASZexionEvent = "AS Zexion Event"
DataZexionEvent = "Data Zexion Event"
Oc2TitanCupEvent = "Titan Cup Event"
Oc2GofCupEvent = "Goddess of Fate Cup Event"
Oc2CupsEvent = "Olympus Coliseum Cups Event"
HadesCupEvents = "Olympus Coliseum Hade's Paradox Event"
PrisonKeeperEvent = "Prison Keeper Event"
OogieBoogieEvent = "Oogie Boogie Event"
ExperimentEvent = "The Experiment Event"
ASVexenEvent = "AS Vexen Event"
DataVexenEvent = "Data Vexen Event"
ShanYuEvent = "Shan Yu Event"
AnsemRikuEvent = "Ansem Riku Event"
StormRiderEvent = "Storm Rider Event"
DataXigbarEvent = "Data Xigbar Event"
RoxasEvent = "Roxas Event"
XigbarEvent = "Xigbar Event"
LuxordEvent = "Luxord Event"
SaixEvent = "Saix Event"
XemnasEvent = "Xemnas Event"
ArmoredXemnasEvent = "Armored Xemnas Event"
ArmoredXemnas2Event = "Armored Xemnas 2 Event"
FinalXemnasEvent = "Final Xemnas Event"
DataXemnasEvent = "Data Xemnas Event"
ThresholderEvent = "Thresholder Event"
BeastEvent = "Beast Event"
DarkThornEvent = "Dark Thorn Event"
XaldinEvent = "Xaldin Event"
DataXaldinEvent = "Data Xaldin Event"
TwinLordsEvent = "Twin Lords Event"
GenieJafarEvent = "Genie Jafar Event"
ASLexaeusEvent = "AS Lexaeus Event"
DataLexaeusEvent = "Data Lexaeus Event"
ScarEvent = "Scar Event"
GroundShakerEvent = "Groundshaker Event"
DataSaixEvent = "Data Saix Event"
HBDemyxEvent = "Hollow Bastion Demyx Event"
ThousandHeartlessEvent = "Thousand Heartless Event"
Mushroom13Event = "Mushroom 13 Event"
SephiEvent = "Sephiroth Event"
DataDemyxEvent = "Data Demyx Event"
CorFirstFightEvent = "Cavern of Rememberance:Fight 1 Event"
CorSecondFightEvent = "Cavern of Rememberance:Fight 2 Event"
TransportEvent = "Transport to Rememberance Event"
OldPeteEvent = "Old Pete Event"
FuturePeteEvent = "Future Pete Event"
ASMarluxiaEvent = "AS Marluxia Event"
DataMarluxiaEvent = "Data Marluxia Event"
TerraEvent = "Terra Event"
TwilightThornEvent = "Twilight Thorn Event"
Axel1Event = "Axel 1 Event"
Axel2Event = "Axel 2 Event"
DataRoxasEvent = "Data Roxas Event"

View File

@ -27,7 +27,7 @@ ThroneRoomOgreShield = "(LoD2) Throne Room Ogre Shield"
ThroneRoomMythrilCrystal = "(LoD2) Throne Room Mythril Crystal"
ThroneRoomOrichalcum = "(LoD2) Throne Room Orichalcum"
StormRider = "(LoD2) Storm Rider Bonus: Sora Slot 1"
XigbarDataDefenseBoost = "Data Xigbar"
XigbarDataDefenseBoost = "(Post LoD2: Summit) Data Xigbar"
AgrabahMap = "(AG) Agrabah Map"
AgrabahDarkShard = "(AG) Agrabah Dark Shard"
@ -62,9 +62,10 @@ RuinedChamberTornPages = "(AG2) Ruined Chamber Torn Pages
RuinedChamberRuinsMap = "(AG2) Ruined Chamber Ruins Map"
GenieJafar = "(AG2) Genie Jafar"
WishingLamp = "(AG2) Wishing Lamp"
LexaeusBonus = "Lexaeus Bonus: Sora Slot 1"
LexaeusASStrengthBeyondStrength = "AS Lexaeus"
LexaeusDataLostIllusion = "Data Lexaeus"
LexaeusBonus = "(Post AG2: Peddler's Shop) Lexaeus Bonus: Sora Slot 1"
LexaeusASStrengthBeyondStrength = "(Post AG2: Peddler's Shop) AS Lexaeus"
LexaeusDataLostIllusion = "(Post AG2: Peddler's Shop) Data Lexaeus"
DCCourtyardMythrilShard = "(DC) Courtyard Mythril Shard"
DCCourtyardStarRecipe = "(DC) Courtyard Star Recipe"
DCCourtyardAPBoost = "(DC) Courtyard AP Boost"
@ -89,12 +90,15 @@ FuturePete = "(TR) Future Pete Bonus: Sora Sl
FuturePeteGetBonus = "(TR) Future Pete Bonus: Sora Slot 2"
Monochrome = "(TR) Monochrome"
WisdomForm = "(TR) Wisdom Form"
MarluxiaGetBonus = "Marluxia Bonus: Sora Slot 1"
MarluxiaASEternalBlossom = "AS Marluxia"
MarluxiaDataLostIllusion = "Data Marluxia"
LingeringWillBonus = "Lingering Will Bonus: Sora Slot 1"
LingeringWillProofofConnection = "Lingering Will Proof of Connection"
LingeringWillManifestIllusion = "Lingering Will Manifest Illusion"
MarluxiaGetBonus = "(Post TR:Hall of the Cornerstone) Marluxia Bonus: Sora Slot 1"
MarluxiaASEternalBlossom = "(Post TR:Hall of the Cornerstone) AS Marluxia"
MarluxiaDataLostIllusion = "(Post TR:Hall of the Cornerstone) Data Marluxia"
LingeringWillBonus = "(Post TR:Hall of the Cornerstone) Lingering Will Bonus: Sora Slot 1"
LingeringWillProofofConnection = "(Post TR:Hall of the Cornerstone) Lingering Will Proof of Connection"
LingeringWillManifestIllusion = "(Post TR:Hall of the Cornerstone) Lingering Will Manifest Illusion"
PoohsHouse100AcreWoodMap = "(100Acre) Pooh's House 100 Acre Wood Map"
PoohsHouseAPBoost = "(100Acre) Pooh's House AP Boost"
PoohsHouseMythrilStone = "(100Acre) Pooh's House Mythril Stone"
@ -119,6 +123,7 @@ StarryHillCosmicRing = "(100Acre) Starry Hill Cosmic Ri
StarryHillStyleRecipe = "(100Acre) Starry Hill Style Recipe"
StarryHillCureElement = "(100Acre) Starry Hill Cure Element"
StarryHillOrichalcumPlus = "(100Acre) Starry Hill Orichalcum+"
PassageMythrilShard = "(OC) Passage Mythril Shard"
PassageMythrilStone = "(OC) Passage Mythril Stone"
PassageEther = "(OC) Passage Ether"
@ -162,9 +167,9 @@ SkillfulRingTitanCup = "Skillful Ring Titan Cup"
FatalCrestGoddessofFateCup = "Fatal Crest Goddess of Fate Cup"
OrichalcumPlusGoddessofFateCup = "Orichalcum+ Goddess of Fate Cup"
HadesCupTrophyParadoxCups = "Hades Cup Trophy Paradox Cups"
ZexionBonus = "Zexion Bonus: Sora Slot 1"
ZexionASBookofShadows = "AS Zexion"
ZexionDataLostIllusion = "Data Zexion"
ZexionBonus = "(Post OC2: Cave of the Dead Inner Chamber) Zexion Bonus: Sora Slot 1"
ZexionASBookofShadows = "(Post OC2: Cave of the Dead Inner Chamber) AS Zexion"
ZexionDataLostIllusion = "(Post OC2: Cave of the Dead Inner Chamber) Data Zexion"
BCCourtyardAPBoost = "(BC) Courtyard AP Boost"
@ -198,7 +203,7 @@ CastleWallsMap = "(BC2) Castle Walls Map"
Xaldin = "(BC2) Xaldin Bonus: Sora Slot 1"
XaldinGetBonus = "(BC2) Xaldin Bonus: Sora Slot 2"
SecretAnsemReport4 = "(BC2) Secret Ansem Report 4 (Xaldin)"
XaldinDataDefenseBoost = "Data Xaldin"
XaldinDataDefenseBoost = "(Post BC2: Ballroom) Data Xaldin"
@ -223,9 +228,9 @@ CentralComputerCoreCosmicArts = "(SP2) Central Computer Core Cos
CentralComputerCoreMap = "(SP2) Central Computer Core Map"
MCP = "(SP2) MCP Bonus: Sora Slot 1"
MCPGetBonus = "(SP2) MCP Bonus: Sora Slot 2"
LarxeneBonus = "Larxene Bonus: Sora Slot 1"
LarxeneASCloakedThunder = "AS Larxene"
LarxeneDataLostIllusion = "Data Larxene"
LarxeneBonus = "(Post SP2: Central Computer Core) Larxene Bonus: Sora Slot 1"
LarxeneASCloakedThunder = "(Post SP2: Central Computer Core) AS Larxene"
LarxeneDataLostIllusion = "(Post SP2: Central Computer Core) Data Larxene"
GraveyardMythrilShard = "(HT) Graveyard Mythril Shard"
GraveyardSerenityGem = "(HT) Graveyard Serenity Gem"
@ -249,9 +254,9 @@ Present = "(HT2) Present"
DecoyPresents = "(HT2) Decoy Presents"
Experiment = "(HT2) Experiment Bonus: Sora Slot 1"
DecisivePumpkin = "(HT2) Decisive Pumpkin"
VexenBonus = "Vexen Bonus: Sora Slot 1"
VexenASRoadtoDiscovery = "AS Vexen"
VexenDataLostIllusion = "Data Vexen"
VexenBonus = "(Post HT2: Yuletide Hill) Vexen Bonus: Sora Slot 1"
VexenASRoadtoDiscovery = "(Post HT2: Yuletide Hill) AS Vexen"
VexenDataLostIllusion = "(Post HT2: Yuletide Hill) Data Vexen"
RampartNavalMap = "(PR) Rampart Naval Map"
RampartMythrilStone = "(PR) Rampart Mythril Stone"
@ -286,7 +291,7 @@ SeadriftRowShipGraveyardMap = "(PR2) Seadrift Row Ship Graveya
GrimReaper2 = "(PR2) Grim Reaper 2 Bonus: Sora Slot 1"
SecretAnsemReport6 = "(PR2) Secret Ansem Report 6 (Grim Reaper 2)"
LuxordDataAPBoost = "Data Luxord"
LuxordDataAPBoost = "(Post PR2: Treasure Heap) Data Luxord"
MarketplaceMap = "(HB) Marketplace Map"
BoroughDriveRecovery = "(HB) Borough Drive Recovery"
@ -329,7 +334,7 @@ SephirothBonus = "Sephiroth Bonus: Sora Slot 1"
SephirothFenrir = "Sephiroth Fenrir"
WinnersProof = "(HB2) Winner's Proof"
ProofofPeace = "(HB2) Proof of Peace"
DemyxDataAPBoost = "Data Demyx"
DemyxDataAPBoost = "(Post HB2: Restoration Site) Data Demyx"
CoRDepthsAPBoost = "(CoR) Depths AP Boost"
CoRDepthsPowerCrystal = "(CoR) Depths Power Crystal"
@ -386,7 +391,7 @@ ScarFireElement = "(PL) Scar Fire Element"
Hyenas2 = "(PL2) Hyenas 2 Bonus: Sora Slot 1"
Groundshaker = "(PL2) Groundshaker Bonus: Sora Slot 1"
GroundshakerGetBonus = "(PL2) Groundshaker Bonus: Sora Slot 2"
SaixDataDefenseBoost = "Data Saix"
SaixDataDefenseBoost = "(Post PL2: Peak) Data Saix"
TwilightTownMap = "(STT) Twilight Town Map"
MunnyPouchOlette = "(STT) Munny Pouch Olette"
@ -415,7 +420,7 @@ MansionMap = "(STT) Mansion Map"
MansionLibraryHiPotion = "(STT) Mansion Library Hi-Potion"
Axel2 = "(STT) Axel 2"
MansionBasementCorridorHiPotion = "(STT) Mansion Basement Corridor Hi-Potion"
RoxasDataMagicBoost = "Data Roxas"
RoxasDataMagicBoost = "(Post STT: Mansion Pod Room) Data Roxas"
OldMansionPotion = "(TT) Old Mansion Potion"
OldMansionMythrilShard = "(TT) Old Mansion Mythril Shard"
@ -468,46 +473,46 @@ BeamSecretAnsemReport10 = "(TT3) Beam Secret Ansem Report
MansionBasementCorridorUltimateRecipe = "(TT3) Mansion Basement Corridor Ultimate Recipe"
BetwixtandBetween = "(TT3) Betwixt and Between"
BetwixtandBetweenBondofFlame = "(TT3) Betwixt and Between Bond of Flame"
AxelDataMagicBoost = "Data Axel"
AxelDataMagicBoost = "(Post TT3: Betwixt and Between) Data Axel"
FragmentCrossingMythrilStone = "(TWTNW) Fragment Crossing Mythril Stone"
FragmentCrossingMythrilCrystal = "(TWTNW) Fragment Crossing Mythril Crystal"
FragmentCrossingAPBoost = "(TWTNW) Fragment Crossing AP Boost"
FragmentCrossingOrichalcum = "(TWTNW) Fragment Crossing Orichalcum"
Roxas = "(TWTNW) Roxas Bonus: Sora Slot 1"
RoxasGetBonus = "(TWTNW) Roxas Bonus: Sora Slot 2"
RoxasSecretAnsemReport8 = "(TWTNW) Roxas Secret Ansem Report 8"
TwoBecomeOne = "(TWTNW) Two Become One"
MemorysSkyscaperMythrilCrystal = "(TWTNW) Memory's Skyscaper Mythril Crystal"
MemorysSkyscaperAPBoost = "(TWTNW) Memory's Skyscaper AP Boost"
MemorysSkyscaperMythrilStone = "(TWTNW) Memory's Skyscaper Mythril Stone"
TheBrinkofDespairDarkCityMap = "(TWTNW) The Brink of Despair Dark City Map"
TheBrinkofDespairOrichalcumPlus = "(TWTNW) The Brink of Despair Orichalcum+"
NothingsCallMythrilGem = "(TWTNW) Nothing's Call Mythril Gem"
NothingsCallOrichalcum = "(TWTNW) Nothing's Call Orichalcum"
TwilightsViewCosmicBelt = "(TWTNW) Twilight's View Cosmic Belt"
XigbarBonus = "(TWTNW) Xigbar Bonus: Sora Slot 1"
XigbarSecretAnsemReport3 = "(TWTNW) Xigbar Secret Ansem Report 3"
NaughtsSkywayMythrilGem = "(TWTNW) Naught's Skyway Mythril Gem"
NaughtsSkywayOrichalcum = "(TWTNW) Naught's Skyway Orichalcum"
NaughtsSkywayMythrilCrystal = "(TWTNW) Naught's Skyway Mythril Crystal"
Oblivion = "(TWTNW) Oblivion"
CastleThatNeverWasMap = "(TWTNW) Castle That Never Was Map"
Luxord = "(TWTNW) Luxord"
LuxordGetBonus = "(TWTNW) Luxord Bonus: Sora Slot 1"
LuxordSecretAnsemReport9 = "(TWTNW) Luxord Secret Ansem Report 9"
SaixBonus = "(TWTNW) Saix Bonus: Sora Slot 1"
SaixSecretAnsemReport12 = "(TWTNW) Saix Secret Ansem Report 12"
PreXemnas1SecretAnsemReport11 = "(TWTNW) Secret Ansem Report 11 (Pre-Xemnas 1)"
RuinandCreationsPassageMythrilStone = "(TWTNW) Ruin and Creation's Passage Mythril Stone"
RuinandCreationsPassageAPBoost = "(TWTNW) Ruin and Creation's Passage AP Boost"
RuinandCreationsPassageMythrilCrystal = "(TWTNW) Ruin and Creation's Passage Mythril Crystal"
RuinandCreationsPassageOrichalcum = "(TWTNW) Ruin and Creation's Passage Orichalcum"
Xemnas1 = "(TWTNW) Xemnas 1 Bonus: Sora Slot 1"
Xemnas1GetBonus = "(TWTNW) Xemnas 1 Bonus: Sora Slot 2"
Xemnas1SecretAnsemReport13 = "(TWTNW) Xemnas 1 Secret Ansem Report 13"
Roxas = "(TWTNW2) Roxas Bonus: Sora Slot 1"
RoxasGetBonus = "(TWTNW2) Roxas Bonus: Sora Slot 2"
RoxasSecretAnsemReport8 = "(TWTNW2) Roxas Secret Ansem Report 8"
TwoBecomeOne = "(TWTNW2) Two Become One"
MemorysSkyscaperMythrilCrystal = "(TWTNW2) Memory's Skyscaper Mythril Crystal"
MemorysSkyscaperAPBoost = "(TWTNW2) Memory's Skyscaper AP Boost"
MemorysSkyscaperMythrilStone = "(TWTNW2) Memory's Skyscaper Mythril Stone"
TheBrinkofDespairDarkCityMap = "(TWTNW2) The Brink of Despair Dark City Map"
TheBrinkofDespairOrichalcumPlus = "(TWTNW2) The Brink of Despair Orichalcum+"
NothingsCallMythrilGem = "(TWTNW2) Nothing's Call Mythril Gem"
NothingsCallOrichalcum = "(TWTNW2) Nothing's Call Orichalcum"
TwilightsViewCosmicBelt = "(TWTNW2) Twilight's View Cosmic Belt"
XigbarBonus = "(TWTNW2) Xigbar Bonus: Sora Slot 1"
XigbarSecretAnsemReport3 = "(TWTNW2) Xigbar Secret Ansem Report 3"
NaughtsSkywayMythrilGem = "(TWTNW2) Naught's Skyway Mythril Gem"
NaughtsSkywayOrichalcum = "(TWTNW2) Naught's Skyway Orichalcum"
NaughtsSkywayMythrilCrystal = "(TWTNW2) Naught's Skyway Mythril Crystal"
Oblivion = "(TWTNW2) Oblivion"
CastleThatNeverWasMap = "(TWTNW2) Castle That Never Was Map"
Luxord = "(TWTNW2) Luxord Bonus: Sora Slot 2"
LuxordGetBonus = "(TWTNW2) Luxord Bonus: Sora Slot 1"
LuxordSecretAnsemReport9 = "(TWTNW2) Luxord Secret Ansem Report 9"
SaixBonus = "(TWTNW2) Saix Bonus: Sora Slot 1"
SaixSecretAnsemReport12 = "(TWTNW2) Saix Secret Ansem Report 12"
PreXemnas1SecretAnsemReport11 = "(TWTNW3) Secret Ansem Report 11 (Pre-Xemnas 1)"
RuinandCreationsPassageMythrilStone = "(TWTNW3) Ruin and Creation's Passage Mythril Stone"
RuinandCreationsPassageAPBoost = "(TWTNW3) Ruin and Creation's Passage AP Boost"
RuinandCreationsPassageMythrilCrystal = "(TWTNW3) Ruin and Creation's Passage Mythril Crystal"
RuinandCreationsPassageOrichalcum = "(TWTNW3) Ruin and Creation's Passage Orichalcum"
Xemnas1 = "(TWTNW3) Xemnas 1 Bonus: Sora Slot 1"
Xemnas1GetBonus = "(TWTNW3) Xemnas 1 Bonus: Sora Slot 2"
Xemnas1SecretAnsemReport13 = "(TWTNW3) Xemnas 1 Secret Ansem Report 13"
FinalXemnas = "Final Xemnas"
XemnasDataPowerBoost = "Data Xemnas"
XemnasDataPowerBoost = "(Post TWTNW3: The Altar of Naught) Data Xemnas"
Lvl1 ="Level 01"
Lvl2 ="Level 02"
Lvl3 ="Level 03"
@ -605,7 +610,7 @@ Lvl94 ="Level 94"
Lvl95 ="Level 95"
Lvl96 ="Level 96"
Lvl97 ="Level 97"
Lvl98 ="Level 98"
Lvl98 ="Level 98"
Lvl99 ="Level 99"
Valorlvl1 ="Valor level 1"
Valorlvl2 ="Valor level 2"
@ -643,13 +648,28 @@ Finallvl5 ="Final level 5"
Finallvl6 ="Final level 6"
Finallvl7 ="Final level 7"
Summonlvl2="Summon level 2"
Summonlvl3="Summon level 3"
Summonlvl4="Summon level 4"
Summonlvl5="Summon level 5"
Summonlvl6="Summon level 6"
Summonlvl7="Summon level 7"
GardenofAssemblageMap ="Garden of Assemblage Map"
GoALostIllusion ="GoA Lost Illusion"
ProofofNonexistence ="Proof of Nonexistence Location"
test= "test"
UnderseaKingdomMap ="(AT) Undersea Kingdom Map"
MysteriousAbyss ="(AT) Mysterious Abyss"
MusicalBlizzardElement ="(AT) Musical Blizzard Element"
MusicalOrichalcumPlus ="(AT) Musical Orichalcum+"
DonaldStarting1 ="Donald Starting Item 1"
DonaldStarting2 ="Donald Starting Item 2"
GoofyStarting1 ="Goofy Starting Item 1"
GoofyStarting2 ="Goofy Starting Item 2"
# TODO: remove in 4.3
Crit_1 ="Critical Starting Ability 1"
Crit_2 ="Critical Starting Ability 2"
Crit_3 ="Critical Starting Ability 3"
@ -657,14 +677,9 @@ Crit_4 ="Critical Starting Ability 4"
Crit_5 ="Critical Starting Ability 5"
Crit_6 ="Critical Starting Ability 6"
Crit_7 ="Critical Starting Ability 7"
DonaldStarting1 ="Donald Starting Item 1"
DonaldStarting2 ="Donald Starting Item 2"
GoofyStarting1 ="Goofy Starting Item 1"
GoofyStarting2 ="Goofy Starting Item 2"
DonaldScreens ="(SP) Screens Bonus: Donald Slot 1"
DonaldDemyxHBGetBonus ="(HB) Demyx Bonus: Donald Slot 1"
DonaldDemyxHBGetBonus ="(HB2) Demyx Bonus: Donald Slot 1"
DonaldDemyxOC ="(OC) Demyx Bonus: Donald Slot 1"
DonaldBoatPete ="(TR) Boat Pete Bonus: Donald Slot 1"
DonaldBoatPeteGetBonus ="(TR) Boat Pete Bonus: Donald Slot 2"
@ -694,7 +709,7 @@ GoofyStormRider ="(LoD2) Storm Rider Bonus: Goofy S
GoofyBeast ="(BC) Beast Bonus: Goofy Slot 1"
GoofyInterceptorBarrels ="(PR) Interceptor Barrels Bonus: Goofy Slot 1"
GoofyTreasureRoom ="(AG) Treasure Room Heartless Bonus: Goofy Slot 1"
GoofyZexion ="Zexion Bonus: Goofy Slot 1"
GoofyZexion ="(Post OC2: Cave of the Dead Inner Chamber) Zexion Bonus: Goofy Slot 1"
AdamantShield ="Adamant Shield Slot"
@ -760,4 +775,86 @@ UltimaWeaponSlot ="Ultima Weapon Slot"
WinnersProofSlot ="Winner's Proof Slot"
PurebloodSlot ="Pureblood Slot"
#Final_Region ="Final Form"
Mushroom13_1 = "(Post TWTNW3: Memory's Skyscraper) Mushroom XIII No. 1"
Mushroom13_2 = "(Post HT2: Christmas Tree Plaza) Mushroom XIII No. 2"
Mushroom13_3 = "(Post BC2: Bridge) Mushroom XIII No. 3"
Mushroom13_4 = "(Post LOD2: Palace Gates) Mushroom XIII No. 4"
Mushroom13_5 = "(Post AG2: Treasure Room) Mushroom XIII No. 5"
Mushroom13_6 = "(Post OC2: Atrium) Mushroom XIII No. 6"
Mushroom13_7 = "(Post TT3: Tunnel way) Mushroom XIII No. 7"
Mushroom13_8 = "(Post TT3: Tower) Mushroom XIII No. 8"
Mushroom13_9 = "(Post HB2: Castle Gates) Mushroom XIII No. 9"
Mushroom13_10 = "(Post PR2: Moonlight Nook) Mushroom XIII No. 10"
Mushroom13_11 = "(Post TR: Waterway) Mushroom XIII No. 11"
Mushroom13_12 = "(Post TT3: Old Mansion) Mushroom XIII No. 12"
HostileProgramEventLocation = "Hostile Program Event Location"
McpEventLocation = "Master Control Program Event Location"
ASLarxeneEventLocation = "AS Larxene Event Location"
DataLarxeneEventLocation = "Data Larxene Event Location"
BarbosaEventLocation = "Barbosa Event Location"
GrimReaper1EventLocation = "Grim Reaper 1 Event Location"
GrimReaper2EventLocation = "Grim Reaper 2 Event Location"
DataLuxordEventLocation = "Data Luxord Event Location"
DataAxelEventLocation = "Data Axel Event Location"
CerberusEventLocation = "Cerberus Event Location"
OlympusPeteEventLocation = "Olympus Pete Event Location"
HydraEventLocation = "Hydra Event Location"
OcPainAndPanicCupEventLocation = "Pain and Panic Cup Event Location"
OcCerberusCupEventLocation = "Cerberus Cup Event Location"
HadesEventLocation = "Hades Event Location"
ASZexionEventLocation = "AS Zexion Event Location"
DataZexionEventLocation = "Data Zexion Event Location"
Oc2TitanCupEventLocation = "Titan Cup Event Location"
Oc2GofCupEventLocation = "Goddess of Fate Cup Event Location"
Oc2CupsEventLocation = "Olympus Coliseum Cups Event Location"
HadesCupEventLocations = "Olympus Coliseum Hade's Paradox Event Location"
PrisonKeeperEventLocation = "Prison Keeper Event Location"
OogieBoogieEventLocation = "Oogie Boogie Event Location"
ExperimentEventLocation = "The Experiment Event Location"
ASVexenEventLocation = "AS Vexen Event Location"
DataVexenEventLocation = "Data Vexen Event Location"
ShanYuEventLocation = "Shan Yu Event Location"
AnsemRikuEventLocation = "Ansem Riku Event Location"
StormRiderEventLocation = "Storm Rider Event Location"
DataXigbarEventLocation = "Data Xigbar Event Location"
RoxasEventLocation = "Roxas Event Location"
XigbarEventLocation = "Xigbar Event Location"
LuxordEventLocation = "Luxord Event Location"
SaixEventLocation = "Saix Event Location"
XemnasEventLocation = "Xemnas Event Location"
ArmoredXemnasEventLocation = "Armored Xemnas Event Location"
ArmoredXemnas2EventLocation = "Armored Xemnas 2 Event Location"
FinalXemnasEventLocation = "Final Xemnas Event Location"
DataXemnasEventLocation = "Data Xemnas Event Location"
ThresholderEventLocation = "Thresholder Event Location"
BeastEventLocation = "Beast Event Location"
DarkThornEventLocation = "Dark Thorn Event Location"
XaldinEventLocation = "Xaldin Event Location"
DataXaldinEventLocation = "Data Xaldin Event Location"
TwinLordsEventLocation = "Twin Lords Event Location"
GenieJafarEventLocation = "Genie Jafar Event Location"
ASLexaeusEventLocation = "AS Lexaeus Event Location"
DataLexaeusEventLocation = "Data Lexaeus Event Location"
ScarEventLocation = "Scar Event Location"
GroundShakerEventLocation = "Groundshaker Event Location"
DataSaixEventLocation = "Data Saix Event Location"
HBDemyxEventLocation = "Hollow Bastion Demyx Event Location"
ThousandHeartlessEventLocation = "Thousand Heartless Event Location"
Mushroom13EventLocation = "Mushroom 13 Event Location"
SephiEventLocation = "Sephiroth Event Location"
DataDemyxEventLocation = "Data Demyx Event Location"
CorFirstFightEventLocation = "Cavern of Rememberance:Fight 1 Event Location"
CorSecondFightEventLocation = "Cavern of Rememberance:Fight 2 Event Location"
TransportEventLocation = "Transport to Rememberance Event Location"
OldPeteEventLocation = "Old Pete Event Location"
FuturePeteEventLocation = "Future Pete Event Location"
ASMarluxiaEventLocation = "AS Marluxia Event Location"
DataMarluxiaEventLocation = "Data Marluxia Event Location"
TerraEventLocation = "Terra Event Location"
TwilightThornEventLocation = "Twilight Thorn Event Location"
Axel1EventLocation = "Axel 1 Event Location"
Axel2EventLocation = "Axel 2 Event Location"
DataRoxasEventLocation = "Data Roxas Event Location"

View File

@ -1,90 +1,156 @@
LoD_Region ="Land of Dragons"
LoD2_Region ="Land of Dragons 2"
Ha1 = "Pooh's House"
Ha2 = "Piglet's House"
Ha3 = "Rabbit's House"
Ha4 = "Roo's House"
Ha5 = "Spooky Cave"
Ha6 = "Starry Hill"
Ag_Region ="Agrabah"
Ag2_Region ="Agrabah 2"
SoraLevels = "Sora's Levels"
GoA = "Garden Of Assemblage"
Keyblade = "Weapon Slots"
Dc_Region ="Disney Castle"
Tr_Region ="Timeless River"
Valor = "Valor Form"
Wisdom = "Wisdom Form"
Limit = "Limit Form"
Master = "Master Form"
Final = "Final Form"
Summon = "Summons"
# sp
Sp = "Space Paranoids"
HostileProgram = "Hostile Program"
Sp2 = "Space Paranoids 2"
Mcp = "Master Control Program"
ASLarxene = "AS Larxene"
DataLarxene = "Data Larxene"
HundredAcre1_Region ="Pooh's House"
HundredAcre2_Region ="Piglet's House"
HundredAcre3_Region ="Rabbit's House"
HundredAcre4_Region ="Roo's House"
HundredAcre5_Region ="Spookey Cave"
HundredAcre6_Region ="Starry Hill"
# pr
Pr = "Port Royal"
Barbosa = "Barbosa"
Pr2 = "Port Royal 2"
GrimReaper1 = "Grim Reaper 1"
GrimReaper2 = "Grim Reaper 2"
DataLuxord = "Data Luxord"
Pr_Region ="Port Royal"
Pr2_Region ="Port Royal 2"
Gr2_Region ="Grim Reaper 2"
# tt
Tt = "Twilight Town"
Tt2 = "Twilight Town 2"
Tt3 = "Twilight Town 3"
DataAxel = "Data Axel"
Oc_Region ="Olympus Coliseum"
Oc2_Region ="Olympus Coliseum 2"
Oc2_pain_and_panic_Region ="Pain and Panic Cup"
Oc2_titan_Region ="Titan Cup"
Oc2_cerberus_Region ="Cerberus Cup"
Oc2_gof_Region ="Goddest of Fate Cup"
Oc2Cups_Region ="Olympus Coliseum Cups"
HadesCups_Region ="Olympus Coliseum Hade's Paradox"
# oc
Oc = "Olympus Coliseum"
Cerberus = "Cerberus"
OlympusPete = "Olympus Pete"
Hydra = "Hydra"
OcPainAndPanicCup = "Pain and Panic Cup"
OcCerberusCup = "Cerberus Cup"
Oc2 = "Olympus Coliseum 2"
Hades = "Hades"
ASZexion = "AS Zexion"
DataZexion = "Data Zexion"
Oc2TitanCup = "Titan Cup"
Oc2GofCup = "Goddess of Fate Cup"
Oc2Cups = "Olympus Coliseum Cups"
HadesCups = "Olympus Coliseum Hade's Paradox"
Bc_Region ="Beast's Castle"
Bc2_Region ="Beast's Castle 2"
Xaldin_Region ="Xaldin"
# ht
Ht = "Holloween Town"
PrisonKeeper = "Prison Keeper"
OogieBoogie = "Oogie Boogie"
Ht2 = "Holloween Town 2"
Experiment = "The Experiment"
ASVexen = "AS Vexen"
DataVexen = "Data Vexen"
Sp_Region ="Space Paranoids"
Sp2_Region ="Space Paranoids 2"
Mcp_Region ="Master Control Program"
# lod
LoD = "Land of Dragons"
ShanYu = "Shan Yu"
LoD2 = "Land of Dragons 2"
AnsemRiku = "Ansem Riku"
StormRider = "Storm Rider"
DataXigbar = "Data Xigbar"
Ht_Region ="Holloween Town"
Ht2_Region ="Holloween Town 2"
# twtnw
Twtnw = "The World That Never Was (Pre Roxas)"
Roxas = "Roxas"
Xigbar = "Xigbar"
Luxord = "Luxord"
Saix = "Saix"
Twtnw2 = "The World That Never Was (Second Visit)" # Post riku transformation
Xemnas = "Xemnas"
ArmoredXemnas = "Armored Xemnas"
ArmoredXemnas2 = "Armored Xemnas 2"
FinalXemnas = "Final Xemnas"
DataXemnas = "Data Xemnas"
Hb_Region ="Hollow Bastion"
Hb2_Region ="Hollow Bastion 2"
ThousandHeartless_Region ="Thousand Hearless"
Mushroom13_Region ="Mushroom 13"
CoR_Region ="Cavern of Rememberance"
Transport_Region ="Transport to Rememberance"
# bc
Bc = "Beast's Castle"
Thresholder = "Thresholder"
Beast = "Beast"
DarkThorn = "Dark Thorn"
Bc2 = "Beast's Castle 2"
Xaldin = "Xaldin"
DataXaldin = "Data Xaldin"
Pl_Region ="Pride Lands"
Pl2_Region ="Pride Lands 2"
# ag
Ag = "Agrabah"
TwinLords = "Twin Lords"
Ag2 = "Agrabah 2"
GenieJafar = "Genie Jafar"
ASLexaeus = "AS Lexaeus"
DataLexaeus = "Data Lexaeus"
STT_Region ="Simulated Twilight Town"
# pl
Pl = "Pride Lands"
Scar = "Scar"
Pl2 = "Pride Lands 2"
GroundShaker = "Groundshaker"
DataSaix = "Data Saix"
TT_Region ="Twlight Town"
TT2_Region ="Twlight Town 2"
TT3_Region ="Twlight Town 3"
# hb
Hb = "Hollow Bastion"
Hb2 = "Hollow Bastion 2"
HBDemyx = "Hollow Bastion Demyx"
ThousandHeartless = "Thousand Heartless"
Mushroom13 = "Mushroom 13"
Sephi = "Sephiroth"
DataDemyx = "Data Demyx"
Twtnw_Region ="The World That Never Was (First Visit)"
Twtnw_PostRoxas ="The World That Never Was (Post Roxas)"
Twtnw_PostXigbar ="The World That Never Was (Post Xigbar)"
Twtnw2_Region ="The World That Never Was (Second Visit)" #before riku transformation
# CoR
CoR = "Cavern of Rememberance"
CorFirstFight = "Cavern of Rememberance:Fight 1"
CorSecondFight = "Cavern of Rememberance:Fight 2"
Transport = "Transport to Rememberance"
SoraLevels_Region ="Sora's Levels"
GoA_Region ="Garden Of Assemblage"
Keyblade_Region ="Keyblade Slots"
# dc
Dc = "Disney Castle"
Tr = "Timeless River"
OldPete = "Old Pete"
FuturePete = "Future Pete"
ASMarluxia = "AS Marluxia"
DataMarluxia = "Data Marluxia"
Terra = "Terra"
Valor_Region ="Valor Form"
Wisdom_Region ="Wisdom Form"
Limit_Region ="Limit Form"
Master_Region ="Master Form"
Final_Region ="Final Form"
# stt
Stt = "Simulated Twilight Town"
TwilightThorn = "Twilight Thorn"
Axel1 = "Axel 1"
Axel2 = "Axel 2"
DataRoxas = "Data Roxas"
Terra_Region ="Lingering Will"
Sephi_Region ="Sephiroth"
Marluxia_Region ="Marluxia"
Larxene_Region ="Larxene"
Vexen_Region ="Vexen"
Lexaeus_Region ="Lexaeus"
Zexion_Region ="Zexion"
AtlanticaSongOne = "Atlantica First Song"
AtlanticaSongTwo = "Atlantica Second Song"
AtlanticaSongThree = "Atlantica Third Song"
AtlanticaSongFour = "Atlantica Fourth Song"
LevelsVS1 ="Levels Region (1 Visit Locking Item)"
LevelsVS3 ="Levels Region (3 Visit Locking Items)"
LevelsVS6 ="Levels Region (6 Visit Locking Items)"
LevelsVS9 ="Levels Region (9 Visit Locking Items)"
LevelsVS12 ="Levels Region (12 Visit Locking Items)"
LevelsVS15 ="Levels Region (15 Visit Locking Items)"
LevelsVS18 ="Levels Region (18 Visit Locking Items)"
LevelsVS21 ="Levels Region (21 Visit Locking Items)"
LevelsVS24 ="Levels Region (24 Visit Locking Items)"
LevelsVS26 ="Levels Region (26 Visit Locking Items)"
LevelsVS1 = "Levels Region (1 Visit Locking Item)"
LevelsVS3 = "Levels Region (3 Visit Locking Items)"
LevelsVS6 = "Levels Region (6 Visit Locking Items)"
LevelsVS9 = "Levels Region (9 Visit Locking Items)"
LevelsVS12 = "Levels Region (12 Visit Locking Items)"
LevelsVS15 = "Levels Region (15 Visit Locking Items)"
LevelsVS18 = "Levels Region (18 Visit Locking Items)"
LevelsVS21 = "Levels Region (21 Visit Locking Items)"
LevelsVS24 = "Levels Region (24 Visit Locking Items)"
LevelsVS26 = "Levels Region (26 Visit Locking Items)"

View File

@ -5,7 +5,7 @@ import os
import Utils
import zipfile
from .Items import item_dictionary_table, CheckDupingItems
from .Items import item_dictionary_table
from .Locations import all_locations, SoraLevels, exclusion_table
from .XPValues import lvlStats, formExp, soraExp
from worlds.Files import APContainer
@ -15,7 +15,7 @@ class KH2Container(APContainer):
game: str = 'Kingdom Hearts 2'
def __init__(self, patch_data: dict, base_path: str, output_directory: str,
player=None, player_name: str = "", server: str = ""):
player=None, player_name: str = "", server: str = ""):
self.patch_data = patch_data
self.file_path = base_path
container_path = os.path.join(output_directory, base_path + ".zip")
@ -24,12 +24,6 @@ class KH2Container(APContainer):
def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None:
for filename, yml in self.patch_data.items():
opened_zipfile.writestr(filename, yml)
for root, dirs, files in os.walk(os.path.join(os.path.dirname(__file__), "mod_template")):
for file in files:
opened_zipfile.write(os.path.join(root, file),
os.path.relpath(os.path.join(root, file),
os.path.join(os.path.dirname(__file__), "mod_template")))
# opened_zipfile.writestr(self.zpf_path, self.patch_data)
super().write_contents(opened_zipfile)
@ -59,13 +53,6 @@ def patch_kh2(self, output_directory):
formexp = None
formName = None
levelsetting = list()
slotDataDuping = set()
for values in CheckDupingItems.values():
if isinstance(values, set):
slotDataDuping = slotDataDuping.union(values)
else:
for inner_values in values.values():
slotDataDuping = slotDataDuping.union(inner_values)
if self.multiworld.Keyblade_Minimum[self.player].value > self.multiworld.Keyblade_Maximum[self.player].value:
logging.info(
@ -89,14 +76,19 @@ def patch_kh2(self, output_directory):
levelsetting.extend(exclusion_table["Level99Sanity"])
mod_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.multiworld.get_file_safe_player_name(self.player)}"
all_valid_locations = {location for location, data in all_locations.items()}
for location in self.multiworld.get_filled_locations(self.player):
data = all_locations[location.name]
if location.item.player == self.player:
itemcode = item_dictionary_table[location.item.name].kh2id
if location.name in all_valid_locations:
data = all_locations[location.name]
else:
itemcode = 90 # castle map
continue
if location.item:
if location.item.player == self.player:
itemcode = item_dictionary_table[location.item.name].kh2id
else:
itemcode = 90 # castle map
else:
itemcode = 90
if data.yml == "Chest":
self.formattedTrsr[data.locid] = {"ItemId": itemcode}
@ -129,8 +121,8 @@ def patch_kh2(self, output_directory):
elif data.yml == "Keyblade":
self.formattedItem["Stats"].append({
"Id": data.locid,
"Attack": self.multiworld.per_slot_randoms[self.player].randint(keyblademin, keyblademax),
"Magic": self.multiworld.per_slot_randoms[self.player].randint(keyblademin, keyblademax),
"Attack": self.random.randint(keyblademin, keyblademax),
"Magic": self.random.randint(keyblademin, keyblademax),
"Defense": 0,
"Ability": itemcode,
"AbilityPoints": 0,
@ -154,7 +146,8 @@ def patch_kh2(self, output_directory):
2: self.multiworld.Wisdom_Form_EXP[self.player].value,
3: self.multiworld.Limit_Form_EXP[self.player].value,
4: self.multiworld.Master_Form_EXP[self.player].value,
5: self.multiworld.Final_Form_EXP[self.player].value}
5: self.multiworld.Final_Form_EXP[self.player].value
}
formexp = formDictExp[data.charName]
formName = formDict[data.charName]
self.formattedFmlv[formName] = []
@ -174,7 +167,7 @@ def patch_kh2(self, output_directory):
"GrowthAbilityLevel": 0,
})
# Summons have no checks on them so done fully locally
# Summons have no actual locations so done down here.
self.formattedFmlv["Summon"] = []
for x in range(1, 7):
self.formattedFmlv["Summon"].append({
@ -185,17 +178,18 @@ def patch_kh2(self, output_directory):
"GrowthAbilityLevel": 0,
})
# levels done down here because of optional settings that can take locations out of the pool.
self.i = 1
self.i = 2
for location in SoraLevels:
increaseStat(self.multiworld.per_slot_randoms[self.player].randint(0, 3))
increaseStat(self.random.randint(0, 3))
if location in levelsetting:
data = self.multiworld.get_location(location, self.player)
if data.item.player == self.player:
itemcode = item_dictionary_table[data.item.name].kh2id
else:
itemcode = 90 # castle map
if data.item:
if data.item.player == self.player:
itemcode = item_dictionary_table[data.item.name].kh2id
else:
itemcode = 90 # castle map
else:
increaseStat(self.multiworld.per_slot_randoms[self.player].randint(0, 3))
increaseStat(self.random.randint(0, 3))
itemcode = 0
self.formattedLvup["Sora"][self.i] = {
"Exp": int(soraExp[self.i] / self.multiworld.Sora_Level_EXP[self.player].value),
@ -229,6 +223,193 @@ def patch_kh2(self, output_directory):
"GeneralResistance": 100,
"Unknown": 0
})
self.formattedLvup["Sora"][1] = {
"Exp": int(soraExp[1] / self.multiworld.Sora_Level_EXP[self.player].value),
"Strength": 2,
"Magic": 6,
"Defense": 2,
"Ap": 0,
"SwordAbility": 0,
"ShieldAbility": 0,
"StaffAbility": 0,
"Padding": 0,
"Character": "Sora",
"Level": 1
}
self.mod_yml = {
"assets": [
{
'method': 'binarc',
'name': '00battle.bin',
'source': [
{
'method': 'listpatch',
'name': 'fmlv',
'source': [
{
'name': 'FmlvList.yml',
'type': 'fmlv'
}
],
'type': 'List'
},
{
'method': 'listpatch',
'name': 'lvup',
'source': [
{
'name': 'LvupList.yml',
'type': 'lvup'
}
],
'type': 'List'
},
{
'method': 'listpatch',
'name': 'bons',
'source': [
{
'name': 'BonsList.yml',
'type': 'bons'
}
],
'type': 'List'
}
]
},
{
'method': 'binarc',
'name': '03system.bin',
'source': [
{
'method': 'listpatch',
'name': 'trsr',
'source': [
{
'name': 'TrsrList.yml',
'type': 'trsr'
}
],
'type': 'List'
},
{
'method': 'listpatch',
'name': 'item',
'source': [
{
'name': 'ItemList.yml',
'type': 'item'
}
],
'type': 'List'
}
]
},
{
'name': 'msg/us/po.bar',
'multi': [
{
'name': 'msg/fr/po.bar'
},
{
'name': 'msg/gr/po.bar'
},
{
'name': 'msg/it/po.bar'
},
{
'name': 'msg/sp/po.bar'
}
],
'method': 'binarc',
'source': [
{
'name': 'po',
'type': 'list',
'method': 'kh2msg',
'source': [
{
'name': 'po.yml',
'language': 'en'
}
]
}
]
},
{
'name': 'msg/us/sys.bar',
'multi': [
{
'name': 'msg/fr/sys.bar'
},
{
'name': 'msg/gr/sys.bar'
},
{
'name': 'msg/it/sys.bar'
},
{
'name': 'msg/sp/sys.bar'
}
],
'method': 'binarc',
'source': [
{
'name': 'sys',
'type': 'list',
'method': 'kh2msg',
'source': [
{
'name': 'sys.yml',
'language': 'en'
}
]
}
]
},
],
'title': 'Randomizer Seed'
}
goal_to_text = {
0: "Three Proofs",
1: "Lucky Emblem",
2: "Hitlist",
3: "Lucky Emblem and Hitlist",
}
lucky_emblem_text = {
0: "Your Goal is not Lucky Emblem. It is Hitlist or Three Proofs.",
1: f"Lucky Emblem Required: {self.multiworld.LuckyEmblemsRequired[self.player]} out of {self.multiworld.LuckyEmblemsAmount[self.player]}",
2: "Your Goal is not Lucky Emblem. It is Hitlist or Three Proofs.",
3: f"Lucky Emblem Required: {self.multiworld.LuckyEmblemsRequired[self.player]} out of {self.multiworld.LuckyEmblemsAmount[self.player]}"
}
hitlist_text = {
0: "Your Goal is not Hitlist. It is Lucky Emblem or Three Proofs",
1: "Your Goal is not Hitlist. It is Lucky Emblem or Three Proofs",
2: f"Bounties Required: {self.multiworld.BountyRequired[self.player]} out of {self.multiworld.BountyAmount[self.player]}",
3: f"Bounties Required: {self.multiworld.BountyRequired[self.player]} out of {self.multiworld.BountyAmount[self.player]}",
}
self.pooh_text = [
{
'id': 18326,
'en': f"Your goal is {goal_to_text[self.multiworld.Goal[self.player].value]}"
},
{
'id': 18327,
'en': lucky_emblem_text[self.multiworld.Goal[self.player].value]
},
{
'id': 18328,
'en': hitlist_text[self.multiworld.Goal[self.player].value]
}
]
self.level_depth_text = [
{
'id': 0x3BF1,
'en': f"Your Level Depth is {self.multiworld.LevelDepth[self.player].current_option_name}"
}
]
mod_dir = os.path.join(output_directory, mod_name + "_" + Utils.__version__)
openkhmod = {
@ -237,8 +418,11 @@ def patch_kh2(self, output_directory):
"BonsList.yml": yaml.dump(self.formattedBons, line_break="\n"),
"ItemList.yml": yaml.dump(self.formattedItem, line_break="\n"),
"FmlvList.yml": yaml.dump(self.formattedFmlv, line_break="\n"),
"mod.yml": yaml.dump(self.mod_yml, line_break="\n"),
"po.yml": yaml.dump(self.pooh_text, line_break="\n"),
"sys.yml": yaml.dump(self.level_depth_text, line_break="\n"),
}
mod = KH2Container(openkhmod, mod_dir, output_directory, self.player,
self.multiworld.get_file_safe_player_name(self.player))
self.multiworld.get_file_safe_player_name(self.player))
mod.write()

View File

@ -1,7 +1,8 @@
from Options import Choice, Option, Range, Toggle, OptionSet
import typing
from dataclasses import dataclass
from worlds.kh2 import SupportAbility_Table, ActionAbility_Table
from Options import Choice, Range, Toggle, ItemDict, PerGameCommonOptions, StartInventoryPool
from worlds.kh2 import default_itempool_option
class SoraEXP(Range):
@ -107,23 +108,61 @@ class Visitlocking(Choice):
First and Second Visit Locking: One item for First Visit Two For Second Visit"""
display_name = "Visit locking"
option_no_visit_locking = 0 # starts with 25 visit locking
option_second_visit_locking = 1 # starts with 13 (no icecream/picture)
option_second_visit_locking = 1 # starts with 12 visit locking
option_first_and_second_visit_locking = 2 # starts with nothing
default = 2
class FightLogic(Choice):
"""
The level of logic to use when determining what fights in each KH2 world are beatable.
Easy: For Players not very comfortable doing things without a lot of tools.
Normal: For Players somewhat comfortable doing fights with some of the tools.
Hard: For Players comfortable doing fights with almost no tools.
"""
display_name = "Fight Logic"
option_easy = 0
option_normal = 1
option_hard = 2
default = 1
class FinalFormLogic(Choice):
"""Determines forcing final form logic
No Light and Darkness: Light and Darkness is not in logic.
Light And Darkness: Final Forcing with light and darkness is in logic.
Just a Form: All that requires final forcing is another form.
"""
display_name = "Final Form Logic"
option_no_light_and_darkness = 0
option_light_and_darkness = 1
option_just_a_form = 2
default = 1
class AutoFormLogic(Toggle):
""" Have Auto Forms levels in logic.
"""
display_name = "Auto Form Logic"
default = False
class RandomVisitLockingItem(Range):
"""Start with random amount of visit locking items."""
display_name = "Random Visit Locking Item"
range_start = 0
range_end = 25
default = 3
default = 0
class SuperBosses(Toggle):
"""Terra, Sephiroth and Data Fights Toggle."""
"""Terra Sephiroth and Data Fights Toggle."""
display_name = "Super Bosses"
default = False
default = True
class Cups(Choice):
@ -135,7 +174,7 @@ class Cups(Choice):
option_no_cups = 0
option_cups = 1
option_cups_and_hades_paradox = 2
default = 1
default = 0
class LevelDepth(Choice):
@ -157,67 +196,71 @@ class LevelDepth(Choice):
default = 0
class DonaldGoofyStatsanity(Toggle):
"""Toggles if on Donald and Goofy's Get Bonus locations can be any item"""
display_name = "Donald & Goofy Statsanity"
default = True
class AtlanticaToggle(Toggle):
"""Atlantica Toggle"""
display_name = "Atlantica Toggle"
default = False
class PromiseCharm(Toggle):
"""Add Promise Charm to the Pool"""
"""Add Promise Charm to the pool"""
display_name = "Promise Charm"
default = False
class KeybladeAbilities(Choice):
"""
Action: Action Abilities in the Keyblade Slot Pool.
Support: Support Abilities in the Keyblade Slot Pool.
Both: Action and Support Abilities in the Keyblade Slot Pool."""
display_name = "Keyblade Abilities"
option_support = 0
option_action = 1
option_both = 2
default = 0
class BlacklistKeyblade(OptionSet):
"""Black List these Abilities on Keyblades"""
display_name = "Blacklist Keyblade Abilities"
valid_keys = set(SupportAbility_Table.keys()).union(ActionAbility_Table.keys())
class AntiForm(Toggle):
"""Add Anti Form to the pool"""
display_name = "Anti Form"
default = False
class Goal(Choice):
"""Win Condition
Three Proofs: Get a Gold Crown on Sora's Head.
Three Proofs: Find the 3 Proofs to unlock the final door.
Lucky Emblem Hunt: Find Required Amount of Lucky Emblems .
Lucky Emblem Hunt: Find required amount of Lucky Emblems.
Hitlist (Bounty Hunt): Find Required Amount of Bounties"""
Hitlist (Bounty Hunt): Find required amount of Bounties.
Lucky Emblem and Hitlist: Find the required amount of Lucky Emblems and Bounties."""
display_name = "Goal"
option_three_proofs = 0
option_lucky_emblem_hunt = 1
option_hitlist = 2
default = 0
option_hitlist_and_lucky_emblem = 3
default = 1
class FinalXemnas(Toggle):
"""Kill Final Xemnas to Beat the Game.
This is in addition to your Goal. I.E. get three proofs+kill final Xemnas"""
This is in addition to your Goal.
I.E. get three proofs+kill final Xemnas"""
display_name = "Final Xemnas"
default = True
class LuckyEmblemsRequired(Range):
"""Number of Lucky Emblems to collect to Win/Unlock Final Xemnas Door.
"""Number of Lucky Emblems to collect to Win/Unlock Final Xemnas' Door.
If Goal is not Lucky Emblem Hunt this does nothing."""
If Goal is not Lucky Emblem Hunt or Lucky Emblem and Hitlist this does nothing."""
display_name = "Lucky Emblems Required"
range_start = 1
range_end = 60
default = 30
default = 35
class LuckyEmblemsAmount(Range):
"""Number of Lucky Emblems that are in the pool.
If Goal is not Lucky Emblem Hunt this does nothing."""
If Goal is not Lucky Emblem Hunt or Lucky Emblem and Hitlist this does nothing."""
display_name = "Lucky Emblems Available"
range_start = 1
range_end = 60
@ -227,48 +270,103 @@ class LuckyEmblemsAmount(Range):
class BountyRequired(Range):
"""Number of Bounties to collect to Win/Unlock Final Xemnas Door.
If Goal is not Hitlist this does nothing."""
If Goal is not Hitlist or Lucky Emblem and Hitlist this does nothing."""
display_name = "Bounties Required"
range_start = 1
range_end = 24
range_end = 26
default = 7
class BountyAmount(Range):
"""Number of Bounties that are in the pool.
If Goal is not Hitlist this does nothing."""
If Goal is not Hitlist or Lucky Emblem and Hitlist this does nothing."""
display_name = "Bounties Available"
range_start = 1
range_end = 24
default = 13
range_end = 26
default = 10
KH2_Options: typing.Dict[str, type(Option)] = {
"LevelDepth": LevelDepth,
"Sora_Level_EXP": SoraEXP,
"Valor_Form_EXP": ValorEXP,
"Wisdom_Form_EXP": WisdomEXP,
"Limit_Form_EXP": LimitEXP,
"Master_Form_EXP": MasterEXP,
"Final_Form_EXP": FinalEXP,
"Summon_EXP": SummonEXP,
"Schmovement": Schmovement,
"RandomGrowth": RandomGrowth,
"Promise_Charm": PromiseCharm,
"Goal": Goal,
"FinalXemnas": FinalXemnas,
"LuckyEmblemsAmount": LuckyEmblemsAmount,
"LuckyEmblemsRequired": LuckyEmblemsRequired,
"BountyAmount": BountyAmount,
"BountyRequired": BountyRequired,
"Keyblade_Minimum": KeybladeMin,
"Keyblade_Maximum": KeybladeMax,
"Visitlocking": Visitlocking,
"RandomVisitLockingItem": RandomVisitLockingItem,
"SuperBosses": SuperBosses,
"KeybladeAbilities": KeybladeAbilities,
"BlacklistKeyblade": BlacklistKeyblade,
"Cups": Cups,
class BountyStartHint(Toggle):
"""Start with Bounties Hinted"""
display_name = "Start with Bounties Hinted"
default = False
}
class WeaponSlotStartHint(Toggle):
"""Start with Weapon Slots' Hinted"""
display_name = "Start with Weapon Slots Hinted"
default = False
class CorSkipToggle(Toggle):
"""Toggle for Cor skip.
Tools depend on which difficulty was chosen on Fight Difficulty.
Toggle does not negate fight logic but is an alternative.
Final Chest is also can be put into logic with this skip.
"""
display_name = "CoR Skip Toggle."
default = False
class CustomItemPoolQuantity(ItemDict):
"""Add more of an item into the itempool. Note: You cannot take out items from the pool."""
display_name = "Custom Item Pool"
verify_item_name = True
default = default_itempool_option
class FillerItemsLocal(Toggle):
"""Make all dynamic filler classified items local. Recommended when playing with games with fewer locations than kh2"""
display_name = "Local Filler Items"
default = True
class SummonLevelLocationToggle(Toggle):
"""Toggle Summon levels to have locations."""
display_name = "Summon Level Locations"
default = False
# shamelessly stolen from the messanger
@dataclass
class KingdomHearts2Options(PerGameCommonOptions):
start_inventory: StartInventoryPool
LevelDepth: LevelDepth
Sora_Level_EXP: SoraEXP
Valor_Form_EXP: ValorEXP
Wisdom_Form_EXP: WisdomEXP
Limit_Form_EXP: LimitEXP
Master_Form_EXP: MasterEXP
Final_Form_EXP: FinalEXP
Summon_EXP: SummonEXP
Schmovement: Schmovement
RandomGrowth: RandomGrowth
AntiForm: AntiForm
Promise_Charm: PromiseCharm
Goal: Goal
FinalXemnas: FinalXemnas
LuckyEmblemsAmount: LuckyEmblemsAmount
LuckyEmblemsRequired: LuckyEmblemsRequired
BountyAmount: BountyAmount
BountyRequired: BountyRequired
BountyStartingHintToggle: BountyStartHint
Keyblade_Minimum: KeybladeMin
Keyblade_Maximum: KeybladeMax
WeaponSlotStartHint: WeaponSlotStartHint
FightLogic: FightLogic
FinalFormLogic: FinalFormLogic
AutoFormLogic: AutoFormLogic
DonaldGoofyStatsanity: DonaldGoofyStatsanity
FillerItemsLocal: FillerItemsLocal
Visitlocking: Visitlocking
RandomVisitLockingItem: RandomVisitLockingItem
SuperBosses: SuperBosses
Cups: Cups
SummonLevelLocationToggle: SummonLevelLocationToggle
AtlanticaToggle: AtlanticaToggle
CorSkipToggle: CorSkipToggle
CustomItemPoolQuantity: CustomItemPoolQuantity

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -96,6 +96,10 @@ DC_Checks = {
LocationName.LingeringWillBonus: WorldLocationData(0x370C, 6),
LocationName.LingeringWillProofofConnection: WorldLocationData(0x370C, 6),
LocationName.LingeringWillManifestIllusion: WorldLocationData(0x370C, 6),
'Lingering Will Bonus: Sora Slot 1': WorldLocationData(14092, 6),
'Lingering Will Proof of Connection': WorldLocationData(14092, 6),
'Lingering Will Manifest Illusion': WorldLocationData(14092, 6),
}
TR_Checks = {
LocationName.CornerstoneHillMap: WorldLocationData(0x23B2, 0),
@ -226,6 +230,8 @@ BC_Checks = {
LocationName.DonaldXaldinGetBonus: WorldLocationData(0x3704, 4),
LocationName.SecretAnsemReport4: WorldLocationData(0x1D31, 2),
LocationName.XaldinDataDefenseBoost: WorldLocationData(0x1D34, 7),
'Data Xaldin': WorldLocationData(7476, 7),
}
SP_Checks = {
LocationName.PitCellAreaMap: WorldLocationData(0x23CA, 2),
@ -351,6 +357,7 @@ HB_Checks = {
LocationName.RestorationSiteMoonRecipe: WorldLocationData(0x23C9, 3),
LocationName.RestorationSiteAPBoost: WorldLocationData(0x23DB, 2),
LocationName.DemyxHB: WorldLocationData(0x3707, 4),
'(HB) Demyx Bonus: Donald Slot 1': WorldLocationData(14087, 4),
LocationName.DemyxHBGetBonus: WorldLocationData(0x3707, 4),
LocationName.DonaldDemyxHBGetBonus: WorldLocationData(0x3707, 4),
LocationName.FFFightsCureElement: WorldLocationData(0x1D14, 6),
@ -409,6 +416,25 @@ HB_Checks = {
LocationName.VexenASRoadtoDiscovery: WorldLocationData(0x370C, 0),
LocationName.VexenDataLostIllusion: WorldLocationData(0x370C, 0), #
LocationName.DemyxDataAPBoost: WorldLocationData(0x1D26, 5),
'Lexaeus Bonus: Sora Slot 1': WorldLocationData(14092, 1),
'AS Lexaeus': WorldLocationData(14092, 1),
'Data Lexaeus': WorldLocationData(14092, 1),
'Marluxia Bonus: Sora Slot 1': WorldLocationData(14092, 3),
'AS Marluxia': WorldLocationData(14092, 3),
'Data Marluxia': WorldLocationData(14092, 3),
'Zexion Bonus: Sora Slot 1': WorldLocationData(14092, 2),
'Zexion Bonus: Goofy Slot 1': WorldLocationData(14092, 2),
'AS Zexion': WorldLocationData(14092, 2),
'Data Zexion': WorldLocationData(14092, 2),
'Larxene Bonus: Sora Slot 1': WorldLocationData(14092, 4),
'AS Larxene': WorldLocationData(14092, 4),
'Data Larxene': WorldLocationData(14092, 4),
'Vexen Bonus: Sora Slot 1': WorldLocationData(14092, 0),
'AS Vexen': WorldLocationData(14092, 0),
'Data Vexen': WorldLocationData(14092, 0),
'Data Demyx': WorldLocationData(7462, 5),
LocationName.GardenofAssemblageMap: WorldLocationData(0x23DF, 1),
LocationName.GoALostIllusion: WorldLocationData(0x23DF, 2),
LocationName.ProofofNonexistence: WorldLocationData(0x23DF, 3),
@ -549,50 +575,97 @@ TT_Checks = {
LocationName.BetwixtandBetween: WorldLocationData(0x370B, 7),
LocationName.BetwixtandBetweenBondofFlame: WorldLocationData(0x1CE9, 1),
LocationName.AxelDataMagicBoost: WorldLocationData(0x1CEB, 4),
'Data Axel': WorldLocationData(7403, 4),
}
TWTNW_Checks = {
LocationName.FragmentCrossingMythrilStone: WorldLocationData(0x23CB, 4),
LocationName.FragmentCrossingMythrilCrystal: WorldLocationData(0x23CB, 5),
LocationName.FragmentCrossingAPBoost: WorldLocationData(0x23CB, 6),
LocationName.FragmentCrossingOrichalcum: WorldLocationData(0x23CB, 7),
LocationName.Roxas: WorldLocationData(0x370C, 5),
LocationName.RoxasGetBonus: WorldLocationData(0x370C, 5),
LocationName.RoxasSecretAnsemReport8: WorldLocationData(0x1ED1, 1),
LocationName.TwoBecomeOne: WorldLocationData(0x1ED1, 1),
LocationName.MemorysSkyscaperMythrilCrystal: WorldLocationData(0x23CD, 3),
LocationName.MemorysSkyscaperAPBoost: WorldLocationData(0x23DC, 0),
LocationName.MemorysSkyscaperMythrilStone: WorldLocationData(0x23DC, 1),
LocationName.TheBrinkofDespairDarkCityMap: WorldLocationData(0x23CA, 5),
LocationName.TheBrinkofDespairOrichalcumPlus: WorldLocationData(0x23DA, 2),
LocationName.NothingsCallMythrilGem: WorldLocationData(0x23CC, 0),
LocationName.NothingsCallOrichalcum: WorldLocationData(0x23CC, 1),
LocationName.TwilightsViewCosmicBelt: WorldLocationData(0x23CA, 6),
LocationName.XigbarBonus: WorldLocationData(0x3706, 7),
LocationName.XigbarSecretAnsemReport3: WorldLocationData(0x1ED2, 2),
LocationName.NaughtsSkywayMythrilGem: WorldLocationData(0x23CC, 2),
LocationName.NaughtsSkywayOrichalcum: WorldLocationData(0x23CC, 3),
LocationName.NaughtsSkywayMythrilCrystal: WorldLocationData(0x23CC, 4),
LocationName.Oblivion: WorldLocationData(0x1ED2, 4),
LocationName.CastleThatNeverWasMap: WorldLocationData(0x1ED2, 4),
LocationName.Luxord: WorldLocationData(0x3707, 0),
LocationName.LuxordGetBonus: WorldLocationData(0x3707, 0),
LocationName.LuxordSecretAnsemReport9: WorldLocationData(0x1ED2, 7),
LocationName.SaixBonus: WorldLocationData(0x3707, 1),
LocationName.SaixSecretAnsemReport12: WorldLocationData(0x1ED3, 2),
LocationName.PreXemnas1SecretAnsemReport11: WorldLocationData(0x1ED3, 6),
LocationName.RuinandCreationsPassageMythrilStone: WorldLocationData(0x23CC, 7),
LocationName.RuinandCreationsPassageAPBoost: WorldLocationData(0x23CD, 0),
LocationName.RuinandCreationsPassageMythrilCrystal: WorldLocationData(0x23CD, 1),
LocationName.RuinandCreationsPassageOrichalcum: WorldLocationData(0x23CD, 2),
LocationName.Xemnas1: WorldLocationData(0x3707, 2),
LocationName.Xemnas1GetBonus: WorldLocationData(0x3707, 2),
LocationName.Xemnas1SecretAnsemReport13: WorldLocationData(0x1ED4, 5),
LocationName.FinalXemnas: WorldLocationData(0x1ED8, 1),
LocationName.XemnasDataPowerBoost: WorldLocationData(0x1EDA, 2),
LocationName.XigbarDataDefenseBoost: WorldLocationData(0x1ED9, 7),
LocationName.SaixDataDefenseBoost: WorldLocationData(0x1EDA, 0),
LocationName.LuxordDataAPBoost: WorldLocationData(0x1EDA, 1),
LocationName.RoxasDataMagicBoost: WorldLocationData(0x1ED9, 6),
LocationName.FragmentCrossingMythrilStone: WorldLocationData(0x23CB, 4),
LocationName.FragmentCrossingMythrilCrystal: WorldLocationData(0x23CB, 5),
LocationName.FragmentCrossingAPBoost: WorldLocationData(0x23CB, 6),
LocationName.FragmentCrossingOrichalcum: WorldLocationData(0x23CB, 7),
LocationName.Roxas: WorldLocationData(0x370C, 5),
LocationName.RoxasGetBonus: WorldLocationData(0x370C, 5),
LocationName.RoxasSecretAnsemReport8: WorldLocationData(0x1ED1, 1),
LocationName.TwoBecomeOne: WorldLocationData(0x1ED1, 1),
LocationName.MemorysSkyscaperMythrilCrystal: WorldLocationData(0x23CD, 3),
LocationName.MemorysSkyscaperAPBoost: WorldLocationData(0x23DC, 0),
LocationName.MemorysSkyscaperMythrilStone: WorldLocationData(0x23DC, 1),
LocationName.TheBrinkofDespairDarkCityMap: WorldLocationData(0x23CA, 5),
LocationName.TheBrinkofDespairOrichalcumPlus: WorldLocationData(0x23DA, 2),
LocationName.NothingsCallMythrilGem: WorldLocationData(0x23CC, 0),
LocationName.NothingsCallOrichalcum: WorldLocationData(0x23CC, 1),
LocationName.TwilightsViewCosmicBelt: WorldLocationData(0x23CA, 6),
LocationName.XigbarBonus: WorldLocationData(0x3706, 7),
LocationName.XigbarSecretAnsemReport3: WorldLocationData(0x1ED2, 2),
LocationName.NaughtsSkywayMythrilGem: WorldLocationData(0x23CC, 2),
LocationName.NaughtsSkywayOrichalcum: WorldLocationData(0x23CC, 3),
LocationName.NaughtsSkywayMythrilCrystal: WorldLocationData(0x23CC, 4),
LocationName.Oblivion: WorldLocationData(0x1ED2, 4),
LocationName.CastleThatNeverWasMap: WorldLocationData(0x1ED2, 4),
LocationName.Luxord: WorldLocationData(0x3707, 0),
LocationName.LuxordGetBonus: WorldLocationData(0x3707, 0),
LocationName.LuxordSecretAnsemReport9: WorldLocationData(0x1ED2, 7),
LocationName.SaixBonus: WorldLocationData(0x3707, 1),
LocationName.SaixSecretAnsemReport12: WorldLocationData(0x1ED3, 2),
LocationName.PreXemnas1SecretAnsemReport11: WorldLocationData(0x1ED3, 6),
LocationName.RuinandCreationsPassageMythrilStone: WorldLocationData(0x23CC, 7),
LocationName.RuinandCreationsPassageAPBoost: WorldLocationData(0x23CD, 0),
LocationName.RuinandCreationsPassageMythrilCrystal: WorldLocationData(0x23CD, 1),
LocationName.RuinandCreationsPassageOrichalcum: WorldLocationData(0x23CD, 2),
LocationName.Xemnas1: WorldLocationData(0x3707, 2),
LocationName.Xemnas1GetBonus: WorldLocationData(0x3707, 2),
LocationName.Xemnas1SecretAnsemReport13: WorldLocationData(0x1ED4, 5),
LocationName.FinalXemnas: WorldLocationData(0x1ED8, 1),
LocationName.XemnasDataPowerBoost: WorldLocationData(0x1EDA, 2),
LocationName.XigbarDataDefenseBoost: WorldLocationData(0x1ED9, 7),
LocationName.SaixDataDefenseBoost: WorldLocationData(0x1EDA, 0),
LocationName.LuxordDataAPBoost: WorldLocationData(0x1EDA, 1),
LocationName.RoxasDataMagicBoost: WorldLocationData(0x1ED9, 6),
"(TWTNW) Roxas Bonus: Sora Slot 1": WorldLocationData(14092, 5),
"(TWTNW) Roxas Bonus: Sora Slot 2": WorldLocationData(14092, 5),
"(TWTNW) Roxas Secret Ansem Report 8": WorldLocationData(7889, 1),
"(TWTNW) Two Become One": WorldLocationData(7889, 1),
"(TWTNW) Memory's Skyscaper Mythril Crystal": WorldLocationData(9165, 3),
"(TWTNW) Memory's Skyscaper AP Boost": WorldLocationData(9180, 0),
"(TWTNW) Memory's Skyscaper Mythril Stone": WorldLocationData(9180, 1),
"(TWTNW) The Brink of Despair Dark City Map": WorldLocationData(9162, 5),
"(TWTNW) The Brink of Despair Orichalcum+": WorldLocationData(9178, 2),
"(TWTNW) Nothing's Call Mythril Gem": WorldLocationData(9164, 0),
"(TWTNW) Nothing's Call Orichalcum": WorldLocationData(9164, 1),
"(TWTNW) Twilight's View Cosmic Belt": WorldLocationData(9162, 6),
"(TWTNW) Xigbar Bonus: Sora Slot 1": WorldLocationData(14086, 7),
"(TWTNW) Xigbar Secret Ansem Report 3": WorldLocationData(7890, 2),
"(TWTNW) Naught's Skyway Mythril Gem": WorldLocationData(9164, 2),
"(TWTNW) Naught's Skyway Orichalcum": WorldLocationData(9164, 3),
"(TWTNW) Naught's Skyway Mythril Crystal": WorldLocationData(9164, 4),
"(TWTNW) Oblivion": WorldLocationData(7890, 4),
"(TWTNW) Castle That Never Was Map": WorldLocationData(7890, 4),
"(TWTNW) Luxord": WorldLocationData(14087, 0),
"(TWTNW) Luxord Bonus: Sora Slot 1": WorldLocationData(14087, 0),
"(TWTNW) Luxord Secret Ansem Report 9": WorldLocationData(7890, 7),
"(TWTNW) Saix Bonus: Sora Slot 1": WorldLocationData(14087, 1),
"(TWTNW) Saix Secret Ansem Report 12": WorldLocationData(7891, 2),
"(TWTNW) Secret Ansem Report 11 (Pre-Xemnas 1)": WorldLocationData(7891, 6),
"(TWTNW) Ruin and Creation's Passage Mythril Stone": WorldLocationData(9164, 7),
"(TWTNW) Ruin and Creation's Passage AP Boost": WorldLocationData(9165, 0),
"(TWTNW) Ruin and Creation's Passage Mythril Crystal": WorldLocationData(9165, 1),
"(TWTNW) Ruin and Creation's Passage Orichalcum": WorldLocationData(9165, 2),
"(TWTNW) Xemnas 1 Bonus: Sora Slot 1": WorldLocationData(14087, 2),
"(TWTNW) Xemnas 1 Bonus: Sora Slot 2": WorldLocationData(14087, 2),
"(TWTNW) Xemnas 1 Secret Ansem Report 13": WorldLocationData(7892, 5),
"Data Xemnas": WorldLocationData(7898, 2),
"Data Xigbar": WorldLocationData(7897, 7),
"Data Saix": WorldLocationData(7898, 0),
"Data Luxord": WorldLocationData(7898, 1),
"Data Roxas": WorldLocationData(7897, 6),
}
Atlantica_Checks = {
LocationName.UnderseaKingdomMap: WorldLocationData(0x1DF4, 2),
LocationName.MysteriousAbyss: WorldLocationData(0x1DF5, 3),
LocationName.MusicalOrichalcumPlus: WorldLocationData(0x1DF4, 1),
LocationName.MusicalBlizzardElement: WorldLocationData(0x1DF4, 1)
}
SoraLevels = {
# LocationName.Lvl1: WorldLocationData(0xFFFF,1),
@ -743,6 +816,15 @@ FinalLevels = {
LocationName.Finallvl6: WorldLocationData(0x33D6, 6),
LocationName.Finallvl7: WorldLocationData(0x33D6, 7),
}
SummonLevels = {
LocationName.Summonlvl2: WorldLocationData(0x3526, 2),
LocationName.Summonlvl3: WorldLocationData(0x3526, 3),
LocationName.Summonlvl4: WorldLocationData(0x3526, 4),
LocationName.Summonlvl5: WorldLocationData(0x3526, 5),
LocationName.Summonlvl6: WorldLocationData(0x3526, 6),
LocationName.Summonlvl7: WorldLocationData(0x3526, 7),
}
weaponSlots = {
LocationName.AdamantShield: WorldLocationData(0x35E6, 1),
@ -817,7 +899,6 @@ tornPageLocks = {
all_world_locations = {
**TWTNW_Checks,
**TT_Checks,
**TT_Checks,
**HB_Checks,
**BC_Checks,
**Oc_Checks,
@ -828,11 +909,9 @@ all_world_locations = {
**DC_Checks,
**TR_Checks,
**HT_Checks,
**HB_Checks,
**PR_Checks,
**SP_Checks,
**TWTNW_Checks,
**HB_Checks,
**Atlantica_Checks,
}
levels_locations = {

View File

@ -1,15 +1,25 @@
from BaseClasses import Tutorial, ItemClassification
import logging
from typing import List
from BaseClasses import Tutorial, ItemClassification
from Fill import fill_restrictive
from worlds.LauncherComponents import Component, components, Type, launch_subprocess
from worlds.AutoWorld import World, WebWorld
from .Items import *
from .Locations import all_locations, setup_locations, exclusion_table, AllWeaponSlot
from .Names import ItemName, LocationName
from .Locations import *
from .Names import ItemName, LocationName, RegionName
from .OpenKH import patch_kh2
from .Options import KH2_Options
from .Options import KingdomHearts2Options
from .Regions import create_regions, connect_regions
from .Rules import set_rules
from ..AutoWorld import World, WebWorld
from .logic import KH2Logic
from .Rules import *
def launch_client():
from .Client import launch
launch_subprocess(launch, name="KH2Client")
components.append(Component("KH2 Client", "KH2Client", func=launch_client, component_type=Type.CLIENT))
class KingdomHearts2Web(WebWorld):
@ -23,99 +33,119 @@ class KingdomHearts2Web(WebWorld):
)]
# noinspection PyUnresolvedReferences
class KH2World(World):
"""
Kingdom Hearts II is an action role-playing game developed and published by Square Enix and released in 2005.
It is the sequel to Kingdom Hearts and Kingdom Hearts: Chain of Memories, and like the two previous games,
focuses on Sora and his friends' continued battle against the Darkness.
"""
game: str = "Kingdom Hearts 2"
game = "Kingdom Hearts 2"
web = KingdomHearts2Web()
data_version = 1
required_client_version = (0, 4, 0)
option_definitions = KH2_Options
item_name_to_id = {name: data.code for name, data in item_dictionary_table.items()}
location_name_to_id = {item_name: data.code for item_name, data in all_locations.items() if data.code}
required_client_version = (0, 4, 4)
options_dataclass = KingdomHearts2Options
options: KingdomHearts2Options
item_name_to_id = {item: item_id
for item_id, item in enumerate(item_dictionary_table.keys(), 0x130000)}
location_name_to_id = {item: location
for location, item in enumerate(all_locations.keys(), 0x130000)}
item_name_groups = item_groups
visitlocking_dict: Dict[str, int]
plando_locations: Dict[str, str]
lucky_emblem_amount: int
lucky_emblem_required: int
bounties_required: int
bounties_amount: int
filler_items: List[str]
item_quantity_dict: Dict[str, int]
local_items: Dict[int, int]
sora_ability_dict: Dict[str, int]
goofy_ability_dict: Dict[str, int]
donald_ability_dict: Dict[str, int]
total_locations: int
# growth_list: list[str]
def __init__(self, multiworld: "MultiWorld", player: int):
super().__init__(multiworld, player)
self.valid_abilities = None
self.visitlocking_dict = None
self.plando_locations = None
self.luckyemblemamount = None
self.luckyemblemrequired = None
self.BountiesRequired = None
self.BountiesAmount = None
self.hitlist = None
self.LocalItems = {}
self.RandomSuperBoss = list()
self.filler_items = list()
self.item_quantity_dict = {}
self.donald_ability_pool = list()
self.goofy_ability_pool = list()
self.sora_keyblade_ability_pool = list()
self.keyblade_slot_copy = list(Locations.Keyblade_Slots.keys())
self.keyblade_slot_copy.remove(LocationName.KingdomKeySlot)
self.totalLocations = len(all_locations.items())
# random_super_boss_list List[str]
# has to be in __init__ or else other players affect each other's bounties
self.random_super_boss_list = list()
self.growth_list = list()
for x in range(4):
self.growth_list.extend(Movement_Table.keys())
self.slotDataDuping = set()
self.localItems = dict()
# lists of KH2Item
self.keyblade_ability_pool = list()
self.goofy_get_bonus_abilities = list()
self.goofy_weapon_abilities = list()
self.donald_get_bonus_abilities = list()
self.donald_weapon_abilities = list()
self.slot_data_goofy_weapon = dict()
self.slot_data_sora_weapon = dict()
self.slot_data_donald_weapon = dict()
def fill_slot_data(self) -> dict:
for values in CheckDupingItems.values():
if isinstance(values, set):
self.slotDataDuping = self.slotDataDuping.union(values)
else:
for inner_values in values.values():
self.slotDataDuping = self.slotDataDuping.union(inner_values)
self.LocalItems = {location.address: item_dictionary_table[location.item.name].code
for location in self.multiworld.get_filled_locations(self.player)
if location.item.player == self.player
and location.item.name in self.slotDataDuping
and location.name not in AllWeaponSlot}
for ability in self.slot_data_sora_weapon:
if ability in self.sora_ability_dict and self.sora_ability_dict[ability] >= 1:
self.sora_ability_dict[ability] -= 1
self.donald_ability_dict = {k: v.quantity for k, v in DonaldAbility_Table.items()}
for ability in self.slot_data_donald_weapon:
if ability in self.donald_ability_dict and self.donald_ability_dict[ability] >= 1:
self.donald_ability_dict[ability] -= 1
self.goofy_ability_dict = {k: v.quantity for k, v in GoofyAbility_Table.items()}
for ability in self.slot_data_goofy_weapon:
if ability in self.goofy_ability_dict and self.goofy_ability_dict[ability] >= 1:
self.goofy_ability_dict[ability] -= 1
return {"hitlist": self.hitlist,
"LocalItems": self.LocalItems,
"Goal": self.multiworld.Goal[self.player].value,
"FinalXemnas": self.multiworld.FinalXemnas[self.player].value,
"LuckyEmblemsRequired": self.multiworld.LuckyEmblemsRequired[self.player].value,
"BountyRequired": self.multiworld.BountyRequired[self.player].value}
slot_data = self.options.as_dict("Goal", "FinalXemnas", "LuckyEmblemsRequired", "BountyRequired")
slot_data.update({
"hitlist": [], # remove this after next update
"PoptrackerVersionCheck": 4.3,
"KeybladeAbilities": self.sora_ability_dict,
"StaffAbilities": self.donald_ability_dict,
"ShieldAbilities": self.goofy_ability_dict,
})
return slot_data
def create_item(self, name: str, ) -> Item:
data = item_dictionary_table[name]
if name in Progression_Dicts["Progression"]:
def create_item(self, name: str) -> Item:
"""
Returns created KH2Item
"""
# data = item_dictionary_table[name]
if name in progression_set:
item_classification = ItemClassification.progression
elif name in useful_set:
item_classification = ItemClassification.useful
else:
item_classification = ItemClassification.filler
created_item = KH2Item(name, item_classification, data.code, self.player)
created_item = KH2Item(name, item_classification, self.item_name_to_id[name], self.player)
return created_item
def create_items(self) -> None:
self.visitlocking_dict = Progression_Dicts["AllVisitLocking"].copy()
if self.multiworld.Schmovement[self.player] != "level_0":
for _ in range(self.multiworld.Schmovement[self.player].value):
for name in {ItemName.HighJump, ItemName.QuickRun, ItemName.DodgeRoll, ItemName.AerialDodge,
ItemName.Glide}:
"""
Fills ItemPool and manages schmovement, random growth, visit locking and random starting visit locking.
"""
self.visitlocking_dict = visit_locking_dict["AllVisitLocking"].copy()
if self.options.Schmovement != "level_0":
for _ in range(self.options.Schmovement.value):
for name in Movement_Table.keys():
self.item_quantity_dict[name] -= 1
self.growth_list.remove(name)
self.multiworld.push_precollected(self.create_item(name))
if self.multiworld.RandomGrowth[self.player] != 0:
max_growth = min(self.multiworld.RandomGrowth[self.player].value, len(self.growth_list))
if self.options.RandomGrowth:
max_growth = min(self.options.RandomGrowth.value, len(self.growth_list))
for _ in range(max_growth):
random_growth = self.multiworld.per_slot_randoms[self.player].choice(self.growth_list)
random_growth = self.random.choice(self.growth_list)
self.item_quantity_dict[random_growth] -= 1
self.growth_list.remove(random_growth)
self.multiworld.push_precollected(self.create_item(random_growth))
if self.multiworld.Visitlocking[self.player] == "no_visit_locking":
for item, amount in Progression_Dicts["AllVisitLocking"].items():
if self.options.Visitlocking == "no_visit_locking":
for item, amount in visit_locking_dict["AllVisitLocking"].items():
for _ in range(amount):
self.multiworld.push_precollected(self.create_item(item))
self.item_quantity_dict[item] -= 1
@ -123,19 +153,19 @@ class KH2World(World):
if self.visitlocking_dict[item] == 0:
self.visitlocking_dict.pop(item)
elif self.multiworld.Visitlocking[self.player] == "second_visit_locking":
for item in Progression_Dicts["2VisitLocking"]:
elif self.options.Visitlocking == "second_visit_locking":
for item in visit_locking_dict["2VisitLocking"]:
self.item_quantity_dict[item] -= 1
self.visitlocking_dict[item] -= 1
if self.visitlocking_dict[item] == 0:
self.visitlocking_dict.pop(item)
self.multiworld.push_precollected(self.create_item(item))
for _ in range(self.multiworld.RandomVisitLockingItem[self.player].value):
for _ in range(self.options.RandomVisitLockingItem.value):
if sum(self.visitlocking_dict.values()) <= 0:
break
visitlocking_set = list(self.visitlocking_dict.keys())
item = self.multiworld.per_slot_randoms[self.player].choice(visitlocking_set)
item = self.random.choice(visitlocking_set)
self.item_quantity_dict[item] -= 1
self.visitlocking_dict[item] -= 1
if self.visitlocking_dict[item] == 0:
@ -145,175 +175,258 @@ class KH2World(World):
itempool = [self.create_item(item) for item, data in self.item_quantity_dict.items() for _ in range(data)]
# Creating filler for unfilled locations
itempool += [self.create_filler()
for _ in range(self.totalLocations - len(itempool))]
itempool += [self.create_filler() for _ in range(self.total_locations - len(itempool))]
self.multiworld.itempool += itempool
def generate_early(self) -> None:
# Item Quantity dict because Abilities can be a problem for KH2's Software.
"""
Determines the quantity of items and maps plando locations to items.
"""
# Item: Quantity Map
# Example. Quick Run: 4
self.total_locations = len(all_locations.keys())
for x in range(4):
self.growth_list.extend(Movement_Table.keys())
self.item_quantity_dict = {item: data.quantity for item, data in item_dictionary_table.items()}
self.sora_ability_dict = {k: v.quantity for dic in [SupportAbility_Table, ActionAbility_Table] for k, v in
dic.items()}
# Dictionary to mark locations with their plandoed item
# Example. Final Xemnas: Victory
# 3 random support abilities because there are left over slots
support_abilities = list(SupportAbility_Table.keys())
for _ in range(6):
random_support_ability = self.random.choice(support_abilities)
self.item_quantity_dict[random_support_ability] += 1
self.sora_ability_dict[random_support_ability] += 1
self.plando_locations = dict()
self.hitlist = []
self.starting_invo_verify()
for k, v in self.options.CustomItemPoolQuantity.value.items():
# kh2's items cannot hold more than a byte
if 255 > v > self.item_quantity_dict[k] and k in default_itempool_option.keys():
self.item_quantity_dict[k] = v
elif 255 <= v:
logging.warning(
f"{self.player} has too many {k} in their CustomItemPool setting. Setting to default quantity")
# Option to turn off Promise Charm Item
if not self.multiworld.Promise_Charm[self.player]:
self.item_quantity_dict[ItemName.PromiseCharm] = 0
if not self.options.Promise_Charm:
del self.item_quantity_dict[ItemName.PromiseCharm]
if not self.options.AntiForm:
del self.item_quantity_dict[ItemName.AntiForm]
self.set_excluded_locations()
if self.multiworld.Goal[self.player] == "lucky_emblem_hunt":
self.luckyemblemamount = self.multiworld.LuckyEmblemsAmount[self.player].value
self.luckyemblemrequired = self.multiworld.LuckyEmblemsRequired[self.player].value
if self.options.Goal not in ["hitlist", "three_proofs"]:
self.lucky_emblem_amount = self.options.LuckyEmblemsAmount.value
self.lucky_emblem_required = self.options.LuckyEmblemsRequired.value
self.emblem_verify()
# hitlist
elif self.multiworld.Goal[self.player] == "hitlist":
self.RandomSuperBoss.extend(exclusion_table["Hitlist"])
self.BountiesAmount = self.multiworld.BountyAmount[self.player].value
self.BountiesRequired = self.multiworld.BountyRequired[self.player].value
if self.options.Goal not in ["lucky_emblem_hunt", "three_proofs"]:
self.random_super_boss_list.extend(exclusion_table["Hitlist"])
self.bounties_amount = self.options.BountyAmount.value
self.bounties_required = self.options.BountyRequired.value
self.hitlist_verify()
for bounty in range(self.BountiesAmount):
randomBoss = self.multiworld.per_slot_randoms[self.player].choice(self.RandomSuperBoss)
self.plando_locations[randomBoss] = ItemName.Bounty
self.hitlist.append(self.location_name_to_id[randomBoss])
self.RandomSuperBoss.remove(randomBoss)
self.totalLocations -= 1
prio_hitlist = [location for location in self.multiworld.priority_locations[self.player].value if
location in self.random_super_boss_list]
for bounty in range(self.options.BountyAmount.value):
if prio_hitlist:
random_boss = self.random.choice(prio_hitlist)
prio_hitlist.remove(random_boss)
else:
random_boss = self.random.choice(self.random_super_boss_list)
self.plando_locations[random_boss] = ItemName.Bounty
self.random_super_boss_list.remove(random_boss)
self.total_locations -= 1
self.donald_fill()
self.goofy_fill()
self.keyblade_fill()
self.donald_gen_early()
self.goofy_gen_early()
self.keyblade_gen_early()
if self.multiworld.FinalXemnas[self.player]:
self.plando_locations[LocationName.FinalXemnas] = ItemName.Victory
else:
self.plando_locations[LocationName.FinalXemnas] = self.create_filler().name
self.total_locations -= 1
# same item placed because you can only get one of these 2 locations
# they are both under the same flag so the player gets both locations just one of the two items
random_stt_item = self.create_filler().name
for location in {LocationName.JunkMedal, LocationName.JunkMedal}:
self.plando_locations[location] = random_stt_item
self.level_subtraction()
# subtraction from final xemnas and stt
self.totalLocations -= 3
if self.options.WeaponSlotStartHint:
for location in all_weapon_slot:
self.multiworld.start_location_hints[self.player].value.add(location)
if self.options.FillerItemsLocal:
for item in filler_items:
self.multiworld.local_items[self.player].value.add(item)
# By imitating remote this doesn't have to be plandoded filler anymore
# for location in {LocationName.JunkMedal, LocationName.JunkMedal}:
# self.plando_locations[location] = random_stt_item
if not self.options.SummonLevelLocationToggle:
self.total_locations -= 6
self.total_locations -= self.level_subtraction()
def pre_fill(self):
"""
Plandoing Events and Fill_Restrictive for donald,goofy and sora
"""
self.donald_pre_fill()
self.goofy_pre_fill()
self.keyblade_pre_fill()
for location, item in self.plando_locations.items():
self.multiworld.get_location(location, self.player).place_locked_item(
self.create_item(item))
def create_regions(self):
location_table = setup_locations()
create_regions(self.multiworld, self.player, location_table)
connect_regions(self.multiworld, self.player)
"""
Creates the Regions and Connects them.
"""
create_regions(self)
connect_regions(self)
def set_rules(self):
set_rules(self.multiworld, self.player)
"""
Sets the Logic for the Regions and Locations.
"""
universal_logic = Rules.KH2WorldRules(self)
form_logic = Rules.KH2FormRules(self)
fight_rules = Rules.KH2FightRules(self)
fight_rules.set_kh2_fight_rules()
universal_logic.set_kh2_rules()
form_logic.set_kh2_form_rules()
def generate_output(self, output_directory: str):
"""
Generates the .zip for OpenKH (The KH Mod Manager)
"""
patch_kh2(self, output_directory)
def donald_fill(self):
for item in DonaldAbility_Table:
data = self.item_quantity_dict[item]
for _ in range(data):
self.donald_ability_pool.append(item)
self.item_quantity_dict[item] = 0
# 32 is the amount of donald abilities
while len(self.donald_ability_pool) < 32:
self.donald_ability_pool.append(
self.multiworld.per_slot_randoms[self.player].choice(self.donald_ability_pool))
# Placing Donald Abilities on donald locations
for donaldLocation in Locations.Donald_Checks.keys():
random_ability = self.multiworld.per_slot_randoms[self.player].choice(self.donald_ability_pool)
self.plando_locations[donaldLocation] = random_ability
self.totalLocations -= 1
self.donald_ability_pool.remove(random_ability)
def goofy_fill(self):
for item in GoofyAbility_Table.keys():
data = self.item_quantity_dict[item]
for _ in range(data):
self.goofy_ability_pool.append(item)
self.item_quantity_dict[item] = 0
# 32 is the amount of goofy abilities
while len(self.goofy_ability_pool) < 33:
self.goofy_ability_pool.append(
self.multiworld.per_slot_randoms[self.player].choice(self.goofy_ability_pool))
# Placing Goofy Abilities on goofy locations
for goofyLocation in Locations.Goofy_Checks.keys():
random_ability = self.multiworld.per_slot_randoms[self.player].choice(self.goofy_ability_pool)
self.plando_locations[goofyLocation] = random_ability
self.totalLocations -= 1
self.goofy_ability_pool.remove(random_ability)
def keyblade_fill(self):
if self.multiworld.KeybladeAbilities[self.player] == "support":
self.sora_keyblade_ability_pool = {
**{item: data for item, data in self.item_quantity_dict.items() if item in SupportAbility_Table},
**{ItemName.NegativeCombo: 1, ItemName.AirComboPlus: 1, ItemName.ComboPlus: 1,
ItemName.FinishingPlus: 1}}
elif self.multiworld.KeybladeAbilities[self.player] == "action":
self.sora_keyblade_ability_pool = {item: data for item, data in self.item_quantity_dict.items() if
item in ActionAbility_Table}
# there are too little action abilities so 2 random support abilities are placed
for _ in range(3):
randomSupportAbility = self.multiworld.per_slot_randoms[self.player].choice(
list(SupportAbility_Table.keys()))
while randomSupportAbility in self.sora_keyblade_ability_pool:
randomSupportAbility = self.multiworld.per_slot_randoms[self.player].choice(
list(SupportAbility_Table.keys()))
self.sora_keyblade_ability_pool[randomSupportAbility] = 1
else:
# both action and support on keyblades.
# TODO: make option to just exclude scom
self.sora_keyblade_ability_pool = {
**{item: data for item, data in self.item_quantity_dict.items() if item in SupportAbility_Table},
**{item: data for item, data in self.item_quantity_dict.items() if item in ActionAbility_Table},
**{ItemName.NegativeCombo: 1, ItemName.AirComboPlus: 1, ItemName.ComboPlus: 1,
ItemName.FinishingPlus: 1}}
for ability in self.multiworld.BlacklistKeyblade[self.player].value:
if ability in self.sora_keyblade_ability_pool:
self.sora_keyblade_ability_pool.pop(ability)
# magic number for amount of keyblades
if sum(self.sora_keyblade_ability_pool.values()) < 28:
raise Exception(
f"{self.multiworld.get_file_safe_player_name(self.player)} has too little Keyblade Abilities in the Keyblade Pool")
self.valid_abilities = list(self.sora_keyblade_ability_pool.keys())
# Kingdom Key cannot have No Experience so plandoed here instead of checking 26 times if its kingdom key
random_ability = self.multiworld.per_slot_randoms[self.player].choice(self.valid_abilities)
while random_ability == ItemName.NoExperience:
random_ability = self.multiworld.per_slot_randoms[self.player].choice(self.valid_abilities)
self.plando_locations[LocationName.KingdomKeySlot] = random_ability
self.item_quantity_dict[random_ability] -= 1
self.sora_keyblade_ability_pool[random_ability] -= 1
if self.sora_keyblade_ability_pool[random_ability] == 0:
self.valid_abilities.remove(random_ability)
self.sora_keyblade_ability_pool.pop(random_ability)
# plando keyblades because they can only have abilities
for keyblade in self.keyblade_slot_copy:
random_ability = self.multiworld.per_slot_randoms[self.player].choice(self.valid_abilities)
self.plando_locations[keyblade] = random_ability
def donald_gen_early(self):
random_prog_ability = self.random.choice([ItemName.Fantasia, ItemName.FlareForce])
donald_master_ability = [donald_ability for donald_ability in DonaldAbility_Table.keys() for _ in
range(self.item_quantity_dict[donald_ability]) if
donald_ability != random_prog_ability]
self.donald_weapon_abilities = []
self.donald_get_bonus_abilities = []
# fill goofy weapons first
for _ in range(15):
random_ability = self.random.choice(donald_master_ability)
donald_master_ability.remove(random_ability)
self.donald_weapon_abilities += [self.create_item(random_ability)]
self.item_quantity_dict[random_ability] -= 1
self.sora_keyblade_ability_pool[random_ability] -= 1
if self.sora_keyblade_ability_pool[random_ability] == 0:
self.valid_abilities.remove(random_ability)
self.sora_keyblade_ability_pool.pop(random_ability)
self.totalLocations -= 1
self.total_locations -= 1
self.slot_data_donald_weapon = [item_name.name for item_name in self.donald_weapon_abilities]
if not self.multiworld.DonaldGoofyStatsanity[self.player]:
# pre plando donald get bonuses
self.donald_get_bonus_abilities += [self.create_item(random_prog_ability)]
self.total_locations -= 1
for item_name in donald_master_ability:
self.donald_get_bonus_abilities += [self.create_item(item_name)]
self.item_quantity_dict[item_name] -= 1
self.total_locations -= 1
def goofy_gen_early(self):
random_prog_ability = self.random.choice([ItemName.Teamwork, ItemName.TornadoFusion])
goofy_master_ability = [goofy_ability for goofy_ability in GoofyAbility_Table.keys() for _ in
range(self.item_quantity_dict[goofy_ability]) if goofy_ability != random_prog_ability]
self.goofy_weapon_abilities = []
self.goofy_get_bonus_abilities = []
# fill goofy weapons first
for _ in range(15):
random_ability = self.random.choice(goofy_master_ability)
goofy_master_ability.remove(random_ability)
self.goofy_weapon_abilities += [self.create_item(random_ability)]
self.item_quantity_dict[random_ability] -= 1
self.total_locations -= 1
self.slot_data_goofy_weapon = [item_name.name for item_name in self.goofy_weapon_abilities]
if not self.options.DonaldGoofyStatsanity:
# pre plando goofy get bonuses
self.goofy_get_bonus_abilities += [self.create_item(random_prog_ability)]
self.total_locations -= 1
for item_name in goofy_master_ability:
self.goofy_get_bonus_abilities += [self.create_item(item_name)]
self.item_quantity_dict[item_name] -= 1
self.total_locations -= 1
def keyblade_gen_early(self):
keyblade_master_ability = [ability for ability in SupportAbility_Table.keys() if ability not in progression_set
for _ in range(self.item_quantity_dict[ability])]
self.keyblade_ability_pool = []
for _ in range(len(Keyblade_Slots)):
random_ability = self.random.choice(keyblade_master_ability)
keyblade_master_ability.remove(random_ability)
self.keyblade_ability_pool += [self.create_item(random_ability)]
self.item_quantity_dict[random_ability] -= 1
self.total_locations -= 1
self.slot_data_sora_weapon = [item_name.name for item_name in self.keyblade_ability_pool]
def goofy_pre_fill(self):
"""
Removes donald locations from the location pool maps random donald items to be plandoded.
"""
goofy_weapon_location_list = [self.multiworld.get_location(location, self.player) for location in
Goofy_Checks.keys() if Goofy_Checks[location].yml == "Keyblade"]
# take one of the 2 out
# randomize the list with only
for location in goofy_weapon_location_list:
random_ability = self.random.choice(self.goofy_weapon_abilities)
location.place_locked_item(random_ability)
self.goofy_weapon_abilities.remove(random_ability)
if not self.multiworld.DonaldGoofyStatsanity[self.player]:
# plando goofy get bonuses
goofy_get_bonus_location_pool = [self.multiworld.get_location(location, self.player) for location in
Goofy_Checks.keys() if Goofy_Checks[location].yml != "Keyblade"]
for location in goofy_get_bonus_location_pool:
self.random.choice(self.goofy_get_bonus_abilities)
random_ability = self.random.choice(self.goofy_get_bonus_abilities)
location.place_locked_item(random_ability)
self.goofy_get_bonus_abilities.remove(random_ability)
def donald_pre_fill(self):
donald_weapon_location_list = [self.multiworld.get_location(location, self.player) for location in
Donald_Checks.keys() if Donald_Checks[location].yml == "Keyblade"]
# take one of the 2 out
# randomize the list with only
for location in donald_weapon_location_list:
random_ability = self.random.choice(self.donald_weapon_abilities)
location.place_locked_item(random_ability)
self.donald_weapon_abilities.remove(random_ability)
if not self.multiworld.DonaldGoofyStatsanity[self.player]:
# plando goofy get bonuses
donald_get_bonus_location_pool = [self.multiworld.get_location(location, self.player) for location in
Donald_Checks.keys() if Donald_Checks[location].yml != "Keyblade"]
for location in donald_get_bonus_location_pool:
random_ability = self.random.choice(self.donald_get_bonus_abilities)
location.place_locked_item(random_ability)
self.donald_get_bonus_abilities.remove(random_ability)
def keyblade_pre_fill(self):
"""
Fills keyblade slots with abilities determined on player's setting
"""
keyblade_locations = [self.multiworld.get_location(location, self.player) for location in Keyblade_Slots.keys()]
state = self.multiworld.get_all_state(False)
keyblade_ability_pool_copy = self.keyblade_ability_pool.copy()
fill_restrictive(self.multiworld, state, keyblade_locations, keyblade_ability_pool_copy, True, True)
def starting_invo_verify(self):
"""
Making sure the player doesn't put too many abilities in their starting inventory.
"""
for item, value in self.multiworld.start_inventory[self.player].value.items():
if item in ActionAbility_Table \
or item in SupportAbility_Table or exclusionItem_table["StatUps"] \
or item in SupportAbility_Table or exclusion_item_table["StatUps"] \
or item in DonaldAbility_Table or item in GoofyAbility_Table:
# cannot have more than the quantity for abilties
if value > item_dictionary_table[item].quantity:
@ -324,78 +437,100 @@ class KH2World(World):
self.item_quantity_dict[item] -= value
def emblem_verify(self):
if self.luckyemblemamount < self.luckyemblemrequired:
"""
Making sure lucky emblems have amount>=required.
"""
if self.lucky_emblem_amount < self.lucky_emblem_required:
logging.info(
f"Lucky Emblem Amount {self.multiworld.LuckyEmblemsAmount[self.player].value} is less than required "
f"{self.multiworld.LuckyEmblemsRequired[self.player].value} for player {self.multiworld.get_file_safe_player_name(self.player)}."
f" Setting amount to {self.multiworld.LuckyEmblemsRequired[self.player].value}")
luckyemblemamount = max(self.luckyemblemamount, self.luckyemblemrequired)
self.multiworld.LuckyEmblemsAmount[self.player].value = luckyemblemamount
f"Lucky Emblem Amount {self.options.LuckyEmblemsAmount.value} is less than required "
f"{self.options.LuckyEmblemsRequired.value} for player {self.multiworld.get_file_safe_player_name(self.player)}."
f" Setting amount to {self.options.LuckyEmblemsRequired.value}")
luckyemblemamount = max(self.lucky_emblem_amount, self.lucky_emblem_required)
self.options.LuckyEmblemsAmount.value = luckyemblemamount
self.item_quantity_dict[ItemName.LuckyEmblem] = self.multiworld.LuckyEmblemsAmount[self.player].value
self.item_quantity_dict[ItemName.LuckyEmblem] = self.options.LuckyEmblemsAmount.value
# give this proof to unlock the final door once the player has the amount of lucky emblem required
self.item_quantity_dict[ItemName.ProofofNonexistence] = 0
if ItemName.ProofofNonexistence in self.item_quantity_dict:
del self.item_quantity_dict[ItemName.ProofofNonexistence]
def hitlist_verify(self):
"""
Making sure hitlist have amount>=required.
"""
for location in self.multiworld.exclude_locations[self.player].value:
if location in self.RandomSuperBoss:
self.RandomSuperBoss.remove(location)
if location in self.random_super_boss_list:
self.random_super_boss_list.remove(location)
if not self.options.SummonLevelLocationToggle:
self.random_super_boss_list.remove(LocationName.Summonlvl7)
# Testing if the player has the right amount of Bounties for Completion.
if len(self.RandomSuperBoss) < self.BountiesAmount:
if len(self.random_super_boss_list) < self.bounties_amount:
logging.info(
f"{self.multiworld.get_file_safe_player_name(self.player)} has more bounties than bosses."
f" Setting total bounties to {len(self.RandomSuperBoss)}")
self.BountiesAmount = len(self.RandomSuperBoss)
self.multiworld.BountyAmount[self.player].value = self.BountiesAmount
f" Setting total bounties to {len(self.random_super_boss_list)}")
self.bounties_amount = len(self.random_super_boss_list)
self.options.BountyAmount.value = self.bounties_amount
if len(self.RandomSuperBoss) < self.BountiesRequired:
if len(self.random_super_boss_list) < self.bounties_required:
logging.info(f"{self.multiworld.get_file_safe_player_name(self.player)} has too many required bounties."
f" Setting required bounties to {len(self.RandomSuperBoss)}")
self.BountiesRequired = len(self.RandomSuperBoss)
self.multiworld.BountyRequired[self.player].value = self.BountiesRequired
f" Setting required bounties to {len(self.random_super_boss_list)}")
self.bounties_required = len(self.random_super_boss_list)
self.options.BountyRequired.value = self.bounties_required
if self.BountiesAmount < self.BountiesRequired:
logging.info(f"Bounties Amount {self.multiworld.BountyAmount[self.player].value} is less than required "
f"{self.multiworld.BountyRequired[self.player].value} for player {self.multiworld.get_file_safe_player_name(self.player)}."
f" Setting amount to {self.multiworld.BountyRequired[self.player].value}")
self.BountiesAmount = max(self.BountiesAmount, self.BountiesRequired)
self.multiworld.BountyAmount[self.player].value = self.BountiesAmount
if self.bounties_amount < self.bounties_required:
logging.info(
f"Bounties Amount is less than required for player {self.multiworld.get_file_safe_player_name(self.player)}."
f" Swapping Amount and Required")
temp = self.options.BountyRequired.value
self.options.BountyRequired.value = self.options.BountyAmount.value
self.options.BountyAmount.value = temp
self.multiworld.start_hints[self.player].value.add(ItemName.Bounty)
self.item_quantity_dict[ItemName.ProofofNonexistence] = 0
if self.options.BountyStartingHintToggle:
self.multiworld.start_hints[self.player].value.add(ItemName.Bounty)
if ItemName.ProofofNonexistence in self.item_quantity_dict:
del self.item_quantity_dict[ItemName.ProofofNonexistence]
def set_excluded_locations(self):
"""
Fills excluded_locations from player's settings.
"""
# Option to turn off all superbosses. Can do this individually but its like 20+ checks
if not self.multiworld.SuperBosses[self.player] and not self.multiworld.Goal[self.player] == "hitlist":
for superboss in exclusion_table["Datas"]:
self.multiworld.exclude_locations[self.player].value.add(superboss)
if not self.options.SuperBosses:
for superboss in exclusion_table["SuperBosses"]:
self.multiworld.exclude_locations[self.player].value.add(superboss)
# Option to turn off Olympus Colosseum Cups.
if self.multiworld.Cups[self.player] == "no_cups":
if self.options.Cups == "no_cups":
for cup in exclusion_table["Cups"]:
self.multiworld.exclude_locations[self.player].value.add(cup)
# exclude only hades paradox. If cups and hades paradox then nothing is excluded
elif self.multiworld.Cups[self.player] == "cups":
elif self.options.Cups == "cups":
self.multiworld.exclude_locations[self.player].value.add(LocationName.HadesCupTrophyParadoxCups)
if not self.options.AtlanticaToggle:
for loc in exclusion_table["Atlantica"]:
self.multiworld.exclude_locations[self.player].value.add(loc)
def level_subtraction(self):
# there are levels but level 1 is there for the yamls
if self.multiworld.LevelDepth[self.player] == "level_99_sanity":
# level 99 sanity
self.totalLocations -= 1
elif self.multiworld.LevelDepth[self.player] == "level_50_sanity":
"""
Determine how many locations are on sora's levels.
"""
if self.options.LevelDepth == "level_50_sanity":
# level 50 sanity
self.totalLocations -= 50
elif self.multiworld.LevelDepth[self.player] == "level_1":
return 49
elif self.options.LevelDepth == "level_1":
# level 1. No checks on levels
self.totalLocations -= 99
return 98
elif self.options.LevelDepth in ["level_50", "level_99"]:
# could be if leveldepth!= 99 sanity but this reads better imo
return 75
else:
# level 50/99 since they contain the same amount of levels
self.totalLocations -= 76
return 0
def get_filler_item_name(self) -> str:
return self.multiworld.random.choice(
[ItemName.PowerBoost, ItemName.MagicBoost, ItemName.DefenseBoost, ItemName.APBoost])
"""
Returns random filler item name.
"""
return self.random.choice(filler_items)

View File

@ -1,312 +0,0 @@
from .Names import ItemName
from ..AutoWorld import LogicMixin
class KH2Logic(LogicMixin):
def kh_lod_unlocked(self, player, amount):
return self.has(ItemName.SwordoftheAncestor, player, amount)
def kh_oc_unlocked(self, player, amount):
return self.has(ItemName.BattlefieldsofWar, player, amount)
def kh_twtnw_unlocked(self, player, amount):
return self.has(ItemName.WaytotheDawn, player, amount)
def kh_ht_unlocked(self, player, amount):
return self.has(ItemName.BoneFist, player, amount)
def kh_tt_unlocked(self, player, amount):
return self.has(ItemName.IceCream, player, amount)
def kh_pr_unlocked(self, player, amount):
return self.has(ItemName.SkillandCrossbones, player, amount)
def kh_sp_unlocked(self, player, amount):
return self.has(ItemName.IdentityDisk, player, amount)
def kh_stt_unlocked(self, player: int, amount):
return self.has(ItemName.NamineSketches, player, amount)
# Using Dummy 13 for this
def kh_dc_unlocked(self, player: int, amount):
return self.has(ItemName.CastleKey, player, amount)
def kh_hb_unlocked(self, player, amount):
return self.has(ItemName.MembershipCard, player, amount)
def kh_pl_unlocked(self, player, amount):
return self.has(ItemName.ProudFang, player, amount)
def kh_ag_unlocked(self, player, amount):
return self.has(ItemName.Scimitar, player, amount)
def kh_bc_unlocked(self, player, amount):
return self.has(ItemName.BeastsClaw, player, amount)
def kh_amount_of_forms(self, player, amount, requiredform="None"):
level = 0
formList = [ItemName.ValorForm, ItemName.WisdomForm, ItemName.LimitForm, ItemName.MasterForm,
ItemName.FinalForm]
# required form is in the logic for region connections
if requiredform != "None":
formList.remove(requiredform)
for form in formList:
if self.has(form, player):
level += 1
return level >= amount
def kh_visit_locking_amount(self, player, amount):
visit = 0
# torn pages are not added since you cannot get exp from that world
for item in {ItemName.CastleKey, ItemName.BattlefieldsofWar, ItemName.SwordoftheAncestor, ItemName.BeastsClaw,
ItemName.BoneFist, ItemName.ProudFang, ItemName.SkillandCrossbones, ItemName.Scimitar,
ItemName.MembershipCard,
ItemName.IceCream, ItemName.WaytotheDawn,
ItemName.IdentityDisk, ItemName.NamineSketches}:
visit += self.count(item, player)
return visit >= amount
def kh_three_proof_unlocked(self, player):
return self.has(ItemName.ProofofConnection, player, 1) \
and self.has(ItemName.ProofofNonexistence, player, 1) \
and self.has(ItemName.ProofofPeace, player, 1)
def kh_hitlist(self, player, amount):
return self.has(ItemName.Bounty, player, amount)
def kh_lucky_emblem_unlocked(self, player, amount):
return self.has(ItemName.LuckyEmblem, player, amount)
def kh_victory(self, player):
return self.has(ItemName.Victory, player, 1)
def kh_summon(self, player, amount):
summonlevel = 0
for summon in {ItemName.Genie, ItemName.ChickenLittle, ItemName.Stitch, ItemName.PeterPan}:
if self.has(summon, player):
summonlevel += 1
return summonlevel >= amount
# magic progression
def kh_fire(self, player):
return self.has(ItemName.FireElement, player, 1)
def kh_fira(self, player):
return self.has(ItemName.FireElement, player, 2)
def kh_firaga(self, player):
return self.has(ItemName.FireElement, player, 3)
def kh_blizzard(self, player):
return self.has(ItemName.BlizzardElement, player, 1)
def kh_blizzara(self, player):
return self.has(ItemName.BlizzardElement, player, 2)
def kh_blizzaga(self, player):
return self.has(ItemName.BlizzardElement, player, 3)
def kh_thunder(self, player):
return self.has(ItemName.ThunderElement, player, 1)
def kh_thundara(self, player):
return self.has(ItemName.ThunderElement, player, 2)
def kh_thundaga(self, player):
return self.has(ItemName.ThunderElement, player, 3)
def kh_magnet(self, player):
return self.has(ItemName.MagnetElement, player, 1)
def kh_magnera(self, player):
return self.has(ItemName.MagnetElement, player, 2)
def kh_magnega(self, player):
return self.has(ItemName.MagnetElement, player, 3)
def kh_reflect(self, player):
return self.has(ItemName.ReflectElement, player, 1)
def kh_reflera(self, player):
return self.has(ItemName.ReflectElement, player, 2)
def kh_reflega(self, player):
return self.has(ItemName.ReflectElement, player, 3)
def kh_highjump(self, player, amount):
return self.has(ItemName.HighJump, player, amount)
def kh_quickrun(self, player, amount):
return self.has(ItemName.QuickRun, player, amount)
def kh_dodgeroll(self, player, amount):
return self.has(ItemName.DodgeRoll, player, amount)
def kh_aerialdodge(self, player, amount):
return self.has(ItemName.AerialDodge, player, amount)
def kh_glide(self, player, amount):
return self.has(ItemName.Glide, player, amount)
def kh_comboplus(self, player, amount):
return self.has(ItemName.ComboPlus, player, amount)
def kh_aircomboplus(self, player, amount):
return self.has(ItemName.AirComboPlus, player, amount)
def kh_valorgenie(self, player):
return self.has(ItemName.Genie, player) and self.has(ItemName.ValorForm, player)
def kh_wisdomgenie(self, player):
return self.has(ItemName.Genie, player) and self.has(ItemName.WisdomForm, player)
def kh_mastergenie(self, player):
return self.has(ItemName.Genie, player) and self.has(ItemName.MasterForm, player)
def kh_finalgenie(self, player):
return self.has(ItemName.Genie, player) and self.has(ItemName.FinalForm, player)
def kh_rsr(self, player):
return self.has(ItemName.Slapshot, player, 1) and self.has(ItemName.ComboMaster, player) and self.kh_reflect(
player)
def kh_gapcloser(self, player):
return self.has(ItemName.FlashStep, player, 1) or self.has(ItemName.SlideDash, player)
# Crowd Control and Berserk Hori will be used when I add hard logic.
def kh_crowdcontrol(self, player):
return self.kh_magnera(player) and self.has(ItemName.ChickenLittle, player) \
or self.kh_magnega(player) and self.kh_mastergenie(player)
def kh_berserkhori(self, player):
return self.has(ItemName.HorizontalSlash, player, 1) and self.has(ItemName.BerserkCharge, player)
def kh_donaldlimit(self, player):
return self.has(ItemName.FlareForce, player, 1) or self.has(ItemName.Fantasia, player)
def kh_goofylimit(self, player):
return self.has(ItemName.TornadoFusion, player, 1) or self.has(ItemName.Teamwork, player)
def kh_basetools(self, player):
# TODO: if option is easy then add reflect,gap closer and second chance&once more. #option east scom option normal adds gap closer or combo master #hard is what is right now
return self.has(ItemName.Guard, player, 1) and self.has(ItemName.AerialRecovery, player, 1) \
and self.has(ItemName.FinishingPlus, player, 1)
def kh_roxastools(self, player):
return self.kh_basetools(player) and (
self.has(ItemName.QuickRun, player) or self.has(ItemName.NegativeCombo, player, 2))
def kh_painandpanic(self, player):
return (self.kh_goofylimit(player) or self.kh_donaldlimit(player)) and self.kh_dc_unlocked(player, 2)
def kh_cerberuscup(self, player):
return self.kh_amount_of_forms(player, 2) and self.kh_thundara(player) \
and self.kh_ag_unlocked(player, 1) and self.kh_ht_unlocked(player, 1) \
and self.kh_pl_unlocked(player, 1)
def kh_titan(self, player: int):
return self.kh_summon(player, 2) and (self.kh_thundara(player) or self.kh_magnera(player)) \
and self.kh_oc_unlocked(player, 2)
def kh_gof(self, player):
return self.kh_titan(player) and self.kh_cerberuscup(player) \
and self.kh_painandpanic(player) and self.kh_twtnw_unlocked(player, 1)
def kh_dataroxas(self, player):
return self.kh_basetools(player) and \
((self.has(ItemName.LimitForm, player) and self.kh_amount_of_forms(player, 3) and self.has(
ItemName.TrinityLimit, player) and self.kh_gapcloser(player))
or (self.has(ItemName.NegativeCombo, player, 2) or self.kh_quickrun(player, 2)))
def kh_datamarluxia(self, player):
return self.kh_basetools(player) and self.kh_reflera(player) \
and ((self.kh_amount_of_forms(player, 3) and self.has(ItemName.FinalForm, player) and self.kh_fira(
player)) or self.has(ItemName.NegativeCombo, player, 2) or self.kh_donaldlimit(player))
def kh_datademyx(self, player):
return self.kh_basetools(player) and self.kh_amount_of_forms(player, 5) and self.kh_firaga(player) \
and (self.kh_donaldlimit(player) or self.kh_blizzard(player))
def kh_datalexaeus(self, player):
return self.kh_basetools(player) and self.kh_amount_of_forms(player, 3) and self.kh_reflera(player) \
and (self.has(ItemName.NegativeCombo, player, 2) or self.kh_donaldlimit(player))
def kh_datasaix(self, player):
return self.kh_basetools(player) and (self.kh_thunder(player) or self.kh_blizzard(player)) \
and self.kh_highjump(player, 2) and self.kh_aerialdodge(player, 2) and self.kh_glide(player, 2) and self.kh_amount_of_forms(player, 3) \
and (self.kh_rsr(player) or self.has(ItemName.NegativeCombo, player, 2) or self.has(ItemName.PeterPan,
player))
def kh_dataxaldin(self, player):
return self.kh_basetools(player) and self.kh_donaldlimit(player) and self.kh_goofylimit(player) \
and self.kh_highjump(player, 2) and self.kh_aerialdodge(player, 2) and self.kh_glide(player,
2) and self.kh_magnet(
player)
# and (self.kh_form_level_unlocked(player, 3) or self.kh_berserkhori(player))
def kh_dataxemnas(self, player):
return self.kh_basetools(player) and self.kh_rsr(player) and self.kh_gapcloser(player) \
and (self.has(ItemName.LimitForm, player) or self.has(ItemName.TrinityLimit, player))
def kh_dataxigbar(self, player):
return self.kh_basetools(player) and self.kh_donaldlimit(player) and self.has(ItemName.FinalForm, player) \
and self.kh_amount_of_forms(player, 3) and self.kh_reflera(player)
def kh_datavexen(self, player):
return self.kh_basetools(player) and self.kh_donaldlimit(player) and self.has(ItemName.FinalForm, player) \
and self.kh_amount_of_forms(player, 4) and self.kh_reflera(player) and self.kh_fira(player)
def kh_datazexion(self, player):
return self.kh_basetools(player) and self.kh_donaldlimit(player) and self.has(ItemName.FinalForm, player) \
and self.kh_amount_of_forms(player, 3) \
and self.kh_reflera(player) and self.kh_fira(player)
def kh_dataaxel(self, player):
return self.kh_basetools(player) \
and ((self.kh_reflera(player) and self.kh_blizzara(player)) or self.has(ItemName.NegativeCombo, player, 2))
def kh_dataluxord(self, player):
return self.kh_basetools(player) and self.kh_reflect(player)
def kh_datalarxene(self, player):
return self.kh_basetools(player) and self.kh_reflera(player) \
and ((self.has(ItemName.FinalForm, player) and self.kh_amount_of_forms(player, 4) and self.kh_fire(
player))
or (self.kh_donaldlimit(player) and self.kh_amount_of_forms(player, 2)))
def kh_sephi(self, player):
return self.kh_dataxemnas(player)
def kh_onek(self, player):
return self.kh_reflect(player) or self.has(ItemName.Guard, player)
def kh_terra(self, player):
return self.has(ItemName.ProofofConnection, player) and self.kh_basetools(player) \
and self.kh_dodgeroll(player, 2) and self.kh_aerialdodge(player, 2) and self.kh_glide(player, 3) \
and ((self.kh_comboplus(player, 2) and self.has(ItemName.Explosion, player)) or self.has(
ItemName.NegativeCombo, player, 2))
def kh_cor(self, player):
return self.kh_reflect(player) \
and self.kh_highjump(player, 2) and self.kh_quickrun(player, 2) and self.kh_aerialdodge(player, 2) \
and (self.has(ItemName.MasterForm, player) and self.kh_fire(player)
or (self.has(ItemName.ChickenLittle, player) and self.kh_donaldlimit(player) and self.kh_glide(player,
2)))
def kh_transport(self, player):
return self.kh_basetools(player) and self.kh_reflera(player) \
and ((self.kh_mastergenie(player) and self.kh_magnera(player) and self.kh_donaldlimit(player))
or (self.has(ItemName.FinalForm, player) and self.kh_amount_of_forms(player, 4) and self.kh_fira(
player)))
def kh_gr2(self, player):
return (self.has(ItemName.MasterForm, player) or self.has(ItemName.Stitch, player)) \
and (self.kh_fire(player) or self.kh_blizzard(player) or self.kh_thunder(player))
def kh_xaldin(self, player):
return self.kh_basetools(player) and (self.kh_donaldlimit(player) or self.kh_amount_of_forms(player, 1))
def kh_mcp(self, player):
return self.kh_reflect(player) and (
self.has(ItemName.MasterForm, player) or self.has(ItemName.FinalForm, player))

View File

@ -1,38 +0,0 @@
assets:
- method: binarc
name: 00battle.bin
source:
- method: listpatch
name: fmlv
source:
- name: FmlvList.yml
type: fmlv
type: List
- method: listpatch
name: lvup
source:
- name: LvupList.yml
type: lvup
type: List
- method: listpatch
name: bons
source:
- name: BonsList.yml
type: bons
type: List
- method: binarc
name: 03system.bin
source:
- method: listpatch
name: trsr
source:
- name: TrsrList.yml
type: trsr
type: List
- method: listpatch
name: item
source:
- name: ItemList.yml
type: item
type: List
title: Randomizer Seed

View File

@ -1,30 +0,0 @@
from . import KH2TestBase
from ..Names import ItemName
class TestDefault(KH2TestBase):
options = {}
def testEverything(self):
self.collect_all_but([ItemName.Victory])
self.assertBeatable(True)
class TestLuckyEmblem(KH2TestBase):
options = {
"Goal": 1,
}
def testEverything(self):
self.collect_all_but([ItemName.LuckyEmblem])
self.assertBeatable(True)
class TestHitList(KH2TestBase):
options = {
"Goal": 2,
}
def testEverything(self):
self.collect_all_but([ItemName.Bounty])
self.assertBeatable(True)

View File

@ -1,21 +0,0 @@
import unittest
from test.general import setup_solo_multiworld
from . import KH2TestBase
from .. import KH2World, all_locations, item_dictionary_table, CheckDupingItems, AllWeaponSlot, KH2Item
from ..Names import ItemName
from ... import AutoWorldRegister
from ...AutoWorld import call_all
class TestLocalItems(KH2TestBase):
def testSlotData(self):
gen_steps = ("generate_early", "create_regions", "create_items", "set_rules", "generate_basic", "pre_fill")
multiworld = setup_solo_multiworld(KH2World, gen_steps)
for location in multiworld.get_locations():
if location.item is None:
location.place_locked_item(multiworld.worlds[1].create_item(ItemName.NoExperience))
call_all(multiworld, "fill_slot_data")
slotdata = multiworld.worlds[1].fill_slot_data()
assert len(slotdata["LocalItems"]) > 0, f"{slotdata['LocalItems']} is empty"

View File

@ -1,4 +1,4 @@
from test.TestBase import WorldTestBase
from test.bases import WorldTestBase
class KH2TestBase(WorldTestBase):

View File

@ -0,0 +1,19 @@
from . import KH2TestBase
class TestEasy(KH2TestBase):
options = {
"FightLogic": 0
}
class TestNormal(KH2TestBase):
options = {
"FightLogic": 1
}
class TestHard(KH2TestBase):
options = {
"FightLogic": 2
}

View File

@ -0,0 +1,214 @@
from . import KH2TestBase
from ..Names import ItemName, LocationName
global_all_possible_forms = [ItemName.ValorForm, ItemName.WisdomForm, ItemName.LimitForm, ItemName.MasterForm, ItemName.FinalForm] + [ItemName.AutoValor, ItemName.AutoWisdom, ItemName.AutoLimit, ItemName.AutoMaster, ItemName.AutoFinal]
class KH2TestFormBase(KH2TestBase):
allForms = [ItemName.ValorForm, ItemName.WisdomForm, ItemName.LimitForm, ItemName.MasterForm, ItemName.FinalForm]
autoForms = [ItemName.AutoValor, ItemName.AutoWisdom, ItemName.AutoLimit, ItemName.AutoMaster, ItemName.AutoFinal]
allLevel2 = [LocationName.Valorlvl2, LocationName.Wisdomlvl2, LocationName.Limitlvl2, LocationName.Masterlvl2,
LocationName.Finallvl2]
allLevel3 = [LocationName.Valorlvl3, LocationName.Wisdomlvl3, LocationName.Limitlvl3, LocationName.Masterlvl3,
LocationName.Finallvl3]
allLevel4 = [LocationName.Valorlvl4, LocationName.Wisdomlvl4, LocationName.Limitlvl4, LocationName.Masterlvl4,
LocationName.Finallvl4]
allLevel5 = [LocationName.Valorlvl5, LocationName.Wisdomlvl5, LocationName.Limitlvl5, LocationName.Masterlvl5,
LocationName.Finallvl5]
allLevel6 = [LocationName.Valorlvl6, LocationName.Wisdomlvl6, LocationName.Limitlvl6, LocationName.Masterlvl6,
LocationName.Finallvl6]
allLevel7 = [LocationName.Valorlvl7, LocationName.Wisdomlvl7, LocationName.Limitlvl7, LocationName.Masterlvl7,
LocationName.Finallvl7]
driveToAuto = {
ItemName.FinalForm: ItemName.AutoFinal,
ItemName.MasterForm: ItemName.AutoMaster,
ItemName.LimitForm: ItemName.AutoLimit,
ItemName.WisdomForm: ItemName.AutoWisdom,
ItemName.ValorForm: ItemName.AutoValor,
}
AutoToDrive = {Auto: Drive for Drive, Auto in driveToAuto.items()}
driveFormMap = {
ItemName.ValorForm: [LocationName.Valorlvl2,
LocationName.Valorlvl3,
LocationName.Valorlvl4,
LocationName.Valorlvl5,
LocationName.Valorlvl6,
LocationName.Valorlvl7],
ItemName.WisdomForm: [LocationName.Wisdomlvl2,
LocationName.Wisdomlvl3,
LocationName.Wisdomlvl4,
LocationName.Wisdomlvl5,
LocationName.Wisdomlvl6,
LocationName.Wisdomlvl7],
ItemName.LimitForm: [LocationName.Limitlvl2,
LocationName.Limitlvl3,
LocationName.Limitlvl4,
LocationName.Limitlvl5,
LocationName.Limitlvl6,
LocationName.Limitlvl7],
ItemName.MasterForm: [LocationName.Masterlvl2,
LocationName.Masterlvl3,
LocationName.Masterlvl4,
LocationName.Masterlvl5,
LocationName.Masterlvl6,
LocationName.Masterlvl7],
ItemName.FinalForm: [LocationName.Finallvl2,
LocationName.Finallvl3,
LocationName.Finallvl4,
LocationName.Finallvl5,
LocationName.Finallvl6,
LocationName.Finallvl7],
}
# global_all_possible_forms = allForms + autoForms
class TestDefaultForms(KH2TestFormBase):
"""
Test default form access rules.
"""
options = {
"AutoFormLogic": False,
"FinalFormLogic": "light_and_darkness"
}
def test_default_Auto_Form_Logic(self):
allPossibleForms = global_all_possible_forms
# this tests with a light and darkness in the inventory.
self.collect_all_but(allPossibleForms)
for form in self.allForms:
self.assertFalse((self.can_reach_location(self.driveFormMap[form][0])), form)
self.collect(self.get_item_by_name(self.driveToAuto[form]))
self.assertFalse((self.can_reach_location(self.driveFormMap[form][0])), form)
def test_default_Final_Form(self):
allPossibleForms = global_all_possible_forms
self.collect_all_but(allPossibleForms)
self.collect_by_name(ItemName.FinalForm)
self.assertTrue((self.can_reach_location(LocationName.Finallvl2)))
self.assertTrue((self.can_reach_location(LocationName.Finallvl3)))
self.assertFalse((self.can_reach_location(LocationName.Finallvl4)))
def test_default_without_LnD(self):
allPossibleForms = self.allForms
self.collect_all_but(allPossibleForms)
for form, levels in self.driveFormMap.items():
# final form is unique and breaks using this test. Tested above.
if levels[0] == LocationName.Finallvl2:
continue
for driveForm in self.allForms:
if self.count(driveForm) >= 1:
for _ in range(self.count(driveForm)):
self.remove(self.get_item_by_name(driveForm))
allFormsCopy = self.allForms.copy()
allFormsCopy.remove(form)
self.collect(self.get_item_by_name(form))
for _ in range(self.count(ItemName.LightDarkness)):
self.remove(self.get_item_by_name(ItemName.LightDarkness))
self.assertTrue((self.can_reach_location(levels[0])), levels[0])
self.assertTrue((self.can_reach_location(levels[1])), levels[1])
self.assertFalse((self.can_reach_location(levels[2])), levels[2])
for i in range(3):
self.collect(self.get_item_by_name(allFormsCopy[i]))
# for some reason after collecting a form it can pick up light and darkness
for _ in range(self.count(ItemName.LightDarkness)):
self.remove(self.get_item_by_name(ItemName.LightDarkness))
self.assertTrue((self.can_reach_location(levels[2 + i])))
if i < 2:
self.assertFalse((self.can_reach_location(levels[3 + i])))
else:
self.collect(self.get_item_by_name(allFormsCopy[i + 1]))
for _ in range(self.count(ItemName.LightDarkness)):
self.remove(self.get_item_by_name(ItemName.LightDarkness))
self.assertTrue((self.can_reach_location(levels[3 + i])))
def test_default_with_lnd(self):
allPossibleForms = self.allForms
self.collect_all_but(allPossibleForms)
for form, levels in self.driveFormMap.items():
if form != ItemName.FinalForm:
for driveForm in self.allForms:
for _ in range(self.count(driveForm)):
self.remove(self.get_item_by_name(driveForm))
allFormsCopy = self.allForms.copy()
allFormsCopy.remove(form)
self.collect(self.get_item_by_name(ItemName.LightDarkness))
self.assertFalse((self.can_reach_location(levels[0])))
self.collect(self.get_item_by_name(form))
self.assertTrue((self.can_reach_location(levels[0])))
self.assertTrue((self.can_reach_location(levels[1])))
self.assertTrue((self.can_reach_location(levels[2])))
self.assertFalse((self.can_reach_location(levels[3])))
for i in range(2):
self.collect(self.get_item_by_name(allFormsCopy[i]))
self.assertTrue((self.can_reach_location(levels[i + 3])))
if i <= 2:
self.assertFalse((self.can_reach_location(levels[i + 4])))
class TestJustAForm(KH2TestFormBase):
# this test checks if you can unlock final form with just a form.
options = {
"AutoFormLogic": False,
"FinalFormLogic": "just_a_form"
}
def test_just_a_form_connections(self):
allPossibleForms = self.allForms
self.collect_all_but(allPossibleForms)
allPossibleForms.remove(ItemName.FinalForm)
for form, levels in self.driveFormMap.items():
for driveForm in self.allForms:
for _ in range(self.count(driveForm)):
self.remove(self.get_item_by_name(driveForm))
if form != ItemName.FinalForm:
# reset the forms
allFormsCopy = self.allForms.copy()
allFormsCopy.remove(form)
self.assertFalse((self.can_reach_location(levels[0])))
self.collect(self.get_item_by_name(form))
self.assertTrue((self.can_reach_location(levels[0])))
self.assertTrue((self.can_reach_location(levels[1])))
self.assertTrue((self.can_reach_location(levels[2])))
# level 4 of a form. This tests if the player can unlock final form.
self.assertFalse((self.can_reach_location(levels[3])))
# amount of forms left in the pool are 3. 1 already collected and one is final form.
for i in range(3):
allFormsCopy.remove(allFormsCopy[0])
# so we don't accidentally collect another form like light and darkness in the above tests.
self.collect_all_but(allFormsCopy)
self.assertTrue((self.can_reach_location(levels[3 + i])), levels[3 + i])
if i < 2:
self.assertFalse((self.can_reach_location(levels[4 + i])), levels[4 + i])
class TestAutoForms(KH2TestFormBase):
options = {
"AutoFormLogic": True,
"FinalFormLogic": "light_and_darkness"
}
def test_Nothing(self):
KH2TestBase()
def test_auto_forms_level_progression(self):
allPossibleForms = self.allForms + [ItemName.LightDarkness]
# state has all auto forms
self.collect_all_but(allPossibleForms)
allPossibleFormsCopy = allPossibleForms.copy()
collectedDrives = []
i = 0
for form in allPossibleForms:
currentDriveForm = form
collectedDrives += [currentDriveForm]
allPossibleFormsCopy.remove(currentDriveForm)
self.collect_all_but(allPossibleFormsCopy)
for driveForm in self.allForms:
# +1 every iteration.
self.assertTrue((self.can_reach_location(self.driveFormMap[driveForm][i])), driveForm)
# making sure having the form still gives an extra drive level to its own form.
if driveForm in collectedDrives and i < 5:
self.assertTrue((self.can_reach_location(self.driveFormMap[driveForm][i + 1])), driveForm)
i += 1

View File

@ -0,0 +1,59 @@
from . import KH2TestBase
from ..Names import ItemName
class TestDefault(KH2TestBase):
options = {}
class TestThreeProofs(KH2TestBase):
options = {
"Goal": 0,
}
class TestLuckyEmblem(KH2TestBase):
options = {
"Goal": 1,
}
class TestHitList(KH2TestBase):
options = {
"Goal": 2,
}
class TestLuckyEmblemHitlist(KH2TestBase):
options = {
"Goal": 3,
}
class TestThreeProofsNoXemnas(KH2TestBase):
options = {
"Goal": 0,
"FinalXemnas": False,
}
class TestLuckyEmblemNoXemnas(KH2TestBase):
options = {
"Goal": 1,
"FinalXemnas": False,
}
class TestHitListNoXemnas(KH2TestBase):
options = {
"Goal": 2,
"FinalXemnas": False,
}
class TestLuckyEmblemHitlistNoXemnas(KH2TestBase):
options = {
"Goal": 3,
"FinalXemnas": False,
}