* Fix links to TASVideos.org using HTTP * Revise all docs mentioning Lua in EmuHawk which are in English resolves TASEmulators/BizHawk#3650 * Correct capitalisation of "BizHawk" in strings and camelCase identifiers * Use the term "EmuHawk" when referring to the app, in English docs --------- Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
		
			
				
	
	
		
			739 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			739 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			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 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 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
 | 
						|
 | 
						|
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
 | 
						|
 | 
						|
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
 | 
						|
 | 
						|
function getAllRam()
 | 
						|
    uRangeRAM(0,128);
 | 
						|
    return data
 | 
						|
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 not checkBizHawkVersion() then
 | 
						|
        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)]
 | 
						|
                    localItemLocations[tostring(carry_item)] = nil
 | 
						|
                    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()
 |