852 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
		
		
			
		
	
	
			852 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
|   | 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() |