Enlist: handle shuttles & hostile goodwill

Apply goodwill penalties and notify player when enlisting makes other factions hostile to the enlisted faction; new localization keys added for the letter. Add FCP_Core reference to the Enlist project. Replace fragile reflection logic for custom transport ships with direct type access: load shuttle defs via FactionModExtension, create shuttle Thing/CompTransporter, transfer caravan cargo to the shuttle, create TravellingTransporters with proper fields and add its transporter info; fall back to drop pods on failure. Also change texture lookup to reportFailure:false with BaseContent.BadTex fallback and update GetTransportPodsFloatMenuOptionsAt to accept an optional launchAction. Updated compiled assembly (FCP_Enlist.dll).
This commit is contained in:
RickGrymes
2026-02-09 00:57:51 -06:00
parent 1509942a21
commit e71f36af9d
5 changed files with 93 additions and 85 deletions
Binary file not shown.
@@ -29,4 +29,8 @@
<FCP_WeaponReq_WrongXenotype>Requires xenotype: {0}</FCP_WeaponReq_WrongXenotype>
<FCP_WeaponReq_RequiresApparel>Requires apparel: {0}</FCP_WeaponReq_RequiresApparel>
<!-- Enlistment - Hostile Factions -->
<FCP_Enemy_Of_Enlist_Faction>New Enemy</FCP_Enemy_Of_Enlist_Faction>
<FCP_Enemy_Of_Enlist_Faction_Desc>By enlisting with {ENLISTED_name}, you have become an enemy of {HOSTILE_name}. They are now hostile toward your colony.</FCP_Enemy_Of_Enlist_Faction_Desc>
</LanguageData>
+7
View File
@@ -36,4 +36,11 @@
<Publicize Include="Assembly-CSharp" />
</ItemGroup>
<ItemGroup>
<Reference Include="FCP_Core">
<HintPath>..\..\1.6\Assemblies\FCP_Core.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
</Project>
+21
View File
@@ -54,6 +54,27 @@ public class FactionEnlistWorker
factionOptions.factionsStorages[def] = factionStorage;
}
def.enlistedSoundDef?.PlayOneShotOnCamera();
Faction player = Faction.OfPlayer;
foreach (Faction faction in Find.FactionManager.AllFactionsListForReading)
{
if (faction == toEnlist || faction == player || faction.defeated || faction.IsPlayer) continue;
FactionRelation rel = toEnlist.RelationWith(faction, false);
if (rel == null || rel.kind != FactionRelationKind.Hostile) continue;
if (faction.HostileTo(player)) continue;
int goodwill = faction.GoodwillWith(player);
if (goodwill > -75)
{
faction.TryAffectGoodwillWith(player, -75 - goodwill, false, true);
}
Find.LetterStack.ReceiveLetter(
"FCP_Enemy_Of_Enlist_Faction".Translate(),
"FCP_Enemy_Of_Enlist_Faction_Desc".Translate(toEnlist.Named("ENLISTED"), faction.Named("HOSTILE")),
LetterDefOf.NegativeEvent);
}
}
+61 -85
View File
@@ -7,6 +7,7 @@ using System.Linq;
using System.Text;
using UnityEngine;
using Verse;
using FCP.Core.Shuttles;
namespace FCP.Enlist;
@@ -493,7 +494,7 @@ public class WorldObjectCompEnlist : WorldObjectComp
{
defaultLabel = optionDef.shuttleServiceLabelKey.Translate(faction.Named("FACTION")),
defaultDesc = optionDef.shuttleServiceDescKey.Translate(faction.Named("FACTION")),
icon = ContentFinder<Texture2D>.Get(optionDef.shuttleServiceButtonIconTexPath),
icon = ContentFinder<Texture2D>.Get(optionDef.shuttleServiceButtonIconTexPath, reportFailure: false) ?? BaseContent.BadTex,
action = delegate
{
StartChoosingShuttleDestination(caravan, optionDef);
@@ -692,7 +693,7 @@ public class WorldObjectCompEnlist : WorldObjectComp
Messages.Message("TransportPodDestinationBeyondMaximumRange".Translate(), MessageTypeDefOf.RejectInput, historical: false);
return false;
}
IEnumerable<FloatMenuOption> source = GetTransportPodsFloatMenuOptionsAt(target.Tile, caravan);
IEnumerable<FloatMenuOption> source = GetTransportPodsFloatMenuOptionsAt(target.Tile, caravan, launchAction);
if (!source.Any())
{
if (Find.World.Impassable(target.Tile))
@@ -783,92 +784,63 @@ public class WorldObjectCompEnlist : WorldObjectComp
}
ExtractMoneyFromCaravan(caravan, curFactionEnlistOptionsDef.shuttleServiceCost, curFactionEnlistOptionsDef);
// Try to get custom shuttle from faction using reflection
TransportShipDef transportShipDef = null;
if (parent.Faction?.def?.modExtensions != null)
// Get custom shuttle from faction extension - direct type access (no reflection)
FactionModExtension factionExtension = parent.Faction?.def?.GetModExtension<FactionModExtension>();
TransportShipDef transportShipDef = factionExtension?.transportShipDef;
if (transportShipDef != null && transportShipDef.worldObject != null)
{
foreach (var extension in parent.Faction.def.modExtensions)
// Create the shuttle thing - it's a Building with CompTransporter, not an ActiveTransporter
Thing shuttleThing = ThingMaker.MakeThing(transportShipDef.shipThing);
CompTransporter compTransporter = shuttleThing.TryGetComp<CompTransporter>();
if (compTransporter == null)
{
if (extension.GetType().Name == "FactionModExtension")
{
var transportShipDefField = extension.GetType().GetField("transportShipDef");
if (transportShipDefField != null)
{
transportShipDef = transportShipDefField.GetValue(extension) as TransportShipDef;
break;
}
}
Log.Error($"[FCP Enlist] Shuttle thing {shuttleThing.def.defName} has no CompTransporter!");
TryLaunch(destinationTile, arrivalAction, caravan);
return;
}
}
Thing transportThing;
ThingDef skyfallerDef;
WorldObjectDef worldObjectDef;
if (transportShipDef != null)
{
// Use custom shuttle
transportThing = ThingMaker.MakeThing(transportShipDef.shipThing);
skyfallerDef = transportShipDef.leavingSkyfaller;
worldObjectDef = transportShipDef.worldObject;
}
else
{
// Fall back to drop pods
transportThing = ThingMaker.MakeThing(ThingDefOf.ActiveDropPod);
skyfallerDef = ThingDefOf.DropPodLeaving;
worldObjectDef = WorldObjectDefOf.TravellingTransporters;
}
// Load the transport
CompTransporter compTransporter = transportThing.TryGetComp<CompTransporter>();
if (compTransporter != null)
{
compTransporter.GetDirectlyHeldThings().TryAddRangeOrTransfer(caravan.GetDirectlyHeldThings(), canMergeWithExistingStacks: true, destroyLeftover: true);
}
// Create the skyfaller
Skyfaller skyfaller = (Skyfaller)SkyfallerMaker.MakeSkyfaller(skyfallerDef, transportThing);
if (skyfaller is FlyShipLeaving flyShip)
{
flyShip.groupID = 1;
flyShip.destinationTile = destinationTile;
flyShip.arrivalAction = arrivalAction;
flyShip.worldObjectDef = worldObjectDef;
}
// Create traveling world object
WorldObject travelingPods = WorldObjectMaker.MakeWorldObject(worldObjectDef);
travelingPods.Tile = base.parent.Tile;
travelingPods.SetFaction(Faction.OfPlayer);
// Set destination using reflection since we don't know the exact type
var destTileField = travelingPods.GetType().GetField("destinationTile");
if (destTileField != null)
{
destTileField.SetValue(travelingPods, destinationTile);
}
var arrivalActionField = travelingPods.GetType().GetField("arrivalAction");
if (arrivalActionField != null)
{
arrivalActionField.SetValue(travelingPods, arrivalAction);
}
Find.WorldObjects.Add(travelingPods);
// Add pods using reflection
if (compTransporter != null)
{
var addPodMethod = travelingPods.GetType().GetMethod("AddPod");
if (addPodMethod != null)
// Transfer caravan contents to shuttle's transporter
compTransporter.GetDirectlyHeldThings().TryAddRangeOrTransfer(
caravan.GetDirectlyHeldThings(),
canMergeWithExistingStacks: true,
destroyLeftover: true);
// Create the traveling world object - cast to TravellingTransporters for direct property access
TravellingTransporters travelingShuttle = (TravellingTransporters)WorldObjectMaker.MakeWorldObject(transportShipDef.worldObject);
travelingShuttle.Tile = parent.Tile;
travelingShuttle.SetFaction(Faction.OfPlayer);
travelingShuttle.destinationTile = destinationTile;
// Use custom arrival action that forces map loading to show shuttle landing
travelingShuttle.arrivalAction = arrivalAction ?? new TransportersArrivalAction_FormCaravan();
// Set the transport ship def (still need reflection for this private field)
var transportShipField = typeof(TravellingTransporters).GetField("transportShip",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (transportShipField != null)
{
addPodMethod.Invoke(travelingPods, new object[] { compTransporter.GetDirectlyHeldThings().ToList(), true });
transportShipField.SetValue(travelingShuttle, transportShipDef);
}
Find.WorldObjects.Add(travelingShuttle);
// Wrap the cargo in ActiveTransporterInfo for AddTransporter
ActiveTransporterInfo transporterInfo = new ActiveTransporterInfo();
transporterInfo.innerContainer.TryAddRangeOrTransfer(
compTransporter.GetDirectlyHeldThings(),
canMergeWithExistingStacks: true,
destroyLeftover: false);
travelingShuttle.AddTransporter(transporterInfo, true);
caravan.Destroy();
return;
}
caravan.Destroy();
// Fallback to drop pods
Log.Warning("[FCP Enlist] Failed to launch shuttle, falling back to drop pods");
TryLaunch(destinationTile, arrivalAction, caravan);
}
}
@@ -901,15 +873,19 @@ public class WorldObjectCompEnlist : WorldObjectComp
}
}
}
private IEnumerable<FloatMenuOption> GetTransportPodsFloatMenuOptionsAt(int tile, Caravan caravan)
private IEnumerable<FloatMenuOption> GetTransportPodsFloatMenuOptionsAt(int tile, Caravan caravan, Action<int, TransportersArrivalAction, Caravan> launchAction = null)
{
// Default to TryLaunch if no launchAction specified (for drop pods)
if (launchAction == null)
launchAction = TryLaunch;
bool anything = false;
if (!Find.World.Impassable(tile) && !Find.WorldObjects.AnySettlementBaseAt(tile) && !Find.WorldObjects.AnySiteAt(tile))
{
anything = true;
yield return new FloatMenuOption("FormCaravanHere".Translate(), delegate
{
TryLaunch(tile, new TransportersArrivalAction_FormCaravan(), caravan);
launchAction(tile, new TransportersArrivalAction_FormCaravan(), caravan);
});
}
//List<WorldObject> worldObjects = Find.WorldObjects.AllWorldObjects;
@@ -928,7 +904,7 @@ public class WorldObjectCompEnlist : WorldObjectComp
{
yield return new FloatMenuOption("TransportPodsContentsWillBeLost".Translate(), delegate
{
TryLaunch(tile, null, caravan);
launchAction(tile, null, caravan);
});
}
}