From a6740e7be3ce33cd1802c1f85be201e5477fd246 Mon Sep 17 00:00:00 2001 From: JaredWeakStrike <96694163+JaredWeakStrike@users.noreply.github.com> Date: Wed, 28 Jan 2026 01:10:29 -0500 Subject: [PATCH] KH2: Deathlink and ingame item popups (#5206) --------- Co-authored-by: qwint Co-authored-by: Delilah --- worlds/kh2/Client.py | 1038 +---------------- worlds/kh2/ClientStuff/CMDProcessor.py | 95 ++ worlds/kh2/ClientStuff/Client.py | 754 ++++++++++++ worlds/kh2/ClientStuff/ReadAndWrite.py | 47 + worlds/kh2/ClientStuff/RecieveItems.py | 434 +++++++ worlds/kh2/ClientStuff/SendChecks.py | 187 +++ .../kh2/{ => ClientStuff}/WorldLocations.py | 83 +- worlds/kh2/ClientStuff/__init__.py | 0 worlds/kh2/Locations.py | 1 - worlds/kh2/OpenKH.py | 49 +- worlds/kh2/__init__.py | 29 +- worlds/kh2/data/khapicon.ico | Bin 0 -> 93345 bytes worlds/kh2/data/khapicon.png | Bin 0 -> 21016 bytes worlds/kh2/data/preview.png | Bin 0 -> 44905 bytes worlds/kh2/docs/en_Kingdom Hearts 2.md | 30 +- worlds/kh2/docs/setup_en.md | 24 +- 16 files changed, 1705 insertions(+), 1066 deletions(-) create mode 100644 worlds/kh2/ClientStuff/CMDProcessor.py create mode 100644 worlds/kh2/ClientStuff/Client.py create mode 100644 worlds/kh2/ClientStuff/ReadAndWrite.py create mode 100644 worlds/kh2/ClientStuff/RecieveItems.py create mode 100644 worlds/kh2/ClientStuff/SendChecks.py rename worlds/kh2/{ => ClientStuff}/WorldLocations.py (96%) create mode 100644 worlds/kh2/ClientStuff/__init__.py create mode 100644 worlds/kh2/data/khapicon.ico create mode 100644 worlds/kh2/data/khapicon.png create mode 100644 worlds/kh2/data/preview.png diff --git a/worlds/kh2/Client.py b/worlds/kh2/Client.py index 5a26231c0c..506354f243 100644 --- a/worlds/kh2/Client.py +++ b/worlds/kh2/Client.py @@ -1,1034 +1,4 @@ -import ModuleUpdate -import Utils - -ModuleUpdate.update() - -import os -import asyncio -import json -import requests -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.kh2_finished_game = False - self.serverconnected = 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_data_package = {} - 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 = [] - self.slot_name = None - self.disconnect_from_server = False - # 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.kh2seedname = None - self.kh2_seed_save_path_join = None - - self.kh2slotdata = None - self.mem_json = 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 - } - self.last_world_int = -1 - # PC Address anchors - # epic .10 addresses - self.Now = 0x0716DF8 - self.Save = 0x9A9330 - self.Journal = 0x743260 - self.Shop = 0x743350 - self.Slot1 = 0x2A23018 - - self.kh2_game_version = None # can be egs or steam - - self.kh2_seed_save_path = None - - 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"} - - self.base_hp = 20 - self.base_mp = 100 - self.base_drive = 5 - self.base_accessory_slots = 1 - self.base_armor_slots = 1 - self.base_item_slots = 3 - self.front_ability_slots = [0x2546, 0x2658, 0x276C, 0x2548, 0x254A, 0x254C, 0x265A, 0x265C, 0x265E, 0x276E, - 0x2770, 0x2772] - - 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() - # if slot name != first time login or previous name - # and seed name is none or saved seed name - if not self.slot_name and not self.kh2seedname: - await self.send_connect() - elif self.slot_name == self.auth and self.kh2seedname: - await self.send_connect() - else: - logger.info(f"You are trying to connect with data still cached in the client. Close client or connect to the correct slot: {self.slot_name}") - self.serverconnected = False - self.disconnect_from_server = True - - async def connection_closed(self): - self.kh2connected = False - self.serverconnected = False - if self.kh2seedname is not None and self.auth is not None: - with open(self.kh2_seed_save_path_join, '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.serverconnected = False - self.locations_checked = [] - if self.kh2seedname not in {None} and self.auth not in {None}: - with open(self.kh2_seed_save_path_join, '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(self.kh2_seed_save_path_join, '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)) - - def kh2_read_int(self, address): - return self.kh2.read_int(self.kh2.base_address + address) - - def kh2_write_int(self, address, value): - self.kh2.write_int(self.kh2.base_address + address, value) - - def kh2_read_string(self, address, length): - return self.kh2.read_string(self.kh2.base_address + address, length) - - def on_package(self, cmd: str, args: dict): - if cmd == "RoomInfo": - if not self.kh2seedname: - self.kh2seedname = args['seed_name'] - elif self.kh2seedname != args['seed_name']: - self.disconnect_from_server = True - self.serverconnected = False - self.kh2connected = False - logger.info("Connection to the wrong seed, connect to the correct seed or close the client.") - return - - self.kh2_seed_save_path = f"kh2save2{self.kh2seedname}{self.auth}.json" - self.kh2_seed_save_path_join = os.path.join(self.game_communication_path, self.kh2_seed_save_path) - - if not os.path.exists(self.game_communication_path): - os.makedirs(self.game_communication_path) - if not os.path.exists(self.kh2_seed_save_path_join): - self.kh2_seed_save = { - "Levels": { - "SoraLevel": 0, - "ValorLevel": 0, - "WisdomLevel": 0, - "LimitLevel": 0, - "MasterLevel": 0, - "FinalLevel": 0, - "SummonLevel": 0, - }, - "SoldEquipment": [], - } - with open(self.kh2_seed_save_path_join, 'wt') as f: - pass - # self.locations_checked = set() - elif os.path.exists(self.kh2_seed_save_path_join): - with open(self.kh2_seed_save_path_join) 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 == "Connected": - self.kh2slotdata = args['slot_data'] - - self.kh2_data_package = Utils.load_data_package_for_checksum( - "Kingdom Hearts 2", self.checksums["Kingdom Hearts 2"]) - - if "location_name_to_id" in self.kh2_data_package: - self.data_package_kh2_cache( - self.kh2_data_package["location_name_to_id"], self.kh2_data_package["item_name_to_id"]) - self.connect_to_game() - else: - asyncio.create_task(self.send_msgs([{"cmd": "GetDataPackage", "games": ["Kingdom Hearts 2"]}])) - - self.locations_checked = set(args["checked_locations"]) - - if cmd == "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.serverconnected: - 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 == "RoomUpdate": - if "checked_locations" in args: - new_locations = set(args["checked_locations"]) - self.locations_checked |= new_locations - - if cmd == "DataPackage": - if "Kingdom Hearts 2" in args["data"]["games"]: - self.data_package_kh2_cache( - args["data"]["games"]["Kingdom Hearts 2"]["location_name_to_id"], - args["data"]["games"]["Kingdom Hearts 2"]["item_name_to_id"]) - self.connect_to_game() - asyncio.create_task(self.send_msgs([{'cmd': 'Sync'}])) - - def connect_to_game(self): - if "KeybladeAbilities" in self.kh2slotdata.keys(): - # sora ability to slot - self.AbilityQuantityDict.update(self.kh2slotdata["KeybladeAbilities"]) - # itemid:[slots that are available for that item] - self.AbilityQuantityDict.update(self.kh2slotdata["StaffAbilities"]) - self.AbilityQuantityDict.update(self.kh2slotdata["ShieldAbilities"]) - - self.all_weapon_location_id = {self.kh2_loc_name_to_id[loc] for loc in all_weapon_slot} - - try: - if not self.kh2: - self.kh2 = pymem.Pymem(process_name="KINGDOM HEARTS II FINAL MIX") - self.get_addresses() -# - except Exception as e: - if self.kh2connected: - self.kh2connected = False - logger.info("Game is not open.") - - self.serverconnected = True - self.slot_name = self.auth - - def data_package_kh2_cache(self, loc_to_id, item_to_id): - self.kh2_loc_name_to_id = loc_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 = item_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"]] - - async def checkWorldLocations(self): - try: - currentworldint = self.kh2_read_byte(self.Now) - if self.last_world_int != currentworldint: - self.last_world_int = currentworldint - await self.send_msgs([{ - "cmd": "Set", "key": "Slot: " + str(self.slot) + " :CurrentWorld", - "default": 0, "want_reply": False, "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] - } - 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: - # sleep so we can get the datapackage and not miss any items that were sent to us while we didnt have our item id dicts - while not self.lookup_id_to_item: - await asyncio.sleep(0.5) - itemname = self.lookup_id_to_item[item] - itemdata = self.item_name_to_data[itemname] - if itemdata.ability: - if location in self.all_weapon_location_id: - return - # growth have reserved ability slots because of how the goa handles them - 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 - # abilities have a limit amount of slots. - # we start from the back going down to not mess with stuff. - # Front of Invo - # Sora: Save+24F0+0x54 : 0x2546 - # Donald: Save+2604+0x54 : 0x2658 - # Goofy: Save+2718+0x54 : 0x276C - # Back of Invo. Sora has 6 ability slots that are reserved - # Sora: Save+24F0+0x54+0x92 : 0x25D8 - # Donald: Save+2604+0x54+0x9C : 0x26F4 - # Goofy: Save+2718+0x54+0x9C : 0x2808 - # seed has 2 scans in sora's abilities - # recieved second scan - # if len(seed_save(Scan:[ability slot 52]) < (2)amount of that ability they should have from slot data - # ability_slot = back of inventory that isnt taken - # add ability_slot to seed_save(Scan[]) so now its Scan:[ability slot 52,50] - # decrease back of inventory since its ability_slot is already taken - if 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 - else: - 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 - - if ability_slot in self.front_ability_slots: - self.front_ability_slots.remove(ability_slot) - - # if itemdata in {bitmask} all the forms,summons and a few other things are bitmasks - 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) - - # if itemdata in {magic} - elif itemdata.memaddr in {0x3594, 0x3595, 0x3596, 0x3597, 0x35CF, 0x35D0}: - self.kh2_seed_save_cache["AmountInvo"]["Magic"][itemname] += 1 - - # equipment is a list instead of dict because you can only have 1 currently - elif itemname in self.all_equipment: - self.kh2_seed_save_cache["AmountInvo"]["Equipment"].append(itemname) - - # weapons are done differently since you can only have one and has to check it differently - 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) - - # TODO: this can just be removed and put into the else below it - elif itemname in self.stat_increase_set: - self.kh2_seed_save_cache["AmountInvo"]["StatIncrease"][itemname] += 1 - else: - # "normal" items. They have a unique byte reserved for how many they have - 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(self.Journal) - shop = self.kh2_read_short(self.Shop) - 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(self.Journal) - shop = self.kh2_read_short(self.Shop) - 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)) - # print(self.kh2_seed_save_cache["AmountInvo"]["Ability"]) - 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 ability in self.front_ability_slots: - if self.kh2_read_short(self.Save + ability) != 0: - print(f"removed {self.Save + ability} from {ability}") - self.kh2_write_short(self.Save + ability, 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( - self.Shop) in {10, 8}: - self.kh2_write_byte(self.Save + item_data.memaddr, amount_of_items) - - for item_name in master_stat: - amount_of_items = 0 - amount_of_items += self.kh2_seed_save_cache["AmountInvo"]["StatIncrease"][item_name] - # checking if they talked to the computer to give them these - if self.kh2_read_byte(self.Slot1 + 0x1B2) >= 5 and (self.kh2_read_byte(self.Save + 0x1D27) & 0x1 << 3) > 0: - if item_name == ItemName.MaxHPUp: - if self.kh2_read_byte(self.Save + 0x2498) < 3: # Non-Critical - Bonus = 5 - else: # Critical - Bonus = 2 - if self.kh2_read_int(self.Slot1 + 0x004) != self.base_hp + (Bonus * amount_of_items): - self.kh2_write_int(self.Slot1 + 0x004, self.base_hp + (Bonus * amount_of_items)) - - elif item_name == ItemName.MaxMPUp: - if self.kh2_read_byte(self.Save + 0x2498) < 3: # Non-Critical - Bonus = 10 - else: # Critical - Bonus = 5 - if self.kh2_read_int(self.Slot1 + 0x184) != self.base_mp + (Bonus * amount_of_items): - self.kh2_write_int(self.Slot1 + 0x184, self.base_mp + (Bonus * amount_of_items)) - - elif item_name == ItemName.DriveGaugeUp: - current_max_drive = self.kh2_read_byte(self.Slot1 + 0x1B2) - # change when max drive is changed from 6 to 4 - if current_max_drive < 9 and current_max_drive != self.base_drive + amount_of_items: - self.kh2_write_byte(self.Slot1 + 0x1B2, self.base_drive + amount_of_items) - - elif item_name == ItemName.AccessorySlotUp: - current_accessory = self.kh2_read_byte(self.Save + 0x2501) - if current_accessory != self.base_accessory_slots + amount_of_items: - if 4 > current_accessory < self.base_accessory_slots + amount_of_items: - self.kh2_write_byte(self.Save + 0x2501, current_accessory + 1) - elif self.base_accessory_slots + amount_of_items < 4: - self.kh2_write_byte(self.Save + 0x2501, self.base_accessory_slots + amount_of_items) - - elif item_name == ItemName.ArmorSlotUp: - current_armor_slots = self.kh2_read_byte(self.Save + 0x2500) - if current_armor_slots != self.base_armor_slots + amount_of_items: - if 4 > current_armor_slots < self.base_armor_slots + amount_of_items: - self.kh2_write_byte(self.Save + 0x2500, current_armor_slots + 1) - elif self.base_armor_slots + amount_of_items < 4: - self.kh2_write_byte(self.Save + 0x2500, self.base_armor_slots + amount_of_items) - - elif item_name == ItemName.ItemSlotUp: - current_item_slots = self.kh2_read_byte(self.Save + 0x2502) - if current_item_slots != self.base_item_slots + amount_of_items: - if 8 > current_item_slots < self.base_item_slots + amount_of_items: - self.kh2_write_byte(self.Save + 0x2502, current_item_slots + 1) - elif self.base_item_slots + amount_of_items < 8: - self.kh2_write_byte(self.Save + 0x2502, self.base_item_slots + amount_of_items) - - # 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 get_addresses(self): - if not self.kh2connected and self.kh2 is not None: - if self.kh2_game_version is None: - # current verions is .10 then runs the get from github stuff - if self.kh2_read_string(0x9A98B0, 4) == "KH2J": - self.kh2_game_version = "STEAM" - self.Now = 0x0717008 - self.Save = 0x09A98B0 - self.Slot1 = 0x2A23598 - self.Journal = 0x7434E0 - self.Shop = 0x7435D0 - elif self.kh2_read_string(0x9A9330, 4) == "KH2J": - self.kh2_game_version = "EGS" - else: - if self.game_communication_path: - logger.info("Checking with most up to date addresses from the addresses json.") - #if mem addresses file is found then check version and if old get new one - kh2memaddresses_path = os.path.join(self.game_communication_path, "kh2memaddresses.json") - if not os.path.exists(kh2memaddresses_path): - logger.info("File is not found. Downloading json with memory addresses. This might take a moment") - mem_resp = requests.get("https://raw.githubusercontent.com/JaredWeakStrike/KH2APMemoryValues/master/kh2memaddresses.json") - if mem_resp.status_code == 200: - self.mem_json = json.loads(mem_resp.content) - with open(kh2memaddresses_path, 'w') as f: - f.write(json.dumps(self.mem_json, indent=4)) - else: - with open(kh2memaddresses_path) as f: - self.mem_json = json.load(f) - if self.mem_json: - for key in self.mem_json.keys(): - if self.kh2_read_string(int(self.mem_json[key]["GameVersionCheck"], 0), 4) == "KH2J": - self.Now = int(self.mem_json[key]["Now"], 0) - self.Save = int(self.mem_json[key]["Save"], 0) - self.Slot1 = int(self.mem_json[key]["Slot1"], 0) - self.Journal = int(self.mem_json[key]["Journal"], 0) - self.Shop = int(self.mem_json[key]["Shop"], 0) - self.kh2_game_version = key - - if self.kh2_game_version is not None: - logger.info(f"You are now auto-tracking {self.kh2_game_version}") - self.kh2connected = True - else: - logger.info("Your game version does not match what the client requires. Check in the " - "kingdom-hearts-2-final-mix channel for more information on correcting the game " - "version.") - self.kh2connected = False - - -def finishedGame(ctx: KH2Context): - if ctx.kh2slotdata['FinalXemnas'] == 1: - if not ctx.final_xemnas and ctx.kh2_read_byte( - ctx.Save + all_world_locations[LocationName.FinalXemnas].addrObtained) \ - & 0x1 << all_world_locations[LocationName.FinalXemnas].bitIndex > 0: - 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: - locations = ctx.sending - for boss in ctx.kh2slotdata["hitlist"]: - if boss in 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.serverconnected: - 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()) - if finishedGame(ctx) and not ctx.kh2_finished_game: - await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) - ctx.kh2_finished_game = True - if ctx.sending: - message = [{"cmd": 'LocationChecks', "locations": ctx.sending}] - await ctx.send_msgs(message) - elif not ctx.kh2connected and ctx.serverconnected: - logger.info("Game Connection lost. trying to reconnect.") - ctx.kh2 = None - while not ctx.kh2connected and ctx.serverconnected: - try: - ctx.kh2 = pymem.Pymem(process_name="KINGDOM HEARTS II FINAL MIX") - ctx.get_addresses() - logger.info("Game Connection Established.") - except Exception as e: - await asyncio.sleep(5) - if ctx.disconnect_from_server: - ctx.disconnect_from_server = False - await ctx.disconnect() - 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.just_fix_windows_console() - asyncio.run(main(args)) - colorama.deinit() +# this is just here for backwards compatibility so people can test. +# ClientStuff will be named Client in the fututre and will require KH2Client.py to use +# from worlds.kh2.Client.Client import launch +from .ClientStuff.Client import launch diff --git a/worlds/kh2/ClientStuff/CMDProcessor.py b/worlds/kh2/ClientStuff/CMDProcessor.py new file mode 100644 index 0000000000..70b2368372 --- /dev/null +++ b/worlds/kh2/ClientStuff/CMDProcessor.py @@ -0,0 +1,95 @@ +from CommonClient import ClientCommandProcessor +from typing import TYPE_CHECKING + +# I don't know what is going on here, but it works. +if TYPE_CHECKING: + from . import KH2Context +else: + KH2Context = object + + +class KH2CommandProcessor(ClientCommandProcessor): + ctx: KH2Context + + def _cmd_receive_notif(self, notification_type=""): + """Change receive notification type.Valid Inputs:Puzzle, Info, Chest and None + Puzzle: Puzzle Piece Popup when you receive an item. + Info: Displays the Information notification when you receive an item. + Chest: Displays the Chest notification when you receive an item. + None: Toggle off any of the receiving notifications. + """ + notification_type = notification_type.lower() + if notification_type in {"puzzle", "info", "chest", "none"}: + temp_client_settings = self.ctx.client_settings["receive_popup_type"] + self.ctx.client_settings["receive_popup_type"] = notification_type + self.output(f"Changed receive notification type from {temp_client_settings} to {self.ctx.client_settings['receive_popup_type']}") + else: + self.output(f"Unknown receive notification type:{notification_type}. Valid Inputs: Puzzle, Info, Chest, None") + + def _cmd_send_notif(self, notification_type=""): + """Change send notification type.Valid Inputs:Puzzle, Info, Chest and None + Puzzle: Puzzle Piece Popup when you send an item. + Info: Displays the Information notification when you send an item. + Chest: Displays the Chest notification when you send an item. + None: Toggle off any of the receiving notifications. + """ + notification_type = notification_type.lower() + if notification_type in {"puzzle", "info", "chest", "none"}: + temp_client_settings = self.ctx.client_settings["send_popup_type"] + self.ctx.client_settings["send_popup_type"] = notification_type + # doing it in this order to make sure it actually changes + self.output(f"Changed send notification type from {temp_client_settings} to {self.ctx.client_settings['send_popup_type']}") + else: + self.output(f"Unknown send notification type:{notification_type}. Valid Inputs: Puzzle, Info, Chest, None") + + def _cmd_change_send_truncation_priority(self, priority=""): + """Change what gets truncated first when using Chest or Puzzle piece send notification. Playername min is 5 and ItemName is 15""" + priority = priority.lower() + if priority in {"playername", "itemname"}: + temp_client_settings = self.ctx.client_settings["send_truncate_first"] + self.ctx.client_settings["send_truncate_first"] = priority + self.output(f"Changed receive notification type truncation from {temp_client_settings} to {self.ctx.client_settings['send_truncate_first']}") + else: + self.output(f"Unknown priority: {priority}. Valid Inputs: PlayerName, ItemName") + + def _cmd_change_receive_truncation_priority(self, priority=""): + """Change what gets truncated first when using Chest or Puzzle piece receive notification. Playername min is 5 and ItemName is 15""" + priority = priority.lower() + if priority in {"playername", "itemname"}: + temp_client_settings = self.ctx.client_settings["receive_truncate_first"] + self.ctx.client_settings["receive_truncate_first"] = priority + self.output(f"Changed receive notification truncation type from {temp_client_settings} to {self.ctx.client_settings['receive_truncate_first']}") + else: + self.output(f"Unknown priority: {priority}. Valid Inputs: PlayerName, ItemName") + + def _cmd_deathlink(self): + """Toggles Deathlink""" + if self.ctx.deathlink_toggle: + # self.ctx.tags.add("DeathLink") + self.ctx.deathlink_toggle = False + self.output(f"Death Link turned off") + else: + self.ctx.deathlink_toggle = True + self.output(f"Death Link turned on") + + def _cmd_add_to_blacklist(self, player_name: str = ""): + """Adds player to deathlink blacklist""" + if player_name not in self.ctx.deathlink_blacklist: + self.ctx.deathlink_blacklist.append(player_name) + + def _cmd_remove_from_blacklist(self, player_name: str = ""): + """Removes player from the deathlink blacklist""" + if player_name in self.ctx.deathlink_blacklist: + self.ctx.deathlink_blacklist.remove(player_name) + + #def _cmd_kill(self): + # self.ctx.kh2_write_byte(0x810000, 1) + + #def _cmd_chest(self,itemid:int): + # from .RecieveItems import to_khscii + # from .ReadAndWrite import kh2_write_bytes,kh2_write_byte + # displayed_string = to_khscii(self.ctx,"Yessir") +# + # kh2_write_byte(self.ctx, 0x800150, int(itemid)) + # kh2_write_bytes(self.ctx, address = 0x800154,value = displayed_string) + # kh2_write_byte(self.ctx, 0x800000, 3) diff --git a/worlds/kh2/ClientStuff/Client.py b/worlds/kh2/ClientStuff/Client.py new file mode 100644 index 0000000000..4b4afd8c16 --- /dev/null +++ b/worlds/kh2/ClientStuff/Client.py @@ -0,0 +1,754 @@ +from __future__ import annotations +import ModuleUpdate +import Utils + +ModuleUpdate.update() +import os +import asyncio +import json +import requests + +from pymem import pymem +from worlds.kh2 import item_dictionary_table, exclusion_item_table, CheckDupingItems, all_locations, exclusion_table, \ + SupportAbility_Table, ActionAbility_Table, all_weapon_slot +from worlds.kh2.Names import ItemName +from .WorldLocations import * + +from NetUtils import ClientStatus, NetworkItem +from CommonClient import gui_enabled, logger, get_base_parser, CommonContext, server_loop +from .CMDProcessor import KH2CommandProcessor +from .SendChecks import finishedGame + + +class KH2Context(CommonContext): + command_processor = 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.kh2_finished_game = False + self.serverconnected = 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_data_package = {} + 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.slot_name = None + self.disconnect_from_server = False + self.sending = [] + # queue for the strings to display on the screen + self.queued_puzzle_popup = [] + self.queued_info_popup = [] + self.queued_chest_popup = [] + + # special characters for printing in game + # A dictionary of all the special characters, which + # are hard to convert through a mathematical formula. + self.special_dict = { + ' ': 0x01, '\n': 0x02, '-': 0x54, '!': 0x48, '?': 0x49, '%': 0x4A, '/': 0x4B, + '.': 0x4F, ',': 0x50, ';': 0x51, ':': 0x52, '\'': 0x57, '(': 0x5A, ')': 0x5B, + '[': 0x62, ']': 0x63, 'à': 0xB7, 'á': 0xB8, 'â': 0xB9, 'ä': 0xBA, 'è': 0xBB, + 'é': 0xBC, 'ê': 0xBD, 'ë': 0xBE, 'ì': 0xBF, 'í': 0xC0, 'î': 0xC1, 'ï': 0xC2, + 'ñ': 0xC3, 'ò': 0xC4, 'ó': 0xC5, 'ô': 0xC6, 'ö': 0xC7, 'ù': 0xC8, 'ú': 0xC9, + 'û': 0xCA, 'ü': 0xCB, 'ç': 0xE8, 'À': 0xD0, 'Á': 0xD1, 'Â': 0xD2, 'Ä': 0xD3, + 'È': 0xD4, 'É': 0xD5, 'Ê': 0xD6, 'Ë': 0xD7, 'Ì': 0xD8, 'Í': 0xD9, 'Î': 0xDA, + 'Ï': 0xDB, 'Ñ': 0xDC, 'Ò': 0xDD, 'Ó': 0xDE, 'Ô': 0xDF, 'Ö': 0xE0, 'Ù': 0xE1, + 'Ú': 0xE2, 'Û': 0xE3, 'Ü': 0xE4, '¡': 0xE5, '¿': 0xE6, 'Ç': 0xE7 + } + + # 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": {}, # ItemName: Amount + "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.kh2seedname = None + self.kh2_seed_save_path_join = None + + self.kh2slotdata = None + self.mem_json = None + self.itemamount = {} + self.client_settings = { + "send_truncate_first": "playername", # there is no need to truncate item names for info popup + "receive_truncate_first": "playername", # truncation order. Can be PlayerName or ItemName + "send_popup_type": "chest", # type of popup when you receive an item + "receive_popup_type": "chest", # can be Puzzle, Info, Chest or None + } + + if "localappdata" in os.environ: + self.game_communication_path = os.path.expandvars(r"%localappdata%\KH2AP") + self.kh2_client_settings = f"kh2_client_settings.json" + self.kh2_client_settings_join = os.path.join(self.game_communication_path, self.kh2_client_settings) + if not os.path.exists(self.game_communication_path): + os.makedirs(self.game_communication_path) + if not os.path.exists(self.kh2_client_settings_join): + # make the json with the settings + with open(self.kh2_client_settings_join, "wt") as f: + f.close() + elif os.path.exists(self.kh2_client_settings_join): + with open(self.kh2_client_settings_join) as f: + # if the file isnt empty load it + # this is the best I could fine to valid json stuff https://stackoverflow.com/questions/23344948/validate-and-format-json-files + try: + self.kh2_seed_save = json.load(f) + except json.decoder.JSONDecodeError: + pass + # this is what is effectively doing on + # self.client_settings = default + f.close() + + 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 + } + #Sora,Donald and Goofy are always in your party + self.WorldIDtoParty = { + 4: "Beast", + 6: "Auron", + 7: "Aladdin", + 8: "Mulan", + 10: "Simba", + 14: "Jack Skellington", + 16: "Jack Sparrow", + 17: "Tron", + 18: "Riku" + } + self.last_world_int = -1 + # PC Address anchors + # epic .10 addresses + self.Now = 0x0716DF8 + self.Save = 0x9A9330 + self.Journal = 0x743260 + self.Shop = 0x743350 + self.Slot1 = 0x2A23018 + self.InfoBarPointer = 0xABE2A8 + self.isDead = 0x0BEEF28 + + self.FadeStatus = 0xABAF38 + self.PlayerGaugePointer = 0x0ABCCC8 + + self.kh2_game_version = None # can be egs or steam + + self.kh2_seed_save_path = None + + 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.CharacterAnchors = { + "Sora": 0x24F0, + "Donald": 0x2604, + "Goofy": 0x2718, + "Auron": 0x2940, + "Mulan": 0x2A54, + "Aladdin": 0x2B68, + "Jack Sparrow": 0x2C7C, + "Beast": 0x2D90, + "Jack Skellington": 0x2EA4, + "Simba": 0x2FB8, + "Tron": 0x30CC, + "Riku": 0x31E0 + } + self.Equipment_Anchor_Dict = { + #Sora, Donald, Goofy in that order + # each slot is a short, Sora Anchor:0x24F0, Donald Anchor: 0x2604, Goofy Anchor: 0x2718 + # Each of these has 8 slots that could have them no matter how many slots are unlocked + # If Ability Ring on slot 5 of sora + # ReadShort(Save+CharacterAnchors["Sora"]+Equiptment_Anchor["Accessories][4 (index 5)]) == self.item_name_to_data[item_name].memaddr + "Armor": [0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E, 0x20, 0x22], + + "Accessories": [0x24, 0x26, 0x28, 0x2A, 0x2C, 0x2E, 0x30, 0x32] + } + + 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"} + + self.base_hp = 20 + self.base_mp = 100 + self.base_drive = 5 + self.base_accessory_slots = 1 + self.base_armor_slots = 1 + self.base_item_slots = 3 + self.front_ability_slots = [0x2546, 0x2658, 0x276C, 0x2548, 0x254A, 0x254C, 0x265A, 0x265C, 0x265E, 0x276E, + 0x2770, 0x2772] + + self.deathlink_toggle = False + self.deathlink_blacklist = [] + + from .ReadAndWrite import kh2_read_longlong, kh2_read_int, kh2_read_string, kh2_read_byte, kh2_write_bytes, kh2_write_int, kh2_write_short, kh2_write_byte, kh2_read_short, kh2_return_base_address + from .SendChecks import checkWorldLocations, checkSlots, checkLevels, verifyChests, verifyLevel + from .RecieveItems import displayPuzzlePieceTextinGame, displayInfoTextinGame, displayChestTextInGame, verifyItems, give_item, IsInShop, to_khscii + + 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() + # if slot name != first time login or previous name + # and seed name is none or saved seed name + if not self.slot_name and not self.kh2seedname: + await self.send_connect() + elif self.slot_name == self.auth and self.kh2seedname: + await self.send_connect() + else: + logger.info(f"You are trying to connect with data still cached in the client. Close client or connect to the correct slot: {self.slot_name}") + self.serverconnected = False + self.disconnect_from_server = True + + # to not softlock the client when you connect to the wrong slot/game + def event_invalid_slot(self): + self.kh2seedname = None + CommonContext.event_invalid_slot(self) + + def event_invalid_game(self): + self.kh2seedname = None + CommonContext.event_invalid_slot(self) + + async def connection_closed(self): + self.kh2connected = False + self.serverconnected = False + if self.kh2seedname is not None and self.auth is not None: + with open(self.kh2_seed_save_path_join, 'w') as f: + f.write(json.dumps(self.kh2_seed_save, indent=4)) + f.close() + await super(KH2Context, self).connection_closed() + + async def disconnect(self, allow_autoreconnect: bool = False): + self.kh2connected = False + self.serverconnected = False + self.locations_checked = [] + if self.kh2seedname not in {None} and self.auth not in {None}: + with open(self.kh2_seed_save_path_join, 'w') as f: + f.write(json.dumps(self.kh2_seed_save, indent=4)) + f.close() + 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(self.kh2_seed_save_path_join, 'w') as f: + f.write(json.dumps(self.kh2_seed_save, indent=4)) + f.close() + with open(self.kh2_client_settings_join, 'w') as f2: + f2.write(json.dumps(self.client_settings, indent=4)) + f2.close() + await super(KH2Context, self).shutdown() + + def on_package(self, cmd: str, args: dict): + if cmd == "RoomInfo": + if not self.kh2seedname: + self.kh2seedname = args['seed_name'] + elif self.kh2seedname != args['seed_name']: + self.disconnect_from_server = True + self.serverconnected = False + self.kh2connected = False + logger.info("Connection to the wrong seed, connect to the correct seed or close the client.") + return + self.kh2_seed_save_path = f"kh2save2{self.kh2seedname}{self.auth}.json" + self.kh2_seed_save_path_join = os.path.join(self.game_communication_path, Utils.get_file_safe_name(self.kh2_seed_save_path)) + + if not os.path.exists(self.kh2_seed_save_path_join): + self.kh2_seed_save = { + "Levels": { + "SoraLevel": 0, + "ValorLevel": 0, + "WisdomLevel": 0, + "LimitLevel": 0, + "MasterLevel": 0, + "FinalLevel": 0, + "SummonLevel": 0, + }, + # Item: Amount of them sold + "SoldEquipment": dict(), + } + with open(self.kh2_seed_save_path_join, 'wt') as f: + f.close() + elif os.path.exists(self.kh2_seed_save_path_join): + with open(self.kh2_seed_save_path_join) as f: + try: + self.kh2_seed_save = json.load(f) + except json.decoder.JSONDecodeError: + self.kh2_seed_save = None + if self.kh2_seed_save is None or self.kh2_seed_save == {}: + self.kh2_seed_save = { + "Levels": { + "SoraLevel": 0, + "ValorLevel": 0, + "WisdomLevel": 0, + "LimitLevel": 0, + "MasterLevel": 0, + "FinalLevel": 0, + "SummonLevel": 0, + }, + # Item: Amount of them sold + "SoldEquipment": dict(), + } + f.close() + + if cmd == "Connected": + self.kh2slotdata = args['slot_data'] + + self.kh2_data_package = Utils.load_data_package_for_checksum( + "Kingdom Hearts 2", self.checksums["Kingdom Hearts 2"]) + + if "location_name_to_id" in self.kh2_data_package: + self.data_package_kh2_cache( + self.kh2_data_package["location_name_to_id"], self.kh2_data_package["item_name_to_id"]) + self.connect_to_game() + else: + asyncio.create_task(self.send_msgs([{"cmd": "GetDataPackage", "games": ["Kingdom Hearts 2"]}])) + + self.locations_checked = set(args["checked_locations"]) + + if cmd == "ReceivedItems": + # Sora Front of Ability List:0x2546 + # Donald Front of Ability List:0x2658 + # Goofy Front of Ability List: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": {}, # ItemName: Amount + "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.serverconnected: + self.kh2_seed_save_cache["itemIndex"] = start_index + for item in args['items']: + networkItem = NetworkItem(*item) + + # actually give player the item + asyncio.create_task(self.give_item(networkItem.item, networkItem.location)) + + if cmd == "RoomUpdate": + if "checked_locations" in args: + new_locations = set(args["checked_locations"]) + self.locations_checked |= new_locations + + if cmd == "DataPackage": + if "Kingdom Hearts 2" in args["data"]["games"]: + self.data_package_kh2_cache( + args["data"]["games"]["Kingdom Hearts 2"]["location_name_to_id"], + args["data"]["games"]["Kingdom Hearts 2"]["item_name_to_id"]) + self.connect_to_game() + asyncio.create_task(self.send_msgs([{'cmd': 'Sync'}])) + + if cmd == "PrintJSON": + # shamelessly stolen from kh1 + if args.get("type") == "ItemSend": + item = args["item"] + networkItem = NetworkItem(*item) + itemId = networkItem.item + receiverID = args["receiving"] + senderID = networkItem.player + receive_popup_type = self.client_settings["receive_popup_type"].lower() + send_popup_type = self.client_settings["send_popup_type"].lower() + receive_truncate_first = self.client_settings["receive_truncate_first"].lower() + send_truncate_first = self.client_settings["send_truncate_first"].lower() + # checking if sender is the kh2 player, and you aren't sending yourself the item + if receiverID == self.slot and senderID != self.slot: # item is sent to you and is not from yourself + itemName = self.item_names.lookup_in_game(itemId) + playerName = self.player_names[networkItem.player] # player that sent you the item + totalLength = len(itemName) + len(playerName) + + if receive_popup_type == "info": # no restrictions on size here + temp_length = f"Obtained {itemName} from {playerName}" + if totalLength > 90: + self.queued_info_popup += [temp_length[:90]] # slice it to be 90 + else: + self.queued_info_popup += [temp_length] + else: # either chest or puzzle. they are handled the same length wise + totalLength = len(itemName) + len(playerName) + while totalLength > 25: + if receive_truncate_first == "playername": + if len(playerName) > 5: + playerName = playerName[:-1] + else: + itemName = itemName[:-1] + else: + if len(ItemName) > 15: + itemName = itemName[:-1] + else: + playerName = playerName[:-1] + totalLength = len(itemName) + len(playerName) + # from =6. totalLength of the string cant be over 31 or game crash + if receive_popup_type == "puzzle": # sanitize ItemName and receiver name + self.queued_puzzle_popup += [f"{itemName} from {playerName}"] + else: + self.queued_chest_popup += [f"{itemName} from {playerName}"] + + if receiverID != self.slot and senderID == self.slot: #item is sent to other players + itemName = self.item_names.lookup_in_slot(itemId, receiverID) + playerName = self.player_names[receiverID] + totalLength = len(itemName) + len(playerName) + if send_popup_type == "info": + if totalLength > 90: + temp_length = f"Sent {itemName} to {playerName}" + self.queued_info_popup += [temp_length[:90]] #slice it to be 90 + else: + self.queued_info_popup += [f"Sent {itemName} to {playerName}"] + else: # else chest or puzzle. they are handled the same length wise + while totalLength > 27: + if send_truncate_first == "playername": + if len(playerName) > 5: #limit player name to at least be 5 characters + playerName = playerName[:-1] + else: + itemName = itemName[:-1] + else: + if len(ItemName) > 15: # limit item name to at least be 15 characters + itemName = itemName[:-1] + else: + playerName = playerName[:-1] + totalLength = len(itemName) + len(playerName) + if send_popup_type == "puzzle": + # to = 4 totalLength of the string cant be over 31 or game crash + self.queued_puzzle_popup += [f"{itemName} to {playerName}"] + else: + self.queued_chest_popup += [f"{itemName} to {playerName}"] + + def connect_to_game(self): + if "KeybladeAbilities" in self.kh2slotdata.keys(): + # sora ability to slot + self.AbilityQuantityDict.update(self.kh2slotdata["KeybladeAbilities"]) + # itemid:[slots that are available for that item] + self.AbilityQuantityDict.update(self.kh2slotdata["StaffAbilities"]) + self.AbilityQuantityDict.update(self.kh2slotdata["ShieldAbilities"]) + + self.all_weapon_location_id = {self.kh2_loc_name_to_id[loc] for loc in all_weapon_slot} + + try: + if not self.kh2: + self.kh2 = pymem.Pymem(process_name="KINGDOM HEARTS II FINAL MIX") + self.get_addresses() + + except Exception as e: + if self.kh2connected: + self.kh2connected = False + logger.info("Game is not open. If it is open run the launcher/client as admin.") + self.serverconnected = True + self.slot_name = self.auth + + def data_package_kh2_cache(self, loc_to_id, item_to_id): + self.kh2_loc_name_to_id = loc_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 = item_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"]] + + def on_deathlink(self, data: typing.Dict[str, typing.Any]) -> None: + """Gets dispatched when a new DeathLink is triggered by another linked player.""" + if data["source"] not in self.deathlink_blacklist: + self.last_death_link = max(data["time"], self.last_death_link) + text = data.get("cause", "") + if text: + logger.info(f"DeathLink: {text}") + else: + logger.info(f"DeathLink: Received from {data['source']}") + # kills sora by setting flag for the lua to read + self.kh2_write_byte(0x810000, 1) + + async def is_dead(self): + # General Death link logic: if hp is 0 and sora has 5 drive gauge and deathlink flag isnt set + # if deathlink is on and script is hasnt killed sora and sora isnt dead + if self.deathlink_toggle and self.kh2_read_byte(0x810000) == 0 and self.kh2_read_byte(0x810001) != 0: + # set deathlink flag so it doesn't send out bunch + # basically making the game think it got its death from a deathlink instead of from the game + self.kh2_write_byte(0x810000, 0) + # 0x810001 is set to 1 when you die via the goa script. This is done because the polling rate for the client can miss a death + # but the lua script runs eveery frame so we cant miss them now + self.kh2_write_byte(0x810001, 0) + #todo: read these from the goa lua instead since the deathlink is after they contiune which means that its just before they would've gotten into the fight + Room = self.kh2_read_byte(0x810002) + Event = self.kh2_read_byte(0x810003) + World = self.kh2_read_byte(0x810004) + if (World, Room, Event) in DeathLinkPair.keys(): + + logger.info(f"Deathlink: {self.player_names[self.slot]} died to {DeathLinkPair[(World,Room, Event)]}.") + await self.send_death(death_text=f"{self.player_names[self.slot]} died to {DeathLinkPair[(World,Room, Event)]}.") + else: + logger.info(f"Deathlink: {self.player_names[self.slot]} lost their heart to darkness.") + await self.send_death(death_text=f"{self.player_names[self.slot]} lost their heart to darkness.") + + 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") + + def get_addresses(self): + if not self.kh2connected and self.kh2 is not None: + if self.kh2_game_version is None: + # current verions is .10 then runs the get from github stuff + if self.kh2_read_string(0x9A98B0, 4) == "KH2J": + self.kh2_game_version = "STEAM" + self.Now = 0x0717008 + self.Save = 0x09A98B0 + self.Slot1 = 0x2A23598 + self.Journal = 0x7434E0 + self.Shop = 0x7435D0 + self.InfoBarPointer = 0xABE828 + self.isDead = 0x0BEF4A8 + self.FadeStatus = 0xABB4B8 + self.PlayerGaugePointer = 0x0ABD248 + elif self.kh2_read_string(0x9A9330, 4) == "KH2J": + self.kh2_game_version = "EGS" + else: + if self.game_communication_path: + logger.info("Checking with most up to date addresses from the addresses json.") + # if mem addresses file is found then check version and if old get new one + kh2memaddresses_path = os.path.join(self.game_communication_path, "kh2memaddresses.json") + if not os.path.exists(kh2memaddresses_path): + logger.info("File is not found. Downloading json with memory addresses. This might take a moment") + mem_resp = requests.get("https://raw.githubusercontent.com/JaredWeakStrike/KH2APMemoryValues/master/kh2memaddresses.json") + if mem_resp.status_code == 200: + self.mem_json = json.loads(mem_resp.content) + with open(kh2memaddresses_path, 'w') as f: + f.write(json.dumps(self.mem_json, indent=4)) + f.close() + else: + with open(kh2memaddresses_path) as f: + self.mem_json = json.load(f) + f.close() + if self.mem_json: + for key in self.mem_json.keys(): + if self.kh2_read_string(int(self.mem_json[key]["GameVersionCheck"], 0), 4) == "KH2J": + self.Now = int(self.mem_json[key]["Now"], 0) + self.Save = int(self.mem_json[key]["Save"], 0) + self.Slot1 = int(self.mem_json[key]["Slot1"], 0) + self.Journal = int(self.mem_json[key]["Journal"], 0) + self.Shop = int(self.mem_json[key]["Shop"], 0) + self.InfoBarPointer = int(self.mem_json[key]["InfoBarPointer"], 0) + self.isDead = int(self.mem_json[key]["isDead"], 0) + self.FadeStatus = int(self.mem_json[key]["FadeStatus"], 0) + self.PlayerGaugePointer = int(self.mem_json[key]["PlayerGaugePointer"], 0) + self.kh2_game_version = key + + if self.kh2_game_version is not None: + logger.info(f"You are now auto-tracking {self.kh2_game_version}") + self.kh2connected = True + else: + logger.info("Your game version does not match what the client requires. Check in the " + "kingdom-hearts-2-final-mix channel for more information on correcting the game " + "version.") + self.kh2connected = False + + +async def kh2_watcher(ctx: KH2Context): + while not ctx.exit_event.is_set(): + try: + if ctx.kh2connected and ctx.serverconnected: + 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()) + await asyncio.create_task(ctx.is_dead()) + + if (ctx.deathlink_toggle and "DeathLink" not in ctx.tags) or (not ctx.deathlink_toggle and "DeathLink" in ctx.tags): + await ctx.update_death_link(ctx.deathlink_toggle) + + if finishedGame(ctx) and not ctx.kh2_finished_game: + await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) + ctx.kh2_finished_game = True + + if ctx.sending: + message = [{"cmd": 'LocationChecks', "locations": ctx.sending}] + await ctx.send_msgs(message) + + if ctx.queued_puzzle_popup: + await asyncio.create_task(ctx.displayPuzzlePieceTextinGame(ctx.queued_puzzle_popup[0])) # send the num 1 index of whats in the queue + if ctx.queued_info_popup: + await asyncio.create_task(ctx.displayInfoTextinGame(ctx.queued_info_popup[0])) + if ctx.queued_chest_popup: + await asyncio.create_task(ctx.displayChestTextInGame(ctx.queued_chest_popup[0])) + + elif not ctx.kh2connected and ctx.serverconnected: + logger.info("Game Connection lost. trying to reconnect.") + ctx.kh2 = None + #todo: change this to be an option for the client to auto reconnect with the default being yes + # reason is because the await sleep causes the client to hang if you close the game then the client without disconnecting. + while not ctx.kh2connected and ctx.serverconnected: + try: + ctx.kh2 = pymem.Pymem(process_name="KINGDOM HEARTS II FINAL MIX") + ctx.get_addresses() + logger.info("Game Connection Established.") + except Exception as e: + await asyncio.sleep(5) + if ctx.disconnect_from_server: + ctx.disconnect_from_server = False + await ctx.disconnect() + 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() diff --git a/worlds/kh2/ClientStuff/ReadAndWrite.py b/worlds/kh2/ClientStuff/ReadAndWrite.py new file mode 100644 index 0000000000..c6dd6b46eb --- /dev/null +++ b/worlds/kh2/ClientStuff/ReadAndWrite.py @@ -0,0 +1,47 @@ +# All the write functions return a bool for has written it but there isnt a use case for that I've found +def kh2_read_short(self, address) -> int: + """Reads 2 bytes""" + return self.kh2.read_short(self.kh2.base_address + address) + + +def kh2_write_short(self, address, value) -> None: + """Writes 2 bytes""" + self.kh2.write_short(self.kh2.base_address + address, value) + + +def kh2_write_byte(self, address, value): + """Writes 1 byte""" + return self.kh2.write_bytes(self.kh2.base_address + address, value.to_bytes(1, 'big'), 1) + + +def kh2_read_byte(self, address): + """Reads 1 byte""" + return int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + address, 1)) + + +def kh2_read_int(self, address): + """Reads 4 bytes""" + return self.kh2.read_int(self.kh2.base_address + address) + + +def kh2_write_int(self, address, value): + """Writes 4 bytes""" + self.kh2.write_int(self.kh2.base_address + address, value) + + +def kh2_read_longlong(self, address): + """Reads 8 bytes""" + return self.kh2.read_longlong(self.kh2.base_address + address) + + +def kh2_read_string(self, address, length): + """Reads length amount of bytes""" + return self.kh2.read_string(self.kh2.base_address + address, length) + + +def kh2_write_bytes(self, address, value): + return self.kh2.write_bytes(self.kh2.base_address + address, bytes(value), len(value)) + + +def kh2_return_base_address(self): + return self.kh2.base_address diff --git a/worlds/kh2/ClientStuff/RecieveItems.py b/worlds/kh2/ClientStuff/RecieveItems.py new file mode 100644 index 0000000000..e232f262e8 --- /dev/null +++ b/worlds/kh2/ClientStuff/RecieveItems.py @@ -0,0 +1,434 @@ +from CommonClient import logger +from typing import TYPE_CHECKING +from .WorldLocations import * +from ..Names import ItemName +import re +import asyncio + + +def to_khscii(self, item_name): + # credit to TopazTK for this. + out_list = [] + char_count = 0 + # Throughout the text, do: + while char_count < len(item_name): + char = item_name[char_count] + # Simple character conversion through mathematics. + if 'a' <= char <= 'z': + out_list.append(ord(char) + 0x39) + char_count += 1 + elif 'A' <= char <= 'Z': + out_list.append(ord(char) - 0x13) + char_count += 1 + elif '0' <= char <= '9': + out_list.append(ord(char) + 0x60) + char_count += 1 + # If it hits a "{", we will know it's a command, not a character. + elif char == '{': + # A command is 6 characters long, in the format of "{0xTT}", + # with the "TT" being the 2-digit encode for that command. + command = item_name[char_count:char_count + 6] + if re.match(r'^{0x[a-fA-F0-9][a-fA-F0-9]}$', command): + value = command[1:5] + out_list.append(int(value, 16)) + char_count += 6 + # Should it be anything we do not know, we look through + # the special dictionary. + else: + if char in self.special_dict: + out_list.append(self.special_dict[char]) + else: + out_list.append(0x01) + char_count += 1 + + # When the list ends, we add a terminator and return the string. + out_list.append(0x00) + return out_list + + +async def give_item(self, item, location): + try: + #sleep so we can get the datapackage and not miss any items that were sent to us while we didnt have our item id dicts + while not self.lookup_id_to_item: + await asyncio.sleep(0.5) + 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 + # appending the slot that the ability should be in + # abilities have a limit amount of slots. + # we start from the back going down to not mess with stuff. + # Front of Invo + # Sora: Save+24F0+0x54 : 0x2546 + # Donald: Save+2604+0x54 : 0x2658 + # Goofy: Save+2718+0x54 : 0x276C + # Back of Invo. Sora has 6 ability slots that are reserved + # Sora: Save+24F0+0x54+0x92 : 0x25D8 + # Donald: Save+2604+0x54+0x9C : 0x26F4 + # Goofy: Save+2718+0x54+0x9C : 0x2808 + # seed has 2 scans in sora's abilities + # recieved second scan + # if len(seed_save(Scan:[ability slot 52]) < (2)amount of that ability they should have from slot data + # ability_slot = back of inventory that isnt taken + # add ability_slot to seed_save(Scan[]) so now its Scan:[ability slot 52,50] + # decrease back of inventory since its ability_slot is already taken + if 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 + else: + 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 + + if ability_slot in self.front_ability_slots: + self.front_ability_slots.remove(ability_slot) + # if itemdata in {bitmask} all the forms,summons and a few other things are bitmasks + 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) + + # if itemdata in {magic} + 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 + # equipment is a list instead of dict because you can only have 1 currently + elif itemname in self.all_equipment: + if itemname in self.kh2_seed_save_cache["AmountInvo"]["Equipment"]: + self.kh2_seed_save_cache["AmountInvo"]["Equipment"][itemname] += 1 + else: + self.kh2_seed_save_cache["AmountInvo"]["Equipment"][itemname] = 1 + # weapons are done differently since you can only have one and has to check it differently + 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: + # "normal" items. They have a unique byte reserved for how many they have + 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") + + +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(self.Journal) + shop = self.kh2_read_short(self.Shop) + if (journal == -1 and shop == 5) or (journal != -1 and shop == 10): + # print("your in the shop") + sellable_dict = {} + # for item that the player has received that can be sold + # get amount of sellable items PRE selling them + # basically a snapshot of what the amounts of these items are pre shopping + for itemName in sellable: + itemdata = self.item_name_to_data[itemName] + amount = self.kh2_read_byte(self.Save + itemdata.memaddr) + sellable_dict[itemName] = amount + # wait until user exits the shop + while (journal == -1 and shop == 5) or (journal != -1 and shop == 10): + journal = self.kh2_read_short(self.Journal) + shop = self.kh2_read_short(self.Shop) + await asyncio.sleep(0.5) + # for item and amount pre shop + for item, amount in sellable_dict.items(): + itemdata = self.item_name_to_data[item] + afterShop = self.kh2_read_byte(self.Save + itemdata.memaddr) + alreadySold = 0 + if item in self.kh2_seed_save["SoldEquipment"]: + alreadySold = self.kh2_seed_save["SoldEquipment"][item] + # if afterShop is < Amount post shop i.e if someone sold something + if afterShop < amount: + self.kh2_seed_save["SoldEquipment"][item] = (amount - afterShop) + alreadySold + + +async def verifyItems(self): + try: + # All these sets include items that the player has recieved + # set of all the items that have an + master_amount = list(self.kh2_seed_save_cache["AmountInvo"]["Amount"].keys()) + # set of all the items that have are abilities + master_ability = list(self.kh2_seed_save_cache["AmountInvo"]["Ability"].keys()) + # set of all the items that are bitmasks + master_bitmask = list(self.kh2_seed_save_cache["AmountInvo"]["Bitmask"]) + # sets of all the weapons. These are different because have to check inventory and equipped + master_keyblade = list(self.kh2_seed_save_cache["AmountInvo"]["Weapon"]["Sora"]) + master_staff = list(self.kh2_seed_save_cache["AmountInvo"]["Weapon"]["Donald"]) + master_shield = list(self.kh2_seed_save_cache["AmountInvo"]["Weapon"]["Goofy"]) + # Same with weapons but a lot more slots + master_equipment = list(self.kh2_seed_save_cache["AmountInvo"]["Equipment"].keys()) + # Set of magic, Can only be given when the player is paused due to crashing in loadzones + master_magic = list(self.kh2_seed_save_cache["AmountInvo"]["Magic"].keys()) + # Have to apply them to the slot that sora is in and a lot more dynamic than other amount items + master_stat = list(self.kh2_seed_save_cache["AmountInvo"]["StatIncrease"].keys()) + # Set of all things that could be sold + master_sell = master_equipment + master_staff + master_shield + + await asyncio.create_task(self.IsInShop(master_sell)) + # print(self.kh2_seed_save_cache["AmountInvo"]["Ability"]) + 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 + # 255 is the max limit for a byte + if amount_of_items > 255: + amount_of_items = 255 + 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 + # Checking Normal Sora,Valor Form,Master Form and Final Forms 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 ability in self.front_ability_slots: + if self.kh2_read_short(self.Save + ability) != 0: + print(f"removed {self.Save + ability} from {ability}") + self.kh2_write_short(self.Save + ability, 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] + amount_found_in_slots = 0 + 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 partyMember in ["Sora", "Donald", "Goofy"]: + for SlotOffset in Equipment_Anchor_List: + if self.kh2_read_short(self.Save + self.CharacterAnchors[partyMember] + SlotOffset) == item_data.kh2id: + amount_found_in_slots += 1 + if item_name in self.kh2_seed_save["SoldEquipment"]: + amount_found_in_slots += self.kh2_seed_save["SoldEquipment"][item_name] + inInventory = self.kh2_seed_save_cache["AmountInvo"]["Equipment"][item_name] - amount_found_in_slots + if inInventory != self.kh2_read_byte(self.Save + item_data.memaddr): + self.kh2_write_byte(self.Save + item_data.memaddr, inInventory) + + 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] + # - base address because it reads a pointer then in stead of reading what it points to its pointer+baseaddress which offsets + if self.kh2_read_byte(self.Save + item_data.memaddr) != amount_of_items and self.kh2_read_byte(self.FadeStatus) == 0 \ + and self.kh2_read_longlong(self.PlayerGaugePointer) != 0 \ + and self.kh2_read_int(self.kh2_read_longlong(self.PlayerGaugePointer) + 0x88 - self.kh2_return_base_address()) != 0: + self.kh2_write_byte(self.Save + item_data.memaddr, amount_of_items) + + for item_name in master_stat: + amount_of_items = 0 + amount_of_items += self.kh2_seed_save_cache["AmountInvo"]["StatIncrease"][item_name] + # checking if they talked to the computer to give them these + if self.kh2_read_byte(self.Slot1 + 0x1B2) >= 5 and (self.kh2_read_byte(self.Save + 0x1D27) & 0x1 << 3) > 0: + if item_name == ItemName.MaxHPUp: + if self.kh2_read_byte(self.Save + 0x2498) < 3: # Non-Critical + Bonus = 5 + else: # Critical + Bonus = 2 + if self.kh2_read_int(self.Slot1 + 0x004) != self.base_hp + (Bonus * amount_of_items): + self.kh2_write_int(self.Slot1 + 0x004, self.base_hp + (Bonus * amount_of_items)) + + elif item_name == ItemName.MaxMPUp: + if self.kh2_read_byte(self.Save + 0x2498) < 3: # Non-Critical + Bonus = 10 + else: # Critical + Bonus = 5 + if self.kh2_read_int(self.Slot1 + 0x184) != self.base_mp + (Bonus * amount_of_items): + self.kh2_write_int(self.Slot1 + 0x184, self.base_mp + (Bonus * amount_of_items)) + + elif item_name == ItemName.DriveGaugeUp: + current_max_drive = self.kh2_read_byte(self.Slot1 + 0x1B2) + # change when max drive is changed from 6 to 4 + # drive is maxed at 9 and base_drive is always 5 so if amount is higher set to 4 which is the max it should be + amount_of_items = min(amount_of_items, 4) + if current_max_drive < 9 and current_max_drive != self.base_drive + amount_of_items: + self.kh2_write_byte(self.Slot1 + 0x1B2, self.base_drive + amount_of_items) + # need to do these differently when the amount is dynamic + elif item_name == ItemName.AccessorySlotUp: + current_accessory = self.kh2_read_byte(self.Save + 0x2501) + if current_accessory != self.base_accessory_slots + amount_of_items: + if 4 > current_accessory < self.base_accessory_slots + amount_of_items: + self.kh2_write_byte(self.Save + 0x2501, current_accessory + 1) + elif self.base_accessory_slots + amount_of_items < 4: + self.kh2_write_byte(self.Save + 0x2501, self.base_accessory_slots + amount_of_items) + + elif item_name == ItemName.ArmorSlotUp: + current_armor_slots = self.kh2_read_byte(self.Save + 0x2500) + if current_armor_slots != self.base_armor_slots + amount_of_items: + if 4 > current_armor_slots < self.base_armor_slots + amount_of_items: + self.kh2_write_byte(self.Save + 0x2500, current_armor_slots + 1) + elif self.base_armor_slots + amount_of_items < 4: + self.kh2_write_byte(self.Save + 0x2500, self.base_armor_slots + amount_of_items) + + elif item_name == ItemName.ItemSlotUp: + current_item_slots = self.kh2_read_byte(self.Save + 0x2502) + if current_item_slots != self.base_item_slots + amount_of_items: + if 8 > current_item_slots < self.base_item_slots + amount_of_items: + self.kh2_write_byte(self.Save + 0x2502, current_item_slots + 1) + elif self.base_item_slots + amount_of_items < 8: + self.kh2_write_byte(self.Save + 0x2502, self.base_item_slots + amount_of_items) + + # 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") + + +async def displayInfoTextinGame(self, string_to_display): + infoBarPointerRef = self.kh2_read_longlong(self.InfoBarPointer) + if self.kh2_read_byte(0x800000) == 0 and infoBarPointerRef != 0 and self.kh2.read_int(infoBarPointerRef + 0x48) == 0: + self.kh2_write_byte(0x800000, 1) # displaying info bar popup + displayed_string = self.to_khscii(string_to_display) + self.kh2_write_bytes(0x800004, displayed_string) + self.queued_info_popup.remove(string_to_display) + await asyncio.sleep(0.5) + + +async def displayPuzzlePieceTextinGame(self, string_to_display): + if self.kh2_read_byte(0x800000) == 0: + displayed_string = self.to_khscii(string_to_display) + self.kh2_write_bytes(0x800104, displayed_string) + self.kh2_write_byte(0x800000, 2) # displaying puzzle piece popup + self.queued_puzzle_popup.remove(string_to_display) + await asyncio.sleep(0.5) + + +async def displayChestTextInGame(self, string_to_display): + if self.kh2_read_byte(0x800000) == 0: + + displayed_string = self.to_khscii(string_to_display) + print(f"made display string {displayed_string} from {string_to_display}") + self.kh2_write_byte(0x800150, 0) # item picture. this will change from 0 when I can input icons from the items + print("wrote item picture") + self.kh2_write_bytes(0x800154, displayed_string) # text + print("wrote text") + await asyncio.sleep(1) + self.kh2_write_byte(0x800000, 3) # displaying chest popup + print("called chest popup") + + self.queued_chest_popup.remove(string_to_display) + await asyncio.sleep(0.5) diff --git a/worlds/kh2/ClientStuff/SendChecks.py b/worlds/kh2/ClientStuff/SendChecks.py new file mode 100644 index 0000000000..c56ca8c3e7 --- /dev/null +++ b/worlds/kh2/ClientStuff/SendChecks.py @@ -0,0 +1,187 @@ +from CommonClient import logger +from .WorldLocations import * +from typing import TYPE_CHECKING + +# I don't know what is going on here, but it works. +if TYPE_CHECKING: + from . import KH2Context +else: + KH2Context = object + + +def finishedGame(ctx: KH2Context): + if ctx.kh2slotdata['FinalXemnas'] == 1: + if not ctx.final_xemnas and ctx.kh2_read_byte( + ctx.Save + all_world_locations[LocationName.FinalXemnas].addrObtained) \ + & 0x1 << all_world_locations[LocationName.FinalXemnas].bitIndex > 0: + 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: + locations = ctx.sending + for boss in ctx.kh2slotdata["hitlist"]: + if boss in 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 checkWorldLocations(self): + try: + currentworldint = self.kh2_read_byte(self.Now) + if self.last_world_int != currentworldint: + self.last_world_int = currentworldint + await self.send_msgs([{ + "cmd": "Set", "key": "Slot: " + str(self.slot) + " :CurrentWorld", + "default": 0, "want_reply": False, "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] + } + 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]) diff --git a/worlds/kh2/WorldLocations.py b/worlds/kh2/ClientStuff/WorldLocations.py similarity index 96% rename from worlds/kh2/WorldLocations.py rename to worlds/kh2/ClientStuff/WorldLocations.py index 6df18fc800..6d4cfb5943 100644 --- a/worlds/kh2/WorldLocations.py +++ b/worlds/kh2/ClientStuff/WorldLocations.py @@ -1,5 +1,5 @@ import typing -from .Names import LocationName +from ..Names import LocationName class WorldLocationData(typing.NamedTuple): @@ -896,6 +896,87 @@ tornPageLocks = { "TornPage4": WorldLocationData(0x1DB8, 4), # --Scenario_4_start "TornPage5": WorldLocationData(0x1DB8, 7), # --Scenario_5_start } +# (Room,Event) : Boss event +# Used for deathlink to say Sora died at : (Room,Event) which returns like Old pete +DeathLinkPair = { + (2, 34, 157): "Twilight Thorn", + (2, 5, 88): "Setzer", + (2, 20, 137): "Axel", + (2, 4, 80): "Sandlot", + (2, 41, 186): "Mansion fight", + (2, 40, 161): "Betwixt and Between", + (2, 20, 213): "Data Axel", + (4, 8, 52): "Bailey", + (4, 20, 86): "Corridor", + (4, 18, 73): "Dancers", + (4, 4, 55): "HB Demyx", + (4, 16, 65): "FF Cloud", + (4, 17, 66): "1k Heartless", + (4, 1, 75): "Sephiroth", + (4, 4, 114): "Data Demyx", + (5, 11, 72): "Thresholder", + (5, 3, 69): "Beast", + (5, 5, 79): "Dark Thorn", + (5, 4, 74): "Dragoons", + (5, 15, 82): "Xaldin", + (5, 15, 97): "Data Xaldin", + (6, 7, 114): "Cerberus", + (6, 17, 123): "OC Demyx", + (6, 8, 116): "OC Pete", + (6, 18, 171): "Hydra", + (6, 6, 126): "Auron Statue fight", + (6, 19, 202): "Hades", + (4, 34, 151): "Zexion", # all as fights are in hb "world" + (7, 9, 2): "Abu", + (7, 13, 79): "Chasm fight", + (7, 10, 58): "Treasure Room", + (7, 3, 59): "Lords", + (7, 14, 100): "Carpet", + (7, 5, 62): "Genie Jafar", + (4, 33, 142): "Lexaeus", + (8, 5, 72): "Cave", + (8, 7, 73): "Summit", + (8, 9, 75): "Shan Yu", + (8, 10, 78): "Antechamber fight", + (8, 8, 79): "Storm Rider", + (10,14, 55): "Scar", + (10,15, 59): "Groundshaker", + (11,2, 63): "Tutorial", + (11,9, 65): "Ursula's Revenge", + (11,4, 55): "A New Day is Dawning", + (12,1, 53): "Library (DC)", + (12,0, 51): "Minnie Escort", + (13,1, 58): "Old Pete", + (13,2, 52): "Boat Pete", + (13,3, 53): "DC Pete", + (4, 38, 145): "Marluxia", + (12,7, 67): "Lingering Will", + (14,6, 53): "Candy Cane Lane fight", + (14,3, 52): "Prison Keeper", + (14,9, 55): "Oogie Boogie", + (14,10, 63): "Presents minigame", + (14,7, 64): "Experiment", + (4, 32, 115): "Vexen", + (16,2, 55): "Town", + (16,10, 60): "Barbossa", + (16,14, 62): "Gambler", + (16,1, 54): "Grim Reaper", + (17,3, 54): "Screens", + (17,4, 55): "Hostile Program", + (17,7, 57): "Solar Sailer", + (17,9, 59): "MCP", + (4, 33, 143): "Larxene", + (18,21, 65): "Roxas", + (18,10, 57): "Xigbar", + (18,14, 58): "Luxord", + (18,15, 56): "Saix", + (18,19, 59): "Xemnas 1", + (18,20, 98): "Data Xemnas", + (18,21, 99): "Data Roxas", + (18,10, 100): "Data Xigbar", + (18,15, 102): "Data Saix", + (18,14, 101): "Data Luxord", +} all_world_locations = { **TWTNW_Checks, **TT_Checks, diff --git a/worlds/kh2/ClientStuff/__init__.py b/worlds/kh2/ClientStuff/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/worlds/kh2/Locations.py b/worlds/kh2/Locations.py index 100e971e7d..a73b747f03 100644 --- a/worlds/kh2/Locations.py +++ b/worlds/kh2/Locations.py @@ -982,7 +982,6 @@ popups_set = { LocationName.BaileySecretAnsemReport7, LocationName.BaseballCharm, LocationName.AnsemsStudyMasterForm, - LocationName.AnsemsStudySkillRecipe, LocationName.AnsemsStudySleepingLion, LocationName.FFFightsCureElement, LocationName.ThousandHeartlessSecretAnsemReport1, diff --git a/worlds/kh2/OpenKH.py b/worlds/kh2/OpenKH.py index 7c67fc07de..07d728347a 100644 --- a/worlds/kh2/OpenKH.py +++ b/worlds/kh2/OpenKH.py @@ -5,6 +5,8 @@ import os import Utils import zipfile +from datetime import datetime, UTC + from .Items import item_dictionary_table from .Locations import all_locations, SoraLevels, exclusion_table from .XPValues import lvlStats, formExp, soraExp @@ -76,7 +78,8 @@ def patch_kh2(self, output_directory): if self.options.LevelDepth == "level_99_sanity": 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)}" + curr_timestamp = datetime.strftime(datetime.now(UTC), "%d%b%Y-%H%M%S") + mod_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.multiworld.get_file_safe_player_name(self.player)}-{curr_timestamp}" all_valid_locations = {location for location, data in all_locations.items()} for location in self.multiworld.get_filled_locations(self.player): @@ -384,7 +387,7 @@ def patch_kh2(self, output_directory): { 'name': 'msg/sp/he.bar' } - ], + ], 'method': 'binarc', 'source': [ { @@ -473,7 +476,9 @@ def patch_kh2(self, output_directory): mod_dir = os.path.join(output_directory, mod_name + "_" + Utils.__version__) - self.mod_yml["title"] = f"Randomizer Seed {mod_name}" + self.mod_yml["title"] = f"Archipelago Seed - {self.multiworld.get_file_safe_player_name(self.player)}" + self.mod_yml["originalAuthor"] = "JaredWeakStrike" + self.mod_yml["description"] = f"Seed {self.multiworld.seed_name} was generated for {self.multiworld.get_file_safe_player_name(self.player)} - Player {self.player} at {curr_timestamp} UTC. Have fun! \nCredit to delilahisdidi for the icons!" openkhmod = { "TrsrList.yml": yaml.dump(self.formattedTrsr, line_break="\n"), @@ -487,6 +492,44 @@ def patch_kh2(self, output_directory): "he.yml": yaml.dump(self.cups_text, line_break="\n") } + ## I think I overlooked a really easy way to find the data folder, + ## but it has to determine if it's a local client generating, + ## if it's a server generating, if it's a build, or on the complete + ## offchance that it's a custom world. + + iconbytes = bytes() + previewbytes = bytes() + # local build/server generating + apworldloc = os.path.join("worlds","kh2","data") + if os.path.exists(apworldloc): + try: + with open(os.path.join(apworldloc, "khapicon.png"),'rb') as icon, \ + open(os.path.join(apworldloc, "preview.png"),'rb') as preview: + iconbytes = icon.read() + previewbytes = preview.read() + openkhmod["icon.png"] = iconbytes + openkhmod["preview.png"] = previewbytes + except IOError as openerror: + logging.warning(openerror) + + # client install generating + apworldloc = os.path.join("lib","worlds") + if not os.path.isfile(Utils.user_path(apworldloc, 'kh2.apworld')): + apworldloc = os.path.join("custom_worlds", "") + if os.path.exists(os.path.join(apworldloc,"kh2.apworld")): + try: + with zipfile.ZipFile(Utils.user_path(os.path.join( + apworldloc, 'kh2.apworld')), 'r') as apworld_archive: + # zipfile requires the forward slash + with apworld_archive.open('kh2/data/khapicon.png', 'r') as icon, \ + apworld_archive.open('kh2/data/preview.png', 'r') as preview: + iconbytes = icon.read() + previewbytes = preview.read() + openkhmod["icon.png"] = iconbytes + openkhmod["preview.png"] = previewbytes + except IOError as openerror: + logging.warning(openerror) + mod = KH2Container(openkhmod, mod_dir, output_directory, self.player, self.multiworld.get_file_safe_player_name(self.player)) mod.write() diff --git a/worlds/kh2/__init__.py b/worlds/kh2/__init__.py index f13dec7265..d8b9059ab8 100644 --- a/worlds/kh2/__init__.py +++ b/worlds/kh2/__init__.py @@ -3,7 +3,8 @@ from typing import List from BaseClasses import Tutorial, ItemClassification from Fill import fast_fill -from worlds.LauncherComponents import Component, components, Type, launch as launch_component +from Utils import local_path +from worlds.LauncherComponents import Component, components, icon_paths, Type, launch as launch_component from worlds.AutoWorld import World, WebWorld from .Items import * from .Locations import * @@ -16,11 +17,12 @@ from .Subclasses import KH2Item def launch_client(): - from .Client import launch + from .ClientStuff.Client import launch launch_component(launch, name="KH2Client") -components.append(Component("KH2 Client", func=launch_client, component_type=Type.CLIENT)) +icon_paths['kh2apicon'] = f"ap:{__name__}/data/khapicon.png" +components.append(Component("KH2 Client", func=launch_client, component_type=Type.CLIENT, icon='kh2apicon')) class KingdomHearts2Web(WebWorld): @@ -102,16 +104,16 @@ class KH2World(World): self.goofy_ability_dict[ability] -= 1 slot_data = self.options.as_dict( - "Goal", - "FinalXemnas", - "LuckyEmblemsRequired", - "BountyRequired", - "FightLogic", - "FinalFormLogic", - "AutoFormLogic", - "LevelDepth", - "DonaldGoofyStatsanity", - "CorSkipToggle" + "Goal", + "FinalXemnas", + "LuckyEmblemsRequired", + "BountyRequired", + "FightLogic", + "FinalFormLogic", + "AutoFormLogic", + "LevelDepth", + "DonaldGoofyStatsanity", + "CorSkipToggle" ) slot_data.update({ "hitlist": [], # remove this after next update @@ -201,6 +203,7 @@ class KH2World(World): """ 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()) diff --git a/worlds/kh2/data/khapicon.ico b/worlds/kh2/data/khapicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..426c6d8e744bec0668be50d3279ce24c6b171a8c GIT binary patch literal 93345 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 Guqo! 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 literal 0 HcmV?d00001 diff --git a/worlds/kh2/data/khapicon.png b/worlds/kh2/data/khapicon.png new file mode 100644 index 0000000000000000000000000000000000000000..b3ab0555d00e1895e1c23a3d1814d2c06df44cd2 GIT binary patch literal 21016 zcmV)VK(D`vP)StO&>uS)ve<0AYj>5AR{$W90N^4L=L-RlQUJ&DC0@ZjPh;=*jPLSYvv5M~MFBAl0-BNIsH z15C~g000{K(ZT*WKal6<?_01!^k@7iDG<<3=fuAC~28EsPoqkpK{9G%|Vj005J}`Hw&=0RYXHq~ibpyyzHQsFW8>#s~laM4*8xut5h5 z!4#~(4xGUqyucR%VFpA%3?#rj5JCpzfE)^;7?wd9RKPme1hudO8lVxH;SjXJF*pt9 z;1XPc>u?taU>Kgl7`%oF1VP9M6Ja4bh!J9r*dopd7nzO(B4J20l7OTj>4+3jBE`sZ zqynizYLQ(?Bl0bB6giDtK>Co|$RIL`{EECsF_eL_Q3KQhbwIhO9~z3rpmWi5G!I>X zmZEFX8nhlgfVQHi(M#xcbO3#dj$?q)F%D*o*1Pf{>6$SWH+$s3q(pv=X`qR|$iJF~TPzlc-O$C3+J1 z#CT#lv5;6stS0Uu9wDA3UMCI{Uz12A4#|?_P6{CkNG+sOq(0IRX`DyT~9-sA|ffUF>wk++Z!kWZ5P$;0Hg6gtI-;!FvmBvPc55=u2?Kjj3apE5$3psG>L zsh-pbs)#zDT1jo7c2F-(3)vyY4>O^>2$gY-Gd%Qm(Z8e zYv>2*=jns=cMJ`N4THx>VkjAF8G9M07`GWOnM|ey)0dgZR4~^v8<}UA514ONSSt1^ zd=-((5|uiYR+WC0=c-gyb5%dpd8!Lkt5pxHURHgkMpd&=fR^vEcAI*_=wwAG2sV%zY%w@v@XU~7=xdm1xY6*0;iwVIXu6TaXrs|dqbIl~ z?uTdNHFy_3W~^@g_pF#!K2~{F^;XxcN!DEJEbDF7 zS8PxlSDOr*I-AS3sI8l=#CDr)-xT5$k15hA^;2%zG3@;83hbKf2JJcaVfH2VZT8O{ z%p4LO);n}Nd~$Sk%yw*Wyz8XlG{dRHsl(}4XB%gsbDi@w7p6;)%MzD%mlsoQr;4X; zpL)xc%+^yMd)ZNTI#eJ*$O)i@o$z8)e??LqN_gLa_%;TM>o2SC_ zkmoO6c3xRt`@J4dvz#WL)-Y|z+r(Soy~}%GIzByR`p)SCKE^%*pL(B%zNWq+-#xw~ ze%5}Oeh2)X`#bu}{g3#+;d$~F@lFL`0l@*~0lk45fwKc^10MvL1f>Tx1&sx}1}_Xg z6+#RN4Ot&@lW)Km@*DYMGu&q^n$Z=?2%QyL8~QNJCQKgI5srq>2;UHXZ>IT7>CCnW zh~P(Th`1kV8JQRPeH1AwGO8}>QM6NZadh`A)~w`N`)9q5@sFvDxjWlxwsLl7tZHmh zY-8-3xPZ8-xPf?w_(k!T5_A(J3GIpG#Ms0=iQ{tu=WLoYoaCBRmULsT<=mpV7v|~C z%bs^USv6UZd^m-e5|^?+<%1wXP%juy<)>~<9TW0|n}ttBzM_qyQL(qUN<5P0omQ3h zINdvaL;7fjPeygdGYL;pD|wL_lDQ-EO;$wK-mK5raoH_7l$?~Dqf!lNmb5F^Ft;eT zPi8AClMUo~=55LwlZVRpxOiFd;3B_8yA~shQx|tGF!j;$toK>JuS&gYLDkTP@C~gS@r~shUu{a>bfJ1` z^^VQ7&C1OKHDNXFTgC{M|V%fo{xK_dk6MK@9S!GZ*1JJzrV5xZBjOk z9!NTH<(q(S+MDf~ceQX@Dh|Ry<-sT4rhI$jQ0Sq~!`#Eo-%($2E^vo}is5J@NVEf|KK?WT&2;PCq@=ncR8z zO#GQ^T~S@VXG71PKNocFOt)Y6$@AXlk6rM*aP%VgV%sIRORYVwJx6|U{ozQjTW{-S z_si{9Jg#)~P3t?+@6&(!YQWWV*Z9{iU7vZq@5byKw{9lg9JnRA_4s!7?H6|n?o8ZW zdXIRo{Jz@#>IeD{>VLHUv1Pz*;P_y`V9&!@5AO~Mho1hF|I>%z(nrik)gwkDjgOrl z9~%uCz4Bzvli{bbrxVZ0epdf^>vOB;-~HnIOV3#R*zgPai_gEVd8zYq@2jb=I>#f& zAH2?aJ@Kaet$DEK~#90?Y(DsTt(70e0QHTc{GwnIoYx#OU?;{6Ul*Oa$Zc% zu*6+pfn9dVuuEpiIVXd`7=tkwM{t(2a?T@Z@|=A7`!O;Y*#`E3XYqS|&&@SIEKcaE z`*e48RdqFCjN$)q;DG+#4fsF&82?Ae0S10weAUm1mtT5rv82{{?9=6HK`wnJw)XA zY|S*T25=gH5|7P}be9_?8CfXF$TGj&wT-LD&V_E8*wNXB{&*Z=IROC806qZl0)VRD zQG|Ws|G6F_aJ)x1jT8S^$p&xDP<@?=04SaX=Fy^85_VFkU5u z6wN6g#m=2PecROHvVM`{SX|RM-PEbY7~Rm^;OU7*Y@1pdJ!efgq$|tg)pKd8Ha52) z@`r<_Hnax&9UoS%FMD?7S2xJIb}oQR04(^O#oH$iNJ1zfAcS138OHM^8CkgI@Hthd zj60}PGYqaj5fgROB#bdoLf{Z>#-`48rvRX>t;N-qhzpi(P+3b*N(p#MS#F9?xq0?+ z8>bH|OMUd~#bZ|1ReuEF&j9}QJBzv(4mh)y5CWcZx0;sq{G9U9=0}%Zzv{#>6T1V^ zh^;#k7B#~lY+&|wh>{tSp9wx#O;zH4joR(G~Lyf(W5k{q9P({M#yzKwn;d`YtP57zc%F5I!=kKdKe99k z0I%I{Y-nlpY;9}ydh8B8#w_yb=5?7iK`?pxgJ_fx=$1*^Lj96qn)Ht2PG0}V!)Esa z_$z>0|GN_YG!7_2Y=SUOHH_CL7ZtMyPC9cv0inIIm|!uB>}mNi#^91{reYX)e#O#! z0QmE)x%Gt}uk!l()wx@`+8x=R6xCwv`V9#+5W;huUD5Rq^L(jz_|$ppcuHw^G%O4@!5L$a1c9ZyT>49Emgjue+~6zA$ckM# z*;^ut%!TBHbknTkHXS@<7y!Hl!1v#icKr+C|ACK{F$uy?n`#N55H;=l6INmn2z(NdY0~iiX9Bg~k0>9Xcxt-~s@J|1FvKhXaBT zk>~E9gwT@AtUi(Fna|jl@~6(Zp(;NzuFNFii8j_#^AL(OmLie{rj(#F5h06^0Op=BdP4Nx6VKTE*v4ZT<&92>g=J~tMDIeY6*xze6Eo-m#GR8=j$E&p^;`FMwUo1D88d7-xo)Kev zo<8%uU5hrYEx!4qck%$N1aK*S&gn=10+I%TEC4nzFveIIKr4QwyZ?WU5X#Z; z;2;3y05Xw`Q|v|%Kofv%NGf6>fbM-gi|05H;1=C9?;D<*&)l7zj9Lb5ak-Uy=Uuve zZqcyl{>T6TmXs7Z-W07oaN|$N;~9`=I)G6C zh5<<5;}R7>8-O|hTLCNru>9vWHK?t>DIpvuh{QTmGuY(-rsSl%F?5I@6UXKwuOL05 z>!wLLFrMS6VOrwOZLN+C+d9zF)PtyMgaFJ3@Ctxcd$j=scAM?ogd)E>27tG?wC~!v zr`1i%%u!lG{nGx(7%Z&cr9-O#=)m;=9^I!%0#{+`<{tr^Lx}`x1^};s;8`&IqT%niala=O zaSBZHZUzjWKCu9&pEbSbh#95rg@u0EneNs>csB5@B00h{u((V^;kdA+vL)q%kJgtg z{A}w*Egiv&0K5+12JGP$XJAMu@@pQOUEZR|?;Ypyibv!YM7p98hi;mLqx5I9GZ=ud zB6G#*8S=PvzxWQv0j7BcfM*6@%L$y*GOXK8-MmGx2`D?f3>C90>axqT`(5cS&Eazx zl%rr)^0kB#7>Yq+eKA1}%VOJ>wyezyHcxC^+c*&nR|B{S$x+}xQ|*7+LpYuTG0aB{ z;JU-7l;Dz!538Rwb3{*iewG3?!RrZ)Q&fYPrj>jO0d~Ja6gg(I2^O`976j2~+|lBG z@5A+lk36x&-yMkdAUU3YJoue9LA*lIwWqF_F|+&DqfXe_7mL|AN*G5dQw)RZra^!t z0RpAWCWsaQ=%zt4++O9w%5_Cozx(PC0JkIQD)|w3i9gg(jh6uoshC}%O*wDM&cca> zVL}PCgvJ@FK@H6yj4?t6%m)Sr%2CFPJmW>)lAMyML={@Ms?NXqmDR(WH#7slt4O-~ z_5JDdR}ERB#Je=r_!PjQ58QEL?@fO_X$LrwAr=#Yp#-P5G12GJ z0lU$-qsIHEzq~*3!-bmw;5q=0?Rg)UbQC^kTGl21KJ)yla}S!{UK8lD#dV!#x!eW- z(ie{kF3Dy(M9JuiMFfihrRf>U=GG?9881FQAuP*Z061!|Hu3aa%dlSc7yB{u`k6aN z%o-7(JVmTOCYYK@7-M92ulPBLcF}UByR=wejNkCyhGAbn{&kkF=vx3B37~IZ_xz6u z5k%gtYsLzoT~4B$8dN#re?U9=$b76<_}6S{5@ z%VLlmJcE?30y??#)|V&Uao<8OlEc>{dp^r12t?8Knh6C%hMhfW%C1*eFUyO@V&a6- z^1chE9^O7aCoj%PrG- zdgRd~wG;0-aeZcKW<1&x6%Ey(gpl0}Q^G7pbXBK@YEaWK3HHa&fcgMVUpL5D*3vbvuVd~f(rMKG|PiTbFy_uktKoodp z_t;E`yrqU!Qn{l~T(Y#@)zsAGh{wVr%mg2g8_*2NEE}E|yZn-AeHCLXLN7k`=ERGy zdd&}Dud&`G*`@+m6;l;(0L+2ES7cMX!#J_C#xt{3w1MA%qf8;31z)xM9J^ z*H=xQIkOS*W=|raQp;jsjDZjWN(lskXM$ZYA@atywY9_fRRwx%>!SEr=8+TDIn zw_T0^y#YY7134LhFCCCAAC|6U@~{zybiVM)dG)v6@Z!|_9{bV(U_5}$-z$+)P+$=y z&O+Xsq*{IBs(LvTYl)K`49T+{?D+`_keUzVP_8#!&wn08@WN zhQQ^Ry4g@XwOG1x!IfWDe^Qd>(+6^|Rgl|??3q4^gXLw0|qA1^PxFinXD1*hI>n7<7YINg@F2~vp^{)2D zdXEvS@^N~fZDcBtPym-5u-Q{JyDwFV>2}LwMcAs?!pNLC+XF zsdL_xLp$DF`BiySBm^XbP~U@c6sB&zo06L%&3kU%stwO?$av@0cSiE3^PKL}ft`Tp z7A?EOt}~M%ZpH;uF=?PR;0lm{tFopt+xxXwE-okz4jVr#m^C6R?k)1Fl2c-Y5D<>Q zG)#a2mTr-*U0u#lXNcKcHUq%f06qcm?*8=slRbnNc^I0}GHqN@{=CbM*m>FIFI5cn z04JQ1AB{wPYHD_h=5ad>StR4Bs&KfUIO$*fhf&MczmOKONW<* zMvcl;ii=Y;k3(SH0hwF%U7PKt=a*(?J9p;JUwSBRyLdSoLt~q7x&8Ra^yznuiYWSz z)(n&oFoutLjvl|_`oFH(5a@FK?bT;Sr`R33EGLAM4w~A2-EpVZocin|hi_|d*@k59 z)(>Bx$e(Ly#;a#Pb#|lI>Fs&?yr(DU|2rSynlP-FEqVH>(_8W<=f^~!Xo3M+SmQ&@ zA*r>t)l=16l^zTR?Fb@ipmTs&BpP|Sc@clEKjHGbG@ILIC}D+9gcCv{mf+jAwt4#M z`kZ4=7~94e%iZ$HmRXu9R7BP8C z`l^!~{`HR!hi?CNea2O<-?jxXDNLCMV}NNAQ#Gijsa&Kd!JDS_y@g;|jHGz&1}E}X ze@LdODGt+Rn6$Sy#-}@-`mrY-+WYqVA7|flTP9wf-|0#9XVgtQco;rgw(Gn-2~i9^ zW*Pi96>{R#1c2j@I-%~mNmBypcDwbjSD$re#3E9aVXfVh3;-}x<7{uX7bVk6TE2PU zo7|M?Dd^eUgTi5jy#+@U#QGZhY?PzS?y(yK#tPFFo!8ZbXdGU`^>rFpZx%E=F&T&-(59}Pr>w8Au{d}euj&GMRj zB;)v>57zsB15~Q49`E8)2AKGc3r8PFO4~UUS{twkgvKqR+pv(f`C_ALj!) zjdX{_P(8ra|OS(tbH>^sL^`N!9ayZfSCN{Z8ju5jh$ zS$cD~Wwy2V!X$)uiUOHAb7U02Gyny=pT!shr|aKs5(JbSd35tdlc%?T(@^6bmzE*V zJNc~Yl`c2f9@ZHE2$J9dFei6pE;5H@%9^Zkrp<)hC?l&dD`8m{1py?7WLAGuo$}uG z@0ES|(3gdVYQW)h7z{te<}K4AhH6kHqVTc4m=NiXNa60V#26#3n_6A(|LOe-066LH zld4?lE+ZC*2?PX6SP_aQ7PWgaJitB{slZS45XLZZczG_yj?9nhQJE(sIh2J$3C@@2 z*XN#dT+5o3PmTEOjfDlLojkeM?-9*NRN3!p-Dww@VG(xIU)~t<@{22Svm{{k_8wIrYI43W#a!-npVVdXw*^WI{a13k$08ieSye>}Qm^;O-C!z@unZT9fGI)u%9A1a9 zW#N|e4{!OfeD{5wJ3HMc-g9D&!{;#6xVpD|1FxVQ1t)OU#t$}T$zfR-K67}W^sv%U zq&p%300^L~I`L$A)a;6EM0z5V_ob(O_7INe0AQHMV}s<9EW`MIK245Fk(hAW+|j`q zGqU2}Zs@nazo62`d0qSUZW036Ql0vO&#OF(UR{xU?36TR@s=l7KmF)AwVyB9=BwP? z>;in+$~8@nk+~7BrcQ-DWk?jFWTmIO6Zx4QAnDTo9y&;Kfng!mRPWx|)$YE?=5wU? z_d9nr)p@;g0wAc|^Lj%y$~b{T*6^%;P|%zaISF%@2}3o|UEl2n0V5o-R=%(@KYeI= z;`$}me^)-cJkYSVA$7xh8?r>FXi{!J4owD#4$+LXM}!TFHsk=n)N`h`nU+Zn)!6O+ zf~lKCw2Lfjc$N-eD1fwIL?E0WD`wqiX8y*25F(YLzBauP8Y;9e>BtMMMlvur6nyLRe%Y``Uf?m z!0B_so$glTh|CupRuE0;NRgodJL-32)P7W(;xG0q4v)jgACn)NG;31#wDYI60zmuD zc8_IPAl$xve1sB+4$-V#Uh9js#w71huaaGn9aF*zk3DW-79$J{DcLEZ0Wxxa5g{BQ zX#hOAxxOd}g=PIXx5+daVCpolup}~RJP_&HlD71#7KbCnzT2AtO34qu0l%*+B}L2g z0jri&rY-q;WqMgze*B4t&#Qaq(-zy*gYO!B&sCkCgeon)~>Ae0f60YHv~zr2K-;i-yYr!Ug9lH z(a82Mw)+8~bV_NTEyZRTs^|F`cQRFu+z8!UieFUyz!ETPj;6&cE= zf)us8DI~32Rpp&?Oi8=Xj{)B_0SIB=d)y$H*IhNEd(*1*>D9|C{g2;z^Fc?Ser!u9 zVv%n?Uz+#$O$pxA2$t1M>YP9Sk~)BJ9Hq?Vu~{y!69Ak)ZUU1TU>uZJXvn%2et+#6 zuauRmA2w{XUfo!q&;fa{9+;JE4xt35W^z;iG_@lf2#d>iEFT&i9dtbVz_Sw$opoq) zPH9f;)k|L;x$VTrPgD~ zYI{RNz}DXzvG3T`;nFmd7etO3hQ*>`ndauCBq|P>6o^BxZ2ddZHhsBZh|3EcH4)g} zGj z+<-I9sTGVa2yOm!bAIJ#l|}iZ^W%&$sw+BiXSfw#o-ZNVMKy19UL;T%aOkSek>P}8 zEYl2V-ph)wT^ zrYIG|0V-B!9X9eoPI(FB+U$U0yX0LlQb9T-(+M@eZRwyHxdr+~04#ip{X z;z&-~&~RzzlKe8~=S7h=iqChr^mktL$=Ssvkwi=Z1=_4Vnn(%B5qaLXp}7H?Zj=*_ zLsKH28>(DSlJQAoAh~-gc|AwF48_2@x7X#!A(?AyXmbZb0jDgl5yTi=`{Y8l=P9$9?fnP+3ZdwTfv;}&?Ii_k7o=fID*=-`Tr@M43 zq2tq!He`Idu(D+RitSt^mqTAL*t4lASP9^}0Y!9EK(+uYp@SC$ctQqpw$v#l_03gN z6d9x4X_;QoJRE;~QS{5@Z7x@0V@C1#3|-b3*PjRoOIAe$V{6z3)c_@E-7`*PT2{y@ zN}qjQy>rCZ)jLyc93T2@dnEuwMUJo2O(V4D2g-?Dy=j>E?)C3-CN$~iC{v|5*j6hFfgFMwcm?m!bito-S5|R zB2m0jJ~aKj1)tvhT|vIjBo+f~lCk{j?WxZ{vvkyFpKU=;->2-^#9@9iTztPR%Kp#`Ab6--8d2Z z2=5X?oQyFyA%tU$SpdQqaNO+)!7hw5RAbZRKTM8{Jz;EZ%cd50?V{RLxk~2SX0)ZC z1HcXdz_&L8uu-r#U%aS}u}_zTd!Al(;}wTT@@!y8j_20` z@cg3I{-h)n03GM^@Nb-YsE56E2iTpL6L!^Ar`9pHbkQ9*%+UbE0eFASYj~080W_6N zD`C&*&n&ue@r|o9M`kAA2c!9vfybKw4*Avc0N{8|paMM?KrH}Pc37Ey<%d_Ue=PXe zlIy>`eyuar$uO{4XfNIGj}V(f%9H@(iu0$cRofm|JL|9thU7H$BtTMsi-!Lg1b{fU zDBJV(+kfZSRY!qcdF9c~M;u(n0KEU3yn!PRrVmMHr#x`VE?GrvzS`@Mdm?>ug20(9j;LLo&d! z{-+v2DRoY(ciUu z?c;cnM>G&!2H*%_Z}?3Br8@$Ma6S#!{7zobT{r6wqLjVv0N_qYqQ#a-# zIgbAb9LJ9~P2&at=TJ(yjI0u5WtXC8==jcz%;J#UnWDKpel67BZi~l)Qgh?xjE=S) zDIM)qh=jX=q&4;gKpxt+(Jsyb3&k=Y3iiE zx6anpvC|ff1jICdfs&IqKH>3XYw>tc=#*Gu&!ne0Bk`r_2=tfpws^q zNP;*+)wPEJOe`-Lj?-qG)IN3OAzh=2Dx#_GR72KeQrWuQUf`axg%uD zG)Z@;N8DIj>04I$P2qwypSrt)JpgbofV+?!+V+cIO$g76LkvTEpAa%(!a*k|rX6

0tbhW21VMjrlh)=jksmrv?R_oNvd$AJTf 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�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 X7PRvcfC#{D00000NkvXXu0mjfT52PM literal 0 HcmV?d00001 diff --git a/worlds/kh2/data/preview.png b/worlds/kh2/data/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..1f2b967a98442d9f92ae38b95aa09b57c960cd33 GIT binary patch literal 44905 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 ROM you did during the KH2 Rando setup. ` Have this mod second-highest priority below the .zip seed.
This mod is based upon Num's Garden of Assemblage Mod and requires it to work. Without Num this could not be possible. -### Required: Auto Save Mod and KH2 Lua Library +### Required: Archipelago Enablers -Load these mods just like you loaded the GoA ROM mod during the KH2 Rando setup. `KH2FM-Mods-equations19/auto-save` and `KH2FM-Mods-equations19/KH2-Lua-Library` Location doesn't matter, required in case of crashes. See [Best Practices](#best-practices) on how to load the auto save +Load this mod just like GoA ROM `TopazTK/KH2-ArchipelagoEnablers`. NOTE: if you perfer `KH2FM-Mods-equations19/auto-save` or `KH2FM-Mods-equations19/soft-reset`you need to download `TopazTK/KH2-ArchipelagoEnablersLITE` ### Optional QoL Mods: AP QoL and Bear Skip @@ -52,8 +53,8 @@ After Installing the seed click "Mod Loader -> Build/Build and Run". Every slot ## What the Mod Manager Should Look Like. -![image](https://i.imgur.com/N0WJ8Qn.png) - +![image](https://i.imgur.com/3IAgeee.png) +- if you are using APEnablers Lite, install `KH2FM-Mods-equations19/soft-reset` and `KH2FM-Mods-equations19/KH2-Lua-Library`. Put them below the APCompanion but ABOVE the goa ## Using the KH2 Client @@ -86,6 +87,8 @@ Enter The room's port number into the top box where the x's are and pres - Using a seed from the standalone KH2 Randomizer Seed Generator. - The Archipelago version of the KH2 Randomizer does not use this Seed Generator; refer to the [Archipelago Setup](/tutorial/Archipelago/setup/en) to learn how to generate and play a seed through Archipelago. +- Using equations19/auto-save OR equations19/soft-reset while using TopazTK/KH2-ArchipelagoEnablers. + - Since APEnablers has both of these features they conflict with each-other. If you want to keep on using Equation's mods you need to download TopazTK/KH2-ArchipelagoEnablersLITE instead ## Best Practices - Make a save at the start of the GoA before opening anything. This will be the file to select when loading an autosave if/when your game crashes. @@ -94,6 +97,7 @@ Enter The room's port number into the top box where the x's are and pres - Run the game in windows/borderless windowed mode. Fullscreen is stable but the game can crash if you alt-tab out. - Make sure to save in a different save slot when playing in an async or disconnecting from the server to play a different seed + ## Logic Sheet & PopTracker Autotracking Have any questions on what's in logic? This spreadsheet made by Bulcon has the answer [Requirements/logic sheet](https://docs.google.com/spreadsheets/d/1nNi8ohEs1fv-sDQQRaP45o6NoRcMlLJsGckBonweDMY/edit?usp=sharing) @@ -125,7 +129,7 @@ This pack will handle logic, received items, checked locations and autotabbing f - Why did I not load into the correct visit? - You need to trigger a cutscene or visit The World That Never Was for it to register that you have received the item. - What versions of Kingdom Hearts 2 are supported? - - Currently the only supported versions are Epic Games Version 1.0.0.10_WW and Steam Build Version 15194255. + - Currently, the only supported versions are Epic Games Version 1.0.0.10_WW and Steam Build Version 15194255. - Why am I getting wallpapered while going into a world for the first time? - Your Lua Backend was not configured correctly. Look over the step in the [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/) guide. - Why am I not getting magic? @@ -138,8 +142,6 @@ This pack will handle logic, received items, checked locations and autotabbing f - Why am I getting dummy items or letters? - You will need to get the `JaredWeakStrike/APCompanion` (you can find how to get this if you scroll up) - Why am I not sending or receiving items? - - Make sure you are connected to the KH2 client and the correct room (for more information scroll up) -- Why should I install the auto save mod at `KH2FM-Mods-equations19/auto-save` and `KH2FM-Mods-equations19/KH2-Lua-Library`? - - Because Kingdom Hearts 2 is prone to crashes and will keep you from losing your progress. Both mods are needed for auto save to work. + - Make sure you are connected to the KH2 client and the correct room (for more information scroll up). You may need to run the client/launcher as admin - How do I load an auto save? - To load an auto-save, hold down the Select or your equivalent on your preferred controller while choosing a file. Make sure to hold the button down the whole time.