diff --git a/src/info.c b/src/info.c index eb170a1fe..e58a1f3a7 100644 --- a/src/info.c +++ b/src/info.c @@ -2428,16 +2428,16 @@ state_t states[NUMSTATES] = {SPR_ADST, 6|FF_ANIMATE, 24, {NULL}, 3, 8, S_NULL}, // S_ARIDDUST3 // Minecart - {SPR_NULL, 0, 1, {NULL}, 0, 0, S_MINECART_IDLE}, // S_MINECART_IDLE - {SPR_NULL, 0, 45, {NULL}, 0, 0, S_NULL}, // S_MINECART_DTH1 - {SPR_MCRT, 8|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_MINECARTEND - {SPR_MCRT, 0|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_MINECARTSEG_FRONT - {SPR_MCRT, 1|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_MINECARTSEG_BACK - {SPR_MCRT, 2|FF_PAPERSPRITE, -1, {NULL}, 2, 3, S_NULL}, // S_MINECARTSEG_LEFT - {SPR_MCRT, 5|FF_PAPERSPRITE, -1, {NULL}, 2, 3, S_NULL}, // S_MINECARTSEG_RIGHT - {SPR_LCKN, 2|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_MINECARTSIDEMARK1 - {SPR_LCKN, 0|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_MINECARTSIDEMARK2 - {SPR_MCSP, FF_FULLBRIGHT, 1, {A_MinecartSparkThink}, 0, 0, S_MINECARTSPARK}, // S_MINECARTSPARK + {SPR_NULL, 0, 1, {NULL}, 0, 0, S_MINECART_IDLE}, // S_MINECART_IDLE + {SPR_NULL, 0, 45, {NULL}, 0, 0, S_NULL}, // S_MINECART_DTH1 + {SPR_MCRT, 8|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_MINECARTEND + {SPR_MCRT, 0|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_MINECARTSEG_FRONT + {SPR_MCRT, 1|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_MINECARTSEG_BACK + {SPR_MCRT, 2|FF_PAPERSPRITE|FF_ANIMATE, -1, {NULL}, 2, 3, S_NULL}, // S_MINECARTSEG_LEFT + {SPR_MCRT, 5|FF_PAPERSPRITE|FF_ANIMATE, -1, {NULL}, 2, 3, S_NULL}, // S_MINECARTSEG_RIGHT + {SPR_LCKN, 2|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_MINECARTSIDEMARK1 + {SPR_LCKN, 0|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_MINECARTSIDEMARK2 + {SPR_MCSP, FF_FULLBRIGHT, 1, {A_MinecartSparkThink}, 0, 0, S_MINECARTSPARK}, // S_MINECARTSPARK // Saloon door {SPR_SALD, 0|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_SALOONDOOR diff --git a/src/p_local.h b/src/p_local.h index b686b9f09..2149a95bf 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -229,6 +229,7 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state); boolean P_SetMobjState(mobj_t *mobj, statenum_t state); void P_RunShields(void); void P_RunOverlays(void); +void P_HandleMinecartSegments(mobj_t *mobj); void P_MobjThinker(mobj_t *mobj); boolean P_RailThinker(mobj_t *mobj); void P_PushableThinker(mobj_t *mobj); diff --git a/src/p_mobj.c b/src/p_mobj.c index 6beec3684..30f7fc7d4 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6728,6 +6728,67 @@ static void P_KoopaThinker(mobj_t *koopa) } } +// Spawns and chains the minecart sides. +static void P_SpawnMinecartSegments(mobj_t *mobj, boolean mode) +{ + fixed_t x = mobj->x; + fixed_t y = mobj->y; + fixed_t z = mobj->z; + mobj_t *prevseg = mobj; + mobj_t *seg; + UINT8 i; + + for (i = 0; i < 4; i++) + { + seg = P_SpawnMobj(x, y, z, MT_PARTICLE); + P_SetMobjState(seg, (statenum_t)(S_MINECARTSEG_FRONT + i)); + if (i >= 2) + seg->extravalue1 = (i == 2) ? -18 : 18; + else + { + seg->extravalue2 = (i == 0) ? 24 : -24; + seg->cusval = -90; + } + if (mode) + seg->frame &= ~FF_ANIMATE; + P_SetTarget(&prevseg->tracer, seg); + prevseg = seg; + } +} + +// Updates the chained segments. +static void P_UpdateMinecartSegments(mobj_t *mobj) +{ + mobj_t *seg = mobj->tracer; + fixed_t x = mobj->x; + fixed_t y = mobj->y; + fixed_t z = mobj->z; + angle_t ang = mobj->angle; + angle_t fa = (ang >> ANGLETOFINESHIFT) & FINEMASK; + fixed_t c = FINECOSINE(fa); + fixed_t s = FINESINE(fa); + INT32 dx, dy; + INT32 sang; + + while (seg) + { + dx = seg->extravalue1; + dy = seg->extravalue2; + sang = seg->cusval; + P_TeleportMove(seg, x + s*dx + c*dy, y - c*dx + s*dy, z); + seg->angle = ang + FixedAngle(FRACUNIT*sang); + seg->flags2 = (seg->flags2 & ~MF2_DONTDRAW) | (mobj->flags2 & MF2_DONTDRAW); + seg = seg->tracer; + } +} + +void P_HandleMinecartSegments(mobj_t *mobj) +{ + if (!mobj->tracer) + P_SpawnMinecartSegments(mobj, (mobj->type == MT_MINECART)); + P_UpdateMinecartSegments(mobj); +} + // // P_MobjThinker // @@ -8496,6 +8557,7 @@ void P_MobjThinker(mobj_t *mobj) break; } case MT_MINECARTSPAWNER: + P_HandleMinecartSegments(mobj); if (!mobj->fuse || mobj->fuse > TICRATE) break; if (mobj->fuse == 2) @@ -8505,6 +8567,14 @@ void P_MobjThinker(mobj_t *mobj) } mobj->flags2 ^= MF2_DONTDRAW; break; + case MT_MINECART: + // If player is ded, remove this minecart + if (!mobj->target || P_MobjWasRemoved(mobj->target) || !mobj->target->health) + { + P_KillMobj(mobj, NULL, NULL, 0); + return; + } + break; case MT_SPINFIRE: if (mobj->flags & MF_NOGRAVITY) { diff --git a/src/p_user.c b/src/p_user.c index e5d3f4835..29988c88b 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -9753,6 +9753,421 @@ void P_DoPityCheck(player_t *player) } } +static sector_t *P_GetMinecartSector(fixed_t x, fixed_t y, fixed_t z) +{ + sector_t *sec = R_PointInSubsector(x, y)->sector; + + if ((sec->ceilingheight - sec->floorheight) < 64*FRACUNIT) + return NULL; + + if (sec->ffloors) + { + ffloor_t *rover; + for (rover = sec->ffloors; rover; rover = rover->next) + { + if (!(rover->flags & FF_EXISTS)) + continue; + + fixed_t fofz = rover->t_slope ? P_GetZAt(*rover->t_slope, x, y) : *rover->topheight; + if (abs(z - fofz) <= 40*FRACUNIT) + { + sec = §ors[rover->secnum]; + break; + } + } + + } + return sec; +} + +static size_t P_GetMinecartSpecialLine(sector_t *sec) +{ + size_t line = -1; + + if (!sec) + return -1; + + if (sec->tag != 0) + line = P_FindSpecialLineFromTag(16, sec->tag, -1); + + // Also try for lines facing the sector itself, with tag 0. + if (line == -1) + { + UINT32 i; + for (i = 0; i < sec->linecount; i++) + { + line_t *li = sec->lines[i]; + if (li->tag == 0 && li->special == 16 && li->frontsector == sec) + line = i; + } + } + + return line; +} + +// Get an axis of a certain ID number +static mobj_t *P_GetAxis(UINT16 num) +{ + thinker_t *th; + mobj_t *mobj; + + for (th = thinkercap.next; th != &thinkercap; th = th->next) + { + if (th->function.acp1 != (actionf_p1)P_MobjThinker) + continue; + + mobj = (mobj_t *)th; + + // NiGHTS axes spawn before anything else. If this mobj doesn't have MF2_AXIS, it means we reached the axes' end. + if (!(mobj->flags2 & MF2_AXIS)) + break; + + // Skip if this axis isn't the one we want. + if (!mobj->spawnpoint || mobj->spawnpoint->options != num) + continue; + + return mobj; + } + CONS_Debug(DBG_GAMELOGIC, "P_GetAxis: Track segment %d is missing!\n", num); + return NULL; +} + +// Auxiliary function. For a given position and axis, it calculates the nearest "valid" snap-on position. +static void P_GetAxisPosition(fixed_t x, fixed_t y, mobj_t *amo, fixed_t *newx, fixed_t *newy, angle_t *targetangle, angle_t *grind) +{ + fixed_t ax = amo->x; + fixed_t ay = amo->y; + angle_t ang; + angle_t gr = 0; + + if (amo->type == MT_AXISTRANSFERLINE) + { + ang = amo->angle; + // Extra security for cardinal directions. + if (ang == ANGLE_90 || ang == ANGLE_270) // Vertical lines + x = ax; + else if (ang == 0 || ang == ANGLE_180) // Horizontal lines + y = ay; + else // Diagonal lines + { + fixed_t distance = R_PointToDist2(ax, ay, x, y); + angle_t fad = ((R_PointToAngle2(ax, ay, x, y) - ang) >> ANGLETOFINESHIFT) & FINEMASK; + fixed_t cosine = FINECOSINE(fad); + angle_t fa = (ang >> ANGLETOFINESHIFT) & FINEMASK; + distance = FixedMul(distance, cosine); + x = ax + FixedMul(distance, FINECOSINE(fa)); + y = ay + FixedMul(distance, FINESINE(fa)); + } + } + else // Keep minecart to circle + { + fixed_t rad; + fixed_t distfactor; + + gr = R_PointToAngle2(ax, ay, x, y); + ang = gr + ANGLE_90; + if (amo->spawnpoint->angle < 16384) // Counterclockwise + rad = amo->spawnpoint->angle*FRACUNIT; + else // Clockwise + rad = (amo->spawnpoint->angle - 16384)*FRACUNIT; + + distfactor = FixedDiv(rad, R_PointToDist2(ax, ay, x, y)); + x = ax + FixedMul(x - ax, distfactor); + y = ay + FixedMul(y - ay, distfactor); + } + + *newx = x; + *newy = y; + *targetangle = ang; + *grind = gr; +} + +static void P_SpawnSparks(mobj_t *mo, angle_t maindir) +{ + angle_t fa = (mo->angle >> ANGLETOFINESHIFT) & FINEMASK; + fixed_t c = FixedMul(FINECOSINE(fa), mo->radius); + fixed_t s = FixedMul(FINESINE(fa), mo->radius); + mobj_t *spark; + UINT8 b1 = (leveltime % 2 == 1) ? 1 : -1; + UINT8 b2 = ((leveltime / 2) % 2 == 1) ? 1 : -1; + fixed_t r = FRACUNIT*P_RandomRange(-1, 1); + + spark = P_SpawnMobj(mo->x - b2*s + b1*c, mo->y + b2*c + b1*s, mo->z, MT_MINECARTSPARK); + spark->momx = mo->momx + r; + spark->momy = mo->momy + r; + spark->momz = mo->momz + r; + + if (maindir) + { + fixed_t fm = (maindir >> ANGLETOFINESHIFT) & FINEMASK; + spark->momx += 8*FINECOSINE(fm); + spark->momy += 8*FINESINE(fm); + } + P_Thrust(spark, R_PointToAngle2(mo->x, mo->y, spark->x, spark->y), 8*FRACUNIT); + P_SetScale(spark, FRACUNIT/4); + spark->fuse = TICRATE/3; +} + +// Performs a proximity check on a given direction looking for rails. +static mobj_t *P_LookForRails(mobj_t* mobj, fixed_t c, fixed_t s, angle_t targetangle, fixed_t xcom, fixed_t ycom) +{ + INT16 interval = 16; + INT16 fwooffset = FixedHypot(mobj->momx, mobj->momy) >> FRACBITS; + fixed_t x = mobj->x; + fixed_t y = mobj->y; + fixed_t z = mobj->z + 40*FRACUNIT; + UINT8 i; + + for (i = 4; i <= 10; i++) + { + fixed_t nz; + size_t lline; + + x += interval*xcom*i + fwooffset*c*i; + y += interval*ycom*i + fwooffset*s*i; + nz = P_FloorzAtPos(x, y, z, mobj->height); + + lline = P_GetMinecartSpecialLine(P_GetMinecartSector(x, y, nz)); + if (lline != -1) + { + fixed_t nx, ny; + angle_t nang, dummy, angdiff; + mobj_t *mark; + mobj_t *snax = P_GetAxis(sides[lines[lline].sidenum[0]].textureoffset >> FRACBITS); + P_GetAxisPosition(x, y, snax, &ny, &nx, &nang, &dummy); + angdiff = nang - targetangle; + if (angdiff < ANG10/2 || angdiff > ANGLE_MAX - ANG10/2) + { + mark = P_SpawnMobj(nx, ny, nz, mobj->info->raisestate); + return mark; + } + } + } + return NULL; +} + +static void P_ParabolicMove(mobj_t *mo, fixed_t x, fixed_t y, fixed_t z, fixed_t gravity, fixed_t speed) +{ + fixed_t dx = x - mo->x; + fixed_t dy = y - mo->y; + fixed_t dz = z - mo->z; + fixed_t dh = P_AproxDistance(dx, dy); + fixed_t c = FixedDiv(dx, dh); + fixed_t s = FixedDiv(dy, dh); + fixed_t fixConst = FixedDiv(speed, gravity); + + mo->momx = FixedMul(c, speed); + mo->momy = FixedMul(s, speed); + mo->momz = FixedDiv(dh, 2*fixConst) + FixedDiv(dz, FixedDiv(dh, fixConst/2)); +} + +static void P_MinecartThink(player_t *player) +{ + mobj_t *minecart = player->mo->tracer; + angle_t fa; + + if (!minecart || !P_MobjWasRemoved(minecart) || !minecart->health) + { + // Minecart died on you, so kill yourself. + P_KillMobj(player->mo, NULL, NULL, 0); + return; + } + +#if 0 + //Limit player's angle to a cone. +#define MINECARTCONEMAX FixedAngle(20*FRACUNIT) + { + angle_t angdiff = player->mo->angle - minecart->angle; + if (angdiff < ANGLE_180 && angdiff > MINECARTCONEMAX) + player->mo->angle = minecart->angle + MINECARTCONEMAX; + else if (angdiff > ANGLE_180 && angdiff < InvAngle(MINECARTCONEMAX)) + player->mo->angle = minecart->angle - MINECARTCONEMAX; + + if (angdiff + minecart->angle != player->mo->angle) + { + if (player == &players[consoleplayer]) + localangle = player->mo->angle; + else if (player == &players[secondarydisplayplayer]) + localangle2 = player->mo->angle; + } + } +#endif + + //P_ResetPlayer(player); + + // Player holding jump? + if (player->cmd.buttons & BT_JUMP) + player->pflags |= PF_JUMPDOWN; + else + player->pflags &= ~PF_JUMPDOWN; + + // Handle segments. + P_HandleMinecartSegments(minecart); + + // Force 0 friction. + minecart->friction = FRACUNIT; + + fa = (minecart->angle >> ANGLETOFINESHIFT) & FINEMASK; + if (!P_TryMove(minecart, minecart->x + FINECOSINE(fa), minecart->y + FINESINE(fa), true)) + { + P_KillMobj(player->mo, NULL, NULL, 0); + P_KillMobj(minecart, NULL, NULL, 0); + return; + } + + if (P_IsObjectOnGround(minecart)) + { + sector_t *sec; + size_t lnum; + + // Just hit floor. + if (minecart->eflags & MFE_JUSTHITFLOOR) + { + S_StopSound(minecart); + S_StartSound(minecart, sfx_s3k96); + } + + sec = P_GetMinecartSector(minecart->x, minecart->y, minecart->z); + + if (sec) + lnum = P_GetMinecartSpecialLine(sec); + + // Update axis if the cart is standing on a rail. + if (sec && lnum != -1) + { + mobj_t *axis = P_GetAxis(sides[lines[lnum].sidenum[0]].textureoffset >> FRACBITS); + fixed_t newx, newy; + angle_t targetangle, grind; + angle_t prevangle, angdiff; + mobj_t *detleft = NULL; + mobj_t *detright = NULL; + mobj_t *sidelock = NULL; + boolean jumped = false; + fixed_t currentSpeed; + + minecart->movefactor = 0; + P_ResetScore(player); + // Handle angle and position + P_GetAxisPosition(minecart->x, minecart->y, axis, &newx, &newy, &targetangle, &grind); + if (grind) + P_SpawnSparks(minecart, grind); + P_TryMove(minecart, newx, newy, true); + + // Set angle based on target + prevangle = minecart->angle; + angdiff = targetangle - minecart->angle; + if (angdiff < ANGLE_90 + ANG2 || angdiff > ANGLE_270 - ANG2) + minecart->angle = targetangle; + else + minecart->angle = targetangle + ANGLE_180; + player->mo->angle += (minecart->angle - prevangle); // maintain relative angle on turns + if (angdiff + minecart->angle != targetangle) + { + if (player == &players[consoleplayer]) + localangle = player->mo->angle; + else if (player == &players[secondarydisplayplayer]) + localangle2 = player->mo->angle; + } + + // Sideways detection + if (minecart->flags2 & MF2_AMBUSH) + { + angle_t fa = minecart->angle; + fixed_t c = FINECOSINE(fa); + fixed_t s = FINESINE(fa); + + detleft = P_LookForRails(minecart, c, s, targetangle, -s, c); + detright = P_LookForRails(minecart, c, s, targetangle, s, -c); + } + + // How fast are we going? + currentSpeed = FixedHypot(minecart->momx, minecart->momy); + angdiff = R_PointToAngle2(0, 0, minecart->momx, minecart->momy) - minecart->angle; + if (angdiff > ANGLE_90 && angdiff < ANGLE_270) + currentSpeed *= -1; + + // Player-specific behavior. + // Update side hopper marker sprites if pressing strafe. + if (detleft && player->cmd.sidemove < 0) + { + P_SetMobjState(detleft, detleft->info->seestate); + sidelock = detleft; + } + else if (detright && player->cmd.sidemove > 0) + { + P_SetMobjState(detright, detright->info->seestate); + sidelock = detright; + } + + //if (player->cmd.buttons & BT_USE && currentSpeed > 4*FRACUNIT) + // currentSpeed -= FRACUNIT/8; + + // Jumping + if (player->cmd.buttons & BT_JUMP) + { + if (minecart->eflags & MFE_ONGROUND) + minecart->eflags &= ~MFE_ONGROUND; + minecart->z += P_MobjFlip(minecart); + if (sidelock) + P_ParabolicMove(minecart, sidelock->x, sidelock->y, sidelock->z, gravity, max(currentSpeed, 10 * FRACUNIT)); + else + minecart->momz = 10 * FRACUNIT; + + S_StartSound(minecart, sfx_s3k51); + jumped = true; + } + + if (!jumped) + { + // Natural acceleration and boosters + if (currentSpeed < minecart->info->seesound) + currentSpeed += FRACUNIT/4; + + if (minecart->standingslope) + { + fa = (minecart->angle >> ANGLETOFINESHIFT) & FINEMASK; + fixed_t front = P_GetZAt(minecart->standingslope, minecart->x, minecart->y); + fixed_t back = P_GetZAt(minecart->standingslope, minecart->x - FINECOSINE(fa), minecart->y - FINESINE(fa)); + + if (abs(front - back) < 3*FRACUNIT) + currentSpeed += (back - front)/3; + } + + // Go forward at our current speed + P_InstaThrust(minecart, minecart->angle, currentSpeed); + + // On-track ka-klong sound FX. + minecart->movecount += abs(currentSpeed); + if (minecart->movecount > 128*FRACUNIT) + { + minecart->movecount %= 128*FRACUNIT; + S_StartSound(minecart, minecart->info->activesound); + } + } + } + else + { + minecart->movefactor++; + if ((P_IsObjectOnGround(minecart) && minecart->movefactor >= 5) // off rail + || (abs(minecart->momx) < minecart->scale/2 && abs(minecart->momy) < minecart->scale/2)) // hit a wall + { + P_KillMobj(player->mo, NULL, NULL, 0); + P_KillMobj(minecart, NULL, NULL, 0); + return; + } + } + } + + P_SetMobjState(player->mo, S_PLAY_STND); + + // Move player to minecart. + P_TeleportMove(player->mo, minecart->x - minecart->momx, minecart->y - minecart->momy, minecart->z + max(minecart->momz, 0) + 8*FRACUNIT); + player->mo->momx = minecart->momx; + player->mo->momy = minecart->momy; + player->mo->momz = 0; + P_TryMove(player->mo, player->mo->x + minecart->momx, player->mo->y + minecart->momy, true); +} + // // P_PlayerThink // @@ -10052,6 +10467,8 @@ void P_PlayerThink(player_t *player) // for a bit after a teleport. if (player->mo->reactiontime) player->mo->reactiontime--; + else if (player->powers[pw_carry] == CR_MINECART) + P_MinecartThink(player); else if (player->mo->tracer && player->mo->tracer->type == MT_TUBEWAYPOINT && (player->powers[pw_carry] == CR_ROPEHANG || player->powers[pw_carry] == CR_ZOOMTUBE)) { if (player->powers[pw_carry] == CR_ROPEHANG) @@ -10113,7 +10530,15 @@ void P_PlayerThink(player_t *player) switch (player->powers[pw_carry]) { case CR_PLAYER: - player->drawangle = (player->mo->tracer->player ? player->mo->tracer->player->drawangle : player->mo->tracer->angle); + if (player->mo->tracer->player) + { + player->drawangle = player->mo->tracer->player->drawangle; + break; + } + /* FALLTHRU */ + case CR_MINECART: + case CR_GENERIC: + player->drawangle = player->mo->tracer->angle; break; /* -- in case we wanted to have the camera freely movable during zoom tubes case CR_ZOOMTUBE:*/