diff --git a/SNIClient.py b/SNIClient.py index ba13818f..c155073f 100644 --- a/SNIClient.py +++ b/SNIClient.py @@ -201,7 +201,8 @@ async def deathlink_kill_player(ctx: Context): snes_buffered_write(ctx, WRAM_START + 0x0373, bytes([8])) # deal 1 full heart of damage at next opportunity elif ctx.game == GAME_SM: - snes_buffered_write(ctx, WRAM_START + 0x09C2, bytes([0, 0])) # set current health to 0 + snes_buffered_write(ctx, WRAM_START + 0x09C2, bytes([1, 0])) # set current health to 1 (to prevent saving with 0 energy) + snes_buffered_write(ctx, WRAM_START + 0x0A50, bytes([255])) # deal 255 of damage at next opportunity if not ctx.death_link_allow_survive: snes_buffered_write(ctx, WRAM_START + 0x09D6, bytes([0, 0])) # set current reserve to 0 await snes_flush_writes(ctx) @@ -266,6 +267,7 @@ SM_RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte SM_RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte SM_DEATH_LINK_ACTIVE_ADDR = ROM_START + 0x277f04 # 1 byte +SM_REMOTE_ITEM_FLAG_ADDR = ROM_START + 0x277f06 # 1 byte # SMZ3 SMZ3_ROMNAME_START = 0x00FFC0 @@ -983,7 +985,8 @@ async def game_watcher(ctx: Context): continue elif game_name[:2] == b"SM": ctx.game = GAME_SM - ctx.items_handling = 0b001 # full local + item_handling = await snes_read(ctx, SM_REMOTE_ITEM_FLAG_ADDR, 1) + ctx.items_handling = 0b001 if item_handling is None else item_handling[0] else: game_name = await snes_read(ctx, SMZ3_ROMNAME_START, 3) if game_name == b"ZSM": @@ -1133,13 +1136,15 @@ async def game_watcher(ctx: Context): itemOutPtr = data[2] | (data[3] << 8) from worlds.sm.Items import items_start_id + from worlds.sm.Locations import locations_start_id if itemOutPtr < len(ctx.items_received): item = ctx.items_received[itemOutPtr] itemId = item.item - items_start_id + locationId = (item.location - locations_start_id) if item.location >= 0 else 0x00 playerID = item.player if item.player <= SM_ROM_PLAYER_LIMIT else 0 snes_buffered_write(ctx, SM_RECV_PROGRESS_ADDR + itemOutPtr * 4, bytes( - [playerID & 0xFF, (playerID >> 8) & 0xFF, itemId & 0xFF, (itemId >> 8) & 0xFF])) + [playerID & 0xFF, (playerID >> 8) & 0xFF, itemId & 0xFF, locationId & 0xFF])) itemOutPtr += 1 snes_buffered_write(ctx, SM_RECV_PROGRESS_ADDR + 0x602, bytes([itemOutPtr & 0xFF, (itemOutPtr >> 8) & 0xFF])) diff --git a/worlds/sm/Options.py b/worlds/sm/Options.py index 18991562..dc696a74 100644 --- a/worlds/sm/Options.py +++ b/worlds/sm/Options.py @@ -52,6 +52,10 @@ class DeathLink(Choice): alias_true = 1 default = 0 +class RemoteItems(Toggle): + """Indicates you get items sent from your own world. This allows coop play of a world.""" + display_name = "Remote Items" + class MaxDifficulty(Choice): """Depending on the perceived difficulties of the techniques, bosses, hell runs etc. from the preset, it will prevent the Randomizer from placing an item in a location too difficult to reach with the current items.""" display_name = "Maximum Difficulty" @@ -225,6 +229,7 @@ sm_options: typing.Dict[str, type(Option)] = { "start_inventory_removes_from_pool": StartItemsRemovesFromPool, "preset": Preset, "start_location": StartLocation, + "remote_items": RemoteItems, "death_link": DeathLink, #"majors_split": "Full", #"scav_num_locs": "10", diff --git a/worlds/sm/__init__.py b/worlds/sm/__init__.py index 59d6d780..dee37537 100644 --- a/worlds/sm/__init__.py +++ b/worlds/sm/__init__.py @@ -91,6 +91,8 @@ class SMWorld(World): if (self.variaRando.args.morphPlacement == "early"): self.world.local_items[self.player].value.add('Morph') + self.remote_items = self.world.remote_items[self.player] + if (len(self.variaRando.randoExec.setup.restrictedLocs) > 0): self.world.accessibility[self.player] = self.world.accessibility[self.player].from_text("items") logger.warning(f"accessibility forced to 'items' for player {self.world.get_player_name(self.player)} because of 'fun' settings") @@ -285,6 +287,7 @@ class SMWorld(World): openTourianGreyDoors = {0x07C823 + 5: [0x0C], 0x07C831 + 5: [0x0C]} deathLink = {0x277f04: [self.world.death_link[self.player].value]} + remoteItem = {0x277f06: self.getWordArray(0b001 + (0b010 if self.remote_items else 0b000))} playerNames = {} playerNameIDMap = {} @@ -299,6 +302,7 @@ class SMWorld(World): 'offworldSprites': offworldSprites, 'openTourianGreyDoors': openTourianGreyDoors, 'deathLink': deathLink, + 'remoteItem': remoteItem, 'PlayerName': playerNames, 'PlayerNameIDMap': playerNameIDMap} romPatcher.applyIPSPatchDict(patchDict) @@ -310,6 +314,7 @@ class SMWorld(World): self.romName.extend([0] * (21 - len(self.romName))) romPatcher.applyIPSPatch('ROMName', { 'ROMName': {0x1C4F00 : self.romName, 0x007FC0 : self.romName} }) + startItemROMAddressBase = 0x2FD8B9 # current, base value or bitmask, max, base value or bitmask diff --git a/worlds/sm/variaRandomizer/patches/common/ips/basepatch.ips b/worlds/sm/variaRandomizer/patches/common/ips/basepatch.ips index 1acf93ef..b0b34963 100644 Binary files a/worlds/sm/variaRandomizer/patches/common/ips/basepatch.ips and b/worlds/sm/variaRandomizer/patches/common/ips/basepatch.ips differ