From 1992a6259251e7b59d6755a6593cba4c0cb4b0db Mon Sep 17 00:00:00 2001 From: Andrei Alexeyev <0x416b617269@gmail.com> Date: Sun, 8 Oct 2017 14:30:51 +0300 Subject: [PATCH] desperate attempt at refactoring plrmodes --- src/CMakeLists.txt | 6 + src/cli.c | 24 +- src/cli.h | 6 +- src/dialog.c | 6 +- src/dialog.h | 6 +- src/ending.c | 23 +- src/ending.h | 8 +- src/main.c | 9 +- src/menu/charselect.c | 81 ++-- src/menu/common.c | 13 +- src/menu/mainmenu.c | 7 +- src/menu/replayview.c | 2 +- src/menu/savereplay.c | 2 +- src/player.c | 106 +++-- src/player.h | 44 +- src/plrmodes.c | 805 +++---------------------------------- src/plrmodes.h | 78 +++- src/plrmodes/marisa.c | 43 ++ src/plrmodes/marisa.h | 18 + src/plrmodes/marisa_a.c | 196 +++++++++ src/plrmodes/marisa_a.h | 13 + src/plrmodes/marisa_b.c | 155 +++++++ src/plrmodes/marisa_b.h | 13 + src/plrmodes/youmu.c | 71 ++++ src/plrmodes/youmu.h | 19 + src/plrmodes/youmu_a.c | 175 ++++++++ src/plrmodes/youmu_a.h | 13 + src/plrmodes/youmu_b.c | 234 +++++++++++ src/plrmodes/youmu_b.h | 13 + src/replay.c | 8 +- src/stage.c | 13 +- src/stages/stage1_events.c | 32 +- src/stages/stage2_events.c | 10 +- src/stages/stage3_events.c | 7 +- src/stages/stage4_events.c | 10 +- src/stages/stage5_events.c | 18 +- src/stages/stage6_events.c | 7 +- 37 files changed, 1314 insertions(+), 980 deletions(-) create mode 100644 src/plrmodes/marisa.c create mode 100644 src/plrmodes/marisa.h create mode 100644 src/plrmodes/marisa_a.c create mode 100644 src/plrmodes/marisa_a.h create mode 100644 src/plrmodes/marisa_b.c create mode 100644 src/plrmodes/marisa_b.h create mode 100644 src/plrmodes/youmu.c create mode 100644 src/plrmodes/youmu.h create mode 100644 src/plrmodes/youmu_a.c create mode 100644 src/plrmodes/youmu_a.h create mode 100644 src/plrmodes/youmu_b.c create mode 100644 src/plrmodes/youmu_b.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d4ad6721..2af34728 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/cli.c b/src/cli.c index c034dd8e..ea85cf24 100644 --- a/src/cli.c +++ b/src/cli.c @@ -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; diff --git a/src/cli.h b/src/cli.h index fd985bfb..d579c78a 100644 --- a/src/cli.h +++ b/src/cli.h @@ -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); diff --git a/src/dialog.c b/src/dialog.c index bed2d1ac..484d0167 100644 --- a/src/dialog.c +++ b/src/dialog.c @@ -11,7 +11,7 @@ #include #include -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); diff --git a/src/dialog.h b/src/dialog.h index 441df202..46f2f8ec 100644 --- a/src/dialog.h +++ b/src/dialog.h @@ -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); diff --git a/src/ending.c b/src/ending.c index 96a04b74..d2a4e4c0 100644 --- a/src/ending.c +++ b/src/ending.c @@ -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 isn’t done yet. ^^", NULL); } diff --git a/src/ending.h b/src/ending.h index 8246fd19..f73aa99e 100644 --- a/src/ending.h +++ b/src/ending.h @@ -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); diff --git a/src/main.c b/src/main.c index 070594ca..7bf07f45 100644 --- a/src/main.c +++ b/src/main.c @@ -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); diff --git a/src/menu/charselect.c b/src/menu/charselect.c index 49ff029e..0dbb6947 100644 --- a/src/menu/charselect.c +++ b/src/menu/charselect.c @@ -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); diff --git a/src/menu/common.c b/src/menu/common.c index f97e35f1..fbbefd05 100644 --- a/src/menu/common.c +++ b/src/menu/common.c @@ -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; } diff --git a/src/menu/mainmenu.c b/src/menu/mainmenu.c index cf129e12..cdde5fe8 100644 --- a/src/menu/mainmenu.c +++ b/src/menu/mainmenu.c @@ -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); + } } diff --git a/src/menu/replayview.c b/src/menu/replayview.c index 21cccdcf..4399c5f9 100644 --- a/src/menu/replayview.c +++ b/src/menu/replayview.c @@ -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; diff --git a/src/menu/savereplay.c b/src/menu/savereplay.c index efd84e24..fe13a0cb 100644 --- a/src/menu/savereplay.c +++ b/src/menu/savereplay.c @@ -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'; diff --git a/src/player.c b/src/player.c index 9f5bea6a..f80407a1 100644 --- a/src/player.c +++ b/src/player.c @@ -14,39 +14,44 @@ #include "stage.h" #include "stagetext.h" -#include - -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); } diff --git a/src/player.h b/src/player.h index 37d51e21..00ed2abc 100644 --- a/src/player.h +++ b/src/player.h @@ -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); - - diff --git a/src/plrmodes.c b/src/plrmodes.c index cee9c646..5354fa6b 100644 --- a/src/plrmodes.c +++ b/src/plrmodes.c @@ -8,776 +8,97 @@ #include -#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(e->args[0]/6)) - fade = t/e->args[0]*6; - - if(t > creal(e->args[0])/4*3) - fade = 1-t/e->args[0]*4 + 3; - - glColor4f(1,0.85,1,fade); - glTranslatef(creal(global.plr.pos), cimag(global.plr.pos), 0); - glRotatef(-angle,0,0,1); - draw_texture(0, -450, "masterspark"); - glColor4f(0.85,1,1,fade); - glRotatef(angle*2,0,0,1); - draw_texture(0, -450, "masterspark"); - glColor4f(1,1,1,fade); - glRotatef(-angle,0,0,1); - draw_texture(0, -450, "masterspark"); - glPopMatrix(); - -// glColor4f(0.9,1,1,fade*0.8); - int i; - for(i = 0; i < 8; i++) - draw_masterspark_ring(global.plr.pos - 50.0*I, t%20 + 10*i, fade); - - glColor4f(1,1,1,1); -} - -int master_spark(Enemy *e, int t) { - if(t == EVENT_BIRTH) { - global.shake_view = 8; - return 1; - } - - if(t > creal(e->args[0]) || global.frames - global.plr.recovery > 0) { - global.shake_view = 0; - return ACTION_DESTROY; - } - - return 1; -} - -// Star Sign - -void MariStar(Projectile *p, int t) { - glPushMatrix(); - glTranslatef(creal(p->pos), cimag(p->pos), 0); - glRotatef(t*10, 0, 0, 1); - - if(p->clr) { - parse_color_call(p->clr, glColor4f); - draw_texture_p(0, 0, p->tex); - glColor4f(1, 1, 1, 1); - } else { - draw_texture_p(0, 0, p->tex); - } - - glPopMatrix(); -} - -void MariStarTrail(Projectile *p, int t) { - float s = 1 - t / creal(p->args[0]); - - Color clr = derive_color(p->clr, CLRMASK_A, rgba(0, 0, 0, s*0.5)); - - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - glPushMatrix(); - glTranslatef(creal(p->pos), cimag(p->pos), 0); - glRotatef(t*10, 0, 0, 1); - glScalef(s, s, 1); - parse_color_call(clr, glColor4f); - draw_texture_p(0, 0, p->tex); - glColor4f(1,1,1,1); - glPopMatrix(); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); -} - -void MariStarBomb(Projectile *p, int t) { - MariStar(p, t); - create_particle1c("maristar_orbit", p->pos, 0, GrowFadeAdd, timeout, 40)->type = PlrProj; -} - -int marisa_star_projectile(Projectile *p, int t) { - float c = 0.3 * psin(t * 0.2); - p->clr = rgb(1 - c, 0.7 + 0.3 * psin(t * 0.1), 0.9 + c/3); - - int r = accelerated(p, t); - create_projectile_p(&global.particles, get_tex("proj/maristar"), p->pos, p->clr, MariStarTrail, timeout, 10, 0, 0, 0)->type = PlrProj; - - if(t == EVENT_DEATH) { - Projectile *impact = create_projectile_p(&global.particles, get_tex("proj/maristar"), p->pos, p->clr, GrowFadeAdd, timeout, 40, 2, 0, 0); - impact->type = PlrProj; - impact->angle = frand() * 2 * M_PI; - } - - return r; -} - -int marisa_star_slave(Enemy *e, int t) { - double focus = global.plr.focus/30.0; - - if(should_shoot(true) && !(global.frames % 15)) { - complex v = e->args[1] * 2; - complex a = e->args[2]; - - v = creal(v) * (1 - 5 * focus) + I * cimag(v) * (1 - 2.5 * focus); - a = creal(a) * focus * -0.0525 + I * cimag(a) * 2; - - create_projectile_p(&global.projs, get_tex("proj/maristar"), e->pos, rgb(1, 0.5, 1), MariStar, marisa_star_projectile, - v, a, 0, 0)->type = PlrProj+e->args[3]*15; - } - - e->pos = global.plr.pos + (1 - focus)*e->pos0 + focus*e->args[0]; - - return 1; -} - -int marisa_star_orbit(Projectile *p, int t) { // a[0]: x' a[1]: x'' - if(t == 0) - p->pos0 = global.plr.pos; - if(t < 0) - return 1; - - if(t > 300 || global.frames - global.plr.recovery > 0) - return ACTION_DESTROY; - - float r = cabs(p->pos0 - p->pos); - - p->args[1] = (0.5e5-t*t)*cexp(I*carg(p->pos0 - p->pos))/(r*r); - p->args[0] += p->args[1]*0.2; - p->pos += p->args[0]; - - return 1; -} - - -// Generic Marisa - -void marisa_shot(Player *plr) { - if(should_shoot(false)) { - if(!(global.frames % 4)) - play_sound("generic_shot"); - - if(!(global.frames % 6)) { - create_projectile1c("marisa", plr->pos + 10 - 15.0*I, 0, linear, -20.0*I)->type = PlrProj+175; - create_projectile1c("marisa", plr->pos - 10 - 15.0*I, 0, linear, -20.0*I)->type = PlrProj+175; - } - } -} - -void marisa_bomb(Player *plr) { - int i; - float r, phi; - switch(plr->shot) { - case MarisaLaser: - play_sound("masterspark"); - create_enemy_p(&plr->slaves, 40.0*I, ENEMY_BOMB, MasterSpark, master_spark, 280,0,0,0); - break; - case MarisaStar: - for(i = 0; i < 20; i++) { - r = frand()*40 + 100; - phi = frand()*2*M_PI; - create_particle1c("maristar_orbit", plr->pos + r*cexp(I*phi), 0, MariStarBomb, marisa_star_orbit, I*r*cexp(I*(phi+frand()*0.5))/10)->type=PlrProj; - } - break; - default: - break; - } -} - -void marisa_power(Player *plr, short npow) { - Enemy *e = plr->slaves, *tmp; - double dmg; - - if(plr->power / 100 == npow / 100) - return; - - while(e != 0) { - tmp = e; - e = e->next; - if(tmp->hp == ENEMY_IMMUNE) - delete_enemy(&plr->slaves, tmp); - } - - switch(plr->shot) { - case MarisaLaser: - dmg = 4; - - if(npow / 100 == 1) { - create_enemy_p(&plr->slaves, -40.0*I, ENEMY_IMMUNE, MariLaserSlave, marisa_laser_slave, -40.0*I, dmg, 0, 0); - } - - if(npow >= 200) { - create_enemy_p(&plr->slaves, 25-5.0*I, ENEMY_IMMUNE, MariLaserSlave, marisa_laser_slave, 8-40.0*I, dmg, -M_PI/30, 0); - create_enemy_p(&plr->slaves, -25-5.0*I, ENEMY_IMMUNE, MariLaserSlave, marisa_laser_slave, -8-40.0*I, dmg, M_PI/30, 0); - } - - if(npow / 100 == 3) { - create_enemy_p(&plr->slaves, -30.0*I, ENEMY_IMMUNE, MariLaserSlave, marisa_laser_slave, -50.0*I, dmg, 0, 0); - } - - if(npow >= 400) { - create_enemy_p(&plr->slaves, 17-30.0*I, ENEMY_IMMUNE, MariLaserSlave, marisa_laser_slave, 4-45.0*I, dmg, M_PI/60, 0); - create_enemy_p(&plr->slaves, -17-30.0*I, ENEMY_IMMUNE, MariLaserSlave, marisa_laser_slave, -4-45.0*I, dmg, -M_PI/60, 0); - } - break; - case MarisaStar: - dmg = 5; - - if(npow / 100 == 1) { - create_enemy_p(&plr->slaves, 40.0*I, ENEMY_IMMUNE, MariLaserSlave, marisa_star_slave, +30.0*I, -2.0*I, -0.1*I, dmg); - } - - if(npow >= 200) { - create_enemy_p(&plr->slaves, 30.0*I+15, ENEMY_IMMUNE, MariLaserSlave, marisa_star_slave, +30.0*I+10, -0.3-2.0*I, 1-0.1*I, dmg); - create_enemy_p(&plr->slaves, 30.0*I-15, ENEMY_IMMUNE, MariLaserSlave, marisa_star_slave, +30.0*I-10, 0.3-2.0*I, -1-0.1*I, dmg); - } - - if(npow / 100 == 3) { - create_enemy_p(&plr->slaves, -30.0*I, ENEMY_IMMUNE, MariLaserSlave, marisa_star_slave, +30.0*I, -2.0*I, -0.1*I, dmg); - } - - if(npow >= 400) { - create_enemy_p(&plr->slaves, 30, ENEMY_IMMUNE, MariLaserSlave, marisa_star_slave, 25+30.0*I, -0.6-2.0*I, 2-0.1*I, dmg); - create_enemy_p(&plr->slaves, -30, ENEMY_IMMUNE, MariLaserSlave, marisa_star_slave, -25+30.0*I, 0.6-2.0*I, -2-0.1*I, dmg); - } - break; - } -} - -int plrmode_repr(char *out, size_t outsize, Character pchar, ShotMode pshot) { - char *plr, sht; - - switch(pchar) { - case Marisa : plr = "marisa" ; break; - case Youmu : plr = "youmu" ; break; - default : plr = "wtf" ; break; - } - - switch(pshot) { - case MarisaLaser: sht = 'A' ; break; - case MarisaStar : sht = 'B' ; break; - default : sht = '?' ; break; - } - - return snprintf(out, outsize, "%s%c", plr, sht); -} - -// Inverse of plrmode_repr. Sets cha/shot according to name. Returns 0 iff the name is valid. -int plrmode_parse(const char *name, Character *cha, ShotMode *shot) { - if(!strcasecmp(name,"marisaA")) { - *cha = Marisa; *shot = MarisaLaser; - } else if(!strcasecmp(name,"marisaB")) { - *cha = Marisa; *shot = MarisaStar; - } else if(!strcasecmp(name,"youmuA")) { - *cha = Youmu; *shot = YoumuOpposite; - } else if(!strcasecmp(name,"youmuB")) { - *cha = Youmu; *shot = YoumuHoming; - } else { - return 1; - } - return 0; + return plrmode_find(char_id, shot_id); } diff --git a/src/plrmodes.h b/src/plrmodes.h index b3e1a83c..aa9fbaa8 100644 --- a/src/plrmodes.h +++ b/src/plrmodes.h @@ -11,26 +11,76 @@ #include "enemy.h" #include "projectile.h" #include "player.h" +#include "ending.h" -/* Youmu */ +typedef enum { + /* do not reorder - screws replays */ -void youmu_opposite_draw(Enemy *e, int t); -void youmu_opposite_logic(Enemy *e, int t); + PLR_CHAR_MARISA = 0, + PLR_CHAR_YOUMU = 1, + // PLR_CHAR_REIMU = 2, -int youmu_homing(Projectile *p, int t); + NUM_CHARACTERS, +} CharacterID; -void youmu_shot(Player *plr); -void youmu_bomb(Player *plr); -void youmu_power(Player *plr, short npow); +typedef enum { + /* do not reorder - screws replays */ -/* Marisa */ + PLR_SHOT_MARISA_LASER = 0, + PLR_SHOT_MARISA_STAR = 1, -void marisa_shot(Player *plr); -void marisa_bomb(Player *plr); -void marisa_power(Player *plr, short npow); + PLR_SHOT_YOUMU_MIRROR = 0, + PLR_SHOT_YOUMU_HAUNTING = 1, -/* Misc */ -int plrmode_repr(char *out, size_t outsize, Character pchar, ShotMode pshot); -int plrmode_parse(const char *name, Character *cha, ShotMode *shot); + NUM_SHOT_MODES_PER_CHARACTER, +} ShotModeID; +typedef void (*PlrCharEndingProc)(Ending *e); +typedef struct PlayerCharacter { + char id; + const char *lower_name; + const char *proper_name; + const char *full_name; + const char *title; + const char *dialog_sprite_name; + const char *player_sprite_name; + + struct { + PlrCharEndingProc good; + PlrCharEndingProc bad; + } ending; +} PlayerCharacter; + +typedef void (*PlayerModeThinkProc)(Player *plr); +typedef void (*PlayerModeShotProc)(Player *plr); +typedef void (*PlayerModeBombProc)(Player *plr); +typedef void (*PlayerModePowerProc)(Player *plr, short npow); +typedef double (*PlayerModeSpeedModProc)(Player *plr, double speed); +typedef void (*PlayerModePreloadProc)(Player *plr); + +typedef struct PlayerMode { + const char *name; + PlayerCharacter *character; + ShotModeID shot_mode; + + struct { + PlayerModeThinkProc think; + PlayerModeShotProc shot; + PlayerModeBombProc bomb; + PlayerModePowerProc power; + PlayerModeSpeedModProc speed_mod; + PlayerModePreloadProc preload; + } procs; +} PlayerMode; + +enum { + NUM_PLAYER_MODES = NUM_CHARACTERS * NUM_SHOT_MODES_PER_CHARACTER, +}; + +PlayerCharacter* plrchar_get(CharacterID id); +void plrchar_preload(PlayerCharacter *pc); + +PlayerMode* plrmode_find(CharacterID charid, ShotModeID shotid); +int plrmode_repr(char *out, size_t outsize, PlayerMode *mode); +PlayerMode* plrmode_parse(const char *name); diff --git a/src/plrmodes/marisa.c b/src/plrmodes/marisa.c new file mode 100644 index 00000000..30fc213c --- /dev/null +++ b/src/plrmodes/marisa.c @@ -0,0 +1,43 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2017, Lukas Weber . + * Copyright (c) 2012-2017, Andrei Alexeyev . + */ + +#include "global.h" +#include "plrmodes.h" + +PlayerCharacter character_marisa = { + .id = PLR_CHAR_MARISA, + .lower_name = "marisa", + .proper_name = "Marisa", + .full_name = "Kirisame Marisa", + .title = "Black Magician", + .dialog_sprite_name = "dialog/marisa", + .player_sprite_name = "marisa", + .ending = { + .good = good_ending_marisa, + .bad = bad_ending_marisa, + }, +}; + +void marisa_common_shot(Player *plr) { + if(!(global.frames % 4)) { + play_sound("generic_shot"); + } + + if(!(global.frames % 6)) { + create_projectile1c("marisa", plr->pos + 10 - 15.0*I, 0, linear, -20.0*I)->type = PlrProj+175; + create_projectile1c("marisa", plr->pos - 10 - 15.0*I, 0, linear, -20.0*I)->type = PlrProj+175; + } +} + +void marisa_common_slave_draw(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(); +} diff --git a/src/plrmodes/marisa.h b/src/plrmodes/marisa.h new file mode 100644 index 00000000..87a8a520 --- /dev/null +++ b/src/plrmodes/marisa.h @@ -0,0 +1,18 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2017, Lukas Weber . + * Copyright (c) 2012-2017, Andrei Alexeyev . + */ + +#pragma once + +#include "plrmodes.h" +#include "marisa_a.h" +#include "marisa_b.h" + +extern PlayerCharacter character_marisa; + +void marisa_common_shot(Player *plr); +void marisa_common_slave_draw(Enemy *e, int t); diff --git a/src/plrmodes/marisa_a.c b/src/plrmodes/marisa_a.c new file mode 100644 index 00000000..5f94fa21 --- /dev/null +++ b/src/plrmodes/marisa_a.c @@ -0,0 +1,196 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2017, Lukas Weber . + * Copyright (c) 2012-2017, Andrei Alexeyev . + */ + +#include "global.h" +#include "plrmodes.h" +#include "marisa.h" + +static void marisa_laser_draw(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); +} + +static int marisa_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; +} + +static int marisa_laser_slave(Enemy *e, int t) { + if(player_should_shoot(&global.plr, true)) { + if(!(global.frames % 4)) + create_projectile_p(&global.projs, get_tex("proj/marilaser"), 0, 0, marisa_laser_draw, marisa_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, marisa_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; +} + +static void masterspark_ring_draw(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(); +} + +static void masterspark_draw(Enemy *e, int t) { + glPushMatrix(); + + float angle = 9 - t/e->args[0]*6.0, fade = 1; + + if(t < creal(e->args[0]/6)) + fade = t/e->args[0]*6; + + if(t > creal(e->args[0])/4*3) + fade = 1-t/e->args[0]*4 + 3; + + glColor4f(1,0.85,1,fade); + glTranslatef(creal(global.plr.pos), cimag(global.plr.pos), 0); + glRotatef(-angle,0,0,1); + draw_texture(0, -450, "masterspark"); + glColor4f(0.85,1,1,fade); + glRotatef(angle*2,0,0,1); + draw_texture(0, -450, "masterspark"); + glColor4f(1,1,1,fade); + glRotatef(-angle,0,0,1); + draw_texture(0, -450, "masterspark"); + glPopMatrix(); + +// glColor4f(0.9,1,1,fade*0.8); + int i; + for(i = 0; i < 8; i++) + masterspark_ring_draw(global.plr.pos - 50.0*I, t%20 + 10*i, fade); + + glColor4f(1,1,1,1); +} + +static int masterspark(Enemy *e, int t) { + if(t == EVENT_BIRTH) { + global.shake_view = 8; + return 1; + } + + if(t > creal(e->args[0]) || global.frames - global.plr.recovery > 0) { + global.shake_view = 0; + return ACTION_DESTROY; + } + + return 1; +} + +static void marisa_laser_bomb(Player *plr) { + play_sound("masterspark"); + create_enemy_p(&plr->slaves, 40.0*I, ENEMY_BOMB, masterspark_draw, masterspark, 280,0,0,0); +} + +static void marisa_laser_power(Player *plr, short npow) { + Enemy *e = plr->slaves, *tmp; + double dmg = 4; + + if(plr->power / 100 == npow / 100) + return; + + while(e != 0) { + tmp = e; + e = e->next; + if(tmp->hp == ENEMY_IMMUNE) + delete_enemy(&plr->slaves, tmp); + } + + if(npow / 100 == 1) { + create_enemy_p(&plr->slaves, -40.0*I, ENEMY_IMMUNE, marisa_common_slave_draw, marisa_laser_slave, -40.0*I, dmg, 0, 0); + } + + if(npow >= 200) { + create_enemy_p(&plr->slaves, 25-5.0*I, ENEMY_IMMUNE, marisa_common_slave_draw, marisa_laser_slave, 8-40.0*I, dmg, -M_PI/30, 0); + create_enemy_p(&plr->slaves, -25-5.0*I, ENEMY_IMMUNE, marisa_common_slave_draw, marisa_laser_slave, -8-40.0*I, dmg, M_PI/30, 0); + } + + if(npow / 100 == 3) { + create_enemy_p(&plr->slaves, -30.0*I, ENEMY_IMMUNE, marisa_common_slave_draw, marisa_laser_slave, -50.0*I, dmg, 0, 0); + } + + if(npow >= 400) { + create_enemy_p(&plr->slaves, 17-30.0*I, ENEMY_IMMUNE, marisa_common_slave_draw, marisa_laser_slave, 4-45.0*I, dmg, M_PI/60, 0); + create_enemy_p(&plr->slaves, -17-30.0*I, ENEMY_IMMUNE, marisa_common_slave_draw, marisa_laser_slave, -4-45.0*I, dmg, -M_PI/60, 0); + } +} + +static double marisa_laser_speed_mod(Player *plr, double speed) { + if(global.frames - plr->recovery < 0) { + speed /= 5.0; + } + + return speed; +} + +static void marisa_laser_preload(Player *plr) { + const int flags = RESF_DEFAULT; + + preload_resources(RES_TEXTURE, flags, + "masterspark", + "masterspark_ring", + NULL); + + preload_resources(RES_SFX, flags | RESF_OPTIONAL, + "masterspark", + NULL); +} + +PlayerMode plrmode_marisa_a = { + .name = "Laser Sign", + .character = &character_marisa, + .shot_mode = PLR_SHOT_MARISA_LASER, + .procs = { + .bomb = marisa_laser_bomb, + .shot = marisa_common_shot, + .power = marisa_laser_power, + .speed_mod = marisa_laser_speed_mod, + .preload = marisa_laser_preload, + }, +}; diff --git a/src/plrmodes/marisa_a.h b/src/plrmodes/marisa_a.h new file mode 100644 index 00000000..489f6c19 --- /dev/null +++ b/src/plrmodes/marisa_a.h @@ -0,0 +1,13 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2017, Lukas Weber . + * Copyright (c) 2012-2017, Andrei Alexeyev . + */ + +#pragma once + +#include "plrmodes.h" + +PlayerMode plrmode_marisa_a; diff --git a/src/plrmodes/marisa_b.c b/src/plrmodes/marisa_b.c new file mode 100644 index 00000000..3c50b507 --- /dev/null +++ b/src/plrmodes/marisa_b.c @@ -0,0 +1,155 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2017, Lukas Weber . + * Copyright (c) 2012-2017, Andrei Alexeyev . + */ + +#include "global.h" +#include "plrmodes.h" +#include "marisa.h" + +static void marisa_star(Projectile *p, int t) { + glPushMatrix(); + glTranslatef(creal(p->pos), cimag(p->pos), 0); + glRotatef(t*10, 0, 0, 1); + + if(p->clr) { + parse_color_call(p->clr, glColor4f); + draw_texture_p(0, 0, p->tex); + glColor4f(1, 1, 1, 1); + } else { + draw_texture_p(0, 0, p->tex); + } + + glPopMatrix(); +} + +static void marisa_star_trail_draw(Projectile *p, int t) { + float s = 1 - t / creal(p->args[0]); + + Color clr = derive_color(p->clr, CLRMASK_A, rgba(0, 0, 0, s*0.5)); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glPushMatrix(); + glTranslatef(creal(p->pos), cimag(p->pos), 0); + glRotatef(t*10, 0, 0, 1); + glScalef(s, s, 1); + parse_color_call(clr, glColor4f); + draw_texture_p(0, 0, p->tex); + glColor4f(1,1,1,1); + glPopMatrix(); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +} + +static void marisa_star_bomb_draw(Projectile *p, int t) { + marisa_star(p, t); + create_particle1c("maristar_orbit", p->pos, 0, GrowFadeAdd, timeout, 40)->type = PlrProj; +} + +static int marisa_star_projectile(Projectile *p, int t) { + float c = 0.3 * psin(t * 0.2); + p->clr = rgb(1 - c, 0.7 + 0.3 * psin(t * 0.1), 0.9 + c/3); + + int r = accelerated(p, t); + create_projectile_p(&global.particles, get_tex("proj/maristar"), p->pos, p->clr, marisa_star_trail_draw, timeout, 10, 0, 0, 0)->type = PlrProj; + + if(t == EVENT_DEATH) { + Projectile *impact = create_projectile_p(&global.particles, get_tex("proj/maristar"), p->pos, p->clr, GrowFadeAdd, timeout, 40, 2, 0, 0); + impact->type = PlrProj; + impact->angle = frand() * 2 * M_PI; + } + + return r; +} + +static int marisa_star_slave(Enemy *e, int t) { + double focus = global.plr.focus/30.0; + + if(player_should_shoot(&global.plr, true) && !(global.frames % 15)) { + complex v = e->args[1] * 2; + complex a = e->args[2]; + + v = creal(v) * (1 - 5 * focus) + I * cimag(v) * (1 - 2.5 * focus); + a = creal(a) * focus * -0.0525 + I * cimag(a) * 2; + + create_projectile_p(&global.projs, get_tex("proj/maristar"), e->pos, rgb(1, 0.5, 1), marisa_star, marisa_star_projectile, + v, a, 0, 0)->type = PlrProj+e->args[3]*15; + } + + e->pos = global.plr.pos + (1 - focus)*e->pos0 + focus*e->args[0]; + + return 1; +} + +static int marisa_star_orbit(Projectile *p, int t) { // a[0]: x' a[1]: x'' + if(t == 0) + p->pos0 = global.plr.pos; + if(t < 0) + return 1; + + if(t > 300 || global.frames - global.plr.recovery > 0) + return ACTION_DESTROY; + + float r = cabs(p->pos0 - p->pos); + + p->args[1] = (0.5e5-t*t)*cexp(I*carg(p->pos0 - p->pos))/(r*r); + p->args[0] += p->args[1]*0.2; + p->pos += p->args[0]; + + return 1; +} + +static void marisa_star_bomb(Player *plr) { + for(int i = 0; i < 20; i++) { + float r = frand()*40 + 100; + float phi = frand()*2*M_PI; + create_particle1c("maristar_orbit", plr->pos + r*cexp(I*phi), 0, marisa_star_bomb_draw, marisa_star_orbit, I*r*cexp(I*(phi+frand()*0.5))/10)->type=PlrProj; + } +} + +static void marisa_star_power(Player *plr, short npow) { + Enemy *e = plr->slaves, *tmp; + double dmg = 5; + + if(plr->power / 100 == npow / 100) { + return; + } + + while(e != 0) { + tmp = e; + e = e->next; + if(tmp->hp == ENEMY_IMMUNE) + delete_enemy(&plr->slaves, tmp); + } + + if(npow / 100 == 1) { + create_enemy_p(&plr->slaves, 40.0*I, ENEMY_IMMUNE, marisa_common_slave_draw, marisa_star_slave, +30.0*I, -2.0*I, -0.1*I, dmg); + } + + if(npow >= 200) { + create_enemy_p(&plr->slaves, 30.0*I+15, ENEMY_IMMUNE, marisa_common_slave_draw, marisa_star_slave, +30.0*I+10, -0.3-2.0*I, 1-0.1*I, dmg); + create_enemy_p(&plr->slaves, 30.0*I-15, ENEMY_IMMUNE, marisa_common_slave_draw, marisa_star_slave, +30.0*I-10, 0.3-2.0*I, -1-0.1*I, dmg); + } + + if(npow / 100 == 3) { + create_enemy_p(&plr->slaves, -30.0*I, ENEMY_IMMUNE, marisa_common_slave_draw, marisa_star_slave, +30.0*I, -2.0*I, -0.1*I, dmg); + } + + if(npow >= 400) { + create_enemy_p(&plr->slaves, 30, ENEMY_IMMUNE, marisa_common_slave_draw, marisa_star_slave, 25+30.0*I, -0.6-2.0*I, 2-0.1*I, dmg); + create_enemy_p(&plr->slaves, -30, ENEMY_IMMUNE, marisa_common_slave_draw, marisa_star_slave, -25+30.0*I, 0.6-2.0*I, -2-0.1*I, dmg); + } +} + +PlayerMode plrmode_marisa_b = { + .name = "Star Sign", + .character = &character_marisa, + .shot_mode = PLR_SHOT_MARISA_STAR, + .procs = { + .bomb = marisa_star_bomb, + .shot = marisa_common_shot, + .power = marisa_star_power, + }, +}; diff --git a/src/plrmodes/marisa_b.h b/src/plrmodes/marisa_b.h new file mode 100644 index 00000000..8f878a67 --- /dev/null +++ b/src/plrmodes/marisa_b.h @@ -0,0 +1,13 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2017, Lukas Weber . + * Copyright (c) 2012-2017, Andrei Alexeyev . + */ + +#pragma once + +#include "plrmodes.h" + +PlayerMode plrmode_marisa_b; diff --git a/src/plrmodes/youmu.c b/src/plrmodes/youmu.c new file mode 100644 index 00000000..6953b46a --- /dev/null +++ b/src/plrmodes/youmu.c @@ -0,0 +1,71 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2017, Lukas Weber . + * Copyright (c) 2012-2017, Andrei Alexeyev . + */ + +#include "global.h" +#include "plrmodes.h" + +PlayerCharacter character_youmu = { + .id = PLR_CHAR_YOUMU, + .lower_name = "youmu", + .proper_name = "Yōmu", + .full_name = "Konpaku Yōmu", + .title = "Half-Phantom Girl", + .dialog_sprite_name = "dialog/youmu", + .player_sprite_name = "youmu", + .ending = { + .good = good_ending_youmu, + .bad = bad_ending_youmu, + }, +}; + +int youmu_common_particle_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; +} + +void youmu_common_particle_slice_draw(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 youmu_common_shot(Player *plr) { + 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; + } +} diff --git a/src/plrmodes/youmu.h b/src/plrmodes/youmu.h new file mode 100644 index 00000000..eb4787d5 --- /dev/null +++ b/src/plrmodes/youmu.h @@ -0,0 +1,19 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2017, Lukas Weber . + * Copyright (c) 2012-2017, Andrei Alexeyev . + */ + +#pragma once + +#include "plrmodes.h" +#include "youmu_a.h" +#include "youmu_b.h" + +extern PlayerCharacter character_youmu; + +int youmu_common_particle_spin(Projectile *p, int t); +void youmu_common_particle_slice_draw(Projectile *p, int t); +void youmu_common_shot(Player *plr); diff --git a/src/plrmodes/youmu_a.c b/src/plrmodes/youmu_a.c new file mode 100644 index 00000000..5613001e --- /dev/null +++ b/src/plrmodes/youmu_a.c @@ -0,0 +1,175 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2017, Lukas Weber . + * Copyright (c) 2012-2017, Andrei Alexeyev . + */ + +#include "global.h" +#include "plrmodes.h" +#include "youmu.h" + +static 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); +} + +static 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; +} + +static void myon_draw(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_mirror_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; +} + +static int youmu_mirror_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); + } + } + + 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(player_should_shoot(&global.plr, 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_mirror_myon_proj("youmu", e->pos, v1, a, r1*1, u, dmg_side); + youmu_mirror_myon_proj("youmu", e->pos, v1, a, -r1*1, u, dmg_side); + } + + if(plr->power >= 200) { + youmu_mirror_myon_proj("hghost", e->pos, v2, a, r2*2, 0, dmg_side); + youmu_mirror_myon_proj("hghost", e->pos, v2, a, -r2*2, 0, dmg_side); + } + + if(plr->power >= 300) { + youmu_mirror_myon_proj("youmu", e->pos, v1, a, r1*3, u, dmg_side); + youmu_mirror_myon_proj("youmu", e->pos, v1, a, -r1*3, u, dmg_side); + } + + if(plr->power >= 400) { + youmu_mirror_myon_proj("hghost", e->pos, v2, a, r2*4, 0, dmg_side); + youmu_mirror_myon_proj("hghost", e->pos, v2, a, -r2*4, 0, dmg_side); + } + + youmu_mirror_myon_proj("hghost", e->pos, v2, a, 0, 0, dmg_center); + } + + return 1; +} + +static 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, youmu_common_particle_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, youmu_common_particle_slice_draw, 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; +} + +static void youmu_mirror_bomb(Player *plr) { + create_enemy_p(&plr->slaves, 40.0*I, ENEMY_BOMB, NULL, youmu_split, 280,0,0,0); +} + +static void youmu_mirror_think(Player *plr) { + // XXX: do we really have to do this every frame? + + if(plr->slaves == NULL) { + create_enemy_p(&plr->slaves, 40.0*I, ENEMY_IMMUNE, myon_draw, youmu_mirror_myon, 0, 0, 0, 0); + } +} + +PlayerMode plrmode_youmu_a = { + .name = "Mirror Sign", + .character = &character_youmu, + .shot_mode = PLR_SHOT_YOUMU_MIRROR, + .procs = { + .bomb = youmu_mirror_bomb, + .shot = youmu_common_shot, + .think = youmu_mirror_think, + }, +}; diff --git a/src/plrmodes/youmu_a.h b/src/plrmodes/youmu_a.h new file mode 100644 index 00000000..aecdb1a9 --- /dev/null +++ b/src/plrmodes/youmu_a.h @@ -0,0 +1,13 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2017, Lukas Weber . + * Copyright (c) 2012-2017, Andrei Alexeyev . + */ + +#pragma once + +#include "plrmodes.h" + +extern PlayerMode plrmode_youmu_a; diff --git a/src/plrmodes/youmu_b.c b/src/plrmodes/youmu_b.c new file mode 100644 index 00000000..beeb5df6 --- /dev/null +++ b/src/plrmodes/youmu_b.c @@ -0,0 +1,234 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2017, Lukas Weber . + * Copyright (c) 2012-2017, Andrei Alexeyev . + */ + +#include "global.h" +#include "plrmodes.h" +#include "youmu.h" + +static complex youmu_homing_target(complex org, complex fallback) { + double mindst = DBL_MAX; + complex target = fallback; + + if(global.boss) { + target = global.boss->pos; + mindst = cabs(target - org); + } + + 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; + } + } + + return target; +} + +static 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); +} + +static 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); +} + +static 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); +} + +static 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; +} + +static 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; + } + + if(t > creal(p->args[2])) { + return ACTION_DESTROY; + } + + p->args[3] = youmu_homing_target(p->pos, p->args[3]); + + 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; +} + +static int youmu_trap(Projectile *p, int t) { + if(t == EVENT_DEATH) { + create_particle1c("blast", p->pos, 0, Blast, timeout, 15); + return 1; + } + + double expiretime = creal(p->args[1]); + + if(t > expiretime) { + return ACTION_DESTROY; + } + + 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; +} + +static void YoumuSlash(Enemy *e, int t) { + fade_out(10.0/t+sin(t/10.0)*0.1); +} + +static 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, youmu_common_particle_slice_draw, 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, youmu_common_particle_spin, 300, -7.0*I+afrand(2)*1.0*I); + } + return 1; +} + +static void youmu_haunting_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; + } +} + +static void youmu_haunting_shot(Player *plr) { + youmu_common_shot(plr); + + if(player_should_shoot(plr, 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_haunting_power_shot(plr, p); + } + } + } +} + +static void youmu_haunting_bomb(Player *plr) { + play_sound("haunt"); + create_enemy_p(&plr->slaves, 40.0*I, ENEMY_BOMB, YoumuSlash, youmu_slash, 280,0,0,0); +} + +static void youmu_haunting_preload(Player *plr) { + const int flags = RESF_DEFAULT; + + preload_resources(RES_SFX, flags | RESF_OPTIONAL, + "haunt", + NULL); +} + +PlayerMode plrmode_youmu_b = { + .name = "Haunting Sign", + .character = &character_youmu, + .shot_mode = PLR_SHOT_YOUMU_HAUNTING, + .procs = { + .bomb = youmu_haunting_bomb, + .shot = youmu_haunting_shot, + .preload = youmu_haunting_preload, + }, +}; diff --git a/src/plrmodes/youmu_b.h b/src/plrmodes/youmu_b.h new file mode 100644 index 00000000..6c7d25e8 --- /dev/null +++ b/src/plrmodes/youmu_b.h @@ -0,0 +1,13 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2017, Lukas Weber . + * Copyright (c) 2012-2017, Andrei Alexeyev . + */ + +#pragma once + +#include "plrmodes.h" + +extern PlayerMode plrmode_youmu_b; diff --git a/src/replay.c b/src/replay.c index 34be6226..48b5ff31 100644 --- a/src/replay.c +++ b/src/replay.c @@ -41,8 +41,8 @@ ReplayStage* replay_create_stage(Replay *rpy, StageInfo *stage, uint64_t seed, D s->plr_pos_y = floor(cimag(plr->pos)); s->plr_focus = plr->focus; - s->plr_char = plr->cha; - s->plr_shot = plr->shot; + s->plr_char = plr->mode->character->id; + s->plr_shot = plr->mode->shot_mode; s->plr_lives = plr->lives; s->plr_life_fragments = plr->life_fragments; s->plr_bombs = plr->bombs; @@ -56,8 +56,7 @@ ReplayStage* replay_create_stage(Replay *rpy, StageInfo *stage, uint64_t seed, D void replay_stage_sync_player_state(ReplayStage *stg, Player *plr) { plr->points = stg->points; - plr->shot = stg->plr_shot; - plr->cha = stg->plr_char; + plr->mode = plrmode_find(stg->plr_char, stg->plr_shot); plr->pos = stg->plr_pos_x + I * stg->plr_pos_y; plr->focus = stg->plr_focus; plr->lives = stg->plr_lives; @@ -739,6 +738,7 @@ void replay_play(Replay *rpy, int firstidx) { continue; } + global.plr.mode = plrmode_find(rstg->plr_char, rstg->plr_shot); stage_loop(gstg); if(global.game_over == GAMEOVER_ABORT) { diff --git a/src/stage.c b/src/stage.c index e45b5b91..a71b6128 100644 --- a/src/stage.c +++ b/src/stage.c @@ -185,7 +185,7 @@ static void stage_start(StageInfo *stage) { global.game_over = 0; global.shake_view = 0; - prepare_player_for_next_stage(&global.plr); + player_stage_pre_init(&global.plr); if(stage->type == STAGE_SPELL) { global.plr.lives = 0; @@ -599,7 +599,7 @@ void stage_loop(StageInfo *stage) { global.replay_stage = replay_create_stage(&global.replay, stage, seed, global.diff, global.plr.points, &global.plr); // make sure our player state is consistent with what goes into the replay - init_player(&global.plr); + player_init(&global.plr); replay_stage_sync_player_state(global.replay_stage, &global.plr); } else { global.replay_stage = NULL; @@ -632,17 +632,12 @@ void stage_loop(StageInfo *stage) { log_debug("Random seed: %u", stg->seed); global.diff = stg->diff; - init_player(&global.plr); + player_init(&global.plr); replay_stage_sync_player_state(stg, &global.plr); stg->playpos = 0; } - // 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); - + player_stage_post_init(&global.plr); stage->procs->begin(); StageFrameState fstate = { .stage = stage }; diff --git a/src/stages/stage1_events.c b/src/stages/stage1_events.c index 9c378ddb..1e6f91f3 100644 --- a/src/stages/stage1_events.c +++ b/src/stages/stage1_events.c @@ -10,23 +10,33 @@ #include "global.h" Dialog *stage1_dialog(void) { - Dialog *d = create_dialog(global.plr.cha == Marisa ? "dialog/marisa" : "dialog/youmu", "dialog/cirno"); + PlayerCharacter *pc = global.plr.mode->character; + Dialog *d = create_dialog(pc->dialog_sprite_name, "dialog/cirno"); dadd_msg(d, Right, "Hey! Who’s there?"); - if(global.plr.cha == Marisa) - dadd_msg(d, Left, "It’s me!"); - else - dadd_msg(d, Left, "Just someone?"); + switch(pc->id) { + case PLR_CHAR_MARISA: + dadd_msg(d, Left, "It’s me!"); + break; + + case PLR_CHAR_YOUMU: + dadd_msg(d, Left, "Just someone?"); + break; + } dadd_msg(d, Right, "How dare you pass the lake of the fairies?!\nIt’s a dangerous place for weak humans!"); - if(global.plr.cha == Marisa) { - dadd_msg(d, Left, "You call me weak?"); - dadd_msg(d, Right, "I do!"); - } else { - dadd_msg(d, Left, "I’m just passing by. Got a problem with that?"); - dadd_msg(d, Right, "Of course! You can’t do that!"); + switch(pc->id) { + case PLR_CHAR_MARISA: + dadd_msg(d, Left, "You call me weak?"); + dadd_msg(d, Right, "I do!"); + break; + + case PLR_CHAR_YOUMU: + dadd_msg(d, Left, "I’m just passing by. Got a problem with that?"); + dadd_msg(d, Right, "Of course! You can’t do that!"); + break; } dadd_msg(d, Right, "I’ll freeze you where you stand!"); diff --git a/src/stages/stage2_events.c b/src/stages/stage2_events.c index 8ccb5949..e0ab0058 100644 --- a/src/stages/stage2_events.c +++ b/src/stages/stage2_events.c @@ -12,9 +12,10 @@ #include "enemy.h" Dialog *stage2_dialog(void) { - Dialog *d = create_dialog(global.plr.cha == Marisa ? "dialog/marisa" : "dialog/youmu", "dialog/hina"); + PlayerCharacter *pc = global.plr.mode->character; + Dialog *d = create_dialog(pc->dialog_sprite_name, "dialog/hina"); - if(global.plr.cha == Marisa) { + if(pc->id == PLR_CHAR_MARISA) { dadd_msg(d, Left, "Ha! What are you doing here?\nYou the culprit?"); dadd_msg(d, Right, "Huh? No, you? Everone is going crazy, you know?\nThat’s why I’m here."); dadd_msg(d, Left, "Why? What happened?"); @@ -23,7 +24,7 @@ Dialog *stage2_dialog(void) { dadd_msg(d, Right, "Look, there is a way outside\nright behind us."); dadd_msg(d, Left, "But I’ve got a feeling that you\nwon’t let me pass, haha!"); dadd_msg(d, Right, "First let me see if I can collect some misfortune from you!"); - } else { + } else if(pc->id == PLR_CHAR_YOUMU) { dadd_msg(d, Left, "This must be the place…"); dadd_msg(d, Right, "Hello? "); dadd_msg(d, Left, "You came here because of the\n“crack”, too? Where is it?"); @@ -37,7 +38,8 @@ Dialog *stage2_dialog(void) { } Dialog *stage2_post_dialog(void) { - Dialog *d = create_dialog(global.plr.cha == Marisa ? "dialog/marisa" : "dialog/youmu", NULL); + PlayerCharacter *pc = global.plr.mode->character; + Dialog *d = create_dialog(pc->dialog_sprite_name, NULL); dadd_msg(d, Left, "Well, let’s go then."); diff --git a/src/stages/stage3_events.c b/src/stages/stage3_events.c index f0318abc..7bb347f1 100644 --- a/src/stages/stage3_events.c +++ b/src/stages/stage3_events.c @@ -12,13 +12,14 @@ #include "enemy.h" Dialog *stage3_dialog(void) { - Dialog *d = create_dialog(global.plr.cha == Marisa ? "dialog/marisa" : "dialog/youmu", "dialog/wriggle"); + PlayerCharacter *pc = global.plr.mode->character; + Dialog *d = create_dialog(pc->dialog_sprite_name, "dialog/wriggle"); - if(global.plr.cha == Marisa) { + if(pc->id == PLR_CHAR_MARISA) { dadd_msg(d, Left, "Ugh, it’s like bugs being attracted by the light…"); dadd_msg(d, Right, "That’s right! The light makes us strong!"); dadd_msg(d, Right, "And this place is filled with it!\nFeel my tremendous power!"); - } else { + } else if(pc->id == PLR_CHAR_YOUMU) { dadd_msg(d, Left, "Eww, I can’t stand bugs."); dadd_msg(d, Right, "Don’t discriminate! We are A-class yōkai!\nVery dangerous!"); dadd_msg(d, Left, "You mean very delusional?\nI usually step on you."); diff --git a/src/stages/stage4_events.c b/src/stages/stage4_events.c index c1064963..9be4dcd8 100644 --- a/src/stages/stage4_events.c +++ b/src/stages/stage4_events.c @@ -21,17 +21,18 @@ void kurumi_danmaku(Boss*, int); void kurumi_extra(Boss*, int); Dialog *stage4_dialog(void) { - Dialog *d = create_dialog(global.plr.cha == Marisa ? "dialog/marisa" : "dialog/youmu", "dialog/kurumi"); + PlayerCharacter *pc = global.plr.mode->character; + Dialog *d = create_dialog(pc->dialog_sprite_name, "dialog/kurumi"); dadd_msg(d, Right, "Ah! Intruder! Stop being so persistent!"); - if(global.plr.cha == Marisa) { + if(pc->id == PLR_CHAR_MARISA) { dadd_msg(d, Left, "What? I mean, where am I?"); dadd_msg(d, Right, "You are in the…"); dadd_msg(d, Right, "Ha! you almost got me!\nI will never tell intruders like you!"); dadd_msg(d, Left, "…in the mansion of the\nevil mastermind, right?"); dadd_msg(d, Right, "AHH! Anyway! You won’t reach\nthe end of this corridor!"); - } else { + } else if(pc->id == PLR_CHAR_YOUMU) { dadd_msg(d, Left, "So you are the owner of this place?"); dadd_msg(d, Right, "No, I’m just the guardian!"); dadd_msg(d, Left, "What is there to be guarded?"); @@ -45,7 +46,8 @@ Dialog *stage4_dialog(void) { } Dialog *stage4_dialog_end(void) { - Dialog *d = create_dialog(global.plr.cha == Marisa ? "dialog/marisa" : "dialog/youmu", "dialog/kurumi"); + PlayerCharacter *pc = global.plr.mode->character; + Dialog *d = create_dialog(pc->dialog_sprite_name, "dialog/kurumi"); dadd_msg(d, Left, "Now, where is your master?"); dadd_msg(d, Right, "At the end of this corridor,\nthere is a door."); diff --git a/src/stages/stage5_events.c b/src/stages/stage5_events.c index da4cf763..ccf7054d 100644 --- a/src/stages/stage5_events.c +++ b/src/stages/stage5_events.c @@ -12,7 +12,8 @@ #include Dialog *stage5_post_mid_dialog(void) { - Dialog *d = create_dialog(global.plr.cha == Marisa ? "dialog/marisa" : "dialog/youmu", NULL); + PlayerCharacter *pc = global.plr.mode->character; + Dialog *d = create_dialog(pc->dialog_sprite_name, NULL); dadd_msg(d, Left, "Hey! Stop!"); @@ -20,15 +21,17 @@ Dialog *stage5_post_mid_dialog(void) { } Dialog *stage5_boss_dialog(void) { - Dialog *d = create_dialog(global.plr.cha == Marisa ? "dialog/marisa" : "dialog/youmu", "dialog/iku"); - if(global.plr.cha == Marisa) { + PlayerCharacter *pc = global.plr.mode->character; + Dialog *d = create_dialog(pc->dialog_sprite_name, "dialog/iku"); + + if(pc->id == PLR_CHAR_MARISA) { dadd_msg(d, Left, "Finally!"); dadd_msg(d, Right, "Stop following me!"); dadd_msg(d, Left, "Why? You aren’t involved in this, are you?"); dadd_msg(d, Right, "I don’t have time for your suspicions now."); dadd_msg(d, Left, "Sounds very suspicious, actually."); dadd_msg(d, Right, "Okay, let’s just get this over with."); - } else { + } else if(pc->id == PLR_CHAR_YOUMU) { dadd_msg(d, Left, "There you are!"); dadd_msg(d, Right, "Stop following me!"); dadd_msg(d, Left, "You look very suspicious to me…"); @@ -41,12 +44,13 @@ Dialog *stage5_boss_dialog(void) { } Dialog *stage5_post_boss_dialog(void) { - Dialog *d = create_dialog(global.plr.cha == Marisa ? "dialog/marisa" : "dialog/youmu", NULL); + PlayerCharacter *pc = global.plr.mode->character; + Dialog *d = create_dialog(pc->dialog_sprite_name, NULL); dadd_msg(d, Left, "I can see the top!"); - if(global.plr.cha == Marisa) { + if(pc->id == PLR_CHAR_MARISA) { dadd_msg(d, Left, "Hopefully climbing all those stairs\nwas worth it."); - } else { + } else if(pc->id == PLR_CHAR_YOUMU) { dadd_msg(d, Left, "Looks like she really wasn’t behind this…\nWell this tower looks magnificent though!"); dadd_msg(d, Left, "It’s the type of tower that never comes\nwithout an evil genius at its top."); } diff --git a/src/stages/stage6_events.c b/src/stages/stage6_events.c index 40bd2aaf..8e98f13e 100644 --- a/src/stages/stage6_events.c +++ b/src/stages/stage6_events.c @@ -11,17 +11,18 @@ #include Dialog *stage6_dialog(void) { - Dialog *d = create_dialog(global.plr.cha == Marisa ? "dialog/marisa" : "dialog/youmu", "dialog/elly"); + PlayerCharacter *pc = global.plr.mode->character; + Dialog *d = create_dialog(pc->dialog_sprite_name, "dialog/elly"); dadd_msg(d, Left, "You are responsible for all this?"); dadd_msg(d, Right, "Yes…"); - if(global.plr.cha == Marisa) { + if(pc->id == PLR_CHAR_MARISA) { dadd_msg(d, Left, "I’m going to masterspark you now!"); dadd_msg(d, Right, "What? Why do you want to fight?\nDon’t you even want to hear what\nI have to say?"); dadd_msg(d, Left, "I understand that it’s a huge mess!\nCracking the border, a giant mansion, a giant tower…"); dadd_msg(d, Left, "At first I was curious. But now\nI just want to finish this! Seriously."); - } else { + } else if(pc->id == PLR_CHAR_YOUMU) { dadd_msg(d, Left, "Who are you and what is this?"); dadd_msg(d, Right, "A kind person granted me an unknown power,\nand thanks to that I was able to\ncreate this little place for myself."); dadd_msg(d, Left, "Why did you create *this* kind of place for yourself?");