From 407f34493769c08ba5497349f7cb68a5d8ec8ced Mon Sep 17 00:00:00 2001 From: andreasw Date: Mon, 27 Apr 2026 20:32:07 +0200 Subject: [PATCH 1/5] directional delivery, basic functionality --- .../Include/GameLogic/Module/OCLUpdate.h | 3 + .../Include/GameLogic/TerrainLogic.h | 4 + .../Source/GameLogic/Map/TerrainLogic.cpp | 110 ++++++++++++++++++ .../GameLogic/Object/Update/OCLUpdate.cpp | 14 ++- 4 files changed, 129 insertions(+), 2 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OCLUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OCLUpdate.h index 3e7e0546a09..f9aa8c1e36b 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OCLUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OCLUpdate.h @@ -54,6 +54,9 @@ class OCLUpdateModuleData : public UpdateModuleData Bool m_isCreateAtEdge; ///< Otherwise, it is created on top of myself Bool m_isFactionTriggered; ///< Faction has to be present before update will happen + Bool m_isDirectionalDelivery; ///< Deliver payload aligned to source object + Bool m_isDirectionalDeliveryFurthestEdge; ///< always get the furthest edge matching angle + OCLUpdateModuleData(); static void buildFieldParse(MultiIniFieldParse& p); diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h index 2b734de82d0..5608e0682c6 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h @@ -234,6 +234,10 @@ class TerrainLogic : public Snapshot, virtual void getMaximumPathfindExtent( Region3D *extent ) const { DEBUG_CRASH(("not implemented")); } ///< @todo This should not be a stub - this should own this functionality virtual Coord3D findClosestEdgePoint( const Coord3D *closestTo ) const ; virtual Coord3D findFarthestEdgePoint( const Coord3D *farthestFrom ) const ; + + virtual Coord3D findEdgePointForAngle(const Coord3D* pos, Real angle, bool farthest = FALSE, bool closest = FALSE) const; + + virtual Bool isClearLineOfSight(const Coord3D& pos, const Coord3D& posOther) const; virtual AsciiString getSourceFilename( void ) { return m_filenameString; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp index 4e3fc7054b3..26d697481f5 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp @@ -2169,7 +2169,117 @@ Coord3D TerrainLogic::findFarthestEdgePoint( const Coord3D *farthestFrom ) const } +//------------------------------------------------------------------------------------------------- +/** Returns the ground aligned point on the bounding box closest to the given point*/ +//------------------------------------------------------------------------------------------------- +Coord3D TerrainLogic::findEdgePointForAngle(const Coord3D* pos, Real angle, bool farthest/* = FALSE*/, bool closest/* = FALSE*/) const +{ + Region3D mapExtent; + getExtent(&mapExtent); + + // Map bounds (XY only) + const Real minX = mapExtent.lo.x; + const Real minY = mapExtent.lo.y; + const Real maxX = mapExtent.hi.x; + const Real maxY = mapExtent.hi.y; + + const Real x0 = pos->x; + const Real y0 = pos->y; + + // Direction from angle + const Real dx = std::cos(angle); + const Real dy = std::sin(angle); + + // Small epsilon for float comparisons + const Real eps = static_cast(1e-6); + + struct Hit + { + Real t; + Real x; + Real y; + }; + + Hit hits[4]; + int hitCount = 0; + + auto addHit = [&](Real t) + { + if (t < 0) return; // only forward along the ray + + const Real x = x0 + t * dx; + const Real y = y0 + t * dy; + + // Keep only points that lie on the rectangle boundary + if (x >= minX - eps && x <= maxX + eps && + y >= minY - eps && y <= maxY + eps) + { + // Avoid duplicates (corner hits can be found twice) + for (int i = 0; i < hitCount; ++i) + { + if (std::abs(hits[i].x - x) < eps && + std::abs(hits[i].y - y) < eps) + { + return; + } + } + + hits[hitCount++] = { t, x, y }; + } + }; + + // Intersect ray P(t) = (x0,y0) + t(dx,dy), t >= 0 + // with the 4 rectangle edges + + // x = minX + if (std::abs(dx) > eps) + addHit((minX - x0) / dx); + + // x = maxX + if (std::abs(dx) > eps) + addHit((maxX - x0) / dx); + + // y = minY + if (std::abs(dy) > eps) + addHit((minY - y0) / dy); + + // y = maxY + if (std::abs(dy) > eps) + addHit((maxY - y0) / dy); + + // No hit should only happen if pos is invalid or outside map + if (hitCount == 0) + return *pos; + + // Default behavior: + // - if closest == true -> nearest hit + // - if farthest == true -> farthest hit + // - otherwise -> nearest forward hit + int best = 0; + if (farthest) + { + for (int i = 1; i < hitCount; ++i) + { + if (hits[i].t > hits[best].t) + best = i; + } + } + else // closest or default + { + for (int i = 1; i < hitCount; ++i) + { + if (hits[i].t < hits[best].t) + best = i; + } + } + + Coord3D result = *pos; + result.x = hits[best].x; + result.y = hits[best].y; + // preserve original Z (XY only matters) + return result; +} //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/OCLUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/OCLUpdate.cpp index 52fcf359865..e645fd70266 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/OCLUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/OCLUpdate.cpp @@ -97,6 +97,8 @@ OCLUpdateModuleData::OCLUpdateModuleData() { "MaxDelay", INI::parseDurationUnsignedInt, nullptr, offsetof( OCLUpdateModuleData, m_maxDelay ) }, { "CreateAtEdge", INI::parseBool, nullptr, offsetof( OCLUpdateModuleData, m_isCreateAtEdge ) }, { "FactionTriggered", INI::parseBool, nullptr, offsetof( OCLUpdateModuleData, m_isFactionTriggered ) }, + { "DirectionalDelivery", INI::parseBool, nullptr, offsetof( OCLUpdateModuleData, m_isDirectionalDelivery) }, + { "DirectionalDeliveryFurthestEdge", INI::parseBool, nullptr, offsetof( OCLUpdateModuleData, m_isDirectionalDeliveryFurthestEdge) }, { nullptr, nullptr, nullptr, 0 } }; p.add(dataFieldParse); @@ -190,10 +192,18 @@ UpdateSleepTime OCLUpdate::update( void ) setNextCreationFrame(); Coord3D creationCoord; - if( getOCLUpdateModuleData()->m_isCreateAtEdge ) - creationCoord = TheTerrainLogic->findClosestEdgePoint( getObject()->getPosition() ); + if (data->m_isCreateAtEdge) { + if (data->m_isDirectionalDelivery) { + creationCoord = TheTerrainLogic->findEdgePointForAngle(getObject()->getPosition(), getObject()->getOrientation(), data->m_isDirectionalDeliveryFurthestEdge, FALSE); + } + else { + creationCoord = TheTerrainLogic->findClosestEdgePoint(getObject()->getPosition()); + } + } else + { creationCoord = *getObject()->getPosition(); + } // If this is faction triggered, search through the faction specific OCLs to find the match if (data->m_isFactionTriggered) From ee2b818ef8b5b387d67323883451a21f47624dd4 Mon Sep 17 00:00:00 2001 From: andreasw Date: Tue, 28 Apr 2026 17:31:59 +0200 Subject: [PATCH 2/5] Multiple weaponslots on deliverPayload --- .../GameLogic/Module/DeliverPayloadAIUpdate.h | 5 ++++ .../AIUpdate/DeliverPayloadAIUpdate.cpp | 28 ++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h index f028aabe93a..fba72e098cd 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h @@ -254,6 +254,9 @@ class DeliverPayloadData Coord3D m_dropVariance; UnsignedInt m_dropDelay; Bool m_fireWeapon; + + UnsignedInt m_fireWeaponSlots; + Bool m_selfDestructObject; Int m_visibleNumBones; ///< The number of visible bones to process. Real m_diveStartDistance; @@ -278,6 +281,7 @@ class DeliverPayloadData m_dropVariance.zero(); m_dropDelay = 0; m_fireWeapon = false; + m_fireWeaponSlots = 1u << PRIMARY_WEAPON; m_visibleNumBones = 0; m_diveStartDistance = 0.0f; m_diveEndDistance = 0.0f; @@ -326,6 +330,7 @@ class DeliverPayloadAIUpdate : public AIUpdateInterface const Coord3D& getDropOffset() const { return m_data.m_dropOffset; } const Coord3D& getDropVariance() const { return m_data.m_dropVariance; } Bool isFireWeapon() const { return m_data.m_fireWeapon; } + Bool shouldFireWeaponSlot(WeaponSlotType wslot) const { return (m_data.m_fireWeaponSlots & (1 << wslot)) != 0; } Int getVisibleItemsDelivered() const { return m_visibleItemsDelivered; } void setVisibleItemsDelivered( Int num ) { m_visibleItemsDelivered = num; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp index eee98cc06cc..130f3a7c0f6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp @@ -52,6 +52,20 @@ //------------------------------------------------------------------------------------------------- +// duplicate from TurretAI +static void parseTWS(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) +{ + UnsignedInt* tws = (UnsignedInt*)store; + const char* token = ini->getNextToken(); + while (token != nullptr) + { + WeaponSlotType wslot = (WeaponSlotType)INI::scanIndexList(token, TheWeaponSlotTypeNames); + *tws |= (1 << wslot); + token = ini->getNextTokenOrNull(); + } +} +// --- + const FieldParse* DeliverPayloadData::getFieldParse() { static const FieldParse dataFieldParse[] = @@ -80,6 +94,7 @@ const FieldParse* DeliverPayloadData::getFieldParse() //Weapon based payload { "FireWeapon", INI::parseBool, nullptr, offsetof( DeliverPayloadData, m_fireWeapon ) }, + { "FireWeaponSlots", parseTWS, nullptr, offsetof(DeliverPayloadData, m_fireWeaponSlots) }, //Specify an additional weaponslot to be fired while strafing { "DiveStartDistance", INI::parseReal, nullptr, offsetof( DeliverPayloadData, m_diveStartDistance ) }, @@ -434,6 +449,7 @@ void DeliverPayloadAIUpdate::xfer( Xfer *xfer ) xfer->xferCoord3D(&data.m_dropVariance); xfer->xferUnsignedInt(&data.m_dropDelay); xfer->xferBool(&data.m_fireWeapon); + xfer->xferUnsignedInt(&data.m_fireWeaponSlots); xfer->xferBool(&data.m_selfDestructObject); xfer->xferInt(&data.m_visibleNumBones); xfer->xferReal(&data.m_diveStartDistance); @@ -715,7 +731,17 @@ StateReturnType DeliveringState::update() // Kick a dude out every so often pos.x += ai->getDropOffset().x; pos.y += ai->getDropOffset().y; pos.z += ai->getDropOffset().z; - owner->fireCurrentWeapon( &pos ); + + for (int i = 0; i < WEAPONSLOT_COUNT; i++) { + WeaponSlotType wslot = (WeaponSlotType)i; + if (ai->shouldFireWeaponSlot(wslot)) { + owner->setWeaponLock(wslot, LOCKED_TEMPORARILY); + owner->fireCurrentWeapon(&pos); + } + } + //owner->fireCurrentWeapon( &pos ); + + TheGameLogic->destroyObject( item ); } else From 10d775cb5dae5ecc0e35fd38008a910739ef85be Mon Sep 17 00:00:00 2001 From: Andi Date: Sun, 3 May 2026 20:28:54 +0200 Subject: [PATCH 3/5] carpet bomb experiments --- .../GameLogic/Module/DeliverPayloadAIUpdate.h | 2 +- .../AIUpdate/DeliverPayloadAIUpdate.cpp | 38 +++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h index fba72e098cd..8b6dc6c570a 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h @@ -334,7 +334,7 @@ class DeliverPayloadAIUpdate : public AIUpdateInterface Int getVisibleItemsDelivered() const { return m_visibleItemsDelivered; } void setVisibleItemsDelivered( Int num ) { m_visibleItemsDelivered = num; } - Bool isCloseEnoughToTarget(); + Bool isCloseEnoughToTarget(Bool *isInBound = nullptr); Bool isOffMap() const; Real calcMinTurnRadius(Real* timeToTravelThatDist) const; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp index 130f3a7c0f6..25a473320de 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp @@ -369,7 +369,7 @@ Real DeliverPayloadAIUpdate::calcMinTurnRadius(Real* timeToTravelThatDist) const //------------------------------------------------------------------------------------------------- -Bool DeliverPayloadAIUpdate::isCloseEnoughToTarget() +Bool DeliverPayloadAIUpdate::isCloseEnoughToTarget(Bool* isInBound) { // In addition to testing distance, it is also sensitive to being in/outward bound ////The new getPreOpenDistance() allows the deliver state to fire early, but only if inbound, @@ -380,6 +380,9 @@ Bool DeliverPayloadAIUpdate::isCloseEnoughToTarget() Bool inBound = m_previousDistanceSqr > currentDistanceSqr; m_previousDistanceSqr = currentDistanceSqr;// for the next test + if (isInBound) + *isInBound = inBound; + if ( inBound ) allowedDistanceSqr = sqr(getAllowedDistanceToTarget() + getPreOpenDistance()); @@ -709,7 +712,8 @@ StateReturnType DeliveringState::update() // Kick a dude out every so often m_didOpen = true; m_dropDelayLeft = ai->getDropDelay(); - if (!ai->isCloseEnoughToTarget()) + Bool inBound; + if (!ai->isCloseEnoughToTarget(&inBound)) return STATE_FAILURE; const ContainedItemsList* items = owner->getContain() ? owner->getContain()->getContainedItemsList() : nullptr; @@ -720,7 +724,35 @@ StateReturnType DeliveringState::update() // Kick a dude out every so often return STATE_SUCCESS; } - //Handle contained payload delivery. + // TODO: solve this differently! + + + // Check if goal target needs to be updated: + //if (!inBound) + { + Real currentDistanceSqr = ThePartitionManager->getDistanceSquared(owner, ai->getGoalPosition(), FROM_CENTER_2D); + DEBUG_LOG(("DELIVER_PAYLOAD -> currentDistance = %f", sqrt(currentDistanceSqr))); + if (currentDistanceSqr < (50.0f * 50.0f)) { + + const Coord3D* dir = owner->getUnitDirectionVector2D(); + + Real targetOffsetDist = 70.0f; + + Coord3D newTarget; + newTarget.x = ai->getGoalPosition()->x + dir->x * targetOffsetDist; + newTarget.y = ai->getGoalPosition()->y + dir->y * targetOffsetDist; + newTarget.z = 0.0f; // yeah, yeah, should be terrainpos, but doesn't really matter here... + + ai->aiMoveToPosition(&newTarget, CMD_FROM_AI); + + DEBUG_LOG(("DELIVER_PAYLOAD -> add extra target distance")); + } + + + } + + + // Handle contained payload delivery. if( items ) { Object* item = items->front(); From aef9646de84912e8312cf947fda7cd81facf0710 Mon Sep 17 00:00:00 2001 From: andreasw Date: Thu, 7 May 2026 21:47:09 +0200 Subject: [PATCH 4/5] target offset --- .../GameLogic/Module/DeliverPayloadAIUpdate.h | 2 +- .../GameLogic/Object/ObjectCreationList.cpp | 42 ++++++++++++++----- .../AIUpdate/DeliverPayloadAIUpdate.cpp | 17 ++++++-- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h index 8b6dc6c570a..015ee383e4b 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h @@ -338,7 +338,7 @@ class DeliverPayloadAIUpdate : public AIUpdateInterface Bool isOffMap() const; Real calcMinTurnRadius(Real* timeToTravelThatDist) const; - void deliverPayload( const Coord3D *moveToPos, const Coord3D *targetPos, const DeliverPayloadData *data ); + void deliverPayload( const Coord3D *moveToPos, const Coord3D *targetPos, const DeliverPayloadData *data, const Coord3D *decalOffset = nullptr ); void deliverPayloadViaModuleData( const Coord3D *moveToPos ); const DeliverPayloadData* getData() { return &m_data; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp index 311742793e3..fa6fbd12cef 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp @@ -259,6 +259,7 @@ class DeliverPayloadNugget : public ObjectCreationNugget m_payload.clear(); m_putInContainerName.clear(); m_transportName.clear(); + m_targetOffset = { 0.0, 0.0 }; } virtual Object* create(const Object *primaryObj, const Coord3D *primary, const Coord3D *secondary, Real angle, UnsignedInt lifetimeFrames = 0 ) const @@ -283,7 +284,9 @@ class DeliverPayloadNugget : public ObjectCreationNugget //resultant vectors to the initial vectors, we can calculate the delta positions for each plane. Real CCWx = 0.0f, CCWy = 0.0f, CWx = 0.0f, CWy = 0.0f; - if( m_formationSize > 1 ) + Real targetOffsetX = 0.0f, targetOffsetY = 0.0f; + + if (m_formationSize > 1 || m_targetOffset.x != 0.0f || m_targetOffset.y != 0.0f) { //Get the delta x and y values from the target to the origin. Real dx = primary->x - secondary->x; @@ -296,16 +299,19 @@ class DeliverPayloadNugget : public ObjectCreationNugget dx /= length; dy /= length; + targetOffsetX = dx * m_targetOffset.x - dy * m_targetOffset.y; + targetOffsetY = dy * m_targetOffset.x + dx * m_targetOffset.y; + //Rotate 90 degrees CCW. Real radians = 90.0f * PI / 180.0f; - Real s = Sin( radians ); - Real c = Cos( radians ); + Real s = Sin(radians); + Real c = Cos(radians); CCWx = dx * c + dy * -s + dx; CCWy = dx * s + dy * c + dy; //Rotate 90 degrees CW - s = Sin( -radians ); - c = Cos( -radians ); + s = Sin(-radians); + c = Cos(-radians); CWx = dx * c + dy * -s + dx; CWy = dx * s + dy * c + dy; } @@ -331,6 +337,9 @@ class DeliverPayloadNugget : public ObjectCreationNugget offset.y = CWy * offsetMultiplier; } + offset.x += targetOffsetX; + offset.y += targetOffsetY; + Coord3D startPos = *primary; Coord3D moveToPos = *secondary; startPos.add( &offset ); @@ -407,9 +416,9 @@ class DeliverPayloadNugget : public ObjectCreationNugget static NameKeyType key_DeliverPayloadAIUpdate = NAMEKEY("DeliverPayloadAIUpdate"); DeliverPayloadAIUpdate *ai = (DeliverPayloadAIUpdate*)transport->findUpdateModule(key_DeliverPayloadAIUpdate); - if( ai ) + if (ai) { - if( m_startAtMaxSpeed && createOwner ) + if (m_startAtMaxSpeed && createOwner) { PhysicsBehavior* physics = transport->getPhysics(); if (physics) @@ -420,15 +429,24 @@ class DeliverPayloadNugget : public ObjectCreationNugget startingForce.x *= factor; startingForce.y *= factor; startingForce.z *= factor; - physics->applyMotiveForce( &startingForce ); + physics->applyMotiveForce(&startingForce); } } // only the first guy in each formation gets a delivery decal DeliverPayloadData data = m_data; - if (formationIndex != 0) + if (formationIndex != 0) { data.m_deliveryDecalRadius = 0; - ai->deliverPayload( &moveToPos, &targetPos, &data ); + } + + if (targetOffsetX != 0.0 || targetOffsetY != 0.0) { + Coord3D decalOffset = { -targetOffsetX, -targetOffsetY }; + ai->deliverPayload(&moveToPos, &targetPos, &data, &decalOffset); + } + else { + ai->deliverPayload(&moveToPos, &targetPos, &data); + } + if( m_startAtPreferredHeight && createOwner ) { startPos.z = TheTerrainLogic->getGroundHeight(startPos.x, startPos.y) + ai->getCurLocomotor()->getPreferredHeight(); @@ -538,6 +556,8 @@ class DeliverPayloadNugget : public ObjectCreationNugget { "WeaponErrorRadius", INI::parseReal, nullptr, offsetof( DeliverPayloadNugget, m_errorRadius ) }, { "DelayDeliveryMax", INI::parseDurationUnsignedInt, nullptr, offsetof( DeliverPayloadNugget, m_delayDeliveryFramesMax ) }, + { "TargetOffset", INI::parseCoord2D, nullptr, offsetof( DeliverPayloadNugget, m_targetOffset ) }, + //Payload information (it's all created now and stored inside) { "Payload", parsePayload, nullptr, 0 }, { "PutInContainer", INI::parseAsciiString, nullptr, offsetof( DeliverPayloadNugget, m_putInContainerName) }, @@ -579,6 +599,8 @@ class DeliverPayloadNugget : public ObjectCreationNugget Bool m_startAtPreferredHeight; Bool m_startAtMaxSpeed; + Coord2D m_targetOffset; + //AI specific data passed over to DeliverPayloadAIUpdate::deliver() DeliverPayloadData m_data; }; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp index 25a473320de..5cde5979162 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp @@ -254,7 +254,8 @@ UpdateSleepTime DeliverPayloadAIUpdate::update( void ) void DeliverPayloadAIUpdate::deliverPayload( const Coord3D *moveToPos, const Coord3D *targetPos, - const DeliverPayloadData *data + const DeliverPayloadData *data, + const Coord3D *decalOffset /* = NULL*/ ) { @@ -270,8 +271,18 @@ void DeliverPayloadAIUpdate::deliverPayload( m_data = *data; m_deliveryDecal.clear(); - m_data.m_deliveryDecalTemplate.createRadiusDecal(*targetPos, - m_data.m_deliveryDecalRadius, getObject()->getControllingPlayer(), m_deliveryDecal); + + if (decalOffset != nullptr) { + Coord3D decalPos = *targetPos; + decalPos.add(decalOffset); + m_data.m_deliveryDecalTemplate.createRadiusDecal(decalPos, + m_data.m_deliveryDecalRadius, getObject()->getControllingPlayer(), m_deliveryDecal); + } + else { + m_data.m_deliveryDecalTemplate.createRadiusDecal(*targetPos, + m_data.m_deliveryDecalRadius, getObject()->getControllingPlayer(), m_deliveryDecal); + } + if( m_data.m_diveStartDistance <= 0.0f ) { From 11dca1fe8d9bfee6cb4e79600a5a2fa65680aa30 Mon Sep 17 00:00:00 2001 From: Andi Date: Sat, 9 May 2026 08:33:09 +0200 Subject: [PATCH 5/5] clean up --- .../GameLogic/Module/DeliverPayloadAIUpdate.h | 2 +- .../AIUpdate/DeliverPayloadAIUpdate.cpp | 38 ++----------------- 2 files changed, 4 insertions(+), 36 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h index 015ee383e4b..4dc5ad10018 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h @@ -334,7 +334,7 @@ class DeliverPayloadAIUpdate : public AIUpdateInterface Int getVisibleItemsDelivered() const { return m_visibleItemsDelivered; } void setVisibleItemsDelivered( Int num ) { m_visibleItemsDelivered = num; } - Bool isCloseEnoughToTarget(Bool *isInBound = nullptr); + Bool isCloseEnoughToTarget(); Bool isOffMap() const; Real calcMinTurnRadius(Real* timeToTravelThatDist) const; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp index 5cde5979162..648af6297d7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp @@ -380,7 +380,7 @@ Real DeliverPayloadAIUpdate::calcMinTurnRadius(Real* timeToTravelThatDist) const //------------------------------------------------------------------------------------------------- -Bool DeliverPayloadAIUpdate::isCloseEnoughToTarget(Bool* isInBound) +Bool DeliverPayloadAIUpdate::isCloseEnoughToTarget() { // In addition to testing distance, it is also sensitive to being in/outward bound ////The new getPreOpenDistance() allows the deliver state to fire early, but only if inbound, @@ -391,9 +391,6 @@ Bool DeliverPayloadAIUpdate::isCloseEnoughToTarget(Bool* isInBound) Bool inBound = m_previousDistanceSqr > currentDistanceSqr; m_previousDistanceSqr = currentDistanceSqr;// for the next test - if (isInBound) - *isInBound = inBound; - if ( inBound ) allowedDistanceSqr = sqr(getAllowedDistanceToTarget() + getPreOpenDistance()); @@ -723,8 +720,7 @@ StateReturnType DeliveringState::update() // Kick a dude out every so often m_didOpen = true; m_dropDelayLeft = ai->getDropDelay(); - Bool inBound; - if (!ai->isCloseEnoughToTarget(&inBound)) + if (!ai->isCloseEnoughToTarget()) return STATE_FAILURE; const ContainedItemsList* items = owner->getContain() ? owner->getContain()->getContainedItemsList() : nullptr; @@ -735,35 +731,7 @@ StateReturnType DeliveringState::update() // Kick a dude out every so often return STATE_SUCCESS; } - // TODO: solve this differently! - - - // Check if goal target needs to be updated: - //if (!inBound) - { - Real currentDistanceSqr = ThePartitionManager->getDistanceSquared(owner, ai->getGoalPosition(), FROM_CENTER_2D); - DEBUG_LOG(("DELIVER_PAYLOAD -> currentDistance = %f", sqrt(currentDistanceSqr))); - if (currentDistanceSqr < (50.0f * 50.0f)) { - - const Coord3D* dir = owner->getUnitDirectionVector2D(); - - Real targetOffsetDist = 70.0f; - - Coord3D newTarget; - newTarget.x = ai->getGoalPosition()->x + dir->x * targetOffsetDist; - newTarget.y = ai->getGoalPosition()->y + dir->y * targetOffsetDist; - newTarget.z = 0.0f; // yeah, yeah, should be terrainpos, but doesn't really matter here... - - ai->aiMoveToPosition(&newTarget, CMD_FROM_AI); - - DEBUG_LOG(("DELIVER_PAYLOAD -> add extra target distance")); - } - - - } - - - // Handle contained payload delivery. + //Handle contained payload delivery. if( items ) { Object* item = items->front();