好玩
This commit is contained in:
223
Source/WulaFallenEmpire/Verb/Verb_ShootBeamSplitAndChain.cs
Normal file
223
Source/WulaFallenEmpire/Verb/Verb_ShootBeamSplitAndChain.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
using Verse.Sound;
|
||||
using WulaFallenEmpire.Utils;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class VerbProperties_SplitAndChain : VerbProperties
|
||||
{
|
||||
public bool isSplit = false;
|
||||
public int splitNum;
|
||||
public float splitRange;
|
||||
public int conductNum;
|
||||
public float conductRange;
|
||||
public float splitDamageFactor = 0.8f;
|
||||
public float conductDamageFactor = 0.6f;
|
||||
public float beamArmorPenetration = 0f;
|
||||
public int beamPathSteps = 15;
|
||||
public float flecksPerCell = 2f; // Flecks per cell to control beam density
|
||||
|
||||
public FleckDef splitMoteDef;
|
||||
public FleckDef chainMoteDef;
|
||||
|
||||
public VerbProperties_SplitAndChain()
|
||||
{
|
||||
this.verbClass = typeof(Verb_ShootBeamSplitAndChain);
|
||||
}
|
||||
}
|
||||
|
||||
public class Verb_ShootBeamSplitAndChain : Verb
|
||||
{
|
||||
private VerbProperties_SplitAndChain Props => this.verbProps as VerbProperties_SplitAndChain;
|
||||
private Dictionary<Thing, List<Thing>> attackChains = new Dictionary<Thing, List<Thing>>();
|
||||
private Dictionary<Thing, Effecter> endEffecters = new Dictionary<Thing, Effecter>();
|
||||
private Sustainer sustainer;
|
||||
private int ticksToNextPathStep;
|
||||
|
||||
public override void WarmupComplete()
|
||||
{
|
||||
this.Cleanup();
|
||||
|
||||
List<Thing> mainTargets = new List<Thing>();
|
||||
if (!this.currentTarget.HasThing) { base.WarmupComplete(); return; }
|
||||
|
||||
Thing primaryTarget = this.currentTarget.Thing;
|
||||
if (primaryTarget is Pawn p_primary && (p_primary.Dead || p_primary.Downed)) return;
|
||||
|
||||
mainTargets.Add(primaryTarget);
|
||||
|
||||
if (this.Props.isSplit && this.Props.splitNum > 0)
|
||||
{
|
||||
var potentialTargets = GenRadial.RadialDistinctThingsAround(primaryTarget.Position, this.caster.Map, this.Props.splitRange, false)
|
||||
.OfType<Pawn>()
|
||||
.Where(p => !p.Dead && !p.Downed && p.HostileTo(this.caster.Faction) && !mainTargets.Contains(p) && GenSight.LineOfSight(primaryTarget.Position, p.Position, this.caster.Map, true))
|
||||
.OrderBy(p => p.Position.DistanceToSquared(primaryTarget.Position))
|
||||
.Take(this.Props.splitNum);
|
||||
|
||||
mainTargets.AddRange(potentialTargets);
|
||||
}
|
||||
|
||||
foreach (Thing mainTarget in mainTargets)
|
||||
{
|
||||
List<Thing> currentChain = new List<Thing>();
|
||||
currentChain.Add(mainTarget);
|
||||
|
||||
Thing lastTargetInChain = mainTarget;
|
||||
for (int i = 0; i < this.Props.conductNum; i++)
|
||||
{
|
||||
Thing nextInChain = GenRadial.RadialDistinctThingsAround(lastTargetInChain.Position, this.caster.Map, this.Props.conductRange, false)
|
||||
.OfType<Pawn>()
|
||||
.Where(p => !p.Dead && !p.Downed && !currentChain.Contains(p) && !mainTargets.Except(new[]{mainTarget}).Contains(p) && this.Caster.HostileTo(p) && GenSight.LineOfSight(lastTargetInChain.Position, p.Position, this.caster.Map, true))
|
||||
.OrderBy(p => p.Position.DistanceToSquared(lastTargetInChain.Position))
|
||||
.FirstOrDefault();
|
||||
|
||||
if (nextInChain != null)
|
||||
{
|
||||
currentChain.Add(nextInChain);
|
||||
lastTargetInChain = nextInChain;
|
||||
}
|
||||
else { break; }
|
||||
}
|
||||
attackChains[mainTarget] = currentChain;
|
||||
}
|
||||
|
||||
this.burstShotsLeft = this.verbProps.burstShotCount;
|
||||
this.state = VerbState.Bursting;
|
||||
if (this.Props.soundCastBeam != null)
|
||||
{
|
||||
this.sustainer = this.Props.soundCastBeam.TrySpawnSustainer(SoundInfo.InMap(this.caster, MaintenanceType.PerTick));
|
||||
}
|
||||
base.TryCastNextBurstShot();
|
||||
}
|
||||
|
||||
public override void BurstingTick()
|
||||
{
|
||||
if (this.burstShotsLeft <= 0)
|
||||
{
|
||||
this.Cleanup();
|
||||
base.BurstingTick();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Thing> deadOrInvalidChains = attackChains.Keys.Where(t => t == null || !t.Spawned).ToList();
|
||||
foreach (var key in deadOrInvalidChains)
|
||||
{
|
||||
if(endEffecters.ContainsKey(key))
|
||||
{
|
||||
endEffecters[key].Cleanup();
|
||||
endEffecters.Remove(key);
|
||||
}
|
||||
attackChains.Remove(key);
|
||||
}
|
||||
|
||||
Vector3 casterPos = this.caster.DrawPos;
|
||||
foreach (var chainEntry in attackChains)
|
||||
{
|
||||
Thing mainTarget = chainEntry.Key;
|
||||
List<Thing> conductTargets = chainEntry.Value;
|
||||
|
||||
DrawCurvedBeam(casterPos, mainTarget.DrawPos, Props.splitMoteDef ?? verbProps.beamLineFleckDef);
|
||||
|
||||
for (int i = 0; i < conductTargets.Count - 1; i++)
|
||||
{
|
||||
DrawCurvedBeam(conductTargets[i].DrawPos, conductTargets[i+1].DrawPos, Props.chainMoteDef ?? verbProps.beamLineFleckDef);
|
||||
}
|
||||
|
||||
foreach (Thing target in conductTargets)
|
||||
{
|
||||
if (!endEffecters.ContainsKey(target) || endEffecters[target] == null)
|
||||
{
|
||||
endEffecters[target] = verbProps.beamEndEffecterDef?.Spawn(target.Position, target.Map, Vector3.zero);
|
||||
}
|
||||
endEffecters[target]?.EffectTick(new TargetInfo(target), TargetInfo.Invalid);
|
||||
}
|
||||
}
|
||||
sustainer?.Maintain();
|
||||
}
|
||||
|
||||
protected override bool TryCastShot()
|
||||
{
|
||||
if (this.attackChains.NullOrEmpty()) return false;
|
||||
|
||||
bool anyDamaged = false;
|
||||
foreach (var chainEntry in attackChains)
|
||||
{
|
||||
Thing mainTarget = chainEntry.Key;
|
||||
List<Thing> conductTargets = chainEntry.Value;
|
||||
|
||||
ApplyDamage(mainTarget, Props.splitDamageFactor);
|
||||
anyDamaged = true;
|
||||
|
||||
for (int i = 1; i < conductTargets.Count; i++)
|
||||
{
|
||||
ApplyDamage(conductTargets[i], Props.conductDamageFactor);
|
||||
}
|
||||
}
|
||||
|
||||
this.ticksToNextPathStep = this.verbProps.ticksBetweenBurstShots;
|
||||
return anyDamaged;
|
||||
}
|
||||
|
||||
private void DrawCurvedBeam(Vector3 start, Vector3 end, FleckDef fleckDef)
|
||||
{
|
||||
if (fleckDef == null) return;
|
||||
|
||||
float magnitude = (end - start).MagnitudeHorizontal();
|
||||
if (magnitude <= 0) return;
|
||||
|
||||
// 1. Generate Bezier curve points
|
||||
int segments = Mathf.Max(3, Mathf.CeilToInt(magnitude * Props.flecksPerCell));
|
||||
|
||||
// --- ULTIMATE CURVE FIX ---
|
||||
// The control point must be offset perpendicular to the beam's direction on the XZ plane, not on the Y axis.
|
||||
Vector3 direction = (end - start).normalized;
|
||||
Vector3 perpendicular = new Vector3(-direction.z, 0, direction.x); // Rotated 90 degrees on the XZ plane.
|
||||
|
||||
Vector3 controlPoint = Vector3.Lerp(start, end, 0.5f) + perpendicular * magnitude * Props.beamCurvature;
|
||||
var path = BezierUtil.GenerateQuadraticPoints(start, controlPoint, end, segments);
|
||||
// 2. Check if there are enough points to connect
|
||||
if (path.Count < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Iterate through adjacent point pairs and draw connecting lines
|
||||
for (int i = 0; i < path.Count - 1; i++)
|
||||
{
|
||||
Vector3 pointA = path[i];
|
||||
Vector3 pointB = path[i + 1];
|
||||
FleckMaker.ConnectingLine(pointA, pointB, fleckDef, this.caster.Map, 1f);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyDamage(Thing thing, float damageFactor)
|
||||
{
|
||||
if (thing == null || verbProps.beamDamageDef == null) return;
|
||||
|
||||
float totalDamage = verbProps.beamTotalDamage > 0 ? verbProps.beamTotalDamage / verbProps.burstShotCount : verbProps.beamDamageDef.defaultDamage;
|
||||
float finalDamage = totalDamage * damageFactor;
|
||||
|
||||
var dinfo = new DamageInfo(verbProps.beamDamageDef, finalDamage, Props.beamArmorPenetration, -1, this.caster, null, base.EquipmentSource.def);
|
||||
thing.TakeDamage(dinfo);
|
||||
}
|
||||
|
||||
private void Cleanup()
|
||||
{
|
||||
attackChains.Clear();
|
||||
foreach (var effecter in endEffecters.Values) effecter.Cleanup();
|
||||
endEffecters.Clear();
|
||||
sustainer?.End();
|
||||
sustainer = null;
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
}
|
||||
}
|
||||
}
|
||||
158
Source/WulaFallenEmpire/Verb/Verb_ShootShotgunWithOffset.cs
Normal file
158
Source/WulaFallenEmpire/Verb/Verb_ShootShotgunWithOffset.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using RimWorld;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class Verb_ShootShotgunWithOffset : Verb_Shoot
|
||||
{
|
||||
protected override bool TryCastShot()
|
||||
{
|
||||
// Fire the first shot
|
||||
bool initialShotSuccess = this.BaseTryCastShot(0);
|
||||
if (initialShotSuccess && CasterIsPawn)
|
||||
{
|
||||
CasterPawn.records.Increment(RecordDefOf.ShotsFired);
|
||||
}
|
||||
|
||||
// Get shotgun extension
|
||||
ShotgunExtension shotgunExtension = ShotgunExtension.Get(this.verbProps.defaultProjectile);
|
||||
if (initialShotSuccess && shotgunExtension != null && shotgunExtension.pelletCount > 1)
|
||||
{
|
||||
// Fire the rest of the pellets in a loop
|
||||
for (int i = 1; i < shotgunExtension.pelletCount; i++)
|
||||
{
|
||||
this.BaseTryCastShot(i);
|
||||
}
|
||||
}
|
||||
return initialShotSuccess;
|
||||
}
|
||||
|
||||
protected bool BaseTryCastShot(int pelletIndex)
|
||||
{
|
||||
if (currentTarget.HasThing && currentTarget.Thing.Map != caster.Map)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ThingDef projectile = Projectile;
|
||||
if (projectile == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ShootLine resultingLine;
|
||||
bool flag = TryFindShootLineFromTo(caster.Position, currentTarget, out resultingLine);
|
||||
if (verbProps.stopBurstWithoutLos && !flag)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (base.EquipmentSource != null)
|
||||
{
|
||||
base.EquipmentSource.GetComp<CompChangeableProjectile>()?.Notify_ProjectileLaunched();
|
||||
base.EquipmentSource.GetComp<CompApparelVerbOwner_Charged>()?.UsedOnce();
|
||||
}
|
||||
|
||||
lastShotTick = Find.TickManager.TicksGame;
|
||||
Thing manningPawn = caster;
|
||||
Thing equipmentSource = base.EquipmentSource;
|
||||
CompMannable compMannable = caster.TryGetComp<CompMannable>();
|
||||
if (compMannable?.ManningPawn != null)
|
||||
{
|
||||
manningPawn = compMannable.ManningPawn;
|
||||
equipmentSource = caster;
|
||||
}
|
||||
|
||||
Vector3 drawPos = caster.DrawPos;
|
||||
drawPos = ApplyProjectileOffset(drawPos, equipmentSource, pelletIndex);
|
||||
Projectile projectile2 = (Projectile)GenSpawn.Spawn(projectile, resultingLine.Source, caster.Map);
|
||||
if (equipmentSource.TryGetComp(out CompUniqueWeapon comp))
|
||||
{
|
||||
foreach (WeaponTraitDef item in comp.TraitsListForReading)
|
||||
{
|
||||
if (item.damageDefOverride != null)
|
||||
{
|
||||
projectile2.damageDefOverride = item.damageDefOverride;
|
||||
}
|
||||
|
||||
if (!item.extraDamages.NullOrEmpty())
|
||||
{
|
||||
if (projectile2.extraDamages == null)
|
||||
{
|
||||
projectile2.extraDamages = new List<ExtraDamage>();
|
||||
}
|
||||
projectile2.extraDamages.AddRange(item.extraDamages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (verbProps.ForcedMissRadius > 0.5f)
|
||||
{
|
||||
float num = verbProps.ForcedMissRadius;
|
||||
if (manningPawn is Pawn pawn)
|
||||
{
|
||||
num *= verbProps.GetForceMissFactorFor(equipmentSource, pawn);
|
||||
}
|
||||
|
||||
float num2 = VerbUtility.CalculateAdjustedForcedMiss(num, currentTarget.Cell - caster.Position);
|
||||
if (num2 > 0.5f)
|
||||
{
|
||||
IntVec3 forcedMissTarget = GetForcedMissTarget(num2);
|
||||
if (forcedMissTarget != currentTarget.Cell)
|
||||
{
|
||||
projectile2.Launch(manningPawn, drawPos, forcedMissTarget, currentTarget, ProjectileHitFlags.All, preventFriendlyFire, equipmentSource);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShotReport shotReport = ShotReport.HitReportFor(caster, this, currentTarget);
|
||||
if (verbProps.canGoWild && !Rand.Chance(shotReport.AimOnTargetChance_IgnoringPosture))
|
||||
{
|
||||
bool flyOverhead = projectile2?.def?.projectile != null && projectile2.def.projectile.flyOverhead;
|
||||
resultingLine.ChangeDestToMissWild(shotReport.AimOnTargetChance_StandardTarget, flyOverhead, caster.Map);
|
||||
projectile2.Launch(manningPawn, drawPos, resultingLine.Dest, currentTarget, ProjectileHitFlags.NonTargetWorld, preventFriendlyFire, equipmentSource, shotReport.GetRandomCoverToMissInto()?.def);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (currentTarget.Thing != null && currentTarget.Thing.def.CanBenefitFromCover && !Rand.Chance(shotReport.PassCoverChance))
|
||||
{
|
||||
projectile2.Launch(manningPawn, drawPos, shotReport.GetRandomCoverToMissInto(), currentTarget, ProjectileHitFlags.NonTargetWorld, preventFriendlyFire, equipmentSource, shotReport.GetRandomCoverToMissInto()?.def);
|
||||
return true;
|
||||
}
|
||||
|
||||
projectile2.Launch(manningPawn, drawPos, currentTarget, currentTarget, ProjectileHitFlags.IntendedTarget, preventFriendlyFire, equipmentSource, shotReport.GetRandomCoverToMissInto()?.def);
|
||||
return true;
|
||||
}
|
||||
|
||||
private Vector3 ApplyProjectileOffset(Vector3 originalDrawPos, Thing equipmentSource, int pelletIndex)
|
||||
{
|
||||
if (equipmentSource != null)
|
||||
{
|
||||
ModExtension_ShootWithOffset offsetExtension = (base.EquipmentSource?.def)?.GetModExtension<ModExtension_ShootWithOffset>();
|
||||
|
||||
if (offsetExtension != null && offsetExtension.offsets != null && offsetExtension.offsets.Count > 0)
|
||||
{
|
||||
Vector2 offset = offsetExtension.GetOffsetFor(pelletIndex);
|
||||
|
||||
Vector3 targetPos = currentTarget.CenterVector3;
|
||||
Vector3 casterPos = caster.DrawPos;
|
||||
float rimworldAngle = targetPos.AngleToFlat(casterPos);
|
||||
|
||||
float correctedAngle = -rimworldAngle - 90f;
|
||||
|
||||
Vector2 rotatedOffset = offset.RotatedBy(correctedAngle);
|
||||
|
||||
originalDrawPos += new Vector3(rotatedOffset.x, 0f, rotatedOffset.y);
|
||||
}
|
||||
}
|
||||
return originalDrawPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
262
Source/WulaFallenEmpire/Verb/Verb_ShootWithOffset.cs
Normal file
262
Source/WulaFallenEmpire/Verb/Verb_ShootWithOffset.cs
Normal file
@@ -0,0 +1,262 @@
|
||||
using RimWorld;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class PlaceWorker_ShowTurretWithOffsetRadius : PlaceWorker
|
||||
{
|
||||
public override AcceptanceReport AllowsPlacing(BuildableDef checkingDef, IntVec3 loc, Rot4 rot, Map map, Thing thingToIgnore = null, Thing thing = null)
|
||||
{
|
||||
VerbProperties verbProperties = ((ThingDef)checkingDef).building.turretGunDef.Verbs.Find((VerbProperties v) => v.verbClass == typeof(Verb_ShootWithOffset));
|
||||
if (verbProperties.range > 0f)
|
||||
{
|
||||
GenDraw.DrawRadiusRing(loc, verbProperties.range);
|
||||
}
|
||||
if (verbProperties.minRange > 0f)
|
||||
{
|
||||
GenDraw.DrawRadiusRing(loc, verbProperties.minRange);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public class ModExtension_ShootWithOffset : DefModExtension
|
||||
{
|
||||
public Vector2 GetOffsetFor(int index)
|
||||
{
|
||||
Vector2 result;
|
||||
if (this.offsets.NullOrEmpty<Vector2>())
|
||||
{
|
||||
result = Vector2.zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
int index2 = index % this.offsets.Count;
|
||||
result = this.offsets[index2];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
public List<Vector2> offsets = new List<Vector2>();
|
||||
}
|
||||
public class Verb_ShootWithOffset : Verb_Shoot
|
||||
{
|
||||
public int offset = 0;
|
||||
protected override bool TryCastShot()
|
||||
{
|
||||
bool num = BaseTryCastShot();
|
||||
if (num && CasterIsPawn)
|
||||
{
|
||||
CasterPawn.records.Increment(RecordDefOf.ShotsFired);
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
protected bool BaseTryCastShot()
|
||||
{
|
||||
|
||||
if (currentTarget.HasThing && currentTarget.Thing.Map != caster.Map)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ThingDef projectile = Projectile;
|
||||
if (projectile == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ShootLine resultingLine;
|
||||
bool flag = TryFindShootLineFromTo(caster.Position, currentTarget, out resultingLine);
|
||||
if (verbProps.stopBurstWithoutLos && !flag)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (base.EquipmentSource != null)
|
||||
{
|
||||
base.EquipmentSource.GetComp<CompChangeableProjectile>()?.Notify_ProjectileLaunched();
|
||||
base.EquipmentSource.GetComp<CompApparelVerbOwner_Charged>()?.UsedOnce();
|
||||
}
|
||||
|
||||
lastShotTick = Find.TickManager.TicksGame;
|
||||
Thing manningPawn = caster;
|
||||
Thing equipmentSource = base.EquipmentSource;
|
||||
CompMannable compMannable = caster.TryGetComp<CompMannable>();
|
||||
if (compMannable?.ManningPawn != null)
|
||||
{
|
||||
manningPawn = compMannable.ManningPawn;
|
||||
equipmentSource = caster;
|
||||
}
|
||||
|
||||
Vector3 drawPos = caster.DrawPos;
|
||||
drawPos = ApplyProjectileOffset(drawPos, equipmentSource);
|
||||
Projectile projectile2 = (Projectile)GenSpawn.Spawn(projectile, resultingLine.Source, caster.Map);
|
||||
if (equipmentSource.TryGetComp(out CompUniqueWeapon comp))
|
||||
{
|
||||
foreach (WeaponTraitDef item in comp.TraitsListForReading)
|
||||
{
|
||||
if (item.damageDefOverride != null)
|
||||
{
|
||||
projectile2.damageDefOverride = item.damageDefOverride;
|
||||
}
|
||||
|
||||
if (!item.extraDamages.NullOrEmpty())
|
||||
{
|
||||
Projectile projectile3 = projectile2;
|
||||
if (projectile3.extraDamages == null)
|
||||
{
|
||||
projectile3.extraDamages = new List<ExtraDamage>();
|
||||
}
|
||||
|
||||
projectile2.extraDamages.AddRange(item.extraDamages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (verbProps.ForcedMissRadius > 0.5f)
|
||||
{
|
||||
float num = verbProps.ForcedMissRadius;
|
||||
if (manningPawn is Pawn pawn)
|
||||
{
|
||||
num *= verbProps.GetForceMissFactorFor(equipmentSource, pawn);
|
||||
}
|
||||
|
||||
float num2 = VerbUtility.CalculateAdjustedForcedMiss(num, currentTarget.Cell - caster.Position);
|
||||
if (num2 > 0.5f)
|
||||
{
|
||||
IntVec3 forcedMissTarget = GetForcedMissTarget(num2);
|
||||
if (forcedMissTarget != currentTarget.Cell)
|
||||
{
|
||||
ProjectileHitFlags projectileHitFlags = ProjectileHitFlags.NonTargetWorld;
|
||||
if (Rand.Chance(0.5f))
|
||||
{
|
||||
projectileHitFlags = ProjectileHitFlags.All;
|
||||
}
|
||||
|
||||
if (!canHitNonTargetPawnsNow)
|
||||
{
|
||||
projectileHitFlags &= ~ProjectileHitFlags.NonTargetPawns;
|
||||
}
|
||||
|
||||
projectile2.Launch(manningPawn, drawPos, forcedMissTarget, currentTarget, projectileHitFlags, preventFriendlyFire, equipmentSource);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShotReport shotReport = ShotReport.HitReportFor(caster, this, currentTarget);
|
||||
Thing randomCoverToMissInto = shotReport.GetRandomCoverToMissInto();
|
||||
ThingDef targetCoverDef = randomCoverToMissInto?.def;
|
||||
if (verbProps.canGoWild && !Rand.Chance(shotReport.AimOnTargetChance_IgnoringPosture))
|
||||
{
|
||||
bool flyOverhead = projectile2?.def?.projectile != null && projectile2.def.projectile.flyOverhead;
|
||||
resultingLine.ChangeDestToMissWild(shotReport.AimOnTargetChance_StandardTarget, flyOverhead, caster.Map);
|
||||
ProjectileHitFlags projectileHitFlags2 = ProjectileHitFlags.NonTargetWorld;
|
||||
if (Rand.Chance(0.5f) && canHitNonTargetPawnsNow)
|
||||
{
|
||||
projectileHitFlags2 |= ProjectileHitFlags.NonTargetPawns;
|
||||
}
|
||||
|
||||
projectile2.Launch(manningPawn, drawPos, resultingLine.Dest, currentTarget, projectileHitFlags2, preventFriendlyFire, equipmentSource, targetCoverDef);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (currentTarget.Thing != null && currentTarget.Thing.def.CanBenefitFromCover && !Rand.Chance(shotReport.PassCoverChance))
|
||||
{
|
||||
ProjectileHitFlags projectileHitFlags3 = ProjectileHitFlags.NonTargetWorld;
|
||||
if (canHitNonTargetPawnsNow)
|
||||
{
|
||||
projectileHitFlags3 |= ProjectileHitFlags.NonTargetPawns;
|
||||
}
|
||||
|
||||
projectile2.Launch(manningPawn, drawPos, randomCoverToMissInto, currentTarget, projectileHitFlags3, preventFriendlyFire, equipmentSource, targetCoverDef);
|
||||
return true;
|
||||
}
|
||||
|
||||
ProjectileHitFlags projectileHitFlags4 = ProjectileHitFlags.IntendedTarget;
|
||||
if (canHitNonTargetPawnsNow)
|
||||
{
|
||||
projectileHitFlags4 |= ProjectileHitFlags.NonTargetPawns;
|
||||
}
|
||||
|
||||
if (!currentTarget.HasThing || currentTarget.Thing.def.Fillage == FillCategory.Full)
|
||||
{
|
||||
projectileHitFlags4 |= ProjectileHitFlags.NonTargetWorld;
|
||||
}
|
||||
if (currentTarget.Thing != null)
|
||||
{
|
||||
projectile2.Launch(manningPawn, drawPos, currentTarget, currentTarget, projectileHitFlags4, preventFriendlyFire, equipmentSource, targetCoverDef);
|
||||
}
|
||||
else
|
||||
{
|
||||
projectile2.Launch(manningPawn, drawPos, resultingLine.Dest, currentTarget, projectileHitFlags4, preventFriendlyFire, equipmentSource, targetCoverDef);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private Vector3 ApplyProjectileOffset(Vector3 originalDrawPos, Thing equipmentSource)
|
||||
{
|
||||
if (equipmentSource != null)
|
||||
{
|
||||
// 获取投射物偏移的模组扩展
|
||||
ModExtension_ShootWithOffset offsetExtension =
|
||||
equipmentSource.def.GetModExtension<ModExtension_ShootWithOffset>();
|
||||
|
||||
if (offsetExtension != null && offsetExtension.offsets != null && offsetExtension.offsets.Count > 0)
|
||||
{
|
||||
// 获取当前连发射击的剩余次数
|
||||
int burstShotsLeft = GetBurstShotsLeft();
|
||||
|
||||
// 计算从发射者到目标的角度
|
||||
Vector3 targetPos = currentTarget.CenterVector3;
|
||||
Vector3 casterPos = caster.DrawPos;
|
||||
float rimworldAngle = targetPos.AngleToFlat(casterPos);
|
||||
|
||||
// 将RimWorld角度转换为适合偏移计算的角度
|
||||
float correctedAngle = ConvertRimWorldAngleToOffsetAngle(rimworldAngle);
|
||||
|
||||
// 应用偏移并旋转到正确方向
|
||||
Vector2 offset = offsetExtension.GetOffsetFor(burstShotsLeft);
|
||||
Vector2 rotatedOffset = offset.RotatedBy(correctedAngle);
|
||||
|
||||
// 将2D偏移转换为3D并应用到绘制位置
|
||||
originalDrawPos += new Vector3(rotatedOffset.x, 0f, rotatedOffset.y);
|
||||
}
|
||||
}
|
||||
|
||||
return originalDrawPos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前连发射击剩余次数
|
||||
/// </summary>
|
||||
/// <returns>连发射击剩余次数</returns>
|
||||
private int GetBurstShotsLeft()
|
||||
{
|
||||
if (burstShotsLeft >= 0)
|
||||
{
|
||||
return (int)burstShotsLeft;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将RimWorld角度转换为偏移计算用的角度
|
||||
/// RimWorld使用顺时针角度系统,需要转换为标准的数学角度系统
|
||||
/// </summary>
|
||||
/// <param name="rimworldAngle">RimWorld角度</param>
|
||||
/// <returns>转换后的角度</returns>
|
||||
private float ConvertRimWorldAngleToOffsetAngle(float rimworldAngle)
|
||||
{
|
||||
// RimWorld角度:0°=东,90°=北,180°=西,270°=南
|
||||
// 转换为:0°=东,90°=南,180°=西,270°=北
|
||||
return -rimworldAngle - 90f;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user