-- TODO:
-- Extend waypoints to use a queue-style list.
-- Make the script move Bugs using camera angle, Bugs angle, and waypoints.
-- 0x0069408 / 2 bytes s = Camera turning speed when manually turning camera
-- Changes angle by 11.25 degrees, in BAMS notation (32)
-- 0x0069416 / 2 bytes s = ??? Seems to also be camera, but sloppy?
-- 0x00693D4 / 4 bytes h = Bitfield, Bugs state
--position, speed, direction
Xpos = 0
XposOld = 0
Ypos = 0
YposOld = 0
Zpos = 0
ZposOld = 0
Xspeed = 0
Yspeed = 0
Zspeed = 0
--not really a need to look at Y-speed as it seems to be independent of XZ speed,
--might still be useful for jumping.
XZspeed = 0
XYZspeed = 0
XZdirection = 0
--timer related things
local inlvlObjTimer
--hud update logic things
local update = true
local counter30FPS = 0
local counter30FPSOld = 0
--waypoint things
local waypointX = nil
local waypointZ = nil
local displayWaypoint = false
-- Conversion as per: http://electronicstechnician.tpub.com/14091/css/14091_316.htm
function convertDegreesToBAMS(deg)
-- no conversion necessary
if deg == 360 or deg == 0 then
return 0
end
-- need to convert
local returnValue = 0
if deg >= 180 then
returnValue = returnValue + 0x800
deg = deg - 180
end
if deg >= 90 then
returnValue = returnValue + 0x400
deg = deg - 90
end
if deg >= 45 then
returnValue = returnValue + 0x200
deg = deg - 45
end
if deg >= 22.5 then
returnValue = returnValue + 0x100
deg = deg - 22.5
end
if deg >= 11.25 then
returnValue = returnValue + 0x80
deg = deg - 11.25
end
if deg >= 5.625 then
returnValue = returnValue + 0x40
deg = deg - 5.625
end
if deg >= 2.81 then
returnValue = returnValue + 0x20
deg = deg - 2.81
end
if deg >= 1.4 then
returnValue = returnValue + 0x10
deg = deg - 1.4
end
if deg >= 0.703 then
returnValue = returnValue + 0x8
deg = deg - 0.703
end
if deg >= 0.351 then
returnValue = returnValue + 0x4
deg = deg - 0.351
end
if deg >= 0.175 then
returnValue = returnValue + 0x2
deg = deg - 0.175
end
if deg >= 0.088 then
returnValue = returnValue + 0x1
deg = deg - 0.088
end
-- Any remaining fragments of 'deg' are too small to count,
-- accept the accuracy loss.
return returnValue
end
local function hud()
--Actual speed vector addresses:
--addr 00069D30 X Speed [signed dword]
--addr 00069D34 Y Speed(?) [signed dword]
--addr 00069D38 Z Speed [signed dword]
--This is not reliable as it shows the speed Bugs want to travel at,
--not the actual speed that he moves at.
--Use position as a source for speed instead
Xpos = memory.read_s32_le(0x00069DC0)
Ypos = memory.read_s32_le(0x00069DC4)
Zpos = memory.read_s32_le(0x00069DC8)
--Due to that the game internally runs at 30 fps despite rendering at 60 fps
--the speed values cannot be updated each frame.
--Apply some simpler logic to only update this once per 2 frames by using an
--address that increments at 30 fps.
--The updated flag solves an issue caused by using emu.yield()
counter30FPS = memory.read_u32_le(0x00076010)
local tempXspeed = Xpos - XposOld
local tempYspeed = Ypos - YposOld
local tempZspeed = Zpos - ZposOld
-- Make sure the direction is in 0-360 degree range
local tempXZdirection = math.atan2(tempXspeed, tempZspeed)
if tempXZdirection < 0 then
tempXZdirection = tempXZdirection + (math.pi * 2)
end
tempXZdirection = math.deg(tempXZdirection)
if counter30FPS ~= counter30FPSOld and update == true then
Xspeed = math.abs(tempXspeed)
Yspeed = math.abs(tempYspeed)
Zspeed = math.abs(tempZspeed)
XZdirection = tempXZdirection
XposOld = Xpos
YposOld = Ypos
ZposOld = Zpos
update = false
elseif counter30FPS == counter30FPSOld then
update = true
end
counter30FPSOld = counter30FPS
-- Read out the camera angle, and clamp it to 12-bit BAMS
local cameraAngleBAMS = memory.read_s16_le(0x00069402)
if cameraAngleBAMS < 0 then
cameraAngleBAMS = cameraAngleBAMS + 4096
end
if cameraAngleBAMS == 4096 then
cameraAngleBAMS = 0
end
local xzAngleBAMS = convertDegreesToBAMS(XZdirection)
XZspeed = math.sqrt(Xspeed*Xspeed + Zspeed*Zspeed)
XYZspeed = math.sqrt(Xspeed*Xspeed + Yspeed*Yspeed + Zspeed*Zspeed)
inlvlObjTimer = memory.read_u16_le(0x00069526)
if displayWaypoint == true and (waypointX ~= nil and waypointZ ~= nil) then
local Xdistance = waypointX - Xpos
local Zdistance = waypointZ - Zpos
local waypointAngle = math.atan2(Xdistance, Zdistance)
if waypointAngle < 0 then
waypointAngle = waypointAngle + (math.pi * 2)
end
waypointAngle = math.deg(waypointAngle)
gui.text(20, 200, string.format("%3d", waypointAngle))
end
gui.text(20,60, string.format("Pos: (%6d,%6d,%6d)", Xpos, Ypos, Zpos))
gui.text(20,80, string.format("Speed XZ: %3d Angle: %3d\n",XZspeed,XZdirection))
gui.text(20,100, string.format("Speed 3D: %3d", XYZspeed))
gui.text(20,120, string.format("Speed Y: %3d", Yspeed))
gui.text(20,140, string.format("Obj timer: %3d", inlvlObjTimer))
gui.text(20,300, string.format("Camera BAMS: %4d", cameraAngleBAMS))
gui.text(20,320, string.format("Bugs BAMS: %4d", xzAngleBAMS))
end
-- Hack to not trigger on every frame the key is down
local jKeyDown
local middleMouseDown
local function toggle_waypoints()
local keyboardInput = input.get()
local mouseInput = input.getmouse()
-- jKeyDown is nil when not set, middleMouseDown is false... All for the sake of consistency...?
if (keyboardInput["J"] == true or mouseInput["Middle"] == true) and (jKeyDown == nil and middleMouseDown == false) then
if displayWaypoint == true then
displayWaypoint = false
else
displayWaypoint = true
end
end
jKeyDown = keyboardInput["J"]
middleMouseDown = mouseInput["Middle"]
end
function tablelength(T)
local count = 0
for _ in pairs(T) do count = count + 1 end
return count
end
local form = nil
local textlabel = nil
local textbox = nil
local poslabel = nil
local posbox = nil
local okButton = nil
local copyButton = nil
local function OK_callback()
local text = forms.gettext(textbox)
if text ~= nil and string.len(text) ~= 0 then
local splitText = {}
local i = 0
for v in string.gmatch(text, "[+-]?%d+") do
splitText[i] = v
i = i + 1
end
if tablelength(splitText) == 2 then
waypointX = tonumber(splitText[0], 10)
waypointZ = tonumber(splitText[1], 10)
end
else
waypointX = nil
waypointZ = nil
end
end
local function Copy_callback()
forms.settext(textbox, string.format("%d/%d", Xpos, Zpos))
OK_callback()
end
form = forms.newform(200, 130, "Set X/Z waypoint")
textlabel = forms.label(form, "Wayp X/Z:", 0, 10, 60, 15)
textbox = forms.textbox(form, nil, 120, 20, nil, 60, 5)
okButton = forms.button(form, "Set", OK_callback, 10, 40, 170, 25)
copyButton = forms.button(form, "Copy Pos && Set", Copy_callback, 10, 70, 170, 25)
local function destroy_waypoint_window()
forms.destroyall()
end
-- Not supported in PSXHawk yet:
--event.onexit(destroy_waypoint_window, "Exit handler")
while true do
hud();
toggle_waypoints();
emu.yield();
end