mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
Links Awakening: Implement New Game (#1334)
Adds Link's Awakening: DX. Fully imports and forks LADXR, with permission - https://github.com/daid/LADXR
This commit is contained in:
283
worlds/ladx/ItemTracker.py
Normal file
283
worlds/ladx/ItemTracker.py
Normal file
@@ -0,0 +1,283 @@
|
||||
import json
|
||||
gameStateAddress = 0xDB95
|
||||
validGameStates = {0x0B, 0x0C}
|
||||
gameStateResetThreshold = 0x06
|
||||
|
||||
inventorySlotCount = 16
|
||||
inventoryStartAddress = 0xDB00
|
||||
inventoryEndAddress = inventoryStartAddress + inventorySlotCount
|
||||
|
||||
inventoryItemIds = {
|
||||
0x02: 'BOMB',
|
||||
0x05: 'BOW',
|
||||
0x06: 'HOOKSHOT',
|
||||
0x07: 'MAGIC_ROD',
|
||||
0x08: 'PEGASUS_BOOTS',
|
||||
0x09: 'OCARINA',
|
||||
0x0A: 'FEATHER',
|
||||
0x0B: 'SHOVEL',
|
||||
0x0C: 'MAGIC_POWDER',
|
||||
0x0D: 'BOOMERANG',
|
||||
0x0E: 'TOADSTOOL',
|
||||
0x0F: 'ROOSTER',
|
||||
}
|
||||
|
||||
dungeonKeyDoors = [
|
||||
{ # D1
|
||||
0xD907: [0x04],
|
||||
0xD909: [0x40],
|
||||
0xD90F: [0x01],
|
||||
},
|
||||
{ # D2
|
||||
0xD921: [0x02],
|
||||
0xD925: [0x02],
|
||||
0xD931: [0x02],
|
||||
0xD932: [0x08],
|
||||
0xD935: [0x04],
|
||||
},
|
||||
{ # D3
|
||||
0xD945: [0x40],
|
||||
0xD946: [0x40],
|
||||
0xD949: [0x40],
|
||||
0xD94A: [0x40],
|
||||
0xD956: [0x01, 0x02, 0x04, 0x08],
|
||||
},
|
||||
{ # D4
|
||||
0xD969: [0x04],
|
||||
0xD96A: [0x40],
|
||||
0xD96E: [0x40],
|
||||
0xD978: [0x01],
|
||||
0xD979: [0x04],
|
||||
},
|
||||
{ # D5
|
||||
0xD98C: [0x40],
|
||||
0xD994: [0x40],
|
||||
0xD99F: [0x04],
|
||||
},
|
||||
{ # D6
|
||||
0xD9C3: [0x40],
|
||||
0xD9C6: [0x40],
|
||||
0xD9D0: [0x04],
|
||||
},
|
||||
{ # D7
|
||||
0xDA10: [0x04],
|
||||
0xDA1E: [0x40],
|
||||
0xDA21: [0x40],
|
||||
},
|
||||
{ # D8
|
||||
0xDA39: [0x02],
|
||||
0xDA3B: [0x01],
|
||||
0xDA42: [0x40],
|
||||
0xDA43: [0x40],
|
||||
0xDA44: [0x40],
|
||||
0xDA49: [0x40],
|
||||
0xDA4A: [0x01],
|
||||
},
|
||||
{ # D0(9)
|
||||
0xDDE5: [0x02],
|
||||
0xDDE9: [0x04],
|
||||
0xDDF0: [0x04],
|
||||
},
|
||||
]
|
||||
|
||||
dungeonItemAddresses = [
|
||||
0xDB16, # D1
|
||||
0xDB1B, # D2
|
||||
0xDB20, # D3
|
||||
0xDB25, # D4
|
||||
0xDB2A, # D5
|
||||
0xDB2F, # D6
|
||||
0xDB34, # D7
|
||||
0xDB39, # D8
|
||||
0xDDDA, # Color Dungeon
|
||||
]
|
||||
|
||||
dungeonItemOffsets = {
|
||||
'MAP{}': 0,
|
||||
'COMPASS{}': 1,
|
||||
'STONE_BEAK{}': 2,
|
||||
'NIGHTMARE_KEY{}': 3,
|
||||
'KEY{}': 4,
|
||||
}
|
||||
|
||||
class Item:
|
||||
def __init__(self, id, address, threshold=0, mask=None, increaseOnly=False, count=False, max=None):
|
||||
self.id = id
|
||||
self.address = address
|
||||
self.threshold = threshold
|
||||
self.mask = mask
|
||||
self.increaseOnly = increaseOnly
|
||||
self.count = count
|
||||
self.value = 0 if increaseOnly else None
|
||||
self.rawValue = 0
|
||||
self.diff = 0
|
||||
self.max = max
|
||||
|
||||
def set(self, byte, extra):
|
||||
oldValue = self.value
|
||||
|
||||
if self.mask:
|
||||
byte = byte & self.mask
|
||||
|
||||
if not self.count:
|
||||
byte = int(byte > self.threshold)
|
||||
else:
|
||||
# LADX seems to store one decimal digit per nibble
|
||||
byte = byte - (byte // 16 * 6)
|
||||
|
||||
byte += extra
|
||||
|
||||
if self.max and byte > self.max:
|
||||
byte = self.max
|
||||
|
||||
if self.increaseOnly:
|
||||
if byte > self.rawValue:
|
||||
self.value += byte - self.rawValue
|
||||
else:
|
||||
self.value = byte
|
||||
|
||||
self.rawValue = byte
|
||||
|
||||
if oldValue != self.value:
|
||||
self.diff += self.value - (oldValue or 0)
|
||||
|
||||
class ItemTracker:
|
||||
def __init__(self, gameboy) -> None:
|
||||
self.gameboy = gameboy
|
||||
self.loadItems()
|
||||
pass
|
||||
extraItems = {}
|
||||
|
||||
async def readRamByte(self, byte):
|
||||
return (await self.gameboy.read_memory_cache([byte]))[byte]
|
||||
|
||||
def loadItems(self):
|
||||
self.items = [
|
||||
Item('BOMB', None),
|
||||
Item('BOW', None),
|
||||
Item('HOOKSHOT', None),
|
||||
Item('MAGIC_ROD', None),
|
||||
Item('PEGASUS_BOOTS', None),
|
||||
Item('OCARINA', None),
|
||||
Item('FEATHER', None),
|
||||
Item('SHOVEL', None),
|
||||
Item('MAGIC_POWDER', None),
|
||||
Item('BOOMERANG', None),
|
||||
Item('TOADSTOOL', None),
|
||||
Item('ROOSTER', None),
|
||||
Item('SWORD', 0xDB4E, count=True),
|
||||
Item('POWER_BRACELET', 0xDB43, count=True),
|
||||
Item('SHIELD', 0xDB44, count=True),
|
||||
Item('BOWWOW', 0xDB56),
|
||||
Item('MAX_POWDER_UPGRADE', 0xDB76, threshold=0x20),
|
||||
Item('MAX_BOMBS_UPGRADE', 0xDB77, threshold=0x30),
|
||||
Item('MAX_ARROWS_UPGRADE', 0xDB78, threshold=0x30),
|
||||
Item('TAIL_KEY', 0xDB11),
|
||||
Item('SLIME_KEY', 0xDB15),
|
||||
Item('ANGLER_KEY', 0xDB12),
|
||||
Item('FACE_KEY', 0xDB13),
|
||||
Item('BIRD_KEY', 0xDB14),
|
||||
Item('FLIPPERS', 0xDB3E),
|
||||
Item('SEASHELL', 0xDB41, count=True),
|
||||
Item('GOLD_LEAF', 0xDB42, count=True, max=5),
|
||||
Item('INSTRUMENT1', 0xDB65, mask=1 << 1),
|
||||
Item('INSTRUMENT2', 0xDB66, mask=1 << 1),
|
||||
Item('INSTRUMENT3', 0xDB67, mask=1 << 1),
|
||||
Item('INSTRUMENT4', 0xDB68, mask=1 << 1),
|
||||
Item('INSTRUMENT5', 0xDB69, mask=1 << 1),
|
||||
Item('INSTRUMENT6', 0xDB6A, mask=1 << 1),
|
||||
Item('INSTRUMENT7', 0xDB6B, mask=1 << 1),
|
||||
Item('INSTRUMENT8', 0xDB6C, mask=1 << 1),
|
||||
Item('TRADING_ITEM_YOSHI_DOLL', 0xDB40, mask=1 << 0),
|
||||
Item('TRADING_ITEM_RIBBON', 0xDB40, mask=1 << 1),
|
||||
Item('TRADING_ITEM_DOG_FOOD', 0xDB40, mask=1 << 2),
|
||||
Item('TRADING_ITEM_BANANAS', 0xDB40, mask=1 << 3),
|
||||
Item('TRADING_ITEM_STICK', 0xDB40, mask=1 << 4),
|
||||
Item('TRADING_ITEM_HONEYCOMB', 0xDB40, mask=1 << 5),
|
||||
Item('TRADING_ITEM_PINEAPPLE', 0xDB40, mask=1 << 6),
|
||||
Item('TRADING_ITEM_HIBISCUS', 0xDB40, mask=1 << 7),
|
||||
Item('TRADING_ITEM_LETTER', 0xDB7F, mask=1 << 0),
|
||||
Item('TRADING_ITEM_BROOM', 0xDB7F, mask=1 << 1),
|
||||
Item('TRADING_ITEM_FISHING_HOOK', 0xDB7F, mask=1 << 2),
|
||||
Item('TRADING_ITEM_NECKLACE', 0xDB7F, mask=1 << 3),
|
||||
Item('TRADING_ITEM_SCALE', 0xDB7F, mask=1 << 4),
|
||||
Item('TRADING_ITEM_MAGNIFYING_GLASS', 0xDB7F, mask=1 << 5),
|
||||
Item('SONG1', 0xDB49, mask=1 << 2),
|
||||
Item('SONG2', 0xDB49, mask=1 << 1),
|
||||
Item('SONG3', 0xDB49, mask=1 << 0),
|
||||
Item('RED_TUNIC', 0xDB6D, mask=1 << 0),
|
||||
Item('BLUE_TUNIC', 0xDB6D, mask=1 << 1),
|
||||
Item('GREAT_FAIRY', 0xDDE1, mask=1 << 4),
|
||||
]
|
||||
|
||||
for i in range(len(dungeonItemAddresses)):
|
||||
for item, offset in dungeonItemOffsets.items():
|
||||
if item.startswith('KEY'):
|
||||
self.items.append(Item(item.format(i + 1), dungeonItemAddresses[i] + offset, count=True))
|
||||
else:
|
||||
self.items.append(Item(item.format(i + 1), dungeonItemAddresses[i] + offset))
|
||||
|
||||
self.itemDict = {item.id: item for item in self.items}
|
||||
|
||||
async def readItems(state):
|
||||
extraItems = state.extraItems
|
||||
missingItems = {x for x in state.items if x.address == None}
|
||||
|
||||
# Add keys for opened key doors
|
||||
for i in range(len(dungeonKeyDoors)):
|
||||
item = f'KEY{i + 1}'
|
||||
extraItems[item] = 0
|
||||
|
||||
for address, masks in dungeonKeyDoors[i].items():
|
||||
for mask in masks:
|
||||
value = await state.readRamByte(address) & mask
|
||||
if value > 0:
|
||||
extraItems[item] += 1
|
||||
|
||||
# Main inventory items
|
||||
for i in range(inventoryStartAddress, inventoryEndAddress):
|
||||
value = await state.readRamByte(i)
|
||||
|
||||
if value in inventoryItemIds:
|
||||
item = state.itemDict[inventoryItemIds[value]]
|
||||
extra = extraItems[item.id] if item.id in extraItems else 0
|
||||
item.set(1, extra)
|
||||
missingItems.remove(item)
|
||||
|
||||
for item in missingItems:
|
||||
extra = extraItems[item.id] if item.id in extraItems else 0
|
||||
item.set(0, extra)
|
||||
|
||||
# All other items
|
||||
for item in [x for x in state.items if x.address]:
|
||||
extra = extraItems[item.id] if item.id in extraItems else 0
|
||||
item.set(await state.readRamByte(item.address), extra)
|
||||
|
||||
async def sendItems(self, socket, diff=False):
|
||||
if not self.items:
|
||||
return
|
||||
message = {
|
||||
"type":"item",
|
||||
"refresh": True,
|
||||
"version":"1.0",
|
||||
"diff": diff,
|
||||
"items": [],
|
||||
}
|
||||
items = self.items
|
||||
if diff:
|
||||
items = [item for item in items if item.diff != 0]
|
||||
if not items:
|
||||
return
|
||||
for item in items:
|
||||
value = item.diff if diff else item.value
|
||||
|
||||
message["items"].append(
|
||||
{
|
||||
'id': item.id,
|
||||
'qty': value,
|
||||
}
|
||||
)
|
||||
|
||||
item.diff = 0
|
||||
|
||||
await socket.send(json.dumps(message))
|
||||
Reference in New Issue
Block a user