618 lines
18 KiB
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()
|