Skip to content

Instantly share code, notes, and snippets.

@Terrance
Last active February 26, 2016 21:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Terrance/36539b40b5904b4bc61c to your computer and use it in GitHub Desktop.
Save Terrance/36539b40b5904b4bc61c to your computer and use it in GitHub Desktop.
A Lua library that simplifies scripting custom mods within a song. SM allows stepped songs (in addition to themes) to make use of additional logic programmed in Lua -- the API provides full access to notes, mods and timing.
function val2key(vals)
keys = {}
for i, key in ipairs(vals) do keys[key] = true end
return keys
end
local modmgr = {}
modmgr.__index = modmgr
setmetatable(modmgr, {
__call = function(cls)
local self = setmetatable({}, cls)
self:new()
return self
end
})
function modmgr.new(self)
self.mods = {}
self.plrs = {}
for i, plr in ipairs({PLAYER_1, PLAYER_2}) do
if GAMESTATE:IsPlayerEnabled(plr) then
local state = GAMESTATE:GetPlayerState(plr)
self.plrs[plr] = {
diff = GAMESTATE:GetCurrentSteps(plr):GetDifficulty(),
opts = state:GetPlayerOptions("ModsLevel_Song")
}
end
end
end
-- mods: {[beat] -> function(PlayerOptions, beats/sec)}
function modmgr.add(self, mods, diffs, plrs)
diffs = diffs or {"Difficulty_Beginner", "Difficulty_Easy", "Difficulty_Medium",
"Difficulty_Hard", "Difficulty_Challenge", "Difficulty_Edit"}
plrs = plrs or {"PlayerNumber_P1", "PlayerNumber_P2"}
for beat, mod in pairs(mods) do
self.mods[beat] = self.mods[beat] or {}
table.insert(self.mods[beat], {diffs = val2key(diffs), plrs = val2key(plrs), mod = mod})
end
end
function modmgr.on(self)
self.modBeats = {}
for beat in pairs(self.mods) do table.insert(self.modBeats, beat) end
-- sort in descending order according to beat
table.sort(self.modBeats, function(a, b) return a > b end)
end
function modmgr.run(self, beat, bps)
for i, modOpts in ipairs(self.mods[beat]) do
for i, plr in ipairs({PLAYER_1, PLAYER_2}) do
if self.plrs[plr] and modOpts.plrs[plr] and modOpts.diffs[self.plrs[plr].diff] then
modOpts.mod(self.plrs[plr].opts, bps)
end
end
end
end
function modmgr.actor(self)
actor = Def.Actor({
OnCommand = function(self)
actor.mgr:on()
self.lastMod = -1
self:queuecommand("Update")
end,
UpdateCommand = function(self)
local beat = GAMESTATE:GetSongBeat()
for i, mb in ipairs(actor.mgr.modBeats) do
-- select the first mod that applies (later takes priority), only apply once
if mb <= beat and mb > self.lastMod then
self.lastMod = mb
actor.mgr:run(mb, GAMESTATE:GetSongBPS())
break
end
end
self:sleep(0.005)
self:queuecommand("Update")
end
})
actor.mgr = self
return actor
end
return modmgr

Installation

Save the script as modmgr.lua, inside a folder on StepMania's Lua path (e.g. /Program or /Program/lua).

Writing mod scripts

StepMania will read lua/default.lua inside the song directory.

Add an FGCHANGES line to the .ssc and .sm files:

#FGCHANGES:0.000=lua=1.000=0=0=1=====;

Inside default.lua, import modmgr and create a new instance:

local modmgr = require("modmgr")()

Register a table of mods, which maps the beat to a mod function (the arguments are a PlayerOptions instance, and the current beats per second):

modmgr:add({
    -- e.g. swap arrows between top and bottom           at the top:
    [15] = function(opts, bps) opts:Split(1, bps) end,   -- left, down
    [17] = function(opts, bps) opts:Cross(1, bps) end,   -- left, up
    [19] = function(opts, bps) opts:Reverse(1, bps) end, -- down, right
    [21] = function(opts, bps) opts:Split(0, bps) end,   -- down, up
    -- and reset to normal
    [23] = function(opts, bps) opts:Cross(0, bps * 2) end,
    [23.5] = function(opts, bps) opts:Reverse(0, bps * 2) end,
})

Each call to add() can take two additional arguments: diffs (a table of difficulty names to restrict the mods to) and plrs (as before but for active players).

Finally, add the actor to the rest of the frame:

return Def.ActorFrame({
    Def.Actor({
        Name = "Preserver",
        OnCommand = cmd(sleep, 1000)
    }),
    modmgr:actor()
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment