diff --git a/KH2Client.py b/KH2Client.py index 2068e11e..5223d8a1 100644 --- a/KH2Client.py +++ b/KH2Client.py @@ -119,7 +119,12 @@ class KH2Context(CommonContext): "LimitLevel": 0, "MasterLevel": 0, "FinalLevel": 0, - } + }, + "SoldEquipment": [], + "SoldBoosts": {"Power Boost": 0, + "Magic Boost": 0, + "Defense Boost": 0, + "AP Boost": 0} } self.slotDataProgressionNames = {} self.kh2seedname = None @@ -137,23 +142,23 @@ class KH2Context(CommonContext): self.finalxemnas = False self.worldid = { # 1: {}, # world of darkness (story cutscenes) - 2: TT_Checks, + 2: TT_Checks, # 3: {}, # destiny island doesn't have checks to ima put tt checks here - 4: HB_Checks, - 5: BC_Checks, - 6: Oc_Checks, - 7: AG_Checks, - 8: LoD_Checks, - 9: HundredAcreChecks, - 10: PL_Checks, - 11: DC_Checks, # atlantica isn't a supported world. if you go in atlantica it will check dc - 12: DC_Checks, - 13: TR_Checks, - 14: HT_Checks, - 15: HB_Checks, # world map, but you only go to the world map while on the way to goa so checking hb - 16: PR_Checks, - 17: SP_Checks, - 18: TWTNW_Checks, + 4: HB_Checks, + 5: BC_Checks, + 6: Oc_Checks, + 7: AG_Checks, + 8: LoD_Checks, + 9: HundredAcreChecks, + 10: PL_Checks, + 11: DC_Checks, # atlantica isn't a supported world. if you go in atlantica it will check dc + 12: DC_Checks, + 13: TR_Checks, + 14: HT_Checks, + 15: HB_Checks, # world map, but you only go to the world map while on the way to goa so checking hb + 16: PR_Checks, + 17: SP_Checks, + 18: TWTNW_Checks, # 255: {}, # starting screen } # 0x2A09C00+0x40 is the sve anchor. +1 is the last saved room @@ -412,7 +417,7 @@ class KH2Context(CommonContext): logger.info(e) async def verifyLevel(self): - for leveltype, anchor in {"SoraLevel": 0x24FF, + for leveltype, anchor in {"SoraLevel": 0x24FF, "ValorLevel": 0x32F6, "WisdomLevel": 0x332E, "LimitLevel": 0x3366, @@ -536,6 +541,34 @@ class KH2Context(CommonContext): self.ui = KH2Manager(self) self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI") + async def IsInShop(self, sellable, master_boost): + # journal = 0x741230 shop = 0x741320 + # if journal=-1 and shop = 5 then in shop + # if journam !=-1 and shop = 10 then journal + journal = self.kh2.read_short(self.kh2.base_address + 0x741230) + shop = self.kh2.read_short(self.kh2.base_address + 0x741320) + if (journal == -1 and shop == 5) or (journal != -1 and shop == 10): + # print("your in the shop") + sellable_dict = {} + for itemName in sellable: + itemdata = self.item_name_to_data[itemName] + amount = int.from_bytes( + self.kh2.read_bytes(self.kh2.base_address + self.Save + itemdata.memaddr, 1), "big") + sellable_dict[itemName] = amount + while (journal == -1 and shop == 5) or (journal != -1 and shop == 10): + journal = self.kh2.read_short(self.kh2.base_address + 0x741230) + shop = self.kh2.read_short(self.kh2.base_address + 0x741320) + await asyncio.sleep(0.5) + for item, amount in sellable_dict.items(): + itemdata = self.item_name_to_data[item] + afterShop = int.from_bytes( + self.kh2.read_bytes(self.kh2.base_address + self.Save + itemdata.memaddr, 1), "big") + if afterShop < amount: + if item in master_boost: + self.kh2seedsave["SoldBoosts"][item] += (amount - afterShop) + else: + self.kh2seedsave["SoldEquipment"].append(item) + async def verifyItems(self): try: local_amount = set(self.kh2seedsave["AmountInvo"]["LocalItems"]["Amount"].keys()) @@ -578,6 +611,8 @@ class KH2Context(CommonContext): server_boost = set(self.kh2seedsave["AmountInvo"]["ServerItems"]["Boost"].keys()) master_boost = local_boost | server_boost + master_sell = master_equipment | master_staff | master_shield | master_boost + await asyncio.create_task(self.IsInShop(master_sell, master_boost)) for itemName in master_amount: itemData = self.item_name_to_data[itemName] amountOfItems = 0 @@ -603,7 +638,8 @@ class KH2Context(CommonContext): itemData = self.item_name_to_data[itemName] # if the inventory slot for that keyblade is less than the amount they should have if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + itemData.memaddr, 1), - "big") <= 0: + "big") != 1 and int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + 0x1CFF, 1), + "big") != 13: # Checking form anchors for the keyblade if self.kh2.read_short(self.kh2.base_address + self.Save + 0x24F0) == itemData.kh2id \ or self.kh2.read_short(self.kh2.base_address + self.Save + 0x32F4) == itemData.kh2id \ @@ -618,7 +654,8 @@ class KH2Context(CommonContext): itemData = self.item_name_to_data[itemName] if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + itemData.memaddr, 1), "big") != 1 \ - and self.kh2.read_short(self.kh2.base_address + self.Save + 0x2604) != itemData.kh2id: + and self.kh2.read_short(self.kh2.base_address + self.Save + 0x2604) != itemData.kh2id \ + and itemName not in self.kh2seedsave["SoldEquipment"]: self.kh2.write_bytes(self.kh2.base_address + self.Save + itemData.memaddr, (1).to_bytes(1, 'big'), 1) @@ -626,7 +663,8 @@ class KH2Context(CommonContext): itemData = self.item_name_to_data[itemName] if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + itemData.memaddr, 1), "big") != 1 \ - and self.kh2.read_short(self.kh2.base_address + self.Save + 0x2718) != itemData.kh2id: + and self.kh2.read_short(self.kh2.base_address + self.Save + 0x2718) != itemData.kh2id \ + and itemName not in self.kh2seedsave["SoldEquipment"]: self.kh2.write_bytes(self.kh2.base_address + self.Save + itemData.memaddr, (1).to_bytes(1, 'big'), 1) @@ -679,16 +717,18 @@ class KH2Context(CommonContext): Equipment_Anchor_List = self.Equipment_Anchor_Dict["Accessories"] else: Equipment_Anchor_List = self.Equipment_Anchor_Dict["Armor"] - if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + itemData.memaddr, 1), - "big") != 1: # Checking form anchors for the equipment - for slot in Equipment_Anchor_List: - if self.kh2.read_short(self.kh2.base_address + self.Save + slot) == itemData.kh2id: - isThere = True + for slot in Equipment_Anchor_List: + if self.kh2.read_short(self.kh2.base_address + self.Save + slot) == itemData.kh2id: + isThere = True + if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + itemData.memaddr, 1), + "big") != 0: self.kh2.write_bytes(self.kh2.base_address + self.Save + itemData.memaddr, (0).to_bytes(1, 'big'), 1) - break - if not isThere: + break + if not isThere and itemName not in self.kh2seedsave["SoldEquipment"]: + if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + itemData.memaddr, 1), + "big") != 1: self.kh2.write_bytes(self.kh2.base_address + self.Save + itemData.memaddr, (1).to_bytes(1, 'big'), 1) @@ -736,7 +776,8 @@ class KH2Context(CommonContext): # Ap Boots start at +50 for some reason if itemName == "AP Boost": amountOfUsedBoosts -= 50 - if (amountOfBoostsInInvo + amountOfUsedBoosts) <= amountOfItems and amountOfBoostsInInvo < 255: + totalBoosts = (amountOfBoostsInInvo + amountOfUsedBoosts) + if totalBoosts <= amountOfItems - self.kh2seedsave["SoldBoosts"][itemName] and amountOfBoostsInInvo < 255: self.kh2.write_bytes(self.kh2.base_address + self.Save + itemData.memaddr, (amountOfBoostsInInvo + 1).to_bytes(1, 'big'), 1) @@ -821,9 +862,9 @@ async def kh2_watcher(ctx: KH2Context): currentWorld = int.from_bytes(ctx.kh2.read_bytes(ctx.kh2.base_address + 0x0714DB8, 1), "big") if location not in ctx.kh2seedsave["worldIdChecks"][str(currentWorld)]: ctx.kh2seedsave["worldIdChecks"][str(currentWorld)].append(location) - if location in ctx.kh2LocalItems: - item = ctx.kh2slotdata["LocalItems"][str(location)] - await asyncio.create_task(ctx.give_item(item, "LocalItems")) + if location in ctx.kh2LocalItems: + item = ctx.kh2slotdata["LocalItems"][str(location)] + await asyncio.create_task(ctx.give_item(item, "LocalItems")) await ctx.send_msgs(message) elif not ctx.kh2connected and ctx.serverconneced: logger.info("Game is not open. Disconnecting from Server.") diff --git a/worlds/kh2/Items.py b/worlds/kh2/Items.py index 8e5e004f..aa0e326c 100644 --- a/worlds/kh2/Items.py +++ b/worlds/kh2/Items.py @@ -856,31 +856,19 @@ Progression_Dicts = { ItemName.NamineSketches }, "AllVisitLocking": { - ItemName.CastleKey, - ItemName.CastleKey, - ItemName.BattlefieldsofWar, - ItemName.BattlefieldsofWar, - ItemName.SwordoftheAncestor, - ItemName.SwordoftheAncestor, - ItemName.BeastsClaw, - ItemName.BeastsClaw, - ItemName.BoneFist, - ItemName.BoneFist, - ItemName.ProudFang, - ItemName.ProudFang, - ItemName.SkillandCrossbones, - ItemName.SkillandCrossbones, - ItemName.Scimitar, - ItemName.Scimitar, - ItemName.MembershipCard, - ItemName.MembershipCard, - ItemName.WaytotheDawn, - ItemName.IdentityDisk, - ItemName.IdentityDisk, - ItemName.IceCream, - ItemName.IceCream, - ItemName.IceCream, - ItemName.NamineSketches, + ItemName.CastleKey: 2, + ItemName.BattlefieldsofWar: 2, + ItemName.SwordoftheAncestor: 2, + ItemName.BeastsClaw: 2, + ItemName.BoneFist: 2, + ItemName.ProudFang: 2, + ItemName.SkillandCrossbones: 2, + ItemName.Scimitar: 2, + ItemName.MembershipCard: 2, + ItemName.WaytotheDawn: 1, + ItemName.IdentityDisk: 2, + ItemName.IceCream: 3, + ItemName.NamineSketches: 1, } } diff --git a/worlds/kh2/Locations.py b/worlds/kh2/Locations.py index 87ec02be..9b5cc552 100644 --- a/worlds/kh2/Locations.py +++ b/worlds/kh2/Locations.py @@ -1483,7 +1483,6 @@ exclusion_table = { LocationName.OasisMap, LocationName.OasisTornPages, LocationName.OasisAPBoost, - LocationName.StationofSerenityPotion, LocationName.StationofCallingPotion, LocationName.CentralStationPotion1, LocationName.STTCentralStationHiPotion, diff --git a/worlds/kh2/Options.py b/worlds/kh2/Options.py index 03f60f28..671d8532 100644 --- a/worlds/kh2/Options.py +++ b/worlds/kh2/Options.py @@ -59,7 +59,7 @@ class SummonEXP(Range): class Schmovement(Choice): - """Level of Growth You Start With""" + """Level of Progressive Movement You Start With""" display_name = "Schmovement" option_level_0 = 0 option_level_1 = 1 @@ -106,9 +106,10 @@ class Visitlocking(Choice): class RandomVisitLockingItem(Range): + """Start with random amount of visit locking items.""" display_name = "Random Visit Locking Item" range_start = 0 - range_end = 27 + range_end = 25 default = 3 @@ -191,7 +192,7 @@ class LuckyEmblemsRequired(Range): """Number of Lucky Emblems to collect to Open The Final Door bosses. If Goal is not Lucky Emblem Hunt this does nothing.""" display_name = "Lucky Emblems Required" - range_start = 0 + range_start = 1 range_end = 60 default = 25 @@ -200,7 +201,7 @@ class LuckyEmblemsAmount(Range): """Number of Lucky Emblems that are in the pool. If Goal is not Lucky Emblem Hunt this does nothing.""" display_name = "Lucky Emblems Available" - range_start = 0 + range_start = 1 range_end = 60 default = 40 @@ -209,7 +210,7 @@ class BountyRequired(Range): """Number of Bounties that are Required. If Goal is not Hitlist this does nothing.""" display_name = "Bounties Required" - range_start = 0 + range_start = 1 range_end = 24 default = 7 @@ -218,7 +219,7 @@ class BountyAmount(Range): """Number of Bounties that are in the pool. If Goal is not Hitlist this does nothing.""" display_name = "Bounties Available" - range_start = 0 + range_start = 1 range_end = 24 default = 13 diff --git a/worlds/kh2/__init__.py b/worlds/kh2/__init__.py index 99037ec4..e36c81e8 100644 --- a/worlds/kh2/__init__.py +++ b/worlds/kh2/__init__.py @@ -52,12 +52,12 @@ class KH2World(World): self.goofy_ability_pool = list() self.sora_keyblade_ability_pool = list() self.keyblade_slot_copy = list(Locations.Keyblade_Slots.keys()) + self.keyblade_slot_copy.remove(LocationName.KingdomKeySlot) self.totalLocations = len(all_locations.items()) self.growth_list = list() for x in range(4): self.growth_list.extend(Movement_Table.keys()) - self.visitlockingitem = list() - self.visitlockingitem.extend(Progression_Dicts["AllVisitLocking"]) + self.visitlocking_dict = Progression_Dicts["AllVisitLocking"] def fill_slot_data(self) -> dict: return {"hitlist": self.hitlist, @@ -94,7 +94,7 @@ class KH2World(World): self.sora_keyblade_ability_pool.extend(SupportAbility_Table.keys()) for item, value in self.multiworld.start_inventory[self.player].value.items(): - if item in ActionAbility_Table.keys() or item in SupportAbility_Table.keys(): + if item in ActionAbility_Table.keys() or item in SupportAbility_Table.keys() or exclusionItem_table["StatUps"]: # cannot have more than the quantity for abilties if value > item_dictionary_table[item].quantity: logging.info(f"{self.multiworld.get_file_safe_player_name(self.player)} cannot have more than {item_dictionary_table[item].quantity} of {item}" @@ -216,6 +216,15 @@ class KH2World(World): self.RandomSuperBoss.remove(randomBoss) self.totalLocations -= 1 + # Kingdom Key cannot have No Experience so plandoed here instead of checking 26 times if its kingdom key + random_ability = self.multiworld.per_slot_randoms[self.player].choice(self.sora_keyblade_ability_pool) + while random_ability == ItemName.NoExperience: + random_ability = self.multiworld.per_slot_randoms[self.player].choice(self.sora_keyblade_ability_pool) + self.multiworld.get_location(LocationName.KingdomKeySlot, self.player).place_locked_item(self.create_item(random_ability)) + self.item_quantity_dict[random_ability] -= 1 + self.sora_keyblade_ability_pool.remove(random_ability) + self.totalLocations -= 1 + # plando keyblades because they can only have abilities for keyblade in self.keyblade_slot_copy: random_ability = self.multiworld.per_slot_randoms[self.player].choice(self.sora_keyblade_ability_pool) @@ -266,25 +275,33 @@ class KH2World(World): # no visit locking if self.multiworld.Visitlocking[self.player] == "no_visit_locking": - for item in self.visitlockingitem: - self.multiworld.push_precollected(self.create_item(item)) - self.item_quantity_dict[item] -= 1 - self.visitlockingitem.remove(item) + for item, amount in Progression_Dicts["AllVisitLocking"].items(): + for _ in range(amount): + self.multiworld.push_precollected(self.create_item(item)) + self.item_quantity_dict[item] -= 1 + if self.visitlocking_dict[item] == 0: + self.visitlocking_dict.pop(item) + self.visitlocking_dict[item] -= 1 # first and second visit locking elif self.multiworld.Visitlocking[self.player] == "second_visit_locking": for item in Progression_Dicts["2VisitLocking"]: self.item_quantity_dict[item] -= 1 + self.visitlocking_dict[item] -= 1 + if self.visitlocking_dict[item] == 0: + self.visitlocking_dict.pop(item) self.multiworld.push_precollected(self.create_item(item)) - self.visitlockingitem.remove(item) for _ in range(self.multiworld.RandomVisitLockingItem[self.player].value): - if len(self.visitlockingitem) <= 0: + if sum(self.visitlocking_dict.values()) <= 0: break - item = self.multiworld.per_slot_randoms[self.player].choice(self.visitlockingitem) + visitlocking_set = list(self.visitlocking_dict.keys()) + item = self.multiworld.per_slot_randoms[self.player].choice(visitlocking_set) self.item_quantity_dict[item] -= 1 + self.visitlocking_dict[item] -= 1 + if self.visitlocking_dict[item] == 0: + self.visitlocking_dict.pop(item) self.multiworld.push_precollected(self.create_item(item)) - self.visitlockingitem.remove(item) # there are levels but level 1 is there to keep code clean if self.multiworld.LevelDepth[self.player] == "level_99_sanity":