taisei/src/player.c

556 lines
12 KiB
C
Raw Normal View History

/*
* This software is licensed under the terms of the MIT-License
* See COPYING for further information.
* ---
2017-09-12 03:28:15 +02:00
* Copyright (c) 2011-2017, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2017, Andrei Alexeyev <akari@alienslab.net>.
*/
#include "player.h"
#include "projectile.h"
#include "global.h"
#include "plrmodes.h"
#include "stage.h"
void init_player(Player *plr) {
memset(plr, 0, sizeof(Player));
plr->pos = VIEWPORT_W/2 + I*(VIEWPORT_H-64);
plr->lives = PLR_START_LIVES;
plr->bombs = PLR_START_BOMBS;
plr->deathtime = -1;
}
void prepare_player_for_next_stage(Player *plr) {
plr->recovery = 0;
plr->respawntime = 0;
plr->deathtime = -1;
plr->graze = 0;
plr->movetime = 0;
plr->prevmove = 0;
plr->prevmovetime = 0;
plr->axis_lr = 0;
plr->axis_ud = 0;
}
2012-04-06 17:50:17 +02:00
Animation *player_get_ani(Character cha) {
Animation *ani = NULL;
switch(cha) {
case Youmu:
2012-04-06 17:50:17 +02:00
ani = get_ani("youmu");
break;
case Marisa:
2012-04-06 17:50:17 +02:00
ani = get_ani("marisa");
break;
}
2012-04-06 17:50:17 +02:00
return ani;
}
static void player_full_power(Player *plr) {
play_sound("full_power");
stage_clear_hazards(false);
}
void player_set_power(Player *plr, short npow) {
2017-03-30 17:11:21 +02:00
npow = clamp(npow, 0, PLR_MAX_POWER);
switch(plr->cha) {
case Youmu:
youmu_power(plr, npow);
break;
case Marisa:
marisa_power(plr, npow);
break;
}
int oldpow = plr->power;
plr->power = npow;
if(plr->power == PLR_MAX_POWER && oldpow < PLR_MAX_POWER) {
player_full_power(plr);
}
}
2012-07-20 16:11:24 +02:00
void player_move(Player *plr, complex delta) {
float speed = 0.01*VIEWPORT_W;
if(plr->inputflags & INFLAG_FOCUS)
speed /= 2.0;
2011-07-05 15:20:19 +02:00
if(plr->cha == Marisa && plr->shot == MarisaLaser && global.frames - plr->recovery < 0)
speed /= 5.0;
2011-07-05 15:20:19 +02:00
complex opos = plr->pos - VIEWPORT_W/2.0 - VIEWPORT_H/2.0*I;
complex npos = opos + delta*speed;
2012-04-06 17:50:17 +02:00
Animation *ani = player_get_ani(plr->cha);
2012-04-06 17:50:17 +02:00
short xfac = fabs(creal(npos)) < fabs(creal(opos)) || fabs(creal(npos)) < VIEWPORT_W/2.0 - ani->w/2;
short yfac = fabs(cimag(npos)) < fabs(cimag(opos)) || fabs(cimag(npos)) < VIEWPORT_H/2.0 - ani->h/2;
2011-07-05 15:20:19 +02:00
plr->pos += (creal(delta)*xfac + cimag(delta)*yfac*I)*speed;
}
void player_draw(Player* plr) {
if(plr->deathtime > global.frames)
return;
draw_enemies(plr->slaves);
glPushMatrix();
glTranslatef(creal(plr->pos), cimag(plr->pos), 0);
if(plr->focus) {
glPushMatrix();
glRotatef(global.frames*10, 0, 0, 1);
glScalef(1, 1, 1);
glColor4f(1, 1, 1, 0.2 * (clamp(plr->focus, 0, 15) / 15.0));
draw_texture(0, 0, "fairy_circle");
glColor4f(1,1,1,1);
glPopMatrix();
}
glDisable(GL_CULL_FACE);
if(plr->dir) {
glPushMatrix();
glScalef(-1,1,1);
}
int clr_changed = 0;
if(global.frames - abs(plr->recovery) < 0 && (global.frames/8)&1) {
glColor4f(0.4,0.4,1,0.9);
clr_changed = 1;
}
2012-04-06 17:50:17 +02:00
draw_animation_p(0, 0, !plr->moving, player_get_ani(plr->cha));
if(clr_changed)
glColor3f(1,1,1);
if(plr->dir)
glPopMatrix();
glEnable(GL_CULL_FACE);
if(plr->focus) {
glPushMatrix();
glColor4f(1, 1, 1, plr->focus / 30.0);
glRotatef(global.frames, 0, 0, -1);
draw_texture(0, 0, "focus");
glColor4f(1, 1, 1, 1);
glPopMatrix();
}
glPopMatrix();
}
2017-04-04 02:57:38 +02:00
static void player_fail_spell(Player *plr) {
if( !global.boss ||
!global.boss->current ||
global.boss->current->finished ||
global.boss->current->failtime ||
global.stage->type == STAGE_SPELL
) {
return;
}
global.boss->current->failtime = global.frames;
if(global.boss->current->type == AT_ExtraSpell) {
boss_finish_current_attack(global.boss);
}
}
void player_logic(Player* plr) {
2011-07-05 15:20:19 +02:00
process_enemies(&plr->slaves);
2011-07-04 14:10:12 +02:00
if(plr->deathtime < -1) {
plr->deathtime++;
plr->pos -= I;
2011-07-04 14:10:12 +02:00
return;
}
2012-08-11 14:24:58 +02:00
plr->focus = approach(plr->focus, (plr->inputflags & INFLAG_FOCUS) ? 30 : 0, 1);
2012-08-11 14:24:58 +02:00
2012-08-03 17:06:25 +02:00
switch(plr->cha) {
case Youmu:
youmu_shot(plr);
break;
case Marisa:
marisa_shot(plr);
break;
2012-08-03 17:06:25 +02:00
}
if(global.frames == plr->deathtime)
2012-07-20 16:11:24 +02:00
player_realdeath(plr);
2012-08-11 14:24:58 +02:00
2011-11-01 21:20:40 +01:00
if(global.frames - plr->recovery < 0) {
Enemy *en;
for(en = global.enemies; en; en = en->next)
2012-08-11 14:24:58 +02:00
if(!en->unbombable && en->hp > ENEMY_IMMUNE)
2012-07-28 19:45:51 +02:00
en->hp -= 300;
2012-08-11 14:24:58 +02:00
if(global.boss && boss_is_vulnerable(global.boss)) {
global.boss->current->hp -= 30;
2012-07-28 19:45:51 +02:00
}
2017-04-04 02:57:38 +02:00
stage_clear_hazards(false);
2017-04-04 02:57:38 +02:00
player_fail_spell(plr);
2011-11-01 21:20:40 +01:00
}
}
2012-07-20 16:11:24 +02:00
void player_bomb(Player *plr) {
if(global.boss && global.boss->current && global.boss->current->type == AT_ExtraSpell)
return;
if(global.frames - plr->recovery >= 0 && (plr->bombs > 0 || plr->iddqd) && global.frames - plr->respawntime >= 60) {
2017-04-04 02:57:38 +02:00
player_fail_spell(plr);
delete_projectiles(&global.projs);
2011-07-05 15:20:19 +02:00
switch(plr->cha) {
case Marisa:
marisa_bomb(plr);
break;
case Youmu:
youmu_bomb(plr);
break;
}
plr->bombs--;
if(plr->deathtime > 0) {
plr->deathtime = -1;
if(plr->bombs)
plr->bombs--;
}
if(plr->bombs < 0) {
plr->bombs = 0;
}
2011-07-05 15:20:19 +02:00
plr->recovery = global.frames + BOMB_RECOVERY;
}
}
2012-07-20 16:11:24 +02:00
void player_realdeath(Player *plr) {
2011-07-04 14:10:12 +02:00
plr->deathtime = -DEATH_DELAY-1;
plr->respawntime = global.frames;
plr->inputflags &= ~INFLAGS_MOVE;
const double arc = 0.3 * M_PI;
int drop = max(2, (plr->power * 0.15) / POWER_VALUE);
for(int i = 0; i < drop; ++i) {
double ofs = arc * (i/((double)drop - 1));
create_item(plr->pos, (12+3*frand()) * (cexp(I*(1.5*M_PI - 0.5*arc + ofs)) - 1.0*I), Power);
}
plr->pos = VIEWPORT_W/2 + VIEWPORT_H*I+30.0*I;
2011-07-04 14:21:36 +02:00
plr->recovery = -(global.frames + DEATH_DELAY + 150);
plr->bombs = PLR_START_BOMBS;
plr->bomb_fragments = 0;
if(plr->iddqd)
return;
2017-04-04 02:57:38 +02:00
player_fail_spell(plr);
player_set_power(plr, plr->power * 0.7);
if(plr->lives-- == 0 && global.replaymode != REPLAY_PLAY)
stage_gameover();
}
2012-07-20 16:11:24 +02:00
void player_death(Player *plr) {
if(plr->deathtime == -1 && global.frames - abs(plr->recovery) > 0) {
2017-01-28 19:56:17 +01:00
play_sound("death");
int i;
for(i = 0; i < 20; i++) {
tsrand_fill(2);
create_particle2c("flare", plr->pos, 0, Shrink, timeout_linear, 40, (3+afrand(0)*7)*cexp(I*tsrand_a(1)))->type=PlrProj;
}
2017-03-09 13:21:36 +01:00
create_particle2c("blast", plr->pos, rgba(1,0.3,0.3,0.5), GrowFadeAdd, timeout, 35, 2.4)->type=PlrProj;
plr->deathtime = global.frames + DEATHBOMB_TIME;
}
}
static PlrInputFlag key_to_inflag(KeyIndex key) {
switch(key) {
case KEY_UP: return INFLAG_UP; break;
case KEY_DOWN: return INFLAG_DOWN; break;
case KEY_LEFT: return INFLAG_LEFT; break;
case KEY_RIGHT: return INFLAG_RIGHT; break;
case KEY_FOCUS: return INFLAG_FOCUS; break;
case KEY_SHOT: return INFLAG_SHOT; break;
default: return 0;
}
}
void player_setinputflag(Player *plr, KeyIndex key, bool mode) {
PlrInputFlag flag = key_to_inflag(key);
if(!flag) {
return;
}
2012-08-12 18:00:56 +02:00
if(mode) {
if(flag & INFLAGS_MOVE) {
plr->prevmove = plr->curmove;
plr->prevmovetime = plr->movetime;
plr->curmove = flag;
plr->movetime = global.frames;
}
plr->inputflags |= flag;
2012-08-12 18:00:56 +02:00
} else {
plr->inputflags &= ~flag;
2012-08-12 18:00:56 +02:00
}
}
void player_event(Player* plr, int type, int key) {
switch(type) {
case EV_PRESS:
switch(key) {
case KEY_BOMB:
2012-07-20 16:11:24 +02:00
player_bomb(plr);
break;
case KEY_IDDQD:
plr->iddqd = !plr->iddqd;
break;
2017-03-06 13:02:46 +01:00
case KEY_POWERUP:
player_set_power(plr, plr->power + 100);
break;
case KEY_POWERDOWN:
player_set_power(plr, plr->power - 100);
break;
default:
player_setinputflag(plr, key, true);
break;
}
break;
case EV_RELEASE:
player_setinputflag(plr, key, false);
break;
2012-08-15 16:36:39 +02:00
case EV_AXIS_LR:
plr->axis_lr = key;
break;
2012-08-15 16:36:39 +02:00
case EV_AXIS_UD:
plr->axis_ud = key;
break;
default:
log_warn("Unknown event type %d (value=%d)", type, key);
break;
}
}
2012-08-15 16:36:39 +02:00
// free-axis movement
bool player_applymovement_gamepad(Player *plr) {
if(!plr->axis_lr && !plr->axis_ud) {
if(plr->gamepadmove) {
plr->gamepadmove = false;
plr->inputflags &= ~INFLAGS_MOVE;
}
return false;
}
2017-02-27 23:58:47 +01:00
complex direction = (plr->axis_lr + plr->axis_ud*I) / (double)GAMEPAD_AXIS_MAX;
if(cabs(direction) > 1)
direction /= cabs(direction);
2017-03-06 16:32:51 +01:00
int sr = sign(creal(direction));
int si = sign(cimag(direction));
player_setinputflag(plr, KEY_UP, si == -1);
player_setinputflag(plr, KEY_DOWN, si == 1);
player_setinputflag(plr, KEY_LEFT, sr == -1);
player_setinputflag(plr, KEY_RIGHT, sr == 1);
if(direction) {
plr->gamepadmove = true;
2012-08-15 16:36:39 +02:00
player_move(&global.plr, direction);
}
return true;
2012-08-15 16:36:39 +02:00
}
void player_applymovement(Player *plr) {
if(plr->deathtime < -1)
return;
bool gamepad = player_applymovement_gamepad(plr);
plr->moving = false;
int up = plr->inputflags & INFLAG_UP,
down = plr->inputflags & INFLAG_DOWN,
left = plr->inputflags & INFLAG_LEFT,
right = plr->inputflags & INFLAG_RIGHT;
if(left && !right) {
plr->moving = true;
plr->dir = 1;
} else if(right && !left) {
plr->moving = true;
plr->dir = 0;
2012-08-15 16:36:39 +02:00
}
2012-08-15 16:36:39 +02:00
if(gamepad)
return;
complex direction = 0;
if(up) direction -= 1.0*I;
if(down) direction += 1.0*I;
if(left) direction -= 1;
if(right) direction += 1;
2012-07-20 16:11:24 +02:00
if(cabs(direction))
direction /= cabs(direction);
if(direction)
2012-07-20 16:11:24 +02:00
player_move(&global.plr, direction);
}
void player_input_workaround(Player *plr) {
if(global.dialog)
return;
for(KeyIndex key = KEYIDX_FIRST; key <= KEYIDX_LAST; ++key) {
int flag = key_to_inflag(key);
if(flag) {
int event = -1;
bool flagset = plr->inputflags & flag;
bool keyheld = gamekeypressed(key);
if(flagset && !keyheld) {
// something ate the release event (most likely the ingame menu)
event = EV_RELEASE;
} else if(!flagset && keyheld) {
// something ate the press event (most likely the ingame menu)
event = EV_PRESS;
}
if(event != -1) {
player_event(plr, event, key);
replay_stage_event(global.replay_stage, global.frames, event, key);
}
}
}
}
2012-08-14 16:14:53 +02:00
void player_graze(Player *plr, complex pos, int pts) {
plr->graze++;
player_add_points(&global.plr, pts);
2012-08-14 16:14:53 +02:00
play_sound("graze");
2012-08-14 16:14:53 +02:00
int i = 0; for(i = 0; i < 5; ++i) {
tsrand_fill(3);
create_particle2c("flare", pos, 0, Shrink, timeout_linear, 5 + 5 * afrand(2), (1+afrand(0)*5)*cexp(I*tsrand_a(1)))->type=PlrProj;
2012-08-14 16:14:53 +02:00
}
}
static void player_add_fragments(Player *plr, int frags, int *pwhole, int *pfrags, int maxfrags, int maxwhole, const char *fragsnd, const char *upsnd) {
if(*pwhole >= maxwhole) {
return;
}
*pfrags += frags;
int up = *pfrags / maxfrags;
*pwhole += up;
*pfrags %= maxfrags;
if(up) {
play_sound(upsnd);
}
if(frags) {
// FIXME: when we have the extra life/bomb sounds,
// don't play this if upsnd was just played.
play_sound(fragsnd);
}
if(*pwhole > maxwhole) {
*pwhole = maxwhole;
}
}
void player_add_life_fragments(Player *plr, int frags) {
player_add_fragments(plr, frags, &plr->lives, &plr->life_fragments, PLR_MAX_LIFE_FRAGMENTS, PLR_MAX_LIVES,
"item_generic", // FIXME: replacement needed
"extra_life"
);
}
void player_add_bomb_fragments(Player *plr, int frags) {
player_add_fragments(plr, frags, &plr->bombs, &plr->bomb_fragments, PLR_MAX_BOMB_FRAGMENTS, PLR_MAX_BOMBS,
"item_generic", // FIXME: replacement needed
"extra_bomb"
);
}
void player_add_lives(Player *plr, int lives) {
player_add_life_fragments(plr, PLR_MAX_LIFE_FRAGMENTS);
}
void player_add_bombs(Player *plr, int bombs) {
player_add_bomb_fragments(plr, PLR_MAX_BOMB_FRAGMENTS);
}
static void try_spawn_bonus_item(Player *plr, ItemType type, unsigned int oldpoints, unsigned int reqpoints) {
int items = plr->points / reqpoints - oldpoints / reqpoints;
if(items > 0) {
complex p = creal(plr->pos);
create_item(p, -5*I, type);
spawn_items(p, type, --items, NULL);
}
}
void player_add_points(Player *plr, unsigned int points) {
unsigned int old = plr->points;
plr->points += points;
if(global.stage->type != STAGE_SPELL) {
try_spawn_bonus_item(plr, LifeFrag, old, PLR_SCORE_PER_LIFE_FRAG);
try_spawn_bonus_item(plr, BombFrag, old, PLR_SCORE_PER_BOMB_FRAG);
}
}
void player_preload(void) {
const int flags = RESF_DEFAULT;
preload_resources(RES_TEXTURE, flags,
"focus",
"fairy_circle",
"masterspark",
"masterspark_ring",
NULL);
preload_resources(RES_SFX, flags | RESF_OPTIONAL,
"graze",
"death",
2017-03-13 22:59:49 +01:00
"generic_shot",
"masterspark",
"full_power",
"extra_life",
"extra_bomb",
NULL);
preload_resources(RES_ANIM, flags,
"youmu",
"marisa",
NULL);
}