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 13:44:21 +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 "player.h"
|
|
|
|
|
2024-05-17 04:41:28 +02:00
|
|
|
#include "audio/audio.h"
|
|
|
|
#include "entity.h"
|
|
|
|
#include "gamepad.h"
|
2010-10-12 10:55:23 +02:00
|
|
|
#include "global.h"
|
2011-04-10 11:19:44 +02:00
|
|
|
#include "plrmodes.h"
|
2024-05-17 04:41:28 +02:00
|
|
|
#include "projectile.h"
|
|
|
|
#include "replay/stage.h"
|
|
|
|
#include "replay/struct.h"
|
2012-08-17 20:58:23 +02:00
|
|
|
#include "stage.h"
|
2018-07-04 10:36:16 +02:00
|
|
|
#include "stagedraw.h"
|
2024-05-17 04:41:28 +02:00
|
|
|
#include "stagetext.h"
|
2020-04-26 21:27:13 +02:00
|
|
|
#include "stats.h"
|
2020-01-04 04:24:24 +01:00
|
|
|
#include "util/glm.h"
|
2024-05-17 04:41:28 +02:00
|
|
|
#include "util/graphics.h"
|
2010-10-12 10:55:23 +02:00
|
|
|
|
2020-04-22 23:17:40 +02:00
|
|
|
DEFINE_ENTITY_TYPE(PlayerIndicators, {
|
|
|
|
Player *plr;
|
|
|
|
|
|
|
|
struct {
|
|
|
|
Sprite *focus;
|
|
|
|
} sprites;
|
|
|
|
|
|
|
|
float focus_alpha;
|
|
|
|
int focus_time;
|
|
|
|
});
|
|
|
|
|
2017-10-08 13:30:51 +02:00
|
|
|
void player_init(Player *plr) {
|
2011-06-25 12:41:40 +02:00
|
|
|
memset(plr, 0, sizeof(Player));
|
2019-03-05 12:50:29 +01:00
|
|
|
plr->pos = PLR_SPAWN_POS;
|
2017-03-21 11:09:32 +01:00
|
|
|
plr->lives = PLR_START_LIVES;
|
2011-07-02 12:45:32 +02:00
|
|
|
plr->bombs = PLR_START_BOMBS;
|
2019-02-22 00:56:03 +01:00
|
|
|
plr->point_item_value = PLR_START_PIV;
|
2022-10-03 20:36:21 +02:00
|
|
|
plr->power_stored = 100;
|
2011-06-25 12:41:40 +02:00
|
|
|
plr->deathtime = -1;
|
2017-10-29 23:45:24 +01:00
|
|
|
plr->continuetime = -1;
|
2021-06-04 02:33:11 +02:00
|
|
|
plr->bomb_triggertime = -1;
|
|
|
|
plr->bomb_endtime = 0;
|
2017-10-08 13:30:51 +02:00
|
|
|
plr->mode = plrmode_find(0, 0);
|
2011-07-04 09:14:08 +02:00
|
|
|
}
|
|
|
|
|
2017-10-08 13:30:51 +02:00
|
|
|
void player_stage_pre_init(Player *plr) {
|
2021-06-04 02:33:11 +02:00
|
|
|
plr->recoverytime = 0;
|
2017-02-15 17:09:09 +01:00
|
|
|
plr->respawntime = 0;
|
2021-06-04 02:33:11 +02:00
|
|
|
plr->bomb_triggertime = -1;
|
|
|
|
plr->bomb_endtime = 0;
|
2017-02-15 17:09:09 +01:00
|
|
|
plr->deathtime = -1;
|
|
|
|
plr->axis_lr = 0;
|
|
|
|
plr->axis_ud = 0;
|
2017-10-08 13:30:51 +02:00
|
|
|
}
|
|
|
|
|
2018-05-02 08:08:32 +02:00
|
|
|
double player_property(Player *plr, PlrProperty prop) {
|
|
|
|
return plr->mode->procs.property(plr, prop);
|
|
|
|
}
|
|
|
|
|
2018-04-13 21:13:48 +02:00
|
|
|
static void ent_draw_player(EntityInterface *ent);
|
2018-07-30 09:04:09 +02:00
|
|
|
static DamageResult ent_damage_player(EntityInterface *ent, const DamageInfo *dmg);
|
|
|
|
|
2020-03-13 21:54:07 +01:00
|
|
|
DECLARE_TASK(player_logic, { BoxedPlayer plr; });
|
2020-04-22 23:17:40 +02:00
|
|
|
DECLARE_TASK(player_indicators, { BoxedPlayer plr; });
|
2020-03-13 21:54:07 +01:00
|
|
|
|
2017-10-08 13:30:51 +02:00
|
|
|
void player_stage_post_init(Player *plr) {
|
|
|
|
assert(plr->mode != NULL);
|
|
|
|
|
|
|
|
// ensure the essential callbacks are there. other code tests only for the optional ones
|
2018-05-02 08:08:32 +02:00
|
|
|
assert(plr->mode->procs.property != NULL);
|
2018-07-15 17:48:22 +02:00
|
|
|
assert(plr->mode->character != NULL);
|
|
|
|
assert(plr->mode->dialog != NULL);
|
2017-10-24 04:57:14 +02:00
|
|
|
|
2020-04-27 21:58:25 +02:00
|
|
|
plrchar_render_bomb_portrait(plr->mode->character, &plr->bomb_portrait);
|
|
|
|
aniplayer_create(&plr->ani, plrchar_player_anim(plr->mode->character), "main");
|
2018-04-13 21:13:48 +02:00
|
|
|
|
|
|
|
plr->ent.draw_layer = LAYER_PLAYER;
|
|
|
|
plr->ent.draw_func = ent_draw_player;
|
2018-07-30 09:04:09 +02:00
|
|
|
plr->ent.damage_func = ent_damage_player;
|
2020-04-17 09:18:53 +02:00
|
|
|
ent_register(&plr->ent, ENT_TYPE_ID(Player));
|
2018-04-13 21:13:48 +02:00
|
|
|
|
2020-03-13 21:54:07 +01:00
|
|
|
COEVENT_INIT_ARRAY(plr->events);
|
|
|
|
|
|
|
|
INVOKE_TASK_DELAYED(1, player_logic, ENT_BOX(plr));
|
2020-04-22 23:17:40 +02:00
|
|
|
INVOKE_TASK_DELAYED(1, player_indicators, ENT_BOX(plr));
|
2020-03-13 21:54:07 +01:00
|
|
|
|
|
|
|
if(plr->mode->procs.init != NULL) {
|
|
|
|
plr->mode->procs.init(plr);
|
|
|
|
}
|
|
|
|
|
2019-02-22 00:56:03 +01:00
|
|
|
plr->extralife_threshold = player_next_extralife_threshold(plr->extralives_given);
|
|
|
|
|
|
|
|
while(plr->points >= plr->extralife_threshold) {
|
|
|
|
plr->extralife_threshold = player_next_extralife_threshold(++plr->extralives_given);
|
|
|
|
}
|
2017-02-15 17:09:09 +01:00
|
|
|
}
|
|
|
|
|
2017-11-25 16:51:43 +01:00
|
|
|
void player_free(Player *plr) {
|
2020-03-13 21:54:07 +01:00
|
|
|
COEVENT_CANCEL_ARRAY(plr->events);
|
2019-07-08 02:47:50 +02:00
|
|
|
r_texture_destroy(plr->bomb_portrait.tex);
|
2018-05-08 18:17:18 +02:00
|
|
|
aniplayer_free(&plr->ani);
|
2018-04-13 21:13:48 +02:00
|
|
|
ent_unregister(&plr->ent);
|
2017-11-25 16:51:43 +01:00
|
|
|
}
|
|
|
|
|
2017-03-19 03:56:55 +01:00
|
|
|
static void player_full_power(Player *plr) {
|
2018-01-06 10:24:46 +01:00
|
|
|
stage_clear_hazards(CLEAR_HAZARDS_ALL);
|
2020-06-09 03:33:22 +02:00
|
|
|
stagetext_add("Full Power!", VIEWPORT_W * 0.5 + VIEWPORT_H * 0.33 * I, ALIGN_CENTER, res_font("big"), RGB(1, 1, 1), 0, 60, 20, 20);
|
2017-03-19 03:56:55 +01:00
|
|
|
}
|
|
|
|
|
2022-10-03 20:36:21 +02:00
|
|
|
static int player_track_effective_power_change(Player *plr) {
|
|
|
|
int old_effective = plr->_prev_effective_power;
|
|
|
|
int new_effective = player_get_effective_power(plr);
|
2017-03-30 17:11:21 +02:00
|
|
|
|
2022-10-03 20:36:21 +02:00
|
|
|
if(old_effective != new_effective) {
|
2022-11-12 02:04:05 +01:00
|
|
|
plr->_prev_effective_power = new_effective;
|
2022-10-03 20:36:21 +02:00
|
|
|
coevent_signal(&plr->events.effective_power_changed);
|
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2022-10-03 20:36:21 +02:00
|
|
|
return new_effective;
|
|
|
|
}
|
2019-02-22 00:56:03 +01:00
|
|
|
|
2022-10-03 20:36:21 +02:00
|
|
|
bool player_set_power(Player *plr, short npow) {
|
|
|
|
int old_stored = plr->power_stored;
|
2023-09-28 15:27:33 +02:00
|
|
|
int new_stored = clamp(npow, 0, PLR_MAX_POWER_STORED);
|
2022-10-03 20:36:21 +02:00
|
|
|
plr->power_stored = new_stored;
|
2017-12-26 04:18:57 +01:00
|
|
|
|
2022-10-03 20:36:21 +02:00
|
|
|
if(old_stored / 100 < new_stored / 100) {
|
|
|
|
play_sfx("powerup");
|
2017-03-19 03:56:55 +01:00
|
|
|
}
|
2017-09-19 20:46:28 +02:00
|
|
|
|
2022-10-03 20:36:21 +02:00
|
|
|
bool change = old_stored != new_stored;
|
2020-03-13 21:54:07 +01:00
|
|
|
|
|
|
|
if(change) {
|
2022-10-03 20:36:21 +02:00
|
|
|
if(new_stored >= PLR_MAX_POWER_EFFECTIVE && old_stored < PLR_MAX_POWER_EFFECTIVE) {
|
|
|
|
player_full_power(plr);
|
|
|
|
}
|
|
|
|
|
|
|
|
coevent_signal(&plr->events.stored_power_changed);
|
2020-03-13 21:54:07 +01:00
|
|
|
}
|
|
|
|
|
2022-10-03 20:36:21 +02:00
|
|
|
player_track_effective_power_change(plr);
|
|
|
|
|
2020-03-13 21:54:07 +01:00
|
|
|
return change;
|
2019-02-22 00:56:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool player_add_power(Player *plr, short pdelta) {
|
2022-10-03 20:36:21 +02:00
|
|
|
return player_set_power(plr, plr->power_stored + pdelta);
|
|
|
|
}
|
|
|
|
|
|
|
|
int player_get_effective_power(Player *plr) {
|
2022-10-03 20:52:21 +02:00
|
|
|
int p;
|
|
|
|
|
|
|
|
if(player_is_powersurge_active(plr)) {
|
|
|
|
p = plr->powersurge.player_power;
|
|
|
|
} else {
|
|
|
|
p = plr->power_stored;
|
|
|
|
}
|
|
|
|
|
2023-09-28 15:27:33 +02:00
|
|
|
return clamp(p, 0, PLR_MAX_POWER_EFFECTIVE);
|
2011-07-04 09:14:08 +02:00
|
|
|
}
|
|
|
|
|
2019-11-22 04:37:11 +01:00
|
|
|
void player_move(Player *plr, cmplx delta) {
|
2018-05-02 08:08:32 +02:00
|
|
|
delta *= player_property(plr, PLR_PROP_SPEED);
|
2023-05-30 03:07:23 +02:00
|
|
|
plr->uncapped_velocity = delta;
|
2019-11-22 04:37:11 +01:00
|
|
|
cmplx lastpos = plr->pos;
|
2023-05-30 03:07:23 +02:00
|
|
|
cmplx ofs = CMPLX(PLR_MIN_BORDER_DIST, PLR_MIN_BORDER_DIST);
|
|
|
|
cmplx vp = CMPLX(VIEWPORT_W, VIEWPORT_H);
|
|
|
|
plr->pos = cwclamp(lastpos + delta, ofs, vp - ofs);
|
2020-04-22 23:35:06 +02:00
|
|
|
plr->velocity = plr->pos - lastpos;
|
2011-07-05 15:20:19 +02:00
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2019-07-03 19:50:43 +02:00
|
|
|
void player_draw_overlay(Player *plr) {
|
|
|
|
float a = 1 - plr->bomb_cutin_alpha;
|
|
|
|
|
|
|
|
if(a <= 0 || a >= 1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
r_state_push();
|
|
|
|
r_shader("sprite_default");
|
|
|
|
|
2023-09-28 15:27:33 +02:00
|
|
|
float char_in = clamp(a * 1.5f, 0, 1);
|
2023-09-23 21:56:34 +02:00
|
|
|
float char_out = min(1, 2 - (2 * a));
|
|
|
|
float char_opacity_in = 0.75 * min(1, a * 5);
|
2019-07-03 19:50:43 +02:00
|
|
|
float char_opacity = char_opacity_in * char_out * char_out;
|
|
|
|
float char_xofs = -20 * a;
|
|
|
|
|
2019-07-08 02:47:50 +02:00
|
|
|
Sprite *char_spr = &plr->bomb_portrait;
|
2019-07-03 19:50:43 +02:00
|
|
|
|
|
|
|
for(int i = 1; i <= 3; ++i) {
|
|
|
|
float t = a * 200;
|
|
|
|
float dur = 20;
|
|
|
|
float start = 200 - dur * 5;
|
|
|
|
float end = start + dur;
|
|
|
|
float ofs = 0.2 * dur * (i - 1);
|
|
|
|
float o = 1 - smoothstep(start + ofs, end + ofs, t);
|
|
|
|
|
|
|
|
r_draw_sprite(&(SpriteParams) {
|
|
|
|
.sprite_ptr = char_spr,
|
2023-09-23 21:56:34 +02:00
|
|
|
.pos = { char_spr->w * 0.5 + VIEWPORT_W * powf(1 - char_in, 4 - i * 0.3f) - i + char_xofs, VIEWPORT_H - char_spr->h * 0.5f },
|
2019-07-03 19:50:43 +02:00
|
|
|
.color = color_mul_scalar(color_add(RGBA(0.2, 0.2, 0.2, 0), RGBA(i==1, i==2, i==3, 0)), char_opacity_in * (1 - char_in * o) * o),
|
|
|
|
.flip.x = true,
|
2023-09-23 21:56:34 +02:00
|
|
|
.scale.both = 1.0f + 0.02f * (min(1, a * 1.2f)) + i * 0.5 * powf(1 - o, 2),
|
2019-07-03 19:50:43 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
r_draw_sprite(&(SpriteParams) {
|
|
|
|
.sprite_ptr = char_spr,
|
2023-09-23 21:56:34 +02:00
|
|
|
.pos = { char_spr->w * 0.5f + VIEWPORT_W * powf(1 - char_in, 4) + char_xofs, VIEWPORT_H - char_spr->h * 0.5f },
|
|
|
|
.color = RGBA_MUL_ALPHA(1, 1, 1, char_opacity * min(1, char_in * 2) * (1 - min(1, (1 - char_out) * 5))),
|
2019-07-03 19:50:43 +02:00
|
|
|
.flip.x = true,
|
|
|
|
.scale.both = 1.0 + 0.1 * (1 - char_out),
|
|
|
|
});
|
|
|
|
|
2023-09-23 21:56:34 +02:00
|
|
|
float spell_in = min(1, a * 3.0);
|
|
|
|
float spell_out = min(1, 3 - (3 * a));
|
|
|
|
float spell_opacity = min(1, a * 5) * spell_out * spell_out;
|
2019-07-03 19:50:43 +02:00
|
|
|
|
2023-09-23 21:56:34 +02:00
|
|
|
float spell_x = 128 * (1 - powf(1 - spell_in, 5)) + (VIEWPORT_W + 256) * powf(1 - spell_in, 3);
|
|
|
|
float spell_y = VIEWPORT_H - 128 * sqrtf(a);
|
2019-07-03 19:50:43 +02:00
|
|
|
|
2020-06-09 03:33:22 +02:00
|
|
|
Sprite *spell_spr = res_sprite("spell");
|
2019-07-03 19:50:43 +02:00
|
|
|
|
|
|
|
r_draw_sprite(&(SpriteParams) {
|
|
|
|
.sprite_ptr = spell_spr,
|
|
|
|
.pos = { spell_x, spell_y },
|
|
|
|
.color = color_mul_scalar(RGBA(1, 1, 1, spell_in * 0.5), spell_opacity),
|
|
|
|
.scale.both = 3 - 2 * (1 - pow(1 - spell_in, 3)) + 2 * (1 - spell_out),
|
|
|
|
});
|
|
|
|
|
2020-06-09 03:33:22 +02:00
|
|
|
Font *font = res_font("standard");
|
2019-07-03 19:50:43 +02:00
|
|
|
|
2019-10-12 14:02:15 +02:00
|
|
|
r_mat_mv_push();
|
|
|
|
r_mat_mv_translate(spell_x - spell_spr->w * 0.5 + 10, spell_y + 5 - font_get_metrics(font)->descent, 0);
|
2019-07-03 19:50:43 +02:00
|
|
|
|
|
|
|
TextParams tp = {
|
|
|
|
// .pos = { spell_x - spell_spr->w * 0.5 + 10, spell_y + 5 - font_get_metrics(font)->descent },
|
|
|
|
.shader = "text_hud",
|
|
|
|
.font_ptr = font,
|
|
|
|
.color = color_mul_scalar(RGBA(1, 1, 1, spell_in), spell_opacity),
|
|
|
|
};
|
|
|
|
|
2019-10-12 14:02:15 +02:00
|
|
|
r_mat_mv_push();
|
|
|
|
r_mat_mv_scale(2 - 1 * spell_opacity, 2 - 1 * spell_opacity, 1);
|
2019-07-03 19:50:43 +02:00
|
|
|
text_draw(plr->mode->spellcard_name, &tp);
|
2019-10-12 14:02:15 +02:00
|
|
|
r_mat_mv_pop();
|
2019-07-03 19:50:43 +02:00
|
|
|
|
2019-10-12 14:02:15 +02:00
|
|
|
r_mat_mv_pop();
|
2019-07-03 19:50:43 +02:00
|
|
|
r_state_pop();
|
|
|
|
}
|
|
|
|
|
2018-04-13 21:13:48 +02:00
|
|
|
static void ent_draw_player(EntityInterface *ent) {
|
|
|
|
Player *plr = ENT_CAST(ent, Player);
|
|
|
|
|
2019-03-05 12:50:29 +01:00
|
|
|
if(plr->deathtime >= global.frames) {
|
2018-05-19 01:24:49 +02:00
|
|
|
return;
|
|
|
|
}
|
2017-03-19 03:14:28 +01:00
|
|
|
|
2024-10-03 18:31:16 +02:00
|
|
|
ShaderCustomParams shader_params = { 1.0f };
|
|
|
|
ShaderProgram *shader = res_shader("sprite_particle");
|
|
|
|
|
2020-04-22 23:17:40 +02:00
|
|
|
if(plr->focus_circle_alpha) {
|
2018-04-13 21:13:48 +02:00
|
|
|
r_draw_sprite(&(SpriteParams) {
|
2024-09-22 21:06:57 +02:00
|
|
|
.sprite_ptr = res_sprite("fairy_circle"),
|
2024-10-03 18:31:16 +02:00
|
|
|
.shader_ptr = shader,
|
|
|
|
.shader_params = &shader_params,
|
2018-04-13 21:13:48 +02:00
|
|
|
.rotation.angle = DEG2RAD * global.frames * 10,
|
2020-04-22 23:17:40 +02:00
|
|
|
.color = RGBA_MUL_ALPHA(1, 1, 1, 0.2 * plr->focus_circle_alpha),
|
2023-09-20 04:14:15 +02:00
|
|
|
.pos = { re(plr->pos), im(plr->pos) },
|
2018-04-13 21:13:48 +02:00
|
|
|
});
|
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2018-04-13 21:13:48 +02:00
|
|
|
Color c;
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
if(!player_is_vulnerable(plr)) {
|
|
|
|
float f = 0.3*sin(0.1*global.frames);
|
|
|
|
c = *RGBA_MUL_ALPHA(1.0+f, 1.0, 1.0-f, 0.7+f);
|
2018-04-13 21:13:48 +02:00
|
|
|
} else {
|
2018-07-23 19:07:59 +02:00
|
|
|
c = *RGBA_MUL_ALPHA(1.0, 1.0, 1.0, 1.0);
|
2018-04-13 21:13:48 +02:00
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2018-04-13 21:13:48 +02:00
|
|
|
r_draw_sprite(&(SpriteParams) {
|
|
|
|
.sprite_ptr = aniplayer_get_frame(&plr->ani),
|
2024-10-03 18:31:16 +02:00
|
|
|
.shader_ptr = shader,
|
|
|
|
.shader_params = &shader_params,
|
2020-04-22 23:17:40 +02:00
|
|
|
.pos.as_cmplx = plr->pos,
|
2018-07-23 19:07:59 +02:00
|
|
|
.color = &c,
|
2018-04-13 21:13:48 +02:00
|
|
|
});
|
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2020-04-22 23:17:40 +02:00
|
|
|
static void player_draw_indicators(EntityInterface *ent) {
|
|
|
|
PlayerIndicators *indicators = ENT_CAST(ent, PlayerIndicators);
|
|
|
|
Player *plr = indicators->plr;
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2020-04-22 23:17:40 +02:00
|
|
|
float focus_opacity = indicators->focus_alpha;
|
|
|
|
int t = global.frames - indicators->focus_time;
|
2020-05-09 08:31:20 +02:00
|
|
|
cmplxf pos = plr->pos;
|
2018-05-08 09:06:33 +02:00
|
|
|
|
2019-02-22 00:56:03 +01:00
|
|
|
if(focus_opacity > 0) {
|
2020-04-22 23:17:40 +02:00
|
|
|
float trans_frames = 12;
|
2023-09-23 21:56:34 +02:00
|
|
|
float trans_factor = 1.0f - min(trans_frames, t) / trans_frames;
|
2020-04-22 23:17:40 +02:00
|
|
|
float rot_speed = DEG2RAD * global.frames * (1.0f + 3.0f * trans_factor);
|
|
|
|
float scale = 1.0f + trans_factor;
|
2018-04-13 21:13:48 +02:00
|
|
|
|
2020-04-22 23:17:40 +02:00
|
|
|
SpriteParams sp = {
|
|
|
|
.sprite_ptr = indicators->sprites.focus,
|
2019-02-22 00:56:03 +01:00
|
|
|
.rotation.angle = rot_speed,
|
2020-04-22 23:17:40 +02:00
|
|
|
.color = RGBA_MUL_ALPHA(1, 1, 1, focus_opacity),
|
|
|
|
.pos.as_cmplx = pos,
|
2019-02-22 00:56:03 +01:00
|
|
|
.scale.both = scale,
|
2020-04-22 23:17:40 +02:00
|
|
|
};
|
2019-02-22 00:56:03 +01:00
|
|
|
|
2020-04-22 23:17:40 +02:00
|
|
|
r_draw_sprite(&sp);
|
|
|
|
sp.rotation.angle *= -1;
|
|
|
|
r_draw_sprite(&sp);
|
2019-02-22 00:56:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
float ps_opacity = 1.0;
|
|
|
|
float ps_fill_factor = 1.0;
|
|
|
|
|
2020-04-22 23:17:40 +02:00
|
|
|
if(player_is_powersurge_active(plr)) {
|
|
|
|
ps_opacity *= clamp((global.frames - plr->powersurge.time.activated) / 30.0, 0, 1);
|
|
|
|
} else if(plr->powersurge.time.expired == 0) {
|
2019-02-22 00:56:03 +01:00
|
|
|
ps_opacity = 0;
|
|
|
|
} else {
|
2020-04-22 23:17:40 +02:00
|
|
|
ps_opacity *= (1 - clamp((global.frames - plr->powersurge.time.expired) / 40.0, 0, 1));
|
2019-02-22 00:56:03 +01:00
|
|
|
ps_fill_factor = pow(ps_opacity, 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(ps_opacity > 0) {
|
|
|
|
r_state_push();
|
2019-10-12 14:02:15 +02:00
|
|
|
r_mat_mv_push();
|
2023-09-20 04:14:15 +02:00
|
|
|
r_mat_mv_translate(re(pos), im(pos), 0);
|
2019-10-12 14:02:15 +02:00
|
|
|
r_mat_mv_scale(140, 140, 0);
|
2019-02-22 00:56:03 +01:00
|
|
|
r_shader("healthbar_radial");
|
|
|
|
r_uniform_vec4_rgba("borderColor", RGBA(0.5, 0.5, 0.5, 0.5));
|
|
|
|
r_uniform_vec4_rgba("glowColor", RGBA(0.5, 0.5, 0.5, 0.75));
|
|
|
|
r_uniform_vec4_rgba("fillColor", RGBA(1.5, 0.5, 0.0, 0.75));
|
|
|
|
r_uniform_vec4_rgba("altFillColor", RGBA(0.0, 0.5, 1.5, 0.75));
|
|
|
|
r_uniform_vec4_rgba("coreFillColor", RGBA(0.8, 0.8, 0.8, 0.25));
|
2020-04-22 23:17:40 +02:00
|
|
|
r_uniform_vec2("fill", plr->powersurge.positive * ps_fill_factor, plr->powersurge.negative * ps_fill_factor);
|
2019-02-22 00:56:03 +01:00
|
|
|
r_uniform_float("opacity", ps_opacity);
|
|
|
|
r_draw_quad();
|
2019-10-12 14:02:15 +02:00
|
|
|
r_mat_mv_pop();
|
2019-02-22 00:56:03 +01:00
|
|
|
r_state_pop();
|
|
|
|
|
|
|
|
char buf[64];
|
2020-04-22 23:17:40 +02:00
|
|
|
format_huge_num(0, plr->powersurge.bonus.baseline, sizeof(buf), buf);
|
2020-06-09 03:33:22 +02:00
|
|
|
Font *fnt = res_font("monotiny");
|
2019-02-22 00:56:03 +01:00
|
|
|
|
2023-09-20 04:14:15 +02:00
|
|
|
float x = re(pos);
|
|
|
|
float y = im(pos) + 80;
|
2019-02-22 00:56:03 +01:00
|
|
|
|
|
|
|
float text_opacity = ps_opacity * 0.75;
|
|
|
|
|
|
|
|
x += text_draw(buf, &(TextParams) {
|
|
|
|
.shader = "text_hud",
|
|
|
|
.font_ptr = fnt,
|
|
|
|
.pos = { x, y },
|
|
|
|
.color = RGBA_MUL_ALPHA(1.0, 1.0, 1.0, text_opacity),
|
|
|
|
.align = ALIGN_CENTER,
|
|
|
|
});
|
|
|
|
|
2020-04-22 23:17:40 +02:00
|
|
|
snprintf(buf, sizeof(buf), " +%u", plr->powersurge.bonus.gain_rate);
|
2019-02-22 00:56:03 +01:00
|
|
|
|
|
|
|
text_draw(buf, &(TextParams) {
|
|
|
|
.shader = "text_hud",
|
|
|
|
.font_ptr = fnt,
|
|
|
|
.pos = { x, y },
|
|
|
|
.color = RGBA_MUL_ALPHA(0.3, 0.6, 1.0, text_opacity),
|
|
|
|
.align = ALIGN_LEFT,
|
|
|
|
});
|
|
|
|
|
|
|
|
r_shader("sprite_filled_circle");
|
|
|
|
r_uniform_vec4("color_inner", 0, 0, 0, 0);
|
|
|
|
r_uniform_vec4("color_outer", 0, 0, 0.1 * ps_opacity, 0.1 * ps_opacity);
|
|
|
|
}
|
2018-04-13 21:13:48 +02:00
|
|
|
}
|
|
|
|
|
2020-04-22 23:17:40 +02:00
|
|
|
DEFINE_TASK(player_indicators) {
|
|
|
|
PlayerIndicators *indicators = TASK_HOST_ENT(PlayerIndicators);
|
|
|
|
indicators->plr = TASK_BIND(ARGS.plr);
|
|
|
|
indicators->ent.draw_layer = LAYER_PLAYER_FOCUS;
|
|
|
|
indicators->ent.draw_func = player_draw_indicators;
|
2020-06-09 03:33:22 +02:00
|
|
|
indicators->sprites.focus = res_sprite("focus");
|
2020-04-22 23:17:40 +02:00
|
|
|
|
|
|
|
Player *plr = indicators->plr;
|
|
|
|
|
|
|
|
bool was_focused = false;
|
|
|
|
bool is_focused = false;
|
|
|
|
|
|
|
|
for(;;) {
|
|
|
|
is_focused = (plr->inputflags & INFLAG_FOCUS) && player_is_alive(plr);
|
|
|
|
|
|
|
|
if(is_focused && !was_focused) {
|
|
|
|
indicators->focus_time = global.frames;
|
2023-09-23 21:56:34 +02:00
|
|
|
indicators->focus_alpha = min(indicators->focus_alpha, 0.1f);
|
2020-04-22 23:17:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
was_focused = is_focused;
|
|
|
|
fapproach_p(&indicators->focus_alpha, is_focused, 1.0f/30.0f);
|
|
|
|
|
|
|
|
YIELD;
|
|
|
|
}
|
2010-10-12 10:55:23 +02:00
|
|
|
}
|
|
|
|
|
2017-04-04 02:57:38 +02:00
|
|
|
static void player_fail_spell(Player *plr) {
|
2021-05-14 05:28:16 +02:00
|
|
|
Boss *boss = global.boss;
|
|
|
|
|
|
|
|
if(!boss || global.stage->type == STAGE_SPELL) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Attack *atk = boss->current;
|
|
|
|
|
|
|
|
if(!atk || !attack_is_active(atk) || attack_was_failed(atk)) {
|
2017-04-04 02:57:38 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
global.boss->current->failtime = global.frames;
|
|
|
|
|
|
|
|
if(global.boss->current->type == AT_ExtraSpell) {
|
|
|
|
boss_finish_current_attack(global.boss);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-01 22:08:40 +02:00
|
|
|
bool player_should_shoot(Player *plr) {
|
2018-07-31 09:07:34 +02:00
|
|
|
return
|
|
|
|
(plr->inputflags & INFLAG_SHOT) &&
|
2019-07-03 19:50:43 +02:00
|
|
|
!dialog_is_active(global.dialog) &&
|
2020-04-01 22:08:40 +02:00
|
|
|
player_is_alive(&global.plr);
|
2017-10-08 13:30:51 +02:00
|
|
|
}
|
|
|
|
|
2018-07-30 15:14:27 +02:00
|
|
|
void player_placeholder_bomb_logic(Player *plr) {
|
2018-07-31 09:07:34 +02:00
|
|
|
if(!player_is_bomb_active(plr)) {
|
2018-07-30 15:14:27 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
DamageInfo dmg;
|
|
|
|
dmg.amount = 100;
|
|
|
|
dmg.type = DMG_PLAYER_BOMB;
|
|
|
|
|
|
|
|
for(Enemy *en = global.enemies.first; en; en = en->next) {
|
|
|
|
ent_damage(&en->ent, &dmg);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(global.boss) {
|
|
|
|
ent_damage(&global.boss->ent, &dmg);
|
|
|
|
}
|
|
|
|
|
|
|
|
stage_clear_hazards(CLEAR_HAZARDS_ALL);
|
|
|
|
}
|
|
|
|
|
2019-02-22 00:56:03 +01:00
|
|
|
static void player_powersurge_expired(Player *plr);
|
|
|
|
|
|
|
|
static void _powersurge_trail_draw(Projectile *p, float t, float cmul) {
|
|
|
|
float nt = t / p->timeout;
|
|
|
|
float s = 1 + (2 + 0.5 * psin((t+global.frames*1.23)/5.0)) * nt * nt;
|
|
|
|
|
|
|
|
r_draw_sprite(&(SpriteParams) {
|
|
|
|
.sprite_ptr = p->sprite,
|
|
|
|
.scale.both = s,
|
2023-09-20 04:14:15 +02:00
|
|
|
.pos = { re(p->pos), im(p->pos) },
|
2019-02-22 00:56:03 +01:00
|
|
|
.color = color_mul_scalar(RGBA(0.8, 0.1 + 0.2 * psin((t+global.frames)/5.0), 0.1, 0.0), 0.5 * (1 - nt) * cmul),
|
|
|
|
.shader_params = &(ShaderCustomParams){{ -2 * nt * nt }},
|
2020-03-05 19:38:48 +01:00
|
|
|
.shader_ptr = p->shader,
|
2019-02-22 00:56:03 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-12-11 10:25:57 +01:00
|
|
|
static void powersurge_trail_draw(Projectile *p, int t, ProjDrawRuleArgs args) {
|
2019-02-22 00:56:03 +01:00
|
|
|
if(t > 0) {
|
|
|
|
_powersurge_trail_draw(p, t - 0.5, 0.25);
|
|
|
|
_powersurge_trail_draw(p, t, 0.25);
|
|
|
|
} else {
|
|
|
|
_powersurge_trail_draw(p, t, 0.5);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-22 00:31:48 +01:00
|
|
|
TASK(powersurge_player_particles, { BoxedPlayer plr; }) {
|
|
|
|
Player *plr = TASK_BIND(ARGS.plr);
|
2019-02-22 00:56:03 +01:00
|
|
|
|
2021-02-22 00:31:48 +01:00
|
|
|
DECLARE_ENT_ARRAY(Projectile, trails, 32);
|
|
|
|
DECLARE_ENT_ARRAY(Projectile, fields, 4);
|
|
|
|
|
|
|
|
ShaderProgram *trail_shader = res_shader("sprite_silhouette");
|
|
|
|
Sprite *field_sprite = res_sprite("part/powersurge_field");
|
2019-02-22 00:56:03 +01:00
|
|
|
|
2021-02-22 00:31:48 +01:00
|
|
|
for(int t = 0; player_is_powersurge_active(plr); ++t, YIELD) {
|
|
|
|
ENT_ARRAY_COMPACT(&trails);
|
|
|
|
ENT_ARRAY_ADD(&trails, PARTICLE(
|
|
|
|
.sprite_ptr = aniplayer_get_frame(&plr->ani),
|
|
|
|
.shader_ptr = trail_shader,
|
|
|
|
.pos = plr->pos,
|
|
|
|
.color = RGBA(1, 1, 1, 0.5),
|
|
|
|
.draw_rule = powersurge_trail_draw,
|
2023-02-27 07:10:02 +01:00
|
|
|
.move = move_towards(0, plr->pos, 0),
|
2021-02-22 00:31:48 +01:00
|
|
|
.timeout = 15,
|
|
|
|
.layer = LAYER_PARTICLE_HIGH,
|
|
|
|
.flags = PFLAG_NOREFLECT | PFLAG_MANUALANGLE,
|
|
|
|
));
|
|
|
|
|
|
|
|
if(t % 6 == 0 && plr->powersurge.bonus.discharge_range > 0) {
|
|
|
|
real scale = 2 * plr->powersurge.bonus.discharge_range / field_sprite->w;
|
|
|
|
real angle = rng_angle();
|
|
|
|
Color *color = color_mul_scalar(rng_bool() ? RGBA(1.5, 0.5, 0.0, 0.1) : RGBA(0.0, 0.5, 1.5, 0.1), 0.25);
|
|
|
|
|
|
|
|
ENT_ARRAY_COMPACT(&fields);
|
|
|
|
|
|
|
|
ENT_ARRAY_ADD(&fields, PARTICLE(
|
|
|
|
.sprite_ptr = field_sprite,
|
|
|
|
.pos = plr->pos,
|
|
|
|
.color = color,
|
|
|
|
.draw_rule = pdraw_timeout_fade(1, 0),
|
|
|
|
.timeout = 14,
|
|
|
|
.angle = angle,
|
|
|
|
.layer = LAYER_PLAYER - 1,
|
|
|
|
.flags = PFLAG_NOREFLECT | PFLAG_NOMOVE | PFLAG_REQUIREDPARTICLE,
|
|
|
|
.scale = scale,
|
|
|
|
));
|
|
|
|
|
|
|
|
ENT_ARRAY_ADD(&fields, PARTICLE(
|
|
|
|
.sprite_ptr = field_sprite,
|
|
|
|
.pos = plr->pos,
|
|
|
|
.color = RGBA(0.5, 0.5, 0.5, 0),
|
|
|
|
.draw_rule = pdraw_timeout_fade(1, 0),
|
|
|
|
.timeout = 3,
|
|
|
|
.angle = angle,
|
|
|
|
.layer = LAYER_PLAYER - 1,
|
|
|
|
.flags = PFLAG_NOREFLECT | PFLAG_NOMOVE,
|
|
|
|
.scale = scale,
|
|
|
|
));
|
|
|
|
}
|
2019-02-22 00:56:03 +01:00
|
|
|
|
2021-02-22 00:31:48 +01:00
|
|
|
ENT_ARRAY_FOREACH(&trails, Projectile *p, {
|
|
|
|
p->move.attraction_point = plr->pos;
|
|
|
|
p->move.attraction = 1 - (global.frames - p->birthtime) / p->timeout;
|
|
|
|
});
|
|
|
|
|
|
|
|
ENT_ARRAY_FOREACH(&fields, Projectile *p, {
|
|
|
|
p->pos = plr->pos;
|
|
|
|
});
|
|
|
|
}
|
2019-02-22 00:56:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void player_powersurge_calc_bonus(Player *plr, PowerSurgeBonus *b) {
|
|
|
|
b->gain_rate = round(1000 * plr->powersurge.negative * plr->powersurge.negative);
|
2022-10-03 20:52:21 +02:00
|
|
|
b->baseline = plr->powersurge.total_charge + plr->powersurge.damage_done * 0.4;
|
2019-02-22 00:56:03 +01:00
|
|
|
b->score = b->baseline;
|
|
|
|
b->discharge_power = sqrtf(0.2 * b->baseline + 1024 * log1pf(b->baseline)) * smoothstep(0, 1, 0.0001 * b->baseline);
|
|
|
|
b->discharge_range = 1.2 * b->discharge_power;
|
|
|
|
b->discharge_damage = 10 * pow(b->discharge_power, 1.1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void player_powersurge_logic(Player *plr) {
|
2019-07-03 19:50:43 +02:00
|
|
|
if(dialog_is_active(global.dialog)) {
|
2019-02-28 11:00:35 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-09-23 21:56:34 +02:00
|
|
|
plr->powersurge.positive = max(0, plr->powersurge.positive - lerp(PLR_POWERSURGE_POSITIVE_DRAIN_MIN, PLR_POWERSURGE_POSITIVE_DRAIN_MAX, plr->powersurge.positive));
|
|
|
|
plr->powersurge.negative = max(0, plr->powersurge.negative - lerp(PLR_POWERSURGE_NEGATIVE_DRAIN_MIN, PLR_POWERSURGE_NEGATIVE_DRAIN_MAX, plr->powersurge.negative));
|
2019-02-22 00:56:03 +01:00
|
|
|
|
|
|
|
if(stage_is_cleared()) {
|
|
|
|
player_cancel_powersurge(plr);
|
2019-02-28 11:00:35 +01:00
|
|
|
return;
|
2019-02-22 00:56:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if(plr->powersurge.positive <= plr->powersurge.negative) {
|
|
|
|
player_powersurge_expired(plr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
player_powersurge_calc_bonus(plr, &plr->powersurge.bonus);
|
|
|
|
|
2022-10-03 20:52:21 +02:00
|
|
|
plr->powersurge.total_charge += plr->powersurge.bonus.gain_rate;
|
2019-02-22 00:56:03 +01:00
|
|
|
}
|
|
|
|
|
2020-03-13 21:54:07 +01:00
|
|
|
DEFINE_TASK(player_logic) {
|
|
|
|
Player *plr = TASK_BIND(ARGS.plr);
|
|
|
|
uint prev_inputflags = 0;
|
|
|
|
|
2022-10-03 20:36:21 +02:00
|
|
|
plr->_prev_effective_power = player_get_effective_power(plr);
|
|
|
|
|
2020-03-13 21:54:07 +01:00
|
|
|
for(;;) {
|
|
|
|
YIELD;
|
|
|
|
|
|
|
|
if(prev_inputflags != plr->inputflags) {
|
|
|
|
coevent_signal(&plr->events.inputflags_changed);
|
|
|
|
prev_inputflags = plr->inputflags;
|
|
|
|
}
|
|
|
|
|
|
|
|
fapproach_p(&plr->bomb_cutin_alpha, 0, 1/200.0);
|
|
|
|
|
2021-05-30 03:26:21 +02:00
|
|
|
if(
|
|
|
|
plr->respawntime - PLR_RESPAWN_TIME/2 == global.frames &&
|
|
|
|
plr->lives < 0 && global.replay.input.replay == NULL
|
|
|
|
) {
|
2020-03-13 21:54:07 +01:00
|
|
|
stage_gameover();
|
|
|
|
}
|
|
|
|
|
|
|
|
if(plr->continuetime == global.frames) {
|
|
|
|
plr->lives = PLR_START_LIVES;
|
|
|
|
plr->bombs = PLR_START_BOMBS;
|
|
|
|
plr->point_item_value = PLR_START_PIV;
|
|
|
|
plr->life_fragments = 0;
|
|
|
|
plr->bomb_fragments = 0;
|
2020-04-26 21:27:13 +02:00
|
|
|
stats_track_continue_used(&plr->stats);
|
2020-03-13 21:54:07 +01:00
|
|
|
player_set_power(plr, 0);
|
|
|
|
stage_clear_hazards(CLEAR_HAZARDS_ALL);
|
2022-10-03 20:36:21 +02:00
|
|
|
spawn_items(plr->deathpos, ITEM_POWER, (int)ceil(PLR_MAX_POWER_EFFECTIVE/(real)POWER_VALUE));
|
2020-03-13 21:54:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
aniplayer_update(&plr->ani);
|
|
|
|
|
|
|
|
if(plr->lives < 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(plr->respawntime > global.frames) {
|
|
|
|
double x = PLR_SPAWN_POS_X;
|
|
|
|
double y = lerp(PLR_SPAWN_POS_Y, VIEWPORT_H + 64, smoothstep(0.0, 1.0, (plr->respawntime - global.frames) / (double)PLR_RESPAWN_TIME));
|
|
|
|
plr->pos = CMPLX(x, y);
|
|
|
|
stage_clear_hazards(CLEAR_HAZARDS_ALL | CLEAR_HAZARDS_NOW);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(player_is_powersurge_active(plr)) {
|
|
|
|
player_powersurge_logic(plr);
|
|
|
|
}
|
|
|
|
|
2020-04-22 23:17:40 +02:00
|
|
|
fapproach_p(&plr->focus_circle_alpha, !!(plr->inputflags & INFLAG_FOCUS), 1.0f/15.0f);
|
2020-03-13 21:54:07 +01:00
|
|
|
|
2020-04-01 22:08:40 +02:00
|
|
|
if(player_should_shoot(plr)) {
|
2020-03-13 21:54:07 +01:00
|
|
|
coevent_signal(&plr->events.shoot);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(global.frames == plr->deathtime) {
|
|
|
|
player_realdeath(plr);
|
|
|
|
} else if(plr->deathtime > global.frames) {
|
|
|
|
stage_clear_hazards(CLEAR_HAZARDS_ALL | CLEAR_HAZARDS_NOW);
|
|
|
|
}
|
2022-10-03 20:36:21 +02:00
|
|
|
|
|
|
|
player_track_effective_power_change(plr);
|
2012-08-03 17:06:25 +02:00
|
|
|
}
|
2020-03-13 21:54:07 +01:00
|
|
|
}
|
2012-08-03 17:06:25 +02:00
|
|