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:
parent
c138918400
commit
2ccf11f3d7
892
KH2Client.py
892
KH2Client.py
|
@ -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()
|
||||
|
|
1
setup.py
1
setup.py
|
@ -71,7 +71,6 @@ non_apworlds: set = {
|
|||
"Clique",
|
||||
"DLCQuest",
|
||||
"Final Fantasy",
|
||||
"Kingdom Hearts 2",
|
||||
"Lufia II Ancient Cave",
|
||||
"Meritous",
|
||||
"Ocarina of Time",
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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()
|
1352
worlds/kh2/Items.py
1352
worlds/kh2/Items.py
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
1233
worlds/kh2/Rules.py
1233
worlds/kh2/Rules.py
File diff suppressed because it is too large
Load Diff
|
@ -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 = {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
|
@ -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
|
|
@ -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)
|
|
@ -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"
|
|
@ -1,4 +1,4 @@
|
|||
from test.TestBase import WorldTestBase
|
||||
from test.bases import WorldTestBase
|
||||
|
||||
|
||||
class KH2TestBase(WorldTestBase):
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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,
|
||||
}
|
||||
|
Loading…
Reference in New Issue