OpenTTD/src/roadstop.cpp

377 lines
12 KiB
C++

/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file roadstop.cpp Implementation of the roadstop base class. */
#include "stdafx.h"
#include "roadveh.h"
#include "core/pool_func.hpp"
#include "roadstop_base.h"
#include "station_base.h"
#include "vehicle_func.h"
#include "safeguards.h"
/** The pool of roadstops. */
RoadStopPool _roadstop_pool("RoadStop");
INSTANTIATE_POOL_METHODS(RoadStop)
/**
* De-Initializes RoadStops.
*/
RoadStop::~RoadStop()
{
/* When we are the head we need to free the entries */
if (HasBit(this->status, RSSFB_BASE_ENTRY)) {
delete this->east;
delete this->west;
}
if (CleaningPool()) return;
}
/**
* Get the next road stop accessible by this vehicle.
* @param v the vehicle to get the next road stop for.
* @return the next road stop accessible.
*/
RoadStop *RoadStop::GetNextRoadStop(const RoadVehicle *v) const
{
for (RoadStop *rs = this->next; rs != nullptr; rs = rs->next) {
/* The vehicle cannot go to this roadstop (different roadtype) */
if (!HasTileAnyRoadType(rs->xy, v->compatible_roadtypes)) continue;
/* The vehicle is articulated and can therefore not go to a standard road stop. */
if (IsBayRoadStopTile(rs->xy) && v->HasArticulatedPart()) continue;
/* The vehicle can actually go to this road stop. So, return it! */
return rs;
}
return nullptr;
}
/**
* Join this road stop to another 'base' road stop if possible;
* fill all necessary data to become an actual drive through road stop.
* Also update the length etc.
*/
void RoadStop::MakeDriveThrough()
{
assert(this->east == nullptr && this->west == nullptr);
RoadStopType rst = GetRoadStopType(this->xy);
DiagDirection dir = GetRoadStopDir(this->xy);
/* Use absolute so we always go towards the northern tile */
TileIndexDiff offset = abs(TileOffsByDiagDir(dir));
/* Information about the tile north of us */
TileIndex north_tile = this->xy - offset;
bool north = IsDriveThroughRoadStopContinuation(this->xy, north_tile);
RoadStop *rs_north = north ? RoadStop::GetByTile(north_tile, rst) : nullptr;
/* Information about the tile south of us */
TileIndex south_tile = this->xy + offset;
bool south = IsDriveThroughRoadStopContinuation(this->xy, south_tile);
RoadStop *rs_south = south ? RoadStop::GetByTile(south_tile, rst) : nullptr;
/* Amount of road stops that will be added to the 'northern' head */
int added = 1;
if (north && rs_north->east != nullptr) { // (east != nullptr) == (west != nullptr)
/* There is a more northern one, so this can join them */
this->east = rs_north->east;
this->west = rs_north->west;
if (south && rs_south->east != nullptr) { // (east != nullptr) == (west != nullptr)
/* There more southern tiles too, they must 'join' us too */
ClrBit(rs_south->status, RSSFB_BASE_ENTRY);
/* Free the now unneeded entry structs */
delete rs_south->east;
delete rs_south->west;
/* Make all 'children' of the southern tile take the new master */
for (; IsDriveThroughRoadStopContinuation(this->xy, south_tile); south_tile += offset) {
rs_south = RoadStop::GetByTile(south_tile, rst);
if (rs_south->east == nullptr) break;
rs_south->east = rs_north->east;
rs_south->west = rs_north->west;
added++;
}
}
} else if (south && rs_south->east != nullptr) { // (east != nullptr) == (west != nullptr)
/* There is one to the south, but not to the north... so we become 'parent' */
this->east = rs_south->east;
this->west = rs_south->west;
SetBit(this->status, RSSFB_BASE_ENTRY);
ClrBit(rs_south->status, RSSFB_BASE_ENTRY);
} else {
/* We are the only... so we are automatically the master */
this->east = new Entry();
this->west = new Entry();
SetBit(this->status, RSSFB_BASE_ENTRY);
}
/* Now update the lengths */
added *= TILE_SIZE;
this->east->length += added;
this->west->length += added;
}
/**
* Prepare for removal of this stop; update other neighbouring stops
* if needed. Also update the length etc.
*/
void RoadStop::ClearDriveThrough()
{
assert(this->east != nullptr && this->west != nullptr);
RoadStopType rst = GetRoadStopType(this->xy);
DiagDirection dir = GetRoadStopDir(this->xy);
/* Use absolute so we always go towards the northern tile */
TileIndexDiff offset = abs(TileOffsByDiagDir(dir));
/* Information about the tile north of us */
TileIndex north_tile = this->xy - offset;
bool north = IsDriveThroughRoadStopContinuation(this->xy, north_tile);
RoadStop *rs_north = north ? RoadStop::GetByTile(north_tile, rst) : nullptr;
/* Information about the tile south of us */
TileIndex south_tile = this->xy + offset;
bool south = IsDriveThroughRoadStopContinuation(this->xy, south_tile);
RoadStop *rs_south = south ? RoadStop::GetByTile(south_tile, rst) : nullptr;
/* Must only be cleared after we determined which neighbours are
* part of our little entry 'queue' */
DoClearSquare(this->xy);
if (north) {
/* There is a tile to the north, so we can't clear ourselves. */
if (south) {
/* There are more southern tiles too, they must be split;
* first make the new southern 'base' */
SetBit(rs_south->status, RSSFB_BASE_ENTRY);
rs_south->east = new Entry();
rs_south->west = new Entry();
/* Keep track of the base because we need it later on */
RoadStop *rs_south_base = rs_south;
TileIndex base_tile = south_tile;
/* Make all (even more) southern stops part of the new entry queue */
for (south_tile += offset; IsDriveThroughRoadStopContinuation(base_tile, south_tile); south_tile += offset) {
rs_south = RoadStop::GetByTile(south_tile, rst);
rs_south->east = rs_south_base->east;
rs_south->west = rs_south_base->west;
}
/* Find the other end; the northern most tile */
for (; IsDriveThroughRoadStopContinuation(base_tile, north_tile); north_tile -= offset) {
rs_north = RoadStop::GetByTile(north_tile, rst);
}
/* We have to rebuild the entries to determine the length of the roadstop. */
rs_south_base->east->Rebuild(rs_south_base);
rs_south_base->west->Rebuild(rs_south_base);
assert(HasBit(rs_north->status, RSSFB_BASE_ENTRY));
rs_north->east->Rebuild(rs_north);
rs_north->west->Rebuild(rs_north);
} else {
/* Only we left, so simple update the length. */
rs_north->east->length -= TILE_SIZE;
rs_north->west->length -= TILE_SIZE;
}
} else if (south) {
/* There is only something to the south. Hand over the base entry */
SetBit(rs_south->status, RSSFB_BASE_ENTRY);
rs_south->east->length -= TILE_SIZE;
rs_south->west->length -= TILE_SIZE;
} else {
/* We were the last */
delete this->east;
delete this->west;
}
/* Make sure we don't get used for something 'incorrect' */
ClrBit(this->status, RSSFB_BASE_ENTRY);
this->east = nullptr;
this->west = nullptr;
}
/**
* Leave the road stop
* @param rv the vehicle that leaves the stop
*/
void RoadStop::Leave(RoadVehicle *rv)
{
if (IsBayRoadStopTile(rv->tile)) {
/* Vehicle is leaving a road stop tile, mark bay as free */
this->FreeBay(HasBit(rv->state, RVS_USING_SECOND_BAY));
this->SetEntranceBusy(false);
}
}
/**
* Enter the road stop
* @param rv the vehicle that enters the stop
* @return whether the road stop could actually be entered
*/
bool RoadStop::Enter(RoadVehicle *rv)
{
if (IsBayRoadStopTile(this->xy)) {
/* For normal (non drive-through) road stops
* Check if station is busy or if there are no free bays or whether it is a articulated vehicle. */
if (this->IsEntranceBusy() || !this->HasFreeBay() || rv->HasArticulatedPart()) return false;
SetBit(rv->state, RVS_IN_ROAD_STOP);
/* Allocate a bay and update the road state */
uint bay_nr = this->AllocateBay();
SB(rv->state, RVS_USING_SECOND_BAY, 1, bay_nr);
/* Mark the station entrance as busy */
this->SetEntranceBusy(true);
return true;
}
/* Indicate a drive-through stop */
SetBit(rv->state, RVS_IN_DT_ROAD_STOP);
return true;
}
/**
* Find a roadstop at given tile
* @param tile tile with roadstop
* @param type roadstop type
* @return pointer to RoadStop
* @pre there has to be roadstop of given type there!
*/
/* static */ RoadStop *RoadStop::GetByTile(TileIndex tile, RoadStopType type)
{
const Station *st = Station::GetByTile(tile);
for (RoadStop *rs = st->GetPrimaryRoadStop(type);; rs = rs->next) {
if (rs->xy == tile) return rs;
assert(rs->next != nullptr);
}
}
/**
* Checks whether the 'next' tile is still part of the road same drive through
* stop 'rs' in the same direction for the same vehicle.
* @param rs the road stop tile to check against
* @param next the 'next' tile to check
* @return true if the 'next' tile is part of the road stop at 'next'.
*/
/* static */ bool RoadStop::IsDriveThroughRoadStopContinuation(TileIndex rs, TileIndex next)
{
return IsTileType(next, MP_STATION) &&
GetStationIndex(next) == GetStationIndex(rs) &&
GetStationType(next) == GetStationType(rs) &&
GetRoadStopDir(next) == GetRoadStopDir(rs) &&
IsDriveThroughStopTile(next);
}
/**
* Rebuild, from scratch, the vehicles and other metadata on this stop.
* @param rs the roadstop this entry is part of
*/
void RoadStop::Entry::Rebuild(const RoadStop *rs)
{
assert(HasBit(rs->status, RSSFB_BASE_ENTRY));
DiagDirection dir = GetRoadStopDir(rs->xy);
this->length = 0;
TileIndexDiff offset = abs(TileOffsByDiagDir(dir));
for (TileIndex tile = rs->xy; IsDriveThroughRoadStopContinuation(rs->xy, tile); tile += offset) {
this->length += TILE_SIZE;
}
}
struct TileDataHelper
{
TileIndex tile; ///< The tile we are working on.
DiagDirection dir; ///< The direction the tile is oriented following Trackdir.
uint free = TILE_SIZE;
};
/* Find the amount of free space in this tile, measured from the direction of entry. */
Vehicle *FindTileFreeSpace(Vehicle *v, void *data)
{
TileDataHelper *tdh = (TileDataHelper*)data;
/* Exclude if not a RV, not travelling in the Trackdir direction */
if (v->type != VEH_ROAD || DirToDiagDir(v->direction) != tdh->dir) return nullptr;
RoadVehicle *rv = RoadVehicle::From(v);
uint veh_pos = DiagDirToAxis(tdh->dir) == AXIS_X ? rv->x_pos : rv->y_pos;
uint tile_pos = DiagDirToAxis(tdh->dir) == AXIS_X ? TileX(tdh->tile) : TileY(tdh->tile);
uint tile_free_space = TILE_SIZE;
switch (tdh->dir) {
case DIAGDIR_NE:
case DIAGDIR_NW:
tile_free_space = veh_pos % tile_pos < TILE_SIZE / 2 ? TILE_SIZE / 2 : 0;
tdh->free = std::min(tdh->free, tile_free_space);
break;
case DIAGDIR_SE:
case DIAGDIR_SW:
tile_free_space = veh_pos % tile_pos < TILE_SIZE / 2 ? 0 : TILE_SIZE / 2;
tdh->free = std::min(tdh->free, tile_free_space);
break;
default:
return nullptr;
}
return nullptr;
}
/**
* Get the amount of occupied space in this drive through stop.
* @param rs the roadstop this entry is part of
* @return the occupied space in tile units.
*/
int RoadStop::Entry::GetOccupied(const RoadStop *rs, const DiagDirection dir) const
{
const RoadStop::Entry *entry = rs->GetEntry(dir);
TileDataHelper tdh;
tdh.dir = dir;
int free = 0;
TileIndexDiff offset = TileOffsByDiagDir(dir);
for (TileIndex tile = rs->xy; RoadStop::IsDriveThroughRoadStopContinuation(rs->xy, tile); tile += offset) {
tdh.tile = tile;
FindVehicleOnPos(tile, &tdh, FindTileFreeSpace);
free += tdh.free;
// Tile contains a vehicle - no need to keep calculating.
if (tdh.free < TILE_SIZE) break;
};
return entry->GetLength() - free;
}
/**
* Check the integrity of the data in this struct.
* @param rs the roadstop this entry is part of
*/
void RoadStop::Entry::CheckIntegrity(const RoadStop *rs) const
{
if (!HasBit(rs->status, RSSFB_BASE_ENTRY)) return;
/* The tile 'before' the road stop must not be part of this 'line' */
assert(!IsDriveThroughRoadStopContinuation(rs->xy, rs->xy - abs(TileOffsByDiagDir(GetRoadStopDir(rs->xy)))));
Entry temp;
temp.Rebuild(rs);
if (temp.length != this->length) NOT_REACHED();
}