diff --git a/src/d_main.c b/src/d_main.c index b2559778a..8ba822e21 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -1867,6 +1867,18 @@ void D_SRB2Main(void) G_SetUsedCheats(); } + if (grandprixinfo.gp == true && mapheaderinfo[pstartmap-1]) + { + if (mapheaderinfo[pstartmap-1]->typeoflevel & TOL_SPECIAL) + { + specialStage.active = true; + specialStage.encore = grandprixinfo.encore; + grandprixinfo.eventmode = GPEVENT_SPECIAL; + } + + G_SetUsedCheats(); + } + D_MapChange(pstartmap, gametype, (cv_kartencore.value == 1), true, 0, false, false); } } diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 6a1fdc39c..9679b5631 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2853,7 +2853,7 @@ static void Command_Map_f(void) if (mapheaderinfo[newmapnum-1]) { // Let's just guess so we don't have to specify the gametype EVERY time... - newgametype = (mapheaderinfo[newmapnum-1]->typeoflevel & TOL_RACE) ? GT_RACE : GT_BATTLE; + newgametype = (mapheaderinfo[newmapnum-1]->typeoflevel & (TOL_BATTLE|TOL_BOSS)) ? GT_BATTLE : GT_RACE; } } @@ -2902,11 +2902,59 @@ static void Command_Map_f(void) if (!(netgame || multiplayer)) { + grandprixinfo.gamespeed = (cv_kartspeed.value == KARTSPEED_AUTO ? KARTSPEED_NORMAL : cv_kartspeed.value); + grandprixinfo.masterbots = false; + + if (option_skill) + { + const char *skillname = COM_Argv(option_skill + 1); + INT32 newskill = -1; + INT32 j; + + for (j = 0; gpdifficulty_cons_t[j].strvalue; j++) + { + if (!strcasecmp(gpdifficulty_cons_t[j].strvalue, skillname)) + { + newskill = (INT16)gpdifficulty_cons_t[j].value; + break; + } + } + + if (!gpdifficulty_cons_t[j].strvalue) // reached end of the list with no match + { + INT32 num = atoi(COM_Argv(option_skill + 1)); // assume they gave us a skill number, which is okay too + if (num >= KARTSPEED_EASY && num <= KARTGP_MASTER) + newskill = (INT16)num; + } + + if (newskill != -1) + { + if (newskill == KARTGP_MASTER) + { + grandprixinfo.gamespeed = KARTSPEED_HARD; + grandprixinfo.masterbots = true; + } + else + { + grandprixinfo.gamespeed = newskill; + grandprixinfo.masterbots = false; + } + } + } + + grandprixinfo.encore = newencoremode; + + grandprixinfo.gp = true; + grandprixinfo.roundnum = 0; + grandprixinfo.cup = NULL; + grandprixinfo.wonround = false; + grandprixinfo.initalize = true; + + grandprixinfo.eventmode = GPEVENT_NONE; + if (newgametype == GT_BATTLE) { - grandprixinfo.gp = false; - specialStage.active = false; - K_ResetBossInfo(); + grandprixinfo.eventmode = GPEVENT_BONUS; if (mapheaderinfo[newmapnum-1] && mapheaderinfo[newmapnum-1]->typeoflevel & TOL_BOSS) @@ -2914,71 +2962,24 @@ static void Command_Map_f(void) bossinfo.boss = true; bossinfo.encore = newencoremode; } + else + { + bossinfo.boss = false; + K_ResetBossInfo(); + } } else { if (mapheaderinfo[newmapnum-1] && mapheaderinfo[newmapnum-1]->typeoflevel & TOL_SPECIAL) // Special Stage { - grandprixinfo.gp = false; - bossinfo.boss = false; - specialStage.active = true; specialStage.encore = newencoremode; + grandprixinfo.eventmode = GPEVENT_SPECIAL; } - else // default GP + else { - grandprixinfo.gamespeed = (cv_kartspeed.value == KARTSPEED_AUTO ? KARTSPEED_NORMAL : cv_kartspeed.value); - grandprixinfo.masterbots = false; - - if (option_skill) - { - const char *skillname = COM_Argv(option_skill + 1); - INT32 newskill = -1; - INT32 j; - - for (j = 0; gpdifficulty_cons_t[j].strvalue; j++) - { - if (!strcasecmp(gpdifficulty_cons_t[j].strvalue, skillname)) - { - newskill = (INT16)gpdifficulty_cons_t[j].value; - break; - } - } - - if (!gpdifficulty_cons_t[j].strvalue) // reached end of the list with no match - { - INT32 num = atoi(COM_Argv(option_skill + 1)); // assume they gave us a skill number, which is okay too - if (num >= KARTSPEED_EASY && num <= KARTGP_MASTER) - newskill = (INT16)num; - } - - if (newskill != -1) - { - if (newskill == KARTGP_MASTER) - { - grandprixinfo.gamespeed = KARTSPEED_HARD; - grandprixinfo.masterbots = true; - } - else - { - grandprixinfo.gamespeed = newskill; - grandprixinfo.masterbots = false; - } - } - } - - grandprixinfo.encore = newencoremode; - - grandprixinfo.gp = true; - grandprixinfo.roundnum = 0; - grandprixinfo.cup = NULL; - grandprixinfo.wonround = false; - - bossinfo.boss = false; specialStage.active = false; - - grandprixinfo.initalize = true; } } } diff --git a/src/deh_tables.c b/src/deh_tables.c index 90ac5151a..8e7bee9c1 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -4534,6 +4534,11 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi // Broly Ki Orb "S_BROLY1", "S_BROLY2", + + "S_SPECIAL_UFO_POD", + "S_SPECIAL_UFO_OVERLAY", + "S_SPECIAL_UFO_ARM", + "S_SPECIAL_UFO_STEM", }; // RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1", @@ -5631,6 +5636,9 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_BEAMPOINT", "MT_BROLY", + + "MT_SPECIAL_UFO", + "MT_SPECIAL_UFO_PIECE", }; const char *const MOBJFLAG_LIST[] = { diff --git a/src/discord.c b/src/discord.c index 2fcad7833..28381d250 100644 --- a/src/discord.c +++ b/src/discord.c @@ -549,7 +549,7 @@ void DRPC_UpdatePresence(void) if (gamestate == GS_LEVEL && Playing()) { const time_t currentTime = time(NULL); - const time_t mapTimeStart = currentTime - ((leveltime + (modeattacking ? starttime : 0)) / TICRATE); + const time_t mapTimeStart = currentTime - ((leveltime + starttime) / TICRATE); discordPresence.startTimestamp = mapTimeStart; diff --git a/src/info.c b/src/info.c index b32b5aa17..8e3757190 100644 --- a/src/info.c +++ b/src/info.c @@ -785,6 +785,10 @@ char sprnames[NUMSPRITES + 1][5] = "FLBM", + "UFOB", + "UFOA", + "UFOS", + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later "VIEW", }; @@ -5146,6 +5150,11 @@ state_t states[NUMSTATES] = // Broly Ki Orb {SPR_LSSJ, FF_REVERSESUBTRACT|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_BROLY2}, // S_BROLY1 {SPR_NULL, 0, 5*TICRATE, {A_SSMineFlash}, 0, 0, S_NULL}, // S_BROLY2 + + {SPR_UFOB, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SPECIAL_UFO_POD + {SPR_UFOB, 1|FF_FULLBRIGHT|FF_ANIMATE, -1, {NULL}, 1, 1, S_NULL}, // S_SPECIAL_UFO_OVERLAY + {SPR_UFOA, FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_SPECIAL_UFO_ARM + {SPR_UFOS, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SPECIAL_UFO_STEM }; mobjinfo_t mobjinfo[NUMMOBJTYPES] = @@ -23622,7 +23631,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 1, // damage sfx_kc64, // activesound - MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags + MF_SOLID|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags S_NULL // raisestate }, @@ -29084,6 +29093,60 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP, // flags S_NULL // raisestate }, + + { // MT_SPECIAL_UFO + -1, // doomednum + S_CHAOSEMERALD1, // spawnstate + 101, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // 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 + 108*FRACUNIT, // radius + 72*FRACUNIT, // height + 0, // display offset + 16, // mass + 0, // damage + sfx_None, // activesound + MF_SHOOTABLE|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, + + { // MT_SPECIAL_UFO_PIECE + -1, // doomednum + S_INVISIBLE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // 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 + 8*FRACUNIT, // radius + 16*FRACUNIT, // height + 1, // display offset + 100, // mass + 1, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_DONTENCOREMAP|MF_NOSQUISH, // flags + S_NULL // raisestate + }, }; skincolor_t skincolors[MAXSKINCOLORS] = { diff --git a/src/info.h b/src/info.h index 1eb1eae32..5056d4f69 100644 --- a/src/info.h +++ b/src/info.h @@ -1332,6 +1332,10 @@ typedef enum sprite SPR_FLBM, // Finish line beam + SPR_UFOB, + SPR_UFOA, + SPR_UFOS, + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later SPR_VIEW, @@ -5569,6 +5573,11 @@ typedef enum state S_BROLY1, S_BROLY2, + S_SPECIAL_UFO_POD, + S_SPECIAL_UFO_OVERLAY, + S_SPECIAL_UFO_ARM, + S_SPECIAL_UFO_STEM, + S_FIRSTFREESLOT, S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1, NUMSTATES @@ -6686,6 +6695,9 @@ typedef enum mobj_type MT_BROLY, + MT_SPECIAL_UFO, + MT_SPECIAL_UFO_PIECE, + MT_FIRSTFREESLOT, MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1, NUMMOBJTYPES diff --git a/src/k_kart.c b/src/k_kart.c index 968147a06..f1c0b8be2 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -184,6 +184,9 @@ UINT32 K_GetPlayerDontDrawFlag(player_t *player) { UINT32 flag = 0; + if (player == NULL) + return flag; + if (player == &players[displayplayers[0]]) flag = RF_DONTDRAWP1; else if (r_splitscreen >= 1 && player == &players[displayplayers[1]]) @@ -507,6 +510,51 @@ void K_RunItemCooldowns(void) } } +boolean K_TimeAttackRules(void) +{ + UINT8 playing = 0; + UINT8 i; + + if (specialStage.active == true) + { + // Kind of a hack -- Special Stages + // are expected to be 1-player, so + // we won't use the Time Attack changes + return false; + } + + if (modeattacking != ATTACKING_NONE) + { + // Time Attack obviously uses Time Attack rules :p + return true; + } + + if (battlecapsules == true) + { + // Break the Capsules always uses Time Attack + // rules, since you can bring 2-4 players in + // via Grand Prix. + return true; + } + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false || players[i].spectator == true) + { + continue; + } + + playing++; + if (playing > 1) + { + break; + } + } + + // Use Time Attack gameplay rules with only 1P. + return (playing <= 1); +} + //} //{ SRB2kart p_user.c Stuff @@ -530,6 +578,10 @@ static fixed_t K_PlayerWeight(mobj_t *mobj, mobj_t *against) { weight = 0; // This player does not cause any bump action } + else if (against && against->type == MT_SPECIAL_UFO) + { + weight = 0; + } else { // Applies rubberbanding, to prevent rubberbanding bots @@ -1029,7 +1081,7 @@ static void K_UpdateOffroad(player_t *player) player->offroad = 0; } -static void K_DrawDraftCombiring(player_t *player, player_t *victim, fixed_t curdist, fixed_t maxdist, boolean transparent) +static void K_DrawDraftCombiring(player_t *player, mobj_t *victim, fixed_t curdist, fixed_t maxdist, boolean transparent) { #define CHAOTIXBANDLEN 15 #define CHAOTIXBANDCOLORS 9 @@ -1060,9 +1112,9 @@ static void K_DrawDraftCombiring(player_t *player, player_t *victim, fixed_t cur c = FixedMul(CHAOTIXBANDCOLORS<> FRACBITS; } - stepx = (victim->mo->x - player->mo->x) / CHAOTIXBANDLEN; - stepy = (victim->mo->y - player->mo->y) / CHAOTIXBANDLEN; - stepz = ((victim->mo->z + (victim->mo->height / 2)) - (player->mo->z + (player->mo->height / 2))) / CHAOTIXBANDLEN; + stepx = (victim->x - player->mo->x) / CHAOTIXBANDLEN; + stepy = (victim->y - player->mo->y) / CHAOTIXBANDLEN; + stepz = ((victim->z + (victim->height / 2)) - (player->mo->z + (player->mo->height / 2))) / CHAOTIXBANDLEN; curx = player->mo->x + stepx; cury = player->mo->y + stepy; @@ -1096,7 +1148,7 @@ static void K_DrawDraftCombiring(player_t *player, player_t *victim, fixed_t cur if (transparent) band->renderflags |= RF_GHOSTLY; - band->renderflags |= RF_DONTDRAW & ~(K_GetPlayerDontDrawFlag(player) | K_GetPlayerDontDrawFlag(victim)); + band->renderflags |= RF_DONTDRAW & ~(K_GetPlayerDontDrawFlag(player) | K_GetPlayerDontDrawFlag(victim->player)); } curx += stepx; @@ -1121,6 +1173,129 @@ static boolean K_HasInfiniteTether(player_t *player) return false; } +static boolean K_TryDraft(player_t *player, mobj_t *dest, fixed_t minDist, fixed_t draftdistance, UINT8 leniency) +{ +//#define EASYDRAFTTEST + fixed_t dist, olddraft; + fixed_t theirSpeed = 0; +#ifndef EASYDRAFTTEST + angle_t yourangle, theirangle, diff; +#endif + +#ifndef EASYDRAFTTEST + // Don't draft on yourself :V + if (dest->player && dest->player == player) + { + return false; + } +#endif + + if (dest->player != NULL) + { + // No tethering off of the guy who got the starting bonus :P + if (dest->player->startboost > 0) + { + return false; + } + + theirSpeed = dest->player->speed; + } + else + { + theirSpeed = R_PointToDist2(0, 0, dest->momx, dest->momy); + } + + // They're not enough speed to draft off of them. + if (theirSpeed < 20 * dest->scale) + { + return false; + } + +#ifndef EASYDRAFTTEST + yourangle = K_MomentumAngle(player->mo); + theirangle = K_MomentumAngle(dest); + + // Not in front of this player. + diff = AngleDelta(R_PointToAngle2(player->mo->x, player->mo->y, dest->x, dest->y), yourangle); + if (diff > ANG10) + { + return false; + } + + // Not moving in the same direction. + diff = AngleDelta(yourangle, theirangle); + if (diff > ANGLE_90) + { + return false; + } +#endif + + dist = P_AproxDistance(P_AproxDistance(dest->x - player->mo->x, dest->y - player->mo->y), dest->z - player->mo->z); + +#ifndef EASYDRAFTTEST + // TOO close to draft. + if (dist < minDist) + { + return false; + } + + // Not close enough to draft. + if (dist > draftdistance && draftdistance > 0) + { + return false; + } +#endif + + olddraft = player->draftpower; + + player->draftleeway = leniency; + + if (dest->player != NULL) + { + player->lastdraft = dest->player - players; + } + else + { + player->lastdraft = MAXPLAYERS; + } + + // Draft power is used later in K_GetKartBoostPower, ranging from 0 for normal speed and FRACUNIT for max draft speed. + // How much this increments every tic biases toward acceleration! (min speed gets 1.5% per tic, max speed gets 0.5% per tic) + if (player->draftpower < FRACUNIT) + { + fixed_t add = (FRACUNIT/200) + ((9 - player->kartspeed) * ((3*FRACUNIT)/1600));; + player->draftpower += add; + + if (player->bot && player->botvars.rival) + { + // Double speed for the rival! + player->draftpower += add; + } + + if (gametype == GT_BATTLE) + { + // TODO: gametyperules + // Double speed in Battle + player->draftpower += add; + } + } + + if (player->draftpower > FRACUNIT) + { + player->draftpower = FRACUNIT; + } + + // Play draft finish noise + if (olddraft < FRACUNIT && player->draftpower >= FRACUNIT) + { + S_StartSound(player->mo, sfx_cdfm62); + } + + // Spawn in the visual! + K_DrawDraftCombiring(player, dest, dist, draftdistance, false); + return true; +} + /** \brief Updates the player's drafting values once per frame \param player player object passed from K_KartPlayerThink @@ -1129,6 +1304,9 @@ static boolean K_HasInfiniteTether(player_t *player) */ static void K_UpdateDraft(player_t *player) { + const boolean addUfo = ((specialStage.active == true) + && (specialStage.ufo != NULL && P_MobjWasRemoved(specialStage.ufo) == false)); + fixed_t topspd = K_GetKartSpeed(player, false, false); fixed_t draftdistance; fixed_t minDist; @@ -1166,104 +1344,43 @@ static void K_UpdateDraft(player_t *player) } // Not enough speed to draft. - if (player->speed >= 20*player->mo->scale) + if (player->speed >= 20 * player->mo->scale) { -//#define EASYDRAFTTEST + if (addUfo == true) + { + // Tether off of the UFO! + if (K_TryDraft(player, specialStage.ufo, minDist, draftdistance, leniency) == true) + { + return; // Finished doing our draft. + } + } + // Let's hunt for players to draft off of! for (i = 0; i < MAXPLAYERS; i++) { - fixed_t dist, olddraft; -#ifndef EASYDRAFTTEST - angle_t yourangle, theirangle, diff; -#endif + player_t *otherPlayer = NULL; - if (!playeringame[i] || players[i].spectator || !players[i].mo) - continue; - -#ifndef EASYDRAFTTEST - // Don't draft on yourself :V - if (&players[i] == player) - continue; -#endif - - // Not enough speed to draft off of. - if (players[i].speed < 20*players[i].mo->scale) - continue; - - // No tethering off of the guy who got the starting bonus :P - if (players[i].startboost > 0) - continue; - -#ifndef EASYDRAFTTEST - yourangle = K_MomentumAngle(player->mo); - theirangle = K_MomentumAngle(players[i].mo); - - diff = R_PointToAngle2(player->mo->x, player->mo->y, players[i].mo->x, players[i].mo->y) - yourangle; - if (diff > ANGLE_180) - diff = InvAngle(diff); - - // Not in front of this player. - if (diff > ANG10) - continue; - - diff = yourangle - theirangle; - if (diff > ANGLE_180) - diff = InvAngle(diff); - - // Not moving in the same direction. - if (diff > ANGLE_90) - continue; -#endif - - dist = P_AproxDistance(P_AproxDistance(players[i].mo->x - player->mo->x, players[i].mo->y - player->mo->y), players[i].mo->z - player->mo->z); - -#ifndef EASYDRAFTTEST - // TOO close to draft. - if (dist < minDist) - continue; - - // Not close enough to draft. - if (dist > draftdistance && draftdistance > 0) - continue; -#endif - - olddraft = player->draftpower; - - player->draftleeway = leniency; - player->lastdraft = i; - - // Draft power is used later in K_GetKartBoostPower, ranging from 0 for normal speed and FRACUNIT for max draft speed. - // How much this increments every tic biases toward acceleration! (min speed gets 1.5% per tic, max speed gets 0.5% per tic) - if (player->draftpower < FRACUNIT) + if (playeringame[i] == false) { - fixed_t add = (FRACUNIT/200) + ((9 - player->kartspeed) * ((3*FRACUNIT)/1600));; - player->draftpower += add; - - if (player->bot && player->botvars.rival) - { - // Double speed for the rival! - player->draftpower += add; - } - - if (gametype == GT_BATTLE) - { - // TODO: gametyperules - // Double speed in Battle - player->draftpower += add; - } + continue; } - if (player->draftpower > FRACUNIT) - player->draftpower = FRACUNIT; + otherPlayer = &players[i]; - // Play draft finish noise - if (olddraft < FRACUNIT && player->draftpower >= FRACUNIT) - S_StartSound(player->mo, sfx_cdfm62); + if (otherPlayer->spectator == true) + { + continue; + } - // Spawn in the visual! - K_DrawDraftCombiring(player, &players[i], dist, draftdistance, false); + if (otherPlayer->mo == NULL || P_MobjWasRemoved(otherPlayer->mo) == true) + { + continue; + } - return; // Finished doing our draft. + if (K_TryDraft(player, otherPlayer->mo, minDist, draftdistance, leniency) == true) + { + return; // Finished doing our draft. + } } } @@ -1284,7 +1401,13 @@ static void K_UpdateDraft(player_t *player) { player_t *victim = &players[player->lastdraft]; fixed_t dist = P_AproxDistance(P_AproxDistance(victim->mo->x - player->mo->x, victim->mo->y - player->mo->y), victim->mo->z - player->mo->z); - K_DrawDraftCombiring(player, victim, dist, draftdistance, true); + K_DrawDraftCombiring(player, victim->mo, dist, draftdistance, true); + } + else if (addUfo == true) + { + // kind of a hack to not have to mess with how lastdraft works + fixed_t dist = P_AproxDistance(P_AproxDistance(specialStage.ufo->x - player->mo->x, specialStage.ufo->y - player->mo->y), specialStage.ufo->z - player->mo->z); + K_DrawDraftCombiring(player, specialStage.ufo, dist, draftdistance, true); } } else // Remove draft speed boost. @@ -3135,7 +3258,7 @@ boolean K_PlayerShrinkCheat(player_t *player) return ( (player->pflags & PF_SHRINKACTIVE) && (player->bot == false) - && (modeattacking == false) // Anyone want to make another record attack category? + && (modeattacking == ATTACKING_NONE) // Anyone want to make another record attack category? ); } @@ -6664,12 +6787,18 @@ static void K_MoveHeldObjects(player_t *player) } } -player_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range) +mobj_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range) { fixed_t best = INT32_MAX; - player_t *wtarg = NULL; + mobj_t *wtarg = NULL; INT32 i; + if (specialStage.active == true) + { + // Always target the UFO. + return specialStage.ufo; + } + for (i = 0; i < MAXPLAYERS; i++) { angle_t thisang = ANGLE_MAX; @@ -6685,7 +6814,7 @@ player_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range) player = &players[i]; // Don't target yourself, stupid. - if (player == source) + if (source != NULL && player == source) { continue; } @@ -6724,7 +6853,7 @@ player_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range) if (gametyperules & GTR_CIRCUIT) { - if (player->position >= source->position) + if (source != NULL && player->position >= source->position) { // Don't pay attention to people who aren't above your position continue; @@ -6766,7 +6895,7 @@ player_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range) if (thisScore < best) { - wtarg = player; + wtarg = player->mo; best = thisScore; } } @@ -7895,24 +8024,32 @@ void K_KartPlayerAfterThink(player_t *player) // Jawz reticule (seeking) if (player->itemtype == KITEM_JAWZ && (player->pflags & PF_ITEMOUT)) { - INT32 lastTargID = player->lastjawztarget; - player_t *lastTarg = NULL; - player_t *targ = NULL; + const INT32 lastTargID = player->lastjawztarget; + mobj_t *lastTarg = NULL; + + INT32 targID = MAXPLAYERS; + mobj_t *targ = NULL; + mobj_t *ret = NULL; - if ((lastTargID >= 0 && lastTargID <= MAXPLAYERS) + if (specialStage.active == true && lastTargID == MAXPLAYERS) + { + // Aiming at the UFO. + lastTarg = specialStage.ufo; + } + else if ((lastTargID >= 0 && lastTargID <= MAXPLAYERS) && playeringame[lastTargID] == true) { if (players[lastTargID].spectator == false) { - lastTarg = &players[lastTargID]; + lastTarg = players[lastTargID].mo; } } if (player->throwdir == -1) { // Backwards Jawz targets yourself. - targ = player; + targ = player->mo; player->jawztargetdelay = 0; } else @@ -7921,9 +8058,14 @@ void K_KartPlayerAfterThink(player_t *player) targ = K_FindJawzTarget(player->mo, player, ANGLE_45); } - if (targ != NULL && targ->mo != NULL && P_MobjWasRemoved(targ->mo) == false) + if (targ != NULL && P_MobjWasRemoved(targ) == false) { - if (targ - players == lastTargID) + if (targ->player != NULL) + { + targID = targ->player - players; + } + + if (targID == lastTargID) { // Increment delay. if (player->jawztargetdelay < 10) @@ -7942,33 +8084,33 @@ void K_KartPlayerAfterThink(player_t *player) else { // Allow a swap. - if (P_IsDisplayPlayer(player) || P_IsDisplayPlayer(targ)) + if (P_IsDisplayPlayer(player) || P_IsDisplayPlayer(targ->player)) { S_StartSound(NULL, sfx_s3k89); } else { - S_StartSound(targ->mo, sfx_s3k89); + S_StartSound(targ, sfx_s3k89); } - player->lastjawztarget = targ - players; + player->lastjawztarget = targID; player->jawztargetdelay = 5; } } } - if (targ == NULL || targ->mo == NULL || P_MobjWasRemoved(targ->mo) == true) + if (targ == NULL || P_MobjWasRemoved(targ) == true) { player->lastjawztarget = -1; player->jawztargetdelay = 0; return; } - ret = P_SpawnMobj(targ->mo->x, targ->mo->y, targ->mo->z, MT_PLAYERRETICULE); - ret->old_x = targ->mo->old_x; - ret->old_y = targ->mo->old_y; - ret->old_z = targ->mo->old_z; - P_SetTarget(&ret->target, targ->mo); + ret = P_SpawnMobj(targ->x, targ->y, targ->z, MT_PLAYERRETICULE); + ret->old_x = targ->old_x; + ret->old_y = targ->old_y; + ret->old_z = targ->old_z; + P_SetTarget(&ret->target, targ); ret->frame |= ((leveltime % 10) / 2); ret->tics = 1; ret->color = player->skincolor; diff --git a/src/k_kart.h b/src/k_kart.h index 8831fe0a4..4db338643 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -56,6 +56,9 @@ UINT8 K_ItemResultToAmount(SINT8 getitem); tic_t K_GetItemCooldown(SINT8 itemResult); void K_SetItemCooldown(SINT8 itemResult, tic_t time); void K_RunItemCooldowns(void); + +boolean K_TimeAttackRules(void); + fixed_t K_GetMobjWeight(mobj_t *mobj, mobj_t *against); boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2); boolean K_KartSolidBounce(mobj_t *bounceMobj, mobj_t *solidMobj); @@ -118,7 +121,7 @@ void K_UpdateHnextList(player_t *player, boolean clean); void K_DropHnextList(player_t *player, boolean keepshields); void K_RepairOrbitChain(mobj_t *orbit); void K_CalculateBananaSlope(mobj_t *mobj, fixed_t x, fixed_t y, fixed_t z, fixed_t radius, fixed_t height, boolean flip, boolean player); -player_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range); +mobj_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range); INT32 K_GetKartRingPower(player_t *player, boolean boosted); void K_UpdateDistanceFromFinishLine(player_t *const player); boolean K_CheckPlayersRespawnColliding(INT32 playernum, fixed_t x, fixed_t y); diff --git a/src/k_objects.h b/src/k_objects.h index 4cc4ed1c3..284ec1c61 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -58,4 +58,15 @@ void Obj_DuelBombInit(mobj_t *bomb); mobj_t *Obj_SpawnBrolyKi(mobj_t *source, tic_t duration); void Obj_BrolyKiThink(mobj_t *ki); +/* Special Stage UFO */ +waypoint_t *K_GetSpecialUFOWaypoint(mobj_t *ufo); +void Obj_SpecialUFOThinker(mobj_t *ufo); +boolean Obj_SpecialUFODamage(mobj_t *ufo, mobj_t *inflictor, mobj_t *source, UINT8 damageType); +void Obj_PlayerUFOCollide(mobj_t *ufo, mobj_t *other); +void Obj_UFOPieceThink(mobj_t *piece); +void Obj_UFOPieceDead(mobj_t *piece); +void Obj_UFOPieceRemoved(mobj_t *piece); +mobj_t *Obj_CreateSpecialUFO(void); +UINT32 K_GetSpecialUFODistance(void); + #endif/*k_objects_H*/ diff --git a/src/k_roulette.c b/src/k_roulette.c index dff3282e7..4f3de015f 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -110,7 +110,6 @@ static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS-1][2] = { - //K L { 2, 1 }, // Sneaker { 0, 0 }, // Rocket Sneaker { 4, 1 }, // Invincibility @@ -143,6 +142,40 @@ static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS-1][2] = { 0, 0 } // Gachabom x3 }; +static UINT8 K_KartItemOddsSpecial[NUMKARTRESULTS-1][4] = +{ + { 1, 1, 0, 0 }, // Sneaker + { 0, 0, 0, 0 }, // Rocket Sneaker + { 0, 0, 0, 0 }, // Invincibility + { 0, 0, 0, 0 }, // Banana + { 0, 0, 0, 0 }, // Eggman Monitor + { 1, 1, 0, 0 }, // Orbinaut + { 1, 1, 0, 0 }, // Jawz + { 0, 0, 0, 0 }, // Mine + { 0, 0, 0, 0 }, // Land Mine + { 0, 0, 0, 0 }, // Ballhog + { 0, 0, 0, 1 }, // Self-Propelled Bomb + { 0, 0, 0, 0 }, // Grow + { 0, 0, 0, 0 }, // Shrink + { 0, 0, 0, 0 }, // Lightning Shield + { 0, 0, 0, 0 }, // Bubble Shield + { 0, 0, 0, 0 }, // Flame Shield + { 0, 0, 0, 0 }, // Hyudoro + { 0, 0, 0, 0 }, // Pogo Spring + { 0, 0, 0, 0 }, // Super Ring + { 0, 0, 0, 0 }, // Kitchen Sink + { 0, 0, 0, 0 }, // Drop Target + { 0, 0, 0, 0 }, // Garden Top + { 0, 0, 0, 0 }, // Gachabom + { 0, 1, 1, 0 }, // Sneaker x2 + { 0, 0, 1, 1 }, // Sneaker x3 + { 0, 0, 0, 0 }, // Banana x3 + { 0, 1, 1, 0 }, // Orbinaut x3 + { 0, 0, 1, 1 }, // Orbinaut x4 + { 0, 0, 1, 1 }, // Jawz x2 + { 0, 0, 0, 0 } // Gachabom x3 +}; + static kartitems_t K_KartItemReelTimeAttack[] = { KITEM_SNEAKER, @@ -326,7 +359,6 @@ static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers return 0; } -#if 0 if (specialStage.active == true) { UINT32 ufoDis = K_GetSpecialUFODistance(); @@ -343,7 +375,6 @@ static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers } } else -#endif { UINT8 i; for (i = 0; i < MAXPLAYERS; i++) @@ -475,6 +506,11 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, I_Assert(pos < 2); // DO NOT allow positions past the bounds of the table newOdds = K_KartItemOddsBattle[item-1][pos]; } + else if (specialStage.active == true) + { + I_Assert(pos < 4); // Ditto + newOdds = K_KartItemOddsSpecial[item-1][pos]; + } else { I_Assert(pos < 8); // Ditto @@ -537,29 +573,32 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, return 0; } - if (roulette->firstDist < ENDDIST*2 // No SPB when 1st is almost done - || position == 1) // No SPB for 1st ever + if (specialStage.active == false) { - return 0; - } - else - { - const UINT32 dist = max(0, ((signed)roulette->secondToFirst) - SPBSTARTDIST); - const UINT32 distRange = SPBFORCEDIST - SPBSTARTDIST; - const fixed_t maxOdds = 20 << FRACBITS; - fixed_t multiplier = FixedDiv(dist, distRange); - - if (multiplier < 0) + if (roulette->firstDist < ENDDIST*2 // No SPB when 1st is almost done + || position == 1) // No SPB for 1st ever { - multiplier = 0; + return 0; } - - if (multiplier > FRACUNIT) + else { - multiplier = FRACUNIT; - } + const UINT32 dist = max(0, ((signed)roulette->secondToFirst) - SPBSTARTDIST); + const UINT32 distRange = SPBFORCEDIST - SPBSTARTDIST; + const fixed_t maxOdds = 20 << FRACBITS; + fixed_t multiplier = FixedDiv(dist, distRange); - newOdds = FixedMul(maxOdds, multiplier); + if (multiplier < 0) + { + multiplier = 0; + } + + if (multiplier > FRACUNIT) + { + multiplier = FRACUNIT; + } + + newOdds = FixedMul(maxOdds, multiplier); + } } break; } @@ -666,6 +705,11 @@ static UINT8 K_FindUseodds(const player_t *player, itemroulette_t *const roulett oddsValid[i] = false; continue; } + else if (specialStage.active == true && i > 3) + { + oddsValid[i] = false; + continue; + } for (j = 1; j < NUMKARTRESULTS; j++) { @@ -690,14 +734,24 @@ static UINT8 K_FindUseodds(const player_t *player, itemroulette_t *const roulett } else { - SETUPDISTTABLE(0,1); - SETUPDISTTABLE(1,1); - SETUPDISTTABLE(2,1); - SETUPDISTTABLE(3,2); - SETUPDISTTABLE(4,2); - SETUPDISTTABLE(5,3); - SETUPDISTTABLE(6,3); - SETUPDISTTABLE(7,1); + if (specialStage.active == true) // Special Stages + { + SETUPDISTTABLE(0,2); + SETUPDISTTABLE(1,2); + SETUPDISTTABLE(2,3); + SETUPDISTTABLE(3,1); + } + else + { + SETUPDISTTABLE(0,1); + SETUPDISTTABLE(1,1); + SETUPDISTTABLE(2,1); + SETUPDISTTABLE(3,2); + SETUPDISTTABLE(4,2); + SETUPDISTTABLE(5,3); + SETUPDISTTABLE(6,3); + SETUPDISTTABLE(7,1); + } for (i = 0; i < totalSize; i++) { @@ -754,6 +808,11 @@ static boolean K_ForcedSPB(const player_t *player, itemroulette_t *const roulett return false; } + if (specialStage.active == true) + { + return false; + } + if (player == NULL) { return false; @@ -845,19 +904,36 @@ static void K_InitRoulette(itemroulette_t *const roulette) roulette->exiting++; } - if (players[i].position == 1) + if (specialStage.active == true) { - roulette->firstDist = K_UndoMapScaling(players[i].distancetofinish); + UINT32 dis = K_UndoMapScaling(players[i].distancetofinish); + if (dis < roulette->secondDist) + { + roulette->secondDist = dis; + } } - - if (players[i].position == 2) + else { - roulette->secondDist = K_UndoMapScaling(players[i].distancetofinish); + if (players[i].position == 1) + { + roulette->firstDist = K_UndoMapScaling(players[i].distancetofinish); + } + + if (players[i].position == 2) + { + roulette->secondDist = K_UndoMapScaling(players[i].distancetofinish); + } } } + if (specialStage.active == true) + { + roulette->firstDist = K_UndoMapScaling(K_GetSpecialUFODistance()); + } + // Calculate 2nd's distance from 1st, for SPB - if (roulette->firstDist != UINT32_MAX && roulette->secondDist != UINT32_MAX) + if (roulette->firstDist != UINT32_MAX && roulette->secondDist != UINT32_MAX + && roulette->secondDist > roulette->firstDist) { roulette->secondToFirst = roulette->secondDist - roulette->firstDist; roulette->secondToFirst = K_ScaleItemDistance(roulette->secondToFirst, 16 - roulette->playing); // Reversed scaling @@ -964,7 +1040,7 @@ static void K_CalculateRouletteSpeed(itemroulette_t *const roulette) fixed_t progress = 0; fixed_t total = 0; - if (modeattacking || roulette->playing <= 1) + if (K_TimeAttackRules() == true) { // Time Attack rules; use a consistent speed. roulette->tics = roulette->speed = ROULETTE_SPEED_TIMEATTACK; @@ -1047,7 +1123,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet return; } - else if (modeattacking || roulette->playing <= 1) + else if (K_TimeAttackRules() == true) { switch (gametype) { diff --git a/src/k_specialstage.c b/src/k_specialstage.c index 3a2d751ac..e0551bef1 100644 --- a/src/k_specialstage.c +++ b/src/k_specialstage.c @@ -20,6 +20,7 @@ #include "st_stuff.h" #include "z_zone.h" #include "k_waypoint.h" +#include "k_objects.h" struct specialStage specialStage; @@ -43,6 +44,7 @@ void K_InitSpecialStage(void) INT32 i; specialStage.beamDist = UINT32_MAX; // TODO: make proper value + P_SetTarget(&specialStage.ufo, Obj_CreateSpecialUFO()); for (i = 0; i < MAXPLAYERS; i++) { diff --git a/src/k_specialstage.h b/src/k_specialstage.h index 8e11d761d..c93136b99 100644 --- a/src/k_specialstage.h +++ b/src/k_specialstage.h @@ -22,7 +22,7 @@ extern struct specialStage boolean encore; ///< Copy of encore, just to make sure you can't cheat it with cvars UINT32 beamDist; ///< Where the exit beam is. - mobj_t *capsule; ///< The Chaos Emerald capsule. + mobj_t *ufo; ///< The Chaos Emerald capsule. } specialStage; /*-------------------------------------------------- diff --git a/src/k_waypoint.c b/src/k_waypoint.c index a36cdfeb7..d53a4bbd3 100644 --- a/src/k_waypoint.c +++ b/src/k_waypoint.c @@ -1070,6 +1070,45 @@ static boolean K_WaypointPathfindReachedEnd(void *data, void *setupData) return isEnd; } +/*-------------------------------------------------- + static boolean K_WaypointPathfindNextValid(void *data, void *setupData) + + Returns if the current waypoint data has a next waypoint. + + Input Arguments:- + data - Should point to a pathfindnode_t to compare + setupData - Should point to the pathfindsetup_t to compare + + Return:- + True if the waypoint has a next waypoint, false otherwise. +--------------------------------------------------*/ +static boolean K_WaypointPathfindNextValid(void *data, void *setupData) +{ + boolean nextValid = false; + + if (data == NULL || setupData == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindNextValid received NULL data.\n"); + } + else + { + pathfindnode_t *node = (pathfindnode_t *)data; + pathfindsetup_t *setup = (pathfindsetup_t *)setupData; + waypoint_t *wp = (waypoint_t *)node->nodedata; + + if (setup->getconnectednodes == K_WaypointPathfindGetPrev) + { + nextValid = (wp->numprevwaypoints > 0U); + } + else + { + nextValid = (wp->numnextwaypoints > 0U); + } + } + + return nextValid; +} + /*-------------------------------------------------- static boolean K_WaypointPathfindReachedGScore(void *data, void *setupData) @@ -1094,8 +1133,9 @@ static boolean K_WaypointPathfindReachedGScore(void *data, void *setupData) { pathfindnode_t *node = (pathfindnode_t *)data; pathfindsetup_t *setup = (pathfindsetup_t *)setupData; + boolean nextValid = K_WaypointPathfindNextValid(data, setupData); - scoreReached = (node->gscore >= setup->endgscore); + scoreReached = (node->gscore >= setup->endgscore) || (nextValid == false); } return scoreReached; @@ -1127,8 +1167,9 @@ static boolean K_WaypointPathfindReachedGScoreSpawnable(void *data, void *setupD pathfindnode_t *node = (pathfindnode_t *)data; pathfindsetup_t *setup = (pathfindsetup_t *)setupData; waypoint_t *wp = (waypoint_t *)node->nodedata; + boolean nextValid = K_WaypointPathfindNextValid(data, setupData); - scoreReached = (node->gscore >= setup->endgscore); + scoreReached = (node->gscore >= setup->endgscore) || (nextValid == false); spawnable = K_GetWaypointIsSpawnpoint(wp); } @@ -1251,13 +1292,6 @@ boolean K_PathfindThruCircuit( "K_PathfindThruCircuit: sourcewaypoint with ID %d has no next waypoint\n", K_GetWaypointID(sourcewaypoint)); } - else if (((huntbackwards == false) && (finishline->numprevwaypoints == 0)) - || ((huntbackwards == true) && (finishline->numnextwaypoints == 0))) - { - CONS_Debug(DBG_GAMELOGIC, - "K_PathfindThruCircuit: finishline with ID %d has no previous waypoint\n", - K_GetWaypointID(finishline)); - } else { pathfindsetup_t pathfindsetup = {0}; @@ -1334,13 +1368,6 @@ boolean K_PathfindThruCircuitSpawnable( "K_PathfindThruCircuitSpawnable: sourcewaypoint with ID %d has no next waypoint\n", K_GetWaypointID(sourcewaypoint)); } - else if (((huntbackwards == false) && (finishline->numprevwaypoints == 0)) - || ((huntbackwards == true) && (finishline->numnextwaypoints == 0))) - { - CONS_Debug(DBG_GAMELOGIC, - "K_PathfindThruCircuitSpawnable: finishline with ID %d has no previous waypoint\n", - K_GetWaypointID(finishline)); - } else { pathfindsetup_t pathfindsetup = {0}; diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index b3b5ff2c0..00eadea36 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -9,4 +9,5 @@ target_sources(SRB2SDL2 PRIVATE jawz.c duel-bomb.c broly.c + ufo.c ) diff --git a/src/objects/jawz.c b/src/objects/jawz.c index cc241ba87..0f952551a 100644 --- a/src/objects/jawz.c +++ b/src/objects/jawz.c @@ -24,6 +24,7 @@ #include "../k_waypoint.h" #include "../k_respawn.h" #include "../k_collide.h" +#include "../k_specialstage.h" #define MAX_JAWZ_TURN (ANGLE_90 / 15) // We can turn a maximum of 6 degrees per frame at regular max speed @@ -140,17 +141,21 @@ static void JawzChase(mobj_t *th, boolean grounded) if (jawz_chase(th) == NULL || P_MobjWasRemoved(jawz_chase(th)) == true) { + mobj_t *newChase = NULL; + player_t *owner = NULL; + th->angle = K_MomentumAngle(th); - if (jawz_owner(th) != NULL && P_MobjWasRemoved(jawz_owner(th)) == false - && jawz_owner(th)->player != NULL) + if ((jawz_owner(th) != NULL && P_MobjWasRemoved(jawz_owner(th)) == false) + && (jawz_owner(th)->player != NULL)) { - player_t *newPlayer = K_FindJawzTarget(th, jawz_owner(th)->player, ANGLE_90); + owner = jawz_owner(th)->player; + } - if (newPlayer != NULL) - { - P_SetTarget(&jawz_chase(th), newPlayer->mo); - } + newChase = K_FindJawzTarget(th, owner, ANGLE_90); + if (newChase != NULL) + { + P_SetTarget(&jawz_chase(th), newChase); } } @@ -181,6 +186,11 @@ static void JawzChase(mobj_t *th, boolean grounded) } } +static boolean JawzSteersBetter(void) +{ + return !!!(gametyperules & GTR_CIRCUIT); +} + void Obj_JawzThink(mobj_t *th) { mobj_t *ghost = P_SpawnGhostMobj(th); @@ -211,7 +221,7 @@ void Obj_JawzThink(mobj_t *th) ghost->colorized = true; } - if (!(gametyperules & GTR_CIRCUIT)) + if (JawzSteersBetter() == true) { th->friction = max(0, 3 * th->friction / 4); } diff --git a/src/objects/spb.c b/src/objects/spb.c index 32cb0e12d..b76dfd4f8 100644 --- a/src/objects/spb.c +++ b/src/objects/spb.c @@ -23,6 +23,7 @@ #include "../z_zone.h" #include "../k_waypoint.h" #include "../k_respawn.h" +#include "../k_specialstage.h" #define SPB_SLIPTIDEDELTA (ANG1 * 3) #define SPB_STEERDELTA (ANGLE_90 - ANG10) @@ -292,7 +293,7 @@ static boolean SPBSeekSoundPlaying(mobj_t *spb) || S_SoundPlaying(spb, sfx_spbskc)); } -static void SPBSeek(mobj_t *spb, player_t *bestPlayer) +static void SPBSeek(mobj_t *spb, mobj_t *bestMobj) { const fixed_t desiredSpeed = SPB_DEFAULTSPEED; @@ -321,16 +322,15 @@ static void SPBSeek(mobj_t *spb, player_t *bestPlayer) spb_lastplayer(spb) = -1; // Just make sure this is reset - if (bestPlayer == NULL - || bestPlayer->mo == NULL - || P_MobjWasRemoved(bestPlayer->mo) == true - || bestPlayer->mo->health <= 0 - || (bestPlayer->respawn.state != RESPAWNST_NONE)) + if (bestMobj == NULL + || P_MobjWasRemoved(bestMobj) == true + || bestMobj->health <= 0 + || (bestMobj->player != NULL && bestMobj->player->respawn.state != RESPAWNST_NONE)) { // No one there? Completely STOP. spb->momx = spb->momy = spb->momz = 0; - if (bestPlayer == NULL) + if (bestMobj == NULL) { spbplace = -1; } @@ -339,8 +339,16 @@ static void SPBSeek(mobj_t *spb, player_t *bestPlayer) } // Found someone, now get close enough to initiate the slaughter... - P_SetTarget(&spb_chase(spb), bestPlayer->mo); - spbplace = bestPlayer->position; + P_SetTarget(&spb_chase(spb), bestMobj); + + if (bestMobj->player != NULL) + { + spbplace = bestMobj->player->position; + } + else + { + spbplace = 1; + } dist = SPBDist(spb, spb_chase(spb)); activeDist = FixedMul(SPB_ACTIVEDIST, spb_chase(spb)->scale); @@ -400,7 +408,18 @@ static void SPBSeek(mobj_t *spb, player_t *bestPlayer) curWaypoint = K_GetWaypointFromIndex( (size_t)spb_curwaypoint(spb) ); } - destWaypoint = bestPlayer->nextwaypoint; + if (bestMobj->player != NULL) + { + destWaypoint = bestMobj->player->nextwaypoint; + } + else if (bestMobj->type == MT_SPECIAL_UFO) + { + destWaypoint = K_GetSpecialUFOWaypoint(bestMobj); + } + else + { + destWaypoint = K_GetBestWaypointForMobj(bestMobj); + } if (curWaypoint != NULL) { @@ -433,7 +452,8 @@ static void SPBSeek(mobj_t *spb, player_t *bestPlayer) if (pathfindsuccess == true) { - if (cv_spbtest.value) { + if (cv_spbtest.value) + { if (pathtoplayer.numnodes > 1) { // Go to the next waypoint. @@ -529,45 +549,48 @@ static void SPBSeek(mobj_t *spb, player_t *bestPlayer) SetSPBSpeed(spb, xySpeed, zSpeed); - // see if a player is near us, if they are, try to hit them by slightly thrusting towards them, otherwise, bleh! - steerDist = 1536 * mapobjectscale; - - for (i = 0; i < MAXPLAYERS; i++) + if (specialStage.active == false) { - fixed_t ourDist = INT32_MAX; - INT32 ourDelta = INT32_MAX; + // see if a player is near us, if they are, try to hit them by slightly thrusting towards them, otherwise, bleh! + steerDist = 1536 * mapobjectscale; - if (playeringame[i] == false || players[i].spectator == true) + for (i = 0; i < MAXPLAYERS; i++) { - // Not in-game - continue; + fixed_t ourDist = INT32_MAX; + INT32 ourDelta = INT32_MAX; + + if (playeringame[i] == false || players[i].spectator == true) + { + // Not in-game + continue; + } + + if (players[i].mo == NULL || P_MobjWasRemoved(players[i].mo) == true) + { + // Invalid mobj + continue; + } + + ourDelta = AngleDelta(spb->angle, R_PointToAngle2(spb->x, spb->y, players[i].mo->x, players[i].mo->y)); + if (ourDelta > SPB_STEERDELTA) + { + // Check if the angle wouldn't make us LOSE speed. + continue; + } + + ourDist = R_PointToDist2(spb->x, spb->y, players[i].mo->x, players[i].mo->y); + if (ourDist < steerDist) + { + steerDist = ourDist; + steerMobj = players[i].mo; // it doesn't matter if we override this guy now. + } } - if (players[i].mo == NULL || P_MobjWasRemoved(players[i].mo) == true) + // different player from our main target, try and ram into em~! + if (steerMobj != NULL && steerMobj != spb_chase(spb)) { - // Invalid mobj - continue; + P_Thrust(spb, R_PointToAngle2(spb->x, spb->y, steerMobj->x, steerMobj->y), spb_speed(spb) / 4); } - - ourDelta = AngleDelta(spb->angle, R_PointToAngle2(spb->x, spb->y, players[i].mo->x, players[i].mo->y)); - if (ourDelta > SPB_STEERDELTA) - { - // Check if the angle wouldn't make us LOSE speed. - continue; - } - - ourDist = R_PointToDist2(spb->x, spb->y, players[i].mo->x, players[i].mo->y); - if (ourDist < steerDist) - { - steerDist = ourDist; - steerMobj = players[i].mo; // it doesn't matter if we override this guy now. - } - } - - // different player from our main target, try and ram into em~! - if (steerMobj != NULL && steerMobj != spb_chase(spb)) - { - P_Thrust(spb, R_PointToAngle2(spb->x, spb->y, steerMobj->x, steerMobj->y), spb_speed(spb) / 4); } if (sliptide != 0) @@ -593,7 +616,7 @@ static void SPBSeek(mobj_t *spb, player_t *bestPlayer) } } -static void SPBChase(mobj_t *spb, player_t *bestPlayer) +static void SPBChase(mobj_t *spb, mobj_t *bestMobj) { fixed_t baseSpeed = 0; fixed_t maxSpeed = 0; @@ -642,8 +665,8 @@ static void SPBChase(mobj_t *spb, player_t *bestPlayer) S_StartSound(spb, spb->info->activesound); } - // Maybe we want SPB to target an object later? IDK lol chasePlayer = chase->player; + if (chasePlayer != NULL) { UINT8 fracmax = 32; @@ -679,7 +702,8 @@ static void SPBChase(mobj_t *spb, player_t *bestPlayer) cy = chasePlayer->cmomy; // Switch targets if you're no longer 1st for long enough - if (bestPlayer != NULL && chasePlayer->position <= bestPlayer->position) + if (bestMobj != NULL + && (bestMobj->player == NULL || chasePlayer->position <= bestMobj->player->position)) { spb_modetimer(spb) = SPB_HOTPOTATO; } @@ -697,6 +721,12 @@ static void SPBChase(mobj_t *spb, player_t *bestPlayer) } } } + else + { + spb_lastplayer(spb) = -1; + spbplace = 1; + spb_modetimer(spb) = SPB_HOTPOTATO; + } dist = P_AproxDistance(P_AproxDistance(spb->x - chase->x, spb->y - chase->y), spb->z - chase->z); @@ -807,7 +837,7 @@ static void SPBWait(mobj_t *spb) void Obj_SPBThink(mobj_t *spb) { mobj_t *ghost = NULL; - player_t *bestPlayer = NULL; + mobj_t *bestMobj = NULL; UINT8 bestRank = UINT8_MAX; size_t i; @@ -844,6 +874,15 @@ void Obj_SPBThink(mobj_t *spb) } else { + if (specialStage.active == true) + { + if (specialStage.ufo != NULL && P_MobjWasRemoved(specialStage.ufo) == false) + { + bestRank = 1; + bestMobj = specialStage.ufo; + } + } + // Find the player with the best rank for (i = 0; i < MAXPLAYERS; i++) { @@ -886,7 +925,7 @@ void Obj_SPBThink(mobj_t *spb) if (player->position < bestRank) { bestRank = player->position; - bestPlayer = player; + bestMobj = player->mo; } } @@ -894,11 +933,11 @@ void Obj_SPBThink(mobj_t *spb) { case SPB_MODE_SEEK: default: - SPBSeek(spb, bestPlayer); + SPBSeek(spb, bestMobj); break; case SPB_MODE_CHASE: - SPBChase(spb, bestPlayer); + SPBChase(spb, bestMobj); break; case SPB_MODE_WAIT: @@ -970,14 +1009,18 @@ void Obj_SPBExplode(mobj_t *spb) void Obj_SPBTouch(mobj_t *spb, mobj_t *toucher) { - player_t *player = toucher->player; + player_t *const player = toucher->player; + mobj_t *owner = NULL; + mobj_t *chase = NULL; if (spb_intangible(spb) > 0) { return; } - if ((spb_owner(spb) == toucher || spb_owner(spb) == toucher->target) + owner = spb_owner(spb); + + if ((owner == toucher || owner == toucher->target) && (spb_nothink(spb) > 0)) { return; @@ -988,30 +1031,34 @@ void Obj_SPBTouch(mobj_t *spb, mobj_t *toucher) return; } - if (player->spectator == true) + if (player != NULL) { - return; + if (player->spectator == true) + { + return; + } + + if (player->bubbleblowup > 0) + { + // Stun the SPB, and remove the shield. + K_DropHnextList(player, false); + spb_mode(spb) = SPB_MODE_WAIT; + spb_modetimer(spb) = 55; // Slightly over the respawn timer length + return; + } } - if (player->bubbleblowup > 0) - { - // Stun the SPB, and remove the shield. - K_DropHnextList(player, false); - spb_mode(spb) = SPB_MODE_WAIT; - spb_modetimer(spb) = 55; // Slightly over the respawn timer length - return; - } - - if (spb_chase(spb) != NULL && P_MobjWasRemoved(spb_chase(spb)) == false - && toucher == spb_chase(spb)) + chase = spb_chase(spb); + if (chase != NULL && P_MobjWasRemoved(chase) == false + && toucher == chase) { // Cause the explosion. Obj_SPBExplode(spb); return; } - else + else if (toucher->flags & MF_SHOOTABLE) { // Regular spinout, please. - P_DamageMobj(toucher, spb, spb_owner(spb), 1, DMG_NORMAL); + P_DamageMobj(toucher, spb, owner, 1, DMG_NORMAL); } } diff --git a/src/objects/ufo.c b/src/objects/ufo.c new file mode 100644 index 000000000..5eaf7f645 --- /dev/null +++ b/src/objects/ufo.c @@ -0,0 +1,832 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2022 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2022 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 ufo.c +/// \brief Special Stage UFO + Emerald handler + +#include "../doomdef.h" +#include "../doomstat.h" +#include "../info.h" +#include "../k_kart.h" +#include "../k_objects.h" +#include "../m_random.h" +#include "../p_local.h" +#include "../r_main.h" +#include "../s_sound.h" +#include "../g_game.h" +#include "../z_zone.h" +#include "../k_waypoint.h" +#include "../k_specialstage.h" + +#define UFO_BASE_SPEED (42 * FRACUNIT) // UFO's slowest speed. +#define UFO_SPEEDUP (FRACUNIT >> 1) // Acceleration +#define UFO_SLOWDOWN (FRACUNIT >> 1) // Deceleration +#define UFO_SPACING (768 * FRACUNIT) // How far the UFO wants to stay in front +#define UFO_DEADZONE (2048 * FRACUNIT) // Deadzone where it won't update it's speed as much. +#define UFO_SPEEDFACTOR (FRACUNIT * 3 / 4) // Factor of player's best speed, to make it more fair. +#define UFO_DAMAGED_SPEED (UFO_BASE_SPEED >> 1) // Speed to add when UFO takes damage. +#define UFO_START_SPEED (UFO_BASE_SPEED << 1) // Speed when the map starts. + +#define UFO_NUMARMS (3) +#define UFO_ARMDELTA (ANGLE_MAX / UFO_NUMARMS) + +#define ufo_waypoint(o) ((o)->extravalue1) +#define ufo_distancetofinish(o) ((o)->extravalue2) +#define ufo_speed(o) ((o)->watertop) +#define ufo_collectdelay(o) ((o)->threshold) + +#define ufo_pieces(o) ((o)->hnext) + +#define ufo_piece_type(o) ((o)->extravalue1) + +#define ufo_piece_owner(o) ((o)->target) +#define ufo_piece_next(o) ((o)->hnext) +#define ufo_piece_prev(o) ((o)->hprev) + +enum +{ + UFO_PIECE_TYPE_POD, + UFO_PIECE_TYPE_ARM, + UFO_PIECE_TYPE_STEM, +}; + +static void UFOMoveTo(mobj_t *ufo, fixed_t destx, fixed_t desty, fixed_t destz) +{ + ufo->momx = destx - ufo->x; + ufo->momy = desty - ufo->y; + ufo->momz = destz - ufo->z; +} + +static fixed_t GenericDistance( + fixed_t curx, fixed_t cury, fixed_t curz, + fixed_t destx, fixed_t desty, fixed_t destz) +{ + return P_AproxDistance(P_AproxDistance(destx - curx, desty - cury), destz - curz); +} + +static boolean UFOEmeraldChase(mobj_t *ufo) +{ + return (ufo->health <= 1); +} + +static boolean UFOPieceValid(mobj_t *piece) +{ + return (piece != NULL && P_MobjWasRemoved(piece) == false && piece->health > 0); +} + +static void UFOUpdateDistanceToFinish(mobj_t *ufo) +{ + waypoint_t *finishLine = K_GetFinishLineWaypoint(); + waypoint_t *nextWaypoint = K_GetWaypointFromIndex((size_t)ufo_waypoint(ufo)); + + if (nextWaypoint != NULL && finishLine != NULL) + { + const boolean useshortcuts = false; + const boolean huntbackwards = false; + boolean pathfindsuccess = false; + path_t pathtofinish = {0}; + + pathfindsuccess = + K_PathfindToWaypoint(nextWaypoint, finishLine, &pathtofinish, useshortcuts, huntbackwards); + + // Update the UFO's distance to the finish line if a path was found. + if (pathfindsuccess == true) + { + // Add euclidean distance to the next waypoint to the distancetofinish + UINT32 adddist; + fixed_t disttowaypoint = + P_AproxDistance( + (ufo->x >> FRACBITS) - (nextWaypoint->mobj->x >> FRACBITS), + (ufo->y >> FRACBITS) - (nextWaypoint->mobj->y >> FRACBITS)); + disttowaypoint = P_AproxDistance(disttowaypoint, (ufo->z >> FRACBITS) - (nextWaypoint->mobj->z >> FRACBITS)); + + adddist = (UINT32)disttowaypoint; + + ufo_distancetofinish(ufo) = pathtofinish.totaldist + adddist; + Z_Free(pathtofinish.array); + } + } +} + +static void UFOUpdateSpeed(mobj_t *ufo) +{ + const fixed_t baseSpeed = FixedMul(UFO_BASE_SPEED, K_GetKartGameSpeedScalar(gamespeed)); + const UINT32 spacing = FixedMul(FixedMul(UFO_SPACING, mapobjectscale), K_GetKartGameSpeedScalar(gamespeed)) >> FRACBITS; + const UINT32 deadzone = FixedMul(FixedMul(UFO_DEADZONE, mapobjectscale), K_GetKartGameSpeedScalar(gamespeed)) >> FRACBITS; + + // Best values of all of the players. + UINT32 bestDist = UINT32_MAX; + fixed_t bestSpeed = 0; + + // Desired values for the UFO itself. + UINT32 wantedDist = UINT32_MAX; + fixed_t wantedSpeed = ufo_speed(ufo); + fixed_t speedDelta = 0; + + UINT8 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *player = NULL; + + if (playeringame[i] == false) + { + continue; + } + + player = &players[i]; + if (player->spectator == true) + { + continue; + } + + if (player->mo == NULL || P_MobjWasRemoved(player->mo) == true) + { + continue; + } + + if (player->distancetofinish < bestDist) + { + bestDist = player->distancetofinish; + + // Doesn't matter if a splitscreen player behind is moving faster behind the one most caught up. + bestSpeed = R_PointToDist2(0, 0, player->rmomx, player->rmomy); + bestSpeed = min(bestSpeed, K_GetKartSpeed(player, false, false)); // Don't become unfair with Sneakers. + bestSpeed = FixedDiv(bestSpeed, mapobjectscale); // Unscale from mapobjectscale to FRACUNIT + bestSpeed = FixedMul(bestSpeed, UFO_SPEEDFACTOR); // Make it a bit more lenient + } + } + + if (bestDist == UINT32_MAX) + { + // Invalid, lets go back to base speed. + wantedSpeed = baseSpeed; + } + else + { + INT32 distDelta = 0; + + if (bestDist > spacing) + { + wantedDist = bestDist - spacing; + } + else + { + wantedDist = 0; + } + + distDelta = ufo_distancetofinish(ufo) - wantedDist; + + if (distDelta > 0) + { + // Too far behind! Start speeding up! + wantedSpeed = max(bestSpeed, baseSpeed << 2); + } + else + { + if (abs(distDelta) <= deadzone) + { + // We're in a good spot, try to match the player. + wantedSpeed = max(bestSpeed >> 1, baseSpeed); + } + else + { + // Too far ahead! Start slowing down! + wantedSpeed = baseSpeed; + } + } + } + + // Slowly accelerate or decelerate to + // get to our desired speed. + speedDelta = wantedSpeed - ufo_speed(ufo); + if (speedDelta > 0) + { + if (abs(speedDelta) <= UFO_SPEEDUP) + { + ufo_speed(ufo) = wantedSpeed; + } + else + { + ufo_speed(ufo) += UFO_SPEEDUP; + } + } + else if (speedDelta < 0) + { + if (abs(speedDelta) <= UFO_SLOWDOWN) + { + ufo_speed(ufo) = wantedSpeed; + } + else + { + ufo_speed(ufo) -= UFO_SLOWDOWN; + } + } +} + +static void UFOUpdateAngle(mobj_t *ufo) +{ + angle_t dest = K_MomentumAngle(ufo); + INT32 delta = AngleDeltaSigned(ufo->angle, dest); + ufo->angle += delta >> 2; +} + +waypoint_t *K_GetSpecialUFOWaypoint(mobj_t *ufo) +{ + if ((ufo == NULL) && (specialStage.active == true)) + { + ufo = specialStage.ufo; + } + + if (ufo != NULL && P_MobjWasRemoved(ufo) == false + && ufo->type == MT_SPECIAL_UFO) + { + if (ufo_waypoint(ufo) >= 0) + { + return K_GetWaypointFromIndex((size_t)ufo_waypoint(ufo)); + } + } + + return NULL; +} + +static void UFOMove(mobj_t *ufo) +{ + waypoint_t *curWaypoint = NULL; + waypoint_t *destWaypoint = NULL; + + fixed_t distLeft = INT32_MAX; + fixed_t newX = ufo->x; + fixed_t newY = ufo->y; + fixed_t newZ = ufo->z; + const fixed_t floatHeight = 24 * ufo->scale; + + const boolean useshortcuts = false; + const boolean huntbackwards = false; + boolean pathfindsuccess = false; + path_t pathtofinish = {0}; + size_t pathIndex = 0; + + boolean reachedEnd = false; + + curWaypoint = K_GetSpecialUFOWaypoint(ufo); + destWaypoint = K_GetFinishLineWaypoint(); + + if (curWaypoint == NULL || destWaypoint == NULL) + { + // Waypoints aren't valid. + // Just stand still. + ufo->momx = 0; + ufo->momy = 0; + ufo->momz = 0; + return; + } + + distLeft = FixedMul(ufo_speed(ufo), mapobjectscale); + + while (distLeft > 0) + { + fixed_t wpX = curWaypoint->mobj->x; + fixed_t wpY = curWaypoint->mobj->y; + fixed_t wpZ = curWaypoint->mobj->z + floatHeight; + + fixed_t distToNext = GenericDistance( + newX, newY, newZ, + wpX, wpY, wpZ + ); + + if (distToNext > distLeft) + { + // Only made it partially there. + newX += FixedMul(FixedDiv(wpX - newX, distToNext), distLeft); + newY += FixedMul(FixedDiv(wpY - newY, distToNext), distLeft); + newZ += FixedMul(FixedDiv(wpZ - newZ, distToNext), distLeft); + + distLeft = 0; + } + else + { + // Close enough to the next waypoint, + // move there and remove the distance. + newX = wpX; + newY = wpY; + newZ = wpZ; + + distLeft -= distToNext; + + if (curWaypoint == destWaypoint) + { + // Reached the end. + reachedEnd = true; + break; + } + + // Create waypoint path to our destination. + // Crazy over-engineered, just to catch when + // waypoints are insanely close to each other :P + if (pathfindsuccess == false) + { + pathfindsuccess = K_PathfindToWaypoint( + curWaypoint, destWaypoint, + &pathtofinish, + useshortcuts, huntbackwards + ); + + if (pathfindsuccess == false) + { + // Path isn't valid. + // Just keep going. + break; + } + } + + pathIndex++; + + if (pathIndex >= pathtofinish.numnodes) + { + // Successfully reached the end of the path. + reachedEnd = true; + break; + } + + // Now moving to the next waypoint. + curWaypoint = (waypoint_t *)pathtofinish.array[pathIndex].nodedata; + ufo_waypoint(ufo) = (INT32)K_GetWaypointHeapIndex(curWaypoint); + } + } + + UFOMoveTo(ufo, newX, newY, newZ); + + if (reachedEnd == true) + { + CONS_Printf("You lost...\n"); + ufo_waypoint(ufo) = -1; // Invalidate + } + + if (pathfindsuccess == true) + { + Z_Free(pathtofinish.array); + } +} + +static void UFOEmeraldVFX(mobj_t *ufo) +{ + const INT32 bobS = 32; + const angle_t bobA = (leveltime & (bobS - 1)) * (ANGLE_MAX / bobS); + const fixed_t bobH = 16 * ufo->scale; + + ufo->sprzoff = FixedMul(bobH, FINESINE(bobA >> ANGLETOFINESHIFT)); + + if (leveltime % 3 == 0) + { + mobj_t *sparkle = P_SpawnMobjFromMobj( + ufo, + P_RandomRange(PR_SPARKLE, -48, 48) * FRACUNIT, + P_RandomRange(PR_SPARKLE, -48, 48) * FRACUNIT, + (P_RandomRange(PR_SPARKLE, 0, 64) * FRACUNIT) + FixedDiv(ufo->sprzoff, ufo->scale), + MT_EMERALDSPARK + ); + + sparkle->color = ufo->color; + sparkle->momz += 8 * ufo->scale * P_MobjFlip(ufo); + } +} + +void Obj_SpecialUFOThinker(mobj_t *ufo) +{ + UFOMove(ufo); + UFOUpdateAngle(ufo); + UFOUpdateDistanceToFinish(ufo); + UFOUpdateSpeed(ufo); + + if (UFOEmeraldChase(ufo) == true) + { + // Spawn emerald sparkles + UFOEmeraldVFX(ufo); + ufo_collectdelay(ufo)--; + } + else + { + ufo_collectdelay(ufo) = TICRATE; + } +} + +static void UFOCopyHitlagToPieces(mobj_t *ufo) +{ + mobj_t *piece = NULL; + + piece = ufo_pieces(ufo); + while (UFOPieceValid(piece) == true) + { + piece->hitlag = ufo->hitlag; + piece->eflags = (piece->eflags & ~MFE_DAMAGEHITLAG) | (ufo->eflags & MFE_DAMAGEHITLAG); + piece = ufo_piece_next(piece); + } +} + +static void UFOKillPiece(mobj_t *piece) +{ + angle_t dir = ANGLE_MAX; + fixed_t thrust = 0; + + if (UFOPieceValid(piece) == false) + { + return; + } + + piece->health = 0; + piece->tics = TICRATE; + piece->flags &= ~MF_NOGRAVITY; + + switch (ufo_piece_type(piece)) + { + case UFO_PIECE_TYPE_STEM: + { + piece->tics = 1; + return; + } + case UFO_PIECE_TYPE_ARM: + { + dir = piece->angle; + thrust = 12 * piece->scale; + break; + } + default: + { + dir = FixedAngle(P_RandomRange(PR_DECORATION, 0, 359) << FRACBITS); + thrust = 4 * piece->scale; + break; + } + } + + P_Thrust(piece, dir, -thrust); + P_SetObjectMomZ(piece, 12*FRACUNIT, true); +} + +static void UFOKillPieces(mobj_t *ufo) +{ + mobj_t *piece = NULL; + + piece = ufo_pieces(ufo); + while (UFOPieceValid(piece) == true) + { + UFOKillPiece(piece); + piece = ufo_piece_next(piece); + } +} + +static UINT8 GetUFODamage(mobj_t *inflictor, UINT8 damageType) +{ + if (inflictor != NULL && P_MobjWasRemoved(inflictor) == false) + { + switch (inflictor->type) + { + case MT_JAWZ_SHIELD: + case MT_ORBINAUT_SHIELD: + { + // Shields deal chip damage. + return 10; + } + case MT_JAWZ: + { + // Thrown Jawz deal a bit extra. + return 15; + } + case MT_ORBINAUT: + { + // Thrown orbinauts deal double damage. + return 20; + } + case MT_SPB: + { + // SPB deals triple damage. + return 30; + } + case MT_BANANA: + { + // Banana snipes deal triple damage, + // laid down bananas deal regular damage. + if (inflictor->health > 1) + { + return 30; + } + + return 10; + } + case MT_PLAYER: + { + // Players deal damage relative to how many sneakers they used. + return 15 * max(1, inflictor->player->numsneakers); + } + default: + { + break; + } + } + } + + // Guess from damage type. + switch (damageType & DMG_TYPEMASK) + { + case DMG_NORMAL: + case DMG_STING: + default: + { + return 10; + } + case DMG_WIPEOUT: + { + return 20; + } + case DMG_EXPLODE: + case DMG_TUMBLE: + { + return 30; + } + case DMG_VOLTAGE: + { + return 15; + } + } +} + +boolean Obj_SpecialUFODamage(mobj_t *ufo, mobj_t *inflictor, mobj_t *source, UINT8 damageType) +{ + const fixed_t addSpeed = FixedMul(UFO_DAMAGED_SPEED, K_GetKartGameSpeedScalar(gamespeed)); + UINT8 damage = 1; + + (void)source; + + if (UFOEmeraldChase(ufo) == true) + { + // Damaged fully already, no need for any more. + return false; + } + + damage = GetUFODamage(inflictor, damageType); + + if (damage <= 0) + { + return false; + } + + // Speed up on damage! + ufo_speed(ufo) += addSpeed; + + K_SetHitLagForObjects(ufo, inflictor, (damage / 3) + 2, true); + UFOCopyHitlagToPieces(ufo); + + if (damage >= ufo->health - 1) + { + // Destroy the UFO parts, and make the emerald collectible! + UFOKillPieces(ufo); + + ufo->health = 1; + ufo->flags = (ufo->flags & ~MF_SHOOTABLE) | (MF_SPECIAL|MF_PICKUPFROMBELOW); + ufo->shadowscale = FRACUNIT/3; + + ufo_speed(ufo) += addSpeed; // Even more speed! + return true; + } + + ufo->health -= damage; + return true; +} + +void Obj_PlayerUFOCollide(mobj_t *ufo, mobj_t *other) +{ + if (other->player == NULL) + { + return; + } + + if ((other->player->sneakertimer > 0) + && !P_PlayerInPain(other->player) + && (other->player->flashing == 0)) + { + // Bump and deal damage. + Obj_SpecialUFODamage(ufo, other, other, DMG_STEAL); + K_KartBouncing(other, ufo); + other->player->sneakertimer = 0; + } +} + +void Obj_UFOPieceThink(mobj_t *piece) +{ + mobj_t *ufo = ufo_piece_owner(piece); + + if (ufo == NULL || P_MobjWasRemoved(ufo) == true) + { + P_KillMobj(piece, NULL, NULL, DMG_NORMAL); + return; + } + + piece->destscale = 3 * ufo->destscale / 2; + piece->scalespeed = ufo->scalespeed; + + switch (ufo_piece_type(piece)) + { + case UFO_PIECE_TYPE_POD: + { + UFOMoveTo(piece, ufo->x, ufo->y, ufo->z + (132 * piece->scale)); + break; + } + case UFO_PIECE_TYPE_ARM: + { + fixed_t dis = (88 * piece->scale); + + fixed_t x = ufo->x - FixedMul(dis, FINECOSINE(piece->angle >> ANGLETOFINESHIFT)); + fixed_t y = ufo->y - FixedMul(dis, FINESINE(piece->angle >> ANGLETOFINESHIFT)); + + UFOMoveTo(piece, x, y, ufo->z + (24 * piece->scale)); + + piece->angle -= FixedMul(ANG2, FixedDiv(ufo_speed(ufo), UFO_BASE_SPEED)); + break; + } + case UFO_PIECE_TYPE_STEM: + { + fixed_t stemZ = ufo->z + (294 * piece->scale); + fixed_t sc = FixedDiv(FixedDiv(ufo->ceilingz - stemZ, piece->scale), 15 * FRACUNIT); + + UFOMoveTo(piece, ufo->x, ufo->y, stemZ); + piece->spriteyscale = sc; + break; + } + default: + { + P_RemoveMobj(piece); + return; + } + } +} + +void Obj_UFOPieceDead(mobj_t *piece) +{ + piece->renderflags ^= RF_DONTDRAW; +} + +void Obj_UFOPieceRemoved(mobj_t *piece) +{ + // Repair piece list. + mobj_t *ufo = ufo_piece_owner(piece); + mobj_t *next = ufo_piece_next(piece); + mobj_t *prev = ufo_piece_prev(piece); + + if (prev != NULL && P_MobjWasRemoved(prev) == false) + { + P_SetTarget( + &ufo_piece_next(prev), + (next != NULL && P_MobjWasRemoved(next) == false) ? next : NULL + ); + } + + if (next != NULL && P_MobjWasRemoved(next) == false) + { + P_SetTarget( + &ufo_piece_prev(next), + (prev != NULL && P_MobjWasRemoved(prev) == false) ? prev : NULL + ); + } + + if (ufo != NULL && P_MobjWasRemoved(ufo) == false) + { + if (piece == ufo_pieces(ufo)) + { + P_SetTarget( + &ufo_pieces(ufo), + (next != NULL && P_MobjWasRemoved(next) == false) ? next : NULL + ); + } + } + + P_SetTarget(&ufo_piece_next(piece), NULL); + P_SetTarget(&ufo_piece_prev(piece), NULL); +} + +static mobj_t *InitSpecialUFO(waypoint_t *start) +{ + mobj_t *ufo = NULL; + mobj_t *overlay = NULL; + mobj_t *piece = NULL; + mobj_t *prevPiece = NULL; + size_t i; + + if (start == NULL) + { + // Simply create at the origin with default values. + ufo = P_SpawnMobj(0, 0, 0, MT_SPECIAL_UFO); + ufo_waypoint(ufo) = -1; // Invalidate + ufo_distancetofinish(ufo) = INT32_MAX; + } + else + { + // Create with a proper waypoint track! + ufo = P_SpawnMobj(start->mobj->x, start->mobj->y, start->mobj->z, MT_SPECIAL_UFO); + ufo_waypoint(ufo) = (INT32)K_GetWaypointHeapIndex(start); + UFOUpdateDistanceToFinish(ufo); + } + + ufo_speed(ufo) = FixedMul(UFO_START_SPEED, K_GetKartGameSpeedScalar(gamespeed)); + + // TODO: Adjustable Special Stage emerald color + ufo->color = SKINCOLOR_CHAOSEMERALD1; + + overlay = P_SpawnMobjFromMobj(ufo, 0, 0, 0, MT_OVERLAY); + P_SetTarget(&overlay->target, ufo); + overlay->color = ufo->color; + + // TODO: Super Emeralds / Chaos Rings + P_SetMobjState(overlay, S_CHAOSEMERALD_UNDER); + + // Create UFO pieces. + // First: UFO center. + piece = P_SpawnMobjFromMobj(ufo, 0, 0, 0, MT_SPECIAL_UFO_PIECE); + P_SetTarget(&ufo_piece_owner(piece), ufo); + + P_SetMobjState(piece, S_SPECIAL_UFO_POD); + ufo_piece_type(piece) = UFO_PIECE_TYPE_POD; + + overlay = P_SpawnMobjFromMobj(piece, 0, 0, 0, MT_OVERLAY); + P_SetTarget(&overlay->target, piece); + P_SetMobjState(overlay, S_SPECIAL_UFO_OVERLAY); + + P_SetTarget(&ufo_pieces(ufo), piece); + prevPiece = piece; + + // Add the catcher arms. + for (i = 0; i < UFO_NUMARMS; i++) + { + piece = P_SpawnMobjFromMobj(ufo, 0, 0, 0, MT_SPECIAL_UFO_PIECE); + P_SetTarget(&ufo_piece_owner(piece), ufo); + + P_SetMobjState(piece, S_SPECIAL_UFO_ARM); + ufo_piece_type(piece) = UFO_PIECE_TYPE_ARM; + + piece->angle = UFO_ARMDELTA * i; + + P_SetTarget(&ufo_piece_next(prevPiece), piece); + P_SetTarget(&ufo_piece_prev(piece), prevPiece); + prevPiece = piece; + } + + // Add the stem. + piece = P_SpawnMobjFromMobj(ufo, 0, 0, 0, MT_SPECIAL_UFO_PIECE); + P_SetTarget(&ufo_piece_owner(piece), ufo); + + P_SetMobjState(piece, S_SPECIAL_UFO_STEM); + ufo_piece_type(piece) = UFO_PIECE_TYPE_STEM; + + P_SetTarget(&ufo_piece_next(prevPiece), piece); + P_SetTarget(&ufo_piece_prev(piece), prevPiece); + prevPiece = piece; + + return ufo; +} + +mobj_t *Obj_CreateSpecialUFO(void) +{ + waypoint_t *finishWaypoint = K_GetFinishLineWaypoint(); + waypoint_t *startWaypoint = NULL; + + if (finishWaypoint != NULL) + { + const boolean huntbackwards = true; + const boolean useshortcuts = false; + const UINT32 traveldist = INT32_MAX; // Go as far back as possible. Not UINT32_MAX to avoid possible overflow. + boolean pathfindsuccess = false; + path_t pathtofinish = {0}; + + pathfindsuccess = K_PathfindThruCircuit( + finishWaypoint, traveldist, + &pathtofinish, + useshortcuts, huntbackwards + ); + + if (pathfindsuccess == true) + { + startWaypoint = (waypoint_t *)pathtofinish.array[ pathtofinish.numnodes - 1 ].nodedata; + Z_Free(pathtofinish.array); + } + } + + return InitSpecialUFO(startWaypoint); +} + +UINT32 K_GetSpecialUFODistance(void) +{ + if (specialStage.active == true) + { + if (specialStage.ufo != NULL && P_MobjWasRemoved(specialStage.ufo) == false) + { + return (UINT32)ufo_distancetofinish(specialStage.ufo); + } + } + + return UINT32_MAX; +} diff --git a/src/p_inter.c b/src/p_inter.c index 09f098e4b..ff6ba99b0 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -386,11 +386,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) special->target->player->karmadelay = comebacktime; } return; - case MT_SPB: - { - Obj_SPBTouch(special, toucher); - return; - } case MT_DUELBOMB: { Obj_DuelBombTouch(special, toucher); @@ -409,6 +404,18 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) player->emeralds |= special->extravalue1; K_CheckEmeralds(player); break; + case MT_SPECIAL_UFO: + if (!P_CanPickupItem(player, 0)) + return; + + if (special->threshold > 0) + return; + + if (toucher->hitlag > 0) + return; + + CONS_Printf("You win!\n"); + break; /* case MT_EERIEFOG: special->frame &= ~FF_TRANS80; @@ -1402,7 +1409,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget INT16 spacing = (target->radius >> 1) / target->scale; // set respawn fuse - if (modeattacking) // no respawns + if (K_TimeAttackRules() == true) // no respawns ; else if (target->threshold == KITEM_SUPERRING) target->fuse = 20*TICRATE; @@ -2289,7 +2296,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da K_PlayPainSound(target, source); - if ((hardhit == true) || (cv_kartdebughuddrop.value && !modeattacking)) + if ((hardhit == true) || cv_kartdebughuddrop.value) { K_DropItems(player); } @@ -2311,6 +2318,11 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da } else { + if (target->type == MT_SPECIAL_UFO) + { + return Obj_SpecialUFODamage(target, inflictor, source, damagetype); + } + if (damagetype & DMG_STEAL) { // Not a player, steal damage is intended to not do anything diff --git a/src/p_map.c b/src/p_map.c index 4421bb1b4..ef4819118 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -757,6 +757,53 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) // SRB2kart 011617 - Colission[sic] code for kart items //{ + if (thing->type == MT_SPB) + { + if (tm.thing->type != MT_PLAYER + && thing->tracer != tm.thing) + { + // Needs to be a player or the + // thing that we're chasing. + return BMIT_CONTINUE; + } + + 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 + } + + Obj_SPBTouch(thing, tm.thing); + return BMIT_CONTINUE; + } + else if (tm.thing->type == MT_SPB) + { + if (thing->type != MT_PLAYER + && tm.thing->tracer != thing) + { + // Needs to be a player or the + // thing that we're chasing. + return BMIT_CONTINUE; + } + + 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 + } + + Obj_SPBTouch(tm.thing, thing); + return BMIT_CONTINUE; + } + if (thing->type == MT_SHRINK_GUN || thing->type == MT_SHRINK_PARTICLE) { if (tm.thing->type != MT_PLAYER) @@ -1379,6 +1426,14 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return BMIT_CONTINUE; } + else if (thing->type == MT_SPECIAL_UFO) + { + if (!(thing->flags & MF_SPECIAL)) + { + Obj_PlayerUFOCollide(thing, tm.thing); + return BMIT_CONTINUE; + } + } else if (thing->type == MT_BLUEROBRA_HEAD || thing->type == MT_BLUEROBRA_JOINT) { // see if it went over / under diff --git a/src/p_mobj.c b/src/p_mobj.c index 30374d368..26b38d631 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -46,6 +46,7 @@ #include "k_terrain.h" #include "k_collide.h" #include "k_objects.h" +#include "k_grandprix.h" static CV_PossibleValue_t CV_BobSpeed[] = {{0, "MIN"}, {4*FRACUNIT, "MAX"}, {0, NULL}}; consvar_t cv_movebob = CVAR_INIT ("movebob", "1.0", CV_FLOAT|CV_SAVE, CV_BobSpeed, NULL); @@ -5306,10 +5307,24 @@ void P_RunOverlays(void) mo->pitch = mo->target->pitch; mo->roll = mo->target->roll; +#if 0 + mo->spritexoffset = mo->target->spritexoffset; + mo->spriteyoffset = mo->target->spriteyoffset; + mo->spritexscale = mo->target->spritexscale; + mo->spriteyscale = mo->target->spriteyscale; + + mo->sprxoff = mo->target->sprxoff; + mo->spryoff = mo->target->spryoff; + mo->sprzoff = mo->target->sprzoff; +#endif + + mo->hitlag = mo->target->hitlag; + mo->eflags = (mo->eflags & ~MFE_DAMAGEHITLAG) | (mo->target->eflags & MFE_DAMAGEHITLAG); + if ((mo->flags & MF_DONTENCOREMAP) != (mo->target->flags & MF_DONTENCOREMAP)) mo->flags ^= MF_DONTENCOREMAP; - mo->dispoffset = mo->target->dispoffset + mo->info->dispoffset; + mo->dispoffset = mo->target->dispoffset; if (!(mo->state->frame & FF_ANIMATE)) { @@ -5329,6 +5344,7 @@ void P_RunOverlays(void) // if you're using FF_ANIMATE on an overlay, // then you're on your own. zoffs = 0; + mo->dispoffset++; } P_UnsetThingPosition(mo); @@ -6755,6 +6771,11 @@ static boolean P_MobjDeadThink(mobj_t *mobj) S_StartSound(dust, sfx_s3k3d); } break; + case MT_SPECIAL_UFO_PIECE: + { + Obj_UFOPieceDead(mobj); + break; + } default: break; } @@ -7366,6 +7387,16 @@ static boolean P_MobjRegularThink(mobj_t *mobj) Obj_DuelBombThink(mobj); break; } + case MT_SPECIAL_UFO: + { + Obj_SpecialUFOThinker(mobj); + break; + } + case MT_SPECIAL_UFO_PIECE: + { + Obj_UFOPieceThink(mobj); + break; + } case MT_EMERALD: { if (battleovertime.enabled >= 10*TICRATE) @@ -10121,6 +10152,7 @@ static void P_DefaultMobjShadowScale(mobj_t *thing) case MT_PLAYER: case MT_KART_LEFTOVER: case MT_BATTLECAPSULE: + case MT_SPECIAL_UFO: thing->shadowscale = FRACUNIT; break; case MT_SMALLMACE: @@ -10978,6 +11010,11 @@ void P_RemoveMobj(mobj_t *mobj) Obj_ShrinkGunRemoved(mobj); } + if (mobj->type == MT_SPECIAL_UFO_PIECE) + { + Obj_UFOPieceRemoved(mobj); + } + mobj->health = 0; // Just because // unlink from sector and block lists @@ -11541,13 +11578,12 @@ void P_SpawnPlayer(INT32 playernum) } else if (p->bot) { - /* - if (bonusgame || specialstage || boss) + if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE) { - // Bots should avoid + // Bots aren't supposed to be here. p->spectator = true; } - */ + else { // No point in a spectating bot! p->spectator = false; @@ -12018,7 +12054,7 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i) // in record attack, only spawn ring capsules // (behavior can be inverted with the Extra flag, i.e. item capsule spawns and ring capsule does not) - if (modeattacking + if (K_TimeAttackRules() == true && (!(mthing->args[2] & TMICF_INVERTTIMEATTACK) == !isRingCapsule)) return false; }