Skip to content

Commit

Permalink
Merge pull request #3511 from CombatExtended-Continued/fix-retaliation
Browse files Browse the repository at this point in the history
Fix retaliation raids and shelling
  • Loading branch information
N7Huntsman authored Nov 1, 2024
2 parents 5c87933 + 8d92cdc commit 57d6c77
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ public class ShellingResponseDef : Def
public float defaultRaidPropability = 0.0f;
public float defaultRaidMTBDays = 0.0f;

/// <summary>
/// Penalty to be applied to retaliation shelling based on the current health status of a world object.
/// The lower bound of this range represents the multiplier to be applied at 100% health,
/// while the upper bound at 0% health.
/// </summary>
public FloatRange retaliationShellingCooldownImpact = new FloatRange(1f, 10f);

/// <summary>
/// The list of projectiles that can be used in response when shelled
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,9 @@ protected void RayCastSuppression(IntVec3 muzzle, IntVec3 destination, Map map =
/// <param name="shotSpeed">The shot speed (default: def.projectile.speed)</param>
/// <param name="equipment">The equipment used to fire the projectile.</param>
/// <param name="distance">The distance to the estimated intercept point</param>
/// <param name="ticksToTruePosition">The number of ticks before the bullet is drawn at its true height instead of the muzzle height</param>
/// <remarks>
/// Note that the launcher may not be spawned at all, e.g. for projectiles launched by enemy bases as retaliation.
/// </remarks>
public virtual void Launch(Thing launcher, Vector2 origin, float shotAngle, float shotRotation, float shotHeight = 0f, float shotSpeed = -1f, Thing equipment = null, float distance = -1)
{
this.shotAngle = shotAngle;
Expand All @@ -565,7 +567,7 @@ public virtual void Launch(Thing launcher, Vector2 origin, float shotAngle, floa
this.lerpPosition = props.lerpPosition;
this.GravityFactor = props.Gravity;
}
if (shotHeight >= CollisionVertical.WallCollisionHeight && Position.Roofed(launcher.Map))
if (shotHeight >= CollisionVertical.WallCollisionHeight && launcher.Spawned && Position.Roofed(launcher.Map))
{
ignoreRoof = true;
}
Expand Down
28 changes: 20 additions & 8 deletions Source/CombatExtended/CombatExtended/WorldObjects/HealthComp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,16 @@ public virtual void ThrottledCompTick()
}
}
}
protected virtual void TryFinishDestroyQuests(Map launcherMap)

/// <summary>
/// Clean up quests associated with a world object and update ideology raiding state.
/// </summary>
/// <param name="attackingFaction">The faction that destroyed this world object via intertile shelling.</param>
/// <param name="sourceInfo">The tile the shelling originated from.</param>
protected virtual void TryFinishDestroyQuests(Faction attackingFaction, GlobalTargetInfo sourceInfo)
{
Map launcherMap = sourceInfo.Map;

QuestUtility.SendQuestTargetSignals(parent.questTags, "AllEnemiesDefeated", parent.Named("SUBJECT"), new NamedArgument(launcherMap, "MAP"));
int num;
List<Quest> quests = Find.QuestManager.QuestsListForReading;
Expand All @@ -189,25 +197,29 @@ protected virtual void TryFinishDestroyQuests(Map launcherMap)
{
quest.End(QuestEndOutcome.Fail);
}
IdeoUtility.Notify_PlayerRaidedSomeone(launcherMap.mapPawns.FreeColonistsSpawned);

if (attackingFaction == Faction.OfPlayer && Find.Maps.Contains(launcherMap))
{
IdeoUtility.Notify_PlayerRaidedSomeone(launcherMap.mapPawns.FreeColonistsSpawned);
}
}

IEnumerable<Quest> RelatedQuests => Find.QuestManager.QuestsListForReading.Where(x => !x.Historical && x.QuestLookTargets.Contains(parent));
public void ApplyDamage(ThingDef shellDef, Faction attackingFaction, Map launcherMap)
public void ApplyDamage(ThingDef shellDef, Faction attackingFaction, GlobalTargetInfo sourceInfo)
{
if (Rand.Chance(NegateChance))
{
return;
}
if (DestoyedInstantly)
{
TryFinishDestroyQuests(launcherMap);
TryFinishDestroyQuests(attackingFaction, sourceInfo);
TryDestroy();
return;
}
var damage = shellDef.GetWorldObjectDamageWorker().ApplyDamage(this, shellDef);
recentShells.Add(new WorldDamageInfo() { Value = damage, ShellDef = shellDef });
Notify_DamageTaken(attackingFaction, launcherMap);
Notify_DamageTaken(attackingFaction, sourceInfo);
}


Expand All @@ -218,12 +230,12 @@ void TryDestroy()
parent.Destroy();
}
}
public virtual void Notify_DamageTaken(Faction attackingFaction, Map launcherMap)
public virtual void Notify_DamageTaken(Faction attackingFaction, GlobalTargetInfo sourceInfo)
{
if (health <= 1e-4)
{
TryFinishDestroyQuests(launcherMap);
Notify_PreDestroyed(attackingFaction, new GlobalTargetInfo(launcherMap.Parent));
TryFinishDestroyQuests(attackingFaction, sourceInfo);
Notify_PreDestroyed(attackingFaction, sourceInfo);
Destroy();
return;
}
Expand Down
12 changes: 10 additions & 2 deletions Source/CombatExtended/CombatExtended/WorldObjects/HostilityComp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ public virtual void ThrottledCompTick()
raider.ThrottledTick();
}

/// <summary>
/// Retaliate against a world object due to hostile shelling.
/// </summary>
/// <param name="attackingFaction">The faction to retaliate against.</param>
/// <param name="sourceInfo">The tile the shelling came from.</param>
public virtual void TryHostilityResponse(Faction attackingFaction, GlobalTargetInfo sourceInfo)
{

Expand All @@ -115,9 +120,9 @@ public virtual void TryHostilityResponse(Faction attackingFaction, GlobalTargetI
return;
}
Map attackerMap = sourceInfo.Map;
MapParent attackerMapParent = Find.World.worldObjects.MapParentAt(sourceInfo.Tile);
if (attackerMap == null)
{
MapParent attackerMapParent = Find.World.worldObjects.MapParentAt(sourceInfo.Tile);
if (attackerMapParent != null && attackerMapParent.HasMap && attackerMapParent.Map != null && Find.Maps.Contains(attackerMapParent.Map))
{
attackerMap = attackerMapParent.Map;
Expand Down Expand Up @@ -145,7 +150,10 @@ public virtual void TryHostilityResponse(Faction attackingFaction, GlobalTargetI
Log.Warning($"CE: Threat points {revengePoints}");
}
#endif
if (!sheller.Shooting && Rand.Chance(ShellingPropability))
// Only allow retaliation shelling if this faction is hostile to the owner of the site at the source location,
// in case players shell world objects from other faction maps.
Faction sourceTileFaction = attackerMapParent?.Faction;
if (!sheller.Shooting && Rand.Chance(ShellingPropability) && parent.Faction.HostileTo(sourceTileFaction))
{
sheller.TryStartShelling(sourceInfo, revengePoints, attackingFaction);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public virtual void ThrottledTick()
ticksToRaid -= WorldObjectTrackerCE.THROTTLED_TICK_INTERVAL;
return;
}
if (parms != null)
if (parms != null && Find.Maps.Contains(parms.target))
{
IncidentDef incidentDef = IncidentDefOf.RaidEnemy;
incidentDef.Worker.TryExecute(parms);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,25 @@ private ShellingResponseDef.ShellingResponsePart_Projectile RandomAvailableShell
.Where(p => (budget - p.points) > 0 && p.projectile.projectile is ProjectilePropertiesCE propEC && propEC.shellingProps.range >= Find.WorldGrid.TraversalDistanceBetween(target.Tile, comp.parent.Tile, true) * 0.5f)
.RandomElementByWeightWithFallback(p => p.weight, null);

private int GetTicksToCooldown() => Rand.Range(SHELLER_MINCOOLDOWNTICKS, Mathf.Clamp(7 - (int)comp.parent.Faction.def.techLevel, 1, SHELLER_MAXCOOLDOWNTICKS_TECHMULMAX) * SHELLER_MAXCOOLDOWNTICKS);
private int GetTicksToCooldown() => Rand.Range(SHELLER_MINCOOLDOWNTICKS,
Mathf.Clamp(7 - (int)comp.parent.Faction.def.techLevel, 1, SHELLER_MAXCOOLDOWNTICKS_TECHMULMAX) *
SHELLER_MAXCOOLDOWNTICKS) * HealthMultiplier();

private int GetTicksToShot() => Rand.Range(SHELLER_MIN_TICKSBETWEENSHOTS, SHELLER_MAX_TICKSBETWEENSHOTS);
private int GetTicksToShot() => Rand.Range(SHELLER_MIN_TICKSBETWEENSHOTS, SHELLER_MAX_TICKSBETWEENSHOTS) * HealthMultiplier();

/// <summary>
/// Compute the multiplier to be applied to retaliation fire rate based on the current health of this world object.
/// </summary>
/// <returns>The computed multiplier.</returns>
private int HealthMultiplier()
{
var retaliationShellingCooldownMultiplier =
comp.parent.Faction.GetShellingResponseDef().retaliationShellingCooldownImpact;

var curHealth = comp.parent.GetComponent<HealthComp>()?.Health ?? 1f;

return Mathf.FloorToInt(retaliationShellingCooldownMultiplier.LerpThroughRange(1f - curHealth));
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ protected override void Arrived()
private bool TryShell(WorldObject worldObject)
{
bool shelled = false;
if (worldObject is MapParent mapParent && mapParent.HasMap)
if (worldObject is MapParent mapParent && mapParent.HasMap && Find.Maps.Contains(mapParent.Map))
{
shelled = true;
Map map = mapParent.Map;
Expand Down Expand Up @@ -143,7 +143,7 @@ private bool TryShell(WorldObject worldObject)
if (!shelled)
{
shelled = true;
healthComp.ApplyDamage(shellDef, Faction, globalSource.Map);
healthComp.ApplyDamage(shellDef, Faction, globalSource);
}
}
return shelled;
Expand Down

0 comments on commit 57d6c77

Please sign in to comment.