sm64coopdx/mods/cheats.lua
Hatch 32ec0199d8
Fix Cheat Mod Menu (#219)
Each cheat did not work as labelled. The checkbox for the second cheat listed activated the first cheat listed, and the third checkbox did the second cheat, and so on.

The "- 1" in Line 353 did not need to be there.
2024-07-17 10:38:49 -04:00

383 lines
11 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- 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
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
--- @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)
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 == index then
gPlayerSyncTable[0][cheat.codename] = value
end
end
end
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