sm64coopdx/mods/gun-mod/b-bullet.lua

498 lines
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