User File #38530848800286562

Upload All User Files

#38530848800286562 - DTC6 We Serve Firetrucks Final Lua

DTC6WeServeFiretrucksFinal.lua
Game: Zook Man ZX4 ( GBA, see all files )
927 downloads
Uploaded 4/23/2017 5:31 AM by Fog (see all 6)
-- V2.0:
-- By Warepire, Spikestuff, Fog, Tremane
-- Slightly Less Shitty Script
-- Prints Zook position, charge shot timer, weapons cooldown timer, invulnerable timer
--        Enemy position (different coordinate system because ... fuck you!?), HP, action timer and active flag, invulnerable timer
--        Active item drop, and next five item drops
-- imported: hitbox feature
--
-- Currently no speed value, because there seems to be only 2 speeds: Standing still and moving.
-- If speed is discovered to be more complex, speed value can be added.

camera_x = 0
camera_y = 0

local_x = 0
local_y = 0

-- Level layer positions addresses in IWRAM:
zook_ukn_layer_x = 0x14F0
zook_ukn_layer_y = 0x14F4
zook_fg_layer_x = 0x14F8
zook_fg_layer_y = 0x14FC
zook_lvl_layer_x = 0x1500
zook_lvl_layer_y = 0x1504
zook_bg_layer_x = 0x1508
zook_bg_layer_y = 0x150C


function UpdateCamera()
    -- using lvl layer as "camera":
    memory.usememorydomain("IWRAM")
    camera_x = memory.read_u32_le(zook_lvl_layer_x)
    camera_y = memory.read_u32_le(zook_lvl_layer_y)
end

function PrintZookData()
    -- 0x15E0: Cooldown timer, can only shoot when 0.
    local zook_x_addr = 0x1560
    local zook_y_addr = 0x1564
    local zook_wpn_cooldown_timer_addr = 0x15E0
    local zook_charge_timer_addr = 0x1608
    local zook_invuln_timer_addr = 0x1628
-- These are some odd values, related to movement, goes from 0-31, wraps at 32
--  local zook_subx_addr = 0x1568
--  local zook_suby_addr = 0x156C
    memory.usememorydomain("IWRAM")
    local zook_x = memory.read_u32_le(zook_x_addr)
    local zook_y = memory.read_u32_le(zook_y_addr)
    local zook_wpn_cooldown_timer = memory.read_u32_le(zook_wpn_cooldown_timer_addr)
    local zook_charge_timer = memory.read_u16_le(zook_charge_timer_addr)
    local zook_invuln_timer = memory.read_u32_le(zook_invuln_timer_addr)
 
    -- the charge timer is special
    local zook_charge_timer_base = 60 - zook_charge_timer
    local zook_charge_timer_extra = 80
    if zook_charge_timer_base <= 0 then
        zook_charge_timer_extra = zook_charge_timer_extra + zook_charge_timer_base
        zook_charge_timer_base = 0
    end
    --gui.drawText(local_x+10, local_y+30, string.format("X=%d,Y=%d\nCharge Shot=%d,Extra=%d\nBuster Cooldown=%d\nInvulnerable timer=%d", zook_x, zook_y, zook_charge_timer_base, zook_charge_timer_extra, zook_wpn_cooldown_timer, zook_invuln_timer), "red", null, 10)
	
	
	local x, y, dy = 8, 56, 14
	local function print_info(format, ...)
		gui.text(x, y, string.format(format, ...),"aqua")
		y = y + dy
	end

	print_info("X=%d, Y=%d", zook_x, zook_y)
	print_info("Charge Shot=%d, Extra=%d", zook_charge_timer_base, zook_charge_timer_extra)
	print_info("Buster Cooldown=%d", zook_wpn_cooldown_timer)
	print_info("Invulnerable timer=%d", zook_invuln_timer)
end

initial_boss_bar_needed_hits = 0

function PrintBossData()
    memory.usememorydomain("IWRAM")
    local boss_can_be_damaged = memory.read_u32_le(0x16F4)
    local boss_invuln_timer = memory.read_u32_le(0x16F8)
    local boss_hp_bar_count = memory.read_u32_le(0x1708)
    local boss_hp_bar_needed_hits = memory.read_s32_le(0x170C)
 
	if boss_hp_bar_needed_hits < 0 then
		boss_hp_bar_needed_hits = 0 - boss_hp_bar_needed_hits
	end
 
    if initial_boss_bar_needed_hits == 0 then
        initial_boss_bar_needed_hits = boss_hp_bar_needed_hits
    end
 
    local boss_damage_flag = " "
    if boss_can_be_damaged == 6 then -- so far known: 6 = yes, 7 = no
        boss_damage_flag = "*"
    end
 
    if boss_hp_bar_count == 0 then
        initial_boss_bar_needed_hits = 0
        return
    end
	--print("Boss HP Bar Count: " .. boss_hp_bar_count)
	--print("Initial Boss Bar Needed Hits: " .. initial_boss_bar_needed_hits)
	--print("Boss HP Bar Needed Hits:" .. boss_hp_bar_needed_hits)
    local boss_hp = ((boss_hp_bar_count - 1) * initial_boss_bar_needed_hits) + boss_hp_bar_needed_hits
 
    --gui.drawText(10, 75, string.format("Boss HP=%d %s\nInvulnerable timer=%d", boss_hp, boss_damage_flag, boss_invuln_timer), "red", null, 10)
	
	local x, y, dy = 8, 112, 14
	local function print_info(format, ...)
		gui.text(x, y, string.format(format, ...),"red")
		y = y + dy
	end

	print_info("Boss HP=%d %s", boss_hp, boss_damage_flag)
	print_info("Invulnerable timer=%d", boss_invuln_timer)
end

function PrintItemDrop()
	local active_frame_count = memory.read_u32_le(0x17A0, "IWRAM")
	local item_drop_table = memory.readbyterange(0x3E8880, 132, "ROM") -- 33 entries of four bytes each
	local active_item_drop = item_drop_table[bit.band(active_frame_count, 0x1F) * 4] -- multiplied by four to read the four byte drop table
	local active_item_drop_strings = {"Nothing", "Small Health", "Medium Health", "Large Health", "Small Energy", "Medium Energy", "Large Energy", "Extra Life"}
	
	--gui.drawText(10, 100, string.format("Active Item Drop:\n%s", active_item_drop_strings[active_item_drop + 1]), "red", null, 10)
	
	local x, y, dy = 320, 14, 14
	local function print_info(format, ...)
		gui.text(x, y, string.format(format, ...),"lightgreen")
		y = y + dy
	end
	
	print_info("AID: %s", active_item_drop_strings[active_item_drop + 1]) --Active Item Drop
	print_info("Next Five Drops:", 0)
	for i=1,5 do
		local next_item_drop = item_drop_table[bit.band(active_frame_count + i, 0x1F) * 4] -- multiplied by four to read the four byte drop table
		print_info("%s", active_item_drop_strings[next_item_drop + 1])
	end
end

-- FOV/Hitbox Work

-- -----------------------------------
-- Globals
-- -----------------------------------

local zook_entity_struct_data
local current_entity_struct_data

-- ---------------------------------
-- Constants
-- ---------------------------------

local max_entries = 60
 
local enemies_index_begin = 30 -- enemies start at position 30, first 6 reserved for Zook?
local entity_struct_addr = 0x1DE4
 
local entity_struct_size = 0x74
 
local entity_x_offset = 0x0
local entity_y_offset = 0x4
local entity_sprite_index_offset = 0x8
local entity_sprite_map_offset = 0xA
local entity_direction_offset = 0xC
local entity_type_offset = 0x14
local entity_action_state_offset = 0x16
local entity_action_timer_offset = 0x18
local entity_hp_offset = 0x2C
local entity_field_0x3C_offset = 0x3C
local entity_invuln_timer_offset = 0x58
 
-- maps enemy type(?) to box color, if a color sucks, change it!
-- when a new enemy is identified to map to one of these boxes, update the enemy name in the list!
local color_map = {}
color_map[0x0F] = "AliceBlue"       -- red tank (intro)
color_map[0x17] = "AntiqueWhite"    -- blue tank (intro)
color_map[0x1E] = "Aqua"        	-- cone eneny (intro)
color_map[0x1B] = "Aquamarine"      -- speedy spikey thing (intro)
color_map[0x25] = "Azure"       	-- no clue
color_map[0x4B] = "Beige"       	-- no clue
color_map[0x56] = "Bisque"      	-- no clue
color_map[0x58] = "BlanchedAlmond"  -- no clue
color_map[0x5B] = "Blue"        	-- no clue
color_map[0x5E] = "BlueViolet"      -- no clue
color_map[0x62] = "Brown"       	-- no clue
color_map[0x71] = "BurlyWood"       -- no clue
color_map[0x72] = "CadetBlue"       -- no clue
color_map[0x74] = "Chartreuse"      -- no clue
color_map[0x87] = "Chocolate"       -- no clue
color_map[0x8A] = "Coral"       	-- no clue
color_map[0x8B] = "CornflowerBlue"  -- no clue
color_map[0x8C] = "Cornsilk"        -- no clue
color_map[0x8D] = "Crimson"     	-- no clue
color_map[0xA2] = "Cyan"        	-- no clue
color_map[0xA4] = "DarkBlue"        -- no clue
color_map[0xAB] = "DarkCyan"        -- no clue
color_map[0xAF] = "DarkGoldenrod"   -- no clue
color_map[0xBA] = "DarkGray"        -- no clue ... like really... no clue
color_map[0xC0] = "DarkGreen"       -- no clue
color_map[0xC1] = "DarkKhaki"       -- no clue
color_map[0xC2] = "DarkMagenta"     -- no clue
color_map[0xC3] = "DarkOliveGreen"  -- no clue
color_map[0xC8] = "DarkOrange"      -- no clue ... like really... no clue
color_map[0xD0] = "DarkOrchid"      -- no clue
color_map[0xD5] = "DarkViolet"      -- no clue
color_map[0xDE] = "DarkSalmon"      -- no clue
color_map[0xDF] = "DarkSeaGreen"    -- no clue
color_map[0xE2] = "DarkSlateBlue"   -- no clue
color_map[0xE3] = "DarkSlateGray"   -- no clue
color_map[0xE6] = "DarkTurquoise"   -- no clue

-- ----------------------------------
-- Utility functions
-- ----------------------------------

local function GetWordFromMemTable(table, offset)
    local low_byte = table[offset]
    local high_byte = table[offset+1]
    local word = low_byte + (high_byte * 0x100)
    return word
end
 
local function GetDwordFromMemTable(table, offset)
    local low_word = GetWordFromMemTable(table, offset)
    local high_word = GetWordFromMemTable(table, offset + 2)
    local dword = low_word + (high_word * 0x10000)
    return dword
end
 
 -- exactly how the game computes it, every time. I know IDA says differently sometimes in the pseudocode view.
function GetOffsetFromIndex(index)
    return 4 * (29 * index)
end
 
function read_u32_le_SystemBus(addr)
    if addr >= 0x8000000 then
        return memory.read_u32_le(addr - 0x8000000, "ROM")
    else -- addr >= 0x3000000 then
        return memory.read_u32_le(addr - 0x3000000, "IWRAM")
    end
end
 
-- args: color_id, left, right, top, bottom
function DrawFieldOfViewBox(args)
    assert(args.color_id, "BUG! color_id == nil")
    local x_left = 16 + (args.left or 0)
    local x_right = 16 + (args.right or (239 - 16))
    local y_top = (args.top or 0)
    local y_bottom = (args.bottom or 159)
    gui.drawBox(x_left, y_top, x_right, y_bottom, color_map[args.color_id])
end
 
function DrawHitBox(x1, x2, y1, y2)
    gui.drawBox(x1, y1, x2, y2, "red")
end

-- -----------------------------------
-- Draw logic 
-- -----------------------------------

function DrawEntityData()
	local enemy_x = GetDwordFromMemTable(current_entity_struct_data, entity_x_offset)
	local enemy_y = GetDwordFromMemTable(current_entity_struct_data, entity_y_offset)
	local enemy_type = GetWordFromMemTable(current_entity_struct_data, entity_type_offset)
	local enemy_active_flag = GetWordFromMemTable(current_entity_struct_data, entity_action_state_offset)
	local enemy_action_timer = GetWordFromMemTable(current_entity_struct_data, entity_action_timer_offset)
	local enemy_hp = GetWordFromMemTable(current_entity_struct_data, entity_hp_offset)
	local enemy_invuln_timer = GetDwordFromMemTable(current_entity_struct_data, entity_invuln_timer_offset)

	local active = " "
	if enemy_active_flag ~= 0 then active = "*" end

	local x, y, dy = enemy_x + 16, enemy_y, 7
	local function print_info(format, ...)
		gui.pixelText(x + 0, y, string.format(format, ...),"red")
		y = y + dy
	end
	
	if enemy_x ~= 0 and enemy_y ~= 0 then
		print_info("X=%d, Y=%d", enemy_x, enemy_y)
		if enemy_hp ~= 0 and enemy_hp ~= 0xFFFF then
			print_info("HP=%d", enemy_hp)
			print_info("%sA.T.=%d", active, enemy_action_timer)
			print_info("I.T=%d", enemy_invuln_timer)
		end
		print_info("Type=0x%02X", enemy_type)
	end
end

-- Amazingly enough all the field of view tests are conducted versus the position of Zooks left foot big toe.
-- Note: Some boxes may be inversed, if they are, swap the left/right values!
function FieldOfViewBox()
	-- Function pointers whose detection logic are implemented below, start at 0x1818 in IWRAM and seem to go on for about 256 entries
	local entity_type = GetWordFromMemTable(current_entity_struct_data, entity_type_offset)
	local x = GetDwordFromMemTable(current_entity_struct_data, entity_x_offset)
	local y = GetDwordFromMemTable(current_entity_struct_data, entity_y_offset)
	if entity_type == 0xF or entity_type == 0x17 or entity_type == 0x1E then -- IDA functions: sub_8019208, sub_8018BA8, sub_80185C0
		DrawFieldOfViewBox{color_id=entity_type, left=x-48, right=x+48}
	elseif entity_type == 0x1B then -- IDA functions: sub_8018884
		DrawFieldOfViewBox{color_id=entity_type, left=x-96, right=x+48}
	elseif entity_type == 0x25 then -- IDA functions: sub_80173CC
		DrawFieldOfViewBox{color_id=entity_type, left=x-56, right=x+64}
		DrawFieldOfViewBox{color_id=entity_type, left=x-10, right=x+10, top=y+10, bottom=x-10} -- some sort of inner attack box...?
	elseif entity_type == 0x4B then -- IDA functions: sub_801431C
		DrawFieldOfViewBox{color_id=entity_type, left=48, right=144}
	elseif entity_type == 0x56 then -- IDA functions: sub_8013374
		DrawFieldOfViewBox{color_id=entity_type, left=x-64, right=x+32}
	elseif entity_type == 0x58 then -- IDA functions: sub_80130D8
		DrawFieldOfViewBox{color_id=entity_type, left=x-80, right=x+32}
	elseif entity_type == 0x5B or entity_type == 0x62 then -- IDA functions: sub_8012FE8, sub_8012AAC
		DrawFieldOfViewBox{color_id=entity_type, left=x-80, right=x+80}
	elseif entity_type == 0x5E or entity_type == 0xC3 then -- IDA functions: sub_8012C80, sub_8009FD4
		local dir = GetWordFromMemTable(current_entity_struct_data, entity_direction_offset)
		local left = 0
		if dir == 1 then
			left = 32
		end
		DrawFieldOfViewBox{color_id=entity_type, left=x-left, right=x+112}
	elseif entity_type == 0x71 or entity_type == 0x72 then -- IDA functions: sub_8011708 (0x8011924 is a "subfunction" of that)
		local action_state = GetWordFromMemTable(current_entity_struct_data, entity_action_state_offset)
		if entity_type == 0x71 and action_state == 1 then
			DrawFieldOfViewBox{color_id=entity_type, left=x-112, right=x+96}
		else
			DrawFieldOfViewBox{color_id=entity_type, left=x-80, right=x+64}
		end
	elseif entity_type == 0x74 then -- IDA functions: sub_8011278
		DrawFieldOfViewBox{color_id=entity_type, left=x-48, right=x+48}
		DrawFieldOfViewBox{color_id=entity_type, left=x-20, right=x-48}
	elseif entity_type == 0x87 then -- IDA functions: sub_800F920
		DrawFieldOfViewBox{color_id=entity_type, left=x-88, right=x+48}
	elseif entity_type == 0x8A then -- IDA functions: sub_800F4D4
		DrawFieldOfViewBox{color_id=entity_type, left=x-80, right=x+48}
	elseif entity_type == 0x8B then -- IDA functions: sub_800F394
		DrawFieldOfViewBox{color_id=entity_type, left=x-24, right=x+8}
	elseif entity_type == 0x8C then -- IDA functions: sub_800F298
		DrawFieldOfViewBox{color_id=entity_type, left=x-32, right=x+48}
	elseif entity_type == 0x8D or entity_type == 0xAF then -- IDA functions: sub_800F130, sub_800BAE8
		DrawFieldOfViewBox{color_id=entity_type, left=x-64, right=x+48}
	elseif entity_type == 0xA2 then -- IDA functions: sub_800D514
		DrawFieldOfViewBox{color_id=entity_type, top=y-16, bottom=y-32}
	elseif entity_type == 0xA4 then -- IDA functions: sub_800D234
		DrawFieldOfViewBox{color_id=entity_type, left=x-96, right=x+64, top=y+96, bottom=y-64}
	elseif entity_type == 0xAB then -- IDA functions: sub_800C114
		DrawFieldOfViewBox{color_id=entity_type, top=y-16, bottom=y-40}
	elseif entity_type == 0xBA then -- IDA functions: sub_8017F18
		print("ALERT: state 0xBA found!") -- What does this entity do? It does some math if Zook is in certain parts of the screen.
		x = memory.read_s32_le(entity_struct_addr + GetOffsetFromIndex(1) + entity_x_offset)
		DrawFieldOfViewBox{color_id=entity_type, left=x+90, right=x+200}
		DrawFieldOfViewBox{color_id=entity_type, left=x+40, right=x+80}
	elseif entity_type == 0xC0 then -- IDA functions: sub_800A380
		DrawFieldOfViewBox{color_id=entity_type, left=x-16, right=x+16}
	elseif entity_type == 0xC1 or entity_type == 0xD0 then -- IDA functions: sub_800A27C, sub_8008A18
		DrawFieldOfViewBox{color_id=entity_type, left=x-32, right=x+16}
	elseif entity_type == 0xC2 then -- IDA functions: sub_800A128
		DrawFieldOfViewBox{color_id=entity_type, left=x-72, right=x+48}
	elseif entity_type == 0xC8 then -- IDA functions: sub_8008D28
		print("ALERT: state 0xC8 found!") -- What does this entity do? It does some math depending on which zone of the screen Zook's in.
		y = memory.read_s32_le(entity_struct_addr + GetOffsetFromIndex(1) + entity_y_offset)
		DrawFieldOfViewBox{color_id=entity_type, bottom=40}
		DrawFieldOfViewBox{color_id=entity_type, top=40, bottom=72}
		DrawFieldOfViewBox{color_id=entity_type, top=72}
	elseif entity_type == 0xD5 then -- IDA functions: sub_8008654
		DrawFieldOfViewBox{color_id=entity_type, left=x-80, right=x+32}
	elseif entity_type == 0xDE then -- IDA functions: sub_800728C
		print("ALERT: state 0xDE found!")
		DrawFieldOfViewBox{color_id=entity_type, bottom=y-32}
	elseif entity_type == 0xDF then -- IDA functions: sub_800703C
		-- What does this entity do? It does some math if Zook is in certain parts of the screen.
		DrawFieldOfViewBox{color_id=entity_type, left=48, right=96}
		DrawFieldOfViewBox{color_id=entity_type, left=96, right=144}
	elseif entity_type == 0xE2 then -- IDA functions: sub_8006C6C
		DrawFieldOfViewBox{color_id=entity_type, left=x-58, right=x+88}
	elseif entity_type == 0xE3 then -- IDA functions: sub_8006A78
		DrawFieldOfViewBox{color_id=entity_type, left=x-64, right=192}
		DrawFieldOfViewBox{color_id=entity_type, left=8, right=x-16}
		DrawFieldOfViewBox{color_id=entity_type, top=y+8, bottom=y-24}
	elseif entity_type == 0xE6 then -- IDA functions: sub_8006248
		DrawFieldOfViewBox{color_id=entity_type, top=72, bottom=40}
	end
end

function DrawHitbox() -- sub_801C7A0 & sub_801C72C
    local field_3C = GetDwordFromMemTable(current_entity_struct_data, entity_field_0x3C_offset)
    local sprite_map = GetWordFromMemTable(current_entity_struct_data, entity_sprite_map_offset)
    local sprite_index = GetWordFromMemTable(current_entity_struct_data, entity_sprite_index_offset)
    local x = GetDwordFromMemTable(current_entity_struct_data, entity_x_offset)
    local y = GetDwordFromMemTable(current_entity_struct_data, entity_y_offset)
 
    local addr
    if field_3C == 2 then -- sub_801C7A0
        addr = read_u32_le_SystemBus(0x806885C + (sprite_map * 4))
    else                  -- sub_801C72C
        -- table is defined as: DWORD* array[][17]
        -- however, hitboxes can be read just fine without accessing the array
        addr = read_u32_le_SystemBus(0x83CD5C4 + (memory.read_u32_le(0x152C, "IWRAM") * 4))
        addr = read_u32_le_SystemBus(addr + (sprite_map * 4))
    end
 
    addr = addr + (16 * sprite_index)
 
    local x1 = x + read_u32_le_SystemBus(addr + 8)
    local x2 = x1 + read_u32_le_SystemBus(addr + 12)
    local y1 = y + read_u32_le_SystemBus(addr)
    local y2 = y1 + read_u32_le_SystemBus(addr + 4)
 
    DrawHitBox(x1, x2, y1, y2)
end
 
function DrawZookData()
    zook_entity_struct_data = memory.readbyterange(entity_struct_addr + GetOffsetFromIndex(1), entity_struct_size, "IWRAM")
    local sprite_map = GetWordFromMemTable(zook_entity_struct_data, entity_sprite_map_offset)
    local sprite_index = GetWordFromMemTable(zook_entity_struct_data, entity_sprite_index_offset)
    local x = GetDwordFromMemTable(zook_entity_struct_data, entity_x_offset)
    local y = GetDwordFromMemTable(zook_entity_struct_data, entity_y_offset)
    local addr = read_u32_le_SystemBus(0x8087F34 + (16 * sprite_map))
    addr = addr + (16 * sprite_index)
 
    local x1 = x + read_u32_le_SystemBus(addr + 8)
    local x2 = x1 + read_u32_le_SystemBus(addr + 12)
    local y1 = y + read_u32_le_SystemBus(addr)
    local y2 = y1 + read_u32_le_SystemBus(addr + 4)
 
    DrawHitBox(x1, x2, y1, y2)
   
    -- Zook's x position can be invalid if spawned off screen, so force it to zero to avoid issues
    if x < 0 or x > 240 then
        x = 0
    end
    gui.drawLine(x + 16, 0, x + 16, 159, "orange") -- Test pixel if zook is in field of view. Since height is not used, ignore it and draw a line.
end

function DrawEnemyStuff()
    -- do a framecount check to avoid crashing the script
    if emu.framecount() >= 270 then
        for i=enemies_index_begin,(max_entries - 1) do
            current_entity_struct_data = memory.readbyterange(entity_struct_addr + GetOffsetFromIndex(i), entity_struct_size, "IWRAM")
            DrawEntityData()
            FieldOfViewBox()
            DrawHitbox()
        end
    end
end

----------------------------------------------------------
-- Main:
----------------------------------------------------------

function main()
	client.SetClientExtraPadding(12, 58, 12, 12)
	while true do
		UpdateCamera()
		DrawZookData()
		DrawEnemyStuff()
		PrintZookData()
		PrintBossData()
		PrintItemDrop()
		emu.frameadvance()
	end
end
 
main()