diff --git a/compat/README.txt b/compat/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..82453f74ac5d841a1108e92d30c62c654d70b3ac --- /dev/null +++ b/compat/README.txt @@ -0,0 +1 @@ +Addons that vere modified for compatibility with booststack. diff --git a/compat/slipstream.lua b/compat/slipstream.lua new file mode 100644 index 0000000000000000000000000000000000000000..a75d24147ef564ce89e33e18244eb6bb7d5ac90a --- /dev/null +++ b/compat/slipstream.lua @@ -0,0 +1,424 @@ +-- Based on KL_Slipstream-v3 +-- Added booststack support + +-- Author: Ashnal +-- Adds slipstreaming aka drafting like the later Mario Kart games +-- Youll see small gray speed lines when you're charging a slipstream boost +-- Theres a subtle woosh sound when it activates, and the gray speed lines will become normal size and more frequent +-- You need to have another racer within the maxangle of your heading, and be within the maxdistiance range to charge it +-- the closer you are to them, the faster it will charge +-- KNOWN BUG: the fast lines have some wierd angles on OpenGL rendering. No idea why. + +local cv_slipstream = CV_RegisterVar({ + name = "slipstream_enabled", + defaultvalue = "Off", + flags = CV_NETVAR, + PossibleValue = CV_OnOff +}) +local ss_running = cv_slipstream.value + +local cv_anglefix = CV_RegisterVar({ + name = "slipstream_anglefix", + defaultvalue = "On", + flags = CV_NETVAR, + PossibleValue = CV_OnOff, +}) + +local cv_colorized = CV_RegisterVar({ + name = "slipstream_colorized", + defaultvalue = "On", + flags = NULL, + PossibleValue = CV_OnOff +}) + +local cv_reminder = CV_RegisterVar({ + name = "slipstream_reminder", + defaultvalue = "Off", + flags = NULL, + PossibleValue = CV_OnOff +}) + +local cv_maxdistance = CV_RegisterVar({ + name = "slipstream_maxdistance", + defaultvalue = 2400, -- enough room to charge slowly, and dodge + flags = CV_NETVAR, + PossibleValue = CV_Unsigned +}) + +local cv_t2chargedist = CV_RegisterVar({ + name = "slipstream_t2chargedist", + defaultvalue = 1400, + flags = CV_NETVAR, + PossibleValue = CV_Unsigned +}) + +local cv_t3chargedist = CV_RegisterVar({ + name = "slipstream_t3chargedist", + defaultvalue = 300, -- danger close, woudln't be able to dodge an aimed item + flags = CV_NETVAR, + PossibleValue = CV_Unsigned +}) + +local cv_maxangle = CV_RegisterVar({ + name = "slipstream_maxangle", + defaultvalue = "10", --11 degrees + flags = CV_NETVAR|CV_FLOAT, + PossibleValue = CV_Unsigned +}) + +-- This is measured in tics +local cv_chargetoboost = CV_RegisterVar({ + name = "slipstream_chargetoboost", + defaultvalue = 5*TICRATE/2, + flags = CV_NETVAR, + PossibleValue = CV_Unsigned +}) + +local cv_boosttime = CV_RegisterVar({ + name = "slipstream_boosttime", + defaultvalue = 2*TICRATE, + flags = CV_NETVAR, + PossibleValue = CV_Unsigned +}) + +local cv_minimumspeed = CV_RegisterVar({ + name = "slipstream_minimumspeed", + defaultvalue = 28, + flags = CV_NETVAR, + PossibleValue = CV_Unsigned +}) + +-- Boost youll get if you're a 1 kartspeed +local cv_maxspeedboostpercent = CV_RegisterVar({ + name = "slipstream_maxspeedboostpercent", + defaultvalue = "0.27", + flags = CV_NETVAR|CV_FLOAT, + PossibleValue = {MIN = 0, MAX = INT32_MAX} +}) + +-- Boost youll get if you're a 9 or higher kartspeed +local cv_minspeedboostpercent = CV_RegisterVar({ + name = "slipstream_minspeedboostpercent", + defaultvalue = "0.17", + flags = CV_NETVAR|CV_FLOAT, + PossibleValue = {MIN = 0, MAX = INT32_MAX} +}) + +local cv_accelboostpercent = CV_RegisterVar({ + name = "slipstream_accelboostpercent", + defaultvalue = "0.50", + flags = CV_NETVAR|CV_FLOAT, + PossibleValue = {MIN = 0, MAX = INT32_MAX} +}) + +local cv_maxdraftspeedboostpercent = CV_RegisterVar({ + name = "slipstream_maxdraftspeedboostpercent", + defaultvalue = "0.12", + flags = CV_NETVAR|CV_FLOAT, + PossibleValue = {MIN = 0, MAX = INT32_MAX} +}) + +local cv_mindraftspeedboostpercent = CV_RegisterVar({ + name = "slipstream_mindraftspeedboostpercent", + defaultvalue = "0.01", + flags = CV_NETVAR|CV_FLOAT, + PossibleValue = {MIN = 0, MAX = INT32_MAX} +}) + + +local soundcooldown = 3*TICRATE +local starttime = 6*TICRATE + (3*TICRATE/4) +local slipstreamsfx = sfx_s3k82 + +local function IntPercentToFixed(int) + return FixedDiv(int*FRACUNIT, 100*FRACUNIT) +end + +-- prefer to input all fixed values +-- https://en.wikipedia.org/wiki/Feature_scaling +local function Rescale(value, oldmin, oldmax, newmin, newmax) + return newmin + FixedMul(FixedDiv( value-oldmin , oldmax-oldmin), newmax-newmin) +end + +local function SpawnFastLines(p, tier, color) + if p.fastlinestimer == 1 or tier == 1 then + local fast = P_SpawnMobj(p.mo.x + (P_RandomRange(-36,36) * p.mo.scale), + p.mo.y + (P_RandomRange(-36,36) * p.mo.scale), + p.mo.z + (p.mo.height/2) + (P_RandomRange(-20,20) * p.mo.scale), + MT_FASTLINE) + fast.angle = R_PointToAngle2(0, 0, p.mo.momx, p.mo.momy) + fast.momx = 3*p.mo.momx/4 + fast.momy = 3*p.mo.momy/4 + fast.momz = 3*p.mo.momz/4 + fast.color = color + fast.colorized = true + K_MatchGenericExtraFlags(fast, p.mo) + + fast.scale = $/tier + p.fastlinestimer = tier + end + p.fastlinestimer = max($-1, 1) +end + +local function FSonicRunDust(mo) + local angle = mo.angle + local newx + local newy + local parts + local i + if leveltime%2 then + for i = 0,1 do + newx = mo.x + P_ReturnThrustX(mo, angle + ((i == 0) and -1 or 1)*ANGLE_90, FixedMul(16*FRACUNIT, mo.scale)) + newy = mo.y + P_ReturnThrustY(mo, angle + ((i == 0) and -1 or 1)*ANGLE_90, FixedMul(16*FRACUNIT, mo.scale)) + parts = P_SpawnMobj(newx, newy, (verticalflip and (mo.ceilingz or -1) or mo.floorz), MT_PARTICLE) + parts.target = mo + parts.angle = angle - ((i == 0) and -1 or 1)*ANGLE_45 + parts.scale = mo.scale + parts.destscale = mo.scale*3 + parts.scalespeed = mo.scale/6 + parts.momx = 3*mo.momx/5 + parts.momy = 3*mo.momy/5 + parts.fuse = 5 + end + end +end + +--hud.add(function(v, p, c) +-- if p.spectator then return end +-- v.drawString(12,145,FixedMul(p.kartstuff[k_speedboost], 100*FRACUNIT)>>FRACBITS,V_SNAPTOLEFT,"left") +--end) + +booststack.registerBoostType("slipstreamboost", { + stacktics = false, + dynamic = true, + + getBoostPower = function(pmo) + local clampedkartspeed = max(min(pmo.player.kartspeed, 9), 1) + + local speedboost = Rescale(clampedkartspeed*FRACUNIT, 9*FRACUNIT, FRACUNIT, cv_minspeedboostpercent.value, cv_maxspeedboostpercent.value) + local accelboost = cv_accelboostpercent.value + + return speedboost, accelboost + end, +}) + +booststack.registerBoostType("draftingboost", { + stackable = false, + dynamic = true, + + getBoostPower = function(pmo) + local clampedkartspeed = max(min(pmo.player.kartspeed, 9), 1) + local draftingboost = Rescale(clampedkartspeed*FRACUNIT, 9*FRACUNIT, FRACUNIT, cv_mindraftspeedboostpercent.value, cv_maxdraftspeedboostpercent.value) + + return draftingboost, 0 + end +}) + +local T3_DISTANCE +local T2_DISTANCE +local MAX_DISTANCE + +addHook("NetVars", function(net) + T3_DISTANCE = net(T3_DISTANCE) + T2_DISTANCE = net(T2_DISTANCE) + MAX_DISTANCE = net(MAX_DISTANCE) + ss_running = net(ss_running) +end) + +addHook("ThinkFrame", function() + if leveltime <= 1 then + ss_running = cv_slipstream.value + + T3_DISTANCE = cv_t3chargedist.value * mapobjectscale + T2_DISTANCE = cv_t2chargedist.value * mapobjectscale + MAX_DISTANCE = cv_maxdistance.value * mapobjectscale + end + + if not ss_running then return end -- Has to be turned on + + -- Key - player_t, value - bool + -- Used for optimizing out redundant checks + local nocheck = {} + + -- Key - player_t (slipstreaming), value - player_t (target) + -- Table for slipstream targets found on this frame + local targets = {} + + -- Key - player_t, value - table + -- Yes i had to name it like that + -- It actually stands for "per player" + local nocheck_pp = {} + + for p in players.iterate do + + if leveltime == 3*TICRATE and cv_reminder.value then + chatprintf(p, "\131* Don't forget you can \130draft\131 behind another player to charge a \130slipstream speed boost!", true) + end + + if p.mo and p.mo.valid then -- must have valid player mapobject + local pmo = p.mo + local ks = p.kartstuff + + nocheck_pp[p] = {} + + if ks[k_spinouttimer] and ks[k_wipeoutslow] == 1 then -- no slipstreaming if you've bumped or spun out + p.slipstreamboost = 0 + p.slipstreamcharge = 0 + else + + if p.slipstreamcharge == nil then + p.slipstreamcharge = 0 + p.slipstreamboost = 0 + p.fastlinestimer = 0 + p.slipstreamsoundtimer = false + end + + -- reset until we find a valid slipstreamtarget this frame + local slipstreamtarget + local angletotarget + local mindisttotarget + local disttotarget + local charge = 0 + local speedboost = ks[k_speedboost] + local accelboost = ks[k_accelboost] + + if P_IsObjectOnGround(pmo) + and FixedDiv(p.speed, mapobjectscale)/FRACUNIT >= cv_minimumspeed.value then -- must be moving decently on the ground + + for potentialtarget in players.iterate do + if nocheck[potentialtarget] -- They are too slow, don't even bother to check + or (nocheck_pp[potentialtarget] and nocheck_pp[potentialtarget][p]) -- Bad angle/distance, checked on some previous iteration + or targets[potentialtarget] == p -- If we are their slipstream target, we can't use them as target ourself obviously + or not (potentialtarget.mo and potentialtarget.mo.valid and potentialtarget.mo ~= pmo) then continue end + + local tmo = potentialtarget.mo + + if not potentialtarget.kartstuff[k_hyudorotimer] -- or ghosts + and P_IsObjectOnGround(tmo) -- or airbourne karts + and FixedDiv(potentialtarget.speed, mapobjectscale)/FRACUNIT >= cv_minimumspeed.value then -- or slowpokes + + local checkangle = pmo.angle - (cv_anglefix.value and ((ANG1*11)*ks[k_drift]) or 0) + angletotarget = abs(checkangle - R_PointToAngle2(pmo.x, pmo.y, tmo.x, tmo.y)) + disttotarget = FixedHypot(pmo.x - tmo.x, pmo.y - tmo.y) + + if ks[k_drift] then + if angletotarget > FixedAngle(cv_maxangle.value*2) then + continue + end + + if disttotarget > 3*MAX_DISTANCE/5 then + continue + end -- max slipstream distance while drifting + else + if angletotarget > FixedAngle(cv_maxangle.value) then + continue + end + + if disttotarget > MAX_DISTANCE then + nocheck_pp[p][potentialtarget] = true + continue + end -- max slipstream distance + end -- Narrow angle for following to slipstream + + if mindisttotarget == nil or mindisttotarget > disttotarget then + mindisttotarget = disttotarget + slipstreamtarget = potentialtarget + end + end + end + else + nocheck[p] = true + end + + -- add/remove slipstream charge + if slipstreamtarget then + targets[p] = slipstreamtarget + disttotarget = mindisttotarget + + if disttotarget > T2_DISTANCE then + charge = 1 + elseif disttotarget > T3_DISTANCE then + charge = 2 + else + charge = 3 + end + + p.slipstreamcharge = min($+charge, cv_chargetoboost.value) + -- print(p.mo.skin + " " +slipstreamtarget.mo.skin + " angletotarget: " + angletotarget + " disttotarget: " + disttotarget + " charging slipstream: " + p.slipstreamcharge) + + -- also spawn mini lines if charging to teach/show player the charging area + if p.slipstreamboost == 0 then + + SpawnFastLines(p, 2, SKINCOLOR_WHITE) + + local clampedkartspeed = max(min(p.kartspeed, 9), 1) + local draftingboost = Rescale(clampedkartspeed*FRACUNIT, 9*FRACUNIT, FRACUNIT, cv_mindraftspeedboostpercent.value, cv_maxdraftspeedboostpercent.value) + speedboost = max(speedboost, draftingboost) + p.slipstreamsoundtimer = false + + end + + else + -- if p.slipstreamcharge then print(p.mo.skin + " losing slipstream " + p.slipstreamcharge) end + p.slipstreamcharge = max($-1, 0) + end + + if p.slipstreamcharge >= cv_chargetoboost.value then + + -- print(p.mo.skin + " slipstreaming!") + p.slipstreamboost = cv_boosttime.value + + if p.slipstreamsoundtimer == false then + S_StartSound(p.mo, slipstreamsfx) + p.slipstreamsoundtimer = true + end + + end + + if p.slipstreamboost then + if cv_colorized.value then + SpawnFastLines(p, 1, p.skincolor) + else + SpawnFastLines(p, 1, SKINCOLOR_WHITE) + end + + if P_IsObjectOnGround(pmo) then + FSonicRunDust(pmo) + end + + end + + if not booststack.running then + if p.slipstreamboost then + local clampedkartspeed = max(min(p.kartspeed, 9), 1) + local slipstreamspeedboost = Rescale(clampedkartspeed*FRACUNIT, 9*FRACUNIT, FRACUNIT, cv_minspeedboostpercent.value, cv_maxspeedboostpercent.value) + + speedboost = max(speedboost, slipstreamspeedboost) + accelboost = max(accelboost, cv_accelboostpercent.value) + end + + -- value smoothing + if (speedboost > p.kartstuff[k_speedboost]) then + p.kartstuff[k_speedboost] = speedboost + else + p.kartstuff[k_speedboost] = p.kartstuff[k_speedboost] + (speedboost - p.kartstuff[k_speedboost])/(TICRATE/2) + end + + p.kartstuff[k_accelboost] = accelboost + else + if p.slipstreamboost then + booststack.doBoost(pmo, "slipstreamboost", 1) + elseif charge then + booststack.doBoost(pmo, "draftingboost", 1) + end + end + + -- if p.slipstreamboost then print(p.mo.skin + " slipstreamboost: " + p.slipstreamboost) end + p.slipstreamboost = max($-1, 0) + --p.slipstreamsoundtimer = max($-1, 0) + end + else + nocheck[p] = true + end + end +end)