mirror of
https://github.com/DrBeef/JKXR.git
synced 2024-11-29 23:42:38 +00:00
4597b03873
Opens in Android Studio but haven't even tried to build it yet (it won't.. I know that much!)
994 lines
28 KiB
C++
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;
|
|
}
|
|
}
|
|
}
|
|
|