I am hoping I'll be able to mod a galaxy to be filled with Dyson Spheres, and then let them go to war with 4 Devourer Golems.
Depending on what you mean by "go to war" that should be relatively easy for someone already used to the ExternalCode.
Here's the (in-progress) dyson sphere faction:
using Arcen.AIW2.Core;
using System;
using System.Collections.Generic;
using System.Text;
using Arcen.Universal;
namespace Arcen.AIW2.External
{
public class SpecialFaction_DysonSphere : ISpecialFactionImplementation
{
public static SpecialFaction_DysonSphere Instance;
public SpecialFaction_DysonSphere() { Instance = this; }
public static readonly string DYSON_SPHERE_TAG = "Dyson";
public void SetStartingSideRelationships( WorldSide side )
{
for ( int i = 0; i < World_AIW2.Instance.Sides.Count; i++ )
{
WorldSide otherSide = World_AIW2.Instance.Sides[i];
if ( side == otherSide )
continue;
switch ( otherSide.Type )
{
case WorldSideType.Player:
case WorldSideType.AI:
case WorldSideType.SpecialFaction:
side.MakeHostileTo( otherSide );
otherSide.MakeHostileTo( side );
break;
}
}
}
public ArcenEnumIndexedArray_AIBudgetType<FInt> GetSpendingRatios( WorldSide side )
{
ArcenEnumIndexedArray_AIBudgetType<FInt> result = new ArcenEnumIndexedArray_AIBudgetType<FInt>();
result[AIBudgetType.Reinforcement] = FInt.One;
return result;
}
public bool GetShouldAttackNormallyExcludedTarget( WorldSide side, GameEntity Target )
{
return false;
}
public void SeedStartingEntities( WorldSide side, Galaxy galaxy, ArcenSimContext Context, MapTypeData mapType )
{
galaxy.Mapgen_SeedSpecialEntities( Context, side, DYSON_SPHERE_TAG, 1 );
}
public void DoLongRangePlanning( WorldSide side, ArcenLongTermPlanningContext Context )
{
side.Entities.DoForEntities( GameEntityCategory.Ship, delegate ( GameEntity entity )
{
if ( entity.LongRangePlanningData == null )
return DelReturn.Continue; // if created after the start of this planning cycle, skip
Planet planet = World_AIW2.Instance.GetPlanetByIndex( entity.LongRangePlanningData.CurrentPlanetIndex );
if ( entity.TypeData.GetHasTag( DYSON_SPHERE_TAG ) )
{
// the sphere itself
}
else
{
// something the sphere spawned
}
return DelReturn.Continue;
} );
}
}
}
(the dyson's unit production is defined in xml, like other ships that produce internal "drones" and release them when threatened, though the release conditions may be changed)
And here's the Devourer (done for now, but a bit simplistic) :
using Arcen.AIW2.Core;
using System;
using System.Collections.Generic;
using System.Text;
using Arcen.Universal;
namespace Arcen.AIW2.External
{
public class SpecialFaction_Devourer : ISpecialFactionImplementation
{
public static SpecialFaction_Devourer Instance;
public SpecialFaction_Devourer() { Instance = this; }
public static readonly string DEVOURER_TAG = "Devourer";
public void SetStartingSideRelationships( WorldSide side )
{
for ( int i = 0; i < World_AIW2.Instance.Sides.Count; i++ )
{
WorldSide otherSide = World_AIW2.Instance.Sides[i];
if ( side == otherSide )
continue;
switch ( otherSide.Type )
{
case WorldSideType.Player:
case WorldSideType.AI:
case WorldSideType.SpecialFaction:
side.MakeHostileTo( otherSide );
otherSide.MakeHostileTo( side );
break;
}
}
}
public ArcenEnumIndexedArray_AIBudgetType<FInt> GetSpendingRatios( WorldSide side )
{
ArcenEnumIndexedArray_AIBudgetType<FInt> result = new ArcenEnumIndexedArray_AIBudgetType<FInt>();
result[AIBudgetType.Reinforcement] = FInt.One;
return result;
}
public bool GetShouldAttackNormallyExcludedTarget( WorldSide side, GameEntity Target )
{
return false;
}
public void SeedStartingEntities( WorldSide side, Galaxy galaxy, ArcenSimContext Context, MapTypeData mapType )
{
galaxy.Mapgen_SeedSpecialEntities( Context, side, DEVOURER_TAG, 1 );
}
public void DoLongRangePlanning( WorldSide side, ArcenLongTermPlanningContext Context )
{
Galaxy galaxy = World_AIW2.Instance.SetOfGalaxies.Galaxies[0];
side.Entities.DoForEntities( GameEntityCategory.Ship, delegate ( GameEntity entity )
{
if ( entity.LongRangePlanningData == null )
return DelReturn.Continue; // if created after the start of this planning cycle, skip
if ( !entity.TypeData.GetHasTag( DEVOURER_TAG ) )
return DelReturn.Continue; // if not the Big D, skip (shouldn't happen, but if somebody mods in a reclamator gun for the thing, etc)
if ( entity.LongRangePlanningData.FinalDestinationPlanetIndex != -1 &&
entity.LongRangePlanningData.FinalDestinationPlanetIndex != entity.LongRangePlanningData.CurrentPlanetIndex )
return DelReturn.Continue; // if heading somewhere else, skip
Planet planet = World_AIW2.Instance.GetPlanetByIndex( entity.LongRangePlanningData.CurrentPlanetIndex );
List<GameEntity> threatShipsNotAssignedElsewhere = new List<GameEntity>();
threatShipsNotAssignedElsewhere.Add( entity );
Helper_SendThreatOnRaid( threatShipsNotAssignedElsewhere, side, galaxy, planet, Context );
return DelReturn.Continue;
} );
}
private void Helper_SendThreatOnRaid( List<GameEntity> threatShipsNotAssignedElsewhere, WorldSide worldSide, Galaxy galaxy, Planet planet, ArcenLongTermPlanningContext Context )
{
List<Planet> potentialAttackTargets = new List<Planet>();
List<Planet> planetsToCheckInFlood = new List<Planet>();
planetsToCheckInFlood.Add( planet );
planet.AIPlanning_CheapestRaidPathToHereComesFrom = planet;
for ( int k = 0; k < planetsToCheckInFlood.Count; k++ )
{
Planet floodPlanet = planetsToCheckInFlood[k];
floodPlanet.DoForLinkedNeighbors( delegate ( Planet neighbor )
{
FInt totalCostFromOriginToNeighbor = floodPlanet.AIPlanning_CheapestRaidPathToHereCost + 1;
if ( !potentialAttackTargets.Contains( neighbor ) )
potentialAttackTargets.Add( neighbor );
if ( neighbor.AIPlanning_CheapestRaidPathToHereComesFrom != null &&
neighbor.AIPlanning_CheapestRaidPathToHereCost <= totalCostFromOriginToNeighbor )
return DelReturn.Continue;
neighbor.AIPlanning_CheapestRaidPathToHereComesFrom = floodPlanet;
neighbor.AIPlanning_CheapestRaidPathToHereCost = totalCostFromOriginToNeighbor;
planetsToCheckInFlood.Add( neighbor );
return DelReturn.Continue;
} );
}
if ( potentialAttackTargets.Count <= 0 )
return;
Planet threatTarget = potentialAttackTargets[Context.QualityRandom.Next( 0, potentialAttackTargets.Count )];
List<Planet> path = new List<Planet>();
Planet workingPlanet = threatTarget;
while ( workingPlanet != planet )
{
path.Insert( 0, workingPlanet );
workingPlanet = workingPlanet.AIPlanning_CheapestRaidPathToHereComesFrom;
}
if ( path.Count > 0 )
{
GameCommand command = GameCommand.Create( GameCommandType.SetWormholePath );
for ( int k = 0; k < threatShipsNotAssignedElsewhere.Count; k++ )
command.RelatedEntityIDs.Add( threatShipsNotAssignedElsewhere[k].PrimaryKeyID );
for ( int k = 0; k < path.Count; k++ )
command.RelatedPlanetIndices.Add( path[k].PlanetIndex );
Context.QueueCommandForSendingAtEndOfContext( command );
}
}
}
}
The threat code for the Devourer is more complex than necessary because it's actually a stripped down version of the AI's general-purpose threat code.
So you can change how many are seeded, by just changing the last parameter of the Mapgen_SeedSpecialEntities calls. That will tend to distribute them evenly across the galaxy and at some distance away from the AI and Human homeworlds. If you just want to put one on each planet (for the dysons) you could loop over galaxy.Planets; I forget the call to seed directly from there but I think you have an example somewhere in the mapgen code. I recommend against putting a dyson on the human starting planet
But no reason it wouldn't function, that I know of.
As far as them going to war on each other, their stuff should automatically shoot at each other due to their SetStartingSideRelationships implementations (namely: hostile to every side except self; incidentally, hostile-to-self doesn't work consistently).
They would not seek each other out, but you could put something in the "// something the sphere spawned" branch of the Dyson faction's "DoLongRangePlanning" to make its ships into Devourer-hunters. Basically pull up the Devourer's WorldSide object, side.DoForEntities() and check for the Devourer tag to get your target(s), then decide if you want them to just bum-rush the nearest one, or pool near a sphere until they have a total strength over some threshold first, etc.
And the Devourers could have their DoLongRangePlanning similarly altered to go after planets with Spheres on them.
Those directives would basically only get the respective units to the target
planets. From there the lower level ship-control code is supposed to tell it what point to move towards and what things to shoot at. That said, I don't think there's anything that would stop a GameCommandType.Attack from working.