Archipelago/data/lua/connector_tloz.lua

618 lines
18 KiB
Lua

--Shamelessly based off the FF1 lua
local socket = require("socket")
local json = require('json')
local math = require('math')
require("common")
local STATE_OK = "Ok"
local STATE_TENTATIVELY_CONNECTED = "Tentatively Connected"
local STATE_INITIAL_CONNECTION_MADE = "Initial Connection Made"
local STATE_UNINITIALIZED = "Uninitialized"
local consumableStacks = nil
local prevstate = ""
local curstate = STATE_UNINITIALIZED
local zeldaSocket = nil
local frame = 0
local gameMode = 0
local cave_index
local triforce_byte
local game_state
local isNesHawk = false
local shopsChecked = {}
local shopSlotLeft = 0x0628
local shopSlotMiddle = 0x0629
local shopSlotRight = 0x062A
--N.B.: you won't find these in a RAM map. They're flag values that the base patch derives from the cave ID.
local blueRingShopBit = 0x40
local potionShopBit = 0x02
local arrowShopBit = 0x08
local candleShopBit = 0x10
local shieldShopBit = 0x20
local takeAnyCaveBit = 0x01
local sword = 0x0657
local bombs = 0x0658
local maxBombs = 0x067C
local keys = 0x066E
local arrow = 0x0659
local bow = 0x065A
local candle = 0x065B
local recorder = 0x065C
local food = 0x065D
local waterOfLife = 0x065E
local magicalRod = 0x065F
local raft = 0x0660
local bookOfMagic = 0x0661
local ring = 0x0662
local stepladder = 0x0663
local magicalKey = 0x0664
local powerBracelet = 0x0665
local letter = 0x0666
local clockItem = 0x066C
local heartContainers = 0x066F
local partialHearts = 0x0670
local triforceFragments = 0x0671
local boomerang = 0x0674
local magicalBoomerang = 0x0675
local magicalShield = 0x0676
local rupeesToAdd = 0x067D
local rupeesToSubtract = 0x067E
local itemsObtained = 0x0677
local takeAnyCavesChecked = 0x0678
local localTriforce = 0x0679
local bonusItemsObtained = 0x067A
local itemsObtainedHigh = 0x067B
itemAPids = {
["Boomerang"] = 7100,
["Bow"] = 7101,
["Magical Boomerang"] = 7102,
["Raft"] = 7103,
["Stepladder"] = 7104,
["Recorder"] = 7105,
["Magical Rod"] = 7106,
["Red Candle"] = 7107,
["Book of Magic"] = 7108,
["Magical Key"] = 7109,
["Red Ring"] = 7110,
["Silver Arrow"] = 7111,
["Sword"] = 7112,
["White Sword"] = 7113,
["Magical Sword"] = 7114,
["Heart Container"] = 7115,
["Letter"] = 7116,
["Magical Shield"] = 7117,
["Candle"] = 7118,
["Arrow"] = 7119,
["Food"] = 7120,
["Water of Life (Blue)"] = 7121,
["Water of Life (Red)"] = 7122,
["Blue Ring"] = 7123,
["Triforce Fragment"] = 7124,
["Power Bracelet"] = 7125,
["Small Key"] = 7126,
["Bomb"] = 7127,
["Recovery Heart"] = 7128,
["Five Rupees"] = 7129,
["Rupee"] = 7130,
["Clock"] = 7131,
["Fairy"] = 7132
}
itemCodes = {
["Boomerang"] = 0x1D,
["Bow"] = 0x0A,
["Magical Boomerang"] = 0x1E,
["Raft"] = 0x0C,
["Stepladder"] = 0x0D,
["Recorder"] = 0x05,
["Magical Rod"] = 0x10,
["Red Candle"] = 0x07,
["Book of Magic"] = 0x11,
["Magical Key"] = 0x0B,
["Red Ring"] = 0x13,
["Silver Arrow"] = 0x09,
["Sword"] = 0x01,
["White Sword"] = 0x02,
["Magical Sword"] = 0x03,
["Heart Container"] = 0x1A,
["Letter"] = 0x15,
["Magical Shield"] = 0x1C,
["Candle"] = 0x06,
["Arrow"] = 0x08,
["Food"] = 0x04,
["Water of Life (Blue)"] = 0x1F,
["Water of Life (Red)"] = 0x20,
["Blue Ring"] = 0x12,
["Triforce Fragment"] = 0x1B,
["Power Bracelet"] = 0x14,
["Small Key"] = 0x19,
["Bomb"] = 0x00,
["Recovery Heart"] = 0x22,
["Five Rupees"] = 0x0F,
["Rupee"] = 0x18,
["Clock"] = 0x21,
["Fairy"] = 0x23
}
--Sets correct memory access functions based on whether NesHawk or QuickNES is loaded
local function defineMemoryFunctions()
local memDomain = {}
local domains = memory.getmemorydomainlist()
if domains[1] == "System Bus" then
--NesHawk
isNesHawk = true
memDomain["systembus"] = function() memory.usememorydomain("System Bus") end
memDomain["ram"] = function() memory.usememorydomain("RAM") end
memDomain["saveram"] = function() memory.usememorydomain("Battery RAM") end
memDomain["rom"] = function() memory.usememorydomain("PRG ROM") end
elseif domains[1] == "WRAM" then
--QuickNES
memDomain["systembus"] = function() memory.usememorydomain("System Bus") end
memDomain["ram"] = function() memory.usememorydomain("RAM") end
memDomain["saveram"] = function() memory.usememorydomain("WRAM") end
memDomain["rom"] = function() memory.usememorydomain("PRG ROM") end
end
return memDomain
end
local memDomain = defineMemoryFunctions()
u8 = memory.read_u8
wU8 = memory.write_u8
uRange = memory.readbyterange
itemIDNames = {}
for key, value in pairs(itemAPids) do
itemIDNames[value] = key
end
local function getItemsObtained()
return bit.bor(bit.lshift(u8(itemsObtainedHigh), 8), u8(itemsObtained))
end
local function setItemsObtained(value)
wU8(itemsObtainedHigh, bit.rshift(value, 8))
wU8(itemsObtained, bit.band(value, 0xFF))
end
local function determineItem(array)
memdomain.ram()
currentItemsObtained = getItemsObtained()
end
local function gotSword()
local currentSword = u8(sword)
wU8(sword, math.max(currentSword, 1))
end
local function gotWhiteSword()
local currentSword = u8(sword)
wU8(sword, math.max(currentSword, 2))
end
local function gotMagicalSword()
wU8(sword, 3)
end
local function gotBomb()
local currentBombs = u8(bombs)
local currentMaxBombs = u8(maxBombs)
wU8(bombs, math.min(currentBombs + 4, currentMaxBombs))
wU8(0x505, 0x29) -- Fake bomb to show item get.
end
local function gotArrow()
local currentArrow = u8(arrow)
wU8(arrow, math.max(currentArrow, 1))
end
local function gotSilverArrow()
wU8(arrow, 2)
end
local function gotBow()
wU8(bow, 1)
end
local function gotCandle()
local currentCandle = u8(candle)
wU8(candle, math.max(currentCandle, 1))
end
local function gotRedCandle()
wU8(candle, 2)
end
local function gotRecorder()
wU8(recorder, 1)
end
local function gotFood()
wU8(food, 1)
end
local function gotWaterOfLifeBlue()
local currentWaterOfLife = u8(waterOfLife)
wU8(waterOfLife, math.max(currentWaterOfLife, 1))
end
local function gotWaterOfLifeRed()
wU8(waterOfLife, 2)
end
local function gotMagicalRod()
wU8(magicalRod, 1)
end
local function gotBookOfMagic()
wU8(bookOfMagic, 1)
end
local function gotRaft()
wU8(raft, 1)
end
local function gotBlueRing()
local currentRing = u8(ring)
wU8(ring, math.max(currentRing, 1))
memDomain.saveram()
local currentTunicColor = u8(0x0B92)
if currentTunicColor == 0x29 then
wU8(0x0B92, 0x32)
wU8(0x0804, 0x32)
end
end
local function gotRedRing()
wU8(ring, 2)
memDomain.saveram()
wU8(0x0B92, 0x16)
wU8(0x0804, 0x16)
end
local function gotStepladder()
wU8(stepladder, 1)
end
local function gotMagicalKey()
wU8(magicalKey, 1)
end
local function gotPowerBracelet()
wU8(powerBracelet, 1)
end
local function gotLetter()
wU8(letter, 1)
end
local function gotHeartContainer()
local currentHeartContainers = bit.rshift(bit.band(u8(heartContainers), 0xF0), 4)
if currentHeartContainers < 16 then
currentHeartContainers = math.min(currentHeartContainers + 1, 16)
local currentHearts = bit.band(u8(heartContainers), 0x0F) + 1
wU8(heartContainers, bit.lshift(currentHeartContainers, 4) + currentHearts)
end
end
local function gotTriforceFragment()
local triforceByte = 0xFF
local newTriforceCount = u8(localTriforce) + 1
wU8(localTriforce, newTriforceCount)
end
local function gotBoomerang()
wU8(boomerang, 1)
end
local function gotMagicalBoomerang()
wU8(magicalBoomerang, 1)
end
local function gotMagicalShield()
wU8(magicalShield, 1)
end
local function gotRecoveryHeart()
local currentHearts = bit.band(u8(heartContainers), 0x0F)
local currentHeartContainers = bit.rshift(bit.band(u8(heartContainers), 0xF0), 4)
if currentHearts < currentHeartContainers then
currentHearts = currentHearts + 1
else
wU8(partialHearts, 0xFF)
end
currentHearts = bit.bor(bit.band(u8(heartContainers), 0xF0), currentHearts)
wU8(heartContainers, currentHearts)
end
local function gotFairy()
local currentHearts = bit.band(u8(heartContainers), 0x0F)
local currentHeartContainers = bit.rshift(bit.band(u8(heartContainers), 0xF0), 4)
if currentHearts < currentHeartContainers then
currentHearts = currentHearts + 3
if currentHearts > currentHeartContainers then
currentHearts = currentHeartContainers
wU8(partialHearts, 0xFF)
end
else
wU8(partialHearts, 0xFF)
end
currentHearts = bit.bor(bit.band(u8(heartContainers), 0xF0), currentHearts)
wU8(heartContainers, currentHearts)
end
local function gotClock()
wU8(clockItem, 1)
end
local function gotFiveRupees()
local currentRupeesToAdd = u8(rupeesToAdd)
wU8(rupeesToAdd, math.min(currentRupeesToAdd + 5, 255))
end
local function gotSmallKey()
wU8(keys, math.min(u8(keys) + 1, 9))
end
local function gotItem(item)
--Write itemCode to itemToLift
--Write 128 to itemLiftTimer
--Write 4 to sound effect queue
itemName = itemIDNames[item]
itemCode = itemCodes[itemName]
wU8(0x505, itemCode)
wU8(0x506, 128)
wU8(0x602, 4)
numberObtained = getItemsObtained() + 1
setItemsObtained(numberObtained)
if itemName == "Boomerang" then gotBoomerang() end
if itemName == "Bow" then gotBow() end
if itemName == "Magical Boomerang" then gotMagicalBoomerang() end
if itemName == "Raft" then gotRaft() end
if itemName == "Stepladder" then gotStepladder() end
if itemName == "Recorder" then gotRecorder() end
if itemName == "Magical Rod" then gotMagicalRod() end
if itemName == "Red Candle" then gotRedCandle() end
if itemName == "Book of Magic" then gotBookOfMagic() end
if itemName == "Magical Key" then gotMagicalKey() end
if itemName == "Red Ring" then gotRedRing() end
if itemName == "Silver Arrow" then gotSilverArrow() end
if itemName == "Sword" then gotSword() end
if itemName == "White Sword" then gotWhiteSword() end
if itemName == "Magical Sword" then gotMagicalSword() end
if itemName == "Heart Container" then gotHeartContainer() end
if itemName == "Letter" then gotLetter() end
if itemName == "Magical Shield" then gotMagicalShield() end
if itemName == "Candle" then gotCandle() end
if itemName == "Arrow" then gotArrow() end
if itemName == "Food" then gotFood() end
if itemName == "Water of Life (Blue)" then gotWaterOfLifeBlue() end
if itemName == "Water of Life (Red)" then gotWaterOfLifeRed() end
if itemName == "Blue Ring" then gotBlueRing() end
if itemName == "Triforce Fragment" then gotTriforceFragment() end
if itemName == "Power Bracelet" then gotPowerBracelet() end
if itemName == "Small Key" then gotSmallKey() end
if itemName == "Bomb" then gotBomb() end
if itemName == "Recovery Heart" then gotRecoveryHeart() end
if itemName == "Five Rupees" then gotFiveRupees() end
if itemName == "Fairy" then gotFairy() end
if itemName == "Clock" then gotClock() end
end
local function StateOKForMainLoop()
memDomain.ram()
local gameMode = u8(0x12)
return gameMode == 5
end
local function checkCaveItemObtained()
memDomain.ram()
local returnTable = {}
returnTable["slot1"] = u8(shopSlotLeft)
returnTable["slot2"] = u8(shopSlotMiddle)
returnTable["slot3"] = u8(shopSlotRight)
returnTable["takeAnys"] = u8(takeAnyCavesChecked)
return returnTable
end
function generateOverworldLocationChecked()
memDomain.ram()
data = uRange(0x067E, 0x81)
data[0] = nil
return data
end
function getHCLocation()
memDomain.rom()
data = u8(0x1789A)
return data
end
function getPBLocation()
memDomain.rom()
data = u8(0x10CB2)
return data
end
function generateUnderworld16LocationChecked()
memDomain.ram()
data = uRange(0x06FE, 0x81)
data[0] = nil
return data
end
function generateUnderworld79LocationChecked()
memDomain.ram()
data = uRange(0x077E, 0x81)
data[0] = nil
return data
end
function updateTriforceFragments()
memDomain.ram()
local triforceByte = 0xFF
totalTriforceCount = u8(localTriforce)
local currentPieces = bit.rshift(triforceByte, 8 - math.min(8, totalTriforceCount))
wU8(triforceFragments, currentPieces)
end
function processBlock(block)
if block ~= nil then
local msgBlock = block['messages']
if msgBlock ~= nil then
for i, v in pairs(msgBlock) do
if itemMessages[i] == nil then
local msg = {TTL=450, message=v, color=0xFFFF0000}
itemMessages[i] = msg
end
end
end
local bonusItems = block["bonusItems"]
if bonusItems ~= nil and isInGame then
for i, item in ipairs(bonusItems) do
memDomain.ram()
if i > u8(bonusItemsObtained) then
if u8(0x505) == 0 then
gotItem(item)
setItemsObtained(getItemsObtained() - 1)
wU8(bonusItemsObtained, u8(bonusItemsObtained) + 1)
end
end
end
end
local itemsBlock = block["items"]
memDomain.saveram()
isInGame = StateOKForMainLoop()
updateTriforceFragments()
if itemsBlock ~= nil and isInGame then
memDomain.ram()
--get item from item code
--get function from item
--do function
for i, item in ipairs(itemsBlock) do
memDomain.ram()
if u8(0x505) == 0 then
if i > getItemsObtained() then
gotItem(item)
end
end
end
end
local shopsBlock = block["shops"]
if shopsBlock ~= nil then
wU8(shopSlotLeft, bit.bor(u8(shopSlotLeft), shopsBlock["left"]))
wU8(shopSlotMiddle, bit.bor(u8(shopSlotMiddle), shopsBlock["middle"]))
wU8(shopSlotRight, bit.bor(u8(shopSlotRight), shopsBlock["right"]))
end
end
end
function receive()
l, e = zeldaSocket:receive()
if e == 'closed' then
if curstate == STATE_OK then
print("Connection closed")
end
curstate = STATE_UNINITIALIZED
return
elseif e == 'timeout' then
print("timeout")
return
elseif e ~= nil then
print(e)
curstate = STATE_UNINITIALIZED
return
end
processBlock(json.decode(l))
-- Determine Message to send back
memDomain.rom()
local playerName = uRange(0x1F, 0x11)
playerName[0] = nil
local retTable = {}
retTable["playerName"] = playerName
if StateOKForMainLoop() then
retTable["overworld"] = generateOverworldLocationChecked()
retTable["underworld1"] = generateUnderworld16LocationChecked()
retTable["underworld2"] = generateUnderworld79LocationChecked()
end
retTable["caves"] = checkCaveItemObtained()
memDomain.ram()
if gameMode ~= 19 then
gameMode = u8(0x12)
end
retTable["gameMode"] = gameMode
retTable["overworldHC"] = getHCLocation()
retTable["overworldPB"] = getPBLocation()
retTable["itemsObtained"] = getItemsObtained()
msg = json.encode(retTable).."\n"
local ret, error = zeldaSocket:send(msg)
if ret == nil then
print(error)
elseif curstate == STATE_INITIAL_CONNECTION_MADE then
curstate = STATE_TENTATIVELY_CONNECTED
elseif curstate == STATE_TENTATIVELY_CONNECTED then
print("Connected!")
itemMessages["(0,0)"] = {TTL=240, message="Connected", color="green"}
curstate = STATE_OK
end
end
function main()
if not checkBizHawkVersion() then
return
end
server, error = socket.bind('localhost', 52980)
while true do
gui.drawEllipse(248, 9, 6, 6, "Black", "Yellow")
frame = frame + 1
drawMessages()
if not (curstate == prevstate) then
-- console.log("Current state: "..curstate)
prevstate = curstate
end
if (curstate == STATE_OK) or (curstate == STATE_INITIAL_CONNECTION_MADE) or (curstate == STATE_TENTATIVELY_CONNECTED) then
if (frame % 60 == 0) then
gui.drawEllipse(248, 9, 6, 6, "Black", "Blue")
receive()
else
gui.drawEllipse(248, 9, 6, 6, "Black", "Green")
end
elseif (curstate == STATE_UNINITIALIZED) then
gui.drawEllipse(248, 9, 6, 6, "Black", "White")
if (frame % 60 == 0) then
gui.drawEllipse(248, 9, 6, 6, "Black", "Yellow")
drawText(5, 8, "Waiting for client", 0xFFFF0000)
drawText(5, 32, "Please start Zelda1Client.exe", 0xFFFF0000)
-- Advance so the messages are drawn
emu.frameadvance()
server:settimeout(2)
print("Attempting to connect")
local client, timeout = server:accept()
if timeout == nil then
-- print('Initial Connection Made')
curstate = STATE_INITIAL_CONNECTION_MADE
zeldaSocket = client
zeldaSocket:settimeout(0)
end
end
end
emu.frameadvance()
end
end
main()