Add shell rush gamemode
This commit is contained in:
parent
34d28ffb3c
commit
1773153971
|
@ -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.
|
@ -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)
|
|
@ -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")
|
|
@ -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 } },
|
||||
},
|
||||
},
|
||||
}
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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")
|
|
@ -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")
|
Loading…
Reference in New Issue