RoadRunner/ 0000755 0001750 0001750 00000000000 11225225146 010361 5 ustar b b RoadRunner/Settings.nut 0000755 0001750 0001750 00000002715 11225225146 012721 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* Settings.nut
*
* Handles all settings.
*
* Author: George Weller (Zutty)
* Created: 05/04/2009
* Version: 1.0
*/
class Settings {
}
/*
* Get the level of latency.
*/
function Settings::GetLatency() {
return (5 - PathZilla.GetSetting("latency"));
}
/*
* Get whether or not the AI should play aggressively.
*/
function Settings::IsAggressive() {
return (PathZilla.GetSetting("aggressive") == 1);
}
/*
* Get whether industrial cargo should be routed through towns.
*/
function Settings::RouteCargoThroughTowns() {
return (PathZilla.GetSetting("rt_cargo_towns") == 1);
}
/*
* Get whether industrial cargo should be routed through towns.
*/
function Settings::EnableCountryLanes() {
return (PathZilla.GetSetting("country_lanes") == 1);
} RoadRunner/common.nut 0000755 0001750 0001750 00000022334 11330314534 012405 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* common.nut
*
* A collection of basic helper functions.
*
* Author: George Weller (Zutty)
* Created: 24/05/2008
* Version: 1.2
*/
/*
* Square root using Newton-Raphson method.
*/
::sqrtlut <- [0, 10, 100, 1000, 10000, 100000, 1000000, 10000000];
function sqrt(a){
if (a<=0) return 0;
local s=1.0;
for (;s*sa;q--){}
local e=q;
a=a-e*e*s*s;
for (s=s/10;s>0.00001;s=s/10){
for (q=((a/(20*e))/(s*s)).tointeger();(20*e+q)*q*s*s>a;q--){
//AILog.Info(q+"|"+(20*e+q)*q*s*s);
}
a=a-(20*e+q)*q*s*s;
e=e*10+q;
if (a==0) return e*s;
}
return e*s*10;
}
/*
* Raises num to the power p.
*/
function pow(num, p) {
return (p <= 0) ? 1 : num * pow(num, p - 1);
}
/*
* Get the maximum of two values. This is type agnostic.
*/
function max(a, b) {
if(a >= b) return a;
return b;
}
/*
* Get the minimum of two values. This is type agnostic.
*/
function min(a, b) {
if(a <= b) return a;
return b;
}
/*
* Taylor series approximation is sine.
*/
function sin(x) {
return x - (pow(x, 3) / 6.0) + (pow(x, 5) / 120.0) - (pow(x, 7) / 5040.0);
}
/*
* Taylor series approximation is cosine.
*/
function cos(x) {
return x - (pow(x, 2) / 2.0) + (pow(x, 4) / 24.0) - (pow(x, 6) / 720.0);
}
/*
* Returns true if the array arr contains the element item.
*/
function arraycontains(arr, item) {
foreach(elem in arr) {
if(elem <= item && elem >= item) {
return true;
}
}
return false;
}
/*
* Get the index of the specified item from the specified array if it exists,
* and -1 otherwise.
*/
function arrayfind(arr, item) {
foreach(idx, elem in arr) {
if(elem <= item && elem >= item) {
return idx;
}
}
return -1;
}
/*
* Checks if the specified string ends with the specified pattern
*/
function ends_with(string, pattern) {
return (string.slice(string.len() - pattern.len()) == pattern);
}
/*
* Get the town at the specified tile, or null if there isn't one.
*/
function GetTown(tile) {
local towns = AITownList();
towns.Valuate(AITown.GetLocation);
towns.KeepValue(tile);
if(towns.Count() > 0) {
return towns.Begin();
} else {
return null;
}
}
/*
* Delete the sign at the specified tile, if any.
*/
function RemoveSign(tile) {
for(local i = 0; i < AISign.GetMaxSignID(); i++) {
if(AISign.GetLocation(i) == tile) {
AISign.RemoveSign(i);
}
}
}
/*
* Draw a line on the map between the specified points using signs. If an array
* is also passed as a parameter, the sign ID will be appended to the array so
* that the signs can be cleaned up later.
*/
function DrawLine(a, b, ...) {
local len = sqrt(AITile.GetDistanceSquareToTile(a, b));
local collate = vargc > 0;
if(len > 0) {
local deltaX = AIMap.GetTileX(b) - AIMap.GetTileX(a);
local deltaY = AIMap.GetTileY(b) - AIMap.GetTileY(a);
local factor = 1000;
local stepX = deltaX * factor / len;
local stepY = deltaY * factor / len;
local currentPos = a;
local offX = 0;
local offY = 0;
for(local i = 0; i < len; i++) {
offX = (stepX * i) / factor;
offY = (stepY * i) / factor;
currentPos = AIMap.GetTileIndex((AIMap.GetTileX(a) + offX).tointeger(), (AIMap.GetTileY(a) + offY).tointeger());
local signId = AISign.BuildSign(currentPos, " ");
if(collate) vargv[0].append(signId);
}
}
}
/*
* Draw a series of lines to represent a graph.
*/
function DrawGraph(graph) {
foreach(edge in graph.GetEdges().data) {
this.DrawLine(edge.a.ToTile(), edge.b.ToTile());
}
}
/*
* Get the sum of all values in an AIList.
*/
function ListSum(list) {
local sum = 0;
for(local j = list.Begin(); list.HasNext(); j = list.Next()) {
sum += list.GetValue(j);
}
return sum;
}
/*
* Get a random item from an AIList, using the list values as weights. An item
* with a higher weight value will be chosen with a higher frequency than an
* item with a lower weight. The weights must sum to the value of the sum
* parameter (see ListSum()).
*/
function RandomItemByWeight(list, sum) {
local pivot = AIBase.RandRange(sum);
local item = list.Begin();
local n = 0;
while(list.HasNext()) {
n += list.GetValue(item);
if(n >= pivot) {
break;
} else {
item = list.Next();
}
}
return item;
}
/*
* Convert an AIList to an array of integers. Values will be lost.
*/
function ListToArray(list) {
local array = [];
foreach(item, _ in list) {
array.append(item);
}
return array;
}
/*
* Convert an array of integers to an AIList with no values.
*/
function ArrayToList(array) {
local list = AIList();
foreach(item in array) {
list.AddItem(item, 0);
}
return list;
}
/*
* Truncate a string to match the OpenTTD name length requirements.
*/
function trnc(str) {
return (str.len() > 30) ? str.slice(0, 30) : str;
}
/*
* Get absolute value of a number
*/
function abs(val) {
return (val < 0) ? -val : val;
}
/*
* A dummy class used by the load_class() function
*/
class dummy {
function load(idx) {
return this[idx];
}
}
/*
* Load the class with the given name from the root table
*/
function load_class(classname) {
return ::dummy.instance().load(classname);
}
/*
* Log the values of a structured variable like an array array or table
*/
function show(var) {
foreach(line in split(_show(var, 0), "|")) {
if(line != "") AILog.Info(line);
}
}
/*
* Callback used for the show() function
*/
function _show(var, depth) {
if(var == null) {
return "null";
} else if(typeof var == "table" || typeof var == "instance") {
local indent = "";
for(local i = 0; i < depth; i++) indent += " ";
local indent2 = indent + " ";
local str = "{|";
foreach(name, member in var) {
str += indent2 + name + " = " + _show(member, depth + 1) + "|";
}
str += indent + "}|";
//AILog.Info(""+str);
return str;
} else if(typeof var == "array") {
local indent = "";
for(local i = 0; i < depth; i++) indent += " ";
local indent2 = indent + " ";
local str = "[|";
for(local i = 0; i < depth; i++) indent2 += " ";
foreach(member in var) {
str += indent + _show(member, depth + 1) + "|";
}
str += indent + "]|";
return str;
} else {
return ""+var;
}
}
/*
* Split a string by the specified delimiter into tokens which will be
* returned in an array
*/
function split(str, delim) {
if(str == null) return [];
if(delim == null) return [str];
local idx = str.find(delim);
local ret = [str.slice(0, (idx != null) ? idx : str.len())];
if(idx != null) {
ret.extend(split(str.slice(idx+1), delim));
}
return ret;
}
/*
* Return the minimum length whole-word substring of str to be at least len
* characters long. For instance...
* chopstr("Sentfingley Market", 7) = "Sentfingley"
* chopstr("Fort Sunningbury", 7) = "Fort Sunningbury"
* chopstr("Little Fradinghead Cross", 7) = "Little Fradinghead"
*/
function chopstr(str, len) {
local tokens = split(str, " ");
local newStr = "";
local l = min(len, str.len());
local i = 0;
while(newStr.len() < l) {
if(newStr.len() > 0) newStr += " ";
newStr += tokens[i++];
}
return newStr;
}
/*
* Call the spcified function with an array of arguments, without using acall
*/
function arr_call(func, args) {
switch (args.len()) {
case 0: return func();
case 1: return func(args[0]);
case 2: return func(args[0], args[1]);
case 3: return func(args[0], args[1], args[2]);
case 4: return func(args[0], args[1], args[2], args[3]);
case 5: return func(args[0], args[1], args[2], args[3], args[4]);
case 6: return func(args[0], args[1], args[2], args[3], args[4], args[5]);
case 7: return func(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
case 8: return func(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
case 9: return func(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
case 10: return func(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]);
case 11: return func(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]);
case 12: return func(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11]);
case 13: return func(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12]);
case 14: return func(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13]);
case 15: return func(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14]);
default: throw "Too many arguments to CallFunction";
}
}
RoadRunner/graph/ 0000755 0001750 0001750 00000000000 11225225146 011462 5 ustar b b RoadRunner/graph/Edge.nut 0000755 0001750 0001750 00000003660 11225225146 013066 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* Edge.nut
*
* An edge in a graph, made up of two vertices.
*
* Author: George Weller (Zutty)
* Created: 04/06/2008
* Version: 1.3
*/
class Edge {
a = null;
b = null;
constructor(a, b) {
if(a < b) {
this.a = a;
this.b = b;
} else {
this.a = b;
this.b = a;
}
}
}
/*
* Get the length of the edge
*/
function Edge::GetLength() {
return this.a.GetDistance(this.b);
}
/*
* Checks to see if another edge is the same as another.
*/
function Edge::equals(edge) {
if(edge == null) return false;
return ((this.a.equals(edge.a) && this.b.equals(edge.b)) || (this.a.equals(edge.b) && this.b.equals(edge.a)));
}
/*
* Compare the edge with another. This returns 0 (i.e. equal) if the edges have
* the same vertices, and otherwise orders them by length (TBH I can't remember
* what this method is doing!!).
*/
function Edge::_cmp(edge) {
if((this.a.equals(edge.a) && this.b.equals(edge.b)) || (this.a.equals(edge.b) && this.b.equals(edge.a))) {
return 0;
} else {
local maxTY = max(this.a.y, this.b.y);
local maxEY = max(edge.a.y, edge.b.y);
return (maxTY < maxEY) ? -1 : 1;
}
}
/*
* Gets a string representation of this edge.
*/
function Edge::_tostring() {
return "{" + a + " -> " + b + "}";
} RoadRunner/graph/Graph.nut 0000755 0001750 0001750 00000020366 11225225146 013265 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* Graph.nut
*
* A node graph, composed of a number of vertices.
*
* Author: George Weller (Zutty)
* Created: 05/06/2008
* Version: 1.2
*/
class Graph {
// Serialization constants
CLASS_NAME = "Graph";
SRLZ_EDGE_DATA = 0;
// Member variables
data = null;
vertices = null;
edges = null;
bestPaths = null;
constructor() {
this.data = {};
this.vertices = SortedSet();
this.edges = SortedSet();
this.bestPaths = {};
}
}
/*
* Add a lone vertex to a graph.
*/
function Graph::AddVertex(vertex) {
this.vertices.Insert(vertex);
}
/*
* Removes a vertex from a graph, and also any edges or triangles that were
* connected to it.
*/
function Graph::RemoveVertex(vertex) {
this.data.rawdelete(vertex);
foreach(entry in this.data) {
entry.Remove(vertex);
}
local toRemove = [];
foreach(edge in this.edges.data) {
if(vertex == edge.a || vertex == edge.b) {
toRemove.insert(edge);
}
}
foreach(r in toRemove) {
this.edges.Remove(vertex);
}
this.vertices.Remove(vertex);
}
/*
* Returns true if the specified vertex forms part of the graph.
*/
function Graph::ContainsVertex(vertex) {
return this.vertices.Contains(vertex);
}
/*
* Get a list of all vertices in this graph.
*/
function Graph::GetVertices() {
return this.vertices;
}
/*
* Get a list of all edges in this graph.
*/
function Graph::GetEdges() {
return this.edges;
}
/*
* Get a list of neightboring vertices to specified one.
*/
function Graph::GetNeighbours(vertex) {
local t = vertex.ToTile();
return (t in this.data) ? this.data[t] : SortedSet();
}
/*
* Add an edge to the graph. This also adds both vertices.
*/
function Graph::AddEdge(edge) {
this.edges.Insert(edge);
this.vertices.Insert(edge.a);
this.vertices.Insert(edge.b);
if(!this.data.rawin(edge.a.ToTile())) {
this.data[edge.a.ToTile()] <- SortedSet();
}
this.data[edge.a.ToTile()].Insert(edge.b);
if(!this.data.rawin(edge.b.ToTile())) {
this.data[edge.b.ToTile()] <- SortedSet();
}
this.data[edge.b.ToTile()].Insert(edge.a);
}
/*
* Add all the edges from another graph to this one.
*/
function Graph::Merge(graph) {
this.edges.Merge(graph.edges);
this.vertices.Merge(graph.vertices);
foreach(v in this.vertices) {
if(graph.data.rawin(v.ToTile())) {
if(!this.data.rawin(v.ToTile())) {
this.data[v.ToTile()] <- SortedSet();
}
this.data[v.ToTile()].Merge(graph.data[v.ToTile()]);
}
}
}
/*
* Get the shortest distances accross the graph from the specified source node
* to every other node. This method uses Dijkstra's algorithm.
*/
function Graph::GetShortestDistances(source) {
// Initialise
local queue = BinaryHeap();
local dist = {};
local prev = {};
local infinity = AIMap.GetMapSizeX() + AIMap.GetMapSizeY();
infinity = infinity * infinity; // Square it
// If there is only one vertex, Dijkstra wont work!
if(this.GetVertices().Len() <= 1) {
dist[source.ToTile()] <- 0;
return dist;
}
// Initialise distance and previous node lists
foreach(v in this.GetVertices()) {
local tile = v.ToTile();
dist[tile] <- (tile == source.ToTile()) ? 0 : infinity;
prev[tile] <- null;
queue.Insert(DijkstraNode(tile, dist[tile]));
}
// Process each node in best first order
local steps = 0;
foreach(u in queue) {
// Only sleep once every PROCESSING_PRIORITY iterations
if(steps++ % PathZilla.PROCESSING_PRIORITY == 0) {
PathZilla.Sleep(1);
}
// Find the best cost node
local uTile = u.tile;
local uVertex = Vertex.FromTile(uTile);
// Get the vertices adjacent to the current one and update them
foreach(v in this.GetNeighbours(uVertex)) {
local vTile = v.ToTile();
local alt = dist[uTile] + AIMap.DistanceManhattan(uTile, vTile);
// If the computed cost is better than the stored one then update
if(alt < dist[vTile]) {
dist[vTile] = alt;
prev[vTile] = uVertex;
queue.Insert(DijkstraNode(vTile, dist[vTile]));
}
}
}
return dist;
}
/*
* Find the shortest path accross the edges of the graph between two specified
* vertices. This method caches paths to save CPU time, as graphs do not
* change frequently.
*/
function Graph::FindPath(aVertex, bVertex) {
// Get the tiles
local aTile = aVertex.ToTile();
local bTile = bVertex.ToTile();
// Check the cache first
if(aTile in this.bestPaths) {
if(bTile in this.bestPaths[aTile]) {
return this.bestPaths[aTile][bTile];
}
}
// Initialise
local open = BinaryHeap();
local closed = AIList();
local node = null;
local vertex = null;
local finalPath = null;
local steps = 0;
local MAX_STEPS = 1000;
// Add the root node
open.Insert(GraphPathNode(aVertex, null, aVertex.GetDistance(bVertex)));
// Start the main loop
while(open.Len() > 0) {
// Dont hog all the CPU
if(steps % PathZilla.PROCESSING_PRIORITY == 0) {
PathZilla.Sleep(1);
}
// Get the next node
node = open.Pop();
vertex = node.GetVertex();
// Check that weve not already tried this
if(closed.HasItem(vertex.ToTile())) {
continue;
}
//AISign.BuildSign(tile, ""+node.GetCost().GetTotalCost());
// Ensure we dont try it again
closed.AddItem(vertex.ToTile(), 0);
// Check if we have reached our goal
if(vertex.equals(bVertex)) {
finalPath = node;
break;
}
// Add potential neighbours to the open list
foreach(v in this.GetNeighbours(vertex).data) {
open.Insert(GraphPathNode(v, node, v.GetDistance(bVertex)));
}
// Prevent the pathfinder hanging for a long time for paths that are intractable
if(steps++ >= MAX_STEPS) {
AILog.Error(" Path is taking too long to find.");
break;
}
}
// Add path to cache
if(!this.bestPaths.rawin(aTile)) {
this.bestPaths[aTile] <- {}
}
this.bestPaths[aTile][bTile] <- finalPath;
return finalPath;
}
/*
* Saves data to a table.
*/
function Graph::Serialize() {
local saveData = {};
/* TODO - If non-standard targets are ever used, uncomment this
// Serialise the targets linked to the graph
foreach(v in this.vertices) {
saveData[v.ToTile()+1] <- v.GetTargetId();
}
*/
// Serialise a list of edges from the graph
local edgeData = [];
foreach(e in this.edges) {
edgeData.append(e.a.ToTile());
edgeData.append(e.b.ToTile());
}
saveData[SRLZ_EDGE_DATA] <- edgeData;
return saveData;
}
/*
* Loads data from a table.
*/
function Graph::Unserialize(saveData) {
/* TODO - If non-standard targets are ever used, uncomment this
// Build a list of vertices and their targets
local vtxMap = {};
foreach(idx, targetId in saveData) {
if(idx < 1) continue;
local vTile = idx - 1;
local v = Vertex.FromTile(vTile);
v.targetId = targetId;
this.vertices.RawInsert(v);
vtxMap[vTile] <- v;
}
*/
// Build a graph from a serialised list of edges
local edgeData = saveData[SRLZ_EDGE_DATA];
for(local i = 0; i < edgeData.len(); i += 2) {
// Get the raw data
local aTile = edgeData[i];
local bTile = edgeData[i + 1];
// Build vertices and an edge
local a = Vertex.FromTile(aTile);
local b = Vertex.FromTile(bTile);
local e = Edge(a, b);
// Store them
this.vertices.RawInsert(a);
this.vertices.RawInsert(b);
this.edges.RawInsert(e);
// Build neightbour lists
if(!this.data.rawin(aTile)) {
this.data[aTile] <- SortedSet();
}
this.data[aTile].RawInsert(b);
if(!this.data.rawin(bTile)) {
this.data[bTile] <- SortedSet();
}
this.data[bTile].RawInsert(a);
}
// Remove duplicate vertices
this.vertices.RemoveDuplicates();
}
/*
* Makes a deep copy of this graph.
*/
function Graph::_cloned(original) {
local new = Graph();
new.data = clone original.data;
new.vertices = clone original.vertices;
new.edges = clone original.edges;
return new;
} RoadRunner/graph/GraphPathNode.nut 0000755 0001750 0001750 00000003565 11225225146 014712 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* GraphPathNode.nut
*
* A node in a graph state space search.
*
* Author: George Weller (Zutty)
* Created: 28/06/2008
* Version: 1.0
*/
class GraphPathNode {
vertex = null;
parentNode = null;
cost = 0;
distance = 0;
constructor(vertex, parentNode, cost) {
this.vertex = vertex;
this.parentNode = parentNode;
if (parentNode != null) {
this.cost = cost + parentNode.GetDistance();
this.distance = parentNode.GetDistance() + parentNode.GetVertex().GetDistance(vertex);
} else {
this.cost = 0;
this.distance = 0;
}
}
}
/*
* Get the vertex that defines this node.
*/
function GraphPathNode::GetVertex() {
return this.vertex;
}
/*
* Get the parent node
*/
function GraphPathNode::GetParent() {
return this.parentNode;
}
/*
* Get the cost of this node
*/
function GraphPathNode::GetCost() {
return this.cost;
}
function GraphPathNode::GetDistance() {
return this.distance;
}
/*
* Compare the node to another node. Two path nodes should be compared by their
* total pathfinding cost.
*/
function GraphPathNode::_cmp(node) {
if(node.GetCost() < this.GetCost()) return 1;
if(node.GetCost() > this.GetCost()) return -1;
return 0;
}
RoadRunner/graph/Triangle.nut 0000755 0001750 0001750 00000006716 11225225146 013774 0 ustar b b /*
* Copyright © 2008 George Weller
* Some code adapted from C++ example Copyright © 2005, Sjaak Priester, Amsterdam.
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* Triangle.nut
*
* A triangle in a graph, made up of three vertices.
*
* Author: George Weller (Zutty)
* Created: 03/06/2008
* Version: 1.2
*/
class Triangle {
a = null;
b = null;
c = null;
u = null;
r = 0;
constructor(a, b, c) {
this.a = a;
this.b = b;
this.c = c;
this.GetCircumCircle();
}
}
/*
* Get the triangle's circumscribed circle. This is defined by the unique
* circle that fits on all three vertices of the triangle.
*
* Adapted from C++ example Copyright © 2005, Sjaak Priester, Amsterdam.
*/
function Triangle::GetCircumCircle() {
//AILog.Info(" Scribing circle ("+this.a+", "+this.b+", "+this.c+")");
local aX = this.a.x;
local aY = this.a.y;
local bX = this.b.x;
local bY = this.b.y;
local cX = this.c.x;
local cY = this.c.y;
local abY = bY - aY;
local cbY = cY - bY;
local uX = 0.0;
local uY = 0.0;
if (abY == 0.0) {
if (cbY == 0.0) { // All three vertices are on one horizontal line.
//AILog.Info(" Block 1...");
if (bX > aX) {
if (cX > bX) bX = cX;
} else {
if (cX < aX) aX = cX;
}
uX = (aX + bX) / 2.0;
uY = aY;
} else { // A and B are on one horizontal line.
//AILog.Info(" Block 2...");
local m1 = - ((cX - bX) / cbY);
local mx1 = (bX + cX) / 2.0;
local my1 = (bY + cY) / 2.0;
uX = (aX + bX) / 2.0;
uY = (m1 * (uX - mx1)) + my1;
}
} else if (cbY == 0.0) { // B and C are on one horizontal line.
//AILog.Info(" Block 3...");
local m0 = - ((bX - aX) / abY);
local mx0 = (aX + bX) / 2.0;
local my0 = (aY + bY) / 2.0;
uX = (bX + cX) / 2;
uY = (m0 * (uX - mx0)) + my0;
} else { // 'Common' cases, no multiple vertices are on one horizontal line.
//AILog.Info(" Block 4...");
local m0 = -((bX - aX) / abY);
local m1 = -((cX - bX) / cbY);
local mx0 = (aX + bX) / 2.0;
local my0 = (aY + bY) / 2.0;
local mx1 = (bX + cX) / 2.0;
local my1 = (bY + cY) / 2.0;
local denom = (m0 - m1);
denom = (denom == 0) ? 1 : denom;
uX = ((m0 * mx0) - (m1 * mx1) + my1 - my0) / denom; // Possible divide by zero
//uY = m0 * (uX - mx0) + my0;
uY = m0 * uX - (m0 * mx0) + my0;
}
this.u = Vertex(uX, uY);
local dx = aX - uX;
local dy = aY - uY;
this.r = sqrt(dx * dx + dy * dy); // the radius of the circumcircle
}
/*
* Checks if this triangle is entirely to the south of the specified point.
*/
function Triangle::IsSouthOf(vertex) {
return (vertex.y < this.u.y - (this.r + 2.0));
}
/*
* Compare this triangle to another. This method sorts the triangles in south
* to north order, to enable to sweepline optimisation.
*/
function Triangle::_cmp(tri) {
local a = this.u.y - this.r;
local b = tri.u.y - tri.r;
return ((a < b) ? 1 : -1);
} RoadRunner/graph/Vertex.nut 0000755 0001750 0001750 00000004470 11225225146 013477 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* Vertex.nut
*
* A vertex in a graph, and a point on the map that MIGHT be beyond the
* boundary.
*
* Author: George Weller (Zutty)
* Created: 05/06/2008
* Version: 1.0
*/
class Vertex {
x = 0;
y = 0;
targetId = null;
constructor(x, y, targetId = null) {
this.x = x.tofloat();
this.y = y.tofloat();
this.targetId = targetId;
}
}
/*
* Get the underlying target for this vertex
*/
function Vertex::GetTargetId() {
return this.targetId;
}
/*
* Test if this vertex has the same components as another.
*/
function Vertex::equals(v) {
if(v == null) return false;
return (this.x == v.x && this.y == v.y);
}
/*
* Compares this vertex to another. If the vertices have the same components
* then this function returns 0 (i.e. equal). Otherwise they are ordered by
* their Y component (for delaunay triangulation).
*/
function Vertex::_cmp(v) {
if(this.x == v.x && this.y == v.y) return 0;
if(this.y < v.y) return -1
return 1;
}
/*
* Get a string representation of this vertex
*/
function Vertex::_tostring() {
return "[" + this.x + ", " + this.y + "]";
}
/*
* Get the Euclidean distance between this vertex and another.
*/
function Vertex::GetDistance(v) {
local dX = v.x - this.x;
local dY = v.y - this.y;
return sqrt(dX*dX + dY*dY);
}
/*
* Convert the vertex into a tile index for the current map.
*/
function Vertex::ToTile() {
return AIMap.GetTileIndex(this.x.tointeger(), this.y.tointeger());
}
/*
* Static method to create a vertex based on a tile index in the current map.
*/
function Vertex::FromTile(tile) {
return Vertex(AIMap.GetTileX(tile), AIMap.GetTileY(tile), tile);
} RoadRunner/graph/impl/ 0000755 0001750 0001750 00000000000 11365463557 012442 5 ustar b b RoadRunner/graph/impl/MinimumSpanTree.nut 0000755 0001750 0001750 00000007310 11225225146 016234 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* MinimumSpanTree.nut
*
* The minimum spanning tree of a complete graph, based on Prim's algorithm. A
* minimum spanning tree is a tree that includes every vertex on a graph,
* connected by the minium distance possible.
*
* Prim's algorithm works by building up a graph starting from an arbitrary
* seed node, by adding the shortest edges that connects the graph to a vertex
* not yet in the graph.
*
* This implementation uses a Binary Heap to select the best node, which gives
* around O(e log v) time complexity, where e is the number of edges and v is
* the nuber of vertices. This is an improvement over the old adjacency matrix
* implementation, especially on a large map with many towns.
*
* Author: George Weller (Zutty)
* Created: 06/06/2008
* Version: 1.1
*/
class MinimumSpanTree extends Graph {
constructor(masterGraph) {
Graph.constructor();
AILog.Info(" Computing minimum spanning tree...");
// Use a special case if there are less than three vertices
if(masterGraph.GetVertices().Len() < 3) {
// Just copy the data!
this.vertices = clone masterGraph.vertices;
this.edges = clone masterGraph.edges;
this.data = clone masterGraph.data;
AILog.Info(" Done");
return;
}
local queue = BinaryHeap();
local closed = {};
local edgeSet = SortedSet();
local vtxMap = {};
foreach(v in masterGraph.GetVertices()) {
vtxMap[v.ToTile()] <- v;
}
// Initialise the graph using the home town
local r = masterGraph.GetVertices().Begin();
queue.Insert(PrimNode(r.ToTile(), null, 0));
closed[r.ToTile()] <- false;
// Connect each vertex only once
foreach(u in queue) {
local uTile = u.tile;
if(!closed[uTile]) {
closed[uTile] <- true;
local uVertex = vtxMap[uTile];
if(uTile != r.ToTile()) {
edgeSet.RawInsert(Edge(uVertex, vtxMap[u.otherTile]));
}
foreach(v in masterGraph.GetNeighbours(uVertex)) {
local vTile = v.ToTile();
if(!closed.rawin(vTile)) {
closed[vTile] <- false;
}
queue.Insert(PrimNode(vTile, uTile, AIMap.DistanceSquare(uTile, vTile)));
}
}
}
this.vertices = clone masterGraph.GetVertices();
// Build a graph from the spanning tree edges
foreach(e in edgeSet) {
this.edges.RawInsert(e);
if(!this.data.rawin(e.a.ToTile())) {
this.data[e.a.ToTile()] <- SortedSet();
}
this.data[e.a.ToTile()].RawInsert(e.b);
if(!this.data.rawin(e.b.ToTile())) {
this.data[e.b.ToTile()] <- SortedSet();
}
this.data[e.b.ToTile()].RawInsert(e.a);
}
AILog.Info(" Done.");
}
}
/*
* A node for a Prim's algorithm search, that allows a graph to be
* reconstructed.
*/
class PrimNode {
tile = null;
otherTile = null;
edgeLen = 0;
constructor(u, v, l) {
this.tile = u;
this.otherTile = v;
this.edgeLen = l;
}
}
/*
* Compares this node to another. This methods orders nodes by edge length.
*/
function PrimNode::_cmp(node) {
return (this.edgeLen == node.edgeLen) ? 0 : ((this.edgeLen < node.edgeLen) ? -1 : 1);
} RoadRunner/graph/impl/Triangulation.nut 0000755 0001750 0001750 00000016600 11346656540 016013 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* Triangulation.nut
*
* The delaunay triangulation of a set of vertices. A triangulation is a
* subdivision of a plane into triangles. It is also a planar graph, i.e. that
* no two edges intersect. The delaunay triangulation is a trigulation such
* that no point is inside the circumcircle of any triangle in the graph. This
* avoids long, thin triangles where possible.
*
* The algorithm uses a sweep-line to improve efficiency. Targets are ordered
* from south to north and so triangles are built up in this order. Once the
* main loop reaches a target vertex that is north of any triangle, we know
* that no further changes will be made to it, and so it is considered
* 'complete'. Complete triangles are removed from the main list and re-added
* afterwards, which reduces the number of circumcircle comparisons that we
* need to perform.
*
* This implementation is based on an algorithm by Sjaak Priester. See...
* http://www.codeguru.com/cpp/cpp/algorithms/general/article.php/c8901
*
* Author: George Weller (Zutty)
* Created: 05/06/2008
* Version: 1.1
*/
class Triangulation extends Graph {
edgeSet = null;
constructor(targets) {
Graph.constructor();
targets.SortBy(function (a, b) {
local al = a.GetLocation();
local bl = b.GetLocation();
if(al == bl) return 0;
return (al < bl) ? 1 : -1;
});
AILog.Info(" Computing triangulation over " + targets.Len() + " targets...");
// If there are fewer than three targets then use a special case
if(targets.Len() == 1) {
this.vertices.RawInsert(targets.GetI(0).GetVertex());
AILog.Info(" Done.");
return;
} else if(targets.Len() == 2) {
local a = targets.GetI(0).GetVertex();
local b = targets.GetI(1).GetVertex();
this.vertices.RawInsert(a);
this.vertices.RawInsert(b);
this.edges.RawInsert(Edge(a, b));
this.data[a.ToTile()] <- SortedSet();
this.data[a.ToTile()].RawInsert(b);
this.data[b.ToTile()] <- SortedSet();
this.data[b.ToTile()].RawInsert(a);
AILog.Info(" Done.");
return;
} else if(targets.Len() == 3) {
local a = targets.GetI(0).GetVertex();
local b = targets.GetI(1).GetVertex();
local c = targets.GetI(2).GetVertex();
this.vertices.RawInsert(a);
this.vertices.RawInsert(b);
this.vertices.RawInsert(c);
this.edges.RawInsert(Edge(a, b));
this.edges.RawInsert(Edge(b, c));
this.edges.RawInsert(Edge(c, a));
this.data[a.ToTile()] <- SortedSet();
this.data[a.ToTile()].RawInsert(b);
this.data[a.ToTile()].RawInsert(c);
this.data[b.ToTile()] <- SortedSet();
this.data[b.ToTile()].RawInsert(a);
this.data[b.ToTile()].RawInsert(c);
this.data[c.ToTile()] <- SortedSet();
this.data[c.ToTile()].RawInsert(a);
this.data[c.ToTile()].RawInsert(b);
AILog.Info(" Done.");
return;
}
// Get the corners of the map
local superVertices = [
Vertex(1, 1),
Vertex(AIMap.GetMapSizeX() - 2, 1),
Vertex(1, AIMap.GetMapSizeY() - 2),
Vertex(AIMap.GetMapSizeX() - 2, AIMap.GetMapSizeY() - 2)
];
// Seed the trianglation with two triangles forming a square over the entire map
local liveTriangles = [
Triangle(superVertices[0], superVertices[1], superVertices[2]),
Triangle(superVertices[1], superVertices[2], superVertices[3])
];
local completedTriangles = [];
// Compute the trianglation
local steps = 0;
foreach(target in targets) {
// Only sleep once every PROCESSING_PRIORITY iterations
if(steps++ % PathZilla.PROCESSING_PRIORITY == 0) {
PathZilla.Sleep(1);
}
local vertex = target.GetVertex();
this.edgeSet = [];
local toRemove = [];
// Sort the triangles so that we can cut off when we find the
// first live triangle.
liveTriangles.sort();
// Find triangles that have been completed
foreach(i, tri in liveTriangles) {
local s = tri.IsSouthOf(vertex);
if(s) {
completedTriangles.append(tri);
toRemove.append(i);
} else {
break;
}
}
// Remove the completed triangles
local offset = 0;
foreach(r in toRemove) {
liveTriangles.remove(r - offset);
offset++;
}
// Reset the remove list
toRemove = [];
// Check for non-empty circumcircles
foreach(i, tri in liveTriangles) {
// If the circumcircle is non-empty, mark the triangle for removal
// and add the edges to the edge buffer.
if(tri.u.GetDistance(vertex) <= tri.r) {
this.HandleEdge(tri.a, tri.b);
this.HandleEdge(tri.b, tri.c);
this.HandleEdge(tri.c, tri.a);
toRemove.append(i);
}
}
// Remove the triangles that were marked earlier
offset = 0;
foreach(r in toRemove) {
liveTriangles.remove(r - offset);
offset++;
}
// Build new triangles from the remaining edges in the buffer
foreach(e in this.edgeSet) {
liveTriangles.append(Triangle(e.a, e.b, vertex));
}
}
// Combine the two lists of triangles and sort them
local triangles = [];
triangles.extend(liveTriangles);
triangles.extend(completedTriangles);
triangles.sort();
// Accumulate a list of edges
local edgeAcc = SortedSet();
foreach(tri in triangles) {
local notSuper = !arraycontains(superVertices, tri.a) && !arraycontains(superVertices, tri.b) && !arraycontains(superVertices, tri.c);
// If the triangle does not stem from any of the original super-vertices
// (i.e. the corners of the map) then add it to the graph.
if(notSuper) {
edgeAcc.RawInsert(Edge(tri.a, tri.b));
edgeAcc.RawInsert(Edge(tri.b, tri.c));
edgeAcc.RawInsert(Edge(tri.c, tri.a));
}
}
// Remove duplicate edges
edgeAcc.RemoveDuplicates();
// Build a graph from the accumulated triangles
foreach(edge in edgeAcc) {
this.edges.RawInsert(edge);
this.vertices.RawInsert(edge.a);
this.vertices.RawInsert(edge.b);
if(!this.data.rawin(edge.a.ToTile())) {
this.data[edge.a.ToTile()] <- SortedSet();
}
this.data[edge.a.ToTile()].RawInsert(edge.b);
if(!this.data.rawin(edge.b.ToTile())) {
this.data[edge.b.ToTile()] <- SortedSet();
}
this.data[edge.b.ToTile()].RawInsert(edge.a);
}
// Remove duplicate vertices
// this.vertices.RemoveDuplicates();
if(this.vertices.Len() < targets.Len()) {
AILog.Warning("Some targets were not captured in triangulation.");
// TODO - Handle this in a way that wont break adding vertices at a later time
}
AILog.Info(" Done.");
}
}
/*
* Process a new edge in mid triangulation. If the edge already exists then
* the dupicate is deleted, otherwise it is added to the list.
*/
function Triangulation::HandleEdge(a, b) {
local edge = Edge(a, b);
local idx = arrayfind(this.edgeSet, edge);
if(idx > 0) {
this.edgeSet.remove(idx);
} else {
this.edgeSet.append(edge);
}
}
RoadRunner/license.txt 0000644 0001750 0001750 00000043103 11225225146 012545 0 ustar b b GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
RoadRunner/manager/ 0000755 0001750 0001750 00000000000 11365463557 012012 5 ustar b b RoadRunner/manager/FinanceManager.nut 0000755 0001750 0001750 00000010715 11225225146 015370 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* FinanceManager.nut
*
* Handles all finance and accounting methods.
*
* Author: George Weller (Zutty)
* Created: 10/06/2008
* Version: 1.0
*/
class FinanceManager {
constructor() {
}
}
/*
* Ensure that the current bank balance is set to at least the specified
* amount, by borrowing the shortfall. If sufficient monay cannot be borrowed,
* the function returns false.
*/
function FinanceManager::EnsureFundsAvailable(amount) {
local bankBalance = AICompany.GetBankBalance(AICompany.COMPANY_SELF);
local success = true;
// Only proceed if we actually need to borrow anything
if(amount > bankBalance) {
local amountToBorrow = amount - bankBalance;
local requiredLoan = AICompany.GetLoanAmount() + amountToBorrow;
// Increase our loan if we are within the limit
if(requiredLoan > AICompany.GetMaxLoanAmount()) {
AILog.Info(" Can't borrow enough money!");
requiredLoan = AICompany.GetMaxLoanAmount() - 1;
}
AILog.Info(" Need to borrow an extra " + amountToBorrow);
success = AICompany.SetMinimumLoanAmount(requiredLoan);
if(!success) {
AILog.Info(" ERROR: " + AIError.GetLastErrorString());
}
}
return success;
}
/*
* Ensure that the bank balance is set to exactly (to withinthe loan repayment
* step resolution) the specified float amount, by either borrowing or
* repaying the loan. If we were able to borrow or repay as required, the
* function returns true.
*/
function FinanceManager::MaintainFunds(float) {
local bankBalance = AICompany.GetBankBalance(AICompany.COMPANY_SELF);
local success = true;
if(bankBalance > float) {
success = FinanceManager.RepayLoan(float);
} else {
success = FinanceManager.EnsureFundsAvailable(float)
}
return success;
}
/*
* Repay enough of the loan such that the bank balance has rougly equal to the
* specified float remaining. If we were able to repay the load, or if no
* repayments were necessary the function returns true.
*/
function FinanceManager::RepayLoan(float, quiet = false) {
local bankBalance = AICompany.GetBankBalance(AICompany.COMPANY_SELF);
local currentLoan = AICompany.GetLoanAmount();
local success = true;
// Only proceed if we have a loan and we are capable of repaying
if((currentLoan > 0) && (bankBalance > float)) {
local amountToRepay = bankBalance - float;
local requiredLoan = currentLoan - amountToRepay;
// If we can afford to pay it all off then do so
if(requiredLoan < 0) {
success = AICompany.SetLoanAmount(0);
if(success && !quiet) {
AILog.Info(" Paid off loan!");
}
} else {
// Otherwise just pay enough off to retain our float
success = AICompany.SetMinimumLoanAmount(requiredLoan);
if(AICompany.GetLoanAmount() < currentLoan && !quiet) {
AILog.Info(" Repaid " + amountToRepay);
}
}
}
return success;
}
/*
* Borrow the specified amount. The function returns true if we were able to
* borrow enough.
*/
function FinanceManager::Borrow(amount = -1) {
if(amount < 0) {
amount = AICompany.GetLoanInterval();
}
local success = false;
local requiredLoan = AICompany.GetLoanAmount() + amount;
// Only proceed if we are able to borrow enough
if(requiredLoan <= AICompany.GetMaxLoanAmount()) {
success = AICompany.SetMinimumLoanAmount(requiredLoan);
}
return success;
}
/*
* Returns the total amount of money that is available, including the current
* bank balance and any further loan that can be taken out.
*/
function FinanceManager::GetAvailableFunds() {
return AICompany.GetBankBalance(AICompany.COMPANY_SELF) + (AICompany.GetMaxLoanAmount() - AICompany.GetLoanAmount());
}
/*
* Checks if we can afford the specified amount, based on the
* GetAvailableFunds() function.
*/
function FinanceManager::CanAfford(cost) {
return (cost < FinanceManager.GetAvailableFunds());
} RoadRunner/manager/LandManager.nut 0000755 0001750 0001750 00000030577 11225225146 014713 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* LandManager.nut
*
* A helper class for land and tile based functions
*
* Author: George Weller (Zutty)
* Created: 29/05/2008
* Version: 1.0
*/
class LandManager {
}
/*
* Checks if the tile can be cleared.
*/
function LandManager::IsClearable(tile) {
local _ = AITestMode();
return AITile.DemolishTile(tile);
}
/*
* Returns the cost of demolishing the tile.
*/
function LandManager::GetLandValue(tile) {
local _ = AITestMode();
local acc = AIAccounting();
AITile.DemolishTile(tile);
return acc.GetCosts();
}
/*
* Get the adjacent tiles as an array
*/
function LandManager::GetAdjacentTiles(tile) {
return [
tile - AIMap.GetTileIndex(1,0),
tile + AIMap.GetTileIndex(1,0),
tile + AIMap.GetTileIndex(0,1),
tile - AIMap.GetTileIndex(0,1)
];
}
/*
* Get the adjacent tiles as an AIList
*/
function LandManager::GetAdjacentTileList(tile) {
local adj = AITileList();
adj.AddTile(tile - AIMap.GetTileIndex(1,0));
adj.AddTile(tile + AIMap.GetTileIndex(1,0));
adj.AddTile(tile + AIMap.GetTileIndex(0,1));
adj.AddTile(tile - AIMap.GetTileIndex(0,1));
return adj;
}
/*
* Get the true height of the specified tile, as the highest of the tile's
* four corners
*/
function LandManager::GetTrueHeight(tile) {
local heights = [
AITile.GetHeight(tile),
AITile.GetHeight(tile + AIMap.GetTileIndex(1,1)),
AITile.GetHeight(tile + AIMap.GetTileIndex(1,0)),
AITile.GetHeight(tile + AIMap.GetTileIndex(0,1))
];
heights.sort();
return heights[3];
}
/*
* Get the tile that must be started from to approach a sloped tile from below.
*/
function LandManager::GetSlopeApproachTile(tile) {
local approaches = AITileList();
local slope = AITile.GetSlope(tile);
if(slope == AITile.SLOPE_NE) {
approaches.AddTile(tile - AIMap.GetTileIndex(1, 0));
} else if(slope == AITile.SLOPE_SW) {
approaches.AddTile(tile + AIMap.GetTileIndex(1, 0));
} else if(slope == AITile.SLOPE_NW) {
approaches.AddTile(tile + AIMap.GetTileIndex(0, 1));
} else if(slope == AITile.SLOPE_SE) {
approaches.AddTile(tile - AIMap.GetTileIndex(0, 1));
}
return approaches;
}
/*
* Get the tile that must be ended with to exit a sloped tile from above.
*/
function LandManager::GetSlopeExitTile(tile) {
local approaches = AITileList();
local slope = AITile.GetSlope(tile);
if(slope == AITile.SLOPE_NE) {
approaches.AddTile(tile + AIMap.GetTileIndex(1, 0));
} else if(slope == AITile.SLOPE_SW) {
approaches.AddTile(tile - AIMap.GetTileIndex(1, 0));
} else if(slope == AITile.SLOPE_NW) {
approaches.AddTile(tile - AIMap.GetTileIndex(0, 1));
} else if(slope == AITile.SLOPE_SE) {
approaches.AddTile(tile + AIMap.GetTileIndex(0, 1));
}
return approaches;
}
/*
* Checks if anything can be built flat on the tile.
*/
function LandManager::IsLevel(tile) {
local slope = AITile.GetSlope(tile);
local cornerDown = (slope == AITile.SLOPE_NWS || slope == AITile.SLOPE_WSE || slope == AITile.SLOPE_SEN || slope == AITile.SLOPE_ENW);
return (slope == AITile.SLOPE_FLAT || cornerDown);
}
/*
* Checks if the tile is sloped in any diretion.
*/
function LandManager::IsIncline(tile) {
local slope = AITile.GetSlope(tile);
local smooth = (slope == AITile.SLOPE_NW || slope == AITile.SLOPE_SW || slope == AITile.SLOPE_SE || slope == AITile.SLOPE_NE);
local cornerUp = (slope == AITile.SLOPE_W || slope == AITile.SLOPE_S || slope == AITile.SLOPE_E || slope == AITile.SLOPE_N);
return (smooth || cornerUp);
}
/*
* Checks if the tile is sloped in only one direction
*/
function LandManager::IsSmooth(tile) {
local slope = AITile.GetSlope(tile);
local smooth = (slope == AITile.SLOPE_NW || slope == AITile.SLOPE_SW || slope == AITile.SLOPE_SE || slope == AITile.SLOPE_NE);
return smooth;
}
/*
* Gets the direction of movement between A and B
*/
function LandManager::GetDirection(a, b) {
local dir = -1;
local diff = b - a;
if(diff == AIMap.GetTileIndex(-1,0)) {
dir = PathZilla.DIR_NORTH;
} else if(diff == AIMap.GetTileIndex(1,0)) {
dir = PathZilla.DIR_SOUTH;
} else if(diff == AIMap.GetTileIndex(0,1)) {
dir = PathZilla.DIR_EAST;
} else if(diff == AIMap.GetTileIndex(0,-1)) {
dir = PathZilla.DIR_WEST;
}
return dir;
}
/*
* Checks if the specified tile is a road station tile of any road type.
*/
function LandManager::IsDriveThroughRoadStationAny(tile) {
// First check if there is a staion of the current type
local isDriveThroughRoadStation = AIRoad.IsDriveThroughRoadStationTile(tile);
// If not, change road types and check again
if(!isDriveThroughRoadStation) {
// Buffer the current type
local prevType = AIRoad.GetCurrentRoadType();
// Change types (there are only two at the time of implementation)
if(prevType == AIRoad.ROADTYPE_ROAD) {
AIRoad.SetCurrentRoadType(AIRoad.ROADTYPE_TRAM);
} else {
AIRoad.SetCurrentRoadType(AIRoad.ROADTYPE_ROAD);
}
// Check again
isDriveThroughRoadStation = AIRoad.IsDriveThroughRoadStationTile(tile);
// Reset the previous road type
AIRoad.SetCurrentRoadType(prevType);
}
return isDriveThroughRoadStation;
}
/*
* Checks if the specified tile is a road station tile of any road type.
*/
function LandManager::IsRoadStationAny(tile) {
// First check if there is a staion of the current type
local isRoadStation = AIRoad.IsRoadStationTile(tile);
// If not, change road types and check again
if(!isRoadStation) {
// Buffer the current type
local prevType = AIRoad.GetCurrentRoadType();
// Change types (there are only two at the time of implementation)
if(prevType == AIRoad.ROADTYPE_ROAD) {
AIRoad.SetCurrentRoadType(AIRoad.ROADTYPE_TRAM);
} else {
AIRoad.SetCurrentRoadType(AIRoad.ROADTYPE_ROAD);
}
// Check again
isRoadStation = AIRoad.IsRoadStationTile(tile);
// Reset the previous road type
AIRoad.SetCurrentRoadType(prevType);
}
return isRoadStation;
}
/*
* Checks if the tile can have road or road station built on it.
*/
function LandManager::IsRoadable(tile) {
return AITile.IsBuildable(tile) || AITile.HasTransportType(tile, AITile.TRANSPORT_ROAD);
}
// --- Wormhole Management Functions ---
/*
* Selects the fastest bridge type to connect the specified two tiles
*/
function LandManager::ChooseBridgeType(aTile, bTile) {
local bridgeType = -1;
local length = AIMap.DistanceMax(aTile, bTile) + 1;
// Get a list of all bridge types
local list = AIBridgeList_Length(length);
// Disregard those which we can't afford
list.Valuate(AIBridge.GetPrice, length);
list.RemoveAboveValue(FinanceManager.GetAvailableFunds());
// Sort by their maximum speed
list.Valuate(AIBridge.GetMaxSpeed);
// Get the best bridge, if one exists
if(list.Count() > 0) {
bridgeType = list.Begin();
}
return bridgeType;
}
/*
* Checks if its possible to build a bridge between the specified two tiles
*/
function LandManager::CanBeBridged(aTile, bTile) {
local length = AIMap.DistanceMax(aTile, bTile) + 1;
return (length <= PathZilla.MAX_BRIDGE_LENGTH) && (AIBridgeList_Length(length).Count() > 0);
}
/*
* Finds where a proposed bridge built at the specified tile would end
*/
function LandManager::FindBridgeOtherEnds(tile) {
local slope = AITile.GetSlope(tile);
local otherEnd = tile;
local offsets = {};
local otherEnds = {};
// First check smooth slopes
if(slope == AITile.SLOPE_NE) {
offsets[0] <- AIMap.GetTileIndex(1,0);
} else if(slope == AITile.SLOPE_SW) {
offsets[0] <- AIMap.GetTileIndex(-1,0);
} else if(slope == AITile.SLOPE_NW) {
offsets[0] <- AIMap.GetTileIndex(0,1);
} else if(slope == AITile.SLOPE_SE) {
offsets[0] <- AIMap.GetTileIndex(0,-1);
// Then check the single corner slopes
} else if(slope == AITile.SLOPE_N) {
offsets[0] <- AIMap.GetTileIndex(1,0);
offsets[1] <- AIMap.GetTileIndex(0,1);
} else if(slope == AITile.SLOPE_S) {
offsets[0] <- AIMap.GetTileIndex(-1,0);
offsets[1] <- AIMap.GetTileIndex(0,-1);
} else if(slope == AITile.SLOPE_W) {
offsets[0] <- AIMap.GetTileIndex(0,1);
offsets[1] <- AIMap.GetTileIndex(1,0);
} else if(slope == AITile.SLOPE_E) {
offsets[0] <- AIMap.GetTileIndex(0,-1);
offsets[0] <- AIMap.GetTileIndex(-1,0);
}
foreach(idx, offset in offsets) {
otherEnds[idx] <- tile;
do {
otherEnds[idx] += offset;
} while(AIMap.IsValidTile(otherEnds[idx]) && AITile.IsWaterTile(otherEnds[idx]));
if(!AIMap.IsValidTile(otherEnds[idx]) || AITile.IsWaterTile(otherEnds[idx])) {
otherEnds[idx] = tile;
}
}
return otherEnds;
}
/*
* Get the tile from which to correctly approach a bridge or tunnel going from
* aTile to bTile
*/
function LandManager::GetApproachTile(aTile, bTile) {
local approachTile = aTile;
local goingNS = (AIMap.GetTileY(aTile) == AIMap.GetTileY(bTile));
local goingEW = (AIMap.GetTileX(aTile) == AIMap.GetTileX(bTile));
if(goingNS && AIMap.GetTileX(aTile) > AIMap.GetTileX(bTile)) {
approachTile += AIMap.GetTileIndex(1, 0);
} else if(goingNS && AIMap.GetTileX(aTile) < AIMap.GetTileX(bTile)) {
approachTile += AIMap.GetTileIndex(-1, 0);
} else if(goingEW && AIMap.GetTileY(aTile) > AIMap.GetTileY(bTile)) {
approachTile += AIMap.GetTileIndex(0, 1);
} else if(goingEW && AIMap.GetTileY(aTile) < AIMap.GetTileY(bTile)) {
approachTile += AIMap.GetTileIndex(0, -1);
}
return approachTile;
}
/*
* Get the tile from which to correctly approach a bridge starting at the
* specified tile
*/
function LandManager::GetBridgeApproachTile(bridgeTile) {
return LandManager.GetApproachTile(bridgeTile, AIBridge.GetOtherBridgeEnd(bridgeTile));
}
/*
* Get the tile from which to correctly approach a tunnel starting at the
* specified tile
*/
function LandManager::GetTunnelApproachTile(tunnelTile) {
return LandManager.GetApproachTile(tunnelTile, AITunnel.GetOtherTunnelEnd(tunnelTile));
}
/*
* Get the tile from which to correctly exit a bridge or tunnel going from
* aTile to bTile
*
* This is the reverse of GetApproachTile()
*/
function LandManager::GetExitTile(aTile, bTile) {
return LandManager.GetApproachTile(bTile, aTile);
}
/*
* Get the tile from which to correctly exit a bridge starting at the
* specified tile
*/
function LandManager::GetBridgeExitTile(bridgeTile) {
return LandManager.GetExitTile(bridgeTile, AIBridge.GetOtherBridgeEnd(bridgeTile));
}
/*
* Get the tile from which to correctly exit a tunnel starting at the
* specified tile
*/
function LandManager::GetTunnelExitTile(tunnelTile) {
return LandManager.GetExitTile(tunnelTile, AITunnel.GetOtherTunnelEnd(tunnelTile));
}
/*
* Given a starting tile for a bridge or tunnel and the correct exit tile,
* infer the tile at which the bridge or tunnel ends.
*/
function LandManager::InferOtherEndTile(aTile, exitTile) {
local otherEnd = exitTile;
local goingNS = (AIMap.GetTileY(aTile) == AIMap.GetTileY(exitTile));
local goingEW = (AIMap.GetTileX(aTile) == AIMap.GetTileX(exitTile));
if(goingNS && AIMap.GetTileX(aTile) > AIMap.GetTileX(exitTile)) {
otherEnd += AIMap.GetTileIndex(1, 0);
} else if(goingNS && AIMap.GetTileX(aTile) < AIMap.GetTileX(exitTile)) {
otherEnd += AIMap.GetTileIndex(-1, 0);
} else if(goingEW && AIMap.GetTileY(aTile) > AIMap.GetTileY(exitTile)) {
otherEnd += AIMap.GetTileIndex(0, 1);
} else if(goingEW && AIMap.GetTileY(aTile) < AIMap.GetTileY(exitTile)) {
otherEnd += AIMap.GetTileIndex(0, -1);
}
return otherEnd;
}
/*
* Get the cargo acceptance on a single tile. This will be obsolete once
* OpenTTD 0.7.2 is released.
*/
function LandManager::CargoAcceptanceOnTile(tile, cargo) {
local outer = AITile.GetCargoAcceptance(tile, cargo, 1, 1, 3);
local a = AITile.GetCargoAcceptance(tile - AIMap.GetTileIndex(2,2), cargo, 2, 1, 1);
local b = AITile.GetCargoAcceptance(tile - AIMap.GetTileIndex(-2,2), cargo, 1, 2, 1);
local c = AITile.GetCargoAcceptance(tile - AIMap.GetTileIndex(2,-1), cargo, 1, 2, 1);
local d = AITile.GetCargoAcceptance(tile - AIMap.GetTileIndex(-1,-2), cargo, 2, 1, 1);
return outer - (a + b + c + d);
} RoadRunner/manager/TownManager.nut 0000755 0001750 0001750 00000007501 11364774404 014765 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* TownManager.nut
*
* Handles all transport agnostic town-based functions.
*
* Author: George Weller (Zutty)
* Created: 21/03/2009
* Version: 1.0
*/
class TownManager {
constructor() {
}
}
/*
* Find the nearest town to the specified tile.
*/
function TownManager::FindNearestTown(tile) {
local townList = AITownList();
townList.Valuate(AITown.GetDistanceManhattanToTile, tile);
townList.Sort(AIAbstractList.SORT_BY_VALUE, true);
return townList.Begin()
}
/*
* Check if the company is allowed to build anything in the specified town.
*/
function TownManager::CanBuildInTown(town) {
local rating = AITown.GetRating(town, AICompany.ResolveCompanyID(AICompany.COMPANY_SELF));
return (rating == AITown.TOWN_RATING_NONE || rating > AITown.TOWN_RATING_VERY_POOR);
}
function TownManager::BuildStatue(town) {
if (!AITown.HasStatue(town)&&AITown.IsActionAvailable(town,AITown.TOWN_ACTION_BUILD_STATUE)) AITown.PerformTownAction(town, AITown.TOWN_ACTION_BUILD_STATUE);
return;
}
/*
* Try to improve the local authority rating by bribing and/or building trees.
*/
function TownManager::HandleRating(town) {
local rating = AITown.GetRating(town, AICompany.ResolveCompanyID(AICompany.COMPANY_SELF));
local townLocation = AITown.GetLocation(town);
// If the rating is low, take steps to improve it
if(rating < AITown.TOWN_RATING_GOOD) {
// See if we can bribe the town
local canBribe = (AIGameSettings.GetValue("economy.bribe") == 1);
if(canBribe && rating < AITown.TOWN_RATING_POOR && FinanceManager.CanAfford(PathZilla.BRIBE_THRESHOLD)) {
AITown.PerformTownAction(town, AITown.TOWN_ACTION_BRIBE);
}
}
// Update the rating
rating = AITown.GetRating(town, AICompany.ResolveCompanyID(AICompany.COMPANY_SELF));
// If the rating is still low, take steps to improve it
if(rating < AITown.TOWN_RATING_GOOD) {
// Get a list of tiles to search in
local searchRadius = min(AIMap.DistanceFromEdge(townLocation) - 1, PathZilla.MAX_TOWN_RADIUS);
local offset = AIMap.GetTileIndex(searchRadius, searchRadius);
// After that, find places we can build trees
local tileList = AITileList();
tileList.AddRectangle(townLocation - offset, townLocation + offset);
foreach(tile, _ in tileList) {
local suitable = (!AITile.IsWithinTownInfluence(tile, town) && AITile.IsBuildable(tile) && !AITile.HasTreeOnTile(tile));
tileList.SetValue(tile, (suitable) ? 1 : 0);
}
tileList.RemoveValue(0);
foreach(tile, _ in tileList) {
local r = AITile.GetDistanceManhattanToTile(tile, townLocation) + AIBase.RandRange(6) - 3;
tileList.SetValue(tile, r);
}
tileList.Sort(AIAbstractList.SORT_BY_VALUE, true);
// For the places that are available, build a "green belt" around the town
if(!tileList.IsEmpty()) {
local expenditure = 0;
local tile = tileList.Begin();
while(AITown.GetRating(town, AICompany.ResolveCompanyID(AICompany.COMPANY_SELF)) < AITown.TOWN_RATING_GOOD
&& expenditure < PathZilla.MAX_TREE_SPEND && tileList.HasNext()) {
local acc = AIAccounting();
for(local i = 0; i < 4; i++) {
AITile.PlantTree(tile);
}
expenditure += acc.GetCosts();
tile = tileList.Next();
}
}
}
}
RoadRunner/pathfinding/ 0000755 0001750 0001750 00000000000 11365463557 012673 5 ustar b b RoadRunner/readme.txt 0000755 0001750 0001750 00000015706 11225225146 012373 0 ustar b b PathZilla - © George Weller 2009
================================
Version 6 (r279), 08/07/2009 - tested against 0.7.1
PathZilla is a networking AI. The focus of this AI is on high level planning
and neat, realistic construction.
To get the best results from PathZilla you are *strongly* advised to activate
the advanced setting "Stations" > "Allow drive-through road stops on town owned
roads". PathZilla will still function correctly but will need to perform more
demolition in towns.
The AI has a number of settings which the user should change to their personal
preference. If desired, the player will find a less challenging opponent if
setting planning speed to very slow, turning aggression off, and setting
traffic to low. Regardless of these settings though, the AI will always attempt
to build a complete network and turn a profit.
Settings
--------
* Planning speed - Governs how much time the AI will wait between actions.
If set to 'Fastest', the AI will not wait at all.
* Compete aggressively - If set off, the AI will consider competitors
stations and services when judging how well serviced a town or industry
is, and will not attempt to build close to their stations
* Level of traffic - Governs the fleet size estimates and limits. A higher
setting will allow the AI to build more vehicles
* Route cargo through towns - If set on, the AI will link industries to
their nearest towns if applicable, otherwise it will link industries
directly together
* Build windy lanes - If set on, the AI will makes its roads follow the
terrain, giving them a more natural "windy" appearance. This is applied
to all roads before 1950, and to roads between small towns without
2x2/3x3 grids before 1990
Features
--------
* High-level network planning using graph theory
* Uses two tiers of pathfinding to improve line re-use
* Aesthetic pathfinding builds tram lines alongside roads, honours town
grid layouts, and build windy country lanes where applicable
* Full support for articulated vehicles and trams, where available
* Builds "green belt" around towns to improve local authority rating
* Supports NewGRF vehicles (tested with Zephyris' eGRVTS, George's Long
Vehicles v4, and PikkaBird's HOVS)
* Supports NewGRF house and industry sets (tested with TTRS, George's ECS
Vectors and PikkaBird's PBI)
* Builds multiple road stations per town and maintains fleet sizes
* Supports save/load and difficulty settings
* Supports all primary industries
Changelog
---------
v6 - 08/07/2009
* Added support for primary industries
* Added support for NewGRF house sets
* Improved 2x2/3x3 town grid handling
* Construction of windy "country lanes" at early dates and in small towns
* Improved station and fleet size management
* Added configuration option for amount of traffic to generate
* Limits on fleet sizes to reduce incidence of grid-lock
* Allow AI to sell vehicles from unprofitable services
* Better handling of construction in traffic
* Improved accuracy of triangulation graph
* Reduce initial processing steps to allow AI to start faster
* Build fewer stations initially
* Better handling of failure and error conditions when creating services
* Fixed bug: Crash when there are too few towns for a tram route
* Many other bug fixes
v5 - 24/01/2009
* Changed to library pathfinder (with modifications)
* Updated to latest API changes
* Build depots in towns rather than in the middle of roads
* Split different road types where possible to reduce congestion
* Honour 2x2/3x3 town grid layouts where possible
* Re-wrote station placement to be more flexible
* Maintain fleet sizes based on waiting cargo
* Enable bribing of town authority
* Build trees around a town to improve local authority rating
* Re-attempt pathfinding if construction fails
* Don't build tunnels if funds are too low
* Fixed bug: First order was not updated
* Fixed bug: Don't try to compute shortest paths if there are less than two nodes
* Fixed bug: Crash when adding vehicles to service after load
v4 - 01/10/2008
* Improved DTRS support and enabled ARVs where possible
* Added support for trams
* Added generic cargo support for towns only (i.e. mail)
* Improved vehicle selection criteria. Gives more variety with large sets
like eGRVTS
* Changed fleet size and property management to improve profitability at
early dates (pre-1950)
* Efficiency improvements on busy maps
* Fixed bug: Only connect to an existing road when pathfinding if a
connection can be made to its neighbours
* Fixed bug: Roads can now cross without joining
* Fixed bug: Calculate combined station coverage over a single town
correctly
* Fixed bug: Don't crash if there are fewer than three towns
* Fixed other, minor bugs
v3 - 16/08/2008
* Added save/load support
* Introduced size limit for service planning data sets, to reduce memory
usage and save file size
* Added support for AI difficulty settings, which scale work intervals and
aggression
* Added an 'aggressive' setting. When PathZilla is not aggressive it will
try to avoid building stations near to competitors
* Fixed bug: Do not try to implement any more services when the vehicle
limit has been reached
v2 - 11/08/2008
* Vastly improved performance by optimising graph algorithms
* Introduced limit on number of targets (towns) that will be included in
the master graph, to make very large maps (2048x2048) playable
* Changed service selection routine to process one town at a time, further
improving performance
* No longer allow use of articulated vehicles (temporary fix)
* Fixed bug: Do not try to build bus stops adjacent to competitor's
stations or on their property
* Fixed bug: Do not try to build any bus stops if the local authority
rating is too low
* Fixed bug: Added check to ensure that the entrance road to a depot has
been built
* Fixed various other bugs
* Changed license to GPL v2
v1.1 - 29/07/2008
* Changed require() statements to use cross-platform slashes
* Reduced work intervals to make AI more aggressive
Known Issues
------------
* Has a tendency to "wipe-out" towns when the game runs multiple instances
of PathZilla, if 'aggressive' is set on
* Does not include airports in competitor check for aggression setting
* May tourists incorrectly where towns are directly adjacent
To Do
-----
* Expand and upgrade busy stations
* Upgrade and replace old vehicles
Wish List
---------
* Rail, aircraft, and ship support
* On the fly terraforming
* Build 2-lane highways (once one-way road support is introduced)
* Re-model poorly laid out towns
If you have any questions or comments you can email me at
george.weller@gmail.com or visit one of the following pages...
TT-Forums Thread - http://www.tt-forums.net/viewtopic.php?f=65&t=38645
Google Code Site - http://code.google.com/p/ottd-noai-pathzilla/ RoadRunner/schema/ 0000755 0001750 0001750 00000000000 11365463557 011640 5 ustar b b RoadRunner/service/ 0000755 0001750 0001750 00000000000 11365463557 012040 5 ustar b b RoadRunner/struct/ 0000755 0001750 0001750 00000000000 11365463557 011724 5 ustar b b RoadRunner/struct/BinaryHeap.nut 0000755 0001750 0001750 00000007202 11225225146 014463 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* BinaryHeap.nut
*
* A Squirrel implementation of a binary heap. This is a fast and efficient
* data structure for A* pathfinding, as we always select the node at the top
* of the heap, and we aren't concerned about the ordering of other nodes.
*
* The underlying array should not be accessed for any reason.
*
* Author: George Weller (Zutty)
* Created: 29/05/2008
* Version: 1.1
*/
/*
* Constructs a new empty heap.
*/
class BinaryHeap extends Collection {
// Serialization constants
CLASS_NAME = "BinaryHeap";
constructor() {
::Collection.constructor();
}
}
/*
* Insert a new item into the heap. To maintain the properties of the heap,
* items must only be inserted by the use of this method.
*/
function BinaryHeap::Insert(item) {
if(item != null) {
this.data.append(item);
this.BubbleUp(this.data.len() - 1);
}
}
/*
* Remove the item at the root of the heap. To maintain the shape property,
* items can ONLY be removed from the root.
*/
function BinaryHeap::Pop() {
if(this.data.len() == 0) {
return null;
}
// Copy the top item
local topItem = this.data[0];
// Move the bottom item to the top
local pos = this.data.len() - 1;
this.data[0] = this.data[pos]
this.data.remove(pos);
// Restore the heap property
this.BubbleDown(0);
return topItem;
}
/*
* Return the item at the root of the heap without removing it.
*/
function BinaryHeap::Peek() {
if(this.data.len() == 0) {
return null;
}
// Return the top item
return this.data[0];
}
/*
* Bubble the item at the specified index down to its correct position, to
* maintain the heap property. This method is for private use only.
*/
function BinaryHeap::BubbleDown(index) {
if(index * 2 >= this.data.len()) {
return;
}
local left = index * 2;
local right = index * 2 + 1;
local smallest = left;
if(right < (this.data.len() - 1) && this.data[left] > this.data[right]) {
smallest = right;
}
if(this.data[smallest] < this.data[index]) {
this.Swap(index, smallest);
return this.BubbleDown(smallest);
}
}
/*
* Bubble the item at the specified index up to its correct position, to
* maintain the heap property. This method is for private use only.
*/
function BinaryHeap::BubbleUp(index) {
if(index < 1) {
return;
}
local parnt = index / 2;
if(this.data[index] < this.data[parnt]) {
this.Swap(parnt, index);
return this.BubbleUp(parnt);
}
}
/*
* Swap the items at the specified indeces. This method is for private use
* only.
*/
function BinaryHeap::Swap(p, q) {
local buffer = data[p];
data[p] = data[q];
data[q] = buffer;
}
function BinaryHeap::Prune(n) {
if(this.data.len() > n) {
this.data = this.data.slice(0, n);
}
}
/*
* Used to enable to use of foreach on a binary heap.
*/
function BinaryHeap::_nexti(idx) {
return (this.Len() >= 1) ? "_pop" : null;
}
/*
* Used to enable to use of foreach on a binary heap.
*/
function BinaryHeap::_get(idx) {
return (idx == "_pop") ? this.Pop() : null;
}
RoadRunner/struct/Collection.nut 0000755 0001750 0001750 00000004267 11225225146 014544 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* Collection.nut
*
* A generic collection of data based on an array. This class implements some
* generic methods for concrete collection classes to use. This class should
* not be instantiated directly.
*
* Author: George Weller (Zutty)
* Created: 14/08/2008
* Version: 1.0
*/
class Collection {
data = null;
constructor() {
this.data = [];
}
}
/*
* Get the raw data in this collection.
*/
function Collection::GetData() {
return this.data;
}
/*
* Get the number of items in the collection.
*/
function Collection::Len() {
return this.data.len();
}
/*
* Saves an array to a table.
*/
function Collection::SerializeArray(arr) {
local c = Collection();
c.data = arr;
return c.Serialize();
}
/*
* Loads an array from a table.
*/
function Collection::UnserializeArray(saveData) {
local c = Collection();
c.Unserialize(saveData);
return c.data;
}
/*
* Saves data to a table.
*/
function Collection::Serialize() {
local saveData = [];
if(this.data.len() > 0) {
foreach(item in this.data) {
saveData.append(item.Serialize());
}
saveData.append(this.data.top().getclass().CLASS_NAME);
}
return saveData;
}
/*
* Loads data from a table.
*/
function Collection::Unserialize(saveData) {
this.data = [];
if(saveData.len() > 0) {
local className = saveData.pop();
foreach(item in saveData) {
local newItem = ::load_class(className).instance();
//newItem.constructor();
newItem.Unserialize(item);
this.data.append(newItem);
}
}
} RoadRunner/struct/Map.nut 0000755 0001750 0001750 00000013533 11337674656 013204 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* Map.nut
*
* A generic map structure. This structure uses a table to store the data and
* associate it to keys, and an array of keys to allow the Map to be sorted and
* to allow traversal. It is very important to ensure that the array of keys is
* kept up-to-date, or operations such as SortBy() or RemoveAll() will fail.
*
* Author: George Weller (Zutty)
* Created: 27/06/2009
* Version: 1.0
*/
class Map {
// Serialization constants
CLASS_NAME = "Map";
data = null;
keys = null;
constructor() {
this.data = {};
this.keys = [];
}
}
/*
* Get the raw data in this collection.
*/
function Map::GetData() {
return this.data;
}
/*
* Insert a new item into the Map. The key will be determined using the
* _hashkey() method of the item, which must be specified.
*/
function Map::Insert(item) {
local key = item._hashkey();
this.data[key] <- item;
this.keys.append(key);
}
/*
* Adds all the items and keys from the specified Map.
*/
function Map::Extend(items) {
foreach(key, item in items.data) {
this.data[key] <- item;
this.keys.append(key);
}
this.RebuildKeys();
}
/*
* Rebuild the list of keys to match those in the main table.
*/
function Map::RebuildKeys() {
this.keys = [];
foreach(key, _ in this.data) {
this.keys.append(key);
}
}
/*
* Get the first item in the map as determined by the sorted order of the key
* array. This is guaranteed to be stable.
*/
function Map::Begin() {
return this.GetI(0);
}
/*
* Get the specified item in the map as determined by the sorted order of the
* key array. This is guaranteed to be stable.
*/
function Map::GetI(idx) {
return (this.data.len() > idx) ? this.data[this.keys[idx]] : null;
}
/*
* Re-sort the keys of the map.
*/
function Map::Sort() {
this.keys.sort();
}
/*
* Re-sort the map keys by a certain comparator function.
*/
function Map::SortBy(comparator) {
// Shouldn't have to do this - NEEDS FIXING
this.RebuildKeys();
// We want to sort the keys, but more useful to allow user to specify a
// comparator that sorts items instead.
local me = this;
this.keys.sort(function (a, b):(me, comparator) {
return comparator(me.data[a], me.data[b]);
});
}
/*
* Get the number of items in the map.
*/
function Map::Len() {
return this.data.len();
}
/*
* Remove the specified item from the map. The item is removed by its key,
* which is determined using the _hashkey() method.
*/
function Map::Remove(item) {
this.RemoveKey(item._hashkey());
}
/*
* Remove the item with the specified key from the map.
*/
function Map::RemoveKey(key) {
// Delete the item from the table
delete this.data[key];
// Delete the key from the keyset
foreach(idx, k in this.keys) {
if(k == key) {
this.keys.remove(idx);
break;
}
}
}
/*
* Remove all items from the map with keys found in the specified map.
*/
function Map::RemoveAll(items) {
foreach(key, item in items.data) {
if(key in this.data) {
this.RemoveKey(key);
}
}
}
/*
* Remove those items in the map that when passed to the supplied filter
* function yeild a false return value. The filter should take one
* argument and return true if the item should be filtered from the map.
*/
function Map::Filter(filterFn, ...) {
// Build an array of arguments for the filter function
local argv = [];
for(local i = 0; i < vargc; i++) {
argv.append(vargv[i]);
}
// Select the keys for items to be removed
local toRemove = [];
foreach(key, item in this.data) {
local args = [item];
args.extend(argv);
if(::arr_call(filterFn, args)) {
toRemove.append(key);
}
}
// Remove them
foreach(r in toRemove) {
this.RemoveKey(r);
}
}
/*
* Saves data to a table.
*/
function Map::Serialize() {
local saveData = {};
if(this.data.len() > 0) {
if(this.keys.len() > 0) {
saveData[CLASS_NAME] <- this.data[this.keys.top()].getclass().CLASS_NAME;
foreach(key, item in this.data) {
saveData[key] <- item.Serialize();
}
}
}
return saveData;
}
/*
* Loads data from a table.
*/
function Map::Unserialize(saveData) {
this.data = {};
if(saveData.len() > 0) {
local className = saveData[CLASS_NAME];
foreach(key, item in saveData) {
local newItem = ::load_class(className).instance();
//newItem.constructor();
newItem.Unserialize(item);
this.data[key] <- newItem;
}
}
}
/*
* Make a shallow copy of the map.
*/
function Map::_cloned(original) {
this.data = clone original.data;
this.keys = clone original.keys;
}
/*
* Get an item from to set using squirrel standard notation, e.g. map[1].
*/
function Map::_get(key) {
return this.data[key];
}
/*
* Set an item in the map by the given key.
*/
function Map::_set(key, value) {
this.data[key] = value;
}
/*
* Create a new slot in the map.
*/
function Map::_newslot(key, value) {
this.data[key] <- value;
this.keys.append(key);
}
/*
* Get the next index in the map. This is used by the squirrel foreach keyword.
* This uses an array of keys to specify sorted order and is guaranteed to be
* stable.
*/
function Map::_nexti(prevkey) {
if(prevkey == null) return (this.keys.len() == 0) ? null : this.keys[0];
local idx = ::arrayfind(this.keys, prevkey);
if(idx == this.keys.len() - 1) return null;
return this.keys[idx + 1]
}
RoadRunner/struct/SortedSet.nut 0000755 0001750 0001750 00000012264 11360300013 014344 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 1 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* SortedSet.nut
*
* A sorted set is a unique collection of items that are stored in sorted
* order.
*
* This is just a placeholder implementation, as I really can't be bothered to
* work on this for now!
*
* Author: George Weller (Zutty)
* Created: 15/06/2008
* Version: 1.2
*/
class SortedSet extends Collection {
// Serialization constants
CLASS_NAME = "SortedSet";
constructor() {
::Collection.constructor();
}
}
/*
* Add a new item to the set. If the sety already contains the item then it
* will not be added.
*/
function SortedSet::Insert(item) {
if(!this.Contains(item)) {
this.data.append(item);
//this.data.sort();
}
}
/*
* Adds a new item to the set without checking for duplicates. This is meant to
* be used in conjuection with the RemoveDuplicates() method afterwards.
*/
function SortedSet::RawInsert(item) {
this.data.append(item);
}
/*
* Re-sort the set.
*/
function SortedSet::Sort() {
this.data.sort();
}
function SortedSet::Len() {
return this.data.len();
}
/*
* Re-sort the set by a certain comparator function.
*/
function SortedSet::SortBy(comparator) {
this.data.sort(comparator);
}
/*
* Get the first item in the set.
*/
function SortedSet::Begin() {
return (this.Len() > 0) ? this.data[0] : null;
}
/*
* Remove the first item from the set and return it.
*/
function SortedSet::Pop() {
return (this.Len() > 0) ? this.data.pop() : null;
}
/*
* Check if the set contains the specified item.
*/
function SortedSet::Contains(item) {
foreach(elem in this.data) {
if(elem <= item && elem >= item) {
return true;
}
}
return false;
}
/*
* Perform a binary search on the set for the specified item, between the
* specified left and right side pointers.
*/
function SortedSet::BinarySearch(item, left, right) {
if(right < left) {
return -1;
}
local pivot = left + ((right - left) / 2);
if(this.data[pivot] == item) {
return pivot;
} else if(item < this.data[pivot]) {
return this.BinarySearch(item, left, pivot - 1);
} else {
return this.BinarySearch(item, pivot + 1, right);
}
}
/*
* Remove an item from the set.
*/
function SortedSet::Remove(item) {
foreach(idx, i in this.data) {
if(i <= item && i >= item) {
this.data.remove(idx);
break;
}
}
this.data.sort();
}
/*
* Remove all items in the specified set from this set.
*/
function SortedSet::RemoveAll(set) {
local toRemove = [];
foreach(idx, i in this.data) {
if(set.Contains(i)) {
toRemove.append(idx);
}
}
local offset = 0;
foreach(r in toRemove) {
this.data.remove(r - offset);
offset++;
}
}
/*
* Remove those items in the list that when passed to the supplied filter
* function yeild a false return value. The filter should take one
* argument and return true if the item should be filtered from the set.
*/
function SortedSet::Filter(filterFn, ...) {
local argv = [];
for(local i = 0; i < vargc; i++) {
argv.append(vargv[i]);
}
local toRemove = [];
foreach(idx, i in this.data) {
local args = [i];
args.extend(argv);
if(::arr_call(filterFn, args)) {
toRemove.append(idx);
}
}
local offset = 0;
foreach(r in toRemove) {
this.data.remove(r - offset);
offset++;
}
}
/*
* Remove the item at the specified index.
*/
function SortedSet::Removei(idx) {
this.data.remove(idx);
}
/*
* Remove all duplicate items in the set. The items in the set must implement
* an equals() method, returning true if two items are equal.
*/
function SortedSet::RemoveDuplicates() {
// Ensure that duplicates are adjacent
this.data.sort();
// Find duplicates
local toRemove = [];
local prev = null;
foreach(idx, item in this.data) {
if(item.equals(prev)) {
toRemove.append(idx);
}
prev = item;
}
// Remove the edges that were marked earlier
local offset = 0;
foreach(r in toRemove) {
this.data.remove(r - offset);
offset++;
}
}
/*
* Add all the items from another set to this one, removing duplicates.
*/
function SortedSet::Merge(set) {
this.data.extend(set.data);
this.RemoveDuplicates();
}
/*
* Make a shallow copy of the set.
*/
function SortedSet::_cloned(original) {
this.data = clone original.data;
}
/*
* Get a item from to set using squirrel standard notation, e.g. sortedSet[1].
*/
function SortedSet::_get(idx) {
return this.data[idx];
}
/*
* Get the next index in the set. This is used by the squirrel foreach keyword.
*/
function SortedSet::_nexti(previdx) {
return (previdx == null) ? ((this.data.len() == 0) ? null : 0)
: ((previdx == this.data.len() - 1) ? null : previdx + 1);
}
RoadRunner/graph/impl/ShortestPathTree.nut 0000755 0001750 0001750 00000010121 11375742607 016435 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* ShortestPathTree.nut
*
* The shortest path tree of a complete graph, based on Dijkstra's algorithm. A
* shortest path tree is a graph such that all nodes are connected to a
* specified root node by the shortest distance possible.
*
* Dijkstra's algorithm works by building up two lists; one of the best
* distance to the root found so far for each node, and the other of a previous
* node for each, to trace a path back to the root by a sort of linked list. We
* radiate out from the root node updating these lists as we go, and then when
* finished we compile the previous node list into a list of edges for the
* graph.
*
* Author: George Weller (Zutty)
* Created: 07/06/2008
* Version: 1.1
*/
class ShortestPathTree extends Graph {
constructor(masterGraph, sourceTile) {
Graph.constructor();
AILog.Info(" Computing shortest path tree...");
// Use a special case if there are less than three vertices
if(masterGraph.GetVertices().Len() < 3) {
// Just copy the data!
this.vertices = clone masterGraph.vertices;
this.edges = clone masterGraph.edges;
this.data = clone masterGraph.data;
AILog.Info(" Done");
return;
}
// Initialise
local queue = BinaryHeap();
local dist = {};
local prev = {};
local infinity = AIMap.GetMapSizeX() + AIMap.GetMapSizeY();
infinity = infinity * infinity; // Square it
local vtxMap = {};
foreach(v in masterGraph.GetVertices()) {
vtxMap[v.ToTile()] <- v;
}
// Initialise distance and previous node lists
foreach(v in masterGraph.GetVertices()) {
local tile = v.ToTile();
dist[tile] <- (tile == sourceTile) ? 0 : infinity;
prev[tile] <- null;
queue.Insert(DijkstraNode(tile, dist[tile]));
}
// Process each node in best first order
local steps = 0;
foreach(u in queue) {
// Only sleep once every PROCESSING_PRIORITY iterations
if(steps++ % PathZilla.PROCESSING_PRIORITY == 0) {
PathZilla.Sleep(1);
}
// Find the best cost node
local uTile = u.tile;
local uVertex = vtxMap[uTile];
// Get the vertices adjacent to the current one and update them
foreach(v in masterGraph.GetNeighbours(uVertex)) {
local vTile = v.ToTile();
local alt = dist[uTile] + AIMap.DistanceSquare(uTile, vTile);
// If the computed cost is better than the stored one then update
if(alt < dist[vTile]) {
dist[vTile] = alt;
prev[vTile] = uVertex;
queue.Insert(DijkstraNode(vTile, dist[vTile]));
}
}
if (steps>1000000){
AILog.Info(" Aborted");
return;
}
}
this.vertices = clone masterGraph.GetVertices();
// Compile the linked list of prev nodes into a graph
foreach(uTile, v in prev) {
if(v != null) {
local u = vtxMap[uTile];
local vTile = v.ToTile();
this.edges.RawInsert(Edge(u, v));
if(!this.data.rawin(uTile)) {
this.data[uTile] <- SortedSet();
}
this.data[uTile].RawInsert(v);
if(!this.data.rawin(vTile)) {
this.data[vTile] <- SortedSet();
}
this.data[vTile].RawInsert(u);
}
}
AILog.Info(" Done");
}
}
/*
* A node in a Dijkstra's algorithm search.
*/
class DijkstraNode {
tile = null;
dist = null;
constructor(t, d) {
this.tile = t;
this.dist = d;
}
}
/*
* Compare this node to another. This orders nodes by the shortest distance.
*/
function DijkstraNode::_cmp(node) {
return (this.dist == node.dist) ? 0 : ((this.dist < node.dist) ? -1 : 1);
}
RoadRunner/pathfinding/PathWrapper.nut 0000755 0001750 0001750 00000056132 11404744322 015654 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* PathWrapper.nut
*
* Wrapper for the (modified) library road pathfinder. This class handles
* general path finding a road construction with robust construction and a
* path reassesment mechanism sensetive to changes in the map. This class also
* implements a number of cost decorator functions, which are explained in the
* FindPath method below.
*
* Author: George Weller (Zutty)
* Created: 15/01/2009
* Version: 1.2
*/
class PathWrapper {
// Feature constants
FEAT_ROAD_LOOP = 1;
FEAT_SEPARATE_ROAD_TYPES = 2;
FEAT_GRID_LAYOUT = 3;
FEAT_DEPOT_ALIGN = 4;
FEAT_SHORT_SCOPE = 5;
FEAT_NO_WORMHOLES = 6;
FEAT_COUNTRY_LANE = 7;
FEAT_CHK_CONNECTION = 8
// Costs
COST_ROAD_LOOP = 3000;
COST_SEPARATE_ROAD_TYPES = 200;
COST_PARALLEL_BONUS = 50;
COST_GRID_LAYOUT = 1000;
COST_DEPOT_ALIGN = 3000;
COST_COUNTRY_LANE = 1750;
COST_CHK_CONNECTION = 0;
constructor() {
}
}
/*
* Find a path between the two specified tiles and then attempt to build it. All
* parameters are passed up to the FindPath and TryBuldPath methods.
*/
function PathWrapper::BuildRoad(fromTile, toTile, roadType, ignoreTiles = [], demolish = false, features = []) {
// First, try to find a path
local path = PathWrapper.FindPath(fromTile, toTile, roadType, ignoreTiles, demolish, features);
// If the path could not be found then there is nothing left to try
if(path == null) {
AILog.Error("Could not find a path!");
return false;
}
return PathWrapper.TryBuildPath(path, fromTile, toTile, roadType, ignoreTiles , demolish, features);
}
/*
* Try to build ithe speciofied path with the specified road type up to
* PathZilla.MAX_REPATH_TRIES timesm using the BuildPath method.
*/
function PathWrapper::TryBuildPath(path, fromTile, toTile, roadType, ignoreTiles = [], demolish = false, features = []) {
local tries = 0;
local success = -1;
// Try to build the path
do {
success = PathWrapper.BuildPath(path, roadType);
// If we failed, try to find the path from the point it went wrong
if(success != 0) {
path = PathWrapper.FindPath(fromTile, success, roadType, ignoreTiles, demolish, features);
}
} while(success > 0 && tries++ < PathZilla.MAX_REPATH_TRIES);
// If we still failed after a number of attempts, show an error message
if(success != 0) {
AILog.Error("Road cannot be built.")
return false;
}
return true;
}
/*
* Find a path between the specified tiles and return it. The path will be of
* type roadType, will go around any tiles specified in the ignoreTiles array.
* If demolish is true then the path may go through town houses. Additionally
* a number of features may be specified to control the layout of the eventual
* path. Any of the following can be specified...
*
* FEAT_ROAD_LOOP - Build a loop around the first tile in ignoreTiles
* FEAT_SEPARATE_ROAD_TYPES - Split road types apart to run in parallel
* FEAT_GRID_LAYOUT - Snap roads to 2x2/3x3 town layouts
* FEAT_DEPOT_ALIGN - Join a road to the entrace of a depot not its side
* FEAT_SHORT_SCOPE - Avoid wasting time on paths that are known to be short
* FEAT_NO_WORMHOLES - Disallow bridges and tunnels
*/
function PathWrapper::FindPath(fromTile, toTile, roadType, ignoreTiles = [], demolish = false, features = []) {
// Ignore traffic black spots
ignoreTiles.extend(ListToArray(::trafficBlackSpots));
// Initialise the pathfinder
local pathfinder = Road();
pathfinder.cost.allow_demolition = demolish;
pathfinder.cost.demolition = 2000;
pathfinder.cost.no_existing_road = 100;
pathfinder.cost.max_bridge_length = PathZilla.MAX_BRIDGE_LENGTH;
pathfinder.cost.bridge_per_tile = 600;
pathfinder.cost.tunnel_per_tile = 500;
pathfinder.InitializePath([fromTile], [toTile], 2, 20, ignoreTiles);
// Add on any additional features
foreach(feat in features) {
switch(feat) {
case PathWrapper.FEAT_ROAD_LOOP:
/* local sideRoadList = LandManager.GetAdjacentTileList(ignoreTiles[0]);
sideRoadList.RemoveTile(toTile);
sideRoadList.RemoveTile(LandManager.GetApproachTile(ignoreTiles[0], toTile));
local sideRoads = ListToArray(sideRoadList);
pathfinder.RegisterCostCallback(function (tile, prevTile, sideRoads) {
return (tile == sideRoads[0] || tile == sideRoads[1]) ? PathWrapper.COST_ROAD_LOOP : 0;
}, sideRoads);
*/ break;
case PathWrapper.FEAT_SEPARATE_ROAD_TYPES:
pathfinder.RegisterCostCallback(function (tile, prevTile, roadType) {
local diff = AIMap.GetMapSizeY() / (tile - prevTile);
local parrl = (AIRoad.IsRoadTile(tile + diff) && !AIRoad.HasRoadType(tile + diff, roadType))
|| (AIRoad.IsRoadTile(tile - diff) && !AIRoad.HasRoadType(tile - diff, roadType));
return ((AIRoad.IsRoadTile(tile) && !AIRoad.HasRoadType(tile, roadType)) ? PathWrapper.COST_SEPARATE_ROAD_TYPES : 0) + ((parrl) ? 0 : PathWrapper.COST_PARALLEL_BONUS);
}, roadType);
break;
case PathWrapper.FEAT_GRID_LAYOUT:
local n = AIGameSettings.GetValue("economy.town_layout").tointeger();
if((n >= 2 && n <= 4) && roadType == AIRoad.ROADTYPE_ROAD) {
local towns = AITownList();
// Find the nearest town to the "from" tile
towns.Valuate(AITown.GetDistanceManhattanToTile, fromTile);
towns.Sort(AIAbstractList.SORT_BY_VALUE, true);
local fTown = towns.Begin();
local fTile = AITown.GetLocation(fTown);
// Get some details about this town
local fType = AITown.GetRoadLayout(fTown);
local fSize = AITown.GetPopulation(fTown).tofloat();
local fn = (fType == AITown.ROAD_LAYOUT_2x2) ? 3 : ((fType == AITown.ROAD_LAYOUT_3x3) ? 4 : 0);
local fx = fTile % AIMap.GetMapSizeY();
local fy = fTile / AIMap.GetMapSizeY();
// Find the nearest town to the "to" tile
towns.Valuate(AITown.GetDistanceManhattanToTile, toTile);
towns.Sort(AIAbstractList.SORT_BY_VALUE, true);
local tTown = towns.Begin();
local tTile = AITown.GetLocation(tTown);
// If both towns are the same then we only need to check one grid
if(fTown == tTown && fn > 0) {
pathfinder.RegisterCostCallback(function (tile, prevTile, n, fx, fy) {
local x = tile % AIMap.GetMapSizeY();
local y = tile / AIMap.GetMapSizeY();
local dx = abs(x - fx) % n;
local dy = abs(y - fy) % n;
local len = AIMap.DistanceManhattan(tile, prevTile);
if(len > 1) {
local px = prevTile % AIMap.GetMapSizeY();
local py = prevTile / AIMap.GetMapSizeY();
//local pdx = abs(px - fx) % n;
local pdy = abs(py - fy) % n;
if((x == px && dx == 0) || (y == py && dy == 0)) return 0;
local m = 0;
if(dy == pdy) {
m = ((dx == 0) ? 1 : 0) + (len / n);
} else {
m = ((dy == 0) ? 1 : 0) + (len / n);
}
return PathWrapper.COST_GRID_LAYOUT * (len - m);
} else {
return (dx == 0 || dy == 0) ? 0 : PathWrapper.COST_GRID_LAYOUT;
}
}, fn, fx, fy);
} else if(fTown != tTown) {
// Otherwise get details about the other town
local tType = AITown.GetRoadLayout(tTown);
local tSize = AITown.GetPopulation(tTown).tofloat();
local tn = (tType == AITown.ROAD_LAYOUT_2x2) ? 3 : ((tType == AITown.ROAD_LAYOUT_3x3) ? 4 : 0);
local tx = tTile % AIMap.GetMapSizeY();
local ty = tTile / AIMap.GetMapSizeY();
// Calculate grid weights for each town based on population
local fWeight = min(0.8, max(0.2, fSize / (fSize + tSize)));
local tWeight = min(0.8, max(0.2, tSize / (fSize + tSize)));
// Calculate the distance between towns and initialise
local totalDist = AITile.GetDistanceManhattanToTile(fTile, tTile).tofloat();
local stx = (tx - fx).tofloat() / totalDist;
local sty = (ty - fy).tofloat() / totalDist;
local fRad = null;
local tRad = null;
// Find the "radii" of the two towns, by plotting a line between them
for(local i = 0; i < totalDist; i++) {
local tile = (fx + (stx * i.tofloat()) + ((fy + (sty * i.tofloat())).tointeger() * AIMap.GetMapSizeY())).tointeger();
if(!AITown.IsWithinTownInfluence(fTown, tile) && fRad == null) {
fRad = i - 1;
if(tRad != null) break;
}
if(AITown.IsWithinTownInfluence(tTown, tile) && tRad == null) {
tRad = totalDist - i;
if(fRad != null) break;
}
}
if(fRad == null) fRad = totalDist;
// If either town has a grid road layout then interpolate between the two
if(fn > 0 || tn > 0) {
pathfinder.RegisterCostCallback(function (tile, prevTile, fTile, fWeight, fRad, fn, fx, fy, tTile, tWeight, tRad, tn, tx, ty) {
local x = tile % AIMap.GetMapSizeY();
local y = tile / AIMap.GetMapSizeY();
local fdx, fdy, tdx, tdy;
if(fn > 0) {
fdx = abs(x - fx) % fn;
fdy = abs(y - fy) % fn;
}
if(tn > 0) {
tdx = abs(x - tx) % tn;
tdy = abs(y - ty) % tn;
}
local len = AIMap.DistanceManhattan(tile, prevTile);
local fCost = 0;
local tCost = 0;
// Account for bridges and tunnels
if(len > 1) {
local px = prevTile % AIMap.GetMapSizeY();
local py = prevTile / AIMap.GetMapSizeY();
local fpdy = (fn > 0) ? abs(py - fy) % fn : 0;
local tpdy = (tn > 0) ? abs(py - ty) % tn : 0;
local fm = 0;
if(fn > 0) {
if(fdy == fpdy) {
fm = ((fdx == 0) ? 1 : 0) + (len / fn);
} else {
fm = ((fdy == 0) ? 1 : 0) + (len / fn);
}
}
local tm = 0;
if(tn > 0) {
if(tdy == fpdy) {
tm = ((tdx == 0) ? 1 : 0) + (len / tn);
} else {
tm = ((tdy == 0) ? 1 : 0) + (len / tn);
}
}
fCost = (fn == 0 || (x == px && fdx == 0) || (y == py && fdy == 0)) ? 0.0 : PathWrapper.COST_GRID_LAYOUT * (len - fm - 0.0);
tCost = (tn == 0 || (x == px && tdx == 0) || (y == py && tdy == 0)) ? 0.0 : PathWrapper.COST_GRID_LAYOUT * (len - tm - 0.0);
} else {
fCost = (fn == 0 || fdx == 0 || fdy == 0) ? 0.0 : PathWrapper.COST_GRID_LAYOUT.tofloat();
tCost = (tn == 0 || tdx == 0 || tdy == 0) ? 0.0 : PathWrapper.COST_GRID_LAYOUT.tofloat();
}
local fDist = AITile.GetDistanceManhattanToTile(tile, fTile).tofloat();
local tDist = AITile.GetDistanceManhattanToTile(tile, tTile).tofloat();
local total = (fDist + tDist) - (fRad + tRad);
local GRID_MIX_DEAD_SPOT = 0.05;
// Calculate the balancing weights based on our location within either town
local fBal, tBal;
if(fDist < fRad) {
// If inside the "from" town, only apply the "from" grid
fBal = 1.0;
tBal = 0.0;
} else if(tDist < tRad){
// If inside the "to" town, only apply the "to" grid
fBal = 0.0;
tBal = 1.0;
} else {
// Otherwise mix the two grids based on a weighted distance
fBal = max(0.0, ((tDist - tRad) / (total * (fWeight + GRID_MIX_DEAD_SPOT))) - ((1 / (fWeight + GRID_MIX_DEAD_SPOT)) - 1));
tBal = max(0.0, ((fDist - fRad) / (total * (tWeight + GRID_MIX_DEAD_SPOT))) - ((1 / (tWeight + GRID_MIX_DEAD_SPOT)) - 1));
}
return ((fBal * fCost) + (tBal * tCost)).tointeger();
}, fTile, fWeight, fRad, fn, fx, fy, tTile, tWeight, tRad, tn, tx, ty);
}
}
}
break;
case PathWrapper.FEAT_DEPOT_ALIGN:
local sideTileList = LandManager.GetAdjacentTileList(toTile);
sideTileList.Valuate(function (tile, roadType,toTile) {
return AIRoad.IsRoadTile(tile) && AIRoad.HasRoadType(tile, roadType)&&AIRoad.AreRoadTilesConnected(tile,toTile)&&(!AIRoad.IsDriveThroughRoadStationTile(toTile)||AIRoad.GetRoadStationFrontTile(toTile)==tile||AIRoad.GetDriveThroughBackTile(toTile)==tile)&&(!AIRoad.IsDriveThroughRoadStationTile(tile)||AIRoad.GetRoadStationFrontTile(tile)==toTile||AIRoad.GetDriveThroughBackTile(tile)==toTile);
}, roadType,toTile);
sideTileList.KeepValue(0);
local sideTiles = ListToArray(sideTileList);
if(sideTiles.len() < 4) {
pathfinder.RegisterCostCallback(function (tile, prevTile, sideTiles) {
local misaligned = false;
if(sideTiles.len() >= 3) misaligned = misaligned || (tile == sideTiles[2]);
if(sideTiles.len() >= 2) misaligned = misaligned || (tile == sideTiles[1]);
if(sideTiles.len() >= 1) misaligned = misaligned || (tile == sideTiles[0]);
return (misaligned) ? PathWrapper.COST_DEPOT_ALIGN : 0;
}, sideTiles);
}
break;
case PathWrapper.FEAT_SHORT_SCOPE:
pathfinder.cost.max_cost = 50000+sqrt(AICompany.GetCompanyValue(AICompany.COMPANY_SELF));
break;
case PathWrapper.FEAT_NO_WORMHOLES:
pathfinder.cost.max_tunnel_length = 1;
pathfinder.cost.max_bridge_length = 1;
break;
case PathWrapper.FEAT_COUNTRY_LANE:
if(Settings.EnableCountryLanes() && roadType == AIRoad.ROADTYPE_ROAD) {
local towns = AITownList();
// Find the nearest town to the "from" tile
towns.Valuate(AITown.GetDistanceManhattanToTile, fromTile);
towns.Sort(AIAbstractList.SORT_BY_VALUE, true);
local fSize = AITown.GetPopulation(towns.Begin());
local fType = AITown.GetRoadLayout(towns.Begin());
local fGrid = (fType == AITown.ROAD_LAYOUT_2x2 || fType == AITown.ROAD_LAYOUT_3x3);
// Find the nearest town to the "to" tile
towns.Valuate(AITown.GetDistanceManhattanToTile, toTile);
towns.Sort(AIAbstractList.SORT_BY_VALUE, true);
local tSize = AITown.GetPopulation(towns.Begin());
local tType = AITown.GetRoadLayout(towns.Begin());
local tGrid = (tType == AITown.ROAD_LAYOUT_2x2 || tType == AITown.ROAD_LAYOUT_3x3);
// Get the year
local year = AIDate.GetYear(AIDate.GetCurrentDate());
local LATE_YEAR = 1950;
local EARLY_YEAR = 1940;
// If the towns are small and simple enough, add cost for trees and rough terrain, to make the road "windy"
if(year < LATE_YEAR && ((year >= EARLY_YEAR && !fGrid && !tGrid) || year < EARLY_YEAR) && fSize < 1000 && tSize < 1000) {
pathfinder.RegisterCostCallback(function (tile, prevTile) {
local len = AIMap.DistanceManhattan(tile, prevTile);
if(len > 1) return PathWrapper.COST_COUNTRY_LANE * len;
local slope = AITile.GetSlope(tile);
local unevenTerrain = !(slope == AITile.SLOPE_FLAT || slope == AITile.SLOPE_NW || slope == AITile.SLOPE_SW || slope == AITile.SLOPE_NE || slope == AITile.SLOPE_SE);
return (unevenTerrain || AITile.HasTreeOnTile(tile) || AITile.IsFarmTile(tile) || AITile.IsRockTile(tile) || AITile.IsRoughTile(tile)) ? PathWrapper.COST_COUNTRY_LANE : 0;
});
}
}
break;
case PathWrapper.FEAT_CHK_CONNECTION:
pathfinder.cost.allow_demolition = false;
pathfinder.cost.demolition = 10000000;
pathfinder.cost.no_existing_road = 10000000;
pathfinder.cost.max_bridge_length = PathZilla.MAX_BRIDGE_LENGTH;
pathfinder.cost.bridge_per_tile = 10000000;
pathfinder.cost.tunnel_per_tile = 10000000;
break;
}
}
AILog.Info(" Trying to find a path between [" + AIMap.GetTileX(fromTile) + ", " + AIMap.GetTileY(fromTile) + "] and [" + AIMap.GetTileX(toTile) + ", " + AIMap.GetTileY(toTile) + "]...");
// Make the necessary preparations
FinanceManager.EnsureFundsAvailable(PathZilla.FLOAT);
AIRoad.SetCurrentRoadType(roadType);
// If we are very poor, do not attempt to build tunnels
if(!FinanceManager.CanAfford(PathZilla.FLOAT)) {
pathfinder.cost.max_tunnel_length = 1;
}
// Run the pathfinder
local path = false;
local steps = 0;
local zlr=0;
while (path == false&&zlr++<1000) {
path = pathfinder.FindPath(PathZilla.PROCESSING_PRIORITY);
PathZilla.Sleep(1);
}
AILog.Info(" Done finding path.");
// Return the finished path
return path;
}
/*
* Build the path specified by path as a road of type roadType. If there any
* construction errors the method will re-try to a limited extent. If this also
* fails the method will return non-zero. If the returned value is greater than
* zero it indicates the tile just before which construction failed. If it is
* less than zero it indicates that construction strictly cannot be completed.
*/
function PathWrapper::BuildPath(path, roadType) {
AIRoad.SetCurrentRoadType(roadType);
local prevTile = null;
local stopList = AIList();
AILog.Info(" Building a road...")
while (path != null) {
local par = path.GetParent();
local tile = path.GetTile();
if (par != null) {
local ptile = par.GetTile();
//AILog.Info(AIMap.GetTileX(tile)+"/"+AIMap.GetTileY(tile)+"-"+AIMap.GetTileX(ptile)+"/"+AIMap.GetTileY(ptile));
local distance = AIMap.DistanceManhattan(tile, ptile);
FinanceManager.EnsureFundsAvailable(PathZilla.FLOAT);
// Check if we need to demolish the tile (e.g. is a town house is in the way)
if(!AITile.IsWaterTile(ptile)&&!AITile.IsBuildable(ptile) &&!AIRoad.IsRoadStationTile(ptile)&&!AIRoad.IsRoadDepotTile(ptile)&&!AIRoad.IsDriveThroughRoadStationTile(ptile)&&!(AIRoad.IsRoadTile(ptile) ||AIBridge.IsBridgeTile(ptile) ||AITunnel.IsTunnelTile(ptile))) {
AITile.DemolishTile(ptile);
FinanceManager.EnsureFundsAvailable(PathZilla.FLOAT);
}
local success = false;
local attempts = 0;
// Try to build the next path segment
while(!success && attempts++ < PathZilla.MAX_CONSTR_ATTEMPTS) {
if(distance == 1) {
success=((AIRoad.IsRoadTile(ptile)&&AIRoad.IsRoadTile(tile)&&AIRoad.AreRoadTilesConnected(tile,ptile))||(AITunnel.IsTunnelTile(tile)&&AITunnel.GetOtherTunnelEnd(tile)==ptile)||(AIBridge.IsBridgeTile(tile)&&AIBridge.GetOtherBridgeEnd(tile)==ptile))
if (!success)success = AIRoad.BuildRoad(tile, ptile);
} else {
// Build a bridge or tunnel.
if(!AIBridge.IsBridgeTile(tile) && !AITunnel.IsTunnelTile(tile)&&!AIRoad.IsDriveThroughRoadStationTile(tile)&&!AIRoad.IsRoadStationTile(tile)&&!AIRoad.IsRoadDepotTile(tile)) {
// If it was a road tile, demolish it first. Do this to work around expended roadbits.
if(AIRoad.IsRoadTile(tile)) AITile.DemolishTile(tile);
if(AITunnel.GetOtherTunnelEnd(tile) == ptile) {
success = AITunnel.BuildTunnel(AIVehicle.VT_ROAD, tile);
} else {
local bridgeType = LandManager.ChooseBridgeType(tile, ptile);
success = AIBridge.BuildBridge(AIVehicle.VT_ROAD, bridgeType, tile, ptile);
}
} else if (AIBridge.IsBridgeTile(tile)){
if (AIBridge.GetOtherBridgeEnd(tile)==ptile) success = true;
} else if (AITunnel.IsTunnelTile(tile)){
if (AITunnel.GetOtherTunnelEnd(tile)==ptile) success = true;
}
}
// If something went wrong, try to fix it
if(!success) {
//AILog.Info(AIMap.GetTileX(tile)+"/"+AIMap.GetTileY(tile)+"-"+AIMap.GetTileX(ptile)+"/"+AIMap.GetTileY(ptile));
switch(AIError.GetLastError()) {
case AIError.ERR_AREA_NOT_CLEAR:
// Something must have been built since we check the tile. Clear it.
if (!AITile.IsWaterTile(tile)&&!AIBridge.IsBridgeTile(tile)&&!AITunnel.IsTunnelTile(tile)&&!AIRoad.IsRoadStationTile(tile)&&!AIRoad.IsRoadDepotTile(tile)&&!AIRoad.IsDriveThroughRoadStationTile(tile)){
if(!AITile.DemolishTile(tile)) {
if(AIError.GetLastError() == AIError.ERR_LOCAL_AUTHORITY_REFUSES) {
// Try to influence the local authority
TownManager.HandleRating(TownManager.FindNearestTown(tile));
} else {
// Otherwise just give up
attempts = PathZilla.MAX_CONSTR_ATTEMPTS + 1;
}
}
}
break;
case AIError.ERR_NOT_ENOUGH_CASH:
if(!FinanceManager.CanAfford(PathZilla.FLOAT)) {
// We cant afford to borrow any more money, so give up!
AILog.Error(" Cannot afford path segment!");
SaveMoney();
PathZilla.Sleep(100);
} else {
// Otherwise, borrow some more money
FinanceManager.Borrow();
}
break;
case AIError.ERR_VEHICLE_IN_THE_WAY:
// Theres a vehicle in the way...
if(attempts == 2) {
// If we've already tried once, try to clear
// any of our own vehicles out of the way
// First, find those vehicles that are blocking
// the tile to be built on
local blockers = AIVehicleList();
blockers.Valuate(AIVehicle.GetVehicleType)
blockers.KeepValue(AIVehicle.VT_ROAD);
blockers.Valuate(AIVehicle.GetLocation);
blockers.KeepValue(ptile);
// Then find those vehicles that lie just outside
// the tile to be built on
local outliers = AIVehicleList();
outliers.Valuate(AIVehicle.GetVehicleType)
outliers.KeepValue(AIVehicle.VT_ROAD);
outliers.Valuate(function (v, ptile) {
return AITile.GetDistanceManhattanToTile(ptile, AIVehicle.GetLocation(v));
}, ptile);
outliers.KeepValue(1);
// Stop the outliers from moving into the tile
foreach(v, _ in outliers) {
if(AIVehicle.GetState(v) != AIVehicle.VS_STOPPED) AIVehicle.StartStopVehicle(v);
stopList.AddItem(v, 0);
}
// Move the blockers out of the way
foreach(v, _ in blockers) AIVehicle.ReverseVehicle(v);
} else if(attempts == PathZilla.MAX_CONSTR_ATTEMPTS) {
// If we STILL can't build due to traffic, remember the spot
if (ptile!=null) ::trafficBlackSpots.AddItem(ptile, 0);
}
// Just try waiting a bit
PathZilla.Sleep(30);
break;
// Just don't worry about the rest of these cases!
case AIError.ERR_ALREADY_BUILT:
success = true;
break;
case AIError.ERR_UNKNOWN:
success = true;
break;
}
if (distance==1&&AITunnel.GetOtherTunnelEnd(tile)==ptile&&!success){
success = AITunnel.BuildTunnel(AIVehicle.VT_ROAD, tile)
if (!success) success = AIBridge.BuildBridge(AIVehicle.VT_ROAD, LandManager.ChooseBridgeType(tile, ptile), tile, ptile);
}
}
}
// Check that we DID succeed
if(!success) {
// Restart any stopped vehicles
foreach(v, _ in stopList) {
if(AIVehicle.GetState(v) == AIVehicle.VS_STOPPED) AIVehicle.StartStopVehicle(v);
}
AILog.Error(" Could not complete road!")
return (prevTile != null) ? prevTile : tile;
}
}
prevTile = tile;
path = par;
}
// Restart any stopped vehicles
foreach(v, _ in stopList) {
if(AIVehicle.GetState(v) == AIVehicle.VS_STOPPED) AIVehicle.StartStopVehicle(v);
}
AILog.Info(" Done building road.")
return 0;
}
function PathWrapper::GetFirstTile(path) {
local cpath = clone path;
local tile = null;
while (cpath != null) {
if(cpath.GetParent() != null) tile = cpath.GetTile();
cpath = cpath.GetParent();
}
return tile;
}
RoadRunner/pathfinding/Road.nut 0000755 0001750 0001750 00000051437 11404744311 014305 0 ustar b b /**
* A Road Pathfinder.
* This road pathfinder tries to find a buildable / existing route for
* road vehicles. You can changes the costs below using for example
* roadpf.cost.turn = 30. Note that it's not allowed to change the cost
* between consecutive calls to FindPath. You can change the cost before
* the first call to FindPath and after FindPath has returned an actual
* route. To use only existing roads, set cost.no_existing_road to
* cost.max_cost.
*/
class Road
{
_aystar_class = import("graph.aystar", "", 6);
_max_cost = null; ///< The maximum cost for a route.
_cost_tile = null; ///< The cost for a single tile.
_cost_no_existing_road = null; ///< The cost that is added to _cost_tile if no road exists yet.
_cost_turn = null; ///< The cost that is added to _cost_tile if the direction changes.
_cost_slope = null; ///< The extra cost if a road tile is sloped.
_cost_bridge_per_tile = null; ///< The cost per tile of a new bridge, this is added to _cost_tile.
_cost_tunnel_per_tile = null; ///< The cost per tile of a new tunnel, this is added to _cost_tile.
_cost_coast = null; ///< The extra cost for a coast tile.
_cost_crossing = null; ///< The extra cost for crossing railway track.
_cost_demolition = null; ///< The cost if demolition is required on a tile.
_allow_demolition = null; ///< Whether demolition is allowed.
_pathfinder = null; ///< A reference to the used AyStar object.
_max_bridge_length = null; ///< The maximum length of a bridge that will be build.
_max_tunnel_length = null; ///< The maximum length of a tunnel that will be build.
_max_path_length = null; ///< The maximum length in tiles of the total route.
_estimate_multiplier = null; ///< Every estimate is multiplied by this value. Use 1 for a 'perfect' route, higher values for faster pathfinding.
_goal_estimate_tile = null; ///< The tile we take as goal tile for the estimate function.
cost = null; ///< Used to change the costs.
_cost_callbacks = null; ///< Stores [callback, args] tuples for additional cost.
_running = null;
_no_bridge_over = null;
constructor()
{
this._max_cost = 100000;
this._cost_tile = 100;
this._cost_no_existing_road = 100;
this._cost_turn = 50;
this._cost_slope = 80;
this._cost_bridge_per_tile = 600;
this._cost_tunnel_per_tile = 500;
this._cost_coast = 50;
this._cost_crossing = 500;
this._cost_demolition = 2500;
this._allow_demolition = false;
this._max_bridge_length = 25;
this._max_tunnel_length = 25;
this._estimate_multiplier = 1;
this._pathfinder = this._aystar_class(this, this._Cost, this._Estimate, this._Neighbours, this._CheckDirection);
this.cost = this.Cost(this);
_cost_callbacks = [];
this._running = false;
_no_bridge_over = [];
}
/**
* Initialize a path search between sources and goals.
* @param sources The source tiles.
* @param goals The target tiles.
* @param max_length_multiplier The multiplier for the maximum route length.
* @param max_length_offset The minimum value of the maximum length.
* @see AyStar::InitializePath()
*/
function InitializePath(sources, goals, max_length_multiplier = 0, max_length_offset = 10000, ignored_tiles = [], no_bridge_over = []);
/**
* Register a new cost callback function that will be called with all args specified.
* The callback function must return an integer or an error will be thrown.
* @param callback The callback function. This function will be called with
* as parameters: new_tile, prev_tile, your extra arguments.
*/
function RegisterCostCallback(callback, ...);
/**
* Try to find the path as indicated with InitializePath with the lowest cost.
* @param iterations After how many iterations it should abort for a moment.
* This value should either be -1 for infinite, or > 0. Any other value
* aborts immediatly and will never find a path.
* @return A route if one was found, or false if the amount of iterations was
* reached, or null if no path was found.
* You can call this function over and over as long as it returns false,
* which is an indication it is not yet done looking for a route.
* @see AyStar::FindPath()
*/
function FindPath(iterations);
};
class Road.Cost
{
_main = null;
function _set(idx, val)
{
if (this._main._running) throw("You are not allowed to change parameters of a running pathfinder.");
switch (idx) {
case "max_cost": this._main._max_cost = val; break;
case "tile": this._main._cost_tile = val; break;
case "no_existing_road": this._main._cost_no_existing_road = val; break;
case "turn": this._main._cost_turn = val; break;
case "slope": this._main._cost_slope = val; break;
case "bridge_per_tile": this._main._cost_bridge_per_tile = val; break;
case "tunnel_per_tile": this._main._cost_tunnel_per_tile = val; break;
case "coast": this._main._cost_coast = val; break;
case "crossing": this._main._cost_crossing = val; break;
case "demolition": this._main._cost_demolition = val; break;
case "allow_demolition": this._main._allow_demolition = val; break;
case "max_bridge_length": this._main._max_bridge_length = val; break;
case "max_tunnel_length": this._main._max_tunnel_length = val; break;
case "estimate_multiplier": this._main._estimate_multiplier = val; break;
default: throw("the index '" + idx + "' does not exist");
}
return val;
}
function _get(idx)
{
switch (idx) {
case "max_cost": return this._main._max_cost;
case "tile": return this._main._cost_tile;
case "no_existing_road": return this._main._cost_no_existing_road;
case "turn": return this._main._cost_turn;
case "slope": return this._main._cost_slope;
case "bridge_per_tile": return this._main._cost_bridge_per_tile;
case "tunnel_per_tile": return this._main._cost_tunnel_per_tile;
case "coast": return this._main._cost_coast;
case "crossing": return this._main._cost_crossing;
case "demolition": return this._main._cost_demolition;
case "allow_demolition": return this._main._allow_demolition;
case "max_bridge_length": return this._main._max_bridge_length;
case "max_tunnel_length": return this._main._max_tunnel_length;
case "estimate_multiplier": return this._main._estimate_multiplier;
default: throw("the index '" + idx + "' does not exist");
}
}
constructor(main)
{
this._main = main;
}
};
function Road::InitializePath(sources, goals, max_length_multiplier = 0, max_length_offset = 10000, ignored_tiles = [], no_bridge_over = [])
{
local nsources = [];
foreach (node in sources) {
nsources.push([node, 0xFF]);
}
/* The tile closes to the first source tile is set as estimate tile. */
this._goal_estimate_tile = goals[0];
foreach (tile in goals) {
if (AIMap.DistanceManhattan(sources[0], tile) < AIMap.DistanceManhattan(sources[0], this._goal_estimate_tile)) {
this._goal_estimate_tile = tile;
}
}
this._max_path_length = max_length_offset + max_length_multiplier * AIMap.DistanceManhattan(sources[0], this._goal_estimate_tile);
this._no_bridge_over = no_bridge_over;
this._pathfinder.InitializePath(nsources, goals, ignored_tiles);
}
function Road::RegisterCostCallback(callback, ...)
{
local args = [];
for(local c = 0; c < vargc; c++) {
args.append(vargv[c]);
}
this._cost_callbacks.push([callback, args]);
}
function Road::FindPath(iterations)
{
local test_mode = AITestMode();
local ret = this._pathfinder.FindPath(iterations);
this._running = (ret == false) ? true : false;
return ret;
}
function Road::_GetBridgeNumSlopes(end_a, end_b)
{
local slopes = 0;
local direction = (end_b - end_a) / AIMap.DistanceManhattan(end_a, end_b);
local slope = AITile.GetSlope(end_a);
if (!((slope == AITile.SLOPE_NE && direction == 1) || (slope == AITile.SLOPE_SE && direction == -AIMap.GetMapSizeX()) ||
(slope == AITile.SLOPE_SW && direction == -1) || (slope == AITile.SLOPE_NW && direction == AIMap.GetMapSizeX()) ||
slope == AITile.SLOPE_N || slope == AITile.SLOPE_E || slope == AITile.SLOPE_S || slope == AITile.SLOPE_W)) {
slopes++;
}
local slope = AITile.GetSlope(end_b);
direction = -direction;
if (!((slope == AITile.SLOPE_NE && direction == 1) || (slope == AITile.SLOPE_SE && direction == -AIMap.GetMapSizeX()) ||
(slope == AITile.SLOPE_SW && direction == -1) || (slope == AITile.SLOPE_NW && direction == AIMap.GetMapSizeX()) ||
slope == AITile.SLOPE_N || slope == AITile.SLOPE_E || slope == AITile.SLOPE_S || slope == AITile.SLOPE_W)) {
slopes++;
}
return slopes;
}
function Road::_Cost(self, path, new_tile, new_direction)
{
this = self;
/* path == null means this is the first node of a path, so the cost is 0. */
if (path == null) return 0;
local prev_tile = path.GetTile();
local cost = this._cost_tile;
if (AITile.HasTransportType(new_tile, AITile.TRANSPORT_RAIL)) {
cost += this._cost_crossing;
}
/* If the new tile is a bridge / tunnel tile, check whether we came from the other
* end of the bridge / tunnel or if we just entered the bridge / tunnel. */
if (AIBridge.IsBridgeTile(new_tile)) {
if (AIBridge.GetOtherBridgeEnd(new_tile) != prev_tile) return path.GetCost() + self._cost_tile;
return path.GetCost() + AIMap.DistanceManhattan(new_tile, prev_tile) * self._cost_tile + self._GetBridgeNumSlopes(new_tile, prev_tile) * self._cost_slope;
}
if (AITunnel.IsTunnelTile(new_tile)) {
if (AITunnel.GetOtherTunnelEnd(new_tile) != prev_tile) return path.GetCost() + self._cost_tile;
return path.GetCost() + AIMap.DistanceManhattan(new_tile, prev_tile) * self._cost_tile;
}
/* If the two tiles are more then 1 tile apart, the pathfinder wants a bridge or tunnel
* to be build. It isn't an existing bridge / tunnel, as that case is already handled. */
if (AIMap.DistanceManhattan(new_tile, prev_tile) > 1) {
/* Check if we should build a bridge or a tunnel. */
if (AITunnel.GetOtherTunnelEnd(new_tile) == prev_tile) {
return path.GetCost() + AIMap.DistanceManhattan(new_tile, prev_tile) * (self._cost_tile + self._cost_tunnel_per_tile);
} else {
return path.GetCost() + AIMap.DistanceManhattan(new_tile, prev_tile) * (self._cost_tile + self._cost_bridge_per_tile) + self._GetBridgeNumSlopes(new_tile, prev_tile) * self._cost_slope;
}
}
/* If the new tile is a bridge / tunnel tile, check whether we came from the other
* end of the bridge / tunnel or if we just entered the bridge / tunnel. */
/* if (AIBridge.IsBridgeTile(new_tile)) {
if (AIBridge.GetOtherBridgeEnd(new_tile) == prev_tile) {
cost += (AIMap.DistanceManhattan(new_tile, prev_tile) - 1) * this._cost_tile
+ this._GetBridgeNumSlopes(new_tile, prev_tile) * this._cost_slope;
}
}
if (AITunnel.IsTunnelTile(new_tile)) {
if (AITunnel.GetOtherTunnelEnd(new_tile) == prev_tile) {
cost += (AIMap.DistanceManhattan(new_tile, prev_tile) - 1) * this._cost_tile;
}
}
if (AIMap.DistanceManhattan(new_tile, prev_tile) > 1) {
cost -= this._cost_tile;
if (AITunnel.GetOtherTunnelEnd(new_tile) == prev_tile) {
cost += AIMap.DistanceManhattan(new_tile, prev_tile) * (this._cost_tile + this._cost_tunnel_per_tile);
} else {
cost += AIMap.DistanceManhattan(new_tile, prev_tile) * (this._cost_tile + this._cost_bridge_per_tile) + this._GetBridgeNumSlopes(new_tile, prev_tile) * this._cost_slope;
}
}
*/ /* Check for a turn. We do this by substracting the TileID of the current node from
* the TileID of the previous node and comparing that to the difference between the
* previous node and the node before that. */
if (path.GetParent() != null && (prev_tile - path.GetParent().GetTile()) != (new_tile - prev_tile) &&
AIMap.DistanceManhattan(path.GetParent().GetTile(), prev_tile) == 1) {
cost += this._cost_turn;
}
/* Check if the new tile is a coast tile. */
if (AITile.IsCoastTile(new_tile)) {
cost += this._cost_coast;
}
/* Check if the last tile was sloped. */
if (path.GetParent() != null && !AIBridge.IsBridgeTile(prev_tile) && !AITunnel.IsTunnelTile(prev_tile) &&
this._IsSlopedRoad(path.GetParent().GetTile(), prev_tile, new_tile)) {
cost += this._cost_slope;
}
if (!AIRoad.AreRoadTilesConnected(prev_tile, new_tile)) {
cost += this._cost_no_existing_road;
}
if (!AITile.IsBuildable(new_tile) && !AIRoad.IsRoadTile(new_tile)) {
cost += this._cost_demolition;
}
/* Call all extra cost callbacks. */
foreach(item in this._cost_callbacks) {
local args = [new_tile, prev_tile];
args.extend(item[1]);
local extra_cost = ::arr_call(item[0], args);
if (typeof(extra_cost) != "integer") throw("Cost callback didn't return an integer.");
cost += extra_cost;
}
return path.GetCost() + cost;
}
function Road::_Estimate(self, cur_tile, cur_direction, goal_tiles)
{
this = self;
/* As estimate we multiply the lowest possible cost for a single tile with
* with the minimum number of tiles we need to traverse. */
return AIMap.DistanceManhattan(cur_tile, this._goal_estimate_tile) * this._cost_tile * this._estimate_multiplier;
}
function Road::_Neighbours(self, path, cur_node)
{
this = self;
/* this._max_cost is the maximum path cost, if we go over it, the path isn't valid. */
if (path.GetCost() >= (this._max_cost)+sqrt(AICompany.GetCompanyValue(AICompany.COMPANY_SELF))) return [];
if (path.GetLength() + AIMap.DistanceManhattan(cur_node, this._goal_estimate_tile) > this._max_path_length) return [];
local tiles = [];
/* Check if the current tile is part of a bridge or tunnel. */
if ((AIBridge.IsBridgeTile(cur_node) || AITunnel.IsTunnelTile(cur_node)) &&
AITile.HasTransportType(cur_node, AITile.TRANSPORT_ROAD)) {
local other_end = AIBridge.IsBridgeTile(cur_node) ? AIBridge.GetOtherBridgeEnd(cur_node) : AITunnel.GetOtherTunnelEnd(cur_node);
local next_tile = cur_node + (cur_node - other_end) / AIMap.DistanceManhattan(cur_node, other_end);
if (AIRoad.AreRoadTilesConnected(cur_node, next_tile) || AITile.IsBuildable(next_tile) || AIRoad.IsRoadStationTile(next_tile)||AIRoad.IsRoadDepotTile(next_tile)||AIRoad.IsDriveThroughRoadStationTile(next_tile)||AIRoad.IsRoadTile(next_tile) || (this._allow_demolition && AITile.DemolishTile(next_tile))) {
tiles.push([next_tile, this._GetDirection(cur_node, next_tile, false)]);
}
/* The other end of the bridge / tunnel is a neighbour. */
tiles.push([other_end, this._GetDirection(next_tile, cur_node, true) << 4]);
} else if (path.GetParent() != null && AIMap.DistanceManhattan(cur_node, path.GetParent().GetTile()) > 1) {
local other_end = path.GetParent().GetTile();
local next_tile = cur_node + (cur_node - other_end) / AIMap.DistanceManhattan(cur_node, other_end);
if (AIRoad.AreRoadTilesConnected(cur_node, next_tile) || AIRoad.BuildRoad(cur_node, next_tile)) {
tiles.push([next_tile, this._GetDirection(cur_node, next_tile, false)]);
}
} else {
local offsets = [AIMap.GetTileIndex(0, 1), AIMap.GetTileIndex(0, -1),
AIMap.GetTileIndex(1, 0), AIMap.GetTileIndex(-1, 0)];
/* Check all tiles adjacent to the current tile. */
foreach (offset in offsets) {
local next_tile = cur_node + offset;
local can_demolish = this._allow_demolition && !AITile.IsWaterTile(next_tile)&&!AICompany.IsMine(AITile.GetOwner(next_tile)) && AITile.DemolishTile(next_tile);
/* Make sure never to demolish road tiles. */
can_demolish = can_demolish && !(AIRoad.IsRoadTile(next_tile)||AIBridge.IsBridgeTile(next_tile)||AITunnel.IsTunnelTile(next_tile));
/* We add them to the to the neighbours-list if one of the following applies:
* 1) There already is a connections between the current tile and the next tile.
* 2) We can build a road to the next tile.
* 3) The next tile is the entrance of a tunnel / bridge in the correct direction. */
if (AIRoad.AreRoadTilesConnected(cur_node, next_tile)) {
tiles.push([next_tile, this._GetDirection(cur_node, next_tile, false)]);
} else if ((AITile.IsBuildable(next_tile) || AIRoad.IsRoadTile(next_tile) || can_demolish) &&
(path.GetParent() == null || AIRoad.CanBuildConnectedRoadPartsHere(cur_node, path.GetParent().GetTile(), next_tile) > 0) &&
(AIRoad.BuildRoad(cur_node, next_tile) || (AIError.GetLastError() == AIError.ERR_VEHICLE_IN_THE_WAY && (path.GetParent() == null || AIRoad.CanBuildConnectedRoadPartsHere(cur_node, path.GetParent().GetTile(), next_tile) > 0)) || can_demolish)) {
tiles.push([next_tile, this._GetDirection(cur_node, next_tile, false)]);
} else if (this._CheckTunnelBridge(cur_node, next_tile)) {
tiles.push([next_tile, this._GetDirection(cur_node, next_tile, false)]);
}
}
if (path.GetParent() != null) {
local bridges = this._GetTunnelsBridges(path.GetParent().GetTile(), cur_node, this._GetDirection(path.GetParent().GetTile(), cur_node, true) << 4);
foreach (tile in bridges) {
tiles.push(tile);
}
}
}
return tiles;
}
function Road::_CheckDirection(self, tile, existing_direction, new_direction)
{
return false;
}
function Road::_GetDirection(from, to, is_bridge)
{
if (!is_bridge && AITile.GetSlope(to) == AITile.SLOPE_FLAT) return 0xFF;
if (from - to == 1) return 1;
if (from - to == -1) return 2;
if (from - to == AIMap.GetMapSizeX()) return 4;
if (from - to == -AIMap.GetMapSizeX()) return 8;
}
/**
* Get a list of all bridges and tunnels that can be build from the
* current tile. Bridges will only be build starting on non-flat tiles
* for performance reasons. Tunnels will only be build if no terraforming
* is needed on both ends.
*/
function Road::_GetTunnelsBridges(last_node, cur_node, bridge_dir)
{
local slope = AITile.GetSlope(cur_node);
local next_tile = cur_node + (cur_node - last_node);
if (slope == AITile.SLOPE_FLAT && !AITile.HasTransportType(next_tile, AITile.TRANSPORT_RAIL) && !AITile.HasTransportType(next_tile, AITile.TRANSPORT_WATER)) return [];
local tiles = [];
for (local i = 2; i < this._max_bridge_length; i++) {
local target = cur_node + i * (cur_node - last_node);
local violation = false;
foreach(nbo in this._no_bridge_over) {
if(target == nbo) {
violation = true;
break;
}
}
if(violation) break;
local bridge_list = AIBridgeList_Length(i + 1);
bridge_list.Valuate(AIBridge.GetPrice, i + 1);
bridge_list.Sort(AIAbstractList.SORT_BY_VALUE, true);
if (!bridge_list.IsEmpty() && AIBridge.BuildBridge(AIVehicle.VT_ROAD, bridge_list.Begin(), cur_node, target)) {
tiles.push([target, bridge_dir]);
}
}
if (slope != AITile.SLOPE_SW && slope != AITile.SLOPE_NW && slope != AITile.SLOPE_SE && slope != AITile.SLOPE_NE) return tiles;
local other_tunnel_end = AITunnel.GetOtherTunnelEnd(cur_node);
if (!AIMap.IsValidTile(other_tunnel_end)) return tiles;
local tunnel_length = AIMap.DistanceManhattan(cur_node, other_tunnel_end);
local prev_tile = cur_node + (cur_node - other_tunnel_end) / tunnel_length;
if (AITunnel.GetOtherTunnelEnd(other_tunnel_end) == cur_node && tunnel_length >= 2 &&
prev_tile == last_node && tunnel_length < _max_tunnel_length && AITunnel.BuildTunnel(AIVehicle.VT_ROAD, cur_node)) {
tiles.push([other_tunnel_end, bridge_dir]);
}
return tiles;
}
function Road::_IsSlopedRoad(start, middle, end)
{
local NW = 0; //Set to true if we want to build a road to / from the north-west
local NE = 0; //Set to true if we want to build a road to / from the north-east
local SW = 0; //Set to true if we want to build a road to / from the south-west
local SE = 0; //Set to true if we want to build a road to / from the south-east
if (middle - AIMap.GetMapSizeX() == start || middle - AIMap.GetMapSizeX() == end) NW = 1;
if (middle - 1 == start || middle - 1 == end) NE = 1;
if (middle + AIMap.GetMapSizeX() == start || middle + AIMap.GetMapSizeX() == end) SE = 1;
if (middle + 1 == start || middle + 1 == end) SW = 1;
/* If there is a turn in the current tile, it can't be sloped. */
if ((NW || SE) && (NE || SW)) return false;
local slope = AITile.GetSlope(middle);
/* A road on a steep slope is always sloped. */
if (AITile.IsSteepSlope(slope)) return true;
/* If only one corner is raised, the road is sloped. */
if (slope == AITile.SLOPE_N || slope == AITile.SLOPE_W) return true;
if (slope == AITile.SLOPE_S || slope == AITile.SLOPE_E) return true;
if (NW && (slope == AITile.SLOPE_NW || slope == AITile.SLOPE_SE)) return true;
if (NE && (slope == AITile.SLOPE_NE || slope == AITile.SLOPE_SW)) return true;
return false;
}
function Road::_CheckTunnelBridge(current_tile, new_tile)
{
if (!AIBridge.IsBridgeTile(new_tile) && !AITunnel.IsTunnelTile(new_tile)) return false;
local dir = new_tile - current_tile;
local other_end = AIBridge.IsBridgeTile(new_tile) ? AIBridge.GetOtherBridgeEnd(new_tile) : AITunnel.GetOtherTunnelEnd(new_tile);
local dir2 = other_end - new_tile;
if ((dir < 0 && dir2 > 0) || (dir > 0 && dir2 < 0)) return false;
dir = abs(dir);
dir2 = abs(dir2);
if ((dir >= AIMap.GetMapSizeX() && dir2 < AIMap.GetMapSizeX()) ||
(dir < AIMap.GetMapSizeX() && dir2 >= AIMap.GetMapSizeX())) return false;
return true;
}
RoadRunner/info.nut 0000755 0001750 0001750 00000005664 11405100640 012051 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* info.nut
*
* The basic descriptor for the PathZilla AI.
*
* Author: George Weller (Zutty)
* Created: 27/05/2008
* Version: 1.1
*/
///The original authorship of this code still belongs to George Weller. If you want to thank him for this great AI, you can send him as much money as you want or have. If you have problems with this AI you can make your own AI, download another AI, cry, or you can write suggestions to me on http://www.tt-forums.net. Stefan Brunner (Steffl) 24/4/2010
class PathZilla extends AIInfo {
function GetAuthor() { return "Steffl"; }
function GetName() { return "RoadRunner"; }
function GetDescription() { return "This AI is an improved and updated version of PathZilla, originally written by George Weller. It transports all kinds of cargo, but only uses road vehicles."; }
function GetVersion() { return 2; }
function GetDate() { return "2010-06-13"; }
function CreateInstance() { return "PathZilla"; }
function GetShortName() { return "PZLA"; }
function GetSettings() {
AddSetting({name = "latency", description = "Planning speed of AI", min_value = 0, max_value = 5, easy_value = 5, medium_value = 5, hard_value = 5, custom_value = 5, flags = 0});
AddLabels("latency", {_0="Very Slow", _1="Slow", _2="Medium", _3="Fast", _4="Very Fast", _5="Fastest"});
AddSetting({name = "aggressive", description = "Compete aggressively with other players", easy_value = 0, medium_value = 0, hard_value = 0, custom_value = 0, flags = AICONFIG_BOOLEAN});
AddSetting({name = "traffic", description = "Level of traffic the AI should generate", min_value = 1, max_value = 4, easy_value = 3, medium_value = 3, hard_value = 3, custom_value = 3, flags = 0});
AddLabels("traffic", {_1="Light", _2="Normal", _3="Heavy", _4="Very Heavy"});
AddSetting({name = "rt_cargo_towns", description = "Route all cargo through towns", easy_value = 0, medium_value = 0, hard_value = 0, custom_value = 0, flags = AICONFIG_BOOLEAN});
AddSetting({name = "country_lanes", description = "Build windy country lanes in rural towns", easy_value = 0, medium_value = 0, hard_value = 0, custom_value = 0, flags = AICONFIG_BOOLEAN});
}
function CanLoadFromVersion(version) {
return (version == 5);
}
}
RegisterAI(PathZilla());
RoadRunner/service/Service.nut 0000755 0001750 0001750 00000023330 11406635773 014171 0 ustar b b /* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* Service.nut
*
* A bus service between two towns.
*
* Author: George Weller (Zutty)
* Created: 18/06/2008
* Version: 1.1
*/
class Service {
// Serialization constants
CLASS_NAME = "Service";
SRLZ_SCHEMA_ID = 0;
SRLZ_TARGET_IDS = 1;
SRLZ_CARGO = 2;
SRLZ_TRANSPORT_TYPE = 3;
SRLZ_SUB_TYPE = 4;
SRLZ_ENGINE = 5;
SRLZ_PROFITABILITY = 6;
SRLZ_GROUP = 7;
SRLZ_DISTANCE = 8;
SRLZ_RAW_INCOME = 9;
SRLZ_COVERAGE_TARGET = 10;
SRLZ_TRANSPORTED = 11;
schemaId = null;
targetIds = null;
cargo = 0;
transportType = null;
subType = null;
engine = null;
profitability = 0;
vehicles = null;
group = null;
distance = 0;
rawIncome = 0;
coverageTarget = 0;
transported = 1
constructor(schemaId, targetIds, cargo, transportType, subType, engine, distance, rawIncome, coverageTarget) {
this.schemaId = schemaId;
this.targetIds = targetIds;
this.cargo = cargo;
this.transportType = transportType;
this.subType = subType;
this.engine = engine;
this.distance = distance;
this.rawIncome = rawIncome;
this.coverageTarget = coverageTarget;
this.transported = (this.transported!=1)?this.transported:(targetIds[0]<1000000)?(::pz.GetSchema(this.schemaId).GetTarget(targetIds[0])).GetTransportation(-1,cargo)*max(1,(AIIndustry.GetAmountOfStationsAround(targetIds[0])-RoadManager.GetStations((::pz.GetSchema(this.schemaId).GetTarget(targetIds[0])), this.cargo, this.subType).Count())):((::pz.GetSchema(this.schemaId).GetTarget(targetIds[0])).GetTransportation(-1,cargo)+20);
}
}
function Service::Create() {
// Create a group for the vehicles
this.vehicles = AIList();
this.group = AIGroup.CreateGroup(AIVehicle.VT_ROAD);
// Name the group
local schema = ::pz.GetSchema(this.schemaId);
local last = this.targetIds.len() - 1;
local fstr = chopstr(schema.GetTarget(this.targetIds[0]).GetName(), 7);
local tstr = chopstr(schema.GetTarget(this.targetIds[last]).GetName(), 7);
local strName = AICargo.GetCargoLabel(this.cargo) + " " + fstr + " to " + tstr;
AIGroup.SetName(this.group, trnc(strName));
}
/*
* Get the schema id
*/
function Service::GetSchemaId() {
return this.schemaId;
}
/*
* Get the ids of the targets this service visits.
*/
function Service::GetTargetIds() {
return this.targetIds;
}
/*
* Get the targets this service visits.
*/
function Service::GetTargets() {
local schema = ::pz.GetSchema(this.schemaId);
local targets = [];
local nr=0;
foreach(id in this.targetIds) {
targets.append(schema.GetTarget(id));
nr=1;
}
return targets;
}
function Service::ChangeTarget(targetId,i) {
this.targetIds[i]=targetId;
return;
}
/*
* Get the cargo this service carries.
*/
function Service::GetCargo() {
return this.cargo;
}
/*
* Get the transport type this service uses.
*/
function Service::GetTransportType() {
return this.transportType;
}
/*
* Get the sub-type this service uses.
*/
function Service::GetSubType() {
return this.subType;
}
/*
* Get the engine that this service uses
*/
function Service::GetEngine() {
return this.engine;
}
/*
* Set the engine that this service uses
*/
function Service::SetEngine(e) {
return this.engine = e;
}
/*
* Get the graph path this this service would run along.
*/
function Service::GetDistance() {
return this.distance;
}
function Service::CloseService() {
return this.distance = 1;
}
/*
* Get the estimated income for the proposed service.
*/
function Service::GetRawIncome() {
return this.rawIncome;
}
/*
* Get the town coverage target percentage
*/
function Service::GetCoverageTarget() {
return this.coverageTarget;
}
function Service::WasTransported() {
return this.transported;
}
/*
* Check if the service visits a target with specified Id.
*/
function Service::GoesTo(tgtId) {
foreach(targetId in this.targetIds) {
if(targetId == tgtId) return true;
}
return false;
}
/*
* Check if the service visits all in a list of targets
*/
function Service::GoesToAll(tgtIds) {
foreach(tgtId in tgtIds) {
if(!this.GoesTo(tgtId)) return false;
}
return true;
}
/*
* Checks that all targets in this service are still valid.
*/
function Service::IsValid() {
local nr=0;
foreach(targetId in this.targetIds) {
local target = ::pz.GetSchema(this.schemaId).GetTarget(targetId);
nr=1;
if(!target.IsValid()) return false;
}
return true;
}
/*
* Checks that all targets in this service are still valid.
*/
function Service::IsTwoWayService() {
foreach(target in this.GetTargets()) {
if (!target.IsTown()&&target.IsValid()){
foreach (prodcargo , _ in AIIndustryType.GetProducedCargo(AIIndustry.GetIndustryType(target.GetId()))){
foreach (acceptcargo , _ in AIIndustryType.GetAcceptedCargo(AIIndustry.GetIndustryType(target.GetId()))){
if (this.GetCargo()==prodcargo&&prodcargo==acceptcargo) return true;
}
}
return false;
}
}
return true;
}
/*
* Add a vehicle to the service
*/
function Service::AddVehicle(vehicleId) {
this.vehicles.AddItem(vehicleId, 0);
AIGroup.MoveVehicle(this.group, vehicleId);
}
function Service::RemoveVehicle(vehicleId) {
this.vehicles.RemoveItem(vehicleId);
}
/*
* Get the vehicles that are currently operating this service.
*/
function Service::GetVehicles() {
return this.vehicles;
}
function Service::VehiclesWaiting(station,sendDepot=false){
local vlist = AIVehicleList();
local cargo=this.GetCargo();
local z=0;
for(local j = vlist.Begin(); vlist.HasNext(); j = vlist.Next()) {
if (AIVehicle.IsValidVehicle(j)&&(this.IsTwoWayService()||AIVehicle.GetCargoLoad(j,AIEngine.GetCargoType(AIVehicle.GetEngineType(j)))360&&(this.IsTwoWayService()||AIVehicle.GetCargoLoad(j,cargo)==0)) z++;
}
}
//if (z>0)AILog.Warning(AIStation.GetName(station)+": "+z);
return z;
}
/*
* Check if the service turned an overall profit last year
*/
function Service::IsProfitable(poor=false) {
if (poor==false && AIDate.GetMonth(AIDate.GetCurrentDate())<12) return 0;
local vlist = this.vehicles;
local z=0;
for(local j = vlist.Begin(); vlist.HasNext(); j = vlist.Next()) {
if (AIVehicle.IsValidVehicle(j)&&(AIVehicle.GetAge(j)>((GetDistance()+70)*200)/AIEngine.GetMaxSpeed(AIVehicle.GetEngineType(j)))&& AIVehicle.GetProfitLastYear(j)<0&&AIVehicle.GetProfitThisYear(j)<0){
if (this.distance<400||AIVehicle.GetCurrentSpeed(j)<30){
z++;
}
}
}
return z;
}
/*
* Get the number of vehicles that are currently operating this service.
*/
function Service::GetActualFleetSize() {
return (this.vehicles != null) ? this.vehicles.Count() : 0;
}
/*
* Get a string representation of this service.
*/
function Service::_tostring() {
local strType = "";
if(transportType == AITile.TRANSPORT_ROAD) {
strType = (subType == AIRoad.ROADTYPE_ROAD) ? "road" : "tram";
} else if(transportType == AITile.TRANSPORT_AIR) {
strType = "air";
}
local schema = ::pz.GetSchema(this.schemaId);
local last = this.targetIds.len() - 1;
local strTgts = schema.GetTarget(this.targetIds[0]).GetName() + " to " + schema.GetTarget(this.targetIds[last]).GetName();
local str = "";
if(this.targetIds.len() == 2) {
str = AICargo.GetCargoLabel(this.cargo) + " from " + strTgts + " by " + strType;
}
return str;
}
/*
* Saves data to a table.
*/
function Service::Serialize() {
local data = {};
data[SRLZ_SCHEMA_ID] <- this.schemaId;
data[SRLZ_TARGET_IDS] <- this.targetIds;
data[SRLZ_CARGO] <- this.cargo;
data[SRLZ_TRANSPORT_TYPE] <- this.transportType;
data[SRLZ_SUB_TYPE] <- this.subType;
data[SRLZ_ENGINE] <- this.engine;
data[SRLZ_PROFITABILITY] <- this.profitability;
data[SRLZ_GROUP] <- this.group;
data[SRLZ_DISTANCE] <- this.distance;
data[SRLZ_RAW_INCOME] <- this.rawIncome;
data[SRLZ_COVERAGE_TARGET] <- this.coverageTarget;
data[SRLZ_TRANSPORTED] <- this.transported;
return data;
}
/*
* Loads data from a table.
*/
function Service::Unserialize(data) {
this.schemaId = data[SRLZ_SCHEMA_ID];
this.targetIds = data[SRLZ_TARGET_IDS];
this.cargo = data[SRLZ_CARGO];
this.transportType = data[SRLZ_TRANSPORT_TYPE];
this.subType = data[SRLZ_SUB_TYPE];
this.engine = data[SRLZ_ENGINE];
this.profitability = data[SRLZ_PROFITABILITY];
this.group = data[SRLZ_GROUP];
this.distance = data[SRLZ_DISTANCE];
this.rawIncome = data[SRLZ_RAW_INCOME];
this.coverageTarget = data[SRLZ_COVERAGE_TARGET];
this.transported = data[SRLZ_TRANSPORTED];
this.vehicles = AIList();
}
/*
* Compare this service to another. This function returns 0 (i.e. equal) for
* services that go to/from the same towns, and otherwise orders services by
* profitability.
*/
function Service::_cmp(svc) {
local same = this.cargo == svc.cargo;
same = same && this.transportType == svc.transportType;
same = same && this.subType == svc.subType;
if(same) {
foreach(targetId in this.GetTargetIds()) {
same = same && svc.GoesTo(targetId);
}
}
if(same) return 0;
if(this.rawIncome < svc.rawIncome) return -1;
return 1;
}
RoadRunner/service/Target.nut 0000755 0001750 0001750 00000022246 11407046335 014013 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* Target.nut
*
* An entity that can form part of a service, currently either a town or an
* industry.
*
* Author: George Weller (Zutty)
* Created: 30/01/2008
* Version: 1.0
*/
class Target {
// Serialization constants
CLASS_NAME = "Target";
SRLZ_TYPE = 0;
SRLZ_ID = 1;
SRLZ_TILE = 2;
// Other constants
TYPE_TOWN = 1;
TYPE_INDUSTRY = 2;
TILE_UNFIXED = -4194305; // 2048^2 + 1
type = null;
id = null;
tile = null;
constructor(type, id) {
this.type = type;
this.id = id;
if(type == Target.TYPE_TOWN) {
this.tile = AITown.GetLocation(id-1000000);
} else if(type == Target.TYPE_INDUSTRY) {
this.tile = TILE_UNFIXED;
}
}
}
/*
* Get the tile at to which all links should be made. This tile is guaranteed
* to the buildable.
*/
function Target::GetTile() {
return abs(this.tile);
}
function Target::GetTransportation(station,cargo){
local list=AIIndustryList_CargoProducing(cargo);
local st=null;
if (station==-1&&(AICargo.GetTownEffect(cargo)==AICargo.TE_PASSENGERS||AICargo.GetTownEffect(cargo)==AICargo.TE_MAIL)){
st=this.GetLocation();
}else if(station==-1&&AIIndustry.IsValidIndustry(this.GetId())){
return AIIndustry.GetLastMonthTransportedPercentage(this.GetId(),cargo);
}else{
st=AIStation.GetLocation(station);
}
local t=101;
for(local j = list.Begin(); list.HasNext(); j = list.Next()) {
local slist=AITileList_IndustryProducing(j,4);
for(local jj = slist.Begin(); slist.HasNext(); jj = slist.Next()) {
if (jj==st){
t=min(AIIndustry.GetLastMonthTransportedPercentage(j,cargo),t);
break;
}
}
}
if (t==101&&(AICargo.GetTownEffect(cargo)==AICargo.TE_PASSENGERS||AICargo.GetTownEffect(cargo)==AICargo.TE_MAIL)){
local town=AITile.GetClosestTown(st);
t=(AITown.GetLastMonthTransported(town,cargo)*100)/max(1,AITown.GetMaxProduction(town,cargo));
}
if (t==101) t=0;
return t;
}
function Target::GetProduction(station,cargo){
local list=AIIndustryList_CargoProducing(cargo);
local st=AIStation.GetLocation(station);
local t=-1;
for(local j = list.Begin(); list.HasNext(); j = list.Next()) {
local slist=AITileList_IndustryProducing(j,4);
for(local jj = slist.Begin(); slist.HasNext(); jj = slist.Next()) {
if (jj==st){
if (t!=AIIndustry.GetLastMonthProduction(j,cargo)) t=t+AIIndustry.GetLastMonthProduction(j,cargo);
break;
}
}
}
if (t==-1&&(AICargo.GetTownEffect(cargo)==AICargo.TE_PASSENGERS||AICargo.GetTownEffect(cargo)==AICargo.TE_MAIL)){
local town=AITile.GetClosestTown(st);
t=(AITown.GetMaxProduction(town,cargo));
}
if (t==-1) t=0;
return t;
}
/*
* Returns true if the position of the buildable tile has been fixed yet.
*/
function Target::IsTileFixed() {
return (this.tile > 0);
}
/*
* Check if the position has not yet been fixed.
*/
function Target::IsTileUnfixed() {
return (this.tile == Target.TILE_UNFIXED);
}
/*
* Check if the fixed tile can be refixed.
*/
function Target::IsTileSemiFixed() {
return (this.tile < 0 && this.tile != Target.TILE_UNFIXED);
}
/*
* Fix the seed tile to be a specified tile.
*/
function Target::FixTile(f) {
this.tile = f;
}
/*
* Fix the seed tile in a way that can be reapplied.
*/
function Target::SemiFixTile(f) {
if(this.tile == Target.TILE_UNFIXED) this.tile = -f;
}
/*
* Get the rough location of this target. This tile should NOT be used for
* construction, only for planning.
*/
function Target::GetLocation() {
local tile = -1;
if(!this.IsValid()) return tile;
if(this.type == Target.TYPE_TOWN) {
tile = AITown.GetLocation(this.id-1000000);
} else if(this.type == Target.TYPE_INDUSTRY) {
tile = AIIndustry.GetLocation(this.id);
}
return tile;
}
/*
* Returns this target as a vertex that preserves the underlying state.
*/
function Target::GetVertex() {
local tile = this.GetLocation();
return Vertex(AIMap.GetTileX(tile), AIMap.GetTileY(tile), this._hashkey());
}
/*
* Get the type of this target.
*/
function Target::GetType() {
return this.type;
}
/*
* Returns true if this target ponts to a town.
*/
function Target::IsTown() {
return (this.type == Target.TYPE_TOWN);
}
/*
* Check if the target is still valid.
*/
function Target::IsValid() {
if(this.type == Target.TYPE_TOWN) return AITown.IsValidTown(this.id-1000000);
return AIIndustry.IsValidIndustry(this.id);
}
/*
* Get the underlying Id of the town or industry this target points to.
*/
function Target::GetId() {
return this.id;
}
/*
* Checks if the target produces the specified cargo.
*/
function Target::ProducesCargo(cargo) {
// Pre-condition - Check that the target is still valid
if(!this.IsValid()) return false;
// If the target is a town check each tile in its influence for production
if(this.type == Target.TYPE_TOWN) {
if(AICargo.GetTownEffect(cargo) == AICargo.TE_NONE||AICargo.IsFreight(cargo)) return false;
local searchRadius = min(AIMap.DistanceFromEdge(this.tile) - 1, PathZilla.MAX_TOWN_RADIUS);
local offset = AIMap.GetTileIndex(searchRadius, searchRadius);
local tileList = AITileList();
tileList.AddRectangle(this.tile - offset, this.tile + offset);
foreach(tile, _ in tileList) {
local inTown = AITown.IsWithinTownInfluence(this.id-1000000, tile);
tileList.SetValue(tile, (inTown) ? 1 : 0);
}
tileList.KeepValue(1);
foreach(tile, _ in tileList) {
// TODO - Change the last 1 to 0 after OpenTTD 0.7.2 is released
local production = AITile.GetCargoProduction(tile, cargo, 1, 1, 0);
tileList.SetValue(tile, production);
}
return ListSum(tileList) > 0;
}
// Otherwise check the list of cargos for the appropriate industry type
if(!AIIndustryType.IsValidIndustryType(AIIndustry.GetIndustryType(this.id))) return false;
return AIIndustry.GetLastMonthProduction(this.id,cargo)>10;
}
/*
* Checks if the target accepts the specified cargo.
*/
function Target::AcceptsCargo(cargo) {
// Pre-condition - Check that the target is still valid
if(!this.IsValid()) return false;
// If the target is a town check each tile in its influence for acceptance
if(this.type == Target.TYPE_TOWN) {
if(AICargo.GetTownEffect(cargo) == AICargo.TE_NONE) return false;
local searchRadius = min(AIMap.DistanceFromEdge(this.tile) - 1, PathZilla.MAX_TOWN_RADIUS);
local offset = AIMap.GetTileIndex(searchRadius, searchRadius);
local tileList = AITileList();
tileList.AddRectangle(this.tile - offset, this.tile + offset);
foreach(tile, _ in tileList) {
local inTown = AITown.IsWithinTownInfluence(this.id-1000000, tile);
tileList.SetValue(tile, (tile) ? 1 : 0);
}
tileList.KeepValue(1);
foreach(tile, _ in tileList) {
// TODO - Change the last 1 to 0 after OpenTTD 0.7.2 is released
local acceptance = AITile.GetCargoAcceptance(tile, cargo, 1, 1, 0);
tileList.SetValue(tile, acceptance);
}
return (ListSum(tileList)/8).tointeger() > 1;
}
// Otherwise check the list of cargos for the appropriate industry type
local indType = AIIndustry.GetIndustryType(this.id);
if(!AIIndustryType.IsValidIndustryType(indType)) return false;
return AIIndustryType.GetAcceptedCargo(indType).HasItem(cargo);
}
function Target::GetSize() {
local size=0;
if(type == Target.TYPE_TOWN) {
size = AITown.GetPopulation(this.id-1000000);}
return size
}
/*
* Get the name of the underlying target from the API.
*/
function Target::GetName() {
local name = "Unknown";
if(!this.IsValid()) return name;
if(type == Target.TYPE_TOWN) {
name = AITown.GetName(this.id-1000000);
} else if(type == Target.TYPE_INDUSTRY) {
name = AIIndustry.GetName(this.id);
}
return name;
}
/*
* Get a unique key for this instance.
*/
function Target::_hashkey() {
return this.GetLocation();
}
/*
* Saves data to a table.
*/
function Target::Serialize() {
local data = {};
data[SRLZ_TYPE] <- this.type;
data[SRLZ_ID] <- this.id;
data[SRLZ_TILE] <- this.tile;
return data;
}
/*
* Loads data from a table.
*/
function Target::Unserialize(data) {
this.type = data[SRLZ_TYPE];
this.id = data[SRLZ_ID];
this.tile = data[SRLZ_TILE];
}
/*
* A static method to be used to sort Targets by their profit making potential.
*/
function Target::SortByPotential(homeTown, cargo) {
return function (a,b):(homeTown, cargo) {
local aval = 0;
local bval = 0;
if(a.type == b.type) {
if(a.type == ::Target.TYPE_TOWN) {
aval = (a.id == homeTown) ? 1000000 : ::AITown.GetPopulation(a.id-1000000);
bval = (b.id == homeTown) ? 1000000 : ::AITown.GetPopulation(b.id-1000000);
} else {
aval = ::AIIndustry.GetLastMonthProduction(a.id, cargo);
bval = ::AIIndustry.GetLastMonthProduction(b.id, cargo);
}
} else {
// TODO - Heterogenous services
}
if(aval < bval) return 1;
else if(aval > bval) return -1;
return 0;
}
}
RoadRunner/schema/Schema.nut 0000755 0001750 0001750 00000022313 11407054606 013560 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* Schema.nut
*
* A unique entity that encapsulates the graph, road and cargo types for an
* independant road network. The roads are not necessairly distinct from those
* used by other schemas, but any roads shared by schemas is strictly informal,
* as dictated by the path finder.
*
* Author: George Weller (Zutty)
* Created: 30/08/2008
* Version: 1.0
*/
class Schema {
// Serialization constants
CLASS_NAME = "Schema";
SRLZ_SCHEMA_ID = 0;
SRLZ_SOURCE_NODE = 1;
SRLZ_CARGOS = 2;
SRLZ_TRANSPORT_TYPE = 3;
SRLZ_SUB_TYPE = 4;
SRLZ_PLAN_GRAPH = 5;
SRLZ_ACTUAL_GRAPH = 6;
SRLZ_INDUSTRIAL = 7;
SRLZ_TARGETS = 8;
// Member variables
id = 0;
sourceNode = null;
cargos = null;
transportType = null;
subType = null;
planGraph = null;
actualGraph = null;
industrial = null;
targets = null;
constructor(sourceNode, cargos, transportType, subType) {
this.id = 0;
this.sourceNode = sourceNode;
this.cargos = cargos;
this.transportType = transportType;
this.subType = subType;
this.planGraph = null;
this.actualGraph = null;
this.targets = null;
// Decide if this is an industrial service or not
local sampleCargo = cargos.Begin();
local noEffect = (AICargo.GetTownEffect(sampleCargo) == AICargo.TE_NONE);
if(!AICargo.IsFreight(sampleCargo)&&!noEffect) {
industrial = false;
} else {
industrial = true;
// TODO - Heterogenous services
}
}
}
/*
* Gets the schema id.
*/
function Schema::GetId() {
return this.id;
}
/*
* Sets the schema id.
*/
function Schema::SetId(schemaId) {
this.id = schemaId;
}
/*
* Get the list of cargo IDs.
*/
function Schema::GetCargos() {
return this.cargos;
}
/*
* Get the transport type
*/
function Schema::GetTransportType() {
return this.transportType;
}
/*
* Get the sub type
*/
function Schema::GetSubType() {
return this.subType;
}
/*
* Get a graph showing which links we plan to build.
*/
function Schema::GetPlanGraph() {
if(this.planGraph == null) this.Initialise();
return this.planGraph;
}
/*
* Get a graph showing which links we have already built.
*/
function Schema::GetActualGraph() {
if(this.actualGraph == null) this.Initialise();
return this.actualGraph;
}
/*
* Check if the schema is industrial, i.e. that it services industries rather
* than towns.
*/
function Schema::IsIndustrial() {
return this.industrial;
}
function Schema::GetTargets() {
if(this.targets==null) {
this.InitialiseTargets();
}
local targets=[];
foreach (target in this.targets.GetData()){
if (target.GetId()>=1000000){
targets.append(Target(Target.TYPE_TOWN,target.GetId()))
}else{
targets.append(Target(Target.TYPE_INDUSTRY,target.GetId()))
}
}
return targets;
}
function Schema::GetTarget(id,loc=false) {
if (!loc){
foreach (target in this.targets.GetData()){
if (target.GetId()==id) return target;
}
}
/*if (id>1000000) id=id-1000000;
foreach (town in AITownList()){
if (AITown.GetLocation(town)==id) return Target(Target.TYPE_TOWN,town+1000000)
}
foreach (industry in AIIndustryList()){
if (AIIndustry.GetLocation(industry)==id) return Target(Target.TYPE_INDUSTRY,industry)
}
if (AIMap.IsValidTile(id)){
return Target(Target.TYPE_TOWN,AITile.GetClosestTown(id)+1000000)
}
return Target(Target.TYPE_INDUSTRY,id)
*/
local townlist=AITownList();
for (local town=townlist.Begin();townlist.HasNext();town=townlist.Next()){
if (AITown.GetLocation(town)==id) return Target(Target.TYPE_TOWN, town+1000000);
}
local indlist=AIIndustryList();
for (local industry=indlist.Begin();indlist.HasNext();industry=indlist.Next()){
if (AIIndustry.GetLocation(industry)==id) return Target(Target.TYPE_INDUSTRY, industry);
}
return Target(Target.TYPE_INDUSTRY, 999999);
}
/*
* Create an array of targets from all towns (up to a maximum of MAX_TARGETS)
* on the map.
*/
function Schema::GetTownTargets() {
// Prime a list of the closest MAX_TARGETS targets to the home town
local allTowns = AITownList();
allTowns.Valuate(AITown.GetDistanceManhattanToTile, AITown.GetLocation(this.sourceNode));
allTowns.KeepTop(PathZilla.MAX_TARGETS);
// HACK: If using trams, only consider large towns
if(this.GetSubType() == AIRoad.ROADTYPE_TRAM) {
allTowns.Valuate(AITown.GetPopulation);
allTowns.RemoveBelowValue(800);
}
// Build a list of targets
local targets = Map();
foreach(town, _ in allTowns) {
targets.Insert(Target(Target.TYPE_TOWN, town+1000000));
}
return targets;
}
/*
* Create an array of targets from industries on the map that accept or produce
* the predefined cargo for this schema.
*/
function Schema::GetIndustryTargets() {
// Get a list of all industries that handle the appropriate cargo
local indList = AIList();
foreach(cargo, _ in this.cargos) {
indList.AddList(AIIndustryList_CargoAccepting(cargo));
indList.AddList(AIIndustryList_CargoProducing(cargo));
}
// The source node is currently a town, which is no good!
indList.Valuate(AIIndustry.GetDistanceManhattanToTile, AITown.GetLocation(this.sourceNode));
indList.Sort(AIAbstractList.SORT_BY_VALUE, true);
this.sourceNode = indList.Begin();
// Build a list of targets
local targets = Map();
foreach(industry, _ in indList) {
targets.Insert(Target(Target.TYPE_INDUSTRY, industry));
}
return targets;
}
/*
* Create the list of targets that can be serviced in this schema.
*/
function Schema::InitialiseTargets() {
// Start with either industries or towns
if(this.industrial) {
if(this.targets == null){ this.targets = this.GetIndustryTargets();
} else {
local tlist=Map()
tlist=this.GetIndustryTargets();
foreach (target in tlist.GetData()){
local insert=0;
foreach (t in this.targets.GetData()){
if (target.GetName()==t.GetName()){
insert=1;
break;
}
}
if (insert==0){
this.targets.Insert(target);
}
}
}
// Add towns if we need to route cargo through them
if(AICargo.GetTownEffect(this.cargos.Begin()) != AICargo.TE_NONE||Settings.RouteCargoThroughTowns()) {
this.targets.Extend(this.GetTownTargets());
}
} else {
if(this.targets == null){ this.targets = this.GetTownTargets();
}else {
local tlist=Map()
tlist=this.GetTownTargets();
foreach (target in tlist.GetData()){
local insert=0;
foreach (t in this.targets.GetData()){
if (target.GetName()==t.GetName()){
insert=1;
break;
}
}
if (insert==0) this.targets.Insert(target);
}
}
}
}
/*
* Create the plan and actual graphs based on a triangulation over a list of
* targets, chosen based on the type of schema and global settings.
*/
function Schema::Initialise() {
// Ensure the list of targets has been initialised
if(this.targets == null) this.InitialiseTargets();
// Get the master graph for the whole map
local tlist=Map();
foreach (target in this.targets.GetData()){
if (target.IsValid()){
local insert=0;
foreach (t in tlist.GetData()){
if (target.GetName()==t.GetName()){
insert=1;
break;
}
}
if (insert==0) tlist.Insert(target);
}
}
if (tlist.Begin()!=null&&(tlist.Begin()).IsValid()) this.sourceNode = tlist.Begin().GetId();
local masterGraph = Triangulation(tlist);
//if (this.planGraph!= null) masterGraph.Merge(this.planGraph);
// For the plan graph use a combination of the shortest path from the home
// town and the minimum spanning tree.
this.planGraph = ShortestPathTree(masterGraph, AITown.GetLocation(this.sourceNode));
this.planGraph.Merge(MinimumSpanTree(masterGraph));
// Create a blank graph to represent what has actually been built
this.actualGraph = Graph();
}
/*
* Saves data to a table.
*/
function Schema::Serialize() {
local data = {};
data[SRLZ_SCHEMA_ID] <- this.id;
data[SRLZ_SOURCE_NODE] <- this.sourceNode;
data[SRLZ_CARGOS] <- ListToArray(this.cargos);
data[SRLZ_TRANSPORT_TYPE] <- this.transportType;
data[SRLZ_SUB_TYPE] <- this.subType;
data[SRLZ_INDUSTRIAL] <- this.industrial;
if(this.planGraph != null) data[SRLZ_PLAN_GRAPH] <- this.planGraph.Serialize();
if(this.actualGraph != null) data[SRLZ_ACTUAL_GRAPH] <- this.actualGraph.Serialize();
if(this.targets != null) data[SRLZ_TARGETS] <- this.targets.Serialize();
return data;
}
/*
* Loads data from a table.
*/
function Schema::Unserialize(data) {
this.id = data[SRLZ_SCHEMA_ID];
this.sourceNode = data[SRLZ_SOURCE_NODE];
this.cargos = ArrayToList(data[SRLZ_CARGOS]);
this.transportType = data[SRLZ_TRANSPORT_TYPE];
this.subType = data[SRLZ_SUB_TYPE];
this.industrial = data[SRLZ_INDUSTRIAL];
if(SRLZ_PLAN_GRAPH in data) {
this.planGraph = Graph();
this.planGraph.Unserialize(data[SRLZ_PLAN_GRAPH]);
}
if(SRLZ_ACTUAL_GRAPH in data) {
this.actualGraph = Graph();
this.actualGraph.Unserialize(data[SRLZ_ACTUAL_GRAPH]);
}
if(SRLZ_TARGETS in data) {
this.targets = Map();
this.targets.Unserialize(data[SRLZ_TARGETS]);
}
}
RoadRunner/main.nut 0000755 0001750 0001750 00000047346 11407063376 012065 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* main.nut
*
* PathZilla - A networking AI. See readme.txt for details.
*
* Author: George Weller (Zutty)
* Created: 16/05/2008
* Updated: 08/07/2009
* Version: 6
*/
class PathZilla extends AIController {
// Constants - DO NOT change these!
DIR_NORTH = 1;
DIR_SOUTH = 2;
DIR_EAST = 3;
DIR_WEST = 4;
TILE_LENGTH_KM = 429;
// Info constants
PZ_IDENT = "ROADRUNNER"
PZ_VERSION = 1;
// Serialisation constants
SRLZ_IDENT = 0;
SRLZ_STOP = 1;
SRLZ_COMPANY_NAME = 2;
SRLZ_HOME_TOWN = 3;
SRLZ_TICKER = 4;
SRLZ_SCHEMA_IDX = 5;
SRLZ_SCHEMAS = 6;
SRLZ_SRVC_MANAGER = 7;
SRLZ_TRAFFIC_BLACKSPOTS = 8;
SRLZ_VEHICLES_TO_SELL = 9;
// Configurable constants
PROCESSING_PRIORITY = 1000; // Governs how often intensive procesisng tasks should wait
MAX_TARGETS = 300; // Maximum number of targets that can be in a single graph
MAX_TARGET_COVERAGE = 65; // Maximum percentage of town houses to fall within combined station coverage area
NEW_VEHICLE_SPREAD_DELAY = 10; // The delay in ms between launching new vehicles in a fleet.
MAX_BRIDGE_LENGTH = 15; // The maximum allowable bridge length - to prevent ridiculous bridges
MAX_POTENTIAL_SERVICES = 600; // The maximum allowable number of potential service descriptors
ARV_ACC_THRESHOLD = 50; // Minimum percentage of acceptance via DTRSs before ARVs can be built
ENGINE_SCORE_THRESHOLD = 80; // The minimum score for an engine to be randomly selected
MAX_CONSTR_ATTEMPTS = 8; // The maximum number of attempts when trying to build something
BRIBE_THRESHOLD = 300000; // Minimum funds available before a bribe will be considered
MAX_TREE_SPEND = 10000; // Maximum we can spend on trees to improve rating
MAX_TOWN_RADIUS = 3; // Maximum distance from a town centre that anything can be built
MAX_REPATH_TRIES = 4; // Maximum number a times path can be re-found due to construction problems
MAX_VEHICLES_PER_SVC = 200; // Maximum number of vehicles per service
INDUSTRY_FLEET_MULTI = 4; // Fleet size multiplier for industrial services
TARGET_FIX_RADIUS = 4; // Radius around a target we should look to fix a tile
MAX_INITIAL_STATIONS = 1; // Maximum number of stations to start a service with
PAX_SERVICE_CAP_BASE = 10000000; // Base level for limit on number of passengers AI will aim to transport
SERVICE_PROFIT_THRESHOLD = 0; // Threshold at which to declare a service profitable
// Member variables
stop = false;
loaded = false;
companyName = null;
homeTown = null;
schemaIndex = 0;
schemas = null;
serviceManager = null;
ticker = 0;
FLOAT=5000;
constructor() {
require("graph/Edge.nut");
require("graph/Graph.nut");
require("graph/GraphPathNode.nut");
require("graph/Triangle.nut");
require("graph/Vertex.nut");
require("graph/impl/MinimumSpanTree.nut");
require("graph/impl/ShortestPathTree.nut");
require("graph/impl/Triangulation.nut");
require("manager/FinanceManager.nut");
require("manager/LandManager.nut");
require("manager/RoadManager.nut");
require("manager/TownManager.nut");
require("pathfinding/PathWrapper.nut");
require("pathfinding/Road.nut");
require("schema/Schema.nut");
require("service/Service.nut");
require("service/ServiceManager.nut");
require("service/Target.nut");
require("struct/Collection.nut");
require("struct/BinaryHeap.nut");
require("struct/Map.nut");
require("struct/SortedSet.nut");
require("Settings.nut");
require("common.nut");
// Some presets that must go here
this.loaded = false;
this.schemas = {};
FLOAT=5000;
// Set this as the singleton instance
::pz <- this;
}
}
/*
* Start running. Most of the planning, including calculating the plan graph is
* done before we start looping, though services are selected on the fly. The
* main loop manages the loan and events, maintains existing services, attempts
* to find new services, and then finally builds one.
*/
function PathZilla::Start() {
::trafficBlackSpots <- AIList();
::vehiclesToSell <- AIList();
AILog.Info("RoadRunner is on the road...");
// Initialise the AI
this.Initialise();
//Build headquarter
local searchRadius = PathZilla.MAX_TOWN_RADIUS+10;
local offset = AIMap.GetTileIndex(searchRadius, searchRadius);
local tileList = AITileList();
tileList.AddRectangle(AITown.GetLocation(this.homeTown) - offset, AITown.GetLocation(this.homeTown) + offset);
foreach(tile, _ in tileList) {
local suitable = (AIMap.IsValidTile(tile)&&AITile.IsBuildable(tile) && AITile.GetSlope(tile)==AITile.SLOPE_FLAT&&AITile.IsBuildable(tile+AIMap.GetTileIndex(1,0)) && AITile.GetSlope(tile+AIMap.GetTileIndex(1,0))==AITile.SLOPE_FLAT&&AITile.IsBuildable(tile+AIMap.GetTileIndex(0,1)) && AITile.GetSlope(tile+AIMap.GetTileIndex(0,1))==AITile.SLOPE_FLAT&&AITile.IsBuildable(tile+AIMap.GetTileIndex(1,1)) && AITile.GetSlope(tile+AIMap.GetTileIndex(1,1))==AITile.SLOPE_FLAT);
tileList.SetValue(tile, (suitable) ? 1 : 0);
}
tileList.RemoveValue(0);
foreach(tile, _ in tileList) {
local r = AITile.GetDistanceManhattanToTile(tile, AITown.GetLocation(this.homeTown)) + AIBase.RandRange(8) - 4;
tileList.SetValue(tile, r);
}
tileList.Sort(AIAbstractList.SORT_BY_VALUE, true);
if (AICompany.GetCompanyHQ(AICompany.COMPANY_SELF)==AIMap.TILE_INVALID&&!tileList.IsEmpty()){
local tile=tileList.Begin();
while (!AICompany.BuildCompanyHQ(tile)&&tileList.HasNext())tile=tileList.Next();
}
AILog.Info(" My home town is " + AITown.GetName(this.homeTown));
// Initialise the main loop
local noServices = true;
local startDate=AIDate.GetCurrentDate();
// Load settings for loop latency
local latency = Settings.GetLatency();
local workInterval = max(100, latency * 200);
local maintenanceInterval = max(100, workInterval * latency * 2);
local expansionInterval = max(100, workInterval * latency * 3);
// Start the main loop
while(!this.stop) {
// Try to keep the amount of funds available around FLOAT, by borrowing
// or repaying the loan.
this.FLOAT = (AICompany.GetMaxLoanAmount()/30+AICompany.GetCompanyValue(AICompany.COMPANY_SELF)/100).tointeger(); // Minimum amount of money to keep at all times
FinanceManager.MaintainFunds(PathZilla.FLOAT);
// Check for events
this.HandleEvents();
foreach (v,_ in AIVehicleList()){
if (AIVehicle.IsValidVehicle(v)){
if (AIVehicle.IsStoppedInDepot(v)||AIVehicle.GetState(v)==AIVehicle.VS_STOPPED){
AIVehicle.StartStopVehicle(v);
::vehiclesToSell.RemoveItem(v);
}
}
}
// Maintain existing services
if(ticker % maintenanceInterval == 0) {
this.serviceManager.MaintainServices();
}
// Look for some new services that we can implement
this.serviceManager.FindNewServices();
// Wait until we have a fair bit of cash before building a new line
if((AIDate.GetCurrentDate()-startDate>300||ticker>1100)&&(noServices || (ticker % expansionInterval == 0
&& FinanceManager.GetAvailableFunds() >= max(80000,AICompany.GetMaxLoanAmount() / 4)))) {
local vlist=AIVehicleList();
vlist.Valuate(AIVehicle.GetAge);
if (vlist.Count()<200||ListSum(vlist)/(vlist.Count()+1)<800||AICompany.GetLoanAmount()<30000){
this.serviceManager.ImplementService();
noServices = false;
}
}
// Advance the ticker
ticker += workInterval;
this.Sleep(workInterval);
}
}
/*
* Load state and data structures from a table. This method checks a signature
* and version number before loading, to ensure that the data being loaded is
* compatible with this AI. The method also relies on the classes that are used
* as data structures implementing the Unserialize method.
*/
function PathZilla::Load(version, data) {
local dataValid = false;
// First check that the data is for this AI, and this version
if(SRLZ_IDENT in data) {
if(typeof data[SRLZ_IDENT] == typeof PZ_IDENT) {
dataValid = (data[SRLZ_IDENT] == PZ_IDENT) && (version == PZ_VERSION);
}
}
// If the data is not valid, do not try to load
if(!dataValid) {
AILog.Error("Got invalid save data");
return false;
}
this.loaded = true;
::loadData <- data;
}
/*
* Save state and data structures to a table for the game to persist. This
* method relies on the classes that are used as data structures implementing
* the Serialize method.
*/
function PathZilla::Save() {
local data = {};
// Store the ident
data[SRLZ_IDENT] <- PZ_IDENT;
// Store the global variables
data[SRLZ_TRAFFIC_BLACKSPOTS] <- ListToArray(::trafficBlackSpots);
data[SRLZ_VEHICLES_TO_SELL] <- ListToArray(::vehiclesToSell);
// Store the basic data
data[SRLZ_STOP] <- this.stop;
data[SRLZ_COMPANY_NAME] <- this.companyName;
data[SRLZ_HOME_TOWN] <- this.homeTown;
data[SRLZ_TICKER] <- this.ticker;
// Store the schemas
data[SRLZ_SCHEMA_IDX] <- this.schemaIndex;
data[SRLZ_SCHEMAS] <- {};
foreach(idx, schema in this.schemas) {
data[SRLZ_SCHEMAS][idx] <- schema.Serialize();
}
// Store the service manager, if it has been set
if(this.serviceManager != null) {
data[SRLZ_SRVC_MANAGER] <- this.serviceManager.Serialize();
}
return data;
}
/*
* Initialise the state of the AI, either from saved state or from scratch.
*/
function PathZilla::Initialise() {
// Enable auto-renew
AICompany.SetAutoRenewStatus(true);
// Set the service manager
this.serviceManager = ServiceManager();
// If there is data to load then use it, otherwise start from scratch
if(this.loaded) {
// Load some global variables
::trafficBlackSpots <- ArrayToList(::loadData[SRLZ_TRAFFIC_BLACKSPOTS]);
::vehiclesToSell <- ArrayToList(::loadData[SRLZ_VEHICLES_TO_SELL]);
// Load the basic data
this.stop = ::loadData[SRLZ_STOP];
this.homeTown = ::loadData[SRLZ_HOME_TOWN];
this.companyName = ::loadData[SRLZ_COMPANY_NAME];
// Load the schemas
this.schemaIndex = ::loadData[SRLZ_SCHEMA_IDX];
foreach(idx, schemaData in ::loadData[SRLZ_SCHEMAS]) {
this.schemas[idx] <- Schema.instance();
this.schemas[idx].Unserialize(schemaData);
}
// Load the service manager, if it was saved
if(SRLZ_SRVC_MANAGER in ::loadData) {
this.serviceManager.Unserialize(::loadData[SRLZ_SRVC_MANAGER]);
}
// Load the vehicles into their groups
this.serviceManager.PostLoad();
} else {
// Initialise some global variables
// ::trafficBlackSpots <- AIList();
// ::vehiclesToSell <- AIList();
// Set the basic data
this.stop = false;
this.homeTown = this.SelectHomeTown();
this.companyName = this.ChooseName();
// Build the schemas
this.schemaIndex = -1;
this.BuildSchemas();
}
// Set the company name
AICompany.SetName(trnc(this.companyName));
}
/*
* Build a series of schemas based on the cargos, towns, and industries
* available in the map.
*/
function PathZilla::BuildSchemas() {
// Add passenger schemas by road and tram
local townList = AIList();
local tramList = AIList();
// Check each available cargo
foreach(cargo, _ in AICargoList()) {
// Get the amount of this cargo produced in towns
local townprod = 0;
foreach(town, _ in AITownList()) {
townprod += AITown.GetMaxProduction(town, cargo);
}
// Check if there are any trams that can carry the cargo
local tramable = false;
foreach(engine, _ in AIEngineList(AIVehicle.VT_ROAD)) {
if(AIEngine.GetRoadType(engine) == AIRoad.ROADTYPE_TRAM && AIEngine.CanRefitCargo(engine, cargo)) {
tramable = true;
break;
}
}
// If the cargo is produced in towns and has a town effect, use it in
// the town schema
if(townprod > 0 && AICargo.GetTownEffect(cargo) != AICargo.TE_NONE) {
townList.AddItem(cargo, 0);
}
// If a cargo can be carried by tram and has is of the passenger class,
// add it to the tram schema
if(tramable && AICargo.HasCargoClass(cargo, AICargo.CC_PASSENGERS)) {
tramList.AddItem(cargo, 0);
}
}
// Add the town schema
foreach(c,_ in townList){
local add=AIList();
add.AddItem(c,0);
this.AddSchema(Schema(this.homeTown, add, AITile.TRANSPORT_ROAD, AIRoad.ROADTYPE_ROAD));
add=null;
}
// Add the tram schema, if they are supported
if(AIRoad.IsRoadTypeAvailable(AIRoad.ROADTYPE_TRAM)) this.AddSchema(Schema(this.homeTown, tramList, AITile.TRANSPORT_ROAD, AIRoad.ROADTYPE_TRAM));
// Check each available industry type
foreach(type, _ in AIIndustryTypeList()) {
// Only add support raw industries taht are not on water
if(/*AIIndustryType.IsRawIndustry(type) &&*/ !AIIndustryType.IsBuiltOnWater(type)) {
// Only transport those cargos from this industry that have no town
// effect, i.e. dont carry passengers from oil rigs, etc...
local cargos = AIIndustryType.GetProducedCargo(type);
cargos.Valuate(AICargo.GetTownEffect);
// cargos.KeepValue(AICargo.TE_NONE);
// Add the schema
foreach(c,_ in cargos){
local na=true;
local cst=-1;
local d=true;
for(local sc=this.GetNextSchema();;){
if (cst==sc.GetId())break;
if (cst==-1)cst=sc.GetId();
foreach(cg,_ in sc.cargos){
if (cg==c) na=false;
}
}
if (na){
local add=AIList();
add.AddItem(c,0);
this.AddSchema(Schema(this.homeTown, add, AITile.TRANSPORT_ROAD, AIRoad.ROADTYPE_ROAD));
add=null;
}
}
}
}
}
/*
* Chooses a company name that does not already exist and returns it. The name
* must be applied in exec mode separately.
*/
function PathZilla::ChooseName() {
{
local n=AICompany.GetName(AICompany.COMPANY_SELF);
local str="";
for (local a=0;a-50000&&oz==0&&!a){
oz=1;
local open=(AIEventIndustryOpen.Convert(event)).GetIndustryID();
local log=0
local ez=0;
foreach(type, _ in AIIndustryTypeList()) {
if (!AIIndustryType.IsBuiltOnWater(type)) {
ez++
local appschema=GetSchema(ez);
if (type==AIIndustry.GetIndustryType(open)){
if (log==0)AILog.Info("Adding "+AIIndustry.GetName(open)+" to the target list.");
log=1;
appschema.InitialiseTargets();
appschema.Initialise();
}else {
foreach(c in AIIndustryType.GetAcceptedCargo(AIIndustry.GetIndustryType(open))) {
foreach(cargo, _ in appschema.GetCargos()) {
if (cargo==c) {
if (log==0)AILog.Info(AIIndustry.GetName(open)+" added to the target list.");
log=1;
appschema.InitialiseTargets();
appschema.Initialise();
}
}
}
}
}
}
}
break;
case AIEvent.AI_ET_INDUSTRY_CLOSE:
if (!a) break;
local close=(AIEventIndustryClose.Convert(event)).GetIndustryID();
local ez=0;
local services=this.serviceManager.GetServices();
foreach(service in services) {
local tz=0;
foreach(target in service.GetTargets()) {
if (!target.IsValid()&&service.GetActualFleetSize()>0){
local v=service.GetVehicles().Begin();
local ttz=0;
local station=null;
for (local i=0;i= this.schemas.len()) this.schemaIndex = 0;
return this.schemas[this.schemaIndex];
}
/*
* Add a new network schema to them main table and give it an id.
*/
function PathZilla::AddSchema(schema) {
local schemaId = this.schemas.len();
schema.SetId(schemaId);
return this.schemas[schemaId] <- schema;
return schemaId;
}
RoadRunner/service/ServiceManager.nut 0000755 0001750 0001750 00000144254 11407154320 015456 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* ServiceManager.nut
*
* Handles all service related functions and maintains service lists.
*
* Author: George Weller (Zutty)
* Created: 27/07/2008
* Version: 1.1
*/
class ServiceManager {
// Serialization constants
CLASS_NAME = "ServiceManager";
SRLZ_SERVICE_LIST = 0;
SRLZ_TARGETS_UPDATED = 1;
SRLZ_POTENTIAL_SERVICES = 2;
SRLZ_TARGETS_CONSIDERED = 3;
// Member variables
potentialServices = null;
targetsConsidered = null;
serviceList = null;
targetsUpdated = null;
constructor() {
this.targetsConsidered = Map();
this.targetsUpdated = Map();
this.serviceList = SortedSet();
this.potentialServices = SortedSet();
}
}
/*
* Get a list of services currently in operation.
*/
function ServiceManager::GetServices() {
return this.serviceList;
}
function ServiceManager::TargetClosed(target) {
this.targetsConsidered.Insert(target);
return;
}
function ServiceManager::DeleteService(svc) {
local helplist=this.serviceList;
this.serviceList=SortedSet();
foreach(service in helplist) {
if (service.GetCargo()!=svc.GetCargo()||service.GetTargets()[0]!=svc.GetTargets()[0]||service.GetTargets()[1]!=svc.GetTargets()[1]){
this.serviceList.Insert(service);
}
}
return;
}
/*
* Maintians the operating services. This function ensures that all towns have
* enough stations to cover them, and that all the vehciles in each service are
* distributed adequately.
*/
function ServiceManager::MaintainServices() {
local targetsTried = SortedSet();
foreach(service in this.serviceList) {
// PathZilla.Sleep(1);
// Update fleet size
this.CreateFleet(service, true);
if(service.GetTransportType() == AITile.TRANSPORT_ROAD&&AIDate.GetCurrentDate()%20==1) {
RoadManager.MaintainInfrastructure(service, targetsTried, this.targetsUpdated);
}
local needUpdate = false;
foreach(target in service.GetTargets()) {
needUpdate = needUpdate && this.targetsUpdated.Contains(target);
}
if(needUpdate) {
AILog.Info(" Updating service - " + service);
this.UpdateOrders(service);
}
}
this.targetsUpdated = Map();
}
/*
* Gets a list of potential services as service descriptors. At present this
* searchs through a matrix of all towns in the map, checks which would turn a
* profit, and ranks them by their profitability.
*/
function ServiceManager::FindNewServices() {
local schema = ::pz.GetNextSchema();
// local schema = ::pz.GetSchema(0);
local transportType = schema.GetTransportType();
local subType = schema.GetSubType();
// If there are no targets then just move on
if (schema.GetTargets().len()<2)return;
// Discard the targets that we have already been to, or that can't be reached
local spd=168
local vlist=AIVehicleList();
if (!vlist.IsEmpty()){
vlist.Valuate(AIVehicle.GetCurrentSpeed);
vlist.Sort(AIAbstractList.SORT_BY_VALUE, false);
spd=max(spd,(vlist.GetValue(vlist.Begin()))*3);
}
local tlist=AIList();
foreach(t in schema.GetTargets()){
tlist.AddItem(t.GetId(),0);
}
local targetList = [];
if (((AICompany.GetBankBalance(AICompany.COMPANY_SELF)/10000).tointeger())%100==10){
this.targetsConsidered.RemoveAll(this.targetsConsidered);
this.targetsConsidered.Insert(schema.GetTargets()[0]);
}
foreach (tk in schema.GetTargets()){
local insert=0;
if (this.targetsConsidered!=null){
foreach (tkk in this.targetsConsidered.GetData()){
if (tk.GetName()==tkk.GetName()){
insert=1;
break;
}
}
}
if (insert==0&&tk.IsValid()){
targetList.append(tk);
}
}
// If all targets have already been considered then just move on
if(targetList.len() == 0) return;
// Look for possible services for each cargo type in the current schema
local lcar=0;
foreach(cargo, _ in schema.GetCargos()) {
lcar++;
}
local lzz=0
local cargs=schema.GetCargos();
for(local cargo=cargs.Begin();cargs.HasNext();cargo=cargs.Next()){
lzz++;
AILog.Info(" Looking for potential " + AICargo.GetCargoLabel(cargo) + " services...");
if (subType==AIRoad.ROADTYPE_TRAM){
local eg=0;
local en=null;
foreach(e,_ in AIEngineList(AIVehicle.VT_ROAD)){
if (AIEngine.GetCargoType(e)==cargo){
eg=max(eg,AIEngine.GetMaxSpeed(e));
en=e;
}
}
if (en==null) return;
if (AIEngine.GetRoadType(en)==AIRoad.ROADTYPE_ROAD) subType=AIRoad.ROADTYPE_ROAD;
}
// Discard those targets that don't produce this cargo
local targets = [];
foreach (tk in schema.GetTargets()){
if (tk.IsValid()&&tk.ProducesCargo(cargo)){
local trz=0;
foreach (tr in targets){
if (tr.IsTown()){
if ((AITown.GetMaxProduction(tk.GetId()-1000000,cargo)/((tk.GetTransportation(-1,cargo)+49)/50+1).tointeger())*(100-tk.GetTransportation(-1,cargo)+1)>=(AITown.GetMaxProduction(tr.GetId()-1000000,cargo)/((tr.GetTransportation(-1,cargo)+49)/50+1).tointeger())*(100-tr.GetTransportation(-1,cargo)+1)){
break;
}
}else if ((AIIndustry.GetLastMonthProduction(tk.GetId(),cargo)/((tk.GetTransportation(-1,cargo)+49)/50+1).tointeger())*(100-tk.GetTransportation(-1,cargo)+1)>=(AIIndustry.GetLastMonthProduction(tr.GetId(),cargo)/((tr.GetTransportation(-1,cargo)+49)/50+1).tointeger())*(100-tr.GetTransportation(-1,cargo))){
break;
}
trz++;
}
/*if (tk.IsTown()){AILog.Info(AITown.GetMaxProduction(tk.GetId()-1000000,cargo)+AICargo.GetCargoLabel(cargo)+tk.GetTransportation(-1,cargo)+"%,"+((tk.GetTransportation(-1,cargo)+49)/50+1).tointeger()+","+(100-tk.GetTransportation(-1,cargo))+tk.GetName()+"="+(AITown.GetMaxProduction(tk.GetId()-1000000,cargo)/((tk.GetTransportation(-1,cargo)+49)/50+1).tointeger())*(100-tk.GetTransportation(-1,cargo)+1)+"|"+trz);
}else{AILog.Info(AIIndustry.GetLastMonthProduction(tk.GetId(),cargo)+AICargo.GetCargoLabel(cargo)+tk.GetTransportation(-1,cargo)+"%,"+((tk.GetTransportation(-1,cargo)+49)/50+1).tointeger()+","+(100-tk.GetTransportation(-1,cargo))+tk.GetName()+"="+(AIIndustry.GetLastMonthProduction(tk.GetId(),cargo)/((tk.GetTransportation(-1,cargo)+49)/50+1).tointeger())*(100-tk.GetTransportation(-1,cargo)+1)+"|"+trz);
}
*/
if (trz1000000)?AITown.GetLocation(b):AIIndustry.GetLocation(b)),a)-spd)},aTarget.GetLocation(),spd);
blist.Sort(AIAbstractList.SORT_BY_VALUE, true);
blist.KeepTop(10);
foreach(b,_ in blist) {
local bTarget=((b>1000000)?Target(Target.TYPE_TOWN, b):Target(Target.TYPE_INDUSTRY, b));
if(bTarget.AcceptsCargo(cargo)){
PathZilla.Sleep(1);
// Build a list of targets
local targetIds = [aTarget.GetId(), bTarget.GetId()];
local targetList = [aTarget, bTarget];
// Ensure that its possible to connect to the town, and that we
// don't already provide this service
if(bTarget != aTarget && !this.ProvidesService(targetIds, cargo, transportType, subType)) {
local bTile = bTarget.GetLocation();
// Select an engine
local engine = this.SelectEngine(targetList, cargo, transportType, subType, false);
local crowDist = AITile.GetDistanceManhattanToTile(aTarget.GetLocation(), bTile);
if(engine != null) {
if((bTile in netDist) && netDist[bTile] >= 0) {
// Decide on a limit for the target coverage level
local coverageLimit = PathZilla.MAX_TARGET_COVERAGE;
local year = AIDate.GetYear(AIDate.GetCurrentDate());
if(year < 1950) {
year = max(year, 1910);
coverageLimit = ((year - 1900) * 16 / 10);
}
// Decide on the coverage level itself
// TODO: Move this code into the schema and make it more general
local maxCoverage = PathZilla.MAX_TARGET_COVERAGE;
local coverageTarget = maxCoverage;
if(subType == AIRoad.ROADTYPE_TRAM) coverageTarget = maxCoverage / 2; // Penalise trams to prevent sprawl
if(AICargo.HasCargoClass(cargo, AICargo.CC_MAIL)) coverageTarget = maxCoverage / 4; // Penalise mail to prevent over-servicing
// Ensure the target does not exceed the limit
coverageTarget = min(coverageTarget, coverageLimit);
// Only consider the service if it is more profitable than it is costly
local travelTime=((netDist[bTile]*30)/(sqrt(AIEngine.GetMaxSpeed(engine)+1)*4*((netDist[bTile]+15)/(netDist[bTile]+1)))).tointeger(); // in days
// Get the base income for one trip
local rawIncome = AICargo.GetCargoIncome(cargo, crowDist, travelTime)*AIEngine.GetCapacity(engine);
local production = (aTarget.IsTown()?(AITown.GetMaxProduction(aTarget.GetId()-1000000,cargo)/((aTarget.GetTransportation(-1,cargo)+49)/50+1).tointeger())*(100-aTarget.GetTransportation(-1,cargo)+1)/100:(AIIndustry.GetLastMonthProduction(aTarget.GetId(),cargo)/((aTarget.GetTransportation(-1,cargo)+49)/50+1).tointeger())*(100-aTarget.GetTransportation(-1,cargo)+1)/100).tointeger();
if (Service(schema.GetId(), targetIds, cargo, transportType, subType, engine, netDist[bTile], rawIncome, coverageTarget).IsTwoWayService()){
local productionb = (((bTarget.IsTown()?(AITown.GetMaxProduction(bTarget.GetId()-1000000,cargo)/((bTarget.GetTransportation(-1,cargo)+49)/50+1).tointeger())*(100-bTarget.GetTransportation(-1,cargo)+1)/100:(AIIndustry.GetLastMonthProduction(bTarget.GetId(),cargo)/((bTarget.GetTransportation(-1,cargo)+49)/50+1).tointeger())*(100-bTarget.GetTransportation(-1,cargo)+1)/100))).tointeger();
production=((production+productionb-max(productionb-production,production-productionb)/3*2)*2).tointeger();
} // Project revenue and costs
local annualProfit = ((rawIncome-((travelTime+20)*AIEngine.GetRunningCost(engine)/180))*production*365/(travelTime+20)).tointeger();
if (crowDist>20&&(this.potentialServices.Len()==0||annualProfit*8>(this.potentialServices.Begin()).GetRawIncome())){
local water=1000;
local stpo=max(abs(AIMap.GetTileX(aTarget.GetLocation())-AIMap.GetTileX(bTarget.GetLocation())),abs(AIMap.GetTileY(aTarget.GetLocation())-AIMap.GetTileY(bTarget.GetLocation())));
local wz=0;
for (local i=2;i<=stpo&&wz<=10000000;i++){
local wx=(AIMap.GetTileX(aTarget.GetLocation())-(AIMap.GetTileX(aTarget.GetLocation())-AIMap.GetTileX(bTarget.GetLocation()))*i/stpo).tointeger();
local wy=(AIMap.GetTileY(aTarget.GetLocation())-(AIMap.GetTileY(aTarget.GetLocation())-AIMap.GetTileY(bTarget.GetLocation()))*i/stpo).tointeger();
//if (i%10==0)AILog.Info(wx+"/"+wy+"|"+wz);
if (AITile.IsWaterTile(AIMap.GetTileIndex(wx,wy))){
wz=wz+1+(sqrt(wz*7)).tointeger();
}else{
if (wz>0) water=water+wz;
wz=0;
}
}
local wa=1000;
local wz=0;
for (local i=1;i<=AIMap.DistanceManhattan(aTarget.GetLocation(),bTarget.GetLocation())&&wz<=10000000;i++){
local wx=(AIMap.GetTileX(aTarget.GetLocation())-((AIMap.GetTileX(aTarget.GetLocation())>AIMap.GetTileX(bTarget.GetLocation()))?min((AIMap.GetTileX(aTarget.GetLocation())-AIMap.GetTileX(bTarget.GetLocation())),i):max((AIMap.GetTileX(aTarget.GetLocation())-AIMap.GetTileX(bTarget.GetLocation())),0-i))).tointeger();
local wy=(AIMap.GetTileY(aTarget.GetLocation())-((AIMap.GetTileY(aTarget.GetLocation())>AIMap.GetTileY(bTarget.GetLocation()))?min((AIMap.GetTileY(aTarget.GetLocation())-AIMap.GetTileY(bTarget.GetLocation())),max(0,i-abs(AIMap.GetTileX(aTarget.GetLocation())-AIMap.GetTileX(bTarget.GetLocation())))):max((AIMap.GetTileY(aTarget.GetLocation())-AIMap.GetTileY(bTarget.GetLocation())),0-max(0,i-abs(AIMap.GetTileX(aTarget.GetLocation())-AIMap.GetTileX(bTarget.GetLocation())))))).tointeger();
if (AITile.IsWaterTile(AIMap.GetTileIndex(wx,wy))){
wz=wz+1+(sqrt(wz*7)).tointeger();
}else{
if (wz>0) wa=wa+wz;
wz=0;
}
}
water=min(water,wa);
wa=1000;
wz=0;
for (local i=1;i<=AIMap.DistanceManhattan(aTarget.GetLocation(),bTarget.GetLocation())&&wz<=10000000;i++){
local wy=(AIMap.GetTileY(aTarget.GetLocation())-((AIMap.GetTileY(aTarget.GetLocation())>AIMap.GetTileY(bTarget.GetLocation()))?min((AIMap.GetTileY(aTarget.GetLocation())-AIMap.GetTileY(bTarget.GetLocation())),i):max((AIMap.GetTileY(aTarget.GetLocation())-AIMap.GetTileY(bTarget.GetLocation())),0-i))).tointeger();
local wx=(AIMap.GetTileX(aTarget.GetLocation())-((AIMap.GetTileX(aTarget.GetLocation())>AIMap.GetTileX(bTarget.GetLocation()))?min((AIMap.GetTileX(aTarget.GetLocation())-AIMap.GetTileX(bTarget.GetLocation())),max(0,i-abs(AIMap.GetTileY(aTarget.GetLocation())-AIMap.GetTileY(bTarget.GetLocation())))):max((AIMap.GetTileX(aTarget.GetLocation())-AIMap.GetTileX(bTarget.GetLocation())),0-max(0,i-abs(AIMap.GetTileY(aTarget.GetLocation())-AIMap.GetTileY(bTarget.GetLocation())))))).tointeger();
if (AITile.IsWaterTile(AIMap.GetTileIndex(wx,wy))){
wz=wz+1+(sqrt(wz*7)).tointeger();
}else{
if (wz>0) wa=wa+wz;
wz=0;
}
}
water=min(water,wa);
if (!aTarget.IsTown())annualProfit=annualProfit/max(1,(AIIndustry.GetAmountOfStationsAround(aTarget.GetId())-RoadManager.GetStations(aTarget, cargo, subType).Count()));
annualProfit=((annualProfit*1000)/water).tointeger();
//AILog.Info(aTarget.GetName()+"-"+bTarget.GetName()+crowDist+"dist."+travelTime+"days"+annualProfit+"$"+water);
local svc = Service(schema.GetId(), targetIds, cargo, transportType, subType, engine, netDist[bTile], annualProfit, coverageTarget);
this.potentialServices.Insert(svc);
}
}else{
// AILog.Error(" There is no possible path between " + aTarget.GetName() + " and " + bTarget.GetName());
}
}
}
}
}
// To prevent an exponential buildup of descriptors, keep only the top
// MAX_POTENTIAL_SERVICES most profitable ones
local g=0;
this.potentialServices.SortBy(profit);
local svs=this.potentialServices;
for (local pi=0;piPathZilla.MAX_POTENTIAL_SERVICES||sv.GetRawIncome()*100&&(bestService.GetTargets()[0]).IsTown()){
if (bestService.GetCargo()!=0){
local engine = this.SelectEngine(bestService.GetTargets(), 0, bestService.GetTransportType(), bestService.GetSubType(), false);
if (engine==null) continue;
bestService=Service(bestService.GetSchemaId(),bestService.GetTargetIds(),0,bestService.GetTransportType(),bestService.GetSubType(),engine,bestService.GetDistance(),bestService.GetRawIncome(),bestService.GetCoverageTarget());
}
}
local ll=0;
// If we already provide it then move on to the next one
if (!this.ProvidesThisService(bestService)&&bestService.WasTransported()>=(bestService.GetTargets()[0]).GetTransportation(-1,bestService.GetCargo())/((bestService.GetTargets()[0].IsTown())?1:max(1,(AIIndustry.GetAmountOfStationsAround(bestService.GetTargetIds()[0])-RoadManager.GetStations(bestService.GetTargets()[0], bestService.GetCargo(), bestService.GetSubType()).Count())))){
foreach(serv in this.serviceList) {
if (serv.GetDistance()>1&&bestService.GetCargo()==serv.GetCargo()&&((serv.GetTargets())[0]).GetName()==((bestService.GetTargets())[0]).GetName()){
ll=1;
break;
}
//AILog.Warning(serv.GetDistance()+"|"+ll+"|"+bestService.GetCargo()+"="+serv.GetCargo()+"|"+((serv.GetTargets())[0]).GetName()+"="+((bestService.GetTargets())[0]).GetName());
}
}else{ ll=1;
}
if (ll==1) {
cont=1;
break
}
// Only proceed if there are any services left to implement
if(bestService != null) {
local success = false;
local schema = ::pz.GetSchema(bestService.GetSchemaId());
AILog.Info("Best service takes " + bestService);
// Build infrastructure for the service
if(bestService.GetTransportType() == AITile.TRANSPORT_ROAD) {
success = RoadManager.BuildInfrastructure(bestService, schema, this.targetsUpdated);
}
// If the service implementation failed then move it from the top
// position in the list to allow other services to be implemented
if(!success) {
AILog.Warning(" Demoting service...");
this.potentialServices.Remove(bestService);
this.potentialServices.SortBy(profit);
cont=1;
break;
}
// Create a fleet of vehicles to operate this service
if (cont==0){
bestService.Create();
this.CreateFleet(bestService);
}
// Finally, add the service to the list
if (bestService.GetActualFleetSize()<1){
AILog.Warning(" No fleet created...");
this.potentialServices.Remove(bestService);
this.potentialServices.SortBy(profit);
cont=1;
break;
}
this.serviceList.Insert(bestService);
local cargoList=AIList();
cargoList.AddItem(bestService.GetCargo(),0);
AILog.Warning("Service Nr: "+bestService.GetSchemaId());
AILog.Warning("Cargo: "+AICargo.GetCargoLabel(bestService.GetCargo()));
AILog.Warning("From: "+((bestService.GetTargets())[0]).GetName());
AILog.Warning("To: "+((bestService.GetTargets())[1]).GetName());
AILog.Warning("Distance: "+bestService.GetDistance());
AILog.Warning("Raw Income:"+bestService.GetRawIncome());
AILog.Warning("Coverage: "+bestService.GetCoverageTarget());
if (!(bestService.GetTargets()[0]).IsTown()&&!(bestService.GetTargets()[1]).IsTown()&&bestService.IsValid()){
foreach (prodcargo , _ in AIIndustryType.GetProducedCargo(AIIndustry.GetIndustryType((bestService.GetTargets()[0]).GetId()))){
foreach (acceptcargo , _ in AIIndustryType.GetAcceptedCargo(AIIndustry.GetIndustryType((bestService.GetTargets()[1]).GetId()))){
if (!cargoList.HasItem(prodcargo)&&prodcargo==acceptcargo&&AICargo.GetTownEffect(prodcargo)!=AICargo.TE_PASSENGERS&&AICargo.GetTownEffect(prodcargo)!=AICargo.TE_MAIL) {
local engine = this.SelectEngine(bestService.GetTargets(), prodcargo, bestService.GetTransportType(), bestService.GetSubType(), false);
local farmService=Service(bestService.GetSchemaId(),bestService.GetTargetIds(),prodcargo,bestService.GetTransportType(),bestService.GetSubType(),engine,bestService.GetDistance(),bestService.GetRawIncome(),bestService.GetCoverageTarget());
local schema = ::pz.GetSchema(farmService.GetSchemaId());
farmService.Create();
this.CreateFleet(farmService);
this.serviceList.Insert(farmService);
AILog.Warning("Service Nr: "+bestService.GetSchemaId());
AILog.Warning("Cargo: "+AICargo.GetCargoLabel(prodcargo));
AILog.Warning("From: "+((bestService.GetTargets())[0]).GetName());
AILog.Warning("To: "+((bestService.GetTargets())[1]).GetName());
AILog.Warning("Distance: "+bestService.GetDistance());
AILog.Warning("Raw Income:"+bestService.GetRawIncome());
AILog.Warning("Coverage: "+bestService.GetCoverageTarget());
cargoList.AddItem(prodcargo,0);
}
}
}
}
}
if (cont==1) {
this.targetsConsidered.Remove(best.Service.GetTargets()[0]);
continue;
}
AILog.Info("Done implementing service.");
}
// Don't remove it until we are finished
this.potentialServices.Remove(bestService);
this.potentialServices.SortBy(profit);
if (cont==1&&FinanceManager.GetAvailableFunds() >= (AICompany.GetMaxLoanAmount() / 4)) continue;
return true;
}
}
/*
* Choose an engine to run between two specified towns, and carry the specified
* cargo. This method is compatible with NewGRF sets that require vehciles to
* be refitted.
*/
function ServiceManager::SelectEngine(targets, cargo, transportType, subType, checkStations) {
local availableFunds = FinanceManager.GetAvailableFunds();
local vtTypeMap = {};
vtTypeMap[AITile.TRANSPORT_ROAD] <- AIVehicle.VT_ROAD;
local engineList = AIEngineList(vtTypeMap[transportType]);
foreach(engine, _ in engineList) {
local ok = true;
if(transportType == AITile.TRANSPORT_ROAD) {
if(AIEngine.GetRoadType(engine) != subType) ok = false;
}
if(!(AIEngine.GetCargoType(engine) == cargo || AIEngine.CanRefitCargo(engine, cargo))) ok = false;
if(AIEngine.GetPrice(engine) == 0) ok = false;
if(AIEngine.GetPrice(engine) > availableFunds) ok = false;
if(AIEngine.GetCapacity(engine) <= 0) ok = false;
engineList.SetValue(engine, (ok) ? 1 : 0);
}
// Discount vehicles that are invalid or that can't be built
engineList.RemoveValue(0);
// If none are left, then return with nothing
if(engineList.Count() == 0) {
return null;
}
// Calculate the total distance
local distance = 0;
local prev = 0;
for(local next = 1; next < targets.len(); next++) {
distance += AITile.GetDistanceManhattanToTile(targets[prev].GetTile(), targets[next].GetTile());
}
// Build a function to compute the profit making potential of each vehicle
local profitValuator = function (engine, cargo, distance) {
local travelTime = (179 * distance) / (10 * AIEngine.GetMaxSpeed(engine)); // AIEngine.GetReliability(engine) / 100
travelTime = max(1, travelTime); // Compensate for ultra-fast vehicles
local unitIncome = AICargo.GetCargoIncome(cargo, distance, travelTime);
local period = 5; // years
local tco = AIEngine.GetPrice(engine) + (AIEngine.GetRunningCost(engine) * period);
local income = unitIncome * AIEngine.GetCapacity(engine) * ((364 * period) / travelTime);
local profit = income - tco;
return profit;
}
// Find the highest profit level
engineList.Valuate(profitValuator, cargo, distance);
local maxProfit = engineList.GetValue(engineList.Begin());
// Findthe highest capacity
engineList.Valuate(AIEngine.GetCapacity);
local maxCapactiy = engineList.GetValue(engineList.Begin());
// Get the minimum acceptance for the service
local minAcceptance = -1;
if(checkStations) {
// Get coverage radius of stations the vechies will stop at
local truckStation = !AICargo.HasCargoClass(cargo, AICargo.CC_PASSENGERS);
local stationType = (truckStation) ? AIStation.STATION_TRUCK_STOP : AIStation.STATION_BUS_STOP;
local radius = AIStation.GetCoverageRadius(stationType);
// Create a valuator function to rank stations based on acceptance
local accValuator = function(station, cargo, radius) {
return AITile.GetCargoAcceptance(AIStation.GetLocation(station), cargo, 1, 1, radius) + 1;
}
// Get the minimum level of acceptance for each target
minAcceptance = 10000000;
foreach(target in targets) {
local stations = RoadManager.GetStations(target, cargo, subType);
stations.Valuate(accValuator, cargo, radius);
minAcceptance = min(minAcceptance, ListSum(stations));
}
}
// Rank the remaining engines by their score
foreach(engine, _ in engineList) {
local profitTerm = (max(0, profitValuator(engine, cargo, distance)) * 100) / maxProfit;
local reliabilityTerm = AIEngine.GetReliability(engine);
local normCapacity = (AIEngine.GetCapacity(engine) * 100) / maxCapactiy;
local accUpper = 250;
local overkillTerm = 100 - abs(normCapacity - (min(minAcceptance, accUpper) * 100 / accUpper));
local score = (profitTerm + reliabilityTerm + overkillTerm) / 3;
engineList.SetValue(engine, score);
}
engineList.Sort(AIAbstractList.SORT_BY_VALUE, false);
// If the engines are good enough then choose randomly from the best ones
if(engineList.GetValue(engineList.Begin()) >= PathZilla.ENGINE_SCORE_THRESHOLD) {
engineList.RemoveBelowValue(PathZilla.ENGINE_SCORE_THRESHOLD);
engineList.Valuate(AIBase.RandItem);
}
// Return the selected engine
return engineList.Begin();
}
/*
* Create a fleet of vehicles for the specified service. This method assumes
* that the towns that the service run between are already on the network and
* have stations built. The function finds the nearest depot, then estimates a
* suitable fleet size, then builds the vehicles with randomly distributed
* orders between the stations in both towns.
*/
function ServiceManager::CreateFleet(service, update = false) {
// Initialise
local cargo = service.GetCargo();
local isIndustry = false;
// Get the stations
local stations = {};
local depots = {};
// Get type of station the vechies will stop at
local truckStation = !AICargo.HasCargoClass(cargo, AICargo.CC_PASSENGERS);
local stationType = (truckStation) ? AIStation.STATION_TRUCK_STOP : AIStation.STATION_BUS_STOP;
local radius = AIStation.GetCoverageRadius(stationType);
foreach(target in service.GetTargets()) {
if (!target.IsValid()) return;
stations[target.GetId()] <- RoadManager.GetStations(target, cargo, service.GetSubType());
if(!target.IsTown()) isIndustry = true;
// If the engine type is articulated, forbid the vehicle from visiting regular stations
/* if(AIEngine.IsArticulated(engine)) {
foreach(station, _ in stations[target.GetId()]) {
local driveThru = AIRoad.IsDriveThroughRoadStationTile(AIStation.GetLocation(station));
stations[target.GetId()].SetValue(station, (driveThru) ? 1 : 0);
}
stations[target.GetId()].RemoveValue(0);
}
*/
// If the target has no stations then there is no point in building a
// fleet - defer until stations have been built
if(stations[target.GetId()].Count() == 0) {
// AILog.Warning("No stations at " + target.GetName());
return;
}
}
// Select an engine type
local funds = min(800000,max(0, FinanceManager.GetAvailableFunds() - (PathZilla.FLOAT/10)));
local engine = null;
local price=1000000
local fz=0;
if(update) {
for (;funds-price<0;funds=FinanceManager.GetAvailableFunds() - (PathZilla.FLOAT/10)){
if (fz%10==9){
SaveMoney();
}
PathZilla.Sleep(10);
fz++;
::pz.HandleEvents();
engine = service.GetEngine();
if (engine!=null){
price = AIEngine.GetPrice(engine);
}else{
price=20000;
}
}
if (engine == null){
AILog.Warning(" Demoting service...");
SellVehicles(service,service.GetActualFleetSize());
service.CloseService();
return false;
}
} else {
for (;funds-price<0;funds=FinanceManager.GetAvailableFunds() - (PathZilla.FLOAT/10)){
if (fz%10==9){
SaveMoney();
}
PathZilla.Sleep(10);
::pz.HandleEvents();
fz++
engine = this.SelectEngine(service.GetTargets(), cargo, service.GetTransportType(), service.GetSubType(), true);
if (engine!=null){
price = AIEngine.GetPrice(engine);
}else{
price=20000;
}
}
if (engine == null){
AILog.Warning(" Demoting service...");
this.potentialServices.Remove(bestService);
return false;
}
service.SetEngine(engine);
}
// Get a few basic details
local capacity = AIEngine.GetCapacity(engine);
local speed = AIEngine.GetMaxSpeed(engine);
local distance = 0;
local prev = 0;
local fz=0;
for(local next = 1; next < service.GetTargets().len(); next++) {
distance += AITile.GetDistanceManhattanToTile(service.GetTargets()[prev].GetLocation(), service.GetTargets()[next].GetLocation());
}
// Calculate the required fleet size
local fleetSize = 0;
local updateSize=0;
local jam=0;
// If we are updating the service, base the decision on waiting cargo
if(update) {
// Get the total waiting cargo for all targets
local waitingCargo = 0;
local rating=85;
local vv=0;
// Prime the station lists with waiting cargo values
foreach(target in service.GetTargets()) {
foreach(station, _ in stations[target.GetId()]) {
local waiting = AIStation.GetCargoWaiting(station, cargo)+max(0,(60-AIStation.GetCargoRating(station,cargo)));
// local cap = PathZilla.PAX_SERVICE_CAP_BASE * PathZilla.GetSetting("traffic");
stations[target.GetId()].SetValue(station, waiting);
if (fz==0)rating=min(AIStation.GetCargoRating(station,cargo),rating);
jam+=service.VehiclesWaiting(station,AIStation.GetCargoRating(station,cargo)<1);
if (AIVehicleList_Station(station).IsEmpty()&&service.GetDistance()>1)vv++;
if (rating==69&&AIVehicleList_Station(station).HasItem(service.GetVehicles().Begin())){
TownManager.BuildStatue(AIStation.GetNearestTown(station));
return false;
}
}
if (service.IsTwoWayService()){ fz=0;
}else{ fz++;}
waitingCargo += ListSum(stations[target.GetId()]);
}
// Estimate the number of additional vechiles required based on waiting cargo
/* local year = AIDate.GetYear(AIDate.GetCurrentDate());
year = min(max(year, 1915), 1950);
local multiplier = (70 - (year - 1900)) / 2;
multiplier /= PathZilla.GetSetting("traffic");*/
if(AICargo.GetTownEffect(cargo)==AICargo.TE_MAIL)waitingCargo=waitingCargo/2;
if (waitingCargo==0&&!((service.GetTargets()[0]).IsTown())){
if (!AIIndustryType.IsRawIndustry(AIIndustry.GetIndustryType((service.GetTargets()[0]).GetId())))service.VehiclesWaiting((stations[(service.GetTargets()[0]).GetId()]).Begin(),true);
}
if (waitingCargo65){
if (jam-3-(service.GetActualFleetSize()/10).tointeger()>0){
updateSize = service.GetActualFleetSize()-min((service.GetActualFleetSize()/8+1).tointeger(),(jam-3-(service.GetActualFleetSize()/20).tointeger()));
}else{
updateSize = service.GetActualFleetSize()+(1-min(jam,1))*(55-max(54,min(rating,55)))*min(1,service.GetDistance()-1)*(1-min(1,service.IsProfitable(true)))+vv;
}
}else{
updateSize = (service.GetActualFleetSize()+(sqrt(waitingCargo/(capacity*10))).tointeger()+(1-min(jam,1))*(55-max(54,min(rating,55))))*min(1,service.GetDistance()-1)*(1-min(1,service.IsProfitable(true)))+vv;
}
}
if (service.IsTwoWayService()) jam=(jam/max(3,service.GetActualFleetSize()/10)).tointeger();
// Find the minimum acceptance level
local minAcceptance = 1000000;
local accSum = {};
local prod=0;
local zz=0;
local tt=0;
local big=0;
foreach(target in service.GetTargets()) {
foreach(station, _ in stations[target.GetId()]) {
local acceptance = (AITile.GetCargoAcceptance(AIStation.GetLocation(station), cargo, 1, 1, radius) + 1)*(100-target.GetTransportation(station,cargo))/100;
if (zz==0){
stations[target.GetId()].SetValue(station, 100-AIStation.GetCargoRating(station,cargo));
}else{
stations[target.GetId()].SetValue(station, acceptance*1000/(AIVehicleList_Station(station).Count()+1));
}
if (zz==0||service.IsTwoWayService()&&prod*tt0&&updateSize0){
fleetSize = min(max(updateSize,service.GetActualFleetSize()),((fleetSize*3)/2).tointeger());
}else{
fleetSize = min(max(updateSize,service.GetActualFleetSize()),max(((fleetSize*3)/2).tointeger(),service.GetActualFleetSize()+1))
}
}
// Ensure we have no mare than the maximum number of vehicles
fleetSize = min(fleetSize, PathZilla.MAX_VEHICLES_PER_SVC);
// If were updating, account for vehicles already built
if(update) {
updateSize = fleetSize - service.GetActualFleetSize();
local zz=service.IsProfitable();
if(zz>0) {
AILog.Warning("Service for " + service + " did not turn a profit last year");
local ee = RoadManager.BuildInfrastructure(service, ::pz.GetSchema(service.GetSchemaId()), this.targetsUpdated);
if (ee==false||zz>=service.GetVehicles().Count()){
SellVehicles(service,service.GetVehicles().Count());
AILog.Warning(" Demoting service...");
service.CloseService();
return;
}else{
SellVehicles(service,max(zz,1))
return;
}
} else {
fleetSize = max(0, updateSize);
}
}else{
if (service.IsTwoWayService()){
fleetSize=fleetSize/((isIndustry)?max(stations[(service.GetTargets()[0]).GetId()].Count(),stations[(service.GetTargets()[1]).GetId()].Count()):max(max(15,AITown.GetHouseCount((service.GetTargets()[0]).GetId()-1000000))/15,max(15,AITown.GetHouseCount((service.GetTargets()[1]).GetId()-1000000))/15)).tointeger();
}else{
fleetSize=fleetSize/(((isIndustry)?stations[(service.GetTargets()[0]).GetId()].Count():max(15,AITown.GetHouseCount(target.GetId()-1000000))/15)).tointeger();
}
fleetSize=max(1,fleetSize);
}
// Do not attempt to build more than we can actually afford
local maxFleetSize = funds / AIEngine.GetPrice(engine);
fleetSize = min(maxFleetSize, fleetSize);
// If there is no fleet to build then just return now
if(fleetSize <= 0) return;
local nodep=false;
local bhff={};
local stz=0;
foreach(target in service.GetTargets()) {
stz++;
RoadManager.BuildIndustryStation(target, service.GetCargo(), service.GetSubType(),fleetSize);
stations[target.GetId()]<-RoadManager.GetStations(target, cargo, service.GetSubType());
local conmax=0;
local cons=stations[target.GetId()].Begin();
foreach(stdd,_ in RoadManager.GetStations(target, cargo, service.GetSubType())){
local elist=AIVehicleList_Station(stdd);
local empty=true;
foreach(v,_ in elist){
if (AIEngine.GetCargoType(AIVehicle.GetEngineType(v))==AIEngine.GetCargoType(service.GetEngine())){
empty=false;
break;
}
}
if (empty&&(stz==1||service.IsTwoWayService())){
conmax=100000000;
cons=stdd;
break;
}else if (100-AIStation.GetCargoRating(stdd,cargo)>conmax&&(stz==1||service.IsTwoWayService())){
conmax=100-AIStation.GetCargoRating(stdd,cargo);
cons=stdd;
}else if (AIStation.GetConstructionDate(stdd)>conmax&&stz==2&&!service.IsTwoWayService()){
conmax=AIStation.GetConstructionDate(stdd);
cons=stdd;
}
stations[target.GetId()].SetValue(stdd, 0)
}
stations[target.GetId()].SetValue(cons, accSum[target.GetId()])
bhff[target.GetId()]<-RandomItemByWeight(stations[target.GetId()], accSum[target.GetId()]);
local depotList = AIDepotList(AITile.TRANSPORT_ROAD);
depotList.Valuate(AIRoad.HasRoadType, service.GetSubType());
depotList.KeepValue(1);
depotList.Valuate(AITile.GetDistanceManhattanToTile, AIStation.GetLocation(bhff[target.GetId()]));
depotList.Sort(AIAbstractList.SORT_BY_VALUE, false);
depotList.KeepBottom(1);
depots[target.GetId()] <- depotList.Begin();
(AITile.GetDistanceManhattanToTile(AIStation.GetLocation(bhff[target.GetId()]),depots[target.GetId()])>10)?nodep=true:nodep=false;
}
local newSt=AIVehicleList_Station(bhff[(service.GetTargets()[0]).GetId()]).IsEmpty()
local engineName = AIEngine.GetName(engine);
AILog.Info(((update) ? " Updating a fleet with " : " Building a fleet of ") + fleetSize + " " + engineName + "s...");
// Borrow enough to buy the whole fleet of vehicles
FinanceManager.EnsureFundsAvailable(AIEngine.GetPrice(engine) * (fleetSize + 1));
// Check if the vehicles will need to be refitted
local needRefit = (AIEngine.GetCargoType(engine) != service.GetCargo());
// Clone a fleet from the prototype vehicle
local zz=0;
local ee=0;
local dep=0;
if (distance>60||nodep) {
dep=1;
}else{
dep=0;
}
for(local i = 0; i < fleetSize; i++) {
// Wait some time to spread the vechiles out a baccSum[target.GetId()] it.
PathZilla.Sleep(PathZilla.NEW_VEHICLE_SPREAD_DELAY);
// Alternate between targets if they are towns
local idx = 0;
if(service.IsTwoWayService()) idx = (i + 1) % service.GetTargets().len();
if (idx>0 && service.GetTargets()[idx]==service.GetTargets()[idx-1]){
AILog.Warning(" Demoting service...");
this.potentialServices.Remove(bestService);
return false;
}
local depot = depots[(service.GetTargets()[idx]).GetId()];
// Build a new vehicle at the nearest depot
local v = AIVehicle.BuildVehicle(depot, engine);
// Choose stations and assign orders
local j = 0;
local jj=0;
local bz=0;
zz=zz*ee+1;
local ee=1;
foreach(target in service.GetTargets()) {
local bhf=bhff[target.GetId()];
if ((bz==0&&i>0&&!AIIndustryType.IsRawIndustry(AIIndustry.GetIndustryType(target.GetId()))&&!service.IsTwoWayService()&&AIStation.GetCargoWaiting(bhf, cargo)0&&AIStation.GetCargoWaiting(bhf, cargo)5&&AIMap.DistanceManhattan(AIVehicle.GetLocation(service.GetVehicles().Begin()),AIStation.GetLocation(bhf))<5&&AIVehicle.GetCargoLoad(service.GetVehicles().Begin(),cargo)<1) return;
}
service.VehiclesWaiting(bhf,true);
SaveMoney();
}
PathZilla.Sleep(10);
::pz.HandleEvents();
ee=0;
}
}
bz++;
local tile = AIStation.GetLocation(bhf);
local flags = AIOrder.AIOF_NON_STOP_INTERMEDIATE + AIOrder.AIOF_FULL_LOAD_ANY;
// if(!target.IsTown() && target.ProducesCargo(cargo)) flags = flags | AIOrder.AIOF_FULL_LOAD;
if (service.IsTwoWayService()){
if (big==AITile.GetClosestTown(tile)) {
AIOrder.AppendOrder(v, tile, flags);
if (AIMap.IsValidTile(depots[service.GetTargets()[bz%service.GetTargets().len()].GetId()])&&AIRoad.IsRoadDepotTile(depots[service.GetTargets()[bz%service.GetTargets().len()].GetId()])&&dep==1){
AIOrder.AppendOrder(v, depots[service.GetTargets()[bz%service.GetTargets().len()].GetId()], AIOrder.AIOF_NON_STOP_INTERMEDIATE);
}else if (dep==1){
AIOrder.AppendOrder(v, depots[service.GetTargets()[(bz+1)%service.GetTargets().len()].GetId()], AIOrder.AIOF_NON_STOP_INTERMEDIATE);
}
}else {
AIOrder.AppendOrder(v, tile, AIOrder.AIOF_NON_STOP_INTERMEDIATE);
if (AIMap.IsValidTile(depots[service.GetTargets()[bz%service.GetTargets().len()].GetId()])&&AIRoad.IsRoadDepotTile(depots[service.GetTargets()[bz%service.GetTargets().len()].GetId()])&&dep==1){
AIOrder.AppendOrder(v, depots[service.GetTargets()[bz%service.GetTargets().len()].GetId()], AIOrder.AIOF_NON_STOP_INTERMEDIATE);
}else if (dep==1){
AIOrder.AppendOrder(v, depots[service.GetTargets()[(bz+1)%service.GetTargets().len()].GetId()], AIOrder.AIOF_NON_STOP_INTERMEDIATE);
}
}
}else {
AIOrder.AppendOrder(v, tile, flags);
if (AIMap.IsValidTile(depots[service.GetTargets()[bz%service.GetTargets().len()].GetId()])&&AIRoad.IsRoadDepotTile(depots[service.GetTargets()[bz%service.GetTargets().len()].GetId()])&&dep==1){
AIOrder.AppendOrder(v, depots[service.GetTargets()[bz%service.GetTargets().len()].GetId()], AIOrder.AIOF_NON_STOP_INTERMEDIATE);
}else if (dep==1){
AIOrder.AppendOrder(v, depots[service.GetTargets()[(bz+1)%service.GetTargets().len()].GetId()], AIOrder.AIOF_NON_STOP_INTERMEDIATE);
}
AIOrder.AppendOrder(v, tile, AIOrder.AIOF_NON_STOP_INTERMEDIATE + AIOrder.AIOF_NO_LOAD + AIOrder.AIOF_UNLOAD);
if (AIMap.IsValidTile(depots[service.GetTargets()[bz%service.GetTargets().len()].GetId()])&&AIRoad.IsRoadDepotTile(depots[service.GetTargets()[bz%service.GetTargets().len()].GetId()])&&dep==1){
AIOrder.AppendOrder(v, depots[service.GetTargets()[bz%service.GetTargets().len()].GetId()], AIOrder.AIOF_NON_STOP_INTERMEDIATE);
}else if (dep==1){
AIOrder.AppendOrder(v, depots[service.GetTargets()[(bz+1)%service.GetTargets().len()].GetId()], AIOrder.AIOF_NON_STOP_INTERMEDIATE);
}
AIOrder.InsertConditionalOrder(v, jj,jj+1+dep);
AIOrder.SetOrderCondition(v, jj, AIOrder.OC_LOAD_PERCENTAGE);
AIOrder.SetOrderCompareFunction(v, jj, AIOrder.CF_MORE_THAN);
AIOrder.SetOrderCompareValue(v, jj, 0);
if (jj>0) { AIOrder.InsertConditionalOrder(v, jj-1-dep,jj);
AIOrder.SetOrderCondition(v, jj-1-dep, AIOrder.OC_UNCONDITIONALLY);
jj++;
}
jj=jj+3+dep*2;
}
}
if (jj>0 && !service.IsTwoWayService()) { AIOrder.InsertConditionalOrder(v, jj-1-dep,0);
AIOrder.SetOrderCondition(v, jj-1-dep, AIOrder.OC_UNCONDITIONALLY);
}
// Refit the vehicle if necessary
if(needRefit) {
AIVehicle.RefitVehicle(v, service.GetCargo());
}
// Send the vehicle to the destination nearest the depot we built it at
AIVehicle.SkipToVehicleOrder(v, idx);
// Add the vehicle to the service
service.AddVehicle(v);
// Start the vehicle
AIVehicle.StartStopVehicle(v);
}
}
function ServiceManager::SaveMoney() {
foreach(service in this.serviceList) {
local zfz=service.IsProfitable();
if (zfz>0){
SellVehicles(service,(zfz/9).tointeger()+1);
PathZilla.Sleep(100);
}else{
local smjam=0;
local sfz=0;
local smcargo=service.GetCargo();
local smstations={}
local smwaitingCargo=0;
foreach(smtarget in service.GetTargets()) {
smstations[smtarget.GetId()] <- RoadManager.GetStations(smtarget, smcargo, service.GetSubType());
if ((smstations[smtarget.GetId()]).Begin()!=null){
foreach(smstation, _ in smstations[smtarget.GetId()]) {
local smwaiting = AIStation.GetCargoWaiting(smstation, smcargo)+max(0,(50-AIStation.GetCargoRating(smstation,smcargo)));
smstations[smtarget.GetId()].SetValue(smstation, smwaiting);
if (sfz==0){
smjam+=service.VehiclesWaiting(smstation);
sfz++
}
}
if (service.IsTwoWayService()) sfz=0;
smwaitingCargo = ListSum(smstations[smtarget.GetId()]);
}
if (service.IsTwoWayService()) smjam=smjam/2;
if (service.GetActualFleetSize()<1) continue;
if (smwaitingCargo0){
AILog.Warning("Service for " +service+ ": "+smjam+" vehicles waiting for cargo are too much")
service.VehiclesWaiting((smstations[smtarget.GetId()]).Begin(),true);
SellVehicles(service,min((service.GetActualFleetSize()/8+1).tointeger(),smjam-(((3+(service.GetActualFleetSize()/10).tointeger())*2)/3).tointeger()));
}
smjam=0;
}
}
}
local sz=0;
for (;AICompany.GetBankBalance(AICompany.COMPANY_SELF)400&&AIVehicle.GetProfitLastYear(v)0){
local gid=AIVehicle.GetGroupID(service.GetVehicles().Begin());
foreach (v,_ in ::vehiclesToSell){
if (!AIVehicle.IsValidVehicle(v)) continue;
if (AIVehicle.GetGroupID(v)==gid)number--;
}
}else return;
if (number <1) return;
AILog.Info(" Selling " + number + " vehicles...");
local vlist = service.GetVehicles();
vlist.Valuate(function(veh){return AIVehicle.GetProfitLastYear(veh)+AIVehicle.GetProfitThisYear(veh)+(max,0,30-AIVehicle.GetAge(veh)/36)*AIEngine.GetRunningCost(AIVehicle.GetEngineType(veh))});
foreach(vehicle, _ in vlist) {
local empty = (AIVehicle.GetCargoLoad(vehicle, service.GetCargo()) == 0);
if (!empty) vlist.SetValue(vehicle, vlist.GetValue(vehicle)*10*AIVehicle.GetCargoLoad(vehicle, service.GetCargo()));
}
vlist.Sort(AIAbstractList.SORT_BY_VALUE, false);
// Clone a fleet from the prototype vehicle
local i = 1;
for(local vehicle = vlist.Begin(); vlist.HasNext() && i++ <= number; vehicle = vlist.Next()) {
// Turn the vehcile around (to help clear jams) and send it to a depot
AIVehicle.SendVehicleToDepot(vehicle);
AIVehicle.ReverseVehicle(vehicle);
// Remember to sell the vehcile when it stops in a depot
::vehiclesToSell.AddItem(vehicle, 0);
service.RemoveVehicle(vehicle);
}
}
/*
* Update orders for all the vehicles in a service to ensure that vechicles are
* distributed correctly between the available stations.
*/
function ServiceManager::UpdateOrders(service) {
// Get the stations
if (service.GetDistance()==1) return;
local stations = {};
foreach(target in service.GetTargets()) {
stations[target.GetId()] <- RoadManager.GetStations(target, cargo, service.GetSubType());
// If the engine type is articulated, forbid the vehicle from visiting regular stations
if(AIEngine.IsArticulated(engine)) {
foreach(station, _ in stations[target.GetId()]) {
local driveThru = AIRoad.IsDriveThroughRoadStationTile(AIStation.GetLocation(station));
stations[target.GetId()].SetValue(station, (driveThru) ? 1 : 0);
}
stations[target.GetId()].RemoveValue(0);
}
// If the target has no stations then there is no point in building a
// fleet - defer until stations have been built
if(stations[target.GetId()].Count() == 0) {
AILog.Warning("No stations at " + target.GetName());
return;
}
}
// Get the coverage radius of the stations
local truckStation = !AICargo.HasCargoClass(service.GetCargo(), AICargo.CC_PASSENGERS);
local stationType = (truckStation) ? AIStation.STATION_TRUCK_STOP : AIStation.STATION_BUS_STOP;
local radius = AIStation.GetCoverageRadius(stationType);
// Find the acceptance list sums
local accSum = {};
foreach(target in service.GetTargets()) {
foreach(station, _ in stations[target.GetId()]) {
local acceptance = AITile.GetCargoAcceptance(AIStation.GetLocation(station), cargo, 1, 1, radius) + 1;
stations[target.GetId()].SetValue(station, acceptance);
}
// Get the minimum acceptance of all targets
accSum[target.GetId()] <- ListSum(stations[target.GetId()]);
}
// Shuffle the vehicle orders between the stations
foreach(v, _ in service.GetVehicles()) {
local currentOrder = AIOrder.ResolveOrderPosition(v, AIOrder.ORDER_CURRENT);
// Clear the order list
local ocount = AIOrder.GetOrderCount(v);
for(local i = 0; i < ocount; i++) {
AIOrder.RemoveOrder(v, i);
}
// Set the new orders
foreach(target in service.GetTargets()) {
local tile = AIStation.GetLocation(RandomItemByWeight(stations[target.GetId()], accSum[target.GetId()]));
local flags = AIOrder.AIOF_NON_STOP_INTERMEDIATE;
if(!target.IsTown() && target.ProducesCargo(cargo)) flags = flags & AIOrder.AIOF_FULL_LOAD;
AIOrder.AppendOrder(v, tile, flags);
}
// Ensure the vehicle is still heading to the same town it was before
AIVehicle.SkipToVehicleOrder(v, currentOrder);
}
}
function ServiceManager::profit(a=null,b=null){
if (a==null||b==null) return 0;
if (a.rawIncome==b.rawIncome)return 0;
if (a.rawIncome 0) service.vehicles.AddList(vehicles);
}
}
RoadRunner/manager/RoadManager.nut 0000755 0001750 0001750 00000117075 11407204573 014724 0 ustar b b /*
* Copyright © 2008 George Weller
*
* This file is part of PathZilla.
*
* PathZilla is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PathZilla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PathZilla. If not, see .
*
* RoadManager.nut
*
* Handles all road-based construction functions.
*
* Author: George Weller (Zutty)
* Created: 27/07/2008
* Version: 1.1
*/
class RoadManager {
constructor() {
}
}
/*
* Build the required infrastructure for the specified service in the specified
* schema. Stations will be built if necessary, then a road found between the
* stations, and finally depots will be built if necessary. The supplied set
* targetsUpdated will be updated with targets that were modified in the
* operation.
*/
function RoadManager::BuildInfrastructure(service, schema, targetsUpdated) {
// Set the correcy road type before starting
AIRoad.SetCurrentRoadType(schema.GetSubType());
// Ensure that stations available for each target
foreach(target in service.GetTargets()) {
if(target.GetType() == Target.TYPE_TOWN) {
// Ensure that the source town has stations
local added = RoadManager.BuildTownStations(target, service.GetCargo(), service.GetSubType(), service.GetCoverageTarget(), PathZilla.MAX_INITIAL_STATIONS);
if(added > 0) {
targetsUpdated.Insert(target);
}
} else {
// Ensure there is a station at the target
if(!RoadManager.BuildIndustryStation(target, service.GetCargo(), service.GetSubType())) {
AILog.Error("Could not complete infrastructure");
return false;
}
}
}
// Ensure the targets are connected by road
local prev = 0;
for(local next = 1; next < service.GetTargets().len(); next++) {
local from = service.GetTargets()[prev];
local to = service.GetTargets()[next];
// Find a path through the graph
local path = schema.GetPlanGraph().FindPath(from.GetVertex(), to.GetVertex());
if(path == null||!from.IsValid()||!to.IsValid()) {
AILog.Error("No path could be found");
return false;
}
local success = true;
// Walk along the path and ensure the nodes are connected by roads
local ww=0;
local aTarget=0;
local bTarget=0;
for(local walk = path; walk.GetParent() != null; walk = walk.GetParent()) {
local a = walk.GetVertex();
local b = walk.GetParent().GetVertex();
local edge = Edge(a, b);
// If the nodes are not connected in the actual graph a road needs to be built
if(!schema.GetActualGraph().GetEdges().Contains(edge)||(schema.GetTarget(a.GetTargetId(),true)).GetName()==(service.GetTargets()[1]).GetName()||(schema.GetTarget(b.GetTargetId(),true)).GetName()==(service.GetTargets()[0]).GetName()) {
// Get the towns on this edges
ww++;
if (ww<2){ aTarget = schema.GetTarget(a.GetTargetId(),true);
}else{ aTarget = bTarget;
}
bTarget = schema.GetTarget(b.GetTargetId(),true);
if ((!aTarget.IsValid())||(!bTarget.IsValid())) return false
// If the tile is not yet fixed, find one
local stations=AIAbstractList();
stations.AddList(RoadManager.GetStations(aTarget, service.GetCargo(), service.GetSubType()));
if (!stations.IsEmpty())aTarget.FixTile(AIStation.GetLocation(stations.Begin()));
stations.Clear();
stations.AddList(RoadManager.GetStations(bTarget, service.GetCargo(), service.GetSubType()));
if (!stations.IsEmpty())bTarget.FixTile(AIStation.GetLocation(stations.Begin()));
if(aTarget.IsTileUnfixed()) RoadManager.PreFixTarget(bTarget, aTarget, walk, schema);
if(bTarget.IsTileUnfixed()) RoadManager.PreFixTarget(aTarget, bTarget, walk, schema);
// Ensure we can afford to do some construction
FinanceManager.EnsureFundsAvailable(PathZilla.FLOAT);
// Try to build a link between the towns
AILog.Info(" Building a road between " + aTarget.GetName() + " and " + bTarget.GetName() + "...");
local feat = [PathWrapper.FEAT_DEPOT_ALIGN];
local path = PathWrapper.FindPath(aTarget.GetTile(), bTarget.GetTile(), service.GetSubType(), [], true, feat);
success = PathWrapper.TryBuildPath(path, aTarget.GetTile(), bTarget.GetTile(), service.GetSubType(),[], true, feat);
// Only proceed if we were able to build the link
if((success)&&(path!=null)) {
// Firmly fix tiles to better suit what has been built
if(aTarget.IsTileSemiFixed()) RoadManager.PostFixTarget(aTarget, clone path, false);
if(bTarget.IsTileSemiFixed()) RoadManager.PostFixTarget(bTarget, clone path, true);
// Add the edge to the actual graph
schema.GetActualGraph().AddEdge(edge);
} else {
success=false;
break;
}
}
}
if(!success) {
AILog.Error("Could not link");
return false;
}
prev = next;
}
// Also ensure depots are available for each target
// - We do this after for a reason!
local target= service.GetTargets()[0];
local stations=AIAbstractList();
stations.AddList(RoadManager.GetStations(target, service.GetCargo(), service.GetSubType()));
local targett= service.GetTargets()[1];
local stationst=AIAbstractList();
stationst.AddList(RoadManager.GetStations(targett, service.GetCargo(), service.GetSubType()));
local front=(AIMap.DistanceManhattan(AIRoad.GetRoadStationFrontTile(AIStation.GetLocation(stations.Begin())),AIStation.GetLocation(stationst.Begin()))<=AIMap.DistanceManhattan(AIRoad.GetDriveThroughBackTile(AIStation.GetLocation(stations.Begin())),AIStation.GetLocation(stationst.Begin())));
RoadManager.BuildDepot(stations.Begin(), service.GetSubType(),target.IsTown(),front);
local front=(AIMap.DistanceManhattan(AIRoad.GetRoadStationFrontTile(AIStation.GetLocation(stationst.Begin())),AIStation.GetLocation(stations.Begin()))<=AIMap.DistanceManhattan(AIRoad.GetDriveThroughBackTile(AIStation.GetLocation(stationst.Begin())),AIStation.GetLocation(stations.Begin())));
RoadManager.BuildDepot(stationst.Begin(), service.GetSubType(),targett.IsTown(),front);
return true;
}
/*
* Fix a buildable tile before station construction based on distance to
* neighboring targets.
*/
function RoadManager::PreFixTarget(aTarget, bTarget, walk, schema) {
local aTile = aTarget.GetLocation();
local bTile = bTarget.GetLocation();
local cTile = bTile;
if (walk.GetParent().GetParent() != null) {
local tid = walk.GetParent().GetParent().GetVertex().GetTargetId();
cTile = schema.GetTarget(tid,true).GetLocation();
}
/* if (walk.GetParent().GetParent() != null) {
local tid = walk.GetParent().GetParent().GetVertex().GetTargetId();
foreach (target in schema.GetTargets()){
if (target.GetId()==tid&&target.IsValid()){
cTile = target.GetLocation();
break;
}
}
}
*/
// Get a list of tiles around the target
local rad = PathZilla.TARGET_FIX_RADIUS;
local offset = AIMap.GetTileIndex(rad, rad);
local tileList = AITileList();
tileList.AddRectangle(bTile - offset, bTile + offset);
// Find a tile that is roughly equidistant from the other
// targets and has the most buildable tiles around it
local sqDist = sqrt(AITile.GetDistanceSquareToTile(aTile, cTile));
foreach(tile, _ in tileList) {
local cDist = AITile.GetDistanceSquareToTile(cTile, tile);
local score = sqDist - sqrt(cDist);
tileList.SetValue(tile, (AITile.IsBuildable(tile)) ? score.tointeger() : 0);
}
tileList.Sort(AIAbstractList.SORT_BY_VALUE, false);
bTarget.FixTile(tileList.Begin());
}
/*
* Fix a buildable tile after station construction based on a path.
*/
function RoadManager::PostFixTarget(target, path, rev) {
local ftile = target.GetTile();
while (path != null) {
ftile = path.GetTile();
path = path.GetParent();
if(!rev && AITile.GetDistanceManhattanToTile(target.GetTile(), ftile) < 10) break;
if(rev && AITile.GetDistanceManhattanToTile(target.GetTile(), ftile) > 10) break;
}
target.FixTile(ftile);
}
/*
* Maintain the infrastructure for the specified service, by ensuring that
* enough stations have been built. The supplied set targetsUpdated will be
* updated with targets that were modified in the operation.
*/
function RoadManager::MaintainInfrastructure(service, targetsTried, targetsUpdated) {
foreach(target in service.GetTargets()) {
if(target.GetType() == Target.TYPE_TOWN) {
local completeTram = (service.GetSubType() == AIRoad.ROADTYPE_TRAM) && (RoadManager.GetStations(target, service.GetCargo(), service.GetSubType()).Count() > 0);
if(!targetsTried.Contains(target.GetId()) && !completeTram) {
targetsTried.Insert(target.GetId());
local added = RoadManager.BuildTownStations(target, service.GetCargo(), service.GetSubType(), service.GetCoverageTarget(), 1);
if(added > 0) {
targetsUpdated.Insert(target);
}
}
} else if(target.GetType() == Target.TYPE_INDUSTRY) {
// Ensure a station can be found at the target
RoadManager.BuildIndustryStation(target, service.GetCargo(), service.GetSubType());
}
}
}
/*
* Get a list of all the road stations in a town for a specified cargo
*/
function RoadManager::GetStations(target, cargo, roadType) {
AIRoad.SetCurrentRoadType(roadType);
local truckStation = !AICargo.HasCargoClass(cargo, AICargo.CC_PASSENGERS);
local stationType = (truckStation) ? AIStation.STATION_TRUCK_STOP : AIStation.STATION_BUS_STOP;
local radius = AIStation.GetCoverageRadius(stationType);
local townid=0;
if (target.IsTown()) townid=1000000;
// Ensure we get the right type of station
local stationList = AIList();
if(target.IsTown()) {
stationList = AIStationList(stationType);
foreach (st,_ in stationList){
stationList.SetValue(st, (AITile.GetCargoAcceptance(AIStation.GetLocation(st),cargo,1,1,radius)>=8&&AIStation.IsWithinTownInfluence(st,target.GetId()-1000000)
) ? 1 : 0);
}
stationList.RemoveValue(0);
} else {
local coveredTiles = (target.ProducesCargo(cargo)) ? AITileList_IndustryProducing(target.GetId()-townid, radius) : AITileList_IndustryAccepting(target.GetId()-townid, radius);
foreach(tile, _ in coveredTiles) {
local st = AIStation.GetStationID(tile);
if(AIStation.IsValidStation(st)&&AIStation.HasStationType(st,AIStation.STATION_TRUCK_STOP)) {
stationList.AddItem(st, 0);
}
}
}
// Ensure the stations have the correct road type
foreach(station, _ in stationList) {
local correctRt = (AIRoad.HasRoadType(AIStation.GetLocation(station), roadType));
stationList.SetValue(station, (correctRt) ? 1 : 0);
}
stationList.RemoveValue(0);
return stationList;
}
/*
* Get the combined coverage area of all stations in a town for a specified
* cargo, as a parcentage of all houses in that town. This helps determine how
* many stations can be placed in a town. If the AI is set not to be agressive
* it will count competitor's stations in the total coverage.
*/
function RoadManager::GetTownCoverage(town, cargo, roadType) {
// Initialise a few details
AIRoad.SetCurrentRoadType(roadType);
local townid=0;
if (town>=1000000) local townid=1000000;
local truckStation = !AICargo.HasCargoClass(cargo, AICargo.CC_PASSENGERS);
local stationType = (truckStation) ? AIStation.STATION_TRUCK_STOP : AIStation.STATION_BUS_STOP;
local radius = AIStation.GetCoverageRadius(stationType);
local offset = AIMap.GetTileIndex(radius, radius);
// Get a list of our stations in the town
local stationList = AIStationList(stationType);
stationList.Valuate(AIStation.IsWithinTownInfluence, town-townid);
stationList.RemoveValue(0);
// Ensure the stations have the correct road type
foreach(station, _ in stationList) {
stationList.SetValue(station, (AIRoad.HasRoadType(AIStation.GetLocation(station), roadType)) ? 1 : 0);
}
stationList.RemoveValue(0);
// Get a list of tiles that fall within the coverage area of those stations
local coveredTiles = AITileList();
foreach(station, _ in stationList) {
local tile = AIStation.GetLocation(station);
coveredTiles.AddRectangle(tile - offset, tile + offset);
}
// Include competitors stations if we are not agressive
if(!Settings.IsAggressive()) {
// Get a large area around the town
local townTile = AITown.GetLocation(town-townid);
local searchRadius = min(AIMap.DistanceFromEdge(townTile) - 1, PathZilla.MAX_TOWN_RADIUS+12);
local off = AIMap.GetTileIndex(searchRadius, searchRadius);
local tileList = AITileList();
tileList.AddRectangle(townTile - off, townTile + off);
// Find those tiles that are controlled by competitors
foreach(tile, _ in tileList) {
local owner = AITile.GetOwner(tile);
local isCompetitors = (owner != AICompany.ResolveCompanyID(AICompany.COMPANY_SELF) && owner != AICompany.ResolveCompanyID(AICompany.COMPANY_INVALID));
// If its a station tile and not ours then look into it
if(AITown.IsWithinTownInfluence(town-townid, tile) && isCompetitors && AITile.IsStationTile(tile)) {
// Identify the station type
local stRadius = 0;
if(LandManager.IsRoadStationAny(tile)) {
stRadius = AIStation.GetCoverageRadius(AIStation.STATION_BUS_STOP);
} else if(AITile.HasTransportType(tile, AITile.TRANSPORT_RAIL)) {
stRadius = AIStation.GetCoverageRadius(AIStation.STATION_TRAIN);
} else if(AIMarine.IsDockTile(tile)) {
stRadius = AIStation.GetCoverageRadius(AIStation.STATION_DOCK);
} else if(AIAirport.IsAirportTile(tile)) {
// TODO - This doesn't work - yet!
stRadius = AIAirport.GetAirportCoverageRadius(AIAirport.GetAirportType(tile));
}
// Add the station's coverage radius to the list
if(stRadius > 0) {
local offs = AIMap.GetTileIndex(stRadius, stRadius);
coveredTiles.AddRectangle(tile - offs, tile + offs);
}
}
}
}
// Reinstate the following after OpenTTD 0.7.2 si released
coveredTiles.Valuate(AITile.GetCargoAcceptance, cargo, 1, 1, 0);
coveredTiles.Valuate(LandManager.CargoAcceptanceOnTile, cargo);
coveredTiles.RemoveBelowValue(1);
//AILog.Info(AITown.GetName(town) + " has " + AITown.GetHouseCount(town) + " houses");
//AILog.Info(coveredTiles.Count() + " tiles covered");
return (coveredTiles.Count() * 100) / AITown.GetHouseCount(town-townid);
}
/*
* Build enough stations in a town such that the combined coverage meets or
* exceeds the target coverage percentage.
*
* The function returns the number of stations that were added.
*/
function RoadManager::BuildTownStations(target, cargo, roadType, coverageTarget, maxAdd = 100) {
AIRoad.SetCurrentRoadType(roadType);
local town = target.GetId();
local strType = (roadType == AIRoad.ROADTYPE_ROAD) ? "road" : "tram";
AILog.Info(" Building " + strType + " stations in " + target.GetName() + "...");
local numStationsBuilt = 0;
// Set the correct road type before starting
AIRoad.SetCurrentRoadType(roadType);
// Get the type of station that is needed
local truckStation = !AICargo.HasCargoClass(cargo, AICargo.CC_PASSENGERS);
local stationType = (truckStation) ? AIStation.STATION_TRUCK_STOP : AIStation.STATION_BUS_STOP;
// Get the stations already built in the town
local stationList = AIStationList(stationType);
stationList.Valuate(AIStation.IsWithinTownInfluence, town-1000000);
stationList.RemoveValue(0);
// Build new stations if there are none or until the coverage exceeds the target
local stationID = 0;
local iz=0;
while(((stationList.Count() + numStationsBuilt == 0) || RoadManager.GetTownCoverage(town, cargo, roadType) <= coverageTarget) && stationID >= 0 && numStationsBuilt < maxAdd&&iz<100) {
iz++;
if (iz%10==9&&AICompany.GetBankBalance(AICompany.COMPANY_SELF)<=0){
SaveMoney();
}
PathZilla.Sleep(10);
::pz.HandleEvents();
stationID = RoadManager.BuildStation(target, cargo, roadType);
if(stationID >= 0) {
numStationsBuilt++;
}
}
if (stationList.Count()>2) TownManager.BuildStatue(town-1000000);
return numStationsBuilt;
}
/*
* Check that a station has been built to serivce the specified industry and
* cargo. If not one will be built and the target tile semi-fixed.
*/
function RoadManager::BuildIndustryStation(target, cargo, roadType,fleet=0) {
AIRoad.SetCurrentRoadType(roadType);
local stations = RoadManager.GetStations(target, cargo, roadType);
local station = null;
if(stations.IsEmpty()) {
station = RoadManager.BuildStation(target, cargo, roadType);
}else {
local trucks=fleet;
foreach (service in GetServices()){
foreach (station , _ in stations){
if (service.GetDistance()>1&&AIVehicle.GetGroupID(service.GetVehicles().Begin())==AIVehicle.GetGroupID(AIVehicleList_Station(station).Begin())&&AIVehicle.IsValidVehicle(service.GetVehicles().Begin())){
trucks=trucks+((service.GetActualFleetSize()*100)/service.GetDistance()).tointeger();
break;
}
}
}
if (trucks>100*stations.Count()){
station = RoadManager.BuildStation(target, cargo, roadType);
if (station < 0||station==null) {
station=stations.Begin();
}else{
local path = PathWrapper.FindPath(AIStation.GetLocation(station), AIStation.GetLocation(stations.Begin()), roadType, [], true, []);
if(path == null) {
station=stations.Begin();
}else if(path.GetParent == null){
station=stations.Begin();
}else if(PathWrapper.BuildPath(path, roadType) != 0){
station=stations.Begin();
}
if (!AIStation.IsValidStation(station)) return false;
RoadManager.BuildDepot(station,roadType,false,true);
}
}else{
station = stations.Begin();
}
}
if(!target.IsTileFixed() && station != null && station > -1) {
local tile = AIRoad.GetRoadStationFrontTile(AIStation.GetLocation(station));
target.SemiFixTile(tile);
}
return (station > -1);
}
/*
* Build a single station in the specified town to accept the specified cargo.
* The position of the station will be selected based on the maximum level
* of acceptance.
*/
function RoadManager::BuildStation(target, cargo, roadType) {
AIRoad.SetCurrentRoadType(roadType);
if (!target.IsValid())return -1;
local targetLocation = target.GetLocation();
local targetTile = target.GetTile();
local townid=0;
if (target.IsTown()) townid=1000000;
// Get a list of tiles to search in
local searchRadius = min(AIMap.DistanceFromEdge(targetLocation) - 1, PathZilla.MAX_TOWN_RADIUS+12);
local offset = AIMap.GetTileIndex(searchRadius, searchRadius);
// Before we do anything, check the local authority rating
local nearestTown = TownManager.FindNearestTown(targetLocation)
// Try to improve the local authority rating if necessary
TownManager.HandleRating(nearestTown);
// Check if we are allowed to build in town
if(!TownManager.CanBuildInTown(nearestTown)) {
AILog.Error(AITown.GetName(nearestTown) + " local authority refuses construction");
return -1;
}
// Get the type of station we should build and its radius
local truckStation = !AICargo.HasCargoClass(cargo, AICargo.CC_PASSENGERS);
local stationType = (truckStation) ? AIStation.STATION_TRUCK_STOP : AIStation.STATION_BUS_STOP;
local radius = AIStation.GetCoverageRadius(stationType);
// Get a list of tiles
local tileList = AITileList();
if(target.IsTown()) {
tileList.AddRectangle(targetLocation - offset, targetLocation + offset);
} else {
if(target.ProducesCargo(cargo)) {
tileList = AITileList_IndustryProducing(target.GetId()-townid, radius);
} else {
tileList = AITileList_IndustryAccepting(target.GetId()-townid, radius);
}
}
// Get a list of existing stations - INCOMPATIBLE WITH MULTIPLE TRANSPORT TYPES
local stationList = AIList();
if(target.IsTown()) {
stationList = AIStationList(stationType);
stationList.Valuate(AIStation.IsWithinTownInfluence, target.GetId()-1000000);
stationList.RemoveValue(0);
} else {
local coveredTiles = (target.ProducesCargo(cargo)) ? AITileList_IndustryProducing(target.GetId(), radius) : AITileList_IndustryAccepting(target.GetId(), radius);
foreach(tile, _ in coveredTiles) {
local st = AIStation.GetStationID(tile);
stationList.AddItem(st, 0);
}
}
// Remove tiles surrounging our stations, to ensure they aren't built too close
local stationSpacing = (radius * 3) / 2;
local first=true;
foreach(station, _ in stationList) {
if((target.IsTown()&&AIStation.HasStationType(station,stationType)&&AIStation.IsValidStation(station))){
first=false;
offset = AIMap.GetTileIndex(5,5)
}else{
offset = AIMap.GetTileIndex(1,1);
}
local tile = AIStation.GetLocation(station);
tileList.RemoveRectangle(tile - offset, tile + offset);
}
// Calculate the station spacing
local comptSpacing = (target.IsTown() && (Settings.IsAggressive() || stationList.Count() == 0)) ? 1 : stationSpacing;
// Find a list of tiles that are controlled by competitors
foreach(tile, _ in tileList) {
local owner = AITile.GetOwner(tile);
local isCompetitors = (owner != AICompany.ResolveCompanyID(AICompany.COMPANY_SELF) && owner != AICompany.ResolveCompanyID(AICompany.COMPANY_INVALID));
if(!target.IsTown()&&AITile.IsStationTile(tile)&& isCompetitors) {
tileList.RemoveRectangle(tile-AIMap.GetTileIndex(1,1),tile+AIMap.GetTileIndex(1,1));
}else if(AITile.IsStationTile(tile) || isCompetitors||(target.IsTown()&&!AIRoad.IsRoadTile(tile))) {
tileList.RemoveTile(tile);
}
}
// Check if the game allows us to build DTRSes on town roads and get the road type
local dtrsOnTownRoads = (AIGameSettings.GetValue("construction.road_stop_on_town_road") == 1);
// Get some information about the nearest town's road layout
local layoutType = AITown.GetRoadLayout(nearestTown);
local tlayout = (layoutType == AITown.ROAD_LAYOUT_2x2) ? 3 : ((layoutType == AITown.ROAD_LAYOUT_3x3) ? 4 : 0);
local tx = AIMap.GetTileX(AITown.GetLocation(nearestTown));
local ty = AIMap.GetTileY(AITown.GetLocation(nearestTown));
// Rank those tiles by their suitability for a station
foreach(tile, _ in tileList) {
// Find roads that are connected to the tile
local adjRoadListRd = LandManager.GetAdjacentTileList(tile);
AIRoad.SetCurrentRoadType(AIRoad.ROADTYPE_ROAD);
adjRoadListRd.Valuate(AIRoad.AreRoadTilesConnected, tile);
adjRoadListRd.KeepValue(1);
// Find tram tracks that are connected to the tile
local adjRoadListTrm = LandManager.GetAdjacentTileList(tile);
AIRoad.SetCurrentRoadType(AIRoad.ROADTYPE_TRAM);
adjRoadListTrm.Valuate(AIRoad.AreRoadTilesConnected, tile);
adjRoadListTrm.KeepValue(1);
// Combine to see all the adjacent road tiles of any type that are connected to
adjRoadListTrm.AddList(adjRoadListRd);
local adjRoads = ListToArray(adjRoadListTrm);
local straightRoad = false;
// Check if the road tile is a straight road
if(adjRoads.len() == 1) {
straightRoad = true;
} else if(adjRoads.len() == 2) {
local dx = abs(adjRoads[1] % AIMap.GetMapSizeY()) - abs(adjRoads[0] % AIMap.GetMapSizeY());
local dy = abs(adjRoads[1] / AIMap.GetMapSizeY()) - abs(adjRoads[0] / AIMap.GetMapSizeY());
straightRoad = (dx == 0) || (dy == 0);
}
// Reset road type
AIRoad.SetCurrentRoadType(roadType);
// Find the roads that would run parallel to a DTRS in this spot
local parlRoadList = LandManager.GetAdjacentTileList(tile);
foreach(_tile, _ in parlRoadList) {
local parl = AIRoad.IsRoadTile(_tile) && !AIRoad.AreRoadTilesConnected(tile, _tile);
parlRoadList.SetValue(_tile, (parl) ? 1 : 0);
}
parlRoadList.KeepValue(1);
local parlRoads = ListToArray(parlRoadList);
local inCorner = false;
if(parlRoads.len() >= 3) {
inCorner = true;
} else if(parlRoads.len() == 2) {
local dx = abs(parlRoads[1] % AIMap.GetMapSizeY()) - abs(parlRoads[0] % AIMap.GetMapSizeY());
local dy = abs(parlRoads[1] / AIMap.GetMapSizeY()) - abs(parlRoads[0] / AIMap.GetMapSizeY());
inCorner = (dx != 0) || (dy == 0);
}
// Check if this tile is acceptable
local canBuildOnRoad = (AITile.GetOwner(tile) == AICompany.COMPANY_INVALID) ? dtrsOnTownRoads : AICompany.IsMine(AITile.GetOwner(tile));
local cl = (AIRoad.IsRoadTile(tile)) ? ((canBuildOnRoad) ? straightRoad : false) : LandManager.IsClearable(tile);
local acceptable = LandManager.IsLevel(tile) && cl;
if(target.IsTown()) acceptable = acceptable && AITown.IsWithinTownInfluence(target.GetId()-1000000, tile);
// Do not allow stations on the junctions in a town with a grid layout
if(target.IsTown() && tlayout != 0) {
local dx = abs(AIMap.GetTileX(tile) - tx) % tlayout;
local dy = abs(AIMap.GetTileY(tile) - ty) % tlayout;
if(dx == 0 && dy == 0) acceptable = false;
}
// Get the cargo acceptance around the tile
local score = 0;
local threshold = 0;
if(!target.IsTown() && target.ProducesCargo(cargo)) {
score = AITile.GetCargoProduction(tile, cargo, 1, 1, radius);
} else {
score = AITile.GetCargoAcceptance(tile, cargo, 1, 1, radius);
target.IsTown()&&target.ProducesCargo(cargo)&&(!first)?threshold = 40:threshold=8;
}
acceptable = acceptable && (score >= threshold);
if(target.IsTown()) {
// Penalise tiles in a corner
score /= (inCorner) ? 3 : 1;
// Promote tiles on road we can build on
score += (AIRoad.IsRoadTile(tile) && canBuildOnRoad) ? 30 : 0;
}
// If the spot is acceptable, return tile score
tileList.SetValue(tile, ((acceptable) ? score : 0));
}
tileList.Sort(AIAbstractList.SORT_BY_VALUE, false);
// Remove unacceptable tiles
tileList.RemoveValue(0);
// If we can't find any suitable tiles then just give up!
if(tileList.Count() == 0) {
if(stationList.Count() == 0) {
// AILog.Error(" Station could not be built at " + target.GetName() + "!");
}
return -1;
}
// The tiles we need for reference
local stationTile = null;
local roadTile = null;
local otherSide = null;
// Check each tile for valid paths that will connect it to the town.
local success = false;
foreach(stTile, _ in tileList) {
local path = true;
// Find a path only if we know where were going
if(target.IsTileFixed()) {
path = PathWrapper.FindPath(targetTile, stTile, roadType, [], true, [PathWrapper.FEAT_GRID_LAYOUT, PathWrapper.FEAT_DEPOT_ALIGN, PathWrapper.FEAT_SHORT_SCOPE]);
}
// If no path was found, try another tile
if(path == null) continue;
// Find and check the road and other side tiles
if(target.IsTileFixed()) {
roadTile = (path.GetParent() != null) ? path.GetParent().GetTile() : targetTile;
otherSide = LandManager.GetApproachTile(stTile, roadTile);
// If the other side is unsuitable, try another tile
if(!LandManager.IsRoadable(otherSide)) continue;
} else {
// Choose an orientation for the station
local adj = LandManager.GetAdjacentTileList(stTile);
foreach(rtile, _ in adj) {
local otile = LandManager.GetApproachTile(stTile, rtile);
local acc = LandManager.IsRoadable(rtile) && LandManager.IsRoadable(otile) && AIRoad.CanBuildConnectedRoadPartsHere(stTile, rtile, otile);
local score = ((acc) ? 1 : 0) + ((AIRoad.IsRoadTile(rtile)) ? 1 : 0) + ((AIRoad.IsRoadTile(otile)) ? 1 : 0);
adj.SetValue(rtile, score);
}
adj.RemoveValue(0);
// If it doesn't fit either way around, try another tile
if(adj.IsEmpty()) continue;
// Set the road and other side tiles
roadTile = adj.Begin();
otherSide = LandManager.GetApproachTile(stTile, roadTile);
}
// Choose a tile to loop to
local loopTile = (target.IsTown() && stTile != targetTile) ? targetTile : roadTile;
// Set the list of RPF features for the loop
local features = [PathWrapper.FEAT_SHORT_SCOPE];
if(target.IsTown() || AITown.IsWithinTownInfluence(nearestTown, stTile)) features.append(PathWrapper.FEAT_GRID_LAYOUT);
if(target.IsTown()) features.append(PathWrapper.FEAT_ROAD_LOOP);
// Find a loop back to the town
local loop = PathWrapper.FindPath(loopTile, otherSide, roadType, [stTile],false, features);
// Get the first tile in the loop
local firstTile = (loop != null) ? PathWrapper.GetFirstTile(loop) : -1;
// Check that the loop exists and that it can connect to the station
if((roadType==AIRoad.ROADTYPE_TRAM||!target.IsTown())&&loop != null && loop.GetParent() != null && (AIRoad.CanBuildConnectedRoadPartsHere(otherSide, stTile, loop.GetParent().GetTile()) != 0) && (AIRoad.CanBuildConnectedRoadPartsHere(loopTile, stTile, firstTile) != 0)) {
// Build the path. If it fails try another tile.
local pathed = true;
if(target.IsTileFixed()) pathed = (PathWrapper.BuildPath(path, roadType) == 0);
if(!pathed) continue;
// Build the loop. If it fails try another tile.
local looped = (PathWrapper.BuildPath(loop, roadType) == 0);
if(!looped) continue;
// Join the path and the loop to the station tile
RoadManager.SafelyBuildRoad(otherSide, stTile);
RoadManager.SafelyBuildRoad(roadTile, stTile);
stationTile = stTile;
} else {
if (!target.IsTown()){
AILog.Warning(" Could not find loop to station!");
continue;
}else{
path=PathWrapper.FindPath(AITown.GetLocation(target.GetId()-1000000), stTile, roadType, [], false, [PathWrapper.FEAT_DEPOT_ALIGN, PathWrapper.FEAT_SHORT_SCOPE,PathWrapper.FEAT_CHK_CONNECTION]);
if (path==null)continue;
stationTile=stTile;
}
}
// If we couldn;t find a path to any of the tiles then give up.
if(stationTile == null) {
AILog.Warning(" Station could not be reached at " + target.GetName() + "!");
continue;
}
// Ensure we have a bit of cash available
FinanceManager.EnsureFundsAvailable(PathZilla.FLOAT);
AILog.Info(" Building a station at " + target.GetName() + "...");
// Clean up little road stubs, if any
if(AIRoad.IsRoadTile(stationTile)) {
local sideRoads = LandManager.GetAdjacentTileList(stationTile);
sideRoads.RemoveTile(roadTile);
sideRoads.RemoveTile(otherSide);
foreach(side, _ in sideRoads) {
AIRoad.SetCurrentRoadType(AIRoad.ROADTYPE_ROAD);
AIRoad.RemoveRoad(stationTile, side);
AIRoad.SetCurrentRoadType(AIRoad.ROADTYPE_TRAM);
AIRoad.RemoveRoad(stationTile, side);
}
// Reset the original road type
AIRoad.SetCurrentRoadType(roadType);
}
local attempts = 0;
local fail = false;
// Finally, try to build the station
while(!success && attempts++ < PathZilla.MAX_CONSTR_ATTEMPTS) {
local rvType = (truckStation) ? AIRoad.ROADVEHTYPE_TRUCK : AIRoad.ROADVEHTYPE_BUS;
success = AIRoad.BuildDriveThroughRoadStation(stationTile, roadTile, rvType, AIStation.STATION_NEW);
if(!success) {
switch(AIError.GetLastError()) {
case AIRoad.ERR_ROAD_DRIVE_THROUGH_WRONG_DIRECTION:
// This shouldn't happen. Try to clear the tile, and if
// that doesn't work then just give up.
if(attempts <= 1) {
//AITile.DemolishTile(stationTile);
} else {
break;
}
break;
case AIError.ERR_AREA_NOT_CLEAR:
// Something must have been built since we checked the tile. Clear it.
if (!AITile.IsWaterTile(stationTile)&&!AIRoad.IsRoadDepotTile(stationTile)&&!AIRoad.IsRoadStationTile(stationTile)&&!AIRoad.IsDriveThroughRoadStationTile(stationTile)) AITile.DemolishTile(stationTile);
break;
case AIError.ERR_NOT_ENOUGH_CASH:
if(!FinanceManager.CanAfford((PathZilla.FLOAT)/2)) {
// We cant afford to borrow any more money, so give up!
AILog.Error(" CAN'T AFFORD IT - ABORTING!");
break;
} else {
// Otherwise, borrow some more money
FinanceManager.Borrow();
}
break;
case AIError.ERR_VEHICLE_IN_THE_WAY:
// Theres a vehicle in the way... just wait a bit.
PathZilla.Sleep(50);
break;
// NO idea what to do here! Just give up.
case AIError.ERR_UNKNOWN:
break;
break;
}
}
}
if(!success) {
local strType = (truckStation) ? "TRUCK" : ((roadType == AIRoad.ROADTYPE_TRAM) ? "TRAM" : "BUS");
AILog.Warning(strType + " STOP WAS NOT BUILT");
}
if (success) return AIStation.GetStationID(stationTile);
}
return (success) ? AIStation.GetStationID(stationTile) : -1;
}
/*
* Build a road from tileA to tileB, handling any errors that may occur.
*/
function RoadManager::SafelyBuildRoad(tileA, tileB) {
local built = false;
local tries = 0;
local MAX_TRIES = 100;
while(!built && tries++ < MAX_TRIES) {
built = AIRoad.BuildRoad(tileA, tileB);
if(!built) {
switch(AIError.GetLastError()) {
case AIError.ERR_ALREADY_BUILT:
// Just don't worry about this!
built = true;
break;
case AIError.ERR_AREA_NOT_CLEAR:
// Something must have been built since we check the tile. Clear it.
local cleared=true;
if (!(AIRoad.IsRoadStationTile(tileB)||AIRoad.IsRoadDepotTile(tileB)||AIRoad.IsDriveThroughRoadStationTile(tileB)||AIBridge.IsBridgeTile(tileB)||AITunnel.IsTunnelTile(tileB))){
cleared = AITile.DemolishTile(tileB);
}
if(!cleared) {
AILog.Error(" Construction of bus stop was blocked");
return cleared;
}
break;
case AIError.ERR_NOT_ENOUGH_CASH:
AILog.Error(" CAN'T AFFORD IT!");
if(!FinanceManager.CanAfford(PathZilla.FLOAT)) {
// We cant afford to borrow any more money, so give up!
AILog.Error(" ABORT!!");
return false;
} else {
// Otherwise, borrow some more money
FinanceManager.EnsureFundsAvailable(PathZilla.FLOAT);
}
break;
case AIError.ERR_VEHICLE_IN_THE_WAY:
AILog.Error(" Vehicle in the way");
// Theres a vehicle in the way... just wait a bit.
PathZilla.Sleep(100);
break;
}
}
}
return built;
}
/*
* Build a depot at the specified target if none exits
*/
function RoadManager::BuildDepot(target, roadType, town=false,mustBuild=false,front=true) {
AIRoad.SetCurrentRoadType(roadType);
local strType = (roadType == AIRoad.ROADTYPE_ROAD) ? "road" : "tram";
AILog.Info(" Checking for a " + strType + " depot near " + AIStation.GetName(target) + "...");
local targetTile = AIStation.GetLocation(target);
if (front&&AIMap.IsValidTile(AIRoad.GetRoadStationFrontTile(targetTile))) targetTile=AIRoad.GetRoadStationFrontTile(targetTile);
if ((!front||!AIRoad.IsRoadTile(targetTile))&&AIMap.IsValidTile(AIRoad.GetDriveThroughBackTile(targetTile))) targetTile=AIRoad.GetDriveThroughBackTile(targetTile);
AIRoad.SetCurrentRoadType(roadType);
// Check for existing depots in the town
local depots = AIDepotList(AITile.TRANSPORT_ROAD);
depots.Valuate(AIRoad.HasRoadType, roadType);
depots.KeepValue(1);
if(town) {
foreach(depot, _ in depots) {
local inTown = AITown.IsWithinTownInfluence(AIStation.GetNearestTown(target), depot);
depots.SetValue(depot, (inTown) ? 1 : 0);
}
depots.KeepValue(1);
} else {
depots.Valuate(AITile.GetDistanceManhattanToTile, targetTile);
if (mustBuild==true){
depots.KeepBelowValue(3);
}else{
depots.KeepBelowValue(15);
}
depots.KeepBottom(1);
foreach(depot, _ in depots) {
local value=0;
if (PathWrapper.FindPath(depot,targetTile,roadType,[],false,[PathWrapper.FEAT_CHK_CONNECTION,PathWrapper.FEAT_SHORT_SCOPE,PathWrapper.FEAT_DEPOT_ALIGN])){
value = 1; // TODO - Is depot connected to target tile
}
depots.SetValue(depot, value);
if (value==1) break;
}
depots.KeepValue(1);
}
// If there aren't any we need to build one
if(depots.Count() == 0) {
AILog.Info(" Building a new depot...");
// Get some details about the nearest town
local nearestTown = -1;
if(town) {
nearestTown = AIStation.GetNearestTown(target);
} else {
local towns = AITownList();
towns.Valuate(AITown.GetDistanceManhattanToTile, targetTile);
towns.Sort(AIAbstractList.SORT_BY_VALUE, true);
nearestTown = towns.Begin();
}
local layoutType = AITown.GetRoadLayout(nearestTown);
local tlayout = (layoutType == AITown.ROAD_LAYOUT_2x2) ? 3 : ((layoutType == AITown.ROAD_LAYOUT_3x3) ? 4 : 0);
local tx = AIMap.GetTileX(AITown.GetLocation(nearestTown));
local ty = AIMap.GetTileY(AITown.GetLocation(nearestTown));
// Get a list of tiles to search in
local searchRadius = PathZilla.MAX_TOWN_RADIUS+12;
local tileList = AITileList();
//AILog.Warning(max(AIMap.GetTileX(targetTile) - searchRadius,1)+"/"+max(AIMap.GetTileY(targetTile) - searchRadius,1)+"-"+min(AIMap.GetTileX(targetTile) + searchRadius,AIMap.GetMapSizeX()-2)+"/"+min(AIMap.GetTileY(targetTile) + searchRadius,AIMap.GetMapSizeY()-2));
tileList.AddRectangle(AIMap.GetTileIndex(max(AIMap.GetTileX(targetTile) - searchRadius,1),max(AIMap.GetTileY(targetTile) - searchRadius,1)),AIMap.GetTileIndex(min(AIMap.GetTileX(targetTile) + searchRadius,AIMap.GetMapSizeX()-2),min(AIMap.GetTileY(targetTile) + searchRadius,AIMap.GetMapSizeY()-2)));
// Rank those tiles by their suitability for a depot
foreach(tile, _ in tileList) {
// Find suitable roads adjacent to the tile
local adjRoads = LandManager.GetAdjacentTileList(tile);
foreach(_tile, _ in adjRoads) {
local adj = (AITile.GetSlope(_tile) == AITile.SLOPE_FLAT && AIRoad.IsRoadTile(_tile) && AIRoad.HasRoadType(_tile, roadType));
adjRoads.SetValue(_tile, (adj) ? 1 : 0);
}
adjRoads.KeepValue(1);
local score = 0;
// Only score tiles that can be built on
if(!AITile.IsWaterTile(tile) && LandManager.IsLevel(tile) && !AIRoad.IsRoadTile(tile) && !AIRoad.IsRoadStationTile(tile)
&& !AIBridge.IsBridgeTile(tile) && !AITunnel.IsTunnelTile(tile) && !AIRoad.IsRoadDepotTile(tile)) {
score = AITile.GetDistanceManhattanToTile(targetTile, tile);
score = ((searchRadius * 2)- score+1)
if(adjRoads.Count() > 0) score += 10000;
if(AITile.IsBuildable(tile)) score += 1000;
if(town && AITown.IsWithinTownInfluence(AIStation.GetNearestTown(target), tile)) score += 100;
// If the town has a grid road layout, penalise tiles that fall on the grid
// if(tlayout != 0) {
// local dx = abs(AIMap.GetTileX(tile) - tx) % tlayout;
// local dy = abs(AIMap.GetTileY(tile) - ty) % tlayout;
// if(dx == 0 || dy == 0) score = AITile.GetDistanceManhattanToTile(target.GetTile(), tile);
// }
}
tileList.SetValue(tile, score);
// AILog.Warning(AIMap.GetTileX(tile)+"/"+AIMap.GetTileY(tile)+"="+score);
}
tileList.Sort(AIAbstractList.SORT_BY_VALUE, false);
// Remove tiles that are unsuitable
tileList.RemoveBelowValue(1);
// Try each location
local ignoreTiles=[null];
if (!town){
ignoreTiles[0]=AIStation.GetLocation(target);
}else{
ignoreTiles[0]=0;
}
if (!AIMap.IsValidTile(targetTile)) return false;
foreach(depotTile, _ in tileList) {
if (!AIMap.IsValidTile(depotTile)) continue;
local path = PathWrapper.FindPath(targetTile, depotTile, roadType, ignoreTiles, true, [PathWrapper.FEAT_DEPOT_ALIGN,PathWrapper.FEAT_SHORT_SCOPE]);
local pathb=path;
if (AIMap.IsValidTile(AIStation.GetLocation(target)-(targetTile-AIStation.GetLocation(target))))pathb = PathWrapper.FindPath(AIStation.GetLocation(target)-(targetTile-AIStation.GetLocation(target)), depotTile, roadType, ignoreTiles, true, [PathWrapper.FEAT_DEPOT_ALIGN,PathWrapper.FEAT_SHORT_SCOPE]);
local p=path;
local po=0;
while (p != null) {
if(p.GetParent() != null) po++;
p = p.GetParent();
}
local p=pathb;
local pt=0;
while (p != null&&pt