Initial support for single-spell stages

Adapted all of the current spellcards into spellstages, which will
later be used in a spell practice mode a-la IN.
For now they are only accessible through the stage select menu or
by specifying their ID on the command line; both available only
if you built with -DTAISEI_DEBUG=1
This commit is contained in:
Andrei "Akari" Alexeyev 2017-02-10 12:39:42 +02:00 committed by Martin Herkt
parent 1f6faed3fc
commit 1e6011433c
30 changed files with 758 additions and 113 deletions

View file

@ -7,10 +7,11 @@
#include "boss.h"
#include "global.h"
#include "stage.h"
#include <string.h>
#include <stdio.h>
Boss *create_boss(char *name, char *ani, complex pos) {
Boss* create_boss(char *name, char *ani, complex pos) {
Boss *buf = malloc(sizeof(Boss));
memset(buf, 0, sizeof(Boss));
@ -190,7 +191,7 @@ void start_attack(Boss *b, Attack *a) {
delete_lasers();
}
Attack *boss_add_attack(Boss *boss, AttackType type, char *name, float timeout, int hp, BossRule rule, BossRule draw_rule) {
Attack* boss_add_attack(Boss *boss, AttackType type, char *name, float timeout, int hp, BossRule rule, BossRule draw_rule) {
boss->attacks = realloc(boss->attacks, sizeof(Attack)*(++boss->acount));
Attack *a = &boss->attacks[boss->acount-1];
@ -213,3 +214,19 @@ Attack *boss_add_attack(Boss *boss, AttackType type, char *name, float timeout,
return a;
}
void boss_generic_move(Boss *b, int time) {
if(b->current->info->pos_dest != BOSS_NOMOVE) {
GO_TO(b, b->current->info->pos_dest, 0.1)
}
}
Attack* boss_add_attack_from_info(Boss *boss, AttackInfo *info, char move) {
if(move) {
boss_add_attack(boss, AT_Move, "Generic Move", 1, 0, boss_generic_move, NULL)->info = info;
}
Attack *a = boss_add_attack(boss, info->type, info->name, info->timeout, info->hp, info->rule, info->draw_rule);
a->info = info;
return a;
}

View file

@ -22,6 +22,18 @@ typedef enum AttackType {
AT_SurvivalSpell
} AttackType;
typedef struct AttackInfo {
AttackType type;
char *name;
float timeout;
int hp;
BossRule rule;
BossRule draw_rule;
// complex pos_spawn;
complex pos_dest;
} AttackInfo;
typedef struct Attack {
char *name;
@ -35,6 +47,8 @@ typedef struct Attack {
BossRule rule;
BossRule draw_rule;
AttackInfo *info; // NULL for attacks created directly through boss_add_attack
} Attack;
typedef struct Boss {
@ -54,7 +68,7 @@ typedef struct Boss {
Color *zoomcolor;
} Boss;
Boss *create_boss(char *name, char *ani, complex pos);
Boss* create_boss(char *name, char *ani, complex pos);
void draw_boss(Boss *boss);
void process_boss(Boss *boss);
@ -63,8 +77,13 @@ void free_attack(Attack *a);
void start_attack(Boss *b, Attack *a);
Attack *boss_add_attack(Boss *boss, AttackType type, char *name, float timeout, int hp, BossRule rule, BossRule draw_rule);
Attack* boss_add_attack(Boss *boss, AttackType type, char *name, float timeout, int hp, BossRule rule, BossRule draw_rule);
Attack* boss_add_attack_from_info(Boss *boss, AttackInfo *info, char move);
void boss_death(Boss **boss);
#define BOSS_DEFAULT_SPAWN_POS (VIEWPORT_W * 0.5 - I * VIEWPORT_H * 0.5)
#define BOSS_DEFAULT_GO_POS (VIEWPORT_W * 0.5 + 200.0I)
#define BOSS_NOMOVE (-3142-39942I)
#endif

View file

@ -252,10 +252,22 @@ char* difficulty_name(Difficulty diff) {
case D_Normal: return "Normal"; break;
case D_Hard: return "Hard"; break;
case D_Lunatic: return "Lunatic"; break;
case D_Extra: return "Extra"; break;
default: return "Unknown"; break;
}
}
void difficulty_color(Color *c, Difficulty diff) {
switch(diff) {
case D_Easy: c->r = 0.5; c->g = 1.0; c->b = 0.5; break;
case D_Normal: c->r = 0.5; c->g = 0.5; c->b = 1.0; break;
case D_Hard: c->r = 1.0; c->g = 0.5; c->b = 0.5; break;
case D_Lunatic: c->r = 1.0; c->g = 0.5; c->b = 1.0; break;
case D_Extra: c->r = 0.5; c->g = 1.0; c->b = 1.0; break;
default: c->r = 0.5; c->g = 0.5; c->b = 0.5; break;
}
}
void stralloc(char **dest, const char *src) {
if(*dest)
free(*dest);

View file

@ -98,6 +98,7 @@ typedef struct {
Projectile *particles;
int frames;
int stageuiframes;
int lasttime; // frame limiter
int timer;
int frameskip;
@ -121,6 +122,8 @@ typedef struct {
RandomState rand_game;
RandomState rand_visual;
StageInfo *stage;
} Global;
extern Global global;
@ -150,6 +153,7 @@ double approach(double v, double t, double d);
double psin(double);
bool strendswith(char *s, char *e);
char* difficulty_name(Difficulty diff);
void difficulty_color(Color *c, Difficulty diff);
void stralloc(char **dest, const char *src);
bool gamekeypressed(KeyIndex key);
int getenvint(const char *v);

View file

@ -86,11 +86,23 @@ int run_tests(void) {
#define MKDIR(p) mkdir(p, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)
#endif
int main(int argc, char** argv) {
int main(int argc, char **argv) {
if(run_tests()) {
return 0;
}
#ifdef DEBUG
if(argc >= 2 && argv[1] && !strcmp(argv[1], "dumpstages")) {
stage_init_array();
for(StageInfo *stg = stages; stg->loop; ++stg) {
printf("%i %s: %s\n", stg->id, stg->title, stg->subtitle);
}
return 0;
}
#endif
init_paths();
init_log();
@ -121,6 +133,7 @@ int main(int argc, char** argv) {
draw_loading_screen();
gamepad_init();
stage_init_array();
// Order DOES matter: init_global, then sfx/bgm, then load_resources.
init_sfx();
@ -135,29 +148,36 @@ int main(int argc, char** argv) {
if(argc >= 2 && argv[1]) {
printf("** Entering stage skip mode: Stage %d\n", atoi(argv[1]));
global.diff = D_Easy;
StageInfo* stg = stage_get(atoi(argv[1]));
if(!stg) {
errx(-1, "Invalid stage id");
}
global.diff = stg->difficulty;
if(!global.diff) {
global.diff = D_Easy;
}
if(argc == 3 && argv[2]) {
printf("** Setting difficulty to %d.\n", atoi(argv[2]));
global.diff = atoi(argv[2]);
}
StageInfo* stg = stage_get(atoi(argv[1]));
if(stg) {
printf("** Entering %s.\n", stg->title);
printf("** Entering %s.\n", stg->title);
do {
global.game_over = 0;
init_player(&global.plr);
stg->loop();
} while(global.game_over == GAMEOVER_RESTART);
do {
global.game_over = 0;
init_player(&global.plr);
global.stage = stg;
stg->loop();
} while(global.game_over == GAMEOVER_RESTART);
return 0;
}
printf("** Invalid stage number. Quitting stage skip mode.\n");
return 0;
}
#endif
MenuData menu;

View file

@ -15,17 +15,29 @@
void start_game(MenuData *menu, void *arg) {
MenuData m;
StageInfo *info = arg;
Difficulty stagediff;
init_player(&global.plr);
troll:
create_difficulty_menu(&m);
if(difficulty_menu_loop(&m) == -1)
return;
global.diff = stagediff = info ? info->difficulty : D_Any;
if(stagediff == D_Any) {
create_difficulty_menu(&m);
if(difficulty_menu_loop(&m) == -1) {
return;
}
}
create_char_menu(&m);
if(char_menu_loop(&m) == -1)
if(char_menu_loop(&m) == -1) {
if(stagediff != D_Any) {
return;
}
goto troll;
}
global.replay_stage = NULL;
replay_init(&global.replay);
@ -34,12 +46,14 @@ troll:
int sht = global.plr.shot;
troll2:
if(arg)
((StageInfo*)arg)->loop();
else {
int i;
for(i = 0; stages[i].loop; ++i)
if(info) {
global.stage = info;
info->loop();
} else {
for(int i = 0; stages[i].type == STAGE_STORY; ++i) {
global.stage = stages + i;
stages[i].loop();
}
}
if(global.game_over == GAMEOVER_RESTART) {

View file

@ -56,6 +56,7 @@ void start_replay(MenuData *menu, void *arg) {
continue;
}
global.stage = gstg;
gstg->loop();
if(global.game_over == GAMEOVER_ABORT) {
@ -219,10 +220,17 @@ static void replayview_drawitem(void *n, int item, int cnt) {
case 4:
a = AL_Right;
if(rpy->numstages == 1)
snprintf(tmp, sizeof(tmp), "Stage %i", rpy->stages[0].stage);
else
if(rpy->numstages == 1) {
StageInfo *stg = stage_get(rpy->stages[0].stage);
if(stg) {
snprintf(tmp, sizeof(tmp), "%s", stg->title);
} else {
snprintf(tmp, sizeof(tmp), "?????");
}
} else {
snprintf(tmp, sizeof(tmp), "%i stages", rpy->numstages);
}
break;
}

View file

@ -15,17 +15,22 @@
void create_stage_menu(MenuData *m) {
char title[STGMENU_MAX_TITLE_LENGTH];
int i;
Difficulty lastdiff = D_Any;
create_menu(m);
m->flags = MF_Abortable;
for(i = 0; stages[i].loop; ++i) if(!stages[i].hidden) {
snprintf(title, STGMENU_MAX_TITLE_LENGTH, "%s", stages[i].title);
add_menu_entry(m, title, start_game, stages+i);
for(int i = 0; stages[i].loop; ++i) {
if(stages[i].difficulty < lastdiff || (stages[i].difficulty && !lastdiff)) {
add_menu_separator(m);
}
snprintf(title, STGMENU_MAX_TITLE_LENGTH, "%s: %s", stages[i].title, stages[i].subtitle);
add_menu_entry(m, title, start_game, &(stages[i]));
lastdiff = stages[i].difficulty;
}
add_menu_separator(m);
add_menu_entry(m, "Back", menu_commonaction_close, NULL);
}

View file

@ -52,7 +52,7 @@ typedef struct ReplayStage {
/* BEGIN stored fields */
// initial game settings
uint16_t stage;
uint16_t stage; // must match type of StageInfo.id in replay.h
uint32_t seed; // this also happens to be the game initiation time - and we use this property, don't break it please
uint8_t diff;
uint32_t points;

View file

@ -22,29 +22,229 @@
#include "taisei_err.h"
StageInfo stages[] = {
// id loop hidden title subtitle titleclr bosstitleclr
{1, stage1_loop, false, "Stage 1", "Misty Lake", {1, 1, 1}, {1, 1, 1}},
{2, stage2_loop, false, "Stage 2", "Walk Along the Border", {1, 1, 1}, {1, 1, 1}},
{3, stage3_loop, false, "Stage 3", "Through the Tunnel of Light", {0, 0, 0}, {0, 0, 0}},
{4, stage4_loop, false, "Stage 4", "Forgotten Mansion", {0, 0, 0}, {1, 1, 1}},
{5, stage5_loop, false, "Stage 5", "Climbing the Tower of Babel", {1, 1, 1}, {1, 1, 1}},
{6, stage6_loop, false, "Stage 6", "Roof of the World", {1, 1, 1}, {1, 1, 1}},
//
// ids must be unique
// they don't necessarily have to be human-readable or ordered
// they're basically there just for replays, so don't change the already enstabilished ones unless you must
//
//
// Story stages
//
// id loop type title subtitle titleclr bosstitleclr spell difficulty
{1, stage1_loop, STAGE_STORY, "Stage 1", "Misty Lake", {1, 1, 1}, {1, 1, 1}, NULL, D_Any},
{2, stage2_loop, STAGE_STORY, "Stage 2", "Walk Along the Border", {1, 1, 1}, {1, 1, 1}, NULL, D_Any},
{3, stage3_loop, STAGE_STORY, "Stage 3", "Through the Tunnel of Light", {0, 0, 0}, {0, 0, 0}, NULL, D_Any},
{4, stage4_loop, STAGE_STORY, "Stage 4", "Forgotten Mansion", {0, 0, 0}, {1, 1, 1}, NULL, D_Any},
{5, stage5_loop, STAGE_STORY, "Stage 5", "Climbing the Tower of Babel", {1, 1, 1}, {1, 1, 1}, NULL, D_Any},
{6, stage6_loop, STAGE_STORY, "Stage 6", "Roof of the World", {1, 1, 1}, {1, 1, 1}, NULL, D_Any},
//
// Spell practice stages
//
// subtitles for those are generated later by stage_init_array()
//
#define S STAGE_SPELL_BIT
// Freeze Sign ~ Perfect Freeze
{S| 0, stage1_spellpractice_loop, STAGE_SPELL, "Spell 1", NULL, {1, 1, 1}, {1, 1, 1}, stage1_spells+0, D_Easy},
{S| 1, stage1_spellpractice_loop, STAGE_SPELL, "Spell 2", NULL, {1, 1, 1}, {1, 1, 1}, stage1_spells+0, D_Normal},
{S| 2, stage1_spellpractice_loop, STAGE_SPELL, "Spell 3", NULL, {1, 1, 1}, {1, 1, 1}, stage1_spells+0, D_Hard},
{S| 3, stage1_spellpractice_loop, STAGE_SPELL, "Spell 4", NULL, {1, 1, 1}, {1, 1, 1}, stage1_spells+0, D_Lunatic},
// Freeze Sign ~ Crystal Rain
{S| 4, stage1_spellpractice_loop, STAGE_SPELL, "Spell 5", NULL, {1, 1, 1}, {1, 1, 1}, stage1_spells+1, D_Easy},
{S| 5, stage1_spellpractice_loop, STAGE_SPELL, "Spell 6", NULL, {1, 1, 1}, {1, 1, 1}, stage1_spells+1, D_Normal},
{S| 6, stage1_spellpractice_loop, STAGE_SPELL, "Spell 7", NULL, {1, 1, 1}, {1, 1, 1}, stage1_spells+1, D_Hard},
{S| 7, stage1_spellpractice_loop, STAGE_SPELL, "Spell 8", NULL, {1, 1, 1}, {1, 1, 1}, stage1_spells+1, D_Lunatic},
// Doom Sign ~ Icicle Fall
{S| 8, stage1_spellpractice_loop, STAGE_SPELL, "Spell 9", NULL, {1, 1, 1}, {1, 1, 1}, stage1_spells+2, D_Easy},
{S| 9, stage1_spellpractice_loop, STAGE_SPELL, "Spell 10", NULL, {1, 1, 1}, {1, 1, 1}, stage1_spells+2, D_Normal},
{S| 10, stage1_spellpractice_loop, STAGE_SPELL, "Spell 11", NULL, {1, 1, 1}, {1, 1, 1}, stage1_spells+2, D_Hard},
{S| 11, stage1_spellpractice_loop, STAGE_SPELL, "Spell 12", NULL, {1, 1, 1}, {1, 1, 1}, stage1_spells+2, D_Lunatic},
// Shard ~ Amulet of Harm
{S| 12, stage2_spellpractice_loop, STAGE_SPELL, "Spell 13", NULL, {1, 1, 1}, {1, 1, 1}, stage2_spells+0, D_Easy},
{S| 13, stage2_spellpractice_loop, STAGE_SPELL, "Spell 14", NULL, {1, 1, 1}, {1, 1, 1}, stage2_spells+0, D_Normal},
{S| 14, stage2_spellpractice_loop, STAGE_SPELL, "Spell 15", NULL, {1, 1, 1}, {1, 1, 1}, stage2_spells+0, D_Hard},
{S| 15, stage2_spellpractice_loop, STAGE_SPELL, "Spell 16", NULL, {1, 1, 1}, {1, 1, 1}, stage2_spells+0, D_Lunatic},
// Lottery Sign ~ Bad Pick
{S| 16, stage2_spellpractice_loop, STAGE_SPELL, "Spell 17", NULL, {1, 1, 1}, {1, 1, 1}, stage2_spells+1, D_Easy},
{S| 17, stage2_spellpractice_loop, STAGE_SPELL, "Spell 18", NULL, {1, 1, 1}, {1, 1, 1}, stage2_spells+1, D_Normal},
{S| 18, stage2_spellpractice_loop, STAGE_SPELL, "Spell 19", NULL, {1, 1, 1}, {1, 1, 1}, stage2_spells+1, D_Hard},
{S| 19, stage2_spellpractice_loop, STAGE_SPELL, "Spell 20", NULL, {1, 1, 1}, {1, 1, 1}, stage2_spells+1, D_Lunatic},
// Lottery Sign ~ Wheel of Fortune
{S| 20, stage2_spellpractice_loop, STAGE_SPELL, "Spell 21", NULL, {1, 1, 1}, {1, 1, 1}, stage2_spells+2, D_Easy},
{S| 21, stage2_spellpractice_loop, STAGE_SPELL, "Spell 22", NULL, {1, 1, 1}, {1, 1, 1}, stage2_spells+2, D_Normal},
{S| 22, stage2_spellpractice_loop, STAGE_SPELL, "Spell 23", NULL, {1, 1, 1}, {1, 1, 1}, stage2_spells+2, D_Hard},
{S| 23, stage2_spellpractice_loop, STAGE_SPELL, "Spell 24", NULL, {1, 1, 1}, {1, 1, 1}, stage2_spells+2, D_Lunatic},
// Venom Sign ~ Deadly Dance
{S| 24, stage3_spellpractice_loop, STAGE_SPELL, "Spell 25", NULL, {0, 0, 0}, {0, 0, 0}, stage3_spells+0, D_Easy},
{S| 25, stage3_spellpractice_loop, STAGE_SPELL, "Spell 26", NULL, {0, 0, 0}, {0, 0, 0}, stage3_spells+0, D_Normal},
{S| 26, stage3_spellpractice_loop, STAGE_SPELL, "Spell 27", NULL, {0, 0, 0}, {0, 0, 0}, stage3_spells+0, D_Hard},
{S| 27, stage3_spellpractice_loop, STAGE_SPELL, "Spell 28", NULL, {0, 0, 0}, {0, 0, 0}, stage3_spells+0, D_Lunatic},
// Venom Sign ~ Acid Rain
{S| 28, stage3_spellpractice_loop, STAGE_SPELL, "Spell 29", NULL, {0, 0, 0}, {0, 0, 0}, stage3_spells+1, D_Hard},
{S| 29, stage3_spellpractice_loop, STAGE_SPELL, "Spell 30", NULL, {0, 0, 0}, {0, 0, 0}, stage3_spells+1, D_Lunatic},
// Firefly Sign ~ Moonlight Rocket
{S| 30, stage3_spellpractice_loop, STAGE_SPELL, "Spell 31", NULL, {0, 0, 0}, {0, 0, 0}, stage3_spells+2, D_Easy},
{S| 31, stage3_spellpractice_loop, STAGE_SPELL, "Spell 32", NULL, {0, 0, 0}, {0, 0, 0}, stage3_spells+2, D_Normal},
{S| 32, stage3_spellpractice_loop, STAGE_SPELL, "Spell 33", NULL, {0, 0, 0}, {0, 0, 0}, stage3_spells+2, D_Hard},
{S| 33, stage3_spellpractice_loop, STAGE_SPELL, "Spell 34", NULL, {0, 0, 0}, {0, 0, 0}, stage3_spells+2, D_Lunatic},
// Light Source ~ Wriggle Night Ignite
{S| 34, stage3_spellpractice_loop, STAGE_SPELL, "Spell 35", NULL, {0, 0, 0}, {0, 0, 0}, stage3_spells+3, D_Easy},
{S| 35, stage3_spellpractice_loop, STAGE_SPELL, "Spell 36", NULL, {0, 0, 0}, {0, 0, 0}, stage3_spells+3, D_Normal},
{S| 36, stage3_spellpractice_loop, STAGE_SPELL, "Spell 37", NULL, {0, 0, 0}, {0, 0, 0}, stage3_spells+3, D_Hard},
{S| 37, stage3_spellpractice_loop, STAGE_SPELL, "Spell 38", NULL, {0, 0, 0}, {0, 0, 0}, stage3_spells+3, D_Lunatic},
// Bug Sign ~ Phosphaenus Hemipterus
{S| 38, stage3_spellpractice_loop, STAGE_SPELL, "Spell 39", NULL, {0, 0, 0}, {0, 0, 0}, stage3_spells+4, D_Easy},
{S| 39, stage3_spellpractice_loop, STAGE_SPELL, "Spell 40", NULL, {0, 0, 0}, {0, 0, 0}, stage3_spells+4, D_Normal},
{S| 40, stage3_spellpractice_loop, STAGE_SPELL, "Spell 41", NULL, {0, 0, 0}, {0, 0, 0}, stage3_spells+4, D_Hard},
{S| 41, stage3_spellpractice_loop, STAGE_SPELL, "Spell 42", NULL, {0, 0, 0}, {0, 0, 0}, stage3_spells+4, D_Lunatic},
// Bloodless ~ Gate of Walachia
{S| 42, stage4_spellpractice_loop, STAGE_SPELL, "Spell 43", NULL, {1, 1, 1}, {1, 1, 1}, stage4_spells+0, D_Easy},
{S| 43, stage4_spellpractice_loop, STAGE_SPELL, "Spell 44", NULL, {1, 1, 1}, {1, 1, 1}, stage4_spells+0, D_Normal},
{S| 44, stage4_spellpractice_loop, STAGE_SPELL, "Spell 45", NULL, {1, 1, 1}, {1, 1, 1}, stage4_spells+0, D_Hard},
{S| 45, stage4_spellpractice_loop, STAGE_SPELL, "Spell 46", NULL, {1, 1, 1}, {1, 1, 1}, stage4_spells+0, D_Lunatic},
// Bloodless ~ Dry Fountain
{S| 46, stage4_spellpractice_loop, STAGE_SPELL, "Spell 47", NULL, {1, 1, 1}, {1, 1, 1}, stage4_spells+1, D_Easy},
{S| 47, stage4_spellpractice_loop, STAGE_SPELL, "Spell 48", NULL, {1, 1, 1}, {1, 1, 1}, stage4_spells+1, D_Normal},
// Bloodless ~ Red Spike
{S| 48, stage4_spellpractice_loop, STAGE_SPELL, "Spell 49", NULL, {1, 1, 1}, {1, 1, 1}, stage4_spells+2, D_Hard},
{S| 49, stage4_spellpractice_loop, STAGE_SPELL, "Spell 50", NULL, {1, 1, 1}, {1, 1, 1}, stage4_spells+2, D_Lunatic},
// Limit ~ Animate Wall
{S| 50, stage4_spellpractice_loop, STAGE_SPELL, "Spell 51", NULL, {1, 1, 1}, {1, 1, 1}, stage4_spells+3, D_Easy},
{S| 51, stage4_spellpractice_loop, STAGE_SPELL, "Spell 52", NULL, {1, 1, 1}, {1, 1, 1}, stage4_spells+3, D_Normal},
// Summoning ~ Demon Wall
{S| 52, stage4_spellpractice_loop, STAGE_SPELL, "Spell 53", NULL, {1, 1, 1}, {1, 1, 1}, stage4_spells+4, D_Hard},
{S| 53, stage4_spellpractice_loop, STAGE_SPELL, "Spell 54", NULL, {1, 1, 1}, {1, 1, 1}, stage4_spells+4, D_Lunatic},
// Power Sign ~ Blow the Walls
{S| 54, stage4_spellpractice_loop, STAGE_SPELL, "Spell 55", NULL, {1, 1, 1}, {1, 1, 1}, stage4_spells+5, D_Easy},
{S| 55, stage4_spellpractice_loop, STAGE_SPELL, "Spell 56", NULL, {1, 1, 1}, {1, 1, 1}, stage4_spells+5, D_Normal},
{S| 56, stage4_spellpractice_loop, STAGE_SPELL, "Spell 57", NULL, {1, 1, 1}, {1, 1, 1}, stage4_spells+5, D_Hard},
{S| 57, stage4_spellpractice_loop, STAGE_SPELL, "Spell 58", NULL, {1, 1, 1}, {1, 1, 1}, stage4_spells+5, D_Lunatic},
// Fear Sign ~ Bloody Danmaku
{S| 58, stage4_spellpractice_loop, STAGE_SPELL, "Spell 59", NULL, {1, 1, 1}, {1, 1, 1}, stage4_spells+6, D_Hard},
{S| 59, stage4_spellpractice_loop, STAGE_SPELL, "Spell 60", NULL, {1, 1, 1}, {1, 1, 1}, stage4_spells+6, D_Lunatic},
// High Voltage ~ Atmospheric Discharge
{S| 60, stage5_spellpractice_loop, STAGE_SPELL, "Spell 61", NULL, {1, 1, 1}, {1, 1, 1}, stage5_spells+0, D_Easy},
{S| 61, stage5_spellpractice_loop, STAGE_SPELL, "Spell 62", NULL, {1, 1, 1}, {1, 1, 1}, stage5_spells+0, D_Normal},
{S| 62, stage5_spellpractice_loop, STAGE_SPELL, "Spell 63", NULL, {1, 1, 1}, {1, 1, 1}, stage5_spells+0, D_Hard},
{S| 63, stage5_spellpractice_loop, STAGE_SPELL, "Spell 64", NULL, {1, 1, 1}, {1, 1, 1}, stage5_spells+0, D_Lunatic},
// Charge Sign ~ Artificial Lightning
{S| 64, stage5_spellpractice_loop, STAGE_SPELL, "Spell 65", NULL, {1, 1, 1}, {1, 1, 1}, stage5_spells+1, D_Easy},
{S| 65, stage5_spellpractice_loop, STAGE_SPELL, "Spell 66", NULL, {1, 1, 1}, {1, 1, 1}, stage5_spells+1, D_Normal},
{S| 66, stage5_spellpractice_loop, STAGE_SPELL, "Spell 67", NULL, {1, 1, 1}, {1, 1, 1}, stage5_spells+1, D_Hard},
{S| 67, stage5_spellpractice_loop, STAGE_SPELL, "Spell 68", NULL, {1, 1, 1}, {1, 1, 1}, stage5_spells+1, D_Lunatic},
// Spark Sign ~ Natural Cathode
{S| 68, stage5_spellpractice_loop, STAGE_SPELL, "Spell 69", NULL, {1, 1, 1}, {1, 1, 1}, stage5_spells+2, D_Easy},
{S| 69, stage5_spellpractice_loop, STAGE_SPELL, "Spell 70", NULL, {1, 1, 1}, {1, 1, 1}, stage5_spells+2, D_Normal},
{S| 70, stage5_spellpractice_loop, STAGE_SPELL, "Spell 71", NULL, {1, 1, 1}, {1, 1, 1}, stage5_spells+2, D_Hard},
{S| 71, stage5_spellpractice_loop, STAGE_SPELL, "Spell 72", NULL, {1, 1, 1}, {1, 1, 1}, stage5_spells+2, D_Lunatic},
// Current Sign ~ Induction
{S| 72, stage5_spellpractice_loop, STAGE_SPELL, "Spell 73", NULL, {1, 1, 1}, {1, 1, 1}, stage5_spells+3, D_Easy},
{S| 73, stage5_spellpractice_loop, STAGE_SPELL, "Spell 74", NULL, {1, 1, 1}, {1, 1, 1}, stage5_spells+3, D_Normal},
{S| 74, stage5_spellpractice_loop, STAGE_SPELL, "Spell 75", NULL, {1, 1, 1}, {1, 1, 1}, stage5_spells+3, D_Hard},
{S| 75, stage5_spellpractice_loop, STAGE_SPELL, "Spell 76", NULL, {1, 1, 1}, {1, 1, 1}, stage5_spells+3, D_Lunatic},
// Newton Sign ~ 2.5 Laws of Movement
{S| 76, stage6_spellpractice_loop, STAGE_SPELL, "Spell 77", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+0, D_Easy},
{S| 77, stage6_spellpractice_loop, STAGE_SPELL, "Spell 78", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+0, D_Normal},
{S| 78, stage6_spellpractice_loop, STAGE_SPELL, "Spell 79", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+0, D_Hard},
{S| 79, stage6_spellpractice_loop, STAGE_SPELL, "Spell 80", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+0, D_Lunatic},
// Maxwell Sign ~ Wave Theory
{S| 80, stage6_spellpractice_loop, STAGE_SPELL, "Spell 81", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+1, D_Easy},
{S| 81, stage6_spellpractice_loop, STAGE_SPELL, "Spell 82", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+1, D_Normal},
{S| 82, stage6_spellpractice_loop, STAGE_SPELL, "Spell 83", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+1, D_Hard},
{S| 83, stage6_spellpractice_loop, STAGE_SPELL, "Spell 84", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+1, D_Lunatic},
// Eigenstate ~ Many-World Interpretation
{S| 84, stage6_spellpractice_loop, STAGE_SPELL, "Spell 85", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+2, D_Easy},
{S| 85, stage6_spellpractice_loop, STAGE_SPELL, "Spell 86", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+2, D_Normal},
{S| 86, stage6_spellpractice_loop, STAGE_SPELL, "Spell 87", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+2, D_Hard},
{S| 87, stage6_spellpractice_loop, STAGE_SPELL, "Spell 88", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+2, D_Lunatic},
// Ricci Sign ~ Space Time Curvature
{S| 88, stage6_spellpractice_loop, STAGE_SPELL, "Spell 89", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+3, D_Easy},
{S| 89, stage6_spellpractice_loop, STAGE_SPELL, "Spell 90", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+3, D_Normal},
{S| 90, stage6_spellpractice_loop, STAGE_SPELL, "Spell 91", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+3, D_Hard},
{S| 91, stage6_spellpractice_loop, STAGE_SPELL, "Spell 92", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+3, D_Lunatic},
// LHC ~ Higgs Boson Uncovered
{S| 92, stage6_spellpractice_loop, STAGE_SPELL, "Spell 93", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+4, D_Easy},
{S| 93, stage6_spellpractice_loop, STAGE_SPELL, "Spell 94", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+4, D_Normal},
{S| 94, stage6_spellpractice_loop, STAGE_SPELL, "Spell 95", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+4, D_Hard},
{S| 95, stage6_spellpractice_loop, STAGE_SPELL, "Spell 96", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+4, D_Lunatic},
// Tower of Truth ~ Theory of Everything
{S| 96, stage6_spellpractice_loop, STAGE_SPELL, "Spell 97", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+5, D_Easy},
{S| 97, stage6_spellpractice_loop, STAGE_SPELL, "Spell 98", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+5, D_Normal},
{S| 98, stage6_spellpractice_loop, STAGE_SPELL, "Spell 99", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+5, D_Hard},
{S| 99, stage6_spellpractice_loop, STAGE_SPELL, "Spell 100", NULL, {1, 1, 1}, {1, 1, 1}, stage6_spells+5, D_Lunatic},
#undef S
{0}
};
void stage_init_array(void) {
for(int i = 0; stages[i].loop; ++i) {
if(stages[i].type == STAGE_SPELL) {
char *s, *postfix = difficulty_name(stages[i].difficulty);
// note: never free()'d
stages[i].subtitle = s = malloc(strlen(postfix) + strlen(stages[i].spell->name) + 4);
strcpy(s, stages[i].spell->name);
strcat(s, " ~ ");
strcat(s, postfix);
}
#ifdef DEBUG
if(stages[i].type == STAGE_SPELL && !(stages[i].id & STAGE_SPELL_BIT)) {
errx(-1, "Spell stage has an ID without the spell bit set: %u", stages[i].id);
}
for(int j = 0; stages[j].loop; ++j) {
if(i != j && stages[i].id == stages[j].id) {
errx(-1, "Duplicate ID in stages array: %u", stages[i].id);
}
}
#endif
}
}
// NOTE: This returns the stage BY ID, not by the array index!
StageInfo* stage_get(int n) {
int i;
for(i = 0; stages[i].loop; ++i)
if(stages[i].id == n)
return &(stages[i]);
StageInfo* stage_get(uint16_t n) {
for(StageInfo *stg = stages; stg->loop; ++stg)
if(stg->id == n)
return stg;
return NULL;
}
void stage_start(void) {
global.timer = 0;
global.frames = 0;
global.stageuiframes = 0;
global.game_over = 0;
global.nostagebg = false;
global.shake_view = 0;
@ -191,14 +391,9 @@ void draw_hud(void) {
glTranslatef((SCREEN_W - 615) * 0.25, 20, 0);
glScalef(0.6, 0.6, 0);
float b = 0.5;
switch(global.diff) {
case D_Easy: glColor4f(b, 1, b, 0.7); break;
case D_Normal: glColor4f(b, b, 1, 0.7); break;
case D_Hard: glColor4f(1, b, b, 0.7); break;
case D_Lunatic: glColor4f(1, b, 1, 0.7); break;
}
Color clr;
difficulty_color(&clr, global.diff);
glColor4f(clr.r, clr.g, clr.b, 0.7f);
diff = difficulty_name(global.diff);
draw_text(AL_Center, 1, 1, diff, _fonts.mainmenu);
@ -434,6 +629,7 @@ void stage_logic(int time) {
page_dialog(&global.dialog);
global.frames++;
global.stageuiframes++;
if(!global.dialog && !global.boss)
global.timer++;
@ -447,7 +643,7 @@ void stage_logic(int time) {
set_transition(TransFadeBlack, FADE_TIME, FADE_TIME*2);
}
if(global.timer >= time)
if(time && global.timer >= time)
global.game_over = GAMEOVER_WIN;
// BGM handling
@ -476,13 +672,16 @@ void stage_end(void) {
}
}
void stage_loop(StageInfo* info, StageRule start, StageRule end, StageRule draw, StageRule event, ShaderRule *shaderrules, int endtime) {
void stage_loop(StageRule start, StageRule end, StageRule draw, StageRule event, ShaderRule *shaderrules, int endtime) {
if(global.game_over == GAMEOVER_WIN) {
global.game_over = 0;
} else if(global.game_over) {
return;
}
// I really want to separate all of the game state from the global struct sometime
StageInfo *info = global.stage;
uint32_t seed = (uint32_t)time(0);
tsrand_switch(&global.rand_game);
tsrand_seed_p(&global.rand_game, seed);
@ -532,11 +731,19 @@ void stage_loop(StageInfo* info, StageRule start, StageRule end, StageRule draw,
start();
if(info->type == STAGE_SPELL) {
endtime = 0;
}
while(global.game_over <= 0) {
if(!global.boss && !global.dialog)
event();
((global.replaymode == REPLAY_PLAY)? replay_input : stage_input)();
if(!endtime && info->type == STAGE_SPELL && !global.boss) {
endtime = global.timer + 60;
}
((global.replaymode == REPLAY_PLAY) ? replay_input : stage_input)();
replay_stage_check_desync(global.replay_stage, global.frames, (tsrand() ^ global.plr.points) & 0xFFFF, global.replaymode);
stage_logic(endtime);
@ -571,6 +778,7 @@ void stage_loop(StageInfo* info, StageRule start, StageRule end, StageRule draw,
end();
stage_end();
tsrand_switch(&global.rand_visual);
global.stage = NULL;
}
void draw_title(int t, StageInfo *info, Alignment al, int x, int y, const char *text, TTF_Font *font, Color *color) {
@ -605,7 +813,7 @@ void draw_title(int t, StageInfo *info, Alignment al, int x, int y, const char *
}
void draw_stage_title(StageInfo *info) {
int t = global.frames;
int t = global.stageuiframes;
draw_title(t, info, AL_Center, VIEWPORT_W/2, VIEWPORT_H/2-40, info->title, _fonts.mainmenu, &info->titleclr);
draw_title(t, info, AL_Center, VIEWPORT_W/2, VIEWPORT_H/2, info->subtitle, _fonts.standard, &info->titleclr);

View file

@ -18,13 +18,16 @@
*/
#include <stdbool.h>
#include <projectile.h>
#include "projectile.h"
#include "boss.h"
typedef enum {
D_Easy = 1,
D_Any = 0,
D_Easy,
D_Normal,
D_Hard,
D_Lunatic
D_Lunatic,
D_Extra // reserved for later
} Difficulty;
#define TIMER(ptr) int *__timep = ptr; int _i = 0, _ni = 0; _i = _ni = _i;
@ -40,35 +43,48 @@ typedef enum {
typedef void (*StageRule)(void);
typedef void (*ShaderRule)(int);
// highest bit of uint16_t, WAY higher than the amount of spells in this game can ever possibly be
#define STAGE_SPELL_BIT 0x8000
typedef enum StageType {
STAGE_STORY = 1,
STAGE_EXTRA,
STAGE_SPELL,
} StageType;
typedef struct StageInfo {
int id;
uint16_t id; // must match type of ReplayStage.stage in replay.h
StageRule loop;
bool hidden;
StageType type;
char *title;
char *subtitle;
Color titleclr;
Color bosstitleclr;
AttackInfo *spell;
Difficulty difficulty;
} StageInfo;
extern StageInfo stages[];
StageInfo* stage_get(int);
StageInfo* stage_get(uint16_t);
void stage_init_array(void);
void stage_loop(StageInfo *info, StageRule start, StageRule end, StageRule draw, StageRule event, ShaderRule *shaderrules, int endtime);
void stage_loop(StageRule start, StageRule end, StageRule draw, StageRule event, ShaderRule *shaderrules, int endtime);
void apply_bg_shaders(ShaderRule *shaderrules);
void draw_stage_title(StageInfo *info);
void draw_hud(void);
void stage1_loop(void);
void stage2_loop(void);
void stage3_loop(void);
void stage4_loop(void);
void stage5_loop(void);
void stage6_loop(void);
void stage_pause(void);
void stage_gameover(void);
#include "stages/stage1.h"
#include "stages/stage2.h"
#include "stages/stage3.h"
#include "stages/stage4.h"
#include "stages/stage5.h"
#include "stages/stage6.h"
#endif

View file

@ -13,6 +13,17 @@
static Stage3D bgcontext;
void cirno_perfect_freeze(Boss*, int);
void cirno_crystal_rain(Boss*, int);
void cirno_icicle_fall(Boss*, int);
void cirno_pfreeze_bg(Boss*, int);
AttackInfo stage1_spells[] = {
{AT_Spellcard, "Freeze Sign ~ Perfect Freeze", 32, 20000, cirno_perfect_freeze, cirno_pfreeze_bg, VIEWPORT_W/2.0+100.0I},
{AT_Spellcard, "Freeze Sign ~ Crystal Rain", 28, 28000, cirno_crystal_rain, cirno_pfreeze_bg, VIEWPORT_W/2.0+100.0I},
{AT_Spellcard, "Doom Sign ~ Icicle Fall", 35, 40000, cirno_icicle_fall, cirno_pfreeze_bg, VIEWPORT_W/2.0+100.0I},
};
Dialog *stage1_dialog(void) {
Dialog *d = create_dialog(global.plr.cha == Marisa ? "dialog/marisa" : "dialog/youmu", "dialog/cirno");
@ -210,7 +221,7 @@ Boss *create_cirno_mid(void) {
Boss* cirno = create_boss("Cirno", "cirno", VIEWPORT_W + 220 + 30.0I);
boss_add_attack(cirno, AT_Move, "Introduction", 2, 0, cirno_intro, NULL);
boss_add_attack(cirno, AT_Normal, "Icy Storm", 20, 20000, cirno_icy, NULL);
boss_add_attack(cirno, AT_Spellcard, "Freeze Sign ~ Perfect Freeze", 32, 20000, cirno_perfect_freeze, cirno_pfreeze_bg);
boss_add_attack_from_info(cirno, stage1_spells+0, false);
start_attack(cirno, cirno->attacks);
return cirno;
@ -350,9 +361,9 @@ Boss *create_cirno(void) {
Boss* cirno = create_boss("Cirno", "cirno", -230 + 100.0I);
boss_add_attack(cirno, AT_Move, "Introduction", 2, 0, cirno_intro_boss, NULL);
boss_add_attack(cirno, AT_Normal, "Iceplosion 0", 20, 20000, cirno_iceplosion0, NULL);
boss_add_attack(cirno, AT_Spellcard, "Freeze Sign ~ Crystal Rain", 28, 28000, cirno_crystal_rain, cirno_pfreeze_bg);
boss_add_attack_from_info(cirno, stage1_spells+1, false);
boss_add_attack(cirno, AT_Normal, "Iceplosion 1", 20, 20000, cirno_iceplosion1, NULL);
boss_add_attack(cirno, AT_Spellcard, "Doom Sign ~ Icicle Fall", 35, 40000, cirno_icicle_fall, cirno_pfreeze_bg);
boss_add_attack_from_info(cirno, stage1_spells+2, false);
start_attack(cirno, cirno->attacks);
return cirno;
@ -676,5 +687,21 @@ void stage1_end(void) {
void stage1_loop(void) {
ShaderRule list[] = { stage1_fog, NULL };
stage_loop(stage_get(1), stage1_start, stage1_end, stage1_draw, stage1_events, list, 5200);
stage_loop(stage1_start, stage1_end, stage1_draw, stage1_events, list, 5200);
}
void stage1_spellpractice_events(void) {
TIMER(&global.timer);
AT(0) {
Boss* cirno = create_boss("Cirno", "cirno", BOSS_DEFAULT_SPAWN_POS);
boss_add_attack_from_info(cirno, global.stage->spell, true);
start_attack(cirno, cirno->attacks);
global.boss = cirno;
}
}
void stage1_spellpractice_loop(void) {
ShaderRule list[] = { stage1_fog, NULL };
stage_loop(stage1_start, stage1_end, stage1_draw, stage1_spellpractice_events, list, 0);
}

View file

@ -8,6 +8,11 @@
#ifndef STAGE1_H
#define STAGE1_H
#include "boss.h"
void stage1_loop(void);
void stage1_spellpractice_loop(void);
extern AttackInfo stage1_spells[];
#endif

View file

@ -161,5 +161,23 @@ void stage2_draw(void) {
void stage2_loop(void) {
ShaderRule shaderrules[] = { stage2_fog, stage2_bloom, NULL };
stage_loop(stage_get(2), stage2_start, stage2_end, stage2_draw, stage2_events, shaderrules, 5240);
stage_loop(stage2_start, stage2_end, stage2_draw, stage2_events, shaderrules, 5240);
}
void stage2_spellpractice_events(void) {
TIMER(&global.timer);
AT(0) {
skip_background_anim(&bgcontext, stage2_draw, 180, &global.frames, NULL);
Boss* hina = create_boss("Kagiyama Hina", "hina", BOSS_DEFAULT_SPAWN_POS);
boss_add_attack_from_info(hina, global.stage->spell, true);
start_attack(hina, hina->attacks);
global.boss = hina;
}
}
void stage2_spellpractice_loop(void) {
ShaderRule shaderrules[] = { stage2_fog, stage2_bloom, NULL };
stage_loop(stage2_start, stage2_end, stage2_draw, stage2_spellpractice_events, shaderrules, 0);
}

View file

@ -8,6 +8,11 @@
#ifndef STAGE2_H
#define STAGE2_H
#include "boss.h"
void stage2_loop(void);
void stage2_spellpractice_loop(void);
extern AttackInfo stage2_spells[];
#endif

View file

@ -10,6 +10,17 @@
#include "stage.h"
#include "enemy.h"
void hina_spell_bg(Boss*, int);
void hina_amulet(Boss*, int);
void hina_bad_pick(Boss*, int);
void hina_wheel(Boss*, int);
AttackInfo stage2_spells[] = {
{AT_Spellcard, "Shard ~ Amulet of Harm", 26, 25000, hina_amulet, hina_spell_bg, BOSS_DEFAULT_GO_POS},
{AT_Spellcard, "Lottery Sign ~ Bad Pick", 30, 36000, hina_bad_pick, hina_spell_bg, BOSS_DEFAULT_GO_POS},
{AT_Spellcard, "Lottery Sign ~ Wheel of Fortune", 20, 36000, hina_wheel, hina_spell_bg, BOSS_DEFAULT_GO_POS},
};
Dialog *stage2_dialog(void) {
Dialog *d = create_dialog(global.plr.cha == Marisa ? "dialog/marisa" : "dialog/youmu", "dialog/hina");
@ -396,10 +407,10 @@ Boss *create_hina(void) {
Boss* hina = create_boss("Kagiyama Hina", "hina", VIEWPORT_W + 150 + 100.0I);
boss_add_attack(hina, AT_Move, "Introduction", 2, 0, hina_intro, NULL);
boss_add_attack(hina, AT_Normal, "Cards1", 20, 15000, hina_cards1, NULL);
boss_add_attack(hina, AT_Spellcard, "Shard ~ Amulet of Harm", 26, 25000, hina_amulet, hina_spell_bg);
boss_add_attack_from_info(hina, stage2_spells+0, false);
boss_add_attack(hina, AT_Normal, "Cards2", 17, 15000, hina_cards2, NULL);
boss_add_attack(hina, AT_Spellcard, "Lottery Sign ~ Bad Pick", 30, 36000, hina_bad_pick, hina_spell_bg);
boss_add_attack(hina, AT_Spellcard, "Lottery Sign ~ Wheel of Fortune", 20, 36000, hina_wheel, hina_spell_bg);
boss_add_attack_from_info(hina, stage2_spells+1, false);
boss_add_attack_from_info(hina, stage2_spells+2, false);
start_attack(hina, hina->attacks);
return hina;

View file

@ -68,7 +68,7 @@ void stage3_bg_tunnel_draw(Vector pos) {
*/
draw_quad();
glPopMatrix();
}
}
glPopMatrix();
glDisable(GL_TEXTURE_2D);
@ -281,5 +281,29 @@ void stage3_draw(void) {
void stage3_loop(void) {
ShaderRule shaderrules[] = { stage3_fog, stage3_tunnel, NULL };
stage_loop(stage_get(3), stage3_start, stage3_end, stage3_draw, stage3_events, shaderrules, 5700);
stage_loop(stage3_start, stage3_end, stage3_draw, stage3_events, shaderrules, 5700);
}
void stage3_mid_spellbg(Boss*, int t);
void stage3_spellpractice_events(void) {
TIMER(&global.timer);
AT(0) {
if(global.stage->spell->draw_rule == stage3_mid_spellbg) {
skip_background_anim(&bgcontext, stage3_draw, 2800, &global.timer, NULL);
global.boss = create_boss("Scuttle", "scuttle", BOSS_DEFAULT_SPAWN_POS);
} else {
skip_background_anim(&bgcontext, stage3_draw, 5300, &global.timer, NULL);
global.boss = create_boss("Wriggle EX", "wriggleex", BOSS_DEFAULT_SPAWN_POS);
}
boss_add_attack_from_info(global.boss, global.stage->spell, true);
start_attack(global.boss, global.boss->attacks);
}
}
void stage3_spellpractice_loop(void) {
ShaderRule shaderrules[] = { stage3_fog, stage3_tunnel, NULL };
stage_loop(stage3_start, stage3_end, stage3_draw, stage3_spellpractice_events, shaderrules, 0);
}

View file

@ -8,6 +8,11 @@
#ifndef STAGE3_H
#define STAGE3_H
#include "boss.h"
void stage3_loop(void);
void stage3_spellpractice_loop(void);
extern AttackInfo stage3_spells[];
#endif

View file

@ -11,6 +11,22 @@
#include "stage.h"
#include "enemy.h"
void stage3_mid_spellbg(Boss*, int t);
void stage3_boss_spellbg(Boss*, int t);
void stage3_mid_a1(Boss*, int t);
void stage3_mid_a2(Boss*, int t);
void stage3_boss_a1(Boss*, int t);
void stage3_boss_a2(Boss*, int t);
void stage3_boss_a3(Boss*, int t);
AttackInfo stage3_spells[] = {
{AT_Spellcard, "Venom Sign ~ Deadly Dance", 25, 25000, stage3_mid_a1, stage3_mid_spellbg, BOSS_DEFAULT_GO_POS},
{AT_Spellcard, "Venom Sign ~ Acid Rain", 20, 23000, stage3_mid_a2, stage3_mid_spellbg, BOSS_DEFAULT_GO_POS},
{AT_Spellcard, "Firefly Sign ~ Moonlight Rocket", 30, 20000, stage3_boss_a1, stage3_boss_spellbg, BOSS_DEFAULT_GO_POS},
{AT_Spellcard, "Light Source ~ Wriggle Night Ignite", 25, 40000, stage3_boss_a2, stage3_boss_spellbg, BOSS_DEFAULT_GO_POS},
{AT_Spellcard, "Bug Sign ~ Phosphaenus Hemipterus", 35, 30000, stage3_boss_a3, stage3_boss_spellbg, BOSS_DEFAULT_GO_POS},
};
Dialog *stage3_dialog(void) {
Dialog *d = create_dialog(global.plr.cha == Marisa ? "dialog/marisa" : "dialog/youmu", "dialog/wriggle");
@ -429,9 +445,9 @@ Boss* stage3_create_midboss(void) {
Boss *scuttle = create_boss("Scuttle", "scuttle", VIEWPORT_W/2 - 200.0I);
boss_add_attack(scuttle, AT_Move, "Introduction", 2, 0, stage3_mid_intro, NULL);
boss_add_attack(scuttle, AT_Normal, "Lethal Bite", 30, 25000, stage3_mid_a0, NULL);
boss_add_attack(scuttle, AT_Spellcard, "Venom Sign ~ Deadly Dance", 25, 25000, stage3_mid_a1, stage3_mid_spellbg);
boss_add_attack_from_info(scuttle, stage3_spells+0, false);
if(global.diff > D_Normal)
boss_add_attack(scuttle, AT_Spellcard, "Venom Sign ~ Acid Rain", 20, 23000, stage3_mid_a2, stage3_mid_spellbg);
boss_add_attack_from_info(scuttle, stage3_spells+1, false);
boss_add_attack(scuttle, AT_Move, "Runaway", 2, 1, stage3_mid_outro, NULL);
scuttle->zoomcolor = rgb(0.4, 0.5, 0.4);
@ -760,11 +776,11 @@ Boss* stage3_create_boss(void) {
boss_add_attack(wriggle, AT_Move, "Introduction", 2, 0, stage3_boss_intro, NULL);
boss_add_attack(wriggle, AT_Normal, "", 20, 15000, stage3_boss_prea1, NULL);
boss_add_attack(wriggle, AT_Spellcard, "Firefly Sign ~ Moonlight Rocket", 30, 20000, stage3_boss_a1, stage3_boss_spellbg);
boss_add_attack_from_info(wriggle, stage3_spells+2, false);
boss_add_attack(wriggle, AT_Normal, "", 20, 15000, stage3_boss_prea2, NULL);
boss_add_attack(wriggle, AT_Spellcard, "Light Source ~ Wriggle Night Ignite", 25, 40000, stage3_boss_a2, stage3_boss_spellbg);
boss_add_attack_from_info(wriggle, stage3_spells+3, false);
boss_add_attack(wriggle, AT_Normal, "", 20, 15000, stage3_boss_prea3, NULL);
boss_add_attack(wriggle, AT_Spellcard, "Bug Sign ~ Phosphaenus Hemipterus", 35, 30000, stage3_boss_a3, stage3_boss_spellbg);
boss_add_attack_from_info(wriggle, stage3_spells+4, false);
start_attack(wriggle, wriggle->attacks);
return wriggle;

View file

@ -232,5 +232,21 @@ void stage4_draw(void) {
void stage4_loop(void) {
ShaderRule shaderrules[] = { stage4_fog, NULL };
stage_loop(stage_get(4), stage4_start, stage4_end, stage4_draw, stage4_events, shaderrules, 5550);
stage_loop(stage4_start, stage4_end, stage4_draw, stage4_events, shaderrules, 5550);
}
void stage4_spellpractice_events(void) {
TIMER(&global.timer);
AT(0) {
skip_background_anim(&bgcontext, stage4_draw, 3200, &global.frames, NULL);
global.boss = create_boss("Kurumi", "kurumi", BOSS_DEFAULT_SPAWN_POS);
boss_add_attack_from_info(global.boss, global.stage->spell, true);
start_attack(global.boss, global.boss->attacks);
}
}
void stage4_spellpractice_loop(void) {
ShaderRule shaderrules[] = { stage4_fog, NULL };
stage_loop(stage4_start, stage4_end, stage4_draw, stage4_spellpractice_events, shaderrules, 0);
}

View file

@ -8,6 +8,11 @@
#ifndef STAGE4_H
#define STAGE4_H
#include "boss.h"
void stage4_loop(void);
void stage4_spellpractice_loop(void);
extern AttackInfo stage4_spells[];
#endif

View file

@ -11,6 +11,23 @@
#include "enemy.h"
#include "laser.h"
void kurumi_spell_bg(Boss*, int);
void kurumi_slaveburst(Boss*, int);
void kurumi_redspike(Boss*, int);
void kurumi_aniwall(Boss*, int);
void kurumi_blowwall(Boss*, int);
void kurumi_danmaku(Boss*, int);
AttackInfo stage4_spells[] = {
{AT_Spellcard, "Bloodless ~ Gate of Walachia", 25, 20000, kurumi_slaveburst, kurumi_spell_bg, BOSS_DEFAULT_GO_POS},
{AT_Spellcard, "Bloodless ~ Dry Fountain", 30, 30000, kurumi_redspike, kurumi_spell_bg, BOSS_DEFAULT_GO_POS},
{AT_Spellcard, "Bloodless ~ Red Spike", 30, 30000, kurumi_redspike, kurumi_spell_bg, BOSS_DEFAULT_GO_POS},
{AT_Spellcard, "Limit ~ Animate Wall", 30, 40000, kurumi_aniwall, kurumi_spell_bg, BOSS_DEFAULT_GO_POS},
{AT_Spellcard, "Summoning ~ Demon Wall", 30, 40000, kurumi_aniwall, kurumi_spell_bg, BOSS_DEFAULT_GO_POS},
{AT_Spellcard, "Power Sign ~ Blow the Walls", 30, 32000, kurumi_blowwall, kurumi_spell_bg, BOSS_DEFAULT_GO_POS},
{AT_Spellcard, "Fear Sign ~ Bloody Danmaku", 30, 32000, kurumi_danmaku, kurumi_spell_bg, BOSS_DEFAULT_GO_POS},
};
Dialog *stage4_dialog(void) {
Dialog *d = create_dialog(global.plr.cha == Marisa ? "dialog/marisa" : "dialog/youmu", "dialog/kurumi");
@ -229,7 +246,7 @@ void KurumiSlave(Enemy *e, int t) {
}
void kurumi_intro(Boss *b, int t) {
GO_TO(b, VIEWPORT_W/2.0+200.0I, 0.01);
GO_TO(b, BOSS_DEFAULT_GO_POS, 0.01);
}
int kurumi_burstslave(Enemy *e, int t) {
@ -363,8 +380,12 @@ void kurumi_outro(Boss *b, int time) {
Boss *create_kurumi_mid(void) {
Boss* b = create_boss("Kurumi", "kurumi", VIEWPORT_W/2-400.0I);
boss_add_attack(b, AT_Move, "Introduction", 4, 0, kurumi_intro, NULL);
boss_add_attack(b, AT_Spellcard, "Bloodless ~ Gate of Walachia", 25, 20000, kurumi_slaveburst, kurumi_spell_bg);
boss_add_attack(b, AT_Spellcard, global.diff < D_Hard ? "Bloodless ~ Dry Fountain" : "Bloodless ~ Red Spike", 30, 30000, kurumi_redspike, kurumi_spell_bg);
boss_add_attack_from_info(b, stage4_spells+0, false);
if(global.diff < D_Hard) {
boss_add_attack_from_info(b, stage4_spells+1, false);
} else {
boss_add_attack_from_info(b, stage4_spells+2, false);
}
boss_add_attack(b, AT_Move, "Outro", 2, 1, kurumi_outro, NULL);
start_attack(b, b->attacks);
return b;
@ -413,7 +434,7 @@ int stage4_supercard(Enemy *e, int t) {
void kurumi_boss_intro(Boss *b, int t) {
TIMER(&t);
GO_TO(b, VIEWPORT_W/2.0+200.0I, 0.01);
GO_TO(b, BOSS_DEFAULT_GO_POS, 0.01);
AT(120)
global.dialog = stage4_dialog();
@ -704,11 +725,16 @@ Boss *create_kurumi(void) {
Boss* b = create_boss("Kurumi", "kurumi", -400.0I);
boss_add_attack(b, AT_Move, "Introduction", 4, 0, kurumi_boss_intro, NULL);
boss_add_attack(b, AT_Normal, "Sin Breaker", 20, 20000, kurumi_sbreaker, NULL);
boss_add_attack(b, AT_Spellcard, global.diff < D_Hard ? "Limit ~ Animate Wall" : "Summoning ~ Demon Wall", 30, 40000, kurumi_aniwall, kurumi_spell_bg);
if(global.diff < D_Hard) {
boss_add_attack_from_info(b, stage4_spells+3, false);
} else {
boss_add_attack_from_info(b, stage4_spells+4, false);
}
boss_add_attack(b, AT_Normal, "Cold Breaker", 20, 20000, kurumi_breaker, NULL);
boss_add_attack(b, AT_Spellcard, "Power Sign ~ Blow the Walls", 30, 32000, kurumi_blowwall, kurumi_spell_bg);
if(global.diff > D_Normal)
boss_add_attack(b, AT_Spellcard, "Fear Sign ~ Bloody Danmaku", 30, 32000, kurumi_danmaku, kurumi_spell_bg);
boss_add_attack_from_info(b, stage4_spells+5, false);
if(global.diff > D_Normal) {
boss_add_attack_from_info(b, stage4_spells+6, false);
}
start_attack(b, b->attacks);
return b;

View file

@ -130,5 +130,20 @@ void stage5_end(void) {
}
void stage5_loop(void) {
stage_loop(stage_get(5), stage5_start, stage5_end, stage5_draw, stage5_events, NULL, 5700);
stage_loop(stage5_start, stage5_end, stage5_draw, stage5_events, NULL, 5700);
}
void stage5_spellpractice_events(void) {
TIMER(&global.timer);
AT(0) {
skip_background_anim(&bgcontext, stage5_draw, 5300, &global.timer, NULL);
global.boss = create_boss("Nagae Iku", "iku", BOSS_DEFAULT_SPAWN_POS);
boss_add_attack_from_info(global.boss, global.stage->spell, true);
start_attack(global.boss, global.boss->attacks);
}
}
void stage5_spellpractice_loop(void) {
stage_loop(stage5_start, stage5_end, stage5_draw, stage5_spellpractice_events, NULL, 5700);
}

View file

@ -8,6 +8,11 @@
#ifndef STAGE5_H
#define STAGE5_H
#include "boss.h"
void stage5_loop(void);
void stage5_spellpractice_loop(void);
extern AttackInfo stage5_spells[];
#endif

View file

@ -9,6 +9,19 @@
#include "stage5.h"
#include <global.h>
void iku_spell_bg(Boss*, int);
void iku_atmospheric(Boss*, int);
void iku_lightning(Boss*, int);
void iku_cathode(Boss*, int);
void iku_induction(Boss*, int);
AttackInfo stage5_spells[] = {
{AT_Spellcard, "High Voltage ~ Atmospheric Discharge", 30, 30000, iku_atmospheric, iku_spell_bg, BOSS_DEFAULT_GO_POS},
{AT_Spellcard, "Charge Sign ~ Artificial Lightning", 30, 35000, iku_lightning, iku_spell_bg, BOSS_DEFAULT_GO_POS},
{AT_Spellcard, "Spark Sign ~ Natural Cathode", 30, 35000, iku_cathode, iku_spell_bg, BOSS_DEFAULT_GO_POS},
{AT_Spellcard, "Current Sign ~ Induction", 30, 35000, iku_induction, iku_spell_bg, BOSS_DEFAULT_GO_POS},
};
Dialog *stage5_post_mid_dialog(void) {
Dialog *d = create_dialog(global.plr.cha == Marisa ? "dialog/marisa" : "dialog/youmu", NULL);
@ -377,7 +390,7 @@ void iku_lightning(Boss *b, int time) {
}
if(time < 0) {
GO_TO(b, VIEWPORT_W/2.0+200.0I, 0.03);
GO_TO(b, BOSS_DEFAULT_GO_POS, 0.03);
return;
}
@ -485,12 +498,12 @@ Boss *create_iku(void) {
boss_add_attack(b, AT_Move, "Introduction", 3, 0, iku_intro, NULL);
boss_add_attack(b, AT_Normal, "Bolts1", 20, 20000, iku_bolts, NULL);
boss_add_attack(b, AT_Spellcard, "High Voltage ~ Atmospheric Discharge", 30, 30000, iku_atmospheric, iku_spell_bg);
boss_add_attack_from_info(b, stage5_spells+0, false);
boss_add_attack(b, AT_Normal, "Bolts2", 25, 20000, iku_bolts2, NULL);
boss_add_attack(b, AT_Spellcard, "Charge Sign ~ Artificial Lightning", 30, 35000, iku_lightning, iku_spell_bg);
boss_add_attack_from_info(b, stage5_spells+1, false);
boss_add_attack(b, AT_Normal, "Bolts3", 20, 20000, iku_bolts3, NULL);
boss_add_attack(b, AT_Spellcard, "Spark Sign ~ Natural Cathode", 30, 35000, iku_cathode, iku_spell_bg);
boss_add_attack(b, AT_Spellcard, "Current Sign ~ Induction", 30, 35000, iku_induction, iku_spell_bg);
boss_add_attack_from_info(b, stage5_spells+2, false);
boss_add_attack_from_info(b, stage5_spells+3, false);
return b;
}

View file

@ -176,5 +176,61 @@ void stage6_end(void) {
void stage6_loop(void) {
// ShaderRule shaderrules[] = { stage6_bloom, NULL };
stage_loop(stage_get(6), stage6_start, stage6_end, stage6_draw, stage6_events, NULL, 3900);
stage_loop(stage6_start, stage6_end, stage6_draw, stage6_events, NULL, 3900);
}
void elly_intro(Boss*, int);
void elly_unbound(Boss*, int);
void stage6_spellpractice_events(void) {
TIMER(&global.timer);
AT(0) {
skip_background_anim(&bgcontext, stage6_draw, 3800, &global.timer, &global.frames);
global.boss = create_boss("Elly", "elly", BOSS_DEFAULT_SPAWN_POS);
// Here be dragons
ptrdiff_t spellnum = global.stage->spell - stage6_spells;
char go = true;
switch(spellnum) {
case 0: // Newton Sign ~ 2.5 Laws of Movement
// Scythe required - this creates it
boss_add_attack(global.boss, AT_Move, "Catch the Scythe", 2, 0, elly_intro, NULL);
go = false;
break;
case 1: // Maxwell Sign ~ Wave Theory
// Works fine on its own
break;
case 2: // Eigenstate ~ Many-World Interpretation
case 3: // Ricci Sign ~ Space Time Curvature
case 4: // LHC ~ Higgs Boson Uncovered
// Baryon required - this creates it
boss_add_attack(global.boss, AT_Move, "Unbound", 3, 0, elly_unbound, NULL);
go = false;
break;
case 5: // Tower of Truth ~ Theory of Everything
// Works fine on its own
// Just needs a little extra to make us fall from the tower
start_fall_over();
skip_background_anim(&bgcontext, stage6_draw, 300, &global.timer, &global.frames);
break;
}
boss_add_attack_from_info(global.boss, global.stage->spell, go);
start_attack(global.boss, global.boss->attacks);
}
if(!global.boss) {
killall(global.enemies);
}
}
void stage6_spellpractice_loop(void) {
// ShaderRule shaderrules[] = { stage6_bloom, NULL };
stage_loop(stage6_start, stage6_end, stage6_draw, stage6_spellpractice_events, NULL, 3900);
}

View file

@ -8,7 +8,12 @@
#ifndef STAGE6_H
#define STAGE6_H
#include "boss.h"
void stage6_loop(void);
void stage6_spellpractice_loop(void);
void start_fall_over(void);
extern AttackInfo stage6_spells[];
#endif

View file

@ -9,6 +9,24 @@
#include "stage6.h"
#include <global.h>
void elly_spellbg_classic(Boss*, int);
void elly_spellbg_modern(Boss*, int);
void elly_newton(Boss*, int);
void elly_maxwell(Boss*, int);
void elly_eigenstate(Boss*, int);
void elly_ricci(Boss*, int);
void elly_lhc(Boss*, int);
void elly_theory(Boss*, int);
AttackInfo stage6_spells[] = {
{AT_Spellcard, "Newton Sign ~ 2.5 Laws of Movement", 60, 40000, elly_newton, elly_spellbg_classic, BOSS_DEFAULT_GO_POS},
{AT_Spellcard, "Maxwell Sign ~ Wave Theory", 25, 22000, elly_maxwell, elly_spellbg_classic, BOSS_DEFAULT_GO_POS},
{AT_Spellcard, "Eigenstate ~ Many-World Interpretation", 60, 30000, elly_eigenstate, elly_spellbg_modern, BOSS_DEFAULT_GO_POS},
{AT_Spellcard, "Ricci Sign ~ Space Time Curvature", 50, 40000, elly_ricci, elly_spellbg_modern, BOSS_DEFAULT_GO_POS},
{AT_Spellcard, "LHC ~ Higgs Boson Uncovered", 60, 50000, elly_lhc, elly_spellbg_modern, BOSS_DEFAULT_GO_POS},
{AT_SurvivalSpell, "Tower of Truth ~ Theory of Everything", 70, 40000, elly_theory, elly_spellbg_modern, BOSS_DEFAULT_GO_POS},
};
Dialog *stage6_dialog(void) {
Dialog *d = create_dialog(global.plr.cha == Marisa ? "dialog/marisa" : "dialog/youmu", "dialog/elly");
@ -181,7 +199,18 @@ int scythe_intro(Enemy *e, int t) {
void elly_intro(Boss *b, int t) {
TIMER(&t);
GO_TO(b, VIEWPORT_W/2+200.0I, 0.01);
if(global.stage->type == STAGE_SPELL) {
GO_TO(b, BOSS_DEFAULT_GO_POS, 0.1);
AT(0) {
create_enemy3c(VIEWPORT_W+200.0I, ENEMY_IMMUNE, Scythe, scythe_intro, 0, 1+0.2I, 1);
}
return;
}
GO_TO(b, BOSS_DEFAULT_GO_POS, 0.01);
AT(200) {
create_enemy3c(VIEWPORT_W+200+200.0I, ENEMY_IMMUNE, Scythe, scythe_intro, 0, 1+0.2I, 1);
@ -224,7 +253,7 @@ int scythe_reset(Enemy *e, int t) {
if(t == 1)
e->args[1] = fmod(creal(e->args[1]), 2*M_PI) + I*cimag(e->args[1]);
GO_TO(e, VIEWPORT_W/2.0+200.0I, 0.02);
GO_TO(e, BOSS_DEFAULT_GO_POS, 0.02);
e->args[2] = max(0.6, creal(e->args[2])-0.01*t);
e->args[1] += (0.19-creal(e->args[1]))*0.05;
e->args[1] = creal(e->args[1]) + I*0.9*cimag(e->args[1]);
@ -478,6 +507,11 @@ int scythe_explode(Enemy *e, int t) {
}
void elly_unbound(Boss *b, int t) {
if(global.stage->type == STAGE_SPELL) {
t += 100;
GO_TO(b, BOSS_DEFAULT_GO_POS, 0.1)
}
TIMER(&t);
AT(0) {
@ -916,17 +950,17 @@ Boss *create_elly(void) {
boss_add_attack(b, AT_Move, "Catch the Scythe", 6, 0, elly_intro, NULL);
boss_add_attack(b, AT_Normal, "Frequency", 30, 26000, elly_frequency, NULL);
boss_add_attack(b, AT_Spellcard, "Newton Sign ~ 2.5 Laws of Movement", 60, 40000, elly_newton, elly_spellbg_classic);
boss_add_attack_from_info(b, stage6_spells+0, false);
boss_add_attack(b, AT_Normal, "Frequency2", 40, 23000, elly_frequency2, NULL);
boss_add_attack(b, AT_Spellcard, "Maxwell Sign ~ Wave Theory", 25, 22000, elly_maxwell, elly_spellbg_classic);
boss_add_attack_from_info(b, stage6_spells+1, false);
boss_add_attack(b, AT_Move, "Unbound", 6, 10, elly_unbound, NULL);
boss_add_attack(b, AT_Spellcard, "Eigenstate ~ Many-World Interpretation", 60, 30000, elly_eigenstate, elly_spellbg_modern);
boss_add_attack_from_info(b, stage6_spells+2, false);
boss_add_attack(b, AT_Normal, "Baryon", 40, 23000, elly_baryonattack, NULL);
boss_add_attack(b, AT_Spellcard, "Ricci Sign ~ Space Time Curvature", 50, 40000, elly_ricci, elly_spellbg_modern);
boss_add_attack_from_info(b, stage6_spells+3, false);
boss_add_attack(b, AT_Normal, "Baryon", 25, 23000, elly_baryonattack2, NULL);
boss_add_attack(b, AT_Spellcard, "LHC ~ Higgs Boson Uncovered", 60, 50000, elly_lhc, elly_spellbg_modern);
boss_add_attack_from_info(b, stage6_spells+4, false);
boss_add_attack(b, AT_Move, "Explode", 6, 10, elly_baryon_explode, NULL);
boss_add_attack(b, AT_SurvivalSpell, "Tower of Truth ~ Theory of Everything", 70, 40000, elly_theory, elly_spellbg_modern);
boss_add_attack_from_info(b, stage6_spells+5, false);
start_attack(b, b->attacks);
return b;

View file

@ -43,6 +43,10 @@ void draw_stage3d(Stage3D *s, float maxrange) {
for(i = 0; i < 3;i++)
s->cx[i] += s->cv[i];
if(s->nodraw) {
return;
}
glPushMatrix();
if(s->crot[0])
@ -138,3 +142,29 @@ Vector **single3dpos(Vector q, float maxrange, Vector p) {
return list;
}
}
void skip_background_anim(Stage3D *s3d, void (*drawfunc)(void), int frames, int *timer, int *timer2) {
// two timers because stage 6 is so fucking special
// second is optional
if(s3d) {
s3d->nodraw = true;
}
int targetframes = *timer + frames;
while(++(*timer) < targetframes) {
if(timer2) {
++(*timer2);
}
drawfunc();
}
if(timer2) {
++(*timer2);
}
if(s3d) {
s3d->nodraw = false;
}
}

View file

@ -32,6 +32,9 @@ struct Stage3D {
Vector crot;
float projangle;
// hack to quickly skip through stage animations up to a specific frame
char nodraw;
};
void init_stage3d(Stage3D *s);
@ -47,4 +50,7 @@ void free_stage3d(Stage3D *s);
Vector **linear3dpos(Vector q, float maxrange, Vector p, Vector r);
Vector **single3dpos(Vector q, float maxrange, Vector p);
void skip_background_anim(Stage3D *s3d, void (*drawfunc)(void), int frames, int *timer, int *timer2);
#endif