From bb536b2d601c1bbbd3e92f18c0bbf1ddfd894e8a Mon Sep 17 00:00:00 2001 From: SamaelGray <56392968+SamaelGray@users.noreply.github.com> Date: Sun, 27 Aug 2023 21:29:40 +0330 Subject: [PATCH 001/158] VFE - Deserters patch --- .../Apparel_Belts.xml | 23 ++ .../Buildings_Security_Turrets.xml | 15 ++ .../Damages_Empire.xml | 23 ++ .../RangedSpacer.xml | 251 ++++++++++++++++++ 4 files changed, 312 insertions(+) create mode 100644 Patches/Vanilla Factions Expanded - Deserters/Apparel_Belts.xml create mode 100644 Patches/Vanilla Factions Expanded - Deserters/Buildings_Security_Turrets.xml create mode 100644 Patches/Vanilla Factions Expanded - Deserters/Damages_Empire.xml create mode 100644 Patches/Vanilla Factions Expanded - Deserters/RangedSpacer.xml diff --git a/Patches/Vanilla Factions Expanded - Deserters/Apparel_Belts.xml b/Patches/Vanilla Factions Expanded - Deserters/Apparel_Belts.xml new file mode 100644 index 0000000000..88de27692c --- /dev/null +++ b/Patches/Vanilla Factions Expanded - Deserters/Apparel_Belts.xml @@ -0,0 +1,23 @@ + + + + + +
  • Vanilla Factions Expanded - Deserters
  • +
    + + + +
  • + Defs/ThingDef[defName="VFED_Apparel_BombPack" or defName="VFED_Apparel_InvisibilityEngulfer" or defName="VFED_DeserterDeclassifier"]/statBases + + 3 + 1 + +
  • + +
    +
    +
    + +
    \ No newline at end of file diff --git a/Patches/Vanilla Factions Expanded - Deserters/Buildings_Security_Turrets.xml b/Patches/Vanilla Factions Expanded - Deserters/Buildings_Security_Turrets.xml new file mode 100644 index 0000000000..e5beaf9ada --- /dev/null +++ b/Patches/Vanilla Factions Expanded - Deserters/Buildings_Security_Turrets.xml @@ -0,0 +1,15 @@ + + + + + +
  • Vanilla Factions Expanded - Deserters
  • +
    + + + + + +
    + +
    \ No newline at end of file diff --git a/Patches/Vanilla Factions Expanded - Deserters/Damages_Empire.xml b/Patches/Vanilla Factions Expanded - Deserters/Damages_Empire.xml new file mode 100644 index 0000000000..be33fc2ddb --- /dev/null +++ b/Patches/Vanilla Factions Expanded - Deserters/Damages_Empire.xml @@ -0,0 +1,23 @@ + + + + + +
  • Vanilla Factions Expanded - Deserters
  • +
    + + + +
  • + Defs/DamageDef[defName="VFED_Fletchling"] + + 0.05 + 0.05 + +
  • + +
    +
    +
    + +
    \ No newline at end of file diff --git a/Patches/Vanilla Factions Expanded - Deserters/RangedSpacer.xml b/Patches/Vanilla Factions Expanded - Deserters/RangedSpacer.xml new file mode 100644 index 0000000000..b6a21e8f52 --- /dev/null +++ b/Patches/Vanilla Factions Expanded - Deserters/RangedSpacer.xml @@ -0,0 +1,251 @@ + + + + + +
  • Vanilla Factions Expanded - Deserters
  • +
    + + + + +
  • + Defs/ThingDef[defName = "VFED_Gun_ChargeCycler"]/tools + + +
  • + + +
  • Blunt
  • + + 2 + 1.54 + 1.5 + 0.555 + Grip + +
  • + + +
  • Poke
  • + + 2 + 1.54 + 0.555 + Muzzle + + + + + +
  • + Defs/ThingDef[defName = "VFED_Gun_Fletchling"]/tools + + +
  • + + +
  • Blunt
  • + + 8 + 1.55 + 1.5 + 2.755 + Stock + +
  • + + +
  • Blunt
  • + + 5 + 2.02 + 1.630 + Barrel + +
  • + + +
  • Poke
  • + + 8 + 1.55 + 2.755 + Muzzle + + + + + + +
  • + VFED_Gun_ChargeCycler + + 2.0 + 3.0 + 1.15 + 0.15 + 0.8 + 0.36 + + + 8 + 4 + AmmoSet_6x18mmCharged + + + Snapshot + + +
  • CE_Sidearm
  • +
  • CE_OneHandedWeapon
  • + + + +
  • + Defs/ThingDef[defName="VFED_Gun_ChargeCycler"]/verbs + + +
  • + 3.0 + CombatExtended.Verb_ShootCE + True + Bullet_6x18mmCharged + 0.5 + 12 + VFED_Shot_ChargeCycler + GunTail_Light + 9 +
  • + + + + +
  • + Defs/ThingDef[defName = "VFED_Gun_ChargeCycler"]/comps/li[@Class="MVCF.Comps.CompProperties_VerbProps"] + +
  • + CompEquippable +
  • +
  • + + 1 + 3 + AmmoSet_40x68mmDemo + + + 2.0 + CombatExtended.Verb_ShootCE + true + Bullet_40x68mmDemo_Thump + 1.1 + 25 + + true + + ThumpCannon_Fire + GunTail_Medium + 5 + + + FALSE + SuppressFire + +
  • + + + + + +
  • + Defs + + + + AmmoSet_FletchlingDart + + + Bullet_FletchlingDart + + AmmoSet_ChargedHeavy + + + + Bullet_FletchlingDart + + + Projectile/Bullet_Fletcher + Graphic_Single + TransparentPostLight + + + 2 + 62 + VFED_Fletchling + 3 + 8 + 18 + 8.9 + + + + +
  • + +
  • + Defs/ThingDef[defName="VFED_Gun_ChargeCycler"]/verbs + + +
  • + 3.0 + CombatExtended.Verb_ShootCE + True + Bullet_6x18mmCharged + 0.5 + 12 + VFED_Shot_ChargeCycler + GunTail_Light + 9 +
  • + + + + +
  • + VFED_Gun_Fletchling + + 8 + 0.79 + 1 + 0.15 + 1.8 + 9 + + + 1.0 + CombatExtended.Verb_ShootCE + true + Bullet_FletchlingDart + 0.6 + 25 + VFED_Shot_Fletchling + GunTail_Light + 4 + + + 5 + true + 0.85 + AmmoSet_FletchlingDart + + + FALSE + Snapshot + +
  • + +
    +
    +
    + +
    \ No newline at end of file From 49544c0d24edbd5d47916e15f9754084e6dc0e78 Mon Sep 17 00:00:00 2001 From: SamaelGray <56392968+SamaelGray@users.noreply.github.com> Date: Sun, 27 Aug 2023 21:31:09 +0330 Subject: [PATCH 002/158] VFE-D third party support --- SupportedThirdPartyMods.md | 1 + 1 file changed, 1 insertion(+) diff --git a/SupportedThirdPartyMods.md b/SupportedThirdPartyMods.md index a7bcdea2d6..ea41be9691 100644 --- a/SupportedThirdPartyMods.md +++ b/SupportedThirdPartyMods.md @@ -480,6 +480,7 @@ Vanilla Brewing Expanded | Vanilla Chemfuel Expanded | Vanilla Factions Expanded - Ancients | Vanilla Factions Expanded - Classical | +Vanilla Factions Expanded - Deserters | Vanilla Factions Expanded - Empire | Vanilla Factions Expanded - Insectoids | Vanilla Factions Expanded - Mechanoids | From d177926b4cfb63d1686aa6dad29c0858eb2a91a6 Mon Sep 17 00:00:00 2001 From: SamaelGray <56392968+SamaelGray@users.noreply.github.com> Date: Mon, 28 Aug 2023 00:09:45 +0330 Subject: [PATCH 003/158] Security stuff --- .../Buildings_Security_Turrets.xml | 319 ++++++++++++++++++ .../Things_Security.xml | 75 ++++ 2 files changed, 394 insertions(+) create mode 100644 Patches/Vanilla Factions Expanded - Deserters/Things_Security.xml diff --git a/Patches/Vanilla Factions Expanded - Deserters/Buildings_Security_Turrets.xml b/Patches/Vanilla Factions Expanded - Deserters/Buildings_Security_Turrets.xml index e5beaf9ada..936c91ad14 100644 --- a/Patches/Vanilla Factions Expanded - Deserters/Buildings_Security_Turrets.xml +++ b/Patches/Vanilla Factions Expanded - Deserters/Buildings_Security_Turrets.xml @@ -8,6 +8,325 @@ +
  • + Defs/ThingDef[@Name="VFED_ImperialTurretBase" or defName="VFED_Turret_StrikerTurret"]/fillPercent + + 0.85 + +
  • + +
  • + Defs/ThingDef[@Name="VFED_ImperialTurretBase"]/comps/li[@Class="CompProperties_Explosive"] +
  • + +
  • + Defs/ThingDef[@Name="VFED_ImperialTurretBase" or defName="VFED_Turret_StrikerTurret"]/thingClass + + CombatExtended.Building_TurretGunCE + +
  • + +
  • + Defs/ThingDef[@Name="VFED_ImperialTurretBase"]/statBases/MaxHitPoints + + 800 + +
  • + +
  • + Defs/ThingDef[@Name="VFED_ImperialTurretBase"]/statBases/Flammability + + 0.4 + +
  • + +
  • + Defs/ThingDef[@Name="VFED_ImperialTurretBase"]/comps/li[@Class="CompProperties_Power"] + +
  • + CompPowerTrader + 750 +
  • + + + +
  • + Defs/ThingDef[defName="VFED_Turret_Kontarion" or defName="VFED_Gun_Kontarion" or defName="VFED_Turret_Onager"]/comps/li[@Class="CompProperties_Refuelable"] +
  • + +
  • + Defs/ThingDef[defName="VFED_Turret_Kontarion" or defName="VFED_Gun_Kontarion" or defName="VFED_Turret_Onager"]/comps/li[@Class="CompProperties_BoxRefuel"] +
  • + +
  • + Defs/ThingDef[defName="VFED_Turret_Kontarion" or defName="VFED_Gun_Kontarion" or defName="VFED_Turret_Onager"]/modExtensions/li[@Class="TurretExtension_Barrels"] +
  • + + + +
  • + Defs/ThingDef[defName="VFED_Turret_Kontarion"]/statBases + + 0.75 + 0.5 + +
  • + +
  • + Defs/ThingDef[defName="VFED_Turret_Kontarion"]/statBases/ShootingAccuracyTurret + + 1.5 + +
  • + +
  • + Defs/ThingDef[defName="VFED_Turret_Kontarion"]/building/turretBurstCooldownTime + + 1.0 + +
  • + +
  • + Defs/ThingDef[defName="VFED_Turret_Kontarion"]/costList + + + 550 + 100 + 60 + 14 + + +
  • + +
  • + VFED_Gun_Kontarion + + 2.36 + 0.01 + 2.0 + 0.5 + 150 + + + 4.15 + CombatExtended.Verb_ShootCE + true + Bullet_40x311mmR_AP + 4.6 + 12 + 500 + 12 + 6 + Shot_TurretSniper + GunTail_Heavy + 20 + Mounted + + + 60 + 13.6 + AmmoSet_40x311mmR + + + AimedShot + true + true + +
  • + + + +
  • + Defs/ThingDef[defName="VFED_Turret_Palintone"]/statBases + + 0.75 + +
  • + +
  • + Defs/ThingDef[defName="VFED_Turret_Palintone"]/statBases/ShootingAccuracyTurret + + 1.25 + +
  • + +
  • + Defs/ThingDef[defName="VFED_Turret_Palintone"]/building/turretBurstCooldownTime + + 1.0 + +
  • + +
  • + Defs/ThingDef[defName="VFED_Turret_Palintone"]/costList + + + 550 + 80 + 12 + + +
  • + +
  • + VFED_Gun_Palintone + + 1 + 0.01 + 1.8 + 0.37 + 150 + + + 1.50 + CombatExtended.Verb_ShootCE + true + Bullet_20x102mmNATO_AP + 2.3 + 14 + 500 + 7 + 40 + HeavyMG + GunTail_Heavy + 16 + Mounted + + + 200 + 15.6 + AmmoSet_20x102mmNATO + + + AimedShot + true + true + +
  • + + + +
  • + Defs/ThingDef[defName="VFED_Turret_Onager"]/statBases/ShootingAccuracyTurret + + 1.25 + +
  • + +
  • + Defs/ThingDef[defName="VFED_Turret_Onager"]/statBases + + 1.0 + +
  • + +
  • + Defs/ThingDef[defName="VFED_Turret_Onager"]/building/turretBurstCooldownTime + + 4.2 + +
  • + +
  • + Defs/ThingDef[defName="VFED_Turret_Onager"]/costList + + + 600 + 90 + 15 + + +
  • + +
  • + Defs/ThingDef[defName="VFED_Gun_Onager"]/statBases/AccuracyTouch + | Defs/ThingDef[defName="VFED_Gun_Onager"]/statBases/AccuracyShort + | Defs/ThingDef[defName="VFED_Gun_Onager"]/statBases/AccuracyMedium + | Defs/ThingDef[defName="VFED_Gun_Onager"]/statBases/AccuracyLong +
  • + +
  • + Defs/ThingDef[defName="VFED_Gun_Onager"]/comps + + Defs/ThingDef[defName="VFED_Gun_Onager"] + + + + +
  • + +
  • + Defs/ThingDef[defName="VFED_Gun_Onager"]/comps + +
  • + +
  • 20
  • +
  • 30
  • +
  • 40
  • +
  • 50
  • +
  • 60
  • + + +
  • + 24 + 13 + AmmoSet_50mmRocket +
  • + + + +
  • + Defs/ThingDef[defName="VFED_Gun_Onager"]/verbs + + +
  • + 1.73 + CombatExtended.Verb_ShootMortarCE + false + true + false + Bullet_50mmRocket_HE + 3.6 + 86 + 14 + 6 + 6 + RocketswarmLauncher_Fire + GunTail_Heavy + 39 + 1 + 0.25 + + true + +
  • + + + + + + +
  • + Defs/ThingDef[defName="Turret_MiniTurret"]/statBases + + 0.25 + +
  • + +
  • + Defs/ThingDef[defName="Turret_MiniTurret"]/statBases/ShootingAccuracyTurret + + 0.5 + +
  • + +
  • + Defs/ThingDef[defName="Turret_MiniTurret"]/building/turretBurstCooldownTime + + 1.0 + +
  • +
    diff --git a/Patches/Vanilla Factions Expanded - Deserters/Things_Security.xml b/Patches/Vanilla Factions Expanded - Deserters/Things_Security.xml new file mode 100644 index 0000000000..355300d4fc --- /dev/null +++ b/Patches/Vanilla Factions Expanded - Deserters/Things_Security.xml @@ -0,0 +1,75 @@ + + + + + +
  • Vanilla Factions Expanded - Deserters
  • +
    + + + +
  • + Defs/ThingDef[defName="VFED_RemoteTrapIED_HighExplosive"]/comps/li[@Class="CompProperties_Explosive"] + +
  • + Bomb + 270 + 4.5 + +
  • Bullet
  • +
  • Arrow
  • +
  • ArrowHighVelocity
  • + + + 5 + 30 + + +
  • + + 16 + 100 + +
  • + + + +
  • + Defs/ThingDef[defName="VFED_RemoteTrapIED_Incendiary"]/comps/li[@Class="CompProperties_Explosive"] + +
  • + 15 + PrometheumFlame + 5 + FilthPrometheum + 0.75 + +
  • Bullet
  • +
  • Arrow
  • +
  • ArrowHighVelocity
  • + + + 5 + 20 + + + + + +
  • + Defs/ThingDef[defName="VFED_Shell_Shrapnel" or defName="VFED_Shell_ArmorPiercing" or defName="VFED_Shell_Cluster"] +
  • + +
  • + Defs/ThingDef[defName="VFED_TrapIED_Shrapnel" or defName="VFED_TrapIED_ArmorPiercing" or defName="VFED_TrapIED_Cluster"] +
  • + +
  • + Defs/ThingDef[defName="VFED_RemoteTrapIED_Shrapnel" or defName="VFED_RemoteTrapIED_ArmorPiercing" or defName="VFED_RemoteTrapIED_Cluster"] +
  • + +
    +
    +
    + +
    \ No newline at end of file From 3b6bf84ea5a03e4120e18c01d894aca538e13c77 Mon Sep 17 00:00:00 2001 From: SamaelGray <56392968+SamaelGray@users.noreply.github.com> Date: Mon, 28 Aug 2023 11:00:51 +0330 Subject: [PATCH 004/158] Fixes and tweaks --- .../Buildings_Security_Turrets.xml | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/Patches/Vanilla Factions Expanded - Deserters/Buildings_Security_Turrets.xml b/Patches/Vanilla Factions Expanded - Deserters/Buildings_Security_Turrets.xml index 936c91ad14..acd6e1a86f 100644 --- a/Patches/Vanilla Factions Expanded - Deserters/Buildings_Security_Turrets.xml +++ b/Patches/Vanilla Factions Expanded - Deserters/Buildings_Security_Turrets.xml @@ -51,15 +51,25 @@
  • - Defs/ThingDef[defName="VFED_Turret_Kontarion" or defName="VFED_Gun_Kontarion" or defName="VFED_Turret_Onager"]/comps/li[@Class="CompProperties_Refuelable"] + Defs/ThingDef[@Name="VFED_ImperialTurretBase"]/placeWorkers/li[.="PlaceWorker_ShowTurretRadius"]
  • - Defs/ThingDef[defName="VFED_Turret_Kontarion" or defName="VFED_Gun_Kontarion" or defName="VFED_Turret_Onager"]/comps/li[@Class="CompProperties_BoxRefuel"] + Defs/ThingDef[defName="VFED_Turret_Kontarion"]/comps/li[@Class="CompProperties_Refuelable"] + | Defs/ThingDef[defName="VFED_Turret_Palintone"]/comps/li[@Class="CompProperties_Refuelable"] + | Defs/ThingDef[defName="VFED_Turret_Onager"]/comps/li[@Class="CompProperties_Refuelable"]
  • - Defs/ThingDef[defName="VFED_Turret_Kontarion" or defName="VFED_Gun_Kontarion" or defName="VFED_Turret_Onager"]/modExtensions/li[@Class="TurretExtension_Barrels"] + Defs/ThingDef[defName="VFED_Turret_Kontarion"]/comps/li[@Class="VFED.CompProperties_BoxRefuel"] + | Defs/ThingDef[defName="VFED_Turret_Palintone"]/comps/li[@Class="VFED.CompProperties_BoxRefuel"] + | Defs/ThingDef[defName="VFED_Turret_Onager"]/comps/li[@Class="VFED.CompProperties_BoxRefuel"] +
  • + +
  • + Defs/ThingDef[defName="VFED_Turret_Kontarion"]/modExtensions/li[@Class="VFED.TurretExtension_Barrels"] + | Defs/ThingDef[defName="VFED_Turret_Palintone"]/modExtensions/li[@Class="VFED.TurretExtension_Barrels"] + | Defs/ThingDef[defName="VFED_Turret_Onager"]/modExtensions/li[@Class="VFED.TurretExtension_Barrels"]
  • @@ -103,12 +113,12 @@ 2.36 0.01 - 2.0 + 1.0 0.5 150 - 4.15 + 1.0 CombatExtended.Verb_ShootCE true Bullet_40x311mmR_AP @@ -173,12 +183,12 @@ 1 0.01 - 1.8 + 1.0 0.37 150 - 1.50 + 1.0 CombatExtended.Verb_ShootCE true Bullet_20x102mmNATO_AP @@ -280,7 +290,7 @@
  • - 1.73 + 1.0 CombatExtended.Verb_ShootMortarCE false true @@ -307,21 +317,21 @@
  • - Defs/ThingDef[defName="Turret_MiniTurret"]/statBases + Defs/ThingDef[defName="VFED_Turret_StrikerTurret"]/statBases 0.25
  • - Defs/ThingDef[defName="Turret_MiniTurret"]/statBases/ShootingAccuracyTurret + Defs/ThingDef[defName="VFED_Turret_StrikerTurret"]/statBases/ShootingAccuracyTurret 0.5
  • - Defs/ThingDef[defName="Turret_MiniTurret"]/building/turretBurstCooldownTime + Defs/ThingDef[defName="VFED_Turret_StrikerTurret"]/building/turretBurstCooldownTime 1.0 From c72441e65b8f355fd29a893061b6eb20d5e83162 Mon Sep 17 00:00:00 2001 From: CMDR-Bill-Doors Date: Sat, 9 Sep 2023 18:58:18 +0100 Subject: [PATCH 005/158] initPush --- .../CombatExtended/CE_Utility.cs | 53 +++++++++++++++++++ .../Things/Building_TurretGunCE.cs | 5 +- .../Verbs/Verb_LaunchProjectileCE.cs | 1 + 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/Source/CombatExtended/CombatExtended/CE_Utility.cs b/Source/CombatExtended/CombatExtended/CE_Utility.cs index b4b9af0c63..5119c5bbcd 100644 --- a/Source/CombatExtended/CombatExtended/CE_Utility.cs +++ b/Source/CombatExtended/CombatExtended/CE_Utility.cs @@ -733,6 +733,59 @@ public static Thing GetWeaponFromLauncher(Thing launcher) } */ + /// + /// A copy of the same function in Rimworld.EquipmentUtility, except changing requirement to Verb. + /// + /// + private static readonly SimpleCurve RecoilCurveAxisX = new SimpleCurve + { + new CurvePoint(0f, 0f), + new CurvePoint(1f, 0.02f), + new CurvePoint(2f, 0.03f) + }; + + private static readonly SimpleCurve RecoilCurveAxisY = new SimpleCurve + { + new CurvePoint(0f, 0f), + new CurvePoint(1f, 0.05f), + new CurvePoint(2f, 0.075f) + }; + + private static readonly SimpleCurve RecoilCurveRotation = new SimpleCurve + { + new CurvePoint(0f, 0f), + new CurvePoint(1f, 3f), + new CurvePoint(2f, 4f) + }; + + public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOffset, out float angleOffset, float aimAngle) + { + drawOffset = Vector3.zero; + angleOffset = 0f; + float recoil = ((VerbPropertiesCE)weaponDef.verbs[0]).recoilAmount * 10; + if (!(recoil > 0f) || shootVerb == null) + { + return; + } + Rand.PushState(shootVerb.LastShotTick); + try + { + int num = Find.TickManager.TicksGame - shootVerb.LastShotTick; + if ((float)num < weaponDef.verbs[0].ticksBetweenBurstShots) + { + float num2 = Mathf.Clamp01((float)num / weaponDef.verbs[0].ticksBetweenBurstShots); + float num3 = Mathf.Lerp(recoil, 0f, num2); + drawOffset = new Vector3((float)Rand.Sign * RecoilCurveAxisX.Evaluate(num2), 0f, 0f - RecoilCurveAxisY.Evaluate(num2)) * num3; + angleOffset = (float)Rand.Sign * RecoilCurveRotation.Evaluate(num2) * num3; + drawOffset = drawOffset.RotatedBy(aimAngle); + } + } + finally + { + Log.Message(drawOffset.ToString() + " " + angleOffset.ToString()); + Rand.PopState(); + } + } #endregion Misc #region MoteThrower diff --git a/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs b/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs index 99bb02f5d3..bd0ca7af6f 100644 --- a/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs +++ b/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs @@ -574,7 +574,10 @@ public override string GetInspectString() // Replaced vanilla loaded text public override void Draw() { - top.DrawTurret(Vector3.zero, 0f); + Vector3 drawOffset = Vector3.zero; + float angleOffset = 0f; + CE_Utility.Recoil(def.building.turretGunDef, AttackVerb, out drawOffset, out angleOffset, top.CurRotation); + top.DrawTurret(drawOffset, angleOffset); base.Draw(); } diff --git a/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs b/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs index 34f0835f61..3a96eafa35 100644 --- a/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs @@ -1085,6 +1085,7 @@ public override bool TryCastShot() CompReloadable.UsedOnce(); } } + lastShotTick = Find.TickManager.TicksGame; return true; } From c6526b61c6ae1f3485195367b20b2261c584b333 Mon Sep 17 00:00:00 2001 From: CMDR-Bill-Doors Date: Sat, 9 Sep 2023 19:01:34 +0100 Subject: [PATCH 006/158] add angle offset back before I decide if to remove it --- Source/CombatExtended/CombatExtended/CE_Utility.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/CombatExtended/CombatExtended/CE_Utility.cs b/Source/CombatExtended/CombatExtended/CE_Utility.cs index 5119c5bbcd..bef7c01282 100644 --- a/Source/CombatExtended/CombatExtended/CE_Utility.cs +++ b/Source/CombatExtended/CombatExtended/CE_Utility.cs @@ -777,6 +777,7 @@ public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOf float num3 = Mathf.Lerp(recoil, 0f, num2); drawOffset = new Vector3((float)Rand.Sign * RecoilCurveAxisX.Evaluate(num2), 0f, 0f - RecoilCurveAxisY.Evaluate(num2)) * num3; angleOffset = (float)Rand.Sign * RecoilCurveRotation.Evaluate(num2) * num3; + aimAngle += angleOffset; drawOffset = drawOffset.RotatedBy(aimAngle); } } From 01924ad37c09365ede804cf64543e3e8e21e804c Mon Sep 17 00:00:00 2001 From: CMDR-Bill-Doors Date: Sat, 9 Sep 2023 19:43:45 +0100 Subject: [PATCH 007/158] more fiddles --- .../CombatExtended/CE_Utility.cs | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/CE_Utility.cs b/Source/CombatExtended/CombatExtended/CE_Utility.cs index bef7c01282..064b0bc5d5 100644 --- a/Source/CombatExtended/CombatExtended/CE_Utility.cs +++ b/Source/CombatExtended/CombatExtended/CE_Utility.cs @@ -737,12 +737,6 @@ public static Thing GetWeaponFromLauncher(Thing launcher) /// A copy of the same function in Rimworld.EquipmentUtility, except changing requirement to Verb. /// /// - private static readonly SimpleCurve RecoilCurveAxisX = new SimpleCurve - { - new CurvePoint(0f, 0f), - new CurvePoint(1f, 0.02f), - new CurvePoint(2f, 0.03f) - }; private static readonly SimpleCurve RecoilCurveAxisY = new SimpleCurve { @@ -758,11 +752,14 @@ public static Thing GetWeaponFromLauncher(Thing launcher) new CurvePoint(2f, 4f) }; + const float recoilMagicNumber = 20; + public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOffset, out float angleOffset, float aimAngle) { drawOffset = Vector3.zero; angleOffset = 0f; - float recoil = ((VerbPropertiesCE)weaponDef.verbs[0]).recoilAmount * 10; + float recoil = ((VerbPropertiesCE)weaponDef.verbs[0]).recoilAmount * Math.Min(recoilMagicNumber, recoilMagicNumber / weaponDef.verbs[0].ticksBetweenBurstShots); + float recoilRelaxation = weaponDef.verbs[0].burstShotCount > 1 ? weaponDef.verbs[0].ticksBetweenBurstShots : weaponDef.GetStatValueDef(StatDefOf.RangedWeapon_Cooldown) / 2f; if (!(recoil > 0f) || shootVerb == null) { return; @@ -771,19 +768,18 @@ public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOf try { int num = Find.TickManager.TicksGame - shootVerb.LastShotTick; - if ((float)num < weaponDef.verbs[0].ticksBetweenBurstShots) + if ((float)num < recoilRelaxation) { - float num2 = Mathf.Clamp01((float)num / weaponDef.verbs[0].ticksBetweenBurstShots); + float num2 = Mathf.Clamp01((float)num / recoilRelaxation); float num3 = Mathf.Lerp(recoil, 0f, num2); - drawOffset = new Vector3((float)Rand.Sign * RecoilCurveAxisX.Evaluate(num2), 0f, 0f - RecoilCurveAxisY.Evaluate(num2)) * num3; + drawOffset = new Vector3(0f, 0f, 0f - RecoilCurveAxisY.Evaluate(num2)) * num3; angleOffset = (float)Rand.Sign * RecoilCurveRotation.Evaluate(num2) * num3; - aimAngle += angleOffset; drawOffset = drawOffset.RotatedBy(aimAngle); + aimAngle += angleOffset; } } finally { - Log.Message(drawOffset.ToString() + " " + angleOffset.ToString()); Rand.PopState(); } } From e6c0b8485c88d6b2cb21844e530f7f6aeb736eb7 Mon Sep 17 00:00:00 2001 From: CMDR-Bill-Doors Date: Sat, 9 Sep 2023 20:28:08 +0100 Subject: [PATCH 008/158] squared recoil --- Source/CombatExtended/CombatExtended/CE_Utility.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/CE_Utility.cs b/Source/CombatExtended/CombatExtended/CE_Utility.cs index 064b0bc5d5..26b27180fb 100644 --- a/Source/CombatExtended/CombatExtended/CE_Utility.cs +++ b/Source/CombatExtended/CombatExtended/CE_Utility.cs @@ -752,15 +752,17 @@ public static Thing GetWeaponFromLauncher(Thing launcher) new CurvePoint(2f, 4f) }; - const float recoilMagicNumber = 20; + const float RecoilMagicNumber = 20; public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOffset, out float angleOffset, float aimAngle) { drawOffset = Vector3.zero; angleOffset = 0f; - float recoil = ((VerbPropertiesCE)weaponDef.verbs[0]).recoilAmount * Math.Min(recoilMagicNumber, recoilMagicNumber / weaponDef.verbs[0].ticksBetweenBurstShots); - float recoilRelaxation = weaponDef.verbs[0].burstShotCount > 1 ? weaponDef.verbs[0].ticksBetweenBurstShots : weaponDef.GetStatValueDef(StatDefOf.RangedWeapon_Cooldown) / 2f; - if (!(recoil > 0f) || shootVerb == null) + float recoil = ((VerbPropertiesCE)weaponDef.verbs[0]).recoilAmount; + recoil = recoil * recoil * Mathf.Clamp(RecoilMagicNumber / weaponDef.verbs[0].ticksBetweenBurstShots, 1, RecoilMagicNumber); + + float recoilRelaxation = weaponDef.verbs[0].burstShotCount > 1 ? weaponDef.verbs[0].ticksBetweenBurstShots : weaponDef.GetStatValueDef(StatDefOf.RangedWeapon_Cooldown) * 20f; + if (recoil <= 0f || shootVerb == null) { return; } @@ -776,6 +778,8 @@ public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOf angleOffset = (float)Rand.Sign * RecoilCurveRotation.Evaluate(num2) * num3; drawOffset = drawOffset.RotatedBy(aimAngle); aimAngle += angleOffset; + + Log.Message(recoil.ToString() + " " + recoilRelaxation.ToString()); } } finally From b4d5cd603e13bfdb027173bfe1320813c600e27c Mon Sep 17 00:00:00 2001 From: CMDR-Bill-Doors Date: Sat, 9 Sep 2023 21:44:05 +0100 Subject: [PATCH 009/158] mod extension for outliers --- .../CombatExtended/CombatExtended/CE_Utility.cs | 12 ++++++++++-- .../CombatExtended/Defs/RecoilAdjustExtension.cs | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 Source/CombatExtended/CombatExtended/Defs/RecoilAdjustExtension.cs diff --git a/Source/CombatExtended/CombatExtended/CE_Utility.cs b/Source/CombatExtended/CombatExtended/CE_Utility.cs index 26b27180fb..0116354ea7 100644 --- a/Source/CombatExtended/CombatExtended/CE_Utility.cs +++ b/Source/CombatExtended/CombatExtended/CE_Utility.cs @@ -759,13 +759,21 @@ public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOf drawOffset = Vector3.zero; angleOffset = 0f; float recoil = ((VerbPropertiesCE)weaponDef.verbs[0]).recoilAmount; - recoil = recoil * recoil * Mathf.Clamp(RecoilMagicNumber / weaponDef.verbs[0].ticksBetweenBurstShots, 1, RecoilMagicNumber); - + recoil = Math.Min(recoil * recoil, 20) * Mathf.Clamp(RecoilMagicNumber / weaponDef.verbs[0].ticksBetweenBurstShots, 1, RecoilMagicNumber); float recoilRelaxation = weaponDef.verbs[0].burstShotCount > 1 ? weaponDef.verbs[0].ticksBetweenBurstShots : weaponDef.GetStatValueDef(StatDefOf.RangedWeapon_Cooldown) * 20f; + RecoilAdjustExtension RecoilAdjustExtension = weaponDef.GetModExtension(); + + if (RecoilAdjustExtension != null) + { + recoil *= RecoilAdjustExtension.recoilModifier; + recoilRelaxation = RecoilAdjustExtension.recoilTick > 0 ? RecoilAdjustExtension.recoilTick : recoilRelaxation; + } + if (recoil <= 0f || shootVerb == null) { return; } + Rand.PushState(shootVerb.LastShotTick); try { diff --git a/Source/CombatExtended/CombatExtended/Defs/RecoilAdjustExtension.cs b/Source/CombatExtended/CombatExtended/Defs/RecoilAdjustExtension.cs new file mode 100644 index 0000000000..07461ed357 --- /dev/null +++ b/Source/CombatExtended/CombatExtended/Defs/RecoilAdjustExtension.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Verse; + +namespace CombatExtended +{ + public class RecoilAdjustExtension : DefModExtension + { + public float recoilModifier = 1; + public int recoilTick = -1; + } +} From cabfec4dfce4b5988de25a587ddf8d4bf195b881 Mon Sep 17 00:00:00 2001 From: CMDR-Bill-Doors Date: Sat, 9 Sep 2023 22:42:29 +0100 Subject: [PATCH 010/158] fixed wrong relation with RoF --- Source/CombatExtended/CombatExtended/CE_Utility.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/CE_Utility.cs b/Source/CombatExtended/CombatExtended/CE_Utility.cs index 0116354ea7..ed7369860d 100644 --- a/Source/CombatExtended/CombatExtended/CE_Utility.cs +++ b/Source/CombatExtended/CombatExtended/CE_Utility.cs @@ -752,14 +752,14 @@ public static Thing GetWeaponFromLauncher(Thing launcher) new CurvePoint(2f, 4f) }; - const float RecoilMagicNumber = 20; + const float RecoilMagicNumber = 2; public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOffset, out float angleOffset, float aimAngle) { drawOffset = Vector3.zero; angleOffset = 0f; float recoil = ((VerbPropertiesCE)weaponDef.verbs[0]).recoilAmount; - recoil = Math.Min(recoil * recoil, 20) * Mathf.Clamp(RecoilMagicNumber / weaponDef.verbs[0].ticksBetweenBurstShots, 1, RecoilMagicNumber); + recoil = Math.Min(recoil * recoil, 20) * RecoilMagicNumber * Mathf.Clamp(weaponDef.verbs[0].ticksBetweenBurstShots, 1, 10); float recoilRelaxation = weaponDef.verbs[0].burstShotCount > 1 ? weaponDef.verbs[0].ticksBetweenBurstShots : weaponDef.GetStatValueDef(StatDefOf.RangedWeapon_Cooldown) * 20f; RecoilAdjustExtension RecoilAdjustExtension = weaponDef.GetModExtension(); @@ -769,7 +769,8 @@ public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOf recoilRelaxation = RecoilAdjustExtension.recoilTick > 0 ? RecoilAdjustExtension.recoilTick : recoilRelaxation; } - if (recoil <= 0f || shootVerb == null) + //Prevents recoil for something with absurd ROF, it's too fast for any meaningful recoil animation + if (recoil <= 0f || shootVerb == null || recoilRelaxation < 2) { return; } From 6e11f3a333f7b2724247ff430e25fc33d7f6fe5f Mon Sep 17 00:00:00 2001 From: CMDR-Bill-Doors Date: Sun, 10 Sep 2023 07:47:35 +0100 Subject: [PATCH 011/158] more fiddle --- Source/CombatExtended/CombatExtended/CE_Utility.cs | 7 ++++--- .../CombatExtended/Defs/RecoilAdjustExtension.cs | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/CE_Utility.cs b/Source/CombatExtended/CombatExtended/CE_Utility.cs index ed7369860d..adf53a46da 100644 --- a/Source/CombatExtended/CombatExtended/CE_Utility.cs +++ b/Source/CombatExtended/CombatExtended/CE_Utility.cs @@ -752,14 +752,14 @@ public static Thing GetWeaponFromLauncher(Thing launcher) new CurvePoint(2f, 4f) }; - const float RecoilMagicNumber = 2; + const float RecoilMagicNumber = 2.6f; public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOffset, out float angleOffset, float aimAngle) { drawOffset = Vector3.zero; angleOffset = 0f; float recoil = ((VerbPropertiesCE)weaponDef.verbs[0]).recoilAmount; - recoil = Math.Min(recoil * recoil, 20) * RecoilMagicNumber * Mathf.Clamp(weaponDef.verbs[0].ticksBetweenBurstShots, 1, 10); + recoil = Math.Min(recoil * recoil, 20) * RecoilMagicNumber * Mathf.Clamp((float)Math.Log10(weaponDef.verbs[0].ticksBetweenBurstShots), 0.1f, 10); float recoilRelaxation = weaponDef.verbs[0].burstShotCount > 1 ? weaponDef.verbs[0].ticksBetweenBurstShots : weaponDef.GetStatValueDef(StatDefOf.RangedWeapon_Cooldown) * 20f; RecoilAdjustExtension RecoilAdjustExtension = weaponDef.GetModExtension(); @@ -767,6 +767,7 @@ public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOf { recoil *= RecoilAdjustExtension.recoilModifier; recoilRelaxation = RecoilAdjustExtension.recoilTick > 0 ? RecoilAdjustExtension.recoilTick : recoilRelaxation; + recoil = RecoilAdjustExtension.recoilScale > 0 ? RecoilAdjustExtension.recoilScale : recoil; } //Prevents recoil for something with absurd ROF, it's too fast for any meaningful recoil animation @@ -788,7 +789,7 @@ public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOf drawOffset = drawOffset.RotatedBy(aimAngle); aimAngle += angleOffset; - Log.Message(recoil.ToString() + " " + recoilRelaxation.ToString()); + Log.Message(recoil.ToString() + " " + Mathf.Clamp((float)Math.Log10(weaponDef.verbs[0].ticksBetweenBurstShots), 0.1f, 10).ToString()); } } finally diff --git a/Source/CombatExtended/CombatExtended/Defs/RecoilAdjustExtension.cs b/Source/CombatExtended/CombatExtended/Defs/RecoilAdjustExtension.cs index 07461ed357..7b87c73a86 100644 --- a/Source/CombatExtended/CombatExtended/Defs/RecoilAdjustExtension.cs +++ b/Source/CombatExtended/CombatExtended/Defs/RecoilAdjustExtension.cs @@ -10,6 +10,7 @@ namespace CombatExtended public class RecoilAdjustExtension : DefModExtension { public float recoilModifier = 1; + public float recoilScale = -1; public int recoilTick = -1; } } From 6fabd7c93ff7389153b2857dc5688886d865541b Mon Sep 17 00:00:00 2001 From: CMDR-Bill-Doors Date: Sun, 10 Sep 2023 19:40:49 +0100 Subject: [PATCH 012/158] handheld weapon recoil the harmony transpiler is in a mess. probably need someone better at coding to clean it up --- .../CombatExtended/CE_Utility.cs | 29 ++++++----- .../CombatExtended/Defs/GunDrawExtension.cs | 5 ++ .../Defs/RecoilAdjustExtension.cs | 16 ------ .../Things/Building_TurretGunCE.cs | 2 +- .../Harmony/Harmony_PawnRenderer.cs | 51 ++++++++++++++++++- 5 files changed, 72 insertions(+), 31 deletions(-) delete mode 100644 Source/CombatExtended/CombatExtended/Defs/RecoilAdjustExtension.cs diff --git a/Source/CombatExtended/CombatExtended/CE_Utility.cs b/Source/CombatExtended/CombatExtended/CE_Utility.cs index adf53a46da..fda4ca90c6 100644 --- a/Source/CombatExtended/CombatExtended/CE_Utility.cs +++ b/Source/CombatExtended/CombatExtended/CE_Utility.cs @@ -754,22 +754,18 @@ public static Thing GetWeaponFromLauncher(Thing launcher) const float RecoilMagicNumber = 2.6f; - public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOffset, out float angleOffset, float aimAngle) + public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOffset, out float angleOffset, float aimAngle, bool handheld) { drawOffset = Vector3.zero; angleOffset = 0f; float recoil = ((VerbPropertiesCE)weaponDef.verbs[0]).recoilAmount; recoil = Math.Min(recoil * recoil, 20) * RecoilMagicNumber * Mathf.Clamp((float)Math.Log10(weaponDef.verbs[0].ticksBetweenBurstShots), 0.1f, 10); - float recoilRelaxation = weaponDef.verbs[0].burstShotCount > 1 ? weaponDef.verbs[0].ticksBetweenBurstShots : weaponDef.GetStatValueDef(StatDefOf.RangedWeapon_Cooldown) * 20f; - RecoilAdjustExtension RecoilAdjustExtension = weaponDef.GetModExtension(); - - if (RecoilAdjustExtension != null) + if (recoil > 5 && handheld) { - recoil *= RecoilAdjustExtension.recoilModifier; - recoilRelaxation = RecoilAdjustExtension.recoilTick > 0 ? RecoilAdjustExtension.recoilTick : recoilRelaxation; - recoil = RecoilAdjustExtension.recoilScale > 0 ? RecoilAdjustExtension.recoilScale : recoil; + recoil = 5; } + float recoilRelaxation = weaponDef.verbs[0].burstShotCount > 1 ? weaponDef.verbs[0].ticksBetweenBurstShots : weaponDef.GetStatValueDef(StatDefOf.RangedWeapon_Cooldown) * 20f; //Prevents recoil for something with absurd ROF, it's too fast for any meaningful recoil animation if (recoil <= 0f || shootVerb == null || recoilRelaxation < 2) { @@ -779,17 +775,24 @@ public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOf Rand.PushState(shootVerb.LastShotTick); try { + float muzzleJumpModifier = 1; + GunDrawExtension recoilAdjustExtension = weaponDef.GetModExtension(); + if (recoilAdjustExtension != null) + { + recoil *= recoilAdjustExtension.recoilModifier; + recoilRelaxation = recoilAdjustExtension.recoilTick > 0 ? recoilAdjustExtension.recoilTick : recoilRelaxation; + recoil = recoilAdjustExtension.recoilScale > 0 ? recoilAdjustExtension.recoilScale : recoil; + muzzleJumpModifier = recoilAdjustExtension.muzzleJumpModifier > 0 ? recoilAdjustExtension.muzzleJumpModifier : 1; + } int num = Find.TickManager.TicksGame - shootVerb.LastShotTick; - if ((float)num < recoilRelaxation) + if (num < recoilRelaxation) { - float num2 = Mathf.Clamp01((float)num / recoilRelaxation); + float num2 = Mathf.Clamp01(num / recoilRelaxation); float num3 = Mathf.Lerp(recoil, 0f, num2); drawOffset = new Vector3(0f, 0f, 0f - RecoilCurveAxisY.Evaluate(num2)) * num3; - angleOffset = (float)Rand.Sign * RecoilCurveRotation.Evaluate(num2) * num3; + angleOffset = (handheld ? -1 : Rand.Sign) * RecoilCurveRotation.Evaluate(num2) * num3 * recoil * 0.6f * muzzleJumpModifier; drawOffset = drawOffset.RotatedBy(aimAngle); aimAngle += angleOffset; - - Log.Message(recoil.ToString() + " " + Mathf.Clamp((float)Math.Log10(weaponDef.verbs[0].ticksBetweenBurstShots), 0.1f, 10).ToString()); } } finally diff --git a/Source/CombatExtended/CombatExtended/Defs/GunDrawExtension.cs b/Source/CombatExtended/CombatExtended/Defs/GunDrawExtension.cs index ccb65a230f..026fe93f70 100644 --- a/Source/CombatExtended/CombatExtended/Defs/GunDrawExtension.cs +++ b/Source/CombatExtended/CombatExtended/Defs/GunDrawExtension.cs @@ -7,5 +7,10 @@ public class GunDrawExtension : DefModExtension { public Vector2 DrawSize = Vector2.one; public Vector2 DrawOffset = Vector2.zero; + + public float recoilModifier = 1; + public float recoilScale = -1; + public int recoilTick = -1; + public float muzzleJumpModifier = -1; } } diff --git a/Source/CombatExtended/CombatExtended/Defs/RecoilAdjustExtension.cs b/Source/CombatExtended/CombatExtended/Defs/RecoilAdjustExtension.cs deleted file mode 100644 index 7b87c73a86..0000000000 --- a/Source/CombatExtended/CombatExtended/Defs/RecoilAdjustExtension.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Verse; - -namespace CombatExtended -{ - public class RecoilAdjustExtension : DefModExtension - { - public float recoilModifier = 1; - public float recoilScale = -1; - public int recoilTick = -1; - } -} diff --git a/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs b/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs index bd0ca7af6f..e6c110a32e 100644 --- a/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs +++ b/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs @@ -576,7 +576,7 @@ public override void Draw() { Vector3 drawOffset = Vector3.zero; float angleOffset = 0f; - CE_Utility.Recoil(def.building.turretGunDef, AttackVerb, out drawOffset, out angleOffset, top.CurRotation); + CE_Utility.Recoil(def.building.turretGunDef, AttackVerb, out drawOffset, out angleOffset, top.CurRotation, false); top.DrawTurret(drawOffset, angleOffset); base.Draw(); } diff --git a/Source/CombatExtended/Harmony/Harmony_PawnRenderer.cs b/Source/CombatExtended/Harmony/Harmony_PawnRenderer.cs index 463d2a2216..f434daa3e1 100644 --- a/Source/CombatExtended/Harmony/Harmony_PawnRenderer.cs +++ b/Source/CombatExtended/Harmony/Harmony_PawnRenderer.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; @@ -413,8 +414,27 @@ public static void Prefix(PawnRenderer __instance, Thing eq) equipment = eq; } + private static void RecoilCE(Thing eq, Vector3 position, float aimAngle, float num, CompEquippable compEquippable) + { + if (compEquippable.PrimaryVerb.verbProps is VerbPropertiesCE) + { + //CE_Utility.Recoil(eq.def, compEquippable.PrimaryVerb, out var drawOffset, out var angleOffset, aimAngle); + //drawLoc += drawOffset; + //num += angleOffset; + } + } + private static void DrawMesh(Mesh mesh, Matrix4x4 matrix, Material mat, int layer, Thing eq, Vector3 position, float aimAngle) { + CompEquippable compEquippable = eq.TryGetComp(); + Vector3 recoilOffset = new Vector3(); + float muzzleJump = 0; + if (compEquippable.PrimaryVerb.verbProps is VerbPropertiesCE) + { + CE_Utility.Recoil(eq.def, compEquippable.PrimaryVerb, out var drawOffset, out var angleOffset, aimAngle, true); + recoilOffset = drawOffset; + muzzleJump = angleOffset; + } GunDrawExtension drawData = eq.def.GetModExtension() ?? new GunDrawExtension() { DrawSize = eq.def.graphicData.drawSize }; if (drawData.DrawSize == Vector2.one) { drawData.DrawSize = eq.def.graphicData.drawSize; } Vector3 scale = new Vector3(drawData.DrawSize.x, 1, drawData.DrawSize.y); @@ -422,8 +442,9 @@ private static void DrawMesh(Mesh mesh, Matrix4x4 matrix, Material mat, int laye if (aimAngle > 200 && aimAngle < 340) { posVec.x *= -1; + muzzleJump = -muzzleJump; } - matrix.SetTRS(position + posVec.RotatedBy(matrix.rotation.eulerAngles.y), matrix.rotation, scale); + matrix.SetTRS(position + posVec.RotatedBy(matrix.rotation.eulerAngles.y) + recoilOffset, Quaternion.AngleAxis(matrix.rotation.eulerAngles.y + muzzleJump, Vector3.up), scale); if (eq is WeaponPlatform platform) { platform.DrawPlatform(matrix, mesh == MeshPool.plane10Flip, layer); @@ -434,12 +455,40 @@ private static void DrawMesh(Mesh mesh, Matrix4x4 matrix, Material mat, int laye } } + /* * This replace the last DrawMesh in */ internal static IEnumerable Transpiler(IEnumerable instructions) { var codes = instructions.ToList(); + /* + var recoil_opcodes = new CodeInstruction[] + { + new CodeInstruction(OpCodes.Ldarg_1), + new CodeInstruction(OpCodes.Ldarg_2), + new CodeInstruction(OpCodes.Ldarg_3), + new CodeInstruction(OpCodes.Ldloc_1), + new CodeInstruction(OpCodes.Ldloc_2), + new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(Harmony_PawnRenderer_DrawEquipmentAiming), nameof(RecoilCE))) + }; + bool foundRecoil = false; + int index = 0; + for (int i = 0; i < codes.Count; i++) + { + CodeInstruction code = codes[i]; + if (foundRecoil && code.opcode == OpCodes.Stloc_1) + { + index = i + 1; + break; + } + else if (code.opcode == OpCodes.Call && ReferenceEquals(code.operand, typeof(EquipmentUtility).GetMethod("Recoil"))) + { + foundRecoil = true; + } + } + codes.InsertRange(index, recoil_opcodes); + */ codes[codes.Count - 2].operand = AccessTools.Method(typeof(Harmony_PawnRenderer_DrawEquipmentAiming), nameof(DrawMesh)); codes.InsertRange(codes.Count - 2, new[] From 374483c09a3da527735d0693ef1fffea607e1326 Mon Sep 17 00:00:00 2001 From: CMDR-Bill-Doors Date: Sun, 10 Sep 2023 21:39:11 +0100 Subject: [PATCH 013/158] upgrade? tidied up code make single hand weapon behave differently muzzle rise no longer affected by recoil modifier semi-auto weapons are affected by RoF based recoil reduction too, fixing chain shotgun included mod setting to turn this off --- .../CombatExtended/CE_Utility.cs | 36 ++++++++++++++----- .../CombatExtended/CombatExtended/Settings.cs | 5 +++ .../Things/Building_TurretGunCE.cs | 5 ++- .../Harmony/Harmony_PawnRenderer.cs | 23 +++++------- 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/CE_Utility.cs b/Source/CombatExtended/CombatExtended/CE_Utility.cs index fda4ca90c6..420e29f9cc 100644 --- a/Source/CombatExtended/CombatExtended/CE_Utility.cs +++ b/Source/CombatExtended/CombatExtended/CE_Utility.cs @@ -753,19 +753,18 @@ public static Thing GetWeaponFromLauncher(Thing launcher) }; const float RecoilMagicNumber = 2.6f; + const float MuzzleRiseMagicNumber = 0.04f; public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOffset, out float angleOffset, float aimAngle, bool handheld) { drawOffset = Vector3.zero; angleOffset = 0f; float recoil = ((VerbPropertiesCE)weaponDef.verbs[0]).recoilAmount; - recoil = Math.Min(recoil * recoil, 20) * RecoilMagicNumber * Mathf.Clamp((float)Math.Log10(weaponDef.verbs[0].ticksBetweenBurstShots), 0.1f, 10); - if (recoil > 5 && handheld) - { - recoil = 5; - } float recoilRelaxation = weaponDef.verbs[0].burstShotCount > 1 ? weaponDef.verbs[0].ticksBetweenBurstShots : weaponDef.GetStatValueDef(StatDefOf.RangedWeapon_Cooldown) * 20f; + + recoil = Math.Min(recoil * recoil, 20) * RecoilMagicNumber * Mathf.Clamp((float)Math.Log10(recoilRelaxation), 0.1f, 10); + //Prevents recoil for something with absurd ROF, it's too fast for any meaningful recoil animation if (recoil <= 0f || shootVerb == null || recoilRelaxation < 2) { @@ -775,24 +774,45 @@ public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOf Rand.PushState(shootVerb.LastShotTick); try { - float muzzleJumpModifier = 1; + float muzzleJumpModifier = recoil; GunDrawExtension recoilAdjustExtension = weaponDef.GetModExtension(); if (recoilAdjustExtension != null) { recoil *= recoilAdjustExtension.recoilModifier; recoilRelaxation = recoilAdjustExtension.recoilTick > 0 ? recoilAdjustExtension.recoilTick : recoilRelaxation; recoil = recoilAdjustExtension.recoilScale > 0 ? recoilAdjustExtension.recoilScale : recoil; - muzzleJumpModifier = recoilAdjustExtension.muzzleJumpModifier > 0 ? recoilAdjustExtension.muzzleJumpModifier : 1; + muzzleJumpModifier *= recoilAdjustExtension.muzzleJumpModifier > 0 ? recoilAdjustExtension.muzzleJumpModifier : 1; } + + + if (handheld) + { + if (weaponDef.weaponTags.Contains("CE_OneHandedWeapon")) + { + recoil /= 3; + muzzleJumpModifier *= 1.5f; + } + else + { + recoil /= 1.3f; + muzzleJumpModifier /= 1.5f; + } + if (recoil > 15) + { + recoil = 15; + } + } + int num = Find.TickManager.TicksGame - shootVerb.LastShotTick; if (num < recoilRelaxation) { float num2 = Mathf.Clamp01(num / recoilRelaxation); float num3 = Mathf.Lerp(recoil, 0f, num2); drawOffset = new Vector3(0f, 0f, 0f - RecoilCurveAxisY.Evaluate(num2)) * num3; - angleOffset = (handheld ? -1 : Rand.Sign) * RecoilCurveRotation.Evaluate(num2) * num3 * recoil * 0.6f * muzzleJumpModifier; + angleOffset = (handheld ? -1 : Rand.Sign) * RecoilCurveRotation.Evaluate(num2) * num3 * MuzzleRiseMagicNumber * muzzleJumpModifier; drawOffset = drawOffset.RotatedBy(aimAngle); aimAngle += angleOffset; + Log.Message(recoil.ToString()); } } finally diff --git a/Source/CombatExtended/CombatExtended/Settings.cs b/Source/CombatExtended/CombatExtended/Settings.cs index 33c0f1d18c..52f838ef68 100644 --- a/Source/CombatExtended/CombatExtended/Settings.cs +++ b/Source/CombatExtended/CombatExtended/Settings.cs @@ -19,6 +19,7 @@ public class Settings : ModSettings, ISettingsCE private bool autosetup = true; private bool showCasings = true; private bool createCasingsFilth = true; + private bool recoilAnim = true; private bool showTaunts = true; private bool allowMeleeHunting = false; private bool smokeEffects = true; @@ -128,6 +129,8 @@ public class Settings : ModSettings, ISettingsCE public bool CreateCasingsFilth => createCasingsFilth; + public bool RecoilAnim => recoilAnim; + #endregion private bool lastAmmoSystemStatus; @@ -139,6 +142,7 @@ public override void ExposeData() base.ExposeData(); Scribe_Values.Look(ref showCasings, "showCasings", true); Scribe_Values.Look(ref createCasingsFilth, "createCasingsFilth", true); + Scribe_Values.Look(ref recoilAnim, "recoilAnim", true); Scribe_Values.Look(ref showTaunts, "showTaunts", true); Scribe_Values.Look(ref allowMeleeHunting, "allowMeleeHunting", false); Scribe_Values.Look(ref smokeEffects, "smokeEffects", true); @@ -210,6 +214,7 @@ public void DoWindowContents(Rect canvas, ref int offset) list.CheckboxLabeled("CE_Settings_PartialStats_Title".Translate(), ref partialstats, "CE_Settings_PartialStats_Desc".Translate()); list.CheckboxLabeled("CE_Settings_ShowCasings_Title".Translate(), ref showCasings, "CE_Settings_ShowCasings_Desc".Translate()); list.CheckboxLabeled("CE_Settings_СreateCasingsFilth_Title".Translate(), ref createCasingsFilth, "CE_Settings_СreateCasingsFilth_Desc".Translate()); + list.CheckboxLabeled("CE_Settings_RecoilAnim_Title".Translate(), ref recoilAnim, "CE_Settings_RecoilAnim_Desc".Translate()); list.CheckboxLabeled("CE_Settings_ShowTaunts_Title".Translate(), ref showTaunts, "CE_Settings_ShowTaunts_Desc".Translate()); list.CheckboxLabeled("CE_Settings_AllowMeleeHunting_Title".Translate(), ref allowMeleeHunting, "CE_Settings_AllowMeleeHunting_Desc".Translate()); list.CheckboxLabeled("CE_Settings_SmokeEffects_Title".Translate(), ref smokeEffects, "CE_Settings_SmokeEffects_Desc".Translate()); diff --git a/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs b/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs index e6c110a32e..f63514568a 100644 --- a/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs +++ b/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs @@ -576,7 +576,10 @@ public override void Draw() { Vector3 drawOffset = Vector3.zero; float angleOffset = 0f; - CE_Utility.Recoil(def.building.turretGunDef, AttackVerb, out drawOffset, out angleOffset, top.CurRotation, false); + if (Controller.settings.RecoilAnim) + { + CE_Utility.Recoil(def.building.turretGunDef, AttackVerb, out drawOffset, out angleOffset, top.CurRotation, false); + } top.DrawTurret(drawOffset, angleOffset); base.Draw(); } diff --git a/Source/CombatExtended/Harmony/Harmony_PawnRenderer.cs b/Source/CombatExtended/Harmony/Harmony_PawnRenderer.cs index f434daa3e1..e548680486 100644 --- a/Source/CombatExtended/Harmony/Harmony_PawnRenderer.cs +++ b/Source/CombatExtended/Harmony/Harmony_PawnRenderer.cs @@ -404,6 +404,10 @@ internal static class Harmony_PawnRenderer_DrawEquipmentAiming private static Pawn pawn; + private static Vector3 recoilOffset = new Vector3(); + + private static float muzzleJump = 0; + private static readonly Matrix4x4 TBot5 = Matrix4x4.Translate(new Vector3(0, -0.006f, 0)); private static readonly Matrix4x4 TBot3 = Matrix4x4.Translate(new Vector3(0, -0.004f, 0)); @@ -416,25 +420,16 @@ public static void Prefix(PawnRenderer __instance, Thing eq) private static void RecoilCE(Thing eq, Vector3 position, float aimAngle, float num, CompEquippable compEquippable) { - if (compEquippable.PrimaryVerb.verbProps is VerbPropertiesCE) + if (Controller.settings.RecoilAnim && compEquippable.PrimaryVerb.verbProps is VerbPropertiesCE) { - //CE_Utility.Recoil(eq.def, compEquippable.PrimaryVerb, out var drawOffset, out var angleOffset, aimAngle); - //drawLoc += drawOffset; - //num += angleOffset; + CE_Utility.Recoil(eq.def, compEquippable.PrimaryVerb, out var drawOffset, out var angleOffset, aimAngle, true); + recoilOffset = drawOffset; + muzzleJump = angleOffset; } } private static void DrawMesh(Mesh mesh, Matrix4x4 matrix, Material mat, int layer, Thing eq, Vector3 position, float aimAngle) { - CompEquippable compEquippable = eq.TryGetComp(); - Vector3 recoilOffset = new Vector3(); - float muzzleJump = 0; - if (compEquippable.PrimaryVerb.verbProps is VerbPropertiesCE) - { - CE_Utility.Recoil(eq.def, compEquippable.PrimaryVerb, out var drawOffset, out var angleOffset, aimAngle, true); - recoilOffset = drawOffset; - muzzleJump = angleOffset; - } GunDrawExtension drawData = eq.def.GetModExtension() ?? new GunDrawExtension() { DrawSize = eq.def.graphicData.drawSize }; if (drawData.DrawSize == Vector2.one) { drawData.DrawSize = eq.def.graphicData.drawSize; } Vector3 scale = new Vector3(drawData.DrawSize.x, 1, drawData.DrawSize.y); @@ -462,7 +457,6 @@ private static void DrawMesh(Mesh mesh, Matrix4x4 matrix, Material mat, int laye internal static IEnumerable Transpiler(IEnumerable instructions) { var codes = instructions.ToList(); - /* var recoil_opcodes = new CodeInstruction[] { new CodeInstruction(OpCodes.Ldarg_1), @@ -488,7 +482,6 @@ internal static IEnumerable Transpiler(IEnumerable Date: Sun, 10 Sep 2023 21:59:02 +0100 Subject: [PATCH 014/158] Update CE_Utility.cs slightly increased muzzle rise for low recoil guns and reduce it on high recoil ones, for visual effect --- Source/CombatExtended/CombatExtended/CE_Utility.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/CE_Utility.cs b/Source/CombatExtended/CombatExtended/CE_Utility.cs index 420e29f9cc..734b2dddb1 100644 --- a/Source/CombatExtended/CombatExtended/CE_Utility.cs +++ b/Source/CombatExtended/CombatExtended/CE_Utility.cs @@ -753,7 +753,7 @@ public static Thing GetWeaponFromLauncher(Thing launcher) }; const float RecoilMagicNumber = 2.6f; - const float MuzzleRiseMagicNumber = 0.04f; + const float MuzzleRiseMagicNumber = 0.1f; public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOffset, out float angleOffset, float aimAngle, bool handheld) { @@ -774,7 +774,7 @@ public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOf Rand.PushState(shootVerb.LastShotTick); try { - float muzzleJumpModifier = recoil; + float muzzleJumpModifier = 10 * (float)Math.Log10(recoil) + 3; GunDrawExtension recoilAdjustExtension = weaponDef.GetModExtension(); if (recoilAdjustExtension != null) { @@ -795,7 +795,6 @@ public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOf else { recoil /= 1.3f; - muzzleJumpModifier /= 1.5f; } if (recoil > 15) { @@ -812,7 +811,6 @@ public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOf angleOffset = (handheld ? -1 : Rand.Sign) * RecoilCurveRotation.Evaluate(num2) * num3 * MuzzleRiseMagicNumber * muzzleJumpModifier; drawOffset = drawOffset.RotatedBy(aimAngle); aimAngle += angleOffset; - Log.Message(recoil.ToString()); } } finally From 7167e2a4c16e3eac4f04e3460effa0ce3de0f8f0 Mon Sep 17 00:00:00 2001 From: CMDR-Bill-Doors Date: Sun, 10 Sep 2023 22:12:59 +0100 Subject: [PATCH 015/158] fixed breaking when holding melee weapon --- Source/CombatExtended/CombatExtended/CE_Utility.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Source/CombatExtended/CombatExtended/CE_Utility.cs b/Source/CombatExtended/CombatExtended/CE_Utility.cs index 734b2dddb1..592d94ed4c 100644 --- a/Source/CombatExtended/CombatExtended/CE_Utility.cs +++ b/Source/CombatExtended/CombatExtended/CE_Utility.cs @@ -759,6 +759,10 @@ public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOf { drawOffset = Vector3.zero; angleOffset = 0f; + if (shootVerb == null || shootVerb.IsMeleeAttack) + { + return; + } float recoil = ((VerbPropertiesCE)weaponDef.verbs[0]).recoilAmount; float recoilRelaxation = weaponDef.verbs[0].burstShotCount > 1 ? weaponDef.verbs[0].ticksBetweenBurstShots : weaponDef.GetStatValueDef(StatDefOf.RangedWeapon_Cooldown) * 20f; @@ -766,7 +770,7 @@ public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOf recoil = Math.Min(recoil * recoil, 20) * RecoilMagicNumber * Mathf.Clamp((float)Math.Log10(recoilRelaxation), 0.1f, 10); //Prevents recoil for something with absurd ROF, it's too fast for any meaningful recoil animation - if (recoil <= 0f || shootVerb == null || recoilRelaxation < 2) + if (recoilRelaxation < 2) { return; } @@ -782,6 +786,7 @@ public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOf recoilRelaxation = recoilAdjustExtension.recoilTick > 0 ? recoilAdjustExtension.recoilTick : recoilRelaxation; recoil = recoilAdjustExtension.recoilScale > 0 ? recoilAdjustExtension.recoilScale : recoil; muzzleJumpModifier *= recoilAdjustExtension.muzzleJumpModifier > 0 ? recoilAdjustExtension.muzzleJumpModifier : 1; + if (recoil <= 0) { return; } } From 9f6905c886b7d81496fe0d1fd4e6f81a0c36b14a Mon Sep 17 00:00:00 2001 From: CMDR-Bill-Doors Date: Sun, 10 Sep 2023 22:17:16 +0100 Subject: [PATCH 016/158] fix turret without recoil --- Source/CombatExtended/CombatExtended/CE_Utility.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/CombatExtended/CombatExtended/CE_Utility.cs b/Source/CombatExtended/CombatExtended/CE_Utility.cs index 592d94ed4c..00aca19d41 100644 --- a/Source/CombatExtended/CombatExtended/CE_Utility.cs +++ b/Source/CombatExtended/CombatExtended/CE_Utility.cs @@ -786,9 +786,9 @@ public static void Recoil(ThingDef weaponDef, Verb shootVerb, out Vector3 drawOf recoilRelaxation = recoilAdjustExtension.recoilTick > 0 ? recoilAdjustExtension.recoilTick : recoilRelaxation; recoil = recoilAdjustExtension.recoilScale > 0 ? recoilAdjustExtension.recoilScale : recoil; muzzleJumpModifier *= recoilAdjustExtension.muzzleJumpModifier > 0 ? recoilAdjustExtension.muzzleJumpModifier : 1; - if (recoil <= 0) { return; } } + if (recoil <= 0) { return; } if (handheld) { From a402ecd09e33dee4065974491a9e2f7532de0e58 Mon Sep 17 00:00:00 2001 From: SokyranTheDragon Date: Mon, 11 Sep 2023 17:17:49 +0200 Subject: [PATCH 017/158] Fix Multiplayer issue with Fire at Will gizmo --- .../Compatibility/Multiplayer.cs | 18 +++++++++++++++++- .../Harmony/Harmony_Pawn_DraftController.cs | 11 +++++++++++ .../MultiplayerCompat/MultiplayerCompat.cs | 2 +- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Source/CombatExtended/Compatibility/Multiplayer.cs b/Source/CombatExtended/Compatibility/Multiplayer.cs index f44b1e6d3a..c4d4c05e7f 100644 --- a/Source/CombatExtended/Compatibility/Multiplayer.cs +++ b/Source/CombatExtended/Compatibility/Multiplayer.cs @@ -40,6 +40,19 @@ public static bool InMultiplayer } } + public static bool IsExecutingCommands + { + get + { + if (isMultiplayerActive) + { + return _isExecutingCommands(); + } + + return false; + } + } + public static bool IsExecutingCommandsIssuedBySelf { get @@ -52,14 +65,17 @@ public static bool IsExecutingCommandsIssuedBySelf } } - public static void registerCallbacks(Func inMP, Func iecibs) + public static void registerCallbacks(Func inMP, Func iec, Func iecibs) { _inMultiplayer = inMP; + _isExecutingCommands = iec; _isExecutingCommandsIssuedBySelf = iecibs; } private static Func _inMultiplayer = null; + private static Func _isExecutingCommands = null; + private static Func _isExecutingCommandsIssuedBySelf = null; [AttributeUsage(AttributeTargets.Method)] diff --git a/Source/CombatExtended/Harmony/Harmony_Pawn_DraftController.cs b/Source/CombatExtended/Harmony/Harmony_Pawn_DraftController.cs index 73608b7ad0..4f5c0d031f 100644 --- a/Source/CombatExtended/Harmony/Harmony_Pawn_DraftController.cs +++ b/Source/CombatExtended/Harmony/Harmony_Pawn_DraftController.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using CombatExtended.Compatibility; using Verse.AI; namespace CombatExtended.HarmonyCE @@ -16,6 +17,16 @@ public static void Postfix(Pawn_DraftController __instance, bool ___fireAtWillIn { if (!___fireAtWillInt) { + // In Multiplayer, the FireAtWill setter is marked as a sync method. + // Due to how those work - the method will be stopped from running, the call synchronized to all players and then will run + // fully. However, because of how Harmony works all prefixes, postfixes, finalizers will still run - which will cause issues + // as this postfix will run before it ends up being synchronized in Multiplayer. + // Once Multiplayer API exposes `InInterface` method, it should replace this check (as it'll better handle a few edge cases). + if (Multiplayer.InMultiplayer && !Multiplayer.IsExecutingCommands) + { + return; + } + var jobTracker = __instance.pawn.jobs; foreach (var queuedJob in jobTracker.jobQueue.ToList()) { diff --git a/Source/MultiplayerCompat/MultiplayerCompat/MultiplayerCompat.cs b/Source/MultiplayerCompat/MultiplayerCompat/MultiplayerCompat.cs index 37787a498f..6612c50c92 100644 --- a/Source/MultiplayerCompat/MultiplayerCompat/MultiplayerCompat.cs +++ b/Source/MultiplayerCompat/MultiplayerCompat/MultiplayerCompat.cs @@ -65,7 +65,7 @@ public void SlowInit(ModContentPack content) MP.RegisterAll(); - global::CombatExtended.Compatibility.Multiplayer.registerCallbacks((() => MP.IsInMultiplayer), (() => MP.IsExecutingSyncCommandIssuedBySelf)); + global::CombatExtended.Compatibility.Multiplayer.registerCallbacks((() => MP.IsInMultiplayer), (() => MP.IsExecutingSyncCommand), (() => MP.IsExecutingSyncCommandIssuedBySelf)); } From 1eaf226e4a6fca14987b9864ca1c56e13ad408eb Mon Sep 17 00:00:00 2001 From: CMDR-Bill-Doors Date: Tue, 12 Sep 2023 18:04:33 +0100 Subject: [PATCH 018/158] recoil animation for trycastshotglobal --- Source/CombatExtended/CombatExtended/Verbs/Verb_ShootMortarCE.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootMortarCE.cs b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootMortarCE.cs index 134917c780..2f548cd217 100644 --- a/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootMortarCE.cs +++ b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootMortarCE.cs @@ -215,6 +215,7 @@ public virtual bool TryCastGlobalShot() CompReloadable.UsedOnce(); } } + lastShotTick = Find.TickManager.TicksGame; return true; } From ee60e3bd75130a3d836bb729d41dd0c758d8f212 Mon Sep 17 00:00:00 2001 From: CMDR-Bill-Doors Date: Tue, 12 Sep 2023 20:04:26 +0100 Subject: [PATCH 019/158] Update WorldObjectDamageWorker.cs --- .../CombatExtended/WorldObjects/WorldObjectDamageWorker.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs b/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs index d4370b5e48..fdfd57740c 100644 --- a/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs +++ b/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs @@ -37,12 +37,12 @@ public virtual float CalculateDamage(ThingDef projectile, Faction faction) var result = FragmentsPotentialDamage(projectile) + ExplosionPotentialDamage(projectile) + FirePotentialDamage(projectile) + EMPPotentialDamage(projectile, empModifier); //Damage calculated as in-map damage, needs to be converted into world object damage. 3500f experimentally obtained result /= 3500f; - //Crit/Miss imitation - result *= Rand.Range(0.4f, 1.5f); if (projectile.projectile is ProjectilePropertiesCE projectileProperties && projectileProperties.shellingProps.damage > 0f) { - result *= projectileProperties.shellingProps.damage; + result = projectileProperties.shellingProps.damage; } + //Crit/Miss imitation + result *= Rand.Range(0.4f, 1.5f); return result; } protected const float fragDamageMultipler = 0.04f; From 71f53812edbb61c886de23495ac56b0947af91b5 Mon Sep 17 00:00:00 2001 From: CMDR-Bill-Doors Date: Tue, 12 Sep 2023 20:04:41 +0100 Subject: [PATCH 020/158] Revert "Update WorldObjectDamageWorker.cs" This reverts commit ee60e3bd75130a3d836bb729d41dd0c758d8f212. --- .../CombatExtended/WorldObjects/WorldObjectDamageWorker.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs b/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs index fdfd57740c..d4370b5e48 100644 --- a/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs +++ b/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs @@ -37,12 +37,12 @@ public virtual float CalculateDamage(ThingDef projectile, Faction faction) var result = FragmentsPotentialDamage(projectile) + ExplosionPotentialDamage(projectile) + FirePotentialDamage(projectile) + EMPPotentialDamage(projectile, empModifier); //Damage calculated as in-map damage, needs to be converted into world object damage. 3500f experimentally obtained result /= 3500f; + //Crit/Miss imitation + result *= Rand.Range(0.4f, 1.5f); if (projectile.projectile is ProjectilePropertiesCE projectileProperties && projectileProperties.shellingProps.damage > 0f) { - result = projectileProperties.shellingProps.damage; + result *= projectileProperties.shellingProps.damage; } - //Crit/Miss imitation - result *= Rand.Range(0.4f, 1.5f); return result; } protected const float fragDamageMultipler = 0.04f; From c99709ba0bd5991f7c7a49915ff6d66448f75edf Mon Sep 17 00:00:00 2001 From: CMDR-Bill-Doors Date: Tue, 12 Sep 2023 20:06:02 +0100 Subject: [PATCH 021/158] Update WorldObjectDamageWorker.cs --- .../CombatExtended/WorldObjects/WorldObjectDamageWorker.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs b/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs index d4370b5e48..fdfd57740c 100644 --- a/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs +++ b/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs @@ -37,12 +37,12 @@ public virtual float CalculateDamage(ThingDef projectile, Faction faction) var result = FragmentsPotentialDamage(projectile) + ExplosionPotentialDamage(projectile) + FirePotentialDamage(projectile) + EMPPotentialDamage(projectile, empModifier); //Damage calculated as in-map damage, needs to be converted into world object damage. 3500f experimentally obtained result /= 3500f; - //Crit/Miss imitation - result *= Rand.Range(0.4f, 1.5f); if (projectile.projectile is ProjectilePropertiesCE projectileProperties && projectileProperties.shellingProps.damage > 0f) { - result *= projectileProperties.shellingProps.damage; + result = projectileProperties.shellingProps.damage; } + //Crit/Miss imitation + result *= Rand.Range(0.4f, 1.5f); return result; } protected const float fragDamageMultipler = 0.04f; From 0cd0e775b6e56b1601de0af14044066f5d9a6538 Mon Sep 17 00:00:00 2001 From: Keshash <54061981+Keshash@users.noreply.github.com> Date: Tue, 12 Sep 2023 22:51:08 +0300 Subject: [PATCH 022/158] Turn Motes into Flecks --- Defs/Ammo/HighCaliber/14.5x114mmSoviet.xml | 29 ++++ Defs/Ammo/Rifle/762x51mmNATO.xml | 29 ++++ Defs/Ammo/Shotgun/12Gauge.xml | 20 +++ Defs/Effects/ProjectileFX_Flecks.xml | 27 ++++ Defs/SoundDefs/AmmoSounds.xml | 17 +++ Languages/English/Keyed/ModMenu.xml | 3 + .../Projectiles/ProjectileCE.cs | 3 +- .../CombatExtended/CombatExtended/Settings.cs | 6 +- .../EffectProjectileExtension.cs | 126 ++++++++---------- .../ProjectileImpactFX/TrailThrower.cs | 28 ++-- .../TrailerProjectileExtension.cs | 2 +- 11 files changed, 196 insertions(+), 94 deletions(-) create mode 100644 Defs/Effects/ProjectileFX_Flecks.xml diff --git a/Defs/Ammo/HighCaliber/14.5x114mmSoviet.xml b/Defs/Ammo/HighCaliber/14.5x114mmSoviet.xml index 9889e37f1f..058b383ebe 100644 --- a/Defs/Ammo/HighCaliber/14.5x114mmSoviet.xml +++ b/Defs/Ammo/HighCaliber/14.5x114mmSoviet.xml @@ -126,6 +126,12 @@ Fleck_RifleAmmoCasings_HighCal Filth_RifleAmmoCasings_HighCal + +
  • + DustPuff + 3 +
  • + @@ -162,6 +168,17 @@ + +
  • + true + 0.5 + MicroSparksFast + AirPuff + 2 + CE_HeatGlow_API + 1 +
  • +
    @@ -178,6 +195,18 @@ + +
  • + true + 0.5 + BlastFlame + AirPuff + 2 + ExplosionFlash + 5 + CE_Explosion_APHE +
  • +
    diff --git a/Defs/Ammo/Rifle/762x51mmNATO.xml b/Defs/Ammo/Rifle/762x51mmNATO.xml index 2932762268..6306137d0d 100644 --- a/Defs/Ammo/Rifle/762x51mmNATO.xml +++ b/Defs/Ammo/Rifle/762x51mmNATO.xml @@ -138,6 +138,12 @@ 156 true + +
  • + DustPuff + 2 +
  • +
    @@ -184,6 +190,17 @@ + +
  • + true + 0.5 + MicroSparksFast + AirPuff + 1 + CE_HeatGlow_API + 1 +
  • +
    @@ -200,6 +217,18 @@ + +
  • + true + AirPuff + 1 + BlastFlame + 0.25 + ExplosionFlash + 2 + CE_Explosion_APHE +
  • +
    diff --git a/Defs/Ammo/Shotgun/12Gauge.xml b/Defs/Ammo/Shotgun/12Gauge.xml index ab6a1cbcd6..90dfcb4069 100644 --- a/Defs/Ammo/Shotgun/12Gauge.xml +++ b/Defs/Ammo/Shotgun/12Gauge.xml @@ -127,6 +127,12 @@ 4.52 8.9 + +
  • + DustPuff + 1 +
  • +
    @@ -144,6 +150,12 @@ Fleck_ShotgunShell_Green Filth_ShotgunAmmoCasings_Green + +
  • + DustPuff + 2 +
  • +
    @@ -182,6 +194,14 @@ Fleck_ShotgunShell_Black Filth_ShotgunAmmoCasings_Black + +
  • + ElectricalSpark + 2 + CE_ElectricGlow_EMP + 4 +
  • +
    diff --git a/Defs/Effects/ProjectileFX_Flecks.xml b/Defs/Effects/ProjectileFX_Flecks.xml new file mode 100644 index 0000000000..749ba2a95f --- /dev/null +++ b/Defs/Effects/ProjectileFX_Flecks.xml @@ -0,0 +1,27 @@ + + + + + + CE_ElectricGlow_EMP + + Things/Mote/LightningGlow + MoteGlow + + MoteOverhead + 0.1 + 0.1 + + + + CE_HeatGlow_API + + Things/Mote/HeatGlow + MoteGlow + + MoteOverhead + 0.15 + 0.2 + + + \ No newline at end of file diff --git a/Defs/SoundDefs/AmmoSounds.xml b/Defs/SoundDefs/AmmoSounds.xml index 017150e4a5..4f420b6ce0 100644 --- a/Defs/SoundDefs/AmmoSounds.xml +++ b/Defs/SoundDefs/AmmoSounds.xml @@ -49,4 +49,21 @@ + + CE_Explosion_APHE + MapOnly + 1 + +
  • + +
  • + Weapon/RocketswarmLauncher/Explosion +
  • + + 15 + 1.5~2 + +
    +
    + \ No newline at end of file diff --git a/Languages/English/Keyed/ModMenu.xml b/Languages/English/Keyed/ModMenu.xml index 5a269914f9..ca1df7336e 100644 --- a/Languages/English/Keyed/ModMenu.xml +++ b/Languages/English/Keyed/ModMenu.xml @@ -82,6 +82,9 @@ If true, subsequent shots at the same, or nearby, targets are faster. Faster subsequent shots + Show additional projectile visual effects + Bullets and other projectiles will display additional effects, such as bullets kicking up dust or incendiaries throwing sparks. Cosmetic only, does not affect gameplay. + Bipod settings diff --git a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs index 87b36d30df..30023366c5 100644 --- a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs @@ -1245,9 +1245,8 @@ public virtual void Impact(Thing hitThing) //} if (def.HasModExtension()) { - def.GetModExtension()?.ThrowMote(ExactPosition, + def.GetModExtension()?.ThrowFleck(ExactPosition, Map, - def.projectile.damageDef.explosionCellMote, def.projectile.damageDef.explosionColorCenter, def.projectile.damageDef.soundExplosion, hitThing); diff --git a/Source/CombatExtended/CombatExtended/Settings.cs b/Source/CombatExtended/CombatExtended/Settings.cs index 33c0f1d18c..5bed69bdc0 100644 --- a/Source/CombatExtended/CombatExtended/Settings.cs +++ b/Source/CombatExtended/CombatExtended/Settings.cs @@ -28,6 +28,7 @@ public class Settings : ModSettings, ISettingsCE private bool showTacticalVests = true; private bool genericammo = false; private bool partialstats = true; + private bool enableExtraEffects = true; private bool showExtraTooltips = false; @@ -52,6 +53,7 @@ public class Settings : ModSettings, ISettingsCE public bool ShowTacticalVests => showTacticalVests; public bool PartialStat => partialstats; + public bool EnableExtraEffects => enableExtraEffects; public bool ShowExtraTooltips => showExtraTooltips; public bool ShowExtraStats => showExtraStats; @@ -147,7 +149,7 @@ public override void ExposeData() Scribe_Values.Look(ref showBackpacks, "showBackpacks", true); Scribe_Values.Look(ref showTacticalVests, "showTacticalVests", true); Scribe_Values.Look(ref partialstats, "PartialArmor", true); - + Scribe_Values.Look(ref enableExtraEffects, "enableExtraEffects", true); Scribe_Values.Look(ref showExtraTooltips, "showExtraTooltips", false); Scribe_Values.Look(ref showExtraStats, "showExtraStats", false); @@ -218,7 +220,7 @@ public void DoWindowContents(Rect canvas, ref int offset) list.CheckboxLabeled("CE_Settings_ShowExtraTooltips_Title".Translate(), ref showExtraTooltips, "CE_Settings_ShowExtraTooltips_Desc".Translate()); list.CheckboxLabeled("CE_Settings_ShowExtraStats_Title".Translate(), ref showExtraStats, "CE_Settings_ShowExtraStats_Desc".Translate()); list.CheckboxLabeled("CE_Settings_FasterRepeatShots_Title".Translate(), ref fasterRepeatShots, "CE_Settings_FasterRepeatShots_Desc".Translate()); - + list.CheckboxLabeled("CE_Settings_EnableExtraEffects_Title".Translate(), ref enableExtraEffects, "CE_Settings_EnableExtraEffects_Desc".Translate()); // Only Allow these settings to be changed in the main menu since doing while a // map is loaded will result in rendering issues. if (Current.Game == null) diff --git a/Source/CombatExtended/Contrib/ProjectileImpactFX/EffectProjectileExtension.cs b/Source/CombatExtended/Contrib/ProjectileImpactFX/EffectProjectileExtension.cs index 9cbed40350..7b8fcb3328 100644 --- a/Source/CombatExtended/Contrib/ProjectileImpactFX/EffectProjectileExtension.cs +++ b/Source/CombatExtended/Contrib/ProjectileImpactFX/EffectProjectileExtension.cs @@ -1,5 +1,4 @@ using RimWorld; -using System; using UnityEngine; using Verse; using Verse.Sound; @@ -9,17 +8,18 @@ namespace ProjectileImpactFX // ProjectileImpactFX.EffectProjectileExtension public class EffectProjectileExtension : DefModExtension { - public bool explosionMote = false; - public string explosionMoteDef = string.Empty; - public float explosionMoteSize = 1f; + public bool explosionFleck = false; + public string explosionFleckDef = string.Empty; + public float explosionFleckSize = 1f; public EffecterDef explosionEffecter; - public FloatRange? explosionMoteSizeRange; - public string ImpactMoteDef = string.Empty; - public float ImpactMoteSize = 1f; - public FloatRange? ImpactMoteSizeRange; - public string ImpactGlowMoteDef = string.Empty; - public float ImpactGlowMoteSize = 1f; - public FloatRange? ImpactGlowMoteSizeRange; + public FloatRange? explosionFleckSizeRange; + public string ImpactFleckDef = string.Empty; + public float ImpactFleckSize = 1f; + public FloatRange? ImpactFleckSizeRange; + public string impactSoundDef = string.Empty; + public string ImpactGlowFleckDef = string.Empty; + public float ImpactGlowFleckSize = 1f; + public FloatRange? ImpactGlowFleckSizeRange; public bool muzzleFlare = false; public string muzzleFlareDef = string.Empty; public float muzzleFlareSize = 1f; @@ -27,85 +27,65 @@ public class EffectProjectileExtension : DefModExtension public string muzzleSmokeDef = string.Empty; public float muzzleSmokeSize = 0.35f; - public void ThrowMote(Vector3 loc, Map map, ThingDef explosionMoteDef, Color color, SoundDef sound, Thing hitThing = null) + public void ThrowFleck(Vector3 loc, Map map, Color color, SoundDef sound, Thing hitThing = null) { - ThingDef explosionmoteDef = explosionMoteDef; - ThingDef ImpactMoteDef = DefDatabase.GetNamedSilentFail(this.ImpactMoteDef) ?? null; - ThingDef ImpactGlowMoteDef = DefDatabase.GetNamedSilentFail(this.ImpactGlowMoteDef) ?? null; - float explosionSize = this.explosionMoteSize; - float ImpactMoteSize = this.ImpactMoteSizeRange?.RandomInRange ?? this.ImpactMoteSize; - float ImpactGlowMoteSize = this.ImpactGlowMoteSizeRange?.RandomInRange ?? this.ImpactGlowMoteSize; + FleckDef ExplosionFleck = explosionFleckDef != string.Empty ? DefDatabase.GetNamed(this.explosionFleckDef) : null; + FleckDef ImpactFleck = ImpactFleckDef != string.Empty ? DefDatabase.GetNamed(this.ImpactFleckDef) : null; + FleckDef ImpactGlowFleck = ImpactGlowFleckDef != string.Empty ? DefDatabase.GetNamed(this.ImpactGlowFleckDef) : null; + SoundDef ImpactSound = impactSoundDef != string.Empty ? DefDatabase.GetNamed(this.impactSoundDef) : null; + float explosionSize = this.explosionFleckSizeRange?.RandomInRange ?? this.explosionFleckSize; + float ImpactFleckSize = this.ImpactFleckSizeRange?.RandomInRange ?? this.ImpactFleckSize; + float ImpactGlowFleckSize = this.ImpactGlowFleckSizeRange?.RandomInRange ?? this.ImpactGlowFleckSize; if (!loc.ShouldSpawnMotesAt(map) || map.moteCounter.SaturatedLowPriority) { return; } Rand.PushState(); float rotationRate = Rand.Range(-30f, 30f); - float VelocityAngel = (float)Rand.Range(0, 360); - float VelocitySpeed = Rand.Range(0.48f, 0.72f); + float initialRotation = Rand.Range(0, 360); + float VelocityAngle = (float)Rand.Range(-30, 30); + float VelocitySpeed = Rand.Range(0.5f, 1f); Rand.PopState(); - if (ImpactGlowMoteDef != null) + if (ImpactFleck != null) { - MoteMaker.MakeStaticMote(loc, map, ImpactGlowMoteDef, ImpactGlowMoteSize); - } - if (explosionMote) - { - if (!this.explosionMoteDef.NullOrEmpty()) + if (explosionEffecter != null) { - ThingDef def = DefDatabase.GetNamedSilentFail(this.explosionMoteDef); - if (def != null) - { - explosionmoteDef = def; - } - } - if (explosionmoteDef != null) - { - MoteThrown moteThrown; - moteThrown = (MoteThrown)ThingMaker.MakeThing(explosionmoteDef, null); - moteThrown.Scale = explosionSize; - Rand.PushState(); - moteThrown.rotationRate = Rand.Range(-30f, 30f); - Rand.PopState(); - moteThrown.exactPosition = loc; - moteThrown.instanceColor = color; - moteThrown.SetVelocity(VelocityAngel, VelocitySpeed); - GenSpawn.Spawn(moteThrown, loc.ToIntVec3(), map, WipeMode.Vanish); + TriggerEffect(explosionEffecter, loc, map); } + FleckCreationData creationData = FleckMaker.GetDataStatic(loc, map, ImpactFleck); + creationData.rotation = initialRotation; + creationData.velocityAngle = VelocityAngle; + creationData.velocitySpeed = VelocitySpeed; + creationData.scale = ImpactFleckSize; + creationData.spawnPosition = loc; + map.flecks.CreateFleck(creationData); + //} } - if (ImpactMoteDef != null) + if (ImpactGlowFleck != null) { - if (hitThing != null && hitThing is Pawn pawn) - { - ImpactMoteDef = ThingDef.Named("Mote_BloodPuff"); - if (sound != null) - { - sound.PlayOneShot(new TargetInfo(loc.ToIntVec3(), map, false)); - } - MoteThrown moteThrown; - moteThrown = (MoteThrown)ThingMaker.MakeThing(ImpactMoteDef, null); - moteThrown.Scale = ImpactMoteSize; - Rand.PushState(); - moteThrown.rotationRate = Rand.Range(-30f, 30f); - Rand.PopState(); - moteThrown.exactPosition = loc; - moteThrown.instanceColor = pawn.RaceProps.BloodDef.graphic.color; - moteThrown.SetVelocity(VelocityAngel, VelocitySpeed); - GenSpawn.Spawn(moteThrown, loc.ToIntVec3(), map, WipeMode.Vanish); - if (explosionEffecter != null) - { - TriggerEffect(explosionEffecter, loc, map, hitThing); - } - } - else + FleckCreationData creationData = FleckMaker.GetDataStatic(loc, map, ImpactGlowFleck); + creationData.scale = ImpactGlowFleckSize; + map.flecks.CreateFleck(creationData); + } + if (explosionFleck) + { + if (ExplosionFleck != null) { - if (explosionEffecter != null) - { - TriggerEffect(explosionEffecter, loc, map); - } - MoteMaker.MakeStaticMote(loc, map, ImpactMoteDef, ImpactMoteSize); + FleckCreationData creationData = FleckMaker.GetDataStatic(loc, map, ExplosionFleck); + creationData.scale = explosionSize; + creationData.rotationRate = rotationRate; + creationData.spawnPosition = loc; + creationData.instanceColor = color; + creationData.velocityAngle = VelocityAngle; + creationData.velocitySpeed = VelocitySpeed; + map.flecks.CreateFleck(creationData); } } + if (ImpactSound != null) + { + ImpactSound.PlayOneShot(new TargetInfo(loc.ToIntVec3(), map)); + } } void TriggerEffect(EffecterDef effect, Vector3 position, Map map, Thing hitThing = null) diff --git a/Source/CombatExtended/Contrib/ProjectileImpactFX/TrailThrower.cs b/Source/CombatExtended/Contrib/ProjectileImpactFX/TrailThrower.cs index f73a984297..a030e99890 100644 --- a/Source/CombatExtended/Contrib/ProjectileImpactFX/TrailThrower.cs +++ b/Source/CombatExtended/Contrib/ProjectileImpactFX/TrailThrower.cs @@ -11,31 +11,27 @@ namespace ProjectileImpactFX { public class TrailThrower { - public static void ThrowSmoke(Vector3 loc, float size, Map map, string DefName) + public static void ThrowSmoke(Vector3 loc, float size, Map map, string defName) { if (!loc.ShouldSpawnMotesAt(map) || map.moteCounter.SaturatedLowPriority) { return; } + FleckDef fleck = DefDatabase.GetNamed(defName) ?? null; - //Rand.PushState(); - //MoteThrown moteThrown = (MoteThrown)ThingMaker.MakeThing(ThingDefOf.Mote_Smoke, null); - //moteThrown.Scale = Rand.Range(1.5f, 2.5f) * size; - //moteThrown.rotationRate = Rand.Range(-30f, 30f); - //moteThrown.exactPosition = loc; - //moteThrown.SetVelocity((float)Rand.Range(30, 40), Rand.Range(0.5f, 0.7f)); - //Rand.PopState(); - //GenSpawn.Spawn(moteThrown, loc.ToIntVec3(), map, WipeMode.Vanish); + if (fleck != null) + { + Rand.PushState(); + FleckCreationData dataStatic = FleckMaker.GetDataStatic(loc, map, fleck, Rand.Range(1.5f, 2.5f) * size); - Rand.PushState(); - FleckCreationData dataStatic = FleckMaker.GetDataStatic(loc, map, FleckDefOf.Smoke, Rand.Range(1.5f, 2.5f) * size); + dataStatic.rotationRate = Rand.Range(-30f, 30f); + dataStatic.velocityAngle = (float)Rand.Range(30, 40); + dataStatic.velocitySpeed = Rand.Range(0.5f, 0.7f); - dataStatic.rotationRate = Rand.Range(-30f, 30f); - dataStatic.velocityAngle = (float)Rand.Range(30, 40); - dataStatic.velocitySpeed = Rand.Range(0.5f, 0.7f); + Rand.PopState(); + map.flecks.CreateFleck(dataStatic); + } - Rand.PopState(); - map.flecks.CreateFleck(dataStatic); } } } diff --git a/Source/CombatExtended/Contrib/ProjectileImpactFX/TrailerProjectileExtension.cs b/Source/CombatExtended/Contrib/ProjectileImpactFX/TrailerProjectileExtension.cs index 6ece276fcd..c83e4ed5b2 100644 --- a/Source/CombatExtended/Contrib/ProjectileImpactFX/TrailerProjectileExtension.cs +++ b/Source/CombatExtended/Contrib/ProjectileImpactFX/TrailerProjectileExtension.cs @@ -9,7 +9,7 @@ namespace ProjectileImpactFX // ProjectileImpactFX.TrailerProjectileExtension public class TrailerProjectileExtension : DefModExtension { - public string trailMoteDef = "Mote_Smoke"; + public string trailMoteDef = "AirPuff"; public float trailMoteSize = 0.5f; public int trailerMoteInterval = 30; public int motesThrown = 1; From be1855f50a391ee4277129f45a8af68ccb9eb1b0 Mon Sep 17 00:00:00 2001 From: Keshash <54061981+Keshash@users.noreply.github.com> Date: Wed, 13 Sep 2023 00:05:54 +0300 Subject: [PATCH 023/158] rename field --- Defs/Ammo/HighCaliber/14.5x114mmSoviet.xml | 2 +- Defs/Ammo/Rifle/762x51mmNATO.xml | 2 +- .../Contrib/ProjectileImpactFX/EffectProjectileExtension.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Defs/Ammo/HighCaliber/14.5x114mmSoviet.xml b/Defs/Ammo/HighCaliber/14.5x114mmSoviet.xml index 058b383ebe..d3275cec70 100644 --- a/Defs/Ammo/HighCaliber/14.5x114mmSoviet.xml +++ b/Defs/Ammo/HighCaliber/14.5x114mmSoviet.xml @@ -204,7 +204,7 @@ 2 ExplosionFlash 5 - CE_Explosion_APHE + CE_Explosion_APHE diff --git a/Defs/Ammo/Rifle/762x51mmNATO.xml b/Defs/Ammo/Rifle/762x51mmNATO.xml index 6306137d0d..fd30fe113c 100644 --- a/Defs/Ammo/Rifle/762x51mmNATO.xml +++ b/Defs/Ammo/Rifle/762x51mmNATO.xml @@ -226,7 +226,7 @@ 0.25 ExplosionFlash 2 - CE_Explosion_APHE + CE_Explosion_APHE diff --git a/Source/CombatExtended/Contrib/ProjectileImpactFX/EffectProjectileExtension.cs b/Source/CombatExtended/Contrib/ProjectileImpactFX/EffectProjectileExtension.cs index 7b8fcb3328..34b3b980ad 100644 --- a/Source/CombatExtended/Contrib/ProjectileImpactFX/EffectProjectileExtension.cs +++ b/Source/CombatExtended/Contrib/ProjectileImpactFX/EffectProjectileExtension.cs @@ -16,7 +16,7 @@ public class EffectProjectileExtension : DefModExtension public string ImpactFleckDef = string.Empty; public float ImpactFleckSize = 1f; public FloatRange? ImpactFleckSizeRange; - public string impactSoundDef = string.Empty; + public string ImpactSoundDef = string.Empty; public string ImpactGlowFleckDef = string.Empty; public float ImpactGlowFleckSize = 1f; public FloatRange? ImpactGlowFleckSizeRange; @@ -32,7 +32,7 @@ public void ThrowFleck(Vector3 loc, Map map, Color color, SoundDef sound, Thing FleckDef ExplosionFleck = explosionFleckDef != string.Empty ? DefDatabase.GetNamed(this.explosionFleckDef) : null; FleckDef ImpactFleck = ImpactFleckDef != string.Empty ? DefDatabase.GetNamed(this.ImpactFleckDef) : null; FleckDef ImpactGlowFleck = ImpactGlowFleckDef != string.Empty ? DefDatabase.GetNamed(this.ImpactGlowFleckDef) : null; - SoundDef ImpactSound = impactSoundDef != string.Empty ? DefDatabase.GetNamed(this.impactSoundDef) : null; + SoundDef ImpactSound = ImpactSoundDef != string.Empty ? DefDatabase.GetNamed(this.ImpactSoundDef) : null; float explosionSize = this.explosionFleckSizeRange?.RandomInRange ?? this.explosionFleckSize; float ImpactFleckSize = this.ImpactFleckSizeRange?.RandomInRange ?? this.ImpactFleckSize; float ImpactGlowFleckSize = this.ImpactGlowFleckSizeRange?.RandomInRange ?? this.ImpactGlowFleckSize; From 9e0f50c0cab7e70ff694e7bc01fdf8fc6e39540e Mon Sep 17 00:00:00 2001 From: Keshash <54061981+Keshash@users.noreply.github.com> Date: Wed, 13 Sep 2023 00:08:46 +0300 Subject: [PATCH 024/158] code style --- .../Contrib/ProjectileImpactFX/TrailThrower.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Source/CombatExtended/Contrib/ProjectileImpactFX/TrailThrower.cs b/Source/CombatExtended/Contrib/ProjectileImpactFX/TrailThrower.cs index a030e99890..b3eeb23957 100644 --- a/Source/CombatExtended/Contrib/ProjectileImpactFX/TrailThrower.cs +++ b/Source/CombatExtended/Contrib/ProjectileImpactFX/TrailThrower.cs @@ -1,9 +1,4 @@ using RimWorld; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using UnityEngine; using Verse; @@ -17,9 +12,9 @@ public static void ThrowSmoke(Vector3 loc, float size, Map map, string defName) { return; } - FleckDef fleck = DefDatabase.GetNamed(defName) ?? null; + FleckDef fleck = DefDatabase.GetNamed(defName) ?? null; - if (fleck != null) + if (fleck != null) { Rand.PushState(); FleckCreationData dataStatic = FleckMaker.GetDataStatic(loc, map, fleck, Rand.Range(1.5f, 2.5f) * size); From c5121a846eb729a8ed2d773011dad80cd2a2a648 Mon Sep 17 00:00:00 2001 From: CMDR-Bill-Doors Date: Wed, 13 Sep 2023 13:15:31 +0100 Subject: [PATCH 025/158] Update WorldObjectDamageWorker.cs --- .../CombatExtended/WorldObjects/WorldObjectDamageWorker.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs b/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs index fdfd57740c..be010fb702 100644 --- a/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs +++ b/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs @@ -35,12 +35,13 @@ public virtual float CalculateDamage(ThingDef projectile, Faction faction) } } var result = FragmentsPotentialDamage(projectile) + ExplosionPotentialDamage(projectile) + FirePotentialDamage(projectile) + EMPPotentialDamage(projectile, empModifier); - //Damage calculated as in-map damage, needs to be converted into world object damage. 3500f experimentally obtained - result /= 3500f; if (projectile.projectile is ProjectilePropertiesCE projectileProperties && projectileProperties.shellingProps.damage > 0f) { result = projectileProperties.shellingProps.damage; } + //Damage calculated as in-map damage, needs to be converted into world object damage. 3500f experimentally obtained + result /= 3500f; + Log.Message(result.ToString()); //Crit/Miss imitation result *= Rand.Range(0.4f, 1.5f); return result; From fcc85d4a032028110c0766400aec3a04a367ce55 Mon Sep 17 00:00:00 2001 From: SokyranTheDragon Date: Fri, 15 Sep 2023 01:04:48 +0200 Subject: [PATCH 026/158] Fixed desync with multiplayer 0-tick impact projectiles --- .../CombatExtended/Projectiles/ProjectileCE.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs index 87b36d30df..3520de6655 100644 --- a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs @@ -170,7 +170,12 @@ public float StartingTicksToImpact { destinationInt = origin; startingTicksToImpactInt = 0f; - ImpactSomething(); + // During drawing in Multiplayer - impact causes issues. Will get handled inside of the `Tick` call. + // In the future, replace this with `!InInterface` call, as it's more fitting here. + if (!Multiplayer.InMultiplayer) + { + ImpactSomething(); + } return 0f; } // Multiplied by ticksPerSecond since the calculated time is actually in seconds. From 7e6f6932f9858922b79cd4b4e081143fa9f9dddb Mon Sep 17 00:00:00 2001 From: CMDR-Bill-Doors Date: Tue, 19 Sep 2023 20:29:16 +0100 Subject: [PATCH 027/158] remove a log I forgot to remove --- .../CombatExtended/WorldObjects/WorldObjectDamageWorker.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs b/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs index be010fb702..6d615890b8 100644 --- a/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs +++ b/Source/CombatExtended/CombatExtended/WorldObjects/WorldObjectDamageWorker.cs @@ -41,7 +41,6 @@ public virtual float CalculateDamage(ThingDef projectile, Faction faction) } //Damage calculated as in-map damage, needs to be converted into world object damage. 3500f experimentally obtained result /= 3500f; - Log.Message(result.ToString()); //Crit/Miss imitation result *= Rand.Range(0.4f, 1.5f); return result; From 4468d40608e26976e18facb6b5a11a77196633d0 Mon Sep 17 00:00:00 2001 From: Aelanna Date: Fri, 29 Sep 2023 18:50:33 -0500 Subject: [PATCH 028/158] Updates to CheckForCollisionBetween and BlockerRegistry CheckForCollisionBetween is now consistent with RimWorld 1.4, which causes explosive projectiles to detonate against shields instead of vanishing. BlockerRegistry has been updated with a proper hook for adding vanilla-style projectile interceptors, including the option to choose whether projectiles should impact or vanish. --- .../Projectiles/ProjectileCE.cs | 15 +++++++++- .../Compatibility/BlockerRegistry.cs | 28 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs index 87b36d30df..5a904c828e 100644 --- a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs @@ -761,10 +761,23 @@ private bool CheckForCollisionBetween() { if (CheckIntercept(list[i], list[i].TryGetComp())) { - this.Destroy(DestroyMode.Vanish); + this.Impact(null); return true; } } + (bool intercepted, bool destroyed) = BlockerRegistry.CheckForCollisionBetweenCallback(this, LastPos, ExactPosition); + if (intercepted) + { + if (destroyed) + { + this.Destroy(DestroyMode.Vanish); + } + else + { + this.Impact(null); + } + return true; + } #region Sanity checks if (ticksToImpact < 0 || def.projectile.flyOverhead) diff --git a/Source/CombatExtended/Compatibility/BlockerRegistry.cs b/Source/CombatExtended/Compatibility/BlockerRegistry.cs index ab526dff6d..6027577873 100644 --- a/Source/CombatExtended/Compatibility/BlockerRegistry.cs +++ b/Source/CombatExtended/Compatibility/BlockerRegistry.cs @@ -12,16 +12,27 @@ namespace CombatExtended.Compatibility public static class BlockerRegistry { private static bool enabled = false; + private static List> checkForCollisionBetweenCallbacks; private static List> checkCellForCollisionCallbacks; private static List> impactSomethingCallbacks; private static void Enable() { enabled = true; + checkForCollisionBetweenCallbacks = new List>(); impactSomethingCallbacks = new List>(); checkCellForCollisionCallbacks = new List>(); } + public static void RegisterCheckForCollisionBetweenCallback(Func f) + { + if (!enabled) + { + Enable(); + } + checkForCollisionBetweenCallbacks.Add(f); + } + public static void RegisterCheckForCollisionCallback(Func f) { if (!enabled) @@ -40,6 +51,23 @@ public static void RegisterImpactSomethingCallback(Func Date: Sat, 30 Sep 2023 00:38:45 -0500 Subject: [PATCH 029/158] Separated intercept/impact logic from collision check Created methods allowing shield/interceptor implementations to both directly control what happens to an intercepted projectile (destroyed vs impacted) as well as centralizing logic to handle impact positioning. Methods are virtual to allow projectiles to override default intercept behavior if necessary. Moved a utility method that was a private static on ProjectileCE out to CE_Utility so external shield implementations can use the same calculations for radial intercepts. --- .../CombatExtended/CE_Utility.cs | 25 ++++++++ .../Projectiles/ProjectileCE.cs | 59 ++++++++++--------- .../Compatibility/BlockerRegistry.cs | 17 +++--- 3 files changed, 64 insertions(+), 37 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/CE_Utility.cs b/Source/CombatExtended/CombatExtended/CE_Utility.cs index 25429f39d5..bd8ab7e0de 100644 --- a/Source/CombatExtended/CombatExtended/CE_Utility.cs +++ b/Source/CombatExtended/CombatExtended/CE_Utility.cs @@ -883,6 +883,31 @@ public static float GetCollisionWidth(Thing thing) return 1f; //Buildings, etc. fill out a full square } + /// + /// Calculates whether a line segment intercepts a radius circle. Used to determine if a projectile crosses a shield. + /// + /// The center of the circle + /// The radius of the circle + /// The first point of the line segment + /// The second point of the line segment + /// True if the line segment intercepts the circle + public static bool IntersectLineSphericalOutline(Vector3 center, float radius, Vector3 pointA, Vector3 pointB) + { + var pointAInShield = (center - pointA).sqrMagnitude <= Mathf.Pow(radius, 2); + var pointBInShield = (center - pointB).sqrMagnitude <= Mathf.Pow(radius, 2); + + if (pointAInShield && pointBInShield) + { + return false; + } + if (!pointAInShield && !pointBInShield) + { + return false; + } + + return true; + } + /// /// Calculates body scale factors based on body type /// diff --git a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs index 5a904c828e..d740f2aa9d 100644 --- a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs @@ -644,6 +644,34 @@ public virtual void Launch(Thing launcher, Vector2 origin, Thing equipment = nul #endregion #region Collisions + public virtual void InterceptProjectile(object interceptor, Vector3 impactPosition, bool destroyCompletely = false) + { + if (destroyCompletely) + { + this.Destroy(DestroyMode.Vanish); + } + else + { + this.Impact(null); + ExactPosition = impactPosition; + landed = true; + } + } + public virtual void InterceptProjectile(object interceptor, Vector3 shieldPosition, float shieldRadius, bool destroyCompletely = false) + { + if (destroyCompletely) + { + this.Destroy(DestroyMode.Vanish); + } + else + { + this.Impact(null); + ExactPosition = BlockerRegistry.GetExactPosition(OriginIV3.ToVector3(), ExactPosition, shieldPosition, shieldRadius); + landed = true; + } + } + + private bool CheckIntercept(Thing interceptorThing, CompProjectileInterceptor interceptorComp, bool withDebug = false) { Vector3 shieldPosition = interceptorThing.Position.ToVector3ShiftedWithAltitude(0.5f); @@ -679,7 +707,7 @@ private bool CheckIntercept(Thing interceptorThing, CompProjectileInterceptor in { return false; } - if (!IntersectLineSphericalOutline(shieldPosition, radius, lastExactPos, newExactPos)) + if (!CE_Utility.IntersectLineSphericalOutline(shieldPosition, radius, lastExactPos, newExactPos)) { return false; } @@ -731,23 +759,6 @@ private bool CheckIntercept(Thing interceptorThing, CompProjectileInterceptor in return true; } - private static bool IntersectLineSphericalOutline(Vector3 center, float radius, Vector3 pointA, Vector3 pointB) - { - var pointAInShield = (center - pointA).sqrMagnitude <= Mathf.Pow(radius, 2); - var pointBInShield = (center - pointB).sqrMagnitude <= Mathf.Pow(radius, 2); - - if (pointAInShield && pointBInShield) - { - return false; - } - if (!pointAInShield && !pointBInShield) - { - return false; - } - - return true; - } - //Removed minimum collision distance private bool CheckForCollisionBetween() { @@ -762,20 +773,12 @@ private bool CheckForCollisionBetween() if (CheckIntercept(list[i], list[i].TryGetComp())) { this.Impact(null); + landed = true; return true; } } - (bool intercepted, bool destroyed) = BlockerRegistry.CheckForCollisionBetweenCallback(this, LastPos, ExactPosition); - if (intercepted) + if (BlockerRegistry.CheckForCollisionBetweenCallback(this, LastPos, ExactPosition)) { - if (destroyed) - { - this.Destroy(DestroyMode.Vanish); - } - else - { - this.Impact(null); - } return true; } diff --git a/Source/CombatExtended/Compatibility/BlockerRegistry.cs b/Source/CombatExtended/Compatibility/BlockerRegistry.cs index 6027577873..f120fd5339 100644 --- a/Source/CombatExtended/Compatibility/BlockerRegistry.cs +++ b/Source/CombatExtended/Compatibility/BlockerRegistry.cs @@ -12,19 +12,19 @@ namespace CombatExtended.Compatibility public static class BlockerRegistry { private static bool enabled = false; - private static List> checkForCollisionBetweenCallbacks; + private static List> checkForCollisionBetweenCallbacks; private static List> checkCellForCollisionCallbacks; private static List> impactSomethingCallbacks; private static void Enable() { enabled = true; - checkForCollisionBetweenCallbacks = new List>(); + checkForCollisionBetweenCallbacks = new List>(); impactSomethingCallbacks = new List>(); checkCellForCollisionCallbacks = new List>(); } - public static void RegisterCheckForCollisionBetweenCallback(Func f) + public static void RegisterCheckForCollisionBetweenCallback(Func f) { if (!enabled) { @@ -51,21 +51,20 @@ public static void RegisterImpactSomethingCallback(Func Date: Fri, 29 Sep 2023 23:01:33 -0700 Subject: [PATCH 030/158] Make IntersectLinesphericaloutline branchless --- .../CombatExtended/CombatExtended/CE_Utility.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/CE_Utility.cs b/Source/CombatExtended/CombatExtended/CE_Utility.cs index bd8ab7e0de..2854fbbf9f 100644 --- a/Source/CombatExtended/CombatExtended/CE_Utility.cs +++ b/Source/CombatExtended/CombatExtended/CE_Utility.cs @@ -893,19 +893,8 @@ public static float GetCollisionWidth(Thing thing) /// True if the line segment intercepts the circle public static bool IntersectLineSphericalOutline(Vector3 center, float radius, Vector3 pointA, Vector3 pointB) { - var pointAInShield = (center - pointA).sqrMagnitude <= Mathf.Pow(radius, 2); - var pointBInShield = (center - pointB).sqrMagnitude <= Mathf.Pow(radius, 2); - - if (pointAInShield && pointBInShield) - { - return false; - } - if (!pointAInShield && !pointBInShield) - { - return false; - } - - return true; + var radSq = radius * radius; + return ((center - pointA).sqrMagnitude > radSq) != ((center - pointB).sqrMagnitude > radSq); } /// From eb62205af4db0c4a045bfe48565ef73ac7e5214e Mon Sep 17 00:00:00 2001 From: Logan Perkins Date: Fri, 29 Sep 2023 23:01:57 -0700 Subject: [PATCH 031/158] Defer to default InterceptProjectile Implementation with calculated position --- .../CombatExtended/Projectiles/ProjectileCE.cs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs index d740f2aa9d..6542086f62 100644 --- a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs @@ -659,18 +659,8 @@ public virtual void InterceptProjectile(object interceptor, Vector3 impactPositi } public virtual void InterceptProjectile(object interceptor, Vector3 shieldPosition, float shieldRadius, bool destroyCompletely = false) { - if (destroyCompletely) - { - this.Destroy(DestroyMode.Vanish); - } - else - { - this.Impact(null); - ExactPosition = BlockerRegistry.GetExactPosition(OriginIV3.ToVector3(), ExactPosition, shieldPosition, shieldRadius); - landed = true; - } - } - + InterceptProjectile(interceptor, BlockerRegistry.GetExactPosition(OriginIV3.ToVector3(), ExactPosition, shieldPosition, shieldRadius * shieldRadius)); + } private bool CheckIntercept(Thing interceptorThing, CompProjectileInterceptor interceptorComp, bool withDebug = false) { From 918d9339707484b21f56d1a6ffab1819fba77a9f Mon Sep 17 00:00:00 2001 From: Logan Perkins Date: Fri, 29 Sep 2023 23:02:17 -0700 Subject: [PATCH 032/158] Always set landed and exact position, before calling impact or destroy --- .../CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs index 6542086f62..d5a5524d8b 100644 --- a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs @@ -646,6 +646,8 @@ public virtual void Launch(Thing launcher, Vector2 origin, Thing equipment = nul #region Collisions public virtual void InterceptProjectile(object interceptor, Vector3 impactPosition, bool destroyCompletely = false) { + ExactPosition = impactPosition; + landed = true; if (destroyCompletely) { this.Destroy(DestroyMode.Vanish); @@ -653,8 +655,6 @@ public virtual void InterceptProjectile(object interceptor, Vector3 impactPositi else { this.Impact(null); - ExactPosition = impactPosition; - landed = true; } } public virtual void InterceptProjectile(object interceptor, Vector3 shieldPosition, float shieldRadius, bool destroyCompletely = false) From 3ab3a3d5df340eee5a508df875932d89a3eee7e8 Mon Sep 17 00:00:00 2001 From: Logan Perkins Date: Fri, 29 Sep 2023 23:56:31 -0700 Subject: [PATCH 033/158] Use determinate to identify spherical outline intersections --- Source/CombatExtended/CombatExtended/CE_Utility.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/CE_Utility.cs b/Source/CombatExtended/CombatExtended/CE_Utility.cs index 2854fbbf9f..911ddcb1a5 100644 --- a/Source/CombatExtended/CombatExtended/CE_Utility.cs +++ b/Source/CombatExtended/CombatExtended/CE_Utility.cs @@ -893,8 +893,14 @@ public static float GetCollisionWidth(Thing thing) /// True if the line segment intercepts the circle public static bool IntersectLineSphericalOutline(Vector3 center, float radius, Vector3 pointA, Vector3 pointB) { - var radSq = radius * radius; - return ((center - pointA).sqrMagnitude > radSq) != ((center - pointB).sqrMagnitude > radSq); + Vector3 displacement = pointB - pointA; + double a = displacement.sqrMagnitude; + double b = 2 * (displacement.x * (pointA.x - center.x) + displacement.y * (pointA.y - center.y) + displacement.z * (pointA.z - center.z)); + double c = (center - pointA).sqrMagnitude - radius * radius; + double det = b * b - 4 * a * c; + return det >= 0; +// var radSq = radius * radius; +// return ((center - pointA).sqrMagnitude > radSq) != ((center - pointB).sqrMagnitude > radSq); } /// From abf18a10d891356cc17720980dcafc238c4109ae Mon Sep 17 00:00:00 2001 From: Logan Perkins Date: Fri, 29 Sep 2023 23:57:58 -0700 Subject: [PATCH 034/158] Precalculate and store the squared radius --- .../CombatExtended/Projectiles/ProjectileCE.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs index d5a5524d8b..505ff4724d 100644 --- a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs @@ -667,10 +667,11 @@ private bool CheckIntercept(Thing interceptorThing, CompProjectileInterceptor in Vector3 shieldPosition = interceptorThing.Position.ToVector3ShiftedWithAltitude(0.5f); float radius = interceptorComp.Props.radius; float blockRadius = radius + def.projectile.SpeedTilesPerTick + 0.1f; + float radiusSq = blockRadius * blockRadius; var newExactPos = ExactPosition; - if ((newExactPos - shieldPosition).sqrMagnitude > Mathf.Pow(blockRadius, 2)) + if ((newExactPos - shieldPosition).sqrMagnitude > radiusSq) { return false; } @@ -693,7 +694,7 @@ private bool CheckIntercept(Thing interceptorThing, CompProjectileInterceptor in { return false; } - if (!interceptorComp.Props.interceptOutgoingProjectiles && (shieldPosition - lastExactPos).sqrMagnitude <= Mathf.Pow((float)radius, 2)) + if (!interceptorComp.Props.interceptOutgoingProjectiles && (shieldPosition - lastExactPos).sqrMagnitude <= radius * radius) { return false; } From b0bb86a767ab54b47ace1e2f2440ffdc23c945d4 Mon Sep 17 00:00:00 2001 From: Logan Perkins Date: Fri, 29 Sep 2023 23:58:21 -0700 Subject: [PATCH 035/158] Set landed before calling impact --- .../CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs index 505ff4724d..9f76253d3f 100644 --- a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs @@ -763,8 +763,8 @@ private bool CheckForCollisionBetween() { if (CheckIntercept(list[i], list[i].TryGetComp())) { - this.Impact(null); landed = true; + this.Impact(null); return true; } } From f8018e8113ee57df6bbf8eb23b9d901e483c11e3 Mon Sep 17 00:00:00 2001 From: Logan Perkins Date: Fri, 29 Sep 2023 23:59:07 -0700 Subject: [PATCH 036/158] Use collisionbetween callback for ED SHields --- Source/CombatExtended/Compatibility/EDShields.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/CombatExtended/Compatibility/EDShields.cs b/Source/CombatExtended/Compatibility/EDShields.cs index 4f418ee23f..162a8f008e 100644 --- a/Source/CombatExtended/Compatibility/EDShields.cs +++ b/Source/CombatExtended/Compatibility/EDShields.cs @@ -33,7 +33,7 @@ public bool CanInstall() } public void Install() { - BlockerRegistry.RegisterCheckForCollisionCallback(EDShields.CheckForCollisionCallback); + BlockerRegistry.RegisterCheckForCollisionBetweenCallback(EDShields.CheckForCollisionBetweenCallback); BlockerRegistry.RegisterImpactSomethingCallback(EDShields.ImpactSomethingCallback); Type t = Type.GetType("Jaxxa.EnhancedDevelopment.Shields.Shields.ShieldManagerMapComp, ED-Shields"); HitSoundDef = (SoundDef)t.GetField("HitSoundDef", BindingFlags.Static | BindingFlags.Public).GetValue(null); @@ -42,10 +42,11 @@ public IEnumerable GetCompatList() { yield break; } - public static bool CheckForCollisionCallback(ProjectileCE projectile, IntVec3 cell, Thing launcher) + public static bool CheckForCollisionBetweenCallback(ProjectileCE projectile, Vector3 from, Vector3 to) { /* Check if an active shield can block this projectile, we don't check if the projectile flies overhead, as those projectiles don't call this function */ + Thing launcher = projectile.launcher; Map map = projectile.Map; Vector3 exactPosition = projectile.ExactPosition; IntVec3 origin = projectile.OriginIV3; From 710842ad8dae04339ce29c9e923a6c5e53758be9 Mon Sep 17 00:00:00 2001 From: Logan Perkins Date: Fri, 29 Sep 2023 23:59:46 -0700 Subject: [PATCH 037/158] Don't stop fly-overhead projectiles at the edge of the shield --- Source/CombatExtended/Compatibility/EDShields.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/CombatExtended/Compatibility/EDShields.cs b/Source/CombatExtended/Compatibility/EDShields.cs index 162a8f008e..f2743b036d 100644 --- a/Source/CombatExtended/Compatibility/EDShields.cs +++ b/Source/CombatExtended/Compatibility/EDShields.cs @@ -44,8 +44,11 @@ public IEnumerable GetCompatList() } public static bool CheckForCollisionBetweenCallback(ProjectileCE projectile, Vector3 from, Vector3 to) { - /* Check if an active shield can block this projectile, we don't check if the projectile flies overhead, as those projectiles don't call this function + /* Check if an active shield can block this projectile */ + if (projectile.def.projectile.flyOverhead) { + return false; + } Thing launcher = projectile.launcher; Map map = projectile.Map; Vector3 exactPosition = projectile.ExactPosition; From 953b8137ab191413fdea689db4961d817de7a06c Mon Sep 17 00:00:00 2001 From: Logan Perkins Date: Sat, 30 Sep 2023 00:02:27 -0700 Subject: [PATCH 038/158] Use SphericalOutline function to check for intercept --- .../CombatExtended/Compatibility/EDShields.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/Source/CombatExtended/Compatibility/EDShields.cs b/Source/CombatExtended/Compatibility/EDShields.cs index f2743b036d..560de23a19 100644 --- a/Source/CombatExtended/Compatibility/EDShields.cs +++ b/Source/CombatExtended/Compatibility/EDShields.cs @@ -70,18 +70,15 @@ public static bool CheckForCollisionBetweenCallback(ProjectileCE projectile, Vec continue; } int fieldRadius = (int)generator.FieldRadius_Active(); + Vector3 shieldPosition2D = new Vector3(shield.Position.x, 0, shield.Position.z); + + if (!CE_Utility.IntersectLineSphericalOutline(shieldPosition2D, fieldRadius, from, to)) + { + continue; + } + int fieldRadiusSq = fieldRadius * fieldRadius; - float DistanceSq = projectile.Position.DistanceToSquared(shield.Position) - fieldRadiusSq; - float originDistanceSq = origin.DistanceToSquared(shield.Position) - fieldRadiusSq; - if (DistanceSq > 0) - { - continue; - } - if (originDistanceSq < 0) - { - continue; - } - Vector3 shieldPosition2D = new Vector3(shield.Position.x, 0, shield.Position.z); + Quaternion targetAngle = projectile.ExactRotation; Quaternion shieldProjAng = Quaternion.LookRotation(exactPosition - shieldPosition2D); if ((Quaternion.Angle(targetAngle, shieldProjAng) > 90)) From 469a6ed1701b295541fd5c30f952e2be757b9632 Mon Sep 17 00:00:00 2001 From: Logan Perkins Date: Sat, 30 Sep 2023 00:02:50 -0700 Subject: [PATCH 039/158] Use InterceptProjectile to handle marking the projectile landed and set its position --- Source/CombatExtended/Compatibility/EDShields.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Source/CombatExtended/Compatibility/EDShields.cs b/Source/CombatExtended/Compatibility/EDShields.cs index 560de23a19..0d99eceac1 100644 --- a/Source/CombatExtended/Compatibility/EDShields.cs +++ b/Source/CombatExtended/Compatibility/EDShields.cs @@ -83,17 +83,15 @@ public static bool CheckForCollisionBetweenCallback(ProjectileCE projectile, Vec Quaternion shieldProjAng = Quaternion.LookRotation(exactPosition - shieldPosition2D); if ((Quaternion.Angle(targetAngle, shieldProjAng) > 90)) { - HitSoundDef.PlayOneShot((SoundInfo)new TargetInfo(shield.Position, map, false)); - int damage = (projectile.def.projectile.GetDamageAmount(launcher)); generator.FieldIntegrity_Current -= damage; - exactPosition = BlockerRegistry.GetExactPosition(origin.ToVector3(), projectile.ExactPosition, shield.Position.ToVector3(), (fieldRadius - 1) * (fieldRadius - 1)); + exactPosition = BlockerRegistry.GetExactPosition(from, to, shieldPosition2D, (fieldRadius - 1) * (fieldRadius - 1)); FleckMakerCE.ThrowLightningGlow(exactPosition, map, 0.5f); - projectile.ExactPosition = exactPosition; + projectile.InterceptProjectile(shield, exactPosition, false); return true; } } From e60ab8bca4b299d8516670cbbdcc0e77ff9f971a Mon Sep 17 00:00:00 2001 From: Logan Perkins Date: Sat, 30 Sep 2023 00:19:26 -0700 Subject: [PATCH 040/158] Also set ticksToImpact to 0 --- Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs index 9f76253d3f..e268527337 100644 --- a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs @@ -648,6 +648,7 @@ public virtual void InterceptProjectile(object interceptor, Vector3 impactPositi { ExactPosition = impactPosition; landed = true; + ticksToImpact = 0; if (destroyCompletely) { this.Destroy(DestroyMode.Vanish); From 2c74ae3160d27f9708f76cc938e67fed964a3d0e Mon Sep 17 00:00:00 2001 From: Logan Perkins Date: Sat, 30 Sep 2023 00:19:46 -0700 Subject: [PATCH 041/158] Require the users of the blocker registry to call the projectile cleanup code --- .../CombatExtended/Projectiles/ProjectileCE.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs index e268527337..9189b9bd3b 100644 --- a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs @@ -831,11 +831,7 @@ private bool CheckCellForCollision(IntVec3 cell) { if (BlockerRegistry.CheckCellForCollisionCallback(this, cell, launcher)) { - this.ticksToImpact = 0; - this.landed = true; - - this.Impact(null); - return true; + return true; } var roofChecked = false; @@ -1197,7 +1193,6 @@ public void ImpactSomething() { if (BlockerRegistry.ImpactSomethingCallback(this, launcher)) { - this.Destroy(); return; } var pos = ExactPosition.ToIntVec3(); From f71fe7f847bd15f52b93a9f2304776534a193ebe Mon Sep 17 00:00:00 2001 From: Logan Perkins Date: Sat, 30 Sep 2023 00:20:22 -0700 Subject: [PATCH 042/158] Use new projectile cleanup code in BlockerRegistry users --- .../CombatExtended/Compatibility/EDShields.cs | 1 + .../Compatibility/Rimatomics.cs | 3 ++- .../VanillaFurnitureExpandedSecurity.cs | 21 ++++++++++++------- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Source/CombatExtended/Compatibility/EDShields.cs b/Source/CombatExtended/Compatibility/EDShields.cs index 0d99eceac1..a58810beb1 100644 --- a/Source/CombatExtended/Compatibility/EDShields.cs +++ b/Source/CombatExtended/Compatibility/EDShields.cs @@ -132,6 +132,7 @@ public static bool ImpactSomethingCallback(ProjectileCE projectile, Thing launch FleckMakerCE.ThrowLightningGlow(destination, map, 0.5f); int damage = (projectile.def.projectile.GetDamageAmount(launcher)); generator.FieldIntegrity_Current -= damage; + projectile.InterceptProjectile(shield, projectile.ExactPosition, true); return true; } return false; diff --git a/Source/CombatExtended/Compatibility/Rimatomics.cs b/Source/CombatExtended/Compatibility/Rimatomics.cs index 28495c9d88..d49b887e33 100644 --- a/Source/CombatExtended/Compatibility/Rimatomics.cs +++ b/Source/CombatExtended/Compatibility/Rimatomics.cs @@ -103,7 +103,7 @@ public static bool CheckForCollisionCallback(ProjectileCE projectile, IntVec3 ce } - + projectile.InterceptProjectile(shield, exactPosition, true); return true; } @@ -148,6 +148,7 @@ public static bool ImpactSomethingCallback(ProjectileCE projectile, Thing launch DamageInfo dinfo = new DamageInfo(projectile.def.projectile.damageDef, (float)damage, 0f, -1f, null, null, null, 0, null, true, true); shield.BreakShield(dinfo); } + projectile.InterceptProjectile(shield, projectile.ExactPosition, true); return true; } return false; diff --git a/Source/CombatExtended/Compatibility/VanillaFurnitureExpandedSecurity.cs b/Source/CombatExtended/Compatibility/VanillaFurnitureExpandedSecurity.cs index 700f104c40..c81f89d516 100644 --- a/Source/CombatExtended/Compatibility/VanillaFurnitureExpandedSecurity.cs +++ b/Source/CombatExtended/Compatibility/VanillaFurnitureExpandedSecurity.cs @@ -70,15 +70,16 @@ private static bool CheckCollision(ProjectileCE projectile, IntVec3 cell, Thing continue; } - projectile.ExactPosition = BlockerRegistry.GetExactPosition(projectile.OriginIV3.ToVector3(), - exactPosition, - new Vector3(shield.Position.x, 0, shield.Position.z), - shield.ShieldRadius * shield.ShieldRadius); + exactPosition = BlockerRegistry.GetExactPosition(projectile.OriginIV3.ToVector3(), + exactPosition, + new Vector3(shield.Position.x, 0, shield.Position.z), + shield.ShieldRadius * shield.ShieldRadius); if (!(projectile is ProjectileCE_Explosive)) { shield.AbsorbDamage(projectile.def.projectile.GetDamageAmount(launcher), projectile.def.projectile.damageDef, projectile.ExactRotation.eulerAngles.y); } + projectile.InterceptProjectile(shield, exactPosition, true); return true; } @@ -92,9 +93,15 @@ private static bool ImpactSomething(ProjectileCE projectile, Thing launcher) refreshShields(map); - var blocked = shields.Any(building => ShieldInterceptsProjectile(building as Building_Shield, projectile, launcher)); - CELogger.Message($"Blocked {projectile}? -- {blocked}"); - return blocked; + foreach (var building in shields) + { + if (building is Building_Shield bs && ShieldInterceptsProjectile(bs, projectile, launcher)) + { + projectile.InterceptProjectile(bs, exactPosition, true); + return true; + } + } + return false; } private static bool ShieldInterceptsProjectile(Building building, ProjectileCE projectile, Thing launcher) From 80394de4048dd13056655584437aa8fc4b853842 Mon Sep 17 00:00:00 2001 From: Logan Perkins Date: Sun, 1 Oct 2023 14:11:49 -0700 Subject: [PATCH 043/158] Don't duplicate sqrt calls --- .../CombatExtended/Compatibility/VanillaPsycastExpanded.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs b/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs index 70d8adfec6..2712dd1161 100644 --- a/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs +++ b/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs @@ -171,8 +171,9 @@ public static Vector3[] IntersectionPoint(Vector3 p1, Vector3 p2, Vector3 center // line does not intersect return new Vector3[] { Vector3.zero, Vector3.zero }; } - mu1 = (-b + Mathf.Sqrt(bb4ac)) / (2 * a); - mu2 = (-b - Mathf.Sqrt(bb4ac)) / (2 * a); + let sqrtbb4ac = Mathf.Sqrt(bb4ac) + mu1 = (-b + sqrtbb4ac) / (2 * a); + mu2 = (-b - sqrtbb4ac) / (2 * a); sect = new Vector3[2]; sect[0] = new Vector3(p1.x + mu1 * (p2.x - p1.x), 0, p1.z + mu1 * (p2.z - p1.z)); sect[1] = new Vector3(p1.x + mu2 * (p2.x - p1.x), 0, p1.z + mu2 * (p2.z - p1.z)); From 9ee1354690e45235e328e8a9f5eb9d9387ac79b7 Mon Sep 17 00:00:00 2001 From: Logan Perkins Date: Sun, 1 Oct 2023 14:12:01 -0700 Subject: [PATCH 044/158] Use new InterceptProjectile call --- Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs b/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs index 2712dd1161..ed48a3ba18 100644 --- a/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs +++ b/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs @@ -48,6 +48,7 @@ private static bool ImpactSomething(ProjectileCE projectile, Thing launcher) Effecter eff = new Effecter(EffecterDefOf.Interceptor_BlockedProjectile); eff.Trigger(new TargetInfo(projectile.ExactPosition.ToIntVec3(), interceptor.Map, false), TargetInfo.Invalid); eff.Cleanup(); + projectile.InterceptProjectile(interceptor, projectile.ExactPosition, true); return true; } else @@ -80,6 +81,7 @@ public static bool CheckIntercept(ProjectileCE projectile, IntVec3 cell, Thing l Effecter e = new Effecter(EffecterDefOf.Interceptor_BlockedProjectile); e.Trigger(new TargetInfo(newExactPos.ToIntVec3(), interceptor.pawn.Map, false), TargetInfo.Invalid); e.Cleanup(); + projectile.InterceptProjectile(interceptor, projectile.ExactPosition, true); } return result; @@ -122,6 +124,7 @@ public static bool CheckIntercept(ProjectileCE projectile, IntVec3 cell, Thing l Effecter eff = new Effecter(EffecterDefOf.Interceptor_BlockedProjectile); eff.Trigger(new TargetInfo(newExactPos.ToIntVec3(), interceptor.pawn.Map, false), TargetInfo.Invalid); eff.Cleanup(); + projectile.InterceptProjectile(interceptor, projectile.ExactPosition, true); return true; } From 5f4b626b98e7104e22fe31917c2a020b3db668fe Mon Sep 17 00:00:00 2001 From: Logan Perkins Date: Sun, 1 Oct 2023 14:12:17 -0700 Subject: [PATCH 045/158] Handle the simple cases quickly, then use dot products to find high speed intercepts --- .../CombatExtended/CE_Utility.cs | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/CE_Utility.cs b/Source/CombatExtended/CombatExtended/CE_Utility.cs index 911ddcb1a5..6b87d08e30 100644 --- a/Source/CombatExtended/CombatExtended/CE_Utility.cs +++ b/Source/CombatExtended/CombatExtended/CE_Utility.cs @@ -893,14 +893,45 @@ public static float GetCollisionWidth(Thing thing) /// True if the line segment intercepts the circle public static bool IntersectLineSphericalOutline(Vector3 center, float radius, Vector3 pointA, Vector3 pointB) { - Vector3 displacement = pointB - pointA; - double a = displacement.sqrMagnitude; - double b = 2 * (displacement.x * (pointA.x - center.x) + displacement.y * (pointA.y - center.y) + displacement.z * (pointA.z - center.z)); - double c = (center - pointA).sqrMagnitude - radius * radius; - double det = b * b - 4 * a * c; - return det >= 0; -// var radSq = radius * radius; -// return ((center - pointA).sqrMagnitude > radSq) != ((center - pointB).sqrMagnitude > radSq); + var radSq = radius * radius; + var aInside = ((center - pointA).sqrMagnitude <= radSq); + var bInside = ((center - pointB).sqrMagnitude <= radSq); + if (aInside != bInside) // One end is inside, one is outside -- we crossed + { + return true; + } + if (aInside && bInside) // Both are inside, we did not cross + { + return false; + } + + // Both are outside, so check if the point on the line-segment AB closest to point C is inside. + + Vector3 direction = pointB - pointA; + Vector3 displacement = pointA - center; + + // Calculate the dot product between the relative position at the start and the direction of travel. + float dotProduct = displacement.x * direction.x + displacement.y * direction.y + displacement.z * direction.z; + + if (dotProduct < 0) // Moving *away* from the shield + { + return false; + } + + if (dotProduct > direction.sqrMagnitude) // Still moving closer, might hit next tick but it hasn't arrived. We don't need to check if the end point is inside, because we already did that. + { + return false; + } + + // The center lies some distance perpindicular to the line segment AB + // Rotate the direction vector 90 degrees, and find its dot product with the relative displacement to find that distance. + dotProduct = displacement.x * direction.z - displacement.z * direction.x + displacement.y * direction.y; + + if (dotProduct * dotProduct <= direction.sqrMagnitude * displacement.sqrMagnitude) + { + return true; + } + return false; } /// From 2dd4c69d8a678d818553a36460230b6c5b37bce6 Mon Sep 17 00:00:00 2001 From: MaxDorob Date: Tue, 3 Oct 2023 20:40:59 +0600 Subject: [PATCH 046/158] Build and style fix --- .../CombatExtended/CE_Utility.cs | 78 +++++++++---------- .../Projectiles/ProjectileCE.cs | 14 ++-- .../CombatExtended/Compatibility/EDShields.cs | 23 +++--- .../Compatibility/Rimatomics.cs | 4 +- .../VanillaFurnitureExpandedSecurity.cs | 28 +++---- .../Compatibility/VanillaPsycastExpanded.cs | 8 +- 6 files changed, 78 insertions(+), 77 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/CE_Utility.cs b/Source/CombatExtended/CombatExtended/CE_Utility.cs index 6b87d08e30..77cb2a8a16 100644 --- a/Source/CombatExtended/CombatExtended/CE_Utility.cs +++ b/Source/CombatExtended/CombatExtended/CE_Utility.cs @@ -893,45 +893,45 @@ public static float GetCollisionWidth(Thing thing) /// True if the line segment intercepts the circle public static bool IntersectLineSphericalOutline(Vector3 center, float radius, Vector3 pointA, Vector3 pointB) { - var radSq = radius * radius; - var aInside = ((center - pointA).sqrMagnitude <= radSq); - var bInside = ((center - pointB).sqrMagnitude <= radSq); - if (aInside != bInside) // One end is inside, one is outside -- we crossed - { - return true; - } - if (aInside && bInside) // Both are inside, we did not cross - { - return false; - } - - // Both are outside, so check if the point on the line-segment AB closest to point C is inside. - - Vector3 direction = pointB - pointA; - Vector3 displacement = pointA - center; - - // Calculate the dot product between the relative position at the start and the direction of travel. - float dotProduct = displacement.x * direction.x + displacement.y * direction.y + displacement.z * direction.z; - - if (dotProduct < 0) // Moving *away* from the shield - { - return false; - } - - if (dotProduct > direction.sqrMagnitude) // Still moving closer, might hit next tick but it hasn't arrived. We don't need to check if the end point is inside, because we already did that. - { - return false; - } - - // The center lies some distance perpindicular to the line segment AB - // Rotate the direction vector 90 degrees, and find its dot product with the relative displacement to find that distance. - dotProduct = displacement.x * direction.z - displacement.z * direction.x + displacement.y * direction.y; - - if (dotProduct * dotProduct <= direction.sqrMagnitude * displacement.sqrMagnitude) - { - return true; - } - return false; + var radSq = radius * radius; + var aInside = ((center - pointA).sqrMagnitude <= radSq); + var bInside = ((center - pointB).sqrMagnitude <= radSq); + if (aInside != bInside) // One end is inside, one is outside -- we crossed + { + return true; + } + if (aInside && bInside) // Both are inside, we did not cross + { + return false; + } + + // Both are outside, so check if the point on the line-segment AB closest to point C is inside. + + Vector3 direction = pointB - pointA; + Vector3 displacement = pointA - center; + + // Calculate the dot product between the relative position at the start and the direction of travel. + float dotProduct = displacement.x * direction.x + displacement.y * direction.y + displacement.z * direction.z; + + if (dotProduct < 0) // Moving *away* from the shield + { + return false; + } + + if (dotProduct > direction.sqrMagnitude) // Still moving closer, might hit next tick but it hasn't arrived. We don't need to check if the end point is inside, because we already did that. + { + return false; + } + + // The center lies some distance perpindicular to the line segment AB + // Rotate the direction vector 90 degrees, and find its dot product with the relative displacement to find that distance. + dotProduct = displacement.x * direction.z - displacement.z * direction.x + displacement.y * direction.y; + + if (dotProduct * dotProduct <= direction.sqrMagnitude * displacement.sqrMagnitude) + { + return true; + } + return false; } /// diff --git a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs index 9189b9bd3b..649cf8588b 100644 --- a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs @@ -646,9 +646,9 @@ public virtual void Launch(Thing launcher, Vector2 origin, Thing equipment = nul #region Collisions public virtual void InterceptProjectile(object interceptor, Vector3 impactPosition, bool destroyCompletely = false) { - ExactPosition = impactPosition; - landed = true; - ticksToImpact = 0; + ExactPosition = impactPosition; + landed = true; + ticksToImpact = 0; if (destroyCompletely) { this.Destroy(DestroyMode.Vanish); @@ -660,15 +660,15 @@ public virtual void InterceptProjectile(object interceptor, Vector3 impactPositi } public virtual void InterceptProjectile(object interceptor, Vector3 shieldPosition, float shieldRadius, bool destroyCompletely = false) { - InterceptProjectile(interceptor, BlockerRegistry.GetExactPosition(OriginIV3.ToVector3(), ExactPosition, shieldPosition, shieldRadius * shieldRadius)); - } + InterceptProjectile(interceptor, BlockerRegistry.GetExactPosition(OriginIV3.ToVector3(), ExactPosition, shieldPosition, shieldRadius * shieldRadius)); + } private bool CheckIntercept(Thing interceptorThing, CompProjectileInterceptor interceptorComp, bool withDebug = false) { Vector3 shieldPosition = interceptorThing.Position.ToVector3ShiftedWithAltitude(0.5f); float radius = interceptorComp.Props.radius; float blockRadius = radius + def.projectile.SpeedTilesPerTick + 0.1f; - float radiusSq = blockRadius * blockRadius; + float radiusSq = blockRadius * blockRadius; var newExactPos = ExactPosition; @@ -831,7 +831,7 @@ private bool CheckCellForCollision(IntVec3 cell) { if (BlockerRegistry.CheckCellForCollisionCallback(this, cell, launcher)) { - return true; + return true; } var roofChecked = false; diff --git a/Source/CombatExtended/Compatibility/EDShields.cs b/Source/CombatExtended/Compatibility/EDShields.cs index a58810beb1..6bd75d1e76 100644 --- a/Source/CombatExtended/Compatibility/EDShields.cs +++ b/Source/CombatExtended/Compatibility/EDShields.cs @@ -46,10 +46,11 @@ public static bool CheckForCollisionBetweenCallback(ProjectileCE projectile, Vec { /* Check if an active shield can block this projectile */ - if (projectile.def.projectile.flyOverhead) { - return false; - } - Thing launcher = projectile.launcher; + if (projectile.def.projectile.flyOverhead) + { + return false; + } + Thing launcher = projectile.launcher; Map map = projectile.Map; Vector3 exactPosition = projectile.ExactPosition; IntVec3 origin = projectile.OriginIV3; @@ -70,12 +71,12 @@ public static bool CheckForCollisionBetweenCallback(ProjectileCE projectile, Vec continue; } int fieldRadius = (int)generator.FieldRadius_Active(); - Vector3 shieldPosition2D = new Vector3(shield.Position.x, 0, shield.Position.z); + Vector3 shieldPosition2D = new Vector3(shield.Position.x, 0, shield.Position.z); - if (!CE_Utility.IntersectLineSphericalOutline(shieldPosition2D, fieldRadius, from, to)) - { - continue; - } + if (!CE_Utility.IntersectLineSphericalOutline(shieldPosition2D, fieldRadius, from, to)) + { + continue; + } int fieldRadiusSq = fieldRadius * fieldRadius; @@ -91,7 +92,7 @@ public static bool CheckForCollisionBetweenCallback(ProjectileCE projectile, Vec exactPosition = BlockerRegistry.GetExactPosition(from, to, shieldPosition2D, (fieldRadius - 1) * (fieldRadius - 1)); FleckMakerCE.ThrowLightningGlow(exactPosition, map, 0.5f); - projectile.InterceptProjectile(shield, exactPosition, false); + projectile.InterceptProjectile(shield, exactPosition, false); return true; } } @@ -132,7 +133,7 @@ public static bool ImpactSomethingCallback(ProjectileCE projectile, Thing launch FleckMakerCE.ThrowLightningGlow(destination, map, 0.5f); int damage = (projectile.def.projectile.GetDamageAmount(launcher)); generator.FieldIntegrity_Current -= damage; - projectile.InterceptProjectile(shield, projectile.ExactPosition, true); + projectile.InterceptProjectile(shield, projectile.ExactPosition, true); return true; } return false; diff --git a/Source/CombatExtended/Compatibility/Rimatomics.cs b/Source/CombatExtended/Compatibility/Rimatomics.cs index d49b887e33..2a9bc0d838 100644 --- a/Source/CombatExtended/Compatibility/Rimatomics.cs +++ b/Source/CombatExtended/Compatibility/Rimatomics.cs @@ -103,7 +103,7 @@ public static bool CheckForCollisionCallback(ProjectileCE projectile, IntVec3 ce } - projectile.InterceptProjectile(shield, exactPosition, true); + projectile.InterceptProjectile(shield, exactPosition, true); return true; } @@ -148,7 +148,7 @@ public static bool ImpactSomethingCallback(ProjectileCE projectile, Thing launch DamageInfo dinfo = new DamageInfo(projectile.def.projectile.damageDef, (float)damage, 0f, -1f, null, null, null, 0, null, true, true); shield.BreakShield(dinfo); } - projectile.InterceptProjectile(shield, projectile.ExactPosition, true); + projectile.InterceptProjectile(shield, projectile.ExactPosition, true); return true; } return false; diff --git a/Source/CombatExtended/Compatibility/VanillaFurnitureExpandedSecurity.cs b/Source/CombatExtended/Compatibility/VanillaFurnitureExpandedSecurity.cs index c81f89d516..3852571d47 100644 --- a/Source/CombatExtended/Compatibility/VanillaFurnitureExpandedSecurity.cs +++ b/Source/CombatExtended/Compatibility/VanillaFurnitureExpandedSecurity.cs @@ -70,16 +70,16 @@ private static bool CheckCollision(ProjectileCE projectile, IntVec3 cell, Thing continue; } - exactPosition = BlockerRegistry.GetExactPosition(projectile.OriginIV3.ToVector3(), - exactPosition, - new Vector3(shield.Position.x, 0, shield.Position.z), - shield.ShieldRadius * shield.ShieldRadius); + exactPosition = BlockerRegistry.GetExactPosition(projectile.OriginIV3.ToVector3(), + exactPosition, + new Vector3(shield.Position.x, 0, shield.Position.z), + shield.ShieldRadius * shield.ShieldRadius); if (!(projectile is ProjectileCE_Explosive)) { shield.AbsorbDamage(projectile.def.projectile.GetDamageAmount(launcher), projectile.def.projectile.damageDef, projectile.ExactRotation.eulerAngles.y); } - projectile.InterceptProjectile(shield, exactPosition, true); + projectile.InterceptProjectile(shield, exactPosition, true); return true; } @@ -93,15 +93,15 @@ private static bool ImpactSomething(ProjectileCE projectile, Thing launcher) refreshShields(map); - foreach (var building in shields) - { - if (building is Building_Shield bs && ShieldInterceptsProjectile(bs, projectile, launcher)) - { - projectile.InterceptProjectile(bs, exactPosition, true); - return true; - } - } - return false; + foreach (var building in shields) + { + if (building is Building_Shield bs && ShieldInterceptsProjectile(bs, projectile, launcher)) + { + projectile.InterceptProjectile(bs, exactPosition, true); + return true; + } + } + return false; } private static bool ShieldInterceptsProjectile(Building building, ProjectileCE projectile, Thing launcher) diff --git a/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs b/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs index ed48a3ba18..b7c2ad1920 100644 --- a/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs +++ b/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs @@ -48,7 +48,7 @@ private static bool ImpactSomething(ProjectileCE projectile, Thing launcher) Effecter eff = new Effecter(EffecterDefOf.Interceptor_BlockedProjectile); eff.Trigger(new TargetInfo(projectile.ExactPosition.ToIntVec3(), interceptor.Map, false), TargetInfo.Invalid); eff.Cleanup(); - projectile.InterceptProjectile(interceptor, projectile.ExactPosition, true); + projectile.InterceptProjectile(interceptor, projectile.ExactPosition, true); return true; } else @@ -81,7 +81,7 @@ public static bool CheckIntercept(ProjectileCE projectile, IntVec3 cell, Thing l Effecter e = new Effecter(EffecterDefOf.Interceptor_BlockedProjectile); e.Trigger(new TargetInfo(newExactPos.ToIntVec3(), interceptor.pawn.Map, false), TargetInfo.Invalid); e.Cleanup(); - projectile.InterceptProjectile(interceptor, projectile.ExactPosition, true); + projectile.InterceptProjectile(interceptor, projectile.ExactPosition, true); } return result; @@ -124,7 +124,7 @@ public static bool CheckIntercept(ProjectileCE projectile, IntVec3 cell, Thing l Effecter eff = new Effecter(EffecterDefOf.Interceptor_BlockedProjectile); eff.Trigger(new TargetInfo(newExactPos.ToIntVec3(), interceptor.pawn.Map, false), TargetInfo.Invalid); eff.Cleanup(); - projectile.InterceptProjectile(interceptor, projectile.ExactPosition, true); + projectile.InterceptProjectile(interceptor, projectile.ExactPosition, true); return true; } @@ -174,7 +174,7 @@ public static Vector3[] IntersectionPoint(Vector3 p1, Vector3 p2, Vector3 center // line does not intersect return new Vector3[] { Vector3.zero, Vector3.zero }; } - let sqrtbb4ac = Mathf.Sqrt(bb4ac) + var sqrtbb4ac = Mathf.Sqrt(bb4ac); mu1 = (-b + sqrtbb4ac) / (2 * a); mu2 = (-b - sqrtbb4ac) / (2 * a); sect = new Vector3[2]; From 88305ba219b29eb46687476f8382bf540e849207 Mon Sep 17 00:00:00 2001 From: MaxDorob Date: Tue, 3 Oct 2023 21:15:34 +0600 Subject: [PATCH 047/158] Moved IntersectionPoint to CE_Utility Made LastPos getter public --- .../CombatExtended/CE_Utility.cs | 42 ++++++++++++++++++ .../Projectiles/ProjectileCE.cs | 4 +- .../Compatibility/VanillaExpandedFramework.cs | 5 +-- .../Compatibility/VanillaPsycastExpanded.cs | 43 ++----------------- 4 files changed, 50 insertions(+), 44 deletions(-) diff --git a/Source/CombatExtended/CombatExtended/CE_Utility.cs b/Source/CombatExtended/CombatExtended/CE_Utility.cs index 77cb2a8a16..113d18393e 100644 --- a/Source/CombatExtended/CombatExtended/CE_Utility.cs +++ b/Source/CombatExtended/CombatExtended/CE_Utility.cs @@ -934,6 +934,48 @@ public static bool IntersectLineSphericalOutline(Vector3 center, float radius, V return false; } + /// + /// Calculates whether a line segment intercepts a radius circle. Used to get intersection points. + /// + /// The first point of the line segment + /// The second point of the line segment + /// The center of the circle + /// The radius of the circle + /// Vector3[] { Vector3.zero, Vector3.zero } if there's no intersection, othrewise returns two intersection points + public static Vector3[] IntersectionPoint(Vector3 p1, Vector3 p2, Vector3 center, float radius) + { + Vector3 dp = new Vector3(); + Vector3[] sect; + float a, b, c; + float bb4ac; + float mu1; + float mu2; + + // get the distance between X and Z on the segment + dp.x = p2.x - p1.x; + dp.z = p2.z - p1.z; + // I don't get the math here + a = dp.x * dp.x + dp.z * dp.z; + b = 2 * (dp.x * (p1.x - center.x) + dp.z * (p1.z - center.z)); + c = center.x * center.x + center.z * center.z; + c += p1.x * p1.x + p1.z * p1.z; + c -= 2 * (center.x * p1.x + center.z * p1.z); + c -= radius * radius; + bb4ac = b * b - 4 * a * c; + if (Mathf.Abs(a) < float.Epsilon || bb4ac < 0) + { + // line does not intersect + return new Vector3[] { Vector3.zero, Vector3.zero }; + } + var sqrtbb4ac = Mathf.Sqrt(bb4ac); + mu1 = (-b + sqrtbb4ac) / (2 * a); + mu2 = (-b - sqrtbb4ac) / (2 * a); + sect = new Vector3[2]; + sect[0] = new Vector3(p1.x + mu1 * (p2.x - p1.x), 0, p1.z + mu1 * (p2.z - p1.z)); + sect[1] = new Vector3(p1.x + mu2 * (p2.x - p1.x), 0, p1.z + mu2 * (p2.z - p1.z)); + + return sect; + } /// /// Calculates body scale factors based on body type /// diff --git a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs index 649cf8588b..9a3ed83f7d 100644 --- a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs @@ -271,9 +271,9 @@ public override Vector3 DrawPos } private Vector3 lastExactPos = new Vector3(-1000, 0, 0); - private Vector3 LastPos + public Vector3 LastPos { - set + private set { lastExactPos = value; } diff --git a/Source/CombatExtended/Compatibility/VanillaExpandedFramework.cs b/Source/CombatExtended/Compatibility/VanillaExpandedFramework.cs index 66d8f7b6cb..193cc8c9f3 100644 --- a/Source/CombatExtended/Compatibility/VanillaExpandedFramework.cs +++ b/Source/CombatExtended/Compatibility/VanillaExpandedFramework.cs @@ -39,7 +39,7 @@ private static bool CheckIntercept(ProjectileCE projectile, IntVec3 cell, Thing return false; } var def = projectile.def; - Vector3 lastExactPos = (Vector3)new Traverse(projectile).Field("lastExactPos").GetValue();//Why this field(or linked property) is private? + Vector3 lastExactPos = projectile.LastPos; var newExactPos = projectile.ExactPosition; foreach (var interceptor in interceptors) { @@ -77,8 +77,7 @@ private static bool CheckIntercept(ProjectileCE projectile, IntVec3 cell, Thing { continue; } - var intersectionPoints = //BlockerRegistry.GetExactPosition(projectile.OriginIV3.ToVector3(), projectile.ExactPosition, interceptorThing.Position.ToVector3(), (radius ) * (radius)); - VanillaPsycastExpanded.IntersectionPoint(projectile.OriginIV3.ToVector3(), projectile.ExactPosition, interceptor.parent.Position.ToVector3(), (radius)); + var intersectionPoints = CE_Utility.IntersectionPoint(projectile.OriginIV3.ToVector3(), projectile.ExactPosition, interceptor.parent.Position.ToVector3(), (radius)); if (intersectionPoints == new Vector3[] { Vector3.zero, Vector3.zero }) { continue; diff --git a/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs b/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs index b7c2ad1920..2d6e0bd46f 100644 --- a/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs +++ b/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs @@ -38,7 +38,7 @@ private static bool ImpactSomething(ProjectileCE projectile, Thing launcher) if (interceptor != null) { var hediff = interceptor.health.hediffSet.hediffs.FirstOrDefault(x => x is Hediff_Overshield) as Hediff_Overshield; - projectile.ExactPosition = IntersectionPoint(projectile.OriginIV3.ToVector3(), projectile.ExactPosition, interceptor.DrawPos, hediff.OverlaySize).OrderBy(x => (projectile.OriginIV3.ToVector3() - x).sqrMagnitude).First(); + projectile.ExactPosition = CE_Utility.IntersectionPoint(projectile.OriginIV3.ToVector3(), projectile.ExactPosition, interceptor.DrawPos, hediff.OverlaySize).OrderBy(x => (projectile.OriginIV3.ToVector3() - x).sqrMagnitude).First(); projectile.landed = true; new Traverse(interceptor).Field("lastInterceptAngle").SetValue(projectile.ExactPosition.AngleToFlat(interceptor.TrueCenter())); @@ -64,14 +64,14 @@ public static bool CheckIntercept(ProjectileCE projectile, IntVec3 cell, Thing l .Where(x => typeof(Hediff_Overshield).IsAssignableFrom(x.def.hediffClass)).Cast()) { var def = projectile.def; - Vector3 lastExactPos = (Vector3)new Traverse(projectile).Field("lastExactPos").GetValue();//Why this field(or linked property) is private? + Vector3 lastExactPos = projectile.LastPos; var newExactPos = projectile.ExactPosition; if (interceptor.GetType() == typeof(Hediff_Overshield)) { var result = interceptor.pawn != launcher && (interceptor.pawn.Position == cell || PreventTryColideWithPawn(projectile, interceptor.pawn, newExactPos)); if (result) { - projectile.ExactPosition = IntersectionPoint(projectile.OriginIV3.ToVector3(), projectile.ExactPosition, interceptor.pawn.DrawPos, interceptor.OverlaySize).OrderBy(x => (projectile.OriginIV3.ToVector3() - x).sqrMagnitude).First(); + projectile.ExactPosition = CE_Utility.IntersectionPoint(projectile.OriginIV3.ToVector3(), projectile.ExactPosition, interceptor.pawn.DrawPos, interceptor.OverlaySize).OrderBy(x => (projectile.OriginIV3.ToVector3() - x).sqrMagnitude).First(); projectile.landed = true; new Traverse(interceptor).Field("lastInterceptAngle").SetValue(newExactPos.AngleToFlat(interceptor.pawn.TrueCenter())); @@ -112,8 +112,7 @@ public static bool CheckIntercept(ProjectileCE projectile, IntVec3 cell, Thing l { return false; } - var exactPosition = //BlockerRegistry.GetExactPosition(projectile.OriginIV3.ToVector3(), projectile.ExactPosition, interceptorThing.Position.ToVector3(), (radius ) * (radius)); - IntersectionPoint(projectile.OriginIV3.ToVector3(), projectile.ExactPosition, interceptor.pawn.Position.ToVector3(), (radius)).OrderBy(x => (projectile.OriginIV3.ToVector3() - x).sqrMagnitude).First(); + var exactPosition = CE_Utility.IntersectionPoint(projectile.OriginIV3.ToVector3(), projectile.ExactPosition, interceptor.pawn.Position.ToVector3(), (radius)).OrderBy(x => (projectile.OriginIV3.ToVector3() - x).sqrMagnitude).First(); projectile.ExactPosition = exactPosition; projectile.landed = true; @@ -149,40 +148,6 @@ private static bool PreventTryColideWithPawn(ProjectileCE projectile, Thing pawn return true; } - public static Vector3[] IntersectionPoint(Vector3 p1, Vector3 p2, Vector3 center, float radius) - { - Vector3 dp = new Vector3(); - Vector3[] sect; - float a, b, c; - float bb4ac; - float mu1; - float mu2; - - // get the distance between X and Z on the segment - dp.x = p2.x - p1.x; - dp.z = p2.z - p1.z; - // I don't get the math here - a = dp.x * dp.x + dp.z * dp.z; - b = 2 * (dp.x * (p1.x - center.x) + dp.z * (p1.z - center.z)); - c = center.x * center.x + center.z * center.z; - c += p1.x * p1.x + p1.z * p1.z; - c -= 2 * (center.x * p1.x + center.z * p1.z); - c -= radius * radius; - bb4ac = b * b - 4 * a * c; - if (Mathf.Abs(a) < float.Epsilon || bb4ac < 0) - { - // line does not intersect - return new Vector3[] { Vector3.zero, Vector3.zero }; - } - var sqrtbb4ac = Mathf.Sqrt(bb4ac); - mu1 = (-b + sqrtbb4ac) / (2 * a); - mu2 = (-b - sqrtbb4ac) / (2 * a); - sect = new Vector3[2]; - sect[0] = new Vector3(p1.x + mu1 * (p2.x - p1.x), 0, p1.z + mu1 * (p2.z - p1.z)); - sect[1] = new Vector3(p1.x + mu2 * (p2.x - p1.x), 0, p1.z + mu2 * (p2.z - p1.z)); - - return sect; - } /// /// Copy of ProjectileCE method /// From c3bd3ed414d4146a0b131f93bc3aad56eb6bc1d9 Mon Sep 17 00:00:00 2001 From: MaxDorob Date: Tue, 3 Oct 2023 23:19:36 +0600 Subject: [PATCH 048/158] Removed local IntersectLineSphericalOutline merthod Splited AOE interception check --- .../Compatibility/VanillaExpandedFramework.cs | 21 +--- .../Compatibility/VanillaPsycastExpanded.cs | 101 ++++++------------ 2 files changed, 36 insertions(+), 86 deletions(-) diff --git a/Source/CombatExtended/Compatibility/VanillaExpandedFramework.cs b/Source/CombatExtended/Compatibility/VanillaExpandedFramework.cs index 193cc8c9f3..7437a173c6 100644 --- a/Source/CombatExtended/Compatibility/VanillaExpandedFramework.cs +++ b/Source/CombatExtended/Compatibility/VanillaExpandedFramework.cs @@ -73,7 +73,7 @@ private static bool CheckIntercept(ProjectileCE projectile, IntVec3 cell, Thing { continue; } - if (!IntersectLineSphericalOutline(shieldPosition, radius, lastExactPos, newExactPos)) + if (!CE_Utility.IntersectLineSphericalOutline(shieldPosition, radius, lastExactPos, newExactPos)) { continue; } @@ -89,25 +89,6 @@ private static bool CheckIntercept(ProjectileCE projectile, IntVec3 cell, Thing } return false; } - /// - /// Copy of ProjectileCE method - /// - private static bool IntersectLineSphericalOutline(Vector3 center, float radius, Vector3 pointA, Vector3 pointB) - { - var pointAInShield = (center - pointA).sqrMagnitude <= Mathf.Pow(radius, 2); - var pointBInShield = (center - pointB).sqrMagnitude <= Mathf.Pow(radius, 2); - - if (pointAInShield && pointBInShield) - { - return false; - } - if (!pointAInShield && !pointBInShield) - { - return false; - } - - return true; - } } diff --git a/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs b/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs index 2d6e0bd46f..eb76d65d98 100644 --- a/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs +++ b/Source/CombatExtended/Compatibility/VanillaPsycastExpanded.cs @@ -27,7 +27,7 @@ public IEnumerable GetCompatList() public void Install() { BlockerRegistry.RegisterImpactSomethingCallback(ImpactSomething); - BlockerRegistry.RegisterCheckForCollisionCallback(CheckIntercept); + BlockerRegistry.RegisterCheckForCollisionBetweenCallback(AOE_CheckIntercept); } private static bool ImpactSomething(ProjectileCE projectile, Thing launcher) @@ -38,17 +38,7 @@ private static bool ImpactSomething(ProjectileCE projectile, Thing launcher) if (interceptor != null) { var hediff = interceptor.health.hediffSet.hediffs.FirstOrDefault(x => x is Hediff_Overshield) as Hediff_Overshield; - projectile.ExactPosition = CE_Utility.IntersectionPoint(projectile.OriginIV3.ToVector3(), projectile.ExactPosition, interceptor.DrawPos, hediff.OverlaySize).OrderBy(x => (projectile.OriginIV3.ToVector3() - x).sqrMagnitude).First(); - projectile.landed = true; - - new Traverse(interceptor).Field("lastInterceptAngle").SetValue(projectile.ExactPosition.AngleToFlat(interceptor.TrueCenter())); - new Traverse(interceptor).Field("lastInterceptTicks").SetValue(Find.TickManager.TicksGame); - new Traverse(interceptor).Field("drawInterceptCone").SetValue(true); - - Effecter eff = new Effecter(EffecterDefOf.Interceptor_BlockedProjectile); - eff.Trigger(new TargetInfo(projectile.ExactPosition.ToIntVec3(), interceptor.Map, false), TargetInfo.Invalid); - eff.Cleanup(); - projectile.InterceptProjectile(interceptor, projectile.ExactPosition, true); + OnIntercepted(hediff, projectile); return true; } else @@ -56,41 +46,36 @@ private static bool ImpactSomething(ProjectileCE projectile, Thing launcher) return false; } } - - public static bool CheckIntercept(ProjectileCE projectile, IntVec3 cell, Thing launcher) + public static bool Hediff_Overshield_InterceptCheck(ProjectileCE projectile, IntVec3 cell, Thing launcher) { - foreach (var interceptor in projectile.Map.listerThings.ThingsInGroup(ThingRequestGroup.Pawn).Cast() + foreach (var interceptor in projectile.Map.thingGrid.ThingsListAt(cell).OfType() .SelectMany(x => x.health.hediffSet.hediffs) - .Where(x => typeof(Hediff_Overshield).IsAssignableFrom(x.def.hediffClass)).Cast()) + .Where(x => x.GetType() == typeof(Hediff_Overshield)).Cast()) { + var def = projectile.def; Vector3 lastExactPos = projectile.LastPos; var newExactPos = projectile.ExactPosition; - if (interceptor.GetType() == typeof(Hediff_Overshield)) + var result = interceptor.pawn != launcher && (interceptor.pawn.Position == cell || PreventTryColideWithPawn(projectile, interceptor.pawn, newExactPos)); + if (result) { - var result = interceptor.pawn != launcher && (interceptor.pawn.Position == cell || PreventTryColideWithPawn(projectile, interceptor.pawn, newExactPos)); - if (result) - { - projectile.ExactPosition = CE_Utility.IntersectionPoint(projectile.OriginIV3.ToVector3(), projectile.ExactPosition, interceptor.pawn.DrawPos, interceptor.OverlaySize).OrderBy(x => (projectile.OriginIV3.ToVector3() - x).sqrMagnitude).First(); - projectile.landed = true; - - new Traverse(interceptor).Field("lastInterceptAngle").SetValue(newExactPos.AngleToFlat(interceptor.pawn.TrueCenter())); - new Traverse(interceptor).Field("lastInterceptTicks").SetValue(Find.TickManager.TicksGame); - new Traverse(interceptor).Field("drawInterceptCone").SetValue(true); - - Effecter e = new Effecter(EffecterDefOf.Interceptor_BlockedProjectile); - e.Trigger(new TargetInfo(newExactPos.ToIntVec3(), interceptor.pawn.Map, false), TargetInfo.Invalid); - e.Cleanup(); - projectile.InterceptProjectile(interceptor, projectile.ExactPosition, true); - } - + OnIntercepted(interceptor, projectile); return result; } - #region AOE shields + } + return false; + } + public static bool AOE_CheckIntercept(ProjectileCE projectile, Vector3 from, Vector3 newExactPos) + { + var def = projectile.def; + foreach (var interceptor in projectile.Map.listerThings.ThingsInGroup(ThingRequestGroup.Pawn).Cast() + .SelectMany(x => x.health.hediffSet.hediffs) + .Where(x => x is Hediff_Overshield && x.GetType() != typeof(Hediff_Overshield)).Cast()) + { Vector3 shieldPosition = interceptor.pawn.Position.ToVector3ShiftedWithAltitude(0.5f); float radius = interceptor.OverlaySize; float blockRadius = radius + def.projectile.SpeedTilesPerTick + 0.1f; - if ((lastExactPos - shieldPosition).sqrMagnitude < radius * radius) + if ((from - shieldPosition).sqrMagnitude < radius * radius) { return false; } @@ -104,26 +89,16 @@ public static bool CheckIntercept(ProjectileCE projectile, IntVec3 cell, Thing l return false; } - if ((shieldPosition - lastExactPos).sqrMagnitude <= Mathf.Pow((float)radius, 2)) + if ((shieldPosition - from).sqrMagnitude <= Mathf.Pow((float)radius, 2)) { return false; } - if (!IntersectLineSphericalOutline(shieldPosition, radius, lastExactPos, newExactPos)) + if (!CE_Utility.IntersectLineSphericalOutline(shieldPosition, radius, from, newExactPos)) { return false; } - var exactPosition = CE_Utility.IntersectionPoint(projectile.OriginIV3.ToVector3(), projectile.ExactPosition, interceptor.pawn.Position.ToVector3(), (radius)).OrderBy(x => (projectile.OriginIV3.ToVector3() - x).sqrMagnitude).First(); - projectile.ExactPosition = exactPosition; - projectile.landed = true; - - new Traverse(interceptor).Field("lastInterceptAngle").SetValue(newExactPos.AngleToFlat(interceptor.pawn.TrueCenter())); - new Traverse(interceptor).Field("lastInterceptTicks").SetValue(Find.TickManager.TicksGame); - new Traverse(interceptor).Field("drawInterceptCone").SetValue(true); - Effecter eff = new Effecter(EffecterDefOf.Interceptor_BlockedProjectile); - eff.Trigger(new TargetInfo(newExactPos.ToIntVec3(), interceptor.pawn.Map, false), TargetInfo.Invalid); - eff.Cleanup(); - projectile.InterceptProjectile(interceptor, projectile.ExactPosition, true); + OnIntercepted(interceptor, projectile); return true; } @@ -148,25 +123,19 @@ private static bool PreventTryColideWithPawn(ProjectileCE projectile, Thing pawn return true; } - /// - /// Copy of ProjectileCE method - /// - private static bool IntersectLineSphericalOutline(Vector3 center, float radius, Vector3 pointA, Vector3 pointB) + private static void OnIntercepted(Hediff_Overshield interceptor, ProjectileCE projectile) { - var pointAInShield = (center - pointA).sqrMagnitude <= Mathf.Pow(radius, 2); - var pointBInShield = (center - pointB).sqrMagnitude <= Mathf.Pow(radius, 2); - - if (pointAInShield && pointBInShield) - { - return false; - } - if (!pointAInShield && !pointBInShield) - { - return false; - } - - return true; + var newExactPos = projectile.ExactPosition; + var exactPosition = CE_Utility.IntersectionPoint(projectile.OriginIV3.ToVector3(), projectile.ExactPosition, interceptor.pawn.Position.ToVector3(), interceptor.OverlaySize).OrderBy(x => (projectile.OriginIV3.ToVector3() - x).sqrMagnitude).First(); + projectile.ExactPosition = exactPosition; + new Traverse(interceptor).Field("lastInterceptAngle").SetValue(newExactPos.AngleToFlat(interceptor.pawn.TrueCenter())); + new Traverse(interceptor).Field("lastInterceptTicks").SetValue(Find.TickManager.TicksGame); + new Traverse(interceptor).Field("drawInterceptCone").SetValue(true); + + Effecter eff = new Effecter(EffecterDefOf.Interceptor_BlockedProjectile); + eff.Trigger(new TargetInfo(newExactPos.ToIntVec3(), interceptor.pawn.Map, false), TargetInfo.Invalid); + eff.Cleanup(); + projectile.InterceptProjectile(interceptor, projectile.ExactPosition, true); } } } -#endregion From 87559b9dcefe9a8dbe18218e20f5bf341f119043 Mon Sep 17 00:00:00 2001 From: MaxDorob Date: Wed, 4 Oct 2023 00:13:38 +0600 Subject: [PATCH 049/158] Overshield changes --- .vs/ProjectSettings.json | 3 ++ .vs/VSWorkspaceState.json | 12 +++++ .vs/slnx.sqlite | Bin 0 -> 2297856 bytes .../CombatExtended/CE_DebugUtility.cs | 6 +++ .../Compatibility/VanillaPsycastExpanded.cs | 17 ++----- ...20\276\320\262\320\260\321\202\321\214.cs" | 42 ++++++++++++++++++ 6 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 .vs/ProjectSettings.json create mode 100644 .vs/VSWorkspaceState.json create mode 100644 .vs/slnx.sqlite create mode 100644 "Source/CombatExtended/Harmony/Harmony_Job - \320\232\320\276\320\277\320\270\321\200\320\276\320\262\320\260\321\202\321\214.cs" diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json new file mode 100644 index 0000000000..f8b4888565 --- /dev/null +++ b/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": null +} \ No newline at end of file diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json new file mode 100644 index 0000000000..d10ce9a311 --- /dev/null +++ b/.vs/VSWorkspaceState.json @@ -0,0 +1,12 @@ +{ + "ExpandedNodes": [ + "", + "\\Sounds", + "\\Source", + "\\Source\\CombatExtended", + "\\Source\\CombatExtended\\CombatExtended", + "\\Source\\CombatExtended\\Harmony" + ], + "SelectedNode": "\\Source\\CombatExtended.sln", + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..2fd580a89bb7ee02fcc8e9b743ecfdc539927622 GIT binary patch literal 2297856 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