-- QuakeViewroll: adds Quake-style viewroll to Sonic Robo Blast 2 -- Copyright (C) 2024 Biff -- -- This program is free software; you can redistribute it and/or -- modify it under the terms of the GNU General Public License -- as published by the Free Software Foundation; either version 2 -- of the License, or (at your option) any later version. -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. local function dotProduct(x1,y1,x2,y2) return FixedMul(x1,x2)+FixedMul(y1,y2) end local function angToVec2(ang) return cos(ang), sin(ang) end local function getRightAngle(mobj, camera) if camera.chase == false then -- camera.angle returns 0 here, use mobj's angle return mobj.angle+ANGLE_90 else return camera.angle+ANGLE_90 end end local function viewRoll() if displayplayer == nil then return end if displayplayer.mo == nil then return end if (maptol & TOL_NIGHTS) then return end -- let them handle it local mo = displayplayer.mo local ang = 0 local angx, angy = 0, 0 local momx, momy = mo.momx, mo.momy ang = getRightAngle(mo, camera) angx, angy = angToVec2(ang) -- V_CalcRoll, Quake/WinQuake/view.c:81 local side = 0 local sign = 0 local value = 0 local cl_rollspeed = CV_FindVar("cl_rollspeed") local cl_rollangle = CV_FindVar("cl_rollangle") side = dotProduct(momx, momy, angx, angy) sign = side < 0 and -1 or 1 side = abs($) value = cl_rollangle.value if side < cl_rollspeed.value then -- CV_FLOAT convars return a multiple of FRACUNIT side = FixedDiv(FixedMul(side , value) , cl_rollspeed.value) else side = value end -- Doing viewrollangle = $ - side*sign accumulates viewroll. We don't want that. -- SV_ClientThink, Quake/WinQuake/sv_user.c:407 displayplayer.viewrollangle = FixedMul(InvAngle(FixedAngle(side*sign)), 4*FRACUNIT) local v_kicktime = CV_FindVar("v_kicktime") if displayplayer.v_dmg_time ~= nil and displayplayer.v_dmg_time > 0 then -- Why does C prioritize division here? local kick = FixedMul(FixedDiv(displayplayer.v_dmg_time, v_kicktime.value), displayplayer.v_dmg_roll) displayplayer.viewrollangle = $ + InvAngle(FixedAngle(kick)) displayplayer.v_dmg_time = $ - (FRACUNIT/35); -- one tic end if displayplayer.playerstate == PST_DEAD and CV_FindVar("cl_deathtilt").value ~= 0 then -- Tilt the camera when you die, just like in Quake displayplayer.viewrollangle = FixedAngle(80*FRACUNIT) end end local function kickRoll(target, inflictor)--, source, damage, damagetype) if displayplayer == nil then return end if displayplayer.mo == nil then return end local mo = displayplayer.mo if target == displayplayer.mo then -- V_ParseDamage, Quake/WinQuake/view.c:367 local fromX = displayplayer.mo.x local fromY = displayplayer.mo.y if inflictor ~= nil then fromX = inflictor.x fromY = inflictor.y end fromX = fromX - displayplayer.mo.x fromY = fromY - displayplayer.mo.x -- Normalize the Crocodile local length = FixedHypot(fromX, fromY) fromX = FixedDiv($, length) fromY = FixedDiv($, length) local v_kickroll = CV_FindVar("v_kickroll") local v_kicktime = CV_FindVar("v_kicktime") local ang = getRightAngle(mo, camera) local angX, angY = angToVec2(ang) local side = dotProduct(fromX, fromY, angX, angY) displayplayer.v_dmg_roll = FixedMul(10*FRACUNIT, FixedMul(side, v_kickroll.value)) displayplayer.v_dmg_time = v_kicktime.value end end local convars = {} local initialized = false local function writeConfig() if not initialized then return end local file = io.openlocal("client/qvr_autorun.cfg", "w") if file == nil then return end for _,v in ipairs(convars) do file:write(string.format("%s\t%s\n", v.name, v.string)) end file:close() end local function readConfig() local file = io.openlocal("client/qvr_autorun.cfg", "r") if file == nil then return end for c in file:lines() do local f = {} for k in c:gmatch("[^;,%s]+") do table.insert(f, k) end if #f >= 2 then local convar = CV_FindVar(f[1]) if convar ~= nil then CV_StealthSet(convar, f[2]) end -- don't write back to the cfg end end file:close() end convars[#convars+1] = CV_RegisterVar({ name = "cl_rollspeed", defaultvalue = "36", -- Quake cl_rollspeed. 200 is Quake's run speed, 36 is SRB2's (default) run speed flags = CV_CALL|CV_FLOAT, PossibleValue = nil, func = writeConfig }) convars[#convars+1] = CV_RegisterVar({ name = "cl_rollangle", -- Quake cl_rollangle. Internally multiplied by 4 defaultvalue = "2.0", flags = CV_CALL|CV_FLOAT, PossibleValue = nil, func = writeConfig }) convars[#convars+1] = CV_RegisterVar({ -- Quake v_kickroll. Internally multiplied by 10 name = "v_kickroll", defaultvalue = "0.6", flags = CV_CALL|CV_FLOAT, PossibleValue = nil, func = writeConfig }) convars[#convars+1] = CV_RegisterVar({ -- Quake v_kicktime, in seconds(?) name = "v_kicktime", defaultvalue = "0.5", flags = CV_CALL|CV_FLOAT, PossibleValue = nil, func = writeConfig }) convars[#convars+1] = CV_RegisterVar({ -- Tilt the camera when you die, like in Quake name = "cl_deathtilt", defaultvalue = "Off", flags = CV_CALL, PossibleValue = CV_OnOff, func = writeConfig }) readConfig() initialized = true -- default value being set calls func? addHook("ThinkFrame", viewRoll) addHook("MobjDamage", kickRoll) addHook("PlayerSpawn", function(player) -- avoid debug flood if player == displayplayer then displayplayer.v_dmg_roll = 0; displayplayer.v_dmg_time = 0 end end)