Add shell rush gamemode

This commit is contained in:
MysterD 2022-03-30 20:15:17 -07:00
parent 34d28ffb3c
commit 1773153971
16 changed files with 2005 additions and 0 deletions

278
mods/shell-rush/actions.lua Normal file
View File

@ -0,0 +1,278 @@
gExtraMarioState = { }
for i = 0, (MAX_PLAYERS - 1) do
gExtraMarioState[i] = { }
gExtraMarioState[i].lastY = 0
end
function race_get_slope_physics(m)
local friction = 0.96
local force = 3
if mario_floor_is_slope(m) ~= 0 then
local slopeClass = 0
if m.action ~= ACT_SOFT_BACKWARD_GROUND_KB and m.action ~= ACT_SOFT_FORWARD_GROUND_KB then
slopeClass = mario_get_floor_class(m)
end
if slopeClass == SURFACE_CLASS_VERY_SLIPPERY then
friction = 0.98
force = 3.3
elseif slopeClass == SURFACE_CLASS_SLIPPERY then
friction = 0.97
force = 3.2
end
end
return {
force = force,
friction = friction,
}
end
function race_apply_slope_accel(m)
local physics = race_get_slope_physics(m)
local floor = m.floor
local floorNormal = m.floor.normal
local mTheta = m.faceAngle.y
local mSpeed = m.forwardVel * 1.5 * gGlobalSyncTable.speed
if mSpeed > 135 * gGlobalSyncTable.speed then mSpeed = 135 * gGlobalSyncTable.speed end
local mDir = {
x = sins(mTheta),
y = 0,
z = coss(mTheta)
}
m.slideYaw = m.faceAngle.y
m.slideVelX = 0
m.slideVelZ = 0
-- apply direction
local angle = vec3f_angle_between(m.vel, mDir)
local parallel = vec3f_project(m.vel, mDir)
local perpendicular = { x = m.vel.x - parallel.x, y = m.vel.y - parallel.y, z = m.vel.z - parallel.z }
local parallelMag = vec3f_length(parallel)
local perpendicularMag = vec3f_length(perpendicular)
local originalPerpendicularMag = perpendicularMag
if angle >= math.pi / 2 then
parallelMag = -1
elseif parallelMag < mSpeed then
local lastMag = parallelMag
parallelMag = parallelMag * 0.85 + mSpeed * 0.15
perpendicularMag = perpendicularMag - (parallelMag - lastMag) * 0.12
if perpendicularMag < 0 then perpendicularMag = 0 end
end
vec3f_normalize(parallel)
vec3f_normalize(perpendicular)
vec3f_non_nan(parallel)
vec3f_non_nan(perpendicular)
local combined = {
x = parallel.x * parallelMag + perpendicular.x * perpendicularMag,
y = parallel.y * parallelMag + perpendicular.y * perpendicularMag,
z = parallel.z * parallelMag + perpendicular.z * perpendicularMag,
}
m.vel.x = combined.x
m.vel.z = combined.z
-- apply friction
m.vel.x = m.vel.x * physics.friction
m.vel.z = m.vel.z * physics.friction
m.vel.y = 0.0
-- apply slope
m.vel.x = m.vel.x + physics.force * floorNormal.x
m.vel.z = m.vel.z + physics.force * floorNormal.z
-- apply vanilla forces
local velBeforeVanilla = { x = m.vel.x, y = m.vel.y, z = m.vel.z }
mario_update_moving_sand(m)
mario_update_windy_ground(m)
m.vel.x = m.vel.x * 0.2 + velBeforeVanilla.x * 0.8
m.vel.y = m.vel.y * 0.2 + velBeforeVanilla.y * 0.8
m.vel.z = m.vel.z * 0.2 + velBeforeVanilla.z * 0.8
end
function update_race_shell_speed(m)
local maxTargetSpeed = 0
local targetSpeed = 0
local startForwardVel = m.forwardVel
-- brake
if (m.controller.buttonDown & B_BUTTON) ~= 0 then
m.forwardVel = m.forwardVel * 0.9
end
-- set water level
if m.floorHeight < m.waterLevel then
m.floorHeight = m.waterLevel
m.floor = get_water_surface_pseudo_floor()
m.floor.originOffset = m.waterLevel -- Negative origin offset
end
-- set max target speed
if m.floor ~= nil and m.floor.type == SURFACE_SLOW then
maxTargetSpeed = 48.0
else
maxTargetSpeed = 64.0
end
-- set target speed
targetSpeed = m.intendedMag * 2.0
if targetSpeed > maxTargetSpeed then
targetSpeed = maxTargetSpeed
end
if targetSpeed < 18.0 then
targetSpeed = 18.0
end
-- set speed
if m.forwardVel <= 0.0 then
m.forwardVel = 1.1
elseif m.forwardVel <= targetSpeed + 1.1 then
m.forwardVel = m.forwardVel + 1.1
elseif m.forwardVel > targetSpeed - 1.5 then
m.forwardVel = m.forwardVel - 1.5
elseif m.floor ~= nil and m.floor.normal.y >= 0.95 then
m.forwardVel = m.forwardVel - 1.1
end
if m.forwardVel > 64.0 then
if m.forwardVel > startForwardVel - 3.0 then
m.forwardVel = startForwardVel - 3.0
end
end
local turnSpeed = 0x800
if (m.controller.buttonDown & B_BUTTON) ~= 0 then turnSpeed = 0x650 end
m.faceAngle.y = m.intendedYaw - approach_s32(convert_s16(m.intendedYaw - m.faceAngle.y), 0, turnSpeed, turnSpeed)
race_apply_slope_accel(m)
end
function act_race_shell_ground(m)
if m.actionTimer < 5 then m.actionTimer = m.actionTimer + 1 end
local startYaw = m.faceAngle.y
-- enforce min velocities
if m.forwardVel == 0 then m.forwardVel = 1 end
if vec3f_length(m.vel) == 0 then m.vel.x = 1 end
-- jump
if (m.input & INPUT_A_PRESSED) ~= 0 then
m.vel.x = m.vel.x * 0.9
m.vel.z = m.vel.z * 0.9
return set_mario_action(m, ACT_RIDING_SHELL_JUMP, 0)
end
-- update physics
update_race_shell_speed(m)
-- set animation
if m.actionArg == 0 then
set_mario_animation(m, MARIO_ANIM_START_RIDING_SHELL)
else
set_mario_animation(m, MARIO_ANIM_RIDING_SHELL)
end
local gs = perform_ground_step(m)
if gs == GROUND_STEP_LEFT_GROUND then
m.vel.y = (m.pos.y - gExtraMarioState[m.playerIndex].lastY)
return set_mario_action(m, ACT_RIDING_SHELL_FALL, 0)
elseif gs == GROUND_STEP_HIT_WALL then
-- check if the wall is in the facing direction
local castDir = {
x = sins(m.faceAngle.y) * 200,
y = 0,
z = coss(m.faceAngle.y) * 200
}
local info = collision_find_surface_on_ray(
m.pos.x, m.pos.y + 100, m.pos.z,
castDir.x, castDir.y, castDir.z)
if info.surface ~= nil then
mario_stop_riding_object(m)
play_sound(SOUND_ACTION_BONK, m.marioObj.header.gfx.cameraToObject)
m.particleFlags = m.particleFlags | PARTICLE_VERTICAL_STAR
m.forwardVel = 0
set_mario_action(m, ACT_BACKWARD_GROUND_KB, 0)
end
end
tilt_body_ground_shell(m, startYaw)
if m.floor.type == SURFACE_BURNING then
play_sound(SOUND_MOVING_RIDING_SHELL_LAVA, m.marioObj.header.gfx.cameraToObject)
else
play_sound(SOUND_MOVING_TERRAIN_RIDING_SHELL, m.marioObj.header.gfx.cameraToObject)
end
adjust_sound_for_speed(m)
reset_rumble_timers(m)
gExtraMarioState[m.playerIndex].lastY = m.pos.y
return 0
end
function act_race_shell_air(m)
if m.actionTimer < 5 then m.actionTimer = m.actionTimer + 1 end
play_mario_sound(m, SOUND_ACTION_TERRAIN_JUMP, 0)
set_mario_animation(m, MARIO_ANIM_JUMP_RIDING_SHELL)
if m.vel.y > 65 then m.vel.y = 65 end
local mSpeed = m.forwardVel / 128.0 * gGlobalSyncTable.speed
if mSpeed > 100 * gGlobalSyncTable.speed then mSpeed = 100 * gGlobalSyncTable.speed end
local mDir = {
x = sins(m.intendedYaw),
y = 0,
z = coss(m.intendedYaw)
}
-- apply direction
local parallel = vec3f_project(mDir, m.vel)
local perpendicular = { x = mDir.x - parallel.x, y = mDir.y - parallel.y, z = mDir.z - parallel.z }
local parallelMag = vec3f_length(parallel)
if parallelMag < mSpeed then parallelMag = mSpeed / parallelMag end
local combined = {
x = parallel.x * parallelMag + perpendicular.x * 0.95,
y = parallel.y * parallelMag + perpendicular.y * 0.95,
z = parallel.z * parallelMag + perpendicular.z * 0.95,
}
m.vel.x = m.vel.x + mSpeed * mDir.x
m.vel.z = m.vel.z + mSpeed * mDir.z
-- apply rotation
m.faceAngle.y = m.intendedYaw - approach_s32(convert_s16(m.intendedYaw - m.faceAngle.y), 0, 0x300, 0x300)
local step = perform_air_step(m, 0)
if step == AIR_STEP_LANDED then
set_mario_action(m, ACT_RIDING_SHELL_GROUND, 1)
elseif step == AIR_STEP_HIT_WALL then
mario_set_forward_vel(m, 0.0)
elseif step == AIR_STEP_HIT_LAVA_WALL then
lava_boost_on_wall(m)
end
m.marioObj.header.gfx.pos.y = m.marioObj.header.gfx.pos.y + 42.0
gExtraMarioState[m.playerIndex].lastY = m.pos.y
return 0
end
hook_mario_action(ACT_RIDING_SHELL_GROUND, act_race_shell_ground)
hook_mario_action(ACT_RIDING_SHELL_JUMP, act_race_shell_air)
hook_mario_action(ACT_RIDING_SHELL_FALL, act_race_shell_air)

Binary file not shown.

Binary file not shown.

Binary file not shown.

96
mods/shell-rush/hud.lua Normal file
View File

@ -0,0 +1,96 @@
function on_hud_render()
local s = gPlayerSyncTable[0]
hud_hide()
djui_hud_set_resolution(RESOLUTION_N64)
djui_hud_set_font(FONT_NORMAL)
local scale = 0.25
local width = 0
local height = 4 * scale
for i in pairs(gRankings) do
local np = gNetworkPlayers[gRankings[i].playerIndex]
local w = (djui_hud_measure_text(tostring(i) .. '. ' .. np.name) + 8) * scale
if w > width then width = w end
height = height + 28 * scale
end
djui_hud_set_color(0, 0, 0, 128)
djui_hud_render_rect(0, 0, width, height)
local x = 4 * scale
local y = 0
local rank = 0
-- draw rankings
for i in pairs(gRankings) do
local np = gNetworkPlayers[gRankings[i].playerIndex]
djui_hud_set_color(0, 0, 0, 255)
djui_hud_print_text(tostring(i) .. '. ' .. np.name, x + 2 * scale, y + 2 * scale, scale)
if gRankings[i].playerIndex == 0 then
rank = i
djui_hud_set_color(255, 240, 150, 255)
else
djui_hud_set_color(220, 220, 220, 255)
end
djui_hud_print_text(tostring(i) .. '. ' .. np.name, x + 0 * scale, y + 0 * scale, scale)
y = y + 28 * scale
end
if gGlobalSyncTable.gameState == GAME_STATE_RACE_COUNTDOWN then
-- draw countdown
scale = 0.6
djui_hud_set_font(FONT_MENU)
djui_hud_set_color(64, 128, 255, 255)
local countdown = math.floor((gGlobalSyncTable.raceStartTime - get_network_area_timer()) / 30)
countdown = clamp(countdown + 1, 1, 5)
local countdownText = tostring(countdown)
x = (djui_hud_get_screen_width() - djui_hud_measure_text(countdownText) * scale) / 2
djui_hud_print_text(countdownText, x, 2 * scale, scale)
else
-- draw lap counter
scale = 0.3
djui_hud_set_font(FONT_MENU)
djui_hud_set_color(64, 128, 255, 255)
local lapText = 'LAP ' .. tostring(s.lap) .. ' /' .. tostring(gGlobalSyncTable.maxLaps)
if s.finish ~= nil and s.finish > 0 then lapText = 'FINISHED' end
x = (djui_hud_get_screen_width() - djui_hud_measure_text(lapText) * scale) / 2
djui_hud_print_text(lapText, x, 2 * scale, scale)
-- draw player rank
if rank > 0 then
scale = 0.6
djui_hud_set_color(255, clamp(255 - 255 * (rank / 8), 0, 255), 0, 255)
local rankText = tostring(rank) .. 'th'
if rank == 1 then rankText = '1st' end
if rank == 2 then rankText = '2nd' end
if rank == 3 then rankText = '3rd' end
x = (djui_hud_get_screen_width() - djui_hud_measure_text(rankText) * scale) / 2
y = (djui_hud_get_screen_height() - 80 * scale)
djui_hud_print_text(rankText, x, y, scale)
end
end
if gGlobalSyncTable.raceQuitTime > 0 then
-- draw ending countdown
scale = 0.6
djui_hud_set_font(FONT_MENU)
djui_hud_set_color(64, 128, 255, 255)
local countdown = math.floor((gGlobalSyncTable.raceQuitTime - get_network_area_timer()) / 30)
countdown = clamp(countdown + 1, 1, 20)
local countdownText = tostring(countdown)
x = (djui_hud_get_screen_width() - djui_hud_measure_text(countdownText) * scale) / 2
y = 40 * scale
djui_hud_print_text(countdownText, x, y, scale)
end
end
hook_event(HOOK_ON_HUD_RENDER, on_hud_render)

View File

@ -0,0 +1,74 @@
local itemBoxTimeout = 30 * 4 -- 4 seocnds
define_custom_obj_fields({
oItemBoxTouched = 'u32',
})
function bhv_item_box_init(obj)
obj.oFlags = OBJ_FLAG_UPDATE_GFX_POS_AND_ANGLE
obj.oOpacity = 100
obj_scale(obj, 1.0)
obj.oPosY = obj.oPosY + 100
local floor = cur_obj_update_floor_height_and_get_floor()
if floor ~= nil then
obj.oPosY = obj.oFloorHeight + 130
end
network_init_object(obj, false, {
'oItemBoxTouched',
'oTimer'
})
end
function bhv_item_box_collect(obj)
spawn_sparkles(obj)
spawn_mist(obj)
obj.oItemBoxTouched = 1
obj.oTimer = 0
network_send_object(obj, true)
select_powerup()
cur_obj_play_sound_2(SOUND_GENERAL_COLLECT_1UP)
end
function bhv_item_box_loop(obj)
if obj.oItemBoxTouched == 1 then
if obj.oTimer >= itemBoxTimeout then
obj.oItemBoxTouched = 0
if get_network_player_smallest_global() == gNetworkPlayers[0] then
network_send_object(obj, true)
end
elseif obj.oTimer < 5 then
obj_scale(obj, 1 - (obj.oTimer / 5))
elseif obj.oTimer >= itemBoxTimeout - 10 then
obj_scale(obj, (obj.oTimer - (itemBoxTimeout - 10)) / 10)
cur_obj_unhide()
else
cur_obj_hide()
end
return
else
obj_scale(obj, 1.0)
cur_obj_unhide()
end
obj.oFaceAngleYaw = obj.oFaceAngleYaw + 0x250
obj.oFaceAngleRoll = 0
obj.oFaceAnglePitch = 0
local m = nearest_mario_state_to_object(obj)
if m == gMarioStates[0] then
local s = gPlayerSyncTable[0]
if s.powerup[0] == POWERUP_NONE and s.powerup[1] == POWERUP_NONE and s.powerup[2] == POWERUP_NONE then
local player = m.marioObj
local yDist = math.abs(obj.oPosY - player.oPosY)
local xzDist = math.sqrt(math.pow(obj.oPosX - player.oPosX, 2) + math.pow(obj.oPosZ - player.oPosZ, 2))
if xzDist < 120 and yDist < 200 then
bhv_item_box_collect(obj)
end
end
end
end
id_bhvItemBox = hook_behavior(nil, OBJ_LIST_LEVEL, true, bhv_item_box_init, bhv_item_box_loop)
E_MODEL_ITEM_BOX = smlua_model_util_get_id("item_box_geo")

View File

@ -0,0 +1,147 @@
gLevelDataTable = {
[-1] = {
waypoints = { },
powerups = { },
spawn = { },
erase = { },
platforms = { },
},
[LEVEL_BOB] = {
waypoints = {
{ x = -1953, y = 0, z = 1418 },
{ x = -2224, y = 73, z = 4738 },
{ x = 691, y = 744, z = 5584 },
{ x = 2607, y = 832, z = 6589 },
{ x = 6230, y = 981, z = 5311 },
{ x = 6818, y = 891, z = 2590 },
{ x = 4900, y = 1323, z = 989 },
{ x = 833, y = 768, z = 3215 },
{ x = -1641, y = 768, z = 1748 },
{ x = -3490, y = 1024, z = -275 },
{ x = -4786, y = 1293, z = -3216 },
{ x = -2399, y = 1015, z = -4957 },
{ x = 50, y = 1053, z = -2639 },
{ x = 2967, y = 1611, z = -1526 },
{ x = 5667, y = 1888, z = -3151 },
{ x = 5601, y = 2055, z = -6265 },
{ x = 2961, y = 2466, z = -7244 },
{ x = -341, y = 2603, z = -5406 },
{ x = 615, y = 2843, z = -2512 },
},
powerups = {
{ pos = { x = 4223, y = 768, z = 6768 }, obj = nil },
{ pos = { x = 4267, y = 768, z = 6372 }, obj = nil },
{ pos = { x = 4097, y = 795, z = 5927 }, obj = nil },
{ pos = { x = -4197, y = 1022, z = -1507 }, obj = nil },
{ pos = { x = -3858, y = 1008, z = -1710 }, obj = nil },
{ pos = { x = -4483, y = 1088, z = -1298 }, obj = nil },
{ pos = { x = 5493, y = 1959, z = -4592 }, obj = nil },
{ pos = { x = 5883, y = 1963, z = -4603 }, obj = nil },
{ pos = { x = 6259, y = 2013, z = -4748 }, obj = nil },
},
spawn = {
{
a = { x = -993, y = 0, z = -869 },
b = { x = -1264, y = 0, z = -2489 },
},
{
a = { x = -1658, y = 0, z = -864 },
b = { x = -1900, y = 0, z = -2487 },
},
},
erase = { },
platforms = {
{ pos = { x = 1100, y = 3000, z = -2800 }, rot = { x = 0x4000, y = 0x3604, z = 0 }, scale = { x = 2, y = 2, z = 2 } },
},
},
[LEVEL_SL] = {
waypoints = {
{ x = -6715, y = 1979, z = -621 },
{ x = -2062, y = 1204, z = -4538 },
{ x = 3935, y = 790, z = -3129 },
{ x = 5457, y = 1024, z = 5326 },
{ x = 3614, y = 1024, z = 5615 },
{ x = 2617, y = 1426, z = -1412 },
{ x = -1056, y = 1536, z = -2493 },
{ x = -3857, y = 1024, z = 1497 },
{ x = -4666, y = 1382, z = 4190 },
},
powerups = {
{ pos = { x = -6138, y = 2010, z = -977 }, obj = nil },
{ pos = { x = -6576, y = 2029, z = -1133 }, obj = nil },
{ pos = { x = -7000, y = 2043, z = -1239 }, obj = nil },
{ pos = { x = 232, y = 1352, z = -4544 }, obj = nil },
{ pos = { x = 3793, y = 1024, z = 3271 }, obj = nil },
{ pos = { x = 3232, y = 1024, z = 3317 }, obj = nil },
{ pos = { x = 2723, y = 1024, z = 3359 }, obj = nil },
},
spawn = {
{
a = { x = -6947, y = 1874, z = 291 },
b = { x = -6961, y = 1683, z = 3040 },
},
{
a = { x = -6592, y = 1903, z = 291 },
b = { x = -6488, y = 1640, z = 3040 },
},
},
erase = {
[id_bhvMrBlizzard] = true,
[id_bhvBigChillBully] = true,
[id_bhvMoneybag] = true,
[id_bhvMoneybagHidden] = true,
},
platforms = {
{ pos = { x = 360, y = 2150, z = 1392 }, rot = { x = 0x4000, y = 0x49b2, z = 0 }, scale = { x = 1, y = 1, z = 1 } },
},
},
[LEVEL_CASTLE_GROUNDS] = {
waypoints = {
{ x = -3122, y = 260, z = 4191 },
{ x = -3616, y = 415, z = 365 },
{ x = -5348, y = 492, z = -3201 },
{ x = -6273, y = 497, z = -2918 },
{ x = -6288, y = 336, z = -605 },
{ x = -3708, y = 412, z = 165 },
{ x = -331, y = 806, z = 511 },
{ x = 5171, y = 385, z = -1250 },
{ x = 4673, y = 544, z = -4888 },
{ x = 3930, y = -511, z = -2185 },
{ x = -265, y = -511, z = -1126 },
{ x = -3904, y = -511, z = -1674 },
{ x = -308, y = -511, z = -1189 },
{ x = 3891, y = -511, z = -1034 },
{ x = 4336, y = -800, z = 2988 },
{ x = 297, y = 632, z = 2089 },
},
powerups = {
{ pos = { x = -3801, y = 399, z = 709 }, obj = nil },
{ pos = { x = -3604, y = 415, z = 363 }, obj = nil },
{ pos = { x = -3378, y = 431, z = -4 }, obj = nil },
{ pos = { x = -3302, y = 431, z = 599 }, obj = nil },
{ pos = { x = -3949, y = 396, z = 120 }, obj = nil },
{ pos = { x = -292, y = -511, z = -1156 }, obj = nil },
{ pos = { x = -292, y = -511, z = -1571 }, obj = nil },
{ pos = { x = -292, y = -511, z = -741 }, obj = nil },
},
spawn = {
{
a = { x = -2365, y = 260, z = 4673 },
b = { x = -940, y = 260, z = 5294 },
},
{
a = { x = -2134, y = 260, z = 4143 },
b = { x = -348, y = 260, z = 4922 },
},
},
erase = {
[id_bhvDoorWarp] = true,
},
platforms = {
{ pos = { x = -3369, y = -540, z = -2025 }, rot = { x = 0, y = 0, z = 0 }, scale = { x = 1, y = 1, z = 1 } },
},
},
}

155
mods/shell-rush/level.lua Normal file
View File

@ -0,0 +1,155 @@
gRaceShells = {}
gLevelData = gLevelDataTable[-1]
function erase_unwanted_entities(objList)
local obj = obj_get_first(objList)
while obj ~= nil do
local behaviorId = get_id_from_behavior(obj.behavior)
if gLevelData.erase[behaviorId] ~= nil then
obj.activeFlags = ACTIVE_FLAG_DEACTIVATED
end
-- iterate
obj = obj_get_next(obj)
end
end
function on_level_init()
-- set level data
local level = gNetworkPlayers[0].currLevelNum
if gLevelDataTable[level] ~= nil then
gLevelData = gLevelDataTable[level]
else
gLevelData = gLevelDataTable[-1]
end
-- spawn all of the racing shells
for i = 0, (MAX_PLAYERS - 1) do
gRaceShells[i] = spawn_non_sync_object(
id_bhvRaceShell,
E_MODEL_KOOPA_SHELL,
0, 0, 0,
function (obj) obj.heldByPlayerIndex = i end
)
end
-- spawn all of the waypoints
for i in pairs(gLevelData.waypoints) do
local waypoint = get_waypoint(i)
spawn_non_sync_object(
id_bhvRaceRing,
E_MODEL_WATER_RING,
waypoint.x, waypoint.y, waypoint.z,
function (obj) obj.oWaypointIndex = i end
)
end
-- spawn level-specific platforms
for i in pairs(gLevelData.platforms) do
local p = gLevelData.platforms[i]
spawn_non_sync_object(
id_bhvStaticCheckeredPlatform,
E_MODEL_CHECKERBOARD_PLATFORM,
p.pos.x, p.pos.y, p.pos.z,
function (obj)
obj.oOpacity = 255
obj.oFaceAnglePitch = p.rot.x
obj.oFaceAngleYaw = p.rot.y
obj.oFaceAngleRoll = p.rot.z
obj_scale_xyz(obj, p.scale.x, p.scale.y, p.scale.z)
end)
end
-- reset the local player's data
local s = gPlayerSyncTable[0]
s.waypoint = 1
s.lap = 0
s.finish = 0
for i = 0, 2 do
s.powerup[i] = POWERUP_NONE
end
-- reset the custom level objects
for i in pairs(gLevelData.powerups) do
gLevelData.powerups[i].obj = nil
end
for i = 0, (MAX_PLAYERS - 1) do
for j = 0, 2 do
gPowerups[i][j] = nil
end
end
-- erase specified level entities
erase_unwanted_entities(OBJ_LIST_GENACTOR)
erase_unwanted_entities(OBJ_LIST_LEVEL)
erase_unwanted_entities(OBJ_LIST_SURFACE)
-- reset rankings
race_clear_rankings()
end
function spawn_custom_level_objects()
-- only handle powerups if we're the server
if not network_is_server() then
return
end
-- look for existing powerups
local obj = obj_get_first(OBJ_LIST_LEVEL)
while obj ~= nil do
local behaviorId = get_id_from_behavior(obj.behavior)
if behaviorId == id_bhvItemBox then
-- find closest position to put it in
local objPos = { x = obj.oPosX, y = obj.oPosY, z = obj.oPosZ }
for i in pairs(gLevelData.powerups) do
local powPos = gLevelData.powerups[i].pos
local tempPos = { x = powPos.x, y = objPos.y, z = powPos.z }
local dist = vec3f_dist(objPos, tempPos)
if dist < 5 then
gLevelData.powerups[i].obj = obj
end
end
end
-- iterate
obj = obj_get_next(obj)
end
-- spawn missing powerups
for i in pairs(gLevelData.powerups) do
if gLevelData.powerups[i].obj == nil then
local pos = gLevelData.powerups[i].pos
gLevelData.powerups[i].obj = spawn_sync_object(
id_bhvItemBox,
E_MODEL_ITEM_BOX,
pos.x, pos.y, pos.z,
function (obj)
--obj.oMoveAngleYaw = m.faceAngle.y
end
)
end
end
end
function on_object_unload(obj)
-- react to powerups getting unloaded
for i = 0, (MAX_PLAYERS - 1) do
for j = 0, 2 do
if obj == gPowerups[i][j] then
gPowerups[i][j] = nil
end
end
end
-- react to level objects getting unloaded
for i in pairs(gLevelData.powerups) do
if gLevelData.powerups[i].obj == obj then
gLevelData.powerups[i].obj = nil
end
end
end
hook_event(HOOK_ON_LEVEL_INIT, on_level_init)
hook_event(HOOK_ON_OBJECT_UNLOAD, on_object_unload)

141
mods/shell-rush/main.lua Normal file
View File

@ -0,0 +1,141 @@
-- name: Shell Rush
-- description: Race around SM64 levels on shells.\n\nCollect powerups such as red shells, green shells, bananas, and mushrooms.\n\nOnly use a save that has lowered the water in the moat.
-- incompatible: gamemode
DEBUG = false
UNST22 = true -- gotta work around unst 22 bugs :(
gPowerups = {}
gGlobalSyncTable.speed = 0.8
for i = 0, (MAX_PLAYERS - 1) do
gPowerups[i] = {
[0] = nil,
[1] = nil,
[2] = nil,
}
local s = gPlayerSyncTable[i]
s.waypoint = 1
s.lap = 0
s.powerup = {}
s.powerup[0] = POWERUP_NONE
s.powerup[1] = POWERUP_NONE
s.powerup[2] = POWERUP_NONE
s.finish = 0
s.random = 0
end
-------------------------------------------------------------------------------
function mario_update_local(m)
local s = gPlayerSyncTable[m.playerIndex]
-- crouch to use shell
local pressZ = (m.controller.buttonPressed & Z_TRIG) ~= 0
local blockShell = (m.action & (ACT_FLAG_INVULNERABLE | ACT_GROUP_CUTSCENE | ACT_FLAG_INTANGIBLE)) ~= 0
local allowShell = (m.action & (ACT_FLAG_STATIONARY | ACT_FLAG_MOVING | ACT_FLAG_SWIMMING)) ~= 0
if pressZ and not is_riding(m) and not blockShell and allowShell then
set_mario_action(m, ACT_RIDING_SHELL_GROUND, 0)
m.actionTimer = 0
-- fix vanilla camera
if m.area.camera.mode == CAMERA_MODE_WATER_SURFACE then
set_camera_mode(m.area.camera, CAMERA_MODE_FREE_ROAM, 1)
end
end
-- use powerups
if (m.controller.buttonPressed & Z_TRIG) ~= 0 and ((is_riding(m) and m.actionTimer > 1) or DEBUG) then
for i = 0, 2 do
if s.powerup[i] ~= POWERUP_NONE then
use_powerup(m, s.powerup[i])
s.powerup[i] = POWERUP_NONE
break
end
end
end
-- debug
if DEBUG then
if (m.controller.buttonPressed & D_JPAD) ~= 0 then
warp_to_level(LEVEL_CASTLE_GROUNDS, 1, 16)
print(m.pos.x, m.pos.y, m.pos.z, ' --- ', m.faceAngle.y)
for i = 0, 2 do
gPlayerSyncTable[0].powerup[i] = POWERUP_BANANA
end
end
if (m.controller.buttonPressed & L_JPAD) ~= 0 then
for i = 0, 2 do
gPlayerSyncTable[0].powerup[i] = POWERUP_GREEN_SHELL
end
end
if (m.controller.buttonPressed & R_JPAD) ~= 0 then
for i = 0, 2 do
gPlayerSyncTable[0].powerup[i] = POWERUP_RED_SHELL
end
end
if (m.controller.buttonPressed & U_JPAD) ~= 0 then
for i = 0, 2 do
gPlayerSyncTable[0].powerup[i] = POWERUP_MUSHROOM
end
end
end
end
function on_speed_command(msg)
if not network_is_server() then
djui_chat_message_create('Only the server can change this setting!')
return true
end
if tonumber(msg) > 0 then
gGlobalSyncTable.speed = tonumber(msg)
return true
end
return false
end
function mario_update(m)
local s = gPlayerSyncTable[m.playerIndex]
if not active_player(m) then
return
end
if m.playerIndex == 0 then
mario_update_local(m)
end
-- max health
m.health = 0x880
-- spawn powerups
for i = 0, 2 do
if gPowerups[m.playerIndex][i] == nil and s.powerup[i] ~= POWERUP_NONE then
if s.powerup[i] ~= POWERUP_NONE then
gPowerups[m.playerIndex][i] = spawn_powerup(m, s.powerup[i], i)
end
end
end
end
function allow_pvp_attack(m1, m2)
return false
end
function on_pause_exit(exitToCastle)
return false
end
function on_update()
spawn_custom_level_objects()
race_update()
end
hook_event(HOOK_UPDATE, on_update)
hook_event(HOOK_MARIO_UPDATE, mario_update)
hook_event(HOOK_ALLOW_PVP_ATTACK, allow_pvp_attack)
hook_event(HOOK_ON_PAUSE_EXIT, on_pause_exit)
hook_chat_command('speed', "[decimal number, default: 0.8]", on_speed_command)

273
mods/shell-rush/powerup.lua Normal file
View File

@ -0,0 +1,273 @@
POWERUP_NONE = 0
POWERUP_MUSHROOM = 1
POWERUP_GREEN_SHELL = 2
POWERUP_RED_SHELL = 3
POWERUP_BANANA = 4
POWERUP_MAX = 5
define_custom_obj_fields({
oPowerupType = 'u32',
oPowerupIndex = 'u32',
})
function bhv_powerup_init(obj)
obj.oFlags = OBJ_FLAG_UPDATE_GFX_POS_AND_ANGLE
obj.oOpacity = 255
local m = gMarioStates[obj.heldByPlayerIndex]
local mTheta = m.faceAngle.y
local mMag = 0
if obj.oPowerupType == POWERUP_BANANA then
mMag = 100 * (obj.oPowerupIndex + 1)
end
obj.oPosX = m.pos.x - sins(mTheta) * mMag
obj.oPosY = m.pos.y
obj.oPosZ = m.pos.z - coss(mTheta) * mMag
end
function bhv_powerup_stale(obj)
if obj.oPowerupType == POWERUP_MUSHROOM then
obj.oAction = 1
obj.oTimer = 0
else
obj.activeFlags = ACTIVE_FLAG_DEACTIVATED
end
end
function bhv_powerup_spin(obj)
local m = gMarioStates[obj.heldByPlayerIndex]
local theta = get_network_area_timer() / 8.0
theta = theta + (math.pi * 2) * obj.oPowerupIndex / 3.0
local mag = 120
if obj.oAction == 1 then
local scalar = (1 - (obj.oTimer / 5))
scalar = scalar * scalar
mag = mag * scalar
end
local vec = {
x = m.pos.x + math.sin(theta) * mag,
y = m.pos.y + mag,
z = m.pos.z + math.cos(theta) * mag
}
vec.y = find_floor_height(vec.x, vec.y, vec.z) + 50
if vec.y < m.pos.y + 50 then vec.y = m.pos.y + 50 end
return vec
end
function bhv_powerup_trail(obj)
local prevObj = gMarioStates[obj.heldByPlayerIndex].marioObj
local s = gPlayerSyncTable[obj.heldByPlayerIndex]
for i = 0, 2 do
if i >= obj.oPowerupIndex then
break
end
if s.powerup[i] == POWERUP_BANANA then
prevObj = gPowerups[obj.heldByPlayerIndex][i]
end
end
local theta = math.atan2(prevObj.oPosX - obj.oPosX, prevObj.oPosZ - obj.oPosZ)
if theta ~= theta then theta = 0 end
local mag = 150
local newPos = {
x = prevObj.oPosX - math.sin(theta) * mag,
y = prevObj.oPosY,
z = prevObj.oPosZ - math.cos(theta) * mag
}
local vec = {
x = (newPos.x + obj.oPosX) / 2,
y = (newPos.y + obj.oPosY * 7) / 8,
z = (newPos.z + obj.oPosZ) / 2
}
local floor = find_floor_height(vec.x, vec.y, vec.z) + 25
if vec.y < floor then vec.y = floor end
return vec
end
function bhv_powerup_loop(obj)
local m = gMarioStates[obj.heldByPlayerIndex]
local s = gPlayerSyncTable[obj.heldByPlayerIndex]
local p = gPowerups[obj.heldByPlayerIndex][obj.oPowerupIndex]
if obj.oAction == 0 then
if s.powerup[obj.oPowerupIndex] ~= obj.oPowerupType or p ~= obj then
bhv_powerup_stale(obj)
end
end
local vec = nil
if obj.oPowerupType == POWERUP_BANANA then
vec = bhv_powerup_trail(obj)
else
vec = bhv_powerup_spin(obj)
end
local theta = get_network_area_timer() / 8.0
theta = theta + (math.pi * 2) * obj.oPowerupIndex / 3.0
obj_set_vec3f(obj, vec)
obj.oFaceAngleYaw = theta * 0x6000
obj.oFaceAngleRoll = 0
obj.oFaceAnglePitch = 0
if obj.oAction == 1 and obj.oTimer > 5 then
obj.activeFlags = ACTIVE_FLAG_DEACTIVATED
end
end
id_bhvPowerup = hook_behavior(nil, OBJ_LIST_LEVEL, true, bhv_powerup_init, bhv_powerup_loop)
-----------------
function use_powerup(m, powerup)
local s = gPlayerSyncTable[m.playerIndex]
local theta = m.faceAngle.y
if powerup == POWERUP_BANANA then
theta = theta + 0x8000
end
local spawnPosition = {
x = m.pos.x + sins(theta) * 120,
y = m.pos.y,
z = m.pos.z + coss(theta) * 120,
}
if powerup == POWERUP_MUSHROOM then
m.forwardVel = 300
play_character_sound(m, CHAR_SOUND_YAHOO)
elseif powerup == POWERUP_GREEN_SHELL then
spawn_sync_object(
id_bhvWeaponShell,
E_MODEL_KOOPA_SHELL,
spawnPosition.x, spawnPosition.y, spawnPosition.z,
function (obj)
if UNST22 then
obj.oFlyGuyIdleTimer = 0
obj.oFlyGuyOscTimer = gNetworkPlayers[0].globalIndex
obj.oFlyGuyUnusedJitter = 0
else
obj.oWeaponShellType = 0
obj.oWeaponShellGlobalOwner = gNetworkPlayers[0].globalIndex
obj.oWeaponShellDeactivate = 0
end
obj.oMoveAngleYaw = m.faceAngle.y
obj.oForwardVel = 85
obj.oInteractStatus = 0
end
)
elseif powerup == POWERUP_RED_SHELL then
spawn_sync_object(
id_bhvWeaponShell,
E_MODEL_RED_SHELL,
spawnPosition.x, spawnPosition.y, spawnPosition.z,
function (obj)
if UNST22 then
obj.oFlyGuyIdleTimer = 1
obj.oFlyGuyOscTimer = gNetworkPlayers[0].globalIndex
obj.oFlyGuyUnusedJitter = 0
else
obj.oWeaponShellType = 1
obj.oWeaponShellGlobalOwner = gNetworkPlayers[0].globalIndex
obj.oWeaponShellDeactivate = 0
end
obj.oMoveAngleYaw = m.faceAngle.y
obj.oForwardVel = 85
obj.oInteractStatus = 0
end
)
elseif powerup == POWERUP_BANANA then
spawn_sync_object(
id_bhvWeaponBanana,
E_MODEL_BANANA,
spawnPosition.x, spawnPosition.y, spawnPosition.z,
function (obj)
obj.oMoveAngleYaw = m.faceAngle.y
obj.oWeaponBananaGlobalOwner = gNetworkPlayers[0].globalIndex
end
)
end
end
function spawn_powerup(m, powerup, index)
if not is_in_local_area(m) then
return nil
end
if powerup == POWERUP_MUSHROOM then
return spawn_non_sync_object(
id_bhvPowerup,
E_MODEL_1UP,
0, 0, 0,
function(obj)
obj.heldByPlayerIndex = m.playerIndex
obj.oPowerupType = powerup
obj.oPowerupIndex = index
obj_set_billboard(obj)
obj_scale(obj, 1)
end
)
elseif powerup == POWERUP_GREEN_SHELL then
return spawn_non_sync_object(
id_bhvPowerup,
E_MODEL_KOOPA_SHELL,
0, 0, 0,
function(obj)
obj.heldByPlayerIndex = m.playerIndex
obj.oPowerupType = powerup
obj.oPowerupIndex = index
obj_scale(obj, 0.75)
end
)
elseif powerup == POWERUP_RED_SHELL then
return spawn_non_sync_object(
id_bhvPowerup,
E_MODEL_RED_SHELL,
0, 0, 0,
function(obj)
obj.heldByPlayerIndex = m.playerIndex
obj.oPowerupType = powerup
obj.oPowerupIndex = index
obj_scale(obj, 0.75)
end
)
elseif powerup == POWERUP_BANANA then
return spawn_non_sync_object(
id_bhvPowerup,
E_MODEL_BANANA,
0, 0, 0,
function(obj)
obj.heldByPlayerIndex = m.playerIndex
obj.oPowerupType = powerup
obj.oPowerupIndex = index
obj_scale(obj, 0.75)
end
)
end
return nil
end
function select_powerup()
local m = gMarioStates[0]
local s = gPlayerSyncTable[0]
local pick = math.random(1, POWERUP_MAX-1)
local luck = math.random() < 0.33
if luck then
s.powerup[0] = pick
s.powerup[1] = pick
s.powerup[2] = pick
else
s.powerup[0] = pick
s.powerup[1] = POWERUP_NONE
s.powerup[2] = POWERUP_NONE
end
end

View File

@ -0,0 +1,94 @@
define_custom_obj_fields({
oWaypointIndex = 'u32',
})
function bhv_race_ring_init(obj)
obj.oFlags = OBJ_FLAG_UPDATE_GFX_POS_AND_ANGLE
obj_scale(obj, 4)
obj_set_billboard(obj)
obj.oOpacity = 200
end
function bhv_race_ring_inactive(obj)
obj_scale(obj, 1)
obj.oOpacity = 64
local waypoint = get_waypoint(obj.oWaypointIndex)
obj_set_vec3f(obj, waypoint)
local cur = gPlayerSyncTable[0].waypoint
local nex = get_waypoint_index(cur + 1)
if cur == obj.oWaypointIndex then
obj.oAction = 1
cur_obj_unhide()
elseif nex == obj.oWaypointIndex then
cur_obj_unhide()
else
cur_obj_hide()
end
end
function bhv_race_ring_active(obj)
local player = gMarioStates[0].marioObj
local distanceToPlayer = dist_between_objects(obj, player)
cur_obj_unhide()
obj_scale(obj, 4)
obj.oOpacity = 200
local waypoint = get_waypoint(obj.oWaypointIndex)
obj_set_vec3f(obj, waypoint)
if distanceToPlayer < 573 then
obj.oAction = 2
local s = gPlayerSyncTable[0]
if s.waypoint == obj.oWaypointIndex then
if s.waypoint == 1 then
race_increment_lap()
end
s.waypoint = get_waypoint_index(obj.oWaypointIndex + 1)
end
cur_obj_play_sound_2(SOUND_GENERAL_COIN)
end
end
function bhv_race_ring_shrinking(obj)
local scalar = 1 - (obj.oTimer / (30 * 3))
scalar = scalar * scalar
scalar = scalar * scalar
scalar = scalar * scalar
cur_obj_unhide()
obj_scale(obj, 4 * scalar)
obj.oOpacity = 200 * scalar
local waypoint = get_waypoint(obj.oWaypointIndex)
local nextWaypoint = get_waypoint(obj.oWaypointIndex + 1)
local wpos = vec3f_tween(nextWaypoint, waypoint, scalar)
obj_set_vec3f(obj, wpos)
spawn_non_sync_object(id_bhvTriangleParticleSpawner, E_MODEL_NONE,
obj.oPosX + 300 * (math.random() - 0.5) * scalar,
obj.oPosY + 300 * (math.random() - 0.5) * scalar,
obj.oPosZ + 300 * (math.random() - 0.5) * scalar,
nil)
if scalar <= 0.05 then
obj.oAction = 0
end
end
function bhv_race_ring_loop(obj)
if obj.oAction == 0 then
bhv_race_ring_inactive(obj)
elseif obj.oAction == 1 then
bhv_race_ring_active(obj)
elseif obj.oAction == 2 then
bhv_race_ring_shrinking(obj)
end
end
id_bhvRaceRing = hook_behavior(nil, OBJ_LIST_LEVEL, true, bhv_race_ring_init, bhv_race_ring_loop)

View File

@ -0,0 +1,90 @@
function bhv_race_shell_set_hitbox(obj)
local hitbox = get_temp_object_hitbox()
hitbox.interactType = INTERACT_KOOPA_SHELL
hitbox.downOffset = 0
hitbox.damageOrCoinValue = 4
hitbox.health = 1
hitbox.numLootCoins = 1
hitbox.radius = 50
hitbox.height = 50
hitbox.hurtboxRadius = 50
hitbox.hurtboxHeight = 50
obj_set_hitbox(obj, hitbox)
end
function bhv_race_shell_water_drop(obj)
spawn_non_sync_object(id_bhvObjectWaveTrail, E_MODEL_WAVE_TRAIL, obj.oPosX, obj.oPosY, obj.oPosZ, nil)
if gMarioStates[0].forwardVel > 10.0 then
local drop = spawn_non_sync_object(id_bhvWaterDroplet, E_MODEL_WHITE_PARTICLE_SMALL, obj.oPosX, obj.oPosY, obj.oPosZ, nil)
if drop ~= nil then
obj_scale(drop, 1.5)
drop.oVelY = math.random() * 30.0
obj_translate_xz_random(drop, 110.0)
end
end
end
function bhv_race_shell_flame_spawn(obj)
for i = 0, 1 do
spawn_non_sync_object(id_bhvKoopaShellFlame, E_MODEL_RED_FLAME, obj.oPosX, obj.oPosY, obj.oPosZ, nil)
end
end
function bhv_race_shell_spawn_sparkles(obj, offset)
spawn_non_sync_object(id_bhvSparkleSpawn, E_MODEL_NONE, obj.oPosX, obj.oPosY + offset, obj.oPosZ, nil)
end
function bhv_race_shell_init(obj)
obj.oFlags = OBJ_FLAG_UPDATE_GFX_POS_AND_ANGLE
-- set_obj_physics
obj.oWallHitboxRadius = 30
obj.oGravity = -400 / 100.0
obj.oBounciness = -50 / 100.0
obj.oDragStrength = 1000 / 100.0
obj.oFriction = 1000 / 100.0
obj.oBuoyancy = 200 / 100.0
end
function bhv_race_shell_loop(obj)
local np = gNetworkPlayers[obj.heldByPlayerIndex]
local m = gMarioStates[obj.heldByPlayerIndex]
local player = m.marioObj
local riding = is_riding(m)
if active_player(m) and riding then
cur_obj_unhide()
else
cur_obj_hide()
return
end
--bhv_race_shell_set_hitbox(obj)
cur_obj_scale(1.0)
obj_copy_pos(obj, player)
obj.oFaceAngleYaw = player.oFaceAngleYaw
local surface = cur_obj_update_floor_height_and_get_floor()
local waterLevel = find_water_level(obj.oPosX, obj.oPosZ)
if math.abs(waterLevel - obj.oPosY) < 10.0 then
bhv_race_shell_water_drop(obj)
elseif 5.0 > math.abs(obj.oPosY - obj.oFloorHeight) then
if surface ~= nil and surface.type == 1 then
bhv_race_shell_flame_spawn(obj)
elseif m.forwardVel > 70 then
bhv_race_shell_spawn_sparkles(obj, 10.0)
end
elseif m.forwardVel > 70 then
bhv_race_shell_spawn_sparkles(obj, 10.0)
end
obj.oFaceAngleYaw = player.oMoveAngleYaw
end
id_bhvRaceShell = hook_behavior(nil, OBJ_LIST_LEVEL, true, bhv_race_shell_init, bhv_race_shell_loop)

232
mods/shell-rush/race.lua Normal file
View File

@ -0,0 +1,232 @@
gRankings = {}
GAME_STATE_INACTIVE = 0
GAME_STATE_RACE_COUNTDOWN = 1
GAME_STATE_RACE_ACTIVE = 2
GAME_STATE_RACE_FINISH = 3
gGlobalSyncTable.maxLaps = 5
gGlobalSyncTable.gameState = GAME_STATE_INACTIVE
gGlobalSyncTable.gotoLevel = -1
gGlobalSyncTable.raceStartTime = 0
gGlobalSyncTable.raceQuitTime = 0
function race_start(level)
gGlobalSyncTable.gotoLevel = level
gGlobalSyncTable.gameState = GAME_STATE_RACE_COUNTDOWN
gGlobalSyncTable.raceStartTime = 0
gGlobalSyncTable.raceQuitTime = 0
for i = 0, (MAX_PLAYERS - 1) do
local s = gPlayerSyncTable[i]
s.random = math.random()
s.finish = 0
end
end
function race_clear_rankings()
for k,v in pairs(gRankings) do gRankings[k]=nil end
end
function race_increment_lap()
local s = gPlayerSyncTable[0]
s.lap = s.lap + 1
if s.lap > gGlobalSyncTable.maxLaps then
s.lap = gGlobalSyncTable.maxLaps
if s.finish == 0 then
s.finish = get_network_area_timer()
play_race_fanfare()
end
end
end
function race_update_rankings()
-- order players by score
ordered = {}
for i = 0, (MAX_PLAYERS - 1) do
local m = gMarioStates[i]
local s = gPlayerSyncTable[i]
if active_player(m) then
local score = 0
if s.finish > 0 then
score = (gGlobalSyncTable.maxLaps + 2) * 10000 + (10000 / s.finish)
else
-- figure out distance score
local maxDist = vec3f_dist(get_waypoint(s.waypoint - 1), get_waypoint(s.waypoint))
if maxDist == 0 then maxDist = 1 end
local dist = vec3f_dist(m.pos, get_waypoint(s.waypoint))
local distScore = clamp(1 - (dist/maxDist), 0, 1)
-- figure out entire score
local lastWaypoint = get_waypoint_index(s.waypoint - 1)
score = s.lap * 10000 + lastWaypoint * 100 + distScore
if s.lap == 0 then score = 0 end
end
if score > 0 then
table.insert(ordered, { score = score, m = m })
end
end
end
table.sort(ordered, function (v1, v2) return v1.score > v2.score end)
-- clear rankings
race_clear_rankings()
-- set rankings
for i,v in ipairs(ordered) do
table.insert(gRankings, v.m)
end
end
function race_start_line()
local index = 0
for i = 0, (MAX_PLAYERS - 1) do
local s = gPlayerSyncTable[i]
if network_is_server() then
s.finish = 0
end
if active_player(gMarioStates[i]) and s.random < gPlayerSyncTable[0].random then
index = index + 1
end
end
local lineIndex = (index % 2) + 1
local lineBackIndex = index - (index % 2)
local m = gMarioStates[0]
local spawnLine = gLevelData.spawn[lineIndex]
local point = vec3f_tween(spawnLine.a, spawnLine.b, lineBackIndex / MAX_PLAYERS)
local waypoint = get_waypoint(1)
m.pos.x = point.x
m.pos.y = point.y
m.pos.z = point.z
m.marioObj.oIntangibleTimer = 5
set_mario_action(m, ACT_RIDING_SHELL_GROUND, 0)
m.vel.x = 0
m.vel.y = 0
m.vel.z = 0
m.slideVelX = 0
m.slideVelZ = 0
m.forwardVel = 0
m.faceAngle.x = 0
m.faceAngle.y = atan2s(waypoint.z - m.pos.z, waypoint.x - m.pos.x)
m.faceAngle.z = 0
end
function race_update()
-- automatically start race
if gGlobalSyncTable.gameState == GAME_STATE_INACTIVE and network_player_connected_count() > 1 then
race_start(LEVEL_SL)
end
local np = gNetworkPlayers[0]
if gGlobalSyncTable.gotoLevel ~= -1 then
if np.currLevelNum ~= gGlobalSyncTable.gotoLevel then
if gGlobalSyncTable.gotoLevel == LEVEL_CASTLE_GROUNDS then
warp_to_castle(LEVEL_VCUTM)
else
warp_to_level(gGlobalSyncTable.gotoLevel, 1, 16)
end
end
end
-- make sure this is a valid level
if gLevelData == gLevelDataTable[-1] then
return
end
if gGlobalSyncTable.gameState == GAME_STATE_RACE_COUNTDOWN then
race_start_line()
race_clear_rankings()
if network_is_server() then
if gGlobalSyncTable.raceStartTime == 0 then
if np.currAreaSyncValid then
gGlobalSyncTable.raceStartTime = get_network_area_timer() + 30 * 5
gGlobalSyncTable.raceQuitTime = 0
end
elseif gGlobalSyncTable.raceStartTime > get_network_area_timer() + 30 * 5 then
gGlobalSyncTable.raceStartTime = get_network_area_timer() + 30 * 5
gGlobalSyncTable.raceQuitTime = 0
elseif gGlobalSyncTable.raceStartTime > 0 and get_network_area_timer() >= gGlobalSyncTable.raceStartTime then
gGlobalSyncTable.gameState = GAME_STATE_RACE_ACTIVE
end
end
elseif gGlobalSyncTable.gameState == GAME_STATE_RACE_ACTIVE then
race_update_rankings()
if network_is_server() then
if gGlobalSyncTable.raceQuitTime == 0 then
-- check for race finish
local foundFinisher = false
for i = 0, (MAX_PLAYERS - 1) do
local m = gMarioStates[i]
local s = gPlayerSyncTable[i]
if active_player(m) and s.finish > 0 then
foundFinisher = true
end
end
if foundFinisher then
-- set a timer until the race is finished
gGlobalSyncTable.raceQuitTime = get_network_area_timer() + 30 * 20
end
elseif gGlobalSyncTable.raceQuitTime > 0 and get_network_area_timer() > gGlobalSyncTable.raceQuitTime then
-- race is finished, start a new one
if gLevelData == gLevelDataTable[LEVEL_CASTLE_GROUNDS] then
race_start(LEVEL_BOB)
elseif gLevelData == gLevelDataTable[LEVEL_SL] then
race_start(LEVEL_CASTLE_GROUNDS)
elseif gLevelData == gLevelDataTable[LEVEL_BOB] then
race_start(LEVEL_SL)
end
end
end
end
end
function on_race_command(msg)
if not network_is_server() then
djui_chat_message_create('Only the server can change this setting!')
return true
end
if msg == 'BOB' then
race_start(LEVEL_BOB)
return true
end
if msg == 'SL' then
race_start(LEVEL_SL)
return true
end
if msg == 'CG' then
race_start(LEVEL_CASTLE_GROUNDS)
return true
end
return false
end
function on_laps_command(msg)
if not network_is_server() then
djui_chat_message_create('Only the server can change this setting!')
return true
end
if tonumber(msg) > 0 then
gGlobalSyncTable.maxLaps = math.floor(tonumber(msg))
return true
end
return false
end
function on_game_state_changed(tag, oldVal, newVal)
local m = gMarioStates[0]
if oldVal ~= newVal then
if newVal == GAME_STATE_RACE_ACTIVE then
play_sound(SOUND_GENERAL_RACE_GUN_SHOT, m.marioObj.header.gfx.cameraToObject)
end
end
end
hook_chat_command('race', "[CG|SL|BOB]", on_race_command)
hook_chat_command('laps', "[number]", on_laps_command)
hook_on_sync_table_change(gGlobalSyncTable, 'gameState', i, on_game_state_changed)

128
mods/shell-rush/utils.lua Normal file
View File

@ -0,0 +1,128 @@
function clamp(v, min, max)
if v < min then return min end
if v > max then return max end
return v
end
function convert_s16(num)
local min = -32768
local max = 32767
while (num < min) do
num = max + (num - min)
end
while (num > max) do
num = min + (num - max)
end
return num
end
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
return is_player_active(m)
end
function vec3f_tween(a, b, mult)
if mult < 0 then mult = 0 end
if mult > 1 then mult = 1 end
local amult = 1 - mult
return {
x = a.x * amult + b.x * mult,
y = a.y * amult + b.y * mult,
z = a.z * amult + b.z * mult,
}
end
function obj_set_vec3f(obj, v)
if obj == nil or v == nil then return end
obj.oPosX = v.x
obj.oPosY = v.y
obj.oPosZ = v.z
end
function get_last_waypoint_index()
local index = 1
while gLevelData.waypoints[index + 1] ~= nil do
index = index + 1
end
return index
end
function get_waypoint(i)
return gLevelData.waypoints[get_waypoint_index(i)]
end
function get_waypoint_index(i)
local lastIndex = get_last_waypoint_index()
i = ((i - 1) % lastIndex) + 1
if gLevelData.waypoints[i] == nil then
return 1
end
return i
end
function vec3f_non_nan(v)
if v.x ~= v.x then v.x = 0 end
if v.y ~= v.y then v.y = 0 end
if v.z ~= v.z then v.z = 0 end
end
function vec3f_angle_between(a, b)
return math.acos(vec3f_dot(a, b) / (vec3f_length(a) * vec3f_length(b)))
end
function is_riding(m)
return (m.action == ACT_RIDING_SHELL_GROUND) or (m.action == ACT_RIDING_SHELL_FALL) or (m.action == ACT_RIDING_SHELL_JUMP)
end
function is_in_local_area(m)
local np1 = gNetworkPlayers[0]
local np2 = gNetworkPlayers[m.playerIndex]
return (np1.currCourseNum == np2.currCourseNum) and (np1.currLevelNum == np2.currLevelNum) and (np1.currAreaIndex == np2.currAreaIndex) and (np1.currActNum == np2.currActNum)
end
function spawn_mist(obj)
local spi = obj_get_temp_spawn_particles_info(E_MODEL_MIST)
if spi == nil then
return nil
end
spi.behParam = 3
spi.count = 5
spi.offsetY = 25
spi.forwardVelBase = 6
spi.forwardVelRange = -6
spi.velYBase = 6
spi.velYRange = -6
spi.gravity = 0
spi.dragStrength = 5
spi.sizeBase = 10
spi.sizeRange = 16
cur_obj_spawn_particles(spi)
end
function spawn_triangles(obj)
spawn_non_sync_object(id_bhvTriangleParticleSpawner, E_MODEL_NONE,
obj.oPosX,
obj.oPosY,
obj.oPosZ,
nil)
end
function spawn_sparkles(obj)
for i = 0, 5 do
spawn_non_sync_object(id_bhvSparkleSpawn, E_MODEL_NONE,
obj.oPosX + math.random(-100, 100),
obj.oPosY + math.random(-100, 100),
obj.oPosZ + math.random(-100, 100),
nil)
end
end

View File

@ -0,0 +1,68 @@
local bananaTimeout = 30 * 60 * 1 --- one minute
define_custom_obj_fields({
oWeaponBananaGlobalOwner = 'u32',
})
function bhv_weapon_banana_init(obj)
obj.oGraphYOffset = 0
obj.oFlags = OBJ_FLAG_UPDATE_GFX_POS_AND_ANGLE
obj.oOpacity = 255
obj.oVelY = 0
obj_scale(obj, 0.9)
obj.oPosY = obj.oPosY + 50
local hitbox = get_temp_object_hitbox()
hitbox.interactType = INTERACT_DAMAGE
hitbox.downOffset = 0
hitbox.damageOrCoinValue = 4
hitbox.health = 1
hitbox.numLootCoins = 1
hitbox.radius = 100
hitbox.height = 70
hitbox.hurtboxRadius = 100
hitbox.hurtboxHeight = 70
obj_set_hitbox(obj, hitbox)
cur_obj_play_sound_2(SOUND_GENERAL_BOING1)
network_init_object(obj, true, {
'oWeaponBananaGlobalOwner'
})
end
function bhv_weapon_banana_destroy(obj)
obj.activeFlags = ACTIVE_FLAG_DEACTIVATED
spawn_triangles(obj)
cur_obj_play_sound_2(SOUND_GENERAL_FLAME_OUT)
end
function bhv_weapon_banana_loop(obj)
local floor = cur_obj_update_floor_height_and_get_floor()
if floor ~= nil then
if obj.oPosY < obj.oFloorHeight + 10 then
obj.oVelY = 0
obj.oPosY = obj.oFloorHeight + 5
obj_orient_graph(obj, floor.normal.x, floor.normal.y, floor.normal.z)
else
obj.oVelY = obj.oVelY - 3
obj.oPosY = obj.oPosY + obj.oVelY
if obj.oPosY < obj.oFloorHeight + 10 then
spawn_mist(obj)
end
end
end
-- prevent interactions for the first 5 frames
if obj.oTimer < 5 then
obj.oInteractStatus = 0
end
if cur_obj_check_interacted() ~= 0 or obj.oTimer > bananaTimeout then
bhv_weapon_banana_destroy(obj)
end
end
id_bhvWeaponBanana = hook_behavior(nil, OBJ_LIST_PUSHABLE, true, bhv_weapon_banana_init, bhv_weapon_banana_loop)
E_MODEL_BANANA = smlua_model_util_get_id("banana_geo")

View File

@ -0,0 +1,229 @@
local shellTimeout = 30 * 30 --- 30 seconds
define_custom_obj_fields({
oWeaponShellType = 'u32',
oWeaponShellGlobalOwner = 'u32',
oWeaponShellDeactivate = 'u32',
})
function bhv_weapon_shell_init(obj)
obj.oFlags = OBJ_FLAG_UPDATE_GFX_POS_AND_ANGLE
obj.oOpacity = 255
obj.oVelY = 0
obj.oTimer = 0
if UNST22 then
obj.oFlyGuyUnusedJitter = 0
else
obj.oWeaponShellDeactivate = 0
end
obj_scale(obj, 0.9)
local hitbox = get_temp_object_hitbox()
hitbox.interactType = INTERACT_DAMAGE
hitbox.downOffset = 0
hitbox.damageOrCoinValue = 4
hitbox.health = 1
hitbox.numLootCoins = 1
hitbox.radius = 100
hitbox.height = 70
hitbox.hurtboxRadius = 100
hitbox.hurtboxHeight = 70
obj_set_hitbox(obj, hitbox)
cur_obj_play_sound_2(SOUND_GENERAL_BIG_POUND)
if UNST22 then
network_init_object(obj, false, { 'oFlyGuyUnusedJitter' })
else
network_init_object(obj, false, { 'oWeaponShellDeactivate' })
end
end
function bhv_weapon_shell_destroy(obj)
obj.activeFlags = ACTIVE_FLAG_DEACTIVATED
spawn_triangles(obj)
cur_obj_play_sound_2(SOUND_GENERAL_BREAK_BOX)
if UNST22 then
if obj.oFlyGuyUnusedJitter == 0 then
obj.oFlyGuyUnusedJitter = 1
network_send_object(obj, true)
end
obj.oFlyGuyUnusedJitter = 1
else
if obj.oWeaponShellDeactivate == 0 then
obj.oWeaponShellDeactivate = 1
network_send_object(obj, true)
end
obj.oWeaponShellDeactivate = 1
end
end
function bhv_weapon_shell_move(obj)
local hit = false
local stepHeight = 100
local savedX = obj.oPosX
local savedY = obj.oPosY
local savedZ = obj.oPosZ
-- figure out direction
local v = {
x = sins(obj.oMoveAngleYaw) * obj.oForwardVel,
y = 0,
z = coss(obj.oMoveAngleYaw) * obj.oForwardVel,
}
-- cast ray
local info = collision_find_surface_on_ray(
obj.oPosX, obj.oPosY + stepHeight, obj.oPosZ,
v.x, v.y, v.z)
-- move the shell
obj.oPosX = info.hitPos.x
obj.oPosY = info.hitPos.y
obj.oPosZ = info.hitPos.z
-- figure out how far from floor
local floorHeight = find_floor_height(obj.oPosX, obj.oPosY, obj.oPosZ)
if floorHeight <= -10000.0 then
-- we're OOB
obj.oPosX = savedX
obj.oPosY = savedY
obj.oPosZ = savedZ
obj.oMoveAngleYaw = obj.oMoveAngleYaw + 0x4000
obj.oFaceAngleYaw = obj.oMoveAngleYaw
return true
elseif math.abs(floorHeight - obj.oPosY) > stepHeight * 1.25 then
-- we're in the air
obj.oPosY = obj.oPosY - stepHeight
obj.oVelY = obj.oVelY - 5
obj.oPosY = obj.oPosY + obj.oVelY
if obj.oPosY < floorHeight then
obj.oPosY = floorHeight
end
else
-- we're on the ground
obj.oPosY = floorHeight
obj.oVelY = 0
end
-- figure out if we hit wall
if info.surface ~= nil and math.abs(info.surface.normal.y) < 0.2 then
-- projection
local parallel = vec3f_project(v, info.surface.normal)
local perpendicular = { x = v.x - parallel.x, y = v.y - parallel.y, z = v.z - parallel.z }
-- reflect velocity along normal
local reflect = {
x = perpendicular.x - parallel.x,
y = perpendicular.y - parallel.y,
z = perpendicular.z - parallel.z
}
obj.oPosX = savedX
obj.oPosY = savedY
obj.oPosZ = savedZ
obj.oMoveAngleYaw = atan2s(reflect.z, reflect.x)
obj.oFaceAngleYaw = obj.oMoveAngleYaw
hit = true
spawn_mist(obj)
cur_obj_play_sound_2(SOUND_GENERAL_BOX_LANDING)
end
-- orient to floor
info = collision_find_surface_on_ray(
obj.oPosX, obj.oPosY + stepHeight, obj.oPosZ,
0, stepHeight * -1.5, 0)
if info.surface ~= nil then
obj_orient_graph(obj, info.surface.normal.x, info.surface.normal.y, info.surface.normal.z)
end
-- attach to water
local waterLevel = find_water_level(obj.oPosX, obj.oPosZ)
if obj.oPosY < waterLevel then obj.oPosY = waterLevel end
return hit
end
function bhv_weapon_shell_red_loop(obj, hit)
if hit then
bhv_weapon_shell_destroy(obj)
return
end
-- find target
local target = nil
local targetDist = 1000
local v = {
x = sins(obj.oMoveAngleYaw) * obj.oForwardVel,
y = 0,
z = coss(obj.oMoveAngleYaw) * obj.oForwardVel,
}
for i = 0, (MAX_PLAYERS - 1) do
local m = gMarioStates[i]
local np = gNetworkPlayers[i]
local isntGlobalOwner = (np.globalIndex ~= obj.oWeaponShellGlobalOwner)
if UNST22 then isntGlobalOwner = (np.globalIndex ~= obj.oFlyGuyOscTimer) end
if active_player(m) and isntGlobalOwner then
local dist = dist_between_objects(m.marioObj, obj)
local diff = { x = m.marioObj.oPosX - obj.oPosX, y = 0, z = m.marioObj.oPosZ - obj.oPosZ }
local angleBetween = vec3f_angle_between(diff, v)
if dist < targetDist and angleBetween < 100 then
target = m
targetDist = dist
end
end
end
-- no target found :(
if target == nil then
return
end
-- turn toward target
local turnSpeed = 0x300
local targetYaw = atan2s(target.pos.z - obj.oPosZ, target.pos.x - obj.oPosX)
obj.oMoveAngleYaw = targetYaw - approach_s32(convert_s16(targetYaw - obj.oMoveAngleYaw), 0, turnSpeed, turnSpeed)
obj.oFaceAngleYaw = obj.oMoveAngleYaw
end
function bhv_weapon_shell_loop(obj)
local hit = bhv_weapon_shell_move(obj)
if UNST22 then
if obj.oFlyGuyIdleTimer == 1 then
bhv_weapon_shell_red_loop(obj, hit)
end
if obj.oFlyGuyUnusedJitter ~= 0 then
bhv_weapon_shell_destroy(obj)
return
end
else
if obj.oWeaponShellType == 1 then
bhv_weapon_shell_red_loop(obj, hit)
end
if obj.oWeaponShellDeactivate ~= 0 then
bhv_weapon_shell_destroy(obj)
return
end
end
-- prevent interactions for the first 5 frames
if obj.oTimer < 5 then
obj.oInteractStatus = 0
end
if cur_obj_check_interacted() ~= 0 then
bhv_weapon_shell_destroy(obj)
return
end
if obj.oTimer > shellTimeout then
bhv_weapon_shell_destroy(obj)
return
end
end
id_bhvWeaponShell = hook_behavior(nil, OBJ_LIST_PUSHABLE, true, bhv_weapon_shell_init, bhv_weapon_shell_loop)
E_MODEL_RED_SHELL = smlua_model_util_get_id("red_shell_geo")