General Category > AI War II - Modding

Modding dyson spheres, with code samples.

(1/3) > >>

BadgerBadger:
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.

TheVampire100:
Dyson Spheres produce an endless amount of units. Devourer Golems eat an endless amount of units. Sounds to me like a stalemate for eternity.

https://youtu.be/C8M7HopGZX4?t=1m59s

keith.lamothe:

--- Quote from: BadgerBadger on June 20, 2017, 10:20:50 PM ---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.

--- End quote ---
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:


--- Code: ---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;
            } );
        }
    }
}
--- End code ---

(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) :


--- Code: ---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 );
            }
        }
    }
}
--- End code ---

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.

BadgerBadger:
That's some excellent insight into how to do generic AI modding as well. Thanks!

BadgerBadger:
So if I wanted to force, say, every cluster in a Clusters-style map to have a dyson sphere, I'd need to have my mapgen override the SeedSpecialEntities function in Mapgen_Base (I'd cut and paste it entirely except for the changes I'm making). Then I'd use the Galaxy planet list and my knowledge of the ordering of the planet list (lets say that planets 0-10, 11-20, 21-30.... are in their own clusters) to make sure that at least one planet in each cluster has a Dyson Sphere by calling planet.Mapgen_SeedAIEntity()?

Navigation

[0] Message Index

[#] Next page

Go to full version