From 9d6918793fe9d56d6def4b0dedcdf15022f71ff5 Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Mon, 9 Nov 2020 01:45:27 -0500 Subject: [PATCH 01/32] Add new ship AI setting: 'useAllPrimaryWeapons'. This enables ships with multiple primary weapons installed to fire all of them simultaneously. This is not the same as linked fire because each weapon is fired individually by the AI. The main use case of this is to enable ships with multiple primary weapons to be able to aim them properly while accounting for range, which cannot be done using secondary weapons, while not being forced to only fire one gun at a time. --- Mammoth/Include/TSE.h | 2 +- Mammoth/Include/TSEItemInlines.h | 2 +- Mammoth/Include/TSEShipAI.h | 3 +- Mammoth/Include/TSESpaceObjectsImpl.h | 3 +- Mammoth/TSE/AIManeuvers.cpp | 318 ++++++++++++++++++-------- Mammoth/TSE/CAIBehaviorCtx.cpp | 19 +- Mammoth/TSE/CAISettings.cpp | 9 + Mammoth/TSE/CCExtensions.cpp | 1 + Mammoth/TSE/CShip.cpp | 18 +- Mammoth/TSE/ShipAIImpl.h | 3 +- 10 files changed, 271 insertions(+), 107 deletions(-) diff --git a/Mammoth/Include/TSE.h b/Mammoth/Include/TSE.h index 6412bb988..b770e57eb 100644 --- a/Mammoth/Include/TSE.h +++ b/Mammoth/Include/TSE.h @@ -463,7 +463,7 @@ class CSpaceObject virtual CSpaceObject *GetTarget (DWORD dwFlags = 0) const { return NULL; } virtual CTargetList GetTargetList (void) const { return CTargetList(); } - virtual bool IsTargetableProjectile (void) const { return true; } + virtual bool IsTargetableProjectile (void) const { return false; } int CalcFireSolution (CSpaceObject *pTarget, Metric rMissileSpeed) const; CSpaceObject *CalcTargetToAttack (CSpaceObject *pAttacker, CSpaceObject *pOrderGiver); diff --git a/Mammoth/Include/TSEItemInlines.h b/Mammoth/Include/TSEItemInlines.h index 6c08472a4..35d7d400b 100644 --- a/Mammoth/Include/TSEItemInlines.h +++ b/Mammoth/Include/TSEItemInlines.h @@ -417,7 +417,7 @@ inline bool CInstalledDevice::IsSecondaryWeapon (void) const { DWORD dwLinkedFire; const CDeviceItem DeviceItem = m_pItem->AsDeviceItemOrThrow(); - return (m_fSecondaryWeapon + return (m_fSecondaryWeapon || (dwLinkedFire = DeviceItem.GetLinkedFireOptions()) == CDeviceClass::lkfEnemyInRange || dwLinkedFire == CDeviceClass::lkfTargetInRange); } diff --git a/Mammoth/Include/TSEShipAI.h b/Mammoth/Include/TSEShipAI.h index 7e7724765..4d5c44998 100644 --- a/Mammoth/Include/TSEShipAI.h +++ b/Mammoth/Include/TSEShipAI.h @@ -91,6 +91,7 @@ class CAISettings void ReadFromStream (SLoadCtx &Ctx); void SetMinCombatSeparation (Metric rValue) { m_rMinCombatSeparation = rValue; } CString SetValue (const CString &sSetting, const CString &sValue); + bool UseAllPrimaryWeapons (void) const { return m_fUseAllPrimaryWeapons; } void WriteToStream (IWriteStream *pStream); static AICombatStyles ConvertToAICombatStyle (const CString &sValue); @@ -123,7 +124,7 @@ class CAISettings DWORD m_fNoTargetsOfOpportunity:1; // If TRUE, do not attack targets of opportunity DWORD m_fIsPlayer:1; // If TRUE, we're controlling the player ship (this is usually // for debugging only). - DWORD m_fSpare5:1; + DWORD m_fUseAllPrimaryWeapons:1; // If TRUE, we try to shoot all primary weapons at the same time DWORD m_fSpare6:1; DWORD m_fSpare7:1; DWORD m_fSpare8:1; diff --git a/Mammoth/Include/TSESpaceObjectsImpl.h b/Mammoth/Include/TSESpaceObjectsImpl.h index 4739d8adc..24bb23e3e 100644 --- a/Mammoth/Include/TSESpaceObjectsImpl.h +++ b/Mammoth/Include/TSESpaceObjectsImpl.h @@ -1036,7 +1036,8 @@ class CShip : public TSpaceObjectImpl bool GetWeaponIsReady (DeviceNames iDev); Metric GetWeaponRange (DeviceNames iDev); bool IsWeaponAligned (DeviceNames iDev, CSpaceObject *pTarget, int *retiAimAngle = NULL, int *retiFireAngle = NULL, int *retiFacingAngle = NULL); - bool IsWeaponRepeating (DeviceNames iDev = devNone) const { return m_Devices.IsWeaponRepeating(); } + bool IsWeaponAligned (CInstalledDevice *pWeapon, CSpaceObject *pTarget, int *retiAimAngle = NULL, int *retiFireAngle = NULL, int *retiFacingAngle = NULL); + bool IsWeaponRepeating (DeviceNames iDev = devNone) const { return m_Devices.IsWeaponRepeating(iDev); } // Settings CAbilitySet &GetNativeAbilities (void) { return m_Abilities; } diff --git a/Mammoth/TSE/AIManeuvers.cpp b/Mammoth/TSE/AIManeuvers.cpp index 4397f9bb8..9fb0c8772 100644 --- a/Mammoth/TSE/AIManeuvers.cpp +++ b/Mammoth/TSE/AIManeuvers.cpp @@ -1402,9 +1402,127 @@ void CAIBehaviorCtx::ImplementFireWeaponOnTarget (CShip *pShip, // Select the appropriate weapon. If we're not given a weapon, then choose the // best one. - DeviceNames iWeaponToFire; - Metric rWeaponRange; - if (iWeapon == -1) + std::vector pWeaponsToFire; + std::vector rWeaponRanges; + if (iWeapon == -1 && UsesAllPrimaryWeapons()) + { + // Block for selecting and firing multiple weapons. In this case, we select each weapon on the ship that is not a secondary weapon or linked fire (except for + // selected or selectedSameVariant). If it is selected or selectedSameVariant, add only the gun with the highest weapon score. + int weaponIndex = 0; + + std::map lkfSelectedWeaponsByScore; + std::map lkfSelectedWeaponsToPushBack; + std::map, int> lkfSelectedVariantWeaponsByScore; + std::map, CInstalledDevice*> lkfSelectedVariantWeaponsToPushBack; + + for (CDeviceItem DeviceItem : pShip->GetDeviceSystem()) + { + // Iterate through all weapons. + CInstalledDevice *pWeapon = DeviceItem.GetInstalledDevice(); + + // If this weapon is not working, linked-fire, or secondary, then skip it + auto linkedFireOptions = pWeapon->GetItem()->AsDeviceItemOrThrow().GetLinkedFireOptions(); + bool isLinkedFire = (pWeapon->IsLinkedFire() && !(linkedFireOptions & CDeviceClass::LinkedFireOptions::lkfSelected) + && !(linkedFireOptions & CDeviceClass::LinkedFireOptions::lkfSelectedVariant)); + + if (pWeapon->IsSecondaryWeapon() || isLinkedFire || !pWeapon->IsWorking()) + { + continue; + } + switch (pWeapon->GetCategory()) + { + case itemcatWeapon: + { + // If score is greater than zero, add it to the list + // If it is an lkfSelected weapon, add only the one with the highest score of that type to the list + // If it is an lkfSelectedVariant weapon, add only the one with the highest score and variant combo of that type to the list + int iScore = CalcWeaponScore(pShip, pTarget, pWeapon, rTargetDist2, true); + if (linkedFireOptions & CDeviceClass::LinkedFireOptions::lkfSelected) + { + LONG UNID = pWeapon->GetUNID(); + // Check if this UNID exists in the respective map; if it does then check to see if the score exceeds the stored score, otherwise the score to beat is zero + // If so, set this as the weapon of the given UNID to push_back + int iHighestScoreForCategory = lkfSelectedWeaponsByScore[UNID]; + if (iScore > iHighestScoreForCategory) + { + lkfSelectedWeaponsByScore[UNID] = iScore; + lkfSelectedWeaponsToPushBack[UNID] = pWeapon; + } + } + else if (linkedFireOptions & CDeviceClass::LinkedFireOptions::lkfSelectedVariant) + { + LONG UNID = pWeapon->GetUNID(); + int variant_type = CItemCtx(pShip, pWeapon).GetItemVariantNumber(); + auto identifier = std::make_pair(UNID, variant_type); + // Check if this identifier exists in the respective map; if it does then check to see if the score exceeds the stored score, otherwise the score to beat is zero + // If so, set this as the weapon of the given identifier to push_back + int iHighestScoreForCategory = lkfSelectedVariantWeaponsByScore[identifier]; + if (iScore > iHighestScoreForCategory) + { + lkfSelectedVariantWeaponsByScore[identifier] = iScore; + lkfSelectedVariantWeaponsToPushBack[identifier] = pWeapon; + } + } + else if (iScore > 0) + { + pWeaponsToFire.push_back(pWeapon); + rWeaponRanges.push_back(pWeapon->GetMaxEffectiveRange(pShip, pTarget)); + } + break; + } + + case itemcatLauncher: + { + int iCount = pShip->GetMissileCount(); + int iBestScore = 0; + int iBestWeaponVariant = 0; + if (iCount > 0 && !pShip->IsWeaponRepeating(devMissileWeapon)) + { + pShip->ReadyFirstMissile(); + + for (int j = 0; j < iCount; j++) + { + int iScore = CalcWeaponScore(pShip, pTarget, pWeapon, rTargetDist2); + + // If we only score 1 and we've got secondary weapons, then don't + // bother with this missile (we don't want to waste it) + + if (iScore == 1 && HasSecondaryWeapons()) + { + iScore = 0; + } + + if (iScore > iBestScore) + { + iBestWeaponVariant = j; + iBestScore = iScore; + } + + pShip->ReadyNextMissile(); + } + } + auto iBestWeapon = pWeapon->GetDeviceSlot(); + iBestWeapon = pShip->SelectWeapon(iBestWeapon, iBestWeaponVariant); + pWeaponsToFire.push_back(pWeapon); + rWeaponRanges.push_back(pWeapon->GetMaxEffectiveRange(pShip, pTarget)); + break; + } + } + } + + for (auto& UNIDAndWeapon : lkfSelectedWeaponsToPushBack) + { + pWeaponsToFire.push_back(UNIDAndWeapon.second); + rWeaponRanges.push_back(UNIDAndWeapon.second->GetMaxEffectiveRange(pShip, pTarget)); + } + + for (auto& identifierAndWeapon : lkfSelectedVariantWeaponsToPushBack) + { + pWeaponsToFire.push_back(identifierAndWeapon.second); + rWeaponRanges.push_back(identifierAndWeapon.second->GetMaxEffectiveRange(pShip, pTarget)); + } + } + else if (iWeapon == -1) { if (((iTick % 30) == 0) && (m_fHasMultipleWeapons || m_iBestWeapon == devNone)) @@ -1419,142 +1537,154 @@ void CAIBehaviorCtx::ImplementFireWeaponOnTarget (CShip *pShip, DebugAIOutput(pShip, "Fire: No appropriate weapon found"); return; } - - iWeaponToFire = m_iBestWeapon; - rWeaponRange = m_rBestWeaponRange; + CInstalledDevice* pWeapon = pShip->GetNamedDevice(m_iBestWeapon); + if (pWeapon) + { + pWeaponsToFire.push_back(pWeapon); + rWeaponRanges.push_back(pWeapon->GetMaxEffectiveRange(pShip, pTarget)); + } } else { - iWeaponToFire = pShip->SelectWeapon(iWeapon, iWeaponVariant); - rWeaponRange = pShip->GetWeaponRange(iWeaponToFire); + auto iWeaponToFire = pShip->SelectWeapon(iWeapon, iWeaponVariant); + CInstalledDevice* pWeapon = pShip->GetNamedDevice(iWeaponToFire); + if (pWeapon) + { + pWeaponsToFire.push_back(pWeapon); + rWeaponRanges.push_back(pWeapon->GetMaxEffectiveRange(pShip, pTarget)); + } } - // See if the chosen weapon can hit the target - - int iAimAngle = pShip->GetRotation(); - int iFireAngle = -1; - int iFacingAngle = -1; - bool bAligned; - bAligned = pShip->IsWeaponAligned(iWeaponToFire, - pTarget, + int iWeaponIndex = 0; + for (auto& pWeaponToFire : pWeaponsToFire) + { + // See if the chosen weapon can hit the target + Metric rWeaponRange = rWeaponRanges[iWeaponIndex]; + + int iAimAngle = pShip->GetRotation(); + int iFireAngle = -1; + int iFacingAngle = -1; + bool bAligned; + bAligned = pShip->IsWeaponAligned(pWeaponToFire, + pTarget, &iAimAngle, &iFireAngle, &iFacingAngle); - bool bAimError = false; + bool bAimError = false; - // iAimAngle is the direction that we should fire in order to hit - // the target. - // - // iFireAngle is the direction in which the weapon will fire. - // - // iFacingAngle is the direction in which the ship should face - // in order for the weapon to hit the target. + // iAimAngle is the direction that we should fire in order to hit + // the target. + // + // iFireAngle is the direction in which the weapon will fire. + // + // iFacingAngle is the direction in which the ship should face + // in order for the weapon to hit the target. - // There is a chance of missing + // There is a chance of missing - if (pShip->GetWeaponIsReady(iWeaponToFire)) - { - if (bAligned) + if (pWeaponToFire->IsReady()) { - if (mathRandom(1, 100) > GetFireAccuracy()) + if (bAligned) { - bAligned = false; + if (mathRandom(1, 100) > GetFireAccuracy()) + { + bAligned = false; - // In this case, we happen to be aligned, but because of inaccuracy - // reason we think we're not. We clear the aim angle because for - // omnidirectional weapons, we don't want to try to turn towards - // the new aim point. + // In this case, we happen to be aligned, but because of inaccuracy + // reason we think we're not. We clear the aim angle because for + // omnidirectional weapons, we don't want to try to turn towards + // the new aim point. - iAimAngle = -1; - bAimError = true; - DebugAIOutput(pShip, "Aim error: hold fire when aligned"); + iAimAngle = -1; + bAimError = true; + DebugAIOutput(pShip, "Aim error: hold fire when aligned"); + } } - } - else if (iAimAngle != -1) - { - if (mathRandom(1, 100) <= m_iPrematureFireChance) + else if (iAimAngle != -1) { - int iAimOffset = AngleOffset(iFireAngle, iAimAngle); - if (iAimOffset < 20) + if (mathRandom(1, 100) <= m_iPrematureFireChance) { - bAligned = true; - bAimError = true; - DebugAIOutput(pShip, "Aim error: fire when not aligned"); + int iAimOffset = AngleOffset(iFireAngle, iAimAngle); + if (iAimOffset < 20) + { + bAligned = true; + bAimError = true; + DebugAIOutput(pShip, "Aim error: fire when not aligned"); + } } } } - } - // Fire + // Fire - if (bAligned) - { + if (bAligned) + { #ifdef DEBUG - { - CInstalledDevice *pWeapon = pShip->GetNamedDevice(iWeaponToFire); - - char szDebug[1024]; - if (bAimError) - wsprintf(szDebug, "%s: false positive iAim=%d iFireAngle=%d", pWeapon->GetName().GetASCIIZPointer(), iAimAngle, iFireAngle); - else if (!pShip->GetWeaponIsReady(iWeaponToFire)) - wsprintf(szDebug, "%s: aligned; NOT READY", pWeapon->GetName().GetASCIIZPointer()); - else if (rTargetDist2 > (rWeaponRange * rWeaponRange)) - wsprintf(szDebug, "%s: aligned; TARGET OUT OF RANGE", pWeapon->GetName().GetASCIIZPointer()); - else - wsprintf(szDebug, "%s: aligned", pWeapon->GetName().GetASCIIZPointer()); + { + char szDebug[1024]; + if (bAimError) + wsprintf(szDebug, "%s: false positive iAim=%d iFireAngle=%d", pWeaponToFire->GetName().GetASCIIZPointer(), iAimAngle, iFireAngle); + else if (!pWeaponToFire->IsReady()) + wsprintf(szDebug, "%s: aligned; NOT READY", pWeaponToFire->GetName().GetASCIIZPointer()); + else if (rTargetDist2 > (rWeaponRange * rWeaponRange)) + wsprintf(szDebug, "%s: aligned; TARGET OUT OF RANGE", pWeaponToFire->GetName().GetASCIIZPointer()); + else + wsprintf(szDebug, "%s: aligned", pWeaponToFire->GetName().GetASCIIZPointer()); - DebugAIOutput(pShip, szDebug); - } + DebugAIOutput(pShip, szDebug); + } #endif - // If we're aligned and the weapon is ready, and we're - // in range of the target, then fire! + // If we're aligned and the weapon is ready, and we're + // in range of the target, then fire! - if (pShip->GetWeaponIsReady(iWeaponToFire) + if (pWeaponToFire->IsReady() && rTargetDist2 <= (rWeaponRange * rWeaponRange)) - { - CInstalledDevice *pWeapon = pShip->GetNamedDevice(iWeaponToFire); - - if (iWeaponToFire == devPrimaryWeapon) { - if (CheckForFriendsInLineOfFire(pShip, pWeapon, pTarget, iFireAngle, Max(pWeapon->GetMaxEffectiveRange(pShip), DEFAULT_DIST_CHECK))) + if (pWeaponToFire->GetCategory() == itemcatWeapon) { - if (!bDoNotShoot) - pShip->SetWeaponTriggered(pWeapon); - DebugAIOutput(pShip, "FireOnTarget: Fire primary!"); + if (CheckForFriendsInLineOfFire(pShip, pWeaponToFire, pTarget, iFireAngle, Max(pWeaponToFire->GetMaxEffectiveRange(pShip), DEFAULT_DIST_CHECK))) + { + if (!bDoNotShoot) + pShip->SetWeaponTriggered(pWeaponToFire); + DebugAIOutput(pShip, "FireOnTarget: Fire primary!"); + } + else + DebugAIOutput(pShip, "FireOnTarget: Friendlies in line of fire"); } else - DebugAIOutput(pShip, "FireOnTarget: Friendlies in line of fire"); - } - else - { - if (CheckForFriendsInLineOfFire(pShip, pWeapon, pTarget, iFireAngle, Max(pWeapon->GetMaxEffectiveRange(pShip), DEFAULT_DIST_CHECK))) { - if (!bDoNotShoot) - pShip->SetWeaponTriggered(pWeapon); - DebugAIOutput(pShip, "FireOnTarget: Fire missile!"); + if (CheckForFriendsInLineOfFire(pShip, pWeaponToFire, pTarget, iFireAngle, Max(pWeaponToFire->GetMaxEffectiveRange(pShip), DEFAULT_DIST_CHECK))) + { + if (!bDoNotShoot) + pShip->SetWeaponTriggered(pWeaponToFire); + DebugAIOutput(pShip, "FireOnTarget: Fire missile!"); + } + else + DebugAIOutput(pShip, "FireOnTarget: Friendlies in line of fire"); } - else - DebugAIOutput(pShip, "FireOnTarget: Friendlies in line of fire"); } } - } - else - { - DebugAIOutput(pShip, "Fire: Weapon NOT aligned"); + else + { + DebugAIOutput(pShip, "Fire: Weapon NOT aligned"); #ifdef DEBUG_SHIP - if (bDebug) - pShip->GetUniverse().DebugOutput("Face target at distance: %d moving at: %d%%c", + if (bDebug) + pShip->GetUniverse().DebugOutput("Face target at distance: %d moving at: %d%%c", (int)(vTarget.Length() / LIGHT_SECOND), (int)(100.0 * 0 / LIGHT_SPEED)); #endif + } + + // Turn to aim, even if weapon is already approximately aligned + + if (retiFireDir) + *retiFireDir = iFacingAngle; + iWeaponIndex++; } - // Turn to aim, even if weapon is already approximately aligned - if (retiFireDir) - *retiFireDir = iFacingAngle; DEBUG_CATCH } diff --git a/Mammoth/TSE/CAIBehaviorCtx.cpp b/Mammoth/TSE/CAIBehaviorCtx.cpp index 7a62b0433..033a4ba20 100644 --- a/Mammoth/TSE/CAIBehaviorCtx.cpp +++ b/Mammoth/TSE/CAIBehaviorCtx.cpp @@ -180,7 +180,7 @@ void CAIBehaviorCtx::CalcBestWeapon (CShip *pShip, CSpaceObject *pTarget, Metric // inquire about each missile. if (!m_fRecalcBestWeapon - || pShip->IsWeaponRepeating(devMissileWeapon)) + || pShip->IsWeaponRepeating()) return; // Recompute everything @@ -234,7 +234,18 @@ void CAIBehaviorCtx::CalcBestWeapon (CShip *pShip, CSpaceObject *pTarget, Metric // Skip linked-fire weapons else if (Weapon.IsLinkedFire()) - continue; + { + bool bSkipThisWeapon = false; + if (UsesAllPrimaryWeapons()) + { + auto linkedFireOptions = Weapon.GetItem()->AsDeviceItemOrThrow().GetLinkedFireOptions(); + if (!(linkedFireOptions & CDeviceClass::LinkedFireOptions::lkfSelected) + && !(linkedFireOptions & CDeviceClass::LinkedFireOptions::lkfSelectedVariant)) + continue; + } + else + continue; + } // Otherwise, this is a primary weapon or launcher @@ -703,7 +714,7 @@ void CAIBehaviorCtx::CalcShieldState (CShip *pShip) } } -int CAIBehaviorCtx::CalcWeaponScore (CShip *pShip, CSpaceObject *pTarget, CInstalledDevice *pWeapon, Metric rTargetDist2) +int CAIBehaviorCtx::CalcWeaponScore (CShip *pShip, CSpaceObject *pTarget, CInstalledDevice *pWeapon, Metric rTargetDist2, bool avoidAnyNonReadyWeapons) // CalcWeaponScore // @@ -735,7 +746,7 @@ int CAIBehaviorCtx::CalcWeaponScore (CShip *pShip, CSpaceObject *pTarget, CInsta // If this weapon will take a while to get ready, then // lower the score. - if (pWeapon->GetTimeUntilReady() >= 15) + if (pWeapon->GetTimeUntilReady() >= (avoidAnyNonReadyWeapons ? 1 : 15)) return 1; // Get the item for the selected variant (either the weapon diff --git a/Mammoth/TSE/CAISettings.cpp b/Mammoth/TSE/CAISettings.cpp index 1b6b9f80a..a6ea8a45d 100644 --- a/Mammoth/TSE/CAISettings.cpp +++ b/Mammoth/TSE/CAISettings.cpp @@ -27,6 +27,7 @@ #define NON_COMBATANT_ATTRIB CONSTLIT("nonCombatant") #define PERCEPTION_ATTRIB CONSTLIT("perception") #define STAND_OFF_COMBAT_ATTRIB CONSTLIT("standOffCombat") +#define USE_ALL_PRIMARY_WEAPONS_ATTRIB CONSTLIT("useAllPrimaryWeapons") #define COMBAT_STYLE_ADVANCED CONSTLIT("advanced") #define COMBAT_STYLE_CHASE CONSTLIT("chase") @@ -190,6 +191,8 @@ CString CAISettings::GetValue (const CString &sSetting) return (m_fNonCombatant ? STR_TRUE : NULL_STR); else if (strEquals(sSetting, PERCEPTION_ATTRIB)) return strFromInt(m_iPerception); + else if (strEquals(sSetting, USE_ALL_PRIMARY_WEAPONS_ATTRIB)) + return (m_fUseAllPrimaryWeapons ? STR_TRUE : NULL_STR); else return NULL_STR; } @@ -262,6 +265,7 @@ ALERROR CAISettings::InitFromXML (SDesignLoadCtx &Ctx, CXMLElement *pDesc) m_fNoNavPaths = pDesc->GetAttributeBool(NO_NAV_PATHS_ATTRIB); m_fNoOrderGiver = pDesc->GetAttributeBool(NO_ORDER_GIVER_ATTRIB); m_fIsPlayer = false; + m_fUseAllPrimaryWeapons = pDesc->GetAttributeBool(USE_ALL_PRIMARY_WEAPONS_ATTRIB); return NOERROR; } @@ -294,6 +298,7 @@ void CAISettings::InitToDefault (void) m_fNoAttackOnThreat = false; m_fNoTargetsOfOpportunity = false; m_fIsPlayer = false; + m_fUseAllPrimaryWeapons = false; } void CAISettings::ReadFromStream (SLoadCtx &Ctx) @@ -355,6 +360,7 @@ void CAISettings::ReadFromStream (SLoadCtx &Ctx) } else m_fIsPlayer = ((dwLoad & 0x00000800) ? true : false); + m_fUseAllPrimaryWeapons = ((dwLoad & 0x00001000) ? true : false); } CString CAISettings::SetValue (const CString &sSetting, const CString &sValue) @@ -404,6 +410,8 @@ CString CAISettings::SetValue (const CString &sSetting, const CString &sValue) m_fNonCombatant = !sValue.IsBlank(); else if (strEquals(sSetting, PERCEPTION_ATTRIB)) m_iPerception = Max((int)CSpaceObject::perceptMin, Min(strToInt(sValue, CSpaceObject::perceptNormal), (int)CSpaceObject::perceptMax)); + else if (strEquals(sSetting, USE_ALL_PRIMARY_WEAPONS_ATTRIB)) + m_fUseAllPrimaryWeapons = !sValue.IsBlank(); else return NULL_STR; @@ -451,6 +459,7 @@ void CAISettings::WriteToStream (IWriteStream *pStream) dwSave |= (m_fNoAttackOnThreat ? 0x00000200 : 0); dwSave |= (m_fNoTargetsOfOpportunity ? 0x00000400 : 0); dwSave |= (m_fIsPlayer ? 0x00000800 : 0); + dwSave |= (m_fUseAllPrimaryWeapons ? 0x00001000 : 0); pStream->Write((char *)&dwSave, sizeof(DWORD)); } diff --git a/Mammoth/TSE/CCExtensions.cpp b/Mammoth/TSE/CCExtensions.cpp index c0c0c4624..c326c16cb 100644 --- a/Mammoth/TSE/CCExtensions.cpp +++ b/Mammoth/TSE/CCExtensions.cpp @@ -1410,6 +1410,7 @@ static PRIMITIVEPROCDEF g_Extensions[] = " 'noNavPaths (True/Nil)\n" " 'noOrderGiver (True/Nil)\n" " 'noTargetsOfOpportunity (True/Nil)\n" + " 'useAllPrimaryWeapons (True/Nil)\n" "\n" " 'combatSeparation {pixels}\n" " 'fireAccuracy {percent}\n" diff --git a/Mammoth/TSE/CShip.cpp b/Mammoth/TSE/CShip.cpp index 71147e27f..975fcfb9f 100644 --- a/Mammoth/TSE/CShip.cpp +++ b/Mammoth/TSE/CShip.cpp @@ -3989,14 +3989,24 @@ bool CShip::IsSingletonDevice (ItemCategories iItemCat) } } -bool CShip::IsWeaponAligned (DeviceNames iDev, CSpaceObject *pTarget, int *retiAimAngle, int *retiFireAngle, int *retiFacingAngle) +bool CShip::IsWeaponAligned (DeviceNames iDev, CSpaceObject* pTarget, int* retiAimAngle, int* retiFireAngle, int* retiFacingAngle) + +// IsWeaponAligned +// +// Returns TRUE if the weapon is aligned on target + + { + CInstalledDevice* pWeapon = GetNamedDevice(iDev); + return CShip::IsWeaponAligned(pWeapon, pTarget, retiAimAngle, retiFireAngle, retiFacingAngle); + } + +bool CShip::IsWeaponAligned (CInstalledDevice* pWeapon, CSpaceObject *pTarget, int *retiAimAngle, int *retiFireAngle, int *retiFacingAngle) // IsWeaponAligned // // Returns TRUE if the weapon is aligned on target { - CInstalledDevice *pWeapon = GetNamedDevice(iDev); if (pWeapon) { int iAimAngle; @@ -7827,8 +7837,8 @@ void CShip::SetWeaponTriggered (CInstalledDevice *pWeapon, bool bTriggered) // If this is the primary device, or if it is a device that // is linked to the primary device, then activate it. - - if (&Device == pWeapon + CInstalledDevice* pDevice = &Device; + if (pDevice == pWeapon || (Device.IsLinkedFire(iCat))) Device.SetTriggered(bTriggered); } diff --git a/Mammoth/TSE/ShipAIImpl.h b/Mammoth/TSE/ShipAIImpl.h index cf9d73c59..cda8256fd 100644 --- a/Mammoth/TSE/ShipAIImpl.h +++ b/Mammoth/TSE/ShipAIImpl.h @@ -117,6 +117,7 @@ class CAIBehaviorCtx void SetWaitingForShieldsToRegen (bool bValue = true) { m_fWaitForShieldsToRegen = bValue; } bool ThrustsThroughTurn (void) const { return m_fThrustThroughTurn; } void Update (CShip *pShip); + bool UsesAllPrimaryWeapons (void) const { return m_AISettings.UseAllPrimaryWeapons(); } void WriteToStream (CSystem *pSystem, IWriteStream *pStream); // Maneuvers @@ -160,7 +161,7 @@ class CAIBehaviorCtx void CalcNavPath (CShip *pShip, CSpaceObject *pFrom, CSpaceObject *pTo); void CalcNavPath (CShip *pShip, CNavigationPath *pPath, bool bOwned = false); void CalcShieldState (CShip *pShip); - int CalcWeaponScore (CShip *pShip, CSpaceObject *pTarget, CInstalledDevice *pWeapon, Metric rTargetDist2); + int CalcWeaponScore (CShip *pShip, CSpaceObject *pTarget, CInstalledDevice *pWeapon, Metric rTargetDist2, bool avoidAnyNonReadyWeapons = false); void CancelDocking (CShip *pShip, CSpaceObject *pBase); bool CheckForFriendsInLineOfFire (CShip *pShip, const CInstalledDevice *pDevice, CSpaceObject *pTarget, int iFireAngle, Metric rMaxRange) { return (NoFriendlyFireCheck() || pShip->IsLineOfFireClear(pDevice, pTarget, iFireAngle, rMaxRange)); } From 1d072414a4ef8007346760a60eab6c2d98c3cd0b Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Sat, 14 Nov 2020 23:32:07 -0500 Subject: [PATCH 02/32] Fix LinkedFireSelected for NPCs --- Mammoth/Include/TSEDevices.h | 2 +- Mammoth/TSE/CAIBehaviorCtx.cpp | 1 - Mammoth/TSE/CDeviceItem.cpp | 21 +-------------------- Mammoth/TSE/CInstalledDevice.cpp | 22 ++++++++++++++++++++-- Mammoth/TSE/CShip.cpp | 4 ++-- 5 files changed, 24 insertions(+), 26 deletions(-) diff --git a/Mammoth/Include/TSEDevices.h b/Mammoth/Include/TSEDevices.h index e1011c290..01127fe90 100644 --- a/Mammoth/Include/TSEDevices.h +++ b/Mammoth/Include/TSEDevices.h @@ -674,7 +674,7 @@ class CInstalledDevice bool IsFirstVariantSelected(CSpaceObject *pSource) { return (m_pClass ? m_pClass->IsFirstVariantSelected(pSource, this) : true); } bool IsFuelCompatible (CItemCtx &Ctx, const CItem &FuelItem) { return m_pClass->IsFuelCompatible(Ctx, FuelItem); } bool IsLastVariantSelected(CSpaceObject *pSource) { return (m_pClass ? m_pClass->IsLastVariantSelected(pSource, this) : true); } - bool IsLinkedFire (ItemCategories iTriggerCat = itemcatNone) const; + bool IsLinkedFire (ItemCategories iTriggerCat = itemcatNone, CInstalledDevice *pWeapon = nullptr) const; bool IsSecondaryWeapon (void) const; bool IsSelectable (void) const; bool IsVariantSelected (CSpaceObject *pSource) { return (m_pClass ? m_pClass->IsVariantSelected(pSource, this) : true); } diff --git a/Mammoth/TSE/CAIBehaviorCtx.cpp b/Mammoth/TSE/CAIBehaviorCtx.cpp index 033a4ba20..ecd8b67d7 100644 --- a/Mammoth/TSE/CAIBehaviorCtx.cpp +++ b/Mammoth/TSE/CAIBehaviorCtx.cpp @@ -235,7 +235,6 @@ void CAIBehaviorCtx::CalcBestWeapon (CShip *pShip, CSpaceObject *pTarget, Metric else if (Weapon.IsLinkedFire()) { - bool bSkipThisWeapon = false; if (UsesAllPrimaryWeapons()) { auto linkedFireOptions = Weapon.GetItem()->AsDeviceItemOrThrow().GetLinkedFireOptions(); diff --git a/Mammoth/TSE/CDeviceItem.cpp b/Mammoth/TSE/CDeviceItem.cpp index 0252034ab..df470026c 100644 --- a/Mammoth/TSE/CDeviceItem.cpp +++ b/Mammoth/TSE/CDeviceItem.cpp @@ -53,28 +53,9 @@ CDeviceItem::ECalcTargetTypes CDeviceItem::CalcTargetType (void) const CInstalledDevice *pPrimaryWeapon = pSource->GetNamedDevice(devPrimaryWeapon); CInstalledDevice *pSelectedLauncher = pSource->GetNamedDevice(devMissileWeapon); - // If our options is "never fire", or if our options is "fire if selected" and this is the player ship, - // but the primary weapon or launcher isn't both "fire if selected" AND of the same type, then don't fire. - // If a weapon is "fire if selected and same variant", then it only fires if the primary weapon is of the - // same variant and type. - DWORD dwLinkedFireSelected = CDeviceClass::lkfSelected | CDeviceClass::lkfSelectedVariant; - bool bPrimaryWeaponCheckVariant = pPrimaryWeapon != NULL ? (dwLinkedFireOptions - & CDeviceClass::lkfSelectedVariant ? GetVariantNumber() == CItemCtx(pSource, pPrimaryWeapon).GetItemVariantNumber() : true) : false; - bool bSelectedLauncherCheckVariant = pSelectedLauncher != NULL ? (dwLinkedFireOptions - & CDeviceClass::lkfSelectedVariant ? GetVariantNumber() == CItemCtx(pSource, pSelectedLauncher).GetItemVariantNumber() : true) : false; - - if ((dwLinkedFireOptions & CDeviceClass::lkfNever) || ( - ((!((pPrimaryWeapon != NULL ? (pPrimaryWeapon->GetSlotLinkedFireOptions() & dwLinkedFireSelected) : false) && - (pPrimaryWeapon != NULL ? ((pPrimaryWeapon->GetUNID() == Weapon.GetUNID()) && bPrimaryWeaponCheckVariant) : false)) - && ((Weapon.GetCategory() == itemcatWeapon) && !Weapon.UsesLauncherControls())) || - (!((pSelectedLauncher != NULL ? (pSelectedLauncher->GetSlotLinkedFireOptions() & dwLinkedFireSelected) : false) && - (pSelectedLauncher != NULL ? ((pSelectedLauncher->GetUNID() == Weapon.GetUNID()) && bSelectedLauncherCheckVariant) : false)) - && ((Weapon.GetCategory() == itemcatLauncher) || Weapon.UsesLauncherControls()))) && - (dwLinkedFireOptions & dwLinkedFireSelected) && - pSource->IsPlayer() - )) + if ((dwLinkedFireOptions & CDeviceClass::lkfNever)) { return calcNoTarget; } diff --git a/Mammoth/TSE/CInstalledDevice.cpp b/Mammoth/TSE/CInstalledDevice.cpp index 49662d556..09e9113ab 100644 --- a/Mammoth/TSE/CInstalledDevice.cpp +++ b/Mammoth/TSE/CInstalledDevice.cpp @@ -438,7 +438,7 @@ void CInstalledDevice::Install (CSpaceObject &Source, CItemListManipulator &Item DEBUG_CATCH } -bool CInstalledDevice::IsLinkedFire (ItemCategories iTriggerCat) const +bool CInstalledDevice::IsLinkedFire (ItemCategories iTriggerCat, CInstalledDevice *pWeapon) const // IsLinkedFire // @@ -452,11 +452,29 @@ bool CInstalledDevice::IsLinkedFire (ItemCategories iTriggerCat) const return true; else { + bool bWeaponMatchesSelected = true; + if (pWeapon) + { + auto linkedFireOptions = GetItem()->AsDeviceItemOrThrow().GetLinkedFireOptions(); + auto bLinkedFireSelected = (linkedFireOptions & CDeviceClass::LinkedFireOptions::lkfSelected); + auto bLinkedFireSelectedVariant = (linkedFireOptions & CDeviceClass::LinkedFireOptions::lkfSelectedVariant); + if (bLinkedFireSelected || bLinkedFireSelectedVariant) + { + bWeaponMatchesSelected = pWeapon->GetUNID() == GetUNID(); + } + if (bLinkedFireSelectedVariant) + { + int iGunVariantType = CItemCtx(GetSource(), pWeapon).GetItemVariantNumber(); + int iOtherGunVariantType = CItemCtx(GetSource(), this).GetItemVariantNumber(); + bWeaponMatchesSelected = bWeaponMatchesSelected && (iGunVariantType == iOtherGunVariantType); + } + } + ItemCategories iNewItemCategory = GetClass()->GetCategory(); if (GetClass()->UsesLauncherControls() && iNewItemCategory == itemcatWeapon) iNewItemCategory = itemcatLauncher; - return (iNewItemCategory == iTriggerCat); + return (iNewItemCategory == iTriggerCat) && bWeaponMatchesSelected; } } diff --git a/Mammoth/TSE/CShip.cpp b/Mammoth/TSE/CShip.cpp index 975fcfb9f..83c4bb863 100644 --- a/Mammoth/TSE/CShip.cpp +++ b/Mammoth/TSE/CShip.cpp @@ -7814,7 +7814,7 @@ void CShip::SetWeaponTriggered (DeviceNames iDev, bool bTriggered) // is linked to the primary device, then activate it. if (&Device == pPrimaryDevice - || (Device.IsLinkedFire(iCat))) + || (Device.IsLinkedFire(iCat, pPrimaryDevice))) Device.SetTriggered(bTriggered); } } @@ -7839,7 +7839,7 @@ void CShip::SetWeaponTriggered (CInstalledDevice *pWeapon, bool bTriggered) // is linked to the primary device, then activate it. CInstalledDevice* pDevice = &Device; if (pDevice == pWeapon - || (Device.IsLinkedFire(iCat))) + || (Device.IsLinkedFire(iCat, pWeapon))) Device.SetTriggered(bTriggered); } } From 19dfcb1e8226bcd6af1fb07698efd3a494b76296 Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Thu, 17 Dec 2020 02:12:08 -0500 Subject: [PATCH 03/32] Initial commit for 'targetCriteria', still need to add save file stuff --- Mammoth/Include/TSEDeviceClassesImpl.h | 23 +++++++++++++++++++++++ Mammoth/Include/TSEDevices.h | 6 ++++++ Mammoth/TSE/CCExtensions.cpp | 2 ++ Mammoth/TSE/CDeviceItem.cpp | 4 ++-- Mammoth/TSE/CInstalledDevice.cpp | 9 +++++++++ Mammoth/TSE/CWeaponClass.cpp | 18 ++++++++++++++++++ Mammoth/TSE/Devices.cpp | 26 +++++++++++++++----------- Mammoth/TSE/TSE.vcxproj | 1 + Mammoth/TSE/TSE.vcxproj.filters | 3 +++ 9 files changed, 79 insertions(+), 13 deletions(-) diff --git a/Mammoth/Include/TSEDeviceClassesImpl.h b/Mammoth/Include/TSEDeviceClassesImpl.h index a834fb0f3..c0ddbc984 100644 --- a/Mammoth/Include/TSEDeviceClassesImpl.h +++ b/Mammoth/Include/TSEDeviceClassesImpl.h @@ -5,6 +5,29 @@ #pragma once +class CWeaponTargetDefinition + { + // Weapon target definition that is attached to installed weapon devices. + // Written and read from stream independently using a similar manner to item data. + // Should only be attached to weapon devices through a unique_ptr. + // Note that we use the range, fire arc, and all other property of the attached weapon, since all this does + // is tell our attached weapon what to shoot at. + public: + class CWeaponTargetDefinition () { }; + class CWeaponTargetDefinition (Kernel::CString sCriteria, bool bCheckLineOfFire = false) : m_bCheckLineOfFire(bCheckLineOfFire), m_CriteriaString(sCriteria) { m_TargetCriteria.Init(sCriteria); }; + CSpaceObject* FindTarget (CWeaponClass* pWeapon, CInstalledDevice* pDevice, CSpaceObject* pSource, CItemCtx& ItemCtx) const; + bool AimAndFire (CWeaponClass* pWeapon, CInstalledDevice* pDevice, CSpaceObject* pSource, CDeviceClass::SDeviceUpdateCtx& Ctx) const; + bool GetCheckLineOfFire () { return m_bCheckLineOfFire; }; + CSpaceObjectCriteria GetTargetCriteria () { return m_TargetCriteria; }; + Kernel::CString GetTargetCriteriaString () { return Kernel::CString(m_CriteriaString.c_str()); }; + void SetCheckLineOfFire (bool bCheckLineOfFire) { m_bCheckLineOfFire = bCheckLineOfFire; }; + void SetTargetCriteria (Kernel::CString sCriteria) { m_TargetCriteria.Init(sCriteria); m_CriteriaString = sCriteria; }; + private: + std::string m_CriteriaString = ""; + CSpaceObjectCriteria m_TargetCriteria; + bool m_bCheckLineOfFire = false; // Check line of fire for friendlies + }; + class CAutoDefenseClass : public CDeviceClass { public: diff --git a/Mammoth/Include/TSEDevices.h b/Mammoth/Include/TSEDevices.h index e1011c290..de4c82a3d 100644 --- a/Mammoth/Include/TSEDevices.h +++ b/Mammoth/Include/TSEDevices.h @@ -7,6 +7,7 @@ class CCargoDesc; class CTargetList; +class CWeaponTargetDefinition; struct SShipPerformanceCtx; enum DeviceNames @@ -625,6 +626,9 @@ class CInstalledDevice void SetTimeUntilReady (int iDelay) { m_iTimeUntilReady = iDelay; } void SetTriggered (bool bTriggered) { m_fTriggered = bTriggered; } void SetWaiting (bool bWaiting) const { m_fWaiting = bWaiting; } + void SetWeaponTargetDefinition (Kernel::CString sCriteria) { m_pWeaponTargetDefinition = std::make_unique(sCriteria, true); } + void ClearWeaponTargetDefinition () { m_pWeaponTargetDefinition.reset(); } + // These are wrapper methods for a CDeviceClass method of the same name. // We add our object pointer as a parameter to the call. @@ -668,6 +672,7 @@ class CInstalledDevice void GetStatus (const CSpaceObject *pSource, int *retiStatus, int *retiMaxStatus) const { m_pClass->GetStatus(this, pSource, retiStatus, retiMaxStatus); } CSpaceObject *GetTarget (CSpaceObject *pSource) const; int GetValidVariantCount (CSpaceObject *pSource) { return m_pClass->GetValidVariantCount(pSource, this); } + CWeaponTargetDefinition *GetWeaponTargetDefinition (void) const { return (m_pWeaponTargetDefinition.get()); } bool HasLastShots (void) const { return (m_LastShotIDs.GetCount() > 0); } int IncCharges (CSpaceObject *pSource, int iChange); bool IsAutomatedWeapon (void) { return m_pClass->IsAutomatedWeapon(); } @@ -713,6 +718,7 @@ class CInstalledDevice CEnhancementDesc m_SlotEnhancements; // Enhancements conferred by the slot TSharedPtr m_pEnhancements; // List of enhancements (may be NULL) TArray m_LastShotIDs; // ObjID of last shots (only for continuous beams) + std::unique_ptr m_pWeaponTargetDefinition = nullptr; // Target definition for autofire weapons DWORD m_dwData = 0; // Data specific to device class diff --git a/Mammoth/TSE/CCExtensions.cpp b/Mammoth/TSE/CCExtensions.cpp index 4e48f6aea..12b879b8e 100644 --- a/Mammoth/TSE/CCExtensions.cpp +++ b/Mammoth/TSE/CCExtensions.cpp @@ -902,6 +902,7 @@ static PRIMITIVEPROCDEF g_Extensions[] = " 'repeating\n" " 'shipCounterPerShot\n" " 'stdCost\n" + " 'targetCriteria\n" "\n" "property (armor)\n\n" " 'blindingImmune\n" @@ -2160,6 +2161,7 @@ static PRIMITIVEPROCDEF g_Extensions[] = " 'pos\n" " 'secondary\n" " 'temperature\n" + " 'targetCriteria\n" "\n" "property (armor)\n\n" diff --git a/Mammoth/TSE/CDeviceItem.cpp b/Mammoth/TSE/CDeviceItem.cpp index 0252034ab..8edb1a4ee 100644 --- a/Mammoth/TSE/CDeviceItem.cpp +++ b/Mammoth/TSE/CDeviceItem.cpp @@ -80,9 +80,9 @@ CDeviceItem::ECalcTargetTypes CDeviceItem::CalcTargetType (void) const } // If our options is "fire always" or "fire if selected" then our target is always the same - // as the primary target. + // as the primary target. Same if we have auto fire enabled - else if ((dwLinkedFireOptions & CDeviceClass::lkfAlways) || (dwLinkedFireOptions & dwLinkedFireSelected)) + else if ((dwLinkedFireOptions & CDeviceClass::lkfAlways) || (dwLinkedFireOptions & dwLinkedFireSelected) || Device.GetWeaponTargetDefinition()) { return calcControllerTarget; } diff --git a/Mammoth/TSE/CInstalledDevice.cpp b/Mammoth/TSE/CInstalledDevice.cpp index d741b4059..607a568ee 100644 --- a/Mammoth/TSE/CInstalledDevice.cpp +++ b/Mammoth/TSE/CInstalledDevice.cpp @@ -20,6 +20,7 @@ #define PROPERTY_POS CONSTLIT("pos") #define PROPERTY_SECONDARY CONSTLIT("secondary") #define PROPERTY_TEMPERATURE CONSTLIT("temperature") +#define PROPERTY_TARGET_CRITERIA CONSTLIT("targetCriteria") #define PROPERTY_SHOT_SEPARATION_SCALE CONSTLIT("shotSeparationScale") bool CInstalledDevice::AccumulateSlotEnhancements (CSpaceObject *pSource, TArray &EnhancementIDs, CItemEnhancementStack *pEnhancements) const @@ -1160,6 +1161,14 @@ ESetPropertyResult CInstalledDevice::SetProperty (CItemCtx &Ctx, const CString & SetSecondary(false); } + else if (strEquals(sName, PROPERTY_TARGET_CRITERIA)) + { + if (pValue == NULL || pValue->IsNil()) + ClearWeaponTargetDefinition(); + else + SetWeaponTargetDefinition(pValue->GetStringValue()); + } + else if (strEquals(sName, PROPERTY_TEMPERATURE)) { CSpaceObject *pSource = Ctx.GetSource(); diff --git a/Mammoth/TSE/CWeaponClass.cpp b/Mammoth/TSE/CWeaponClass.cpp index 42b5ea954..e4f6f544a 100644 --- a/Mammoth/TSE/CWeaponClass.cpp +++ b/Mammoth/TSE/CWeaponClass.cpp @@ -5329,6 +5329,24 @@ void CWeaponClass::Update (CInstalledDevice *pDevice, CSpaceObject *pSource, SDe && (!pDevice->IsTriggered() || pDevice->GetTimeUntilReady() > 1)) pDevice->SetLastShotCount(0); + if (pDevice->GetWeaponTargetDefinition() && pSource->IsPlayer()) + { + // If the weapon is not ready, do not autofire. + + if (!pDevice->IsReady()) + return; + + // If the ship is disarmed or paralyzed, then we do not fire. + + if (pSource->GetCondition(ECondition::paralyzed) + || pSource->GetCondition(ECondition::disarmed)) + return; + bool bActivateResult = pDevice->GetWeaponTargetDefinition()->AimAndFire(this, pDevice, pSource, Ctx); + if (bActivateResult) + { + pDevice->SetTimeUntilReady(CalcActivateDelay(ItemCtx)); + } + } DEBUG_CATCH } diff --git a/Mammoth/TSE/Devices.cpp b/Mammoth/TSE/Devices.cpp index f29e20f01..8f50dfe22 100644 --- a/Mammoth/TSE/Devices.cpp +++ b/Mammoth/TSE/Devices.cpp @@ -54,6 +54,7 @@ #define PROPERTY_POWER_USE CONSTLIT("powerUse") #define PROPERTY_SECONDARY CONSTLIT("secondary") #define PROPERTY_SLOT_ID CONSTLIT("slotID") +#define PROPERTY_TARGET_CRITERIA CONSTLIT("targetCriteria") #define PROPERTY_TEMPERATURE CONSTLIT("temperature") #define PROPERTY_SHOT_SEPARATION_SCALE CONSTLIT("shotSeparationScale") @@ -511,8 +512,8 @@ ICCItem *CDeviceClass::FindItemProperty (CItemCtx &Ctx, const CString &sName) else if (strEquals(sName, PROPERTY_CAN_TARGET_MISSILES)) return (pDevice ? CC.CreateBool(pDevice->CanTargetMissiles()) : CC.CreateNil()); else if (strEquals(sName, PROPERTY_CAPACITOR)) - { - CSpaceObject *pSource = Ctx.GetSource(); + { + CSpaceObject* pSource = Ctx.GetSource(); CounterTypes iType; int iLevel; GetCounter(pDevice, pSource, &iType, &iLevel); @@ -520,7 +521,7 @@ ICCItem *CDeviceClass::FindItemProperty (CItemCtx &Ctx, const CString &sName) return CC.CreateNil(); return CC.CreateInteger(iLevel); - } + } else if (strEquals(sName, PROPERTY_CYCLE_FIRE)) return (pDevice ? CC.CreateBool(pDevice->GetCycleFireSettings()) : CC.CreateNil()); @@ -535,25 +536,25 @@ ICCItem *CDeviceClass::FindItemProperty (CItemCtx &Ctx, const CString &sName) return CC.CreateBool(pDevice ? pDevice->IsExternal() : IsExternal()); else if (strEquals(sName, PROPERTY_EXTRA_POWER_USE)) - { + { if (pDevice == NULL) return CC.CreateNil(); return CC.CreateInteger(pDevice->GetExtraPowerUse()); - } + } else if (strEquals(sName, PROPERTY_POS)) - { + { if (pDevice == NULL) return CC.CreateNil(); // Create a list - ICCItem *pResult = CC.CreateLinkedList(); + ICCItem* pResult = CC.CreateLinkedList(); if (pResult->IsError()) return pResult; - CCLinkedList *pList = (CCLinkedList *)pResult; + CCLinkedList* pList = (CCLinkedList*)pResult; // List contains angle, radius, and optional z @@ -565,15 +566,15 @@ ICCItem *CDeviceClass::FindItemProperty (CItemCtx &Ctx, const CString &sName) // Done return pResult; - } + } else if (strEquals(sName, PROPERTY_POWER)) - { + { if (GetCategory() == itemcatReactor) return CTLispConvert::CreatePowerResultMW(GetPowerOutput(Ctx))->Reference(); else return CTLispConvert::CreatePowerResultMW(GetPowerRating(Ctx))->Reference(); - } + } else if (strEquals(sName, PROPERTY_POWER_OUTPUT)) return CreatePowerResult(GetPowerOutput(Ctx) * 100.0); @@ -587,6 +588,9 @@ ICCItem *CDeviceClass::FindItemProperty (CItemCtx &Ctx, const CString &sName) else if (strEquals(sName, PROPERTY_SLOT_ID)) return (pDevice ? CC.CreateString(pDevice->GetID()) : CC.CreateNil()); + else if (strEquals(sName, PROPERTY_TARGET_CRITERIA)) + return (pDevice ? (pDevice->GetWeaponTargetDefinition() ? CC.CreateString(pDevice->GetWeaponTargetDefinition()->GetTargetCriteriaString()) : CC.CreateNil()) : CC.CreateNil()); + else if (strEquals(sName, PROPERTY_TEMPERATURE)) { CSpaceObject *pSource = Ctx.GetSource(); diff --git a/Mammoth/TSE/TSE.vcxproj b/Mammoth/TSE/TSE.vcxproj index 0b86974c7..fc559e849 100644 --- a/Mammoth/TSE/TSE.vcxproj +++ b/Mammoth/TSE/TSE.vcxproj @@ -620,6 +620,7 @@ + Disabled Disabled diff --git a/Mammoth/TSE/TSE.vcxproj.filters b/Mammoth/TSE/TSE.vcxproj.filters index 5b97aa349..2a19c3ad7 100644 --- a/Mammoth/TSE/TSE.vcxproj.filters +++ b/Mammoth/TSE/TSE.vcxproj.filters @@ -1555,5 +1555,8 @@ Source Files\ShipAI + + Source Files\Items + \ No newline at end of file From 871755980d7c3d8bf6f635d5a4aa7c21e2ce37f1 Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Sat, 19 Dec 2020 02:03:03 -0500 Subject: [PATCH 04/32] Finish up remaining tasks for 'targetCriteria --- Mammoth/Include/TSEDeviceClassesImpl.h | 5 ++- Mammoth/Include/TSEDevices.h | 6 +-- Mammoth/TSE/CDeviceItem.cpp | 2 +- Mammoth/TSE/CInstalledDevice.cpp | 62 +++++++++++++++----------- Mammoth/TSE/CShip.cpp | 4 +- Mammoth/TSE/Devices.cpp | 3 ++ 6 files changed, 49 insertions(+), 33 deletions(-) diff --git a/Mammoth/Include/TSEDeviceClassesImpl.h b/Mammoth/Include/TSEDeviceClassesImpl.h index c0ddbc984..4df9e3d43 100644 --- a/Mammoth/Include/TSEDeviceClassesImpl.h +++ b/Mammoth/Include/TSEDeviceClassesImpl.h @@ -22,6 +22,9 @@ class CWeaponTargetDefinition Kernel::CString GetTargetCriteriaString () { return Kernel::CString(m_CriteriaString.c_str()); }; void SetCheckLineOfFire (bool bCheckLineOfFire) { m_bCheckLineOfFire = bCheckLineOfFire; }; void SetTargetCriteria (Kernel::CString sCriteria) { m_TargetCriteria.Init(sCriteria); m_CriteriaString = sCriteria; }; + + static std::unique_ptr ReadFromStream (SLoadCtx& Ctx); + void WriteToStream (IWriteStream* pStream) const; private: std::string m_CriteriaString = ""; CSpaceObjectCriteria m_TargetCriteria; @@ -45,7 +48,7 @@ class CAutoDefenseClass : public CDeviceClass virtual bool GetReferenceDamageType (CItemCtx &Ctx, const CItem &Ammo, DamageTypes *retiDamage, CString *retsReference) const override; virtual DWORD GetTargetTypes (const CDeviceItem &DeviceItem) const override; virtual bool IsAreaWeapon (const CDeviceItem &DeviceItem) const override; - virtual bool IsAutomatedWeapon (void) override { return true; } + virtual bool IsAutomatedWeapon (void) const override { return true; } virtual ALERROR OnDesignLoadComplete (SDesignLoadCtx &Ctx) override; virtual void Update (CInstalledDevice *pDevice, CSpaceObject *pSource, SDeviceUpdateCtx &Ctx) override; diff --git a/Mammoth/Include/TSEDevices.h b/Mammoth/Include/TSEDevices.h index 18d8412db..04e8509ea 100644 --- a/Mammoth/Include/TSEDevices.h +++ b/Mammoth/Include/TSEDevices.h @@ -338,7 +338,7 @@ class CDeviceClass virtual int GetWeaponVariantCount (const CDeviceItem &DeviceItem) const { return 0; } virtual bool IsAmmoWeapon (void) { return false; } virtual bool IsAreaWeapon (const CDeviceItem &DeviceItem) const { return false; } - virtual bool IsAutomatedWeapon (void) { return false; } + virtual bool IsAutomatedWeapon (void) const { return false; } virtual bool IsExternal (void) const { return (m_fExternal ? true : false); } virtual bool IsFirstVariantSelected(CSpaceObject *pSource, CInstalledDevice *pDevice) { return true; } virtual bool IsFuelCompatible (CItemCtx &Ctx, const CItem &FuelItem) { return false; } @@ -675,7 +675,7 @@ class CInstalledDevice CWeaponTargetDefinition *GetWeaponTargetDefinition (void) const { return (m_pWeaponTargetDefinition.get()); } bool HasLastShots (void) const { return (m_LastShotIDs.GetCount() > 0); } int IncCharges (CSpaceObject *pSource, int iChange); - bool IsAutomatedWeapon (void) { return m_pClass->IsAutomatedWeapon(); } + bool IsAutomatedWeapon (void) const { return m_pClass->IsAutomatedWeapon() || (IsSecondaryWeapon() && m_pWeaponTargetDefinition != nullptr); } bool IsFirstVariantSelected(CSpaceObject *pSource) { return (m_pClass ? m_pClass->IsFirstVariantSelected(pSource, this) : true); } bool IsFuelCompatible (CItemCtx &Ctx, const CItem &FuelItem) { return m_pClass->IsFuelCompatible(Ctx, FuelItem); } bool IsLastVariantSelected(CSpaceObject *pSource) { return (m_pClass ? m_pClass->IsLastVariantSelected(pSource, this) : true); } @@ -772,5 +772,5 @@ class CInstalledDevice DWORD m_fOnSegment:1 = false; // If TRUE, then device logically belongs to the segment specified by m_sID. DWORD m_fOnUsedLastAmmo:1 = false; // If TRUE, remember the send statusUsedLastAmmo when done firing - DWORD m_dwSpare2:5; + DWORD m_dwSpare2:3; }; diff --git a/Mammoth/TSE/CDeviceItem.cpp b/Mammoth/TSE/CDeviceItem.cpp index 667f381a6..fe5e3563e 100644 --- a/Mammoth/TSE/CDeviceItem.cpp +++ b/Mammoth/TSE/CDeviceItem.cpp @@ -63,7 +63,7 @@ CDeviceItem::ECalcTargetTypes CDeviceItem::CalcTargetType (void) const // If our options is "fire always" or "fire if selected" then our target is always the same // as the primary target. Same if we have auto fire enabled - else if ((dwLinkedFireOptions & CDeviceClass::lkfAlways) || (dwLinkedFireOptions & dwLinkedFireSelected) || Device.GetWeaponTargetDefinition()) + else if ((dwLinkedFireOptions & CDeviceClass::lkfAlways) || (dwLinkedFireOptions & dwLinkedFireSelected) || (pSource->IsPlayer() && Device.IsAutomatedWeapon())) { return calcControllerTarget; } diff --git a/Mammoth/TSE/CInstalledDevice.cpp b/Mammoth/TSE/CInstalledDevice.cpp index 73032461d..0fa494e94 100644 --- a/Mammoth/TSE/CInstalledDevice.cpp +++ b/Mammoth/TSE/CInstalledDevice.cpp @@ -579,7 +579,7 @@ void CInstalledDevice::PaintDevicePos (const SDeviceDesc &Device, CG32bitImage & } } -void CInstalledDevice::ReadFromStream (CSpaceObject &Source, SLoadCtx &Ctx) +void CInstalledDevice::ReadFromStream (CSpaceObject &Source, SLoadCtx& Ctx) // ReadFromStream // @@ -733,35 +733,36 @@ void CInstalledDevice::ReadFromStream (CSpaceObject &Source, SLoadCtx &Ctx) } Ctx.pStream->Read(dwLoad); - m_fOmniDirectional = ((dwLoad & 0x00000001) ? true : false); - m_f3DPosition = (((dwLoad & 0x00000002) ? true : false) && (Ctx.dwVersion >= 73)); - m_fFateSurvives = (((dwLoad & 0x00000004) ? true : false) && (Ctx.dwVersion >= 58)); - m_fOverdrive = ((dwLoad & 0x00000008) ? true : false); - m_fOptimized = ((dwLoad & 0x00000010) ? true : false); - m_fSecondaryWeapon = ((dwLoad & 0x00000020) ? true : false); - m_fFateDamaged = (((dwLoad & 0x00000040) ? true : false) && (Ctx.dwVersion >= 58)); - m_fEnabled = ((dwLoad & 0x00000080) ? true : false); - m_fWaiting = ((dwLoad & 0x00000100) ? true : false); - m_fTriggered = ((dwLoad & 0x00000200) ? true : false); - m_fRegenerating = ((dwLoad & 0x00000400) ? true : false); + m_fOmniDirectional = ((dwLoad & 0x00000001) ? true : false); + m_f3DPosition = (((dwLoad & 0x00000002) ? true : false) && (Ctx.dwVersion >= 73)); + m_fFateSurvives = (((dwLoad & 0x00000004) ? true : false) && (Ctx.dwVersion >= 58)); + m_fOverdrive = ((dwLoad & 0x00000008) ? true : false); + m_fOptimized = ((dwLoad & 0x00000010) ? true : false); + m_fSecondaryWeapon = ((dwLoad & 0x00000020) ? true : false); + m_fFateDamaged = (((dwLoad & 0x00000040) ? true : false) && (Ctx.dwVersion >= 58)); + m_fEnabled = ((dwLoad & 0x00000080) ? true : false); + m_fWaiting = ((dwLoad & 0x00000100) ? true : false); + m_fTriggered = ((dwLoad & 0x00000200) ? true : false); + m_fRegenerating = ((dwLoad & 0x00000400) ? true : false); m_fLastActivateSuccessful = ((dwLoad & 0x00000800) ? true : false); - m_fLinkedFireAlways = ((dwLoad & 0x00001000) ? true : false); - m_fLinkedFireTarget = ((dwLoad & 0x00002000) ? true : false); - m_fLinkedFireEnemy = ((dwLoad & 0x00004000) ? true : false); - m_fExternal = ((dwLoad & 0x00008000) ? true : false); - m_fDuplicate = ((dwLoad & 0x00010000) ? true : false); - m_fCannotBeEmpty = ((dwLoad & 0x00020000) ? true : false); - m_fFateDestroyed = ((dwLoad & 0x00040000) ? true : false); - m_fFateComponetized = ((dwLoad & 0x00080000) ? true : false); - bool bSlotEnhancements =((dwLoad & 0x00100000) ? true : false); + m_fLinkedFireAlways = ((dwLoad & 0x00001000) ? true : false); + m_fLinkedFireTarget = ((dwLoad & 0x00002000) ? true : false); + m_fLinkedFireEnemy = ((dwLoad & 0x00004000) ? true : false); + m_fExternal = ((dwLoad & 0x00008000) ? true : false); + m_fDuplicate = ((dwLoad & 0x00010000) ? true : false); + m_fCannotBeEmpty = ((dwLoad & 0x00020000) ? true : false); + m_fFateDestroyed = ((dwLoad & 0x00040000) ? true : false); + m_fFateComponetized = ((dwLoad & 0x00080000) ? true : false); + bool bSlotEnhancements = ((dwLoad & 0x00100000) ? true : false); m_fLinkedFireSelected = ((dwLoad & 0x00200000) ? true : false); - m_fLinkedFireNever = ((dwLoad & 0x00400000) ? true : false); + m_fLinkedFireNever = ((dwLoad & 0x00400000) ? true : false); m_fLinkedFireSelectedVariants = ((dwLoad & 0x00800000) ? true : false); - m_fCycleFire = ((dwLoad & 0x01000000) ? true : false); - m_fCanTargetMissiles = ((dwLoad & 0x02000000) ? true : false); - m_fOnSegment = ((dwLoad & 0x04000000) ? true : false); - m_fOnUsedLastAmmo = ((dwLoad & 0x08000000) ? true : false); + m_fCycleFire = ((dwLoad & 0x01000000) ? true : false); + m_fCanTargetMissiles = ((dwLoad & 0x02000000) ? true : false); + m_fOnSegment = ((dwLoad & 0x04000000) ? true : false); + m_fOnUsedLastAmmo = ((dwLoad & 0x08000000) ? true : false); + bool bLoadWeaponTargetDefinition = ((dwLoad & 0x10000000) ? true : false); // Previous versions did not save this flag @@ -810,6 +811,9 @@ void CInstalledDevice::ReadFromStream (CSpaceObject &Source, SLoadCtx &Ctx) if (iSlotBonus != 0) m_SlotEnhancements.InsertHPBonus(iSlotBonus); + + if (bLoadWeaponTargetDefinition) + m_pWeaponTargetDefinition = CWeaponTargetDefinition::ReadFromStream(Ctx); } int CInstalledDevice::IncCharges (CSpaceObject *pSource, int iChange) @@ -1337,6 +1341,7 @@ void CInstalledDevice::WriteToStream (IWriteStream *pStream) // // CItemEnhancementStack // CEnhancementDesc +// CWeaponTargetDefinition { DWORD dwSave; @@ -1406,10 +1411,15 @@ void CInstalledDevice::WriteToStream (IWriteStream *pStream) dwSave |= (m_fCanTargetMissiles ? 0x02000000 : 0); dwSave |= (m_fOnSegment ? 0x04000000 : 0); dwSave |= (m_fOnUsedLastAmmo ? 0x08000000 : 0); + dwSave |= ((m_pWeaponTargetDefinition != nullptr) ? 0x10000000 : 0); pStream->Write(dwSave); CItemEnhancementStack::WriteToStream(m_pEnhancements, pStream); if (!m_SlotEnhancements.IsEmpty()) m_SlotEnhancements.WriteToStream(*pStream); + + if (m_pWeaponTargetDefinition) + m_pWeaponTargetDefinition->WriteToStream(pStream); + } diff --git a/Mammoth/TSE/CShip.cpp b/Mammoth/TSE/CShip.cpp index 3b9cca31b..a830b0e09 100644 --- a/Mammoth/TSE/CShip.cpp +++ b/Mammoth/TSE/CShip.cpp @@ -7985,7 +7985,7 @@ void CShip::SetWeaponTriggered (DeviceNames iDev, bool bTriggered) // is linked to the primary device, then activate it. if (&Device == pPrimaryDevice - || (Device.IsLinkedFire(iCat, pPrimaryDevice))) + || (Device.IsLinkedFire(iCat, pPrimaryDevice) && !Device.IsAutomatedWeapon())) Device.SetTriggered(bTriggered); } } @@ -8010,7 +8010,7 @@ void CShip::SetWeaponTriggered (CInstalledDevice *pWeapon, bool bTriggered) // is linked to the primary device, then activate it. CInstalledDevice* pDevice = &Device; if (pDevice == pWeapon - || (Device.IsLinkedFire(iCat, pWeapon))) + || (Device.IsLinkedFire(iCat, pWeapon) && !Device.IsAutomatedWeapon())) Device.SetTriggered(bTriggered); } } diff --git a/Mammoth/TSE/Devices.cpp b/Mammoth/TSE/Devices.cpp index 8f50dfe22..3724647f0 100644 --- a/Mammoth/TSE/Devices.cpp +++ b/Mammoth/TSE/Devices.cpp @@ -126,6 +126,9 @@ void CDeviceClass::AccumulateAttributes (const CDeviceItem &DeviceItem, const CI DWORD dwOptions = DeviceItem.GetLinkedFireOptions(); if ((dwOptions != 0) && (dwOptions != CDeviceClass::lkfNever)) retList->Insert(SDisplayAttribute(attribPositive, CONSTLIT("linked-fire"))); + + if (IsAutomatedWeapon()) + retList->Insert(SDisplayAttribute(attribPositive, CONSTLIT("automatic"))); } // Let our subclasses add their own attributes From 56da1d177edc229ab78c02e3d0c4b86b859812c1 Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Sun, 27 Dec 2020 22:58:20 -0500 Subject: [PATCH 05/32] Add file CWeaponTargetDefinition --- Mammoth/TSE/CWeaponTargetDefinition.cpp | 159 ++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 Mammoth/TSE/CWeaponTargetDefinition.cpp diff --git a/Mammoth/TSE/CWeaponTargetDefinition.cpp b/Mammoth/TSE/CWeaponTargetDefinition.cpp new file mode 100644 index 000000000..e51014bbc --- /dev/null +++ b/Mammoth/TSE/CWeaponTargetDefinition.cpp @@ -0,0 +1,159 @@ +#include "PreComp.h" + +bool CWeaponTargetDefinition::MatchesTarget(CSpaceObject* pSource, CSpaceObject* pTarget) const + +// MatchesTarget +// +// Returns True if the target matches the criteria + { + CSpaceObjectCriteria::SCtx Ctx(pSource, m_TargetCriteria); + return pTarget ? pTarget->MatchesCriteria(Ctx, m_TargetCriteria) : false; + } + +CSpaceObject* CWeaponTargetDefinition::FindTarget(CWeaponClass* pWeapon, CInstalledDevice* pDevice, CSpaceObject* pSource, CItemCtx& ItemCtx) const + +// FindTarget +// +// Returns an appropriate target (or NULL). + + { + // Look for a target + + bool isOmniDirectional = false; + int iMinFireArc, iMaxFireArc; + auto rotationType = pWeapon->GetRotationType(ItemCtx.GetDeviceItem(), &iMinFireArc, &iMaxFireArc); + + CSpaceObject* pBestTarget = NULL; + CSystem* pSystem = pSource->GetSystem(); + CVector vSourcePos = pDevice->GetPos(pSource); + + // Find out whether our autoDefenseDevice is omnidirectional, + // directional or has a fixed angle + + if (pDevice->IsOmniDirectional() || rotationType == CDeviceRotationDesc::rotOmnidirectional) + isOmniDirectional = true; + + // If not omnidirectional, check directional. If not, then + // our device points in one direction + + else if (!((pDevice->IsDirectional() || rotationType == CDeviceRotationDesc::rotSwivel))) + { + iMinFireArc = AngleMod((pDevice ? pDevice->GetRotation() : 0) + + AngleMiddle(iMinFireArc, iMaxFireArc)); + iMaxFireArc = iMinFireArc; + } + + // Calculate min/max fire arcs given the object's rotation + + iMinFireArc = (pSource->GetRotation() + iMinFireArc) % 360; + iMaxFireArc = (pSource->GetRotation() + iMaxFireArc) % 360; + + // Compute the range + + Metric rBestDist2; + if (m_TargetCriteria.MatchesMaxRadius() < g_InfiniteDistance) + rBestDist2 = (m_TargetCriteria.MatchesMaxRadius() * m_TargetCriteria.MatchesMaxRadius()); + else + rBestDist2 = pDevice->GetMaxRange(ItemCtx) * pDevice->GetMaxRange(ItemCtx); + + // Now look for the nearest object + + CSpaceObjectCriteria::SCtx Ctx(pSource, m_TargetCriteria); + for (int i = 0; i < pSystem->GetObjectCount(); i++) + { + CSpaceObject* pObj = pSystem->GetObject(i); + Metric rDistance2; + if (pObj + && pObj->MatchesCriteriaCategory(Ctx, m_TargetCriteria) + && ((rDistance2 = (pObj->GetPos() - vSourcePos).Length2()) < rBestDist2) + && pObj->MatchesCriteria(Ctx, m_TargetCriteria) + && !pObj->IsIntangible() + && pObj != pSource + && (isOmniDirectional + || AngleInArc(VectorToPolar((pObj->GetPos() - vSourcePos)), iMinFireArc, iMaxFireArc))) + { + pBestTarget = pObj; + rBestDist2 = rDistance2; + } + } + + return pBestTarget; + } + +bool CWeaponTargetDefinition::AimAndFire(CWeaponClass* pWeapon, CInstalledDevice* pDevice, CSpaceObject* pSource, CDeviceClass::SDeviceUpdateCtx& Ctx) const + { + // If we're docked with a station, then we do not fire. + CItemCtx ItemCtx(pSource, pDevice); + + if (m_bCheckLineOfFire && pSource->GetDockedObj()) + return false; + + // Look for a target; if none, then skip. + + CSpaceObject* pTarget = FindTarget(pWeapon, pDevice, pSource, ItemCtx); + if (pTarget == NULL) + return false; + + // Shoot at target + + int iFireAngle; + if (!pWeapon->CalcFireSolution(*pDevice, *pTarget, &iFireAngle)) + return false; + + // If friendlies are in the way, don't shoot + + if (m_bCheckLineOfFire + && !pSource->IsLineOfFireClear(pDevice, pTarget, iFireAngle, pSource->GetDistance(pTarget))) + return false; + + // Since we're using this as a target, set the destroy notify flag + // (Normally beams don't notify, so we need to override this). + + pTarget->SetDestructionNotify(); + + // Fire + + CDeviceClass::SActivateCtx ActivateCtx(pTarget, Ctx.TargetList, iFireAngle); + CWeaponFireDesc* pShot = pWeapon->GetWeaponFireDesc(ItemCtx); + + bool bActivateResult = pWeapon->Activate(*pDevice, ActivateCtx); + + Ctx.bConsumedItems = ActivateCtx.bConsumedItems; + return bActivateResult; + } + +std::unique_ptr CWeaponTargetDefinition::ReadFromStream(SLoadCtx& Ctx) + +// ReadFromStream +// +// Reads from stream + + { + std::unique_ptr weaponTargetDefinition = std::make_unique(); + + Kernel::CString sCriteriaString; + sCriteriaString.ReadFromStream(Ctx.pStream); + weaponTargetDefinition->SetTargetCriteria(sCriteriaString); + + DWORD dwLoad; + Ctx.pStream->Read(dwLoad); + weaponTargetDefinition->SetCheckLineOfFire((dwLoad & 0x00000001) ? true : false); + + return std::move(weaponTargetDefinition); + } + +void CWeaponTargetDefinition::WriteToStream(IWriteStream *pStream) const + +// WriteToStream +// +// CString CriteriaString +// DWORD flags + + { + int iStrLength = m_CriteriaString.size(); + Kernel::CString(m_CriteriaString.c_str()).WriteToStream(pStream); + + DWORD dwSave = 0; + dwSave |= (m_bCheckLineOfFire ? 0x00000001 : 0); + pStream->Write(dwSave); + } From b7bb162185c9df00f16de1c15e0c0826a57ebf83 Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Sat, 19 Dec 2020 22:27:35 -0500 Subject: [PATCH 06/32] Add NPC support for targetCriteria --- Mammoth/Include/TSEDeviceClassesImpl.h | 1 + Mammoth/TSE/CAIBehaviorCtx.cpp | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/Mammoth/Include/TSEDeviceClassesImpl.h b/Mammoth/Include/TSEDeviceClassesImpl.h index 4df9e3d43..1462f7992 100644 --- a/Mammoth/Include/TSEDeviceClassesImpl.h +++ b/Mammoth/Include/TSEDeviceClassesImpl.h @@ -15,6 +15,7 @@ class CWeaponTargetDefinition public: class CWeaponTargetDefinition () { }; class CWeaponTargetDefinition (Kernel::CString sCriteria, bool bCheckLineOfFire = false) : m_bCheckLineOfFire(bCheckLineOfFire), m_CriteriaString(sCriteria) { m_TargetCriteria.Init(sCriteria); }; + bool MatchesTarget (CSpaceObject* pSource, CSpaceObject* pTarget) const; CSpaceObject* FindTarget (CWeaponClass* pWeapon, CInstalledDevice* pDevice, CSpaceObject* pSource, CItemCtx& ItemCtx) const; bool AimAndFire (CWeaponClass* pWeapon, CInstalledDevice* pDevice, CSpaceObject* pSource, CDeviceClass::SDeviceUpdateCtx& Ctx) const; bool GetCheckLineOfFire () { return m_bCheckLineOfFire; }; diff --git a/Mammoth/TSE/CAIBehaviorCtx.cpp b/Mammoth/TSE/CAIBehaviorCtx.cpp index ecd8b67d7..3eee075f5 100644 --- a/Mammoth/TSE/CAIBehaviorCtx.cpp +++ b/Mammoth/TSE/CAIBehaviorCtx.cpp @@ -748,6 +748,14 @@ int CAIBehaviorCtx::CalcWeaponScore (CShip *pShip, CSpaceObject *pTarget, CInsta if (pWeapon->GetTimeUntilReady() >= (avoidAnyNonReadyWeapons ? 1 : 15)) return 1; + // If the target criteria for this weapon excludes the target, then + // we score 1. + if (pWeapon->GetWeaponTargetDefinition()) + { + if (!pWeapon->GetWeaponTargetDefinition()->MatchesTarget(pShip, pTarget)) + return 1; + } + // Get the item for the selected variant (either the weapon // or the ammo) From 9976d5202198ba377b818af4e239f8d95df10b61 Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Thu, 31 Dec 2020 02:28:23 -0500 Subject: [PATCH 07/32] Oops...forgot to require secondaryWeapon for gun to autofire on player --- Mammoth/TSE/CWeaponClass.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mammoth/TSE/CWeaponClass.cpp b/Mammoth/TSE/CWeaponClass.cpp index e4f6f544a..9be2a5f9d 100644 --- a/Mammoth/TSE/CWeaponClass.cpp +++ b/Mammoth/TSE/CWeaponClass.cpp @@ -5329,7 +5329,7 @@ void CWeaponClass::Update (CInstalledDevice *pDevice, CSpaceObject *pSource, SDe && (!pDevice->IsTriggered() || pDevice->GetTimeUntilReady() > 1)) pDevice->SetLastShotCount(0); - if (pDevice->GetWeaponTargetDefinition() && pSource->IsPlayer()) + if (pDevice->GetWeaponTargetDefinition() && pSource->IsPlayer() && pDevice->IsSecondaryWeapon()) { // If the weapon is not ready, do not autofire. From ed5d2b8cddbf0321800eb40fc3daf7f3f5e12883 Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Thu, 31 Dec 2020 23:07:13 -0500 Subject: [PATCH 08/32] Fix bug with repeating weapons autofiring --- Mammoth/TSE/CWeaponClass.cpp | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/Mammoth/TSE/CWeaponClass.cpp b/Mammoth/TSE/CWeaponClass.cpp index e4f6f544a..420f50228 100644 --- a/Mammoth/TSE/CWeaponClass.cpp +++ b/Mammoth/TSE/CWeaponClass.cpp @@ -5260,6 +5260,22 @@ void CWeaponClass::Update (CInstalledDevice *pDevice, CSpaceObject *pSource, SDe if (!pDevice->IsEnabled()) return; + if (pDevice->GetWeaponTargetDefinition() && pSource->IsPlayer() && pDevice->IsSecondaryWeapon()) + { + // If the weapon is not ready, do not autofire. + // If the ship is disarmed or paralyzed, then we also do not autofire. + + if (!(!pDevice->IsReady() || pSource->GetCondition(ECondition::paralyzed) + || pSource->GetCondition(ECondition::disarmed))) + { + bool bActivateResult = pDevice->GetWeaponTargetDefinition()->AimAndFire(this, pDevice, pSource, Ctx); + if (bActivateResult) + { + pDevice->SetTimeUntilReady(CalcActivateDelay(ItemCtx)); + } + } + } + // See if we continue to fire DWORD dwContinuous = GetContinuousFire(pDevice); @@ -5328,25 +5344,6 @@ void CWeaponClass::Update (CInstalledDevice *pDevice, CSpaceObject *pSource, SDe else if (pDevice->HasLastShots() && (!pDevice->IsTriggered() || pDevice->GetTimeUntilReady() > 1)) pDevice->SetLastShotCount(0); - - if (pDevice->GetWeaponTargetDefinition() && pSource->IsPlayer()) - { - // If the weapon is not ready, do not autofire. - - if (!pDevice->IsReady()) - return; - - // If the ship is disarmed or paralyzed, then we do not fire. - - if (pSource->GetCondition(ECondition::paralyzed) - || pSource->GetCondition(ECondition::disarmed)) - return; - bool bActivateResult = pDevice->GetWeaponTargetDefinition()->AimAndFire(this, pDevice, pSource, Ctx); - if (bActivateResult) - { - pDevice->SetTimeUntilReady(CalcActivateDelay(ItemCtx)); - } - } DEBUG_CATCH } From a1d055a882a8ffc95f86d6b2d0459b6f7987e6f3 Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Fri, 1 Jan 2021 21:22:02 -0500 Subject: [PATCH 09/32] Make targetCriteria actually work for npcs --- Mammoth/TSE/CAIBehaviorCtx.cpp | 8 -------- Mammoth/TSE/CWeaponClass.cpp | 10 ++++++++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Mammoth/TSE/CAIBehaviorCtx.cpp b/Mammoth/TSE/CAIBehaviorCtx.cpp index 3eee075f5..ecd8b67d7 100644 --- a/Mammoth/TSE/CAIBehaviorCtx.cpp +++ b/Mammoth/TSE/CAIBehaviorCtx.cpp @@ -748,14 +748,6 @@ int CAIBehaviorCtx::CalcWeaponScore (CShip *pShip, CSpaceObject *pTarget, CInsta if (pWeapon->GetTimeUntilReady() >= (avoidAnyNonReadyWeapons ? 1 : 15)) return 1; - // If the target criteria for this weapon excludes the target, then - // we score 1. - if (pWeapon->GetWeaponTargetDefinition()) - { - if (!pWeapon->GetWeaponTargetDefinition()->MatchesTarget(pShip, pTarget)) - return 1; - } - // Get the item for the selected variant (either the weapon // or the ammo) diff --git a/Mammoth/TSE/CWeaponClass.cpp b/Mammoth/TSE/CWeaponClass.cpp index 9be2a5f9d..52c092d09 100644 --- a/Mammoth/TSE/CWeaponClass.cpp +++ b/Mammoth/TSE/CWeaponClass.cpp @@ -3772,6 +3772,16 @@ int CWeaponClass::GetWeaponEffectiveness (const CDeviceItem &DeviceItem, CSpaceO return -100; } + // If we have a weapon target definition, the target must fit this definition. + if (pTarget && DeviceItem.GetInstalledDevice()) + { + auto pWeaponTargetDefinition = DeviceItem.GetInstalledDevice()->GetWeaponTargetDefinition(); + if ((pWeaponTargetDefinition && !pWeaponTargetDefinition->MatchesTarget(pSource, pTarget))) + { + return -100; + } + } + // Check our state if (const CInstalledDevice *pDevice = DeviceItem.GetInstalledDevice()) From 88b6a6ae0c327ec3b50a206c0ba2f8eaf748ebaf Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Thu, 31 Dec 2020 23:07:13 -0500 Subject: [PATCH 10/32] Fix bug with repeating weapons autofiring --- Mammoth/TSE/CWeaponClass.cpp | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/Mammoth/TSE/CWeaponClass.cpp b/Mammoth/TSE/CWeaponClass.cpp index 52c092d09..ebfa7cd2a 100644 --- a/Mammoth/TSE/CWeaponClass.cpp +++ b/Mammoth/TSE/CWeaponClass.cpp @@ -5270,6 +5270,22 @@ void CWeaponClass::Update (CInstalledDevice *pDevice, CSpaceObject *pSource, SDe if (!pDevice->IsEnabled()) return; + if (pDevice->GetWeaponTargetDefinition() && pSource->IsPlayer() && pDevice->IsSecondaryWeapon()) + { + // If the weapon is not ready, do not autofire. + // If the ship is disarmed or paralyzed, then we also do not autofire. + + if (!(!pDevice->IsReady() || pSource->GetCondition(ECondition::paralyzed) + || pSource->GetCondition(ECondition::disarmed))) + { + bool bActivateResult = pDevice->GetWeaponTargetDefinition()->AimAndFire(this, pDevice, pSource, Ctx); + if (bActivateResult) + { + pDevice->SetTimeUntilReady(CalcActivateDelay(ItemCtx)); + } + } + } + // See if we continue to fire DWORD dwContinuous = GetContinuousFire(pDevice); @@ -5338,25 +5354,6 @@ void CWeaponClass::Update (CInstalledDevice *pDevice, CSpaceObject *pSource, SDe else if (pDevice->HasLastShots() && (!pDevice->IsTriggered() || pDevice->GetTimeUntilReady() > 1)) pDevice->SetLastShotCount(0); - - if (pDevice->GetWeaponTargetDefinition() && pSource->IsPlayer() && pDevice->IsSecondaryWeapon()) - { - // If the weapon is not ready, do not autofire. - - if (!pDevice->IsReady()) - return; - - // If the ship is disarmed or paralyzed, then we do not fire. - - if (pSource->GetCondition(ECondition::paralyzed) - || pSource->GetCondition(ECondition::disarmed)) - return; - bool bActivateResult = pDevice->GetWeaponTargetDefinition()->AimAndFire(this, pDevice, pSource, Ctx); - if (bActivateResult) - { - pDevice->SetTimeUntilReady(CalcActivateDelay(ItemCtx)); - } - } DEBUG_CATCH } From 8d471d29d316a458b79f8b476cd2b3f57dd3765e Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Fri, 1 Jan 2021 21:22:02 -0500 Subject: [PATCH 11/32] Make targetCriteria actually work for npcs --- Mammoth/TSE/CWeaponClass.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Mammoth/TSE/CWeaponClass.cpp b/Mammoth/TSE/CWeaponClass.cpp index 420f50228..ebfa7cd2a 100644 --- a/Mammoth/TSE/CWeaponClass.cpp +++ b/Mammoth/TSE/CWeaponClass.cpp @@ -3772,6 +3772,16 @@ int CWeaponClass::GetWeaponEffectiveness (const CDeviceItem &DeviceItem, CSpaceO return -100; } + // If we have a weapon target definition, the target must fit this definition. + if (pTarget && DeviceItem.GetInstalledDevice()) + { + auto pWeaponTargetDefinition = DeviceItem.GetInstalledDevice()->GetWeaponTargetDefinition(); + if ((pWeaponTargetDefinition && !pWeaponTargetDefinition->MatchesTarget(pSource, pTarget))) + { + return -100; + } + } + // Check our state if (const CInstalledDevice *pDevice = DeviceItem.GetInstalledDevice()) From 5b71bce1ac3ad7ec6593d2d3e38c745f20c2df3f Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Thu, 7 Jan 2021 02:43:33 -0500 Subject: [PATCH 12/32] Implement new aiming code for UseAllPrimaryWeapons --- Mammoth/TSE/AIManeuvers.cpp | 43 +++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/Mammoth/TSE/AIManeuvers.cpp b/Mammoth/TSE/AIManeuvers.cpp index b5fafd1c5..0d7f8f148 100644 --- a/Mammoth/TSE/AIManeuvers.cpp +++ b/Mammoth/TSE/AIManeuvers.cpp @@ -1557,10 +1557,12 @@ void CAIBehaviorCtx::ImplementFireWeaponOnTarget (CShip *pShip, } int iWeaponIndex = 0; + std::vector> aimAngles; for (auto& pWeaponToFire : pWeaponsToFire) { // See if the chosen weapon can hit the target Metric rWeaponRange = rWeaponRanges[iWeaponIndex]; + auto weaponDeviceItem = pWeaponToFire->GetDeviceItem(); int iAimAngle = pShip->GetRotation(); int iFireAngle = -1; @@ -1572,6 +1574,7 @@ void CAIBehaviorCtx::ImplementFireWeaponOnTarget (CShip *pShip, &iFireAngle, &iFacingAngle); bool bAimError = false; + int iAngleToTarget = iAimAngle; // iAimAngle is the direction that we should fire in order to hit // the target. @@ -1679,12 +1682,48 @@ void CAIBehaviorCtx::ImplementFireWeaponOnTarget (CShip *pShip, } // Turn to aim, even if weapon is already approximately aligned + // If 'FireAllPrimaryWeapons' is set, though, we should ignore guns that have large fire arcs + // and that are already approximately aligned (e.g. we can hit the target with those guns) + // We can do this by assigning a weight to each gun; guns that have larger firing arcs and that can hit targets have + // smaller weights, and guns that are more closely aligned with the target have higher weights. + // The former can be done by assigning weight penalties to guns that have the target in the firing arc based + // on how far we have to turn before they are no longer in the firing arc. + // Damaged guns or those where GetWeaponEffectiveness returns a negative value should be ignored entirely, along with omni guns. + // TODO(heliogenesis): We can greatly improve performance with a std::map, so we only do the calculation if the aim angle in question + // is NOT in the std::map (aim angle is the key) and take the highest possible priority we can get (priority is the value) + if (UsesAllPrimaryWeapons()) + { + int iMinFireArc = pWeaponToFire->GetMinFireArc(); + int iMaxFireArc = pWeaponToFire->GetMaxFireArc(); + int iDistanceToFireBoundary = min(min(abs(iMinFireArc - iAngleToTarget), abs(iMaxFireArc - iAngleToTarget)), min(abs((iMinFireArc - 360) - iAngleToTarget), abs((360 + iMaxFireArc) - iAngleToTarget))); + bool bIgnoreThisGun = (weaponDeviceItem.GetWeaponEffectiveness(pTarget) < 0) || pWeaponToFire->IsDamaged() || pWeaponToFire->IsOmniDirectional(); + if (!bIgnoreThisGun) + aimAngles.push_back(std::make_pair(iDistanceToFireBoundary, iFacingAngle)); + } + else + { + if (retiFireDir) + *retiFireDir = iFacingAngle; + } - if (retiFireDir) - *retiFireDir = iFacingAngle; iWeaponIndex++; } + if (UsesAllPrimaryWeapons()) + { + int iLowestScore = 360; + int iFacingAngle = -1; + for (const auto& aimAngle : aimAngles) + { + if (aimAngle.first < iLowestScore) + { + iLowestScore = aimAngle.first; + iFacingAngle = aimAngle.second; + } + } + if (retiFireDir && iFacingAngle >= 0) + *retiFireDir = iFacingAngle; + } DEBUG_CATCH From 0df3c1780e519d5387818a274f4cef96f9ce888a Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Mon, 14 Dec 2020 01:48:57 -0500 Subject: [PATCH 13/32] WIP commit for charging guns --- Mammoth/Include/TSEDevices.h | 1 + Mammoth/Include/TSEWeaponClassImpl.h | 9 ++- Mammoth/Include/TSEWeaponFireDesc.h | 2 + Mammoth/TSE/CCExtensions.cpp | 1 + Mammoth/TSE/CWeaponClass.cpp | 87 +++++++++++++++++++++++----- Mammoth/TSE/CWeaponFireDesc.cpp | 2 + 6 files changed, 84 insertions(+), 18 deletions(-) diff --git a/Mammoth/Include/TSEDevices.h b/Mammoth/Include/TSEDevices.h index e813ef3ee..6c10daf04 100644 --- a/Mammoth/Include/TSEDevices.h +++ b/Mammoth/Include/TSEDevices.h @@ -263,6 +263,7 @@ class CDeviceClass // Used internally int iRepeatingCount = 0; + int iChargeFrame = 0; // Status results diff --git a/Mammoth/Include/TSEWeaponClassImpl.h b/Mammoth/Include/TSEWeaponClassImpl.h index 5c2f1548e..3f8e05d34 100644 --- a/Mammoth/Include/TSEWeaponClassImpl.h +++ b/Mammoth/Include/TSEWeaponClassImpl.h @@ -262,9 +262,10 @@ class CWeaponClass : public CDeviceClass int iFireAngle, int iRepeatingCount, SShotFireResult &retResult); - bool FireWeapon (CInstalledDevice &Device, - const CWeaponFireDesc &ShotDesc, - SActivateCtx &ActivateCtx); + bool FireWeapon (CInstalledDevice &Device, + const CWeaponFireDesc &ShotDesc, + SActivateCtx &ActivateCtx, + const bool IsCharging = false); void FireWeaponShot (CSpaceObject *pSource, CInstalledDevice *pDevice, const CWeaponFireDesc &ShotDesc, @@ -274,6 +275,7 @@ class CWeaponClass : public CDeviceClass CSpaceObject *pTarget, int iRepeatingCount, int iShotNumber); + int GetChargeTime(const CWeaponFireDesc& Shot) const; const CConfigurationDesc &GetConfiguration (const CWeaponFireDesc &ShotDesc) const; int GetContinuous (const CWeaponFireDesc &Shot) const; int GetContinuousFireDelay (const CWeaponFireDesc &Shot) const; @@ -334,6 +336,7 @@ class CWeaponClass : public CDeviceClass int m_iCounterUpdate; // Inc/dec value per update int m_iCounterActivate; // Inc/dec value per shot int m_iCounterPerShot; // How much to increment the ship's counter by per shot + int m_iChargeTime; // Charge time before firing bool m_bTargetStationsOnly; // Do not target ships diff --git a/Mammoth/Include/TSEWeaponFireDesc.h b/Mammoth/Include/TSEWeaponFireDesc.h index 09816adf0..a6e3cbdc3 100644 --- a/Mammoth/Include/TSEWeaponFireDesc.h +++ b/Mammoth/Include/TSEWeaponFireDesc.h @@ -710,6 +710,7 @@ class CWeaponFireDesc int GetAveLifetime (void) const { return m_Lifetime.GetAveValue(); } Metric GetAveParticleCount (void) const; Metric GetAveSpeed (void) const { return 0.5 * (GetRatedSpeed() + m_rMaxMissileSpeed); } + int GetChargeTime (void) const { return m_iChargeTime; } int GetContinuous (void) const { return m_iContinuous; } int GetContinuousFireDelay (void) const { return (m_iContinuous != -1 ? m_iContinuousFireDelay : -1); } const DamageDesc &GetDamage (void) const { return m_Damage; } @@ -823,6 +824,7 @@ class CWeaponFireDesc CConfigurationDesc m_Configuration; // Configuration (empty = default) int m_iContinuous = -1; // repeat for this number of frames (-1 = default) int m_iContinuousFireDelay = -1; // Ticks between continuous fire shots (-1 = default) + int m_iChargeTime = -1; // Ticks before firing (-1 = default) int m_iFireRate = -1; // Ticks between shots (-1 = default to weapon class) int m_iPowerUse = -1; // Power use in 1/10th MWs (-1 = default to weapon class) int m_iIdlePowerUse = -1; // Power use while idle (-1 = default to weapon class) diff --git a/Mammoth/TSE/CCExtensions.cpp b/Mammoth/TSE/CCExtensions.cpp index 83019d36e..d8297f5f8 100644 --- a/Mammoth/TSE/CCExtensions.cpp +++ b/Mammoth/TSE/CCExtensions.cpp @@ -887,6 +887,7 @@ static PRIMITIVEPROCDEF g_Extensions[] = " 'balanceDamage\n" " 'balanceCost\n" " 'balanceExcludeCost\n" + " 'chargeTime\n" " 'damage Average damage per 180 ticks\n" " 'damagePerProjectile\n" " 'damageWMD180 Average WMD damage per 180 ticks\n" diff --git a/Mammoth/TSE/CWeaponClass.cpp b/Mammoth/TSE/CWeaponClass.cpp index af153b983..e26cc64f3 100644 --- a/Mammoth/TSE/CWeaponClass.cpp +++ b/Mammoth/TSE/CWeaponClass.cpp @@ -15,6 +15,7 @@ #define ANGLE_ATTRIB CONSTLIT("angle") #define BURST_TRACKS_TARGETS_ATTRIB CONSTLIT("burstTracksTargets") #define CHARGES_ATTRIB CONSTLIT("charges") +#define CHARGE_TIME_ATTRIB CONSTLIT("chargeTime") #define CONTINUOUS_CONSUME_PERSHOT_ATTRIB CONSTLIT("consumeAmmoPerRepeatingShot") #define COOLING_RATE_ATTRIB CONSTLIT("coolingRate") #define COUNTER_ATTRIB CONSTLIT("counter") @@ -55,6 +56,7 @@ #define FIELD_AVERAGE_DAMAGE CONSTLIT("averageDamage") // Average damage (1000x hp) #define FIELD_BALANCE CONSTLIT("balance") #define FIELD_BALANCE_DAMAGE CONSTLIT("balanceDamage") // Computed damage (per 180 ticks) based on balance +#define FIELD_CHARGE_TIME CONSTLIT("chargeTime") #define FIELD_CONFIGURATION CONSTLIT("configuration") #define FIELD_DAMAGE_180 CONSTLIT("damage") // HP damage per 180 ticks #define FIELD_DAMAGE_TYPE CONSTLIT("damageType") @@ -78,6 +80,7 @@ #define PROPERTY_BALANCE_DAMAGE CONSTLIT("balanceDamage") #define PROPERTY_BALANCE_COST CONSTLIT("balanceCost") #define PROPERTY_BALANCE_EXCLUDE_COST CONSTLIT("balanceExcludeCost") +#define PROPERTY_CHARGE_TIME CONSTLIT("chargeTime") #define PROPERTY_CAN_FIRE_WHEN_BLIND CONSTLIT("canFireWhenBlind") #define PROPERTY_DAMAGE_180 CONSTLIT("damage") // HP damage per 180 ticks #define PROPERTY_DAMAGE_PER_PROJECTILE CONSTLIT("damagePerProjectile") @@ -237,6 +240,7 @@ bool CWeaponClass::Activate (CInstalledDevice &Device, SActivateCtx &ActivateCtx const CWeaponFireDesc *pShotDesc = GetWeaponFireDesc(Device); ActivateCtx.iRepeatingCount = 0; + ActivateCtx.iChargeFrame = 0; ActivateCtx.bConsumedItems = false; // If not enabled, no firing @@ -252,9 +256,9 @@ bool CWeaponClass::Activate (CInstalledDevice &Device, SActivateCtx &ActivateCtx if (!m_bCanFireWhenBlind && SourceObj.IsBlind()) ActivateCtx.pTarget = NULL; - // Fire the weapon + // Fire the weapon if it isn't a charging weapon - bool bSuccess = FireWeapon(Device, *pShotDesc, ActivateCtx); + bool bSuccess = GetChargeTime(*pShotDesc) > 0 ? true : FireWeapon(Device, *pShotDesc, ActivateCtx); // If firing the weapon destroyed the ship, then we bail out @@ -265,6 +269,19 @@ bool CWeaponClass::Activate (CInstalledDevice &Device, SActivateCtx &ActivateCtx Device.SetLastActivateSuccessful(bSuccess); + // If we have nonzero charge time then set continuous fire device data + // We set to -1 because we skip the first Update after the call + // to Activate (since it happens on the same tick) + // Note that we can't combine this with the if block later on because + // bSuccess is false here (we technically didn't fire any shots by charging) + + if (GetChargeTime(*pShotDesc) > 0) + { + SetContinuousFire(&Device, CONTINUOUS_START); + // Return true so we consume power + return true; + } + // If we did not succeed, then we're done if (!bSuccess) @@ -1312,7 +1329,7 @@ bool CWeaponClass::CalcSingleTarget (CInstalledDevice &Device, // For repeating weapons, we need the target stored in the device - if (ActivateCtx.iRepeatingCount != 0) + if (ActivateCtx.iRepeatingCount != 0 || ActivateCtx.iChargeFrame != 0) { CDeviceItem DeviceItem = Device.GetDeviceItem(); CSpaceObject &Source = Device.GetSourceOrThrow(); @@ -1898,6 +1915,7 @@ ALERROR CWeaponClass::CreateFromXML (SDesignLoadCtx &Ctx, CXMLElement *pDesc, CI pWeapon->m_iFailureChance = pDesc->GetAttributeInteger(FAILURE_CHANCE_ATTRIB); pWeapon->m_iMinFireArc = AngleMod(pDesc->GetAttributeInteger(MIN_FIRE_ARC_ATTRIB)); pWeapon->m_iMaxFireArc = AngleMod(pDesc->GetAttributeInteger(MAX_FIRE_ARC_ATTRIB)); + pWeapon->m_iChargeTime = pDesc->GetAttributeIntegerBounded(CHARGE_TIME_ATTRIB, 0, -1, 0); pWeapon->m_bCharges = pDesc->GetAttributeBool(CHARGES_ATTRIB); pWeapon->m_bOmnidirectional = pDesc->GetAttributeBool(OMNIDIRECTIONAL_ATTRIB); @@ -1905,7 +1923,7 @@ ALERROR CWeaponClass::CreateFromXML (SDesignLoadCtx &Ctx, CXMLElement *pDesc, CI pWeapon->m_bReportAmmo = pDesc->GetAttributeBool(REPORT_AMMO_ATTRIB); pWeapon->m_bTargetStationsOnly = pDesc->GetAttributeBool(TARGET_STATIONS_ONLY_ATTRIB); pWeapon->m_bContinuousConsumePerShot = pDesc->GetAttributeBool(CONTINUOUS_CONSUME_PERSHOT_ATTRIB); - pWeapon->m_iCounterPerShot = pDesc->GetAttributeIntegerBounded(SHIP_COUNTER_PER_SHOT_ATTRIB, 0, -1, 0); + pWeapon->m_iCounterPerShot = pDesc->GetAttributeIntegerBounded(SHIP_COUNTER_PER_SHOT_ATTRIB, 0, -1, 0); // TODO(heliogenesis): Enable shots to have this statistic too pWeapon->m_bBurstTracksTargets = pDesc->GetAttributeBool(BURST_TRACKS_TARGETS_ATTRIB); pWeapon->m_bCanFireWhenBlind = pDesc->GetAttributeBool(CAN_FIRE_WHEN_BLIND_ATTRIB); pWeapon->m_bUsesLauncherControls = pDesc->GetAttributeBool(USES_LAUNCHER_CONTROLS_ATTRIB); @@ -2160,6 +2178,8 @@ bool CWeaponClass::FindAmmoDataField (const CItem &Ammo, const CString &sField, } else if (strEquals(sField, FIELD_IS_ALTERNATING)) *retsValue = (GetConfiguration(*pShot).IsAlternating() ? CString("True") : NULL_STR); + else if (strEquals(sField, FIELD_CHARGE_TIME)) + *retsValue = strFromInt(GetChargeTime(*pShot)); else return pShot->FindDataField(sField, retsValue); @@ -2219,6 +2239,7 @@ bool CWeaponClass::FireAllShots (CInstalledDevice &Device, const CWeaponFireDesc // Fire out to event, if the weapon has one. // Otherwise, we create weapon fire + // TODO(heliogenesis): Add event to fire OnChargeWeapon here if (FireOnFireWeapon(ItemCtx, ShotDesc, @@ -2415,9 +2436,10 @@ bool CWeaponClass::FireOnFireWeapon (CItemCtx &ItemCtx, return true; } -bool CWeaponClass::FireWeapon (CInstalledDevice &Device, - const CWeaponFireDesc &ShotDesc, - SActivateCtx &ActivateCtx) +bool CWeaponClass::FireWeapon (CInstalledDevice &Device, + const CWeaponFireDesc &ShotDesc, + SActivateCtx &ActivateCtx, + const bool IsCharging) // FireWeapon // @@ -2479,7 +2501,8 @@ bool CWeaponClass::FireWeapon (CInstalledDevice &Device, int iNewPolarity; if ((ActivateCtx.iRepeatingCount == GetContinuous(ShotDesc)) - && GetConfiguration(ShotDesc).IncPolarity(GetAlternatingPos(&Device), &iNewPolarity)) + && GetConfiguration(ShotDesc).IncPolarity(GetAlternatingPos(&Device), &iNewPolarity) + && (ActivateCtx.iChargeFrame == GetChargeTime(ShotDesc))) SetAlternatingPos(&Device, iNewPolarity); // Remember last shot count @@ -2650,6 +2673,25 @@ const CConfigurationDesc &CWeaponClass::GetConfiguration (const CWeaponFireDesc return m_Configuration; } +int CWeaponClass::GetChargeTime (const CWeaponFireDesc &Shot) const + +// GetContinous +// +// If this is a weapon with charge time, we return the number of ticks before +// shots. 0 means no charge time. + + { + // Check the shot first, which can override the weapon. + + int iChargeTime = Shot.GetChargeTime(); + if (iChargeTime != -1) + return iChargeTime; + + // Check the weapon + + return m_iChargeTime; + } + int CWeaponClass::GetContinuous (const CWeaponFireDesc &Shot) const // GetContinous @@ -2864,7 +2906,7 @@ ICCItem *CWeaponClass::FindAmmoItemProperty (CItemCtx &Ctx, const CItem &Ammo, c if (Balance.iLevel == 0) return CC.CreateNil(); - const SStdStats &Stats = STD_WEAPON_STATS[Balance.iLevel - 1]; + const SStdStats& Stats = STD_WEAPON_STATS[Balance.iLevel - 1]; // Compute the balance assuming that damage is standard. @@ -2883,7 +2925,7 @@ ICCItem *CWeaponClass::FindAmmoItemProperty (CItemCtx &Ctx, const CItem &Ammo, c if (Balance.iLevel == 0) return CC.CreateNil(); - const SStdStats &Stats = STD_WEAPON_STATS[Balance.iLevel - 1]; + const SStdStats& Stats = STD_WEAPON_STATS[Balance.iLevel - 1]; // Compute the balance assuming that cost is standard. @@ -2905,6 +2947,9 @@ ICCItem *CWeaponClass::FindAmmoItemProperty (CItemCtx &Ctx, const CItem &Ammo, c return CC.CreateInteger(mathRound(Balance.rBalance - Balance.rCost)); } + else if (strEquals(sProperty, PROPERTY_CHARGE_TIME)) + return CC.CreateInteger(GetChargeTime(*pShot)); + else if (strEquals(sProperty, PROPERTY_DAMAGE_180)) { Metric rDamagePerShot = CalcDamagePerShot(*pShot, pEnhancements); @@ -5464,8 +5509,12 @@ void CWeaponClass::Update (CInstalledDevice *pDevice, CSpaceObject *pSource, SDe } } } + // TODO(heliogenesis): If we have charge frames, and are in charge frames, create a charge effect + // instead of firing. We assume a delay of 0 when dealing with charge frames. // See if we continue to fire + // dwContinouous starts at maximum repeating count and counts backwards towards zero; it represents + // how many frames we have left in this burst DWORD dwContinuous = GetContinuousFire(pDevice); if (dwContinuous == CONTINUOUS_START) @@ -5475,17 +5524,18 @@ void CWeaponClass::Update (CInstalledDevice *pDevice, CSpaceObject *pSource, SDe { int iContinuous = GetContinuous(*pShot); int iContinuousDelay = Max(1, GetContinuousFireDelay(*pShot) + 1); + int iChargeTime = Max(0, GetChargeTime(*pShot)); // -1 is used to skip the first update cycle // (which happens on the same tick as Activate) if (iContinuousDelay > 1) { - SetContinuousFire(pDevice, ((iContinuous + 1) * iContinuousDelay) - 1); + SetContinuousFire(pDevice, (iChargeTime + ((iContinuous + 1) * iContinuousDelay)) - 1); } else { - SetContinuousFire(pDevice, iContinuous); + SetContinuousFire(pDevice, iContinuous + iChargeTime); } } else @@ -5501,14 +5551,21 @@ void CWeaponClass::Update (CInstalledDevice *pDevice, CSpaceObject *pSource, SDe { int iContinuous = GetContinuous(*pShot); int iContinuousDelay = Max(1, GetContinuousFireDelay(*pShot) + 1); + int iChargeTime = Max(0, GetChargeTime(*pShot)); + int iBurstLengthInFrames = iContinuousDelay > 1 ? (iChargeTime + ((iContinuous + 1) * iContinuousDelay)) - 1 : iContinuous + iChargeTime; + int iFireFrame = max(0, iBurstLengthInFrames - int(dwContinuous)); SActivateCtx ActivateCtx(Ctx); - if ((dwContinuous % iContinuousDelay) == 0) + if ((dwContinuous % iContinuousDelay) == 0 || (int(dwContinuous) > iBurstLengthInFrames)) { - ActivateCtx.iRepeatingCount = 1 + iContinuous - (dwContinuous / iContinuousDelay); + if (ActivateCtx.TargetList.IsEmpty()) + ActivateCtx.TargetList = pSource->GetTargetList(); + + ActivateCtx.iRepeatingCount = 1 + iContinuous - min(int(dwContinuous / iContinuousDelay), iContinuous + 1); + ActivateCtx.iChargeFrame = 1 + iBurstLengthInFrames - min((int(dwContinuous) - iBurstLengthInFrames), iBurstLengthInFrames + 1); - FireWeapon(*pDevice, *pShot, ActivateCtx); + FireWeapon(*pDevice, *pShot, ActivateCtx, (int(dwContinuous) <= iBurstLengthInFrames)); if (pSource->IsDestroyed()) return; diff --git a/Mammoth/TSE/CWeaponFireDesc.cpp b/Mammoth/TSE/CWeaponFireDesc.cpp index a9cd8a946..048702044 100644 --- a/Mammoth/TSE/CWeaponFireDesc.cpp +++ b/Mammoth/TSE/CWeaponFireDesc.cpp @@ -21,6 +21,7 @@ #define AREA_DAMAGE_DENSITY_ATTRIB CONSTLIT("areaDamageDensity") #define AUTO_TARGET_ATTRIB CONSTLIT("autoAcquireTarget") #define CAN_HIT_SOURCE_ATTRIB CONSTLIT("canHitSource") +#define CHARGE_TIME_ATTRIB CONSTLIT("chargeTime") #define COUNT_ATTRIB CONSTLIT("count") #define EXHAUST_RATE_ATTRIB CONSTLIT("creationRate") #define DAMAGE_ATTRIB CONSTLIT("damage") @@ -2153,6 +2154,7 @@ ALERROR CWeaponFireDesc::InitFromXML (SDesignLoadCtx &Ctx, CXMLElement *pDesc, c m_iContinuous = pDesc->GetAttributeIntegerBounded(BEAM_CONTINUOUS_ATTRIB, 0, -1, -1); m_iContinuousFireDelay = pDesc->GetAttributeIntegerBounded(CONTINUOUS_FIRE_DELAY_ATTRIB, 0, -1, -1); + m_iChargeTime = pDesc->GetAttributeIntegerBounded(CHARGE_TIME_ATTRIB, 0, -1, -1); if (pDesc->FindAttributeInteger(PASSTHROUGH_ATTRIB, &m_iPassthrough)) { From 6d80636cce9b8767f84bfb5bf99fc1b2c21e9a69 Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Mon, 18 Jan 2021 02:08:18 -0500 Subject: [PATCH 14/32] More work on charging guns, squash when done --- Mammoth/TSE/CWeaponClass.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Mammoth/TSE/CWeaponClass.cpp b/Mammoth/TSE/CWeaponClass.cpp index e26cc64f3..4209d8332 100644 --- a/Mammoth/TSE/CWeaponClass.cpp +++ b/Mammoth/TSE/CWeaponClass.cpp @@ -2457,6 +2457,13 @@ bool CWeaponClass::FireWeapon (CInstalledDevice &Device, if (Shots.GetCount() == 0) return false; + // If we're charging, then we don't fire shots - instead we only increment the ship counter (if needed), + // create the fire effect, and return True (so that we consume power). + if (IsCharging) + { + return true; + } + // Figure out when happens when we try to consume ammo, etc. EFireResults iResult = Consume(DeviceItem, ShotDesc, ActivateCtx.iRepeatingCount, &ActivateCtx.bConsumedItems); @@ -5552,7 +5559,7 @@ void CWeaponClass::Update (CInstalledDevice *pDevice, CSpaceObject *pSource, SDe int iContinuous = GetContinuous(*pShot); int iContinuousDelay = Max(1, GetContinuousFireDelay(*pShot) + 1); int iChargeTime = Max(0, GetChargeTime(*pShot)); - int iBurstLengthInFrames = iContinuousDelay > 1 ? (iChargeTime + ((iContinuous + 1) * iContinuousDelay)) - 1 : iContinuous + iChargeTime; + int iBurstLengthInFrames = iContinuousDelay > 1 ? ((iContinuous + 1) * iContinuousDelay) - 1 : iContinuous; int iFireFrame = max(0, iBurstLengthInFrames - int(dwContinuous)); SActivateCtx ActivateCtx(Ctx); @@ -5563,9 +5570,9 @@ void CWeaponClass::Update (CInstalledDevice *pDevice, CSpaceObject *pSource, SDe ActivateCtx.TargetList = pSource->GetTargetList(); ActivateCtx.iRepeatingCount = 1 + iContinuous - min(int(dwContinuous / iContinuousDelay), iContinuous + 1); - ActivateCtx.iChargeFrame = 1 + iBurstLengthInFrames - min((int(dwContinuous) - iBurstLengthInFrames), iBurstLengthInFrames + 1); + ActivateCtx.iChargeFrame = 1 + iChargeTime - min((int(dwContinuous) - iBurstLengthInFrames) + 1, iChargeTime + 1); - FireWeapon(*pDevice, *pShot, ActivateCtx, (int(dwContinuous) <= iBurstLengthInFrames)); + FireWeapon(*pDevice, *pShot, ActivateCtx, (int(dwContinuous) > iBurstLengthInFrames + 1)); if (pSource->IsDestroyed()) return; From 2983bdb40a34f03a18e059003139bf56bd855357 Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Sat, 6 Mar 2021 23:31:13 -0500 Subject: [PATCH 15/32] Complete weapon charge effect --- Mammoth/Include/TSEEffects.h | 4 ++ Mammoth/Include/TSEWeaponClassImpl.h | 16 +++-- Mammoth/Include/TSEWeaponFireDesc.h | 7 +- Mammoth/TSE/CCExtensions.cpp | 4 +- Mammoth/TSE/CCreatePainterCtx.cpp | 8 +++ Mammoth/TSE/CSpaceObject.cpp | 2 +- Mammoth/TSE/CWeaponClass.cpp | 95 +++++++++++++++++++++++++++- Mammoth/TSE/CWeaponFireDesc.cpp | 94 ++++++++++++++++++++++++++- 8 files changed, 219 insertions(+), 11 deletions(-) diff --git a/Mammoth/Include/TSEEffects.h b/Mammoth/Include/TSEEffects.h index 6450d72c2..edb00df81 100644 --- a/Mammoth/Include/TSEEffects.h +++ b/Mammoth/Include/TSEEffects.h @@ -192,6 +192,8 @@ class CCreatePainterCtx void SetAPIVersion (DWORD dwVersion) { m_dwAPIVersion = dwVersion; } void SetDamageCtx (SDamageCtx &Ctx) { m_pDamageCtx = &Ctx; } void SetDefaultParam (const CString &sParam, const CEffectParamDesc &Value); + void SetFireCharge (int iFireCharge) { m_iFireCharge = iFireCharge; }; + void SetFireRepeat (int iFireRepeat) { m_iFireRepeat = iFireRepeat; }; void SetLifetime (int iLifetime) { m_iLifetime = iLifetime; } void SetLoadVersion (DWORD dwVersion) { m_dwLoadVersion = dwVersion; } void SetNoSingleton (bool bValue = true) { m_bNoSingleton = bValue; } @@ -226,6 +228,8 @@ class CCreatePainterCtx TUniquePtr m_pDefaultParams; // Default parameters (owned by us) DWORD m_dwLoadVersion = SYSTEM_SAVE_VERSION; // Optional system version at load time DWORD m_dwAPIVersion = API_VERSION; // API version of creator + int m_iFireRepeat = 0; // Optional fire repeat count + int m_iFireCharge = 0; // Optional fire charge count bool m_bUseObjectCenter = false; // If TRUE, particle clouds always use the object as center bool m_bTracking = false; // If TRUE, object sets velocity diff --git a/Mammoth/Include/TSEWeaponClassImpl.h b/Mammoth/Include/TSEWeaponClassImpl.h index 3f8e05d34..31fe647cd 100644 --- a/Mammoth/Include/TSEWeaponClassImpl.h +++ b/Mammoth/Include/TSEWeaponClassImpl.h @@ -38,8 +38,9 @@ class CWeaponClass : public CDeviceClass evtOnFireWeapon = 0, evtGetAmmoToConsume = 1, evtGetAmmoCountToDisplay = 2, + evtOnChargeWeapon = 3, - evtCount = 3, + evtCount = 4, }; struct SBalance @@ -252,9 +253,9 @@ class CWeaponClass : public CDeviceClass void FailureExplosion (CItemCtx &ItemCtx, const CWeaponFireDesc &ShotDesc, bool *retbSourceDestroyed); bool FireAllShots (CInstalledDevice &Device, const CWeaponFireDesc &ShotDesc, CShotArray &Shots, int iRepeatingCount, SShotFireResult &retResult); bool FireGetAmmoCountToDisplay (const CDeviceItem &DeviceItem, const CWeaponFireDesc &Shot, int *retiAmmoCount = NULL) const; - int FireGetAmmoToConsume(CItemCtx &ItemCtx, - const CWeaponFireDesc &ShotDesc, - int iRepeatingCount) const; + int FireGetAmmoToConsume (CItemCtx &ItemCtx, + const CWeaponFireDesc &ShotDesc, + int iRepeatingCount) const; bool FireOnFireWeapon (CItemCtx &ItemCtx, const CWeaponFireDesc &ShotDesc, const CVector &vSource, @@ -262,6 +263,13 @@ class CWeaponClass : public CDeviceClass int iFireAngle, int iRepeatingCount, SShotFireResult &retResult); + bool FireOnChargeWeapon (CItemCtx& ItemCtx, + const CWeaponFireDesc& ShotDesc, + const CVector& vSource, + CSpaceObject* pTarget, + int iFireAngle, + int iRepeatingCount, + SShotFireResult& retResult); bool FireWeapon (CInstalledDevice &Device, const CWeaponFireDesc &ShotDesc, SActivateCtx &ActivateCtx, diff --git a/Mammoth/Include/TSEWeaponFireDesc.h b/Mammoth/Include/TSEWeaponFireDesc.h index a6e3cbdc3..c119106f9 100644 --- a/Mammoth/Include/TSEWeaponFireDesc.h +++ b/Mammoth/Include/TSEWeaponFireDesc.h @@ -670,8 +670,9 @@ class CWeaponFireDesc bool CanDamageSource (void) const { return (m_fCanDamageSource ? true : false); } bool CanHit (const CSpaceObject &Obj) const; bool CanHitFriends (void) const { return !m_fNoFriendlyFire; } + void CreateChargeEffect (CSystem* pSystem, CSpaceObject* pSource, const CVector& vPos, const CVector& vVel, int iDir, int iFireRepeat) const; IEffectPainter *CreateEffectPainter (SShotCreateCtx &CreateCtx); - void CreateFireEffect (CSystem *pSystem, CSpaceObject *pSource, const CVector &vPos, const CVector &vVel, int iDir) const; + void CreateFireEffect (CSystem *pSystem, CSpaceObject *pSource, const CVector &vPos, const CVector &vVel, int iDir, int iFireRepeat) const; void CreateHitEffect (CSystem *pSystem, SDamageCtx &DamageCtx) const; IEffectPainter *CreateParticlePainter (void); IEffectPainter *CreateSecondaryPainter (bool bTrackingObj = false, bool bUseObjectCenter = false); @@ -778,6 +779,7 @@ class CWeaponFireDesc void MarkImages (void); ALERROR OnDesignLoadComplete (SDesignLoadCtx &Ctx); void PlayFireSound (CSpaceObject *pSource) const { m_FireSound.PlaySound(pSource); } + void PlayChargeSound (CSpaceObject *pSource) const { m_ChargeSound.PlaySound(pSource); } bool ProximityBlast (void) const { return (m_fProximityBlast ? true : false); } bool ShowsHint (EDamageHint iHint) const; @@ -795,6 +797,7 @@ class CWeaponFireDesc int CalcDefaultInteraction (void) const; Metric CalcMaxEffectiveRange (void) const; static Metric CalcSpeed (Metric rPercentOfLight, bool bRelativistic); + CEffectCreator* GetChargeEffect (void) const; CEffectCreator *GetFireEffect (void) const; SOldEffects &GetOldEffects (void) const { return (m_pOldEffects ? *m_pOldEffects : m_NullOldEffects); } CUniverse &GetUniverse (void) const { return *g_pUniverse; } @@ -843,7 +846,9 @@ class CWeaponFireDesc CEffectCreatorRef m_pEffect; // Effect for the actual bullet/missile/beam CEffectCreatorRef m_pHitEffect; // Effect when we hit/explode CEffectCreatorRef m_pFireEffect; // Effect when we fire (muzzle flash) + CEffectCreatorRef m_pChargeEffect; // Effect when we charge (muzzle flash) CSoundRef m_FireSound; // Sound when weapon is fired + CSoundRef m_ChargeSound; // Sound when weapon is charged SOldEffects *m_pOldEffects = NULL; // Non-painter effects. CWeaponFireDescRef m_pExplosionType; // Explosion to create when ship is destroyed diff --git a/Mammoth/TSE/CCExtensions.cpp b/Mammoth/TSE/CCExtensions.cpp index d8297f5f8..829268e45 100644 --- a/Mammoth/TSE/CCExtensions.cpp +++ b/Mammoth/TSE/CCExtensions.cpp @@ -8800,6 +8800,7 @@ ICCItem *fnObjSet (CEvalContext *pEvalCtx, ICCItem *pArgs, DWORD dwData) CVector vOldFirePos; GetPosOrObject(pEvalCtx, pCC->LookupGlobal(CONSTLIT("aFirePos"), pCtx), &vOldFirePos); ICCItem *p_OldFireRepeat = pCC->LookupGlobal(CONSTLIT("aFireRepeat"), pCtx); + ICCItem *p_OldFireCharge = pCC->LookupGlobal(CONSTLIT("aFireCharge"), pCtx); ICCItem *p_OldTargetObj = pCC->LookupGlobal(CONSTLIT("aTargetObj"), pCtx); ICCItem *p_OldWeaponBonus = pCC->LookupGlobal(CONSTLIT("aWeaponBonus"), pCtx); ICCItem *p_OldWeaponType = pCC->LookupGlobal(CONSTLIT("aWeaponType"), pCtx); @@ -8814,6 +8815,7 @@ ICCItem *fnObjSet (CEvalContext *pEvalCtx, ICCItem *pArgs, DWORD dwData) pCtx->DefineInteger(CONSTLIT("aFireAngle"), p_OldFireAngle->GetIntegerValue()); pCtx->DefineVector(CONSTLIT("aFirePos"), vOldFirePos); pCtx->DefineInteger(CONSTLIT("aFireRepeat"), p_OldFireRepeat->GetIntegerValue()); + pCtx->DefineInteger(CONSTLIT("aFireCharge"), p_OldFireCharge->GetIntegerValue()); pCtx->DefineSpaceObject(CONSTLIT("aTargetObj"), CreateObjFromItem(p_OldTargetObj)); pCtx->DefineInteger(CONSTLIT("aWeaponBonus"), p_OldWeaponBonus->GetIntegerValue()); pCtx->DefineItemType(CONSTLIT("aWeaponType"), pCtx->AsItem(p_OldWeaponType).GetType()); @@ -12666,7 +12668,7 @@ ICCItem *fnSystemCreate (CEvalContext *pEvalCtx, ICCItem *pArgs, DWORD dwData) // Create barrel flash effect if (bFireEffect) - pDesc->CreateFireEffect(pSystem, pSource, vPos, CVector(), iDir); + pDesc->CreateFireEffect(pSystem, pSource, vPos, CVector(), iDir, 0); // If we have a bonus, we need an enhancement stack diff --git a/Mammoth/TSE/CCreatePainterCtx.cpp b/Mammoth/TSE/CCreatePainterCtx.cpp index e5843ef3c..2c901b2cd 100644 --- a/Mammoth/TSE/CCreatePainterCtx.cpp +++ b/Mammoth/TSE/CCreatePainterCtx.cpp @@ -10,15 +10,19 @@ #define FIELD_ARMOR_SEG CONSTLIT("armorSeg") #define FIELD_AVERAGE_DAMAGE_HP CONSTLIT("averageDamageHP") #define FIELD_CAUSE CONSTLIT("cause") +#define FIELD_CHARGE_TIME CONSTLIT("chargeTime") #define FIELD_DAMAGE_HP CONSTLIT("damageHP") #define FIELD_DAMAGE_PER_PROJECTILE CONSTLIT("damagePerProjectile") #define FIELD_DAMAGE_TYPE CONSTLIT("damageType") +#define FIELD_FIRE_CHARGE CONSTLIT("fireCharge") +#define FIELD_FIRE_REPEAT CONSTLIT("fireRepeat") #define FIELD_HIT_DIR CONSTLIT("hitDir") #define FIELD_HIT_POS CONSTLIT("hitPos") #define FIELD_OBJ_HIT CONSTLIT("objHit") #define FIELD_ORDER_GIVER CONSTLIT("orderGiver") #define FIELD_OVERLAY_TYPE CONSTLIT("overlayType") #define FIELD_PARTICLE_COUNT CONSTLIT("particleCount") +#define FIELD_REPEATING CONSTLIT("repeating") #define FIELD_SPEED CONSTLIT("speed") #define FIELD_WEAPON_UNID CONSTLIT("weaponUNID") @@ -158,8 +162,12 @@ void CCreatePainterCtx::SetWeaponFireDescData (ICCItem *pTable, const CWeaponFir // Sets the data from a weapon fire desc to the data block. { + pTable->SetIntegerAt(FIELD_CHARGE_TIME, mathRound(Desc.GetChargeTime())); pTable->SetIntegerAt(FIELD_DAMAGE_HP, mathRound(Desc.GetAveDamage())); pTable->SetStringAt(FIELD_DAMAGE_TYPE, GetDamageShortName(Desc.GetDamageType())); + pTable->SetIntegerAt(FIELD_FIRE_CHARGE, m_iFireCharge); + pTable->SetIntegerAt(FIELD_FIRE_REPEAT, m_iFireRepeat); + pTable->SetIntegerAt(FIELD_REPEATING, mathRound(Desc.GetContinuous())); pTable->SetIntegerAt(FIELD_SPEED, mathRound(100.0 * Desc.GetAveInitialSpeed() / LIGHT_SPEED)); if (Desc.GetType() == CWeaponFireDesc::ftParticles) pTable->SetIntegerAt(FIELD_PARTICLE_COUNT, mathRound(Desc.GetAveParticleCount())); diff --git a/Mammoth/TSE/CSpaceObject.cpp b/Mammoth/TSE/CSpaceObject.cpp index aadf5c9fe..c11463b5f 100644 --- a/Mammoth/TSE/CSpaceObject.cpp +++ b/Mammoth/TSE/CSpaceObject.cpp @@ -3647,7 +3647,7 @@ void CSpaceObject::FireOnSystemWeaponFire (CSpaceObject *pShot, CSpaceObject *pS CCodeChainCtx Ctx(GetUniverse()); Ctx.DefineContainingType(this); Ctx.SaveAndDefineSourceVar(this); - Ctx.DefineInteger(CONSTLIT("aFireRepeat"), iRepeatingCount); + Ctx.DefineInteger(CONSTLIT("aFireRepeat"), iRepeatingCount); // TODO: support aFireCharge Ctx.DefineSpaceObject(CONSTLIT("aShotObj"), pShot); Ctx.DefineSpaceObject(CONSTLIT("aWeaponObj"), pSource); Ctx.DefineInteger(CONSTLIT("aWeaponUNID"), dwItemUNID); diff --git a/Mammoth/TSE/CWeaponClass.cpp b/Mammoth/TSE/CWeaponClass.cpp index 4209d8332..5c8dada49 100644 --- a/Mammoth/TSE/CWeaponClass.cpp +++ b/Mammoth/TSE/CWeaponClass.cpp @@ -51,6 +51,7 @@ #define ON_FIRE_WEAPON_EVENT CONSTLIT("OnFireWeapon") #define GET_AMMO_TO_CONSUME_EVENT CONSTLIT("GetAmmoToConsume") #define GET_AMMO_COUNT_TO_DISPLAY_EVENT CONSTLIT("GetAmmoCountToDisplay") +#define ON_CHARGE_WEAPON_EVENT CONSTLIT("OnChargeWeapon") #define FIELD_AMMO_TYPE CONSTLIT("ammoType") #define FIELD_AVERAGE_DAMAGE CONSTLIT("averageDamage") // Average damage (1000x hp) @@ -212,6 +213,7 @@ static const char *CACHED_EVENTS[CWeaponClass::evtCount] = "OnFireWeapon", "GetAmmoToConsume", "GetAmmoCountToDisplay", + "OnChargeWeapon", }; CFailureDesc CWeaponClass::g_DefaultFailure(CFailureDesc::profileWeaponFailure); @@ -2239,7 +2241,6 @@ bool CWeaponClass::FireAllShots (CInstalledDevice &Device, const CWeaponFireDesc // Fire out to event, if the weapon has one. // Otherwise, we create weapon fire - // TODO(heliogenesis): Add event to fire OnChargeWeapon here if (FireOnFireWeapon(ItemCtx, ShotDesc, @@ -2263,7 +2264,7 @@ bool CWeaponClass::FireAllShots (CInstalledDevice &Device, const CWeaponFireDesc // Create the barrel flash effect, unless canceled if (Result.bFireEffect) - ShotDesc.CreateFireEffect(Source.GetSystem(), &Source, Shots[i].vPos, CVector(), Shots[i].iDir); + ShotDesc.CreateFireEffect(Source.GetSystem(), &Source, Shots[i].vPos, CVector(), Shots[i].iDir, iRepeatingCount); // Create the sound effect, if necessary @@ -2371,6 +2372,68 @@ int CWeaponClass::FireGetAmmoToConsume (CItemCtx &ItemCtx, const CWeaponFireDesc return 1; } +bool CWeaponClass::FireOnChargeWeapon (CItemCtx& ItemCtx, + const CWeaponFireDesc& ShotDesc, + const CVector& vSource, + CSpaceObject* pTarget, + int iFireAngle, + int iRepeatingCount, + SShotFireResult& retResult) +// FireOnChargeWeapon +// +// Fires OnChargeWeapon event. +// +// default (or Nil) = charge weapon as normal; sound plays on first frame only and fire effect is drawn on all frames +// noEffect = do not consume power or ammo; do not play sound or draw fire effect +// effectDrawn (or True) = consume power and ammo normally + { + SEventHandlerDesc Event; + if (!FindEventHandlerWeaponClass(evtOnChargeWeapon, &Event)) + return false; + + retResult = SShotFireResult(); + + CCodeChainCtx Ctx(GetUniverse()); + TSharedPtr pEnhancements = ItemCtx.GetEnhancementStack(); + + Ctx.DefineContainingType(GetItemType()); + Ctx.SaveAndDefineSourceVar(ItemCtx.GetSource()); + Ctx.SaveAndDefineItemVar(ItemCtx); + Ctx.DefineInteger(CONSTLIT("aFireAngle"), iFireAngle); + Ctx.DefineVector(CONSTLIT("aFirePos"), vSource); + Ctx.DefineInteger(CONSTLIT("aFireCharge"), iRepeatingCount); + Ctx.DefineInteger(CONSTLIT("aFireRepeat"), 0); + Ctx.DefineSpaceObject(CONSTLIT("aTargetObj"), pTarget); + Ctx.DefineInteger(CONSTLIT("aWeaponBonus"), (pEnhancements ? pEnhancements->GetBonus() : 0)); + Ctx.DefineItemType(CONSTLIT("aWeaponType"), ShotDesc.GetWeaponType()); + + ICCItemPtr pResult = Ctx.RunCode(Event); + if (pResult->IsError()) + { + ItemCtx.GetSource()->ReportEventError(ON_CHARGE_WEAPON_EVENT, pResult); + return true; + } + else if (pResult->IsNil()) + return false; + + else if (pResult->IsTrue() || pResult->GetBooleanAt(CONSTLIT("effectDrawn"))) + return true; + + else + { + retResult.bShotFired = !pResult->GetBooleanAt(CONSTLIT("noEffect")); + retResult.bFireEffect = retResult.bShotFired && !pResult->GetBooleanAt("noFireEffect"); + retResult.bSoundEffect = retResult.bShotFired && !pResult->GetBooleanAt("noSoundEffect"); + retResult.bRecoil = retResult.bShotFired && !pResult->GetBooleanAt("noRecoil"); + + return true; + } + + // Done + + return true; + } + bool CWeaponClass::FireOnFireWeapon (CItemCtx &ItemCtx, const CWeaponFireDesc &ShotDesc, const CVector &vSource, @@ -2404,6 +2467,7 @@ bool CWeaponClass::FireOnFireWeapon (CItemCtx &ItemCtx, Ctx.SaveAndDefineItemVar(ItemCtx); Ctx.DefineInteger(CONSTLIT("aFireAngle"), iFireAngle); Ctx.DefineVector(CONSTLIT("aFirePos"), vSource); + Ctx.DefineInteger(CONSTLIT("aFireCharge"), GetChargeTime(ShotDesc)); Ctx.DefineInteger(CONSTLIT("aFireRepeat"), iRepeatingCount); Ctx.DefineSpaceObject(CONSTLIT("aTargetObj"), pTarget); Ctx.DefineInteger(CONSTLIT("aWeaponBonus"), (pEnhancements ? pEnhancements->GetBonus() : 0)); @@ -2458,9 +2522,34 @@ bool CWeaponClass::FireWeapon (CInstalledDevice &Device, return false; // If we're charging, then we don't fire shots - instead we only increment the ship counter (if needed), - // create the fire effect, and return True (so that we consume power). + // create the fire effect, and return True (so that we consume power). Charging does not consume ammo, + // and cannot fail due to damaged or disrupted weapons. if (IsCharging) { + for (int i = 0; i < Shots.GetCount(); i++) + { + CSpaceObject& Source = Device.GetSourceOrThrow(); + CItemCtx ItemCtx(&Source, &Device); + SShotFireResult Result; + if (FireOnChargeWeapon(ItemCtx, + ShotDesc, + Shots[i].vPos, + Shots[i].pTarget, + Shots[i].iDir, + ActivateCtx.iChargeFrame, + Result)) + { + if (Source.IsDestroyed()) + return false; + } + + // TODO(heliogenesis): Change to CreateChargeEffect. We will need to call SetFireRepeat in this function, else it is similar to CreateFireEffect. + if (Result.bFireEffect) + ShotDesc.CreateChargeEffect(Source.GetSystem(), &Source, Shots[i].vPos, CVector(), Shots[i].iDir, ActivateCtx.iChargeFrame); + + if (Result.bSoundEffect) + ShotDesc.PlayChargeSound(&Source); + } return true; } diff --git a/Mammoth/TSE/CWeaponFireDesc.cpp b/Mammoth/TSE/CWeaponFireDesc.cpp index 048702044..5c667ac25 100644 --- a/Mammoth/TSE/CWeaponFireDesc.cpp +++ b/Mammoth/TSE/CWeaponFireDesc.cpp @@ -5,6 +5,7 @@ #include "PreComp.h" +#define CHARGE_EFFECT_TAG CONSTLIT("ChargeEffect") #define DAMAGE_TAG CONSTLIT("Damage") #define EFFECT_TAG CONSTLIT("Effect") #define ENHANCED_TAG CONSTLIT("Enhanced") @@ -21,7 +22,9 @@ #define AREA_DAMAGE_DENSITY_ATTRIB CONSTLIT("areaDamageDensity") #define AUTO_TARGET_ATTRIB CONSTLIT("autoAcquireTarget") #define CAN_HIT_SOURCE_ATTRIB CONSTLIT("canHitSource") +#define CHARGE_EFFECT_ATTRIB CONSTLIT("chargeEffect") #define CHARGE_TIME_ATTRIB CONSTLIT("chargeTime") +#define CHARGE_SOUND_ATTRIB CONSTLIT("chargeSound") #define COUNT_ATTRIB CONSTLIT("count") #define EXHAUST_RATE_ATTRIB CONSTLIT("creationRate") #define DAMAGE_ATTRIB CONSTLIT("damage") @@ -89,6 +92,7 @@ #define VAPOR_TRAIL_WIDTH_ATTRIB CONSTLIT("vaporTrailWidth") #define VAPOR_TRAIL_WIDTH_INC_ATTRIB CONSTLIT("vaporTrailWidthInc") +#define FIELD_CHARGE_SOUND CONSTLIT("chargeSound") #define FIELD_PARTICLE_COUNT CONSTLIT("particleCount") #define FIELD_SOUND CONSTLIT("sound") @@ -172,6 +176,7 @@ void CWeaponFireDesc::AddTypesUsed (TSortMap *retTypesUsed) retTypesUsed->SetAt(m_pEffect.GetUNID(), true); retTypesUsed->SetAt(m_pHitEffect.GetUNID(), true); retTypesUsed->SetAt(m_pFireEffect.GetUNID(), true); + retTypesUsed->SetAt(m_pChargeEffect.GetUNID(), true); retTypesUsed->SetAt(m_pExplosionType.GetUNID(), true); if (m_pParticleDesc) @@ -498,6 +503,44 @@ bool CWeaponFireDesc::CanHit (const CSpaceObject &Obj) const return true; } +void CWeaponFireDesc::CreateChargeEffect (CSystem* pSystem, CSpaceObject* pSource, const CVector& vPos, const CVector& vVel, int iDir, int iFireRepeat) const + +// CreateChargeEffect +// +// Creates a charge effect. + + { + // If we have a source, then we add the charge effect as an effect on the source. + + if (pSource) + { + // Create a painter. + + CCreatePainterCtx Ctx; + Ctx.SetWeaponFireDesc(this); + Ctx.SetFireCharge(iFireRepeat); + + IEffectPainter* pPainter = m_pChargeEffect.CreatePainter(Ctx, &GetUniverse().GetDefaultFireEffect(m_Damage.GetDamageType())); + if (pPainter == NULL) + return; + + // Add the effect + + pSource->AddEffect(pPainter, vPos, 0, iDir); + } + + // Otherwise, we add a stand-alone effect + + else + { + CEffectCreator* pFireEffect = GetFireEffect(); + if (pFireEffect == NULL) + return; + + pFireEffect->CreateEffect(pSystem, pSource, vPos, vVel, iDir); + } + } + IEffectPainter *CWeaponFireDesc::CreateEffectPainter (SShotCreateCtx &CreateCtx) // CreateEffectPainter @@ -531,7 +574,7 @@ IEffectPainter *CWeaponFireDesc::CreateEffectPainter (SShotCreateCtx &CreateCtx) return m_pEffect.CreatePainter(PainterCtx); } -void CWeaponFireDesc::CreateFireEffect (CSystem *pSystem, CSpaceObject *pSource, const CVector &vPos, const CVector &vVel, int iDir) const +void CWeaponFireDesc::CreateFireEffect (CSystem *pSystem, CSpaceObject *pSource, const CVector &vPos, const CVector &vVel, int iDir, int iFireRepeat) const // CreateFireEffect // @@ -546,6 +589,8 @@ void CWeaponFireDesc::CreateFireEffect (CSystem *pSystem, CSpaceObject *pSource, CCreatePainterCtx Ctx; Ctx.SetWeaponFireDesc(this); + Ctx.SetFireCharge(GetChargeTime()); + Ctx.SetFireRepeat(iFireRepeat); IEffectPainter *pPainter = m_pFireEffect.CreatePainter(Ctx, &GetUniverse().GetDefaultFireEffect(m_Damage.GetDamageType())); if (pPainter == NULL) @@ -699,6 +744,8 @@ bool CWeaponFireDesc::FindDataField (const CString &sField, CString *retsValue) } else if (strEquals(sField, FIELD_SOUND)) *retsValue = (m_FireSound.GetSound() != -1 ? strFromInt(m_FireSound.GetUNID(), false) : NULL_STR); + else if (strEquals(sField, FIELD_CHARGE_SOUND)) + *retsValue = (m_ChargeSound.GetSound() != -1 ? strFromInt(m_ChargeSound.GetUNID(), false) : NULL_STR); else return false; @@ -756,6 +803,9 @@ CEffectCreator *CWeaponFireDesc::FindEffectCreator (const CString &sUNID) case 'f': return pDesc->m_pFireEffect; + case 'c': + return pDesc->m_pChargeEffect; + case 'p': return (pDesc->m_pParticleDesc ? pDesc->m_pParticleDesc->GetParticleEffect() : NULL); @@ -1501,6 +1551,25 @@ DamageTypes CWeaponFireDesc::GetDamageType (void) const return iType; } +CEffectCreator* CWeaponFireDesc::GetChargeEffect(void) const + +// GetChargeEffect +// +// Returns the charge effect creator (or NULL if there is none). + +{ + // If we have a custom fire effect, use that. + + if (m_pChargeEffect) + return m_pChargeEffect; + + // Otherwise, see if the universe has a default effect for this damage + // type. + // TODO(heliogenesis): Add GetDefaultChargeEffect + + return &GetUniverse().GetDefaultFireEffect(m_Damage.GetDamageType()); +} + CEffectCreator *CWeaponFireDesc::GetFireEffect (void) const // GetFireEffect @@ -1833,6 +1902,7 @@ void CWeaponFireDesc::InitFromDamage (const DamageDesc &Damage) m_pHitEffect.Set(NULL); m_pFireEffect.Set(NULL); + m_pChargeEffect.Set(NULL); // Old effects @@ -1841,6 +1911,7 @@ void CWeaponFireDesc::InitFromDamage (const DamageDesc &Damage) // Sound m_FireSound = CSoundRef(); + m_ChargeSound = CSoundRef(); // Compute max effective range @@ -2307,6 +2378,13 @@ ALERROR CWeaponFireDesc::InitFromXML (SDesignLoadCtx &Ctx, CXMLElement *pDesc, c pDesc->GetAttribute(FIRE_EFFECT_ATTRIB))) return error; + + if (error = m_pChargeEffect.LoadEffect(Ctx, + strPatternSubst("%s:f", m_sUNID), + pDesc->GetContentElementByTag(CHARGE_EFFECT_TAG), + pDesc->GetAttribute(CHARGE_EFFECT_ATTRIB))) + return error; + // Explosion if (error = m_pExplosionType.LoadUNID(Ctx, pDesc->GetAttribute(EXPLOSION_TYPE_ATTRIB))) @@ -2336,6 +2414,8 @@ ALERROR CWeaponFireDesc::InitFromXML (SDesignLoadCtx &Ctx, CXMLElement *pDesc, c if (error = m_FireSound.LoadUNID(Ctx, pDesc->GetAttribute(SOUND_ATTRIB))) return error; + if (error = m_ChargeSound.LoadUNID(Ctx, pDesc->GetAttribute(CHARGE_SOUND_ATTRIB))) + return error; // Events @@ -2666,7 +2746,9 @@ ALERROR CWeaponFireDesc::InitScaledStats (SDesignLoadCtx &Ctx, CXMLElement *pDes m_pEffect = Src.m_pEffect; m_pHitEffect = Src.m_pHitEffect; m_pFireEffect = Src.m_pFireEffect; + m_pChargeEffect = Src.m_pChargeEffect; m_FireSound = Src.m_FireSound; + m_ChargeSound = Src.m_ChargeSound; m_pOldEffects = (Src.m_pOldEffects ? new SOldEffects(*Src.m_pOldEffects) : NULL); m_pExplosionType = Src.m_pExplosionType; @@ -2787,10 +2869,14 @@ void CWeaponFireDesc::MarkImages (void) if (m_pFireEffect) m_pFireEffect->MarkImages(); + if (m_pChargeEffect) + m_pChargeEffect->MarkImages(); + if (m_pExplosionType) m_pExplosionType->MarkImages(); m_FireSound.Mark(); + m_ChargeSound.Mark(); SFragmentDesc *pFragment = m_pFirstFragment; while (pFragment) @@ -2836,9 +2922,15 @@ ALERROR CWeaponFireDesc::OnDesignLoadComplete (SDesignLoadCtx &Ctx) if (error = m_pFireEffect.Bind(Ctx)) return error; + if (error = m_pChargeEffect.Bind(Ctx)) + return error; + if (error = m_FireSound.Bind(Ctx)) return error; + if (error = m_ChargeSound.Bind(Ctx)) + return error; + if (m_pParticleDesc) if (error = m_pParticleDesc->Bind(Ctx)) return error; From bf1c403fc659edcfef1268d0f7f866ad2fdb8192 Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Sun, 7 Mar 2021 00:09:44 -0500 Subject: [PATCH 16/32] Add playSoundOncePerBurst option --- Mammoth/Include/TSEWeaponFireDesc.h | 2 ++ Mammoth/TSE/CWeaponClass.cpp | 2 +- Mammoth/TSE/CWeaponFireDesc.cpp | 4 ++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Mammoth/Include/TSEWeaponFireDesc.h b/Mammoth/Include/TSEWeaponFireDesc.h index c119106f9..e6e0983a4 100644 --- a/Mammoth/Include/TSEWeaponFireDesc.h +++ b/Mammoth/Include/TSEWeaponFireDesc.h @@ -746,6 +746,7 @@ class CWeaponFireDesc const CParticleSystemDesc *GetParticleSystemDesc (void) const { return m_pParticleDesc; } int GetPassthrough (void) const { return m_iPassthrough; } int GetPowerUse (void) const { return m_iPowerUse; } + bool GetPlaySoundOncePerBurst (void) const { return m_bPlaySoundOncePerBurst; } int GetProximityFailsafe (void) const { return m_iProximityFailsafe; } Metric GetRatedSpeed (void) const { return m_rMissileSpeed; } CWeaponFireDesc *GetScaledDesc (int iLevel) const; @@ -851,6 +852,7 @@ class CWeaponFireDesc CSoundRef m_ChargeSound; // Sound when weapon is charged SOldEffects *m_pOldEffects = NULL; // Non-painter effects. CWeaponFireDescRef m_pExplosionType; // Explosion to create when ship is destroyed + bool m_bPlaySoundOncePerBurst; // If TRUE, play the fire sound only once per burst // Missile stuff (m_iFireType == ftMissile) int m_iAccelerationFactor = 0; // % increase in speed per 10 ticks diff --git a/Mammoth/TSE/CWeaponClass.cpp b/Mammoth/TSE/CWeaponClass.cpp index 5c8dada49..da01d91c5 100644 --- a/Mammoth/TSE/CWeaponClass.cpp +++ b/Mammoth/TSE/CWeaponClass.cpp @@ -2268,7 +2268,7 @@ bool CWeaponClass::FireAllShots (CInstalledDevice &Device, const CWeaponFireDesc // Create the sound effect, if necessary - if (Result.bSoundEffect) + if (Result.bSoundEffect && !(ShotDesc.GetPlaySoundOncePerBurst() && iRepeatingCount > 0)) retResult.bSoundEffect = true; // Recoil diff --git a/Mammoth/TSE/CWeaponFireDesc.cpp b/Mammoth/TSE/CWeaponFireDesc.cpp index 5c667ac25..709bbf2fa 100644 --- a/Mammoth/TSE/CWeaponFireDesc.cpp +++ b/Mammoth/TSE/CWeaponFireDesc.cpp @@ -75,6 +75,7 @@ #define PARTICLE_SPREAD_WIDTH_ATTRIB CONSTLIT("particleSpreadWidth") #define PASSTHROUGH_ATTRIB CONSTLIT("passthrough") #define POWER_USE_ATTRIB CONSTLIT("powerUse") +#define PLAY_SOUND_ONCE_PER_BURST_ATTRIB CONSTLIT("playSoundOncePerBurst") #define RANGE_ATTRIB CONSTLIT("range") #define RELATIVISTIC_SPEED_ATTRIB CONSTLIT("relativisticSpeed") #define BEAM_CONTINUOUS_ATTRIB CONSTLIT("repeating") @@ -1912,6 +1913,7 @@ void CWeaponFireDesc::InitFromDamage (const DamageDesc &Damage) m_FireSound = CSoundRef(); m_ChargeSound = CSoundRef(); + m_bPlaySoundOncePerBurst = false; // Compute max effective range @@ -2416,6 +2418,7 @@ ALERROR CWeaponFireDesc::InitFromXML (SDesignLoadCtx &Ctx, CXMLElement *pDesc, c return error; if (error = m_ChargeSound.LoadUNID(Ctx, pDesc->GetAttribute(CHARGE_SOUND_ATTRIB))) return error; + m_bPlaySoundOncePerBurst = pDesc->GetAttributeBool(PLAY_SOUND_ONCE_PER_BURST_ATTRIB); // Events @@ -2749,6 +2752,7 @@ ALERROR CWeaponFireDesc::InitScaledStats (SDesignLoadCtx &Ctx, CXMLElement *pDes m_pChargeEffect = Src.m_pChargeEffect; m_FireSound = Src.m_FireSound; m_ChargeSound = Src.m_ChargeSound; + m_bPlaySoundOncePerBurst = Src.m_bPlaySoundOncePerBurst; m_pOldEffects = (Src.m_pOldEffects ? new SOldEffects(*Src.m_pOldEffects) : NULL); m_pExplosionType = Src.m_pExplosionType; From 20a38165fe75d30775fc61b9e7b4f5f9855e6e70 Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Mon, 8 Mar 2021 01:41:07 -0500 Subject: [PATCH 17/32] Fix aFireTarget not being set properly in charging weapons --- Mammoth/TSE/CWeaponClass.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Mammoth/TSE/CWeaponClass.cpp b/Mammoth/TSE/CWeaponClass.cpp index da01d91c5..ddf08152d 100644 --- a/Mammoth/TSE/CWeaponClass.cpp +++ b/Mammoth/TSE/CWeaponClass.cpp @@ -260,7 +260,7 @@ bool CWeaponClass::Activate (CInstalledDevice &Device, SActivateCtx &ActivateCtx // Fire the weapon if it isn't a charging weapon - bool bSuccess = GetChargeTime(*pShotDesc) > 0 ? true : FireWeapon(Device, *pShotDesc, ActivateCtx); + bool bSuccess = FireWeapon(Device, *pShotDesc, ActivateCtx, GetChargeTime(*pShotDesc) > 0); // If firing the weapon destroyed the ship, then we bail out @@ -2550,6 +2550,20 @@ bool CWeaponClass::FireWeapon (CInstalledDevice &Device, if (Result.bSoundEffect) ShotDesc.PlayChargeSound(&Source); } + + // Set the device angle so that repeating weapons can get access to it. + if (ActivateCtx.iChargeFrame == 0) + { + Device.SetTarget(Shots[0].pTarget); + if (bSetFireAngle) + { + Device.SetFireAngle(iFireAngle); + } + else if (ActivateCtx.iRepeatingCount == 0) + { + Device.SetFireAngle(-1); + } + } return true; } @@ -5659,7 +5673,7 @@ void CWeaponClass::Update (CInstalledDevice *pDevice, CSpaceObject *pSource, SDe ActivateCtx.TargetList = pSource->GetTargetList(); ActivateCtx.iRepeatingCount = 1 + iContinuous - min(int(dwContinuous / iContinuousDelay), iContinuous + 1); - ActivateCtx.iChargeFrame = 1 + iChargeTime - min((int(dwContinuous) - iBurstLengthInFrames) + 1, iChargeTime + 1); + ActivateCtx.iChargeFrame = 1 + iChargeTime - min((int(dwContinuous) - iBurstLengthInFrames), iChargeTime + 1); FireWeapon(*pDevice, *pShot, ActivateCtx, (int(dwContinuous) > iBurstLengthInFrames + 1)); From a76a75b397d5ebc42d3b96d6decd588361bd099d Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Mon, 8 Mar 2021 01:47:00 -0500 Subject: [PATCH 18/32] Fix formatting --- Mammoth/TSE/CWeaponClass.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mammoth/TSE/CWeaponClass.cpp b/Mammoth/TSE/CWeaponClass.cpp index ddf08152d..a6329a08c 100644 --- a/Mammoth/TSE/CWeaponClass.cpp +++ b/Mammoth/TSE/CWeaponClass.cpp @@ -5672,8 +5672,8 @@ void CWeaponClass::Update (CInstalledDevice *pDevice, CSpaceObject *pSource, SDe if (ActivateCtx.TargetList.IsEmpty()) ActivateCtx.TargetList = pSource->GetTargetList(); - ActivateCtx.iRepeatingCount = 1 + iContinuous - min(int(dwContinuous / iContinuousDelay), iContinuous + 1); - ActivateCtx.iChargeFrame = 1 + iChargeTime - min((int(dwContinuous) - iBurstLengthInFrames), iChargeTime + 1); + ActivateCtx.iRepeatingCount = 1 + iContinuous - min(int(dwContinuous) / iContinuousDelay, iContinuous + 1); + ActivateCtx.iChargeFrame = 1 + iChargeTime - min(int(dwContinuous) - iBurstLengthInFrames, iChargeTime + 1); FireWeapon(*pDevice, *pShot, ActivateCtx, (int(dwContinuous) > iBurstLengthInFrames + 1)); From 9392915400291f9377d21dfead76b8cdccdd6a6a Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Thu, 11 Mar 2021 22:03:11 -0500 Subject: [PATCH 19/32] Fix charging guns firing randomly on NPC ships --- Mammoth/TSE/CWeaponClass.cpp | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/Mammoth/TSE/CWeaponClass.cpp b/Mammoth/TSE/CWeaponClass.cpp index a6329a08c..436f47b54 100644 --- a/Mammoth/TSE/CWeaponClass.cpp +++ b/Mammoth/TSE/CWeaponClass.cpp @@ -271,6 +271,11 @@ bool CWeaponClass::Activate (CInstalledDevice &Device, SActivateCtx &ActivateCtx Device.SetLastActivateSuccessful(bSuccess); + // If we did not succeed, then we're done + + if (!bSuccess) + return false; + // If we have nonzero charge time then set continuous fire device data // We set to -1 because we skip the first Update after the call // to Activate (since it happens on the same tick) @@ -278,16 +283,11 @@ bool CWeaponClass::Activate (CInstalledDevice &Device, SActivateCtx &ActivateCtx // bSuccess is false here (we technically didn't fire any shots by charging) if (GetChargeTime(*pShotDesc) > 0) - { + { SetContinuousFire(&Device, CONTINUOUS_START); // Return true so we consume power return true; - } - - // If we did not succeed, then we're done - - if (!bSuccess) - return false; + } // If this is a continuous fire weapon then set the device data // We set to -1 because we skip the first Update after the call @@ -2552,17 +2552,14 @@ bool CWeaponClass::FireWeapon (CInstalledDevice &Device, } // Set the device angle so that repeating weapons can get access to it. - if (ActivateCtx.iChargeFrame == 0) + Device.SetTarget(Shots[0].pTarget); + if (bSetFireAngle) { - Device.SetTarget(Shots[0].pTarget); - if (bSetFireAngle) - { - Device.SetFireAngle(iFireAngle); - } - else if (ActivateCtx.iRepeatingCount == 0) - { - Device.SetFireAngle(-1); - } + Device.SetFireAngle(iFireAngle); + } + else if (ActivateCtx.iRepeatingCount == 0) + { + Device.SetFireAngle(-1); } return true; } @@ -2785,7 +2782,7 @@ const CConfigurationDesc &CWeaponClass::GetConfiguration (const CWeaponFireDesc int CWeaponClass::GetChargeTime (const CWeaponFireDesc &Shot) const -// GetContinous +// GetChargeTime // // If this is a weapon with charge time, we return the number of ticks before // shots. 0 means no charge time. From 595b7830126765cf70d7c4b0f784e1fc8ed018eb Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Sun, 14 Mar 2021 04:12:56 -0400 Subject: [PATCH 20/32] Use ship counter when charging guns --- Mammoth/TSE/CWeaponClass.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Mammoth/TSE/CWeaponClass.cpp b/Mammoth/TSE/CWeaponClass.cpp index 436f47b54..c0a9953ac 100644 --- a/Mammoth/TSE/CWeaponClass.cpp +++ b/Mammoth/TSE/CWeaponClass.cpp @@ -2528,6 +2528,16 @@ bool CWeaponClass::FireWeapon (CInstalledDevice &Device, { for (int i = 0; i < Shots.GetCount(); i++) { + // If we're using ship counters, make sure we have enough. + + if (!CanConsumeShipCounter(DeviceItem, ShotDesc)) + return false; + + // Update the ship energy/heat counter. + + if (m_iCounterPerShot != 0) + ConsumeShipCounter(DeviceItem, ShotDesc); + CSpaceObject& Source = Device.GetSourceOrThrow(); CItemCtx ItemCtx(&Source, &Device); SShotFireResult Result; From 029a1c4986f664af1af2b172ae3e91b4468913d9 Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Thu, 25 Mar 2021 22:30:59 -0400 Subject: [PATCH 21/32] Revert CHexarcService.cpp --- Mammoth/TSUI/CHexarcService.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mammoth/TSUI/CHexarcService.cpp b/Mammoth/TSUI/CHexarcService.cpp index 441a985df..9caf49adc 100644 --- a/Mammoth/TSUI/CHexarcService.cpp +++ b/Mammoth/TSUI/CHexarcService.cpp @@ -123,7 +123,7 @@ // NOTE: We keep the actual private key in HexarcKeys.h. -#ifndef HEXARC_SANDBOX +#ifdef HEXARC_SANDBOX const int KEY_SIZE = 64; From bc634000a029bdbb056a572a0c18ac2a83de931a Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Thu, 25 Mar 2021 22:32:07 -0400 Subject: [PATCH 22/32] Fix indenting in Devices.cpp --- Mammoth/TSE/Devices.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Mammoth/TSE/Devices.cpp b/Mammoth/TSE/Devices.cpp index 4454799e0..644ca1e61 100644 --- a/Mammoth/TSE/Devices.cpp +++ b/Mammoth/TSE/Devices.cpp @@ -515,7 +515,7 @@ ICCItem *CDeviceClass::FindItemProperty (CItemCtx &Ctx, const CString &sName) else if (strEquals(sName, PROPERTY_CAN_TARGET_MISSILES)) return (pDevice ? CC.CreateBool(pDevice->CanTargetMissiles()) : CC.CreateNil()); else if (strEquals(sName, PROPERTY_CAPACITOR)) - { + { CSpaceObject* pSource = Ctx.GetSource(); CounterTypes iType; int iLevel; @@ -524,7 +524,7 @@ ICCItem *CDeviceClass::FindItemProperty (CItemCtx &Ctx, const CString &sName) return CC.CreateNil(); return CC.CreateInteger(iLevel); - } + } else if (strEquals(sName, PROPERTY_CYCLE_FIRE)) return (pDevice ? CC.CreateBool(pDevice->GetCycleFireSettings()) : CC.CreateNil()); @@ -539,15 +539,15 @@ ICCItem *CDeviceClass::FindItemProperty (CItemCtx &Ctx, const CString &sName) return CC.CreateBool(pDevice ? pDevice->IsExternal() : IsExternal()); else if (strEquals(sName, PROPERTY_EXTRA_POWER_USE)) - { + { if (pDevice == NULL) return CC.CreateNil(); return CC.CreateInteger(pDevice->GetExtraPowerUse()); - } + } else if (strEquals(sName, PROPERTY_POS)) - { + { if (pDevice == NULL) return CC.CreateNil(); @@ -569,15 +569,15 @@ ICCItem *CDeviceClass::FindItemProperty (CItemCtx &Ctx, const CString &sName) // Done return pResult; - } + } else if (strEquals(sName, PROPERTY_POWER)) - { + { if (GetCategory() == itemcatReactor) return CTLispConvert::CreatePowerResultMW(GetPowerOutput(Ctx))->Reference(); else return CTLispConvert::CreatePowerResultMW(GetPowerRating(Ctx))->Reference(); - } + } else if (strEquals(sName, PROPERTY_POWER_OUTPUT)) return CreatePowerResult(GetPowerOutput(Ctx) * 100.0); From 0072308795b1ee2b98b5639ae61bcede2593bb9e Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Thu, 25 Mar 2021 22:37:04 -0400 Subject: [PATCH 23/32] Fix formatting in CInstalledDevice.cpp --- Mammoth/TSE/CInstalledDevice.cpp | 62 ++++++++++++++++---------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/Mammoth/TSE/CInstalledDevice.cpp b/Mammoth/TSE/CInstalledDevice.cpp index 0fa494e94..07df6f804 100644 --- a/Mammoth/TSE/CInstalledDevice.cpp +++ b/Mammoth/TSE/CInstalledDevice.cpp @@ -579,7 +579,7 @@ void CInstalledDevice::PaintDevicePos (const SDeviceDesc &Device, CG32bitImage & } } -void CInstalledDevice::ReadFromStream (CSpaceObject &Source, SLoadCtx& Ctx) +void CInstalledDevice::ReadFromStream (CSpaceObject &Source, SLoadCtx &Ctx) // ReadFromStream // @@ -733,36 +733,36 @@ void CInstalledDevice::ReadFromStream (CSpaceObject &Source, SLoadCtx& Ctx) } Ctx.pStream->Read(dwLoad); - m_fOmniDirectional = ((dwLoad & 0x00000001) ? true : false); - m_f3DPosition = (((dwLoad & 0x00000002) ? true : false) && (Ctx.dwVersion >= 73)); - m_fFateSurvives = (((dwLoad & 0x00000004) ? true : false) && (Ctx.dwVersion >= 58)); - m_fOverdrive = ((dwLoad & 0x00000008) ? true : false); - m_fOptimized = ((dwLoad & 0x00000010) ? true : false); - m_fSecondaryWeapon = ((dwLoad & 0x00000020) ? true : false); - m_fFateDamaged = (((dwLoad & 0x00000040) ? true : false) && (Ctx.dwVersion >= 58)); - m_fEnabled = ((dwLoad & 0x00000080) ? true : false); - m_fWaiting = ((dwLoad & 0x00000100) ? true : false); - m_fTriggered = ((dwLoad & 0x00000200) ? true : false); - m_fRegenerating = ((dwLoad & 0x00000400) ? true : false); - m_fLastActivateSuccessful = ((dwLoad & 0x00000800) ? true : false); - - m_fLinkedFireAlways = ((dwLoad & 0x00001000) ? true : false); - m_fLinkedFireTarget = ((dwLoad & 0x00002000) ? true : false); - m_fLinkedFireEnemy = ((dwLoad & 0x00004000) ? true : false); - m_fExternal = ((dwLoad & 0x00008000) ? true : false); - m_fDuplicate = ((dwLoad & 0x00010000) ? true : false); - m_fCannotBeEmpty = ((dwLoad & 0x00020000) ? true : false); - m_fFateDestroyed = ((dwLoad & 0x00040000) ? true : false); - m_fFateComponetized = ((dwLoad & 0x00080000) ? true : false); - bool bSlotEnhancements = ((dwLoad & 0x00100000) ? true : false); - m_fLinkedFireSelected = ((dwLoad & 0x00200000) ? true : false); - m_fLinkedFireNever = ((dwLoad & 0x00400000) ? true : false); - m_fLinkedFireSelectedVariants = ((dwLoad & 0x00800000) ? true : false); - m_fCycleFire = ((dwLoad & 0x01000000) ? true : false); - m_fCanTargetMissiles = ((dwLoad & 0x02000000) ? true : false); - m_fOnSegment = ((dwLoad & 0x04000000) ? true : false); - m_fOnUsedLastAmmo = ((dwLoad & 0x08000000) ? true : false); - bool bLoadWeaponTargetDefinition = ((dwLoad & 0x10000000) ? true : false); + m_fOmniDirectional = ((dwLoad & 0x00000001) ? true : false); + m_f3DPosition = (((dwLoad & 0x00000002) ? true : false) && (Ctx.dwVersion >= 73)); + m_fFateSurvives = (((dwLoad & 0x00000004) ? true : false) && (Ctx.dwVersion >= 58)); + m_fOverdrive = ((dwLoad & 0x00000008) ? true : false); + m_fOptimized = ((dwLoad & 0x00000010) ? true : false); + m_fSecondaryWeapon = ((dwLoad & 0x00000020) ? true : false); + m_fFateDamaged = (((dwLoad & 0x00000040) ? true : false) && (Ctx.dwVersion >= 58)); + m_fEnabled = ((dwLoad & 0x00000080) ? true : false); + m_fWaiting = ((dwLoad & 0x00000100) ? true : false); + m_fTriggered = ((dwLoad & 0x00000200) ? true : false); + m_fRegenerating = ((dwLoad & 0x00000400) ? true : false); + m_fLastActivateSuccessful = ((dwLoad & 0x00000800) ? true : false); + + m_fLinkedFireAlways = ((dwLoad & 0x00001000) ? true : false); + m_fLinkedFireTarget = ((dwLoad & 0x00002000) ? true : false); + m_fLinkedFireEnemy = ((dwLoad & 0x00004000) ? true : false); + m_fExternal = ((dwLoad & 0x00008000) ? true : false); + m_fDuplicate = ((dwLoad & 0x00010000) ? true : false); + m_fCannotBeEmpty = ((dwLoad & 0x00020000) ? true : false); + m_fFateDestroyed = ((dwLoad & 0x00040000) ? true : false); + m_fFateComponetized = ((dwLoad & 0x00080000) ? true : false); + bool bSlotEnhancements = ((dwLoad & 0x00100000) ? true : false); + m_fLinkedFireSelected = ((dwLoad & 0x00200000) ? true : false); + m_fLinkedFireNever = ((dwLoad & 0x00400000) ? true : false); + m_fLinkedFireSelectedVariants = ((dwLoad & 0x00800000) ? true : false); + m_fCycleFire = ((dwLoad & 0x01000000) ? true : false); + m_fCanTargetMissiles = ((dwLoad & 0x02000000) ? true : false); + m_fOnSegment = ((dwLoad & 0x04000000) ? true : false); + m_fOnUsedLastAmmo = ((dwLoad & 0x08000000) ? true : false); + bool bLoadWeaponTargetDefinition = ((dwLoad & 0x10000000) ? true : false); // Previous versions did not save this flag From 7c8e3189c2f0a565579bef0c97a90fc1b38a6670 Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Fri, 26 Mar 2021 00:02:05 -0400 Subject: [PATCH 24/32] Use CString instead of std::string --- Mammoth/Include/TSEDeviceClassesImpl.h | 4 ++-- Mammoth/TSE/CWeaponTargetDefinition.cpp | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Mammoth/Include/TSEDeviceClassesImpl.h b/Mammoth/Include/TSEDeviceClassesImpl.h index 8bca74dc7..a0ecb8379 100644 --- a/Mammoth/Include/TSEDeviceClassesImpl.h +++ b/Mammoth/Include/TSEDeviceClassesImpl.h @@ -20,14 +20,14 @@ class CWeaponTargetDefinition bool AimAndFire (CWeaponClass* pWeapon, CInstalledDevice* pDevice, CSpaceObject* pSource, CDeviceClass::SDeviceUpdateCtx& Ctx) const; bool GetCheckLineOfFire () { return m_bCheckLineOfFire; }; CSpaceObjectCriteria GetTargetCriteria () { return m_TargetCriteria; }; - Kernel::CString GetTargetCriteriaString () { return Kernel::CString(m_CriteriaString.c_str()); }; + Kernel::CString GetTargetCriteriaString () { return m_CriteriaString; }; void SetCheckLineOfFire (bool bCheckLineOfFire) { m_bCheckLineOfFire = bCheckLineOfFire; }; void SetTargetCriteria (Kernel::CString sCriteria) { m_TargetCriteria.Init(sCriteria); m_CriteriaString = sCriteria; }; static std::unique_ptr ReadFromStream (SLoadCtx& Ctx); void WriteToStream (IWriteStream* pStream) const; private: - std::string m_CriteriaString = ""; + Kernel::CString m_CriteriaString = ""; CSpaceObjectCriteria m_TargetCriteria; bool m_bCheckLineOfFire = false; // Check line of fire for friendlies }; diff --git a/Mammoth/TSE/CWeaponTargetDefinition.cpp b/Mammoth/TSE/CWeaponTargetDefinition.cpp index 8068acf65..9fce26ab9 100644 --- a/Mammoth/TSE/CWeaponTargetDefinition.cpp +++ b/Mammoth/TSE/CWeaponTargetDefinition.cpp @@ -150,8 +150,7 @@ void CWeaponTargetDefinition::WriteToStream(IWriteStream *pStream) const // DWORD flags { - int iStrLength = m_CriteriaString.size(); - Kernel::CString(m_CriteriaString.c_str()).WriteToStream(pStream); + m_CriteriaString.WriteToStream(pStream); DWORD dwSave = 0; dwSave |= (m_bCheckLineOfFire ? 0x00000001 : 0); From 7db9e7fe58acd784edd27edd0fdd0239cdd9c418 Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Fri, 26 Mar 2021 02:45:44 -0400 Subject: [PATCH 25/32] Refactor --- Mammoth/TSE/AIManeuvers.cpp | 616 ++++++++++++++++++++---------------- Mammoth/TSE/ShipAIImpl.h | 4 + 2 files changed, 349 insertions(+), 271 deletions(-) diff --git a/Mammoth/TSE/AIManeuvers.cpp b/Mammoth/TSE/AIManeuvers.cpp index 136b52130..7cbe76d4d 100644 --- a/Mammoth/TSE/AIManeuvers.cpp +++ b/Mammoth/TSE/AIManeuvers.cpp @@ -1375,157 +1375,284 @@ void CAIBehaviorCtx::ImplementFireWeapon (CShip *pShip, DeviceNames iDev) } } -void CAIBehaviorCtx::ImplementFireWeaponOnTarget (CShip *pShip, - int iWeapon, - int iWeaponVariant, - CSpaceObject *pTarget, - const CVector &vTarget, - Metric rTargetDist2, - int *retiFireDir, - bool bDoNotShoot) - -// ImplementFireWeaponOnTarget -// -// Fires the given weapon (if aligned) on target - +void CAIBehaviorCtx::GetPrimaryWeaponsToFire (CShip* pShip, + CSpaceObject* pTarget, + Metric rTargetDist2, + TArray &pWeaponsToFire, + TArray &rWeaponRanges) { - DEBUG_TRY - - int iTick = pShip->GetSystem()->GetTick(); - -#ifdef DEBUG - bool bDebug = pShip->IsSelected(); -#endif - - ASSERT(pTarget); - - // Select the appropriate weapon. If we're not given a weapon, then choose the - // best one. - - std::vector pWeaponsToFire; - std::vector rWeaponRanges; - if (iWeapon == -1 && UsesAllPrimaryWeapons()) + // Block for selecting and firing multiple weapons. In this case, we select each weapon on the ship that is not a secondary weapon or linked fire (except for + // selected or selectedSameVariant). If it is selected or selectedSameVariant, add only the gun with the highest weapon score. + std::map lkfSelectedWeaponsByScore; + std::map lkfSelectedWeaponsToPushBack; + std::map, int> lkfSelectedVariantWeaponsByScore; + std::map, CInstalledDevice*> lkfSelectedVariantWeaponsToPushBack; + + for (CDeviceItem DeviceItem : pShip->GetDeviceSystem()) { - // Block for selecting and firing multiple weapons. In this case, we select each weapon on the ship that is not a secondary weapon or linked fire (except for - // selected or selectedSameVariant). If it is selected or selectedSameVariant, add only the gun with the highest weapon score. - int weaponIndex = 0; + // Iterate through all weapons. + CInstalledDevice* pWeapon = DeviceItem.GetInstalledDevice(); - std::map lkfSelectedWeaponsByScore; - std::map lkfSelectedWeaponsToPushBack; - std::map, int> lkfSelectedVariantWeaponsByScore; - std::map, CInstalledDevice*> lkfSelectedVariantWeaponsToPushBack; + // If this weapon is not working, linked-fire, or secondary, then skip it + auto linkedFireOptions = pWeapon->GetItem()->AsDeviceItemOrThrow().GetLinkedFireOptions(); + bool isLinkedFire = (pWeapon->IsLinkedFire() && !(linkedFireOptions & CDeviceClass::LinkedFireOptions::lkfSelected) + && !(linkedFireOptions & CDeviceClass::LinkedFireOptions::lkfSelectedVariant)); - for (CDeviceItem DeviceItem : pShip->GetDeviceSystem()) + if (pWeapon->IsSecondaryWeapon() || isLinkedFire || !pWeapon->IsWorking()) { - // Iterate through all weapons. - CInstalledDevice *pWeapon = DeviceItem.GetInstalledDevice(); - - // If this weapon is not working, linked-fire, or secondary, then skip it - auto linkedFireOptions = pWeapon->GetItem()->AsDeviceItemOrThrow().GetLinkedFireOptions(); - bool isLinkedFire = (pWeapon->IsLinkedFire() && !(linkedFireOptions & CDeviceClass::LinkedFireOptions::lkfSelected) - && !(linkedFireOptions & CDeviceClass::LinkedFireOptions::lkfSelectedVariant)); - - if (pWeapon->IsSecondaryWeapon() || isLinkedFire || !pWeapon->IsWorking()) + continue; + } + switch (pWeapon->GetCategory()) + { + case itemcatWeapon: + { + // If score is greater than zero, add it to the list + // If it is an lkfSelected weapon, add only the one with the highest score of that type to the list + // If it is an lkfSelectedVariant weapon, add only the one with the highest score and variant combo of that type to the list + int iScore = CalcWeaponScore(pShip, pTarget, pWeapon, rTargetDist2, true); + if (linkedFireOptions & CDeviceClass::LinkedFireOptions::lkfSelected) { - continue; + LONG UNID = pWeapon->GetUNID(); + // Check if this UNID exists in the respective map; if it does then check to see if the score exceeds the stored score, otherwise the score to beat is zero + // If so, set this as the weapon of the given UNID to push_back + int iHighestScoreForCategory = lkfSelectedWeaponsByScore[UNID]; + if (iScore > iHighestScoreForCategory) + { + lkfSelectedWeaponsByScore[UNID] = iScore; + lkfSelectedWeaponsToPushBack[UNID] = pWeapon; + } } - switch (pWeapon->GetCategory()) + else if (linkedFireOptions & CDeviceClass::LinkedFireOptions::lkfSelectedVariant) { - case itemcatWeapon: + LONG UNID = pWeapon->GetUNID(); + int variant_type = CItemCtx(pShip, pWeapon).GetItemVariantNumber(); + auto identifier = std::make_pair(UNID, variant_type); + // Check if this identifier exists in the respective map; if it does then check to see if the score exceeds the stored score, otherwise the score to beat is zero + // If so, set this as the weapon of the given identifier to push_back + int iHighestScoreForCategory = lkfSelectedVariantWeaponsByScore[identifier]; + if (iScore > iHighestScoreForCategory) { - // If score is greater than zero, add it to the list - // If it is an lkfSelected weapon, add only the one with the highest score of that type to the list - // If it is an lkfSelectedVariant weapon, add only the one with the highest score and variant combo of that type to the list - int iScore = CalcWeaponScore(pShip, pTarget, pWeapon, rTargetDist2, true); - if (linkedFireOptions & CDeviceClass::LinkedFireOptions::lkfSelected) - { - LONG UNID = pWeapon->GetUNID(); - // Check if this UNID exists in the respective map; if it does then check to see if the score exceeds the stored score, otherwise the score to beat is zero - // If so, set this as the weapon of the given UNID to push_back - int iHighestScoreForCategory = lkfSelectedWeaponsByScore[UNID]; - if (iScore > iHighestScoreForCategory) - { - lkfSelectedWeaponsByScore[UNID] = iScore; - lkfSelectedWeaponsToPushBack[UNID] = pWeapon; - } - } - else if (linkedFireOptions & CDeviceClass::LinkedFireOptions::lkfSelectedVariant) - { - LONG UNID = pWeapon->GetUNID(); - int variant_type = CItemCtx(pShip, pWeapon).GetItemVariantNumber(); - auto identifier = std::make_pair(UNID, variant_type); - // Check if this identifier exists in the respective map; if it does then check to see if the score exceeds the stored score, otherwise the score to beat is zero - // If so, set this as the weapon of the given identifier to push_back - int iHighestScoreForCategory = lkfSelectedVariantWeaponsByScore[identifier]; - if (iScore > iHighestScoreForCategory) + lkfSelectedVariantWeaponsByScore[identifier] = iScore; + lkfSelectedVariantWeaponsToPushBack[identifier] = pWeapon; + } + } + else if (iScore > 0) + { + pWeaponsToFire.Insert(pWeapon); + rWeaponRanges.Insert(pWeapon->GetMaxEffectiveRange(pShip, pTarget)); + } + break; + } + + case itemcatLauncher: + { + int iCount = pShip->GetMissileCount(); + int iBestScore = 0; + int iBestWeaponVariant = 0; + if (iCount > 0 && !pShip->IsWeaponRepeating(devMissileWeapon)) + { + pShip->ReadyFirstMissile(); + + for (int j = 0; j < iCount; j++) + { + int iScore = CalcWeaponScore(pShip, pTarget, pWeapon, rTargetDist2); + + // If we only score 1 and we've got secondary weapons, then don't + // bother with this missile (we don't want to waste it) + + if (iScore == 1 && HasSecondaryWeapons()) { - lkfSelectedVariantWeaponsByScore[identifier] = iScore; - lkfSelectedVariantWeaponsToPushBack[identifier] = pWeapon; - } + iScore = 0; } - else if (iScore > 0) + + if (iScore > iBestScore) { - pWeaponsToFire.push_back(pWeapon); - rWeaponRanges.push_back(pWeapon->GetMaxEffectiveRange(pShip, pTarget)); + iBestWeaponVariant = j; + iBestScore = iScore; } - break; + + pShip->ReadyNextMissile(); } + } + auto iBestWeapon = pWeapon->GetDeviceSlot(); + iBestWeapon = pShip->SelectWeapon(iBestWeapon, iBestWeaponVariant); + pWeaponsToFire.Insert(pWeapon); + rWeaponRanges.Insert(pWeapon->GetMaxEffectiveRange(pShip, pTarget)); + break; + } + } + } - case itemcatLauncher: - { - int iCount = pShip->GetMissileCount(); - int iBestScore = 0; - int iBestWeaponVariant = 0; - if (iCount > 0 && !pShip->IsWeaponRepeating(devMissileWeapon)) - { - pShip->ReadyFirstMissile(); + for (auto& UNIDAndWeapon : lkfSelectedWeaponsToPushBack) + { + pWeaponsToFire.Insert(UNIDAndWeapon.second); + rWeaponRanges.Insert(UNIDAndWeapon.second->GetMaxEffectiveRange(pShip, pTarget)); + } - for (int j = 0; j < iCount; j++) - { - int iScore = CalcWeaponScore(pShip, pTarget, pWeapon, rTargetDist2); + for (auto& identifierAndWeapon : lkfSelectedVariantWeaponsToPushBack) + { + pWeaponsToFire.Insert(identifierAndWeapon.second); + rWeaponRanges.Insert(identifierAndWeapon.second->GetMaxEffectiveRange(pShip, pTarget)); + } + } - // If we only score 1 and we've got secondary weapons, then don't - // bother with this missile (we don't want to waste it) +void CAIBehaviorCtx::FireWeaponIfOnTarget(CShip *pShip, CSpaceObject *pTarget, CInstalledDevice *pWeaponToFire, Metric rWeaponRange, Metric rTargetDist2, bool bDoNotShoot, int *retiFacingAngle, int *retiAngleToTarget) + { +#ifdef DEBUG + bool bDebug = pShip->IsSelected(); +#endif - if (iScore == 1 && HasSecondaryWeapons()) - { - iScore = 0; - } + int iAimAngle = pShip->GetRotation(); + int iFireAngle = -1; + *retiFacingAngle = -1; + bool bAligned; + bAligned = pShip->IsWeaponAligned(pWeaponToFire, + pTarget, + &iAimAngle, + &iFireAngle, + retiFacingAngle); + bool bAimError = false; + *retiAngleToTarget = iAimAngle; + + // iAimAngle is the direction that we should fire in order to hit + // the target. + // + // iFireAngle is the direction in which the weapon will fire. + // + // iFacingAngle is the direction in which the ship should face + // in order for the weapon to hit the target. + + // There is a chance of missing + + if (pWeaponToFire->IsReady()) + { + if (bAligned) + { + if (mathRandom(1, 100) > GetFireAccuracy()) + { + bAligned = false; - if (iScore > iBestScore) - { - iBestWeaponVariant = j; - iBestScore = iScore; - } + // In this case, we happen to be aligned, but because of inaccuracy + // reason we think we're not. We clear the aim angle because for + // omnidirectional weapons, we don't want to try to turn towards + // the new aim point. - pShip->ReadyNextMissile(); - } - } - auto iBestWeapon = pWeapon->GetDeviceSlot(); - iBestWeapon = pShip->SelectWeapon(iBestWeapon, iBestWeaponVariant); - pWeaponsToFire.push_back(pWeapon); - rWeaponRanges.push_back(pWeapon->GetMaxEffectiveRange(pShip, pTarget)); - break; + iAimAngle = -1; + bAimError = true; + DebugAIOutput(pShip, "Aim error: hold fire when aligned"); + } + } + else if (iAimAngle != -1) + { + if (mathRandom(1, 100) <= m_iPrematureFireChance) + { + int iAimOffset = AngleOffset(iFireAngle, iAimAngle); + if (iAimOffset < 20) + { + bAligned = true; + bAimError = true; + DebugAIOutput(pShip, "Aim error: fire when not aligned"); } } } + } + + // Fire - for (auto& UNIDAndWeapon : lkfSelectedWeaponsToPushBack) + if (bAligned) + { +#ifdef DEBUG { - pWeaponsToFire.push_back(UNIDAndWeapon.second); - rWeaponRanges.push_back(UNIDAndWeapon.second->GetMaxEffectiveRange(pShip, pTarget)); + char szDebug[1024]; + if (bAimError) + wsprintf(szDebug, "%s: false positive iAim=%d iFireAngle=%d", pWeaponToFire->GetName().GetASCIIZPointer(), iAimAngle, iFireAngle); + else if (!pWeaponToFire->IsReady()) + wsprintf(szDebug, "%s: aligned; NOT READY", pWeaponToFire->GetName().GetASCIIZPointer()); + else if (rTargetDist2 > (rWeaponRange * rWeaponRange)) + wsprintf(szDebug, "%s: aligned; TARGET OUT OF RANGE", pWeaponToFire->GetName().GetASCIIZPointer()); + else + wsprintf(szDebug, "%s: aligned", pWeaponToFire->GetName().GetASCIIZPointer()); + + DebugAIOutput(pShip, szDebug); } +#endif - for (auto& identifierAndWeapon : lkfSelectedVariantWeaponsToPushBack) + // If we're aligned and the weapon is ready, and we're + // in range of the target, then fire! + + if (pWeaponToFire->IsReady() + && rTargetDist2 <= (rWeaponRange * rWeaponRange)) { - pWeaponsToFire.push_back(identifierAndWeapon.second); - rWeaponRanges.push_back(identifierAndWeapon.second->GetMaxEffectiveRange(pShip, pTarget)); + if (pWeaponToFire->GetCategory() == itemcatWeapon) + { + if (CheckForFriendsInLineOfFire(pShip, pWeaponToFire, pTarget, iFireAngle, Max(pWeaponToFire->GetMaxEffectiveRange(pShip), DEFAULT_DIST_CHECK))) + { + if (!bDoNotShoot) + pShip->SetWeaponTriggered(pWeaponToFire); + DebugAIOutput(pShip, "FireOnTarget: Fire primary!"); + } + else + DebugAIOutput(pShip, "FireOnTarget: Friendlies in line of fire"); + } + else + { + if (CheckForFriendsInLineOfFire(pShip, pWeaponToFire, pTarget, iFireAngle, Max(pWeaponToFire->GetMaxEffectiveRange(pShip), DEFAULT_DIST_CHECK))) + { + if (!bDoNotShoot) + pShip->SetWeaponTriggered(pWeaponToFire); + DebugAIOutput(pShip, "FireOnTarget: Fire missile!"); + } + else + DebugAIOutput(pShip, "FireOnTarget: Friendlies in line of fire"); + } } } - else if (iWeapon == -1) + else + { + DebugAIOutput(pShip, "Fire: Weapon NOT aligned"); + +#ifdef DEBUG_SHIP + if (bDebug) + pShip->GetUniverse().DebugOutput("Face target at distance: %d moving at: %d%%c", + (int)(vTarget.Length() / LIGHT_SECOND), + (int)(100.0 * 0 / LIGHT_SPEED)); +#endif + } + } + +void CAIBehaviorCtx::ImplementFireSingleWeaponOnTarget (CShip* pShip, + int iWeapon, + int iWeaponVariant, + CSpaceObject* pTarget, + const CVector& vTarget, + Metric rTargetDist2, + int* retiFireDir, + bool bDoNotShoot) + +// ImplementFireWeaponOnTarget +// +// Fires the given weapon (if aligned) on target + + { + DEBUG_TRY + + int iTick = pShip->GetSystem()->GetTick(); + +#ifdef DEBUG + bool bDebug = pShip->IsSelected(); +#endif + + ASSERT(pTarget); + + // Select the appropriate weapon. If we're not given a weapon, then choose the + // best one. + + DeviceNames iWeaponToFire; + Metric rWeaponRange; + if (iWeapon == -1) { if (((iTick % 30) == 0) - && (m_fHasMultipleWeapons || m_iBestWeapon == devNone)) + && (m_fHasMultipleWeapons || m_iBestWeapon == devNone)) ClearBestWeapon(); CalcBestWeapon(pShip, pTarget, rTargetDist2); @@ -1537,12 +1664,63 @@ void CAIBehaviorCtx::ImplementFireWeaponOnTarget (CShip *pShip, DebugAIOutput(pShip, "Fire: No appropriate weapon found"); return; } - CInstalledDevice* pWeapon = pShip->GetNamedDevice(m_iBestWeapon); - if (pWeapon) - { - pWeaponsToFire.push_back(pWeapon); - rWeaponRanges.push_back(pWeapon->GetMaxEffectiveRange(pShip, pTarget)); - } + + iWeaponToFire = m_iBestWeapon; + rWeaponRange = m_rBestWeaponRange; + } + else + { + iWeaponToFire = pShip->SelectWeapon(iWeapon, iWeaponVariant); + rWeaponRange = pShip->GetWeaponRange(iWeaponToFire); + } + + // See if the chosen weapon can hit the target + + int iAimAngle = pShip->GetRotation(); + int iFireAngle = -1; + int iFacingAngle = -1; + bool bAligned; + bAligned = pShip->IsWeaponAligned(iWeaponToFire, + pTarget, + &iAimAngle, + &iFireAngle, + &iFacingAngle); + bool bAimError = false; + + // iAimAngle is the direction that we should fire in order to hit + // the target. + // + // iFireAngle is the direction in which the weapon will fire. + // + // iFacingAngle is the direction in which the ship should face + // in order for the weapon to hit the target. + FireWeaponIfOnTarget(pShip, pTarget, pShip->GetNamedDevice(iWeaponToFire), rWeaponRange, rTargetDist2, bDoNotShoot, &iFacingAngle, &iAimAngle); + + // Turn to aim, even if weapon is already approximately aligned + + if (retiFireDir) + *retiFireDir = iFacingAngle; + + DEBUG_CATCH + } + +void CAIBehaviorCtx::ImplementFireAllWeaponsOnTarget (CShip* pShip, + int iWeapon, + int iWeaponVariant, + CSpaceObject* pTarget, + const CVector& vTarget, + Metric rTargetDist2, + int* retiFireDir, + bool bDoNotShoot) + { + DEBUG_TRY + + ASSERT(pTarget); + TArray pWeaponsToFire; + TArray rWeaponRanges; + if (iWeapon == -1) + { + GetPrimaryWeaponsToFire(pShip, pTarget, rTargetDist2, pWeaponsToFire, rWeaponRanges); } else { @@ -1550,135 +1728,22 @@ void CAIBehaviorCtx::ImplementFireWeaponOnTarget (CShip *pShip, CInstalledDevice* pWeapon = pShip->GetNamedDevice(iWeaponToFire); if (pWeapon) { - pWeaponsToFire.push_back(pWeapon); - rWeaponRanges.push_back(pWeapon->GetMaxEffectiveRange(pShip, pTarget)); + pWeaponsToFire.Insert(pWeapon); + rWeaponRanges.Insert(pWeapon->GetMaxEffectiveRange(pShip, pTarget)); } } int iWeaponIndex = 0; std::vector> aimAngles; - for (auto& pWeaponToFire : pWeaponsToFire) + for (int iWeaponIndex = 0; iWeaponIndex < pWeaponsToFire.GetCount(); iWeaponIndex++) { + auto& pWeaponToFire = pWeaponsToFire.GetAt(iWeaponIndex); // See if the chosen weapon can hit the target Metric rWeaponRange = rWeaponRanges[iWeaponIndex]; auto weaponDeviceItem = pWeaponToFire->GetDeviceItem(); - - int iAimAngle = pShip->GetRotation(); - int iFireAngle = -1; + int iAngleToTarget = -1; int iFacingAngle = -1; - bool bAligned; - bAligned = pShip->IsWeaponAligned(pWeaponToFire, - pTarget, - &iAimAngle, - &iFireAngle, - &iFacingAngle); - bool bAimError = false; - int iAngleToTarget = iAimAngle; - - // iAimAngle is the direction that we should fire in order to hit - // the target. - // - // iFireAngle is the direction in which the weapon will fire. - // - // iFacingAngle is the direction in which the ship should face - // in order for the weapon to hit the target. - - // There is a chance of missing - - if (pWeaponToFire->IsReady()) - { - if (bAligned) - { - if (mathRandom(1, 100) > GetFireAccuracy()) - { - bAligned = false; - - // In this case, we happen to be aligned, but because of inaccuracy - // reason we think we're not. We clear the aim angle because for - // omnidirectional weapons, we don't want to try to turn towards - // the new aim point. - - iAimAngle = -1; - bAimError = true; - DebugAIOutput(pShip, "Aim error: hold fire when aligned"); - } - } - else if (iAimAngle != -1) - { - if (mathRandom(1, 100) <= m_iPrematureFireChance) - { - int iAimOffset = AngleOffset(iFireAngle, iAimAngle); - if (iAimOffset < 20) - { - bAligned = true; - bAimError = true; - DebugAIOutput(pShip, "Aim error: fire when not aligned"); - } - } - } - } - - // Fire - - if (bAligned) - { -#ifdef DEBUG - { - char szDebug[1024]; - if (bAimError) - wsprintf(szDebug, "%s: false positive iAim=%d iFireAngle=%d", pWeaponToFire->GetName().GetASCIIZPointer(), iAimAngle, iFireAngle); - else if (!pWeaponToFire->IsReady()) - wsprintf(szDebug, "%s: aligned; NOT READY", pWeaponToFire->GetName().GetASCIIZPointer()); - else if (rTargetDist2 > (rWeaponRange * rWeaponRange)) - wsprintf(szDebug, "%s: aligned; TARGET OUT OF RANGE", pWeaponToFire->GetName().GetASCIIZPointer()); - else - wsprintf(szDebug, "%s: aligned", pWeaponToFire->GetName().GetASCIIZPointer()); - - DebugAIOutput(pShip, szDebug); - } -#endif - - // If we're aligned and the weapon is ready, and we're - // in range of the target, then fire! - - if (pWeaponToFire->IsReady() - && rTargetDist2 <= (rWeaponRange * rWeaponRange)) - { - if (pWeaponToFire->GetCategory() == itemcatWeapon) - { - if (CheckForFriendsInLineOfFire(pShip, pWeaponToFire, pTarget, iFireAngle, Max(pWeaponToFire->GetMaxEffectiveRange(pShip), DEFAULT_DIST_CHECK))) - { - if (!bDoNotShoot) - pShip->SetWeaponTriggered(pWeaponToFire); - DebugAIOutput(pShip, "FireOnTarget: Fire primary!"); - } - else - DebugAIOutput(pShip, "FireOnTarget: Friendlies in line of fire"); - } - else - { - if (CheckForFriendsInLineOfFire(pShip, pWeaponToFire, pTarget, iFireAngle, Max(pWeaponToFire->GetMaxEffectiveRange(pShip), DEFAULT_DIST_CHECK))) - { - if (!bDoNotShoot) - pShip->SetWeaponTriggered(pWeaponToFire); - DebugAIOutput(pShip, "FireOnTarget: Fire missile!"); - } - else - DebugAIOutput(pShip, "FireOnTarget: Friendlies in line of fire"); - } - } - } - else - { - DebugAIOutput(pShip, "Fire: Weapon NOT aligned"); - -#ifdef DEBUG_SHIP - if (bDebug) - pShip->GetUniverse().DebugOutput("Face target at distance: %d moving at: %d%%c", - (int)(vTarget.Length() / LIGHT_SECOND), - (int)(100.0 * 0 / LIGHT_SPEED)); -#endif - } + FireWeaponIfOnTarget(pShip, pTarget, pWeaponToFire, rWeaponRange, rTargetDist2, bDoNotShoot, &iFacingAngle, &iAngleToTarget); // Turn to aim, even if weapon is already approximately aligned // If 'FireAllPrimaryWeapons' is set, though, we should ignore guns that have large fire arcs @@ -1690,44 +1755,53 @@ void CAIBehaviorCtx::ImplementFireWeaponOnTarget (CShip *pShip, // Damaged guns or those where GetWeaponEffectiveness returns a negative value should be ignored entirely, along with omni guns. // TODO(heliogenesis): We can greatly improve performance with a std::map, so we only do the calculation if the aim angle in question // is NOT in the std::map (aim angle is the key) and take the highest possible priority we can get (priority is the value) - if (UsesAllPrimaryWeapons()) - { - int iMinFireArc = pWeaponToFire->GetMinFireArc(); - int iMaxFireArc = pWeaponToFire->GetMaxFireArc(); - int iDistanceToFireBoundary = min(min(abs(iMinFireArc - iAngleToTarget), abs(iMaxFireArc - iAngleToTarget)), min(abs((iMinFireArc - 360) - iAngleToTarget), abs((360 + iMaxFireArc) - iAngleToTarget))); - bool bIgnoreThisGun = (weaponDeviceItem.GetWeaponEffectiveness(pTarget) < 0) || pWeaponToFire->IsDamaged() || pWeaponToFire->IsOmniDirectional(); - if (!bIgnoreThisGun) - aimAngles.push_back(std::make_pair(iDistanceToFireBoundary, iFacingAngle)); - } - else - { - if (retiFireDir) - *retiFireDir = iFacingAngle; - } + int iMinFireArc = pWeaponToFire->GetMinFireArc(); + int iMaxFireArc = pWeaponToFire->GetMaxFireArc(); + int iDistanceToFireBoundary = min(min(abs(iMinFireArc - iAngleToTarget), abs(iMaxFireArc - iAngleToTarget)), min(abs((iMinFireArc - 360) - iAngleToTarget), abs((360 + iMaxFireArc) - iAngleToTarget))); + bool bIgnoreThisGun = (weaponDeviceItem.GetWeaponEffectiveness(pTarget) < 0) || pWeaponToFire->IsDamaged() || pWeaponToFire->IsOmniDirectional(); + if (!bIgnoreThisGun) + aimAngles.push_back(std::make_pair(iDistanceToFireBoundary, iFacingAngle)); - iWeaponIndex++; } - if (UsesAllPrimaryWeapons()) + int iLowestScore = 360; + int iFacingAngle = -1; + for (const auto& aimAngle : aimAngles) { - int iLowestScore = 360; - int iFacingAngle = -1; - for (const auto& aimAngle : aimAngles) + if (aimAngle.first < iLowestScore) { - if (aimAngle.first < iLowestScore) - { - iLowestScore = aimAngle.first; - iFacingAngle = aimAngle.second; - } + iLowestScore = aimAngle.first; + iFacingAngle = aimAngle.second; } - if (retiFireDir && iFacingAngle >= 0) - *retiFireDir = iFacingAngle; } - + if (retiFireDir && iFacingAngle >= 0) + *retiFireDir = iFacingAngle; DEBUG_CATCH } +void CAIBehaviorCtx::ImplementFireWeaponOnTarget (CShip *pShip, + int iWeapon, + int iWeaponVariant, + CSpaceObject *pTarget, + const CVector &vTarget, + Metric rTargetDist2, + int *retiFireDir, + bool bDoNotShoot) + +// ImplementFireWeaponOnTarget +// +// Fires the given weapon (if aligned) on target + + { + + if (UsesAllPrimaryWeapons()) + ImplementFireAllWeaponsOnTarget(pShip, iWeapon, iWeaponVariant, pTarget, vTarget, rTargetDist2, retiFireDir, bDoNotShoot); + else + ImplementFireSingleWeaponOnTarget(pShip, iWeapon, iWeaponVariant, pTarget, vTarget, rTargetDist2, retiFireDir, bDoNotShoot); + + } + void CAIBehaviorCtx::ImplementFollowNavPath (CShip *pShip, bool *retbAtDestination) // ImplementFollowNavPath diff --git a/Mammoth/TSE/ShipAIImpl.h b/Mammoth/TSE/ShipAIImpl.h index 17a36949d..708f9ac16 100644 --- a/Mammoth/TSE/ShipAIImpl.h +++ b/Mammoth/TSE/ShipAIImpl.h @@ -146,6 +146,8 @@ class CAIBehaviorCtx void ImplementFireOnTargetsOfOpportunity (CShip *pShip, CSpaceObject *pTarget = NULL, CSpaceObject *pExcludeObj = NULL); void ImplementFireWeapon (CShip *pShip, DeviceNames iDev = devNone); void ImplementFireWeaponOnTarget (CShip *pShip, int iWeapon, int iWeaponVariant, CSpaceObject *pTarget, const CVector &vTarget, Metric rTargetDist2, int *retiFireDir = NULL, bool bDoNotShoot = false); + void ImplementFireSingleWeaponOnTarget (CShip *pShip, int iWeapon, int iWeaponVariant, CSpaceObject *pTarget, const CVector &vTarget, Metric rTargetDist2, int *retiFireDir = NULL, bool bDoNotShoot = false); + void ImplementFireAllWeaponsOnTarget (CShip *pShip, int iWeapon, int iWeaponVariant, CSpaceObject *pTarget, const CVector &vTarget, Metric rTargetDist2, int *retiFireDir = NULL, bool bDoNotShoot = false); void ImplementFollowNavPath (CShip *pShip, bool *retbAtDestination = NULL); void ImplementFormationManeuver (CShip *pShip, const CVector vDest, const CVector vDestVel, int iDestFacing, bool *retbInFormation = NULL); void ImplementGating (CShip *pShip, CSpaceObject *pTarget); @@ -189,6 +191,8 @@ class CAIBehaviorCtx bool CalcFlockingFormationCloud (CShip *pShip, CSpaceObject *pLeader, Metric rFOVRange, Metric rSeparationRange, CVector *retvPos, CVector *retvVel, int *retiFacing); bool CalcFlockingFormationRandom (CShip *pShip, CSpaceObject *pLeader, CVector *retvPos, CVector *retvVel, int *retiFacing); bool ImplementAttackTargetManeuver (CShip *pShip, CSpaceObject *pTarget, const CVector &vTarget, Metric rTargetDist2); + void GetPrimaryWeaponsToFire (CShip* pShip, CSpaceObject *pTarget, Metric rTargetDist2, TArray &pWeaponsToFire, TArray &rWeaponRanges); + void FireWeaponIfOnTarget (CShip* pShip, CSpaceObject *pTarget, CInstalledDevice *pWeaponToFire, Metric rWeaponRange, Metric rTargetDist2, bool bDoNotShoot, int *retiFacingAngle, int *retiAngleToTarget); CAISettings m_AISettings; // Settings From ed32d0f484650754362e5952c96c7458d71eda8c Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Sat, 27 Mar 2021 03:45:17 -0400 Subject: [PATCH 26/32] Fix compile errors --- Mammoth/Include/TSEDevices.h | 2 +- Mammoth/TSE/CSpaceObject.cpp | 2 +- Mammoth/TSE/CWeaponClass.cpp | 9 ++++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Mammoth/Include/TSEDevices.h b/Mammoth/Include/TSEDevices.h index e567c565a..299661d7c 100644 --- a/Mammoth/Include/TSEDevices.h +++ b/Mammoth/Include/TSEDevices.h @@ -250,7 +250,7 @@ class CDeviceClass } CTargetList &GetTargetList (); - void SetTargetList (CTargetList &TargetListArg) + void SetTargetList (CTargetList TargetListArg) { m_pTargetList = &TargetListArg; } diff --git a/Mammoth/TSE/CSpaceObject.cpp b/Mammoth/TSE/CSpaceObject.cpp index a7e163056..495fce813 100644 --- a/Mammoth/TSE/CSpaceObject.cpp +++ b/Mammoth/TSE/CSpaceObject.cpp @@ -3628,7 +3628,7 @@ void CSpaceObject::FireOnSystemWeaponFire (CSpaceObject *pShot, CSpaceObject *pS CCodeChainCtx Ctx(GetUniverse()); Ctx.DefineContainingType(this); Ctx.SaveAndDefineSourceVar(this); - Ctx.DefineInteger(CONSTLIT("aFireRepeat"), iRepeatingCount); // TODO: support aFireCharge + Ctx.DefineInteger(CONSTLIT("aFireRepeat"), iRepeatingCount); Ctx.DefineSpaceObject(CONSTLIT("aShotObj"), pShot); Ctx.DefineSpaceObject(CONSTLIT("aWeaponObj"), pSource); Ctx.DefineInteger(CONSTLIT("aWeaponUNID"), dwItemUNID); diff --git a/Mammoth/TSE/CWeaponClass.cpp b/Mammoth/TSE/CWeaponClass.cpp index 41df46b28..6a7fac4be 100644 --- a/Mammoth/TSE/CWeaponClass.cpp +++ b/Mammoth/TSE/CWeaponClass.cpp @@ -2554,7 +2554,6 @@ bool CWeaponClass::FireWeapon (CInstalledDevice &Device, return false; } - // TODO(heliogenesis): Change to CreateChargeEffect. We will need to call SetFireRepeat in this function, else it is similar to CreateFireEffect. if (Result.bFireEffect) ShotDesc.CreateChargeEffect(Source.GetSystem(), &Source, Shots[i].vPos, CVector(), Shots[i].iDir, ActivateCtx.iChargeFrame); @@ -5556,8 +5555,12 @@ void CWeaponClass::Update (CInstalledDevice *pDevice, CSpaceObject *pSource, SDe if ((dwContinuous % iContinuousDelay) == 0 || (int(dwContinuous) > iBurstLengthInFrames)) { - if (ActivateCtx.TargetList.IsEmpty()) - ActivateCtx.TargetList = pSource->GetTargetList(); + CTargetList pSourceTargetList; + if (ActivateCtx.GetTargetList().IsEmpty()) + { + pSourceTargetList = pSource->GetTargetList(); + ActivateCtx.SetTargetList(pSourceTargetList); + } ActivateCtx.iRepeatingCount = 1 + iContinuous - min(int(dwContinuous) / iContinuousDelay, iContinuous + 1); ActivateCtx.iChargeFrame = 1 + iChargeTime - min(int(dwContinuous) - iBurstLengthInFrames, iChargeTime + 1); From 76e3ae79ad3d4a427346606809790af420aae33f Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Sun, 6 Jun 2021 15:31:31 -0400 Subject: [PATCH 27/32] Make GetChargeTime public --- Mammoth/Include/TSEWeaponClassImpl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mammoth/Include/TSEWeaponClassImpl.h b/Mammoth/Include/TSEWeaponClassImpl.h index 98bb0a9f7..88dcea82c 100644 --- a/Mammoth/Include/TSEWeaponClassImpl.h +++ b/Mammoth/Include/TSEWeaponClassImpl.h @@ -110,6 +110,7 @@ class CWeaponClass : public CDeviceClass CItemType *GetAmmoItem (int iIndex) const; int GetAmmoItemCount (void) const; + int GetChargeTime (const CWeaponFireDesc& Shot) const; const CConfigurationDesc &GetConfiguration (const CWeaponFireDesc &ShotDesc) const; int GetContinuous (const CWeaponFireDesc &Shot) const; bool GetContinuousConsumePerShot (const CWeaponFireDesc &Shot) const { return m_bContinuousConsumePerShot; } @@ -286,7 +287,6 @@ class CWeaponClass : public CDeviceClass CSpaceObject *pTarget, int iRepeatingCount, int iShotNumber); - int GetChargeTime (const CWeaponFireDesc& Shot) const; int GetContinuousFireDelay (const CWeaponFireDesc &Shot) const; int GetFireDelay (const CWeaponFireDesc &ShotDesc) const; const CWeaponFireDesc *GetReferenceShotData (const CWeaponFireDesc *pShot, int *retiFragments = NULL) const; From aaadf167e79b31f46295a749a0bfe50140b7e57e Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Thu, 29 Jul 2021 23:12:02 -0400 Subject: [PATCH 28/32] Add support for limited traverse rates on swivel weapons; still need to add property support --- Mammoth/Include/TSEDevices.h | 17 ++++- Mammoth/Include/TSEWeaponClassImpl.h | 12 +++- Mammoth/TSE/CWeaponClass.cpp | 100 ++++++++++++++++++++++----- 3 files changed, 109 insertions(+), 20 deletions(-) diff --git a/Mammoth/Include/TSEDevices.h b/Mammoth/Include/TSEDevices.h index 2d1c97bd9..81b4f798d 100644 --- a/Mammoth/Include/TSEDevices.h +++ b/Mammoth/Include/TSEDevices.h @@ -326,7 +326,7 @@ class CDeviceClass virtual const CRepairerClass *AsRepairerClass (void) const { return NULL; } virtual CShieldClass *AsShieldClass (void) { return NULL; } virtual CWeaponClass *AsWeaponClass (void) { return NULL; } - virtual bool CalcFireSolution (const CInstalledDevice &Device, CSpaceObject &Target, int *retiAimAngle = NULL, Metric *retrDist = NULL) const { return false; } + virtual bool CalcFireSolution (const CInstalledDevice &Device, const CSpaceObject &Target, int *retiAimAngle = NULL, Metric *retrDist = NULL) const { return false; } virtual int CalcPowerUsed (SUpdateCtx &Ctx, CInstalledDevice *pDevice, CSpaceObject *pSource) { return 0; } virtual bool CanHitFriends (void) const { return true; } virtual void Deplete (CInstalledDevice *pDevice, CSpaceObject *pSource) { } @@ -362,6 +362,9 @@ class CDeviceClass bool bUseCustomAmmoCountHandler = false) { if (retsLabel) *retsLabel = NULL_STR; if (retiAmmoLeft) *retiAmmoLeft = -1; if (retpType) *retpType = NULL; } virtual Metric GetShotSpeed (CItemCtx &Ctx) const { return 0.0; } virtual void GetStatus (const CInstalledDevice *pDevice, const CSpaceObject *pSource, int *retiStatus, int *retiMaxStatus) { *retiStatus = 0; *retiMaxStatus = 0; } + virtual int GetSwivelPivotPerTick (const CInstalledDevice* pDevice) const { return 360; } + virtual float GetSwivelPivotPerTickExact (const CInstalledDevice* pDevice) const { return 360.0; } + virtual int GetSwivelUpdateRate (const CInstalledDevice* pDevice) const { return 0; } virtual DWORD GetTargetTypes (const CDeviceItem &DeviceItem) const { return 0; } virtual int GetValidVariantCount (CSpaceObject *pSource, CInstalledDevice *pDevice) { return 0; } virtual int GetWeaponEffectiveness (const CDeviceItem &DeviceItem, CSpaceObject *pTarget) const { return 0; } @@ -690,6 +693,7 @@ class CInstalledDevice int GetDefaultFireAngle (void) const { return m_pClass->GetDefaultFireAngle(*this); } int GetHitPoints (CItemCtx &ItemCtx, int *retiMaxHP = NULL) const { return m_pClass->GetHitPoints(ItemCtx, retiMaxHP); } CSpaceObject *GetLastShot (CSpaceObject *pSource, int iIndex) const; + int GetLastSwivelTime (void) const { return m_iLastSwivelTime; } Metric GetMaxEffectiveRange (CSpaceObject *pSource, CSpaceObject *pTarget = NULL) const { return m_pClass->GetMaxEffectiveRange(pSource, this, pTarget); } Metric GetMaxRange (CItemCtx &ItemCtx) const { return m_pClass->GetMaxRange(ItemCtx); } CString GetName (void) { return m_pClass->GetName(); } @@ -705,6 +709,9 @@ class CInstalledDevice CSpaceObject *GetSource (void) const { return m_pSource; } CSpaceObject &GetSourceOrThrow (void) const { if (m_pSource) return *m_pSource; else throw CException(ERR_FAIL); } void GetStatus (const CSpaceObject *pSource, int *retiStatus, int *retiMaxStatus) const { m_pClass->GetStatus(this, pSource, retiStatus, retiMaxStatus); } + int GetSwivelPivotPerTick (void) const { return m_iSwivelPivotPerTick; } + float GetSwivelPivotPerTickExact (void) { return m_iSwivelUpdateRate == 0 ? 360 : float(m_iSwivelPivotPerTick) / float(m_iSwivelUpdateRate); } + int GetSwivelUpdateRate (void) const { return m_iSwivelUpdateRate; } CSpaceObject *GetTarget (CSpaceObject *pSource) const; int GetValidVariantCount (CSpaceObject *pSource) { return m_pClass->GetValidVariantCount(pSource, this); } CWeaponTargetDefinition *GetWeaponTargetDefinition (void) const { return (m_pWeaponTargetDefinition.get()); } @@ -727,6 +734,10 @@ class CInstalledDevice void SetHitPoints (CItemCtx &ItemCtx, int iHP) { m_pClass->SetHitPoints(ItemCtx, iHP); } void SetLastShot (CSpaceObject *pObj, int iIndex); void SetLastShotCount (int iCount); + void SetLastSwivelTime (int iLastSwivelTime) { m_iLastSwivelTime = iLastSwivelTime; } + void SetSwivelPivotPerTick (int iSwivelPivotPerTick) { m_iSwivelPivotPerTick = iSwivelPivotPerTick; } + void SetSwivelPivotPerTick (float fSwivelPivotPerTick) { m_iSwivelPivotPerTick = fSwivelPivotPerTick >= 1.0 ? int(fSwivelPivotPerTick) : 1; m_iSwivelUpdateRate = fSwivelPivotPerTick >= 1.0 ? 1 : int(1.0 / fSwivelPivotPerTick); } + void SetSwivelUpdateRate (int iSwivelUpdateRate) { m_iSwivelUpdateRate = iSwivelUpdateRate; } inline void SetTarget (CSpaceObject *pObj); bool ShowActivationDelayCounter (CSpaceObject *pSource) { return m_pClass->ShowActivationDelayCounter(pSource, this); } @@ -764,6 +775,10 @@ class CInstalledDevice int m_iPosZ:16 = 0; // Position of installation (height) int m_iMinFireArc:16 = 0; // Min angle of fire arc (degrees) int m_iMaxFireArc:16 = 0; // Max angle of fire arc (degrees) + int m_iSwivelPivotPerTick : 16 = -1; // Max angle the weapon can pivot during a single tick (degrees) + int m_iSwivelUpdateRate : 16 = -1; // Update weapon firing angle every Nth tick during a burst (ticks) + // TODO(heliogenesis): Save swivel values in the save file, and add support for it in device slots and property functions + int m_iLastSwivelTime : 16 = 32767; // Last tick that the weapon updated its firing angle during (tick) int m_iShotSeparationScale:16 = 32767; // Scaled by 32767. Governs scaling of shot separation for dual etc weapons int m_iMaxFireRange:16 = 0; // Max effective fire range (in light-seconds); 0 = no limit diff --git a/Mammoth/Include/TSEWeaponClassImpl.h b/Mammoth/Include/TSEWeaponClassImpl.h index 30c625028..20b8b600c 100644 --- a/Mammoth/Include/TSEWeaponClassImpl.h +++ b/Mammoth/Include/TSEWeaponClassImpl.h @@ -133,7 +133,7 @@ class CWeaponClass : public CDeviceClass virtual bool Activate (CInstalledDevice &Device, SActivateCtx &ActivateCtx) override; virtual CWeaponClass *AsWeaponClass (void) override { return this; } - virtual bool CalcFireSolution (const CInstalledDevice &Device, CSpaceObject &Target, int *retiAimAngle = NULL, Metric *retrDist = NULL) const override; + virtual bool CalcFireSolution (const CInstalledDevice &Device, const CSpaceObject &Target, int *retiAimAngle = NULL, Metric *retrDist = NULL) const override; virtual int CalcPowerUsed (SUpdateCtx &Ctx, CInstalledDevice *pDevice, CSpaceObject *pSource) override; virtual ICCItem *FindAmmoItemProperty (CItemCtx &Ctx, const CItem &Ammo, const CString &sProperty) override; virtual int GetActivateDelay (CItemCtx &ItemCtx) const override; @@ -161,6 +161,9 @@ class CWeaponClass : public CDeviceClass CItemType **retpType = NULL, bool bUseCustomAmmoCountHandler = false) override; virtual Metric GetShotSpeed (CItemCtx &Ctx) const override; + virtual int GetSwivelPivotPerTick (const CInstalledDevice* pDevice) const override; + virtual float GetSwivelPivotPerTickExact (const CInstalledDevice* pDevice) const override; + virtual int GetSwivelUpdateRate (const CInstalledDevice* pDevice) const override; virtual DWORD GetTargetTypes (const CDeviceItem &DeviceItem) const override; virtual int GetValidVariantCount (CSpaceObject *pSource, CInstalledDevice *pDevice) override; virtual int GetWeaponEffectiveness (const CDeviceItem &DeviceItem, CSpaceObject *pTarget) const override; @@ -249,7 +252,8 @@ class CWeaponClass : public CDeviceClass Metric CalcConfigurationMultiplier (const CWeaponFireDesc *pShot = NULL, bool bIncludeFragments = true) const; Metric CalcDamage (const CWeaponFireDesc &ShotDesc, const CItemEnhancementStack *pEnhancements = NULL, DWORD dwDamageFlags = 0) const; Metric CalcDamagePerShot (const CWeaponFireDesc &ShotDesc, const CItemEnhancementStack *pEnhancements = NULL, DWORD dwDamageFlags = 0) const; - int CalcFireAngle (CItemCtx &ItemCtx, Metric rSpeed, CSpaceObject *pTarget, bool *retbSetDeviceAngle = NULL) const; + int CalcFireAngle (CItemCtx &ItemCtx, Metric rSpeed, const CSpaceObject *pTarget, bool *retbSetDeviceAngle = NULL) const; + int CalcFireAngleRestrictedBySwivelRate (const int iFireAngle, const CInstalledDevice* pDevice, const CSpaceObject* pSource) const; int CalcLevel (const CWeaponFireDesc &ShotDesc) const; TArray CalcMIRVTargets (CInstalledDevice &Device, const CTargetList &TargetList, int iMaxCount) const; int CalcReachableFireAngle (const CInstalledDevice &Device, int iDesiredAngle, int iDefaultAngle = -1) const; @@ -308,7 +312,7 @@ class CWeaponClass : public CDeviceClass bool IsMIRV (const CWeaponFireDesc &ShotDesc) const { return (m_bMIRV || ShotDesc.IsMIRV()); } bool IsSinglePointOrigin (void) const { return m_Configuration.IsSinglePointOrigin(); } bool IsTemperatureEnabled (void) { return (m_Counter == EDeviceCounterType::Temperature); } - bool IsTargetReachable (const CInstalledDevice &Device, CSpaceObject &Target, int iDefaultFireAngle = -1, int *retiFireAngle = NULL, int *retiAimAngle = NULL) const; + bool IsTargetReachable (const CInstalledDevice &Device, const CSpaceObject &Target, int iDefaultFireAngle = -1, int *retiFireAngle = NULL, int *retiAimAngle = NULL) const; bool IsTracking (const CDeviceItem &DeviceItem, const CWeaponFireDesc *pShot) const; bool UpdateTemperature (CItemCtx &ItemCtx, const CWeaponFireDesc &ShotDesc, CFailureDesc::EFailureTypes *retiFailureMode, bool *retbSourceDestroyed); bool UsesAmmo (void) const { return (m_ShotData.GetCount() > 0 && m_ShotData[0].pDesc->GetAmmoType() != NULL); } @@ -344,6 +348,8 @@ class CWeaponClass : public CDeviceClass int m_iContinuousFireDelay = 0; // Delay between shots bool m_bContinuousConsumePerShot; // If a continuous weapon, consume ammunition for every shot in burst bool m_bBurstTracksTargets; // If the weapon is continuous, whether or not to track the target during the entire burst + int m_iSwivelPivotPerTick = 0; // Max angle the weapon can pivot during a single tick (degrees) + int m_iSwivelUpdateRate = 0; // Update weapon firing angle every Nth tick during a burst (ticks) bool m_bCharges; // TRUE if weapon has charges instead of ammo bool m_bUsesLauncherControls; // TRUE if weapon is selected/fired as a launcher instead of as a primary gun diff --git a/Mammoth/TSE/CWeaponClass.cpp b/Mammoth/TSE/CWeaponClass.cpp index b8a5dbc3a..eb9cab761 100644 --- a/Mammoth/TSE/CWeaponClass.cpp +++ b/Mammoth/TSE/CWeaponClass.cpp @@ -29,6 +29,7 @@ #define LAUNCHER_ATTRIB CONSTLIT("launcher") #define LINKED_FIRE_ATTRIB CONSTLIT("linkedFire") #define MAX_FIRE_ARC_ATTRIB CONSTLIT("maxFireArc") +#define MAX_SWIVEL_PER_TICK_ATTRIB CONSTLIT("maxSwivelPerTick") #define MIN_FIRE_ARC_ATTRIB CONSTLIT("minFireArc") #define MULTI_TARGET_ATTRIB CONSTLIT("multiTarget") #define CAN_FIRE_WHEN_BLIND_ATTRIB CONSTLIT("canFireWhenBlind") @@ -941,7 +942,7 @@ Metric CWeaponClass::CalcDamagePerShot (const CWeaponFireDesc &ShotDesc, const C return CalcConfigurationMultiplier(&ShotDesc, false) * CalcDamage(ShotDesc, pEnhancements, dwDamageFlags); } -int CWeaponClass::CalcFireAngle (CItemCtx &ItemCtx, Metric rSpeed, CSpaceObject *pTarget, bool *retbSetDeviceAngle) const +int CWeaponClass::CalcFireAngle (CItemCtx &ItemCtx, Metric rSpeed, const CSpaceObject *pTarget, bool *retbSetDeviceAngle) const // CalcFireAngle // @@ -998,7 +999,31 @@ int CWeaponClass::CalcFireAngle (CItemCtx &ItemCtx, Metric rSpeed, CSpaceObject } } -bool CWeaponClass::CalcFireSolution (const CInstalledDevice &Device, CSpaceObject &Target, int *retiAimAngle, Metric *retrDist) const +int CWeaponClass::CalcFireAngleRestrictedBySwivelRate (const int iFireAngle, const CInstalledDevice *pDevice, const CSpaceObject *pSource) const +// If the fire angle is restricted due to traversal limits, then restrict the fire angle to the bounds. + + { + int iNewFireAngle = iFireAngle; + int iSwivelPivotPerTick = GetSwivelPivotPerTick(pDevice); + if (m_bBurstTracksTargets && iSwivelPivotPerTick > 0 && pDevice->GetFireAngle() != -1) + { + float fPivotAmount = float(pDevice->GetLastSwivelTime() - pDevice->GetContinuousFire()) / max(1.0f, float(GetSwivelUpdateRate(pDevice))); + int pivotAmount = int(fPivotAmount) * iSwivelPivotPerTick; + int iShipRotation = pSource->GetRotation(); + int iOldFireAngle = AngleMod(pDevice->GetFireAngle() + iShipRotation); + int iMinDelta = AngleMod(iOldFireAngle - pivotAmount); + int iMaxDelta = AngleMod(iOldFireAngle + pivotAmount); + if (AngleMod(abs(iFireAngle - iOldFireAngle)) > pivotAmount) + { + int iDistToMinDelta = AngleMod(iMinDelta - iFireAngle); + int iDistToMaxDelta = AngleMod(iFireAngle - iMaxDelta); + iNewFireAngle = iDistToMinDelta < iDistToMaxDelta ? iMinDelta : iMaxDelta; + } + } + return iNewFireAngle; + } + +bool CWeaponClass::CalcFireSolution (const CInstalledDevice &Device, const CSpaceObject &Target, int *retiAimAngle, Metric *retrDist) const // CalcFireSolution // @@ -1325,6 +1350,9 @@ bool CWeaponClass::CalcSingleTarget (CInstalledDevice &Device, // use the same value already stored. retbSetFireAngle = false; + int iContinuousFire = Device.GetContinuousFire(); + int iSwivelUpdateRate = GetSwivelUpdateRate(&Device); + bool bCanUpdateFireAngle = (Device.GetLastSwivelTime() - iContinuousFire) >= iSwivelUpdateRate || iSwivelUpdateRate == 0; // If we need a target, then get it from the device. @@ -1332,10 +1360,14 @@ bool CWeaponClass::CalcSingleTarget (CInstalledDevice &Device, { retpTarget = Device.GetTarget(&Source); retiFireAngle = Device.GetFireAngle(); + if (m_bBurstTracksTargets) + { + retiFireAngle = AngleMod(retiFireAngle + Source.GetRotation()); + } // If necessary, we recompute the fire angle - if (retiFireAngle == -1 || m_bBurstTracksTargets) + if (retiFireAngle == -1 || (m_bBurstTracksTargets && bCanUpdateFireAngle)) { if (retpTarget) { @@ -1356,6 +1388,18 @@ bool CWeaponClass::CalcSingleTarget (CInstalledDevice &Device, else retiFireAngle = -1; } + + if (GetSwivelPivotPerTick(&Device) > 0 && m_bBurstTracksTargets && retiFireAngle == -1) + { + retiFireAngle = Device.GetDefaultFireAngle(); + } + + if (retiFireAngle != -1) + { + retiFireAngle = CalcFireAngleRestrictedBySwivelRate(retiFireAngle, &Device, &Source); + Device.SetFireAngle(AngleMod(retiFireAngle - (m_bBurstTracksTargets ? Source.GetRotation() : 0))); + } + Device.SetLastSwivelTime(iContinuousFire); } } @@ -1374,7 +1418,6 @@ bool CWeaponClass::CalcSingleTarget (CInstalledDevice &Device, else { CDeviceItem DeviceItem = Device.GetDeviceItem(); - switch (DeviceItem.CalcTargetType()) { case CDeviceItem::calcNoTarget: @@ -1417,6 +1460,7 @@ bool CWeaponClass::CalcSingleTarget (CInstalledDevice &Device, default: return false; } + Device.SetLastSwivelTime(GetChargeTime(ShotDesc) + ((1 + GetContinuous(ShotDesc)) * (GetContinuousFireDelay(ShotDesc) + 1))); } // Fire! @@ -1924,12 +1968,15 @@ ALERROR CWeaponClass::CreateFromXML (SDesignLoadCtx &Ctx, CXMLElement *pDesc, CI pWeapon->m_iContinuous = pDesc->GetAttributeIntegerBounded(REPEATING_ATTRIB, 0, -1, 0); pWeapon->m_iContinuousFireDelay = pDesc->GetAttributeIntegerBounded(REPEATING_DELAY_ATTRIB, 0, -1, 0); + Metric fSwivelPerTick = pDesc->GetAttributeFloat(MAX_SWIVEL_PER_TICK_ATTRIB); + pWeapon->m_iSwivelPivotPerTick = fSwivelPerTick > 0.0 ? max(1, int(fSwivelPerTick)) : 0; + pWeapon->m_iSwivelUpdateRate = fSwivelPerTick >= 1.0 || fSwivelPerTick <= 0.0 ? 0 : int(1.0 / fSwivelPerTick); // NOTE: For now we don't support a combination of repeating fire and // repeating delay that exceeds 254. if (pWeapon->m_iContinuous > CONTINUOUS_DATA_LIMIT - || pWeapon->m_iContinuous * pWeapon->m_iContinuousFireDelay > CONTINUOUS_DATA_LIMIT) + || pWeapon->m_iContinuous * (pWeapon->m_iContinuousFireDelay + 1) > CONTINUOUS_DATA_LIMIT) { Ctx.sError = CONSTLIT("Unfortunately, that combination of repeating= and repeatingDelay= is too high for the engine."); return ERR_FAIL; @@ -2502,6 +2549,10 @@ bool CWeaponClass::FireWeapon (CInstalledDevice &Device, bool bSetFireAngle; int iFireAngle; + if (ActivateCtx.iRepeatingCount == 0 && ActivateCtx.iChargeFrame == 0) + { + Device.SetFireAngle(-1); + } CShotArray Shots = CalcShotsFired(Device, ShotDesc, ActivateCtx, iFireAngle, bSetFireAngle); if (Shots.GetCount() == 0) return false; @@ -2549,11 +2600,8 @@ bool CWeaponClass::FireWeapon (CInstalledDevice &Device, Device.SetTarget(Shots[0].pTarget); if (bSetFireAngle) { - Device.SetFireAngle(iFireAngle); - } - else if (ActivateCtx.iRepeatingCount == 0) - { - Device.SetFireAngle(-1); + CSpaceObject& Source = Device.GetSourceOrThrow(); + Device.SetFireAngle(AngleMod(iFireAngle - (m_bBurstTracksTargets ? Source.GetRotation() : 0))); } return true; } @@ -2591,11 +2639,8 @@ bool CWeaponClass::FireWeapon (CInstalledDevice &Device, Device.SetTarget(Shots[0].pTarget); if (bSetFireAngle) { - Device.SetFireAngle(iFireAngle); - } - else if (ActivateCtx.iRepeatingCount == 0) - { - Device.SetFireAngle(-1); + CSpaceObject& Source = Device.GetSourceOrThrow(); + Device.SetFireAngle(AngleMod(iFireAngle - (m_bBurstTracksTargets ? Source.GetRotation() : 0))); } // Increment polarity, if necessary @@ -4029,6 +4074,29 @@ const CWeaponClass::SStdStats &CWeaponClass::GetStdStats (int iLevel) } } +int CWeaponClass::GetSwivelPivotPerTick (const CInstalledDevice* pDevice) const + { + int deviceSwivelPivot = pDevice->GetSwivelPivotPerTick(); + return deviceSwivelPivot > 0 ? deviceSwivelPivot : m_iSwivelPivotPerTick; + } + +float CWeaponClass::GetSwivelPivotPerTickExact (const CInstalledDevice* pDevice) const + { + int deviceSwivelPivot = pDevice->GetSwivelPivotPerTick(); + int deviceSwivelUpdateRate = pDevice->GetSwivelUpdateRate(); + if (deviceSwivelPivot >= 0 && deviceSwivelUpdateRate >= 0) + { + return deviceSwivelUpdateRate == 0 ? 360 : float(deviceSwivelPivot) / float(deviceSwivelUpdateRate); + } + return m_iSwivelUpdateRate == 0 ? 360 : float(m_iSwivelPivotPerTick) / float(m_iSwivelUpdateRate); + } + +int CWeaponClass::GetSwivelUpdateRate (const CInstalledDevice* pDevice) const + { + int deviceSwivelUpdateRate = pDevice->GetSwivelUpdateRate(); + return deviceSwivelUpdateRate > 0 ? deviceSwivelUpdateRate : m_iSwivelUpdateRate; + } + int CWeaponClass::GetValidVariantCount (CSpaceObject *pSource, CInstalledDevice *pDevice) // GetValidVariantCount @@ -4756,7 +4824,7 @@ bool CWeaponClass::IsStdDamageType (DamageTypes iDamageType, int iLevel) return (iLevel >= iTierLevel && iLevel < iTierLevel + 3); } -bool CWeaponClass::IsTargetReachable (const CInstalledDevice &Device, CSpaceObject &Target, int iDefaultFireAngle, int *retiFireAngle, int *retiAimAngle) const +bool CWeaponClass::IsTargetReachable (const CInstalledDevice &Device, const CSpaceObject &Target, int iDefaultFireAngle, int *retiFireAngle, int *retiAimAngle) const // IsTargetReachable // From 2a550214d2871a537565ce98e768fbbf4f2ee9cc Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Sat, 15 Jan 2022 02:18:38 -0500 Subject: [PATCH 29/32] Refactor charging code and clean up PR --- Mammoth/Include/TSEDevices.h | 16 ++-- Mammoth/Include/TSEWeaponClassImpl.h | 6 +- Mammoth/TSE/CWeaponClass.cpp | 113 ++++++++++++++------------- 3 files changed, 72 insertions(+), 63 deletions(-) diff --git a/Mammoth/Include/TSEDevices.h b/Mammoth/Include/TSEDevices.h index df3682ce8..87e84d66f 100644 --- a/Mammoth/Include/TSEDevices.h +++ b/Mammoth/Include/TSEDevices.h @@ -222,24 +222,27 @@ class CDeviceClass struct SActivateCtx { - SActivateCtx (SUpdateCtx &Ctx, CSpaceObject *pTargetArg, int iFireAngleArg = -1) : + SActivateCtx (SUpdateCtx &Ctx, CSpaceObject *pTargetArg, int iFireAngleArg = -1, bool bIsChargingArg = false) : m_ObjCtx(Ctx), pTarget(pTargetArg), - iFireAngle(iFireAngleArg) + iFireAngle(iFireAngleArg), + bIsCharging(bIsChargingArg) { } - SActivateCtx (SUpdateCtx &Ctx, CSpaceObject *pTargetArg, CTargetList &TargetListArg, int iFireAngleArg = -1) : + SActivateCtx (SUpdateCtx &Ctx, CSpaceObject *pTargetArg, CTargetList &TargetListArg, int iFireAngleArg = -1, bool bIsChargingArg = false) : m_ObjCtx(Ctx), pTarget(pTargetArg), m_pTargetList(&TargetListArg), - iFireAngle(iFireAngleArg) + iFireAngle(iFireAngleArg), + bIsCharging(bIsChargingArg) { } - SActivateCtx (SDeviceUpdateCtx &UpdateCtx, CSpaceObject *pTargetArg = NULL, int iFireAngleArg = -1) : + SActivateCtx (SDeviceUpdateCtx &UpdateCtx, CSpaceObject *pTargetArg = NULL, int iFireAngleArg = -1, bool bIsChargingArg = false) : m_ObjCtx(UpdateCtx.GetObjCtx()), m_pTargetList(UpdateCtx.GetTargetListOverride()), pTarget(pTargetArg), - iFireAngle(iFireAngleArg) + iFireAngle(iFireAngleArg), + bIsCharging(bIsChargingArg) { } @@ -258,6 +261,7 @@ class CDeviceClass int iRepeatingCount = 0; int iChargeFrame = 0; + bool bIsCharging = false; // Status results diff --git a/Mammoth/Include/TSEWeaponClassImpl.h b/Mammoth/Include/TSEWeaponClassImpl.h index 30c625028..1041c7b0c 100644 --- a/Mammoth/Include/TSEWeaponClassImpl.h +++ b/Mammoth/Include/TSEWeaponClassImpl.h @@ -256,7 +256,8 @@ class CWeaponClass : public CDeviceClass CShotArray CalcShotsFired (CInstalledDevice &Device, const CWeaponFireDesc &ShotDesc, SActivateCtx &ActivateCtx, int &retiFireAngle, bool &retbSetFireAngle) const; bool CalcSingleTarget (CInstalledDevice &Device, const CWeaponFireDesc &ShotDesc, SActivateCtx &ActivateCtx, int &retiFireAngle, CSpaceObject *&retpTarget, bool &retbSetFireAngle) const; bool CanConsumeAmmo (const CDeviceItem &DeviceItem, const CWeaponFireDesc &ShotDesc, int iRepeatingCount, int &retiAmmoToConsume) const; - bool CanConsumeShipCounter (const CDeviceItem &DeviceItem, const CWeaponFireDesc &ShotDesc) const; + bool CanConsumeShipCounter (const CDeviceItem &DeviceItem, const CWeaponFireDesc &ShotDesc) const; // TODO(heliogenesis: Rename to 'CanConsumeShipHeat') + bool ChargeWeapon (const bool bSetFireAngle, const int iFireAngle, const CWeaponFireDesc& ShotDesc, CDeviceItem& DeviceItem, CShotArray& Shots, SActivateCtx& ActivateCtx, CInstalledDevice& Device); EFireResults Consume (CDeviceItem &DeviceItem, const CWeaponFireDesc &ShotDesc, int iRepeatingCount, bool *retbConsumedItems = NULL); void ConsumeAmmo (CItemCtx &ItemCtx, const CWeaponFireDesc &ShotDesc, int iRepeatingCount, int iAmmoToConsume, bool *retbConsumed); bool ConsumeCapacitor (CItemCtx &ItemCtx, const CWeaponFireDesc &ShotDesc); @@ -283,8 +284,7 @@ class CWeaponClass : public CDeviceClass SShotFireResult& retResult); bool FireWeapon (CInstalledDevice &Device, const CWeaponFireDesc &ShotDesc, - SActivateCtx &ActivateCtx, - const bool IsCharging = false); + SActivateCtx &ActivateCtx); void FireWeaponShot (CSpaceObject *pSource, CInstalledDevice *pDevice, const CWeaponFireDesc &ShotDesc, diff --git a/Mammoth/TSE/CWeaponClass.cpp b/Mammoth/TSE/CWeaponClass.cpp index 3b878e11f..e9c916e00 100644 --- a/Mammoth/TSE/CWeaponClass.cpp +++ b/Mammoth/TSE/CWeaponClass.cpp @@ -260,7 +260,8 @@ bool CWeaponClass::Activate (CInstalledDevice &Device, SActivateCtx &ActivateCtx // Fire the weapon if it isn't a charging weapon - bool bSuccess = FireWeapon(Device, *pShotDesc, ActivateCtx, GetChargeTime(*pShotDesc) > 0); + ActivateCtx.bIsCharging = GetChargeTime(*pShotDesc) > 0; + bool bSuccess = FireWeapon(Device, *pShotDesc, ActivateCtx); // If firing the weapon destroyed the ship, then we bail out @@ -283,11 +284,11 @@ bool CWeaponClass::Activate (CInstalledDevice &Device, SActivateCtx &ActivateCtx // bSuccess is false here (we technically didn't fire any shots by charging) if (GetChargeTime(*pShotDesc) > 0) - { + { SetContinuousFire(&Device, CONTINUOUS_START); // Return true so we consume power return true; - } + } // If this is a continuous fire weapon then set the device data // We set to -1 because we skip the first Update after the call @@ -2483,10 +2484,59 @@ bool CWeaponClass::FireOnFireWeapon (CItemCtx &ItemCtx, return true; } +bool CWeaponClass::ChargeWeapon (const bool bSetFireAngle, const int iFireAngle, const CWeaponFireDesc &ShotDesc, CDeviceItem &DeviceItem, CShotArray &Shots, SActivateCtx &ActivateCtx, CInstalledDevice &Device) + +// ChargeWeapon +// +// Handle charging for charging weapons. Returns TRUE if we should consume power, etc. + { + for (int i = 0; i < Shots.GetCount(); i++) + { + // If we're using ship heat, make sure we have enough. + + if (!CanConsumeShipHeat(DeviceItem, ShotDesc)) + return false; + + // Update the ship energy/heat counter. + + if (m_iHeatPerShot != 0) + ConsumeShipHeat(DeviceItem, ShotDesc); + + CSpaceObject& Source = Device.GetSourceOrThrow(); + CItemCtx ItemCtx(&Source, &Device); + SShotFireResult Result; + if (FireOnChargeWeapon(ItemCtx, + ShotDesc, + Shots[i].vPos, + Shots[i].pTarget, + Shots[i].iDir, + ActivateCtx.iChargeFrame, + Result)) + { + if (Source.IsDestroyed()) + return false; + } + + if (Result.bFireEffect) + ShotDesc.CreateChargeEffect(Source.GetSystem(), &Source, Shots[i].vPos, CVector(), Shots[i].iDir, ActivateCtx.iChargeFrame); + + if (Result.bSoundEffect) + ShotDesc.PlayChargeSound(&Source); + } + + // Set the device angle so that repeating weapons can get access to it. + Device.SetTarget(Shots[0].pTarget); + if (bSetFireAngle) + { + CSpaceObject& Source = Device.GetSourceOrThrow(); + Device.SetFireAngle(AngleMod(iFireAngle - (m_bBurstTracksTargets ? Source.GetRotation() : 0))); + } + return true; + } + bool CWeaponClass::FireWeapon (CInstalledDevice &Device, const CWeaponFireDesc &ShotDesc, - SActivateCtx &ActivateCtx, - const bool IsCharging) + SActivateCtx &ActivateCtx) // FireWeapon // @@ -2507,54 +2557,8 @@ bool CWeaponClass::FireWeapon (CInstalledDevice &Device, // If we're charging, then we don't fire shots - instead we only increment the ship counter (if needed), // create the fire effect, and return True (so that we consume power). Charging does not consume ammo, // and cannot fail due to damaged or disrupted weapons. - if (IsCharging) - { - for (int i = 0; i < Shots.GetCount(); i++) - { - // If we're using ship counters, make sure we have enough. - - if (!CanConsumeShipCounter(DeviceItem, ShotDesc)) - return false; - - // Update the ship energy/heat counter. - - if (m_iCounterPerShot != 0) - ConsumeShipCounter(DeviceItem, ShotDesc); - - CSpaceObject& Source = Device.GetSourceOrThrow(); - CItemCtx ItemCtx(&Source, &Device); - SShotFireResult Result; - if (FireOnChargeWeapon(ItemCtx, - ShotDesc, - Shots[i].vPos, - Shots[i].pTarget, - Shots[i].iDir, - ActivateCtx.iChargeFrame, - Result)) - { - if (Source.IsDestroyed()) - return false; - } - - if (Result.bFireEffect) - ShotDesc.CreateChargeEffect(Source.GetSystem(), &Source, Shots[i].vPos, CVector(), Shots[i].iDir, ActivateCtx.iChargeFrame); - - if (Result.bSoundEffect) - ShotDesc.PlayChargeSound(&Source); - } - - // Set the device angle so that repeating weapons can get access to it. - Device.SetTarget(Shots[0].pTarget); - if (bSetFireAngle) - { - Device.SetFireAngle(iFireAngle); - } - else if (ActivateCtx.iRepeatingCount == 0) - { - Device.SetFireAngle(-1); - } - return true; - } + if (ActivateCtx.bIsCharging) + return ChargeWeapon(bSetFireAngle, iFireAngle, ShotDesc, DeviceItem, Shots, ActivateCtx, Device); // Figure out when happens when we try to consume ammo, etc. @@ -5619,8 +5623,9 @@ void CWeaponClass::Update (CInstalledDevice *pDevice, CSpaceObject *pSource, SDe ActivateCtx.iRepeatingCount = 1 + iContinuous - min(int(dwContinuous) / iContinuousDelay, iContinuous + 1); ActivateCtx.iChargeFrame = 1 + iChargeTime - min(int(dwContinuous) - iBurstLengthInFrames, iChargeTime + 1); + ActivateCtx.bIsCharging = int(dwContinuous) > iBurstLengthInFrames + 1; - FireWeapon(*pDevice, *pShot, ActivateCtx, (int(dwContinuous) > iBurstLengthInFrames + 1)); + FireWeapon(*pDevice, *pShot, ActivateCtx); if (pSource->IsDestroyed()) return; From 2e51eb2dd44a942de024fa17ebe6c7ca2c32c99e Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Sat, 15 Jan 2022 02:34:19 -0500 Subject: [PATCH 30/32] Fix compile errors --- Mammoth/TSE/CWeaponClass.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Mammoth/TSE/CWeaponClass.cpp b/Mammoth/TSE/CWeaponClass.cpp index e9c916e00..e42b848c3 100644 --- a/Mammoth/TSE/CWeaponClass.cpp +++ b/Mammoth/TSE/CWeaponClass.cpp @@ -2494,13 +2494,13 @@ bool CWeaponClass::ChargeWeapon (const bool bSetFireAngle, const int iFireAngle, { // If we're using ship heat, make sure we have enough. - if (!CanConsumeShipHeat(DeviceItem, ShotDesc)) + if (!CanConsumeShipCounter(DeviceItem, ShotDesc)) return false; // Update the ship energy/heat counter. - if (m_iHeatPerShot != 0) - ConsumeShipHeat(DeviceItem, ShotDesc); + if (m_iCounterPerShot != 0) + ConsumeShipCounter(DeviceItem, ShotDesc); CSpaceObject& Source = Device.GetSourceOrThrow(); CItemCtx ItemCtx(&Source, &Device); From ada06cbebfc230d2f3c6779a038f6b6fd556af62 Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Sat, 15 Jan 2022 02:39:46 -0500 Subject: [PATCH 31/32] Fix indents --- Mammoth/TSE/CWeaponClass.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Mammoth/TSE/CWeaponClass.cpp b/Mammoth/TSE/CWeaponClass.cpp index e42b848c3..89be5ecda 100644 --- a/Mammoth/TSE/CWeaponClass.cpp +++ b/Mammoth/TSE/CWeaponClass.cpp @@ -2393,10 +2393,10 @@ bool CWeaponClass::FireOnChargeWeapon (CItemCtx& ItemCtx, ICCItemPtr pResult = Ctx.RunCode(Event); if (pResult->IsError()) - { + { ItemCtx.GetSource()->ReportEventError(ON_CHARGE_WEAPON_EVENT, pResult); return true; - } + } else if (pResult->IsNil()) return false; @@ -2404,14 +2404,14 @@ bool CWeaponClass::FireOnChargeWeapon (CItemCtx& ItemCtx, return true; else - { + { retResult.bShotFired = !pResult->GetBooleanAt(CONSTLIT("noEffect")); retResult.bFireEffect = retResult.bShotFired && !pResult->GetBooleanAt("noFireEffect"); retResult.bSoundEffect = retResult.bShotFired && !pResult->GetBooleanAt("noSoundEffect"); retResult.bRecoil = retResult.bShotFired && !pResult->GetBooleanAt("noRecoil"); return true; - } + } // Done From 14b59b94a20234b52428983a137dc3d09010153a Mon Sep 17 00:00:00 2001 From: Bryon Leung Date: Sat, 15 Jan 2022 02:40:56 -0500 Subject: [PATCH 32/32] Change back to pass by reference --- Mammoth/Include/TSEDevices.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mammoth/Include/TSEDevices.h b/Mammoth/Include/TSEDevices.h index 0618787e4..3acf54205 100644 --- a/Mammoth/Include/TSEDevices.h +++ b/Mammoth/Include/TSEDevices.h @@ -247,7 +247,7 @@ class CDeviceClass } CTargetList &GetTargetList (); - void SetTargetList (CTargetList TargetListArg) + void SetTargetList (CTargetList &TargetListArg) { m_pTargetList = &TargetListArg; }