diff --git a/Mammoth/Include/TSEDevices.h b/Mammoth/Include/TSEDevices.h index 897ac0378..3acf54205 100644 --- a/Mammoth/Include/TSEDevices.h +++ b/Mammoth/Include/TSEDevices.h @@ -330,7 +330,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) { } @@ -366,6 +366,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; } @@ -694,6 +697,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(); } @@ -709,6 +713,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()); } @@ -731,6 +738,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); } @@ -768,6 +779,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 1041c7b0c..008ebcaef 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 89be5ecda..fea2b49d9 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") @@ -942,7 +943,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 // @@ -999,7 +1000,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 // @@ -1326,6 +1351,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. @@ -1333,10 +1361,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) { @@ -1357,6 +1389,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); } } @@ -1375,7 +1419,6 @@ bool CWeaponClass::CalcSingleTarget (CInstalledDevice &Device, else { CDeviceItem DeviceItem = Device.GetDeviceItem(); - switch (DeviceItem.CalcTargetType()) { case CDeviceItem::calcNoTarget: @@ -1418,6 +1461,7 @@ bool CWeaponClass::CalcSingleTarget (CInstalledDevice &Device, default: return false; } + Device.SetLastSwivelTime(GetChargeTime(ShotDesc) + ((1 + GetContinuous(ShotDesc)) * (GetContinuousFireDelay(ShotDesc) + 1))); } // Fire! @@ -1925,12 +1969,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; @@ -2550,6 +2597,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; @@ -2593,11 +2644,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 @@ -4031,6 +4079,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 @@ -4758,7 +4829,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 //