Adventure: implement new game (#1531)

Adds Adventure for the Atari 2600, NTSC version. New randomizer, not based on prior works. Somewhat atypical of current AP rom patch games; The generator does not require the adventure rom, but writes some data to an .apadvn APContainer file that the client uses along with a base bsdiff patch to generate a final rom file.
This commit is contained in:
JusticePS
2023-03-22 07:25:55 -07:00
committed by GitHub
parent 206f8cf5ed
commit d48e1e447f
20 changed files with 3619 additions and 2 deletions

View File

@@ -0,0 +1,851 @@
local socket = require("socket")
local json = require('json')
local math = require('math')
local STATE_OK = "Ok"
local STATE_TENTATIVELY_CONNECTED = "Tentatively Connected"
local STATE_INITIAL_CONNECTION_MADE = "Initial Connection Made"
local STATE_UNINITIALIZED = "Uninitialized"
local SCRIPT_VERSION = 1
local APItemValue = 0xA2
local APItemRam = 0xE7
local BatAPItemValue = 0xAB
local BatAPItemRam = 0xEA
local PlayerRoomAddr = 0x8A -- if in number room, we're not in play mode
local WinAddr = 0xDE -- if not 0 (I think if 0xff specifically), we won (and should update once, immediately)
-- If any of these are 2, that dragon ate the player (should send update immediately
-- once, and reset that when none of them are 2 again)
local DragonState = {0xA8, 0xAD, 0xB2}
local last_dragon_state = {0, 0, 0}
local carryAddress = 0x9D -- uses rom object table
local batRoomAddr = 0xCB
local batCarryAddress = 0xD0 -- uses ram object location
local batInvalidCarryItem = 0x78
local batItemCheckAddr = 0xf69f
local batMatrixLen = 11 -- number of pairs
local last_carry_item = 0xB4
local frames_with_no_item = 0
local ItemTableStart = 0xfe9d
local PlayerSlotAddress = 0xfff9
local itemMessages = {}
local nullObjectId = 0xB4
local ItemsReceived = nil
local sha256hash = nil
local foreign_items = nil
local foreign_items_by_room = {}
local bat_no_touch_locations_by_room = {}
local bat_no_touch_items = {}
local autocollect_items = {}
local localItemLocations = {}
local prev_bat_room = 0xff
local prev_player_room = 0
local prev_ap_room_index = nil
local pending_foreign_items_collected = {}
local pending_local_items_collected = {}
local rendering_foreign_item = nil
local skip_inventory_items = {}
local inventory = {}
local next_inventory_item = nil
local input_button_address = 0xD7
local deathlink_rec = nil
local deathlink_send = 0
local deathlink_sent = false
local prevstate = ""
local curstate = STATE_UNINITIALIZED
local atariSocket = nil
local frame = 0
local ItemIndex = 0
local yorgle_speed_address = 0xf725
local grundle_speed_address = 0xf740
local rhindle_speed_address = 0xf70A
local read_switch_a = 0xf780
local read_switch_b = 0xf764
local yorgle_speed = nil
local grundle_speed = nil
local rhindle_speed = nil
local slow_yorgle_id = tostring(118000000 + 0x103)
local slow_grundle_id = tostring(118000000 + 0x104)
local slow_rhindle_id = tostring(118000000 + 0x105)
local yorgle_dead = false
local grundle_dead = false
local rhindle_dead = false
local diff_a_locked = false
local diff_b_locked = false
local bat_logic = 0
local is_dead = 0
local freeincarnates_available = 0
local send_freeincarnate_used = false
local current_bat_ap_item = nil
local was_in_number_room = false
local u8 = nil
local wU8 = nil
local u16
local bizhawk_version = client.getversion()
local is23Or24Or25 = (bizhawk_version=="2.3.1") or (bizhawk_version:sub(1,3)=="2.4") or (bizhawk_version:sub(1,3)=="2.5")
local is26To28 = (bizhawk_version:sub(1,3)=="2.6") or (bizhawk_version:sub(1,3)=="2.7") or (bizhawk_version:sub(1,3)=="2.8")
u8 = memory.read_u8
wU8 = memory.write_u8
u16 = memory.read_u16_le
function uRangeRam(address, bytes)
data = memory.read_bytes_as_array(address, bytes, "Main RAM")
return data
end
function uRangeRom(address, bytes)
data = memory.read_bytes_as_array(address+0xf000, bytes, "System Bus")
return data
end
function uRangeAddress(address, bytes)
data = memory.read_bytes_as_array(address, bytes, "System Bus")
return data
end
function table.empty (self)
for _, _ in pairs(self) do
return false
end
return true
end
function slice (tbl, s, e)
local pos, new = 1, {}
for i = s + 1, e do
new[pos] = tbl[i]
pos = pos + 1
end
return new
end
local function createForeignItemsByRoom()
foreign_items_by_room = {}
if foreign_items == nil then
return
end
for _, foreign_item in pairs(foreign_items) do
if foreign_items_by_room[foreign_item.room_id] == nil then
foreign_items_by_room[foreign_item.room_id] = {}
end
new_foreign_item = {}
new_foreign_item.room_id = foreign_item.room_id
new_foreign_item.room_x = foreign_item.room_x
new_foreign_item.room_y = foreign_item.room_y
new_foreign_item.short_location_id = foreign_item.short_location_id
table.insert(foreign_items_by_room[foreign_item.room_id], new_foreign_item)
end
end
function debugPrintNoTouchLocations()
for room_id, list in pairs(bat_no_touch_locations_by_room) do
for index, notouch_location in ipairs(list) do
print("ROOM "..tostring(room_id).. "["..tostring(index).."]: "..tostring(notouch_location.short_location_id))
end
end
end
function processBlock(block)
if block == nil then
return
end
local block_identified = 0
local msgBlock = block['messages']
if msgBlock ~= nil then
block_identified = 1
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 itemsBlock = block["items"]
if itemsBlock ~= nil then
block_identified = 1
ItemsReceived = itemsBlock
end
local apItemsBlock = block["foreign_items"]
if apItemsBlock ~= nil then
block_identified = 1
print("got foreign items block")
foreign_items = apItemsBlock
createForeignItemsByRoom()
end
local autocollectItems = block["autocollect_items"]
if autocollectItems ~= nil then
block_identified = 1
autocollect_items = {}
for _, acitem in pairs(autocollectItems) do
if autocollect_items[acitem.room_id] == nil then
autocollect_items[acitem.room_id] = {}
end
table.insert(autocollect_items[acitem.room_id], acitem)
end
end
local localLocalItemLocations = block["local_item_locations"]
if localLocalItemLocations ~= nil then
block_identified = 1
localItemLocations = localLocalItemLocations
print("got local item locations")
end
local checkedLocationsBlock = block["checked_locations"]
if checkedLocationsBlock ~= nil then
block_identified = 1
for room_id, foreign_item_list in pairs(foreign_items_by_room) do
for i, foreign_item in pairs(foreign_item_list) do
short_id = foreign_item.short_location_id
for j, checked_id in pairs(checkedLocationsBlock) do
if checked_id == short_id then
table.remove(foreign_item_list, i)
break
end
end
end
end
if foreign_items ~= nil then
for i, foreign_item in pairs(foreign_items) do
short_id = foreign_item.short_location_id
for j, checked_id in pairs(checkedLocationsBlock) do
if checked_id == short_id then
foreign_items[i] = nil
break
end
end
end
end
end
local dragon_speeds_block = block["dragon_speeds"]
if dragon_speeds_block ~= nil then
block_identified = 1
yorgle_speed = dragon_speeds_block[slow_yorgle_id]
grundle_speed = dragon_speeds_block[slow_grundle_id]
rhindle_speed = dragon_speeds_block[slow_rhindle_id]
end
local diff_a_block = block["difficulty_a_locked"]
if diff_a_block ~= nil then
block_identified = 1
diff_a_locked = diff_a_block
end
local diff_b_block = block["difficulty_b_locked"]
if diff_b_block ~= nil then
block_identified = 1
diff_b_locked = diff_b_block
end
local freeincarnates_available_block = block["freeincarnates_available"]
if freeincarnates_available_block ~= nil then
block_identified = 1
if freeincarnates_available ~= freeincarnates_available_block then
freeincarnates_available = freeincarnates_available_block
local msg = {TTL=450, message="freeincarnates: "..tostring(freeincarnates_available), color=0xFFFF0000}
itemMessages[-2] = msg
end
end
local bat_logic_block = block["bat_logic"]
if bat_logic_block ~= nil then
block_identified = 1
bat_logic = bat_logic_block
end
local bat_no_touch_locations_block = block["bat_no_touch_locations"]
if bat_no_touch_locations_block ~= nil then
block_identified = 1
for _, notouch_location in pairs(bat_no_touch_locations_block) do
local room_id = tonumber(notouch_location.room_id)
if bat_no_touch_locations_by_room[room_id] == nil then
bat_no_touch_locations_by_room[room_id] = {}
end
table.insert(bat_no_touch_locations_by_room[room_id], notouch_location)
if notouch_location.local_item ~= nil and notouch_location.local_item ~= 255 then
bat_no_touch_items[tonumber(notouch_location.local_item)] = true
-- print("no touch: "..tostring(notouch_location.local_item))
end
end
-- debugPrintNoTouchLocations()
end
deathlink_rec = deathlink_rec or block["deathlink"]
if( block_identified == 0 ) then
print("unidentified block")
print(block)
end
end
local function clearScreen()
if is23Or24Or25 then
return
elseif is26To28 then
drawText(0, 0, "", "black")
end
end
local function getMaxMessageLength()
if is23Or24Or25 then
return client.screenwidth()/11
elseif is26To28 then
return client.screenwidth()/12
end
end
function drawText(x, y, message, color)
if is23Or24Or25 then
gui.addmessage(message)
elseif is26To28 then
gui.drawText(x, y, message, color, 0xB0000000, 18, "Courier New", nil, nil, nil, "client")
end
end
local function drawMessages()
if table.empty(itemMessages) then
clearScreen()
return
end
local y = 10
found = false
maxMessageLength = getMaxMessageLength()
for k, v in pairs(itemMessages) do
if v["TTL"] > 0 then
message = v["message"]
while true do
drawText(5, y, message:sub(1, maxMessageLength), v["color"])
y = y + 16
message = message:sub(maxMessageLength + 1, message:len())
if message:len() == 0 then
break
end
end
newTTL = 0
if is26To28 then
newTTL = itemMessages[k]["TTL"] - 1
end
itemMessages[k]["TTL"] = newTTL
found = true
end
end
if found == false then
clearScreen()
end
end
function difference(a, b)
local aa = {}
for k,v in pairs(a) do aa[v]=true end
for k,v in pairs(b) do aa[v]=nil end
local ret = {}
local n = 0
for k,v in pairs(a) do
if aa[v] then n=n+1 ret[n]=v end
end
return ret
end
function getAllRam()
uRangeRAM(0,128);
return data
end
local function arrayEqual(a1, a2)
if #a1 ~= #a2 then
return false
end
for i, v in ipairs(a1) do
if v ~= a2[i] then
return false
end
end
return true
end
local function alive_mode()
return (u8(PlayerRoomAddr) ~= 0x00 and u8(WinAddr) == 0x00)
end
local function generateLocationsChecked()
list_of_locations = {}
for s, f in pairs(pending_foreign_items_collected) do
table.insert(list_of_locations, f.short_location_id + 118000000)
end
for s, f in pairs(pending_local_items_collected) do
table.insert(list_of_locations, f + 118000000)
end
return list_of_locations
end
function receive()
l, e = atariSocket:receive()
if e == 'closed' then
if curstate == STATE_OK then
print("Connection closed")
end
curstate = STATE_UNINITIALIZED
return
elseif e == 'timeout' then
return
elseif e ~= nil then
print(e)
curstate = STATE_UNINITIALIZED
return
end
if l ~= nil then
processBlock(json.decode(l))
end
-- Determine Message to send back
newSha256 = memory.hash_region(0xF000, 0x1000, "System Bus")
if (sha256hash ~= nil and sha256hash ~= newSha256) then
print("ROM changed, quitting")
curstate = STATE_UNINITIALIZED
return
end
sha256hash = newSha256
local retTable = {}
retTable["scriptVersion"] = SCRIPT_VERSION
retTable["romhash"] = sha256hash
if (alive_mode()) then
retTable["locations"] = generateLocationsChecked()
end
if (u8(WinAddr) ~= 0x00) then
retTable["victory"] = 1
end
if( deathlink_sent or deathlink_send == 0 ) then
retTable["deathLink"] = 0
else
print("Sending deathlink "..tostring(deathlink_send))
retTable["deathLink"] = deathlink_send
deathlink_sent = true
end
deathlink_send = 0
if send_freeincarnate_used == true then
print("Sending freeincarnate used")
retTable["freeincarnate"] = true
send_freeincarnate_used = false
end
msg = json.encode(retTable).."\n"
local ret, error = atariSocket: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!")
curstate = STATE_OK
end
end
function AutocollectFromRoom()
if autocollect_items ~= nil and autocollect_items[prev_player_room] ~= nil then
for _, item in pairs(autocollect_items[prev_player_room]) do
pending_foreign_items_collected[item.short_location_id] = item
end
end
end
function SetYorgleSpeed()
if yorgle_speed ~= nil then
emu.setregister("A", yorgle_speed);
end
end
function SetGrundleSpeed()
if grundle_speed ~= nil then
emu.setregister("A", grundle_speed);
end
end
function SetRhindleSpeed()
if rhindle_speed ~= nil then
emu.setregister("A", rhindle_speed);
end
end
function SetDifficultySwitchB()
if diff_b_locked then
local a = emu.getregister("A")
if a < 128 then
emu.setregister("A", a + 128)
end
end
end
function SetDifficultySwitchA()
if diff_a_locked then
local a = emu.getregister("A")
if (a > 128 and a < 128 + 64) or (a < 64) then
emu.setregister("A", a + 64)
end
end
end
function TryFreeincarnate()
if freeincarnates_available > 0 then
freeincarnates_available = freeincarnates_available - 1
for index, state_addr in pairs(DragonState) do
if last_dragon_state[index] == 1 then
send_freeincarnate_used = true
memory.write_u8(state_addr, 1, "System Bus")
local msg = {TTL=450, message="used freeincarnate", color=0xFF00FF00}
itemMessages[-1] = msg
end
end
end
end
function GetLinkedObject()
if emu.getregister("X") == batRoomAddr then
bat_interest_item = emu.getregister("A")
-- if the bat can't touch that item, we'll switch it to the number item, which should never be
-- in the same room as the bat.
if bat_no_touch_items[bat_interest_item] ~= nil then
emu.setregister("A", 0xDD )
emu.setregister("Y", 0xDD )
end
end
end
function CheckCollectAPItem(carry_item, target_item_value, target_item_ram, rendering_foreign_item)
if( carry_item == target_item_value and rendering_foreign_item ~= nil ) then
memory.write_u8(carryAddress, nullObjectId, "System Bus")
memory.write_u8(target_item_ram, 0xFF, "System Bus")
pending_foreign_items_collected[rendering_foreign_item.short_location_id] = rendering_foreign_item
for index, fi in pairs(foreign_items_by_room[rendering_foreign_item.room_id]) do
if( fi.short_location_id == rendering_foreign_item.short_location_id ) then
table.remove(foreign_items_by_room[rendering_foreign_item.room_id], index)
break
end
end
for index, fi in pairs(foreign_items) do
if( fi.short_location_id == rendering_foreign_item.short_location_id ) then
foreign_items[index] = nil
break
end
end
prev_ap_room_index = 0
return true
end
return false
end
function BatCanTouchForeign(foreign_item, bat_room)
if bat_no_touch_locations_by_room[bat_room] == nil or bat_no_touch_locations_by_room[bat_room][1] == nil then
return true
end
for index, location in ipairs(bat_no_touch_locations_by_room[bat_room]) do
if location.short_location_id == foreign_item.short_location_id then
return false
end
end
return true;
end
function main()
memory.usememorydomain("System Bus")
if (is23Or24Or25 or is26To28) == false then
print("Must use a version of bizhawk 2.3.1 or higher")
return
end
local playerSlot = memory.read_u8(PlayerSlotAddress)
local port = 17242 + playerSlot
print("Using port"..tostring(port))
server, error = socket.bind('localhost', port)
if( error ~= nil ) then
print(error)
end
event.onmemoryexecute(SetYorgleSpeed, yorgle_speed_address);
event.onmemoryexecute(SetGrundleSpeed, grundle_speed_address);
event.onmemoryexecute(SetRhindleSpeed, rhindle_speed_address);
event.onmemoryexecute(SetDifficultySwitchA, read_switch_a)
event.onmemoryexecute(SetDifficultySwitchB, read_switch_b)
event.onmemoryexecute(GetLinkedObject, batItemCheckAddr)
-- TODO: Add an onmemoryexecute event to intercept the bat reading item rooms, and don't 'see' an item in the
-- room if it is in bat_no_touch_locations_by_room. Although realistically, I may have to handle this in the rom
-- for it to be totally reliable, because it won't work before the script connects (I might have to reset them?)
-- TODO: Also remove those items from the bat_no_touch_locations_by_room if they have been collected
while true do
frame = frame + 1
drawMessages()
if not (curstate == prevstate) then
print("Current state: "..curstate)
prevstate = curstate
end
local current_player_room = u8(PlayerRoomAddr)
local bat_room = u8(batRoomAddr)
local bat_carrying_item = u8(batCarryAddress)
local bat_carrying_ap_item = (BatAPItemRam == bat_carrying_item)
if current_player_room == 0x1E then
if u8(PlayerRoomAddr + 1) > 0x4B then
memory.write_u8(PlayerRoomAddr + 1, 0x4B)
end
end
if current_player_room == 0x00 then
if not was_in_number_room then
print("reset "..tostring(bat_carrying_ap_item).." "..tostring(bat_carrying_item))
memory.write_u8(batCarryAddress, batInvalidCarryItem)
memory.write_u8(batCarryAddress+ 1, 0)
createForeignItemsByRoom()
memory.write_u8(BatAPItemRam, 0xff)
memory.write_u8(APItemRam, 0xff)
prev_ap_room_index = 0
prev_player_room = 0
rendering_foreign_item = nil
was_in_number_room = true
end
else
was_in_number_room = false
end
if bat_room ~= prev_bat_room then
if bat_carrying_ap_item then
if foreign_items_by_room[prev_bat_room] ~= nil then
for r,f in pairs(foreign_items_by_room[prev_bat_room]) do
if f.short_location_id == current_bat_ap_item.short_location_id then
-- print("removing item from "..tostring(r).." in "..tostring(prev_bat_room))
table.remove(foreign_items_by_room[prev_bat_room], r)
break
end
end
end
if foreign_items_by_room[bat_room] == nil then
foreign_items_by_room[bat_room] = {}
end
-- print("adding item to "..tostring(bat_room))
table.insert(foreign_items_by_room[bat_room], current_bat_ap_item)
else
-- set AP item room and position for new room, or to invalid room
if foreign_items_by_room[bat_room] ~= nil and foreign_items_by_room[bat_room][1] ~= nil
and BatCanTouchForeign(foreign_items_by_room[bat_room][1], bat_room) then
if current_bat_ap_item ~= foreign_items_by_room[bat_room][1] then
current_bat_ap_item = foreign_items_by_room[bat_room][1]
-- print("Changing bat item to "..tostring(current_bat_ap_item.short_location_id))
end
memory.write_u8(BatAPItemRam, bat_room)
memory.write_u8(BatAPItemRam + 1, current_bat_ap_item.room_x)
memory.write_u8(BatAPItemRam + 2, current_bat_ap_item.room_y)
else
memory.write_u8(BatAPItemRam, 0xff)
if current_bat_ap_item ~= nil then
-- print("clearing bat item")
end
current_bat_ap_item = nil
end
end
end
prev_bat_room = bat_room
-- update foreign_items_by_room position and room id for bat item if bat carrying an item
if bat_carrying_ap_item then
-- this is setting the item using the bat's position, which is somewhat wrong, but I think
-- there will be more problems with the room not matching sometimes if I use the actual item position
current_bat_ap_item.room_id = bat_room
current_bat_ap_item.room_x = u8(batRoomAddr + 1)
current_bat_ap_item.room_y = u8(batRoomAddr + 2)
end
if (alive_mode()) then
if (current_player_room ~= prev_player_room) then
memory.write_u8(APItemRam, 0xFF, "System Bus")
prev_ap_room_index = 0
prev_player_room = current_player_room
AutocollectFromRoom()
end
local carry_item = memory.read_u8(carryAddress, "System Bus")
bat_no_touch_items[carry_item] = nil
if (next_inventory_item ~= nil) then
if ( carry_item == nullObjectId and last_carry_item == nullObjectId ) then
frames_with_no_item = frames_with_no_item + 1
if (frames_with_no_item > 10) then
frames_with_no_item = 10
local input_value = memory.read_u8(input_button_address, "System Bus")
if( input_value >= 64 and input_value < 128 ) then -- high bit clear, second highest bit set
memory.write_u8(carryAddress, next_inventory_item)
local item_ram_location = memory.read_u8(ItemTableStart + next_inventory_item)
if( memory.read_u8(batCarryAddress) ~= 0x78 and
memory.read_u8(batCarryAddress) == item_ram_location) then
memory.write_u8(batCarryAddress, batInvalidCarryItem)
memory.write_u8(batCarryAddress+ 1, 0)
memory.write_u8(item_ram_location, current_player_room)
memory.write_u8(item_ram_location + 1, memory.read_u8(PlayerRoomAddr + 1))
memory.write_u8(item_ram_location + 2, memory.read_u8(PlayerRoomAddr + 2))
end
ItemIndex = ItemIndex + 1
next_inventory_item = nil
end
end
else
frames_with_no_item = 0
end
end
if( carry_item ~= last_carry_item ) then
if ( localItemLocations ~= nil and localItemLocations[tostring(carry_item)] ~= nil ) then
pending_local_items_collected[localItemLocations[tostring(carry_item)]] =
localItemLocations[tostring(carry_item)]
table.remove(localItemLocations, tostring(carry_item))
skip_inventory_items[carry_item] = carry_item
end
end
last_carry_item = carry_item
CheckCollectAPItem(carry_item, APItemValue, APItemRam, rendering_foreign_item)
if CheckCollectAPItem(carry_item, BatAPItemValue, BatAPItemRam, current_bat_ap_item) and bat_carrying_ap_item then
memory.write_u8(batCarryAddress, batInvalidCarryItem)
memory.write_u8(batCarryAddress+ 1, 0)
end
rendering_foreign_item = nil
if( foreign_items_by_room[current_player_room] ~= nil ) then
if( foreign_items_by_room[current_player_room][prev_ap_room_index] ~= nil ) and memory.read_u8(APItemRam) ~= 0xff then
foreign_items_by_room[current_player_room][prev_ap_room_index].room_x = memory.read_u8(APItemRam + 1)
foreign_items_by_room[current_player_room][prev_ap_room_index].room_y = memory.read_u8(APItemRam + 2)
end
prev_ap_room_index = prev_ap_room_index + 1
local invalid_index = -1
if( foreign_items_by_room[current_player_room][prev_ap_room_index] == nil ) then
prev_ap_room_index = 1
end
if( foreign_items_by_room[current_player_room][prev_ap_room_index] ~= nil and current_bat_ap_item ~= nil and
foreign_items_by_room[current_player_room][prev_ap_room_index].short_location_id == current_bat_ap_item.short_location_id) then
invalid_index = prev_ap_room_index
prev_ap_room_index = prev_ap_room_index + 1
if( foreign_items_by_room[current_player_room][prev_ap_room_index] == nil ) then
prev_ap_room_index = 1
end
end
if( foreign_items_by_room[current_player_room][prev_ap_room_index] ~= nil and prev_ap_room_index ~= invalid_index ) then
memory.write_u8(APItemRam, current_player_room)
rendering_foreign_item = foreign_items_by_room[current_player_room][prev_ap_room_index]
memory.write_u8(APItemRam + 1, rendering_foreign_item.room_x)
memory.write_u8(APItemRam + 2, rendering_foreign_item.room_y)
else
memory.write_u8(APItemRam, 0xFF, "System Bus")
end
end
if is_dead == 0 then
dragons_revived = false
player_dead = false
new_dragon_state = {0,0,0}
for index, dragon_state_addr in pairs(DragonState) do
new_dragon_state[index] = memory.read_u8(dragon_state_addr, "System Bus" )
if last_dragon_state[index] == 1 and new_dragon_state[index] ~= 1 then
dragons_revived = true
elseif last_dragon_state[index] ~= 1 and new_dragon_state[index] == 1 then
dragon_real_index = index - 1
print("Killed dragon: "..tostring(dragon_real_index))
local dragon_item = {}
dragon_item["short_location_id"] = 0xD0 + dragon_real_index
pending_foreign_items_collected[dragon_item.short_location_id] = dragon_item
end
if new_dragon_state[index] == 2 then
player_dead = true
end
end
if dragons_revived and player_dead == false then
TryFreeincarnate()
end
last_dragon_state = new_dragon_state
end
elseif (u8(PlayerRoomAddr) == 0x00) then -- not alive mode, in number room
ItemIndex = 0 -- reset our inventory
next_inventory_item = nil
skip_inventory_items = {}
end
if (curstate == STATE_OK) or (curstate == STATE_INITIAL_CONNECTION_MADE) or (curstate == STATE_TENTATIVELY_CONNECTED) then
if (frame % 5 == 0) then
receive()
if alive_mode() then
local was_dead = is_dead
is_dead = 0
for index, dragonStateAddr in pairs(DragonState) do
local dragonstateval = memory.read_u8(dragonStateAddr, "System Bus")
if ( dragonstateval == 2) then
is_dead = index
end
end
if was_dead ~= 0 and is_dead == 0 then
TryFreeincarnate()
end
if deathlink_rec == true and is_dead == 0 then
print("setting dead from deathlink")
deathlink_rec = false
deathlink_sent = true
is_dead = 1
memory.write_u8(carryAddress, nullObjectId, "System Bus")
memory.write_u8(DragonState[1], 2, "System Bus")
end
if (is_dead > 0 and deathlink_send == 0 and not deathlink_sent) then
deathlink_send = is_dead
print("setting deathlink_send to "..tostring(is_dead))
elseif (is_dead == 0) then
deathlink_send = 0
deathlink_sent = false
end
if ItemsReceived ~= nil and ItemsReceived[ItemIndex + 1] ~= nil then
while ItemsReceived[ItemIndex + 1] ~= nil and skip_inventory_items[ItemsReceived[ItemIndex + 1]] ~= nil do
print("skip")
ItemIndex = ItemIndex + 1
end
local static_id = ItemsReceived[ItemIndex + 1]
if static_id ~= nil then
inventory[static_id] = 1
if next_inventory_item == nil then
next_inventory_item = static_id
end
end
end
end
end
elseif (curstate == STATE_UNINITIALIZED) then
if (frame % 60 == 0) then
print("Waiting for client.")
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
atariSocket = client
atariSocket:settimeout(0)
end
end
end
emu.frameadvance()
end
end
main()

380
data/lua/ADVENTURE/json.lua Normal file
View File

@@ -0,0 +1,380 @@
--
-- json.lua
--
-- Copyright (c) 2015 rxi
--
-- This library is free software; you can redistribute it and/or modify it
-- under the terms of the MIT license. See LICENSE for details.
--
local json = { _version = "0.1.0" }
-------------------------------------------------------------------------------
-- Encode
-------------------------------------------------------------------------------
local encode
local escape_char_map = {
[ "\\" ] = "\\\\",
[ "\"" ] = "\\\"",
[ "\b" ] = "\\b",
[ "\f" ] = "\\f",
[ "\n" ] = "\\n",
[ "\r" ] = "\\r",
[ "\t" ] = "\\t",
}
local escape_char_map_inv = { [ "\\/" ] = "/" }
for k, v in pairs(escape_char_map) do
escape_char_map_inv[v] = k
end
local function escape_char(c)
return escape_char_map[c] or string.format("\\u%04x", c:byte())
end
local function encode_nil(val)
return "null"
end
local function encode_table(val, stack)
local res = {}
stack = stack or {}
-- Circular reference?
if stack[val] then error("circular reference") end
stack[val] = true
if val[1] ~= nil or next(val) == nil then
-- Treat as array -- check keys are valid and it is not sparse
local n = 0
for k in pairs(val) do
if type(k) ~= "number" then
error("invalid table: mixed or invalid key types")
end
n = n + 1
end
if n ~= #val then
error("invalid table: sparse array")
end
-- Encode
for i, v in ipairs(val) do
table.insert(res, encode(v, stack))
end
stack[val] = nil
return "[" .. table.concat(res, ",") .. "]"
else
-- Treat as an object
for k, v in pairs(val) do
if type(k) ~= "string" then
error("invalid table: mixed or invalid key types")
end
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
end
stack[val] = nil
return "{" .. table.concat(res, ",") .. "}"
end
end
local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end
local function encode_number(val)
-- Check for NaN, -inf and inf
if val ~= val or val <= -math.huge or val >= math.huge then
error("unexpected number value '" .. tostring(val) .. "'")
end
return string.format("%.14g", val)
end
local type_func_map = {
[ "nil" ] = encode_nil,
[ "table" ] = encode_table,
[ "string" ] = encode_string,
[ "number" ] = encode_number,
[ "boolean" ] = tostring,
}
encode = function(val, stack)
local t = type(val)
local f = type_func_map[t]
if f then
return f(val, stack)
end
error("unexpected type '" .. t .. "'")
end
function json.encode(val)
return ( encode(val) )
end
-------------------------------------------------------------------------------
-- Decode
-------------------------------------------------------------------------------
local parse
local function create_set(...)
local res = {}
for i = 1, select("#", ...) do
res[ select(i, ...) ] = true
end
return res
end
local space_chars = create_set(" ", "\t", "\r", "\n")
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local literals = create_set("true", "false", "null")
local literal_map = {
[ "true" ] = true,
[ "false" ] = false,
[ "null" ] = nil,
}
local function next_char(str, idx, set, negate)
for i = idx, #str do
if set[str:sub(i, i)] ~= negate then
return i
end
end
return #str + 1
end
local function decode_error(str, idx, msg)
--local line_count = 1
--local col_count = 1
--for i = 1, idx - 1 do
-- col_count = col_count + 1
-- if str:sub(i, i) == "\n" then
-- line_count = line_count + 1
-- col_count = 1
-- end
-- end
-- emu.message( string.format("%s at line %d col %d", msg, line_count, col_count) )
end
local function codepoint_to_utf8(n)
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
local f = math.floor
if n <= 0x7f then
return string.char(n)
elseif n <= 0x7ff then
return string.char(f(n / 64) + 192, n % 64 + 128)
elseif n <= 0xffff then
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
elseif n <= 0x10ffff then
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
f(n % 4096 / 64) + 128, n % 64 + 128)
end
error( string.format("invalid unicode codepoint '%x'", n) )
end
local function parse_unicode_escape(s)
local n1 = tonumber( s:sub(3, 6), 16 )
local n2 = tonumber( s:sub(9, 12), 16 )
-- Surrogate pair?
if n2 then
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
else
return codepoint_to_utf8(n1)
end
end
local function parse_string(str, i)
local has_unicode_escape = false
local has_surrogate_escape = false
local has_escape = false
local last
for j = i + 1, #str do
local x = str:byte(j)
if x < 32 then
decode_error(str, j, "control character in string")
end
if last == 92 then -- "\\" (escape char)
if x == 117 then -- "u" (unicode escape sequence)
local hex = str:sub(j + 1, j + 5)
if not hex:find("%x%x%x%x") then
decode_error(str, j, "invalid unicode escape in string")
end
if hex:find("^[dD][89aAbB]") then
has_surrogate_escape = true
else
has_unicode_escape = true
end
else
local c = string.char(x)
if not escape_chars[c] then
decode_error(str, j, "invalid escape char '" .. c .. "' in string")
end
has_escape = true
end
last = nil
elseif x == 34 then -- '"' (end of string)
local s = str:sub(i + 1, j - 1)
if has_surrogate_escape then
s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
end
if has_unicode_escape then
s = s:gsub("\\u....", parse_unicode_escape)
end
if has_escape then
s = s:gsub("\\.", escape_char_map_inv)
end
return s, j + 1
else
last = x
end
end
decode_error(str, i, "expected closing quote for string")
end
local function parse_number(str, i)
local x = next_char(str, i, delim_chars)
local s = str:sub(i, x - 1)
local n = tonumber(s)
if not n then
decode_error(str, i, "invalid number '" .. s .. "'")
end
return n, x
end
local function parse_literal(str, i)
local x = next_char(str, i, delim_chars)
local word = str:sub(i, x - 1)
if not literals[word] then
decode_error(str, i, "invalid literal '" .. word .. "'")
end
return literal_map[word], x
end
local function parse_array(str, i)
local res = {}
local n = 1
i = i + 1
while 1 do
local x
i = next_char(str, i, space_chars, true)
-- Empty / end of array?
if str:sub(i, i) == "]" then
i = i + 1
break
end
-- Read token
x, i = parse(str, i)
res[n] = x
n = n + 1
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "]" then break end
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
end
return res, i
end
local function parse_object(str, i)
local res = {}
i = i + 1
while 1 do
local key, val
i = next_char(str, i, space_chars, true)
-- Empty / end of object?
if str:sub(i, i) == "}" then
i = i + 1
break
end
-- Read key
if str:sub(i, i) ~= '"' then
decode_error(str, i, "expected string for key")
end
key, i = parse(str, i)
-- Read ':' delimiter
i = next_char(str, i, space_chars, true)
if str:sub(i, i) ~= ":" then
decode_error(str, i, "expected ':' after key")
end
i = next_char(str, i + 1, space_chars, true)
-- Read value
val, i = parse(str, i)
-- Set
res[key] = val
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "}" then break end
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
end
return res, i
end
local char_func_map = {
[ '"' ] = parse_string,
[ "0" ] = parse_number,
[ "1" ] = parse_number,
[ "2" ] = parse_number,
[ "3" ] = parse_number,
[ "4" ] = parse_number,
[ "5" ] = parse_number,
[ "6" ] = parse_number,
[ "7" ] = parse_number,
[ "8" ] = parse_number,
[ "9" ] = parse_number,
[ "-" ] = parse_number,
[ "t" ] = parse_literal,
[ "f" ] = parse_literal,
[ "n" ] = parse_literal,
[ "[" ] = parse_array,
[ "{" ] = parse_object,
}
parse = function(str, idx)
local chr = str:sub(idx, idx)
local f = char_func_map[chr]
if f then
return f(str, idx)
end
decode_error(str, idx, "unexpected character '" .. chr .. "'")
end
function json.decode(str)
if type(str) ~= "string" then
error("expected argument of type string, got " .. type(str))
end
return ( parse(str, next_char(str, 1, space_chars, true)) )
end
return json

View File

@@ -0,0 +1,132 @@
-----------------------------------------------------------------------------
-- LuaSocket helper module
-- Author: Diego Nehab
-- RCS ID: $Id: socket.lua,v 1.22 2005/11/22 08:33:29 diego Exp $
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Declare module and import dependencies
-----------------------------------------------------------------------------
local base = _G
local string = require("string")
local math = require("math")
local socket = require("socket.core")
module("socket")
-----------------------------------------------------------------------------
-- Exported auxiliar functions
-----------------------------------------------------------------------------
function connect(address, port, laddress, lport)
local sock, err = socket.tcp()
if not sock then return nil, err end
if laddress then
local res, err = sock:bind(laddress, lport, -1)
if not res then return nil, err end
end
local res, err = sock:connect(address, port)
if not res then return nil, err end
return sock
end
function bind(host, port, backlog)
local sock, err = socket.tcp()
if not sock then return nil, err end
sock:setoption("reuseaddr", true)
local res, err = sock:bind(host, port)
if not res then return nil, err end
res, err = sock:listen(backlog)
if not res then return nil, err end
return sock
end
try = newtry()
function choose(table)
return function(name, opt1, opt2)
if base.type(name) ~= "string" then
name, opt1, opt2 = "default", name, opt1
end
local f = table[name or "nil"]
if not f then base.error("unknown key (".. base.tostring(name) ..")", 3)
else return f(opt1, opt2) end
end
end
-----------------------------------------------------------------------------
-- Socket sources and sinks, conforming to LTN12
-----------------------------------------------------------------------------
-- create namespaces inside LuaSocket namespace
sourcet = {}
sinkt = {}
BLOCKSIZE = 2048
sinkt["close-when-done"] = function(sock)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function(self, chunk, err)
if not chunk then
sock:close()
return 1
else return sock:send(chunk) end
end
})
end
sinkt["keep-open"] = function(sock)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function(self, chunk, err)
if chunk then return sock:send(chunk)
else return 1 end
end
})
end
sinkt["default"] = sinkt["keep-open"]
sink = choose(sinkt)
sourcet["by-length"] = function(sock, length)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function()
if length <= 0 then return nil end
local size = math.min(socket.BLOCKSIZE, length)
local chunk, err = sock:receive(size)
if err then return nil, err end
length = length - string.len(chunk)
return chunk
end
})
end
sourcet["until-closed"] = function(sock)
local done
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function()
if done then return nil end
local chunk, err, partial = sock:receive(socket.BLOCKSIZE)
if not err then return chunk
elseif err == "closed" then
sock:close()
done = 1
return partial
else return nil, err end
end
})
end
sourcet["default"] = sourcet["until-closed"]
source = choose(sourcet)