Commit 85e4adfa authored by Max Maton's avatar Max Maton

Version 1

parents
import("pathfinder.road", "RoadPathFinder", 4);
class EconomyManager {
constructor(queueFunc, _gridSize, _minPopularity, _populationMultiplier) {
enqueueAction = queueFunc;
gridSize = _gridSize;
minPopularity = _minPopularity;
populationMultiplier = _populationMultiplier;
}
enqueueAction = false;
isInitialized = false;
worldPop = 0;
gridPopularity = {};
gridTowns = {};
gridIndustries = {};
gridDelta = {};
gridSize = 32;
minPopularity = 3000;
populationMultiplier = 10;
function Initialize() {
if (isInitialized)
return
isInitialized = true;
worldPop = 0;
local townPopulations = GSTownList();
townPopulations.Valuate(GSTown.GetPopulation);
foreach(townId, townPopulation in townPopulations) {
GSTown.SetGrowthRate(townId, GSTown.TOWN_GROWTH_NONE);
worldPop += townPopulation;
}
yield "World population: " + worldPop;
UpdateTowns();
UpdateIndustry();
GSLog.Info(gridTowns.len());
yield "len: " + gridTowns.len();
foreach(grid, _ in gridTowns) {
yield "grid: " + grid;
local towns = gridTowns[grid];
local townLocations = [];
if (towns.len() >= 1) {
local prev = GSTown.GetLocation(towns[0]);
townLocations = [prev];
foreach(i, townId in towns) {
if (i == 0)
continue;
local next = GSTown.GetLocation(townId);
townLocations.append(next);
foreach(output in _buildCountryRoad([prev], [next])) { yield output; }
}
}
if (!gridIndustries.rawin(grid) || townLocations.len() == 0)
continue;
foreach(i, industryId in gridIndustries[grid]) {
local destPoints = _getIndustryEntrypoints(GSIndustry.GetLocation(industryId));
if (destPoints.len() == 0) {
GSLog.Info("Unable to find industry entry points.");
break;
}
foreach(output in _buildCountryRoad(townLocations, destPoints)) { yield output; }
}
}
yield "done"
}
function UpdateEconomy() {
foreach(output in Initialize()) { yield output; }
gridPopularity = {};
gridTowns = {};
gridIndustries = {};
gridDelta = {};
UpdateTowns();
UpdateIndustry();
UpdateGrids();
//DrawPopularity();
foreach(output in SimulateGrid()) { yield output; }
}
function DrawPopularity() {
//local signs = GSSignList();
//foreach(id, _ in signs) {
// GSSign.RemoveSign(id);
//}
local offset = GSMap.GetTileIndex(8, 8);
local totalPopularity = 0;
foreach(gridId, value in gridPopularity) {
local sustained = value - 3000;
sustained = sustained > 0 ? 10 * sqrt(sustained) : 0;
//if (value > 25)
//GSSign.BuildSign(gridId + offset, "Popularity: " + value + " = " + sustained);
totalPopularity += value;
}
//GSLog.Info("popular: " + totalPopularity);
}
function UpdateIndustry() {
local industries = GSIndustryList();
foreach(industryId, _ in industries) {
local grid = _toGrid(GSIndustry.GetLocation(industryId));
local productivity = 0;
local cargoes = GSCargoList();
foreach(cargoId, _ in cargoes) {
productivity += GSIndustry.GetLastMonthProduction(industryId, cargoId) * GSIndustry.GetLastMonthTransportedPercentage(industryId, cargoId);
}
if (!gridIndustries.rawin(grid))
gridIndustries[grid] <- [];
gridIndustries[grid].append(industryId);
if (productivity == 0)
continue;
if (!gridPopularity.rawin(grid))
gridPopularity[grid] <- 0;
gridPopularity[grid] += productivity;
}
}
function UpdateTowns() {
local towns = GSTownList();
foreach(townId, _ in towns) {
local townLocation = GSTown.GetLocation(townId);
local grid = _toGrid(townLocation);
local productivity = 0;
local cargoes = GSCargoList();
foreach(cargoId, _ in cargoes) {
productivity += GSTown.GetLastMonthProduction(townId, cargoId) * GSTown.GetLastMonthTransportedPercentage(townId, cargoId);
}
if (!gridTowns.rawin(grid))
gridTowns[grid] <- [];
gridTowns[grid].append(townId);
if (productivity == 0)
continue;
if (!gridPopularity.rawin(grid))
gridPopularity[grid] <- 0;
gridPopularity[grid] += productivity;
}
}
function UpdateGrids() {
foreach(gridId, popularity in gridPopularity) {
local popCapacity = sqrt(popularity > minPopularity ? popularity - minPopularity : 0) * populationMultiplier;
local totalPopulation = 0;
if (gridTowns.rawin(gridId))
foreach(_, townId in gridTowns[gridId]) {
totalPopulation += GSTown.GetPopulation(townId);
}
gridDelta[gridId] <- popCapacity - totalPopulation;
}
}
function SimulateGrid() {
foreach(gridId, delta in gridDelta) {
if (!GSBase.Chance(1, 10))
continue;
// Has city, delta > 100
if (gridTowns.rawin(gridId) && delta > 20) {
// Grow one of the cities in this grid
local towns = gridTowns[gridId];
yield "Expanding " + gridId;
GSTown.ExpandTown(towns[GSBase.RandRange(towns.len())], 1)
foreach(output in _reducePopulation(10)) { yield output; }
continue;
}
// Has no city, delta > 1000
if (!gridTowns.rawin(gridId) && delta > 300) {
local success = false;
local pos = -1;
yield "Making town in " + gridId + ", " + gridTowns.len();
// make one, try a few positions
for (local i = 0; i < 20; i++) {
local offset = GSMap.GetTileIndex(GSBase.RandRange(gridSize), GSBase.RandRange(gridSize));
pos = gridId + offset;
success = GSTown.FoundTown(pos, GSTown.TOWN_SIZE_SMALL, false, GSTown.ROAD_LAYOUT_BETTER_ROADS, null)
if (success)
break;
}
if (!success) {
yield "failed founding town";
continue;
}
foreach(output in _reducePopulation(150)) { yield output; }
if (success && gridIndustries.rawin(gridId)) {
foreach(_, industryId in gridIndustries[gridId]) {
local destPoints = _getIndustryEntrypoints(GSIndustry.GetLocation(industryId));
if (destPoints.len() == 0) {
GSLog.Info("Unable to find industry entry points.");
break;
}
foreach(output in _buildCountryRoad([pos], destPoints)) { yield output; }
}
}
continue;
}
}
}
function _reducePopulation(amount) {
while (amount > 0) {
amount -= _removeBuilding();
yield "Population removal, " + amount + " left"
}
}
function _removeBuilding() {
local totalPop = 0;
local currentChoice = null;
foreach(gridId, towns in gridTowns) {
if (!gridDelta.rawin(gridId) || gridDelta[gridId] >= 0)
continue;
foreach(_, townId in towns) {
local currentPop = GSTown.GetPopulation(townId);
totalPop += currentPop;
if (GSBase.Chance(currentPop, totalPop))
currentChoice = townId;
}
}
if (currentChoice == null)
return 10000; // No valid towns found, assume people are immigrating :)
local prevPop = GSTown.GetPopulation(currentChoice);
local tiles = GSTileList();
local townPos = GSTown.GetLocation(currentChoice);
local area = GSMap.DistanceFromEdge(townPos);
if (area > 12)
area = 12;
local offset = GSMap.GetTileIndex(area, area);
tiles.AddRectangle(townPos - offset, townPos + offset);
tiles.Valuate(GSTile.GetTownAuthority)
tiles.KeepValue(currentChoice)
tiles.Valuate(GSTile.IsBuildable);
tiles.KeepValue(0); // houses are not buildable
tiles.Valuate(GSTile.GetOwner);
tiles.KeepValue(-1);
tiles.Valuate(GSRoad.IsRoadTile);
tiles.KeepValue(0);
tiles.Valuate(_isIndustry);
tiles.KeepValue(0);
foreach(tileId, _ in tiles) {
local result = GSTile.DemolishTile(tileId);
GSLog.Info("demolish " + tileId + " = " + result + " ... " + GSError.GetLastErrorString());
local delta = prevPop - GSTown.GetPopulation(currentChoice);
if (delta > 0)
return delta;
}
GSLog.Info("Failed to demolish building in town " + currentChoice);
return 1; // Retry but end eventually
}
function _isIndustry(tileId) {
return GSIndustry.IsValidIndustry(GSIndustry.GetIndustryID(tileId));
}
function _toGrid(tileId) {
local x = GSMap.GetTileX(tileId);
local y = GSMap.GetTileY(tileId);
x = (x / gridSize) * gridSize;
y = (y / gridSize) * gridSize;
return GSMap.GetTileIndex(x, y);
}
function _buildCountryRoad(from, to) {
local pathfinder = RoadPathFinder();
GSRoad.SetCurrentRoadType(GSRoad.ROADTYPE_ROAD);
pathfinder.cost.turn = 0;
pathfinder.cost.max_cost = 10000;
pathfinder.InitializePath(from, to);
local path = false;
local step = 0;
while (path == false) {
path = pathfinder.FindPath(100);
step += 100;
yield "Pathfinding step " + step;
if (step >= 400) {
path = null;
break;
}
}
if (path == null) {
GSLog.Info("Path failed from " + from + ", to " + to + ".");
}
while (path != null) {
local par = path.GetParent();
if (par == null)
break;
local lastNode = path.GetTile();
if (GSMap.DistanceManhattan(path.GetTile(), par.GetTile()) == 1) {
GSRoad.BuildRoad(path.GetTile(), par.GetTile());
path = par;
continue;
}
// Bridge or tunnel
if (GSBridge.IsBridgeTile(path.GetTile()) || GSTunnel.IsTunnelTile(path.GetTile()))
path = par;
continue; // Already built, skip
if (GSRoad.IsRoadTile(path.GetTile()))
GSTile.DemolishTile(path.GetTile()); // clean up road we're supposed to replace
if (GSTunnel.GetOtherTunnelEnd(path.GetTile()) == par.GetTile()) {
GSTunnel.BuildTunnel(GSVehicle.VT_ROAD, path.GetTile());
path = par;
continue;
}
local bridge_list = GSBridgeList_Length(GSMap.DistanceManhattan(path.GetTile(), par.GetTile()) + 1);
bridge_list.Valuate(GSBridge.GetMaxSpeed);
bridge_list.Sort(AIList.SORT_BY_VALUE, true); // Pick slowest (and hopefully cheapest) bridge.
GSBridge.BuildBridge(AIVehicle.VT_ROAD, bridge_list.Begin(), path.GetTile(), par.GetTile());
path = par;
}
}
function _getIndustryEntrypoints(location) {
local maxDist = GSMap.DistanceFromEdge(location);
if (maxDist > 5)
maxDist = 5;
local xoff = GSMap.GetTileIndex(1, 0);
local yoff = GSMap.GetTileIndex(0, 1);
local results = [];
for (local i = 1; i <= maxDist; i+=1)
{
local probeLocation = location + i * xoff;
if (GSTile.IsBuildable(probeLocation) || GSRoad.IsRoadTile(probeLocation)) {
results.append(probeLocation);
break;
}
}
for (local i = 1; i <= maxDist; i+=1)
{
local probeLocation = location - i * xoff;
if (GSTile.IsBuildable(probeLocation) || GSRoad.IsRoadTile(probeLocation)) {
results.append(probeLocation);
break;
}
}
for (local i = 1; i <= maxDist; i+=1)
{
local probeLocation = location + i * yoff;
if (GSTile.IsBuildable(probeLocation) || GSRoad.IsRoadTile(probeLocation)) {
results.append(probeLocation);
break;
}
}
for (local i = 1; i <= maxDist; i+=1)
{
local probeLocation = location - i * yoff;
if (GSTile.IsBuildable(probeLocation) || GSRoad.IsRoadTile(probeLocation)) {
results.append(probeLocation);
break;
}
}
return results;
}
}
class MigrationScriptInfo extends GSInfo {
function GetAuthor() { return "Max Maton"; }
function GetName() { return "Migrations"; }
function GetShortName() { return "MIGR"; }
function GetDescription() { return "Town migrations"; }
function GetVersion() { return 1; }
function MinVersionToLoad() { return 1; }
function GetDate() { return "2018-09-13"; }
function UseAsRandomAI() { return true; }
function CreateInstance() { return "MigrationScript"; }
function GetAPIVersion() { return "1.8"; }
function GetURL() { return "https://gitlab.com/thexa4/migrations/"; }
function GetSettings() {
AddSetting({name = "log_level", description = "Debug: Log level (higher = print more)", easy_value = 3, medium_value = 3, hard_value = 3, custom_value = 3, flags = CONFIG_INGAME, min_value = 1, max_value = 3});
AddLabels("log_level", {_1 = "1: Info", _2 = "2: Verbose", _3 = "3: Debug" } );
AddSetting({name = "city_creation_floor", description = "How hard it is for a new town to start.", easy_value = 3000, medium_value = 5000, hard_value = 7000, custom_value = 3000, flags = 0, min_value = 100, max_value = 10000});
AddSetting({name = "population_modifier", description = "How quickly a town grows once created.", easy_value = 10, medium_value = 8, hard_value = 5, custom_value = 10, flags = 0, min_value = 1, max_value = 100});
AddSetting({name = "grid_size", description = "Size of industry regions. (higher is faster but results in fewer towns)", easy_value = 32, medium_value = 32, hard_value = 32, custom_value = 32, flags = 0, min_value = 16, max_value = 256});
}
}
RegisterGS(MigrationScriptInfo());
require("economy_manager.nut")
class MigrationScript extends GSController {
workQueue = [];
economyManager = false;
constructor() {
local _gridSize = MigrationScript.GetSetting("grid_size");
local _minPopularity = MigrationScript.GetSetting("city_creation_floor");
local _populationMultiplier = MigrationScript.GetSetting("population_modifier");
economyManager = EconomyManager(this.QueueAction.bindenv(this), _gridSize, _minPopularity, _populationMultiplier);
}
function Start();
function FindSomethingToDo()
function QueueAction(func);
}
function MigrationScript::QueueAction(func) {
workQueue.push(func)
}
function MigrationScript::Start() {
while(true) {
if (workQueue.len() == 0)
FindSomethingToDo();
local currentList = workQueue;
workQueue = []
foreach(workItem in currentList) {
local output = resume workItem;
if (workItem.getstatus() != "dead")
workQueue.push(workItem)
if (output != null)
GSLog.Info(this.GetTick() + "(" + this.GetOpsTillSuspend() + "): " + output)
}
if (workQueue.len() == 0)
this.Sleep(100 - (this.GetTick() % 100));
}
}
function MigrationScript::FindSomethingToDo() {
QueueAction(economyManager.UpdateEconomy());
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment