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 ;