399 lines
8.8 KiB
C
399 lines
8.8 KiB
C
/*
|
|
* This software is licensed under the terms of the MIT License.
|
|
* See COPYING for further information.
|
|
* ---
|
|
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
|
|
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
|
|
*/
|
|
|
|
#include "common_tasks.h"
|
|
|
|
#include "audio/audio.h"
|
|
#include "random.h"
|
|
#include "stage.h"
|
|
#include "util/glm.h"
|
|
|
|
void common_drop_items(cmplx pos, const ItemCounts *items) {
|
|
for(int i = 0; i < ARRAY_SIZE(items->as_array); ++i) {
|
|
for(int j = items->as_array[i]; j; --j) {
|
|
spawn_item(pos, i + ITEM_FIRST);
|
|
WAIT(2);
|
|
}
|
|
}
|
|
}
|
|
|
|
DEFINE_EXTERN_TASK(common_drop_items) {
|
|
common_drop_items(*ARGS.pos, &ARGS.items);
|
|
}
|
|
|
|
void common_move_loop(cmplx *restrict pos, MoveParams *restrict mp) {
|
|
for(;;) {
|
|
move_update(pos, mp);
|
|
YIELD;
|
|
}
|
|
}
|
|
|
|
DEFINE_EXTERN_TASK(common_move) {
|
|
if(ARGS.ent.ent) {
|
|
TASK_BIND(ARGS.ent);
|
|
}
|
|
|
|
MoveParams p = ARGS.move_params;
|
|
common_move_loop(ARGS.pos, &p);
|
|
}
|
|
|
|
DEFINE_EXTERN_TASK(common_move_ext) {
|
|
if(ARGS.ent.ent) {
|
|
TASK_BIND(ARGS.ent);
|
|
}
|
|
|
|
common_move_loop(ARGS.pos, ARGS.move_params);
|
|
}
|
|
|
|
DEFINE_EXTERN_TASK(common_call_func) {
|
|
ARGS.func();
|
|
}
|
|
|
|
DEFINE_EXTERN_TASK(common_start_bgm) {
|
|
stage_start_bgm(ARGS.bgm);
|
|
}
|
|
|
|
static Projectile *spawn_charge_particle_smoke(cmplx target, real power) {
|
|
real scale = power * rng_range(0.7, 1);
|
|
real opacity = power * rng_range(0.5, 0.8);
|
|
cmplx pos = target + rng_dir() * 16 * power;
|
|
MoveParams move = move_asymptotic_simple(0.5 * rng_dir(), 3);
|
|
real angle = rng_angle();
|
|
int timeout = rng_irange(30, 60);
|
|
|
|
return PARTICLE(
|
|
.sprite = "smoke",
|
|
.pos = pos,
|
|
.draw_rule = pdraw_timeout_scalefade_exp(0.01, scale, opacity, 0, 2),
|
|
.move = move,
|
|
.timeout = timeout,
|
|
.flags = PFLAG_NOREFLECT | PFLAG_MANUALANGLE,
|
|
.angle = angle,
|
|
.layer = LAYER_PARTICLE_HIGH,
|
|
);
|
|
}
|
|
|
|
static Projectile *spawn_charge_particle(cmplx target, real dist, const Color *clr, real power) {
|
|
cmplx pos = target + rng_dir() * dist;
|
|
MoveParams move = move_towards(0, target, rng_range(0.1, 0.2) + 0.05 * power);
|
|
move.retention = 0.25 * cdir(1.5 * rng_sign());
|
|
|
|
spawn_charge_particle_smoke(target, power);
|
|
|
|
return PARTICLE(
|
|
.sprite = "graze",
|
|
.color = clr,
|
|
.pos = pos,
|
|
.draw_rule = pdraw_timeout_scalefade(2, 0.05, 0, 1),
|
|
.move = move,
|
|
.timeout = rng_irange(25, 35),
|
|
.flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE,
|
|
.layer = LAYER_PARTICLE_HIGH,
|
|
);
|
|
}
|
|
|
|
static void randomize_hue(Color *clr, float r) {
|
|
float h, s, l, a = clr->a;
|
|
float m = max(clr->r, max(clr->g, clr->b));
|
|
|
|
if(UNLIKELY(m == 0)) {
|
|
return;
|
|
}
|
|
|
|
color_div_scalar(clr, m);
|
|
color_get_hsl(clr, &h, &s, &l);
|
|
h += rng_f32s() * r;
|
|
*clr = *HSLA(h, s, l, a);
|
|
color_mul_scalar(clr, m);
|
|
}
|
|
|
|
TASK(charge_sound_stopper, { SFXPlayID id; }) {
|
|
stop_sfx(ARGS.id);
|
|
}
|
|
|
|
static int common_charge_impl(
|
|
int time,
|
|
const cmplx *anchor,
|
|
cmplx offset,
|
|
const Color *color,
|
|
const char *snd_charge,
|
|
const char *snd_discharge
|
|
) {
|
|
real dist = 256;
|
|
int delay = 3;
|
|
real rayfactor = 1.0 / time;
|
|
float hue_rand = 0.02;
|
|
SFXPlayID charge_snd_id = snd_charge ? play_sfx(snd_charge) : 0;
|
|
DECLARE_ENT_ARRAY(Projectile, particles, 256);
|
|
|
|
BoxedTask snd_stopper_task = { 0 };
|
|
|
|
if(charge_snd_id) {
|
|
snd_stopper_task = cotask_box(INVOKE_TASK_AFTER(&TASK_EVENTS(THIS_TASK)->finished, charge_sound_stopper, charge_snd_id));
|
|
}
|
|
|
|
int wait_time = 0;
|
|
|
|
for(int i = 0; i < time; ++i, wait_time += WAIT(1)) {
|
|
cmplx pos = *anchor + offset;
|
|
|
|
ENT_ARRAY_COMPACT(&particles);
|
|
ENT_ARRAY_FOREACH(&particles, Projectile *p, {
|
|
p->move.attraction_point = pos;
|
|
});
|
|
|
|
if(i % delay) {
|
|
continue;
|
|
}
|
|
|
|
int stage = (5 * i) / time;
|
|
int nparts = stage + 1;
|
|
real power = (stage + 1) / 6.0;
|
|
real sdist = dist * glm_ease_quad_out(power);
|
|
|
|
for(int j = 0; j < nparts; ++j) {
|
|
Color c = *color;
|
|
randomize_hue(&c, hue_rand);
|
|
color_lerp(&c, RGBA(1, 1, 1, 0), rng_real() * 0.2);
|
|
Projectile *p = spawn_charge_particle(pos, sdist * (1 + 0.1 * rng_sreal()), &c, power);
|
|
ENT_ARRAY_ADD(&particles, p);
|
|
color_mul_scalar(&c, 0.2);
|
|
}
|
|
|
|
Color c = *color;
|
|
randomize_hue(&c, hue_rand);
|
|
color_lerp(&c, RGBA(1, 1, 1, 0), rng_real() * 0.2);
|
|
color_mul_scalar(&c, 0.5);
|
|
|
|
ENT_ARRAY_ADD(&particles, PARTICLE(
|
|
.sprite = "blast_huge_rays",
|
|
.color = &c,
|
|
.pos = pos,
|
|
.draw_rule = pdraw_timeout_scalefade(0, 1, 1, 0),
|
|
.move = move_towards(0, pos, 0.1),
|
|
.timeout = 30,
|
|
.flags = PFLAG_NOREFLECT | PFLAG_MANUALANGLE,
|
|
.scale = glm_ease_bounce_out(rayfactor * (i + 1)),
|
|
.angle = rng_angle(),
|
|
.layer = LAYER_PARTICLE_HIGH,
|
|
));
|
|
}
|
|
|
|
CANCEL_TASK(snd_stopper_task);
|
|
|
|
if(snd_discharge) {
|
|
replace_sfx(charge_snd_id, snd_discharge);
|
|
} else {
|
|
stop_sfx(charge_snd_id);
|
|
}
|
|
|
|
Color c = *color;
|
|
randomize_hue(&c, hue_rand);
|
|
color_mul_scalar(&c, 2.0);
|
|
|
|
cmplx pos = *anchor + offset;
|
|
|
|
PARTICLE(
|
|
.sprite = "blast_huge_halo",
|
|
.color = &c,
|
|
.pos = pos,
|
|
.draw_rule = pdraw_timeout_scalefade(0, 2, 1, 0),
|
|
.timeout = 30,
|
|
.flags = PFLAG_NOREFLECT,
|
|
.angle = rng_angle(),
|
|
.layer = LAYER_PARTICLE_HIGH,
|
|
);
|
|
|
|
ENT_ARRAY_FOREACH(&particles, Projectile *p, {
|
|
if(!(p->flags & PFLAG_MANUALANGLE)) {
|
|
p->move.attraction = 0;
|
|
p->move.acceleration += cnormalize(p->pos - pos);
|
|
p->move.retention = 0.8;
|
|
}
|
|
});
|
|
|
|
assert(time == wait_time);
|
|
return wait_time;
|
|
}
|
|
|
|
int common_charge(int time, const cmplx *anchor, cmplx offset, const Color *color) {
|
|
return common_charge_impl(time, anchor, offset, color, COMMON_CHARGE_SOUND_CHARGE, COMMON_CHARGE_SOUND_DISCHARGE);
|
|
}
|
|
|
|
int common_charge_static(int time, cmplx pos, const Color *color) {
|
|
cmplx anchor = 0;
|
|
return common_charge_impl(time, &anchor, pos, color, COMMON_CHARGE_SOUND_CHARGE, COMMON_CHARGE_SOUND_DISCHARGE);
|
|
}
|
|
|
|
int common_charge_custom(
|
|
int time,
|
|
const cmplx *anchor,
|
|
cmplx offset,
|
|
const Color *color,
|
|
const char *snd_charge,
|
|
const char *snd_discharge
|
|
) {
|
|
cmplx local_anchor = 0;
|
|
anchor = anchor ? anchor : &local_anchor;
|
|
return common_charge_impl(
|
|
time,
|
|
anchor,
|
|
offset,
|
|
color,
|
|
snd_charge,
|
|
snd_discharge
|
|
);
|
|
}
|
|
|
|
DEFINE_EXTERN_TASK(common_charge) {
|
|
Color local_color;
|
|
const Color *p_color = ARGS.color_ref;
|
|
if(!p_color) {
|
|
local_color = *NOT_NULL(ARGS.color);
|
|
p_color = &local_color;
|
|
}
|
|
|
|
if(ARGS.bind_to_entity.ent) {
|
|
TASK_BIND(ARGS.bind_to_entity);
|
|
}
|
|
|
|
common_charge_custom(
|
|
ARGS.time,
|
|
ARGS.anchor,
|
|
ARGS.pos,
|
|
p_color,
|
|
ARGS.sound.charge,
|
|
ARGS.sound.discharge
|
|
);
|
|
}
|
|
|
|
DEFINE_EXTERN_TASK(common_set_bitflags) {
|
|
assume(ARGS.pflags != NULL);
|
|
*ARGS.pflags = ((*ARGS.pflags & ARGS.mask) | ARGS.set);
|
|
}
|
|
|
|
DEFINE_EXTERN_TASK(common_kill_projectile) {
|
|
kill_projectile(TASK_BIND(ARGS.proj));
|
|
}
|
|
|
|
DEFINE_EXTERN_TASK(common_kill_enemy) {
|
|
enemy_kill(TASK_BIND(ARGS.enemy));
|
|
}
|
|
|
|
cmplx common_wander(cmplx origin, double dist, Rect bounds) {
|
|
int attempts = 32;
|
|
double angle = 0;
|
|
cmplx dest = 0;
|
|
cmplx dir = 0;
|
|
|
|
// assert(point_in_rect(origin, bounds));
|
|
|
|
while(attempts--) {
|
|
angle = rng_angle();
|
|
dir = cdir(angle);
|
|
dest = origin + dist * dir;
|
|
|
|
if(point_in_rect(dest, bounds)) {
|
|
return dest;
|
|
}
|
|
}
|
|
|
|
log_warn("Clipping fallback origin = %f%+fi dist = %f bounds.top_left = %f%+fi bounds.bottom_right = %f%+fi",
|
|
re(origin), im(origin),
|
|
dist,
|
|
re(bounds.top_left), im(bounds.top_left),
|
|
re(bounds.bottom_right), im(bounds.bottom_right)
|
|
);
|
|
|
|
// TODO: implement proper line-clipping here?
|
|
|
|
double step = cabs(bounds.bottom_right - bounds.top_left) / 16;
|
|
dir *= step;
|
|
dest = origin;
|
|
|
|
while(point_in_rect(dest + dir, bounds)) {
|
|
dest += dir;
|
|
}
|
|
|
|
return dest;
|
|
}
|
|
|
|
DEFINE_EXTERN_TASK(common_easing_animate) {
|
|
float from = *ARGS.value;
|
|
float scale = ARGS.to - from;
|
|
float ftime = ARGS.duration;
|
|
|
|
for(int t = 1; t <= ARGS.duration; t++) {
|
|
YIELD;
|
|
*ARGS.value = from + scale * ARGS.ease(t / ftime);
|
|
}
|
|
}
|
|
|
|
DEFINE_EXTERN_TASK(common_easing_animate_vec3) {
|
|
vec3 from;
|
|
glm_vec3_copy(*ARGS.value, from);
|
|
|
|
vec3 scale;
|
|
glm_vec3_sub(ARGS.to, from, scale);
|
|
|
|
float ftime = ARGS.duration;
|
|
|
|
for(int t = 1; t <= ARGS.duration; t++) {
|
|
YIELD;
|
|
|
|
float f = ARGS.ease(t / ftime);
|
|
vec3 d;
|
|
glm_vec3_scale(scale, f, d);
|
|
glm_vec3_add(from, d, *ARGS.value);
|
|
}
|
|
}
|
|
|
|
DEFINE_EXTERN_TASK(common_easing_animate_vec4) {
|
|
vec4 from;
|
|
glm_vec4_copy(*ARGS.value, from);
|
|
|
|
vec4 scale;
|
|
glm_vec4_sub((float*)ARGS.to, from, scale);
|
|
|
|
float ftime = ARGS.duration;
|
|
|
|
for(int t = 1; t <= ARGS.duration; t++) {
|
|
YIELD;
|
|
|
|
float f = ARGS.ease(t / ftime);
|
|
vec4 d;
|
|
glm_vec4_scale(scale, f, d);
|
|
glm_vec4_add(from, d, *ARGS.value);
|
|
}
|
|
}
|
|
|
|
void common_rotate_velocity(MoveParams *move, real angle, int duration) {
|
|
cmplx r = cdir(angle / duration);
|
|
move->retention *= r;
|
|
WAIT(duration);
|
|
move->retention /= r;
|
|
}
|
|
|
|
DEFINE_EXTERN_TASK(common_rotate_velocity) {
|
|
common_rotate_velocity(ARGS.move, ARGS.angle, ARGS.duration);
|
|
}
|
|
|
|
DEFINE_EXTERN_TASK(common_easing_animated) {
|
|
double from = *ARGS.value;
|
|
double scale = ARGS.to - from;
|
|
double ftime = ARGS.duration;
|
|
|
|
for(int t = 1; t <= ARGS.duration; t++) {
|
|
YIELD;
|
|
*ARGS.value = from + scale * ARGS.ease(t / ftime);
|
|
}
|
|
}
|
|
|
|
DEFINE_EXTERN_TASK(common_play_sfx) {
|
|
play_sfx(ARGS.name);
|
|
}
|