jkxr/Projects/Android/jni/OpenJK/code/game/g_rail.cpp
Simon 4597b03873 Initial Commit
Opens in Android Studio but haven't even tried to build it yet (it won't.. I know that much!)
2022-09-18 16:37:21 +01:00

994 lines
28 KiB
C++

/*
===========================================================================
Copyright (C) 2000 - 2013, Raven Software, Inc.
Copyright (C) 2001 - 2013, Activision, Inc.
Copyright (C) 2013 - 2015, OpenJK contributors
This file is part of the OpenJK source code.
OpenJK is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/
////////////////////////////////////////////////////////////////////////////////////////
// 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 "../qcommon/q_shared.h"
#include "bg_public.h"
#include "../cgame/cg_local.h"
#include "g_functions.h"
////////////////////////////////////////////////////////////////////////////////////////
// Externs & Fwd Decl.
////////////////////////////////////////////////////////////////////////////////////////
extern void G_SoundAtSpot( vec3_t org, int soundIndex, qboolean broadcast );
extern void CG_DrawEdge( vec3_t start, vec3_t end, int type );
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
////////////////////////////////////////////////////////////////////////////////////////
// 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<CRailMover*, MAX_COLS, MAX_ROWS> mCells;
ratl::vector_vs<CRailMover*, MAX_MOVERS_TRACK> mMovers;
ratl::vector_vs<int, MAX_ROWS> mTestCols;
};
ratl::vector_vs<CRailTrack, MAX_TRACKS> 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; i<mRailTracks.size(); i++)
{
if (mRailTracks[i].mName==mNameTrack)
{
mTrack = &(mRailTracks[i]);
mTrack->SnapVectorToGrid(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<CRailLane, MAX_LANES> 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; track<mRailTracks.size(); track++)
{
if (mRailTracks[track].mName==target)
{
mTrack = &(mRailTracks[track]);
break;
}
}
if (mTrack==0)
{
for (int lane=0; lane<mRailLanes.size(); lane++)
{
if (mRailLanes[lane].mName==target)
{
mLane = &(mRailLanes[lane]);
mTrack = mLane->mTrack;
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<CRailMover, MAX_MOVERS> 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<int, 20> mWooshSml; // Small Building
ratl::vector_vs<int, 20> mWooshMed; // Medium Building
ratl::vector_vs<int, 10> mWooshLar; // Large Building
ratl::vector_vs<int, 10> mWooshSup; // Track Support
ratl::vector_vs<int, 3> 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; lane<mRailLanes.size(); lane++)
{
mRailLanes[lane].Initialize();
}
for (int mover=0; mover<mRailMovers.size(); mover++)
{
mRailMovers[mover].Initialize();
}
// Precache All The Woosh Sounds
//-------------------------------
if (!mRailMovers.empty())
{
mWooshMed.push_back(G_SoundIndex("sound/effects/woosh1"));
mWooshSml.push_back(G_SoundIndex("sound/effects/woosh2"));
mWooshMed.push_back(G_SoundIndex("sound/effects/woosh3"));
mWooshSml.push_back(G_SoundIndex("sound/effects/woosh4"));
mWooshLar.push_back(G_SoundIndex("sound/effects/woosh5"));
mWooshSml.push_back(G_SoundIndex("sound/effects/woosh6"));
mWooshSup.push_back(G_SoundIndex("sound/effects/woosh7"));
mWooshSup.push_back(G_SoundIndex("sound/effects/woosh8"));
mWooshSup.push_back(G_SoundIndex("sound/effects/woosh9"));
mWooshLar.push_back(G_SoundIndex("sound/effects/woosh10"));
mWooshLar.push_back(G_SoundIndex("sound/effects/woosh11"));
mWooshLar.push_back(G_SoundIndex("sound/effects/woosh12"));
mWooshSml.push_back(G_SoundIndex("sound/effects/woosh13"));
mWooshMed.push_back(G_SoundIndex("sound/effects/woosh14"));
mWooshMed.push_back(G_SoundIndex("sound/effects/woosh15"));
mWooshMed.push_back(G_SoundIndex("sound/effects/woosh16"));
mWooshSml.push_back(G_SoundIndex("sound/effects/woosh17"));
mWooshMed.push_back(G_SoundIndex("sound/effects/woosh18"));
mWooshMed.push_back(G_SoundIndex("sound/effects/woosh19"));
mWooshMed.push_back(G_SoundIndex("sound/effects/woosh20"));
mWooshMed.push_back(G_SoundIndex("sound/effects/woosh21"));
mWooshLar.push_back(G_SoundIndex("sound/effects/woosh22"));
mWooshLar.push_back(G_SoundIndex("sound/effects/woosh23"));
mWooshSup.push_back(G_SoundIndex("sound/effects/woosh24"));
mWooshSup.push_back(G_SoundIndex("sound/effects/woosh25"));
mWooshMed.push_back(G_SoundIndex("sound/effects/woosh26"));
mWooshMed.push_back(G_SoundIndex("sound/effects/woosh27"));
mWooshMed.push_back(G_SoundIndex("sound/effects/woosh28"));
mWooshLar.push_back(G_SoundIndex("sound/effects/woosh29"));
mWooshTun.push_back(G_SoundIndex("sound/effects/whoosh_tunnel"));
}
}
////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////
void Rail_Update()
{
if (mRailSystemActive)// && false)
{
for (int track=0; track<mRailTracks.size(); track++)
{
if (level.time>mRailTracks[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; moverIndex<mRailMovers.size(); moverIndex++)
{
CRailMover& mover = mRailMovers[moverIndex];
// Is It Active, And Has The Sound Already Played On It?
//--------------------------------------------------------
if (mover.Active() && !mover.mSoundPlayed)
{
VectorAdd(mover.mEnt->currentOrigin, 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
(playerToMoverDistance<WOOSH_ALL_RANGE) && // And Close Enough
(DotProduct(playerToMover, mover.mTrack->mDirection.v)>-0.45f)) // And On The Side
|| //OR
((!mover.mLane && mover.mCenter) && // Is Center Track
(playerToMoverDistance<WOOSH_SUPPORT_RANGE || // And Close Enough for Support
(playerToMoverDistance<WOOSH_TUNNEL_RANGE && mover.mRows>10)) // 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; track<mRailTracks.size(); track++)
{
if (mRailTracks[track].mName==name)
{
mRailTracks[track].mCenterLocked = true;
return;
}
}
assert(0);
}
////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////
void Rail_UnLockCenterOfTrack(const char* trackName)
{
hstring name = trackName;
for (int track=0; track<mRailTracks.size(); track++)
{
if (mRailTracks[track].mName==name)
{
mRailTracks[track].mCenterLocked = false;
return;
}
}
assert(0);
}
////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////
void CRailTrack::Update()
{
mNextUpdateTime = level.time + mNextUpdateDelay;
// Now, Attempt To Add A Number Of Movers To The Track
//-----------------------------------------------------
int attempt;
int startCol;
int stopCol;
int atCol;
int testColIndex;
for (attempt=0; attempt<mNumMoversPerRow; attempt++)
{
// Randomly Select A Mover And Test To See If It Is Active
//---------------------------------------------------------
CRailMover* mover = mMovers[Q_irand(0, mMovers.size()-1)];
if (mover->Active())
{
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; i<stopCol; i++)
{
mTestCols.push_back(i);
}
// Now Try All The Cols To See If The Building Can Fit
//-----------------------------------------------------
while (!mTestCols.empty())
{
// Randomly Pick A Column, Then Remove It From The Vector
//--------------------------------------------------------
testColIndex = Q_irand(0, mTestCols.size()-1);
atCol = mTestCols[testColIndex];
mTestCols.erase_swap(testColIndex);
if (TestMoverInCells(mover, atCol))
{
// Ok, We've Found A Safe Column To Insert This Mover
//----------------------------------------------------
InsertMoverInCells(mover, atCol);
// Now Transport The Actual Mover Entity Into Position, Link It & Send It Off
//----------------------------------------------------------------------------
CVec3 StartPos(mGridBottomLeftCorner);
StartPos[mWAxis] += ((atCol * mGridCellSize) + ((mover->mCols/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; col<mCols; col++)
{
mCells.get(col, EraseRow) = 0;
}
}
////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////
void CRailTrack::RandomizeTestCols(int startCol, int stopCol)
{
int numCols = (stopCol - startCol);
int swapA;
int swapB;
for (int swapNum=0; swapNum<numCols; swapNum++)
{
swapA = Q_irand(0, numCols-1);
swapB = Q_irand(0, numCols-1);
if (swapA!=swapB)
{
mTestCols.swap(swapA, swapB);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////
bool CRailTrack::TestMoverInCells(CRailMover* mover, int atCol)
{
//for (int moverRow=0; (moverRow<mover->mRows); moverRow++)
//{
for (int moverCol=0; (moverCol<mover->mCols); moverCol++)
{
if (mCells.get(atCol+moverCol, mRow/*+moverRow*/)!=0)
{
return false;
}
}
//}
return true;
}
////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////
void CRailTrack::InsertMoverInCells(CRailMover* mover, int atCol)
{
for (int moverCol=0; (moverCol<mover->mCols); moverCol++)
{
int col = atCol+moverCol;
for (int moverRow=0; (moverRow<mover->mRows); moverRow++)
{
int row = mRow+moverRow;
if (row>=mRows)
{
row -= mRows;
}
assert(mCells.get(col, row)==0);
mCells.get(col, row) = mover;
}
}
}