YoumuB bomb tweaks, some laser fixups
This commit is contained in:
parent
4e7c37976f
commit
dc88572167
10 changed files with 178 additions and 65 deletions
21
src/entity.c
21
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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
115
src/laser.c
115
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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--;
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
|
31
src/stage.c
31
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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) &&
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue