mirror of
https://github.com/coop-deluxe/sm64coopdx.git
synced 2024-11-25 13:35:12 +00:00
498 lines
No EOL
16 KiB
Lua
498 lines
No EOL
16 KiB
Lua
----------------------
|
|
-- bullet functions --
|
|
----------------------
|
|
|
|
--- @param obj Object
|
|
--- @return nil
|
|
function bullet_ricochet(obj)
|
|
if obj == nil then return end
|
|
|
|
-- ricochet
|
|
obj.oAction = 1
|
|
if obj.oVelY == 0 then
|
|
obj.oForwardVel = -obj.oForwardVel
|
|
else
|
|
obj.oVelX = -obj.oVelX
|
|
obj.oVelY = -obj.oVelY
|
|
obj.oVelZ = -obj.oVelZ
|
|
end
|
|
obj.oPosX = obj.header.gfx.prevPos.x
|
|
obj.oPosY = obj.header.gfx.prevPos.y
|
|
obj.oPosZ = obj.header.gfx.prevPos.z
|
|
|
|
audio_sample_play(SOUND_CUSTOM_RICOCHET, { x = obj.oPosX, y = obj.oPosY, z = obj.oPosZ }, 1)
|
|
end
|
|
|
|
--- @param obj Object
|
|
local function bullet_hit(obj)
|
|
spawn_mist_particles()
|
|
|
|
--[[if obj_count_objects_with_behavior_id(id_bhvBulletHole) < 100 then
|
|
local raycast = collision_find_surface_on_ray(obj.header.gfx.prevPos.x, obj.header.gfx.prevPos.y, obj.header.gfx.prevPos.z, obj.oVelX, obj.oVelY, obj.oVelZ)
|
|
if raycast.surface ~= nil and (cur_obj_detect_steep_floor(89) == 0 or cur_obj_detect_steep_floor(-89)) then
|
|
spawn_non_sync_object(
|
|
id_bhvBulletHole,
|
|
E_MODEL_BULLET_HOLE,
|
|
raycast.hitPos.x, raycast.hitPos.y, raycast.hitPos.z,
|
|
--- @param o Object
|
|
function(o)
|
|
o.parentObj = raycast.surface.object
|
|
end
|
|
)
|
|
end
|
|
end]]
|
|
|
|
obj_mark_for_deletion(obj)
|
|
end
|
|
|
|
|
|
---------------------------
|
|
-- hitbox hurt functions --
|
|
---------------------------
|
|
|
|
--- @param obj Object
|
|
--- @param bulletObj Object
|
|
function hurt_player(obj, bulletObj)
|
|
local m = gMarioStates[network_local_index_from_global(obj.globalPlayerIndex)]
|
|
if (m.flags & MARIO_METAL_CAP) ~= 0 then
|
|
bullet_ricochet(bulletObj)
|
|
play_sound(SOUND_GENERAL_METAL_POUND, m.marioObj.header.gfx.cameraToObject)
|
|
return false
|
|
elseif (m.flags & MARIO_VANISH_CAP) ~= 0 or m.invincTimer > 0 or (m.action & ACT_FLAG_INTANGIBLE) ~= 0 then
|
|
return false
|
|
else
|
|
packet_send(true, PACKET_ATTACK, { globalIndex = obj.globalPlayerIndex, weaponId = obj_get_weapon_id(bulletObj), yoshi = bulletObj.oAction == 2 })
|
|
end
|
|
return true
|
|
end
|
|
|
|
--- @param bulletObj Object
|
|
function hurt_star(_, bulletObj)
|
|
bullet_ricochet(bulletObj)
|
|
return false
|
|
end
|
|
|
|
--- @param obj Object
|
|
--- @param bulletObj Object
|
|
function hurt_sign(obj, bulletObj)
|
|
if obj.oGmHealth <= 0 or gWeaponTable[obj_get_weapon_id(bulletObj)].strong then
|
|
obj.oFaceAnglePitch = -0x4000
|
|
obj.oInteractType = 0
|
|
obj.hitboxRadius = 0
|
|
obj.hitboxHeight = 0
|
|
network_send_object(obj, false)
|
|
end
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
function hurt_toad(obj)
|
|
obj_spawn_yellow_coins(obj, 1)
|
|
obj_mark_for_deletion(obj)
|
|
network_send_object(obj, false)
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
function hurt_exclamation_box(obj)
|
|
if obj.oAction < 4 then
|
|
obj.oAction = 4
|
|
network_send_object(obj, true)
|
|
end
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
function hurt_breakable_box(obj)
|
|
if obj.oGmHealth <= 0 then
|
|
obj.oInteractStatus = INT_STATUS_INTERACTED | INT_STATUS_WAS_ATTACKED
|
|
network_send_object(obj, false)
|
|
end
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
function hurt_breakable_box_small(obj)
|
|
obj.oInteractStatus = ATTACK_KICK_OR_TRIP | INT_STATUS_INTERACTED | INT_STATUS_WAS_ATTACKED | INT_STATUS_STOP_RIDING
|
|
network_send_object(obj, false)
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
--- @param bulletObj Object
|
|
function hurt_bowling_ball(obj, bulletObj)
|
|
if obj.oGmHealth <= 0 or gWeaponTable[obj_get_weapon_id(bulletObj)].strong then
|
|
play_sound(SOUND_GENERAL_BREAK_BOX, obj.header.gfx.cameraToObject)
|
|
spawn_triangle_break_particles(30, 138, 3, 4)
|
|
obj_mark_for_deletion(obj)
|
|
end
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
function hurt_water_bomb(obj)
|
|
play_sound(SOUND_OBJ_DIVING_IN_WATER, obj.header.gfx.cameraToObject)
|
|
set_camera_shake_from_point(SHAKE_POS_SMALL, obj.oPosX, obj.oPosY, obj.oPosZ)
|
|
obj.oAction = WATER_BOMB_ACT_EXPLODE
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
--- @param bulletObj Object
|
|
function hurt_tree(obj, bulletObj)
|
|
for _ = 1, 10 do
|
|
spawn_non_sync_object(
|
|
id_bhvLeafParticleSpawner,
|
|
E_MODEL_NONE,
|
|
bulletObj.oPosX, bulletObj.oPosY, bulletObj.oPosZ,
|
|
nil
|
|
)
|
|
end
|
|
play_sound(SOUND_ACTION_CLIMB_UP_TREE, obj.header.gfx.cameraToObject)
|
|
return false
|
|
end
|
|
|
|
--- @param obj Object
|
|
--- @param bulletObj Object
|
|
function hurt_chain_chomp(obj, bulletObj)
|
|
if gWeaponTable[obj_get_weapon_id(bulletObj)].strong then
|
|
for _ = 1, 5 do
|
|
spawn_non_sync_object(
|
|
id_bhvExplosion,
|
|
E_MODEL_EXPLOSION,
|
|
obj.oPosX + math.random(-200, 200), obj.oPosY + 90 + math.random(-200, 200), obj.oPosZ + math.random(-200, 200),
|
|
nil
|
|
)
|
|
end
|
|
obj_mark_for_deletion(obj)
|
|
else
|
|
obj.oForwardVel = obj.oForwardVel - 5
|
|
obj.oVelY = -20
|
|
obj.oGravity = -4
|
|
end
|
|
network_send_object(obj, false)
|
|
play_sound(SOUND_GENERAL_CHAIN_CHOMP1, obj.header.gfx.cameraToObject)
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
--- @param bulletObj Object
|
|
function hurt_goomba(obj, bulletObj)
|
|
if gWeaponTable[obj_get_weapon_id(bulletObj)].strong then
|
|
obj.oInteractStatus = ATTACK_GROUND_POUND_OR_TWIRL | INT_STATUS_INTERACTED | INT_STATUS_WAS_ATTACKED
|
|
else
|
|
obj.oInteractStatus = ATTACK_KICK_OR_TRIP | INT_STATUS_INTERACTED | INT_STATUS_WAS_ATTACKED
|
|
end
|
|
network_send_object(obj, false)
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
function hurt_bobomb(obj)
|
|
if obj.oAction ~= BOBOMB_ACT_EXPLODE then
|
|
obj.oAction = BOBOMB_ACT_LAUNCHED
|
|
end
|
|
network_send_object(obj, false)
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
--- @param bulletObj Object
|
|
function hurt_amp(obj, bulletObj)
|
|
if gWeaponTable[obj_get_weapon_id(bulletObj)].strong then
|
|
play_sound(SOUND_GENERAL_BREAK_BOX, obj.header.gfx.cameraToObject)
|
|
spawn_triangle_break_particles(30, 138, 3, 4)
|
|
obj_mark_for_deletion(obj)
|
|
network_send_object(obj, false)
|
|
return true
|
|
else
|
|
bullet_ricochet(bulletObj)
|
|
play_sound(SOUND_ACTION_METAL_BONK, obj.header.gfx.cameraToObject)
|
|
end
|
|
return false
|
|
end
|
|
|
|
--- @param obj Object
|
|
function hurt_koopa(obj)
|
|
if obj.oKoopaMovementType == KOOPA_BP_UNSHELLED or obj.oKoopaMovementType == KOOPA_BP_NORMAL then
|
|
obj.oInteractStatus = ATTACK_KICK_OR_TRIP + (INT_STATUS_INTERACTED | INT_STATUS_WAS_ATTACKED)
|
|
network_send_object(obj, false)
|
|
end
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
function hurt_snufit(obj)
|
|
obj.oInteractStatus = ATTACK_PUNCH + (INT_STATUS_INTERACTED | INT_STATUS_WAS_ATTACKED)
|
|
network_send_object(obj, false)
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
function hurt_chuckya(obj)
|
|
obj_spawn_loot_yellow_coins(obj, 5, 20)
|
|
play_sound(SOUND_OBJ_CHUCKYA_DEATH, obj.header.gfx.cameraToObject)
|
|
obj_mark_for_deletion(obj)
|
|
network_send_object(obj, false)
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
function hurt_piranha_plant(obj)
|
|
obj.oAction = PIRANHA_PLANT_ACT_ATTACKED
|
|
stop_secondary_music(50)
|
|
network_send_object(obj, false)
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
function hurt_spindrift(obj)
|
|
obj.oInteractStatus = ATTACK_PUNCH + (INT_STATUS_INTERACTED | INT_STATUS_WAS_ATTACKED | INT_ATTACK_NOT_FROM_BELOW)
|
|
network_send_object(obj, false)
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
function hurt_mr_blizzard(obj)
|
|
play_sound(SOUND_OBJ_SNOWMAN_EXPLODE, obj.header.gfx.cameraToObject)
|
|
obj_mark_for_deletion(obj)
|
|
obj_spawn_loot_yellow_coins(obj, obj.oNumLootCoins, 20)
|
|
network_send_object(obj, false)
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
function hurt_scuttlebug(obj)
|
|
play_sound(SOUND_OBJ_DYING_ENEMY1, obj.header.gfx.cameraToObject)
|
|
obj_spawn_loot_yellow_coins(obj, obj.oNumLootCoins, 20)
|
|
obj_mark_for_deletion(obj)
|
|
network_send_object(obj, false)
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
function hurt_pokey(obj)
|
|
obj.oInteractStatus = ATTACK_PUNCH + (INT_STATUS_INTERACTED | INT_STATUS_WAS_ATTACKED)
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
function hurt_mr_i(obj)
|
|
obj.oAction = 3
|
|
network_send_object(obj, false)
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
--- @param bulletObj Object
|
|
function hurt_bully(obj, bulletObj)
|
|
obj.oAction = BULLY_ACT_KNOCKBACK
|
|
obj.oMoveAngleYaw = gMarioStates[network_local_index_from_global(obj_get_weapon_owner(bulletObj))].faceAngle.y
|
|
obj.oVelY = 30
|
|
obj.oForwardVel = if_then_else(gWeaponTable[obj_get_weapon_id(bulletObj)].strong, 30, 10)
|
|
obj.oInteractStatus = ATTACK_PUNCH + (INT_STATUS_INTERACTED | INT_STATUS_WAS_ATTACKED | INT_ATTACK_NOT_FROM_BELOW)
|
|
network_send_object(obj, false)
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
function hurt_moneybag(obj)
|
|
obj.oInteractStatus = INT_STATUS_INTERACTED | INT_STATUS_WAS_ATTACKED | INT_ATTACK_NOT_FROM_BELOW
|
|
network_send_object(obj, false)
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
--- @param bulletObj Object
|
|
function hurt_king_bobomb(obj, bulletObj)
|
|
if (obj.oGmHealth <= 0 or gWeaponTable[obj_get_weapon_id(bulletObj)].strong) and obj.oHealth > 0 then
|
|
obj.oGmHealth = HEALTH_KING_BOBOMB
|
|
obj.oMoveFlags = obj.oMoveFlags | OBJ_MOVE_LANDED
|
|
obj.oAction = 4
|
|
network_send_object(obj, true)
|
|
end
|
|
return true
|
|
end
|
|
|
|
--- @param obj Object
|
|
function hurt_bowser(obj)
|
|
if obj.oGmHealth <= 0 and obj.oHealth > 0 then
|
|
obj.oGmHealth = HEALTH_BOWSER
|
|
obj.oHealth = obj.oHealth - 1
|
|
if obj.oHealth <= 0 then
|
|
obj.oAction = 4
|
|
else
|
|
obj.oAction = 12
|
|
end
|
|
network_send_object(obj, true)
|
|
end
|
|
return true
|
|
end
|
|
|
|
|
|
--- @param bulletObj Object
|
|
function hurt_yoshi(_, bulletObj)
|
|
bullet_ricochet(bulletObj)
|
|
bulletObj.oAction = 2
|
|
|
|
djui_chat_message_create("\\#a0ffa0\\Yoshi\\#dcdcdc\\: no lol")
|
|
play_sound(SOUND_MENU_MESSAGE_APPEAR, gGlobalSoundSource)
|
|
return false
|
|
end
|
|
|
|
|
|
-------------
|
|
-- objects --
|
|
-------------
|
|
|
|
--- @param o Object
|
|
local function bhv_debug_indicator_init(o)
|
|
o.oFlags = OBJ_FLAG_UPDATE_GFX_POS_AND_ANGLE
|
|
obj_set_billboard(o)
|
|
cur_obj_scale(0.5)
|
|
end
|
|
|
|
local function bhv_debug_indicator_loop(o)
|
|
if o.oTimer > 90 then obj_mark_for_deletion(o) end
|
|
end
|
|
|
|
id_bhvDebugIndicator = hook_behavior(nil, OBJ_LIST_UNIMPORTANT, true, bhv_debug_indicator_init, bhv_debug_indicator_loop)
|
|
|
|
|
|
--- @param o Object
|
|
local function bhv_bullet_init(o)
|
|
o.oFlags = OBJ_FLAG_UPDATE_GFX_POS_AND_ANGLE
|
|
o.oGraphYOffset = if_then_else(obj_has_model_extended(o, E_MODEL_YELLOW_COIN) ~= 0, -5, 0)
|
|
o.hitboxRadius = 20
|
|
o.hitboxHeight = 20
|
|
o.hitboxDownOffset = 10
|
|
o.oWallHitboxRadius = 30
|
|
o.oIntangibleTimer = 0
|
|
|
|
vec3f_set(o.header.gfx.pos, o.oPosX, o.oPosY, o.oPosZ)
|
|
obj_set_billboard(o)
|
|
cur_obj_scale(gWeaponTable[obj_get_weapon_id(o)].bulletScale)
|
|
cur_obj_hide()
|
|
network_init_object(o, true, {})
|
|
end
|
|
|
|
--- @param o Object
|
|
local function bhv_bullet_loop(o)
|
|
if o.oTimer > 150 then
|
|
obj_mark_for_deletion(o)
|
|
return
|
|
end
|
|
|
|
if o.oTimer == 1 then cur_obj_unhide() end
|
|
|
|
local prevPos = { x = o.oPosX, y = o.oPosY, z = o.oPosZ }
|
|
if o.oVelY == 0 then
|
|
cur_obj_move_xz_using_fvel_and_yaw()
|
|
else
|
|
cur_obj_move_using_vel()
|
|
end
|
|
cur_obj_update_floor_and_resolve_wall_collisions(0)
|
|
|
|
local marioObj = gMarioStates[network_local_index_from_global(obj_get_weapon_owner(o))].marioObj
|
|
local shootableHitboxes = gShootableHitboxes -- localizing saves microseconds
|
|
-- loop through every shootable behavior
|
|
for behavior, hurt in pairs(shootableHitboxes) do
|
|
-- get the nearest object with the current shootable behavior
|
|
local target = obj_get_nearest_object_with_behavior_id(o, behavior)
|
|
if target ~= nil then
|
|
-- check if the bullet (o) intersects with the target object (target)
|
|
local shot = obj_check_hitbox_overlap(target, o)
|
|
-- if not, begin using the substep system to see if hitboxes overlap then
|
|
local weapon = gWeaponTable[obj_get_weapon_id(o)]
|
|
if not shot then
|
|
local bulletSteps = weapon.bulletSteps
|
|
for i = 0, bulletSteps do
|
|
local step = i / bulletSteps
|
|
-- go from the previous position of the bullet to the current one over bullet steps
|
|
local x = lerp(prevPos.x, o.oPosX, step)
|
|
local y = lerp(prevPos.y, o.oPosY, step)
|
|
local z = lerp(prevPos.z, o.oPosZ, step)
|
|
-- spawn_non_sync_object(id_bhvDebugIndicator, E_MODEL_RED_COIN_NO_SHADOW, x, y, z, nil)
|
|
-- check if the bullet (o) now intersects with target object (target)
|
|
shot = obj_check_hitbox_overlap_xyz(target, o, x, y, z)
|
|
-- hit target and break out of loop if so, otherwise continue
|
|
if shot and (marioObj._pointer ~= target._pointer or o.oAction > 0) then
|
|
target.oGmHealth = target.oGmHealth - weapon.damage
|
|
if hurt ~= nil and hurt(target, o) then
|
|
bullet_hit(o)
|
|
return
|
|
end
|
|
break
|
|
end
|
|
end
|
|
elseif marioObj._pointer ~= target._pointer or o.oAction > 0 then
|
|
target.oGmHealth = target.oGmHealth - weapon.damage
|
|
if hurt ~= nil and hurt(target, o) then
|
|
bullet_hit(o)
|
|
return
|
|
end
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
-- spawn_non_sync_object(id_bhvDebugIndicator, E_MODEL_YELLOW_COIN_NO_SHADOW, prevPos.x, prevPos.y, prevPos.z, nil)
|
|
-- spawn_non_sync_object(id_bhvDebugIndicator, E_MODEL_YELLOW_COIN_NO_SHADOW, o.oPosX, o.oPosY, o.oPosZ, nil)
|
|
|
|
local raycast = collision_find_surface_on_ray(prevPos.x, prevPos.y, prevPos.z, o.oPosX - prevPos.x, o.oPosY - prevPos.y, o.oPosZ - prevPos.z)
|
|
if raycast.surface ~= nil or (o.oMoveFlags & OBJ_MOVE_HIT_WALL) ~= 0 then
|
|
vec3f_to_object_pos(o, raycast.hitPos)
|
|
bullet_hit(o)
|
|
end
|
|
end
|
|
|
|
id_bhvBullet = hook_behavior(nil, OBJ_LIST_UNIMPORTANT, true, bhv_bullet_init, bhv_bullet_loop, "bhvGmBullet")
|
|
|
|
|
|
--- @param o Object
|
|
local function bhv_bullet_hole_init(o)
|
|
o.oFlags = OBJ_FLAG_UPDATE_GFX_POS_AND_ANGLE
|
|
end
|
|
|
|
--- @param o Object
|
|
local function bhv_bullet_hole_loop(o)
|
|
cur_obj_align_gfx_with_floor()
|
|
if o.oTimer == 450 then obj_mark_for_deletion(o) end
|
|
end
|
|
|
|
id_bhvBulletHole = hook_behavior(nil, OBJ_LIST_UNIMPORTANT, true, bhv_bullet_hole_init, bhv_bullet_hole_loop, "bhvGmBulletHole")
|
|
|
|
|
|
function spawn_bullets_player(x, y, z, count)
|
|
local weapon = cur_weapon()
|
|
if weapon == nil then return end
|
|
--- @type MarioState
|
|
local m = gMarioStates[0]
|
|
|
|
for _ = 1, count do
|
|
local spread = math.random() * weapon.spread
|
|
spawn_sync_object(
|
|
id_bhvBullet,
|
|
weapon.bulletModel,
|
|
x + spread, y + spread, z + spread,
|
|
--- @param o Object
|
|
function(o)
|
|
obj_set_weapon_params(o, gNetworkPlayers[0].globalIndex, weapon.id, 0, 0)
|
|
local enabled = get_first_person_enabled()
|
|
o.oFaceAngleYaw = if_then_else(enabled, m.area.camera.yaw + 0x8000, m.faceAngle.y)
|
|
o.oForwardVel = weapon.bulletSpeed
|
|
|
|
if enabled or (m.input & INPUT_FIRST_PERSON) ~= 0 then
|
|
-- thanks Peachy
|
|
local dx = m.area.camera.focus.x - m.area.camera.pos.x
|
|
local dy = m.area.camera.focus.y - m.area.camera.pos.y
|
|
local dz = m.area.camera.focus.z - m.area.camera.pos.z
|
|
local dv = math.sqrt(dx * dx + dy * dy + dz * dz)
|
|
o.oVelX = weapon.bulletSpeed * (dx / dv)
|
|
o.oVelY = weapon.bulletSpeed * (dy / dv)
|
|
o.oVelZ = weapon.bulletSpeed * (dz / dv)
|
|
end
|
|
end
|
|
)
|
|
end
|
|
end |