MetaLib-7/Array.nut 0000666 0000000 0000000 00000052111 12305126323 012474 0 ustar 0000000 0000000 /* Array SubLibrary, v.6 [2014-03-03],
* part of Minchinweb's MetaLibrary v.8,
* originally part of WmDOT v.5 r.53d [2011-04-09]
* and WmArray library v.1 r.1 [2011-02-13].
* Copyright © 2011-14 by W. Minchin. For more info,
* please visit https://github.com/MinchinWeb/openttd-metalibrary
*
* Permission is granted to you to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell this software, and provide these
* rights to others, provided:
*
* + The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the software.
* + Attribution is provided in the normal place for recognition of 3rd party
* contributions.
* + You accept that this software is provided to you "as is", without warranty.
*/
/** \brief Array
* \version v.5 (2014-02-28)
* \author W. Minchin (%MinchinWeb)
* \since MetaLibrary v.6
*
* This is a collection of functions to make working with Arrays easier.
* \note While Arrays are powerful, also consider using
* [AIList](http://noai.openttd.org/api/trunk/classAIList.html)'s.
*/
class _MinchinWeb_Array_ {
main = null;
/** \publicsection
* \brief Creates a one dimensional (1-D) array.
* \param length the desired length of the array
* \return empty array of the given size
* \see Create2D()
* \see Create3D()
* \static
*/
function Create1D(length) { return array[length]; }
/** \brief Creates a two dimensional (2-D) array.
* \param length the desired length of the array (in the first
* dimension)
* \param width the desired length of the array (in the second
* dimension)
* \return empty array of the given size (in two dimensions)
* \see Create1D()
* \see Create3D()
* \static
*/
function Create2D(length, width);
/** \brief Creates a three dimensional (3-D) array.
* \param length the desired length of the array (in the first
* dimension)
* \param width the desired length of the array (in the second
* dimension)
* \param height the desired length of the array (in the third
* dimension)
* \return empty array of the given size (in three dimensions)
* \see Create1D()
* \see Create2D()
* \static
*/
function Create3D(length, width, height);
/** \brief Converts a one dimensional array to a nice string format.
*
* This function was created to aid in the output of arrays to the AI
* debug screen.
* \param InArray one dimensional (1-D) array
* \param DisplayLength whether to prefix the output with the length
* of the array
* \param replaceNull whether the replace `null` values with '-'
* \return string version of array. e.g. `The array is 3 long. 3 4 5`.
* \return `null` if `InArray` is `null`.
* \see ToString2D()
* \see ToStringTiles1D()
* \todo Add error check that an array is provided
* \static
*/
function ToString1D(InArray, DisplayLength = true, replaceNull = false);
/** \brief Converts a one dimensional array to a nice string format.
*
* This function was created to aid in the output of arrays to the AI
* debug screen.
* \param InArray two dimensional (2-D) array
* \param DisplayLength whether to prefix the output with the length
* of the array
* \return string version of array.
* e.g. `The array is 2 long. 3 4 / 5 6`.
* \return `null` if `InArray`
* is `null`.
* \see ToString1D()
* \see ToStringTiles2D()
* \todo Add error check that a 2D array is provided
* \static
*/
function ToString2D(InArray, DisplayLength = true);
/** \brief Searches an array for a given value.
*
* \param InArray array to search
* (assumed to be one dimensional (1-D))
* \param SearchValue what is searched for
* \return `true` if found at least once, `false` if not. `null` if
* `InArray` is `null`.
* \see ContainedIn1DIn2D()
* \see ContainedIn2D()
* \see ContainedIn3D()
* \see Find1D()
* \todo Add error check that an array is provided
* \static
*/
function ContainedIn1D(InArray, SearchValue);
/** \brief Searches a (two dimensional) array for a given value.
* \param InArray array to search
* (assumed to be two dimensional (2-D))
* \param SearchValue what is searched for
* \return `true` if found at least once, `false` if not. `null` if
* `InArray` is `null`.
* \note using this to see if an given array is an element of the parent
* array does not seem to be returning expected results. Use
* ContainedInPairs() instead.
* \see ContainedInPairs()
* \see ContainedIn1DIn2D()
* \see ContainedIn1D()
* \see ContainedIn3D()
* \see Find2D()
* \todo Add error check that an array is provided
* \static
*/
function ContainedIn2D(InArray, SearchValue);
/** \brief Searches a (three dimensional) array for a given value.
* \param InArray array to search
* (assumed to be three dimensional (3-D))
* \param SearchValue what is searched for
* \return `true` if found at least once, `false` if not. `null` if
* `InArray` is `null`.
* \see Find3D()
* \todo Add error check that an array is provided
* \static
*/
function ContainedIn3D(InArray, SearchValue);
/** \brief Searches a two dimensional array for a given one dimensional array.
* \param InArray array to search
* (assumed to be two dimensional (2-D))
* \param SearchValue array to search for
* (assumed to be one dimensional (1-D))
* \return `true` if found at least once, `false` if not. `null` if
* `InArray` is `null`.
* \see ContainedIn1D()
* \todo Add error check that arrays are provided
* \static
*/
function ContainedIn1DIn2D(InArray2D, SearchArray1D);
/** \brief Searches an array for a given value.
* \param InArray array to search
* (assumed to be two dimensional (2-D))
* \param SearchArray array to search for
* (assumed to be one dimensional (1-D))
* \return array index of the first time `SearchValue` is found (as an
* integer), `false` if not. `null` if `InArray` is `null`.
* \see ContainedIn1D()
* \see Find2D()
* \see Find3D()
* \see FindPairs()
* \note using this to see if an given array is an element of the parent
* array does not seem to be returning expected results. Use
* FindPairs() instead.
* \todo Add error check that an array is provided
* \static
*/
function Find1D(InArray, SearchValue);
/** \brief Searches an array for a given value.
* \param InArray array to search
* (assumed to be two dimensional (2-D))
* \param SearchValue what is searched for
* \return array index of the first time `SearchValue` is found (as an
* two dimensional array, of the from `[i, j]`), `false` if not.
* `null` if `InArray` is `null`.
* \see ContainedIn2D()
* \see Find1D()
* \see Find3D()
* \todo Add error check that an array is provided
* \static
*/
function Find2D(InArray, SearchValue);
/** \brief Searches an array for a given value.
* \param InArray array to search
* (assumed to be three dimensional (3-D))
* \param SearchValue what is searched for
* \return array index of the first time `SearchValue` is found (as an
* three dimensional array, of the from `[i, j, k]`), `false` if
* not. `null` if `InArray` is `null`.
* \see ContainedIn3D()
* \see Find1D()
* \see Find2D()
* \todo Add error check that an array is provided
* \static
*/
function Find3D(InArray, SearchValue);
/** \brief Removes an element from the array.
*
* Removes the value at the index, and shifts the rest of the array to the
* left. The returned array is thus one shorter than the supplied array.
* \param InArray the array to remove the element from
* \param Index the index of the element to remove
* \return `InArray` sans the element at `Index`, the elements beyond it
* are shifted to the left.
* \todo Add error check that an array is provided
* \static
*/
function RemoveValueAt(InArray, Index);
/** \brief Adds an element from the array.
*
* Adds `Value` to the `InArray` at the given `Index`. The rest of the
* array is shifted one place to the right. The returned array is thus one
* longer than `InArray`.
* \param InArray the array to add the element to
* \param Index the index of where to add `Value` at
* \param Value the element to add
* \return `InArray`, now with the element `Value` at `Index`, the elements
* beyond it shifted to the right.
* \todo Add error check that an array is provided
* \todo Add error check that `Index` is reasonable
* \static
*/
function InsertValueAt(InArray, Index, Value);
/** \brief Converts a one dimensional array of tiles to a nice string format.
*
* This function was created to aid in the output of arrays of tiles to the
* AI debug screen.
* \param InArrayOfTiles one dimensional (1-D) array of Tiles
* \param ArrayLength (`true` or `false`) whether to print the prefix
* noting the length of the array. Default is `false`.
* \return string version of array. e.g. `The array is 3 long. 12,45
* 62,52 59,10`.
* \return `null` if `InArrayOfTiles` is `null`.
* \see ToString1D()
* \see ToStringTiles2D()
* \todo Add error check that an array is provided
* \todo Add a better error message if you try and feed it not a 1-D array
* \static
*/
function ToStringTiles1D(InArrayOfTiles, ArrayLength = false);
/** \brief Converts a one dimensional array of tiles to a nice string format.
*
* This function was created to aid in the output of arrays of tiles to the
* AI debug screen.
* \param InArrayOfTiles two dimensional (2-D) array of Tiles
* \param ArrayLength (`true` or `false`) whether to print the prefix
* noting the length of the array. Default is
* `false`.
* \return string version of array. e.g. `The array is 2 long. 12,45
* 62,52 / 59,10 5,37`.
* \return `null` if `InArrayOfTiles` is `null`.
* \see ToString2D()
* \see ToStringTiles1D()
* \todo Add error check that an array is provided
* \todo Add a better error message if you try and feed it not a 2-D array
* \static
*/
function ToStringTiles2D(InArrayOfTiles, ArrayLength = false);
/** \brief Searches an array for a given pair of values.
*
* The idea is to provide an array of arrays of pairs (e.g. tile x and tile
* y, starting and ending points, etc.), and find out if `SearchValue1` and
* `SearchValue2` are among the pairs. The order that `SearchValue1` and
* `SearchValue2` is not considered.
* \param InArray2D two dimensional (2-D) array
* \param SearchValue1 values to search for
* \param SearchValue2 values to search for
* \return index (as an integer) of the array matching the search values.
* `null` if `InArray2D` is `null`.
* \see ContainedInPairs()
* \todo Add error check that a 2D array is provided
* \static
*/
function FindPairs(InArray2D, SearchValue1, SearchValue2);
/** \brief Searches an array for a given pair of values.
*
* The idea is to provide an array of arrays of pairs (e.g. tile x and tile
* y, starting and ending points, etc.), and find out if `SearchValue1` and
* `SearchValue2` are among the pairs. The order that `SearchValue1` and
* `SearchValue2` are in is not considered.
* \param InArray2D two dimensional (2-D) array
* \param SearchValue1 value to search for
* \param SearchValue2 value to search for
* \return `true` if the search values are found at least once, `false`
* otherwise. `null` if `InArray2D` is `null`.
* \see FindInPairs()
* \todo Add error check that a 2D array is provided
* \static
*/
function ContainedInPairs(InArray2D, SearchValue1, SearchValue2);
/** \brief Compares the two arrays item for item.
*
* Returns true if every item pair matches.
* \param InArray1D one dimensional (1-D) array, that is considered
* 'known'
* \param TestArray1D one dimensional (1-D) array, that is considered
* 'unknown'
* \return `true` if the `InArray1D` and `TestArray1D` equal each other for
* the comparison of each pair of elements. `false` otherwise.
* \note I wrote this because I don't trust `InArray == TestArray` to
* work this way...
* \static
*/
function Compare1D(InArray1D, TestArray1D);
/** \brief Appends one array to another.
* \param Array1 the first array
* \param Array2 the second array
* \return An array that the items has `Array2` appended to the end of the
* items of `Array1`
* \static
* \note Consider using Squirrel's built-in function:
* `MyArray.append(Item)` to append individual items to an array
*/
function Append(Array1, Array2);
/** \brief Removes duplicates from an array.
*
* The item is maintain at its first location and removed at all subsequent
* locations.
* \param Array array to remove duplicates from
* \return An array minus the duplicate items.
* \todo Add error check that an array is provided.
* \static
*/
function RemoveDuplicates(Array);
/** \brief Turns an Array in an AIList
* \return An AIList with the contents of the Array
* \todo Add error check that an array is provided.
* \static
*/
function ToAIList(Array);
};
// == Function definitions ==================================================
function _MinchinWeb_Array_::Create2D(length, width) {
local ReturnArray = array(length);
local tempArray = array(width);
for (local i=0; i < length; i++) {
ReturnArray[i] = array(width);
}
return ReturnArray;
}
function _MinchinWeb_Array_::Create3D(length, width, height) {
local ReturnArray = array(length);
for (local i=0; i < length; i++) {
ReturnArray[i] = array(width)
for (local j=0; j < width; j++) {
ReturnArray[i][j] = array(height);
}
}
return ReturnArray;
}
function _MinchinWeb_Array_::ToString1D(InArray, DisplayLength = true, replaceNull = false) {
if (InArray == null) {
return null;
} else {
local Length = InArray.len();
local i = 0;
local Temp = "";
while (i < InArray.len() ) {
if ((replaceNull == true) && (InArray[i] == null)) {
Temp = Temp + "-" + " ";
} else {
Temp = Temp + InArray[i] + " ";
}
i++;
}
if (DisplayLength == true) {
Temp = "The array is " + Length + " long. " + Temp;
}
return (Temp);
}
}
function _MinchinWeb_Array_::ToString2D(InArray, DisplayLength = true) {
if (InArray == null) {
return null;
} else {
local Length = InArray.len();
local i = 0;
local Temp = "";
while (i < InArray.len() ) {
local InnerArray = [];
InnerArray = InArray[i];
local j = 0;
while (j < InnerArray.len() ) {
Temp = Temp + InnerArray[j] + " ";
j++;
}
Temp = Temp + "/ ";
i++;
}
// get rid of last slash
if (Temp.len() > 3) {
Temp = Temp.slice(0, Temp.len() - 3);
}
if (DisplayLength == true) {
Temp = "The array is " + Length + " long. " + Temp;
}
return (Temp);
}
}
function _MinchinWeb_Array_::ContainedIn1D(InArray, SearchValue) {
if (InArray == null) {
return null;
} else {
for (local i = 0; i < InArray.len(); i++ ) {
if (InArray[i] == SearchValue) {
return true;
}
}
return false;
}
}
function _MinchinWeb_Array_::ContainedIn2D(InArray, SearchValue) {
if (InArray == null) {
return null;
} else {
for (local i = 0; i < InArray.len(); i++ ) {
for (local j=0; j < InArray[i].len(); j++ ) {
if (InArray[i][j] == SearchValue) {
return true;
}
}
}
return false;
}
}
function _MinchinWeb_Array_::ContainedIn3D(InArray, SearchValue) {
if (InArray == null) {
return null;
} else {
for (local i = 0; i < InArray.len(); i++ ) {
for (local j=0; j < InArray[i].len(); j++ ) {
for (local k=0; k < InArray[i].len(); k++)
if (InArray[i][j][k] == SearchValue) {
return true;
}
}
}
return false;
}
}
function _MinchinWeb_Array_::ContainedIn1DIn2D(InArray2D, SearchArray1D) {
if (InArray2D == null) {
return null;
} else {
for (local i = 0; i < InArray2D.len(); i++ ) {
if (_MinchinWeb_Array_.Compare1D(InArray2D[i], SearchArray1D) == true) {
return true;
}
}
return false;
}
}
function _MinchinWeb_Array_::Find1D(InArray, SearchValue) {
if (InArray == null) {
return null;
} else {
for (local i = 0; i < InArray.len(); i++ ) {
if (InArray[i] == SearchValue) {
return i;
}
}
return false;
}
}
function _MinchinWeb_Array_::Find2D(InArray, SearchValue) {
if (InArray == null) {
return null;
} else {
for (local i = 0; i < InArray.len(); i++ ) {
for (local j=0; j < InArray[i].len(); j++ ) {
if (InArray[i][j] == SearchValue) {
return [i, j];
}
}
}
return false;
}
}
function _MinchinWeb_Array_::Find3D(InArray, SearchValue) {
if (InArray == null) {
return null;
} else {
for (local i = 0; i < InArray.len(); i++ ) {
for (local j=0; j < InArray[i].len(); j++ ) {
for (local k=0; k < InArray[i].len(); k++)
if (InArray[i][j][k] == SearchValue) {
return [i,j,k];
}
}
}
return false;
}
}
function _MinchinWeb_Array_::RemoveValueAt(InArray, Index) {
local i = 0;
local Return = [];
for (i; i < Index; i++) {
Return.push(InArray[i]);
}
i++;
for (i; i < InArray.len(); i++) {
Return.push(InArray[i]);
}
return Return;
}
function _MinchinWeb_Array_::InsertValueAt(InArray, Index, Value) {
local i = 0;
local Return = [];
for (i; i < Index; i++) {
Return.push(InArray[i]);
}
Return.push(Value);
for (i; i < InArray.len(); i++) {
Return.push(InArray[i]);
}
return Return;
}
function _MinchinWeb_Array_::ToStringTiles1D(InArrayOfTiles, ArrayLength = false) {
if (InArrayOfTiles == null) {
return null;
} else {
local Length = InArrayOfTiles.len();
local Temp = "";
foreach (Tile in InArrayOfTiles) {
Temp = Temp + " " + AIMap.GetTileX(Tile) + "," + AIMap.GetTileY(Tile);
}
if (ArrayLength == true) {
Temp = "The array is " + Length + " long. " + Temp;
}
return Temp;
}
}
function _MinchinWeb_Array_::ToStringTiles2D(InArrayOfTiles, ArrayLength = false) {
if (InArrayOfTiles == null) {
return null;
} else {
local Length = InArrayOfTiles.len();
local i = 0;
local Temp = "";
while (i < InArrayOfTiles.len() ) {
local InnerArray = [];
InnerArray = InArrayOfTiles[i];
local j = 0;
while (j < InnerArray.len() ) {
Temp = Temp + AIMap.GetTileX(InnerArray[j]) + "," + AIMap.GetTileY(InnerArray[j]) + " ";
j++;
}
Temp = Temp + "/ ";
i++;
}
// get rid of last slash
if (Temp.len() > 3) {
Temp = Temp.slice(0, Temp.len() - 3);
}
if (ArrayLength == true) {
Temp = "The array is " + Length + " long. " + Temp;
}
return (Temp);
}
}
function _MinchinWeb_Array_::FindPairs(InArray2D, SearchValue1, SearchValue2) {
if (InArray2D == null) {
return null;
} else {
local Return1 = false;
local Return2 = false;
for (local i = 0; i < InArray2D.len(); i++ ) {
for (local j=0; j < InArray2D[i].len(); j++ ) {
if ((InArray2D[i][j] == SearchValue1) && !Return1) {
Return1 = true;
} else if (InArray2D[i][j] == SearchValue2) {
Return2 = true;
}
}
if (Return1 && Return2) {
return i;
} else {
Return1 = false;
Return2 = false;
}
}
return false;
}
}
function _MinchinWeb_Array_::ContainedInPairs(InArray2D, SearchValue1, SearchValue2) {
if (InArray2D == null) {
return null;
} else {
local Return1 = false;
local Return2 = false;
for (local i = 0; i < InArray2D.len(); i++ ) {
for (local j=0; j < InArray2D[i].len(); j++ ) {
if ((InArray2D[i][j] == SearchValue1) && !Return1) {
Return1 = true;
} else if (InArray2D[i][j] == SearchValue2) {
Return2 = true;
}
}
if (Return1 && Return2) {
return true;
} else {
Return1 = false;
Return2 = false;
}
}
return false;
}
}
function _MinchinWeb_Array_::Compare1D(InArray1D, TestArray1D) {
if (InArray1D.len() != TestArray1D.len() ) {
return false;
}
for (local i = 0; i < InArray1D.len(); i++) {
if (InArray1D[i] != TestArray1D[i]) {
return false;
}
}
return true;
}
function _MinchinWeb_Array_::Append(Array1, Array2) {
local ReturnArray = [];
for (local i=0; i < Array1.len(); i++) {
ReturnArray.push(Array1[i]);
}
for (local i=0; i < Array2.len(); i++) {
ReturnArray.push(Array2[i]);
}
return ReturnArray;
}
function _MinchinWeb_Array_::RemoveDuplicates(Array) {
local ReturnArray = Array;
for (local i=0; i < ReturnArray.len(); i++) {
for (local j=i+1; j < ReturnArray.len(); j++) {
if (ReturnArray[i] == ReturnArray[j]) {
ReturnArray = _MinchinWeb_Array_.RemoveValueAt(ReturnArray, j);
j--;
}
}
}
return ReturnArray;
}
function _MinchinWeb_Array_::ToAIList(Array) {
local list = AIList();
foreach (item in Array) {
list.AddItem(item, 0);
}
return list;
}
// EOF
MetaLib-7/Atlas.nut 0000666 0000000 0000000 00000035732 12304433725 012502 0 ustar 0000000 0000000 /* Atlas v.1 r.221 [2012-01-28],
* part of Minchinweb's MetaLibrary v.4,
* Copyright © 2011-14 by W. Minchin. For more info,
* please visit https://github.com/MinchinWeb/openttd-metalibrary
*
* Permission is granted to you to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell this software, and provide these
* rights to others, provided:
*
* + The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the software.
* + Attribution is provided in the normal place for recognition of 3rd party
* contributions.
* + You accept that this software is provided to you "as is", without warranty.
*/
/* Included functions:
Atlas()
Atlas.Reset()
- Resets the Atlas (dumps all entered data)
.AddSource(Source, Priority)
- Adds a source to the sources list with the given priority
- Assumes Source to be a TileIndex
.AddAttraction(Attraction, Priority)
- Adds an attraction to the attraction list with the given priority
- Assumes Source to be a TileIndex
.AddBoth(AddedTile, Priority)
- Adds a tile to the BOTH the sources list and the attractions
list with the (same) given priority
.RunModel()
- Takes the provided sources and destinations and runs the
selected traffic model, populating the 'pairs' heap
.Pop()
- Returns the top rated pair as an array and removes the pair from
the model
.Peek()
- Returns the top rated pair (as an array) but DOES NOT remove the
pair from the model
.Count()
- Returns the amount of items currently in the list.
.Exists
- Check if an item exists in the list. Returns true/false.
.SetModel(newmodel)
- Sets the model type to the provided type
.GetModel()
- Returns the current model type (as the enum)
.PrintModelType(ToPrint)
- given a ModelType, returns the string equivalent
.ApplyTrafficModel(StartTile, StartPriority, EndTile, EndPriority,
Model)
- Given the start and end points, applies the traffic model and
returns the weighting (Smaller weightings are considered better)
- This function is independent of the model/class, so is useful if
you want to apply the traffic model to a given set of points. It
is what is called internally to apply the model
.SetMaxDistance(distance = -1)
- Sets the maximum distance between sources and attractions to be
included in the model
- Negative values remove the limit
.SetMaxDistanceModel(newmodel)
- Sets the model type to the provided type
- Used to calculate the distance between the source and attraction
for applying maxdistance
- DISTANCE_NONE is invalid. Use MinchinWeb.Atlas.SetMaxDistance(-1)
instead.
- ONE_OVER_T_SQUARED is invalid.
*/
/** \brief Model Types used by \_MinchinWeb\_Atlas\_
*/
enum ModelType {
ONE_D, ///< One Dimensional - maximum of Δx and Δy
DISTANCE_MANHATTAN, ///< Manhattan Distance - sum of Δx and Δy
DISTANCE_SHIP, ///< Ship Distance - see \_MinchinWeb\_Marine\_.DistanceShip()
DISTANCE_AIR, ///< As the crow flies - √(Δx2 + Δy2)
DISTANCE_NONE, ///< No consideration of distance.
ONE_OVER_T_SQUARED, ///< 1 / (travel time)2
};
/** \brief Keep track of the world -- match sources and destinations.
* \version v.1 (2012-01-28)
* \author W. Minchin (%MinchinWeb)
* \since MetaLibrary v.2
*
* The Atlas takes sources (departs) and attractions (destinations) and then
* generates a heap of pairs sorted by rating. I use this class to decide
* which pairs of industries or towns to service.
*
* Ratings can be generated based on distance alone or can be altered by user
* defined ratings (e.g. industry productions or town populations).
*
* \requires Binary Heap v.1
* \requires ::ModelType
*/
class _MinchinWeb_Atlas_ {
/** \publicsection
*/
function GetVersion() { return 1; }
function GetRevision() { return 187; }
function GetDate() { return "2012-01-04"; }
function GetName() { return "Atlas Library"; }
/** \privatesection
*/
_heap_class = import("Queue.Binary_Heap", "", 1);
_sources = []; ///< 'From' here... (array)
_attractions = []; ///< 'To' here (array)
_pairs = null; ///< Heap of paired sources and attractions
_ignorepairs = []; ///< A list of pairs to ignore
_maxdistance = null; ///< This is the maximum distance between sources and attractions to include in the model
_maxdistancemodel = null; ///< This is how to measure distances between sources and attractions to determine weather they exceed "_maxdistance"
_model = null; ///< Stores models selected, see ::ModelType
constructor() {
this._pairs = this._heap_class();
this._model = ModelType.DISTANCE_MANHATTAN;
this._ignorepairs = [[-1,-1]];
this._maxdistance = -1;
this._maxdistancemodel = ModelType.DISTANCE_MANHATTAN;
this._model = 1;
}
/** \publicsection
* \brief Resets the Atlas.
*
* Dumps all entered data.
*/
function Reset();
/** \brief Adds a source to the sources list with the given priority.
* \param Source Assumed to be a TileIndex.
*/
function AddSource(Source, Priority) {
this._sources.push([Source, Priority]);
};
/** \brief Adds an attraction to the attraction list with the given
* priority.
* \param Attraction Assumed to be a TileIndex.
*/
function AddAttraction(Attraction, Priority) {
this._attractions.push([Attraction, Priority]);
};
/** \brief Adds to both sources and attractions.
*
* Adds a tile to the BOTH the sources list and the attractions list with
* the (same) given priority.
* \param AddedTile Assumed to be a TileIndex.
*/
function AddBoth(AddedTile, Priority);
/** \brief Runs the Atlas.
*
* Takes the provided sources and destinations and runs the selected
* traffic model, populating the 'pairs' heap.
*/
function RunModel();
/** \brief Pop the heap.
* \return The top rated pair as an array.
* \warning Removes the pair from the model.
* \see Peek()
*/
function Pop();
/** \brief Peek the heap.
* \return The top rated pair as an array.
* \note Leaves the pair in the model.
* \see Pop()
*/
function Peek() { return this._pairs.Peek(); };
/** \brief Number of pairs in the heap.
* \return Number of pairs in the heap.
*/
function Count() { return this._pairs.Count(); };
/** \brief Check if an item exists in the heap.
* \return `True` or `False`
*/
function Exists() { return this._pairs.Exists(); };
/** \brief Set the model type.
* \see ::ModelType
*/
function SetModel(newmodel);
/** \brief Returns the current model type.
* \return The current model type, as the enum.
* \see ::ModelType
*/
function GetModel() { return this._model; };
/** \brief The string equivalent of the ModelType
* \param ToPrint ModelType, as enum
* \return The string equivalent.
* \see ::ModelType
*/
function PrintModelType(ToPrint);
/** \brief Apply the Atlas model
*
* Given the start and end tiles, applies the traffic model and returns
* the weighting. Smaller weightings are considered better.
* \note This function is independent of the model/class (i.e. it is a
* static function), so is useful if you want to apply the traffic
* model to a given set of tiles. It is what is called internally
* to apply the model.
* \param StartTile Assumed to be a TileId
* \param StartPriority Priority (weighting) assigned to Start
* \param EndTile Assumed to be a TileId
* \param EndPriotity Priority (weighting) assigned to End
* \param Model Assumed to be one of the enum ::ModelType
* \static
*/
function ApplyTrafficModel(StartTile, StartPriority, EndTile, EndPriority, Model);
/** \brief Set the maximum distance the model will allow
* \param distance Sets the maximum distance between sources and
* attractions to be included in the model.
* Negative values remove the limit.
* \see SetMaxDistanceModel()
*/
function SetMaxDistance(distance = -1);
/** \brief Set the ::ModelType for determining max distance.
*
* Used to calculate the distance between the source and attraction for
* applying `maxdistance`.
* \param newmodel ::ModelType to use
* \note For `newmodel`, ::ModelType .DISTANCE_NONE is invalid. Use
* `SetMaxDistance(-1)` instead.
* \note For `newmodel`, ::ModelType .ONE_OVER_T_SQUARED is invalid.
* \see SetMaxDistance()
*/
function SetMaxDistanceModel(newmodel);
};
// == Function definition ==================================================
function _MinchinWeb_Atlas_::Reset() {
// Resets the Atlas
this._pairs = this._heap_class();
this._model = ModelType.DISTANCE_MANHATTAN;
this._ignorepairs = [[-1,-1]];
this._maxdistance = -1;
this._sources = [];
this._attractions = [];
this._maxdistancemodel = ModelType.DISTANCE_MANHATTAN;
return;
}
function _MinchinWeb_Atlas_::AddBoth(AddedTile, Priority) {
// Adds a tile to the BOTH the sources list and the attractions list with the (same) given priority
this._sources.push([AddedTile, Priority]);
this._attractions.push([AddedTile, Priority]);
}
function _MinchinWeb_Atlas_::RunModel() {
// Takes the provided sources and destinations and runs the selected traffic model, populating the 'pairs' heap
this._pairs = this._heap_class();
for (local i = 0; i < this._sources.len(); i++) {
_MinchinWeb_Log_.Note(" i = " + i, 7);
for (local j = 0; j < this._attractions.len(); j++) {
_MinchinWeb_Log_.Note(" j = " + j + " " + (_MinchinWeb_Array_.ContainedIn1D([this._sources[i][0], this._attractions[j][0]], this._ignorepairs) == false) + "; " + _MinchinWeb_Atlas_.ApplyTrafficModel(this._sources[i][0], 1, this._attractions[j][0], 0, this._maxdistancemodel) + " < " + this._maxdistance + " = " + ((_MinchinWeb_Atlas_.ApplyTrafficModel(this._sources[i][0], 1, this._attractions[j][0], 0, this._maxdistancemodel) < this._maxdistance) || (this._maxdistance < 0)) + " ;; " + _MinchinWeb_Array_.ToStringTiles1D([this._sources[i][0]]) + " - " + this._sources[i][1] + " " + _MinchinWeb_Array_.ToStringTiles1D([this._attractions[j][0]]) + " - " + this._attractions[j][1] + " -- " + this._model + " :: " + AIMap.DistanceManhattan(this._sources[i][0], this._attractions[j][0]) + " / (" + this._sources[i][1] + " + " + this._attractions[j][1] + ") = " + (AIMap.DistanceManhattan(this._sources[i][0], this._attractions[j][0]) / (this._sources[i][1].tofloat() + this._attractions[j][1].tofloat())) + " : " + _MinchinWeb_Atlas_.ApplyTrafficModel(this._sources[i][0], this._sources[i][1], this._attractions[j][0], this._attractions[j][1], this._model), 7);
if ((_MinchinWeb_Array_.ContainedIn1D([this._sources[i][0], this._attractions[j][0]], this._ignorepairs) == false)
&& ((_MinchinWeb_Atlas_.ApplyTrafficModel(this._sources[i][0], 1, this._attractions[j][0], 0, this._maxdistancemodel) < this._maxdistance)
|| (this._maxdistance < 0))) {
this._pairs.Insert([this._sources[i][0], this._attractions[j][0]],
_MinchinWeb_Atlas_.ApplyTrafficModel(this._sources[i][0], this._sources[i][1], this._attractions[j][0], this._attractions[j][1], this._model));
}
}
}
}
function _MinchinWeb_Atlas_::Pop() {
// Returns the top rated pair as an array and removes the pair from the model
// If the two tiles returned are equal, pop another one
local KeepTrying = true;
local Test;
while (KeepTrying == true) {
Test = this._pairs.Pop();
if ((Test == null) || (Test[0] != Test[1])) {
KeepTrying = false;
}
}
return Test;
}
function _MinchinWeb_Atlas_::SetModel(newmodel) {
// Sets the model type to the provided type
if ((newmodel == ModelType.ONE_D) || (newmodel == ModelType.DISTANCE_MANHATTAN) || (newmodel == ModelType.DISTANCE_SHIP) ||
(newmodel == ModelType.DISTANCE_AIR) || (newmodel == ModelType.DISTANCE_NONE) || (newmodel == ModelType.ONE_OVER_T_SQUARED)) {
this._model = newmodel;
} else {
AILog.Warning("MinchinWeb.Atlas.SetModel() was supplied with an invalid ModelType. Was supplied: " + newmodel ".");
}
}
function _MinchinWeb_Atlas_::PrintModelType(ToPrint) {
// given a ModelType, returns the string equivalent
switch (ToPrint) {
case ModelType.ONE_D :
return "1-D";
break;
case ModelType.DISTANCE_MANHATTAN :
return "Distance Manhattan";
break;
case ModelType.DISTANCE_SHIP :
return "Distance Ship";
break;
case ModelType.DISTANCE_AIR :
return "Distance Air";
break;
case ModelType.DISTANCE_NONE :
return "Distance Disregarded";
break;
case ModelType.ONE_OVER_T_SQUARED :
return "1/t^2";
break;
default:
return "ERROR: Bad ModelType. Supplied " + ToPrint;
break;
}
}
function _MinchinWeb_Atlas_::ApplyTrafficModel(StartTile, StartPriority, EndTile, EndPriority, Model) {
// Given the start and end points, applies the traffic model and returns the
// weighting
// Smaller weightings are considered better
switch (Model) {
case ModelType.ONE_D :
return AIMap.DistanceMax(StartTile, EndTile) / (StartPriority.tofloat() + EndPriority.tofloat());
break;
case ModelType.DISTANCE_MANHATTAN :
return AIMap.DistanceManhattan(StartTile, EndTile) / (StartPriority.tofloat() + EndPriority.tofloat());
break;
case ModelType.DISTANCE_SHIP :
return _MinchinWeb_Marine_.DistanceShip(StartTile, EndTile) / (StartPriority.tofloat() + EndPriority.tofloat());
break;
case ModelType.DISTANCE_AIR :
return AIMap.DistanceSquare(StartTile, EndTile) / (StartPriority.tofloat() + EndPriority.tofloat());
break;
case ModelType.DISTANCE_NONE :
return (1.0 / (StartPriority.tofloat() + EndPriority.tofloat()));
break;
case ModelType.ONE_OVER_T_SQUARED :
return ((AIMap.DistanceManhattan(StartTile, EndTile) * AIMap.DistanceManhattan(StartTile, EndTile)) / (StartPriority.tofloat() + EndPriority.tofloat()));
break;
default:
return "ERROR: Bad ModelType. Was supplied: " + Model;
break;
}
}
function _MinchinWeb_Atlas_::SetMaxDistance(distance = -1) {
// Sets the maximum distance between sources and attractions to be included in the model
// Negative values remove the limit
if (distance < 0) {
this._maxdistance = -1;
} else {
this._maxdistance = distance;
}
}
function _MinchinWeb_Atlas_::SetMaxDistanceModel(newmodel) {
// Sets the model type to the provided type
// Used to calculate the distance between the source and attraction for applying maxdistance
// DISTANCE_NONE is invalid. Use MinchinWeb.Atlas.SetMaxDistance(-1) instead.
// ONE_OVER_T_SQUARED is invalid.
if ((newmodel == ModelType.ONE_D) || (newmodel == ModelType.DISTANCE_MANHATTAN) || (newmodel == ModelType.DISTANCE_SHIP) ||
(newmodel == ModelType.DISTANCE_AIR)) {
this._maxdistancemodel = newmodel;
} else {
AILog.Warning("MinchinWeb.Atlas.SetMaxDistanceModel() was supplied with an invalide ModelType. Was supplied: " + newmodel ".");
}
}
// EOF
MetaLib-7/changelog.txt 0000666 0000000 0000000 00000006752 12305212737 013375 0 ustar 0000000 0000000 Changelog
===============================================================================
Version 7 {#v7}
===============================================================================
Released 2014-02-28
- Added Lakes as a replacement for WaterBodyCheck
- Ship Pathfinder now uses Lakes rather than WaterBodyCheck
- Ship Pathfinder now makes sure every point is in the same waterbody before
adding it to the path
- WaterBodyCheck is now deprecated in favour of Lakes
- Documentation for MetaLibrary is now online at
[Minchin.ca](http://minchin.ca/openttd-metalibrary)
- Fix array creation bugs in Array.Create2D(), Array.Create3D()
- Added Array.RemoveDuplicates(Array)
- Added Array.ToAIList(Array)
- Added Extras.MinDistance(TileID, TargetArray); can be used as a valuator
- Split Constants from Extras (file only, function access remains the same)
- Split Industry from Extras (file only, function access remains the same)
- Split Station from Extras (file only, function access remains the same)
- Bumped maximum Log `Debug_Level` to 8
- Added separate Changelog file
- Rename `Readme.txt` to `Readme.md`
- Update requirement to Fibonacci Heap, v.3
Version 6 {#v6}
===============================================================================
Released 2012-12-31
- Added Dominion Land System (DLS) which allows for grid based pathfinding
- Update license statement
- Moved source code to
[GitHub](https://github.com/MinchinWeb/openttd-metalibrary/) and
updated URL's
- Road Pathfinder no longer chokes if a bridge doesn't have a parent path
Version 5 {#v5}
===============================================================================
Released 2012-06-27
- Added MinchinWeb.Station.IsNextToDock(TileID)
- Added MinchinWeb.Marine.RankShips(EngineID, Life, Cargo)
- Added MinchinWeb.Marine.NearestDepot(TileID)
- Ship depot builder no longer will build the depot next to a dock
Version 4 {#v4}
===============================================================================
Released 2012-01-30
- Added Log
- Bug fix to Spiral Walker
Version 3 {#v3}
===============================================================================
Released 2012-01-14
- Minor update; released to coincide with the release of WmDOT v8
- Bug fixes and improvements to the Ship and Road Pathfinder
- Road Pathfinder can now bridge over canals, rivers, and railroads
Version 2 {#v2}
===============================================================================
Released 2012-01-11
- Major update; released to coincide with the release of WmDOT v7
- Added the Ship Pathfinder (v2), Line Walker (v1), and Atlas (v1) classes
- Added Constants, Station, Industry, and Marine (v1) class functions
- Updated Extras (v.2) and Arrays (v.3)
Version 1 {#v1}
===============================================================================
Released 2011-04-28
- Initial public release; released to coincide with the release of WmDOT v6
- Included Arrays v2, Extras v1, Road Pathfinder v7, Spiral Walker v2, and
Waterbody Check v1
MetaLib-7/Constants.nut 0000666 0000000 0000000 00000010753 12304266533 013407 0 ustar 0000000 0000000 /* Constants for MetaLibrary v.1 r.206 [2012-01-12],
* part of Minchinweb's MetaLibrary v.6,
* originally part of MetaLibrary v.2
* Copyright © 2011-14 by W. Minchin. For more info,
* please visit https://github.com/MinchinWeb/openttd-metalibrary
*
* Permission is granted to you to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell this software, and provide these
* rights to others, provided:
*
* + The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the software.
* + Attribution is provided in the normal place for recognition of 3rd party
* contributions.
* + You accept that this software is provided to you "as is", without warranty.
*/
/* Functions provided:
* MinchinWeb.Constants.Infinity() - returns 10,000
* .FloatOffset() - returns 1/2000
* .Pi() - returns 3.1415...
* .e() - returns 2.7182...
* .IndustrySize() - returns 4
* .InvalidIndustry() - returns 0xFFFF (65535)
* .InvalidTile() - returns 0xFFFFFF
* .MaxStationSpread() - returns the maximum station spread
* .BuoyOffset() - returns 3
* .WaterDepotOffset() - return 4
*
*/
/** \class _MinchinWeb_C_
* \brief Constants
* \version v.1
* \author W. Minchin (%MinchinWeb)
* \since MetaLibrary v.2
*
* In general, these are constants used by other sub-libraries within
* MetaLibrary.
*/
class _MinchinWeb_C_ {
// These are constants called by the various sublibraries
/** \publicsection
* \brief A number close enough to infinity to work for our purposes here.
* \return 10,000
* \note Slopes are capped at 10,000 and 1/10,000
* \note A tile that is a Manhattan Distance of 'infinity' tiles away
* will always be outside of the defined map.
* \static
*/
function Infinity() { return 10000; }
/** \brief Used to compare floating point numbers to determine if they are
* "equal".
*
* Two floating point numbers (i.e. numbers with decimal points)
* are considered to be equal if they differ by less than this
* value.
* \note Floating points, due to the imprecision is translating binary
* numbers (as they are stored by the computer) to decimal numbers,
* and then performing math with these imperfectly translated
* numbers, can result in numbers than are otherwise equal, except
* for very small remainders. This is an attempt to sidestep this
* issue.
* \return 0.000,5 (or 1/2,000)
* \todo Convert from an absolute number to a percentage.
* \static
*/
function FloatOffset() { return 0.0005; } // = 1/2000
/** \brief Pi (π = 3.14...) to 31 decimal places
* \static
*/
function Pi() { return 3.1415926535897932384626433832795; }
/** \brief Euler's number (*e* = 2.718...) to 31 decimal places
* \static
*/
function e() { return 2.7182818284590452353602874713527; }
/** \brief Industries are assumed to fit within a 4x4 box
* \return 4
* \static
*/
function IndustrySize() { return 4; }
/** \brief Number returned by OpenTTD for an invalid industry (65535)
* \return 0xFFFF
* \static
*/
function InvalidIndustry() { return 0xFFFF; }
/** \brief A number beyond the a valid TileIndex.
*
* Valid (or invalid, if you prefer) for at least up to 2048x2048 maps.
* \return 0xFFFFFF
* \todo Check that this is still valid on 4096x4096 maps.
* \static
*/
function InvalidTile() { return 0xFFFFFF; }
/** \brief This is the assumed minimum desired spacing between buoys.
* \return 3
* \static
*/
function BuoyOffset() { return 3; }
/** \brief This is the maximum desired spacing between docks and depots.
* \return 4
* \static
*/
function WaterDepotOffset() { return 4; }
/** \brief Returns the OpenTTD setting for maximum station spread
* \static
*/
function MaxStationSpread();
};
// == Function definitions =================================================
function _MinchinWeb_C_::MaxStationSpread() {
if (AIGameSettings.IsValid("station_spread")) {
return AIGameSettings.GetValue("station_spread");
} else {
try {
AILog.Error("'station_spread' is no longer valid! (MinchinWeb.Constants.MaxStationSpread(), v." + this.GetVersion() + " r." + this.GetRevision() + ")");
AILog.Error("Please report this problem to http://www.tt-forums.net/viewtopic.php?f=65&t=57903");
} catch (idx) {
// pass
}
return 16;
}
}
// EOF
MetaLib-7/Dominion.Roads.nut 0000666 0000000 0000000 00000055065 12304267132 014257 0 ustar 0000000 0000000 /* Dominion Land System Roads v.1.1 [2013-01-01],
* part of Minchinweb's MetaLibrary v.6,
* Copyright © 2012-14 by W. Minchin. For more info,
* please visit https://github.com/MinchinWeb/openttd-metalibrary
*
* Permission is granted to you to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell this software, and provide these
* rights to others, provided:
*
* + The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the software.
* + Attribution is provided in the normal place for recognition of 3rd party
* contributions.
* + You accept that this software is provided to you "as is", without warranty.
*/
/** \brief Dominion Land System (Road Pathfinder)
* \version v.1 (2013-01-01)
* \author W. Minchin (%MinchinWeb)
* \since MetaLibrary v.6
*
* *Dominion Land System* refers to the system of survey in Western Canada.
* Land was surveyed into 1/2 mile x 1/2 mile "quarter sections" that would be
* sold to settlers (around 1905, the price was $10). Roads were placed on a
* 1 mile x 2 mile grid along the edges of these quarter sections.
*
* Here, we follow the same idea, although on a square grid. The default grid
* is 8x8 tiles. This is designed to run as a wrapper on the main road
* pathfinder.
*
* \requires *Pathfinder.Road.nut*
* \see \_MinchinWeb\_RoadPathfinder\_
*/
/* This file provides the following functions
MinchinWeb.DLS()
.DLS.Info.GetVersion()
.GetRevision()
.GetDate()
.GetName()
.DLS.SetDatum()
.GetDatum()
.IsGridPoint(Point)
.GridPoints(End1, End2)
.AllGridPoints()
.FindPath(cycles=10000)
.InitializePath(StartArray, EndArray)
.BuildPath()
.InitializePathOnTowns(StartTown, EndTown)
.GetPath()
.GetPathLength()
.PathToTilePairs()
.PathToTiles()
.TilePairsToBuild()
.GetBuildCost()
.PresetOriginal()
.PresetPerfectPath()
.PresetQuickAndDirty()
.PresetMode6()
.PresetStreetcar()
*/
class _MinchinWeb_DLS_ {
_gridx = null; ///< Grid spacing in x direction (default is 8 tiles)
_gridy = null; ///< Grid spacing in y direction (default is 8 tiles)
_datum = null; ///< This is the 'centre' of our survey system
_basedatum = null; ///< this is the 'grid point' closest to 0,0
_pathfinder = null; ///< the pathfinder itself
_starttile = null; ///< starting tile
_endtile = null; ///< ending tile
_path = null; ///< used to store that path as a array of tile pairs
_running = null; ///< Is the pathfinder currently running?
_road_type = null; ///< See
constructor() {
this._gridx = 8;
this._gridy = 8;
this._datum = 0;
this._pathfinder = _MinchinWeb_RoadPathfinder_();
this._road_type = AIRoad.ROADTYPE_ROAD;
}
/** \publicsection
* \brief Sets network Datum.
*
* Used to set the datum for our road system.
* \note In surveying, a 'datum' is where all other points are measured
* from.
* \param NewDatum assumed to be a TileIndex
* \todo Add error check
*/
function SetDatum(NewDatum);
/** \brief Returns the currently set Datum
* \return The current Datum (as a TileIndex)
* \note In surveying, a 'datum' is where all other points are measured
* from.
*/
function GetDatum() { return this._datum; }
/** \brief Is the tile given a grid point
* \return `True` if and only if `Point` is a gird point;
* `False` otherwise.
*/
function IsGridPoint(Point);
/** \brief Get all the grid points between two tiles
* \param End1 expected to be a TileIndex
* \param End2 expected to be a TileIndex
* \return An array of TileIndexs that are 'grid points' or where roads
* will have intersections.
* \note `End1` and `End2` will **NOT** be included in the return array.
*/
function GridPoints(End1, End2);
/** \brief Get all grid points
* \return An array of all the 'grid points' on the map
*/
function AllGridPoints();
/** \brief Run the pathfinder.
* \param cycles number of iterations to run before returning.
* \return `True` when the path is found
* \see BuildPath()
* \see GetPath()
* \note The path must be initialized before it can be run.
* \see InitializePath()
* \see InitializePathOnTowns()
* \todo stop ignoring the passed parameter of `cycles`
*/
function FindPath(cycles = 10000);
/** \brief Initializes the pathfinder
* \param StartArray the first item assumed be a TileIndex, the of the
* items in the array are ignored.
* \param EndArray the first item assumed be a TileIndex, the of the
* items in the array are ignored.
*/
function InitializePath(StartArray, EndArray);
/** \brief Build the path
* \note requires that the path has already been found.
* \see FindPath()
*/
function BuildPath();
/** \brief Initializes the pathfinder using two towns.
* \param StartTown Assumed to be a TownID
* \param EndTown Assumed to be a TownID
* \note This assumes that the town centres are road tiles (if this is
* not the case, the pathfinder will still run, but it will take a
* long time and eventually fail to return a path). This is not
* typically an issue because on map generation, the centre of each
* town is a road tile.
*/
function InitializePathOnTowns(StartTown, EndTown);
/** \brief Returns the path stored by the pathfinder
*/
function GetPath();
/** \brief Returns the length of the path stored by the pathfinder.
* \return The lenght of the path in tiles.
*/
function GetPathLength();
/** \brief Convert the path to tile pairs.
* \return A 2D array that has each pair of tiles that the path joins.
* \see TileParisToBuild()
*/
function PathToTilePairs();
/** \brief Get all the tiles in the path.
* \return A 1D array of the tiles in the path
*/
function PathToTiles() { return this._path; }
/** \brief Get the road tile pairs that need built.
*
* Similar to PathToTilePairs(), but only returns those pairs where there
* isn't a current road connection.
* \see PathToTilePairs()
* \return A 2D array that has each pair of tiles that the path joins that
* are not current joined by road.
*/
function TilePairsToBuild();
/** \brief Determine how much it will cost to build the path.
*
* Turns to 'test mode,' builds the route provided, and returns the cost.
* \return The build cost, in British Pounds.
* \return `False` if the test build fails somewhere.
* \note Note that due to inflation, this value can get stale.
*/
function GetBuildCost();
/** Pass-thru functions to RoadPathfinder
* \see \_MinchinWeb\_RoadPathfinder\_.PresetOriginal()
*/
function PresetOriginal() {
return this._pathfinder.PresetOriginal();
}
/** Pass-thru functions to RoadPathfinder
* \see \_MinchinWeb\_RoadPathfinder\_.PresetPerfectPath()
*/
function PresetPerfectPath() {
return this._pathfinder.PresetPerfectPath();
}
/** Pass-thru functions to RoadPathfinder
* \see \_MinchinWeb\_RoadPathfinder\_.PresetQuickAndDirty()
*/
function PresetQuickAndDirty() {
return this._pathfinder.PresetQuickAndDirty();
}
/** Pass-thru functions to RoadPathfinder
* \see \_MinchinWeb\_RoadPathfinder\_.PresetCheckExisting()
*/
function PresetCheckExisting() {
return this._pathfinder.PresetCheckExisting()
}
/** Pass-thru functions to RoadPathfinder
* \see \_MinchinWeb\_RoadPathfinder\_.PresetMode6()
*/
function PresetPresetMode6() {
return this._pathfinder.PresetMode6();
}
/** Pass-thru functions to RoadPathfinder
* \see \_MinchinWeb\_RoadPathfinder\_.PresetStreetcar()
*/
function PresetPresetStreetcar() {
return this._pathfinder.PresetStreetcar();
}
};
class _MinchinWeb_DLS_.Info {
_main = null;
function GetVersion() { return 1; }
// function GetMinorVersion() { return 0; }
function GetRevision() { return 130101; }
function GetDate() { return "2013-01-01"; }
function GetName() { return "Dominion Land System Roads"; }
constructor(main) {
this._main = main;
}
};
function _MinchinWeb_DLS_::SetDatum(NewDatum) {
this._datum = NewDatum;
// _MinchinWeb_Log_.Note("Base Datum: x " + AIMap.GetTileX(this._datum) + "%" + this._gridx + "=" + AIMap.GetTileX(this._datum)%this._gridx + ", y:" + AIMap.GetTileY(this._datum) + "%" + this._gridy + "=" + AIMap.GetTileX(this._datum)%this._gridy, 6);
this._basedatum = AIMap.GetTileIndex(AIMap.GetTileX(this._datum)%this._gridx, AIMap.GetTileY(this._datum)%this._gridy);
_MinchinWeb_Log_.Note("Datum set to " + AIMap.GetTileX(this._datum) + ", " + AIMap.GetTileY(this._datum) + "; BaseDatum set to " + AIMap.GetTileX(this._basedatum) + ", " + AIMap.GetTileY(this._basedatum), 5);
}
function _MinchinWeb_DLS_::IsGridPoint(Point) {
// Returns 'true' iff Point is a grid point
if (((AIMap.GetTileX(Point) - AIMap.GetTileX(this._datum)) % this._gridx == 0) && ((AIMap.GetTileY(Point) - AIMap.GetTileY(this._datum)) % this._gridy == 0)) {
return true;
} else {
return false;
}
}
function _MinchinWeb_DLS_::GridPoints(End1, End2) {
// ## GridPoints
// Returns an array of TileIndexs that are 'grid points' or where roads will
// have intersections.
// *End1* and *End2* are expected to be TileIndex'es
// End1 and End2 will not be included in the return array
local x1 = AIMap.GetTileX(End1);
local y1 = AIMap.GetTileY(End1);
local x2 = AIMap.GetTileX(End2);
local y2 = AIMap.GetTileY(End2);
if (x1 > x2) {
local tempx = x1;
x1 = x2;
x2 = tempx;
}
if (y1 > y2) {
local tempy = y1;
y1 = y2;
y2 = tempy;
}
_MinchinWeb_Log_.Note("Generating grid points from " + x1 + ", " + y1 + " to " + x2 + ", " + y2, 6);
// move to first grid x
local workingx = x1;
while ((workingx - AIMap.GetTileX(this._datum)) % this._gridx != 0) {
workingx++;
}
// move to first grid y
local workingy = y1;
while ((workingy - AIMap.GetTileY(this._datum)) % this._gridy != 0) {
workingy++;
}
// Cycle through all the grid points and add them to our array
// use *do..while* to ensure we get at least one set of grid point per
// direction
local MyArray = [];
local starty = workingy;
do {
do {
local Holding = AIMap.GetTileIndex(workingx, workingy);
if ((Holding != End1) && (Holding != End2)) {
MyArray.push(Holding);
_MinchinWeb_Log_.Note("Add grid point at " + workingx + ", " + workingy, 7)
}
workingy = workingy + this._gridy;
} while (workingy < y2);
workingx = workingx + this._gridx;
workingy = starty;
} while (workingx < x2);
_MinchinWeb_Log_.Note("Generated " + MyArray.len() + " grid points.", 6);
return MyArray;
}
function _MinchinWeb_DLS_::AllGridPoints() {
// Returns an array of all the 'grid points' on the map
return _MinchinWeb_DLS_.GridPoints(AIMap.GetTileIndex(1,1), AIMap.GetTileIndex(AIMap.GetMapSizeX() - 2, AIMap.GetMapSizeY() - 2));
}
function _MinchinWeb_DLS_::FindPath(cycles = 10000) {
// runs the pathfinder
// add all grid points between the StartTile and EndTile as intermediate end points
// if the pathfinder ends on an intermediate point, make that the new start point and run the pathfinder again
local AllTiles = []; // we use this to return an array of tiles
local LastTile = this._starttile;
cycles = 10000;
local StartArray = [];
local EndArray = [];
AllTiles.push(LastTile); // this adds our starting square to the array
local WhileLoopCounter = 0;
_MinchinWeb_Log_.Note("+ while loop before: LastTile " + _MinchinWeb_Array_.ToStringTiles1D([LastTile]), 7);
do {
_MinchinWeb_Log_.Note("++ while loop start: LastTile " + _MinchinWeb_Array_.ToStringTiles1D([LastTile]), 7);
StartArray = [LastTile];
EndArray = _MinchinWeb_DLS_.GridPoints(LastTile, this._endtile);
EndArray.push(this._endtile);
_MinchinWeb_Log_.Note("StartArray: " + _MinchinWeb_Array_.ToStringTiles1D(StartArray), 7);
_MinchinWeb_Log_.Note("EndArray: " + _MinchinWeb_Array_.ToStringTiles1D(EndArray), 7);
this._pathfinder = null; // dump old pathfinder
this._pathfinder = _MinchinWeb_RoadPathfinder_();
this._pathfinder.PresetQuickAndDirty();
this._pathfinder.InitializePath(EndArray, StartArray);
local Ret = this._pathfinder.FindPath(cycles);
this._running = (Ret == false) ? true : false;
_MinchinWeb_Log_.Note("AllTiles (before) : " + _MinchinWeb_Array_.ToStringTiles1D(AllTiles, true), 7);
local TilestoAdd = this._pathfinder.PathToTiles();
if ((typeof (TilestoAdd) == "array") && (TilestoAdd.len() > 0)) {
TilestoAdd.remove(0); // remove the first tile so we don't get duplicates
_MinchinWeb_Log_.Note("Path to Tiles: " + _MinchinWeb_Array_.ToStringTiles1D(TilestoAdd, true), 7);
AllTiles.extend(TilestoAdd);
}
_MinchinWeb_Log_.Note("AllTiles (1D): " + _MinchinWeb_Array_.ToStringTiles1D(AllTiles, true), 6);
LastTile = AllTiles.top();
_MinchinWeb_Log_.Note("+++ while loop: " + (LastTile != this._endtile) + " && " + (this._running == true) + " = " + ((LastTile != this._endtile) && (this._running == true)) + " ; LastTile " + _MinchinWeb_Array_.ToStringTiles1D([LastTile]), 7);
_MinchinWeb_Log_.Note("LastTile " + _MinchinWeb_Array_.ToStringTiles1D([LastTile]), 5);
WhileLoopCounter++;
// } while ((LastTile != this._endtile) && (this._running == true));
} while (LastTile != this._endtile)
if (LastTile == this._endtile) {
this._path = AllTiles;
return true;
} else {
// I don't think we should ever get here...
// I guess it might get here if the pathfinder fails
return false;
}
}
function _MinchinWeb_DLS_::InitializePath(StartArray, EndArray) {
// Assumed only the first tile of the start and end array are the ones we care about
this._starttile = StartArray[0];
this._endtile = EndArray[0];
this._path = null;
}
// Reimplement Pathfinder Functions
function _MinchinWeb_DLS_::BuildPath()
{
if (this._running) {
AILog.Warning("You can't build a path while there's a running pathfinder.");
return false;
}
if (this._path == null) {
AILog.Warning("You have tried to build a 'null' path.");
return false;
}
local TestMode = AIExecMode(); // We're really doing this!
AIRoad.SetCurrentRoadType(this._road_type);
for (local i=0; i < this._path.len() - 2; i++) {
if (AIMap.DistanceManhattan(this._path[i], this._path[i+1]) == 1) {
// MD == 1 == road joining the two tiles
if (!AIRoad.BuildRoad(this._path[i], this._path[i+1])) {
// If we get here, then the road building has failed
// Possible that the road already exists
// TO-DO:
// - fail the road builder if the road cannot be built and
// does not already exist
// return null;
}
} else {
// Implies that we're building either a tunnel or a bridge
if (!AIBridge.IsBridgeTile(this._path[i]) && !AITunnel.IsTunnelTile(this._path[i])) {
if (AIRoad.IsRoadTile(this._path[i])) {
// Original example demolishes tile if it's already a road
// tile to get around expanded roadbits.
// I don't like this approach as it could destroy Railway
// tracks/tram tracks/station
// TO-DO:
// - figure out a way to do this while keeping the other
// things I've built on the tile
// (can I just remove the road?)
AITile.DemolishTile(this._path[i]);
}
if (AITunnel.GetOtherTunnelEnd(this._path[i]) == this._path[i+1]) {
// The assumption here is that the land hasn't changed
// from when the pathfinder was run and when we try to
// build the path. If the tunnel building fails, we
// get the 'can't build tunnel' message, but if the
// land has changed such that the tunnel end is at a
// different spot than is was when the pathfinder ran,
// we skip tunnel building and try and build a bridge
// instead, which will fail because the slopes are wrong...
if (!AITunnel.BuildTunnel(AIVehicle.VT_ROAD, this._path[i])) {
// At this point, an error has occurred while building the tunnel.
// Fail the pathfiner
// return null;
AILog.Warning("MinchinWeb.DLS.BuildPath can't build a tunnel from " + AIMap.GetTileX(this._path[i]) + "," + AIMap.GetTileY(this._path[i]) + " to " + AIMap.GetTileX(this._path[i+1]) + "," + AIMap.GetTileY(this._path[i+1]) + "!!" );
}
} else {
// if not a tunnel, we assume we're buildng a bridge
local BridgeList = AIBridgeList_Length(AIMap.DistanceManhattan(this._path[i], this._path[i+1] + 1));
BridgeList.Valuate(AIBridge.GetMaxSpeed);
BridgeList.Sort(AIList.SORT_BY_VALUE, false);
if (!AIBridge.BuildBridge(AIVehicle.VT_ROAD, BridgeList.Begin(), this._path[i], this._path[i+1])) {
// At this point, an error has occurred while building the bridge.
// Fail the pathfiner
// return null;
AILog.Warning("MinchinWeb.DLS.BuildPath can't build a bridge from " + AIMap.GetTileX(this._path[i]) + "," + AIMap.GetTileY(this._path[i]) + " to " + AIMap.GetTileX(this._path[i+1]) + "," + AIMap.GetTileY(this._path[i+1]) + "!! (or the tunnel end moved...)" );
}
}
}
}
}
// End build sequence
return true;
}
function _MinchinWeb_DLS_::InitializePathOnTowns(StartTown, EndTown)
{
// Initializes the pathfinder using two towns
// Assumes that the town centres are road tiles (if this is not the case, the
// pathfinder will still run, but it will take a long time and eventually
// fail to return a path)
return this.InitializePath([AITown.GetLocation(StartTown)], [AITown.GetLocation(EndTown)]);
}
function _MinchinWeb_DLS_::GetPath()
{
// Returns the path stored by the pathfinder
if (this._running) {
AILog.Warning("You can't get the path while there's a running pathfinder.");
return false;
}
return this._path;
}
function _MinchinWeb_DLS_::GetPathLength()
{
// Runs over the path to determine its length
if (this._running) {
AILog.Warning("You can't get the path length while there's a running pathfinder.");
return false;
}
if (this._path == null) {
AILog.Warning("You have tried to get the length of a 'null' path.");
return false;
}
local Length = 0;
for (local i=0; i < this._path.len() - 2; i++) {
Length = Length + AIMap.DistanceManhattan(this._path[i], this._path[i+1]);
}
return Length;
}
function _MinchinWeb_DLS_::PathToTilePairs()
{
// Returns a 2D array that has each pair of tiles that path joins
if (this._running) {
AILog.Warning("You can't convert a path while there's a running pathfinder.");
return false;
}
if (this._path == null) {
AILog.Warning("You have tried to convert a 'null' path.");
return false;
}
local TilePairs = [];
for (local i=0; i < this._path.len() - 2; i++) {
TilePairs.push([this._path[i], this._path[i+1]]);
}
// End build sequence
return TilePairs;
}
function _MinchinWeb_DLS_::TilePairsToBuild()
{
// Similar to PathToTilePairs(), but only returns those pairs where there
// isn't a current road connection
if (this._running) {
AILog.Warning("You can't convert a (partial) path while there's a running pathfinder.");
return false;
}
if (this._path == null) {
AILog.Warning("You have tried to convert a (partial) 'null' path.");
return false;
}
local TilePairs = [];
for (local i=0; i < this._path.len() - 2; i++) {
TilePairs.push([this._path[i], this._path[i+1]]);
}
// End build sequence
return TilePairs;
}
function _MinchinWeb_DLS_::GetBuildCost()
{
// Turns to 'test mode,' builds the route provided, and returns the cost (all
// money for AI's is in British Pounds)
// Note that due to inflation, this value can get stale
// Returns false if the test build fails somewhere
if (this._running) {
AILog.Warning("You can't find the build costs while there's a running pathfinder.");
return false;
}
if (this._path == null) {
AILog.Warning("You have tried to get the build costs of a 'null' path.");
return false;
}
local BeanCounter = AIAccounting();
local TestMode = AITestMode();
local Path = this._path;
AIRoad.SetCurrentRoadType(this._road_type);
for (local i=0; i < this._path.len() - 2; i++) {
if (AIMap.DistanceManhattan(this._path[i], this._path[i+1]) == 1) {
// MD == 1 == road joining the two tiles
if (!AIRoad.BuildRoad(this._path[i], this._path[i+1])) {
// If we get here, then the road building has failed
// Possible that the road already exists
// TO-DO
// - fail the road builder if the road cannot be built and
// does not already exist
// return null;
}
} else {
// Implies that we're building either a tunnel or a bridge
if (!AIBridge.IsBridgeTile(this._path[i]) && !AITunnel.IsTunnelTile(this._path[i])) {
if (AIRoad.IsRoadTile(this._path[i])) {
// Original example demolishes tile if it's already a road
// tile to get around expanded roadbits.
// I don't like this approach as it could destroy Railway
// tracks/tram tracks/station
// TO-DO
// - figure out a way to do this while keeping the other
// things I've built on the tile
// (can I just remove the road?)
AITile.DemolishTile(this._path[i]);
}
if (AITunnel.GetOtherTunnelEnd(this._path[i]) == this._path[i+1]) {
if (!AITunnel.BuildTunnel(AIVehicle.VT_ROAD, this._path[i])) {
// At this point, an error has occurred while building the tunnel.
// Fail the pathfiner
// return null;
AILog.Warning("MinchinWeb.DLS.GetBuildCost can't build a tunnel from " + AIMap.GetTileX(this._path[i]) + "," + AIMap.GetTileY(this._path[i]) + " to " + AIMap.GetTileX(this._path[i+1]) + "," + AIMap.GetTileY(this._path[i+1]) + "!!" );
}
} else {
// if not a tunnel, we assume we're building a bridge
local BridgeList = AIBridgeList_Length(AIMap.DistanceManhattan(this._path[i], this._path[i+1] + 1));
BridgeList.Valuate(AIBridge.GetMaxSpeed);
BridgeList.Sort(AIList.SORT_BY_VALUE, false);
if (!AIBridge.BuildBridge(AIVehicle.VT_ROAD, BridgeList.Begin(), this._path[i], this._path[i+1])) {
// At this point, an error has occurred while building the bridge.
// Fail the pathfiner
// return null;
AILog.Warning("MinchinWeb.DLS.GetBuildCost can't build a bridge from " + AIMap.GetTileX(this._path[i]) + "," + AIMap.GetTileY(this._path[i]) + " to " + AIMap.GetTileX(this._path[i+1]) + "," + AIMap.GetTileY(this._path[i+1]) + "!!" );
}
}
}
}
}
// End build sequence
return BeanCounter.GetCosts();
}
// EOF
MetaLib-7/Extras.nut 0000666 0000000 0000000 00000030103 12304267627 012675 0 ustar 0000000 0000000 /* Extra functions v.6 [2014-02-26],
* part of Minchinweb's MetaLibrary v.7
* Copyright © 2011-14 by W. Minchin. For more info,
* please visit https://github.com/MinchinWeb/openttd-metalibrary
*
* Permission is granted to you to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell this software, and provide these
* rights to others, provided:
*
* + The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the software.
* + Attribution is provided in the normal place for recognition of 3rd party
* contributions.
* + You accept that this software is provided to you "as is", without warranty.
*/
// TO-DO: Break this into Math, Geometry, and Extras
/* These are 'random' functions that didn't seem to fit well elsewhere.
*
* Comparison functions will return the first value if the two are equal
*/
/** \brief Extra functions
* \version v.5 (2012-07-01)
* \author W. Minchin (%MinchinWeb)
* \since MetaLibrary v.1
*
* These are 'random' functions that didn't seem to fit well elsewhere.
* Many of them are math helper functions. Many others are helpful in dealing
* geometry.
*/
class _MinchinWeb_Extras_ {
_infinity = null; ///< pointer to \_MinchinWeb\_C\_::Infinity()
constructor() {
this._infinity = _MinchinWeb_C_.Infinity();
}
/** \publicsection
* \brief Get the location of a sign.
* \param text message to search for
* \return TileID of the first instance where the sign matches the given
* text.
* \return `null` if no matching sign can be found.
* \static
*/
function SignLocation(text);
/** \brief Find the tile halfway between two other tiles.
* \param TileA one 'end' tile
* \param TileB the other 'end' tile
* \return the `TileID` of the tile halfway between `TileA` and `TileB`
* \static
*/
function MidPoint(TileA, TileB);
/** \brief Get the perpendicular slope
* \param SlopeIn original slope
* \return slope perpendicular to `SlopeIn` (as a floating point number)
* \note Perpendicular slopes are inverses of each other.
* \see Slope()
* \see \_MinchinWeb\_C\_.Infinity()
* \static
*/
function Perpendicular(SlopeIn);
/** \brief Get the slope between two tiles.
* \param TileA first 'end' tile
* \param TileB tile at the other 'end'
* \return Slope between the two tiles (typically as a floating point
* number.
* \return If the slope is vertical, "Infinity (`Constants.Infinity()`)"
* is returned.
* \return If the slope is flat (i.e. 0), `1/Infinity` is returned.
* \see \_MinchinWeb\_C\_.Infinity()
* \static
*/
function Slope(TileA, TileB);
/** \brief Does `Value` fall between the bounds?
* \param Bound1 one limit
* \param Bound2 another limit
* \param Value the value being tested
* \return `True` is `Value` falls between the bounds, `False` otherwise.
* \note This is helpful in that there is no requirement that `Bound1` be
* larger than `Bound2` or vis-versa.
* \see WithinFloat()
* \static
*/
function Within(Bound1, Bound2, Value);
/** \brief Does `Value` fall between the bounds?
* \param Bound1 one limit
* \param Bound2 another limit
* \param Value the value being tested
* \return `True` is `Value` falls between the bounds, `False` otherwise.
* \note This is helpful in that there is no requirement that `Bound1` be
* larger than `Bound2` or vis-versa.
* \note This version explicitly converts all three parameters to
* floating point numbers before comparing them.
* \see Within()
* \static
*/
function WithinFloat(Bound1, Bound2, Value);
/** \brief Takes the absolute value of both numbers and then returns the
* smaller of the two.
* \return the magnitude of the value closer to zero (this will always
* be positive).
* \see MinAbsFloatKeepSign()
* \see MaxAbsFloat()
* \static
*/
function MinAbsFloat(Value1, Value2);
/** \brief Takes the absolute value of both numbers and then returns the
* larger of the two.
* \return the magnitude of the value farther to zero (this will always
* be positive).
* \see MinAbsFloat()
* \see MaxAbsFloatKeepSign()
* \static
*/
function MaxAbsFloat(Value1, Value2);
/** \brief Returns the absolute value of a given number.
* \return the absolute value of a given number (this will always
* be positive) (this will typically be a floating point number).
* \static
*/
function AbsFloat(Value);
/** \brief Returns the sign of a given number
* \return +1 if the Value >= 0, -1 Value < 0
* \static
*/
function Sign(Value);
/** \brief Returns the smaller of the two numbers
* \return The smaller of the two numbers, as a floating point number.
* \see MaxFloat()
* \static
*/
function MinFloat(Value1, Value2);
/** \brief Returns the larger of the two numbers
* \return The larger of the two numbers, as a floating point number.
* \see MinFloat()
* \static
*/
function MaxFloat(Value1, Value2);
/** \brief Takes the absolute value of both numbers and then returns the
* number with the lesser of the two, sign intact.
* \see MaxAbsFloatKeepSign()
* \see MinAbsFloat()
* \static
*/
function MinAbsFloatKeepSign(Value1, Value2);
/** \brief Takes the absolute value of both numbers and then returns the
* number with the greater of the two, sign intact.
* \see MinAbsFloatKeepSign()
* \see MaxAbsFloat()
* \static
*/
function MaxAbsFloatKeepSign(Value1, Value2);
/** \brief The tile that is neighbouring `StartTile` that is closest to
* `TowardsTile`
*
* Given a `StartTile` and a `TowardsTile`, will give the tile immediately
* next (Manhattan Distance == 1) to `StartTile` that is closest to
* `TowardsTile`.
* \return a neighbouring tile to `StartTile`
* \static
*/
function NextCardinalTile(StartTile, TowardsTile);
/** \brief Returns the revision number of the current build of OpenTTD
* \see See AILib.Common for more details on what is contained in the
* full returned version number.
* \note I determine this at the beginning of my AI's run so that when I
* get bug reports, I know what version of OpenTTD was being run.
* \note This might also be useful if you want to turn on or off certain
* features, depending on if they are in the user's version of OpenTTD.
* \static
*/
function GetOpenTTDRevision();
/** \brief Get the minimum distance between TileID and any of the tiles
* in TargetArray
* \note This is designed such that it can be run as a validator on an
* AIList of tiles
* \param TileID Tile we measure distance from
* \param TargetArray An array to tiles that we want to measure distance
* to. This can also be an AIList where the items are
* tiles.
* \return the minimum distance between TileID and any of the TargetArray
* \note Distances is measured using Manhattan Distance
* \note Distances to invalid tiles is reported as `-1`; therefore
* invalid tiles are always 'closer' that valid tiles.
* \todo Remove invalid tiles from the TargetArray
*/
function MinDistance(TileID, TargetArray);
};
// == Function definitions =================================================
function _MinchinWeb_Extras_::SignLocation(text) {
local sign_list = AISignList();
for (local i = sign_list.Begin(); !sign_list.IsEnd(); i = sign_list.Next()) {
if(AISign.GetName(i) == text)
{
return AISign.GetLocation(i);
}
}
return null;
}
function _MinchinWeb_Extras_::MidPoint(TileA, TileB) {
local X = (AIMap.GetTileX(TileA) + AIMap.GetTileX(TileB)) / 2 + 0.5;
local Y = (AIMap.GetTileY(TileA) + AIMap.GetTileY(TileB)) / 2 + 0.5;
// the 0.5 is to make rounding work
X = X.tointeger();
Y = Y.tointeger();
return AIMap.GetTileIndex(X, Y);
}
function _MinchinWeb_Extras_::Perpendicular(SlopeIn) {
if (SlopeIn == 0) {
return this._infinity;
} else {
SlopeIn = SlopeIn.tofloat();
return (-1 / SlopeIn);
}
}
function _MinchinWeb_Extras_::Slope(TileA, TileB) {
local dx = AIMap.GetTileX(TileB) - AIMap.GetTileX(TileA);
local dy = AIMap.GetTileY(TileB) - AIMap.GetTileY(TileA);
// Zero check
if (dx == 0) {
return _MinchinWeb_C_.Infinity() * _MinchinWeb_Extras_.Sign(dy);
} else if (dy == 0) {
return (1.0 / _MinchinWeb_C_.Infinity()) * _MinchinWeb_Extras_.Sign(dx);
} else {
dx = dx.tofloat();
dy = dy.tofloat();
return (dy / dx);
}
}
function _MinchinWeb_Extras_::Within(Bound1, Bound2, Value)
{
local UpperBound = max(Bound1, Bound2);
local LowerBound = min(Bound1, Bound2);
return ((Value <= UpperBound) && (Value >= LowerBound));
}
function _MinchinWeb_Extras_::WithinFloat(Bound1, Bound2, Value) {
local UpperBound = _MinchinWeb_Extras_.MaxFloat(Bound1, Bound2) + _MinchinWeb_C_.FloatOffset();
local LowerBound = _MinchinWeb_Extras_.MinFloat(Bound1, Bound2) - _MinchinWeb_C_.FloatOffset();
local Value = Value.tofloat();
// _MinchinWeb_Log_.Note(" Extras.WithinFloat: Val=" + Value + " B1=" + Bound1 + " B2=" + Bound2 + " : UB=" + UpperBound + " LB=" + LowerBound + " is " + (Value <= UpperBound) + " " + (Value >= LowerBound) + " : " + ((Value <= UpperBound) && (Value >= LowerBound)) + " : above " + (Value - UpperBound) + " below " + (LowerBound - Value) + " : " + _MinchinWeb_C_.FloatOffset() , 7);
return ((Value <= UpperBound) && (Value >= LowerBound));
}
function _MinchinWeb_Extras_::MinAbsFloat(Value1, Value2) {
if (Value1 < 0) { Value1 *= -1.0; }
if (Value2 < 0) { Value2 *= -1.0; }
if (Value1 <= Value2) {
return Value1;
} else {
return Value2;
}
}
function _MinchinWeb_Extras_::MaxAbsFloat(Value1, Value2) {
if (Value1 < 0) { Value1 *= -1.0; }
if (Value2 < 0) { Value2 *= -1.0; }
if (Value1 >= Value2) {
return Value1;
} else {
return Value2;
}
}
function _MinchinWeb_Extras_::AbsFloat(Value)
{
if (Value >= 0) {
return Value;
} else {
return (Value * (-1.0));
}
}
function _MinchinWeb_Extras_::Sign(Value) {
if (Value >= 0) {
return 1;
} else {
return -1;
}
}
function _MinchinWeb_Extras_::MinFloat(Value1, Value2) {
if (Value1 <= Value2) {
return (Value1).tofloat();
} else {
return (Value2).tofloat();
}
}
function _MinchinWeb_Extras_::MaxFloat(Value1, Value2) {
if (Value1 >= Value2) {
return (Value1).tofloat();
} else {
return (Value2).tofloat();
}
}
function _MinchinWeb_Extras_::MinAbsFloatKeepSign(Value1, Value2) {
local Sign1 = _MinchinWeb_Extras_.Sign(Value1);
local Sign2 = _MinchinWeb_Extras_.Sign(Value2);
if (Value1 < 0) { Value1 *= -1.0; }
if (Value2 < 0) { Value2 *= -1.0; }
if (Value1 <= Value2) {
return (Value1 * Sign1).tofloat();
} else {
return (Value2 * Sign2).tofloat();
}
}
function _MinchinWeb_Extras_::MaxAbsFloatKeepSign(Value1, Value2) {
local Sign1 = _MinchinWeb_Extras_.Sign(Value1);
local Sign2 = _MinchinWeb_Extras_.Sign(Value2);
if (Value1 < 0) { Value1 *= -1.0; }
if (Value2 < 0) { Value2 *= -1.0; }
if (Value1 >= Value2) {
return (Value1 * Sign1).tofloat();
} else {
return (Value2 * Sign2).tofloat();
}
}
function _MinchinWeb_Extras_::NextCardinalTile(StartTile, TowardsTile) {
local Tiles = AITileList();
local offsets = [AIMap.GetTileIndex(0, 1), AIMap.GetTileIndex(0, -1),
AIMap.GetTileIndex(1, 0), AIMap.GetTileIndex(-1, 0)];
foreach (offset in offsets) {
Tiles.AddItem(StartTile + offset, AIMap.DistanceSquare(StartTile + offset, TowardsTile));
}
Tiles.Sort(AIList.SORT_BY_VALUE, AIList.SORT_ASCENDING);
return Tiles.Begin();
}
function _MinchinWeb_Extras_::GetOpenTTDRevision() {
local Version = AIController.GetVersion();
local Revision = Version & 0x0007FFFF;
return Revision;
}
function _MinchinWeb_Extras_::MinDistance(TileID, TargetArray) {
local MinDist = _MinchinWeb_C_.Infinity();
foreach (Target in TargetArray) {
// note that with AIList's, Target is the `Value`, not the `Item`
MinDist = min(MinDist, AITile.GetDistanceManhattanToTile(TileID, Target));
}
return MinDist;
}
// EOF
MetaLib-7/Industry.nut 0000666 0000000 0000000 00000004657 12304466127 013263 0 ustar 0000000 0000000 /* Industry related functions v.1 r.206 [2012-01-12],
* part of Minchinweb's MetaLibrary v.6,
* Copyright © 2011-14 by W. Minchin. For more info,
* please visit https://github.com/MinchinWeb/openttd-metalibrary
*
* Permission is granted to you to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell this software, and provide these
* rights to others, provided:
*
* + The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the software.
* + Attribution is provided in the normal place for recognition of 3rd party
* contributions.
* + You accept that this software is provided to you "as is", without warranty.
*/
/** \brief Industries
* \version v.1 (2012-01-12)
* \author W. Minchin (%MinchinWeb)
* \since MetaLibrary v.2
*
* These are functions relating to dealing with OpenTTD industries.
*/
class _MinchinWeb_Industry_ {
main = null;
/** \publicsection
* \brief Get the `IndustryID` of the industry at a given `TileID`.
* \param Tile TileID to start the search from
* \return `IndustryID` of the industry at (or around) `TileID`.
* \return `Constants.InvalidIndustry()` if no valid industries can be
* found.
* \note `AIIndustry.GetIndustryID( AIIndustry.GetLocation( IndustryID ) )`
* sometimes fails because `GetLocation()` returns the northmost
* tile of the industry which may be a dock, heliport, or not part
* of the industry at all.
* \note This function starts at the northmost tile, and then searches a
* square out (up to `Constants.StationSize()` ) until it finds a
* tile with a valid `IndustryID`.
* \static
*/
function GetIndustryID(Tile);
};
// == Function definitions ================================================
function _MinchinWeb_Industry_::GetIndustryID(Tile) {
local StartX = AIMap.GetTileX(Tile);
local StartY = AIMap.GetTileY(Tile);
local EndX = AIMap.GetTileX(Tile) + _MinchinWeb_C_.IndustrySize();
local EndY = AIMap.GetTileY(Tile) + _MinchinWeb_C_.IndustrySize();
for (local i = StartX; i < EndX; i++) {
for (local j = StartY; j < EndY; j++) {
if (AIIndustry.GetIndustryID(AIMap.GetTileIndex(i,j)) != _MinchinWeb_C_.InvalidIndustry()) {
return AIIndustry.GetIndustryID(AIMap.GetTileIndex(i,j));
}
}
}
// if no valid industry is found...
return _MinchinWeb_C_.InvalidIndustry();
}
// EOF
MetaLib-7/Lakes.nut 0000666 0000000 0000000 00000053045 12305212523 012462 0 ustar 0000000 0000000 /* Lakes Check v.3 [2014-03-02],
* part of Minchinweb's MetaLibrary v.7,
* replacement for WaterBody Check
* Copyright © 2011-14 by W. Minchin. For more info,
* please visit https://github.com/MinchinWeb/openttd-metalibrary
*
* Permission is granted to you to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell this software, and provide these
* rights to others, provided:
*
* + The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the software.
* + Attribution is provided in the normal place for recognition of 3rd party
* contributions.
* + You accept that this software is provided to you "as is", without warranty.
*/
/** \brief Lakes
* \version v.3 (2012-03-02)
* \author W. Minchin (%MinchinWeb)
* \since MetaLibrary v.7
*
* Lakes is a replacement for WaterBody Check (\_MinchinWeb\_WBC\_). Lakes
* serves to determine if two water tiles are connected by water (i.e. if a
* ship could sail between them). It trades memory usage for speed by
* caching results. Like WaterBody Check, it will keep trying to find a
* connections until there is no possible connection left.
*
* Approximate program flow:
*
* \dot
digraph G {
Start -> {B; A};
A -> AreaA;
B -> AreaB;
AreaA -> AddPtA [label="unknown"];
{AreaA; AreaB} -> Match [label="known"];
AreaB -> AddPtB [label="unknown"];
Match -> Yes1 [label="yes"];
Match -> StillEdges [label="no"];
{AddPtA; AddPtB} -> StillEdges;
StillEdges -> StillEdgeA [label="yes"];
StillEdgeA -> StillEdgeB [label="no"];
StillEdgeB -> StillEdges [label="no"]
StillEdgeA -> PickEdgeA -> AMatchB;
AMatchB -> FoundMatch [label="yes"];
AMatchB -> AddA [label="no"];
AddA -> AddEdgeA -> StillEdgeB -> PickEdgeB;
PickEdgeB -> BMatchA;
BMatchA -> FoundMatch [label="yes"];
BMatchA -> AddB [label="no"];
AddB -> AddEdgeB -> StillEdges;
FoundMatch -> Yes2;
StillEdges -> No1 [label="no"];
Start [shape=box];
A [label="Point A"];
B [label="Point B"];
AreaA [shape=diamond, label="Area?"];
AreaB [shape=diamond, label="Area?"];
AddPtA [label="Add Point"];
AddPtB [label="Add Point"];
Match [shape=diamond, label="Areas\nmatch?"];
Yes1 [shape=box, label="return\n'true'"];
Yes2 [shape=box, label="return\n'true'"];
No1 [shape=box, label="return\n'null'"];
StillEdges [shape=diamond, label="Still\nedges?"];
StillEdgeA [shape=diamond, label="Edge left\non A?"];
StillEdgeB [shape=diamond, label="Edge left\non B?"];
PickEdgeA [label="Pick past-edge\nclosest to B"];
PickEdgeB [label="Pick past-edge\nclosest to A"];
AMatchB [shape=diamond, label="In B's\n area?"];
BMatchA [shape=diamond, label="In A's\n area?"];
AddA [label="Add tile\nto\nArea A"];
AddB [label="Add tile\nto\nArea B"];
AddEdgeA [label="Add new\npast-edges"];
AddEdgeB [label="Add new\npast-edges"];
FoundMatch [label="Match\nfound!"];
{ rank=same; A -> B [color=white]; }
{ rank=same; StillEdgeA; StillEdgeB; }
}
* \enddot
*
* \requires \_MinchinWeb\_DLS\_
* \see \_MinchinWeb\_ShipPathfinder\_
* \see \_MinchinWeb\_WBC\_
* \note Although _map and _group_tiles keep the same information (which
* tiles are in which group), there are independently created and
* maintained. If for some reason they become de-synced, Lakes
* will not work as expected. However, the previous approach of
* effectively creating _group_tiles from _map on the fly twice a
* loop was deemed too time demanding.
*/
class _MinchinWeb_Lakes_
{
/** \brief AIList that tells which group each tile belongs in
*
* `item` is TileIndex, `value` is Group. `value == -2` means the value
* remains unset, `value == -1` means the tile is land.
*/
_map = null;
/** \brief Array that shows the connections to each tile group
*
* `index` is TileGroup.
*/
_connections = null;
/** \brief Array of the defined tile groups
*
* `index` is TileGroup.
*/
_areas = null;
/** \brief Array of tiles that are open from each tile group
*
* `index` is TileGroup; form is [Edge_Tile, Past_Edge_Tile]
*/
_open_neighbours = null;
/** \brief Array of AIList's of the tiles that are in each group
*
* `index` is TileGroup
* \note Does not contain land tiles (i.e. group `-1`)
*/
_group_tiles = null;
_AGroup = null; ///< array of groups containing source tiles
_BGroup = null; ///< array of groups containing goal tiles
_A = null; ///< array of source tiles
_B = null; ///< array of goal tiles
_running = null; ///< is Lakes currently running?
constructor() {
this._map = AIList();
for (local i = 0; i < AIMap.GetMapSize(); i++) {
this._map.AddItem(i, -2);
}
this._connections = array(0);
this._areas = array(0);
this._open_neighbours = array(0);
this._group_tiles = array(0);
this._running = false;
_AddGridPoints();
};
/** \publicsection
* Initialize a path search between sources and goals.
* \param sources The source tiles. Assumed to be an array.
* \param goals The target tiles. Assumed to be an array.
*/
function InitializePath(sources, goals) {
this._AGroup = array(0);
this._BGroup = array(0);
this._A = sources;
this._B = goals;
foreach (node in sources) {
this._AGroup.push(this.AddPoint(node));
}
foreach (node in goals) {
this._BGroup.push(this.AddPoint(node));
}
this._AGroup = _MinchinWeb_Array_.RemoveDuplicates(this._AGroup);
this._BGroup = _MinchinWeb_Array_.RemoveDuplicates(this._BGroup);
this._running = true;
};
/**
* Try to find if the source and goal tiles are within the same waterbody.
* \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 immediately and will
* never find a path.
* \return 'true' if within the same waterbody, 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.
*/
function FindPath(iterations);
/** \private
* \brief Seeds the grid points to Lakes.
*
* Called by the class initialization function.
*/
function _AddGridPoints();
/** \public
* \brief Seeds a point into Lakes.
* \param myTileID Assumed to be a tile index.
* \return Area the tile is in. -1 if the tile is on land.
*/
function AddPoint(myTileID);
/** \private
* \brief Given a starting group, return all groups attached to it
* \param StartGroupArray assumed to be an array
* \return An array listing all the attached tile groups
*/
function _AllGroups(StartGroupArray);
/** \public
* \brief Get the minimum distance between the source and destination
* tiles.
* \note Distance is calculated as Manhattan Distance
*/
function GetPathLength();
/** \privatesection
* \brief Processes `NextTile`s neighbours
* \param NextTile Tile we consider the neighbours of
* \return `[null]` if no neighbours are added.
* \return An array of the neighbours added otherwise.
*/
function _AddNeighbour(NextTile);
};
function _MinchinWeb_Lakes_::FindPath(iterations) {
// This is where the meat and potatoes is!
// See the diagram in the docs for how this works
if (iterations < 0) {
iterations = _MinchinWeb_C_.Infinity();
}
for (local i = 0; i < iterations; i++) {
local tick = AIController.GetTick();
if (_MinchinWeb_Array_.Compare1D(this._AGroup, [-1]) || _MinchinWeb_Array_.Compare1D(this._BGroup, [-1])) {
// one end is all on land
// no path is possible
_MinchinWeb_Log_.Note("Lakes failed to find a path. One end is all on land.", 5);
this._running = false;
return null;
}
// Get not only the groups the tiles are in, but all the tile groups
// that are connected
local AAllGroups = _AllGroups(this._AGroup);
_MinchinWeb_Log_.Note("AGroup: " + _MinchinWeb_Array_.ToString1D(this._AGroup, false) + " All A:" + _MinchinWeb_Array_.ToString1D(AAllGroups, false), 6);
foreach (Group in AAllGroups) {
if (_MinchinWeb_Array_.ContainedIn1D(this._BGroup, Group)) {
// If we have a connection, return 'true'
_MinchinWeb_Log_.Note("B Group (" + this._BGroup[0] + ")found in A All Groups!", 6);
this._running = false;
return true;
}
}
// No match (yet anyway...)
// Get all the open neighbours of A
local ANeighbours = array(0);
local AEdge = array(0);
local BAllGroups = array(0);
AAllGroups = _AllGroups(this._AGroup);
BAllGroups = _AllGroups(this._BGroup);
foreach (Group in AAllGroups){
ANeighbours = _MinchinWeb_Array_.Append(ANeighbours, this._open_neighbours[Group]);
}
foreach (neighbour in ANeighbours) {
AEdge.append(neighbour[0]);
}
// remove duplicates
AEdge = _MinchinWeb_Array_.RemoveDuplicates(AEdge);
_MinchinWeb_Log_.Note("A -- Edge: " + AEdge.len() + " Neighbours: " + ANeighbours.len(), 6);
_MinchinWeb_Log_.Note("A -- Edge: " + _MinchinWeb_Array_.ToStringTiles1D(AEdge), 7);
if (ANeighbours.len() > 0) {
// Get the tile from AEdge that is closest to B's
local AEdgeList = AIList();
foreach (edge in AEdge) {
AEdgeList.AddItem(edge, 0);
}
local BTileList = AIList();
foreach (group in BAllGroups) {
BTileList.AddList(this._group_tiles[group]);
}
//_MinchinWeb_Log_.Note(" B -- Tiles: " + _MinchinWeb_Array_.ToStringTiles1D(BTileArray), 7);
AEdgeList.Valuate(_MinchinWeb_Extras_.MinDistance, BTileList);
// Process the tile's 4 neighbours x12
local NeighbourLoops = 12;
AEdgeList.Sort(AIList.SORT_BY_VALUE, AIList.SORT_ASCENDING);
AEdgeList.KeepTop(NeighbourLoops + 1);
for (local j=1; j <= min(AEdgeList.Count(), NeighbourLoops); j++) {
local NextNeighbour = AEdgeList.Begin();
AEdgeList.RemoveItem(NextNeighbour);
local AddedNeighbours = _AddNeighbour(NextNeighbour);
foreach (Tile in AddedNeighbours) {
if (Tile != null) {
AEdgeList.AddItem(Tile, _MinchinWeb_Extras_.MinDistance(Tile, BTileList));
}
}
AEdgeList.Sort(AIList.SORT_BY_VALUE, AIList.SORT_ASCENDING);
}
} else {
// With no 'open neighbours', there can be no more connections
this._running = false;
return null;
}
local BNeighbours = array(0);
local BEdge = array(0);
AAllGroups = _AllGroups(this._AGroup);
BAllGroups = _AllGroups(this._BGroup);
// Check to see if we have a connection (and can go home!)
_MinchinWeb_Log_.Note("BGroup: " + _MinchinWeb_Array_.ToString1D(this._AGroup, false) + " All B:" + _MinchinWeb_Array_.ToString1D(AAllGroups, false), 6);
foreach (Group in BAllGroups) {
if (_MinchinWeb_Array_.ContainedIn1D(this._AGroup, Group)) {
// If we have a connection, return 'true'
_MinchinWeb_Log_.Note("A Group (" + this._AGroup[0] + ") found in B All Groups!", 6);
this._running = false;
return true;
}
}
foreach (Group in BAllGroups) {
BNeighbours = _MinchinWeb_Array_.Append(BNeighbours, this._open_neighbours[Group]);
}
foreach (neighbour in BNeighbours) {
BEdge.append(neighbour[0]);
}
// remove duplicates
BEdge = _MinchinWeb_Array_.RemoveDuplicates(BEdge);
_MinchinWeb_Log_.Note("B -- Edge: " + BEdge.len() + " Neighbours: " + BNeighbours.len(), 6);
_MinchinWeb_Log_.Note("B -- Edge: " + _MinchinWeb_Array_.ToStringTiles1D(BEdge), 7);
if (BNeighbours.len() > 0) {
// Get the tile from AEdge that is closest to BEdge
local BEdgeList = AIList();
foreach (neighbour in BNeighbours) {
BEdgeList.AddItem(neighbour[0], 0);
}
//local ATileArray = array(0);
local ATileList = AIList();
foreach (group in AAllGroups) {
ATileList.AddList(this._group_tiles[group]);
}
//_MinchinWeb_Log_.Note(" A -- Tiles: " + _MinchinWeb_Array_.ToStringTiles1D(ATileArray), 7);
BEdgeList.Valuate(_MinchinWeb_Extras_.MinDistance, ATileList);
// Process the tile's 4 neighbours x12
local NeighbourLoops = 12;
BEdgeList.Sort(AIList.SORT_BY_VALUE, AIList.SORT_ASCENDING);
BEdgeList.KeepTop(NeighbourLoops + 1);
for (local j=1; j <= min(BEdgeList.Count(), NeighbourLoops); j++) {
local NextNeighbour = BEdgeList.Begin();
BEdgeList.RemoveItem(NextNeighbour);
local AddedNeighbours = _AddNeighbour(NextNeighbour);
foreach (Tile in AddedNeighbours) {
if (Tile != null) {
BEdgeList.AddItem(Tile, _MinchinWeb_Extras_.MinDistance(Tile, ATileList));
}
}
BEdgeList.Sort(AIList.SORT_BY_VALUE, AIList.SORT_ASCENDING);
}
} else {
// With no 'open neighbours', there can be no more connections
this._running = false;
return null;
}
_MinchinWeb_Log_.Note("B -- " + (AIController.GetTick() - tick) + " ticks.", 8);
}
// ran out of loops, we're still running
return false;
}
function _MinchinWeb_Lakes_::GetPathLength() {
local BList = _MinchinWeb_Array_.ToAIList(this._B);
local MinDist = _MinchinWeb_C_.Infinity();
BList.Valuate(_MinchinWeb_Extras_.MinDistance, this._A);
return BList.GetValue(BList.Begin()); // value of first item
}
// == Functions not related to the pathfinder ===============================
function _MinchinWeb_Lakes_::_AddGridPoints() {
local myDLS = _MinchinWeb_DLS_();
local Grid = myDLS.AllGridPoints()
foreach (point in Grid) {
AddPoint(point)
}
}
function _MinchinWeb_Lakes_::AddPoint(myTileID) {
// _MinchinWeb_Log_.Note("Tile Area " + this._map.GetValue(myTileID) + " : " + this._map.Count() + " / " + this._areas.len(), 6);
switch (this._map.GetValue(myTileID)) {
case -2:
// tile is unset (i.e. in no group)
if (AITile.IsWaterTile(myTileID) == true) {
// add to _map if a water tile
local myArea = this._areas.len();
this._areas.append(myTileID);
this._open_neighbours.append([]);
this._connections.append([]);
this._map.SetValue(myTileID, myArea);
this._group_tiles.append(AIList());
this._group_tiles[myArea].AddItem(myTileID, myTileID);
local offsets = [AIMap.GetTileIndex(0, 1), AIMap.GetTileIndex(0, -1),
AIMap.GetTileIndex(1, 0), AIMap.GetTileIndex(-1, 0)];
foreach (offset in offsets) {
local next_tile = myTileID + offset;
if (AIMarine.AreWaterTilesConnected(myTileID, next_tile)) {
if (this._map.GetValue(next_tile) == -2) {
this._open_neighbours[myArea].append([myTileID, next_tile]);
} else if (this._map.GetValue(next_tile) == -1) {
// the tile has ceased to be land (somehow...)
this._map.SetValue(next_tile, -2);
this._open_neighbours[myArea].append([myTileID, next_tile]);
} else {
// register connection right now
local ConnectedArea = this._map.GetValue(next_tile);
this._connections[myArea].append(ConnectedArea);
this._connections[ConnectedArea].append(myArea);
local AllConnectedAreas = _AllGroups([ConnectedArea]);
// remove open neighbour from AllConnectedAreas to next_tile
foreach (ThisArea in AllConnectedAreas) {
for (local i=0; i < this._open_neighbours[ThisArea].len(); i++) {
if (this._open_neighbours[ThisArea][i][1] == myTileID) {
// we're looking for the reverse tile pair to the one we just tried to add
this._open_neighbours[ThisArea] = _MinchinWeb_Array_.RemoveValueAt(this._open_neighbours[ThisArea], i);
i--;
}
}
}
}
}
}
_MinchinWeb_Log_.Note(myArea + " : " + _MinchinWeb_Array_.ToStringTiles2D(this._open_neighbours[myArea], false), 7);
_MinchinWeb_Log_.Sign(myTileID, "L" + myArea, 8);
return myArea;
} else {
// land tile
this._map.SetValue(myTileID, -1);
// not added to _group_tiles
return -1;
}
case -1:
// land tile
return -1;
default:
// already in _map
return this._map.GetValue(myTileID);
}
}
function _MinchinWeb_Lakes_::_AllGroups(StartGroupArray) {
// this function starts with an array of starting groups
// starts at the beginning and for the first group, appends all connected
// groups to the end of the array
// does the same for the second and so on until the original end of the
// array
// next it compacts the resulting array by removing duplicates
// then it picks up where it left off in the (now larger) array and starts
// adding connections again
// this cycle keeps going until no more connections are added that aren't
// duplicates
local loops = 0;
local StartIndex = 0;
local ReturnGroup = StartGroupArray;
local NextStartIndex = 0;
local MoreAdded = true;
do {
//_MinchinWeb_Log_.Note("In AllGroups(), loop " + loops + ". start: " + StartIndex + " // " + _MinchinWeb_Array_.ToString1D(ReturnGroup, false), 6);
MoreAdded = false;
NextStartIndex = ReturnGroup.len();
for (local i = StartIndex; i < NextStartIndex; i++) {
if ((ReturnGroup[i] >= 0) && (this._connections[ReturnGroup[i]].len() > 0)) {
ReturnGroup = _MinchinWeb_Array_.Append(ReturnGroup, this._connections[ReturnGroup[i]]);
ReturnGroup = _MinchinWeb_Array_.RemoveDuplicates(ReturnGroup);
MoreAdded = true;
}
}
StartIndex = NextStartIndex;
loops++;
} while (MoreAdded == true)
return ReturnGroup;
}
function _MinchinWeb_Lakes_::_AddNeighbour(NextTile) {
// Start by finding out what area we're expanding
local ReturnTiles = array(0);
local OnwardTiles = array(0);
for (local i = 0; i < this._open_neighbours.len(); i++) {
for (local j = 0; j < this._open_neighbours[i].len(); j++) {
if (this._open_neighbours[i][j][0] == NextTile) {
OnwardTiles.append(this._open_neighbours[i][j][1]);
this._open_neighbours[i] = _MinchinWeb_Array_.RemoveValueAt(this._open_neighbours[i], j);
j--;
}
}
}
// remove duplicates
OnwardTiles = _MinchinWeb_Array_.RemoveDuplicates(OnwardTiles);
// remove onward tiles that are already in a tile group
local ConnectedGroups = _AllGroups([this._map[NextTile]]);
for (local i = 0; i < OnwardTiles.len(); i++) {
if (this._map[OnwardTiles[i]] != -2) {
// But only if we've already registered the connection
if (_MinchinWeb_Array_.ContainedIn1D(ConnectedGroups, this._map[OnwardTiles[i]])) {
OnwardTiles = _MinchinWeb_Array_.RemoveValueAt(OnwardTiles, i);
// Add remove from open neighbours
i--;
}
}
}
_MinchinWeb_Log_.Note("NextTile: " + _MinchinWeb_Array_.ToStringTiles1D([NextTile]) + " // Onward Tiles: " + _MinchinWeb_Array_.ToStringTiles1D(OnwardTiles), 8);
if (OnwardTiles.len() == 0) {
// if something broke, spit out useful debug information
_MinchinWeb_Log_.Note(" MinchinWeb.Lakes._AddNeighbour(): No source for " + _MinchinWeb_Array_.ToStringTiles1D([NextTile]), 8);
/*_MinchinWeb_Log_.Note(" this._open_neighbours");
for (local i = 0; i < this._open_neighbours.len(); i++) {
_MinchinWeb_Log_.Note(" [" + i + "] " + _MinchinWeb_Array_.ToString2D(this._open_neighbours[i]), 0);
}*/
return [null];
} else {
local FromGroup = this._map.GetValue(NextTile);
local offsets = [AIMap.GetTileIndex(0, 1), AIMap.GetTileIndex(0, -1),
AIMap.GetTileIndex(1, 0), AIMap.GetTileIndex(-1, 0)];
// Check that each neighbour is still attached by water to our from tile,
// and then add it to the FromGroup
// Then add possible neighbours to the open_neighbours list
foreach (OnwardTile in OnwardTiles) {
if (AIMarine.AreWaterTilesConnected(NextTile, OnwardTile)) {
this._map.SetValue(OnwardTile, FromGroup);
this._group_tiles[FromGroup].AddItem(OnwardTile, OnwardTile);
ReturnTiles.append(OnwardTile);
_MinchinWeb_Log_.Sign(OnwardTile, "L" + FromGroup, 8);
foreach (offset in offsets) {
local next_tile = OnwardTile + offset;
if (AIMarine.AreWaterTilesConnected(OnwardTile, next_tile) && (this._map.GetValue(next_tile) != this._map.GetValue(OnwardTile))) {
this._open_neighbours[FromGroup].append([OnwardTile, next_tile]);
}
}
}
// If more than one groups list this tile as an open neighbour,
// register the two groups are now linked
for (local i = 0; i < this._open_neighbours.len(); i++) {
for (local j = 0; j < this._open_neighbours[i].len(); j++) {
if (this._open_neighbours[i][j][1] == OnwardTile) {
local ActiveFromGroup = this._map.GetValue(this._open_neighbours[i][j][0]);
this._connections[FromGroup].append(ActiveFromGroup);
this._connections[ActiveFromGroup].append(FromGroup);
this._open_neighbours[i] = _MinchinWeb_Array_.RemoveValueAt(this._open_neighbours[i], j);
j--;
// remove duplicates
local oldActiveFromGroup = this._connections[ActiveFromGroup];
local oldFromGroup = this._connections[FromGroup];
this._connections[ActiveFromGroup] = _MinchinWeb_Array_.RemoveDuplicates(this._connections[ActiveFromGroup]);
this._connections[FromGroup] = _MinchinWeb_Array_.RemoveDuplicates(this._connections[FromGroup]);
//_MinchinWeb_Log_.Note(" Connections: " + FromGroup + " : " + _MinchinWeb_Array_.ToString1D(oldFromGroup, false) + "-> " + _MinchinWeb_Array_.ToString1D(this._connections[FromGroup], false), 7);
//_MinchinWeb_Log_.Note(" Connections: " + ActiveFromGroup + " : " + _MinchinWeb_Array_.ToString1D(oldActiveFromGroup, false) + "-> " + _MinchinWeb_Array_.ToString1D(this._connections[ActiveFromGroup], false), 7);
}
}
}
}
}
return ReturnTiles;
}
// EOF
MetaLib-7/library.nut 0000666 0000000 0000000 00000003251 12305212673 013067 0 ustar 0000000 0000000 /* Minchinweb's MetaLibrary v.7.2 [2014-03-03],
* originally part of WmDOT v.10
* Copyright © 2011-14 by W. Minchin. For more info,
* please visit https://github.com/MinchinWeb/openttd-metalibrary
*
* Permission is granted to you to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell this software, and provide these
* rights to others, provided:
*
* + The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the software.
* + Attribution is provided in the normal place for recognition of 3rd party
* contributions.
* + You accept that this software is provided to you "as is", without warranty.
*/
/** \brief MinchinWeb extends AILibrary so that it is registered as a library
* by OpenTTD.
* \see MinchinWeb
*/
class MinchinWeb extends AILibrary {
function GetAuthor() { return "W. Minchin"; }
function GetName() { return "MinchinWeb"; }
function GetShortName() { return "LMmW"; } // William's MetaLibrary
function GetDescription() { return "Minchinweb's MetaLibrary for AI development. See the minchin.ca/openttd-metalibrary/ for included functions. (v.7.2, 2014-03-03)"; }
function GetVersion() { return 7; }
function GetDate() { return "2014-03-03"; }
function CreateInstance() { return "MinchinWeb"; }
function GetCategory() { return "Util"; }
// function GetURL() { return "http://www.tt-forums.net/viewtopic.php?f=65&t=57903"; }
// function GetAPIVersion() { return "1.2"; }
function MinVersionToLoad() { return 1; }
}
RegisterLibrary(MinchinWeb());
// requires AyStar v6
// requires Fibonacci Heap v3
MetaLib-7/License.txt 0000666 0000000 0000000 00000001407 12304465331 013017 0 ustar 0000000 0000000 MinchinWeb's MetaLibrary License
v.7, 2014-02-28
Copyright © 2011-14 by W. Minchin. For more info, please visit
https://github.com/MinchinWeb/openttd-metalibrary
-- License -------------------------------------------------------------------
Permission is granted to you to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell this software, and provide these rights to others,
provided:
+ The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the software.
+ Attribution is provided in the normal place for recognition of 3rd party
contributions.
+ You accept that this software is provided to you "as is", without
warranty.
MetaLib-7/Line.Walker.nut 0000666 0000000 0000000 00000026277 12304271223 013546 0 ustar 0000000 0000000 /* LineWalker class v.1 r.221 [2012-01-28],
* part of Minchinweb's MetaLibrary v.4,
* originally part of WmDOT v.7
* Copyright © 2011-12 by W. Minchin. For more info,
* please visit https://github.com/MinchinWeb/openttd-metalibrary
*
* Permission is granted to you to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell this software, and provide these
* rights to others, provided:
*
* + The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the software.
* + Attribution is provided in the normal place for recognition of 3rd party
* contributions.
* + You accept that this software is provided to you "as is", without warranty.
*/
/** \brief Line Walker
* \version v.1 (2012-01-28)
* \author W. Minchin (%MinchinWeb)
* \since MetaLibrary v.4
*
* The LineWalker class allows you to define a starting and endpoint, and then
* 'walk' all the tiles between the two. Alternately, you can give a
* starting point and a slope. It was originally part of my Ship
* Pathfinder, also part of Minchinweb's MetaLibrary.
*/
/* Functions provided:
* MetaLib.LineWalker()
* MetaLib.LineWalker.Start(Tile)
* .End(Tile)
* .Slope(Slope)
* .Reset()
* .Restart()
* .Walk()
* .IsEnd()
* .GetStart()
* .GetEnd()
*/
/** \note Plane geometry does funky things when you don't have an infinity, or
* by extension, zero (the inverse of infinity) for slopes. To get
* around the fact integer conversions drop everything past the decimal
* point (effectively rounding down), slopes are set so that there is a
* slight inflection point at the 'origin' so that as you move out from
* the start point, so stay slightly above the 'unchanging' index...
*
* \note `LineWalker` is designed to be a persistent class.
* \see \_MinchinWeb\_SW\_
*/
class _MinchinWeb_LW_ {
_start = null; ///< start tile
_end = null; ///< end tile
_slope = null; ///< line slope
_startx = null; ///< x value of start tile
_starty = null; ///< y value of start tile
_endx = null; ///< x value of end tile
_endy = null; ///< y value of end tile
_past_end = null;
_x = null; ///< x value of current tile
_y = null; ///< y value of current tile
_dirx = null;
_current_tile = null; ///< current tile
constructor() {
this._past_end = true;
// this._infinity = _MinchinWeb_C_.Infinity(); // close enough to infinity :P
// Slopes are capped at 10,000 and 1/10,000
}
/** \publicsection
* \brief Sets the starting tile for LineWalker
* \see End()
* \see Slope()
* \see Restart()
* \see Reset()
* \see GetStart()
*/
function Start(Tile);
/** \brief Sets the ending tile for LineWalker
* \note If the slope is also directly set, the start and end tiles
* define a bounding box. Otherwise, the slope is calculated
* between the start and end tiles.
* \see Start()
* \see Slope()
* \see GetEnd()
*/
function End(Tile);
/** \brief Sets the slope for LineWalker
* \note Assumes that the slope is in the first or second quadrant unless
* `ThirdQuadrant == true`
* \todo Add a picture of the four Cartesian quadrants
*/
function Slope(Slope, ThirdQuadrant = false);
/** \brief Resets the variables for the LineWalker
* \see Restart()
*/
function Reset();
/** \brief Moves the LineWalker to the original starting position
* \see Reset()
*/
function Restart();
// === LineWalker Walk ===
// This is where (most) of the action is!
/** \brief 'Walks' the LineWalker one tile at a tile
* \return the tile that the LineWalker is now "standing on"
* \note This is where (most) of the action is!
* \note Before calling this function, you need to set the Start() and at
* least one of End() or Slope().
*/
function Walk();
/** \brief Returns `True` if we are at the edge of the bounding box defined
* by the Starting and Ending point
* \return `True` if we are at the edge of the bounding box defined
* by the Starting and Ending point
* \see End()
* \see GetEnd()
*/
function IsEnd() { return this._past_end; }
/** \brief Returns the tile the LineWalker is starting on
* \return The tile the LineWalker is starting on
* \see Start()
*/
function GetStart() { return this._start; }
/** \brief Returns the tile the LineWalker is ending on
* \return The tile the LineWalker is ending on
* \see End()
* \see IsEnd()
*/
function GetEnd() { return this._end; }
};
// == Function definitions ==================================================
function _MinchinWeb_LW_::Start(Tile) {
this._start = Tile;
this._startx = AIMap.GetTileX(Tile);
this._starty = AIMap.GetTileY(Tile);
this._x = this._startx;
this._y = this._starty;
this._past_end = false;
this._current_tile = AIMap.GetTileIndex(this._x, this._y);
this._x = this._x.tofloat();
this._y = this._y.tofloat();
if (this._end != null) {
if (this._slope == null) {
this._slope = _MinchinWeb_Extras_.Slope(this._start, this._end);
}
if (this._startx < this._endx) {
this._dirx = 1;
} else if (this._startx > this._endx) {
this._dirx = -1;
} else {
// startX == EndX
if (this._starty < this._endy) {
this._dirx = 1;
} else {
this._dirx = 1;
}
this._endx = this._endx.tofloat() + (1.0 - (1.0 / _MinchinWeb_C_.Infinity()));
}
if (this._starty == this._endy) {
this._endy = this._endy.tofloat() + (1.0 - (1.0 / _MinchinWeb_C_.Infinity()));
}
}
// _MinchinWeb_Log_.Note(" LineWalker.Start out: " + this._startx + " " + this._starty + " m" + this._slope + " ± " + this._dirx, 6);
}
function _MinchinWeb_LW_::End(Tile) {
this._end = Tile;
this._endx = AIMap.GetTileX(Tile);
this._endy = AIMap.GetTileY(Tile);
if (this._start != null) {
if (this._slope == null) {
this._slope = _MinchinWeb_Extras_.Slope(this._start, this._end);
}
if (this._startx < this._endx) {
this._dirx = 1;
} else if (this._startx > this._endx) {
this._dirx = -1;
} else {
// startX == EndX
if (this._starty < this._endy) {
this._dirx = 1;
} else {
this._dirx = 1;
}
this._endx = this._endx.tofloat() + (1.0 - (1.0 / _MinchinWeb_C_.Infinity()));
}
if (this._starty == this._endy) {
this._endy = this._endy.tofloat() + (1.0 - (1.0 / _MinchinWeb_C_.Infinity()));
}
}
// _MinchinWeb_Log_.Note(" LineWalker.End out: " + this._endx + " " + this._endy + " m" + this._slope + " ± " + this._dirx + " mult=" + _MinchinWeb_Extras_.MinAbsFloat(1.0, (1.0 / this._slope) ), 6);
}
function _MinchinWeb_LW_::Slope(Slope, ThirdQuadrant = false) {
if (_MinchinWeb_Extras_.AbsFloat(Slope) > _MinchinWeb_C_.Infinity()) {
AILog.Warning("Slope is capped at " + _MinchinWeb_C_.Infinity() + ", you provided " + Slope + ".");
this._slope = _MinchinWeb_C_.Infinity();
} else if (_MinchinWeb_Extras_.AbsFloat(Slope) < (1.0 / _MinchinWeb_C_.Infinity())) {
AILog.Warning("Slope is capped at 1/" + _MinchinWeb_C_.Infinity() + ", you provided " + Slope + ".");
this._slope = (1.0 / _MinchinWeb_C_.Infinity());
} else {
this._slope = Slope;
}
if (ThirdQuadrant == false) {
this._dirx = 1;
this._endx = AIMap.GetMapSizeX();
if (this._slope > 0.0) {
this._endy = AIMap.GetMapSizeY();
} else {
this._endy = 0;
}
} else {
this._dirx = -1;
// this._x += (1.0 - (1.0 / _MinchinWeb_C_.Infinity()));
// this._endx = -1 * this._infinity;
// this._endy = -1 * this._endy;
this._endx = 0;
if (this._slope > 0.0) {
// this._endy = AIMap.GetMapSizeY();
this._endy = 0;
} else {
// this._endy = 0;
this._endy = AIMap.GetMapSizeY();
}
}
// _MinchinWeb_Log_.Note(" LineWalker.Slope out: " + Slope + " " + ThirdQuadrant + " : " + this._endx + " " + this._endy + " " + this._slope + " ± " + this._dirx, 6);
}
function _MinchinWeb_LW_::Reset() {
this._start = null;
this._end = null;
this._slope = null;
this._startx = null;
this._starty = null;
this._endx = null;
this._endy = null;
this._past_end = true;
this._x = null;
this._y = null;
this._current_tile = null;
this._dirx = null;
}
function _MinchinWeb_LW_::Restart() {
this._x = this._startx.tofloat();
this._y = this._starty.tofloat();
this._past_end = false;
this._current_tile = AIMap.GetTileIndex(this._x.tointeger(), this._y.tointeger());
}
// === LineWalker Walk ===
// This is where (most) of the action is!
function _MinchinWeb_LW_::Walk() {
if (this._past_end == true) {
return this._current_tile;
}
if ((AIMap.DistanceManhattan(this._current_tile, AIMap.GetTileIndex(this._x.tointeger(), this._y.tointeger())) == 1 ) && _MinchinWeb_Extras_.WithinFloat(this._startx.tofloat(), this._endx.tofloat(), this._x.tointeger()) &&_MinchinWeb_Extras_.WithinFloat(this._starty.tofloat(), this._endy.tofloat(), this._y.tointeger())) {
this._current_tile = AIMap.GetTileIndex(this._x.tointeger(), this._y.tointeger());
// _MinchinWeb_Log_.Note("Linewalker output " + AIMap.GetTileX(this._current_tile) + "," + AIMap.GetTileY(this._current_tile) + " from " + this._x + "," + this._y, 7);
return this._current_tile;
}
// Infinity assumed to be 10,000
local multiplier = 0.0;
// We need to find the value, such that MAX(ABS(∆x, m∆x)) == 1
// Therefore, our multiplier is MIN(ABS(1, 1/m))
multiplier = _MinchinWeb_Extras_.MinAbsFloat(1.0, (1.0 / this._slope) );
local NewX = 0.0;
local NewY = 0.0;
NewX = this._x + multiplier * this._dirx;
NewY = this._y + this._slope * multiplier * this._dirx;
// _MinchinWeb_Log_.Note("Linewalker new : " + NewX + "," + NewY, 7);
if (AIMap.DistanceManhattan(this._current_tile, AIMap.GetTileIndex(NewX.tointeger(), NewY.tointeger())) == 1 ) {
this._current_tile = AIMap.GetTileIndex(NewX.tointeger(), NewY.tointeger());
} else if (AIMap.DistanceManhattan(this._current_tile, AIMap.GetTileIndex(NewX.tointeger(), this._y.tointeger())) == 1 ) {
this._current_tile = AIMap.GetTileIndex(NewX.tointeger(), this._y.tointeger());
}
this._x = NewX;
this._y = NewY;
// Check that we're still within our bounding box
// _MinchinWeb_Log_.Note(" " + this._startx + " , " + this._endx + " , " + this._x.tointeger() + " , " + this._starty + " , " + this._endy + " , " + this._y.tointeger(), 7);
if (!_MinchinWeb_Extras_.WithinFloat(this._startx.tofloat(), this._endx.tofloat(), this._x) || !_MinchinWeb_Extras_.WithinFloat(this._starty.tofloat(), this._endy.tofloat(), this._y)) {
// _MinchinWeb_Log_.Note("Linewalker outside box " + this._startx + " " + this._endx + " " + this._x + " " + _MinchinWeb_Extras_.WithinFloat(this._startx.tofloat(), this._endx.tofloat(), this._x) + " : " + this._starty + " " + this._endy + " " + this._y + " " + (_MinchinWeb_Extras_.WithinFloat(this._starty.tofloat(), this._endy.tofloat(), this._y)), 6);
this._past_end = true;
return this._current_tile;
} else {
// _MinchinWeb_Log_.Note("Linewalker output " + AIMap.GetTileX(this._current_tile) + "," + AIMap.GetTileY(this._current_tile), 6);
return this._current_tile;
}
}
// EOF
MetaLib-7/Log.nut 0000666 0000000 0000000 00000015053 12304177551 012153 0 ustar 0000000 0000000 /* Logging Interface v.4, [2014-02-14]
* part of MinchinWeb's MetaLibrary v.4,
* originally part of WmDOT v.5
* Copyright © 2011-14 by W. Minchin. For more info,
* please visit https://github.com/MinchinWeb/openttd-metalibrary
*
* Permission is granted to you to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell this software, and provide these
* rights to others, provided:
*
* + The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the software.
* + Attribution is provided in the normal place for recognition of 3rd party
* contributions.
* + You accept that this software is provided to you "as is", without warranty.
*/
/** \brief Logging Interface
* \version v.4 (2014-02-14)
* \author W. Minchin (%MinchinWeb)
* \since MetaLibrary v.4
*
* To make use of this library, add this you the `info.nut` file of your AI:
*
* ~~~
function GetSettings() {
AddSetting({name = "Debug_Level", description = "Debug Level ", min_value = 0, max_value = 8, easy_value = 3, medium_value = 3, hard_value = 3, custom_value = 3, flags = CONFIG_INGAME});
}
* ~~~
*
* This will add an option to your AI allowing users to control the debug output
* from your AI. You can allow this setting to be configured in-game, as it is
* read each time the class is called.
*
* \note There is no requirement that you use the Logging interface in your
* AI if you make use of MetaLibrary. You just will not see any
* debugging output from the library.
* \note This class will work equally well as a static or a non-static class,
* as Debug Level is determined at each call.
*/
class _MinchinWeb_Log_ {
/** \publicsection
* \fn GetVersion()
* \return current version of the Logging Interface
* \static
* \fn GetDate()
* \return the date of the last update to the Logging Interface
* \static
* \fn GetName()
* \return the name of the Logging Interface
* \static
* \fn GetRevision()
* \return The revision (as per svn) of the last update to the Logging
* Interface.
* \static
* \note This has been changed with the move to git. On files updated
* since the move to git, this will be of the form YYMMDD.
*/
function GetVersion() { return 4; };
function GetRevision() { return 140214; };
function GetDate() { return "2014-02-14"; };
function GetName() { return "Logging Interface"; };
/** \privatesection
* \var _DebugLevel
* Used to hold the current debug level.
*
* Messages with a debug level less than or equal to the current debug
* level will be printed to the AI debug screen (or a sign placed, as
* appropriate). Others will be silently ignored.
*
* The debug level is not designed to be set directly by the AI.
*
* This is how I have 'translated' the various levels 1 through 7 on the
* AI settings to what I would expect to see on the debug screen.
*
* - AI's
* - 0 - run silently
* - 1 - 'Operations' noted here
* - 2 - 'normal' debugging - each step
* - 3 - substeps
* - 4 - most verbose (including arrays)
* - 5 - including signs (but generally nothing more from the AI to
* the debug screen)
* - Libraries
* - 5 - basic
* - 6 - verbose
* - 7 - signs
* - 8 - signs for library subfunctions
*
* Every level beyond 1 is indented 5 spaces per higher level.
*/
_DebugLevel = null;
/** \private
* Does nothing
*/
function constructor() { };
/** \publicsection
* \brief Output messages to the AI debug screen.
* Displays the message if the Debug level is set high enough.
*
* Can be used as a replacement for `AILog.Info()`
* \param Message message to print to AI debug screen
* \param Level required minimum level to print message (default is 3)
* \static
*/
function Note(Message, Level = 3);
/** \public
* \brief Output warnings to the AI debug screen.
*
* Displays the message as a Warning (in yellow text).
*
* Can be used as a replacement for `AILog.Warning()`
* \param Message message to print to AI debug screen
* \static
*/
function Warning(Message) { AILog.Warning(Message); };
/** \public
* \brief Output errors to the AI debug screen.
*
* Displays the message as an Error (in red text).
*
* Can be used as a replacement for `AILog.Error()`.
* If not captured, errors (including calling this function) will crash
* your AI.
* \param Message message to print to AI debug screen
* \static
*/
function Error(Message) { AILog.Error(Message); };
/** \public
* \brief Prints a message on a sign.
*
* \param Tile tile to place the sign on (as an `AITile` object)
* \param Message message to print on the sign
* \param Level required minimum level to place tile (default is 5)
* \static
*/
function Sign(Tile, Message, Level = 5);
/** \public
* \brief Prints the current debug level to the AI debug screen.
* \return nothing
* \static
*/
function PrintDebugLevel();
/** \public
* \brief Looks for an AI setting for Debug Level, and set the debug level to that.
*
* This is a bit of a failsafe. If there is no AI setting for the debug
* level, then a default of 3 is used.
*
* This function will not typically need to be called directly.
* \return current debug level, as per AI setting.
* \see _DebugLevel
* \static
*/
function UpdateDebugLevel() {
local DebugLevel = 3;
if (AIController.GetSetting("Debug_Level") != -1) {
DebugLevel = AIController.GetSetting("Debug_Level");
}
return DebugLevel;
};
};
// == Function definition =================================================
function _MinchinWeb_Log_::Note(Message, Level = 3) {
if (Level <= _MinchinWeb_Log_.UpdateDebugLevel() ) {
local i = 1;
while (i < Level) {
Message = " " + Message;
Level--;
}
AILog.Info(Message);
}
}
function _MinchinWeb_Log_::Sign(Tile, Message, Level = 5) {
if (Level <= _MinchinWeb_Log_.UpdateDebugLevel() ) {
AISign.BuildSign(Tile, Message);
}
}
function _MinchinWeb_Log_::PrintDebugLevel() {
try {
AILog.Info("OpLog is running at level " + AIController.GetSetting("Debug_Level") + ".");
} catch (all) {
AILog.Warning("No AI setting 'Debug_Level'");
}
}
function _MinchinWeb_Log_::UpdateDebugLevel() {
local DebugLevel = 3;
if (AIController.GetSetting("Debug_Level") != -1) {
DebugLevel = AIController.GetSetting("Debug_Level");
}
return DebugLevel;
}
// EOF
MetaLib-7/main.nut 0000666 0000000 0000000 00000010055 12304426440 012345 0 ustar 0000000 0000000 /* Minchinweb's MetaLibrary v.7 [2014-02-28],
* originally part of, WmDOT v.10
* Copyright © 2011-14 by W. Minchin. For more info,
* please visit https://github.com/MinchinWeb/openttd-metalibrary
*
* Permission is granted to you to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell this software, and provide these
* rights to others, provided:
*
* + The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the software.
* + Attribution is provided in the normal place for recognition of 3rd party
* contributions.
* + You accept that this software is provided to you "as is", without warranty.
*/
/* See the README for a list of the functions included in this library.
*/
require("Pathfinder.Road.nut");
// Requires Graph.AyStar v6 library
// require("AyStar.WM.nut");
require("Array.nut");
// require("Fibonacci.Heap.WM.nut");
require("Extras.nut");
require("Constants.nut");
require("Waterbody.Check.nut");
require("Lakes.nut");
require("Pathfinder.Ship.nut");
require("Line.Walker.nut");
require("Spiral.Walker.nut");
require("Atlas.nut");
require("Marine.nut");
require("Log.nut");
require("Dominion.Roads.nut");
require("Industry.nut");
require("Station.nut");
/** \brief Main Library Class
*
* This is the main class of the Library. It will be renamed on importing the
* library into your AI.
*
* Import("util.MinchinWeb", "[your_access_name]", 7);
*
* (Don't really use `[your_access_name]`, use something that is easy enough
* to type and will remind you of where the functions are coming from. I like
* to use `%MinchinWeb`.)
*
* Using imported name for the library, you can then access the various
* sublibraries. For example:
* - `%MinchinWeb.Atlas` <- \_MinchinWeb\_Atlas\_
* - `%MinchinWeb.Array` <- \_MinchinWeb\_Array\_
* - `%MinchinWeb.Constants` <- \_MinchinWeb\_C\_
* - `%MinchinWeb.DLS` <- \_MinchinWeb\_DLS\_
* - `%MinchinWeb.Extras` <- \_MinchinWeb\_Extras\_
* - `%MinchinWeb.Lakes` <- \_MinchinWeb\_Lakes\_
* - `%MinchinWeb.LineWalker` <- \_MinchinWeb\_LW\_
* - `%MinchinWeb.Log` <- \_MinchinWeb\_Log\_
* - `%MinchinWeb.Industry` <- \_MinchinWeb\_Industry\_
* - `%MinchinWeb.Marine` <- \_MinchinWeb\_Marine\_
* - `%MinchinWeb.ShipPathfinder` <- \_MinchinWeb\_ShipPathfinder\_
* - `%MinchinWeb.SpiralWalker` <- \_MinchinWeb\_SW\_
* - `%MinchinWeb.Station` <- \_MinchinWeb\_Station\_
* - `%MinchinWeb.RoadPathfinder` <- \_MinchinWeb\_RoadPathfinder\_
* - `%MinchinWeb.WaterbodyCheck` <- \_MinchinWeb\_WBC\_
*/
class MinchinWeb {
/** \publicsection
*/
function GetVersion() { return 7; }
function GetRevision() { return 140228; }
function GetDate() { return "2014-02-28"; }
function GetName() { return "MinchinWeb's MetaLibrary"; }
static RoadPathfinder = _MinchinWeb_RoadPathfinder_;
///< \see \_MinchinWeb\_RoadPathfinder\_
static ShipPathfinder = _MinchinWeb_ShipPathfinder_;
///< \see \_MinchinWeb\_ShipPathfinder\_
static Array = _MinchinWeb_Array_;
///< \see \_MinchinWeb\_Array\_
static Extras = _MinchinWeb_Extras_;
///< \see \_MinchinWeb\_Extras\_
static WaterbodyCheck = _MinchinWeb_WBC_;
///< \see \_MinchinWeb\_WBC\_
static LineWalker = _MinchinWeb_LW_;
///< \see \_MinchinWeb\_LW\_
static SpiralWalker = _MinchinWeb_SW_;
///< \see \_MinchinWeb\_SW\_
static Constants = _MinchinWeb_C_;
///< \see \_MinchinWeb\_C\_
// in Constants.nut
static Atlas = _MinchinWeb_Atlas_;
///< \see \_MinchinWeb\_Atlas\_
static Marine = _MinchinWeb_Marine_;
///< \see \_MinchinWeb\_Marine\_
static Industry = _MinchinWeb_Industry_;
///< \see \_MinchinWeb\_Industry\_
static Station = _MinchinWeb_Station_;
///< \see \_MinchinWeb\_Station\_
static Log = _MinchinWeb_Log_;
///< \see \_MinchinWeb\_Log\_
static DLS = _MinchinWeb_DLS_;
///< \see \_MinchinWeb\_DLS\_
// in Dominion.Roads.nut
static Lakes = _MinchinWeb_Lakes_;
///< \see \_MinchinWeb\_Lakes\_
};
// EOF
MetaLib-7/Marine.nut 0000666 0000000 0000000 00000042257 12304421276 012647 0 ustar 0000000 0000000 /* Ship and Marine functions v.3 r.242 [2012-06-23],
* part of Minchinweb's MetaLibrary v.5,
* originally part of WmDOT v.10
* Copyright © 2011-12 by W. Minchin. For more info,
* please visit https://github.com/MinchinWeb/openttd-metalibrary
*
* Permission is granted to you to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell this software, and provide these
* rights to others, provided:
*
* + The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the software.
* + Attribution is provided in the normal place for recognition of 3rd party
* contributions.
* + You accept that this software is provided to you "as is", without warranty.
*/
/** \brief Water and Ship related functions.
* \version v.3 (2012-06-23)
* \author W. Minchin (%MinchinWeb)
* \since MetaLibrary v.2
* \see \_MinchinWeb\_ShipPathfinder\_
*/
/*
* MinchinWeb.Marine.DistanceShip(TileA, TileB)
* - Assuming open ocean, ship in OpenTTD will travel
* 45° angle where possible, and then finish up the
* trip by going along a cardinal direction
* .GetPossibleDockTiles(IndustryID)
* - Given an industry (by IndustryID), searches for
* possible tiles to build a dock and retruns the
* list as an array of TileIndexs
* - Tiles given should be checked to ensure that the
* desired cargo is still accepted
* .GetDockFrontTiles(Tile)
* - Given a tile, returns an array of possible 'front'
* tiles that a ship could access the dock from
* - Can be either the land tile of a dock, or the
* water tile
* - Does not test if there is currently a dock at the
* tile
* - Might do funny things if the tile given is next to
* a river (i.e. a flat tile next to a water tile)
* .BuildBuoy(Tile)
* - Attempts to build a buoy, but first checks the box
* within MinchinWeb.Constants.BuoyOffset() for an
* existing buoy, and makes sure there's nothing
* but water between the two. If no existing buoy
* is found, one is built.
* - Returns the location of the existing or built bouy.
* - This will fail if the Tile given is a dock (or
* any tile that is not a water tile)
* .BuildDepot(DockTile, Front)
* - Attempts to build a (water) depot, but first checks
* the box within Constants.WaterDepotOffset() for
* an existing depot, and makes sure there's nothing
* but water between the depot and dock. If no
* existing depot is found, one is built.
* - Returns the location of the existing or built depot.
* - This will fail if the DockTile given is a dock (or
* any tile that is not a water tile)
* .RateShips(EngineID, Life, Cargo)
* - Designed to Run as a validator
* - Given the EngineID, it will score them; higher is better
* - Life is assumed to be in years
* - Note: Cargo doesn't work yet. Capacity is measured in
* the default cargo.
* .NearestDepot(TileID)
* - Returns the tile of the Ship Depot nearest to the
* given TileID
*
* See also MinchinWeb.ShipPathfinder
*/
class _MinchinWeb_Marine_ {
main = null;
/** \publicsection
* \brief Distance, as measured by a ship.
*
* Assuming open ocean, ship in OpenTTD will travel 45° angle where
* possible, and then finish up the trip by going along a cardinal
* direction.
* \static
*/
function DistanceShip(TileA, TileB);
/** \brief Tiles where a dock can be built near an industry.
*
* Given an industry (by IndustryID), searches for possible tiles to build
* a dock and returns the list as an array of TileIndexs.
* \note Tiles returned should be checked to ensure that the desired
* cargo is still accepted.
* \note Assumes that the industry location returned is the NE corner of
* the industry, and that industries fit within a 4x4 block.
* \param IndustryID The IndustryID you wanted checked.
* \return An array of tiles that a dock could be built on near the
* industry.
* \return If the industry has a built-in dock, that tile will be included
* in the tiles returned.
* \static
*/
function GetPossibleDockTiles(IndustryID);
/** \brief The tiles a ship can access a dock from.
*
* Given a tile, returns an array of possible 'front' tiles that a ship
* could access the dock from.
* \param Tile Can be either the land tile of a dock, or the water
* tile.
* \note Does not test if there is currently a dock at the tile.
*
* \note Might do funny things if the tile given is next to a river
* (i.e. a flat tile next to a water tile).
* \static
*/
function GetDockFrontTiles(Tile);
/** \brief Builds a buoy.
*
* Attempts to build a buoy, but first checks the box within
* \_MinchinWeb\_C\_.BuoyOffset() for an existing buoy, and makes sure
* there's nothing but water between the two. If no existing buoy is found,
* one is built.
* \return The location of the existing or built buoy.
* \static
*/
function BuildBuoy(Tile);
/** \brief Build a (ship) depot next to a dock.
*
* Attempts to build a (water) depot, but first checks the box within
* \_MinchinWeb\_C\_.WaterDepotOffset() for an existing depot, and makes
* sure there's nothing but water between the depot and dock. If no
* existing depot is found, one is built.
* \param DockTile Must be a water tile.
* \param NotNextToDock When `True`, will keep the dock from being built
* next to an existing dock.
* \note This will fail if the `DockTile` given is a dock (or any tile
* that is not a water tile).
* \return The location of the existing or built depot.
* \todo Check documentation of parameters.
* \static
*/
function BuildDepot(DockTile, Front, NotNextToDock=true);
/** \brief Ship Scoring
*
* Given an EngineID, the function will score them; higher is better.
* \param Life Desired lifespan of route, assumed to be in years.
* \param Cargo Doesn't work yet. Capacity is measured in the default
* cargo.
* \note Designed to run as a valuator on a AIList of EngineID's.
* \todo Add example of validator code.
* \todo Implement ship capacity in given cargo.
* \static
*/
function RateShips(EngineID, Life, Cargo);
/** \brief Nearest ship depot.
* \return The tile of the Ship Depot nearest to the given TileID
* \todo Add check that depot is connected to the given TileID.
* \todo Check that there is a depot to return.
* \static
*/
function NearestDepot(TileID);
};
// == Function definitions ================================================
function _MinchinWeb_Marine_::DistanceShip(TileA, TileB) {
// Assuming open ocean, ship in OpenTTD will travel 45° angle where possible,
// and then finish up the trip by going along a cardinal direction
return ((AIMap.DistanceManhattan(TileA, TileB) - AIMap.DistanceMax(TileA, TileB)) * 0.4 + AIMap.DistanceMax(TileA, TileB)).tointeger();
}
function _MinchinWeb_Marine_::GetPossibleDockTiles(IndustryID) {
// Given an industry (by IndustryID), searches for possible tiles to build a
// dock and returns the list as an array of TileIndexs
// Tiles given should be checked to ensure that the desired cargo is still
// accepted
// Assumes that the industry location returned is the NE corner of the
// industry, and that industries fit within a 4x4 block
local Tiles = [];
if (AIIndustry.IsValidIndustry(IndustryID) == true) {
// Check if the industry already has a dock
if (AIIndustry.HasDock(IndustryID) == true) {
return [AIIndustry.GetDockLocation(IndustryID)];
} else {
local ex = AITestMode();
local Walker = _MinchinWeb_SW_(); // Spiral Walker
Walker.Start(AIIndustry.GetLocation(IndustryID));
while (Walker.GetStage() <= ((_MinchinWeb_C_.IndustrySize() + AIStation.GetCoverageRadius(AIStation.STATION_DOCK)) * 4)) {
if (AIMarine.BuildDock(Walker.Walk(), AIStation.STATION_NEW) == true) {
Tiles.push(Walker.GetTile());
}
}
}
_MinchinWeb_Log_.Note("MinchinWeb.Marine.GetPossibleDockTiles() " + _MinchinWeb_Array_.ToStringTiles1D(Tiles, true), 6);
return Tiles;
} else {
AILog.Warning("MinchinWeb.Marine.GetPossibleDockTiles() was supplied with an invalid IndustryID. Was supplied " + IndustryID + ".");
return Tiles;
}
}
function _MinchinWeb_Marine_::GetDockFrontTiles(Tile) {
// Given a tile, returns an array of possible 'front' tiles that a ship could
// access the dock from
// Can be either the land tile of a dock, or the water tile
// Does not test if there is currently a dock at the tile
// Tiles under Oil Rigs do NOT return AITile.IsWaterTile(Tile) == true
// Might do funny things if the tile given is next to a river (i.e. a flat tile
// next to a water tile)
local ReturnTiles = [];
local offset = AIMap.GetTileIndex(0, 0);;
local DockEnd = null;
local offsets = [AIMap.GetTileIndex(0, 1), AIMap.GetTileIndex(0, -1),
AIMap.GetTileIndex(1, 0), AIMap.GetTileIndex(-1, 0)];
local next_tile;
if (AIMap.IsValidTile(Tile)) {
if (AITile.IsWaterTile(Tile)) {
// water tile
DockEnd = Tile;
} else {
// land tile
switch (AITile.GetSlope(Tile)) {
// see http://vcs.openttd.org/svn/browser/trunk/docs/tileh.png
// for slopes
case 0:
// flat
offset = AIMap.GetTileIndex(0, 0);
break;
case 3:
offset = AIMap.GetTileIndex(-1, 0);
break;
case 6:
offset = AIMap.GetTileIndex(0, -1);
break;
case 9:
offset = AIMap.GetTileIndex(0, 1);
break;
case 12:
offset = AIMap.GetTileIndex(1, 0);
break;
}
DockEnd = Tile + offset;
}
if (DockEnd != null) {
/* Check all tiles adjacent to the current tile. */
foreach (offset in offsets) {
next_tile = DockEnd + offset;
if (AITile.IsWaterTile(next_tile)) {
ReturnTiles.push(next_tile);
}
}
}
} else {
AILog.Warning("MinchinWeb.Marine.GetDockFrontTiles() was supplied with an invalid TileIndex. Was supplied " + Tile + ".");
}
return ReturnTiles;
}
function _MinchinWeb_Marine_::BuildBuoy(Tile) {
// Attempts to build a buoy, but first checks the box within
// MinchinWeb.Constants.BuoyOffset() for an existing buoy, and makes sure
// there's nothing but water between the two. If no existing buoy is found,
// one is built.
// Returns the location of the existing or built buoy.
local Existing = AITileList();
local UseExistingAt = null;
local Walker = _MinchinWeb_SW_(); // Spiral Walker
Walker.Start(Tile);
while (Walker.GetStage() <= (_MinchinWeb_C_.BuoyOffset() * 4)) {
if (AIMarine.IsBuoyTile(Walker.Walk())) {
Existing.AddItem(Walker.GetTile(), AIMap.DistanceManhattan(Tile, Walker.GetTile()));
_MinchinWeb_Log_.Note("BuildBuoy() : Insert Existing at" + _MinchinWeb_Array_.ToStringTiles1D([Walker.GetTile()]), 7);
}
}
if (Existing.Count() > 0) {
Existing.Sort(AIList.SORT_BY_VALUE, AIList.SORT_ASCENDING);
local TestBuoy = Existing.Begin();
local KeepTrying = true;
local WBC = _MinchinWeb_WBC_();
while (KeepTrying == true) {
WBC.InitializePath([TestBuoy], [Tile]);
WBC.PresetSafety(TestBuoy, Tile);
local WBCResults = WBC.FindPath(-1);
if (WBCResults != null) {
UseExistingAt = TestBuoy;
KeepTrying = false;
} else {
if (Existing.IsEnd()) {
KeepTrying = false;
UseExistingAt = null;
} else {
TestBuoy = Existing.Next();
}
}
}
}
if (UseExistingAt == null) {
AIMarine.BuildBuoy(Tile);
return Tile;
} else {
return UseExistingAt;
}
}
function _MinchinWeb_Marine_::BuildDepot(DockTile, Front, NotNextToDock=true) {
// Attempts to build a (water) depot, but first checks the box within
// MinchinWeb.Constants.WaterDepotOffset() for an existing depot, and makes
// sure there's nothing but water between the depot and dock. If no
// existing depot is found, one is built.
// Returns the location of the existing or built depot.
// This will fail if the DockTile given is a dock (or any tile that is not a water tile)
// 'NotNextToDock,' when set, will keep the dock from being built next to an
// existing dock
local StartX = AIMap.GetTileX(DockTile) - _MinchinWeb_C_.WaterDepotOffset();
local StartY = AIMap.GetTileY(DockTile) - _MinchinWeb_C_.WaterDepotOffset();
local EndX = AIMap.GetTileX(DockTile) + _MinchinWeb_C_.WaterDepotOffset();
local EndY = AIMap.GetTileY(DockTile) + _MinchinWeb_C_.WaterDepotOffset();
local Existing = AITileList();
local UseExistingAt = null;
for (local i = StartX; i < EndX; i++) {
for (local j = StartY; j < EndY; j++) {
if (AIMarine.IsWaterDepotTile(AIMap.GetTileIndex(i,j))) {
Existing.AddItem(AIMap.GetTileIndex(i,j), AIMap.DistanceManhattan(DockTile, AIMap.GetTileIndex(i,j)));
_MinchinWeb_Log_.Note("BuildDepot() : Insert Existing at" + _MinchinWeb_Array_.ToStringTiles1D([AIMap.GetTileIndex(i,j)]), 6);
}
}
}
if (Existing.Count() > 0) {
Existing.Sort(AIList.SORT_BY_VALUE, AIList.SORT_ASCENDING);
local TestDepot = Existing.Begin();
local KeepTrying = true;
local WBC = _MinchinWeb_WBC_();
while (KeepTrying == true) {
WBC.InitializePath([TestDepot], [DockTile]);
WBC.PresetSafety(TestDepot, DockTile);
local WBCResults = WBC.FindPath(-1);
if (WBCResults != null) {
UseExistingAt = TestDepot;
KeepTrying = false;
} else {
if (Existing.IsEnd()) {
KeepTrying = false;
UseExistingAt = null;
} else {
TestDepot = Existing.Next();
}
}
}
}
if (UseExistingAt == null) {
if(AIMarine.BuildWaterDepot(DockTile, Front)) {
// try and build right at the given spot
UseExistingAt = DockTile;
} else {
// if that doesn't work, build it close by
// Generate a list of water tiles, and pick one at random
Existing.Clear();
for (local i = StartX; i < EndX; i++) {
for (local j = StartY; j < EndY; j++) {
if (AITile.IsWaterTile(AIMap.GetTileIndex(i,j)) && (_MinchinWeb_Station_.IsNextToDock(AIMap.GetTileIndex(i,j)) == false) ) {
Existing.AddItem(AIMap.GetTileIndex(i,j), AIBase.Rand());
_MinchinWeb_Log_.Note("BuildDepot() : Insert WaterTile at" + _MinchinWeb_Array_.ToStringTiles1D([AIMap.GetTileIndex(i,j)]), 7);
}
}
}
Existing.Sort(AIList.SORT_BY_VALUE, AIList.SORT_ASCENDING);
local TestDepot = Existing.Begin();
local KeepTrying = true;
local WBC = _MinchinWeb_WBC_();
while (KeepTrying == true) {
WBC.InitializePath([TestDepot], [DockTile]);
WBC.PresetSafety(TestDepot, DockTile);
local WBCResults = WBC.FindPath(-1);
_MinchinWeb_Log_.Note("BuildDepot() : WBC on" + _MinchinWeb_Array_.ToStringTiles1D([TestDepot, DockTile]) + " returned " + WBCResults, 7);
if (WBCResults != null) {
local Front2 = _MinchinWeb_Extras_.NextCardinalTile(TestDepot, DockTile);
if (AIMarine.BuildWaterDepot(TestDepot, Front2))
{
UseExistingAt = TestDepot;
KeepTrying = false;
} else {
if (Existing.IsEnd()) {
KeepTrying = false;
UseExistingAt = null;
} else {
TestDepot = Existing.Next();
}
}
} else {
if (Existing.IsEnd()) {
KeepTrying = false;
UseExistingAt = null;
} else {
TestDepot = Existing.Next();
}
}
}
}
}
return UseExistingAt;
}
function _MinchinWeb_Marine_::RateShips(EngineID, Life, Cargo) {
// Designed to Run as a validator
// Given the EngineID, it will score them; higher is better
// Score = [(Capacity in Cargo)*Reliability*Speed] /
// [ (Purchase Price over Life) + (Running Costs)*Life ]
//
// Life is assumed to be in years
// Note: Cargo doesn't work yet. Capacity is measured in the default cargo.
local Score = 0;
local Age = AIEngine.GetMaxAge(EngineID);
local BuyTimes = (Life / Age/366).tointeger() + 1;;
// GetMaxAge is given in days
local Cost = (BuyTimes * AIEngine.GetPrice(EngineID)) + (Life * AIEngine.GetRunningCost(EngineID)) + 0.001;
local Return = (AIEngine.GetCapacity(EngineID) * AIEngine.GetReliability(EngineID) * AIEngine.GetMaxSpeed(EngineID)) + 0.001;
if (Return == 0) {
Score = 0;
} else {
Score = (Return * 1000 / Cost).tointeger();
}
_MinchinWeb_Log_.Note("Rate Ship : " + Score + " : " +AIEngine.GetName(EngineID) + " : " + AIEngine.GetCapacity(EngineID) + " * " + AIEngine.GetReliability(EngineID) + " * " + AIEngine.GetMaxSpeed(EngineID) + " / " + BuyTimes + " * " + AIEngine.GetPrice(EngineID) + " + " + Life + " * " + AIEngine.GetRunningCost(EngineID), 7);
return Score;
}
function _MinchinWeb_Marine_::NearestDepot(TileID) {
// Returns the tile of the Ship Depot nearest to the given TileID
// To-Do: Add check that depot is connected to tile
// To-Do: Check that there is a depot to return
local AllDepots = AIDepotList(AITile.TRANSPORT_WATER);
AllDepots.Valuate(AITile.GetDistanceManhattanToTile, TileID);
AllDepots.Sort(AIList.SORT_BY_VALUE, AIList.SORT_ASCENDING);
return AllDepots.Begin();
}
// EOF
MetaLib-7/Pathfinder.Road.nut 0000666 0000000 0000000 00000121130 12304430261 014362 0 ustar 0000000 0000000 /* RoadPathfinder v.9 [2012-12-28],
* part of Minchinweb's MetaLibrary v.5,
* originally part of WmDOT v.4 r.50 [2011-04-06]
* Copyright © 2011-14 by W. Minchin. For more info,
* please visit https://github.com/MinchinWeb/openttd-metalibrary
*/
/* $Id: main.nut 15101 2009-01-16 00:05:26Z truebrain $ */
/** \note This file is licensed under the original license -- **LGPL v2.1** --
* and is based on the NoAI Team's Road Pathfinder v3.
*
*
* \brief A Road Pathfinder (and extras)
* \version v.9 (2012-12-28)
* \author NoAI Team
* \author W. Minchin (%MinchinWeb)
* \since MetaLibrary v.1
*
* 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 `roadpf.cost.only_existing_road =
* True`.
*
* The pathfinder has been extended to provide 'presets' for configuration,
* store the found path, and build the found path.
*
* \requires Graph.AyStar v6
* \see \\_MinchinWeb\\_DLS\\_
* \see \\_MinchinWeb\\_ShipPathfinder\\_
*/
/* This file provides functions:
MinchinWeb.RoadPathfinder.InitializePath(sources, goals)
Set up the pathfinder
MinchinWeb.RoadPathfinder.FindPath(iterations)
Run the pathfinder; returns false if it isn't finished the path
if it has finished, and null if it can't find a path
MinchinWeb.RoadPathfinder.cost.[xx]
Allows you to set or find out the pathfinder costs directly.
See the function below for valid entries
MinchinWeb.RoadPathfinder.Info.GetVersion()
.GetMinorVersion()
.GetRevision()
.GetDate()
.GetName()
Useful for check provided version or debugging screen output
MinchinWeb.RoadPathfinder.PresetOriginal()
.PresetPerfectPath()
.PresetQuickAndDirty()
.PresetCheckExisting()
.PresetMode6()
.PresetStreetcar()
Presets for the pathfinder parameters
MinchinWeb.RoadPathfinder.GetBuildCost() // How much would it be to build the path?
MinchinWeb.RoadPathfinder.BuildPath() // Build the path
MinchinWeb.RoadPathfinder.GetPathLength() // How long is the path?
MinchinWeb.RoadPathfinder.LoadPath(Path) // Provide your own path
MinchinWeb.RoadPathfinder.GetPath() // Returns the path as stored by the pathfinder
MinchinWeb.RoadPathfinder.InitializePathOnTowns(StartTown, EndTown)
Initializes the pathfinder using the seed tiles to the given towns
MinchinWeb.RoadPathfinder.PathToTilePairs()
Returns a 2D array that has each pair of tiles that path joins
MinchinWeb.RoadPathfinder.TilesPairsToBuild()
Similar to PathToTilePairs(), but only returns those pairs
where there isn't a current road connection
*/
/** \todo upgrade slow bridges along path
* \todo convert existing level crossings (road/rail) to road bridge
* \todo do something about one-way roads - build a pair? route around?
*
* if(AIRoad.AreRoadTilesConnected(new_tile, prev_tile) &&
* !AIRoad.AreRoadTilesConnected(prev_tile, new_tile))
* \todo allow pre-building of tunnels and bridges
* \todo Add example usage code.
*/
class _MinchinWeb_RoadPathfinder_ {
_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_level_crossing = null; ///< the extra cost for rail/road level crossings.
_cost_drivethru_station = null; ///< The extra cost for drive-thru road stations.
_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.
_cost_only_existing_roads = null; ///< Choose whether to only search through existing connected roads
_distance_penalty = null; ///< Penalty to use to speed up pathfinder, 1 is no penalty
_road_type = null;
cost = null; ///< Used to change the costs.
_mypath = null; ///< Used to store the path after it's been found for Building functions
_running = null;
info = null;
// presets = null;
constructor() {
this._max_cost = 10000000;
this._cost_tile = 100;
this._cost_no_existing_road = 40;
this._cost_turn = 100;
this._cost_slope = 200;
this._cost_bridge_per_tile = 150;
this._cost_tunnel_per_tile = 120;
this._cost_coast = 20;
this._cost_level_crossing = 0;
this._cost_drivethru_station = 0;
this._max_bridge_length = 10;
this._max_tunnel_length = 20;
this._cost_only_existing_roads = false;
this._pathfinder = this._aystar_class(this, this._Cost, this._Estimate, this._Neighbours, this._CheckDirection);
this._distance_penalty = 1;
this._road_type = AIRoad.ROADTYPE_ROAD;
this._mypath = null;
this.cost = this.Cost(this);
this.info = this.Info(this);
// this.presets = this.Presets(this);
this._running = false;
}
/**
* \publicsection
* \brief Initialize a path search between sources and goals.
* @param sources The source tiles.
* @param goals The target tiles.
* @see AyStar::InitializePath()
* \see InitializePathOnTowns()
*/
function InitializePath(sources, goals) {
local nsources = [];
foreach (node in sources) {
nsources.push([node, 0xFF]);
}
this._pathfinder.InitializePath(nsources, goals);
this._mypath = null;
}
/**
* \brief 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 immediately 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);
/** \brief The settings in the original (v3) pathfinder by NoAI Team.
*/
function PresetOriginal();
/** \brief Good preset for reusing existing roads.
*
* My slightly updated version of PresetOriginal().
*/
function PresetPerfectPath();
/** \brief Quick but messy preset.
*
* Runs in as little as 5% of the time of PresetPerfectPath(), but builds
* odd bridges and loops.
*/
function PresetQuickAndDirty();
/** \brief Preset that only uses existing roads.
*
* Based on PerfectPath, but uses only existing roads. Useful for checking
* if there an existing route and how long it is.
*/
function PresetCheckExisting();
/** \brief Reserved.
*
* Reserved preset for future use for intraurban tram lines.
*/
function PresetStreetcar();
/** \brief Cost to build found path.
*
* Turns to 'test mode,' builds the route provided, and returns the cost.
* \note All money for AI's is in British Pounds.
* \note Due to inflation, this value can get stale
* \return Cost to build path
* \return `False` if the test build fails somewhere
*/
function GetBuildCost();
/** \brief Build the found path.
* \see BuildPath()
*/
function BuildPath();
/** \brief Load an existing path.
*
* 'Loads' a path to allow GetBuildCost(), BuildPath() and GetPathLength()
* to be used.
* \see GetBuildCost()
* \see FindPath()
*/
function LoadPath();
/** \brief Export the found path.
* \return The path stored by the pathfinder
* \see GetPath()
* \see BuildPath()
*/
function GetPath();
/** \brief Get the length of the found path.
*
* Runs over the path to determine its length.
* \return The length of the path in tiles.
* \see LoadPath()
*/
function GetPathLength();
/** \brief Initializes the pathfinder using two towns.
* \note Assumes that the town centres are road tiles. If this is not the
* case, the pathfinder will still run, but it will take a long
* time and eventually fail to return a path. This is generally not
* an issue because on map creation the centre of town will be a
* road tile.
* \see InitializePath()
*/
function InitializePathOnTowns();
/** \brief Get the found path as tile pairs.
* \return 2D array that has each pair of tiles that path joins.
* \see TilePairsToBuild()
* \see PathToTiles()
*/
function PathToTilePairs();
/** \brief Get a list of all the tiles in the path.
* \return 1D array that has each pair of tiles that path covers.
* \see PathToTilePairs()
*/
function PathToTiles();
/** \brief Tiles in the path that need to be built.
*
* Similar to PathToTilePairs(), but only returns those pairs where there
* isn't a current road connection.
* \return 2D array that has each pair of tiles that path joins that are
* not currently joined by road.
* \see BuildPath()
*/
function TilePairsToBuild();
/** \privatesection
*/
function _GetBridgeNumSlopes(end_a, end_b);
function _Cost(self, path, new_tile, new_direction);
function _Estimate(self, cur_tile, cur_direction, goal_tiles);
function _Neighbours(self, path, cur_node);
function _CheckDirection(self, tile, existing_direction, new_direction);
function _GetDirection(from, to, is_bridge);
function _GetTunnelsBridges(last_node, cur_node, bridge_dir);
function _IsSlopedRoad(start, middle, end);
function _CheckTunnelBridge(current_tile, new_tile);
};
class _MinchinWeb_RoadPathfinder_.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 "level_crossing": this._main._cost_level_crossing = 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 "only_existing_roads": this._main._cost_only_existing_roads = val; break;
case "drivethru_station": this._main._cost_drivethru_station = val; break;
case "distance_penalty": this._main._distance_penalty = 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 "level_crossing": return this._main._cost_level_crossing;
case "max_bridge_length": return this._main._max_bridge_length;
case "max_tunnel_length": return this._main._max_tunnel_length;
case "only_existing_roads": return this._main._cost_only_existing_roads;
case "drivethru_station": return this._main._cost_drivethru_station;
case "distance_penalty": return this._main._distance_penalty;
default: throw("the index '" + idx + "' does not exist");
}
}
constructor(main)
{
this._main = main;
}
};
function _MinchinWeb_RoadPathfinder_::FindPath(iterations) {
local test_mode = AITestMode();
local ret = this._pathfinder.FindPath(iterations);
this._running = (ret == false) ? true : false;
if (this._running == false) { this._mypath = ret; }
return ret;
}
function _MinchinWeb_RoadPathfinder_::_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 _MinchinWeb_RoadPathfinder_::_Cost(self, path, new_tile, new_direction) {
/* 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();
/* If the new tile is (already) 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;
} else {
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;
}
}
/* 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. */
local cost = self._cost_tile;
if (path.GetParent() != null && (prev_tile - path.GetParent().GetTile()) != (new_tile - prev_tile) &&
AIMap.DistanceManhattan(path.GetParent().GetTile(), prev_tile) == 1) {
cost += self._cost_turn;
}
/* Check if the new tile is a coast tile. */
if (AITile.IsCoastTile(new_tile)) {
cost += self._cost_coast;
}
/* Check if the last tile was sloped. */
if (path.GetParent() != null && !AIBridge.IsBridgeTile(prev_tile) && !AITunnel.IsTunnelTile(prev_tile) &&
self._IsSlopedRoad(path.GetParent().GetTile(), prev_tile, new_tile)) {
cost += self._cost_slope;
}
/* Add a cost to "outcost" all paths that aren't using already existing
* roads, if that's what we're after */
if (!AIRoad.AreRoadTilesConnected(prev_tile, new_tile)) {
cost += self._cost_no_existing_road;
}
/* Add a penalty for road/rail level crossings. */
if(AITile.HasTransportType(new_tile, AITile.TRANSPORT_RAIL)) {
cost += self._cost_level_crossing;
}
/* Add a penalty for exisiting drive thru road stations */
if(AIRoad.IsDriveThroughRoadStationTile(new_tile)) {
cost += self._cost_drivethru_station;
}
return path.GetCost() + cost;
}
function _MinchinWeb_RoadPathfinder_::_Estimate(self, cur_tile, cur_direction, goal_tiles) {
local min_cost = self._max_cost;
/* As estimate we multiply the lowest possible cost for a single tile with
* with the minimum number of tiles we need to traverse. */
foreach (tile in goal_tiles) {
min_cost = min(AIMap.DistanceManhattan(cur_tile, tile) * self._cost_tile * self._distance_penalty, min_cost);
}
return min_cost;
}
function _MinchinWeb_RoadPathfinder_::_Neighbours(self, path, cur_node) {
/* self._max_cost is the maximum path cost, if we go over it, the path isn't valid. */
if (path.GetCost() >= self._max_cost) 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.IsRoadTile(next_tile)) {
tiles.push([next_tile, self._GetDirection(cur_node, next_tile, false)]);
}
/* The other end of the bridge / tunnel is a neighbour. */
tiles.push([other_end, self._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, self._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;
/* 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, self._GetDirection(cur_node, next_tile, false)]);
} else if ((self._cost_only_existing_roads != true) && (AITile.IsBuildable(next_tile) || AIRoad.IsRoadTile(next_tile)) &&
(path.GetParent() == null || AIRoad.CanBuildConnectedRoadPartsHere(cur_node, path.GetParent().GetTile(), next_tile)) &&
AIRoad.BuildRoad(cur_node, next_tile)) {
// WM - add '&& (only_existing_roads != true)' so that non-connected roads are ignored
tiles.push([next_tile, self._GetDirection(cur_node, next_tile, false)]);
} else if ((self._cost_only_existing_roads != true) && self._CheckTunnelBridge(cur_node, next_tile)) {
tiles.push([next_tile, self._GetDirection(cur_node, next_tile, false)]);
}
// Test for water (i.e. rivers or canals or rails to bridge over them
local iTile = cur_node + offset;
local BridgeLength = 2;
while (AITile.HasTransportType(iTile, AITile.TRANSPORT_RAIL) || AITile.IsWaterTile(iTile)) {
iTile += offset;
BridgeLength++;
}
// test to see if we could actually build said bridge
// TO-DO: Check to see if this test is done elsewhere...
if (BridgeLength > 2) {
// TO-DO: test for map wraparound... _SuperLib_Tile::IsStraight(tile1, tile2)
local BridgeList = AIBridgeList_Length(BridgeLength);
if ((BridgeList.Count()) > 0 && (AIBridge.BuildBridge(AIVehicle.VT_ROAD, BridgeList.Begin(), cur_node, iTile))) {
local PathCheck = path;
local PathParent = path.GetParent();
// _MinchinWeb_Log_.Note("Adding Bridge-over tile: " + _MinchinWeb_Array_.ToStringTiles1D([cur_node]) + _MinchinWeb_Array_.ToStringTiles1D([iTile]) + " . " + (self._GetDirection(iTile, cur_node, true) << 4), 7);
tiles.push([iTile, self._GetDirection(iTile, cur_node, true) << 4]);
}
}
}
if (path.GetParent() != null) {
local bridges = self._GetTunnelsBridges(path.GetParent().GetTile(), cur_node, self._GetDirection(path.GetParent().GetTile(), cur_node, true) << 4);
foreach (tile in bridges) {
tiles.push(tile);
}
}
}
return tiles;
}
function _MinchinWeb_RoadPathfinder_::_CheckDirection(self, tile, existing_direction, new_direction) {
return false;
}
function _MinchinWeb_RoadPathfinder_::_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;
// for bridges that don't have a parent tile
local direction = from - to;
if (direction > 0) {
// so direction is positive
if (direction < (AIMap.GetMapSizeX() / 2 - 1)) return 1;
else return 4;
} else {
if ((direction * -1) < (AIMap.GetMapSizeX() / 2 - 1)) return 2;
else 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 _MinchinWeb_RoadPathfinder_::_GetTunnelsBridges(last_node, cur_node, bridge_dir) {
// By rights, adding bridge over railroads and water should be added here
local slope = AITile.GetSlope(cur_node);
if (slope == AITile.SLOPE_FLAT) return [];
local tiles = [];
for (local i = 2; i < this._max_bridge_length; i++) {
local bridge_list = AIBridgeList_Length(i + 1);
local target = cur_node + i * (cur_node - last_node);
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 _MinchinWeb_RoadPathfinder_::_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 _MinchinWeb_RoadPathfinder_::_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;
}
/* These are supplementary to the Road Pathfinder itself, but will
* hopefully prove useful either directly or as a model for writing your
* own functions. They include:
* - Info class - useful for outputting the details of the library to the debug
* screen
* - Build function - used to build the path generated by the pathfinder
* - Cost function - used to determine the cost of building the path generated
* by the pathfinder
* - Length - used to determine how long the generated path is
* - Presets - a combination of settings for the pathfinder for using it in
* different circumstances
* - Original - the settings in the original (v3) pathfinder by NoAI Team
* - PerfectPath - my slightly updated version of Original. Good for
* reusing existing roads
* - Dirty - quick but messy preset. Runs in as little as 5% of the time
* of 'PerfectPath', but builds odd bridges and loops
* - ExistingCheck - based on PerfectPath, but uses only existing roads.
* Useful for checking if there an existing route and how long it is
* - Streetcar - reserved for future use for intraurban tram lines
* If you would like a preset added here, I would be happy to include it
* in future versions!
*/
class _MinchinWeb_RoadPathfinder_.Info
{
_main = null;
function GetVersion() { return 9; }
// function GetMinorVersion() { return 0; }
function GetRevision() { return 121228; }
function GetDate() { return "2012-12-28"; }
function GetName() { return "Road Pathfinder (Wm)"; }
constructor(main)
{
this._main = main;
}
}
// Presets
function _MinchinWeb_RoadPathfinder_::PresetOriginal() {
// the settings in the original (v3) pathfinder by NoAI Team
this._max_cost = 10000000;
this._cost_tile = 100;
this._cost_no_existing_road = 40;
this._cost_turn = 100;
this._cost_slope = 200;
this._cost_bridge_per_tile = 150;
this._cost_tunnel_per_tile = 120;
this._cost_coast = 20;
this._max_bridge_length = 10;
this._max_tunnel_length = 20;
this._cost_only_existing_roads = false;
this._distance_penalty = 1;
this._road_type = AIRoad.ROADTYPE_ROAD;
this._cost_level_crossing = 0;
this._cost_drivethru_station = 0;
return;
}
function _MinchinWeb_RoadPathfinder_::PresetPerfectPath() {
// my slightly updated version of Original. Good for reusing existing
// roads
this._max_cost = 100000;
this._cost_tile = 30;
this._cost_no_existing_road = 40;
this._cost_turn = 100;
this._cost_slope = 200;
this._cost_bridge_per_tile = 150;
this._cost_tunnel_per_tile = 120;
this._cost_coast = 20;
this._max_bridge_length = 10;
this._max_tunnel_length = 20;
this._cost_only_existing_roads = false;
this._distance_penalty = 1;
this._road_type = AIRoad.ROADTYPE_ROAD;
this._cost_level_crossing = 0;
this._cost_drivethru_station = 0;
return;
}
function _MinchinWeb_RoadPathfinder_::PresetQuickAndDirty() {
// quick but messy preset. Runs in as little as 5% of the time of
// 'PerfectPath', but builds odd bridges and loops
// v4 DOT
this._max_cost = 100000;
this._cost_tile = 30;
this._cost_no_existing_road = 120;
this._cost_turn = 50;
this._cost_slope = 300;
this._cost_bridge_per_tile = 200;
this._cost_tunnel_per_tile = 120;
this._cost_coast = 20;
this._max_bridge_length = 16;
this._max_tunnel_length = 10;
this._cost_only_existing_roads = false;
this._distance_penalty = 5;
this._road_type = AIRoad.ROADTYPE_ROAD;
// new for WmDOT v8
this._cost_level_crossing = 700;
this._cost_drivethru_station = 100;
return;
}
function _MinchinWeb_RoadPathfinder_::PresetCheckExisting() {
// based on PerfectPath, but uses only existing roads. Useful for checking
// if there an existing route and how long it is
this._max_cost = 100000;
this._cost_tile = 30;
this._cost_no_existing_road = 40;
this._cost_turn = 100;
this._cost_slope = 200;
this._cost_bridge_per_tile = 150;
this._cost_tunnel_per_tile = 120;
this._cost_coast = 20;
this._max_bridge_length = 9999;
this._max_tunnel_length = 9999;
this._cost_only_existing_roads = true;
this._distance_penalty = 3;
this._road_type = AIRoad.ROADTYPE_ROAD;
this._cost_level_crossing = 0;
this._cost_drivethru_station = 0;
return;
}
function _MinchinWeb_RoadPathfinder_::PresetStreetcar() {
// reserved for future use for intraurban tram lines
return;
}
function _MinchinWeb_RoadPathfinder_::GetBuildCost() {
// Turns to 'test mode,' builds the route provided, and returns the cost
// (all money for AI's is in British Pounds)
// Note that due to inflation, this value can get stale
// Returns false if the test build fails somewhere
if (this._running) {
AILog.Warning("You can't find the build costs while there's a running pathfinder.");
return false;
}
if (this._mypath == null) {
AILog.Warning("You have tried to get the build costs of a 'null' path.");
return false;
}
local BeanCounter = AIAccounting();
local TestMode = AITestMode();
local Path = this._mypath;
AIRoad.SetCurrentRoadType(this._road_type);
while (Path != null) {
local SubPath = Path.GetParent();
if (SubPath != null) {
local Node = Path.GetTile();
if (AIMap.DistanceManhattan(Path.GetTile(), SubPath.GetTile()) == 1) {
// MD == 1 == road joining the two tiles
if (!AIRoad.BuildRoad(Path.GetTile(), SubPath.GetTile())) {
// If we get here, then the road building has failed
// Possible that the road already exists
// TO-DO
// - fail the road builder if the road cannot be built and
// does not already exist
// return null;
}
} else {
// Implies that we're building either a tunnel or a bridge
if (!AIBridge.IsBridgeTile(Path.GetTile()) && !AITunnel.IsTunnelTile(Path.GetTile())) {
if (AIRoad.IsRoadTile(Path.GetTile())) {
// Original example demolishes tile if it's already a road
// tile to get around expanded roadbits.
// I don't like this approach as it could destroy
// Railway tracks/tram tracks/station
// TO-DO
// - figure out a way to do this while keeping the
// other things I've built on the tile
// (can I just remove the road?)
AITile.DemolishTile(Path.GetTile());
}
if (AITunnel.GetOtherTunnelEnd(Path.GetTile()) == SubPath.GetTile()) {
if (!AITunnel.BuildTunnel(AIVehicle.VT_ROAD, Path.GetTile())) {
// At this point, an error has occurred while
// building the tunnel.
// Fail the pathfinder
// return null;
AILog.Warning("MinchinWeb.RoadPathfinder.GetBuildCost can't build a tunnel from " + AIMap.GetTileX(Path.GetTile()) + "," + AIMap.GetTileY(Path.GetTile()) + " to " + AIMap.GetTileX(SubPath.GetTile()) + "," + AIMap.GetTileY(SubPath.GetTile()) + "!!" );
}
} else {
// if not a tunnel, we assume we're building a bridge
local BridgeList = AIBridgeList_Length(AIMap.DistanceManhattan(Path.GetTile(), SubPath.GetTile() + 1));
BridgeList.Valuate(AIBridge.GetMaxSpeed);
BridgeList.Sort(AIList.SORT_BY_VALUE, false);
if (!AIBridge.BuildBridge(AIVehicle.VT_ROAD, BridgeList.Begin(), Path.GetTile(), SubPath.GetTile())) {
// At this point, an error has occurred while
// building the bridge.
// Fail the pathfinder
// return null;
AILog.Warning("MinchinWeb.RoadPathfinder.GetBuildCost can't build a bridge from " + AIMap.GetTileX(Path.GetTile()) + "," + AIMap.GetTileY(Path.GetTile()) + " to " + AIMap.GetTileX(SubPath.GetTile()) + "," + AIMap.GetTileY(SubPath.GetTile()) + "!!" );
}
}
}
}
}
Path = SubPath;
}
// End build sequence
return BeanCounter.GetCosts();
}
function _MinchinWeb_RoadPathfinder_::BuildPath() {
if (this._running) {
AILog.Warning("You can't build a path while there's a running pathfinder.");
return false;
}
if (this._mypath == null) {
AILog.Warning("You have tried to build a 'null' path.");
return false;
}
local TestMode = AIExecMode(); // We're really doing this!
local Path = this._mypath;
AIRoad.SetCurrentRoadType(this._road_type);
while (Path != null) {
local SubPath = Path.GetParent();
if (SubPath != null) {
local Node = Path.GetTile();
if (AIMap.DistanceManhattan(Path.GetTile(), SubPath.GetTile()) == 1) {
// MD == 1 == road joining the two tiles
if (!AIRoad.BuildRoad(Path.GetTile(), SubPath.GetTile())) {
// If we get here, then the road building has failed
// Possible that the road already exists
// TO-DO:
// - fail the road builder if the road cannot be built and
// does not already exist
// return null;
}
} else {
// Implies that we're building either a tunnel or a bridge
if (!AIBridge.IsBridgeTile(Path.GetTile()) && !AITunnel.IsTunnelTile(Path.GetTile())) {
if (AIRoad.IsRoadTile(Path.GetTile())) {
// Original example demolishes tile if it's already a
// road tile to get around expanded roadbits.
// I don't like this approach as it could destroy
// Railway tracks/tram tracks/station
// TO-DO:
// - figure out a way to do this while keeping the
// other things I've built on the tile
// (can I just remove the road?)
AITile.DemolishTile(Path.GetTile());
}
if (AITunnel.GetOtherTunnelEnd(Path.GetTile()) == SubPath.GetTile()) {
// The assumption here is that the land hasn't changed
// from when the pathfinder was run and when we try
// to build the path. If the tunnel building fails,
// we get the 'can't build tunnel' message, but if
// the land has changed such that the tunnel end is
// at a different spot than is was when the
// pathfinder ran, we skip tunnel building and try
// and build a bridge instead, which will fail
// because the slopes are wrong...
if (!AITunnel.BuildTunnel(AIVehicle.VT_ROAD, Path.GetTile())) {
// At this point, an error has occurred while
// building the tunnel.
// Fail the pathfinder
// return null;
AILog.Warning("MinchinWeb.RoadPathfinder.BuildPath can't build a tunnel from " + AIMap.GetTileX(Path.GetTile()) + "," + AIMap.GetTileY(Path.GetTile()) + " to " + AIMap.GetTileX(SubPath.GetTile()) + "," + AIMap.GetTileY(SubPath.GetTile()) + "!!" );
}
} else {
// if not a tunnel, we assume we're building a bridge
local BridgeList = AIBridgeList_Length(AIMap.DistanceManhattan(Path.GetTile(), SubPath.GetTile() + 1));
BridgeList.Valuate(AIBridge.GetMaxSpeed);
BridgeList.Sort(AIList.SORT_BY_VALUE, false);
if (!AIBridge.BuildBridge(AIVehicle.VT_ROAD, BridgeList.Begin(), Path.GetTile(), SubPath.GetTile())) {
// At this point, an error has occurred while
// building the bridge.
// Fail the pathfinder
// return null;
AILog.Warning("MinchinWeb.RoadPathfinder.BuildPath can't build a bridge from " + AIMap.GetTileX(Path.GetTile()) + "," + AIMap.GetTileY(Path.GetTile()) + " to " + AIMap.GetTileX(SubPath.GetTile()) + "," + AIMap.GetTileY(SubPath.GetTile()) + "!! (or the tunnel end moved...)" );
}
}
}
}
}
Path = SubPath;
}
// End build sequence
return true;
}
function _MinchinWeb_RoadPathfinder_::LoadPath (Path) {
// 'Loads' a path to allow GetBuildCost(), BuildPath() and GetPathLength()
// to be used
if (this._running) {
AILog.Warning("You can't load a path while there's a running pathfinder.");
return false;
}
this._mypath = Path;
}
function _MinchinWeb_RoadPathfinder_::GetPath() {
// Returns the path stored by the pathfinder
if (this._running) {
AILog.Warning("You can't get the path while there's a running pathfinder.");
return false;
}
return this._mypath;
}
function _MinchinWeb_RoadPathfinder_::GetPathLength() {
// Runs over the path to determine its length
if (this._running) {
AILog.Warning("You can't get the path length while there's a running pathfinder.");
return false;
}
if (this._mypath == null) {
AILog.Warning("You have tried to get the length of a 'null' path.");
return false;
}
return this._mypath.GetLength();
}
function _MinchinWeb_RoadPathfinder_::InitializePathOnTowns(StartTown, EndTown) {
// Initializes the pathfinder using two towns
// Assumes that the town centres are road tiles (if this is not the case,
// the pathfinder will still run, but it will take a long time and
// eventually fail to return a path)
return this.InitializePath([AITown.GetLocation(StartTown)], [AITown.GetLocation(EndTown)]);
}
function _MinchinWeb_RoadPathfinder_::PathToTilePairs() {
// Returns a 2D array that has each pair of tiles that path joins
if (this._running) {
AILog.Warning("You can't convert a path while there's a running pathfinder.");
return false;
}
if (this._mypath == null) {
AILog.Warning("You have tried to convert a 'null' path.");
return false;
}
local Path = this._mypath;
local TilePairs = [];
while (Path != null) {
local SubPath = Path.GetParent();
if (SubPath != null) {
TilePairs.push([Path.GetTile(), SubPath.GetTile()]);
}
Path = SubPath;
}
// End build sequence
return TilePairs;
}
function _MinchinWeb_RoadPathfinder_::PathToTiles() {
// Returns a 1D array that has each pair of tiles that path covers
if (this._running) {
AILog.Warning("You can't convert a path while there's a running pathfinder.");
return false;
}
if (this._mypath == null) {
AILog.Warning("You have tried to convert a 'null' path.");
return false;
}
local Path = this._mypath;
local Tiles = [];
while (Path != null) {
Tiles.push(Path.GetTile());
Path = Path.GetParent();
}
return Tiles;
}
function _MinchinWeb_RoadPathfinder_::TilePairsToBuild() {
// Similar to PathToTilePairs(), but only returns those pairs where there
// isn't a current road connection
if (this._running) {
AILog.Warning("You can't convert a (partial) path while there's a running pathfinder.");
return false;
}
if (this._mypath == null) {
AILog.Warning("You have tried to convert a (partial) 'null' path.");
return false;
}
local Path = this._mypath;
local TilePairs = [];
while (Path != null) {
local SubPath = Path.GetParent();
if (SubPath != null) {
if (AIMap.DistanceManhattan(Path.GetTile(), SubPath.GetTile()) == 1) {
// Could join with a road
if (AIRoad.AreRoadTilesConnected(Path.GetTile(), SubPath.GetTile()) != true) {
TilePairs.push([Path.GetTile(), SubPath.GetTile()]);
}
} else {
// Implies that we're building either a tunnel or a bridge
if (!AIBridge.IsBridgeTile(Path.GetTile()) && !AITunnel.IsTunnelTile(Path.GetTile())) {
TilePairs.push([Path.GetTile(), SubPath.GetTile()]);
}
}
}
Path = SubPath;
}
// End build sequence
return TilePairs;
}
// EOF
MetaLib-7/Pathfinder.Ship.nut 0000666 0000000 0000000 00000070445 12304431065 014417 0 ustar 0000000 0000000 /* ShipPathfinder v.5, [2014-02-28],
* part of Minchinweb's MetaLibrary v.7,
* originally part of WmDOT v.7
* Copyright © 2011-14 by W. Minchin. For more info,
* please visit https://github.com/MinchinWeb/openttd-metalibrary
*
* Permission is granted to you to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell this software, and provide these
* rights to others, provided:
*
* + The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the software.
* + Attribution is provided in the normal place for recognition of 3rd party
* contributions.
* + You accept that this software is provided to you "as is", without warranty.
*/
/** \brief A Ship Pathfinder.
* \version v.5 (2014-02-28)
* \author W. Minchin (%MinchinWeb)
* \since MetaLibrary v.2
*
* I decided to create a pathfinder based on geometry rather than using the A*
* approach I used for roads. My pathfinder works like this:
* - To initialize, a path with two points (the start and end) is added to
* the pathfinder. For each following loop:
* 1. The shortest (unfinished) path is pulled from the pathfinder.
* 2. The path is walked, point-to-point, until land is reached.
* - If land is reached, two lines are drawn at right angles starting
* the midpoint (of the land). If water if reached, that point is
* then added to the path, and the path is added to the
* 'unfinished' list.
* 3. If the shortest path is on the 'finished' list (i.e. all water),
* then that path is returned. Otherwise, the loop restarts.
*
* With simple geometries, it works fast and well. However, on complex
* geometries, it doesn't work as well as I would like. The other problem I
* have is that the geometry only works on the basis that the start and end
* points are in the same waterbody, and so I created \_MinchinWeb\_Lakes\_
* (Lakes) to confirm this is the case; however it adds running time
* to the whole pathfinder. One the plus side, building the path is very
* simple: just build buoys at each point along the path!
*
* \note \_MinchinWeb\_WBC\_ has been depreciated in favour of
* \_MinchinWeb\_Lakes\_.
*
* \requires Fibonacci Heap v.3
* \see \_MinchinWeb\_WBC\_
* \see \_MinchinWeb\_Lakes\_
* \see \_MinchinWeb\_RoadPathfinder\_
* \todo Add image showing how the Ship Pathfinder works
*/
class _MinchinWeb_ShipPathfinder_
{
_heap_class = import("queue.fibonacci_heap", "", 3);
_WBC_class = _MinchinWeb_Lakes_; ///< Class used to check if the two points are within the same waterbody
_WBC = null; ///< actual instance of class used to check if the points are within the same waterbody
_max_cost = null; ///< The maximum (pathfinder) cost for a route.
_cost_tile = null; ///< The (pathfinder) cost for a single tile.
_cost_turn = null; ///< The (pathfinder) cost that is added to _cost_tile if the direction changes.
cost = null; ///< Used to change the (pathfinder) costs.
_max_buoy_spacing = null; ///< The maximum spacing between buoys
// _infinity = null;
_first_run = null;
_first_run2 = null;
_waterbody_check = null;
_points = null; ///< Used to store points considered by the pathfinder. Stored as TileIndexes
_paths = null; ///< Used to store the paths the pathfinder is working with. Stored as indexes to _points
_clearedpaths = null; ///< Used to store paths that have already been cleared (i.e. all water).
_UnfinishedPaths = null; ///< Used to sort in-progress paths
_FinishedPaths = null; ///< Used to store finished paths
_testedpaths = null;
_mypath = null; ///< Used to store the path after it's been found for Building functions
_running = null; ///< Is the pathfinder running?
info = null;
constructor()
{
this._max_cost = 10000;
this._cost_tile = 1;
this._cost_turn = 1;
this._max_buoy_spacing = 50;
// this._infinity = _MinchinWeb_C_Infinity();
// this._infinity = 10; // For Testing
this._points = [];
this._paths = [];
this._clearedpaths = [];
this._testedpaths = [];
this._UnfinishedPaths = this._heap_class();
this._FinishedPaths = this._heap_class();
this._WBC = this._WBC_class();
this._mypath = null;
this._running = false;
this.cost = this.Cost(this);
this.info = this.Info(this);
}
/** \publicsection
* \brief Initializes the pathfinder.
* \param source Starting tile, as a TileID as the first element of an
* array.
* \param goal Ending tile, as a TileID as the first element of an
* array.
* \note Assumes only one source and goal tile.
*/
function InitializePath(source, goal) {
// Assumes only one source and goal tile...
this._points = [];
this._paths = [];
this._clearedpaths = [];
this._UnfinishedPaths = this._heap_class();
this._FinishedPaths = this._heap_class();
this._mypath = null;
this._first_run = true;
this._first_run2 = true;
this._running = true;
this._points.push(source[0]);
this._points.push(goal[0]);
this._paths.push([0,1]);
this._UnfinishedPaths.Insert(0, _MinchinWeb_ShipPathfinder_._PathLength(0));
}
/** \brief Runs the pathfinder.
* \param iterations Number of cycles to run the pathfinder before
* returning. If set to `-1`, will run until a path
* is found.
* \note One of the first things the pathfinder will do is confirm the
* start and end points are in the same waterbody. If `iterations`
* is not set to `-1`, it will return after completing this.
* Therefore, if `iterations` is set to a finite amount, this
* function will need to be called at least twice to return a path.
* \return `null` if a path cannot be found.
* \return the path, if a path is found.
* \return `false` if the pathfinder is unfinished.
*/
function FindPath(iterations);
/** \brief Find land!
*
* Starting one two water tiles, this function will walk the line between
* them, starting at the outside ends, and return the tiles where it hits
* land.
* \param TileA A water tile
* \param TileB Another water tile
* \return A two element, one dimensional array of the tile indexes of the
* first land tiles hit after starting at TileA and TileB.
* \return `[-1, -1]` if the path is all water (no land).
* \static
*/
function LandHo(TileA, TileB);
/** \brief To the sea! (Find water)
*
* Starts at a given tile and then walks out at the given slope until it
* hits water.
* \param StartTile A land tile.
* \param Slope The slope of the line to follow out.
* \param ThirdQuadrant Whether to follow the slope in the third or
* fourth quadrant.
* \return The first water tile hit.
* \todo Add image showing the Cartesian quadrants.
* \todo Move to \_MinchinWeb\_Marine\_
*
* \static
*/
function WaterHo(StartTile, Slope, ThirdQuadrant = false);
/** \brief Runs over the path to determine its length.
* \return Path length in tiles
*/
function GetPathLength();
/** \brief Returns the number of potential buoys that may need to be built.
*/
function CountPathBuoys();
/** \brief Build the buoys along the path.
*
* Build the buoys that may need to be built.
* Changes `this._mypath` to be the list of these buoys.
*/
function BuildPathBuoys();
/** \brief Get the current path.
* \return The path, as currently held by the pathfinder.
*/
function GetPath();
/** \brief Skip Waterbody Check
*
* This function skips the Waterbody Check at the beginning of the Ship
* Pathfinder run. This is intended for if you have already run Waterbody
* Check or otherwise know that the two points are in the same waterbody.
* \warning The Ship Pathfinder's behaviour without this check in place
* is not tested, as the Ship Pathfinder assumes the two points
* are in the same waterbody.... Use at your own risk.
* \note This is less of an issue with the introduction of Lakes.
* While Lakes may take a long time on the first run,
* by reusing the pathfinder, subsequent checks of the same
* path are very fast (in the order of 4 ticks).
*/
function OverrideWBC();
/** \privatesection
* \brief Turns a path into an index to tiles.
*
* Just the start, end, and turning points.
*/
function _PathToTilesArray(PathIndex);
/** \private
* \brief Inserts a point into the point list.
*
* Does a check to insure that the same point does not show up twice at
* different indexes.
* \return The index of the (added) point.
*/
function _InsertPoint(TileIndex);
/** \privatesection
*/
function _PathLength(PathIndex);
};
class _MinchinWeb_ShipPathfinder_.Info {
_main = null;
function GetVersion() { return 5; }
// function GetMinorVersion() { return 0; }
function GetRevision() { return 140228; }
function GetDate() { return "2014-02-28"; }
function GetName() { return "Ship Pathfinder (Wm)"; }
constructor(main)
{
this._main = main;
}
};
class _MinchinWeb_ShipPathfinder_.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 "turn": this._main._cost_turn = val; break;
case "max_buoy_spacing": this._main._max_buoy_spacing = 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 "turn": return this._main._cost_turn;
case "max_buoy_spacing": return this._main._max_buoy_spacing;
default: throw("the index '" + idx + "' does not exist");
}
}
constructor(main) {
this._main = main;
}
};
// == Function definitions ================================================
function _MinchinWeb_ShipPathfinder_::FindPath(iterations) {
// Waterbody Check
if (this._first_run == true) {
_MinchinWeb_Log_.Note("Ship Pathfinder running WaterBody Check... (at tick " + AIController.GetTick() + ")", 6);
if (this._first_run2 == true) {
this._WBC.InitializePath([this._points[this._paths[0][0]]], [this._points[this._paths[0][1]]]);
this._first_run2 = false;
}
local SameWaterBody = this._WBC.FindPath(iterations);
if ((SameWaterBody == false) || (SameWaterBody == null)) {
return SameWaterBody;
} else {
this._first_run = false;
}
if (iterations != -1) { return false; }
}
_MinchinWeb_Log_.Note("Starting Ship Pathfinder (at tick " + AIController.GetTick() + ")", 7);
if (iterations == -1) { iterations = _MinchinWeb_C_.Infinity() } // = 10000; close enough to infinity but able to avoid infinite loops?
for (local j = 0; j < iterations; j++) {
_MinchinWeb_Log_.Note("UnfinishedPaths count " + this._UnfinishedPaths.Count() + " : " + j + " of " + iterations + " iterations.", 6);
// Pop the shortest path from the UnfinishedPath Heap
local WorkingPath = this._UnfinishedPaths.Pop(); // WorkingPath is the Index to the path in question
_MinchinWeb_Log_.Note(" UnfinishedPath count after Pop... " + this._UnfinishedPaths.Count(), 7);
_MinchinWeb_Log_.Note(" Path " + WorkingPath + " popped: " + _MinchinWeb_Array_.ToString1D(this._paths[WorkingPath]) + " l=" + _PathLength(WorkingPath), 6);
local ReturnWP = false;
// Walk the path segment by segment until we hit land
for (local i = 0; i < (this._paths[WorkingPath].len() - 1); i++) {
// End is around line 306...
_MinchinWeb_Log_.Note("Contained in test... " + i + " : " + (this._paths[WorkingPath].len() - 2) + " : " + _MinchinWeb_Array_.ToString2D(this._clearedpaths) + " " + this._points[this._paths[WorkingPath][i]] + " " + this._points[this._paths[WorkingPath][i+1]] + " : " + _MinchinWeb_Array_.ContainedInPairs(this._clearedpaths, this._points[this._paths[WorkingPath][i]], this._points[this._paths[WorkingPath][i+1]]), 7);
if (_MinchinWeb_Array_.ContainedInPairs(this._clearedpaths, this._points[this._paths[WorkingPath][i]], this._points[this._paths[WorkingPath][i+1]]) != true) {
// This means we haven't already cleared the path...
local Land = LandHo(this._points[this._paths[WorkingPath][i]], this._points[this._paths[WorkingPath][i+1]]);
// _MinchinWeb_Log_.Note("Land : " + _MinchinWeb_Array_.ToString1D(Land) + " : " + _MinchinWeb_Array_.ToStringTiles1D(Land), 7);
_MinchinWeb_Log_.Note("Land : " + _MinchinWeb_Array_.ToString1D(Land), 7);
if ((Land[0] == -1) && (Land[1] == -1)) {
// All water
this._clearedpaths.push([this._points[this._paths[WorkingPath][i]], this._points[this._paths[WorkingPath][i+1]]]);
ReturnWP = true;
} else {
ReturnWP = false;
// We're going to test this path and don't want to endlessly
// be coming back to it
this._testedpaths.push(this._paths[WorkingPath]);
// On hitting land, do the right angle split creating two copies
// of the path with a new midpoint
local m = _MinchinWeb_Extras_.Perpendicular(_MinchinWeb_Extras_.Slope(this._points[this._paths[WorkingPath][i]], this._points[this._paths[WorkingPath][i+1]]));
local MidPoint = _MinchinWeb_Extras_.MidPoint(Land[0], Land[1]);
// Check if Midpoint is on Water. If it is, add it and skip the right angle split
// Midpoint should only be added if it's in the same Waterbody as the start and finish...
// WBCOnMidPoint will only be true iff the point is a water tile
this._WBC.InitializePath([MidPoint], [this._points[0]]);
local WBCOnMidPoint = this._WBC.FindPath(-1);
if ((WBCOnMidPoint == true) && ((Land[0] == -1) || (Land[1] == -1))) {
local WPPoints = this._paths[WorkingPath];
local NewPointZIndex = _InsertPoint(MidPoint);
_MinchinWeb_Log_.Sign(MidPoint, NewPointZIndex + "", 7);
local WPPointsZ = _MinchinWeb_Array_.InsertValueAt(WPPoints, i+1, NewPointZIndex);
this._paths[WorkingPath] = WPPointsZ;
this._UnfinishedPaths.Insert(WorkingPath, _PathLength(WorkingPath));
_MinchinWeb_Log_.Note(" Midpoint on Water...", 7);
_MinchinWeb_Log_.Note(" Inserting Path #" + WorkingPath + " : " + _MinchinWeb_Array_.ToString1D(this._paths[WorkingPath]) + " l=" + _PathLength(WorkingPath), 6);
} else {
local NewPoint1 = WaterHo(MidPoint, m, false);
local NewPoint2 = WaterHo(MidPoint, m, true);
local WPPoints = this._paths[WorkingPath];
if (NewPoint1 != null) {
// only add the point if it's in the same waterbody
this._WBC.InitializePath([NewPoint1], [this._points[0]]);
local WBCOnPoint1 = this._WBC.FindPath(-1);
if (WBCOnPoint1 == true) {
local NewPoint1Index = _InsertPoint(NewPoint1);
_MinchinWeb_Log_.Sign(NewPoint1, NewPoint1Index + "", 7);
local WPPoints1 = _MinchinWeb_Array_.InsertValueAt(WPPoints, i+1, NewPoint1Index);
// With the new point, check both forward and back to see if the
// points both before and after the new midpoint to see if
// they can be removed from the path (iff the resulting
// segment would be only on the water)
if ( ((i+3) < WPPoints1.len()) && (LandHo(this._points[WPPoints1[i+1]], this._points[WPPoints1[i+3]])[0] == -1) ) {
WPPoints1 = _MinchinWeb_Array_.RemoveValueAt(WPPoints1, i+2);
}
// With the new point, check we're not putting the point in
// twice in a row...
if ( ((i+2) < WPPoints1.len()) && (WPPoints1[i+1] == WPPoints1[i+2]) ) {
WPPoints1 = _MinchinWeb_Array_.RemoveValueAt(WPPoints1, i+1);
_MinchinWeb_Log_.Note(" Point Removed! " + WPPoints1[i+1] + " i=" + i, 6);
} else {
_MinchinWeb_Log_.Note(" Point Kept " + WPPoints1[i+1] + " " + WPPoints1[i+2] + " i=" + i, 6);
}
if ( ((i-1) > 0) && (LandHo(this._points[WPPoints1[i-1]], this._points[WPPoints1[i+1]])[0] == -1)) {
WPPoints1 = _MinchinWeb_Array_.RemoveValueAt(WPPoints1, i);
i--; // For double point check
}
if ( (i > 0) && (WPPoints1[i+1] == WPPoints1[i]) ) {
WPPoints1 = _MinchinWeb_Array_.RemoveValueAt(WPPoints1, i+1);
_MinchinWeb_Log_.Note(" Point Removed! " + WPPoints1[i+1] + " i=" + i, 6);
} else {
_MinchinWeb_Log_.Note(" Point Kept " + WPPoints1[i] + " " + WPPoints1[i+1] + " i=" + i, 6);
}
// Put both paths back into the UnfinishedPath heap
// (assuming we haven't been down this path before...)
if (_MinchinWeb_Array_.ContainedIn1DIn2D(this._testedpaths, WPPoints1) != true) {
this._paths[WorkingPath] = WPPoints1;
_MinchinWeb_Log_.Note(" Inserting Path #" + WorkingPath + " : " + _MinchinWeb_Array_.ToString1D(this._paths[WorkingPath]) + " l=" + _PathLength(WorkingPath), 6);
this._UnfinishedPaths.Insert(WorkingPath, _PathLength(WorkingPath));
}
}
}
if (NewPoint2 != null) {
// only add the point if it's in the same waterbody
this._WBC.InitializePath([NewPoint2], [this._points[0]]);
local WBCOnPoint2 = this._WBC.FindPath(-1);
if (WBCOnPoint2 == true) {
local NewPoint2Index = _InsertPoint(NewPoint2);
_MinchinWeb_Log_.Sign(NewPoint2, NewPoint2Index + "", 7);
local WPPoints2 = _MinchinWeb_Array_.InsertValueAt(WPPoints, i+1, NewPoint2Index);
if ( ((i+3) < WPPoints2.len()) && (LandHo(this._points[WPPoints2[i+1]], this._points[WPPoints2[i+3]])[0] == -1) ) {
WPPoints2 = _MinchinWeb_Array_.RemoveValueAt(WPPoints2, i+2);
}
if ( ((i+2) < WPPoints2.len()) && (WPPoints2[i+1] == WPPoints2[i+2]) ) {
WPPoints2 = _MinchinWeb_Array_.RemoveValueAt(WPPoints2, i+1);
_MinchinWeb_Log_.Note(" Point Removed! " + WPPoints2[i+1] + " i=" + i, 6);
} else {
_MinchinWeb_Log_.Note(" Point Kept " + WPPoints2[i+1] + " " + WPPoints2[i+2] + " i=" + i, 6);
}
if ( ((i-1) > 0) && (LandHo(this._points[WPPoints2[i-1]], this._points[WPPoints2[i+1]])[0] == -1)) {
WPPoints2 = _MinchinWeb_Array_.RemoveValueAt(WPPoints2, i);
i--;
}
if ( (i > 0) && (WPPoints2[i+1] == WPPoints2[i]) ) {
WPPoints2 = _MinchinWeb_Array_.RemoveValueAt(WPPoints2, i+1);
_MinchinWeb_Log_.Note(" Point Removed! " + WPPoints2[i+1] + " i=" + i, 6);
} else {
_MinchinWeb_Log_.Note(" Point Kept " + WPPoints2[i] + " " + WPPoints2[i+1] + " i=" + i, 6);
}
// Put the paths into the UnfinishedPath heap
// (assuming we haven't been down this path before...)
if (_MinchinWeb_Array_.ContainedIn1DIn2D(this._testedpaths, WPPoints2) != true) {
this._paths.push(WPPoints2);
_MinchinWeb_Log_.Note(" Inserting Path #" + (this._paths.len() - 1) + " : " + _MinchinWeb_Array_.ToString1D(WPPoints2) + " l=" + _PathLength(this._paths.len() - 1), 5);
this._UnfinishedPaths.Insert(this._paths.len() - 1, _PathLength(this._paths.len() - 1));
}
}
}
} // End of if MidPoint is on Water
i = this._paths[WorkingPath].len(); // Exits us from the for... loop
}
// i = this._paths[WorkingPath].len(); // Exits us from the for... loop
} else if (i == (this._paths[WorkingPath].len() - 2)){
// If we don't hit land, add the path to the FinishedPaths heap
_MinchinWeb_Log_.Note("Inserting Finished Path " + WorkingPath + " l=" + _PathLength(WorkingPath), 5);
this._FinishedPaths.Insert(WorkingPath, _PathLength(WorkingPath));
}
} // END for (local i = 0; i < (this._paths[WorkingPath].len() - 1); i++) i.e. stepping through path
if (ReturnWP == true) {
// If everything was water...
_MinchinWeb_Log_.Note(" Inserting Path #" + WorkingPath + " (all water) on ReturnWP; l=" + _PathLength(WorkingPath), 5);
this._UnfinishedPaths.Insert(WorkingPath, _PathLength(WorkingPath));
}
if (this._UnfinishedPaths.Count() == 0) {
_MinchinWeb_Log_.Note("Unfinsihed count: " + this._UnfinishedPaths.Count() + " finished: " + this._FinishedPaths.Count(), 6);
if (this._FinishedPaths.Count() !=0) {
this._running = false;
this._mypath = _PathToTilesArray(this._FinishedPaths.Peek());
_MinchinWeb_Log_.Note("My Path is " + _MinchinWeb_Array_.ToString1D(this._mypath), 5);
return this._mypath;
} else {
// If the UnfinishedPath heap is empty, fail the pathfinder
this._running = false;
return null;
}
} else {
if (this._FinishedPaths.Count() !=0) {
// If the Finished heap contains a path that is shorter than
// any of the unfinished paths, return the finished path
// Actually, if the shortest finished path is within 10% of
// shortest unfinished path, call it good enough!!
local finished = this._PathLength(this._FinishedPaths.Peek());
local unfinished = this._PathLength(this._UnfinishedPaths.Peek());
if ((finished * 100) < (unfinished * 110)) {
this._running = false;
this._mypath = _PathToTilesArray(this._FinishedPaths.Peek());
_MinchinWeb_Log_.Note("My Path is " + _MinchinWeb_Array_.ToString1D(this._mypath), 5);
return this._mypath;
}
_MinchinWeb_Log_.Note(" Finished =" + finished + " ; Unfinsihed = " + unfinished, 5);
}
}
}
return false;
}
function _MinchinWeb_ShipPathfinder_::_PathLength(PathIndex)
{
local Length = 0.0;
for (local i = 0; i < (this._paths[PathIndex].len() - 1); i++) {
Length += _MinchinWeb_Marine_.DistanceShip(this._points[this._paths[PathIndex][i]], this._points[this._paths[PathIndex][i + 1]]);
}
return Length;
}
/* static */ function _MinchinWeb_ShipPathfinder_::LandHo(TileA, TileB) {
_MinchinWeb_Log_.Note("Running LandHo... (" + _MinchinWeb_Array_.ToStringTiles1D([TileA, TileB]) + ").", 7);
local LandA = 0;
local LandB = 0;
local Walker = _MinchinWeb_LW_();
Walker.Start(TileA);
Walker.End(TileB);
local PrevTile = Walker.GetStart();
local CurTile = Walker.Walk();
while (!Walker.IsEnd() && (LandA == 0)) {
if (AIMarine.AreWaterTilesConnected(PrevTile, CurTile) != true) {
LandA = PrevTile
}
PrevTile = CurTile;
CurTile = Walker.Walk();
}
if (Walker.IsEnd()) {
// We're all water!
return [-1,-1];
}
Walker.Reset();
Walker.Start(TileB);
Walker.End(TileA);
PrevTile = Walker.GetStart();
CurTile = Walker.Walk();
while (!Walker.IsEnd() && (LandB == 0)) {
if (AIMarine.AreWaterTilesConnected(PrevTile, CurTile) != true) {
LandB = PrevTile
}
PrevTile = CurTile;
CurTile = Walker.Walk();
}
if (Walker.IsEnd()) {
// We're all water!
return [-1,-1];
}
return [LandA, LandB];
}
/* static */ function _MinchinWeb_ShipPathfinder_::WaterHo(StartTile, Slope, ThirdQuadrant = false) {
// Starts at a given tile and then walks out at the given slope until it
// hits water
local Walker = _MinchinWeb_LW_();
Walker.Start(StartTile);
Walker.Slope(Slope, ThirdQuadrant);
_MinchinWeb_Log_.Note(" WaterHo! " + StartTile + " , m=" + Slope + " 3rdQ " + ThirdQuadrant, 7);
local PrevTile = Walker.GetStart();
local CurTile = Walker.Walk();
while ((AIMarine.AreWaterTilesConnected(PrevTile, CurTile) != true) && (AIMap.DistanceManhattan(PrevTile, CurTile) == 1)) {
PrevTile = CurTile;
CurTile = Walker.Walk();
}
if (AIMarine.AreWaterTilesConnected(PrevTile, CurTile) == true) {
_MinchinWeb_Log_.Note(" WaterHo returning " + _MinchinWeb_Array_.ToStringTiles1D([CurTile]), 7);
return CurTile;
} else {
return null;
}
}
function _MinchinWeb_ShipPathfinder_::_PathToTilesArray(PathIndex) {
// turns a path into an index to tiles (just the start, end, and turning points)
local Tiles = [];
for (local i = 0; i < (this._paths[PathIndex].len()); i++) {
Tiles.push(this._points[this._paths[PathIndex][i]]);
}
_MinchinWeb_Log_.Note("PathToTilesArray input " + _MinchinWeb_Array_.ToString1D(this._paths[PathIndex]), 7);
_MinchinWeb_Log_.Note(" and output " + _MinchinWeb_Array_.ToString1D(Tiles), 7);
return Tiles;
}
function _MinchinWeb_ShipPathfinder_::GetPathLength() {
// Runs over the path to determine its length
if (this._running) {
AILog.Warning("You can't get the path length while there's a running pathfinder.");
return false;
}
if (this._mypath == null) {
AILog.Warning("You have tried to get the length of a 'null' path.");
return false;
}
local Length = 0;
for (local i = 0; i < (this._mypath.len() - 1); i++) {
Length += _MinchinWeb_Marine_.DistanceShip(this._mypath[i], this._mypath[i + 1]);
}
return Length;
}
function _MinchinWeb_ShipPathfinder_::_InsertPoint(TileIndex) {
// Inserts a point into point list. Does a check to insure that the same
// point does not show up twice at different indexes.
// Returns the index of the point
local Index = _MinchinWeb_Array_.Find1D(this._points, TileIndex);
if (Index == false) {
this._points.push(TileIndex);
return (this._points.len() - 1);
} else {
return Index;
}
}
function _MinchinWeb_ShipPathfinder_::CountPathBuoys() {
// returns the number of potential buoys that may need to be built
_MinchinWeb_Log_.Note("My Path is " + _MinchinWeb_Array_.ToString1D(this._mypath), 7);
if (this._mypath == null) {
AILog.Warning("MinchinWeb.ShipPathfinder.CountBuoys() must be supplied with a valid path.");
} else {
// basic direction changes (minus the two ends)
local Buoys = this._mypath.len() - 2;
// test for long segments
for (local i = 1; i < this._mypath.len(); i++) {
local TestLength = _MinchinWeb_Marine_.DistanceShip(this._mypath[i-1], this._mypath[i]);
while (TestLength > this._max_buoy_spacing) {
TestLength -= this._max_buoy_spacing;
Buoys ++;
}
}
return Buoys;
}
}
function _MinchinWeb_ShipPathfinder_::BuildPathBuoys() {
// Build the buoys that may need to be built
// changes this._mypath to be the list of these buoys
if (this._mypath == null) {
AILog.Warning("MinchinWeb.ShipPathfinder.BuildBuoys() must be supplied with a valid path.");
} else {
for (local i = 0; i < this._mypath.len(); i++) {
// skip first and last points
if ((i != 0) && (i != (this._mypath.len() - 1))) {
// Build a bouy at each junction, and update the path if an existing buoy is used
_MinchinWeb_Log_.Note("Build Buoy " + i + " :" + _MinchinWeb_Array_.ToStringTiles1D([this._mypath[i]]), 5);
this._mypath[i] = _MinchinWeb_Marine_.BuildBuoy(this._mypath[i]);
}
}
// Build extra buoys for long stretches
for (local i = 1; i < this._mypath.len(); i++) {
if (_MinchinWeb_Marine_.DistanceShip(this._mypath[i-1], this._mypath[i]) > this._max_buoy_spacing ) {
local midpoint = _MinchinWeb_Extras_.MidPoint(this._mypath[i-1], this._mypath[i]);
_MinchinWeb_Marine_.BuildBuoy(midpoint);
this._mypath = _MinchinWeb_Array_.InsertValueAt(this._mypath, i, midpoint);
_MinchinWeb_Log_.Note("Build Buoy " + i + " : new dist=" + _MinchinWeb_Marine_.DistanceShip(this._mypath[i-1], this._mypath[i]) + " : at" + _MinchinWeb_Array_.ToStringTiles1D([this._mypath[i]]), 7);
i--; // rescan the section...
}
}
return this._mypath;
}
}
function _MinchinWeb_ShipPathfinder_::GetPath() {
// Returns the path, as currently held by the pathfinder
if (this._mypath == null) {
AILog.Warning("MinchinWeb.ShipPathfinder.BuildBuoys() must be supplied with a valid path.");
} else {
return this._mypath;
}
}
function _MinchinWeb_ShipPathfinder_::OverrideWBC() {
// This function skips the Waterbody Check at the beginning of the Ship
// Pathfinder run
// This is intended for if you have already run Waterbody Check or
// otherwise know that the two points are in the same waterbody.
// Be warned that Ship Pathfinder's behaviour without this check in place
// is not tested, as the Ship Pathfinder assumes the two points are
// in the same waterbody...
this._first_run == false;
_MinchinWeb_Log_.Note("WaterBody Check has been overridden", 6);
}
// EOF
MetaLib-7/readme.txt 0000666 0000000 0000000 00000023166 12305212740 012673 0 ustar 0000000 0000000 About MetaLibrary {#mainpage}
===============================================================================
MetaLibrary is the collection of code I've written for
[WmDOT](http://www.tt-forums.net/viewtopic.php?f=65&t=53698), my AI for
[OpenTTD](http://www.openttd.org/), that I felt should properly be in a
library. Separating my AI from this library has made it easier to write my
AI, but I also hope will this code will help some aspiring AI writer get off
the ground a little bit faster. ;)
GameScript
===============================================================================
A GameScript version of this library is also available. All functions are
available, although some may require an active company context.
Sub-Libraries Available {#sublibraries}
===============================================================================
- `MinchinWeb.Atlas` <- _MinchinWeb_Atlas_
- given a list of 'sources' and 'attractions', will weight the combinations
and generate a list of pairs to connect.
- `MinchinWeb.Array` <- _MinchinWeb_Array_
- Array functions
- `MinchinWeb.Constants` <- _MinchinWeb_C_
- `MinchinWeb.DLS` <- _MinchinWeb_DLS_
- **Dominion Land System** -- a wrapper on the road pathfinder to encourage
roads to be built on a grid.
- `MinchinWeb.Extras` <- _MinchinWeb_Extras_
- Extra functions, for things like math, map geometry, ship support, etc.
- `MinchinWeb.Lakes` <- _MinchinWeb_Lakes_
- **Lakes** serves as a check to determine if two points are in the same
waterbody. It was written as a replacement for Waterbody Check. Lakes
remembers the work it has previously done, which makes subsequent requests
much faster.
- `MinchinWeb.LineWalker` <- _MinchinWeb_LW_
- **Line Walker** -- walk the map between two points, or start at a given
point and walk out at a given slope.
- `MinchinWeb.Log` <- _MinchinWeb_Log_
- help (dynamically) control the amount of debugging output displayed.
- `MinchinWeb.Industry` <- _MinchinWeb_Industry_
- `MinchinWeb.Marine` <- _MinchinWeb_Marine_
- Marine and ship related functions.
- `MinchinWeb.ShipPathfinder` <- _MinchinWeb_ShipPathfinder_
- A geometry based Ship Pathfinder.
- `MinchinWeb.SpiralWalker` <- _MinchinWeb_SW_
- **Spiral Walker** -- allows you to define a starting point, and then
'walk' all the tiles in a spiral outward. It was originally used to find a
buildable spot for my HQ in WmDOT, but is useful for many other things as
well.
- `MinchinWeb.Station` <- _MinchinWeb_Station_
- some station related functions
- `MinchinWeb.RoadPathfinder` <- _MinchinWeb_RoadPathfinder_
- Based on NoAI Team's pathfinder. The pathfinder uses the A* search pattern
and includes functions to find the path, determine its cost, and build it.
Can bridge over rivers, canals, and railroad tracks.
- `MinchinWeb.WaterbodyCheck` <- _MinchinWeb_WBC_
- **Note**: Waterbody Check has been deprecated in favour of Lakes
- Waterbody Check - in effect a specialized pathfinder. It serves to check
whether two points are in the same waterbody (i.e. a ship could travel
between them). It is optimized to run extremely fast (I hope!). It can be
called separately, but was originally designed as a pre-run check for my
Ship Pathfinder.
Changelog
===============================================================================
### Version 7
Released 2014-02-28
- Added Lakes as a replacement for WaterBodyCheck
- Ship Pathfinder now uses Lakes rather than WaterBodyCheck
- Ship Pathfinder now makes sure every point is in the same waterbody before
adding it to the path
- WaterBodyCheck is now deprecated
- Documentation for MetaLibrary is now online at
[Minchin.ca](http://minchin.ca/openttd-metalibrary)
- Fix array creation bugs in Array.Create2D(), Array.Create3D()
- Added Array.RemoveDuplicates(Array)
- Added Array.ToAIList(Array)
- Added Extras.MinDistance(TileID, TargetArray); can be used as a valuator
- Split Constants from Extras (file only, function access remains the same)
- Split Industry from Extras (file only, function access remains the same)
- Split Station from Extras (file only, function access remains the same)
- Bumped maximum Log `Debug_Level` to 8
- Added separate Changelog file
- Rename `Readme.txt` to `Readme.md`
- Update requirement to Fibonacci Heap, v.3
Read the complete [Changelog](md_openttd-metalibrary_changelog.html).
Installation {#installation}
===============================================================================
The easiest way install MetaLibrary is the use the in-game downloader in
OpenTTD.
If you want to manually install it, download the folder and place it in your
`..\OpenTTD\ai\library\` folder.
For you to use the library in your AI's you'll need to import it. Somewhere
outside of any other class or function, add an import statement like:
Import("util.MinchinWeb", "MinchinWeb", 7);
Requirements {#requirements}
===============================================================================
If installed from the in-game downloader, the dependencies will
automatically be downloaded and installed. Otherwise, you'll need the
following libraries:
- [Binary Heap], v.1 (`Queue.BinaryHeap-1.tar`)
- [Fibonacci Heap], v.3 (`Queue.FibonacciHeap-3.tar`)
- [Graph.AyStar], v.6 (`Graph.AyStar-6.tar`)
[Binary Heap]: http://binaries.openttd.org/bananas/ailibrary/Queue.BinaryHeap-1.tar.gz
[Graph.AyStar]: http://binaries.openttd.org/bananas/ailibrary/Graph.AyStar-6.tar.gz
[Fibonacci Heap]: http://binaries.openttd.org/bananas/ailibrary/Queue.FibonacciHeap-3.tar.gz
OpenTTD is able to read uncompressed `tar` files without any problem.
FAQ {#faq}
===============================================================================
**Q:** How do I use the sub-libraries directly?
**A:** Import the main library, and then create global points to the
sub-libaries you want to use. Eg:
~~~
Import("util.MinchinWeb", "MinchinWeb", 7);
Arrays <- MinchinWeb.Arrays;
~~~
*Info:* See the sub-library files for the functions available and their
implementation.
**Q:** What is the _MinchinWeb_ ... all over the place?
**A:** I can't answer it better than Zuu when he put together his SuperLib, so
I'll quote him.
> " Unfortunately due to constraints in OpenTTD and Squirrel, only the
> main class of a library will be renamed at import. For [MetaLib]
> that is the [MetaLib] class in this file. Every other class in this
> file or other .nut files that the library is built up by will end
> up at the global scope at the AI that imports the library. The
> global scope of the library will get merged with the global scope
> of your AI.
>
> " To reduce the risk of causing you conflict problems this library
> prefixes everything that ends up at the global scope of AIs with
> [ _MinchinWeb_ ]. That is also why the library is not named Utils or
> something with higher risk of you already having at your global
> scope.
>
> " You should however never need to use any of the [ _MinchinWeb_ ... ]
> names as a user of this library. It is not even recommended to do
> so as it is part of the implementation and could change without
> notice. "
>
> -- Zuu, SuperLib v.7 documentation
A grand 'Thank You' to Zuu for his SuperLib that provided a very useful
model, to all the NoAI team to their work on making the AI system work,
and to everyone that has brought us the amazing game of OpenTTD.
License {#license}
===============================================================================
**Minchinweb's MetaLibrary** v.7 [2017-02-28]
Copyright © 2011-14 by W. Minchin.
For more info,
please visit
Permission is granted to you to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell this software, and provide these
rights to others, provided:
- The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the software.
- Attribution is provided in the normal place for recognition of 3rd party
contributions.
- You accept that this software is provided to you "as is", without warranty.
\note _MinchinWeb_RoadPathfinder_ is separately licensed under
LGPL v.2.1.
Links {#links}
===============================================================================
- Discussion thread for MetaLibarary on TT-Forums --
- MetaLibrary code, hosted on GitHub --
- MetaLibrary documentation --
Notes To Me {#notes}
===============================================================================
\todo Notes about static classes, what they are, and which classes
are 'static'
\todo Consider Fibonacci Heap version in NoCAB
\todo Add picture of in game downloader
\todo Look into theming dOxygen output
\todo Add 'News' tab pointing back to my Blog updates on MetaLibrary
MetaLib-7/Spiral.Walker.nut 0000666 0000000 0000000 00000013144 12304425444 014104 0 ustar 0000000 0000000 /* SpiralWalker class v.3 r.223 [2012-01-28],
* part of Minchinweb's MetaLibrary v.4,
* originally part of WmDOT v.5
* Copyright © 2011-12 by W. Minchin. For more info,
* please visit https://github.com/MinchinWeb/openttd-metalibrary
*
* Permission is granted to you to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell this software, and provide these
* rights to others, provided:
*
* + The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the software.
* + Attribution is provided in the normal place for recognition of 3rd party
* contributions.
* + You accept that this software is provided to you "as is", without warranty.
*/
/** \brief Spiral Walker
* \version v.3 (2012-01-28)
* \author W. Minchin (%MinchinWeb)
* \since MetaLibrary v.4
*
* The SpiralWalker class allows you to define a starting point, and then
* 'walk' all the tiles in a spiral outward. It was originally used to
* find a build-able spot for my HQ in WmDOT, but is useful for many other
* things as well.
*
* \note `SpiralWalker` is designed to be a persistent class.
* \see \_MinchinWeb\_LW\_
* \todo add image showing the walk out pattern
*/
/* Functions provided:
* MetaLib.SpiralWalker()
* MetaLib.SpiralWalker.Start(Tile)
* .Reset()
* .Restart()
* .Walk()
* .GetStart()
* .GetStage()
* .GetStep()
*/
class _MinchinWeb_SW_ {
_start = null; ///< start tile
_startx = null; ///< x value of start tile
_starty = null; ///< y value of start tile
_x = null; ///< x value of current tile
_y = null; ///< y value of current tile
_current_tile = null; ///< current tile
_dx = null;
_dy = null;
_Steps = null; ///< see GetStep()
_Stage = null; ///< see GetStage()
_StageMax = null;
_StageSteps = null;
constructor() {
this._dx = -1;
this._dy = 0;
this._Steps = 0;
this._Stage = 1;
this._StageMax = 1;
this._StageSteps = 0;
}
/** \publicsection
* \brief Sets the starting tile for SpiralWalker
* \see Restart()
*/
function Start(Tile);
/** \brief Resets the variables for the SpiralWalker
* \see Restart()
*/
function Reset();
/** \brief Moves the SpiralWalker to the original starting position
* \see Reset()
*/
function Restart();
/** \brief 'Walks' the SpiralWalker one tile at a tile
* \return the tile that the SpiralWalker is now "standing on"
* \note This is where (most) of the action is!
* \note Before calling this function, you need to set the Start().
*/
function Walk();
/** \brief Returns the tile the SpiralWalker is starting on
* \return The tile the SpiralWalker is starting on
* \see Start()
*/
function GetStart() { return this._start; }
/** \brief Returns the Stage the SpiralWalker is on.
*
* Basically, the line segments its completed plus one; it takes four
* stages to complete a revolution.
* \return stage number
* \see GetStep()
* \todo Add an image showing how stages are counted
*/
function GetStage() { return this._Stage; }
/** \brief Returns the Tile the SpiralWalker is on.
* \return the Tile the SpiralWalker is on.
* \see GetStep()
* \see GetStage()
*/
function GetTile() { return this._current_tile; }
/** \brief Returns the number of steps the SpiralWalker has done.
* \return The number of steps the SpiralWalker has done.
* \see GetStage()
* \todo Add an image showing how steps are counted
*/
function GetStep() { return this._Steps; }
};
// == Function definition ==================================================
function _MinchinWeb_SW_::Start(Tile) {
this._start = Tile;
this._startx = AIMap.GetTileX(Tile);
this._starty = AIMap.GetTileY(Tile);
this._x = this._startx;
this._y = this._starty;
this._current_tile = this._start;
this._dx = -1;
this._dy = 0;
this._Steps = 0;
// this._Stage = 1;
this._Stage = 1;
this._StageMax = 1;
this._StageSteps = 0;
}
function _MinchinWeb_SW_::Reset() {
this._start = null;
this._startx = null;
this._starty = null;
this._x = null;
this._y = null;
this._current_tile = null;
}
function _MinchinWeb_SW_::Restart() {
this._x = this._startx;
this._y = this._starty;
this._current_tile = this._start;
this._dx = -1;
this._dy = 0;
this._Steps = 0;
this._Stage = 1;
this._StageMax = 1;
this._StageSteps = 0;
}
function _MinchinWeb_SW_::Walk() {
if (this._Steps == 0) {
this._Steps++;
} else {
this._x += this._dx;
this._y += this._dy;
this._StageSteps ++;
this._Steps ++;
// Check if it's time to turn
if (this._StageSteps == this._StageMax) {
this._StageSteps = 0;
if (this._Stage % 2 == 0) {
this._StageMax++;
}
this._Stage ++;
// Turn Clockwise
switch (this._dx) {
case 0:
switch (this._dy) {
case -1:
this._dx = -1;
this._dy = 0;
break;
case 1:
this._dx = 1;
this._dy = 0;
break;
}
break;
case -1:
this._dx = 0;
this._dy = 1;
break;
case 1:
this._dx = 0;
this._dy = -1;
break;
}
}
}
_MinchinWeb_Log_.Note(" SpiralWalker.Walk: " + this._dx + " " + this._dy + " : " + this._Steps + " " + this._Stage + " " + this._StageSteps + " " + this._StageMax + " :: " + this._x + ", " + this._y, 7);
this._current_tile = AIMap.GetTileIndex(this._x, this._y);
// AISign.BuildSign(this._current_tile, "" + this._Steps);
return this._current_tile;
}
// EOF
MetaLib-7/Station.nut 0000666 0000000 0000000 00000010135 12273314170 013042 0 ustar 0000000 0000000 /* Station functions v.3 r.253 [2011-07-01],
* part of Minchinweb's MetaLibrary v.6,
* Copyright © 2011-14 by W. Minchin. For more info,
* please visit https://github.com/MinchinWeb/openttd-metalibrary
*
* Permission is granted to you to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell this software, and provide these
* rights to others, provided:
*
* + The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the software.
* + Attribution is provided in the normal place for recognition of 3rd party
* contributions.
* + You accept that this software is provided to you "as is", without warranty.
*/
/* Functions provided:
* MinchinWeb.Station.IsCargoAccepted(StationID, CargoID)
* - Checks whether a certain Station accepts a given cargo
* - Returns null if the StationID or CargoID are invalid
* - Returns true or false, depending on if the cargo is accepted
* .IsNextToDock(TileID)
* - Checks whether a given tile is next to a dock. Returns true if
* this is the case
* .DistanceFromStation(VehicleID, StationID)
* - Returns the distance between a given vehicle and a given station
* - Designed to be usable as a Valuator on a list of vehicles
*/
/** \brief Station
* \version v.3 (2011-07-21)
* \author W. Minchin (%MinchinWeb)
* \since MetaLibrary v.2
*
* These are functions relating to dealing with stations.
*/
class _MinchinWeb_Station_ {
main = null;
/** \publicsection
* \brief Checks whether a certain Station accepts a given cargo
* \param StationID ID of the station (as an integer)
* \param CargoID ID of the cargo (as an integer)
* \note Can be used as a Valuator on a AIList of stations
* \return Returns `null` if the StationID or CargoID are invalid.
* Returns true or false, depending on if the cargo is accepted
* \todo Add example of valuator code
* \static
*/
function IsCargoAccepted(StationID, CargoID);
/** \brief Checks whether a given tile is next to a dock.
* \param TileID ID of the tile (as an integer)
* \return `True` if the tile is next to a dock, `False` otherwise.
* \static
*/
function IsNextToDock(TileID);
/** \brief Returns the distance between a given vehicle and a given station.
* \note Designed to be usable as a Valuator on a AIList of vehicles
* \param VehicleID ID of the vehicle (as an integer)
* \param StationID ID of the station (as an integer)
* \return Manhattan Distance between the vehicle and the station.
* \todo Add check that supplied VehicleID and StationID are valid
* \todo Add example of valuator code
* \static
*/
function DistanceFromStation(VehicleID, StationID);
};
// == Function definitions ==================================================
function _MinchinWeb_Station_::IsCargoAccepted(StationID, CargoID) {
if (!AIStation.IsValidStation(StationID) || !AICargo.IsValidCargo(CargoID)) {
AILog.Warning("MinchinWeb.Station.IsCargoAccepted() was provided with invalid input. Was provided " + StationID + " and " + CargoID + ".");
return null;
} else {
local AllCargos = AICargoList_StationAccepting(StationID);
_MinchinWeb_Log_.Note("MinchinWeb.Station.IsCargoAccepted() was provided with " + StationID + " and " + CargoID + ". AllCargos: " + AllCargos.Count(), 6);
if (AllCargos.HasItem(CargoID)) {
return true;
} else {
return false;
}
}
}
function _MinchinWeb_Station_::IsNextToDock(TileID) {
local offsets = [0, AIMap.GetTileIndex(0, 1), AIMap.GetTileIndex(0, -1),
AIMap.GetTileIndex(1, 0), AIMap.GetTileIndex(-1, 0)];
foreach (offset in offsets) {
if (AIMarine.IsDockTile(TileID + offset)) {
return true;
}
}
return false;
}
function _MinchinWeb_Station_::DistanceFromStation(VehicleID, StationID) {
local VehicleTile = AIVehicle.GetLocation(VehicleID);
local StationTile = AIBaseStation.GetLocation(StationID);
return AITile.GetDistanceManhattanToTile(VehicleTile, StationTile);
}
// EOF
MetaLib-7/Waterbody.Check.nut 0000666 0000000 0000000 00000020743 12304431342 014377 0 ustar 0000000 0000000 /* Waterbody Check v.1, r.193, [2012-01-05],
* part of Minchinweb's MetaLibrary v.2,
* originally part of WmDOT v.7
* Copyright © 2011-14 by W. Minchin. For more info,
* please visit https://github.com/MinchinWeb/openttd-metalibrary
*
* Permission is granted to you to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell this software, and provide these
* rights to others, provided:
*
* + The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the software.
* + Attribution is provided in the normal place for recognition of 3rd party
* contributions.
* + You accept that this software is provided to you "as is", without warranty.
*/
/** \brief Waterbody Check (deprecated)
* \version v.1 (2012-01-05)
* \author W. Minchin (MinchinWeb)
* \since MetaLibrary v.1
*
* \note Waterbody Check has been deprecated in favour of
* \_MinchinWeb\_Lakes\_ (Lakes).
*
* Waterbody check is in effect a specialized pathfinder. It serves to check
* whether two points are in the same waterbody (i.e. a ship could travel
* between them). It is optimized to run extremely fast (I hope!). It can
* be called separately, but was originally designed as a pre-run check
* for my Ship Pathfinder (also included in this MetaLibrary).
*
* It is based on the NoAI Team's Road Pathfinder v3.
*
* \requires Graph.AyStar v6 library
* \see \_MinchinWeb\_ShipPathfinder\_
* \see \_MinchinWeb\_Lakes\_
* \todo Add a cost for turns that then this would function as a 'real'
* pathfinder (maybe...)
*/
/* This file provides functions:
* MinchinWeb.WaterbodyCheck.InitializePath(sources, goals)
* - Set up the pathfinder
* .FindPath(iterations)
* - Run the pathfinder; returns false if it
* isn't finished the path if it has
* finished, and null if it can't find a path
* .WaterbodyCheck.Cost.[xx]
* - Allows you to set or find out the
* pathfinder costs directly.
* .GetPathLength()
* - Runs over the path to determine its length
* .PresetSafety(Start, End)
* - Caps the pathfinder as twice the Manhattan
* distance between the two tiles
*
* See the function below for valid entries.
*/
class _MinchinWeb_WBC_ {
_aystar_class = import("graph.aystar", "", 6);
_cost_per_tile = null;
_max_cost = null; ///< The maximum cost for a route.
_distance_penalty = null; ///< Penalty to use to speed up pathfinder, 1 is no penalty
_pathfinder = null;
cost = null; ///< Used to change the costs.
_running = null; ///< Is it running?
_mypath = null;
constructor() {
this._max_cost = 16000;
this._cost_per_tile = 1;
this._distance_penalty = 1;
this._pathfinder = this._aystar_class(this, this._Cost, this._Estimate, this._Neighbours, this._CheckDirection);
this.cost = this.Cost(this);
this._running = false;
}
/** \publicsection
* Initialize a path search between sources and goals.
* @param sources The source tiles.
* @param goals The target tiles.
* @see AyStar::InitializePath()
*/
function InitializePath(sources, goals) {
local nsources = [];
foreach (node in sources) {
nsources.push([node, 0xFF]);
}
this._pathfinder.InitializePath(nsources, goals);
this._mypath = null;
}
/**
* 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 immediately 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.
* @return 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);
/** \brief How long is the (found) path?
* \return Path length in tiles
*/
function GetPathLength();
/** \brief Caps the pathfinder as twice the Manhattan distance between the
* two tiles
*/
function PresetSafety(Start, End);
/** \privatesection
*/
function _Cost(self, path, new_tile, new_direction);
function _Estimate(self, cur_tile, cur_direction, goal_tiles);
function _Neighbours(self, path, cur_node);
function _CheckDirection(self, tile, existing_direction, new_direction);
function _GetDirection(from, to);
};
class _MinchinWeb_WBC_.Cost {
/** \brief Used to set (and get) pathfinder parameters
*
* Valid values are:
* - max_cost
* - cost_per_tile
* - distance_penalty
*/
_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 "cost_per_tile": this._main._cost_per_tile = val; break;
case "distance_penalty": this._main._distance_penalty = 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 "cost_per_tile": return this._main._cost_per_tile;
case "distance_penalty": return this._main._distance_penalty;
default: throw("the index '" + idx + "' does not exist");
}
}
constructor(main) {
this._main = main;
}
};
function _MinchinWeb_WBC_::FindPath(iterations) {
local ret = this._pathfinder.FindPath(iterations);
this._running = (ret == false) ? true : false;
if (this._running == false) { this._mypath = ret; }
return ret;
}
function _MinchinWeb_WBC_::_Cost(self, path, new_tile, new_direction) {
/* 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 = self._cost_per_tile;
// if (AIMarine.AreWaterTilesConnected(new_tile, prev_tile) != true) {
// cost = self._max_cost * 10; // Basically, way over the top
// }
// return path.GetCost() + cost;
// this pathfinder will only return tiles adjacent to one another (done in Neighbours...)
return path.GetCost() + self._cost_per_tile;
}
function _MinchinWeb_WBC_::_Estimate(self, cur_tile, cur_direction, goal_tiles) {
local min_cost = self._max_cost;
/* As estimate we multiply the lowest possible cost for a single tile with
* with the minimum number of tiles we need to traverse. */
foreach (tile in goal_tiles) {
min_cost = min(AIMap.DistanceManhattan(cur_tile, tile) * self._cost_per_tile * self._distance_penalty, min_cost);
}
return min_cost;
}
function _MinchinWeb_WBC_::_Neighbours(self, path, cur_node) {
/* self._max_cost is the maximum path cost, if we go over it, the path isn't valid. */
if (path.GetCost() >= self._max_cost) return [];
local tiles = [];
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;
if (AIMarine.AreWaterTilesConnected(cur_node, next_tile)) {
tiles.push([next_tile, self._GetDirection(cur_node, next_tile)]);
}
}
/** \todo Add diagonals to possible neighbours
*/
return tiles;
}
function _MinchinWeb_WBC_::_CheckDirection(self, tile, existing_direction, new_direction) {
return false;
}
function _MinchinWeb_WBC_::_GetDirection(from, to) {
if (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;
}
function _MinchinWeb_WBC_::GetPathLength()
{
// Runs over the path to determine its length
if (this._running) {
AILog.Warning("You can't get the path length while there's a running pathfinder.");
return false;
}
if (this._mypath == null) {
AILog.Warning("You have tried to get the length of a 'null' path.");
return false;
}
return _mypath.GetLength();
}
function _MinchinWeb_WBC_::PresetSafety(Start, End)
{
// Caps the pathfinder as twice the Manhattan distance between the two tiles
this._max_cost = this._cost_per_tile * AIMap.DistanceManhattan(Start, End) * 2;
}