From dc885721676a9082def527b528c68f9f5bcc38d8 Mon Sep 17 00:00:00 2001 From: Andrei Alexeyev <0x416b617269@gmail.com> Date: Tue, 26 Mar 2019 17:58:38 +0200 Subject: [PATCH] YoumuB bomb tweaks, some laser fixups --- src/entity.c | 21 ++++++++ src/entity.h | 1 + src/laser.c | 115 +++++++++++++++++++++++------------------ src/laser.h | 2 + src/player.c | 2 +- src/plrmodes/youmu_b.c | 41 ++++++++++++--- src/stage.c | 31 +++++++++++ src/stage.h | 1 + src/util/geometry.c | 28 +++++++--- src/util/geometry.h | 1 + 10 files changed, 178 insertions(+), 65 deletions(-) diff --git a/src/entity.c b/src/entity.c index 0407a732..d37a27f9 100644 --- a/src/entity.c +++ b/src/entity.c @@ -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); } diff --git a/src/entity.h b/src/entity.h index ac2b9c75..7b4d65e4 100644 --- a/src/entity.h +++ b/src/entity.h @@ -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); diff --git a/src/laser.c b/src/laser.c index 66783305..268189e9 100644 --- a/src/laser.c +++ b/src/laser.c @@ -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; } diff --git a/src/laser.h b/src/laser.h index ccddadd7..97c89f46 100644 --- a/src/laser.h +++ b/src/laser.h @@ -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 diff --git a/src/player.c b/src/player.c index 8bd2de8e..4db40e90 100644 --- a/src/player.c +++ b/src/player.c @@ -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--; diff --git a/src/plrmodes/youmu_b.c b/src/plrmodes/youmu_b.c index 7f74d4b6..643b2e71 100644 --- a/src/plrmodes/youmu_b.c +++ b/src/plrmodes/youmu_b.c @@ -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, }, }; diff --git a/src/stage.c b/src/stage.c index a09d4764..fd9d4fce 100644 --- a/src/stage.c +++ b/src/stage.c @@ -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); diff --git a/src/stage.h b/src/stage.h index 095b392f..85e4c6cd 100644 --- a/src/stage.h +++ b/src/stage.h @@ -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); diff --git a/src/util/geometry.c b/src/util/geometry.c index f3889680..dc4a5bbf 100644 --- a/src/util/geometry.c +++ b/src/util/geometry.c @@ -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) && diff --git a/src/util/geometry.h b/src/util/geometry.h index 990d2a3c..d5a96a79 100644 --- a/src/util/geometry.h +++ b/src/util/geometry.h @@ -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);