Skip to content

Instantly share code, notes, and snippets.

@chadac
Created October 23, 2023 00:22
Show Gist options
  • Save chadac/339c2f8a9239e793d209af595282ffa5 to your computer and use it in GitHub Desktop.
Save chadac/339c2f8a9239e793d209af595282ffa5 to your computer and use it in GitHub Desktop.
Transformice Speedrun Module
catData = {
["vanilla%"] = {
description = "All vanilla maps that can be completed by a single player. Plays as shaman only when needed.",
sham = false, --default sham settings
maps = {
{id="0",sham=true}, {id="1"}, {id="2"}, {id="3",sham=true},
{id="4",sham=true}, {id="5",sham=true}, {id="6"}, {id="7"},
{id="8"}, {id="9",sham=true}, {id="10"}, {id="11"},
{id="12"}, {id="13"}, {id="14"}, {id="15",sham=true},
{id="16",sham=true}, {id="17",sham=true},
{id="18",sham=true}, {id="19"}, {id="20"},
{id="21",sham=true}, {id="22"}, {id="23"}, {id="24"},
{id="25",sham=true}, {id="26"}, {id="27"}, {id="28"},
{id="30"}, {id="31"}, {id="32",sham=true},
{id="34",sham=true}, {id="35"}, {id="36",sham=true},
{id="37"}, {id="38",sham=true}, {id="39",sham=true},
{id="40"}, {id="41"}, {id="42"}, {id="43"}, {id="44"},
{id="45"}, {id="46",sham=true}, {id="47",sham=true},
{id="48",sham=true}, {id="49", sham=true}, {id="50",sham=true},
{id="51",sham=true}, {id="52",sham=true}, {id="53"},
{id="54",sham=true}, {id="55"}, {id="56"}, {id="57"},
{id="58"}, {id="59"}, {id="60"}, {id="61",sham=true},
{id="62"}, {id="63",sham=true}, {id="64",sham=true},
{id="65",sham=true}, {id="66",sham=true}, {id="67"},
{id="68",sham=true}, {id="69"}, {id="70"}, {id="71"},
{id="72",sham=true}, {id="73"}, {id="74"}, {id="75"},
{id="76"}, {id="77"}, {id="78"}, {id="79"}, {id="80"},
{id="81",sham=true}, {id="82"}, {id="83"}, {id="84"},
{id="85"}, {id="86"}, {id="87",sham=true}, {id="88"},
{id="89"}, {id="90",sham=true}, {id="91",sham=true},
{id="92"}, {id="93",sham=true}, {id="94",sham=true},
{id="95",sham=true}, {id="96"}, {id="97",sham=true},
{id="98",sham=true}, {id="99",sham=true}, {id="100"},
{id="101",sham=true}, {id="102",sham=true},
{id="103",sham=true}, {id="104",sham=true},
{id="105",sham=true}, {id="106",sham=true},
{id="107",sham=true}, {id="115",sham=true},
{id="116",sham=true}, {id="117"}, {id="118"}, {id="119"},
{id="120"}, {id="121"}, {id="122"}, {id="123"}, {id="124"},
{id="125"}, {id="126"}, {id="127"}, {id="128",sham=true},
{id="129",sham=true}, {id="130",sham=true},
{id="131",sham=true}, {id="132",sham=true},
{id="133",sham=true}, {id="134",sham=true}, {id="136"},
{id="137",sham=true}, {id="138"}, {id="139",sham=true},
{id="140",sham=true}, {id="141",sham=true}, {id="142"},
{id="143",sham=true}, {id="145"},
{id="146",sham=true}, {id="147",sham=true}, {id="148"},
{id="149"}, {id="150"}, {id="151"}
}
},
["vanilla% shamless"] = {
description = "All vanilla maps that do not need shaman.",
sham = false,
maps = {
{id="1"}, {id="2"}, {id="6"}, {id="7"}, {id="8"},
{id="10"}, {id="11"}, {id="12"}, {id="13"}, {id="14"},
{id="19"}, {id="20"}, {id="22"}, {id="23"}, {id="24"},
{id="26"}, {id="27"}, {id="28"}, {id="30"}, {id="31"},
{id="33"}, {id="35"}, {id="37"}, {id="40"}, {id="41"},
{id="42"}, {id="43"}, {id="44"}, {id="45"}, {id="49"},
{id="53"}, {id="55"}, {id="56"}, {id="57"}, {id="58"},
{id="59"}, {id="60"}, {id="62"}, {id="67"}, {id="69"},
{id="70"}, {id="71"}, {id="73"}, {id="74"}, {id="75"},
{id="76"}, {id="77"}, {id="78"}, {id="79"}, {id="80"},
{id="82"}, {id="83"}, {id="84"}, {id="85"}, {id="86"},
{id="88"}, {id="89"}, {id="92"}, {id="96"}, {id="100"},
{id="114"}, {id="117"}, {id="118"}, {id="119"}, {id="120"},
{id="121"}, {id="122"}, {id="123"}, {id="124"}, {id="125"},
{id="126"}, {id="127"}, {id="136"}, {id="138"}, {id="142"},
{id="145"}, {id="148"}, {id="149"}, {id="150"}, {id="151"}
}
},
["bootcamp%"] = {
description = "Selection of bootcamp maps.",
sham = false,
maps = {}
},
["regular"] = {
description = "Selection of regular maps.",
sham = false,
maps = {}
}
}
player = nil
category = nil
mapidx = 1
totalTime = 0
numDeaths = 0
gameIsRunning = false
respawnTimer = 0
needsRespawn = false
wins = 0
skips = 0
defaultMap = "@0"
----------------------------------------------------------------------
-- SPEEDRUN COMMANDS
----------------------------------------------------------------------
function newGame(playerName, categoryName)
if catData[categoryName] == nil then
print('<b><font color="#FF0000">ERROR:</font></b> Could not find category with given name.')
end
ui.removeTextArea(3, nil)
ui.addTextArea(2, "00:00:00:000", nil, -110, 5, 100, 20, 0x324650, 0x000000, 1, true)
player = playerName
category = categoryName
mapidx = 1
-- this makes the estimate an upper bound
totalTime = 500
numDeaths = 0
skips = 0
wins = 0
gameIsRunning = true
respawn()
end
function gameOver(complete)
if skips > 0 then
complete = false
end
if complete then
completeMsg = ""
else
completeMsg = "(incomplete)"
end
gameIsRunning = false
local content = [[
<b>GAME OVER</b>
%s
Time: %s
Deaths: %d
Maps completed: %d
]]
content = string.format(content, completeMsg, getTime(), numDeaths, wins)
ui.removeTextArea(2, nil)
ui.addTextArea(3, content, nil, 350, 50, 150, 70, 0x324650, 0x000000, 1, true)
tfm.exec.newGame(defaultMap)
end
function respawn()
if respawnTimer == 0 and not needsRespawn then
resetMap()
else
needsRespawn = true
end
end
----------------------------------------------------------------------
-- PRACTICE COMMANDS
----------------------------------------------------------------------
----------------------------------------------------------------------
-- MAP COMMANDS
----------------------------------------------------------------------
function resetMap()
respawnTimer = 7
local map = catData[category].maps[mapidx]
tfm.exec.newGame(map.id)
-- Kill all other players
for playerName, playerData in pairs(tfm.get.room.playerList) do
if playerName ~= player then
tfm.exec.killPlayer(playerName)
end
end
-- Set the shaman if needed
local sham = catData[category].sham
if map.sham ~= nil then sham = map.sham end
if sham then tfm.exec.setShaman(player) end
-- Set the game time to be much longer than necessary
tfm.exec.setGameTime(1200)
end
function nextMap()
mapidx = mapidx + 1
if mapidx <= #catData[category].maps then
respawn()
else
gameOver(true)
end
end
----------------------------------------------------------------------
-- TIMER COMMANDS
----------------------------------------------------------------------
function getTime(tmp)
tmp = 0
local time = totalTime + tmp
local millis = math.fmod(time, 1000)
local seconds = math.fmod(math.floor(time / 1000), 60)
local minutes = math.fmod(math.floor(time / 60000), 60)
local hours = math.fmod(math.floor(time / 3600000), 60)
return string.format("%02d:%02d:%02d:%03d", hours, minutes, seconds, millis)
end
function updateTimer(tmp)
if not gameIsRunning then return end
totalTime = totalTime + 500
ui.updateTextArea(2, getTime(tmp), nil)
end
----------------------------------------------------------------------
-- EVENTS
----------------------------------------------------------------------
function eventChatCommand(playerName, message)
if gameIsRunning and playerName == player then
if message == "reset" then
numDeaths = numDeaths + 1
respawn()
return
elseif message == "stop" then
gameOver(false)
return
elseif message == "skip" then
skips = skips + 1
nextMap()
return
end
end
if string.sub(message, 0, 4) == "play" then
local categoryName = string.sub(message, 6)
newGame(playerName, categoryName)
elseif message == "categories" then
msg = '\n <b><font color="#FF0000">CATEGORIES</font></b>'
for categoryName, data in pairs(catData) do
msg = msg .. '\n <font color="#4444FF">' .. categoryName .. "</font> " .. data.description
end
print(msg)
elseif message == "help" then
msg = [[
commands:
<font color="#FF0000">!play</font> <font color="#00FF00">[category]</font> - Play a game category.
<font color="#FF0000">!categories</font> - List all categories
<font color="#FF0000">!help</font> - Show this dialog
in-game commands: (current player only)
<font color="#FF0000">!reset</font> - Resets the current map
<font color="#FF0000">!skip</font> - Skips the current map, but invalidates the run
<font color="#FF0000">!stop</font> - Stops the current run
]]
print(msg)
else
print('<b><font color="#FF0000">ERROR:</font></b> Invalid command.')
end
end
function eventPlayerWon(playerName, timeElapsed, timeElapsedSinceRespawn)
if not gameIsRunning then return end
if playerName == player then
wins = wins + 1
nextMap()
end
end
function eventPlayerDied(playerName)
if not gameIsRunning then return end
if playerName == player then
numDeaths = numDeaths + 1
respawn()
end
end
function eventLoop(currentTime, timeRemaining)
if not gameIsRunning then return end
updateTimer()
if respawnTimer > 0 then
respawnTimer = respawnTimer - 1
end
if respawnTimer == 0 and needsRespawn then
needsRespawn = false
resetMap()
end
end
----------------------------------------------------------------------
-- MODULE INITIALIZATION
----------------------------------------------------------------------
-- Basic game settings
tfm.exec.disableAutoNewGame(true)
tfm.exec.disableAutoScore(true)
tfm.exec.disableAutoTimeLeft(true)
tfm.exec.disableAutoShaman(true)
tfm.exec.disableAllShamanSkills(true)
tfm.exec.disableAfkDeath(true)
tfm.exec.setRoomMaxPlayers(1)
-- Start in the defult map
tfm.exec.newGame(defaultMap)
-- Print a welcome message
print([[
This module is for speed running Transformice maps.
To see the map categories available, enter <font color="#FF0000">!categories</font>
To run a particular category, enter <font color="#FF0000">!play</font> <font color="#00FF00">[category]</font>
To see a description of all commands, enter <font color="#FF0000">!help</font>
]])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment