diff --git a/src/dehacked.c b/src/dehacked.c index 4e2db6975..6a59df31c 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -9398,6 +9398,15 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit "S_SPINDASHDUST", "S_SPINDASHWIND", + // Finish line beam + "S_FINISHBEAM1", + "S_FINISHBEAM2", + "S_FINISHBEAM3", + "S_FINISHBEAM4", + "S_FINISHBEAM5", + "S_FINISHBEAMEND1", + "S_FINISHBEAMEND2", + #ifdef SEENAMES "S_NAMECHECK", #endif diff --git a/src/info.c b/src/info.c index c6f082a5c..d804c5978 100644 --- a/src/info.c +++ b/src/info.c @@ -732,15 +732,17 @@ char sprnames[NUMSPRITES + 1][5] = "GCHA", // follower: generic chao "CHEZ", // follower: cheese - // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later - "VIEW", - "DBCL", // Drift boost clip "DBNC", // Drift boost clip's sparks "DBST", // Drift boost plume "SDDS", // Spindash dust "SDWN", // Spindash wind + + "FLBM", + + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later + "VIEW", }; char spr2names[NUMPLAYERSPRITES][5] = @@ -5115,6 +5117,15 @@ state_t states[NUMSTATES] = {SPR_SDDS, FF_ANIMATE, 9, {NULL}, 9, 1, S_NULL}, // S_SPINDASHDUST {SPR_SDWN, FF_ANIMATE|FF_PAPERSPRITE, 18, {NULL}, 9, 2, S_NULL}, // S_SPINDASHWIND + // Finish line beam + {SPR_FLBM, FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_NULL}, // S_FINISHBEAM1 + {SPR_FLBM, FF_FULLBRIGHT|1, 1, {NULL}, 0, 0, S_NULL}, // S_FINISHBEAM2 + {SPR_FLBM, FF_FULLBRIGHT|2, 1, {NULL}, 0, 0, S_NULL}, // S_FINISHBEAM3 + {SPR_FLBM, FF_FULLBRIGHT|3, 1, {NULL}, 0, 0, S_NULL}, // S_FINISHBEAM4 + {SPR_FLBM, FF_FULLBRIGHT|4, 1, {NULL}, 0, 0, S_NULL}, // S_FINISHBEAM5 + {SPR_FLBM, FF_PAPERSPRITE|5, 1, {NULL}, 0, 0, S_NULL}, // S_FINISHBEAMEND1 + {SPR_FLBM, FF_PAPERSPRITE|6, 1, {NULL}, 0, 0, S_NULL}, // S_FINISHBEAMEND2 + #ifdef SEENAMES {SPR_NULL, 0, 1, {NULL}, 0, 0, S_NULL}, // S_NAMECHECK #endif diff --git a/src/info.h b/src/info.h index 6a2749672..099f9c412 100644 --- a/src/info.h +++ b/src/info.h @@ -1003,9 +1003,6 @@ typedef enum sprite SPR_GCHA, // follower: generic chao SPR_CHEZ, // follower: cheese - // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later - SPR_VIEW, - SPR_DBCL, // Drift boost clip SPR_DBNC, // Drift boost clip's sparks SPR_DBST, // Drift boost plume @@ -1013,6 +1010,11 @@ typedef enum sprite SPR_SDDS, // Spindash dust SPR_SDWN, // Spindash wind + SPR_FLBM, // Finish line beam + + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later + SPR_VIEW, + SPR_FIRSTFREESLOT, SPR_LASTFREESLOT = SPR_FIRSTFREESLOT + NUMSPRITEFREESLOTS - 1, NUMSPRITES @@ -5271,6 +5273,14 @@ typedef enum state S_SPINDASHDUST, S_SPINDASHWIND, + S_FINISHBEAM1, + S_FINISHBEAM2, + S_FINISHBEAM3, + S_FINISHBEAM4, + S_FINISHBEAM5, + S_FINISHBEAMEND1, + S_FINISHBEAMEND2, + #ifdef SEENAMES S_NAMECHECK, #endif diff --git a/src/k_kart.c b/src/k_kart.c index 0d87dbe12..5b774506b 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -3106,6 +3106,250 @@ static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, I return NULL; } +// Spawns the finish line fault-indicator effect +#define FINISHLINEBEAM_SPACING (48*mapobjectscale) +static void K_DrawFinishLineBeamForLine(fixed_t offset, angle_t aiming, line_t *line, boolean reverse) +{ + const fixed_t linelength = P_AproxDistance(line->dx, line->dy); + const fixed_t xstep = FixedDiv(line->dx, linelength); + const fixed_t ystep = FixedDiv(line->dy, linelength); + + fixed_t linex = line->v1->x; + fixed_t liney = line->v1->y; + angle_t lineangle = R_PointToAngle2(0, 0, line->dx, line->dy) + ANGLE_90; + + UINT8 i; + + if (line->flags & ML_NOCLIMB) + { + // Line is flipped + lineangle += ANGLE_180; + } + + linex += FixedMul(offset, xstep); + liney += FixedMul(offset, ystep); + + while (offset < linelength) + { +#define COLORCYCLELEN 10 + const UINT8 colorcycle[COLORCYCLELEN] = { + SKINCOLOR_PERIWINKLE, + SKINCOLOR_SLATE, + SKINCOLOR_BLOSSOM, + SKINCOLOR_RASPBERRY, + SKINCOLOR_ORANGE, + SKINCOLOR_YELLOW, + SKINCOLOR_LIME, + SKINCOLOR_TURTLE, + SKINCOLOR_ROBIN, + SKINCOLOR_JAWZ + }; + + const UINT8 numframes = 5; + const angle_t framethreshold = ANGLE_180 / (numframes-1); + const angle_t frameaim = aiming + (framethreshold / 2); + + fixed_t x, y, z; + UINT8 spriteframe = 0; + + x = linex + FixedMul(FixedMul(FINISHLINEBEAM_SPACING, FINECOSINE(lineangle >> ANGLETOFINESHIFT)), FINECOSINE(aiming >> ANGLETOFINESHIFT)); + y = liney + FixedMul(FixedMul(FINISHLINEBEAM_SPACING, FINESINE(lineangle >> ANGLETOFINESHIFT)), FINECOSINE(aiming >> ANGLETOFINESHIFT)); + z = FINISHLINEBEAM_SPACING + FixedMul(FINISHLINEBEAM_SPACING, FINESINE(aiming >> ANGLETOFINESHIFT)); + + if (leveltime >= starttime) + { + spriteframe = 4; // Weakest sprite when passable + } + else if (frameaim > ANGLE_180) + { + spriteframe = (ANGLE_MAX - frameaim) / framethreshold; + } + else + { + spriteframe = frameaim / framethreshold; + } + + for (i = 0; i <= r_splitscreen; i++) + { + if (playeringame[displayplayers[i]] && players[displayplayers[i]].mo && !P_MobjWasRemoved(players[displayplayers[i]].mo)) + { + mobj_t *beam; + + beam = P_SpawnMobj(x, y, players[displayplayers[i]].mo->z + z, MT_THOK); + P_SetMobjState(beam, S_FINISHBEAM1 + spriteframe); + + beam->colorized = true; + beam->drawflags = MFD_DONTDRAW & ~K_GetPlayerDontDrawFlag(&players[displayplayers[i]]); + + if (reverse) + { + beam->color = colorcycle[((leveltime / 4) + (COLORCYCLELEN/2)) % COLORCYCLELEN]; + } + else + { + beam->color = colorcycle[(leveltime / 4) % COLORCYCLELEN]; + } + } + } + + offset += FINISHLINEBEAM_SPACING; + linex += FixedMul(FINISHLINEBEAM_SPACING, xstep); + liney += FixedMul(FINISHLINEBEAM_SPACING, ystep); + + if (reverse) + { + aiming -= ANGLE_45; + } + else + { + aiming += ANGLE_45; + } + } + + for (i = 0; i <= r_splitscreen; i++) + { + if (playeringame[displayplayers[i]] && players[displayplayers[i]].mo && !P_MobjWasRemoved(players[displayplayers[i]].mo)) + { + UINT8 j; + for (j = 0; j < 2; j++) + { + vertex_t *v = line->v1; + mobj_t *end1, *end2; + angle_t a = R_PointToAngle2(0, 0, line->dx, line->dy); + fixed_t sx; + fixed_t sy; + + //if (line->flags & ML_NOCLIMB) + //{ + //a += ANGLE_180; + //} + + sx = FixedMul(3*mapobjectscale, FINECOSINE(a >> ANGLETOFINESHIFT)); + sy = FixedMul(3*mapobjectscale, FINESINE(a >> ANGLETOFINESHIFT)); + + if (j == 1) + { + v = line->v2; + sx = -sx; + sy = -sy; + } + + end1 = P_SpawnMobj( + v->x + sx, + v->y + sy, + players[displayplayers[i]].mo->z + FINISHLINEBEAM_SPACING, + MT_THOK + ); + + P_SetMobjState(end1, S_FINISHBEAMEND1); + end1->drawflags = MFD_DONTDRAW & ~K_GetPlayerDontDrawFlag(&players[displayplayers[i]]); + end1->angle = lineangle; + + end2 = P_SpawnMobj( + v->x + (8*sx), + v->y + (8*sy), + players[displayplayers[i]].mo->z + FINISHLINEBEAM_SPACING, + MT_THOK + ); + + P_SetMobjState(end2, S_FINISHBEAMEND2); + end2->drawflags = MFD_DONTDRAW & ~K_GetPlayerDontDrawFlag(&players[displayplayers[i]]); + end2->angle = lineangle; + + P_SetTarget(&end2->tracer, end1); + end2->flags2 |= MF2_LINKDRAW; + } + } + } +} + +void K_RunFinishLineBeam(void) +{ + INT64 bounds[4]; + angle_t angle = 0; + UINT32 flags = 0; + boolean valid = false; + UINT32 i; + + if (!(leveltime < starttime || rainbowstartavailable == true)) + { + return; + } + + // this does NOT support finish lines that curve. + // I wanted to! But I have a headache from trying to code it for like, 3 hours! + // so I'm not! + + bounds[0] = INT64_MAX; // min x + bounds[1] = INT64_MIN; // max x + bounds[2] = INT64_MAX; // min y + bounds[3] = INT64_MIN; // max y + + for (i = 0; i < numlines; i++) + { + if (lines[i].special == 2001) + { + angle_t thisAngle = R_PointToAngle2(0, 0, lines[i].dx, lines[i].dy); + + bounds[0] = min(bounds[0], min(lines[i].v1->x, lines[i].v2->x)); // min x + bounds[1] = max(bounds[1], max(lines[i].v1->x, lines[i].v2->x)); // max x + bounds[2] = min(bounds[2], min(lines[i].v1->y, lines[i].v2->y)); // min y + bounds[3] = max(bounds[3], max(lines[i].v1->y, lines[i].v2->y)); // max y + + if (valid == false) + { + angle = thisAngle; + flags = lines[i].flags; + } + else if (angle != thisAngle) + { + valid = false; + break; + } + + valid = true; + } + } + + if (valid == true) + { + fixed_t span = P_AproxDistance(bounds[1] - bounds[0], bounds[3] - bounds[2]) / 2; + + fixed_t cx = (bounds[0] + bounds[1]) / 2; + fixed_t cy = (bounds[2] + bounds[3]) / 2; + + vertex_t v1, v2; // fake vertexes + line_t junk; // fake linedef + + const angle_t angoffset = ANGLE_45; + const angle_t angadd = ANGLE_11hh; + const fixed_t speed = 6 * mapobjectscale; + + fixed_t offseta = (leveltime * speed) % FINISHLINEBEAM_SPACING; + angle_t aiminga = (angoffset * -((leveltime * speed) / FINISHLINEBEAM_SPACING)) + (angadd * leveltime); + + fixed_t offsetb = FINISHLINEBEAM_SPACING - offseta; + angle_t aimingb = (angoffset * -((leveltime * speed) / FINISHLINEBEAM_SPACING)) - (angadd * leveltime); + + v1.x = cx - FixedMul(span, FINECOSINE(angle >> ANGLETOFINESHIFT)); + v1.y = cy - FixedMul(span, FINESINE(angle >> ANGLETOFINESHIFT)); + + v2.x = cx + FixedMul(span, FINECOSINE(angle >> ANGLETOFINESHIFT)); + v2.y = cy + FixedMul(span, FINESINE(angle >> ANGLETOFINESHIFT)); + + junk.v1 = &v1; + junk.v2 = &v2; + junk.dx = v2.x - v1.x; + junk.dy = v2.y - v1.y; + junk.flags = flags; + + K_DrawFinishLineBeamForLine(offseta, aiminga, &junk, false); + K_DrawFinishLineBeamForLine(offsetb, aimingb, &junk, true); + } +} + +#undef FINISHLINEBEAM_SPACING + UINT16 K_DriftSparkColor(player_t *player, INT32 charge) { INT32 ds = K_GetKartDriftSparkValue(player); diff --git a/src/k_kart.h b/src/k_kart.h index 37a31e27b..9f18b4661 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -52,6 +52,7 @@ void K_DestroyBumpers(player_t *player, UINT8 amount); void K_TakeBumpersFromPlayer(player_t *player, player_t *victim, UINT8 amount); void K_SpawnKartExplosion(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, angle_t rotangle, boolean spawncenter, boolean ghostit, mobj_t *source); void K_SpawnMineExplosion(mobj_t *source, UINT8 color); +void K_RunFinishLineBeam(void); UINT16 K_DriftSparkColor(player_t *player, INT32 charge); void K_SpawnBoostTrail(player_t *player); void K_SpawnSparkleTrail(mobj_t *mo); diff --git a/src/p_tick.c b/src/p_tick.c index c7e6a946a..4d4ba4b26 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -332,6 +332,9 @@ static inline void P_RunThinkers(void) ps_thlist_times[i] = I_GetTimeMicros() - ps_thlist_times[i]; } + if (gametyperules & GTR_CIRCUIT) + K_RunFinishLineBeam(); + if (gametyperules & GTR_PAPERITEMS) K_RunPaperItemSpawners();