Initial commit of Command & Conquer Generals and Command & Conquer Generals Zero Hour source code.
This commit is contained in:
parent
2e338c00cb
commit
3d0ee53a05
6072 changed files with 2283311 additions and 0 deletions
543
Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp
Normal file
543
Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp
Normal file
|
@ -0,0 +1,543 @@
|
|||
/*
|
||||
** Command & Conquer Generals(tm)
|
||||
** Copyright 2025 Electronic Arts Inc.
|
||||
**
|
||||
** This program is free software: you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation, either version 3 of the License, or
|
||||
** (at your option) any later version.
|
||||
**
|
||||
** 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/>.
|
||||
*/
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// (c) 2001-2003 Electronic Arts Inc. //
|
||||
// //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// PolygonTrigger.cpp
|
||||
// Class to encapsulate polygon trigger areas.
|
||||
// Author: John Ahlquist, November 2001
|
||||
|
||||
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
|
||||
|
||||
#include "Common/DataChunk.h"
|
||||
#include "Common/MapObject.h"
|
||||
#include "Common/MapReaderWriterInfo.h"
|
||||
#include "Common/Xfer.h"
|
||||
#include "GameLogic/PolygonTrigger.h"
|
||||
#include "GameLogic/TerrainLogic.h"
|
||||
|
||||
/* ********* PolygonTrigger class ****************************/
|
||||
PolygonTrigger *PolygonTrigger::ThePolygonTriggerListPtr = NULL;
|
||||
Int PolygonTrigger::s_currentID = 1;
|
||||
/**
|
||||
PolygonTrigger - Constructor.
|
||||
*/
|
||||
PolygonTrigger::PolygonTrigger(Int initialAllocation) :
|
||||
m_nextPolygonTrigger(NULL),
|
||||
m_points(NULL),
|
||||
m_numPoints(0),
|
||||
m_sizePoints(0),
|
||||
m_exportWithScripts(false),
|
||||
m_isWaterArea(false),
|
||||
//Added By Sadullah Nader
|
||||
//Initializations inserted
|
||||
m_isRiver(FALSE),
|
||||
m_riverStart(0)
|
||||
//
|
||||
{
|
||||
if (initialAllocation < 2) initialAllocation = 2;
|
||||
m_points = NEW ICoord3D[initialAllocation]; // pool[]ify
|
||||
m_sizePoints = initialAllocation;
|
||||
m_triggerID = s_currentID++;
|
||||
|
||||
m_waterHandle.m_polygon = this;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
PolygonTrigger - Destructor - note - if linked, deletes linked items.
|
||||
*/
|
||||
PolygonTrigger::~PolygonTrigger(void)
|
||||
{
|
||||
if (m_points) {
|
||||
delete [] m_points;
|
||||
m_points = NULL;
|
||||
}
|
||||
if (m_nextPolygonTrigger) {
|
||||
PolygonTrigger *cur = m_nextPolygonTrigger;
|
||||
PolygonTrigger *next;
|
||||
while (cur) {
|
||||
next = cur->getNext();
|
||||
cur->setNextPoly(NULL); // prevents recursion.
|
||||
cur->deleteInstance();
|
||||
cur = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
PolygonTrigger::reallocate - increases the size of the points list.
|
||||
NOTE: It is expected that this will only get called in the editor, as in the game
|
||||
the poly triggers don't change.
|
||||
*/
|
||||
void PolygonTrigger::reallocate(void)
|
||||
{
|
||||
DEBUG_ASSERTCRASH(m_numPoints <= m_sizePoints, ("Invalid m_numPoints."));
|
||||
if (m_numPoints == m_sizePoints) {
|
||||
// Reallocate.
|
||||
m_sizePoints += m_sizePoints;
|
||||
ICoord3D *newPts = NEW ICoord3D[m_sizePoints];
|
||||
Int i;
|
||||
for (i=0; i<m_numPoints; i++) {
|
||||
newPts[i] = m_points[i];
|
||||
}
|
||||
delete [] m_points;
|
||||
m_points = newPts;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the polygon trigger with the matching ID
|
||||
*/
|
||||
PolygonTrigger *PolygonTrigger::getPolygonTriggerByID(Int triggerID)
|
||||
{
|
||||
|
||||
for( PolygonTrigger *poly = PolygonTrigger::getFirstPolygonTrigger();
|
||||
poly; poly = poly->getNext() )
|
||||
if( poly->getID() == triggerID )
|
||||
return poly;
|
||||
|
||||
// not found
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* PolygonTrigger::ParsePolygonTriggersDataChunk - read a polygon triggers chunk.
|
||||
* Format is the newer CHUNKY format.
|
||||
* See PolygonTrigger::WritePolygonTriggersDataChunk for the writer.
|
||||
* Input: DataChunkInput
|
||||
*
|
||||
*/
|
||||
Bool PolygonTrigger::ParsePolygonTriggersDataChunk(DataChunkInput &file, DataChunkInfo *info, void *userData)
|
||||
{
|
||||
Int count;
|
||||
Int numPoints;
|
||||
Int triggerID;
|
||||
Int maxTriggerId = 0;
|
||||
Bool isWater;
|
||||
Bool isRiver;
|
||||
Int riverStart;
|
||||
AsciiString triggerName;
|
||||
// Remove any existing polygon triggers, if any.
|
||||
PolygonTrigger::deleteTriggers(); // just in case.
|
||||
PolygonTrigger *pPrevTrig = NULL;
|
||||
ICoord3D loc;
|
||||
count = file.readInt();
|
||||
while (count>0) {
|
||||
count--;
|
||||
triggerName = file.readAsciiString();
|
||||
triggerID = file.readInt();
|
||||
isWater = false;
|
||||
if (info->version >= K_TRIGGERS_VERSION_2) {
|
||||
isWater = file.readByte();
|
||||
}
|
||||
isRiver = false;
|
||||
riverStart = 0;
|
||||
if (info->version >= K_TRIGGERS_VERSION_3) {
|
||||
isRiver = file.readByte();
|
||||
riverStart = file.readInt();
|
||||
}
|
||||
|
||||
numPoints = file.readInt();
|
||||
PolygonTrigger *pTrig = newInstance(PolygonTrigger)(numPoints+1);
|
||||
pTrig->setTriggerName(triggerName);
|
||||
pTrig->setWaterArea(isWater);
|
||||
pTrig->setRiver(isRiver);
|
||||
pTrig->setRiverStart(riverStart);
|
||||
pTrig->m_triggerID = triggerID;
|
||||
if (triggerID > maxTriggerId) {
|
||||
maxTriggerId = triggerID;
|
||||
}
|
||||
Int i;
|
||||
for (i=0; i<numPoints; i++) {
|
||||
loc.x = file.readInt();
|
||||
loc.y = file.readInt();
|
||||
loc.z = file.readInt();
|
||||
pTrig->addPoint(loc);
|
||||
}
|
||||
if (pPrevTrig) {
|
||||
pPrevTrig->setNextPoly(pTrig);
|
||||
} else {
|
||||
PolygonTrigger::addPolygonTrigger(pTrig);
|
||||
}
|
||||
pPrevTrig = pTrig;
|
||||
}
|
||||
if (info->version == K_TRIGGERS_VERSION_1)
|
||||
{
|
||||
// before water areas existed, so create a default one.
|
||||
PolygonTrigger *pTrig = newInstance(PolygonTrigger)(4);
|
||||
pTrig->setWaterArea(true);
|
||||
#ifdef _DEBUG
|
||||
pTrig->setTriggerName("AutoAddedWaterAreaTrigger");
|
||||
#endif
|
||||
pTrig->m_triggerID = maxTriggerId++;
|
||||
loc.x = -30*MAP_XY_FACTOR;
|
||||
loc.y = -30*MAP_XY_FACTOR;
|
||||
loc.z = 7; // The old water position.
|
||||
pTrig->addPoint(loc);
|
||||
loc.x = 30*MAP_XY_FACTOR + TheGlobalData->m_waterExtentX;
|
||||
pTrig->addPoint(loc);
|
||||
loc.y = 30*MAP_XY_FACTOR + TheGlobalData->m_waterExtentY;
|
||||
pTrig->addPoint(loc);
|
||||
loc.x = -30*MAP_XY_FACTOR;
|
||||
pTrig->addPoint(loc);
|
||||
if (pPrevTrig) {
|
||||
pPrevTrig->setNextPoly(pTrig);
|
||||
} else {
|
||||
PolygonTrigger::addPolygonTrigger(pTrig);
|
||||
}
|
||||
pPrevTrig = pTrig;
|
||||
}
|
||||
s_currentID = maxTriggerId+1;
|
||||
DEBUG_ASSERTCRASH(file.atEndOfChunk(), ("Incorrect data file length."));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* PolygonTrigger::WritePolygonTriggersDataChunk - Writes a Polygon triggers chunk.
|
||||
* Format is the newer CHUNKY format.
|
||||
* See PolygonTrigger::ParsePolygonTriggersDataChunk for the reader.
|
||||
* Input: DataChunkInput
|
||||
*
|
||||
*/
|
||||
void PolygonTrigger::WritePolygonTriggersDataChunk(DataChunkOutput &chunkWriter)
|
||||
{
|
||||
chunkWriter.openDataChunk("PolygonTriggers", K_TRIGGERS_VERSION_3);
|
||||
|
||||
PolygonTrigger *pTrig;
|
||||
Int count = 0;
|
||||
for (pTrig=PolygonTrigger::getFirstPolygonTrigger(); pTrig; pTrig = pTrig->getNext()) {
|
||||
count++;
|
||||
}
|
||||
chunkWriter.writeInt(count);
|
||||
for (pTrig=PolygonTrigger::getFirstPolygonTrigger(); pTrig; pTrig = pTrig->getNext()) {
|
||||
chunkWriter.writeAsciiString(pTrig->getTriggerName());
|
||||
chunkWriter.writeInt(pTrig->getID());
|
||||
chunkWriter.writeByte(pTrig->isWaterArea());
|
||||
chunkWriter.writeByte(pTrig->isRiver());
|
||||
chunkWriter.writeInt(pTrig->getRiverStart());
|
||||
chunkWriter.writeInt(pTrig->getNumPoints());
|
||||
Int i;
|
||||
for (i=0; i<pTrig->getNumPoints(); i++) {
|
||||
ICoord3D loc = *pTrig->getPoint(i);
|
||||
chunkWriter.writeInt( loc.x);
|
||||
chunkWriter.writeInt( loc.y);
|
||||
chunkWriter.writeInt( loc.z);
|
||||
}
|
||||
}
|
||||
|
||||
chunkWriter.closeDataChunk();
|
||||
}
|
||||
|
||||
/**
|
||||
PolygonTrigger::updateBounds - Updates the bounds.
|
||||
*/
|
||||
void PolygonTrigger::updateBounds(void) const
|
||||
{
|
||||
const Int BIG_INT=0x7ffff0;
|
||||
m_bounds.lo.x = m_bounds.lo.y = BIG_INT;
|
||||
m_bounds.hi.x = m_bounds.hi.y = -BIG_INT;
|
||||
Int i;
|
||||
for (i=0; i<m_numPoints; i++) {
|
||||
if (m_points[i].x < m_bounds.lo.x) m_bounds.lo.x = m_points[i].x;
|
||||
if (m_points[i].y < m_bounds.lo.y) m_bounds.lo.y = m_points[i].y;
|
||||
if (m_points[i].x > m_bounds.hi.x) m_bounds.hi.x = m_points[i].x;
|
||||
if (m_points[i].y > m_bounds.hi.y) m_bounds.hi.y = m_points[i].y;
|
||||
}
|
||||
m_boundsNeedsUpdate = 0;
|
||||
Real halfWidth = (m_bounds.hi.x - m_bounds.lo.x) / 2.0f;
|
||||
Real halfHeight = (m_bounds.hi.y + m_bounds.lo.y) / 2.0f;
|
||||
|
||||
m_radius = sqrt(halfHeight*halfHeight + halfWidth*halfWidth);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
PolygonTrigger::addPolygonTrigger adds a trigger to the list of triggers.
|
||||
*/
|
||||
void PolygonTrigger::addPolygonTrigger(PolygonTrigger *pTrigger)
|
||||
{
|
||||
for (PolygonTrigger *pTrig=getFirstPolygonTrigger(); pTrig; pTrig = pTrig->getNext()) {
|
||||
DEBUG_ASSERTCRASH(pTrig != pTrigger, ("Attempting to add trigger already in list."));
|
||||
if (pTrig==pTrigger) return;
|
||||
}
|
||||
pTrigger->m_nextPolygonTrigger = ThePolygonTriggerListPtr;
|
||||
ThePolygonTriggerListPtr = pTrigger;
|
||||
}
|
||||
|
||||
/**
|
||||
PolygonTrigger::removePolygonTrigger removes a trigger to the list of
|
||||
triggers. note - does NOT delete pTrigger.
|
||||
*/
|
||||
void PolygonTrigger::removePolygonTrigger(PolygonTrigger *pTrigger)
|
||||
{
|
||||
PolygonTrigger *pPrev = NULL;
|
||||
for (PolygonTrigger *pTrig=getFirstPolygonTrigger(); pTrig; pTrig = pTrig->getNext()) {
|
||||
if (pTrig==pTrigger) break;
|
||||
pPrev = pTrig;
|
||||
}
|
||||
DEBUG_ASSERTCRASH(pTrig, ("Attempting to remove a polygon not in the list."));
|
||||
if (pTrig) {
|
||||
if (pPrev) {
|
||||
DEBUG_ASSERTCRASH(pTrigger==pPrev->m_nextPolygonTrigger, ("Logic errror. jba."));
|
||||
pPrev->m_nextPolygonTrigger = pTrig->m_nextPolygonTrigger;
|
||||
} else {
|
||||
DEBUG_ASSERTCRASH(pTrigger==ThePolygonTriggerListPtr, ("Logic errror. jba."));
|
||||
ThePolygonTriggerListPtr = pTrig->m_nextPolygonTrigger;
|
||||
}
|
||||
}
|
||||
pTrigger->m_nextPolygonTrigger = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
PolygonTrigger::deleteTriggers Deletes list of triggers.
|
||||
*/
|
||||
void PolygonTrigger::deleteTriggers(void)
|
||||
{
|
||||
PolygonTrigger *pList = ThePolygonTriggerListPtr;
|
||||
ThePolygonTriggerListPtr = NULL;
|
||||
s_currentID = 1;
|
||||
pList->deleteInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
PolygonTrigger::addPoint adds a point at the end of the polygon.
|
||||
NOTE: It is expected that this will only get called in the editor, as in the game
|
||||
the poly triggers don't change.
|
||||
*/
|
||||
void PolygonTrigger::addPoint(const ICoord3D &point)
|
||||
{
|
||||
DEBUG_ASSERTCRASH(m_numPoints <= m_sizePoints, ("Invalid m_numPoints."));
|
||||
if (m_numPoints == m_sizePoints) {
|
||||
reallocate();
|
||||
}
|
||||
m_points[m_numPoints] = point;
|
||||
m_numPoints++;
|
||||
m_boundsNeedsUpdate = true;
|
||||
}
|
||||
|
||||
/**
|
||||
PolygonTrigger::setPoint sets the point at index ndx.
|
||||
NOTE: It is expected that this will only get called in the editor, as in the game
|
||||
the poly triggers don't change.
|
||||
*/
|
||||
void PolygonTrigger::setPoint(const ICoord3D &point, Int ndx)
|
||||
{
|
||||
DEBUG_ASSERTCRASH(ndx>=0 && ndx <= m_numPoints, ("Invalid ndx."));
|
||||
if (ndx<0) return;
|
||||
if (ndx == m_numPoints) { // we are setting first available unused point
|
||||
addPoint(point);
|
||||
return;
|
||||
}
|
||||
if (ndx>m_numPoints) { // Can't skip points.
|
||||
return;
|
||||
}
|
||||
m_points[ndx] = point;
|
||||
m_boundsNeedsUpdate = true;
|
||||
}
|
||||
|
||||
/**
|
||||
PolygonTrigger::insertPoint .
|
||||
NOTE: It is expected that this will only get called in the editor, as in the game
|
||||
the poly triggers don't change.
|
||||
*/
|
||||
void PolygonTrigger::insertPoint(const ICoord3D &point, Int ndx)
|
||||
{
|
||||
DEBUG_ASSERTCRASH(ndx>=0 && ndx <= m_numPoints, ("Invalid ndx."));
|
||||
if (ndx<0) return;
|
||||
if (ndx == m_numPoints) { // we are setting first available unused point
|
||||
addPoint(point);
|
||||
return;
|
||||
}
|
||||
if (m_numPoints == m_sizePoints) {
|
||||
reallocate();
|
||||
}
|
||||
Int i;
|
||||
for (i=m_numPoints; i>ndx; i--) {
|
||||
m_points[i] = m_points[i-1];
|
||||
}
|
||||
m_points[ndx] = point;
|
||||
m_numPoints++;
|
||||
m_boundsNeedsUpdate = true;
|
||||
}
|
||||
|
||||
/**
|
||||
PolygonTrigger::deletePoint .
|
||||
NOTE: It is expected that this will only get called in the editor, as in the game
|
||||
the poly triggers don't change.
|
||||
*/
|
||||
void PolygonTrigger::deletePoint(Int ndx)
|
||||
{
|
||||
DEBUG_ASSERTCRASH(ndx>=0 && ndx < m_numPoints, ("Invalid ndx."));
|
||||
if (ndx<0 || ndx>=m_numPoints) return;
|
||||
Int i;
|
||||
for (i=ndx; i<m_numPoints-1; i++) {
|
||||
m_points[i] = m_points[i+1];
|
||||
}
|
||||
m_numPoints--;
|
||||
m_boundsNeedsUpdate = true;
|
||||
}
|
||||
|
||||
void PolygonTrigger::getCenterPoint(Coord3D* pOutCoord) const
|
||||
{
|
||||
DEBUG_ASSERTCRASH(pOutCoord != NULL, ("pOutCoord was null. Non-Fatal, but shouldn't happen."));
|
||||
if (!pOutCoord) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_boundsNeedsUpdate) {
|
||||
updateBounds();
|
||||
}
|
||||
(*pOutCoord).x = (m_bounds.lo.x + m_bounds.hi.x) / 2.0f;
|
||||
(*pOutCoord).y = (m_bounds.lo.y + m_bounds.hi.y) / 2.0f;
|
||||
|
||||
(*pOutCoord).z = TheTerrainLogic->getGroundHeight(pOutCoord->x, pOutCoord->y);
|
||||
}
|
||||
|
||||
Real PolygonTrigger::getRadius(void) const
|
||||
{
|
||||
if (m_boundsNeedsUpdate) {
|
||||
updateBounds();
|
||||
}
|
||||
return m_radius;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
PolygonTrigger - pointInTrigger.
|
||||
*/
|
||||
Bool PolygonTrigger::pointInTrigger(ICoord3D &point) const
|
||||
{
|
||||
if (m_boundsNeedsUpdate) {
|
||||
updateBounds();
|
||||
}
|
||||
if (point.x < m_bounds.lo.x) return false;
|
||||
if (point.y < m_bounds.lo.y) return false;
|
||||
if (point.x > m_bounds.hi.x) return false;
|
||||
if (point.y > m_bounds.hi.y) return false;
|
||||
|
||||
Bool inside = false;
|
||||
Int i;
|
||||
for (i=0; i<m_numPoints; i++) {
|
||||
ICoord3D pt1 = m_points[i];
|
||||
ICoord3D pt2;
|
||||
if (i==m_numPoints-1) {
|
||||
pt2 = m_points[0];
|
||||
} else {
|
||||
pt2 = m_points[i+1];
|
||||
}
|
||||
if (pt1.y == pt2.y) {
|
||||
continue; // ignore horizontal lines.
|
||||
}
|
||||
if (pt1.y < point.y && pt2.y < point.y) continue;
|
||||
if (pt1.y >= point.y && pt2.y >= point.y) continue;
|
||||
if (pt1.x<point.x && pt2.x < point.x) continue;
|
||||
// Line segment crosses ray from point x->infinity.
|
||||
Int dy = pt2.y-pt1.y;
|
||||
Int dx = pt2.x-pt1.x;
|
||||
|
||||
Real intersectionX = pt1.x + (dx * (point.y-pt1.y)) / ((Real)dy);
|
||||
if (intersectionX >= point.x) {
|
||||
inside = !inside;
|
||||
}
|
||||
}
|
||||
return inside;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
const WaterHandle* PolygonTrigger::getWaterHandle(void) const
|
||||
{
|
||||
|
||||
if( isWaterArea() )
|
||||
return &m_waterHandle;
|
||||
|
||||
return NULL; // this polygon trigger is not a water area
|
||||
|
||||
}
|
||||
|
||||
Bool PolygonTrigger::isValid(void) const
|
||||
{
|
||||
if (m_numPoints == 0) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
/** CRC */
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void PolygonTrigger::crc( Xfer *xfer )
|
||||
{
|
||||
|
||||
} // end crc
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
/** Xfer method
|
||||
* Version Info:
|
||||
* 1: Initial version */
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void PolygonTrigger::xfer( Xfer *xfer )
|
||||
{
|
||||
|
||||
// version
|
||||
XferVersion currentVersion = 1;
|
||||
XferVersion version = currentVersion;
|
||||
xfer->xferVersion( &version, currentVersion );
|
||||
|
||||
// number of data points
|
||||
xfer->xferInt( &m_numPoints );
|
||||
|
||||
// xfer all data points
|
||||
ICoord3D *point;
|
||||
for( Int i = 0; i < m_numPoints; ++i )
|
||||
{
|
||||
|
||||
// get this point
|
||||
point = &m_points[ i ];
|
||||
|
||||
// xfer point
|
||||
xfer->xferICoord3D( point );
|
||||
|
||||
} // end for, i
|
||||
|
||||
// bounds
|
||||
xfer->xferIRegion2D( &m_bounds );
|
||||
|
||||
// radius
|
||||
xfer->xferReal( &m_radius );
|
||||
|
||||
// bounds need update
|
||||
xfer->xferBool( &m_boundsNeedsUpdate );
|
||||
|
||||
} // end xfer
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
/** Load post process */
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void PolygonTrigger::loadPostProcess( void )
|
||||
{
|
||||
|
||||
} // end loadPostProcess
|
1158
Generals/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp
Normal file
1158
Generals/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp
Normal file
File diff suppressed because it is too large
Load diff
2970
Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp
Normal file
2970
Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp
Normal file
File diff suppressed because it is too large
Load diff
Reference in a new issue