import settings.game_settings; from empire_ai.EmpireAI import AIController; import AIComponent@ createColonization() from "empire_ai.weasel.Colonization"; import AIComponent@ createResources() from "empire_ai.weasel.Resources"; import AIComponent@ createPlanets() from "empire_ai.weasel.Planets"; import AIComponent@ createSystems() from "empire_ai.weasel.Systems"; import AIComponent@ createFleets() from "empire_ai.weasel.Fleets"; import AIComponent@ createScouting() from "empire_ai.weasel.Scouting"; import AIComponent@ createDevelopment() from "empire_ai.weasel.Development"; import AIComponent@ createDesigns() from "empire_ai.weasel.Designs"; import AIComponent@ createBudget() from "empire_ai.weasel.Budget"; import AIComponent@ createConstruction() from "empire_ai.weasel.Construction"; import AIComponent@ createMilitary() from "empire_ai.weasel.Military"; import AIComponent@ createMovement() from "empire_ai.weasel.Movement"; import AIComponent@ createCreeping() from "empire_ai.weasel.Creeping"; import AIComponent@ createRelations() from "empire_ai.weasel.Relations"; import AIComponent@ createIntelligence() from "empire_ai.weasel.Intelligence"; import AIComponent@ createWar() from "empire_ai.weasel.War"; import AIComponent@ createResearch() from "empire_ai.weasel.Research"; import AIComponent@ createEnergy() from "empire_ai.weasel.Energy"; import IAIComponent@ createDiplomacy() from "empire_ai.weasel.Diplomacy"; import AIComponent@ createConsider() from "empire_ai.weasel.Consider"; import AIComponent@ createOrbitals() from "empire_ai.weasel.Orbitals"; import AIComponent@ createHyperdrive() from "empire_ai.weasel.ftl.Hyperdrive"; import AIComponent@ createGate() from "empire_ai.weasel.ftl.Gate"; import AIComponent@ createFling() from "empire_ai.weasel.ftl.Fling"; import AIComponent@ createSlipstream() from "empire_ai.weasel.ftl.Slipstream"; import AIComponent@ createJumpdrive() from "empire_ai.weasel.ftl.Jumpdrive"; import AIComponent@ createVerdant() from "empire_ai.weasel.race.Verdant"; import AIComponent@ createMechanoid() from "empire_ai.weasel.race.Mechanoid"; import AIComponent@ createStarChildren() from "empire_ai.weasel.race.StarChildren"; import AIComponent@ createExtragalactic() from "empire_ai.weasel.race.Extragalactic"; import AIComponent@ createLinked() from "empire_ai.weasel.race.Linked"; import AIComponent@ createDevout() from "empire_ai.weasel.race.Devout"; import AIComponent@ createAncient() from "empire_ai.weasel.race.Ancient"; import AIComponent@ createInvasion() from "empire_ai.weasel.misc.Invasion"; import bool hasInvasionMap() from "Invasion.InvasionMap"; from buildings import BuildingType; from orbitals import OrbitalModule; import util.formatting; from empire import ai_full_speed; from traits import getTraitID; export IAIComponent, AIComponent, AI; uint GUARD = 0xDEADBEEF; enum AddedComponent { AC_Invasion = 0x1, }; interface IAIComponent : Savable { void set(AI& ai); void setLog(); void setLogCritical(); double getPrevFocus(); void setPrevFocus(double value); void create(); void start(); void tick(double time); void focusTick(double time); void turn(); void save(SaveFile& file); void load(SaveFile& file); void postLoad(AI& ai); void postSave(AI& ai); void loadFinalize(AI& ai); }; class AIComponent : IAIComponent, Savable { AI@ ai; double prevFocus = 0; bool log = false; bool logCritical = false; bool logErrors = true; double getPrevFocus() { return prevFocus; } void setPrevFocus(double value) { prevFocus = value; } void setLog() { log = true; } void setLogCritical() { logCritical = true; } void set(AI& ai) { @this.ai = ai; } void create() {} void start() {} void tick(double time) {} void focusTick(double time) {} void turn() {} void save(SaveFile& file) {} void load(SaveFile& file) {} void postLoad(AI& ai) {} void postSave(AI& ai) {} void loadFinalize(AI& ai) {} }; class ProfileData { double tickPeak = 0.0; double tickAvg = 0.0; double tickCount = 0.0; double focusPeak = 0.0; double focusAvg = 0.0; double focusCount = 0.0; }; final class AIBehavior { //How many focuses we can manage in a tick uint focusPerTick = 2; //The maximum colonizations this AI can do in one turn uint maxColonizations = UINT_MAX; //How many colonizations we're guaranteed to be able to do in one turn regardless of finances uint guaranteeColonizations = 2; //How many colonizations at most we can be doing at once uint maxConcurrentColonizations = UINT_MAX; //Whether this AI will colonize planets in systems owned by someone else // TODO: This should be partially ignored for border systems, so it can try to aggressively expand into the border bool colonizeEnemySystems = false; bool colonizeNeutralOwnedSystems = false; bool colonizeAllySystems = false; //How much this AI values claiming new systems instead of colonizing stuff in its existing ones double weightOutwardExpand = 2.0; //How much money this AI considers a colonization event to cost out of the budget int colonizeBudgetCost = 80; //Whether to do any generic expansion beyond any requests bool colonizeGenericExpand = true; //Latest percentage into a budget cycle that we still allow colonization double colonizeMaxBudgetProgress = 0.66; //Time after initial ownership change that an incomplete colonization is canceled double colonizeFailGraceTime = 100.0; //Time a planet that we failed to colonize is disregarded for colonization double colonizePenalizeTime = 9.0 * 60.0; //Maximum amount of scouting missions that can be performed simultaneously uint maxScoutingMissions = UINT_MAX; //Minimum time after losing vision over a system that we can scout it again double minScoutingInterval = 3.0 * 60.0; //Weight that it gives to exploring things near our empire instead of greedily exploring nearby things double exploreBorderWeight = 2.0; //How long we consider all fleets viable for scouting with double scoutAllTimer = 3.0 * 60.0; //How many scouts we want to have active uint scoutsActive = 2; //How many scanning missions we can do at once uint maxScanningMissions = 1; //Whether to prioritize scouting over scanning if we only have one scout bool prioritizeScoutOverScan = true; //Weights for what to do in generic planet development // Leveling up an existing development focus double focusDevelopWeight = 1.0; // Colonizing a new scalable or high tier to focus on double focusColonizeNewWeight = 4.0; // Colonizing a new high tier resource to import to one of our focuses double focusColonizeHighTierWeight = 1.0; //How many potential designs are evaluated before choosing the best one uint designEvaluateCount = 10; //How long a fleet has to be fully idle before it returns to its stationed system double fleetIdleReturnStationedTime = 60.0; //How long we try to have a fleet be capable of firing before running out of supplies double fleetAimSupplyDuration = 2.0 * 60.0; //How long a potential construction can take at most before we consider it unreasonable double constructionMaxTime = 10.0 * 60.0; //How long a factory has to have been idle for us to consider constructing labor storage double laborStoreIdleTimer = 60.0; //Maximum amount of time worth of labor we want to store in our warehouses double laborStoreMaxFillTime = 60.0 * 10.0; //Whether to use labor to build asteroids in the background bool backgroundBuildAsteroids = true; //Whether to choose the best resource on an asteroid, instead of doing it randomly bool chooseAsteroidResource = true; //Whether to distribute labor to shipyards when planets are idle bool distributeLaborExports = true; //Whether to build a shipyard to consolidate multiple planets of labor where possible bool consolidateLaborExports = true; //Estimate amount of labor spent per point of support ship size double estSizeSupportLabor = 0.25; //Maximum combat fleets we can have in service at once (counts starting fleet(s)) uint maxActiveFleets = UINT_MAX; //How much flagship size we try to make per available money double shipSizePerMoney = 1.0 / 3.5; //How much flagship size we try to make per available labor double shipSizePerLabor = 1.0 / 0.33; //How much maintenance we expect per ship size double maintenancePerShipSize = 2.0; //Minimum percentage increase in size before we decide to retrofit a flagship to be bigger double shipRetrofitThreshold = 0.5; //Whether to retrofit our free starting fleet if appropriate bool retrofitFreeFleets = false; //Minimum percentage of average current flagship size new fleets should be double flagshipBuildMinAvgSize = 1.00; //Minimum game time before we consider constructing new flagships double flagshipBuildMinGameTime = 4.0 * 60.0; //Whether to build factories when we need labor bool buildFactoryForLabor = true; //Whether to build warehouses when we're not using labor bool buildLaborStorage = true; //Whether factories can queue labor resource imports when needed bool allowRequestLaborImports = true; //Whether fleets with ghosted supports attempt to rebuild the ghosts or just clear them bool fleetsRebuildGhosts = true; //When trying to order supports on a fleet, wait for the planet to construct its supports so we can claim them bool supportOrderWaitOnFactory = true; //How much stronger we need to be than a remnant fleet to clear it double remnantOverkillFactor = 1.5; //Whether to allow idle fleets to be sent to clear remnants // Modified by Relations bool remnantAllowArbitraryClear = true; //Whether we should aggressively try to take out enemies bool aggressive = false; //Whether to become aggressive after we get boxed in and can no longer expand anywhere bool aggressiveWhenBoxedIn = false; //Whether we should never declare war ourselves bool passive = false; //Whether to hate human players the most bool biased = false; //How much stronger we need to be than someone to declare war out of hatred double hatredWarOverkill = 0.5; //How much stronger we need to be than someone to try to take them out in an aggressive war double aggressiveWarOverkill = 1.5; //How much stronger we want to be before we attack a system double attackStrengthOverkill = 1.5; //How many battles we can be performing at once uint maxBattles = UINT_MAX; //How much we try to overkill while fighting double battleStrengthOverkill = 1.5; //How many fleets we don't commit to attacks when we're already currently fighting uint battleReserveFleets = 1; //How much extra supply we try to have before starting a capture, to make sure we can actually do it double captureSupplyEstimate = 1.5; //Maximum hop distance we use as staging areas for our attacks int stagingMaxHops = 5; //If our fleet fill is less than this, immediately move back to factory from staging double stagingToFactoryFill = 0.6; //How much ftl is reserved for critical applications double ftlReservePctCritical = 0.25; //How much ftl is reserved to not be used for background applications double ftlReservePctNormal = 0.25; //How many artifacts we consider where to use per focus turn uint artifactFocusConsiderCount = 2; //How long after trying to build a generically requested building we give up double genericBuildExpire = 3.0 * 60.0; //How much the hate in a relationship decays to every minute double hateDecayRate = 0.9; //How much weaker we need to be to even consider surrender double surrenderMinStrength = 0.5; //How many of our total war points have to be taken by an empire for us to surrender double acceptSurrenderRatio = 0.75; double offerSurrenderRatio = 0.5; void setDifficulty(int diff, uint flags) { //This changes the behavior values based on difficulty and flags if(flags & AIF_Aggressive != 0) aggressive = true; if(flags & AIF_Passive != 0) passive = true; if(flags & AIF_Biased != 0) biased = true; //Low difficulties can't colonize as many things at once if(diff <= 0) { maxConcurrentColonizations = 1; guaranteeColonizations = 1; weightOutwardExpand = 0.5; } else if(diff <= 1) { maxConcurrentColonizations = 2; weightOutwardExpand = 1.0; } //Hard AI becomes aggressive when it gets boxed in aggressiveWhenBoxedIn = diff >= 2; //Easy difficulty can't attack and defend at the same time if(diff <= 0) maxBattles = 1; //Low difficulties aren't as good at managing labor if(diff <= 0) { distributeLaborExports = false; consolidateLaborExports = false; buildLaborStorage = false; } else if(diff <= 1) { consolidateLaborExports = false; } //Low difficulties aren't as good at managing fleets if(diff <= 0) { maxActiveFleets = 2; retrofitFreeFleets = true; } //Low difficulties aren't as good at scouting if(diff <= 1) scoutAllTimer = 0.0; //Low difficulties are worse at designing if(diff <= 0) designEvaluateCount = 3; else if(diff <= 1) designEvaluateCount = 8; else designEvaluateCount = 12; //Easy is a bit slow if(diff <= 0) focusPerTick = 1; else if(diff >= 2) focusPerTick = 3; } }; final class AIDefs { const BuildingType@ Factory; const BuildingType@ LaborStorage; const OrbitalModule@ Shipyard; }; final class AI : AIController, Savable { Empire@ empire; AIBehavior behavior; AIDefs defs; int cycleId = -1; uint componentCycle = 0; uint addedComponents = 0; uint majorMask = 0; uint difficulty = 0; uint flags = 0; bool isLoading = false; array components; array profileData; IAIComponent@ fleets; IAIComponent@ budget; IAIComponent@ colonization; IAIComponent@ resources; IAIComponent@ planets; IAIComponent@ systems; IAIComponent@ scouting; IAIComponent@ development; IAIComponent@ designs; IAIComponent@ construction; IAIComponent@ military; IAIComponent@ movement; IAIComponent@ creeping; IAIComponent@ relations; IAIComponent@ intelligence; IAIComponent@ war; IAIComponent@ research; IAIComponent@ energy; IAIComponent@ diplomacy; IAIComponent@ consider; IAIComponent@ orbitals; IAIComponent@ ftl; IAIComponent@ race; IAIComponent@ invasion; void createComponents() { //NOTE: This is also save/load order, so //make sure to add loading logic when changing this list @budget = add(createBudget()); @planets = add(createPlanets()); @resources = add(createResources()); @colonization = add(createColonization()); @systems = add(createSystems()); @fleets = add(createFleets()); @scouting = add(createScouting()); @development = add(createDevelopment()); @designs = add(createDesigns()); @construction = add(createConstruction()); @military = add(createMilitary()); @movement = add(createMovement()); @creeping = add(createCreeping()); @relations = add(createRelations()); @intelligence = add(createIntelligence()); @war = add(createWar()); @research = add(createResearch()); @energy = add(createEnergy()); @diplomacy = add(createDiplomacy()); @consider = add(createConsider()); @orbitals = add(createOrbitals()); //Make FTL component if(empire.hasTrait(getTraitID("Hyperdrive"))) @ftl = add(createHyperdrive()); else if(empire.hasTrait(getTraitID("Gate"))) @ftl = add(createGate()); else if(empire.hasTrait(getTraitID("Fling"))) @ftl = add(createFling()); else if(empire.hasTrait(getTraitID("Slipstream"))) @ftl = add(createSlipstream()); else if(empire.hasTrait(getTraitID("Jumpdrive"))) @ftl = add(createJumpdrive()); //Make racial component if(empire.hasTrait(getTraitID("Verdant"))) @race = add(createVerdant()); else if(empire.hasTrait(getTraitID("Mechanoid"))) @race = add(createMechanoid()); else if(empire.hasTrait(getTraitID("StarChildren"))) @race = add(createStarChildren()); else if(empire.hasTrait(getTraitID("Extragalactic"))) @race = add(createExtragalactic()); else if(empire.hasTrait(getTraitID("Linked"))) @race = add(createLinked()); else if(empire.hasTrait(getTraitID("Devout"))) @race = add(createDevout()); else if(empire.hasTrait(getTraitID("Ancient"))) @race = add(createAncient()); //Misc components if(hasInvasionMap() || addedComponents & AC_Invasion != 0) { @invasion = add(createInvasion()); addedComponents |= AC_Invasion; } //if(empire is playerEmpire) { //log(race); // log(colonization); // log(resources); // log(construction); //} //log(intelligence); //logAll(); logCritical(); profileData.length = components.length; for(uint i = 0, cnt = components.length; i < cnt; ++i) components[i].create(); } void createGeneral() { } void init(Empire& emp, EmpireSettings& settings) { @this.empire = emp; flags = settings.aiFlags; difficulty = settings.difficulty; behavior.setDifficulty(difficulty, flags); createComponents(); } int getDifficultyLevel() { return difficulty; } void load(SaveFile& file) { file >> empire; file >> cycleId; file >> majorMask; file >> difficulty; file >> flags; if(file >= SV_0153) file >> addedComponents; behavior.setDifficulty(difficulty, flags); createComponents(); createGeneral(); uint loadCnt = 0; file >> loadCnt; loadCnt = loadCnt; for(uint i = 0; i < loadCnt; ++i) { double prevFocus = 0; file >> prevFocus; components[i].setPrevFocus(prevFocus); file >> components[i]; uint check = 0; file >> check; if(check != GUARD) error("ERROR: AI Load error detected in component "+addrstr(components[i])+" of "+empire.name); } for(uint i = 0, cnt = components.length; i < cnt; ++i) components[i].postLoad(this); isLoading = true; } void save(SaveFile& file) { file << empire; file << cycleId; file << majorMask; file << difficulty; file << flags; file << addedComponents; uint saveCnt = components.length; file << saveCnt; for(uint i = 0; i < saveCnt; ++i) { file << components[i].getPrevFocus(); file << components[i]; file << GUARD; } for(uint i = 0, cnt = components.length; i < cnt; ++i) components[i].postSave(this); } void log(IAIComponent@ comp) { if(comp is null) return; comp.setLog(); comp.setLogCritical(); } void logCritical() { for(uint i = 0, cnt = components.length; i < cnt; ++i) components[i].setLogCritical(); } void logAll() { for(uint i = 0, cnt = components.length; i < cnt; ++i) { components[i].setLog(); components[i].setLogCritical(); } } IAIComponent@ add(IAIComponent& component) { component.set(this); components.insertLast(component); return component; } void init(Empire& emp) { majorMask = 0; for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) { Empire@ emp = getEmpire(i); if(emp.major) majorMask |= emp.mask; } createGeneral(); } bool hasStarted = false; void tick(Empire& emp, double time) { if(isLoading) { for(uint i = 0, cnt = components.length; i < cnt; ++i) components[i].loadFinalize(this); isLoading = false; hasStarted = true; } else if(!hasStarted) { for(uint i = 0, cnt = components.length; i < cnt; ++i) components[i].start(); hasStarted = true; } else if(emp.Victory == -1) { //Don't do anything when actually defeated return; } //Manage gametime-specific behaviors behavior.colonizeGenericExpand = gameTime >= 6.0 * 60.0; //Find cycled turns int curCycle = emp.BudgetCycleId; if(curCycle != cycleId) { for(uint i = 0, cnt = components.length; i < cnt; ++i) components[i].turn(); cycleId = curCycle; } //Generic ticks double startTime = getExactTime(); for(uint i = 0, cnt = components.length; i < cnt; ++i) { auto@ comp = components[i]; comp.tick(time); double endTime = getExactTime(); //double ms = 1000.0 * (endTime - startTime); startTime = endTime; //auto@ dat = profileData[i]; //dat.tickPeak = max(dat.tickPeak, ms); //dat.tickAvg += ms; //dat.tickCount += 1.0; } //Do focuseds tick on components uint focusCount = behavior.focusPerTick; if(ai_full_speed.value == 1.0) focusCount = max(uint(round((time / 0.25) * behavior.focusPerTick)), behavior.focusPerTick); double allocStart = startTime; for(uint n = 0; n < focusCount; ++n) { componentCycle = (componentCycle+1) % components.length; auto@ focusComp = components[componentCycle]; focusComp.focusTick(gameTime - focusComp.getPrevFocus()); focusComp.setPrevFocus(gameTime); double endTime = getExactTime(); //double ms = 1000.0 * (endTime - startTime); startTime = endTime; if(endTime - allocStart > 4000.0) break; //auto@ dat = profileData[componentCycle]; //dat.focusPeak = max(dat.focusPeak, ms); //dat.focusAvg += ms; //dat.focusCount += 1.0; } } void dumpProfile() { for(uint i = 0, cnt = components.length; i < cnt; ++i) { auto@ c = profileData[i]; print(pad(addrstr(components[i]), 40)+" tick peak "+toString(c.tickPeak,2)+" tick avg "+toString(c.tickAvg/c.tickCount, 2) +" focus peak "+toString(c.focusPeak,2)+" focus avg "+toString(c.focusAvg/c.focusCount, 2)); } } void resetProfile() { for(uint i = 0, cnt = profileData.length; i < cnt; ++i) { auto@ c = profileData[i]; c.tickPeak = 0.0; c.tickAvg = 0.0; c.tickCount = 0.0; c.focusPeak = 0.0; c.focusAvg = 0.0; c.focusCount = 0.0; } } uint get_mask() { return empire.mask; } uint get_teamMask() { //TODO return empire.mask; } uint get_visionMask() { return majorMask & empire.visionMask; } uint get_allyMask() { return empire.mutualDefenseMask | empire.ForcedPeaceMask.value; } uint get_enemyMask() { return empire.hostileMask & majorMask; } uint get_neutralMask() { return majorMask & ~allyMask & ~mask & ~enemyMask; } uint get_otherMask() { return majorMask & ~mask; } string pad(const string& input, uint width) { string str = input; while(str.length < width) str += " "; return str; } void print(const string& info, Object@ related = null, double value = INFINITY, bool flag = false, Empire@ emp = null) { string str = info; if(related !is null) str = pad(related.name, 16)+" | "+str; str = pad("["+empire.index+": "+empire.name+" AI] ", 20)+str; str = formatGameTime(gameTime) + " " + str; if(value != INFINITY) str += " | Value = "+standardize(value, true); if(flag) str += " | FLAGGED On"; if(emp !is null) str += " | Target = "+emp.name; ::print(str); } void debugAI() {} void commandAI(string cmd) {} void aiPing(Empire@ fromEmpire, vec3d position, uint type) {} void pause(Empire& emp) {} void resume(Empire& emp) {} vec3d get_aiFocus() { return vec3d(); } string getOpinionOf(Empire& emp, Empire@ other) { return ""; } int getStandingTo(Empire& emp, Empire@ other) { return 0; } }; AIController@ createWeaselAI() { return AI(); }