Consistency/data/entities/animals/boss_limbs/boss_limbs_update.lua

351 lines
8.4 KiB
Lua
Raw Normal View History

2021-08-20 01:53:47 +00:00
dofile( "data/scripts/lib/coroutines.lua" )
dofile( "data/scripts/lib/utilities.lua" )
-- enum for changing C++ logic state. keep this in sync with the values in limbboss_system.cpp
MoveAroundNest = 0
FollowPlayer = 1
Escape = 2
DontMove = 3
local details_hidden = false
local is_dead = false
local did_wait = false
-- gather some data we're gonna reuse --------------
local herd_id = get_herd_id( GetUpdatedEntityID() )
local force_coeff_orig = component_get_value_float( GetUpdatedEntityID(), "PhysicsAIComponent", "force_coeff", 10.0 )
-- animate eyes and skull randomly -----------------
async_loop(function()
wait(120)
if details_hidden == false then
animate_random_detail()
end
end)
-- do various attack patterns -----------------
function phase0()
-- init
set_logic_state( FollowPlayer )
-- timeline
boss_wait(5 * 60)
choose_random_phase()
end
function phase1()
-- init
set_logic_state( DontMove )
-- open and shoot
expose_weak_spot()
circleshot()
boss_wait(50)
circleshot()
boss_wait(50)
circleshot()
-- keep the weak spot temporarily exposed
boss_wait(3 * 80)
-- close
hide_weak_spot()
boss_wait(10)
choose_random_phase()
end
function phase2()
-- init
set_logic_state( FollowPlayer )
-- timeline
spawn_minion()
boss_wait(30)
spawn_minion()
boss_wait(100)
choose_random_phase()
end
function phase3()
-- init
set_logic_state( FollowPlayer )
-- timeline
prepare_chase()
chase_start()
boss_wait(1 * 80)
chase_stop()
state = phase1
end
function phase4()
-- init
set_logic_state( DontMove )
-- open and shoot
expose_weak_spot()
homingshot()
boss_wait(40)
homingshot()
boss_wait(2 * 60)
-- close
hide_weak_spot()
boss_wait(10)
state = phase0
end
function choose_random_phase()
local r = math.random(0,4)
if r == 0 then state = phase0
elseif r == 1 then state = phase1
elseif r == 2 then state = phase2
elseif r == 3 then state = phase3
else state = phase4
end
end
-- helpers -----------------
function circleshot()
set_main_animation( "attack_ranged", "opened" )
boss_wait(15)
local this = GetUpdatedEntityID()
local pos_x, pos_y = EntityGetTransform( this )
local angle = 0
local amount = 8
local space = math.floor(360 / amount)
local speed = 230
for i=1,amount do
local vel_x = math.cos( math.rad(angle) ) * speed
local vel_y = math.sin( math.rad(angle) ) * speed
angle = angle + space
local orb = shoot_projectile( this, "data/entities/animals/boss_limbs/orb_boss_limbs.xml", pos_x, pos_y, vel_x, vel_y )
end
end
function homingshot()
set_main_animation( "attack_ranged", "opened" )
boss_wait(15)
local this = GetUpdatedEntityID()
local pos_x, pos_y = EntityGetTransform( this )
local vel_x = 0
local vel_y = -30
shoot_projectile( this, "data/entities/animals/boss_limbs/orb_pink_big.xml", pos_x, pos_y, vel_x, vel_y )
end
function spawn_minion()
-- check that we only have less than N minions
local existing_minion_count = 0
local existing_minions = EntityGetWithTag( "slimeshooter_boss_limbs" )
if ( #existing_minions > 0 ) then
existing_minion_count = #existing_minions
end
if existing_minion_count >= 2 then
return
end
-- spawn
local x, y = EntityGetTransform( GetUpdatedEntityID() )
SetRandomSeed( GameGetFrameNum(), x + y )
local slime = EntityLoad( "data/entities/animals/boss_limbs/slimeshooter_boss_limbs.xml", x, y )
edit_component( slime, "VelocityComponent", function(comp,vars)
local vel_x = Random(-90,90)
local vel_y = Random(-150,25)
ComponentSetValueVector2( comp, "mVelocity", vel_x, vel_y )
end)
end
function get_idle_animation_name()
return "stand"
end
function prepare_chase()
set_main_animation( "charge", get_idle_animation_name() )
boss_wait(40)
end
function chase_start()
local physics_ai = EntityGetFirstComponent( GetUpdatedEntityID(), "PhysicsAIComponent" )
ComponentSetValue( physics_ai, "force_coeff", tostring( force_coeff_orig * 5.0 ) )
local celleater = EntityGetFirstComponent( GetUpdatedEntityID(), "CellEaterComponent" )
ComponentSetValue( celleater, "eat_probability", tostring(100.0) )
end
function chase_stop()
local physics_ai = EntityGetFirstComponent( GetUpdatedEntityID(), "PhysicsAIComponent" )
ComponentSetValue( physics_ai, "force_coeff", tostring( force_coeff_orig ) )
local celleater = EntityGetFirstComponent( GetUpdatedEntityID(), "CellEaterComponent" )
ComponentSetValue( celleater, "eat_probability", tostring(0.0) )
end
function expose_weak_spot()
set_main_animation( "open", "opened" )
set_details_hidden( true )
boss_wait(10)
set_hitboxes_weak( true )
boss_wait(20)
end
function hide_weak_spot()
set_main_animation( "close", get_idle_animation_name() )
boss_wait(10)
set_hitboxes_weak( false )
boss_wait(30)
set_details_hidden( false )
end
function set_hitboxes_weak( weak_spot_exposed )
EntitySetComponentsWithTagEnabled( GetUpdatedEntityID(), "hitbox_weak_spot", weak_spot_exposed )
EntitySetComponentsWithTagEnabled( GetUpdatedEntityID(), "hitbox_default", weak_spot_exposed == false )
end
function set_main_animation( current_name, next_name )
local sprite = EntityGetFirstComponent( GetUpdatedEntityID(), "SpriteComponent" )
if ( sprite ~= nil ) then
animate_sprite( sprite, current_name, next_name )
end
end
function animate_random_detail()
local which = math.random(1, 3)
for_comps( "SpriteComponent", function(i,sprite)
if i == which + 1 then
animate_sprite_simple( sprite, "animate", "stand" )
end
end)
end
function set_details_hidden( is_hidden )
if is_hidden then
set_detail_animation( "invisible" )
else
set_detail_animation( "stand" )
end
details_hidden = is_hidden
end
function set_detail_animation( current_name )
for_comps( "SpriteComponent", function(i,sprite)
if i > 1 then
ComponentSetValue( sprite, "rect_animation", current_name )
end
end)
end
function animate_sprite( sprite, current_name, next_name )
GamePlayAnimation( GetUpdatedEntityID(), current_name, 0, next_name, 0 )
--ComponentSetValue( sprite, "rect_animation", current_name )
--ComponentSetValue( sprite, "next_rect_animation", next_name )
end
function animate_sprite_simple( sprite, current_name, next_name )
ComponentSetValue( sprite, "rect_animation", current_name )
ComponentSetValue( sprite, "next_rect_animation", next_name )
end
function set_logic_state( state )
local comp = EntityGetFirstComponent( GetUpdatedEntityID(), "LimbBossComponent" )
if( comp ~= nil ) then
ComponentSetValue( comp, "state", state )
end
end
function check_death()
local comp = EntityGetFirstComponent( GetUpdatedEntityID(), "DamageModelComponent" )
if( comp ~= nil ) then
--ComponentSetValue( comp, "hp", "-0.1" ) -- DEBUG: kill immediately
local hp = ComponentGetValueFloat( comp, "hp" )
if ( hp <= 0.0 ) then
-- disable the attack limb
local children = EntityGetAllChildren( GetUpdatedEntityID() )
for i,child in ipairs( children ) do
if EntityGetName( child ) == "limb_attacker" then
EntityKill( child )
end
end
-- run death sequence
set_hitboxes_weak( false )
set_details_hidden( true )
set_logic_state( DontMove )
set_main_animation( "death1", "death2" )
SetRandomSeed( GameGetFrameNum(), GameGetFrameNum() )
local rand = function() return Random( -10, 10 ) end
for i = 1,40 do
local x,y = EntityGetTransform( GetUpdatedEntityID() )
GameScreenshake( i * 1, x, y )
GameCreateParticle( "slime_green", x + rand(), y + rand(), 10, i * 5.5, i * 5.5, true, false )
if i > 20 then
GameCreateParticle( "gunpowder_unstable", x + rand(), y + rand(), 3, 40.0, 40.0, true, false )
end
wait( 3 )
end
-- kill
comp = EntityGetFirstComponent( GetUpdatedEntityID(), "DamageModelComponent" )
if( comp ~= nil ) then
ComponentSetValue( comp, "kill_now", "1" )
end
StatsLogPlayerKill( GetUpdatedEntityID() )
is_dead = true
AddFlagPersistent( "miniboss_limbs" )
return
end
end
end
function boss_wait( frames )
check_death()
wait( frames )
check_death()
did_wait = true
end
-- init --------------------------------------------
set_hitboxes_weak( false )
set_main_animation( get_idle_animation_name(), get_idle_animation_name() )
set_details_hidden( false )
-- run phase state machine -----------------
state = phase0
async_loop(function()
-- alive
if is_dead then
wait(60 * 10)
else
did_wait = false
state()
if did_wait == false then -- ensure the coroutine doesn't get stuck in an infinite loop if the states never wait
wait(1)
end
end
end)