diff --git a/distributed-builder.ank b/distributed-builder.ank new file mode 100644 index 0000000..c05c7b4 --- /dev/null +++ b/distributed-builder.ank @@ -0,0 +1,440 @@ +// ============================================================================= +// OGame Ninja Multi-Planet Ship Builder Script +// ============================================================================= +// This script automates building ships on multiple planets, importing resources +// from a master planet when needed, and parking ships on the main planet. +// ============================================================================= + +// ========================= CONFIGURATION ========================= + +// Target ship quantities - using NewShipsInfos() and Set() method +TARGET_SHIPS = NewShipsInfos() +TARGET_SHIPS.Set(LIGHTFIGHTER, 20000) // Light Fighters +TARGET_SHIPS.Set(HEAVYFIGHTER, 15000) // Heavy Fighters +TARGET_SHIPS.Set(CRUISER, 2000) // Cruisers +TARGET_SHIPS.Set(BATTLESHIP, 1000) // Battleships +TARGET_SHIPS.Set(LARGECARGO, 3000) // Large Cargo +TARGET_SHIPS.Set(SMALLCARGO, 3000) // Small Cargo +TARGET_SHIPS.Set(RECYCLER, 500) // Recyclers +TARGET_SHIPS.Set(ESPIONAGEPROBE, 0) // Espionage Probes + +// List of planets to build ships on +BUILDER_PLANETS = ["1:2:1", "1:2:2", "1:2:3", "1:2:4", "1:2:5", "1:2:6", "1:2:7"] + +// *** FIX: Manually define an array of ship IDs to iterate over *** +SHIP_IDS_TO_BUILD = [LIGHTFIGHTER, HEAVYFIGHTER, CRUISER, BATTLESHIP, LARGECARGO, SMALLCARGO, RECYCLER, ESPIONAGEPROBE] + +// Master planet for resource imports +MASTER_PLANET = "2:2:14" + +// Main planet where to park all ships +MAIN_PLANET = "2:2:14" + +// Resource threshold - minimum resources to keep on master planet +RESOURCE_THRESHOLD = NewResources(1000000, 500000, 100000) + +// Minimum resources needed before attempting ship building +MIN_RESOURCES_TO_BUILD = NewResources(100000, 50000, 10000) + +// Building check interval (milliseconds) +CHECK_INTERVAL = 5 * 60 * 1000 + +// Fleet parking interval (milliseconds) +PARKING_INTERVAL = 30 * 60 * 1000 + +// Transport fleet configuration +TRANSPORT_SHIPS = NewShipsInfos() +TRANSPORT_SHIPS.Set(LARGECARGO, 100) + +// ========================= GLOBAL VARIABLES ========================= + +lastParkingCheck = 0 + +// ========================= MAIN FUNCTIONS ========================= + +func main() { + LogInfo("=== Multi-Planet Ship Builder Started ===") + LogInfof("Builder planets count: %d", len(BUILDER_PLANETS)) + LogInfof("Master planet: %s", MASTER_PLANET) + LogInfof("Main planet: %s", MAIN_PLANET) + + // Main loop + for { + LogInfo("--- Starting build cycle ---") + + // Check each builder planet + for i = 0; i < len(BUILDER_PLANETS); i++ { + planetCoord = BUILDER_PLANETS[i] + processPlanet(planetCoord) + Sleep(Random(2000, 5000)) + } + + // Check if it's time to park fleets + currentTime = GetTimestamp() + if currentTime - lastParkingCheck > PARKING_INTERVAL / 1000 { + parkAllFleets() + lastParkingCheck = currentTime + } + + // Print status at the end of each cycle + printStatus() + + LogInfo("--- Build cycle complete, sleeping ---") + Sleep(CHECK_INTERVAL) + } +} + +func processPlanet(planetCoord) { + LogInfof("Processing planet: %s", planetCoord) + + planet, _ = GetPlanet(planetCoord) + if planet == nil { + LogErrorf("Could not get planet %s", planetCoord) + return + } + + // Check if planet has shipyard + facilities, _ = planet.GetFacilities() + if facilities.Shipyard == 0 { + LogWarnf("Planet %s has no shipyard", planetCoord) + return + } + + // Check if shipyard is busy with buildings or ships + bldgID, shipsCount, _ = planet.GetProduction() + if bldgID != 0 { + LogInfof("Shipyard is blocked by building construction on %s", planetCoord) + return + } + if shipsCount > 0 { + LogInfof("Shipyard already building ships on %s", planetCoord) + return + } + + // Get current ships on planet + currentShips, _ = planet.GetShips() + + // Check what ships need to be built + shipsToBuild = calculateShipsNeeded(currentShips) + + // Check if any ships are needed + totalNeeded = 0 + for i = 0; i < len(SHIP_IDS_TO_BUILD); i++ { + shipID = SHIP_IDS_TO_BUILD[i] + totalNeeded = totalNeeded + shipsToBuild.ByID(shipID) + } + + if totalNeeded == 0 { + LogInfof("Planet %s - No ships needed", planetCoord) + return + } + + // Check if we have enough resources + resources, _ = planet.GetResources() + requiredResources = calculateRequiredResources(shipsToBuild) + + neededMetal = 0 + neededCrystal = 0 + neededDeut = 0 + if requiredResources.Metal > resources.Metal { neededMetal = requiredResources.Metal - resources.Metal } + if requiredResources.Crystal > resources.Crystal { neededCrystal = requiredResources.Crystal - resources.Crystal } + if requiredResources.Deuterium > resources.Deuterium { neededDeut = requiredResources.Deuterium - resources.Deuterium } + + // If we need resources, our only job this cycle is to try and get them. + if neededMetal > 0 || neededCrystal > 0 || neededDeut > 0 { + LogInfof("Planet %s needs resources. Required: M:%d C:%d D:%d", planetCoord, neededMetal, neededCrystal, neededDeut) + + fleets, slots = GetFleets() + isReceivingTransport = false + for i = 0; i < len(fleets); i++ { + f = fleets[i] + if f.Mission == TRANSPORT && f.Destination.String() == planetCoord { + isReceivingTransport = true + break + } + } + + if isReceivingTransport { + LogInfof("Waiting for incoming resources on %s.", planetCoord) + } else { + if len(fleets) >= slots.Total { + LogWarnf("No free fleet slot to import resources to %s.", planetCoord) + } else { + missingResources = NewResources(neededMetal, neededCrystal, neededDeut) + LogInfo("Requesting resource import from master planet.") + importResources(planetCoord, missingResources) + } + } + return // End this planet's turn. We either sent res, are waiting, or couldn't send. + } + + // If we've reached this point, we have enough resources and can try to build. + LogInfo("Sufficient resources found. Attempting to build ships.") + buildShips(planet, shipsToBuild, resources) +} + +func calculateShipsNeeded(currentShips) { + needed = NewShipsInfos() + for i = 0; i < len(SHIP_IDS_TO_BUILD); i++ { + shipID = SHIP_IDS_TO_BUILD[i] + targetAmount = TARGET_SHIPS.ByID(shipID) + currentAmount = currentShips.ByID(shipID) + if currentAmount < targetAmount { + stillNeeded = targetAmount - currentAmount + buildBatch = getBuildBatchSize(shipID, stillNeeded) + if buildBatch > 0 { + needed.Set(shipID, buildBatch) + } + } + } + return needed +} + +func getBuildBatchSize(shipID, stillNeeded) { + batchSize = 0 + switch shipID { + case LIGHTFIGHTER: + batchSize = 200 + case HEAVYFIGHTER: + batchSize = 150 + case CRUISER: + batchSize = 50 + case BATTLESHIP: + batchSize = 25 + case LARGECARGO: + batchSize = 100 + case SMALLCARGO: + batchSize = 100 + case RECYCLER: + batchSize = 20 + case ESPIONAGEPROBE: + batchSize = 0 + default: + batchSize = 10 + } + + if stillNeeded < batchSize { + return stillNeeded + } + return batchSize +} + +func calculateRequiredResources(ships) { + totalMetal = 0 + totalCrystal = 0 + totalDeut = 0 + for i = 0; i < len(SHIP_IDS_TO_BUILD); i++ { + shipID = SHIP_IDS_TO_BUILD[i] + quantity = ships.ByID(shipID) + if quantity > 0 { + cost = GetPrice(shipID, quantity) + totalMetal += cost.Metal + totalCrystal += cost.Crystal + totalDeut += cost.Deuterium + } + } + return NewResources(totalMetal, totalCrystal, totalDeut) +} + +func importResources(targetPlanetCoord, neededResources) { + LogInfof("Importing resources to %s", targetPlanetCoord) + masterPlanet, _ = GetPlanet(MASTER_PLANET) + if masterPlanet == nil { + LogErrorf("Could not get master planet %s", MASTER_PLANET) + return + } + masterResources, _ = masterPlanet.GetResources() + + availableMetal = 0 + availableCrystal = 0 + availableDeut = 0 + if masterResources.Metal > RESOURCE_THRESHOLD.Metal { availableMetal = masterResources.Metal - RESOURCE_THRESHOLD.Metal } + if masterResources.Crystal > RESOURCE_THRESHOLD.Crystal { availableCrystal = masterResources.Crystal - RESOURCE_THRESHOLD.Crystal } + if masterResources.Deuterium > RESOURCE_THRESHOLD.Deuterium { availableDeut = masterResources.Deuterium - RESOURCE_THRESHOLD.Deuterium } + + toSendMetal = neededResources.Metal + toSendCrystal = neededResources.Crystal + toSendDeut = neededResources.Deuterium + if toSendMetal > availableMetal { toSendMetal = availableMetal } + if toSendCrystal > availableCrystal { toSendCrystal = availableCrystal } + if toSendDeut > availableDeut { toSendDeut = availableDeut } + + if toSendMetal <= 0 && toSendCrystal <= 0 && toSendDeut <= 0 { + LogInfo("Master planet doesn't have enough resources to send") + return + } + + LogInfof("Sending: M:%d C:%d D:%d", toSendMetal, toSendCrystal, toSendDeut) + fleet = NewFleet() + fleet.SetOrigin(MASTER_PLANET) + fleet.SetDestination(targetPlanetCoord) + fleet.SetSpeed(HUNDRED_PERCENT) + fleet.SetMission(TRANSPORT) + fleet.SetShips(*TRANSPORT_SHIPS) + fleet.SetResources(NewResources(toSendMetal, toSendCrystal, toSendDeut)) + _, err = fleet.SendNow() + if err != nil { + LogErrorf("Failed to send transport: %s", err) + } else { + LogInfof("Transport sent to %s", targetPlanetCoord) + } +} + +func buildShips(planet, shipsToBuild, resources) { + coordinate, _ = planet.GetCoordinate() + LogInfof("Building ships on %s", coordinate) + + availableMetal = resources.Metal + availableCrystal = resources.Crystal + availableDeuterium = resources.Deuterium + + priorityOrder = [LARGECARGO, LIGHTFIGHTER, HEAVYFIGHTER, CRUISER, BATTLESHIP, SMALLCARGO, RECYCLER] + for i = 0; i < len(priorityOrder); i++ { + shipID = priorityOrder[i] + quantity = shipsToBuild.ByID(shipID) + if quantity > 0 { + cost = GetPrice(shipID, quantity) + if availableMetal >= cost.Metal && availableCrystal >= cost.Crystal && availableDeuterium >= cost.Deuterium { + err = planet.BuildShips(shipID, quantity) + if err != nil { + LogErrorf("Failed to build ships: %s", err) + } else { + LogInfof("Building %d %s", quantity, ID2Str(shipID)) + availableMetal -= cost.Metal + availableCrystal -= cost.Crystal + availableDeuterium -= cost.Deuterium + } + } else { + LogInfof("Not enough resources for %d %s", quantity, ID2Str(shipID)) + } + } + } +} + +func parkAllFleets() { + LogInfo("=== Parking fleets on main planet ===") + for i = 0; i < len(BUILDER_PLANETS); i++ { + planetCoord = BUILDER_PLANETS[i] + if planetCoord == MAIN_PLANET { continue } + planet, _ = GetPlanet(planetCoord) + if planet == nil { continue } + ships, _ = planet.GetShips() + + shipsToPark = NewShipsInfos() + if ships.ByID(LIGHTFIGHTER) > 100 { shipsToPark.Set(LIGHTFIGHTER, ships.ByID(LIGHTFIGHTER) - 100) } + if ships.ByID(HEAVYFIGHTER) > 50 { shipsToPark.Set(HEAVYFIGHTER, ships.ByID(HEAVYFIGHTER) - 50) } + if ships.ByID(CRUISER) > 0 { shipsToPark.Set(CRUISER, ships.ByID(CRUISER)) } + if ships.ByID(BATTLESHIP) > 0 { shipsToPark.Set(BATTLESHIP, ships.ByID(BATTLESHIP)) } + if ships.ByID(LARGECARGO) > 20 { shipsToPark.Set(LARGECARGO, ships.ByID(LARGECARGO) - 20) } + if ships.ByID(SMALLCARGO) > 20 { shipsToPark.Set(SMALLCARGO, ships.ByID(SMALLCARGO) - 20) } + if ships.ByID(RECYCLER) > 5 { shipsToPark.Set(RECYCLER, ships.ByID(RECYCLER) - 5) } + + totalToPark = 0 + for j = 0; j < len(SHIP_IDS_TO_BUILD); j++ { + shipID = SHIP_IDS_TO_BUILD[j] + totalToPark += shipsToPark.ByID(shipID) + } + + if totalToPark > 0 { + fleets, slots = GetFleets() + if len(fleets) >= slots.Total { + LogWarnf("No free fleet slots to park ships from %s. Skipping.", planetCoord) + continue + } + LogInfof("Parking %d ships from %s", totalToPark, planetCoord) + fleet = NewFleet() + fleet.SetOrigin(planetCoord) + fleet.SetDestination(MAIN_PLANET) + fleet.SetSpeed(HUNDRED_PERCENT) + fleet.SetMission(PARK) + fleet.SetShips(*shipsToPark) + _, err = fleet.SendNow() + if err != nil { + LogErrorf("Failed to park fleet: %s", err) + } else { + LogInfof("Fleet sent to main planet from %s", planetCoord) + } + } else { + LogInfof("No ships to park from %s", planetCoord) + } + Sleep(Random(2000, 5000)) + } +} + +// ========================= MONITORING FUNCTIONS ========================= + +func printStatus() { + LogInfo("=== Current Status ===") + totalShips = NewShipsInfos() + uniquePlanets = [] + uniquePlanets = uniquePlanets + [MAIN_PLANET] // FIX: Use + operator instead of append + for i = 0; i < len(BUILDER_PLANETS); i++ { + planetCoord = BUILDER_PLANETS[i] + isAlreadyInList = false + for j = 0; j < len(uniquePlanets); j++ { + if planetCoord == uniquePlanets[j] { + isAlreadyInList = true + break + } + } + if !isAlreadyInList { + uniquePlanets = uniquePlanets + [planetCoord] // FIX: Use + operator instead of append + } + } + + for i = 0; i < len(uniquePlanets); i++ { + planetCoord = uniquePlanets[i] + planet, _ = GetPlanet(planetCoord) + if planet == nil { continue } + ships, _ = planet.GetShips() + LogInfof("Planet %s: LF:%d HF:%d CR:%d BS:%d LC:%d SC:%d Rec:%d", + planetCoord, ships.ByID(LIGHTFIGHTER), ships.ByID(HEAVYFIGHTER), + ships.ByID(CRUISER), ships.ByID(BATTLESHIP), ships.ByID(LARGECARGO), + ships.ByID(SMALLCARGO), ships.ByID(RECYCLER)) + totalShips.Add(ships) + } + + LogInfo("---") + LogInfo("Total ships across all planets:") + LogInfof("Light Fighters: %d/%d", totalShips.ByID(LIGHTFIGHTER), TARGET_SHIPS.ByID(LIGHTFIGHTER)) + LogInfof("Heavy Fighters: %d/%d", totalShips.ByID(HEAVYFIGHTER), TARGET_SHIPS.ByID(HEAVYFIGHTER)) + LogInfof("Cruisers: %d/%d", totalShips.ByID(CRUISER), TARGET_SHIPS.ByID(CRUISER)) + LogInfof("Battleships: %d/%d", totalShips.ByID(BATTLESHIP), TARGET_SHIPS.ByID(BATTLESHIP)) + LogInfof("Large Cargo: %d/%d", totalShips.ByID(LARGECARGO), TARGET_SHIPS.ByID(LARGECARGO)) + LogInfof("Small Cargo: %d/%d", totalShips.ByID(SMALLCARGO), TARGET_SHIPS.ByID(SMALLCARGO)) + LogInfof("Recyclers: %d/%d", totalShips.ByID(RECYCLER), TARGET_SHIPS.ByID(RECYCLER)) + LogInfo("---") +} + +func forceResourceImport() { + LogInfo("=== Forcing resource import to all planets ===") + for i = 0; i < len(BUILDER_PLANETS); i++ { + planetCoord = BUILDER_PLANETS[i] + if planetCoord != MASTER_PLANET { + importResources(planetCoord, NewResources(500000, 250000, 50000)) + Sleep(5000) + } + } +} + +func forceParkAll() { + LogInfo("=== Forcing fleet parking ===") + parkAllFleets() +} + +func showProgress() { + printStatus() +} + +// ========================= STARTUP ========================= + +CronExec("0 0 * * * *", func() { printStatus() }) +CronExec("0 0 */2 * * *", func() { parkAllFleets() }) + +LogInfo("Starting Multi-Planet Ship Builder...") +LogInfo("Commands available:") +LogInfo(" showProgress() - Display current status") +LogInfo(" forceResourceImport() - Manually import resources") +LogInfo(" forceParkAll() - Manually park all fleets") + +main() \ No newline at end of file