sm64coopdx/mods/cheats.lua

385 lines
11 KiB
Lua
Raw Normal View History

-- name: Cheats
-- incompatible: cheats
-- description: Cheats\nA mod that adds a bunch of cheats to the mod menu, accessible through the pause menu.
-- localize functions to improve performance
local math_floor,smlua_text_utils_get_language,table_insert,approach_s32,set_mario_action,get_network_area_timer = math.floor,smlua_text_utils_get_language,table.insert,approach_s32,set_mario_action,get_network_area_timer
--- @class Cheat
--- @field public codename string
--- @field public names table
--- @field public hook LuaHookedEventType
--- @field public func function
--- @field public allowHazardSurfaces boolean
2024-06-29 17:14:15 +02:00
local CHEATS_VERSION = "v1.0"
--- @type Cheat[]
local sCheats = {}
--- @param m MarioState
--- Checks if `m` is active
local function active_player(m)
local np = gNetworkPlayers[m.playerIndex]
if m.playerIndex == 0 then
return true
end
if not np.connected then
return false
end
if np.currCourseNum ~= gNetworkPlayers[0].currCourseNum then
return false
end
if np.currActNum ~= gNetworkPlayers[0].currActNum then
return false
end
if np.currLevelNum ~= gNetworkPlayers[0].currLevelNum then
return false
end
if np.currAreaIndex ~= gNetworkPlayers[0].currAreaIndex then
return false
end
return true
end
--- @param num integer
--- Limits an integer in the s16 range
local function s16(num)
num = math_floor(num) & 0xFFFF
if num >= 32768 then return num - 65536 end
return num
end
local function lang_string(strings)
local table = strings[smlua_text_utils_get_language()]
if table == nil then return strings["English"] end
return table
end
--- @param codename string
2024-06-29 17:14:15 +02:00
--- @param names table<string, string>
--- @param hook LuaHookedEventType
--- @param func function
--- @param allowHazardSurfaces boolean
--- Registers a cheat
---
--- Supported hooks:
--- - `HOOK_MARIO_UPDATE`
--- - `HOOK_BEFORE_MARIO_UPDATE`
--- - `HOOK_BEFORE_PHYS_STEP`
local function register_cheat(codename, names, hook, func, allowHazardSurfaces)
table_insert(sCheats, {
codename = codename,
names = names,
hook = hook,
func = func,
allowHazardSurfaces = allowHazardSurfaces
})
for i = 0, MAX_PLAYERS - 1 do
gPlayerSyncTable[i][codename] = false
end
end
--- @param m MarioState
local function moon_jump_update(m)
if m.controller.buttonDown & L_TRIG ~= 0 then
m.faceAngle.y = m.intendedYaw - approach_s32(s16(m.intendedYaw - m.faceAngle.y), 0, 0x800, 0x800)
m.vel.y = 40
if m.action == ACT_FORWARD_GROUND_KB or
m.action == ACT_BACKWARD_GROUND_KB or
m.action == ACT_SOFT_FORWARD_GROUND_KB or
m.action == ACT_HARD_BACKWARD_GROUND_KB or
m.action == ACT_FORWARD_AIR_KB or
m.action == ACT_BACKWARD_AIR_KB or
m.action == ACT_HARD_FORWARD_AIR_KB or
m.action == ACT_HARD_BACKWARD_AIR_KB or
m.action == ACT_AIR_HIT_WALL then
set_mario_action(m, ACT_FREEFALL, 0)
end
end
end
--- @param m MarioState
local function god_mode_update(m)
m.health = 0x880
m.healCounter = 0
m.hurtCounter = 0
m.peakHeight = m.pos.y
end
--- @param m MarioState
local function infinite_lives_update(m)
m.numLives = 100
end
--- @param m MarioState
local function super_speed_update(m)
if m.action ~= ACT_BUBBLED and m.action ~= ACT_WATER_JUMP and m.action ~= ACT_HOLD_WATER_JUMP then
m.vel.x = m.vel.x * 4
m.vel.z = m.vel.z * 4
end
end
--- @param m MarioState
local function responsive_controls_update(m)
if m.action == ACT_WALKING or
m.action == ACT_HOLD_WALKING or
m.action == ACT_HOLD_HEAVY_WALKING or
m.action == ACT_FINISH_TURNING_AROUND or
m.action == ACT_CRAWLING then
m.faceAngle.y = m.intendedYaw
end
end
--- @param m MarioState
local function rapid_fire_update(m)
2024-06-29 17:14:15 +02:00
if (m.controller.buttonDown & A_BUTTON) ~= 0 and get_global_timer() % 2 == 0 then
m.controller.buttonPressed = m.controller.buttonPressed | A_BUTTON
end
end
local function blj_anywhere_update(m)
if m.action == ACT_LONG_JUMP and
m.controller.buttonDown & Z_TRIG ~= 0 and
m.forwardVel < -15 then
m.vel.y = -30
end
end
local function always_triple_jump_update(m, action)
if m.forwardVel < 20 and m.action == ACT_DOUBLE_JUMP_LAND and action == ACT_JUMP then
return ACT_TRIPLE_JUMP
end
end
register_cheat(
"moonJump",
{
["Czech"] = "Nekonečný Skok",
["Dutch"] = "Maan Sprong",
["English"] = "Moon Jump",
["French"] = "Saut Antigravité",
["German"] = "Mond-Sprung",
["Italian"] = "Salto della Luna",
["Polish"] = "Skok Ksiezycowy",
["Portuguese"] = "Pulo da Lua",
["Russian"] = "Супер прыжок",
["Spanish"] = "Salto Lunar"
},
HOOK_MARIO_UPDATE,
moon_jump_update,
true
)
register_cheat(
"godMode",
{
["Czech"] = "Nenech Se Zranit",
["Dutch"] = "God Modus",
["English"] = "God Mode",
["French"] = "Mode Invincible",
["German"] = "Gott Modus",
["Italian"] = "Modalità Dio",
["Polish"] = "Tryb Boga",
["Portuguese"] = "Modo Deus",
["Russian"] = "Режим бога",
["Spanish"] = "Modo Dios",
},
HOOK_MARIO_UPDATE,
god_mode_update,
false
)
register_cheat(
"infiniteLives",
{
["Czech"] = "Nekonečné Životy",
["Dutch"] = "Oneindige Levens",
["English"] = "Infinite Lives",
["French"] = "Vies Infinies",
["German"] = "Unbegrenzte Leben",
["Italian"] = "Vite Infinite",
["Polish"] = "Nieskonczone Zycia",
["Portuguese"] = "Vidas Infinitas",
["Russian"] = "Бесконечные жизни",
["Spanish"] = "Vidas Infinitas",
},
HOOK_MARIO_UPDATE,
infinite_lives_update,
true
)
register_cheat(
"superSpeed",
{
["Czech"] = "Super Rychlost",
["Dutch"] = "Super Snelheid",
["English"] = "Super Speed",
["French"] = "Super Vitesse",
["German"] = "Supergeschwindigkeit",
["Italian"] = "Super Velocità",
["Polish"] = "Super Szybkosc",
["Portuguese"] = "Super Velocidade",
["Russian"] = "Супер cкорость",
["Spanish"] = "Super Velocidad",
},
HOOK_BEFORE_PHYS_STEP,
super_speed_update,
true
)
register_cheat(
"responsiveControls",
{
["Czech"] = "Citlivé Ovládání",
["Dutch"] = "Snel Reagerende Controles",
["English"] = "Responsive Controls",
["French"] = "Contrôles Réactifs",
["German"] = "Reaktionsschnelle Steuerung",
["Italian"] = "Controlli Reattivi",
["Polish"] = "Responsywne Sterowanie",
["Portuguese"] = "Controle Responsivos",
["Russian"] = "Отзывчивое управление",
["Spanish"] = "Controles Responsivos",
},
HOOK_MARIO_UPDATE,
responsive_controls_update,
true
)
register_cheat(
"rapidFire",
{
["Czech"] = "Rychle Mačkat Tlačítko",
["Dutch"] = "Snel Vuur",
["English"] = "Rapid Fire",
["French"] = "Tir Rapide",
["German"] = "Schnellfeuer",
["Italian"] = "Fuoco Rapido",
["Polish"] = "Szybkostrzelnosc",
["Portuguese"] = "Fogo Rápido",
["Russian"] = "Быстрый огонь",
["Spanish"] = "Pulsación Rápida",
},
HOOK_BEFORE_MARIO_UPDATE,
rapid_fire_update,
true
)
register_cheat(
"bljAnywhere",
{
["Czech"] = "BLJ Všude",
["Dutch"] = "BLJ Overal",
["English"] = "BLJ Anywhere",
["French"] = "BLJ N'importe Où",
["German"] = "Überall Rückwertsweitspringen",
["Italian"] = "BLJ Ovunque",
["Polish"] = "BLJ Gdziekolwiek",
["Portuguese"] = "BLJ Em Qualquer Lugar",
["Russian"] = "BLJ в любом месте",
["Spanish"] = "BLJ Donde Sea",
},
HOOK_BEFORE_MARIO_UPDATE,
blj_anywhere_update,
true
)
register_cheat(
"alwaysTripleJump",
{
["Czech"] = "Vždy Trojitý Skok",
["Dutch"] = "Altijd Drievoudige Sprong",
["English"] = "Always Triple Jump",
["French"] = "Triple Sauts Infinis",
["German"] = "Immer Dreisprung",
["Italian"] = "Sempre Salto Triplo",
["Polish"] = "Potrojny Skok Zawsze",
["Portuguese"] = "Sempre Fazer Triple Jump",
["Russian"] = "Всегда тройной прыжок",
["Spanish"] = "Siempre Hacer Salto Triple",
},
HOOK_BEFORE_SET_MARIO_ACTION,
always_triple_jump_update,
true
)
--- @param hookType LuaHookedEventType
local function generate_mario_hook_function(hookType)
--- @param m MarioState
return function(m)
if not active_player(m) then return end
for _, cheat in ipairs(sCheats) do
if cheat.hook == hookType and gPlayerSyncTable[m.playerIndex][cheat.codename] then
cheat.func(m)
end
end
end
end
local function before_set_mario_action(m, action)
if not active_player(m) then return end
for _, cheat in ipairs(sCheats) do
if cheat.hook == HOOK_BEFORE_SET_MARIO_ACTION and gPlayerSyncTable[m.playerIndex][cheat.codename] then
return cheat.func(m, action)
end
end
end
--- @param m MarioState
local function allow_hazard_surface(m)
for _, cheat in ipairs(sCheats) do
if gPlayerSyncTable[m.playerIndex][cheat.codename] and not cheat.allowHazardSurfaces then return false end
end
return true
end
local function on_language_changed()
for i, cheat in ipairs(sCheats) do
update_mod_menu_element_name(i - 1, lang_string(cheat.names))
end
end
--- @param index integer
--- @param value boolean
local function update_cheat(index, value)
for i, cheat in ipairs(sCheats) do
if i - 1 == index then
gPlayerSyncTable[0][cheat.codename] = value
end
end
end
2024-06-29 17:14:15 +02:00
local sReadonlyMetatable = {
__index = function(table, key)
return rawget(table, key)
end,
__newindex = function()
error("attempt to update a read-only table", 2)
end
}
_G.cheatsApi = {
version = CHEATS_VERSION,
register_cheat = register_cheat
}
setmetatable(_G.cheatsApi, sReadonlyMetatable)
hook_event(HOOK_MARIO_UPDATE, generate_mario_hook_function(HOOK_MARIO_UPDATE))
hook_event(HOOK_BEFORE_MARIO_UPDATE, generate_mario_hook_function(HOOK_BEFORE_MARIO_UPDATE))
hook_event(HOOK_BEFORE_PHYS_STEP, generate_mario_hook_function(HOOK_BEFORE_PHYS_STEP))
hook_event(HOOK_BEFORE_SET_MARIO_ACTION, before_set_mario_action)
hook_event(HOOK_ALLOW_HAZARD_SURFACE, allow_hazard_surface)
hook_event(HOOK_ON_LANGUAGE_CHANGED, on_language_changed)
for _, cheat in ipairs(sCheats) do
hook_mod_menu_checkbox(lang_string(cheat.names), false, update_cheat)
end