//////////////////////////////////////////////////////////////////////////////////////// // RAVEN SOFTWARE - STAR WARS: JK II // (c) 2002 Activision // // Rail System // // The rail system is intended to provide a means for generating moving entities along // tracks of varying speed and direction. The entities are pulled from the map based // upon their targets and recycled in random positions and order // //////////////////////////////////////////////////////////////////////////////////////// #include "g_headers.h" //////////////////////////////////////////////////////////////////////////////////////// // Externs & Fwd Decl. //////////////////////////////////////////////////////////////////////////////////////// //extern cvar_t* g_nav1; extern void G_SoundAtSpot( vec3_t org, int soundIndex, qboolean broadcast ); class CRailTrack; class CRailLane; class CRailMover; //////////////////////////////////////////////////////////////////////////////////////// // Includes //////////////////////////////////////////////////////////////////////////////////////// #include "b_local.h" #if !defined(RATL_ARRAY_VS_INC) #include "..\Ratl\array_vs.h" #endif #if !defined(RATL_VECTOR_VS_INC) #include "..\Ratl\vector_vs.h" #endif #if !defined(RAVL_VEC_INC) #include "..\Ravl\CVec.h" #endif #if !defined(RUFL_HSTRING_INC) #include "..\Rufl\hstring.h" #endif #if !defined(RATL_GRID_VS_INC) #include "..\Ratl\grid_vs.h" #endif #if !defined(RATL_POOL_VS_INC) #include "..\Ratl\pool_vs.h" #endif #ifdef _XBOX using dllNamespace::hstring; #endif //////////////////////////////////////////////////////////////////////////////////////// // Constants //////////////////////////////////////////////////////////////////////////////////////// #define MAX_TRACKS 4 #define MAX_LANES 8 #define MAX_MOVERS 150 #define MAX_MOVER_ENTS 10 #define MAX_MOVERS_TRACK 80 #define MAX_COLS 32 #define MAX_ROWS 96 #define MAX_ROW_HISTORY 10 #define WOOSH_DEBUG 0 #define WOOSH_ALL_RANGE 1500.0f #define WOOSH_SUPPORT_RANGE 2500.0f #define WOOSH_TUNNEL_RANGE 3000.0f bool mRailSystemActive = false; //////////////////////////////////////////////////////////////////////////////////////// // The Rail Track // // Tracks are the central component to the rails system. They provide the master // repositry of all movers and maintain the list of available movers as well as // //////////////////////////////////////////////////////////////////////////////////////// class CRailTrack { public: void Setup(gentity_t *ent) { mName = ent->targetname; mSpeedGridCellsPerSecond = ent->speed; mNumMoversPerRow = ent->count; mMins = ent->mins; mMaxs = ent->maxs; mStartTime = ent->delay + level.time; mGridCellSize = (ent->radius!=0.0f)?(ent->radius):(1.0f); mVertical = (ent->s.angles[1]==90.0f || ent->s.angles[1]==270.0f); mNegative = (ent->s.angles[1]==180.0f || ent->s.angles[1]==270.0f); // From Maxs To Mins mWAxis = (mVertical)?(0):(1); mHAxis = (mVertical)?(1):(0); mTravelDistanceUnits = ent->maxs[mHAxis] - ent->mins[mHAxis]; mRow = 0; mNextUpdateTime = 0; mCenterLocked = false; SnapVectorToGrid(mMins); SnapVectorToGrid(mMaxs); // Calculate Number Of Rows And Columns //-------------------------------------- mRows = ((mMaxs[mHAxis] - mMins[mHAxis]) / mGridCellSize); mCols = ((mMaxs[mWAxis] - mMins[mWAxis]) / mGridCellSize); // Calculate Grid Center //----------------------- mGridCenter = ((mMins+mMaxs)*0.5f); SnapVectorToGrid(mGridCenter); // Calculate Speed & Velocity //---------------------------- mSpeedUnitsPerMillisecond = mSpeedGridCellsPerSecond * mGridCellSize / 1000.0f; mTravelTimeMilliseconds = mTravelDistanceUnits / mSpeedUnitsPerMillisecond; AngleVectors(ent->s.angles, mDirection.v, 0, 0); mDirection.SafeNorm(); mVelocity = mDirection; mVelocity *= (mSpeedGridCellsPerSecond * mGridCellSize); mNextUpdateDelay = 1000.0f / (float)(mSpeedGridCellsPerSecond); // Calculate Bottom Left Corner //------------------------------ mGridBottomLeftCorner = ent->mins; if (ent->s.angles[1]==180.0f) { mGridBottomLeftCorner[0] = mMaxs[0]; } else if (ent->s.angles[1]==270.0f) { mGridBottomLeftCorner[1] = mMaxs[1]; } SnapVectorToGrid(mGridBottomLeftCorner); mCells.set_size(mCols/*xSize*/, mRows/*ySize*/); mCells.init(0); mMovers.clear(); if (!mNumMoversPerRow) { mNumMoversPerRow = 3; } // Safe Clamp Number Of Rows & Cols //---------------------------------- if (mRows>(MAX_ROWS-1)) { mRows = (MAX_ROWS-1); assert(0); } if (mCols>(MAX_COLS-1)) { mCols = (MAX_COLS-1); assert(0); } } void SnapVectorToGrid(CVec3& Vec) { SnapFloatToGrid(Vec[0]); SnapFloatToGrid(Vec[1]); } void SnapFloatToGrid(float& f) { f = (int)(f); bool fNeg = (f<0); if (fNeg) { f *= -1; // Temporarly make it positive } int Offset = ((int)(f) % (int)(mGridCellSize)); int OffsetAbs = abs(Offset); if (OffsetAbs>(mGridCellSize/2)) { Offset = (mGridCellSize - OffsetAbs) * -1; } f -= Offset; if (fNeg) { f *= -1; // Put It Back To Negative } f = (int)(f); assert(((int)(f)%(int)(mGridCellSize)) == 0); } void Update(); bool TestMoverInCells(CRailMover* mover, int atCol); void InsertMoverInCells(CRailMover* mover, int atCol); void RandomizeTestCols(int startCol, int stopCol); public: hstring mName; int mRow; int mNumMoversPerRow; int mNextUpdateTime; int mNextUpdateDelay; int mStartTime; int mRows; int mCols; bool mVertical; bool mNegative; int mHAxis; int mWAxis; int mSpeedGridCellsPerSecond; float mSpeedUnitsPerMillisecond; int mTravelTimeMilliseconds; float mTravelDistanceUnits; CVec3 mDirection; CVec3 mVelocity; CVec3 mMins; CVec3 mMaxs; CVec3 mGridBottomLeftCorner; CVec3 mGridCenter; float mGridCellSize; bool mCenterLocked; ratl::grid2_vs mCells; ratl::vector_vs mMovers; ratl::vector_vs mTestCols; }; ratl::vector_vs mRailTracks; //////////////////////////////////////////////////////////////////////////////////////// /*QUAKED rail_track (0 .5 .8) ? x x x x x x x x A rail track determines what location and direction rail_mover entities go. Don't bother with any origin brushes. Make sure to set: "radius" Number of units to break down into grid size "speed" Number of grid sized units per second rail_movers will go at "angle" The direction rail_movers will go "count" The number of mover ents the track will try to add per row "delay" How long the ent will wait from the start of the level before placing movers */ //////////////////////////////////////////////////////////////////////////////////////// void SP_rail_track(gentity_t *ent) { gi.SetBrushModel(ent, ent->model); G_SpawnInt("delay", "0", &ent->delay); mRailTracks.push_back().Setup(ent); G_FreeEntity(ent); mRailSystemActive = true; } //////////////////////////////////////////////////////////////////////////////////////// // The Rail Lane // // // //////////////////////////////////////////////////////////////////////////////////////// class CRailLane { public: //////////////////////////////////////////////////////////////////////////////////// // From Entity Setup Spawn //////////////////////////////////////////////////////////////////////////////////// void Setup(gentity_t* ent) { mName = ent->targetname; mNameTrack = ent->target; mMins = ent->mins; mMaxs = ent->maxs; mStartTime = ent->delay + level.time; } hstring mName; hstring mNameTrack; CVec3 mMins; CVec3 mMaxs; int mStartTime; public: //////////////////////////////////////////////////////////////////////////////////// // Initialize // // This function scans through the list of tracks and hooks itself up with the // track //////////////////////////////////////////////////////////////////////////////////// void Initialize() { mTrack = 0; mMinCol = 0; mMaxCol = 0; // int dummy; for (int i=0; iSnapVectorToGrid(mMins); mTrack->SnapVectorToGrid(mMaxs); mMinCol = (int)((mMins[mTrack->mWAxis] - mTrack->mMins[mTrack->mWAxis])/mTrack->mGridCellSize); mMaxCol = (int)((mMaxs[mTrack->mWAxis] - mTrack->mMins[mTrack->mWAxis] - (mTrack->mGridCellSize/2.0f))/mTrack->mGridCellSize); //if (mTrack->mNegative) //{ // mMinCol = (mTrack->mCols - mMinCol - 1); // mMaxCol = (mTrack->mCols - mMaxCol - 1); //} // mTrack->mCells.get_cell_coords(mMins[mTrack->mWAxis], 0, mMinCol, dummy); // mTrack->mCells.get_cell_coords((mMaxs[mTrack->mWAxis]-10.0f), 0, mMaxCol, dummy); break; } } assert(mTrack!=0); } CRailTrack* mTrack; int mMinCol; int mMaxCol; }; ratl::vector_vs mRailLanes; //////////////////////////////////////////////////////////////////////////////////////// /*QUAKED rail_lane (0 .5 .8) ? x x x x x x x x Use rail lanes to split up tracks. Just target it to a track that you want to break up into pieces "delay" How long the ent will wait from the start of the level before placing movers */ //////////////////////////////////////////////////////////////////////////////////////// void SP_rail_lane(gentity_t *ent) { gi.SetBrushModel(ent, ent->model); G_SpawnInt("delay", "0", &ent->delay); mRailLanes.push_back().Setup(ent); G_FreeEntity(ent); } //////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////// class CRailMover { public: //////////////////////////////////////////////////////////////////////////////////// // From Entity Setup Spawn //////////////////////////////////////////////////////////////////////////////////// void Setup(gentity_t* ent) { mEnt = ent; mCenter = (ent->spawnflags&1); mSoundPlayed = false; mOriginOffset = ent->mins; mOriginOffset += ent->maxs; mOriginOffset *= 0.5f; mOriginOffset[2] = 0;//((ent->maxs[2] - ent->mins[2]) * 0.5); ent->e_ReachedFunc = reachedF_NULL; ent->moverState = MOVER_POS1; ent->svFlags = SVF_USE_CURRENT_ORIGIN; ent->s.eType = ET_MOVER; ent->s.eFlags |= EF_NODRAW; ent->contents = 0; ent->clipmask = 0; ent->s.pos.trType = TR_STATIONARY; ent->s.pos.trDuration = 0; ent->s.pos.trTime = 0; VectorCopy( ent->pos1, ent->currentOrigin ); VectorCopy( ent->pos1, ent->s.pos.trBase ); gi.linkentity(ent); } gentity_t* mEnt; bool mCenter; CVec3 mOriginOffset; bool mSoundPlayed; bool Active() { assert(mEnt!=0); return (level.time<(mEnt->s.pos.trDuration + mEnt->s.pos.trTime)); } public: //////////////////////////////////////////////////////////////////////////////////// // Initialize // // This function scans through the list of tracks and hooks itself up with the // track (and possibly lane) //////////////////////////////////////////////////////////////////////////////////// void Initialize() { mTrack = 0; mLane = 0; mCols = 0; mRows = 0; hstring target = mEnt->target; for (int track=0; trackmTrack; break; } } } assert(mTrack!=0); if (mTrack) { mTrack->mMovers.push_back(this); mCols = (int)((mEnt->maxs[mTrack->mWAxis] - mEnt->mins[mTrack->mWAxis]) / mTrack->mGridCellSize) + 1; mRows = (int)((mEnt->maxs[mTrack->mHAxis] - mEnt->mins[mTrack->mHAxis]) / mTrack->mGridCellSize) + 1; // Make Sure The Mover Fits In The Track And Lane //------------------------------------------------ if (mRows>mTrack->mRows) { // assert(0); mRows = mTrack->mRows; } if (mCols>mTrack->mCols) { // assert(0); mCols = mTrack->mCols; } if (mLane && mCols>(mLane->mMaxCol - mLane->mMinCol + 1)) { // assert(0); mCols = (mLane->mMaxCol - mLane->mMinCol + 1); } } } CRailTrack* mTrack; CRailLane* mLane; int mCols; int mRows; }; ratl::vector_vs mRailMovers; //////////////////////////////////////////////////////////////////////////////////////// /*QUAKED rail_mover (0 .5 .8) ? CENTER x x x x x x x Rail Mover will go along the track and lane of your choice. Just target it to either a track or a lane. Don't bother with any origin brushes. CENTER Will force this mover to attempt to center in the track or lane "target" The track or lane you want this entity to move through "model" A model you wish to use, not necessary - can be just a brush "angle" Random angle rotation allowable on this thing */ //////////////////////////////////////////////////////////////////////////////////////// void SP_rail_mover(gentity_t *ent) { gi.SetBrushModel(ent, ent->model); mRailMovers.push_back().Setup(ent); } ratl::vector_vs mWooshSml; // Small Building ratl::vector_vs mWooshMed; // Medium Building ratl::vector_vs mWooshLar; // Large Building ratl::vector_vs mWooshSup; // Track Support ratl::vector_vs mWooshTun; // Tunnel //////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////// void Rail_Reset() { mRailSystemActive = false; mRailTracks.clear(); mRailLanes.clear(); mRailMovers.clear(); mWooshSml.clear(); mWooshMed.clear(); mWooshLar.clear(); mWooshSup.clear(); mWooshTun.clear(); } //////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////// void Rail_Initialize() { for (int lane=0; lanemRailTracks[track].mNextUpdateTime && !mRailTracks[track].mMovers.empty()) { mRailTracks[track].Update(); } } // Is The Player Outside? //------------------------ if (player && gi.WE_IsOutside(player->currentOrigin)) { int wooshSound; vec3_t wooshSoundPos; vec3_t moverOrigin; vec3_t playerToMover; float playerToMoverDistance; float playerToMoverDistanceFraction; // Iterate Over All The Movers //----------------------------- for (int moverIndex=0; moverIndexcurrentOrigin, mover.mOriginOffset.v, moverOrigin); VectorSubtract(moverOrigin, player->currentOrigin, playerToMover); playerToMover[2] = 0.0f; playerToMoverDistance = VectorNormalize(playerToMover); // Is It Close Enough? //--------------------- if ((( mover.mLane || !mover.mCenter) && // Not Center Track (playerToMoverDistancemDirection.v)>-0.45f)) // And On The Side || //OR ((!mover.mLane && mover.mCenter) && // Is Center Track (playerToMoverDistance10)) // Or Close Enough For Tunnel )) { mover.mSoundPlayed = true; wooshSound = 0; // The Centered Entities Play Right On The Player's Head For Full Volume //----------------------------------------------------------------------- if (mover.mCenter && !mover.mLane) { VectorCopy(player->currentOrigin, wooshSoundPos); wooshSoundPos[2] += 50; // If It Is Very Long, Play The Tunnel Sound //------------------------------------------- if (mover.mRows>10) { wooshSound = mWooshTun[Q_irand(0, mWooshTun.size()-1)]; } // Otherwise It Is A Support //--------------------------- else { wooshSound = mWooshSup[Q_irand(0, mWooshSup.size()-1)]; } } // All Other Entities Play At A Fraction Of Their Normal Range //------------------------------------------------------------- else { // Scale The Play Pos By The Square Of The Distance //-------------------------------------------------- playerToMoverDistanceFraction = playerToMoverDistance/WOOSH_ALL_RANGE; playerToMoverDistanceFraction *= playerToMoverDistanceFraction; playerToMoverDistanceFraction *= 0.6f; playerToMoverDistance *= playerToMoverDistanceFraction; VectorMA(player->currentOrigin, playerToMoverDistance, playerToMover, wooshSoundPos); // Large Building //---------------- if (mover.mRows>4) { wooshSound = mWooshLar[Q_irand(0, mWooshLar.size()-1)]; } // Medium Building //----------------- else if (mover.mRows>2) { wooshSound = mWooshMed[Q_irand(0, mWooshMed.size()-1)]; } // Small Building //---------------- else { wooshSound = mWooshSml[Q_irand(0, mWooshSml.size()-1)]; } } // If A Woosh Sound Was Selected, Play It Now //-------------------------------------------- if (wooshSound) { G_SoundAtSpot(wooshSoundPos, wooshSound, qfalse); if (WOOSH_DEBUG) { CG_DrawEdge(player->currentOrigin, wooshSoundPos, EDGE_WHITE_TWOSECOND); } } } } } } } } //////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////// void Rail_LockCenterOfTrack(const char* trackName) { hstring name = trackName; for (int track=0; trackActive()) { continue; } // Don't Spawn Until Start Time Has Expired //------------------------------------------ if (level.time < ((mover->mLane)?(mover->mLane->mStartTime):(mStartTime))) { continue; } // If Center Locked, Stop Spawning Center Track Movers //----------------------------------------------------- if (mover->mCenter && mCenterLocked) { continue; } // Restrict It To A Lane //----------------------- if (mover->mLane) { startCol = mover->mLane->mMinCol; stopCol = mover->mLane->mMaxCol+1; } // Or Let It Go Anywhere On The Track //------------------------------------ else { startCol = 0; stopCol = mCols; } stopCol -= (mover->mCols-1); // If The Mover Is Too Big To Fit In The Lane, Go On To Next Attempt //------------------------------------------------------------------- if (stopCol<=startCol) { assert(0); // Should Not Happen continue; } // Force It To Center //-------------------- if (mover->mCenter && stopCol!=(startCol+1)) { startCol = ((mCols/2) - (mover->mCols/2)); stopCol = startCol+1; } // Construct A List Of Columns To Test For Insertion //--------------------------------------------------- mTestCols.clear(); for (int i=startCol; imCols/2.0f) * mGridCellSize)); StartPos[mHAxis] += (((mover->mRows/2.0f) * mGridCellSize) * ((mNegative)?(1):(-1))); StartPos[2] = 0; // If Centered, Actually Put It At EXACTLY The Right Position On The Width Axis //------------------------------------------------------------------------------ if (mover->mCenter) { StartPos[mWAxis] = mGridCenter[mWAxis]; float deltaOffset = mGridCenter[mWAxis] - mover->mOriginOffset[mWAxis]; if (deltaOffset<(mGridCellSize*0.5f) ) { StartPos[mWAxis] -= deltaOffset; } } StartPos -= mover->mOriginOffset; G_SetOrigin(mover->mEnt, StartPos.v); // Start It Moving //----------------- VectorCopy(StartPos.v, mover->mEnt->s.pos.trBase); VectorCopy(mVelocity.v, mover->mEnt->s.pos.trDelta); mover->mEnt->s.pos.trTime = level.time; mover->mEnt->s.pos.trDuration = mTravelTimeMilliseconds + (mNextUpdateDelay*mover->mRows); mover->mEnt->s.pos.trType = TR_LINEAR_STOP; mover->mEnt->s.eFlags &= ~EF_NODRAW; mover->mSoundPlayed = false; // Successfully Inserted This Mover. Now Move On To The Next Mover //------------------------------------------------------------------ break; } } } // Incriment The Current Row //--------------------------- mRow++; if (mRow>=mRows) { mRow = 0; } // Erase The Erase Row //--------------------- int EraseRow = mRow - MAX_ROW_HISTORY; if (EraseRow<0) { EraseRow += mRows; } for (int col=0; colmRows); moverRow++) //{ for (int moverCol=0; (moverColmCols); moverCol++) { if (mCells.get(atCol+moverCol, mRow/*+moverRow*/)!=0) { return false; } } //} return true; } //////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////// void CRailTrack::InsertMoverInCells(CRailMover* mover, int atCol) { for (int moverCol=0; (moverColmCols); moverCol++) { int col = atCol+moverCol; for (int moverRow=0; (moverRowmRows); moverRow++) { int row = mRow+moverRow; if (row>=mRows) { row -= mRows; } assert(mCells.get(col, row)==0); mCells.get(col, row) = mover; } } }