forked from Mirrors/openclonk
618 lines
20 KiB
C
618 lines
20 KiB
C
/**
|
|
Cable Station
|
|
Library for cable stations and crossings. This is included by
|
|
cable crossings, and should be included by structures which
|
|
want to make use of the cable network.
|
|
|
|
@author Randrian, Clonkonaut, Maikel
|
|
*/
|
|
|
|
/*--- Overloads ---*/
|
|
|
|
// Overload these functions as you feel fit
|
|
|
|
// This function is called whenever a change in the cable network occured, i.e. destinations have been added / removed.
|
|
private func DestinationsUpdated() { }
|
|
|
|
// Called by cable lines whenever a car starts travelling along a connected cable.
|
|
// Can be used to start animation or sounds or similar.
|
|
// count is a value indicating the amount of activations.
|
|
public func CableActivation(int count) { }
|
|
|
|
// Called likewise as Activation() whenever a car leaves the cable.
|
|
// count is a value indicating the amount of deactivations (e.g. a cable with more than one car broke).
|
|
public func CableDeactivation(int count) { }
|
|
|
|
// Called by arriving cable cars if this station is the final stop
|
|
public func OnCableCarArrival(object car) { }
|
|
|
|
// Called by cable cars if it stopped at this station (usually because of a problem)
|
|
public func OnCableCarStopped(object car) { }
|
|
|
|
// Called by departing cable cars if it just starts a new journey
|
|
public func OnCableCarDeparture(object car) { }
|
|
|
|
// Called by a cable car that has been hooked up to the rail at this station
|
|
public func OnCableCarEngaged(object car) { }
|
|
|
|
// Called by a cable car that has been taken off the rail at this station
|
|
public func OnCableCarDisengaged(object car) { }
|
|
|
|
// Called by a cable car that has been destroyed at this station
|
|
public func OnCableCarDestruction(object car) { }
|
|
|
|
// Called when a cable car wants to pick up resources for a delivery
|
|
public func OnCableCarPickUp(object car, proplist order) { }
|
|
|
|
// Called when a cable car with a requested delivery arrives
|
|
public func OnCableCarDelivery(object car, proplist order) { }
|
|
|
|
// Called by other stations to check if a certain object and amount are available for delivery at this station.
|
|
// Return true if there are means to collect the required amount.
|
|
public func IsAvailable(proplist order) { return false; }
|
|
|
|
// Called to get idle cable cars at this station.
|
|
public func GetIdleCars() { return []; }
|
|
|
|
|
|
/*--- Callbacks ---*/
|
|
|
|
// Be sure to always call these via _inherited(...);
|
|
|
|
public func Construction()
|
|
{
|
|
destination_list = [];
|
|
request_queue = [];
|
|
return _inherited(...);
|
|
}
|
|
|
|
public func Destruction()
|
|
{
|
|
// The connection with other stations is broken via the cable and the network updating is handled there.
|
|
// So there is updating to be performed here.
|
|
return _inherited(...);
|
|
}
|
|
|
|
/*--- Status ---*/
|
|
|
|
local lib_crossing_is_station;
|
|
|
|
/** This object is a cable crossing.
|
|
E.g. checked by whatever object wants to connect a cable.
|
|
Does not mean that there actually is a cable connected to this crossing.
|
|
*/
|
|
public func IsCableCrossing() { return true; }
|
|
|
|
/** This function should return true if this crossing is considered a station.
|
|
A station is selectable as a target if a lorry is sent its way.
|
|
Functional buildings should always be a station, the 'crossing' building only if set to.
|
|
*/
|
|
public func IsCableStation() { return lib_crossing_is_station; }
|
|
|
|
/*--- Interface ---*/
|
|
|
|
// For switching the station status
|
|
public func SetCableStation(bool station)
|
|
{
|
|
lib_crossing_is_station = station;
|
|
}
|
|
|
|
// Returns the cable hookup position for proper positioning of a car along the line.
|
|
public func GetCablePosition(array coordinates, int prec)
|
|
{
|
|
if (!prec)
|
|
prec = 1;
|
|
if (!coordinates)
|
|
coordinates = [];
|
|
coordinates[0] = GetX(prec);
|
|
coordinates[1] = GetY(prec);
|
|
if (this.LineAttach)
|
|
{
|
|
coordinates[0] += this.LineAttach[0] * prec;
|
|
coordinates[1] += this.LineAttach[1] * prec;
|
|
}
|
|
return coordinates;
|
|
}
|
|
|
|
// Called by cable cars to retrieve selectable destinations for the destination selection menu.
|
|
// Returns the list of destinations sorted by shortest distance first.
|
|
public func GetDestinationList()
|
|
{
|
|
var dest_list = [];
|
|
for (var destination in destination_list)
|
|
if (destination[const_finaldestination]->IsCableStation())
|
|
PushBack(dest_list, [destination[const_finaldestination], destination[const_distance]]);
|
|
SortArrayByArrayElement(dest_list, 1);
|
|
for (var index = 0; index < GetLength(dest_list); index++)
|
|
dest_list[index] = dest_list[index][0];
|
|
return dest_list;
|
|
}
|
|
|
|
/*--- Maintaining the destination list ---*/
|
|
|
|
/* Functions:
|
|
GetDestinations()
|
|
AddCableConnection(object cable)
|
|
AddCableDestinations(array new_list, object crossing)
|
|
AddCableDestination(object new_destination, object crossing, int distance_add)
|
|
ClearConnections()
|
|
RenewConnections()
|
|
*/
|
|
|
|
/** Returns the destination array so it can be used by other crossings.
|
|
*/
|
|
public func GetDestinations()
|
|
{
|
|
// This is a nested array, so ensure a proper deep copy is made.
|
|
var deep_copy = [];
|
|
for (var dest in destination_list)
|
|
PushBack(deep_copy, dest[:]);
|
|
return deep_copy;
|
|
}
|
|
|
|
// Stores the next crossing (waypoint) to take when advancing to a certain final point. The list may not contain the crossing itself.
|
|
// Scheme (2D array): [Desired final point, Next waypoint to take, Distance (not airline!) until final point].
|
|
local destination_list;
|
|
|
|
// Constants for easier script reading
|
|
// These correspond to the aforementioned values of destination_list
|
|
local const_finaldestination = 0; // :D
|
|
local const_nextwaypoint = 1;
|
|
local const_distance = 2;
|
|
|
|
/** Adds a new connection via the cable \a cable to this crossing
|
|
Does nothing if the other connected object of the cable is not a cable crossing
|
|
@param cable The newly connected cable
|
|
*/
|
|
public func AddCableConnection(object cable)
|
|
{
|
|
// Failsafe
|
|
if (!cable || !cable->~IsCableLine())
|
|
return false;
|
|
// Line setup finished?
|
|
var other_crossing = cable->~GetConnectedObject(this);
|
|
if (!other_crossing || !other_crossing->~IsCableCrossing())
|
|
return false;
|
|
// Acquire destinations of the other crossing, all these are now in reach
|
|
AddCableDestinations(other_crossing->GetDestinations(), other_crossing);
|
|
// Send own destinations, now in reach for the other one
|
|
other_crossing->AddCableDestinations(this->GetDestinations(), this);
|
|
// Destinations have been updated for this crossing.
|
|
DestinationsUpdated();
|
|
return true;
|
|
}
|
|
|
|
public func RemoveCableConnection(object cable)
|
|
{
|
|
// It is easiest to just update all connections.
|
|
UpdateConnections();
|
|
// Awesome, more power to the network!
|
|
DestinationsUpdated();
|
|
return true;
|
|
}
|
|
|
|
/** Adds a whole list of destinations to the crossing
|
|
* @param new_list The new destination list, formated like a crossing's normal destination list
|
|
* @param crossing The crossing where this list comes from
|
|
*/
|
|
public func AddCableDestinations(array new_list, object crossing, bool has_reversed)
|
|
{
|
|
if (!crossing)
|
|
return false;
|
|
// Append crossing itself to the list with zero distance.
|
|
new_list[GetLength(new_list)] = [crossing, crossing, 0];
|
|
// This value is to be added to every distance
|
|
var distance_add = ObjectDistance(crossing);
|
|
// Check every new destination
|
|
for (var list_item in new_list)
|
|
{
|
|
if (!list_item)
|
|
continue;
|
|
// Destination may not be this crossing.
|
|
if (list_item[const_finaldestination] == this)
|
|
continue;
|
|
// Check whether the destination is already in already in the list.
|
|
var handled = false;
|
|
for (var i = 0, destination = nil; i < GetLength(destination_list); i++)
|
|
{
|
|
if (!destination_list[i])
|
|
continue;
|
|
destination = destination_list[i];
|
|
if (destination[const_finaldestination] == list_item[const_finaldestination])
|
|
{
|
|
// Already known destination, check whether the new path is shorter
|
|
handled = true;
|
|
if (destination[const_distance] > list_item[const_distance] + distance_add)
|
|
{
|
|
// It is shorter, replace, route through crossing
|
|
destination_list[i] = [list_item[const_finaldestination], crossing, list_item[const_distance] + distance_add];
|
|
// Inform the destination.
|
|
list_item[const_finaldestination]->UpdateCableDestination(this, crossing, distance_add);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Destination is replaced or to be dismissed (because the new path would be longer), do nothing.
|
|
if (handled)
|
|
continue;
|
|
|
|
// Destination is a new one, add to the list.
|
|
AddCableDestination(list_item[const_finaldestination], crossing, list_item[const_distance] + distance_add);
|
|
// Add me to the new destination (the way to me is the same than to crossing).
|
|
if (list_item[const_finaldestination] != crossing && !has_reversed)
|
|
{
|
|
// This new crossing may have a bunch of other connections that need to be explored and potentially added.
|
|
// The new crossing is connected to this crossing via the crossing specified as the parameter to this function.
|
|
// So for the new destinations we want to add, we need to add the distance to the crossing.
|
|
var add_destinations = list_item[const_finaldestination]->GetDestinations();
|
|
var add_dest_distance = 0;
|
|
for (var dest in crossing->GetDestinations())
|
|
if (dest[const_finaldestination] == list_item[const_finaldestination])
|
|
add_dest_distance = dest[const_distance];
|
|
for (var dest in add_destinations)
|
|
dest[const_distance] += add_dest_distance;
|
|
this->AddCableDestinations(add_destinations, crossing, has_reversed);
|
|
|
|
// However, this crossing and its destinations must also be added in reverse to the new crossing found.
|
|
var reverse_destinations = this->GetDestinations();
|
|
PushBack(reverse_destinations, [this, crossing, 0]);
|
|
var reverse_crossing = nil;
|
|
for (var dest in list_item[const_finaldestination]->GetDestinations())
|
|
if (dest[const_finaldestination] == crossing)
|
|
reverse_crossing = dest[const_nextwaypoint];
|
|
if (reverse_crossing == nil)
|
|
return FatalError("AddCableDestinations(): reverse_crossing not found.");
|
|
var add_reverse_distance = distance_add;
|
|
if (reverse_crossing != crossing)
|
|
{
|
|
for (var dest in reverse_crossing->GetDestinations())
|
|
if (dest[const_finaldestination] == crossing)
|
|
add_reverse_distance += dest[const_distance];
|
|
if (add_reverse_distance == distance_add)
|
|
return FatalError("AddCableDestinations(): reverse_distance not correct.");
|
|
}
|
|
for (var dest in reverse_destinations)
|
|
dest[const_distance] += add_reverse_distance;
|
|
list_item[const_finaldestination]->AddCableDestinations(reverse_destinations, reverse_crossing, true);
|
|
}
|
|
}
|
|
DestinationsUpdated();
|
|
return true;
|
|
}
|
|
|
|
/** Adds a single destination \a new_destination. The link is \a crossing; \a crossing should already be known, otherwise it returns false.
|
|
* @param new_destination The destination to add
|
|
* @param crossing The crossing which links to the new destination
|
|
* @param distance_add The distance between crossing and new_destination
|
|
*/
|
|
public func AddCableDestination(object new_destination, object crossing, int distance)
|
|
{
|
|
// Failsafes.
|
|
if (!new_destination || !crossing)
|
|
return;
|
|
if (new_destination == this)
|
|
return;
|
|
if (!IsDirectlyConnectToCrossing(crossing))
|
|
return FatalError(Format("destination to %v for %v is not connected to crossing link %v.", new_destination, this, crossing));
|
|
// Add to destination list.
|
|
PushBack(destination_list, [new_destination, crossing, distance]);
|
|
DestinationsUpdated();
|
|
return;
|
|
}
|
|
|
|
/** Updates the path to \a known_destination via \a crossing (e.g. because the path is shorter through \a crossing)
|
|
* @param known_destination The destination to update
|
|
* @param crossing The crossing which links to the destination
|
|
* @param distance_add The distance between crossing and known_destination
|
|
*/
|
|
public func UpdateCableDestination(object known_destination, object crossing, int distance_add)
|
|
{
|
|
// Failsafe
|
|
if (!known_destination || !crossing) return false;
|
|
if (known_destination == crossing) return false;
|
|
// Find the entries of crossing and known_destination
|
|
var crossing_item, destination_item, i = 0;
|
|
for (var list_item in destination_list)
|
|
{
|
|
if (!list_item)
|
|
continue;
|
|
if (list_item[const_finaldestination] == crossing)
|
|
crossing_item = list_item;
|
|
if (list_item[const_finaldestination] == known_destination)
|
|
destination_item = i;
|
|
i++;
|
|
}
|
|
// Failsafe
|
|
if (!crossing_item || !destination_item)
|
|
return false;
|
|
// Save the updated path
|
|
destination_list[destination_item][const_nextwaypoint] = crossing_item[const_nextwaypoint];
|
|
destination_list[destination_item][const_distance] = crossing_item[const_distance] + distance_add;
|
|
DestinationsUpdated();
|
|
return true;
|
|
}
|
|
|
|
local clearing;
|
|
|
|
// Updates the network for this crossing, this is called when a cable to this crossing has changed.
|
|
// It first clears every waypoint from the network and then renews the whole information.
|
|
public func UpdateConnections()
|
|
{
|
|
// Clear this crossing and then other connected crossings.
|
|
ClearConnections();
|
|
RenewConnections();
|
|
return;
|
|
}
|
|
|
|
// Called automatically by UpdateConnections, see description there.
|
|
public func ClearConnections()
|
|
{
|
|
if (clearing)
|
|
return;
|
|
clearing = true;
|
|
destination_list = [];
|
|
for (var connection in FindObjects(Find_Func("IsConnectedTo", this)))
|
|
{
|
|
if (!connection->~IsCableLine())
|
|
continue;
|
|
var other_crossing = connection->~GetConnectedObject(this);
|
|
if (!other_crossing || !other_crossing->~IsCableCrossing())
|
|
continue;
|
|
other_crossing->ClearConnections();
|
|
}
|
|
}
|
|
|
|
// Called automatically by UpdateConnections, see description there.
|
|
public func RenewConnections()
|
|
{
|
|
if (!clearing)
|
|
return;
|
|
clearing = false;
|
|
for (var connection in FindObjects(Find_Func("IsConnectedTo", this)))
|
|
{
|
|
if (!connection->~IsCableLine())
|
|
continue;
|
|
var other_crossing = connection->~GetConnectedObject(this);
|
|
if (!other_crossing || !other_crossing->~IsCableCrossing())
|
|
continue;
|
|
// Acquire destinations of the other crossing, all these are now in reach
|
|
AddCableDestinations(other_crossing->GetDestinations(), other_crossing);
|
|
// Send own destinations, now in reach for the other one
|
|
other_crossing->AddCableDestinations(this->GetDestinations(), this);
|
|
// Destinations have been updated for this crossing.
|
|
DestinationsUpdated();
|
|
// Also update other connections.
|
|
other_crossing->RenewConnections();
|
|
}
|
|
}
|
|
|
|
// Returns whether there is a direct conenctions between this and the other crossing.
|
|
public func IsDirectlyConnectToCrossing(object other_crossing)
|
|
{
|
|
for (var connection in FindObjects(Find_Func("IsConnectedTo", this)))
|
|
{
|
|
if (!connection->~IsCableLine())
|
|
continue;
|
|
if (other_crossing == connection->~GetConnectedObject(this))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/*-- Pathfinding --*/
|
|
|
|
/* Functions:
|
|
GetNextWaypoint(object end)
|
|
GetLengthToTarget(object end)
|
|
*/
|
|
|
|
/** Returns the waypoint to take next for the desired final point \a end
|
|
* @param end The final destination for the information is queried
|
|
*/
|
|
public func GetNextWaypoint(object end)
|
|
{
|
|
if (!destination_list)
|
|
return nil;
|
|
for (var item in destination_list)
|
|
{
|
|
if (!item)
|
|
continue;
|
|
if (item[const_finaldestination] == end)
|
|
return item[const_nextwaypoint];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
/** Returns the actual traveling distance for the desired final point \a end
|
|
* This is not the airline distance but the length of all cables to take via traveling
|
|
* @param end The final destination for the information is queried
|
|
*/
|
|
public func GetLengthToTarget(object end)
|
|
{
|
|
if (!destination_list)
|
|
return nil;
|
|
for (var item in destination_list)
|
|
{
|
|
if (!item)
|
|
continue;
|
|
if (item[const_finaldestination] == end)
|
|
return item[const_distance];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
|
|
/*-- Auto production --*/
|
|
|
|
local request_queue;
|
|
|
|
// Add a new acquiring order, this order will be delivered to this station and is a proplist with information.
|
|
// { type = <id of the object>, min_amount = <minimal amount to be delivered>, max_amount = <maximal amount to be delivered>}.
|
|
public func AddRequest(proplist order)
|
|
{
|
|
if (!order || !order.type)
|
|
return false;
|
|
if (!order.min_amount)
|
|
order.min_amount = 1;
|
|
|
|
// First of all check if a similar request already is on the line
|
|
// Similar requests will be dismissed, even if they are technically new
|
|
for (var request in request_queue)
|
|
if (request.type == order.type)
|
|
if (request.min_amount == order.min_amount)
|
|
return true; // The request is considered handled
|
|
|
|
// Find source station which has the order's items.
|
|
var source = CheckAvailability(order);
|
|
if (!source)
|
|
return false;
|
|
|
|
// Find cable car that is at source station or closest to source station.
|
|
var car = source->GetAvailableCableCar(order, this);
|
|
if (!car)
|
|
return false;
|
|
|
|
// Great. Start working immediately
|
|
PushBack(request_queue, order);
|
|
car->AddRequest(order, this, source);
|
|
return true;
|
|
}
|
|
|
|
// Check all connected stations for available objects.
|
|
public func CheckAvailability(proplist order)
|
|
{
|
|
var nearest_station;
|
|
var length_to;
|
|
// Loop over all stations, including this one.
|
|
var network_stations = [this];
|
|
for (var station in GetDestinations())
|
|
if (station)
|
|
PushBack(network_stations, station[const_finaldestination]);
|
|
for (var station in network_stations)
|
|
{
|
|
if (station->IsAvailable(order))
|
|
{
|
|
if (!nearest_station)
|
|
{
|
|
nearest_station = station;
|
|
length_to = GetLengthToTarget(nearest_station);
|
|
}
|
|
else
|
|
{
|
|
// Storages (like chests) are always preferred over other producer because the items
|
|
// might be stored in another producer to produce something.
|
|
if (station->~IsStorage() && !nearest_station->~IsStorage())
|
|
{
|
|
nearest_station = station;
|
|
length_to = GetLengthToTarget(nearest_station);
|
|
}
|
|
else if (nearest_station->~IsStorage() && !station->~IsStorage())
|
|
continue;
|
|
// Otherwise the shorter the path, the better.
|
|
else if (GetLengthToTarget(station) < length_to)
|
|
{
|
|
nearest_station = station;
|
|
length_to = GetLengthToTarget(nearest_station);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nearest_station;
|
|
}
|
|
|
|
// Check if there is a cable car ready for delivery
|
|
public func GetAvailableCableCar(proplist order, object requesting_station)
|
|
{
|
|
|
|
}
|
|
|
|
// A delivery needs to be picked up at this station.
|
|
public func RequestPickUp(object car, proplist order)
|
|
{
|
|
OnCableCarPickUp(car, order);
|
|
}
|
|
|
|
// A delivery has arrived, remove it from the queue and handle the request
|
|
public func RequestArrived(object car, proplist order)
|
|
{
|
|
if (!HasRequest(order))
|
|
return;
|
|
|
|
OnCableCarDelivery(car, order);
|
|
RemoveRequest(order);
|
|
}
|
|
|
|
public func HasRequest(proplist order)
|
|
{
|
|
for (var test_order in request_queue)
|
|
if (test_order.type == order.type && test_order.min_amount == order.min_amount)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
public func RemoveRequest(proplist order)
|
|
{
|
|
for (var i = 0; i < GetLength(request_queue); i++)
|
|
{
|
|
if (request_queue[i].type != order.type)
|
|
continue;
|
|
if (request_queue[i].min_amount != order.min_amount)
|
|
continue;
|
|
break;
|
|
}
|
|
if (i >= GetLength(request_queue))
|
|
return false;
|
|
RemoveArrayIndex(request_queue, i, true);
|
|
return true;
|
|
}
|
|
|
|
|
|
/*-- Misc Queries --*/
|
|
|
|
// Returns the closest object satisfying find_crit that can be found on the cables of this network.
|
|
// Returns {obj = <closest object>, station1 = <first station>, station2 = <second station>}.
|
|
public func FindObjectOnNetworkCables(array find_crit)
|
|
{
|
|
var destinations = GetDestinations();
|
|
SortArrayByArrayElement(destinations, this.const_distance, false);
|
|
PushFront(destinations, [this, this, 0]);
|
|
for (var dest in destinations)
|
|
{
|
|
var station = dest[this.const_finaldestination];
|
|
// Check all cables to other stations.
|
|
for (var connection in FindObjects(Find_Func("IsConnectedTo", station)))
|
|
{
|
|
if (!connection->~IsCableLine())
|
|
continue;
|
|
var other_station = connection->~GetConnectedObject(station);
|
|
if (!other_station || !other_station->~IsCableCrossing())
|
|
continue;
|
|
var sx = station->GetCablePosition()[0];
|
|
var sy = station->GetCablePosition()[1];
|
|
var ox = other_station->GetCablePosition()[0];
|
|
var oy = other_station->GetCablePosition()[1];
|
|
var obj = Global->FindObject(Find_OnLine(sx, sy, ox, oy), find_crit);
|
|
if (obj)
|
|
return [obj, station, other_station];
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
// Returns a free cable car satsfying find_crit on the network closest to this station.
|
|
public func FindCableCar(array find_crit)
|
|
{
|
|
var destinations = GetDestinations();
|
|
SortArrayByArrayElement(destinations, this.const_distance, false);
|
|
PushFront(destinations, [this, this, 0]);
|
|
for (var dest in destinations)
|
|
{
|
|
var station = dest[this.const_finaldestination];
|
|
var car = Global->FindObject(Find_InArray(station->GetIdleCars()), find_crit);
|
|
if (car)
|
|
return car;
|
|
}
|
|
return nil;
|
|
}
|