/* ** 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 . */ //////////////////////////////////////////////////////////////////////////////// // // // (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; igetNext() ) 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; iaddPoint(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; igetNumPoints(); 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_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; igetGroundHeight(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= point.y && pt2.y >= point.y) continue; if (pt1.xinfinity. 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