YoumuB bomb tweaks, some laser fixups

This commit is contained in:
Andrei Alexeyev 2019-03-26 17:58:38 +02:00
parent 4e7c37976f
commit dc88572167
No known key found for this signature in database
GPG key ID: 363707CD4C7FE8A4
10 changed files with 178 additions and 65 deletions

View file

@ -206,6 +206,27 @@ void ent_area_damage(complex origin, float radius, const DamageInfo *damage, Ent
}
}
void ent_area_damage_ellipse(Ellipse ellipse, const DamageInfo *damage, EntityAreaDamageCallback callback, void *callback_arg) {
for(Enemy *e = global.enemies.first; e; e = e->next) {
if(
point_in_ellipse(e->pos, ellipse) &&
ent_damage(&e->ent, damage) == DMG_RESULT_OK &&
callback != NULL
) {
callback(&e->entity_interface, e->pos, callback_arg);
}
}
if(
global.boss != NULL &&
point_in_ellipse(global.boss->pos, ellipse) &&
ent_damage(&global.boss->ent, damage) == DMG_RESULT_OK &&
callback != NULL
) {
callback(&global.boss->entity_interface, global.boss->pos, callback_arg);
}
}
void ent_hook_pre_draw(EntityDrawHookCallback callback, void *arg) {
add_hook(&entities.hooks.pre_draw, callback, arg);
}

View file

@ -144,6 +144,7 @@ void ent_unregister(EntityInterface *ent) attr_nonnull(1);
void ent_draw(EntityPredicate predicate);
DamageResult ent_damage(EntityInterface *ent, const DamageInfo *damage) attr_nonnull(1, 2);
void ent_area_damage(complex origin, float radius, const DamageInfo *damage, EntityAreaDamageCallback callback, void *callback_arg) attr_nonnull(3);
void ent_area_damage_ellipse(Ellipse ellipse, const DamageInfo *damage, EntityAreaDamageCallback callback, void *callback_arg) attr_nonnull(2);
void ent_hook_pre_draw(EntityDrawHookCallback callback, void *arg);
void ent_unhook_pre_draw(EntityDrawHookCallback callback);

View file

@ -357,6 +357,10 @@ void delete_lasers(void) {
alist_foreach(&global.lasers, _delete_laser, NULL);
}
bool laser_is_clearable(Laser *l) {
return !l->unclearable;
}
bool clear_laser(Laser *l, uint flags) {
if(!(flags & CLEAR_HAZARDS_FORCE) && l->unclearable) {
return false;
@ -366,7 +370,7 @@ bool clear_laser(Laser *l, uint flags) {
return true;
}
static bool collision_laser_curve(Laser *l);
static bool laser_collision(Laser *l);
void process_lasers(void) {
Laser *laser = global.lasers.first, *del = NULL;
@ -404,7 +408,7 @@ void process_lasers(void) {
}
}
} else {
if(collision_laser_curve(laser)) {
if(laser_collision(laser)) {
ent_damage(&global.plr.ent, &(DamageInfo) { .type = DMG_ENEMY_SHOT });
}
@ -423,14 +427,43 @@ void process_lasers(void) {
}
}
static bool collision_laser_curve(Laser *l) {
static inline bool laser_collision_segment(Laser *l, LineSegment *segment, Circle *collision_area, float t, float width_tail_factor, float tail) {
float t1 = t - l->timespan / 2;
float widthfac_orig = width_tail_factor * (t1 - tail) * (t1 + tail);
float widthfac = max(0.25, pow(widthfac_orig, l->width_exponent));
segment->b = l->prule(l, t);
collision_area->radius = widthfac * l->width * 0.5 + 1;
if(lineseg_circle_intersect(*segment, *collision_area) >= 0) {
return true;
}
if(global.frames >= l->next_graze && global.frames - abs(global.plr.recovery) > 0) {
double exponent;
collision_area->radius = laser_graze_width(l, &exponent) * max(0.25, pow(widthfac_orig, exponent));
assert(collision_area->radius > 0);
float f = lineseg_circle_intersect(*segment, *collision_area);
if(f >= 0) {
player_graze(&global.plr, segment->a + f * (segment->b - segment->a), 7, 5, &l->color);
l->next_graze = global.frames + 4;
}
}
segment->a = segment->b;
return false;
}
static bool laser_collision(Laser *l) {
if(l->width <= 3.0) {
return false;
}
float t_end = (global.frames - l->birthtime) * l->speed + l->timeshift; // end of the laser based on length
float t_death = l->deathtime * l->speed + l->timeshift; // end of the laser based on lifetime
float t = t_end - l->timespan;
float t_end_len = (global.frames - l->birthtime) * l->speed + l->timeshift; // end of the laser based on length
float t_end_lifetime = l->deathtime * l->speed + l->timeshift; // end of the laser based on lifetime
float t = t_end_len - l->timespan;
float t_end = fmin(t_end_len, t_end_lifetime);
if(t < 0) {
t = 0;
@ -439,80 +472,59 @@ static bool collision_laser_curve(Laser *l) {
LineSegment segment = { .a = l->prule(l,t) };
Circle collision_area = { .origin = global.plr.pos };
for(t += l->collision_step; t <= min(t_end,t_death); t += l->collision_step) {
float t1 = t - l->timespan / 2;
float tail = l->timespan / 1.9;
float widthfac_orig = -0.75 / pow(tail, 2) * (t1 - tail) * (t1 + tail);
float widthfac = max(0.25, pow(widthfac_orig, l->width_exponent));
float tail = l->timespan / 1.9;
float width_tail_factor = -0.75 / (tail * tail);
segment.b = l->prule(l, t);
collision_area.radius = widthfac * l->width * 0.5 + 1;
if(lineseg_circle_intersect(segment, collision_area) >= 0) {
for(t += l->collision_step; t <= t_end; t += l->collision_step) {
if(laser_collision_segment(l, &segment, &collision_area, t, width_tail_factor, tail)) {
return true;
}
if(global.frames >= l->next_graze && global.frames - abs(global.plr.recovery) > 0) {
double exponent;
collision_area.radius = laser_graze_width(l, &exponent) * max(0.25, pow(widthfac_orig, exponent));
assert(collision_area.radius > 0);
float f = lineseg_circle_intersect(segment, collision_area);
if(f >= 0) {
player_graze(&global.plr, segment.a + f * (segment.b - segment.a), 7, 5, &l->color);
l->next_graze = global.frames + 4;
}
}
segment.a = segment.b;
}
segment.b = l->prule(l, min(t_end, t_death));
collision_area.radius = l->width * 0.5; // WTF: what is this sorcery?
return lineseg_circle_intersect(segment, collision_area) >= 0;
return laser_collision_segment(l, &segment, &collision_area, t_end, width_tail_factor, tail);
}
bool laser_intersects_circle(Laser *l, Circle circle) {
// TODO: lots of copypasta from the function above here, maybe refactor both somehow.
bool laser_intersects_ellipse(Laser *l, Ellipse ellipse) {
// NOTE: this function does not take laser width into account
float t_end = (global.frames - l->birthtime) * l->speed + l->timeshift; // end of the laser based on length
float t_death = l->deathtime * l->speed + l->timeshift; // end of the laser based on lifetime
float t = t_end - l->timespan;
float t_end_len = (global.frames - l->birthtime) * l->speed + l->timeshift; // end of the laser based on length
float t_end_lifetime = l->deathtime * l->speed + l->timeshift; // end of the laser based on lifetime
float t = t_end_len - l->timespan;
float t_end = fmin(t_end_len, t_end_lifetime);
if(t < 0) {
t = 0;
}
LineSegment segment = { .a = l->prule(l, t) };
double orig_radius = circle.radius;
for(t += l->collision_step; t <= min(t_end, t_death); t += l->collision_step) {
float t1 = t - l->timespan / 2; // i have no idea
float tail = l->timespan / 1.9;
float widthfac = -0.75 / pow(tail, 2) * (t1 - tail) * (t1 + tail);
widthfac = max(0.25, pow(widthfac, l->width_exponent));
for(t += l->collision_step; t <= t_end; t += l->collision_step) {
segment.b = l->prule(l, t);
circle.radius = orig_radius + widthfac * l->width * 0.5 + 1;
if(lineseg_circle_intersect(segment, circle) >= 0) {
if(lineseg_ellipse_intersect(segment, ellipse)) {
return true;
}
segment.a = segment.b;
}
segment.b = l->prule(l, min(t_end, t_death));
circle.radius = orig_radius + l->width * 0.5; // WTF: what is this sorcery?
segment.b = l->prule(l, t_end);
return lineseg_ellipse_intersect(segment, ellipse);
}
return lineseg_circle_intersect(segment, circle) >= 0;
bool laser_intersects_circle(Laser *l, Circle circle) {
Ellipse ellipse = {
.origin = circle.origin,
.axes = circle.radius * 2 * (1 + I),
};
return laser_intersects_ellipse(l, ellipse);
}
complex las_linear(Laser *l, float t) {
if(t == EVENT_BIRTH) {
l->shader = r_shader_get_optional("lasers/linear");
l->collision_step = max(3,l->timespan/10);
l->collision_step = max(3, l->timespan/10);
return 0;
}
@ -522,6 +534,7 @@ complex las_linear(Laser *l, float t) {
complex las_accel(Laser *l, float t) {
if(t == EVENT_BIRTH) {
l->shader = r_shader_get_optional("lasers/accelerated");
l->collision_step = max(3, l->timespan/10);
return 0;
}

View file

@ -63,6 +63,7 @@ Laser *create_laser(complex pos, float time, float deathtime, const Color *color
void delete_lasers(void);
void process_lasers(void);
bool laser_is_clearable(Laser *l);
bool clear_laser(Laser *l, uint flags);
complex las_linear(Laser *l, float t);
@ -77,5 +78,6 @@ float laser_charge(Laser *l, int t, float charge, float width);
void static_laser(Laser *l, int t);
bool laser_intersects_circle(Laser *l, Circle circle);
bool laser_intersects_ellipse(Laser *l, Ellipse ellipse);
#endif // IGUARD_laser_h

View file

@ -542,7 +542,7 @@ static bool player_bomb(Player *plr) {
if(!player_is_bomb_active(plr) && (plr->bombs > 0 || plr->iddqd) && global.frames >= plr->respawntime) {
player_fail_spell(plr);
// player_cancel_powersurge(plr);
stage_clear_hazards(CLEAR_HAZARDS_ALL);
// stage_clear_hazards(CLEAR_HAZARDS_ALL);
plr->mode->procs.bomb(plr);
plr->bombs--;

View file

@ -226,13 +226,34 @@ static void youmu_particle_slice_draw(Projectile *p, int t) {
r_mat_translate(creal(p->pos), cimag(p->pos),0);
r_mat_rotate_deg(p->angle/M_PI*180,0,0,1);
r_mat_scale(f,1,1);
//draw_texture(0,0,"part/youmu_slice");
ProjDrawCore(p, &p->color);
r_mat_pop();
double slicelen = 500;
complex slicepos = p->pos-(tt>0.1)*slicelen*I*cexp(I*p->angle)*(5*pow(tt-0.1,1.1)-0.5);
draw_sprite_batched_p(creal(slicepos), cimag(slicepos), aniplayer_get_frame(&global.plr.ani));
/*
Sprite *s = get_sprite("part/smoothdot");
r_draw_sprite(&(SpriteParams) {
.sprite_ptr = s,
.pos = { creal(p->pos), cimag(p->pos) },
.rotation.angle = p->angle + M_PI/2,
.scale.x = 512 / s->w,
.scale.y = 32 / s->h,
.color = RGBA(0.5, 0, 0, 0.5),
});
*/
}
static int youmu_slice_petal(Projectile *p, int t) {
int r = accelerated(p, t);
if(t >= 0) {
p->color = *color_mul_scalar(RGBA(0.2, 0.2, 1, 0), min(1, t / 40.0));
}
return r;
}
static int youmu_particle_slice_logic(Projectile *p, int t) {
@ -253,15 +274,15 @@ static int youmu_particle_slice_logic(Projectile *p, int t) {
p->color = *RGBA(a, a, a, 0);
complex phase = cexp(p->angle*I);
complex phase = cexp(p->angle * I);
if(t%5 == 0) {
tsrand_fill(4);
PARTICLE(
.sprite = "petal",
.pos = p->pos-400*phase,
.rule = accelerated,
.rule = youmu_slice_petal,
.draw_rule = Petal,
.color = RGBA(0.1, 0.1, 0.5, 0),
.args = {
phase,
phase*cexp(0.1*I),
@ -273,13 +294,22 @@ static int youmu_particle_slice_logic(Projectile *p, int t) {
);
}
Ellipse e = {
.origin = p->pos,
.axes = CMPLX(512, 64),
.angle = p->angle + M_PI * 0.5,
};
// FIXME: this may still be too slow for lasers in some cases
stage_clear_hazards_in_ellipse(e, CLEAR_HAZARDS_ALL | CLEAR_HAZARDS_NOW);
ent_area_damage_ellipse(e, &(DamageInfo) { 52, DMG_PLAYER_BOMB }, NULL, NULL);
return ACTION_NONE;
}
static void YoumuSlash(Enemy *e, int t, bool render) {
}
static int youmu_slash(Enemy *e, int t) {
if(t > creal(e->args[0]))
return ACTION_DESTROY;
@ -429,6 +459,5 @@ PlayerMode plrmode_youmu_b = {
.init = youmu_haunting_init,
.shot = youmu_haunting_shot,
.preload = youmu_haunting_preload,
.think = player_placeholder_bomb_logic,
},
};

View file

@ -482,10 +482,16 @@ static void stage_logic(void) {
}
void stage_clear_hazards_predicate(bool (*predicate)(EntityInterface *ent, void *arg), void *arg, ClearHazardsFlags flags) {
bool force = flags & CLEAR_HAZARDS_FORCE;
if(flags & CLEAR_HAZARDS_BULLETS) {
for(Projectile *p = global.projs.first, *next; p; p = next) {
next = p->next;
if(!force && !projectile_is_clearable(p)) {
continue;
}
if(!predicate || predicate(&p->ent, arg)) {
clear_projectile(p, flags);
}
@ -496,6 +502,10 @@ void stage_clear_hazards_predicate(bool (*predicate)(EntityInterface *ent, void
for(Laser *l = global.lasers.first, *next; l; l = next) {
next = l->next;
if(!force && !laser_is_clearable(l)) {
continue;
}
if(!predicate || predicate(&l->ent, arg)) {
clear_laser(l, flags);
}
@ -525,11 +535,32 @@ static bool proximity_predicate(EntityInterface *ent, void *varg) {
}
}
static bool ellipse_predicate(EntityInterface *ent, void *varg) {
Ellipse *e = varg;
switch(ent->type) {
case ENT_PROJECTILE: {
Projectile *p = ENT_CAST(ent, Projectile);
return point_in_ellipse(p->pos, *e);
}
case ENT_LASER: {
Laser *l = ENT_CAST(ent, Laser);
return laser_intersects_ellipse(l, *e);
}
default: UNREACHABLE;
}
}
void stage_clear_hazards_at(complex origin, double radius, ClearHazardsFlags flags) {
Circle area = { origin, radius };
stage_clear_hazards_predicate(proximity_predicate, &area, flags);
}
void stage_clear_hazards_in_ellipse(Ellipse e, ClearHazardsFlags flags) {
stage_clear_hazards_predicate(ellipse_predicate, &e, flags);
}
static void stage_free(void) {
delete_enemies(&global.enemies);
delete_enemies(&global.plr.slaves);

View file

@ -139,6 +139,7 @@ typedef enum ClearHazardsFlags {
void stage_clear_hazards(ClearHazardsFlags flags);
void stage_clear_hazards_at(complex origin, double radius, ClearHazardsFlags flags);
void stage_clear_hazards_in_ellipse(Ellipse e, ClearHazardsFlags flags);
void stage_clear_hazards_predicate(bool (*predicate)(EntityInterface *ent, void *arg), void *arg, ClearHazardsFlags flags);
void stage_set_voltage_thresholds(uint easy, uint normal, uint hard, uint lunatic);

View file

@ -10,6 +10,12 @@
#include "geometry.h"
static inline void ellipse_bbox(const Ellipse *e, Rect *r) {
float largest_radius = fmax(creal(e->axes), cimag(e->axes)) * 0.5;
r->top_left = e->origin - largest_radius - I * largest_radius;
r->bottom_right = e->origin + largest_radius + I * largest_radius;
}
bool point_in_ellipse(complex p, Ellipse e) {
double Xp = creal(p);
double Yp = cimag(p);
@ -17,7 +23,10 @@ bool point_in_ellipse(complex p, Ellipse e) {
double Ye = cimag(e.origin);
double a = e.angle;
return (
Rect e_bbox;
ellipse_bbox(&e, &e_bbox);
return point_in_rect(p, e_bbox) && (
pow(cos(a) * (Xp - Xe) + sin(a) * (Yp - Ye), 2) / pow(creal(e.axes)/2, 2) +
pow(sin(a) * (Xp - Xe) - cos(a) * (Yp - Ye), 2) / pow(cimag(e.axes)/2, 2)
) <= 1;
@ -32,11 +41,8 @@ static bool segment_ellipse_nonintersection_heuristic(LineSegment seg, Ellipse e
.bottom_right = fmax(creal(seg.a), creal(seg.b)) + I * fmax(cimag(seg.a), cimag(seg.b))
};
float largest_radius = fmax(creal(e.axes), cimag(e.axes))/2;
Rect e_bbox = {
.top_left = e.origin - largest_radius - I*largest_radius,
.bottom_right = e.origin + largest_radius + I*largest_radius
};
Rect e_bbox;
ellipse_bbox(&e, &e_bbox);
return !rect_rect_intersect(seg_bbox, e_bbox, true, true);
}
@ -66,7 +72,7 @@ static double lineseg_circle_intersect_fallback(LineSegment seg, Circle c) {
projection = -creal(v*conj(m)) / lm; // project v onto the line
// now the distance can be calculated by Pythagoras
distance = sqrt(pow(lv, 2) - pow(projection, 2));
distance = sqrt(lv*lv - projection*projection);
if(distance <= c.radius) {
double f = projection/lm;
@ -110,6 +116,14 @@ double lineseg_circle_intersect(LineSegment seg, Circle c) {
return lineseg_circle_intersect_fallback(seg, c);
}
bool point_in_rect(complex p, Rect r) {
return
creal(p) >= rect_left(r) &&
creal(p) <= rect_right(r) &&
cimag(p) >= rect_top(r) &&
cimag(p) <= rect_bottom(r);
}
bool rect_in_rect(Rect inner, Rect outer) {
return
rect_left(inner) >= rect_left(outer) &&

View file

@ -102,6 +102,7 @@ void rect_move(Rect *r, complex pos) {
r->bottom_right += vector;
}
bool point_in_rect(complex p, Rect r);
bool rect_in_rect(Rect inner, Rect outer) attr_const;
bool rect_rect_intersect(Rect r1, Rect r2, bool edges, bool corners) attr_const;
bool rect_rect_intersection(Rect r1, Rect r2, bool edges, bool corners, Rect *out) attr_pure attr_nonnull(5);