desperate attempt at refactoring plrmodes

This commit is contained in:
Andrei Alexeyev 2017-10-08 14:30:51 +03:00
parent 0dbd045a31
commit 1992a62592
37 changed files with 1314 additions and 980 deletions

View file

@ -58,6 +58,12 @@ set(SRCs
hashtable.c
boss.c
plrmodes.c
plrmodes/marisa.c
plrmodes/marisa_a.c
plrmodes/marisa_b.c
plrmodes/youmu.c
plrmodes/youmu_a.c
plrmodes/youmu_b.c
laser.c
dialog.c
fbo.c

View file

@ -87,13 +87,7 @@ int cli_args(int argc, char **argv, CLIAction *a) {
int c;
uint16_t stageid = 0;
#define INVALID_CHAR ((Character)-1)
#define INVALID_SHOT ((ShotMode)-1)
// these may be unsigned depending on the compiler.
Character cha = INVALID_CHAR;
ShotMode shot = INVALID_SHOT;
PlayerMode *plrmode = NULL;
while((c = getopt_long(argc, argv, optc, opts, 0)) != -1) {
char *endptr = NULL;
@ -135,8 +129,8 @@ int cli_args(int argc, char **argv, CLIAction *a) {
break;
case 's':
if(plrmode_parse(optarg,&cha,&shot))
log_fatal("Invalid shotmode '%s'",optarg);
if(!(plrmode = plrmode_parse(optarg)))
log_fatal("Invalid shotmode '%s'", optarg);
break;
case 'f':
a->frameskip = 1;
@ -170,12 +164,12 @@ int cli_args(int argc, char **argv, CLIAction *a) {
}
}
if(a->type != CLI_SelectStage && (cha != INVALID_CHAR || shot != INVALID_SHOT))
log_warn("--shotmode was ignored");
if(cha != INVALID_CHAR && shot != INVALID_SHOT) {
a->plrcha = cha;
a->plrshot = shot;
if(plrmode) {
if(a->type == CLI_SelectStage) {
a->plrmode = plrmode;
} else {
log_warn("--shotmode was ignored");
}
}
a->stageid = stageid;

View file

@ -8,7 +8,7 @@
#pragma once
#include "player.h"
#include "plrmodes.h"
typedef enum {
CLI_RunNormally = 0,
@ -26,9 +26,7 @@ struct CLIAction {
int stageid;
int diff;
int frameskip;
Character plrcha;
ShotMode plrshot;
PlayerMode *plrmode;
};
int cli_args(int argc, char **argv, CLIAction *a);

View file

@ -11,7 +11,7 @@
#include <stdlib.h>
#include <string.h>
Dialog *create_dialog(char *left, char *right) {
Dialog *create_dialog(const char *left, const char *right) {
Dialog *d = malloc(sizeof(Dialog));
memset(d, 0, sizeof(Dialog));
@ -25,11 +25,11 @@ Dialog *create_dialog(char *left, char *right) {
return d;
}
void dset_image(Dialog *d, Side side, char *name) {
void dset_image(Dialog *d, Side side, const char *name) {
d->images[side] = get_tex(name);
}
void dadd_msg(Dialog *d, Side side, char *msg) {
void dadd_msg(Dialog *d, Side side, const char *msg) {
d->messages = realloc(d->messages, (++d->count)*sizeof(DialogMessage));
d->messages[d->count-1].side = side;
d->messages[d->count-1].msg = malloc(strlen(msg) + 1);

View file

@ -37,9 +37,9 @@ typedef struct Dialog {
int birthtime;
} Dialog;
Dialog *create_dialog(char *left, char *right);
void dset_image(Dialog *d, Side side, char *name);
void dadd_msg(Dialog *d, Side side, char *msg);
Dialog *create_dialog(const char *left, const char *right);
void dset_image(Dialog *d, Side side, const char *name);
void dadd_msg(Dialog *d, Side side, const char *msg);
void delete_dialog(Dialog *d);
void draw_dialog(Dialog *dialog);

View file

@ -32,6 +32,10 @@ void add_ending_entry(Ending *e, int dur, char *msg, char *tex) {
entry->tex = NULL;
}
/*
* These ending callbacks are referenced in plrmodes/ code
*/
void bad_ending_marisa(Ending *e) {
add_ending_entry(e, 300, "After her consciousness had faded while she was falling down the tower,\nMarisa found herself waking up in a clearing of the magical forest.", NULL);
add_ending_entry(e, 300, "She saw the sun set.", NULL);
@ -72,25 +76,10 @@ void create_ending(Ending *e) {
memset(e, 0, sizeof(Ending));
if(global.continues || global.diff == D_Easy) {
switch(global.plr.cha) {
case Marisa:
bad_ending_marisa(e);
break;
case Youmu:
bad_ending_youmu(e);
break;
}
global.plr.mode->character->ending.bad(e);
add_ending_entry(e, 400, "Try a no continue run on higher difficulties. You can do it!", NULL);
} else {
switch(global.plr.cha) {
case Marisa:
good_ending_marisa(e);
break;
case Youmu:
good_ending_youmu(e);
break;
}
global.plr.mode->character->ending.good(e);
add_ending_entry(e, 400, "Sorry, extra stage isnt done yet. ^^", NULL);
}

View file

@ -49,4 +49,10 @@ void ending_loop(void);
void free_ending(Ending *e);
void ending_preload(void);
/*
* These ending callbacks are referenced in plrmodes/ code
*/
void bad_ending_marisa(Ending *e);
void bad_ending_youmu(Ending *e);
void good_ending_marisa(Ending *e);
void good_ending_youmu(Ending *e);

View file

@ -228,9 +228,12 @@ int main(int argc, char **argv) {
do {
global.game_over = 0;
init_player(&global.plr);
global.plr.cha = a.plrcha;
global.plr.shot = a.plrshot;
player_init(&global.plr);
if(a.plrmode) {
global.plr.mode = a.plrmode;
}
stage_loop(stg);
} while(global.game_over == GAMEOVER_RESTART);

View file

@ -11,20 +11,25 @@
#include "common.h"
#include "global.h"
// static to preserve between menu restarts
static CharacterID selected_charid;
static ShotModeID selected_shotid;
void set_player(MenuData *m, void *p) {
global.plr.cha = (Character) (uintptr_t) p;
selected_charid = (CharacterID)(uintptr_t)p;
}
void set_shotmode(MenuData *m, void *p) {
global.plr.shot = (ShotMode) (uintptr_t) p;
selected_shotid = (ShotModeID)(uintptr_t)p;
}
void create_shottype_menu(MenuData *m) {
create_menu(m);
m->transition = NULL;
add_menu_entry(m, "Laser Sign|Mirror Sign", set_shotmode, (void *) YoumuOpposite);
add_menu_entry(m, "Star Sign|Haunting Sign", set_shotmode, (void *) YoumuHoming);
for(uintptr_t i = 0; i < NUM_SHOT_MODES_PER_CHARACTER; ++i) {
add_menu_entry(m, NULL, set_shotmode, (void*)i);
}
}
void char_menu_input(MenuData*);
@ -41,8 +46,9 @@ void create_char_menu(MenuData *m) {
m->context = malloc(sizeof(MenuData));
create_shottype_menu(m->context);
add_menu_entry(m, "dialog/marisa|Kirisame Marisa|Black Magician", set_player, (void *)Marisa)->transition = TransFadeBlack;
add_menu_entry(m, "dialog/youmu|Konpaku Yōmu|Half-Phantom Girl", set_player, (void *)Youmu)->transition = TransFadeBlack;
for(uintptr_t i = 0; i < NUM_CHARACTERS; ++i) {
add_menu_entry(m, NULL, set_player, (void*)i)->transition = TransFadeBlack;
}
}
void draw_char_menu(MenuData *menu) {
@ -55,23 +61,22 @@ void draw_char_menu(MenuData *menu) {
glColor4f(0,0,0,0.7);
glTranslatef(SCREEN_W/4*3, SCREEN_H/2, 0);
glScalef(300, SCREEN_H, 1);
draw_quad();
glPopMatrix();
char buf[128];
int i;
for(i = 0; i < menu->ecount; i++) {
strlcpy(buf, menu->entries[i].name, sizeof(buf));
char *save;
CharacterID current_char;
char *tex = strtok_r(buf,"|", &save);
char *name = strtok_r(NULL, "|", &save);
char *title = strtok_r(NULL, "|", &save);
for(int i = 0; i < menu->ecount; i++) {
PlayerCharacter *pchar = plrchar_get((CharacterID)(uintptr_t)menu->entries[i].arg);
assert(pchar != NULL);
if(!(tex && name && title))
continue;
const char *tex = pchar->dialog_sprite_name;
const char *name = pchar->full_name;
const char *title = pchar->title;
if(menu->cursor == i) {
current_char = pchar->id;
}
menu->entries[i].drawdata += 0.08*(1.0*(menu->cursor != i) - menu->entries[i].drawdata);
@ -91,29 +96,36 @@ void draw_char_menu(MenuData *menu) {
draw_text(AL_Center, 0, 0, name, _fonts.mainmenu);
glPopMatrix();
if(menu->entries[i].drawdata)
if(menu->entries[i].drawdata) {
glColor4f(1,1,1,1-menu->entries[i].drawdata*3);
draw_text(AL_Center, 0, 70, title, _fonts.standard);
strlcpy(buf, mod->entries[i].name, sizeof(buf));
char *mari = strtok_r(buf, "|", &save);
char *youmu = strtok_r(NULL, "|", &save);
char *use = menu->entries[menu->cursor].arg == (void *)Marisa ? mari : youmu;
if(menu->entries[i].drawdata)
} else {
glColor4f(1,1,1,1);
}
if(mod->cursor == i)
glColor4f(0.9,0.6,0.2,1);
draw_text(AL_Center, 0, 200+40*i, use, _fonts.standard);
draw_text(AL_Center, 0, 70, title, _fonts.standard);
glPopMatrix();
}
glPushMatrix();
glTranslatef(SCREEN_W/4*3, SCREEN_H/3, 0);
for(int i = 0; i < mod->ecount; i++) {
PlayerMode *mode = plrmode_find(current_char, (ShotModeID)(uintptr_t)mod->entries[i].arg);
assert(mode != NULL);
if(mod->cursor == i) {
glColor4f(0.9,0.6,0.2,1);
} else {
glColor4f(1,1,1,1);
}
draw_text(AL_Center, 0, 200+40*i, mode->name, _fonts.standard);
}
glPopMatrix();
glColor4f(1,1,1,0.3*sin(menu->frames/20.0)+0.5);
for(i = 0; i <= 1; i++) {
for(int i = 0; i <= 1; i++) {
glPushMatrix();
glTranslatef(60 + (SCREEN_W/2 - 30)*i, SCREEN_H/2+80, 0);
@ -180,6 +192,7 @@ void char_menu_input(MenuData *menu) {
}
void free_char_menu(MenuData *menu) {
global.plr.mode = plrmode_find(selected_charid, selected_shotid);
MenuData *mod = menu->context;
destroy_menu(mod);
free(mod);

View file

@ -20,7 +20,7 @@ static void start_game_internal(MenuData *menu, StageInfo *info, bool difficulty
Difficulty stagediff;
bool restart;
init_player(&global.plr);
player_init(&global.plr);
do {
restart = false;
@ -49,9 +49,7 @@ static void start_game_internal(MenuData *menu, StageInfo *info, bool difficulty
global.replay_stage = NULL;
replay_init(&global.replay);
int chr = global.plr.cha;
int sht = global.plr.shot;
PlayerMode *mode = global.plr.mode;
do {
restart = false;
@ -68,13 +66,12 @@ static void start_game_internal(MenuData *menu, StageInfo *info, bool difficulty
}
if(global.game_over == GAMEOVER_RESTART) {
init_player(&global.plr);
player_init(&global.plr);
replay_destroy(&global.replay);
replay_init(&global.replay);
global.game_over = 0;
init_player(&global.plr);
global.plr.cha = chr;
global.plr.shot = sht;
player_init(&global.plr);
global.plr.mode = mode;
restart = true;
}

View file

@ -22,6 +22,7 @@
#include "video.h"
#include "stage.h"
#include "version.h"
#include "plrmodes.h"
void enter_options(MenuData *menu, void *arg) {
MenuData m;
@ -218,8 +219,6 @@ void menu_preload(void) {
"mainmenu/logo",
"part/smoke",
"part/petal",
"dialog/marisa",
"dialog/youmu",
"charselect_arrow",
NULL);
@ -232,4 +231,8 @@ void menu_preload(void) {
preload_resources(RES_BGM, RESF_PERMANENT | RESF_OPTIONAL,
"menu",
NULL);
for(int i = 0; i < NUM_CHARACTERS; ++i) {
preload_resource(RES_TEXTURE, plrchar_get(i)->dialog_sprite_name, RESF_PERMANENT);
}
}

View file

@ -161,7 +161,7 @@ static void replayview_drawitem(void *n, int item, int cnt) {
break;
case 2:
plrmode_repr(tmp, sizeof(tmp), rpy->stages[0].plr_char, rpy->stages[0].plr_shot);
plrmode_repr(tmp, sizeof(tmp), plrmode_find(rpy->stages[0].plr_char, rpy->stages[0].plr_shot));
tmp[0] = tmp[0] - 'a' + 'A';
break;

View file

@ -26,7 +26,7 @@ void save_rpy(MenuData *menu, void *a) {
strftime(strtime, 128, "%Y%m%d_%H-%M-%S_%Z", timeinfo);
char prepr[16], drepr[16];
plrmode_repr(prepr, 16, rpy->stages[0].plr_char, rpy->stages[0].plr_shot);
plrmode_repr(prepr, 16, plrmode_find(rpy->stages[0].plr_char, rpy->stages[0].plr_shot));
strlcpy(drepr, difficulty_name(rpy->stages[0].diff), 16);
drepr[0] += 'a' - 'A';

View file

@ -14,39 +14,44 @@
#include "stage.h"
#include "stagetext.h"
#include <SDL_bits.h>
Animation *player_get_ani(Character cha) {
Animation *ani = NULL;
switch(cha) {
case Youmu:
ani = get_ani("youmu");
break;
case Marisa:
ani = get_ani("marisa");
break;
}
return ani;
}
void init_player(Player *plr) {
void player_init(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;
aniplayer_create(&plr->ani, player_get_ani(plr->cha));
plr->mode = plrmode_find(0, 0);
}
void prepare_player_for_next_stage(Player *plr) {
void player_stage_pre_init(Player *plr) {
plr->recovery = 0;
plr->respawntime = 0;
plr->deathtime = -1;
plr->graze = 0;
plr->axis_lr = 0;
plr->axis_ud = 0;
assert(plr->mode != NULL);
plrchar_preload(plr->mode->character);
if(plr->mode->procs.preload) {
plr->mode->procs.preload(plr);
}
}
void player_stage_post_init(Player *plr) {
// TODO: remove handle_fullpower from player_set_power and get rid of this hack
short power = global.plr.power;
global.plr.power = -1;
delete_enemies(&global.plr.slaves);
player_set_power(&global.plr, power, false);
assert(plr->mode != NULL);
aniplayer_create(&plr->ani, get_ani(plr->mode->character->player_sprite_name));
// ensure the essential callbacks are there. other code tests only for the optional ones
assert(plr->mode->procs.shot != NULL);
assert(plr->mode->procs.bomb != NULL);
}
static void player_full_power(Player *plr) {
@ -58,13 +63,8 @@ static void player_full_power(Player *plr) {
bool player_set_power(Player *plr, short npow, bool handle_fullpower) {
npow = clamp(npow, 0, PLR_MAX_POWER);
switch(plr->cha) {
case Youmu:
youmu_power(plr, npow);
break;
case Marisa:
marisa_power(plr, npow);
break;
if(plr->mode->procs.power) {
plr->mode->procs.power(plr, npow);
}
int oldpow = plr->power;
@ -80,16 +80,18 @@ bool player_set_power(Player *plr, short npow, bool handle_fullpower) {
void player_move(Player *plr, complex delta) {
float speed = 0.01*VIEWPORT_W;
if(plr->inputflags & INFLAG_FOCUS)
if(plr->inputflags & INFLAG_FOCUS) {
speed /= 2.0;
}
if(plr->cha == Marisa && plr->shot == MarisaLaser && global.frames - plr->recovery < 0)
speed /= 5.0;
if(plr->mode->procs.speed_mod) {
speed = plr->mode->procs.speed_mod(plr, speed);
}
complex opos = plr->pos - VIEWPORT_W/2.0 - VIEWPORT_H/2.0*I;
complex npos = opos + delta*speed;
Animation *ani = player_get_ani(plr->cha);
Animation *ani = plr->ani.ani;
bool xfac = fabs(creal(npos)) < fabs(creal(opos)) || fabs(creal(npos)) < VIEWPORT_W/2.0 - ani->w/2;
bool yfac = fabs(cimag(npos)) < fabs(cimag(opos)) || fabs(cimag(npos)) < VIEWPORT_H/2.0 - ani->h/2;
@ -104,8 +106,6 @@ void player_move(Player *plr, complex delta) {
}
void player_draw(Player* plr) {
plr->ani.ani = player_get_ani(plr->cha); // because plr->cha is not set at init
// FIXME: death animation?
if(plr->deathtime > global.frames)
return;
@ -168,6 +168,11 @@ static void player_fail_spell(Player *plr) {
}
}
bool player_should_shoot(Player *plr, bool extra) {
return (plr->inputflags & INFLAG_SHOT) &&
(!extra || (global.frames - plr->recovery >= 0 && plr->deathtime >= -1));
}
void player_logic(Player* plr) {
process_enemies(&plr->slaves);
aniplayer_update(&plr->ani);
@ -179,13 +184,12 @@ void player_logic(Player* plr) {
plr->focus = approach(plr->focus, (plr->inputflags & INFLAG_FOCUS) ? 30 : 0, 1);
switch(plr->cha) {
case Youmu:
youmu_shot(plr);
break;
case Marisa:
marisa_shot(plr);
break;
if(plr->mode->procs.think) {
plr->mode->procs.think(plr);
}
if(player_should_shoot(plr, false)) {
plr->mode->procs.shot(plr);
}
if(global.frames == plr->deathtime)
@ -212,17 +216,8 @@ bool player_bomb(Player *plr) {
if(global.frames - plr->recovery >= 0 && (plr->bombs > 0 || plr->iddqd) && global.frames - plr->respawntime >= 60) {
player_fail_spell(plr);
delete_projectiles(&global.projs);
switch(plr->cha) {
case Marisa:
marisa_bomb(plr);
break;
case Youmu:
youmu_bomb(plr);
break;
}
stage_clear_hazards(false);
plr->mode->procs.bomb(plr);
plr->bombs--;
if(plr->deathtime > 0) {
@ -618,23 +613,14 @@ void player_preload(void) {
preload_resources(RES_TEXTURE, flags,
"focus",
"fairy_circle",
"masterspark",
"masterspark_ring",
NULL);
preload_resources(RES_SFX, flags | RESF_OPTIONAL,
"graze",
"death",
"generic_shot",
"masterspark",
"haunt",
"full_power",
"extra_life",
"extra_bomb",
NULL);
preload_resources(RES_ANIM, flags,
"youmu",
"marisa",
NULL);
}

View file

@ -55,53 +55,34 @@ enum {
INFLAGS_MOVE = INFLAG_UP | INFLAG_DOWN | INFLAG_LEFT | INFLAG_RIGHT
};
typedef enum {
Youmu = 0,
Marisa
} Character;
typedef enum {
YoumuOpposite = 0,
YoumuHoming,
MarisaLaser = YoumuOpposite,
MarisaStar = YoumuHoming
} ShotMode;
typedef struct {
complex pos;
short focus;
short power;
int graze;
unsigned int points;
int lives;
int bombs;
int life_fragments;
int bomb_fragments;
short power;
int recovery;
int deathtime;
int respawntime;
struct PlayerMode *mode;
AniPlayer ani;
Character cha;
ShotMode shot;
Enemy *slaves;
int inputflags;
bool gamepadmove;
complex lastmovedir;
int axis_ud;
int axis_lr;
char iddqd;
bool iddqd;
#ifdef PLR_DPS_STATS
int total_dmg;
@ -120,13 +101,24 @@ enum {
EV_INFLAGS,
};
void init_player(Player*);
void prepare_player_for_next_stage(Player*);
// This is called first before we even enter stage_loop.
// It's also called right before syncing player state from a replay stage struct, if a replay is being watched or recorded, before every stage.
// The entire state is reset here, and defaults for story mode are set.
void player_init(Player *plr);
// This is called early in stage_loop, before creating or reading replay stage data.
// State that is not supposed to be preserved between stages is reset here, and any plrmode-specific resources are preloaded.
void player_stage_pre_init(Player *plr);
// This is called right before the stage's begin proc. After that, the actual game loop starts.
void player_stage_post_init(Player *plr);
// Yes, that's 3 different initialization functions right here.
void player_draw(Player*);
void player_logic(Player*);
bool player_should_shoot(Player *plr, bool extra);
void player_set_char(Player*, Character);
bool player_set_power(Player *plr, short npow, bool handle_fullpower);
void player_move(Player*, complex delta);
@ -147,5 +139,3 @@ void player_add_bombs(Player *plr, int bombs);
void player_add_points(Player *plr, unsigned int points);
void player_preload(void);

View file

@ -8,776 +8,97 @@
#include <float.h>
#include "plrmodes.h"
#include "player.h"
#include "global.h"
#include "stage.h"
static bool should_shoot(bool extra) {
return (global.plr.inputflags & INFLAG_SHOT) &&
(!extra || (global.frames - global.plr.recovery >= 0 && global.plr.deathtime >= -1));
#include "plrmodes.h"
#include "plrmodes/marisa.h"
#include "plrmodes/youmu.h"
static PlayerCharacter *player_characters[] = {
&character_marisa,
&character_youmu,
};
static PlayerMode *player_modes[] = {
&plrmode_marisa_a,
&plrmode_marisa_b,
&plrmode_youmu_a,
&plrmode_youmu_b,
};
PlayerCharacter* plrchar_get(CharacterID id) {
assert((unsigned)id < NUM_CHARACTERS);
PlayerCharacter *pc = player_characters[id];
assert(pc->id == id);
return pc;
}
/* Yōmu */
void plrchar_preload(PlayerCharacter *pc) {
preload_resource(RES_ANIM, pc->player_sprite_name, RESF_DEFAULT);
preload_resource(RES_TEXTURE, pc->dialog_sprite_name, RESF_DEFAULT);
}
// Haunting Sign
int plrmode_repr(char *out, size_t outsize, PlayerMode *mode) {
assert(mode->character != NULL);
assert((unsigned)mode->shot_mode < NUM_SHOT_MODES_PER_CHARACTER);
complex youmu_homing_target(complex org, complex fallback) {
double mindst = DBL_MAX;
complex target = fallback;
return snprintf(out, outsize, "%s%c",
mode->character->lower_name,
mode->shot_mode + 'A'
);
}
if(global.boss) {
target = global.boss->pos;
mindst = cabs(target - org);
}
PlayerMode* plrmode_find(CharacterID char_id, ShotModeID shot_id) {
for(int i = 0; i < NUM_PLAYER_MODES; ++i) {
PlayerMode *mode = player_modes[i];
for(Enemy *e = global.enemies; e; e = e->next) {
if(e->hp == ENEMY_IMMUNE){
continue;
}
double dst = cabs(e->pos - org);
if(dst < mindst) {
mindst = dst;
target = e->pos;
if(mode->character->id == char_id && mode->shot_mode == shot_id) {
return mode;
}
}
return target;
return NULL;
}
void youmu_homing_draw_common(Projectile *p, int t, float clrfactor, float alpha) {
glColor4f(0.7f + 0.3f * clrfactor, 0.9f + 0.1f * clrfactor, 1, alpha);
ProjDraw(p, t);
glColor4f(1, 1, 1, 1);
}
PlayerMode* plrmode_parse(const char *name) {
CharacterID char_id = (CharacterID)-1;
ShotModeID shot_id = (ShotModeID)-1;
char buf[strlen(name) + 1];
strcpy(buf, name);
void youmu_homing_draw_proj(Projectile *p, int t) {
float a = clamp(1.0f - (float)t / p->args[2], 0, 1);
youmu_homing_draw_common(p, t, a, 0.5f);
}
void youmu_homing_draw_trail(Projectile *p, int t) {
float a = clamp(1.0f - (float)t / p->args[0], 0, 1);
youmu_homing_draw_common(p, t, a, 0.15f * a);
}
void youmu_homing_trail(Projectile *p, complex v, int to) {
Projectile *trail = create_projectile_p(&global.particles, p->tex, p->pos, 0, youmu_homing_draw_trail, timeout_linear, to, v, 0, 0);
trail->type = PlrProj;
trail->angle = p->angle;
}
int youmu_homing(Projectile *p, int t) { // a[0]: velocity, a[1]: aim (r: linear, i: accelerated), a[2]: timeout, a[3]: initial target
if(t == EVENT_DEATH) {
return 1;
for(int i = 0; i < sizeof(buf); ++i) {
buf[i] = tolower(buf[i]);
}
if(t > creal(p->args[2])) {
return ACTION_DESTROY;
if(!*buf) {
log_debug("Got an empty string");
return NULL;
}
p->args[3] = youmu_homing_target(p->pos, p->args[3]);
char shot = buf[sizeof(buf) - 2];
double v = cabs(p->args[0]);
complex aimdir = cexp(I*carg(p->args[3] - p->pos));
p->args[0] += creal(p->args[1]) * aimdir;
p->args[0] = v * cexp(I*carg(p->args[0])) + cimag(p->args[1]) * aimdir;
p->angle = carg(p->args[0]);
p->pos += p->args[0];
youmu_homing_trail(p, 0.5 * p->args[0], 12);
return 1;
}
int youmu_trap(Projectile *p, int t) {
if(t == EVENT_DEATH) {
create_particle1c("blast", p->pos, 0, Blast, timeout, 15);
return 1;
if(shot < 'a' || shot >= 'a' + NUM_SHOT_MODES_PER_CHARACTER) {
log_debug("Got invalid shotmode: %c", shot);
return NULL;
}
double expiretime = creal(p->args[1]);
shot_id = shot - 'a';
buf[sizeof(buf) - 2] = 0;
if(t > expiretime) {
return ACTION_DESTROY;
}
for(int i = 0; i < NUM_CHARACTERS; ++i) {
PlayerCharacter *chr = player_characters[i];
if(!(global.plr.inputflags & INFLAG_FOCUS)) {
create_particle1c("blast", p->pos, 0, Blast, timeout, 20);
create_particle1c("blast", p->pos, 0, Blast, timeout, 23);
int cnt = creal(p->args[2]);
int dmg = cimag(p->args[2]);
int dur = 45 + 10 * nfrand(); // creal(p->args[3]) + nfrand() * cimag(p->args[3]);
complex aim = p->args[3];
for(int i = 0; i < cnt; ++i) {
float a = (i / (float)cnt) * M_PI * 2;
complex dir = cexp(I*(a));
Projectile *proj = create_projectile4c("hghost", p->pos, 0, youmu_homing, 5 * dir, aim, dur, global.plr.pos);
proj->type = PlrProj + dmg;
proj->draw = youmu_homing_draw_proj;
}
return ACTION_DESTROY;
}
p->angle = global.frames + t;
p->pos += p->args[0] * (0.01 + 0.99 * max(0, (10 - t) / 10.0));
youmu_homing_trail(p, cexp(I*p->angle), 30);
return 1;
}
void Slice(Projectile *p, int t) {
if(t < creal(p->args[0])/20.0)
p->args[1] += 1;
if(t > creal(p->args[0])-10) {
p->args[1] += 3;
p->args[2] += 1;
}
float f = p->args[1]/p->args[0]*20.0;
glColor4f(1,1,1,1.0 - p->args[2]/p->args[0]*20.0);
glPushMatrix();
glTranslatef(creal(p->pos), cimag(p->pos),0);
glRotatef(p->angle,0,0,1);
glScalef(f,1,1);
draw_texture(0,0,"part/youmu_slice");
glPopMatrix();
glColor4f(1,1,1,1);
}
void YoumuSlash(Enemy *e, int t) {
fade_out(10.0/t+sin(t/10.0)*0.1);
}
int spin(Projectile *p, int t) {
int i = timeout_linear(p, t);
if(t < 0)
return 1;
p->args[3] += 0.06;
p->angle = p->args[3];
return i;
}
int youmu_slash(Enemy *e, int t) {
if(t > creal(e->args[0]))
return ACTION_DESTROY;
if(t < 0)
return 1;
if(global.frames - global.plr.recovery > 0) {
return ACTION_DESTROY;
}
TIMER(&t);
AT(0)
global.plr.pos = VIEWPORT_W/5.0 + (VIEWPORT_H - 100)*I;
FROM_TO(8,20,1)
global.plr.pos = VIEWPORT_W + (VIEWPORT_H - 100)*I - exp(-_i/8.0+log(4*VIEWPORT_W/5.0));
FROM_TO(30, 60, 10) {
tsrand_fill(3);
create_particle1c("youmu_slice", VIEWPORT_W/2.0 - 150 + 100*_i + VIEWPORT_H/2.0*I - 10-10.0*I + 20*afrand(0)+20.0*I*afrand(1), 0, Slice, timeout, 200)->angle = -10.0+20.0*afrand(2);
}
FROM_TO(40,200,1)
if(frand() > 0.7) {
tsrand_fill(6);
create_particle2c("blast", VIEWPORT_W*afrand(0) + (VIEWPORT_H+50)*I, rgb(afrand(1),afrand(2),afrand(3)), Shrink, timeout_linear, 80, 3*(1-2.0*afrand(4))-14.0*I+afrand(5)*2.0*I);
}
int tpar = 30;
if(t < 30)
tpar = t;
if(t < creal(e->args[0])-60 && frand() > 0.2) {
tsrand_fill(3);
create_particle2c("smoke", VIEWPORT_W*afrand(0) + (VIEWPORT_H+100)*I, rgba(0.4,0.4,0.4,afrand(1)*0.2 - 0.2 + 0.6*(tpar/30.0)), PartDraw, spin, 300, -7.0*I+afrand(2)*1.0*I);
}
return 1;
}
// Opposite Sign
int myon_particle_rule(Projectile *p, int t) {
float a = 1.0;
if(t > 0) {
double mt = creal(p->args[0]);
a *= (mt - t) / mt;
}
p->clr = mix_colors(
rgba(0.85, 0.9, 1.0, 1.0),
rgba(0.5, 0.7, 1.0, a),
1 - pow(1 - a, 1 + psin((t + global.frames) * 0.1)));
return timeout_linear(p, t);
}
void myon_particle_draw(Projectile *p, int t) {
// hack to bypass the bullet color shader
Color clr = p->clr;
p->clr = 0;
parse_color_call(clr, glColor4f);
Shrink(p, t);
glColor4f(1, 1, 1, 1);
p->clr = clr;
}
void YoumuOppositeMyon(Enemy *e, int t) {
float a = global.frames * 0.07;
complex pos = e->pos + 3 * (cos(a) + I * sin(a));
complex dir = cexp(I*(0.1 * sin(global.frames * 0.05) + carg(global.plr.pos - e->pos)));
double v = 4 * cabs(global.plr.pos - e->pos) / (VIEWPORT_W * 0.5);
create_particle2c("flare", pos, 0, myon_particle_draw, myon_particle_rule, 20, v*dir)->type = PlrProj;
}
static void youmu_opposite_myon_proj(char *tex, complex pos, double speed, double angle, double aoffs, double upfactor, int dmg) {
complex dir = cexp(I*(M_PI/2 + aoffs)) * upfactor + cexp(I * (angle + aoffs)) * (1 - upfactor);
dir = dir / cabs(dir);
create_projectile1c(tex, pos, 0, linear, speed*dir)->type = PlrProj+dmg;
}
int youmu_opposite_myon(Enemy *e, int t) {
if(t == EVENT_BIRTH)
e->pos = e->pos0 + global.plr.pos;
if(t < 0)
return 1;
Player *plr = &global.plr;
float rad = cabs(e->pos0);
double nfocus = plr->focus / 30.0;
if(!(plr->inputflags & INFLAG_FOCUS)) {
if((plr->inputflags & INFLAGS_MOVE)) {
e->pos0 = rad * -plr->lastmovedir;
} else {
e->pos0 = e->pos - plr->pos;
e->pos0 *= rad / cabs(e->pos0);
if(!strcmp(buf, chr->lower_name)) {
char_id = chr->id;
}
}
complex target = plr->pos + e->pos0;
e->pos += cexp(I*carg(target - e->pos)) * min(10, 0.07 * max(0, cabs(target - e->pos) - VIEWPORT_W * 0.5 * nfocus));
if(!(plr->inputflags & INFLAG_FOCUS)) {
e->args[0] = plr->pos - e->pos;
if(char_id == (CharacterID)-1) {
log_debug("Got invalid character: %s", buf);
return NULL;
}
if(should_shoot(true) && !(global.frames % 6) && global.plr.deathtime >= -1) {
int v1 = -21;
int v2 = -10;
double r1 = (psin(global.frames * 2) * 0.5 + 0.5) * 0.1;
double r2 = (psin(global.frames * 1.2) * 0.5 + 0.5) * 0.1;
double a = carg(e->args[0]);
double u = 1 - nfocus;
int p = plr->power / 100;
int dmg_center = 160 - 30 * p;
int dmg_side = 23 + 2 * p;
if(plr->power >= 100) {
youmu_opposite_myon_proj("youmu", e->pos, v1, a, r1*1, u, dmg_side);
youmu_opposite_myon_proj("youmu", e->pos, v1, a, -r1*1, u, dmg_side);
}
if(plr->power >= 200) {
youmu_opposite_myon_proj("hghost", e->pos, v2, a, r2*2, 0, dmg_side);
youmu_opposite_myon_proj("hghost", e->pos, v2, a, -r2*2, 0, dmg_side);
}
if(plr->power >= 300) {
youmu_opposite_myon_proj("youmu", e->pos, v1, a, r1*3, u, dmg_side);
youmu_opposite_myon_proj("youmu", e->pos, v1, a, -r1*3, u, dmg_side);
}
if(plr->power >= 400) {
youmu_opposite_myon_proj("hghost", e->pos, v2, a, r2*4, 0, dmg_side);
youmu_opposite_myon_proj("hghost", e->pos, v2, a, -r2*4, 0, dmg_side);
}
youmu_opposite_myon_proj("hghost", e->pos, v2, a, 0, 0, dmg_center);
}
return 1;
}
int youmu_split(Enemy *e, int t) {
if(t < 0)
return 1;
if(t > creal(e->args[0]))
return ACTION_DESTROY;
if(global.frames - global.plr.recovery > 0) {
return ACTION_DESTROY;
}
TIMER(&t);
FROM_TO(30,200,1) {
tsrand_fill(2);
create_particle2c("smoke", VIEWPORT_W/2 + VIEWPORT_H/2*I, rgba(0.4,0.4,0.4,afrand(0)*0.2+0.4), PartDraw, spin, 300, 6*cexp(I*afrand(1)*2*M_PI));
}
FROM_TO(100,170,10) {
tsrand_fill(3);
create_particle1c("youmu_slice", VIEWPORT_W/2.0 + VIEWPORT_H/2.0*I - 200-200.0*I + 400*afrand(0)+400.0*I*afrand(1), 0, Slice, timeout, 100-_i)->angle = 360.0*afrand(2);
}
FROM_TO(0, 220, 1) {
float talt = atan((t-e->args[0]/2)/30.0)*10+atan(-e->args[0]/2);
global.plr.pos = VIEWPORT_W/2.0 + (VIEWPORT_H-80)*I + VIEWPORT_W/3.0*sin(talt);
}
return 1;
}
// Youmu Generic
void youmu_homing_power_shot(Player *plr, int p) {
int d = -2;
double spread = 4;
complex aim = (0.5 + 0.1 * p) + (0.1 - p * 0.025) * I;
double speed = 10;
if(plr->power / 100 < p || (global.frames + d * p) % 12) {
return;
}
Projectile **dst = &global.projs;
Texture *t = get_tex("proj/hghost");
for(int sign = -1; sign < 2; sign += 2) {
create_projectile_p(dst, t, plr->pos, 0, youmu_homing_draw_proj, youmu_homing,
speed * cexp(I*carg(sign*p*spread-speed*I)), aim, 60, VIEWPORT_W*0.5)->type = PlrProj+54;
}
}
void youmu_shot(Player *plr) {
if(should_shoot(false)) {
if(!(global.frames % 4))
play_sound("generic_shot");
if(!(global.frames % 6)) {
create_projectile1c("youmu", plr->pos + 10 - I*20, 0, linear, -20.0*I)->type = PlrProj+120;
create_projectile1c("youmu", plr->pos - 10 - I*20, 0, linear, -20.0*I)->type = PlrProj+120;
}
if(plr->shot == YoumuHoming && should_shoot(true)) {
if(plr->inputflags & INFLAG_FOCUS) {
int pwr = plr->power / 100;
if(!(global.frames % (45 - 4 * pwr))) {
int pcnt = 11 + pwr * 4;
int pdmg = 120 - 18 * 4 * (1 - pow(1 - pwr / 4.0, 1.5));
complex aim = 0.75;
create_projectile4c("youhoming", plr->pos, 0, youmu_trap, -30.0*I, 120, pcnt+pdmg*I, aim)->type = PlrProj+1000;
}
} else {
if(!(global.frames % 6)) {
create_projectile4c("hghost", plr->pos, 0, youmu_homing, -10.0*I, 0.25 + 0.1*I, 60, VIEWPORT_W*0.5)->type = PlrProj+120;
}
for(int p = 1; p <= PLR_MAX_POWER/100; ++p) {
youmu_homing_power_shot(plr, p);
}
}
}
}
if(plr->shot == YoumuOpposite && plr->slaves == NULL)
create_enemy_p(&plr->slaves, 40.0*I, ENEMY_IMMUNE, YoumuOppositeMyon, youmu_opposite_myon, 0, 0, 0, 0);
}
void youmu_bomb(Player *plr) {
switch(plr->shot) {
case YoumuOpposite:
create_enemy_p(&plr->slaves, 40.0*I, ENEMY_BOMB, NULL, youmu_split, 280,0,0,0);
break;
case YoumuHoming:
play_sound("haunt");
create_enemy_p(&plr->slaves, 40.0*I, ENEMY_BOMB, YoumuSlash, youmu_slash, 280,0,0,0);
break;
}
}
void youmu_power(Player *plr, short npow) {
if(plr->shot == YoumuOpposite && plr->slaves == NULL)
create_enemy_p(&plr->slaves, 40.0*I, ENEMY_IMMUNE, YoumuOppositeMyon, youmu_opposite_myon, 0, 0, 0, 0);
}
/* Marisa */
// Laser Sign
void MariLaser(Projectile *p, int t) {
if(REF(p->args[1]) == NULL)
return;
if(cimag(p->pos) - cimag(global.plr.pos) < 90) {
float s = resources.fbo.fg[0].scale;
glScissor(0, s * (VIEWPORT_H - cimag(((Enemy *)REF(p->args[1]))->pos)), VIEWPORT_W*s, VIEWPORT_H*s);
glEnable(GL_SCISSOR_TEST);
}
ProjDraw(p, t);
glDisable(GL_SCISSOR_TEST);
}
int mari_laser(Projectile *p, int t) {
if(t == EVENT_DEATH) {
free_ref(p->args[1]);
return 1;
}
if(REF(p->args[1]) == NULL)
return ACTION_DESTROY;
float angle = creal(p->args[2]);
float factor = (1-global.plr.focus/30.0) * !!angle;
complex dir = -cexp(I*((angle+0.125*sin(global.frames/25.0)*(angle > 0? 1 : -1))*factor + M_PI/2));
p->args[0] = 20*dir;
linear(p, t);
p->pos = ((Enemy *)REF(p->args[1]))->pos + p->pos;
return 1;
}
int marisa_laser_slave(Enemy *e, int t) {
if(should_shoot(true)) {
if(!(global.frames % 4))
create_projectile_p(&global.projs, get_tex("proj/marilaser"), 0, 0, MariLaser, mari_laser, 0, add_ref(e),e->args[2],0)->type = PlrProj+e->args[1]*4;
if(!(global.frames % 3)) {
float s = 0.5 + 0.3*sin(global.frames/7.0);
create_particle3c("marilaser_part0", 0, rgb(1-s,0.5,s), PartDraw, mari_laser, 0, add_ref(e), e->args[2])->type=PlrProj;
}
create_particle1c("lasercurve", e->pos, 0, Fade, timeout, 4)->type = PlrProj;
}
e->pos = global.plr.pos + (1 - global.plr.focus/30.0)*e->pos0 + (global.plr.focus/30.0)*e->args[0];
return 1;
}
void MariLaserSlave(Enemy *e, int t) {
glPushMatrix();
glTranslatef(creal(e->pos), cimag(e->pos), -1);
glRotatef(global.frames * 3, 0, 0, 1);
draw_texture(0,0,"part/lasercurve");
glPopMatrix();
}
// Laser sign bomb (implemented as Enemy)
static void draw_masterspark_ring(complex base, int t, float fade) {
glPushMatrix();
glTranslatef(creal(base), cimag(base)-t*t*0.4, 0);
float f = sqrt(t/500.0)*1200;
glColor4f(1,1,1,fade*20.0/t);
Texture *tex = get_tex("masterspark_ring");
glScalef(f/tex->w, 1-tex->h/f,0);
draw_texture_p(0,0,tex);
glPopMatrix();
}
void MasterSpark(Enemy *e, int t) {
glPushMatrix();
float angle = 9 - t/e->args[0]*6.0, fade = 1;
if(t < creal(