if (SpringESkinTunes ~= nil) return end //TODO: //1. do the todo deliberately placed lower down //DOWNLOAD THE README FROM THE SRB2 MESSAGE BOARD FOR AN IMPLEMENTATION GUIDE: // //BACK-END----------------------------------------------- local function IsNumber(nbr) return nbr ~= nil and type(nbr) == "number" end local function IsTable(tbl) return tbl ~= nil and type(tbl) == "table" end local function IsString(str) return str ~= nil and type(str) == "string" end local function TableHasValue(tbl, val) if(IsTable(tbl) ~= true) return 0 end for k, v in pairs(tbl) if(v == val) return true end end return false end local function CountTable(tbl) if(IsTable(tbl) ~= true) return 0 end local c = 0 for k, v in pairs(tbl) c = $ + 1 end return c end local function StringEmptyOrNil(str, freeType) if(freeType ~= true and IsString(str) ~= true) return true end if(str==nil)//'nil' can tostring() into "nil" but i don't want that return true end str = tostring(str) local nospaces="" for c in str.gmatch(str,".")//for every character in line ('.' matcher being 'any condition') if(c~=" ") nospaces = nospaces..c end end return string.len(nospaces) <= 0 end local function IsBool(b) if(b==nil) return false end if(type(b) ~= "boolean") return false end return true end local function IsPlayer(p) if(p==nil) return false end if(type(p)~="userdata") return false end if(userdataType(p)~="player_t") return false end if(p.valid ~= true) return false end return true end //specially designed to circumvent the fact that dedicated servers count as a //non-spectator player in players.iterate (yes, really) local function PlayerPhysicallyExists(p) if(IsPlayer(p) ~= true) return false end if(p.spectator == true) return false end if(p.mo==nil) return false end if(type(p.mo)~="userdata") return false end if(userdataType(p.mo)~="mobj_t") return false end if(IsBool(p.mo.valid)~=true) return false end if(p.mo.valid~=true) return false end return true end //FRONT-END----------------------------------------------------- local register = {} local tuneInfo = {} tuneInfo["invinc"] = {} tuneInfo["invinc"]["origSong"] = "kinvnc" tuneInfo["invinc"]["condition"] = function(p) return IsPlayer(p) == true and p.invincibilitytimer > 0 == true end tuneInfo["grow"] = {} tuneInfo["grow"]["origSong"] = "kgrow" tuneInfo["grow"]["condition"] = function(p) return IsPlayer(p) == true and p.growshrinktimer > 0 == true end //late joiners will match their registry to the host. this //is only relevant if people decide to use the API calls //non-deterministically which is entirely possible so might as well //keep my mind open addHook("NetVars", function(x) register = x($) end) addHook("PostThinkFrame", function() for tuneKey, tuneInfoSingular in pairs(tuneInfo) Music_Remap(tuneKey, tuneInfoSingular["origSong"]) end //do it backwards to give port priority for i = 3, 0, -1 local p = displayplayers[i] if(PlayerPhysicallyExists(p) ~= true) continue end for skin, tuneList in pairs(register) if(p.mo.skin ~= skin) continue end for tune, songsAndSeed in pairs(tuneList) if(tuneInfo[tune]["condition"](p) == true) Music_Remap(tune, songsAndSeed["songs"][(songsAndSeed["seed"] % CountTable(songsAndSeed["songs"])) + 1]) else songsAndSeed["seed"] = leveltime end end end end end) local function Set(skinName, tuneInfoKey, tuneSongs) if(StringEmptyOrNil(skinName, false) == true) return false end //TODO: add check for skin existing if this gets merged: https://gitlab.com/kart-krew-dev/ring-racers/-/merge_requests/42 if(StringEmptyOrNil(tuneInfoKey, false) == true) return false end if(IsString(tuneSongs) == true) tuneSongs = {tuneSongs} end if(IsTable(tuneSongs) ~= true) return false end local tuneInfoExists = false for k, v in pairs(tuneInfo) if(k == tuneInfoKey) tuneInfoExists = true break end end if(tuneInfoExists ~= true) return false end local tuneSongs_filtered = {} local tuneSongs_filtered_writeIndex = 1 for k, v in pairs(tuneSongs) //can't check if 'v' (aka song in hardcode) is a real song //but the default response from the game is to just play silence //which i'm okay with if(StringEmptyOrNil(v, false) ~= true and TableHasValue(tuneSongs_filtered, v) ~= true) tuneSongs_filtered[tuneSongs_filtered_writeIndex] = v tuneSongs_filtered_writeIndex = $ + 1 end end if(tuneSongs_filtered_writeIndex <= 1) //if no songs passed the filter return false end if(IsTable(register[skinName]) ~=true) register[skinName] = {} end if(IsTable(register[skinName][tuneInfoKey]) ~= true) register[skinName][tuneInfoKey] = {} end register[skinName][tuneInfoKey]["songs"] = tuneSongs_filtered register[skinName][tuneInfoKey]["seed"] = leveltime return true end local function Erase(skinKey, tuneInfoKey, tuneSongs) if(IsString(tuneSongs) == true) tuneSongs = {tuneSongs} end local skinExists = StringEmptyOrNil(skinKey, false) ~= true and register[skinKey] ~= nil local tuneInfoKeyExists = skinExists == true and StringEmptyOrNil(tuneInfoKey, false) ~= true and register[skinKey][tuneInfoKey] ~= nil local anyTuneSongExists = tuneInfoKeyExists == true and IsTable(tuneSongs) == true if(anyTuneSongExists == true) anyTuneSongExists = false for k, v in pairs(register[skinKey][tuneInfoKey]["songs"]) if(StringEmptyOrNil(v, false) ~= true and TableHasValue(tuneSongs, v) == true) anyTuneSongExists = true break end end end local ret = {} ret[1] = false ret[2] = 0 if(anyTuneSongExists) local newSongs = {} local newSongs_writeIndex = 1 for k, v in pairs(register[skinKey][tuneInfoKey]["songs"]) if(StringEmptyOrNil(v, false) ~= true and TableHasValue(tuneSongs, v) ~= true and TableHasValue(newSongs, v) ~= true) newSongs[newSongs_writeIndex] = v newSongs_writeIndex = $ + 1 end end if(newSongs_writeIndex > 1) register[skinKey][tuneInfoKey]["songs"] = newSongs ret[2] = newSongs_writeIndex -1 else local removeCount = CountTable(register[skinKey][tuneInfoKey]) register[skinKey][tuneInfoKey] = nil //i'm pretty sure this outcome is impossible ret[2] = removeCount end elseif(tuneInfoKeyExists) if(IsTable(tuneSongs) ~= true) register[skinKey][tuneInfoKey] = nil ret[2] = 1 else ret[2] = 0 end elseif(skinExists) if(IsString(tuneInfoKey) ~= true and IsTable(tuneSongs) ~= true) register[skinKey] = nil ret[2] = 1 else ret[2] = 0 end else //wrapping outcomes in these conditionals because i don't want attempting to delete something that doesn't already exist to turn into falling backwards into deleting something more important (i.e. you try to delete the 'ana ng' skin but that skin doesn't exist so otherwise it'd just delete the whole table) if(IsString(skinKey) ~= true and IsString(tuneInfoKey) ~= true and IsTable(tuneSongs) ~= true) register = {} ret[2] = 1 else ret[2] = 0 end end ret[1] = ret[2] >= 1 return ret end //not actually a part of the api nor used anywhere but i'm not getting rid of it bc //it's really useful for debugging and i'm not giving just this item its own entire file local function DBGRegistryToString() local ret = "REGISTRY AT TIC "..tostring(leveltime)..":\n" for skinName, skinInfo in pairs(register) ret = $..skinName..":\n" for tuneName, tuneInfo in pairs(skinInfo) ret = $.." "..tuneName..":\n" for anonKey, songName in pairs(tuneInfo["songs"]) ret = $.." "..songName.."\n" end end end return ret end local skinTunes = {} skinTunes.GetVersion = function() return 1 end //Advance this by 1 every time anything about this Lua file changes so that those relying on the script can conditionalize to the assumed state of the API skinTunes.Set = Set skinTunes.Erase = Erase rawset(_G, "SpringESkinTunes", skinTunes)