diff --git a/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h b/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h index ed4423f1648..ff139ac3d2e 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h @@ -193,6 +193,8 @@ enum KindOfType CPP_11(: Int) KINDOF_TELEPORTER, KINDOF_SHIPYARD, KINDOF_NO_MOVE_EFFECTS_ON_WATER, + KINDOF_SHIP, + KINDOF_SUBMARINE, KINDOF_EXTRA1, KINDOF_EXTRA2, diff --git a/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h b/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h index 1e667b3a402..8758b164f95 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h @@ -285,6 +285,9 @@ enum ModelConditionFlagType CPP_11(: Int) MODELCONDITION_CARRIER_DOOR_OPENING, MODELCONDITION_CARRIER_DOOR_CLOSING, + // Ship death + MODELCONDITION_SHIP_TOPPLING, + MODELCONDITION_SHIP_SINKING, // // Note: these values are saved in save files, so you MUST NOT REMOVE OR CHANGE // existing values! diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h index 2a967b7efa0..5ebdf91593f 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h @@ -102,6 +102,7 @@ enum DamageType CPP_11(: Int) // Specific damage types with special logic attached DAMAGE_TORPEDO, ///< can only attack units over water + DAMAGE_ANTI_TORPEDO, /// destroy torpedoes DAMAGE_CHRONO_GUN, ///< Disable target and remove them once health threshold is reached DAMAGE_CHRONO_UNRESISTABLE, ///< Used for recovery from CHRONO_GUN // DAMAGE_ZOMBIE_VIRUS, // TODO diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ContainModule.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ContainModule.h index 005c446d65c..2a065d44a25 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ContainModule.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ContainModule.h @@ -211,5 +211,7 @@ class ContainModuleInterface virtual short getPortableSlot(ObjectID portableID) const = 0; // get the slot occupied by the object. virtual const ContainedItemsList* getAddOnList() const = 0; virtual ContainedItemsList* getAddOnList() = 0; + + virtual Coord3D getEnterPositionOffset(ObjectID object) const = 0; }; //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DroneCarrierContain.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DroneCarrierContain.h index 39745177d0d..84f4869de0a 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DroneCarrierContain.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DroneCarrierContain.h @@ -21,11 +21,13 @@ class DroneCarrierContainModuleData: public TransportContainModuleData public: Real m_launchVelocityBoost; DeathType m_deathTypeToContained; + std::vector m_enterPositionOffsets; + Bool m_keepSlotAssignment; DroneCarrierContainModuleData(); static void buildFieldParse(MultiIniFieldParse& p); - + static void parseEnterPositionOffset(INI* ini, void* instance, void* /*store*/, const void* /*userData*/); }; //------------------------------------------------------------------------------------------------- @@ -56,15 +58,20 @@ class DroneCarrierContain: public TransportContain virtual const ContainedItemsList* getAddOnList() const override; virtual ContainedItemsList* getAddOnList() override; + virtual Coord3D getEnterPositionOffset(ObjectID object) const override; + virtual void onDie(const DamageInfo* damageInfo) override; // Called from the AI update to reload the contained drones void updateContainedReloadingStatus(); + // Called from Carrier AI if a drone dies + void onDroneDeath(ObjectID deadDrone); + protected: // Saves slot assignement and frame when entered - std::vector> m_contained_units; + std::vector> m_contained_units; }; #endif // __TransportContain_H_ diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MultiAddOnContain.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MultiAddOnContain.h index 20b18feb0a0..816d1d32153 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MultiAddOnContain.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MultiAddOnContain.h @@ -134,6 +134,8 @@ class MultiAddOnContain : public TransportContain virtual const ContainedItemsList* getAddOnList() const { return &m_addOnList; } virtual ContainedItemsList* getAddOnList() { return &m_addOnList; } + virtual Coord3D getEnterPositionOffset(ObjectID object) const override { return Coord3D(0,0,0); }; + private: void parseAddOnEntry(INI* ini, void* instance, void* store, const void* /*userData*/); diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OpenContain.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OpenContain.h index 9bfde6631fa..0f58abbbd71 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OpenContain.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OpenContain.h @@ -257,6 +257,8 @@ class OpenContain : public UpdateModule, virtual const ContainedItemsList* getAddOnList() const { return NULL; } virtual ContainedItemsList* getAddOnList() { return NULL; } + virtual Coord3D getEnterPositionOffset(ObjectID object) const override { return Coord3D(0, 0, 0); }; + void pruneDeadWanters(); ContainedItemsList m_containList; ///< the list of contained objects diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OverlordContain.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OverlordContain.h index ed441173227..c1b5d9c606e 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OverlordContain.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OverlordContain.h @@ -117,6 +117,8 @@ class OverlordContain : public TransportContain virtual const ContainedItemsList* getAddOnList() const; virtual ContainedItemsList* getAddOnList(); + virtual Coord3D getEnterPositionOffset(ObjectID object) const override { return Coord3D(0, 0, 0); }; + private: /**< An empty overlord is a conatiner, but a full one redirects calls to its passengers. If this returns NULL, we are either empty or carrying a non container. diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h index f1b4e73eedc..04741615938 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h @@ -659,6 +659,9 @@ class Object : public Thing, public Snapshot // player. These are friend_s for player. void friend_adjustPowerForPlayer( Bool incoming ); + // Get position where to enter this object + Coord3D getEnterPosition(ObjectID enteringObject) const; + protected: void setOrRestoreTeam( Team* team, Bool restoring ); diff --git a/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp b/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp index a1f73a2e9fd..48079bac291 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp @@ -216,6 +216,9 @@ const char* const ModelConditionFlags::s_bitNameList[] = "CARRIER_DOOR_OPENING", "CARRIER_DOOR_CLOSING", + + "SHIP_TOPPLING", + "SHIP_SINKING", NULL }; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp index 9aae4ed4dab..506857e553f 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp @@ -182,6 +182,8 @@ const char* const KindOfMaskType::s_bitNameList[] = "TELEPORTER", "SHIPYARD", "NO_MOVE_EFFECTS_ON_WATER", + "SHIP", + "SUBMARINE", "EXTRA1", "EXTRA2", diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp index b783c775a40..f3b25cdc77c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp @@ -6470,7 +6470,7 @@ StateReturnType AIEnterState::onEnter() if( !TheActionManager->canEnterObject( obj, goal, obj->getAI()->getLastCommandSource(), CHECK_CAPACITY ) ) return STATE_FAILURE; - m_goalPosition = *goal->getPosition(); + m_goalPosition = goal->getEnterPosition(obj->getID()); ContainModuleInterface* contain = goal->getContain(); if (contain) @@ -6548,8 +6548,9 @@ StateReturnType AIEnterState::update() return STATE_FAILURE; } - m_goalPosition = *goal->getPosition(); + m_goalPosition = goal->getEnterPosition(obj->getID()); obj->getAI()->friend_setGoalObject(goal); + m_goalPosition = goal->getEnterPosition(obj->getID());// setGoalObject will override the goal position if (!TheActionManager->canEnterObject(obj, goal, obj->getAI()->getLastCommandSource(), CHECK_CAPACITY)) { /* diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/DroneCarrierContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/DroneCarrierContain.cpp index 29c3363b2a4..d912f0af94d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/DroneCarrierContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/DroneCarrierContain.cpp @@ -31,6 +31,8 @@ DroneCarrierContainModuleData::DroneCarrierContainModuleData() : TransportContai { m_launchVelocityBoost = 0.0f; m_deathTypeToContained = DEATH_DETONATED; + m_enterPositionOffsets.clear(); + m_keepSlotAssignment = false; } void DroneCarrierContainModuleData::buildFieldParse(MultiIniFieldParse& p) @@ -41,6 +43,8 @@ void DroneCarrierContainModuleData::buildFieldParse(MultiIniFieldParse& p) { { "ContainedUnitsDeathType", INI::parseIndexList, TheDeathNames, offsetof(DroneCarrierContainModuleData, m_deathTypeToContained) }, { "LaunchVelocityBoost", INI::parseReal, NULL, offsetof(DroneCarrierContainModuleData, m_launchVelocityBoost) }, + { "EnterPositionOffset", DroneCarrierContainModuleData::parseEnterPositionOffset, NULL, 0}, + { "KeepSlotAssignment", INI::parseBool, NULL, offsetof(DroneCarrierContainModuleData, m_keepSlotAssignment)}, { 0, 0, 0, 0 } }; p.add(dataFieldParse); @@ -51,11 +55,11 @@ void DroneCarrierContainModuleData::buildFieldParse(MultiIniFieldParse& p) DroneCarrierContain::DroneCarrierContain(Thing* thing, const ModuleData* moduleData) : TransportContain(thing, moduleData) { - const TransportContainModuleData* data = dynamic_cast(moduleData); + const DroneCarrierContainModuleData* data = dynamic_cast(moduleData); if (data != nullptr) { m_contained_units.reserve(data->m_slotCapacity); for (size_t i = 0; i < data->m_slotCapacity; i++) { - m_contained_units.emplace_back(INVALID_ID, 0); + m_contained_units.emplace_back(INVALID_ID, 0, false); } } } @@ -240,7 +244,13 @@ void DroneCarrierContain::onRemoving(Object* rider) // Remove unit from slot assign vector for (size_t i = 0; i < m_contained_units.size(); i++) { if (std::get<0>(m_contained_units[i]) == rider->getID()){ - m_contained_units[i] = { INVALID_ID, 0 }; + if (d->m_keepSlotAssignment) { + //keep id and flag as outside + m_contained_units[i] = { rider->getID(), 0, false }; + } + else { + m_contained_units[i] = { INVALID_ID, 0, false }; + } } } @@ -260,12 +270,25 @@ void DroneCarrierContain::onContaining(Object* obj, Bool wasSelected) if (draw != nullptr) { draw->enableAmbientSound(false); } + bool hasSlot{ false }; + const DroneCarrierContainModuleData* d = getDroneCarrierContainModuleData(); + if (d->m_keepSlotAssignment) { + for (size_t i = 0; i < m_contained_units.size() && !hasSlot; i++) { + if (std::get<0>(m_contained_units[i]) == obj->getID()) { + //Update enter time and inside flag + m_contained_units[i] = { obj->getID(), TheGameLogic->getFrame(), true }; + hasSlot = true; + } + } + } - // Assing in first free slot - for (size_t i = 0; i < m_contained_units.size(); i++) { - if (std::get<0>(m_contained_units[i]) == INVALID_ID) { - m_contained_units[i] = { obj->getID(), TheGameLogic->getFrame() }; - break; + if (!hasSlot) { + // Assing in first free slot + for (size_t i = 0; i < m_contained_units.size(); i++) { + if (std::get<0>(m_contained_units[i]) == INVALID_ID) { + m_contained_units[i] = { obj->getID(), TheGameLogic->getFrame(), true }; + break; + } } } } @@ -346,14 +369,40 @@ ContainedItemsList* DroneCarrierContain::getAddOnList() return &m_containList; } +Coord3D DroneCarrierContain::getEnterPositionOffset(ObjectID object) const +{ + const DroneCarrierContainModuleData* data = getDroneCarrierContainModuleData(); + if (!data->m_enterPositionOffsets.empty()) { + + short index = -1; + if (data->m_keepSlotAssignment) { + index = getRiderSlot(object); + } + + if (index < 0) { + //get next free slot + for (size_t i = 0; i < m_contained_units.size() && index < 0; i++) { + if (std::get<0>(m_contained_units[i]) == INVALID_ID) { + index = i; + } + } + } + + if (index > -1) { + return data->m_enterPositionOffsets.at(index % data->m_enterPositionOffsets.size()); + } + } + return Coord3D(0, 0, 0); +} + // Similar to jet ai when parking void DroneCarrierContain::updateContainedReloadingStatus() { UnsignedInt now = TheGameLogic->getFrame(); for (size_t i = 0; i < m_contained_units.size(); i++) { - const auto& [objectID, enteredFrame] = m_contained_units[i]; - if (objectID != INVALID_ID) { + const auto& [objectID, enteredFrame, inside] = m_contained_units[i]; + if (objectID != INVALID_ID && inside) { Object* drone = TheGameLogic->findObjectByID(objectID); if (drone != nullptr) { @@ -376,6 +425,31 @@ void DroneCarrierContain::updateContainedReloadingStatus() } } +void DroneCarrierContain::onDroneDeath(ObjectID deadDrone) +{ + const DroneCarrierContainModuleData* data = getDroneCarrierContainModuleData(); + if (!data->m_keepSlotAssignment) return; + + for (size_t i = 0; i < m_contained_units.size(); i++) { + if (std::get<0>(m_contained_units[i]) == deadDrone) { + m_contained_units[i] = { INVALID_ID, 0, false }; + return; + } + } + DEBUG_CRASH(("DroneCarrierContain: dead drone %d was not in contained units, this should not happen when keeping slot assignments!")); +} + +void DroneCarrierContainModuleData::parseEnterPositionOffset(INI* ini, void* instance, void*, const void*) +{ + // Accept multiple listings of Coord3D's. + DroneCarrierContainModuleData* self = (DroneCarrierContainModuleData*)instance; + + Coord3D offset(0,0,0); + INI::parseCoord3D(ini, NULL, &offset, NULL); + + self->m_enterPositionOffsets.push_back(offset); +} + // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ @@ -403,18 +477,18 @@ void DroneCarrierContain::xfer(Xfer* xfer) xfer->xferUnsignedShort(&listCount); // xfer vector data - std::tuple entry; + std::tuple entry; if (xfer->getXferMode() == XFER_SAVE || xfer->getXferMode() == XFER_CRC) { // save all tuples - std::vector< std::tuple >::const_iterator it; + std::vector< std::tuple >::const_iterator it; for (it = m_contained_units.begin(); it != m_contained_units.end(); ++it) { entry = *it; xfer->xferObjectID(&std::get<0>(entry)); xfer->xferUnsignedInt(&std::get<1>(entry)); - + xfer->xferBool(&std::get<2>(entry)); } // end for } // end if, save @@ -438,6 +512,7 @@ void DroneCarrierContain::xfer(Xfer* xfer) xfer->xferObjectID(&std::get<0>(entry)); xfer->xferUnsignedInt(&std::get<1>(entry)); + xfer->xferBool(&std::get<2>(entry)); m_contained_units.push_back(entry); } // end for, i diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 3cf6bfdb66a..03d6e50aa7b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -6732,3 +6732,21 @@ ObjectID Object::calculateCountermeasureToDivertTo( const Object& victim ) } return INVALID_ID; } + +Coord3D Object::getEnterPosition(ObjectID enteringObject) const { + Coord3D ret (*getPosition()); + + if (getContain() != nullptr) { + auto* cmi = getContain(); + Coord3D offset = cmi->getEnterPositionOffset(enteringObject); + if (offset.x != 0.0f || offset.y != 0.0f) { + //Rotate x/y position + Coord2D pos2d(offset.x, offset.y); + pos2d.rotateByAngle(getOrientation()); + ret.x += pos2d.x; + ret.y += pos2d.y; + } + ret.z += offset.z; + } + return ret; +} diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/CarrierDroneAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/CarrierDroneAIUpdate.cpp index 9d008272cfe..57bfe03d885 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/CarrierDroneAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/CarrierDroneAIUpdate.cpp @@ -150,6 +150,7 @@ UpdateSleepTime CarrierDroneAIUpdate::update() Bool isDocking = false; Real distanceToTargetSquared = 0; + bool isDead = getObject()->isEffectivelyDead(); Real dockingDistSquared = data->m_dockingDistance * data->m_dockingDistance; if (getStateMachine()->getCurrentStateID() == AI_ENTER && getGoalObject() != NULL) { distanceToTargetSquared = ThePartitionManager->getDistanceSquared(obj, getGoalObject(), FROM_CENTER_2D); @@ -161,7 +162,7 @@ UpdateSleepTime CarrierDroneAIUpdate::update() Locomotor* loco = getCurLocomotor(); - if (isDocking && !m_isDocking) { + if (isDocking && !m_isDocking && !isDead) { //DEBUG_LOG((">>> CarrierDroneAIUpdate::update() - ENTER LANDING STATE %d", obj->getID())); //loco->setMaxSpeed(DOCKING_SPEED); @@ -181,7 +182,7 @@ UpdateSleepTime CarrierDroneAIUpdate::update() m_isDocking = true; } - else if (!isDocking && m_isDocking) { + else if (!isDocking && m_isDocking && !isDead) { //DEBUG_LOG(("<<< CarrierDroneAIUpdate::update() - EXIT LANDING STATE %d", obj->getID())); obj->clearModelConditionState(MODELCONDITION_LANDING); @@ -201,7 +202,7 @@ UpdateSleepTime CarrierDroneAIUpdate::update() // FLYING STATE (not a real AI State) // ------------- PhysicsBehavior* physics = obj->getPhysics(); - if (!isDocking && physics->getVelocityMagnitude() > 0 && !obj->isContained()) { + if (!isDocking && !isDead && physics->getVelocityMagnitude() > 0 && !obj->isContained()) { obj->setModelConditionState(MODELCONDITION_JETEXHAUST); } else { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DroneCarrierAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DroneCarrierAIUpdate.cpp index b80b67e72fb..efe85136c9a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DroneCarrierAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DroneCarrierAIUpdate.cpp @@ -155,6 +155,17 @@ void DroneCarrierAIUpdate::onSpawnDeath(ObjectID deadSpawn, DamageInfo* damageIn { auto it = std::find(m_spawnIDs.begin(), m_spawnIDs.end(), deadSpawn); + Object* parent = getObject(); + ContainModuleInterface* contain = parent->getContain(); + if (contain != nullptr) { + DroneCarrierContain* dcontain = dynamic_cast(contain); + // If we have a contain inform it about spawn Death + + if (dcontain != nullptr) { + dcontain->onDroneDeath(deadSpawn); + } + } + // If the iterator is at the end, we didn't find deadSpawn, so bail out. // Otherwise, bad crash stuff will happen. if (it == m_spawnIDs.end()) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp index a4144e500dc..890e311373f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp @@ -96,6 +96,7 @@ const char* const DamageTypeFlags::s_bitNameList[] = "ANTI_SHIP", // Specific damage types with special logic attached "TORPEDO", + "ANTI_TORPEDO", "CHRONO_GUN", "CHRONO_UNRESISTABLE", //"ZOMBIE_VIRUS", // TODO