From 17731539711d1bbf57ebe116404ea9f34a178f5d Mon Sep 17 00:00:00 2001 From: MysterD Date: Wed, 30 Mar 2022 20:15:17 -0700 Subject: [PATCH] Add shell rush gamemode --- mods/shell-rush/actions.lua | 278 +++++++++++++++++++++++ mods/shell-rush/actors/banana_geo.bin | Bin 0 -> 4463 bytes mods/shell-rush/actors/item_box_geo.bin | Bin 0 -> 1323 bytes mods/shell-rush/actors/red_shell_geo.bin | Bin 0 -> 6263 bytes mods/shell-rush/hud.lua | 96 ++++++++ mods/shell-rush/item-box.lua | 74 ++++++ mods/shell-rush/level-data.lua | 147 ++++++++++++ mods/shell-rush/level.lua | 155 +++++++++++++ mods/shell-rush/main.lua | 141 ++++++++++++ mods/shell-rush/powerup.lua | 273 ++++++++++++++++++++++ mods/shell-rush/race-ring.lua | 94 ++++++++ mods/shell-rush/race-shell.lua | 90 ++++++++ mods/shell-rush/race.lua | 232 +++++++++++++++++++ mods/shell-rush/utils.lua | 128 +++++++++++ mods/shell-rush/weapon-banana.lua | 68 ++++++ mods/shell-rush/weapon-shell.lua | 229 +++++++++++++++++++ 16 files changed, 2005 insertions(+) create mode 100644 mods/shell-rush/actions.lua create mode 100644 mods/shell-rush/actors/banana_geo.bin create mode 100644 mods/shell-rush/actors/item_box_geo.bin create mode 100644 mods/shell-rush/actors/red_shell_geo.bin create mode 100644 mods/shell-rush/hud.lua create mode 100644 mods/shell-rush/item-box.lua create mode 100644 mods/shell-rush/level-data.lua create mode 100644 mods/shell-rush/level.lua create mode 100644 mods/shell-rush/main.lua create mode 100644 mods/shell-rush/powerup.lua create mode 100644 mods/shell-rush/race-ring.lua create mode 100644 mods/shell-rush/race-shell.lua create mode 100644 mods/shell-rush/race.lua create mode 100644 mods/shell-rush/utils.lua create mode 100644 mods/shell-rush/weapon-banana.lua create mode 100644 mods/shell-rush/weapon-shell.lua diff --git a/mods/shell-rush/actions.lua b/mods/shell-rush/actions.lua new file mode 100644 index 000000000..eb08d04d9 --- /dev/null +++ b/mods/shell-rush/actions.lua @@ -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) diff --git a/mods/shell-rush/actors/banana_geo.bin b/mods/shell-rush/actors/banana_geo.bin new file mode 100644 index 0000000000000000000000000000000000000000..ce937a7cdaa9cc8d986c95c34c437a539595e7ee GIT binary patch literal 4463 zcmb_f4Nz3q6+ZX9WwS0TF1Ri`3t5PQg2)DqCrLIi)P zk|zF4ASqNQWYne_W9^t?WHb>7>5tVWany0*=!y!qM#Z0q5#{xq_x7#3X+@mrW8U69 zd(J)gJKs6yUM@G{wcJAdv$_UH-fOvICs-HRTBDOBydSqssMc1D3>aNvU@e#v2wtVnr zvTw@N_+`&G>mvWWcJ6zZW9`*pgI|oy%<&1_uy)ID5(b z$(y@t`m{q2G{1lCQ>E{o<6sTB6@1{+O4n%&8SoCGD3c zwxn$STyZC-)W7>T^_hQqyKcgg@Ir0I(CL~Ztqnc?)_~08b+&(B${Mp^sc4pN0xx4ml-S^Fo9itt>`yYOM{6yKw z1qqA%&*J)vg>;A zPE5g|qSRj$1DHq9%gT05F2x^Lr%juh_4DcNr)&JHLNz{Lhtw#iO&!y!yk7a0`CVn% zfo=B=H*g!uxjf;+dyULi?He4ViQ|>yjbHiZH0Bv%u5ybu_@0YvZ1piWPl@{V$Zlgy zXo9Bull?QdXp{y&ZEv_E-B=aqWZjY41xK%@r(WR4-H+aKHGN~?!g*6dZl^1@7KUHg zfk+@NW!AKPKV7(%nOs%2hbdeH>BXb4*!@bFQuAYjd&5%6JJxCB+C(LS=w%E}yQJK%(?Gz%DfkDt%s z+esom-2gr$i|gSi*?~p$|HLk$nE;mWEmN4MvpaA*he{W!WAt(7K!XqITXoJW@Jp@$yqW&e6etG8@w_4c2z#miq#O01iYTwL#+VM*@zOo->VqZ5R?$S2{`kUp@@?e8?= z=|Q}5JWYrPFdk3)T0^Us{7zu2m;ZWmyqCZGi0_f$4?~czcy^qF=oP|u!eB9*JXW6LdMR^cg$3xL1lvR+5 z`nzzJ2Xm=Fxg+~{R>Nnby)RY^`LAZ9`93* zQT%pBdKvFuxb2Ma%Kk`C{$%{5C;amIq^I?trkCTR`BHpRJS0;*p7mW9G}(=Z_^Z7_ z_&xYVd#`?Zq1+vh7;jh7<9efd3`=z9PwXdig1erm|FWk~aQDMrtas`Mx&C8Z^{)nh z_yPHgV!OFKbGv)JCO9PW8-)A|sJ{Xax#G?kL66;j8|5T+n|o#cDIpqja~JSoehd76F0c+sNe>mDZQC`Q=_)u1j*^}(wW&h%abXL3b`2lO> zD4(AFMe)k{7x5taq*q2=W zm@nM%HNm$cUpKJ7i2oZfiob>HUBP~&e9d7-{u9jCDf~|SCt-g%alNPMzjpci6#m@5ei01@;r~L0^6?`?~b)r4h%Rx4Te;ebI`>&N97Wa$)#xLbV+^;Yn zazBglV7|QUquagquiEF^y!=x?$^AzAC-s}0Pud?TpYr)b`)NJ)i`)+hu6~fui-VY7 zIxl2A#6RUzj)(F^`-vPc$rP{5KiL!iGC!mzeq{bhPyEY#l1zLm`agF|9CmBW9PA-e zH!wQ=`!^SlX|x6fC!UI+Rw-$R1FfG%9QC>YaSS#Z#PJ#2(y1^|x1qoWiq_=eR;iFs zs}vGy8NEiO7SU?de&Xn-){3KADUDjC6qORB(V*9fdZS*7(jBcaSo}TMIbbr)p71z@ zI9;PwiKCxJ8Z~|r48H(rbT4m^U_1jGiJr^-zu9oXF}PSTnj|)!0p2VB1Ky9>&}yV4 z1!$#FrKYb#4Cn)g^^@wesF*PuPY?W z^R#31cWIK$K(y5x9<$ZYCNY^?X@sfwvI}>z}9v^WnAGK?(Jlih^;g5Q}zx@>!1FHfkX$qA$UBCVVDMVlj literal 0 HcmV?d00001 diff --git a/mods/shell-rush/actors/item_box_geo.bin b/mods/shell-rush/actors/item_box_geo.bin new file mode 100644 index 0000000000000000000000000000000000000000..8bade9a1fac037a899016604a72748f4e311b281 GIT binary patch literal 1323 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 S^v`xcN1^QKCZQd5!G8d2hY7|2 literal 0 HcmV?d00001 diff --git a/mods/shell-rush/hud.lua b/mods/shell-rush/hud.lua new file mode 100644 index 000000000..3178e39ca --- /dev/null +++ b/mods/shell-rush/hud.lua @@ -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) diff --git a/mods/shell-rush/item-box.lua b/mods/shell-rush/item-box.lua new file mode 100644 index 000000000..c4d1880ed --- /dev/null +++ b/mods/shell-rush/item-box.lua @@ -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") diff --git a/mods/shell-rush/level-data.lua b/mods/shell-rush/level-data.lua new file mode 100644 index 000000000..3eef859ea --- /dev/null +++ b/mods/shell-rush/level-data.lua @@ -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 } }, + }, + }, +} diff --git a/mods/shell-rush/level.lua b/mods/shell-rush/level.lua new file mode 100644 index 000000000..72644f9db --- /dev/null +++ b/mods/shell-rush/level.lua @@ -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) diff --git a/mods/shell-rush/main.lua b/mods/shell-rush/main.lua new file mode 100644 index 000000000..a14090ae6 --- /dev/null +++ b/mods/shell-rush/main.lua @@ -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) diff --git a/mods/shell-rush/powerup.lua b/mods/shell-rush/powerup.lua new file mode 100644 index 000000000..5b77e81a9 --- /dev/null +++ b/mods/shell-rush/powerup.lua @@ -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 \ No newline at end of file diff --git a/mods/shell-rush/race-ring.lua b/mods/shell-rush/race-ring.lua new file mode 100644 index 000000000..4faaddeff --- /dev/null +++ b/mods/shell-rush/race-ring.lua @@ -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) diff --git a/mods/shell-rush/race-shell.lua b/mods/shell-rush/race-shell.lua new file mode 100644 index 000000000..efb501508 --- /dev/null +++ b/mods/shell-rush/race-shell.lua @@ -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) diff --git a/mods/shell-rush/race.lua b/mods/shell-rush/race.lua new file mode 100644 index 000000000..8e653cbf8 --- /dev/null +++ b/mods/shell-rush/race.lua @@ -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) diff --git a/mods/shell-rush/utils.lua b/mods/shell-rush/utils.lua new file mode 100644 index 000000000..e4ac3fdae --- /dev/null +++ b/mods/shell-rush/utils.lua @@ -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 \ No newline at end of file diff --git a/mods/shell-rush/weapon-banana.lua b/mods/shell-rush/weapon-banana.lua new file mode 100644 index 000000000..d6687e508 --- /dev/null +++ b/mods/shell-rush/weapon-banana.lua @@ -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") diff --git a/mods/shell-rush/weapon-shell.lua b/mods/shell-rush/weapon-shell.lua new file mode 100644 index 000000000..861b33ae3 --- /dev/null +++ b/mods/shell-rush/weapon-shell.lua @@ -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")