diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 190bb4db..e80e4875 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,22 +21,22 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.x + dotnet-version: 9.0.x - name: Restore dependencies run: dotnet restore - name: Setup GO uses: actions/setup-go@v2 with: - go-version: '~1.18.0' + go-version: '~1.23.0' - name: Build TBot win64 - run: dotnet publish TBot -c Release -r win-x64 -o publish/win64/ -p:PublishSingleFile=true --self-contained false + run: dotnet publish TBot -c Release -r win-x64 -o publish/win64/ -p:PublishSingleFile=true --self-contained true - name: Build ogamed win64 run: | - cd ${{ github.workspace }}/ogame/cmd/ogamed + cd ${{ github.workspace }}/ogame/cmd/ogamed-tbot env GOOS=windows GOARCH=amd64 go build -o ogamed.exe cd ${{ github.workspace }} - mv ${{ github.workspace }}/ogame/cmd/ogamed/ogamed.exe ${{ github.workspace }}/publish/win64/ + mv ${{ github.workspace }}/ogame/cmd/ogamed-tbot/ogamed.exe ${{ github.workspace }}/publish/win64/ cp ${{ github.workspace }}/README.md ${{ github.workspace }}/publish/win64/ - name: Package win64 uses: montudor/action-zip@v1 @@ -44,13 +44,13 @@ jobs: args: zip -qq -r TBot-${{ env.SNAP_VERSION }}-win64.zip publish/win64/ - name: Build TBot win32 - run: dotnet publish TBot -c Release -r win-x86 -o publish/win32/ -p:PublishSingleFile=true --self-contained false + run: dotnet publish TBot -c Release -r win-x86 -o publish/win32/ -p:PublishSingleFile=true --self-contained true - name: Build ogamed win32 run: | - cd ${{ github.workspace }}/ogame/cmd/ogamed + cd ${{ github.workspace }}/ogame/cmd/ogamed-tbot env GOOS=windows GOARCH=386 go build -o ogamed.exe cd ${{ github.workspace }}/ - mv ${{ github.workspace }}/ogame/cmd/ogamed/ogamed.exe ${{ github.workspace }}/publish/win32/ + mv ${{ github.workspace }}/ogame/cmd/ogamed-tbot/ogamed.exe ${{ github.workspace }}/publish/win32/ cp ${{ github.workspace }}/README.md ${{ github.workspace }}/publish/win32/ - name: Package win32 uses: montudor/action-zip@v1 @@ -58,13 +58,13 @@ jobs: args: zip -qq -r TBot-${{ env.SNAP_VERSION }}-win32.zip publish/win32/ - name: Build TBot TBot linux64 - run: dotnet publish TBot -c Release -r linux-x64 -o publish/linux64/ -p:PublishSingleFile=true --self-contained false + run: dotnet publish TBot -c Release -r linux-x64 -o publish/linux64/ -p:PublishSingleFile=true --self-contained true - name: Build ogamed linux64 run: | - cd ${{ github.workspace }}/ogame/cmd/ogamed + cd ${{ github.workspace }}/ogame/cmd/ogamed-tbot env GOOS=linux GOARCH=amd64 go build -o ogamed cd ${{ github.workspace }} - mv ${{ github.workspace }}/ogame/cmd/ogamed/ogamed ${{ github.workspace }}/publish/linux64/ + mv ${{ github.workspace }}/ogame/cmd/ogamed-tbot/ogamed ${{ github.workspace }}/publish/linux64/ chmod +x ${{ github.workspace }}/publish/linux64/TBot chmod +x ${{ github.workspace }}/publish/linux64/ogamed cp ${{ github.workspace }}/README.md ${{ github.workspace }}/publish/linux64/ @@ -74,13 +74,13 @@ jobs: args: zip -qq -r TBot-${{ env.SNAP_VERSION }}-linux64.zip publish/linux64/ - name: Build TBot TBot linuxarm - run: dotnet publish TBot -c Release -r linux-arm -o publish/linuxarm/ -p:PublishSingleFile=true --self-contained false + run: dotnet publish TBot -c Release -r linux-arm -o publish/linuxarm/ -p:PublishSingleFile=true --self-contained true - name: Build ogamed linuxarm run: | - cd ${{ github.workspace }}/ogame/cmd/ogamed + cd ${{ github.workspace }}/ogame/cmd/ogamed-tbot env GOOS=linux GOARCH=arm GOARM=7 go build -o ogamed cd ${{ github.workspace }} - mv ${{ github.workspace }}/ogame/cmd/ogamed/ogamed ${{ github.workspace }}/publish/linuxarm/ + mv ${{ github.workspace }}/ogame/cmd/ogamed-tbot/ogamed ${{ github.workspace }}/publish/linuxarm/ chmod +x ${{ github.workspace }}/publish/linuxarm/TBot chmod +x ${{ github.workspace }}/publish/linuxarm/ogamed cp ${{ github.workspace }}/README.md ${{ github.workspace }}/publish/linuxarm/ @@ -90,13 +90,13 @@ jobs: args: zip -qq -r TBot-${{ env.SNAP_VERSION }}-linuxarm.zip publish/linuxarm/ - name: Build TBot TBot linuxarm64 - run: dotnet publish TBot -c Release -r linux-arm64 -o publish/linuxarm64/ -p:PublishSingleFile=true --self-contained false + run: dotnet publish TBot -c Release -r linux-arm64 -o publish/linuxarm64/ -p:PublishSingleFile=true --self-contained true - name: Build ogamed linuxarm64 run: | - cd ${{ github.workspace }}/ogame/cmd/ogamed + cd ${{ github.workspace }}/ogame/cmd/ogamed-tbot env GOOS=linux GOARCH=arm64 go build -o ogamed cd ${{ github.workspace }} - mv ${{ github.workspace }}/ogame/cmd/ogamed/ogamed ${{ github.workspace }}/publish/linuxarm64/ + mv ${{ github.workspace }}/ogame/cmd/ogamed-tbot/ogamed ${{ github.workspace }}/publish/linuxarm64/ chmod +x ${{ github.workspace }}/publish/linuxarm64/TBot chmod +x ${{ github.workspace }}/publish/linuxarm64/ogamed cp ${{ github.workspace }}/README.md ${{ github.workspace }}/publish/linuxarm64/ @@ -106,13 +106,13 @@ jobs: args: zip -qq -r TBot-${{ env.SNAP_VERSION }}-linuxarm64.zip publish/linuxarm64/ - name: Build TBot TBot osx64 - run: dotnet publish TBot -c Release -r osx-x64 -o publish/osx64/ -p:PublishSingleFile=true --self-contained false + run: dotnet publish TBot -c Release -r osx-x64 -o publish/osx64/ -p:PublishSingleFile=true --self-contained true - name: Build ogamed osx64 run: | - cd ${{ github.workspace }}/ogame/cmd/ogamed + cd ${{ github.workspace }}/ogame/cmd/ogamed-tbot env GOOS=darwin GOARCH=amd64 go build -o ogamed cd ${{ github.workspace }} - mv ${{ github.workspace }}/ogame/cmd/ogamed/ogamed ${{ github.workspace }}/publish/osx64/ + mv ${{ github.workspace }}/ogame/cmd/ogamed-tbot/ogamed ${{ github.workspace }}/publish/osx64/ chmod +x ${{ github.workspace }}/publish/osx64/TBot chmod +x ${{ github.workspace }}/publish/osx64/ogamed cp ${{ github.workspace }}/README.md ${{ github.workspace }}/publish/osx64/ @@ -122,13 +122,13 @@ jobs: args: zip -qq -r TBot-${{ env.SNAP_VERSION }}-osx64.zip publish/osx64/ - name: Build TBot TBot osx-arm64 - run: dotnet publish TBot -c Release -r osx-arm64 -o publish/osx-arm64/ -p:PublishSingleFile=true --self-contained false + run: dotnet publish TBot -c Release -r osx-arm64 -o publish/osx-arm64/ -p:PublishSingleFile=true --self-contained true - name: Build ogamed osx-arm64 run: | - cd ${{ github.workspace }}/ogame/cmd/ogamed - env GOOS=darwin GOARCH=amd64 go build -o ogamed + cd ${{ github.workspace }}/ogame/cmd/ogamed-tbot + env GOOS=darwin GOARCH=arm64 go build -o ogamed cd ${{ github.workspace }} - mv ${{ github.workspace }}/ogame/cmd/ogamed/ogamed ${{ github.workspace }}/publish/osx-arm64/ + mv ${{ github.workspace }}/ogame/cmd/ogamed-tbot/ogamed ${{ github.workspace }}/publish/osx-arm64/ chmod +x ${{ github.workspace }}/publish/osx-arm64/TBot chmod +x ${{ github.workspace }}/publish/osx-arm64/ogamed cp ${{ github.workspace }}/README.md ${{ github.workspace }}/publish/osx-arm64/ diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index eac931ff..afc88037 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -22,22 +22,22 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.x + dotnet-version: 9.0.x - name: Restore dependencies run: dotnet restore - name: Setup GO uses: actions/setup-go@v2 with: - go-version: '~1.18.0' + go-version: '~1.23.0' - name: Build TBot win64 - run: dotnet publish TBot -c Release -r win-x64 -o publish/win64/ -p:PublishSingleFile=true --self-contained false + run: dotnet publish TBot -c Release -r win-x64 -o publish/win64/ -p:PublishSingleFile=true --self-contained true - name: Build ogamed win64 run: | - cd ${{ github.workspace }}/ogame/cmd/ogamed + cd ${{ github.workspace }}/ogame/cmd/ogamed-tbot env GOOS=windows GOARCH=amd64 go build -o ogamed.exe cd ${{ github.workspace }} - mv ${{ github.workspace }}/ogame/cmd/ogamed/ogamed.exe ${{ github.workspace }}/publish/win64/ + mv ${{ github.workspace }}/ogame/cmd/ogamed-tbot/ogamed.exe ${{ github.workspace }}/publish/win64/ - name: Package win64 uses: montudor/action-zip@v1 with: @@ -49,13 +49,13 @@ jobs: path: ${{ github.workspace }}/TBot-${{ env.SNAP_VERSION }}-win64.zip - name: Build TBot win32 - run: dotnet publish TBot -c Release -r win-x86 -o publish/win32/ -p:PublishSingleFile=true --self-contained false + run: dotnet publish TBot -c Release -r win-x86 -o publish/win32/ -p:PublishSingleFile=true --self-contained true - name: Build ogamed win32 run: | - cd ${{ github.workspace }}/ogame/cmd/ogamed + cd ${{ github.workspace }}/ogame/cmd/ogamed-tbot env GOOS=windows GOARCH=386 go build -o ogamed.exe cd ${{ github.workspace }}/ - mv ${{ github.workspace }}/ogame/cmd/ogamed/ogamed.exe ${{ github.workspace }}/publish/win32/ + mv ${{ github.workspace }}/ogame/cmd/ogamed-tbot/ogamed.exe ${{ github.workspace }}/publish/win32/ - name: Package win32 uses: montudor/action-zip@v1 with: @@ -67,13 +67,13 @@ jobs: path: ${{ github.workspace }}/TBot-${{ env.SNAP_VERSION }}-win32.zip - name: Build TBot linux64 - run: dotnet publish TBot -c Release -r linux-x64 -o publish/linux64/ -p:PublishSingleFile=true --self-contained false + run: dotnet publish TBot -c Release -r linux-x64 -o publish/linux64/ -p:PublishSingleFile=true --self-contained true - name: Build ogamed linux64 run: | - cd ${{ github.workspace }}/ogame/cmd/ogamed + cd ${{ github.workspace }}/ogame/cmd/ogamed-tbot env GOOS=linux GOARCH=amd64 go build -o ogamed cd ${{ github.workspace }} - mv ${{ github.workspace }}/ogame/cmd/ogamed/ogamed ${{ github.workspace }}/publish/linux64/ + mv ${{ github.workspace }}/ogame/cmd/ogamed-tbot/ogamed ${{ github.workspace }}/publish/linux64/ chmod +x ${{ github.workspace }}/publish/linux64/TBot chmod +x ${{ github.workspace }}/publish/linux64/ogamed - name: Package linux64 @@ -87,13 +87,13 @@ jobs: path: ${{ github.workspace }}/TBot-${{ env.SNAP_VERSION }}-linux64.zip - name: Build TBot linuxarm - run: dotnet publish TBot -c Release -r linux-arm -o publish/linuxarm/ -p:PublishSingleFile=true --self-contained false + run: dotnet publish TBot -c Release -r linux-arm -o publish/linuxarm/ -p:PublishSingleFile=true --self-contained true - name: Build ogamed linuxarm run: | - cd ${{ github.workspace }}/ogame/cmd/ogamed + cd ${{ github.workspace }}/ogame/cmd/ogamed-tbot env GOOS=linux GOARCH=arm GOARM=7 go build -o ogamed cd ${{ github.workspace }} - mv ${{ github.workspace }}/ogame/cmd/ogamed/ogamed ${{ github.workspace }}/publish/linuxarm/ + mv ${{ github.workspace }}/ogame/cmd/ogamed-tbot/ogamed ${{ github.workspace }}/publish/linuxarm/ chmod +x ${{ github.workspace }}/publish/linuxarm/TBot chmod +x ${{ github.workspace }}/publish/linuxarm/ogamed - name: Package linuxarm @@ -107,13 +107,13 @@ jobs: path: ${{ github.workspace }}/TBot-${{ env.SNAP_VERSION }}-linuxarm.zip - name: Build TBot linuxarm64 - run: dotnet publish TBot -c Release -r linux-arm64 -o publish/linuxarm64/ -p:PublishSingleFile=true --self-contained false + run: dotnet publish TBot -c Release -r linux-arm64 -o publish/linuxarm64/ -p:PublishSingleFile=true --self-contained true - name: Build ogamed linuxarm64 run: | - cd ${{ github.workspace }}/ogame/cmd/ogamed + cd ${{ github.workspace }}/ogame/cmd/ogamed-tbot env GOOS=linux GOARCH=arm64 go build -o ogamed cd ${{ github.workspace }} - mv ${{ github.workspace }}/ogame/cmd/ogamed/ogamed ${{ github.workspace }}/publish/linuxarm64/ + mv ${{ github.workspace }}/ogame/cmd/ogamed-tbot/ogamed ${{ github.workspace }}/publish/linuxarm64/ chmod +x ${{ github.workspace }}/publish/linuxarm64/TBot chmod +x ${{ github.workspace }}/publish/linuxarm64/ogamed - name: Package linuxarm64 @@ -127,13 +127,13 @@ jobs: path: ${{ github.workspace }}/TBot-${{ env.SNAP_VERSION }}-linuxarm64.zip - name: Build TBot osx64 - run: dotnet publish TBot -c Release -r osx-x64 -o publish/osx64/ -p:PublishSingleFile=true --self-contained false + run: dotnet publish TBot -c Release -r osx-x64 -o publish/osx64/ -p:PublishSingleFile=true --self-contained true - name: Build ogamed osx64 run: | - cd ${{ github.workspace }}/ogame/cmd/ogamed + cd ${{ github.workspace }}/ogame/cmd/ogamed-tbot env GOOS=darwin GOARCH=amd64 go build -o ogamed cd ${{ github.workspace }} - mv ${{ github.workspace }}/ogame/cmd/ogamed/ogamed ${{ github.workspace }}/publish/osx64/ + mv ${{ github.workspace }}/ogame/cmd/ogamed-tbot/ogamed ${{ github.workspace }}/publish/osx64/ chmod +x ${{ github.workspace }}/publish/osx64/TBot chmod +x ${{ github.workspace }}/publish/osx64/ogamed - name: Package osx64 @@ -147,13 +147,13 @@ jobs: path: ${{ github.workspace }}/TBot-${{ env.SNAP_VERSION }}-osx64.zip - name: Build TBot osx-arm64 - run: dotnet publish TBot -c Release -r osx-arm64 -o publish/osx-arm64/ -p:PublishSingleFile=true --self-contained false + run: dotnet publish TBot -c Release -r osx-arm64 -o publish/osx-arm64/ -p:PublishSingleFile=true --self-contained true - name: Build ogamed osx-arm64 run: | - cd ${{ github.workspace }}/ogame/cmd/ogamed + cd ${{ github.workspace }}/ogame/cmd/ogamed-tbot env GOOS=darwin GOARCH=arm64 go build -o ogamed cd ${{ github.workspace }} - mv ${{ github.workspace }}/ogame/cmd/ogamed/ogamed ${{ github.workspace }}/publish/osx-arm64/ + mv ${{ github.workspace }}/ogame/cmd/ogamed-tbot/ogamed ${{ github.workspace }}/publish/osx-arm64/ chmod +x ${{ github.workspace }}/publish/osx-arm64/TBot chmod +x ${{ github.workspace }}/publish/osx-arm64/ogamed - name: Package osx-arm64 diff --git a/.gitmodules b/.gitmodules index 02037af9..58de3448 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "ogame"] path = ogame - url = https://github.com/ogame-tbot-net/ogame.git - branch = develop_v11.9 + url = https://github.com/0xE232FE/ogame.mod.git + branch = develop-ogamed-tbot diff --git a/TBot.Common/Logging/LogSender.cs b/TBot.Common/Logging/LogSender.cs index 2c0592e0..5683270f 100644 --- a/TBot.Common/Logging/LogSender.cs +++ b/TBot.Common/Logging/LogSender.cs @@ -13,6 +13,7 @@ public enum LogSender { Brain, //Brain Features AutoCargo, + AutoDefence, AutoMine, AutoRepatriate, AutoResearch, diff --git a/TBot.Common/Logging/Sinks/TBotColoredConsoleSink.cs b/TBot.Common/Logging/Sinks/TBotColoredConsoleSink.cs index 80dfe81a..aa47dc65 100644 --- a/TBot.Common/Logging/Sinks/TBotColoredConsoleSink.cs +++ b/TBot.Common/Logging/Sinks/TBotColoredConsoleSink.cs @@ -37,6 +37,7 @@ public void Emit(LogEvent logEvent) { ConsoleColor consoleColor = sender switch { LogSender.Brain => ConsoleColor.Blue, LogSender.AutoCargo => ConsoleColor.Blue, + LogSender.AutoDefence => ConsoleColor.Blue, LogSender.AutoMine => ConsoleColor.Blue, LogSender.AutoRepatriate => ConsoleColor.Blue, LogSender.AutoResearch => ConsoleColor.Blue, diff --git a/TBot.Common/TBot.Common.csproj b/TBot.Common/TBot.Common.csproj index 3f439b8a..f2a0a7fe 100644 --- a/TBot.Common/TBot.Common.csproj +++ b/TBot.Common/TBot.Common.csproj @@ -1,18 +1,18 @@ - net6.0 + net9.0 enable enable - - - + + + - - + + diff --git a/TBot.Ogame.Infrastructure/Enums/Feature.cs b/TBot.Ogame.Infrastructure/Enums/Feature.cs index d9fdd956..555fd575 100644 --- a/TBot.Ogame.Infrastructure/Enums/Feature.cs +++ b/TBot.Ogame.Infrastructure/Enums/Feature.cs @@ -21,6 +21,7 @@ public enum Feature { BrainLifeformAutoMine = 12, BrainLifeformAutoResearch = 13, AutoDiscovery = 14, + BrainAutobuildDefence = 15, BrainCelestialAutoMine = 101, BrainCelestialLifeformAutoMine = 102, diff --git a/TBot.Ogame.Infrastructure/Enums/Features.cs b/TBot.Ogame.Infrastructure/Enums/Features.cs index 32f220ba..5d7dc956 100644 --- a/TBot.Ogame.Infrastructure/Enums/Features.cs +++ b/TBot.Ogame.Infrastructure/Enums/Features.cs @@ -9,6 +9,7 @@ public static class Features { public static readonly List AllFeatures = new List() { Feature.Defender, Feature.BrainAutobuildCargo, + Feature.BrainAutobuildDefence, Feature.BrainAutoRepatriate, Feature.BrainAutoMine, Feature.BrainLifeformAutoMine, diff --git a/TBot.Ogame.Infrastructure/Enums/SendFleetCode.cs b/TBot.Ogame.Infrastructure/Enums/SendFleetCode.cs index 79b83a8e..45df11af 100644 --- a/TBot.Ogame.Infrastructure/Enums/SendFleetCode.cs +++ b/TBot.Ogame.Infrastructure/Enums/SendFleetCode.cs @@ -9,6 +9,7 @@ public enum SendFleetCode : int { GenericError = 0, AfterSleepTime = -1, NotEnoughSlots = -2, - QuickerToWaitForProduction = -3 + QuickerToWaitForProduction = -3, + NotEnoughRessources = -4 } } diff --git a/TBot.Ogame.Infrastructure/IOgameService.cs b/TBot.Ogame.Infrastructure/IOgameService.cs index 1c52da84..230e9811 100644 --- a/TBot.Ogame.Infrastructure/IOgameService.cs +++ b/TBot.Ogame.Infrastructure/IOgameService.cs @@ -95,6 +95,8 @@ void Initialize(Credentials credentials, Task SendMessage(int playerID, string message); Task SetUserAgent(string userAgent); Task SolveCaptcha(string challengeID, int answer); - Task AbandonCelestial(Celestial celestial); + Task AbandonCelestial(Celestial celestial); + Task> GetPositionsAvailableForDiscoveryFleet(Celestial celestial, Coordinate coordinate); + Task GetAvailableDiscoveries(Celestial celestial); } } diff --git a/TBot.Ogame.Infrastructure/Models/AuctionResourcesValue.cs b/TBot.Ogame.Infrastructure/Models/AuctionResourcesValue.cs index 7046124c..6f01d3ab 100644 --- a/TBot.Ogame.Infrastructure/Models/AuctionResourcesValue.cs +++ b/TBot.Ogame.Infrastructure/Models/AuctionResourcesValue.cs @@ -15,7 +15,7 @@ public class AuctionResourcesValue { [JsonProperty("name")] public string Name { get; set; } - public int otherPlanetId { get; set; } = 0; + public int? otherPlanetId { get; set; } = 0; public override string ToString() { string outStr = "["; diff --git a/TBot.Ogame.Infrastructure/Models/Defences.cs b/TBot.Ogame.Infrastructure/Models/Defences.cs index 926cca06..55823f5a 100644 --- a/TBot.Ogame.Infrastructure/Models/Defences.cs +++ b/TBot.Ogame.Infrastructure/Models/Defences.cs @@ -19,15 +19,179 @@ public class Defences { public long AntiBallisticMissiles { get; set; } public long InterplanetaryMissiles { get; set; } - public int GetAmount(Buildables defence) { - int output = 0; - foreach (PropertyInfo prop in GetType().GetProperties()) { - if (prop.Name == defence.ToString()) { - output = (int) prop.GetValue(this); + public Defences( + long rocketlauncher = 0, + long lightlaser = 0, + long heavylaser = 0, + long gausscannon = 0, + long ioncannon = 0, + long plasmaturret = 0, + long smallshielddome = 0, + long largeshielddome = 0, + long antiballisticmissiles = 0, + long interplanetarymissiles = 0 + ) { + RocketLauncher = rocketlauncher; + LightLaser = lightlaser; + HeavyLaser = heavylaser; + GaussCannon = gausscannon; + IonCannon = ioncannon; + PlasmaTurret = plasmaturret; + SmallShieldDome = smallshielddome; + LargeShieldDome = largeshielddome; + AntiBallisticMissiles = antiballisticmissiles; + InterplanetaryMissiles = interplanetarymissiles; + } + public bool IsEmpty() { + return RocketLauncher == 0 + && LightLaser == 0 + && HeavyLaser == 0 + && GaussCannon == 0 + && IonCannon == 0 + && PlasmaTurret == 0 + && SmallShieldDome == 0 + && LargeShieldDome == 0 + && AntiBallisticMissiles == 0 + && InterplanetaryMissiles == 0; + } + public long GetDefencePoints() { + long output = 0; + output += RocketLauncher * 20; + output += LightLaser * 20; + output += HeavyLaser * 80; + output += GaussCannon * 370; + output += IonCannon * 80; + output += PlasmaTurret * 1300; + output += SmallShieldDome * 200; + output += LargeShieldDome * 1000; + output += AntiBallisticMissiles * 100; + output += InterplanetaryMissiles * 245; + return output / 10; + } + public Resources GetDefenceCost() { + Resources output = new(); + output.Metal = RocketLauncher * 2000 + LightLaser * 1500 + HeavyLaser * 6000 + GaussCannon * 20000 + IonCannon * 3000 + PlasmaTurret * 50000 + SmallShieldDome * 10000 + LargeShieldDome * 50000 + AntiBallisticMissiles * 2000 + InterplanetaryMissiles * 5000; + output.Crystal = RocketLauncher * 1000 + LightLaser * 500 + HeavyLaser * 2000 + GaussCannon * 15000 + IonCannon * 1000 + PlasmaTurret * 50000 + SmallShieldDome * 5000 + LargeShieldDome * 25000 + AntiBallisticMissiles * 2000 + InterplanetaryMissiles * 2500; + output.Deuterium = RocketLauncher * 0 + LightLaser * 0 + HeavyLaser * 0 + GaussCannon * 0 + IonCannon * 0 + PlasmaTurret * 2000 + SmallShieldDome * 0 + LargeShieldDome * 10000 + AntiBallisticMissiles * 0 + InterplanetaryMissiles * 1000; + return output; + } + public Defences Add(String buildable, long quantity) { + foreach (PropertyInfo prop in this.GetType().GetProperties()) { + if (prop.Name == buildable) { + prop.SetValue(this, (long) prop.GetValue(this) + quantity); + } + } + return this; + } + + public Defences Remove(String buildable, int quantity) { + foreach (PropertyInfo prop in this.GetType().GetProperties()) { + if (prop.Name == buildable) { + long val = (long) prop.GetValue(this); + if (val >= quantity) + prop.SetValue(this, val); + else + prop.SetValue(this, 0); + } + } + return this; + } + + public long GetAmount(Defences buildable) { + foreach (PropertyInfo prop in this.GetType().GetProperties()) { + if (prop.Name == buildable.ToString()) { + return (long) prop.GetValue(this); + } + } + return 0; + } + + public void SetAmount(Defences buildable, long number) { + foreach (PropertyInfo prop in this.GetType().GetProperties()) { + if (prop.Name == buildable.ToString()) { + prop.SetValue(this, number); + return; } } + } + + public bool HasAtLeast(Defences defences, long times = 1) { + foreach (PropertyInfo prop in this.GetType().GetProperties()) { + if ((long) prop.GetValue(this) * times < (long) prop.GetValue(defences)) { + return false; + } + } + return true; + } + + public override string ToString() { + string output = ""; + foreach (PropertyInfo prop in this.GetType().GetProperties()) { + if ((long) prop.GetValue(this) == 0) + continue; + output += $"{prop.Name}: {prop.GetValue(this)}; "; + } return output; } - } + public Defences Sum(Defences defencesToSum) { + Defences output = new(); + output.RocketLauncher = RocketLauncher + defencesToSum.RocketLauncher; + output.LightLaser = LightLaser + defencesToSum.LightLaser; + output.HeavyLaser = HeavyLaser + defencesToSum.HeavyLaser; + output.GaussCannon = GaussCannon + defencesToSum.GaussCannon; + output.IonCannon = IonCannon + defencesToSum.IonCannon; + output.PlasmaTurret = PlasmaTurret + defencesToSum.PlasmaTurret; + output.SmallShieldDome = SmallShieldDome + defencesToSum.SmallShieldDome; + output.LargeShieldDome = LargeShieldDome + defencesToSum.LargeShieldDome; + output.AntiBallisticMissiles = AntiBallisticMissiles + defencesToSum.AntiBallisticMissiles; + output.InterplanetaryMissiles = InterplanetaryMissiles + defencesToSum.InterplanetaryMissiles; -} + return output; + } + public Defences Difference(Defences defencesToSubtract) { + Defences output = new(); + output.RocketLauncher = RocketLauncher - defencesToSubtract.RocketLauncher; + if (output.RocketLauncher < 0) + output.RocketLauncher = 0; + output.LightLaser = LightLaser - defencesToSubtract.LightLaser; + if (output.LightLaser < 0) + output.LightLaser = 0; + output.HeavyLaser = HeavyLaser - defencesToSubtract.HeavyLaser; + if (output.HeavyLaser < 0) + output.HeavyLaser = 0; + output.GaussCannon = GaussCannon - defencesToSubtract.GaussCannon; + if (output.GaussCannon < 0) + output.GaussCannon = 0; + output.IonCannon = IonCannon - defencesToSubtract.IonCannon; + if (output.IonCannon < 0) + output.IonCannon = 0; + output.PlasmaTurret = PlasmaTurret - defencesToSubtract.PlasmaTurret; + if (output.PlasmaTurret < 0) + output.PlasmaTurret = 0; + output.SmallShieldDome = SmallShieldDome - defencesToSubtract.SmallShieldDome; + if (output.SmallShieldDome < 0) + output.SmallShieldDome = 0; + output.LargeShieldDome = LargeShieldDome - defencesToSubtract.LargeShieldDome; + if (output.LargeShieldDome < 0) + output.LargeShieldDome = 0; + output.AntiBallisticMissiles = AntiBallisticMissiles - defencesToSubtract.AntiBallisticMissiles; + if (output.AntiBallisticMissiles < 0) + output.AntiBallisticMissiles = 0; + output.InterplanetaryMissiles = InterplanetaryMissiles - defencesToSubtract.InterplanetaryMissiles; + if (output.InterplanetaryMissiles < 0) + output.InterplanetaryMissiles = 0; + + return output; + } + public Dictionary GetDefenceTypesWithAmount() { + Dictionary defenceTypes = new(); + foreach (var prop in this.GetType().GetProperties()) { + Buildables buildable = (Buildables) Enum.Parse(typeof(Buildables), prop.Name); + long amount = (long) prop.GetValue(this); + if (amount > 0) + defenceTypes.Add(buildable, amount); + } + return defenceTypes; + } + } +} \ No newline at end of file diff --git a/TBot.Ogame.Infrastructure/Models/EspionageReport.cs b/TBot.Ogame.Infrastructure/Models/EspionageReport.cs index 051f5a75..88298908 100644 --- a/TBot.Ogame.Infrastructure/Models/EspionageReport.cs +++ b/TBot.Ogame.Infrastructure/Models/EspionageReport.cs @@ -112,8 +112,8 @@ public override string ToString() { /// Get whether or not the scanned planet has any defence (either ships or defence) against an attack. /// /// Returns true if the target is defenceless, false otherwise. - public bool IsDefenceless() { - if (HasDefensesInformation && HasFleetInformation) { + public bool IsDefenceless(bool isUsingProbes = false) { + if (HasDefensesInformation && HasFleetInformation && !isUsingProbes) { return LightFighter == null && HeavyFighter == null && Cruiser == null @@ -135,6 +135,29 @@ public bool IsDefenceless() { && PlasmaTurret == null && SmallShieldDome == null && LargeShieldDome == null; + } else if (HasDefensesInformation && HasFleetInformation && isUsingProbes) { + return SolarSatellite == null + && LightFighter == null + && HeavyFighter == null + && Cruiser == null + && Battleship == null + && Battlecruiser == null + && Bomber == null + && Destroyer == null + && Deathstar == null + && SmallCargo == null + && LargeCargo == null + && Recycler == null + && Reaper == null + && Pathfinder == null + && RocketLauncher == null + && LightLaser == null + && HeavyLaser == null + && GaussCannon == null + && IonCannon == null + && PlasmaTurret == null + && SmallShieldDome == null + && LargeShieldDome == null; } return false; } diff --git a/TBot.Ogame.Infrastructure/Models/GalaxyInfo.cs b/TBot.Ogame.Infrastructure/Models/GalaxyInfo.cs index 148062cc..f55ac372 100644 --- a/TBot.Ogame.Infrastructure/Models/GalaxyInfo.cs +++ b/TBot.Ogame.Infrastructure/Models/GalaxyInfo.cs @@ -3,12 +3,14 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using TBot.Ogame.Infrastructure.Enums; namespace TBot.Ogame.Infrastructure.Models { public class GalaxyInfo { public int Galaxy { get; set; } public int System { get; set; } public List Planets { get; set; } + public List AvailableMissions { get; set; } public ExpeditionDebris ExpeditionDebris { get; set; } } } diff --git a/TBot.Ogame.Infrastructure/Models/LFBonuses.cs b/TBot.Ogame.Infrastructure/Models/LFBonuses.cs index 42e0b6bb..c3f96945 100644 --- a/TBot.Ogame.Infrastructure/Models/LFBonuses.cs +++ b/TBot.Ogame.Infrastructure/Models/LFBonuses.cs @@ -8,6 +8,32 @@ namespace TBot.Ogame.Infrastructure.Models { public class LFBonuses { + + public LFBonusesResources LfResourceBonuses { get; set; } + public LFBonusesCharacterClasses CharacterClassesBonuses { get; set; } + public Dictionary LfShipBonuses { get; set; } + public Dictionary CostTimeBonuses { get; set; } + public LFBonusesMisc MiscBonuses { get; set; } + public Dictionary LfShipBonusesInt { + get { + if (LfShipBonuses == null) + return new Dictionary(); + return LfShipBonuses + .Where(kvp => int.TryParse(kvp.Key, out _)) + .ToDictionary(kvp => int.Parse(kvp.Key), kvp => kvp.Value); + } + } + public Dictionary CostTimeBonusesInt { + get { + if (CostTimeBonuses == null) + return new Dictionary(); + return CostTimeBonuses + .Where(kvp => int.TryParse(kvp.Key, out _)) + .ToDictionary(kvp => int.Parse(kvp.Key), kvp => kvp.Value); + } + } + + public LFBonusesProduction Production { get; set; } public LFBonusesExpeditions Expeditions { get; set; } public LFBonusesDens Dens { get; set; } @@ -29,6 +55,12 @@ public class LFBonuses { public float InactivesLoot { get; set; } public LFBonuses() { + LfResourceBonuses = new LFBonusesResources(); + CharacterClassesBonuses = new LFBonusesCharacterClasses(); + LfShipBonuses = new Dictionary(); + CostTimeBonuses = new Dictionary(); + MiscBonuses = new LFBonusesMisc(); + Production = new LFBonusesProduction(); Expeditions = new LFBonusesExpeditions(); Dens = new LFBonusesDens(); @@ -44,24 +76,24 @@ public LFBonuses() { public float GetShipCargoBonus(Buildables buildable) { float bonusCargo = 0; - if (this != null && Ships != null && Ships.Count > 0 && Ships.ContainsKey((int) buildable)) { - bonusCargo = Ships.GetValueOrDefault((int) buildable).Cargo; + if (this != null && LfShipBonusesInt != null && LfShipBonusesInt.Count > 0 && LfShipBonusesInt.ContainsKey((int) buildable)) { + bonusCargo = LfShipBonusesInt.GetValueOrDefault((int) buildable).CargoCapacity; } return bonusCargo; } public float GetShipSpeedBonus(Buildables buildable) { float bonusSpeed = 0; - if (this != null && Ships != null && Ships.Count > 0 && Ships.ContainsKey((int) buildable)) { - bonusSpeed = Ships.GetValueOrDefault((int) buildable).Speed; + if (this != null && LfShipBonusesInt != null && LfShipBonusesInt.Count > 0 && LfShipBonusesInt.ContainsKey((int) buildable)) { + bonusSpeed = LfShipBonusesInt.GetValueOrDefault((int) buildable).Speed; } return bonusSpeed; } public float GetShipConsumptionBonus(Buildables buildable) { float bonusCons = 0; - if (this != null && Ships != null && Ships.Count > 0 && Ships.ContainsKey((int) buildable)) { - bonusCons = Ships.GetValueOrDefault((int) buildable).Consumption; + if (this != null && LfShipBonusesInt != null && LfShipBonusesInt.Count > 0 && LfShipBonusesInt.ContainsKey((int) buildable)) { + bonusCons = LfShipBonusesInt.GetValueOrDefault((int) buildable).FuelConsumption; } return bonusCons; } diff --git a/TBot.Ogame.Infrastructure/Models/LFBonusesBase.cs b/TBot.Ogame.Infrastructure/Models/LFBonusesBase.cs index 8cb9e220..229c92f0 100644 --- a/TBot.Ogame.Infrastructure/Models/LFBonusesBase.cs +++ b/TBot.Ogame.Infrastructure/Models/LFBonusesBase.cs @@ -9,9 +9,9 @@ public class LFBonusesBase { public float Cost { get; set; } public float Duration { get; set; } - public LFBonusesBase() { - Cost = 0; - Duration = 0; + public LFBonusesBase(float cost = 0, float duration = 0) { + Cost = cost; + Duration = duration; } } } diff --git a/TBot.Ogame.Infrastructure/Models/LFBonusesCharacterClasses.cs b/TBot.Ogame.Infrastructure/Models/LFBonusesCharacterClasses.cs new file mode 100644 index 00000000..fef8ab6e --- /dev/null +++ b/TBot.Ogame.Infrastructure/Models/LFBonusesCharacterClasses.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TBot.Ogame.Infrastructure.Models { + public class LFBonusesCharacterClasses { + public float Characterclasses3 { get; set; } + public LFBonusesCharacterClasses(float characterclasses3 = 0) { + Characterclasses3 = characterclasses3; + } + } +} diff --git a/TBot.Ogame.Infrastructure/Models/LFBonusesCrawlers.cs b/TBot.Ogame.Infrastructure/Models/LFBonusesCrawlers.cs index 3d97aa6d..3dc5987c 100644 --- a/TBot.Ogame.Infrastructure/Models/LFBonusesCrawlers.cs +++ b/TBot.Ogame.Infrastructure/Models/LFBonusesCrawlers.cs @@ -10,10 +10,10 @@ public class LFBonusesCrawlers { public float Production { get; set; } public float Number { get; set; } - public LFBonusesCrawlers() { - EnergyReduction = 0; - Production = 0; - Number = 0; + public LFBonusesCrawlers(float energyReduction = 0, float production = 0, float number = 0) { + EnergyReduction = energyReduction; + Production = production; + Number = number; } } } diff --git a/TBot.Ogame.Infrastructure/Models/LFBonusesDens.cs b/TBot.Ogame.Infrastructure/Models/LFBonusesDens.cs index e9155fd2..351c843d 100644 --- a/TBot.Ogame.Infrastructure/Models/LFBonusesDens.cs +++ b/TBot.Ogame.Infrastructure/Models/LFBonusesDens.cs @@ -10,10 +10,10 @@ public class LFBonusesDens { public float Crystal { get; set; } public float Deuterium { get; set; } - public LFBonusesDens() { - Metal = 0; - Crystal = 0; - Deuterium = 0; + public LFBonusesDens(float metal = 0, float crystal = 0, float deuterium = 0) { + Metal = metal; + Crystal = crystal; + Deuterium = deuterium; } } } diff --git a/TBot.Ogame.Infrastructure/Models/LFBonusesExpeditions.cs b/TBot.Ogame.Infrastructure/Models/LFBonusesExpeditions.cs index 40a44d95..e2f4cbe8 100644 --- a/TBot.Ogame.Infrastructure/Models/LFBonusesExpeditions.cs +++ b/TBot.Ogame.Infrastructure/Models/LFBonusesExpeditions.cs @@ -14,14 +14,14 @@ public class LFBonusesExpeditions { public float Slots { get; set; } public float LessEnemies {get; set;} - public LFBonusesExpeditions() { - Ships = 0; - Resources = 0; - Speed = 0; - DarkMatter = 0; - FleetLoss = 0; - Slots = 0; - LessEnemies = 0; + public LFBonusesExpeditions(float ships = 0, float resources = 0, float speed = 0, float darkmatter = 0, float fleetloss = 0, float slots = 0, float lessenemies = 0) { + Ships = ships; + Resources = resources; + Speed = speed; + DarkMatter = darkmatter; + FleetLoss = fleetloss; + Slots = slots; + LessEnemies = lessenemies; } } } diff --git a/TBot.Ogame.Infrastructure/Models/LFBonusesMisc.cs b/TBot.Ogame.Infrastructure/Models/LFBonusesMisc.cs new file mode 100644 index 00000000..8013f286 --- /dev/null +++ b/TBot.Ogame.Infrastructure/Models/LFBonusesMisc.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TBot.Ogame.Infrastructure.Models { + public class LFBonusesMisc { + public float PhalanxRange { get; set; } + + public LFBonusesMisc(float phalanxrange = 0) { + PhalanxRange = phalanxrange; + } + } +} diff --git a/TBot.Ogame.Infrastructure/Models/LFBonusesMoons.cs b/TBot.Ogame.Infrastructure/Models/LFBonusesMoons.cs index 9cb7d919..42e00f1b 100644 --- a/TBot.Ogame.Infrastructure/Models/LFBonusesMoons.cs +++ b/TBot.Ogame.Infrastructure/Models/LFBonusesMoons.cs @@ -10,10 +10,10 @@ public class LFBonusesMoons { public float Size { get; set; } public float Chance { get; set; } - public LFBonusesMoons() { - Fields = 0; - Size = 0; - Chance = 0; + public LFBonusesMoons(float fields = 0, float size = 0, float chance = 0) { + Fields = fields; + Size = size; + Chance = chance; } } } diff --git a/TBot.Ogame.Infrastructure/Models/LFBonusesProduction.cs b/TBot.Ogame.Infrastructure/Models/LFBonusesProduction.cs index 992dbf1e..3461e0de 100644 --- a/TBot.Ogame.Infrastructure/Models/LFBonusesProduction.cs +++ b/TBot.Ogame.Infrastructure/Models/LFBonusesProduction.cs @@ -13,13 +13,13 @@ public class LFBonusesProduction { public float Food { get; set; } public float Population { get; set; } - public LFBonusesProduction() { - Metal = 0; - Crystal = 0; - Deuterium = 0; - Energy = 0; - Food = 0; - Population = 0; + public LFBonusesProduction(float metal = 0, float crystal = 0, float deuterium = 0, float energy = 0, float food = 0, float population = 0) { + Metal = metal; + Crystal = crystal; + Deuterium = deuterium; + Energy = energy; + Food = food; + Population = population; } } } diff --git a/TBot.Ogame.Infrastructure/Models/LFBonusesResources.cs b/TBot.Ogame.Infrastructure/Models/LFBonusesResources.cs new file mode 100644 index 00000000..1c8c57ab --- /dev/null +++ b/TBot.Ogame.Infrastructure/Models/LFBonusesResources.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TBot.Ogame.Infrastructure.Models { + public class LFBonusesResources { + public float ResourcesExpedition { get; set; } + public LFBonusesResources(float resourcesexpedition = 0) { + ResourcesExpedition = resourcesexpedition; + } + } +} diff --git a/TBot.Ogame.Infrastructure/Models/LFBonusesShip.cs b/TBot.Ogame.Infrastructure/Models/LFBonusesShip.cs index 25bd6a48..bc266456 100644 --- a/TBot.Ogame.Infrastructure/Models/LFBonusesShip.cs +++ b/TBot.Ogame.Infrastructure/Models/LFBonusesShip.cs @@ -6,22 +6,22 @@ namespace TBot.Ogame.Infrastructure.Models { public class LFBonusesShip { - public float Armour { get; set; } - public float Shield { get; set; } - public float Weapon { get; set; } - public float Cargo { get; set; } - public float Speed { get; set; } - public float Consumption { get; set; } - public float Duration { get; set; } + public int ID { get; set; } + public float StructuralIntegrity { get; set; } + public float ShieldPower { get; set; } + public float WeaponPower { get; set; } + public float Speed { get; set; } + public float CargoCapacity { get; set; } + public float FuelConsumption { get; set; } - public LFBonusesShip() { - Armour = 0; - Shield = 0; - Weapon = 0; - Cargo = 0; - Speed = 0; - Consumption = 0; - Duration = 0; + public LFBonusesShip(int id = -1, float armour = 0, float shield = 0, float weapon = 0, float speed = 0, float cargo = 0, float consumption = 0) { + ID = id; + StructuralIntegrity = armour; + ShieldPower = shield; + WeaponPower = weapon; + Speed = speed; + CargoCapacity = cargo; + FuelConsumption = consumption; } } } diff --git a/TBot.Ogame.Infrastructure/Models/LFBuildings.cs b/TBot.Ogame.Infrastructure/Models/LFBuildings.cs index 01368af3..e4c03c6d 100644 --- a/TBot.Ogame.Infrastructure/Models/LFBuildings.cs +++ b/TBot.Ogame.Infrastructure/Models/LFBuildings.cs @@ -66,6 +66,58 @@ public class LFBuildings { public int ShipManufacturingHall { get; set; } public int SupraRefractor { get; set; } + public LFBuildings(int lifeformType = 0, int residentialsector = 0, int biospherefarm = 0, int researchcentre = 0, int academyofsciences = 0, int neurocalibrationcentre = 0, int highenergysmelting = 0, int foodsilo = 0, int fusionpoweredproduction = 0, int skyscraper = 0, int biotechlab = 0, int metropolis = 0, int planetaryshield = 0, int meditationenclave = 0, int crystalfarm = 0, int runetechnologium = 0, int runeforge = 0, int oriktorium = 0, int magmaforge = 0, int disruptionchamber = 0, int megalith = 0, int crystalrefinery = 0, int deuteriumsynthesiser = 0, int mineralresearchcentre = 0, int advancedrecyclingplant = 0, int assemblyline = 0, int fusioncellfactory = 0, int roboticsresearchcentre = 0, int updatenetwork = 0, int quantumcomputercentre = 0, int automatisedassemblycentre = 0, int highperformancetransformer = 0, int microchipassemblyline = 0, int productionassemblyhall = 0, int highperformancesynthesiser = 0, int chipmassproduction = 0, int nanorepairbots = 0, int sanctuary = 0, int antimattercondenser = 0, int vortexchamber = 0, int hallsofrealisation = 0, int forumoftranscendence = 0, int antimatterconvector = 0, int cloninglaboratory = 0, int chrysalisaccelerator = 0, int biomodifier = 0, int psionicmodulator = 0, int shipmanufacturinghall = 0, int suprarefractor = 0) { + LifeformType = lifeformType; + ResidentialSector = residentialsector; + BiosphereFarm = biospherefarm; + ResearchCentre = researchcentre; + AcademyOfSciences = academyofsciences; + NeuroCalibrationCentre = neurocalibrationcentre; + HighEnergySmelting = highenergysmelting; + FoodSilo = foodsilo; + FusionPoweredProduction = fusionpoweredproduction; + Skyscraper = skyscraper; + BiotechLab = biotechlab; + Metropolis = metropolis; + PlanetaryShield = planetaryshield; + MeditationEnclave = meditationenclave; + CrystalFarm = crystalfarm; + RuneTechnologium = runetechnologium; + RuneForge = runeforge; + Oriktorium = oriktorium; + MagmaForge = magmaforge; + DisruptionChamber = disruptionchamber; + Megalith = megalith; + CrystalRefinery = crystalrefinery; + DeuteriumSynthesiser = deuteriumsynthesiser; + MineralResearchCentre = mineralresearchcentre; + AdvancedRecyclingPlant = advancedrecyclingplant; + AssemblyLine = assemblyline; + FusionCellFactory = fusioncellfactory; + RoboticsResearchCentre = roboticsresearchcentre; + UpdateNetwork = updatenetwork; + QuantumComputerCentre = quantumcomputercentre; + AutomatisedAssemblyCentre = automatisedassemblycentre; + HighPerformanceTransformer = highperformancetransformer; + MicrochipAssemblyLine = microchipassemblyline; + ProductionAssemblyHall = productionassemblyhall; + HighPerformanceSynthesiser = highperformancesynthesiser; + ChipMassProduction = chipmassproduction; + NanoRepairBots = nanorepairbots; + Sanctuary = sanctuary; + AntimatterCondenser = antimattercondenser; + VortexChamber = vortexchamber; + HallsOfRealisation = hallsofrealisation; + ForumOfTranscendence = forumoftranscendence; + AntimatterConvector = antimatterconvector; + CloningLaboratory = cloninglaboratory; + ChrysalisAccelerator = chrysalisaccelerator; + BioModifier = biomodifier; + PsionicModulator = psionicmodulator; + ShipManufacturingHall = shipmanufacturinghall; + SupraRefractor = suprarefractor; + } + public int GetLevel(LFBuildables building) { int output = 0; foreach (PropertyInfo prop in GetType().GetProperties()) { diff --git a/TBot.Ogame.Infrastructure/Models/LFTechs.cs b/TBot.Ogame.Infrastructure/Models/LFTechs.cs index 1adbf90e..c2cf47bf 100644 --- a/TBot.Ogame.Infrastructure/Models/LFTechs.cs +++ b/TBot.Ogame.Infrastructure/Models/LFTechs.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Text; @@ -9,96 +10,174 @@ namespace TBot.Ogame.Infrastructure.Models { public class LFTechs { //Humans - public int IntergalacticEnvoys { get; set; } - public int HighPerformanceExtractors { get; set; } - public int FusionDrives { get; set; } - public int StealthFieldGenerator { get; set; } - public int OrbitalDen { get; set; } - public int ResearchAI { get; set; } - public int HighPerformanceTerraformer { get; set; } - public int EnhancedProductionTechnologies { get; set; } - public int LightFighterMkII { get; set; } - public int CruiserMkII { get; set; } - public int ImprovedLabTechnology { get; set; } - public int PlasmaTerraformer { get; set; } - public int LowTemperatureDrives { get; set; } - public int BomberMkII { get; set; } - public int DestroyerMkII { get; set; } - public int BattlecruiserMkII { get; set; } - public int RobotAssistants { get; set; } - public int Supercomputer { get; set; } + public int? IntergalacticEnvoys { get; set; } + public int? HighPerformanceExtractors { get; set; } + public int? FusionDrives { get; set; } + public int? StealthFieldGenerator { get; set; } + public int? OrbitalDen { get; set; } + public int? ResearchAI { get; set; } + public int? HighPerformanceTerraformer { get; set; } + public int? EnhancedProductionTechnologies { get; set; } + public int? LightFighterMkII { get; set; } + public int? CruiserMkII { get; set; } + public int? ImprovedLabTechnology { get; set; } + public int? PlasmaTerraformer { get; set; } + public int? LowTemperatureDrives { get; set; } + public int? BomberMkII { get; set; } + public int? DestroyerMkII { get; set; } + public int? BattlecruiserMkII { get; set; } + public int? RobotAssistants { get; set; } + public int? Supercomputer { get; set; } //Rocktal - public int VolcanicBatteries { get; set; } - public int AcousticScanning { get; set; } - public int HighEnergyPumpSystems { get; set; } - public int CargoHoldExpansionCivilianShips { get; set; } - public int MagmaPoweredProduction { get; set; } - public int GeothermalPowerPlants { get; set; } - public int DepthSounding { get; set; } - public int IonCrystalEnhancementHeavyFighter { get; set; } - public int ImprovedStellarator { get; set; } - public int HardenedDiamondDrillHeads { get; set; } - public int SeismicMiningTechnology { get; set; } - public int MagmaPoweredPumpSystems { get; set; } - public int IonCrystalModules { get; set; } - public int OptimisedSiloConstructionMethod { get; set; } - public int DiamondEnergyTransmitter { get; set; } - public int ObsidianShieldReinforcement { get; set; } - public int RuneShields { get; set; } - public int RocktalCollectorEnhancement { get; set; } + public int? VolcanicBatteries { get; set; } + public int? AcousticScanning { get; set; } + public int? HighEnergyPumpSystems { get; set; } + public int? CargoHoldExpansionCivilianShips { get; set; } + public int? MagmaPoweredProduction { get; set; } + public int? GeothermalPowerPlants { get; set; } + public int? DepthSounding { get; set; } + public int? IonCrystalEnhancementHeavyFighter { get; set; } + public int? ImprovedStellarator { get; set; } + public int? HardenedDiamondDrillHeads { get; set; } + public int? SeismicMiningTechnology { get; set; } + public int? MagmaPoweredPumpSystems { get; set; } + public int? IonCrystalModules { get; set; } + public int? OptimisedSiloConstructionMethod { get; set; } + public int? DiamondEnergyTransmitter { get; set; } + public int? ObsidianShieldReinforcement { get; set; } + public int? RuneShields { get; set; } + public int? RocktalCollectorEnhancement { get; set; } //Mechas - public int CatalyserTechnology { get; set; } - public int PlasmaDrive { get; set; } - public int EfficiencyModule { get; set; } - public int DepotAI { get; set; } - public int GeneralOverhaulLightFighter { get; set; } - public int AutomatedTransportLines { get; set; } - public int ImprovedDroneAI { get; set; } - public int ExperimentalRecyclingTechnology { get; set; } - public int GeneralOverhaulCruiser { get; set; } - public int SlingshotAutopilot { get; set; } - public int HighTemperatureSuperconductors { get; set; } - public int GeneralOverhaulBattleship { get; set; } - public int ArtificialSwarmIntelligence { get; set; } - public int GeneralOverhaulBattlecruiser { get; set; } - public int GeneralOverhaulBomber { get; set; } - public int GeneralOverhaulDestroyer { get; set; } - public int ExperimentalWeaponsTechnology { get; set; } - public int MechanGeneralEnhancement { get; set; } + public int? CatalyserTechnology { get; set; } + public int? PlasmaDrive { get; set; } + public int? EfficiencyModule { get; set; } + public int? DepotAI { get; set; } + public int? GeneralOverhaulLightFighter { get; set; } + public int? AutomatedTransportLines { get; set; } + public int? ImprovedDroneAI { get; set; } + public int? ExperimentalRecyclingTechnology { get; set; } + public int? GeneralOverhaulCruiser { get; set; } + public int? SlingshotAutopilot { get; set; } + public int? HighTemperatureSuperconductors { get; set; } + public int? GeneralOverhaulBattleship { get; set; } + public int? ArtificialSwarmIntelligence { get; set; } + public int? GeneralOverhaulBattlecruiser { get; set; } + public int? GeneralOverhaulBomber { get; set; } + public int? GeneralOverhaulDestroyer { get; set; } + public int? ExperimentalWeaponsTechnology { get; set; } + public int? MechanGeneralEnhancement { get; set; } //Kaelesh - public int HeatRecovery { get; set; } - public int SulphideProcess { get; set; } - public int PsionicNetwork { get; set; } - public int TelekineticTractorBeam { get; set; } - public int EnhancedSensorTechnology { get; set; } - public int NeuromodalCompressor { get; set; } - public int NeuroInterface { get; set; } - public int InterplanetaryAnalysisNetwork { get; set; } - public int OverclockingHeavyFighter { get; set; } - public int TelekineticDrive { get; set; } - public int SixthSense { get; set; } - public int Psychoharmoniser { get; set; } - public int EfficientSwarmIntelligence { get; set; } - public int OverclockingLargeCargo { get; set; } - public int GravitationSensors { get; set; } - public int OverclockingBattleship { get; set; } - public int PsionicShieldMatrix { get; set; } - public int KaeleshDiscovererEnhancement { get; set; } + public int? HeatRecovery { get; set; } + public int? SulphideProcess { get; set; } + public int? PsionicNetwork { get; set; } + public int? TelekineticTractorBeam { get; set; } + public int? EnhancedSensorTechnology { get; set; } + public int? NeuromodalCompressor { get; set; } + public int? NeuroInterface { get; set; } + public int? InterplanetaryAnalysisNetwork { get; set; } + public int? OverclockingHeavyFighter { get; set; } + public int? TelekineticDrive { get; set; } + public int? SixthSense { get; set; } + public int? Psychoharmoniser { get; set; } + public int? EfficientSwarmIntelligence { get; set; } + public int? OverclockingLargeCargo { get; set; } + public int? GravitationSensors { get; set; } + public int? OverclockingBattleship { get; set; } + public int? PsionicShieldMatrix { get; set; } + public int? KaeleshDiscovererEnhancement { get; set; } + + public LFTechs(int? intergalacticenvoys = null, int? highperformanceextractors = null, int? fusiondrives = null, int? stealthfieldgenerator = null, int? orbitalden = null, int? researchai = null, int? highperformanceterraformer = null, int? enhancedproductiontechnologies = null, int? lightfightermkii = null, int? cruisermkii = null, int? improvedlabtechnology = null, int? plasmaterraformer = null, int? lowtemperaturedrives = null, int? bombermkii = null, int? destroyermkii = null, int? battlecruisermkii = null, int? robotassistants = null, int? supercomputer = null, int? volcanicbatteries = null, int? acousticscanning = null, int? highenergypumpsystems = null, int? cargoholdexpansioncivilianships = null, int? magmapoweredproduction = null, int? geothermalpowerplants = null, int? depthsounding = null, int? ioncrystalenhancementheavyfighter = null, int? improvedstellarator = null, int? hardeneddiamonddrillheads = null, int? seismicminingtechnology = null, int? magmapoweredpumpsystems = null, int? ioncrystalmodules = null, int? optimisedsiloconstructionmethod = null, int? diamondenergytransmitter = null, int? obsidianshieldreinforcement = null, int? runeshields = null, int? rocktalcollectorenhancement = null, int? catalysertechnology = null, int? plasmadrive = null, int? efficiencymodule = null, int? depotai = null, int? generaloverhaullightfighter = null, int? automatedtransportlines = null, int? improveddroneai = null, int? experimentalrecyclingtechnology = null, int? generaloverhaulcruiser = null, int? slingshotautopilot = null, int? hightemperaturesuperconductors = null, int? generaloverhaulbattleship = null, int? artificialswarmintelligence = null, int? generaloverhaulbattlecruiser = null, int? generaloverhaulbomber = null, int? generaloverhauldestroyer = null, int? experimentalweaponstechnology = null, int? mechangeneralenhancement = null, int? heatrecovery = null, int? sulphideprocess = null, int? psionicnetwork = null, int? telekinetictractorbeam = null, int? enhancedsensortechnology = null, int? neuromodalcompressor = null, int? neurointerface = null, int? interplanetaryanalysisnetwork = null, int? overclockingheavyfighter = null, int? telekineticdrive = null, int? sixthsense = null, int? psychoharmoniser = null, int? efficientswarmintelligence = null, int? overclockinglargecargo = null, int? gravitationsensors = null, int? overclockingbattleship = null, int? psionicshieldmatrix = null, int? kaeleshdiscovererenhancement = null) { + IntergalacticEnvoys = intergalacticenvoys ?? 0; + HighPerformanceExtractors = highperformanceextractors ?? 0; + FusionDrives = fusiondrives ?? 0; + StealthFieldGenerator = stealthfieldgenerator ?? 0; + OrbitalDen = orbitalden ?? 0; + ResearchAI = researchai ?? 0; + HighPerformanceTerraformer = highperformanceterraformer ?? 0; + EnhancedProductionTechnologies = enhancedproductiontechnologies ?? 0; + LightFighterMkII = lightfightermkii ?? 0; + CruiserMkII = cruisermkii ?? 0; + ImprovedLabTechnology = improvedlabtechnology ?? 0; + PlasmaTerraformer = plasmaterraformer ?? 0; + LowTemperatureDrives = lowtemperaturedrives ?? 0; + BomberMkII = bombermkii ?? 0; + DestroyerMkII = destroyermkii ?? 0; + BattlecruiserMkII = battlecruisermkii ?? 0; + RobotAssistants = robotassistants ?? 0; + Supercomputer = supercomputer ?? 0; + VolcanicBatteries = volcanicbatteries ?? 0; + AcousticScanning = acousticscanning ?? 0; + HighEnergyPumpSystems = highenergypumpsystems ?? 0; + CargoHoldExpansionCivilianShips = cargoholdexpansioncivilianships ?? 0; + MagmaPoweredProduction = magmapoweredproduction ?? 0; + GeothermalPowerPlants = geothermalpowerplants ?? 0; + DepthSounding = depthsounding ?? 0; + IonCrystalEnhancementHeavyFighter = ioncrystalenhancementheavyfighter ?? 0; + ImprovedStellarator = improvedstellarator ?? 0; + HardenedDiamondDrillHeads = hardeneddiamonddrillheads ?? 0; + SeismicMiningTechnology = seismicminingtechnology ?? 0; + MagmaPoweredPumpSystems = magmapoweredpumpsystems ?? 0; + IonCrystalModules = ioncrystalmodules ?? 0; + OptimisedSiloConstructionMethod = optimisedsiloconstructionmethod ?? 0; + DiamondEnergyTransmitter = diamondenergytransmitter ?? 0; + ObsidianShieldReinforcement = obsidianshieldreinforcement ?? 0; + RuneShields = runeshields ?? 0; + RocktalCollectorEnhancement = rocktalcollectorenhancement ?? 0; + CatalyserTechnology = catalysertechnology ?? 0; + PlasmaDrive = plasmadrive ?? 0; + EfficiencyModule = efficiencymodule ?? 0; + DepotAI = depotai ?? 0; + GeneralOverhaulLightFighter = generaloverhaullightfighter ?? 0; + AutomatedTransportLines = automatedtransportlines ?? 0; + ImprovedDroneAI = improveddroneai ?? 0; + ExperimentalRecyclingTechnology = experimentalrecyclingtechnology ?? 0; + GeneralOverhaulCruiser = generaloverhaulcruiser ?? 0; + SlingshotAutopilot = slingshotautopilot ?? 0; + HighTemperatureSuperconductors = hightemperaturesuperconductors ?? 0; + GeneralOverhaulBattleship = generaloverhaulbattleship ?? 0; + ArtificialSwarmIntelligence = artificialswarmintelligence ?? 0; + GeneralOverhaulBattlecruiser = generaloverhaulbattlecruiser ?? 0; + GeneralOverhaulBomber = generaloverhaulbomber ?? 0; + GeneralOverhaulDestroyer = generaloverhauldestroyer ?? 0; + ExperimentalWeaponsTechnology = experimentalweaponstechnology ?? 0; + MechanGeneralEnhancement = mechangeneralenhancement ?? 0; + HeatRecovery = heatrecovery ?? 0; + SulphideProcess = sulphideprocess ?? 0; + PsionicNetwork = psionicnetwork ?? 0; + TelekineticTractorBeam = telekinetictractorbeam ?? 0; + EnhancedSensorTechnology = enhancedsensortechnology ?? 0; + NeuromodalCompressor = neuromodalcompressor ?? 0; + NeuroInterface = neurointerface ?? 0; + InterplanetaryAnalysisNetwork = interplanetaryanalysisnetwork ?? 0; + OverclockingHeavyFighter = overclockingheavyfighter ?? 0; + TelekineticDrive = telekineticdrive ?? 0; + SixthSense = sixthsense ?? 0; + Psychoharmoniser = psychoharmoniser ?? 0; + EfficientSwarmIntelligence = efficientswarmintelligence ?? 0; + OverclockingLargeCargo = overclockinglargecargo ?? 0; + GravitationSensors = gravitationsensors ?? 0; + OverclockingBattleship = overclockingbattleship ?? 0; + PsionicShieldMatrix = psionicshieldmatrix ?? 0; + KaeleshDiscovererEnhancement = kaeleshdiscovererenhancement ?? 0; + } public int GetLevel(LFTechno building) { int output = 0; foreach (PropertyInfo prop in GetType().GetProperties()) { if (prop.Name == building.ToString()) { - output = (int) prop.GetValue(this); + object value = prop.GetValue(this, null); + if (value is not null) { + output = (int) prop.GetValue(this, null); + } } } return output; } - public LFTechs SetLevel(LFTechno buildable, int level) { + public LFTechs SetLevel(LFTechno buildable, int? level) { foreach (PropertyInfo prop in this.GetType().GetProperties()) { if (prop.Name == buildable.ToString()) { prop.SetValue(this, level); diff --git a/TBot.Ogame.Infrastructure/Models/RankSlotsPriority.cs b/TBot.Ogame.Infrastructure/Models/RankSlotsPriority.cs new file mode 100644 index 00000000..4cadc455 --- /dev/null +++ b/TBot.Ogame.Infrastructure/Models/RankSlotsPriority.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Text.RegularExpressions; +using TBot.Ogame.Infrastructure.Enums; + +namespace TBot.Ogame.Infrastructure.Models { + public class RankSlotsPriority { + public RankSlotsPriority(Feature feature = Feature.Null, int rank = int.MinValue, bool active = false, int maxSlots = 0, int slotsUsed = 0) { + Rank = rank < 1 ? int.MinValue : (int) rank; + Active = active; + MaxSlots = maxSlots; + SlotsUsed = slotsUsed; + Feature = feature; + } + public int Rank { get; set; } + public bool Active { get; set; } + public int MaxSlots { get; set; } + public int SlotsUsed { get; set; } + public Feature Feature { get; set; } + public bool HasPriorityOn(RankSlotsPriority feature) { + return this.Rank < feature.Rank; + } + public override string ToString() { + return $"Rank {Rank} -- {Feature.ToString()} is {Active}, using {SlotsUsed}/{MaxSlots}"; + } + } + +} \ No newline at end of file diff --git a/TBot.Ogame.Infrastructure/Models/Transport.cs b/TBot.Ogame.Infrastructure/Models/Transport.cs new file mode 100644 index 00000000..9ec16a2a --- /dev/null +++ b/TBot.Ogame.Infrastructure/Models/Transport.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Text.RegularExpressions; +using TBot.Ogame.Infrastructure.Enums; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace TBot.Ogame.Infrastructure.Models { + public class TransportSettings { + public TransportSettings(bool active = false, Buildables cargoType = Buildables.SmallCargo, long deutToLeave = 0, bool roundResources = true, bool sendToTheMoonIfPossible = true, Celestial origin = null, long maxSlots = 0, bool checkMoonOrPlanetFirst = false, bool multipleTransports = false, MultipleOrigins multipleOrigins = null) { + Active = active; + CargoType = cargoType; + DeutToLeave = deutToLeave; + RoundResources = roundResources; + SendToTheMoonIfPossible = sendToTheMoonIfPossible; + Origin = origin ?? new(); + MaxSlots = maxSlots; + CheckMoonOrPlanetFirst = checkMoonOrPlanetFirst; + DoMultipleTransportIsNotEnoughShipButSamePosition = multipleTransports; + MultipleOrigin = multipleOrigins ?? new(); + } + public bool Active { get; set; } + public Buildables CargoType { get; set; } + public long DeutToLeave { get; set; } + public bool RoundResources { get; set; } + public bool SendToTheMoonIfPossible { get; set; } + public Celestial Origin { get; set; } + public long MaxSlots { get; set; } + public bool CheckMoonOrPlanetFirst { get; set; } + public bool DoMultipleTransportIsNotEnoughShipButSamePosition { get; set; } + public MultipleOrigins MultipleOrigin { get; set; } + } + + public class MultipleOrigins { + public MultipleOrigins(bool active = false, bool onlyFromMoons = true, long minimumResourcesToSend = 0, bool priorityToP = true, List exclude = null) { + Active = active; + OnlyFromMoons = onlyFromMoons; + MinimumResourcesToSend = minimumResourcesToSend; + PriorityToProximityOverQuantity = priorityToP; + Exclude = exclude ?? new(); + } + public bool Active { get; set; } + public bool OnlyFromMoons { get; set; } + public long MinimumResourcesToSend { get; set; } + public bool PriorityToProximityOverQuantity { get; set; } + public List Exclude { get; set; } + } + +} \ No newline at end of file diff --git a/TBot.Ogame.Infrastructure/OgameService.cs b/TBot.Ogame.Infrastructure/OgameService.cs index 5edb9e08..744c44f0 100644 --- a/TBot.Ogame.Infrastructure/OgameService.cs +++ b/TBot.Ogame.Infrastructure/OgameService.cs @@ -441,7 +441,8 @@ public async Task GetLFBuildings(Celestial celestial) { } public async Task GetLFBonuses(Celestial celestial) { - return await GetAsync($"/bot/planets/{celestial.ID}/lifeform-bonuses"); + //return await GetAsync($"/bot/planets/{celestial.ID}/lifeform-bonuses"); + return await GetAsync($"/bot/lfbonuses"); } public async Task GetLFTechs(Celestial celestial) { @@ -562,34 +563,38 @@ public async Task CancelResearch(Celestial celestial) { } public async Task SendFleet(Celestial origin, Ships ships, Coordinate destination, Missions mission, decimal speed, Resources payload) { - List> parameters = new List>(); - parameters.Add(new KeyValuePair("galaxy", destination.Galaxy.ToString())); - parameters.Add(new KeyValuePair("system", destination.System.ToString())); - parameters.Add(new KeyValuePair("position", destination.Position.ToString())); - parameters.Add(new KeyValuePair("type", ((int) destination.Type).ToString())); - - var request = new RestRequest { - Resource = $"/bot/planets/{origin.ID}/send-fleet", - Method = Method.Post, - }; - foreach (PropertyInfo prop in ships.GetType().GetProperties()) { - long qty = (long) prop.GetValue(ships, null); - if (qty == 0) - continue; - if (Enum.TryParse(prop.Name, out Buildables buildable)) { - parameters.Add(new KeyValuePair("ships", (int) buildable + "," + prop.GetValue(ships, null))); + try { + List> parameters = new List>(); + parameters.Add(new KeyValuePair("galaxy", destination.Galaxy.ToString())); + parameters.Add(new KeyValuePair("system", destination.System.ToString())); + parameters.Add(new KeyValuePair("position", destination.Position.ToString())); + parameters.Add(new KeyValuePair("type", ((int)destination.Type).ToString())); + + var request = new RestRequest { + Resource = $"/bot/planets/{origin.ID}/send-fleet", + Method = Method.Post, + }; + foreach (PropertyInfo prop in ships.GetType().GetProperties()) { + long qty = (long) prop.GetValue(ships, null); + if (qty == 0) + continue; + if (Enum.TryParse(prop.Name, out Buildables buildable)) { + parameters.Add(new KeyValuePair("ships", (int) buildable + "," + prop.GetValue(ships, null))); + } } - } - parameters.Add(new KeyValuePair("mission", ((int) mission).ToString())); + parameters.Add(new KeyValuePair("mission", ((int) mission).ToString())); - parameters.Add(new KeyValuePair("speed", speed.ToString())); - parameters.Add(new KeyValuePair("metal", payload.Metal.ToString())); - parameters.Add(new KeyValuePair("crystal", payload.Crystal.ToString())); - parameters.Add(new KeyValuePair("deuterium", payload.Deuterium.ToString())); - parameters.Add(new KeyValuePair("food", payload.Food.ToString())); + parameters.Add(new KeyValuePair("speed", speed.ToString())); + parameters.Add(new KeyValuePair("metal", payload.Metal.ToString())); + parameters.Add(new KeyValuePair("crystal", payload.Crystal.ToString())); + parameters.Add(new KeyValuePair("deuterium", payload.Deuterium.ToString())); + parameters.Add(new KeyValuePair("food", payload.Food.ToString())); - return await PostAsync($"/bot/planets/{origin.ID}/send-fleet", parameters.ToArray()); + return await PostAsync($"/bot/planets/{origin.ID}/send-fleet", parameters.ToArray()); + } catch (OgamedException e) when (e.Message.Contains("no ships to send")) { + return null; + } } public async Task CancelFleet(Fleet fleet) { @@ -728,7 +733,19 @@ public async Task SendDiscovery(Celestial origin, Coordinate coords) { return success; } - public async Task AbandonCelestial(Celestial celestial) { + public async Task> GetPositionsAvailableForDiscoveryFleet(Celestial celestial, Coordinate coordinate) { + List> parameters = new List>(); + parameters.Add(new KeyValuePair("galaxy", coordinate.Galaxy.ToString())); + parameters.Add(new KeyValuePair("system", coordinate.System.ToString())); + parameters.Add(new KeyValuePair("position", coordinate.Position.ToString())); + return await PostAsync>($"/bot/planets/{celestial.ID}/get-system-available-discovery", parameters.ToArray()); + } + + public async Task GetAvailableDiscoveries(Celestial celestial) { + return await GetAsync($"/bot/planets/{celestial.ID}/get-available-discoveries"); + } + + public async Task AbandonCelestial(Celestial celestial) { bool success = false; try { Abandon result = await GetAsync($"/bot/celestials/{celestial.ID}/abandon"); diff --git a/TBot.Ogame.Infrastructure/TBot.Ogame.Infrastructure.csproj b/TBot.Ogame.Infrastructure/TBot.Ogame.Infrastructure.csproj index 469351a1..239fa33a 100644 --- a/TBot.Ogame.Infrastructure/TBot.Ogame.Infrastructure.csproj +++ b/TBot.Ogame.Infrastructure/TBot.Ogame.Infrastructure.csproj @@ -1,17 +1,17 @@ - net6.0 + net9.0 enable enable - + - + - + diff --git a/TBot.WebUI/TBot.WebUI.csproj b/TBot.WebUI/TBot.WebUI.csproj index d019a0f1..0272c640 100644 --- a/TBot.WebUI/TBot.WebUI.csproj +++ b/TBot.WebUI/TBot.WebUI.csproj @@ -1,7 +1,7 @@  - net6.0 + net9.0 enable enable Library @@ -35,9 +35,9 @@ - - - + + + diff --git a/TBot.WebUI/Views/Shared/_Layout.cshtml b/TBot.WebUI/Views/Shared/_Layout.cshtml index c4c2a887..f12c42fd 100644 --- a/TBot.WebUI/Views/Shared/_Layout.cshtml +++ b/TBot.WebUI/Views/Shared/_Layout.cshtml @@ -20,7 +20,6 @@ T-Bot - diff --git a/TBot/Includes/CalculationService.cs b/TBot/Includes/CalculationService.cs index b6a4690e..f45390c9 100644 --- a/TBot/Includes/CalculationService.cs +++ b/TBot/Includes/CalculationService.cs @@ -18,6 +18,7 @@ using Microsoft.CodeAnalysis.Operations; using System.Reflection.Emit; using System.Data; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Tbot.Includes { @@ -154,7 +155,7 @@ public int CalcShipCapacity(Buildables buildable, int hyperspaceTech, ServerData default: return 0; } - double totalBonus = Math.Round(Math.Round((double) bonus / 100, 2, MidpointRounding.ToZero) + Math.Round((double) buildableCargoBonus / 100, 2, MidpointRounding.ToZero), 2, MidpointRounding.ToZero) - 0.01; + double totalBonus = Math.Round(Math.Round((double) bonus / 100, 2, MidpointRounding.ToZero) + Math.Round((double) buildableCargoBonus, 2, MidpointRounding.ToZero), 2, MidpointRounding.ToZero) - 0.01; int output = (int) Math.Floor((double) baseCargo + (double) (baseCargo * totalBonus)); return output; } @@ -310,7 +311,7 @@ public int CalcShipSpeed(Buildables buildable, int combustionDrive, int impulseD default: return 0; } - var output = ((float) baseSpeed * (lfBonus / 100)) + ((float) baseSpeed * ((float) bonus + 10) / 10); + var output = ((float) baseSpeed * (lfBonus)) + ((float) baseSpeed * ((float) bonus + 10) / 10); return (int) Math.Round(output, MidpointRounding.ToZero); } @@ -366,7 +367,7 @@ public int CalcShipConsumption(Buildables buildable, int impulseDrive, int hyper int baseConsumption; switch (buildable) { case Buildables.SmallCargo: - baseConsumption = 20; + baseConsumption = 10; if (impulseDrive >= 5) baseConsumption *= 2; break; @@ -389,7 +390,7 @@ public int CalcShipConsumption(Buildables buildable, int impulseDrive, int hyper baseConsumption = 1000; break; case Buildables.Recycler: - baseConsumption = 2000; + baseConsumption = 300; if (hyperspaceDrive >= 15) baseConsumption *= 3; else if (impulseDrive >= 17) @@ -421,7 +422,7 @@ public int CalcShipConsumption(Buildables buildable, int impulseDrive, int hyper default: return 0; } - double fuelConsumption = (double) deuteriumSaveFactor * (double) baseConsumption * (1 - lfBonus / 100); + double fuelConsumption = (double) deuteriumSaveFactor * (double) baseConsumption * (1 - lfBonus); if (playerClass == CharacterClass.General) fuelConsumption /= 2; fuelConsumption = Math.Round(fuelConsumption); @@ -594,7 +595,7 @@ public Ships CalcIdealExpeditionShips(Buildables buildable, int hyperspaceTech, float topOnePoints = serverData.TopScore; float buildableCargoBonus = 0; if (shipBonus != null && shipBonus.Count > 0 && shipBonus.ContainsKey((int) buildable)) { - buildableCargoBonus = shipBonus.GetValueOrDefault((int) buildable).Cargo; + buildableCargoBonus = shipBonus.GetValueOrDefault((int) buildable).CargoCapacity; } int freightCap; if (topOnePoints < 10000) @@ -622,7 +623,7 @@ public Ships CalcIdealExpeditionShips(Buildables buildable, int hyperspaceTech, freightCap *= 2; if(expeditionResourcesBonus > 0) - freightCap += (int) Math.Round((float) freightCap * expeditionResourcesBonus / 100, MidpointRounding.ToPositiveInfinity); + freightCap += (int) Math.Round((float) freightCap * expeditionResourcesBonus, MidpointRounding.ToPositiveInfinity); int oneCargoCapacity = CalcShipCapacity(buildable, hyperspaceTech, serverData, buildableCargoBonus, playerClass, probeCargo); int cargoNumber = (int) Math.Round((float) freightCap / (float) oneCargoCapacity, MidpointRounding.ToPositiveInfinity); @@ -662,7 +663,8 @@ public Ships CalcExpeditionShips(Ships fleet, Buildables primaryShip, int expedi long availableVal = (long) prop.GetValue(fleet); long idealVal = (long) prop.GetValue(ideal); if (availableVal < idealVal * expeditionsNumber) { - long realVal = (long) Math.Round(((float) availableVal / (float) expeditionsNumber), MidpointRounding.AwayFromZero); + //long realVal = (long) Math.Round(((float) availableVal / (float) expeditionsNumber), MidpointRounding.AwayFromZero); + long realVal = (long) Math.Floor(((float) availableVal / (float) expeditionsNumber)); prop.SetValue(ideal, realVal); } } @@ -671,7 +673,7 @@ public Ships CalcExpeditionShips(Ships fleet, Buildables primaryShip, int expedi } public Ships CalcExpeditionShips(Ships fleet, Buildables primaryShip, int expeditionsNumber, ServerData serverdata, Researches researches, LFBonuses LFBonuses, CharacterClass playerClass = CharacterClass.NoClass, int probeCargo = 0) { - return CalcExpeditionShips(fleet, primaryShip, expeditionsNumber, researches.HyperspaceTechnology, LFBonuses.Expeditions.Resources, LFBonuses.Ships, serverdata, playerClass, probeCargo); + return CalcExpeditionShips(fleet, primaryShip, expeditionsNumber, researches.HyperspaceTechnology, LFBonuses.LfResourceBonuses.ResourcesExpedition, LFBonuses.LfShipBonusesInt, serverdata, playerClass, probeCargo); } public bool MayAddShipToExpedition(Ships fleet, Buildables buildable, int expeditionsNumber) { @@ -1226,12 +1228,12 @@ public Resources CalcPrice(Buildables buildable, int level, LFBonuses lfBonuses } if (lfBonuses != null) { - float reduction = 0; - if (lfBonuses.Buildings.Keys.Any(b => b == (int) buildable)) { - reduction = lfBonuses.Buildings[(int) buildable].Cost; + float reduction = 0; + if (lfBonuses.CostTimeBonusesInt.Keys.Any(b => b == (int) buildable)) { + reduction = lfBonuses.CostTimeBonusesInt[(int) buildable].Cost; } - else if (lfBonuses.Researches.Keys.Any(b => b == (int) buildable)) { - reduction = lfBonuses.Researches[(int) buildable].Cost; + else if (lfBonuses.CostTimeBonusesInt.Keys.Any(b => b == (int) buildable)) { + reduction = lfBonuses.CostTimeBonusesInt[(int) buildable].Cost; } output.Metal = (long) (output.Metal - (output.Metal * reduction)); output.Crystal = (long) (output.Crystal - (output.Crystal * reduction)); @@ -1612,8 +1614,8 @@ public Resources CalcPrice(LFBuildables buildable, int level, double costReducti metalBaseCost = 6; crystalbaseCost = 3; energyBaseCost = 9; - metalFactor = 1.21; - crystalFactor = 1.21; + metalFactor = 1.20; + crystalFactor = 1.20; energyFactor = 1.02; break; case LFBuildables.VortexChamber: @@ -3027,6 +3029,29 @@ public long CalcMaxBuildableNumber(Buildables buildable, Resources resources) { return output; } + public Defences CalcmaxDefencesBuildable(Defences defences, Resources resources) { + Defences output = new(); + Dictionary defenceTypes = defences.GetDefenceTypesWithAmount(); + Resources resourcesLeft = resources; + + bool canBuild = true; + while (canBuild) { + canBuild = false; + foreach (var (defenceType, amountNeeded) in defenceTypes) { + if (amountNeeded <= 0) + continue; + Resources unitCost = CalcPrice(defenceType, 1); + if (resourcesLeft.IsEnoughFor(unitCost)) { + output.Add(defenceType.ToString(), 1); + resourcesLeft = resourcesLeft.Difference(unitCost); + defenceTypes[defenceType]--; + canBuild = true; + } + } + } + return output; + } + public long GetRequiredEnergyDelta(Buildables buildable, int level) { if (buildable == Buildables.MetalMine || buildable == Buildables.CrystalMine || buildable == Buildables.DeuteriumSynthesizer) { if (level > 1) { @@ -3100,12 +3125,15 @@ public int GetNextLevel(Celestial planet, LFBuildables buildable) { public int GetNextLevel(Celestial planet, LFTechno buildable) { int output = 0; - if (planet is Planet) { - foreach (PropertyInfo prop in planet.LFTechs.GetType().GetProperties()) { - if (prop.Name == buildable.ToString()) { - output = (int) prop.GetValue(planet.LFTechs) + 1; - } + if (buildable is LFTechno.None) { + return output; + } + if (planet is Celestial) { + int? level = planet.LFTechs.GetLevel(buildable); + if (level is null) { + return output; } + output = planet.LFTechs.GetLevel(buildable) + 1; } return output; } @@ -4041,15 +4069,16 @@ public LFTechno GetNextLFTechToBuild(Celestial celestial, LFTechs MaxReasearchLe //TODO //As planets can have any lifeform techs, its complicated to find which techs are existing on a planet if the techs are not at least level 1 //Therefore, for the moment, up only techs that are minimum level 1, its a way to also allows player to chose which research to up - foreach (PropertyInfo prop in celestial.LFTechs.GetType().GetProperties()) { - foreach (LFTechno nextLFTech in Enum.GetValues()) { - if ((int) prop.GetValue(celestial.LFTechs) > 0 && GetNextLevel(celestial, nextLFTech) <= MaxReasearchLevel.GetLevel(nextLFTech) && prop.Name == nextLFTech.ToString()) { - //Console.WriteLine($"-----------------------------> {nextLFTech}: {GetNextLevel(celestial, nextLFTech)} / {MaxReasearchLevel.GetLevel(nextLFTech)}"); - return nextLFTech; - } - + foreach (LFTechno nextLFTech in Enum.GetValues()) { + int? level = celestial.LFTechs.GetLevel(nextLFTech); + if (level is null) { + continue; } + if (level > 0 && GetNextLevel(celestial, nextLFTech) <= MaxReasearchLevel.GetLevel(nextLFTech)) { + //Console.WriteLine($"-----------------------------> {nextLFTech}: {GetNextLevel(celestial, nextLFTech)} / {MaxReasearchLevel.GetLevel(nextLFTech)}"); + return nextLFTech; + } } return LFTechno.None; } @@ -4057,20 +4086,24 @@ public LFTechno GetNextLFTechToBuild(Celestial celestial, LFTechs MaxReasearchLe public LFTechno GetLessExpensiveLFTechToBuild(Celestial celestial, Resources currentcost, LFTechs MaxReasearchLevel, double costReduction = 0) { LFTechno nextLFtech = LFTechno.None; Resource nextLFtechcost = new(); - foreach (PropertyInfo prop in celestial.LFTechs.GetType().GetProperties()) { - foreach (LFTechno next in Enum.GetValues()) { - if ((int) prop.GetValue(celestial.LFTechs) > 0 && GetNextLevel(celestial, next) <= MaxReasearchLevel.GetLevel(next) && prop.Name == next.ToString()) { - //Console.WriteLine($"-----------------------------> {next}: {GetNextLevel(celestial, next)} / {MaxReasearchLevel.GetLevel(next)}"); - var nextLFtechlvl = GetNextLevel(celestial, next); - Resources newcost = CalcPrice(next, nextLFtechlvl, costReduction); - if (newcost.ConvertedDeuterium < currentcost.ConvertedDeuterium) { - currentcost = newcost; - nextLFtech = next; - } - } - + foreach (LFTechno next in Enum.GetValues()) { + if (next is LFTechno.None) { + continue; + } + int? level = celestial.LFTechs.GetLevel(next); + if (level is null) { + continue; } + if (level > 0 && GetNextLevel(celestial, next) <= MaxReasearchLevel.GetLevel(next)) { + //Console.WriteLine($"-----------------------------> {next}: {GetNextLevel(celestial, next)} / {MaxReasearchLevel.GetLevel(next)}"); + var nextLFtechlvl = GetNextLevel(celestial, next); + Resources newcost = CalcPrice(next, nextLFtechlvl, costReduction); + if (newcost.ConvertedDeuterium < currentcost.ConvertedDeuterium) { + currentcost = newcost; + nextLFtech = next; + } + } } return nextLFtech; } @@ -4408,11 +4441,15 @@ public bool ShouldResearchEnergyTech(List planets, Researches researches return ShouldResearchEnergyTech(planets, researches.EnergyTechnology, maxEnergyTech, playerClass, hasEngineer, hasStaff); } - public Buildables GetNextResearchToBuild(Planet celestial, Researches researches, bool prioritizeRobotsAndNanitesOnNewPlanets = false, Slots slots = null, int maxEnergyTechnology = 20, int maxLaserTechnology = 12, int maxIonTechnology = 5, int maxHyperspaceTechnology = 20, int maxPlasmaTechnology = 20, int maxCombustionDrive = 19, int maxImpulseDrive = 17, int maxHyperspaceDrive = 15, int maxEspionageTechnology = 8, int maxComputerTechnology = 20, int maxAstrophysics = 23, int maxIntergalacticResearchNetwork = 12, int maxWeaponsTechnology = 25, int maxShieldingTechnology = 25, int maxArmourTechnology = 25, bool optimizeForStart = true, bool ensureExpoSlots = true, CharacterClass playerClass = CharacterClass.NoClass, bool hasGeologist = false, bool hasAdmiral = false) { - if (ShouldBuildResearchLab(celestial, 12, researches)) + public Buildables GetNextResearchToBuild(Planet celestial, Researches researches, bool prioritizeRobotsAndNanitesOnNewPlanets = false, Slots slots = null, int maxEnergyTechnology = 20, int maxLaserTechnology = 12, int maxIonTechnology = 5, int maxHyperspaceTechnology = 20, int maxPlasmaTechnology = 20, int maxCombustionDrive = 19, int maxImpulseDrive = 17, int maxHyperspaceDrive = 15, int maxEspionageTechnology = 8, int maxComputerTechnology = 20, int maxAstrophysics = 23, int maxIntergalacticResearchNetwork = 12, int maxWeaponsTechnology = 25, int maxShieldingTechnology = 25, int maxArmourTechnology = 25, bool optimizeForStart = true, bool ensureExpoSlots = true, bool ForceResearchWhateverTheLabLevel = false, CharacterClass playerClass = CharacterClass.NoClass, bool hasGeologist = false, bool hasAdmiral = false) { + if (ShouldBuildResearchLab(celestial, 12, researches) && !ForceResearchWhateverTheLabLevel) { + _logger.WriteLog(LogLevel.Information, LogSender.Tbot, $"Skipping AutoResearch: Can't build any research, Research Lab need to be upgraded first (actually cheaper than the next mine)."); return Buildables.Null; + } if (optimizeForStart) { + if (researches.ComputerTechnology < 6 && celestial.Facilities.ResearchLab >= 1 && researches.Astrophysics > 1 && researches.ComputerTechnology < maxComputerTechnology) + return Buildables.ComputerTechnology; if (researches.EnergyTechnology == 0 && celestial.Facilities.ResearchLab > 0 && researches.EnergyTechnology < maxEnergyTechnology) return Buildables.EnergyTechnology; if (researches.CombustionDrive < 2 && celestial.Facilities.ResearchLab > 0 && researches.EnergyTechnology >= 1 && researches.CombustionDrive < maxCombustionDrive) @@ -4740,11 +4777,13 @@ public int CalcMaxPlanets(Researches researches) { return researches == null ? 1 : CalcMaxPlanets(researches.Astrophysics); } - public bool CalcLimitAstro(int pos, Researches researches) { - return ((pos >= 4 && pos <= 13 && (int)researches.Astrophysics >= 4) || - (pos >= 2 && pos <= 14 && (int)researches.Astrophysics >= 6) || - (pos >= 1 && pos <= 15 && (int)researches.Astrophysics >= 8)); - } + public bool IsAstrophysicsPositionValid(int pos, int astrophysics) => astrophysics switch { + 8 => pos >= 1 && pos <= 15, + 7 or 6 => pos >= 2 && pos <= 14, + 5 or 4 => pos >= 3 && pos <= 13, + 3 or 2 or 1 => pos >= 4 && pos <= 12, + _ => true + }; public int CalcMaxCrawlers(Planet planet, CharacterClass userClass, bool hasGeologist) { if (userClass == CharacterClass.Collector && hasGeologist) { @@ -4806,5 +4845,224 @@ public int CountPlanetsInRange(List planets, int galaxy, int minSyste .Where(planet => (planet as Planet).Temperature.Max >= minTemperature && (planet as Planet).Temperature.Max <= maxTemperature) .Count(); } + + public bool IsThereMoonHere(List planets, Celestial celestial) { + Celestial moon = planets.SingleOrDefault( + c => c.HasCoords(new( + (int) celestial.Coordinate.Galaxy, + (int) celestial.Coordinate.System, + (int) celestial.Coordinate.Position, + Celestials.Moon + ) + )) ?? new() { ID = 0 }; + /*.Where(c => c.Coordinate.Galaxy == (int) celestial.Coordinate.Galaxy) + .Where(c => c.Coordinate.System == (int) celestial.Coordinate.System) + .Where(c => c.Coordinate.Position == (int) celestial.Coordinate.Position) + .Where(c => c.Coordinate.Type == Celestials.Moon) + .SingleOrDefault() ?? new() { ID = 0 };*/ + + return moon.ID == 0 ? false: true; + } + + public int CalcSlotsPriority(Feature feature, List rankSlotsPriority, Slots slots, List fleets, int slotsToLeaveFree = 0) { + LogSender logsender; + switch (feature) { + case Feature.BrainAutoMine: logsender = LogSender.AutoMine; break; + case Feature.BrainAutoResearch: logsender = LogSender.AutoResearch; break; + case Feature.BrainLifeformAutoMine: logsender = LogSender.LifeformsAutoMine; break; + case Feature.BrainLifeformAutoResearch: logsender = LogSender.LifeformsAutoResearch; break; + case Feature.Expeditions: logsender = LogSender.Expeditions; break; + case Feature.AutoFarm: logsender = LogSender.AutoFarm; break; + case Feature.Colonize: logsender = LogSender.Colonize; break; + case Feature.AutoDiscovery: logsender = LogSender.AutoDiscovery; break; + case Feature.Harvest: logsender = LogSender.Harvest; break; + default: logsender = LogSender.FleetScheduler; break; + } + RankSlotsPriority actualFeature = rankSlotsPriority.Single(f => f.Feature == feature); + int slotsAvailable = 0; + int reservedSlots = 0; + int usedSlots = 0; + int otherSlots = fleets.Count(fleet => + fleet.Mission != Missions.Transport && + fleet.Mission != Missions.Expedition && + fleet.Mission != Missions.Spy && + fleet.Mission != Missions.Attack && + fleet.Mission != Missions.Colonize && + fleet.Mission != Missions.Discovery && + fleet.Mission != Missions.Harvest); + + if (actualFeature.MaxSlots - actualFeature.SlotsUsed <= 0) { + _logger.WriteLog(LogLevel.Information, logsender, $"All slots for feature {feature} are already used ({actualFeature.SlotsUsed}/{actualFeature.MaxSlots})."); + return 0; + } + + if (actualFeature.Rank > 0) { + reservedSlots = rankSlotsPriority.Where(fp => fp.Active) + .Where(fp => fp.Rank > 0) + .Where(fp => fp.Rank < actualFeature.Rank) + .Sum(fp => fp.MaxSlots); + usedSlots = rankSlotsPriority.Where(fp => fp.Active) + .Where(fp => fp.Rank > actualFeature.Rank) + .Sum(fp => fp.SlotsUsed); + otherSlots += rankSlotsPriority.Where(fp => !fp.Active) + .Sum(fp => fp.SlotsUsed); + slotsAvailable = slots.Total - slotsToLeaveFree - reservedSlots - usedSlots - otherSlots < 0 ? 0 : slots.Total - slotsToLeaveFree - reservedSlots - usedSlots - otherSlots; + } else { + slotsAvailable = slots.Free - slotsToLeaveFree < 0 ? 0 : slots.Free - slotsToLeaveFree; + } + if (slotsAvailable <= 0) { + _logger.WriteLog(LogLevel.Information, logsender, $"No slots available for feature {feature}. Slots available: {slotsAvailable}."); + return 0; + } else { + if (actualFeature.Rank > 0) { + _logger.WriteLog(LogLevel.Information, logsender, $"Slots Total: {slots.Total}, Reserved: {reservedSlots + slotsToLeaveFree}, Other: {usedSlots + otherSlots}. Available: {slotsAvailable}."); + if (slotsAvailable < actualFeature.MaxSlots) { + _logger.WriteLog(LogLevel.Information, logsender, $"Less slots available ({slotsAvailable}) than maximum usable slots ({actualFeature.MaxSlots}). Stepping down to usable slots."); + actualFeature.MaxSlots = slotsAvailable; + } + slotsAvailable = Math.Min(slotsAvailable, actualFeature.MaxSlots - actualFeature.SlotsUsed); + _logger.WriteLog(LogLevel.Information, logsender, $"{feature} will use {slotsAvailable} slots ({actualFeature.SlotsUsed}/{actualFeature.MaxSlots} already used)."); + } else { + _logger.WriteLog(LogLevel.Information, logsender, $"{feature} has no rank and can use all available slots: {slotsAvailable}."); + } + } + + return slotsAvailable; + } + + public List> CalcMultipleOrigin(Celestial celestialToBuild, List allCelestials, Resources missingResources, TransportSettings transportSettings, List fleets, UserData userData) { + if (missingResources.TotalResources < transportSettings.MultipleOrigin.MinimumResourcesToSend) { + _logger.WriteLog(LogLevel.Information, LogSender.Brain, $"Skipping transport: missing resources {missingResources.TransportableResources} is less than minimum {transportSettings.MultipleOrigin.MinimumResourcesToSend}"); + return new(); + } + List> result = new(); + bool roundRes = transportSettings.RoundResources; + Resources resources = new(); + List closestCelestials = transportSettings.MultipleOrigin.OnlyFromMoons ? + allCelestials + .Where(planet => !transportSettings.MultipleOrigin.Exclude.Has(planet)) + .Where(planet => planet.Resources.TotalResources > 0) + .Where(planet => planet.Coordinate.Type == Celestials.Moon) + .OrderByDescending(planet => planet.Coordinate.Type == Celestials.Moon) + .ToList(): + allCelestials + .Where(c => !transportSettings.MultipleOrigin.Exclude.Has(c)) + .Where(c => c.Resources.TotalResources > 0) + .ToList(); + + Resources TotalResources = closestCelestials.Aggregate(new Resources(), (total, celestial) => total.Sum(celestial.Resources.Difference(new Resources(0, 0, transportSettings.DeutToLeave))) ); + if (!TotalResources.IsEnoughFor(missingResources)) { + _logger.WriteLog(LogLevel.Information, LogSender.Brain, $"Not enough resources available on all celestials: Needed: {missingResources.TransportableResources} - Available: {TotalResources.TransportableResources}"); + return new(); + } else { + _logger.WriteLog(LogLevel.Information, LogSender.Brain, $"Enough resources available on all celestials: Needed: {missingResources.TransportableResources} - Available: {TotalResources.TransportableResources}"); + } + + closestCelestials = transportSettings.MultipleOrigin.PriorityToProximityOverQuantity ? + closestCelestials.OrderBy(c => CalcDistance(c.Coordinate, celestialToBuild.Coordinate, userData.serverData)).ToList() : + closestCelestials.OrderByDescending(c => c.Resources.TotalResources).ToList(); + + Celestial destination = celestialToBuild; + if ((bool) transportSettings.SendToTheMoonIfPossible && celestialToBuild.Coordinate.Type == Celestials.Planet && IsThereMoonHere(allCelestials, celestialToBuild) && (!celestialToBuild.Ships.IsEmpty() || celestialToBuild.Resources.TotalResources > 0)) { + destination = allCelestials.Unique() + .Single(c => c.HasCoords(new( + (int) celestialToBuild.Coordinate.Galaxy, + (int) celestialToBuild.Coordinate.System, + (int) celestialToBuild.Coordinate.Position, + Celestials.Moon + ) + )); + destination.Resources = roundRes ? destination.Resources.Round() : destination.Resources; + if (destination.Resources.IsEnoughFor(missingResources)) { + if (destination.Ships.GetAmount(transportSettings.CargoType) >= CalcShipNumberForPayload(missingResources, transportSettings.CargoType, userData.researches.HyperspaceTechnology, userData.serverData, destination.LFBonuses.GetShipCargoBonus(transportSettings.CargoType), userData.userInfo.Class, userData.serverData.ProbeCargo)) { + result.Add(new Dictionary { { destination, missingResources } } ); + return result; + } else if (transportSettings.DoMultipleTransportIsNotEnoughShipButSamePosition) { + result.Add(new Dictionary { { destination, CalcMaxTransportableResources(destination.Ships, missingResources, userData.researches.HyperspaceTechnology, userData.serverData, destination.LFBonuses, userData.userInfo.Class, 0, userData.serverData.ProbeCargo) } } ); + return result; + } else { + _logger.WriteLog(LogLevel.Information, LogSender.Brain, $"Enough Resources on {destination.ToString()}, but not enough transporters and \"DoMultipleTransportIsNotEnoughShipButSamePosition\" is false."); + return new(); + } + } else { + missingResources = missingResources.Difference(destination.Resources); + } + } else { + destination = celestialToBuild; + } + closestCelestials = closestCelestials.Where(c => !c.Coordinate.IsSame(destination.Coordinate)) + .Where(c => !IsThereTransportTowardsCelestial(c, fleets)) + .ToList(); + if (closestCelestials.Count() == 0) { + _logger.WriteLog(LogLevel.Information, LogSender.Brain, $"Skipping transport: no celestial available"); + return new(); + } + if (IsThereTransportTowardsCelestial(destination, fleets)) { + _logger.WriteLog(LogLevel.Information, LogSender.Brain, $"Skipping transport: there is already a transport incoming in {destination.ToString()}"); + return new(); + } + TotalResources = closestCelestials.Aggregate(new Resources(), (total, celestial) => { + if (celestial.Resources.TotalResources < transportSettings.MultipleOrigin.MinimumResourcesToSend || celestial.Resources.TotalResources == 0) + return total; + if (celestial.Ships.GetAmount(transportSettings.CargoType) >= CalcShipNumberForPayload(celestial.Resources.Difference(new Resources(0, 0, transportSettings.DeutToLeave)), transportSettings.CargoType, userData.researches.HyperspaceTechnology, userData.serverData, celestial.LFBonuses.GetShipCargoBonus(transportSettings.CargoType), userData.userInfo.Class, userData.serverData.ProbeCargo)) { + return total.Sum(celestial.Resources.Difference(new Resources(0, 0, transportSettings.DeutToLeave))); + } else { + Ships ships = new(); + ships.Add(transportSettings.CargoType, celestial.Ships.GetAmount(transportSettings.CargoType)); + Resources res = CalcMaxTransportableResources(ships, missingResources.Difference(total), userData.researches.HyperspaceTechnology, userData.serverData, celestial.LFBonuses, userData.userInfo.Class, 0, userData.serverData.ProbeCargo); + return total.Sum(res); + } + } ); + if (!TotalResources.IsEnoughFor(missingResources)) { + _logger.WriteLog(LogLevel.Information, LogSender.Brain, $"Not enough resources available on all celestials: Needed: {missingResources.TransportableResources} - Available: {TotalResources.TransportableResources}"); + return new(); + } + + for (int i = 0; i < closestCelestials.Count(); i++) { + Celestial cel = closestCelestials[i]; + Resources celResources = cel.Resources.Difference(new Resources(0, 0, transportSettings.DeutToLeave)); + if (resources.Sum(celResources).IsEnoughFor(missingResources)) { + celResources = roundRes ? missingResources.Difference(resources).Round() : missingResources.Difference(resources); + } else { + celResources = roundRes ? + celResources.Difference(celResources.Difference(missingResources.Difference(resources))).Round(): + celResources.Difference(celResources.Difference(missingResources.Difference(resources))); + } + if (celResources.TotalResources < transportSettings.MultipleOrigin.MinimumResourcesToSend || celResources.TotalResources == 0) { + _logger.WriteLog(LogLevel.Information, LogSender.Brain, $"Skipping transport from {cel.ToString()}: resources under minimum threshold ({celResources.TotalResources} < {transportSettings.MultipleOrigin.MinimumResourcesToSend})"); + continue; + } + if (cel.Ships.GetAmount(transportSettings.CargoType) >= CalcShipNumberForPayload(celResources, transportSettings.CargoType, userData.researches.HyperspaceTechnology, userData.serverData, cel.LFBonuses.GetShipCargoBonus(transportSettings.CargoType), userData.userInfo.Class, userData.serverData.ProbeCargo)) { + resources = resources.Sum(celResources); + result.Add(new Dictionary { { cel, celResources } } ); + } else { + if (i < closestCelestials.Count() - 1) { + Ships ships = new(); + ships.Add(transportSettings.CargoType, cel.Ships.GetAmount(transportSettings.CargoType)); + Resources res = CalcMaxTransportableResources(ships, celResources, userData.researches.HyperspaceTechnology, userData.serverData, cel.LFBonuses, userData.userInfo.Class, 0, userData.serverData.ProbeCargo); + resources = resources.Sum(res); + celResources = res; + if (celResources.TotalResources == 0) + continue; + result.Add(new Dictionary { { cel, celResources } } ); + } else { + _logger.WriteLog(LogLevel.Information, LogSender.Brain, $"Not enough resources transportable"); + return new(); + } + } + _logger.WriteLog(LogLevel.Information, LogSender.Tbot, $"Sending resources from: {cel.Coordinate.ToString()} to {destination.Coordinate.ToString()} - Resources: {celResources.TransportableResources}"); + if (resources.IsEnoughFor(missingResources)) { + _logger.WriteLog(LogLevel.Information, LogSender.Tbot, $"{result.Count()} transports will be send to {destination.ToString()}"); + return result; + } + } + if (resources.IsEnoughFor(missingResources)) { + _logger.WriteLog(LogLevel.Information, LogSender.Tbot, $"{result.Count()} transports will be send to {destination.ToString()}"); + return result; + } else { + _logger.WriteLog(LogLevel.Information, LogSender.Tbot, $"Not enough resources transportable"); + return new(); + } + } } } diff --git a/TBot/Includes/ICalculationService.cs b/TBot/Includes/ICalculationService.cs index 0ca15f86..ab84d941 100644 --- a/TBot/Includes/ICalculationService.cs +++ b/TBot/Includes/ICalculationService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Tbot.Services; using TBot.Model; using TBot.Ogame.Infrastructure; using TBot.Ogame.Infrastructure.Enums; @@ -37,12 +38,13 @@ public interface ICalculationService { Ships CalcFullExpeditionShips(Ships fleet, Buildables primaryShip, int expeditionsNumber, ServerData serverdata, Researches researches, LFBonuses LFBonuses, CharacterClass playerClass = CharacterClass.NoClass, int probeCargo = 0); Ships CalcIdealExpeditionShips(Buildables buildable, int hyperspaceTech, float expeditionResourcesBonus, Dictionary shipsBonus, ServerData serverData, CharacterClass playerClass = CharacterClass.NoClass, int probeCargo = 0); long CalcMaxBuildableNumber(Buildables buildable, Resources resources); + Defences CalcmaxDefencesBuildable(Defences defences, Resources resources); int CalcMaxCrawlers(Planet planet, CharacterClass userClass, bool hasGeologist); int CalcMaxExpeditions(int astrophysics, bool isDiscoverer, bool hasAdmiral); int CalcMaxExpeditions(Researches researches, CharacterClass playerClass, Staff staff); int CalcMaxPlanets(int astrophysics); int CalcMaxPlanets(Researches researches); - bool CalcLimitAstro(int pos, Researches researches); + bool IsAstrophysicsPositionValid(int pos, int astrophysics); Resources CalcMaxTransportableResources(Ships ships, Resources resources, int hyperspaceTech, ServerData serverData, LFBonuses lfBonuses = null, CharacterClass playerClass = CharacterClass.NoClass, long deutToLeave = 0, int probeCargo = 0); long CalcMetalProduction(Buildings buildings, int position, int speedFactor, float ratio = 1, Researches researches = null, LFBonuses lfBonuses = null, CharacterClass playerClass = CharacterClass.NoClass, bool hasGeologist = false, bool hasStaff = false, int crawlers = 0, float crawlerRatio = 1); long CalcMetalProduction(int level, int position, int speedFactor, float ratio = 1, int plasma = 0, float metalLFBonus = 0, CharacterClass playerClass = CharacterClass.NoClass, bool hasGeologist = false, bool hasStaff = false, int crawlers = 0, float crawlerRatio = 1); @@ -124,7 +126,7 @@ public interface ICalculationService { Buildables GetNextMineToBuild(Planet planet, int maxMetalMine = 100, int maxCrystalMine = 100, int maxDeuteriumSynthetizer = 100, bool optimizeForStart = true); Buildables GetNextMineToBuild(Planet planet, Researches researches = null, int speedFactor = 1, int maxMetalMine = 100, int maxCrystalMine = 100, int maxDeuteriumSynthetizer = 100, float ratio = 1, CharacterClass playerClass = CharacterClass.NoClass, bool hasGeologist = false, bool hasStaff = false, bool optimizeForStart = true, float maxDaysOfInvestmentReturn = 36500); Buildables GetNextMineToBuild(Planet planet, Researches researches, Buildings maxBuildings, Facilities maxFacilities, CharacterClass playerClass, Staff staff, ServerData serverData, AutoMinerSettings settings, float ratio = 1); - Buildables GetNextResearchToBuild(Planet celestial, Researches researches, bool prioritizeRobotsAndNanitesOnNewPlanets = false, Slots slots = null, int maxEnergyTechnology = 20, int maxLaserTechnology = 12, int maxIonTechnology = 5, int maxHyperspaceTechnology = 20, int maxPlasmaTechnology = 20, int maxCombustionDrive = 19, int maxImpulseDrive = 17, int maxHyperspaceDrive = 15, int maxEspionageTechnology = 8, int maxComputerTechnology = 20, int maxAstrophysics = 23, int maxIntergalacticResearchNetwork = 12, int maxWeaponsTechnology = 25, int maxShieldingTechnology = 25, int maxArmourTechnology = 25, bool optimizeForStart = true, bool ensureExpoSlots = true, CharacterClass playerClass = CharacterClass.NoClass, bool hasGeologist = false, bool hasAdmiral = false); + Buildables GetNextResearchToBuild(Planet celestial, Researches researches, bool prioritizeRobotsAndNanitesOnNewPlanets = false, Slots slots = null, int maxEnergyTechnology = 20, int maxLaserTechnology = 12, int maxIonTechnology = 5, int maxHyperspaceTechnology = 20, int maxPlasmaTechnology = 20, int maxCombustionDrive = 19, int maxImpulseDrive = 17, int maxHyperspaceDrive = 15, int maxEspionageTechnology = 8, int maxComputerTechnology = 20, int maxAstrophysics = 23, int maxIntergalacticResearchNetwork = 12, int maxWeaponsTechnology = 25, int maxShieldingTechnology = 25, int maxArmourTechnology = 25, bool optimizeForStart = true, bool ensureExpoSlots = true, bool ForceResearchWhateverTheLabLevel = false, CharacterClass playerClass = CharacterClass.NoClass, bool hasGeologist = false, bool hasAdmiral = false); long GetProductionEnergyDelta(Buildables buildable, int level, int energyTechnology = 0, float ratio = 1, CharacterClass userClass = CharacterClass.NoClass, bool hasEngineer = false, bool hasStaff = false); long GetRequiredEnergyDelta(Buildables buildable, int level); int GetSolarSatelliteOutput(Planet planet, bool isCollector = false, bool hasEngineer = false, bool hasFullStaff = false); @@ -152,7 +154,9 @@ public interface ICalculationService { bool ShouldAbandon(Planet celestial, int maxCases, int Temperature, Fields fieldsSettings, Temperature temperaturesSettings); int CountPlanetsInRange(List planets, int galaxy, int minSystem, int maxSystem, int minPosition, int maxPositions, int minSlots, int minTemperature, int maxTemperature); int CountPlanetsInRange(List planets, int galaxy, int minSystem, int maxSystem, int minPosition, int maxPositions, int minSlots, int minTemperature, int maxTemperature); - + bool IsThereMoonHere(List planets, Celestial celestial); + int CalcSlotsPriority(Feature feature, List rankSlotsPriority, Slots slots, List fleets, int slotsToLeaveFree = 0); + List> CalcMultipleOrigin(Celestial celestialToBuild, List allCelestials, Resources missingResources, TransportSettings transportSettings, List fleets, UserData userData); } } diff --git a/TBot/Includes/IFleetScheduler.cs b/TBot/Includes/IFleetScheduler.cs index 1509e0a2..f029c3ec 100644 --- a/TBot/Includes/IFleetScheduler.cs +++ b/TBot/Includes/IFleetScheduler.cs @@ -18,10 +18,10 @@ public interface IFleetScheduler { Task CancelFleet(Fleet fleet); Task> UpdateFleets(); void RetireFleet(object fleet); - Task HandleMinerTransport(Celestial origin, Celestial destination, Resources resources, Buildables buildable = Buildables.Null, Buildings maxBuildings = null, Facilities maxFacilities = null, Facilities maxLunarFacilities = null, AutoMinerSettings autoMinerSettings = null); - Task HandleMinerTransport(Celestial origin, Celestial destination, Resources resources, LFBuildables buildable = LFBuildables.None, LFBuildings maxLFBuildings = null, bool preventIfMoreExpensiveThanNextMine = false); - Task Collect(); - Task CollectImpl(bool fromTelegram = false); + Task HandleMinerTransport(Celestial origin, Celestial destination, Celestial differentDestination, Resources resources, Buildables buildable = Buildables.Null, Buildings maxBuildings = null, Facilities maxFacilities = null, Facilities maxLunarFacilities = null, AutoMinerSettings autoMinerSettings = null); + Task HandleMinerTransport(Celestial origin, Celestial destination, Celestial differentDestination, Resources resources, LFBuildables buildable = LFBuildables.None, LFBuildings maxLFBuildings = null, bool preventIfMoreExpensiveThanNextMine = false); + Task Collect(bool noLimit = false); + Task CollectImpl(bool fromTelegram = false, bool noLimit = false); Task CollectDeut(long minAmount = 0); } } diff --git a/TBot/Model/AutoFarmBlacklist.cs b/TBot/Model/AutoFarmBlacklist.cs new file mode 100644 index 00000000..2a17ff72 --- /dev/null +++ b/TBot/Model/AutoFarmBlacklist.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Newtonsoft.Json; +using TBot.Ogame.Infrastructure.Models; + +namespace TBot.Model { + public enum BlacklistReason { + HasFleet, + HasDefense, + LowResources, + ManuallyAdded + } + + public class BlacklistedTarget { + public Coordinate Coordinate { get; set; } + public BlacklistReason Reason { get; set; } + public DateTime BlacklistedAt { get; set; } + public DateTime ExpiresAt { get; set; } + + public BlacklistedTarget() { + } + + public BlacklistedTarget(Coordinate coordinate, BlacklistReason reason, DateTime expiresAt) { + Coordinate = coordinate; + Reason = reason; + BlacklistedAt = DateTime.UtcNow; + ExpiresAt = expiresAt; + } + + public bool IsExpired() { + return DateTime.UtcNow >= ExpiresAt; + } + } + + public class AutoFarmBlacklist { + private List _blacklistedTargets; + private readonly object _lock = new object(); + private string _filePath; + + public AutoFarmBlacklist() { + _blacklistedTargets = new List(); + } + + public AutoFarmBlacklist(string filePath) { + string dataFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "data"); + if (!Directory.Exists(dataFolder)) { + Directory.CreateDirectory(dataFolder); + } + _filePath = Path.Combine(dataFolder, filePath); + _blacklistedTargets = new List(); + LoadFromFile(); + } + + public void AddTarget(Coordinate coordinate, BlacklistReason reason, int hoursUntilReset) { + lock (_lock) { + _blacklistedTargets.RemoveAll(t => t.Coordinate.Galaxy == coordinate.Galaxy + && t.Coordinate.System == coordinate.System + && t.Coordinate.Position == coordinate.Position); + + DateTime expiresAt = DateTime.UtcNow.AddHours(hoursUntilReset); + _blacklistedTargets.Add(new BlacklistedTarget(coordinate, reason, expiresAt)); + SaveToFile(); + } + } + + public bool IsBlacklisted(Coordinate coordinate) { + lock (_lock) { + CleanupExpiredTargets(); + + return _blacklistedTargets.Any(t => + t.Coordinate.Galaxy == coordinate.Galaxy + && t.Coordinate.System == coordinate.System + && t.Coordinate.Position == coordinate.Position); + } + } + + public BlacklistedTarget GetBlacklistedTarget(Coordinate coordinate) { + lock (_lock) { + CleanupExpiredTargets(); + + return _blacklistedTargets.FirstOrDefault(t => + t.Coordinate.Galaxy == coordinate.Galaxy + && t.Coordinate.System == coordinate.System + && t.Coordinate.Position == coordinate.Position); + } + } + + public void RemoveTarget(Coordinate coordinate) { + lock (_lock) { + _blacklistedTargets.RemoveAll(t => + t.Coordinate.Galaxy == coordinate.Galaxy + && t.Coordinate.System == coordinate.System + && t.Coordinate.Position == coordinate.Position); + } + SaveToFile(); + } + + public void ClearAll() { + lock (_lock) { + _blacklistedTargets.Clear(); + } + SaveToFile(); + } + + public int GetBlacklistedCount() { + lock (_lock) { + CleanupExpiredTargets(); + return _blacklistedTargets.Count; + } + } + + public List GetAllBlacklisted() { + lock (_lock) { + CleanupExpiredTargets(); + return new List(_blacklistedTargets); + } + } + + private void CleanupExpiredTargets() { + _blacklistedTargets.RemoveAll(t => t.IsExpired()); + } + + public void ManualCleanup() { + lock (_lock) { + CleanupExpiredTargets(); + } + } + private void LoadFromFile() { + if (string.IsNullOrEmpty(_filePath) || !File.Exists(_filePath)) { + return; + } + + try { + string json = File.ReadAllText(_filePath); + var loaded = JsonConvert.DeserializeObject>(json); + if (loaded != null) { + lock (_lock) { + _blacklistedTargets = loaded; + CleanupExpiredTargets(); + } + } + } catch (Exception) { + lock (_lock) { + _blacklistedTargets = new List(); + } +} + } + + private void SaveToFile() { + if (string.IsNullOrEmpty(_filePath)) { + return; + } + + List snapshot; + lock (_lock) { + snapshot = _blacklistedTargets.ToList(); + } + + try { + string json = JsonConvert.SerializeObject(snapshot, Formatting.Indented); + File.WriteAllText(_filePath, json); + } catch (Exception) { + } + } + } +} diff --git a/TBot/Model/AutoFarmSuccessfulTargets.cs b/TBot/Model/AutoFarmSuccessfulTargets.cs new file mode 100644 index 00000000..eef9351b --- /dev/null +++ b/TBot/Model/AutoFarmSuccessfulTargets.cs @@ -0,0 +1,306 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Newtonsoft.Json; +using TBot.Ogame.Infrastructure.Models; + +namespace TBot.Model +{ + public class SuccessfulTarget + { + public string Coordinate { get; set; } + public DateTime FirstAttacked { get; set; } + public DateTime LastAttacked { get; set; } + public int TimesAttacked { get; set; } + public Resources TotalLootCollected { get; set; } + public Resources AverageLoot { get; set; } + + public SuccessfulTarget() + { + TotalLootCollected = new Resources(); + AverageLoot = new Resources(); + } + + public SuccessfulTarget(string coordinate, Resources loot) + { + Coordinate = coordinate; + FirstAttacked = DateTime.UtcNow; + LastAttacked = DateTime.UtcNow; + TimesAttacked = 1; + + TotalLootCollected = new Resources + { + Metal = loot.Metal, + Crystal = loot.Crystal, + Deuterium = loot.Deuterium + }; + + AverageLoot = new Resources + { + Metal = loot.Metal, + Crystal = loot.Crystal, + Deuterium = loot.Deuterium + }; + } + + public void AddAttack(Resources loot) + { + LastAttacked = DateTime.UtcNow; + TimesAttacked++; + + TotalLootCollected.Metal += loot.Metal; + TotalLootCollected.Crystal += loot.Crystal; + TotalLootCollected.Deuterium += loot.Deuterium; + + AverageLoot.Metal = TotalLootCollected.Metal / TimesAttacked; + AverageLoot.Crystal = TotalLootCollected.Crystal / TimesAttacked; + AverageLoot.Deuterium = TotalLootCollected.Deuterium / TimesAttacked; + } + } + + public class AutoFarmSuccessfulTargets + { + private List _successfulTargets; + private readonly object _lock = new object(); + + private string _filePath; + + private DateTime _lastSaveUtc = DateTime.MinValue; + private int _pendingChanges = 0; + + private readonly TimeSpan _saveInterval = TimeSpan.FromSeconds(60); + private const int SaveAfterChanges = 20; + + private const int MaxTargets = 5000; + private readonly TimeSpan _staleCutoff = TimeSpan.FromDays(30); + + public AutoFarmSuccessfulTargets() + { + _successfulTargets = new List(); + } + + public AutoFarmSuccessfulTargets(string fileName) + { + string dataFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "data"); + if (!Directory.Exists(dataFolder)) + { + Directory.CreateDirectory(dataFolder); + } + + string safeFileName = MakeSafeFileName(fileName); + _filePath = Path.Combine(dataFolder, safeFileName); + + _successfulTargets = new List(); + LoadFromFile(); + } + + public void RecordAttack(Coordinate coordinate, Resources loot) + { + lock (_lock) + { + string coordStr = $"{coordinate.Galaxy}:{coordinate.System}:{coordinate.Position}"; + + var existing = _successfulTargets.FirstOrDefault(t => t.Coordinate == coordStr); + if (existing != null) + { + existing.AddAttack(loot); + } + else + { + _successfulTargets.Add(new SuccessfulTarget(coordStr, loot)); + } + + _pendingChanges++; + + CleanupUnsafeGrowth_NoThrow(); + + SaveIfNeeded_NoThrow(); + } + } + + public SuccessfulTarget GetTarget(Coordinate coordinate) + { + lock (_lock) + { + string coordStr = $"{coordinate.Galaxy}:{coordinate.System}:{coordinate.Position}"; + return _successfulTargets.FirstOrDefault(t => t.Coordinate == coordStr); + } + } + + public List GetAllTargets() + { + lock (_lock) + { + return new List(_successfulTargets); + } + } + + public int GetTotalCount() + { + lock (_lock) + { + return _successfulTargets.Count; + } + } + + public long GetTotalLootCollected() + { + lock (_lock) + { + return _successfulTargets.Sum(t => + t.TotalLootCollected.Metal + + t.TotalLootCollected.Crystal + + t.TotalLootCollected.Deuterium + ); + } + } + + + public void ForceSave() + { + lock (_lock) + { + SaveToFile_NoThrow(force: true); + } + } + + private void LoadFromFile() + { + if (string.IsNullOrEmpty(_filePath) || !File.Exists(_filePath)) + { + return; + } + + try + { + string json = File.ReadAllText(_filePath); + var loaded = JsonConvert.DeserializeObject>(json); + if (loaded != null) + { + _successfulTargets = loaded; + } + } + catch (Exception ex) + { + _successfulTargets = new List(); + WriteError_NoThrow($"LoadFromFile failed: {ex.GetType().Name}: {ex.Message}"); + } + } + + private void SaveIfNeeded_NoThrow() + { + if (string.IsNullOrEmpty(_filePath)) + return; + + var now = DateTime.UtcNow; + + bool timeDue = (now - _lastSaveUtc) >= _saveInterval; + bool changesDue = _pendingChanges >= SaveAfterChanges; + + if (timeDue || changesDue) + { + SaveToFile_NoThrow(force: false); + } + } + + private void SaveToFile_NoThrow(bool force) + { + if (string.IsNullOrEmpty(_filePath)) + return; + + try + { + if (!force && _pendingChanges <= 0) + return; + + string json = JsonConvert.SerializeObject(_successfulTargets, Formatting.Indented); + + string tmpPath = _filePath + ".tmp"; + + File.WriteAllText(tmpPath, json); + + if (File.Exists(_filePath)) + { + try + { + File.Replace(tmpPath, _filePath, null); + } + catch + { + File.Delete(_filePath); + File.Move(tmpPath, _filePath); + } + } + else + { + File.Move(tmpPath, _filePath); + } + + _lastSaveUtc = DateTime.UtcNow; + _pendingChanges = 0; + } + catch (Exception ex) + { + WriteError_NoThrow($"SaveToFile failed: {ex.GetType().Name}: {ex.Message}"); + } + } + + private void CleanupUnsafeGrowth_NoThrow() + { + try + { + var cutoff = DateTime.UtcNow - _staleCutoff; + _successfulTargets.RemoveAll(t => t.LastAttacked < cutoff); + + if (_successfulTargets.Count > MaxTargets) + { + _successfulTargets = _successfulTargets + .OrderByDescending(t => t.LastAttacked) + .Take(MaxTargets) + .ToList(); + } + } + catch (Exception ex) + { + WriteError_NoThrow($"Cleanup failed: {ex.GetType().Name}: {ex.Message}"); + } + } + + private static string MakeSafeFileName(string fileName) + { + if (string.IsNullOrWhiteSpace(fileName)) + return "autofarm_successful_targets.json"; + + string name = Path.GetFileName(fileName); + + if (!name.EndsWith(".json", StringComparison.OrdinalIgnoreCase)) + name += ".json"; + + foreach (char c in Path.GetInvalidFileNameChars()) + { + name = name.Replace(c, '_'); + } + + return name; + } + + private void WriteError_NoThrow(string message) + { + try + { + string baseDir = Path.GetDirectoryName(_filePath); + if (string.IsNullOrEmpty(baseDir)) + return; + + string errPath = Path.Combine(baseDir, "autofarm_successful_targets.errors.log"); + string line = $"[{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC] {message}{Environment.NewLine}"; + File.AppendAllText(errPath, line); + } + catch + { + + } + } + } +} diff --git a/TBot/Model/SharedFarmState.cs b/TBot/Model/SharedFarmState.cs new file mode 100644 index 00000000..299b993c --- /dev/null +++ b/TBot/Model/SharedFarmState.cs @@ -0,0 +1,601 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using Newtonsoft.Json; +using TBot.Ogame.Infrastructure.Models; +using TBot.Ogame.Infrastructure.Enums; + +namespace TBot.Model +{ + + public class FarmBotInstance + { + public string Name { get; set; } + public DateTime LastHeartbeat { get; set; } + public bool IsActive { get; set; } + + public bool IsAlive(TimeSpan timeout) + { + return (DateTime.UtcNow - LastHeartbeat) < timeout; + } + } + + public class ScannedTarget + { + public string Coord { get; set; } + public string ScannedBy { get; set; } + public DateTime ScannedAt { get; set; } + public DateTime ExpiresAt { get; set; } + public long TotalResources { get; set; } + public bool HasFleet { get; set; } + public bool HasDefense { get; set; } + + public bool IsExpired() + { + return DateTime.UtcNow >= ExpiresAt; + } + + public Coordinate GetCoordinate() + { + if (string.IsNullOrWhiteSpace(Coord)) + return null; + + var parts = Coord.Split(':'); + if (parts.Length != 3) + return null; + + if (!int.TryParse(parts[0], out var g)) return null; + if (!int.TryParse(parts[1], out var s)) return null; + if (!int.TryParse(parts[2], out var p)) return null; + + return new Coordinate + { + Galaxy = g, + System = s, + Position = p, + Type = Celestials.Planet + }; + } + } + + public class ClaimedAttack + { + public string Coord { get; set; } + public string ClaimedBy { get; set; } + public DateTime ClaimedAt { get; set; } + public DateTime? AttackSentAt { get; set; } + public DateTime ReturnsAt { get; set; } + + public bool IsExpired() + { + if (DateTime.UtcNow >= ReturnsAt) + return true; + + if (!AttackSentAt.HasValue && (DateTime.UtcNow - ClaimedAt).TotalHours > 2) + return true; + + return false; + } + + public Coordinate GetCoordinate() + { + if (string.IsNullOrWhiteSpace(Coord)) + return null; + + var parts = Coord.Split(':'); + if (parts.Length != 3) + return null; + + if (!int.TryParse(parts[0], out var g)) return null; + if (!int.TryParse(parts[1], out var s)) return null; + if (!int.TryParse(parts[2], out var p)) return null; + + return new Coordinate + { + Galaxy = g, + System = s, + Position = p, + Type = Celestials.Planet + }; + } + } + + public class SharedFarmState + { + private const string DEFAULT_FILENAME = "shared_farm_state.json"; + private const int MAX_RETRY_ATTEMPTS = 5; + private const int RETRY_DELAY_MS = 100; + private const string MUTEX_GLOBAL_NAME = @"Global\TBot_SharedFarmState_Mutex"; + private const string MUTEX_LOCAL_NAME = @"Local\TBot_SharedFarmState_Mutex"; + + public string Version { get; set; } = "1.1"; + public DateTime LastUpdated { get; set; } + public List Instances { get; set; } + public List ScannedTargets { get; set; } + public List ClaimedAttacks { get; set; } + + private static readonly object _globalLock = new object(); + private string _filePath; + + public SharedFarmState() + { + Instances = new List(); + ScannedTargets = new List(); + ClaimedAttacks = new List(); + LastUpdated = DateTime.UtcNow; + } + + public static SharedFarmState Initialize(string instanceName = null) + { + string dataFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "data"); + if (!Directory.Exists(dataFolder)) + Directory.CreateDirectory(dataFolder); + + string filePath = Path.Combine(dataFolder, DEFAULT_FILENAME); + var state = LoadFromFile(filePath); + state._filePath = filePath; + + if (!string.IsNullOrEmpty(instanceName)) + { + state.UpdateInstanceHeartbeat(instanceName); + state.Save(); + } + + return state; + } + + private static SharedFarmState LoadFromFile(string filePath) + { + lock (_globalLock) + { + if (!File.Exists(filePath)) + return new SharedFarmState(); + + for (int attempt = 0; attempt < MAX_RETRY_ATTEMPTS; attempt++) + { + try + { + string json = File.ReadAllText(filePath); + var state = JsonConvert.DeserializeObject(json); + + if (state != null) + { + state._filePath = filePath; + state.CleanupExpiredEntries(); + return state; + } + } + catch (IOException) when (attempt < MAX_RETRY_ATTEMPTS - 1) + { + Thread.Sleep(RETRY_DELAY_MS * (attempt + 1)); + } + catch (JsonException) + { + try + { + string backupPath = filePath + ".corrupt." + DateTime.UtcNow.Ticks; + File.Move(filePath, backupPath); + } + catch {} + return new SharedFarmState(); + } + catch + { + return new SharedFarmState(); + } + } + + return new SharedFarmState(); + } + } + + private static Mutex CreateNamedMutex() + { + try + { + return new Mutex(false, MUTEX_GLOBAL_NAME); + } + catch + { + return new Mutex(false, MUTEX_LOCAL_NAME); + } + } + + private static bool WithCrossProcessMutex(TimeSpan timeout, Action action) + { + using (var mutex = CreateNamedMutex()) + { + try + { + if (!mutex.WaitOne(timeout)) + return false; + + try + { + action(); + return true; + } + finally + { + try { mutex.ReleaseMutex(); } catch { } + } + } + catch (AbandonedMutexException) + { + try + { + action(); + return true; + } + finally + { + try { mutex.ReleaseMutex(); } catch { } + } + } + catch + { + return false; + } + } + } + + public void Save() + { + if (string.IsNullOrEmpty(_filePath)) + return; + + WithCrossProcessMutex(TimeSpan.FromSeconds(5), () => + { + SaveNoMutex(); + }); + } + + private void SaveNoMutex() + { + lock (_globalLock) + { + LastUpdated = DateTime.UtcNow; + CleanupExpiredEntries(); + + string tempPath = _filePath + ".tmp"; + string backupPath = _filePath + ".bak"; + + try + { + string json = JsonConvert.SerializeObject(this, Formatting.Indented); + File.WriteAllText(tempPath, json); + + try + { + if (File.Exists(_filePath)) + File.Replace(tempPath, _filePath, backupPath, ignoreMetadataErrors: true); + else + File.Move(tempPath, _filePath); + } + catch + { + if (File.Exists(_filePath)) + File.Delete(_filePath); + + File.Move(tempPath, _filePath); + } + } + catch + { + if (File.Exists(tempPath)) + { + try { File.Delete(tempPath); } catch { } + } + } + } + } + + public void Reload() + { + if (string.IsNullOrEmpty(_filePath)) + return; + + var fresh = LoadFromFile(_filePath); + fresh._filePath = _filePath; + + Version = fresh.Version; + LastUpdated = fresh.LastUpdated; + Instances = fresh.Instances; + ScannedTargets = fresh.ScannedTargets; + ClaimedAttacks = fresh.ClaimedAttacks; + } + + public void UpdateInstanceHeartbeat(string instanceName) + { + var instance = Instances.FirstOrDefault(i => i.Name == instanceName); + if (instance == null) + { + instance = new FarmBotInstance + { + Name = instanceName, + IsActive = true + }; + Instances.Add(instance); + } + + instance.LastHeartbeat = DateTime.UtcNow; + instance.IsActive = true; + } + + public List GetActiveBots() + { + return Instances.Where(i => i.IsAlive(TimeSpan.FromMinutes(5))).ToList(); + } + + public ScannedTarget GetRecentScan(Coordinate coord, int withinHours = 8) + { + string coordStr = $"{coord.Galaxy}:{coord.System}:{coord.Position}"; + return ScannedTargets.FirstOrDefault(s => + s.Coord == coordStr && + (DateTime.UtcNow - s.ScannedAt).TotalHours < withinHours); + } + + public void AddScannedTarget(Coordinate coord, string scannedBy, long totalResources, + bool hasFleet, bool hasDefense, int expiresInHours = 8) + { + string coordStr = $"{coord.Galaxy}:{coord.System}:{coord.Position}"; + + ScannedTargets.RemoveAll(s => s.Coord == coordStr); + + ScannedTargets.Add(new ScannedTarget + { + Coord = coordStr, + ScannedBy = scannedBy, + ScannedAt = DateTime.UtcNow, + ExpiresAt = DateTime.UtcNow.AddHours(expiresInHours), + TotalResources = totalResources, + HasFleet = hasFleet, + HasDefense = hasDefense + }); + } + + public void UpdateScannedTarget(Coordinate coord, long totalResources, bool hasFleet, bool hasDefense) + { + string coordStr = $"{coord.Galaxy}:{coord.System}:{coord.Position}"; + var existing = ScannedTargets.FirstOrDefault(s => s.Coord == coordStr); + + if (existing != null) + { + existing.TotalResources = totalResources; + existing.HasFleet = hasFleet; + existing.HasDefense = hasDefense; + } + } + + public List GetGoodTargets(long minResources, int maxAgeMinutes = 30) + { + return ScannedTargets + .Where(s => !s.IsExpired()) + .Where(s => (DateTime.UtcNow - s.ScannedAt).TotalMinutes <= maxAgeMinutes) + .Where(s => s.TotalResources > 0) + .Where(s => s.TotalResources >= minResources) + .Where(s => !s.HasFleet) + .Where(s => !s.HasDefense) + .Where(s => !IsTargetClaimed(s.GetCoordinate())) + .OrderByDescending(s => s.TotalResources) + .ToList(); + } + + public List GetTargetsWithoutReports(string scannedByInstance = null) + { + var query = ScannedTargets + .Where(s => !s.IsExpired()) + .Where(s => s.TotalResources == 0); + + if (!string.IsNullOrEmpty(scannedByInstance)) + query = query.Where(s => s.ScannedBy == scannedByInstance); + + return query.OrderBy(s => s.ScannedAt).ToList(); + } + + public bool ShouldRescan(Coordinate coord, int maxAgeMinutes = 30) + { + string coordStr = $"{coord.Galaxy}:{coord.System}:{coord.Position}"; + var scan = ScannedTargets.FirstOrDefault(s => s.Coord == coordStr && !s.IsExpired()); + + if (scan == null) return true; + if (scan.TotalResources == 0) return true; + + return (DateTime.UtcNow - scan.ScannedAt).TotalMinutes > maxAgeMinutes; + } + + public bool TryClaimAttack(Coordinate coord, string claimedBy, DateTime returnsAt) + { + Reload(); + return TryClaimAttack_NoReload(coord, claimedBy, returnsAt); + } + + private bool TryClaimAttack_NoReload(Coordinate coord, string claimedBy, DateTime returnsAt) + { + string coordStr = $"{coord.Galaxy}:{coord.System}:{coord.Position}"; + + var existingClaim = ClaimedAttacks.FirstOrDefault(c => c.Coord == coordStr && !c.IsExpired()); + if (existingClaim != null && existingClaim.ClaimedBy != claimedBy) + return false; + + ClaimedAttacks.RemoveAll(c => c.Coord == coordStr); + ClaimedAttacks.Add(new ClaimedAttack + { + Coord = coordStr, + ClaimedBy = claimedBy, + ClaimedAt = DateTime.UtcNow, + AttackSentAt = null, + ReturnsAt = returnsAt + }); + + return true; + } + + public void MarkAttackSent(Coordinate coord, string claimedBy) + { + string coordStr = $"{coord.Galaxy}:{coord.System}:{coord.Position}"; + var claim = ClaimedAttacks.FirstOrDefault(c => c.Coord == coordStr && c.ClaimedBy == claimedBy); + if (claim != null) + { + claim.AttackSentAt = DateTime.UtcNow; + } + } + + public bool IsTargetClaimed(Coordinate coord) + { + if (coord == null) return false; + string coordStr = $"{coord.Galaxy}:{coord.System}:{coord.Position}"; + return ClaimedAttacks.Any(c => c.Coord == coordStr && !c.IsExpired()); + } + + public string GetClaimOwner(Coordinate coord) + { + if (coord == null) return null; + string coordStr = $"{coord.Galaxy}:{coord.System}:{coord.Position}"; + return ClaimedAttacks.FirstOrDefault(c => c.Coord == coordStr && !c.IsExpired())?.ClaimedBy; + } + + private void CleanupExpiredEntries() + { + ScannedTargets.RemoveAll(s => s.IsExpired()); + ClaimedAttacks.RemoveAll(c => c.IsExpired()); + + foreach (var instance in Instances) + { + if (!instance.IsAlive(TimeSpan.FromMinutes(5))) + instance.IsActive = false; + } + } + + public string GetStats() + { + CleanupExpiredEntries(); + return $"Instances: {Instances.Count(i => i.IsActive)} active, " + + $"Scanned: {ScannedTargets.Count}, " + + $"Claims: {ClaimedAttacks.Count}"; + } + + public static void AddScannedTargetAtomic(string filePath, Coordinate coord, string scannedBy, + long totalResources, bool hasFleet, bool hasDefense) + { + WithCrossProcessMutex(TimeSpan.FromSeconds(5), () => + { + var state = LoadFromFile(filePath); + state._filePath = filePath; + + state.AddScannedTarget(coord, scannedBy, totalResources, hasFleet, hasDefense); + state.SaveNoMutex(); + }); + } + + public static void UpdateScannedTargetAtomic(string filePath, Coordinate coord, long totalResources, bool hasFleet, bool hasDefense) + { + WithCrossProcessMutex(TimeSpan.FromSeconds(5), () => + { + var state = LoadFromFile(filePath); + state._filePath = filePath; + + state.UpdateScannedTarget(coord, totalResources, hasFleet, hasDefense); + state.SaveNoMutex(); + }); + } + + public static void MarkAttackSentAtomic(string filePath, Coordinate coord, string claimedBy) + { + WithCrossProcessMutex(TimeSpan.FromSeconds(5), () => + { + var state = LoadFromFile(filePath); + state._filePath = filePath; + + state.MarkAttackSent(coord, claimedBy); + state.SaveNoMutex(); + }); + } + + public static bool TryReserveScan(string filePath, Coordinate coord, string scannedBy, + int withinHours = 8, int maxAgeMinutes = 30) + { + bool result = false; + + bool acquired = WithCrossProcessMutex(TimeSpan.FromSeconds(5), () => + { + var state = LoadFromFile(filePath); + state._filePath = filePath; + + if (state.ShouldRescan(coord, maxAgeMinutes)) + { + state.AddScannedTarget(coord, scannedBy, 0, false, false); + state.SaveNoMutex(); + result = true; + return; + } + + string coordStr = $"{coord.Galaxy}:{coord.System}:{coord.Position}"; + var recentScan = state.ScannedTargets.FirstOrDefault(s => + s.Coord == coordStr && + (DateTime.UtcNow - s.ScannedAt).TotalHours < withinHours); + + if (recentScan != null) + { + if (recentScan.ScannedBy == scannedBy) + { + if (state.ShouldRescan(coord, maxAgeMinutes)) + { + state.AddScannedTarget(coord, scannedBy, 0, false, false); + state.SaveNoMutex(); + result = true; + return; + } + result = false; + return; + } + else + { + if ((DateTime.UtcNow - recentScan.ScannedAt).TotalMinutes <= maxAgeMinutes) + { + result = false; + return; + } + + state.AddScannedTarget(coord, scannedBy, 0, false, false); + state.SaveNoMutex(); + result = true; + return; + } + } + + state.AddScannedTarget(coord, scannedBy, 0, false, false); + state.SaveNoMutex(); + result = true; + }); + + if (!acquired) + return false; + + return result; + } + + public static bool TryClaimAttackAtomic(string filePath, Coordinate coord, string claimedBy, DateTime returnsAt) + { + bool claimed = false; + + bool acquired = WithCrossProcessMutex(TimeSpan.FromSeconds(5), () => + { + var state = LoadFromFile(filePath); + state._filePath = filePath; + + claimed = state.TryClaimAttack_NoReload(coord, claimedBy, returnsAt); + + if (claimed) + state.SaveNoMutex(); + }); + + if (!acquired) + return false; + + return claimed; + } + } +} diff --git a/TBot/Services/InstanceManager.cs b/TBot/Services/InstanceManager.cs index fc2ac212..f06c6e6c 100644 --- a/TBot/Services/InstanceManager.cs +++ b/TBot/Services/InstanceManager.cs @@ -149,6 +149,11 @@ public async void OnSettingsChanged() { string alias = instanceToBeInited.Alias; _logger.WriteLog(LogLevel.Information, LogSender.Main, $"Asynchronously initializing instance \"{alias}\" \"{cInstanceSettingPath}\""); awaitingInstances.Add(StartTBotMain(cInstanceSettingPath, alias)); + //Generate random sleeptime + Random waitTime = new Random(); + int millisecondsTimeout = waitTime.Next(30, 60) * 1000; + //Put the thread to sleep + System.Threading.Thread.Sleep(millisecondsTimeout); } // Await initialization and add initialized instances diff --git a/TBot/Services/TBotMain.cs b/TBot/Services/TBotMain.cs index 6a09f4c5..2f89bc92 100644 --- a/TBot/Services/TBotMain.cs +++ b/TBot/Services/TBotMain.cs @@ -135,7 +135,7 @@ private Credentials GetCredentialsFromSettings() { private Device GetDeviceFromSettings() { return new() { - Name = ((string) InstanceSettings.Credentials.DeviceConf.Name).ToUpper(), + Name = ((string) InstanceSettings.Credentials.DeviceConf.Name), System = (string) InstanceSettings.Credentials.DeviceConf.System ?? "", Browser = (string) InstanceSettings.Credentials.DeviceConf.Browser ?? "", UserAgent = (string) InstanceSettings.Credentials.DeviceConf.UserAgent ?? "", @@ -185,7 +185,7 @@ private async Task InitializeOgame() { _ogameService.Initialize(GetCredentialsFromSettings(), GetDeviceFromSettings(), proxy, (string) host, int.Parse(port), (string) captchaKey); - await _ogameService.SetUserAgent((string) InstanceSettings.Credentials.DeviceConf.UserAgent); + await Task.Delay(RandomizeHelper.CalcRandomInterval(IntervalType.AFewSeconds)); } private async Task ResolveCaptcha() { @@ -376,6 +376,7 @@ public async Task InitializeFeature(Feature feat) { long dueTime = feat switch { Feature.Defender => RandomizeHelper.CalcRandomInterval(IntervalType.AFewSeconds), Feature.BrainAutobuildCargo => RandomizeHelper.CalcRandomInterval(IntervalType.AMinuteOrTwo), + Feature.BrainAutobuildDefence => RandomizeHelper.CalcRandomInterval(IntervalType.AMinuteOrTwo), Feature.BrainAutoRepatriate => RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds), Feature.BrainAutoMine => RandomizeHelper.CalcRandomInterval(IntervalType.AFewSeconds), Feature.BrainLifeformAutoMine => RandomizeHelper.CalcRandomInterval(IntervalType.AFewSeconds), @@ -461,10 +462,10 @@ public async Task EditSettings(Celestial celestial = null, Feature feature } if (feature == Feature.BrainAutoRepatriate || feature == Feature.Null) { - jsonObj["Brain"]["AutoRepatriate"]["Target"]["Galaxy"] = (int) celestial.Coordinate.Galaxy; - jsonObj["Brain"]["AutoRepatriate"]["Target"]["System"] = (int) celestial.Coordinate.System; - jsonObj["Brain"]["AutoRepatriate"]["Target"]["Position"] = (int) celestial.Coordinate.Position; - jsonObj["Brain"]["AutoRepatriate"]["Target"]["Type"] = type; + jsonObj["Brain"]["AutoRepatriate"]["Target"][0]["Galaxy"] = (int) celestial.Coordinate.Galaxy; + jsonObj["Brain"]["AutoRepatriate"]["Target"][0]["System"] = (int) celestial.Coordinate.System; + jsonObj["Brain"]["AutoRepatriate"]["Target"][0]["Position"] = (int) celestial.Coordinate.Position; + jsonObj["Brain"]["AutoRepatriate"]["Target"][0]["Type"] = type; } if (feature == Feature.Expeditions || feature == Feature.Null) { @@ -841,9 +842,8 @@ await SendTelegramMessage($"Auction done with Resources of Planet {celestial.Nam } } - public void TelegramCollect() { - _fleetScheduler.Collect(); - + public void TelegramCollect(bool noLimit = false) { + _fleetScheduler.Collect(noLimit); return; } @@ -899,7 +899,7 @@ public async Task TelegramJumpGate(Celestial origin, Coordinate destination, str if (mode.Equals("auto")) { float cargoBonus = 0; if (origin.LFBonuses != null && origin.LFBonuses.Ships != null && origin.LFBonuses.Ships.Count > 0 && origin.LFBonuses.Ships.ContainsKey((int) Buildables.SmallCargo)) { - cargoBonus = origin.LFBonuses.Ships.GetValueOrDefault((int) Buildables.SmallCargo).Cargo; + cargoBonus = origin.LFBonuses.Ships.GetValueOrDefault((int) Buildables.SmallCargo).CargoCapacity; } long idealSmallCargo = _helpersService.CalcShipNumberForPayload(payload, Buildables.SmallCargo, userData.researches.HyperspaceTechnology, userData.serverData, cargoBonus, userData.userInfo.Class, userData.serverData.ProbeCargo); @@ -908,7 +908,7 @@ public async Task TelegramJumpGate(Celestial origin, Coordinate destination, str } else { cargoBonus = 0; if (origin.LFBonuses != null && origin.LFBonuses.Ships != null && origin.LFBonuses.Ships.Count > 0 && origin.LFBonuses.Ships.ContainsKey((int) Buildables.LargeCargo)) { - cargoBonus = origin.LFBonuses.Ships.GetValueOrDefault((int) Buildables.LargeCargo).Cargo; + cargoBonus = origin.LFBonuses.Ships.GetValueOrDefault((int) Buildables.LargeCargo).CargoCapacity; } long idealLargeCargo = _helpersService.CalcShipNumberForPayload(payload, Buildables.LargeCargo, userData.researches.HyperspaceTechnology, userData.serverData, cargoBonus, userData.userInfo.Class, userData.serverData.ProbeCargo); if (idealLargeCargo <= origin.Ships.GetAmount(Buildables.LargeCargo)) { @@ -1445,6 +1445,17 @@ public async Task TelegramRetireFleet(int fleetId) { } _fleetScheduler.RetireFleet(ToRecallFleet); } + public async Task TelegramRetireFleetM(Missions mission) { + userData.fleets = await _fleetScheduler.UpdateFleets(); + List ToRecallFleet = userData.fleets.Where(f => f.Mission == mission).ToList(); + if (ToRecallFleet.Count == 0) { + await SendTelegramMessage($"Unable to recall fleet ! No mission ?"); + return; + } + foreach (var fleet in ToRecallFleet) { + _fleetScheduler.RetireFleet(fleet); + } + } public async Task TelegramMesgAttacker(string message) { userData.attacks = await _ogameService.GetAttacks(); diff --git a/TBot/Services/TelegramMessenger.cs b/TBot/Services/TelegramMessenger.cs index effe657e..1815b268 100644 --- a/TBot/Services/TelegramMessenger.cs +++ b/TBot/Services/TelegramMessenger.cs @@ -163,7 +163,8 @@ public async Task RemoveTBotInstance(TBotMain instance) { public async Task SendMessage(string message, ParseMode parseMode = ParseMode.Html, CancellationToken cancellationToken = default) { try { isTyping = false; - await Client.SendTextMessageAsync( + //await Client.SendTextMessageAsync( + await Client.SendMessage( chatId: Channel, text: message, parseMode: parseMode, @@ -178,9 +179,10 @@ public Task SendTyping(CancellationToken cancellationToken) { isTyping = true; Task.Run(async () => { while (isTyping) { - await Client.SendChatActionAsync( + //await Client.SendChatActionAsync( + await Client.SendChatAction( chatId: Channel, - chatAction: ChatAction.Typing, + action: ChatAction.Typing,//ChatAction.Typing, cancellationToken: cancellationToken); await Task.Delay(3000); } @@ -191,7 +193,8 @@ await Client.SendChatActionAsync( public async Task SendReplyMarkup(string text, IEnumerable> buttons, CancellationToken ct) { isTyping = false; var inlineKeyboard = new InlineKeyboardMarkup(buttons); - await Client.SendTextMessageAsync( + //await Client.SendTextMessageAsync( + await Client.SendMessage( chatId: Channel, text: text, replyMarkup: inlineKeyboard, @@ -202,7 +205,9 @@ await Client.SendTextMessageAsync( public async Task SendMessage(ITelegramBotClient client, Chat chat, string message, ParseMode parseMode = ParseMode.Html) { try { isTyping = false; - await client.SendTextMessageAsync(chat, message, parseMode); + //await client.SendTextMessageAsync(chat, message, parseMode); + //await client.SendTextMessageAsync(chat, message, null, parseMode); + await client.SendMessage(chat, message, parseMode); } catch (Exception e) { _logger.WriteLog(LogLevel.Error, LogSender.Tbot, $"Could not send Telegram message: an exception has occurred: {e.Message}"); } @@ -235,6 +240,7 @@ public async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, "/wakeup", "/build", "/collect", + "/collectall", "/collectdeut", "/minexpecargo", "/stopexpe", @@ -253,6 +259,7 @@ public async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, "/getinfo", "/celestial", "/cancel", + "/cancelmission", "/cancelghostsleep", "/editsettings", "/spycrash", @@ -419,12 +426,14 @@ await SendMessage(botClient, message.Chat, "/spycrash - Create a debris field by crashing a probe on target or automatically selected planet. Format: /spycrash 2:41:9/auto\n" + "/recall - Enable/disable fleet auto recall. Format: /recall true/false\n" + "/collect - Collect planets resources to JSON setting celestial\n" + + "/collectall - Collect planets resources to JSON setting celestial with no MinimumResources\n" + "/build - Try to build buildable on each planet. Build max possible if no number value sent /build LightFighter [100]\n" + "/collectdeut - Collect planets only deut resources -> to JSON repatriate setting celestial\n" + "/msg - Send a message to current attacker. Format: /msg hello dude\n" + "/sleep - Stop bot for the specified amount of hours. Format: /sleep 4h3m or 3m50s\n" + "/wakeup - Wakeup bot\n" + "/cancel - Cancel fleet with specified ID. Format: /cancel 65656\n" + + "/cancelmission - Cancel all fleets with specified mission. Format: /cancel Deploy or other mission\n" + "/getcelestials - Return the list of your celestials\n" + "/attacked - check if you're (still) under attack\n" + "/celestial - Update program current celestial target. Format: /celestial 2:45:8 Moon/Planet\n" + @@ -789,6 +798,18 @@ await SendMessage(botClient, message.Chat, return; + case "/cancelmission": + if (message.Text.Split(' ').Length != 2) { + await SendMessage(botClient, message.Chat, "Mission argument required!"); + return; + } + arg = message.Text.Split(' ')[1]; + Missions.TryParse(arg, out mission); + + await currInstance.TelegramRetireFleetM(mission); + return; + + case "/cancelghostsleep": if (message.Text.Split(' ').Length != 1) { await SendMessage(botClient, message.Chat, "No argument accepted with this command!"); @@ -899,6 +920,16 @@ await SendMessage(botClient, message.Chat, return; + case "/collectall": + if (message.Text.Split(' ').Length != 1) { + await SendMessage(botClient, message.Chat, "No argument accepted with this command!"); + return; + } + + currInstance.TelegramCollect(true); + return; + + case "/collectdeut": if (message.Text.Split(' ').Length != 2) { await SendMessage(botClient, message.Chat, "Need minimum deut amount argument /collectdeut 500000"); @@ -1278,7 +1309,8 @@ await SendMessage(botClient, message.Chat, async Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, CancellationToken cancellationToken) { try { if (exception is ApiRequestException apiRequestException) { - await botClient.SendTextMessageAsync(Channel, apiRequestException.ToString()); + //await botClient.SendTextMessageAsync(Channel, apiRequestException.ToString()); + await botClient.SendMessage(Channel, apiRequestException.ToString()); } } catch { } } @@ -1291,7 +1323,8 @@ public async Task TelegramBot() { var receiverOptions = new ReceiverOptions { AllowedUpdates = Array.Empty(), - ThrowPendingUpdates = true + DropPendingUpdates = false, + //ThrowPendingUpdates = true }; receivingTask = Client.ReceiveAsync(HandleUpdateAsync, HandleErrorAsync, receiverOptions, ct); diff --git a/TBot/Services/UserData.cs b/TBot/Services/UserData.cs index 228a821d..2330f71f 100644 --- a/TBot/Services/UserData.cs +++ b/TBot/Services/UserData.cs @@ -9,31 +9,33 @@ namespace Tbot.Services { - // Data required by TBotMain instances - public class UserData { - public Server serverInfo = new(); - public ServerData serverData; - public UserInfo userInfo; - public AllianceClass allianceClass; - public List celestials; - public List fleets; - public List attacks; - public Slots slots; - public Researches researches; + public class UserData { + public Server serverInfo = new(); + public ServerData serverData; + public UserInfo userInfo; + public AllianceClass allianceClass; + public List celestials; + public List fleets; + public List attacks; + public Slots slots; + public Researches researches; - public List scheduledFleets; - public List farmTargets; - public Dictionary discoveryBlackList; - public float lastDOIR; - public float nextDOIR; - public Staff staff; - public bool isSleeping = false; - } + public List scheduledFleets; + public List farmTargets; + public Dictionary discoveryBlackList; + public float lastDOIR; + public float nextDOIR; + public Staff staff; + public bool isSleeping = false; - // Data used by TelegramMessenger binded to TBotMain - public class TelegramUserData { - public Celestial CurrentCelestial; // Willingly left to null - public Celestial CurrentCelestialToSave; // Willingly left to null - public Missions Mission = Missions.None; - } -} + public int autoFarmLastGalaxy = 0; + public int autoFarmLastSystem = 0; + public int autoFarmLastRangeIndex = 0; + } + + public class TelegramUserData { + public Celestial CurrentCelestial; + public Celestial CurrentCelestialToSave; + public Missions Mission = Missions.None; + } +} \ No newline at end of file diff --git a/TBot/TBot.csproj b/TBot/TBot.csproj index b6845c3b..3418f260 100644 --- a/TBot/TBot.csproj +++ b/TBot/TBot.csproj @@ -2,18 +2,18 @@ Exe - net6.0 + net9.0 disable TBot - 0.3.4 + 3.3.9 Tbot.Program AnyCPU;x64;x86 - 2023 © TBot + 2026 © TBot https://licenses.nuget.org/MIT https://www.ogame-tbot.net https://github.com/ogame-tbot/TBot - 0.3.4.0 - 0.3.4.0 + 3.3.9 + 3.3.9 OGame Bot False ELK-Lab.pfx @@ -42,12 +42,12 @@ - - + + - - - + + + diff --git a/TBot/Workers/AutoDiscoveryWorker.cs b/TBot/Workers/AutoDiscoveryWorker.cs index ba48bdca..e4b2c261 100644 --- a/TBot/Workers/AutoDiscoveryWorker.cs +++ b/TBot/Workers/AutoDiscoveryWorker.cs @@ -21,6 +21,21 @@ public class AutoDiscoveryWorker : WorkerBase { private readonly IFleetScheduler _fleetScheduler; private readonly ICalculationService _calculationService; private readonly ITBotOgamedBridge _tbotOgameBridge; + + private sealed class OriginCursor { + public int System; + public int NextPosition; + } + private readonly Dictionary _originCursors = new(); + + private sealed class DiscoveryRunContext { + public int FleetsToSend; + public bool Stop; + public int Skips; + public int GlobalAttempts; + public int MaxGlobalAttempts; + } + public AutoDiscoveryWorker(ITBotMain parentInstance, IOgameService ogameService, IFleetScheduler fleetScheduler, @@ -33,154 +48,379 @@ public AutoDiscoveryWorker(ITBotMain parentInstance, _tbotOgameBridge = tbotOgameBridge; } + private sealed class CoordinateComparer : IEqualityComparer { + public bool Equals(Coordinate x, Coordinate y) { + if (ReferenceEquals(x, y)) return true; + if (x is null || y is null) return false; + return x.Galaxy == y.Galaxy && x.System == y.System && x.Position == y.Position && x.Type == y.Type; + } + + public int GetHashCode(Coordinate obj) { + if (obj is null) return 0; + unchecked { + int hash = 17; + hash = hash * 31 + obj.Galaxy.GetHashCode(); + hash = hash * 31 + obj.System.GetHashCode(); + hash = hash * 31 + obj.Position.GetHashCode(); + hash = hash * 31 + obj.Type.GetHashCode(); + return hash; + } + } + } + + private static readonly IEqualityComparer _coordinateComparer = new CoordinateComparer(); + + private void EnsureBlacklistInitialized() { + if (_tbotInstance?.UserData == null) return; + + if (_tbotInstance.UserData.discoveryBlackList == null) { + _tbotInstance.UserData.discoveryBlackList = new Dictionary(_coordinateComparer); + return; + } + + if (!ReferenceEquals(_tbotInstance.UserData.discoveryBlackList.Comparer, _coordinateComparer)) { + var old = _tbotInstance.UserData.discoveryBlackList; + var fresh = new Dictionary(_coordinateComparer); + foreach (var kv in old) { + + if (fresh.TryGetValue(kv.Key, out var existing)) { + if (kv.Value > existing) fresh[kv.Key] = kv.Value; + } else { + fresh[kv.Key] = kv.Value; + } + } + _tbotInstance.UserData.discoveryBlackList = fresh; + } + } + + private void CleanupExpiredBlacklistAll(DateTime now) { + if (_tbotInstance?.UserData?.discoveryBlackList == null) return; + var toRemove = _tbotInstance.UserData.discoveryBlackList + .Where(kv => kv.Value <= now) + .Select(kv => kv.Key) + .ToList(); + foreach (var k in toRemove) + _tbotInstance.UserData.discoveryBlackList.Remove(k); + } + + private bool IsBlacklistedAndActive(Coordinate c, DateTime now) { + if (_tbotInstance?.UserData?.discoveryBlackList == null) return false; + return _tbotInstance.UserData.discoveryBlackList.TryGetValue(c, out var until) && until > now; + } + + private void UpsertBlacklist(Coordinate c, DateTime until) { + if (_tbotInstance?.UserData?.discoveryBlackList == null) return; + _tbotInstance.UserData.discoveryBlackList[c] = until; + } + + private List GetConfiguredOrigins(List celestials) { + var result = new List(); + if (celestials == null || celestials.Count == 0) return result; + + var originsCfg = _tbotInstance?.InstanceSettings?.AutoDiscovery?.Origin; + if (originsCfg == null) return result; + + foreach (var o in originsCfg) { + int galaxy = Convert.ToInt32(o.Galaxy); + int system = Convert.ToInt32(o.System); + int position = Convert.ToInt32(o.Position); + string typeStr = Convert.ToString(o.Type) ?? "Planet"; + + Celestials wantedType = Celestials.Planet; + if (Enum.TryParse(typeStr, true, out Celestials parsed)) + wantedType = parsed; + + var found = celestials.FirstOrDefault(c => + c?.Coordinate != null && + c.Coordinate.Galaxy == galaxy && + c.Coordinate.System == system && + c.Coordinate.Position == position && + c.Coordinate.Type == wantedType + ); + + if (found != null) + result.Add(found); + else + _tbotInstance.log(LogLevel.Warning, LogSender.AutoDiscovery, + $"AutoDiscovery origin not found in user celestials: {galaxy}:{system}:{position} ({typeStr})"); + } + + return result; + } + + private string OriginKey(Celestial origin) { + + return $"{origin.Coordinate.Galaxy}:{origin.Coordinate.System}:{origin.Coordinate.Position}:{origin.Coordinate.Type}"; + } + + private OriginCursor GetOrInitCursor(Celestial origin) { + var key = OriginKey(origin); + if (!_originCursors.TryGetValue(key, out var cursor) || cursor == null) { + cursor = new OriginCursor { + System = origin.Coordinate.System, + NextPosition = 1 + }; + _originCursors[key] = cursor; + } + + int maxSystem = _tbotInstance.UserData.serverData.Systems; + if (cursor.System < 1 || cursor.System > maxSystem) cursor.System = origin.Coordinate.System; + if (cursor.NextPosition < 1 || cursor.NextPosition > 15) cursor.NextPosition = 1; + + return cursor; + } + + private int AdvanceSystem(int system) { + int maxSystem = _tbotInstance.UserData.serverData.Systems; + system++; + if (system > maxSystem) system = 1; + return system; + } + + private async Task ProcessOneSystemForOrigin( + Celestial origin, + OriginCursor cursor, + int discoveries, + DiscoveryRunContext ctx) { + + if (origin?.Coordinate == null) return discoveries; + if (discoveries <= 0 || ctx.FleetsToSend <= 0 || ctx.Stop) return discoveries; + + int systemToDo = cursor.System; + + DateTime now = await _tbotOgameBridge.GetDateTime(); + int resumeNextPos = cursor.NextPosition; + for (int pos = cursor.NextPosition; pos <= 15; pos++) { + resumeNextPos = pos; + + if (discoveries <= 0 || ctx.FleetsToSend <= 0 || ctx.Stop) break; + if (_tbotInstance.UserData.slots.Free <= (int)_tbotInstance.InstanceSettings.General.SlotsToLeaveFree) { ctx.Stop = true; break; } + + if (++ctx.GlobalAttempts > ctx.MaxGlobalAttempts) { ctx.Stop = true; break; } + + var dest = new Coordinate { + Galaxy = origin.Coordinate.Galaxy, + System = systemToDo, + Position = pos, + Type = Celestials.Planet + }; + + if (IsBlacklistedAndActive(dest, now)) { ctx.Skips++; continue; } + + var ok = await _ogameService.SendDiscovery(origin, dest); + if (!ok) { + DoLog(LogLevel.Warning, $"Failed to send discovery fleet to {dest} from {origin}."); + UpsertBlacklist(dest, now.AddDays(7)); + } else { + DoLog(LogLevel.Information, $"Discovery fleet sent to {dest} from {origin}."); + UpsertBlacklist(dest, now.AddDays(7)); + discoveries--; + ctx.FleetsToSend--; + } + + resumeNextPos = pos + 1; + + _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); + } + + if (!ctx.Stop && resumeNextPos > 15 && discoveries > 0 && ctx.FleetsToSend > 0 && + _tbotInstance.UserData.slots.Free > (int)_tbotInstance.InstanceSettings.General.SlotsToLeaveFree) { + + cursor.System = AdvanceSystem(systemToDo); + cursor.NextPosition = 1; + } else { + + if (resumeNextPos < 1) resumeNextPos = 1; + if (resumeNextPos > 15) resumeNextPos = 15; + cursor.NextPosition = resumeNextPos; + } + + return discoveries; + } + + + private long CalcFallbackIntervalMs() { + return RandomizeHelper.CalcRandomInterval( + (int)_tbotInstance.InstanceSettings.AutoDiscovery.CheckIntervalMin, + (int)_tbotInstance.InstanceSettings.AutoDiscovery.CheckIntervalMax + ); + } + + private long CalcNextIntervalFromDiscoveryFleetsOrFallback() { + var discFleet = _tbotInstance.UserData.fleets + .Where(f => f.Mission == Missions.Discovery && f.BackIn.HasValue) + .OrderBy(f => f.BackIn.Value) + .FirstOrDefault(); + + if (discFleet == null) return CalcFallbackIntervalMs(); + + long backInSec = discFleet.BackIn ?? 0; + long interval = backInSec * 1000 + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); + if (interval <= 0) interval = RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); + return interval; + } + protected override async Task Execute() { bool delay = false; bool stop = false; - int failures = 0; int skips = 0; - var rand = new Random(); + try { - if (_tbotInstance.UserData.discoveryBlackList == null) { - _tbotInstance.UserData.discoveryBlackList = new Dictionary(); + DoLog(LogLevel.Information, $"Starting AutoDiscovery..."); + + EnsureBlacklistInitialized(); + + if (_tbotInstance.UserData.isSleeping) { + stop = true; + return; } - if (!_tbotInstance.UserData.isSleeping) { - DoLog(LogLevel.Information, $"Starting AutoDiscovery..."); - _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); - _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); - - Celestial origin = _tbotInstance.UserData.celestials - .Unique() - .Where(c => c.Coordinate.Galaxy == (int) _tbotInstance.InstanceSettings.AutoDiscovery.Origin.Galaxy) - .Where(c => c.Coordinate.System == (int) _tbotInstance.InstanceSettings.AutoDiscovery.Origin.System) - .Where(c => c.Coordinate.Position == (int) _tbotInstance.InstanceSettings.AutoDiscovery.Origin.Position) - .Where(c => c.Coordinate.Type == Enum.Parse((string) _tbotInstance.InstanceSettings.AutoDiscovery.Origin.Type)) - .SingleOrDefault() ?? new() { ID = 0 }; - if (origin.ID == 0) { + + if ((bool)_tbotInstance.InstanceSettings.SleepMode.Active) { + DateTime.TryParse((string)_tbotInstance.InstanceSettings.SleepMode.GoToSleep, out DateTime goToSleep); + DateTime.TryParse((string)_tbotInstance.InstanceSettings.SleepMode.WakeUp, out DateTime wakeUp); + DateTime timeSleep = await _tbotOgameBridge.GetDateTime(); + if (GeneralHelper.ShouldSleep(timeSleep, goToSleep, wakeUp)) { + DoLog(LogLevel.Warning, "Unable to send discovery fleet: bed time has passed"); stop = true; - DoLog(LogLevel.Warning, "Unable to parse AutoDiscovery origin"); return; } - - if ((bool) _tbotInstance.InstanceSettings.SleepMode.Active) { - DateTime.TryParse((string) _tbotInstance.InstanceSettings.SleepMode.GoToSleep, out DateTime goToSleep); - DateTime.TryParse((string) _tbotInstance.InstanceSettings.SleepMode.WakeUp, out DateTime wakeUp); - DateTime time = await _tbotOgameBridge.GetDateTime(); - if (GeneralHelper.ShouldSleep(time, goToSleep, wakeUp)) { - DoLog(LogLevel.Warning, "Unable to send discovery fleet: bed time has passed"); - stop = true; - return; - } - } + } - List possibleDestinations = new(); - for (int i = 1; i <= _tbotInstance.UserData.serverData.Systems; i++) { - for (int j = 1; j <= 15; j++) { - possibleDestinations.Add(new Coordinate() { - Galaxy = origin.Coordinate.Galaxy, - System = i, - Position = j - }); - } - } - possibleDestinations = possibleDestinations - .Shuffle() - .OrderBy(c => _calculationService.CalcDistance(origin.Coordinate, c, _tbotInstance.UserData.serverData)) - .ToList(); - - while (possibleDestinations.Count > 0 && _tbotInstance.UserData.fleets.Where(s => s.Mission == Missions.Discovery).Count() < (int) _tbotInstance.InstanceSettings.AutoDiscovery.MaxSlots && _tbotInstance.UserData.slots.Free > (int) _tbotInstance.InstanceSettings.General.SlotsToLeaveFree) { - Coordinate dest = possibleDestinations.First(); - possibleDestinations.Remove(dest); - - Coordinate blacklistedCoord = _tbotInstance.UserData.discoveryBlackList.Keys - .Where(c => c.Galaxy == dest.Galaxy) - .Where(c => c.System == dest.System) - .Where(c => c.Position == dest.Position) - .SingleOrDefault() ?? null; - if (blacklistedCoord != null) { - if (_tbotInstance.UserData.discoveryBlackList.Single(d => d.Key.Galaxy == dest.Galaxy && d.Key.System == dest.System && d.Key.Position == dest.Position).Value > DateTime.Now) { - //DoLog(LogLevel.Information, $"Skipping {dest.ToString()} because it's blacklisted until {_tbotInstance.UserData.discoveryBlackList[blacklistedCoord].ToString()}"); - skips++; - if (skips >= _tbotInstance.UserData.serverData.Systems * 15) { - DoLog(LogLevel.Information, $"Galaxy depleted: stopping"); - stop = true; - break; - } else { - continue; - } - } else { - _tbotInstance.UserData.discoveryBlackList.Remove(blacklistedCoord); - } - } - - origin = await _tbotOgameBridge.UpdatePlanet(origin, UpdateTypes.Resources); - if (!origin.Resources.IsEnoughFor(new Resources { Metal = 5000, Crystal = 1000, Deuterium = 500 })) { - DoLog(LogLevel.Warning, $"Failed to send discovery fleet from {origin.ToString()}: not enough resources."); - return; - } - - var result = await _ogameService.SendDiscovery(origin, dest); - if (!result) { - failures++; - DoLog(LogLevel.Warning, $"Failed to send discovery fleet to {dest.ToString()} from {origin.ToString()}."); - _tbotInstance.UserData.discoveryBlackList.Add(dest, DateTime.Now.AddDays(1)); - } - else { - DoLog(LogLevel.Information, $"Sent discovery fleet to {dest.ToString()} from {origin.ToString()}."); - _tbotInstance.UserData.discoveryBlackList.Add(dest, DateTime.Now.AddDays(7)); - } - - if (failures >= (int) _tbotInstance.InstanceSettings.AutoDiscovery.MaxFailures) { - DoLog(LogLevel.Warning, $"Max failures reached"); - break; - } - - _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); - _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); - if (_tbotInstance.UserData.slots.Free <= 1) { - DoLog(LogLevel.Information, $"AutoDiscoveryWorker: No slots left, dealying"); - delay = true; - break; - } - } + DateTime now = await _tbotOgameBridge.GetDateTime(); + CleanupExpiredBlacklistAll(now); + + _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); + _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); + + List rankSlotsPriority = new() { + new RankSlotsPriority(Feature.BrainAutoMine, (int)_tbotInstance.InstanceSettings.General.SlotPriorityLevel.Brain, + ((bool)_tbotInstance.InstanceSettings.Brain.Active && (bool)_tbotInstance.InstanceSettings.Brain.Transports.Active && + ((bool)_tbotInstance.InstanceSettings.Brain.AutoMine.Active || (bool)_tbotInstance.InstanceSettings.Brain.AutoResearch.Active || + (bool)_tbotInstance.InstanceSettings.Brain.LifeformAutoMine.Active || (bool)_tbotInstance.InstanceSettings.Brain.LifeformAutoResearch.Active)), + (int)_tbotInstance.InstanceSettings.Brain.Transports.MaxSlots, + (int)_tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Transport) + ), + new RankSlotsPriority(Feature.Expeditions, (int)_tbotInstance.InstanceSettings.General.SlotPriorityLevel.Expeditions, + (bool)_tbotInstance.InstanceSettings.Expeditions.Active, + (int)_tbotInstance.UserData.slots.ExpTotal, + (int)_tbotInstance.UserData.slots.ExpInUse + ), + new RankSlotsPriority(Feature.AutoFarm, (int)_tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoFarm, + (bool)_tbotInstance.InstanceSettings.AutoFarm.Active, + (int)_tbotInstance.InstanceSettings.AutoFarm.MaxSlots, + (int)_tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Attack) + ), + new RankSlotsPriority(Feature.Colonize, (int)_tbotInstance.InstanceSettings.General.SlotPriorityLevel.Colonize, + (bool)_tbotInstance.InstanceSettings.AutoColonize.Active, + (bool)_tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.Active ? (int)_tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.MaxSlots : 1, + (int)_tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Colonize) + ), + new RankSlotsPriority(Feature.AutoDiscovery, (int)_tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoDiscovery, + (bool)_tbotInstance.InstanceSettings.AutoDiscovery.Active, + (int)_tbotInstance.InstanceSettings.AutoDiscovery.MaxSlots, + (int)_tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Discovery) + ), + new RankSlotsPriority(Feature.Harvest, (int)_tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoHarvest, + (bool)_tbotInstance.InstanceSettings.AutoHarvest.Active, + (int)_tbotInstance.InstanceSettings.AutoHarvest.MaxSlots, + (int)_tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Harvest) + ) + }; + + int fleetsToSend = _calculationService.CalcSlotsPriority( + Feature.AutoDiscovery, rankSlotsPriority, + _tbotInstance.UserData.slots, _tbotInstance.UserData.fleets, + (int)_tbotInstance.InstanceSettings.General.SlotsToLeaveFree + ); + + if (fleetsToSend <= 0) { + delay = true; + return; } - else { + + var celestials = await _ogameService.GetCelestials(); + var origins = GetConfiguredOrigins(celestials); + + if (origins == null || origins.Count == 0) { stop = true; + DoLog(LogLevel.Warning, "Unable to parse AutoDiscovery origin."); + return; } - } catch (Exception ex) { - DoLog(LogLevel.Error, "AutoDiscovery exception"); - DoLog(LogLevel.Warning, ex.ToString()); - } - finally { + + var ctx = new DiscoveryRunContext { + FleetsToSend = fleetsToSend, + Stop = false, + Skips = 0, + GlobalAttempts = 0, + MaxGlobalAttempts = 600 + }; + + foreach (var origin in origins) { + if (ctx.Stop) break; + if (ctx.FleetsToSend <= 0) break; + if (origin?.Coordinate == null) continue; + + int discoveries = await _ogameService.GetAvailableDiscoveries(origin); + if (discoveries <= 0) { + DoLog(LogLevel.Information, $"No discoveries available from {origin} right now."); + continue; + } + + var cursor = GetOrInitCursor(origin); + DoLog(LogLevel.Information, $"Origin {origin} cursor system={cursor.System}, nextPos={cursor.NextPosition} (discoveries={discoveries})"); + + discoveries = await ProcessOneSystemForOrigin(origin, cursor, discoveries, ctx); + + fleetsToSend = ctx.FleetsToSend; + stop = ctx.Stop; + skips = ctx.Skips; + } + + if (skips > 0) + DoLog(LogLevel.Information, $"{skips} positions skipped (blacklisted)"); + + _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); + + long interval = CalcNextIntervalFromDiscoveryFleetsOrFallback(); + DateTime time = await _tbotOgameBridge.GetDateTime(); + var nextTime = time.AddMilliseconds(interval); + + ChangeWorkerPeriod(interval); + DoLog(LogLevel.Information, $"Next AutoDiscovery check at {nextTime.ToString()}"); + await _tbotOgameBridge.CheckCelestials(); + } catch (Exception e) { + DoLog(LogLevel.Error, $"An error occured: {e.Message}"); + DoLog(LogLevel.Debug, e.StackTrace); + } finally { if (stop) { DoLog(LogLevel.Information, $"Stopping feature."); await EndExecution(); - } else { - long interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.AutoDiscovery.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.AutoDiscovery.CheckIntervalMax); - if (delay) { - DoLog(LogLevel.Information, $"Delaying..."); - _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); - interval = (_tbotInstance.UserData.fleets.OrderBy(f => f.BackIn).First().BackIn ?? 0) * 1000 + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); - } - if (interval <= 0) - interval = RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); - var time = await _tbotOgameBridge.GetDateTime(); - var newTime = time.AddMilliseconds(interval); - ChangeWorkerPeriod(interval); - DoLog(LogLevel.Information, $"Next AutoDiscovery check at {newTime.ToString()}"); } + + if (delay) { + DoLog(LogLevel.Information, $"Delaying..."); + var timeDelay = await _tbotOgameBridge.GetDateTime(); + _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); + + long intervalDelay = CalcNextIntervalFromDiscoveryFleetsOrFallback(); + var newTimeDelay = timeDelay.AddMilliseconds(intervalDelay); + + ChangeWorkerPeriod(intervalDelay); + DoLog(LogLevel.Information, $"Next AutoDiscovery check at {newTimeDelay.ToString()}"); + } + await _tbotOgameBridge.CheckCelestials(); - } + } } public override bool IsWorkerEnabledBySettings() { try { - return - (bool) _tbotInstance.InstanceSettings.AutoDiscovery.Active - ; + return (bool)_tbotInstance.InstanceSettings.AutoDiscovery.Active; } catch (Exception) { return false; } } + public override string GetWorkerName() { return "AutoDiscovery"; } diff --git a/TBot/Workers/AutoFarmWorker.cs b/TBot/Workers/AutoFarmWorker.cs index 2aff82c2..1f544e0c 100644 --- a/TBot/Workers/AutoFarmWorker.cs +++ b/TBot/Workers/AutoFarmWorker.cs @@ -21,6 +21,8 @@ public class AutoFarmWorker : WorkerBase { private readonly IFleetScheduler _fleetScheduler; private readonly ICalculationService _calculationService; private readonly ITBotOgamedBridge _tbotOgameBridge; + private readonly AutoFarmBlacklist _blacklist; + private readonly AutoFarmSuccessfulTargets _successfulTargets; public AutoFarmWorker(ITBotMain parentInstance, IOgameService ogameService, IFleetScheduler fleetScheduler, @@ -31,6 +33,10 @@ public AutoFarmWorker(ITBotMain parentInstance, _fleetScheduler = fleetScheduler; _calculationService = calculationService; _tbotOgameBridge = tbotOgameBridge; + string blacklistPath = $"autofarm_blacklist_{_tbotInstance.InstanceAlias}.json"; + _blacklist = new AutoFarmBlacklist(blacklistPath); + string successfulPath = $"autofarm_successful_{_tbotInstance.InstanceAlias}.json"; + _successfulTargets = new AutoFarmSuccessfulTargets(successfulPath); } public override bool IsWorkerEnabledBySettings() { try { @@ -90,11 +96,61 @@ private bool ShouldExcludeSystem(int galaxy, int system) { } private async Task> GetScannedTargetsFromGalaxy(int galaxy, int system) { - var galaxyInfo = await _ogameService.GetGalaxyInfo(galaxy, system); + GalaxyInfo galaxyInfo = null; + int retryCount = 0; + int maxRetries = 5; + + while (retryCount < maxRetries) { + try { + galaxyInfo = await _ogameService.GetGalaxyInfo(galaxy, system); + break; + } catch (Exception e) when (e.Message.Contains("system must be within") || e.Message.Contains("503") || e.Message.Contains("Service Unavailable")) { + retryCount++; + int waitSeconds = retryCount * 3; + + _tbotInstance.log(LogLevel.Debug, LogSender.AutoFarm, $"Exception details: {e.Message}"); + _tbotInstance.log(LogLevel.Warning, LogSender.AutoFarm, $"Galaxy scan failed. Retry {retryCount}/{maxRetries} in {waitSeconds}s..."); + + if (retryCount < maxRetries) { + await Task.Delay(waitSeconds * 1000); + + try { + _tbotInstance.UserData.serverData = await _ogameService.GetServerData(); + _tbotInstance.log(LogLevel.Debug, LogSender.AutoFarm, $"ServerData after refresh: Systems={_tbotInstance.UserData.serverData.Systems}"); + } catch (Exception serverDataEx) { + _tbotInstance.log(LogLevel.Error, LogSender.AutoFarm, $"Failed to refresh ServerData: {serverDataEx.Message}"); + } + + if (retryCount == 4 && _tbotInstance.UserData.serverData.Systems == 0) { + _tbotInstance.log(LogLevel.Warning, LogSender.AutoFarm, $"ServerData broken. Root cause: {e.Message}. Restarting ogamed..."); + + try { + _ogameService.KillOgamedExecutable(); + await Task.Delay(5000); + _ogameService.RerunOgamed(); + await Task.Delay(10000); + + try { + _tbotInstance.UserData.serverData = await _ogameService.GetServerData(); + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"ServerData after restart: Systems={_tbotInstance.UserData.serverData.Systems}"); + } catch (Exception restartEx) { + _tbotInstance.log(LogLevel.Error, LogSender.AutoFarm, $"Failed to get ServerData after ogamed restart: {restartEx.Message}"); + } + } catch (Exception ogamedEx) { + _tbotInstance.log(LogLevel.Error, LogSender.AutoFarm, $"Failed to restart ogamed: {ogamedEx.Message}"); + _tbotInstance.log(LogLevel.Warning, LogSender.AutoFarm, $"Stacktrace: {ogamedEx.StackTrace}"); + } + } + } else { + _tbotInstance.log(LogLevel.Error, LogSender.AutoFarm, $"Galaxy scan failed after {maxRetries} retries."); + throw; + } + } + } + var planets = galaxyInfo.Planets.Where(p => p != null && p.Inactive && !p.Administrator && !p.Banned && !p.Vacation); List scannedTargets = planets.Cast().ToList(); await _fleetScheduler.UpdateFleets(); - //Remove all targets that are currently under attack (necessary if bot or instance is restarted) scannedTargets.RemoveAll(t => _tbotInstance.UserData.fleets.Any(f => f.Destination.IsSame(t.Coordinate) && f.Mission == Missions.Attack)); return scannedTargets; } @@ -151,8 +207,6 @@ private void AddMoons(List scannedTargets) { private FarmTarget CheckDuplicatesAndGetExisting(Celestial planet) { var exists = _tbotInstance.UserData.farmTargets.Where(t => t != null && t.Celestial.HasCoords(planet.Coordinate)).ToList(); if (exists.Count() > 1) { - // It can exist if was processed in a previous execution - //Remove all except the first to be able to continue var firstExisting = exists.First(); _tbotInstance.UserData.farmTargets.RemoveAll(c => c.Celestial.HasCoords(planet.Coordinate)); _tbotInstance.UserData.farmTargets.Add(firstExisting); @@ -165,27 +219,32 @@ private FarmTarget CheckDuplicatesAndGetExisting(Celestial planet) { } private FarmTarget GetFarmTarget(Celestial planet) { - // Check if planet with coordinates exists already in _tbotInstance.UserData.farmTargets list. + bool blacklistActive = SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "Blacklist") && + SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm.Blacklist, "Active") && + (bool) _tbotInstance.InstanceSettings.AutoFarm.Blacklist.Active; + + if (blacklistActive && _blacklist.IsBlacklisted(planet.Coordinate)) { + var blacklistedTarget = _blacklist.GetBlacklistedTarget(planet.Coordinate); + _tbotInstance.log(LogLevel.Debug, LogSender.AutoFarm, $"Target {planet.ToString()} is blacklisted (Reason: {blacklistedTarget.Reason}). Skipping..."); + return null; + } + var target = CheckDuplicatesAndGetExisting(planet); if (target == null) { - // Does not exist, add to _tbotInstance.UserData.farmTargets list, set state to probes pending. target = new(planet, FarmState.ProbesPending); _tbotInstance.UserData.farmTargets.Add(target); } else { - // Already exists, update _tbotInstance.UserData.farmTargets list with updated planet. target.Celestial = planet; if (target.State == FarmState.Idle) target.State = FarmState.ProbesPending; - // If target marked not suitable based on a non-expired espionage report, skip probing. if (target.State == FarmState.NotSuitable && target.Report != null) { _tbotInstance.log(LogLevel.Debug, LogSender.AutoFarm, $"Target {planet.ToString()} marked as Not Suitable. Skipping..."); return null; } - // If probes are already sent or if an attack is pending, skip probing. if (target.State == FarmState.ProbesSent || target.State == FarmState.AttackPending) { _tbotInstance.log(LogLevel.Debug, LogSender.AutoFarm, $"Target {planet.ToString()} marked as {target.State.ToString()}. Skipping..."); return null; @@ -226,25 +285,19 @@ private async Task GetBestOrigin(List closestCelesti int neededProbes, int slotsToLeaveFree, int freeSlots) { - //Set first celestial as default best Origin SpyOriginResult bestOrigin = new SpyOriginResult(closestCelestials.First(), int.MaxValue, freeSlots); foreach (var closest in closestCelestials) { - // Update ships of the current planet var tempCelestial = await _tbotOgameBridge.UpdatePlanet(closest, UpdateTypes.Ships); celestialProbes.Remove(closest.ID); celestialProbes.Add(closest.ID, tempCelestial.Ships.EspionageProbe); if (celestialProbes[closest.ID] >= neededProbes) { - //There are enough probes so it's the best origin and we can stop searching bestOrigin = new SpyOriginResult(closest, freeSlots); break; } - // No probes available in this celestial _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); - // If there are no free slots, update the minimum time to wait for current missions return. - // If there are no free slots, wait for probes to come back to current celestial. if (freeSlots <= slotsToLeaveFree) { var espionageMissions = _calculationService.GetMissionsInProgress(closest.Coordinate, Missions.Spy, _tbotInstance.UserData.fleets); if (espionageMissions.Any()) { @@ -263,12 +316,10 @@ private async Task GetBestOrigin(List closestCelesti } } } else { - //If no bestOrigin detected, the total number of probes is not enough but there are free slots, then calculate if can be built from this celestial _tbotInstance.log(LogLevel.Warning, LogSender.AutoFarm, $"Cannot spy {target.Celestial.Coordinate.ToString()} from {closest.Coordinate.ToString()}, insufficient probes ({celestialProbes[closest.ID]}/{neededProbes})."); if (bestOrigin.BackIn < int.MaxValue) continue; - //If there is no bestOrigin, check if can be a good origin (it has enough resources to build probes) tempCelestial = await _tbotOgameBridge.UpdatePlanet(closest, UpdateTypes.Constructions); if (tempCelestial.Constructions.BuildingID == (int) Buildables.Shipyard || tempCelestial.Constructions.BuildingID == (int) Buildables.NaniteFactory) { Buildables buildingInProgress = (Buildables) tempCelestial.Constructions.BuildingID; @@ -315,7 +366,6 @@ private async Task WaitForFreeSlots(int freeSlots, int slotsToLeaveFree) { _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); while (freeSlots <= slotsToLeaveFree) { - // No slots available, wait for first fleet of any mission type to return. _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); if (_tbotInstance.UserData.fleets.Any()) { int interval = (int) ((1000 * _tbotInstance.UserData.fleets.OrderBy(fleet => fleet.BackIn).First().BackIn) + RandomizeHelper.CalcRandomInterval(IntervalType.LessThanASecond)); @@ -325,7 +375,7 @@ private async Task WaitForFreeSlots(int freeSlots, int slotsToLeaveFree) { freeSlots = _tbotInstance.UserData.slots.Free; } else { _tbotInstance.log(LogLevel.Error, LogSender.AutoFarm, "Error: No fleet slots available and no fleets returning!"); - throw new Exception("No fleet slots available and no fleets returning!"); //TODO: Create custom exception + throw new Exception("No fleet slots available and no fleets returning!"); } } return freeSlots; @@ -333,14 +383,15 @@ private async Task WaitForFreeSlots(int freeSlots, int slotsToLeaveFree) { protected override async Task Execute() { bool stop = false; + bool stopAfterFullScan = false; + bool finishedFullScan = false; try { - _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, "Running autofarm..."); - - if ((bool) _tbotInstance.InstanceSettings.AutoFarm.Active) { - // If not enough slots are free, the farmer cannot run. + stopAfterFullScan = SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "StopAfterFullScan") + && (bool)_tbotInstance.InstanceSettings.AutoFarm.StopAfterFullScan; + if ((bool) _tbotInstance.InstanceSettings.AutoFarm.Active) { _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); - + int freeSlots = _tbotInstance.UserData.slots.Free; int slotsToLeaveFree = (int) _tbotInstance.InstanceSettings.AutoFarm.SlotsToLeaveFree; if (freeSlots <= slotsToLeaveFree) { @@ -349,47 +400,141 @@ protected override async Task Execute() { } try { - // Prune all reports older than KeepReportFor and all reports of state AttackSent: information no longer actual. await PruneOldReports(); - // Keep local record of _tbotInstance.UserData.celestials, to be updated by autofarmer itself, to reduce ogamed calls. var celestialProbes = await GetCelestialProbes(); - // Keep track of number of targets probed. int numProbed = 0; - /// Galaxy scanning + target probing. _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, "Detecting farm targets..."); bool stopAutoFarm = false; - foreach (var range in _tbotInstance.InstanceSettings.AutoFarm.ScanRange) { + var scanRanges = ((IEnumerable)_tbotInstance.InstanceSettings.AutoFarm.ScanRange).ToList(); + + var allScanRanges = ((IEnumerable)_tbotInstance.InstanceSettings.AutoFarm.ScanRange).ToList(); + int totalSystemsAcrossAllGalaxies = allScanRanges.Sum(r => (int)r.EndSystem - (int)r.StartSystem + 1); + int instanceHash = Math.Abs(_tbotInstance.InstanceAlias.GetHashCode()); + int minSpacing = 499; + int numSlotsGlobal = Math.Max(1, totalSystemsAcrossAllGalaxies / minSpacing); + int globalSlotIndex = instanceHash % numSlotsGlobal; + int globalOffset = globalSlotIndex * minSpacing; + + int targetGalaxy = 1; + int targetSystem = 1; + int remainingOffset = globalOffset; + foreach (var r in allScanRanges) { + int systemsInRange = (int)r.EndSystem - (int)r.StartSystem + 1; + if (remainingOffset < systemsInRange) { + targetGalaxy = (int)r.Galaxy; + targetSystem = (int)r.StartSystem + remainingOffset; + break; + } + remainingOffset -= systemsInRange; + } + + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"[GLOBAL SPACING] Instance: {_tbotInstance.InstanceAlias}"); + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"[GLOBAL SPACING] Total systems across all galaxies: {totalSystemsAcrossAllGalaxies}"); + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"[GLOBAL SPACING] Num slots globally: {numSlotsGlobal}"); + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"[GLOBAL SPACING] Global slot index: {globalSlotIndex}"); + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"[GLOBAL SPACING] Global offset: {globalOffset} systems"); + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"[GLOBAL SPACING] Target Galaxy: {targetGalaxy}, Target System: {targetSystem}"); + + var orderedRanges = scanRanges + .OrderBy(r => (int)r.Galaxy) + .ThenBy(r => (int)r.StartSystem) + .ToList(); + + if (!orderedRanges.Any()) { + _tbotInstance.log(LogLevel.Warning, LogSender.AutoFarm, "No scan ranges match galaxies with planets. Skipping AutoFarm."); + stopAutoFarm = true; + } + + int startRangeIndex = _tbotInstance.UserData.autoFarmLastRangeIndex; + if (startRangeIndex < 0 || startRangeIndex >= orderedRanges.Count) { + startRangeIndex = 0; + } + if (_tbotInstance.UserData.autoFarmLastGalaxy == 0 && _tbotInstance.UserData.autoFarmLastSystem == 0) { + int idx = orderedRanges.FindIndex(r => (int)r.Galaxy == targetGalaxy); + if (idx >= 0) startRangeIndex = idx; + _tbotInstance.UserData.autoFarmLastRangeIndex = startRangeIndex; + } + + bool globalOffsetUsed = false; + + for (int rangeIndex = startRangeIndex; rangeIndex < orderedRanges.Count; rangeIndex++) { if (stopAutoFarm) break; - if (SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "TargetsProbedBeforeAttack") && ((int) _tbotInstance.InstanceSettings.AutoFarm.TargetsProbedBeforeAttack != 0) && numProbed >= (int) _tbotInstance.InstanceSettings.AutoFarm.TargetsProbedBeforeAttack) { + if (SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "TargetsProbedBeforeAttack") && ((int)_tbotInstance.InstanceSettings.AutoFarm.TargetsProbedBeforeAttack != 0) && numProbed >= (int)_tbotInstance.InstanceSettings.AutoFarm.TargetsProbedBeforeAttack) { break; } + var range = orderedRanges[rangeIndex]; + int galaxy = (int) range.Galaxy; - int startSystem = (int) range.StartSystem; + int originalStartSystem = (int) range.StartSystem; int endSystem = (int) range.EndSystem; - - // Loop from start to end system. - for (var system = startSystem; system <= endSystem; system++) { + int startSystem = originalStartSystem; + bool isRandomStart = false; + + if (_tbotInstance.UserData.autoFarmLastGalaxy == galaxy && + _tbotInstance.UserData.autoFarmLastSystem >= originalStartSystem && + _tbotInstance.UserData.autoFarmLastSystem <= endSystem) { + + startSystem = _tbotInstance.UserData.autoFarmLastSystem; + isRandomStart = false; + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, + $"Resuming scan from Galaxy {galaxy} System {startSystem}"); + } + else { + startSystem = originalStartSystem; + isRandomStart = false; + + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, + $"[START] Galaxy {galaxy} - Ordered start at system {startSystem}"); + } + + int systemsToScan = endSystem - originalStartSystem + 1; + int scannedCount = 0; + + for (var offset = 0; offset < systemsToScan && scannedCount < systemsToScan; offset++) { + int system = startSystem + offset; + if (system > endSystem) { + break; + } + scannedCount++; if (stopAutoFarm) break; if (SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "TargetsProbedBeforeAttack") && ((int) _tbotInstance.InstanceSettings.AutoFarm.TargetsProbedBeforeAttack != 0) && numProbed >= (int) _tbotInstance.InstanceSettings.AutoFarm.TargetsProbedBeforeAttack) { _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, "Maximum number of targets to probe reached, proceeding to attack."); + int nextSystem = system + 1; + if (nextSystem <= endSystem) { + _tbotInstance.UserData.autoFarmLastRangeIndex = rangeIndex; + _tbotInstance.UserData.autoFarmLastGalaxy = galaxy; + _tbotInstance.UserData.autoFarmLastSystem = nextSystem; + } else { + int nextRangeIndex = rangeIndex + 1; + if (nextRangeIndex < orderedRanges.Count) { + var nextRange = orderedRanges[nextRangeIndex]; + _tbotInstance.UserData.autoFarmLastRangeIndex = nextRangeIndex; + _tbotInstance.UserData.autoFarmLastGalaxy = (int)nextRange.Galaxy; + _tbotInstance.UserData.autoFarmLastSystem = (int)nextRange.StartSystem; + } else { + _tbotInstance.UserData.autoFarmLastRangeIndex = 0; + _tbotInstance.UserData.autoFarmLastGalaxy = 0; + _tbotInstance.UserData.autoFarmLastSystem = 0; + finishedFullScan = true; + } + } + stopAutoFarm = true; break; } - // Check excluded system. bool excludeSystem = ShouldExcludeSystem(galaxy, system); if (excludeSystem) continue; var scannedTargets = await GetScannedTargetsFromGalaxy(galaxy, system); - _tbotInstance.log(LogLevel.Debug, LogSender.AutoFarm, $"Found {scannedTargets.Count} targets on System {galaxy}:{system}"); if (!scannedTargets.Any()) @@ -399,11 +544,9 @@ protected override async Task Execute() { AddMoons(scannedTargets); } - // Add each planet that has inactive status to _tbotInstance.UserData.farmTargets. foreach (Celestial planet in scannedTargets) { if (stopAutoFarm) break; - // Check if target is below set minimum rank. if (!IsTargetInMinimumRank(planet, scannedTargets)) { continue; } @@ -414,21 +557,24 @@ protected override async Task Execute() { break; } - // Check excluded planet. if (ShouldExcludePlanet(planet)) continue; - // Manage existing Target or generate a new one. If should not be processed returns null var target = GetFarmTarget(planet); if (target == null) continue; - // Send spy probe from closest celestial with available probes to the target. List tempCelestials = (_tbotInstance.InstanceSettings.AutoFarm.Origin.Length > 0) ? _calculationService.ParseCelestialsList(_tbotInstance.InstanceSettings.AutoFarm.Origin, _tbotInstance.UserData.celestials) : _tbotInstance.UserData.celestials; + List closestCelestials = tempCelestials .OrderByDescending(planet => planet.Coordinate.Type == Celestials.Moon) .OrderBy(c => _calculationService.CalcDistance(c.Coordinate, target.Celestial.Coordinate, _tbotInstance.UserData.serverData)).ToList(); + if (!closestCelestials.Any()) { + _tbotInstance.log(LogLevel.Debug, LogSender.AutoFarm, $"No origin celestials available. Skipping target {target.Celestial.ToString()}"); + continue; + } + int neededProbes = GetNeededProbes(target); @@ -437,7 +583,6 @@ protected override async Task Execute() { _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); var probesInMission = _tbotInstance.UserData.fleets.Select(c => c.Ships).Sum(c => c.EspionageProbe); - //Calculate best origin var bestOrigin = await GetBestOrigin(closestCelestials, celestialProbes, target, @@ -458,7 +603,6 @@ protected override async Task Execute() { continue; } - // If local record indicate not enough espionage probes are available, update record to make sure this is correct. if (celestialProbes[bestOrigin.Origin.ID] < neededProbes) { var tempCelestial = await _tbotOgameBridge.UpdatePlanet(bestOrigin.Origin, UpdateTypes.Ships); celestialProbes.Remove(bestOrigin.Origin.ID); @@ -468,7 +612,7 @@ protected override async Task Execute() { if (celestialProbes[bestOrigin.Origin.ID] < neededProbes) { _tbotInstance.log(LogLevel.Warning, LogSender.AutoFarm, $"Insufficient probes ({celestialProbes[bestOrigin.Origin.ID]}/{neededProbes})."); if (SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "BuildProbes") && _tbotInstance.InstanceSettings.AutoFarm.BuildProbes == true) { - //Check if probes can be built + var tempCelestial = await _tbotOgameBridge.UpdatePlanet(bestOrigin.Origin, UpdateTypes.Constructions); if (tempCelestial.Constructions.BuildingID == (int) Buildables.Shipyard || tempCelestial.Constructions.BuildingID == (int) Buildables.NaniteFactory) { Buildables buildingInProgress = (Buildables) tempCelestial.Constructions.BuildingID; @@ -529,6 +673,17 @@ protected override async Task Execute() { _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); freeSlots = _tbotInstance.UserData.slots.Free; freeSlots = await WaitForFreeSlots(freeSlots, slotsToLeaveFree); + + bestOrigin.Origin = await _tbotOgameBridge.UpdatePlanet(bestOrigin.Origin, UpdateTypes.Ships); + var availableProbes = bestOrigin.Origin.Ships.EspionageProbe; + + if (availableProbes < neededProbes) { + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, + $"Insufficient probes on {bestOrigin.Origin.ToString()} ({availableProbes}/{neededProbes}). Skipping {target.ToString()}."); + fleetId = (int) SendFleetCode.GenericError; + break; + } + fleetId = await _fleetScheduler.SendFleet(bestOrigin.Origin, ships, target.Celestial.Coordinate, Missions.Spy, Speeds.HundredPercent); if (fleetId == (int)SendFleetCode.NotEnoughSlots) { _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"Another worker took the slot, waiting again for a free slot... Retry count: {retryCount}/{maxRetryCount}"); @@ -562,13 +717,36 @@ protected override async Task Execute() { } } } + if (!stopAutoFarm && ( + !SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "TargetsProbedBeforeAttack") || + (int)_tbotInstance.InstanceSettings.AutoFarm.TargetsProbedBeforeAttack == 0 || + numProbed <= (int)_tbotInstance.InstanceSettings.AutoFarm.TargetsProbedBeforeAttack) + ) { + _tbotInstance.UserData.autoFarmLastGalaxy = 0; + _tbotInstance.UserData.autoFarmLastSystem = 0; + + _tbotInstance.UserData.autoFarmLastRangeIndex = 0; + + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, + "Full scan cycle completed, resetting scan position for next cycle"); + + if (SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "StopAfterFullScan") && + (bool)_tbotInstance.InstanceSettings.AutoFarm.StopAfterFullScan) { + + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, + "StopAfterFullScan=true -> Full scan completed. Stopping AutoFarm and waiting for /startautofarm."); + + stop = true; + return; + } + } + } catch (Exception e) { _tbotInstance.log(LogLevel.Debug, LogSender.AutoFarm, $"Exception: {e.Message}"); _tbotInstance.log(LogLevel.Warning, LogSender.AutoFarm, $"Stacktrace: {e.StackTrace}"); _tbotInstance.log(LogLevel.Warning, LogSender.AutoFarm, "Unable to parse scan range"); } - // Wait for all espionage fleets to return. _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); Fleet firstReturning = _calculationService.GetLastReturningEspionage(_tbotInstance.UserData.fleets); if (firstReturning != null) { @@ -579,10 +757,46 @@ protected override async Task Execute() { _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, "Processing espionage reports of found inactives..."); - /// Process reports. await AutoFarmProcessReports(); - /// Send attacks. + _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); + _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); + List rankSlotsPriority = new() { + new RankSlotsPriority(Feature.BrainAutoMine, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Brain, + ((bool) _tbotInstance.InstanceSettings.Brain.Active && (bool) _tbotInstance.InstanceSettings.Brain.Transports.Active && ((bool) _tbotInstance.InstanceSettings.Brain.AutoMine.Active || (bool) _tbotInstance.InstanceSettings.Brain.AutoResearch.Active || (bool) _tbotInstance.InstanceSettings.Brain.LifeformAutoMine.Active || (bool) _tbotInstance.InstanceSettings.Brain.LifeformAutoResearch.Active)), + (int) _tbotInstance.InstanceSettings.Brain.Transports.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Transport)), + new RankSlotsPriority(Feature.Expeditions, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Expeditions, + (bool) _tbotInstance.InstanceSettings.Expeditions.Active, + (int) _tbotInstance.UserData.slots.ExpTotal, + (int)_tbotInstance.UserData.slots.ExpInUse), + new RankSlotsPriority(Feature.AutoFarm, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoFarm, + (bool) _tbotInstance.InstanceSettings.AutoFarm.Active, + (int) _tbotInstance.InstanceSettings.AutoFarm.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Attack)), + new RankSlotsPriority(Feature.Colonize, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Colonize, + (bool) _tbotInstance.InstanceSettings.AutoColonize.Active, + (bool) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.Active ? + (int) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.MaxSlots : + 1, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Colonize)), + new RankSlotsPriority(Feature.AutoDiscovery, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoDiscovery, + (bool) _tbotInstance.InstanceSettings.AutoDiscovery.Active, + (int) _tbotInstance.InstanceSettings.AutoDiscovery.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Discovery)), + new RankSlotsPriority(Feature.Harvest, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoHarvest, + (bool) _tbotInstance.InstanceSettings.AutoHarvest.Active, + (int) _tbotInstance.InstanceSettings.AutoHarvest.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Harvest)) + }; + int MaxSlots = _calculationService.CalcSlotsPriority(Feature.AutoFarm, rankSlotsPriority, _tbotInstance.UserData.slots, _tbotInstance.UserData.fleets, (int) _tbotInstance.InstanceSettings.General.SlotsToLeaveFree); + List attackTargets; if (_tbotInstance.InstanceSettings.AutoFarm.PreferedResource == "Metal") attackTargets = _tbotInstance.UserData.farmTargets.Where(t => t.State == FarmState.AttackPending).OrderByDescending(t => t.Report.Loot(_tbotInstance.UserData.userInfo.Class).Metal).ToList(); @@ -625,7 +839,7 @@ protected override async Task Execute() { attackTargetsCount++; _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"Attacking target {attackTargetsCount}/{attackTargets.Count()} at {target.Celestial.Coordinate.ToString()} for {target.Report.Loot(_tbotInstance.UserData.userInfo.Class).TransportableResources}."); var loot = target.Report.Loot(_tbotInstance.UserData.userInfo.Class); - Celestial tempCelestial = _tbotInstance.UserData.celestials.Where(c => c.Coordinate.Type == Celestials.Moon).First(); + Celestial tempCelestial = _tbotInstance.UserData.celestials.Where(c => c.Coordinate.Type == Celestials.Planet).First(); tempCelestial = await _tbotOgameBridge.UpdatePlanet(tempCelestial, UpdateTypes.LFBonuses); float cargoBonus = tempCelestial.LFBonuses.GetShipCargoBonus(cargoShip); var numCargo = _calculationService.CalcShipNumberForPayload(loot, cargoShip, _tbotInstance.UserData.researches.HyperspaceTechnology, _tbotInstance.UserData.serverData, cargoBonus, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.serverData.ProbeCargo); @@ -646,9 +860,8 @@ protected override async Task Execute() { tempCelestial = await _tbotOgameBridge.UpdatePlanet(tempCelestial, UpdateTypes.Resources); tempCelestial = await _tbotOgameBridge.UpdatePlanet(tempCelestial, UpdateTypes.LFBonuses); if (tempCelestial.Ships != null && tempCelestial.Ships.GetAmount(cargoShip) >= (numCargo + _tbotInstance.InstanceSettings.AutoFarm.MinCargosToKeep)) { - // TODO Future: If fleet composition is changed, update ships passed to CalcFlightTime. speed = 0; - if (/*cargoShip == Buildables.EspionageProbe &&*/ SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "MinLootFuelRatio") && _tbotInstance.InstanceSettings.AutoFarm.MinLootFuelRatio != 0) { + if (SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "MinLootFuelRatio") && _tbotInstance.InstanceSettings.AutoFarm.MinLootFuelRatio != 0) { long maxFlightTime = SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "MaxFlightTime") ? (long) _tbotInstance.InstanceSettings.AutoFarm.MaxFlightTime : 86400; var optimalSpeed = _calculationService.CalcOptimalFarmSpeed(tempCelestial.Coordinate, target.Celestial.Coordinate, attackingShips, target.Report.Loot(_tbotInstance.UserData.userInfo.Class), lootFuelRatio, maxFlightTime, _tbotInstance.UserData.researches, _tbotInstance.UserData.serverData, tempCelestial.LFBonuses, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.allianceClass); if (optimalSpeed == 0) { @@ -688,13 +901,11 @@ protected override async Task Execute() { if (fromCelestial == null) { _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"No origin celestial available near destination {target.Celestial.ToString()} with enough cargo ships."); - // TODO Future: If prefered cargo ship is not available or not sufficient capacity, combine with other cargo type. foreach (var closest in closestCelestials) { tempCelestial = closest; tempCelestial = await _tbotOgameBridge.UpdatePlanet(tempCelestial, UpdateTypes.Ships); tempCelestial = await _tbotOgameBridge.UpdatePlanet(tempCelestial, UpdateTypes.Resources); tempCelestial = await _tbotOgameBridge.UpdatePlanet(tempCelestial, UpdateTypes.LFBonuses); - // TODO Future: If fleet composition is changed, update ships passed to CalcFlightTime. speed = 0; if (SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "FleetSpeed") && _tbotInstance.InstanceSettings.AutoFarm.FleetSpeed > 0) { speed = (int) _tbotInstance.InstanceSettings.AutoFarm.FleetSpeed / 10; @@ -704,7 +915,7 @@ protected override async Task Execute() { } } else { speed = 0; - if (/*cargoShip == Buildables.EspionageProbe &&*/ SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "MinLootFuelRatio") && _tbotInstance.InstanceSettings.AutoFarm.MinLootFuelRatio != 0) { + if (SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "MinLootFuelRatio") && _tbotInstance.InstanceSettings.AutoFarm.MinLootFuelRatio != 0) { long maxFlightTime = SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "MaxFlightTime") ? (long) _tbotInstance.InstanceSettings.AutoFarm.MaxFlightTime : 86400; var optimalSpeed = _calculationService.CalcOptimalFarmSpeed(tempCelestial.Coordinate, target.Celestial.Coordinate, attackingShips, target.Report.Loot(_tbotInstance.UserData.userInfo.Class), lootFuelRatio, maxFlightTime, _tbotInstance.UserData.researches, _tbotInstance.UserData.serverData, tempCelestial.LFBonuses, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.allianceClass); if (optimalSpeed == 0) { @@ -777,7 +988,6 @@ protected override async Task Execute() { continue; } - // Only execute update slots if our local copy indicates we have run out. if (freeSlots <= slotsToLeaveFree) { _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); freeSlots = _tbotInstance.UserData.slots.Free; @@ -785,7 +995,6 @@ protected override async Task Execute() { while (freeSlots <= slotsToLeaveFree) { _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); - // No slots free, wait for first fleet to come back. if (_tbotInstance.UserData.fleets.Any()) { int interval = (int) ((1000 * _tbotInstance.UserData.fleets.OrderBy(fleet => fleet.BackIn).First().BackIn) + RandomizeHelper.CalcRandomInterval(IntervalType.AFewSeconds)); if (SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "MaxWaitTime") && (int) _tbotInstance.InstanceSettings.AutoFarm.MaxWaitTime != 0 && interval > (int) _tbotInstance.InstanceSettings.AutoFarm.MaxWaitTime * 1000) { @@ -808,13 +1017,37 @@ protected override async Task Execute() { .Where(fleet => fleet.Mission == Missions.Attack) .ToList(); - if (_tbotInstance.UserData.slots.Free > slotsToLeaveFree && slotUsed.Count() < (int) _tbotInstance.InstanceSettings.AutoFarm.MaxSlots) { + if (_tbotInstance.UserData.slots.Free > slotsToLeaveFree && slotUsed.Count() < MaxSlots) { + fromCelestial = await _tbotOgameBridge.UpdatePlanet(fromCelestial, UpdateTypes.Ships); + var availableShips = fromCelestial.Ships.GetAmount(cargoShip) - (long) _tbotInstance.InstanceSettings.AutoFarm.MinCargosToKeep; + if (availableShips <= 0) { + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"No {cargoShip.ToString()} available on {fromCelestial.ToString()} (all ships already in use). Skipping target."); + continue; + } + if (availableShips < numCargo) { + _tbotInstance.log(LogLevel.Debug, LogSender.AutoFarm, $"Only {availableShips} {cargoShip.ToString()} available (needed {numCargo}). Adjusting fleet size."); + numCargo = availableShips; + attackingShips = new Ships(); + attackingShips.Add(cargoShip, numCargo); + + var cargoCapacity = _calculationService.CalcFleetCapacity( + attackingShips, _tbotInstance.UserData.serverData, _tbotInstance.UserData.researches.HyperspaceTechnology, + fromCelestial.LFBonuses, _tbotInstance.UserData.userInfo.Class); + var totalLoot = target.Report.Loot(_tbotInstance.UserData.userInfo.Class).TotalResources; + + if (cargoCapacity < totalLoot) { + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, + $"Insufficient cargo space after adjustment. {numCargo} {cargoShip.ToString()} can carry {cargoCapacity:N0} but need {totalLoot:N0}. Skipping target."); + continue; + } + } + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"Attacking {target.ToString()} from {fromCelestial} with {numCargo} {cargoShip.ToString()}."); Ships ships = new(); fromCelestial = await _tbotOgameBridge.UpdatePlanet(fromCelestial, UpdateTypes.LFBonuses); speed = 0; - if (/*cargoShip == Buildables.EspionageProbe &&*/ SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "MinLootFuelRatio") && _tbotInstance.InstanceSettings.AutoFarm.MinLootFuelRatio != 0) { + if (SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "MinLootFuelRatio") && _tbotInstance.InstanceSettings.AutoFarm.MinLootFuelRatio != 0) { long maxFlightTime = SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "MaxFlightTime") ? (long) _tbotInstance.InstanceSettings.AutoFarm.MaxFlightTime : 86400; var optimalSpeed = _calculationService.CalcOptimalFarmSpeed(fromCelestial.Coordinate, target.Celestial.Coordinate, attackingShips, target.Report.Loot(_tbotInstance.UserData.userInfo.Class), lootFuelRatio, maxFlightTime, _tbotInstance.UserData.researches, _tbotInstance.UserData.serverData, fromCelestial.LFBonuses, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.allianceClass); if (optimalSpeed == 0) { @@ -841,6 +1074,8 @@ protected override async Task Execute() { if (fleetId > (int) SendFleetCode.GenericError) { freeSlots--; + + _successfulTargets.RecordAttack(target.Celestial.Coordinate, loot); } else if (fleetId == (int) SendFleetCode.AfterSleepTime) { stop = true; return; @@ -850,7 +1085,7 @@ protected override async Task Execute() { target.State = FarmState.AttackSent; _tbotInstance.UserData.farmTargets.Add(target); } else { - _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"Unable to attack {target.Celestial.Coordinate}: {slotUsed.Count()} slots used by AutoFarm, {_tbotInstance.InstanceSettings.AutoFarm.MaxSlots} slots usable by AutoFarm, {_tbotInstance.UserData.slots.Free} slots free, {slotsToLeaveFree} must remain free."); + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"Unable to attack {target.Celestial.Coordinate}: {slotUsed.Count()} slots used by AutoFarm, {MaxSlots} slots usable by AutoFarm, {_tbotInstance.UserData.slots.Free} slots free, {_tbotInstance.InstanceSettings.General.SlotsToLeaveFree} must remain free."); return; } } @@ -859,17 +1094,33 @@ protected override async Task Execute() { _tbotInstance.log(LogLevel.Error, LogSender.AutoFarm, $"AutoFarm Exception: {e.Message}"); _tbotInstance.log(LogLevel.Warning, LogSender.AutoFarm, $"Stacktrace: {e.StackTrace}"); } finally { + if (stopAfterFullScan && finishedFullScan) { + stop = true; + } + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"Attacked targets: {_tbotInstance.UserData.farmTargets.Where(t => t.State == FarmState.AttackSent).Count()}"); - _tbotInstance.UserData.farmTargets.RemoveAll(t => t.State == FarmState.ProbesSent); //At this point no ProbesSent should remain in farmTargets + _tbotInstance.UserData.farmTargets.RemoveAll(t => t.State == FarmState.ProbesSent); if (!_tbotInstance.UserData.isSleeping) { if (stop) { _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"Stopping feature."); await EndExecution(); } else { var time = await _tbotOgameBridge.GetDateTime(); - var interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.AutoFarm.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.AutoFarm.CheckIntervalMax); - if (interval <= 0) - interval = RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); + _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); + List orderedFleets = _tbotInstance.UserData.fleets + .Where(fleet => fleet.Mission == Missions.Attack) + .ToList(); + orderedFleets = orderedFleets + .OrderByDescending(fleet => fleet.BackIn) + .ToList(); + long interval; + try { + interval = (int) ((1000 * orderedFleets.First().BackIn) + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds)); + } catch { + interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.AutoFarm.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.AutoFarm.CheckIntervalMax); + if (interval <= 0) + interval = RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); + } var newTime = time.AddMilliseconds(interval); ChangeWorkerPeriod(interval); _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"Next autofarm check at {newTime.ToString()}"); @@ -879,12 +1130,7 @@ protected override async Task Execute() { } } - /// - /// Checks all received espionage reports and updates _tbotInstance.UserData.farmTargets to reflect latest data retrieved from reports. - /// private async Task AutoFarmProcessReports() { - // TODO Future: Read espionage reports in separate thread (concurently with probing itself). - // TODO Future: Check if probes were destroyed, blacklist target if so to avoid additional kills. List summaryReports = await _ogameService.GetEspionageReports(); foreach (var summary in summaryReports) { if (summary.Type == EspionageReportType.Action) @@ -901,10 +1147,8 @@ private async Task AutoFarmProcessReports() { FarmTarget target; var matchingTarget = _tbotInstance.UserData.farmTargets.Where(t => t.HasCoords(report.Coordinate)); if (matchingTarget.Count() == 0) { - // Report received of planet not in _tbotInstance.UserData.farmTargets. If inactive: add, otherwise: ignore. if (!report.IsInactive) continue; - //Get corresponding planet. Add to target list. var galaxyInfo = await _ogameService.GetGalaxyInfo(report.Coordinate.Galaxy, report.Coordinate.System); var planet = galaxyInfo.Planets.FirstOrDefault(p => p != null && p.Inactive && !p.Administrator && !p.Banned && !p.Vacation && p.HasCoords(report.Coordinate)); if (planet != null) { @@ -920,11 +1164,13 @@ private async Task AutoFarmProcessReports() { var newFarmTarget = target; if (target.Report != null && DateTime.Compare(report.Date, target.Report.Date) < 0) { - // Target has a more recent report. Delete report. await _ogameService.DeleteReport(report.ID); continue; } + Buildables cargoShip; + Enum.TryParse((string) _tbotInstance.InstanceSettings.AutoFarm.CargoType, true, out cargoShip); + bool isUsingProbes = cargoShip == Buildables.EspionageProbe && _tbotInstance.UserData.serverData.ProbeCargo == 1 ? true : false; newFarmTarget.Report = report; if (_tbotInstance.InstanceSettings.AutoFarm.PreferedResource == "Metal" && report.Loot(_tbotInstance.UserData.userInfo.Class).Metal > _tbotInstance.InstanceSettings.AutoFarm.MinimumResources || _tbotInstance.InstanceSettings.AutoFarm.PreferedResource == "Crystal" && report.Loot(_tbotInstance.UserData.userInfo.Class).Crystal > _tbotInstance.InstanceSettings.AutoFarm.MinimumResources @@ -939,23 +1185,94 @@ private async Task AutoFarmProcessReports() { newFarmTarget.State = FarmState.ProbesRequired; _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"Need more probes on {report.Coordinate}. Loot: {report.Loot(_tbotInstance.UserData.userInfo.Class)}"); - } else if (report.IsDefenceless()) { + } else if (report.IsDefenceless(isUsingProbes)) { newFarmTarget.State = FarmState.AttackPending; _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"Attack pending on {report.Coordinate}. Loot: {report.Loot(_tbotInstance.UserData.userInfo.Class)}"); } else { newFarmTarget.State = FarmState.NotSuitable; _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"Target {report.Coordinate} not suitable - defences present."); + bool blacklistActive = SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "Blacklist") && + SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm.Blacklist, "Active") && + (bool) _tbotInstance.InstanceSettings.AutoFarm.Blacklist.Active; + if (blacklistActive) { + int hoursUntilReset = SettingsService.GetSetting(_tbotInstance.InstanceSettings.AutoFarm.Blacklist, "ResetAfterHours", 48); + _blacklist.AddTarget(report.Coordinate, BlacklistReason.HasDefense, hoursUntilReset); + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"Target {report.Coordinate} blacklisted for {hoursUntilReset}h (defenses present)."); + } } } else { newFarmTarget.State = FarmState.NotSuitable; _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"Target {report.Coordinate} not suitable - insufficient loot ({report.Loot(_tbotInstance.UserData.userInfo.Class)})"); + bool blacklistActiveLowRes = SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "Blacklist") && + SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm.Blacklist, "Active") && + (bool) _tbotInstance.InstanceSettings.AutoFarm.Blacklist.Active; + if (blacklistActiveLowRes) { + long minResources = SettingsService.GetSetting(_tbotInstance.InstanceSettings.AutoFarm.Blacklist, "MinimumResourcesToNotBlacklist", (long)500000); + if (report.Loot(_tbotInstance.UserData.userInfo.Class).TotalResources < minResources) { + int hoursUntilReset = SettingsService.GetSetting(_tbotInstance.InstanceSettings.AutoFarm.Blacklist, "ResetAfterHours", 48); + _blacklist.AddTarget(report.Coordinate, BlacklistReason.LowResources, hoursUntilReset); + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"Target {report.Coordinate} blacklisted for {hoursUntilReset}h (low resources: {report.Loot(_tbotInstance.UserData.userInfo.Class)})."); + } + } } _tbotInstance.UserData.farmTargets.Remove(target); _tbotInstance.UserData.farmTargets.Add(newFarmTarget); + } else { + bool processAllReports = (SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "ProcessAllReports") && + (bool) _tbotInstance.InstanceSettings.AutoFarm.ProcessAllReports) || + (SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "Blacklist") && + SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm.Blacklist, "ProcessAllReports") && + (bool) _tbotInstance.InstanceSettings.AutoFarm.Blacklist.ProcessAllReports); + if (processAllReports && report.IsInactive) { + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"Processing report for {report.Coordinate} not scanned by TBot (ProcessAllReports enabled)..."); + var galaxyInfo = await _ogameService.GetGalaxyInfo(report.Coordinate.Galaxy, report.Coordinate.System); + var planet = galaxyInfo.Planets.FirstOrDefault(p => p != null && p.Inactive && !p.Administrator && !p.Banned && !p.Vacation && p.HasCoords(report.Coordinate)); + if (planet != null) { + var target = GetFarmTarget(planet); + if (target != null) { + var newFarmTarget = target; + Buildables cargoShip; + Enum.TryParse((string) _tbotInstance.InstanceSettings.AutoFarm.CargoType, true, out cargoShip); + bool isUsingProbes = cargoShip == Buildables.EspionageProbe && _tbotInstance.UserData.serverData.ProbeCargo == 1 ? true : false; + newFarmTarget.Report = report; + if (report.Loot(_tbotInstance.UserData.userInfo.Class).TotalResources > _tbotInstance.InstanceSettings.AutoFarm.MinimumResources) { + if (report.HasFleetInformation && report.HasDefensesInformation) { + if (report.IsDefenceless(isUsingProbes)) { + newFarmTarget.State = FarmState.AttackPending; + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"Attack pending on {report.Coordinate}. Loot: {report.Loot(_tbotInstance.UserData.userInfo.Class)}"); + } else { + newFarmTarget.State = FarmState.NotSuitable; + bool blacklistActiveDefense2 = SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "Blacklist") && + (bool) _tbotInstance.InstanceSettings.AutoFarm.Blacklist.Active; + if (blacklistActiveDefense2) { + int hoursUntilReset = SettingsService.GetSetting(_tbotInstance.InstanceSettings.AutoFarm.Blacklist, "ResetAfterHours", 48); + _blacklist.AddTarget(report.Coordinate, BlacklistReason.HasDefense, hoursUntilReset); + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"Target {report.Coordinate} blacklisted for {hoursUntilReset}h (defenses present)."); + } + } + } + } else { + newFarmTarget.State = FarmState.NotSuitable; + bool blacklistActiveLowRes2 = SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.AutoFarm, "Blacklist") && + (bool) _tbotInstance.InstanceSettings.AutoFarm.Blacklist.Active; + if (blacklistActiveLowRes2) { + long minResources = SettingsService.GetSetting(_tbotInstance.InstanceSettings.AutoFarm.Blacklist, "MinimumResourcesToNotBlacklist", (long)500000); + if (report.Loot(_tbotInstance.UserData.userInfo.Class).TotalResources < minResources) { + int hoursUntilReset = SettingsService.GetSetting(_tbotInstance.InstanceSettings.AutoFarm.Blacklist, "ResetAfterHours", 48); + _blacklist.AddTarget(report.Coordinate, BlacklistReason.LowResources, hoursUntilReset); + _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"Target {report.Coordinate} blacklisted for {hoursUntilReset}h (low resources: {report.Loot(_tbotInstance.UserData.userInfo.Class)})."); + } + } + } + _tbotInstance.UserData.farmTargets.Remove(target); + _tbotInstance.UserData.farmTargets.Add(newFarmTarget); + } + } } else { _tbotInstance.log(LogLevel.Information, LogSender.AutoFarm, $"Target {report.Coordinate} not scanned by TBot, ignoring..."); } + } } catch (Exception e) { _tbotInstance.log(LogLevel.Error, LogSender.AutoFarm, $"AutoFarmProcessReports Exception: {e.Message}"); _tbotInstance.log(LogLevel.Warning, LogSender.AutoFarm, $"Stacktrace: {e.StackTrace}"); @@ -963,8 +1280,21 @@ private async Task AutoFarmProcessReports() { } } - await _ogameService.DeleteAllEspionageReports(); + int deleteRetries = 3; + for (int i = 0; i < deleteRetries; i++) { + try { + await _ogameService.DeleteAllEspionageReports(); + break; + } catch (Exception e) when (e.Message.Contains("503") || e.Message.Contains("Service Unavailable") || e.Message.Contains("Unable to delete")) { + if (i < deleteRetries - 1) { + _tbotInstance.log(LogLevel.Warning, LogSender.AutoFarm, $"Failed to delete espionage reports (503 error), retry {i + 1}/{deleteRetries}..."); + await Task.Delay(3000); + } else { + _tbotInstance.log(LogLevel.Warning, LogSender.AutoFarm, $"Could not delete espionage reports after {deleteRetries} attempts. Will try next cycle."); + } + } + } } } -} +} \ No newline at end of file diff --git a/TBot/Workers/Brain/AutoDefenceWorker.cs b/TBot/Workers/Brain/AutoDefenceWorker.cs new file mode 100644 index 00000000..c49c2c43 --- /dev/null +++ b/TBot/Workers/Brain/AutoDefenceWorker.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Tbot.Helpers; +using Tbot.Includes; +using Tbot.Services; +using TBot.Common.Logging; +using TBot.Model; +using TBot.Ogame.Infrastructure; +using TBot.Ogame.Infrastructure.Enums; +using TBot.Ogame.Infrastructure.Models; + +namespace Tbot.Workers.Brain { + public class AutoDefenceWorker : WorkerBase { + private readonly IOgameService _ogameService; + private readonly IFleetScheduler _fleetScheduler; + private readonly ICalculationService _calculationService; + private readonly ITBotOgamedBridge _tbotOgameBridge; + public AutoDefenceWorker(ITBotMain parentInstance, + IOgameService ogameService, + IFleetScheduler fleetScheduler, + ICalculationService calculationService, + ITBotOgamedBridge tbotOgameBridge) : + base(parentInstance) { + _ogameService = ogameService; + _fleetScheduler = fleetScheduler; + _calculationService = calculationService; + _tbotOgameBridge = tbotOgameBridge; + } + + protected override async Task Execute() { + try { + DoLog(LogLevel.Information, "Running autodefence..."); + + List newCelestials = _tbotInstance.UserData.celestials.ToList(); + List celestialsToExclude = _calculationService.ParseCelestialsList(_tbotInstance.InstanceSettings.Brain.AutoDefence.Exclude, _tbotInstance.UserData.celestials); + + Defences neededDefences = new( + (long) _tbotInstance.InstanceSettings.Brain.AutoDefence.DefenceToReach.RocketLauncher, + (long) _tbotInstance.InstanceSettings.Brain.AutoDefence.DefenceToReach.LightLaser, + (long) _tbotInstance.InstanceSettings.Brain.AutoDefence.DefenceToReach.HeavyLaser, + (long) _tbotInstance.InstanceSettings.Brain.AutoDefence.DefenceToReach.GaussCannon, + (long) _tbotInstance.InstanceSettings.Brain.AutoDefence.DefenceToReach.IonCannon, + (long) _tbotInstance.InstanceSettings.Brain.AutoDefence.DefenceToReach.PlasmaTurret, + (long) ((bool) _tbotInstance.InstanceSettings.Brain.AutoDefence.DefenceToReach.SmallShieldDome ? 1: 0), + (long) ((bool) _tbotInstance.InstanceSettings.Brain.AutoDefence.DefenceToReach.LargeShieldDome ? 1: 0), + (long) _tbotInstance.InstanceSettings.Brain.AutoDefence.DefenceToReach.AntiBallisticMissiles, + (long) _tbotInstance.InstanceSettings.Brain.AutoDefence.DefenceToReach.InterplanetaryMissiles + ); + foreach (Celestial celestial in (bool) _tbotInstance.InstanceSettings.Brain.AutoDefence.RandomOrder ? _tbotInstance.UserData.celestials.Shuffle().ToList() : _tbotInstance.UserData.celestials) { + if (celestialsToExclude.Has(celestial)) { + DoLog(LogLevel.Information, $"Skipping {celestial.ToString()}: celestial in exclude list."); + continue; + } + + var tempCelestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.Fast); + + tempCelestial = await _tbotOgameBridge.UpdatePlanet(tempCelestial, UpdateTypes.Productions); + if (tempCelestial.HasProduction()) { + DoLog(LogLevel.Warning, $"Skipping {tempCelestial.ToString()}: there is already a production ongoing."); + foreach (Production production in tempCelestial.Productions) { + Buildables productionType = (Buildables) production.ID; + DoLog(LogLevel.Information, $"Skipping {tempCelestial.ToString()}: {production.Nbr}x{productionType.ToString()} are already in production."); + } + continue; + } + tempCelestial = await _tbotOgameBridge.UpdatePlanet(tempCelestial, UpdateTypes.Constructions); + if (tempCelestial.Constructions.BuildingID == (int) Buildables.Shipyard || tempCelestial.Constructions.BuildingID == (int) Buildables.NaniteFactory) { + Buildables buildingInProgress = (Buildables) tempCelestial.Constructions.BuildingID; + DoLog(LogLevel.Information, $"Skipping {tempCelestial.ToString()}: {buildingInProgress.ToString()} is upgrading."); + continue; + } + + tempCelestial = await _tbotOgameBridge.UpdatePlanet(tempCelestial, UpdateTypes.Ships); + tempCelestial = await _tbotOgameBridge.UpdatePlanet(tempCelestial, UpdateTypes.Defences); + tempCelestial = await _tbotOgameBridge.UpdatePlanet(tempCelestial, UpdateTypes.Resources); + tempCelestial = await _tbotOgameBridge.UpdatePlanet(tempCelestial, UpdateTypes.LFBonuses); + + var capacity = _calculationService.CalcFleetCapacity(tempCelestial.Ships, _tbotInstance.UserData.serverData, _tbotInstance.UserData.researches.HyperspaceTechnology, tempCelestial.LFBonuses, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.serverData.ProbeCargo); + if (tempCelestial.Coordinate.Type == Celestials.Moon && (bool) _tbotInstance.InstanceSettings.Brain.AutoDefence.ExcludeMoons) { + DoLog(LogLevel.Information, $"Skipping {tempCelestial.ToString()}: celestial is a moon."); + continue; + } + Defences currentDefences = tempCelestial.Defences; + Defences defencesToBuild = neededDefences.Difference(currentDefences); + if (defencesToBuild.IsEmpty()) { + DoLog(LogLevel.Information, $"Skipping {tempCelestial.ToString()}: all defences are already built."); + continue; + } else { + Resources defenceCost = defencesToBuild.GetDefenceCost(); + if (!tempCelestial.Resources.IsEnoughFor(defenceCost)) { + DoLog(LogLevel.Information, $"{tempCelestial.ToString()}: Not enough resources to build all defences. Will build only what is possible."); + defencesToBuild = _calculationService.CalcmaxDefencesBuildable(defencesToBuild, tempCelestial.Resources); + } + if (defencesToBuild.IsEmpty()) { + DoLog(LogLevel.Information, $"{tempCelestial.ToString()}: Not enough resources to build any defence."); + continue; + } + DoLog(LogLevel.Information, $"{tempCelestial.ToString()}: Defences to build: {defencesToBuild.ToString()}"); + try { + foreach (var (defenceType, amountNeeded) in defencesToBuild.GetDefenceTypesWithAmount()) { + await _ogameService.BuildDefences(tempCelestial, defenceType, amountNeeded); + } + DoLog(LogLevel.Information, "Production succesfully started."); + } catch { + DoLog(LogLevel.Warning, "Unable to start defence production."); + } + tempCelestial = await _tbotOgameBridge.UpdatePlanet(tempCelestial, UpdateTypes.Productions); + foreach (Production production in tempCelestial.Productions) { + Buildables productionType = (Buildables) production.ID; + DoLog(LogLevel.Information, $"{tempCelestial.ToString()}: {production.Nbr}x{productionType.ToString()} are in production."); + } + } + + newCelestials.Remove(celestial); + newCelestials.Add(tempCelestial); + } + _tbotInstance.UserData.celestials = newCelestials; + } catch (Exception e) { + DoLog(LogLevel.Error, $"Unable to complete autodefence: {e.Message}"); + DoLog(LogLevel.Warning, $"Stacktrace: {e.StackTrace}"); + } finally { + var time = await _tbotOgameBridge.GetDateTime(); + var interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.Brain.AutoDefence.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.Brain.AutoDefence.CheckIntervalMax); + if (interval <= 0) + interval = RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); + var newTime = time.AddMilliseconds(interval); + ChangeWorkerPeriod(interval); + DoLog(LogLevel.Information, $"Next Defence check at {newTime.ToString()}"); + await _tbotOgameBridge.CheckCelestials(); + } + } + + public override bool IsWorkerEnabledBySettings() { + try { + return ((bool) _tbotInstance.InstanceSettings.Brain.Active && (bool) _tbotInstance.InstanceSettings.Brain.AutoDefence.Active); + } catch(Exception) { + return false; + } + } + + public override string GetWorkerName() { + return "AutoDefence"; + } + public override Feature GetFeature() { + return Feature.BrainAutobuildDefence; + } + + public override LogSender GetLogSender() { + return LogSender.AutoDefence; + } + } +} diff --git a/TBot/Workers/Brain/AutoMineCelestialWorker.cs b/TBot/Workers/Brain/AutoMineCelestialWorker.cs index bc3aa727..83401c54 100644 --- a/TBot/Workers/Brain/AutoMineCelestialWorker.cs +++ b/TBot/Workers/Brain/AutoMineCelestialWorker.cs @@ -134,6 +134,7 @@ private async Task AutoMineCelestial(Celestial celestial, Buildings maxBuildings celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.LFBonuses); celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.LFBuildings); Planet abaCelestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.Fast) as Planet; + _tbotInstance.UserData.celestials = await _tbotOgameBridge.UpdateCelestials(); if ( (!SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.Brain.AutoMine, "BuildCrawlers") || (bool) _tbotInstance.InstanceSettings.Brain.AutoMine.BuildCrawlers) && celestial.Coordinate.Type == Celestials.Planet && @@ -203,7 +204,7 @@ private async Task AutoMineCelestial(Celestial celestial, Buildings maxBuildings DoLog(LogLevel.Debug, $"Days of investment return: {Math.Round(DOIR, 2).ToString()} days."); } - Resources xCostBuildable = _calculationService.CalcPrice(buildable, level); + Resources xCostBuildable = _calculationService.CalcPrice(buildable, level, celestial.LFBonuses); if (celestial is Moon) xCostBuildable.Deuterium += (long) autoMinerSettings.DeutToLeaveOnMoons; @@ -213,7 +214,7 @@ private async Task AutoMineCelestial(Celestial celestial, Buildings maxBuildings if ((bool) _tbotInstance.InstanceSettings.Brain.AutoMine.BuildSolarSatellites) { buildable = Buildables.SolarSatellite; level = _calculationService.CalcNeededSolarSatellites(celestial as Planet, xCostBuildable.Energy - celestial.ResourcesProduction.Energy.CurrentProduction, _tbotInstance.UserData.userInfo.Class == CharacterClass.Collector, _tbotInstance.UserData.staff.Engineer, _tbotInstance.UserData.staff.IsFull); - xCostBuildable = _calculationService.CalcPrice(buildable, level); + xCostBuildable = _calculationService.CalcPrice(buildable, level, celestial.LFBonuses); } else { DoLog(LogLevel.Information, $"Unable to build SolarSatellites for Terraformer. Stopping AutoMiner for celestial {celestial.ToString()}"); @@ -297,32 +298,258 @@ private async Task AutoMineCelestial(Celestial celestial, Buildings maxBuildings } if (buildable == Buildables.SolarSatellite || buildable == Buildables.Crawler) { DoLog(LogLevel.Information, $"Not enough resources to build: {level.ToString()}x {buildable.ToString()} on {celestial.ToString()}. Needed: {xCostBuildable.TransportableResources} - Available: {celestial.Resources.TransportableResources}"); - } else { DoLog(LogLevel.Information, $"Not enough resources to build: {buildable.ToString()} level {level.ToString()} on {celestial.ToString()}. Needed: {xCostBuildable.TransportableResources} - Available: {celestial.Resources.TransportableResources}"); } if ((bool) _tbotInstance.InstanceSettings.Brain.AutoMine.Transports.Active && (bool) _tbotInstance.InstanceSettings.Brain.Transports.Active) { + _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); - if (!_calculationService.IsThereTransportTowardsCelestial(celestial, _tbotInstance.UserData.fleets)) { - Celestial origin = _tbotInstance.UserData.celestials - .Unique() - .Where(c => c.Coordinate.Galaxy == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Galaxy) - .Where(c => c.Coordinate.System == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.System) - .Where(c => c.Coordinate.Position == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Position) - .Where(c => c.Coordinate.Type == Enum.Parse((string) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Type)) - .SingleOrDefault() ?? new() { ID = 0 }; - fleetId = await _fleetScheduler.HandleMinerTransport(origin, celestial, xCostBuildable, buildable, maxBuildings, maxFacilities, maxLunarFacilities, autoMinerSettings); - - if (fleetId == (int) SendFleetCode.AfterSleepTime) { - stop = true; - return; - } - if (fleetId == (int) SendFleetCode.NotEnoughSlots) { - delay = true; - return; + List rankSlotsPriority = new() { + new RankSlotsPriority(Feature.BrainAutoMine, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Brain, + ((bool) _tbotInstance.InstanceSettings.Brain.Active && (bool) _tbotInstance.InstanceSettings.Brain.Transports.Active && ((bool) _tbotInstance.InstanceSettings.Brain.AutoMine.Active || (bool) _tbotInstance.InstanceSettings.Brain.AutoResearch.Active || (bool) _tbotInstance.InstanceSettings.Brain.LifeformAutoMine.Active || (bool) _tbotInstance.InstanceSettings.Brain.LifeformAutoResearch.Active)), + (int) _tbotInstance.InstanceSettings.Brain.Transports.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Transport)), + new RankSlotsPriority(Feature.Expeditions, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Expeditions, + (bool) _tbotInstance.InstanceSettings.Expeditions.Active, + (int) _tbotInstance.UserData.slots.ExpTotal, + (int)_tbotInstance.UserData.slots.ExpInUse), + new RankSlotsPriority(Feature.AutoFarm, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoFarm, + (bool) _tbotInstance.InstanceSettings.AutoFarm.Active, + (int) _tbotInstance.InstanceSettings.AutoFarm.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Attack)), + new RankSlotsPriority(Feature.Colonize, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Colonize, + (bool) _tbotInstance.InstanceSettings.AutoColonize.Active, + (bool) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.Active ? + (int) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.MaxSlots : + 1, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Colonize)), + new RankSlotsPriority(Feature.AutoDiscovery, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoDiscovery, + (bool) _tbotInstance.InstanceSettings.AutoDiscovery.Active, + (int) _tbotInstance.InstanceSettings.AutoDiscovery.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Discovery)), + new RankSlotsPriority(Feature.Harvest, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoHarvest, + (bool) _tbotInstance.InstanceSettings.AutoHarvest.Active, + (int) _tbotInstance.InstanceSettings.AutoHarvest.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Harvest)) + }; + int MaxSlots = _calculationService.CalcSlotsPriority(Feature.BrainAutoMine, rankSlotsPriority, _tbotInstance.UserData.slots, _tbotInstance.UserData.fleets, (int) _tbotInstance.InstanceSettings.General.SlotsToLeaveFree); + + if (MaxSlots > 0) { + if (!_calculationService.IsThereTransportTowardsCelestial(celestial, _tbotInstance.UserData.fleets)) { + Celestial origin = new() { ID = 0 }; + List allCelestials = _tbotInstance.UserData.celestials; + for (int i = 0; i < allCelestials.Count(); i++) { + allCelestials[i] = await _tbotOgameBridge.UpdatePlanet(allCelestials[i], UpdateTypes.Resources); + allCelestials[i] = await _tbotOgameBridge.UpdatePlanet(allCelestials[i], UpdateTypes.Ships); + allCelestials[i] = await _tbotOgameBridge.UpdatePlanet(allCelestials[i], UpdateTypes.LFBonuses); + } + Resources missingResources = xCostBuildable.Difference(celestial.Resources); + if ((bool) _tbotInstance.InstanceSettings.Brain.Transports.CheckMoonOrPlanetFirst) { + if (celestial.Coordinate.Type == Celestials.Planet && ((bool) _tbotInstance.InstanceSettings.Brain.Transports.CheckMoonOrPlanetFirst && _calculationService.IsThereMoonHere(allCelestials, celestial))) { + origin = allCelestials.Unique() + .Where(c => c.Coordinate.Galaxy == celestial.Coordinate.Galaxy) + .Where(c => c.Coordinate.System == celestial.Coordinate.System) + .Where(c => c.Coordinate.Position == celestial.Coordinate.Position) + .Where(c => c.Coordinate.Type == Celestials.Moon) + .SingleOrDefault() ?? new() { ID = 0 }; + } + if (celestial.Coordinate.Type == Celestials.Moon) { + origin = allCelestials.Unique() + .Where(c => c.Coordinate.Galaxy == celestial.Coordinate.Galaxy) + .Where(c => c.Coordinate.System == celestial.Coordinate.System) + .Where(c => c.Coordinate.Position == celestial.Coordinate.Position) + .Where(c => c.Coordinate.Type == Celestials.Planet) + .SingleOrDefault() ?? new() { ID = 0 }; + } + if (origin.ID != 0) { + if (origin.Resources.IsEnoughFor(missingResources)) { + missingResources = _tbotInstance.InstanceSettings.Brain.Transports.RoundResources ? missingResources.Round() : missingResources; + Buildables preferredShip = Buildables.SmallCargo; + if (!Enum.TryParse((string) _tbotInstance.InstanceSettings.Brain.Transports.CargoType, true, out preferredShip)) { + _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, "Unable to parse CargoType. Falling back to default SmallCargo"); + preferredShip = Buildables.SmallCargo; + } + long shipsNeeded = _calculationService.CalcShipNumberForPayload(missingResources, preferredShip, _tbotInstance.UserData.researches.HyperspaceTechnology, _tbotInstance.UserData.serverData, origin.LFBonuses.GetShipCargoBonus(preferredShip), _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.serverData.ProbeCargo); + if (origin.Ships.GetAmount(preferredShip) >= shipsNeeded) { + Ships ships = new(); + ships.Add((Buildables) preferredShip, shipsNeeded); + fleetId = await _fleetScheduler.SendFleet(origin, ships, celestial.Coordinate, Missions.Transport, Speeds.HundredPercent, missingResources); + if (fleetId == (int) SendFleetCode.AfterSleepTime) { + stop = true; + } + if (fleetId == (int) SendFleetCode.NotEnoughSlots) { + delay = true; + } + } else if (_tbotInstance.InstanceSettings.Brain.Transports.DoMultipleTransportIsNotEnoughShipButSamePosition) { + DoLog(LogLevel.Warning, $"Not enough Cargo available. Multiple transports will take place."); + Ships ships = new(); + ships.Add((Buildables) preferredShip, origin.Ships.GetAmount(preferredShip)); + Resources transportableResources = _calculationService.CalcMaxTransportableResources(ships, missingResources, _tbotInstance.UserData.researches.HyperspaceTechnology, _tbotInstance.UserData.serverData, origin.LFBonuses, _tbotInstance.UserData.userInfo.Class, 0, _tbotInstance.UserData.serverData.ProbeCargo); + fleetId = await _fleetScheduler.SendFleet(origin, ships, celestial.Coordinate, Missions.Transport, Speeds.HundredPercent, transportableResources); + if (fleetId == (int) SendFleetCode.AfterSleepTime) { + stop = true; + } + if (fleetId == (int) SendFleetCode.NotEnoughSlots) { + delay = true; + } + } else { + DoLog(LogLevel.Warning, $"Not enough Cargo available. Skipping CheckMoonOrPlanetFirst."); + } + } else { + DoLog(LogLevel.Warning, $"Not enough resources available on {origin.ToString()} only to send resources to {celestial.ToString()}."); + } + } else { + DoLog(LogLevel.Warning, $"No Moon available on {celestial.ToString()}."); + } + } + + if (fleetId <= 0) { + if ((bool) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.Active) { + origin = _tbotInstance.UserData.celestials + .Unique() + .Where(c => c.Coordinate.Galaxy == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Galaxy) + .Where(c => c.Coordinate.System == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.System) + .Where(c => c.Coordinate.Position == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Position) + .Where(c => c.Coordinate.Type == Enum.Parse((string) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Type)) + .SingleOrDefault() ?? new() { ID = 0 }; + List celestialsToExclude = _calculationService.ParseCelestialsList(_tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.Exclude, allCelestials); + Buildables preferredShip = Buildables.SmallCargo; + if (!Enum.TryParse((string) _tbotInstance.InstanceSettings.Brain.Transports.CargoType, true, out preferredShip)) { + _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, "Unable to parse CargoType. Falling back to default SmallCargo"); + preferredShip = Buildables.SmallCargo; + } + TransportSettings transportsSettings = new((bool) _tbotInstance.InstanceSettings.Brain.Transports.Active, + preferredShip, + (long) _tbotInstance.InstanceSettings.Brain.Transports.DeutToLeaveOnMoons, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.RoundResources, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.SendToTheMoonIfPossible, + origin, + (long) _tbotInstance.InstanceSettings.Brain.Transports.MaxSlots, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.CheckMoonOrPlanetFirst, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.DoMultipleTransportIsNotEnoughShipButSamePosition, + new MultipleOrigins((bool) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.Active, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.OnlyFromMoons, + (long) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.MinimumResourcesToSend, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.PriorityToProximityOverQuantity, + celestialsToExclude) + ); + + Celestial destination; + if ((bool) transportsSettings.SendToTheMoonIfPossible && celestial.Coordinate.Type == Celestials.Planet && _calculationService.IsThereMoonHere(allCelestials, celestial)) { + destination = allCelestials + .Unique() + .Where(planet => planet.Coordinate.Galaxy == celestial.Coordinate.Galaxy) + .Where(planet => planet.Coordinate.System == celestial.Coordinate.System) + .Where(planet => planet.Coordinate.Position == celestial.Coordinate.Position) + .Where(planet => planet.Coordinate.Type == Celestials.Moon) + .SingleOrDefault() ?? new() { ID = 0 }; + if (destination.Ships.IsEmpty() || celestial.Resources.TotalResources == 0) + destination = celestial; + } else { + destination = allCelestials + .Unique() + .Where(planet => planet.Coordinate.Galaxy == celestial.Coordinate.Galaxy) + .Where(planet => planet.Coordinate.System == celestial.Coordinate.System) + .Where(planet => planet.Coordinate.Position == celestial.Coordinate.Position) + .Where(planet => planet.Coordinate.Type == celestial.Coordinate.Type) + .SingleOrDefault() ?? new() { ID = 0 }; + } + + var resultOrigins = _calculationService.CalcMultipleOrigin(celestial, allCelestials, missingResources, transportsSettings, _tbotInstance.UserData.fleets, _tbotInstance.UserData); + + if (resultOrigins.Count() == 0) { + DoLog(LogLevel.Information, $"No origin is available. This may be due to a lack of resources or cargo."); + return; + } + if (resultOrigins.Count() > MaxSlots) { + DoLog(LogLevel.Information, $"Not enough slots available to send all resources to build: {buildable.ToString()} level {level.ToString()} on {celestial.ToString()}. Slots needed: {resultOrigins.Count().ToString()}/{MaxSlots}."); + delay = true; + return; + } + + Ships ships = new(); + + foreach (var item in resultOrigins) { + ships = new(); + ships.Add((Buildables) transportsSettings.CargoType, _calculationService.CalcShipNumberForPayload(item.FirstOrDefault().Value, (Buildables) transportsSettings.CargoType, _tbotInstance.UserData.researches.HyperspaceTechnology, _tbotInstance.UserData.serverData, celestial.LFBonuses.GetShipCargoBonus(transportsSettings.CargoType), _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.serverData.ProbeCargo)); + if (item.FirstOrDefault().Key.Coordinate.IsSame(destination.Coordinate) && transportsSettings.SendToTheMoonIfPossible && destination.Coordinate.Type == Celestials.Moon) + fleetId= await _fleetScheduler.SendFleet(item.FirstOrDefault().Key, ships, celestial.Coordinate, Missions.Transport, Speeds.HundredPercent, item.FirstOrDefault().Value); + else + fleetId= await _fleetScheduler.SendFleet(item.FirstOrDefault().Key, ships, destination.Coordinate, Missions.Transport, Speeds.HundredPercent, item.FirstOrDefault().Value); + + if (fleetId == (int) SendFleetCode.AfterSleepTime) { + stop = true; + return; + } + if (fleetId == (int) SendFleetCode.NotEnoughSlots) { + delay = true; + return; + } + } + } else { + Celestial destination; + if ((bool) _tbotInstance.InstanceSettings.Brain.Transports.SendToTheMoonIfPossible && celestial.Coordinate.Type == Celestials.Planet && _calculationService.IsThereMoonHere(_tbotInstance.UserData.celestials, celestial) && (!celestial.Ships.IsEmpty() || celestial.Resources.TotalResources > 0)) { + destination = allCelestials + .Unique() + .Where(planet => planet.Coordinate.Galaxy == celestial.Coordinate.Galaxy) + .Where(planet => planet.Coordinate.System == celestial.Coordinate.System) + .Where(planet => planet.Coordinate.Position == celestial.Coordinate.Position) + .Where(planet => planet.Coordinate.Type == Celestials.Moon) + .SingleOrDefault() ?? new() { ID = 0 }; + if (destination.Ships.IsEmpty() || celestial.Resources.TotalResources == 0) + destination = celestial; + xCostBuildable = xCostBuildable.Difference(destination.Resources); + } else { + destination = allCelestials + .Unique() + .Where(planet => planet.Coordinate.Galaxy == celestial.Coordinate.Galaxy) + .Where(planet => planet.Coordinate.System == celestial.Coordinate.System) + .Where(planet => planet.Coordinate.Position == celestial.Coordinate.Position) + .Where(planet => planet.Coordinate.Type == celestial.Coordinate.Type) + .SingleOrDefault() ?? new() { ID = 0 }; + } + origin = allCelestials + .Unique() + .Where(c => c.Coordinate.Galaxy == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Galaxy) + .Where(c => c.Coordinate.System == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.System) + .Where(c => c.Coordinate.Position == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Position) + .Where(c => c.Coordinate.Type == Enum.Parse((string) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Type)) + .SingleOrDefault() ?? new() { ID = 0 }; + fleetId = await _fleetScheduler.HandleMinerTransport(origin, celestial, destination, xCostBuildable, buildable, maxBuildings, maxFacilities, maxLunarFacilities, autoMinerSettings); + if (fleetId == (int) SendFleetCode.AfterSleepTime) { + stop = true; + } + if (fleetId == (int) SendFleetCode.NotEnoughSlots) { + delay = true; + } + } + } + } else { + DoLog(LogLevel.Information, $"Skipping transport: there is already a transport incoming in {celestial.ToString()}"); + fleetId = _tbotInstance.UserData.fleets.Where(f => f.Mission == Missions.Transport) + .Where(f => f.Resources.TotalResources > 0) + .Where(f => f.ReturnFlight == false) + .Where(f => f.Destination.Galaxy == celestial.Coordinate.Galaxy) + .Where(f => f.Destination.System == celestial.Coordinate.System) + .Where(f => f.Destination.Position == celestial.Coordinate.Position) + .Where(f => f.Destination.Type == celestial.Coordinate.Type) + .First().ID; } } else { - DoLog(LogLevel.Information, $"Skipping transport: there is already a transport incoming in {celestial.ToString()}"); + if (_tbotInstance.InstanceSettings.General.SlotPriorityLevel.Brain > 0) { + if (_tbotInstance.InstanceSettings.Brain.Transports.MaxSlots == 0) + DoLog(LogLevel.Information, $"Transports.MaxSlots is set to 0, you should increase it."); + else + DoLog(LogLevel.Information, $"0 slots available."); + } + DoLog(LogLevel.Information, $"Not enough slots available for Transports.MaxSlots, delaying."); + delay = true; } } } @@ -368,6 +595,7 @@ private async Task AutoMineCelestial(Celestial celestial, Buildings maxBuildings var time = await _tbotOgameBridge.GetDateTime(); string autoMineTimer = $"AutoMine-{celestial.ID.ToString()}"; DateTime newTime; + long interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.Brain.AutoMine.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.Brain.AutoMine.CheckIntervalMax); if (stop) { DoLog(LogLevel.Information, $"Stopping AutoMine check for {celestial.ToString()}."); await EndExecution(); @@ -376,7 +604,6 @@ private async Task AutoMineCelestial(Celestial celestial, Buildings maxBuildings celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.Facilities); DoLog(LogLevel.Information, $"Delaying..."); time = await _tbotOgameBridge.GetDateTime(); - long interval; try { interval = _calculationService.CalcProductionTime((Buildables) celestial.Productions.First().ID, celestial.Productions.First().Nbr, _tbotInstance.UserData.serverData, celestial.Facilities) * 1000 + RandomizeHelper.CalcRandomInterval(IntervalType.AFewSeconds); } catch { @@ -389,9 +616,8 @@ private async Task AutoMineCelestial(Celestial celestial, Buildings maxBuildings DoLog(LogLevel.Information, $"Delaying..."); time = await _tbotOgameBridge.GetDateTime(); _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); - long interval; try { - interval = (_tbotInstance.UserData.fleets.OrderBy(f => f.BackIn).First().BackIn ?? 0) * 1000 + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); + interval = (_tbotInstance.UserData.fleets.Where(f => f.Mission == Missions.Transport).OrderBy(f => f.BackIn).First().BackIn ?? 0) * 1000 + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); } catch { interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.Brain.AutoMine.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.Brain.AutoMine.CheckIntervalMax); } @@ -399,7 +625,7 @@ private async Task AutoMineCelestial(Celestial celestial, Buildings maxBuildings ChangeWorkerPeriod(interval); DoLog(LogLevel.Information, $"Next AutoMine check for {celestial.ToString()} at {newTime.ToString()}"); } else if (started) { - long interval = (long) celestial.Constructions.BuildingCountdown * (long) 1000 + (long) RandomizeHelper.CalcRandomInterval(IntervalType.AFewSeconds); + interval = (long) celestial.Constructions.BuildingCountdown * (long) 1000 + (long) RandomizeHelper.CalcRandomInterval(IntervalType.AFewSeconds); newTime = time.AddMilliseconds(interval); ChangeWorkerPeriod(interval); @@ -414,16 +640,27 @@ private async Task AutoMineCelestial(Celestial celestial, Buildings maxBuildings DoLog(LogLevel.Information, $"Next AutoMine check for {celestial.ToString()} at {newTime.ToString()}"); } else { - long interval = await CalcAutoMineTimer(celestial, buildable, level, started, maxBuildings, maxFacilities, maxLunarFacilities, autoMinerSettings); - - if (fleetId != 0 && fleetId != -1 && fleetId != -2) { + interval = await CalcAutoMineTimer(celestial, buildable, level, started, maxBuildings, maxFacilities, maxLunarFacilities, autoMinerSettings); + if (fleetId > 0) { _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); var transportfleet = _tbotInstance.UserData.fleets.Single(f => f.ID == fleetId && f.Mission == Missions.Transport); interval = (transportfleet.ArriveIn * 1000) + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); - } else { interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.Brain.AutoMine.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.Brain.AutoMine.CheckIntervalMax); } + if ((bool) _tbotInstance.InstanceSettings.Brain.Transports.DoMultipleTransportIsNotEnoughShipButSamePosition) { + if (_tbotInstance.UserData.fleets.Where(f => f.Mission == Missions.Transport).Count() > 0) { + var transportfleet2 = _tbotInstance.UserData.fleets.Where(f => f.Mission == Missions.Transport) + .Where(f => f.Destination.IsSame(celestial.Coordinate)) + .Where(f => f.Origin.Galaxy == celestial.Coordinate.Galaxy) + .Where(f => f.Origin.System == celestial.Coordinate.System) + .Where(f => f.Origin.Position == celestial.Coordinate.Position) + .Where(f => f.Origin.Type == (celestial.Coordinate.Type == Celestials.Planet ? Celestials.Moon : Celestials.Planet)) + .ToList(); + if (transportfleet2.Count() > 0) + interval = (long) (transportfleet2.First().BackIn * 1000) + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); + } + } if (interval == long.MaxValue || interval == long.MinValue) interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.Brain.AutoMine.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.Brain.AutoMine.CheckIntervalMax); @@ -467,9 +704,10 @@ private async Task CalcAutoMineTimer(Celestial celestial, Buildables build } else { celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.Buildings); celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.Facilities); + celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.LFBonuses); if (buildable != Buildables.Null) { - var price = _calculationService.CalcPrice(buildable, level); + var price = _calculationService.CalcPrice(buildable, level, celestial.LFBonuses); var productionTime = long.MaxValue; var transportTime = long.MaxValue; var returningExpoTime = long.MaxValue; @@ -525,17 +763,19 @@ private async Task CalcAutoMineTimer(Celestial celestial, Buildables build .Where(c => c.Coordinate.Position == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Position) .Where(c => c.Coordinate.Type == Enum.Parse((string) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Type)) .SingleOrDefault() ?? new() { ID = 0 }; - var returningExpoOrigin = _calculationService.GetFirstReturningExpedition(origin.Coordinate, _tbotInstance.UserData.fleets); - if (returningExpoOrigin != null) { - returningExpoOriginTime = (long) (returningExpoOrigin.BackIn * 1000) + RandomizeHelper.CalcRandomInterval(IntervalType.AMinuteOrTwo); - //DoLog(LogLevel.Debug, $"Next expedition returning in transport origin celestial by {now.AddMilliseconds(returningExpoOriginTime).ToString()}"); - } + if (origin.ID != 0) { + var returningExpoOrigin = _calculationService.GetFirstReturningExpedition(origin.Coordinate, _tbotInstance.UserData.fleets); + if (returningExpoOrigin != null) { + returningExpoOriginTime = (long) (returningExpoOrigin.BackIn * 1000) + RandomizeHelper.CalcRandomInterval(IntervalType.AMinuteOrTwo); + //DoLog(LogLevel.Debug, $"Next expedition returning in transport origin celestial by {now.AddMilliseconds(returningExpoOriginTime).ToString()}"); + } - var incomingOriginFleets = _calculationService.GetIncomingFleetsWithResources(origin, _tbotInstance.UserData.fleets); - if (incomingOriginFleets.Any()) { - var fleet = incomingOriginFleets.First(); - transportOriginTime = ((fleet.Mission == Missions.Transport || fleet.Mission == Missions.Deploy) && !fleet.ReturnFlight ? (long) fleet.ArriveIn : (long) fleet.BackIn) * 1000; - //DoLog(LogLevel.Debug, $"Next fleet with resources arriving in transport origin celestial by {DateTime.Now.AddMilliseconds(transportOriginTime).ToString()}"); + var incomingOriginFleets = _calculationService.GetIncomingFleetsWithResources(origin, _tbotInstance.UserData.fleets); + if (incomingOriginFleets.Any()) { + var fleet = incomingOriginFleets.First(); + transportOriginTime = ((fleet.Mission == Missions.Transport || fleet.Mission == Missions.Deploy) && !fleet.ReturnFlight ? (long) fleet.ArriveIn : (long) fleet.BackIn) * 1000; + //DoLog(LogLevel.Debug, $"Next fleet with resources arriving in transport origin celestial by {DateTime.Now.AddMilliseconds(transportOriginTime).ToString()}"); + } } } diff --git a/TBot/Workers/Brain/AutoMineWorker.cs b/TBot/Workers/Brain/AutoMineWorker.cs index 6e428459..0fe83276 100644 --- a/TBot/Workers/Brain/AutoMineWorker.cs +++ b/TBot/Workers/Brain/AutoMineWorker.cs @@ -110,13 +110,15 @@ protected override async Task Execute() { cel = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.LFBuildings); cel = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.LFBonuses); Planet abaCelestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.Fast) as Planet; - var nextMine = _calculationService.GetNextMineToBuild(cel as Planet, _tbotInstance.UserData.researches, _tbotInstance.UserData.serverData.Speed, int.MaxValue, int.MaxValue, int.MaxValue, 1, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.staff.Geologist, _tbotInstance.UserData.staff.IsFull, true, int.MaxValue); - var lv = _calculationService.GetNextLevel(cel, nextMine); - var DOIR = _calculationService.CalcNextDaysOfInvestmentReturn(cel as Planet, _tbotInstance.UserData.researches, _tbotInstance.UserData.serverData.Speed, 1, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.staff.Geologist, _tbotInstance.UserData.staff.IsFull); - DoLog(LogLevel.Debug, $"Celestial {cel.ToString()}: Next Mine: {nextMine.ToString()} lv {lv.ToString()}; DOIR: {DOIR.ToString()}."); - if (DOIR < _tbotInstance.UserData.nextDOIR || _tbotInstance.UserData.nextDOIR == 0) { - _tbotInstance.UserData.nextDOIR = DOIR; - } + var nextMine = _calculationService.GetNextMineToBuild(cel as Planet, _tbotInstance.UserData.researches, _tbotInstance.UserData.serverData.Speed, maxBuildings.MetalMine, maxBuildings.CrystalMine, maxBuildings.DeuteriumSynthesizer, 1, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.staff.Geologist, _tbotInstance.UserData.staff.IsFull, true, int.MaxValue); + if (nextMine != Buildables.Null) { + var lv = _calculationService.GetNextLevel(cel, nextMine); + var DOIR = _calculationService.CalcNextDaysOfInvestmentReturn(cel as Planet, _tbotInstance.UserData.researches, _tbotInstance.UserData.serverData.Speed, 1, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.staff.Geologist, _tbotInstance.UserData.staff.IsFull); + DoLog(LogLevel.Debug, $"Celestial {cel.ToString()}: Next Mine: {nextMine.ToString()} lv {lv.ToString()}; DOIR: {DOIR.ToString()}."); + if (DOIR < _tbotInstance.UserData.nextDOIR || _tbotInstance.UserData.nextDOIR == 0) { + _tbotInstance.UserData.nextDOIR = DOIR; + } + } if (celestial.Coordinate.Type == Celestials.Planet && celestial.Fields.Built == 0 && (bool) _tbotInstance.InstanceSettings.AutoColonize.Abandon.Active) { if (_calculationService.ShouldAbandon(celestial as Planet, celestial.Fields.Total, abaCelestial.Temperature.Max, fieldsSettings, temperaturesSettings)) { DoLog(LogLevel.Debug, $"Skipping {celestial.ToString()}: planet should be abandoned."); diff --git a/TBot/Workers/Brain/AutoResearchWorker.cs b/TBot/Workers/Brain/AutoResearchWorker.cs index b61f57d4..6476d364 100644 --- a/TBot/Workers/Brain/AutoResearchWorker.cs +++ b/TBot/Workers/Brain/AutoResearchWorker.cs @@ -41,6 +41,7 @@ protected override async Task Execute() { DoLog(LogLevel.Information, "Running autoresearch..."); _tbotInstance.UserData.researches = await _ogameService.GetResearches(); + _tbotInstance.UserData.celestials = await _tbotOgameBridge.UpdateCelestials(); _tbotInstance.UserData.celestials = await _tbotOgameBridge.UpdatePlanets(UpdateTypes.LFBonuses); Planet celestial; var parseSucceded = _tbotInstance.UserData.celestials @@ -86,9 +87,12 @@ protected override async Task Execute() { return; } _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); + celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.Buildings) as Planet; celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.Facilities) as Planet; celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.Resources) as Planet; celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.ResourcesProduction) as Planet; + celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.LFBonuses) as Planet; + celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.Ships) as Planet; Buildables research; @@ -133,10 +137,10 @@ protected override async Task Execute() { ) { research = Buildables.Astrophysics; } else { - research = _calculationService.GetNextResearchToBuild(celestial as Planet, _tbotInstance.UserData.researches, (bool) _tbotInstance.InstanceSettings.Brain.AutoMine.PrioritizeRobotsAndNanites, _tbotInstance.UserData.slots, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxEnergyTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxLaserTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxIonTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxHyperspaceTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxPlasmaTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxCombustionDrive, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxImpulseDrive, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxHyperspaceDrive, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxEspionageTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxComputerTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxAstrophysics, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxIntergalacticResearchNetwork, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxWeaponsTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxShieldingTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxArmourTechnology, (bool) _tbotInstance.InstanceSettings.Brain.AutoResearch.OptimizeForStart, (bool) _tbotInstance.InstanceSettings.Brain.AutoResearch.EnsureExpoSlots, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.staff.Geologist, _tbotInstance.UserData.staff.Admiral); + research = _calculationService.GetNextResearchToBuild(celestial as Planet, _tbotInstance.UserData.researches, (bool) _tbotInstance.InstanceSettings.Brain.AutoMine.PrioritizeRobotsAndNanites, _tbotInstance.UserData.slots, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxEnergyTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxLaserTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxIonTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxHyperspaceTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxPlasmaTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxCombustionDrive, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxImpulseDrive, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxHyperspaceDrive, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxEspionageTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxComputerTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxAstrophysics, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxIntergalacticResearchNetwork, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxWeaponsTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxShieldingTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxArmourTechnology, (bool) _tbotInstance.InstanceSettings.Brain.AutoResearch.OptimizeForStart, (bool) _tbotInstance.InstanceSettings.Brain.AutoResearch.EnsureExpoSlots, (bool) _tbotInstance.InstanceSettings.Brain.AutoResearch.ForceResearchWhateverTheLabLevel, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.staff.Geologist, _tbotInstance.UserData.staff.Admiral); } } else { - research = _calculationService.GetNextResearchToBuild(celestial as Planet, _tbotInstance.UserData.researches, (bool) _tbotInstance.InstanceSettings.Brain.AutoMine.PrioritizeRobotsAndNanites, _tbotInstance.UserData.slots, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxEnergyTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxLaserTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxIonTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxHyperspaceTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxPlasmaTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxCombustionDrive, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxImpulseDrive, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxHyperspaceDrive, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxEspionageTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxComputerTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxAstrophysics, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxIntergalacticResearchNetwork, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxWeaponsTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxShieldingTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxArmourTechnology, (bool) _tbotInstance.InstanceSettings.Brain.AutoResearch.OptimizeForStart, (bool) _tbotInstance.InstanceSettings.Brain.AutoResearch.EnsureExpoSlots, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.staff.Geologist, _tbotInstance.UserData.staff.Admiral); + research = _calculationService.GetNextResearchToBuild(celestial as Planet, _tbotInstance.UserData.researches, (bool) _tbotInstance.InstanceSettings.Brain.AutoMine.PrioritizeRobotsAndNanites, _tbotInstance.UserData.slots, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxEnergyTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxLaserTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxIonTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxHyperspaceTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxPlasmaTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxCombustionDrive, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxImpulseDrive, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxHyperspaceDrive, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxEspionageTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxComputerTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxAstrophysics, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxIntergalacticResearchNetwork, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxWeaponsTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxShieldingTechnology, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.MaxArmourTechnology, (bool) _tbotInstance.InstanceSettings.Brain.AutoResearch.OptimizeForStart, (bool) _tbotInstance.InstanceSettings.Brain.AutoResearch.EnsureExpoSlots, (bool) _tbotInstance.InstanceSettings.Brain.AutoResearch.ForceResearchWhateverTheLabLevel, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.staff.Geologist, _tbotInstance.UserData.staff.Admiral); } if ( @@ -160,7 +164,9 @@ protected override async Task Execute() { int level = _calculationService.GetNextLevel(_tbotInstance.UserData.researches, research); if (research != Buildables.Null) { celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.Resources) as Planet; - Resources cost = _calculationService.CalcPrice(research, level); + celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.Ships) as Planet; + celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.LFBonuses) as Planet; + Resources cost = _calculationService.CalcPrice(research, level, celestial.LFBonuses); if (celestial.Resources.IsEnoughFor(cost)) { try { await _ogameService.BuildCancelable(celestial, research); @@ -171,35 +177,248 @@ protected override async Task Execute() { } else { DoLog(LogLevel.Information, $"Not enough resources to build: {research.ToString()} level {level.ToString()} on {celestial.ToString()}. Needed: {cost.TransportableResources} - Available: {celestial.Resources.TransportableResources}"); if ((bool) _tbotInstance.InstanceSettings.Brain.AutoResearch.Transports.Active && (bool) _tbotInstance.InstanceSettings.Brain.Transports.Active) { + _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); - if (!_calculationService.IsThereTransportTowardsCelestial(celestial, _tbotInstance.UserData.fleets)) { - Celestial origin = _tbotInstance.UserData.celestials - .Unique() - .Where(c => c.Coordinate.Galaxy == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Galaxy) - .Where(c => c.Coordinate.System == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.System) - .Where(c => c.Coordinate.Position == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Position) - .Where(c => c.Coordinate.Type == Enum.Parse((string) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Type)) - .SingleOrDefault() ?? new() { ID = 0 }; - fleetId = await _fleetScheduler.HandleMinerTransport(origin, celestial, cost, Buildables.Null); - if (fleetId == (int) SendFleetCode.AfterSleepTime) { - stop = true; - } - if (fleetId == (int) SendFleetCode.NotEnoughSlots) { - delay = true; + List rankSlotsPriority = new() { + new RankSlotsPriority(Feature.BrainAutoResearch, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Brain, + ((bool) _tbotInstance.InstanceSettings.Brain.Active && (bool) _tbotInstance.InstanceSettings.Brain.Transports.Active && ((bool) _tbotInstance.InstanceSettings.Brain.AutoMine.Active || (bool) _tbotInstance.InstanceSettings.Brain.AutoResearch.Active || (bool) _tbotInstance.InstanceSettings.Brain.LifeformAutoMine.Active || (bool) _tbotInstance.InstanceSettings.Brain.LifeformAutoResearch.Active)), + (int) _tbotInstance.InstanceSettings.Brain.Transports.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Transport)), + new RankSlotsPriority(Feature.Expeditions, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Expeditions, + (bool) _tbotInstance.InstanceSettings.Expeditions.Active, + (int) _tbotInstance.UserData.slots.ExpTotal, + (int)_tbotInstance.UserData.slots.ExpInUse), + new RankSlotsPriority(Feature.AutoFarm, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoFarm, + (bool) _tbotInstance.InstanceSettings.AutoFarm.Active, + (int) _tbotInstance.InstanceSettings.AutoFarm.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Attack)), + new RankSlotsPriority(Feature.Colonize, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Colonize, + (bool) _tbotInstance.InstanceSettings.AutoColonize.Active, + (bool) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.Active ? + (int) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.MaxSlots : + 1, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Colonize)), + new RankSlotsPriority(Feature.AutoDiscovery, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoDiscovery, + (bool) _tbotInstance.InstanceSettings.AutoDiscovery.Active, + (int) _tbotInstance.InstanceSettings.AutoDiscovery.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Discovery)), + new RankSlotsPriority(Feature.Harvest, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoHarvest, + (bool) _tbotInstance.InstanceSettings.AutoHarvest.Active, + (int) _tbotInstance.InstanceSettings.AutoHarvest.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Harvest)) + }; + int MaxSlots = _calculationService.CalcSlotsPriority(Feature.BrainAutoResearch, rankSlotsPriority, _tbotInstance.UserData.slots, _tbotInstance.UserData.fleets, (int) _tbotInstance.InstanceSettings.General.SlotsToLeaveFree); + + if (MaxSlots > 0) { + if (!_calculationService.IsThereTransportTowardsCelestial(celestial, _tbotInstance.UserData.fleets)) { + Celestial origin = new() { ID = 0 }; + List allCelestials = _tbotInstance.UserData.celestials; + for (int i = 0; i < allCelestials.Count(); i++) { + allCelestials[i] = await _tbotOgameBridge.UpdatePlanet(allCelestials[i], UpdateTypes.Resources); + allCelestials[i] = await _tbotOgameBridge.UpdatePlanet(allCelestials[i], UpdateTypes.Ships); + allCelestials[i] = await _tbotOgameBridge.UpdatePlanet(allCelestials[i], UpdateTypes.LFBonuses); + } + Resources missingResources = cost.Difference(celestial.Resources); + if ((bool) _tbotInstance.InstanceSettings.Brain.Transports.CheckMoonOrPlanetFirst) { + if (celestial.Coordinate.Type == Celestials.Planet && ((bool) _tbotInstance.InstanceSettings.Brain.Transports.CheckMoonOrPlanetFirst && _calculationService.IsThereMoonHere(allCelestials, celestial))) { + origin = allCelestials.Unique() + .Where(c => c.Coordinate.Galaxy == celestial.Coordinate.Galaxy) + .Where(c => c.Coordinate.System == celestial.Coordinate.System) + .Where(c => c.Coordinate.Position == celestial.Coordinate.Position) + .Where(c => c.Coordinate.Type == Celestials.Moon) + .SingleOrDefault() ?? new() { ID = 0 }; + } + if (celestial.Coordinate.Type == Celestials.Moon) { + origin = allCelestials.Unique() + .Where(c => c.Coordinate.Galaxy == celestial.Coordinate.Galaxy) + .Where(c => c.Coordinate.System == celestial.Coordinate.System) + .Where(c => c.Coordinate.Position == celestial.Coordinate.Position) + .Where(c => c.Coordinate.Type == Celestials.Planet) + .SingleOrDefault() ?? new() { ID = 0 }; + } + if (origin.ID != 0) { + if (origin.Resources.IsEnoughFor(missingResources)) { + missingResources = _tbotInstance.InstanceSettings.Brain.Transports.RoundResources ? missingResources.Round() : missingResources; + Buildables preferredShip = Buildables.SmallCargo; + if (!Enum.TryParse((string) _tbotInstance.InstanceSettings.Brain.Transports.CargoType, true, out preferredShip)) { + _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, "Unable to parse CargoType. Falling back to default SmallCargo"); + preferredShip = Buildables.SmallCargo; + } + long shipsNeeded = _calculationService.CalcShipNumberForPayload(missingResources, preferredShip, _tbotInstance.UserData.researches.HyperspaceTechnology, _tbotInstance.UserData.serverData, origin.LFBonuses.GetShipCargoBonus(preferredShip), _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.serverData.ProbeCargo); + if (origin.Ships.GetAmount(preferredShip) >= shipsNeeded) { + Ships ships = new(); + ships.Add((Buildables) preferredShip, shipsNeeded); + fleetId = await _fleetScheduler.SendFleet(origin, ships, celestial.Coordinate, Missions.Transport, Speeds.HundredPercent, missingResources); + if (fleetId == (int) SendFleetCode.AfterSleepTime) { + stop = true; + } + if (fleetId == (int) SendFleetCode.NotEnoughSlots) { + delay = true; + } + } else if (_tbotInstance.InstanceSettings.Brain.Transports.DoMultipleTransportIsNotEnoughShipButSamePosition) { + DoLog(LogLevel.Warning, $"Not enough Cargo available. Multiple transports will take place."); + Ships ships = new(); + ships.Add((Buildables) preferredShip, origin.Ships.GetAmount(preferredShip)); + Resources transportableResources = _calculationService.CalcMaxTransportableResources(ships, missingResources, _tbotInstance.UserData.researches.HyperspaceTechnology, _tbotInstance.UserData.serverData, origin.LFBonuses, _tbotInstance.UserData.userInfo.Class, 0, _tbotInstance.UserData.serverData.ProbeCargo); + fleetId = await _fleetScheduler.SendFleet(origin, ships, celestial.Coordinate, Missions.Transport, Speeds.HundredPercent, transportableResources); + if (fleetId == (int) SendFleetCode.AfterSleepTime) { + stop = true; + } + if (fleetId == (int) SendFleetCode.NotEnoughSlots) { + delay = true; + } + } else { + DoLog(LogLevel.Warning, $"Not enough Cargo available. Skipping CheckMoonOrPlanetFirst."); + } + } else { + DoLog(LogLevel.Warning, $"Not enough resources available on {origin.ToString()} only to send resources to {celestial.ToString()}."); + } + } else { + DoLog(LogLevel.Warning, $"No Moon available on {celestial.ToString()}."); + } + } + + if (fleetId <= 0) { + if ((bool) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.Active) { + origin = _tbotInstance.UserData.celestials + .Unique() + .Where(c => c.Coordinate.Galaxy == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Galaxy) + .Where(c => c.Coordinate.System == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.System) + .Where(c => c.Coordinate.Position == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Position) + .Where(c => c.Coordinate.Type == Enum.Parse((string) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Type)) + .SingleOrDefault() ?? new() { ID = 0 }; + List celestialsToExclude = _calculationService.ParseCelestialsList(_tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.Exclude, allCelestials); + Buildables preferredShip = Buildables.SmallCargo; + if (!Enum.TryParse((string) _tbotInstance.InstanceSettings.Brain.Transports.CargoType, true, out preferredShip)) { + _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, "Unable to parse CargoType. Falling back to default SmallCargo"); + preferredShip = Buildables.SmallCargo; + } + TransportSettings transportsSettings = new((bool) _tbotInstance.InstanceSettings.Brain.Transports.Active, + preferredShip, + (long) _tbotInstance.InstanceSettings.Brain.Transports.DeutToLeaveOnMoons, + (bool)_tbotInstance.InstanceSettings.Brain.Transports.RoundResources, + (bool)_tbotInstance.InstanceSettings.Brain.Transports.SendToTheMoonIfPossible, + origin, + (long) _tbotInstance.InstanceSettings.Brain.Transports.MaxSlots, + (bool)_tbotInstance.InstanceSettings.Brain.Transports.CheckMoonOrPlanetFirst, + (bool)_tbotInstance.InstanceSettings.Brain.Transports.DoMultipleTransportIsNotEnoughShipButSamePosition, + new MultipleOrigins((bool) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.Active, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.OnlyFromMoons, + (long) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.MinimumResourcesToSend, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.PriorityToProximityOverQuantity, + celestialsToExclude) + ); + + Celestial destination; + if ((bool) transportsSettings.SendToTheMoonIfPossible && _calculationService.IsThereMoonHere(allCelestials, celestial)) { + destination = allCelestials + .Unique() + .Where(planet => planet.Coordinate.Galaxy == celestial.Coordinate.Galaxy) + .Where(planet => planet.Coordinate.System == celestial.Coordinate.System) + .Where(planet => planet.Coordinate.Position == celestial.Coordinate.Position) + .Where(planet => planet.Coordinate.Type == Celestials.Moon) + .SingleOrDefault() ?? new() { ID = 0 }; + if (destination.Ships.IsEmpty() || celestial.Resources.TotalResources == 0) + destination = celestial; + } else { + destination = celestial; + } + + var resultOrigins = _calculationService.CalcMultipleOrigin(celestial, allCelestials, missingResources, transportsSettings, _tbotInstance.UserData.fleets, _tbotInstance.UserData); + + if (resultOrigins.Count() == 0) { + DoLog(LogLevel.Information, $"No origin is available. This may be due to a lack of resources or cargo."); + return; + } + if (resultOrigins.Count() > MaxSlots) { + DoLog(LogLevel.Information, $"Not enough slots available to send all resources to build: {research.ToString()} level {level.ToString()} on {celestial.ToString()}. Slots needed: {resultOrigins.Count().ToString()}/{MaxSlots}."); + delay = true; + return; + } + + Ships ships = new(); + + foreach (var item in resultOrigins) { + ships = new(); + ships.Add((Buildables) transportsSettings.CargoType, _calculationService.CalcShipNumberForPayload(item.FirstOrDefault().Value, (Buildables) transportsSettings.CargoType, _tbotInstance.UserData.researches.HyperspaceTechnology, _tbotInstance.UserData.serverData, celestial.LFBonuses.GetShipCargoBonus(transportsSettings.CargoType), _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.serverData.ProbeCargo)); + if (item.FirstOrDefault().Key.Coordinate.IsSame(destination.Coordinate) && transportsSettings.SendToTheMoonIfPossible && destination.Coordinate.Type == Celestials.Moon) + fleetId= await _fleetScheduler.SendFleet(item.FirstOrDefault().Key, ships, celestial.Coordinate, Missions.Transport, Speeds.HundredPercent, item.FirstOrDefault().Value); + else + fleetId= await _fleetScheduler.SendFleet(item.FirstOrDefault().Key, ships, destination.Coordinate, Missions.Transport, Speeds.HundredPercent, item.FirstOrDefault().Value); + + if (fleetId == (int) SendFleetCode.AfterSleepTime) { + stop = true; + return; + } + if (fleetId == (int) SendFleetCode.NotEnoughSlots) { + delay = true; + return; + } + } + } else { + Celestial destination; + if ((bool) _tbotInstance.InstanceSettings.Brain.Transports.SendToTheMoonIfPossible && celestial.Coordinate.Type == Celestials.Planet && _calculationService.IsThereMoonHere(_tbotInstance.UserData.celestials, celestial) && (!celestial.Ships.IsEmpty() || celestial.Resources.TotalResources > 0)) { + destination = allCelestials + .Unique() + .Where(planet => planet.Coordinate.Galaxy == celestial.Coordinate.Galaxy) + .Where(planet => planet.Coordinate.System == celestial.Coordinate.System) + .Where(planet => planet.Coordinate.Position == celestial.Coordinate.Position) + .Where(planet => planet.Coordinate.Type == Celestials.Moon) + .SingleOrDefault() ?? new() { ID = 0 }; + if (destination.Ships.IsEmpty() || celestial.Resources.TotalResources == 0) + destination = celestial; + cost = cost.Difference(destination.Resources); + } else { + destination = allCelestials + .Unique() + .Where(planet => planet.Coordinate.Galaxy == celestial.Coordinate.Galaxy) + .Where(planet => planet.Coordinate.System == celestial.Coordinate.System) + .Where(planet => planet.Coordinate.Position == celestial.Coordinate.Position) + .Where(planet => planet.Coordinate.Type == celestial.Coordinate.Type) + .SingleOrDefault() ?? new() { ID = 0 }; + } + origin = allCelestials + .Unique() + .Where(c => c.Coordinate.Galaxy == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Galaxy) + .Where(c => c.Coordinate.System == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.System) + .Where(c => c.Coordinate.Position == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Position) + .Where(c => c.Coordinate.Type == Enum.Parse((string) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Type)) + .SingleOrDefault() ?? new() { ID = 0 }; + fleetId = await _fleetScheduler.HandleMinerTransport(origin, celestial, destination, cost, Buildables.Null); + if (fleetId == (int) SendFleetCode.AfterSleepTime) { + stop = true; + } + if (fleetId == (int) SendFleetCode.NotEnoughSlots) { + delay = true; + } + } + } + } else { + DoLog(LogLevel.Information, $"Skipping transport: there is already a transport incoming in {celestial.ToString()}"); + fleetId = _tbotInstance.UserData.fleets.Where(f => f.Mission == Missions.Transport) + .Where(f => f.Resources.TotalResources > 0) + .Where(f => f.ReturnFlight == false) + .Where(f => f.Destination.Galaxy == celestial.Coordinate.Galaxy) + .Where(f => f.Destination.System == celestial.Coordinate.System) + .Where(f => f.Destination.Position == celestial.Coordinate.Position) + .Where(f => f.Destination.Type == celestial.Coordinate.Type) + .First().ID; } } else { - DoLog(LogLevel.Information, $"Skipping transport: there is already a transport incoming in {celestial.ToString()}"); - fleetId = (_tbotInstance.UserData.fleets - .Where(f => f.Mission == Missions.Transport) - .Where(f => f.Resources.TotalResources > 0) - .Where(f => f.ReturnFlight == false) - .Where(f => f.Destination.Galaxy == celestial.Coordinate.Galaxy) - .Where(f => f.Destination.System == celestial.Coordinate.System) - .Where(f => f.Destination.Position == celestial.Coordinate.Position) - .Where(f => f.Destination.Type == celestial.Coordinate.Type) - .OrderByDescending(f => f.ArriveIn) - .FirstOrDefault() ?? new() { ID = 0 }) - .ID; + if (_tbotInstance.InstanceSettings.General.SlotPriorityLevel.Brain > 0) { + if (_tbotInstance.InstanceSettings.Brain.Transports.MaxSlots == 0) + DoLog(LogLevel.Information, $"Transports.MaxSlots is set to 0, you should increase it."); + else + DoLog(LogLevel.Information, $"0 slots available."); + } + DoLog(LogLevel.Information, $"Not enough slots available for Transports.MaxSlots, delaying."); + delay = true; } } } @@ -218,7 +437,7 @@ protected override async Task Execute() { _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); long interval; try { - interval = (_tbotInstance.UserData.fleets.OrderBy(f => f.BackIn).First().BackIn ?? 0) * 1000 + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); + interval = (_tbotInstance.UserData.fleets.Where(f => f.Mission == Missions.Transport).OrderBy(f => f.BackIn).First().BackIn ?? 0) * 1000 + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); } catch { interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.AutoResearch.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.AutoResearch.CheckIntervalMax); } @@ -261,7 +480,25 @@ protected override async Task Execute() { .First(); interval = (((fleet.Mission == Missions.Transport || fleet.Mission == Missions.Deploy) ? (long) fleet.ArriveIn : (long) fleet.BackIn) * 1000) + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); } else { - interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.Brain.AutoResearch.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.CheckIntervalMax); + if (fleetId > 0) { + _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); + var transportfleet = _tbotInstance.UserData.fleets.Single(f => f.ID == fleetId && f.Mission == Missions.Transport); + interval = (transportfleet.ArriveIn * 1000) + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); + } else { + interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.Brain.AutoResearch.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.Brain.AutoResearch.CheckIntervalMax); + } + if ((bool) _tbotInstance.InstanceSettings.Brain.Transports.DoMultipleTransportIsNotEnoughShipButSamePosition) { + var transportfleet2 = _tbotInstance.UserData.fleets.Where(f => f.Mission == Missions.Transport) + .Where(f => f.Destination.IsSame(celestial.Coordinate)) + .Where(f => f.Origin.Galaxy == celestial.Coordinate.Galaxy) + .Where(f => f.Origin.System == celestial.Coordinate.System) + .Where(f => f.Origin.Position == celestial.Coordinate.Position) + .Where(f => f.Origin.Type == (celestial.Coordinate.Type == Celestials.Planet ? Celestials.Moon : Celestials.Planet)) + .ToList(); + if (transportfleet2.Count() > 0) { + interval = (long) (transportfleet2.First().BackIn * 1000) + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); + } + } } } if (interval <= 0) diff --git a/TBot/Workers/Brain/BuyOfferOfTheDayWorker.cs b/TBot/Workers/Brain/BuyOfferOfTheDayWorker.cs index 7b7255e5..16f7abb5 100644 --- a/TBot/Workers/Brain/BuyOfferOfTheDayWorker.cs +++ b/TBot/Workers/Brain/BuyOfferOfTheDayWorker.cs @@ -25,8 +25,6 @@ public BuyOfferOfTheDayWorker(ITBotMain parentInstance, _tbotOgameBridge = tbotOgameBridge; } protected override async Task Execute() { - bool stop = true; - _tbotInstance.log(LogLevel.Information, GetLogSender(), "Buying offer of the day..."); OfferOfTheDayStatus sts = await _ogameService.BuyOfferOfTheDay(); @@ -36,23 +34,16 @@ protected override async Task Execute() { _tbotInstance.log(LogLevel.Information, GetLogSender(), "Offer of the day already bought."); } else { _tbotInstance.log(LogLevel.Information, GetLogSender(), "Error buying Offer of the day. Already bought?"); - stop = false; } - - if (stop) { - _tbotInstance.log(LogLevel.Information, GetLogSender(), $"Stopping BuyOfferOfTheDay."); - await EndExecution(); - } else { - var time = await _tbotOgameBridge.GetDateTime(); - var interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.Brain.BuyOfferOfTheDay.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.Brain.BuyOfferOfTheDay.CheckIntervalMax); - if (interval <= 0) - interval = RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); - var newTime = time.AddMilliseconds(interval); - ChangeWorkerPeriod(interval); - _tbotInstance.log(LogLevel.Information, GetLogSender(), $"Next BuyOfferOfTheDay check at {newTime.ToString()}"); - await _tbotOgameBridge.CheckCelestials(); - } + var time = await _tbotOgameBridge.GetDateTime(); + var interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.Brain.BuyOfferOfTheDay.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.Brain.BuyOfferOfTheDay.CheckIntervalMax); + if (interval <= 0) + interval = RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); + var newTime = time.AddMilliseconds(interval); + ChangeWorkerPeriod(interval); + _tbotInstance.log(LogLevel.Information, GetLogSender(), $"Next BuyOfferOfTheDay check at {newTime.ToString()}"); + await _tbotOgameBridge.CheckCelestials(); } public override bool IsWorkerEnabledBySettings() { try { diff --git a/TBot/Workers/Brain/LifeformsAutoMineCelestialWorker.cs b/TBot/Workers/Brain/LifeformsAutoMineCelestialWorker.cs index 63e285f3..cc68cff9 100644 --- a/TBot/Workers/Brain/LifeformsAutoMineCelestialWorker.cs +++ b/TBot/Workers/Brain/LifeformsAutoMineCelestialWorker.cs @@ -101,6 +101,8 @@ private async Task LifeformAutoMineCelestial(Celestial celestial) { celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.LFBuildings); celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.Buildings); celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.Constructions); + celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.LFBonuses); + celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.Ships); bool preventTechBuilding = false; if (celestial.Constructions.LFResearchCountdown > 0) { @@ -180,37 +182,240 @@ private async Task LifeformAutoMineCelestial(Celestial celestial) { DoLog(LogLevel.Information, $"Not enough resources to build: {buildable.ToString()} level {level.ToString()} on {celestial.ToString()}. Needed: {xCostBuildable.LFBuildingCostResources.ToString()} - Available: {celestial.Resources.LFBuildingCostResources.ToString()}"); if ((bool) _tbotInstance.InstanceSettings.Brain.LifeformAutoMine.Transports.Active && (bool) _tbotInstance.InstanceSettings.Brain.Transports.Active) { + _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); - if (!_calculationService.IsThereTransportTowardsCelestial(celestial, _tbotInstance.UserData.fleets)) { - Celestial origin = _tbotInstance.UserData.celestials - .Unique() - .Where(c => c.Coordinate.Galaxy == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Galaxy) - .Where(c => c.Coordinate.System == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.System) - .Where(c => c.Coordinate.Position == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Position) - .Where(c => c.Coordinate.Type == Enum.Parse((string) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Type)) - .SingleOrDefault() ?? new() { ID = 0 }; - fleetId = await _fleetScheduler.HandleMinerTransport(origin, celestial, xCostBuildable, buildable, maxLFBuildings, preventIfMoreExpensiveThanNextMine); - if (fleetId == (int) SendFleetCode.AfterSleepTime) { - stop = true; - return; - } - if (fleetId == (int) SendFleetCode.NotEnoughSlots) { - delay = true; - return; + List rankSlotsPriority = new() { + new RankSlotsPriority(Feature.BrainLifeformAutoMine, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Brain, + ((bool) _tbotInstance.InstanceSettings.Brain.Active && (bool) _tbotInstance.InstanceSettings.Brain.Transports.Active && ((bool) _tbotInstance.InstanceSettings.Brain.AutoMine.Active || (bool) _tbotInstance.InstanceSettings.Brain.AutoResearch.Active || (bool) _tbotInstance.InstanceSettings.Brain.LifeformAutoMine.Active || (bool) _tbotInstance.InstanceSettings.Brain.LifeformAutoResearch.Active)), + (int) _tbotInstance.InstanceSettings.Brain.Transports.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Transport)), + new RankSlotsPriority(Feature.Expeditions, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Expeditions, + (bool) _tbotInstance.InstanceSettings.Expeditions.Active, + (int) _tbotInstance.UserData.slots.ExpTotal, + (int)_tbotInstance.UserData.slots.ExpInUse), + new RankSlotsPriority(Feature.AutoFarm, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoFarm, + (bool) _tbotInstance.InstanceSettings.AutoFarm.Active, + (int) _tbotInstance.InstanceSettings.AutoFarm.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Attack)), + new RankSlotsPriority(Feature.Colonize, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Colonize, + (bool) _tbotInstance.InstanceSettings.AutoColonize.Active, + (bool) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.Active ? + (int) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.MaxSlots : + 1, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Colonize)), + new RankSlotsPriority(Feature.AutoDiscovery, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoDiscovery, + (bool) _tbotInstance.InstanceSettings.AutoDiscovery.Active, + (int) _tbotInstance.InstanceSettings.AutoDiscovery.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Discovery)), + new RankSlotsPriority(Feature.Harvest, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoHarvest, + (bool) _tbotInstance.InstanceSettings.AutoHarvest.Active, + (int) _tbotInstance.InstanceSettings.AutoHarvest.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Harvest)) + }; + int MaxSlots = _calculationService.CalcSlotsPriority(Feature.BrainLifeformAutoMine, rankSlotsPriority, _tbotInstance.UserData.slots, _tbotInstance.UserData.fleets, (int) _tbotInstance.InstanceSettings.General.SlotsToLeaveFree); + + if (MaxSlots > 0) { + if (!_calculationService.IsThereTransportTowardsCelestial(celestial, _tbotInstance.UserData.fleets)) { + Celestial origin = new() { ID = 0 }; + List allCelestials = _tbotInstance.UserData.celestials; + for (int i = 0; i < allCelestials.Count(); i++) { + allCelestials[i] = await _tbotOgameBridge.UpdatePlanet(allCelestials[i], UpdateTypes.Resources); + allCelestials[i] = await _tbotOgameBridge.UpdatePlanet(allCelestials[i], UpdateTypes.Ships); + allCelestials[i] = await _tbotOgameBridge.UpdatePlanet(allCelestials[i], UpdateTypes.LFBonuses); + } + Resources missingResources = xCostBuildable.Difference(celestial.Resources); + if ((bool) _tbotInstance.InstanceSettings.Brain.Transports.CheckMoonOrPlanetFirst) { + if ((bool) _tbotInstance.InstanceSettings.Brain.Transports.CheckMoonOrPlanetFirst && _calculationService.IsThereMoonHere(allCelestials, celestial)) { + origin = allCelestials.Unique() + .Where(c => c.Coordinate.Galaxy == celestial.Coordinate.Galaxy) + .Where(c => c.Coordinate.System == celestial.Coordinate.System) + .Where(c => c.Coordinate.Position == celestial.Coordinate.Position) + .Where(c => c.Coordinate.Type == Celestials.Moon) + .SingleOrDefault() ?? new() { ID = 0 }; + } + if (origin.ID != 0) { + if (origin.Resources.IsEnoughFor(missingResources)) { + missingResources = _tbotInstance.InstanceSettings.Brain.Transports.RoundResources ? missingResources.Round() : missingResources; + Buildables preferredShip = Buildables.SmallCargo; + if (!Enum.TryParse((string) _tbotInstance.InstanceSettings.Brain.Transports.CargoType, true, out preferredShip)) { + _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, "Unable to parse CargoType. Falling back to default SmallCargo"); + preferredShip = Buildables.SmallCargo; + } + long shipsNeeded = _calculationService.CalcShipNumberForPayload(missingResources, preferredShip, _tbotInstance.UserData.researches.HyperspaceTechnology, _tbotInstance.UserData.serverData, origin.LFBonuses.GetShipCargoBonus(preferredShip), _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.serverData.ProbeCargo); + if (origin.Ships.GetAmount(preferredShip) >= shipsNeeded) { + Ships ships = new(); + ships.Add((Buildables) preferredShip, shipsNeeded); + fleetId = await _fleetScheduler.SendFleet(origin, ships, celestial.Coordinate, Missions.Transport, Speeds.HundredPercent, missingResources); + if (fleetId == (int) SendFleetCode.AfterSleepTime) { + stop = true; + } + if (fleetId == (int) SendFleetCode.NotEnoughSlots) { + delay = true; + } + } else if (_tbotInstance.InstanceSettings.Brain.Transports.DoMultipleTransportIsNotEnoughShipButSamePosition) { + DoLog(LogLevel.Warning, $"Not enough Cargo available. Multiple transports will take place."); + Ships ships = new(); + ships.Add((Buildables) preferredShip, origin.Ships.GetAmount(preferredShip)); + Resources transportableResources = _calculationService.CalcMaxTransportableResources(ships, missingResources, _tbotInstance.UserData.researches.HyperspaceTechnology, _tbotInstance.UserData.serverData, origin.LFBonuses, _tbotInstance.UserData.userInfo.Class, 0, _tbotInstance.UserData.serverData.ProbeCargo); + fleetId = await _fleetScheduler.SendFleet(origin, ships, celestial.Coordinate, Missions.Transport, Speeds.HundredPercent, transportableResources); + if (fleetId == (int) SendFleetCode.AfterSleepTime) { + stop = true; + } + if (fleetId == (int) SendFleetCode.NotEnoughSlots) { + delay = true; + } + } else { + DoLog(LogLevel.Warning, $"Not enough Cargo available. Skipping CheckMoonOrPlanetFirst."); + } + } else { + DoLog(LogLevel.Warning, $"Not enough resources available on {origin.ToString()} only to send resources to {celestial.ToString()}."); + } + } else { + DoLog(LogLevel.Warning, $"No Moon available on {celestial.ToString()}."); + } + } + + if (fleetId <= 0) { + if ((bool) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.Active) { + origin = _tbotInstance.UserData.celestials + .Unique() + .Where(c => c.Coordinate.Galaxy == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Galaxy) + .Where(c => c.Coordinate.System == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.System) + .Where(c => c.Coordinate.Position == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Position) + .Where(c => c.Coordinate.Type == Enum.Parse((string) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Type)) + .SingleOrDefault() ?? new() { ID = 0 }; + List celestialsToExclude = _calculationService.ParseCelestialsList(_tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.Exclude, allCelestials); + Buildables preferredShip = Buildables.SmallCargo; + if (!Enum.TryParse((string) _tbotInstance.InstanceSettings.Brain.Transports.CargoType, true, out preferredShip)) { + _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, "Unable to parse CargoType. Falling back to default SmallCargo"); + preferredShip = Buildables.SmallCargo; + } + TransportSettings transportsSettings = new((bool) _tbotInstance.InstanceSettings.Brain.Transports.Active, + preferredShip, + (long) _tbotInstance.InstanceSettings.Brain.Transports.DeutToLeaveOnMoons, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.RoundResources, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.SendToTheMoonIfPossible, + origin, + (long) _tbotInstance.InstanceSettings.Brain.Transports.MaxSlots, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.CheckMoonOrPlanetFirst, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.DoMultipleTransportIsNotEnoughShipButSamePosition, + new MultipleOrigins((bool) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.Active, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.OnlyFromMoons, + (long) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.MinimumResourcesToSend, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.PriorityToProximityOverQuantity, + celestialsToExclude) + ); + + Celestial destination; + if ((bool) transportsSettings.SendToTheMoonIfPossible && _calculationService.IsThereMoonHere(allCelestials, celestial)) { + destination = allCelestials + .Unique() + .Where(planet => planet.Coordinate.Galaxy == celestial.Coordinate.Galaxy) + .Where(planet => planet.Coordinate.System == celestial.Coordinate.System) + .Where(planet => planet.Coordinate.Position == celestial.Coordinate.Position) + .Where(planet => planet.Coordinate.Type == Celestials.Moon) + .SingleOrDefault() ?? new() { ID = 0 }; + if (destination.Ships.IsEmpty() || celestial.Resources.TotalResources == 0) + destination = celestial; + } else { + destination = celestial; + } + + var resultOrigins = _calculationService.CalcMultipleOrigin(celestial, allCelestials, missingResources, transportsSettings, _tbotInstance.UserData.fleets, _tbotInstance.UserData); + + if (resultOrigins.Count() == 0) { + DoLog(LogLevel.Information, $"No origin is available. This may be due to a lack of resources or cargo."); + return; + } + if (resultOrigins.Count() > MaxSlots) { + DoLog(LogLevel.Information, $"Not enough slots available to send all resources to build: {buildable.ToString()} level {level.ToString()} on {celestial.ToString()}. Slots needed: {resultOrigins.Count().ToString()}/{MaxSlots}."); + delay = true; + return; + } + + Ships ships = new(); + + foreach (var item in resultOrigins) { + ships = new(); + ships.Add((Buildables) transportsSettings.CargoType, _calculationService.CalcShipNumberForPayload(item.FirstOrDefault().Value, (Buildables) transportsSettings.CargoType, _tbotInstance.UserData.researches.HyperspaceTechnology, _tbotInstance.UserData.serverData, celestial.LFBonuses.GetShipCargoBonus(transportsSettings.CargoType), _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.serverData.ProbeCargo)); + if (item.FirstOrDefault().Key.Coordinate.IsSame(destination.Coordinate) && transportsSettings.SendToTheMoonIfPossible && destination.Coordinate.Type == Celestials.Moon) + fleetId= await _fleetScheduler.SendFleet(item.FirstOrDefault().Key, ships, celestial.Coordinate, Missions.Transport, Speeds.HundredPercent, item.FirstOrDefault().Value); + else + fleetId= await _fleetScheduler.SendFleet(item.FirstOrDefault().Key, ships, destination.Coordinate, Missions.Transport, Speeds.HundredPercent, item.FirstOrDefault().Value); + + if (fleetId == (int) SendFleetCode.AfterSleepTime) { + stop = true; + return; + } + if (fleetId == (int) SendFleetCode.NotEnoughSlots) { + delay = true; + return; + } + } + } else { + Celestial destination; + if ((bool) _tbotInstance.InstanceSettings.Brain.Transports.SendToTheMoonIfPossible && _calculationService.IsThereMoonHere(_tbotInstance.UserData.celestials, celestial)) { + destination = allCelestials + .Unique() + .Where(planet => planet.Coordinate.Galaxy == celestial.Coordinate.Galaxy) + .Where(planet => planet.Coordinate.System == celestial.Coordinate.System) + .Where(planet => planet.Coordinate.Position == celestial.Coordinate.Position) + .Where(planet => planet.Coordinate.Type == Celestials.Moon) + .SingleOrDefault() ?? new() { ID = 0 }; + if (destination.Ships.IsEmpty() || celestial.Resources.TotalResources == 0) + destination = celestial; + xCostBuildable = xCostBuildable.Difference(destination.Resources); + } else { + destination = allCelestials + .Unique() + .Where(planet => planet.Coordinate.Galaxy == celestial.Coordinate.Galaxy) + .Where(planet => planet.Coordinate.System == celestial.Coordinate.System) + .Where(planet => planet.Coordinate.Position == celestial.Coordinate.Position) + .Where(planet => planet.Coordinate.Type == celestial.Coordinate.Type) + .SingleOrDefault() ?? new() { ID = 0 }; + } + origin = allCelestials + .Unique() + .Where(c => c.Coordinate.Galaxy == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Galaxy) + .Where(c => c.Coordinate.System == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.System) + .Where(c => c.Coordinate.Position == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Position) + .Where(c => c.Coordinate.Type == Enum.Parse((string) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Type)) + .SingleOrDefault() ?? new() { ID = 0 }; + fleetId = await _fleetScheduler.HandleMinerTransport(origin, celestial, destination, xCostBuildable, buildable, maxLFBuildings, preventIfMoreExpensiveThanNextMine); + if (fleetId == (int) SendFleetCode.AfterSleepTime) { + stop = true; + } + if (fleetId == (int) SendFleetCode.NotEnoughSlots) { + delay = true; + } + } + } + } else { + DoLog(LogLevel.Information, $"Skipping transport: there is already a transport incoming in {celestial.ToString()}"); + fleetId = _tbotInstance.UserData.fleets.Where(f => f.Mission == Missions.Transport) + .Where(f => f.Resources.TotalResources > 0) + .Where(f => f.ReturnFlight == false) + .Where(f => f.Destination.Galaxy == celestial.Coordinate.Galaxy) + .Where(f => f.Destination.System == celestial.Coordinate.System) + .Where(f => f.Destination.Position == celestial.Coordinate.Position) + .Where(f => f.Destination.Type == celestial.Coordinate.Type) + .First().ID; } } else { - DoLog(LogLevel.Information, $"Skipping transport: there is already a transport incoming in {celestial.ToString()}"); - try { - fleetId = _tbotInstance.UserData.fleets.Where(f => f.Mission == Missions.Transport) - .Where(f => f.Resources.TotalResources > 0) - .Where(f => f.ReturnFlight == false) - .Where(f => f.Destination.Galaxy == celestial.Coordinate.Galaxy) - .Where(f => f.Destination.System == celestial.Coordinate.System) - .Where(f => f.Destination.Position == celestial.Coordinate.Position) - .Where(f => f.Destination.Type == celestial.Coordinate.Type) - .First().ID; + if (_tbotInstance.InstanceSettings.General.SlotPriorityLevel.Brain > 0) { + if (_tbotInstance.InstanceSettings.Brain.Transports.MaxSlots == 0) + DoLog(LogLevel.Information, $"Transports.MaxSlots is set to 0, you should increase it."); + else + DoLog(LogLevel.Information, $"0 slots available."); } - catch { } + DoLog(LogLevel.Information, $"Not enough slots available for Transports.MaxSlots, delaying."); + delay = true; } } } @@ -244,7 +449,7 @@ private async Task LifeformAutoMineCelestial(Celestial celestial) { DoLog(LogLevel.Information, $"Delaying..."); _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); try { - interval = (_tbotInstance.UserData.fleets.OrderBy(f => f.BackIn).First().BackIn ?? 0) * 1000 + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); + interval = (_tbotInstance.UserData.fleets.Where(f => f.Mission == Missions.Transport).OrderBy(f => f.BackIn).First().BackIn ?? 0) * 1000 + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); } catch { interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.Brain.LifeformAutoMine.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.Brain.LifeformAutoMine.CheckIntervalMax); } @@ -254,7 +459,9 @@ private async Task LifeformAutoMineCelestial(Celestial celestial) { interval = delayTime; } else { if (fleetId == (int) SendFleetCode.QuickerToWaitForProduction) { - var price = _calculationService.CalcPrice(buildable, level); + float costReduction = _calculationService.CalcLFBuildingsResourcesCostBonus(celestial); + float popReduction = _calculationService.CalcLFBuildingsPopulationCostBonus(celestial); + var price = _calculationService.CalcPrice(buildable, level, costReduction, 0, popReduction); long productionTime = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.Brain.LifeformAutoMine.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.Brain.LifeformAutoMine.CheckIntervalMax); celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.ResourcesProduction); DateTime now = await _tbotOgameBridge.GetDateTime(); @@ -283,13 +490,26 @@ private async Task LifeformAutoMineCelestial(Celestial celestial) { } } interval = productionTime + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); - } - else if (fleetId != 0 && fleetId != -1 && fleetId != -2) { - _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); - var transportfleet = _tbotInstance.UserData.fleets.Single(f => f.ID == fleetId && f.Mission == Missions.Transport); - interval = (transportfleet.ArriveIn * 1000) + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); } else { - interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.Brain.LifeformAutoMine.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.Brain.LifeformAutoMine.CheckIntervalMax); + if (fleetId > 0) { + _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); + var transportfleet = _tbotInstance.UserData.fleets.Single(f => f.ID == fleetId && f.Mission == Missions.Transport); + interval = (transportfleet.ArriveIn * 1000) + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); + } else { + interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.Brain.LifeformAutoMine.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.Brain.LifeformAutoMine.CheckIntervalMax); + } + if ((bool) _tbotInstance.InstanceSettings.Brain.Transports.DoMultipleTransportIsNotEnoughShipButSamePosition) { + var transportfleet2 = _tbotInstance.UserData.fleets.Where(f => f.Mission == Missions.Transport) + .Where(f => f.Destination.IsSame(celestial.Coordinate)) + .Where(f => f.Origin.Galaxy == celestial.Coordinate.Galaxy) + .Where(f => f.Origin.System == celestial.Coordinate.System) + .Where(f => f.Origin.Position == celestial.Coordinate.Position) + .Where(f => f.Origin.Type == (celestial.Coordinate.Type == Celestials.Planet ? Celestials.Moon : Celestials.Planet)) + .ToList(); + if (transportfleet2.Count() > 0) { + interval = (long) (transportfleet2.First().BackIn * 1000) + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); + } + } } } time = await _tbotOgameBridge.GetDateTime(); diff --git a/TBot/Workers/Brain/LifeformsAutoResearchCelestialWorker.cs b/TBot/Workers/Brain/LifeformsAutoResearchCelestialWorker.cs index f396d81d..54c38f72 100644 --- a/TBot/Workers/Brain/LifeformsAutoResearchCelestialWorker.cs +++ b/TBot/Workers/Brain/LifeformsAutoResearchCelestialWorker.cs @@ -86,12 +86,24 @@ private async Task LifeformAutoResearchCelestial(Celestial celestial) { bool delayProduction = false; long delayTime = 0; long interval = 0; + _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); + List planets = new(); + foreach (var p in _tbotInstance.UserData.celestials) { + if (p.Coordinate.Type == Celestials.Planet) { + var newPlanet = await _tbotOgameBridge.UpdatePlanet(p, UpdateTypes.Facilities); + newPlanet = await _tbotOgameBridge.UpdatePlanet(p, UpdateTypes.Buildings); + newPlanet = await _tbotOgameBridge.UpdatePlanet(p, UpdateTypes.LFBonuses); + planets.Add(newPlanet); + } + } try { DoLog(LogLevel.Information, $"Running Lifeform AutoResearch on {celestial.ToString()}"); celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.Fast); celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.Resources); celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.LFTechs); celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.Constructions); + celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.LFBonuses); + celestial = await _tbotOgameBridge.UpdatePlanet(celestial, UpdateTypes.Ships); int maxResearchLevel = SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.Brain.LifeformAutoResearch, "MaxResearchLevel") ? (int) _tbotInstance.InstanceSettings.Brain.LifeformAutoResearch.MaxResearchLevel : 1; int maxTechs11 = SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.Brain.LifeformAutoResearch, "MaxTechs11") ? (int) _tbotInstance.InstanceSettings.Brain.LifeformAutoResearch.MaxTechs11 : maxResearchLevel; @@ -180,26 +192,240 @@ private async Task LifeformAutoResearchCelestial(Celestial celestial) { DoLog(LogLevel.Information, $"Not enough resources to build: {buildable.ToString()} level {level.ToString()} on {celestial.ToString()}. Needed: {xCostBuildable.LFBuildingCostResources.ToString()} - Available: {celestial.Resources.LFBuildingCostResources.ToString()}"); if ((bool) _tbotInstance.InstanceSettings.Brain.LifeformAutoResearch.Transports.Active && (bool) _tbotInstance.InstanceSettings.Brain.Transports.Active) { + _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); - if (!_calculationService.IsThereTransportTowardsCelestial(celestial, _tbotInstance.UserData.fleets)) { - Celestial origin = _tbotInstance.UserData.celestials - .Unique() - .Where(c => c.Coordinate.Galaxy == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Galaxy) - .Where(c => c.Coordinate.System == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.System) - .Where(c => c.Coordinate.Position == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Position) - .Where(c => c.Coordinate.Type == Enum.Parse((string) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Type)) - .SingleOrDefault() ?? new() { ID = 0 }; - fleetId = await _fleetScheduler.HandleMinerTransport(origin, celestial, xCostBuildable, Buildables.Null); - if (fleetId == (int) SendFleetCode.AfterSleepTime) { - stop = true; - return; - } - if (fleetId == (int) SendFleetCode.NotEnoughSlots) { - delay = true; - return; + List rankSlotsPriority = new() { + new RankSlotsPriority(Feature.BrainLifeformAutoResearch, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Brain, + ((bool) _tbotInstance.InstanceSettings.Brain.Active && (bool) _tbotInstance.InstanceSettings.Brain.Transports.Active && ((bool) _tbotInstance.InstanceSettings.Brain.AutoMine.Active || (bool) _tbotInstance.InstanceSettings.Brain.AutoResearch.Active || (bool) _tbotInstance.InstanceSettings.Brain.LifeformAutoMine.Active || (bool) _tbotInstance.InstanceSettings.Brain.LifeformAutoResearch.Active)), + (int) _tbotInstance.InstanceSettings.Brain.Transports.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Transport)), + new RankSlotsPriority(Feature.Expeditions, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Expeditions, + (bool) _tbotInstance.InstanceSettings.Expeditions.Active, + (int) _tbotInstance.UserData.slots.ExpTotal, + (int)_tbotInstance.UserData.slots.ExpInUse), + new RankSlotsPriority(Feature.AutoFarm, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoFarm, + (bool) _tbotInstance.InstanceSettings.AutoFarm.Active, + (int) _tbotInstance.InstanceSettings.AutoFarm.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Attack)), + new RankSlotsPriority(Feature.Colonize, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Colonize, + (bool) _tbotInstance.InstanceSettings.AutoColonize.Active, + (bool) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.Active ? + (int) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.MaxSlots : + 1, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Colonize)), + new RankSlotsPriority(Feature.AutoDiscovery, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoDiscovery, + (bool) _tbotInstance.InstanceSettings.AutoDiscovery.Active, + (int) _tbotInstance.InstanceSettings.AutoDiscovery.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Discovery)), + new RankSlotsPriority(Feature.Harvest, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoHarvest, + (bool) _tbotInstance.InstanceSettings.AutoHarvest.Active, + (int) _tbotInstance.InstanceSettings.AutoHarvest.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Harvest)) + }; + int MaxSlots = _calculationService.CalcSlotsPriority(Feature.BrainLifeformAutoResearch, rankSlotsPriority, _tbotInstance.UserData.slots, _tbotInstance.UserData.fleets, (int) _tbotInstance.InstanceSettings.General.SlotsToLeaveFree); + + if (MaxSlots > 0) { + if (!_calculationService.IsThereTransportTowardsCelestial(celestial, _tbotInstance.UserData.fleets)) { + Celestial origin = new() { ID = 0 }; + List allCelestials = _tbotInstance.UserData.celestials; + for (int i = 0; i < allCelestials.Count(); i++) { + allCelestials[i] = await _tbotOgameBridge.UpdatePlanet(allCelestials[i], UpdateTypes.Resources); + allCelestials[i] = await _tbotOgameBridge.UpdatePlanet(allCelestials[i], UpdateTypes.Ships); + allCelestials[i] = await _tbotOgameBridge.UpdatePlanet(allCelestials[i], UpdateTypes.LFBonuses); + } + Resources missingResources = xCostBuildable.Difference(celestial.Resources); + if ((bool) _tbotInstance.InstanceSettings.Brain.Transports.CheckMoonOrPlanetFirst) { + if ((bool) _tbotInstance.InstanceSettings.Brain.Transports.CheckMoonOrPlanetFirst && _calculationService.IsThereMoonHere(allCelestials, celestial)) { + origin = allCelestials.Unique() + .Where(c => c.Coordinate.Galaxy == celestial.Coordinate.Galaxy) + .Where(c => c.Coordinate.System == celestial.Coordinate.System) + .Where(c => c.Coordinate.Position == celestial.Coordinate.Position) + .Where(c => c.Coordinate.Type == Celestials.Moon) + .SingleOrDefault() ?? new() { ID = 0 }; + } + if (origin.ID != 0) { + if (origin.Resources.IsEnoughFor(missingResources)) { + missingResources = _tbotInstance.InstanceSettings.Brain.Transports.RoundResources ? missingResources.Round() : missingResources; + Buildables preferredShip = Buildables.SmallCargo; + if (!Enum.TryParse((string) _tbotInstance.InstanceSettings.Brain.Transports.CargoType, true, out preferredShip)) { + _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, "Unable to parse CargoType. Falling back to default SmallCargo"); + preferredShip = Buildables.SmallCargo; + } + long shipsNeeded = _calculationService.CalcShipNumberForPayload(missingResources, preferredShip, _tbotInstance.UserData.researches.HyperspaceTechnology, _tbotInstance.UserData.serverData, origin.LFBonuses.GetShipCargoBonus(preferredShip), _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.serverData.ProbeCargo); + if (origin.Ships.GetAmount(preferredShip) >= shipsNeeded) { + Ships ships = new(); + ships.Add((Buildables) preferredShip, shipsNeeded); + fleetId = await _fleetScheduler.SendFleet(origin, ships, celestial.Coordinate, Missions.Transport, Speeds.HundredPercent, missingResources); + if (fleetId == (int) SendFleetCode.AfterSleepTime) { + stop = true; + } + if (fleetId == (int) SendFleetCode.NotEnoughSlots) { + delay = true; + } + } else if (_tbotInstance.InstanceSettings.Brain.Transports.DoMultipleTransportIsNotEnoughShipButSamePosition) { + DoLog(LogLevel.Warning, $"Not enough Cargo available. Multiple transports will take place."); + Ships ships = new(); + ships.Add((Buildables) preferredShip, origin.Ships.GetAmount(preferredShip)); + Resources transportableResources = _calculationService.CalcMaxTransportableResources(ships, missingResources, _tbotInstance.UserData.researches.HyperspaceTechnology, _tbotInstance.UserData.serverData, origin.LFBonuses, _tbotInstance.UserData.userInfo.Class, 0, _tbotInstance.UserData.serverData.ProbeCargo); + fleetId = await _fleetScheduler.SendFleet(origin, ships, celestial.Coordinate, Missions.Transport, Speeds.HundredPercent, transportableResources); + if (fleetId == (int) SendFleetCode.AfterSleepTime) { + stop = true; + } + if (fleetId == (int) SendFleetCode.NotEnoughSlots) { + delay = true; + } + } else { + DoLog(LogLevel.Warning, $"Not enough Cargo available. Skipping CheckMoonOrPlanetFirst."); + } + } else { + DoLog(LogLevel.Warning, $"Not enough resources available on {origin.ToString()} only to send resources to {celestial.ToString()}."); + } + } else { + DoLog(LogLevel.Warning, $"No Moon available on {celestial.ToString()}."); + } + } + + if (fleetId <= 0) { + if ((bool) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.Active) { + origin = _tbotInstance.UserData.celestials + .Unique() + .Where(c => c.Coordinate.Galaxy == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Galaxy) + .Where(c => c.Coordinate.System == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.System) + .Where(c => c.Coordinate.Position == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Position) + .Where(c => c.Coordinate.Type == Enum.Parse((string) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Type)) + .SingleOrDefault() ?? new() { ID = 0 }; + List celestialsToExclude = _calculationService.ParseCelestialsList(_tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.Exclude, allCelestials); + Buildables preferredShip = Buildables.SmallCargo; + if (!Enum.TryParse((string) _tbotInstance.InstanceSettings.Brain.Transports.CargoType, true, out preferredShip)) { + _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, "Unable to parse CargoType. Falling back to default SmallCargo"); + preferredShip = Buildables.SmallCargo; + } + TransportSettings transportsSettings = new((bool) _tbotInstance.InstanceSettings.Brain.Transports.Active, + preferredShip, + (long) _tbotInstance.InstanceSettings.Brain.Transports.DeutToLeaveOnMoons, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.RoundResources, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.SendToTheMoonIfPossible, + origin, + (long) _tbotInstance.InstanceSettings.Brain.Transports.MaxSlots, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.CheckMoonOrPlanetFirst, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.DoMultipleTransportIsNotEnoughShipButSamePosition, + new MultipleOrigins((bool) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.Active, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.OnlyFromMoons, + (long) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.MinimumResourcesToSend, + (bool) _tbotInstance.InstanceSettings.Brain.Transports.MultipleOrigins.PriorityToProximityOverQuantity, + celestialsToExclude) + ); + + Celestial destination; + if ((bool) transportsSettings.SendToTheMoonIfPossible && _calculationService.IsThereMoonHere(allCelestials, celestial)) { + destination = allCelestials + .Unique() + .Where(planet => planet.Coordinate.Galaxy == celestial.Coordinate.Galaxy) + .Where(planet => planet.Coordinate.System == celestial.Coordinate.System) + .Where(planet => planet.Coordinate.Position == celestial.Coordinate.Position) + .Where(planet => planet.Coordinate.Type == Celestials.Moon) + .SingleOrDefault() ?? new() { ID = 0 }; + if (destination.Ships.IsEmpty() || celestial.Resources.TotalResources == 0) + destination = celestial; + } else { + destination = celestial; + } + + var resultOrigins = _calculationService.CalcMultipleOrigin(celestial, allCelestials, missingResources, transportsSettings, _tbotInstance.UserData.fleets, _tbotInstance.UserData); + + if (resultOrigins.Count() == 0) { + DoLog(LogLevel.Information, $"No origin is available. This may be due to a lack of resources or cargo."); + return; + } + if (resultOrigins.Count() > MaxSlots) { + DoLog(LogLevel.Information, $"Not enough slots available to send all resources to build: {buildable.ToString()} level {level.ToString()} on {celestial.ToString()}. Slots needed: {resultOrigins.Count().ToString()}/{MaxSlots}."); + delay = true; + return; + } + + Ships ships = new(); + + foreach (var item in resultOrigins) { + ships = new(); + ships.Add((Buildables) transportsSettings.CargoType, _calculationService.CalcShipNumberForPayload(item.FirstOrDefault().Value, (Buildables) transportsSettings.CargoType, _tbotInstance.UserData.researches.HyperspaceTechnology, _tbotInstance.UserData.serverData, celestial.LFBonuses.GetShipCargoBonus(transportsSettings.CargoType), _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.serverData.ProbeCargo)); + if (item.FirstOrDefault().Key.Coordinate.IsSame(destination.Coordinate) && transportsSettings.SendToTheMoonIfPossible && destination.Coordinate.Type == Celestials.Moon) + fleetId= await _fleetScheduler.SendFleet(item.FirstOrDefault().Key, ships, celestial.Coordinate, Missions.Transport, Speeds.HundredPercent, item.FirstOrDefault().Value); + else + fleetId= await _fleetScheduler.SendFleet(item.FirstOrDefault().Key, ships, destination.Coordinate, Missions.Transport, Speeds.HundredPercent, item.FirstOrDefault().Value); + + if (fleetId == (int) SendFleetCode.AfterSleepTime) { + stop = true; + return; + } + if (fleetId == (int) SendFleetCode.NotEnoughSlots) { + delay = true; + return; + } + } + } else { + Celestial destination; + if ((bool) _tbotInstance.InstanceSettings.Brain.Transports.SendToTheMoonIfPossible && _calculationService.IsThereMoonHere(_tbotInstance.UserData.celestials, celestial)) { + destination = allCelestials + .Unique() + .Where(planet => planet.Coordinate.Galaxy == celestial.Coordinate.Galaxy) + .Where(planet => planet.Coordinate.System == celestial.Coordinate.System) + .Where(planet => planet.Coordinate.Position == celestial.Coordinate.Position) + .Where(planet => planet.Coordinate.Type == Celestials.Moon) + .SingleOrDefault() ?? new() { ID = 0 }; + if (destination.Ships.IsEmpty() || celestial.Resources.TotalResources == 0) + destination = celestial; + xCostBuildable = xCostBuildable.Difference(destination.Resources); + } else { + destination = allCelestials + .Unique() + .Where(planet => planet.Coordinate.Galaxy == celestial.Coordinate.Galaxy) + .Where(planet => planet.Coordinate.System == celestial.Coordinate.System) + .Where(planet => planet.Coordinate.Position == celestial.Coordinate.Position) + .Where(planet => planet.Coordinate.Type == celestial.Coordinate.Type) + .SingleOrDefault() ?? new() { ID = 0 }; + } + origin = allCelestials + .Unique() + .Where(c => c.Coordinate.Galaxy == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Galaxy) + .Where(c => c.Coordinate.System == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.System) + .Where(c => c.Coordinate.Position == (int) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Position) + .Where(c => c.Coordinate.Type == Enum.Parse((string) _tbotInstance.InstanceSettings.Brain.Transports.Origin.Type)) + .SingleOrDefault() ?? new() { ID = 0 }; + fleetId = await _fleetScheduler.HandleMinerTransport(origin, celestial, destination, xCostBuildable, LFBuildables.None); + if (fleetId == (int) SendFleetCode.AfterSleepTime) { + stop = true; + } + if (fleetId == (int) SendFleetCode.NotEnoughSlots) { + delay = true; + } + } + } + } else { + DoLog(LogLevel.Information, $"Skipping transport: there is already a transport incoming in {celestial.ToString()}"); + fleetId = _tbotInstance.UserData.fleets.Where(f => f.Mission == Missions.Transport) + .Where(f => f.Resources.TotalResources > 0) + .Where(f => f.ReturnFlight == false) + .Where(f => f.Destination.Galaxy == celestial.Coordinate.Galaxy) + .Where(f => f.Destination.System == celestial.Coordinate.System) + .Where(f => f.Destination.Position == celestial.Coordinate.Position) + .Where(f => f.Destination.Type == celestial.Coordinate.Type) + .First().ID; } } else { - DoLog(LogLevel.Information, $"Skipping transport: there is already a transport incoming in {celestial.ToString()}"); + if (_tbotInstance.InstanceSettings.General.SlotPriorityLevel.Brain > 0) { + if (_tbotInstance.InstanceSettings.Brain.Transports.MaxSlots == 0) + DoLog(LogLevel.Information, $"Transports.MaxSlots is set to 0, you should increase it."); + else + DoLog(LogLevel.Information, $"0 slots available."); + } + DoLog(LogLevel.Information, $"Not enough slots available for Transports.MaxSlots, delaying."); + delay = true; } } } @@ -225,19 +451,31 @@ private async Task LifeformAutoResearchCelestial(Celestial celestial) { DoLog(LogLevel.Information, $"Delaying..."); _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); try { - interval = (_tbotInstance.UserData.fleets.OrderBy(f => f.BackIn).First().BackIn ?? 0) * 1000 + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); + interval = (_tbotInstance.UserData.fleets.Where(f => f.Mission == Missions.Transport).OrderBy(f => f.BackIn).First().BackIn ?? 0) * 1000 + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); } catch { interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.Brain.LifeformAutoResearch.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.Brain.LifeformAutoResearch.CheckIntervalMax); } } else if (started) { interval = ((long) celestial.Constructions.LFResearchCountdown * (long) 1000) + (long) RandomizeHelper.CalcRandomInterval(IntervalType.AFewSeconds); } else { - if (fleetId != 0 && fleetId != -1 && fleetId != -2) { - _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); + if (fleetId > 0) { + _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); var transportfleet = _tbotInstance.UserData.fleets.Single(f => f.ID == fleetId && f.Mission == Missions.Transport); interval = (transportfleet.ArriveIn * 1000) + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); } else { - interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.Brain.LifeformAutoMine.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.Brain.LifeformAutoMine.CheckIntervalMax); + interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.Brain.LifeformAutoResearch.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.Brain.LifeformAutoResearch.CheckIntervalMax); + } + if ((bool) _tbotInstance.InstanceSettings.Brain.Transports.DoMultipleTransportIsNotEnoughShipButSamePosition) { + var transportfleet2 = _tbotInstance.UserData.fleets.Where(f => f.Mission == Missions.Transport) + .Where(f => f.Destination.IsSame(celestial.Coordinate)) + .Where(f => f.Origin.Galaxy == celestial.Coordinate.Galaxy) + .Where(f => f.Origin.System == celestial.Coordinate.System) + .Where(f => f.Origin.Position == celestial.Coordinate.Position) + .Where(f => f.Origin.Type == (celestial.Coordinate.Type == Celestials.Planet ? Celestials.Moon : Celestials.Planet)) + .ToList(); + if (transportfleet2.Count() > 0) { + interval = (long) (transportfleet2.First().BackIn * 1000) + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); + } } } diff --git a/TBot/Workers/ColonizeWorker.cs b/TBot/Workers/ColonizeWorker.cs index b10ec365..213069ee 100644 --- a/TBot/Workers/ColonizeWorker.cs +++ b/TBot/Workers/ColonizeWorker.cs @@ -49,10 +49,9 @@ public override LogSender GetLogSender() { return LogSender.Colonize; } - protected override async Task Execute() { + protected override async Task Execute() { await _tbotOgameBridge.CheckCelestials(); bool stop = false; - bool delay = false; Fields fieldsSettings = new() { Total = (int) _tbotInstance.InstanceSettings.AutoColonize.Abandon.MinFields }; @@ -98,7 +97,44 @@ protected override async Task Execute() { if (currentPlanets + slotsToLeaveFree < maxPlanets) { _tbotInstance.log(LogLevel.Information, LogSender.Colonize, "A new planet is needed."); + _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); + List rankSlotsPriority = new() { + new RankSlotsPriority(Feature.BrainAutoMine, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Brain, + ((bool) _tbotInstance.InstanceSettings.Brain.Active && (bool) _tbotInstance.InstanceSettings.Brain.Transports.Active && ((bool) _tbotInstance.InstanceSettings.Brain.AutoMine.Active || (bool) _tbotInstance.InstanceSettings.Brain.AutoResearch.Active || (bool) _tbotInstance.InstanceSettings.Brain.LifeformAutoMine.Active || (bool) _tbotInstance.InstanceSettings.Brain.LifeformAutoResearch.Active)), + (int) _tbotInstance.InstanceSettings.Brain.Transports.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Transport)), + new RankSlotsPriority(Feature.Expeditions, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Expeditions, + (bool) _tbotInstance.InstanceSettings.Expeditions.Active, + (int) _tbotInstance.UserData.slots.ExpTotal, + (int)_tbotInstance.UserData.slots.ExpInUse), + new RankSlotsPriority(Feature.AutoFarm, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoFarm, + (bool) _tbotInstance.InstanceSettings.AutoFarm.Active, + (int) _tbotInstance.InstanceSettings.AutoFarm.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Attack)), + new RankSlotsPriority(Feature.Colonize, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Colonize, + (bool) _tbotInstance.InstanceSettings.AutoColonize.Active, + (bool) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.Active ? + (int) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.MaxSlots : + 1, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Colonize)), + new RankSlotsPriority(Feature.AutoDiscovery, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoDiscovery, + (bool) _tbotInstance.InstanceSettings.AutoDiscovery.Active, + (int) _tbotInstance.InstanceSettings.AutoDiscovery.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Discovery)), + new RankSlotsPriority(Feature.Harvest, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoHarvest, + (bool) _tbotInstance.InstanceSettings.AutoHarvest.Active, + (int) _tbotInstance.InstanceSettings.AutoHarvest.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Harvest)) + }; + int MaxSlots = _calculationService.CalcSlotsPriority(Feature.Colonize, rankSlotsPriority, _tbotInstance.UserData.slots, _tbotInstance.UserData.fleets, (int) _tbotInstance.InstanceSettings.General.SlotsToLeaveFree); + if ( (!(bool) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.Active && _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Colonize && !f.ReturnFlight) >= maxPlanets - currentPlanets) || ((bool) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.Active && _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Colonize && !f.ReturnFlight) > 0) @@ -107,7 +143,7 @@ protected override async Task Execute() { interval = (_tbotInstance.UserData.fleets .OrderBy(f => f.ArriveIn) .First(f => !f.ReturnFlight) - .ArriveIn * 1000) + RandomizeHelper.CalcRandomInterval(IntervalType.AFewSeconds); + .ArriveIn * 1000) + RandomizeHelper.CalcRandomInterval(IntervalType.LessThanFiveSeconds); } else { Coordinate originCoords = new( (int) _tbotInstance.InstanceSettings.AutoColonize.Origin.Galaxy, @@ -141,7 +177,7 @@ protected override async Task Execute() { (int) ii, Celestials.Planet ); - if (_calculationService.CalcLimitAstro((int) targetCoords.Position, _tbotInstance.UserData.researches)) { + if (_calculationService.IsAstrophysicsPositionValid((int) targetCoords.Position, (int) _tbotInstance.UserData.researches.Astrophysics)) { targets.Add(targetCoords); } } @@ -169,7 +205,7 @@ protected override async Task Execute() { } filteredTargets = (bool) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.Active ? filteredTargetsRdm - .Take((int) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.MaxSlots) + .Take(MaxSlots) .ToList() : filteredTargetsRdm .Take(maxPlanets - currentPlanets) @@ -178,7 +214,7 @@ protected override async Task Execute() { filteredTargets = (bool) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.Active ? filteredTargets .OrderBy(t => _calculationService.CalcDistance(origin.Coordinate, t, _tbotInstance.UserData.serverData)) - .Take((int) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.MaxSlots) + .Take(MaxSlots) .ToList() : filteredTargets .OrderBy(t => _calculationService.CalcDistance(origin.Coordinate, t, _tbotInstance.UserData.serverData)) @@ -226,7 +262,14 @@ protected override async Task Execute() { return; } if (fleetId == (int) SendFleetCode.NotEnoughSlots) { - delay = true; + long delayInterval = 0; + try { + delayInterval = (_tbotInstance.UserData.fleets.OrderBy(f => f.BackIn).First().BackIn ?? 0) * 1000 + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); + } catch { + delayInterval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.AutoColonize.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.AutoColonize.CheckIntervalMax); + } + _tbotInstance.log(LogLevel.Information, LogSender.Colonize, $"Not enough fleet slots available. Delaying for {TimeSpan.FromMilliseconds(delayInterval).TotalSeconds}s."); + await Task.Delay((int)delayInterval); return; } var minWaitNextFleet = (int) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.MinWaitNextFleet; @@ -324,20 +367,7 @@ protected override async Task Execute() { _tbotInstance.log(LogLevel.Information, LogSender.Colonize, $"Stopping feature."); await EndExecution(); } - if (delay) { - _tbotInstance.log(LogLevel.Information, LogSender.Colonize, $"Delaying..."); - var time = await _tbotOgameBridge.GetDateTime(); - _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); - long interval; - try { - interval = (_tbotInstance.UserData.fleets.OrderBy(f => f.BackIn).First().BackIn ?? 0) * 1000 + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); - } catch { - interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.AutoColonize.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.AutoColonize.CheckIntervalMax); - } - var newTime = time.AddMilliseconds(interval); - ChangeWorkerPeriod(interval); - _tbotInstance.log(LogLevel.Information, LogSender.Colonize, $"Next check at {newTime}"); - } + await _tbotOgameBridge.CheckCelestials(); } } diff --git a/TBot/Workers/DefenderWorker.cs b/TBot/Workers/DefenderWorker.cs index eaca570c..5d864d90 100644 --- a/TBot/Workers/DefenderWorker.cs +++ b/TBot/Workers/DefenderWorker.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Concurrent; using System.Linq; using System.Text; using System.Threading; @@ -18,6 +19,9 @@ namespace Tbot.Workers { internal class DefenderWorker : WorkerBase { + private static readonly ConcurrentDictionary _handledAttackIds = new(); + private static readonly TimeSpan _handledAttackTtl = TimeSpan.FromMinutes(60); + private readonly IFleetScheduler _fleetScheduler; private readonly IOgameService _ogameService; private readonly ITBotOgamedBridge _tbotOgameBridge; @@ -41,12 +45,11 @@ protected override async Task Execute() { DateTime time = await _tbotOgameBridge.GetDateTime(); if (isUnderAttack) { if ((bool) _tbotInstance.InstanceSettings.Defender.Alarm.Active) - await Task.Factory.StartNew(() => ConsoleHelpers.PlayAlarm(), _ct); - // UpdateTitle(false, true); + await Task.Run(() => ConsoleHelpers.PlayAlarm(), _ct); DoLog(LogLevel.Warning, "ENEMY ACTIVITY!!!"); _tbotInstance.UserData.attacks = await _ogameService.GetAttacks(); foreach (AttackerFleet attack in _tbotInstance.UserData.attacks) { - HandleAttack(attack); + await HandleAttack(attack); } } else { DoLog(LogLevel.Information, "Your empire is safe"); @@ -55,7 +58,6 @@ protected override async Task Execute() { if (interval <= 0) interval = RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); - // Display dateTime for logging DateTime newTime = time.AddMilliseconds(interval); ChangeWorkerPeriod(TimeSpan.FromMilliseconds(interval)); DoLog(LogLevel.Information, $"Next check at {newTime.ToString()}"); @@ -93,8 +95,6 @@ public override LogSender GetLogSender() { private async Task FakeActivity() { - //checking if under attack by making activity on planet/moon configured in settings (otherwise make acti on latest activated planet) - // And make activity on one more random planet to fake real player Celestial celestial; Celestial randomCelestial; @@ -124,7 +124,23 @@ private async Task FakeActivity() { return; } - private async void HandleAttack(AttackerFleet attack) { + private async Task HandleAttack(AttackerFleet attack) { + try { + var nowUtc = DateTime.UtcNow; + foreach (var kv in _handledAttackIds.ToArray()) { + if (nowUtc - kv.Value > _handledAttackTtl) + _handledAttackIds.TryRemove(kv.Key, out _); + } + if (attack != null && attack.ID != 0 && + _handledAttackIds.TryGetValue(attack.ID, out var seenAt) && + (nowUtc - seenAt) <= _handledAttackTtl) { + DoLog(LogLevel.Information, $"Attack {attack.ID} already handled recently; skipping duplicate actions."); + return; + } + if (attack != null && attack.ID != 0) { + _handledAttackIds[attack.ID] = nowUtc; + } + if (_tbotInstance.UserData.celestials.Count() == 0) { DateTime time = await _tbotOgameBridge.GetDateTime(); long interval = RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); @@ -135,20 +151,42 @@ private async void HandleAttack(AttackerFleet attack) { return; } - Celestial attackedCelestial = _tbotInstance.UserData.celestials.Unique().SingleOrDefault(planet => planet.HasCoords(attack.Destination)); + Celestial attackedCelestial = _tbotInstance.UserData.celestials.Unique().FirstOrDefault(planet => planet.HasCoords(attack.Destination)); + if (attackedCelestial == null) { + DoLog(LogLevel.Warning, $"Unable to handle attack {attack.ID}: attacked celestial not found in account data."); + return; + } attackedCelestial = await _tbotOgameBridge.UpdatePlanet(attackedCelestial, UpdateTypes.Ships); + try { + if ((bool)_tbotInstance.InstanceSettings.Defender.IgnoreAttackIfIHave.Active) { + attackedCelestial = await _tbotOgameBridge.UpdatePlanet(attackedCelestial, UpdateTypes.Resources); + } + } catch { + } + try { - if ((_tbotInstance.InstanceSettings.Defender.WhiteList as long[]).Any()) { - foreach (int playerID in (long[]) _tbotInstance.InstanceSettings.Defender.WhiteList) { - if (attack.AttackerID == playerID) { - DoLog(LogLevel.Information, $"Attack {attack.ID.ToString()} skipped: attacker {attack.AttackerName} whitelisted."); - return; - } + var wlObj = _tbotInstance.InstanceSettings.Defender.WhiteList; + IEnumerable whiteListIds = wlObj switch { + long[] a => a, + int[] a => a.Select(x => (long)x), + IEnumerable e => e, + IEnumerable e => e.Select(x => (long)x), + _ => Enumerable.Empty() + }; + + if (!whiteListIds.Any() && wlObj != null) { + DoLog(LogLevel.Debug, $"Defender WhiteList present but unsupported type: {wlObj.GetType().FullName}"); + } + + foreach (var playerId in whiteListIds) { + if (attack.AttackerID == playerId) { + DoLog(LogLevel.Information, $"Attack {attack.ID.ToString()} skipped: attacker {attack.AttackerName} whitelisted."); + return; } } - } catch { - DoLog(LogLevel.Warning, "An error has occurred while checking Defender WhiteList"); + } catch (Exception ex) { + DoLog(LogLevel.Warning, $"An error has occurred while checking Defender WhiteList: {ex.Message}"); } try { @@ -170,13 +208,17 @@ private async void HandleAttack(AttackerFleet attack) { Type = Celestials.Planet })); } - defenderCelestial = await _tbotOgameBridge.UpdatePlanet(attackedCelestial, UpdateTypes.Facilities); + if (defenderCelestial == null) { + DoLog(LogLevel.Warning, $"Missile attack detected on {attack.Destination.ToString()} but planet celestial was not found in account data. Skipping missile defence."); + return; + } + defenderCelestial = await _tbotOgameBridge.UpdatePlanet(defenderCelestial, UpdateTypes.Facilities); if (defenderCelestial.Facilities.MissileSilo >= 2) { - defenderCelestial = await _tbotOgameBridge.UpdatePlanet(attackedCelestial, UpdateTypes.Defences); - defenderCelestial = await _tbotOgameBridge.UpdatePlanet(attackedCelestial, UpdateTypes.Productions); + defenderCelestial = await _tbotOgameBridge.UpdatePlanet(defenderCelestial, UpdateTypes.Defences); + defenderCelestial = await _tbotOgameBridge.UpdatePlanet(defenderCelestial, UpdateTypes.Productions); if (defenderCelestial.Productions.Count == 0) { var availableSpace = defenderCelestial.Facilities.MissileSilo - defenderCelestial.Defences.AntiBallisticMissiles - (2 * defenderCelestial.Defences.InterplanetaryMissiles); - defenderCelestial = await _tbotOgameBridge.UpdatePlanet(attackedCelestial, UpdateTypes.Resources); + defenderCelestial = await _tbotOgameBridge.UpdatePlanet(defenderCelestial, UpdateTypes.Resources); if (availableSpace > 0) { DoLog(LogLevel.Information, $"Building {availableSpace} AntiBallisticMissiles on {defenderCelestial.ToString()}"); await _ogameService.BuildDefences(defenderCelestial, Buildables.AntiBallisticMissiles, availableSpace); @@ -214,10 +256,14 @@ private async void HandleAttack(AttackerFleet attack) { } else { DoLog(LogLevel.Information, "Unable to detect fleet composition."); } + var ignoreAttackIfIHaveActive = (bool) _tbotInstance.InstanceSettings.Defender.IgnoreAttackIfIHave.Active; + var totalResources = attackedCelestial.Resources?.TotalResources ?? 0; + var fleetPoints = attackedCelestial.Ships?.GetFleetPoints() ?? 0; + if ( - (bool) _tbotInstance.InstanceSettings.Defender.IgnoreAttackIfIHave.Active && - attackedCelestial.Resources.TotalResources < (long) _tbotInstance.InstanceSettings.Defender.IgnoreAttackIfIHave.MinResourcesToSave && - (attackedCelestial.Ships.GetFleetPoints() *1000) < (long) _tbotInstance.InstanceSettings.Defender.IgnoreAttackIfIHave.MinFleetToSave + ignoreAttackIfIHaveActive && + totalResources < (long) _tbotInstance.InstanceSettings.Defender.IgnoreAttackIfIHave.MinResourcesToSave && + (fleetPoints * 1000) < (long) _tbotInstance.InstanceSettings.Defender.IgnoreAttackIfIHave.MinFleetToSave ) { DoLog(LogLevel.Information, $"Attack {attack.ID.ToString()} skipped: it's not worth it."); return; @@ -228,14 +274,16 @@ private async void HandleAttack(AttackerFleet attack) { if ((bool) _tbotInstance.InstanceSettings.Defender.TelegramMessenger.Active) { await _tbotInstance.SendTelegramMessage($"Player {attack.AttackerName} ({attack.AttackerID}) is attacking your planet {attack.Destination.ToString()} arriving at {attack.ArrivalTime.ToString()}"); - if (attack.Ships != null) + if (attack.Ships != null) { await Task.Delay(1000, _ct); - await _tbotInstance.SendTelegramMessage($"The attack is composed by: {attack.Ships.ToString()}"); + await _tbotInstance.SendTelegramMessage($"The attack is composed by: {attack.Ships.ToString()}"); + } } DoLog(LogLevel.Warning, $"Player {attack.AttackerName} ({attack.AttackerID}) is attacking your planet {attackedCelestial.ToString()} arriving at {attack.ArrivalTime.ToString()}"); - if (attack.Ships != null) + if (attack.Ships != null) { await Task.Delay(1000, _ct); - DoLog(LogLevel.Warning, $"The attack is composed by: {attack.Ships.ToString()}"); + DoLog(LogLevel.Warning, $"The attack is composed by: {attack.Ships.ToString()}"); + } if ((bool) _tbotInstance.InstanceSettings.Defender.SpyAttacker.Active) { _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); @@ -246,8 +294,12 @@ private async void HandleAttack(AttackerFleet attack) { Coordinate destination = attack.Origin; Ships ships = new() { EspionageProbe = (int) _tbotInstance.InstanceSettings.Defender.SpyAttacker.Probes }; int fleetId = await _fleetScheduler.SendFleet(attackedCelestial, ships, destination, Missions.Spy, Speeds.HundredPercent, new Resources(), _tbotInstance.UserData.userInfo.Class); - Fleet fleet = _tbotInstance.UserData.fleets.Single(fleet => fleet.ID == fleetId); - DoLog(LogLevel.Information, $"Spying attacker from {attackedCelestial.ToString()} to {destination.ToString()} with {_tbotInstance.InstanceSettings.Defender.SpyAttacker.Probes} probes. Arrival at {fleet.ArrivalTime.ToString()}"); + var fleet = _tbotInstance.UserData.fleets.SingleOrDefault(f => f.ID == fleetId); + if (fleet == null) { + DoLog(LogLevel.Warning, $"SpyAttacker: SendFleet returned id={fleetId}, but fleet was not found in current fleet list (send may have failed or list not updated yet)."); + } else { + DoLog(LogLevel.Information, $"Spying attacker from {attackedCelestial.ToString()} to {destination.ToString()} with {_tbotInstance.InstanceSettings.Defender.SpyAttacker.Probes} probes. Arrival at {fleet.ArrivalTime.ToString()}"); + } } catch (Exception e) { DoLog(LogLevel.Error, $"Could not spy attacker: an exception has occurred: {e.Message}"); DoLog(LogLevel.Warning, $"Stacktrace: {e.StackTrace}"); @@ -287,6 +339,10 @@ private async void HandleAttack(AttackerFleet attack) { DoLog(LogLevel.Warning, $"Stacktrace: {e.StackTrace}"); } } + } catch (Exception e) { + DoLog(LogLevel.Error, $"HandleAttack error for attack {attack?.ID}: {e.Message}"); + DoLog(LogLevel.Warning, $"Stacktrace: {e.StackTrace}"); + } } } -} +} \ No newline at end of file diff --git a/TBot/Workers/ExpeditionsWorker.cs b/TBot/Workers/ExpeditionsWorker.cs index fc7f2063..197ab4bd 100644 --- a/TBot/Workers/ExpeditionsWorker.cs +++ b/TBot/Workers/ExpeditionsWorker.cs @@ -34,7 +34,7 @@ public ExpeditionsWorker(ITBotMain parentInstance, } public override bool IsWorkerEnabledBySettings() { try { - return (bool) _tbotInstance.InstanceSettings.Expeditions.Active; + return (bool)_tbotInstance.InstanceSettings.Expeditions.Active; } catch (Exception) { return false; } @@ -49,18 +49,25 @@ public override Feature GetFeature() { public override LogSender GetLogSender() { return LogSender.Expeditions; } - - + private int CountActiveExpeditionsFromOrigin(Celestial origin) { + return _tbotInstance.UserData.fleets.Count(f => + f.Mission == Missions.Expedition && + f.Origin != null && + f.Origin.Galaxy == origin.Coordinate.Galaxy && + f.Origin.System == origin.Coordinate.System && + f.Origin.Position == origin.Coordinate.Position && + (int)f.Origin.Type == (int)origin.Coordinate.Type + ); +} protected override async Task Execute() { bool stop = false; bool delay = false; try { - // Wait for the thread semaphore to avoid the concurrency with itself long interval; DateTime time; DateTime newTime; - if ((bool) _tbotInstance.InstanceSettings.Expeditions.Active) { + if ((bool)_tbotInstance.InstanceSettings.Expeditions.Active) { _tbotInstance.UserData.researches = await _tbotOgameBridge.UpdateResearches(); if (_tbotInstance.UserData.researches.Astrophysics == 0) { DoLog(LogLevel.Information, "Skipping: Astrophysics not yet researched!"); @@ -75,8 +82,54 @@ protected override async Task Execute() { _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); _tbotInstance.UserData.serverData = await _ogameService.GetServerData(); + + List rankSlotsPriority = new() { + new RankSlotsPriority(Feature.BrainAutoMine, + (int)_tbotInstance.InstanceSettings.General.SlotPriorityLevel.Brain, + ((bool)_tbotInstance.InstanceSettings.Brain.Active && (bool)_tbotInstance.InstanceSettings.Brain.Transports.Active && + ((bool)_tbotInstance.InstanceSettings.Brain.AutoMine.Active || + (bool)_tbotInstance.InstanceSettings.Brain.AutoResearch.Active || + (bool)_tbotInstance.InstanceSettings.Brain.LifeformAutoMine.Active || + (bool)_tbotInstance.InstanceSettings.Brain.LifeformAutoResearch.Active)), + (int)_tbotInstance.InstanceSettings.Brain.Transports.MaxSlots, + (int)_tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Transport)), + new RankSlotsPriority(Feature.Expeditions, + (int)_tbotInstance.InstanceSettings.General.SlotPriorityLevel.Expeditions, + (bool)_tbotInstance.InstanceSettings.Expeditions.Active, + (int)_tbotInstance.UserData.slots.ExpTotal, + (int)_tbotInstance.UserData.slots.ExpInUse), + new RankSlotsPriority(Feature.AutoFarm, + (int)_tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoFarm, + (bool)_tbotInstance.InstanceSettings.AutoFarm.Active, + (int)_tbotInstance.InstanceSettings.AutoFarm.MaxSlots, + (int)_tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Attack)), + new RankSlotsPriority(Feature.Colonize, + (int)_tbotInstance.InstanceSettings.General.SlotPriorityLevel.Colonize, + (bool)_tbotInstance.InstanceSettings.AutoColonize.Active, + (bool)_tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.Active ? + (int)_tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.MaxSlots : 1, + (int)_tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Colonize)), + new RankSlotsPriority(Feature.AutoDiscovery, + (int)_tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoDiscovery, + (bool)_tbotInstance.InstanceSettings.AutoDiscovery.Active, + (int)_tbotInstance.InstanceSettings.AutoDiscovery.MaxSlots, + (int)_tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Discovery)), + new RankSlotsPriority(Feature.Harvest, + (int)_tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoHarvest, + (bool)_tbotInstance.InstanceSettings.AutoHarvest.Active, + (int)_tbotInstance.InstanceSettings.AutoHarvest.MaxSlots, + (int)_tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Harvest)) + }; + + int MaxSlots = _tbotInstance.UserData.slots.Total + - (int)_tbotInstance.InstanceSettings.General.SlotsToLeaveFree; + + if (MaxSlots < 0) + MaxSlots = 0; + int expsToSend; - if (SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.Expeditions, "WaitForAllExpeditions") && (bool) _tbotInstance.InstanceSettings.Expeditions.WaitForAllExpeditions) { + if (SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.Expeditions, "WaitForAllExpeditions") + && (bool)_tbotInstance.InstanceSettings.Expeditions.WaitForAllExpeditions) { if (_tbotInstance.UserData.slots.ExpInUse == 0) expsToSend = _tbotInstance.UserData.slots.ExpTotal; else @@ -84,25 +137,40 @@ protected override async Task Execute() { } else { expsToSend = Math.Min(_tbotInstance.UserData.slots.ExpFree, _tbotInstance.UserData.slots.Free); } + DoLog(LogLevel.Debug, $"Expedition slot free: {expsToSend}"); - if (SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.Expeditions, "WaitForMajorityOfExpeditions") && (bool) _tbotInstance.InstanceSettings.Expeditions.WaitForMajorityOfExpeditions) { - if ((double) expsToSend < Math.Round((double) _tbotInstance.UserData.slots.ExpTotal / 2D, 0, MidpointRounding.ToZero) + 1D) { + + if (SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.Expeditions, "WaitForMajorityOfExpeditions") + && (bool)_tbotInstance.InstanceSettings.Expeditions.WaitForMajorityOfExpeditions) { + if ((double)expsToSend < Math.Round((double)_tbotInstance.UserData.slots.ExpTotal / 2D, 0, MidpointRounding.ToZero) + 1D) { DoLog(LogLevel.Debug, $"Majority of expedition already in flight, Skipping..."); expsToSend = 0; } } + expsToSend = expsToSend < MaxSlots ? expsToSend : MaxSlots; + + if (expsToSend <= 0) { + var idleInterval = RandomizeHelper.CalcRandomInterval(IntervalType.AboutFiveMinutes); + var nowIdle = await _tbotOgameBridge.GetDateTime(); + DoLog(LogLevel.Information, "Expeditions idle – no free expedition slots"); + DoLog(LogLevel.Information, $"Next expedition check at {nowIdle.AddMilliseconds(idleInterval)}"); + ChangeWorkerPeriod(idleInterval); + return; + } + if (expsToSend > 0) { if (_tbotInstance.UserData.slots.ExpFree > 0) { if (_tbotInstance.UserData.slots.Free > 0) { + List origins = new(); if (_tbotInstance.InstanceSettings.Expeditions.Origin.Length > 0) { try { foreach (var origin in _tbotInstance.InstanceSettings.Expeditions.Origin) { Coordinate customOriginCoords = new( - (int) origin.Galaxy, - (int) origin.System, - (int) origin.Position, + (int)origin.Galaxy, + (int)origin.System, + (int)origin.Position, Enum.Parse(origin.Type.ToString()) ); Celestial customOrigin = _tbotInstance.UserData.celestials @@ -121,7 +189,14 @@ protected override async Task Execute() { _tbotInstance.UserData.celestials = await _tbotOgameBridge.UpdatePlanets(UpdateTypes.LFBonuses); origins.Add(_tbotInstance.UserData.celestials .OrderBy(planet => planet.Coordinate.Type == Celestials.Moon) - .ThenByDescending(planet => _calculationService.CalcFleetCapacity(planet.Ships, _tbotInstance.UserData.serverData, _tbotInstance.UserData.researches.HyperspaceTechnology, null, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.serverData.ProbeCargo)) + .ThenByDescending(planet => + _calculationService.CalcFleetCapacity( + planet.Ships, + _tbotInstance.UserData.serverData, + _tbotInstance.UserData.researches.HyperspaceTechnology, + null, + _tbotInstance.UserData.userInfo.Class, + _tbotInstance.UserData.serverData.ProbeCargo)) .First() ); } @@ -130,55 +205,121 @@ protected override async Task Execute() { _tbotInstance.UserData.celestials = await _tbotOgameBridge.UpdatePlanets(UpdateTypes.LFBonuses); origins.Add(_tbotInstance.UserData.celestials .OrderBy(planet => planet.Coordinate.Type == Celestials.Moon) - .ThenByDescending(planet => _calculationService.CalcFleetCapacity(planet.Ships, _tbotInstance.UserData.serverData, _tbotInstance.UserData.researches.HyperspaceTechnology, null, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.serverData.ProbeCargo)) + .ThenByDescending(planet => + _calculationService.CalcFleetCapacity( + planet.Ships, + _tbotInstance.UserData.serverData, + _tbotInstance.UserData.researches.HyperspaceTechnology, + null, + _tbotInstance.UserData.userInfo.Class, + _tbotInstance.UserData.serverData.ProbeCargo)) .First() ); } - if ((bool) _tbotInstance.InstanceSettings.Expeditions.RandomizeOrder) { + + if ((bool)_tbotInstance.InstanceSettings.Expeditions.RandomizeOrder) { origins = origins.Shuffle().ToList(); } - Dictionary originExps = new(); - int quot = (int) Math.Floor((float) expsToSend / (float) origins.Count()); + LFBonuses lfBonuses = origins.First().LFBonuses; + + _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); + + var expFleets = _tbotInstance.UserData.fleets + .Where(f => f.Mission == Missions.Expedition && f.Origin != null) + .ToList(); + + DoLog(LogLevel.Warning, $"[EXP DEBUG] Active expeditions total = {expFleets.Count}"); + + foreach (var f in expFleets) { + DoLog( + LogLevel.Warning, + $"[EXP DEBUG] Fleet origin: G{f.Origin.Galaxy}:{f.Origin.System}:{f.Origin.Position} Type={f.Origin.Type}" + ); + } + int maxPerOrigin = int.MaxValue; + if (SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.Expeditions, "MaxExpeditionsPerOrigin")) { + maxPerOrigin = (int)_tbotInstance.InstanceSettings.Expeditions.MaxExpeditionsPerOrigin; + } + + DoLog(LogLevel.Warning, + $"[EXP DEBUG] Origins count={origins.Count}, expsToSend={expsToSend}, maxPerOrigin={maxPerOrigin}"); + + var capacity = new Dictionary(); + foreach (var o in origins) { + int active = CountActiveExpeditionsFromOrigin(o); + int cap = Math.Max(0, maxPerOrigin - active); + capacity[o] = cap; + + DoLog(LogLevel.Warning, + $"[EXP DEBUG] Origin {o.Coordinate.Galaxy}:{o.Coordinate.System}:{o.Coordinate.Position} Type={o.Coordinate.Type} " + + $"active={active} maxPerOrigin={maxPerOrigin} cap={cap}"); + } + + var originExps = origins.ToDictionary(o => o, o => 0); + + // Use .Sum(x => (long)x) to ensure the addition happens in 64-bit space + long remaining = (long) Math.Min((long) expsToSend, capacity.Values.Sum(x => (long) x)); + + while (remaining > 0) { + bool progressed = false; + + foreach (var o in origins) { + if (remaining <= 0) break; + + if (originExps[o] < capacity[o]) { + originExps[o]++; + remaining--; + progressed = true; + } + } + + if (!progressed) break; + } + + foreach (var o in origins) { + DoLog(LogLevel.Warning, + $"[EXP DEBUG] PLAN origin {o.Coordinate.Galaxy}:{o.Coordinate.System}:{o.Coordinate.Position} Type={o.Coordinate.Type} " + + $"willSend={originExps[o]} (cap={capacity[o]})"); + } + + int delayExpedition = 0; foreach (var origin in origins) { - originExps.Add(origin, quot); - } - int rest = (int) Math.Floor((float) expsToSend % (float) origins.Count()); - for (int i = 0; i < rest; i++) { - originExps[origins[i]]++; - } - LFBonuses lfBonuses = origins.First().LFBonuses; - foreach (var origin in originExps.Keys) { int expsToSendFromThisOrigin = originExps[origin]; if (expsToSendFromThisOrigin == 0) { - continue; + if (delayExpedition > 0) + delayExpedition--; + else + continue; } else if (origin.Ships.IsEmpty()) { DoLog(LogLevel.Warning, "Unable to send expeditions: no ships available"); + delayExpedition++; continue; } else { Ships fleet; - if ((bool) _tbotInstance.InstanceSettings.Expeditions.ManualShips.Active) { + if ((bool)_tbotInstance.InstanceSettings.Expeditions.ManualShips.Active) { fleet = new( - (long) _tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.LightFighter, - (long) _tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.HeavyFighter, - (long) _tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.Cruiser, - (long) _tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.Battleship, - (long) _tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.Battlecruiser, - (long) _tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.Bomber, - (long) _tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.Destroyer, - (long) _tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.Deathstar, - (long) _tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.SmallCargo, - (long) _tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.LargeCargo, - (long) _tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.ColonyShip, - (long) _tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.Recycler, - (long) _tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.EspionageProbe, + (long)_tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.LightFighter, + (long)_tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.HeavyFighter, + (long)_tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.Cruiser, + (long)_tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.Battleship, + (long)_tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.Battlecruiser, + (long)_tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.Bomber, + (long)_tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.Destroyer, + (long)_tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.Deathstar, + (long)_tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.SmallCargo, + (long)_tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.LargeCargo, + (long)_tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.ColonyShip, + (long)_tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.Recycler, + (long)_tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.EspionageProbe, 0, 0, - (long) _tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.Reaper, - (long) _tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.Pathfinder + (long)_tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.Reaper, + (long)_tbotInstance.InstanceSettings.Expeditions.ManualShips.Ships.Pathfinder ); if (!origin.Ships.HasAtLeast(fleet, expsToSendFromThisOrigin)) { - DoLog(LogLevel.Warning, $"Unable to send expeditions: not enough ships in origin {origin.ToString()}"); + DoLog(LogLevel.Warning, $"Unable to send expeditions: not enough ships in origin {origin}"); + delayExpedition++; continue; } } else { @@ -189,61 +330,39 @@ protected override async Task Execute() { } if (primaryShip == Buildables.Null) { DoLog(LogLevel.Warning, "Unable to send expeditions: primary ship is Null"); + delayExpedition++; continue; } var availableShips = origin.Ships.GetMovableShips(); - if (SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.Expeditions, "PrimaryToKeep") && (int) _tbotInstance.InstanceSettings.Expeditions.PrimaryToKeep > 0) { - availableShips.SetAmount(primaryShip, Math.Max(0, availableShips.GetAmount(primaryShip) - (long) _tbotInstance.InstanceSettings.Expeditions.PrimaryToKeep)); - } - DoLog(LogLevel.Warning, $"Available {primaryShip.ToString()} in origin {origin.ToString()}: {availableShips.GetAmount(primaryShip)} ({_tbotInstance.InstanceSettings.Expeditions.PrimaryToKeep} must be kept at dock)"); - fleet = _calculationService.CalcFullExpeditionShips(availableShips, primaryShip, expsToSendFromThisOrigin, _tbotInstance.UserData.serverData, _tbotInstance.UserData.researches, lfBonuses, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.serverData.ProbeCargo); - if (fleet.GetAmount(primaryShip) < (long) _tbotInstance.InstanceSettings.Expeditions.MinPrimaryToSend) { - fleet.SetAmount(primaryShip, (long) _tbotInstance.InstanceSettings.Expeditions.MinPrimaryToSend); - if (!availableShips.HasAtLeast(fleet, expsToSendFromThisOrigin)) { - DoLog(LogLevel.Warning, $"Unable to send expeditions: available {primaryShip.ToString()} in origin {origin.ToString()} under set min number of {(long) _tbotInstance.InstanceSettings.Expeditions.MinPrimaryToSend}"); - continue; - } - } - Buildables secondaryShip = Buildables.Null; - if (!Enum.TryParse(_tbotInstance.InstanceSettings.Expeditions.SecondaryShip, true, out secondaryShip)) { - DoLog(LogLevel.Warning, "Unable to parse SecondaryShip. Falling back to default Null"); - secondaryShip = Buildables.Null; - } - if (secondaryShip != Buildables.Null) { - long secondaryToSend = Math.Min( - (long) Math.Round( - availableShips.GetAmount(secondaryShip) / (float) expsToSendFromThisOrigin, - 0, - MidpointRounding.ToZero - ), - (long) Math.Round( - fleet.GetAmount(primaryShip) * (float) _tbotInstance.InstanceSettings.Expeditions.SecondaryToPrimaryRatio, - 0, - MidpointRounding.ToZero - ) - ); - if (secondaryToSend < (long) _tbotInstance.InstanceSettings.Expeditions.MinSecondaryToSend) { - DoLog(LogLevel.Warning, $"Unable to send expeditions: available {secondaryShip.ToString()} in origin {origin.ToString()} under set number of {(long) _tbotInstance.InstanceSettings.Expeditions.MinSecondaryToSend}"); - continue; - } else { - fleet.Add(secondaryShip, secondaryToSend); - if (!availableShips.HasAtLeast(fleet, expsToSendFromThisOrigin)) { - DoLog(LogLevel.Warning, $"Unable to send expeditions: not enough ships in origin {origin.ToString()}"); - continue; - } - } + if (SettingsService.IsSettingSet(_tbotInstance.InstanceSettings.Expeditions, "PrimaryToKeep") + && (int)_tbotInstance.InstanceSettings.Expeditions.PrimaryToKeep > 0) { + availableShips.SetAmount( + primaryShip, + Math.Max(0, availableShips.GetAmount(primaryShip) + - (long)_tbotInstance.InstanceSettings.Expeditions.PrimaryToKeep)); } + + fleet = _calculationService.CalcFullExpeditionShips( + availableShips, + primaryShip, + expsToSendFromThisOrigin, + _tbotInstance.UserData.serverData, + _tbotInstance.UserData.researches, + lfBonuses, + _tbotInstance.UserData.userInfo.Class, + _tbotInstance.UserData.serverData.ProbeCargo + ); } - DoLog(LogLevel.Information, $"{expsToSendFromThisOrigin.ToString()} expeditions with {fleet.ToString()} will be sent from {origin.ToString()}"); + DoLog(LogLevel.Information, $"{expsToSendFromThisOrigin} expeditions with {fleet} will be sent from {origin}"); + List syslist = new(); for (int i = 0; i < expsToSendFromThisOrigin; i++) { Coordinate destination; - if ((bool) _tbotInstance.InstanceSettings.Expeditions.SplitExpeditionsBetweenSystems.Active) { + if ((bool)_tbotInstance.InstanceSettings.Expeditions.SplitExpeditionsBetweenSystems.Active) { var rand = new Random(); - - int range = (int) _tbotInstance.InstanceSettings.Expeditions.SplitExpeditionsBetweenSystems.Range; + int range = (int)_tbotInstance.InstanceSettings.Expeditions.SplitExpeditionsBetweenSystems.Range; while (expsToSendFromThisOrigin > range * 2) range += 1; @@ -265,36 +384,58 @@ protected override async Task Execute() { Type = Celestials.DeepSpace }; } + _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); - Resources payload = new(); - if ((long) _tbotInstance.InstanceSettings.Expeditions.FuelToCarry > 0) { - payload.Deuterium = (long) _tbotInstance.InstanceSettings.Expeditions.FuelToCarry; - } + Resources payload = new(); + if ((long)_tbotInstance.InstanceSettings.Expeditions.FuelToCarry > 0) { + payload.Deuterium = (long)_tbotInstance.InstanceSettings.Expeditions.FuelToCarry; + } if (_tbotInstance.UserData.slots.ExpFree > 0) { - var fleetId = await _fleetScheduler.SendFleet(origin, fleet, destination, Missions.Expedition, Speeds.HundredPercent, payload); - if (fleetId == (int) SendFleetCode.AfterSleepTime) { + var originUpdated = await _tbotOgameBridge.UpdatePlanet(origin, UpdateTypes.Ships); + + _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); + if (_tbotInstance.UserData.slots.ExpFree <= 0) { + DoLog(LogLevel.Information, "Unable to send expeditions: no expedition slots available."); + delay = true; + return; + } + + if (fleet == null || fleet.IsEmpty() || !originUpdated.Ships.HasAtLeast(fleet, 1)) { + DoLog(LogLevel.Warning, $"Skipping expedition: no ships available on origin {originUpdated}"); + delayExpedition++; + break; + } + + var fleetId = await _fleetScheduler.SendFleet( + originUpdated, + fleet, + destination, + Missions.Expedition, + Speeds.HundredPercent, + payload + ); + if (fleetId == (int)SendFleetCode.AfterSleepTime) { stop = true; return; } - if (fleetId == (int) SendFleetCode.NotEnoughSlots) { + if (fleetId == (int)SendFleetCode.NotEnoughSlots) { delay = true; return; } - - var minWaitNextFleet = (int) _tbotInstance.InstanceSettings.Expeditions.MinWaitNextFleet; - var maxWaitNextFleet = (int) _tbotInstance.InstanceSettings.Expeditions.MaxWaitNextFleet; - if (minWaitNextFleet < 0) - minWaitNextFleet = 0; - if (maxWaitNextFleet < 1) - maxWaitNextFleet = 1; + int minWait = (int)_tbotInstance.InstanceSettings.Expeditions.MinWaitNextFleet; + int maxWait = (int)_tbotInstance.InstanceSettings.Expeditions.MaxWaitNextFleet; - var rndWaitTimeMs = (int) RandomizeHelper.CalcRandomIntervalSecToMs(minWaitNextFleet, maxWaitNextFleet); + if (maxWait < minWait) { + DoLog(LogLevel.Warning, + $"Expeditions wait misconfigured (MinWaitNextFleet={minWait} > MaxWaitNextFleet={maxWait}). Swapping values."); + (minWait, maxWait) = (maxWait, minWait); + } + var rndWaitTimeMs = (int)RandomizeHelper.CalcRandomIntervalSecToMs(minWait, maxWait); - DoLog(LogLevel.Information, $"Wait {((float) rndWaitTimeMs / 1000).ToString("0.00")}s for next Expedition"); + DoLog(LogLevel.Information, $"Wait {(rndWaitTimeMs / 1000f):0.00}s for next Expedition"); await Task.Delay(rndWaitTimeMs, _ct); - } else { DoLog(LogLevel.Information, "Unable to send expeditions: no expedition slots available."); delay = true; @@ -303,11 +444,7 @@ protected override async Task Execute() { } } } - } else { - DoLog(LogLevel.Warning, "Unable to send expeditions: no fleet slots available"); } - } else { - DoLog(LogLevel.Warning, "Unable to send expeditions: no expeditions slots available"); } } @@ -315,47 +452,42 @@ protected override async Task Execute() { List orderedFleets = _tbotInstance.UserData.fleets .Where(fleet => fleet.Mission == Missions.Expedition) .ToList(); - if ((bool) _tbotInstance.InstanceSettings.Expeditions.WaitForAllExpeditions) { - orderedFleets = orderedFleets - .OrderByDescending(fleet => fleet.BackIn) - .ToList(); + + if ((bool)_tbotInstance.InstanceSettings.Expeditions.WaitForAllExpeditions) { + orderedFleets = orderedFleets.OrderByDescending(fleet => fleet.BackIn).ToList(); } else { - orderedFleets = orderedFleets - .OrderBy(fleet => fleet.BackIn) - .ToList(); + orderedFleets = orderedFleets.OrderBy(fleet => fleet.BackIn).ToList(); } _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); - if ((orderedFleets.Count() == 0) || (_tbotInstance.UserData.slots.ExpFree > 0 && (!((bool) _tbotInstance.InstanceSettings.Expeditions.WaitForAllExpeditions) && !((bool) _tbotInstance.InstanceSettings.Expeditions.WaitForMajorityOfExpeditions)))) { - interval = RandomizeHelper.CalcRandomInterval(IntervalType.AboutFiveMinutes); + if ((orderedFleets.Count == 0) || + (_tbotInstance.UserData.slots.ExpFree > 0 && + !((bool)_tbotInstance.InstanceSettings.Expeditions.WaitForAllExpeditions) && + !((bool)_tbotInstance.InstanceSettings.Expeditions.WaitForMajorityOfExpeditions))) { + interval = RandomizeHelper.CalcRandomInterval(IntervalType.AboutFiveMinutes); } else { - - var minWaitNextRound = (int) _tbotInstance.InstanceSettings.Expeditions.MinWaitNextRound; - var maxWaitNextRound = (int) _tbotInstance.InstanceSettings.Expeditions.MaxWaitNextRound; - - if (minWaitNextRound < 0) minWaitNextRound = 0; - if (maxWaitNextRound < 1) maxWaitNextRound = 1; - - interval = (int) ((1000 * orderedFleets.First().BackIn) + RandomizeHelper.CalcRandomIntervalSecToMs(minWaitNextRound, maxWaitNextRound)); - + interval = (int)((1000 * orderedFleets.First().BackIn) + + RandomizeHelper.CalcRandomIntervalSecToMs( + (int)_tbotInstance.InstanceSettings.Expeditions.MinWaitNextRound, + (int)_tbotInstance.InstanceSettings.Expeditions.MaxWaitNextRound)); } + time = await _tbotOgameBridge.GetDateTime(); - if (interval <= 0) - interval = RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); newTime = time.AddMilliseconds(interval); ChangeWorkerPeriod(interval); - DoLog(LogLevel.Information, $"Next check at {newTime.ToString()}"); + DoLog(LogLevel.Information, $"Next check at {newTime}"); await _tbotOgameBridge.CheckCelestials(); } - } catch (Exception e) { + } + catch (Exception e) { DoLog(LogLevel.Warning, $"HandleExpeditions exception: {e.Message}"); DoLog(LogLevel.Warning, $"Stacktrace: {e.StackTrace}"); - long interval = (long) (RandomizeHelper.CalcRandomInterval(IntervalType.AMinuteOrTwo)); + long interval = RandomizeHelper.CalcRandomInterval(IntervalType.AMinuteOrTwo); var time = await _tbotOgameBridge.GetDateTime(); - DateTime newTime = time.AddMilliseconds(interval); ChangeWorkerPeriod(interval); - DoLog(LogLevel.Information, $"Next check at {newTime.ToString()}"); - } finally { + DoLog(LogLevel.Information, $"Next check at {time.AddMilliseconds(interval)}"); + } + finally { if (!_tbotInstance.UserData.isSleeping) { if (stop) { DoLog(LogLevel.Information, $"Stopping feature."); @@ -367,13 +499,15 @@ protected override async Task Execute() { _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); long interval; try { - interval = (_tbotInstance.UserData.fleets.OrderBy(f => f.BackIn).First().BackIn ?? 0) * 1000 + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); + interval = (_tbotInstance.UserData.fleets.OrderBy(f => f.BackIn).First().BackIn ?? 0) * 1000 + + RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); } catch { - interval = RandomizeHelper.CalcRandomInterval((int) _tbotInstance.InstanceSettings.Expeditions.CheckIntervalMin, (int) _tbotInstance.InstanceSettings.Expeditions.CheckIntervalMax); + interval = RandomizeHelper.CalcRandomInterval( + (int)_tbotInstance.InstanceSettings.Expeditions.CheckIntervalMin, + (int)_tbotInstance.InstanceSettings.Expeditions.CheckIntervalMax); } - var newTime = time.AddMilliseconds(interval); ChangeWorkerPeriod(interval); - DoLog(LogLevel.Information, $"Next check at {newTime.ToString()}"); + DoLog(LogLevel.Information, $"Next check at {time.AddMilliseconds(interval)}"); } await _tbotOgameBridge.CheckCelestials(); } diff --git a/TBot/Workers/FleetScheduler.cs b/TBot/Workers/FleetScheduler.cs index 6ae792d7..244f4dcb 100644 --- a/TBot/Workers/FleetScheduler.cs +++ b/TBot/Workers/FleetScheduler.cs @@ -27,7 +27,43 @@ public class FleetScheduler : IFleetScheduler { private readonly ICalculationService _calcService; - private Dictionary timers = new(); + private readonly Dictionary timers = new(); + private readonly object _timersLock = new(); + + private bool TryGetTimer(string key, out Timer timer) { + lock (_timersLock) { + return timers.TryGetValue(key, out timer); + } + } + private bool ContainsTimer(string key) { + lock (_timersLock) { + return timers.ContainsKey(key); + } + } + private void AddTimer(string key, Timer timer) { + lock (_timersLock) { + if (!timers.ContainsKey(key)) timers.Add(key, timer); + } + } + private void ReplaceTimer(string key, Timer timer) { + lock (_timersLock) { + if (timers.TryGetValue(key, out var oldTimer)) { + try { oldTimer.Dispose(); } catch { } + timers.Remove(key); + } + timers[key] = timer; + } + } + private void DisposeAndRemoveTimer(string key) { + lock (_timersLock) { + if (timers.TryGetValue(key, out var t)) { + try { t.Dispose(); } catch { } + timers.Remove(key); + } + } + } + + public FleetScheduler( ICalculationService helpService, IOgameService ogameService) { @@ -55,48 +91,65 @@ public async Task SpyCrash(Celestial fromCelestial, Coordinate target = null) { await _tbotInstance.SendTelegramMessage($"No probes or no Fuel on {fromCelestial.Coordinate.ToString()}!"); return; } - // spycrash auto part - if (target == null) { - List spycrash = new(); - int playerid = _tbotInstance.UserData.userInfo.PlayerID; - int sys = 0; - for (sys = fromCelestial.Coordinate.System - 2; sys <= fromCelestial.Coordinate.System + 2; sys++) { - sys = GeneralHelper.ClampSystem(sys); - GalaxyInfo galaxyInfo = await _ogameService.GetGalaxyInfo(fromCelestial.Coordinate.Galaxy, sys); - foreach (var planet in galaxyInfo.Planets) { - try { - if (planet != null && !planet.Administrator && !planet.Inactive && !planet.StrongPlayer && !planet.Newbie && !planet.Banned && !planet.Vacation) { - if (planet.Player.ID != playerid) { //exclude player planet - spycrash.Add(new(planet.Coordinate.Galaxy, planet.Coordinate.System, planet.Coordinate.Position, Celestials.Planet)); - } - } - } catch (NullReferenceException) { - continue; - } - } - } - - if (spycrash.Count() == 0) { - await _tbotInstance.SendTelegramMessage($"No planet to spycrash on could be found over system -2 -> +2"); - return; - } else { - target = spycrash[random.Next(spycrash.Count())]; - } - } - var attackingShips = new Ships().Add(Buildables.EspionageProbe, 1); - - int fleetId = await SendFleet(fromCelestial, attackingShips, target, Missions.Attack, speed); - - if (fleetId != (int) SendFleetCode.GenericError || - fleetId != (int) SendFleetCode.AfterSleepTime || - fleetId != (int) SendFleetCode.NotEnoughSlots) { - _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"EspionageProbe sent to crash on {target.ToString()}"); - - await _tbotInstance.SendTelegramMessage($"EspionageProbe sent to crash on {target.ToString()}"); - } - return; - } - + List spycrash = new(); + + if (target == null) { + int playerid = _tbotInstance.UserData.userInfo.PlayerID; + + for (int sys = fromCelestial.Coordinate.System - 2; sys <= fromCelestial.Coordinate.System + 2; sys++) { + int clampedSys = GeneralHelper.ClampSystem(sys); + + GalaxyInfo galaxyInfo = await _ogameService.GetGalaxyInfo(fromCelestial.Coordinate.Galaxy, clampedSys); + if (galaxyInfo?.Planets == null) continue; + + foreach (var planet in galaxyInfo.Planets) { + try { + if (planet != null + && !planet.Administrator + && !planet.Inactive + && !planet.StrongPlayer + && !planet.Newbie + && !planet.Banned + && !planet.Vacation + && planet.Player != null + && planet.Player.ID != playerid) { + + spycrash.Add(new Coordinate( + planet.Coordinate.Galaxy, + planet.Coordinate.System, + planet.Coordinate.Position, + Celestials.Planet)); + } + } catch (NullReferenceException) { + continue; + } + } + } + + if (spycrash.Count == 0) { + await _tbotInstance.SendTelegramMessage("No planet to spycrash on could be found over system -2 -> +2"); + return; + } + + target = spycrash[random.Next(spycrash.Count)]; + } + var attackingShips = new Ships().Add(Buildables.EspionageProbe, 1); + + int fleetId = await SendFleet(fromCelestial, attackingShips, target, Missions.Attack, speed); + + if (fleetId != (int)SendFleetCode.GenericError && + fleetId != (int)SendFleetCode.AfterSleepTime && + fleetId != (int)SendFleetCode.NotEnoughSlots) + { + _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, + $"EspionageProbe sent to crash on {target}"); + + await _tbotInstance.SendTelegramMessage( + $"EspionageProbe sent to crash on {target}"); + } + + return; + } public async Task AutoFleetSave(Celestial celestial, bool isSleepTimeFleetSave = false, long minDuration = 0, bool WaitFleetsReturn = false, Missions TelegramMission = Missions.None, bool fromTelegram = false, bool saveall = false) { DateTime departureTime = await _tbotOgameBridge.GetDateTime(); _tbotInstance.SleepDuration = minDuration; @@ -116,8 +169,7 @@ public async Task AutoFleetSave(Celestial celestial, bool isSleepTimeFleetSave = interval = 0; } - if (interval > 0 && (!timers.TryGetValue("GhostSleepTimer", out Timer value))) { - //Stop features which are sending fleets + if (interval > 0 && (!TryGetTimer("GhostSleepTimer", out Timer value))) { List features = new List { Feature.Colonize, Feature.BrainAutoRepatriate, @@ -141,15 +193,15 @@ public async Task AutoFleetSave(Celestial celestial, bool isSleepTimeFleetSave = _tbotInstance.NextWakeUpTime = TimeToGhost.AddMilliseconds(minDuration * 1000); if (saveall) - timers.Add("GhostSleepTimer", new Timer(GhostandSleepAfterFleetsReturnAll, null, interval, Timeout.Infinite)); + AddTimer("GhostSleepTimer", new Timer(GhostandSleepAfterFleetsReturnAll, null, interval, Timeout.Infinite)); else - timers.Add("GhostSleepTimer", new Timer(GhostandSleepAfterFleetsReturn, null, interval, Timeout.Infinite)); + AddTimer("GhostSleepTimer", new Timer(GhostandSleepAfterFleetsReturn, null, interval, Timeout.Infinite)); _tbotInstance.log(LogLevel.Information, LogSender.SleepMode, $"Fleets active, Next check at {TimeToGhost.ToString()}"); await _tbotInstance.SendTelegramMessage($"Waiting for fleets return, delaying ghosting at {TimeToGhost.ToString()}"); return; - } else if (interval == 0 && (!timers.TryGetValue("GhostSleepTimer", out Timer value2))) { + } else if (interval == 0 && (!TryGetTimer("GhostSleepTimer", out Timer value2))) { _tbotInstance.log(LogLevel.Information, LogSender.SleepMode, $"No fleets active, Ghosting now."); _tbotInstance.NextWakeUpTime = departureTime.AddMilliseconds(minDuration * 1000); @@ -159,7 +211,7 @@ public async Task AutoFleetSave(Celestial celestial, bool isSleepTimeFleetSave = GhostandSleepAfterFleetsReturn(null); return; - } else if (timers.TryGetValue("GhostSleepTimer", out Timer value3)) { + } else if (TryGetTimer("GhostSleepTimer", out Timer value3)) { await _tbotInstance.SendTelegramMessage($"GhostSleep already planned, try /cancelghostsleep"); return; } @@ -204,9 +256,8 @@ public async Task AutoFleetSave(Celestial celestial, bool isSleepTimeFleetSave = FleetHypotesis possibleFleet = new(); int fleetId = (int) SendFleetCode.GenericError; - bool AlreadySent = false; //permit to swith to Harvest mission if not enough fuel to Deploy if celestial far away + bool AlreadySent = false; - //Doing DefaultMission or telegram /ghostto mission Missions mission; if (!Missions.TryParse(_tbotInstance.InstanceSettings.SleepMode.AutoFleetSave.DefaultMission, out mission)) { _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, "Error: Could not parse 'DefaultMission' from settings, value set to Harvest."); @@ -223,18 +274,17 @@ public async Task AutoFleetSave(Celestial celestial, bool isSleepTimeFleetSave = if (CheckFuel(fleet, celestial)) { fleetId = await SendFleet(fleet.Origin, fleet.Ships, fleet.Destination, fleet.Mission, fleet.Speed, payload, _tbotInstance.UserData.userInfo.Class, true); - if (fleetId != (int) SendFleetCode.GenericError || - fleetId != (int) SendFleetCode.AfterSleepTime || - fleetId != (int) SendFleetCode.NotEnoughSlots) { - possibleFleet = fleet; - AlreadySent = true; - break; - } + if (fleetId != (int)SendFleetCode.GenericError && + fleetId != (int)SendFleetCode.AfterSleepTime && + fleetId != (int)SendFleetCode.NotEnoughSlots) { + possibleFleet = fleet; + AlreadySent = true; + break; +} } } } - //If /ghostto -> leaving function if failed if (fromTelegram && !AlreadySent && mission == Missions.Harvest && fleetHypotesis.Count() == 0) { await _tbotInstance.SendTelegramMessage($"No debris field found for {mission}, try to /spycrash."); return; @@ -243,7 +293,6 @@ public async Task AutoFleetSave(Celestial celestial, bool isSleepTimeFleetSave = return; } - //Doing Deploy if (!AlreadySent) { _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, $"Fleetsave from {celestial.ToString()} no {mission} possible, checking next mission.."); if (mission == Missions.Harvest) { mission = Missions.Deploy; } else { mission = Missions.Harvest; }; @@ -255,19 +304,18 @@ public async Task AutoFleetSave(Celestial celestial, bool isSleepTimeFleetSave = if (CheckFuel(fleet, celestial)) { fleetId = await SendFleet(fleet.Origin, fleet.Ships, fleet.Destination, fleet.Mission, fleet.Speed, payload, _tbotInstance.UserData.userInfo.Class, true); - if (fleetId != (int) SendFleetCode.GenericError || - fleetId != (int) SendFleetCode.AfterSleepTime || - fleetId != (int) SendFleetCode.AfterSleepTime || - fleetId != (int) SendFleetCode.NotEnoughSlots) { - possibleFleet = fleet; - AlreadySent = true; - break; - } + if (fleetId != (int)SendFleetCode.GenericError && + fleetId != (int)SendFleetCode.AfterSleepTime && + fleetId != (int)SendFleetCode.NotEnoughSlots) +{ + possibleFleet = fleet; + AlreadySent = true; + break; +} } } } } - //Doing colonize if (!AlreadySent && celestial.Ships.ColonyShip > 0) { _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, $"Fleetsave from {celestial.ToString()} no {mission} found, checking Colonize destination..."); mission = Missions.Colonize; @@ -278,18 +326,20 @@ public async Task AutoFleetSave(Celestial celestial, bool isSleepTimeFleetSave = if (CheckFuel(fleet, celestial)) { fleetId = await SendFleet(fleet.Origin, fleet.Ships, fleet.Destination, fleet.Mission, fleet.Speed, payload, _tbotInstance.UserData.userInfo.Class, true); - if (fleetId != (int) SendFleetCode.GenericError || - fleetId != (int) SendFleetCode.AfterSleepTime || - fleetId != (int) SendFleetCode.NotEnoughSlots) { - possibleFleet = fleet; - AlreadySent = true; - break; - } + if (fleetId != (int)SendFleetCode.GenericError && + fleetId != (int)SendFleetCode.AfterSleepTime && + fleetId != (int)SendFleetCode.NotEnoughSlots) +{ + possibleFleet = fleet; + AlreadySent = true; + break; +} + } } } } - //Doing Spy + if (!AlreadySent && celestial.Ships.EspionageProbe > 0) { _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, $"Fleetsave from {celestial.ToString()} no {mission} found, checking Spy destination..."); mission = Missions.Spy; @@ -300,25 +350,47 @@ public async Task AutoFleetSave(Celestial celestial, bool isSleepTimeFleetSave = if (CheckFuel(fleet, celestial)) { fleetId = await SendFleet(fleet.Origin, fleet.Ships, fleet.Destination, fleet.Mission, fleet.Speed, payload, _tbotInstance.UserData.userInfo.Class, true); - if (fleetId != (int) SendFleetCode.GenericError || - fleetId != (int) SendFleetCode.AfterSleepTime || - fleetId != (int) SendFleetCode.NotEnoughSlots) { - possibleFleet = fleet; - AlreadySent = true; - break; - } + if (fleetId != (int)SendFleetCode.GenericError && + fleetId != (int)SendFleetCode.AfterSleepTime && + fleetId != (int)SendFleetCode.NotEnoughSlots) +{ + possibleFleet = fleet; + AlreadySent = true; + break; +} + + } + } + } + } + + if (!AlreadySent) { + _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, $"Fleetsave from {celestial.ToString()} no {mission} found, checking Transport destination..."); + mission = Missions.Transport; + fleetHypotesis = await GetFleetSaveDestination(_tbotInstance.UserData.celestials, celestial, departureTime, minDuration, mission, maxDeuterium); + if (fleetHypotesis.Count > 0) { + foreach (FleetHypotesis fleet in fleetHypotesis.OrderBy(pf => pf.Fuel).ThenBy(pf => pf.Duration <= minDuration)) { + _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, $"checking {mission} fleet to: {fleet.Destination}"); + if (CheckFuel(fleet, celestial)) { + fleetId = await SendFleet(fleet.Origin, fleet.Ships, fleet.Destination, fleet.Mission, fleet.Speed, payload, _tbotInstance.UserData.userInfo.Class, true); + + if (fleetId != (int)SendFleetCode.GenericError && + fleetId != (int)SendFleetCode.AfterSleepTime && + fleetId != (int)SendFleetCode.NotEnoughSlots) +{ + possibleFleet = fleet; + AlreadySent = true; + break; +} + } } } } - //Doing switch bool hasMoon = _tbotInstance.UserData.celestials.Count(c => c.HasCoords(new Coordinate(celestial.Coordinate.Galaxy, celestial.Coordinate.System, celestial.Coordinate.Position, Celestials.Moon))) == 1; - if (!AlreadySent && hasMoon && !timers.TryGetValue("GhostSleepTimer", out Timer val)) { + if (!AlreadySent && hasMoon && !TryGetTimer("GhostSleepTimer", out Timer val)) { _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, $"Fleetsave from {celestial.ToString()} no {mission} possible (missing fuel?), checking for switch if has Moon"); - //var validSpeeds = _tbotInstance.UserData.userInfo.Class == CharacterClass.General ? Speeds.GetGeneralSpeedsList() : Speeds.GetNonGeneralSpeedsList(); - //Random randomSpeed = new Random(); - //decimal speed = validSpeeds[randomSpeed.Next(validSpeeds.Count)]; decimal speed = 10; AlreadySent = await _tbotInstance.TelegramSwitch(speed, celestial); } @@ -330,34 +402,42 @@ public async Task AutoFleetSave(Celestial celestial, bool isSleepTimeFleetSave = } - if ((bool) _tbotInstance.InstanceSettings.SleepMode.AutoFleetSave.Recall && AlreadySent) { - if (fleetId != (int) SendFleetCode.GenericError || - fleetId != (int) SendFleetCode.AfterSleepTime || - fleetId != (int) SendFleetCode.NotEnoughSlots) { - Fleet fleet = _tbotInstance.UserData.fleets.Single(fleet => fleet.ID == fleetId); - DateTime time = await _tbotOgameBridge.GetDateTime(); - var interval = ((minDuration / 2) * 1000) + RandomizeHelper.CalcRandomInterval(IntervalType.AMinuteOrTwo); - if (interval <= 0) - interval = RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); - DateTime newTime = time.AddMilliseconds(interval); - timers.Add($"RecallTimer-{fleetId.ToString()}", new Timer(RetireFleet, fleet, interval, Timeout.Infinite)); - _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"The fleet will be recalled at {newTime.ToString()}"); - if (fromTelegram) - await _tbotInstance.SendTelegramMessage($"Fleet {fleetId} send to {possibleFleet.Mission} on {possibleFleet.Destination.ToString()}, fuel consumed: {possibleFleet.Fuel.ToString("#,#", CultureInfo.InvariantCulture)}, recalled at {newTime.ToString()}"); - } - } else { - if (fleetId != (int) SendFleetCode.GenericError || - fleetId != (int) SendFleetCode.AfterSleepTime || - fleetId != (int) SendFleetCode.NotEnoughSlots) { - Fleet fleet = _tbotInstance.UserData.fleets.Single(fleet => fleet.ID == fleetId); - DateTime returntime = (DateTime) fleet.BackTime; - _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"Fleet {fleetId} send to {possibleFleet.Mission} on {possibleFleet.Destination.ToString()}, arrive at {possibleFleet.Duration} fuel consumed: {possibleFleet.Fuel.ToString("#,#", CultureInfo.InvariantCulture)}"); - if (fromTelegram) - await _tbotInstance.SendTelegramMessage($"Fleet {fleetId} send to {possibleFleet.Mission} on {possibleFleet.Destination.ToString()}, arrive at {possibleFleet.Duration.ToString()}, returned at {returntime.ToString()} fuel consumed: {possibleFleet.Fuel.ToString("#,#", CultureInfo.InvariantCulture)}"); - } - } - } + if ((bool)_tbotInstance.InstanceSettings.SleepMode.AutoFleetSave.Recall && AlreadySent) +{ + if (fleetId > 0) + { + Fleet fleet = _tbotInstance.UserData.fleets.SingleOrDefault(f => f.ID == fleetId); +if (fleet == null) return; + DateTime time = await _tbotOgameBridge.GetDateTime(); + var interval = ((minDuration / 2) * 1000) + RandomizeHelper.CalcRandomInterval(IntervalType.AMinuteOrTwo); + + if (interval <= 0) + interval = RandomizeHelper.CalcRandomInterval(IntervalType.SomeSeconds); + + DateTime newTime = time.AddMilliseconds(interval); + AddTimer($"RecallTimer-{fleetId}", new Timer(RetireFleet, fleet, interval, Timeout.Infinite)); + _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, + $"The fleet will be recalled at {newTime}"); + } +} + + else +{ + if (fleetId > 0) + { + Fleet fleet = _tbotInstance.UserData.fleets.Single(f => f.ID == fleetId); + DateTime returntime = (DateTime)fleet.BackTime; + + _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, + $"Fleet {fleetId} send to {possibleFleet.Mission} on {possibleFleet.Destination}, arrive at {possibleFleet.Duration} fuel consumed: {possibleFleet.Fuel.ToString("#,#", CultureInfo.InvariantCulture)}"); + + if (fromTelegram) + await _tbotInstance.SendTelegramMessage( + $"Fleet {fleetId} send to {possibleFleet.Mission} on {possibleFleet.Destination}, arrive at {possibleFleet.Duration}, returned at {returntime} fuel consumed: {possibleFleet.Fuel.ToString("#,#", CultureInfo.InvariantCulture)}"); + } +} + } public async Task SendFleet(Celestial origin, Ships ships, Coordinate destination, Missions mission, decimal speed, Resources payload = null, CharacterClass playerClass = CharacterClass.NoClass, bool force = false) { _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"Sending fleet from {origin.Coordinate.ToString()} to {destination.ToString()}. Mission: {mission.ToString()}. Speed: {(speed * 10).ToString()}% Ships: {ships.ToString()}"); @@ -393,77 +473,94 @@ public async Task SendFleet(Celestial origin, Ships ships, Coordinate desti } } - /* - if ( - playerClass != CharacterClass.General && ( - speed == Speeds.FivePercent || - speed == Speeds.FifteenPercent || - speed == Speeds.TwentyfivePercent || - speed == Speeds.ThirtyfivePercent || - speed == Speeds.FourtyfivePercent || - speed == Speeds.FiftyfivePercent || - speed == Speeds.SixtyfivePercent || - speed == Speeds.SeventyfivePercent || - speed == Speeds.EightyfivePercent || - speed == Speeds.NinetyfivePercent - ) - ) {*/ - - if (!_calcService.GetValidSpeedsForClass(playerClass).Any(s => s == speed)) { - _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, "Unable to send fleet: speed not available for your class"); - return (int) SendFleetCode.GenericError; - } - origin = await _tbotOgameBridge.UpdatePlanet(origin, UpdateTypes.LFBonuses); - FleetPrediction fleetPrediction = _calcService.CalcFleetPrediction(origin.Coordinate, destination, ships, mission, speed, _tbotInstance.UserData.researches, _tbotInstance.UserData.serverData, origin.LFBonuses, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.allianceClass); - _tbotInstance.log(LogLevel.Debug, LogSender.FleetScheduler, $"Calculated flight time (one-way): {TimeSpan.FromSeconds(fleetPrediction.Time).ToString()}"); +if (!_calcService.GetValidSpeedsForClass(playerClass).Any(s => s == speed)) { + _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, + "Unable to send fleet: speed not available for your class"); + return (int)SendFleetCode.GenericError; +} - var flightTime = mission switch { - Missions.Deploy => fleetPrediction.Time, - Missions.Expedition => (long) Math.Round((double) (2 * fleetPrediction.Time) + 3600, 0, MidpointRounding.ToPositiveInfinity), - _ => (long) Math.Round((double) (2 * fleetPrediction.Time), 0, MidpointRounding.ToPositiveInfinity), - }; - _tbotInstance.log(LogLevel.Debug, LogSender.FleetScheduler, $"Calculated flight time (full trip): {TimeSpan.FromSeconds(flightTime).ToString()}"); - _tbotInstance.log(LogLevel.Debug, LogSender.FleetScheduler, $"Calculated flight fuel: {fleetPrediction.Fuel.ToString()}"); +origin = await _tbotOgameBridge.UpdatePlanet(origin, UpdateTypes.LFBonuses); + +FleetPrediction fleetPrediction = _calcService.CalcFleetPrediction( + origin.Coordinate, + destination, + ships, + mission, + speed, + _tbotInstance.UserData.researches, + _tbotInstance.UserData.serverData, + origin.LFBonuses, + _tbotInstance.UserData.userInfo.Class, + _tbotInstance.UserData.allianceClass +); + +_tbotInstance.log(LogLevel.Debug, LogSender.FleetScheduler, + $"Calculated flight time (one-way): {TimeSpan.FromSeconds(fleetPrediction.Time)}"); + +var flightTime = mission switch +{ + Missions.Deploy => fleetPrediction.Time, + Missions.Expedition => (long)Math.Round((double)(2 * fleetPrediction.Time) + 3600, 0, MidpointRounding.ToPositiveInfinity), + _ => (long)Math.Round((double)(2 * fleetPrediction.Time), 0, MidpointRounding.ToPositiveInfinity) +}; +_tbotInstance.log(LogLevel.Debug, LogSender.FleetScheduler, + $"Calculated flight time (full trip): {TimeSpan.FromSeconds(flightTime)}"); +_tbotInstance.log(LogLevel.Debug, LogSender.FleetScheduler, + $"Calculated flight fuel: {fleetPrediction.Fuel}"); + +origin = await _tbotOgameBridge.UpdatePlanet(origin, UpdateTypes.Resources); + +if (origin.Resources.Deuterium < fleetPrediction.Fuel) { + _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, + "Unable to send fleet: not enough deuterium!"); + return (int)SendFleetCode.GenericError; +} - origin = await _tbotOgameBridge.UpdatePlanet(origin, UpdateTypes.Resources); - if (origin.Resources.Deuterium < fleetPrediction.Fuel) { - _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, "Unable to send fleet: not enough deuterium!"); - return (int) SendFleetCode.GenericError; - } - if (_calcService.CalcFleetFuelCapacity(ships, _tbotInstance.UserData.serverData, _tbotInstance.UserData.researches.HyperspaceTechnology, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.serverData.ProbeCargo) != 0 && _calcService.CalcFleetFuelCapacity(ships, _tbotInstance.UserData.serverData, _tbotInstance.UserData.researches.HyperspaceTechnology, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.serverData.ProbeCargo) < fleetPrediction.Fuel) { - _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, "Unable to send fleet: ships don't have enough fuel capacity!"); - return (int) SendFleetCode.GenericError; - } - if ( - (bool) _tbotInstance.InstanceSettings.SleepMode.Active && - DateTime.TryParse((string) _tbotInstance.InstanceSettings.SleepMode.GoToSleep, out DateTime goToSleep) && - DateTime.TryParse((string) _tbotInstance.InstanceSettings.SleepMode.WakeUp, out DateTime wakeUp) && - !force - ) { - DateTime time = await _tbotOgameBridge.GetDateTime(); - if (GeneralHelper.ShouldSleep(time, goToSleep, wakeUp)) { - _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, "Unable to send fleet: bed time has passed"); - return (int) SendFleetCode.AfterSleepTime; - } - if (goToSleep >= wakeUp) { - wakeUp = wakeUp.AddDays(1); - } - if (goToSleep < time) { - goToSleep = goToSleep.AddDays(1); - } - if (wakeUp < time) { - wakeUp = wakeUp.AddDays(1); - } - _tbotInstance.log(LogLevel.Debug, LogSender.FleetScheduler, $"goToSleep : {goToSleep.ToString()}"); - _tbotInstance.log(LogLevel.Debug, LogSender.FleetScheduler, $"wakeUp : {wakeUp.ToString()}"); - DateTime returnTime = time.AddSeconds(flightTime); - _tbotInstance.log(LogLevel.Debug, LogSender.FleetScheduler, $"returnTime : {returnTime.ToString()}"); - - if (returnTime >= goToSleep && returnTime <= wakeUp) { - _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, "Unable to send fleet: it would come back during sleep time"); - return (int) SendFleetCode.AfterSleepTime; - } - } +long fuelCapacity = _calcService.CalcFleetFuelCapacity( + ships, + _tbotInstance.UserData.serverData, + _tbotInstance.UserData.researches.HyperspaceTechnology, + _tbotInstance.UserData.userInfo.Class, + _tbotInstance.UserData.serverData.ProbeCargo +); + +if (fuelCapacity != 0 && fuelCapacity < fleetPrediction.Fuel) { + _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, + "Unable to send fleet: ships don't have enough fuel capacity!"); + return (int)SendFleetCode.GenericError; +} + +bool ignoreSleepForExpeditions = + mission == Missions.Expedition && + (bool)_tbotInstance.InstanceSettings.Expeditions.IgnoreSleep; + +if ( + (bool)_tbotInstance.InstanceSettings.SleepMode.Active && + DateTime.TryParse((string)_tbotInstance.InstanceSettings.SleepMode.GoToSleep, out DateTime goToSleep) && + DateTime.TryParse((string)_tbotInstance.InstanceSettings.SleepMode.WakeUp, out DateTime wakeUp) && + !force +) +{ + DateTime time = await _tbotOgameBridge.GetDateTime(); + + if (GeneralHelper.ShouldSleep(time, goToSleep, wakeUp)) { + _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, + "Unable to send fleet: bed time has passed"); + return (int)SendFleetCode.AfterSleepTime; + } + + if (goToSleep >= wakeUp) wakeUp = wakeUp.AddDays(1); + if (goToSleep < time) goToSleep = goToSleep.AddDays(1); + if (wakeUp < time) wakeUp = wakeUp.AddDays(1); + + DateTime returnTime = time.AddSeconds(flightTime); + + if (returnTime >= goToSleep && returnTime <= wakeUp && !ignoreSleepForExpeditions) { + _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, + "Unable to send fleet: it would come back during sleep time"); + return (int)SendFleetCode.AfterSleepTime; + } +} _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); int slotsToLeaveFree = (int) _tbotInstance.InstanceSettings.General.SlotsToLeaveFree; if (_tbotInstance.UserData.slots.Free == 0) { @@ -481,11 +578,15 @@ public async Task SendFleet(Celestial origin, Ships ships, Coordinate desti payload.Deuterium = 0; if (payload.Food < 0) payload.Food = 0; - Fleet fleet = await _ogameService.SendFleet(origin, ships, destination, mission, speed, payload); - _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, "Fleet succesfully sent"); - _tbotInstance.UserData.fleets = await _ogameService.GetFleets(); + Fleet newFleet = await _ogameService.SendFleet(origin, ships, destination, mission, speed, payload); + if (newFleet == null) { + _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, $"Unable to send fleet from {origin.Coordinate.ToString()}: no ships to send."); + return (int) SendFleetCode.GenericError; + } + _tbotInstance.UserData.fleets.Add(newFleet); + _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, "Fleet sent successfully!"); _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); - return fleet.ID; + return newFleet.ID; } catch (Exception e) { _tbotInstance.log(LogLevel.Error, LogSender.FleetScheduler, $"Unable to send fleet: an exception has occurred: {e.Message}"); _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, $"Stacktrace: {e.StackTrace}"); @@ -498,14 +599,58 @@ public async Task SendFleet(Celestial origin, Ships ships, Coordinate desti } public async Task CancelFleet(Fleet fleet) { - //_tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"Recalling fleet id {fleet.ID} originally from {fleet.Origin.ToString()} to {fleet.Destination.ToString()} with mission: {fleet.Mission.ToString()}. Start time: {fleet.StartTime.ToString()} - Arrival time: {fleet.ArrivalTime.ToString()} - Ships: {fleet.Ships.ToString()}"); - _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); + try { + bool isUnderAttack = await _ogameService.IsUnderAttack(); + if (isUnderAttack) { + var attacks = await _ogameService.GetAttacks(); + var attacksOnOrigin = attacks + .Where(a => a.Destination.IsSame(fleet.Origin)) + .OrderByDescending(a => a.ArriveIn) + .ToList(); + + if (attacksOnOrigin.Any()) { + var lastAttack = attacksOnOrigin.First(); + var currentTime = await _tbotOgameBridge.GetDateTime(); + var currentFleet = _tbotInstance.UserData.fleets.SingleOrDefault(f => f.ID == fleet.ID); + if (currentFleet != null && currentFleet.ArriveIn > 0) { + long estimatedReturnSeconds = currentFleet.ArriveIn * 2; + + _tbotInstance.log(LogLevel.Debug, LogSender.FleetScheduler, $"[RECALL-CHECK] Fleet {fleet.ID} would return in ~{estimatedReturnSeconds}s if recalled now"); + _tbotInstance.log(LogLevel.Debug, LogSender.FleetScheduler, $"[RECALL-CHECK] Last attack on {fleet.Origin} arrives in {lastAttack.ArriveIn}s"); + if (estimatedReturnSeconds < lastAttack.ArriveIn) { + _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, $"[RECALL-BLOCKED] Fleet {fleet.ID} would return in {estimatedReturnSeconds}s but attack arrives in {lastAttack.ArriveIn}s - DELAYING RECALL!"); + + var newRecallDelay = (lastAttack.ArriveIn - estimatedReturnSeconds + (lastAttack.ArriveIn / 100 * 30)) * 1000; + if (newRecallDelay < 60000) newRecallDelay = 60000; + + _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"[RECALL-RESCHEDULED] New recall in {newRecallDelay/1000}s"); + + DisposeAndRemoveTimer($"RecallTimer-{fleet.ID.ToString()}"); + AddTimer($"RecallTimer-{fleet.ID.ToString()}", new Timer(RetireFleet, fleet, (int)newRecallDelay, Timeout.Infinite)); + + if ((bool) _tbotInstance.InstanceSettings.Defender.TelegramMessenger.Active) { + await _tbotInstance.SendTelegramMessage($"⚠️ Recall delayed! Attack still incoming on {fleet.Origin}. New recall in {newRecallDelay/1000}s"); + } + return; + } else { + _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"[RECALL-OK] Fleet {fleet.ID} return time ({estimatedReturnSeconds}s) is AFTER last attack ({lastAttack.ArriveIn}s) - proceeding with recall"); + } + } + } + } + } catch (Exception e) { + _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, $"[RECALL-CHECK] Error checking attacks before recall: {e.Message}"); + } + try { await Task.Delay((int) RandomizeHelper.CalcRandomInterval(IntervalType.AFewSeconds)); _tbotInstance.log(LogLevel.Debug, LogSender.FleetScheduler, $"Recall Fleet with ID: {fleet.ID}"); await _ogameService.CancelFleet(fleet); await Task.Delay((int) RandomizeHelper.CalcRandomInterval(IntervalType.AFewSeconds)); - _tbotInstance.UserData.fleets = await UpdateFleets(); + var updatedFleets = await UpdateFleets(); + lock (_fleetLock) { + _tbotInstance.UserData.fleets = updatedFleets; + } Fleet recalledFleet = _tbotInstance.UserData.fleets.SingleOrDefault(f => f.ID == fleet.ID) ?? new() { ID = (int) SendFleetCode.GenericError }; if (recalledFleet.ID == (int) SendFleetCode.GenericError) { _tbotInstance.log(LogLevel.Error, LogSender.FleetScheduler, "Unable to recall fleet: an unknon error has occurred, already recalled ?."); @@ -521,12 +666,8 @@ public async Task CancelFleet(Fleet fleet) { _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, $"Stacktrace: {e.StackTrace}"); return; } finally { - if (timers.TryGetValue($"RecallTimer-{fleet.ID.ToString()}", out Timer value)) { - value.Dispose(); - timers.Remove($"RecallTimer-{fleet.ID.ToString()}"); - } - - } + DisposeAndRemoveTimer($"RecallTimer-{fleet.ID.ToString()}"); +} } public async Task> UpdateFleets() { try { @@ -540,35 +681,40 @@ public async Task> UpdateFleets() { } public async void RetireFleet(object fleet) { - await CancelFleet((Fleet)fleet); + try { + await CancelFleet((Fleet)fleet); + } catch (Exception ex) { + _tbotInstance?.log(LogLevel.Error, LogSender.FleetScheduler, $"RetireFleet callback failed: {ex.Message}"); + } } private async void GhostandSleepAfterFleetsReturnAll(object state) { - if (timers.TryGetValue("GhostSleepTimer", out Timer value)) - value.Dispose(); - timers.Remove("GhostSleepTimer"); - + try { + DisposeAndRemoveTimer("GhostSleepTimer"); - var celestialsToFleetsave = await _tbotOgameBridge.UpdateCelestials(); - celestialsToFleetsave = celestialsToFleetsave.Where(c => c.Coordinate.Type == Celestials.Moon).ToList(); - if (celestialsToFleetsave.Count == 0) - celestialsToFleetsave = celestialsToFleetsave.Where(c => c.Coordinate.Type == Celestials.Planet).ToList(); + var celestialsToFleetsave = await _tbotOgameBridge.UpdateCelestials(); + var moons = celestialsToFleetsave.Where(c => c.Coordinate.Type == Celestials.Moon).ToList(); + var targets = moons.Count > 0 ? moons : celestialsToFleetsave.Where(c => c.Coordinate.Type == Celestials.Planet).ToList(); - foreach (Celestial celestial in celestialsToFleetsave) - await AutoFleetSave(celestial, false, _tbotInstance.SleepDuration, false, _tbotInstance.TelegramUserData.Mission, true); + foreach (Celestial celestial in targets) + await AutoFleetSave(celestial, false, _tbotInstance.SleepDuration, false, _tbotInstance.TelegramUserData.Mission, true); - await _tbotInstance.SleepNow(_tbotInstance.NextWakeUpTime); + await _tbotInstance.SleepNow(_tbotInstance.NextWakeUpTime); + } catch (Exception ex) { + _tbotInstance?.log(LogLevel.Error, LogSender.SleepMode, $"GhostSleep(all) timer callback failed: {ex.Message}"); + } } private async void GhostandSleepAfterFleetsReturn(object state) { - if (timers.TryGetValue("GhostSleepTimer", out Timer value)) - value.Dispose(); - timers.Remove("GhostSleepTimer"); - - await AutoFleetSave(_tbotInstance.TelegramUserData.CurrentCelestialToSave, false, _tbotInstance.SleepDuration, false, _tbotInstance.TelegramUserData.Mission, true); + try { + DisposeAndRemoveTimer("GhostSleepTimer"); - await _tbotInstance.SleepNow(_tbotInstance.NextWakeUpTime); + await AutoFleetSave(_tbotInstance.TelegramUserData.CurrentCelestialToSave, false, _tbotInstance.SleepDuration, false, _tbotInstance.TelegramUserData.Mission, true); + await _tbotInstance.SleepNow(_tbotInstance.NextWakeUpTime); + } catch (Exception ex) { + _tbotInstance?.log(LogLevel.Error, LogSender.SleepMode, $"GhostSleep timer callback failed: {ex.Message}"); + } } private bool CheckFuel(FleetHypotesis fleetHypotesis, Celestial celestial) { @@ -593,7 +739,49 @@ private async Task> GetFleetSaveDestination(List origin = await _tbotOgameBridge.UpdatePlanet(origin, UpdateTypes.LFBonuses); int sys = 0; + int playerid = _tbotInstance.UserData.userInfo.PlayerID; + switch (mission) { + case Missions.Transport: + for (sys = origin.Coordinate.System - 5; sys <= origin.Coordinate.System + 5; sys++) { + if (possibleDestinations.Count() == 10) { + break; + } + sys = GeneralHelper.ClampSystem(sys); + + var gi = await _ogameService.GetGalaxyInfo(origin.Coordinate.Galaxy, sys); + if (gi?.Planets == null) continue; + + foreach (var planet in gi.Planets) { + if (planet != null && !planet.Vacation && !planet.Administrator && !planet.Banned && !planet.StrongPlayer) { + possibleDestinations.Add(new(planet.Coordinate.Galaxy, planet.Coordinate.System, planet.Coordinate.Position, Celestials.Planet)); + } + } + } + + if (possibleDestinations.Count() > 0) { + foreach (var possibleDestination in possibleDestinations) { + foreach (var currentSpeed in validSpeeds) { + FleetPrediction fleetPrediction = _calcService.CalcFleetPrediction(origin.Coordinate, possibleDestination, origin.Ships.GetMovableShips(), mission, currentSpeed, _tbotInstance.UserData.researches, _tbotInstance.UserData.serverData, origin.LFBonuses, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.allianceClass); + + FleetHypotesis fleetHypotesis = new() { + Origin = origin, + Destination = possibleDestination, + Ships = origin.Ships.GetMovableShips(), + Mission = mission, + Speed = currentSpeed, + Duration = fleetPrediction.Time, + Fuel = fleetPrediction.Fuel + }; + if (fleetHypotesis.Duration >= minFlightTime / 2 && fleetHypotesis.Fuel <= maxFuel) { + possibleFleets.Add(fleetHypotesis); + break; + } + } + } + } + break; + case Missions.Spy: if (origin.Ships.EspionageProbe == 0) { _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"No espionageprobe available, skipping to next mission..."); @@ -630,15 +818,15 @@ private async Task> GetFleetSaveDestination(List galaxyInfo = await _ogameService.GetGalaxyInfo(origin.Coordinate); for (sys = origin.Coordinate.System - 5; sys <= origin.Coordinate.System + 5; sys++) { - int pos = 1; + int pos = 0; sys = GeneralHelper.ClampSystem(sys); galaxyInfo = await _ogameService.GetGalaxyInfo(origin.Coordinate.Galaxy, sys); foreach (var planet in galaxyInfo.Planets) { - if (planet == null) { - possibleDestinations.Add(new(origin.Coordinate.Galaxy, origin.Coordinate.System, pos)); - } - pos++; - } + pos++; + if (pos >= 1 && pos <= 15 && planet == null) { + possibleDestinations.Add(new(origin.Coordinate.Galaxy, sys, pos)); + } + } } if (possibleDestinations.Count() > 0) { @@ -669,7 +857,6 @@ private async Task> GetFleetSaveDestination(List _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"No recycler available, skipping to next mission..."); break; } - int playerid = _tbotInstance.UserData.userInfo.PlayerID; for (sys = origin.Coordinate.System - 5; sys <= origin.Coordinate.System + 5; sys++) { sys = GeneralHelper.ClampSystem(sys); galaxyInfo = await _ogameService.GetGalaxyInfo(origin.Coordinate.Galaxy, sys); @@ -751,7 +938,7 @@ private async Task> GetFleetSaveDestination(List } } - public async Task HandleMinerTransport(Celestial origin, Celestial destination, Resources resources, Buildables buildable = Buildables.Null, Buildings maxBuildings = null, Facilities maxFacilities = null, Facilities maxLunarFacilities = null, AutoMinerSettings autoMinerSettings = null) { + public async Task HandleMinerTransport(Celestial origin, Celestial destination, Celestial differentDestination, Resources resources, Buildables buildable = Buildables.Null, Buildings maxBuildings = null, Facilities maxFacilities = null, Facilities maxLunarFacilities = null, AutoMinerSettings autoMinerSettings = null) { try { if (origin.ID == destination.ID) { _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, "Skipping transport: origin and destination are the same."); @@ -762,8 +949,8 @@ public async Task HandleMinerTransport(Celestial origin, Celestial destinat } else { var missingResources = resources.Difference(destination.Resources); Resources resToLeave = new(0, 0, 0); - if ((long) _tbotInstance.InstanceSettings.Brain.Transports.DeutToLeave > 0) - resToLeave.Deuterium = (long) _tbotInstance.InstanceSettings.Brain.Transports.DeutToLeave; + if ((long) _tbotInstance.InstanceSettings.Brain.Transports.DeutToLeaveOnMoons > 0) + resToLeave.Deuterium = (long) _tbotInstance.InstanceSettings.Brain.Transports.DeutToLeaveOnMoons; origin = await _tbotOgameBridge.UpdatePlanet(origin, UpdateTypes.Resources); if (origin.Resources.IsEnoughFor(missingResources, resToLeave)) { @@ -783,7 +970,6 @@ public async Task HandleMinerTransport(Celestial origin, Celestial destinat tempShips.Add(preferredShip, 1); var flightPrediction = _calcService.CalcFleetPrediction(origin.Coordinate, destination.Coordinate, tempShips, Missions.Transport, Speeds.HundredPercent, _tbotInstance.UserData.researches, _tbotInstance.UserData.serverData, origin.LFBonuses, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.allianceClass); long flightTime = flightPrediction.Time; - idealShips = _calcService.CalcShipNumberForPayload(missingResources, preferredShip, _tbotInstance.UserData.researches.HyperspaceTechnology, _tbotInstance.UserData.serverData, cargoBonus, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.serverData.ProbeCargo); var availableShips = origin.Ships.GetAmount(preferredShip); if (buildable != Buildables.Null) { int level = _calcService.GetNextLevel(destination, buildable); @@ -836,8 +1022,19 @@ public async Task HandleMinerTransport(Celestial origin, Celestial destinat idealShips = _calcService.CalcShipNumberForPayload(missingResources, preferredShip, _tbotInstance.UserData.researches.HyperspaceTechnology, _tbotInstance.UserData.serverData, cargoBonus, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.serverData.ProbeCargo); } - if (idealShips <= origin.Ships.GetAmount(preferredShip)) { - ships.Add(preferredShip, idealShips); + bool doMultipleTransports = (idealShips > origin.Ships.GetAmount(preferredShip) && (bool) _tbotInstance.InstanceSettings.Brain.Transports.DoMultipleTransportIsNotEnoughShipButSamePosition) && + (origin.Coordinate.Galaxy == destination.Coordinate.Galaxy && + origin.Coordinate.System == destination.Coordinate.System && + origin.Coordinate.Position == destination.Coordinate.Position) ? + true: + false; + + if (idealShips <= origin.Ships.GetAmount(preferredShip) || (doMultipleTransports && !origin.Ships.IsEmpty())) { + if (doMultipleTransports) { + ships.Add(preferredShip, origin.Ships.GetAmount(preferredShip)); + } else { + ships.Add(preferredShip, idealShips); + } if (destination.Coordinate.Type == Celestials.Planet) { destination = await _tbotOgameBridge.UpdatePlanet(destination, UpdateTypes.ResourceSettings); @@ -862,13 +1059,17 @@ public async Task HandleMinerTransport(Celestial origin, Celestial destinat resources.Crystal > destination.ResourcesProduction.Crystal.StorageCapacity || resources.Deuterium > destination.ResourcesProduction.Deuterium.StorageCapacity ) { + if (differentDestination != null && differentDestination.ID != 0) + destination = differentDestination; _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"Sending {ships.ToString()} with {missingResources.TransportableResources} from {origin.ToString()} to {destination.ToString()}"); return await SendFleet(origin, ships, destination.Coordinate, Missions.Transport, Speeds.HundredPercent, missingResources, _tbotInstance.UserData.userInfo.Class); } else { _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, "Skipping transport: it is quicker to wait for production."); - return 0; + return (int) SendFleetCode.QuickerToWaitForProduction; } } else { + if (differentDestination != null && differentDestination.ID != 0) + destination = differentDestination; _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"Sending {ships.ToString()} with {missingResources.TransportableResources} from {origin.ToString()} to {destination.ToString()}"); return await SendFleet(origin, ships, destination.Coordinate, Missions.Transport, Speeds.HundredPercent, missingResources, _tbotInstance.UserData.userInfo.Class); } @@ -877,8 +1078,8 @@ public async Task HandleMinerTransport(Celestial origin, Celestial destinat return 0; } } else { - _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"Skipping transport: not enough resources in origin. Needed: {missingResources.TransportableResources} - Available: {origin.Resources.TransportableResources}"); - return 0; + _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"Skipping transport: not enough resources in {origin.ToString()}. Needed: {missingResources.TransportableResources} - Available: {origin.Resources.TransportableResources}"); + return (int) SendFleetCode.NotEnoughRessources; } } } catch (Exception e) { @@ -888,7 +1089,7 @@ public async Task HandleMinerTransport(Celestial origin, Celestial destinat } } - public async Task HandleMinerTransport(Celestial origin, Celestial destination, Resources resources, LFBuildables buildable = LFBuildables.None, LFBuildings maxLFBuildings = null, bool preventIfMoreExpensiveThanNextMine = false) { + public async Task HandleMinerTransport(Celestial origin, Celestial destination, Celestial differentDestination, Resources resources, LFBuildables buildable = LFBuildables.None, LFBuildings maxLFBuildings = null, bool preventIfMoreExpensiveThanNextMine = false) { try { if (origin.ID == destination.ID) { _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, "Skipping transport: origin and destination are the same."); @@ -899,8 +1100,8 @@ public async Task HandleMinerTransport(Celestial origin, Celestial destinat } else { var missingResources = resources.Difference(destination.Resources); Resources resToLeave = new(0, 0, 0); - if ((long) _tbotInstance.InstanceSettings.Brain.Transports.DeutToLeave > 0) - resToLeave.Deuterium = (long) _tbotInstance.InstanceSettings.Brain.Transports.DeutToLeave; + if ((long) _tbotInstance.InstanceSettings.Brain.Transports.DeutToLeaveOnMoons > 0) + resToLeave.Deuterium = (long) _tbotInstance.InstanceSettings.Brain.Transports.DeutToLeaveOnMoons; origin = await _tbotOgameBridge.UpdatePlanet(origin, UpdateTypes.Resources); if (origin.Resources.IsEnoughFor(missingResources, resToLeave)) { @@ -927,6 +1128,9 @@ public async Task HandleMinerTransport(Celestial origin, Celestial destinat idealShips = _calcService.CalcShipNumberForPayload(missingResources, preferredShip, _tbotInstance.UserData.researches.HyperspaceTechnology, _tbotInstance.UserData.serverData, cargoBonus, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.serverData.ProbeCargo); var availableShips = origin.Ships.GetAmount(preferredShip); if (buildable != LFBuildables.None) { + destination = await _tbotOgameBridge.UpdatePlanet(destination, UpdateTypes.LFBuildings); + destination = await _tbotOgameBridge.UpdatePlanet(destination, UpdateTypes.LFTechs); + destination = await _tbotOgameBridge.UpdatePlanet(destination, UpdateTypes.LFBonuses); int level = _calcService.GetNextLevel(destination, buildable); long buildTime = _calcService.CalcProductionTime(buildable, level, _tbotInstance.UserData.serverData, destination); var tempCelestial = destination; @@ -975,8 +1179,19 @@ public async Task HandleMinerTransport(Celestial origin, Celestial destinat idealShips = _calcService.CalcShipNumberForPayload(missingResources, preferredShip, _tbotInstance.UserData.researches.HyperspaceTechnology, _tbotInstance.UserData.serverData, cargoBonus, _tbotInstance.UserData.userInfo.Class, _tbotInstance.UserData.serverData.ProbeCargo); } - if (idealShips <= origin.Ships.GetAmount(preferredShip)) { - ships.Add(preferredShip, idealShips); + bool doMultipleTransports = (idealShips > origin.Ships.GetAmount(preferredShip) && (bool) _tbotInstance.InstanceSettings.Brain.Transports.DoMultipleTransportIsNotEnoughShipButSamePosition) && + (origin.Coordinate.Galaxy == destination.Coordinate.Galaxy && + origin.Coordinate.System == destination.Coordinate.System && + origin.Coordinate.Position == destination.Coordinate.Position) ? + true: + false; + + if (idealShips <= origin.Ships.GetAmount(preferredShip) || (doMultipleTransports && !origin.Ships.IsEmpty())) { + if (doMultipleTransports) { + ships.Add(preferredShip, origin.Ships.GetAmount(preferredShip)); + } else { + ships.Add(preferredShip, idealShips); + } if (destination.Coordinate.Type == Celestials.Planet) { destination = await _tbotOgameBridge.UpdatePlanet(destination, UpdateTypes.ResourceSettings); @@ -1001,6 +1216,8 @@ public async Task HandleMinerTransport(Celestial origin, Celestial destinat resources.Crystal > destination.ResourcesProduction.Crystal.StorageCapacity || resources.Deuterium > destination.ResourcesProduction.Deuterium.StorageCapacity ) { + if (differentDestination != null && differentDestination.ID != 0) + destination = differentDestination; _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"Sending {ships.ToString()} with {missingResources.TransportableResources} from {origin.ToString()} to {destination.ToString()}"); return await SendFleet(origin, ships, destination.Coordinate, Missions.Transport, Speeds.HundredPercent, missingResources, _tbotInstance.UserData.userInfo.Class); } else { @@ -1008,6 +1225,8 @@ public async Task HandleMinerTransport(Celestial origin, Celestial destinat return (int) SendFleetCode.QuickerToWaitForProduction; } } else { + if (differentDestination != null && differentDestination.ID != 0) + destination = differentDestination; _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"Sending {ships.ToString()} with {missingResources.TransportableResources} from {origin.ToString()} to {destination.ToString()}"); return await SendFleet(origin, ships, destination.Coordinate, Missions.Transport, Speeds.HundredPercent, missingResources, _tbotInstance.UserData.userInfo.Class); } @@ -1016,8 +1235,8 @@ public async Task HandleMinerTransport(Celestial origin, Celestial destinat return 0; } } else { - _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"Skipping transport: not enough resources in origin. Needed: {missingResources.TransportableResources} - Available: {origin.Resources.TransportableResources}"); - return 0; + _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"Skipping transport: not enough resources in {origin.ToString()}. Needed: {missingResources.TransportableResources} - Available: {origin.Resources.TransportableResources}"); + return (int) SendFleetCode.NotEnoughRessources; } } } catch (Exception e) { @@ -1027,8 +1246,8 @@ public async Task HandleMinerTransport(Celestial origin, Celestial destinat } } - public async Task Collect() { - await CollectImpl(true); + public async Task Collect(bool noLimit = false) { + await CollectImpl(true, noLimit); } public async Task CollectDeut(long MinAmount = 0) { @@ -1114,30 +1333,51 @@ public async Task CollectDeut(long MinAmount = 0) { } } - public async Task CollectImpl(bool fromTelegram) { - bool stop = false; - bool delay = false; + public async Task CollectImpl(bool fromTelegram, bool noLimit = false) { try { _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, "Repatriating resources..."); if (fromTelegram) { _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"Telegram collect initated.."); } - if (_tbotInstance.InstanceSettings.Brain.AutoRepatriate.Target) { + + List tempCelestials = new(); + if (_tbotInstance.InstanceSettings.Brain.AutoRepatriate.Target.Length > 0) { + tempCelestials = _calcService.ParseCelestialsList(_tbotInstance.InstanceSettings.Brain.AutoRepatriate.Target, _tbotInstance.UserData.celestials); + } else { + _tbotInstance.log(LogLevel.Warning, LogSender.FleetScheduler, "Skipping autorepatriate: no custom destination"); + return RepatriateCode.Failure; + } + if (tempCelestials.Count() > 0) { _tbotInstance.UserData.fleets = await UpdateFleets(); long TotalMet = 0; long TotalCri = 0; long TotalDeut = 0; - Coordinate destinationCoordinate = new( - (int) _tbotInstance.InstanceSettings.Brain.AutoRepatriate.Target.Galaxy, - (int) _tbotInstance.InstanceSettings.Brain.AutoRepatriate.Target.System, - (int) _tbotInstance.InstanceSettings.Brain.AutoRepatriate.Target.Position, - Enum.Parse((string) _tbotInstance.InstanceSettings.Brain.AutoRepatriate.Target.Type) - ); + bool samePosition = (bool) _tbotInstance.InstanceSettings.Brain.AutoRepatriate.TargetAssociateMoon; List newCelestials = _tbotInstance.UserData.celestials.ToList(); List celestialsToExclude = _calcService.ParseCelestialsList(_tbotInstance.InstanceSettings.Brain.AutoRepatriate.Exclude, _tbotInstance.UserData.celestials); - - foreach (Celestial celestial in (bool) _tbotInstance.InstanceSettings.Brain.AutoRepatriate.RandomOrder ? _tbotInstance.UserData.celestials.Shuffle().ToList() : _tbotInstance.UserData.celestials.OrderBy(c => _calcService.CalcDistance(c.Coordinate, destinationCoordinate, _tbotInstance.UserData.serverData)).ToList()) { + List celestialList = _tbotInstance.UserData.celestials.ToList(); + + celestialList = (bool) _tbotInstance.InstanceSettings.Brain.AutoRepatriate.RandomOrder ? celestialList.Shuffle().ToList() : celestialList.ToList(); + + foreach (Celestial celestial in celestialList) { + List closestCelestials = tempCelestials + .OrderBy(c => _calcService.CalcDistance(c.Coordinate, celestial.Coordinate, _tbotInstance.UserData.serverData)).ToList(); + Coordinate destinationCoordinate = new(); + if (samePosition) { + if (celestial.Coordinate.Type == Celestials.Planet && !_calcService.IsThereMoonHere(_tbotInstance.UserData.celestials, celestial)) { + _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"Skipping {celestial.ToString()}: There is no moon."); + continue; + } + destinationCoordinate = new( + (int) celestial.Coordinate.Galaxy, + (int) celestial.Coordinate.System, + (int) celestial.Coordinate.Position, + celestial.Coordinate.Type == Celestials.Planet ? Celestials.Moon : Celestials.Planet + ); + } else { + destinationCoordinate = closestCelestials.First().Coordinate; + } if (celestialsToExclude.Has(celestial)) { _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"Skipping {celestial.ToString()}: celestial in exclude list."); continue; @@ -1179,7 +1419,7 @@ public async Task CollectImpl(bool fromTelegram) { } } - if (payload.TotalResources < (long) _tbotInstance.InstanceSettings.Brain.AutoRepatriate.MinimumResources || payload.IsEmpty()) { + if ((!noLimit && payload.TotalResources < (long) _tbotInstance.InstanceSettings.Brain.AutoRepatriate.MinimumResources) || payload.IsEmpty()) { _tbotInstance.log(LogLevel.Information, LogSender.FleetScheduler, $"Skipping {tempCelestial.ToString()}: resources under set limit"); continue; } @@ -1200,11 +1440,9 @@ public async Task CollectImpl(bool fromTelegram) { if (payload.TotalResources > 0) { var fleetId = await SendFleet(tempCelestial, ships, destinationCoordinate, Missions.Transport, Speeds.HundredPercent, payload); if (fleetId == (int) SendFleetCode.AfterSleepTime) { - stop = true; return RepatriateCode.Stop; } if (fleetId == (int) SendFleetCode.NotEnoughSlots) { - delay = true; return RepatriateCode.Delay; } TotalMet += payload.Metal; @@ -1219,7 +1457,6 @@ public async Task CollectImpl(bool fromTelegram) { newCelestials.Add(tempCelestial); } _tbotInstance.UserData.celestials = newCelestials; - //send notif only if sent via telegram if (fromTelegram) { if ((TotalMet > 0) || (TotalCri > 0) || (TotalDeut > 0)) { await _tbotInstance.SendTelegramMessage($"Resources sent!:\n{TotalMet} Metal\n{TotalCri} Crystal\n{TotalDeut} Deuterium"); @@ -1238,5 +1475,8 @@ public async Task CollectImpl(bool fromTelegram) { return RepatriateCode.Failure; } } - } + + + } +} \ No newline at end of file diff --git a/TBot/Workers/HarvestWorker.cs b/TBot/Workers/HarvestWorker.cs index ba70ca2d..93c0464c 100644 --- a/TBot/Workers/HarvestWorker.cs +++ b/TBot/Workers/HarvestWorker.cs @@ -141,10 +141,54 @@ protected override async Task Execute() { newCelestials.Add(tempCelestial); } _tbotInstance.UserData.celestials = newCelestials; + _tbotInstance.UserData.slots = await _tbotOgameBridge.UpdateSlots(); + _tbotInstance.UserData.fleets = await _fleetScheduler.UpdateFleets(); + List rankSlotsPriority = new() { + new RankSlotsPriority(Feature.BrainAutoMine, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Brain, + ((bool) _tbotInstance.InstanceSettings.Brain.Active && (bool) _tbotInstance.InstanceSettings.Brain.Transports.Active && ((bool) _tbotInstance.InstanceSettings.Brain.AutoMine.Active || (bool) _tbotInstance.InstanceSettings.Brain.AutoResearch.Active || (bool) _tbotInstance.InstanceSettings.Brain.LifeformAutoMine.Active || (bool) _tbotInstance.InstanceSettings.Brain.LifeformAutoResearch.Active)), + (int) _tbotInstance.InstanceSettings.Brain.Transports.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Transport)), + new RankSlotsPriority(Feature.Expeditions, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Expeditions, + (bool) _tbotInstance.InstanceSettings.Expeditions.Active, + (int) _tbotInstance.UserData.slots.ExpTotal, + (int)_tbotInstance.UserData.slots.ExpInUse), + new RankSlotsPriority(Feature.AutoFarm, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoFarm, + (bool) _tbotInstance.InstanceSettings.AutoFarm.Active, + (int) _tbotInstance.InstanceSettings.AutoFarm.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Attack)), + new RankSlotsPriority(Feature.Colonize, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.Colonize, + (bool) _tbotInstance.InstanceSettings.AutoColonize.Active, + (bool) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.Active ? + (int) _tbotInstance.InstanceSettings.AutoColonize.IntensiveResearch.MaxSlots : + 1, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Colonize)), + new RankSlotsPriority(Feature.AutoDiscovery, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoDiscovery, + (bool) _tbotInstance.InstanceSettings.AutoDiscovery.Active, + (int) _tbotInstance.InstanceSettings.AutoDiscovery.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Discovery)), + new RankSlotsPriority(Feature.Harvest, + (int) _tbotInstance.InstanceSettings.General.SlotPriorityLevel.AutoHarvest, + (bool) _tbotInstance.InstanceSettings.AutoHarvest.Active, + (int) _tbotInstance.InstanceSettings.AutoHarvest.MaxSlots, + (int) _tbotInstance.UserData.fleets.Count(f => f.Mission == Missions.Harvest)) + }; + int MaxSlots = _calculationService.CalcSlotsPriority(Feature.Harvest, rankSlotsPriority, _tbotInstance.UserData.slots, _tbotInstance.UserData.fleets, (int) _tbotInstance.InstanceSettings.General.SlotsToLeaveFree); if (dic.Count() == 0) DoLog(LogLevel.Information, "Skipping harvest: there are no fields to harvest."); + if (dic.Count() > MaxSlots) { + var pairsToRemove = dic.Take(dic.Count() - MaxSlots); + foreach (var pair in pairsToRemove) { + dic.Remove(pair.Key); + } + } + foreach (Coordinate destination in dic.Keys) { var fleetId = (int) SendFleetCode.GenericError; Celestial origin = dic[destination]; diff --git a/TBot/Workers/TBotOgamedBridge.cs b/TBot/Workers/TBotOgamedBridge.cs index 7798c252..25c23fe3 100644 --- a/TBot/Workers/TBotOgamedBridge.cs +++ b/TBot/Workers/TBotOgamedBridge.cs @@ -130,7 +130,7 @@ public async Task UpdatePlanet(Celestial planet, UpdateTypes UpdateTy public async Task> UpdatePlanets(UpdateTypes UpdateTypes = UpdateTypes.Full) { // log(LogLevel.Information, LogSender.Tbot, $"Updating userData.celestials... Mode: {UpdateTypes.ToString()}"); - List localPlanets = await GetPlanets(); + List localPlanets = (await GetPlanets()).ToList(); List newPlanets = new(); try { foreach (Celestial planet in localPlanets) { @@ -239,7 +239,7 @@ public async Task UpdateUserInfo() { public async Task> UpdateCelestials() { try { - return await _ogameService.GetCelestials(); + return (await _ogameService.GetCelestials()).ToList(); } catch (Exception e) { _tbotInstance.log(LogLevel.Debug, LogSender.Tbot, $"UpdateCelestials() Exception: {e.Message}"); _tbotInstance.log(LogLevel.Warning, LogSender.Tbot, $"Stacktrace: {e.StackTrace}"); diff --git a/TBot/Workers/WorkerFactory.cs b/TBot/Workers/WorkerFactory.cs index c2f24866..36e363a1 100644 --- a/TBot/Workers/WorkerFactory.cs +++ b/TBot/Workers/WorkerFactory.cs @@ -42,6 +42,7 @@ public ITBotWorker InitializeWorker(Feature feat, ITBotMain tbotMainInstance, IT ITBotWorker newWorker = feat switch { Feature.Defender => new DefenderWorker(tbotMainInstance, _ogameService, _fleetScheduler, tbotOgameBridge), Feature.BrainAutobuildCargo => new AutoCargoWorker(tbotMainInstance, _ogameService, _fleetScheduler, _calculationService, tbotOgameBridge), + Feature.BrainAutobuildDefence => new AutoDefenceWorker(tbotMainInstance, _ogameService, _fleetScheduler, _calculationService, tbotOgameBridge), Feature.BrainAutoRepatriate => new AutoRepatriateWorker(tbotMainInstance, _fleetScheduler, _calculationService, tbotOgameBridge), Feature.BrainAutoMine => new AutoMineWorker(tbotMainInstance, _ogameService, _fleetScheduler, _calculationService, tbotOgameBridge, this), Feature.BrainOfferOfTheDay => new BuyOfferOfTheDayWorker(tbotMainInstance, _ogameService, tbotOgameBridge), @@ -105,6 +106,7 @@ public ITBotCelestialWorker GetCelestialWorker(ITBotWorker parentWorker, Celesti private bool IsBrain(Feature feat) { switch (feat) { case Feature.BrainAutobuildCargo: + case Feature.BrainAutobuildDefence: case Feature.BrainAutoRepatriate: case Feature.BrainAutoMine: case Feature.BrainOfferOfTheDay: diff --git a/TBot/instance_settings.json b/TBot/instance_settings.json index 74cdb7e7..6f77812b 100644 --- a/TBot/instance_settings.json +++ b/TBot/instance_settings.json @@ -21,7 +21,7 @@ "Height": 1080, "Timezone": "Europe/London", "Lang": "en" - } + } }, "General": { "Host": "localhost", @@ -36,7 +36,15 @@ }, "CaptchaAPIKey": "", "CustomTitle": "", - "SlotsToLeaveFree": 1 + "SlotsToLeaveFree": 1, + "SlotPriorityLevel": { + "Brain": 2, + "Expeditions": 1, + "AutoFarm": 3, + "AutoColonize": 4, + "AutoDiscovery": 10, + "AutoHarvest": 99 + } }, "SleepMode": { "Active": false, @@ -110,13 +118,31 @@ "Transports": { "Active": true, "CargoType": "SmallCargo", - "DeutToLeave": 1000000, + "DeutToLeaveOnMoons": 1000000, "RoundResources": true, + "SendToTheMoonIfPossible": false, "Origin": { "Galaxy": 3, "System": 333, "Position": 8, "Type": "Moon" + }, + "MaxSlots": 10, + "CheckMoonOrPlanetFirst": false, + "DoMultipleTransportIsNotEnoughShipButSamePosition": false, + "MultipleOrigins": { + "Active": false, + "OnlyFromMoons": true, + "MinimumResourcesToSend": 10000000, + "PriorityToProximityOverQuantity": false, + "Exclude": [ + { + "Galaxy": 1, + "System": 1, + "Position": 1, + "Type": "Planet" + } + ] } }, "AutoMine": { @@ -195,40 +221,33 @@ "CheckIntervalMax": 60 }, "LifeformAutoResearch": { - "Active": true, - "Transports": { - "Active": true - }, - "MaxResearchLevel": 6, - "MaxTechs11": 6, - "MaxTechs12": 6, - "MaxTechs13": 6, - "MaxTechs14": 6, - "MaxTechs15": 6, - "MaxTechs16": 6, - "MaxTechs21": 6, - "MaxTechs22": 6, - "MaxTechs23": 6, - "MaxTechs24": 6, - "MaxTechs25": 6, - "MaxTechs26": 6, - "MaxTechs31": 6, - "MaxTechs32": 6, - "MaxTechs33": 6, - "MaxTechs34": 6, - "MaxTechs35": 6, - "MaxTechs36": 6, - "Exclude": [ - { - "Galaxy": 3, - "System": 333, - "Position": 15, - "Type": "Planet" - } - ], - "CheckIntervalMin": 30, - "CheckIntervalMax": 60 - }, + "Active": false, + "Transports": { + "Active": true + }, + "MaxResearchLevel": 15, + "MaxTechs11": 15, + "MaxTechs12": 13, + "MaxTechs13": 12, + "MaxTechs14": 12, + "MaxTechs15": 15, + "MaxTechs16": 9, + "MaxTechs21": 9, + "MaxTechs22": 9, + "MaxTechs23": 5, + "MaxTechs24": 8, + "MaxTechs25": 14, + "MaxTechs26": 9, + "MaxTechs31": 0, + "MaxTechs32": 0, + "MaxTechs33": 0, + "MaxTechs34": 0, + "MaxTechs35": 0, + "MaxTechs36": 0, + "Exclude": [], + "CheckIntervalMin": 60, + "CheckIntervalMax": 120 + }, "AutoResearch": { "Active": true, "Transports": { @@ -261,6 +280,7 @@ "PrioritizePlasmaTechnology": true, "PrioritizeEnergyTechnology": true, "PrioritizeIntergalacticResearchNetwork": true, + "ForceResearchWhateverTheLabLevel": false, "CheckIntervalMin": 10, "CheckIntervalMax": 20 }, @@ -284,6 +304,29 @@ "CheckIntervalMin": 60, "CheckIntervalMax": 240 }, + "AutoDefence": { + "Active": false, + "ExcludeMoons": true, + "RandomOrder": true, + "DefenceToReach": { + "RocketLauncher": 100000, + "HeavyLaser": 10000, + "GaussCannon": 1000, + "PlasmaTurret": 1000, + "SmallShieldDome": true, + "LargeShieldDome": true + }, + "Exclude": [ + { + "Galaxy": 3, + "System": 333, + "Position": 15, + "Type": "Moon" + } + ], + "CheckIntervalMin": 60, + "CheckIntervalMax": 240 + }, "AutoRepatriate": { "Active": true, "ExcludeMoons": false, @@ -292,34 +335,31 @@ "OnlyOnMoons": true, "DeutToLeave": 1000000 }, - "Target": { - "Galaxy": 5, - "System": 63, - "Position": 8, - "Type": "Moon" - }, - "CargoType": "LargeCargo", - "RandomOrder": true, - "SkipIfIncomingTransport": true, - "Exclude": [ + "Target": [ { - "Galaxy": 3, - "System": 333, - "Position": 15, - "Type": "Planet" + "Galaxy": 5, + "System": 63, + "Position": 8, + "Type": "Moon" } ], - "CheckIntervalMin": 30, - "CheckIntervalMax": 60 + "TargetAssociateMoon": false, + "CargoType": "LargeCargo", + "RandomOrder": false, + "SkipIfIncomingTransport": true, + "Exclude": [], + "CheckIntervalMin": 3000, + "CheckIntervalMax": 6000 }, - "BuyOfferOfTheDay": { - "Active": true, - "CheckIntervalMin": 240, - "CheckIntervalMax": 360 - } - }, + "BuyOfferOfTheDay": { + "Active": false, + "CheckIntervalMin": 240, + "CheckIntervalMax": 360 + } + }, "Expeditions": { "Active": true, + "IgnoreSleep": false, "MinWaitNextFleet": 5, "MaxWaitNextFleet": 15, "PrimaryShip": "LargeCargo", @@ -364,29 +404,29 @@ ] }, "AutoFarm": { - "Active": true, + "Active": false, "ExcludeMoons": true, "ScanRange": [ { - "Galaxy": 2, - "StartSystem": 360, - "EndSystem": 420 + "Galaxy": 5, + "StartSystem": 199, + "EndSystem": 499 } ], "Exclude": [ { - "Galaxy": 3, - "System": 3, - "Planet": 3 + "Galaxy": 1, + "System": 1, + "Planet": 1 } ], - "KeepReportFor": 180, - "NumProbes": 10, + "KeepReportFor": 1440, + "NumProbes": 52, "Origin": [ { - "Galaxy": 2, - "System": 396, - "Position": 4, + "Galaxy": 5, + "System": 313, + "Position": 8, "Type": "Planet" } ], @@ -412,9 +452,10 @@ "AutoHarvest": { "Active": false, "HarvestOwnDF": true, - "HarvestDeepSpace": true, + "HarvestDeepSpace": false, "MinimumResourcesOwnDF": 300, "MinimumResourcesDeepSpace": 50000, + "MaxSlots": 5, "CheckIntervalMin": 30, "CheckIntervalMax": 60 }, @@ -457,12 +498,12 @@ "MaxPlanets": 2 } ], - "RandomPosition": true, - "CheckIntervalMin": 30, - "CheckIntervalMax": 60 + "RandomPosition": false, + "CheckIntervalMin": 900, + "CheckIntervalMax": 1200 }, "AutoDiscovery": { - "Active": true, + "Active": false, "Origin": { "Galaxy": 5, "System": 64, @@ -471,6 +512,7 @@ }, "MaxSlots": 5, "MaxFailures": 5, + "RandomizeDestination": false, "CheckIntervalMin": 30, "CheckIntervalMax": 60 } diff --git a/ogame b/ogame index e46c1828..a7eed63e 160000 --- a/ogame +++ b/ogame @@ -1 +1 @@ -Subproject commit e46c1828cc1784e70102304173e44c4cf0a50dd8 +Subproject commit a7eed63e62de4e7092a97d7a786221a0abc15559