2010-10-12 10:55:23 +02:00
/*
2019-08-03 19:43:48 +02:00
* This software is licensed under the terms of the MIT License .
2017-02-10 23:05:22 +01:00
* See COPYING for further information .
2011-03-05 19:03:32 +01:00
* - - -
2024-05-16 23:30:41 +02:00
* Copyright ( c ) 2011 - 2024 , Lukas Weber < laochailan @ web . de > .
* Copyright ( c ) 2012 - 2024 , Andrei Alexeyev < akari @ taisei - project . org > .
2010-10-12 10:55:23 +02:00
*/
# include "projectile.h"
# include "global.h"
2010-10-27 19:51:49 +02:00
# include "list.h"
2017-12-13 20:05:12 +01:00
# include "stageobjects.h"
2019-12-11 10:25:57 +01:00
# include "util/glm.h"
2023-07-30 06:13:09 +02:00
# include "stage.h"
2011-04-26 16:55:18 +02:00
2019-12-07 02:09:31 +01:00
static ht_ptr2int_t shader_sublayer_map ;
2019-01-04 23:59:39 +01:00
2017-11-10 21:49:16 +01:00
static ProjArgs defaults_proj = {
2018-02-06 07:19:25 +01:00
. sprite = " proj/ " ,
2017-11-10 21:49:16 +01:00
. dest = & global . projs ,
2019-03-28 17:42:07 +01:00
. type = PROJ_ENEMY ,
2018-07-30 09:04:09 +02:00
. damage_type = DMG_ENEMY_SHOT ,
2017-11-10 21:49:16 +01:00
. color = RGB ( 1 , 1 , 1 ) ,
2018-07-23 19:07:59 +02:00
. blend = BLEND_PREMUL_ALPHA ,
2018-04-12 16:08:48 +02:00
. shader = " sprite_bullet " ,
2018-04-13 21:13:48 +02:00
. layer = LAYER_BULLET ,
2017-11-10 21:49:16 +01:00
} ;
static ProjArgs defaults_part = {
2018-02-06 07:19:25 +01:00
. sprite = " part/ " ,
2017-11-10 21:49:16 +01:00
. dest = & global . particles ,
2019-03-28 17:42:07 +01:00
. type = PROJ_PARTICLE ,
2018-07-30 09:04:09 +02:00
. damage_type = DMG_UNDEFINED ,
2017-11-10 21:49:16 +01:00
. color = RGB ( 1 , 1 , 1 ) ,
2018-07-23 19:07:59 +02:00
. blend = BLEND_PREMUL_ALPHA ,
2019-12-11 10:25:57 +01:00
. shader = " sprite_particle " ,
2019-01-04 23:59:39 +01:00
. layer = LAYER_PARTICLE_MID ,
2017-11-10 21:49:16 +01:00
} ;
static void process_projectile_args ( ProjArgs * args , ProjArgs * defaults ) {
2018-05-02 06:46:48 +02:00
if ( args - > proto & & args - > proto - > process_args ) {
args - > proto - > process_args ( args - > proto , args ) ;
return ;
2017-11-10 21:49:16 +01:00
}
2018-05-02 06:46:48 +02:00
// TODO: move this stuff into prototypes along with the defaults?
2018-02-06 07:19:25 +01:00
if ( args - > sprite ) {
args - > sprite_ptr = prefix_get_sprite ( args - > sprite , defaults - > sprite ) ;
2017-11-10 21:49:16 +01:00
}
2018-04-12 16:08:48 +02:00
if ( ! args - > shader_ptr ) {
if ( args - > shader ) {
2020-06-09 03:33:22 +02:00
args - > shader_ptr = res_shader ( args - > shader ) ;
2018-04-12 16:08:48 +02:00
} else {
args - > shader_ptr = defaults - > shader_ptr ;
}
}
2019-12-11 10:25:57 +01:00
if ( ! args - > draw_rule . func ) {
args - > draw_rule = pdraw_basic ( ) ;
2017-11-10 21:49:16 +01:00
}
2018-04-12 16:08:48 +02:00
if ( ! args - > blend ) {
args - > blend = defaults - > blend ;
}
2017-11-10 21:49:16 +01:00
if ( ! args - > dest ) {
args - > dest = defaults - > dest ;
}
if ( ! args - > type ) {
args - > type = defaults - > type ;
}
if ( ! args - > color ) {
args - > color = defaults - > color ;
}
2021-02-18 18:03:47 +01:00
if ( ! args - > max_viewport_dist & & args - > type = = PROJ_PARTICLE ) {
// TODO consider a saner value?
2017-11-10 21:49:16 +01:00
args - > max_viewport_dist = 300 ;
}
2018-04-13 21:13:48 +02:00
if ( ! args - > layer ) {
2019-03-28 17:42:07 +01:00
if ( args - > type = = PROJ_PLAYER ) {
2018-04-13 21:13:48 +02:00
args - > layer = LAYER_PLAYER_SHOT ;
} else {
args - > layer = defaults - > layer ;
}
}
2018-07-30 09:04:09 +02:00
if ( args - > damage_type = = DMG_UNDEFINED ) {
args - > damage_type = defaults - > damage_type ;
2019-03-28 17:42:07 +01:00
if ( args - > type = = PROJ_PLAYER & & args - > damage_type = = DMG_ENEMY_SHOT ) {
2018-07-30 09:04:09 +02:00
args - > damage_type = DMG_PLAYER_SHOT ;
}
}
2019-12-11 10:25:57 +01:00
if ( args - > scale = = 0 ) {
args - > scale = 1 + I ;
2023-09-20 04:14:15 +02:00
} else if ( im ( args - > scale ) = = 0 ) {
args - > scale = CMPLXF ( re ( args - > scale ) , re ( args - > scale ) ) ;
2019-12-11 10:25:57 +01:00
}
if ( args - > opacity = = 0 ) {
args - > opacity = 1 ;
}
2019-03-28 17:42:07 +01:00
assert ( args - > type < = PROJ_PLAYER ) ;
2017-11-10 21:49:16 +01:00
}
2023-09-23 20:06:27 +02:00
static cmplx projectile_size ( Projectile * p ) {
cmplx r ;
if ( p - > type = = PROJ_PARTICLE & & LIKELY ( p - > sprite ! = NULL ) ) {
r = p - > sprite - > extent . as_cmplx ;
2017-11-23 16:27:41 +01:00
} else {
2023-09-23 20:06:27 +02:00
r = p - > size ;
2017-11-23 16:27:41 +01:00
}
2018-05-02 06:46:48 +02:00
2023-09-23 20:06:27 +02:00
assert ( re ( r ) > 0 ) ;
assert ( im ( r ) > 0 ) ;
return r ;
2018-05-02 06:46:48 +02:00
}
2023-02-24 01:44:54 +01:00
static Projectile * spawn_bullet_spawning_effect ( Projectile * p ) ;
2019-01-04 23:59:39 +01:00
2023-02-24 04:02:29 +01:00
// Returns true if projectile should be destroyed
static inline bool proj_update ( Projectile * p , int t ) {
bool destroy = false ;
2018-01-06 07:06:12 +01:00
2018-05-16 03:39:33 +02:00
if ( p - > timeout > 0 & & t > = p - > timeout ) {
2023-02-24 04:02:29 +01:00
destroy = true ;
2019-07-20 15:15:51 +02:00
} else if ( t > = 0 ) {
2019-09-01 22:27:11 +02:00
if ( ! ( p - > flags & PFLAG_NOMOVE ) ) {
move_update ( & p - > pos , & p - > move ) ;
}
2020-03-08 16:13:09 +01:00
if ( p - > flags & PFLAG_MANUALANGLE ) {
p - > angle + = p - > angle_delta ;
} else {
2019-12-18 15:19:13 +01:00
cmplx delta_pos = p - > pos - p - > prevpos ;
2019-07-20 15:15:51 +02:00
2019-09-01 22:27:11 +02:00
if ( delta_pos ) {
2023-09-23 20:08:18 +02:00
real angle ;
if ( p - > _cached_delta_pos = = delta_pos ) {
angle = p - > _cached_angle ;
} else {
angle = carg ( delta_pos ) ;
p - > _cached_delta_pos = delta_pos ;
p - > _cached_angle = angle ;
}
p - > angle = angle + p - > angle_delta ;
2019-09-01 22:27:11 +02:00
}
2019-07-20 15:15:51 +02:00
}
2018-05-16 03:39:33 +02:00
}
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
2023-05-28 02:08:19 +02:00
if ( t = = 1 ) {
2023-02-24 01:44:54 +01:00
// FIXME: not sure if this should happen before or after move_update,
// or maybe even directly at spawn.
2019-01-04 23:59:39 +01:00
spawn_bullet_spawning_effect ( p ) ;
}
2023-02-24 04:02:29 +01:00
return destroy ;
2017-11-23 16:27:41 +01:00
}
2018-05-02 06:46:48 +02:00
void projectile_set_prototype ( Projectile * p , ProjPrototype * proto ) {
if ( p - > proto & & p - > proto - > deinit_projectile ) {
p - > proto - > deinit_projectile ( p - > proto , p ) ;
}
if ( proto & & proto - > init_projectile ) {
proto - > init_projectile ( proto , p ) ;
}
p - > proto = proto ;
2018-01-08 17:56:46 +01:00
}
2019-11-22 04:37:11 +01:00
cmplx projectile_graze_size ( Projectile * p ) {
2019-03-28 17:40:04 +01:00
if (
2019-03-28 17:42:07 +01:00
p - > type = = PROJ_ENEMY & &
2019-03-28 17:40:04 +01:00
! ( p - > flags & ( PFLAG_NOGRAZE | PFLAG_NOCOLLISION ) ) & &
p - > graze_counter < 3 & &
global . frames > = p - > graze_cooldown
) {
2023-09-23 20:06:27 +02:00
real scale = 420.0 /* graze it */ / ( 2 * p - > graze_counter + 1 ) ;
cmplx s = scale * p - > size ;
return CMPLX ( sqrt ( re ( s ) ) , sqrt ( im ( s ) ) ) ;
2018-05-13 15:08:58 +02:00
}
return 0 ;
}
2020-05-09 08:25:38 +02:00
float projectile_timeout_factor ( Projectile * p ) {
2019-12-11 10:25:57 +01:00
return p - > timeout ? ( global . frames - p - > birthtime ) / p - > timeout : 0 ;
}
2018-08-12 02:10:21 +02:00
static double projectile_rect_area ( Projectile * p ) {
2023-09-23 20:06:27 +02:00
cmplx s = projectile_size ( p ) ;
return re ( s ) * im ( s ) ;
2018-08-12 02:10:21 +02:00
}
2019-12-11 10:25:57 +01:00
void projectile_set_layer ( Projectile * p , drawlayer_t layer ) {
if ( ! ( layer & LAYER_LOW_MASK ) ) {
drawlayer_low_t sublayer ;
switch ( p - > type ) {
case PROJ_ENEMY :
// 1. Large projectiles go below smaller ones.
sublayer = LAYER_LOW_MASK - ( drawlayer_low_t ) projectile_rect_area ( p ) ;
sublayer = ( sublayer < < 4 ) & LAYER_LOW_MASK ;
// 2. Group by shader (hardcoded precedence).
sublayer | = ht_get ( & shader_sublayer_map , p - > shader , 0 ) & 0xf ;
// If specific blending order is required, then you should set up the sublayer manually.
layer | = sublayer ;
break ;
case PROJ_PARTICLE :
// 1. Group by shader (hardcoded precedence).
sublayer = ht_get ( & shader_sublayer_map , p - > shader , 0 ) & 0xf ;
sublayer < < = 4 ;
sublayer | = 0x100 ;
// If specific blending order is required, then you should set up the sublayer manually.
layer | = sublayer ;
break ;
default :
break ;
}
}
p - > ent . draw_layer = layer ;
}
2023-02-24 01:44:54 +01:00
static void ent_draw_projectile ( EntityInterface * ent ) ;
2017-11-10 21:49:16 +01:00
static Projectile * _create_projectile ( ProjArgs * args ) {
2017-11-15 07:05:47 +01:00
if ( IN_DRAW_CODE ) {
log_fatal ( " Tried to spawn a projectile while in drawing code " ) ;
}
2024-06-05 21:58:58 +02:00
auto p = STAGE_ACQUIRE_OBJ ( Projectile ) ;
2017-02-10 23:05:22 +01:00
2011-03-07 07:26:43 +01:00
p - > birthtime = global . frames ;
2018-05-13 15:08:58 +02:00
p - > pos = p - > pos0 = p - > prevpos = args - > pos ;
2017-11-10 21:49:16 +01:00
p - > angle = args - > angle ;
2020-03-08 16:13:09 +01:00
p - > angle_delta = args - > angle_delta ;
2017-11-10 21:49:16 +01:00
p - > draw_rule = args - > draw_rule ;
2018-04-12 16:08:48 +02:00
p - > shader = args - > shader_ptr ;
p - > blend = args - > blend ;
2018-02-06 07:19:25 +01:00
p - > sprite = args - > sprite_ptr ;
2017-11-10 21:49:16 +01:00
p - > type = args - > type ;
2018-07-23 19:07:59 +02:00
p - > color = * args - > color ;
2017-11-10 21:49:16 +01:00
p - > max_viewport_dist = args - > max_viewport_dist ;
p - > size = args - > size ;
2018-05-13 15:08:58 +02:00
p - > collision_size = args - > collision_size ;
2017-11-10 21:49:16 +01:00
p - > flags = args - > flags ;
2018-05-02 06:46:48 +02:00
p - > timeout = args - > timeout ;
2018-07-30 09:04:09 +02:00
p - > damage = args - > damage ;
p - > damage_type = args - > damage_type ;
2019-02-22 00:56:03 +01:00
p - > clear_flags = 0 ;
2019-07-20 15:15:51 +02:00
p - > move = args - > move ;
2019-12-11 10:25:57 +01:00
p - > scale = args - > scale ;
p - > opacity = args - > opacity ;
2018-07-23 19:07:59 +02:00
2023-09-23 20:08:18 +02:00
p - > _cached_angle = p - > angle ;
2018-05-02 06:46:48 +02:00
p - > ent . draw_func = ent_draw_projectile ;
projectile_set_prototype ( p , args - > proto ) ;
2018-08-11 21:13:48 +02:00
2018-05-13 15:08:58 +02:00
// p->collision_size *= 10;
// p->size *= 5;
2018-05-02 06:46:48 +02:00
2023-09-20 04:14:15 +02:00
if ( ( p - > type = = PROJ_ENEMY | | p - > type = = PROJ_PLAYER ) & & ( re ( p - > size ) < = 0 | | im ( p - > size ) < = 0 ) ) {
log_fatal ( " Tried to spawn a projectile with invalid size %f x %f " , re ( p - > size ) , im ( p - > size ) ) ;
2018-05-02 06:46:48 +02:00
}
2018-04-13 21:13:48 +02:00
2019-12-11 10:25:57 +01:00
projectile_set_layer ( p , args - > layer ) ;
2018-08-12 02:10:21 +02:00
2023-01-06 04:11:13 +01:00
if ( ! ( p - > flags & ( PFLAG_MANUALANGLE | PFLAG_NOMOVE ) ) ) {
p - > angle = carg ( p - > move . velocity ) + p - > angle_delta ;
}
2020-02-23 08:29:42 +01:00
COEVENT_INIT_ARRAY ( p - > events ) ;
2020-04-17 09:18:53 +02:00
ent_register ( & p - > ent , ENT_TYPE_ID ( Projectile ) ) ;
2019-01-04 23:59:39 +01:00
alist_append ( args - > dest , p ) ;
2017-10-20 20:31:21 +02:00
2019-01-04 23:59:39 +01:00
return p ;
2010-10-12 10:55:23 +02:00
}
2017-11-10 21:49:16 +01:00
Projectile * create_projectile ( ProjArgs * args ) {
process_projectile_args ( args , & defaults_proj ) ;
return _create_projectile ( args ) ;
}
Projectile * create_particle ( ProjArgs * args ) {
process_projectile_args ( args , & defaults_part ) ;
return _create_projectile ( args ) ;
}
# ifdef PROJ_DEBUG
2017-11-23 16:27:41 +01:00
Projectile * _proj_attach_dbginfo ( Projectile * p , DebugInfo * dbg , const char * callsite_str ) {
// log_debug("Spawn: [%s]", callsite_str);
2017-11-15 07:05:47 +01:00
memcpy ( & p - > debug , dbg , sizeof ( DebugInfo ) ) ;
set_debug_info ( dbg ) ;
2017-11-10 21:49:16 +01:00
return p ;
}
# endif
2021-02-18 19:06:16 +01:00
static void signal_event_with_collision_result ( Projectile * p , CoEvent * evt , ProjCollisionResult * col ) {
assert ( p - > collision = = NULL ) ;
p - > collision = col ;
coevent_signal ( evt ) ;
assert ( p - > collision = = col ) ;
p - > collision = NULL ;
}
static void delete_projectile ( ProjectileList * projlist , Projectile * p , ProjCollisionResult * col ) {
signal_event_with_collision_result ( p , & p - > events . killed , col ) ;
2020-02-23 08:29:42 +01:00
COEVENT_CANCEL_ARRAY ( p - > events ) ;
2018-04-13 21:13:48 +02:00
ent_unregister ( & p - > ent ) ;
2024-06-05 21:58:58 +02:00
STAGE_RELEASE_OBJ ( alist_unlink ( projlist , p ) ) ;
2011-04-26 22:39:50 +02:00
}
2021-02-18 19:06:16 +01:00
static void * foreach_delete_projectile ( ListAnchor * projlist , List * proj , void * arg ) {
delete_projectile ( ( ProjectileList * ) projlist , ( Projectile * ) proj , arg ) ;
return NULL ;
2010-10-12 10:55:23 +02:00
}
2018-06-01 20:40:18 +02:00
void delete_projectiles ( ProjectileList * projlist ) {
2021-02-18 19:06:16 +01:00
alist_foreach ( projlist , foreach_delete_projectile , NULL ) ;
2010-10-12 10:55:23 +02:00
}
2017-12-28 10:05:54 +01:00
void calc_projectile_collision ( Projectile * p , ProjCollisionResult * out_col ) {
out_col - > type = PCOL_NONE ;
out_col - > entity = NULL ;
out_col - > fatal = false ;
out_col - > location = p - > pos ;
2018-07-30 09:04:09 +02:00
out_col - > damage . amount = p - > damage ;
out_col - > damage . type = p - > damage_type ;
2017-12-28 10:05:54 +01:00
2019-03-26 12:57:01 +01:00
if ( p - > flags & PFLAG_NOCOLLISION ) {
goto skip_collision ;
}
2019-03-28 17:42:07 +01:00
if ( p - > type = = PROJ_ENEMY ) {
2018-05-13 15:08:58 +02:00
Ellipse e_proj = {
. axes = p - > collision_size ,
. angle = p - > angle + M_PI / 2 ,
} ;
2017-11-01 05:47:36 +01:00
2018-05-13 15:08:58 +02:00
LineSegment seg = {
. a = global . plr . pos - global . plr . velocity - p - > prevpos ,
. b = global . plr . pos - p - > pos
} ;
2017-02-10 23:05:22 +01:00
2022-01-12 13:59:00 +01:00
# ifdef DEBUG
attr_unused real seglen2 = cabs2 ( seg . a - seg . b ) ;
2018-05-16 03:39:33 +02:00
2022-01-12 13:59:00 +01:00
if ( seglen2 > 30 * 30 ) {
attr_unused real seglen = sqrt ( seglen2 ) ;
2018-05-16 03:39:33 +02:00
log_debug (
seglen > VIEWPORT_W
2019-05-06 12:22:49 +02:00
? " Lerp over HUGE distance %f; this is ABSOLUTELY a bug! Player speed was %f. Spawned at %s:%d (%s); proj time = %d "
: " Lerp over large distance %f; this is either a bug or a very fast projectile, investigate. Player speed was %f. Spawned at %s:%d (%s); proj time = %d " ,
2018-05-16 03:39:33 +02:00
seglen ,
cabs ( global . plr . velocity ) ,
p - > debug . file ,
p - > debug . line ,
2019-05-06 12:22:49 +02:00
p - > debug . func ,
global . frames - p - > birthtime
2018-05-16 03:39:33 +02:00
) ;
}
2022-01-12 13:59:00 +01:00
# endif
2018-05-16 03:39:33 +02:00
2018-05-13 15:08:58 +02:00
if ( lineseg_ellipse_intersect ( seg , e_proj ) ) {
2018-07-30 09:04:09 +02:00
out_col - > type = PCOL_ENTITY ;
out_col - > entity = & global . plr . ent ;
2020-04-08 07:38:29 +02:00
out_col - > fatal = ! ( p - > flags & PFLAG_INDESTRUCTIBLE ) ;
2018-05-13 15:08:58 +02:00
} else {
e_proj . axes = projectile_graze_size ( p ) ;
2023-09-20 04:14:15 +02:00
if ( re ( e_proj . axes ) > 1 & & lineseg_ellipse_intersect ( seg , e_proj ) ) {
2018-05-13 15:08:58 +02:00
out_col - > type = PCOL_PLAYER_GRAZE ;
2018-07-30 09:04:09 +02:00
out_col - > entity = & global . plr . ent ;
2019-01-04 23:59:39 +01:00
out_col - > location = p - > pos ;
2018-05-13 15:08:58 +02:00
}
2012-08-12 21:29:10 +02:00
}
2019-03-28 17:42:07 +01:00
} else if ( p - > type = = PROJ_PLAYER ) {
2018-06-01 20:40:18 +02:00
for ( Enemy * e = global . enemies . first ; e ; e = e - > next ) {
2022-01-12 13:59:00 +01:00
if (
! ( e - > flags & EFLAG_NO_HIT ) & &
cabs2 ( e - > pos - p - > pos ) < e - > hit_radius * e - > hit_radius
) {
2018-07-30 09:04:09 +02:00
out_col - > type = PCOL_ENTITY ;
out_col - > entity = & e - > ent ;
2020-04-08 07:38:29 +02:00
out_col - > fatal = ! ( p - > flags & PFLAG_INDESTRUCTIBLE ) ;
2017-03-06 13:02:46 +01:00
2017-12-28 10:05:54 +01:00
return ;
2010-10-17 10:39:07 +02:00
}
}
2017-02-10 23:05:22 +01:00
2022-01-12 13:59:00 +01:00
if (
global . boss & &
boss_is_vulnerable ( global . boss ) & &
cabs2 ( global . boss - > pos - p - > pos ) < 42 * 42
) {
out_col - > type = PCOL_ENTITY ;
out_col - > entity = & global . boss - > ent ;
out_col - > fatal = ! ( p - > flags & PFLAG_INDESTRUCTIBLE ) ;
2017-12-28 10:05:54 +01:00
}
}
2019-03-26 12:57:01 +01:00
skip_collision :
2022-01-12 13:59:00 +01:00
if (
out_col - > type = = PCOL_NONE & &
! ( p - > flags & PFLAG_NOAUTOREMOVE ) & &
! projectile_in_viewport ( p )
) {
2017-12-28 10:05:54 +01:00
out_col - > type = PCOL_VOID ;
out_col - > fatal = true ;
}
}
2019-01-26 02:54:57 +01:00
void apply_projectile_collision ( ProjectileList * projlist , Projectile * p , ProjCollisionResult * col ) {
2021-02-18 19:06:16 +01:00
signal_event_with_collision_result ( p , & p - > events . collision , col ) ;
2017-12-28 10:05:54 +01:00
switch ( col - > type ) {
2018-08-11 21:13:48 +02:00
case PCOL_NONE :
case PCOL_VOID :
2017-12-28 10:05:54 +01:00
break ;
case PCOL_PLAYER_GRAZE : {
2019-03-28 17:48:48 +01:00
player_graze ( ENT_CAST ( col - > entity , Player ) , col - > location , 10 + 10 * p - > graze_counter , 3 + p - > graze_counter , & p - > color ) ;
2018-01-06 10:44:27 +01:00
2018-05-13 15:08:58 +02:00
p - > graze_counter + + ;
2019-02-22 00:56:03 +01:00
p - > graze_cooldown = global . frames + 12 ;
2018-05-13 15:08:58 +02:00
p - > graze_counter_reset_timer = global . frames ;
2017-12-28 10:05:54 +01:00
break ;
}
2018-07-30 09:04:09 +02:00
case PCOL_ENTITY : {
2018-08-11 21:13:48 +02:00
ent_damage ( col - > entity , & col - > damage ) ;
2017-12-28 10:05:54 +01:00
break ;
}
2018-08-11 21:13:48 +02:00
default :
UNREACHABLE ;
2010-10-17 10:39:07 +02:00
}
2017-12-28 10:05:54 +01:00
if ( col - > fatal ) {
2021-02-18 19:06:16 +01:00
delete_projectile ( projlist , p , col ) ;
2017-12-28 10:05:54 +01:00
}
2010-10-17 09:27:44 +02:00
}
2018-04-13 21:13:48 +02:00
static void ent_draw_projectile ( EntityInterface * ent ) {
Projectile * proj = ENT_CAST ( ent , Projectile ) ;
2018-07-23 19:07:59 +02:00
r_blend ( proj - > blend ) ;
2018-04-12 16:08:48 +02:00
r_shader_ptr ( proj - > shader ) ;
2018-01-11 08:33:51 +01:00
2017-11-15 07:05:47 +01:00
# ifdef PROJ_DEBUG
static Projectile prev_state ;
memcpy ( & prev_state , proj , sizeof ( Projectile ) ) ;
2019-12-11 10:25:57 +01:00
proj - > draw_rule . func ( proj , global . frames - proj - > birthtime , proj - > draw_rule . args ) ;
2017-10-24 04:57:14 +02:00
2017-11-15 07:05:47 +01:00
if ( memcmp ( & prev_state , proj , sizeof ( Projectile ) ) ) {
set_debug_info ( & proj - > debug ) ;
log_fatal ( " Projectile modified its state in draw rule " ) ;
}
# else
2019-12-11 10:25:57 +01:00
proj - > draw_rule . func ( proj , global . frames - proj - > birthtime , proj - > draw_rule . args ) ;
2017-11-10 21:49:16 +01:00
# endif
}
bool projectile_in_viewport ( Projectile * proj ) {
2023-09-23 20:06:27 +02:00
real e = proj - > max_viewport_dist ;
cmplx size = projectile_size ( proj ) ;
cmplx buffer = 0.5 * size + CMPLX ( e , e ) ;
cmplx pos = proj - > pos ;
cmplx br = pos + buffer ;
if ( re ( br ) < 0 | | im ( br ) < 0 ) {
return false ;
}
cmplx tl = pos - buffer ;
if ( re ( tl ) > VIEWPORT_W | | im ( tl ) > VIEWPORT_H ) {
return false ;
}
2017-11-01 05:47:36 +01:00
2023-09-23 20:06:27 +02:00
return true ;
2017-10-24 04:57:14 +02:00
}
2021-02-18 19:06:16 +01:00
Projectile * spawn_projectile_collision_effect ( Projectile * proj ) {
2018-01-20 11:18:11 +01:00
if ( proj - > flags & PFLAG_NOCOLLISIONEFFECT ) {
return NULL ;
}
2019-01-04 23:59:39 +01:00
2018-02-06 07:19:25 +01:00
if ( proj - > sprite = = NULL ) {
2018-01-06 10:24:46 +01:00
return NULL ;
}
return PARTICLE (
2018-02-06 07:19:25 +01:00
. sprite_ptr = proj - > sprite ,
2018-05-02 06:46:48 +02:00
. size = proj - > size ,
2018-01-06 10:24:46 +01:00
. pos = proj - > pos ,
2018-07-23 19:07:59 +02:00
. color = & proj - > color ,
2019-01-04 23:59:39 +01:00
. flags = proj - > flags | PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE ,
2019-04-15 07:37:01 +02:00
. layer = LAYER_PARTICLE_HIGH ,
2018-04-12 16:08:48 +02:00
. shader_ptr = proj - > shader ,
2020-04-17 18:31:37 +02:00
. draw_rule = pdraw_timeout_scale ( 2 + I , 0.0001 + I ) ,
2018-01-20 11:25:38 +01:00
. angle = proj - > angle ,
2022-01-12 13:59:00 +01:00
. move = { . velocity = 5 * cdir ( proj - > angle ) , . retention = 0.95 } ,
2018-05-02 06:46:48 +02:00
. timeout = 10 ,
2018-01-06 10:24:46 +01:00
) ;
}
2019-01-26 02:54:57 +01:00
static void really_clear_projectile ( ProjectileList * projlist , Projectile * proj ) {
2019-12-27 16:59:15 +01:00
spawn_projectile_clear_effect ( proj ) ;
2018-08-05 19:58:50 +02:00
2019-01-26 02:54:57 +01:00
if ( ! ( proj - > flags & PFLAG_NOCLEARBONUS ) ) {
2019-12-27 16:59:15 +01:00
create_clear_item ( proj - > pos , proj - > clear_flags ) ;
2019-01-26 02:54:57 +01:00
}
2019-01-04 23:59:39 +01:00
2021-02-18 19:06:16 +01:00
// TODO: synthetic collision type for clears?
delete_projectile ( projlist , proj , NULL ) ;
2019-01-26 02:54:57 +01:00
}
2019-01-04 23:59:39 +01:00
2019-02-22 00:56:03 +01:00
bool clear_projectile ( Projectile * proj , uint flags ) {
switch ( proj - > type ) {
2019-03-28 17:42:07 +01:00
case PROJ_PLAYER :
case PROJ_PARTICLE :
2019-02-22 00:56:03 +01:00
return false ;
default : break ;
}
if ( ! ( flags & CLEAR_HAZARDS_FORCE ) & & ! projectile_is_clearable ( proj ) ) {
2019-01-26 02:54:57 +01:00
return false ;
}
2018-01-06 10:29:14 +01:00
2019-03-28 17:42:07 +01:00
proj - > type = PROJ_DEAD ;
2019-02-22 00:56:03 +01:00
proj - > clear_flags | = flags ;
2020-02-23 08:29:42 +01:00
coevent_signal_once ( & proj - > events . cleared ) ;
2018-01-06 10:24:46 +01:00
2018-01-06 19:23:38 +01:00
return true ;
2018-01-06 10:24:46 +01:00
}
2021-02-18 19:06:16 +01:00
void kill_projectile ( Projectile * proj ) {
2019-07-19 11:48:22 +02:00
proj - > flags | = PFLAG_INTERNAL_DEAD | PFLAG_NOCOLLISION | PFLAG_NOCLEAR ;
2019-12-11 10:25:57 +01:00
proj - > ent . draw_layer = LAYER_NODRAW ;
2021-02-18 19:06:16 +01:00
assert ( proj - > collision = = NULL ) ;
2019-12-11 10:25:57 +01:00
// WARNING: must be done last, an event handler may cancel the task this function is running in!
coevent_signal_once ( & proj - > events . killed ) ;
2019-07-19 11:48:22 +02:00
}
2018-06-01 20:40:18 +02:00
void process_projectiles ( ProjectileList * projlist , bool collision ) {
2017-12-28 10:05:54 +01:00
ProjCollisionResult col = { 0 } ;
2019-02-22 00:56:03 +01:00
bool stage_cleared = stage_is_cleared ( ) ;
2017-12-28 10:05:54 +01:00
2019-01-26 02:54:57 +01:00
for ( Projectile * proj = projlist - > first , * next ; proj ; proj = next ) {
next = proj - > next ;
2019-02-22 00:56:03 +01:00
2019-07-19 11:48:22 +02:00
if ( proj - > flags & PFLAG_INTERNAL_DEAD ) {
2021-02-18 19:06:16 +01:00
delete_projectile ( projlist , proj , NULL ) ;
2019-07-19 11:48:22 +02:00
continue ;
}
2019-02-22 00:56:03 +01:00
if ( stage_cleared ) {
clear_projectile ( proj , CLEAR_HAZARDS_BULLETS | CLEAR_HAZARDS_FORCE ) ;
}
2023-02-24 04:02:29 +01:00
bool destroy = proj_update ( proj , global . frames - proj - > birthtime ) ;
2017-02-10 23:05:22 +01:00
2018-05-13 15:08:58 +02:00
if ( proj - > graze_counter & & proj - > graze_counter_reset_timer - global . frames < = - 90 ) {
proj - > graze_counter - - ;
proj - > graze_counter_reset_timer = global . frames ;
}
2019-12-27 16:59:15 +01:00
if ( proj - > type = = PROJ_DEAD & & ! ( proj - > clear_flags & CLEAR_HAZARDS_NOW ) ) {
2019-02-22 00:56:03 +01:00
proj - > clear_flags | = CLEAR_HAZARDS_NOW ;
2011-05-13 19:03:02 +02:00
}
2017-02-10 23:05:22 +01:00
2023-02-24 04:02:29 +01:00
if ( destroy ) {
2019-03-28 17:25:55 +01:00
memset ( & col , 0 , sizeof ( col ) ) ;
col . fatal = true ;
} else if ( collision ) {
2017-12-28 10:05:54 +01:00
calc_projectile_collision ( proj , & col ) ;
2017-12-31 10:23:57 +01:00
if ( col . fatal & & col . type ! = PCOL_VOID ) {
2018-01-06 10:24:46 +01:00
spawn_projectile_collision_effect ( proj ) ;
2017-12-28 10:05:54 +01:00
}
} else {
memset ( & col , 0 , sizeof ( col ) ) ;
2017-02-10 23:05:22 +01:00
2020-04-08 07:38:29 +02:00
if ( ! ( proj - > flags & PFLAG_NOAUTOREMOVE ) & & ! projectile_in_viewport ( proj ) ) {
2017-12-28 10:05:54 +01:00
col . fatal = true ;
}
}
2017-02-10 23:05:22 +01:00
2023-08-17 01:22:29 +02:00
proj - > prevpos = proj - > pos ;
2019-01-26 02:54:57 +01:00
apply_projectile_collision ( projlist , proj , & col ) ;
}
for ( Projectile * proj = projlist - > first , * next ; proj ; proj = next ) {
next = proj - > next ;
2019-03-28 17:42:07 +01:00
if ( proj - > type = = PROJ_DEAD & & ( proj - > clear_flags & CLEAR_HAZARDS_NOW ) ) {
2019-01-26 02:54:57 +01:00
really_clear_projectile ( projlist , proj ) ;
}
2010-10-27 19:51:49 +02:00
}
2010-10-12 10:55:23 +02:00
}
2017-12-28 11:40:40 +01:00
int trace_projectile ( Projectile * p , ProjCollisionResult * out_col , ProjCollisionType stopflags , int timeofs ) {
int t ;
2021-06-18 15:11:00 +02:00
for ( t = timeofs ; ; + + t ) {
2023-02-24 04:02:29 +01:00
bool destroy = proj_update ( p , t ) ;
2017-12-28 10:05:54 +01:00
calc_projectile_collision ( p , out_col ) ;
2017-11-01 05:47:36 +01:00
2023-02-24 04:02:29 +01:00
if ( out_col - > type & stopflags | | destroy ) {
2017-12-28 11:40:40 +01:00
return t ;
2017-11-01 05:47:36 +01:00
}
}
}
2011-05-21 15:02:19 +02:00
2018-01-06 09:54:13 +01:00
bool projectile_is_clearable ( Projectile * p ) {
2019-03-28 17:42:07 +01:00
if ( p - > type = = PROJ_DEAD ) {
2018-01-06 09:54:13 +01:00
return true ;
}
2019-03-28 17:42:07 +01:00
if ( p - > type = = PROJ_ENEMY ) {
2018-01-06 09:54:13 +01:00
return ( p - > flags & PFLAG_NOCLEAR ) ! = PFLAG_NOCLEAR ;
}
return false ;
}
2019-12-11 10:25:57 +01:00
int projectile_time ( Projectile * p ) {
return global . frames - p - > birthtime ;
}
2019-01-04 23:59:39 +01:00
static inline bool proj_uses_spawning_effect ( Projectile * proj , ProjFlags effect_flag ) {
2019-03-28 17:42:07 +01:00
if ( proj - > type ! = PROJ_ENEMY ) {
2019-01-04 23:59:39 +01:00
return false ;
}
2019-03-28 17:40:04 +01:00
if ( ( proj - > flags & effect_flag ) = = effect_flag ) {
2019-01-04 23:59:39 +01:00
return false ;
}
return true ;
}
static float proj_spawn_effect_factor ( Projectile * proj , int t ) {
static const int maxt = 16 ;
if ( t > = maxt | | ! proj_uses_spawning_effect ( proj , PFLAG_NOSPAWNFADE ) ) {
return 1 ;
}
return t / ( float ) maxt ;
}
2019-12-11 10:25:57 +01:00
static void bullet_highlight_draw ( Projectile * p , int t , ProjDrawRuleArgs args ) {
2019-01-04 23:59:39 +01:00
float timefactor = t / p - > timeout ;
2019-12-11 10:25:57 +01:00
float sx = args [ 0 ] . as_float [ 0 ] ;
float sy = args [ 0 ] . as_float [ 1 ] ;
float tex_angle = args [ 1 ] . as_float [ 0 ] ;
2019-01-04 23:59:39 +01:00
2020-11-05 09:18:19 +01:00
float opacity = pow ( 1 - timefactor , 3 ) ;
2023-09-23 21:56:34 +02:00
opacity = min ( 1 , 1.5 * opacity ) * min ( 1 , timefactor * 10 ) ;
2019-12-11 10:25:57 +01:00
opacity * = p - > opacity ;
2019-01-04 23:59:39 +01:00
2020-11-05 09:18:19 +01:00
r_mat_mv_push ( ) ;
2023-09-20 04:14:15 +02:00
r_mat_mv_translate ( re ( p - > pos ) , im ( p - > pos ) , 0 ) ;
2020-11-05 09:18:19 +01:00
r_mat_mv_rotate ( p - > angle + M_PI * 0.5 , 0 , 0 , 1 ) ;
r_mat_mv_scale ( sx , sy , 1 ) ;
r_mat_mv_rotate ( tex_angle , 0 , 0 , 1 ) ;
2019-01-04 23:59:39 +01:00
r_draw_sprite ( & ( SpriteParams ) {
. sprite_ptr = p - > sprite ,
. shader_ptr = p - > shader ,
2019-12-11 10:25:57 +01:00
. shader_params = & ( ShaderCustomParams ) { { opacity } } ,
2019-01-04 23:59:39 +01:00
. color = & p - > color ,
} ) ;
2020-11-05 09:18:19 +01:00
r_mat_mv_pop ( ) ;
2019-01-04 23:59:39 +01:00
}
static Projectile * spawn_projectile_highlight_effect_internal ( Projectile * p , bool flare ) {
if ( ! p - > sprite ) {
return NULL ;
2017-11-10 21:49:16 +01:00
}
2017-04-20 18:37:46 +02:00
2019-01-04 23:59:39 +01:00
Color clr = p - > color ;
2023-09-23 21:56:34 +02:00
clr . r = max ( 0.1f , clr . r ) ;
clr . g = max ( 0.1f , clr . g ) ;
clr . b = max ( 0.1f , clr . b ) ;
2019-01-04 23:59:39 +01:00
float h , s , l ;
color_get_hsl ( & clr , & h , & s , & l ) ;
s = s > 0 ? 0.75 : 0 ;
l = 0.5 ;
color_hsla ( & clr , h , s , l , 0.05 ) ;
float sx , sy ;
if ( flare ) {
sx = pow ( p - > sprite - > w , 0.7 ) ;
sy = pow ( p - > sprite - > h , 0.7 ) ;
2019-12-07 02:09:31 +01:00
RNG_ARRAY ( R , 5 ) ;
2019-01-04 23:59:39 +01:00
PARTICLE (
. sprite = " stardust_green " ,
. shader = " sprite_bullet " ,
. size = p - > size * 4.5 ,
. layer = LAYER_PARTICLE_HIGH | 0x40 ,
2023-09-23 21:56:34 +02:00
. draw_rule = pdraw_timeout_scalefade_exp ( 0 , 0.2f * max ( sx , sy ) * vrng_f32_range ( R [ 0 ] , 0.8f , 1.0f ) , 1 , 0 , 2 ) ,
2019-12-07 02:09:31 +01:00
. angle = vrng_angle ( R [ 1 ] ) ,
. pos = p - > pos + vrng_range ( R [ 2 ] , 0 , 8 ) * vrng_dir ( R [ 3 ] ) ,
2019-01-04 23:59:39 +01:00
. flags = PFLAG_NOREFLECT ,
2019-12-07 02:09:31 +01:00
. timeout = vrng_range ( R [ 4 ] , 22 , 26 ) ,
2019-01-04 23:59:39 +01:00
. color = & clr ,
) ;
2017-11-10 21:49:16 +01:00
}
2017-04-20 18:37:46 +02:00
2020-11-05 09:18:19 +01:00
sx = pow ( p - > sprite - > w , 0.75 ) * ( 1 + 0.02 * rng_real ( ) ) ;
sy = pow ( p - > sprite - > h , 0.75 ) * ( 1 + 0.02 * rng_real ( ) ) ;
clr . a = 0.0 ;
2019-01-04 23:59:39 +01:00
2019-12-07 02:09:31 +01:00
RNG_ARRAY ( R , 5 ) ;
2019-01-04 23:59:39 +01:00
return PARTICLE (
2020-11-05 09:18:19 +01:00
. sprite = " bullet_flare " ,
2019-01-04 23:59:39 +01:00
. size = p - > size * 4.5 ,
. shader = " sprite_bullet " ,
. layer = LAYER_PARTICLE_HIGH | 0x80 ,
2019-12-11 10:25:57 +01:00
. draw_rule = {
bullet_highlight_draw ,
. args [ 0 ] . as_cmplx = 0.125 * ( sx + I * sy ) ,
. args [ 1 ] . as_float = vrng_angle ( R [ 0 ] ) ,
} ,
2019-01-04 23:59:39 +01:00
. angle = p - > angle ,
2020-11-05 09:18:19 +01:00
. pos = p - > pos + vrng_range ( R [ 1 ] , 0 , 2 ) * vrng_dir ( R [ 2 ] ) ,
2019-01-04 23:59:39 +01:00
. flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE ,
2019-12-07 02:09:31 +01:00
. timeout = vrng_range ( R [ 3 ] , 30 , 34 ) ,
2019-01-04 23:59:39 +01:00
. color = & clr ,
) ;
}
Projectile * spawn_projectile_highlight_effect ( Projectile * p ) {
return spawn_projectile_highlight_effect_internal ( p , true ) ;
}
static Projectile * spawn_bullet_spawning_effect ( Projectile * p ) {
if ( proj_uses_spawning_effect ( p , PFLAG_NOSPAWNFLARE ) ) {
return spawn_projectile_highlight_effect ( p ) ;
}
return NULL ;
}
2019-12-11 10:25:57 +01:00
static void projectile_clear_effect_draw ( Projectile * p , int t , ProjDrawRuleArgs args ) {
2019-12-27 16:59:15 +01:00
float o_tf = projectile_timeout_factor ( p ) ;
float tf = glm_ease_circ_out ( o_tf ) ;
2019-01-04 23:59:39 +01:00
2019-12-27 16:59:15 +01:00
Animation * ani = args [ 0 ] . as_ptr ;
AniSequence * seq = args [ 1 ] . as_ptr ;
float angle = args [ 2 ] . as_float [ 0 ] ;
float scale = args [ 2 ] . as_float [ 1 ] ;
2019-01-04 23:59:39 +01:00
2019-12-27 16:59:15 +01:00
SpriteParamsBuffer spbuf ;
SpriteParams sp = projectile_sprite_params ( p , & spbuf ) ;
2019-01-04 23:59:39 +01:00
2019-12-27 16:59:15 +01:00
float o = spbuf . shader_params . vector [ 0 ] ;
2023-09-23 21:56:34 +02:00
spbuf . shader_params . vector [ 0 ] = o * max ( 0 , 1.5f * ( 1 - tf ) - 0.5f ) ;
2019-01-04 23:59:39 +01:00
2019-12-27 16:59:15 +01:00
r_draw_sprite ( & sp ) ;
2019-01-04 23:59:39 +01:00
2019-12-27 16:59:15 +01:00
sp . sprite_ptr = animation_get_frame ( ani , seq , o_tf * ( seq - > length - 1 ) ) ;
2023-09-23 21:56:34 +02:00
sp . scale . as_cmplx * = scale * ( 0.0f + 1.5f * tf ) ;
2019-12-27 16:59:15 +01:00
spbuf . color . a * = ( 1 - tf ) ;
spbuf . shader_params . vector [ 0 ] = o ;
2023-09-23 21:56:34 +02:00
sp . rotation . angle + = angle ;
2019-01-04 23:59:39 +01:00
2019-12-27 16:59:15 +01:00
r_draw_sprite ( & sp ) ;
2019-01-04 23:59:39 +01:00
}
2019-12-11 10:25:57 +01:00
Projectile * spawn_projectile_clear_effect ( Projectile * proj ) {
2019-02-22 00:56:03 +01:00
if ( ( proj - > flags & PFLAG_NOCLEAREFFECT ) | | proj - > sprite = = NULL ) {
2019-01-04 23:59:39 +01:00
return NULL ;
}
2019-12-27 16:59:15 +01:00
cmplx v = proj - > move . velocity ;
if ( ! v ) {
v = proj - > pos - proj - > prevpos ;
}
2019-01-04 23:59:39 +01:00
2020-06-09 03:33:22 +02:00
Animation * ani = res_anim ( " part/bullet_clear " ) ;
2019-12-27 16:59:15 +01:00
AniSequence * seq = get_ani_sequence ( ani , " main " ) ;
2019-01-04 23:59:39 +01:00
2020-01-04 02:03:08 +01:00
Sprite * sprite_ref = animation_get_frame ( ani , seq , 0 ) ;
2023-09-23 21:56:34 +02:00
float scale = max ( proj - > sprite - > w , proj - > sprite - > h ) / sprite_ref - > w ;
2019-01-04 23:59:39 +01:00
return PARTICLE (
. sprite_ptr = proj - > sprite ,
. size = proj - > size ,
. pos = proj - > pos ,
. color = & proj - > color ,
. flags = proj - > flags | PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE ,
2019-12-27 16:59:15 +01:00
. shader_ptr = proj - > shader ,
. draw_rule = {
projectile_clear_effect_draw ,
. args [ 0 ] . as_ptr = ani ,
. args [ 1 ] . as_ptr = seq ,
. args [ 2 ] . as_float = { rng_angle ( ) , scale } ,
} ,
2019-01-04 23:59:39 +01:00
. angle = proj - > angle ,
2019-12-11 10:25:57 +01:00
. opacity = proj - > opacity ,
. scale = proj - > scale ,
2019-12-27 16:59:15 +01:00
. timeout = seq - > length - 1 ,
. layer = LAYER_PARTICLE_BULLET_CLEAR ,
. move = move_asymptotic ( v , 0 , 0.85 ) ,
2019-01-04 23:59:39 +01:00
) ;
2012-07-18 23:12:31 +02:00
}
2019-12-11 10:25:57 +01:00
SpriteParams projectile_sprite_params ( Projectile * proj , SpriteParamsBuffer * spbuf ) {
spbuf - > color = proj - > color ;
spbuf - > shader_params = ( ShaderCustomParams ) { { proj - > opacity , 0 , 0 , 0 } } ;
SpriteParams sp = { 0 } ;
sp . blend = proj - > blend ;
sp . color = & spbuf - > color ;
2023-09-20 04:14:15 +02:00
sp . pos . x = re ( proj - > pos ) ;
sp . pos . y = im ( proj - > pos ) ;
2019-12-11 10:25:57 +01:00
sp . rotation = ( SpriteRotationParams ) {
. angle = proj - > angle + ( float ) ( M_PI / 2 ) ,
. vector = { 0 , 0 , 1 } ,
} ;
2023-09-20 04:14:15 +02:00
sp . scale . x = re ( proj - > scale ) ;
sp . scale . y = im ( proj - > scale ) ;
2019-12-11 10:25:57 +01:00
sp . shader_params = & spbuf - > shader_params ;
sp . shader_ptr = proj - > shader ;
sp . sprite_ptr = proj - > sprite ;
return sp ;
}
2020-01-06 05:47:43 +01:00
static void pdraw_basic_func ( Projectile * proj , int t , ProjDrawRuleArgs args ) {
2019-12-11 10:25:57 +01:00
SpriteParamsBuffer spbuf ;
SpriteParams sp = projectile_sprite_params ( proj , & spbuf ) ;
2019-01-04 23:59:39 +01:00
2019-12-11 10:25:57 +01:00
float eff = proj_spawn_effect_factor ( proj , t ) ;
2019-01-04 23:59:39 +01:00
if ( eff < 1 ) {
2019-12-11 10:25:57 +01:00
spbuf . color . a * = eff ;
2023-09-23 21:56:34 +02:00
spbuf . shader_params . vector [ 0 ] * = min ( 1.0f , eff * 2.0f ) ;
2019-01-04 23:59:39 +01:00
}
2019-12-11 10:25:57 +01:00
r_draw_sprite ( & sp ) ;
2011-06-29 17:01:03 +02:00
}
2011-11-02 20:00:14 +01:00
2019-12-11 10:25:57 +01:00
ProjDrawRule pdraw_basic ( void ) {
2020-01-06 05:47:43 +01:00
return ( ProjDrawRule ) { pdraw_basic_func } ;
2017-10-18 01:53:42 +02:00
}
2019-12-11 10:25:57 +01:00
static void pdraw_blast_func ( Projectile * p , int t , ProjDrawRuleArgs args ) {
vec3 rot_axis = {
args [ 0 ] . as_float [ 0 ] ,
args [ 0 ] . as_float [ 1 ] ,
args [ 1 ] . as_float [ 0 ] ,
} ;
2020-05-09 08:25:38 +02:00
float rot_angle = args [ 1 ] . as_float [ 1 ] ;
float secondary_scale = args [ 2 ] . as_float [ 0 ] ;
2017-11-10 21:49:16 +01:00
2020-05-09 08:25:38 +02:00
float tf = projectile_timeout_factor ( p ) ;
float opacity = ( 1.0f - tf ) * p - > opacity ;
2019-12-11 10:25:57 +01:00
if ( tf < = 0 | | opacity < = 0 ) {
return ;
2018-05-02 06:46:48 +02:00
}
2019-12-11 10:25:57 +01:00
SpriteParamsBuffer spbuf ;
SpriteParams sp = projectile_sprite_params ( p , & spbuf ) ;
sp . rotation . angle = rot_angle ;
glm_vec3_copy ( rot_axis , sp . rotation . vector ) ;
sp . scale . x = tf ;
sp . scale . y = tf ;
2017-11-10 21:49:16 +01:00
2019-12-11 10:25:57 +01:00
spbuf . color = * RGBA ( 0.3 , 0.6 , 1.0 , 1 ) ;
spbuf . shader_params . vector [ 0 ] = opacity ;
2019-10-12 14:02:15 +02:00
2019-12-11 10:25:57 +01:00
r_disable ( RCAP_CULL_FACE ) ;
2019-10-12 14:02:15 +02:00
r_draw_sprite ( & sp ) ;
2019-12-11 10:25:57 +01:00
sp . scale . as_cmplx * = secondary_scale ;
spbuf . color . a = 0 ;
2019-10-12 14:02:15 +02:00
r_draw_sprite ( & sp ) ;
2011-06-28 19:23:02 +02:00
}
2017-02-10 23:05:22 +01:00
2019-12-11 10:25:57 +01:00
ProjDrawRule pdraw_blast ( void ) {
2020-05-09 08:25:38 +02:00
float rot_angle = rng_f32_angle ( ) ;
float x = rng_f32 ( ) ;
float y = rng_f32 ( ) ;
float z = rng_f32 ( ) ;
float secondary_scale = rng_f32_range ( 0.5 , 1.5 ) ;
2019-12-11 10:25:57 +01:00
vec3 rot_axis = { x , y , z } ;
glm_vec3_normalize ( rot_axis ) ;
return ( ProjDrawRule ) {
. func = pdraw_blast_func ,
. args [ 0 ] . as_float = { rot_axis [ 0 ] , rot_axis [ 1 ] } ,
. args [ 1 ] . as_float = { rot_axis [ 2 ] , rot_angle } ,
. args [ 2 ] . as_float = { secondary_scale , } ,
} ;
}
static void pdraw_scalefade_func ( Projectile * p , int t , ProjDrawRuleArgs args ) {
2020-05-09 08:31:20 +02:00
cmplxf scale0 = args [ 0 ] . as_cmplx ;
cmplxf scale1 = args [ 1 ] . as_cmplx ;
2020-05-09 08:25:38 +02:00
float opacity0 = args [ 2 ] . as_float [ 0 ] ;
float opacity1 = args [ 2 ] . as_float [ 1 ] ;
float opacity_exp = args [ 3 ] . as_float [ 0 ] ;
2019-12-11 10:25:57 +01:00
2020-05-09 08:25:38 +02:00
float timefactor = t / p - > timeout ;
2019-12-11 10:25:57 +01:00
2020-05-09 08:31:20 +02:00
cmplxf scale = clerpf ( scale0 , scale1 , timefactor ) ;
2020-05-09 08:25:38 +02:00
float opacity = lerpf ( opacity0 , opacity1 , timefactor ) ;
2019-12-11 10:25:57 +01:00
opacity = powf ( opacity , opacity_exp ) ;
2023-09-20 04:14:15 +02:00
if ( re ( scale ) = = 0 | | im ( scale ) = = 0 | | opacity = = 0 ) {
2023-01-06 03:19:54 +01:00
return ;
}
2019-12-11 10:25:57 +01:00
SpriteParamsBuffer spbuf ;
SpriteParams sp = projectile_sprite_params ( p , & spbuf ) ;
spbuf . shader_params . vector [ 0 ] * = opacity ;
sp . scale . as_cmplx = cwmulf ( sp . scale . as_cmplx , scale ) ;
r_draw_sprite ( & sp ) ;
}
2020-05-09 08:31:20 +02:00
ProjDrawRule pdraw_timeout_scalefade_exp ( cmplxf scale0 ,
cmplxf scale1 , float opacity0 , float opacity1 , float opacity_exp ) {
2023-09-20 04:14:15 +02:00
if ( im ( scale0 ) = = 0 ) {
scale0 = CMPLXF ( re ( scale0 ) , re ( scale0 ) ) ;
2019-12-11 10:25:57 +01:00
}
2023-09-20 04:14:15 +02:00
if ( im ( scale1 ) = = 0 ) {
scale1 = CMPLXF ( re ( scale1 ) , re ( scale1 ) ) ;
2019-12-11 10:25:57 +01:00
}
2017-02-10 23:05:22 +01:00
2019-12-11 10:25:57 +01:00
return ( ProjDrawRule ) {
. func = pdraw_scalefade_func ,
. args [ 0 ] . as_cmplx = scale0 ,
. args [ 1 ] . as_cmplx = scale1 ,
. args [ 2 ] . as_float = { opacity0 , opacity1 } ,
. args [ 3 ] . as_float = { opacity_exp } ,
} ;
}
2020-05-09 08:31:20 +02:00
ProjDrawRule pdraw_timeout_scalefade (
cmplxf scale0 , cmplxf scale1 , float opacity0 , float opacity1 ) {
2019-12-11 10:25:57 +01:00
return pdraw_timeout_scalefade_exp ( scale0 , scale1 , opacity0 , opacity1 , 1.0f ) ;
}
2020-05-09 08:31:20 +02:00
ProjDrawRule pdraw_timeout_scale ( cmplxf scale0 , cmplxf scale1 ) {
2020-01-03 23:12:20 +01:00
// TODO: specialized code path without fade component
2019-12-11 10:25:57 +01:00
return pdraw_timeout_scalefade ( scale0 , scale1 , 1 , 1 ) ;
}
2020-05-09 08:25:38 +02:00
ProjDrawRule pdraw_timeout_fade ( float opacity0 , float opacity1 ) {
2020-01-03 23:12:20 +01:00
// TODO: specialized code path without scale component
2019-12-11 10:25:57 +01:00
return pdraw_timeout_scalefade ( 1 + I , 1 + I , opacity0 , opacity1 ) ;
}
static void pdraw_petal_func ( Projectile * p , int t , ProjDrawRuleArgs args ) {
vec3 rot_axis = {
args [ 0 ] . as_float [ 0 ] ,
args [ 0 ] . as_float [ 1 ] ,
args [ 1 ] . as_float [ 0 ] ,
} ;
2020-05-09 08:25:38 +02:00
float rot_angle = args [ 1 ] . as_float [ 1 ] ;
2019-12-11 10:25:57 +01:00
SpriteParamsBuffer spbuf ;
SpriteParams sp = projectile_sprite_params ( p , & spbuf ) ;
glm_vec3_copy ( rot_axis , sp . rotation . vector ) ;
sp . rotation . angle = DEG2RAD * t * 4.0f + rot_angle ;
spbuf . shader_params . vector [ 0 ] * = ( 1.0f - projectile_timeout_factor ( p ) ) ;
2017-03-24 14:53:57 +01:00
2018-04-12 16:08:48 +02:00
r_disable ( RCAP_CULL_FACE ) ;
2019-12-11 10:25:57 +01:00
r_draw_sprite ( & sp ) ;
}
2020-05-09 08:25:38 +02:00
ProjDrawRule pdraw_petal ( float rot_angle , vec3 rot_axis ) {
2019-12-11 10:25:57 +01:00
glm_vec3_normalize ( rot_axis ) ;
2020-05-09 08:25:38 +02:00
float x = rot_axis [ 0 ] ;
float y = rot_axis [ 1 ] ;
float z = rot_axis [ 2 ] ;
2019-12-11 10:25:57 +01:00
return ( ProjDrawRule ) {
. func = pdraw_petal_func ,
. args [ 0 ] . as_float = { x , y } ,
. args [ 1 ] . as_float = { z , rot_angle } ,
} ;
}
ProjDrawRule pdraw_petal_random ( void ) {
2020-05-09 08:25:38 +02:00
float x = rng_f32 ( ) ;
float y = rng_f32 ( ) ;
float z = rng_f32 ( ) ;
float rot_angle = rng_f32_angle ( ) ;
2019-12-11 10:25:57 +01:00
return pdraw_petal ( rot_angle , ( vec3 ) { x , y , z } ) ;
2011-08-28 10:57:45 +02:00
}
2019-11-22 04:37:11 +01:00
void petal_explosion ( int n , cmplx pos ) {
2017-11-21 10:26:11 +01:00
for ( int i = 0 ; i < n ; i + + ) {
2019-12-11 10:25:57 +01:00
cmplx v = rng_dir ( ) ;
v * = rng_range ( 3 , 8 ) ;
2019-12-07 02:09:31 +01:00
real t = rng_real ( ) ;
2017-11-10 21:49:16 +01:00
2018-05-02 06:46:48 +02:00
PARTICLE (
. sprite = " petal " ,
. pos = pos ,
2019-01-04 23:59:39 +01:00
. color = RGBA ( sin ( 5 * t ) * t , cos ( 5 * t ) * t , 0.5 * t , 0 ) ,
2019-12-11 10:25:57 +01:00
. move = move_asymptotic_simple ( v , 5 ) ,
. draw_rule = pdraw_petal_random ( ) ,
2020-03-04 19:34:47 +01:00
. flags = ( n % 2 ? 0 : PFLAG_REQUIREDPARTICLE ) | PFLAG_MANUALANGLE ,
2019-01-04 23:59:39 +01:00
. layer = LAYER_PARTICLE_PETAL ,
2017-11-10 21:49:16 +01:00
) ;
2011-08-28 10:57:45 +02:00
}
2012-07-18 12:33:37 +02:00
}
2017-03-11 04:41:57 +01:00
2023-03-22 23:41:32 +01:00
void projectiles_preload ( ResourceGroup * rg ) {
2019-01-04 23:59:39 +01:00
const char * shaders [ ] = {
// This array defines a shader-based fallback draw order
" sprite_silhouette " ,
2018-04-12 16:08:48 +02:00
defaults_proj . shader ,
defaults_part . shader ,
2020-03-05 19:38:48 +01:00
" sprite_default " ,
2019-01-04 23:59:39 +01:00
} ;
2018-04-12 16:08:48 +02:00
2019-01-04 23:59:39 +01:00
const uint num_shaders = sizeof ( shaders ) / sizeof ( * shaders ) ;
for ( uint i = 0 ; i < num_shaders ; + + i ) {
2023-03-22 23:41:32 +01:00
res_group_preload ( rg , RES_SHADER_PROGRAM , RESF_DEFAULT , shaders [ i ] , NULL ) ;
2019-01-04 23:59:39 +01:00
}
// TODO: Maybe split this up into stage-specific preloads too?
// some of these are ubiquitous, but some only appear in very specific parts.
2023-03-22 23:41:32 +01:00
res_group_preload ( rg , RES_SPRITE , RESF_DEFAULT ,
2017-03-11 04:41:57 +01:00
" part/blast " ,
2020-11-05 09:18:19 +01:00
" part/bullet_flare " ,
2017-03-11 04:41:57 +01:00
" part/flare " ,
2019-01-04 23:59:39 +01:00
" part/graze " ,
2017-03-20 07:38:07 +01:00
" part/lightning0 " ,
" part/lightning1 " ,
" part/lightningball " ,
2019-01-04 23:59:39 +01:00
" part/petal " ,
" part/smoke " ,
2018-02-06 07:19:25 +01:00
" part/smoothdot " ,
2019-01-04 23:59:39 +01:00
" part/stain " ,
2023-06-17 04:19:11 +02:00
" part/stardust " ,
2019-01-04 23:59:39 +01:00
" part/stardust_green " ,
2017-03-11 04:41:57 +01:00
NULL ) ;
2023-03-22 23:41:32 +01:00
res_group_preload ( rg , RES_ANIM , RESF_DEFAULT ,
2019-12-27 16:59:15 +01:00
" part/bullet_clear " ,
NULL ) ;
2023-03-22 23:41:32 +01:00
res_group_preload ( rg , RES_SFX , RESF_OPTIONAL ,
2017-09-30 20:11:10 +02:00
" shot1 " ,
2017-10-29 04:43:32 +01:00
" shot2 " ,
2018-08-14 02:56:16 +02:00
" shot3 " ,
2017-09-30 20:11:10 +02:00
" shot1_loop " ,
2017-10-22 21:11:15 +02:00
" shot_special1 " ,
" redirect " ,
2023-06-17 04:19:11 +02:00
" warp " ,
2017-09-30 20:11:10 +02:00
NULL ) ;
2018-04-12 16:08:48 +02:00
2023-03-22 23:41:32 +01:00
# define PP(name) (_pp_##name).preload(&_pp_##name, rg);
2018-05-02 06:46:48 +02:00
# include "projectile_prototypes/all.inc.h"
2019-01-04 23:59:39 +01:00
ht_create ( & shader_sublayer_map ) ;
for ( uint i = 0 ; i < num_shaders ; + + i ) {
2020-06-09 03:33:22 +02:00
ht_set ( & shader_sublayer_map , res_shader ( shaders [ i ] ) , i + 1 ) ;
2019-01-04 23:59:39 +01:00
}
2020-06-09 03:33:22 +02:00
defaults_proj . shader_ptr = res_shader ( defaults_proj . shader ) ;
defaults_part . shader_ptr = res_shader ( defaults_part . shader ) ;
2017-03-11 04:41:57 +01:00
}
2019-01-04 23:59:39 +01:00
void projectiles_free ( void ) {
ht_destroy ( & shader_sublayer_map ) ;
2023-03-22 23:41:32 +01:00
# define PP(name) (_pp_##name).reset(&_pp_##name);
# include "projectile_prototypes/all.inc.h"
2019-01-04 23:59:39 +01:00
}