diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 027407767..2bfc47f24 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -5592,7 +5592,7 @@ static INT16 Consistancy(void) // Coop desynching enemies is painful if (gamestate == GS_LEVEL) { - for (i = 0; i < PRNUMCLASS; i++) + for (i = 0; i < PRNUMSYNCED; i++) { if (i & 1) { diff --git a/src/d_main.cpp b/src/d_main.cpp index 7df05d12f..29bb57091 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -853,8 +853,6 @@ void D_SRB2Loop(void) realtics = entertic - oldentertics; oldentertics = entertic; - refreshdirmenu = 0; // not sure where to put this, here as good as any? - if (demo.playback && gamestate == GS_LEVEL) { // Nicer place to put this. @@ -870,8 +868,16 @@ void D_SRB2Loop(void) interp = R_UsingFrameInterpolation() && !dedicated; doDisplay = false; - if (realtics > 0 || singletics) + renderisnewtic = (realtics > 0 || singletics); + + bool timeisprogressing = (!(paused || P_AutoPause()) && !hu_stopped); + + if (renderisnewtic) { + refreshdirmenu = 0; + + P_ResetInterpHudRandSeed(timeisprogressing); + // don't skip more than 10 frames at a time // (fadein / fadeout cause massive frame skip!) if (realtics > 8) @@ -906,19 +912,13 @@ void D_SRB2Loop(void) doDisplay = true; } - - renderisnewtic = true; - } - else - { - renderisnewtic = false; } if (interp) { renderdeltatics = FLOAT_TO_FIXED(deltatics); - if (!(paused || P_AutoPause()) && !hu_stopped) + if (timeisprogressing) { rendertimefrac = g_time.timefrac; } @@ -938,6 +938,9 @@ void D_SRB2Loop(void) if ((interp || doDisplay) && !frameskip) { + if (!renderisnewtic) + P_ResetInterpHudRandSeed(false); + ranwipe = D_Display(); } diff --git a/src/deh_soc.c b/src/deh_soc.c index 394e2d12d..c436cb87c 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -785,9 +785,11 @@ void readgametype(MYFILE *f, char *gtname) INT32 newgttimelimit = 0; UINT8 newgtinttype = 0; char gtconst[MAXLINELEN]; + char gppic[9]; + char gppicmini[9]; // Empty strings. - gtconst[0] = '\0'; + gtconst[0] = gppic[0] = gppicmini[0] = '\0'; do { @@ -827,7 +829,15 @@ void readgametype(MYFILE *f, char *gtname) else if (fastcmp(word, "IDENTIFIER")) { // GT_ - strncpy(gtconst, word2, MAXLINELEN); + strlcpy(gtconst, word2, MAXLINELEN); + } + else if (fastcmp(word, "GPPIC")) + { + strlcpy(gppic, word2, 9); + } + else if (fastcmp(word, "GPPICMINI")) + { + strlcpy(gppicmini, word2, 9); } // Point and time limits else if (fastcmp(word, "DEFAULTPOINTLIMIT")) @@ -926,6 +936,8 @@ void readgametype(MYFILE *f, char *gtname) newgametype->name = Z_StrDup((const char *)gtname); newgametype->rules = newgtrules; newgametype->constant = G_PrepareGametypeConstant((const char *)gtconst); + strlcpy(newgametype->gppic, gppic, 9); + strlcpy(newgametype->gppicmini, gppicmini, 9); newgametype->tol = newgttol; newgametype->intermission = newgtinttype; newgametype->pointlimit = newgtpointlimit; diff --git a/src/deh_tables.c b/src/deh_tables.c index 458c3aed0..3023b70eb 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -4730,6 +4730,46 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_BALLSWITCH_BALL_ACTIVE", "S_BALLSWITCH_PAD", "S_BALLSWITCH_PAD_ACTIVE", + + "S_SPIKEDTARGET", + "S_SPIKEDLENS", + + "S_BLENDEYE_MAIN", + "S_BLENDEYE_MAIN_LAUNCHED", + "S_BLENDEYE_EYE", + "S_BLENDEYE_EYE_FLASH", + "S_BLENDEYE_GLASS", + "S_BLENDEYE_GLASS_STRESS", + + "S_BLENDEYE_SHIELD", + "S_BLENDEYE_SHIELD_L", + "S_BLENDEYE_SHIELD_R", + "S_BLENDEYE_SHIELD_BUSTED", + "S_BLENDEYE_SHIELD_BUSTED_L", + "S_BLENDEYE_SHIELD_BUSTED_R", + + "S_BLENDEYE_EGGBEATER_EXTEND_1", + "S_BLENDEYE_EGGBEATER_EXTEND_2", + "S_BLENDEYE_EGGBEATER", + "S_BLENDEYE_EGGBEATER_SPIN", + + "S_BLENDEYE_FLAME", + + "S_BLENDEYE_GENERATOR", + "S_BLENDEYE_GENERATOR_BUSTED_L", + "S_BLENDEYE_GENERATOR_BUSTED_R", + + "S_BLENDEYE_PUYO_SPAWN_1", + "S_BLENDEYE_PUYO_SPAWN_2", + "S_BLENDEYE_PUYO_SPAWN_3", + "S_BLENDEYE_PUYO", + "S_BLENDEYE_PUYO_LAND_1", + "S_BLENDEYE_PUYO_LAND_2", + "S_BLENDEYE_PUYO_LAND_3", + "S_BLENDEYE_PUYO_LAND_4", + "S_BLENDEYE_PUYO_SHOCK", + "S_BLENDEYE_PUYO_DIE", + "S_BLENDEYE_PUYO_DUST", }; // RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1", @@ -5914,6 +5954,19 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_BALLSWITCH_BALL", "MT_BALLSWITCH_PAD", + + "MT_BOSSARENACENTER", + "MT_SPIKEDTARGET", + + "MT_BLENDEYE_MAIN", + "MT_BLENDEYE_EYE", + "MT_BLENDEYE_GLASS", + "MT_BLENDEYE_SHIELD", + "MT_BLENDEYE_EGGBEATER", + "MT_BLENDEYE_GENERATOR", + "MT_BLENDEYE_PUYO", + "MT_BLENDEYE_PUYO_DUST", + "MT_BLENDEYE_PUYO_DUST_COFFEE", }; const char *const MOBJFLAG_LIST[] = { diff --git a/src/doomstat.h b/src/doomstat.h index 0ba0f3b1d..354c9c919 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -607,6 +607,8 @@ struct gametype_t UINT8 intermission; INT32 pointlimit; INT32 timelimit; + char gppic[9]; + char gppicmini[9]; }; extern gametype_t *gametypes[MAXGAMETYPES+1]; diff --git a/src/f_finale.c b/src/f_finale.c index 93d2efd1f..e751084bc 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -1475,6 +1475,8 @@ void F_StartTitleScreen(void) INT32 titleMapNum; setup_numplayers = 0; + encoremode = false; + if (gamestate != GS_TITLESCREEN && gamestate != GS_WAITINGPLAYERS) { ttuser_count = 0; @@ -1495,9 +1497,6 @@ void F_StartTitleScreen(void) titlemapinaction = true; gamemap = titleMapNum+1; - maptol = mapheaderinfo[titleMapNum]->typeoflevel; - globalweather = mapheaderinfo[titleMapNum]->weather; - G_DoLoadLevelEx(true, GS_TITLESCREEN); if (!titlemap) return; diff --git a/src/g_demo.c b/src/g_demo.c index 9a44db79c..d039ddfdd 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -364,7 +364,7 @@ void G_ReadDemoExtraData(void) switch (p) { case DW_RNG: - for (i = 0; i < PRNUMCLASS; i++) + for (i = 0; i < PRNUMSYNCED; i++) { rng = READUINT32(demobuf.p); @@ -507,7 +507,7 @@ void G_WriteDemoExtraData(void) timeout = 16; WRITEUINT8(demobuf.p, DW_RNG); - for (i = 0; i < PRNUMCLASS; i++) + for (i = 0; i < PRNUMSYNCED; i++) { WRITEUINT32(demobuf.p, P_GetRandSeed(i)); } @@ -1213,7 +1213,7 @@ readghosttic: else if (ziptic == DW_RNG) { INT32 i; - for (i = 0; i < PRNUMCLASS; i++) + for (i = 0; i < PRNUMSYNCED; i++) { g->p += 4; // RNG seed } @@ -2449,7 +2449,7 @@ void G_BeginRecording(void) demotime_p = NULL; } - for (i = 0; i < PRNUMCLASS; i++) + for (i = 0; i < PRNUMSYNCED; i++) { WRITEUINT32(demobuf.p, P_GetInitSeed(i)); } @@ -2913,7 +2913,7 @@ void G_LoadDemoInfo(menudemo_t *pdemo) goto badreplay; } - for (i = 0; i < PRNUMCLASS; i++) + for (i = 0; i < PRNUMSYNCED; i++) { info.p += 4; // RNG seed } @@ -3042,7 +3042,7 @@ void G_DoPlayDemo(const char *defdemoname) char *pdemoname; UINT8 availabilities[MAXPLAYERS][MAXAVAILABILITY]; UINT8 version,subversion; - UINT32 randseed[PRNUMCLASS]; + UINT32 randseed[PRNUMSYNCED]; char msg[1024]; boolean spectator, bot; @@ -3322,7 +3322,7 @@ void G_DoPlayDemo(const char *defdemoname) hu_demolap = READUINT32(demobuf.p); // Random seed - for (i = 0; i < PRNUMCLASS; i++) + for (i = 0; i < PRNUMSYNCED; i++) { randseed[i] = READUINT32(demobuf.p); } @@ -3544,7 +3544,7 @@ void G_DoPlayDemo(const char *defdemoname) R_ExecuteSetViewSize(); - for (i = 0; i < PRNUMCLASS; i++) + for (i = 0; i < PRNUMSYNCED; i++) { P_SetRandSeed(i, randseed[i]); } @@ -3681,7 +3681,7 @@ void G_AddGhost(savebuffer_t *buffer, char *defdemoname) if (flags & ATTACKING_LAP) p += 4; - for (i = 0; i < PRNUMCLASS; i++) + for (i = 0; i < PRNUMSYNCED; i++) { p += 4; // random seed } @@ -3890,7 +3890,7 @@ staffbrief_t *G_GetStaffGhostBrief(UINT8 *buffer) if (flags & ATTACKING_LAP) temp.lap = READUINT32(p); - for (i = 0; i < PRNUMCLASS; i++) + for (i = 0; i < PRNUMSYNCED; i++) { p += 4; // random seed } diff --git a/src/g_game.c b/src/g_game.c index e65baacd1..b42085fc9 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1761,7 +1761,7 @@ void G_Ticker(boolean run) } } - D_MapChange(gamemap, gametype, (cv_kartencore.value == 1), false, 1, false, false); + D_MapChange(gamemap, gametype, encoremode, false, 1, false, false); } } @@ -3036,6 +3036,8 @@ static gametype_t defaultgametypes[] = int_time, 0, 0, + "", + "", }, // GT_BATTLE @@ -3047,6 +3049,8 @@ static gametype_t defaultgametypes[] = int_scoreortimeattack, 0, 3, + "TT_RNDB", + "TT_RNSB", }, // GT_SPECIAL @@ -3058,6 +3062,8 @@ static gametype_t defaultgametypes[] = int_time, 0, 0, + "TT_RNDSS", + "TT_RNSSS", }, // GT_VERSUS @@ -3069,6 +3075,8 @@ static gametype_t defaultgametypes[] = int_scoreortimeattack, 0, 0, + "", + "", }, // GT_TUTORIAL @@ -3080,6 +3088,8 @@ static gametype_t defaultgametypes[] = int_none, 0, 0, + "", + "", }, }; @@ -5820,9 +5830,6 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr gamemap = map; - maptol = mapheaderinfo[gamemap-1]->typeoflevel; - globalweather = mapheaderinfo[gamemap-1]->weather; - // Don't carry over custom music change to another map. mapmusflags |= MUSIC_RELOADRESET; diff --git a/src/info.c b/src/info.c index 5bb38d732..23c3225ee 100644 --- a/src/info.c +++ b/src/info.c @@ -933,6 +933,23 @@ char sprnames[NUMSPRITES + 1][5] = "SA2S", // SA2-style Ball Switch + "STRG", // Spiked Target + + "BLEA", // m'A'in unit + "BLEB", // o'B'server + "BLEC", // 'C'lear glass + "BLED", // shiel'D' + "BLEE", // 'E'ggbeater + "BLEF", // 'F'lamejet + "BLEG", // 'G'enerator + + // Puyo hazards + "PUYA", + "PUYB", + "PUYC", + "PUYD", + "PUYE", + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later "VIEW", }; @@ -5521,6 +5538,48 @@ state_t states[NUMSTATES] = {SPR_SA2S, FF_FULLBRIGHT|FF_ANIMATE|FF_GLOBALANIM|4, -1, {NULL}, 1, 1, S_NULL}, // S_BALLSWITCH_BALL_ACTIVE {SPR_SA2S, FF_FLOORSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_BALLSWITCH_PAD {SPR_SA2S, FF_FLOORSPRITE|FF_ANIMATE|FF_GLOBALANIM|1, -1, {NULL}, 1, 1, S_NULL}, // S_BALLSWITCH_PAD_ACTIVE + + {SPR_STRG, FF_ADD|FF_FLOORSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_SPIKEDTARGET, + {SPR_STRG, FF_ADD, -1, {NULL}, 0, 0, S_NULL}, // S_SPIKEDLENS, + + {SPR_BLEA, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BLENDEYE_MAIN, + {SPR_BLEA, 1, -1, {NULL}, 0, 0, S_NULL}, // S_BLENDEYE_MAIN_LAUNCHED, + + {SPR_BLEB, FF_PAPERSPRITE|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_BLENDEYE_EYE, + {SPR_BLEB, FF_PAPERSPRITE|FF_FULLBRIGHT|FF_ANIMATE, -1, {NULL}, 1, 1, S_BLENDEYE_EYE}, // S_BLENDEYE_EYE_FLASH, + + {SPR_BLEC, FF_PAPERSPRITE|FF_ADD, -1, {NULL}, 0, 0, S_NULL}, // S_BLENDEYE_GLASS, + {SPR_BLEC, 1|FF_PAPERSPRITE|FF_ADD|FF_ANIMATE, 2, {NULL}, 1, 1, S_BLENDEYE_GLASS}, // S_BLENDEYE_GLASS_STRESS, + + {SPR_BLED, FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_BLENDEYE_SHIELD, + {SPR_BLED, 1|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_BLENDEYE_SHIELD_L, + {SPR_BLED, 2|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_BLENDEYE_SHIELD_R, + {SPR_BLED, 3|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_BLENDEYE_SHIELD_BUSTED, + {SPR_BLED, 5|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_BLENDEYE_SHIELD_BUSTED_L, + {SPR_BLED, 6|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_BLENDEYE_SHIELD_BUSTED_R, + + {SPR_BLEE, 0, 4, {NULL}, 0, 0, S_BLENDEYE_EGGBEATER_EXTEND_2}, // S_BLENDEYE_EGGBEATER_EXTEND_1, + {SPR_BLEE, 1|FF_ANIMATE, 8, {NULL}, 1, 2, S_BLENDEYE_EGGBEATER}, // S_BLENDEYE_EGGBEATER_EXTEND_2, + {SPR_BLEE, 2, -1, {NULL}, 0, 0, S_NULL}, // S_BLENDEYE_EGGBEATER, + {SPR_BLEE, 2|FF_ANIMATE, -1, {NULL}, 1, 2, S_NULL}, // S_BLENDEYE_EGGBEATER_SPIN, + + {SPR_BLEF, FF_ADD|FF_ANIMATE, -1, {NULL}, 1, 2, S_NULL}, // S_BLENDEYE_FLAME, + + {SPR_BLEG, FF_PAPERSPRITE|FF_ANIMATE, -1, {NULL}, 1, 1, S_NULL}, // S_BLENDEYE_GENERATOR, + {SPR_BLEG, 2|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_BLENDEYE_GENERATOR_BUSTED_L, + {SPR_BLEG, 3|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_BLENDEYE_GENERATOR_BUSTED_R, + + {SPR_PUYA, 6, 2, {A_BlendEyePuyoHack}, 0, 0, S_BLENDEYE_PUYO_SPAWN_2}, // S_BLENDEYE_PUYO_SPAWN_1, + {SPR_PUYA, 5, 2, {A_BlendEyePuyoHack}, 0, 0, S_BLENDEYE_PUYO_SPAWN_3}, // S_BLENDEYE_PUYO_SPAWN_2, + {SPR_PUYA, 4, 2, {A_BlendEyePuyoHack}, 0, 0, S_BLENDEYE_PUYO}, // S_BLENDEYE_PUYO_SPAWN_3, + {SPR_PUYA, 0, -1, {A_BlendEyePuyoHack}, 0, 0, S_NULL}, // S_BLENDEYE_PUYO, + {SPR_PUYA, 2, 2, {A_BlendEyePuyoHack}, 0, 0, S_BLENDEYE_PUYO_LAND_2}, // S_BLENDEYE_PUYO_LAND_1, + {SPR_PUYA, 0, 2, {A_BlendEyePuyoHack}, 0, 0, S_BLENDEYE_PUYO_LAND_3}, // S_BLENDEYE_PUYO_LAND_2, + {SPR_PUYA, 1, 2, {A_BlendEyePuyoHack}, 0, 0, S_BLENDEYE_PUYO_LAND_4}, // S_BLENDEYE_PUYO_LAND_3, + {SPR_PUYA, 0, 2, {A_BlendEyePuyoHack}, 1, S_BLENDEYE_PUYO_LAND_1, S_BLENDEYE_PUYO}, // S_BLENDEYE_PUYO_LAND_4, + {SPR_PUYA, 3, -1, {A_BlendEyePuyoHack}, 0, 0, S_NULL}, // S_BLENDEYE_PUYO_SHOCK, + {SPR_PUYA, 4|FF_ANIMATE, 5, {A_BlendEyePuyoHack}, 2, 2, S_NULL}, // S_BLENDEYE_PUYO_DIE, + {SPR_PUYA, 5, 2, {A_BlendEyePuyoHack}, 0, 0, S_BLENDEYE_PUYO_DIE}, // S_BLENDEYE_PUYO_DUST, }; mobjinfo_t mobjinfo[NUMMOBJTYPES] = @@ -31106,6 +31165,303 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_SCENERY, // flags S_NULL // raisestate }, + + { // MT_BOSSARENACENTER + 2130, // doomednum + S_INVISIBLE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + FRACUNIT, // radius + FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY|MF_SCENERY, // flags + S_NULL // raisestate + }, + + { // MT_SPIKEDTARGET + -1, // doomednum + S_SPIKEDTARGET, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 24*FRACUNIT, // radius + 48*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SCENERY|MF_NOCLIPTHING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_BLENDEYE_MAIN + 2131, // doomednum + S_BLENDEYE_MAIN, // spawnstate + 3, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_BLENDEYE_MAIN, // painstate + 0, // painchance + sfx_mbs60, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_BLENDEYE_MAIN, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 42*FRACUNIT, // radius + 56*FRACUNIT, // height + 0, // display offset + DMG_NORMAL, // mass + 0, // damage + sfx_None, // activesound + MF_BOSS|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPTHING|MF_SOLID, // flags + S_BLENDEYE_MAIN_LAUNCHED // raisestate + }, + + { // MT_BLENDEYE_EYE + -1, // doomednum + S_BLENDEYE_EYE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 14*FRACUNIT, // radius + 28*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOCLIPTHING|MF_NOGRAVITY, // flags + S_BLENDEYE_EYE_FLASH // raisestate + }, + + { // MT_BLENDEYE_GLASS + -1, // doomednum + S_BLENDEYE_GLASS, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_BLENDEYE_GLASS_STRESS, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 96*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOGRAVITY|MF_PAPERCOLLISION|MF_PAIN, // flags + S_NULL // raisestate + }, + + { // MT_BLENDEYE_SHIELD + -1, // doomednum + S_BLENDEYE_SHIELD, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 17*FRACUNIT, // radius + 42*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOGRAVITY|MF_PAPERCOLLISION|MF_PAIN, // flags + S_NULL // raisestate + }, + + { // MT_BLENDEYE_EGGBEATER + -1, // doomednum + S_BLENDEYE_EGGBEATER_EXTEND_1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 26*FRACUNIT, // radius + 56*FRACUNIT, // height + -1, // display offset + DMG_TUMBLE, // mass + 0, // damage + sfx_None, // activesound + MF_NOGRAVITY|MF_PAIN, // flags + S_BLENDEYE_EGGBEATER_SPIN // raisestate + }, + + { // MT_BLENDEYE_GENERATOR + -1, // doomednum + S_BLENDEYE_GENERATOR, // spawnstate + 1, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_BLENDEYE_GENERATOR_BUSTED_L, // deathstate + S_BLENDEYE_GENERATOR_BUSTED_R, // xdeathstate + sfx_None, // deathsound + 0, // speed + 26*FRACUNIT, // radius + 42*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SHOOTABLE|MF_PAPERCOLLISION|MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_BLENDEYE_PUYO + -1, // doomednum + S_BLENDEYE_PUYO_SPAWN_1, // spawnstate + 1, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_BLENDEYE_PUYO_DIE, // deathstate + S_NULL, // xdeathstate + sfx_mbs4c, // deathsound + 0, // speed + 8*FRACUNIT, // radius + 12*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOHITLAGFORME, // flags + S_NULL // raisestate + }, + + { // MT_BLENDEYE_PUYO_DUST + -1, // doomednum + S_BLENDEYE_PUYO_DUST, // spawnstate + 1, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 4*FRACUNIT, // radius + 4*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SCENERY|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_BLENDEYE_PUYO_DUST_COFFEE + -1, // doomednum + S_BLENDEYE_PUYO_DUST, // spawnstate + 1, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 4*FRACUNIT, // radius + 4*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SCENERY|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, }; diff --git a/src/info.h b/src/info.h index d99201fb7..b57082a5f 100644 --- a/src/info.h +++ b/src/info.h @@ -296,6 +296,7 @@ enum actionnum A_SPAWNITEMDEBRISCLOUD, A_RINGSHOOTERFACE, A_SPAWNSNEAKERPANEL, + A_BLENDEYEPUYOHACK, NUMACTIONS }; @@ -572,6 +573,7 @@ void A_InvincSparkleRotate(); void A_SpawnItemDebrisCloud(); void A_RingShooterFace(); void A_SpawnSneakerPanel(); +void A_BlendEyePuyoHack(); extern boolean actionsoverridden[NUMACTIONS]; @@ -1485,6 +1487,23 @@ typedef enum sprite SPR_SA2S, // SA2-style Ball Switch + SPR_STRG, // Spiked Target + + SPR_BLEA, // m'A'in unit + SPR_BLEB, // o'B'server + SPR_BLEC, // 'C'lear glass + SPR_BLED, // shiel'D' + SPR_BLEE, // 'E'ggbeater + SPR_BLEF, // 'F'lamejet + SPR_BLEG, // 'G'enerator + + // Puyo hazards + SPR_PUYA, + SPR_PUYB, + SPR_PUYC, + SPR_PUYD, + SPR_PUYE, + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later SPR_VIEW, @@ -5946,6 +5965,46 @@ typedef enum state S_BALLSWITCH_PAD, S_BALLSWITCH_PAD_ACTIVE, + S_SPIKEDTARGET, + S_SPIKEDLENS, + + S_BLENDEYE_MAIN, + S_BLENDEYE_MAIN_LAUNCHED, + S_BLENDEYE_EYE, + S_BLENDEYE_EYE_FLASH, + S_BLENDEYE_GLASS, + S_BLENDEYE_GLASS_STRESS, + + S_BLENDEYE_SHIELD, + S_BLENDEYE_SHIELD_L, + S_BLENDEYE_SHIELD_R, + S_BLENDEYE_SHIELD_BUSTED, + S_BLENDEYE_SHIELD_BUSTED_L, + S_BLENDEYE_SHIELD_BUSTED_R, + + S_BLENDEYE_EGGBEATER_EXTEND_1, + S_BLENDEYE_EGGBEATER_EXTEND_2, + S_BLENDEYE_EGGBEATER, + S_BLENDEYE_EGGBEATER_SPIN, + + S_BLENDEYE_FLAME, + + S_BLENDEYE_GENERATOR, + S_BLENDEYE_GENERATOR_BUSTED_L, + S_BLENDEYE_GENERATOR_BUSTED_R, + + S_BLENDEYE_PUYO_SPAWN_1, + S_BLENDEYE_PUYO_SPAWN_2, + S_BLENDEYE_PUYO_SPAWN_3, + S_BLENDEYE_PUYO, + S_BLENDEYE_PUYO_LAND_1, + S_BLENDEYE_PUYO_LAND_2, + S_BLENDEYE_PUYO_LAND_3, + S_BLENDEYE_PUYO_LAND_4, + S_BLENDEYE_PUYO_SHOCK, + S_BLENDEYE_PUYO_DIE, + S_BLENDEYE_PUYO_DUST, + S_FIRSTFREESLOT, S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1, NUMSTATES @@ -7149,6 +7208,19 @@ typedef enum mobj_type MT_BALLSWITCH_BALL, MT_BALLSWITCH_PAD, + MT_BOSSARENACENTER, + MT_SPIKEDTARGET, + + MT_BLENDEYE_MAIN, + MT_BLENDEYE_EYE, + MT_BLENDEYE_GLASS, + MT_BLENDEYE_SHIELD, + MT_BLENDEYE_EGGBEATER, + MT_BLENDEYE_GENERATOR, + MT_BLENDEYE_PUYO, + MT_BLENDEYE_PUYO_DUST, + MT_BLENDEYE_PUYO_DUST_COFFEE, + MT_FIRSTFREESLOT, MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1, NUMMOBJTYPES diff --git a/src/k_battle.c b/src/k_battle.c index 1304489a3..d5577320d 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -39,7 +39,7 @@ UINT8 numtargets = 0; // Capsules busted INT32 K_StartingBumperCount(void) { - if (battleprisons) + if (battleprisons || K_CheckBossIntro()) { if (grandprixinfo.gp) { diff --git a/src/k_boss.c b/src/k_boss.c index 7b0a8fe96..58f5bb98c 100644 --- a/src/k_boss.c +++ b/src/k_boss.c @@ -1,4 +1,4 @@ -// SONIC ROBO BLAST 2 KART +// DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2018-2023 by Vivian "toastergrl" Grannell // Copyright (C) 2018-2023 by Kart Krew diff --git a/src/k_boss.h b/src/k_boss.h index 8e41e0cdf..0fbd2857c 100644 --- a/src/k_boss.h +++ b/src/k_boss.h @@ -1,4 +1,4 @@ -// SONIC ROBO BLAST 2 KART +// DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2018-2023 by Vivian "toastergrl" Grannell // Copyright (C) 2018-2023 by Kart Krew @@ -129,6 +129,30 @@ void K_DeclareWeakspot(mobj_t *spot, spottype_t spottype, UINT16 color, boolean boolean K_CheckBossIntro(void); +// Arena objects + +boolean VS_ArenaCenterInit(mobj_t *mobj, mapthing_t *mthing); +mobj_t *VS_GetArena(INT32 bossindex); +fixed_t *VS_PredictAroundArena(mobj_t *arena, mobj_t *movingobject, fixed_t magnitude, angle_t mompoint, fixed_t radiussubtract, boolean forcegoaround, fixed_t radiusdeltafactor); +fixed_t *VS_RandomPointOnArena(mobj_t *arena, fixed_t radiussubtract); + +// Blend Eye + +void VS_BlendEye_Init(mobj_t *mobj); +void VS_BlendEye_Thinker(mobj_t *mobj); +boolean VS_BlendEye_Touched(mobj_t *special, mobj_t *toucher); +void VS_BlendEye_Damage(mobj_t *mobj, mobj_t *inflictor, mobj_t *source, INT32 damage); +void VS_BlendEye_Death(mobj_t *mobj); + +boolean VS_BlendEye_Eye_Thinker(mobj_t *mobj); +void VS_BlendEye_Glass_Death(mobj_t *mobj); +void VS_BlendEye_Eggbeater_Touched(mobj_t *t1, mobj_t *t2); +void VS_BlendEye_Generator_DeadThinker(mobj_t *mobj); + +boolean VS_PuyoTouched(mobj_t *special, mobj_t *toucher); +void VS_PuyoThinker(mobj_t *mobj); +void VS_PuyoDeath(mobj_t *mobj); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_hud.c b/src/k_hud.c index fdb249c1f..ab26ae388 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -2432,7 +2432,7 @@ static void K_drawBossHealthBar(void) { rolrand = 10; } - V_DrawRightAlignedThinString(startx, starty-rolrand, V_HUDTRANS|V_SLIDEIN|V_SNAPTOBOTTOM|V_SNAPTORIGHT, bossinfo.enemyname); + V_DrawRightAlignedThinString(startx, starty-rolrand, V_FORCEUPPERCASE|V_HUDTRANS|V_SLIDEIN|V_SNAPTOBOTTOM|V_SNAPTORIGHT, bossinfo.enemyname); rolrand = 0; } @@ -2448,8 +2448,8 @@ static void K_drawBossHealthBar(void) randtemp = bossinfo.visualbar-(bossinfo.visualdiv/(2*FRACUNIT)); if (randtemp > 0) - randlen = M_RandomKey(randtemp)+1; - randsign = M_RandomChance(FRACUNIT/2); + randlen = P_RandomKey(PR_INTERPHUDRANDOM, randtemp)+1; + randsign = P_RandomChance(PR_INTERPHUDRANDOM, FRACUNIT/2); // Right wing. V_DrawScaledPatch(startx-1, starty, V_HUDTRANS|V_SLIDEIN|V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_FLIP, kp_bossbar[0]); @@ -2465,10 +2465,10 @@ static void K_drawBossHealthBar(void) { randtemp = bossinfo.visualbar-(bossinfo.visualdiv/(2*FRACUNIT)); if (randtemp > 0) - randlen = M_RandomKey(randtemp)+1; + randlen = P_RandomKey(PR_INTERPHUDRANDOM, randtemp)+1; if (barstatus > 1) { - rolrand = M_RandomKey(barstatus)+1; + rolrand = P_RandomKey(PR_INTERPHUDRANDOM, barstatus)+1; } else { diff --git a/src/k_kart.c b/src/k_kart.c index e3002e5ca..088421b2b 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -12544,16 +12544,16 @@ UINT32 K_PointLimitForGametype(void) return 0; } - if (cv_pointlimit.value != -1) - { - return cv_pointlimit.value; - } - if (K_Cooperative()) { return 0; } + if (K_CanChangeRules(true) == true && cv_pointlimit.value != -1) + { + return cv_pointlimit.value; + } + if ((gametyperules & battleRules) == battleRules) // why isn't this just another GTR_?? { INT32 i; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 94cd652a6..e478e1bcf 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4767,43 +4767,22 @@ void M_DrawPause(void) if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE) { - const char *append = NULL; - - switch (grandprixinfo.eventmode) - { - case GPEVENT_SPECIAL: - { - append = "SS"; - break; - } - - case GPEVENT_BONUS: - { - append = "B"; - break; - } - - default: - break; - } - - if (append) + if (gametypes[gametype]->gppicmini[0]) + smallroundpatch = W_CachePatchName(gametypes[gametype]->gppicmini, PU_PATCH); + else + smallroundpatch = W_CachePatchName("TT_RNSX", PU_PATCH); + } + else if (roundqueue.size > 0) + { + if (roundqueue.roundnum > 0 && roundqueue.roundnum <= 10) { smallroundpatch = W_CachePatchName( - va("TT_RNS%s", append), + va("TT_RNS%d", roundqueue.roundnum), PU_PATCH ); } } - else if (roundqueue.roundnum > 0 && roundqueue.roundnum <= 10) - { - smallroundpatch = - W_CachePatchName( - va("TT_RNS%d", roundqueue.roundnum), - PU_PATCH - ); - } if (smallroundpatch != NULL) { diff --git a/src/k_podium.cpp b/src/k_podium.cpp index fcd8fb940..daada2ec6 100644 --- a/src/k_podium.cpp +++ b/src/k_podium.cpp @@ -932,8 +932,7 @@ boolean K_StartCeremony(void) { gamemap = podiumMapNum+1; - maptol = mapheaderinfo[gamemap-1]->typeoflevel; - globalweather = static_cast(mapheaderinfo[gamemap-1]->weather); + encoremode = grandprixinfo.encore; if (savedata.lives > 0) { diff --git a/src/k_tally.cpp b/src/k_tally.cpp index 012c1d684..af1413141 100644 --- a/src/k_tally.cpp +++ b/src/k_tally.cpp @@ -295,6 +295,7 @@ void level_tally_t::Init(player_t *player) time = std::min(static_cast(player->realtime), (100 * 60 * TICRATE) - 1); ringPool = player->totalring; + livesAdded = 0; position = numPlayers = 0; rings = 0; @@ -410,10 +411,16 @@ void level_tally_t::Init(player_t *player) ); } - if (roundqueue.size > 0 && roundqueue.roundnum > 0 - && (grandprixinfo.gp == false || grandprixinfo.eventmode == GPEVENT_NONE)) + if (roundqueue.size > 0 && roundqueue.roundnum > 0) { - roundNum = roundqueue.roundnum; + if ((grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE)) + { + roundNum = INTERMISSIONROUND_BONUS; + } + else + { + roundNum = roundqueue.roundnum; + } } } else @@ -436,7 +443,7 @@ void level_tally_t::Init(player_t *player) roundNum = roundqueue.roundnum; } - else if (bossinfo.valid == true && bossinfo.enemyname) + else if (K_CheckBossIntro() == true && bossinfo.enemyname) { snprintf( header, sizeof header, @@ -567,7 +574,10 @@ boolean level_tally_t::IncrementLine(void) } value = &displayStat[i]; - lives_check = (stats[i] == TALLY_STAT_TOTALRINGS); + lives_check = ( + stats[i] == TALLY_STAT_TOTALRINGS // Rings also shows the Lives. + && livesAdded < owner->xtralife // Don't check if we've maxxed out! + ); switch (stats[i]) { @@ -649,6 +659,8 @@ boolean level_tally_t::IncrementLine(void) return true; } + const boolean playSounds = P_IsDisplayPlayer(owner); + if (*value == dest) { // We've reached our destination @@ -656,7 +668,6 @@ boolean level_tally_t::IncrementLine(void) } const INT32 prevVal = *value; - const boolean playSounds = P_IsDisplayPlayer(owner); if (playSounds == true && tickSound == 0) { @@ -693,6 +704,7 @@ boolean level_tally_t::IncrementLine(void) // Handle extra life sound & blinking if (extra > oldExtra) { + livesAdded++; xtraBlink = TICRATE; if (playSounds == true) @@ -775,7 +787,24 @@ void level_tally_t::Tick(void) { if (IncrementLine() == true) { - if (playSounds) + if (grandprixinfo.gp == true // In GP + && lines >= lineCount // Finished the bonuses + && livesAdded < owner->xtralife // Didn't max out by other causes + ) + { + // This is only true if Rings alone aren't responsible for our added lives. + // Generally for Prison Break, but could be earned in custom contexts too. + livesAdded = owner->xtralife; + xtraBlink = TICRATE; + + if (playSounds == true) + { + S_StopSoundByNum(sfx_cdfm73); + S_StartSound(NULL, sfx_cdfm73); + } + } + + if (playSounds == true) { S_StopSoundByNum(sfx_mbs5b); S_StartSound(NULL, (lines >= lineCount) ? sfx_mbs70 : sfx_mbs5b); @@ -1190,13 +1219,8 @@ void level_tally_t::Draw(void) .colormap(owner->skin, color) .patch(faceprefix[owner->skin][r_splitscreen ? FACE_MINIMAP : FACE_RANK]); - const UINT8 lifethreshold = 20; - const UINT8 oldExtra = ringPool / lifethreshold; - const UINT8 extra = displayStat[i] / lifethreshold; - const INT32 increase = (extra - oldExtra); - - UINT8 lives_num = owner->lives + increase; - if (xtraBlink > 0 && (xtraBlink & 1) == 0 && increase > 0) + UINT8 lives_num = std::min(owner->lives + livesAdded, 10); + if (xtraBlink > 0 && (xtraBlink & 1) == 0 && livesAdded > 0) { lives_num = 0; } diff --git a/src/k_tally.h b/src/k_tally.h index 0cf03e8ca..1eb37020f 100644 --- a/src/k_tally.h +++ b/src/k_tally.h @@ -72,6 +72,7 @@ struct level_tally_t // Stats INT32 time; UINT16 ringPool; + UINT8 livesAdded; tally_stat_e stats[TALLY_WINDOW_SIZE]; // Possible grade metrics diff --git a/src/k_vote.c b/src/k_vote.c index 731a6cd8b..8a2fce4ed 100644 --- a/src/k_vote.c +++ b/src/k_vote.c @@ -864,7 +864,7 @@ void Y_VoteDrawer(void) static void Y_FinalizeVote(const SINT8 level) { nextmap = g_voteLevels[level][0]; - deferencoremode = (g_voteLevels[level][1] & VOTE_MOD_ENCORE); + deferencoremode = ((g_voteLevels[level][1] & VOTE_MOD_ENCORE) == VOTE_MOD_ENCORE); } static void Y_VoteStops(SINT8 pick, SINT8 level) diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 69f0aed21..2395614b0 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -2559,6 +2559,8 @@ static int lib_gAddGametype(lua_State *L) const char *gtname = NULL; const char *gtconst = NULL; + const char *gppic = NULL; + const char *gppicmini = NULL; UINT32 newgtrules = 0; UINT32 newgttol = 0; INT32 newgtpointlimit = 0; @@ -2621,6 +2623,14 @@ static int lib_gAddGametype(lua_State *L) if (!lua_isnumber(L, 3)) TYPEERROR("defaulttimelimit", LUA_TNUMBER) newgttimelimit = (INT32)lua_tointeger(L, 3); + } else if (i == 8 || (k && fasticmp(k, "gppic"))) { + if (!lua_isstring(L, 3)) + TYPEERROR("gppic", LUA_TSTRING) + gppic = lua_tostring(L, 3); + } else if (i == 9 || (k && fasticmp(k, "gppicmini"))) { + if (!lua_isstring(L, 3)) + TYPEERROR("gppicmini", LUA_TSTRING) + gppicmini = lua_tostring(L, 3); } lua_pop(L, 1); } @@ -2662,6 +2672,18 @@ static int lib_gAddGametype(lua_State *L) newgametype->pointlimit = newgtpointlimit; newgametype->timelimit = newgttimelimit; + if (gppic != NULL) + { + // Calloc means only set if valid + strlcpy(newgametype->gppic, gppic, 9); + } + + if (gppicmini != NULL) + { + // Calloc means only set if valid + strlcpy(newgametype->gppicmini, gppicmini, 9); + } + gametypes[numgametypes++] = newgametype; // done @@ -3507,6 +3529,49 @@ static int lib_kDeclareWeakspot(lua_State *L) return 0; } +static int lib_vsGetArena(lua_State *L) +{ + INT32 bossindex = luaL_checkinteger(L, 1); + //HUDSAFE + LUA_PushUserdata(L, VS_GetArena(bossindex), META_MOBJ); + return 1; +} + +static int lib_vsPredictAroundArena(lua_State *L) +{ + mobj_t *arena = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); + mobj_t *movingobject = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ)); + fixed_t magnitude = luaL_checkfixed(L, 3); + fixed_t mompoint = luaL_checkangle(L, 4); + fixed_t radiussubtract = luaL_checkfixed(L, 5); + boolean forcegoaround = lua_optboolean(L, 6); + fixed_t radiusdeltafactor = luaL_optinteger(L, 7, FRACUNIT); //optfixed? + NOHUD + if (!arena || !movingobject) + return LUA_ErrInvalid(L, "mobj_t"); + + fixed_t *result = VS_PredictAroundArena(arena, movingobject, magnitude, mompoint, radiussubtract, forcegoaround, radiusdeltafactor); + + lua_pushfixed(L, result[0]); + lua_pushfixed(L, result[1]); + return 2; +} + +static int lib_vsRandomPointOnArena(lua_State *L) +{ + mobj_t *arena = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); + fixed_t radiussubtract = luaL_checkfixed(L, 2); + NOHUD + if (!arena) + return LUA_ErrInvalid(L, "mobj_t"); + + fixed_t *result = VS_RandomPointOnArena(arena, radiussubtract); + + lua_pushfixed(L, result[0]); + lua_pushfixed(L, result[1]); + return 2; +} + static int lib_getTimeMicros(lua_State *L) { lua_pushinteger(L, I_GetPreciseTime() / (I_GetPrecisePrecision() / 1000000)); @@ -3782,6 +3847,9 @@ static luaL_Reg lib[] = { {"K_InitBossHealthBar", lib_kInitBossHealthBar}, {"K_UpdateBossHealthBar", lib_kUpdateBossHealthBar}, {"K_DeclareWeakspot", lib_kDeclareWeakspot}, + {"VS_GetArena", lib_vsGetArena}, + {"VS_PredictAroundArena", lib_vsPredictAroundArena}, + {"VS_RandomPointOnArena", lib_vsRandomPointOnArena}, // hu_stuff technically? {"HU_DoTitlecardCEcho", lib_startTitlecardCecho}, diff --git a/src/m_random.c b/src/m_random.c index 9401bb341..0d490bb5b 100644 --- a/src/m_random.c +++ b/src/m_random.c @@ -342,6 +342,37 @@ void P_SetRandSeedNetD(const char *rfile, INT32 rline, pr_class_t pr_class, UINT rng.seed[pr_class] = seed; } +/** Change PR_INTERPHUDRANDOM state. + * Used for interp-safe HUD randomisation. + * + * \sa P_SetRandSeed + */ +#ifndef DEBUGRANDOM +void P_ResetInterpHudRandSeed(boolean newframe) +{ +#else +void P_ResetInterpHudRandSeedD(const char *rfile, INT32 rline, boolean newframe) +{ + CONS_Printf("P_ResetInterpHudRandSeed(%c) at: %sp %d\n", (newframe ? 'T' : 'F'), rfile, rline); +#endif + + if (newframe == true) + { + // Advance the initialisation to the current seed. + rng.init[PR_INTERPHUDRANDOM] = rng.seed[PR_INTERPHUDRANDOM]; + } + else + { + // Rewind the seed to the last initialisation. + rng.seed[PR_INTERPHUDRANDOM] = rng.init[PR_INTERPHUDRANDOM]; + } + + // xorshift requires a nonzero seed + // this should never happen, but just in case it DOES, we check + if (!rng.seed[PR_INTERPHUDRANDOM]) + rng.seed[PR_INTERPHUDRANDOM] = rng.init[PR_INTERPHUDRANDOM] = DEFAULT_SEED; +} + /** Initializes random seeds for all classes. * Used at the beginning of a game. * @@ -354,7 +385,7 @@ void P_ClearRandom(UINT32 seed) if (!seed) seed = DEFAULT_SEED; - for (i = 0; i < PRNUMCLASS; i++) + for (i = 0; i < PRNUMSYNCED; i++) { P_SetRandSeed(i, seed); diff --git a/src/m_random.h b/src/m_random.h index 479ea24ef..77a3f33ce 100644 --- a/src/m_random.h +++ b/src/m_random.h @@ -83,6 +83,10 @@ typedef enum PR_FUZZ, // Stability testing + PRNUMSYNCED, + + PR_INTERPHUDRANDOM = PRNUMSYNCED, // Interpolation-accomodating HUD randomisation + PRNUMCLASS } pr_class_t; @@ -132,15 +136,18 @@ UINT32 P_RandomPeek(pr_class_t pr_class); #define P_GetInitSeed(pr) P_GetInitSeedD(__FILE__, __LINE__, pr) #define P_SetRandSeed(pr, s) P_SetRandSeedD(__FILE__, __LINE__, pr, s) #define P_SetRandSeedNet(pr, i, s) P_SetRandSeedD(__FILE__, __LINE__, pr, i, s) +#define P_ResetInterpHudRandSeed(newframe) P_ResetInterpHudRandSeedD(__FILE__, __LINE__, newframe) UINT32 P_GetRandSeedD(const char *rfile, INT32 rline, pr_class_t pr_class); UINT32 P_GetInitSeedD(const char *rfile, INT32 rline, pr_class_t pr_class); void P_SetRandSeedD(const char *rfile, INT32 rline, pr_class_t pr_class, UINT32 seed); void P_SetRandSeedNetD(const char *rfile, INT32 rline, pr_class_t pr_class, UINT32 init, UINT32 seed); +void P_ResetInterpHudRandSeedD(const char *rfile, INT32 rline, boolean newframe); #else UINT32 P_GetRandSeed(pr_class_t pr_class); UINT32 P_GetInitSeed(pr_class_t pr_class); void P_SetRandSeed(pr_class_t pr_class, UINT32 seed); void P_SetRandSeedNet(pr_class_t pr_class, UINT32 init, UINT32 seed); +void P_ResetInterpHudRandSeed(boolean newframe); #endif void P_ClearRandom(UINT32 seed); diff --git a/src/music.cpp b/src/music.cpp index 02f62c61d..a2aef3070 100644 --- a/src/music.cpp +++ b/src/music.cpp @@ -31,6 +31,8 @@ void Music_Init(void) tune.resume_fade_in = 750; tune.sync = true; tune.credit = true; + tune.vapes = true; + tune.nightcoreable = true; } { @@ -75,6 +77,7 @@ void Music_Init(void) tune.song = "racent"; tune.priority = 40; + tune.vapes = true; } { diff --git a/src/music_tune.hpp b/src/music_tune.hpp index 2b5880145..b22efb23d 100644 --- a/src/music_tune.hpp +++ b/src/music_tune.hpp @@ -16,6 +16,7 @@ #include "doomdef.h" #include "doomtype.h" +#include "k_boss.h" #include "music_detail.hpp" namespace srb2::music @@ -61,6 +62,10 @@ public: // resumed). bool credit = false; + // This tune will vape in encoremode contexts. + bool vapes = false; + bool nightcoreable = false; + // Start playing this number of tics into the tune. tic_t seek = 0; @@ -78,12 +83,21 @@ public: bool paused() const { return pause_.has_value(); } bool can_end() const { return end_ != INFTICS; } + // Slow level music down a bit in Encore. (Values are vibe-based. WE GET IT YOU VAPE) + const float encoremul = 0.86471f; + float speed() const { - // Slow level music down a bit in Encore. (Values are vibe-based. WE GET IT YOU VAPE) - if (encoremode && gamestate == GS_LEVEL) + // Apply to all tunes that support vape mode. + if (encoremode && vapes) { - return 0.86471f; + if (nightcoreable && K_CheckBossIntro()) + { + // In Versus, the vape makes you think you can start a nightcore YT channel + return (1.f/encoremul); + } + + return encoremul; } return 1.f; diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 1e99c2f0f..1e035d52d 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -40,3 +40,5 @@ target_sources(SRB2SDL2 PRIVATE shadow.cpp ball-switch.cpp ) + +add_subdirectory(versus) diff --git a/src/objects/orbinaut.c b/src/objects/orbinaut.c index 4e9483b98..631b6f79e 100644 --- a/src/objects/orbinaut.c +++ b/src/objects/orbinaut.c @@ -255,6 +255,12 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) P_DamageMobj(t2, t1, t1->target, 1, DMG_NORMAL); damageitem = true; } + else if (t2->flags & MF_PAIN) + { + // Hazard blocks + P_SpawnMobj(t2->x/2 + t1->x/2, t2->y/2 + t1->y/2, t2->z/2 + t1->z/2, MT_ITEMCLASH); + damageitem = true; + } if (t1->type == MT_GARDENTOP) { diff --git a/src/objects/random-item.c b/src/objects/random-item.c index 160a516c9..8df3fb937 100644 --- a/src/objects/random-item.c +++ b/src/objects/random-item.c @@ -127,7 +127,7 @@ void Obj_RandomItemVisuals(mobj_t *mobj) // Don't transform stuff that isn't a Ring Box, idiot statenum_t boxstate = mobj->state - states; - if (boxstate >= S_RANDOMITEM1 && boxstate <= S_RANDOMITEM12) + if (boxstate < S_RINGBOX1 || boxstate > S_RINGBOX12) return; if (mobj->extravalue1 == RINGBOX_TIME || specialstageinfo.valid) @@ -141,7 +141,7 @@ void Obj_RandomItemVisuals(mobj_t *mobj) boolean Obj_RandomItemSpawnIn(mobj_t *mobj) { - if ((leveltime == starttime) && !(gametyperules & GTR_CIRCUIT) && (mobj->flags2 & MF2_BOSSNOTRAP)) // here on map start? + if ((leveltime == starttime) && !(gametyperules & GTR_CIRCUIT) && (mobj->flags2 & MF2_BOSSFLEE)) // here on map start? { if (gametyperules & GTR_PAPERITEMS) { @@ -192,6 +192,18 @@ void Obj_RandomItemSpawn(mobj_t *mobj) P_SetMobjState(mobj, S_RANDOMITEM1); } + if (leveltime == 0) + { + mobj->flags2 |= MF2_BOSSFLEE; // mark as here on map start + if ((gametyperules & GTR_CIRCUIT) != GTR_CIRCUIT) // delayed + { + P_UnsetThingPosition(mobj); + mobj->flags |= (MF_NOCLIPTHING|MF_NOBLOCKMAP); + mobj->renderflags |= RF_DONTDRAW; + P_SetThingPosition(mobj); + } + } + mobj->destscale = Obj_RandomItemScale(mobj->destscale); P_SetScale(mobj, mobj->destscale); } diff --git a/src/objects/versus/CMakeLists.txt b/src/objects/versus/CMakeLists.txt new file mode 100644 index 000000000..ade3ee75f --- /dev/null +++ b/src/objects/versus/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources(SRB2SDL2 PRIVATE + arena.c + blendeye.c +) diff --git a/src/objects/versus/arena.c b/src/objects/versus/arena.c new file mode 100644 index 000000000..bc0fbc4df --- /dev/null +++ b/src/objects/versus/arena.c @@ -0,0 +1,150 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Vivian "toastergrl" Grannell +// Copyright (C) 2022-2023 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file arena.c +/// \brief Boss battle arena logic + +#include "../../p_local.h" +#include "../../p_setup.h" +#include "../../m_random.h" +#include "../../k_boss.h" +#include "../../r_main.h" // R_PointToAngle2, R_PointToDist2 + +boolean VS_ArenaCenterInit(mobj_t *mobj, mapthing_t *mthing) +{ + INT32 dist1 = mthing->thing_args[1]*FRACUNIT; + INT32 dist2 = mthing->thing_args[2]*FRACUNIT; + + if (dist1 || dist2) + { + if (dist1 > dist2) + { + INT32 swap = dist1; + dist1 = dist2; + dist2 = swap; + } + + mobj->extravalue1 = dist1; + mobj->extravalue2 = dist2; + return true; + } + + CONS_Alert(CONS_ERROR, "Versus arena of index %d has no max radius.", mthing->thing_args[0]); + return false; +} + +mobj_t *VS_GetArena(INT32 bossindex) +{ + size_t i; + + for (i = 0; i < nummapthings; ++i) + { + if (mapthings[i].type != mobjinfo[MT_BOSSARENACENTER].doomednum) + continue; + + if (mapthings[i].thing_args[0] != bossindex) + continue; + + if (mapthings[i].mobj == NULL) + continue; + + return mapthings[i].mobj; + } + + CONS_Alert(CONS_ERROR, "No Versus arena of index %d found.", bossindex); + return NULL; +} + +fixed_t *VS_PredictAroundArena(mobj_t *arena, mobj_t *movingobject, fixed_t magnitude, angle_t mompoint, fixed_t radiussubtract, boolean forcegoaround, fixed_t radiusdeltafactor) +{ + static fixed_t dest[2] = {0, 0}; + + if (P_MobjWasRemoved(arena)) + { + CONS_Alert(CONS_ERROR, "VS_PredictAroundArena: No Versus arena provided."); + return dest; + } + + if (P_MobjWasRemoved(movingobject)) + { + CONS_Alert(CONS_ERROR, "VS_PredictAroundArena: No moving object provided."); + return dest; + } + + fixed_t radius = FixedHypot(movingobject->x - arena->x, movingobject->y - arena->y); + angle_t basedir = R_PointToAngle2(arena->x, arena->y, movingobject->x, movingobject->y); + fixed_t radiusdelta = 0; + + const fixed_t minarena = arena->extravalue1 + radiussubtract; + const fixed_t maxarena = arena->extravalue2 - radiussubtract; + + mompoint -= basedir; + + if (radiusdeltafactor > 0) // for kneecapping the prediction + { + boolean clipped = false; + + // Add radius so we can compare against the arena size. + radiusdelta = radius + P_ReturnThrustX(arena, mompoint, magnitude); + + if (radiusdelta > maxarena) + { + radiusdelta = maxarena; + clipped = true; + } + else if (radiusdelta < minarena) + { + radiusdelta = minarena; + clipped = true; + } + + if (clipped == true && forcegoaround == true) + { + mompoint = (mompoint < ANGLE_180) ? ANGLE_90 : ANGLE_270; + } + + // Subtract the radius so it's usable as a delta! + radiusdelta -= radius; + + if (radiusdelta && radiusdeltafactor != FRACUNIT) + radiusdelta = FixedDiv(radiusdelta, radiusdeltafactor); + } + + radius += (radiusdelta/2); // average... + if (radius > 0) + { + fixed_t sidecomponent = P_ReturnThrustY(arena, mompoint, magnitude); + // percent*(TAU/100)% of circumference = radians*r, so divide by r to reverse that + fixed_t radians = FixedDiv(sidecomponent, radius); + basedir += FixedAngle(FixedDiv(360*radians, M_TAU_FIXED)); // converting to degrees along the way. + } + radius += (radiusdelta/2); // ...and then finalise! + + dest[0] = arena->x + P_ReturnThrustX(arena, basedir, radius); + dest[1] = arena->y + P_ReturnThrustY(arena, basedir, radius); + return dest; +} + +fixed_t *VS_RandomPointOnArena(mobj_t *arena, fixed_t radiussubtract) +{ + static fixed_t dest[2] = {0, 0}; + + if (P_MobjWasRemoved(arena)) + return dest; + + const fixed_t minarena = arena->extravalue1 + radiussubtract; + const fixed_t maxarena = arena->extravalue2 - radiussubtract; + + angle_t rand1 = FixedAngle(P_RandomKey(PR_MOVINGTARGET, 360)*FRACUNIT); + fixed_t rand2 = P_RandomRange(PR_MOVINGTARGET, minarena/FRACUNIT, maxarena/FRACUNIT)*FRACUNIT; + dest[0] = arena->x + P_ReturnThrustX(arena, rand1, rand2); + dest[1] = arena->y + P_ReturnThrustY(arena, rand1, rand2); + + return dest; +} diff --git a/src/objects/versus/blendeye.c b/src/objects/versus/blendeye.c new file mode 100644 index 000000000..e56f516c4 --- /dev/null +++ b/src/objects/versus/blendeye.c @@ -0,0 +1,1620 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2023 by Vivian "toastergrl" Grannell +// Copyright (C) 2018-2023 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_boss.h +/// \brief Blend Eye boss encounter + +#include "../../p_local.h" +#include "../../m_random.h" +#include "../../k_kart.h" +#include "../../k_hitlag.h" +#include "../../k_battle.h" +#include "../../k_boss.h" +#include "../../k_respawn.h" +#include "../../s_sound.h" +#include "../../g_game.h" +#include "../../r_main.h" // R_PointToAngle2, R_PointToDist2 + +#define PUYOARCMULTIPLIER (3) + +typedef enum +{ + BLENDEYE_RESTART = 0, + BLENDEYE_LOADAMMO, + BLENDEYE_WAITAMMO, + BLENDEYE_BLENDING, + BLENDEYE_EXPLODING, + BLENDEYE_PINCH_THROWN, + BLENDEYE_PINCH_BOBBING, + BLENDEYE_PINCH_WHISKING, + BLENDEYE_PINCH_DRILLING, + BLENDEYE_PINCH_EXPLODING, + BLENDEYE_PINCH_BLOWNUP, +} blendeye_phase_e; + +#define EYECOLOR \ + (encoremode \ + ? SKINCOLOR_JAWZ \ + : SKINCOLOR_KETCHUP \ + ) + +#define K_SpawnBlendEyeExplosion(mo) \ + K_SpawnMineExplosion( \ + mo, \ + EYECOLOR, \ + 0 \ + ) + +/// - MAIN BODY - /// + +void VS_BlendEye_Init(mobj_t *mobj) +{ + UINT8 i; + mobj_t *prev, *newmo; + angle_t ang = 0; + + // necessary preamble + P_SetScale(mobj, (mobj->destscale = mobj->scale*2)); + + // spawn the glass + prev = mobj; + for (i = 0; i < 8; i++) + { + ang = mobj->angle + FixedAngle(i*360*FRACUNIT/8); + newmo = P_SpawnMobjFromMobj(mobj, + P_ReturnThrustX(mobj, ang, 39*FRACUNIT), + P_ReturnThrustY(mobj, ang, 39*FRACUNIT), + 44*FRACUNIT, + MT_BLENDEYE_GLASS); + newmo->angle = ang - ANGLE_90; + P_SetTarget(&prev->hnext, newmo); + prev = newmo; + } + + // spawn the generators + prev = mobj; + ang = mobj->angle + ANGLE_45; + UINT8 numgenerators = (encoremode ? 4 : 3); + for (i = 0; i < numgenerators; i++) + { + newmo = P_SpawnMobjFromMobj(mobj, + P_ReturnThrustX(mobj, ang, 62*FRACUNIT), + P_ReturnThrustY(mobj, ang, 62*FRACUNIT), + 0, + MT_BLENDEYE_GENERATOR); + P_SetScale(newmo, newmo->destscale = (3*newmo->destscale)/4); + newmo->momz = -512*FRACUNIT; // cba to hardcode a diff. z coord + newmo->angle = ang - ANGLE_90; + P_SetTarget(&newmo->tracer, mobj); + P_SetTarget(&prev->hprev, newmo); + prev = newmo; + if (i == 1 && numgenerators == 3) + ang -= ANGLE_135; + else + ang -= ANGLE_90; + } + + // spawn the eye... + { + P_SetTarget(&mobj->tracer, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_BLENDEYE_EYE)); + P_SetTarget(&mobj->tracer->target, mobj); + mobj->tracer->angle = mobj->angle - ANGLE_90; + // ...and shield! + prev = mobj->tracer; + for (i = 0; i < 8; i++) + { + ang = FixedAngle(i*360*FRACUNIT/8); + newmo = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_BLENDEYE_SHIELD); + newmo->angle = mobj->angle + ang-ANGLE_90; + newmo->movedir = ang; + if (i == 0) + P_SetMobjState(newmo, S_INVISIBLE); + else if (i == 1) + P_SetMobjState(newmo, S_BLENDEYE_SHIELD_R); + else if (i == 7) + P_SetMobjState(newmo, S_BLENDEYE_SHIELD_L); + newmo->cvmem = i; + P_SetTarget(&prev->hnext, newmo); + prev = newmo; + } + } + + // initialise other important stuff + if (encoremode) + mobj->health++; + mobj->tracer->health = mobj->health; + mobj->health += numgenerators; + mobj->tracer->cvmem = mobj->health; + + mobj->movedir = BLENDEYE_RESTART; + mobj->movecount = TICRATE; + + mobj->reactiontime = mobj->z + (3*mobj->height)/2; + + { + const char *enemyname, *subtitle; + if (!encoremode) + { + enemyname = "Blend Eye"; + subtitle = "Here to serve"; + } + else + { + enemyname = "Mean Blend Eye"; + subtitle = "Promoted to assistant manager"; + } + + K_InitBossHealthBar(enemyname, subtitle, 0, mobj->tracer->health*(FRACUNIT/mobj->health), mobj->tracer->cvmem); + K_DeclareWeakspot(mobj, SPOT_NONE, EYECOLOR, true); + } +} + +static mobj_t *sourceofmurder; + +static inline BlockItReturn_t PIT_MurderPuyos(mobj_t *thing) +{ + if (thing->type != MT_BLENDEYE_PUYO) + return BMIT_CONTINUE; // not a puyo + + if (thing->z < sourceofmurder->z) + return BMIT_CONTINUE; // too low + + P_InstaThrust(thing, R_PointToAngle2(sourceofmurder->x, sourceofmurder->y, thing->x, thing->y), (thing->z - sourceofmurder->z)/4); + thing->momz = (thing->z - sourceofmurder->z)/16; + P_KillMobj(thing, sourceofmurder, sourceofmurder, DMG_NORMAL); + + return BMIT_CONTINUE; // Indiscriminate +} + +static void VS_BlendEye_MurderPuyos(mobj_t *mobj) +{ + INT32 bx, by, xl, xh, yl, yh; + + sourceofmurder = mobj; + + yh = (unsigned)(mobj->y + (mobj->radius + MAXRADIUS) - bmaporgy)>>MAPBLOCKSHIFT; + yl = (unsigned)(mobj->y - (mobj->radius + MAXRADIUS) - bmaporgy)>>MAPBLOCKSHIFT; + xh = (unsigned)(mobj->x + (mobj->radius + MAXRADIUS) - bmaporgx)>>MAPBLOCKSHIFT; + xl = (unsigned)(mobj->x - (mobj->radius + MAXRADIUS) - bmaporgx)>>MAPBLOCKSHIFT; + + BMBOUNDFIX (xl, xh, yl, yh); + + for (by = yl; by <= yh; by++) + for (bx = xl; bx <= xh; bx++) + P_BlockThingsIterator(bx, by, PIT_MurderPuyos); +} + +void VS_BlendEye_Thinker(mobj_t *mobj) +{ + boolean deathwatch = false; // may be useful for later + + // Init. + { + if (!mobj->tracer) + { + CONS_Alert(CONS_ERROR, "VS_BlendEye_Thinker: Reached thinker but init failed"); + P_RemoveMobj(mobj); + return; + } + + if (!mobj->tracer->tracer) + { + P_SetTarget(&mobj->tracer->tracer, VS_GetArena(mobj->thing_args[0])); + if (!mobj->tracer->tracer) + { + P_RemoveMobj(mobj); + return; + } + } + } + + mobj->flags2 &= ~MF2_FRET; + + // Targeting. + if (P_MobjWasRemoved(mobj->target) + || !mobj->target->player + || mobj->target->player->respawn.state != RESPAWNST_NONE + || mobj->target->health <= 0) + { + P_SupermanLook4Players(mobj); + deathwatch = (P_MobjWasRemoved(mobj->target) + || !mobj->target->player + || mobj->target->player->respawn.state != RESPAWNST_NONE + || mobj->target->health <= 0); + } + + INT32 i; + fixed_t tiltstrength; + + // phases and attacks + switch (mobj->movedir) + { + case BLENDEYE_LOADAMMO: + { + // SPAWN YOUR AMMO + if ((mobj->movecount % 5) == 0) + { + i = (mobj->movecount/5); + angle_t ang = mobj->tracer->cusval + FixedAngle(i*360*FRACUNIT/3); + fixed_t h = mobj->z + mobj->height - 4*mapobjectscale; + fixed_t dist = mobj->cvmem - 2*FRACUNIT; + if (i <= 3) + ang += ANGLE_180; + + mobj_t *ammo = P_SpawnMobjFromMobj(mobj, + P_ReturnThrustX(mobj, ang, dist), + P_ReturnThrustY(mobj, ang, dist), + 256*FRACUNIT, + MT_BLENDEYE_PUYO); + P_SetScale(ammo, (ammo->destscale *= 2)); + + if (i <= 3) + h += ammo->height; + else + ammo->cusval = (i-3)*TICRATE; + + P_SetObjectMomZ(ammo, -10*FRACUNIT, false); + ammo->angle = ang; + ammo->cvmem = dist; + ammo->movefactor = (TICRATE/2) + P_RandomKey(PR_MOVINGTARGET, TICRATE/8); + P_SetTarget(&ammo->tracer, mobj); + ammo->extravalue1 = h; + ammo->flags2 |= MF2_STRONGBOX; + } + + if ((--mobj->movecount) == 0) + { + mobj->movedir = BLENDEYE_WAITAMMO; + mobj->movecount = TICRATE; + } + + break; + } + case BLENDEYE_WAITAMMO: + { + // WAIT #1 + if (deathwatch) + ; + else if ((--mobj->movecount) == 0) + { + mobj->movedir = BLENDEYE_BLENDING; + mobj->movecount = 4*TICRATE; + mobj->extravalue1 = mobj->reactiontime; + mobj->flags2 |= MF2_AMBUSH; + S_StartSound(NULL, sfx_befan1); + if (mobj->tracer) + P_SetMobjState(mobj->tracer, mobj->tracer->info->raisestate); + } + + break; + } + case BLENDEYE_BLENDING: + { + // BLENDING IN ACTION + if (!S_SoundPlaying(NULL, sfx_befan1) && !S_SoundPlaying(NULL, sfx_befan2)) + S_StartSound(NULL, sfx_befan2); + + tiltstrength = 5; + if (leveltime & 1) + tiltstrength = -tiltstrength; + + mobj->rollangle = FixedAngle(tiltstrength*FRACUNIT); + + if ((--mobj->movecount) == 0) + { + mobj->rollangle = 0; + mobj->movedir = 0; + mobj->movecount = 3*TICRATE/2; + mobj->flags2 &= ~MF2_AMBUSH; + //S_StopSoundByID(NULL, sfx_befan2); + //S_StartSound(NULL, sfx_s3k85); + if (mobj->tracer) + P_SetMobjState(mobj->tracer, mobj->tracer->info->spawnstate); + } + + break; + } + case BLENDEYE_EXPLODING: + { + // BLOWS UP!? + if ((--mobj->movecount) == 0) + { + // PINCH TRANSITION BEGIN ! + mobj->rollangle = 0; + mobj->movedir = BLENDEYE_PINCH_THROWN; + mobj_t *ref = mobj->hnext; + mobj_t *refnext = NULL; + while (ref) + { + refnext = ref->hnext; + P_KillMobj(ref, mobj, mobj, DMG_NORMAL); + ref = refnext; + } + S_StartSound(NULL, sfx_shattr); + if (mobj->tracer) + { + boolean lastturned = false; + ref = mobj->tracer->hnext; + P_SetTarget(&mobj->tracer->hnext, NULL); + while (ref) + { + if (ref->cvmem == 0) + ref->flags |= MF_NOCLIPTHING; + else if (ref->cvmem == 1) + P_SetMobjState(ref, S_BLENDEYE_SHIELD_BUSTED_R); + else if (ref->cvmem == 7) + P_SetMobjState(ref, S_BLENDEYE_SHIELD_BUSTED_L); + else if (lastturned == true) + lastturned = false; + else if (P_RandomChance(PR_DECORATION, FRACUNIT/2)) + { + P_SetMobjState(ref, S_BLENDEYE_SHIELD_BUSTED); + if (P_RandomChance(PR_DECORATION, FRACUNIT/2)) + { + ref->frame++; + } + lastturned = true; + } + ref = ref->hnext; + } + mobj->tracer->renderflags |= RF_DONTDRAW; + mobj->flags &= ~MF_NOGRAVITY; + mobj->flags2 |= MF2_FRET; + + mobj_t *boomo = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_FZEROBOOM); + boomo->extravalue1 = 2; + P_SetScale(boomo, boomo->scale/3); + boomo->destscale = mapobjectscale; + K_SpawnBlendEyeExplosion(boomo); + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + P_FlashPal(&players[i], PAL_WHITE, 1); + } + S_StartSound(NULL, sfx_s3k4e); + } + + VS_BlendEye_MurderPuyos(mobj); + + mobj->momz = 12*mobj->scale; + P_SetMobjState(mobj, mobj->info->raisestate); + mobj->extravalue1 = mobj->reactiontime; + if (!deathwatch) + { + mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y); + } + mobj->movecount = P_RandomChance(PR_MOVINGTARGET, FRACUNIT/2) ? 1 : -1; + P_InstaThrust(mobj, mobj->angle + mobj->movecount*ANGLE_90, 4*mobj->scale); + } + else if ((mobj->movecount % 3) == 0) + { + // SPAWN A MINI-POP + angle_t rot = FixedAngle(P_RandomKey(PR_EXPLOSION, 360)*FRACUNIT); + fixed_t offs = P_RandomKey(PR_EXPLOSION, (mobj->info->height - mobjinfo[MT_SONIC3KBOSSEXPLODE].height)/FRACUNIT)*FRACUNIT; + P_SpawnMobjFromMobj(mobj, + P_ReturnThrustX(mobj, rot, 56*FRACUNIT), + P_ReturnThrustY(mobj, rot, 56*FRACUNIT), + offs, + MT_SONIC3KBOSSEXPLODE); + S_StartSound(NULL, sfx_s3kb4); + mobj_t *ref = mobj->hnext; + while (ref) + { + P_SetMobjState(ref, ref->info->painstate); + ref = ref->hnext; + } + + tiltstrength = 5; + if (((mobj->movecount/2) % 3) == 0) + tiltstrength = -tiltstrength; + + mobj->rollangle = FixedAngle(tiltstrength*FRACUNIT); + } + else + { + mobj_t *ref = mobj->hnext; + while (ref) + { + P_SetMobjState(ref, ref->info->spawnstate); + ref = ref->hnext; + } + mobj->rollangle = 0; + } + + return; + } + case BLENDEYE_PINCH_THROWN: + { + // WOOOAAAAH, MY HEAD'S SPINNING + mobj->rollangle += (mobj->movecount*ANG20); + if (mobj->momz < 0 && mobj->z < mobj->extravalue1) + { + mobj->movedir = BLENDEYE_PINCH_BOBBING; + mobj->flags |= MF_NOGRAVITY|MF_SHOOTABLE; + mobj->flags &= ~MF_NOCLIPTHING; + mobj->flags2 &= ~MF2_FRET; + mobj->momx = mobj->momy = mobj->momz = 0; + mobj->rollangle = 0; + mobj->movecount = -TICRATE/2; + P_SetMobjState(mobj, mobj->info->spawnstate); + if (mobj->tracer) + mobj->tracer->renderflags &= ~RF_DONTDRAW; + + if (!deathwatch) + { + mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y); + } + + P_SetTarget(&mobj->hprev, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY)); + P_SetTarget(&mobj->hprev->target, mobj); + mobj->hprev->flags |= MF_DONTENCOREMAP; + mobj->hprev->threshold |= OV_DONTROLL; + P_SetMobjState(mobj->hprev, S_BLENDEYE_FLAME); + S_StartSound(NULL, sfx_s3k4f); + mobj->cvmem = mobj->info->radius + 4*FRACUNIT; + } + + //return; + break; + } + case BLENDEYE_PINCH_BOBBING: + case BLENDEYE_PINCH_WHISKING: + { + // BOBBING UP AND DOWN + fixed_t bobsize = 6*mobj->scale; + fixed_t bob = mobj->movecount; + bob = -P_ReturnThrustY(mobj, FixedAngle(bob*10*FRACUNIT), bobsize); + mobj->movecount++; + if (mobj->movedir == BLENDEYE_PINCH_WHISKING) + { + if (encoremode) // RUN FOR LONGER + mobj->extravalue1 -= (mobj->scale/4); + else + mobj->extravalue1 -= (2*mobj->scale/7); + + if (!S_SoundPlaying(NULL, sfx_befan1) && !S_SoundPlaying(NULL, sfx_befan2)) + S_StartSound(NULL, sfx_befan2); + + tiltstrength = 5; + if (leveltime & 1) + tiltstrength = -tiltstrength; + mobj->rollangle = FixedAngle(tiltstrength*FRACUNIT); + + if (deathwatch) + { + mobj->movedir = BLENDEYE_PINCH_BOBBING; + mobj->movecount = 0; + mobj->rollangle = 0; + mobj->extravalue1 = mobj->reactiontime; + mobj->flags2 &= ~MF2_AMBUSH; + if (mobj->tracer && mobj->tracer->hprev) + { + P_SetMobjState(mobj->tracer->hprev, S_BLENDEYE_EGGBEATER); + P_SetMobjState(mobj->tracer, mobj->tracer->info->spawnstate); + } + } + else if (mobj->tracer && mobj->tracer->tracer && mobj->tracer->hprev) + { + // MOVE TOWARDS TARGET + angle_t mompoint = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y); // replace first two with mobj->tracer->hnext->x and ->y for alt behaviour + fixed_t mommag = FixedHypot(mobj->target->x - mobj->x, mobj->target->y - mobj->y); + fixed_t bestspeed = 5*mobj->scale; + fixed_t accel = (mobj->scale/2); + boolean forcegoaround = false; + angle_t test = R_PointToAngle2(mobj->tracer->tracer->x, mobj->tracer->tracer->y, mobj->x, mobj->y) + - R_PointToAngle2(mobj->tracer->tracer->x, mobj->tracer->tracer->y, mobj->target->x, mobj->target->y); + if (test >= ANGLE_180) + test = InvAngle(test); + + if (test > ANGLE_90) + { + bestspeed += (AngleFixed(test-ANGLE_90)/(encoremode ? 20 : 30)); + forcegoaround = true; + } + + if (mobj->tracer->hprev->extravalue1 + accel < bestspeed) + mobj->tracer->hprev->extravalue1 += accel; + else + mobj->tracer->hprev->extravalue1 = bestspeed; + + if (mommag > mobj->tracer->hprev->extravalue1) + mommag = mobj->tracer->hprev->extravalue1; + + if (mommag) + { + fixed_t *predict = VS_PredictAroundArena(mobj->tracer->tracer, mobj, mommag, mompoint, mobj->radius, forcegoaround, FRACUNIT); + P_MoveOrigin(mobj, predict[0], predict[1], mobj->z); + } + + // Slight bonus lifting hazard for those who get too close! + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + + if (players[i].spectator) + continue; + + if (P_MobjWasRemoved(players[i].mo)) + continue; + + if (players[i].mo->health == 0 || players[i].mo->hitlag) + continue; + + if (players[i].mo->z > mobj->z) + continue; + + if (FixedHypot(players[i].mo->x - mobj->x, players[i].mo->y - mobj->y) > (mobj->radius + 4*mapobjectscale)) + continue; + + players[i].mo->momz += 4*mapobjectscale; + } + } + } + else if (!mobj->tracer->hprev && mobj->movecount == TICRATE/3) + { + // SPAWN THE EGGBEATER + P_SetTarget(&mobj->tracer->hprev, + P_SpawnMobjFromMobj(mobj, 0, 0, + -mobjinfo[MT_BLENDEYE_EGGBEATER].height, + MT_BLENDEYE_EGGBEATER + ) + ); + P_SetTarget(&mobj->tracer->hprev->tracer, mobj); + mobj->tracer->hprev->extravalue1 = 0; + S_StartSound(NULL, sfx_cdfm39); + + // Gainax sharpness + mobj_t *shwing = P_SpawnMobjFromMobj(mobj->tracer->hprev, 0, 0, 0, MT_OVERLAY); + shwing->threshold = OV_DONTSCREENOFFSET; + P_SetTarget(&shwing->target, mobj->tracer->hprev); + P_SetMobjState(shwing, S_EMERALDFLARE1); + shwing->fuse = (TICRATE - mobj->movecount); + shwing->spritexoffset = -24*FRACUNIT; + shwing->spriteyoffset = 14*FRACUNIT; + + // Dr. Robontik's Rage Stagers ?! + mobj_t *angery = P_SpawnMobjFromMobj(mobj->tracer, + P_ReturnThrustX(mobj, mobj->angle, FRACUNIT), + P_ReturnThrustY(mobj, mobj->angle, FRACUNIT), + 15*FRACUNIT, + MT_GHOST + ); + + P_SetMobjState(angery, S_SPIKEDLENS); + P_SetScale(angery, angery->scale/2); + angery->height = 1; // prevent z correction in P_MobjScaleThink + angery->destscale *= 4; + angery->fuse = TICRATE/7; // 5 + angery->scalespeed = ((angery->destscale - angery->scale)/angery->fuse); + angery->colorized = true; + angery->color = EYECOLOR; + angery->flags2 |= MF2_BOSSNOTRAP; // Quicker fadeout + } + else if (mobj->movecount >= TICRATE && !deathwatch) + { + mobj->movedir = BLENDEYE_PINCH_WHISKING; + if (mobj->tracer) + { + P_SetMobjState(mobj->tracer, mobj->tracer->info->raisestate); + if (mobj->tracer->hprev) + P_SetMobjState(mobj->tracer->hprev, S_BLENDEYE_EGGBEATER_SPIN); + + mobj->tracer->cusval = (encoremode ? 6 : 5); + } + S_StartSound(NULL, sfx_befan1); + mobj->flags2 |= MF2_AMBUSH; + } + if (mobj->z < mobj->extravalue1 - bobsize) + mobj->z = (2*mobj->z + mobj->extravalue1 + bob)/3; + else + mobj->z = mobj->extravalue1 + bob; + + if (P_MobjWasRemoved(mobj->hprev)) + ; + else if (mobj->z <= mobj->extravalue1) + { + if ((mobj->hprev->state-states) != S_BLENDEYE_FLAME) + P_SetMobjState(mobj->hprev, S_BLENDEYE_FLAME); + + if (!S_SoundPlaying(mobj->hprev, sfx_s3k7f)) + S_StartSound(mobj->hprev, sfx_s3k7f); + } + else + { + if ((mobj->hprev->state-states) != S_INVISIBLE) + P_SetMobjState(mobj->hprev, S_INVISIBLE); + } + + if (mobj->tracer && mobj->tracer->hprev) + { + if ((mobj->extravalue1 + bob) <= (mobj->floorz + mobj->tracer->hprev->height)) + { + mobj->movedir = BLENDEYE_PINCH_DRILLING; + mobj->movecount = 0; + S_StopSoundByID(NULL, sfx_befan2); + S_StartSound(NULL, sfx_s3k85); + mobj->flags2 &= ~MF2_AMBUSH; + if (mobj->hprev) + P_SetMobjState(mobj->hprev, S_INVISIBLE); + mobj->tracer->hprev->flags &= ~MF_PAIN; + } + else if (deathwatch) + ; + else if (mobj->tracer->cusval && !(mobj->movecount % (TICRATE))) + { + angle_t ang = FixedAngle(P_RandomKey(PR_MOVINGTARGET, 360)*FRACUNIT); + fixed_t dist = 2*FRACUNIT; + mobj_t *ammo = P_SpawnMobjFromMobj(mobj, + P_ReturnThrustX(mobj, ang, dist), + P_ReturnThrustY(mobj, ang, dist), + 0, + MT_BLENDEYE_PUYO); + P_SetOrigin(ammo, ammo->x, ammo->y, ammo->floorz+1); + P_SetScale(ammo, (ammo->destscale *= 2)); + ammo->angle = ang; + ammo->cvmem = dist; + ammo->movefactor = (TICRATE/2) + P_RandomKey(PR_MOVINGTARGET, TICRATE/8); + P_SetTarget(&ammo->tracer, mobj); + ammo->extravalue1 = mobj->floorz; + if (mobj->tracer->cusval & 1) + ammo->flags2 |= MF2_BOSSNOTRAP; + mobj->tracer->cusval--; + } + } + + break; + } + case BLENDEYE_PINCH_DRILLING: + { + // ACCIDENTIALLY DRILLING INTO THE GROUND + if (mobj->z > mobj->floorz+FRACUNIT) + { + tiltstrength = 10; + if (leveltime & 1) + tiltstrength = -tiltstrength; + mobj->rollangle = FixedAngle(tiltstrength*FRACUNIT); + mobj->angle -= (ANG10+ANG20); + mobj->z = (2*mobj->z + mobj->floorz)/3; + mobj->tracer->cusval = FixedAngle(P_RandomKey(PR_MOVINGTARGET, 360)*FRACUNIT); + if (!mobj->tracer->hprev) + ; + else for (i = 0; i < 6; i++) + { + angle_t angle = mobj->tracer->cusval + FixedAngle(i*360*FRACUNIT/12); + fixed_t momx = P_ReturnThrustX(mobj, angle, mobj->tracer->hprev->radius); + fixed_t momy = P_ReturnThrustY(mobj, angle, mobj->tracer->hprev->radius); + mobj_t *dustmo = P_SpawnMobjFromMobj( + mobj->tracer->hprev, + 0, 0, 0, + (encoremode + ? MT_BLENDEYE_PUYO_DUST + : MT_BLENDEYE_PUYO_DUST_COFFEE + ) + ); + P_SetScale(dustmo, (dustmo->destscale *= 2)); + dustmo->momx = momx/(3-(i & 1)); + dustmo->momy = momy/(3-(i & 1)); + dustmo->momz = (mobj->z - mobj->floorz)/(2+(i & 1)); + } + } + else + { + mobj->z = mobj->floorz; + if ((mobj->tracer->hprev->state-states) != S_BLENDEYE_EGGBEATER) + { + mobj->rollangle = 0; + P_SetMobjState(mobj->tracer->hprev, S_BLENDEYE_EGGBEATER); + P_SetMobjState(mobj->tracer, mobj->tracer->info->spawnstate); + } + + if ((mobj->movecount == TICRATE/2) && !deathwatch) + K_DeclareWeakspot(mobj, SPOT_WEAK, SKINCOLOR_EMERALD, false); + + if ((mobj->movecount > 2*TICRATE) && (leveltime & 1)) + { + if (mobj->hprev && (mobj->hprev->state-states) != S_BLENDEYE_FLAME) + { + P_SetMobjState(mobj->hprev, S_BLENDEYE_FLAME); + S_StartSound(NULL, sfx_s3k4f); + } + mobj->z += FRACUNIT; + if (!S_SoundPlaying(NULL, sfx_s3k69)) + S_StartSound(NULL, sfx_s3k69); + } + + if ((++mobj->movecount) == 3*TICRATE) + { + mobj->movedir = BLENDEYE_PINCH_BOBBING; + mobj->movecount = 0; + mobj->rollangle = 0; + mobj->extravalue1 = mobj->reactiontime; + S_StopSoundByID(NULL, sfx_s3k69); // it's like poetry + S_StartSound(NULL, sfx_mbs53); + if (mobj->tracer) + { + P_SetMobjState(mobj->tracer, mobj->tracer->info->spawnstate); + if (mobj->tracer->hprev && !(mobj->tracer->hprev->flags & MF_PAIN)) + { + mobj->tracer->hprev->flags |= MF_PAIN; + mobj->tracer->hprev->extravalue1 = 0; + if (mobj->tracer->hprev->z != mobj->floorz) + ; + else for (i = 0; i < 6; i++) + { + angle_t angle = mobj->tracer->cusval + FixedAngle(i*360*FRACUNIT/12); + fixed_t momx = P_ReturnThrustX(mobj, angle, mobj->tracer->hprev->radius); + fixed_t momy = P_ReturnThrustY(mobj, angle, mobj->tracer->hprev->radius); + mobj_t *dustmo = P_SpawnMobjFromMobj( + mobj->tracer->hprev, + 0, 0, 0, + (encoremode + ? MT_BLENDEYE_PUYO_DUST + : MT_BLENDEYE_PUYO_DUST_COFFEE + ) + ); + P_SetScale(dustmo, (dustmo->destscale *= 2)); + dustmo->momx = momx/(3-(i & 1)); + dustmo->momy = momy/(3-(i & 1)); + dustmo->momz = (mobj->tracer->hprev->radius)/(2+(i & 1)); + } + } + } + } + } + + break; + } + case BLENDEYE_PINCH_EXPLODING: + { + // SHE'S DYING... + if ((--mobj->movecount) == 0) + { + // ALL DONE + { + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + if (players[i].spectator) + continue; + if (players[i].pflags & PF_NOCONTEST) + continue; + + P_DoAllPlayersExit(0, true); + break; + } + } + + mobj->movedir = BLENDEYE_PINCH_BLOWNUP; + + fixed_t offs = P_RandomChance(PR_EXPLOSION, FRACUNIT/2) + ? ANGLE_135 + : InvAngle(ANGLE_135); + P_InstaThrust(mobj, mobj->angle + offs, 8*mapobjectscale); + mobj->momz = 16*mapobjectscale; + mobj->flags |= MF_NOCLIPHEIGHT; + mobj->flags &= ~MF_NOGRAVITY; + mobj->z++; + + P_InstaThrust(mobj->tracer, mobj->angle, 8*mapobjectscale); + mobj->tracer->momz = 12*mapobjectscale; + mobj->tracer->flags |= MF_NOCLIPHEIGHT; + mobj->tracer->flags &= ~MF_NOGRAVITY; + mobj->tracer->z++; + + if (mobj->tracer->hprev) + { + P_InstaThrust(mobj->tracer->hprev, mobj->angle - offs, 8*mapobjectscale); + mobj->tracer->hprev->momz = 6*mapobjectscale; + mobj->tracer->hprev->flags |= MF_NOCLIPHEIGHT; + mobj->tracer->hprev->flags &= ~MF_NOGRAVITY; + mobj->tracer->hprev->z++; + P_SetScale(mobj->hprev, mapobjectscale/4); + K_SpawnBlendEyeExplosion(mobj->hprev); + S_StartSound(NULL, sfx_s3k4e); + } + + P_RemoveMobj(mobj->hprev); + P_SetTarget(&mobj->hprev, NULL); + } + else if ((mobj->movecount % 2) == 0) + { + // SPAWN A MINI-POP (2) + angle_t rot = FixedAngle(P_RandomKey(PR_EXPLOSION, 360)*FRACUNIT); + fixed_t offs = P_RandomKey(PR_EXPLOSION, (mobj->info->height - mobjinfo[MT_SONIC3KBOSSEXPLODE].height)/FRACUNIT)*FRACUNIT; + P_SpawnMobjFromMobj(mobj, + P_ReturnThrustX(mobj, rot, mobj->info->radius), + P_ReturnThrustY(mobj, rot, mobj->info->radius), + offs, + MT_SONIC3KBOSSEXPLODE); + S_StartSound(NULL, sfx_s3kb4); + tiltstrength = 5; + if (((mobj->movecount/2) % 2) == 0) + tiltstrength = -tiltstrength; + + mobj->rollangle = FixedAngle(tiltstrength*FRACUNIT); + } + else + mobj->rollangle = 0; + + return; + } + case BLENDEYE_PINCH_BLOWNUP: + { + // CURTAINS FOR YOU + if (mobj->z != mobj->floorz) + mobj->rollangle += ANG20; + else + mobj->rollangle = ANGLE_180; + + if (mobj->tracer->hprev->z != mobj->floorz) + mobj->tracer->hprev->rollangle += ANG20; + else + mobj->tracer->hprev->rollangle = (mobj->tracer->hprev->rollangle >= ANGLE_180) + ? ANGLE_135 + : InvAngle(ANGLE_135); + + mobj->renderflags ^= RF_DONTDRAW; + mobj->tracer->renderflags ^= RF_DONTDRAW; + if (mobj->tracer->hprev) + mobj->tracer->hprev->renderflags ^= RF_DONTDRAW; + + return; + } + default: + { + // RESTART CYCLE + if (deathwatch) + ; + else if ((--mobj->movecount) == 0) + { + mobj->movedir = BLENDEYE_LOADAMMO; + mobj->movecount = 6*5; + mobj->tracer->cusval = FixedAngle(P_RandomKey(PR_MOVINGTARGET, 360)*FRACUNIT); + mobj->cvmem = 24*FRACUNIT; + } + else if (mobj->movecount == TICRATE) + { + mobj_t *ref = mobj->hprev; + while (ref) + { + if (ref->health > 0) + K_DeclareWeakspot(ref, SPOT_WEAK, SKINCOLOR_EMERALD, false); + ref = ref->hprev; + } + } + + break; + } + } + + // Look around. + if (mobj->target) // !deathwatch + { + fixed_t x, y; + INT32 angledelta = AngleDeltaSigned(R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y), mobj->angle)/4; + const INT32 maxdelta = (ANGLE_45/2); + + if (angledelta > maxdelta) + angledelta = maxdelta; + else if (angledelta < -maxdelta) + angledelta = -maxdelta; + + // Before angle update, move the shield. + if (mobj->tracer && mobj->tracer->hnext && mobj->movedir < BLENDEYE_EXPLODING) + { + mobj_t *ref = mobj->tracer->hnext; + while (ref) + { + x = mobj->x + P_ReturnThrustX(mobj, mobj->tracer->movedir + ref->movedir, 40*mobj->scale); + y = mobj->y + P_ReturnThrustY(mobj, mobj->tracer->movedir + ref->movedir, 40*mobj->scale); + P_MoveOrigin(ref, x, y, mobj->z); + ref->angle = mobj->tracer->movedir + ref->movedir - ANGLE_90; + ref = ref->hnext; + } + mobj->tracer->movedir = mobj->angle; + } + + if (mobj->movedir != BLENDEYE_PINCH_DRILLING || mobj->movecount > TICRATE/2) + mobj->angle += angledelta; + + // And after, move the eye. + if (mobj->tracer) + { + x = mobj->x + P_ReturnThrustX(mobj, mobj->angle, 38*mobj->scale); + y = mobj->y + P_ReturnThrustY(mobj, mobj->angle, 38*mobj->scale); + P_MoveOrigin(mobj->tracer, x, y, mobj->z + (mobj->height - mobj->tracer->height)/2); + mobj->tracer->angle = mobj->angle - ANGLE_90; + if (mobj->tracer->hprev) + { + P_MoveOrigin(mobj->tracer->hprev, mobj->x, mobj->y, max(mobj->floorz, mobj->z - (mobj->tracer->hprev->height))); + } + } + } +} + +boolean VS_BlendEye_Touched(mobj_t *special, mobj_t *toucher) +{ + if (toucher->hitlag > 0) + return false; + + fixed_t thrust = FixedHypot(toucher->momx, toucher->momy); + angle_t ang = R_PointToAngle2(special->x, special->y, toucher->x - toucher->momx, toucher->y - toucher->momy); + P_InstaThrust(toucher, ang + ANGLE_90, P_ReturnThrustX(toucher, ang + ANGLE_90, thrust)); + thrust = P_ReturnThrustX(toucher, ang, thrust); + if (thrust < 4*mapobjectscale) + thrust = (4*mapobjectscale); + P_Thrust(toucher, ang, thrust); + return true; +} + +void VS_BlendEye_Damage(mobj_t *mobj, mobj_t *inflictor, mobj_t *source, INT32 damage) +{ + (void)inflictor; + + S_StartSound(NULL, mobj->info->painsound); + K_UpdateBossHealthBar((mobj->health - damage)*(FRACUNIT/mobj->tracer->cvmem), 8); + if (mobj->tracer) + { + if (mobj->movedir == BLENDEYE_PINCH_BOBBING) + { + if (mobj->hprev) + P_SetMobjState(mobj->hprev, S_INVISIBLE); + } + else if (mobj->movedir == BLENDEYE_PINCH_WHISKING) + { + mobj->movedir = BLENDEYE_PINCH_BOBBING; + mobj->movecount = 0; + mobj->rollangle = 0; + mobj->flags2 &= ~MF2_AMBUSH; + mobj->extravalue1 = mobj->reactiontime; + if (mobj->hprev && (mobj->hprev->state-states) != S_BLENDEYE_FLAME) + { + P_SetMobjState(mobj->hprev, S_BLENDEYE_FLAME); + S_StartSound(NULL, sfx_s3k4f); + } + if (mobj->tracer->hprev) + { + P_SetMobjState(mobj->tracer->hprev, S_BLENDEYE_EGGBEATER); + mobj->tracer->hprev->flags |= MF_PAIN; + P_SetMobjState(mobj->tracer, mobj->tracer->info->spawnstate); + } + } + else if (mobj->movedir == BLENDEYE_PINCH_DRILLING) + { + if (mobj->tracer->hprev) + mobj->tracer->hprev->flags &= ~MF_PAIN; + + mobj->movecount = (2*TICRATE)+(TICRATE/2); + P_SetMobjState(mobj->tracer, mobj->tracer->info->spawnstate); + } + else if ((mobj->health - damage) == mobj->tracer->health) //pre-pinch + { + mobj->flags2 &= ~MF2_AMBUSH; + mobj->rollangle = 0; + mobj->movedir = BLENDEYE_EXPLODING; + mobj->movecount = 2*TICRATE; + P_SetMobjState(mobj->tracer, mobj->tracer->info->spawnstate); + } + } + + // make the glass stress + mobj_t *ref = mobj->hnext; + while (ref) + { + P_SetMobjState(ref, ref->info->painstate); + ref = ref->hnext; + } + + if (source && source->player) + { + P_AddPlayerScore(source->player, 1); + K_SpawnBattlePoints(source->player, NULL, 1); + } +} + +void VS_BlendEye_Death(mobj_t *mobj) +{ + mobj->flags2 &= ~MF2_AMBUSH; + mobj->rollangle = 0; + mobj->movedir = BLENDEYE_PINCH_EXPLODING; + mobj->movecount = 3*TICRATE/2; + + if (mobj->tracer) + { + P_SetMobjState(mobj->tracer, mobj->tracer->info->spawnstate); + mobj->tracer->flags |= MF_NOCLIP|MF_NOCLIPTHING; + + if (mobj->hprev) + mobj->tracer->hprev->flags |= MF_NOCLIP|MF_NOCLIPTHING; + + if (mobj->tracer->hprev) + { + P_SetMobjState(mobj->tracer->hprev, S_BLENDEYE_EGGBEATER); + } + } + + if (mobj->hprev) + P_SetMobjState(mobj->hprev, S_INVISIBLE); + + mobj->flags |= MF_NOCLIP|MF_NOCLIPTHING; + + K_AddHitLag(mobj, 6, true); +} + +/// - AUXILLIARY OBJECTS - /// + +boolean VS_BlendEye_Eye_Thinker(mobj_t *mobj) +{ + if (P_MobjWasRemoved(mobj->target)) + { + P_RemoveMobj(mobj); + return false; + } + + if (mobj->target->hitlag) + { + P_InstaThrust(mobj, mobj->angle, 25*mobj->scale); + K_AddHitLag(mobj, mobj->target->hitlag, (mobj->target->eflags & MFE_DAMAGEHITLAG) == MFE_DAMAGEHITLAG); + return false; + } + + if (!(mobj->flags & MF_NOCLIPHEIGHT)) + { + mobj->momx = mobj->momy = 0; + } + + return true; +} + +void VS_BlendEye_Glass_Death(mobj_t *mobj) +{ + UINT8 i = 0; + fixed_t thrust = 30*mapobjectscale; + for (i = 0; i <= 8; i++) + { + fixed_t along = P_RandomRange(PR_DECORATION, -mobj->radius/FRACUNIT, mobj->radius/FRACUNIT)*FRACUNIT; + fixed_t up = (mobj->height - FixedMul(mobjinfo[MT_BATTLEBUMPER_DEBRIS].height, mobj->scale)); + if (up > 0) + up = P_RandomKey(PR_DECORATION, mobj->height/FRACUNIT)*FRACUNIT; + mobj_t *glassmo = P_SpawnMobj( + mobj->x + P_ReturnThrustX(mobj, mobj->angle, along), + mobj->y + P_ReturnThrustY(mobj, mobj->angle, along), + mobj->z + up, + MT_BATTLEBUMPER_DEBRIS); + glassmo->color = SKINCOLOR_WHITE; + glassmo->renderflags |= RF_ADD; + angle_t vang = R_PointToAngle2(0, mobj->z, 39*FRACUNIT, glassmo->z); + P_InstaThrust(glassmo, mobj->angle + ANGLE_90, P_ReturnThrustX(glassmo, vang, thrust)); + glassmo->momz = P_ReturnThrustX(glassmo, vang, thrust); + } +} + +void VS_BlendEye_Eggbeater_Touched(mobj_t *t1, mobj_t *t2) +{ + if (t2->hitlag || (t1->health <= 0 || t2->health <= 0)) + return; + + if (t1->z == t1->floorz) + return; + + INT32 minextravalue1 = -(P_IsObjectOnGround(t2) ? 5 : 15)*t1->scale; + if (t1->extravalue1 > minextravalue1) + t1->extravalue1 = minextravalue1; + + if ((t2->player->tumbleBounces > 0) && (t2->momz > 0)) + return; + + fixed_t thrust = FixedHypot(t2->momx, t2->momy); + if (thrust < 20*mapobjectscale) + thrust = 20*mapobjectscale; + + P_Thrust( + t2, + R_PointToAngle2(t1->x, t1->y, t2->x - t2->momx, t2->y - t2->momy), + thrust + ); +} + +void VS_BlendEye_Generator_DeadThinker(mobj_t *mobj) +{ + if (leveltime & 1) + return; + + if (P_RandomChance(PR_DECORATION, FRACUNIT/4)) + return; + + angle_t twist = mobj->angle; + fixed_t dist = 18*FRACUNIT; + fixed_t z = 26*FRACUNIT; + fixed_t fb = 6*FRACUNIT; + + if (P_RandomChance(PR_DECORATION, FRACUNIT/2)) + { + z = 42*FRACUNIT; + dist = 28*FRACUNIT; + twist += ANGLE_180; + fb = -6*FRACUNIT; + } + + if ((mobj->state-states) != S_BLENDEYE_GENERATOR_BUSTED_R) + twist += ANGLE_180; + + mobj_t *dust = P_SpawnMobjFromMobj( + mobj, + P_ReturnThrustX(mobj, twist, dist) + P_ReturnThrustY(mobj, twist, fb), + P_ReturnThrustY(mobj, twist, dist) + P_ReturnThrustX(mobj, twist, fb), + z, + encoremode + ? MT_BLENDEYE_PUYO_DUST + : MT_BLENDEYE_PUYO_DUST_COFFEE + ); + dust->angle = FixedAngle(P_RandomKey(PR_DECORATION, 360)*FRACUNIT); + P_InstaThrust(dust, dust->angle, 4*mapobjectscale); + dust->momz = 8*mapobjectscale; +} + +/// - PUYO HAZARDS - /// + +boolean VS_PuyoTouched(mobj_t *special, mobj_t *toucher) +{ + if (!special->health || !toucher->health) + return false; // too dead + + if (special->state-states < S_BLENDEYE_PUYO) + return false; // too small + + P_DamageMobj(toucher, special, special, 1, DMG_NORMAL); + + special->momx = 0; + special->momy = 0; + special->momz = 0; + + return true; +} + +// Basically a duplication of A_Boss5Jump. I'm not adding A_ action calls to header files. + +static void VS_PuyoJump(mobj_t *shot, mobj_t *target) +{ + fixed_t v; // Velocity to jump at + fixed_t a1, a2, aToUse; // Velocity squared + fixed_t g; // Gravity + fixed_t x; // Horizontal difference + INT32 x_int; // x! But in integer form! + fixed_t y; // Vertical difference (yes that's normally z in SRB2 shut up) + INT32 y_int; // y! But in integer form! + INT32 intHypotenuse; // x^2 + y^2. Frequently overflows fixed point, hence why we need integers proper. + fixed_t fixedHypotenuse; // However, we can work around that and still get a fixed-point number. + angle_t theta; // Angle of attack + // INT32 locvar1 = var1; + // INT32 locvar2 = var2; + + if (!shot || !target) + return; // Don't even bother if we've got nothing to aim at. + + // Scale with map + g = FixedMul(gravity, mapobjectscale); + + // Look up distance between shot and its target + x = FixedHypot(target->x - shot->x, target->y - shot->y); + // Look up height difference between shot and its target + y = target->z - shot->z; + + // Get x^2 + y^2. Have to do it in a roundabout manner, because this overflows fixed_t way too easily otherwise. + x_int = x>>FRACBITS; + y_int = y>>FRACBITS; + intHypotenuse = (x_int*x_int) + (y_int*y_int); + fixedHypotenuse = FixedSqrt(intHypotenuse) *256; + + // a = g(y+/-sqrt(x^2+y^2)). a1 can be +, a2 can be -. + a1 = FixedMul(g,y+fixedHypotenuse); + a2 = FixedMul(g,y-fixedHypotenuse); + + // Determine which one isn't actually an imaginary number (or the smaller of the two, if both are real), and use that for v. + if (a1 < 0 || a2 < 0) + { + if (a1 < 0 && a2 < 0) + { + //Somehow, v^2 is negative in both cases. v is therefore imaginary and something is horribly wrong. Abort! + return; + } + // Just find which one's NOT negative, and use that + aToUse = max(a1,a2); + } + else + { + // Both are positive; use whichever's smaller so it can decay faster + aToUse = min(a1,a2); + } + v = FixedSqrt(aToUse); + // Okay, so we know the velocity. Let's actually find theta. + // We can cut the "+/- sqrt" part out entirely, since v was calculated specifically for it to equal zero. So: + //theta = tantoangle[FixedDiv(aToUse,FixedMul(g,x)) >> DBITS]; + theta = tantoangle[SlopeDiv(aToUse,FixedMul(g,x))]; + + // Okay, complicated math done. Let's make this object jump already. + + shot->angle = R_PointToAngle2(shot->x, shot->y, target->x, target->y); + + if (shot->eflags & MFE_VERTICALFLIP) + shot->z--; + else + shot->z++; + + // Horizontal axes first. First parameter is initial horizontal impulse, second is to correct its angle. + fixedHypotenuse = FixedMul(v, FINECOSINE(theta >> ANGLETOFINESHIFT)); // variable reuse + shot->momx = FixedMul(fixedHypotenuse, FINECOSINE(shot->angle >> ANGLETOFINESHIFT)); + shot->momy = FixedMul(fixedHypotenuse, FINESINE(shot->angle >> ANGLETOFINESHIFT)); + // Then the vertical axis. No angle-correction needed here. + shot->momz = FixedMul(v, FINESINE(theta >> ANGLETOFINESHIFT)); + // I hope that's all that's needed, ugh +} + +static mobj_t *VS_PredictedPuyoShot(mobj_t *arena, mobj_t *source, mobj_t *shot, mobj_t *target, mobj_t *reticule) +{ + fixed_t x = 0, y = 0, z = INT32_MIN; + fixed_t momx = 0, momy = 0, mommag = 0; + angle_t mompoint = 0; + + if (P_MobjWasRemoved(arena)) + { + CONS_Alert(CONS_ERROR, "VS_PredictedPuyoShot: No Versus arena provided."); + return NULL; + } + + // handle coords + if (P_MobjWasRemoved(target) == false) // aimed leading shot + { + x = target->x; + y = target->y; + z = target->floorz; + + if (P_MobjWasRemoved(shot) == false + && (target->player == NULL || P_PlayerInPain(target->player) == false)) + { + // a beyond mediocre guess - intentionally not DIRECTLY tied to maxarena because + // this was determined via trial and error and shouldn't change + fixed_t possiblearcmultiplier = ( + ((TICRATE/4)*FRACUNIT) + + (3*TICRATE* + FixedDiv( + FixedHypot(x - shot->x, y - shot->y), + 2*280*FRACUNIT + ) + ) + ); + + momx = FixedMul(target->momx, possiblearcmultiplier); + momy = FixedMul(target->momy, possiblearcmultiplier); + + mommag = FixedHypot(momx, momy); + mompoint = R_PointToAngle2(0, 0, momx, momy); + + fixed_t *predict = VS_PredictAroundArena( + arena, + target, + mommag, + mompoint, + shot->radius, + false, + 0 + ); + + momx = predict[0] - x; + momy = predict[1] - y; + } + } + else // unaimed random shot + { + fixed_t *predict = VS_RandomPointOnArena( + arena, + (P_MobjWasRemoved(shot) ? 0 : shot->radius) + ); + + x = predict[0]; + y = predict[1]; + } + + // handle reticule + fixed_t tempz = ((z == INT32_MIN && !P_MobjWasRemoved(source)) ? source->z : z); + + if (P_MobjWasRemoved(reticule) == false) + { + P_SetOrigin(reticule, x, y, tempz); + } + else + { + reticule = P_SpawnMobj(x, y, tempz, MT_SPIKEDTARGET); + reticule->renderflags |= RF_NOSPLATBILLBOARD; + reticule->destscale = 2*reticule->destscale; + reticule->radius = FixedMul(mobjinfo[MT_PLAYER].radius, mapobjectscale)/2; + //P_SetScale(reticule, reticule->destscale); -- intentionally not here, for animation + + if (P_MobjWasRemoved(shot) == false) + P_SetTarget(&reticule->target, shot); + else if (P_MobjWasRemoved(source) == false) + P_SetTarget(&reticule->target, source); + } + + if (z == INT32_MIN) + { + z = reticule->old_z = reticule->floorz; + } + + reticule->z = z + FixedMul(mobjinfo[MT_PLAYER].height, mapobjectscale); + + // handle reticle movement + if (momx != 0 || momy != 0) + { + UINT8 attempt = 0; + boolean lastsuccess = true; + // tries to immediately jump to the final location. + // if that fails, tries to xeno's paradox it: + // halve the distance and try TWO steps at this magnitude + while (attempt < 5) + { + if (P_TryMove(reticule, reticule->x + momx, reticule->y + momy, false, NULL)) + { + if (lastsuccess) + break; + + lastsuccess = true; + continue; + } + lastsuccess = false; + momx /= 2; + momy /= 2; + attempt++; + } + } + + // handle launching + if (P_MobjWasRemoved(shot) == false) + { + P_SetTarget(&shot->tracer, reticule); + + VS_PuyoJump(shot, reticule); + + P_Thrust(shot, shot->angle, 3*mapobjectscale); // needs this little extra kick just to make it, for some reason + shot->momz *= PUYOARCMULTIPLIER; + shot->flags |= (MF_NOGRAVITY|MF_SHOOTABLE); + if (P_RandomChance(PR_DECORATION, FRACUNIT/2)) + shot->flags2 |= MF2_BOSSNOTRAP; + } + + // handle finalisation + P_SetOrigin(reticule, reticule->x, reticule->y, z); + return reticule; +} + +static mobj_t *referencepuyo = NULL; +static mobj_t *bestpuyo = NULL; +fixed_t bestpuyodist = INT32_MAX; + +static inline BlockItReturn_t PIT_GetBestLaunchablePuyo(mobj_t *thing) +{ + if (thing->type != MT_BLENDEYE_PUYO) + return BMIT_CONTINUE; // not a puyo + + if (thing->cusval > 0) + return BMIT_CONTINUE; // a bottom puyo + + if (P_MobjWasRemoved(thing->tracer)) + return BMIT_CONTINUE; // a launched puyo + + fixed_t dist = FixedHypot(referencepuyo->x - thing->x, referencepuyo->y - thing->y); + + if (dist >= bestpuyodist) + return BMIT_CONTINUE; // too far away + + bestpuyo = thing; + bestpuyodist = dist; + + return BMIT_CONTINUE; // Still could be a better selection +} + +static void VS_FindBestPuyo(mobj_t *reference, mobj_t *source) +{ + INT32 bx, by, xl, xh, yl, yh; + + referencepuyo = reference; + bestpuyo = NULL; + bestpuyodist = INT32_MAX; + + yh = (unsigned)(source->y + (source->radius + MAXRADIUS) - bmaporgy)>>MAPBLOCKSHIFT; + yl = (unsigned)(source->y - (source->radius + MAXRADIUS) - bmaporgy)>>MAPBLOCKSHIFT; + xh = (unsigned)(source->x + (source->radius + MAXRADIUS) - bmaporgx)>>MAPBLOCKSHIFT; + xl = (unsigned)(source->x - (source->radius + MAXRADIUS) - bmaporgx)>>MAPBLOCKSHIFT; + + BMBOUNDFIX (xl, xh, yl, yh); + + for (by = yl; by <= yh; by++) + for (bx = xl; bx <= xh; bx++) + P_BlockThingsIterator(bx, by, PIT_GetBestLaunchablePuyo); +} + +void VS_PuyoThinker(mobj_t *mobj) +{ + if (!mobj->health) + return; + + if (mobj->z <= mobj->floorz) + { + if (mobj->flags & MF_NOCLIPHEIGHT) + { + mobj->z = mobj->floorz; + mobj->momx = mobj->momy = mobj->momz = 0; + mobj->flags &= ~(MF_NOCLIPHEIGHT); + //P_KillMobj(mobj); + P_SetMobjState(mobj, S_BLENDEYE_PUYO_LAND_1); + mobj->fuse = 20*TICRATE; + } + return; + } + + if (P_MobjWasRemoved(mobj->tracer) || (mobj->tracer->type == MT_SPIKEDTARGET)) // being thrown... + { + if (mobj->flags2 & MF2_BOSSNOTRAP) + mobj->rollangle -= ANG10; + else + mobj->rollangle += ANG10; + + mobj->momz -= (PUYOARCMULTIPLIER*FixedMul(gravity, mapobjectscale)); + } + else if (mobj->tracer->flags2 & MF2_AMBUSH) // being whirled + { + if (mobj->movefactor) + { + if (mobj->movefactor > 2 || ((mobj->state-states) == S_BLENDEYE_PUYO)) + { + if ((--mobj->movefactor) == 0) + P_SetMobjState(mobj, S_BLENDEYE_PUYO_SHOCK); + } + } + else if (mobj->cusval > 0) // popping and throwing the object on top of them + { + if ((--mobj->cusval) == 1) + { + P_InstaThrust(mobj, mobj->angle + ANGLE_90, 5*FRACUNIT); + mobj->momz = 6*FRACUNIT; + + VS_FindBestPuyo(mobj, mobj->tracer); + if (bestpuyo != NULL) + { + VS_PredictedPuyoShot( + (P_MobjWasRemoved(mobj->tracer->tracer) == false + ? mobj->tracer->tracer->tracer + : NULL + ), + mobj->tracer, + bestpuyo, + mobj->tracer->target, + NULL + ); + } + + P_KillMobj(mobj, mobj->tracer, mobj->tracer, DMG_NORMAL); + return; + } + } + fixed_t x = mobj->tracer->x, y = mobj->tracer->y, z = mobj->z+1; + fixed_t dist = mobj->cvmem; + + if (mobj->extravalue1 == mobj->tracer->extravalue1 || mobj->cvmem == mobj->tracer->cvmem) + ; + else if (mobj->z >= mobj->tracer->extravalue1) + dist = mobj->tracer->cvmem; + else + dist = mobj->cvmem + FixedMul(FixedDiv(mobj->z - mobj->extravalue1, mobj->tracer->extravalue1 - mobj->extravalue1), mobj->tracer->cvmem - mobj->cvmem); + + mobj->angle += FixedAngle(((3*TICRATE/4) - mobj->movefactor)*FRACUNIT); + x += P_ReturnThrustX(mobj, mobj->angle, dist/2); + y += P_ReturnThrustY(mobj, mobj->angle, dist/2); + if (mobj->flags2 & MF2_STRONGBOX) + { + z = mobj->extravalue1+1; + if (((leveltime & 1) == 1) != (mobj->cusval > 0)) + z += mobj->scale; + } + else // being thrown off the side of pinch mode + { + if (mobj->z >= mobj->tracer->extravalue1) + { + mobj_t *target = ((mobj->flags2 & MF2_BOSSNOTRAP) == 0) + ? mobj->tracer->target + : NULL; + mobj->flags2 &= ~MF2_BOSSNOTRAP; + VS_PredictedPuyoShot( + (P_MobjWasRemoved(mobj->tracer->tracer) == false + ? mobj->tracer->tracer->tracer + : NULL + ), + mobj->tracer, + mobj, + target, + NULL + ); + S_StartSound(NULL, sfx_mbs5b); + return; + } + z += mobj->scale; + } + P_MoveOrigin(mobj, x, y, z); + mobj->flags |= MF_NOGRAVITY; + } + else // being inserted into the blender + { + if (mobj->momz < 0) + { + if (mobj->z < mobj->extravalue1) + { + mobj->momz = 0; + mobj->flags |= MF_NOGRAVITY; + mobj->z = mobj->extravalue1; + S_StartSound(NULL, sfx_mbs42); + P_SetMobjState(mobj, S_BLENDEYE_PUYO_LAND_1); + } + } + else if (mobj->z > mobj->extravalue1) + mobj->flags &= ~MF_NOGRAVITY; + + if ( + (mobj->state-states) != S_BLENDEYE_PUYO_SHOCK + && mobj->tracer->movedir == BLENDEYE_EXPLODING + && ( + (mobj->tracer->movecount == 3) + || ( + mobj->tracer->movecount == 4 + && P_RandomChance(PR_DECORATION, FRACUNIT/2) + ) + ) + ) + { + P_SetMobjState(mobj, S_BLENDEYE_PUYO_SHOCK); + } + } + mobj->sprite = mobj->movedir; +} + +void VS_PuyoDeath(mobj_t *mobj) +{ + mobjtype_t dusttype = (encoremode ? MT_BLENDEYE_PUYO_DUST : MT_BLENDEYE_PUYO_DUST_COFFEE); + UINT8 i; + fixed_t momx, momy; + mobj_t *dustmo; + + mobj->renderflags &= ~RF_DONTDRAW; + mobj->rollangle = 0; + + mobj->angle = FixedAngle(P_RandomKey(PR_DECORATION, 360)*FRACUNIT); + for (i = 0; i <= 2; i++) + { + momx = P_ReturnThrustX(mobj, mobj->angle, 3*mobj->scale); + momy = P_ReturnThrustY(mobj, mobj->angle, 3*mobj->scale); + + dustmo = P_SpawnMobjFromMobj(mobj, 0, 0, 0, dusttype); + dustmo->momx = mobj->momx + momx; + dustmo->momy = mobj->momy + momy; + dustmo->momz = mobj->momz + 4*mobj->scale; + dustmo->movedir = dustmo->sprite = mobj->movedir; + + dustmo = P_SpawnMobjFromMobj(mobj, 0, 0, 0, dusttype); + dustmo->momx = mobj->momx - momx; + dustmo->momy = mobj->momy - momy; + dustmo->momz = mobj->momz - 4*mobj->scale; + dustmo->movedir = dustmo->sprite = mobj->movedir; + + mobj->angle += ANGLE_135; + } + S_StartSound(NULL, ((mobj->tracer && mobj->tracer->type != MT_SPIKEDTARGET) ? sfx_mbs4c : sfx_mbs45)); +} diff --git a/src/p_enemy.c b/src/p_enemy.c index 81cd6c517..2f7700a30 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -328,6 +328,7 @@ void A_InvincSparkleRotate(mobj_t *actor); void A_SpawnItemDebrisCloud(mobj_t *actor); void A_RingShooterFace(mobj_t *actor); void A_SpawnSneakerPanel(mobj_t *actor); +void A_BlendEyePuyoHack(mobj_t *actor); //for p_enemy.c @@ -9232,27 +9233,29 @@ void A_ForceStop(mobj_t *actor) // // Description: Makes all players win the level. // -// var1 = unused +// var1: +// if var1 == 1, give a life in GP contexts // var2 = unused // void A_ForceWin(mobj_t *actor) { INT32 i; + INT32 locvar1 = var1; if (LUA_CallAction(A_FORCEWIN, actor)) return; for (i = 0; i < MAXPLAYERS; i++) { - if (playeringame[i] && ((players[i].mo && players[i].mo->health) - && !(players[i].pflags & PF_NOCONTEST))) + if (playeringame[i] + && !(players[i].pflags & PF_NOCONTEST)) break; } if (i == MAXPLAYERS) return; - P_DoAllPlayersExit(0, false); + P_DoAllPlayersExit(0, (locvar1 == 1)); } // Function: A_SpikeRetract @@ -13754,3 +13757,42 @@ void A_SpawnSneakerPanel(mobj_t *actor) mo->angle = actor->angle; Obj_SneakerPanelSpriteScale(mo); } + +// Function: A_BlendEyePuyoHack +// +// Description: Blend Eye Puyo hazards visual/repeat behaviour +// +// var1: +// var2: +// See A_Repeat +// +void A_BlendEyePuyoHack(mobj_t *actor) +{ + INT32 locvar1 = var1; + INT32 locvar2 = var2; + + if (LUA_CallAction(A_BLENDEYEPUYOHACK, actor)) + { + return; + } + + actor->sprite = actor->movedir; + + if (locvar1 != 0 && !(actor->state->frame & FF_ANIMATE)) + { + // See A_Repeat + if ((!actor->extravalue2 || actor->extravalue2 > locvar1)) + actor->extravalue2 = locvar1; + + if (--actor->extravalue2 > 0) + P_SetMobjState(actor, locvar2); + } + + if (actor->movedir == SPR_PUYC + && (actor->state-states) == S_BLENDEYE_PUYO_SHOCK + && P_RandomChance(PR_DECORATION, FRACUNIT/50)) + { + // Funny + actor->frame = 7; + } +} diff --git a/src/p_inter.c b/src/p_inter.c index 61b3403b0..f3b66694d 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -253,8 +253,13 @@ static void P_ItemPop(mobj_t *actor) Obj_SpawnItemDebrisEffects(actor, actor->target); - if (!specialstageinfo.valid) // In Special, you'll respawn as a Ring Box (random-item.c), don't confuse the player. + if (!specialstageinfo.valid + && (gametyperules & GTR_SPHERES) != GTR_SPHERES) + { + // Doesn't apply to Special P_SetMobjState(actor, S_RINGBOX1); + } + actor->extravalue1 = 0; // de-solidify @@ -281,7 +286,7 @@ static void P_ItemPop(mobj_t *actor) */ // Here at mapload in battle? - if (!(gametyperules & GTR_CIRCUIT) && (actor->flags2 & MF2_BOSSNOTRAP)) + if (!(gametyperules & GTR_CIRCUIT) && (actor->flags2 & MF2_BOSSFLEE)) { numgotboxes++; @@ -362,6 +367,12 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) /////ENEMIES & BOSSES!!///////////////////////////////// //////////////////////////////////////////////////////// + if (special->type == MT_BLENDEYE_MAIN) + { + if (!VS_BlendEye_Touched(special, toucher)) + return; + } + P_DamageMobj(toucher, special, special, 1, DMG_NORMAL); return; } @@ -381,6 +392,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) P_InstaThrust(player->mo, player->mo->angle, 20<scale < special->destscale/2) + return; if (special->threshold >= FIRSTPOWERUP) { if (P_PlayerInPain(player)) @@ -946,6 +960,13 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) return; } + case MT_BLENDEYE_PUYO: + { + if (!VS_PuyoTouched(special, toucher)) + return; + break; + } + default: // SOC or script pickup P_SetTarget(&special->target, toucher); break; @@ -1075,7 +1096,7 @@ static void P_AddBrokenPrison(mobj_t *target, mobj_t *inflictor, mobj_t *source) if (++numtargets >= maptargets) { - P_DoAllPlayersExit(0, (grandprixinfo.gp == true)); + P_DoAllPlayersExit(0, true); } else { @@ -2163,6 +2184,15 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget case MT_BATTLEUFO: Obj_BattleUFODeath(target); break; + case MT_BLENDEYE_MAIN: + VS_BlendEye_Death(target); + break; + case MT_BLENDEYE_GLASS: + VS_BlendEye_Glass_Death(target); + break; + case MT_BLENDEYE_PUYO: + VS_PuyoDeath(target); + break; default: break; } @@ -2206,7 +2236,6 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget P_InstaThrust(target, R_PointToAngle2(inflictor->x, inflictor->y, target->x, target->y)+ANGLE_90, 16<type == MT_SPIKE && target->info->deathstate != S_NULL) { @@ -2349,6 +2378,27 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget if (!sprflip) target->frame |= FF_VERTICALFLIP; } + else if (target->type == MT_BLENDEYE_GENERATOR && !P_MobjWasRemoved(inflictor)) + { + mobj_t *refobj = (inflictor->type == MT_INSTAWHIP) ? source : inflictor; + angle_t impactangle = R_PointToAngle2(target->x, target->y, refobj->x - refobj->momx, refobj->y - refobj->momy) - (target->angle + ANGLE_90); + + if (P_MobjWasRemoved(target->tracer) == false) + { + target->tracer->flags2 &= ~MF2_FRET; + target->tracer->flags |= MF_SHOOTABLE; + P_DamageMobj(target->tracer, inflictor, source, 1, DMG_NORMAL); + target->tracer->flags &= ~MF_SHOOTABLE; + } + + P_SetMobjState( + target, + ((impactangle < ANGLE_180) + ? target->info->deathstate + : target->info->xdeathstate + ) + ); + } else if (target->player) { P_SetPlayerMobjState(target, target->info->deathstate); @@ -3089,12 +3139,54 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da { return Obj_SpecialUFODamage(target, inflictor, source, damagetype); } + else if (target->type == MT_BLENDEYE_MAIN) + { + VS_BlendEye_Damage(target, inflictor, source, damage); + } if (damagetype & DMG_STEAL) { // Not a player, steal damage is intended to not do anything return false; } + + if ((target->flags & MF_BOSS) == MF_BOSS) + { + targetdamaging_t targetdamaging = UFOD_GENERIC; + if (P_MobjWasRemoved(inflictor) == true) + ; + else switch (inflictor->type) + { + case MT_GACHABOM: + targetdamaging = UFOD_GACHABOM; + break; + case MT_ORBINAUT: + case MT_ORBINAUT_SHIELD: + targetdamaging = UFOD_ORBINAUT; + break; + case MT_BANANA: + targetdamaging = UFOD_BANANA; + break; + case MT_INSTAWHIP: + inflictor->extravalue2 = 1; // Disable whip collision + targetdamaging = UFOD_WHIP; + break; + case MT_PLAYER: + targetdamaging = UFOD_BOOST; + break; + case MT_JAWZ: + case MT_JAWZ_SHIELD: + targetdamaging = UFOD_JAWZ; + break; + case MT_SPB: + targetdamaging = UFOD_SPB; + break; + default: + break; + } + + P_TrackRoundConditionTargetDamage(targetdamaging); + } } // do the damage diff --git a/src/p_map.c b/src/p_map.c index 9bd4a141a..79b9ba59e 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -33,6 +33,7 @@ #include "i_system.h" // SRB2kart #include "k_terrain.h" #include "k_objects.h" +#include "k_boss.h" #include "r_splats.h" @@ -658,6 +659,11 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return BMIT_CONTINUE; // force no collide } + // Blend-Eye internal noclip + if ((thing->type == MT_BLENDEYE_GLASS || thing->type == MT_BLENDEYE_SHIELD || thing->type == MT_BLENDEYE_EGGBEATER) + && (tm.thing->type == MT_BLENDEYE_MAIN || tm.thing->type == MT_BLENDEYE_EYE || tm.thing->type == MT_BLENDEYE_PUYO)) + return BMIT_CONTINUE; + // When solid spikes move, assume they just popped up and teleport things on top of them to hurt. if (tm.thing->type == MT_SPIKE && tm.thing->flags & MF_SOLID) { @@ -689,7 +695,9 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) if (P_DamageMobj(tm.thing, thing, thing, 1, damagetype) && (damagetype = (thing->info->mass>>8))) S_StartSound(thing, damagetype); } - return BMIT_CONTINUE; + + if (P_MobjWasRemoved(tm.thing) || P_MobjWasRemoved(thing)) + return BMIT_CONTINUE; } else if (tm.thing->flags & MF_PAIN && thing->player) { // Painful thing splats player in the face @@ -705,7 +713,9 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) if (P_DamageMobj(thing, tm.thing, tm.thing, 1, damagetype) && (damagetype = (tm.thing->info->mass>>8))) S_StartSound(tm.thing, damagetype); } - return BMIT_CONTINUE; + + if (P_MobjWasRemoved(tm.thing) || P_MobjWasRemoved(thing)) + return BMIT_CONTINUE; } // check for skulls slamming into things @@ -779,6 +789,22 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return BMIT_CONTINUE; } + if (thing->type == MT_BLENDEYE_EGGBEATER + && tm.thing->type == MT_PLAYER && tm.thing->player) + { + if (tm.thing->z > thing->z + thing->height) + { + return BMIT_CONTINUE; // overhead + } + + if (tm.thing->z + tm.thing->height < thing->z) + { + return BMIT_CONTINUE; // underneath + } + + VS_BlendEye_Eggbeater_Touched(thing, tm.thing); + } + if (thing->type == MT_SPB) { if (tm.thing->type != MT_PLAYER diff --git a/src/p_mobj.c b/src/p_mobj.c index 41d266b61..1ada630c8 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -38,6 +38,7 @@ // SRB2kart #include "k_kart.h" +#include "k_boss.h" #include "k_battle.h" #include "k_color.h" #include "k_follower.h" @@ -2388,16 +2389,14 @@ boolean P_ZMovement(mobj_t *mo) break; } - if (!mo->player && P_CheckDeathPitCollide(mo)) + if (!mo->player && P_CheckDeathPitCollide(mo) && mo->health) { - if (mo->flags & MF_ENEMY || mo->flags & MF_BOSS || mo->type == MT_MINECART) + if ((mo->flags & (MF_ENEMY|MF_BOSS)) == MF_ENEMY) { - // Kill enemies, bosses and minecarts that fall into death pits. - if (mo->health) - { - P_KillMobj(mo, NULL, NULL, DMG_NORMAL); - } - return false; + // Kill enemies that fall into death pits. + P_KillMobj(mo, NULL, NULL, DMG_NORMAL); + if (P_MobjWasRemoved(mo)) + return false; } } @@ -4662,28 +4661,42 @@ boolean P_BossTargetPlayer(mobj_t *actor, boolean closest) // Finds the player no matter what they're hiding behind (even lead!) boolean P_SupermanLook4Players(mobj_t *actor) { - INT32 c, stop = 0; - player_t *playersinthegame[MAXPLAYERS]; + UINT8 c, stop = 0; + UINT8 playersinthegame[MAXPLAYERS]; for (c = 0; c < MAXPLAYERS; c++) { - if (playeringame[c] && !players[c].spectator) - { - if (!players[c].mo) - continue; + // Playing status + if (!playeringame[c]) + continue; + if (players[c].spectator) + continue; - if (players[c].mo->health <= 0) - continue; // dead + // Mobj status + if (!players[c].mo) + continue; + if (players[c].mo->health <= 0) + continue; // dead - playersinthegame[stop] = &players[c]; - stop++; - } + // Pain status + if (players[c].respawn.state != RESPAWNST_NONE) + continue; // don't wail on the respawning + + playersinthegame[stop] = c; + stop++; } if (!stop) return false; - P_SetTarget(&actor->target, playersinthegame[P_RandomKey(PR_UNDEFINED, stop)]->mo); + P_SetTarget( + &actor->target, + players[ + playersinthegame[ + P_RandomKey(PR_MOVINGTARGET, stop) + ] + ].mo + ); return true; } @@ -5872,8 +5885,21 @@ static void P_MobjSceneryThink(mobj_t *mobj) P_RemoveMobj(mobj); return; } - else - P_AddOverlay(mobj); + + if (mobj->fuse) + { + mobj->fuse--; + if (!mobj->fuse) + { + if (!LUA_HookMobj(mobj, MOBJ_HOOK(MobjFuse))) + { + P_RemoveMobj(mobj); + return; + } + } + } + + P_AddOverlay(mobj); if (mobj->target->hitlag) // move to the correct position, update to the correct properties, but DON'T STATE-ANIMATE return; switch (mobj->target->type) @@ -6783,6 +6809,17 @@ static void P_MobjSceneryThink(mobj_t *mobj) break; } + case MT_SPIKEDTARGET: + { + if (P_MobjWasRemoved(mobj->target) || (mobj->target->health <= 0) || (mobj->target->z == mobj->target->floorz)) + { + P_RemoveMobj(mobj); + return; + } + + mobj->angle += ANG2; + break; + } case MT_VWREF: case MT_VWREB: { @@ -6827,16 +6864,19 @@ static boolean P_MobjBossThink(mobj_t *mobj) } else if (P_MobjWasRemoved(mobj)) return false; - else - switch (mobj->type) - { - // No SRB2Kart bosses... yet :) + else switch (mobj->type) + { + case MT_BLENDEYE_MAIN: + VS_BlendEye_Thinker(mobj); + break; default: // Generic SOC-made boss if (mobj->flags2 & MF2_SKULLFLY) P_SpawnGhostMobj(mobj); P_GenericBossThinker(mobj); break; - } + } + if (P_MobjWasRemoved(mobj)) + return false; if (mobj->flags2 & MF2_BOSSFLEE) { if (mobj->extravalue1) @@ -7031,6 +7071,11 @@ static boolean P_MobjDeadThink(mobj_t *mobj) } break; } + case MT_BLENDEYE_GENERATOR: + { + VS_BlendEye_Generator_DeadThinker(mobj); + break; + } default: break; } @@ -9952,6 +9997,24 @@ static boolean P_MobjRegularThink(mobj_t *mobj) break; } + case MT_BLENDEYE_EYE: + { + if (!VS_BlendEye_Eye_Thinker(mobj)) + { + return false; + } + break; + } + case MT_BLENDEYE_PUYO: + { + VS_PuyoThinker(mobj); + if (P_MobjWasRemoved(mobj)) + { + return false; + } + break; + } + default: // check mobj against possible water content, before movement code P_MobjCheckWater(mobj); @@ -10069,6 +10132,7 @@ static boolean P_CanFlickerFuse(mobj_t *mobj) case MT_POGOSPRING: case MT_KART_LEFTOVER: case MT_EMERALD: + case MT_BLENDEYE_PUYO: if (mobj->fuse <= TICRATE) { return true; @@ -10538,13 +10602,6 @@ void P_MobjThinker(mobj_t *mobj) P_SquishThink(mobj); K_UpdateTerrainOverlay(mobj); - if (mobj->flags & (MF_ENEMY|MF_BOSS) && mobj->health - && P_CheckDeathPitCollide(mobj)) // extra pit check in case these didn't have momz - { - P_KillMobj(mobj, NULL, NULL, DMG_DEATHPIT); - return; - } - // Crush enemies! if (mobj->ceilingz - mobj->floorz < mobj->height) { @@ -11441,6 +11498,24 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) case MT_BALLSWITCH_BALL: Obj_BallSwitchInit(mobj); break; + case MT_BLENDEYE_MAIN: + VS_BlendEye_Init(mobj); + break; + case MT_BLENDEYE_PUYO: + mobj->sprite = mobj->movedir = P_RandomRange(PR_DECORATION, SPR_PUYA, SPR_PUYE); + if (encoremode == false) + { + mobj->color = SKINCOLOR_LEATHER; + mobj->colorized = true; + } + break; + case MT_BLENDEYE_PUYO_DUST_COFFEE: + mobj->color = SKINCOLOR_LEATHER; + mobj->colorized = true; + // FALLTHRU + case MT_BLENDEYE_PUYO_DUST: + mobj->sprite = mobj->movedir = P_RandomRange(PR_DECORATION, SPR_PUYA, SPR_PUYE); + break; default: break; } @@ -12153,6 +12228,8 @@ void P_RespawnBattleBoxes(void) if (gametyperules & GTR_CIRCUIT) return; + tic_t setduration = (nummapboxes > 1) ? TICRATE : (2*TICRATE); + for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) { mobj_t *box; @@ -12163,13 +12240,20 @@ void P_RespawnBattleBoxes(void) box = (mobj_t *)th; if (box->type != MT_RANDOMITEM - || (box->flags2 & MF2_DONTRESPAWN) + || ((box->flags2 & (MF2_DONTRESPAWN|MF2_BOSSFLEE)) != MF2_BOSSFLEE) || !(box->flags & MF_NOCLIPTHING) || box->fuse) continue; // only popped items - box->fuse = TICRATE; // flicker back in - P_SetMobjState(box, box->info->raisestate); + box->fuse = setduration; // flicker back in + P_SetMobjState( + box, + (((gametyperules & GTR_SPHERES) == GTR_SPHERES) + ? box->info->raisestate + : box->info->spawnstate + ) + ); + box->renderflags |= RF_DONTDRAW; // guarantee start invisible if (numgotboxes > 0) numgotboxes--; // you've restored a box, remove it from the count @@ -13410,6 +13494,15 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj) P_InitSkyboxPoint(mobj, mthing); break; } + case MT_BOSSARENACENTER: + { + if (!VS_ArenaCenterInit(mobj, mthing)) + { + P_RemoveMobj(mobj); + return false; + } + break; + } case MT_EGGSTATUE: if (mthing->thing_args[1]) { @@ -13772,18 +13865,6 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj) } case MT_RANDOMITEM: { - const boolean delayed = !(gametyperules & GTR_CIRCUIT); - if (leveltime == 0) - { - mobj->flags2 |= MF2_BOSSNOTRAP; // mark as here on map start - if (delayed) - { - P_UnsetThingPosition(mobj); - mobj->flags |= (MF_NOCLIPTHING|MF_NOBLOCKMAP); - mobj->renderflags |= RF_DONTDRAW; - P_SetThingPosition(mobj); - } - } if (mthing->thing_args[0] == 1) mobj->flags2 |= MF2_BOSSDEAD; break; diff --git a/src/p_saveg.c b/src/p_saveg.c index f6f8880dc..a1c3d1f64 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -6367,7 +6367,7 @@ static void P_NetArchiveRNG(savebuffer_t *save) WRITEUINT32(save->p, ARCHIVEBLOCK_RNG); - for (i = 0; i < PRNUMCLASS; i++) + for (i = 0; i < PRNUMSYNCED; i++) { WRITEUINT32(save->p, P_GetInitSeed(i)); WRITEUINT32(save->p, P_GetRandSeed(i)); @@ -6381,7 +6381,7 @@ static inline void P_NetUnArchiveRNG(savebuffer_t *save) if (READUINT32(save->p) != ARCHIVEBLOCK_RNG) I_Error("Bad $$$.sav at archive block RNG"); - for (i = 0; i < PRNUMCLASS; i++) + for (i = 0; i < PRNUMSYNCED; i++) { UINT32 init = READUINT32(save->p); UINT32 seed = READUINT32(save->p); diff --git a/src/p_setup.c b/src/p_setup.c index 0012dda6d..4ba02f340 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7953,9 +7953,14 @@ static void P_InitGametype(void) G_RecordDemo(buf); } - // Started a game? Move on to the next jam when you go back to the title screen - CV_SetValue(&cv_menujam_update, 1); - gamedata->musicstate = GDMUSIC_NONE; + if (gamestate != GS_TITLESCREEN) + { + // Started a game? Move on to the next jam when you go back to the title screen + // this permits all but titlescreen, instead of only GS_LEVEL + // because that one's way too easy to activate again and again + CV_SetValue(&cv_menujam_update, 1); + gamedata->musicstate = GDMUSIC_NONE; + } } struct minimapinfo minimapinfo; diff --git a/src/p_user.c b/src/p_user.c index fb4a2f031..419e78ab6 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1296,7 +1296,7 @@ void P_DoPlayerExit(player_t *player, pflags_t flags) player->hudrings = 20; if (grandprixinfo.gp == true - && grandprixinfo.eventmode == GPEVENT_NONE + && grandprixinfo.eventmode != GPEVENT_SPECIAL && player->bot == false && losing == false) { const UINT8 lifethreshold = 20; @@ -1364,6 +1364,13 @@ void P_DoAllPlayersExit(pflags_t flags, boolean trygivelife) UINT8 i; const boolean dofinishsound = (musiccountdown == 0); + if (grandprixinfo.gp == false + || grandprixinfo.eventmode == GPEVENT_SPECIAL + || (flags & PF_NOCONTEST)) + { + trygivelife = false; + } + for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) @@ -1382,7 +1389,7 @@ void P_DoAllPlayersExit(pflags_t flags, boolean trygivelife) continue; } - P_GivePlayerLives(&players[i], 1); + players[i].xtralife++; } if (!dofinishsound) diff --git a/src/sounds.c b/src/sounds.c index 8c2b32daf..cbb5836a2 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -839,7 +839,7 @@ sfxinfo_t S_sfx[NUMSFX] = // Mean Bean Machine sounds {"mbs41", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, - {"mbs42", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + {"mbs42", true, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, {"mbs43", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, {"mbs44", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, {"mbs45", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, @@ -849,7 +849,7 @@ sfxinfo_t S_sfx[NUMSFX] = {"mbs49", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, {"mbs4a", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, {"mbs4b", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, - {"mbs4c", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + {"mbs4c", true, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, {"mbs4d", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, {"mbs4e", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, {"mbs4f", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, @@ -1240,6 +1240,9 @@ sfxinfo_t S_sfx[NUMSFX] = {"ridr3", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Rideroid Loop {"ridr4", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Leaving Rideroid + {"befan1", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, "Whisking"}, // Blend Eye whisk startup + {"befan2", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, "Whisking"}, // Blend Eye whisk + // Damage sounds {"dmga1", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR, "Damaged"}, {"dmga2", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR, "Damaged"}, diff --git a/src/sounds.h b/src/sounds.h index e5648e3cd..a19d2b4ed 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -1302,13 +1302,17 @@ typedef enum sfx_rainbr, sfx_rank, - + // rideroid sfx_ridr1, sfx_ridr2, sfx_ridr3, sfx_ridr4, + // Blend Eye + sfx_befan1, + sfx_befan2, + // Damage sounds sfx_dmga1, sfx_dmga2, diff --git a/src/st_stuff.c b/src/st_stuff.c index 305f50266..c02ad0c8e 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -567,8 +567,8 @@ static patch_t *tcroundbonus; static patch_t *tcactnum[10]; static patch_t *tcact; -static patch_t *twarn; -static patch_t *twarn2; +static patch_t *twarn[2]; +static patch_t *ttext[2]; // some coordinates define to make my life easier.... #define FINAL_ROUNDX (24) @@ -625,8 +625,11 @@ static void ST_cacheLevelTitle(void) tcact = (patch_t *)W_CachePatchName("TT_ACT", PU_HUDGFX); - twarn = (patch_t *)W_CachePatchName("K_BOSW01", PU_HUDGFX); - twarn2 = (patch_t *)W_CachePatchName("K_BOSW02", PU_HUDGFX); + twarn[0] = (patch_t *)W_CachePatchName("K_BOSW01", PU_HUDGFX); + twarn[1] = (patch_t *)W_CachePatchName("K_BOSW02", PU_HUDGFX); + + ttext[0] = (patch_t *)W_CachePatchName("K_BOST01", PU_HUDGFX); + ttext[1] = (patch_t *)W_CachePatchName("K_BOST02", PU_HUDGFX); // Cache round # for (i=1; i < 11; i++) @@ -634,7 +637,7 @@ static void ST_cacheLevelTitle(void) sprintf(buf, "TT_RND%d", i); tcroundnum[i-1] = (patch_t *)W_CachePatchName(buf, PU_HUDGFX); } - tcroundbonus = (patch_t *)W_CachePatchName("TT_RNDB", PU_HUDGFX); + tcroundbonus = (patch_t *)W_CachePatchName("TT_RNDX", PU_HUDGFX); // Cache act # for (i=0; i < 10; i++) @@ -747,7 +750,20 @@ void ST_runTitleCard(void) } } // No matter the circumstances, scroll the WARN... - bannerx = -((lt_ticker*2)%((encoremode ? twarn2 : twarn)->width)); + { + patch_t *localwarn = twarn[encoremode ? 1 : 0]; + patch_t *localtext = ttext[encoremode ? 1 : 0]; + + if (localwarn->width) + { + bannerx = ((lt_ticker*2)%(localwarn->width)); + } + + if (localtext->width) + { + bannery = -((lt_ticker*4)%(localtext->width)); + } + } if (run && lt_ticker < PRELEVELTIME) { @@ -935,27 +951,40 @@ void ST_drawTitleCard(void) { #define LOTIME 5 #define HITIME 15 - patch_t *localwarn = (encoremode ? twarn2 : twarn); - INT32 transp = (lt_ticker+HITIME) % (LOTIME+HITIME); + + patch_t *localwarn; + INT32 transp; boolean encorehack = ((levelfadecol == 0) && lt_ticker <= PRELEVELTIME+4); - if ((localwarn->width > 0) && (lt_ticker + (HITIME-transp) <= lt_endtime)) - { - if (transp > HITIME-1) - { - transp = HITIME-1; - } - - transp = (((10*transp)/HITIME)< -pad) - bx -= localwarn->width; - while (bx < BASEVIDWIDTH+pad) - { - V_DrawFixedPatch(bx*FRACUNIT, 55*FRACUNIT, FRACUNIT, V_SNAPTOLEFT|transp, localwarn, NULL); - bx += localwarn->width; - } +#define DRAWBOSSWARN(pat) \ + localwarn = pat[encoremode ? 1 : 0];\ + \ + if ((localwarn->width > 0) && (lt_ticker + (HITIME-transp) <= lt_endtime)) \ + { \ + if (transp > HITIME-1)\ + { \ + transp = HITIME-1; \ + } \ + \ + transp = (((10*transp)/HITIME)< -pad) \ + bx -= localwarn->width; \ + while (bx < BASEVIDWIDTH+pad) \ + { \ + V_DrawFixedPatch(bx*FRACUNIT, 60*FRACUNIT, FRACUNIT, V_SNAPTOLEFT|transp, localwarn, NULL); \ + bx += localwarn->width; \ + } \ } + + transp = (lt_ticker+HITIME) % (LOTIME+HITIME); + DRAWBOSSWARN(twarn); + + transp = (lt_ticker+HITIME+3) % (LOTIME+HITIME); + bx = bannery; + DRAWBOSSWARN(ttext); + +#undef DRAWBOSSWARN #undef LOTIME #undef HITIME } @@ -966,7 +995,7 @@ void ST_drawTitleCard(void) bx = V_TitleCardStringWidth(bossinfo.enemyname, false); // Name. - V_DrawTitleCardString((BASEVIDWIDTH - bx)/2, 75, bossinfo.enemyname, 0, true, bossinfo.titleshow, lt_exitticker, false); + V_DrawTitleCardString((BASEVIDWIDTH - bx)/2, 80, bossinfo.enemyname, 0, true, bossinfo.titleshow, lt_exitticker, false); // Under-bar. { @@ -994,7 +1023,7 @@ void ST_drawTitleCard(void) // Handle subtitle. else if (bossinfo.subtitle && lt_ticker >= TICRATE/2) { - INT32 by = 75+32; + INT32 by = 80+32; if (lt_ticker == TICRATE/2 || lt_exitticker == 1) { ; @@ -1008,15 +1037,15 @@ void ST_drawTitleCard(void) by += 5; } - V_DrawRightAlignedThinString((BASEVIDWIDTH+bx)/2, by, 0, bossinfo.subtitle); + V_DrawRightAlignedThinString((BASEVIDWIDTH+bx)/2, by, V_FORCEUPPERCASE, bossinfo.subtitle); } // Now draw the under-bar itself. if (scalex > 0) { bx = FixedMul(bx, scalex); - V_DrawFill((BASEVIDWIDTH-(bx+2))/2, 75+32, bx+2, 3, 31); - V_DrawFill((BASEVIDWIDTH-(bx))/2, 75+32+1, bx, 1, 0); + V_DrawFill((BASEVIDWIDTH-(bx+2))/2, 80+32, bx+2, 3, 31); + V_DrawFill((BASEVIDWIDTH-(bx))/2, 80+32+1, bx, 1, 0); } } } @@ -1060,17 +1089,10 @@ void ST_drawTitleCard(void) ; // TODO: Ruby else if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE) { - switch (grandprixinfo.eventmode) - { - case GPEVENT_BONUS: - roundico = tcroundbonus; // TODO don't show capsule if we have other bonus types - break; - /*case GPEVENT_SPECIAL: - ; // TODO: Emerald/mount - break;*/ - default: - break; - } + if (gametypes[gametype]->gppic[0]) + roundico = W_CachePatchName(gametypes[gametype]->gppic, PU_PATCH); + else + roundico = tcroundbonus; // Generic star } else if (roundqueue.size > 0) { diff --git a/src/y_inter.c b/src/y_inter.c index bd685aad2..bc7264a57 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -381,13 +381,16 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) skins[players[i].skin].realname); } - if (roundqueue.size > 0 - && roundqueue.roundnum > 0 - && (grandprixinfo.gp == false - || grandprixinfo.eventmode == GPEVENT_NONE) - ) + if (roundqueue.size > 0 && roundqueue.roundnum > 0) { - data.roundnum = roundqueue.roundnum; + if ((grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE)) + { + data.roundnum = INTERMISSIONROUND_BONUS; + } + else + { + data.roundnum = roundqueue.roundnum; + } } } else @@ -411,7 +414,7 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) data.roundnum = roundqueue.roundnum; } - else if (bossinfo.valid == true && bossinfo.enemyname) + else if (K_CheckBossIntro() == true && bossinfo.enemyname) { snprintf(data.headerstring, sizeof data.headerstring, @@ -833,12 +836,16 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, // Progress markers patch_t *level_dot[BPP_MAIN]; + patch_t *bonus_dot[BPP_MAIN]; patch_t *capsu_dot[BPP_MAIN]; patch_t *prize_dot[BPP_MAIN]; level_dot[BPP_AHEAD] = W_CachePatchName("R_RRMRK2", PU_PATCH); level_dot[BPP_DONE] = W_CachePatchName("R_RRMRK1", PU_PATCH); + bonus_dot[BPP_AHEAD] = W_CachePatchName("R_RRMRK7", PU_PATCH); + bonus_dot[BPP_DONE] = W_CachePatchName("R_RRMRK8", PU_PATCH); + capsu_dot[BPP_AHEAD] = W_CachePatchName("R_RRMRK3", PU_PATCH); capsu_dot[BPP_DONE] = W_CachePatchName("R_RRMRK5", PU_PATCH); @@ -1296,7 +1303,14 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, && roundqueue.entries[i].gametype != roundqueue.entries[0].gametype ) { - chose_dot = capsu_dot; + if ((gametypes[roundqueue.entries[i].gametype]->rules & GTR_PRISONS) == GTR_PRISONS) + { + chose_dot = capsu_dot; + } + else + { + chose_dot = bonus_dot; + } } else { @@ -1459,14 +1473,29 @@ void Y_DrawIntermissionHeader(fixed_t x, fixed_t y, boolean gotthrough, const ch } // Draw round numbers - if (roundnum > 0 && roundnum <= 10) + patch_t *roundpatch = NULL; + + if (roundnum == INTERMISSIONROUND_BONUS) { - patch_t *roundpatch = - W_CachePatchName( - va("TT_RN%s%d", (small ? "S" : "D"), roundnum), + const char *gppic = (small ? gametypes[gametype]->gppicmini : gametypes[gametype]->gppic); + if (gppic[0]) + roundpatch = W_CachePatchName(gppic, PU_PATCH); + else + roundpatch = W_CachePatchName( + va("TT_RN%cX", (small ? 'S' : 'D')), PU_PATCH ); + } + else if (roundnum > 0 && roundnum <= 10) + { + roundpatch = W_CachePatchName( + va("TT_RN%c%d", (small ? 'S' : 'D'), roundnum), + PU_PATCH + ); + } + if (roundpatch) + { fixed_t roundx = (v_width * 3 * FRACUNIT) / 4; if (headerwidth != 0) diff --git a/src/y_inter.h b/src/y_inter.h index c23909cf6..a85017409 100644 --- a/src/y_inter.h +++ b/src/y_inter.h @@ -16,6 +16,8 @@ extern "C" { #endif +#define INTERMISSIONROUND_BONUS UINT8_MAX + typedef struct { boolean rankingsmode; // rankings mode