Bean's Dynamite Derby and What I Made for It

Note

Once again Out of Character, as it will be hard for me to properly explain things in character.

Also this post is STILL incomplete! There may be more things added in the future.

What the FUCK is a Dynamite Derby?


Well...

Bean's Dynamite Derby is a gamemode mod for Dr. Robotnik's Ring Racers, with it being a remake of the Elimination mod from SRB2Kart, featuring not just a fresh coat of paint, but also new features for better gameplay.

This is not the first project I've worked on for Ring Racers, see Party Phase for one of them, nor this is the first gamemode modification I've made (Combinator is the first for Ring Racers in that regard).

This is however, besides Party Phase, one of the biggest projects I've worked on, and now that it's released, I can show you what I've contributed to the mod, and some scrapped stuff in between.

How I got in the dev team in the first place


One of the first few things noted in the mod during testing about a few months ago was crucial and needed to be fixed- rampant desyncs.

A desync is a term referring to the client not keeping up with what the server sees, and the server sees that, so it resends the server's information about that to the client. This is a bad thing, as it'll stutter the server for a bit, and may get longer the more info the server needs to send. It was even worse back in SRB2Kart, where everyone basically stalls as the server attempts to sync back a client, with a high chance of it just straight up not working and kicking the desynching player anyway.

I was called into the dev team by Mr. Logan (the artist) for this purpose- attempt to figure out the reason for the desyncs. And in a bit me and Aqua figured out why.

Erm...

The bdd table, at the time, was sending the entire table every time it needs to send the info (e.g. on player join)

addHook("NetVars", function(net)
    bdd = net($)
end)
lua/derbyInit.lua

The bdd table contains specific information that can't be archived to be sent, like cvar pointers. These are userdata that can't be sent specifically.

After fixing that, we concluded that the resyncs were mostly gone.

DESYNC DEFEATED

String packets.

Programming splitscreen


The horror.

Splitscreen- A Ring Racers feature, and the hardest to develop for, atleast in terms of making HUD code for it.

It also doesn't help that a dedicated video flag to help with said splitscreen jimbo jumbo does not work in Lua due to the differences of how Lua hud hooks render.

-- This don't work !!!
-- Always defaults to the last screen
v.draw(0, 0, v.cachePatch("MISSING"), V_SPLITSCREEN)
fuck.lua

So for this, I made an entire dedicated function to fucking do the splitscreen snapping myself.

Feel free to take this, or anything in derbyUtils.lua for that matter.

local function DBU_SnapSplitscreen(v, c, x, y, scr, spl)
	scr = $ or 0
	spl = $ or DBU_FSplitscreen
	-- heeere it is
	local screenwidth = (v.width()/v.dupx())<<FRACBITS
	local screenheight = (v.height()/v.dupy())<<FRACBITS
	local basewidth = 320<<FRACBITS
	local baseheight = 200<<FRACBITS
	local splitnum = c.pnum - 1 -- can you believe it guys??? an undocumented feature !!!!
	-- If splitscreen is not available, then simply default to 0
	-- TODO: Use a displayplayers hack to do normal splitscreen on 2.3
	local splitscr = spl
	
	-- Handle halving depending on splitscreen shit
	if splitscr > 0 then
		screenheight = $ / 2
		baseheight = $ / 2
		if splitscr > 1 then 
			screenwidth = $ / 2
			basewidth = $ / 2
		end
	end
	
	-- Splitscreen non 16:10 resolution never works. Lets try anyway.
	if v.width() ~= 320*v.dupx() then
		if (scr & V_SNAPTORIGHT) then x = $ + (screenwidth - basewidth) 
		elseif ((scr & V_SNAPTOLEFT) ~= V_SNAPTOLEFT) then x = $ + ((screenwidth - basewidth) / 2) end
	end
	
	if v.height() ~= 200*v.dupy() then
		if (scr & V_SNAPTOBOTTOM) then y = $ + (screenheight - baseheight) 
		elseif ((scr & V_SNAPTOTOP) ~= V_SNAPTOTOP) then y = $ + ((screenheight - baseheight) / 2) end
	end
	
	-- The part thats actually important
	if splitscr == 1 and splitnum == 1 then
		y = $ + screenheight
	elseif splitscr > 1 then
		if (splitnum == 1 or splitnum == 3) then
			x = $ + screenwidth
		end
		if (splitnum >= 2) then
			y = $ + screenheight
		end
	end
	
	return x, y
end
lua/derbyUtils.lua

Now all you needed to do is define splitscreen positions for a single screen of that splitscreen type, add V_SNAPTOLEFT|V_SNAPTOTOP to the draw flags (as the function automatically handles that) and voila.

local OUT_X = 320/2
local OUT_Y = 200/2
	
-- Handle splitscreen positioning
if splitscr then -- splitscreen
	OUT_Y = 200/4
	if splitscr > 1 then  -- 3/4p
		OUT_X = 320/4
	end
end
	
-- Killing everyone
OUT_X, OUT_Y = BDD_SplitscreenFUCK(v, c, OUT_X<<FRACBITS, OUT_Y<<FRACBITS) -- Alias, and its original name before it was moved
	
-- 3/4p, stationary (2.3)
if splitscr > 1 then 
	-- oh hey we can ignore scrolling for now
	v.drawScaled(OUT_X - ((patch.width/2)<<FRACBITS), OUT_Y - ((patch.height/2)<<FRACBITS), FRACUNIT, patch, splitflags)
	return
end
lua/derbyHudNeoNeo.lua

I wish to no longer work on splitscreen again. Atleast without this function.

I FUCKING HATE SPLITSCREEN

Tally


Ah... the tally.... The custom end of level tally I worked probably too hard on.

The tally

As you can see, the rings counter and input display are still on screen. I cannot hide them. Teehee!

This arguably took me less time to make than splitscreen logic, though it was hard to actually get the tally to function normally.

You cannot hide the vanilla tally. Best you can do is to not trigger the tally at all.

But what about triggering stuff on p.exiting you say?????

Just don't. Lmao

-- ThinkFrame runs after player thinkers, which is why this works
addHook("ThinkFrame", function()
    for p in players.iterate do
        if p.spectator then continue end
        -- Pretend there's more here...
        p.exiting = 1
    end
end)

-- At the start of the next tic, set it back to zero. This is called as the "exiting hack".
addHook("PreThinkFrame", function()
    for p in players.iterate do
        if p.spectator then continue end
        -- Pretend there's more here too...
        p.exiting = 0
    end
end)
example.lua

As to why we didn't use P_DoPlayerExit, well... it bugged out the point distribution. That's a story for another day.

The tally itself is set up in a way similar to the vanilla tally, though it's global, but sets specific stuff for the player in HUD code. Nothing too special.

Bean's Woods


Not yet. Not yet....

Conclusion


Bean's Woods is not done, therefore no conclusion. I did say this is still incomplete after all.....

:)

lmao