-- Auto analog input script written by TASeditor
-- Main window
-- This function runs after the user clicked on the Start button.
function Start()
if PauseFlag == false
then if StartFlag == false
then if forms.ischecked(PosCheck) and not forms.ischecked(AngCheck)
then CalcAngle();
FollowAngle = forms.gettext(AngFollowTxt);
RadiusMin = forms.gettext(RadiusMinTxt);
RadiusMax = forms.getttext(RadiusMaxTxt);
Optimisation = forms.gettext(OptDrop);
TwoStep = forms.ischecked(TwoStepCheck);
--savestate.saveslot(0);
StartFlag = true;
forms.settext(StatLabel, "Started");
-- if forms.ischecked(AdvCheck)
-- then AdvMode = true;
-- else AdvMode = false;
-- end;
--X_0 = XPosition;
--Y_0 = YPosition;
--frame_start = emu.framecount();
elseif not forms.ischecked(PosCheck) and forms.ischecked(AngCheck)
then FollowAngle = forms.gettext(AngFollowTxt);
RadiusMin = forms.gettext(RadiusMinTxt);
RadiusMax = forms.gettext(RadiusMaxTxt);
Optimisation = forms.gettext(OptDrop);
TwoStep = forms.ischecked(TwoStepCheck);
--savestate.saveslot(0);
StartFlag = true;
forms.settext(StatLabel, "Started");
elseif forms.ischecked(PosCheck) and forms.ischecked(AngCheck)
then forms.settext(StatLabel, "Error: Uncheck one checkbox");
end;
end;
end;
end;
-- This function runs after the user clicked on the Pause button.
function Pause()
if StartFlag == true
then if PauseFlag == false
then PauseFlag = true;
forms.settext(StatLabel, "Paused");
forms.settext(PauseButton, "Continue");
client.pause();
Xinput["P1 X Axis"] = 0;
Yinput["P1 Y Axis"] = 0;
joypad.setanalog(Xinput);
joypad.setanalog(Yinput);
else PauseFlag = false
forms.settext(StatLabel, "Started");
forms.settext(PauseButton, "Pause");
end;
end;
end;
-- This function runs after the user clicked on the Stop button.
function Stop()
if StartFlag == true
then StartFlag = false;
PauseFlag = false;
forms.settext(StatLabel, "Stopped");
forms.settext(PauseButton, "Pause");
client.pause();
Xinput["P1 X Axis"] = 0;
Yinput["P1 Y Axis"] = 0;
joypad.setanalog(Xinput);
joypad.setanalog(Yinput);
X = 0;
Y = 0;
end;
end;
-- These functions run after the user clicked the "+" or "-" button.
function Add()
console.writeline("not implemented");
end;
function Sub()
console.writeline("not implemented");
end;
function CalcAngle()
DeltaX = forms.gettext(XPosGotoTxt) - memory.readfloat(XPosAddr, true);
DeltaY = forms.gettext(YPosGotoTxt) - memory.readfloat(YPosAddr, true);
Distance = math.sqrt(DeltaX^2 + DeltaY^2);
if FloatBool == "true" -- TODO:
then Alpha = math.atan2(DeltaX , DeltaY) * 180 / math.pi;
else Alpha = math.floor(math.atan2(DeltaX , DeltaY) * 32768/math.pi + 0.5);
end;
forms.settext(AngFollowTxt, Alpha);
end;
-- This function creates the main window.
function WindowForm()
local OptTable = {"None", "Line drawing", "Rotate around"};
Window = forms.newform(300, 500, "Auto analog input");
PosCheck = forms.checkbox(Window, "Go to position:", 5, 20);
forms.label(Window, "X =", 110, 10, 30, 20);
XPosGotoTxt = forms.textbox(Window, "0", 120, 20, nil, 140, 5);
forms.label(Window, "Y =", 110, 40, 30, 20);
YPosGotoTxt = forms.textbox(Window, "0", 120, 20, nil, 140, 35);
AngCheck = forms.checkbox(Window, "Follow angle:", 5, 75);
forms.label(Window, "a =", 110, 80, 30, 20);
AngFollowTxt = forms.textbox(Window, "0", 120, 20, nil, 140, 75);
forms.label(Window, "Radius: min =", 5, 120, 75, 20);
RadiusMinTxt = forms.textbox(Window, "", 30, 20, nil, 80, 115);
forms.label(Window, "max =", 115, 120, 35, 20)
RadiusMaxTxt = forms.textbox(Window, "", 30, 20, nil, 155, 115);
forms.label(Window, "Increment/decrement input angle:", 5, 150, 170, 20);
forms.button(Window, "+", Add, 200, 145, 23, 23);
forms.button(Window, "-", Sub, 175, 145, 23, 23);
forms.label(Window, "Optimisation:", 5, 180, 70, 20);
OptDrop = forms.dropdown(Window, OptTable, 80, 175, 100, 20);
TwoStepCheck = forms.checkbox(Window, "Two stepping", 190, 175);
forms.label(Window, "Status:", 5, 210, 40, 15);
StatLabel = forms.label(Window, "Stopped", 45, 210, 200, 20);
forms.button(Window, "Start", Start, 5, 230);
PauseButton = forms.button(Window, "Pause", Pause, 105, 230);
forms.button(Window, "Stop", Stop, 205, 230);
forms.label(Window, "Information:", 5, 270, 70, 20);
forms.label(Window, "Current angle:", 5, 290, 73, 20);
CurrAngLabel = forms.label(Window, "", 75, 290, 40, 20);
forms.label(Window, "Error%:", 130, 290, 40, 20);
ErrorLabel = forms.label(Window, "", 210, 290, 40, 20);
forms.label(Window, "Total error:", 5, 310, 70, 20);
TotErrLabel = forms.label(Window, "", 75, 310, 40, 20);
forms.label(Window, "Average error%:", 130, 310, 83, 20);
AvErrorLabel = forms.label(Window, "", 210, 310, 40, 20);
forms.label(Window, "U Position:", 5, 330, 58, 20);
UPosLabel = forms.label(Window, "", 75, 330, 80, 20);
forms.label(Window, "Distance:", 5, 350, 52, 20);
DistLabel = forms.label(Window, "", 75, 350, 80, 20);
--OptCheck = forms.checkbox(Window, "Optimize", 5, 280);
-- if tastudio.engaged()
-- then xLabel = forms.label(Window, "", 5, 280, 30, 15);
-- yLabel = forms.label(Window, "", 40, 280, 30, 15);
-- end;
end;
-- Address window
-- This function checks wheter the user has typed in the memory addresses or not.
-- It doesn't check if the typed address is the correct one.
-- The "0x" should not be deleted.
function Check()
success = false;
XPosAddr = forms.gettext(XPosAddrTxt);
YPosAddr = forms.gettext(YPosAddrTxt);
MovAngAddr = forms.gettext(MovAngAddrTxt);
CamAngAddr = forms.gettext(CamAngAddrTxt);
Offset = forms.gettext(OffsetTxt);
Type = forms.gettext(TypeDrop);
Modulo = forms.gettext(ModTxt);
DeadzoneMin = forms.gettext(MinTxt);
DeadzoneMax = forms.gettext(MaxTxt);
if XPosAddr ~= "0x" and YPosAddr ~= "0x" and MovAngAddr ~= "0x" and Offset ~= ""
then success = true;
end;
if CamAngAddr == "0x"
then HasGameRotatingCam = false;
CamAngAddr = 0;
else HasGameRotatingCam = true;
end;
if (Type == "Byte" and Modulo == "")
then Modulo = 256;
elseif (Type == "Word" and Modulo == "")
then Modulo = 65536;
elseif (Type == "DWord" and Modulo == "")
then Modulo = 4294967296;
elseif (Type == "Float" and Modulo == "")
then success = false;
end
if DeadzoneMin == ""
then DeadzoneMin = 0;
else DeadzoneMin = tonumber(DeadzoneMin);
end
if DeadzoneMax == ""
then DeadzoneMax = 129;
else DeadzoneMax = tonumber(DeadzoneMax);
end
if DeadzoneMin > DeadzoneMax
then success = false;
end;
if success == true
then -- Writes the addresses into a text file.
-- The user doesn't have to type in the addresses everytime.
AddrFile = io.open(ROMname, "a");
AddrFile:write(tonumber(XPosAddr), "\n",
tonumber(YPosAddr), "\n",
tonumber(MovAngAddr), "\n",
tostring(HasGameRotatingCam), "\n",
tonumber(CamAngAddr), "\n",
tonumber(Offset), "\n",
tostring(Type), "\n",
tonumber(Modulo), "\n",
DeadzoneMin, "\n",
DeadzoneMax);
AddrFile:close();
-- Closes the form where the user typed in the addresses.
forms.destroy(Addr);
WindowForm();
end;
end;
-- This function creates the form where the user needs to type in memory addresses.
function AddrForm()
local TypeTable = {"Byte","Word","DWord", "Float"};
Addr = forms.newform(280, 370, "Settings");
forms.label(Addr, "Horizontal position addresses:", 5, 5, 280, 20);
forms.label(Addr, "X:",5, 30, 20, 20);
XPosAddrTxt = forms.textbox(Addr, "0x", 70, 20, nil, 25, 25);
forms.label(Addr, "Y:",105, 30, 20, 20);
YPosAddrTxt = forms.textbox(Addr, "0x", 70, 20, nil, 125, 25);
forms.label(Addr, "Horizontal movement angle address:", 5, 55, 350, 20);
MovAngAddrTxt = forms.textbox(Addr, "0x", 70, 20, nil, 10, 75);
--FloatCheck = forms.checkbox(Addr, "Float?", 120, 75);
forms.label(Addr, "Horizontal camera angle address:", 5, 105, 340, 20);
CamAngAddrTxt = forms.textbox(Addr, "0x", 70, 20, nil, 10, 125);
forms.label(Addr, "Offset for the analog stick angle:", 5, 155, 360, 20)
OffsetTxt = forms.textbox(Addr, "", 70, 20, nil, 10, 175);
forms.label(Addr, "Unit system for angles:", 5, 205, 300, 20);
forms.label(Addr, "Type:", 5, 230, 40, 20);
TypeDrop = forms.dropdown(Addr, TypeTable , 45, 225, 80, 20);
forms.label(Addr, "modulo:", 130, 230, 45, 20);
ModTxt = forms.textbox(Addr, "", 60, 20, nil, 180, 225);
forms.label(Addr, "Deadzone:", 5, 255, 100, 20);
forms.label(Addr, "min:", 5, 280, 30, 20);
MinTxt = forms.textbox(Addr, "", 30, 20, nil, 35, 275);
forms.label(Addr, "max:", 70, 280, 30, 20);
MaxTxt = forms.textbox(Addr, "", 30, 20, nil, 105, 275);
forms.button(Addr, "Done", Check, 5, 300);
end;
-- Reads out the memory addresses from the file, if there's content in the file.
-- The memory addresses are saved in decimal numbers.
-- The file is in the main BizHawk folder and is called "<romname>.txt".
-- The main window will open.
XPosAddr = nil;
YPosAddr = nil;
MovAngAddr = nil;
HasGameRotatingCam = nil;
CamAngAddr = nil;
CamAngAddr = nil;
Offset = nil;
Type = nil;
Modulo = nil;
DeadzoneMin = nil;
DeadzoneMax = nil;
AddrFile = nil;
ROMname = gameinfo.getromname()..".ais";
AddrFile = io.open(ROMname, "r");
if AddrFile ~= nil
then XPosAddr = tonumber(AddrFile:read("*line"));
YPosAddr = tonumber(AddrFile:read("*line"));
MovAngAddr = tonumber(AddrFile:read("*line"));
HasGameRotatingCam = tostring(AddrFile:read("*line"));
CamAngAddr = tonumber(AddrFile:read("*line"));
Offset = tonumber(AddrFile:read("*line"));
Type = tostring(AddrFile:read("*line"));
Modulo = tonumber(AddrFile:read("*line"));
DeadzoneMin = tonumber(AddrFile:read("*line"));
DeadzoneMax = tonumber(AddrFile:read("*line"));
WindowForm();
AddrFile:close();
end;
-- If there's no content in the file a window will open, where the user types in the memory addresses once.
if AddrFile == nil---XPosAddr == nil and YPosAddr == nil and MovAngAddr == nil and HasGameRotatingCam == nil and CamAngAddr == nil
then AddrForm();
--Prevents crash.
-- XPosAddr = 0;
--YPosAddr = 0;
--MovAngAddr = 0;
--CamAngAddr = 0;
end
--**************************************************************************************************--
--Brute force script --
--**************************************************************************************************--
Xinput = {};
Yinput = {};
StartFlag = false;
PauseFlag = false;
X = 0; Y = 0;
CamAngle = 0;
InputAngle = 0;
Radius = 0;
steps = 0;
done = false;
--j = 0;
--diff = 10;
--bestdiff = 10;
-- X_0=0; Y_0=0;
-- X_g=0; Y_g=0;
-- X_b=0; Y_b=0;
-- frame_start = 0;
f=0;
f_old=0;
function sgn(x)
if x > 0
then return 1;
elseif x < 0
then return -1;
else return 0;
end;
end;
function LineDrawing(xStart, yStart, xEnd, yEnd)
Points = {};
i = 0;
dx = xEnd - xStart;
dy = yEnd - yStart;
incx = sgn(dx)
incy = sgn(dy)
if dx < 0 then dx = -dx; end;
if dy < 0 then dy = -dy; end;
if dx > dy
then pdx = incx; -- parallel step
pdy = 0;
ddx = incx; -- diagonal step
ddy = incy;
ef = dy; -- error steps fast, slow
es = dx;
else pdx = 0;
pdy = incy;
ddx = incx;
ddy = incy;
ef = dx;
es = dy;
end;
x = xStart;
y = yStart;
err = es/2;
--Points[0] = {X = x, Y = y}
for t = 0, es, 1 do
err = err - ef;
if err < 0
then err = err + es
x = x + ddx;
y = y + ddy;
else x = x + pdx;
y = y + pdy;
end
radius = math.sqrt(x^2+y^2);
if (math.abs(x) >= DeadzoneMin and math.abs(y) >= DeadzoneMin and
math.abs(x) <= DeadzoneMax and math.abs(y) <= DeadzoneMax and
radius >= tonumber(RadiusMin) and radius <= tonumber(RadiusMax))
then
Points[i] = {X = x, Y = y};
i = i+1;
end
end
bestDist = 9999999999;
for i, pt in pairs(Points) do
newDist = math.abs(math.atan2(pt.Y, pt.X) - InputAngle);
--print(tostring(math.abs(math.atan2(pt.Y, pt.X) - InputAngle)))
--print(pt)
--print(pt.X.." "..pt.Y.." "..(math.atan2(pt.Y, pt.X) % Modulo).." "..newDist.." ".. math.abs(math.atan2(Ybest, Xbest)-InputAngle));
if newDist < bestDist
then bestDist = newDist;
Xbest = Points[i].X;
Ybest = Points[i].Y;
end;
end;
end;
function RotateAround()
-- X = Xbest
-- Y = Ybest
-- InputAngleInt = math.atan2(Y, X); --console.writeline(InputAngle.." "..InputAngleInt);
-- if InputAngleInt == InputAngle
-- then --console.writeline("perfect");
-- else
-- repeat
-- if Steps % 2 == 0
-- then for i = 1,Steps,1 do
-- X = X - 1;
-- InputAngleInt = math.atan2(Y, X); --console.writeline(X.." "..Y.." "..Steps);
-- diff = math.abs(InputAngleInt - InputAngle);
-- if diff < bestdiff then Xbest = X; Ybest = Y; bestdiff = diff; end;
-- end;
-- if InputAngleInt == InputAngle then Xbest = X; Ybest = Y; break; end;
-- for i = 1,Steps,1 do
-- Y = Y - 1;
-- InputAngleInt = math.atan2(Y, X); --console.writeline(X.." "..Y.." "..Steps);
-- diff = math.abs(InputAngleInt - InputAngle);
-- if diff < bestdiff then Xbest = X; Ybest = Y; bestdiff = diff; end;
-- end;
-- if InputAngleInt == InputAngle then Xbest = X; Ybest = Y; break; end;
-- else for i = 1,Steps,1 do
-- X = X + 1;
-- InputAngleInt = math.atan2(Y, X); --console.writeline(X.." "..Y.." "..Steps);
-- diff = math.abs(InputAngleInt - InputAngle);
-- if diff < bestdiff then Xbest = X; Ybest = Y; bestdiff = diff; end;
-- end;
-- if InputAngleInt == InputAngle then Xbest = X; Ybest = Y; break; end;
-- for i = 1,Steps,1 do
-- Y = Y + 1;
-- InputAngleInt = math.atan2(Y, X); --console.writeline(X.." "..Y.." "..Steps);
-- diff = math.abs(InputAngleInt - InputAngle);
-- if diff < bestdiff then Xbest = X; Ybest = Y; bestdiff = diff; end;
-- end;
-- if InputAngleInt == InputAngle then Xbest = X; Ybest = Y; break; end;
-- end;
-- Steps = Steps + 1;
-- j = j +1;
-- until j == 100 --how long does this need to run?
-- Steps = 1;
-- end
-- done = true;
-- console.writeline("test");
-- j = 0;
end;
function NoOptimisation(radius)
Xbest = math.floor(math.cos(InputAngle)*radius+0.5);
Ybest = math.floor(math.sin(InputAngle)*radius+0.5);
--print(math.atan2(Ybest, Xbest).." ".. math.abs(math.atan2(Ybest, Xbest)-InputAngle));
end;
function TwoStepping()
end;
function CreateInput()
XPosition = memory.readfloat(XPosAddr, true);
YPosition = memory.readfloat(YPosAddr, true);
if Type == "Byte"
then MovAngle = memory.read_u8(MovAngAddr);
if HasGameRotatingCam == "true" then CamAngle = memory.read_u8(CamAngAddr); end;
elseif Type == "Word"
then MovAngle = memory.read_u16_be(MovAngAddr);
if HasGameRotatingCam == "true" then CamAngle = memory.read_u16_be(CamAngAddr); end;
elseif Type == "DWord"
then MovAngle = memory.read_u32_be(MovAngAddr);
if HasGameRotatingCam == "true" then CamAngle = memory.read_u32_be(CamAngAddr); end;
elseif Type == "Float"
then MovAngle = memory.readfloat(MovAngAddr, true);
if HasGameRotatingCam == "true" then CamAngle = memory.readfloat(CamAngAddr, true);end;
end;
if HasGameRotatingCam == "true"
then InputAngle = ((FollowAngle - CamAngle - Offset) % Modulo)*math.pi/(Modulo/2); --print("standard input");
else InputAngle = ((FollowAngle - Offset) % Modulo)*math.pi/(Modulo/2);
end;
if Optimisation == "None" then NoOptimisation(RadiusMax);
elseif Optimisation == "Rotate around" then RotateAround();
elseif Optimisation == "Line drawing" then LineDrawing(0,0, math.cos(InputAngle)*182, math.sin(InputAngle)*182)
end
-- if tastudio.engaged()
-- then forms.settext(xLabel, Xbest);
-- forms.settext(yLabel, Ybest);
-- else
Xinput["P1 X Axis"] = Xbest;
Yinput["P1 Y Axis"] = Ybest;
joypad.setanalog(Xinput);
joypad.setanalog(Yinput);
done = true
--print(MovAngle);
end;
function endscript()
forms.destroyall();
end;
while true do
f = emu.framecount();
if f_old ~= f then done = false; end;
f_old = f;
if StartFlag and not PauseFlag and not emu.islagged() and not done
then CreateInput();
end;
--event.onexit(endscript);
emu.yield();
end;