diff options
Diffstat (limited to 'src')
154 files changed, 14606 insertions, 502 deletions
diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index 385eb9be1..93554585e 100644 --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -36,6 +36,7 @@ #include "core/random_func.hpp" #include "core/backup_type.hpp" #include "zoom_func.h" +#include "cargotype.h" #include "table/strings.h" @@ -575,6 +576,13 @@ void UpdateAircraftCache(Aircraft *v, bool update_range) /* Squared it now so we don't have to do it later all the time. */ v->acache.cached_max_range_sqr = v->acache.cached_max_range * v->acache.cached_max_range; } + + /* Cache carried cargo types. */ + uint32 cargo_mask = 0; + for (Aircraft *u = v; u != NULL; u = u->Next()) { + if (u->cargo_type != INVALID_CARGO && u->cargo_cap > 0) SetBit(cargo_mask, u->cargo_type); + } + v->vcache.cached_cargo_mask = cargo_mask; } @@ -1221,7 +1229,6 @@ static void AircraftEntersTerminal(Aircraft *v) if (v->current_order.IsType(OT_GOTO_DEPOT)) return; Station *st = Station::Get(v->targetairport); - v->last_station_visited = v->targetairport; /* Check if station was ever visited before */ if (!(st->had_vehicle_of_type & HVOT_AIRCRAFT)) { @@ -1238,7 +1245,7 @@ static void AircraftEntersTerminal(Aircraft *v) Game::NewEvent(new ScriptEventStationFirstVehicle(st->index, v->index)); } - v->BeginLoading(); + v->BeginLoading(v->targetairport); } /** diff --git a/src/base_consist.h b/src/base_consist.h index 3679afd35..474ab9741 100644 --- a/src/base_consist.h +++ b/src/base_consist.h @@ -14,6 +14,7 @@ #include "order_type.h" #include "date_type.h" +#include "timetable.h" /** Various front vehicle properties that are preserved when autoreplacing, using order-backup or switching front engines within a consist. */ struct BaseConsist { @@ -22,7 +23,11 @@ struct BaseConsist { /* Used for timetabling. */ uint32 current_order_time; ///< How many ticks have passed since this order started. int32 lateness_counter; ///< How many ticks late (or early if negative) this vehicle is. +#if WALLCLOCK_NETWORK_COMPATIBLE Date timetable_start; ///< When the vehicle is supposed to start the timetable. +#else + DateTicks timetable_start; ///< When the vehicle is supposed to start the timetable. +#endif uint16 service_interval; ///< The interval for (automatic) servicing; either in days or %. diff --git a/src/base_station_base.h b/src/base_station_base.h index 812692bb1..dfbc2dd8c 100644 --- a/src/base_station_base.h +++ b/src/base_station_base.h @@ -28,6 +28,13 @@ struct StationSpecList { /** StationRect - used to track station spread out rectangle - cheaper than scanning whole map */ +/* +** Патч "layer" +** Критическое изменение: +** "Rect" подразумевает только верхний слой (поверхность) +** (станция может располагаться на любом слое, -- +** Rect описывает соответствующую часть верхнего слоя) +*/ struct StationRect : public Rect { enum StationRectMode { @@ -38,7 +45,8 @@ struct StationRect : public Rect { StationRect(); void MakeEmpty(); - bool PtInExtendedRect(int x, int y, int distance = 0) const; + bool PtInExtendedRect(int topx, int topy, int distance = 0) const; + bool AreaInExtendedRect(const TileArea& area, int distance = 0) const; bool IsEmpty() const; CommandCost BeforeAddTile(TileIndex tile, StationRectMode mode); CommandCost BeforeAddRect(TileIndex tile, int w, int h, StationRectMode mode); diff --git a/src/bridge_gui.cpp b/src/bridge_gui.cpp index 4a349b605..f6412352c 100644 --- a/src/bridge_gui.cpp +++ b/src/bridge_gui.cpp @@ -24,6 +24,7 @@ #include "cmd_helper.h" #include "tunnelbridge_map.h" #include "road_gui.h" +#include "tilehighlight_func.h" #include "widgets/bridge_widget.h" @@ -116,8 +117,10 @@ private: case TRANSPORT_ROAD: _last_roadbridge_type = this->bridges->Get(i)->index; break; default: break; } - DoCommandP(this->end_tile, this->start_tile, this->type | this->bridges->Get(i)->index, - CMD_BUILD_BRIDGE | CMD_MSG(STR_ERROR_CAN_T_BUILD_BRIDGE_HERE), CcBuildBridge); + if (DoCommandP(this->end_tile, this->start_tile, this->type | this->bridges->Get(i)->index, + CMD_BUILD_BRIDGE | CMD_MSG(STR_ERROR_CAN_T_BUILD_BRIDGE_HERE), CcBuildBridge)) { + StoreRailPlacementEndpoints(this->start_tile, this->end_tile, (TileX(this->start_tile) == TileX(this->end_tile)) ? TRACK_Y : TRACK_X, false); + } } /** Sort the builable bridges */ diff --git a/src/cargodest.cpp b/src/cargodest.cpp new file mode 100644 index 000000000..26c9379d8 --- /dev/null +++ b/src/cargodest.cpp @@ -0,0 +1,1197 @@ +/* $Id$ */ + +/* + * 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 cargodest.cpp Implementation of cargo destinations. */ + +#include "stdafx.h" +#include "cargodest_type.h" +#include "cargodest_base.h" +#include "cargodest_func.h" +#include "core/bitmath_func.hpp" +#include "core/random_func.hpp" +#include "core/pool_func.hpp" +#include "cargotype.h" +#include "settings_type.h" +#include "town.h" +#include "industry.h" +#include "window_func.h" +#include "vehicle_base.h" +#include "station_base.h" +#include "pathfinder/yapf/yapf.h" +#include "company_base.h" + + +/* Possible link weight modifiers. */ +static const byte LWM_ANYWHERE = 1; ///< Weight modifier for undetermined destinations. +static const byte LWM_TONY_ANY = 2; ///< Default weight modifier for towns. +static const byte LWM_TONY_BIG = 3; ///< Weight modifier for big towns. +static const byte LWM_TONY_CITY = 4; ///< Weight modifier for cities. +static const byte LWM_TONY_NEARBY = 5; ///< Weight modifier for nearby towns. +static const byte LWM_INTOWN = 8; ///< Weight modifier for in-town links. +static const byte LWM_IND_ANY = 2; ///< Default weight modifier for industries. +static const byte LWM_IND_NEARBY = 3; ///< Weight modifier for nearby industries. +static const byte LWM_IND_PRODUCING = 4; ///< Weight modifier for producing industries. + +static const uint MAX_EXTRA_LINKS = 2; ///< Number of extra links allowed. +static const uint MAX_IND_STOCKPILE = 1000; ///< Maximum stockpile to consider for industry link weight. + +static const uint BASE_TOWN_LINKS = 0; ///< Index into _settings_game.economy.cargodest.base_town_links for normal cargo +static const uint BASE_TOWN_LINKS_SYMM = 1; ///< Index into _settings_game.economy.cargodest.base_town_links for symmteric cargoes +static const uint BASE_IND_LINKS = 0; ///< Index into _settings_game.economy.cargodest.base_ind_links for normal cargo +static const uint BASE_IND_LINKS_TOWN = 1; ///< Index into _settings_game.economy.cargodest.base_ind_links for town cargoes +static const uint BASE_IND_LINKS_SYMM = 2; ///< Index into _settings_game.economy.cargodest.base_ind_links for symmetric cargoes +static const uint BIG_TOWN_POP_MAIL = 0; ///< Index into _settings_game.economy.cargodest.big_town_pop for mail +static const uint BIG_TOWN_POP_PAX = 1; ///< Index into _settings_game.economy.cargodest.big_town_pop for passengers +static const uint SCALE_TOWN = 0; ///< Index into _settings_game.economy.cargodest.pop_scale_town/weight_scale_town for normal cargo +static const uint SCALE_TOWN_BIG = 1; ///< Index into _settings_game.economy.cargodest.pop_scale_town/weight_scale_town for normal cargo of big towns +static const uint SCALE_TOWN_PAX = 2; ///< Index into _settings_game.economy.cargodest.pop_scale_town/weight_scale_town for passengers +static const uint SCALE_TOWN_BIG_PAX = 3; ///< Index into _settings_game.economy.cargodest.pop_scale_town/weight_scale_town for passengers of big towns +static const uint CARGO_SCALE_IND = 0; ///< Index into _settings_game.economy.cargodest.cargo_scale_ind for normal cargo +static const uint CARGO_SCALE_IND_TOWN = 1; ///< Index into _settings_game.economy.cargodest.cargo_scale_ind for town cargoes +static const uint MIN_WEIGHT_TOWN = 0; ///< Index into _settings_game.economy.cargodest.min_weight_town for normal cargo +static const uint MIN_WEIGHT_TOWN_PAX = 1; ///< Index into _settings_game.economy.cargodest.min_weight_town for passengers +static const uint WEIGHT_SCALE_IND_PROD = 0; ///< Index into _settings_game.economy.cargodest.weight_scale_ind for produced cargo +static const uint WEIGHT_SCALE_IND_PILE = 1; ///< Index into _settings_game.economy.cargodest.weight_scale_ind for stockpiled cargo + +/** Are cargo destinations for this cargo type enabled? */ +bool CargoHasDestinations(CargoID cid) +{ + const CargoSpec *spec = CargoSpec::Get(cid); + switch (spec->town_effect) { + case TE_PASSENGERS: + case TE_MAIL: + return _settings_game.economy.cargodest.mode_pax_mail != CRM_OFF; + + case TE_GOODS: + case TE_WATER: + case TE_FOOD: + return _settings_game.economy.cargodest.mode_town_cargo != CRM_OFF; + + default: + return _settings_game.economy.cargodest.mode_others != CRM_OFF; + } +} + +/** Are cargo destinations for all cargo types disabled? */ +bool CargoDestinationsDisabled() +{ + return _settings_game.economy.cargodest.mode_pax_mail == CRM_OFF && _settings_game.economy.cargodest.mode_town_cargo == CRM_OFF && _settings_game.economy.cargodest.mode_others == CRM_OFF; +} + +/** Should this cargo type primarily have towns as a destination? */ +static bool IsTownCargo(CargoID cid) +{ + const CargoSpec *spec = CargoSpec::Get(cid); + return spec->town_effect != TE_NONE; +} + +/** Does this cargo have a symmetric demand? */ +static bool IsSymmetricCargo(CargoID cid) +{ + const CargoSpec *spec = CargoSpec::Get(cid); + return spec->town_effect == TE_PASSENGERS; +} + +/** Is this a passenger cargo. */ +static bool IsPassengerCargo(CargoID cid) +{ + const CargoSpec *spec = CargoSpec::Get(cid); + return spec->town_effect == TE_PASSENGERS; +} + + +/** Information for the town/industry enumerators. */ +struct EnumRandomData { + CargoSourceSink *source; + TileIndex source_xy; + CargoID cid; + bool limit_links; +}; + +/** + * Test whether two tiles are nearby with map-size scaling. + * @param t1 First tile. + * @param t2 Second tile. + * @param dist_square Allowed squared distance between the tiles. + * @return True if the tiles are nearby. + */ +static bool IsNearby(TileIndex t1, TileIndex t2, uint32 dist_square) +{ + /* Scale distance by 1D map size to make sure that there are still + * candidates left on larger maps with few towns, but don't scale + * by 2D map size so the map still feels bigger. */ + return DistanceSquare(t1, t2) < ScaleByMapSize1D(dist_square); +} + +/** + * Test whether a tiles is near a town. + * @param t The town. + * @param ti The tile to test. + * @return True if the tiles is near the town. + */ +static bool IsTownNearby(const Town *t, TileIndex ti) +{ + return IsNearby(t->xy, ti, _settings_game.economy.cargodest.town_nearby_dist); +} + +/** + * Test whether a tiles is near an industry. + * @param ind The industry. + * @param ti The tile to test. + * @return True if the tiles is near the town. + */ +static bool IsIndustryNearby(const Industry *ind, TileIndex ti) +{ + return IsNearby(ind->location.tile, ti, _settings_game.economy.cargodest.ind_nearby_dist); +} + +/** Common helper for town/industry enumeration. */ +static bool EnumAnyDest(const CargoSourceSink *dest, EnumRandomData *erd) +{ + /* Already a destination? */ + if (erd->source->HasLinkTo(erd->cid, dest)) return false; + + /* Destination already has too many links? */ + if (erd->limit_links && dest->cargo_links[erd->cid].Length() > dest->num_links_expected[erd->cid] + MAX_EXTRA_LINKS) return false; + + return true; +} + +/** Enumerate any town not already a destination and accepting a specific cargo.*/ +static bool EnumAnyTown(const Town *t, void *data) +{ + EnumRandomData *erd = (EnumRandomData *)data; + return EnumAnyDest(t, erd) && t->AcceptsCargo(erd->cid); +} + +/** Enumerate cities. */ +static bool EnumCity(const Town *t, void *data) +{ + return EnumAnyTown(t, data) && t->larger_town; +} + +/** Enumerate towns with a big population. */ +static bool EnumBigTown(const Town *t, void *data) +{ + EnumRandomData *erd = (EnumRandomData *)data; + return EnumAnyTown(t, erd) && (IsPassengerCargo(erd->cid) ? t->supplied[CT_PASSENGERS].old_max > _settings_game.economy.cargodest.big_town_pop[BIG_TOWN_POP_PAX] : t->supplied[CT_MAIL].old_max > _settings_game.economy.cargodest.big_town_pop[BIG_TOWN_POP_MAIL]); +} + +/** Enumerate nearby towns. */ +static bool EnumNearbyTown(const Town *t, void *data) +{ + EnumRandomData *erd = (EnumRandomData *)data; + return EnumAnyTown(t, data) && IsTownNearby(t, erd->source_xy); +} + +/** Enumerate any industry not already a destination and accepting a specific cargo. */ +static bool EnumAnyIndustry(const Industry *ind, void *data) +{ + EnumRandomData *erd = (EnumRandomData *)data; + return EnumAnyDest(ind, erd) && ind->AcceptsCargo(erd->cid); +} + +/** Enumerate nearby industries. */ +static bool EnumNearbyIndustry(const Industry *ind, void *data) +{ + EnumRandomData *erd = (EnumRandomData *)data; + return EnumAnyIndustry(ind, data) && IsIndustryNearby(ind, erd->source_xy); +} + +/** Enumerate industries that are producing cargo. */ +static bool EnumProducingIndustry(const Industry *ind, void *data) +{ + return EnumAnyIndustry(ind, data) && (ind->produced_cargo[0] != CT_INVALID || ind->produced_cargo[1] != CT_INVALID); +} + +/** Enumerate cargo sources supplying a specific cargo. */ +template <typename T> +static bool EnumAnySupplier(const T *css, void *data) +{ + return css->SuppliesCargo(((EnumRandomData *)data)->cid); +} + +/** Enumerate nearby cargo sources supplying a specific cargo. */ +static bool EnumNearbySupplier(const Industry *ind, void *data) +{ + EnumRandomData *erd = (EnumRandomData *)data; + return EnumAnySupplier(ind, data) && IsIndustryNearby(ind, erd->source_xy); +} + +/** Enumerate nearby cargo sources supplying a specific cargo. */ +static bool EnumNearbySupplier(const Town *t, void *data) +{ + EnumRandomData *erd = (EnumRandomData *)data; + return EnumAnySupplier(t, data) && IsTownNearby(t, erd->source_xy); +} + + +/** Find a town as a destination. */ +static CargoSourceSink *FindTownDestination(byte &weight_mod, CargoSourceSink *source, TileIndex source_xy, CargoID cid, const uint8 destclass_chance[4], TownID skip = INVALID_TOWN) +{ + /* Enum functions for: nearby town, city, big town, and any town. */ + static const Town::EnumTownProc destclass_enum[] = { + &EnumNearbyTown, &EnumCity, &EnumBigTown, &EnumAnyTown + }; + static const byte weight_mods[] = {LWM_TONY_NEARBY, LWM_TONY_CITY, LWM_TONY_BIG, LWM_TONY_ANY}; + assert_compile(lengthof(destclass_enum) == lengthof(weight_mods)); + + EnumRandomData erd = {source, source_xy, cid, IsSymmetricCargo(cid)}; + + /* Determine destination class. If no town is found in this class, + * the search falls through to the following classes. */ + byte destclass = RandomRange(destclass_chance[3]); + + weight_mod = LWM_ANYWHERE; + Town *dest = NULL; + for (uint i = 0; i < lengthof(destclass_enum) && dest == NULL; i++) { + /* Skip if destination class not reached. */ + if (destclass > destclass_chance[i]) continue; + + dest = Town::GetRandom(destclass_enum[i], skip, &erd); + weight_mod = weight_mods[i]; + } + + return dest; +} + +/** Find an industry as a destination. */ +static CargoSourceSink *FindIndustryDestination(byte &weight_mod, CargoSourceSink *source, TileIndex source_xy, CargoID cid, IndustryID skip = INVALID_INDUSTRY) +{ + /* Enum functions for: nearby industry, producing industry, and any industry. */ + static const Industry::EnumIndustryProc destclass_enum[] = { + &EnumNearbyIndustry, &EnumProducingIndustry, &EnumAnyIndustry + }; + static const byte weight_mods[] = {LWM_IND_NEARBY, LWM_IND_PRODUCING, LWM_IND_ANY}; + assert_compile(lengthof(destclass_enum) == lengthof(_settings_game.economy.cargodest.ind_chances)); + + EnumRandomData erd = {source, source_xy, cid, IsSymmetricCargo(cid)}; + + /* Determine destination class. If no industry is found in this class, + * the search falls through to the following classes. */ + byte destclass = RandomRange(*lastof(_settings_game.economy.cargodest.ind_chances)); + + weight_mod = LWM_ANYWHERE; + Industry *dest = NULL; + for (uint i = 0; i < lengthof(destclass_enum) && dest == NULL; i++) { + /* Skip if destination class not reached. */ + if (destclass > _settings_game.economy.cargodest.ind_chances[i]) continue; + + dest = Industry::GetRandom(destclass_enum[i], skip, &erd); + weight_mod = weight_mods[i]; + } + + return dest; +} + +/** Find a supply for a cargo type. */ +static CargoSourceSink *FindSupplySource(Industry *dest, CargoID cid) +{ + EnumRandomData erd = {dest, dest->location.tile, cid, false}; + + CargoSourceSink *source = NULL; + + /* Even chance for industry source first, town second and vice versa. + * Try a nearby supplier first, then check all suppliers. */ + if (Chance16(1, 2)) { + source = Industry::GetRandom(&EnumNearbySupplier, dest->index, &erd); + if (source == NULL) source = Town::GetRandom(&EnumNearbySupplier, INVALID_TOWN, &erd); + if (source == NULL) source = Industry::GetRandom(&EnumAnySupplier, dest->index, &erd); + if (source == NULL) source = Town::GetRandom(&EnumAnySupplier, INVALID_TOWN, &erd); + } else { + source = Town::GetRandom(&EnumNearbySupplier, INVALID_TOWN, &erd); + if (source == NULL) source = Industry::GetRandom(&EnumNearbySupplier, dest->index, &erd); + if (source == NULL) source = Town::GetRandom(&EnumAnySupplier, INVALID_TOWN, &erd); + if (source == NULL) source = Industry::GetRandom(&EnumAnySupplier, dest->index, &erd); + } + + return source; +} + +/* virtual */ void CargoSourceSink::CreateSpecialLinks(CargoID cid) +{ + /* First link is for undetermined destinations. */ + if (this->cargo_links[cid].Length() == 0) { + *this->cargo_links[cid].Append() = CargoLink(NULL, LWM_ANYWHERE); + } + if (this->cargo_links[cid].Get(0)->dest != NULL) { + /* Insert link at first place. */ + *this->cargo_links[cid].Append() = *this->cargo_links[cid].Get(0); + *this->cargo_links[cid].Get(0) = CargoLink(NULL, LWM_ANYWHERE); + } +} + +/* virtual */ void Town::CreateSpecialLinks(CargoID cid) +{ + CargoSourceSink::CreateSpecialLinks(cid); + + if (this->AcceptsCargo(cid)) { + /* Add special link for town-local demand if not already present. */ + if (this->cargo_links[cid].Length() < 2) *this->cargo_links[cid].Append() = CargoLink(this, LWM_INTOWN); + if (this->cargo_links[cid].Get(1)->dest != this) { + /* Insert link at second place. */ + *this->cargo_links[cid].Append() = *this->cargo_links[cid].Get(1); + *this->cargo_links[cid].Get(1) = CargoLink(this, LWM_INTOWN); + } + } else { + /* Remove link for town-local demand if present. */ + if (this->cargo_links[cid].Length() > 1 && this->cargo_links[cid].Get(1)->dest == this) { + this->cargo_links[cid].Erase(this->cargo_links[cid].Get(1)); + } + } +} + +/** + * Remove the link with the lowest weight from a cargo source. The + * reverse link is removed as well if the cargo has symmetric demand. + * @param source Remove the link from this cargo source. + * @param cid Cargo type of the link to remove. + */ +static void RemoveLowestLink(CargoSourceSink *source, CargoID cid) +{ + uint lowest_weight = UINT_MAX; + CargoLink *lowest_link = NULL; + + for (CargoLink *l = source->cargo_links[cid].Begin(); l != source->cargo_links[cid].End(); l++) { + /* Don't remove special links. */ + if (l->dest == NULL || l->dest == source) continue; + + if (l->weight < lowest_weight) { + lowest_weight = l->weight; + lowest_link = l; + } + } + + if (lowest_link != NULL) { + /* If this is a symmetric cargo, also remove the reverse link. */ + if (IsSymmetricCargo(cid) && lowest_link->dest->HasLinkTo(cid, source)) { + source->num_incoming_links[cid]--; + lowest_link->dest->cargo_links[cid].Erase(lowest_link->dest->cargo_links[cid].Find(CargoLink(source, LWM_ANYWHERE))); + } + lowest_link->dest->num_incoming_links[cid]--; + source->cargo_links[cid].Erase(lowest_link); + } +} + +/** Create missing cargo links for a source. */ +static void CreateNewLinks(CargoSourceSink *source, TileIndex source_xy, CargoID cid, uint chance_a, uint chance_b, const uint8 town_chance[], TownID skip_town, IndustryID skip_ind) +{ + uint num_links = source->num_links_expected[cid]; + + /* Remove the link with the lowest weight if the + * town has more than links more than expected. */ + if (source->cargo_links[cid].Length() > num_links + MAX_EXTRA_LINKS) { + RemoveLowestLink(source, cid); + } + + /* Add new links until the expected link count is reached. */ + while (source->cargo_links[cid].Length() < num_links) { + CargoSourceSink *dest = NULL; + byte weight_mod = LWM_ANYWHERE; + + /* Chance for town/industry is chance_a/chance_b, otherwise try industry/town. */ + if (Chance16(chance_a, chance_b)) { + dest = FindTownDestination(weight_mod, source, source_xy, cid, town_chance, skip_town); + /* No town found? Try an industry. */ + if (dest == NULL) dest = FindIndustryDestination(weight_mod, source, source_xy, cid, skip_ind); + } else { + dest = FindIndustryDestination(weight_mod, source, source_xy, cid, skip_ind); + /* No industry found? Try a town. */ + if (dest == NULL) dest = FindTownDestination(weight_mod, source, source_xy, cid, town_chance, skip_town); + } + + /* If we didn't find a destination, break out of the loop because no + * more destinations are left on the map. */ + if (dest == NULL) break; + + /* If this is a symmetric cargo and we accept it as well, create a back link. */ + if (IsSymmetricCargo(cid) && dest->SuppliesCargo(cid) && source->AcceptsCargo(cid)) { + *dest->cargo_links[cid].Append() = CargoLink(source, weight_mod); + source->num_incoming_links[cid]++; + } + + *source->cargo_links[cid].Append() = CargoLink(dest, weight_mod); + dest->num_incoming_links[cid]++; + } +} + +/** Remove invalid links from a cargo source/sink. */ +static void RemoveInvalidLinks(CargoSourceSink *css) +{ + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + /* Remove outgoing links if cargo isn't supplied anymore. */ + if (!css->SuppliesCargo(cid)) { + for (CargoLink *l = css->cargo_links[cid].Begin(); l != css->cargo_links[cid].End(); l++) { + if (l->dest != NULL && l->dest != css) l->dest->num_incoming_links[cid]--; + } + css->cargo_links[cid].Clear(); + css->cargo_links_weight[cid] = 0; + } + + /* Remove outgoing links if the dest doesn't accept the cargo anymore. */ + for (CargoLink *l = css->cargo_links[cid].Begin(); l != css->cargo_links[cid].End(); ) { + if (l->dest != NULL && !l->dest->AcceptsCargo(cid)) { + if (l->dest != css) l->dest->num_incoming_links[cid]--; + css->cargo_links[cid].Erase(l); + } else { + l++; + } + } + } +} + +/** Updated the desired link count for each cargo. */ +void UpdateExpectedLinks(Town *t) +{ + CargoID cid; + + FOR_EACH_SET_CARGO_ID(cid, t->cargo_produced) { + if (CargoHasDestinations(cid)) { + t->CreateSpecialLinks(cid); + + uint max_amt = IsPassengerCargo(cid) ? t->supplied[CT_PASSENGERS].old_max : t->supplied[CT_MAIL].old_max; + uint big_amt = _settings_game.economy.cargodest.big_town_pop[IsPassengerCargo(cid) ? BIG_TOWN_POP_PAX : BIG_TOWN_POP_MAIL]; + + uint num_links = _settings_game.economy.cargodest.base_town_links[IsSymmetricCargo(cid) ? BASE_TOWN_LINKS_SYMM : BASE_TOWN_LINKS]; + /* Add links based on the available cargo amount. */ + num_links += min(max_amt, big_amt) / _settings_game.economy.cargodest.pop_scale_town[IsPassengerCargo(cid) ? SCALE_TOWN_PAX : SCALE_TOWN]; + if (max_amt > big_amt) num_links += (max_amt - big_amt) / _settings_game.economy.cargodest.pop_scale_town[IsPassengerCargo(cid) ? SCALE_TOWN_BIG_PAX : SCALE_TOWN_BIG]; + /* Ensure a city has at least city_town_links more than the base value. + * This improves the link distribution at the beginning of a game when + * the towns are still small. */ + if (t->larger_town) num_links = max<uint>(num_links, _settings_game.economy.cargodest.city_town_links + _settings_game.economy.cargodest.base_town_links[IsSymmetricCargo(cid) ? BASE_TOWN_LINKS_SYMM : BASE_TOWN_LINKS]); + + /* Account for the two special links. */ + num_links++; + if (t->cargo_links[cid].Length() > 1 && t->cargo_links[cid].Get(1)->dest == t) num_links++; + + t->num_links_expected[cid] = ClampToU16(num_links); + } + } +} + +/** Updated the desired link count for each cargo. */ +void UpdateExpectedLinks(Industry *ind) +{ + for (uint i = 0; i < lengthof(ind->produced_cargo); i++) { + CargoID cid = ind->produced_cargo[i]; + if (cid == INVALID_CARGO) continue; + + if (CargoHasDestinations(cid)) { + ind->CreateSpecialLinks(cid); + + uint num_links; + /* Use different base values for symmetric cargoes, cargoes + * with a town effect and all other cargoes. */ + num_links = _settings_game.economy.cargodest.base_ind_links[IsSymmetricCargo(cid) ? BASE_IND_LINKS_SYMM : (IsTownCargo(cid) ? BASE_IND_LINKS_TOWN : BASE_IND_LINKS)]; + /* Add links based on the average industry production. */ + num_links += ind->average_production[i] / _settings_game.economy.cargodest.cargo_scale_ind[IsTownCargo(cid) ? CARGO_SCALE_IND_TOWN : CARGO_SCALE_IND]; + + /* Account for the one special link. */ + num_links++; + + ind->num_links_expected[cid] = ClampToU16(num_links); + } + } +} + +/** Make sure an industry has at least one incoming link for each accepted cargo. */ +void AddMissingIndustryLinks(Industry *ind) +{ + for (uint i = 0; i < lengthof(ind->accepts_cargo); i++) { + CargoID cid = ind->accepts_cargo[i]; + if (cid == INVALID_CARGO) continue; + + /* Do we already have at least one cargo source? */ + if (ind->num_incoming_links[cid] > 0) continue; + + CargoSourceSink *source = FindSupplySource(ind, cid); + if (source == NULL) continue; // Too bad... + + if (source->cargo_links[cid].Length() >= source->num_links_expected[cid] + MAX_EXTRA_LINKS) { + /* Increase the expected link count if adding another link would + * exceed the count, as otherwise this (or another) link would + * get removed right again. */ + source->num_links_expected[cid]++; + } + + *source->cargo_links[cid].Append() = CargoLink(ind, LWM_IND_ANY); + ind->num_incoming_links[cid]++; + + /* If this is a symmetric cargo and we produce it as well, create a back link. */ + if (IsSymmetricCargo(cid) && ind->SuppliesCargo(cid) && source->AcceptsCargo(cid)) { + *ind->cargo_links[cid].Append() = CargoLink(source, LWM_IND_ANY); + source->num_incoming_links[cid]++; + } + } +} + +/** Update the demand links. */ +void UpdateCargoLinks(Town *t) +{ + CargoID cid; + + FOR_EACH_SET_CARGO_ID(cid, t->cargo_produced) { + if (CargoHasDestinations(cid)) { + /* If this is a town cargo, 95% chance for town/industry destination and + * 5% for industry/town. The reverse chance otherwise. */ + CreateNewLinks(t, t->xy, cid, IsTownCargo(cid) ? 19 : 1, 20, t->larger_town ? _settings_game.economy.cargodest.town_chances_city : _settings_game.economy.cargodest.town_chances_town, t->index, INVALID_INDUSTRY); + } + } +} + +/** Update the demand links. */ +void UpdateCargoLinks(Industry *ind) +{ + for (uint i = 0; i < lengthof(ind->produced_cargo); i++) { + CargoID cid = ind->produced_cargo[i]; + if (cid == INVALID_CARGO) continue; + + if (CargoHasDestinations(cid)) { + /* If this is a town cargo, 75% chance for town/industry destination and + * 25% for industry/town. The reverse chance otherwise. */ + CreateNewLinks(ind, ind->location.tile, cid, IsTownCargo(cid) ? 3 : 1, 4, _settings_game.economy.cargodest.town_chances_town, INVALID_TOWN, ind->index); + } + } +} + +/* virtual */ uint Town::GetDestinationWeight(CargoID cid, byte weight_mod) const +{ + uint max_amt = IsPassengerCargo(cid) ? this->supplied[CT_PASSENGERS].old_max : this->supplied[CT_PASSENGERS].old_max; + uint big_amt = _settings_game.economy.cargodest.big_town_pop[IsPassengerCargo(cid) ? BIG_TOWN_POP_PAX : BIG_TOWN_POP_MAIL]; + + /* The weight is calculated by a piecewise function. We start with a predefined + * minimum weight and then add the weight for the cargo amount up to the big + * town amount. If the amount is more than the big town amount, this is also + * added to the weight with a different scale factor to make sure that big towns + * don't siphon the cargo away too much from the smaller destinations. */ + uint weight = _settings_game.economy.cargodest.min_weight_town[IsPassengerCargo(cid) ? MIN_WEIGHT_TOWN_PAX : MIN_WEIGHT_TOWN]; + weight += min(max_amt, big_amt) * weight_mod / _settings_game.economy.cargodest.weight_scale_town[IsPassengerCargo(cid) ? SCALE_TOWN_PAX : SCALE_TOWN]; + if (max_amt > big_amt) weight += (max_amt - big_amt) * weight_mod / _settings_game.economy.cargodest.weight_scale_town[IsPassengerCargo(cid) ? SCALE_TOWN_BIG_PAX : SCALE_TOWN_BIG]; + + return weight; +} + +/* virtual */ uint Industry::GetDestinationWeight(CargoID cid, byte weight_mod) const +{ + uint weight = _settings_game.economy.cargodest.min_weight_ind; + + for (uint i = 0; i < lengthof(this->accepts_cargo); i++) { + if (this->accepts_cargo[i] != cid) continue; + /* Empty stockpile means more weight for the link. Stockpiles + * above a fixed maximum have no further effect. */ + uint stockpile = ClampU(this->incoming_cargo_waiting[i], 0, MAX_IND_STOCKPILE); + weight += (MAX_IND_STOCKPILE - stockpile) * weight_mod / _settings_game.economy.cargodest.weight_scale_ind[WEIGHT_SCALE_IND_PILE]; + } + + /* Add a weight for the produced cargo. Use the average production + * here so the weight isn't fluctuating that much when the input + * cargo isn't delivered regularly. */ + weight += (this->average_production[0] + this->average_production[1]) * weight_mod / _settings_game.economy.cargodest.weight_scale_ind[WEIGHT_SCALE_IND_PROD]; + + return weight; +} + +/** Recalculate the link weights. */ +void UpdateLinkWeights(Town *t) +{ + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + uint weight_sum = 0; + + if (t->cargo_links[cid].Length() == 0) continue; + + t->cargo_links[cid].Begin()->amount.NewMonth(); + + /* Skip the special link for undetermined destinations. */ + for (CargoLink *l = t->cargo_links[cid].Begin() + 1; l != t->cargo_links[cid].End(); l++) { + l->weight = l->dest->GetDestinationWeight(cid, l->weight_mod); + weight_sum += l->weight; + + l->amount.NewMonth(); + } + + /* Limit the weight of the in-town link to at most 1/3 of the total weight. */ + if (t->cargo_links[cid].Length() > 1 && t->cargo_links[cid].Get(1)->dest == t) { + uint new_weight = min(t->cargo_links[cid].Get(1)->weight, weight_sum / 3); + weight_sum -= t->cargo_links[cid].Get(1)->weight - new_weight; + t->cargo_links[cid].Get(1)->weight = new_weight; + } + + /* Set weight for the undetermined destination link to random_dest_chance%. */ + t->cargo_links[cid].Begin()->weight = weight_sum == 0 ? 1 : (weight_sum * _settings_game.economy.cargodest.random_dest_chance) / (100 - _settings_game.economy.cargodest.random_dest_chance); + + t->cargo_links_weight[cid] = weight_sum + t->cargo_links[cid].Begin()->weight; + } +} + +/** Recalculate the link weights. */ +void UpdateLinkWeights(CargoSourceSink *css) +{ + for (uint cid = 0; cid < NUM_CARGO; cid++) { + uint weight_sum = 0; + + if (css->cargo_links[cid].Length() == 0) continue; + + css->cargo_links[cid].Begin()->amount.NewMonth(); + + for (CargoLink *l = css->cargo_links[cid].Begin() + 1; l != css->cargo_links[cid].End(); l++) { + l->weight = l->dest->GetDestinationWeight(cid, l->weight_mod); + weight_sum += l->weight; + + l->amount.NewMonth(); + } + + /* Set weight for the undetermined destination link to random_dest_chance%. */ + css->cargo_links[cid].Begin()->weight = weight_sum == 0 ? 1 : (weight_sum * _settings_game.economy.cargodest.random_dest_chance) / (100 - _settings_game.economy.cargodest.random_dest_chance); + + css->cargo_links_weight[cid] = weight_sum + css->cargo_links[cid].Begin()->weight; + } +} + +/* virtual */ CargoSourceSink::~CargoSourceSink() +{ + if (Town::CleaningPool() || Industry::CleaningPool()) return; + + /* Remove all demand links having us as a destination. */ + Town *t; + FOR_ALL_TOWNS(t) { + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + if (t->HasLinkTo(cid, this)) { + t->cargo_links[cid].Erase(t->cargo_links[cid].Find(CargoLink(this, LWM_ANYWHERE))); + InvalidateWindowData(WC_TOWN_VIEW, t->index, 1); + } + } + } + + Industry *ind; + FOR_ALL_INDUSTRIES(ind) { + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + if (ind->HasLinkTo(cid, this)) { + ind->cargo_links[cid].Erase(ind->cargo_links[cid].Find(CargoLink(this, LWM_ANYWHERE))); + InvalidateWindowData(WC_INDUSTRY_VIEW, ind->index, 1); + } + } + } + + /* Decrement incoming link count for all link destinations. */ + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + for (CargoLink *l = this->cargo_links[cid].Begin(); l != this->cargo_links[cid].End(); l++) { + if (l->dest != NULL) l->dest->num_incoming_links[cid]--; + } + } +} + +/** Rebuild the cached count of incoming cargo links. */ +void RebuildCargoLinkCounts() +{ + /* Clear incoming link count of all towns and industries. */ + CargoSourceSink *source; + FOR_ALL_TOWNS(source) MemSetT(source->num_incoming_links, 0, lengthof(source->num_incoming_links)); + FOR_ALL_INDUSTRIES(source) MemSetT(source->num_incoming_links, 0, lengthof(source->num_incoming_links)); + + /* Count all incoming links. */ + FOR_ALL_TOWNS(source) { + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + for (CargoLink *l = source->cargo_links[cid].Begin(); l != source->cargo_links[cid].End(); l++) { + if (l->dest != NULL && l->dest != source) l->dest->num_incoming_links[cid]++; + } + } + } + FOR_ALL_INDUSTRIES(source) { + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + for (CargoLink *l = source->cargo_links[cid].Begin(); l != source->cargo_links[cid].End(); l++) { + if (l->dest != NULL && l->dest != source) l->dest->num_incoming_links[cid]++; + } + } + } +} + +/** Update the demand links of all towns and industries. */ +void UpdateCargoLinks() +{ + if (CargoDestinationsDisabled()) return; + + Town *t; + Industry *ind; + + /* Remove links that have become invalid. */ + FOR_ALL_TOWNS(t) RemoveInvalidLinks(t); + FOR_ALL_INDUSTRIES(ind) RemoveInvalidLinks(ind); + + /* Recalculate the number of expected links. */ + FOR_ALL_TOWNS(t) UpdateExpectedLinks(t); + FOR_ALL_INDUSTRIES(ind) UpdateExpectedLinks(ind); + + /* Make sure each industry gets at at least some input cargo. */ + FOR_ALL_INDUSTRIES(ind) AddMissingIndustryLinks(ind); + + /* Update the demand link list. */ + FOR_ALL_TOWNS(t) UpdateCargoLinks(t); + FOR_ALL_INDUSTRIES(ind) UpdateCargoLinks(ind); + + /* Recalculate links weights. */ + FOR_ALL_TOWNS(t) UpdateLinkWeights(t); + FOR_ALL_INDUSTRIES(ind) UpdateLinkWeights(ind); + + InvalidateWindowClassesData(WC_TOWN_VIEW, 1); + InvalidateWindowClassesData(WC_INDUSTRY_VIEW, 1); +} + +/** + * Get a random demand link. + * @param cid Cargo type + * @param allow_self Indicates if the local link is acceptable as a result. + * @return Pointer to a demand link or this->cargo_links[cid].End() if no link found. + */ +CargoLink *CargoSourceSink::GetRandomLink(CargoID cid, bool allow_self) +{ + /* Randomly choose a cargo link. */ + uint weight = RandomRange(this->cargo_links_weight[cid] - 1); + uint cur_sum = 0; + + CargoLink *l; + for (l = this->cargo_links[cid].Begin(); l != this->cargo_links[cid].End(); ++l) { + cur_sum += l->weight; + if (weight < cur_sum) { + /* Link is valid if it is random destination or only the + * local link if allowed and accepts the cargo. */ + if (l->dest == NULL || ((allow_self || l->dest != this) && l->dest->AcceptsCargo(cid))) break; + } + } + + return l; +} + + +/** Get a random destination tile index for this cargo. */ +/* virtual */ TileArea Town::GetTileForDestination(CargoID cid) +{ + assert(this->cargo_accepted_weights[cid] != 0); + + /* Randomly choose a target square. */ + uint32 weight = RandomRange(this->cargo_accepted_weights[cid] - 1); + + /* Iterate over all grid squares till the chosen square is found. */ + uint32 weight_sum = 0; + const TileArea &area = this->cargo_accepted.GetArea(); + TILE_AREA_LOOP(tile, area) { + if (TileX(tile) % AcceptanceMatrix::GRID == 0 && TileY(tile) % AcceptanceMatrix::GRID == 0) { + weight_sum += this->cargo_accepted_max_weight - (DistanceMax(this->xy_aligned, tile) / AcceptanceMatrix::GRID) * 2; + /* Return tile area inside the grid square if this is the chosen square. */ + if (weight < weight_sum) return TileArea(tile + TileDiffXY(1, 1), 2, 2); + } + } + + /* Something went wrong here... */ + NOT_REACHED(); +} + +/** Enumerate all towns accepting a specific cargo. */ +static bool EnumAcceptingTown(const Town *t, void *data) +{ + return t->AcceptsCargo((CargoID)(size_t)data); +} + +/** Enumerate all industries accepting a specific cargo. */ +static bool EnumAcceptingIndustry(const Industry *ind, void *data) +{ + return ind->AcceptsCargo((CargoID)(size_t)data); +} + +/** + * Move a single packet of cargo to a station with destination information. + * @param cid Cargo type. + * @param amount[in,out] Cargo amount, return is actually moved cargo. + * @param source_type Type of the cargo source. + * @param source_id ID of the cargo source. + * @param all_stations List of possible target stations. + * @param src_tile Source tile. + * @return True if the cargo was handled has having destinations. + */ +bool MoveCargoWithDestinationToStationWorker(CargoID cid, uint *amount, SourceType source_type, SourceID source_id, const StationList *all_stations, TileIndex src_tile) +{ + CargoSourceSink *source = NULL; + CargoSourceSink *dest = NULL; + CargoLink *l = NULL; + + /* Company HQ doesn't have cargo links. */ + if (source_type != ST_HEADQUARTERS) { + source = (source_type == ST_TOWN) ? static_cast<CargoSourceSink *>(Town::Get(source_id)) : static_cast<CargoSourceSink *>(Industry::Get(source_id)); + /* No links yet? Create cargo without destination. */ + if (source->cargo_links[cid].Length() == 0) return false; + + /* Randomly choose a cargo link. */ + l = source->GetRandomLink(cid, true); + + if (l != source->cargo_links[cid].End()) { + l->amount.new_max += *amount; + dest = l->dest; + } + } + + /* No destination or random destination? Try a random town. */ + if (dest == NULL) dest = Town::GetRandom(&EnumAcceptingTown, INVALID_TOWN, (void *)(size_t)cid); + /* No luck? Try a random industry. */ + if (dest == NULL) dest = Industry::GetRandom(&EnumAcceptingIndustry, INVALID_INDUSTRY, (void *)(size_t)cid); + /* Still no luck, nothing left to try. */ + if (dest == NULL) return false; + + /* Pick a tile that belongs to the destination. */ + TileArea dest_area = dest->GetTileForDestination(cid); + + /* Maximum pathfinder penalty based on distance. */ + uint r = RandomRange(_settings_game.economy.cargodest.max_route_penalty[1]); + uint max_cost = _settings_game.economy.cargodest.max_route_penalty[0] + r; + max_cost *= DistanceSquare(src_tile, dest_area.tile); + + /* Randomly determine the routing flags for the packet. + * Right now only the two lowest bits are defined. */ + byte flags = r & 0x3; + + /* Find a route to the destination. */ + StationID st, st_unload; + bool found = false; + RouteLink *route_link = YapfChooseRouteLink(cid, all_stations, src_tile, dest_area, &st, &st_unload, flags, &found, INVALID_ORDER, max_cost); + + if (route_link == NULL) { + /* No suitable link found (or direct delivery), nothing is + * moved to the station, but count it as transported anyway. */ + if (found && l != NULL) l->amount.new_act += *amount; + *amount = 0; + return true; + } + + /* Move cargo to the station. */ + Station *from = Station::Get(st); + *amount = UpdateStationWaiting(from, cid, *amount * from->goods[cid].rating, source_type, source_id, dest_area.tile, dest->GetType(), dest->GetID(), route_link->GetOriginOrderId(), st_unload, flags); + if (found && l != NULL) l->amount.new_act += *amount; + + /* If this is a symmetric cargo type, try to generate some cargo going from + * destination to source as well. It's no error if that is not possible. */ + if (IsSymmetricCargo(cid)) { + /* Try to find the matching cargo link back to the source. If no + * link is found, don't generate return traffic. */ + CargoLink *back_link = dest->cargo_links[cid].Find(CargoLink(source, LWM_ANYWHERE)); + if (back_link == dest->cargo_links[cid].End()) return true; + + back_link->amount.new_max += *amount; + + /* Find stations around the new source area. */ + StationFinder stf(dest_area); + TileIndex tile = dest_area.tile; + + /* The the new destination area. */ + switch (source_type) { + case ST_INDUSTRY: + dest_area = static_cast<Industry *>(source)->location; + break; + case ST_TOWN: + dest_area = TileArea(src_tile, 2, 2); + break; + case ST_HEADQUARTERS: + dest_area = TileArea(Company::Get(source_id)->location_of_HQ, 2, 2); + break; + } + + /* Find a route and update transported amount if found. */ + route_link = YapfChooseRouteLink(cid, stf.GetStations(), tile, dest_area, &st, &st_unload, flags, &found, INVALID_ORDER, max_cost); + if (found) back_link->amount.new_act += *amount; + + if (route_link != NULL) { + /* Found a back link, move to station. */ + UpdateStationWaiting(Station::Get(st), cid, *amount * 256, dest->GetType(), dest->GetID(), dest_area.tile, source_type, source_id, route_link->GetOriginOrderId(), st_unload, flags); + } + } + + return true; +} + +/** + * Move cargo to a station with destination information. + * @param cid Cargo type. + * @param amount[in,out] Cargo amount, return is actually moved cargo. + * @param source_type Type of the cargo source. + * @param source_id ID of the cargo source. + * @param all_stations List of possible target stations. + * @param src_tile Source tile. + * @return True if the cargo was handled has having destinations. + */ +bool MoveCargoWithDestinationToStation(CargoID cid, uint *amount, SourceType source_type, SourceID source_id, const StationList *all_stations, TileIndex src_tile) +{ + if (!CargoHasDestinations(cid)) return false; + + /* Split the cargo into multiple destinations for industries. */ + int num_packets = 1; + if (source_type == ST_INDUSTRY) { + if (*amount > 5) num_packets++; + if (*amount > 50) num_packets += 2; + } + + /* Generate num_packets different destinations while making + * sure no cargo is lost due to rounding. */ + uint amount_packet = *amount / num_packets; + uint last_packet = *amount - (num_packets - 1) * amount_packet; + uint moved = 0; + for (; num_packets > 0; num_packets--) { + uint cur_amount = (num_packets == 1) ? last_packet : amount_packet; + + /* If we fail once, we will fail always. */ + if (!MoveCargoWithDestinationToStationWorker(cid, &cur_amount, source_type, source_id, all_stations, src_tile)) return false; + moved += cur_amount; + } + + *amount = moved; + return true; +} + +/** + * Get the current best route link for a cargo packet at a station. + * @param st Station the route starts at. + * @param cid Cargo type. + * @param cp Cargo packet with destination information. + * @param order Incoming order of the cargo packet. + * @param[out] found Set to true if a route was found. + * @return The preferred route link or NULL if either no suitable link found or the station is the final destination. + */ +RouteLink *FindRouteLinkForCargo(Station *st, CargoID cid, const CargoPacket *cp, StationID *next_unload, OrderID order, bool *found) +{ + if (cp->DestinationID() == INVALID_SOURCE) return NULL; + + StationList sl; + *sl.Append() = st; + + TileArea area = (cp->DestinationType() == ST_INDUSTRY) ? Industry::Get(cp->DestinationID())->location : TileArea(cp->DestinationXY(), 2, 2); + return YapfChooseRouteLink(cid, &sl, st->xy, area, NULL, next_unload, cp->Flags(), found, order); +} + + +/* Initialize the RouteLink-pool */ +RouteLinkPool _routelink_pool("RouteLink"); +INSTANTIATE_POOL_METHODS(RouteLink) + +/** + * Invalidate some stuff on destruction. + */ +RouteLink::~RouteLink() +{ + if (RouteLink::CleaningPool()) return; + + if (this->GetOriginOrderId() != INVALID_ORDER) StationCargoList::InvalidateAllTo(this->GetOriginOrderId(), this->GetDestination()); +} + +/** + * Update or create a single route link for a specific vehicle and cargo. + * @param v The vehicle. + * @param cargoes Create links for the cargo types whose bit is set. + * @param clear_others Should route links for cargo types nor carried be cleared? + * @param from Originating station. + * @param from_oid Originating order. + * @param to_id Destination station ID. + * @param to_oid Destination order. + * @param travel_time Travel time for the route. + */ +void UpdateVehicleRouteLinks(const Vehicle *v, uint32 cargoes, bool clear_others, Station *from, OrderID from_oid, StationID to_id, OrderID to_oid, uint32 travel_time) +{ + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + bool has_cargo = HasBit(cargoes, cid); + /* Skip if cargo not carried and we aren't supposed to clear other links. */ + if (!clear_others && !has_cargo) continue; + /* Skip cargo types that don't have destinations enabled. */ + if (!CargoHasDestinations(cid)) continue; + + RouteLinkList::iterator link; + for (link = from->goods[cid].routes.begin(); link != from->goods[cid].routes.end(); ++link) { + if ((*link)->GetOriginOrderId() == from_oid) { + if (has_cargo) { + /* Update destination if necessary. */ + (*link)->SetDestination(to_id, to_oid); + (*link)->UpdateTravelTime(travel_time); + } else { + /* Remove link. */ + delete *link; + from->goods[cid].routes.erase(link); + } + break; + } + } + + /* No link found? Append a new one. */ + if (has_cargo && link == from->goods[cid].routes.end() && RouteLink::CanAllocateItem()) { + from->goods[cid].routes.push_back(new RouteLink(to_id, from_oid, to_oid, v->owner, travel_time, v->type)); + } + } +} + +/** + * Update route links after a vehicle has arrived at a station. + * @param v The vehicle. + * @param arrived_at The station the vehicle arrived at. + */ +void UpdateVehicleRouteLinks(const Vehicle *v, StationID arrived_at) +{ + /* Only update links if we have valid previous station and orders. */ + if (v->last_station_loaded == INVALID_STATION || v->last_order_id == INVALID_ORDER || v->current_order.index == INVALID_ORDER) return; + /* Loop? Not good. */ + if (v->last_station_loaded == arrived_at) return; + + Station *from = Station::Get(v->last_station_loaded); + Station *to = Station::Get(arrived_at); + + /* Update incoming route link. */ + UpdateVehicleRouteLinks(v, v->vcache.cached_cargo_mask, false, from, v->last_order_id, arrived_at, v->current_order.index, v->travel_time); + + /* Update outgoing links. */ + CargoID cid; + FOR_EACH_SET_CARGO_ID(cid, v->vcache.cached_cargo_mask) { + /* Skip cargo types that don't have destinations enabled. */ + if (!CargoHasDestinations(cid)) continue; + + for (RouteLinkList::iterator link = to->goods[cid].routes.begin(); link != to->goods[cid].routes.end(); ++link) { + if ((*link)->GetOriginOrderId() == v->current_order.index) { + (*link)->VehicleArrived(); + break; + } + } + } +} + +/** + * Pre-fill the route links from the orders of a vehicle. + * @param v The vehicle to get the orders from. + */ +void PrefillRouteLinks(const Vehicle *v) +{ + if (CargoDestinationsDisabled()) return; + if (v->orders.list == NULL || v->orders.list->GetNumOrders() < 2) return; + + /* Can't pre-fill if the vehicle has refit or conditional orders. */ + uint count = 0; + Order *order; + FOR_VEHICLE_ORDERS(v, order) { + if (order->IsType(OT_GOTO_DEPOT) && order->IsRefit()) return; + if (order->IsType(OT_CONDITIONAL)) return; + if ((order->IsType(OT_IMPLICIT) || order->IsType(OT_GOTO_STATION)) && (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) == 0) count++; + } + + /* Increment count by one to account for the circular nature of the order list. */ + if (count > 0) count++; + + /* Collect cargo types carried by all vehicles in the shared order list. */ + uint32 transported_cargoes = 0; + for (Vehicle *u = v->FirstShared(); u != NULL; u = u->NextShared()) { + transported_cargoes |= u->vcache.cached_cargo_mask; + } + + /* Loop over all orders to update/pre-fill the route links. */ + order = v->orders.list->GetFirstOrder(); + Order *prev_order = NULL; + do { + /* Goto station or implicit order and not a go via-order, consider as destination. */ + if ((order->IsType(OT_IMPLICIT) || order->IsType(OT_GOTO_STATION)) && (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) == 0) { + /* Previous destination is set and the new destination is different, create/update route links. */ + if (prev_order != NULL && prev_order != order && prev_order->GetDestination() != order->GetDestination()) { + Station *from = Station::Get(prev_order->GetDestination()); + Station *to = Station::Get(order->GetDestination()); + /* A vehicle with the speed of 128 km/h-ish would take one tick for each of the + * #TILE_SIZE steps per tile. For aircraft, the time needs to be scaled with the + * plane speed factor. */ + uint time = DistanceManhattan(from->xy, to->xy) * TILE_SIZE * 128 / v->GetDisplayMaxSpeed(); + if (v->type == VEH_AIRCRAFT) time *= _settings_game.vehicle.plane_speed; + UpdateVehicleRouteLinks(v, transported_cargoes, true, from, prev_order->index, order->GetDestination(), order->index, time); + } + + prev_order = order; + count--; + } + + /* Get next order, wrap around if necessary. */ + order = order->next; + if (order == NULL) order = v->orders.list->GetFirstOrder(); + } while (count > 0); +} + +/** + * Remove all route links to and from a station. + * @param station Station being removed. + */ +void InvalidateStationRouteLinks(Station *station) +{ + /* Delete all outgoing links. */ + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + for (RouteLinkList::iterator link = station->goods[cid].routes.begin(); link != station->goods[cid].routes.end(); ++link) { + delete *link; + } + } + + /* Delete all incoming link. */ + Station *st_from; + FOR_ALL_STATIONS(st_from) { + if (st_from == station) continue; + + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + /* Don't increment the iterator directly in the for loop as we don't want to increment when deleting a link. */ + for (RouteLinkList::iterator link = st_from->goods[cid].routes.begin(); link != st_from->goods[cid].routes.end(); ) { + if ((*link)->GetDestination() == station->index) { + delete *link; + link = st_from->goods[cid].routes.erase(link); + } else { + ++link; + } + } + } + } +} + +/** + * Remove all route links referencing an order. + * @param order The order being removed. + */ +void InvalidateOrderRouteLinks(OrderID order) +{ + Station *st; + FOR_ALL_STATIONS(st) { + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + /* Don't increment the iterator directly in the for loop as we don't want to increment when deleting a link. */ + for (RouteLinkList::iterator link = st->goods[cid].routes.begin(); link != st->goods[cid].routes.end(); ) { + if ((*link)->GetOriginOrderId() == order || (*link)->GetDestOrderId() == order) { + delete *link; + link = st->goods[cid].routes.erase(link); + } else { + ++link; + } + } + } + } +} + +/** Age and expire route links of a station. */ +void AgeRouteLinks(Station *st) +{ + /* Reset waiting time for all vehicles currently loading. */ + for (std::list<Vehicle *>::const_iterator v_itr = st->loading_vehicles.begin(); v_itr != st->loading_vehicles.end(); ++v_itr) { + CargoID cid; + FOR_EACH_SET_CARGO_ID(cid, (*v_itr)->vcache.cached_cargo_mask) { + for (RouteLinkList::iterator link = st->goods[cid].routes.begin(); link != st->goods[cid].routes.end(); ++link) { + if ((*link)->GetOriginOrderId() == (*v_itr)->last_order_id) (*link)->wait_time = 0; + } + } + } + + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + /* Don't increment the iterator directly in the for loop as we don't want to increment when deleting a link. */ + for (RouteLinkList::iterator link = st->goods[cid].routes.begin(); link != st->goods[cid].routes.end(); ) { + if ((*link)->wait_time++ > _settings_game.economy.cargodest.max_route_age) { + delete *link; + link = st->goods[cid].routes.erase(link); + } else { + ++link; + } + } + } +} diff --git a/src/cargodest_base.h b/src/cargodest_base.h new file mode 100644 index 000000000..8d71e2f8f --- /dev/null +++ b/src/cargodest_base.h @@ -0,0 +1,182 @@ +/* $Id$ */ + +/* + * 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 cargodest_base.h Classes and types for entities having cargo destinations. */ + +#ifndef CARGODEST_BASE_H +#define CARGODEST_BASE_H + +#include "cargodest_type.h" +#include "cargo_type.h" +#include "town_type.h" +#include "core/smallvec_type.hpp" +#include "core/pool_type.hpp" +#include "order_type.h" +#include "station_type.h" +#include "company_type.h" +#include "vehicle_type.h" + +struct CargoSourceSink; + +/** Information about a demand link for cargo. */ +struct CargoLink { + CargoSourceSink *dest; ///< Destination of the link. + TransportedCargoStat<uint32> amount; ///< Transported cargo statistics. + uint weight; ///< Weight of this link. + byte weight_mod; ///< Weight modifier. + + CargoLink(CargoSourceSink *d, byte mod) : dest(d), weight(1), weight_mod(mod) {} + + /* Compare two cargo links for inequality. */ + bool operator !=(const CargoLink &other) const + { + return other.dest != dest; + } +}; + +/** An entity producing or accepting cargo with a destination. */ +struct CargoSourceSink { + /** List of destinations for each cargo type. */ + SmallVector<CargoLink, 8> cargo_links[NUM_CARGO]; + /** Sum of the destination weights for each cargo type. */ + uint cargo_links_weight[NUM_CARGO]; + + /** NOSAVE: Desired link count for each cargo. */ + uint16 num_links_expected[NUM_CARGO]; + + /** NOSAVE: Incoming link count for each cargo. */ + uint num_incoming_links[NUM_CARGO]; + + virtual ~CargoSourceSink(); + + /** Get the type of this entity. */ + virtual SourceType GetType() const = 0; + /** Get the source ID corresponding with this entity. */ + virtual SourceID GetID() const = 0; + + /** + * Test if a demand link to a destination exists. + * @param cid Cargo type for which a link should be searched. + * @param dest Destination to search for. + * @return True if a link to the destination is present. + */ + bool HasLinkTo(CargoID cid, const CargoSourceSink *dest) const + { + return this->cargo_links[cid].Contains(CargoLink(const_cast<CargoSourceSink *>(dest), 1)); + } + + /** Is this cargo accepted? */ + virtual bool AcceptsCargo(CargoID cid) const = 0; + /** Is this cargo produced? */ + virtual bool SuppliesCargo(CargoID cid) const = 0; + + /** Get the link weight for this as a destination for a specific cargo. */ + virtual uint GetDestinationWeight(CargoID cid, byte weight_mod) const = 0; + + CargoLink *GetRandomLink(CargoID cid, bool allow_self); + + /** Create the special cargo links for a cargo if not already present. */ + virtual void CreateSpecialLinks(CargoID cid); + + /** Get a random destination tile index for this cargo. */ + virtual TileArea GetTileForDestination(CargoID cid) = 0; + + void SaveCargoSourceSink(); + void LoadCargoSourceSink(); + void PtrsCargoSourceSink(); +}; + + +/** Pool of route links. */ +typedef Pool<RouteLink, RouteLinkID, 512, 262144> RouteLinkPool; +extern RouteLinkPool _routelink_pool; + +/** Holds information about a route service between two stations. */ +struct RouteLink : public RouteLinkPool::PoolItem<&_routelink_pool> { +private: + friend const struct SaveLoad *GetRouteLinkDescription(); ///< Saving and loading of route links. + friend void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner); + friend void AgeRouteLinks(Station *st); + + StationID dest; ///< Destination station id. + OrderID prev_order; ///< Id of the order the vehicle had when arriving at the origin. + OrderID next_order; ///< Id of the order the vehicle will leave the station with. + OwnerByte owner; ///< Owner of the vehicle of the link. + VehicleTypeByte vtype; ///< Vehicle type traveling this link. + uint32 travel_time; ///< Average travel duration of this link. + uint16 wait_time; ///< Days since the last vehicle traveled this link. + +public: + /** Constructor */ + RouteLink(StationID dest = INVALID_STATION, OrderID prev_order = INVALID_ORDER, OrderID next_order = INVALID_ORDER, Owner owner = INVALID_OWNER, uint32 travel_time = 0, VehicleType vtype = VEH_INVALID) + : dest(dest), prev_order(prev_order), next_order(next_order), travel_time(travel_time), wait_time(0) + { + this->owner = owner; + this->vtype = vtype; + } + + ~RouteLink(); + + /** Get the target station of this link. */ + inline StationID GetDestination() const { return this->dest; } + + /** Get the order id that lead to the origin station. */ + inline OrderID GetOriginOrderId() const { return this->prev_order; } + + /** Get the order id that lead to the destination station. */ + inline OrderID GetDestOrderId() const { return this->next_order; } + + /** Get the owner of this link. */ + inline Owner GetOwner() const { return this->owner; } + + /** Get the type of the vehicles on this link. */ + inline VehicleType GetVehicleType() const { return this->vtype; } + + /** Get the travel time of this link. */ + inline uint32 GetTravelTime() const { return this->travel_time; } + + /** Get the wait time at the origin station. */ + inline uint16 GetWaitTime() const { return this->wait_time; } + + /** Update the destination of the route link. */ + inline void SetDestination(StationID dest_id, OrderID dest_order_id) + { + this->dest = dest_id; + this->next_order = dest_order_id; + } + + /** Update the travel time with a new travel time. */ + void UpdateTravelTime(uint32 new_time) + { + /* Weighted average so that a single late vehicle will not skew the time. */ + this->travel_time = (3 * this->travel_time + new_time) / 4; + } + + /** A vehicle arrived at the origin of the link, reset waiting time. */ + void VehicleArrived() + { + this->wait_time = 0; + } +}; + + +/** + * Iterate over all valid route links from a given start. + * @param var The variable to use as the "iterator". + * @param start The #RouteLinkID to start the iteration from. + */ +#define FOR_ALL_ROUTELINKS_FROM(var, start) FOR_ALL_ITEMS_FROM(RouteLink, routelink_index, var, start) + +/** + * Iterate over all valid route links. + * @param var The variable to use as the "iterator". + */ +#define FOR_ALL_ROUTELINKS(var) FOR_ALL_ROUTELINKS_FROM(var, 0) + +#endif /* CARGODEST_BASE_H */ diff --git a/src/cargodest_func.h b/src/cargodest_func.h new file mode 100644 index 000000000..846069d86 --- /dev/null +++ b/src/cargodest_func.h @@ -0,0 +1,35 @@ +/* $Id$ */ + +/* + * 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 cargodest_func.h Functions related to cargo destinations. */ + +#ifndef CARGODEST_FUNC_H +#define CARGODEST_FUNC_H + +#include "cargodest_type.h" +#include "cargo_type.h" +#include "vehicle_type.h" +#include "station_type.h" +#include "order_type.h" + +bool CargoHasDestinations(CargoID cid); + +RouteLink *FindRouteLinkForCargo(Station *st, CargoID cid, const struct CargoPacket *cp, StationID *next_unload, OrderID order = INVALID_ORDER, bool *found = NULL); +bool MoveCargoWithDestinationToStation(CargoID cid, uint *amount, SourceType source_type, SourceID source_id, const StationList *all_stations, TileIndex src_tile); + +void UpdateVehicleRouteLinks(const Vehicle *v, StationID arrived_at); +void PrefillRouteLinks(const Vehicle *v); +void InvalidateStationRouteLinks(Station *station); +void InvalidateOrderRouteLinks(OrderID order); +void AgeRouteLinks(Station *st); + +void RebuildCargoLinkCounts(); +void UpdateCargoLinks(); + +#endif /* CARGODEST_FUNC_H */ diff --git a/src/cargodest_gui.cpp b/src/cargodest_gui.cpp new file mode 100644 index 000000000..bf5fdd1d4 --- /dev/null +++ b/src/cargodest_gui.cpp @@ -0,0 +1,176 @@ +/* $Id$ */ + +/* + * 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 cargodest_gui.cpp GUI for cargo destinations. */ + +#include "stdafx.h" +#include "window_gui.h" +#include "gfx_func.h" +#include "strings_func.h" +#include "cargodest_base.h" +#include "cargodest_gui.h" +#include "town.h" +#include "industry.h" +#include "string_func.h" +#include "gui.h" +#include "viewport_func.h" + +#include "table/strings.h" + +static const CargoSourceSink *_cur_cargo_source; + +int CDECL CargoLinkSorter(const GUICargoLink *a, const GUICargoLink *b) +{ + /* Sort by cargo type. */ + if (a->cid != b->cid) return a->cid < b->cid ? -1 : +1; + + /* Sort unspecified destination links always last. */ + if (a->link->dest == NULL) return +1; + if (b->link->dest == NULL) return -1; + + /* Sort link with the current source as destination first. */ + if (a->link->dest == _cur_cargo_source) return -1; + if (b->link->dest == _cur_cargo_source) return +1; + + /* Sort towns before industries. */ + if (a->link->dest->GetType() != b->link->dest->GetType()) { + return a->link->dest->GetType() < b->link->dest->GetType() ? +1 : -1; + } + + /* Sort by name. */ + static const CargoLink *last_b = NULL; + static char last_name[128]; + + char name[128]; + SetDParam(0, a->link->dest->GetID()); + GetString(name, a->link->dest->GetType() == ST_TOWN ? STR_TOWN_NAME : STR_INDUSTRY_NAME, lastof(name)); + + /* Cache name lookup of 'b', as the sorter is often called + * multiple times with the same 'b'. */ + if (b->link != last_b) { + last_b = b->link; + + SetDParam(0, b->link->dest->GetID()); + GetString(last_name, b->link->dest->GetType() == ST_TOWN ? STR_TOWN_NAME : STR_INDUSTRY_NAME, lastof(last_name)); + } + + return strcmp(name, last_name); +} + +CargoDestinationList::CargoDestinationList(const CargoSourceSink *o) : obj(o) +{ + this->InvalidateData(); +} + +/** Rebuild the link list from the source object. */ +void CargoDestinationList::RebuildList() +{ + if (!this->link_list.NeedRebuild()) return; + + this->link_list.Clear(); + for (CargoID i = 0; i < lengthof(this->obj->cargo_links); i++) { + for (const CargoLink *l = this->obj->cargo_links[i].Begin(); l != this->obj->cargo_links[i].End(); l++) { + *this->link_list.Append() = GUICargoLink(i, l); + } + } + + this->link_list.Compact(); + this->link_list.RebuildDone(); +} + +/** Sort the link list. */ +void CargoDestinationList::SortList() +{ + _cur_cargo_source = this->obj; + this->link_list.Sort(&CargoLinkSorter); +} + +/** Rebuild the list, e.g. when a new cargo link was added. */ +void CargoDestinationList::InvalidateData() +{ + this->link_list.ForceRebuild(); + this->RebuildList(); + this->SortList(); +} + +/** Resort the list, e.g. when a town is renamed. */ +void CargoDestinationList::Resort() +{ + this->link_list.ForceResort(); + this->SortList(); +} + +/** + * Get the height needed to display the destination list. + * @param obj Object to display the destinations of. + * @return Height needed for display. + */ +uint CargoDestinationList::GetListHeight() const +{ + uint lines = 2 + this->link_list.Length(); + return lines > 2 ? WD_PAR_VSEP_WIDE + lines * FONT_HEIGHT_NORMAL : 0; +} + +/** + * Draw the destination list. + * @param left The left most position to draw on. + * @param right The right most position to draw on. + * @param y The top position to start drawing. + * @return New \c y value below the drawn text. + */ +uint CargoDestinationList::DrawList(uint left, uint right, uint y) const +{ + if (this->link_list.Length() == 0) return y; + + DrawString(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y += WD_PAR_VSEP_WIDE + FONT_HEIGHT_NORMAL, STR_VIEW_CARGO_LAST_MONTH_OUT); + + for (const GUICargoLink *l = this->link_list.Begin(); l != this->link_list.End(); l++) { + SetDParam(0, l->cid); + SetDParam(1, l->link->amount.old_act); + SetDParam(2, l->cid); + SetDParam(3, l->link->amount.old_max); + + /* Select string according to the destination type. */ + if (l->link->dest == NULL) { + DrawString(left + 2 * WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y += FONT_HEIGHT_NORMAL, STR_VIEW_CARGO_LAST_MONTH_OTHER); + } else if (l->link->dest == obj) { + DrawString(left + 2 * WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y += FONT_HEIGHT_NORMAL, STR_VIEW_CARGO_LAST_MONTH_LOCAL); + } else { + SetDParam(4, l->link->dest->GetID()); + DrawString(left + 2 * WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y += FONT_HEIGHT_NORMAL, l->link->dest->GetType() == ST_TOWN ? STR_VIEW_CARGO_LAST_MONTH_TOWN : STR_VIEW_CARGO_LAST_MONTH_INDUSTRY); + } + } + + return y + FONT_HEIGHT_NORMAL; +} + +/** + * Handle click event onto the destination list. + * @param y Position of the click in relative to the top of the destination list. + */ +void CargoDestinationList::OnClick(uint y) const +{ + /* Subtract caption height. */ + y -= WD_PAR_VSEP_WIDE + 2 * FONT_HEIGHT_NORMAL; + + /* Calculate line from click pos. */ + y /= FONT_HEIGHT_NORMAL; + if (y >= this->link_list.Length()) return; + + /* Move viewpoint to the position of the destination. */ + const CargoLink *l = this->link_list[y].link; + if (l->dest == NULL) return; + + TileIndex xy = l->dest->GetType() == ST_TOWN ? static_cast<const Town *>(l->dest)->xy : static_cast<const Industry *>(l->dest)->location.tile; + if (_ctrl_pressed) { + ShowExtraViewPortWindow(xy); + } else { + ScrollMainWindowToTile(xy); + } +} diff --git a/src/cargodest_gui.h b/src/cargodest_gui.h new file mode 100644 index 000000000..4c7915ea0 --- /dev/null +++ b/src/cargodest_gui.h @@ -0,0 +1,48 @@ +/* $Id$ */ + +/* + * 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 cargodest_gui.h Functions/types related to the cargodest GUI. */ + +#ifndef CARGODEST_GUI_H +#define CARGODEST_GUI_H + +#include "cargodest_base.h" +#include "sortlist_type.h" + +/** Helper encapsulating a #CargoLink. */ +struct GUICargoLink { + CargoID cid; ///< Cargo ID of this link. + const CargoLink *link; ///< Pointer to the link. + + GUICargoLink(CargoID c, const CargoLink *l) : cid(c), link(l) {} +}; + +/** Sorted list of demand destinations for displaying. */ +class CargoDestinationList +{ +private: + const CargoSourceSink *obj; ///< The object which destinations are displayed. + GUIList<GUICargoLink> link_list; ///< Sorted list of destinations. + + void RebuildList(); + void SortList(); + +public: + CargoDestinationList(const CargoSourceSink *o); + + void InvalidateData(); + void Resort(); + + uint GetListHeight() const; + uint DrawList(uint left, uint right, uint y) const; + + void OnClick(uint y) const; +}; + +#endif /* CARGODEST_GUI_H */ diff --git a/src/cargodest_type.h b/src/cargodest_type.h new file mode 100644 index 000000000..fb18af0ea --- /dev/null +++ b/src/cargodest_type.h @@ -0,0 +1,31 @@ +/* $Id$ */ + +/* + * 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 cargodest_type.h Type definitions for cargo destinations. */ + +#ifndef CARGODEST_TYPE_H +#define CARGODEST_TYPE_H + +/** Flags specifying the demand mode for cargo. */ +enum CargoRoutingMode { + CRM_OFF, ///< No routing, original behaviour + CRM_FIXED_DEST, ///< Fixed destinations +}; + +/** Flags specifying how cargo should be routed. */ +enum RoutingFlags { + RF_WANT_FAST, ///< Cargo wants to travel as fast as possible. + RF_WANT_CHEAP, ///< Cargo wants to travel as cheap as possible. +}; + +/** Unique identifier for a routing link. */ +typedef uint32 RouteLinkID; +struct RouteLink; + +#endif /* CARGODEST_TYPE_H */ diff --git a/src/cargopacket.cpp b/src/cargopacket.cpp index 0b92de0c8..42eca0b55 100644 --- a/src/cargopacket.cpp +++ b/src/cargopacket.cpp @@ -12,6 +12,10 @@ #include "stdafx.h" #include "core/pool_func.hpp" #include "economy_base.h" +#include "station_base.h" +#include "cargodest_func.h" +#include "cargodest_base.h" +#include "settings_type.h" /* Initialize the cargopacket-pool */ CargoPacketPool _cargopacket_pool("CargoPacket"); @@ -24,6 +28,12 @@ CargoPacket::CargoPacket() { this->source_type = ST_INDUSTRY; this->source_id = INVALID_SOURCE; + this->dest_xy = INVALID_TILE; + this->dest_id = INVALID_SOURCE; + this->dest_type = ST_INDUSTRY; + this->flags = 0; + this->next_order = INVALID_ORDER; + this->next_station = INVALID_STATION; } /** @@ -33,21 +43,33 @@ CargoPacket::CargoPacket() * @param count Number of cargo entities to put in this packet. * @param source_type 'Type' of source the packet comes from (for subsidies). * @param source_id Actual source of the packet (for subsidies). + * @param dest_xy Destination location of the packet. + * @param dest_type 'Type' of the destination. + * @param dest_id Actual destination of the packet. + * @param next_order Desired next hop of the packet. + * @param next_station Station to unload the packet next. + * @param flags Routing flags of the packet. * @pre count != 0 * @note We have to zero memory ourselves here because we are using a 'new' * that, in contrary to all other pools, does not memset to 0. */ -CargoPacket::CargoPacket(StationID source, TileIndex source_xy, uint16 count, SourceType source_type, SourceID source_id) : +CargoPacket::CargoPacket(StationID source, TileIndex source_xy, uint16 count, SourceType source_type, SourceID source_id, TileIndex dest_xy, SourceType dest_type, SourceID dest_id, OrderID next_order, StationID next_station, byte flags) : feeder_share(0), count(count), days_in_transit(0), source_id(source_id), source(source), source_xy(source_xy), - loaded_at_xy(0) + loaded_at_xy(0), + dest_xy(dest_xy), + dest_id(dest_id), + flags(flags), + next_order(next_order), + next_station(next_station) { assert(count != 0); this->source_type = source_type; + this->dest_type = dest_type; } /** @@ -61,20 +83,32 @@ CargoPacket::CargoPacket(StationID source, TileIndex source_xy, uint16 count, So * @param feeder_share Feeder share the packet has already accumulated. * @param source_type 'Type' of source the packet comes from (for subsidies). * @param source_id Actual source of the packet (for subsidies). + * @param dest_xy Destination location of the packet. + * @param dest_type 'Type' of the destination. + * @param dest_id Actual destination of the packet. + * @param next_order Desired next hop of the packet. + * @param next_station Station to unload the packet next. + * @param flags Routing flags of the packet. * @note We have to zero memory ourselves here because we are using a 'new' * that, in contrary to all other pools, does not memset to 0. */ -CargoPacket::CargoPacket(uint16 count, byte days_in_transit, StationID source, TileIndex source_xy, TileIndex loaded_at_xy, Money feeder_share, SourceType source_type, SourceID source_id) : +CargoPacket::CargoPacket(uint16 count, byte days_in_transit, StationID source, TileIndex source_xy, TileIndex loaded_at_xy, Money feeder_share, SourceType source_type, SourceID source_id, TileIndex dest_xy, SourceType dest_type, SourceID dest_id, OrderID next_order, StationID next_station, byte flags) : feeder_share(feeder_share), count(count), days_in_transit(days_in_transit), source_id(source_id), source(source), source_xy(source_xy), - loaded_at_xy(loaded_at_xy) + loaded_at_xy(loaded_at_xy), + dest_xy(dest_xy), + dest_id(dest_id), + flags(flags), + next_order(next_order), + next_station(next_station) { assert(count != 0); this->source_type = source_type; + this->dest_type = dest_type; } /** @@ -87,7 +121,7 @@ inline CargoPacket *CargoPacket::Split(uint new_size) if (!CargoPacket::CanAllocateItem()) return NULL; Money fs = this->feeder_share * new_size / static_cast<uint>(this->count); - CargoPacket *cp_new = new CargoPacket(new_size, this->days_in_transit, this->source, this->source_xy, this->loaded_at_xy, fs, this->source_type, this->source_id); + CargoPacket *cp_new = new CargoPacket(new_size, this->days_in_transit, this->source, this->source_xy, this->loaded_at_xy, fs, this->source_type, this->source_id, this->dest_xy, this->dest_type, this->dest_id, this->next_order, this->next_station, this->flags); this->feeder_share -= fs; this->count -= new_size; return cp_new; @@ -111,9 +145,16 @@ inline void CargoPacket::Merge(CargoPacket *cp) */ /* static */ void CargoPacket::InvalidateAllFrom(SourceType src_type, SourceID src) { + /* Clear next hop of those packets that loose their destination. */ + StationCargoList::InvalidateAllTo(src_type, src); + CargoPacket *cp; FOR_ALL_CARGOPACKETS(cp) { if (cp->source_type == src_type && cp->source_id == src) cp->source_id = INVALID_SOURCE; + if (cp->dest_type == src_type && cp->dest_id == src) { + cp->dest_id = INVALID_SOURCE; + cp->dest_xy = INVALID_TILE; + } } } @@ -126,6 +167,7 @@ inline void CargoPacket::Merge(CargoPacket *cp) CargoPacket *cp; FOR_ALL_CARGOPACKETS(cp) { if (cp->source == sid) cp->source = INVALID_STATION; + if (cp->next_station == sid) cp->next_station = INVALID_STATION; } } @@ -229,6 +271,7 @@ void CargoList<Tinst>::Truncate(uint max_remaining) uint diff = local_count - max_remaining; this->count -= diff; this->cargo_days_in_transit -= cp->days_in_transit * diff; + static_cast<Tinst *>(this)->RemoveFromCacheLocal(cp, diff); cp->count = max_remaining; max_remaining = 0; } else { @@ -245,56 +288,121 @@ void CargoList<Tinst>::Truncate(uint max_remaining) * - MTA_CARGO_LOAD: Sets the loaded_at_xy value of the moved packets. * - MTA_TRANSFER: Just move without side effects. * - MTA_UNLOAD: Just move without side effects. + * - MTA_NO_ACTION: Does nothing for packets without destination, otherwise either like MTA_TRANSFER or MTA_FINAL_DELIVERY. * @param dest Destination to move the cargo to. * @param max_move Amount of cargo entities to move. * @param mta How to handle the moving (side effects). - * @param data Depending on mta the data of this variable differs: - * - MTA_FINAL_DELIVERY - Station ID of packet's origin not to remove. - * - MTA_CARGO_LOAD - Station's tile index of load. - * - MTA_TRANSFER - Unused. - * - MTA_UNLOAD - Unused. + * @param st Station ID where we are loading/unloading or STATION_INVALID for move from vehicle to vehicle. * @param payment The payment helper. + * @param cur_order The current order of the loading vehicle. + * @param did_transfer Set to true if some cargo was transfered. * * @pre mta == MTA_FINAL_DELIVERY || dest != NULL * @pre mta == MTA_UNLOAD || mta == MTA_CARGO_LOAD || payment != NULL + * @pre st != INVALID_STATION || (mta != MTA_CARGO_LOAD && payment == NULL) * @return True if there are still packets that might be moved from this cargo list. */ template <class Tinst> template <class Tother_inst> -bool CargoList<Tinst>::MoveTo(Tother_inst *dest, uint max_move, MoveToAction mta, CargoPayment *payment, uint data) +bool CargoList<Tinst>::MoveTo(Tother_inst *dest, uint max_move, MoveToAction mta, CargoPayment *payment, StationID st, OrderID cur_order, CargoID cid, bool *did_transfer) { assert(mta == MTA_FINAL_DELIVERY || dest != NULL); assert(mta == MTA_UNLOAD || mta == MTA_CARGO_LOAD || payment != NULL); + assert(st != INVALID_STATION || (mta != MTA_CARGO_LOAD && payment == NULL)); +restart:; Iterator it(this->packets.begin()); while (it != this->packets.end() && max_move > 0) { CargoPacket *cp = *it; - if (cp->source == data && mta == MTA_FINAL_DELIVERY) { - /* Skip cargo that originated from this station. */ + MoveToAction cp_mta = mta; + OrderID current_next_order = cp->NextHop(); + StationID current_next_unload = cp->NextStation(); + + if (cp_mta == MTA_CARGO_LOAD) { + /* Invalid next hop but valid destination? Recompute next hop. */ + if (current_next_order == INVALID_ORDER && cp->DestinationID() != INVALID_SOURCE) { + if (!this->UpdateCargoNextHop(cp, Station::Get(st), cid)) { + /* Failed to find destination, drop packet. */ + it = this->packets.erase(it); + continue; + } + current_next_order = cp->NextHop(); + current_next_unload = cp->NextStation(); + } + + /* Loading and not for the current vehicle? Skip. */ + if (current_next_order != cur_order) { + ++it; + continue; + } + } + + /* Has this packet a destination and are we unloading to a station (not autoreplace)? */ + if (cp->DestinationID() != INVALID_SOURCE && cp_mta != MTA_CARGO_LOAD && payment != NULL) { + /* Not forced unload and not for unloading at this station? Skip the packet. */ + if (cp_mta != MTA_UNLOAD && cp->NextStation() != INVALID_STATION && cp->NextStation() != st) { + ++it; + continue; + } + + Station *station = Station::Get(st); + + bool found; + StationID next_unload; + RouteLink *link = FindRouteLinkForCargo(station, cid, cp, &next_unload, cur_order, &found); + if (!found) { + /* Sorry, link to destination vanished, make cargo disappear. */ + static_cast<Tinst *>(this)->RemoveFromCache(cp); + delete cp; + it = this->packets.erase(it); + continue; + } + + if (link != NULL) { + /* Not final destination. */ + if (link->GetOriginOrderId() == cur_order && cp_mta != MTA_UNLOAD) { + /* Cargo should stay on the vehicle and not forced unloading? Skip. */ + ++it; + continue; + } + /* Force transfer and update next hop. */ + cp_mta = MTA_TRANSFER; + current_next_order = link->GetOriginOrderId(); + current_next_unload = next_unload; + } else { + /* Final destination, deliver. */ + cp_mta = MTA_FINAL_DELIVERY; + } + } else if (cp_mta == MTA_NO_ACTION || (cp->source == st && cp_mta == MTA_FINAL_DELIVERY)) { + /* Skip cargo that is not accepted or originated from this station. */ ++it; continue; } + if (did_transfer != NULL && cp_mta == MTA_TRANSFER) *did_transfer = true; + if (cp->count <= max_move) { /* Can move the complete packet */ max_move -= cp->count; it = this->packets.erase(it); static_cast<Tinst *>(this)->RemoveFromCache(cp); - switch (mta) { + cp->next_order = current_next_order; + cp->next_station = current_next_unload; + switch (cp_mta) { case MTA_FINAL_DELIVERY: payment->PayFinalDelivery(cp, cp->count); delete cp; continue; // of the loop case MTA_CARGO_LOAD: - cp->loaded_at_xy = data; + cp->loaded_at_xy = Station::Get(st)->xy; break; case MTA_TRANSFER: cp->feeder_share += payment->PayTransfer(cp, cp->count); break; - case MTA_UNLOAD: + default: break; } dest->Append(cp); @@ -302,7 +410,7 @@ bool CargoList<Tinst>::MoveTo(Tother_inst *dest, uint max_move, MoveToAction mta } /* Can move only part of the packet */ - if (mta == MTA_FINAL_DELIVERY) { + if (cp_mta == MTA_FINAL_DELIVERY) { /* Final delivery doesn't need package splitting. */ payment->PayFinalDelivery(cp, max_move); @@ -323,12 +431,14 @@ bool CargoList<Tinst>::MoveTo(Tother_inst *dest, uint max_move, MoveToAction mta if (cp_new == NULL) return false; static_cast<Tinst *>(this)->RemoveFromCache(cp_new); // this reflects the changes in cp. + cp_new->next_order = current_next_order; + cp_new->next_station = current_next_unload; - if (mta == MTA_TRANSFER) { + if (cp_mta == MTA_TRANSFER) { /* Add the feeder share before inserting in dest. */ cp_new->feeder_share += payment->PayTransfer(cp_new, max_move); - } else if (mta == MTA_CARGO_LOAD) { - cp_new->loaded_at_xy = data; + } else if (cp_mta == MTA_CARGO_LOAD) { + cp_new->loaded_at_xy = Station::Get(st)->xy; } dest->Append(cp_new); @@ -337,6 +447,12 @@ bool CargoList<Tinst>::MoveTo(Tother_inst *dest, uint max_move, MoveToAction mta max_move = 0; } + if (max_move > 0 && mta == MTA_CARGO_LOAD && cur_order != INVALID_ORDER && Station::Get(st)->goods[cid].cargo.CountForNextHop(INVALID_ORDER) > 0) { + /* We loaded all packets for the next hop, now load all packets without destination. */ + cur_order = INVALID_ORDER; + goto restart; + } + return it != packets.end(); } @@ -396,6 +512,158 @@ void VehicleCargoList::InvalidateCache() this->Parent::InvalidateCache(); } +/** Invalidate next unload station of all cargo packets. */ +void VehicleCargoList::InvalidateNextStation() +{ + for (VehicleCargoList::ConstIterator it = this->packets.begin(); it != this->packets.end(); ++it) { + (*it)->next_station = INVALID_STATION; + } +} + +/** + * Update the local next-hop count cache. + * @param cp Packet the be removed. + * @param amount Cargo amount to be removed. + */ +void StationCargoList::RemoveFromCacheLocal(const CargoPacket *cp, uint amount) +{ + this->order_cache[cp->next_order] -= amount; + if (this->order_cache[cp->next_order] == 0) this->order_cache.erase(cp->next_order); +} + +/** + * Update the cached values to reflect the removal of this packet. + * Decreases count and days_in_transit. + * @param cp Packet to be removed from cache. + */ +void StationCargoList::RemoveFromCache(const CargoPacket *cp) +{ + this->RemoveFromCacheLocal(cp, cp->count); + this->Parent::RemoveFromCache(cp); +} + +/** + * Update the cache to reflect adding of this packet. + * Increases count and days_in_transit. + * @param cp New packet to be inserted. + */ +void StationCargoList::AddToCache(const CargoPacket *cp) +{ + this->order_cache[cp->next_order] += cp->count; + this->Parent::AddToCache(cp); +} + +/** Invalidates the cached data and rebuild it. */ +void StationCargoList::InvalidateCache() +{ + this->order_cache.clear(); + this->Parent::InvalidateCache(); +} + +/** + * Recompute the desired next hop of a cargo packet. + * @param cp Cargo packet to update. + * @param st Station of this list. + * @param cid Cargo type of this list. + * @return False if the packet was deleted, true otherwise. + */ +bool StationCargoList::UpdateCargoNextHop(CargoPacket *cp, Station *st, CargoID cid) +{ + StationID next_unload; + RouteLink *l = FindRouteLinkForCargo(st, cid, cp, &next_unload); + + if (l == NULL) { + /* No link to destination, drop packet. */ + this->RemoveFromCache(cp); + delete cp; + return false; + } + + /* Update next hop info. */ + this->RemoveFromCache(cp); + cp->next_station = next_unload; + cp->next_order = l->GetOriginOrderId(); + this->AddToCache(cp); + + return true; +} + +/** + * Recompute the desired next hop of all cargo packets. + * @param st Station of this list. + * @param cid Cargo type of this list. + */ +void StationCargoList::UpdateCargoNextHop(Station *st, CargoID cid) +{ + uint count = 0; + StationCargoList::Iterator iter; + for (iter = this->packets.begin(); count < this->next_start + _settings_game.economy.cargodest.route_recalc_chunk && iter != this->packets.end(); count++) { + if (count < this->next_start) continue; + if ((*iter)->DestinationID() != INVALID_SOURCE) { + if (this->UpdateCargoNextHop(*iter, st, cid)) { + ++iter; + } else { + iter = this->packets.erase(iter); + } + } else { + ++iter; + } + } + + /* Update start counter for next loop. */ + this->next_start = count; + if (this->next_start >= this->packets.size()) this->next_start = 0; +} + +/** + * Invalidates the next hop info of all cargo packets with a given next order or unload station. + * @param order Next order to invalidate. + * @param st_unload Unload station to invalidate. + */ +/* static */ void StationCargoList::InvalidateAllTo(OrderID order, StationID st_unload) +{ + Station *st; + FOR_ALL_STATIONS(st) { + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + for (StationCargoList::Iterator it = st->goods[cid].cargo.packets.begin(); it != st->goods[cid].cargo.packets.end(); ++it) { + CargoPacket *cp = *it; + if (cp->next_order == order || cp->next_station == st_unload) { + /* Invalidate both order and unload station as both likely + * don't make sense anymore. */ + st->goods[cid].cargo.RemoveFromCache(cp); + cp->next_order = INVALID_ORDER; + cp->next_station = INVALID_STATION; + st->goods[cid].cargo.AddToCache(cp); + } + } + } + } +} + +/** + * Invalidates the next hop info of all cargo packets for a given destination. + * @param order Next order to invalidate. + */ +/* static */ void StationCargoList::InvalidateAllTo(SourceType type, SourceID dest) +{ + Station *st; + FOR_ALL_STATIONS(st) { + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + for (StationCargoList::Iterator it = st->goods[cid].cargo.packets.begin(); it != st->goods[cid].cargo.packets.end(); ++it) { + CargoPacket *cp = *it; + if (cp->dest_id == dest && cp->dest_type == type) { + /* Invalidate both next order and unload station as we + * want the packets to be not routed anymore. */ + st->goods[cid].cargo.RemoveFromCache(cp); + cp->next_order = INVALID_ORDER; + cp->next_station = INVALID_STATION; + st->goods[cid].cargo.AddToCache(cp); + } + } + } + } +} + /* * We have to instantiate everything we want to be usable. */ @@ -403,8 +671,8 @@ template class CargoList<VehicleCargoList>; template class CargoList<StationCargoList>; /** Autoreplace Vehicle -> Vehicle 'transfer'. */ -template bool CargoList<VehicleCargoList>::MoveTo(VehicleCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, uint data); +template bool CargoList<VehicleCargoList>::MoveTo(VehicleCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, StationID st, OrderID cur_order, CargoID cid, bool *did_transfer); /** Cargo unloading at a station. */ -template bool CargoList<VehicleCargoList>::MoveTo(StationCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, uint data); +template bool CargoList<VehicleCargoList>::MoveTo(StationCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, StationID st, OrderID cur_order, CargoID cid, bool *did_transfer); /** Cargo loading at a station. */ -template bool CargoList<StationCargoList>::MoveTo(VehicleCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, uint data); +template bool CargoList<StationCargoList>::MoveTo(VehicleCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, StationID st, OrderID cur_order, CargoID cid, bool *did_transfer); diff --git a/src/cargopacket.h b/src/cargopacket.h index dcb1415fc..c7ced9b49 100644 --- a/src/cargopacket.h +++ b/src/cargopacket.h @@ -17,7 +17,10 @@ #include "station_type.h" #include "cargo_type.h" #include "vehicle_type.h" +#include "order_type.h" +#include "cargotype.h" #include <list> +#include <map> /** Unique identifier for a single cargo packet. */ typedef uint32 CargoPacketID; @@ -44,6 +47,12 @@ private: StationID source; ///< The station where the cargo came from first. TileIndex source_xy; ///< The origin of the cargo (first station in feeder chain). TileIndex loaded_at_xy; ///< Location where this cargo has been loaded into the vehicle. + TileIndex dest_xy; ///< Destination tile or INVALID_TILE if no specific destination + SourceID dest_id; ///< Index of the destination. + SourceTypeByte dest_type; ///< Type of #dest_id. + byte flags; ///< Flags influencing the routing decision of this packet, see #RouteFlags. + OrderID next_order; ///< Next desired hop. + StationID next_station; ///< Unload at this station next. /** The CargoList caches, thus needs to know about it. */ template <class Tinst> friend class CargoList; @@ -51,13 +60,14 @@ private: friend class StationCargoList; /** We want this to be saved, right? */ friend const struct SaveLoad *GetCargoPacketDesc(); + friend bool CargodestModeChanged(int32 p1); public: /** Maximum number of items in a single cargo packet. */ static const uint16 MAX_COUNT = UINT16_MAX; CargoPacket(); - CargoPacket(StationID source, TileIndex source_xy, uint16 count, SourceType source_type, SourceID source_id); - CargoPacket(uint16 count, byte days_in_transit, StationID source, TileIndex source_xy, TileIndex loaded_at_xy, Money feeder_share = 0, SourceType source_type = ST_INDUSTRY, SourceID source_id = INVALID_SOURCE); + CargoPacket(StationID source, TileIndex source_xy, uint16 count, SourceType source_type, SourceID source_id, TileIndex dest_xy = INVALID_TILE, SourceType dest_type = ST_INDUSTRY, SourceID dest_id = INVALID_SOURCE, OrderID next_order = INVALID_ORDER, StationID next_station = INVALID_STATION, byte flags = 0); + CargoPacket(uint16 count, byte days_in_transit, StationID source, TileIndex source_xy, TileIndex loaded_at_xy, Money feeder_share = 0, SourceType source_type = ST_INDUSTRY, SourceID source_id = INVALID_SOURCE, TileIndex dest_xy = INVALID_TILE, SourceType dest_type = ST_INDUSTRY, SourceID dest_id = INVALID_SOURCE, OrderID next_order = INVALID_ORDER, StationID next_station = INVALID_STATION, byte flags = 0); /** Destroy the packet. */ ~CargoPacket() { } @@ -140,6 +150,59 @@ public: return this->loaded_at_xy; } + /** + * Gets the coordinates of the cargo's destination. + * @return The destination tile. + */ + inline TileIndex DestinationXY() const + { + return this->dest_xy; + } + + /** + * Gets the ID of the destination of the cargo. + * @return The destination ID. + */ + inline SourceID DestinationID() const + { + return this->dest_id; + } + + /** + * Gets the type of the destination of the cargo. + * @return The destination type. + */ + inline SourceType DestinationType() const + { + return this->dest_type; + } + + /** + * Gets the routing behaviour flags of this packet. + * @return The routing flags. + */ + inline byte Flags() const + { + return this->flags; + } + + /** + * Gets the order ID of the next desired hop. + * @return The order ID of the next desired hop. + */ + inline OrderID NextHop() const + { + return this->next_order; + } + + /** + * Gets the station ID where the packet should be unloaded next. + * @return The station ID where the packet should be unloaded. + */ + inline StationID NextStation() const + { + return this->next_station; + } static void InvalidateAllFrom(SourceType src_type, SourceID src); static void InvalidateAllFrom(StationID sid); @@ -179,8 +242,11 @@ public: MTA_CARGO_LOAD, ///< Load the packet onto a vehicle, i.e. set the last loaded station ID. MTA_TRANSFER, ///< The cargo is moved as part of a transfer. MTA_UNLOAD, ///< The cargo is moved as part of a forced unload. + MTA_NO_ACTION, ///< The station doesn't accept the cargo, so do nothing (only applicable to cargo without destination) }; + friend bool CargodestModeChanged(int32 p1); + protected: uint count; ///< Cache for the number of cargo entities. uint cargo_days_in_transit; ///< Cache for the sum of number of days in transit of each entity; comparable to man-hours. @@ -191,11 +257,18 @@ protected: void RemoveFromCache(const CargoPacket *cp); + void RemoveFromCacheLocal(const CargoPacket *cp, uint amount) {} + + virtual bool UpdateCargoNextHop(CargoPacket *cp, Station *st, CargoID cid) + { + return true; + } + public: /** Create the cargo list. */ CargoList() {} - ~CargoList(); + virtual ~CargoList(); void OnCleanPool(); @@ -249,7 +322,7 @@ public: void Truncate(uint max_remaining); template <class Tother_inst> - bool MoveTo(Tother_inst *dest, uint count, MoveToAction mta, CargoPayment *payment, uint data = 0); + bool MoveTo(Tother_inst *dest, uint count, MoveToAction mta, CargoPayment *payment, StationID st = INVALID_STATION, OrderID cur_order = INVALID_ORDER, CargoID cid = INVALID_CARGO, bool *did_transfer = NULL); void InvalidateCache(); }; @@ -286,6 +359,8 @@ public: void InvalidateCache(); + void InvalidateNextStation(); + /** * Are two the two CargoPackets mergeable in the context of * a list of CargoPackets for a Vehicle? @@ -299,7 +374,13 @@ public: cp1->days_in_transit == cp2->days_in_transit && cp1->source_type == cp2->source_type && cp1->source_id == cp2->source_id && - cp1->loaded_at_xy == cp2->loaded_at_xy; + cp1->loaded_at_xy == cp2->loaded_at_xy && + cp1->dest_xy == cp2->dest_xy && + cp1->dest_type == cp2->dest_type && + cp1->dest_id == cp2->dest_id && + cp1->next_order == cp2->next_order && + cp1->next_station == cp2->next_station && + cp1->flags == cp2->flags; } }; @@ -308,11 +389,51 @@ public: */ class StationCargoList : public CargoList<StationCargoList> { public: + typedef std::map<OrderID, int> OrderMap; + +protected: + /** The (direct) parent of this class. */ + typedef CargoList<StationCargoList> Parent; + + OrderMap order_cache; + uint32 next_start; ///< Packet number to start the next hop update loop from. + + void AddToCache(const CargoPacket *cp); + void RemoveFromCache(const CargoPacket *cp); + void RemoveFromCacheLocal(const CargoPacket *cp, uint amount); + + /* virtual */ bool UpdateCargoNextHop(CargoPacket *cp, Station *st, CargoID cid); + +public: /** The super class ought to know what it's doing. */ friend class CargoList<StationCargoList>; /** The stations, via GoodsEntry, have a CargoList. */ friend const struct SaveLoad *GetGoodsDesc(); + void InvalidateCache(); + + void UpdateCargoNextHop(Station *st, CargoID cid); + + /** + * Gets the cargo counts per next hop. + * @return Cargo counts. + */ + const OrderMap& CountForNextHop() const + { + return this->order_cache; + } + + /** + * Gets the cargo count for a next hop. + * @param order The next hop. + * @return The cargo count for the specified next hop. + */ + int CountForNextHop(OrderID order) const + { + OrderMap::const_iterator i = this->order_cache.find(order); + return i != this->order_cache.end() ? i->second : 0; + } + /** * Are two the two CargoPackets mergeable in the context of * a list of CargoPackets for a Vehicle? @@ -325,8 +446,17 @@ public: return cp1->source_xy == cp2->source_xy && cp1->days_in_transit == cp2->days_in_transit && cp1->source_type == cp2->source_type && - cp1->source_id == cp2->source_id; + cp1->source_id == cp2->source_id && + cp1->dest_xy == cp2->dest_xy && + cp1->dest_type == cp2->dest_type && + cp1->dest_id == cp2->dest_id && + cp1->next_order == cp2->next_order && + cp1->next_station == cp2->next_station && + cp1->flags == cp2->flags; } + + static void InvalidateAllTo(OrderID order, StationID st_unload); + static void InvalidateAllTo(SourceType type, SourceID dest); }; #endif /* CARGOPACKET_H */ diff --git a/src/clear_cmd.cpp b/src/clear_cmd.cpp index 46598866f..74a38d139 100644 --- a/src/clear_cmd.cpp +++ b/src/clear_cmd.cpp @@ -14,6 +14,7 @@ #include "command_func.h" #include "landscape.h" #include "genworld.h" +#include "layer_func.h" #include "viewport_func.h" #include "water.h" #include "core/random_func.hpp" @@ -98,8 +99,28 @@ static void DrawClearLandFence(const TileInfo *ti) EndSpriteCombine(); } +static void DrawUndergroundTile_Clear(TileInfo *ti) +{ + +} + static void DrawTile_Clear(TileInfo *ti) { + uint base_tile = TopTile(ti->tile); + uint underground_tile = DownTile(base_tile); + + bool self_underground = IsUnderground(ti->tile); + + bool have_canalization = IsTileType(base_tile, MP_HOUSE); + bool have_underground = !IsTileType(underground_tile, MP_CLEAR); + + if (self_underground && !have_canalization) + DrawGroundSprite(SPR_FLAT_BARE_LAND + SlopeToSpriteOffset(ti->tileh), PAL_NONE); + + if (self_underground && have_canalization) + DrawGroundSprite(SPR_FLAT_GRASS_TILE + SlopeToSpriteOffset(ti->tileh), PAL_NONE); + + if (!self_underground) switch (GetClearGround(ti->tile)) { case CLEAR_GRASS: DrawClearLandTile(ti, GetClearDensity(ti->tile)); @@ -124,6 +145,9 @@ static void DrawTile_Clear(TileInfo *ti) break; } + if (!self_underground && have_underground) + DrawGroundSprite(SPR_FLAT_BARE_LAND + SlopeToSpriteOffset(ti->tileh), PAL_NONE); + DrawBridgeMiddle(ti); } diff --git a/src/command.cpp b/src/command.cpp index eef7652f4..71c0f6259 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -180,9 +180,13 @@ CommandProc CmdChangeTimetable; CommandProc CmdSetVehicleOnTime; CommandProc CmdAutofillTimetable; CommandProc CmdSetTimetableStart; +CommandProc CmdReinitSeparation; CommandProc CmdOpenCloseAirport; +CommandProc CmdBuildTrafficLights; +CommandProc CmdRemoveTrafficLights; + #define DEF_CMD(proc, flags, type) {proc, #proc, (CommandFlags)flags, type} /** @@ -324,8 +328,11 @@ static const Command _command_proc_table[] = { DEF_CMD(CmdSetVehicleOnTime, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SET_VEHICLE_ON_TIME DEF_CMD(CmdAutofillTimetable, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_AUTOFILL_TIMETABLE DEF_CMD(CmdSetTimetableStart, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SET_TIMETABLE_START - + DEF_CMD(CmdReinitSeparation, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_REINIT_SEPARATION DEF_CMD(CmdOpenCloseAirport, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_OPEN_CLOSE_AIRPORT + + DEF_CMD(CmdBuildTrafficLights, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_TRAFFICLIGHTS + DEF_CMD(CmdRemoveTrafficLights, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_TRAFFICLIGHTS }; /*! diff --git a/src/command_type.h b/src/command_type.h index 49b11d3e4..d0a0a21ac 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -301,9 +301,13 @@ enum Commands { CMD_SET_VEHICLE_ON_TIME, ///< set the vehicle on time feature (timetable) CMD_AUTOFILL_TIMETABLE, ///< autofill the timetable CMD_SET_TIMETABLE_START, ///< set the date that a timetable should start + CMD_REINIT_SEPARATION, ///< reinit timetable separation with new parameters CMD_OPEN_CLOSE_AIRPORT, ///< open/close an airport to incoming aircraft + CMD_BUILD_TRAFFICLIGHTS, ///< place traffic lights on a road crossing + CMD_REMOVE_TRAFFICLIGHTS, ///< remove traffic lights + CMD_END, ///< Must ALWAYS be on the end of this list!! (period) }; diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index a131131ad..679ec7299 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -27,6 +27,7 @@ #include "screenshot.h" #include "genworld.h" #include "strings_func.h" +#include "layer_func.h" #include "viewport_func.h" #include "window_func.h" #include "date_func.h" @@ -1059,6 +1060,7 @@ DEF_CONSOLE_CMD(ConRestart) } /* Don't copy the _newgame pointers to the real pointers, so call SwitchToMode directly */ + _settings_game.game_creation.layers = FindFirstBit(LayerCount()); _settings_game.game_creation.map_x = MapLogX(); _settings_game.game_creation.map_y = FindFirstBit(MapSizeY()); _switch_mode = SM_RESTARTGAME; diff --git a/src/date.cpp b/src/date.cpp index 9df648321..579c0ab0c 100644 --- a/src/date.cpp +++ b/src/date.cpp @@ -19,6 +19,7 @@ #include "vehicle_base.h" #include "rail_gui.h" #include "saveload/saveload.h" +#include "cargodest_func.h" Year _cur_year; ///< Current year, starting at 0 Month _cur_month; ///< Current month (0..11) @@ -154,6 +155,15 @@ Date ConvertYMDToDate(Year year, Month month, Day day) return DAYS_TILL(year) + days; } +/** + * Converts the current day counter and date fraction into an absolute tick value. + * @returns current time as ticks + */ +Ticks GetCurrentTickCount() +{ + return _date * DAY_TICKS + _date_fract; +} + /** Functions used by the IncreaseDate function */ extern void EnginesDailyLoop(); @@ -238,6 +248,7 @@ static void OnNewMonth() IndustryMonthlyLoop(); SubsidyMonthlyLoop(); StationMonthlyLoop(); + UpdateCargoLinks(); #ifdef ENABLE_NETWORK if (_network_server) NetworkServerMonthlyLoop(); #endif /* ENABLE_NETWORK */ diff --git a/src/date_func.h b/src/date_func.h index 6bbde5955..f32e6c7c9 100644 --- a/src/date_func.h +++ b/src/date_func.h @@ -23,6 +23,9 @@ extern uint16 _tick_counter; void SetDate(Date date, DateFract fract); void ConvertDateToYMD(Date date, YearMonthDay *ymd); Date ConvertYMDToDate(Year year, Month month, Day day); +Ticks GetCurrentTickCount(); + +#define YMD_TO_DATE(ymd) (ConvertYMDToDate(ymd.year, ymd.month, ymd.day)) /** * Checks whether the given year is a leap year or not. diff --git a/src/date_gui.cpp b/src/date_gui.cpp index b2421556c..8a59b7e74 100644 --- a/src/date_gui.cpp +++ b/src/date_gui.cpp @@ -16,6 +16,7 @@ #include "window_gui.h" #include "date_gui.h" #include "core/geometry_func.hpp" +#include "settings_type.h" #include "widgets/dropdown_type.h" #include "widgets/date_widget.h" @@ -63,7 +64,7 @@ struct SetDateWindow : Window { * Helper function to construct the dropdown. * @param widget the dropdown widget to create the dropdown for */ - void ShowDateDropDown(int widget) + virtual void ShowDateDropDown(int widget) { int selected; DropDownList *list = new DropDownList(); @@ -144,9 +145,8 @@ struct SetDateWindow : Window { case WID_SD_YEAR: ShowDateDropDown(widget); break; - case WID_SD_SET_DATE: - if (this->callback != NULL) this->callback(this->parent, ConvertYMDToDate(this->date.year, this->date.month, this->date.day)); + if (this->callback != NULL) this->callback(this->parent, ConvertYMDToDate(this->date.year, this->date.month, this->date.day) * DAY_TICKS); delete this; break; } @@ -171,6 +171,122 @@ struct SetDateWindow : Window { } }; +struct SetMinutesWindow : SetDateWindow +{ + Minutes minutes; + + /** Constructor. */ + SetMinutesWindow(const WindowDesc *desc, WindowNumber window_number, Window *parent, DateTicks initial_date, Year min_year, Year max_year, SetDateCallback *callback) : + SetDateWindow(desc, window_number, parent, initial_date, min_year, max_year, callback), + minutes(initial_date / _settings_client.gui.ticks_per_minute) + { + } + + /** + * Helper function to construct the dropdown. + * @param widget the dropdown widget to create the dropdown for + */ + virtual void ShowDateDropDown(int widget) + { + int selected; + DropDownList *list = new DropDownList(); + + switch (widget) { + default: NOT_REACHED(); + + case WID_SD_DAY: + for (uint i = 0; i < 60; i++) { + DropDownListParamStringItem *item = new DropDownListParamStringItem(STR_JUST_INT, i, false); + item->SetParam(0, i); + list->push_back(item); + } + selected = MINUTES_MINUTE(minutes); + break; + + case WID_SD_MONTH: + for (uint i = 0; i < 24; i++) { + DropDownListParamStringItem *item = new DropDownListParamStringItem(STR_JUST_INT, i, false); + item->SetParam(0, i); + list->push_back(item); + } + selected = MINUTES_HOUR(minutes); + + break; + } + + ShowDropDownList(this, list, selected, widget); + } + + virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) + { + Dimension d = {0, 0}; + switch (widget) { + default: return; + + case WID_SD_DAY: + for (uint i = 0; i < 60; i++) { + SetDParam(0, i); + d = maxdim(d, GetStringBoundingBox(STR_JUST_INT)); + } + break; + + case WID_SD_MONTH: + for (uint i = 0; i < 24; i++) { + SetDParam(0, i); + d = maxdim(d, GetStringBoundingBox(STR_JUST_INT)); + } + break; + } + + d.width += padding.width; + d.height += padding.height; + *size = d; + } + + virtual void SetStringParameters(int widget) const + { + switch (widget) { + case WID_SD_DAY: SetDParam(0, MINUTES_MINUTE(minutes)); break; + case WID_SD_MONTH: SetDParam(0, MINUTES_HOUR(minutes)); break; + } + } + + virtual void OnClick(Point pt, int widget, int click_count) + { + switch (widget) { + case WID_SD_DAY: + case WID_SD_MONTH: + case WID_SD_YEAR: + ShowDateDropDown(widget); + break; + + case WID_SD_SET_DATE: + if (this->callback != NULL) this->callback(this->parent, ((DateTicks)minutes - _settings_client.gui.clock_offset) * _settings_client.gui.ticks_per_minute); + delete this; + break; + } + } + + virtual void OnDropdownSelect(int widget, int index) + { + Minutes current = 0; + switch (widget) { + case WID_SD_DAY: + current = MINUTES_DATE(MINUTES_DAY(CURRENT_MINUTE), MINUTES_HOUR(minutes), index); + break; + + case WID_SD_MONTH: + current = MINUTES_DATE(MINUTES_DAY(CURRENT_MINUTE), index, MINUTES_MINUTE(minutes)); + break; + } + + if (current < (CURRENT_MINUTE - 60)) current += 60 * 24; + minutes = current; + + this->SetDirty(); + } +}; + /** Widgets for the date setting window. */ static const NWidgetPart _nested_set_date_widgets[] = { NWidget(NWID_HORIZONTAL), @@ -193,6 +309,26 @@ static const NWidgetPart _nested_set_date_widgets[] = { EndContainer() }; +static const NWidgetPart _nested_set_minutes_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_BROWN), + NWidget(WWT_CAPTION, COLOUR_BROWN), SetDataTip(STR_DATE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_BROWN), + NWidget(NWID_VERTICAL), SetPIP(6, 6, 6), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(6, 6, 6), + NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_SD_MONTH), SetFill(1, 0), SetDataTip(STR_JUST_INT, STR_DATE_MINUTES_MONTH_TOOLTIP), + NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_SD_DAY), SetFill(1, 0), SetDataTip(STR_JUST_INT, STR_DATE_MINUTES_DAY_TOOLTIP), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(NWID_SPACER), SetFill(1, 0), + NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_SD_SET_DATE), SetMinimalSize(100, 12), SetDataTip(STR_DATE_SET_DATE, STR_DATE_SET_DATE_TOOLTIP), + NWidget(NWID_SPACER), SetFill(1, 0), + EndContainer(), + EndContainer(), + EndContainer() +}; + /** Description of the date setting window. */ static const WindowDesc _set_date_desc( WDP_CENTER, 0, 0, @@ -201,6 +337,13 @@ static const WindowDesc _set_date_desc( _nested_set_date_widgets, lengthof(_nested_set_date_widgets) ); +static const WindowDesc _set_minutes_desc( + WDP_CENTER, 0, 0, + WC_SET_DATE, WC_NONE, + 0, + _nested_set_minutes_widgets, lengthof(_nested_set_minutes_widgets) +); + /** * Create the new 'set date' window * @param window_number number for the window @@ -210,8 +353,13 @@ static const WindowDesc _set_date_desc( * @param max_year the maximum year (inclusive) to show in the year dropdown * @param callback the callback to call once a date has been selected */ -void ShowSetDateWindow(Window *parent, int window_number, Date initial_date, Year min_year, Year max_year, SetDateCallback *callback) +void ShowSetDateWindow(Window *parent, int window_number, DateTicks initial_date, Year min_year, Year max_year, SetDateCallback *callback) { DeleteWindowByClass(WC_SET_DATE); - new SetDateWindow(&_set_date_desc, window_number, parent, initial_date, min_year, max_year, callback); + + if (!_settings_client.gui.time_in_minutes) { + new SetDateWindow(&_set_date_desc, window_number, parent, initial_date / DAY_TICKS, min_year, max_year, callback); + } else { + new SetMinutesWindow(&_set_minutes_desc, window_number, parent, initial_date + (_settings_client.gui.clock_offset * _settings_client.gui.ticks_per_minute), min_year, max_year, callback); + } } diff --git a/src/date_gui.h b/src/date_gui.h index 314baba3c..0c8dcba86 100644 --- a/src/date_gui.h +++ b/src/date_gui.h @@ -20,8 +20,8 @@ * @param w the window that sends the callback * @param date the date that has been chosen */ -typedef void SetDateCallback(const Window *w, Date date); +typedef void SetDateCallback(const Window *w, DateTicks date); -void ShowSetDateWindow(Window *parent, int window_number, Date initial_date, Year min_year, Year max_year, SetDateCallback *callback); +void ShowSetDateWindow(Window *parent, int window_number, DateTicks initial_date, Year min_year, Year max_year, SetDateCallback *callback); #endif /* DATE_GUI_H */ diff --git a/src/date_type.h b/src/date_type.h index a004413a7..120bab508 100644 --- a/src/date_type.h +++ b/src/date_type.h @@ -12,10 +12,11 @@ #ifndef DATE_TYPE_H #define DATE_TYPE_H - typedef int32 Date; ///< The type to store our dates in typedef uint16 DateFract; ///< The fraction of a date we're in, i.e. the number of ticks since the last date changeover typedef int32 Ticks; ///< The type to store ticks in +typedef int64 DateTicks; ///< The type to store dates in when tick-precision is required +typedef int32 Minutes; ///< The type to store minutes in typedef int32 Year; ///< Type for the year, note: 0 based, i.e. starts at the year 0. typedef uint8 Month; ///< Type for the month, note: 0 based, i.e. 0 = January, 11 = December. @@ -31,6 +32,8 @@ static const int DAY_TICKS = 74; ///< ticks per day static const int DAYS_IN_YEAR = 365; ///< days per year static const int DAYS_IN_LEAP_YEAR = 366; ///< sometimes, you need one day more... +#define DATE_UNIT_SIZE (_settings_client.gui.time_in_minutes ? _settings_client.gui.ticks_per_minute : DAY_TICKS) + static const int STATION_RATING_TICKS = 185; ///< cycle duration for updating station rating static const int STATION_ACCEPTANCE_TICKS = 250; ///< cycle duration for updating station acceptance static const int CARGO_AGING_TICKS = 185; ///< cycle duration for aging cargo @@ -38,7 +41,6 @@ static const int INDUSTRY_PRODUCE_TICKS = 256; ///< cycle duration for industr static const int TOWN_GROWTH_TICKS = 70; ///< cycle duration for towns trying to grow. (this originates from the size of the town array in TTD static const int INDUSTRY_CUT_TREE_TICKS = INDUSTRY_PRODUCE_TICKS * 2; ///< cycle duration for lumber mill's extra action - /* * ORIGINAL_BASE_YEAR, ORIGINAL_MAX_YEAR and DAYS_TILL_ORIGINAL_BASE_YEAR are * primarily used for loading newgrf and savegame data and returning some @@ -95,6 +97,21 @@ static const Year MAX_YEAR = 5000000; /** The number of days till the last day */ #define MAX_DAY (DAYS_TILL(MAX_YEAR + 1) - 1) +/** The day when converting to minutes */ +#define MINUTES_DAY(minutes) (minutes / 1440) + +/** The hour when converting to minutes */ +#define MINUTES_HOUR(minutes) ((minutes / 60) % 24) + +/** The day when converting to minutes */ +#define MINUTES_MINUTE(minutes) (minutes % 60) + +/** Convert minutes to a date */ +#define MINUTES_DATE(day, hour, minute) ((day * 1440) + (hour * 60) + minute) + +/** Get the current date in minutes */ +#define CURRENT_MINUTE ((((DateTicks)_date * DAY_TICKS) + _date_fract) / _settings_client.gui.ticks_per_minute) + /** * Data structure to convert between Date and triplet (year, month, and day). * @see ConvertDateToYMD(), ConvertYMDToDate() diff --git a/src/debug.cpp b/src/debug.cpp index e6fb61292..02afa9928 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -39,6 +39,7 @@ int _debug_sl_level; int _debug_gamelog_level; int _debug_desync_level; int _debug_console_level; +int _debug_cargodest_level; #ifdef RANDOM_DEBUG int _debug_random_level; #endif @@ -67,6 +68,7 @@ struct DebugLevel { DEBUG_LEVEL(gamelog), DEBUG_LEVEL(desync), DEBUG_LEVEL(console), + DEBUG_LEVEL(cargodest), #ifdef RANDOM_DEBUG DEBUG_LEVEL(random), #endif diff --git a/src/debug.h b/src/debug.h index f7e771719..630b1c98f 100644 --- a/src/debug.h +++ b/src/debug.h @@ -51,6 +51,7 @@ extern int _debug_gamelog_level; extern int _debug_desync_level; extern int _debug_console_level; + extern int _debug_cargodest_level; #ifdef RANDOM_DEBUG extern int _debug_random_level; #endif diff --git a/src/departures.cpp b/src/departures.cpp new file mode 100644 index 000000000..9edefa3e0 --- /dev/null +++ b/src/departures.cpp @@ -0,0 +1,646 @@ +/* $Id$ */ + +/* + * 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 departures.cpp Scheduled departures from a station. */ + +#include "stdafx.h" +#include "debug.h" +#include "gui.h" +#include "textbuf_gui.h" +#include "strings_func.h" +#include "window_func.h" +#include "vehicle_func.h" +#include "string_func.h" +#include "window_gui.h" +#include "timetable.h" +#include "vehiclelist.h" +#include "company_base.h" +#include "date_func.h" +#include "departures_gui.h" +#include "station_base.h" +#include "vehicle_gui_base.h" +#include "vehicle_base.h" +#include "vehicle_gui.h" +#include "order_base.h" +#include "settings_type.h" +#include "core/smallvec_type.hpp" +#include "date_type.h" +#include "company_type.h" +#include "cargo_type.h" +#include "departures_func.h" +#include "departures_type.h" + +/** A scheduled order. */ +typedef struct OrderDate +{ + const Order *order; ///< The order + const Vehicle *v; ///< The vehicle carrying out the order + DateTicks expected_date;///< The date on which the order is expected to complete + Ticks lateness; ///< How late this order is expected to finish + DepartureStatus status; ///< Whether the vehicle has arrived to carry out the order yet +} OrderDate; + +static bool IsDeparture(const Order *order, StationID station) { + return (order->GetType() == OT_GOTO_STATION && + (StationID)order->GetDestination() == station && + (order->GetLoadType() != OLFB_NO_LOAD || + _settings_client.gui.departure_show_all_stops) && + order->wait_time != 0); +} + +static bool IsVia(const Order *order, StationID station) { + return ((order->GetType() == OT_GOTO_STATION || + order->GetType() == OT_GOTO_WAYPOINT) && + (StationID)order->GetDestination() == station && + (order->GetNonStopType() == ONSF_NO_STOP_AT_ANY_STATION || + order->GetNonStopType() == ONSF_NO_STOP_AT_DESTINATION_STATION)); +} + +static bool IsArrival(const Order *order, StationID station) { + return (order->GetType() == OT_GOTO_STATION && + (StationID)order->GetDestination() == station && + (order->GetUnloadType() != OUFB_NO_UNLOAD || + _settings_client.gui.departure_show_all_stops) && + order->wait_time != 0); +} + +/** + * Compute an up-to-date list of departures for a station. + * @param station the station to compute the departures of + * @param show_vehicle_types the types of vehicles to include in the departure list + * @param type the type of departures to get (departures or arrivals) + * @param show_vehicles_via whether to include vehicles that have this station in their orders but do not stop at it + * @return a list of departures, which is empty if an error occurred + */ +DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[5], DepartureType type, bool show_vehicles_via) +{ + /* This function is the meat of the departure boards functionality. */ + /* As an overview, it works by repeatedly considering the best possible next departure to show. */ + /* By best possible we mean the one expected to arrive at the station first. */ + /* However, we do not consider departures whose scheduled time is too far in the future, even if they are expected before some delayed ones. */ + /* This code can probably be made more efficient. I haven't done so in order to keep both its (relative) simplicity and my (relative) sanity. */ + /* Having written that, it's not exactly slow at the moment. */ + + /* The list of departures which will be returned as a result. */ + SmallVector<Departure*, 32> *result = new SmallVector<Departure*, 32>(); + + /* A list of the next scheduled orders to be considered for inclusion in the departure list. */ + SmallVector<OrderDate*, 32> next_orders; + + /* The maximum possible date for departures to be scheduled to occur. */ + DateTicks max_date = _settings_client.gui.max_departure_time * DAY_TICKS; + + /* The scheduled order in next_orders with the earliest expected_date field. */ + OrderDate *least_order = NULL; + + /* Get all the vehicles stopping at this station. */ + /* We do this to get the order which is the first time they will stop at this station. */ + /* This order is stored along with some more information. */ + /* We keep a pointer to the `least' order (the one with the soonest expected completion time). */ + for (uint i = 0; i < 4; ++i) { + VehicleList vehicles; + + if (!show_vehicle_types[i]) { + /* Don't show vehicles whose type we're not interested in. */ + continue; + } + + /* MAX_COMPANIES is probably the wrong thing to put here, but it works. GenerateVehicleSortList doesn't check the company when the type of list is VL_STATION_LIST (r20801). */ + if (!GenerateVehicleSortList(&vehicles, VehicleListIdentifier(VL_STATION_LIST, (VehicleType)(VEH_TRAIN + i), MAX_COMPANIES, station).Pack())) { + /* Something went wrong: panic! */ + return result; + } + + /* Get the first order for each vehicle for the station we're interested in that doesn't have No Loading set. */ + /* We find the least order while we're at it. */ + for (const Vehicle **v = vehicles.Begin(); v != vehicles.End(); v++) { + if (_settings_client.gui.departure_only_passengers) { + bool carries_passengers = false; + + const Vehicle *u = *v; + while (u != NULL) { + if (u->cargo_type == CT_PASSENGERS && u->cargo_cap > 0) { + carries_passengers = true; + break; + } + u = u->Next(); + } + + if (carries_passengers == false) { + continue; + } + } + + const Order *order = (*v)->GetOrder((*v)->cur_implicit_order_index % (*v)->GetNumOrders()); + DateTicks start_date = (DateTicks)_date_fract - (*v)->current_order_time; + DepartureStatus status = D_TRAVELLING; + + /* If the vehicle is stopped in a depot, ignore it. */ + if ((*v)->IsStoppedInDepot()) { + continue; + } + + /* If the vehicle is heading for a depot to stop there, then its departures are cancelled. */ + if ((*v)->current_order.IsType(OT_GOTO_DEPOT) && (*v)->current_order.GetDepotActionType() & ODATFB_HALT) { + status = D_CANCELLED; + } + + if ((*v)->current_order.IsType(OT_LOADING)) { + /* Account for the vehicle having reached the current order and being in the loading phase. */ + status = D_ARRIVED; + start_date -= order->travel_time + (((*v)->lateness_counter < 0) ? (*v)->lateness_counter : 0); + } + + /* Loop through the vehicle's orders until we've found a suitable order or we've determined that no such order exists. */ + /* We only need to consider each order at most once. */ + bool foundOrder = false; + for (int i = (*v)->GetNumOrders(); i > 0; --i) { + start_date += order->travel_time + order->wait_time; + + /* If the scheduled departure date is too far in the future, stop. */ + if (start_date - (*v)->lateness_counter > max_date) { + break; + } + + /* If the order is a conditional branch, handle it. */ + if (order->IsType(OT_CONDITIONAL)) { + switch(_settings_client.gui.departure_conditionals) { + case 0: { + /* Give up */ + break; + } + case 1: { + /* Take the branch */ + if (status != D_CANCELLED) { + status = D_TRAVELLING; + } + order = (*v)->GetOrder(order->GetConditionSkipToOrder()); + if (order == NULL) { + break; + } + + start_date -= order->travel_time; + + continue; + } + case 2: { + /* Do not take the branch */ + if (status != D_CANCELLED) { + status = D_TRAVELLING; + } + order = (order->next == NULL) ? (*v)->GetFirstOrder() : order->next; + continue; + } + } + } + + /* Skip it if it's an automatic order. */ + if (order->IsType(OT_IMPLICIT)) { + order = (order->next == NULL) ? (*v)->GetFirstOrder() : order->next; + continue; + } + + /* If an order doesn't have a travel time set, then stop. */ + if (order->travel_time == 0) { + break; + } + + /* If the vehicle will be stopping at and loading from this station, and its wait time is not zero, then it is a departure. */ + /* If the vehicle will be stopping at and unloading at this station, and its wait time is not zero, then it is an arrival. */ + if ((type == D_DEPARTURE && IsDeparture(order, station)) || + (type == D_DEPARTURE && show_vehicles_via && IsVia(order, station)) || + (type == D_ARRIVAL && IsArrival(order, station))) { + /* If the departure was scheduled to have already begun and has been cancelled, do not show it. */ + if (start_date < 0 && status == D_CANCELLED) { + break; + } + + OrderDate *od = new OrderDate(); + od->order = order; + od->v = *v; + /* We store the expected date for now, so that vehicles will be shown in order of expected time. */ + od->expected_date = start_date; + od->lateness = (*v)->lateness_counter > 0 ? (*v)->lateness_counter : 0; + od->status = status; + + /* If we are early, use the scheduled date as the expected date. We also take lateness to be zero. */ + if ((*v)->lateness_counter < 0 && !(*v)->current_order.IsType(OT_LOADING)) { + od->expected_date -= (*v)->lateness_counter; + } + + /* Update least_order if this is the current least order. */ + if (least_order == NULL) { + least_order = od; + } else if (least_order->expected_date - least_order->lateness - (type == D_ARRIVAL ? least_order->order->wait_time : 0) > od->expected_date - od->lateness - (type == D_ARRIVAL ? od->order->wait_time : 0)) { + least_order = od; + } + + *(next_orders.Append(1)) = od; + + foundOrder = true; + /* We're done with this vehicle. */ + break; + } else { + /* Go to the next order in the list. */ + if (status != D_CANCELLED) { + status = D_TRAVELLING; + } + order = (order->next == NULL) ? (*v)->GetFirstOrder() : order->next; + } + } + } + } + + /* No suitable orders found? Then stop. */ + if (next_orders.Length() == 0) { + return result; + } + + /* We now find as many departures as we can. It's a little involved so I'll try to explain each major step. */ + /* The countdown from 10000 is a safeguard just in case something nasty happens. 10000 seemed large enough. */ + for(int i = 10000; i > 0; --i) { + /* I should probably try to convince you that this loop always terminates regardless of the safeguard. */ + /* 1. next_orders contains at least one element. */ + /* 2. The loop terminates if result->Length() exceeds a fixed (for this loop) value, or if the least order's scheduled date is later than max_date. */ + /* (We ignore the case that the least order's scheduled date has overflown, as it is a relative rather than absolute date.) */ + /* 3. Every time we loop round, either result->Length() will have increased -OR- we will have increased the expected_date of one of the elements of next_orders. */ + /* 4. Therefore the loop must eventually terminate. */ + + /* least_order is the best candidate for the next departure. */ + + /* First, we check if we can stop looking for departures yet. */ + if (result->Length() >= _settings_client.gui.max_departures || + least_order->expected_date - least_order->lateness > max_date) { + break; + } + + /* We already know the least order and that it's a suitable departure, so make it into a departure. */ + Departure *d = new Departure(); + d->scheduled_date = (DateTicks)_date * DAY_TICKS + least_order->expected_date - least_order->lateness; + d->lateness = least_order->lateness; + d->status = least_order->status; + d->vehicle = least_order->v; + d->type = type; + d->order = least_order->order; + + /* We'll be going through the order list later, so we need a separate variable for it. */ + const Order *order = least_order->order; + + if (type == D_DEPARTURE) { + /* Computing departures: */ + /* We want to find out where it will terminate, making a list of the stations it calls at along the way. */ + /* We only count stations where unloading happens as being called at - i.e. pickup-only stations are ignored. */ + /* Where the vehicle terminates is defined as the last unique station called at by the vehicle from the current order. */ + + /* If the vehicle loops round to the current order without a terminus being found, then it terminates upon reaching its current order again. */ + + /* We also determine which station this departure is going via, if any. */ + /* A departure goes via a station if it is the first station for which the vehicle has an order to go via or non-stop via. */ + /* Multiple departures on the same journey may go via different stations. That a departure can go via at most one station is intentional. */ + + /* We keep track of potential via stations along the way. If we call at a station immediately after going via it, then it is the via station. */ + StationID candidate_via = INVALID_STATION; + + /* Go through the order list, looping if necessary, to find a terminus. */ + /* Get the next order, which may be the vehicle's first order. */ + order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + /* We only need to consider each order at most once. */ + bool found_terminus = false; + CallAt c = CallAt((StationID)order->GetDestination(), d->scheduled_date); + for (int i = least_order->v->GetNumOrders(); i > 0; --i) { + /* If we reach the order at which the departure occurs again, then use the departure station as the terminus. */ + if (order == least_order->order) { + /* If we're not calling anywhere, then skip this departure. */ + found_terminus = (d->calling_at.Length() > 0); + break; + } + + /* If the order is a conditional branch, handle it. */ + if (order->IsType(OT_CONDITIONAL)) { + switch(_settings_client.gui.departure_conditionals) { + case 0: { + /* Give up */ + break; + } + case 1: { + /* Take the branch */ + order = least_order->v->GetOrder(order->GetConditionSkipToOrder()); + if (order == NULL) { + break; + } + continue; + } + case 2: { + /* Do not take the branch */ + order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + continue; + } + } + } + + /* If we reach the original station again, then use it as the terminus. */ + if (order->GetType() == OT_GOTO_STATION && + (StationID)order->GetDestination() == station && + (order->GetUnloadType() != OUFB_NO_UNLOAD || + _settings_client.gui.departure_show_all_stops) && + order->GetNonStopType() != ONSF_NO_STOP_AT_ANY_STATION && + order->GetNonStopType() != ONSF_NO_STOP_AT_DESTINATION_STATION) { + /* If we're not calling anywhere, then skip this departure. */ + found_terminus = (d->calling_at.Length() > 0); + break; + } + + /* Check if we're going via this station. */ + if ((order->GetNonStopType() == ONSF_NO_STOP_AT_ANY_STATION || + order->GetNonStopType() == ONSF_NO_STOP_AT_DESTINATION_STATION) && + order->GetType() == OT_GOTO_STATION && + d->via == INVALID_STATION) { + candidate_via = (StationID)order->GetDestination(); + } + + if (c.scheduled_date != 0 && order->travel_time != 0) { + c.scheduled_date += order->travel_time; + } else { + c.scheduled_date = 0; + } + + c.station = (StationID)order->GetDestination(); + + /* We're not interested in this order any further if we're not calling at it. */ + if ((order->GetUnloadType() == OUFB_NO_UNLOAD && + !_settings_client.gui.departure_show_all_stops) || + (order->GetType() != OT_GOTO_STATION && + order->GetType() != OT_IMPLICIT) || + order->GetNonStopType() == ONSF_NO_STOP_AT_ANY_STATION || + order->GetNonStopType() == ONSF_NO_STOP_AT_DESTINATION_STATION) { + c.scheduled_date += order->wait_time; + order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + continue; + } + + /* If this order's station is already in the calling, then the previous called at station is the terminus. */ + if (d->calling_at.Contains(c)) { + found_terminus = true; + break; + } + + /* If appropriate, add the station to the calling at list and make it the candidate terminus. */ + if ((order->GetType() == OT_GOTO_STATION || + order->GetType() == OT_IMPLICIT) && + order->GetNonStopType() != ONSF_NO_STOP_AT_ANY_STATION && + order->GetNonStopType() != ONSF_NO_STOP_AT_DESTINATION_STATION) { + if (d->via == INVALID_STATION && candidate_via == (StationID)order->GetDestination()) { + d->via = (StationID)order->GetDestination(); + } + d->terminus = c; + *(d->calling_at.Append(1)) = c; + } + + /* If we unload all at this station, then it is the terminus. */ + if (order->GetType() == OT_GOTO_STATION && + order->GetUnloadType() == OUFB_UNLOAD) { + if (d->calling_at.Length() > 0) { + found_terminus = true; + } + break; + } + + c.scheduled_date += order->wait_time; + + /* Get the next order, which may be the vehicle's first order. */ + order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + } + + if (found_terminus) { + /* Add the departure to the result list. */ + bool duplicate = false; + + if (_settings_client.gui.departure_merge_identical) { + for (uint i = 0; i < result->Length(); ++i) { + if (*d == **(result->Get(i))) { + duplicate = true; + break; + } + } + } + + if (!duplicate) { + *(result->Append(1)) = d; + + if (_settings_client.gui.departure_smart_terminus && type == D_DEPARTURE) { + for (uint i = 0; i < result->Length()-1; ++i) { + Departure *d_first = *(result->Get(i)); + uint k = d_first->calling_at.Length()-2; + for (uint j = d->calling_at.Length(); j > 0; --j) { + CallAt c = CallAt(*(d->calling_at.Get(j-1))); + + if (d_first->terminus >= c && d_first->calling_at.Length() >= 2) { + d_first->terminus = CallAt(*(d_first->calling_at.Get(k))); + + if (k == 0) break; + + k--; + } + } + } + } + + /* If the vehicle is expected to be late, we want to know what time it will arrive rather than depart. */ + /* This is done because it looked silly to me to have a vehicle not be expected for another few days, yet it be at the same time pulling into the station. */ + if (d->status != D_ARRIVED && + d->lateness > 0) { + d->lateness -= least_order->order->wait_time; + } + } + } + } else { + /* Computing arrivals: */ + /* First we need to find the origin of the order. This is somewhat like finding a terminus, but a little more involved since order lists are singly linked. */ + /* The next stage is simpler. We just need to add all the stations called at on the way to the current station. */ + /* Again, we define a station as being called at if the vehicle loads from it. */ + + /* However, the very first thing we do is use the arrival time as the scheduled time instead of the departure time. */ + d->scheduled_date -= order->wait_time; + + const Order *candidate_origin = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + bool found_origin = false; + + while (candidate_origin != least_order->order) { + if ((candidate_origin->GetLoadType() != OLFB_NO_LOAD || + _settings_client.gui.departure_show_all_stops) && + (candidate_origin->GetType() == OT_GOTO_STATION || + candidate_origin->GetType() == OT_IMPLICIT) && + candidate_origin->GetDestination() != station) { + const Order *o = (candidate_origin->next == NULL) ? least_order->v->GetFirstOrder() : candidate_origin->next; + bool found_collision = false; + + /* Check if the candidate origin's destination appears again before the original order or the station does. */ + while (o != least_order->order) { + if (o->GetUnloadType() == OUFB_UNLOAD) { + found_collision = true; + break; + } + + if ((o->GetType() == OT_GOTO_STATION || + o->GetType() == OT_IMPLICIT) && + (o->GetDestination() == candidate_origin->GetDestination() || + o->GetDestination() == station)) { + found_collision = true; + break; + } + + o = (o->next == NULL) ? least_order->v->GetFirstOrder() : o->next; + } + + /* If it doesn't, then we have found the origin. */ + if (!found_collision) { + found_origin = true; + break; + } + } + + candidate_origin = (candidate_origin->next == NULL) ? least_order->v->GetFirstOrder() : candidate_origin->next; + } + + order = (candidate_origin->next == NULL) ? least_order->v->GetFirstOrder() : candidate_origin->next; + + while (order != least_order->order) { + if (order->GetType() == OT_GOTO_STATION && + (order->GetLoadType() != OLFB_NO_LOAD || + _settings_client.gui.departure_show_all_stops)) { + *(d->calling_at.Append(1)) = CallAt((StationID)order->GetDestination()); + } + + order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + } + + d->terminus = CallAt((StationID)candidate_origin->GetDestination()); + + if (found_origin) { + bool duplicate = false; + + if (_settings_client.gui.departure_merge_identical) { + for (uint i = 0; i < result->Length(); ++i) { + if (*d == **(result->Get(i))) { + duplicate = true; + break; + } + } + } + + if (!duplicate) { + *(result->Append(1)) = d; + } + } + } + + /* Save on pointer dereferences in the coming loop. */ + order = least_order->order; + + /* Now we find the next suitable order for being a departure for this vehicle. */ + /* We do this in a similar way to finding the first suitable order for the vehicle. */ + + /* Go to the next order so we don't add the current order again. */ + order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + least_order->expected_date += order->travel_time + order->wait_time; + + /* Go through the order list to find the next candidate departure. */ + /* We only need to consider each order at most once. */ + bool found_next_order = false; + for (int i = least_order->v->GetNumOrders(); i > 0; --i) { + /* If the order is a conditional branch, handle it. */ + if (order->IsType(OT_CONDITIONAL)) { + switch(_settings_client.gui.departure_conditionals) { + case 0: { + /* Give up */ + break; + } + case 1: { + /* Take the branch */ + order = least_order->v->GetOrder(order->GetConditionSkipToOrder()); + if (order == NULL) { + break; + } + + least_order->expected_date += order->wait_time; + + continue; + } + case 2: { + /* Do not take the branch */ + order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + least_order->expected_date += order->travel_time + order->wait_time; + continue; + } + } + } + + /* Skip it if it's an automatic order. */ + if (order->IsType(OT_IMPLICIT)) { + order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + continue; + } + + /* If an order doesn't have a travel time set, then stop. */ + if (order->travel_time == 0) { + break; + } + + /* If the departure is scheduled to be too late, then stop. */ + if (least_order->expected_date - least_order->lateness > max_date) { + break; + } + + /* If the order loads from this station (or unloads if we're computing arrivals) and has a wait time set, then it is suitable for being a departure. */ + if ((type == D_DEPARTURE && IsDeparture(order, station)) || + (type == D_DEPARTURE && show_vehicles_via && IsVia(order, station)) || + (type == D_ARRIVAL && IsArrival(order, station))) { + least_order->order = order; + found_next_order = true; + break; + } + + order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + least_order->expected_date += order->travel_time + order->wait_time; + } + + /* If we didn't find a suitable order for being a departure, then we can ignore this vehicle from now on. */ + if (!found_next_order) { + /* Make sure we don't try to get departures out of this order. */ + /* This is cheaper than deleting it from next_orders. */ + /* If we ever get to a state where _date * DAY_TICKS is close to INT_MAX, then we'll have other problems anyway as departures' scheduled dates will wrap around. */ + least_order->expected_date = INT64_MAX; + } + + /* The vehicle can't possibly have arrived at its next candidate departure yet. */ + if (least_order->status == D_ARRIVED) { + least_order->status = D_TRAVELLING; + } + + /* Find the new least order. */ + for (uint i = 0; i < next_orders.Length(); ++i) { + OrderDate *od = *(next_orders.Get(i)); + + DateTicks lod = least_order->expected_date - least_order->lateness; + DateTicks odd = od->expected_date - od->lateness; + + if (type == D_ARRIVAL) { + lod -= least_order->order->wait_time; + odd -= od->order->wait_time; + } + + if (lod > odd && od->expected_date - od->lateness < max_date) { + least_order = od; + } + } + } + + /* Done. Phew! */ + return result; +} diff --git a/src/departures_func.h b/src/departures_func.h new file mode 100644 index 000000000..a5fb42bb9 --- /dev/null +++ b/src/departures_func.h @@ -0,0 +1,21 @@ +/* $Id: departures_func.h $ */ + +/* + * 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 departures_func.h Functions related to departures. */ + +#ifndef DEPARTURES_FUNC_H +#define DEPARTURES_FUNC_H + +#include "station_base.h" +#include "core/smallvec_type.hpp" +#include "departures_type.h" + +DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[4], DepartureType type = D_DEPARTURE, bool show_vehicles_via = false); + +#endif /* DEPARTURES_FUNC_H */ diff --git a/src/departures_gui.cpp b/src/departures_gui.cpp new file mode 100644 index 000000000..6ccc29f89 --- /dev/null +++ b/src/departures_gui.cpp @@ -0,0 +1,856 @@ +/* $Id$ */ + +/* + * 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 departures_gui.cpp Scheduled departures from a station. */ + +#include "stdafx.h" +#include "debug.h" +#include "gui.h" +#include "textbuf_gui.h" +#include "strings_func.h" +#include "window_func.h" +#include "vehicle_func.h" +#include "string_func.h" +#include "window_gui.h" +#include "timetable.h" +#include "vehiclelist.h" +#include "company_base.h" +#include "date_func.h" +#include "departures_gui.h" +#include "station_base.h" +#include "vehicle_gui_base.h" +#include "vehicle_base.h" +#include "vehicle_gui.h" +#include "order_base.h" +#include "settings_type.h" +#include "core/smallvec_type.hpp" +#include "date_type.h" +#include "company_type.h" +#include "departures_func.h" +#include "cargotype.h" + +#include "table/sprites.h" +#include "table/strings.h" + +static const NWidgetPart _nested_departures_list[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_GREY), + NWidget(WWT_CAPTION, COLOUR_GREY, WID_DB_CAPTION), SetDataTip(STR_DEPARTURES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_SHADEBOX, COLOUR_GREY), + NWidget(WWT_STICKYBOX, COLOUR_GREY), + EndContainer(), + + NWidget(NWID_HORIZONTAL), + NWidget(WWT_MATRIX, COLOUR_GREY, WID_DB_LIST), SetMinimalSize(0, 0), SetFill(1, 0), SetResize(1, 1), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_DB_SCROLLBAR), + EndContainer(), + + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(0, 12), SetResize(1, 0), SetFill(1, 1), EndContainer(), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_ARRS), SetMinimalSize(6, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_ARRIVALS, STR_DEPARTURES_ARRIVALS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_DEPS), SetMinimalSize(6, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_DEPARTURES, STR_DEPARTURES_DEPARTURES_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_VIA), SetMinimalSize(11, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_VIA_BUTTON, STR_DEPARTURES_VIA_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_TRAINS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_TRAIN, STR_STATION_VIEW_SCHEDULED_TRAINS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_ROADVEHS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_LORRY, STR_STATION_VIEW_SCHEDULED_ROAD_VEHICLES_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_SHIPS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_SHIP, STR_STATION_VIEW_SCHEDULED_SHIPS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_PLANES), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_PLANE, STR_STATION_VIEW_SCHEDULED_AIRCRAFT_TOOLTIP), + NWidget(WWT_RESIZEBOX, COLOUR_GREY), + EndContainer(), +}; + +static WindowDesc _departures_desc( + WDP_AUTO, 260, 246, + WC_DEPARTURES_BOARD, WC_NONE, + 0, + _nested_departures_list, lengthof(_nested_departures_list) +); + +static uint cached_date_width = 0; ///< The cached maximum width required to display a date. +static uint cached_status_width = 0; ///< The cached maximum width required to show the status field. +static uint cached_date_arrow_width = 0; ///< The cached width of the red/green arrows that may be displayed alongside times. +static bool cached_date_display_method; ///< Whether the above cached values refers to original (d,m,y) dates or the 24h clock. +static bool cached_arr_dep_display_method; ///< Whether to show departures and arrivals on a single line. + +template<bool Twaypoint = false> +struct DeparturesWindow : public Window { +protected: + StationID station; ///< The station whose departures we're showing. + DepartureList *departures; ///< The current list of departures from this station. + DepartureList *arrivals; ///< The current list of arrivals from this station. + uint entry_height; ///< The height of an entry in the departures list. + uint tick_count; ///< The number of ticks that have elapsed since the window was created. Used for scrolling text. + int calc_tick_countdown; ///< The number of ticks to wait until recomputing the departure list. Signed in case it goes below zero. + bool show_types[4]; ///< The vehicle types to show in the departure list. + bool departure_types[3]; ///< The types of departure to show in the departure list. + uint min_width; ///< The minimum width of this window. + Scrollbar *vscroll; + + virtual uint GetMinWidth() const; + static void RecomputeDateWidth(); + virtual void DrawDeparturesListItems(const Rect &r) const; + void DeleteDeparturesList(DepartureList* list); +public: + + DeparturesWindow(const WindowDesc *desc, WindowNumber window_number) : Window(), + station(window_number), + departures(new DepartureList()), + arrivals(new DepartureList()), + entry_height(1 + FONT_HEIGHT_NORMAL + 1 + (_settings_client.gui.departure_larger_font ? FONT_HEIGHT_NORMAL : FONT_HEIGHT_SMALL) + 1 + 1), + tick_count(0), + calc_tick_countdown(0), + min_width(400) + { + this->CreateNestedTree(desc); + this->vscroll = this->GetScrollbar(WID_DB_SCROLLBAR); + this->FinishInitNested(desc, window_number); + + /* By default, only show departures. */ + departure_types[0] = true; + departure_types[1] = false; + departure_types[2] = false; + this->LowerWidget(WID_DB_SHOW_DEPS); + this->RaiseWidget(WID_DB_SHOW_ARRS); + this->RaiseWidget(WID_DB_SHOW_VIA); + + for (uint i = 0; i < 4; ++i) { + show_types[i] = true; + this->LowerWidget(WID_DB_SHOW_TRAINS + i); + } + + if (Twaypoint) { + this->GetWidget<NWidgetCore>(WID_DB_CAPTION)->SetDataTip(STR_DEPARTURES_CAPTION_WAYPOINT, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS); + + for (uint i = 0; i < 4; ++i) { + this->DisableWidget(WID_DB_SHOW_TRAINS + i); + } + + this->DisableWidget(WID_DB_SHOW_ARRS); + this->DisableWidget(WID_DB_SHOW_DEPS); + this->DisableWidget(WID_DB_SHOW_VIA); + + departure_types[2] = true; + + this->LowerWidget(WID_DB_SHOW_VIA); + } + } + + virtual ~DeparturesWindow() + { + this->DeleteDeparturesList(departures); + this->DeleteDeparturesList(this->arrivals); + } + + virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) + { + switch (widget) { + case WID_DB_LIST: + resize->height = DeparturesWindow::entry_height; + size->height = 2 * resize->height; + break; + } + } + + virtual void SetStringParameters(int widget) const + { + if (widget == WID_DB_CAPTION) { + const Station *st = Station::Get(this->station); + SetDParam(0, st->index); + } + } + + virtual void OnClick(Point pt, int widget, int click_count) + { + switch (widget) { + case WID_DB_SHOW_TRAINS: // Show trains to this station + case WID_DB_SHOW_ROADVEHS: // Show road vehicles to this station + case WID_DB_SHOW_SHIPS: // Show ships to this station + case WID_DB_SHOW_PLANES: // Show aircraft to this station + this->show_types[widget - WID_DB_SHOW_TRAINS] = !this->show_types[widget - WID_DB_SHOW_TRAINS]; + if (this->show_types[widget - WID_DB_SHOW_TRAINS]) { + this->LowerWidget(widget); + } else { + this->RaiseWidget(widget); + } + /* We need to recompute the departures list. */ + this->calc_tick_countdown = 0; + /* We need to redraw the button that was pressed. */ + this->SetWidgetDirty(widget); + break; + + case WID_DB_SHOW_DEPS: + case WID_DB_SHOW_ARRS: + if (_settings_client.gui.departure_show_both) break; + /* FALL THROUGH */ + + case WID_DB_SHOW_VIA: + + this->departure_types[widget - WID_DB_SHOW_DEPS] = !this->departure_types[widget - WID_DB_SHOW_DEPS]; + if (this->departure_types[widget - WID_DB_SHOW_DEPS]) { + this->LowerWidget(widget); + } else { + this->RaiseWidget(widget); + } + + if (!this->departure_types[0]) { + this->RaiseWidget(WID_DB_SHOW_VIA); + this->DisableWidget(WID_DB_SHOW_VIA); + } else { + this->EnableWidget(WID_DB_SHOW_VIA); + + if (this->departure_types[2]) { + this->LowerWidget(WID_DB_SHOW_VIA); + } + } + /* We need to recompute the departures list. */ + this->calc_tick_countdown = 0; + /* We need to redraw the button that was pressed. */ + this->SetWidgetDirty(widget); + break; + + case WID_DB_LIST: // Matrix to show departures + /* We need to find the departure corresponding to where the user clicked. */ + uint32 id_v = (pt.y - this->GetWidget<NWidgetBase>(WID_DB_LIST)->pos_y) / this->entry_height; + + if (id_v >= this->vscroll->GetCapacity()) return; // click out of bounds + + id_v += this->vscroll->GetPosition(); + + if (id_v >= (this->departures->Length() + this->arrivals->Length())) return; // click out of list bound + + uint departure = 0; + uint arrival = 0; + + /* Draw each departure. */ + for (uint i = 0; i <= id_v; ++i) { + const Departure *d; + + if (arrival == this->arrivals->Length()) { + d = (*(this->departures))[departure++]; + } else if (departure == this->departures->Length()) { + d = (*(this->arrivals))[arrival++]; + } else { + d = (*(this->departures))[departure]; + const Departure *a = (*(this->arrivals))[arrival]; + + if (a->scheduled_date < d->scheduled_date) { + d = a; + arrival++; + } else { + departure++; + } + } + + if (i == id_v) { + ShowVehicleViewWindow(d->vehicle); + break; + } + } + + break; + } + } + + virtual void OnTick() + { + if (_pause_mode == PM_UNPAUSED) { + this->tick_count += 1; + this->calc_tick_countdown -= 1; + } + + /* Recompute the minimum date display width if the cached one is no longer valid. */ + if (cached_date_width == 0 || + _settings_client.gui.time_in_minutes != cached_date_display_method || + _settings_client.gui.departure_show_both != cached_arr_dep_display_method) { + this->RecomputeDateWidth(); + } + + /* We need to redraw the scrolling text in its new position. */ + this->SetWidgetDirty(WID_DB_LIST); + + /* Recompute the list of departures if we're due to. */ + if (this->calc_tick_countdown <= 0) { + this->calc_tick_countdown = _settings_client.gui.departure_calc_frequency; + this->DeleteDeparturesList(this->departures); + this->DeleteDeparturesList(this->arrivals); + this->departures = (this->departure_types[0] ? MakeDepartureList(this->station, this->show_types, D_DEPARTURE, Twaypoint || this->departure_types[2]) : new DepartureList()); + this->arrivals = (this->departure_types[1] && !_settings_client.gui.departure_show_both ? MakeDepartureList(this->station, this->show_types, D_ARRIVAL ) : new DepartureList()); + this->SetWidgetDirty(WID_DB_LIST); + } + + uint new_width = this->GetMinWidth(); + + if (new_width != this->min_width) { + NWidgetCore *n = this->GetWidget<NWidgetCore>(WID_DB_LIST); + n->SetMinimalSize(new_width, 0); + this->ReInit(); + this->min_width = new_width; + } + + uint new_height = 1 + FONT_HEIGHT_NORMAL + 1 + (_settings_client.gui.departure_larger_font ? FONT_HEIGHT_NORMAL : FONT_HEIGHT_SMALL) + 1 + 1; + + if (new_height != this->entry_height) { + this->entry_height = new_height; + this->SetWidgetDirty(WID_DB_LIST); + this->ReInit(); + } + } + + virtual void OnPaint() + { + if (Twaypoint || _settings_client.gui.departure_show_both) { + this->DisableWidget(WID_DB_SHOW_ARRS); + this->DisableWidget(WID_DB_SHOW_DEPS); + } else { + this->EnableWidget(WID_DB_SHOW_ARRS); + this->EnableWidget(WID_DB_SHOW_DEPS); + } + + this->vscroll->SetCount(min(_settings_client.gui.max_departures, this->departures->Length() + this->arrivals->Length())); + this->DrawWidgets(); + } + + virtual void DrawWidget(const Rect &r, int widget) const + { + switch (widget) { + case WID_DB_LIST: + this->DrawDeparturesListItems(r); + break; + } + } + + virtual void OnResize() + { + this->vscroll->SetCapacityFromWidget(this, WID_DB_LIST); + this->GetWidget<NWidgetCore>(WID_DB_LIST)->widget_data = (this->vscroll->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START); + } +}; + +/** + * Shows a window of scheduled departures for a station. + * @param station the station to show a departures window for + */ +void ShowStationDepartures(StationID station) +{ + AllocateWindowDescFront<DeparturesWindow<> >(&_departures_desc, station); +} + +/** + * Shows a window of scheduled departures for a station. + * @param station the station to show a departures window for + */ +void ShowWaypointDepartures(StationID waypoint) +{ + AllocateWindowDescFront<DeparturesWindow<true> >(&_departures_desc, waypoint); +} + +template<bool Twaypoint> +void DeparturesWindow<Twaypoint>::RecomputeDateWidth() +{ + cached_date_width = 0; + cached_status_width = 0; + cached_date_display_method = _settings_client.gui.time_in_minutes; + cached_arr_dep_display_method = _settings_client.gui.departure_show_both; + + cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_ON_TIME)).width, cached_status_width); + cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_DELAYED)).width, cached_status_width); + cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_CANCELLED)).width, cached_status_width); + + uint interval = cached_date_display_method ? _settings_client.gui.ticks_per_minute : DAY_TICKS; + uint count = cached_date_display_method ? 24*60 : 365; + + for (uint i = 0; i < count; ++i) { + SetDParam(0, INT_MAX - (i*interval)); + SetDParam(1, INT_MAX - (i*interval)); + cached_date_width = max(GetStringBoundingBox(cached_arr_dep_display_method ? STR_DEPARTURES_TIME_BOTH : STR_DEPARTURES_TIME_DEP).width, cached_date_width); + cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_EXPECTED)).width, cached_status_width); + } + + SetDParam(0, 0); + cached_date_arrow_width = GetStringBoundingBox(STR_DEPARTURES_TIME_DEP).width - GetStringBoundingBox(STR_DEPARTURES_TIME).width; + + if (!_settings_client.gui.departure_show_both) { + cached_date_width -= cached_date_arrow_width; + } +} + +template<bool Twaypoint> +uint DeparturesWindow<Twaypoint>::GetMinWidth() const +{ + uint result = 0; + + /* Time */ + result = cached_date_width; + + /* Vehicle type icon */ + result += _settings_client.gui.departure_show_vehicle_type ? (GetStringBoundingBox(STR_DEPARTURES_TYPE_PLANE)).width : 0; + + /* Status */ + result += cached_status_width; + + /* Find the maximum company name width. */ + int toc_width = 0; + + /* Find the maximum company name width. */ + int group_width = 0; + + /* Find the maximum vehicle name width. */ + int veh_width = 0; + + if (_settings_client.gui.departure_show_vehicle || _settings_client.gui.departure_show_company || _settings_client.gui.departure_show_group) { + for (uint i = 0; i < 4; ++i) { + VehicleList vehicles; + + /* MAX_COMPANIES is probably the wrong thing to put here, but it works. GenerateVehicleSortList doesn't check the company when the type of list is VL_STATION_LIST (r20801). */ + if (!GenerateVehicleSortList(&vehicles, VehicleListIdentifier(VL_STATION_LIST, (VehicleType)(VEH_TRAIN + i), MAX_COMPANIES, station).Pack())) { + /* Something went wrong: panic! */ + continue; + } + + for (const Vehicle **v = vehicles.Begin(); v != vehicles.End(); v++) { + SetDParam(0, (uint64)((*v)->index)); + int width = (GetStringBoundingBox(STR_DEPARTURES_VEH)).width; + if (_settings_client.gui.departure_show_vehicle && width > veh_width) veh_width = width; + + if ((*v)->group_id != INVALID_GROUP && (*v)->group_id != DEFAULT_GROUP) { + SetDParam(0, (uint64)((*v)->group_id)); + width = (GetStringBoundingBox(STR_DEPARTURES_GROUP)).width; + if (_settings_client.gui.departure_show_group && width > group_width) group_width = width; + } + + SetDParam(0, (uint64)((*v)->owner)); + width = (GetStringBoundingBox(STR_DEPARTURES_TOC)).width; + if (_settings_client.gui.departure_show_company && width > toc_width) toc_width = width; + } + } + } + + result += toc_width + veh_width + group_width; + + return result + 140; +} + +/** + * Deletes this window's departure list. + */ +template<bool Twaypoint> +void DeparturesWindow<Twaypoint>::DeleteDeparturesList(DepartureList *list) +{ + /* SmallVector uses free rather than delete on its contents (which doesn't invoke the destructor), so we need to delete each departure manually. */ + for (uint i = 0; i < list->Length(); ++i) { + Departure **d = list->Get(i); + delete *d; + /* Make sure a double free doesn't happen. */ + *d = NULL; + } + list->Reset(); + delete list; + list = NULL; +} + +/** + * Draws a list of departures. + */ +template<bool Twaypoint> +void DeparturesWindow<Twaypoint>::DrawDeparturesListItems(const Rect &r) const +{ + int left = r.left + WD_MATRIX_LEFT; + int right = r.right - WD_MATRIX_RIGHT; + + bool rtl = _current_text_dir == TD_RTL; + bool ltr = !rtl; + + int text_offset = WD_FRAMERECT_RIGHT; + int text_left = left + (rtl ? 0 : text_offset); + int text_right = right - (rtl ? text_offset : 0); + + int y = r.top + 1; + uint max_departures = min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->departures->Length() + this->arrivals->Length()); + + if (max_departures > _settings_client.gui.max_departures) { + max_departures = _settings_client.gui.max_departures; + } + + byte small_font_size = _settings_client.gui.departure_larger_font ? FONT_HEIGHT_NORMAL : FONT_HEIGHT_SMALL; + + /* Draw the black background. */ + GfxFillRect(r.left + 1, r.top, r.right - 1, r.bottom, PC_BLACK); + + /* Nothing selected? Then display the information text. */ + bool none_selected[2] = {true, true}; + for (uint i = 0; i < 4; ++i) + { + if (this->show_types[i]) { + none_selected[0] = false; + break; + } + } + + for (uint i = 0; i < 2; ++i) + { + if (this->departure_types[i]) { + none_selected[1] = false; + break; + } + } + + if (none_selected[0] || none_selected[1]) { + DrawString(text_left, text_right, y + 1, STR_DEPARTURES_NONE_SELECTED); + return; + } + + /* No scheduled departures? Then display the information text. */ + if (max_departures == 0) { + DrawString(text_left, text_right, y + 1, STR_DEPARTURES_EMPTY); + return; + } + + /* Find the maximum possible width of the departure time and "Expt <time>" fields. */ + int time_width = cached_date_width; + + if (!_settings_client.gui.departure_show_both) { + time_width += (departure_types[0] && departure_types[1] ? cached_date_arrow_width : 0); + } + + /* Vehicle type icon */ + int type_width = _settings_client.gui.departure_show_vehicle_type ? (GetStringBoundingBox(STR_DEPARTURES_TYPE_PLANE)).width : 0; + + /* Find the maximum width of the status field */ + int status_width = cached_status_width; + + /* Find the width of the "Calling at:" field. */ + int calling_at_width = (GetStringBoundingBox(_settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LARGE : STR_DEPARTURES_CALLING_AT)).width; + + /* Find the maximum company name width. */ + int toc_width = 0; + + /* Find the maximum group name width. */ + int group_width = 0; + + /* Find the maximum vehicle name width. */ + int veh_width = 0; + + if (_settings_client.gui.departure_show_vehicle || _settings_client.gui.departure_show_company || _settings_client.gui.departure_show_group) { + for (uint i = 0; i < 4; ++i) { + VehicleList vehicles; + + /* MAX_COMPANIES is probably the wrong thing to put here, but it works. GenerateVehicleSortList doesn't check the company when the type of list is VL_STATION_LIST (r20801). */ + if (!GenerateVehicleSortList(&vehicles, VehicleListIdentifier(VL_STATION_LIST, (VehicleType)(VEH_TRAIN + i), MAX_COMPANIES, station).Pack())) { + /* Something went wrong: panic! */ + continue; + } + + for (const Vehicle **v = vehicles.Begin(); v != vehicles.End(); v++) { + SetDParam(0, (uint64)((*v)->index)); + int width = (GetStringBoundingBox(STR_DEPARTURES_VEH)).width; + if (_settings_client.gui.departure_show_vehicle && width > veh_width) veh_width = width; + + if ((*v)->group_id != INVALID_GROUP && (*v)->group_id != DEFAULT_GROUP) { + SetDParam(0, (uint64)((*v)->group_id)); + width = (GetStringBoundingBox(STR_DEPARTURES_GROUP)).width; + if (_settings_client.gui.departure_show_group && width > group_width) group_width = width; + } + + SetDParam(0, (uint64)((*v)->owner)); + width = (GetStringBoundingBox(STR_DEPARTURES_TOC)).width; + if (_settings_client.gui.departure_show_company && width > toc_width) toc_width = width; + } + } + } + + uint departure = 0; + uint arrival = 0; + + /* Draw each departure. */ + for (uint i = 0; i < max_departures; ++i) { + const Departure *d; + + if (arrival == this->arrivals->Length()) { + d = (*(this->departures))[departure++]; + } else if (departure == this->departures->Length()) { + d = (*(this->arrivals))[arrival++]; + } else { + d = (*(this->departures))[departure]; + const Departure *a = (*(this->arrivals))[arrival]; + + if (a->scheduled_date < d->scheduled_date) { + d = a; + arrival++; + } else { + departure++; + } + } + + if (i < this->vscroll->GetPosition()) { + continue; + } + + /* If for some reason the departure is too far in the future or is at a negative time, skip it. */ + if ((d->scheduled_date / DAY_TICKS) > (_date + _settings_client.gui.max_departure_time) || + d->scheduled_date < 0) { + continue; + } + + if (d->terminus == INVALID_STATION) continue; + + StringID time_str = (departure_types[0] && departure_types[1]) ? (d->type == D_DEPARTURE ? STR_DEPARTURES_TIME_DEP : STR_DEPARTURES_TIME_ARR) : STR_DEPARTURES_TIME; + + if (_settings_client.gui.departure_show_both) time_str = STR_DEPARTURES_TIME_BOTH; + + /* Time */ + SetDParam(0, d->scheduled_date); + SetDParam(1, d->scheduled_date - d->order->wait_time); + ltr ? DrawString( text_left, text_left + time_width, y + 1, time_str) + : DrawString(text_right - time_width, text_right, y + 1, time_str); + + /* Vehicle type icon, with thanks to sph */ + if (_settings_client.gui.departure_show_vehicle_type) { + StringID type = STR_DEPARTURES_TYPE_TRAIN; + int offset = (_settings_client.gui.departure_show_vehicle_color ? 1 : 0); + + switch (d->vehicle->type) { + case VEH_TRAIN: + type = STR_DEPARTURES_TYPE_TRAIN; + break; + case VEH_ROAD: + type = IsCargoInClass(d->vehicle->cargo_type, CC_PASSENGERS) ? STR_DEPARTURES_TYPE_BUS : STR_DEPARTURES_TYPE_LORRY; + break; + case VEH_SHIP: + type = STR_DEPARTURES_TYPE_SHIP; + break; + case VEH_AIRCRAFT: + type = STR_DEPARTURES_TYPE_PLANE; + break; + default: + break; + } + + type += offset; + + DrawString(text_left + time_width + 3, text_left + time_width + type_width + 3, y, type); + } + + /* The icons to show with the destination and via stations. */ + StringID icon = STR_DEPARTURES_STATION_NONE; + StringID icon_via = STR_DEPARTURES_STATION_NONE; + + if (_settings_client.gui.departure_destination_type) { + Station *t = Station::Get(d->terminus.station); + + if (t->facilities & FACIL_DOCK && + t->facilities & FACIL_AIRPORT && + d->vehicle->type != VEH_SHIP && + d->vehicle->type != VEH_AIRCRAFT) { + icon = STR_DEPARTURES_STATION_PORTAIRPORT; + } else if (t->facilities & FACIL_DOCK && + d->vehicle->type != VEH_SHIP) { + icon = STR_DEPARTURES_STATION_PORT; + } else if (t->facilities & FACIL_AIRPORT && + d->vehicle->type != VEH_AIRCRAFT) { + icon = STR_DEPARTURES_STATION_AIRPORT; + } + } + + if (_settings_client.gui.departure_destination_type && d->via != INVALID_STATION) { + Station *t = Station::Get(d->via); + + if (t->facilities & FACIL_DOCK && + t->facilities & FACIL_AIRPORT && + d->vehicle->type != VEH_SHIP && + d->vehicle->type != VEH_AIRCRAFT) { + icon_via = STR_DEPARTURES_STATION_PORTAIRPORT; + } else if (t->facilities & FACIL_DOCK && + d->vehicle->type != VEH_SHIP) { + icon_via = STR_DEPARTURES_STATION_PORT; + } else if (t->facilities & FACIL_AIRPORT && + d->vehicle->type != VEH_AIRCRAFT) { + icon_via = STR_DEPARTURES_STATION_AIRPORT; + } + } + + /* Destination */ + if (d->via == INVALID_STATION) { + /* Only show the terminus. */ + SetDParam(0, d->terminus.station); + SetDParam(1, icon); + ltr ? DrawString( text_left + time_width + type_width + 6, text_right - status_width - (toc_width + veh_width + group_width + 2) - 2, y + 1, STR_DEPARTURES_TERMINUS) + : DrawString(text_left + status_width + (toc_width + veh_width + group_width + 2) + 2, text_right - time_width - type_width - 6, y + 1, STR_DEPARTURES_TERMINUS); + } else { + /* Show the terminus and the via station. */ + SetDParam(0, d->terminus.station); + SetDParam(1, icon); + SetDParam(2, d->via); + SetDParam(3, icon_via); + int text_width = (GetStringBoundingBox(STR_DEPARTURES_TERMINUS_VIA_STATION)).width; + + if (text_width < text_right - status_width - (toc_width + veh_width + group_width + 2) - 2 - (text_left + time_width + type_width + 6)) { + /* They will both fit, so show them both. */ + SetDParam(0, d->terminus.station); + SetDParam(1, icon); + SetDParam(2, d->via); + SetDParam(3, icon_via); + ltr ? DrawString( text_left + time_width + type_width + 6, text_right - status_width - (toc_width + veh_width + group_width + 2) - 2, y + 1, STR_DEPARTURES_TERMINUS_VIA_STATION) + : DrawString(text_left + status_width + (toc_width + veh_width + group_width + 2) + 2, text_right - time_width - type_width - 6, y + 1, STR_DEPARTURES_TERMINUS_VIA_STATION); + } else { + /* They won't both fit, so switch between showing the terminus and the via station approximately every 4 seconds. */ + if (this->tick_count & (1 << 7)) { + SetDParam(0, d->via); + SetDParam(1, icon_via); + ltr ? DrawString( text_left + time_width + type_width + 6, text_right - status_width - (toc_width + veh_width + group_width + 2) - 2, y + 1, STR_DEPARTURES_VIA) + : DrawString(text_left + status_width + (toc_width + veh_width + group_width + 2) + 2, text_right - time_width - type_width - 6, y + 1, STR_DEPARTURES_VIA); + } else { + SetDParam(0, d->terminus.station); + SetDParam(1, icon); + ltr ? DrawString( text_left + time_width + type_width + 6, text_right - status_width - (toc_width + veh_width + group_width + 2) - 2, y + 1, STR_DEPARTURES_TERMINUS_VIA) + : DrawString(text_left + status_width + (toc_width + veh_width + group_width + 2) + 2, text_right - time_width - type_width - 6, y + 1, STR_DEPARTURES_TERMINUS_VIA); + } + } + } + + /* Status */ + { + int status_left = ltr ? text_right - status_width - 2 - (toc_width + veh_width + group_width + 2) : text_left + (toc_width + veh_width + group_width + 2) + 2; + int status_right = ltr ? text_right - (toc_width + veh_width + group_width + 2) + 2 : text_left + status_width + 2 + (toc_width + veh_width + group_width + 2); + + if (d->status == D_ARRIVED) { + /* The vehicle has arrived. */ + DrawString(status_left, status_right, y + 1, STR_DEPARTURES_ARRIVED); + } else if(d->status == D_CANCELLED) { + /* The vehicle has been cancelled. */ + DrawString(status_left, status_right, y + 1, STR_DEPARTURES_CANCELLED); + } else{ + if (d->lateness <= DAY_TICKS && d->scheduled_date > ((_date * DAY_TICKS) + _date_fract)) { + /* We have no evidence that the vehicle is late, so assume it is on time. */ + DrawString(status_left, status_right, y + 1, STR_DEPARTURES_ON_TIME); + } else { + if ((d->scheduled_date + d->lateness) < ((_date * DAY_TICKS) + _date_fract)) { + /* The vehicle was expected to have arrived by now, even if we knew it was going to be late. */ + /* We assume that the train stays at least a day at a station so it won't accidentally be marked as delayed for a fraction of a day. */ + DrawString(status_left, status_right, y + 1, STR_DEPARTURES_DELAYED); + } else { + /* The vehicle is expected to be late and is not yet due to arrive. */ + SetDParam(0, d->scheduled_date + d->lateness); + DrawString(status_left, status_right, y + 1, STR_DEPARTURES_EXPECTED); + } + } + } + } + + /* Vehicle name */ + + if (_settings_client.gui.departure_show_vehicle) { + SetDParam(0, (uint64)(d->vehicle->index)); + ltr ? DrawString(text_right - (toc_width + veh_width + group_width + 2), text_right - toc_width - group_width - 2, y + 1, STR_DEPARTURES_VEH) + : DrawString( text_left + toc_width + group_width + 2, text_left + (toc_width + veh_width + group_width + 2), y + 1, STR_DEPARTURES_VEH); + } + + /* Group name */ + + if (_settings_client.gui.departure_show_group && d->vehicle->group_id != INVALID_GROUP && d->vehicle->group_id != DEFAULT_GROUP) { + SetDParam(0, (uint64)(d->vehicle->group_id)); + ltr ? DrawString(text_right - (toc_width + group_width + 2), text_right - toc_width - 2, y + 1, STR_DEPARTURES_GROUP) + : DrawString( text_left + toc_width + 2, text_left + (toc_width + group_width + 2), y + 1, STR_DEPARTURES_GROUP); + } + + /* Operating company */ + if (_settings_client.gui.departure_show_company) { + SetDParam(0, (uint64)(d->vehicle->owner)); + ltr ? DrawString(text_right - toc_width, text_right, y + 1, STR_DEPARTURES_TOC, TC_FROMSTRING, SA_RIGHT) + : DrawString( text_left, text_left + toc_width, y + 1, STR_DEPARTURES_TOC, TC_FROMSTRING, SA_LEFT); + } + + int bottom_y = y + this->entry_height - small_font_size - (_settings_client.gui.departure_larger_font ? 1 : 3); + + /* Calling at */ + ltr ? DrawString( text_left, text_left + calling_at_width, bottom_y, _settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LARGE : STR_DEPARTURES_CALLING_AT) + : DrawString(text_right - calling_at_width, text_right, bottom_y, _settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LARGE : STR_DEPARTURES_CALLING_AT); + + /* List of stations */ + /* RTL languages can be handled in the language file, e.g. by having the following: */ + /* STR_DEPARTURES_CALLING_AT_STATION :{STATION}, {RAW_STRING} */ + /* STR_DEPARTURES_CALLING_AT_LAST_STATION :{STATION} & {RAW_STRING}*/ + char buffer[512], scratch[512]; + + if (d->calling_at.Length() != 0) { + SetDParam(0, (uint64)(*d->calling_at.Get(0)).station); + GetString(scratch, STR_DEPARTURES_CALLING_AT_FIRST_STATION, lastof(scratch)); + + StationID continuesTo = INVALID_STATION; + + if (d->calling_at.Get(0)->station == d->terminus.station && d->calling_at.Length() > 1) { + continuesTo = d->calling_at.Get(d->calling_at.Length() - 1)->station; + } else if (d->calling_at.Length() > 1) { + /* There's more than one stop. */ + + uint i; + /* For all but the last station, write out ", <station>". */ + for (i = 1; i < d->calling_at.Length() - 1; ++i) { + StationID s = d->calling_at.Get(i)->station; + if (s == d->terminus.station) { + continuesTo = d->calling_at.Get(d->calling_at.Length() - 1)->station; + break; + } + SetDParam(0, (uint64)scratch); + SetDParam(1, (uint64)s); + GetString(buffer, STR_DEPARTURES_CALLING_AT_STATION, lastof(buffer)); + strncpy(scratch, buffer, sizeof(scratch)); + } + + /* Finally, finish off with " and <station>". */ + SetDParam(0, (uint64)scratch); + SetDParam(1, (uint64)d->calling_at.Get(i)->station); + GetString(buffer, STR_DEPARTURES_CALLING_AT_LAST_STATION, lastof(buffer)); + strncpy(scratch, buffer, sizeof(scratch)); + } + + SetDParam(0, (uint64)scratch); + StringID string; + if (continuesTo == INVALID_STATION) { + string = _settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LIST_LARGE : STR_DEPARTURES_CALLING_AT_LIST; + } else { + SetDParam(1, continuesTo); + string = _settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LIST_SMART_TERMINUS_LARGE : STR_DEPARTURES_CALLING_AT_LIST_SMART_TERMINUS; + } + GetString(buffer, string, lastof(buffer)); + } else { + buffer[0] = 0; + //SetDParam(0, d->terminus); + //GetString(scratch, STR_DEPARTURES_CALLING_AT_FIRST_STATION, lastof(scratch)); + } + + int list_width = (GetStringBoundingBox(buffer, _settings_client.gui.departure_larger_font ? FS_NORMAL : FS_SMALL)).width; + + /* Draw the whole list if it will fit. Otherwise scroll it. */ + if (list_width < text_right - (text_left + calling_at_width + 2)) { + ltr ? DrawString(text_left + calling_at_width + 2, text_right, bottom_y, buffer) + : DrawString( text_left, text_right - calling_at_width - 2, bottom_y, buffer); + } else { + DrawPixelInfo tmp_dpi; + if (ltr + ? !FillDrawPixelInfo(&tmp_dpi, text_left + calling_at_width + 2, bottom_y, text_right - (text_left + calling_at_width + 2), small_font_size + 3) + : !FillDrawPixelInfo(&tmp_dpi, text_left , bottom_y, text_right - (text_left + calling_at_width + 2), small_font_size + 3)) { + y += this->entry_height; + continue; + } + DrawPixelInfo *old_dpi = _cur_dpi; + _cur_dpi = &tmp_dpi; + + /* The scrolling text starts out of view at the right of the screen and finishes when it is out of view at the left of the screen. */ + int pos = ltr + ? text_right - (this->tick_count % (list_width + text_right - text_left)) + : text_left + (this->tick_count % (list_width + text_right - text_left)); + + ltr ? DrawString( pos, INT16_MAX, 0, buffer, TC_FROMSTRING, SA_LEFT | SA_FORCE) + : DrawString(-INT16_MAX, pos, 0, buffer, TC_FROMSTRING, SA_RIGHT | SA_FORCE); + + _cur_dpi = old_dpi; + } + + y += this->entry_height; + } +} diff --git a/src/departures_gui.h b/src/departures_gui.h new file mode 100644 index 000000000..aedbf2986 --- /dev/null +++ b/src/departures_gui.h @@ -0,0 +1,22 @@ +/* $Id: departures_gui.h $ */ + +/* + * 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 departures_gui.h */ + +#ifndef DEPARTURES_GUI_H +#define DEPARTURES_GUI_H + +#include "departures_type.h" +#include "station_base.h" +#include "widgets/departures_widget.h" + +void ShowStationDepartures(StationID station); +void ShowWaypointDepartures(StationID waypoint); + +#endif /* DEPARTURES_GUI_H */ diff --git a/src/departures_type.h b/src/departures_type.h new file mode 100644 index 000000000..cb06b144f --- /dev/null +++ b/src/departures_type.h @@ -0,0 +1,101 @@ +/* $Id: departures_type.h $ */ + +/* + * 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 departures_type.h Types related to departures. */ + +#ifndef DEPARTURES_TYPE_H +#define DEPARTURES_TYPE_H + +#include "station_base.h" +#include "order_base.h" +#include "vehicle_base.h" + +/** Whether or not a vehicle has arrived for a departure. */ +typedef enum { + D_TRAVELLING = 0, + D_ARRIVED = 1, + D_CANCELLED = 2, +} DepartureStatus; + +/** The type of departures. */ +typedef enum { + D_DEPARTURE = 0, + D_ARRIVAL = 1, +} DepartureType; + +typedef struct CallAt { + StationID station; + DateTicks scheduled_date; + + CallAt(const StationID& s) : station(s), scheduled_date(0) { } + CallAt(const StationID& s, const DateTicks& t) : station(s), scheduled_date(t) { } + CallAt(const CallAt& c) : station(c.station), scheduled_date(c.scheduled_date) { } + + inline bool operator==(const CallAt& c) const { + return this->station == c.station; + } + + inline bool operator!=(const CallAt& c) const { + return this->station != c.station; + } + + inline bool operator>=(const CallAt& c) const { + return this->station == c.station && + this->scheduled_date != 0 && + c.scheduled_date != 0 && + this->scheduled_date >= c.scheduled_date; + } + + CallAt& operator=(const CallAt& c) { + this->station = c.station; + this->scheduled_date = c.scheduled_date; + return *this; + } + + inline bool operator==(StationID s) const { + return this->station == s; + } +} CallAt; + +/** A scheduled departure. */ +typedef struct Departure { + DateTicks scheduled_date; ///< The date this departure is scheduled to finish on (i.e. when the vehicle leaves the station) + DateTicks lateness; ///< How delayed the departure is expected to be + CallAt terminus; ///< The station at which the vehicle will terminate following this departure + StationID via; ///< The station the departure should list as going via + SmallVector<CallAt, 32> calling_at; ///< The stations both called at and unloaded at by the vehicle after this departure before it terminates + DepartureStatus status; ///< Whether the vehicle has arrived yet for this departure + DepartureType type; ///< The type of the departure (departure or arrival) + const Vehicle *vehicle; ///< The vehicle performing this departure + const Order *order; ///< The order corresponding to this departure + Departure() : terminus(INVALID_STATION), via(INVALID_STATION), calling_at(), vehicle(NULL) { } + ~Departure() + { + calling_at.Reset(); + } + + inline bool operator==(const Departure& d) const { + if (this->calling_at.Length() != d.calling_at.Length()) return false; + + for (uint i = 0; i < this->calling_at.Length(); ++i) { + if (*(this->calling_at.Get(i)) != *(d.calling_at.Get(i))) return false; + } + + return + (this->scheduled_date / DATE_UNIT_SIZE) == (d.scheduled_date / DATE_UNIT_SIZE) && + this->vehicle->type == d.vehicle->type && + this->via == d.via && + this->type == d.type + ; + } +} Departure; + +typedef SmallVector<Departure*, 32> DepartureList; + +#endif /* DEPARTURES_TYPE_H */ diff --git a/src/economy.cpp b/src/economy.cpp index 39cb8ddaa..424f3c29f 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -46,6 +46,7 @@ #include "game/game.hpp" #include "cargomonitor.h" #include "goal_base.h" +#include "cargodest_func.h" #include "table/strings.h" #include "table/pricebase.h" @@ -493,6 +494,13 @@ void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner) /* if a company goes bankrupt, set owner to OWNER_NONE so the sign doesn't disappear immediately * also, drawing station window would cause reading invalid company's colour */ st->owner = new_owner == INVALID_OWNER ? OWNER_NONE : new_owner; + + /* Move route links to the new company. */ + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + for (RouteLinkList::iterator itr = st->goods[cid].routes.begin(); itr != st->goods[cid].routes.end(); ++itr) { + if ((*itr)->owner == old_owner) (*itr)->owner = new_owner; + } + } } } @@ -985,6 +993,33 @@ Money GetTransportedGoodsIncome(uint num_pieces, uint dist, byte transit_days, C /** The industries we've currently brought cargo to. */ static SmallIndustryList _cargo_delivery_destinations; + /** + * Deliver goods to an industry. Cargo acceptance by the industry is checked. + * @param ind The industry to deliver to. + * @param cargo_type Type of cargo delivered. + * @param num_pieces Amount of cargo delivered. + * @return Accepted pieces of cargo. + */ +static uint DeliverGoodsToIndustry(Industry *ind, CargoID cargo_type, uint num_pieces) +{ + uint cargo_index; + for (cargo_index = 0; cargo_index < lengthof(ind->accepts_cargo); cargo_index++) { + if (cargo_type == ind->accepts_cargo[cargo_index]) break; + } + /* Check if matching cargo has been found */ + if (cargo_index >= lengthof(ind->accepts_cargo)) return 0; + + /* Check if industry temporarily refuses acceptance */ + if (IndustryTemporarilyRefusesCargo(ind, cargo_type)) return 0; + + /* Insert the industry into _cargo_delivery_destinations, if not yet contained */ + _cargo_delivery_destinations.Include(ind); + + uint amount = min(num_pieces, 0xFFFFU - ind->incoming_cargo_waiting[cargo_index]); + ind->incoming_cargo_waiting[cargo_index] += amount; + return amount; +} + /** * Transfer goods from station to industry. * All cargo is delivered to the nearest (Manhattan) industry to the station sign, which is inside the acceptance rectangle and actually accepts the cargo. @@ -1009,21 +1044,7 @@ static uint DeliverGoodsToIndustry(const Station *st, CargoID cargo_type, uint n Industry *ind = st->industries_near[i]; if (ind->index == source) continue; - uint cargo_index; - for (cargo_index = 0; cargo_index < lengthof(ind->accepts_cargo); cargo_index++) { - if (cargo_type == ind->accepts_cargo[cargo_index]) break; - } - /* Check if matching cargo has been found */ - if (cargo_index >= lengthof(ind->accepts_cargo)) continue; - - /* Check if industry temporarily refuses acceptance */ - if (IndustryTemporarilyRefusesCargo(ind, cargo_type)) continue; - - /* Insert the industry into _cargo_delivery_destinations, if not yet contained */ - _cargo_delivery_destinations.Include(ind); - - uint amount = min(num_pieces, 0xFFFFU - ind->incoming_cargo_waiting[cargo_index]); - ind->incoming_cargo_waiting[cargo_index] += amount; + uint amount = DeliverGoodsToIndustry(ind, cargo_type, num_pieces); num_pieces -= amount; accepted += amount; } @@ -1041,17 +1062,25 @@ static uint DeliverGoodsToIndustry(const Station *st, CargoID cargo_type, uint n * @param company The company delivering the cargo * @param src_type Type of source of cargo (industry, town, headquarters) * @param src Index of source of cargo + * @param cp_dest_type Type of destination of cargo + * @param cp_dest Index of the destination of cargo * @return Revenue for delivering cargo * @note The cargo is just added to the stockpile of the industry. It is due to the caller to trigger the industry's production machinery */ -static Money DeliverGoods(int num_pieces, CargoID cargo_type, StationID dest, TileIndex source_tile, byte days_in_transit, Company *company, SourceType src_type, SourceID src) +static Money DeliverGoods(int num_pieces, CargoID cargo_type, StationID dest, TileIndex source_tile, byte days_in_transit, Company *company, SourceType src_type, SourceID src, SourceType cp_dest_type, SourceID cp_dest) { assert(num_pieces > 0); Station *st = Station::Get(dest); - /* Give the goods to the industry. */ - uint accepted = DeliverGoodsToIndustry(st, cargo_type, num_pieces, src_type == ST_INDUSTRY ? src : INVALID_INDUSTRY); + uint accepted = 0; + if (cp_dest != INVALID_SOURCE) { + /* If this cargo has an industry as destination, deliver the cargo to it. */ + if (cp_dest_type == ST_INDUSTRY) accepted = DeliverGoodsToIndustry(Industry::Get(cp_dest), cargo_type, num_pieces); + } else { + /* Give the goods to any accepting industry. */ + accepted = DeliverGoodsToIndustry(st, cargo_type, num_pieces, src_type == ST_INDUSTRY ? src : INVALID_INDUSTRY); + } /* If this cargo type is always accepted, accept all */ if (HasBit(st->always_accepted, cargo_type)) accepted = num_pieces; @@ -1140,21 +1169,29 @@ CargoPayment::~CargoPayment() this->front->cargo_payment = NULL; - if (this->visual_profit == 0) return; + if (this->visual_profit == 0 && this->transfer_profit == 0) return; Backup<CompanyByte> cur_company(_current_company, this->front->owner, FILE_LINE); SubtractMoneyFromCompany(CommandCost(this->front->GetExpenseType(true), -this->route_profit)); - this->front->profit_this_year += this->visual_profit << 8; + this->front->profit_this_year += (this->visual_profit + this->transfer_profit) << 8; + + int z = this->front->z_pos; if (this->route_profit != 0) { - if (IsLocalCompany() && !PlayVehicleSound(this->front, VSE_LOAD_UNLOAD)) { - SndPlayVehicleFx(SND_14_CASHTILL, this->front); - } + /* Show profit/loss from final delivery. */ + ShowCostOrIncomeAnimation(this->front->x_pos, this->front->y_pos, z, -this->visual_profit); + z += VPSM_TOP + FONT_HEIGHT_NORMAL + VPSM_BOTTOM; + } - ShowCostOrIncomeAnimation(this->front->x_pos, this->front->y_pos, this->front->z_pos, -this->visual_profit); - } else { - ShowFeederIncomeAnimation(this->front->x_pos, this->front->y_pos, this->front->z_pos, this->visual_profit); + if (this->transfer_profit != 0) { + /* Show transfer credits. */ + ShowFeederIncomeAnimation(this->front->x_pos, this->front->y_pos, z, this->transfer_profit); + } + + /* Play cash sound. */ + if (IsLocalCompany() && !PlayVehicleSound(this->front, VSE_LOAD_UNLOAD)) { + SndPlayVehicleFx(SND_14_CASHTILL, this->front); } cur_company.Restore(); @@ -1172,7 +1209,7 @@ void CargoPayment::PayFinalDelivery(const CargoPacket *cp, uint count) } /* Handle end of route payment */ - Money profit = DeliverGoods(count, this->ct, this->current_station, cp->SourceStationXY(), cp->DaysInTransit(), this->owner, cp->SourceSubsidyType(), cp->SourceSubsidyID()); + Money profit = DeliverGoods(count, this->ct, this->current_station, cp->SourceStationXY(), cp->DaysInTransit(), this->owner, cp->SourceSubsidyType(), cp->SourceSubsidyID(), cp->DestinationType(), cp->DestinationID()); this->route_profit += profit; /* The vehicle's profit is whatever route profit there is minus feeder shares. */ @@ -1196,7 +1233,7 @@ Money CargoPayment::PayTransfer(const CargoPacket *cp, uint count) profit = profit * _settings_game.economy.feeder_payment_share / 100; - this->visual_profit += profit; // accumulate transfer profits for whole vehicle + this->transfer_profit += profit; // accumulate transfer profits for whole vehicle return profit; // account for the (virtual) profit already made for the cargo packet } @@ -1252,10 +1289,12 @@ static bool IsArticulatedVehicleEmpty(Vehicle *v) * picked up by another vehicle when all * previous vehicles have loaded. */ -static void LoadUnloadVehicle(Vehicle *front, int *cargo_left) +static void LoadUnloadVehicle(Vehicle *front, StationCargoList::OrderMap (&cargo_left)[NUM_CARGO]) { assert(front->current_order.IsType(OT_LOADING)); + OrderID last_order = front->last_order_id; + /* We have not waited enough time till the next round of loading/unloading */ if (front->load_unload_ticks != 0) { if (_settings_game.order.improved_load && (front->current_order.GetLoadType() & OLFB_FULL_LOAD)) { @@ -1263,7 +1302,15 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left) for (Vehicle *v = front; v != NULL; v = v->Next()) { int cap_left = v->cargo_cap; if (!HasBit(v->vehicle_flags, VF_CARGO_UNLOADING)) cap_left -= v->cargo.Count(); - if (cap_left > 0) cargo_left[v->cargo_type] -= cap_left; + if (cap_left > 0) { + /* Try the bucket for our next destination first. */ + int loaded = min(cap_left, cargo_left[v->cargo_type][last_order]); + cargo_left[v->cargo_type][last_order] -= loaded; + + /* Reserve from the common bucket if still space left. */ + loaded = min(cap_left - loaded, cargo_left[v->cargo_type][INVALID_ORDER]); + cargo_left[v->cargo_type][INVALID_ORDER] -= loaded; + } } } return; @@ -1344,15 +1391,44 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left) uint amount_unloaded = _settings_game.order.gradual_loading ? min(cargo_count, load_amount) : cargo_count; bool remaining = false; // Are there cargo entities in this vehicle that can still be unloaded here? bool accepted = false; // Is the cargo accepted by the station? + bool did_transfer = false; payment->SetCargo(v->cargo_type); - if (HasBit(ge->acceptance_pickup, GoodsEntry::GES_ACCEPTANCE) && !(front->current_order.GetUnloadType() & OUFB_TRANSFER)) { - /* The cargo has reached its final destination, the packets may now be destroyed */ - remaining = v->cargo.MoveTo<StationCargoList>(NULL, amount_unloaded, VehicleCargoList::MTA_FINAL_DELIVERY, payment, last_visited); + if (CargoHasDestinations(v->cargo_type)) { + /* This cargo type has destinations enabled, this means explicit transfer + * orders are overridden for cargo packets with destination. + * Default action for destination-less cargo packets is final delivery when + * accepted, otherwise no action. If the current order is forced unload, + * always unload all cargo. */ + VehicleCargoList::MoveToAction mta = HasBit(ge->acceptance_pickup, GoodsEntry::GES_ACCEPTANCE) ? VehicleCargoList::MTA_FINAL_DELIVERY : VehicleCargoList::MTA_NO_ACTION; + if (front->current_order.GetUnloadType() & OUFB_UNLOAD) mta = VehicleCargoList::MTA_UNLOAD; //front was u + remaining = v->cargo.MoveTo(&ge->cargo, amount_unloaded, mta, payment, last_visited, last_order, v->cargo_type, &did_transfer); dirty_vehicle = true; accepted = true; + } else { + /* Cargo destinations are not enabled, handle transfer orders. */ + if (HasBit(ge->acceptance_pickup, GoodsEntry::GES_ACCEPTANCE) && !(front->current_order.GetUnloadType() & OUFB_TRANSFER)) { //front was u + /* The cargo has reached its final destination, the packets may now be destroyed */ + remaining = v->cargo.MoveTo<StationCargoList>(NULL, amount_unloaded, VehicleCargoList::MTA_FINAL_DELIVERY, payment, last_visited, v->cargo_type); + + dirty_vehicle = true; + accepted = true; + } + + /* The !accepted || v->cargo.Count == cargo_count clause is there + * to make it possible to force unload vehicles at the station where + * they were loaded, but to not force unload the vehicle when the + * station is still accepting the cargo in the vehicle. It doesn't + * accept cargo that was loaded at the same station. */ + if ((front->current_order.GetUnloadType() & (OUFB_UNLOAD | OUFB_TRANSFER)) && (!accepted || v->cargo.Count() == cargo_count)) { //front was u + remaining = v->cargo.MoveTo(&ge->cargo, amount_unloaded, front->current_order.GetUnloadType() & OUFB_TRANSFER ? VehicleCargoList::MTA_TRANSFER : VehicleCargoList::MTA_UNLOAD, payment, last_visited, v->cargo_type); //front was u + + did_transfer = true; + dirty_vehicle = true; + accepted = true; + } } /* The !accepted || v->cargo.Count == cargo_count clause is there @@ -1360,15 +1436,15 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left) * they were loaded, but to not force unload the vehicle when the * station is still accepting the cargo in the vehicle. It doesn't * accept cargo that was loaded at the same station. */ - if ((front->current_order.GetUnloadType() & (OUFB_UNLOAD | OUFB_TRANSFER)) && (!accepted || v->cargo.Count() == cargo_count)) { - remaining = v->cargo.MoveTo(&ge->cargo, amount_unloaded, front->current_order.GetUnloadType() & OUFB_TRANSFER ? VehicleCargoList::MTA_TRANSFER : VehicleCargoList::MTA_UNLOAD, payment); + if (did_transfer) { + /* Update station information. */ if (!HasBit(ge->acceptance_pickup, GoodsEntry::GES_PICKUP)) { SetBit(ge->acceptance_pickup, GoodsEntry::GES_PICKUP); InvalidateWindowData(WC_STATION_LIST, last_visited); } - - dirty_vehicle = dirty_station = true; - } else if (!accepted) { + dirty_station = true; + } + if (!accepted) { /* The order changed while unloading (unset unload/transfer) or the * station does not accept our goods. */ ClrBit(v->vehicle_flags, VF_CARGO_UNLOADING); @@ -1432,13 +1508,13 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left) FOR_EACH_SET_CARGO_ID(cid, refit_mask) { /* Consider refitting to this cargo, if other vehicles of the consist cannot * already take the cargo without refitting */ - if (cargo_left[cid] > (int)consist_capleft[cid] + amount) { + if (cargo_left[cid][last_order] + cargo_left[cid][INVALID_ORDER] > (int)consist_capleft[cid] + amount) { /* Try to find out if auto-refitting would succeed. In case the refit is allowed, * the returned refit capacity will be greater than zero. */ new_subtype = GetBestFittingSubType(v, v, cid); DoCommand(v_start->tile, v_start->index, cid | 1U << 6 | new_subtype << 8 | 1U << 16, DC_QUERY_COST, GetCmdRefitVeh(v_start)); // Auto-refit and only this vehicle including artic parts. if (_returned_refit_capacity > 0) { - amount = cargo_left[cid] - consist_capleft[cid]; + amount = cargo_left[cid][last_order] + cargo_left[cid][INVALID_ORDER] - consist_capleft[cid]; new_cid = cid; } } @@ -1491,11 +1567,12 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left) int cap_left = v->cargo_cap - v->cargo.Count(); if (!ge->cargo.Empty() && cap_left > 0) { uint cap = cap_left; - uint count = ge->cargo.Count(); + uint count = ge->cargo.CountForNextHop(last_order) + ge->cargo.CountForNextHop(INVALID_ORDER); /* Skip loading this vehicle if another train/vehicle is already handling - * the same cargo type at this station */ - if (_settings_game.order.improved_load && cargo_left[v->cargo_type] <= 0) { + * the same cargo type at this station. Check the buckets for our next + * destination and the general bucket. */ + if (_settings_game.order.improved_load && cargo_left[v->cargo_type][last_order] <= 0 && cargo_left[v->cargo_type][INVALID_ORDER] <= 0) { SetBit(cargo_not_full, v->cargo_type); continue; } @@ -1507,14 +1584,23 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left) } if (_settings_game.order.improved_load) { /* Don't load stuff that is already 'reserved' for other vehicles */ - cap = min((uint)cargo_left[v->cargo_type], cap); - count = cargo_left[v->cargo_type]; + count = cargo_left[v->cargo_type][last_order] + cargo_left[v->cargo_type][INVALID_ORDER]; + + /* Try the bucket for our next destination first. */ + int load_next = min<uint>(cap, cargo_left[v->cargo_type][last_order]); + + /* Reserve from the common bucket if still space left. */ + int load_common = min<uint>(cap - load_next, cargo_left[v->cargo_type][INVALID_ORDER]); + + cap = load_next + load_common; + cargo_left[v->cargo_type][last_order] -= load_next; + if (use_autorefit) { /* When using autorefit, reserve all cargo for this wagon to prevent other wagons * from feeling the need to refit. */ - uint total_cap_left = v->cargo_cap - v->cargo.Count(); - cargo_left[v->cargo_type] -= total_cap_left; + uint total_cap_left = v->cargo_cap - v->cargo.Count() - load_common; consist_capleft[v->cargo_type] -= total_cap_left; + cargo_left[v->cargo_type][INVALID_ORDER] -= total_cap_left; if (total_cap_left > cap && count > cap) { /* Remember if there are reservations left so that we don't stop * loading before they're loaded. */ @@ -1523,7 +1609,7 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left) } else { /* Update cargo left; but don't reserve everything yet, so other wagons * of the same consist load in parallel. */ - cargo_left[v->cargo_type] -= cap; + cargo_left[v->cargo_type][INVALID_ORDER] -= load_common; } } @@ -1546,7 +1632,7 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left) completely_emptied = false; anything_loaded = true; - ge->cargo.MoveTo(&v->cargo, cap, StationCargoList::MTA_CARGO_LOAD, NULL, st->xy); + ge->cargo.MoveTo(&v->cargo, cap, StationCargoList::MTA_CARGO_LOAD, NULL, last_visited, last_order, v->cargo_type); st->time_since_load = 0; st->last_vehicle_type = v->type; @@ -1596,7 +1682,15 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left) cap_left -= v->cargo.Count(); } } - if (cap_left > 0) cargo_left[v->cargo_type] -= cap_left; + if (cap_left > 0) { + /* Try the bucket for our next destination first. */ + int loaded = min(cap_left, cargo_left[v->cargo_type][last_order]); + cargo_left[v->cargo_type][last_order] -= loaded; + + /* Reserve from the common bucket if still space left. */ + loaded = min(cap_left - loaded, cargo_left[v->cargo_type][INVALID_ORDER]); + cargo_left[v->cargo_type][INVALID_ORDER] -= loaded; + } } } @@ -1714,9 +1808,10 @@ void LoadUnloadStation(Station *st) */ if (last_loading == NULL) return; - int cargo_left[NUM_CARGO]; - - for (uint i = 0; i < NUM_CARGO; i++) cargo_left[i] = st->goods[i].cargo.Count(); + StationCargoList::OrderMap cargo_left[NUM_CARGO]; + for (CargoID i = 0; i < NUM_CARGO; i++) { + cargo_left[i] = st->goods[i].cargo.CountForNextHop(); + } for (iter = st->loading_vehicles.begin(); iter != st->loading_vehicles.end(); ++iter) { Vehicle *v = *iter; diff --git a/src/economy_base.h b/src/economy_base.h index 742d01878..1e32f3b08 100644 --- a/src/economy_base.h +++ b/src/economy_base.h @@ -24,9 +24,10 @@ extern CargoPaymentPool _cargo_payment_pool; * Helper class to perform the cargo payment. */ struct CargoPayment : CargoPaymentPool::PoolItem<&_cargo_payment_pool> { - Vehicle *front; ///< The front vehicle to do the payment of - Money route_profit; ///< The amount of money to add/remove from the bank account - Money visual_profit; ///< The visual profit to show + Vehicle *front; ///< The front vehicle to do the payment of + Money route_profit; ///< The amount of money to add/remove from the bank account + Money visual_profit; ///< The visual (non-transfer) profit to show + Money transfer_profit; ///< The transfer profit to show /* Unsaved variables */ Company *owner; ///< The owner of the vehicle diff --git a/src/economy_func.h b/src/economy_func.h index 111ce85a2..b3517896c 100644 --- a/src/economy_func.h +++ b/src/economy_func.h @@ -31,7 +31,7 @@ int UpdateCompanyRatingAndValue(Company *c, bool update); void StartupIndustryDailyChanges(bool init_counter); Money GetTransportedGoodsIncome(uint num_pieces, uint dist, byte transit_days, CargoID cargo_type); -uint MoveGoodsToStation(CargoID type, uint amount, SourceType source_type, SourceID source_id, const StationList *all_stations); +uint MoveGoodsToStation(CargoID type, uint amount, SourceType source_type, SourceID source_id, const StationList *all_stations, TileIndex src_tile); void PrepareUnload(Vehicle *front_v); void LoadUnloadStation(Station *st); diff --git a/src/genworld.cpp b/src/genworld.cpp index 78fa6e3e7..7f3d426a1 100644 --- a/src/genworld.cpp +++ b/src/genworld.cpp @@ -29,6 +29,7 @@ #include "newgrf.h" #include "core/random_func.hpp" #include "core/backup_type.hpp" +#include "cargodest_func.h" #include "progress.h" #include "error.h" #include "game/game.hpp" @@ -138,6 +139,7 @@ static void _GenerateWorld(void *) GenerateIndustries(); GenerateObjects(); GenerateTrees(); + UpdateCargoLinks(); } } diff --git a/src/genworld_gui.cpp b/src/genworld_gui.cpp index 94a06e748..8c2798779 100644 --- a/src/genworld_gui.cpp +++ b/src/genworld_gui.cpp @@ -18,6 +18,8 @@ #include "window_func.h" #include "date_func.h" #include "sound_func.h" +#include "map_type.h" +#include "layer_type.h" #include "fios.h" #include "string_func.h" #include "widgets/dropdown_type.h" @@ -79,6 +81,7 @@ static const NWidgetPart _nested_generate_landscape_widgets[] = { /* Left column with labels. */ NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 4, 0), NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_MAPSIZE, STR_MAPGEN_MAPSIZE_TOOLTIP), SetFill(1, 1), + NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_UNDERGROUND_LAYER_COUNT, STR_NULL), SetFill(1, 1), NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_LAND_GENERATOR, STR_NULL), SetFill(1, 1), NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_NUMBER_OF_TOWNS, STR_NULL), SetFill(1, 1), NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_TERRAIN_TYPE, STR_NULL), SetFill(1, 1), @@ -96,6 +99,7 @@ static const NWidgetPart _nested_generate_landscape_widgets[] = { NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_BY, STR_NULL), SetPadding(1, 0, 0, 0), SetFill(1, 1), NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_MAPSIZE_Y_PULLDOWN), SetDataTip(STR_JUST_INT, STR_MAPGEN_MAPSIZE_TOOLTIP), SetFill(1, 0), EndContainer(), + NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_LAYER_COUNT_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL), SetFill(1, 0), NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_LANDSCAPE_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0), NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_TOWN_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0), NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_TERRAIN_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0), @@ -197,6 +201,7 @@ static const NWidgetPart _nested_heightmap_load_widgets[] = { NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 4, 0), NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_HEIGHTMAP_NAME, STR_NULL), SetFill(1, 1), NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_MAPSIZE, STR_NULL), SetFill(1, 1), + NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_UNDERGROUND_LAYER_COUNT, STR_NULL), SetFill(1, 1), NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_NUMBER_OF_TOWNS, STR_NULL), SetFill(1, 1), NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_NUMBER_OF_INDUSTRIES, STR_NULL), SetFill(1, 1), NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_RANDOM_SEED, STR_NULL), SetFill(1, 1), @@ -215,6 +220,7 @@ static const NWidgetPart _nested_heightmap_load_widgets[] = { NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_BY, STR_NULL), SetPadding(1, 0, 0, 0), SetFill(1, 1), NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_MAPSIZE_Y_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL), SetFill(1, 0), EndContainer(), + NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_LAYER_COUNT_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL), SetFill(1, 0), NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_TOWN_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0), NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_INDUSTRY_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0), NWidget(WWT_EDITBOX, COLOUR_ORANGE, WID_GL_RANDOM_EDITBOX), SetDataTip(STR_MAPGEN_RANDOM_SEED_OSKTITLE, STR_MAPGEN_RANDOM_SEED_HELP), SetFill(1, 1), @@ -277,11 +283,11 @@ static void LandscapeGenerationCallback(Window *w, bool confirmed) if (confirmed) StartGeneratingLandscape((GenenerateLandscapeWindowMode)w->window_number); } -static DropDownList *BuildMapsizeDropDown() +static DropDownList *BuildBitListDropDown(uint min_bits, uint max_bits) { DropDownList *list = new DropDownList(); - for (uint i = MIN_MAP_SIZE_BITS; i <= MAX_MAP_SIZE_BITS; i++) { + for (uint i = min_bits; i <= max_bits; i++) { DropDownListParamStringItem *item = new DropDownListParamStringItem(STR_JUST_INT, i, false); item->SetParam(0, 1 << i); list->push_back(item); @@ -290,6 +296,16 @@ static DropDownList *BuildMapsizeDropDown() return list; } +static DropDownList *BuildMapsizeDropDown() +{ + return BuildBitListDropDown(MIN_MAP_SIZE_BITS, MAX_MAP_SIZE_BITS); +} + +static DropDownList *BuildLayerDropDown() +{ + return BuildBitListDropDown(MIN_LAYER_COUNT_BITS, MAX_LAYER_COUNT_BITS); +} + static const StringID _elevations[] = {STR_TERRAIN_TYPE_VERY_FLAT, STR_TERRAIN_TYPE_FLAT, STR_TERRAIN_TYPE_HILLY, STR_TERRAIN_TYPE_MOUNTAINOUS, INVALID_STRING_ID}; static const StringID _sea_lakes[] = {STR_SEA_LEVEL_VERY_LOW, STR_SEA_LEVEL_LOW, STR_SEA_LEVEL_MEDIUM, STR_SEA_LEVEL_HIGH, STR_SEA_LEVEL_CUSTOM, INVALID_STRING_ID}; static const StringID _rivers[] = {STR_RIVERS_NONE, STR_RIVERS_FEW, STR_RIVERS_MODERATE, STR_RIVERS_LOT, INVALID_STRING_ID}; @@ -339,6 +355,7 @@ struct GenerateLandscapeWindow : public Window { case WID_GL_START_DATE_TEXT: SetDParam(0, ConvertYMDToDate(_settings_newgame.game_creation.starting_year, 0, 1)); break; case WID_GL_MAPSIZE_X_PULLDOWN: SetDParam(0, 1 << _settings_newgame.game_creation.map_x); break; case WID_GL_MAPSIZE_Y_PULLDOWN: SetDParam(0, 1 << _settings_newgame.game_creation.map_y); break; + case WID_GL_LAYER_COUNT_PULLDOWN: SetDParam(0, 1 << _settings_newgame.game_creation.layers); break; case WID_GL_SNOW_LEVEL_TEXT: SetDParam(0, _settings_newgame.game_creation.snow_line_height); break; case WID_GL_TOWN_PULLDOWN: @@ -453,6 +470,11 @@ struct GenerateLandscapeWindow : public Window { *size = GetStringBoundingBox(STR_JUST_INT); break; + case WID_GL_LAYER_COUNT_PULLDOWN: + SetDParam(0, MAX_LAYER_COUNT); + *size = GetStringBoundingBox(STR_JUST_INT); + break; + case WID_GL_SNOW_LEVEL_TEXT: SetDParamMaxValue(0, MAX_TILE_HEIGHT); *size = GetStringBoundingBox(STR_JUST_INT); @@ -539,6 +561,10 @@ struct GenerateLandscapeWindow : public Window { ShowDropDownList(this, BuildMapsizeDropDown(), _settings_newgame.game_creation.map_y, WID_GL_MAPSIZE_Y_PULLDOWN); break; + case WID_GL_LAYER_COUNT_PULLDOWN: // Mapsize Z + ShowDropDownList(this, BuildLayerDropDown(), _settings_newgame.game_creation.layers, WID_GL_LAYER_COUNT_PULLDOWN); + break; + case WID_GL_TOWN_PULLDOWN: // Number of towns ShowDropDownMenu(this, _num_towns, _settings_newgame.difficulty.number_towns, WID_GL_TOWN_PULLDOWN, 0, 0); break; @@ -709,6 +735,7 @@ struct GenerateLandscapeWindow : public Window { switch (widget) { case WID_GL_MAPSIZE_X_PULLDOWN: _settings_newgame.game_creation.map_x = index; break; case WID_GL_MAPSIZE_Y_PULLDOWN: _settings_newgame.game_creation.map_y = index; break; + case WID_GL_LAYER_COUNT_PULLDOWN: _settings_newgame.game_creation.layers = index; break; case WID_GL_TREE_PULLDOWN: _settings_newgame.game_creation.tree_placer = index; break; case WID_GL_RIVER_PULLDOWN: _settings_newgame.game_creation.amount_of_rivers = index; break; case WID_GL_SMOOTHNESS_PULLDOWN: _settings_newgame.game_creation.tgen_smoothness = index; break; @@ -891,6 +918,10 @@ struct CreateScenarioWindow : public Window SetDParam(0, 1 << _settings_newgame.game_creation.map_y); break; + case WID_CS_LAYER_COUNT_PULLDOWN: + SetDParam(0, 1 << _settings_newgame.game_creation.layers); + break; + case WID_CS_FLAT_LAND_HEIGHT_TEXT: SetDParam(0, _settings_newgame.game_creation.se_flat_world_height); break; @@ -924,6 +955,10 @@ struct CreateScenarioWindow : public Window case WID_CS_MAPSIZE_X_PULLDOWN: case WID_CS_MAPSIZE_Y_PULLDOWN: SetDParamMaxValue(0, MAX_MAP_SIZE); + break; + + case WID_CS_LAYER_COUNT_PULLDOWN: + SetDParam(0, MAX_LAYER_COUNT); break; case WID_CS_FLAT_LAND_HEIGHT_TEXT: @@ -957,6 +992,10 @@ struct CreateScenarioWindow : public Window ShowDropDownList(this, BuildMapsizeDropDown(), _settings_newgame.game_creation.map_y, WID_CS_MAPSIZE_Y_PULLDOWN); break; + case WID_CS_LAYER_COUNT_PULLDOWN: // Mapsize Y + ShowDropDownList(this, BuildLayerDropDown(), _settings_newgame.game_creation.layers, WID_CS_LAYER_COUNT_PULLDOWN); + break; + case WID_CS_EMPTY_WORLD: // Empty world / flat world StartGeneratingLandscape(GLWM_SCENARIO); break; @@ -1019,6 +1058,7 @@ struct CreateScenarioWindow : public Window switch (widget) { case WID_CS_MAPSIZE_X_PULLDOWN: _settings_newgame.game_creation.map_x = index; break; case WID_CS_MAPSIZE_Y_PULLDOWN: _settings_newgame.game_creation.map_y = index; break; + case WID_CS_LAYER_COUNT_PULLDOWN: _settings_newgame.game_creation.layers = index; break; } this->SetDirty(); } @@ -1074,6 +1114,7 @@ static const NWidgetPart _nested_create_scenario_widgets[] = { NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_CS_MAPSIZE_X_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL), SetPadding(0, 4, 0, 0), NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_BY, STR_NULL), SetPadding(1, 2, 0, 0), NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_CS_MAPSIZE_Y_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL), + NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_CS_LAYER_COUNT_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL), EndContainer(), /* Date. */ NWidget(NWID_HORIZONTAL), diff --git a/src/gfx_type.h b/src/gfx_type.h index 2b792d090..e7d7cb1a5 100644 --- a/src/gfx_type.h +++ b/src/gfx_type.h @@ -145,6 +145,7 @@ struct DrawPixelInfo { void *dst_ptr; int left, top, width, height; int pitch; + int layer; ZoomLevel zoom; }; diff --git a/src/gfxinit.cpp b/src/gfxinit.cpp index 0ee82d05e..7cc8ab7c6 100644 --- a/src/gfxinit.cpp +++ b/src/gfxinit.cpp @@ -192,6 +192,9 @@ static void LoadSpriteTables() /* Initialize the unicode to sprite mapping table */ InitializeUnicodeGlyphMap(); + /* Load traffic lights graphics. */ + LoadGrfFile("trafficlights.grf", SPR_TRAFFICLIGHTS_BASE, i++); + /* * Load the base NewGRF with OTTD required graphics as first NewGRF. * However, we do not want it to show up in the list of used NewGRFs, diff --git a/src/heightmap.cpp b/src/heightmap.cpp index 8f0fac6ad..27fc27e63 100644 --- a/src/heightmap.cpp +++ b/src/heightmap.cpp @@ -16,6 +16,7 @@ #include "error.h" #include "saveload/saveload.h" #include "bmp.h" +#include "layer_func.h" #include "gfx_func.h" #include "fios.h" #include "fileio_func.h" @@ -386,6 +387,9 @@ void FixSlopes() width = MapSizeX(); height = MapSizeY(); + /* Layers height correct */ + FixUndergroundHeights(); + /* Top and left edge */ for (row = 0; (uint)row < height; row++) { for (col = 0; (uint)col < width; col++) { diff --git a/src/industry.h b/src/industry.h index 5e5d04688..7e329a7bd 100644 --- a/src/industry.h +++ b/src/industry.h @@ -16,6 +16,7 @@ #include "subsidy_type.h" #include "industry_map.h" #include "tilearea_type.h" +#include "cargodest_base.h" typedef Pool<Industry, IndustryID, 64, 64000> IndustryPool; @@ -36,7 +37,7 @@ enum ProductionLevels { /** * Defines the internal data of a functional industry. */ -struct Industry : IndustryPool::PoolItem<&_industry_pool> { +struct Industry : IndustryPool::PoolItem<&_industry_pool>, CargoSourceSink { TileArea location; ///< Location of the industry Town *town; ///< Nearest town CargoID produced_cargo[2]; ///< 2 production cargo slots @@ -45,11 +46,13 @@ struct Industry : IndustryPool::PoolItem<&_industry_pool> { byte production_rate[2]; ///< production rate for each cargo byte prod_level; ///< general production level CargoID accepts_cargo[3]; ///< 3 input cargo slots + uint32 produced_accepted_mask; ///< Bit mask of all cargoes that are always accepted and also produced uint16 this_month_production[2]; ///< stats of this month's production per cargo uint16 this_month_transported[2]; ///< stats of this month's transport per cargo byte last_month_pct_transported[2]; ///< percentage transported per cargo in the last full month uint16 last_month_production[2]; ///< total units produced per cargo in the last full month uint16 last_month_transported[2]; ///< total units transported per cargo in the last full month + uint16 average_production[2]; ///< average production during the last months uint16 counter; ///< used for animation and/or production (if available cargo) IndustryType type; ///< type of industry. @@ -76,6 +79,41 @@ struct Industry : IndustryPool::PoolItem<&_industry_pool> { void RecomputeProductionMultipliers(); + /* virtual */ SourceType GetType() const + { + return ST_INDUSTRY; + } + + /* virtual */ SourceID GetID() const + { + return this->index; + } + + /* virtual */ bool AcceptsCargo(CargoID cid) const + { + if (HasBit(this->produced_accepted_mask, cid)) return true; + + for (uint i = 0; i < lengthof(this->accepts_cargo); i++) { + if (this->accepts_cargo[i] == cid) return true; + } + return false; + } + + /* virtual */ bool SuppliesCargo(CargoID cid) const + { + for (uint i = 0; i < lengthof(this->produced_cargo); i++) { + if (this->produced_cargo[i] == cid) return true; + } + return false; + } + + /* virtual */ uint GetDestinationWeight(CargoID cid, byte weight_mod) const; + + /* virtual */ TileArea GetTileForDestination(CargoID cid) + { + return this->location; + } + /** * Check if a given tile belongs to this industry. * @param tile The tile to check. @@ -97,7 +135,10 @@ struct Industry : IndustryPool::PoolItem<&_industry_pool> { return Industry::Get(GetIndustryIndex(tile)); } - static Industry *GetRandom(); + /** Callback function for #Industry::GetRandom. */ + typedef bool (*EnumIndustryProc)(const Industry *ind, void *data); + + static Industry *GetRandom(EnumIndustryProc enum_proc = NULL, IndustryID skip = INVALID_INDUSTRY, void *data = NULL); static void PostDestructor(size_t index); /** @@ -147,6 +188,8 @@ void PlantRandomFarmField(const Industry *i); void ReleaseDisastersTargetingIndustry(IndustryID); +void UpdateIndustryAcceptance(Industry *ind); + bool IsTileForestIndustry(TileIndex tile); #define FOR_ALL_INDUSTRIES_FROM(var, start) FOR_ALL_ITEMS_FROM(Industry, industry_index, var, start) diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp index 69d83ba14..4cead964f 100644 --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -14,6 +14,7 @@ #include "industry.h" #include "station_base.h" #include "landscape.h" +#include "layer_func.h" #include "viewport_func.h" #include "command_func.h" #include "town.h" @@ -193,17 +194,36 @@ void Industry::PostDestructor(size_t index) /** - * Return a random valid industry. - * @return random industry, NULL if there are no industries + * Return a random industry that statisfies some criteria + * specified with a callback function. + * + * @param enum_proc Callback function. Return true for a matching industry and false to continue iterating. + * @param skip Skip over this industry id when searching. + * @param data Optional data passed to the callback function. + * @return An industry satisfying the search criteria or NULL if no such industry exists. */ -/* static */ Industry *Industry::GetRandom() +/* static */ Industry *Industry::GetRandom(EnumIndustryProc enum_proc, IndustryID skip, void *data) { - if (Industry::GetNumItems() == 0) return NULL; - int num = RandomRange((uint16)Industry::GetNumItems()); - size_t index = MAX_UVALUE(size_t); + assert(skip == INVALID_INDUSTRY || Industry::IsValidID(skip)); + + uint16 max_num = 0; + if (enum_proc != NULL) { + /* A callback was given, count all matching industries. */ + Industry *ind; + FOR_ALL_INDUSTRIES(ind) { + if (ind->index != skip && enum_proc(ind, data)) max_num++; + } + } else { + max_num = (uint16)Industry::GetNumItems(); + /* Subtract one if an industry to skip was given. max_num is at least + * one here as otherwise skip could not be valid. */ + if (skip != INVALID_INDUSTRY) max_num--; + } + if (max_num == 0) return NULL; - while (num >= 0) { - num--; + uint num = RandomRange(max_num) + 1; + size_t index = MAX_UVALUE(size_t); + do { index++; /* Make sure we have a valid industry */ @@ -211,7 +231,9 @@ void Industry::PostDestructor(size_t index) index++; assert(index < Industry::GetPoolSize()); } - } + + if (index != skip && (enum_proc == NULL || enum_proc(Industry::Get(index), data))) num--; + } while (num > 0); return Industry::Get(index); } @@ -506,7 +528,7 @@ static void TransportIndustryGoods(TileIndex tile) i->this_month_production[j] += cw; - uint am = MoveGoodsToStation(i->produced_cargo[j], cw, ST_INDUSTRY, i->index, stations.GetStations()); + uint am = MoveGoodsToStation(i->produced_cargo[j], cw, ST_INDUSTRY, i->index, stations.GetStations(), tile); i->this_month_transported[j] += am; moved_cargo |= (am != 0); @@ -1380,7 +1402,7 @@ static CommandCost CheckIfIndustryTilesAreFree(TileIndex tile, const IndustryTil IndustryGfx gfx = GetTranslatedIndustryTileID(it->gfx); TileIndex cur_tile = TileAddWrap(tile, it->ti.x, it->ti.y); - if (!IsValidTile(cur_tile)) { + if (!IsValidTile(cur_tile) || IsUnderground(cur_tile)) { return_cmd_error(STR_ERROR_SITE_UNSUITABLE); } @@ -1583,6 +1605,28 @@ static CommandCost CheckIfFarEnoughFromConflictingIndustry(TileIndex tile, int t return CommandCost(); } +/** Update the mask of always accepted cargoes that are also produced. */ +void UpdateIndustryAcceptance(Industry *ind) +{ + CargoArray accepted; + uint32 always_accepted = 0; + + /* Gather always accepted cargoes for all tiles of this industry. */ + TILE_AREA_LOOP(tile, ind->location) { + if (IsTileType(tile, MP_INDUSTRY) && GetIndustryIndex(tile) == ind->index) { + AddAcceptedCargo_Industry(tile, accepted, &always_accepted); + } + } + + /* Create mask of produced cargoes. */ + uint32 produced = 0; + for (uint i = 0; i < lengthof(ind->produced_cargo); i++) { + if (ind->produced_cargo[i] != CT_INVALID) SetBit(produced, ind->produced_cargo[i]); + } + + ind->produced_accepted_mask = always_accepted & produced; +} + /** * Advertise about a new industry opening. * @param ind Industry being opened. @@ -1756,6 +1800,7 @@ static void DoCreateNewIndustry(Industry *i, TileIndex tile, IndustryType type, } InvalidateWindowData(WC_INDUSTRY_DIRECTORY, 0, 0); + UpdateIndustryAcceptance(i); Station::RecomputeIndustriesNearForAll(); } @@ -2153,6 +2198,9 @@ static void UpdateIndustryStatistics(Industry *i) i->last_month_transported[j] = i->this_month_transported[j]; i->this_month_transported[j] = 0; + + /* Average production over the last eight months. */ + i->average_production[j] = (i->average_production[j] * 7 + i->last_month_production[j]) / 8; } } } @@ -2697,6 +2745,7 @@ void IndustryMonthlyLoop() Industry *i; FOR_ALL_INDUSTRIES(i) { UpdateIndustryStatistics(i); + UpdateIndustryAcceptance(i); if (i->prod_level == PRODLEVEL_CLOSURE) { delete i; } else { diff --git a/src/industry_gui.cpp b/src/industry_gui.cpp index 9db4a5a94..58550b1e2 100644 --- a/src/industry_gui.cpp +++ b/src/industry_gui.cpp @@ -38,6 +38,7 @@ #include "smallmap_gui.h" #include "widgets/dropdown_type.h" #include "widgets/industry_widget.h" +#include "cargodest_gui.h" #include "table/strings.h" @@ -653,8 +654,11 @@ class IndustryViewWindow : public Window int production_offset_y; ///< The offset of the production texts/buttons int info_height; ///< Height needed for the #WID_IV_INFO panel + CargoDestinationList dest_list; ///< Sorted list of demand destinations. + int dest_list_top; ///< Top coordinate of the destination list. + public: - IndustryViewWindow(const WindowDesc *desc, WindowNumber window_number) : Window() + IndustryViewWindow(const WindowDesc *desc, WindowNumber window_number) : Window(), dest_list(Industry::Get(window_number)) { this->flags |= WF_DISABLE_VP_SCROLL; this->editbox_line = IL_NONE; @@ -792,6 +796,10 @@ public: } } } + + this->dest_list_top = y; + y = this->dest_list.DrawList(left, right, y); + return y + WD_FRAMERECT_BOTTOM; } @@ -811,6 +819,13 @@ public: case WID_IV_INFO: { Industry *i = Industry::Get(this->window_number); InfoLine line = IL_NONE; + NWidgetBase *nwi = this->GetWidget<NWidgetBase>(widget); + + /* Test for click on destination list. */ + if (pt.y > this->dest_list_top) { + this->dest_list.OnClick(pt.y - this->dest_list_top); + return; + } switch (this->editable) { case EA_NONE: break; @@ -835,7 +850,6 @@ public: } if (line == IL_NONE) return; - NWidgetBase *nwi = this->GetWidget<NWidgetBase>(widget); int left = nwi->pos_x + WD_FRAMETEXT_LEFT; int right = nwi->pos_x + nwi->current_x - 1 - WD_FRAMERECT_RIGHT; if (IsInsideMM(pt.x, left, left + SETTING_BUTTON_WIDTH)) { @@ -963,6 +977,13 @@ public: } else { this->editable = EA_NONE; } + + /* Rebuild destination list if data is not zero, otherwise just resort. */ + if (data != 0) { + this->dest_list.InvalidateData(); + } else { + this->dest_list.Resort(); + } } virtual bool IsNewGRFInspectable() const diff --git a/src/landscape.cpp b/src/landscape.cpp index a4b12f857..e39693f6e 100644 --- a/src/landscape.cpp +++ b/src/landscape.cpp @@ -722,11 +722,18 @@ void RunTileLoop() * shift register (LFSR). This allows a deterministic pseudorandom ordering, but * still with minimal state and fast iteration. */ - /* Maximal length LFSR feedback terms, from 12-bit (for 64x64 maps) to 22-bit (for 2048x2048 maps). + /* Maximal length LFSR feedback terms, from 12-bit (for 64x64 maps) over 22-bit (for 2048x2048 maps) to 32-bit. * Extracted from http://www.ece.cmu.edu/~koopman/lfsr/ */ static const uint32 feedbacks[] = { - 0xD8F, 0x1296, 0x2496, 0x4357, 0x8679, 0x1030E, 0x206CD, 0x403FE, 0x807B8, 0x1004B2, 0x2006A8 + // 12 - 22 + 0xD8F, 0x1296, 0x2496, 0x4357, 0x8679, 0x1030E, 0x206CD, 0x403FE, 0x807B8, 0x1004B2, 0x2006A8, + // 23 - 30 + 0x4004B2, 0x800B87, 0x10004F3, 0x200072D, 0x40006AE, 0x80009E3, 0x10000583, 0x20000C92, + // 31 - 32 + 0x400005B6, 0x80000EA6 }; + + assert(MapLogX() + MapLogY() <= 32); const uint32 feedback = feedbacks[MapLogX() + MapLogY() - 12]; /* We update every tile every 256 ticks, so divide the map size by 2^8 = 256 */ diff --git a/src/lang/english.txt b/src/lang/english.txt index f2f28f794..893a8fa77 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -431,6 +431,13 @@ STR_AIRCRAFT_MENU_AIRPORT_CONSTRUCTION :Airport constru STR_LANDSCAPING_MENU_LANDSCAPING :Landscaping STR_LANDSCAPING_MENU_PLANT_TREES :Plant trees STR_LANDSCAPING_MENU_PLACE_SIGN :Place sign +STR_LANDSCAPING_MENU_SEPARATOR1 : +STR_LANDSCAPING_MENU_UNDERGROUND :Special constructions +STR_LANDSCAPING_MENU_SEPARATOR2 : +STR_LANDSCAPING_MENU_LAYER_1 :Surface +STR_LANDSCAPING_MENU_LAYER_2 :Undergroound (-1) +STR_LANDSCAPING_MENU_LAYER_3 :Undergroound (-2) +STR_LANDSCAPING_MENU_LAYER_4 :Undergroound (-3) ############ range ends here ############ range for music menu starts @@ -661,17 +668,20 @@ STR_SMALLMAP_CAPTION :{WHITE}Map - {S STR_SMALLMAP_TYPE_CONTOURS :Contours STR_SMALLMAP_TYPE_VEHICLES :Vehicles STR_SMALLMAP_TYPE_INDUSTRIES :Industries +STR_SMALLMAP_TYPE_ROUTELINKS :Route links STR_SMALLMAP_TYPE_ROUTES :Routes STR_SMALLMAP_TYPE_VEGETATION :Vegetation STR_SMALLMAP_TYPE_OWNERS :Owners STR_SMALLMAP_TOOLTIP_SHOW_LAND_CONTOURS_ON_MAP :{BLACK}Show land contours on map STR_SMALLMAP_TOOLTIP_SHOW_VEHICLES_ON_MAP :{BLACK}Show vehicles on map STR_SMALLMAP_TOOLTIP_SHOW_INDUSTRIES_ON_MAP :{BLACK}Show industries on map +STR_SMALLMAP_TOOLTIP_SHOW_ROUTE_LINKS_ON_MAP :{BLACK}Show route links on map STR_SMALLMAP_TOOLTIP_SHOW_TRANSPORT_ROUTES_ON :{BLACK}Show transport routes on map STR_SMALLMAP_TOOLTIP_SHOW_VEGETATION_ON_MAP :{BLACK}Show vegetation on map STR_SMALLMAP_TOOLTIP_SHOW_LAND_OWNERS_ON_MAP :{BLACK}Show land owners on map STR_SMALLMAP_TOOLTIP_INDUSTRY_SELECTION :{BLACK}Click on an industry type to toggle displaying it. Ctrl+Click disables all types except the selected one. Ctrl+Click on it again to enable all industry types STR_SMALLMAP_TOOLTIP_COMPANY_SELECTION :{BLACK}Click on a company to toggle displaying its property. Ctrl+Click disables all companies except the selected one. Ctrl+Click on it again to enable all companies +STR_SMALLMAP_TOOLTIP_ROUTELINK_SELECTION :{BLACK}Click on a cargo to toggle displaying the route links. Ctrl+Click disables all route links except the selected one. Ctrl+Click on it again to enable all route links STR_SMALLMAP_LEGENDA_ROADS :{TINY_FONT}{BLACK}Roads STR_SMALLMAP_LEGENDA_RAILROADS :{TINY_FONT}{BLACK}Railways @@ -707,6 +717,7 @@ STR_SMALLMAP_CENTER :{BLACK}Centre t STR_SMALLMAP_INDUSTRY :{TINY_FONT}{STRING} ({NUM}) STR_SMALLMAP_COMPANY :{TINY_FONT}{COMPANY} STR_SMALLMAP_TOWN :{TINY_FONT}{WHITE}{TOWN} +STR_SMALLMAP_CARGO :{TINY_FONT}{STRING} STR_SMALLMAP_DISABLE_ALL :{BLACK}Disable all STR_SMALLMAP_ENABLE_ALL :{BLACK}Enable all STR_SMALLMAP_SHOW_HEIGHT :{BLACK}Show height @@ -715,6 +726,8 @@ STR_SMALLMAP_TOOLTIP_ENABLE_ALL_INDUSTRIES :{BLACK}Display STR_SMALLMAP_TOOLTIP_SHOW_HEIGHT :{BLACK}Toggle display of heightmap STR_SMALLMAP_TOOLTIP_DISABLE_ALL_COMPANIES :{BLACK}Display no company property on the map STR_SMALLMAP_TOOLTIP_ENABLE_ALL_COMPANIES :{BLACK}Display all company property on the map +STR_SMALLMAP_TOOLTIP_DISABLE_ALL_ROUTELINKS :{BLACK}Display no route links on the map +STR_SMALLMAP_TOOLTIP_ENABLE_ALL_ROUTELINKS :{BLACK}Display route links for all cargoes on the map # Status bar messages STR_STATUSBAR_TOOLTIP_SHOW_LAST_NEWS :{BLACK}Show last message or news report @@ -1340,8 +1353,23 @@ STR_CONFIG_SETTING_LOADING_INDICATORS :Use loading ind STR_CONFIG_SETTING_LOADING_INDICATORS_HELPTEXT :Select whether loading indicators are displayed above loading or unloading vehicles STR_CONFIG_SETTING_TIMETABLE_IN_TICKS :Show timetable in ticks rather than days: {STRING2} STR_CONFIG_SETTING_TIMETABLE_IN_TICKS_HELPTEXT :Show travel times in time tables in game ticks instead of days +STR_CONFIG_SETTING_TIME_IN_MINUTES :Show time in minutes rather than days: {STRING2} +STR_CONFIG_SETTING_TIME_IN_MINUTES_HELPTEXT :Select whether to use hours and minutes instead of days +STR_CONFIG_SETTING_TICKS_PER_MINUTE :Ticks per minute: {STRING2} +STR_CONFIG_SETTING_TICKS_PER_MINUTE_HELPTEXT :The number of game ticks per minute +STR_CONFIG_SETTING_DATE_WITH_TIME :Show date with time in status bar: {STRING2} +STR_CONFIG_SETTING_DATE_WITH_TIME_HELPTEXT :Show the real game date in the status bar as well as the time +STR_CONFIG_SETTING_CLOCK_OFFSET :Clock offset in minutes: {STRING2} +STR_CONFIG_SETTING_CLOCK_OFFSET_HELPTEXT :The number of minutes the game clock is offset by +STR_CONFIG_SETTING_DATE_WITH_TIME_NONE :None +STR_CONFIG_SETTING_DATE_WITH_TIME_Y :Year +STR_CONFIG_SETTING_DATE_WITH_TIME_YM :Month and year +STR_CONFIG_SETTING_DATE_WITH_TIME_YMD :Full date +STR_CONFIG_SETTING_TIMETABLE_START_TEXT_ENTRY :Enter timetable start times as text (requires time to be in minutes): {STRING2} +STR_CONFIG_SETTING_TIMETABLE_START_TEXT_ENTRY_HELPTEXT :Select whether timetable start times may be entered as text if time is being shown in minutes STR_CONFIG_SETTING_TIMETABLE_SHOW_ARRIVAL_DEPARTURE :Show arrival and departure in timetables: {STRING2} STR_CONFIG_SETTING_TIMETABLE_SHOW_ARRIVAL_DEPARTURE_HELPTEXT :Display anticipated arrival and departure times in timetables +STR_CONFIG_SETTING_TIMETABLE_ENABLE_SEPARATION :{LTBLUE}Enable automatic timetable separation: {ORANGE}{STRING1} STR_CONFIG_SETTING_QUICKGOTO :Quick creation of vehicle orders: {STRING2} STR_CONFIG_SETTING_QUICKGOTO_HELPTEXT :Pre-select the 'goto cursor' when opening the orders window STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE :Default rail type (after new game/game load): {STRING2} @@ -1467,6 +1495,8 @@ STR_CONFIG_SETTING_ALLOW_SHARES :Allow buying sh STR_CONFIG_SETTING_ALLOW_SHARES_HELPTEXT :When enabled, allow buying and selling of company shares. Shares will only be available for companies reaching a certain age STR_CONFIG_SETTING_FEEDER_PAYMENT_SHARE :Percentage of leg profit to pay in feeder systems: {STRING2} STR_CONFIG_SETTING_FEEDER_PAYMENT_SHARE_HELPTEXT :Percentage of income given to the intermediate legs in feeder systems, giving more control over the income +STR_CONFIG_SETTING_SIMULATE_SIGNALS :Simulate signals in tunnels, bridges every: {STRING2} +STR_CONFIG_SETTING_SIMULATE_SIGNALS_VALUE :{COMMA} tile{P 0 "" s} STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY :When dragging, place signals every: {STRING2} STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY_HELPTEXT :Set the distance at which signals will be built on a track up to the next obstacle (signal, junction), if signals are dragged STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY_VALUE :{COMMA} tile{P 0 "" s} @@ -1486,6 +1516,7 @@ STR_CONFIG_SETTING_CYCLE_SIGNAL_TYPES_HELPTEXT :Select which si STR_CONFIG_SETTING_CYCLE_SIGNAL_NORMAL :Block signals only STR_CONFIG_SETTING_CYCLE_SIGNAL_PBS :Path signals only STR_CONFIG_SETTING_CYCLE_SIGNAL_ALL :All +STR_CONFIG_PATCHES_RANDOM_ROAD_CONSTRUCTION :Probability of random road construction (0 = off): {STRING2} STR_CONFIG_SETTING_TOWN_LAYOUT :Road layout for new towns: {STRING2} STR_CONFIG_SETTING_TOWN_LAYOUT_HELPTEXT :Layout for the road network of towns @@ -1549,6 +1580,27 @@ STR_CONFIG_SETTING_CITY_SIZE_MULTIPLIER :Initial city si STR_CONFIG_SETTING_CITY_SIZE_MULTIPLIER_HELPTEXT :Average size of cities relative to normal towns at start of the game STR_CONFIG_SETTING_MODIFIED_ROAD_REBUILD :Remove absurd road-elements during the road construction: {STRING2} STR_CONFIG_SETTING_MODIFIED_ROAD_REBUILD_HELPTEXT :Remove dead road ends during funded road reconstruction +STR_CONFIG_SETTING_TRAFFIC_LIGHTS :Enable traffic lights:{STRING2} +STR_CONFIG_SETTING_TRAFFIC_LIGHTS_HELPTEXT :Enables/disables building of traffic lights completely +STR_CONFIG_SETTING_TOWNS_BUILD_TRAFFIC_LIGHTS :Towns can build traffic lights: {STRING2} +STR_CONFIG_SETTING_TOWNS_BUILD_TRAFFIC_LIGHTS_HELPTEXT :Allows/dissallows towns to build traffic lights during road construction +STR_CONFIG_SETTING_ALLOW_BUILDING_TLS_ON_TOWN_ROADS :Build and remove traffic lights on town roads: {STRING2} +STR_CONFIG_SETTING_ALLOW_BUILDING_TLS_ON_TOWN_ROADS_HELPTEXT :Allows/disallows the players building and removing traffic lights on town roads +STR_CONFIG_SETTING_TRAFFIC_LIGHTS_GREEN_PHASE :Length of traffic lights' green phase: {STRING2} +STR_CONFIG_SETTING_TRAFFIC_LIGHTS_GREEN_PHASE_HELPTEXT :Time in seconds that traffic lights stay green +STR_CONFIG_SETTING_MAX_TRAFFIC_LIGHT_CONSIST_SIZE :Maximum TL consist size: {STRING2} +STR_CONFIG_SETTING_MAX_TRAFFIC_LIGHT_CONSIST_SIZE_HELPTEXT :Maximum number of trafficlights that can be synchronised. Warning: High setting slow down the game +STR_CONFIG_SETTING_MAX_TRAFFIC_LIGHT_CONSIST_DISTANCE :Maximum distance between two trafficlights for synchronising: {STRING2} +STR_CONFIG_SETTING_MAX_TRAFFIC_LIGHT_CONSIST_DISTANCE_HELPTEXT :Sets the maximum tile distance between two trafficlights for them to be synchronised + +STR_CONFIG_SETTING_CARGODEST_PAX :Destination mode for passengers/mail: {STRING2} +STR_CONFIG_SETTING_CARGODEST_PAX_HELPTEXT :Enable passengers and mail choosing destinations on their own +STR_CONFIG_SETTING_CARGODEST_TOWN :Destination mode for town-accepted cargoes: {STRING2} +STR_CONFIG_SETTING_CARGODEST_TOWN_HELPTEXT :Enable town-accepted cargoes choosing destinations on their own +STR_CONFIG_SETTING_CARGODEST_OTHER :Destination mode for other cargoes: {STRING2} +STR_CONFIG_SETTING_CARGODEST_OTHER_HELPTEXT :Enable other cargoes choosing destinations on their own +STR_CONFIG_SETTING_CARGODEST_MODE_OFF :Original +STR_CONFIG_SETTING_CARGODEST_MODE_DEST :Fixed destinations STR_CONFIG_SETTING_GUI :{ORANGE}Interface STR_CONFIG_SETTING_CONSTRUCTION :{ORANGE}Construction @@ -1561,6 +1613,7 @@ STR_CONFIG_SETTING_INTERACTION :{ORANGE}Interac STR_CONFIG_SETTING_SOUND :{ORANGE}Sound effects STR_CONFIG_SETTING_NEWS :{ORANGE}News and messages STR_CONFIG_SETTING_CONSTRUCTION_SIGNALS :{ORANGE}Signals +STR_CONFIG_SETTING_CONSTRUCTION_TRAFFIC_LIGHTS :{ORANGE}Traffic lights STR_CONFIG_SETTING_STATIONS_CARGOHANDLING :{ORANGE}Cargo handling STR_CONFIG_SETTING_AI_NPC :{ORANGE}Computer players STR_CONFIG_SETTING_VEHICLES_AUTORENEW :{ORANGE}Autorenew @@ -2261,6 +2314,7 @@ STR_ROAD_TOOLBAR_TOOLTIP_BUILD_PASSENGER_TRAM_STATION :{BLACK}Build pa STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRUCK_LOADING_BAY :{BLACK}Build lorry loading bay. Ctrl enables joining stations. Shift toggles building/showing cost estimate STR_ROAD_TOOLBAR_TOOLTIP_BUILD_CARGO_TRAM_STATION :{BLACK}Build freight tram station. Ctrl enables joining stations. Shift toggles building/showing cost estimate STR_ROAD_TOOLBAR_TOOLTIP_TOGGLE_ONE_WAY_ROAD :{BLACK}Activate/Deactivate one way roads +STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAFFIC_LIGHT :{BLACK}Build traffic light STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_BRIDGE :{BLACK}Build road bridge. Shift toggles building/showing cost estimate STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAMWAY_BRIDGE :{BLACK}Build tramway bridge. Shift toggles building/showing cost estimate STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_TUNNEL :{BLACK}Build road tunnel. Shift toggles building/showing cost estimate @@ -2490,6 +2544,7 @@ STR_LAI_ROAD_DESCRIPTION_ROAD_WITH_STREETLIGHTS :Road with stree STR_LAI_ROAD_DESCRIPTION_TREE_LINED_ROAD :Tree-lined road STR_LAI_ROAD_DESCRIPTION_ROAD_VEHICLE_DEPOT :Road vehicle depot STR_LAI_ROAD_DESCRIPTION_ROAD_RAIL_LEVEL_CROSSING :Road/rail level crossing +STR_LAI_ROAD_DESCRIPTION_ROAD_WITH_TRAFFIC_LIGHTS :Road with traffic lights STR_LAI_ROAD_DESCRIPTION_TRAMWAY :Tramway # Houses come directly from their building names @@ -2518,8 +2573,10 @@ STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT :Ship depot # Industries come directly from their industry names STR_LAI_TUNNEL_DESCRIPTION_RAILROAD :Railway tunnel +STR_LAI_TUNNEL_DESCRIPTION_RAILROAD_SIGNAL :Railway tunnel with signal simulation STR_LAI_TUNNEL_DESCRIPTION_ROAD :Road tunnel +STR_LAI_BRIDGE_DESCRIPTION_RAILROAD_SIGNAL :Railway bridge with signal simulation STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_STEEL :Steel suspension rail bridge STR_LAI_BRIDGE_DESCRIPTION_RAIL_GIRDER_STEEL :Steel girder rail bridge STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL :Steel cantilever rail bridge @@ -2812,6 +2869,13 @@ STR_EDIT_SIGN_PREVIOUS_SIGN_TOOLTIP :{BLACK}Go to pr STR_EDIT_SIGN_SIGN_OSKTITLE :{BLACK}Enter a name for the sign +# Cargodest UI strings +STR_VIEW_CARGO_LAST_MONTH_OUT :{BLACK}Outgoing cargo last month: +STR_VIEW_CARGO_LAST_MONTH_TOWN :{BLACK}{CARGO_SHORT} out of {CARGO_LONG} to {TOWN} +STR_VIEW_CARGO_LAST_MONTH_INDUSTRY :{BLACK}{CARGO_SHORT} out of {CARGO_LONG} to {INDUSTRY} +STR_VIEW_CARGO_LAST_MONTH_LOCAL :{BLACK}{CARGO_SHORT} out of {CARGO_LONG} to local destinations +STR_VIEW_CARGO_LAST_MONTH_OTHER :{BLACK}{CARGO_SHORT} out of {CARGO_LONG} to other destinations + # Town directory window STR_TOWN_DIRECTORY_CAPTION :{WHITE}Towns STR_TOWN_DIRECTORY_NONE :{ORANGE}- None - @@ -2930,9 +2994,20 @@ STR_STATION_LIST_NO_WAITING_CARGO :{BLACK}No cargo # Station view window STR_STATION_VIEW_CAPTION :{WHITE}{STATION} {STATION_FEATURES} +STR_STATION_VIEW_WAITING_BUTTON :{BLACK}Source +STR_STATION_VIEW_WAITING_TOOLTIP :{BLACK}Show list of waiting cargo with its source +STR_STATION_VIEW_WAITING_TO_BUTTON :{BLACK}Destination +STR_STATION_VIEW_WAITING_TO_TOOLTIP :{BLACK}Show list of waiting cargo with its destination +STR_STATION_VIEW_WAITING_VIA_BUTTON :{BLACK}Next hop +STR_STATION_VIEW_WAITING_VIA_TOOLTIP :{BLACK}Show list of waiting cargo with its next hop +STR_STATION_VIEW_WAITING_TRANSFER_BUTTON :{BLACK}Transfer +STR_STATION_VIEW_WAITING_TRANSFER_TOOLTIP :{BLACK}Show list of waiting cargo with its next transfer station STR_STATION_VIEW_WAITING_TITLE :{BLACK}Waiting: {WHITE}{STRING} STR_STATION_VIEW_WAITING_CARGO :{WHITE}{CARGO_LONG} STR_STATION_VIEW_EN_ROUTE_FROM :{YELLOW}({CARGO_SHORT} en-route from {STATION}) +STR_STATION_VIEW_WAITING_VIA :{YELLOW}{CARGO_SHORT} en-route via {STATION} +STR_STATION_VIEW_WAITING_TRANSFER :{YELLOW}{CARGO_SHORT} transfering at {STATION} +STR_STATION_VIEW_WAITING_TO :{YELLOW}{CARGO_SHORT} en-route to {STRING1} STR_STATION_VIEW_ACCEPTS_BUTTON :{BLACK}Accepts STR_STATION_VIEW_ACCEPTS_TOOLTIP :{BLACK}Show list of accepted cargo @@ -2946,6 +3021,9 @@ STR_STATION_VIEW_RATINGS_TOOLTIP :{BLACK}Show sta STR_STATION_VIEW_CARGO_RATINGS_TITLE :{BLACK}Local rating of transport service: STR_STATION_VIEW_CARGO_RATING :{WHITE}{STRING}: {YELLOW}{STRING} ({COMMA}%) +STR_STATION_VIEW_DEPARTURES_BUTTON :{BLACK}Departures +STR_STATION_VIEW_DEPARTURES_TOOLTIP :{BLACK}Show list of scheduled departures + ############ range for rating starts STR_CARGO_RATING_APPALLING :Appalling STR_CARGO_RATING_VERY_POOR :Very Poor @@ -2966,10 +3044,102 @@ STR_STATION_VIEW_SCHEDULED_AIRCRAFT_TOOLTIP :{BLACK}Show all STR_STATION_VIEW_SCHEDULED_SHIPS_TOOLTIP :{BLACK}Show all ships which have this station on their schedule STR_STATION_VIEW_RENAME_STATION_CAPTION :Rename station/loading area - STR_STATION_VIEW_CLOSE_AIRPORT :{BLACK}Close airport STR_STATION_VIEW_CLOSE_AIRPORT_TOOLTIP :{BLACK}Prevent aircraft from landing on this airport +# Departures window +STR_DEPARTURES_CAPTION :{WHITE}{STATION} Live Travel Information +STR_DEPARTURES_CAPTION_WAYPOINT :{WHITE}{WAYPOINT} Live Travel Information +STR_DEPARTURES_DEPARTURES :{BLACK}{TINY_FONT}D +STR_DEPARTURES_ARRIVALS :{BLACK}{TINY_FONT}A +STR_DEPARTURES_VIA_BUTTON :{BLACK}{TINY_FONT}via +STR_DEPARTURES_DEPARTURES_TOOLTIP :{BLACK}Show timetabled departures +STR_DEPARTURES_ARRIVALS_TOOLTIP :{BLACK}Show timetabled arrivals +STR_DEPARTURES_VIA_TOOLTIP :{BLACK}Show timetabled vehicles that do not stop here +STR_DEPARTURES_EMPTY :{ORANGE}No vehicles are currently timetabled for this station. +STR_DEPARTURES_NONE_SELECTED :{ORANGE}No timetable information has been requested. +STR_DEPARTURES_TIME :{ORANGE}{DATE_WALLCLOCK_TINY} +STR_DEPARTURES_TIME_DEP :{ORANGE}{DATE_WALLCLOCK_TINY} {GREEN}{UP_ARROW} +STR_DEPARTURES_TIME_ARR :{ORANGE}{DATE_WALLCLOCK_TINY} {RED}{DOWN_ARROW} +STR_DEPARTURES_TIME_BOTH :{ORANGE}{1:DATE_WALLCLOCK_TINY} {RED}{DOWN_ARROW} {ORANGE}{0:DATE_WALLCLOCK_TINY} {GREEN}{UP_ARROW} +STR_DEPARTURES_TERMINUS :{ORANGE}{STATION}{STRING} +STR_DEPARTURES_TERMINUS_VIA_STATION :{ORANGE}{STATION}{STRING} via {STATION}{STRING} +STR_DEPARTURES_TERMINUS_VIA :{ORANGE}{STATION}{STRING} via +STR_DEPARTURES_VIA :{ORANGE}via {STATION}{STRING} +STR_DEPARTURES_TOC :{ORANGE}{COMPANY} +STR_DEPARTURES_GROUP :{ORANGE}{GROUP} +STR_DEPARTURES_VEH :{ORANGE}{VEHICLE} +STR_DEPARTURES_CALLING_AT :{TINY_FONT}{ORANGE}Calling at: +STR_DEPARTURES_CALLING_AT_LARGE :{ORANGE}Calling at: +STR_DEPARTURES_CALLING_AT_FIRST_STATION :{STATION} +STR_DEPARTURES_CALLING_AT_STATION :{RAW_STRING}, {STATION} +STR_DEPARTURES_CALLING_AT_LAST_STATION :{RAW_STRING} and {STATION} +STR_DEPARTURES_CALLING_AT_LIST :{TINY_FONT}{ORANGE}{RAW_STRING}. +STR_DEPARTURES_CALLING_AT_LIST_LARGE :{ORANGE}{RAW_STRING}. +STR_DEPARTURES_CALLING_AT_LIST_SMART_TERMINUS :{TINY_FONT}{ORANGE}{RAW_STRING}. This service continues to {STATION}. +STR_DEPARTURES_CALLING_AT_LIST_SMART_TERMINUS_LARGE :{ORANGE}{RAW_STRING}. This service continues to {STATION}. + +STR_DEPARTURES_TYPE_TRAIN :{ORANGE}{TRAIN} +STR_DEPARTURES_TYPE_TRAIN_SILVER :{SILVER}{TRAIN} +STR_DEPARTURES_TYPE_BUS :{ORANGE}{BUS} +STR_DEPARTURES_TYPE_BUS_SILVER :{SILVER}{BUS} +STR_DEPARTURES_TYPE_LORRY :{ORANGE}{LORRY} +STR_DEPARTURES_TYPE_LORRY_SILVER :{SILVER}{LORRY} +STR_DEPARTURES_TYPE_PLANE :{ORANGE}{PLANE} +STR_DEPARTURES_TYPE_PLANE_SILVER :{SILVER}{PLANE} +STR_DEPARTURES_TYPE_SHIP :{ORANGE}{SHIP} +STR_DEPARTURES_TYPE_SHIP_SILVER :{SILVER}{SHIP} + +STR_DEPARTURES_STATION_NONE : +STR_DEPARTURES_STATION_PORT :{ORANGE} {SHIP} +STR_DEPARTURES_STATION_AIRPORT :{ORANGE} {PLANE} +STR_DEPARTURES_STATION_PORTAIRPORT :{ORANGE} {SHIP} {PLANE} + +############ possible statuses start +STR_DEPARTURES_ON_TIME :{GREEN}On time +STR_DEPARTURES_ARRIVED :{GREEN}Arrived +STR_DEPARTURES_DELAYED :{YELLOW}Delayed +STR_DEPARTURES_EXPECTED :{YELLOW}Expt {DATE_WALLCLOCK_TINY} +STR_DEPARTURES_CANCELLED :{RED}Cancelled + +############ config settings +STR_CONFIG_SETTING_DEPARTUREBOARDS :{ORANGE}Departure boards +STR_CONFIG_MAX_DEPARTURES :Show at most {STRING2} departures at each station +STR_CONFIG_MAX_DEPARTURES_HELPTEXT :The maximum number of departures to show on a departure board +STR_CONFIG_MAX_DEPARTURE_TIME :Show departures at most {STRING2} days in advance +STR_CONFIG_MAX_DEPARTURE_TIME_HELPTEXT :How far in advance to show departures, in days +STR_CONFIG_DEPARTURE_CALC_FREQUENCY :Calculate departures every {STRING2} ticks +STR_CONFIG_DEPARTURE_CALC_FREQUENCY_HELPTEXT :How frequently to refresh a list of departures, in ticks +STR_CONFIG_DEPARTURE_VEHICLE_NAME :Show vehicle name with departures: {STRING2} +STR_CONFIG_DEPARTURE_VEHICLE_NAME_HELPTEXT :Whether to show vehicle names next to their departures +STR_CONFIG_DEPARTURE_GROUP_NAME :Show group name with departures: {STRING2} +STR_CONFIG_DEPARTURE_GROUP_NAME_HELPTEXT :Whether to show names of groups that a vehicle belongs to next to its departures +STR_CONFIG_DEPARTURE_COMPANY_NAME :Show company name with departures: {STRING2} +STR_CONFIG_DEPARTURE_COMPANY_NAME_HELPTEXT :Whether to show a company's name next to its vehicles' departures +STR_CONFIG_DEPARTURE_VEHICLE_TYPE :Show vehicle type icon with departures: {STRING2} +STR_CONFIG_DEPARTURE_VEHICLE_TYPE_HELPTEXT :Whether to show a vehicle's type as an icon next to its departures +STR_CONFIG_DEPARTURE_VEHICLE_COLOR :Show vehicle type icon in silver: {STRING2} +STR_CONFIG_DEPARTURE_VEHICLE_COLOR_HELPTEXT :Whether to show vehicle type icons in silver +STR_CONFIG_DEPARTURE_LARGER_FONT :Use larger font for stations called at on departure boards: {STRING2} +STR_CONFIG_DEPARTURE_LARGER_FONT_HELPTEXT :Whether to use a larger font for lists of stations called at +STR_CONFIG_DEPARTURE_DESTINATION_TYPE :Show icons for destinations that are docks or airports: {STRING2} +STR_CONFIG_DEPARTURE_DESTINATION_TYPE_HELPTEXT :Whether to show icons next to destinations that are docks or airports +STR_CONFIG_DEPARTURE_SHOW_BOTH :Show arrival and departure times on the same line: {STRING2} +STR_CONFIG_DEPARTURE_SHOW_BOTH_HELPTEXT :Whether to show both arrival and departure times next to departures +STR_CONFIG_DEPARTURE_ONLY_PASSENGERS :Only show departures for vehicles that can carry passengers: {STRING2} +STR_CONFIG_DEPARTURE_ONLY_PASSENGERS_HELPTEXT :Whether to only show departures of vehicles that can carry passengers +STR_CONFIG_DEPARTURE_SMART_TERMINUS :Don't show termini that can be reached sooner on a later vehicle: {STRING2} +STR_CONFIG_DEPARTURE_SMART_TERMINUS_HELPTEXT :Whether to show termini that can be reached sooner on another vehicle that departs later +STR_CONFIG_DEPARTURE_CONDITIONALS :Handle conditional order jumps by: {STRING2} +STR_CONFIG_DEPARTURE_CONDITIONALS_HELPTEXT :How conditional orders should be dealt with when calculating departures +STR_CONFIG_DEPARTURE_CONDITIONALS_1 :giving up +STR_CONFIG_DEPARTURE_CONDITIONALS_2 :assuming they will be taken +STR_CONFIG_DEPARTURE_CONDITIONALS_3 :assuming they will not be taken +STR_CONFIG_DEPARTURE_SHOW_ALL_STOPS :Show all stations called at regardless of loading/unloading: {STRING2} +STR_CONFIG_DEPARTURE_SHOW_ALL_STOPS_HELPTEXT :Whether stations that a vehicle only loads from will be shown in the calling at list +STR_CONFIG_DEPARTURE_MERGE_IDENTICAL :Merge identical departures: {STRING2} +STR_CONFIG_DEPARTURE_MERGE_IDENTICAL_HELPTEXT :Whether identical departures should be merged into a single departure + # Waypoint/buoy view window STR_WAYPOINT_VIEW_CAPTION :{WHITE}{WAYPOINT} STR_WAYPOINT_VIEW_CENTER_TOOLTIP :{BLACK}Centre main view on waypoint location. Ctrl+Click opens a new viewport on waypoint location @@ -3472,6 +3642,7 @@ STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY_MULT :{LTBLUE}- {CARG STR_VEHICLE_DETAILS_CARGO_EMPTY :{LTBLUE}Empty STR_VEHICLE_DETAILS_CARGO_FROM :{LTBLUE}{CARGO_LONG} from {STATION} STR_VEHICLE_DETAILS_CARGO_FROM_MULT :{LTBLUE}{CARGO_LONG} from {STATION} (x{NUM}) +STR_VEHICLE_DETAILS_CARGO_TO :{LTBLUE}{CARGO_SHORT} to {STRING1} STR_VEHICLE_DETAIL_TAB_CARGO :{BLACK}Cargo STR_VEHICLE_DETAILS_TRAIN_CARGO_TOOLTIP :{BLACK}Show details of cargo carried @@ -3680,6 +3851,7 @@ STR_TIMETABLE_STAY_FOR :and stay for {S STR_TIMETABLE_AND_TRAVEL_FOR :and travel for {STRING1} STR_TIMETABLE_DAYS :{COMMA} day{P "" s} STR_TIMETABLE_TICKS :{COMMA} tick{P "" s} +STR_TIMETABLE_MINUTES :{COMMA} minute{P "" s} STR_TIMETABLE_TOTAL_TIME :{BLACK}This timetable will take {STRING1} to complete STR_TIMETABLE_TOTAL_TIME_INCOMPLETE :{BLACK}This timetable will take at least {STRING1} to complete (not all timetabled) @@ -3726,6 +3898,8 @@ STR_DATE_SET_DATE_TOOLTIP :{BLACK}Use the STR_DATE_DAY_TOOLTIP :{BLACK}Select day STR_DATE_MONTH_TOOLTIP :{BLACK}Select month STR_DATE_YEAR_TOOLTIP :{BLACK}Select year +STR_DATE_MINUTES_DAY_TOOLTIP :{BLACK}Select minute +STR_DATE_MINUTES_MONTH_TOOLTIP :{BLACK}Select hour # AI debug window @@ -3830,6 +4004,27 @@ STR_FEEDER :{YELLOW}Transfe STR_MESSAGE_ESTIMATED_COST :{WHITE}Estimated Cost: {CURRENCY_LONG} STR_MESSAGE_ESTIMATED_INCOME :{WHITE}Estimated Income: {CURRENCY_LONG} +# Timetable separation UI strings +STR_TTSEPARATION_AUTO :Auto +STR_TTSEPARATION_MAN_NUM :Manual Number +STR_TTSEPARATION_MAN_TIME :Manual Time +STR_TTSEPARATION_BUFFERED_AUTO :Buffered Auto +STR_TTSEPARATION_OFF :Off +STR_TTSEPARATION_APPLY :{BLACK}Apply +STR_TTSEPARATION_RESET :{BLACK}Reset +STR_TTSEPARATION_MODE_DESC :{BLACK}Separation mode +STR_TTSEPARATION_SETTINGS_DESC :{BLACK}Separation settings +STR_TTSEPARATION_SET_XX :{BLACK}Set {STRING} +STR_TTSEPARATION_SET_NUM :number +STR_TTSEPARATION_SET_TIME :time +STR_TTSEPARATION_STATUS_DESC :{BLACK}Status:{}{STRING} +STR_TTSEPARATION_STATUS_INIT :{RED}Initializing +STR_TTSEPARATION_STATUS_RUNNING :{GREEN}Running +STR_TTSEPARATION_STATUS_OFF :{BLACK}Off +STR_TTSEPARATION_REQ_TIME_DESC_TICKS :{NUM} ticks between +STR_TTSEPARATION_REQ_TIME_DESC_DAYS :{NUM} days between +STR_TTSEPARATION_REQ_NUM_DESC :{NUM} vehicles + # Saveload messages STR_ERROR_SAVE_STILL_IN_PROGRESS :{WHITE}Saving still in progress,{}please wait until it is finished! STR_ERROR_AUTOSAVE_FAILED :{WHITE}Autosave failed @@ -4071,6 +4266,13 @@ STR_ERROR_CAN_T_BUILD_ROAD_HERE :{WHITE}Can't bu STR_ERROR_CAN_T_BUILD_TRAMWAY_HERE :{WHITE}Can't build tramway here... STR_ERROR_CAN_T_REMOVE_ROAD_FROM :{WHITE}Can't remove road from here... STR_ERROR_CAN_T_REMOVE_TRAMWAY_FROM :{WHITE}Can't remove tramway from here... +STR_ERROR_CAN_T_REMOVE_TRAFFIC_LIGHTS_FROM :{WHITE}Can't remove traffic lights from here... +STR_ERROR_MUST_REMOVE_TRAFFIC_LIGHTS_FIRST :{WHITE}... must remove traffic lights first +STR_ERROR_TRAFFIC_LIGHT_CONSIST_TOO_BIG :{WHITE}... traffic light consist too big +STR_ERROR_CAN_T_PLACE_TRAFFIC_LIGHTS :{WHITE}Can't place traffic lights... +STR_ERROR_CAN_ONLY_BE_PLACED_ON_ROAD_JUNCTIONS :{WHITE}... can only be placed on road junctions +STR_ERROR_BUILDING_TRAFFIC_LIGHTS_DISABLED :{WHITE}... building trafficlights is disabled, enable it in Advanced settings -> Construction -> Traffic lights +STR_ERROR_TRAFFIC_LIGHTS_NOT_ALLOWED_ON_TOWN_ROADS :{WHITE}... building trafficlights on town roads is disabled, enable it in Advanced settings -> Construction -> Traffic lights STR_ERROR_THERE_IS_NO_ROAD :{WHITE}... there is no road STR_ERROR_THERE_IS_NO_TRAMWAY :{WHITE}... there is no tramway @@ -4602,6 +4804,11 @@ STR_FORMAT_DATE_SHORT :{STRING} {NUM} STR_FORMAT_DATE_LONG :{STRING} {STRING} {NUM} STR_FORMAT_DATE_ISO :{2:NUM}-{1:RAW_STRING}-{0:RAW_STRING} +STR_FORMAT_DATE_MINUTES :{0:RAW_STRING}:{1:RAW_STRING} +STR_FORMAT_DATE_MINUTES_WITH_Y :{0:RAW_STRING}:{1:RAW_STRING} / {2:NUM} +STR_FORMAT_DATE_MINUTES_WITH_YM :{0:RAW_STRING}:{1:RAW_STRING} / {2:DATE_SHORT} +STR_FORMAT_DATE_MINUTES_WITH_YMD :{0:RAW_STRING}:{1:RAW_STRING} / {2:DATE_LONG} + STR_FORMAT_BUOY_NAME :{TOWN} Buoy STR_FORMAT_BUOY_NAME_SERIAL :{TOWN} Buoy #{COMMA} STR_FORMAT_COMPANY_NUM :(Company {COMMA}) @@ -4665,6 +4872,10 @@ STR_JUST_DATE_TINY :{DATE_TINY} STR_JUST_DATE_SHORT :{DATE_SHORT} STR_JUST_DATE_LONG :{DATE_LONG} STR_JUST_DATE_ISO :{DATE_ISO} +STR_JUST_DATE_WALLCLOCK_TINY :{DATE_WALLCLOCK_TINY} +STR_JUST_DATE_WALLCLOCK_SHORT :{DATE_WALLCLOCK_SHORT} +STR_JUST_DATE_WALLCLOCK_LONG :{DATE_WALLCLOCK_LONG} +STR_JUST_DATE_WALLCLOCK_ISO :{DATE_WALLCLOCK_ISO} STR_JUST_STRING :{STRING} STR_JUST_STRING_STRING :{STRING}{STRING} STR_JUST_RAW_STRING :{RAW_STRING} @@ -4682,6 +4893,8 @@ STR_BLACK_DATE_LONG :{BLACK}{DATE_LO STR_BLACK_CROSS :{BLACK}{CROSS} STR_SILVER_CROSS :{SILVER}{CROSS} STR_WHITE_DATE_LONG :{WHITE}{DATE_LONG} +STR_WHITE_DATE_WALLCLOCK_LONG :{WHITE}{DATE_WALLCLOCK_LONG} +STR_WHITE_DATE_WALLCLOCK_SHORT :{WHITE}{DATE_WALLCLOCK_SHORT} STR_SHORT_DATE :{WHITE}{DATE_TINY} STR_DATE_LONG_SMALL :{TINY_FONT}{BLACK}{DATE_LONG} STR_TINY_GROUP :{TINY_FONT}{GROUP} @@ -4717,3 +4930,35 @@ STR_PLANE :{BLACK}{PLANE} STR_SHIP :{BLACK}{SHIP} STR_TOOLBAR_RAILTYPE_VELOCITY :{STRING} ({VELOCITY}) + +# underground + +# error +STR_ERROR_UNDERGROUND_CAN_T_BUILD_UNDER_GROUND :{WHITE}Can't build this underground +STR_ERROR_UNDERGROUND_CAN_T_BUILD_OVER_GROUND :{WHITE}Can't build this on surface +STR_ERROR_UNDERGROUND_CAN_T_TERRAFORM :{WHITE}Can't terraform underground +STR_ERROR_UNDERGROUND_CAN_T_BUILD_PART :{WHITE}Can't build escalator here... +STR_ERROR_UNDERGROUND_CAN_T_BUILD_TOP_PART :{WHITE}Can't build escalator top here... +STR_ERROR_UNDERGROUND_CAN_T_BUILD_BOTTOM_PART :{WHITE}Can't build escalator bottom here... + +# menus +STR_UNDERGROUND_LAYER_COUNT :{BLACK}Layers count +STR_UNDERGROUND_BUILD :{WHITE}Underground + +# underground land types +STR_UNDERGROUND_FLAT :Cave +STR_UNDERGROUND_SOLID :Ground +STR_UNDERGROUND_ROCKS :Rocks +STR_UNDERGROUND_HEAVY_ROCKS :Hard rocks + +# underground special build types +STR_UNDERGROUND_ESCALATOR :Escalator +STR_UNDERGROUND_CONNECT :Connect +STR_UNDERGROUND_PIPE :Pipe + +# underground tool tip +STR_UNDERGROUND_TOOLTIP_ESCALATOR :{BLACK}Build escalator (underground station connect with surface) +STR_UNDERGROUND_TOOLTIP_CONNECT :{BLACK}Build connect (underground track connect with surface) +STR_UNDERGROUND_TOOLTIP_PIPE :{BLACK}Pipe (xm... anything...) + +# end underground string diff --git a/src/lang/hungarian.txt b/src/lang/hungarian.txt index c22dc927e..c54ddcff0 100644 --- a/src/lang/hungarian.txt +++ b/src/lang/hungarian.txt @@ -3010,6 +3010,35 @@ STR_STATION_VIEW_RATINGS_TOOLTIP :{BLACK}VГ©lemГ© STR_STATION_VIEW_CARGO_RATINGS_TITLE :{BLACK}A szГЎllГtГЎs helyi megГtГ©lГ©se: STR_STATION_VIEW_CARGO_RATING :{WHITE}{STRING}: {YELLOW}{STRING} ({COMMA}%) +STR_STATION_VIEW_DEPARTURES_BUTTON :{BLACK}Menetrend +STR_STATION_VIEW_DEPARTURES_TOOLTIP :{BLACK}Mutasd az ГЎllomГЎs menetrendjГ©t + +# Departures window +STR_DEPARTURES_CAPTION :{WHITE}{STATION} AktuГЎlis menetrend +STR_DEPARTURES_CAPTION_WAYPOINT :{WHITE}{WAYPOINT} AktuГЎlis menetrend +STR_DEPARTURES_DEPARTURES :{BLACK}{TINY_FONT}Ind +STR_DEPARTURES_ARRIVALS :{BLACK}{TINY_FONT}Г‰rk +STR_DEPARTURES_VIA_BUTTON :{BLACK}{TINY_FONT}ГЃth +STR_DEPARTURES_DEPARTURES_TOOLTIP :{BLACK}Mutasd az indulГЎsi idЕ‘pontokat +STR_DEPARTURES_ARRIVALS_TOOLTIP :{BLACK}Mutasd az Г©rkezГ©si idЕ‘pontokat +STR_DEPARTURES_VIA_TOOLTIP :{BLACK}Mutasd a keresztГјlhaladГі forgalmat +STR_DEPARTURES_EMPTY :{ORANGE}Ez az ГЎllomГЎs mГ©g nincs benne egy jГЎrmЕ± menetrendjГ©ben sem. +STR_DEPARTURES_NONE_SELECTED :{ORANGE}Nincs megjelenГthetЕ‘ menetrend + +############ possible statuses start +STR_DEPARTURES_ON_TIME :{GREEN}IdЕ‘ben +STR_DEPARTURES_ARRIVED :{GREEN}MegГ©rkezett +STR_DEPARTURES_DELAYED :{YELLOW}KГ©sik +STR_DEPARTURES_EXPECTED :{YELLOW}VГЎrhatГі {DATE_WALLCLOCK_TINY} +STR_DEPARTURES_CANCELLED :{RED}TГ¶rГ¶lve + +############ config settings +STR_CONFIG_SETTING_DEPARTUREBOARDS :{ORANGE}Menetrendek +STR_TIMETABLE_MINUTES :{COMMA} perc +STR_DATE_MINUTES_DAY_TOOLTIP :{BLACK}VГЎlassz percet +STR_DATE_MINUTES_MONTH_TOOLTIP :{BLACK}VГЎlassz ГіrГЎt + + ############ range for rating starts STR_CARGO_RATING_APPALLING :szГ¶rnyЕ± STR_CARGO_RATING_VERY_POOR :nagyon rossz diff --git a/src/lang/russian.txt b/src/lang/russian.txt index 68b2f76ab..768ea7011 100644 --- a/src/lang/russian.txt +++ b/src/lang/russian.txt @@ -557,6 +557,13 @@ STR_AIRCRAFT_MENU_AIRPORT_CONSTRUCTION :СтроитеРSTR_LANDSCAPING_MENU_LANDSCAPING :Ландшафт STR_LANDSCAPING_MENU_PLANT_TREES :Высадка деревьев STR_LANDSCAPING_MENU_PLACE_SIGN :Поставить метку +STR_LANDSCAPING_MENU_SEPARATOR1 : +STR_LANDSCAPING_MENU_UNDERGROUND :Спец. конструкции +STR_LANDSCAPING_MENU_SEPARATOR2 : +STR_LANDSCAPING_MENU_LAYER_1 :Поверхность +STR_LANDSCAPING_MENU_LAYER_2 :Подземелье (-1) +STR_LANDSCAPING_MENU_LAYER_3 :Подземелье (-2) +STR_LANDSCAPING_MENU_LAYER_4 :Подземелье (-3) ############ range ends here ############ range for music menu starts @@ -4940,3 +4947,35 @@ STR_PLANE :{BLACK}{PLANE} STR_SHIP :{BLACK}{SHIP} STR_TOOLBAR_RAILTYPE_VELOCITY :{STRING} ({VELOCITY}) + +# underground + +# error +STR_ERROR_UNDERGROUND_CAN_T_BUILD_UNDER_GROUND :{WHITE}РќРµ может строиться РїРѕРґ землей +STR_ERROR_UNDERGROUND_CAN_T_BUILD_OVER_GROUND :{WHITE}РќРµ может строиться РЅР° поверхности +STR_ERROR_UNDERGROUND_CAN_T_TERRAFORM :{WHITE}Рзменение ландшафта недоступно +STR_ERROR_UNDERGROUND_CAN_T_BUILD_PART :{WHITE}Нельзя построить эскалатор... +STR_ERROR_UNDERGROUND_CAN_T_BUILD_TOP_PART :{WHITE}Нельзя построить верхнюю часть эскалатора... +STR_ERROR_UNDERGROUND_CAN_T_BUILD_BOTTOM_PART :{WHITE}Нельзя построить нижнюю часть эскалатора... + +# menus +STR_UNDERGROUND_LAYER_COUNT :{BLACK}Количество слоев +STR_UNDERGROUND_BUILD :{WHITE}Подземелье + +# underground land types +STR_UNDERGROUND_FLAT :Пещера +STR_UNDERGROUND_SOLID :Грунт +STR_UNDERGROUND_ROCKS :Скалы +STR_UNDERGROUND_HEAVY_ROCKS :Твердые РїРѕСЂРѕРґС‹ + +# underground special build types +STR_UNDERGROUND_ESCALATOR :Рскалатор +STR_UNDERGROUND_CONNECT :Соединение +STR_UNDERGROUND_PIPE :РўСЂСѓР±С‹ + +# underground tool tip +STR_UNDERGROUND_TOOLTIP_ESCALATOR :{BLACK}Постройка эскалатора (СЃРІСЏР·СЊ подземной станции СЃ поверхностью) +STR_UNDERGROUND_TOOLTIP_CONNECT :{BLACK}Постройка подъема (подземные рельсы выходят наружу) +STR_UNDERGROUND_TOOLTIP_PIPE :{BLACK}РўСЂСѓР±С‹ (С…Рј... что-то...) + +# end underground string diff --git a/src/layer.cpp b/src/layer.cpp new file mode 100644 index 000000000..b897c45a3 --- /dev/null +++ b/src/layer.cpp @@ -0,0 +1,137 @@ +/* $Id: map.cpp 23740 2012-01-03 21:32:51Z $ */ +/* + +Это модуль, для возможности полноценной игры в трех измерениях +(подземелье, метро, итп.) + + +Плоское игровое поле выглядит примерно так: +*-----------* +| | +| | +| | +*-----------* + +Мысленно нарежем его на одинаковые части: +*---*---*---* +| | | | +| | | | +| | | | +*---*---*---* + +Мысленно соберем части в вертикальную стопочку: +*---* +| *---* +| | *---* +| | | | +* | | | + * | | + *---* + +Таким образом имея плоскую карту мы описываем 3д пространство. + +Для простоты вся карта делится только по оси Y (разрезы параллельны оси X) +Деление происходит на 1, 2, 4, или 8 кусочков. + +Например было выбрано поле 64х64, с 4-мя слоями: +Создается карта 64х256, подразумевается, что: + + X Y + верхний слой 0--63 0--63 + второй слой 0--63 64--127 + третий слой 0--63 128--191 + четвертый слой 0--63 192--255 + + +Обычная карта + MapSizeX х (MapSizeY) + +Представление в виде слоев + LayerSizeX x (LayerSizeY x LayerCount) + +Иными словами игровые координаты "плоского" пространства + + MapX, MapY, MapZ + +переходят в координаты нового "3д" пространства + + // Константы + LayerCount = число кусочков... (1, или 2, или 4, итд.) + LayerSizeZ = сдвиг слоя по вертикали (например 1) + + // Аксиомы + MapSizeX == LayerSizeX + MapSizeY == LayerSizeY * LayerCount + + // Расчет координат + LayerIndex = MapY / LayerSizeY + + WorldX = MapX + WorldY = MapY - LayerIndex*LayerSizeY + WorldZ = MapZ + LayerIndex*LayerSizeZ +*/ + +/** @file map.cpp Base functions related to the map and distances on them. */ + +#include "stdafx.h" +#include "debug.h" +#include "core/alloc_func.hpp" +#include "void_map.h" +#include "layer_func.h" +#include "layer_type.h" +#include "landscape.h" + +#if defined(_MSC_VER) +/* Why the hell is that not in all MSVC headers?? */ +extern "C" _CRTIMP void __cdecl _assert(void *, void *, unsigned); +#endif + +uint _layer_size_x; ///< Size of the map along the X +uint _layer_size_y; ///< Size of the map along the Y +uint _layer_count; ///< The number of tiles on the map +uint _layer_count_log; +uint _layer_size; ///< Layer size (sizeX * sizeY) + +void InstallLayerSystem(uint size_x, uint size_y, uint layer_count) +{ + if (!IsInsideMM(layer_count, MIN_LAYER_COUNT, MAX_LAYER_COUNT)) + error("invalid layer count"); + + _layer_size_x = size_x; + _layer_size_y = size_y; + _layer_size = size_x * size_y; + _layer_count = layer_count; + _layer_count_log = FindFirstBit(layer_count); +} + +void FixUndergroundHeights() +{ + uint width = MapSizeX(); + uint height = MapSizeY(); + + /* Layer correct */ + for (uint row = 0; (uint)row < height; row++) { + + /* Граница между слоями */ + if (!(row % LayerSizeY())) + for (uint x = 0; x < width; x++) MakeVoid(width * row + x); + + for (uint col = 0; (uint)col < width; col++) { + uint tile = TileXY(row, col); + if (IsUnderground(tile)) + SetTileHeight(tile, 0); + } + } +} + +uint8 calculateLayer(const ViewPort *vp) +{ + // Функция ViewportDoDraw вызывается несколько раз с разными параметрами + // Нужно же найти только один слой. + // Опираемся на вьюпорт. + + Point pt = InverseRemapCoords(vp->virtual_left+(vp->virtual_width >> 1),vp->virtual_top+(vp->virtual_height >> 1)); + TileIndex center = TileVirtXY(pt.x, pt.y); + return LayerIndex(center); +} + diff --git a/src/layer_func.h b/src/layer_func.h new file mode 100644 index 000000000..250e79a3d --- /dev/null +++ b/src/layer_func.h @@ -0,0 +1,159 @@ +/* $Id: layer_func.h 2012-09-07 18:11:11 constructor $ */ + +/* +* Подробое описание см. в layer.cpp +*/ + +/** @file layer_func.h Functions related to layer in maps. */ + +#ifndef LAYER_FUNC_H +#define LAYER_FUNC_H + +#include "map_func.h" +#include "viewport_type.h" + +/* +* +* Инициализация "подземелий" +* Количество слоев "1" равносильно игре без "подземелий" +* +*/ +void InstallLayerSystem(uint size_x, uint size_y, uint layer_count); + +/* Корректировка "подземных" слоев +* (в будущем слои могут менять высоты -- в пределах соседей) */ +void FixUndergroundHeights(); + +#define FOR_ALL_LAYERS(var) for (uint var = 0; var < LayerCount(); var++) + + +/** + * Get the size of the layer along the X + * @return the number of tiles along the X of the layer + */ +static inline uint LayerSizeX() +{ + extern uint _map_size_x; + return _map_size_x; +} + +/** + * Get the size of the layer along the Y + * @return the number of tiles along the Y of the layer + */ +static inline uint LayerSizeY() +{ + extern uint _layer_size_y; + return _layer_size_y; +} + +/** + * Gets the maximum X coordinate within the map, including MP_VOID + * @return the maximum X coordinate + */ +static inline uint LayerMaxX() +{ + return LayerSizeX() - 1; +} + +/** + * Gets the maximum Y coordinate within the map, including MP_VOID + * @return the maximum Y coordinate + */ +static inline uint LayerMaxY() +{ + return LayerSizeY() - 1; +} + +/** + * Get the layer counts + * @return the number of layers + */ +static inline uint LayerCount() +{ + extern uint _layer_count; + return _layer_count; +} + +/** + * Get the layer counts + * @return the number of layers + */ +static inline uint LayerCountLog() +{ + extern uint _layer_count_log; + return _layer_count_log; +} + +/** + * Get the X component of a tile + * @param tile the tile to get the X component of + * @return the X component + */ +static inline uint LayerX(TileIndex tile) +{ + return tile & LayerMaxX(); +} + +/** + * Get the Y component of a tile + * @param tile the tile to get the Y component of + * @return the Y component + */ +static inline uint LayerY(TileIndex tile) +{ + return (tile >> MapLogX()) & LayerMaxY(); +} + +static inline uint LayerIndex(TileIndex tile) +{ + return (tile >> MapLogX()) / LayerSizeY(); +} + +static inline bool IsUnderground(TileIndex tile) +{ + return LayerIndex(tile) != 0; +} + +/** +* Размер слоя. +* Можно прибавить к клетке, чтобы получить клетку ниже +*/ +static inline uint LayerSize() +{ + extern uint _layer_size; + return _layer_size; +} + +/** + * Ищем клетку поверхности для данной (самую верхнюю клетку) + * @param tile the tile to get the Y component of + * @return the Y component + */ +static inline uint TopTile(TileIndex tile) +{ + uint layer = LayerIndex(tile); + return (tile - layer * LayerSize()); +} + +/* Определить верхняя ли клеточка очень просто */ +static inline bool IsTopTile(TileIndex tile) +{ + return (tile < LayerSize()); +} + +/* Ищет клетку над данной. (Для самой верхней вернет нижнюю??) +*/ +static inline uint UpTile(TileIndex tile) +{ + return TILE_MASK(tile - LayerSize()); +} + +/* Ищет клетку под данной. (Для самой нижней вернет верхнюю??) +*/ +static inline uint DownTile(TileIndex tile) +{ + return TILE_MASK(tile + LayerSize()); +} + +#endif /* LAYER_FUNC_H */ diff --git a/src/layer_gui.h b/src/layer_gui.h new file mode 100644 index 000000000..303fa32a3 --- /dev/null +++ b/src/layer_gui.h @@ -0,0 +1,17 @@ +/* $Id: layer_func.h 2012-09-07 18:11:11 constructor $ */ + +/* +* Подробое описание см. в layer.cpp +*/ + +/** @file layer_gui.h Functions for visualisation map with support layers */ + +#ifndef LAYER_GUI_H +#define LAYER_GUI_H + +#include "layer_func.h" +#include "viewport_type.h" + +uint8 calculateLayer(const ViewPort *vp); + +#endif /* LAYER_GUI_H */ diff --git a/src/layer_type.h b/src/layer_type.h new file mode 100644 index 000000000..53eafdde6 --- /dev/null +++ b/src/layer_type.h @@ -0,0 +1,22 @@ +/* $Id: layer_type.h 21493 2012-09-11 2:21:53Z constructor $ */ + +/* + * 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 layer_type.h Types related to maps. */ + +#ifndef LAYER_TYPE_H +#define LAYER_TYPE_H + +/** Minimal and maximal layer counts */ +static const uint MIN_LAYER_COUNT_BITS = 0; ///< Minimal size of map is equal to 2 ^ MIN_LAYER_SIZE_BITS +static const uint MAX_LAYER_COUNT_BITS = 3; ///< Maximal size of map is equal to 2 ^ MAX_LAYER_SIZE_BITS +static const uint MIN_LAYER_COUNT = 1 << MIN_LAYER_COUNT_BITS; ///< Minimal layer count = 1 +static const uint MAX_LAYER_COUNT = 1 << MAX_LAYER_COUNT_BITS; ///< Maximal layer count = 8 + + +#endif /* LAYER_TYPE_H */ diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 5cced077f..7e8ae90f6 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp @@ -104,7 +104,7 @@ bool HandlePlacePushButton(Window *w, int widget, CursorID cursor, HighLightStyl if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); w->SetDirty(); - if (w->IsWidgetLowered(widget)) { + if (w->IsWidgetLowered(widget) && mode == _thd.place_mode) { ResetObjectToPlace(); return false; } diff --git a/src/map.cpp b/src/map.cpp index 1ee0ba247..cc62390db 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -13,6 +13,7 @@ #include "debug.h" #include "core/alloc_func.hpp" #include "water_map.h" +#include "layer_func.h" #if defined(_MSC_VER) /* Why the hell is that not in all MSVC headers?? */ @@ -35,7 +36,7 @@ TileExtended *_me = NULL; ///< Extended Tiles of the map * @param size_x the width of the map along the NE/SW edge * @param size_y the 'height' of the map along the SE/NW edge */ -void AllocateMap(uint size_x, uint size_y) +void AllocateMap(uint size_x, uint size_y, uint layer_count) { /* Make sure that the map size is within the limits and that * size of both axes is a power of 2. */ @@ -46,6 +47,10 @@ void AllocateMap(uint size_x, uint size_y) error("Invalid map size"); } + /* Поскольку слои часть карты, включаем их здесь */ + InstallLayerSystem(size_x, size_y, layer_count); + size_y *= layer_count; + DEBUG(map, 1, "Allocating map of size %dx%d", size_x, size_y); _map_log_x = FindFirstBit(size_x); diff --git a/src/map_func.h b/src/map_func.h index 9198c2cd1..f999e0dcf 100644 --- a/src/map_func.h +++ b/src/map_func.h @@ -43,7 +43,7 @@ extern Tile *_m; */ extern TileExtended *_me; -void AllocateMap(uint size_x, uint size_y); +void AllocateMap(uint size_x, uint size_y, uint layer_count); /** * Logarithm of the map size along the X side. diff --git a/src/misc.cpp b/src/misc.cpp index c967d5369..49c5850fd 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -10,6 +10,7 @@ /** @file misc.cpp Misc functions that shouldn't be here. */ #include "stdafx.h" +#include "layer_func.h" #include "landscape.h" #include "news_func.h" #include "ai/ai.hpp" @@ -54,7 +55,7 @@ void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settin * related to the new game we're about to start/load. */ UnInitWindowSystem(); - AllocateMap(size_x, size_y); + AllocateMap(size_x, size_y, 1 << _settings_game.game_creation.layers); _pause_mode = PM_UNPAUSED; _fast_forward = 0; @@ -106,6 +107,7 @@ void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settin InitializeEconomy(); ResetObjectToPlace(); + ClearRailPlacementEndpoints(); GamelogReset(); GamelogStartAction(GLAT_START); diff --git a/src/newgrf.cpp b/src/newgrf.cpp index a1dfd7917..c8d5530a7 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -9013,6 +9013,9 @@ static void AfterLoadGRFs() InitializeSortedCargoSpecs(); + /* Create dynamic list of cargo legends for smallmap_gui.cpp. */ + BuildCargoTypesLegend(); + /* Sort the list of industry types. */ SortIndustryTypes(); diff --git a/src/object_cmd.cpp b/src/object_cmd.cpp index 9ba2ba83c..eb8bbb0bc 100644 --- a/src/object_cmd.cpp +++ b/src/object_cmd.cpp @@ -586,7 +586,7 @@ static void TileLoop_Object(TileIndex tile) if (GB(r, 0, 8) < (256 / 4 / (6 - level))) { uint amt = GB(r, 0, 8) / 8 / 4 + 1; if (EconomyIsInRecession()) amt = (amt + 1) >> 1; - MoveGoodsToStation(CT_PASSENGERS, amt, ST_HEADQUARTERS, GetTileOwner(tile), stations.GetStations()); + MoveGoodsToStation(CT_PASSENGERS, amt, ST_HEADQUARTERS, GetTileOwner(tile), stations.GetStations(), tile); } /* Top town building generates 90, HQ can make up to 196. The @@ -595,7 +595,7 @@ static void TileLoop_Object(TileIndex tile) if (GB(r, 8, 8) < (196 / 4 / (6 - level))) { uint amt = GB(r, 8, 8) / 8 / 4 + 1; if (EconomyIsInRecession()) amt = (amt + 1) >> 1; - MoveGoodsToStation(CT_MAIL, amt, ST_HEADQUARTERS, GetTileOwner(tile), stations.GetStations()); + MoveGoodsToStation(CT_MAIL, amt, ST_HEADQUARTERS, GetTileOwner(tile), stations.GetStations(), tile); } } diff --git a/src/order_base.h b/src/order_base.h index 82e1371ee..8e059142f 100644 --- a/src/order_base.h +++ b/src/order_base.h @@ -20,6 +20,7 @@ #include "station_type.h" #include "vehicle_type.h" #include "date_type.h" +#include "date_func.h" typedef Pool<Order, OrderID, 256, 64000> OrderPool; typedef Pool<OrderList, OrderListID, 128, 64000> OrderListPool; @@ -192,13 +193,41 @@ public: uint32 Pack() const; uint16 MapOldOrder() const; void ConvertFromOldSavegame(); + + static void PostDestructor(size_t index); }; void InsertOrder(Vehicle *v, Order *new_o, VehicleOrderID sel_ord); void DeleteOrder(Vehicle *v, VehicleOrderID sel_ord); -/** - * Shared order list linking together the linked list of orders and the list +/** Working modes for timetable separation. */ +enum TTSepMode { + /** Timetable separation works in fully automatic mode, determining its parameters from the situation in game. */ + TTS_MODE_AUTO, + + /** Timetable separation is deactivated altogether. */ + TTS_MODE_OFF, + + /** Timetable separation is active in manual time mode. The amount of time between vehicles is set in ticks by the user. */ + TTS_MODE_MAN_T, + + /** Timetable separation is active in manual number mode. The user sets a number of vehicles that are supposed to be running + * the timetable simultaneously. The algorithm acts according to this specification regardless of the actual number of + * running vehicles.*/ + TTS_MODE_MAN_N, + + /** Timetable separation works in buffered automatic mode that keeps one vehicle waiting at the first stop as + * reserve for delay compensation and behaves like full automatic otherwise. */ + TTS_MODE_BUFFERED_AUTO, +}; + +struct TTSepSettings { + TTSepMode mode; + uint num_veh, sep_ticks; + TTSepSettings() : mode(TTS_MODE_AUTO), num_veh(0), sep_ticks(0) { } +}; + +/** Shared order list linking together the linked list of orders and the list * of vehicles sharing this order list. */ struct OrderList : OrderListPool::PoolItem<&_orderlist_pool> { @@ -206,19 +235,29 @@ private: friend void AfterLoadVehicles(bool part_of_load); ///< For instantiating the shared vehicle chain friend const struct SaveLoad *GetOrderListDescription(); ///< Saving and loading of order lists. + /** Returns the number of running (i.e. not stopped) vehicles in the shared orders list. */ + int GetNumRunningVehicles(); + Order *first; ///< First order of the order list. VehicleOrderID num_orders; ///< NOSAVE: How many orders there are in the list. VehicleOrderID num_manual_orders; ///< NOSAVE: How many manually added orders are there in the list. uint num_vehicles; ///< NOSAVE: Number of vehicles that share this order list. Vehicle *first_shared; ///< NOSAVE: pointer to the first vehicle in the shared order chain. - Ticks timetable_duration; ///< NOSAVE: Total duration of the order list + Ticks last_timetable_init; ///< Contains the last absolute time of initialization in ticks. + uint separation_counter; ///< Counts the vehicles that arrive at the first shared order for separation timing. + bool is_separation_valid; ///< Is true if the separation has been initialized since last load or vehicle list change. + Ticks current_separation; ///< The current separation between vehicles in the shared order list. + TTSepMode current_sep_mode; ///< The current mode of vehicle separation. + uint num_sep_vehicles; ///< Number of planned vehicles for separation. public: /** Default constructor producing an invalid order list. */ OrderList(VehicleOrderID num_orders = INVALID_VEH_ORDER_ID) : first(NULL), num_orders(num_orders), num_manual_orders(0), num_vehicles(0), first_shared(NULL), - timetable_duration(0) { } + timetable_duration(0), last_timetable_init(INVALID_TICKS), separation_counter(0), + is_separation_valid(false), current_separation(INVALID_TICKS), current_sep_mode(TTS_MODE_OFF), + num_sep_vehicles(0) { } /** * Create an order list with the given order chain for the given vehicle. @@ -280,6 +319,24 @@ public: */ inline uint GetNumVehicles() const { return this->num_vehicles; } + /** + * Returns the amount of separation time between vehicles. + * @return the amount of separation time between vehicles. + */ + inline uint GetSepTime() const + { + if (this->is_separation_valid) { + return this->current_separation; + } else { + return this->GetTimetableTotalDuration() / this->GetNumVehicles(); + } + } + + TTSepSettings GetSepSettings(); + + void SetSepSettings(TTSepSettings s); + void SetSepSettings(TTSepMode Mode, uint Parameter); + bool IsVehicleInSharedOrdersList(const Vehicle *v) const; int GetPositionInSharedOrderList(const Vehicle *v) const; @@ -313,6 +370,64 @@ public: */ void UpdateOrderTimetable(Ticks delta) { this->timetable_duration += delta; } + /** + * Gets the last absolute time in Ticks since separation was initalized. + * @return last arrival time of first vehicle at first order. + */ + inline Ticks GetSeparationInitTime() const + { + return this->last_timetable_init; + } + + /** + * Gets the current value of the timetable separation counter. + * @return the current value of the timetable separation counter. + */ + inline uint GetSeparationCounter() const + { + return this->separation_counter; + } + + /** Increases the timetable separation counter. */ + void IncreaseSeparationCounter() + { + this->separation_counter++; + } + + /** Marks timetable separation invalid so it has to be initialized again. */ + void MarkSeparationInvalid() + { + if ((this->current_sep_mode == TTS_MODE_AUTO)||(this->current_sep_mode == TTS_MODE_BUFFERED_AUTO)) + this->is_separation_valid = false; + } + + /** + * Returns the new delay for the current vehicle and increases the separation counter. + * @return the new delay + */ + Ticks SeparateVehicle(); + + /** + * Gets whether the timetable separation is currently valid or not. + * @return whether the timetable separation is currently valid or not. + */ + inline bool IsSeparationValid() const + { + return this->is_separation_valid; + } + + /** + * Gets whether timetable separation is currently switched on or not. + * @return whether the timetable separation is currently switched on or not. + */ + inline bool IsSeparationOn() const + { + return this->current_sep_mode != TTS_MODE_OFF; + } + + /** Initializes the separation system. */ + void InitializeSeparation(); + void FreeChain(bool keep_orderlist = false); void DebugCheckSanity() const; diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index e86e31900..145f27652 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -26,6 +26,7 @@ #include "waypoint_base.h" #include "company_base.h" #include "order_backup.h" +#include "cargodest_func.h" #include "table/strings.h" @@ -245,6 +246,21 @@ Order::Order(uint32 packed) } /** + * Invalidating some stuff after removing item from the pool. + * @param index index of deleted item. + */ +/* static */ void Order::PostDestructor(size_t index) +{ + Vehicle *v; + FOR_ALL_VEHICLES(v) { + if (v->current_order.index == index) v->current_order.index = INVALID_ORDER; + if (v->last_order_id == index) v->last_order_id = INVALID_ORDER; + } + + InvalidateOrderRouteLinks((OrderID)index); +} + +/** * * Updates the widgets of a vehicle which contains the order-data * @@ -529,6 +545,146 @@ void OrderList::DebugCheckSanity() const this->num_vehicles, this->timetable_duration); } +/** Returns the number of running (i.e. not stopped) vehicles in the shared orders list. */ +int OrderList::GetNumRunningVehicles() +{ + int num_running_vehicles = 0; + + for (const Vehicle *v = this->first_shared; v != NULL; v = v->NextShared()) { + if (!(v->vehstatus & (VS_STOPPED | VS_CRASHED))) num_running_vehicles++; + } + + return num_running_vehicles; +} + +/** (Re-)Initializes Separation if necessary and possible. */ +void OrderList::InitializeSeparation() +{ + // Check whether separation can be used at all + if(!this->IsCompleteTimetable() || this->current_sep_mode == TTS_MODE_OFF) { + this->is_separation_valid = false; + return; + } + + // Save current tick count as reference for future timetable start dates and reset the separation counter. + this->last_timetable_init = GetCurrentTickCount(); + this->separation_counter = 0; + + // Calculate separation amount depending on mode of operation. + switch (current_sep_mode) { + case TTS_MODE_AUTO: { + int num_running_vehicles = this->GetNumRunningVehicles(); + assert(num_running_vehicles > 0); + + this->current_separation = this->GetTimetableTotalDuration() / num_running_vehicles; + break; + } + + case TTS_MODE_MAN_N: + this->current_separation = this->GetTimetableTotalDuration() / this->num_sep_vehicles; + break; + + case TTS_MODE_MAN_T: + // separation is set manually -> nothing to do + break; + + case TTS_MODE_BUFFERED_AUTO: { + int num_running_vehicles = this->GetNumRunningVehicles(); + assert(num_running_vehicles > 0); + + if(num_running_vehicles > 1) + num_running_vehicles--; + + this->current_separation = this->GetTimetableTotalDuration() / num_running_vehicles; + break; + } + + default: + NOT_REACHED(); + break; + } + + this->is_separation_valid = true; +} + +/** + * Returns the delay setting required for correct separation and increases the separation counter by 1. + * @return the delay setting required for correct separation. */ +Ticks OrderList::SeparateVehicle() +{ + if (!this->is_separation_valid || this->current_sep_mode == TTS_MODE_OFF) + return INVALID_TICKS; + + Ticks result = GetCurrentTickCount() - (this->separation_counter * this->current_separation + this->last_timetable_init); + this->separation_counter++; + + return result; +} + +/** + * Returns the current separation settings. + * @return the current separation settings. + */ +TTSepSettings OrderList::GetSepSettings() +{ + TTSepSettings result; + + result.mode = this->current_sep_mode; + result.sep_ticks = GetSepTime(); + + // Depending on the operation mode return either the user setting or the true amount of vehicles running the timetable. + result.num_veh = (result.mode == TTS_MODE_MAN_N) ? this->num_sep_vehicles : GetNumVehicles(); + return result; +} + +/** + * Prepares command to set new separation settings. + * @param s Contains the new settings to be used for separation. + * @todo Clean this up (e.g. via union type) + */ +void OrderList::SetSepSettings(TTSepSettings s) +{ + uint32 p2 = GB<uint32>(s.mode,0,3); + AB<uint32, uint>(p2,3,29, (s.mode == TTS_MODE_MAN_N) ? s.num_veh : s.sep_ticks); + DoCommandP(0, this->first_shared->index, p2, CMD_REINIT_SEPARATION); +} + +/** + * Sets new separation settings. + * @param mode Contains the operation mode that is to be used for separation. + * @param parameter Depending on the operation mode this contains either the number of vehicles (#TTS_MODE_MAN_N) + * or the time between vehicles in ticks (#TTS_MODE_MAN_T). For other modes, this is undefined. + */ +void OrderList::SetSepSettings(TTSepMode mode, uint32 parameter) +{ + this->current_sep_mode = mode; + + switch (this->current_sep_mode) + { + case TTS_MODE_MAN_N: + this->current_separation = this->GetTimetableTotalDuration() / parameter; + this->num_sep_vehicles = parameter; + break; + + case TTS_MODE_MAN_T: + this->current_separation = parameter; + this->num_sep_vehicles = this->GetTimetableTotalDuration() / this->current_separation; + break; + + case TTS_MODE_AUTO: + case TTS_MODE_BUFFERED_AUTO: + case TTS_MODE_OFF: + /* nothing to do */ + break; + + default: + NOT_REACHED(); + break; + } + + this->is_separation_valid = false; +} + /** * Checks whether the order goes to a station or not, i.e. whether the * destination is a station @@ -902,6 +1058,8 @@ void InsertOrder(Vehicle *v, Order *new_o, VehicleOrderID sel_ord) cur_order_id++; } + PrefillRouteLinks(v); + /* Make sure to rebuild the whole list */ InvalidateWindowClassesData(GetWindowClassForVehicleType(v->type), 0); } @@ -967,6 +1125,17 @@ static void CancelLoadingDueToDeletedOrder(Vehicle *v) } /** + * Invalidate the next unload station of all cargo packets of a vehicle chain. + * @param v The vehicle. + */ +static void InvalidateNextStation(Vehicle *v) +{ + for (; v != NULL; v = v->Next()) { + v->cargo.InvalidateNextStation(); + } +} + +/** * Delete an order but skip the parameter validation. * @param v The vehicle to delete the order from. * @param sel_ord The id of the order to be deleted. @@ -977,6 +1146,7 @@ void DeleteOrder(Vehicle *v, VehicleOrderID sel_ord) Vehicle *u = v->FirstShared(); DeleteOrderWarnings(u); + PrefillRouteLinks(u); for (; u != NULL; u = u->NextShared()) { assert(v->orders.list == u->orders.list); @@ -1005,6 +1175,9 @@ void DeleteOrder(Vehicle *v, VehicleOrderID sel_ord) /* Update any possible open window of the vehicle */ InvalidateVehicleOrder(u, sel_ord | (INVALID_VEH_ORDER_ID << 8)); + + /* Clear the next unload station of all cargo packets, it might not be in the orders anymore. */ + InvalidateNextStation(u); } /* As we delete an order, the order to skip to will be 'wrong'. */ @@ -1294,7 +1467,11 @@ CommandCost CmdModifyOrder(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 switch (mof) { case MOF_NON_STOP: order->SetNonStopType((OrderNonStopFlags)data); - if (data & ONSF_NO_STOP_AT_DESTINATION_STATION) order->SetRefit(CT_NO_REFIT); + if (data & ONSF_NO_STOP_AT_DESTINATION_STATION) { + InvalidateOrderRouteLinks(order->index); + order->SetRefit(CT_NO_REFIT); + } + PrefillRouteLinks(v); break; case MOF_STOP_LOCATION: @@ -1394,6 +1571,9 @@ CommandCost CmdModifyOrder(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 u->current_order.SetLoadType(order->GetLoadType()); } InvalidateVehicleOrder(u, VIWD_MODIFY_ORDERS); + + /* Invalidate the next unload station of all packets as we might not unload there anymore. */ + InvalidateNextStation(u); } } @@ -1502,6 +1682,7 @@ CommandCost CmdCloneOrder(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 /* Link this vehicle in the shared-list */ dst->AddToShared(src); + PrefillRouteLinks(dst); InvalidateVehicleOrder(dst, VIWD_REMOVE_ALL_ORDERS); InvalidateVehicleOrder(src, VIWD_MODIFY_ORDERS); @@ -1566,6 +1747,7 @@ CommandCost CmdCloneOrder(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 dst->orders.list = new OrderList(first, dst); } + PrefillRouteLinks(dst); InvalidateVehicleOrder(dst, VIWD_REMOVE_ALL_ORDERS); InvalidateWindowClassesData(GetWindowClassForVehicleType(dst->type), 0); @@ -1804,6 +1986,9 @@ void DeleteVehicleOrders(Vehicle *v, bool keep_orderlist, bool reset_order_indic if (!keep_orderlist) v->orders.list = NULL; } + /* Invalidate the next unload station of all cargo. */ + InvalidateNextStation(v); + if (reset_order_indices) { v->cur_implicit_order_index = v->cur_real_order_index = 0; if (v->current_order.IsType(OT_LOADING)) { @@ -2011,7 +2196,12 @@ bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth, bool } v->current_order = *order; + /* Set the index of the current order to the index of the implicit order, + * this is needed as the index is used for route link generation. */ + Order *implicit_order = v->GetOrder(v->cur_implicit_order_index); + v->current_order.index = implicit_order->index; return UpdateOrderDest(v, order, conditional_depth + 1, pbs_look_ahead); + } /** @@ -2094,6 +2284,10 @@ bool ProcessOrders(Vehicle *v) /* Otherwise set it, and determine the destination tile. */ v->current_order = *order; + /* Set the index of the current order to the index of the implicit order, + * this is needed as the index is used for route link generation. */ + Order *implicit_order = v->GetOrder(v->cur_implicit_order_index); + v->current_order.index = implicit_order->index; InvalidateVehicleOrder(v, VIWD_MODIFY_ORDERS); switch (v->type) { diff --git a/src/os/macosx/osx_stdafx.h b/src/os/macosx/osx_stdafx.h index 688762be9..86bd56ace 100644 --- a/src/os/macosx/osx_stdafx.h +++ b/src/os/macosx/osx_stdafx.h @@ -44,6 +44,7 @@ #define WindowClass OTTDWindowClass #define ScriptOrder OTTDScriptOrder #define Palette OTTDPalette +#define RoutingFlags OTTDRoutingFlags #include <CoreServices/CoreServices.h> #include <ApplicationServices/ApplicationServices.h> @@ -53,6 +54,7 @@ #undef WindowClass #undef ScriptOrder #undef Palette +#undef RoutingFlags /* remove the variables that CoreServices defines, but we define ourselves too */ #undef bool diff --git a/src/pathfinder/follow_track.hpp b/src/pathfinder/follow_track.hpp index 3fdc3f1f8..228817ab2 100644 --- a/src/pathfinder/follow_track.hpp +++ b/src/pathfinder/follow_track.hpp @@ -352,7 +352,7 @@ protected: if (IsTunnel(m_new_tile)) { if (!m_is_tunnel) { DiagDirection tunnel_enterdir = GetTunnelBridgeDirection(m_new_tile); - if (tunnel_enterdir != m_exitdir) { + if (tunnel_enterdir != m_exitdir || IsTunnelBridgeExit(m_new_tile)) { m_err = EC_NO_WAY; return false; } @@ -360,7 +360,7 @@ protected: } else { // IsBridge(m_new_tile) if (!m_is_bridge) { DiagDirection ramp_enderdir = GetTunnelBridgeDirection(m_new_tile); - if (ramp_enderdir != m_exitdir) { + if (ramp_enderdir != m_exitdir || IsTunnelBridgeExit(m_new_tile)) { m_err = EC_NO_WAY; return false; } diff --git a/src/pathfinder/npf/npf.cpp b/src/pathfinder/npf/npf.cpp index 5c4ccb3cc..f8cca276f 100644 --- a/src/pathfinder/npf/npf.cpp +++ b/src/pathfinder/npf/npf.cpp @@ -333,6 +333,8 @@ static int32 NPFRoadPathCost(AyStar *as, AyStarNode *current, OpenListNode *pare cost = NPF_TILE_LENGTH; /* Increase the cost for level crossings */ if (IsLevelCrossing(tile)) cost += _settings_game.pf.npf.npf_crossing_penalty; + /* Increase the cost for juctions with trafficlights. */ + if (HasTrafficLights(tile)) cost += _settings_game.pf.npf.npf_road_trafficlight_penalty; break; case MP_STATION: { diff --git a/src/pathfinder/yapf/nodelist.hpp b/src/pathfinder/yapf/nodelist.hpp index f0924a414..a8bcff35a 100644 --- a/src/pathfinder/yapf/nodelist.hpp +++ b/src/pathfinder/yapf/nodelist.hpp @@ -21,7 +21,7 @@ * Implements open list, closed list and priority queue for A-star * path finder. */ -template <class Titem_, int Thash_bits_open_, int Thash_bits_closed_> +template <class Titem_, int Thash_bits_open_, int Thash_bits_closed_, int Tnum_array_items_ = 65536> class CNodeList_HashTableT { public: /** make Titem_ visible from outside of class */ @@ -29,7 +29,7 @@ public: /** make Titem_::Key a property of HashTable */ typedef typename Titem_::Key Key; /** type that we will use as item container */ - typedef SmallArray<Titem_, 65536, 256> CItemArray; + typedef SmallArray<Titem_, Tnum_array_items_, 256> CItemArray; /** how pointers to open nodes will be stored */ typedef CHashTableT<Titem_, Thash_bits_open_ > COpenList; /** how pointers to closed nodes will be stored */ diff --git a/src/pathfinder/yapf/yapf.h b/src/pathfinder/yapf/yapf.h index 00eb7e562..ce4ae4b66 100644 --- a/src/pathfinder/yapf/yapf.h +++ b/src/pathfinder/yapf/yapf.h @@ -15,6 +15,8 @@ #include "../../direction_type.h" #include "../../track_type.h" #include "../../vehicle_type.h" +#include "../../cargodest_type.h" +#include "../../order_type.h" #include "../pathfinder_type.h" /** @@ -97,4 +99,6 @@ bool YapfTrainCheckReverse(const Train *v); */ bool YapfTrainFindNearestSafeTile(const Train *v, TileIndex tile, Trackdir td, bool override_railtype); +RouteLink *YapfChooseRouteLink(CargoID cid, const StationList *stations, TileIndex src, const TileArea &dest, StationID *start_station, StationID *next_unload, byte flags, bool *found = NULL, OrderID order = INVALID_ORDER, int max_cost = INT_MAX); + #endif /* YAPF_H */ diff --git a/src/pathfinder/yapf/yapf_cargo.cpp b/src/pathfinder/yapf/yapf_cargo.cpp new file mode 100644 index 000000000..22e995197 --- /dev/null +++ b/src/pathfinder/yapf/yapf_cargo.cpp @@ -0,0 +1,442 @@ +/* $Id$ */ + +/* + * 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 yapf_cargo.cpp Implementation of YAPF for cargo routing. */ + +#include "../../stdafx.h" +#include "../../cargodest_base.h" +#include "../../station_base.h" +#include "../../town.h" +#include "yapf.hpp" + + +/** YAPF node key for cargo routing. */ +struct CYapfRouteLinkNodeKeyT { + RouteLink *m_link; + + /** Initialize this node key. */ + inline void Set(RouteLink *link) + { + this->m_link = link; + } + + /** Calculate the hash of this cargo/route key. */ + inline int CalcHash() const + { + return (int)(size_t)this->m_link >> 4; + } + + inline bool operator == (const CYapfRouteLinkNodeKeyT& other) const + { + return this->m_link == other.m_link; + } + + void Dump(DumpTarget &dmp) const + { + dmp.WriteLine("m_link = %u", this->m_link->GetDestination()); + } +}; + +/** YAPF node class for cargo routing. */ +struct CYapfRouteLinkNodeT : public CYapfNodeT<CYapfRouteLinkNodeKeyT, CYapfRouteLinkNodeT> { + typedef CYapfNodeT<CYapfRouteLinkNodeKeyT, CYapfRouteLinkNodeT> Base; + + uint m_num_transfers; ///< Number of transfers to reach this node. + + /** Initialize this node. */ + inline void Set(CYapfRouteLinkNodeT *parent, RouteLink *link) + { + Base::Set(parent, false); + this->m_key.Set(link); + this->m_num_transfers = (parent != NULL) ? parent->m_num_transfers : 0; + } + + /** Get the route link of this node. */ + inline RouteLink *GetRouteLink() const { return this->m_key.m_link; } + + /** Get the number of transfers needed to reach this node. */ + inline int GetNumberOfTransfers() const { return this->m_num_transfers; } +}; + +typedef CNodeList_HashTableT<CYapfRouteLinkNodeT, 8, 10, 2048> CRouteLinkNodeList; + +/** Route link follower. */ +struct CFollowRouteLinkT { + CargoID m_cid; + RouteLink *m_old_link; + RouteLinkList *m_new_links; + + CFollowRouteLinkT(CargoID cid) : m_cid(cid) {} + + /** Fill in route links reachable by this route link. */ + inline bool Follow(RouteLink *from) + { + this->m_old_link = from; + + Station *st = Station::Get(from->GetDestination()); + m_new_links = &st->goods[this->m_cid].routes; + return !this->m_new_links->empty(); + } +}; + +/** YAPF cost provider for route links. */ +template <class Types> +class CYapfCostRouteLinkT { + typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class). + typedef typename Types::TrackFollower Follower; ///< The route follower. + typedef typename Types::NodeList::Titem Node; ///< This will be our node type. + + static const int PENALTY_DIVISOR = 16; ///< Penalty factor divisor for fixed-point arithmetics. + static const int LOCAL_PENALTY_FACTOR = 16; ///< Penalty factor for source-local delivery. + static const int RF_DISTANCE_FACTOR = 2; ///< Vehicle modifier for "cheap" cargo packets. + static const int RF_TIME_FACTOR = 3; ///< Time modifier for "fast" cargo packets. + + /** To access inherited path finder. */ + inline Tpf& Yapf() { return *static_cast<Tpf*>(this); } + inline const Tpf& Yapf() const { return *static_cast<const Tpf*>(this); } + + /** Check if this is a valid connection. */ + inline bool ValidLink(Node &n, const RouteLink *link, const RouteLink *parent) const + { + /* If the parent link has an owner, and the owner is different to + * the new owner, discard the node. Otherwise cargo could switch + * companies at oil rigs, which would mess up payment. */ + if (parent->GetOwner() != INVALID_OWNER && link->GetOwner() != parent->GetOwner()) return false; + + /* Check for no loading/no unloading when transferring. */ + if (link->GetOriginOrderId() != parent->GetDestOrderId() || (Order::Get(link->GetOriginOrderId())->GetUnloadType() & OUFB_UNLOAD) != 0) { + /* Can't transfer if the current order prohibits loading. */ + if ((Order::Get(link->GetOriginOrderId())->GetLoadType() & OLFB_NO_LOAD) != 0) return false; + + /* Can't transfer if the last order prohibits unloading. */ + if (parent->GetDestOrderId() != INVALID_ORDER && (Order::Get(parent->GetDestOrderId())->GetUnloadType() & OUFB_NO_UNLOAD) != 0) return false; + + /* Increase transfer counter and stop if max number of transfers is exceeded. */ + if (++n.m_num_transfers > Yapf().PfGetSettings().route_max_transfers) return false; + } + + return true; + } + + /** Cost of a single route link. */ + inline int RouteLinkCost(const RouteLink *link, const RouteLink *parent) const + { + int cost = 0; + + /* Distance cost. */ + const Station *from = Station::Get(parent->GetDestination()); + const Station *to = Station::Get(link->GetDestination()); + cost = DistanceManhattan(from->xy, to->xy) * this->Yapf().PfGetSettings().route_distance_factor; + + /* Modulate the distance by a vehicle-type specific factor to + * simulate the different costs. Cost is doubled if the cargo + * wants to go cheap. */ + assert_compile(lengthof(_settings_game.pf.yapf.route_mode_cost_factor) == VEH_AIRCRAFT + 1); + byte dfactor = this->Yapf().PfGetSettings().route_mode_cost_factor[link->GetVehicleType()]; + if (HasBit(this->Yapf().GetFlags(), RF_WANT_CHEAP)) dfactor *= RF_DISTANCE_FACTOR; + cost *= dfactor; + + /* Factor for the time penalties based on whether the cargo wants to go fast. */ + uint time_factor = HasBit(this->Yapf().GetFlags(), RF_WANT_FAST) ? RF_TIME_FACTOR : 1; + + /* Transfer penalty when switching vehicles or forced unloading. */ + if (link->GetOriginOrderId() != parent->GetDestOrderId() || (Order::Get(link->GetOriginOrderId())->GetUnloadType() & OUFB_UNLOAD) != 0) { + cost += this->Yapf().PfGetSettings().route_transfer_cost; + + /* Penalty for time since the last vehicle arrived. */ + cost += link->GetWaitTime() * this->Yapf().PfGetSettings().route_station_last_veh_factor * time_factor / PENALTY_DIVISOR; + + /* Penalty for cargo waiting on our link. */ + cost += (from->goods[this->Yapf().GetCargoID()].cargo.CountForNextHop(link->GetOriginOrderId()) * this->Yapf().PfGetSettings().route_station_waiting_factor) / PENALTY_DIVISOR; + } + + /* Penalty for travel time. */ + cost += (link->GetTravelTime() * this->Yapf().PfGetSettings().route_travel_time_factor * time_factor) / PENALTY_DIVISOR; + + return cost; + } + +public: + /** Called by YAPF to calculate the cost from the origin to the given node. */ + inline bool PfCalcCost(Node& n, const Follower *follow) + { + int segment_cost = 0; + + if (this->Yapf().PfDetectDestination(n)) { + Station *st = Station::Get(n.m_parent->GetRouteLink()->GetDestination()); + /* Discard node if the station doesn't accept the cargo type. */ + if (!HasBit(st->goods[follow->m_cid].acceptance_pickup, GoodsEntry::GES_ACCEPTANCE)) return false; + /* Destination node, get delivery cost. Parent has the station. */ + segment_cost += this->Yapf().DeliveryCost(st); + /* If this link comes from an origin station, penalize it to encourage + * delivery using other stations. */ + if (n.m_parent->GetRouteLink()->GetDestOrderId() == INVALID_ORDER) segment_cost *= LOCAL_PENALTY_FACTOR; + } else { + RouteLink *link = n.GetRouteLink(); + RouteLink *parent = n.m_parent->GetRouteLink(); + + /* Check if the link is a valid connection. */ + if (!this->ValidLink(n, link, parent)) return false; + + /* Cost of the single route link. */ + segment_cost += this->RouteLinkCost(link, parent); + } + + /* Apply it. */ + n.m_cost = n.m_parent->m_cost + segment_cost; + return n.m_cost <= this->Yapf().GetMaxCost(); + } +}; + +/** YAPF origin provider for route links. */ +template <class Types> +class CYapfOriginRouteLinkT { + typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class). + typedef typename Types::NodeList::Titem Node; ///< This will be our node type. + + CargoID m_cid; + TileIndex m_src; + OrderID m_order; + byte m_flags; + SmallVector<RouteLink, 2> m_origin; + + /** To access inherited path finder. */ + inline Tpf& Yapf() { return *static_cast<Tpf*>(this); } + +public: + /** Get the current cargo type. */ + inline CargoID GetCargoID() const + { + return this->m_cid; + } + + /** Get the cargo routing flags. */ + inline byte GetFlags() const + { + return this->m_flags; + } + + /** Set origin. */ + void SetOrigin(CargoID cid, TileIndex src, const StationList *stations, bool cargo_creation, OrderID order, byte flags) + { + this->m_cid = cid; + this->m_src = src; + this->m_order = order; + this->m_flags = flags; + /* Create fake links for the origin stations. */ + for (const Station * const *st = stations->Begin(); st != stations->End(); st++) { + if (cargo_creation) { + /* Exclusive rights in effect? Only serve those stations. */ + if ((*st)->town->exclusive_counter > 0 && (*st)->town->exclusivity != (*st)->owner) continue; + /* Selectively servicing stations, and not this one. */ + if (_settings_game.order.selectgoods && (*st)->goods[cid].last_speed == 0) continue; + } + + *this->m_origin.Append() = RouteLink((*st)->index, INVALID_ORDER, this->m_order); + } + } + + /** Called when YAPF needs to place origin nodes into the open list. */ + void PfSetStartupNodes() + { + for (RouteLink *link = this->m_origin.Begin(); link != this->m_origin.End(); link++) { + Node &n = this->Yapf().CreateNewNode(); + n.Set(NULL, link); + /* Prefer stations closer to the source tile. */ + n.m_cost = DistanceSquare(this->m_src, Station::Get(link->GetDestination())->xy) * this->Yapf().PfGetSettings().route_distance_factor; + this->Yapf().AddStartupNode(n); + } + } +}; + +/** YAPF destination provider for route links. */ +template <class Types> +class CYapfDestinationRouteLinkT { + typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class). + typedef typename Types::NodeList::Titem Node; ///< This will be our node type. + + TileArea m_dest; + int m_max_cost; ///< Maximum node cost. + + /** To access inherited path finder. */ + inline Tpf& Yapf() { return *static_cast<Tpf*>(this); } + +public: + /** Get the maximum allowed node cost. */ + inline int GetMaxCost() const + { + return this->m_max_cost; + } + + /** Set destination. */ + void SetDestination(const TileArea &dest, uint max_cost) + { + this->m_dest = dest; + this->m_max_cost = max_cost; + } + + /** Cost for delivering the cargo to the final destination tile. */ + inline int DeliveryCost(Station *st) + { + int x = TileX(this->m_dest.tile); + int y = TileY(this->m_dest.tile); + + /* Inside the station area? Delivery costs "nothing". */ + if (st->rect.PtInExtendedRect(x, y)) return 0; + + int dist_x = x < st->rect.left ? x - st->rect.left : x - st->rect.right; + int dist_y = y < st->rect.top ? y - st->rect.top : y - st->rect.bottom; + + return (dist_x * dist_x + dist_y * dist_y) * this->Yapf().PfGetSettings().route_distance_factor; + } + + /** Called by YAPF to detect if the station reaches the destination. */ + inline bool PfDetectDestination(StationID st_id) const + { + const Station *st = Station::Get(st_id); + return st->rect.AreaInExtendedRect(this->m_dest, st->GetCatchmentRadius()); + } + + /** Called by YAPF to detect if the node reaches the destination. */ + inline bool PfDetectDestination(const Node& n) const + { + return n.GetRouteLink() == NULL; + } + + /** Called by YAPF to calculate the estimated cost to the destination. */ + inline bool PfCalcEstimate(Node& n) + { + if (this->PfDetectDestination(n)) { + n.m_estimate = n.m_cost; + return true; + } + + /* Estimate based on Manhattan distance to destination. */ + Station *from = Station::Get(n.GetRouteLink()->GetDestination()); + int d = DistanceManhattan(from->xy, this->m_dest.tile) * this->Yapf().PfGetSettings().route_distance_factor; + + n.m_estimate = n.m_cost + d; + assert(n.m_estimate >= n.m_parent->m_estimate); + return true; + } +}; + +/** Main route finding class. */ +template <class Types> +class CYapfFollowRouteLinkT { + typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class). + typedef typename Types::TrackFollower Follower; ///< The route follower. + typedef typename Types::NodeList::Titem Node; ///< This will be our node type. + + /** To access inherited path finder. */ + inline Tpf& Yapf() { return *static_cast<Tpf*>(this); } + +public: + /** Called by YAPF to move from the given node to the next nodes. */ + inline void PfFollowNode(Node& old_node) + { + Follower f(this->Yapf().GetCargoID()); + + if (this->Yapf().PfDetectDestination(old_node.GetRouteLink()->GetDestination()) && (old_node.GetRouteLink()->GetDestOrderId() == INVALID_ORDER || (Order::Get(old_node.GetRouteLink()->GetDestOrderId())->GetUnloadType() & OUFB_NO_UNLOAD) == 0)) { + /* Possible destination? Add sentinel node for final delivery. */ + Node &n = this->Yapf().CreateNewNode(); + n.Set(&old_node, NULL); + this->Yapf().AddNewNode(n, f); + } + + if (f.Follow(old_node.GetRouteLink())) { + for (RouteLinkList::iterator link = f.m_new_links->begin(); link != f.m_new_links->end(); ++link) { + /* Add new node. */ + Node &n = this->Yapf().CreateNewNode(); + n.Set(&old_node, *link); + this->Yapf().AddNewNode(n, f); + } + } + } + + /** Return debug report character to identify the transportation type. */ + inline char TransportTypeChar() const + { + return 'c'; + } + + /** Find the best cargo routing from a station to a destination. */ + static RouteLink *ChooseRouteLink(CargoID cid, const StationList *stations, TileIndex src, const TileArea &dest, StationID *start_station, StationID *next_unload, byte flags, bool *found, OrderID order, int max_cost) + { + /* Initialize pathfinder instance. */ + Tpf pf; + pf.SetOrigin(cid, src, stations, start_station != NULL, order, flags); + pf.SetDestination(dest, max_cost); + + *next_unload = INVALID_STATION; + + /* Do it. Exit if we didn't find a path. */ + bool res = pf.FindPath(NULL); + if (found != NULL) *found = res; + if (!res) return NULL; + + /* Walk back to find the start node. */ + Node *node = pf.GetBestNode(); + while (node->m_parent->m_parent != NULL) { + /* Transfer? Then save transfer station as next unload station. */ + if (node->GetRouteLink() == NULL || (node->GetRouteLink()->GetOriginOrderId() != node->m_parent->GetRouteLink()->GetDestOrderId())) { + *next_unload = node->m_parent->GetRouteLink()->GetDestination(); + } + + node = node->m_parent; + } + + /* Save result. */ + if (start_station != NULL) { + *start_station = node->m_parent->GetRouteLink()->GetDestination(); + /* Path starts and ends at the same station, do local delivery. */ + if (*start_station == pf.GetBestNode()->m_parent->GetRouteLink()->GetDestination()) return NULL; + } + return node->GetRouteLink(); + } +}; + +/** Config struct for route link finding. */ +template <class Tpf_> +struct CYapfRouteLink_TypesT { + typedef CYapfRouteLink_TypesT<Tpf_> Types; + + typedef Tpf_ Tpf; ///< Pathfinder type + typedef CFollowRouteLinkT TrackFollower; ///< Node follower + typedef CRouteLinkNodeList NodeList; ///< Node list type + typedef Vehicle VehicleType; ///< Dummy type + + typedef CYapfBaseT<Types> PfBase; ///< Base pathfinder class + typedef CYapfFollowRouteLinkT<Types> PfFollow; ///< Node follower + typedef CYapfOriginRouteLinkT<Types> PfOrigin; ///< Origin provider + typedef CYapfDestinationRouteLinkT<Types> PfDestination; ///< Destination/distance provider + typedef CYapfSegmentCostCacheNoneT<Types> PfCache; ///< Cost cache provider + typedef CYapfCostRouteLinkT<Types> PfCost; ///< Cost provider +}; + +struct CYapfRouteLink : CYapfT<CYapfRouteLink_TypesT<CYapfRouteLink> > {}; + + +/** + * Find the best cargo routing from a station to a destination. + * @param cid Cargo type to route. + * @param stations Set of possible originating stations. + * @param dest Destination tile area. + * @param[out] start_station Station the best route link originates from. + * @param[out] next_unload Next station the cargo should be unloaded from the vehicle. + * @param flags Routing flags of the cargo. + * @param[out] found True if a link was found. + * @param order Order the vehicle arrived at the origin station. + * @param max_cost Maxmimum allowed node cost. + * @return The best RouteLink to the target or NULL if either no link found or one of the origin stations is the best destination. + */ +RouteLink *YapfChooseRouteLink(CargoID cid, const StationList *stations, TileIndex src, const TileArea &dest, StationID *start_station, StationID *next_unload, byte flags, bool *found, OrderID order, int max_cost) +{ + return CYapfRouteLink::ChooseRouteLink(cid, stations, src, dest, start_station, next_unload, flags, found, order, max_cost); +} diff --git a/src/pathfinder/yapf/yapf_node.hpp b/src/pathfinder/yapf/yapf_node.hpp index 39f393381..e1b41c615 100644 --- a/src/pathfinder/yapf/yapf_node.hpp +++ b/src/pathfinder/yapf/yapf_node.hpp @@ -54,9 +54,8 @@ struct CYapfNodeT { int m_cost; int m_estimate; - inline void Set(Node *parent, TileIndex tile, Trackdir td, bool is_choice) + inline void Set(Node *parent, bool is_choice) { - m_key.Set(tile, td); m_hash_next = NULL; m_parent = parent; m_cost = 0; @@ -65,8 +64,6 @@ struct CYapfNodeT { inline Node *GetHashNext() {return m_hash_next;} inline void SetHashNext(Node *pNext) {m_hash_next = pNext;} - inline TileIndex GetTile() const {return m_key.m_tile;} - inline Trackdir GetTrackdir() const {return m_key.m_td;} inline const Tkey_& GetKey() const {return m_key;} inline int GetCost() const {return m_cost;} inline int GetCostEstimate() const {return m_estimate;} @@ -81,4 +78,21 @@ struct CYapfNodeT { } }; +/** Yapf Node base for trackdir based specialisation. */ +template <class Tkey_, class Tnode> +struct CYapfNodeTrackT : public CYapfNodeT<Tkey_, Tnode> +{ + typedef CYapfNodeT<Tkey_, Tnode> Base; + typedef Tnode Node; + + inline void Set(Node *parent, TileIndex tile, Trackdir td, bool is_choice) + { + Base::Set(parent, is_choice); + this->m_key.Set(tile, td); + } + + inline TileIndex GetTile() const { return this->m_key.m_tile; } + inline Trackdir GetTrackdir() const { return this->m_key.m_td; } +}; + #endif /* YAPF_NODE_HPP */ diff --git a/src/pathfinder/yapf/yapf_node_rail.hpp b/src/pathfinder/yapf/yapf_node_rail.hpp index 765fa0daf..cc562b9fa 100644 --- a/src/pathfinder/yapf/yapf_node_rail.hpp +++ b/src/pathfinder/yapf/yapf_node_rail.hpp @@ -190,9 +190,9 @@ struct CYapfRailSegment /** Yapf Node for rail YAPF */ template <class Tkey_> struct CYapfRailNodeT - : CYapfNodeT<Tkey_, CYapfRailNodeT<Tkey_> > + : CYapfNodeTrackT<Tkey_, CYapfRailNodeT<Tkey_> > { - typedef CYapfNodeT<Tkey_, CYapfRailNodeT<Tkey_> > base; + typedef CYapfNodeTrackT<Tkey_, CYapfRailNodeT<Tkey_> > base; typedef CYapfRailSegment CachedData; CYapfRailSegment *m_segment; diff --git a/src/pathfinder/yapf/yapf_node_road.hpp b/src/pathfinder/yapf/yapf_node_road.hpp index 5cc2d5539..55f9fc46b 100644 --- a/src/pathfinder/yapf/yapf_node_road.hpp +++ b/src/pathfinder/yapf/yapf_node_road.hpp @@ -15,9 +15,9 @@ /** Yapf Node for road YAPF */ template <class Tkey_> struct CYapfRoadNodeT - : CYapfNodeT<Tkey_, CYapfRoadNodeT<Tkey_> > + : CYapfNodeTrackT<Tkey_, CYapfRoadNodeT<Tkey_> > { - typedef CYapfNodeT<Tkey_, CYapfRoadNodeT<Tkey_> > base; + typedef CYapfNodeTrackT<Tkey_, CYapfRoadNodeT<Tkey_> > base; TileIndex m_segment_last_tile; Trackdir m_segment_last_td; diff --git a/src/pathfinder/yapf/yapf_node_ship.hpp b/src/pathfinder/yapf/yapf_node_ship.hpp index 7a1358af6..59f0e9f5a 100644 --- a/src/pathfinder/yapf/yapf_node_ship.hpp +++ b/src/pathfinder/yapf/yapf_node_ship.hpp @@ -15,7 +15,7 @@ /** Yapf Node for ships */ template <class Tkey_> struct CYapfShipNodeT - : CYapfNodeT<Tkey_, CYapfShipNodeT<Tkey_> > + : CYapfNodeTrackT<Tkey_, CYapfShipNodeT<Tkey_> > { }; diff --git a/src/pathfinder/yapf/yapf_road.cpp b/src/pathfinder/yapf/yapf_road.cpp index 82493241f..9e4317810 100644 --- a/src/pathfinder/yapf/yapf_road.cpp +++ b/src/pathfinder/yapf/yapf_road.cpp @@ -90,6 +90,9 @@ protected: } else { /* non-diagonal trackdir */ cost = YAPF_TILE_CORNER_LENGTH + Yapf().PfGetSettings().road_curve_penalty; + + /* Extra cost for traffic lights. */ + if (HasTrafficLights(tile)) cost += Yapf().PfGetSettings().road_trafficlight_penalty; } return cost; } diff --git a/src/rail.h b/src/rail.h index 249c1bea1..d8386fd15 100644 --- a/src/rail.h +++ b/src/rail.h @@ -141,7 +141,7 @@ struct RailtypeInfo { SpriteID signals[SIGTYPE_END][2][2]; ///< signal GUI sprites (type, variant, state) } gui_sprites; - struct { + struct Cursor { CursorID rail_ns; ///< Cursor for building rail in N-S direction CursorID rail_swne; ///< Cursor for building rail in X direction CursorID rail_ew; ///< Cursor for building rail in E-W direction diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index e362a132b..327d8f998 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -41,6 +41,7 @@ typedef SmallVector<Train *, 16> TrainList; RailtypeInfo _railtypes[RAILTYPE_END]; +TileIndex _rail_track_endtile; ///< The end of a rail track; as hidden return from the rail build/remove command for GUI purposes. assert_compile(sizeof(_original_railtypes) <= sizeof(_railtypes)); @@ -557,6 +558,7 @@ CommandCost CmdBuildSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1, u } cost.AddCost(RailBuildCost(railtype)); + _rail_track_endtile = tile; return cost; } @@ -701,6 +703,7 @@ CommandCost CmdRemoveSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1, if (v != NULL) TryPathReserve(v, true); } + _rail_track_endtile = tile; return cost; } @@ -1011,9 +1014,12 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, if (sigtype > SIGTYPE_LAST) return CMD_ERROR; if (cycle_start > cycle_stop || cycle_stop > SIGTYPE_LAST) return CMD_ERROR; - /* You can only build signals on plain rail tiles, and the selected track must exist */ - if (!ValParamTrackOrientation(track) || !IsPlainRailTile(tile) || - !HasTrack(tile, track)) { + /* You can only build signals on plain rail tiles or tunnel/bridges, and the selected track must exist */ + if (IsTileType(tile, MP_TUNNELBRIDGE)) { + if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) return CMD_ERROR; + CommandCost ret = EnsureNoTrainOnTrack(GetOtherTunnelBridgeEnd(tile), track); + //if (ret.Failed()) return ret; + } else if (!ValParamTrackOrientation(track) || !IsPlainRailTile(tile) || !HasTrack(tile, track)) { return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK); } /* Protect against invalid signal copying */ @@ -1022,6 +1028,53 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, CommandCost ret = CheckTileOwnership(tile); if (ret.Failed()) return ret; + CommandCost cost; + /* handle signals simulation on tunnel/bridge. */ + if (IsTileType(tile, MP_TUNNELBRIDGE)) { + TileIndex tile_exit = GetOtherTunnelBridgeEnd(tile); + cost = CommandCost(); + if (!HasWormholeSignals(tile)) { // toggle signal zero costs. + if (p2 != 12) cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_SIGNALS] * ((GetTunnelBridgeLength(tile, tile_exit) + 4) >> 2)); // minimal 1 + } + if (flags & DC_EXEC) { + if (p2 == 0 && HasWormholeSignals(tile)){ // Toggle signal if already signals present. + if (IsTunnelBridgeEntrance (tile)) { + ClrBitTunnelBridgeSignal(tile); + ClrBitTunnelBridgeExit(tile_exit); + SetBitTunnelBridgeExit(tile); + SetBitTunnelBridgeSignal(tile_exit); + } else { + ClrBitTunnelBridgeSignal(tile_exit); + ClrBitTunnelBridgeExit(tile); + SetBitTunnelBridgeExit(tile_exit); + SetBitTunnelBridgeSignal(tile); + } + } else{ + /* Create one direction tunnel/bridge if required. */ + if (p2 == 0) { + SetBitTunnelBridgeSignal(tile); + SetBitTunnelBridgeExit(tile_exit); + } else if (p2 == 4 || p2 == 8) { + DiagDirection tbdir = GetTunnelBridgeDirection(tile); + /* If signal only on one side build accoringly one-way tunnel/bridge. */ + if ((p2 == 8 && (tbdir == DIAGDIR_NE || tbdir == DIAGDIR_SE)) || + (p2 == 4 && (tbdir == DIAGDIR_SW || tbdir == DIAGDIR_NW))) { + SetBitTunnelBridgeSignal(tile); + SetBitTunnelBridgeExit(tile_exit); + } else { + SetBitTunnelBridgeSignal(tile_exit); + SetBitTunnelBridgeExit(tile); + } + } + } + MarkTileDirtyByTile(tile); + MarkTileDirtyByTile(tile_exit); + AddSideToSignalBuffer(tile, INVALID_DIAGDIR, _current_company); + YapfNotifyTrackLayoutChange(tile, track); + } + return cost; + } + { /* See if this is a valid track combination for signals, (ie, no overlap) */ TrackBits trackbits = GetTrackBits(tile); @@ -1038,7 +1091,6 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, /* you can not convert a signal if no signal is on track */ if (convert_signal && !HasSignalOnTrack(tile, track)) return_cmd_error(STR_ERROR_THERE_ARE_NO_SIGNALS); - CommandCost cost; if (!HasSignalOnTrack(tile, track)) { /* build new signals */ cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_SIGNALS]); @@ -1196,6 +1248,7 @@ static bool CheckSignalAutoFill(TileIndex &tile, Trackdir &trackdir, int &signal return true; case MP_TUNNELBRIDGE: { + if (!remove && HasWormholeSignals(tile)) return false; TileIndex orig_tile = tile; // backup old value if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) return false; @@ -1307,7 +1360,8 @@ static CommandCost CmdSignalTrackHelper(TileIndex tile, DoCommandFlag flags, uin bool had_success = false; for (;;) { /* only build/remove signals with the specified density */ - if (remove || minimise_gaps || signal_ctr % signal_density == 0) { + + if (remove || minimise_gaps || signal_ctr % signal_density == 0 || IsTileType(tile, MP_TUNNELBRIDGE)) { uint32 p1 = GB(TrackdirToTrack(trackdir), 0, 3); SB(p1, 3, 1, mode); SB(p1, 4, 1, semaphores); @@ -1343,13 +1397,20 @@ static CommandCost CmdSignalTrackHelper(TileIndex tile, DoCommandFlag flags, uin /* Collect cost. */ if (!test_only) { - /* Be user-friendly and try placing signals as much as possible */ - if (ret.Succeeded()) { - had_success = true; - total_cost.AddCost(ret); - last_used_ctr = last_suitable_ctr; - last_suitable_tile = INVALID_TILE; + /* Be user-friendly and try placing signals as much as possible */ + if (ret.Succeeded()) { + had_success = true; + if (IsTileType(tile, MP_TUNNELBRIDGE)) { + if ((!autofill && GetTunnelBridgeDirection(tile) == TrackdirToExitdir(trackdir)) || + (autofill && GetTunnelBridgeDirection(tile) != TrackdirToExitdir(trackdir))) { + total_cost.AddCost(ret); + } } else { + total_cost.AddCost(ret); + } + last_used_ctr = last_suitable_ctr; + last_suitable_tile = INVALID_TILE; + } else { /* The "No railway" error is the least important one. */ if (ret.GetErrorMessage() != STR_ERROR_THERE_IS_NO_RAILROAD_TRACK || last_error.GetErrorMessage() == INVALID_STRING_ID) { @@ -1420,22 +1481,48 @@ CommandCost CmdBuildSignalTrack(TileIndex tile, DoCommandFlag flags, uint32 p1, CommandCost CmdRemoveSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { Track track = Extract<Track, 0, 3>(p1); + Money cost = _price[PR_CLEAR_SIGNALS]; - if (!ValParamTrackOrientation(track) || !IsPlainRailTile(tile) || !HasTrack(tile, track)) { - return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK); - } - if (!HasSignalOnTrack(tile, track)) { - return_cmd_error(STR_ERROR_THERE_ARE_NO_SIGNALS); + if (IsTileType(tile, MP_TUNNELBRIDGE)) { + TileIndex end = GetOtherTunnelBridgeEnd(tile); + if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK); + if (!HasWormholeSignals(tile)) return_cmd_error(STR_ERROR_THERE_ARE_NO_SIGNALS); + + cost *= ((GetTunnelBridgeLength(tile, end) + 4) >> 2); + + CommandCost ret = EnsureNoTrainOnTrack(GetOtherTunnelBridgeEnd(tile), track); + //if (ret.Failed()) return ret; + } else { + if (!ValParamTrackOrientation(track) || !IsPlainRailTile(tile) || !HasTrack(tile, track)) { + return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK); + } + if (!HasSignalOnTrack(tile, track)) { + return_cmd_error(STR_ERROR_THERE_ARE_NO_SIGNALS); + } + CommandCost ret = EnsureNoTrainOnTrack(tile, track); + //if (ret.Failed()) return ret; } /* Only water can remove signals from anyone */ if (_current_company != OWNER_WATER) { - CommandCost ret = CheckTileOwnership(tile); - if (ret.Failed()) return ret; } /* Do it? */ if (flags & DC_EXEC) { + + if (HasWormholeSignals(tile)) { // handle tunnel/bridge signals. + TileIndex end = GetOtherTunnelBridgeEnd(tile); + ClrBitTunnelBridgeExit(tile); + ClrBitTunnelBridgeExit(end); + ClrBitTunnelBridgeSignal(tile); + ClrBitTunnelBridgeSignal(end); + _m[tile].m2 = 0; + _m[end].m2 = 0; + MarkTileDirtyByTile(tile); + MarkTileDirtyByTile(end); + return CommandCost(EXPENSES_CONSTRUCTION, cost); + } + Train *v = NULL; if (HasReservedTracks(tile, TrackToTrackBits(track))) { v = GetTrainForReservation(tile, track); @@ -1471,7 +1558,7 @@ CommandCost CmdRemoveSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1 MarkTileDirtyByTile(tile); } - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_SIGNALS]); + return CommandCost(EXPENSES_CONSTRUCTION, cost); } /** @@ -1946,6 +2033,7 @@ static void DrawTrackFence_WE_1(const TileInfo *ti, SpriteID base_image) ti->x + TILE_SIZE / 2, ti->y + TILE_SIZE / 2, 1, 1, 4, z); } + /** * Draw fence at northern side of track. */ @@ -2483,11 +2571,14 @@ static Foundation GetFoundation_Track(TileIndex tile, Slope tileh) return IsPlainRail(tile) ? GetRailFoundation(tileh, GetTrackBits(tile)) : FlatteningFoundation(tileh); } + static void TileLoop_Track(TileIndex tile) { RailGroundType old_ground = GetRailGroundType(tile); RailGroundType new_ground; + ReduceStuckCounter(tile); + if (old_ground == RAIL_GROUND_WATER) { TileLoop_Water(tile); return; diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index ed1bd0ac0..254be5a81 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -49,6 +49,8 @@ static bool _convert_signal_button; ///< convert signal button in the s static SignalVariant _cur_signal_variant; ///< set the signal variant (for signal GUI) static SignalType _cur_signal_type; ///< set the signal type (for signal GUI) +extern TileIndex _rail_track_endtile; // rail_cmd.cpp + /* Map the setting: default_signal_type to the corresponding signal type */ static const SignalType _default_signal_type[] = {SIGTYPE_NORMAL, SIGTYPE_PBS, SIGTYPE_PBS_ONEWAY}; @@ -88,9 +90,9 @@ void CcPlaySound1E(const CommandCost &result, TileIndex tile, uint32 p1, uint32 if (result.Succeeded() && _settings_client.sound.confirm) SndPlayTileFx(SND_20_SPLAT_2, tile); } -static void GenericPlaceRail(TileIndex tile, int cmd) +static bool GenericPlaceRail(TileIndex tile, Track track) { - DoCommandP(tile, _cur_railtype, cmd, + return DoCommandP(tile, _cur_railtype, track, _remove_button_clicked ? CMD_REMOVE_SINGLE_RAIL | CMD_MSG(STR_ERROR_CAN_T_REMOVE_RAILROAD_TRACK) : CMD_BUILD_SINGLE_RAIL | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_TRACK), @@ -279,6 +281,17 @@ void CcBuildRailTunnel(const CommandCost &result, TileIndex tile, uint32 p1, uin } /** + * Place a rail tunnel. + * @param tile Position of the first tile of the tunnel. + */ +static void PlaceRail_Tunnel(TileIndex tile) +{ + if (DoCommandP(tile, _cur_railtype | (TRANSPORT_RAIL << 8), 0, CMD_BUILD_TUNNEL | CMD_MSG(STR_ERROR_CAN_T_BUILD_TUNNEL_HERE), CcBuildRailTunnel)) { + StoreRailPlacementEndpoints(tile, _build_tunnel_endtile, TileX(tile) == TileX(_build_tunnel_endtile) ? TRACK_Y : TRACK_X, false); + } +} + +/** * Toggles state of the Remove button of Build rail toolbar * @param w window the button belongs to */ @@ -346,9 +359,9 @@ static void BuildRailClick_Remove(Window *w) } } -static void DoRailroadTrack(int mode) +static bool DoRailroadTrack(TileIndex start_tile, TileIndex end_tile, Track track) { - DoCommandP(TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y), _cur_railtype | (mode << 4), + return DoCommandP(start_tile, end_tile, _cur_railtype | (track << 4), _remove_button_clicked ? CMD_REMOVE_RAILROAD_TRACK | CMD_MSG(STR_ERROR_CAN_T_REMOVE_RAILROAD_TRACK) : CMD_BUILD_RAILROAD_TRACK | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_TRACK)); @@ -356,14 +369,15 @@ static void DoRailroadTrack(int mode) static void HandleAutodirPlacement() { - int trackstat = _thd.drawstyle & HT_DIR_MASK; // 0..5 - - if (_thd.drawstyle & HT_RAIL) { // one tile case - GenericPlaceRail(TileVirtXY(_thd.selend.x, _thd.selend.y), trackstat); - return; + Track track = (Track)(_thd.drawstyle & HT_DIR_MASK); // 0..5 + TileIndex start_tile = TileVirtXY(_thd.selstart.x, _thd.selstart.y); + TileIndex end_tile = TileVirtXY(_thd.selend.x, _thd.selend.y); + + if (_thd.drawstyle & HT_RAIL ? + GenericPlaceRail(end_tile, track) : // one tile case + DoRailroadTrack(start_tile, end_tile, track)) { // longer track + StoreRailPlacementEndpoints(start_tile, _rail_track_endtile, track, true); } - - DoRailroadTrack(trackstat); } /** @@ -409,6 +423,20 @@ static void HandleAutoSignalPlacement() CcPlaySound1E); } +typedef CursorID (RailtypeInfo::Cursor::* RailCursorType); + +static const RailCursorType RAIL_CURSOR_NS = &RailtypeInfo::Cursor::rail_ns; +static const RailCursorType RAIL_CURSOR_X = &RailtypeInfo::Cursor::rail_swne; +static const RailCursorType RAIL_CURSOR_EW = &RailtypeInfo::Cursor::rail_ew; +static const RailCursorType RAIL_CURSOR_Y = &RailtypeInfo::Cursor::rail_nwse; +static const RailCursorType RAIL_CURSOR_AUTORAIL = &RailtypeInfo::Cursor::autorail; + +void HandleRailPlacePushButton(Window *w, int widget, RailCursorType cursor, HighLightStyle mode) +{ + if (_ctrl_pressed) mode |= HT_POLY; + HandlePlacePushButton(w, widget, GetRailTypeInfo(_cur_railtype)->cursor.*cursor, mode); +} + /** Rail toolbar management class. */ struct BuildRailToolbarWindow : Window { @@ -490,6 +518,24 @@ struct BuildRailToolbarWindow : Window { } } + virtual void DrawWidget(const Rect &r, int widget) const + { + switch (widget) { + case WID_RAT_BUILD_NS: + case WID_RAT_BUILD_X: + case WID_RAT_BUILD_EW: + case WID_RAT_BUILD_Y: + case WID_RAT_AUTORAIL: + if (this->IsWidgetLowered(widget) && _thd.place_mode & HT_POLY) { + DrawSprite(SPR_BLOT, PALETTE_TO_WHITE, r.left + WD_IMGBTN_LEFT, r.top + WD_IMGBTN_TOP); + } + break; + + default: + break; + } + } + virtual void SetStringParameters(int widget) const { if (widget == WID_RAT_CAPTION) { @@ -511,27 +557,27 @@ struct BuildRailToolbarWindow : Window { _remove_button_clicked = false; switch (widget) { case WID_RAT_BUILD_NS: - HandlePlacePushButton(this, WID_RAT_BUILD_NS, GetRailTypeInfo(_cur_railtype)->cursor.rail_ns, HT_LINE | HT_DIR_VL); + HandleRailPlacePushButton(this, WID_RAT_BUILD_NS, RAIL_CURSOR_NS, HT_LINE | HT_DIR_VL); this->last_user_action = widget; break; case WID_RAT_BUILD_X: - HandlePlacePushButton(this, WID_RAT_BUILD_X, GetRailTypeInfo(_cur_railtype)->cursor.rail_swne, HT_LINE | HT_DIR_X); + HandleRailPlacePushButton(this, WID_RAT_BUILD_X, RAIL_CURSOR_X, HT_LINE | HT_DIR_X); this->last_user_action = widget; break; case WID_RAT_BUILD_EW: - HandlePlacePushButton(this, WID_RAT_BUILD_EW, GetRailTypeInfo(_cur_railtype)->cursor.rail_ew, HT_LINE | HT_DIR_HL); + HandleRailPlacePushButton(this, WID_RAT_BUILD_EW, RAIL_CURSOR_EW, HT_LINE | HT_DIR_HL); this->last_user_action = widget; break; case WID_RAT_BUILD_Y: - HandlePlacePushButton(this, WID_RAT_BUILD_Y, GetRailTypeInfo(_cur_railtype)->cursor.rail_nwse, HT_LINE | HT_DIR_Y); + HandleRailPlacePushButton(this, WID_RAT_BUILD_Y, RAIL_CURSOR_Y, HT_LINE | HT_DIR_Y); this->last_user_action = widget; break; case WID_RAT_AUTORAIL: - HandlePlacePushButton(this, WID_RAT_AUTORAIL, GetRailTypeInfo(_cur_railtype)->cursor.autorail, HT_RAIL); + HandleRailPlacePushButton(this, WID_RAT_AUTORAIL, RAIL_CURSOR_AUTORAIL, HT_RAIL); this->last_user_action = widget; break; @@ -607,6 +653,14 @@ struct BuildRailToolbarWindow : Window { virtual void OnPlaceObject(Point pt, TileIndex tile) { + /* Test if we are in "polyline" mode. */ + if (_thd.drawstyle & HT_POLY) { + /* There is no drag-dropping while in "polyline" mode, we build the track + * immidiately after pushing mouse button down. */ + HandleAutodirPlacement(); + return; + } + switch (this->last_user_action) { case WID_RAT_BUILD_NS: VpStartPlaceSizing(tile, VPM_FIX_VERTICAL | VPM_RAILDIRS, DDSP_PLACE_RAIL); @@ -655,7 +709,7 @@ struct BuildRailToolbarWindow : Window { break; case WID_RAT_BUILD_TUNNEL: - DoCommandP(tile, _cur_railtype | (TRANSPORT_RAIL << 8), 0, CMD_BUILD_TUNNEL | CMD_MSG(STR_ERROR_CAN_T_BUILD_TUNNEL_HERE), CcBuildRailTunnel); + PlaceRail_Tunnel(tile); break; case WID_RAT_CONVERT_RAIL: @@ -758,13 +812,15 @@ struct BuildRailToolbarWindow : Window { }; const uint16 _railtoolbar_autorail_keys[] = {'5', 'A' | WKC_GLOBAL_HOTKEY, 0}; +const uint16 _railtoolbar_polyline_autorail_keys[] = {'5' | WKC_CTRL, 'A' | WKC_GLOBAL_HOTKEY | WKC_CTRL, 0}; Hotkey<BuildRailToolbarWindow> BuildRailToolbarWindow::railtoolbar_hotkeys[] = { Hotkey<BuildRailToolbarWindow>('1', "build_ns", WID_RAT_BUILD_NS), Hotkey<BuildRailToolbarWindow>('2', "build_x", WID_RAT_BUILD_X), Hotkey<BuildRailToolbarWindow>('3', "build_ew", WID_RAT_BUILD_EW), Hotkey<BuildRailToolbarWindow>('4', "build_y", WID_RAT_BUILD_Y), - Hotkey<BuildRailToolbarWindow>(_railtoolbar_autorail_keys, "autorail", WID_RAT_AUTORAIL), + Hotkey<BuildRailToolbarWindow>(_railtoolbar_autorail_keys, "autorail", WID_RAT_AUTORAIL), // without CTRL + Hotkey<BuildRailToolbarWindow>(_railtoolbar_polyline_autorail_keys, "polyrail", WID_RAT_AUTORAIL), // with CTRL Hotkey<BuildRailToolbarWindow>('6', "demolish", WID_RAT_DEMOLISH), Hotkey<BuildRailToolbarWindow>('7', "depot", WID_RAT_BUILD_DEPOT), Hotkey<BuildRailToolbarWindow>('8', "waypoint", WID_RAT_BUILD_WAYPOINT), diff --git a/src/rail_map.h b/src/rail_map.h index c8033a311..8a385c5b4 100644 --- a/src/rail_map.h +++ b/src/rail_map.h @@ -525,9 +525,7 @@ static inline void MakeRailNormal(TileIndex t, Owner o, TrackBits b, RailType r) _m[t].m3 = r; _m[t].m4 = 0; _m[t].m5 = RAIL_TILE_NORMAL << 6 | b; - SB(_m[t].m6, 2, 4, 0); - _me[t].m7 = 0; -} + SB(_m[t].m6, 2, 4, 0); _me[t].m7 = 0;} static inline void MakeRailDepot(TileIndex t, Owner o, DepotID did, DiagDirection d, RailType r) @@ -542,4 +540,28 @@ static inline void MakeRailDepot(TileIndex t, Owner o, DepotID did, DiagDirectio _me[t].m7 = 0; } + + +static inline void IncreaseStuckCounter(TileIndex t) +{ + if (!IsTileType(t, MP_RAILWAY)) return; + if (_me[t].m7 == 255) return; + _me[t].m7++; +} + + +static inline void ReduceStuckCounter(TileIndex t) +{ + if (!IsTileType(t, MP_RAILWAY)) return; + _me[t].m7 -= _me[t].m7/4; +} + + +static inline byte GetStuckCounter(TileIndex t) +{ + if (!IsTileType(t, MP_RAILWAY)) return 0; + return _me[t].m7; + +} + #endif /* RAIL_MAP_H */ diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index 7931fa965..5954b6547 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -35,6 +35,7 @@ #include "date_func.h" #include "genworld.h" #include "company_gui.h" +#include "trafficlight_func.h" #include "table/strings.h" @@ -173,9 +174,10 @@ CommandCost CheckAllowRemoveRoad(TileIndex tile, RoadBits remove, Owner owner, R * @param pieces roadbits to remove * @param rt roadtype to remove * @param crossing_check should we check if there is a tram track when we are removing road from crossing? + * @param trafficlights_check Should we check if there is a traffic light before removing road bits? * @param town_check should we check if the town allows removal? */ -static CommandCost RemoveRoad(TileIndex tile, DoCommandFlag flags, RoadBits pieces, RoadType rt, bool crossing_check, bool town_check = true) +static CommandCost RemoveRoad(TileIndex tile, DoCommandFlag flags, RoadBits pieces, RoadType rt, bool crossing_check, bool trafficlights_check, bool town_check = true) { RoadTypes rts = GetRoadTypes(tile); /* The tile doesn't have the given road type */ @@ -299,6 +301,11 @@ static CommandCost RemoveRoad(TileIndex tile, DoCommandFlag flags, RoadBits piec /* Now set present what it will be after the remove */ present ^= pieces; + /* Check if traffic lights are present and if removing them would cause the tile to have less than three roadbits (of any kind!). */ + if (trafficlights_check && HasTrafficLights(tile) && (CountBits(present | GetOtherRoadBits(tile, rt)) < 3)) { + return_cmd_error(STR_ERROR_MUST_REMOVE_TRAFFIC_LIGHTS_FIRST); + } + /* Check for invalid RoadBit combinations on slopes */ if (tileh != SLOPE_FLAT && present != ROAD_NONE && (present & _invalid_tileh_slopes_road[0][tileh & SLOPE_ELEVATED]) == present) { @@ -399,7 +406,6 @@ static CommandCost RemoveRoad(TileIndex tile, DoCommandFlag flags, RoadBits piec } } - /** * Calculate the costs for roads on slopes * Aside modify the RoadBits to fit on the slopes @@ -956,7 +962,7 @@ CommandCost CmdRemoveLongRoad(TileIndex start_tile, DoCommandFlag flags, uint32 /* try to remove the halves. */ if (bits != 0) { - CommandCost ret = RemoveRoad(tile, flags & ~DC_EXEC, bits, rt, true); + CommandCost ret = RemoveRoad(tile, flags & ~DC_EXEC, bits, rt, true, true); if (ret.Succeeded()) { if (flags & DC_EXEC) { money -= ret.GetCost(); @@ -964,7 +970,7 @@ CommandCost CmdRemoveLongRoad(TileIndex start_tile, DoCommandFlag flags, uint32 _additional_cash_required = DoCommand(start_tile, end_tile, p2, flags & ~DC_EXEC, CMD_REMOVE_LONG_ROAD).GetCost(); return cost; } - RemoveRoad(tile, flags, bits, rt, true, false); + RemoveRoad(tile, flags, bits, rt, true, true, false); } cost.AddCost(ret); had_success = true; @@ -1067,9 +1073,18 @@ static CommandCost ClearTile_Road(TileIndex tile, DoCommandFlag flags) /* Clear the road if only one piece is on the tile OR we are not using the DC_AUTO flag */ if ((HasExactlyOneBit(b) && GetRoadBits(tile, ROADTYPE_TRAM) == ROAD_NONE) || !(flags & DC_AUTO)) { CommandCost ret(EXPENSES_CONSTRUCTION); + + /* Remove traffic light if necessary. */ + if (HasTrafficLights(tile)) { + CommandCost tl_ret = CmdRemoveTrafficLights(tile, flags, 0, 0, 0); + if (tl_ret.Failed()) return tl_ret; + ret.AddCost(tl_ret); + } + + /* Remove road bits. */ RoadType rt; FOR_EACH_SET_ROADTYPE(rt, GetRoadTypes(tile)) { - CommandCost tmp_ret = RemoveRoad(tile, flags, GetRoadBits(tile, rt), rt, true); + CommandCost tmp_ret = RemoveRoad(tile, flags, GetRoadBits(tile, rt), rt, true, false); if (tmp_ret.Failed()) return tmp_ret; ret.AddCost(tmp_ret); } @@ -1089,7 +1104,7 @@ static CommandCost ClearTile_Road(TileIndex tile, DoCommandFlag flags) RoadType rt = ROADTYPE_TRAM; do { if (HasBit(rts, rt)) { - CommandCost tmp_ret = RemoveRoad(tile, flags, GetCrossingRoadBits(tile), rt, false); + CommandCost tmp_ret = RemoveRoad(tile, flags, GetCrossingRoadBits(tile), rt, false, false); if (tmp_ret.Failed()) return tmp_ret; ret.AddCost(tmp_ret); } @@ -1212,7 +1227,7 @@ void DrawTramCatenary(const TileInfo *ti, RoadBits tram) * @param dy the offset from the top of the BB of the tile * @param h the height of the sprite to draw */ -static void DrawRoadDetail(SpriteID img, const TileInfo *ti, int dx, int dy, int h) +void DrawRoadDetail(SpriteID img, TileInfo *ti, int dx, int dy, int h) { int x = ti->x | dx; int y = ti->y | dy; @@ -1278,6 +1293,8 @@ static void DrawRoadBits(TileInfo *ti) } } + if (_settings_game.construction.traffic_lights && HasTrafficLights(ti->tile) && _cur_dpi->zoom <= ZOOM_LVL_DETAIL) DrawTrafficLights(ti); + if (HasRoadWorks(ti->tile)) { /* Road works */ DrawGroundSprite((road | tram) & ROAD_X ? SPR_EXCAVATION_X : SPR_EXCAVATION_Y, PAL_NONE); @@ -1466,6 +1483,19 @@ static Foundation GetFoundation_Road(TileIndex tile, Slope tileh) } } +/** + * Animates the traffic lights on a tile. + * @param tile This tile. + * @pre The setting must be anabled. + * @pre The tile must have trafficlights. + */ +static void AnimateTile_Road(TileIndex tile) +{ + if (_settings_game.construction.traffic_lights && HasTrafficLights(tile)) { + if (_tick_counter % 16 == 0) MarkTileDirtyByTile(tile); + } +} + static const Roadside _town_road_types[][2] = { { ROADSIDE_GRASS, ROADSIDE_GRASS }, { ROADSIDE_PAVED, ROADSIDE_PAVED }, @@ -1511,7 +1541,7 @@ static void TileLoop_Road(TileIndex tile) grp = GetTownRadiusGroup(t, tile); /* Show an animation to indicate road work */ - if (t->road_build_months != 0 && + if ((t->road_build_months != 0 || Chance16(_settings_game.economy.random_road_construction, 100)) && (DistanceManhattan(t->xy, tile) < 8 || grp != HZB_TOWN_EDGE) && IsNormalRoad(tile) && !HasAtMostOneBit(GetAllRoadBits(tile))) { if (GetFoundationSlope(tile) == SLOPE_FLAT && EnsureNoVehicleOnGround(tile).Succeeded() && Chance16(1, 40)) { @@ -1550,20 +1580,29 @@ static void TileLoop_Road(TileIndex tile) SetRoadside(tile, cur_rs); MarkTileDirtyByTile(tile); } - } else if (IncreaseRoadWorksCounter(tile)) { - TerminateRoadWorks(tile); + } else { + /* In the first half of roadworks, generate traffic lights with a certain chance. */ + if (_settings_game.construction.traffic_lights && _settings_game.construction.towns_build_traffic_lights && + (GetRoadWorksCounter(tile) < 8) && (CountBits(GetRoadBits(tile, ROADTYPE_ROAD)) >= 3) && + !HasTrafficLights(tile) && Chance16(1, 20)) { + CmdBuildTrafficLights(tile, DC_EXEC | DC_AUTO | DC_NO_WATER, 0, 0, 0); + MarkTileDirtyByTile(tile); + } + if (IncreaseRoadWorksCounter(tile)) { + TerminateRoadWorks(tile); - if (_settings_game.economy.mod_road_rebuild) { - /* Generate a nicer town surface */ - const RoadBits old_rb = GetAnyRoadBits(tile, ROADTYPE_ROAD); - const RoadBits new_rb = CleanUpRoadBits(tile, old_rb); + if (_settings_game.economy.mod_road_rebuild) { + /* Generate a nicer town surface. */ + const RoadBits old_rb = GetAnyRoadBits(tile, ROADTYPE_ROAD); + const RoadBits new_rb = CleanUpRoadBits(tile, old_rb); - if (old_rb != new_rb) { - RemoveRoad(tile, DC_EXEC | DC_AUTO | DC_NO_WATER, (old_rb ^ new_rb), ROADTYPE_ROAD, true); + if (old_rb != new_rb) { + RemoveRoad(tile, DC_EXEC | DC_AUTO | DC_NO_WATER, (old_rb ^ new_rb), ROADTYPE_ROAD, true, true); + } } - } - MarkTileDirtyByTile(tile); + MarkTileDirtyByTile(tile); + } } } @@ -1598,7 +1637,7 @@ static const TrackBits _road_trackbits[16] = { static TrackStatus GetTileTrackStatus_Road(TileIndex tile, TransportType mode, uint sub_mode, DiagDirection side) { TrackdirBits trackdirbits = TRACKDIR_BIT_NONE; - TrackdirBits red_signals = TRACKDIR_BIT_NONE; // crossing barred + TrackdirBits red_signals = TRACKDIR_BIT_NONE; // Crossing barred or red traffic light. switch (mode) { case TRANSPORT_RAIL: if (IsLevelCrossing(tile)) trackdirbits = TrackBitsToTrackdirBits(GetCrossingRailBits(tile)); @@ -1617,6 +1656,8 @@ static TrackStatus GetTileTrackStatus_Road(TileIndex tile, TransportType mode, u uint multiplier = drd_to_multiplier[rt == ROADTYPE_TRAM ? DRD_NONE : GetDisallowedRoadDirections(tile)]; if (!HasRoadWorks(tile)) trackdirbits = (TrackdirBits)(_road_trackbits[bits] * multiplier); + if (_settings_game.construction.traffic_lights && HasTrafficLights(tile)) + red_signals = trackdirbits & GetTrafficLightDisallowedDirections(tile); break; } @@ -1686,11 +1727,16 @@ static void GetTileDesc_Road(TileIndex tile, TileDesc *td) default: { RoadTypes rts = GetRoadTypes(tile); - td->str = (HasBit(rts, ROADTYPE_ROAD) ? _road_tile_strings[GetRoadside(tile)] : STR_LAI_ROAD_DESCRIPTION_TRAMWAY); + if (HasTrafficLights(tile)) { + td->str = STR_LAI_ROAD_DESCRIPTION_ROAD_WITH_TRAFFIC_LIGHTS; + } else { + td->str = (HasBit(rts, ROADTYPE_ROAD) ? _road_tile_strings[GetRoadside(tile)] : STR_LAI_ROAD_DESCRIPTION_TRAMWAY); + } if (HasBit(rts, ROADTYPE_ROAD)) road_owner = GetRoadOwner(tile, ROADTYPE_ROAD); if (HasBit(rts, ROADTYPE_TRAM)) tram_owner = GetRoadOwner(tile, ROADTYPE_TRAM); break; } + } /* Now we have to discover, if the tile has only one owner or many: @@ -1846,7 +1892,7 @@ extern const TileTypeProcs _tile_type_road_procs = { GetTileDesc_Road, // get_tile_desc_proc GetTileTrackStatus_Road, // get_tile_track_status_proc ClickTile_Road, // click_tile_proc - NULL, // animate_tile_proc + AnimateTile_Road, // animate_tile_proc TileLoop_Road, // tile_loop_proc ChangeTileOwner_Road, // change_tile_owner_proc NULL, // add_produced_cargo_proc diff --git a/src/road_cmd.h b/src/road_cmd.h index 3cf588dcd..e00f808f2 100644 --- a/src/road_cmd.h +++ b/src/road_cmd.h @@ -14,8 +14,11 @@ #include "direction_type.h" #include "road_type.h" +#include "tile_cmd.h" +#include "gfx_type.h" void DrawRoadDepotSprite(int x, int y, DiagDirection dir, RoadType rt); void UpdateNearestTownForRoadTiles(bool invalidate); +void DrawRoadDetail(SpriteID img, TileInfo *ti, int dx, int dy, int h); #endif /* ROAD_CMD_H */ diff --git a/src/road_gui.cpp b/src/road_gui.cpp index bf4ef40d1..1ca1b3ced 100644 --- a/src/road_gui.cpp +++ b/src/road_gui.cpp @@ -67,6 +67,23 @@ void CcPlaySound1D(const CommandCost &result, TileIndex tile, uint32 p1, uint32 } /** + * Place trafficlights on a tile or returns an error. + * @param tile This tile. + */ +static void PlaceRoad_TrafficLights(TileIndex tile, Window *w) +{ + if (_remove_button_clicked) { + DoCommandP(tile, 0, 0, CMD_REMOVE_TRAFFICLIGHTS | CMD_MSG(STR_ERROR_CAN_T_REMOVE_TRAFFIC_LIGHTS_FROM), CcPlaySound1D); + } else { + if (!_settings_game.construction.traffic_lights) { + DoCommandP(tile, 0, 0, CMD_BUILD_TRAFFICLIGHTS | CMD_MSG(STR_ERROR_BUILDING_TRAFFIC_LIGHTS_DISABLED), CcPlaySound1D); + } else{ + DoCommandP(tile, 0, 0, CMD_BUILD_TRAFFICLIGHTS | CMD_MSG(STR_ERROR_CAN_T_PLACE_TRAFFIC_LIGHTS), CcPlaySound1D); + } + } +} + +/** * Callback to start placing a bridge. * @param tile Start tile of the bridge. */ @@ -301,6 +318,12 @@ static bool RoadToolbar_CtrlChanged(Window *w) } } + /* Allow ctrl also for traffic lights. */ + if (w->IsWidgetLowered(WID_ROT_TRAFFIC_LIGHT)) { + ToggleRoadButton_Remove(w); + return true; + } + return false; } @@ -316,6 +339,11 @@ struct BuildRoadToolbarWindow : Window { WID_ROT_ONE_WAY, WIDGET_LIST_END); + this->SetWidgetsDisabledState(!_settings_game.construction.traffic_lights, + WID_ROT_TRAFFIC_LIGHT, + WIDGET_LIST_END); + if (!_settings_game.construction.traffic_lights && this->IsWidgetLowered(WID_ROT_TRAFFIC_LIGHT)) ResetObjectToPlace(); + this->OnInvalidateData(); this->last_started_action = WIDGET_LIST_END; @@ -365,6 +393,7 @@ struct BuildRoadToolbarWindow : Window { case WID_ROT_BUS_STATION: case WID_ROT_TRUCK_STATION: + case WID_ROT_TRAFFIC_LIGHT: this->DisableWidget(WID_ROT_ONE_WAY); this->SetWidgetDisabledState(WID_ROT_REMOVE, !this->IsWidgetLowered(clicked_widget)); break; @@ -449,6 +478,11 @@ struct BuildRoadToolbarWindow : Window { SetSelectionRed(false); break; + case WID_ROT_TRAFFIC_LIGHT: + HandlePlacePushButton(this, WID_ROT_TRAFFIC_LIGHT, SPR_CURSOR_TRAFFIC_LIGHT, HT_RECT); + this->last_started_action = widget; + break; + case WID_ROT_BUILD_BRIDGE: HandlePlacePushButton(this, WID_ROT_BUILD_BRIDGE, SPR_CURSOR_BRIDGE, HT_RECT); this->last_started_action = widget; @@ -523,6 +557,10 @@ struct BuildRoadToolbarWindow : Window { PlaceRoad_TruckStation(tile); break; + case WID_ROT_TRAFFIC_LIGHT: + PlaceRoad_TrafficLights(tile, this); + break; + case WID_ROT_BUILD_BRIDGE: PlaceRoad_Bridge(tile, this); break; @@ -704,6 +742,8 @@ static const NWidgetPart _nested_build_road_widgets[] = { NWidget(WWT_PANEL, COLOUR_DARK_GREEN, -1), SetMinimalSize(0, 22), SetFill(1, 1), EndContainer(), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_ONE_WAY), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_ONE_WAY, STR_ROAD_TOOLBAR_TOOLTIP_TOGGLE_ONE_WAY_ROAD), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_TRAFFIC_LIGHT), + SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_TRAFFIC_LIGHT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAFFIC_LIGHT), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUILD_BRIDGE), SetFill(0, 1), SetMinimalSize(43, 22), SetDataTip(SPR_IMG_BRIDGE, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_BRIDGE), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUILD_TUNNEL), @@ -743,6 +783,8 @@ static const NWidgetPart _nested_build_tramway_widgets[] = { SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_TRUCK_BAY, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_CARGO_TRAM_STATION), NWidget(WWT_PANEL, COLOUR_DARK_GREEN, -1), SetMinimalSize(0, 22), SetFill(1, 1), EndContainer(), NWidget(WWT_EMPTY, COLOUR_DARK_GREEN, WID_ROT_ONE_WAY), SetMinimalSize(0, 0), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_TRAFFIC_LIGHT), + SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_TRAFFIC_LIGHT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAFFIC_LIGHT), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUILD_BRIDGE), SetFill(0, 1), SetMinimalSize(43, 22), SetDataTip(SPR_IMG_BRIDGE, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAMWAY_BRIDGE), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUILD_TUNNEL), @@ -803,6 +845,8 @@ static const NWidgetPart _nested_build_road_scen_widgets[] = { NWidget(WWT_PANEL, COLOUR_DARK_GREEN, -1), SetMinimalSize(0, 22), SetFill(1, 1), EndContainer(), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_ONE_WAY), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_ONE_WAY, STR_ROAD_TOOLBAR_TOOLTIP_TOGGLE_ONE_WAY_ROAD), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_TRAFFIC_LIGHT), + SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_TRAFFIC_LIGHT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAFFIC_LIGHT), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUILD_BRIDGE), SetFill(0, 1), SetMinimalSize(43, 22), SetDataTip(SPR_IMG_BRIDGE, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_BRIDGE), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUILD_TUNNEL), diff --git a/src/road_map.h b/src/road_map.h index 6fe1a02b1..f79192f1e 100644 --- a/src/road_map.h +++ b/src/road_map.h @@ -251,6 +251,29 @@ static inline bool HasTownOwnedRoad(TileIndex t) return HasTileRoadType(t, ROADTYPE_ROAD) && IsRoadOwner(t, ROADTYPE_ROAD, OWNER_TOWN); } +static inline void MakeTrafficLights(TileIndex t) +{ + assert(IsTileType(t, MP_ROAD)); + assert(GetRoadTileType(t) == ROAD_TILE_NORMAL); + SetBit(_me[t].m7, 4); +} + +static inline void ClearTrafficLights(TileIndex t) +{ + assert(IsTileType(t, MP_ROAD)); + assert(GetRoadTileType(t) == ROAD_TILE_NORMAL); + ClrBit(_me[t].m7, 4); +} + +/** + * Check if a tile has traffic lights returns true if tile has traffic lights. + * @param t The tile to check. + */ +static inline bool HasTrafficLights(TileIndex t) +{ + return (IsTileType(t, MP_ROAD) && (GetRoadTileType(t) == ROAD_TILE_NORMAL) && HasBit(_me[t].m7, 4)); +} + /** Which directions are disallowed ? */ enum DisallowedRoadDirections { DRD_NONE, ///< None of the directions are disallowed @@ -493,7 +516,14 @@ static inline bool IncreaseRoadWorksCounter(TileIndex t) { AB(_me[t].m7, 0, 4, 1); - return GB(_me[t].m7, 0, 4) == 15; +// return GB(_me[t].m7, 0, 4) == 15; +//DC-1 - kevesebb idot az utjavitasra + return GB(_me[t].m7, 0, 4) == 1; +} + +static inline byte GetRoadWorksCounter(TileIndex t) +{ + return GB(_m[t].m3, 0, 4); } /** diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index e740e26f9..13ec454e0 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -216,6 +216,8 @@ void RoadVehUpdateCache(RoadVehicle *v, bool same_length) v->gcache.cached_total_length = 0; + uint32 cargo_mask = 0; + for (RoadVehicle *u = v; u != NULL; u = u->Next()) { /* Check the v->first cache. */ assert(u->First() == v); @@ -237,12 +239,15 @@ void RoadVehUpdateCache(RoadVehicle *v, bool same_length) /* Invalidate the vehicle colour map */ u->colourmap = PAL_NONE; + /* Update carried cargo. */ + if (u->cargo_type != INVALID_CARGO && u->cargo_cap > 0) SetBit(cargo_mask, u->cargo_type); /* Update cargo aging period. */ u->vcache.cached_cargo_age_period = GetVehicleProperty(u, PROP_ROADVEH_CARGO_AGE_PERIOD, EngInfo(u->engine_type)->cargo_age_period); } uint max_speed = GetVehicleProperty(v, PROP_ROADVEH_SPEED, 0); v->vcache.cached_max_speed = (max_speed != 0) ? max_speed * 4 : RoadVehInfo(v->engine_type)->max_speed; + v->vcache.cached_cargo_mask = cargo_mask; } /** @@ -1392,9 +1397,8 @@ again: v->owner == GetTileOwner(v->tile) && !v->current_order.IsType(OT_LEAVESTATION) && GetRoadStopType(v->tile) == (v->IsBus() ? ROADSTOP_BUS : ROADSTOP_TRUCK)) { Station *st = Station::GetByTile(v->tile); - v->last_station_visited = st->index; RoadVehArrivesAt(v, st); - v->BeginLoading(); + v->BeginLoading(st->index); } return false; } @@ -1453,13 +1457,13 @@ again: rs->SetEntranceBusy(false); SetBit(v->state, RVS_ENTERED_STOP); - v->last_station_visited = st->index; - if (IsDriveThroughStopTile(v->tile) || (v->current_order.IsType(OT_GOTO_STATION) && v->current_order.GetDestination() == st->index)) { RoadVehArrivesAt(v, st); - v->BeginLoading(); + v->BeginLoading(st->index); return false; } + + v->last_station_visited = st->index; } else { /* Vehicle is ready to leave a bay in a road stop */ if (rs->IsEntranceBusy()) { diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 15c44d392..2f84ef0e8 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -51,7 +51,9 @@ #include "../core/backup_type.hpp" #include "../smallmap_gui.h" #include "../news_func.h" +#include "../cargodest_func.h" #include "../error.h" +#include "../trafficlight_func.h" #include "saveload_internal.h" @@ -254,6 +256,7 @@ static void InitializeWindowsAndCaches() Station::RecomputeIndustriesNearForAll(); RebuildSubsidisedSourceAndDestinationCache(); + RebuildCargoLinkCounts(); /* Towns have a noise controlled number of airports system * So each airport's noise value must be added to the town->noise_reached value @@ -2761,6 +2764,50 @@ bool AfterLoadGame() _settings_game.script.settings_profile = IsInsideMM(_old_diff_level, SP_BEGIN, SP_END) ? _old_diff_level : (uint)SP_MEDIUM; } + if (IsSavegameVersionBefore(180)) { + /* Update cargo acceptance map of towns. */ + for (TileIndex t = 0; t < map_size; t++) { + if (!IsTileType(t, MP_HOUSE)) continue; + Town::Get(GetTownIndex(t))->cargo_accepted.Add(t); + } + + Town *town; + FOR_ALL_TOWNS(town) { + UpdateTownCargoes(town); + } + + /* Update cargo acceptance of industries. */ + Industry *ind; + FOR_ALL_INDUSTRIES(ind) { + UpdateIndustryAcceptance(ind); + ind->average_production[0] = ind->last_month_production[0]; + ind->average_production[1] = ind->last_month_production[1]; + } + + UpdateCargoLinks(); + + Vehicle *v; + FOR_ALL_VEHICLES(v) { + /* Set the current order index from the order list. */ + Order *o = v->GetOrder(v->cur_implicit_order_index); + if (o != NULL) v->current_order.index = o->index; + + /* Pre-fill route links from orders. */ + if (v->IsFrontEngine()) PrefillRouteLinks(v); + } + } + + if (IsSavegameVersionBefore(TL_SV)) { + /* Savegames before this version can not have trafficlights already. + * Sometimes they are added randomly when loading old savegames. + * Unfortunately also in the wrong places -> not on crossings. + * + * Iterate over the whole map and remove any trafficlights found. */ + for (TileIndex tile = 0; tile < MapSize(); tile++) { + if (HasTrafficLights(tile)) ClearTrafficLights(tile); + } + } + /* Road stops is 'only' updating some caches */ AfterLoadRoadStops(); AfterLoadLabelMaps(); diff --git a/src/saveload/cargodest_sl.cpp b/src/saveload/cargodest_sl.cpp new file mode 100644 index 000000000..1950268c0 --- /dev/null +++ b/src/saveload/cargodest_sl.cpp @@ -0,0 +1,181 @@ +/* $Id$ */ + +/* + * 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 cargodest_sl.cpp Code handling saving and loading of cargo destinations. */ + +#include "../stdafx.h" +#include "../cargodest_base.h" +#include "../town.h" +#include "../industry.h" +#include "saveload.h" + +static uint32 _cargolink_uint; +static const SaveLoadGlobVarList _cargolink_uint_desc[] = { + SLEG_VAR(_cargolink_uint, SLE_UINT32), + SLEG_END() +}; + +static const SaveLoad _cargolink_desc[] = { + SLE_VAR(CargoLink, amount.old_max, SLE_UINT32), + SLE_VAR(CargoLink, amount.new_max, SLE_UINT32), + SLE_VAR(CargoLink, amount.old_act, SLE_UINT32), + SLE_VAR(CargoLink, amount.new_act, SLE_UINT32), + SLE_VAR(CargoLink, weight, SLE_UINT32), + SLE_VAR(CargoLink, weight_mod, SLE_UINT8), + SLE_END() +}; + +void CargoSourceSink::SaveCargoSourceSink() +{ + if (IsSavegameVersionBefore(180)) return; + + static const SaveLoad _cargosourcesink_desc[] = { + SLE_ARR(CargoSourceSink, cargo_links_weight, SLE_UINT32, NUM_CARGO), + SLE_END() + }; + SlObject(this, _cargosourcesink_desc); + + for (uint cid = 0; cid < lengthof(this->cargo_links); cid++) { + _cargolink_uint = this->cargo_links[cid].Length(); + SlObject(NULL, _cargolink_uint_desc); + for (CargoLink *l = this->cargo_links[cid].Begin(); l != this->cargo_links[cid].End(); l++) { + SourceID dest = INVALID_SOURCE; + SourceTypeByte type; + type = ST_TOWN; + + if (l->dest != NULL) { + type = l->dest->GetType(); + dest = l->dest->GetID(); + } + + /* Pack type and destination index into temp variable. */ + assert_compile(sizeof(SourceID) <= 3); + _cargolink_uint = type | (dest << 8); + + SlGlobList(_cargolink_uint_desc); + SlObject(l, _cargolink_desc); + } + } +} + +void CargoSourceSink::LoadCargoSourceSink() +{ + if (IsSavegameVersionBefore(180)) return; + + static const SaveLoad _cargosourcesink_desc[] = { + SLE_ARR(CargoSourceSink, cargo_links_weight, SLE_UINT32, NUM_CARGO), + SLE_END() + }; + SlObject(this, _cargosourcesink_desc); + + for (uint cid = 0; cid < lengthof(this->cargo_links); cid++) { + /* Remove links created by constructors. */ + this->cargo_links[cid].Clear(); + /* Read vector length and allocate storage. */ + SlObject(NULL, _cargolink_uint_desc); + this->cargo_links[cid].Append(_cargolink_uint); + + for (CargoLink *l = this->cargo_links[cid].Begin(); l != this->cargo_links[cid].End(); l++) { + /* Read packed type and dest and store in dest pointer. */ + SlGlobList(_cargolink_uint_desc); + *(size_t*)&l->dest = _cargolink_uint; + + SlObject(l, _cargolink_desc); + } + } +} + +void CargoSourceSink::PtrsCargoSourceSink() +{ + if (IsSavegameVersionBefore(180)) return; + + for (uint cid = 0; cid < lengthof(this->cargo_links); cid++) { + for (CargoLink *l = this->cargo_links[cid].Begin(); l != this->cargo_links[cid].End(); l++) { + /* Extract type and destination index. */ + SourceType type = (SourceType)((size_t)l->dest & 0xFF); + SourceID dest = (SourceID)((size_t)l->dest >> 8); + + /* Resolve index. */ + l->dest = NULL; + if (dest != INVALID_SOURCE) { + switch (type) { + case ST_TOWN: + if (!Town::IsValidID(dest)) SlErrorCorrupt("Invalid cargo link destination"); + l->dest = Town::Get(dest); + break; + + case ST_INDUSTRY: + if (!Industry::IsValidID(dest)) SlErrorCorrupt("Invalid cargo link destination"); + l->dest = Industry::Get(dest); + break; + + default: + SlErrorCorrupt("Invalid cargo link destination type"); + } + } + } + } +} + +/** + * Wrapper function to get the RouteLinks's internal structure while + * some of the variables itself are private. + * @return The SaveLoad description for RouteLinks. + */ +const SaveLoad *GetRouteLinkDescription() +{ + static const SaveLoad _routelink_desc[] = { + SLE_VAR(RouteLink, dest, SLE_UINT16), + SLE_VAR(RouteLink, prev_order, SLE_UINT16), + SLE_VAR(RouteLink, next_order, SLE_UINT16), + SLE_VAR(RouteLink, owner, SLE_UINT8), + SLE_VAR(RouteLink, vtype, SLE_UINT8), + SLE_VAR(RouteLink, travel_time, SLE_UINT32), + SLE_VAR(RouteLink, wait_time, SLE_UINT16), + + SLE_END() + }; + return _routelink_desc; +} + +/** Save the RouteLink chunk. */ +static void Save_RTLN() +{ + RouteLink *link; + + FOR_ALL_ROUTELINKS(link) { + SlSetArrayIndex(link->index); + SlObject(link, GetRouteLinkDescription()); + } +} + +/** Load the RouteLink chunk. */ +static void Load_RTLN() +{ + int index; + + while ((index = SlIterateArray()) != -1) { + RouteLink *link = new (index) RouteLink(); + SlObject(link, GetRouteLinkDescription()); + } +} + +/** Resolve references after loading the RouteLink chunk. */ +static void Ptrs_RTLN() +{ + RouteLink *link; + + FOR_ALL_ROUTELINKS(link) { + SlObject(link, GetRouteLinkDescription()); + } +} + +extern const ChunkHandler _routelink_chunk_handlers[] = { + { 'RTLN', Save_RTLN, Load_RTLN, Ptrs_RTLN, NULL, CH_ARRAY | CH_LAST}, +}; diff --git a/src/saveload/cargopacket_sl.cpp b/src/saveload/cargopacket_sl.cpp index 092301e95..95a8d1554 100644 --- a/src/saveload/cargopacket_sl.cpp +++ b/src/saveload/cargopacket_sl.cpp @@ -95,6 +95,12 @@ const SaveLoad *GetCargoPacketDesc() SLE_VAR(CargoPacket, feeder_share, SLE_INT64), SLE_CONDVAR(CargoPacket, source_type, SLE_UINT8, 125, SL_MAX_VERSION), SLE_CONDVAR(CargoPacket, source_id, SLE_UINT16, 125, SL_MAX_VERSION), + SLE_CONDVAR(CargoPacket, dest_xy, SLE_UINT32, 180, SL_MAX_VERSION), + SLE_CONDVAR(CargoPacket, dest_id, SLE_UINT16, 180, SL_MAX_VERSION), + SLE_CONDVAR(CargoPacket, dest_type, SLE_UINT8, 180, SL_MAX_VERSION), + SLE_CONDVAR(CargoPacket, flags, SLE_UINT8, 180, SL_MAX_VERSION), + SLE_CONDVAR(CargoPacket, next_order, SLE_UINT16, 180, SL_MAX_VERSION), + SLE_CONDVAR(CargoPacket, next_station, SLE_UINT16, 180, SL_MAX_VERSION), /* Used to be paid_for, but that got changed. */ SLE_CONDNULL(1, 0, 120), diff --git a/src/saveload/economy_sl.cpp b/src/saveload/economy_sl.cpp index 9bdad61a9..e5d6c9ed4 100644 --- a/src/saveload/economy_sl.cpp +++ b/src/saveload/economy_sl.cpp @@ -67,6 +67,8 @@ static const SaveLoad _cargopayment_desc[] = { SLE_VAR(CargoPayment, route_profit, SLE_INT64), SLE_VAR(CargoPayment, visual_profit, SLE_INT64), + SLE_CONDVAR(CargoPayment, transfer_profit, SLE_INT64, 180, SL_MAX_VERSION), + SLE_END() }; diff --git a/src/saveload/industry_sl.cpp b/src/saveload/industry_sl.cpp index 8943a5d52..be51bdc57 100644 --- a/src/saveload/industry_sl.cpp +++ b/src/saveload/industry_sl.cpp @@ -30,12 +30,14 @@ static const SaveLoad _industry_desc[] = { SLE_ARR(Industry, production_rate, SLE_UINT8, 2), SLE_CONDNULL( 3, 0, 60), ///< used to be industry's accepts_cargo SLE_CONDARR(Industry, accepts_cargo, SLE_UINT8, 3, 78, SL_MAX_VERSION), + SLE_CONDVAR(Industry, produced_accepted_mask, SLE_UINT32, 180, SL_MAX_VERSION), SLE_VAR(Industry, prod_level, SLE_UINT8), SLE_ARR(Industry, this_month_production, SLE_UINT16, 2), SLE_ARR(Industry, this_month_transported, SLE_UINT16, 2), SLE_ARR(Industry, last_month_pct_transported, SLE_UINT8, 2), SLE_ARR(Industry, last_month_production, SLE_UINT16, 2), SLE_ARR(Industry, last_month_transported, SLE_UINT16, 2), + SLE_CONDARR(Industry, average_production, SLE_UINT16, 2, 180, SL_MAX_VERSION), SLE_VAR(Industry, counter, SLE_UINT16), @@ -63,6 +65,12 @@ static const SaveLoad _industry_desc[] = { SLE_END() }; +static void RealSave_INDY(Industry *ind) +{ + SlObject(ind, _industry_desc); + ind->SaveCargoSourceSink(); +} + static void Save_INDY() { Industry *ind; @@ -70,7 +78,7 @@ static void Save_INDY() /* Write the industries */ FOR_ALL_INDUSTRIES(ind) { SlSetArrayIndex(ind->index); - SlObject(ind, _industry_desc); + SlAutolength((AutolengthProc *)RealSave_INDY, ind); } } @@ -93,6 +101,7 @@ static void Load_INDY() while ((index = SlIterateArray()) != -1) { Industry *i = new (index) Industry(); SlObject(i, _industry_desc); + i->LoadCargoSourceSink(); /* Before savegame version 161, persistent storages were not stored in a pool. */ if (IsSavegameVersionBefore(161) && !IsSavegameVersionBefore(76)) { @@ -121,6 +130,7 @@ static void Ptrs_INDY() FOR_ALL_INDUSTRIES(i) { SlObject(i, _industry_desc); + i->PtrsCargoSourceSink(); } } diff --git a/src/saveload/map_sl.cpp b/src/saveload/map_sl.cpp index 7088a4406..c54e54e92 100644 --- a/src/saveload/map_sl.cpp +++ b/src/saveload/map_sl.cpp @@ -11,6 +11,7 @@ #include "../stdafx.h" #include "../map_func.h" +#include "../layer_func.h" #include "../core/bitmath_func.hpp" #include "../fios.h" @@ -18,10 +19,12 @@ static uint32 _map_dim_x; static uint32 _map_dim_y; +static uint32 _layer_count; static const SaveLoadGlobVarList _map_dimensions[] = { SLEG_CONDVAR(_map_dim_x, SLE_UINT32, 6, SL_MAX_VERSION), SLEG_CONDVAR(_map_dim_y, SLE_UINT32, 6, SL_MAX_VERSION), + SLEG_CONDVAR(_layer_count, SLE_UINT32, 6, SL_MAX_VERSION), SLEG_END() }; @@ -29,13 +32,14 @@ static void Save_MAPS() { _map_dim_x = MapSizeX(); _map_dim_y = MapSizeY(); + _layer_count = LayerCount(); SlGlobList(_map_dimensions); } static void Load_MAPS() { SlGlobList(_map_dimensions); - AllocateMap(_map_dim_x, _map_dim_y); + AllocateMap(_map_dim_x, _map_dim_y/_layer_count, _layer_count); } static void Check_MAPS() diff --git a/src/saveload/order_sl.cpp b/src/saveload/order_sl.cpp index 4d2a4b02e..0799b0b33 100644 --- a/src/saveload/order_sl.cpp +++ b/src/saveload/order_sl.cpp @@ -203,7 +203,14 @@ static void Ptrs_ORDR() const SaveLoad *GetOrderListDescription() { static const SaveLoad _orderlist_desc[] = { - SLE_REF(OrderList, first, REF_ORDER), + SLE_REF( OrderList, first, REF_ORDER), + SLE_CONDVAR(OrderList, current_sep_mode, SLE_UINT, SL_TTSEP_VER, SL_MAX_VERSION), + SLE_CONDVAR(OrderList, num_sep_vehicles, SLE_UINT, SL_TTSEP_VER, SL_MAX_VERSION), + SLE_CONDVAR(OrderList, separation_counter, SLE_UINT, SL_TTSEP_VER, SL_MAX_VERSION), + SLE_CONDVAR(OrderList, separation_counter, SLE_UINT, SL_TTSEP_VER, SL_MAX_VERSION), + SLE_CONDVAR(OrderList, is_separation_valid, SLE_BOOL, SL_TTSEP_VER, SL_MAX_VERSION), + SLE_CONDVAR(OrderList, current_separation, SLE_INT, SL_TTSEP_VER, SL_MAX_VERSION), + SLE_CONDVAR(OrderList, last_timetable_init, SLE_INT, SL_TTSEP_VER, SL_MAX_VERSION), SLE_END() }; diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index a21f6242a..da3dab7c9 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -41,6 +41,7 @@ #include "../string_func.h" #include "../fios.h" #include "../error.h" +#include "../cargodest_base.h" #include "table/strings.h" @@ -245,7 +246,7 @@ * 179 24810 * 180 24998 */ -extern const uint16 SAVEGAME_VERSION = 180; ///< Current savegame version of OpenTTD. +extern const uint16 SAVEGAME_VERSION = SL_TTSEP_VER; ///< Current savegame version of OpenTTD. SavegameType _savegame_type; ///< type of savegame we are loading @@ -426,6 +427,7 @@ extern const ChunkHandler _autoreplace_chunk_handlers[]; extern const ChunkHandler _labelmaps_chunk_handlers[]; extern const ChunkHandler _airport_chunk_handlers[]; extern const ChunkHandler _object_chunk_handlers[]; +extern const ChunkHandler _routelink_chunk_handlers[]; extern const ChunkHandler _persistent_storage_chunk_handlers[]; /** Array of all chunks in a savegame, \c NULL terminated. */ @@ -460,6 +462,7 @@ static const ChunkHandler * const _chunk_handlers[] = { _labelmaps_chunk_handlers, _airport_chunk_handlers, _object_chunk_handlers, + _routelink_chunk_handlers, _persistent_storage_chunk_handlers, NULL, }; @@ -1214,6 +1217,7 @@ static size_t ReferenceToInt(const void *obj, SLRefType rt) case REF_ENGINE_RENEWS: return ((const EngineRenew*)obj)->index + 1; case REF_CARGO_PACKET: return ((const CargoPacket*)obj)->index + 1; case REF_ORDERLIST: return ((const OrderList*)obj)->index + 1; + case REF_ROUTE_LINK: return ((const RouteLink*)obj)->index + 1; case REF_STORAGE: return ((const PersistentStorage*)obj)->index + 1; default: NOT_REACHED(); } @@ -1284,6 +1288,10 @@ static void *IntToReference(size_t index, SLRefType rt) if (CargoPacket::IsValidID(index)) return CargoPacket::Get(index); SlErrorCorrupt("Referencing invalid CargoPacket"); + case REF_ROUTE_LINK: + if (RouteLink::IsValidID(index)) return RouteLink::Get(index); + SlErrorCorrupt("Referencing invalid RouteLink"); + case REF_STORAGE: if (PersistentStorage::IsValidID(index)) return PersistentStorage::Get(index); SlErrorCorrupt("Referencing invalid PersistentStorage"); diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index c5ffbeff8..c4a26b4fa 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -15,6 +15,8 @@ #include "../fileio_type.h" #include "../strings_type.h" +#define SL_TTSEP_VER 200 + /** Save or load result codes. */ enum SaveOrLoadResult { SL_OK = 0, ///< completed successfully @@ -82,9 +84,13 @@ enum SLRefType { REF_ENGINE_RENEWS = 6, ///< Load/save a reference to an engine renewal (autoreplace). REF_CARGO_PACKET = 7, ///< Load/save a reference to a cargo packet. REF_ORDERLIST = 8, ///< Load/save a reference to an orderlist. - REF_STORAGE = 9, ///< Load/save a reference to a persistent storage. + REF_ROUTE_LINK = 9, ///< Load/save a reference to a route link. + REF_STORAGE = 10, ///< Load/save a reference to a persistent storage. }; +/* Current savegame version. */ +static const uint TL_SV = 200; + /** Highest possible savegame version. */ #define SL_MAX_VERSION 255 diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp index 9feb1f274..f7793bd8c 100644 --- a/src/saveload/station_sl.cpp +++ b/src/saveload/station_sl.cpp @@ -258,7 +258,10 @@ const SaveLoad *GetGoodsDesc() SLEG_CONDVAR( _cargo_feeder_share, SLE_FILE_U32 | SLE_VAR_I64, 14, 64), SLEG_CONDVAR( _cargo_feeder_share, SLE_INT64, 65, 67), SLE_CONDVAR(GoodsEntry, amount_fract, SLE_UINT8, 150, SL_MAX_VERSION), + SLE_CONDVAR(GoodsEntry, cargo_counter, SLE_UINT16, 180, SL_MAX_VERSION), SLE_CONDLST(GoodsEntry, cargo.packets, REF_CARGO_PACKET, 68, SL_MAX_VERSION), + SLE_CONDVAR(GoodsEntry, cargo.next_start, SLE_UINT32, 180, SL_MAX_VERSION), + SLE_CONDLST(GoodsEntry, routes, REF_ROUTE_LINK, 180, SL_MAX_VERSION), SLE_END() }; diff --git a/src/saveload/town_sl.cpp b/src/saveload/town_sl.cpp index de52604f5..6041bd98e 100644 --- a/src/saveload/town_sl.cpp +++ b/src/saveload/town_sl.cpp @@ -249,6 +249,8 @@ static void RealSave_Town(Town *t) SlObject(&t->received[i], _town_received_desc); } + t->SaveCargoSourceSink(); + if (IsSavegameVersionBefore(166)) return; SlObject(&t->cargo_accepted, GetTileMatrixDesc()); @@ -287,6 +289,8 @@ static void Load_TOWN() SlErrorCorrupt("Invalid town name generator"); } + t->LoadCargoSourceSink(); + if (IsSavegameVersionBefore(166)) continue; SlObject(&t->cargo_accepted, GetTileMatrixDesc()); @@ -298,16 +302,26 @@ static void Load_TOWN() /* Rebuild total cargo acceptance. */ UpdateTownCargoTotal(t); } + + /* Cache the aligned tile index of the centre tile. */ + uint town_x = (TileX(t->xy) / AcceptanceMatrix::GRID) * AcceptanceMatrix::GRID; + uint town_y = (TileY(t->xy) / AcceptanceMatrix::GRID) * AcceptanceMatrix::GRID; + t->xy_aligned= TileXY(town_x, town_y); } } /** Fix pointers when loading town data. */ static void Ptrs_TOWN() { + Town *t; + + FOR_ALL_TOWNS(t) { + t->PtrsCargoSourceSink(); + } + /* Don't run when savegame version lower than 161. */ if (IsSavegameVersionBefore(161)) return; - Town *t; FOR_ALL_TOWNS(t) { SlObject(t, _town_desc); } diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp index a33ceb6d3..8599f3d9f 100644 --- a/src/saveload/vehicle_sl.cpp +++ b/src/saveload/vehicle_sl.cpp @@ -604,6 +604,8 @@ const SaveLoad *GetVehicleDescription(VehicleType vt) SLE_VAR(Vehicle, vehstatus, SLE_UINT8), SLE_CONDVAR(Vehicle, last_station_visited, SLE_FILE_U8 | SLE_VAR_U16, 0, 4), SLE_CONDVAR(Vehicle, last_station_visited, SLE_UINT16, 5, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, last_station_loaded, SLE_UINT16, 180, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, last_order_id, SLE_UINT16, 180, SL_MAX_VERSION), SLE_VAR(Vehicle, cargo_type, SLE_UINT8), SLE_CONDVAR(Vehicle, cargo_subtype, SLE_UINT8, 35, SL_MAX_VERSION), @@ -616,6 +618,7 @@ const SaveLoad *GetVehicleDescription(VehicleType vt) SLE_CONDLST(Vehicle, cargo.packets, REF_CARGO_PACKET, 68, SL_MAX_VERSION), SLE_CONDVAR(Vehicle, cargo_age_counter, SLE_UINT16, 162, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, travel_time, SLE_UINT32, 180, SL_MAX_VERSION), SLE_VAR(Vehicle, day_counter, SLE_UINT8), SLE_VAR(Vehicle, tick_counter, SLE_UINT8), SLE_CONDVAR(Vehicle, running_ticks, SLE_UINT8, 88, SL_MAX_VERSION), @@ -635,6 +638,7 @@ const SaveLoad *GetVehicleDescription(VehicleType vt) SLE_CONDVAR(Vehicle, current_order.type, SLE_UINT8, 5, SL_MAX_VERSION), SLE_CONDVAR(Vehicle, current_order.flags, SLE_UINT8, 5, SL_MAX_VERSION), SLE_CONDVAR(Vehicle, current_order.dest, SLE_UINT16, 5, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, current_order.index, SLE_UINT16, 180, SL_MAX_VERSION), /* Refit in current order */ SLE_CONDVAR(Vehicle, current_order.refit_cargo, SLE_UINT8, 36, SL_MAX_VERSION), diff --git a/src/script/api/script_window.hpp b/src/script/api/script_window.hpp index 5500f53fc..ef4ba3085 100644 --- a/src/script/api/script_window.hpp +++ b/src/script/api/script_window.hpp @@ -1261,6 +1261,7 @@ public: WID_GL_MAPSIZE_X_PULLDOWN = ::WID_GL_MAPSIZE_X_PULLDOWN, ///< Dropdown 'map X size'. WID_GL_MAPSIZE_Y_PULLDOWN = ::WID_GL_MAPSIZE_Y_PULLDOWN, ///< Dropdown 'map Y size'. + WID_GL_LAYER_COUNT_PULLDOWN = ::WID_GL_LAYER_COUNT_PULLDOWN, ///< Dropdown 'map layer count' WID_GL_TOWN_PULLDOWN = ::WID_GL_TOWN_PULLDOWN, ///< Dropdown 'No. of towns'. WID_GL_INDUSTRY_PULLDOWN = ::WID_GL_INDUSTRY_PULLDOWN, ///< Dropdown 'No. of industries'. @@ -1308,6 +1309,7 @@ public: WID_CS_RANDOM_WORLD = ::WID_CS_RANDOM_WORLD, ///< Generate random land button WID_CS_MAPSIZE_X_PULLDOWN = ::WID_CS_MAPSIZE_X_PULLDOWN, ///< Pull-down arrow for x map size. WID_CS_MAPSIZE_Y_PULLDOWN = ::WID_CS_MAPSIZE_Y_PULLDOWN, ///< Pull-down arrow for y map size. + WID_CS_LAYER_COUNT_PULLDOWN = ::WID_CS_LAYER_COUNT_PULLDOWN, ///< Pull-down arrow for map layer count. WID_CS_START_DATE_DOWN = ::WID_CS_START_DATE_DOWN, ///< Decrease start year (start earlier). WID_CS_START_DATE_TEXT = ::WID_CS_START_DATE_TEXT, ///< Clickable start date value. WID_CS_START_DATE_UP = ::WID_CS_START_DATE_UP, ///< Increase start year (start later). diff --git a/src/settings.cpp b/src/settings.cpp index 293ce406d..8e7db97e7 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -32,6 +32,7 @@ #include "command_func.h" #include "console_func.h" #include "pathfinder/pathfinder_type.h" +#include "layer_type.h" #include "genworld.h" #include "train.h" #include "news_func.h" @@ -64,6 +65,9 @@ #include "roadveh.h" #include "fios.h" #include "strings_func.h" +#include "trafficlight_func.h" +#include "industry.h" +#include "cargodest_func.h" #include "void_map.h" #include "station_base.h" @@ -1170,6 +1174,21 @@ static size_t ConvertLandscape(const char *value) return LookupOneOfMany("normal|hilly|desert|candy", value); } +/** + * What to do when traffic light Setting was changed. + * @param p1 unused + * @return always 0 + */ +static bool TLSettingChanged(int32 p1) +{ + /* Road building gui changed. */ + MarkWholeScreenDirty(); + + /* If traffic lights got disabled, clear them all. */ + if (!_settings_game.construction.traffic_lights) ClearAllTrafficLights(); + return true; +} + static bool CheckFreeformEdges(int32 p1) { if (_game_mode == GM_MENU) return true; @@ -1253,6 +1272,64 @@ static bool StationCatchmentChanged(int32 p1) return true; } +bool CargodestModeChanged(int32 p1) +{ + /* Clear route links and destinations for cargoes that aren't routed anymore. */ + Station *st; + FOR_ALL_STATIONS(st) { + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + if (CargoHasDestinations(cid)) continue; + + /* Clear route links. */ + for (RouteLinkList::iterator i = st->goods[cid].routes.begin(); i != st->goods[cid].routes.end(); ++i) { + delete *i; + } + st->goods[cid].routes.clear(); + + /* Remove destinations from cargo packets. */ + for (StationCargoList::Iterator i = st->goods[cid].cargo.packets.begin(); i != st->goods[cid].cargo.packets.end(); ++i) { + (*i)->dest_id = INVALID_SOURCE; + (*i)->next_order = INVALID_ORDER; + (*i)->next_station = INVALID_STATION; + } + st->goods[cid].cargo.InvalidateCache(); + } + } + + Vehicle *v; + FOR_ALL_VEHICLES(v) { + if (v->IsFrontEngine()) PrefillRouteLinks(v); + if (CargoHasDestinations(v->cargo_type)) continue; + /* Remove destination from all cargoes that aren't routed anymore. */ + for (VehicleCargoList::Iterator i = v->cargo.packets.begin(); i != v->cargo.packets.end(); ++i) { + (*i)->dest_id = INVALID_SOURCE; + (*i)->next_order = INVALID_ORDER; + (*i)->next_station = INVALID_STATION; + } + v->cargo.InvalidateCache(); + } + + /* Clear all links for cargoes that aren't routed anymore. */ + CargoSourceSink *css; + FOR_ALL_TOWNS(css) { + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + if (!CargoHasDestinations(cid)) css->cargo_links[cid].Clear(); + } + } + FOR_ALL_INDUSTRIES(css) { + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + if (!CargoHasDestinations(cid)) css->cargo_links[cid].Clear(); + } + } + + /* Recount incoming cargo links. */ + RebuildCargoLinkCounts(); + + /* Update remaining links. */ + UpdateCargoLinks(); + + return true; +} #ifdef ENABLE_NETWORK diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 0b5917490..9a17ab1c7 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -1459,44 +1459,67 @@ static SettingEntry _settings_ui_interaction[] = { static SettingsPage _settings_ui_interaction_page = {_settings_ui_interaction, lengthof(_settings_ui_interaction)}; static SettingEntry _settings_ui_sound[] = { - SettingEntry("sound.click_beep"), - SettingEntry("sound.confirm"), - SettingEntry("sound.news_ticker"), - SettingEntry("sound.news_full"), - SettingEntry("sound.new_year"), - SettingEntry("sound.disaster"), - SettingEntry("sound.vehicle"), - SettingEntry("sound.ambient"), + SettingEntry("sound.click_beep"), + SettingEntry("sound.confirm"), + SettingEntry("sound.news_ticker"), + SettingEntry("sound.news_full"), + SettingEntry("sound.new_year"), + SettingEntry("sound.disaster"), + SettingEntry("sound.vehicle"), + SettingEntry("sound.ambient"), }; /** Sound effects sub-page */ static SettingsPage _settings_ui_sound_page = {_settings_ui_sound, lengthof(_settings_ui_sound)}; static SettingEntry _settings_ui_news[] = { - SettingEntry("news_display.arrival_player"), - SettingEntry("news_display.arrival_other"), - SettingEntry("news_display.accident"), - SettingEntry("news_display.company_info"), - SettingEntry("news_display.open"), - SettingEntry("news_display.close"), - SettingEntry("news_display.economy"), - SettingEntry("news_display.production_player"), - SettingEntry("news_display.production_other"), - SettingEntry("news_display.production_nobody"), - SettingEntry("news_display.advice"), - SettingEntry("news_display.new_vehicles"), - SettingEntry("news_display.acceptance"), - SettingEntry("news_display.subsidies"), - SettingEntry("news_display.general"), - SettingEntry("gui.coloured_news_year"), + SettingEntry("news_display.arrival_player"), + SettingEntry("news_display.arrival_other"), + SettingEntry("news_display.accident"), + SettingEntry("news_display.company_info"), + SettingEntry("news_display.open"), + SettingEntry("news_display.close"), + SettingEntry("news_display.economy"), + SettingEntry("news_display.production_player"), + SettingEntry("news_display.production_other"), + SettingEntry("news_display.production_nobody"), + SettingEntry("news_display.advice"), + SettingEntry("news_display.new_vehicles"), + SettingEntry("news_display.acceptance"), + SettingEntry("news_display.subsidies"), + SettingEntry("news_display.general"), + SettingEntry("gui.coloured_news_year"), }; /** News sub-page */ static SettingsPage _settings_ui_news_page = {_settings_ui_news, lengthof(_settings_ui_news)}; + +static SettingEntry _settings_ui_departureboards[] = { + SettingEntry("gui.max_departures"), + SettingEntry("gui.max_departure_time"), + SettingEntry("gui.departure_calc_frequency"), + SettingEntry("gui.departure_show_vehicle"), + SettingEntry("gui.departure_show_group"), + SettingEntry("gui.departure_show_company"), + SettingEntry("gui.departure_show_vehicle_type"), + SettingEntry("gui.departure_show_vehicle_color"), + SettingEntry("gui.departure_larger_font"), + SettingEntry("gui.departure_destination_type"), + SettingEntry("gui.departure_show_both"), + SettingEntry("gui.departure_only_passengers"), + SettingEntry("gui.departure_smart_terminus"), + SettingEntry("gui.departure_conditionals"), + SettingEntry("gui.departure_show_all_stops"), + SettingEntry("gui.departure_merge_identical"), +}; +/** Departureboards sub-page */ +static SettingsPage _settings_ui_departureboards_page = {_settings_ui_departureboards, lengthof(_settings_ui_departureboards)}; + static SettingEntry _settings_ui[] = { SettingEntry(&_settings_ui_display_page, STR_CONFIG_SETTING_DISPLAY_OPTIONS), SettingEntry(&_settings_ui_interaction_page, STR_CONFIG_SETTING_INTERACTION), SettingEntry(&_settings_ui_sound_page, STR_CONFIG_SETTING_SOUND), SettingEntry(&_settings_ui_news_page, STR_CONFIG_SETTING_NEWS), + SettingEntry(&_settings_ui_departureboards_page, STR_CONFIG_SETTING_DEPARTUREBOARDS), SettingEntry("gui.show_finances"), SettingEntry("gui.errmsg_duration"), SettingEntry("gui.hover_delay"), @@ -1506,6 +1529,11 @@ static SettingEntry _settings_ui[] = { SettingEntry("gui.pause_on_newgame"), SettingEntry("gui.advanced_vehicle_list"), SettingEntry("gui.timetable_in_ticks"), + SettingEntry("gui.time_in_minutes"), + SettingEntry("gui.timetable_start_text_entry"), + SettingEntry("gui.ticks_per_minute"), + SettingEntry("gui.date_with_time"), + SettingEntry("gui.clock_offset"), SettingEntry("gui.timetable_arrival_departure"), SettingEntry("gui.quick_goto"), SettingEntry("gui.default_rail_type"), @@ -1518,6 +1546,7 @@ static SettingsPage _settings_ui_page = {_settings_ui, lengthof(_settings_ui)}; static SettingEntry _settings_construction_signals[] = { SettingEntry("construction.train_signal_side"), SettingEntry("gui.enable_signal_gui"), + SettingEntry("gui.simulated_wormhole_signals"), SettingEntry("gui.drag_signals_fixed_distance"), SettingEntry("gui.semaphore_build_before"), SettingEntry("gui.default_signal_type"), @@ -1526,8 +1555,21 @@ static SettingEntry _settings_construction_signals[] = { /** Signals subpage */ static SettingsPage _settings_construction_signals_page = {_settings_construction_signals, lengthof(_settings_construction_signals)}; +static SettingEntry _settings_construction_trafficlights[] = { + SettingEntry("construction.traffic_lights"), + SettingEntry("construction.towns_build_traffic_lights"), + SettingEntry("construction.allow_building_tls_in_towns"), + SettingEntry("construction.traffic_lights_green_phase"), + SettingEntry("construction.max_tlc_size"), + SettingEntry("construction.max_tlc_distance"), +}; + +/** Traffic lights subpage */ +static SettingsPage _settings_construction_trafficlights_page = {_settings_construction_trafficlights, lengthof(_settings_construction_trafficlights)}; + static SettingEntry _settings_construction[] = { SettingEntry(&_settings_construction_signals_page, STR_CONFIG_SETTING_CONSTRUCTION_SIGNALS), + SettingEntry(&_settings_construction_trafficlights_page, STR_CONFIG_SETTING_CONSTRUCTION_TRAFFIC_LIGHTS), SettingEntry("construction.build_on_slopes"), SettingEntry("construction.autoslope"), SettingEntry("construction.extra_dynamite"), @@ -1576,6 +1618,7 @@ static SettingEntry _settings_economy_towns[] = { SettingEntry("economy.town_growth_rate"), SettingEntry("economy.larger_towns"), SettingEntry("economy.initial_city_size"), + SettingEntry("economy.random_road_construction"), }; /** Towns sub-page */ static SettingsPage _settings_economy_towns_page = {_settings_economy_towns, lengthof(_settings_economy_towns)}; @@ -1601,6 +1644,9 @@ static SettingEntry _settings_economy[] = { SettingEntry("economy.smooth_economy"), SettingEntry("economy.feeder_payment_share"), SettingEntry("economy.infrastructure_maintenance"), + SettingEntry("economy.cargodest.mode_pax_mail"), + SettingEntry("economy.cargodest.mode_town_cargo"), + SettingEntry("economy.cargodest.mode_others"), SettingEntry("difficulty.vehicle_costs"), SettingEntry("difficulty.construction_cost"), SettingEntry("difficulty.disasters"), @@ -1690,6 +1736,7 @@ static SettingEntry _settings_vehicles[] = { SettingEntry("vehicle.max_ships"), SettingEntry("vehicle.plane_speed"), SettingEntry("vehicle.plane_crashes"), + SettingEntry("order.automatic_timetable_separation"), SettingEntry("vehicle.dynamic_engines"), SettingEntry("vehicle.roadveh_acceleration_model"), SettingEntry("vehicle.roadveh_slope_steepness"), diff --git a/src/settings_type.h b/src/settings_type.h index e6ac3e015..d567152e8 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -110,13 +110,35 @@ struct GUISettings { uint8 scrollwheel_scrolling; ///< scrolling using the scroll wheel? uint8 scrollwheel_multiplier; ///< how much 'wheel' per incoming event from the OS? bool timetable_arrival_departure; ///< show arrivals and departures in vehicle timetables + uint8 max_departures; ///< maximum number of departures to show per station + uint16 max_departure_time; ///< maximum time in advance to show departures + uint16 departure_calc_frequency; ///< how often to calculate departures (in ticks) + bool departure_show_vehicle; ///< whether to show vehicle names with departures + bool departure_show_group; ///< whether to show group names with departures + bool departure_show_company; ///< whether to show company names with departures + bool departure_show_vehicle_type; ///< whether to show vehicle type icons with departures + bool departure_show_vehicle_color; ///< whether to show vehicle type icons in silver instead of orange + bool departure_larger_font; ///< whether to show the calling at list in a larger font + bool departure_destination_type; ///< whether to show destination types for ports and airports + bool departure_show_both; ///< whether to show departure and arrival times on the same line + bool departure_only_passengers; ///< whether to only show passenger services + bool departure_smart_terminus; ///< whether to only show passenger services + uint8 departure_conditionals; ///< how to handle conditional orders + bool departure_show_all_stops; ///< whether to show stops regardless of loading/unloading done at them + bool departure_merge_identical; ///< whether to merge identical departures bool left_mouse_btn_scrolling; ///< left mouse button scroll bool pause_on_newgame; ///< whether to start new games paused or not bool enable_signal_gui; ///< show the signal GUI when the signal button is pressed Year coloured_news_year; ///< when does newspaper become coloured? bool timetable_in_ticks; ///< whether to show the timetable in ticks rather than days + bool time_in_minutes; ///< whether to use the hh:mm conversion when printing dates + bool timetable_start_text_entry; ///< whether to enter timetable start times as text (hhmm format) + uint8 ticks_per_minute; ///< how many ticks per minute + uint8 date_with_time; ///< whether to show the month and year with the time + uint16 clock_offset; ///< clock offset in minutes bool quick_goto; ///< Allow quick access to 'goto button' in vehicle orders window bool auto_euro; ///< automatically switch to euro in 2002 + byte simulated_wormhole_signals; ///< simulate signals in tunnel byte drag_signals_density; ///< many signals density bool drag_signals_fixed_distance; ///< keep fixed distance between signals when dragging Year semaphore_build_before; ///< build semaphore signals automatically before this year @@ -135,6 +157,8 @@ struct GUISettings { uint8 graph_line_thickness; ///< the thickness of the lines in the various graph guis uint8 osk_activation; ///< Mouse gesture to trigger the OSK. + uint32 layer_view_type; ///< зарезервировано (тип отображения) + uint16 console_backlog_timeout; ///< the minimum amount of time items should be in the console backlog before they will be removed in ~3 seconds granularity. uint16 console_backlog_length; ///< the minimum amount of items in the console backlog before items will be removed. #ifdef ENABLE_NETWORK @@ -151,7 +175,6 @@ struct GUISettings { uint8 settings_restriction_mode; ///< selected restriction mode in adv. settings GUI. @see RestrictionMode bool newgrf_show_old_versions; ///< whether to show old versions in the NewGRF list uint8 newgrf_default_palette; ///< default palette to use for NewGRFs without action 14 palette information - /** * Returns true when the user has sufficient privileges to edit newgrfs on a running game * @return whether the user has sufficient privileges to edit newgrfs in an existing game @@ -265,6 +288,7 @@ struct GameCreationSettings { Year starting_year; ///< starting date uint8 map_x; ///< X size of map uint8 map_y; ///< Y size of map + uint8 layers; ///< map layer count byte land_generator; ///< the landscape generator byte oil_refinery_limit; ///< distance oil refineries allowed from map edge byte snow_line_height; ///< a number 0-15 that configured snow line height @@ -305,6 +329,13 @@ struct ConstructionSettings { uint16 clear_frame_burst; ///< how many tiles may, over a short period, be cleared? uint32 tree_per_64k_frames; ///< how many trees may, over a long period, be planted per 65536 frames? uint16 tree_frame_burst; ///< how many trees may, over a short period, be planted? + + bool traffic_lights; ///< Whether traffic lights are enabled. + bool towns_build_traffic_lights; ///< Whether towns build traffic lights during road construction. + bool allow_building_tls_in_towns; ///< Whether the players are allowed to build traffic lights on town owned roads. + uint8 traffic_lights_green_phase; ///< How long traffic lights' green phase lasts. + uint8 max_tlc_size; ///< Maximum size for traffic light consists. + uint8 max_tlc_distance; ///< Maximum distance between traffic lights for synchronising them. }; /** Settings related to the AI. */ @@ -352,6 +383,7 @@ struct NPFSettings { uint32 npf_road_curve_penalty; ///< the penalty for curves uint32 npf_crossing_penalty; ///< the penalty for level crossings uint32 npf_road_drive_through_penalty; ///< the penalty for going through a drive-through road stop + uint32 npf_road_trafficlight_penalty; ///< Penalty for junctions with traffic lights. uint32 npf_road_dt_occupied_penalty; ///< the penalty multiplied by the fill percentage of a drive-through road stop uint32 npf_road_bay_occupied_penalty; ///< the penalty multiplied by the fill percentage of a road bay }; @@ -368,6 +400,7 @@ struct YAPFSettings { uint32 road_curve_penalty; ///< penalty for curves uint32 road_crossing_penalty; ///< penalty for level crossing uint32 road_stop_penalty; ///< penalty for going through a drive-through road stop + uint32 road_trafficlight_penalty; ///< Penalty for junctions with traffic lights. uint32 road_stop_occupied_penalty; ///< penalty multiplied by the fill percentage of a drive-through road stop uint32 road_stop_bay_occupied_penalty; ///< penalty multiplied by the fill percentage of a road bay bool rail_firstred_twoway_eol; ///< treat first red two-way signal as dead end @@ -394,6 +427,14 @@ struct YAPFSettings { uint32 rail_longer_platform_per_tile_penalty; ///< penalty for longer station platform than train (per tile) uint32 rail_shorter_platform_penalty; ///< penalty for shorter station platform than train uint32 rail_shorter_platform_per_tile_penalty; ///< penalty for shorter station platform than train (per tile) + + uint32 route_transfer_cost; ///< penalty for transferring to a different vehicle + uint32 route_max_transfers; ///< maximum number of allowed transfers + uint16 route_distance_factor; ///< factor for the link length + uint16 route_travel_time_factor; ///< factor * CYapfCostRouteLinkT::PENALTY_DIVISOR (=16) for the link travel time + uint16 route_station_last_veh_factor; ///< factor * CYapfCostRouteLinkT::PENALTY_DIVISOR (=16) for the time since the last vehicle arrived at a station + uint16 route_station_waiting_factor; ///< factor * CYapfCostRouteLinkT::PENALTY_DIVISOR (=16) for the waiting cargo at a station + byte route_mode_cost_factor[4]; ///< vehicle type dependent factor for the link length }; /** Settings related to all pathfinders. */ @@ -425,6 +466,7 @@ struct OrderSettings { bool gradual_loading; ///< load vehicles gradually bool selectgoods; ///< only send the goods to station if a train has been there bool no_servicing_if_no_breakdowns; ///< don't send vehicles to depot when breakdowns are disabled + bool automatic_timetable_separation; ///< Enable automatic separation of vehicles in the timetable. bool serviceathelipad; ///< service helicopters at helipads automatically (no need to send to depot) }; @@ -451,6 +493,33 @@ struct VehicleSettings { uint8 plane_crashes; ///< number of plane crashes, 0 = none, 1 = reduced, 2 = normal }; +/** Settings related to cargo destinations. */ +struct CargodestSettings { + uint8 mode_pax_mail; ///< routing mode for cargoes with TE_PASSENGERS or TE_MAIL + uint8 mode_town_cargo; ///< routing mode for cargoes with other town effects + uint8 mode_others; ///< routing mode for all other cargoes + uint8 base_town_links[2]; ///< minimum number of town demand links for (0=#BASE_TOWN_LINKS) all cargoes except (1=#BASE_TOWN_LINKS_SYMM) symmetric cargoes + uint8 base_ind_links[3]; ///< minimum number of industry demand links for (0=#BASE_IND_LINKS) all cargoes except (1=#BASE_IND_LINKS_TOWN) town cargoes and (2=#BASE_IND_LINKS_SYMM) symmetric cargoes + uint8 city_town_links; ///< additional number of links for cities + uint8 town_chances_town[4]; ///< chances a link from a town to a town has a specific destination class (@see FindTownDestination) + uint8 town_chances_city[4]; ///< chances a link from a city to a town has a specific destination class (@see FindTownDestination) + uint8 ind_chances[3]; ///< chances a link to an industry has a specific destination class (@see FindIndustryDestination) + uint8 random_dest_chance; ///< percentage for traffic with random destination + uint32 big_town_pop[2]; ///< (0=#BIG_TOWN_POP_MAIL) mail, (1=#BIG_TOWN_POP_PAX) passenger amount to count as a big town + uint16 pop_scale_town[4]; ///< population/cargo amount scale divisor for (0=#SCALE_TOWN) all cargoes (1=#SCALE_TOWN_BIG) for big towns except (2=#SCALE_TOWN_PAX) passengers (3=#SCALE_TOWN_BIG_PAX) for big towns + uint16 cargo_scale_ind[2]; ///< cargo amount scale divisor for (0=#CARGO_SCALE_IND) all cargoes except (1=#CARGO_SCALE_IND_TOWN) town cargoes + uint16 min_weight_town[2]; ///< minimum link weight for (0=MIN_WEIGHT_TOWN) all cargoes except (1=MIN_WEIGHT_TOWN_PAX) passengers + uint16 min_weight_ind; ///< minimum link weight for industry links + uint16 weight_scale_town[4]; ///< weight scale divisor for (0=#SCALE_TOWN) all cargoes (1=#SCALE_TOWN_BIG) for big towns except (2=#SCALE_TOWN_PAX) passengers (3=#SCALE_TOWN_BIG_PAX) for big towns + uint16 weight_scale_ind[2]; ///< weight scale divisor for (0=#WEIGHT_SCALE_IND_PROD) produced cargo (1=#WEIGHT_SCALE_IND_PILE) stockpiled cargo + uint32 town_nearby_dist; ///< squared distance (on a 256x256 map) inside which a town is considered nearby + uint32 ind_nearby_dist; ///< squared distance (on a 256x256 map) inside which an industry is considered nearby + uint16 max_route_age; ///< maximum days since the last vehicle traveled a link until link expiration + uint16 route_recalc_delay; ///< delay in ticks between recalculating the next hop of cargo packets + uint16 route_recalc_chunk; ///< maximum amount of cargo packets to recalculate in one step + uint16 max_route_penalty[2]; ///< maximum penalty factor based on distance, (1) base value, (2) random additional span +}; + /** Settings related to the economy. */ struct EconomySettings { bool inflation; ///< disable inflation @@ -473,8 +542,10 @@ struct EconomySettings { TownFoundingByte found_town; ///< town founding, @see TownFounding bool station_noise_level; ///< build new airports when the town noise level is still within accepted limits uint16 town_noise_population[3]; ///< population to base decision on noise evaluation (@see town_council_tolerance) + uint8 random_road_construction; ///< Chance for towns to start random road construction bool allow_town_level_crossings; ///< towns are allowed to build level crossings bool infrastructure_maintenance; ///< enable monthly maintenance fee for owner infrastructure + CargodestSettings cargodest; ///< settings related to cargo destinations }; /** Settings related to stations. */ diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index 55c695603..63b693d72 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -31,6 +31,7 @@ #include "pathfinder/opf/opf_ship.h" #include "engine_base.h" #include "company_base.h" +#include "cargotype.h" #include "tunnelbridge_map.h" #include "zoom_func.h" @@ -181,6 +182,7 @@ static void CheckIfShipNeedsService(Vehicle *v) void Ship::UpdateCache() { const ShipVehicleInfo *svi = ShipVehInfo(this->engine_type); + this->vcache.cached_cargo_mask = (this->cargo_type != INVALID_CARGO && this->cargo_cap > 0) ? 1 << this->cargo_type : 0; /* Get speed fraction for the current water type. Aqueducts are always canals. */ bool is_ocean = GetEffectiveWaterClass(this->tile) == WATER_CLASS_SEA; @@ -496,6 +498,24 @@ static const byte _ship_subcoord[4][6][3] = { } }; +/* Used to find DiagDirection from tile to next tile if track is followed */ +static const DiagDirection _diagdir_to_next_tile[6][4] = { + { DIAGDIR_NE, DIAGDIR_SE, DIAGDIR_SW, DIAGDIR_NW }, + { DIAGDIR_NE, DIAGDIR_SE, DIAGDIR_SW, DIAGDIR_NW }, + { DIAGDIR_SE, DIAGDIR_NE, DIAGDIR_NW, DIAGDIR_SW }, + { DIAGDIR_SE, DIAGDIR_NW, DIAGDIR_NE, DIAGDIR_SW }, + { DIAGDIR_NW, DIAGDIR_SW, DIAGDIR_SE, DIAGDIR_NE }, + { DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_SE, DIAGDIR_NE } +}; + +/** Helper function for collision avoidance. */ +static Vehicle *FindShipOnTile(Vehicle *v, void *data) +{ + if (v->type != VEH_SHIP || v->vehstatus & VS_STOPPED) return NULL; + + return v; +} + static void ShipController(Ship *v) { uint32 r; @@ -558,14 +578,13 @@ static void ShipController(Ship *v) return; } } else if (v->current_order.IsType(OT_GOTO_STATION)) { - v->last_station_visited = v->current_order.GetDestination(); - /* Process station in the orderlist. */ Station *st = Station::Get(v->current_order.GetDestination()); if (st->facilities & FACIL_DOCK) { // ugly, ugly workaround for problem with ships able to drop off cargo at wrong stations ShipArrivesAt(v, st); - v->BeginLoading(); + v->BeginLoading(st->index); } else { // leave stations without docks right aways + v->last_station_visited = v->current_order.GetDestination(); v->current_order.MakeLeaveStation(); v->IncrementRealOrderIndex(); } @@ -588,6 +607,25 @@ static void ShipController(Ship *v) track = ChooseShipTrack(v, gp.new_tile, diagdir, tracks); if (track == INVALID_TRACK) goto reverse_direction; + /* Try to avoid collision and keep distance between each other. */ + if (_settings_game.pf.forbid_90_deg && DistanceManhattan(v->dest_tile, gp.new_tile) > 3) { + if (HasVehicleOnPos(gp.new_tile, NULL, &FindShipOnTile) || + HasVehicleOnPos(TileAddByDiagDir(gp.new_tile, _diagdir_to_next_tile[track][diagdir]), NULL, &FindShipOnTile)) { + + v->cur_speed /= 4; // Go quarter speed. + + Track old = track; + switch (tracks) { + default: break; + case TRACK_BIT_3WAY_NE: track == TRACK_RIGHT ? track = TRACK_X : track = TRACK_UPPER; break; + case TRACK_BIT_3WAY_SE: track == TRACK_LOWER ? track = TRACK_Y : track = TRACK_RIGHT; break; + case TRACK_BIT_3WAY_SW: track == TRACK_LEFT ? track = TRACK_X : track = TRACK_LOWER; break; + case TRACK_BIT_3WAY_NW: track == TRACK_UPPER ? track = TRACK_Y : track = TRACK_LEFT; break; + } + if (!IsWaterTile(gp.new_tile) || !IsWaterTile(TileAddByDiagDir(gp.new_tile, _diagdir_to_next_tile[track][diagdir]))) track = old; // Don't bump in coast, don't get stuck. + } + } + b = _ship_subcoord[diagdir][track]; gp.x = (gp.x & ~0xF) | b[0]; diff --git a/src/signal.cpp b/src/signal.cpp index 2d16e9a73..dd6911a27 100644 --- a/src/signal.cpp +++ b/src/signal.cpp @@ -195,6 +195,14 @@ static Vehicle *TrainOnTileEnum(Vehicle *v, void *) return v; } +/** Check whether there is a train only on ramp. */ +static Vehicle *TrainInWormholeTileEnum(Vehicle *v, void *data) +{ + /* Only look for front engine or last wagon. */ + if (v->type != VEH_TRAIN || (v->Previous() != NULL && v->Next() != NULL)) return NULL; + if (*(TileIndex *)data != TileVirtXY(v->x_pos, v->y_pos)) return NULL; + return v; +} /** * Perform some operations before adding data into Todo set @@ -373,17 +381,39 @@ static SigFlags ExploreSegment(Owner owner) if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) continue; DiagDirection dir = GetTunnelBridgeDirection(tile); - if (enterdir == INVALID_DIAGDIR) { // incoming from the wormhole - if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN; - enterdir = dir; - exitdir = ReverseDiagDir(dir); - tile += TileOffsByDiagDir(exitdir); // just skip to next tile - } else { // NOT incoming from the wormhole! - if (ReverseDiagDir(enterdir) != dir) continue; - if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN; - tile = GetOtherTunnelBridgeEnd(tile); // just skip to exit tile - enterdir = INVALID_DIAGDIR; - exitdir = INVALID_DIAGDIR; + if (HasWormholeSignals(tile)) { + if (enterdir == INVALID_DIAGDIR) { // incoming from the wormhole + if (!(flags & SF_TRAIN) && IsTunnelBridgeExit(tile)) { // tunnel entrence is ignored + if (HasVehicleOnPos(GetOtherTunnelBridgeEnd(tile), &tile, &TrainInWormholeTileEnum)) flags |= SF_TRAIN; + if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, &tile, &TrainInWormholeTileEnum)) flags |= SF_TRAIN; + } + enterdir = dir; + exitdir = ReverseDiagDir(dir); + tile += TileOffsByDiagDir(exitdir); // just skip to next tile + } else { // NOT incoming from the wormhole! + if (ReverseDiagDir(enterdir) != dir) continue; + if (!(flags & SF_TRAIN)) { + if (HasVehicleOnPos(tile, &tile, &TrainInWormholeTileEnum)) flags |= SF_TRAIN; + if (!(flags & SF_TRAIN) && IsTunnelBridgeExit(tile)) { + if (HasVehicleOnPos(GetOtherTunnelBridgeEnd(tile), &tile, &TrainInWormholeTileEnum)) flags |= SF_TRAIN; + } + } + continue; + } + } else { + if (enterdir == INVALID_DIAGDIR) { // incoming from the wormhole + if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN; + enterdir = dir; + exitdir = ReverseDiagDir(dir); + tile += TileOffsByDiagDir(exitdir); // just skip to next tile + } else { // NOT incoming from the wormhole! + if (ReverseDiagDir(enterdir) != dir) continue; + if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN; + tile = GetOtherTunnelBridgeEnd(tile); // just skip to exit tile + enterdir = INVALID_DIAGDIR; + exitdir = INVALID_DIAGDIR; + } + } } break; @@ -491,7 +521,9 @@ static SigSegState UpdateSignalsInBuffer(Owner owner) assert(GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL); assert(dir == INVALID_DIAGDIR || dir == ReverseDiagDir(GetTunnelBridgeDirection(tile))); _tbdset.Add(tile, INVALID_DIAGDIR); // we can safely start from wormhole centre - _tbdset.Add(GetOtherTunnelBridgeEnd(tile), INVALID_DIAGDIR); + if (!HasWormholeSignals(tile)) { // Don't worry with other side of tunnel. + _tbdset.Add(GetOtherTunnelBridgeEnd(tile), INVALID_DIAGDIR); + } break; case MP_RAILWAY: diff --git a/src/smallmap_gui.cpp b/src/smallmap_gui.cpp index 4a573f3a9..fa1c6f6c7 100644 --- a/src/smallmap_gui.cpp +++ b/src/smallmap_gui.cpp @@ -26,6 +26,10 @@ #include "sound_func.h" #include "window_func.h" #include "company_base.h" +#include "station_base.h" +#include "company_func.h" +#include "cargotype.h" +#include "core/smallmap_type.hpp" #include "widgets/smallmap_widget.h" @@ -33,6 +37,7 @@ static int _smallmap_industry_count; ///< Number of used industries static int _smallmap_company_count; ///< Number of entries in the owner legend. +static int _smallmap_cargo_count; ///< Number of entries in the cargo legend. static const int NUM_NO_COMPANY_ENTRIES = 4; ///< Number of entries in the owner legend that are not companies. @@ -44,25 +49,25 @@ static const uint8 PC_TREES = 0x57; ///< Green palette colour for tree static const uint8 PC_WATER = 0xCA; ///< Dark blue palette colour for water. /** Macro for ordinary entry of LegendAndColour */ -#define MK(a, b) {a, b, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, true, false, false} +#define MK(a, b) {a, b, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, INVALID_CARGO, true, false, false} /** Macro for a height legend entry with configurable colour. */ -#define MC(height) {0, STR_TINY_BLACK_HEIGHT, INVALID_INDUSTRYTYPE, height, INVALID_COMPANY, true, false, false} +#define MC(height) {0, STR_TINY_BLACK_HEIGHT, INVALID_INDUSTRYTYPE, height, INVALID_COMPANY, INVALID_CARGO, true, false, false} /** Macro for non-company owned property entry of LegendAndColour */ -#define MO(a, b) {a, b, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, true, false, false} +#define MO(a, b) {a, b, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, INVALID_CARGO, true, false, false} /** Macro used for forcing a rebuild of the owner legend the first time it is used. */ -#define MOEND() {0, 0, INVALID_INDUSTRYTYPE, 0, OWNER_NONE, true, true, false} +#define MOEND() {0, 0, INVALID_INDUSTRYTYPE, 0, OWNER_NONE, INVALID_CARGO, true, true, false} /** Macro for end of list marker in arrays of LegendAndColour */ -#define MKEND() {0, STR_NULL, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, true, true, false} +#define MKEND() {0, STR_NULL, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, INVALID_CARGO, true, true, false} /** * Macro for break marker in arrays of LegendAndColour. * It will have valid data, though */ -#define MS(a, b) {a, b, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, true, false, true} +#define MS(a, b) {a, b, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, INVALID_CARGO, true, false, true} /** Structure for holding relevant data for legends in small map */ struct LegendAndColour { @@ -71,6 +76,7 @@ struct LegendAndColour { IndustryType type; ///< Type of industry. Only valid for industry entries. uint8 height; ///< Height in tiles. Only valid for height legend entries. CompanyID company; ///< Company to display. Only valid for company entries of the owner legend. + CargoID cid; ///< Cargo type to display. Only valid for entries of the cargo legend. bool show_on_map; ///< For filtering industries, if \c true, industry is shown on the map in colour. bool end; ///< This is the end of the list. bool col_break; ///< Perform a column break and go further at the next column. @@ -156,6 +162,10 @@ static LegendAndColour _legend_land_owners[NUM_NO_COMPANY_ENTRIES + MAX_COMPANIE static LegendAndColour _legend_from_industries[NUM_INDUSTRYTYPES + 1]; /** For connecting industry type to position in industries list(small map legend) */ static uint _industry_to_list_pos[NUM_INDUSTRYTYPES]; +/** Legend text for the cargo types in the route link legend. */ +static LegendAndColour _legend_from_cargoes[NUM_CARGO + 1]; +/** For connecting cargo type to position in route link legend. */ +static uint _cargotype_to_list_pos[NUM_CARGO]; /** Show heightmap in industry and owner mode of smallmap window. */ static bool _smallmap_show_heightmap = false; /** Highlight a specific industry type */ @@ -196,10 +206,38 @@ void BuildIndustriesLegend() _smallmap_industry_count = j; } +/** Fills the array for the route link legend. */ +void BuildCargoTypesLegend() +{ + uint j = 0; + + /* Add all standard cargo types. */ + const CargoSpec *cs; + FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { + _legend_from_cargoes[j].legend = cs->name; + _legend_from_cargoes[j].colour = cs->legend_colour; + _legend_from_cargoes[j].cid = cs->Index(); + _legend_from_cargoes[j].show_on_map = true; + _legend_from_cargoes[j].col_break = false; + _legend_from_cargoes[j].end = false; + + /* Store widget number for this cargo type. */ + _cargotype_to_list_pos[cs->Index()] = j; + j++; + } + + /* Terminate list. */ + _legend_from_cargoes[j].end = true; + + /* Store number of enabled cargoes. */ + _smallmap_cargo_count = j; +} + static const LegendAndColour * const _legend_table[] = { _legend_land_contours, _legend_vehicles, _legend_from_industries, + _legend_from_cargoes, _legend_routes, _legend_vegetation, _legend_land_owners, @@ -242,6 +280,21 @@ static const uint32 _green_map_heights[] = { }; assert_compile(lengthof(_green_map_heights) == MAX_TILE_HEIGHT + 1); +/** + * Colour Coding for Stuck Counter + */ +static const uint32 _stuck_counter_colours[] = { + MKCOLOUR(0xD0D0D0D0), + MKCOLOUR(0xCECECECE), + MKCOLOUR(0xBFBFBFBF), + MKCOLOUR(0xBDBDBDBD), + MKCOLOUR(0xBABABABA), + MKCOLOUR(0xB8B8B8B8), + MKCOLOUR(0xB6B6B6B6), + MKCOLOUR(0xB4B4B4B4), +}; +assert_compile(lengthof(_stuck_counter_colours) == 8); + /** Height map colours for the dark green colour scheme, ordered by height. */ static const uint32 _dark_green_map_heights[] = { MKCOLOUR_XXXX(0x60), @@ -464,9 +517,10 @@ static inline uint32 GetSmallMapIndustriesPixels(TileIndex tile, TileType t) * * @param tile The tile of which we would like to get the colour. * @param t Effective tile type of the tile (see #GetEffectiveTileType). + * @param show_height Whether to show the height of plain tiles. * @return The colour of tile in the small map in mode "Routes" */ -static inline uint32 GetSmallMapRoutesPixels(TileIndex tile, TileType t) +static inline uint32 GetSmallMapRoutesPixels(TileIndex tile, TileType t, bool show_height = false) { if (t == MP_STATION) { switch (GetStationType(tile)) { @@ -478,18 +532,14 @@ static inline uint32 GetSmallMapRoutesPixels(TileIndex tile, TileType t) default: return MKCOLOUR_FFFF; } } else if (t == MP_RAILWAY) { - AndOr andor = { - MKCOLOUR_0XX0(GetRailTypeInfo(GetRailType(tile))->map_colour), - _smallmap_contours_andor[t].mand - }; - - const SmallMapColourScheme *cs = &_heightmap_schemes[_settings_client.gui.smallmap_land_colour]; - return ApplyMask(cs->default_colour, &andor); + byte c = GetStuckCounter(tile); + if (c==0) return 0; + return _stuck_counter_colours[c/32]; } /* Ground colour */ const SmallMapColourScheme *cs = &_heightmap_schemes[_settings_client.gui.smallmap_land_colour]; - return ApplyMask(cs->default_colour, &_smallmap_contours_andor[t]); + return ApplyMask(show_height ? cs->height_colours[TileHeight(tile)] : cs->default_colour, &_smallmap_contours_andor[t]); } @@ -576,6 +626,7 @@ class SmallMapWindow : public Window { SMT_CONTOUR, SMT_VEHICLES, SMT_INDUSTRY, + SMT_ROUTE_LINKS, SMT_ROUTES, SMT_VEGETATION, SMT_OWNER, @@ -774,6 +825,9 @@ class SmallMapWindow : public Window { case SMT_INDUSTRY: return GetSmallMapIndustriesPixels(tile, et); + case SMT_ROUTE_LINKS: + return GetSmallMapRoutesPixels(tile, et, _smallmap_show_heightmap); + case SMT_ROUTES: return GetSmallMapRoutesPixels(tile, et); @@ -901,6 +955,92 @@ class SmallMapWindow : public Window { } /** + * Adds the route links to the smallmap. + */ + void DrawRouteLinks() const + { + /* Iterate all shown cargo types. */ + for (int i = 0; i < _smallmap_cargo_count; i++) { + if (_legend_from_cargoes[i].show_on_map) { + CargoID cid = _legend_from_cargoes[i].cid; + + /* Iterate all stations. */ + const Station *st; + FOR_ALL_STATIONS(st) { + Point src_pt = this->RemapTile(TileX(st->xy), TileY(st->xy)); + src_pt.x -= this->subscroll; + + /* Collect waiting cargo per destination station. */ + std::map<StationID, uint> links; + for (RouteLinkList::const_iterator l = st->goods[cid].routes.begin(); l != st->goods[cid].routes.end(); ++l) { + if (IsInteractiveCompany((*l)->GetOwner())) links[(*l)->GetDestination()] += st->goods[cid].cargo.CountForNextHop((*l)->GetOriginOrderId()); + } + + /* Add cargo count on back-links. */ + for (std::map<StationID, uint>::iterator itr = links.begin(); itr != links.end(); ++itr) { + /* Get destination location. */ + const Station *dest = Station::Get(itr->first); + Point dest_pt = this->RemapTile(TileX(dest->xy), TileY(dest->xy)); + dest_pt.x -= this->subscroll; + + /* Get total count including back-links. */ + uint count = itr->second; + for (RouteLinkList::const_iterator j = dest->goods[cid].routes.begin(); j != dest->goods[cid].routes.end(); ++j) { + if ((*j)->GetDestination() == st->index && IsInteractiveCompany((*j)->GetOwner())) count += dest->goods[cid].cargo.CountForNextHop((*j)->GetOriginOrderId()); + } + + /* Calculate line size from waiting cargo. */ + int size = 1; + if (count >= 400) size++; + if (count >= 800) size++; + if (count >= 1600) size++; + if (count >= 3200) size++; + + /* Draw black border and cargo coloured line. */ + GfxDrawLine(src_pt.x, src_pt.y, dest_pt.x, dest_pt.y, PC_BLACK, size + 2); + GfxDrawLine(src_pt.x, src_pt.y, dest_pt.x, dest_pt.y, _legend_from_cargoes[i].colour, size); + } + } + } + } + + /* Draw station rect. */ + const Station *st; + FOR_ALL_STATIONS(st) { + /* Count total cargo and check for links for all shown cargo types. */ + uint total = 0; + bool show = false; + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + if (_legend_from_cargoes[_cargotype_to_list_pos[cid]].show_on_map) { + total += st->goods[cid].cargo.Count(); + show |= !st->goods[cid].routes.empty(); + } + } + + if (!show) continue; + + /* Get rect size from total cargo count. */ + int d = 1; + if (total >= 200) d++; + if (total >= 400) d++; + if (total >= 800) d++; + if (total >= 1600) d++; + if (total >= 3200) d++; + if (total >= 6400) d++; + + /* Get top-left corner of the rect. */ + Point dest_pt = this->RemapTile(TileX(st->xy), TileY(st->xy)); + dest_pt.x -= this->subscroll + d/2; + dest_pt.y -= d/2; + + /* Draw black border and company-colour inset. */ + byte colour = _colour_gradient[Company::IsValidID(st->owner) ? Company::Get(st->owner)->colour : (byte)COLOUR_GREY][6]; + GfxFillRect(dest_pt.x - 1, dest_pt.y - 1, dest_pt.x + d + 1, dest_pt.y + d + 1, PC_BLACK); // Draw black frame + GfxFillRect(dest_pt.x, dest_pt.y, dest_pt.x + d, dest_pt.y + d, colour); // Draw colour insert + } + } + + /** * Draws vertical part of map indicator * @param x X coord of left/right border of main viewport * @param y Y coord of top border of main viewport @@ -1007,6 +1147,9 @@ class SmallMapWindow : public Window { /* Draw vehicles */ if (this->map_type == SMT_CONTOUR || this->map_type == SMT_VEHICLES) this->DrawVehicles(dpi, blitter); + /* Draw route links. */ + if (this->map_type == SMT_ROUTE_LINKS) this->DrawRouteLinks(); + /* Draw town names */ if (this->show_towns) this->DrawTowns(dpi); @@ -1040,6 +1183,13 @@ class SmallMapWindow : public Window { plane = 0; break; + case SMT_ROUTE_LINKS: + legend_tooltip = STR_SMALLMAP_TOOLTIP_ROUTELINK_SELECTION; + enable_all_tooltip = STR_SMALLMAP_TOOLTIP_ENABLE_ALL_ROUTELINKS; + disable_all_tooltip = STR_SMALLMAP_TOOLTIP_DISABLE_ALL_ROUTELINKS; + plane = 0; + break; + default: legend_tooltip = STR_NULL; enable_all_tooltip = STR_NULL; @@ -1140,6 +1290,9 @@ public: } else { str = tbl->legend; } + } else if (i == SMT_ROUTE_LINKS) { + SetDParam(0, tbl->legend); + str = STR_SMALLMAP_CARGO; } else { if (tbl->col_break) { this->min_number_of_fixed_rows = max(this->min_number_of_fixed_rows, height); @@ -1187,7 +1340,7 @@ public: case WID_SM_LEGEND: { uint columns = this->GetNumberColumnsLegend(r.right - r.left + 1); - uint number_of_rows = max((this->map_type == SMT_INDUSTRY || this->map_type == SMT_OWNER) ? CeilDiv(max(_smallmap_company_count, _smallmap_industry_count), columns) : 0, this->min_number_of_fixed_rows); + uint number_of_rows = max((this->map_type == SMT_INDUSTRY || this->map_type == SMT_OWNER || this->map_type == SMT_ROUTE_LINKS) ? CeilDiv(max(_smallmap_company_count, max(_smallmap_industry_count, _smallmap_cargo_count)), columns) : 0, this->min_number_of_fixed_rows); bool rtl = _current_text_dir == TD_RTL; uint y_org = r.top + WD_FRAMERECT_TOP; uint x = rtl ? r.right - this->column_width - WD_FRAMERECT_RIGHT : r.left + WD_FRAMERECT_LEFT; @@ -1201,7 +1354,7 @@ public: uint blob_right = rtl ? this->column_width - 1 : LEGEND_BLOB_WIDTH; for (const LegendAndColour *tbl = _legend_table[this->map_type]; !tbl->end; ++tbl) { - if (tbl->col_break || ((this->map_type == SMT_INDUSTRY || this->map_type == SMT_OWNER) && i++ >= number_of_rows)) { + if (tbl->col_break || ((this->map_type == SMT_INDUSTRY || this->map_type == SMT_OWNER || this->map_type == SMT_ROUTE_LINKS) && i++ >= number_of_rows)) { /* Column break needed, continue at top, COLUMN_WIDTH pixels * (one "row") to the right. */ x += rtl ? -(int)this->column_width : this->column_width; @@ -1227,6 +1380,16 @@ public: DrawString(x + text_left, x + text_right, y, STR_SMALLMAP_INDUSTRY, TC_BLACK); GfxFillRect(x + blob_left, y + 1, x + blob_right, y + row_height - 1, PC_BLACK); // Outer border of the legend colour } + } else if (this->map_type == SMT_ROUTE_LINKS) { + /* Cargo name needs formatting for tiny font. */ + SetDParam(0, tbl->legend); + if (!tbl->show_on_map) { + /* Draw only the string and not the border of the legend colour. */ + DrawString(x + text_left, x + text_right, y, STR_SMALLMAP_CARGO, TC_GREY); + } else { + DrawString(x + text_left, x + text_right, y, STR_SMALLMAP_CARGO, TC_BLACK); + GfxFillRect(x + blob_left, y + 1, x + blob_right, y + row_height - 1, PC_BLACK); // Outer border of the legend colour + } } else if (this->map_type == SMT_OWNER && tbl->company != INVALID_COMPANY) { SetDParam(0, tbl->company); if (!tbl->show_on_map) { @@ -1277,7 +1440,7 @@ public: const NWidgetBase *wi = this->GetWidget<NWidgetBase>(WID_SM_LEGEND); uint line = (pt.y - wi->pos_y - WD_FRAMERECT_TOP) / FONT_HEIGHT_SMALL; uint columns = this->GetNumberColumnsLegend(wi->current_x); - uint number_of_rows = max(CeilDiv(max(_smallmap_company_count, _smallmap_industry_count), columns), this->min_number_of_fixed_rows); + uint number_of_rows = max(CeilDiv(max(_smallmap_company_count, max(_smallmap_industry_count, _smallmap_cargo_count)), columns), this->min_number_of_fixed_rows); if (line >= number_of_rows) return -1; bool rtl = _current_text_dir == TD_RTL; @@ -1349,6 +1512,7 @@ public: case WID_SM_CONTOUR: // Show land contours case WID_SM_VEHICLES: // Show vehicles case WID_SM_INDUSTRIES: // Show industries + case WID_SM_ROUTE_LINKS:// Show route links case WID_SM_ROUTES: // Show transport routes case WID_SM_VEGETATION: // Show vegetation case WID_SM_OWNERS: // Show land owners @@ -1371,7 +1535,7 @@ public: break; case WID_SM_LEGEND: // Legend - if (this->map_type == SMT_INDUSTRY || this->map_type == SMT_OWNER) { + if (this->map_type == SMT_INDUSTRY || this->map_type == SMT_OWNER || this->map_type == SMT_ROUTE_LINKS) { /* If industry type small map*/ if (this->map_type == SMT_INDUSTRY) { /* If click on industries label, find right industry type and enable/disable it. */ @@ -1422,6 +1586,30 @@ public: _legend_land_owners[company_pos].show_on_map = !_legend_land_owners[company_pos].show_on_map; } } + } else if (this->map_type == SMT_ROUTE_LINKS) { + /* If click on cargo label, find right cargo type and enable/disable it. */ + int cargo_pos = GetPositionOnLegend(pt); + if (cargo_pos < _smallmap_cargo_count) { + if (_ctrl_pressed) { + /* Disable all, except the clicked one */ + bool changes = false; + for (int i = 0; i != _smallmap_cargo_count; i++) { + bool new_state = i == cargo_pos; + if (_legend_from_cargoes[i].show_on_map != new_state) { + changes = true; + _legend_from_cargoes[i].show_on_map = new_state; + } + } + if (!changes) { + /* Nothing changed? Then show all (again). */ + for (int i = 0; i != _smallmap_cargo_count; i++) { + _legend_from_cargoes[i].show_on_map = true; + } + } + } else { + _legend_from_cargoes[cargo_pos].show_on_map = !_legend_from_cargoes[cargo_pos].show_on_map; + } + } } this->SetDirty(); } @@ -1436,6 +1624,10 @@ public: for (int i = NUM_NO_COMPANY_ENTRIES; i != _smallmap_company_count; i++) { _legend_land_owners[i].show_on_map = true; } + } else if (this->map_type == SMT_ROUTE_LINKS) { + for (int i = 0; i != _smallmap_cargo_count; i++) { + _legend_from_cargoes[i].show_on_map = true; + } } this->SetDirty(); break; @@ -1445,10 +1637,14 @@ public: for (int i = 0; i != _smallmap_industry_count; i++) { _legend_from_industries[i].show_on_map = false; } - } else { + } else if (this->map_type == SMT_OWNER) { for (int i = NUM_NO_COMPANY_ENTRIES; i != _smallmap_company_count; i++) { _legend_land_owners[i].show_on_map = false; } + } else if (this->map_type == SMT_ROUTE_LINKS) { + for (int i = 0; i != _smallmap_cargo_count; i++) { + _legend_from_cargoes[i].show_on_map = false; + } } this->SetDirty(); break; @@ -1686,6 +1882,8 @@ static const NWidgetPart _nested_smallmap_bar[] = { SetDataTip(SPR_IMG_SHOW_VEHICLES, STR_SMALLMAP_TOOLTIP_SHOW_VEHICLES_ON_MAP), SetFill(1, 1), NWidget(WWT_IMGBTN, COLOUR_BROWN, WID_SM_INDUSTRIES), SetDataTip(SPR_IMG_INDUSTRY, STR_SMALLMAP_TOOLTIP_SHOW_INDUSTRIES_ON_MAP), SetFill(1, 1), + NWidget(WWT_IMGBTN, COLOUR_BROWN, WID_SM_ROUTE_LINKS), + SetDataTip(SPR_IMG_SHOW_ROUTES, STR_SMALLMAP_TOOLTIP_SHOW_ROUTE_LINKS_ON_MAP), SetFill(1, 1), EndContainer(), /* Bottom button row. */ NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), diff --git a/src/smallmap_gui.h b/src/smallmap_gui.h index 572a17534..1cb23e9f6 100644 --- a/src/smallmap_gui.h +++ b/src/smallmap_gui.h @@ -13,6 +13,7 @@ #define SMALLMAP_GUI_H void BuildIndustriesLegend(); +void BuildCargoTypesLegend(); void ShowSmallMap(); void BuildLandLegend(); void BuildOwnerLegend(); diff --git a/src/station.cpp b/src/station.cpp index 43b659476..edfbf7d49 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -13,6 +13,7 @@ #include "company_func.h" #include "company_base.h" #include "roadveh.h" +#include "layer_func.h" #include "viewport_func.h" #include "date_func.h" #include "command_func.h" @@ -24,6 +25,7 @@ #include "roadstop_base.h" #include "industry.h" #include "core/random_func.hpp" +#include "cargodest_func.h" #include "table/strings.h" @@ -93,6 +95,9 @@ Station::~Station() if (v->last_station_visited == this->index) { v->last_station_visited = INVALID_STATION; } + if (v->last_station_loaded == this->index) { + v->last_station_loaded = INVALID_STATION; + } } /* Clear the persistent storage. */ @@ -118,6 +123,7 @@ Station::~Station() } CargoPacket::InvalidateAllFrom(this->index); + InvalidateStationRouteLinks(this); } @@ -307,8 +313,8 @@ static bool FindIndustryToDeliver(TileIndex ind_tile, void *user_data) if (riv->industries_near->Contains(ind)) return false; /* Only process tiles in the station acceptance rectangle */ - int x = TileX(ind_tile); - int y = TileY(ind_tile); + int x = LayerX(ind_tile); + int y = LayerY(ind_tile); if (x < riv->rect.left || x > riv->rect.right || y < riv->rect.top || y > riv->rect.bottom) return false; /* Include only industries that can accept cargo */ @@ -338,7 +344,8 @@ void Station::RecomputeIndustriesNear() }; /* Compute maximum extent of acceptance rectangle wrt. station sign */ - TileIndex start_tile = this->xy; + /* Охватываем верхнюю территорию */ + TileIndex start_tile = TopTile(this->xy); uint max_radius = max( max(DistanceManhattan(start_tile, TileXY(riv.rect.left, riv.rect.top)), DistanceManhattan(start_tile, TileXY(riv.rect.left, riv.rect.bottom))), max(DistanceManhattan(start_tile, TileXY(riv.rect.right, riv.rect.top)), DistanceManhattan(start_tile, TileXY(riv.rect.right, riv.rect.bottom))) @@ -373,7 +380,7 @@ void StationRect::MakeEmpty() /** * Determines whether a given point (x, y) is within a certain distance of * the station rectangle. - * @note x and y are in Tile coordinates + * @note x and y are in Tile coordinates (in top layer) * @param x X coordinate * @param y Y coordinate * @param distance The maximum distance a point may have (L1 norm) @@ -385,6 +392,23 @@ bool StationRect::PtInExtendedRect(int x, int y, int distance) const this->top - distance <= y && y <= this->bottom + distance; } +/** + * Determines whether a tile area intersects the station rectangle with a given offset. + * @param area The tile area to test. + * @param distance Offset the station rect is grown on all sides (L1 norm). + * @return True if the tile area intersects with the station rectangle. + */ +bool StationRect::AreaInExtendedRect(const TileArea& area, int distance) const +{ + int area_left = TileX(area.tile); + int area_right = area_left + area.w; + int area_top = TileY(area.tile); + int area_bottom = area_top + area.h; + + return this->left - distance <= area_right && area_left <= this->right + distance && + this->top - distance <= area_bottom && area_top <= this->bottom + distance; +} + bool StationRect::IsEmpty() const { return this->left == 0 || this->left > this->right || this->top > this->bottom; @@ -392,8 +416,10 @@ bool StationRect::IsEmpty() const CommandCost StationRect::BeforeAddTile(TileIndex tile, StationRectMode mode) { - int x = TileX(tile); - int y = TileY(tile); + /* Станция может находится на любом уровне. + * Но охватывает только поверхность */ + int x = LayerX(tile); + int y = LayerY(tile); if (this->IsEmpty()) { /* we are adding the first station tile */ if (mode != ADD_TEST) { @@ -446,18 +472,25 @@ CommandCost StationRect::BeforeAddRect(TileIndex tile, int w, int h, StationRect */ /* static */ bool StationRect::ScanForStationTiles(StationID st_id, int left_a, int top_a, int right_a, int bottom_a) { + /* Станция может находится на любом уровне. + * Значит надо обойти все слои */ TileArea ta(TileXY(left_a, top_a), TileXY(right_a, bottom_a)); - TILE_AREA_LOOP(tile, ta) { - if (IsTileType(tile, MP_STATION) && GetStationIndex(tile) == st_id) return true; - } + FOR_ALL_LAYERS(layer) { + ta.tile += layer * LayerSize(); + TILE_AREA_LOOP(tile, ta) { + if (IsTileType(tile, MP_STATION) && GetStationIndex(tile) == st_id) return true; + } + } return false; } bool StationRect::AfterRemoveTile(BaseStation *st, TileIndex tile) { - int x = TileX(tile); - int y = TileY(tile); + /* Станция может находится на любом уровне. + * Но охватывает только поверхность */ + int x = LayerX(tile); + int y = LayerY(tile); /* look if removed tile was on the bounding rect edge * and try to reduce the rect by this edge @@ -506,8 +539,13 @@ bool StationRect::AfterRemoveTile(BaseStation *st, TileIndex tile) bool StationRect::AfterRemoveRect(BaseStation *st, TileArea ta) { - assert(this->PtInExtendedRect(TileX(ta.tile), TileY(ta.tile))); - assert(this->PtInExtendedRect(TileX(ta.tile) + ta.w - 1, TileY(ta.tile) + ta.h - 1)); + /* Станция может находится на любом уровне. + * Но охватывает только поверхность */ + int topx = LayerX(ta.tile); + int topy = LayerY(ta.tile); + + assert(this->PtInExtendedRect(topx, topy)); + assert(this->PtInExtendedRect(topx + ta.w - 1, topy + ta.h - 1)); bool empty = this->AfterRemoveTile(st, ta.tile); if (ta.w != 1 || ta.h != 1) empty = empty || this->AfterRemoveTile(st, TILE_ADDXY(ta.tile, ta.w - 1, ta.h - 1)); diff --git a/src/station_base.h b/src/station_base.h index 07d1d2294..d0f6e5b51 100644 --- a/src/station_base.h +++ b/src/station_base.h @@ -17,12 +17,16 @@ #include "cargopacket.h" #include "industry_type.h" #include "newgrf_storage.h" +#include "cargodest_type.h" typedef Pool<BaseStation, StationID, 32, 64000> StationPool; extern StationPool _station_pool; static const byte INITIAL_STATION_RATING = 175; +/** List of RouteLinks. */ +typedef std::list<RouteLink *> RouteLinkList; + /** * Stores station stats for a single cargo. */ @@ -74,7 +78,8 @@ struct GoodsEntry { time_since_pickup(255), rating(INITIAL_STATION_RATING), last_speed(0), - last_age(255) + last_age(255), + cargo_counter(0) {} byte acceptance_pickup; ///< Status of this cargo, see #GoodsEntryStatus. @@ -106,7 +111,9 @@ struct GoodsEntry { byte last_age; byte amount_fract; ///< Fractional part of the amount in the cargo list + uint16 cargo_counter; ///< Update timer for the packets' next hop StationCargoList cargo; ///< The cargo packets of cargo waiting in this station + RouteLinkList routes; ///< List of originating route links /** * Reports whether a vehicle has ever tried to load the cargo at this station. diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index dc898d9bc..acfbc55de 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -13,6 +13,7 @@ #include "aircraft.h" #include "bridge_map.h" #include "cmd_helper.h" +#include "layer_func.h" #include "viewport_func.h" #include "command_func.h" #include "town.h" @@ -48,6 +49,7 @@ #include "table/airporttile_ids.h" #include "newgrf_airporttiles.h" #include "order_backup.h" +#include "cargodest_func.h" #include "newgrf_house.h" #include "company_gui.h" #include "widgets/station_widget.h" @@ -85,24 +87,28 @@ bool IsHangar(TileIndex t) * @return Succeeded command (if zero or one station found) or failed command (for two or more stations found). */ template <class T> -CommandCost GetStationAround(TileArea ta, StationID closest_station, T **st) +CommandCost GetStationAround(TileArea ta, StationID closest_station, T **st, bool layers=false) { ta.tile -= TileDiffXY(1, 1); ta.w += 2; ta.h += 2; /* check around to see if there's any stations there */ - TILE_AREA_LOOP(tile_cur, ta) { - if (IsTileType(tile_cur, MP_STATION)) { - StationID t = GetStationIndex(tile_cur); - if (!T::IsValidID(t)) continue; - - if (closest_station == INVALID_STATION) { - closest_station = t; - } else if (closest_station != t) { - return_cmd_error(STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING); + FOR_ALL_LAYERS(layer) { + TILE_AREA_LOOP(tile, ta) { + TileIndex tile_cur = (layers ? TopTile(tile) + layer*LayerSize() : tile); + if (IsTileType(tile_cur, MP_STATION)) { + StationID t = GetStationIndex(tile_cur); + if (!T::IsValidID(t)) continue; + + if (closest_station == INVALID_STATION) { + closest_station = t; + } else if (closest_station != t) { + return_cmd_error(STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING); + } } } + if (!layers) break; } *st = (closest_station == INVALID_STATION) ? NULL : T::Get(closest_station); return CommandCost(); @@ -656,7 +662,6 @@ static CommandCost BuildStationPart(Station **st, DoCommandFlag flags, bool reus if (flags & DC_EXEC) { *st = new Station(area.tile); - (*st)->town = ClosestTownFromTile(area.tile, UINT_MAX); (*st)->string_id = GenerateStationName(*st, area.tile, name_class); @@ -967,11 +972,13 @@ CommandCost CanExpandRailStation(const BaseStation *st, TileArea &new_ta, Axis a TileArea cur_ta = st->train_station; /* determine new size of train station region.. */ - int x = min(TileX(cur_ta.tile), TileX(new_ta.tile)); - int y = min(TileY(cur_ta.tile), TileY(new_ta.tile)); - new_ta.w = max(TileX(cur_ta.tile) + cur_ta.w, TileX(new_ta.tile) + new_ta.w) - x; - new_ta.h = max(TileY(cur_ta.tile) + cur_ta.h, TileY(new_ta.tile) + new_ta.h) - y; - new_ta.tile = TileXY(x, y); + /* Фактически, подземная станция ("эскалатор") больше ширины карты. + * Поэтому проверям размер в пределах одного слоя */ + int topx = min(LayerX(cur_ta.tile), LayerX(new_ta.tile)); + int topy = min(LayerY(cur_ta.tile), LayerY(new_ta.tile)); + new_ta.w = max(LayerX(cur_ta.tile) + cur_ta.w, LayerX(new_ta.tile) + new_ta.w) - topx; + new_ta.h = max(LayerY(cur_ta.tile) + cur_ta.h, LayerY(new_ta.tile) + new_ta.h) - topy; + new_ta.tile = TileXY(topx, topy); /* make sure the final size is not too big. */ if (new_ta.w > _settings_game.station.station_spread || new_ta.h > _settings_game.station.station_spread) { @@ -1043,7 +1050,7 @@ void GetStationLayout(byte *layout, int numtracks, int plat_len, const StationSp * @return command cost with the error or 'okay' */ template <class T, StringID error_message> -CommandCost FindJoiningBaseStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, T **st) +CommandCost FindJoiningBaseStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, T **st, bool layers=false) { assert(*st == NULL); bool check_surrounding = true; @@ -1069,7 +1076,7 @@ CommandCost FindJoiningBaseStation(StationID existing_station, StationID station if (check_surrounding) { /* Make sure there are no similar stations around us. */ - CommandCost ret = GetStationAround(ta, existing_station, st); + CommandCost ret = GetStationAround(ta, existing_station, st, layers); if (ret.Failed()) return ret; } @@ -1088,9 +1095,9 @@ CommandCost FindJoiningBaseStation(StationID existing_station, StationID station * @param st 'return' pointer for the found station * @return command cost with the error or 'okay' */ -static CommandCost FindJoiningStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Station **st) +static CommandCost FindJoiningStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Station **st, bool layers=false) { - return FindJoiningBaseStation<Station, STR_ERROR_MUST_REMOVE_RAILWAY_STATION_FIRST>(existing_station, station_to_join, adjacent, ta, st); + return FindJoiningBaseStation<Station, STR_ERROR_MUST_REMOVE_RAILWAY_STATION_FIRST>(existing_station, station_to_join, adjacent, ta, st, layers); } /** @@ -1216,8 +1223,10 @@ CommandCost CmdBuildRailStation(TileIndex tile_org, DoCommandFlag flags, uint32 byte numtracks_orig; Track track; + TileIndex top_tile = TopTile(new_location.tile); st->train_station = new_location; - st->AddFacility(FACIL_TRAIN, new_location.tile); + st->train_station.tile = top_tile; + st->AddFacility(FACIL_TRAIN, tile_org); st->rect.BeforeAddRect(tile_org, w_org, h_org, StationRect::ADD_TRY); @@ -1497,7 +1506,7 @@ CommandCost RemoveFromRailBaseStation(TileArea ta, SmallVector<T *, 4> &affected /* now we need to make the "spanned" area of the railway station smaller * if we deleted something at the edges. * we also need to adjust train_tile. */ - MakeRailStationAreaSmaller(st); + st->rect.AfterRemoveTile(st, st->xy); UpdateStationSignCoord(st); /* if we deleted the whole station, delete the train facility. */ @@ -1590,11 +1599,16 @@ CommandCost RemoveRailStation(T *st, DoCommandFlag flags) /* determine width and height of platforms */ TileArea ta = st->train_station; - assert(ta.w != 0 && ta.h != 0); + /* TileArea is top finite area */ + assert(IsTopTile(ta.tile)); + assert(ta.IsFinite()); CommandCost cost(EXPENSES_CONSTRUCTION); + /* Check all layers */ + FOR_ALL_LAYERS(layer) /* clear all areas of the station */ - TILE_AREA_LOOP(tile, ta) { + TILE_AREA_LOOP(top_tile, ta) { + TileIndex tile = top_tile + layer * LayerSize(); /* only remove tiles that are actually train station tiles */ if (!st->TileBelongsToRailStation(tile)) continue; @@ -1992,11 +2006,20 @@ CommandCost CmdRemoveRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, ui /* Check if the first tile and the last tile are valid */ if (!IsValidTile(tile) || TileAddWrap(tile, width - 1, height - 1) == INVALID_TILE) return CMD_ERROR; - TileArea roadstop_area(tile, width, height); + /* Это территория удаления остановок (НЕ самих остановок) */ + TileArea roadstop_area(TopTile(tile), width, height); + + /* TileArea is top finite area */ + assert(IsTopTile(roadstop_area.tile)); + assert(roadstop_area.IsFinite()); int quantity = 0; CommandCost cost(EXPENSES_CONSTRUCTION); - TILE_AREA_LOOP(cur_tile, roadstop_area) { + + /* Check all layers */ + FOR_ALL_LAYERS(layer) + TILE_AREA_LOOP(top_tile, roadstop_area) { + TileIndex cur_tile = top_tile + layer * LayerSize(); /* Make sure the specified tile is a road stop of the correct type */ if (!IsTileType(cur_tile, MP_STATION) || !IsRoadStop(cur_tile) || (uint32)GetRoadStopType(cur_tile) != GB(p2, 0, 1)) continue; @@ -2173,6 +2196,10 @@ CommandCost CmdBuildAirport(TileIndex tile, DoCommandFlag flags, uint32 p1, uint if (w > _settings_game.station.station_spread || h > _settings_game.station.station_spread) { return_cmd_error(STR_ERROR_STATION_TOO_SPREAD_OUT); } + /* can't make underground airport */ + if (IsUnderground(tile)) { + return_cmd_error(STR_ERROR_UNDERGROUND_CAN_T_BUILD_UNDER_GROUND); + } CommandCost cost = CheckFlatLand(airport_area, flags); if (cost.Failed()) return cost; @@ -3349,6 +3376,17 @@ void OnTick_Station() TriggerStationAnimation(st, st->xy, SAT_250_TICKS); if (Station::IsExpected(st)) AirportAnimationTrigger(Station::From(st), AAT_STATION_250_TICKS); } + + if (Station::IsExpected(st)) { + /* Age and expire route links. */ + Station *s = Station::From(st); + if (s->index % DAY_TICKS == _date_fract) AgeRouteLinks(s); + + /* Decrement cargo update counter. */ + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + if (s->goods[cid].cargo_counter > 0) s->goods[cid].cargo_counter--; + } + } } } @@ -3385,7 +3423,7 @@ void ModifyStationRatingAround(TileIndex tile, Owner owner, int amount, uint rad } } -static uint UpdateStationWaiting(Station *st, CargoID type, uint amount, SourceType source_type, SourceID source_id) +uint UpdateStationWaiting(Station *st, CargoID type, uint amount, SourceType source_type, SourceID source_id, TileIndex dest_tile, SourceType dest_type, SourceID dest_id, OrderID next_hop, StationID next_unload, byte flags) { /* We can't allocate a CargoPacket? Then don't do anything * at all; i.e. just discard the incoming cargo. */ @@ -3399,7 +3437,7 @@ static uint UpdateStationWaiting(Station *st, CargoID type, uint amount, SourceT /* No new "real" cargo item yet. */ if (amount == 0) return 0; - ge.cargo.Append(new CargoPacket(st->index, st->xy, amount, source_type, source_id)); + ge.cargo.Append(new CargoPacket(st->index, st->xy, amount, source_type, source_id, dest_tile, dest_type, dest_id, next_hop, next_unload, flags)); if (!HasBit(ge.acceptance_pickup, GoodsEntry::GES_PICKUP)) { InvalidateWindowData(WC_STATION_LIST, st->index); @@ -3524,11 +3562,14 @@ const StationList *StationFinder::GetStations() return &this->stations; } -uint MoveGoodsToStation(CargoID type, uint amount, SourceType source_type, SourceID source_id, const StationList *all_stations) +uint MoveGoodsToStation(CargoID type, uint amount, SourceType source_type, SourceID source_id, const StationList *all_stations, TileIndex src_tile) { /* Return if nothing to do. Also the rounding below fails for 0. */ if (amount == 0) return 0; + /* Handle cargo that has cargo destinations enabled. */ + if (MoveCargoWithDestinationToStation(type, &amount, source_type, source_id, all_stations, src_tile)) return amount; + Station *st1 = NULL; // Station with best rating Station *st2 = NULL; // Second best station uint best_rating1 = 0; // rating of st1 diff --git a/src/station_cmd.cpp.orig b/src/station_cmd.cpp.orig new file mode 100644 index 000000000..dc898d9bc --- /dev/null +++ b/src/station_cmd.cpp.orig @@ -0,0 +1,3848 @@ +/* $Id$ */ + +/* + * 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 station_cmd.cpp Handling of station tiles. */ + +#include "stdafx.h" +#include "aircraft.h" +#include "bridge_map.h" +#include "cmd_helper.h" +#include "viewport_func.h" +#include "command_func.h" +#include "town.h" +#include "news_func.h" +#include "train.h" +#include "ship.h" +#include "roadveh.h" +#include "industry.h" +#include "newgrf_cargo.h" +#include "newgrf_debug.h" +#include "newgrf_station.h" +#include "newgrf_canal.h" /* For the buoy */ +#include "pathfinder/yapf/yapf_cache.h" +#include "road_internal.h" /* For drawing catenary/checking road removal */ +#include "autoslope.h" +#include "water.h" +#include "strings_func.h" +#include "clear_func.h" +#include "date_func.h" +#include "vehicle_func.h" +#include "string_func.h" +#include "animated_tile_func.h" +#include "elrail_func.h" +#include "station_base.h" +#include "roadstop_base.h" +#include "newgrf_railtype.h" +#include "waypoint_base.h" +#include "waypoint_func.h" +#include "pbs.h" +#include "debug.h" +#include "core/random_func.hpp" +#include "company_base.h" +#include "table/airporttile_ids.h" +#include "newgrf_airporttiles.h" +#include "order_backup.h" +#include "newgrf_house.h" +#include "company_gui.h" +#include "widgets/station_widget.h" + +#include "table/strings.h" + +/** + * Check whether the given tile is a hangar. + * @param t the tile to of whether it is a hangar. + * @pre IsTileType(t, MP_STATION) + * @return true if and only if the tile is a hangar. + */ +bool IsHangar(TileIndex t) +{ + assert(IsTileType(t, MP_STATION)); + + /* If the tile isn't an airport there's no chance it's a hangar. */ + if (!IsAirport(t)) return false; + + const Station *st = Station::GetByTile(t); + const AirportSpec *as = st->airport.GetSpec(); + + for (uint i = 0; i < as->nof_depots; i++) { + if (st->airport.GetHangarTile(i) == t) return true; + } + + return false; +} + +/** + * Look for a station around the given tile area. + * @param ta the area to search over + * @param closest_station the closest station found so far + * @param st to 'return' the found station + * @return Succeeded command (if zero or one station found) or failed command (for two or more stations found). + */ +template <class T> +CommandCost GetStationAround(TileArea ta, StationID closest_station, T **st) +{ + ta.tile -= TileDiffXY(1, 1); + ta.w += 2; + ta.h += 2; + + /* check around to see if there's any stations there */ + TILE_AREA_LOOP(tile_cur, ta) { + if (IsTileType(tile_cur, MP_STATION)) { + StationID t = GetStationIndex(tile_cur); + if (!T::IsValidID(t)) continue; + + if (closest_station == INVALID_STATION) { + closest_station = t; + } else if (closest_station != t) { + return_cmd_error(STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING); + } + } + } + *st = (closest_station == INVALID_STATION) ? NULL : T::Get(closest_station); + return CommandCost(); +} + +/** + * Function to check whether the given tile matches some criterion. + * @param tile the tile to check + * @return true if it matches, false otherwise + */ +typedef bool (*CMSAMatcher)(TileIndex tile); + +/** + * Counts the numbers of tiles matching a specific type in the area around + * @param tile the center tile of the 'count area' + * @param cmp the comparator/matcher (@see CMSAMatcher) + * @return the number of matching tiles around + */ +static int CountMapSquareAround(TileIndex tile, CMSAMatcher cmp) +{ + int num = 0; + + for (int dx = -3; dx <= 3; dx++) { + for (int dy = -3; dy <= 3; dy++) { + TileIndex t = TileAddWrap(tile, dx, dy); + if (t != INVALID_TILE && cmp(t)) num++; + } + } + + return num; +} + +/** + * Check whether the tile is a mine. + * @param tile the tile to investigate. + * @return true if and only if the tile is a mine + */ +static bool CMSAMine(TileIndex tile) +{ + /* No industry */ + if (!IsTileType(tile, MP_INDUSTRY)) return false; + + const Industry *ind = Industry::GetByTile(tile); + + /* No extractive industry */ + if ((GetIndustrySpec(ind->type)->life_type & INDUSTRYLIFE_EXTRACTIVE) == 0) return false; + + for (uint i = 0; i < lengthof(ind->produced_cargo); i++) { + /* The industry extracts something non-liquid, i.e. no oil or plastic, so it is a mine. + * Also the production of passengers and mail is ignored. */ + if (ind->produced_cargo[i] != CT_INVALID && + (CargoSpec::Get(ind->produced_cargo[i])->classes & (CC_LIQUID | CC_PASSENGERS | CC_MAIL)) == 0) { + return true; + } + } + + return false; +} + +/** + * Check whether the tile is water. + * @param tile the tile to investigate. + * @return true if and only if the tile is a water tile + */ +static bool CMSAWater(TileIndex tile) +{ + return IsTileType(tile, MP_WATER) && IsWater(tile); +} + +/** + * Check whether the tile is a tree. + * @param tile the tile to investigate. + * @return true if and only if the tile is a tree tile + */ +static bool CMSATree(TileIndex tile) +{ + return IsTileType(tile, MP_TREES); +} + +#define M(x) ((x) - STR_SV_STNAME) + +enum StationNaming { + STATIONNAMING_RAIL, + STATIONNAMING_ROAD, + STATIONNAMING_AIRPORT, + STATIONNAMING_OILRIG, + STATIONNAMING_DOCK, + STATIONNAMING_HELIPORT, +}; + +/** Information to handle station action 0 property 24 correctly */ +struct StationNameInformation { + uint32 free_names; ///< Current bitset of free names (we can remove names). + bool *indtypes; ///< Array of bools telling whether an industry type has been found. +}; + +/** + * Find a station action 0 property 24 station name, or reduce the + * free_names if needed. + * @param tile the tile to search + * @param user_data the StationNameInformation to base the search on + * @return true if the tile contains an industry that has not given + * its name to one of the other stations in town. + */ +static bool FindNearIndustryName(TileIndex tile, void *user_data) +{ + /* All already found industry types */ + StationNameInformation *sni = (StationNameInformation*)user_data; + if (!IsTileType(tile, MP_INDUSTRY)) return false; + + /* If the station name is undefined it means that it doesn't name a station */ + IndustryType indtype = GetIndustryType(tile); + if (GetIndustrySpec(indtype)->station_name == STR_UNDEFINED) return false; + + /* In all cases if an industry that provides a name is found two of + * the standard names will be disabled. */ + sni->free_names &= ~(1 << M(STR_SV_STNAME_OILFIELD) | 1 << M(STR_SV_STNAME_MINES)); + return !sni->indtypes[indtype]; +} + +static StringID GenerateStationName(Station *st, TileIndex tile, StationNaming name_class) +{ + static const uint32 _gen_station_name_bits[] = { + 0, // STATIONNAMING_RAIL + 0, // STATIONNAMING_ROAD + 1U << M(STR_SV_STNAME_AIRPORT), // STATIONNAMING_AIRPORT + 1U << M(STR_SV_STNAME_OILFIELD), // STATIONNAMING_OILRIG + 1U << M(STR_SV_STNAME_DOCKS), // STATIONNAMING_DOCK + 1U << M(STR_SV_STNAME_HELIPORT), // STATIONNAMING_HELIPORT + }; + + const Town *t = st->town; + uint32 free_names = UINT32_MAX; + + bool indtypes[NUM_INDUSTRYTYPES]; + memset(indtypes, 0, sizeof(indtypes)); + + const Station *s; + FOR_ALL_STATIONS(s) { + if (s != st && s->town == t) { + if (s->indtype != IT_INVALID) { + indtypes[s->indtype] = true; + continue; + } + uint str = M(s->string_id); + if (str <= 0x20) { + if (str == M(STR_SV_STNAME_FOREST)) { + str = M(STR_SV_STNAME_WOODS); + } + ClrBit(free_names, str); + } + } + } + + TileIndex indtile = tile; + StationNameInformation sni = { free_names, indtypes }; + if (CircularTileSearch(&indtile, 7, FindNearIndustryName, &sni)) { + /* An industry has been found nearby */ + IndustryType indtype = GetIndustryType(indtile); + const IndustrySpec *indsp = GetIndustrySpec(indtype); + /* STR_NULL means it only disables oil rig/mines */ + if (indsp->station_name != STR_NULL) { + st->indtype = indtype; + return STR_SV_STNAME_FALLBACK; + } + } + + /* Oil rigs/mines name could be marked not free by looking for a near by industry. */ + free_names = sni.free_names; + + /* check default names */ + uint32 tmp = free_names & _gen_station_name_bits[name_class]; + if (tmp != 0) return STR_SV_STNAME + FindFirstBit(tmp); + + /* check mine? */ + if (HasBit(free_names, M(STR_SV_STNAME_MINES))) { + if (CountMapSquareAround(tile, CMSAMine) >= 2) { + return STR_SV_STNAME_MINES; + } + } + + /* check close enough to town to get central as name? */ + if (DistanceMax(tile, t->xy) < 8) { + if (HasBit(free_names, M(STR_SV_STNAME))) return STR_SV_STNAME; + + if (HasBit(free_names, M(STR_SV_STNAME_CENTRAL))) return STR_SV_STNAME_CENTRAL; + } + + /* Check lakeside */ + if (HasBit(free_names, M(STR_SV_STNAME_LAKESIDE)) && + DistanceFromEdge(tile) < 20 && + CountMapSquareAround(tile, CMSAWater) >= 5) { + return STR_SV_STNAME_LAKESIDE; + } + + /* Check woods */ + if (HasBit(free_names, M(STR_SV_STNAME_WOODS)) && ( + CountMapSquareAround(tile, CMSATree) >= 8 || + CountMapSquareAround(tile, IsTileForestIndustry) >= 2) + ) { + return _settings_game.game_creation.landscape == LT_TROPIC ? STR_SV_STNAME_FOREST : STR_SV_STNAME_WOODS; + } + + /* check elevation compared to town */ + int z = GetTileZ(tile); + int z2 = GetTileZ(t->xy); + if (z < z2) { + if (HasBit(free_names, M(STR_SV_STNAME_VALLEY))) return STR_SV_STNAME_VALLEY; + } else if (z > z2) { + if (HasBit(free_names, M(STR_SV_STNAME_HEIGHTS))) return STR_SV_STNAME_HEIGHTS; + } + + /* check direction compared to town */ + static const int8 _direction_and_table[] = { + ~( (1 << M(STR_SV_STNAME_WEST)) | (1 << M(STR_SV_STNAME_EAST)) | (1 << M(STR_SV_STNAME_NORTH)) ), + ~( (1 << M(STR_SV_STNAME_SOUTH)) | (1 << M(STR_SV_STNAME_WEST)) | (1 << M(STR_SV_STNAME_NORTH)) ), + ~( (1 << M(STR_SV_STNAME_SOUTH)) | (1 << M(STR_SV_STNAME_EAST)) | (1 << M(STR_SV_STNAME_NORTH)) ), + ~( (1 << M(STR_SV_STNAME_SOUTH)) | (1 << M(STR_SV_STNAME_WEST)) | (1 << M(STR_SV_STNAME_EAST)) ), + }; + + free_names &= _direction_and_table[ + (TileX(tile) < TileX(t->xy)) + + (TileY(tile) < TileY(t->xy)) * 2]; + + tmp = free_names & ((1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 6) | (1 << 7) | (1 << 12) | (1 << 26) | (1 << 27) | (1 << 28) | (1 << 29) | (1 << 30)); + return (tmp == 0) ? STR_SV_STNAME_FALLBACK : (STR_SV_STNAME + FindFirstBit(tmp)); +} +#undef M + +/** + * Find the closest deleted station of the current company + * @param tile the tile to search from. + * @return the closest station or NULL if too far. + */ +static Station *GetClosestDeletedStation(TileIndex tile) +{ + uint threshold = 8; + Station *best_station = NULL; + Station *st; + + FOR_ALL_STATIONS(st) { + if (!st->IsInUse() && st->owner == _current_company) { + uint cur_dist = DistanceManhattan(tile, st->xy); + + if (cur_dist < threshold) { + threshold = cur_dist; + best_station = st; + } + } + } + + return best_station; +} + + +void Station::GetTileArea(TileArea *ta, StationType type) const +{ + switch (type) { + case STATION_RAIL: + *ta = this->train_station; + return; + + case STATION_AIRPORT: + *ta = this->airport; + return; + + case STATION_TRUCK: + *ta = this->truck_station; + return; + + case STATION_BUS: + *ta = this->bus_station; + return; + + case STATION_DOCK: + case STATION_OILRIG: + ta->tile = this->dock_tile; + break; + + default: NOT_REACHED(); + } + + ta->w = 1; + ta->h = 1; +} + +/** + * Update the virtual coords needed to draw the station sign. + */ +void Station::UpdateVirtCoord() +{ + Point pt = RemapCoords2(TileX(this->xy) * TILE_SIZE, TileY(this->xy) * TILE_SIZE); + + pt.y -= 32 * ZOOM_LVL_BASE; + if ((this->facilities & FACIL_AIRPORT) && this->airport.type == AT_OILRIG) pt.y -= 16 * ZOOM_LVL_BASE; + + SetDParam(0, this->index); + SetDParam(1, this->facilities); + this->sign.UpdatePosition(pt.x, pt.y, STR_VIEWPORT_STATION); + + SetWindowDirty(WC_STATION_VIEW, this->index); +} + +/** Update the virtual coords needed to draw the station sign for all stations. */ +void UpdateAllStationVirtCoords() +{ + BaseStation *st; + + FOR_ALL_BASE_STATIONS(st) { + st->UpdateVirtCoord(); + } +} + +/** + * Get a mask of the cargo types that the station accepts. + * @param st Station to query + * @return the expected mask + */ +static uint GetAcceptanceMask(const Station *st) +{ + uint mask = 0; + + for (CargoID i = 0; i < NUM_CARGO; i++) { + if (HasBit(st->goods[i].acceptance_pickup, GoodsEntry::GES_ACCEPTANCE)) mask |= 1 << i; + } + return mask; +} + +/** + * Items contains the two cargo names that are to be accepted or rejected. + * msg is the string id of the message to display. + */ +static void ShowRejectOrAcceptNews(const Station *st, uint num_items, CargoID *cargo, StringID msg) +{ + for (uint i = 0; i < num_items; i++) { + SetDParam(i + 1, CargoSpec::Get(cargo[i])->name); + } + + SetDParam(0, st->index); + AddNewsItem(msg, NT_ACCEPTANCE, NF_INCOLOUR | NF_SMALL, NR_STATION, st->index); +} + +/** + * Get the cargo types being produced around the tile (in a rectangle). + * @param tile Northtile of area + * @param w X extent of the area + * @param h Y extent of the area + * @param rad Search radius in addition to the given area + */ +CargoArray GetProductionAroundTiles(TileIndex tile, int w, int h, int rad) +{ + CargoArray produced; + + int x = TileX(tile); + int y = TileY(tile); + + /* expand the region by rad tiles on each side + * while making sure that we remain inside the board. */ + int x2 = min(x + w + rad, MapSizeX()); + int x1 = max(x - rad, 0); + + int y2 = min(y + h + rad, MapSizeY()); + int y1 = max(y - rad, 0); + + assert(x1 < x2); + assert(y1 < y2); + assert(w > 0); + assert(h > 0); + + TileArea ta(TileXY(x1, y1), TileXY(x2 - 1, y2 - 1)); + + /* Loop over all tiles to get the produced cargo of + * everything except industries */ + TILE_AREA_LOOP(tile, ta) AddProducedCargo(tile, produced); + + /* Loop over the industries. They produce cargo for + * anything that is within 'rad' from their bounding + * box. As such if you have e.g. a oil well the tile + * area loop might not hit an industry tile while + * the industry would produce cargo for the station. + */ + const Industry *i; + FOR_ALL_INDUSTRIES(i) { + if (!ta.Intersects(i->location)) continue; + + for (uint j = 0; j < lengthof(i->produced_cargo); j++) { + CargoID cargo = i->produced_cargo[j]; + if (cargo != CT_INVALID) produced[cargo]++; + } + } + + return produced; +} + +/** + * Get the acceptance of cargoes around the tile in 1/8. + * @param tile Center of the search area + * @param w X extent of area + * @param h Y extent of area + * @param rad Search radius in addition to given area + * @param always_accepted bitmask of cargo accepted by houses and headquarters; can be NULL + */ +CargoArray GetAcceptanceAroundTiles(TileIndex tile, int w, int h, int rad, uint32 *always_accepted) +{ + CargoArray acceptance; + if (always_accepted != NULL) *always_accepted = 0; + + int x = TileX(tile); + int y = TileY(tile); + + /* expand the region by rad tiles on each side + * while making sure that we remain inside the board. */ + int x2 = min(x + w + rad, MapSizeX()); + int y2 = min(y + h + rad, MapSizeY()); + int x1 = max(x - rad, 0); + int y1 = max(y - rad, 0); + + assert(x1 < x2); + assert(y1 < y2); + assert(w > 0); + assert(h > 0); + + for (int yc = y1; yc != y2; yc++) { + for (int xc = x1; xc != x2; xc++) { + TileIndex tile = TileXY(xc, yc); + AddAcceptedCargo(tile, acceptance, always_accepted); + } + } + + return acceptance; +} + +/** + * Update the acceptance for a station. + * @param st Station to update + * @param show_msg controls whether to display a message that acceptance was changed. + */ +void UpdateStationAcceptance(Station *st, bool show_msg) +{ + /* old accepted goods types */ + uint old_acc = GetAcceptanceMask(st); + + /* And retrieve the acceptance. */ + CargoArray acceptance; + if (!st->rect.IsEmpty()) { + acceptance = GetAcceptanceAroundTiles( + TileXY(st->rect.left, st->rect.top), + st->rect.right - st->rect.left + 1, + st->rect.bottom - st->rect.top + 1, + st->GetCatchmentRadius(), + &st->always_accepted + ); + } + + /* Adjust in case our station only accepts fewer kinds of goods */ + for (CargoID i = 0; i < NUM_CARGO; i++) { + uint amt = min(acceptance[i], 15); + + /* Make sure the station can accept the goods type. */ + bool is_passengers = IsCargoInClass(i, CC_PASSENGERS); + if ((!is_passengers && !(st->facilities & ~FACIL_BUS_STOP)) || + (is_passengers && !(st->facilities & ~FACIL_TRUCK_STOP))) { + amt = 0; + } + + SB(st->goods[i].acceptance_pickup, GoodsEntry::GES_ACCEPTANCE, 1, amt >= 8); + } + + /* Only show a message in case the acceptance was actually changed. */ + uint new_acc = GetAcceptanceMask(st); + if (old_acc == new_acc) return; + + /* show a message to report that the acceptance was changed? */ + if (show_msg && st->owner == _local_company && st->IsInUse()) { + /* List of accept and reject strings for different number of + * cargo types */ + static const StringID accept_msg[] = { + STR_NEWS_STATION_NOW_ACCEPTS_CARGO, + STR_NEWS_STATION_NOW_ACCEPTS_CARGO_AND_CARGO, + }; + static const StringID reject_msg[] = { + STR_NEWS_STATION_NO_LONGER_ACCEPTS_CARGO, + STR_NEWS_STATION_NO_LONGER_ACCEPTS_CARGO_OR_CARGO, + }; + + /* Array of accepted and rejected cargo types */ + CargoID accepts[2] = { CT_INVALID, CT_INVALID }; + CargoID rejects[2] = { CT_INVALID, CT_INVALID }; + uint num_acc = 0; + uint num_rej = 0; + + /* Test each cargo type to see if its acceptance has changed */ + for (CargoID i = 0; i < NUM_CARGO; i++) { + if (HasBit(new_acc, i)) { + if (!HasBit(old_acc, i) && num_acc < lengthof(accepts)) { + /* New cargo is accepted */ + accepts[num_acc++] = i; + } + } else { + if (HasBit(old_acc, i) && num_rej < lengthof(rejects)) { + /* Old cargo is no longer accepted */ + rejects[num_rej++] = i; + } + } + } + + /* Show news message if there are any changes */ + if (num_acc > 0) ShowRejectOrAcceptNews(st, num_acc, accepts, accept_msg[num_acc - 1]); + if (num_rej > 0) ShowRejectOrAcceptNews(st, num_rej, rejects, reject_msg[num_rej - 1]); + } + + /* redraw the station view since acceptance changed */ + SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_ACCEPT_RATING_LIST); +} + +static void UpdateStationSignCoord(BaseStation *st) +{ + const StationRect *r = &st->rect; + + if (r->IsEmpty()) return; // no tiles belong to this station + + /* clamp sign coord to be inside the station rect */ + st->xy = TileXY(ClampU(TileX(st->xy), r->left, r->right), ClampU(TileY(st->xy), r->top, r->bottom)); + st->UpdateVirtCoord(); +} + +/** + * Common part of building various station parts and possibly attaching them to an existing one. + * @param [in,out] st Station to attach to + * @param flags Command flags + * @param reuse Whether to try to reuse a deleted station (gray sign) if possible + * @param area Area occupied by the new part + * @param name_class Station naming class to use to generate the new station's name + * @return Command error that occured, if any + */ +static CommandCost BuildStationPart(Station **st, DoCommandFlag flags, bool reuse, TileArea area, StationNaming name_class) +{ + /* Find a deleted station close to us */ + if (*st == NULL && reuse) *st = GetClosestDeletedStation(area.tile); + + if (*st != NULL) { + if ((*st)->owner != _current_company) { + return_cmd_error(STR_ERROR_TOO_CLOSE_TO_ANOTHER_STATION); + } + + CommandCost ret = (*st)->rect.BeforeAddRect(area.tile, area.w, area.h, StationRect::ADD_TEST); + if (ret.Failed()) return ret; + } else { + /* allocate and initialize new station */ + if (!Station::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING); + + if (flags & DC_EXEC) { + *st = new Station(area.tile); + + (*st)->town = ClosestTownFromTile(area.tile, UINT_MAX); + (*st)->string_id = GenerateStationName(*st, area.tile, name_class); + + if (Company::IsValidID(_current_company)) { + SetBit((*st)->town->have_ratings, _current_company); + } + } + } + return CommandCost(); +} + +/** + * This is called right after a station was deleted. + * It checks if the whole station is free of substations, and if so, the station will be + * deleted after a little while. + * @param st Station + */ +static void DeleteStationIfEmpty(BaseStation *st) +{ + if (!st->IsInUse()) { + st->delete_ctr = 0; + InvalidateWindowData(WC_STATION_LIST, st->owner, 0); + } + /* station remains but it probably lost some parts - station sign should stay in the station boundaries */ + UpdateStationSignCoord(st); +} + +CommandCost ClearTile_Station(TileIndex tile, DoCommandFlag flags); + +/** + * Checks if the given tile is buildable, flat and has a certain height. + * @param tile TileIndex to check. + * @param invalid_dirs Prohibited directions for slopes (set of #DiagDirection). + * @param allowed_z Height allowed for the tile. If allowed_z is negative, it will be set to the height of this tile. + * @param allow_steep Whether steep slopes are allowed. + * @param check_bridge Check for the existence of a bridge. + * @return The cost in case of success, or an error code if it failed. + */ +CommandCost CheckBuildableTile(TileIndex tile, uint invalid_dirs, int &allowed_z, bool allow_steep, bool check_bridge = true) +{ + if (check_bridge && MayHaveBridgeAbove(tile) && IsBridgeAbove(tile)) { + return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); + } + + CommandCost ret = EnsureNoVehicleOnGround(tile); + if (ret.Failed()) return ret; + + int z; + Slope tileh = GetTileSlope(tile, &z); + + /* Prohibit building if + * 1) The tile is "steep" (i.e. stretches two height levels). + * 2) The tile is non-flat and the build_on_slopes switch is disabled. + */ + if ((!allow_steep && IsSteepSlope(tileh)) || + ((!_settings_game.construction.build_on_slopes) && tileh != SLOPE_FLAT)) { + return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + } + + CommandCost cost(EXPENSES_CONSTRUCTION); + int flat_z = z + GetSlopeMaxZ(tileh); + if (tileh != SLOPE_FLAT) { + /* Forbid building if the tile faces a slope in a invalid direction. */ + for (DiagDirection dir = DIAGDIR_BEGIN; dir != DIAGDIR_END; dir++) { + if (HasBit(invalid_dirs, dir) && !CanBuildDepotByTileh(dir, tileh)) { + return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + } + } + cost.AddCost(_price[PR_BUILD_FOUNDATION]); + } + + /* The level of this tile must be equal to allowed_z. */ + if (allowed_z < 0) { + /* First tile. */ + allowed_z = flat_z; + } else if (allowed_z != flat_z) { + return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + } + + return cost; +} + +/** + * Tries to clear the given area. + * @param tile_area Area to check. + * @param flags Operation to perform. + * @return The cost in case of success, or an error code if it failed. + */ +CommandCost CheckFlatLand(TileArea tile_area, DoCommandFlag flags) +{ + CommandCost cost(EXPENSES_CONSTRUCTION); + int allowed_z = -1; + + TILE_AREA_LOOP(tile_cur, tile_area) { + CommandCost ret = CheckBuildableTile(tile_cur, 0, allowed_z, true); + if (ret.Failed()) return ret; + cost.AddCost(ret); + + ret = DoCommand(tile_cur, 0, 0, flags, CMD_LANDSCAPE_CLEAR); + if (ret.Failed()) return ret; + cost.AddCost(ret); + } + + return cost; +} + +/** + * Checks if a rail station can be built at the given area. + * @param tile_area Area to check. + * @param flags Operation to perform. + * @param axis Rail station axis. + * @param station StationID to be queried and returned if available. + * @param rt The rail type to check for (overbuilding rail stations over rail). + * @param affected_vehicles List of trains with PBS reservations on the tiles + * @param spec_class Station class. + * @param spec_index Index into the station class. + * @param plat_len Platform length. + * @param numtracks Number of platforms. + * @return The cost in case of success, or an error code if it failed. + */ +static CommandCost CheckFlatLandRailStation(TileArea tile_area, DoCommandFlag flags, Axis axis, StationID *station, RailType rt, SmallVector<Train *, 4> &affected_vehicles, StationClassID spec_class, byte spec_index, byte plat_len, byte numtracks) +{ + CommandCost cost(EXPENSES_CONSTRUCTION); + int allowed_z = -1; + uint invalid_dirs = 5 << axis; + + const StationSpec *statspec = StationClass::Get(spec_class)->GetSpec(spec_index); + bool slope_cb = statspec != NULL && HasBit(statspec->callback_mask, CBM_STATION_SLOPE_CHECK); + + TILE_AREA_LOOP(tile_cur, tile_area) { + CommandCost ret = CheckBuildableTile(tile_cur, invalid_dirs, allowed_z, false); + if (ret.Failed()) return ret; + cost.AddCost(ret); + + if (slope_cb) { + /* Do slope check if requested. */ + ret = PerformStationTileSlopeCheck(tile_area.tile, tile_cur, statspec, axis, plat_len, numtracks); + if (ret.Failed()) return ret; + } + + /* if station is set, then we have special handling to allow building on top of already existing stations. + * so station points to INVALID_STATION if we can build on any station. + * Or it points to a station if we're only allowed to build on exactly that station. */ + if (station != NULL && IsTileType(tile_cur, MP_STATION)) { + if (!IsRailStation(tile_cur)) { + return ClearTile_Station(tile_cur, DC_AUTO); // get error message + } else { + StationID st = GetStationIndex(tile_cur); + if (*station == INVALID_STATION) { + *station = st; + } else if (*station != st) { + return_cmd_error(STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING); + } + } + } else { + /* Rail type is only valid when building a railway station; if station to + * build isn't a rail station it's INVALID_RAILTYPE. */ + if (rt != INVALID_RAILTYPE && + IsPlainRailTile(tile_cur) && !HasSignals(tile_cur) && + HasPowerOnRail(GetRailType(tile_cur), rt)) { + /* Allow overbuilding if the tile: + * - has rail, but no signals + * - it has exactly one track + * - the track is in line with the station + * - the current rail type has power on the to-be-built type (e.g. convert normal rail to el rail) + */ + TrackBits tracks = GetTrackBits(tile_cur); + Track track = RemoveFirstTrack(&tracks); + Track expected_track = HasBit(invalid_dirs, DIAGDIR_NE) ? TRACK_X : TRACK_Y; + + if (tracks == TRACK_BIT_NONE && track == expected_track) { + /* Check for trains having a reservation for this tile. */ + if (HasBit(GetRailReservationTrackBits(tile_cur), track)) { + Train *v = GetTrainForReservation(tile_cur, track); + if (v != NULL) { + *affected_vehicles.Append() = v; + } + } + CommandCost ret = DoCommand(tile_cur, 0, track, flags, CMD_REMOVE_SINGLE_RAIL); + if (ret.Failed()) return ret; + cost.AddCost(ret); + /* With flags & ~DC_EXEC CmdLandscapeClear would fail since the rail still exists */ + continue; + } + } + ret = DoCommand(tile_cur, 0, 0, flags, CMD_LANDSCAPE_CLEAR); + if (ret.Failed()) return ret; + cost.AddCost(ret); + } + } + + return cost; +} + +/** + * Checks if a road stop can be built at the given tile. + * @param tile_area Area to check. + * @param flags Operation to perform. + * @param invalid_dirs Prohibited directions (set of DiagDirections). + * @param is_drive_through True if trying to build a drive-through station. + * @param is_truck_stop True when building a truck stop, false otherwise. + * @param axis Axis of a drive-through road stop. + * @param station StationID to be queried and returned if available. + * @param rts Road types to build. + * @return The cost in case of success, or an error code if it failed. + */ +static CommandCost CheckFlatLandRoadStop(TileArea tile_area, DoCommandFlag flags, uint invalid_dirs, bool is_drive_through, bool is_truck_stop, Axis axis, StationID *station, RoadTypes rts) +{ + CommandCost cost(EXPENSES_CONSTRUCTION); + int allowed_z = -1; + + TILE_AREA_LOOP(cur_tile, tile_area) { + CommandCost ret = CheckBuildableTile(cur_tile, invalid_dirs, allowed_z, !is_drive_through); + if (ret.Failed()) return ret; + cost.AddCost(ret); + + /* If station is set, then we have special handling to allow building on top of already existing stations. + * Station points to INVALID_STATION if we can build on any station. + * Or it points to a station if we're only allowed to build on exactly that station. */ + if (station != NULL && IsTileType(cur_tile, MP_STATION)) { + if (!IsRoadStop(cur_tile)) { + return ClearTile_Station(cur_tile, DC_AUTO); // Get error message. + } else { + if (is_truck_stop != IsTruckStop(cur_tile) || + is_drive_through != IsDriveThroughStopTile(cur_tile)) { + return ClearTile_Station(cur_tile, DC_AUTO); // Get error message. + } + /* Drive-through station in the wrong direction. */ + if (is_drive_through && IsDriveThroughStopTile(cur_tile) && DiagDirToAxis(GetRoadStopDir(cur_tile)) != axis){ + return_cmd_error(STR_ERROR_DRIVE_THROUGH_DIRECTION); + } + StationID st = GetStationIndex(cur_tile); + if (*station == INVALID_STATION) { + *station = st; + } else if (*station != st) { + return_cmd_error(STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING); + } + } + } else { + bool build_over_road = is_drive_through && IsNormalRoadTile(cur_tile); + /* Road bits in the wrong direction. */ + RoadBits rb = IsNormalRoadTile(cur_tile) ? GetAllRoadBits(cur_tile) : ROAD_NONE; + if (build_over_road && (rb & (axis == AXIS_X ? ROAD_Y : ROAD_X)) != 0) { + /* Someone was pedantic and *NEEDED* three fracking different error messages. */ + switch (CountBits(rb)) { + case 1: + return_cmd_error(STR_ERROR_DRIVE_THROUGH_DIRECTION); + + case 2: + if (rb == ROAD_X || rb == ROAD_Y) return_cmd_error(STR_ERROR_DRIVE_THROUGH_DIRECTION); + return_cmd_error(STR_ERROR_DRIVE_THROUGH_CORNER); + + default: // 3 or 4 + return_cmd_error(STR_ERROR_DRIVE_THROUGH_JUNCTION); + } + } + + RoadTypes cur_rts = IsNormalRoadTile(cur_tile) ? GetRoadTypes(cur_tile) : ROADTYPES_NONE; + uint num_roadbits = 0; + if (build_over_road) { + /* There is a road, check if we can build road+tram stop over it. */ + if (HasBit(cur_rts, ROADTYPE_ROAD)) { + Owner road_owner = GetRoadOwner(cur_tile, ROADTYPE_ROAD); + if (road_owner == OWNER_TOWN) { + if (!_settings_game.construction.road_stop_on_town_road) return_cmd_error(STR_ERROR_DRIVE_THROUGH_ON_TOWN_ROAD); + } else if (!_settings_game.construction.road_stop_on_competitor_road && road_owner != OWNER_NONE) { + CommandCost ret = CheckOwnership(road_owner); + if (ret.Failed()) return ret; + } + num_roadbits += CountBits(GetRoadBits(cur_tile, ROADTYPE_ROAD)); + } + + /* There is a tram, check if we can build road+tram stop over it. */ + if (HasBit(cur_rts, ROADTYPE_TRAM)) { + Owner tram_owner = GetRoadOwner(cur_tile, ROADTYPE_TRAM); + if (!_settings_game.construction.road_stop_on_competitor_road && tram_owner != OWNER_NONE) { + CommandCost ret = CheckOwnership(tram_owner); + if (ret.Failed()) return ret; + } + num_roadbits += CountBits(GetRoadBits(cur_tile, ROADTYPE_TRAM)); + } + + /* Take into account existing roadbits. */ + rts |= cur_rts; + } else { + ret = DoCommand(cur_tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); + if (ret.Failed()) return ret; + cost.AddCost(ret); + } + + uint roadbits_to_build = CountBits(rts) * 2 - num_roadbits; + cost.AddCost(_price[PR_BUILD_ROAD] * roadbits_to_build); + } + } + + return cost; +} + +/** + * Check whether we can expand the rail part of the given station. + * @param st the station to expand + * @param new_ta the current (and if all is fine new) tile area of the rail part of the station + * @param axis the axis of the newly build rail + * @return Succeeded or failed command. + */ +CommandCost CanExpandRailStation(const BaseStation *st, TileArea &new_ta, Axis axis) +{ + TileArea cur_ta = st->train_station; + + /* determine new size of train station region.. */ + int x = min(TileX(cur_ta.tile), TileX(new_ta.tile)); + int y = min(TileY(cur_ta.tile), TileY(new_ta.tile)); + new_ta.w = max(TileX(cur_ta.tile) + cur_ta.w, TileX(new_ta.tile) + new_ta.w) - x; + new_ta.h = max(TileY(cur_ta.tile) + cur_ta.h, TileY(new_ta.tile) + new_ta.h) - y; + new_ta.tile = TileXY(x, y); + + /* make sure the final size is not too big. */ + if (new_ta.w > _settings_game.station.station_spread || new_ta.h > _settings_game.station.station_spread) { + return_cmd_error(STR_ERROR_STATION_TOO_SPREAD_OUT); + } + + return CommandCost(); +} + +static inline byte *CreateSingle(byte *layout, int n) +{ + int i = n; + do *layout++ = 0; while (--i); + layout[((n - 1) >> 1) - n] = 2; + return layout; +} + +static inline byte *CreateMulti(byte *layout, int n, byte b) +{ + int i = n; + do *layout++ = b; while (--i); + if (n > 4) { + layout[0 - n] = 0; + layout[n - 1 - n] = 0; + } + return layout; +} + +/** + * Create the station layout for the given number of tracks and platform length. + * @param layout The layout to write to. + * @param numtracks The number of tracks to write. + * @param plat_len The length of the platforms. + * @param statspec The specification of the station to (possibly) get the layout from. + */ +void GetStationLayout(byte *layout, int numtracks, int plat_len, const StationSpec *statspec) +{ + if (statspec != NULL && statspec->lengths >= plat_len && + statspec->platforms[plat_len - 1] >= numtracks && + statspec->layouts[plat_len - 1][numtracks - 1]) { + /* Custom layout defined, follow it. */ + memcpy(layout, statspec->layouts[plat_len - 1][numtracks - 1], + plat_len * numtracks); + return; + } + + if (plat_len == 1) { + CreateSingle(layout, numtracks); + } else { + if (numtracks & 1) layout = CreateSingle(layout, plat_len); + numtracks >>= 1; + + while (--numtracks >= 0) { + layout = CreateMulti(layout, plat_len, 4); + layout = CreateMulti(layout, plat_len, 6); + } + } +} + +/** + * Find a nearby station that joins this station. + * @tparam T the class to find a station for + * @tparam error_message the error message when building a station on top of others + * @param existing_station an existing station we build over + * @param station_to_join the station to join to + * @param adjacent whether adjacent stations are allowed + * @param ta the area of the newly build station + * @param st 'return' pointer for the found station + * @return command cost with the error or 'okay' + */ +template <class T, StringID error_message> +CommandCost FindJoiningBaseStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, T **st) +{ + assert(*st == NULL); + bool check_surrounding = true; + + if (_settings_game.station.adjacent_stations) { + if (existing_station != INVALID_STATION) { + if (adjacent && existing_station != station_to_join) { + /* You can't build an adjacent station over the top of one that + * already exists. */ + return_cmd_error(error_message); + } else { + /* Extend the current station, and don't check whether it will + * be near any other stations. */ + *st = T::GetIfValid(existing_station); + check_surrounding = (*st == NULL); + } + } else { + /* There's no station here. Don't check the tiles surrounding this + * one if the company wanted to build an adjacent station. */ + if (adjacent) check_surrounding = false; + } + } + + if (check_surrounding) { + /* Make sure there are no similar stations around us. */ + CommandCost ret = GetStationAround(ta, existing_station, st); + if (ret.Failed()) return ret; + } + + /* Distant join */ + if (*st == NULL && station_to_join != INVALID_STATION) *st = T::GetIfValid(station_to_join); + + return CommandCost(); +} + +/** + * Find a nearby station that joins this station. + * @param existing_station an existing station we build over + * @param station_to_join the station to join to + * @param adjacent whether adjacent stations are allowed + * @param ta the area of the newly build station + * @param st 'return' pointer for the found station + * @return command cost with the error or 'okay' + */ +static CommandCost FindJoiningStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Station **st) +{ + return FindJoiningBaseStation<Station, STR_ERROR_MUST_REMOVE_RAILWAY_STATION_FIRST>(existing_station, station_to_join, adjacent, ta, st); +} + +/** + * Find a nearby waypoint that joins this waypoint. + * @param existing_waypoint an existing waypoint we build over + * @param waypoint_to_join the waypoint to join to + * @param adjacent whether adjacent waypoints are allowed + * @param ta the area of the newly build waypoint + * @param wp 'return' pointer for the found waypoint + * @return command cost with the error or 'okay' + */ +CommandCost FindJoiningWaypoint(StationID existing_waypoint, StationID waypoint_to_join, bool adjacent, TileArea ta, Waypoint **wp) +{ + return FindJoiningBaseStation<Waypoint, STR_ERROR_MUST_REMOVE_RAILWAYPOINT_FIRST>(existing_waypoint, waypoint_to_join, adjacent, ta, wp); +} + +/** + * Build rail station + * @param tile_org northern most position of station dragging/placement + * @param flags operation to perform + * @param p1 various bitstuffed elements + * - p1 = (bit 0- 3) - railtype + * - p1 = (bit 4) - orientation (Axis) + * - p1 = (bit 8-15) - number of tracks + * - p1 = (bit 16-23) - platform length + * - p1 = (bit 24) - allow stations directly adjacent to other stations. + * @param p2 various bitstuffed elements + * - p2 = (bit 0- 7) - custom station class + * - p2 = (bit 8-15) - custom station id + * - p2 = (bit 16-31) - station ID to join (NEW_STATION if build new one) + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdBuildRailStation(TileIndex tile_org, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + /* Unpack parameters */ + RailType rt = Extract<RailType, 0, 4>(p1); + Axis axis = Extract<Axis, 4, 1>(p1); + byte numtracks = GB(p1, 8, 8); + byte plat_len = GB(p1, 16, 8); + bool adjacent = HasBit(p1, 24); + + StationClassID spec_class = Extract<StationClassID, 0, 8>(p2); + byte spec_index = GB(p2, 8, 8); + StationID station_to_join = GB(p2, 16, 16); + + /* Does the authority allow this? */ + CommandCost ret = CheckIfAuthorityAllowsNewStation(tile_org, flags); + if (ret.Failed()) return ret; + + if (!ValParamRailtype(rt)) return CMD_ERROR; + + /* Check if the given station class is valid */ + if ((uint)spec_class >= StationClass::GetClassCount() || spec_class == STAT_CLASS_WAYP) return CMD_ERROR; + if (spec_index >= StationClass::Get(spec_class)->GetSpecCount()) return CMD_ERROR; + if (plat_len == 0 || numtracks == 0) return CMD_ERROR; + + int w_org, h_org; + if (axis == AXIS_X) { + w_org = plat_len; + h_org = numtracks; + } else { + h_org = plat_len; + w_org = numtracks; + } + + bool reuse = (station_to_join != NEW_STATION); + if (!reuse) station_to_join = INVALID_STATION; + bool distant_join = (station_to_join != INVALID_STATION); + + if (distant_join && (!_settings_game.station.distant_join_stations || !Station::IsValidID(station_to_join))) return CMD_ERROR; + + if (h_org > _settings_game.station.station_spread || w_org > _settings_game.station.station_spread) return CMD_ERROR; + + /* these values are those that will be stored in train_tile and station_platforms */ + TileArea new_location(tile_org, w_org, h_org); + + /* Make sure the area below consists of clear tiles. (OR tiles belonging to a certain rail station) */ + StationID est = INVALID_STATION; + SmallVector<Train *, 4> affected_vehicles; + /* Clear the land below the station. */ + CommandCost cost = CheckFlatLandRailStation(new_location, flags, axis, &est, rt, affected_vehicles, spec_class, spec_index, plat_len, numtracks); + if (cost.Failed()) return cost; + /* Add construction expenses. */ + cost.AddCost((numtracks * _price[PR_BUILD_STATION_RAIL] + _price[PR_BUILD_STATION_RAIL_LENGTH]) * plat_len); + cost.AddCost(numtracks * plat_len * RailBuildCost(rt)); + + Station *st = NULL; + ret = FindJoiningStation(est, station_to_join, adjacent, new_location, &st); + if (ret.Failed()) return ret; + + ret = BuildStationPart(&st, flags, reuse, new_location, STATIONNAMING_RAIL); + if (ret.Failed()) return ret; + + if (st != NULL && st->train_station.tile != INVALID_TILE) { + CommandCost ret = CanExpandRailStation(st, new_location, axis); + if (ret.Failed()) return ret; + } + + /* Check if we can allocate a custom stationspec to this station */ + const StationSpec *statspec = StationClass::Get(spec_class)->GetSpec(spec_index); + int specindex = AllocateSpecToStation(statspec, st, (flags & DC_EXEC) != 0); + if (specindex == -1) return_cmd_error(STR_ERROR_TOO_MANY_STATION_SPECS); + + if (statspec != NULL) { + /* Perform NewStation checks */ + + /* Check if the station size is permitted */ + if (HasBit(statspec->disallowed_platforms, numtracks - 1) || HasBit(statspec->disallowed_lengths, plat_len - 1)) { + return CMD_ERROR; + } + + /* Check if the station is buildable */ + if (HasBit(statspec->callback_mask, CBM_STATION_AVAIL)) { + uint16 cb_res = GetStationCallback(CBID_STATION_AVAILABILITY, 0, 0, statspec, NULL, INVALID_TILE); + if (cb_res != CALLBACK_FAILED && !Convert8bitBooleanCallback(statspec->grf_prop.grffile, CBID_STATION_AVAILABILITY, cb_res)) return CMD_ERROR; + } + } + + if (flags & DC_EXEC) { + TileIndexDiff tile_delta; + byte *layout_ptr; + byte numtracks_orig; + Track track; + + st->train_station = new_location; + st->AddFacility(FACIL_TRAIN, new_location.tile); + + st->rect.BeforeAddRect(tile_org, w_org, h_org, StationRect::ADD_TRY); + + if (statspec != NULL) { + /* Include this station spec's animation trigger bitmask + * in the station's cached copy. */ + st->cached_anim_triggers |= statspec->animation.triggers; + } + + tile_delta = (axis == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); + track = AxisToTrack(axis); + + layout_ptr = AllocaM(byte, numtracks * plat_len); + GetStationLayout(layout_ptr, numtracks, plat_len, statspec); + + numtracks_orig = numtracks; + + Company *c = Company::Get(st->owner); + TileIndex tile_track = tile_org; + do { + TileIndex tile = tile_track; + int w = plat_len; + do { + byte layout = *layout_ptr++; + if (IsRailStationTile(tile) && HasStationReservation(tile)) { + /* Check for trains having a reservation for this tile. */ + Train *v = GetTrainForReservation(tile, AxisToTrack(GetRailStationAxis(tile))); + if (v != NULL) { + FreeTrainTrackReservation(v); + *affected_vehicles.Append() = v; + if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), false); + for (; v->Next() != NULL; v = v->Next()) { } + if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(ReverseTrackdir(v->GetVehicleTrackdir())), false); + } + } + + /* Railtype can change when overbuilding. */ + if (IsRailStationTile(tile)) { + if (!IsStationTileBlocked(tile)) c->infrastructure.rail[GetRailType(tile)]--; + c->infrastructure.station--; + } + + /* Remove animation if overbuilding */ + DeleteAnimatedTile(tile); + byte old_specindex = HasStationTileRail(tile) ? GetCustomStationSpecIndex(tile) : 0; + MakeRailStation(tile, st->owner, st->index, axis, layout & ~1, rt); + /* Free the spec if we overbuild something */ + DeallocateSpecFromStation(st, old_specindex); + + SetCustomStationSpecIndex(tile, specindex); + SetStationTileRandomBits(tile, GB(Random(), 0, 4)); + SetAnimationFrame(tile, 0); + + if (!IsStationTileBlocked(tile)) c->infrastructure.rail[rt]++; + c->infrastructure.station++; + + if (statspec != NULL) { + /* Use a fixed axis for GetPlatformInfo as our platforms / numtracks are always the right way around */ + uint32 platinfo = GetPlatformInfo(AXIS_X, GetStationGfx(tile), plat_len, numtracks_orig, plat_len - w, numtracks_orig - numtracks, false); + + /* As the station is not yet completely finished, the station does not yet exist. */ + uint16 callback = GetStationCallback(CBID_STATION_TILE_LAYOUT, platinfo, 0, statspec, NULL, tile); + if (callback != CALLBACK_FAILED) { + if (callback < 8) { + SetStationGfx(tile, (callback & ~1) + axis); + } else { + ErrorUnknownCallbackResult(statspec->grf_prop.grffile->grfid, CBID_STATION_TILE_LAYOUT, callback); + } + } + + /* Trigger station animation -- after building? */ + TriggerStationAnimation(st, tile, SAT_BUILT); + } + + tile += tile_delta; + } while (--w); + AddTrackToSignalBuffer(tile_track, track, _current_company); + YapfNotifyTrackLayoutChange(tile_track, track); + tile_track += tile_delta ^ TileDiffXY(1, 1); // perpendicular to tile_delta + } while (--numtracks); + + for (uint i = 0; i < affected_vehicles.Length(); ++i) { + /* Restore reservations of trains. */ + Train *v = affected_vehicles[i]; + if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), true); + TryPathReserve(v, true, true); + for (; v->Next() != NULL; v = v->Next()) { } + if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(ReverseTrackdir(v->GetVehicleTrackdir())), true); + } + + /* Check whether we need to expand the reservation of trains already on the station. */ + TileArea update_reservation_area; + if (axis == AXIS_X) { + update_reservation_area = TileArea(tile_org, 1, numtracks_orig); + } else { + update_reservation_area = TileArea(tile_org, numtracks_orig, 1); + } + + TILE_AREA_LOOP(tile, update_reservation_area) { + DiagDirection dir = AxisToDiagDir(axis); + TileIndexDiff tile_offset = TileOffsByDiagDir(dir); + TileIndex platform_begin = tile - tile_offset * (st->GetPlatformLength(tile, ReverseDiagDir(dir)) - 1); + TileIndex platform_end = tile + tile_offset * (st->GetPlatformLength(tile, dir) - 1); + + /* If there is at least on reservation on the platform, we reserve the whole platform. */ + bool reservation = false; + for (TileIndex t = platform_begin; !reservation && t <= platform_end; t += tile_offset) { + reservation = HasStationReservation(t); + } + + if (reservation) { + SetRailStationPlatformReservation(platform_begin, dir, true); + } + } + + st->MarkTilesDirty(false); + st->UpdateVirtCoord(); + UpdateStationAcceptance(st, false); + st->RecomputeIndustriesNear(); + InvalidateWindowData(WC_SELECT_STATION, 0, 0); + InvalidateWindowData(WC_STATION_LIST, st->owner, 0); + SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_TRAINS); + DirtyCompanyInfrastructureWindows(st->owner); + } + + return cost; +} + +static void MakeRailStationAreaSmaller(BaseStation *st) +{ + TileArea ta = st->train_station; + +restart: + + /* too small? */ + if (ta.w != 0 && ta.h != 0) { + /* check the left side, x = constant, y changes */ + for (uint i = 0; !st->TileBelongsToRailStation(ta.tile + TileDiffXY(0, i));) { + /* the left side is unused? */ + if (++i == ta.h) { + ta.tile += TileDiffXY(1, 0); + ta.w--; + goto restart; + } + } + + /* check the right side, x = constant, y changes */ + for (uint i = 0; !st->TileBelongsToRailStation(ta.tile + TileDiffXY(ta.w - 1, i));) { + /* the right side is unused? */ + if (++i == ta.h) { + ta.w--; + goto restart; + } + } + + /* check the upper side, y = constant, x changes */ + for (uint i = 0; !st->TileBelongsToRailStation(ta.tile + TileDiffXY(i, 0));) { + /* the left side is unused? */ + if (++i == ta.w) { + ta.tile += TileDiffXY(0, 1); + ta.h--; + goto restart; + } + } + + /* check the lower side, y = constant, x changes */ + for (uint i = 0; !st->TileBelongsToRailStation(ta.tile + TileDiffXY(i, ta.h - 1));) { + /* the left side is unused? */ + if (++i == ta.w) { + ta.h--; + goto restart; + } + } + } else { + ta.Clear(); + } + + st->train_station = ta; +} + +/** + * Remove a number of tiles from any rail station within the area. + * @param ta the area to clear station tile from. + * @param affected_stations the stations affected. + * @param flags the command flags. + * @param removal_cost the cost for removing the tile, including the rail. + * @param keep_rail whether to keep the rail of the station. + * @tparam T the type of station to remove. + * @return the number of cleared tiles or an error. + */ +template <class T> +CommandCost RemoveFromRailBaseStation(TileArea ta, SmallVector<T *, 4> &affected_stations, DoCommandFlag flags, Money removal_cost, bool keep_rail) +{ + /* Count of the number of tiles removed */ + int quantity = 0; + CommandCost total_cost(EXPENSES_CONSTRUCTION); + + /* Do the action for every tile into the area */ + TILE_AREA_LOOP(tile, ta) { + /* Make sure the specified tile is a rail station */ + if (!HasStationTileRail(tile)) continue; + + /* If there is a vehicle on ground, do not allow to remove (flood) the tile */ + CommandCost ret = EnsureNoVehicleOnGround(tile); + if (ret.Failed()) continue; + + /* Check ownership of station */ + T *st = T::GetByTile(tile); + if (st == NULL) continue; + + if (_current_company != OWNER_WATER) { + CommandCost ret = CheckOwnership(st->owner); + if (ret.Failed()) continue; + } + + /* If we reached here, the tile is valid so increase the quantity of tiles we will remove */ + quantity++; + + if (keep_rail || IsStationTileBlocked(tile)) { + /* Don't refund the 'steel' of the track when we keep the + * rail, or when the tile didn't have any rail at all. */ + total_cost.AddCost(-_price[PR_CLEAR_RAIL]); + } + + if (flags & DC_EXEC) { + /* read variables before the station tile is removed */ + uint specindex = GetCustomStationSpecIndex(tile); + Track track = GetRailStationTrack(tile); + Owner owner = GetTileOwner(tile); + RailType rt = GetRailType(tile); + Train *v = NULL; + + if (HasStationReservation(tile)) { + v = GetTrainForReservation(tile, track); + if (v != NULL) { + /* Free train reservation. */ + FreeTrainTrackReservation(v); + if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), false); + Vehicle *temp = v; + for (; temp->Next() != NULL; temp = temp->Next()) { } + if (IsRailStationTile(temp->tile)) SetRailStationPlatformReservation(temp->tile, TrackdirToExitdir(ReverseTrackdir(temp->GetVehicleTrackdir())), false); + } + } + + bool build_rail = keep_rail && !IsStationTileBlocked(tile); + if (!build_rail && !IsStationTileBlocked(tile)) Company::Get(owner)->infrastructure.rail[rt]--; + + DoClearSquare(tile); + DeleteNewGRFInspectWindow(GSF_STATIONS, tile); + if (build_rail) MakeRailNormal(tile, owner, TrackToTrackBits(track), rt); + Company::Get(owner)->infrastructure.station--; + DirtyCompanyInfrastructureWindows(owner); + + st->rect.AfterRemoveTile(st, tile); + AddTrackToSignalBuffer(tile, track, owner); + YapfNotifyTrackLayoutChange(tile, track); + + DeallocateSpecFromStation(st, specindex); + + affected_stations.Include(st); + + if (v != NULL) { + /* Restore station reservation. */ + if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), true); + TryPathReserve(v, true, true); + for (; v->Next() != NULL; v = v->Next()) { } + if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(ReverseTrackdir(v->GetVehicleTrackdir())), true); + } + } + } + + if (quantity == 0) return_cmd_error(STR_ERROR_THERE_IS_NO_STATION); + + for (T **stp = affected_stations.Begin(); stp != affected_stations.End(); stp++) { + T *st = *stp; + + /* now we need to make the "spanned" area of the railway station smaller + * if we deleted something at the edges. + * we also need to adjust train_tile. */ + MakeRailStationAreaSmaller(st); + UpdateStationSignCoord(st); + + /* if we deleted the whole station, delete the train facility. */ + if (st->train_station.tile == INVALID_TILE) { + st->facilities &= ~FACIL_TRAIN; + SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_TRAINS); + st->UpdateVirtCoord(); + DeleteStationIfEmpty(st); + } + } + + total_cost.AddCost(quantity * removal_cost); + return total_cost; +} + +/** + * Remove a single tile from a rail station. + * This allows for custom-built station with holes and weird layouts + * @param start tile of station piece to remove + * @param flags operation to perform + * @param p1 start_tile + * @param p2 various bitstuffed elements + * - p2 = bit 0 - if set keep the rail + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdRemoveFromRailStation(TileIndex start, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + TileIndex end = p1 == 0 ? start : p1; + if (start >= MapSize() || end >= MapSize()) return CMD_ERROR; + + TileArea ta(start, end); + SmallVector<Station *, 4> affected_stations; + + CommandCost ret = RemoveFromRailBaseStation(ta, affected_stations, flags, _price[PR_CLEAR_STATION_RAIL], HasBit(p2, 0)); + if (ret.Failed()) return ret; + + /* Do all station specific functions here. */ + for (Station **stp = affected_stations.Begin(); stp != affected_stations.End(); stp++) { + Station *st = *stp; + + if (st->train_station.tile == INVALID_TILE) SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_TRAINS); + st->MarkTilesDirty(false); + st->RecomputeIndustriesNear(); + } + + /* Now apply the rail cost to the number that we deleted */ + return ret; +} + +/** + * Remove a single tile from a waypoint. + * This allows for custom-built waypoint with holes and weird layouts + * @param start tile of waypoint piece to remove + * @param flags operation to perform + * @param p1 start_tile + * @param p2 various bitstuffed elements + * - p2 = bit 0 - if set keep the rail + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdRemoveFromRailWaypoint(TileIndex start, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + TileIndex end = p1 == 0 ? start : p1; + if (start >= MapSize() || end >= MapSize()) return CMD_ERROR; + + TileArea ta(start, end); + SmallVector<Waypoint *, 4> affected_stations; + + return RemoveFromRailBaseStation(ta, affected_stations, flags, _price[PR_CLEAR_WAYPOINT_RAIL], HasBit(p2, 0)); +} + + +/** + * Remove a rail station/waypoint + * @param st The station/waypoint to remove the rail part from + * @param flags operation to perform + * @tparam T the type of station to remove + * @return cost or failure of operation + */ +template <class T> +CommandCost RemoveRailStation(T *st, DoCommandFlag flags) +{ + /* Current company owns the station? */ + if (_current_company != OWNER_WATER) { + CommandCost ret = CheckOwnership(st->owner); + if (ret.Failed()) return ret; + } + + /* determine width and height of platforms */ + TileArea ta = st->train_station; + + assert(ta.w != 0 && ta.h != 0); + + CommandCost cost(EXPENSES_CONSTRUCTION); + /* clear all areas of the station */ + TILE_AREA_LOOP(tile, ta) { + /* only remove tiles that are actually train station tiles */ + if (!st->TileBelongsToRailStation(tile)) continue; + + CommandCost ret = EnsureNoVehicleOnGround(tile); + if (ret.Failed()) return ret; + + cost.AddCost(_price[PR_CLEAR_STATION_RAIL]); + if (flags & DC_EXEC) { + /* read variables before the station tile is removed */ + Track track = GetRailStationTrack(tile); + Owner owner = GetTileOwner(tile); // _current_company can be OWNER_WATER + Train *v = NULL; + if (HasStationReservation(tile)) { + v = GetTrainForReservation(tile, track); + if (v != NULL) FreeTrainTrackReservation(v); + } + if (!IsStationTileBlocked(tile)) Company::Get(owner)->infrastructure.rail[GetRailType(tile)]--; + Company::Get(owner)->infrastructure.station--; + DoClearSquare(tile); + DeleteNewGRFInspectWindow(GSF_STATIONS, tile); + AddTrackToSignalBuffer(tile, track, owner); + YapfNotifyTrackLayoutChange(tile, track); + if (v != NULL) TryPathReserve(v, true); + } + } + + if (flags & DC_EXEC) { + st->rect.AfterRemoveRect(st, st->train_station); + + st->train_station.Clear(); + + st->facilities &= ~FACIL_TRAIN; + + free(st->speclist); + st->num_specs = 0; + st->speclist = NULL; + st->cached_anim_triggers = 0; + + DirtyCompanyInfrastructureWindows(st->owner); + SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_TRAINS); + st->UpdateVirtCoord(); + DeleteStationIfEmpty(st); + } + + return cost; +} + +/** + * Remove a rail station + * @param tile Tile of the station. + * @param flags operation to perform + * @return cost or failure of operation + */ +static CommandCost RemoveRailStation(TileIndex tile, DoCommandFlag flags) +{ + /* if there is flooding, remove platforms tile by tile */ + if (_current_company == OWNER_WATER) { + return DoCommand(tile, 0, 0, DC_EXEC, CMD_REMOVE_FROM_RAIL_STATION); + } + + Station *st = Station::GetByTile(tile); + CommandCost cost = RemoveRailStation(st, flags); + + if (flags & DC_EXEC) st->RecomputeIndustriesNear(); + + return cost; +} + +/** + * Remove a rail waypoint + * @param tile Tile of the waypoint. + * @param flags operation to perform + * @return cost or failure of operation + */ +static CommandCost RemoveRailWaypoint(TileIndex tile, DoCommandFlag flags) +{ + /* if there is flooding, remove waypoints tile by tile */ + if (_current_company == OWNER_WATER) { + return DoCommand(tile, 0, 0, DC_EXEC, CMD_REMOVE_FROM_RAIL_WAYPOINT); + } + + return RemoveRailStation(Waypoint::GetByTile(tile), flags); +} + + +/** + * @param truck_station Determines whether a stop is #ROADSTOP_BUS or #ROADSTOP_TRUCK + * @param st The Station to do the whole procedure for + * @return a pointer to where to link a new RoadStop* + */ +static RoadStop **FindRoadStopSpot(bool truck_station, Station *st) +{ + RoadStop **primary_stop = (truck_station) ? &st->truck_stops : &st->bus_stops; + + if (*primary_stop == NULL) { + /* we have no roadstop of the type yet, so write a "primary stop" */ + return primary_stop; + } else { + /* there are stops already, so append to the end of the list */ + RoadStop *stop = *primary_stop; + while (stop->next != NULL) stop = stop->next; + return &stop->next; + } +} + +static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags); + +/** + * Find a nearby station that joins this road stop. + * @param existing_stop an existing road stop we build over + * @param station_to_join the station to join to + * @param adjacent whether adjacent stations are allowed + * @param ta the area of the newly build station + * @param st 'return' pointer for the found station + * @return command cost with the error or 'okay' + */ +static CommandCost FindJoiningRoadStop(StationID existing_stop, StationID station_to_join, bool adjacent, TileArea ta, Station **st) +{ + return FindJoiningBaseStation<Station, STR_ERROR_MUST_REMOVE_ROAD_STOP_FIRST>(existing_stop, station_to_join, adjacent, ta, st); +} + +/** + * Build a bus or truck stop. + * @param tile Northernmost tile of the stop. + * @param flags Operation to perform. + * @param p1 bit 0..7: Width of the road stop. + * bit 8..15: Length of the road stop. + * @param p2 bit 0: 0 For bus stops, 1 for truck stops. + * bit 1: 0 For normal stops, 1 for drive-through. + * bit 2..3: The roadtypes. + * bit 5: Allow stations directly adjacent to other stations. + * bit 6..7: Entrance direction (#DiagDirection). + * bit 16..31: Station ID to join (NEW_STATION if build new one). + * @param text Unused. + * @return The cost of this operation or an error. + */ +CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + bool type = HasBit(p2, 0); + bool is_drive_through = HasBit(p2, 1); + RoadTypes rts = Extract<RoadTypes, 2, 2>(p2); + StationID station_to_join = GB(p2, 16, 16); + bool reuse = (station_to_join != NEW_STATION); + if (!reuse) station_to_join = INVALID_STATION; + bool distant_join = (station_to_join != INVALID_STATION); + + uint8 width = (uint8)GB(p1, 0, 8); + uint8 lenght = (uint8)GB(p1, 8, 8); + + /* Check if the requested road stop is too big */ + if (width > _settings_game.station.station_spread || lenght > _settings_game.station.station_spread) return_cmd_error(STR_ERROR_STATION_TOO_SPREAD_OUT); + /* Check for incorrect width / length. */ + if (width == 0 || lenght == 0) return CMD_ERROR; + /* Check if the first tile and the last tile are valid */ + if (!IsValidTile(tile) || TileAddWrap(tile, width - 1, lenght - 1) == INVALID_TILE) return CMD_ERROR; + + TileArea roadstop_area(tile, width, lenght); + + if (distant_join && (!_settings_game.station.distant_join_stations || !Station::IsValidID(station_to_join))) return CMD_ERROR; + + if (!HasExactlyOneBit(rts) || !HasRoadTypesAvail(_current_company, rts)) return CMD_ERROR; + + /* Trams only have drive through stops */ + if (!is_drive_through && HasBit(rts, ROADTYPE_TRAM)) return CMD_ERROR; + + DiagDirection ddir = Extract<DiagDirection, 6, 2>(p2); + + /* Safeguard the parameters. */ + if (!IsValidDiagDirection(ddir)) return CMD_ERROR; + /* If it is a drive-through stop, check for valid axis. */ + if (is_drive_through && !IsValidAxis((Axis)ddir)) return CMD_ERROR; + + CommandCost ret = CheckIfAuthorityAllowsNewStation(tile, flags); + if (ret.Failed()) return ret; + + /* Total road stop cost. */ + CommandCost cost(EXPENSES_CONSTRUCTION, roadstop_area.w * roadstop_area.h * _price[type ? PR_BUILD_STATION_TRUCK : PR_BUILD_STATION_BUS]); + StationID est = INVALID_STATION; + ret = CheckFlatLandRoadStop(roadstop_area, flags, is_drive_through ? 5 << ddir : 1 << ddir, is_drive_through, type, DiagDirToAxis(ddir), &est, rts); + if (ret.Failed()) return ret; + cost.AddCost(ret); + + Station *st = NULL; + ret = FindJoiningRoadStop(est, station_to_join, HasBit(p2, 5), roadstop_area, &st); + if (ret.Failed()) return ret; + + /* Check if this number of road stops can be allocated. */ + if (!RoadStop::CanAllocateItem(roadstop_area.w * roadstop_area.h)) return_cmd_error(type ? STR_ERROR_TOO_MANY_TRUCK_STOPS : STR_ERROR_TOO_MANY_BUS_STOPS); + + ret = BuildStationPart(&st, flags, reuse, roadstop_area, STATIONNAMING_ROAD); + if (ret.Failed()) return ret; + + if (flags & DC_EXEC) { + /* Check every tile in the area. */ + TILE_AREA_LOOP(cur_tile, roadstop_area) { + RoadTypes cur_rts = GetRoadTypes(cur_tile); + Owner road_owner = HasBit(cur_rts, ROADTYPE_ROAD) ? GetRoadOwner(cur_tile, ROADTYPE_ROAD) : _current_company; + Owner tram_owner = HasBit(cur_rts, ROADTYPE_TRAM) ? GetRoadOwner(cur_tile, ROADTYPE_TRAM) : _current_company; + + if (IsTileType(cur_tile, MP_STATION) && IsRoadStop(cur_tile)) { + RemoveRoadStop(cur_tile, flags); + } + + RoadStop *road_stop = new RoadStop(cur_tile); + /* Insert into linked list of RoadStops. */ + RoadStop **currstop = FindRoadStopSpot(type, st); + *currstop = road_stop; + + if (type) { + st->truck_station.Add(cur_tile); + } else { + st->bus_station.Add(cur_tile); + } + + /* Initialize an empty station. */ + st->AddFacility((type) ? FACIL_TRUCK_STOP : FACIL_BUS_STOP, cur_tile); + + st->rect.BeforeAddTile(cur_tile, StationRect::ADD_TRY); + + RoadStopType rs_type = type ? ROADSTOP_TRUCK : ROADSTOP_BUS; + if (is_drive_through) { + /* Update company infrastructure counts. If the current tile is a normal + * road tile, count only the new road bits needed to get a full diagonal road. */ + RoadType rt; + FOR_EACH_SET_ROADTYPE(rt, cur_rts | rts) { + Company *c = Company::GetIfValid(rt == ROADTYPE_ROAD ? road_owner : tram_owner); + if (c != NULL) { + c->infrastructure.road[rt] += 2 - (IsNormalRoadTile(cur_tile) && HasBit(cur_rts, rt) ? CountBits(GetRoadBits(cur_tile, rt)) : 0); + DirtyCompanyInfrastructureWindows(c->index); + } + } + + MakeDriveThroughRoadStop(cur_tile, st->owner, road_owner, tram_owner, st->index, rs_type, rts | cur_rts, DiagDirToAxis(ddir)); + road_stop->MakeDriveThrough(); + } else { + /* Non-drive-through stop never overbuild and always count as two road bits. */ + Company::Get(st->owner)->infrastructure.road[FIND_FIRST_BIT(rts)] += 2; + MakeRoadStop(cur_tile, st->owner, st->index, rs_type, rts, ddir); + } + Company::Get(st->owner)->infrastructure.station++; + DirtyCompanyInfrastructureWindows(st->owner); + + MarkTileDirtyByTile(cur_tile); + } + } + + if (st != NULL) { + st->UpdateVirtCoord(); + UpdateStationAcceptance(st, false); + st->RecomputeIndustriesNear(); + InvalidateWindowData(WC_SELECT_STATION, 0, 0); + InvalidateWindowData(WC_STATION_LIST, st->owner, 0); + SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_ROADVEHS); + } + return cost; +} + + +static Vehicle *ClearRoadStopStatusEnum(Vehicle *v, void *) +{ + if (v->type == VEH_ROAD) { + /* Okay... we are a road vehicle on a drive through road stop. + * But that road stop has just been removed, so we need to make + * sure we are in a valid state... however, vehicles can also + * turn on road stop tiles, so only clear the 'road stop' state + * bits and only when the state was 'in road stop', otherwise + * we'll end up clearing the turn around bits. */ + RoadVehicle *rv = RoadVehicle::From(v); + if (HasBit(rv->state, RVS_IN_DT_ROAD_STOP)) rv->state &= RVSB_ROAD_STOP_TRACKDIR_MASK; + } + + return NULL; +} + + +/** + * Remove a bus station/truck stop + * @param tile TileIndex been queried + * @param flags operation to perform + * @return cost or failure of operation + */ +static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags) +{ + Station *st = Station::GetByTile(tile); + + if (_current_company != OWNER_WATER) { + CommandCost ret = CheckOwnership(st->owner); + if (ret.Failed()) return ret; + } + + bool is_truck = IsTruckStop(tile); + + RoadStop **primary_stop; + RoadStop *cur_stop; + if (is_truck) { // truck stop + primary_stop = &st->truck_stops; + cur_stop = RoadStop::GetByTile(tile, ROADSTOP_TRUCK); + } else { + primary_stop = &st->bus_stops; + cur_stop = RoadStop::GetByTile(tile, ROADSTOP_BUS); + } + + assert(cur_stop != NULL); + + /* don't do the check for drive-through road stops when company bankrupts */ + if (IsDriveThroughStopTile(tile) && (flags & DC_BANKRUPT)) { + /* remove the 'going through road stop' status from all vehicles on that tile */ + if (flags & DC_EXEC) FindVehicleOnPos(tile, NULL, &ClearRoadStopStatusEnum); + } else { + CommandCost ret = EnsureNoVehicleOnGround(tile); + if (ret.Failed()) return ret; + } + + if (flags & DC_EXEC) { + if (*primary_stop == cur_stop) { + /* removed the first stop in the list */ + *primary_stop = cur_stop->next; + /* removed the only stop? */ + if (*primary_stop == NULL) { + st->facilities &= (is_truck ? ~FACIL_TRUCK_STOP : ~FACIL_BUS_STOP); + } + } else { + /* tell the predecessor in the list to skip this stop */ + RoadStop *pred = *primary_stop; + while (pred->next != cur_stop) pred = pred->next; + pred->next = cur_stop->next; + } + + /* Update company infrastructure counts. */ + RoadType rt; + FOR_EACH_SET_ROADTYPE(rt, GetRoadTypes(tile)) { + Company *c = Company::GetIfValid(GetRoadOwner(tile, rt)); + if (c != NULL) { + c->infrastructure.road[rt] -= 2; + DirtyCompanyInfrastructureWindows(c->index); + } + } + Company::Get(st->owner)->infrastructure.station--; + + if (IsDriveThroughStopTile(tile)) { + /* Clears the tile for us */ + cur_stop->ClearDriveThrough(); + } else { + DoClearSquare(tile); + } + + SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_ROADVEHS); + delete cur_stop; + + /* Make sure no vehicle is going to the old roadstop */ + RoadVehicle *v; + FOR_ALL_ROADVEHICLES(v) { + if (v->First() == v && v->current_order.IsType(OT_GOTO_STATION) && + v->dest_tile == tile) { + v->dest_tile = v->GetOrderStationLocation(st->index); + } + } + + st->rect.AfterRemoveTile(st, tile); + + st->UpdateVirtCoord(); + st->RecomputeIndustriesNear(); + DeleteStationIfEmpty(st); + + /* Update the tile area of the truck/bus stop */ + if (is_truck) { + st->truck_station.Clear(); + for (const RoadStop *rs = st->truck_stops; rs != NULL; rs = rs->next) st->truck_station.Add(rs->xy); + } else { + st->bus_station.Clear(); + for (const RoadStop *rs = st->bus_stops; rs != NULL; rs = rs->next) st->bus_station.Add(rs->xy); + } + } + + return CommandCost(EXPENSES_CONSTRUCTION, _price[is_truck ? PR_CLEAR_STATION_TRUCK : PR_CLEAR_STATION_BUS]); +} + +/** + * Remove bus or truck stops. + * @param tile Northernmost tile of the removal area. + * @param flags Operation to perform. + * @param p1 bit 0..7: Width of the removal area. + * bit 8..15: Height of the removal area. + * @param p2 bit 0: 0 For bus stops, 1 for truck stops. + * @param text Unused. + * @return The cost of this operation or an error. + */ +CommandCost CmdRemoveRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + uint8 width = (uint8)GB(p1, 0, 8); + uint8 height = (uint8)GB(p1, 8, 8); + + /* Check for incorrect width / height. */ + if (width == 0 || height == 0) return CMD_ERROR; + /* Check if the first tile and the last tile are valid */ + if (!IsValidTile(tile) || TileAddWrap(tile, width - 1, height - 1) == INVALID_TILE) return CMD_ERROR; + + TileArea roadstop_area(tile, width, height); + + int quantity = 0; + CommandCost cost(EXPENSES_CONSTRUCTION); + TILE_AREA_LOOP(cur_tile, roadstop_area) { + /* Make sure the specified tile is a road stop of the correct type */ + if (!IsTileType(cur_tile, MP_STATION) || !IsRoadStop(cur_tile) || (uint32)GetRoadStopType(cur_tile) != GB(p2, 0, 1)) continue; + + /* Save the stop info before it is removed */ + bool is_drive_through = IsDriveThroughStopTile(cur_tile); + RoadTypes rts = GetRoadTypes(cur_tile); + RoadBits road_bits = IsDriveThroughStopTile(cur_tile) ? + ((GetRoadStopDir(cur_tile) == DIAGDIR_NE) ? ROAD_X : ROAD_Y) : + DiagDirToRoadBits(GetRoadStopDir(cur_tile)); + + Owner road_owner = GetRoadOwner(cur_tile, ROADTYPE_ROAD); + Owner tram_owner = GetRoadOwner(cur_tile, ROADTYPE_TRAM); + CommandCost ret = RemoveRoadStop(cur_tile, flags); + if (ret.Failed()) return ret; + cost.AddCost(ret); + + quantity++; + /* If the stop was a drive-through stop replace the road */ + if ((flags & DC_EXEC) && is_drive_through) { + MakeRoadNormal(cur_tile, road_bits, rts, ClosestTownFromTile(cur_tile, UINT_MAX)->index, + road_owner, tram_owner); + + /* Update company infrastructure counts. */ + RoadType rt; + FOR_EACH_SET_ROADTYPE(rt, rts) { + Company *c = Company::GetIfValid(GetRoadOwner(cur_tile, rt)); + if (c != NULL) { + c->infrastructure.road[rt] += CountBits(road_bits); + DirtyCompanyInfrastructureWindows(c->index); + } + } + } + } + + if (quantity == 0) return_cmd_error(STR_ERROR_THERE_IS_NO_STATION); + + return cost; +} + +/** + * Computes the minimal distance from town's xy to any airport's tile. + * @param it An iterator over all airport tiles. + * @param town_tile town's tile (t->xy) + * @return minimal manhattan distance from town_tile to any airport's tile + */ +static uint GetMinimalAirportDistanceToTile(TileIterator &it, TileIndex town_tile) +{ + uint mindist = UINT_MAX; + + for (TileIndex cur_tile = it; cur_tile != INVALID_TILE; cur_tile = ++it) { + mindist = min(mindist, DistanceManhattan(town_tile, cur_tile)); + } + + return mindist; +} + +/** + * Get a possible noise reduction factor based on distance from town center. + * The further you get, the less noise you generate. + * So all those folks at city council can now happily slee... work in their offices + * @param as airport information + * @param it An iterator over all airport tiles. + * @param town_tile TileIndex of town's center, the one who will receive the airport's candidature + * @return the noise that will be generated, according to distance + */ +uint8 GetAirportNoiseLevelForTown(const AirportSpec *as, TileIterator &it, TileIndex town_tile) +{ + /* 0 cannot be accounted, and 1 is the lowest that can be reduced from town. + * So no need to go any further*/ + if (as->noise_level < 2) return as->noise_level; + + uint distance = GetMinimalAirportDistanceToTile(it, town_tile); + + /* The steps for measuring noise reduction are based on the "magical" (and arbitrary) 8 base distance + * adding the town_council_tolerance 4 times, as a way to graduate, depending of the tolerance. + * Basically, it says that the less tolerant a town is, the bigger the distance before + * an actual decrease can be granted */ + uint8 town_tolerance_distance = 8 + (_settings_game.difficulty.town_council_tolerance * 4); + + /* now, we want to have the distance segmented using the distance judged bareable by town + * This will give us the coefficient of reduction the distance provides. */ + uint noise_reduction = distance / town_tolerance_distance; + + /* If the noise reduction equals the airport noise itself, don't give it for free. + * Otherwise, simply reduce the airport's level. */ + return noise_reduction >= as->noise_level ? 1 : as->noise_level - noise_reduction; +} + +/** + * Finds the town nearest to given airport. Based on minimal manhattan distance to any airport's tile. + * If two towns have the same distance, town with lower index is returned. + * @param as airport's description + * @param it An iterator over all airport tiles + * @return nearest town to airport + */ +Town *AirportGetNearestTown(const AirportSpec *as, const TileIterator &it) +{ + Town *t, *nearest = NULL; + uint add = as->size_x + as->size_y - 2; // GetMinimalAirportDistanceToTile can differ from DistanceManhattan by this much + uint mindist = UINT_MAX - add; // prevent overflow + FOR_ALL_TOWNS(t) { + if (DistanceManhattan(t->xy, it) < mindist + add) { // avoid calling GetMinimalAirportDistanceToTile too often + TileIterator *copy = it.Clone(); + uint dist = GetMinimalAirportDistanceToTile(*copy, t->xy); + delete copy; + if (dist < mindist) { + nearest = t; + mindist = dist; + } + } + } + + return nearest; +} + + +/** Recalculate the noise generated by the airports of each town */ +void UpdateAirportsNoise() +{ + Town *t; + const Station *st; + + FOR_ALL_TOWNS(t) t->noise_reached = 0; + + FOR_ALL_STATIONS(st) { + if (st->airport.tile != INVALID_TILE && st->airport.type != AT_OILRIG) { + const AirportSpec *as = st->airport.GetSpec(); + AirportTileIterator it(st); + Town *nearest = AirportGetNearestTown(as, it); + nearest->noise_reached += GetAirportNoiseLevelForTown(as, it, nearest->xy); + } + } +} + +/** + * Place an Airport. + * @param tile tile where airport will be built + * @param flags operation to perform + * @param p1 + * - p1 = (bit 0- 7) - airport type, @see airport.h + * - p1 = (bit 8-15) - airport layout + * @param p2 various bitstuffed elements + * - p2 = (bit 0) - allow airports directly adjacent to other airports. + * - p2 = (bit 16-31) - station ID to join (NEW_STATION if build new one) + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdBuildAirport(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + StationID station_to_join = GB(p2, 16, 16); + bool reuse = (station_to_join != NEW_STATION); + if (!reuse) station_to_join = INVALID_STATION; + bool distant_join = (station_to_join != INVALID_STATION); + byte airport_type = GB(p1, 0, 8); + byte layout = GB(p1, 8, 8); + + if (distant_join && (!_settings_game.station.distant_join_stations || !Station::IsValidID(station_to_join))) return CMD_ERROR; + + if (airport_type >= NUM_AIRPORTS) return CMD_ERROR; + + CommandCost ret = CheckIfAuthorityAllowsNewStation(tile, flags); + if (ret.Failed()) return ret; + + /* Check if a valid, buildable airport was chosen for construction */ + const AirportSpec *as = AirportSpec::Get(airport_type); + if (!as->IsAvailable() || layout >= as->num_table) return CMD_ERROR; + + Direction rotation = as->rotation[layout]; + int w = as->size_x; + int h = as->size_y; + if (rotation == DIR_E || rotation == DIR_W) Swap(w, h); + TileArea airport_area = TileArea(tile, w, h); + + if (w > _settings_game.station.station_spread || h > _settings_game.station.station_spread) { + return_cmd_error(STR_ERROR_STATION_TOO_SPREAD_OUT); + } + + CommandCost cost = CheckFlatLand(airport_area, flags); + if (cost.Failed()) return cost; + + /* The noise level is the noise from the airport and reduce it to account for the distance to the town center. */ + AirportTileTableIterator iter(as->table[layout], tile); + Town *nearest = AirportGetNearestTown(as, iter); + uint newnoise_level = GetAirportNoiseLevelForTown(as, iter, nearest->xy); + + /* Check if local auth would allow a new airport */ + StringID authority_refuse_message = STR_NULL; + Town *authority_refuse_town = NULL; + + if (_settings_game.economy.station_noise_level) { + /* do not allow to build a new airport if this raise the town noise over the maximum allowed by town */ + if ((nearest->noise_reached + newnoise_level) > nearest->MaxTownNoise()) { + authority_refuse_message = STR_ERROR_LOCAL_AUTHORITY_REFUSES_NOISE; + authority_refuse_town = nearest; + } + } else { + Town *t = ClosestTownFromTile(tile, UINT_MAX); + uint num = 0; + const Station *st; + FOR_ALL_STATIONS(st) { + if (st->town == t && (st->facilities & FACIL_AIRPORT) && st->airport.type != AT_OILRIG) num++; + } + if (num >= 2) { + authority_refuse_message = STR_ERROR_LOCAL_AUTHORITY_REFUSES_AIRPORT; + authority_refuse_town = t; + } + } + + if (authority_refuse_message != STR_NULL) { + SetDParam(0, authority_refuse_town->index); + return_cmd_error(authority_refuse_message); + } + + Station *st = NULL; + ret = FindJoiningStation(INVALID_STATION, station_to_join, HasBit(p2, 0), airport_area, &st); + if (ret.Failed()) return ret; + + /* Distant join */ + if (st == NULL && distant_join) st = Station::GetIfValid(station_to_join); + + ret = BuildStationPart(&st, flags, reuse, airport_area, (GetAirport(airport_type)->flags & AirportFTAClass::AIRPLANES) ? STATIONNAMING_AIRPORT : STATIONNAMING_HELIPORT); + if (ret.Failed()) return ret; + + if (st != NULL && st->airport.tile != INVALID_TILE) { + return_cmd_error(STR_ERROR_TOO_CLOSE_TO_ANOTHER_AIRPORT); + } + + for (AirportTileTableIterator iter(as->table[layout], tile); iter != INVALID_TILE; ++iter) { + cost.AddCost(_price[PR_BUILD_STATION_AIRPORT]); + } + + if (flags & DC_EXEC) { + /* Always add the noise, so there will be no need to recalculate when option toggles */ + nearest->noise_reached += newnoise_level; + + st->AddFacility(FACIL_AIRPORT, tile); + st->airport.type = airport_type; + st->airport.layout = layout; + st->airport.flags = 0; + st->airport.rotation = rotation; + + st->rect.BeforeAddRect(tile, w, h, StationRect::ADD_TRY); + + for (AirportTileTableIterator iter(as->table[layout], tile); iter != INVALID_TILE; ++iter) { + MakeAirport(iter, st->owner, st->index, iter.GetStationGfx(), WATER_CLASS_INVALID); + SetStationTileRandomBits(iter, GB(Random(), 0, 4)); + st->airport.Add(iter); + + if (AirportTileSpec::Get(GetTranslatedAirportTileID(iter.GetStationGfx()))->animation.status != ANIM_STATUS_NO_ANIMATION) AddAnimatedTile(iter); + } + + /* Only call the animation trigger after all tiles have been built */ + for (AirportTileTableIterator iter(as->table[layout], tile); iter != INVALID_TILE; ++iter) { + AirportTileAnimationTrigger(st, iter, AAT_BUILT); + } + + UpdateAirplanesOnNewStation(st); + + Company::Get(st->owner)->infrastructure.airport++; + DirtyCompanyInfrastructureWindows(st->owner); + + st->UpdateVirtCoord(); + UpdateStationAcceptance(st, false); + st->RecomputeIndustriesNear(); + InvalidateWindowData(WC_SELECT_STATION, 0, 0); + InvalidateWindowData(WC_STATION_LIST, st->owner, 0); + InvalidateWindowData(WC_STATION_VIEW, st->index); + + if (_settings_game.economy.station_noise_level) { + SetWindowDirty(WC_TOWN_VIEW, st->town->index); + } + } + + return cost; +} + +/** + * Remove an airport + * @param tile TileIndex been queried + * @param flags operation to perform + * @return cost or failure of operation + */ +static CommandCost RemoveAirport(TileIndex tile, DoCommandFlag flags) +{ + Station *st = Station::GetByTile(tile); + + if (_current_company != OWNER_WATER) { + CommandCost ret = CheckOwnership(st->owner); + if (ret.Failed()) return ret; + } + + tile = st->airport.tile; + + CommandCost cost(EXPENSES_CONSTRUCTION); + + const Aircraft *a; + FOR_ALL_AIRCRAFT(a) { + if (!a->IsNormalAircraft()) continue; + if (a->targetairport == st->index && a->state != FLYING) return CMD_ERROR; + } + + if (flags & DC_EXEC) { + const AirportSpec *as = st->airport.GetSpec(); + /* The noise level is the noise from the airport and reduce it to account for the distance to the town center. + * And as for construction, always remove it, even if the setting is not set, in order to avoid the + * need of recalculation */ + AirportTileIterator it(st); + Town *nearest = AirportGetNearestTown(as, it); + nearest->noise_reached -= GetAirportNoiseLevelForTown(as, it, nearest->xy); + } + + TILE_AREA_LOOP(tile_cur, st->airport) { + if (!st->TileBelongsToAirport(tile_cur)) continue; + + CommandCost ret = EnsureNoVehicleOnGround(tile_cur); + if (ret.Failed()) return ret; + + cost.AddCost(_price[PR_CLEAR_STATION_AIRPORT]); + + if (flags & DC_EXEC) { + if (IsHangarTile(tile_cur)) OrderBackup::Reset(tile_cur, false); + DeleteAnimatedTile(tile_cur); + DoClearSquare(tile_cur); + DeleteNewGRFInspectWindow(GSF_AIRPORTTILES, tile_cur); + } + } + + if (flags & DC_EXEC) { + /* Clear the persistent storage. */ + delete st->airport.psa; + + for (uint i = 0; i < st->airport.GetNumHangars(); ++i) { + DeleteWindowById( + WC_VEHICLE_DEPOT, st->airport.GetHangarTile(i) + ); + } + + st->rect.AfterRemoveRect(st, st->airport); + + st->airport.Clear(); + st->facilities &= ~FACIL_AIRPORT; + + InvalidateWindowData(WC_STATION_VIEW, st->index); + + if (_settings_game.economy.station_noise_level) { + SetWindowDirty(WC_TOWN_VIEW, st->town->index); + } + + Company::Get(st->owner)->infrastructure.airport--; + DirtyCompanyInfrastructureWindows(st->owner); + + st->UpdateVirtCoord(); + st->RecomputeIndustriesNear(); + DeleteStationIfEmpty(st); + DeleteNewGRFInspectWindow(GSF_AIRPORTS, st->index); + } + + return cost; +} + +/** + * Open/close an airport to incoming aircraft. + * @param tile Unused. + * @param flags Operation to perform. + * @param p1 Station ID of the airport. + * @param p2 Unused. + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdOpenCloseAirport(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + if (!Station::IsValidID(p1)) return CMD_ERROR; + Station *st = Station::Get(p1); + + if (!(st->facilities & FACIL_AIRPORT) || st->owner == OWNER_NONE) return CMD_ERROR; + + CommandCost ret = CheckOwnership(st->owner); + if (ret.Failed()) return ret; + + if (flags & DC_EXEC) { + st->airport.flags ^= AIRPORT_CLOSED_block; + SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_CLOSE_AIRPORT); + } + return CommandCost(); +} + +/** + * Tests whether the company's vehicles have this station in orders + * @param station station ID + * @param include_company If true only check vehicles of \a company, if false only check vehicles of other companies + * @param company company ID + */ +bool HasStationInUse(StationID station, bool include_company, CompanyID company) +{ + const Vehicle *v; + FOR_ALL_VEHICLES(v) { + if ((v->owner == company) == include_company) { + const Order *order; + FOR_VEHICLE_ORDERS(v, order) { + if ((order->IsType(OT_GOTO_STATION) || order->IsType(OT_GOTO_WAYPOINT)) && order->GetDestination() == station) { + return true; + } + } + } + } + return false; +} + +static const TileIndexDiffC _dock_tileoffs_chkaround[] = { + {-1, 0}, + { 0, 0}, + { 0, 0}, + { 0, -1} +}; +static const byte _dock_w_chk[4] = { 2, 1, 2, 1 }; +static const byte _dock_h_chk[4] = { 1, 2, 1, 2 }; + +/** + * Build a dock/haven. + * @param tile tile where dock will be built + * @param flags operation to perform + * @param p1 (bit 0) - allow docks directly adjacent to other docks. + * @param p2 bit 16-31: station ID to join (NEW_STATION if build new one) + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdBuildDock(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + StationID station_to_join = GB(p2, 16, 16); + bool reuse = (station_to_join != NEW_STATION); + if (!reuse) station_to_join = INVALID_STATION; + bool distant_join = (station_to_join != INVALID_STATION); + + if (distant_join && (!_settings_game.station.distant_join_stations || !Station::IsValidID(station_to_join))) return CMD_ERROR; + + DiagDirection direction = GetInclinedSlopeDirection(GetTileSlope(tile)); + if (direction == INVALID_DIAGDIR) return_cmd_error(STR_ERROR_SITE_UNSUITABLE); + direction = ReverseDiagDir(direction); + + /* Docks cannot be placed on rapids */ + if (HasTileWaterGround(tile)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE); + + CommandCost ret = CheckIfAuthorityAllowsNewStation(tile, flags); + if (ret.Failed()) return ret; + + if (MayHaveBridgeAbove(tile) && IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); + + ret = DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); + if (ret.Failed()) return ret; + + TileIndex tile_cur = tile + TileOffsByDiagDir(direction); + + if (!IsTileType(tile_cur, MP_WATER) || GetTileSlope(tile_cur) != SLOPE_FLAT) { + return_cmd_error(STR_ERROR_SITE_UNSUITABLE); + } + + if (MayHaveBridgeAbove(tile_cur) && IsBridgeAbove(tile_cur)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); + + /* Get the water class of the water tile before it is cleared.*/ + WaterClass wc = GetWaterClass(tile_cur); + + ret = DoCommand(tile_cur, 0, 0, flags, CMD_LANDSCAPE_CLEAR); + if (ret.Failed()) return ret; + + tile_cur += TileOffsByDiagDir(direction); + if (!IsTileType(tile_cur, MP_WATER) || GetTileSlope(tile_cur) != SLOPE_FLAT) { + return_cmd_error(STR_ERROR_SITE_UNSUITABLE); + } + + TileArea dock_area = TileArea(tile + ToTileIndexDiff(_dock_tileoffs_chkaround[direction]), + _dock_w_chk[direction], _dock_h_chk[direction]); + + /* middle */ + Station *st = NULL; + ret = FindJoiningStation(INVALID_STATION, station_to_join, HasBit(p1, 0), dock_area, &st); + if (ret.Failed()) return ret; + + /* Distant join */ + if (st == NULL && distant_join) st = Station::GetIfValid(station_to_join); + + ret = BuildStationPart(&st, flags, reuse, dock_area, STATIONNAMING_DOCK); + if (ret.Failed()) return ret; + + if (st != NULL && st->dock_tile != INVALID_TILE) return_cmd_error(STR_ERROR_TOO_CLOSE_TO_ANOTHER_DOCK); + + if (flags & DC_EXEC) { + st->dock_tile = tile; + st->AddFacility(FACIL_DOCK, tile); + + st->rect.BeforeAddRect(dock_area.tile, dock_area.w, dock_area.h, StationRect::ADD_TRY); + + /* If the water part of the dock is on a canal, update infrastructure counts. + * This is needed as we've unconditionally cleared that tile before. */ + if (wc == WATER_CLASS_CANAL) { + Company::Get(st->owner)->infrastructure.water++; + } + Company::Get(st->owner)->infrastructure.station += 2; + DirtyCompanyInfrastructureWindows(st->owner); + + MakeDock(tile, st->owner, st->index, direction, wc); + + st->UpdateVirtCoord(); + UpdateStationAcceptance(st, false); + st->RecomputeIndustriesNear(); + InvalidateWindowData(WC_SELECT_STATION, 0, 0); + InvalidateWindowData(WC_STATION_LIST, st->owner, 0); + SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_SHIPS); + } + + return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_STATION_DOCK]); +} + +/** + * Remove a dock + * @param tile TileIndex been queried + * @param flags operation to perform + * @return cost or failure of operation + */ +static CommandCost RemoveDock(TileIndex tile, DoCommandFlag flags) +{ + Station *st = Station::GetByTile(tile); + CommandCost ret = CheckOwnership(st->owner); + if (ret.Failed()) return ret; + + TileIndex docking_location = TILE_ADD(st->dock_tile, ToTileIndexDiff(GetDockOffset(st->dock_tile))); + + TileIndex tile1 = st->dock_tile; + TileIndex tile2 = tile1 + TileOffsByDiagDir(GetDockDirection(tile1)); + + ret = EnsureNoVehicleOnGround(tile1); + if (ret.Succeeded()) ret = EnsureNoVehicleOnGround(tile2); + if (ret.Failed()) return ret; + + if (flags & DC_EXEC) { + DoClearSquare(tile1); + MarkTileDirtyByTile(tile1); + MakeWaterKeepingClass(tile2, st->owner); + + st->rect.AfterRemoveTile(st, tile1); + st->rect.AfterRemoveTile(st, tile2); + + st->dock_tile = INVALID_TILE; + st->facilities &= ~FACIL_DOCK; + + Company::Get(st->owner)->infrastructure.station -= 2; + DirtyCompanyInfrastructureWindows(st->owner); + + SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_SHIPS); + st->UpdateVirtCoord(); + st->RecomputeIndustriesNear(); + DeleteStationIfEmpty(st); + + /* All ships that were going to our station, can't go to it anymore. + * Just clear the order, then automatically the next appropriate order + * will be selected and in case of no appropriate order it will just + * wander around the world. */ + Ship *s; + FOR_ALL_SHIPS(s) { + if (s->current_order.IsType(OT_LOADING) && s->tile == docking_location) { + s->LeaveStation(); + } + + if (s->dest_tile == docking_location) { + s->dest_tile = 0; + s->current_order.Free(); + } + } + } + + return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_STATION_DOCK]); +} + +#include "table/station_land.h" + +const DrawTileSprites *GetStationTileLayout(StationType st, byte gfx) +{ + return &_station_display_datas[st][gfx]; +} + +/** + * Check whether a sprite is a track sprite, which can be replaced by a non-track ground sprite and a rail overlay. + * If the ground sprite is suitable, \a ground is replaced with the new non-track ground sprite, and \a overlay_offset + * is set to the overlay to draw. + * @param ti Positional info for the tile to decide snowyness etc. May be NULL. + * @param [in,out] ground Groundsprite to draw. + * @param [out] overlay_offset Overlay to draw. + * @return true if overlay can be drawn. + */ +bool SplitGroundSpriteForOverlay(const TileInfo *ti, SpriteID *ground, RailTrackOffset *overlay_offset) +{ + bool snow_desert; + switch (*ground) { + case SPR_RAIL_TRACK_X: + snow_desert = false; + *overlay_offset = RTO_X; + break; + + case SPR_RAIL_TRACK_Y: + snow_desert = false; + *overlay_offset = RTO_Y; + break; + + case SPR_RAIL_TRACK_X_SNOW: + snow_desert = true; + *overlay_offset = RTO_X; + break; + + case SPR_RAIL_TRACK_Y_SNOW: + snow_desert = true; + *overlay_offset = RTO_Y; + break; + + default: + return false; + } + + if (ti != NULL) { + /* Decide snow/desert from tile */ + switch (_settings_game.game_creation.landscape) { + case LT_ARCTIC: + snow_desert = (uint)ti->z > GetSnowLine() * TILE_HEIGHT; + break; + + case LT_TROPIC: + snow_desert = GetTropicZone(ti->tile) == TROPICZONE_DESERT; + break; + + default: + break; + } + } + + *ground = snow_desert ? SPR_FLAT_SNOW_DESERT_TILE : SPR_FLAT_GRASS_TILE; + return true; +} + +static void DrawTile_Station(TileInfo *ti) +{ + const NewGRFSpriteLayout *layout = NULL; + DrawTileSprites tmp_rail_layout; + const DrawTileSprites *t = NULL; + RoadTypes roadtypes; + int32 total_offset; + const RailtypeInfo *rti = NULL; + uint32 relocation = 0; + uint32 ground_relocation = 0; + BaseStation *st = NULL; + const StationSpec *statspec = NULL; + uint tile_layout = 0; + + if (HasStationRail(ti->tile)) { + rti = GetRailTypeInfo(GetRailType(ti->tile)); + roadtypes = ROADTYPES_NONE; + total_offset = rti->GetRailtypeSpriteOffset(); + + if (IsCustomStationSpecIndex(ti->tile)) { + /* look for customization */ + st = BaseStation::GetByTile(ti->tile); + statspec = st->speclist[GetCustomStationSpecIndex(ti->tile)].spec; + + if (statspec != NULL) { + tile_layout = GetStationGfx(ti->tile); + + if (HasBit(statspec->callback_mask, CBM_STATION_SPRITE_LAYOUT)) { + uint16 callback = GetStationCallback(CBID_STATION_SPRITE_LAYOUT, 0, 0, statspec, st, ti->tile); + if (callback != CALLBACK_FAILED) tile_layout = (callback & ~1) + GetRailStationAxis(ti->tile); + } + + /* Ensure the chosen tile layout is valid for this custom station */ + if (statspec->renderdata != NULL) { + layout = &statspec->renderdata[tile_layout < statspec->tiles ? tile_layout : (uint)GetRailStationAxis(ti->tile)]; + if (!layout->NeedsPreprocessing()) { + t = layout; + layout = NULL; + } + } + } + } + } else { + roadtypes = IsRoadStop(ti->tile) ? GetRoadTypes(ti->tile) : ROADTYPES_NONE; + total_offset = 0; + } + + StationGfx gfx = GetStationGfx(ti->tile); + if (IsAirport(ti->tile)) { + gfx = GetAirportGfx(ti->tile); + if (gfx >= NEW_AIRPORTTILE_OFFSET) { + const AirportTileSpec *ats = AirportTileSpec::Get(gfx); + if (ats->grf_prop.spritegroup[0] != NULL && DrawNewAirportTile(ti, Station::GetByTile(ti->tile), gfx, ats)) { + return; + } + /* No sprite group (or no valid one) found, meaning no graphics associated. + * Use the substitute one instead */ + assert(ats->grf_prop.subst_id != INVALID_AIRPORTTILE); + gfx = ats->grf_prop.subst_id; + } + switch (gfx) { + case APT_RADAR_GRASS_FENCE_SW: + t = &_station_display_datas_airport_radar_grass_fence_sw[GetAnimationFrame(ti->tile)]; + break; + case APT_GRASS_FENCE_NE_FLAG: + t = &_station_display_datas_airport_flag_grass_fence_ne[GetAnimationFrame(ti->tile)]; + break; + case APT_RADAR_FENCE_SW: + t = &_station_display_datas_airport_radar_fence_sw[GetAnimationFrame(ti->tile)]; + break; + case APT_RADAR_FENCE_NE: + t = &_station_display_datas_airport_radar_fence_ne[GetAnimationFrame(ti->tile)]; + break; + case APT_GRASS_FENCE_NE_FLAG_2: + t = &_station_display_datas_airport_flag_grass_fence_ne_2[GetAnimationFrame(ti->tile)]; + break; + } + } + + Owner owner = GetTileOwner(ti->tile); + + PaletteID palette; + if (Company::IsValidID(owner)) { + palette = COMPANY_SPRITE_COLOUR(owner); + } else { + /* Some stations are not owner by a company, namely oil rigs */ + palette = PALETTE_TO_GREY; + } + + if (layout == NULL && (t == NULL || t->seq == NULL)) t = GetStationTileLayout(GetStationType(ti->tile), gfx); + + /* don't show foundation for docks */ + if (ti->tileh != SLOPE_FLAT && !IsDock(ti->tile)) { + if (statspec != NULL && HasBit(statspec->flags, SSF_CUSTOM_FOUNDATIONS)) { + /* Station has custom foundations. + * Check whether the foundation continues beyond the tile's upper sides. */ + uint edge_info = 0; + int z; + Slope slope = GetFoundationPixelSlope(ti->tile, &z); + if (!HasFoundationNW(ti->tile, slope, z)) SetBit(edge_info, 0); + if (!HasFoundationNE(ti->tile, slope, z)) SetBit(edge_info, 1); + SpriteID image = GetCustomStationFoundationRelocation(statspec, st, ti->tile, tile_layout, edge_info); + if (image == 0) goto draw_default_foundation; + + if (HasBit(statspec->flags, SSF_EXTENDED_FOUNDATIONS)) { + /* Station provides extended foundations. */ + + static const uint8 foundation_parts[] = { + 0, 0, 0, 0, // Invalid, Invalid, Invalid, SLOPE_SW + 0, 1, 2, 3, // Invalid, SLOPE_EW, SLOPE_SE, SLOPE_WSE + 0, 4, 5, 6, // Invalid, SLOPE_NW, SLOPE_NS, SLOPE_NWS + 7, 8, 9 // SLOPE_NE, SLOPE_ENW, SLOPE_SEN + }; + + AddSortableSpriteToDraw(image + foundation_parts[ti->tileh], PAL_NONE, ti->x, ti->y, 16, 16, 7, ti->z); + } else { + /* Draw simple foundations, built up from 8 possible foundation sprites. */ + + /* Each set bit represents one of the eight composite sprites to be drawn. + * 'Invalid' entries will not drawn but are included for completeness. */ + static const uint8 composite_foundation_parts[] = { + /* Invalid (00000000), Invalid (11010001), Invalid (11100100), SLOPE_SW (11100000) */ + 0x00, 0xD1, 0xE4, 0xE0, + /* Invalid (11001010), SLOPE_EW (11001001), SLOPE_SE (11000100), SLOPE_WSE (11000000) */ + 0xCA, 0xC9, 0xC4, 0xC0, + /* Invalid (11010010), SLOPE_NW (10010001), SLOPE_NS (11100100), SLOPE_NWS (10100000) */ + 0xD2, 0x91, 0xE4, 0xA0, + /* SLOPE_NE (01001010), SLOPE_ENW (00001001), SLOPE_SEN (01000100) */ + 0x4A, 0x09, 0x44 + }; + + uint8 parts = composite_foundation_parts[ti->tileh]; + + /* If foundations continue beyond the tile's upper sides then + * mask out the last two pieces. */ + if (HasBit(edge_info, 0)) ClrBit(parts, 6); + if (HasBit(edge_info, 1)) ClrBit(parts, 7); + + if (parts == 0) { + /* We always have to draw at least one sprite to make sure there is a boundingbox and a sprite with the + * correct offset for the childsprites. + * So, draw the (completely empty) sprite of the default foundations. */ + goto draw_default_foundation; + } + + StartSpriteCombine(); + for (int i = 0; i < 8; i++) { + if (HasBit(parts, i)) { + AddSortableSpriteToDraw(image + i, PAL_NONE, ti->x, ti->y, 16, 16, 7, ti->z); + } + } + EndSpriteCombine(); + } + + OffsetGroundSprite(31, 1); + ti->z += ApplyPixelFoundationToSlope(FOUNDATION_LEVELED, &ti->tileh); + } else { +draw_default_foundation: + DrawFoundation(ti, FOUNDATION_LEVELED); + } + } + + if (IsBuoy(ti->tile)) { + DrawWaterClassGround(ti); + SpriteID sprite = GetCanalSprite(CF_BUOY, ti->tile); + if (sprite != 0) total_offset = sprite - SPR_IMG_BUOY; + } else if (IsDock(ti->tile) || (IsOilRig(ti->tile) && IsTileOnWater(ti->tile))) { + if (ti->tileh == SLOPE_FLAT) { + DrawWaterClassGround(ti); + } else { + assert(IsDock(ti->tile)); + TileIndex water_tile = ti->tile + TileOffsByDiagDir(GetDockDirection(ti->tile)); + WaterClass wc = GetWaterClass(water_tile); + if (wc == WATER_CLASS_SEA) { + DrawShoreTile(ti->tileh); + } else { + DrawClearLandTile(ti, 3); + } + } + } else { + if (layout != NULL) { + /* Sprite layout which needs preprocessing */ + bool separate_ground = HasBit(statspec->flags, SSF_SEPARATE_GROUND); + uint32 var10_values = layout->PrepareLayout(total_offset, rti->fallback_railtype, 0, 0, separate_ground); + uint8 var10; + FOR_EACH_SET_BIT(var10, var10_values) { + uint32 var10_relocation = GetCustomStationRelocation(statspec, st, ti->tile, var10); + layout->ProcessRegisters(var10, var10_relocation, separate_ground); + } + tmp_rail_layout.seq = layout->GetLayout(&tmp_rail_layout.ground); + t = &tmp_rail_layout; + total_offset = 0; + } else if (statspec != NULL) { + /* Simple sprite layout */ + ground_relocation = relocation = GetCustomStationRelocation(statspec, st, ti->tile, 0); + if (HasBit(statspec->flags, SSF_SEPARATE_GROUND)) { + ground_relocation = GetCustomStationRelocation(statspec, st, ti->tile, 1); + } + ground_relocation += rti->fallback_railtype; + } + + SpriteID image = t->ground.sprite; + PaletteID pal = t->ground.pal; + RailTrackOffset overlay_offset; + if (rti != NULL && rti->UsesOverlay() && SplitGroundSpriteForOverlay(ti, &image, &overlay_offset)) { + SpriteID ground = GetCustomRailSprite(rti, ti->tile, RTSG_GROUND); + DrawGroundSprite(image, PAL_NONE); + DrawGroundSprite(ground + overlay_offset, PAL_NONE); + + if (_game_mode != GM_MENU && _settings_client.gui.show_track_reservation && HasStationReservation(ti->tile)) { + SpriteID overlay = GetCustomRailSprite(rti, ti->tile, RTSG_OVERLAY); + DrawGroundSprite(overlay + overlay_offset, PALETTE_CRASH); + } + } else { + image += HasBit(image, SPRITE_MODIFIER_CUSTOM_SPRITE) ? ground_relocation : total_offset; + if (HasBit(pal, SPRITE_MODIFIER_CUSTOM_SPRITE)) pal += ground_relocation; + DrawGroundSprite(image, GroundSpritePaletteTransform(image, pal, palette)); + + /* PBS debugging, draw reserved tracks darker */ + if (_game_mode != GM_MENU && _settings_client.gui.show_track_reservation && HasStationRail(ti->tile) && HasStationReservation(ti->tile)) { + const RailtypeInfo *rti = GetRailTypeInfo(GetRailType(ti->tile)); + DrawGroundSprite(GetRailStationAxis(ti->tile) == AXIS_X ? rti->base_sprites.single_x : rti->base_sprites.single_y, PALETTE_CRASH); + } + } + } + + if (HasStationRail(ti->tile) && HasCatenaryDrawn(GetRailType(ti->tile))) DrawCatenary(ti); + + if (HasBit(roadtypes, ROADTYPE_TRAM)) { + Axis axis = GetRoadStopDir(ti->tile) == DIAGDIR_NE ? AXIS_X : AXIS_Y; + DrawGroundSprite((HasBit(roadtypes, ROADTYPE_ROAD) ? SPR_TRAMWAY_OVERLAY : SPR_TRAMWAY_TRAM) + (axis ^ 1), PAL_NONE); + DrawTramCatenary(ti, axis == AXIS_X ? ROAD_X : ROAD_Y); + } + + if (IsRailWaypoint(ti->tile)) { + /* Don't offset the waypoint graphics; they're always the same. */ + total_offset = 0; + } + + DrawRailTileSeq(ti, t, TO_BUILDINGS, total_offset, relocation, palette); +} + +void StationPickerDrawSprite(int x, int y, StationType st, RailType railtype, RoadType roadtype, int image) +{ + int32 total_offset = 0; + PaletteID pal = COMPANY_SPRITE_COLOUR(_local_company); + const DrawTileSprites *t = GetStationTileLayout(st, image); + const RailtypeInfo *rti = NULL; + + if (railtype != INVALID_RAILTYPE) { + rti = GetRailTypeInfo(railtype); + total_offset = rti->GetRailtypeSpriteOffset(); + } + + SpriteID img = t->ground.sprite; + RailTrackOffset overlay_offset; + if (rti != NULL && rti->UsesOverlay() && SplitGroundSpriteForOverlay(NULL, &img, &overlay_offset)) { + SpriteID ground = GetCustomRailSprite(rti, INVALID_TILE, RTSG_GROUND); + DrawSprite(img, PAL_NONE, x, y); + DrawSprite(ground + overlay_offset, PAL_NONE, x, y); + } else { + DrawSprite(img + total_offset, HasBit(img, PALETTE_MODIFIER_COLOUR) ? pal : PAL_NONE, x, y); + } + + if (roadtype == ROADTYPE_TRAM) { + DrawSprite(SPR_TRAMWAY_TRAM + (t->ground.sprite == SPR_ROAD_PAVED_STRAIGHT_X ? 1 : 0), PAL_NONE, x, y); + } + + /* Default waypoint has no railtype specific sprites */ + DrawRailTileSeqInGUI(x, y, t, st == STATION_WAYPOINT ? 0 : total_offset, 0, pal); +} + +static int GetSlopePixelZ_Station(TileIndex tile, uint x, uint y) +{ + return GetTileMaxPixelZ(tile); +} + +static Foundation GetFoundation_Station(TileIndex tile, Slope tileh) +{ + return FlatteningFoundation(tileh); +} + +static void GetTileDesc_Station(TileIndex tile, TileDesc *td) +{ + td->owner[0] = GetTileOwner(tile); + if (IsDriveThroughStopTile(tile)) { + Owner road_owner = INVALID_OWNER; + Owner tram_owner = INVALID_OWNER; + RoadTypes rts = GetRoadTypes(tile); + if (HasBit(rts, ROADTYPE_ROAD)) road_owner = GetRoadOwner(tile, ROADTYPE_ROAD); + if (HasBit(rts, ROADTYPE_TRAM)) tram_owner = GetRoadOwner(tile, ROADTYPE_TRAM); + + /* Is there a mix of owners? */ + if ((tram_owner != INVALID_OWNER && tram_owner != td->owner[0]) || + (road_owner != INVALID_OWNER && road_owner != td->owner[0])) { + uint i = 1; + if (road_owner != INVALID_OWNER) { + td->owner_type[i] = STR_LAND_AREA_INFORMATION_ROAD_OWNER; + td->owner[i] = road_owner; + i++; + } + if (tram_owner != INVALID_OWNER) { + td->owner_type[i] = STR_LAND_AREA_INFORMATION_TRAM_OWNER; + td->owner[i] = tram_owner; + } + } + } + td->build_date = BaseStation::GetByTile(tile)->build_date; + + if (HasStationTileRail(tile)) { + const StationSpec *spec = GetStationSpec(tile); + + if (spec != NULL) { + td->station_class = StationClass::Get(spec->cls_id)->name; + td->station_name = spec->name; + + if (spec->grf_prop.grffile != NULL) { + const GRFConfig *gc = GetGRFConfig(spec->grf_prop.grffile->grfid); + td->grf = gc->GetName(); + } + } + + const RailtypeInfo *rti = GetRailTypeInfo(GetRailType(tile)); + td->rail_speed = rti->max_speed; + } + + if (IsAirport(tile)) { + const AirportSpec *as = Station::GetByTile(tile)->airport.GetSpec(); + td->airport_class = AirportClass::Get(as->cls_id)->name; + td->airport_name = as->name; + + const AirportTileSpec *ats = AirportTileSpec::GetByTile(tile); + td->airport_tile_name = ats->name; + + if (as->grf_prop.grffile != NULL) { + const GRFConfig *gc = GetGRFConfig(as->grf_prop.grffile->grfid); + td->grf = gc->GetName(); + } else if (ats->grf_prop.grffile != NULL) { + const GRFConfig *gc = GetGRFConfig(ats->grf_prop.grffile->grfid); + td->grf = gc->GetName(); + } + } + + StringID str; + switch (GetStationType(tile)) { + default: NOT_REACHED(); + case STATION_RAIL: str = STR_LAI_STATION_DESCRIPTION_RAILROAD_STATION; break; + case STATION_AIRPORT: + str = (IsHangar(tile) ? STR_LAI_STATION_DESCRIPTION_AIRCRAFT_HANGAR : STR_LAI_STATION_DESCRIPTION_AIRPORT); + break; + case STATION_TRUCK: str = STR_LAI_STATION_DESCRIPTION_TRUCK_LOADING_AREA; break; + case STATION_BUS: str = STR_LAI_STATION_DESCRIPTION_BUS_STATION; break; + case STATION_OILRIG: str = STR_INDUSTRY_NAME_OIL_RIG; break; + case STATION_DOCK: str = STR_LAI_STATION_DESCRIPTION_SHIP_DOCK; break; + case STATION_BUOY: str = STR_LAI_STATION_DESCRIPTION_BUOY; break; + case STATION_WAYPOINT: str = STR_LAI_STATION_DESCRIPTION_WAYPOINT; break; + } + td->str = str; +} + + +static TrackStatus GetTileTrackStatus_Station(TileIndex tile, TransportType mode, uint sub_mode, DiagDirection side) +{ + TrackBits trackbits = TRACK_BIT_NONE; + + switch (mode) { + case TRANSPORT_RAIL: + if (HasStationRail(tile) && !IsStationTileBlocked(tile)) { + trackbits = TrackToTrackBits(GetRailStationTrack(tile)); + } + break; + + case TRANSPORT_WATER: + /* buoy is coded as a station, it is always on open water */ + if (IsBuoy(tile)) { + trackbits = TRACK_BIT_ALL; + /* remove tracks that connect NE map edge */ + if (TileX(tile) == 0) trackbits &= ~(TRACK_BIT_X | TRACK_BIT_UPPER | TRACK_BIT_RIGHT); + /* remove tracks that connect NW map edge */ + if (TileY(tile) == 0) trackbits &= ~(TRACK_BIT_Y | TRACK_BIT_LEFT | TRACK_BIT_UPPER); + } + break; + + case TRANSPORT_ROAD: + if ((GetRoadTypes(tile) & sub_mode) != 0 && IsRoadStop(tile)) { + DiagDirection dir = GetRoadStopDir(tile); + Axis axis = DiagDirToAxis(dir); + + if (side != INVALID_DIAGDIR) { + if (axis != DiagDirToAxis(side) || (IsStandardRoadStopTile(tile) && dir != side)) break; + } + + trackbits = AxisToTrackBits(axis); + } + break; + + default: + break; + } + + return CombineTrackStatus(TrackBitsToTrackdirBits(trackbits), TRACKDIR_BIT_NONE); +} + + +static void TileLoop_Station(TileIndex tile) +{ + /* FIXME -- GetTileTrackStatus_Station -> animated stationtiles + * hardcoded.....not good */ + switch (GetStationType(tile)) { + case STATION_AIRPORT: + AirportTileAnimationTrigger(Station::GetByTile(tile), tile, AAT_TILELOOP); + break; + + case STATION_DOCK: + if (GetTileSlope(tile) != SLOPE_FLAT) break; // only handle water part + /* FALL THROUGH */ + case STATION_OILRIG: //(station part) + case STATION_BUOY: + TileLoop_Water(tile); + break; + + default: break; + } +} + + +static void AnimateTile_Station(TileIndex tile) +{ + if (HasStationRail(tile)) { + AnimateStationTile(tile); + return; + } + + if (IsAirport(tile)) { + AnimateAirportTile(tile); + } +} + + +static bool ClickTile_Station(TileIndex tile) +{ + const BaseStation *bst = BaseStation::GetByTile(tile); + + if (bst->facilities & FACIL_WAYPOINT) { + ShowWaypointWindow(Waypoint::From(bst)); + } else if (IsHangar(tile)) { + const Station *st = Station::From(bst); + ShowDepotWindow(st->airport.GetHangarTile(st->airport.GetHangarNum(tile)), VEH_AIRCRAFT); + } else { + ShowStationViewWindow(bst->index); + } + return true; +} + +static VehicleEnterTileStatus VehicleEnter_Station(Vehicle *v, TileIndex tile, int x, int y) +{ + if (v->type == VEH_TRAIN) { + StationID station_id = GetStationIndex(tile); + if (!v->current_order.ShouldStopAtStation(v, station_id)) return VETSB_CONTINUE; + if (!IsRailStation(tile) || !v->IsFrontEngine()) return VETSB_CONTINUE; + + int station_ahead; + int station_length; + int stop = GetTrainStopLocation(station_id, tile, Train::From(v), &station_ahead, &station_length); + + /* Stop whenever that amount of station ahead + the distance from the + * begin of the platform to the stop location is longer than the length + * of the platform. Station ahead 'includes' the current tile where the + * vehicle is on, so we need to subtract that. */ + if (!IsInsideBS(stop + station_ahead, station_length, TILE_SIZE)) return VETSB_CONTINUE; + + DiagDirection dir = DirToDiagDir(v->direction); + + x &= 0xF; + y &= 0xF; + + if (DiagDirToAxis(dir) != AXIS_X) Swap(x, y); + if (y == TILE_SIZE / 2) { + if (dir != DIAGDIR_SE && dir != DIAGDIR_SW) x = TILE_SIZE - 1 - x; + stop &= TILE_SIZE - 1; + + if (x == stop) return VETSB_ENTERED_STATION | (VehicleEnterTileStatus)(station_id << VETS_STATION_ID_OFFSET); // enter station + if (x < stop) { + uint16 spd; + + v->vehstatus |= VS_TRAIN_SLOWING; + spd = max(0, (stop - x) * 20 - 15); + if (spd < v->cur_speed) v->cur_speed = spd; + } + } + } else if (v->type == VEH_ROAD) { + RoadVehicle *rv = RoadVehicle::From(v); + if (rv->state < RVSB_IN_ROAD_STOP && !IsReversingRoadTrackdir((Trackdir)rv->state) && rv->frame == 0) { + if (IsRoadStop(tile) && rv->IsFrontEngine()) { + /* Attempt to allocate a parking bay in a road stop */ + return RoadStop::GetByTile(tile, GetRoadStopType(tile))->Enter(rv) ? VETSB_CONTINUE : VETSB_CANNOT_ENTER; + } + } + } + + return VETSB_CONTINUE; +} + +/** + * Run the watched cargo callback for all houses in the catchment area. + * @param st Station. + */ +void TriggerWatchedCargoCallbacks(Station *st) +{ + /* Collect cargoes accepted since the last big tick. */ + uint cargoes = 0; + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + if (HasBit(st->goods[cid].acceptance_pickup, GoodsEntry::GES_ACCEPTED_BIGTICK)) SetBit(cargoes, cid); + } + + /* Anything to do? */ + if (cargoes == 0) return; + + /* Loop over all houses in the catchment. */ + Rect r = st->GetCatchmentRect(); + TileArea ta(TileXY(r.left, r.top), TileXY(r.right, r.bottom)); + TILE_AREA_LOOP(tile, ta) { + if (IsTileType(tile, MP_HOUSE)) { + WatchedCargoCallback(tile, cargoes); + } + } +} + +/** + * This function is called for each station once every 250 ticks. + * Not all stations will get the tick at the same time. + * @param st the station receiving the tick. + * @return true if the station is still valid (wasn't deleted) + */ +static bool StationHandleBigTick(BaseStation *st) +{ + if (!st->IsInUse()) { + if (++st->delete_ctr >= 8) delete st; + return false; + } + + if (Station::IsExpected(st)) { + TriggerWatchedCargoCallbacks(Station::From(st)); + + for (CargoID i = 0; i < NUM_CARGO; i++) { + ClrBit(Station::From(st)->goods[i].acceptance_pickup, GoodsEntry::GES_ACCEPTED_BIGTICK); + } + } + + + if ((st->facilities & FACIL_WAYPOINT) == 0) UpdateStationAcceptance(Station::From(st), true); + + return true; +} + +static inline void byte_inc_sat(byte *p) +{ + byte b = *p + 1; + if (b != 0) *p = b; +} + +static void UpdateStationRating(Station *st) +{ + bool waiting_changed = false; + + byte_inc_sat(&st->time_since_load); + byte_inc_sat(&st->time_since_unload); + + const CargoSpec *cs; + FOR_ALL_CARGOSPECS(cs) { + GoodsEntry *ge = &st->goods[cs->Index()]; + /* Slowly increase the rating back to his original level in the case we + * didn't deliver cargo yet to this station. This happens when a bribe + * failed while you didn't moved that cargo yet to a station. */ + if (!HasBit(ge->acceptance_pickup, GoodsEntry::GES_PICKUP) && ge->rating < INITIAL_STATION_RATING) { + ge->rating++; + } + + /* Only change the rating if we are moving this cargo */ + if (HasBit(ge->acceptance_pickup, GoodsEntry::GES_PICKUP)) { + byte_inc_sat(&ge->time_since_pickup); + + bool skip = false; + int rating = 0; + uint waiting = ge->cargo.Count(); + + if (HasBit(cs->callback_mask, CBM_CARGO_STATION_RATING_CALC)) { + /* Perform custom station rating. If it succeeds the speed, days in transit and + * waiting cargo ratings must not be executed. */ + + /* NewGRFs expect last speed to be 0xFF when no vehicle has arrived yet. */ + uint last_speed = ge->HasVehicleEverTriedLoading() ? ge->last_speed : 0xFF; + + uint32 var18 = min(ge->time_since_pickup, 0xFF) | (min(waiting, 0xFFFF) << 8) | (min(last_speed, 0xFF) << 24); + /* Convert to the 'old' vehicle types */ + uint32 var10 = (st->last_vehicle_type == VEH_INVALID) ? 0x0 : (st->last_vehicle_type + 0x10); + uint16 callback = GetCargoCallback(CBID_CARGO_STATION_RATING_CALC, var10, var18, cs); + if (callback != CALLBACK_FAILED) { + skip = true; + rating = GB(callback, 0, 14); + + /* Simulate a 15 bit signed value */ + if (HasBit(callback, 14)) rating -= 0x4000; + } + } + + if (!skip) { + int b = ge->last_speed - 85; + if (b >= 0) rating += b >> 2; + + byte waittime = ge->time_since_pickup; + if (st->last_vehicle_type == VEH_SHIP) waittime >>= 2; + (waittime > 21) || + (rating += 25, waittime > 12) || + (rating += 25, waittime > 6) || + (rating += 45, waittime > 3) || + (rating += 35, true); + + (rating -= 90, waiting > 1500) || + (rating += 55, waiting > 1000) || + (rating += 35, waiting > 600) || + (rating += 10, waiting > 300) || + (rating += 20, waiting > 100) || + (rating += 10, true); + } + + if (Company::IsValidID(st->owner) && HasBit(st->town->statues, st->owner)) rating += 26; + + byte age = ge->last_age; + (age >= 3) || + (rating += 10, age >= 2) || + (rating += 10, age >= 1) || + (rating += 13, true); + + { + int or_ = ge->rating; // old rating + + /* only modify rating in steps of -2, -1, 0, 1 or 2 */ + ge->rating = rating = or_ + Clamp(Clamp(rating, 0, 255) - or_, -2, 2); + + /* if rating is <= 64 and more than 200 items waiting, + * remove some random amount of goods from the station */ + if (rating <= 64 && waiting >= 200) { + int dec = Random() & 0x1F; + if (waiting < 400) dec &= 7; + waiting -= dec + 1; + waiting_changed = true; + } + + /* if rating is <= 127 and there are any items waiting, maybe remove some goods. */ + if (rating <= 127 && waiting != 0) { + uint32 r = Random(); + if (rating <= (int)GB(r, 0, 7)) { + /* Need to have int, otherwise it will just overflow etc. */ + waiting = max((int)waiting - (int)GB(r, 8, 2) - 1, 0); + waiting_changed = true; + } + } + + /* At some point we really must cap the cargo. Previously this + * was a strict 4095, but now we'll have a less strict, but + * increasingly aggressive truncation of the amount of cargo. */ + static const uint WAITING_CARGO_THRESHOLD = 1 << 12; + static const uint WAITING_CARGO_CUT_FACTOR = 1 << 6; + static const uint MAX_WAITING_CARGO = 1 << 15; + + if (waiting > WAITING_CARGO_THRESHOLD) { + uint difference = waiting - WAITING_CARGO_THRESHOLD; + waiting -= (difference / WAITING_CARGO_CUT_FACTOR); + + waiting = min(waiting, MAX_WAITING_CARGO); + waiting_changed = true; + } + + if (waiting_changed) ge->cargo.Truncate(waiting); + } + } + } + + StationID index = st->index; + if (waiting_changed) { + SetWindowDirty(WC_STATION_VIEW, index); // update whole window + } else { + SetWindowWidgetDirty(WC_STATION_VIEW, index, WID_SV_ACCEPT_RATING_LIST); // update only ratings list + } +} + +/* called for every station each tick */ +static void StationHandleSmallTick(BaseStation *st) +{ + if ((st->facilities & FACIL_WAYPOINT) != 0 || !st->IsInUse()) return; + + byte b = st->delete_ctr + 1; + if (b >= STATION_RATING_TICKS) b = 0; + st->delete_ctr = b; + + if (b == 0) UpdateStationRating(Station::From(st)); +} + +void OnTick_Station() +{ + if (_game_mode == GM_EDITOR) return; + + BaseStation *st; + FOR_ALL_BASE_STATIONS(st) { + StationHandleSmallTick(st); + + /* Run STATION_ACCEPTANCE_TICKS = 250 tick interval trigger for station animation. + * Station index is included so that triggers are not all done + * at the same time. */ + if ((_tick_counter + st->index) % STATION_ACCEPTANCE_TICKS == 0) { + /* Stop processing this station if it was deleted */ + if (!StationHandleBigTick(st)) continue; + TriggerStationAnimation(st, st->xy, SAT_250_TICKS); + if (Station::IsExpected(st)) AirportAnimationTrigger(Station::From(st), AAT_STATION_250_TICKS); + } + } +} + +/** Monthly loop for stations. */ +void StationMonthlyLoop() +{ + Station *st; + + FOR_ALL_STATIONS(st) { + for (CargoID i = 0; i < NUM_CARGO; i++) { + GoodsEntry *ge = &st->goods[i]; + SB(ge->acceptance_pickup, GoodsEntry::GES_LAST_MONTH, 1, GB(ge->acceptance_pickup, GoodsEntry::GES_CURRENT_MONTH, 1)); + ClrBit(ge->acceptance_pickup, GoodsEntry::GES_CURRENT_MONTH); + } + } +} + + +void ModifyStationRatingAround(TileIndex tile, Owner owner, int amount, uint radius) +{ + Station *st; + + FOR_ALL_STATIONS(st) { + if (st->owner == owner && + DistanceManhattan(tile, st->xy) <= radius) { + for (CargoID i = 0; i < NUM_CARGO; i++) { + GoodsEntry *ge = &st->goods[i]; + + if (ge->acceptance_pickup != 0) { + ge->rating = Clamp(ge->rating + amount, 0, 255); + } + } + } + } +} + +static uint UpdateStationWaiting(Station *st, CargoID type, uint amount, SourceType source_type, SourceID source_id) +{ + /* We can't allocate a CargoPacket? Then don't do anything + * at all; i.e. just discard the incoming cargo. */ + if (!CargoPacket::CanAllocateItem()) return 0; + + GoodsEntry &ge = st->goods[type]; + amount += ge.amount_fract; + ge.amount_fract = GB(amount, 0, 8); + + amount >>= 8; + /* No new "real" cargo item yet. */ + if (amount == 0) return 0; + + ge.cargo.Append(new CargoPacket(st->index, st->xy, amount, source_type, source_id)); + + if (!HasBit(ge.acceptance_pickup, GoodsEntry::GES_PICKUP)) { + InvalidateWindowData(WC_STATION_LIST, st->index); + SetBit(ge.acceptance_pickup, GoodsEntry::GES_PICKUP); + } + + TriggerStationRandomisation(st, st->xy, SRT_NEW_CARGO, type); + TriggerStationAnimation(st, st->xy, SAT_NEW_CARGO, type); + AirportAnimationTrigger(st, AAT_STATION_NEW_CARGO, type); + + SetWindowDirty(WC_STATION_VIEW, st->index); + st->MarkTilesDirty(true); + return amount; +} + +static bool IsUniqueStationName(const char *name) +{ + const Station *st; + + FOR_ALL_STATIONS(st) { + if (st->name != NULL && strcmp(st->name, name) == 0) return false; + } + + return true; +} + +/** + * Rename a station + * @param tile unused + * @param flags operation to perform + * @param p1 station ID that is to be renamed + * @param p2 unused + * @param text the new name or an empty string when resetting to the default + * @return the cost of this operation or an error + */ +CommandCost CmdRenameStation(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + Station *st = Station::GetIfValid(p1); + if (st == NULL) return CMD_ERROR; + + CommandCost ret = CheckOwnership(st->owner); + if (ret.Failed()) return ret; + + bool reset = StrEmpty(text); + + if (!reset) { + if (Utf8StringLength(text) >= MAX_LENGTH_STATION_NAME_CHARS) return CMD_ERROR; + if (!IsUniqueStationName(text)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE); + } + + if (flags & DC_EXEC) { + free(st->name); + st->name = reset ? NULL : strdup(text); + + st->UpdateVirtCoord(); + InvalidateWindowData(WC_STATION_LIST, st->owner, 1); + } + + return CommandCost(); +} + +/** + * Find all stations around a rectangular producer (industry, house, headquarter, ...) + * + * @param location The location/area of the producer + * @param stations The list to store the stations in + */ +void FindStationsAroundTiles(const TileArea &location, StationList *stations) +{ + /* area to search = producer plus station catchment radius */ + uint max_rad = (_settings_game.station.modified_catchment ? MAX_CATCHMENT : CA_UNMODIFIED); + + uint x = TileX(location.tile); + uint y = TileY(location.tile); + + uint min_x = (x > max_rad) ? x - max_rad : 0; + uint max_x = x + location.w + max_rad; + uint min_y = (y > max_rad) ? y - max_rad : 0; + uint max_y = y + location.h + max_rad; + + if (min_x == 0 && _settings_game.construction.freeform_edges) min_x = 1; + if (min_y == 0 && _settings_game.construction.freeform_edges) min_y = 1; + if (max_x >= MapSizeX()) max_x = MapSizeX() - 1; + if (max_y >= MapSizeY()) max_y = MapSizeY() - 1; + + for (uint cy = min_y; cy < max_y; cy++) { + for (uint cx = min_x; cx < max_x; cx++) { + TileIndex cur_tile = TileXY(cx, cy); + if (!IsTileType(cur_tile, MP_STATION)) continue; + + Station *st = Station::GetByTile(cur_tile); + /* st can be NULL in case of waypoints */ + if (st == NULL) continue; + + if (_settings_game.station.modified_catchment) { + int rad = st->GetCatchmentRadius(); + int rad_x = cx - x; + int rad_y = cy - y; + + if (rad_x < -rad || rad_x >= rad + location.w) continue; + if (rad_y < -rad || rad_y >= rad + location.h) continue; + } + + /* Insert the station in the set. This will fail if it has + * already been added. + */ + stations->Include(st); + } + } +} + +/** + * Run a tile loop to find stations around a tile, on demand. Cache the result for further requests + * @return pointer to a StationList containing all stations found + */ +const StationList *StationFinder::GetStations() +{ + if (this->tile != INVALID_TILE) { + FindStationsAroundTiles(*this, &this->stations); + this->tile = INVALID_TILE; + } + return &this->stations; +} + +uint MoveGoodsToStation(CargoID type, uint amount, SourceType source_type, SourceID source_id, const StationList *all_stations) +{ + /* Return if nothing to do. Also the rounding below fails for 0. */ + if (amount == 0) return 0; + + Station *st1 = NULL; // Station with best rating + Station *st2 = NULL; // Second best station + uint best_rating1 = 0; // rating of st1 + uint best_rating2 = 0; // rating of st2 + + for (Station * const *st_iter = all_stations->Begin(); st_iter != all_stations->End(); ++st_iter) { + Station *st = *st_iter; + + /* Is the station reserved exclusively for somebody else? */ + if (st->town->exclusive_counter > 0 && st->town->exclusivity != st->owner) continue; + + if (st->goods[type].rating == 0) continue; // Lowest possible rating, better not to give cargo anymore + + if (_settings_game.order.selectgoods && !st->goods[type].HasVehicleEverTriedLoading()) continue; // Selectively servicing stations, and not this one + + if (IsCargoInClass(type, CC_PASSENGERS)) { + if (st->facilities == FACIL_TRUCK_STOP) continue; // passengers are never served by just a truck stop + } else { + if (st->facilities == FACIL_BUS_STOP) continue; // non-passengers are never served by just a bus stop + } + + /* This station can be used, add it to st1/st2 */ + if (st1 == NULL || st->goods[type].rating >= best_rating1) { + st2 = st1; best_rating2 = best_rating1; st1 = st; best_rating1 = st->goods[type].rating; + } else if (st2 == NULL || st->goods[type].rating >= best_rating2) { + st2 = st; best_rating2 = st->goods[type].rating; + } + } + + /* no stations around at all? */ + if (st1 == NULL) return 0; + + /* From now we'll calculate with fractal cargo amounts. + * First determine how much cargo we really have. */ + amount *= best_rating1 + 1; + + if (st2 == NULL) { + /* only one station around */ + return UpdateStationWaiting(st1, type, amount, source_type, source_id); + } + + /* several stations around, the best two (highest rating) are in st1 and st2 */ + assert(st1 != NULL); + assert(st2 != NULL); + assert(best_rating1 != 0 || best_rating2 != 0); + + /* Then determine the amount the worst station gets. We do it this way as the + * best should get a bonus, which in this case is the rounding difference from + * this calculation. In reality that will mean the bonus will be pretty low. + * Nevertheless, the best station should always get the most cargo regardless + * of rounding issues. */ + uint worst_cargo = amount * best_rating2 / (best_rating1 + best_rating2); + assert(worst_cargo <= (amount - worst_cargo)); + + /* And then send the cargo to the stations! */ + uint moved = UpdateStationWaiting(st1, type, amount - worst_cargo, source_type, source_id); + /* These two UpdateStationWaiting's can't be in the statement as then the order + * of execution would be undefined and that could cause desyncs with callbacks. */ + return moved + UpdateStationWaiting(st2, type, worst_cargo, source_type, source_id); +} + +void BuildOilRig(TileIndex tile) +{ + if (!Station::CanAllocateItem()) { + DEBUG(misc, 0, "Can't allocate station for oilrig at 0x%X, reverting to oilrig only", tile); + return; + } + + Station *st = new Station(tile); + st->town = ClosestTownFromTile(tile, UINT_MAX); + + st->string_id = GenerateStationName(st, tile, STATIONNAMING_OILRIG); + + assert(IsTileType(tile, MP_INDUSTRY)); + DeleteAnimatedTile(tile); + MakeOilrig(tile, st->index, GetWaterClass(tile)); + + st->owner = OWNER_NONE; + st->airport.type = AT_OILRIG; + st->airport.Add(tile); + st->dock_tile = tile; + st->facilities = FACIL_AIRPORT | FACIL_DOCK; + st->build_date = _date; + + st->rect.BeforeAddTile(tile, StationRect::ADD_FORCE); + + st->UpdateVirtCoord(); + UpdateStationAcceptance(st, false); + st->RecomputeIndustriesNear(); +} + +void DeleteOilRig(TileIndex tile) +{ + Station *st = Station::GetByTile(tile); + + MakeWaterKeepingClass(tile, OWNER_NONE); + + st->dock_tile = INVALID_TILE; + st->airport.Clear(); + st->facilities &= ~(FACIL_AIRPORT | FACIL_DOCK); + st->airport.flags = 0; + + st->rect.AfterRemoveTile(st, tile); + + st->UpdateVirtCoord(); + st->RecomputeIndustriesNear(); + if (!st->IsInUse()) delete st; +} + +static void ChangeTileOwner_Station(TileIndex tile, Owner old_owner, Owner new_owner) +{ + if (IsRoadStopTile(tile)) { + for (RoadType rt = ROADTYPE_ROAD; rt < ROADTYPE_END; rt++) { + /* Update all roadtypes, no matter if they are present */ + if (GetRoadOwner(tile, rt) == old_owner) { + if (HasTileRoadType(tile, rt)) { + /* A drive-through road-stop has always two road bits. No need to dirty windows here, we'll redraw the whole screen anyway. */ + Company::Get(old_owner)->infrastructure.road[rt] -= 2; + if (new_owner != INVALID_OWNER) Company::Get(new_owner)->infrastructure.road[rt] += 2; + } + SetRoadOwner(tile, rt, new_owner == INVALID_OWNER ? OWNER_NONE : new_owner); + } + } + } + + if (!IsTileOwner(tile, old_owner)) return; + + if (new_owner != INVALID_OWNER) { + /* Update company infrastructure counts. Only do it here + * if the new owner is valid as otherwise the clear + * command will do it for us. No need to dirty windows + * here, we'll redraw the whole screen anyway.*/ + Company *old_company = Company::Get(old_owner); + Company *new_company = Company::Get(new_owner); + + /* Update counts for underlying infrastructure. */ + switch (GetStationType(tile)) { + case STATION_RAIL: + case STATION_WAYPOINT: + if (!IsStationTileBlocked(tile)) { + old_company->infrastructure.rail[GetRailType(tile)]--; + new_company->infrastructure.rail[GetRailType(tile)]++; + } + break; + + case STATION_BUS: + case STATION_TRUCK: + /* Road stops were already handled above. */ + break; + + case STATION_BUOY: + case STATION_DOCK: + if (GetWaterClass(tile) == WATER_CLASS_CANAL) { + old_company->infrastructure.water--; + new_company->infrastructure.water++; + } + break; + + default: + break; + } + + /* Update station tile count. */ + if (!IsBuoy(tile) && !IsAirport(tile)) { + old_company->infrastructure.station--; + new_company->infrastructure.station++; + } + + /* for buoys, owner of tile is owner of water, st->owner == OWNER_NONE */ + SetTileOwner(tile, new_owner); + InvalidateWindowClassesData(WC_STATION_LIST, 0); + } else { + if (IsDriveThroughStopTile(tile)) { + /* Remove the drive-through road stop */ + DoCommand(tile, 1 | 1 << 8, (GetStationType(tile) == STATION_TRUCK) ? ROADSTOP_TRUCK : ROADSTOP_BUS, DC_EXEC | DC_BANKRUPT, CMD_REMOVE_ROAD_STOP); + assert(IsTileType(tile, MP_ROAD)); + /* Change owner of tile and all roadtypes */ + ChangeTileOwner(tile, old_owner, new_owner); + } else { + DoCommand(tile, 0, 0, DC_EXEC | DC_BANKRUPT, CMD_LANDSCAPE_CLEAR); + /* Set tile owner of water under (now removed) buoy and dock to OWNER_NONE. + * Update owner of buoy if it was not removed (was in orders). + * Do not update when owned by OWNER_WATER (sea and rivers). */ + if ((IsTileType(tile, MP_WATER) || IsBuoyTile(tile)) && IsTileOwner(tile, old_owner)) SetTileOwner(tile, OWNER_NONE); + } + } +} + +/** + * Check if a drive-through road stop tile can be cleared. + * Road stops built on town-owned roads check the conditions + * that would allow clearing of the original road. + * @param tile road stop tile to check + * @param flags command flags + * @return true if the road can be cleared + */ +static bool CanRemoveRoadWithStop(TileIndex tile, DoCommandFlag flags) +{ + /* Yeah... water can always remove stops, right? */ + if (_current_company == OWNER_WATER) return true; + + RoadTypes rts = GetRoadTypes(tile); + if (HasBit(rts, ROADTYPE_TRAM)) { + Owner tram_owner = GetRoadOwner(tile, ROADTYPE_TRAM); + if (tram_owner != OWNER_NONE && CheckOwnership(tram_owner).Failed()) return false; + } + if (HasBit(rts, ROADTYPE_ROAD)) { + Owner road_owner = GetRoadOwner(tile, ROADTYPE_ROAD); + if (road_owner != OWNER_TOWN) { + if (road_owner != OWNER_NONE && CheckOwnership(road_owner).Failed()) return false; + } else { + if (CheckAllowRemoveRoad(tile, GetAnyRoadBits(tile, ROADTYPE_ROAD), OWNER_TOWN, ROADTYPE_ROAD, flags).Failed()) return false; + } + } + + return true; +} + +/** + * Clear a single tile of a station. + * @param tile The tile to clear. + * @param flags The DoCommand flags related to the "command". + * @return The cost, or error of clearing. + */ +CommandCost ClearTile_Station(TileIndex tile, DoCommandFlag flags) +{ + if (flags & DC_AUTO) { + switch (GetStationType(tile)) { + default: break; + case STATION_RAIL: return_cmd_error(STR_ERROR_MUST_DEMOLISH_RAILROAD); + case STATION_WAYPOINT: return_cmd_error(STR_ERROR_BUILDING_MUST_BE_DEMOLISHED); + case STATION_AIRPORT: return_cmd_error(STR_ERROR_MUST_DEMOLISH_AIRPORT_FIRST); + case STATION_TRUCK: return_cmd_error(HasTileRoadType(tile, ROADTYPE_TRAM) ? STR_ERROR_MUST_DEMOLISH_CARGO_TRAM_STATION_FIRST : STR_ERROR_MUST_DEMOLISH_TRUCK_STATION_FIRST); + case STATION_BUS: return_cmd_error(HasTileRoadType(tile, ROADTYPE_TRAM) ? STR_ERROR_MUST_DEMOLISH_PASSENGER_TRAM_STATION_FIRST : STR_ERROR_MUST_DEMOLISH_BUS_STATION_FIRST); + case STATION_BUOY: return_cmd_error(STR_ERROR_BUOY_IN_THE_WAY); + case STATION_DOCK: return_cmd_error(STR_ERROR_MUST_DEMOLISH_DOCK_FIRST); + case STATION_OILRIG: + SetDParam(1, STR_INDUSTRY_NAME_OIL_RIG); + return_cmd_error(STR_ERROR_GENERIC_OBJECT_IN_THE_WAY); + } + } + + switch (GetStationType(tile)) { + case STATION_RAIL: return RemoveRailStation(tile, flags); + case STATION_WAYPOINT: return RemoveRailWaypoint(tile, flags); + case STATION_AIRPORT: return RemoveAirport(tile, flags); + case STATION_TRUCK: + if (IsDriveThroughStopTile(tile) && !CanRemoveRoadWithStop(tile, flags)) { + return_cmd_error(STR_ERROR_MUST_DEMOLISH_TRUCK_STATION_FIRST); + } + return RemoveRoadStop(tile, flags); + case STATION_BUS: + if (IsDriveThroughStopTile(tile) && !CanRemoveRoadWithStop(tile, flags)) { + return_cmd_error(STR_ERROR_MUST_DEMOLISH_BUS_STATION_FIRST); + } + return RemoveRoadStop(tile, flags); + case STATION_BUOY: return RemoveBuoy(tile, flags); + case STATION_DOCK: return RemoveDock(tile, flags); + default: break; + } + + return CMD_ERROR; +} + +static CommandCost TerraformTile_Station(TileIndex tile, DoCommandFlag flags, int z_new, Slope tileh_new) +{ + if (_settings_game.construction.build_on_slopes && AutoslopeEnabled()) { + /* TODO: If you implement newgrf callback 149 'land slope check', you have to decide what to do with it here. + * TTDP does not call it. + */ + if (GetTileMaxZ(tile) == z_new + GetSlopeMaxZ(tileh_new)) { + switch (GetStationType(tile)) { + case STATION_WAYPOINT: + case STATION_RAIL: { + DiagDirection direction = AxisToDiagDir(GetRailStationAxis(tile)); + if (!AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, direction)) break; + if (!AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, ReverseDiagDir(direction))) break; + return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); + } + + case STATION_AIRPORT: + return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); + + case STATION_TRUCK: + case STATION_BUS: { + DiagDirection direction = GetRoadStopDir(tile); + if (!AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, direction)) break; + if (IsDriveThroughStopTile(tile)) { + if (!AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, ReverseDiagDir(direction))) break; + } + return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); + } + + default: break; + } + } + } + return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); +} + + +extern const TileTypeProcs _tile_type_station_procs = { + DrawTile_Station, // draw_tile_proc + GetSlopePixelZ_Station, // get_slope_z_proc + ClearTile_Station, // clear_tile_proc + NULL, // add_accepted_cargo_proc + GetTileDesc_Station, // get_tile_desc_proc + GetTileTrackStatus_Station, // get_tile_track_status_proc + ClickTile_Station, // click_tile_proc + AnimateTile_Station, // animate_tile_proc + TileLoop_Station, // tile_loop_proc + ChangeTileOwner_Station, // change_tile_owner_proc + NULL, // add_produced_cargo_proc + VehicleEnter_Station, // vehicle_enter_tile_proc + GetFoundation_Station, // get_foundation_proc + TerraformTile_Station, // terraform_tile_proc +}; diff --git a/src/station_func.h b/src/station_func.h index 000bd19ba..13984aeb6 100644 --- a/src/station_func.h +++ b/src/station_func.h @@ -17,6 +17,7 @@ #include "road_type.h" #include "economy_func.h" #include "rail.h" +#include "order_type.h" void ModifyStationRatingAround(TileIndex tile, Owner owner, int amount, uint radius); @@ -29,6 +30,7 @@ CargoArray GetProductionAroundTiles(TileIndex tile, int w, int h, int rad); CargoArray GetAcceptanceAroundTiles(TileIndex tile, int w, int h, int rad, uint32 *always_accepted = NULL); void UpdateStationAcceptance(Station *st, bool show_msg); +uint UpdateStationWaiting(Station *st, CargoID type, uint amount, SourceType source_type, SourceID source_id, TileIndex dest_tile = INVALID_TILE, SourceType dest_type = ST_INDUSTRY, SourceID dest_id = INVALID_SOURCE, OrderID next_hop = INVALID_ORDER, StationID next_unload = INVALID_STATION, byte flags = 0); const DrawTileSprites *GetStationTileLayout(StationType st, byte gfx); void StationPickerDrawSprite(int x, int y, StationType st, RailType railtype, RoadType roadtype, int image); diff --git a/src/station_gui.cpp b/src/station_gui.cpp index b0709e2c2..219bcae9d 100644 --- a/src/station_gui.cpp +++ b/src/station_gui.cpp @@ -20,6 +20,7 @@ #include "station_gui.h" #include "strings_func.h" #include "window_func.h" +#include "layer_func.h" #include "viewport_func.h" #include "widgets/dropdown_func.h" #include "station_base.h" @@ -30,6 +31,9 @@ #include "core/geometry_func.hpp" #include "vehiclelist.h" #include "town.h" +#include "industry.h" +#include "cargodest_base.h" +#include "departures_gui.h" #include "widgets/station_widget.h" @@ -743,14 +747,18 @@ static const NWidgetPart _nested_station_view_widgets[] = { NWidget(WWT_PANEL, COLOUR_GREY, WID_SV_ACCEPT_RATING_LIST), SetMinimalSize(249, 32), SetResize(1, 0), EndContainer(), NWidget(NWID_HORIZONTAL), NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_LOCATION), SetMinimalSize(45, 12), SetResize(1, 0), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_LOCATION), SetMinimalSize(36, 12), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_BUTTON_LOCATION, STR_STATION_VIEW_CENTER_TOOLTIP), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_ACCEPTS_RATINGS), SetMinimalSize(46, 12), SetResize(1, 0), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_ACCEPTS_RATINGS), SetMinimalSize(37, 12), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_STATION_VIEW_RATINGS_BUTTON, STR_STATION_VIEW_RATINGS_TOOLTIP), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_RENAME), SetMinimalSize(45, 12), SetResize(1, 0), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_CARGO_FROM_TO_VIA), SetMinimalSize(36, 12), SetResize(1, 0), SetFill(1, 1), + SetDataTip(STR_STATION_VIEW_WAITING_VIA_BUTTON, STR_STATION_VIEW_WAITING_VIA_TOOLTIP), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_RENAME), SetMinimalSize(36, 12), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_BUTTON_RENAME, STR_STATION_VIEW_RENAME_TOOLTIP), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_DEPARTURES), SetMinimalSize(80, 12), SetResize(1, 0), SetFill(1, 1), + SetDataTip(STR_STATION_VIEW_DEPARTURES_BUTTON, STR_STATION_VIEW_DEPARTURES_TOOLTIP), EndContainer(), - NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SV_CLOSE_AIRPORT), SetMinimalSize(45, 12), SetResize(1, 0), SetFill(1, 1), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SV_CLOSE_AIRPORT), SetMinimalSize(36, 12), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_STATION_VIEW_CLOSE_AIRPORT, STR_STATION_VIEW_CLOSE_AIRPORT_TOOLTIP), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_TRAINS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_TRAIN, STR_STATION_VIEW_SCHEDULED_TRAINS_TOOLTIP), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_ROADVEHS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_LORRY, STR_STATION_VIEW_SCHEDULED_ROAD_VEHICLES_TOOLTIP), @@ -786,18 +794,145 @@ static void DrawCargoIcons(CargoID i, uint waiting, int left, int right, int y) struct CargoData { CargoID cargo; - StationID source; + union { + StationID station; + SourceID css; + }; uint count; + SourceType type; - CargoData(CargoID cargo, StationID source, uint count) : + CargoData(CargoID cargo, StationID station, uint count, SourceType type = ST_INDUSTRY) : cargo(cargo), - source(source), - count(count) + station(station), + count(count), + type(type) { } }; typedef std::list<CargoData> CargoDataList; +/** List of cargo for either one next hop or one destination. */ +struct CargoDestEntry { + typedef std::list<CargoDestEntry> List; + + /** Enum for type of stored data. */ + enum Type { + FINAL_DEST, ///< Data is the final destination. + NEXT_HOP, ///< Data is the next hop. + TRANSFER_HOP ///< Data is the transfer station. + }; + + List children; ///< Child entries of this entry. + CargoData data; ///< Stores the info for the current item. + Type type; ///< Type of the data stored in #entry. + uint16 start_row; ///< Row number of the header line. + bool expanded; ///< Is this entry expanded? + + CargoDestEntry(Type type, StationID station, uint count, SourceType st = ST_INDUSTRY) : + data(INVALID_CARGO, station, count, st), + type(type), + start_row(0), + expanded(false) + { } + + /** Zero out this entry and all child entries. */ + void Zero() + { + for (List::iterator i = this->children.begin(); i != this->children.end(); ++i ) { + i->Zero(); + } + this->data.count = 0; + this->start_row = 0; + } + + /** Remove all empty child entries. */ + void RemoveEmpty() + { + for (List::iterator i = this->children.begin(); i != this->children.end(); ) { + if (i->data.count > 0) { + i->RemoveEmpty(); + ++i; + } else { + i = this->children.erase(i); + } + } + } + + /** Update header row number. */ + int UpdateRowCount(int row) + { + this->start_row = ++row; + if (this->expanded) { + for (List::iterator i = this->children.begin(); i != this->children.end(); ++i) { + row = i->UpdateRowCount(row); + } + } + return row; + } +}; + +/** + * Get the next hop of a cargo packet. + * @param ge Station cargo info for the matching cargo type. + * @param cp The cargo packet. + * @return Station ID of the next hop or INVALID_STATION if not possible. + */ +static StationID GetNextHopStation(const GoodsEntry &ge, const CargoPacket *cp) +{ + StationID next = INVALID_STATION; + for (RouteLinkList::const_iterator i = ge.routes.begin(); i != ge.routes.end(); ++i) { + if ((*i)->GetOriginOrderId() == cp->NextHop()) { + next = (*i)->GetDestination(); + break; + } + } + return next; +} + +/** + * Add a cargo packet to a #CargoDestEntry list. + * @param list The list to add the packet to. + * @param type Which value to select as the entry info. + * @param cp The cargo packet. + * @param ge Where this cargo packets belongs to. + * @return Pointer to the added entry or NULL if the packet had no valid destination of the specified type. + */ +static CargoDestEntry *AddCargoPacketToList(CargoDestEntry::List &list, CargoDestEntry::Type type, const CargoPacket *cp, const GoodsEntry &ge) +{ + assert_compile(INVALID_STATION == INVALID_SOURCE); + + /* Extract the wanted sort type from the cargo packet. */ + uint16 sort_val; + switch (type) { + case CargoDestEntry::FINAL_DEST: + sort_val = cp->DestinationID(); + break; + case CargoDestEntry::NEXT_HOP: + sort_val = GetNextHopStation(ge, cp); + break; + case CargoDestEntry::TRANSFER_HOP: + sort_val = cp->NextStation(); + break; + default: + NOT_REACHED(); + } + + if (sort_val == INVALID_STATION) return NULL; + + /* Search for a matching child. */ + for (CargoDestEntry::List::iterator i = list.begin(); i != list.end(); ++i) { + if (type == CargoDestEntry::FINAL_DEST ? i->data.css == sort_val && i->data.type == cp->DestinationType() : i->data.station == sort_val) { + i->data.count += cp->Count(); + return &*i; + } + } + + /* No entry found, add new. */ + list.push_back(CargoDestEntry(type, sort_val, cp->Count(), cp->DestinationType())); + return &list.back(); +} + + /** * The StationView window */ @@ -808,6 +943,10 @@ struct StationViewWindow : public Window { int rating_lines; ///< Number of lines in the cargo ratings view. int accepts_lines; ///< Number of lines in the accepted cargo view. Scrollbar *vscroll; + CargoDestEntry::List cargodest_list[NUM_CARGO]; ///< List of cargoes sorted by destination. + + static StringID last_cargo_from_str; + static StringID last_cargo_from_tooltip; /** Height of the #WID_SV_ACCEPT_RATING_LIST widget for different views. */ enum AcceptListHeight { @@ -822,6 +961,7 @@ struct StationViewWindow : public Window { this->CreateNestedTree(desc); this->vscroll = this->GetScrollbar(WID_SV_SCROLLBAR); + this->GetWidget<NWidgetCore>(WID_SV_CARGO_FROM_TO_VIA)->SetDataTip(StationViewWindow::last_cargo_from_str, StationViewWindow::last_cargo_from_tooltip); /* Nested widget tree creation is done in two steps to ensure that this->GetWidget<NWidgetCore>(WID_SV_ACCEPTS_RATINGS) exists in UpdateWidgetSize(). */ this->FinishInitNested(desc, window_number); @@ -866,9 +1006,30 @@ struct StationViewWindow : public Window { { CargoDataList cargolist; uint32 transfers = 0; - this->OrderWaitingCargo(&cargolist, &transfers); - this->vscroll->SetCount((int)cargolist.size() + 1); // update scrollbar + NWidgetCore *cargo_btn = this->GetWidget<NWidgetCore>(WID_SV_CARGO_FROM_TO_VIA); + if (cargo_btn->widget_data == STR_STATION_VIEW_WAITING_TO_BUTTON) { + this->OrderWaitingCargo(&cargolist, &transfers); + this->vscroll->SetCount((int)cargolist.size() + 1); // update scrollbar + } else { + /* Determine the current view. */ + CargoDestEntry::Type dest_type; + switch (cargo_btn->widget_data) { + case STR_STATION_VIEW_WAITING_VIA_BUTTON: + dest_type = CargoDestEntry::FINAL_DEST; + break; + case STR_STATION_VIEW_WAITING_TRANSFER_BUTTON: + dest_type = CargoDestEntry::NEXT_HOP; + break; + case STR_STATION_VIEW_WAITING_BUTTON: + dest_type = CargoDestEntry::TRANSFER_HOP; + break; + default: + NOT_REACHED(); + } + int num = this->FillCargodestList(dest_type, this->cargodest_list); + this->vscroll->SetCount(num + 1); // update scrollbar + } /* disable some buttons */ const Station *st = Station::Get(this->window_number); @@ -905,7 +1066,11 @@ struct StationViewWindow : public Window { /* Draw waiting cargo. */ NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_SV_WAITING); Rect waiting_rect = {nwi->pos_x, nwi->pos_y, nwi->pos_x + nwi->current_x - 1, nwi->pos_y + nwi->current_y - 1}; - this->DrawWaitingCargo(waiting_rect, cargolist, transfers); + if (cargo_btn->widget_data == STR_STATION_VIEW_WAITING_TO_BUTTON) { + this->DrawWaitingCargo(waiting_rect, cargolist, transfers); + } else { + this->DrawWaitingCargoByDest(waiting_rect, this->cargodest_list); + } } } @@ -959,7 +1124,7 @@ struct StationViewWindow : public Window { /* Check if we already have this source in the list */ for (CargoDataList::iterator jt(cargolist->begin()); jt != cargolist->end(); jt++) { CargoData *cd = &(*jt); - if (cd->cargo == i && cd->source == cp->SourceStation()) { + if (cd->cargo == i && cd->station == cp->SourceStation()) { cd->count += cp->Count(); added = true; break; @@ -974,6 +1139,70 @@ struct StationViewWindow : public Window { } /** + * Fill cargo list sorted by type and destination/next hop. + * @param sort_via Set to true to sort by next hop, false to sort by final destination. + * @param list Cargo list to fill. + * @return Number of visible lines. + */ + int FillCargodestList(CargoDestEntry::Type sort_by, CargoDestEntry::List *list) + { + StationID station_id = this->window_number; + const Station *st = Station::Get(station_id); + + int lines = 0; + + /* Fill the list for each cargo type. */ + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + /* Zero out all existing items. */ + for (CargoDestEntry::List::iterator i = list[cid].begin(); i != list[cid].end(); ++i) { + i->Zero(); + } + + /* Remove all entries if no cargo of this type is present. */ + if (st->goods[cid].cargo.Empty()) { + this->cargo_rows[cid] = 0; + list[cid].clear(); + continue; + } + + /* Store line number of the header line. */ + this->cargo_rows[cid] = ++lines; + + /* Add each cargo packet to the list. */ + const StationCargoList::List *packets = st->goods[cid].cargo.Packets(); + for (StationCargoList::ConstIterator it = packets->begin(); it != packets->end(); ++it) { + const CargoPacket *cp = *it; + + /* Add entry and sub-entries according to the chosen sort type. */ + static const CargoDestEntry::Type sort_types[][3] = { + {CargoDestEntry::FINAL_DEST, CargoDestEntry::NEXT_HOP, CargoDestEntry::TRANSFER_HOP}, + {CargoDestEntry::NEXT_HOP, CargoDestEntry::TRANSFER_HOP, CargoDestEntry::FINAL_DEST}, + {CargoDestEntry::TRANSFER_HOP, CargoDestEntry::NEXT_HOP, CargoDestEntry::FINAL_DEST} + }; + + CargoDestEntry *entry = AddCargoPacketToList(list[cid], sort_types[sort_by][0], cp, st->goods[cid]); + if (entry != NULL) { + entry = AddCargoPacketToList(entry->children, sort_types[sort_by][1], cp, st->goods[cid]); + if (entry != NULL) AddCargoPacketToList(entry->children, sort_types[sort_by][2], cp, st->goods[cid]); + } + } + + /* Remove all empty list items and update visible row numbers. */ + for (CargoDestEntry::List::iterator i = list[cid].begin(); i != list[cid].end(); ) { + if (i->data.count > 0) { + i->RemoveEmpty(); + if (HasBit(this->cargo, cid)) lines = i->UpdateRowCount(lines); + ++i; + } else { + i = list[cid].erase(i); + } + } + } + + return lines; + } + + /** * Draw waiting cargo. * @param r Rectangle of the widget. * @param cargolist Cargo, ordered by type and destination. @@ -1006,7 +1235,7 @@ struct StationViewWindow : public Window { for (CargoDataList::const_iterator it = cargolist.begin(); it != cargolist.end() && pos > -maxrows; ++it) { if (--pos < 0) { const CargoData *cd = &(*it); - if (cd->source == INVALID_STATION) { + if (cd->station == INVALID_STATION) { /* Heading */ DrawCargoIcons(cd->cargo, cd->count, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y); SetDParam(0, cd->cargo); @@ -1022,7 +1251,7 @@ struct StationViewWindow : public Window { } else { SetDParam(0, cd->cargo); SetDParam(1, cd->count); - SetDParam(2, cd->source); + SetDParam(2, cd->station); DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_STATION_VIEW_EN_ROUTE_FROM, TC_FROMSTRING, SA_RIGHT); } @@ -1032,6 +1261,111 @@ struct StationViewWindow : public Window { } /** + * Draw a dest entry and its children. + * @param cid Current cargo type. + * @param pos Scroll position + * @param maxrows Number of visible rows. + * @param left Left string bound. + * @param right Right string bound. + * @param shrink_left Left bound of the expand marker. + * @param shrink_right Right bound of the expand marker. + * @param offs_left Child offset of the left bound. + * @param offs_right Child offset of the right bound. + * @param y Top of the current line. + * @param entry The entry to draw. + * @return The new y value. + */ + int DrawSingleDestEntry(CargoID cid, int *pos, int maxrows, int left, int right, int shrink_left, int shrink_right, int offs_left, int offs_right, int y, const CargoDestEntry &entry) const + { + if (--(*pos) < 0) { + /* Draw current line. */ + StringID str; + + SetDParam(0, cid); + SetDParam(1, entry.data.count); + if (entry.type == CargoDestEntry::FINAL_DEST) { + SetDParam(2, entry.data.type == ST_INDUSTRY ? STR_INDUSTRY_NAME : (entry.data.type == ST_TOWN ? STR_TOWN_NAME : STR_COMPANY_NAME)); + SetDParam(3, entry.data.css); + str = STR_STATION_VIEW_WAITING_TO; + } else { + SetDParam(2, entry.data.station); + str = (entry.type == CargoDestEntry::NEXT_HOP) ? STR_STATION_VIEW_WAITING_VIA : STR_STATION_VIEW_WAITING_TRANSFER; + } + DrawString(left, right, y, str); + y += FONT_HEIGHT_NORMAL; + + if (!entry.children.empty()) { + /* Draw expand/collapse marker. */ + DrawString(shrink_left, shrink_right, y - FONT_HEIGHT_NORMAL, entry.expanded ? "-" : "+", TC_YELLOW, SA_RIGHT); + + if (entry.expanded) { + /* Draw visible children. */ + for (CargoDestEntry::List::const_iterator i = entry.children.begin(); i != entry.children.end() && *pos > -maxrows; ++i) { + y = this->DrawSingleDestEntry(cid, pos, maxrows, left + offs_left, right + offs_right, shrink_left, shrink_right, offs_left, offs_right, y, *i); + } + } + } + } + + return y; + } + + /** + * Draw waiting cargo ordered by destination/next hop. + * @param r Rectangle of the widget. + * @param list List to draw. + */ + void DrawWaitingCargoByDest(const Rect &r, const CargoDestEntry::List *list) const + { + int y = r.top + WD_FRAMERECT_TOP; + int pos = this->vscroll->GetPosition(); + + const Station *st = Station::Get(this->window_number); + if (--pos < 0) { + StringID str = STR_JUST_NOTHING; + for (CargoID i = 0; i < NUM_CARGO; i++) { + if (!st->goods[i].cargo.Empty()) str = STR_EMPTY; + } + SetDParam(0, str); + DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_STATION_VIEW_WAITING_TITLE); + y += FONT_HEIGHT_NORMAL; + } + + bool rtl = _current_text_dir == TD_RTL; + int text_left = rtl ? r.left + this->expand_shrink_width : r.left + WD_FRAMERECT_LEFT; + int text_right = rtl ? r.right - WD_FRAMERECT_LEFT : r.right - this->expand_shrink_width; + int shrink_left = rtl ? r.left + WD_FRAMERECT_LEFT : r.right - this->expand_shrink_width + WD_FRAMERECT_LEFT; + int shrink_right = rtl ? r.left + this->expand_shrink_width - WD_FRAMERECT_RIGHT : r.right - WD_FRAMERECT_RIGHT; + + int offs_left = rtl ? 0 : this->expand_shrink_width; + int offs_right = rtl ? this->expand_shrink_width : 0; + + int maxrows = this->vscroll->GetCapacity(); + for (CargoID cid = 0; cid < NUM_CARGO && pos > -maxrows; cid++) { + if (st->goods[cid].cargo.Empty()) continue; + + if (--pos < 0) { + /* Draw heading. */ + DrawCargoIcons(cid, st->goods[cid].cargo.Count(), r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMERECT_RIGHT, y); + SetDParam(0, cid); + SetDParam(1, st->goods[cid].cargo.Count()); + DrawString(text_left, text_right, y, STR_STATION_VIEW_WAITING_CARGO, TC_FROMSTRING, SA_RIGHT); + if (!list[cid].empty()) { + DrawString(shrink_left, shrink_right, y, HasBit(this->cargo, cid) ? "-" : "+", TC_YELLOW, SA_RIGHT); + } + y += FONT_HEIGHT_NORMAL; + } + + /* Draw sub-entries. */ + if (HasBit(this->cargo, cid)) { + for (CargoDestEntry::List::const_iterator i = list[cid].begin(); i != list[cid].end() && pos > -maxrows; ++i) { + y = this->DrawSingleDestEntry(cid, &pos, maxrows, text_left + offs_left, text_right + offs_right, shrink_left, shrink_right, offs_left, offs_right, y, *i); + } + } + } + } + + /** * Draw accepted cargo in the #WID_SV_ACCEPT_RATING_LIST widget. * @param r Rectangle of the widget. * @return Number of lines needed for drawing the accepted cargo. @@ -1082,16 +1416,93 @@ struct StationViewWindow : public Window { return CeilDiv(y - r.top - WD_FRAMERECT_TOP, FONT_HEIGHT_NORMAL); } + /** + * Test and handle a possible mouse click on a dest entry and its children. + * @param entry The entry to test for a hit. + * @param row The number of the clicked row. + * @return True if further entries need to be processed. + */ + bool HandleCargoDestEntryClick(CargoDestEntry &entry, int row) + { + if (entry.start_row == row) { + if (_ctrl_pressed) { + /* Scroll viewport to destination tile .*/ + TileIndex dest_tile = 0; + switch (entry.type) { + case CargoDestEntry::FINAL_DEST: + switch (entry.data.type) { + case ST_INDUSTRY: + dest_tile = Industry::Get(entry.data.css)->location.tile; + break; + case ST_TOWN: + dest_tile = Town::Get(entry.data.css)->xy; + break; + case ST_HEADQUARTERS: + dest_tile = Company::Get(entry.data.css)->location_of_HQ; + break; + + default: + NOT_REACHED(); + } + break; + + case CargoDestEntry::NEXT_HOP: + case CargoDestEntry::TRANSFER_HOP: + dest_tile = Station::Get(entry.data.station)->xy; + break; + + default: + NOT_REACHED(); + } + ScrollMainWindowToTile(dest_tile); + } else if (!entry.children.empty()) { + /* Expand/collapse entry. */ + entry.expanded = !entry.expanded; + this->SetWidgetDirty(WID_SV_WAITING); + this->SetWidgetDirty(WID_SV_SCROLLBAR); + } + } + + if (entry.start_row < row) { + /* Test child entries. */ + for (CargoDestEntry::List::iterator i = entry.children.begin(); i != entry.children.end(); ++i) { + if (!this->HandleCargoDestEntryClick(*i, row)) return false; + } + return true; + } + + return false; + } + void HandleCargoWaitingClick(int row) { if (row == 0) return; + bool dest_view = this->GetWidget<NWidgetCore>(WID_SV_CARGO_FROM_TO_VIA)->widget_data != STR_STATION_VIEW_WAITING_TO_BUTTON; + for (CargoID c = 0; c < NUM_CARGO; c++) { + /* Test for cargo type line. */ if (this->cargo_rows[c] == row) { ToggleBit(this->cargo, c); this->SetWidgetDirty(WID_SV_WAITING); + this->SetWidgetDirty(WID_SV_SCROLLBAR); break; } + + if (dest_view) { + /* Test for dest view lines. */ + for (CargoDestEntry::List::iterator i = this->cargodest_list[c].begin(); i != this->cargodest_list[c].end(); ++i) { + if (!this->HandleCargoDestEntryClick(*i, row)) break; + } + } + } + } + + /** Clear the 'cargo by destination' list. */ + void ClearCargodestList() + { + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + this->cargodest_list[cid].clear(); } } @@ -1125,6 +1536,38 @@ struct StationViewWindow : public Window { break; } + case WID_SV_CARGO_FROM_TO_VIA: { + /* Swap between 'Source', 'Destination', 'Next hop' and 'Transfer' view. + * Store the new view so the next opened station window shows the same view. */ + NWidgetCore *nwi = this->GetWidget<NWidgetCore>(WID_SV_CARGO_FROM_TO_VIA); + switch (nwi->widget_data) { + case STR_STATION_VIEW_WAITING_BUTTON: + StationViewWindow::last_cargo_from_str = STR_STATION_VIEW_WAITING_TO_BUTTON; + StationViewWindow::last_cargo_from_tooltip = STR_STATION_VIEW_WAITING_TO_TOOLTIP; + break; + case STR_STATION_VIEW_WAITING_TO_BUTTON: + StationViewWindow::last_cargo_from_str = STR_STATION_VIEW_WAITING_VIA_BUTTON; + StationViewWindow::last_cargo_from_tooltip = STR_STATION_VIEW_WAITING_VIA_TOOLTIP; + break; + case STR_STATION_VIEW_WAITING_VIA_BUTTON: + StationViewWindow::last_cargo_from_str = STR_STATION_VIEW_WAITING_TRANSFER_BUTTON; + StationViewWindow::last_cargo_from_tooltip = STR_STATION_VIEW_WAITING_TRANSFER_TOOLTIP; + break; + case STR_STATION_VIEW_WAITING_TRANSFER_BUTTON: + StationViewWindow::last_cargo_from_str = STR_STATION_VIEW_WAITING_BUTTON; + StationViewWindow::last_cargo_from_tooltip = STR_STATION_VIEW_WAITING_TOOLTIP; + break; + default: + NOT_REACHED(); + } + nwi->SetDataTip(StationViewWindow::last_cargo_from_str, StationViewWindow::last_cargo_from_tooltip); + this->ClearCargodestList(); + this->SetWidgetDirty(WID_SV_CARGO_FROM_TO_VIA); + this->SetWidgetDirty(WID_SV_WAITING); + this->SetWidgetDirty(WID_SV_SCROLLBAR); + break; + } + case WID_SV_RENAME: SetDParam(0, this->window_number); ShowQueryString(STR_STATION_NAME, STR_STATION_VIEW_RENAME_STATION_CAPTION, MAX_LENGTH_STATION_NAME_CHARS, @@ -1142,7 +1585,11 @@ struct StationViewWindow : public Window { Owner owner = Station::Get(this->window_number)->owner; ShowVehicleListWindow(owner, (VehicleType)(widget - WID_SV_TRAINS), (StationID)this->window_number); break; - } + } + + case WID_SV_DEPARTURES: + ShowStationDepartures((StationID)this->window_number); + break; } } @@ -1169,6 +1616,8 @@ struct StationViewWindow : public Window { } }; +StringID StationViewWindow::last_cargo_from_str = STR_STATION_VIEW_WAITING_VIA_BUTTON; +StringID StationViewWindow::last_cargo_from_tooltip = STR_STATION_VIEW_WAITING_VIA_TOOLTIP; static const WindowDesc _station_view_desc( WDP_AUTO, 249, 110, @@ -1254,8 +1703,15 @@ static const T *FindStationsNearby(TileArea ta, bool distant_join) _deleted_stations_nearby.Clear(); /* Check the inside, to return, if we sit on another station */ - TILE_AREA_LOOP(t, ta) { - if (t < MapSize() && IsTileType(t, MP_STATION) && T::IsValidID(GetStationIndex(t))) return T::GetByTile(t); + FOR_ALL_LAYERS(layer) { + TILE_AREA_LOOP(tile, ta) { + TileIndex t = TopTile(tile) + layer * LayerSize(); + if (t < MapSize() && IsTileType(t, MP_STATION) && T::IsValidID(GetStationIndex(t))) + { + if (t == tile) return T::GetByTile(t); + AddNearbyStation<T>(t, &ctx); + } + } } /* Look for deleted stations */ @@ -1263,14 +1719,14 @@ static const T *FindStationsNearby(TileArea ta, bool distant_join) FOR_ALL_BASE_STATIONS(st) { if (T::IsExpected(st) && !st->IsInUse() && st->owner == _local_company) { /* Include only within station spread (yes, it is strictly less than) */ - if (max(DistanceMax(ta.tile, st->xy), DistanceMax(TILE_ADDXY(ta.tile, ta.w - 1, ta.h - 1), st->xy)) < _settings_game.station.station_spread) { + if (max(DistanceMax(TopTile(ta.tile), TopTile(st->xy)), DistanceMax(TILE_ADDXY(TopTile(ta.tile), ta.w - 1, ta.h - 1), TopTile(st->xy))) < _settings_game.station.station_spread) { TileAndStation *ts = _deleted_stations_nearby.Append(); ts->tile = st->xy; ts->station = st->index; /* Add the station when it's within where we're going to build */ - if (IsInsideBS(TileX(st->xy), TileX(ctx.tile), ctx.w) && - IsInsideBS(TileY(st->xy), TileY(ctx.tile), ctx.h)) { + if (IsInsideBS(LayerX(st->xy), LayerX(ctx.tile), ctx.w) && + IsInsideBS(LayerY(st->xy), LayerY(ctx.tile), ctx.h)) { AddNearbyStation<T>(st->xy, &ctx); } } @@ -1283,8 +1739,11 @@ static const T *FindStationsNearby(TileArea ta, bool distant_join) if (distant_join && min(ta.w, ta.h) >= _settings_game.station.station_spread) return NULL; uint max_dist = distant_join ? _settings_game.station.station_spread - min(ta.w, ta.h) : 1; - TileIndex tile = TILE_ADD(ctx.tile, TileOffsByDir(DIR_N)); - CircularTileSearch(&tile, max_dist, ta.w, ta.h, AddNearbyStation<T>, &ctx); + FOR_ALL_LAYERS(layer) { + ctx.tile = TopTile(ctx.tile) + layer * LayerSize(); + TileIndex tile = TILE_ADD(ctx.tile, TileOffsByDir(DIR_N)); + CircularTileSearch(&tile, max_dist, ta.w, ta.h, AddNearbyStation<T>, &ctx); + } return NULL; } diff --git a/src/statusbar_gui.cpp b/src/statusbar_gui.cpp index f8d39ceaf..7ac28b5eb 100644 --- a/src/statusbar_gui.cpp +++ b/src/statusbar_gui.cpp @@ -131,8 +131,8 @@ struct StatusBarWindow : Window { switch (widget) { case WID_S_LEFT: /* Draw the date */ - SetDParam(0, _date); - DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, STR_WHITE_DATE_LONG, TC_FROMSTRING, SA_HOR_CENTER); + SetDParam(0, ((DateTicks)_date * DAY_TICKS) + _date_fract); + DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, STR_WHITE_DATE_WALLCLOCK_LONG, TC_FROMSTRING, SA_HOR_CENTER); break; case WID_S_RIGHT: { @@ -213,6 +213,10 @@ struct StatusBarWindow : Window { { if (_pause_mode != PM_UNPAUSED) return; + if (_settings_client.gui.time_in_minutes) { + this->SetWidgetDirty(WID_S_LEFT); + } + if (this->ticker_scroll < TICKER_STOP) { // Scrolling text this->ticker_scroll += COUNTER_STEP; this->SetWidgetDirty(WID_S_MIDDLE); @@ -229,7 +233,7 @@ struct StatusBarWindow : Window { static const NWidgetPart _nested_main_status_widgets[] = { NWidget(NWID_HORIZONTAL), - NWidget(WWT_PANEL, COLOUR_GREY, WID_S_LEFT), SetMinimalSize(140, 12), EndContainer(), + NWidget(WWT_PANEL, COLOUR_GREY, WID_S_LEFT), SetMinimalSize(160, 12), EndContainer(), NWidget(WWT_PUSHBTN, COLOUR_GREY, WID_S_MIDDLE), SetMinimalSize(40, 12), SetDataTip(0x0, STR_STATUSBAR_TOOLTIP_SHOW_LAST_NEWS), SetResize(1, 0), NWidget(WWT_PUSHBTN, COLOUR_GREY, WID_S_RIGHT), SetMinimalSize(140, 12), EndContainer(), diff --git a/src/strings.cpp b/src/strings.cpp index 94de57f6f..8f69a2cc5 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -413,6 +413,29 @@ static char *FormatBytes(char *buff, int64 number, const char *last) return buff; } +static char *FormatWallClockString(char *buff, DateTicks ticks, const char *last, bool show_date, uint case_index) +{ + Minutes minutes = ticks / _settings_client.gui.ticks_per_minute + _settings_client.gui.clock_offset; + char hour[3], minute[3]; + snprintf(hour, lengthof(hour), "%02i", MINUTES_HOUR(minutes) ); + snprintf(minute, lengthof(minute), "%02i", MINUTES_MINUTE(minutes)); + if (show_date) { + int64 args[3] = { (int64)hour, (int64)minute, (int64)ticks / DAY_TICKS }; + if (_settings_client.gui.date_with_time == 1) { + YearMonthDay ymd; + ConvertDateToYMD(args[2], &ymd); + args[2] = ymd.year; + } + + StringParameters tmp_params(args); + return FormatString(buff, GetStringPtr(STR_FORMAT_DATE_MINUTES + _settings_client.gui.date_with_time), &tmp_params, last, case_index); + } else { + int64 args[2] = { (int64)hour, (int64)minute }; + StringParameters tmp_params(args); + return FormatString(buff, GetStringPtr(STR_FORMAT_DATE_MINUTES), &tmp_params, last, case_index); + } +} + static char *FormatYmdString(char *buff, Date date, const char *last, uint case_index) { YearMonthDay ymd; @@ -1215,6 +1238,42 @@ static char *FormatString(char *buff, const char *str_arg, StringParameters *arg next_substr_case_index = 0; break; + case SCC_DATE_WALLCLOCK_LONG: { // {DATE_WALLCLOCK_LONG} + if (_settings_client.gui.time_in_minutes) { + buff = FormatWallClockString(buff, args->GetInt64(SCC_DATE_WALLCLOCK_LONG), last, _settings_client.gui.date_with_time, next_substr_case_index); + } else { + buff = FormatYmdString(buff, args->GetInt64(SCC_DATE_WALLCLOCK_LONG) / DAY_TICKS, last, next_substr_case_index); + } + break; + } + + case SCC_DATE_WALLCLOCK_SHORT: { // {DATE_WALLCLOCK_SHORT} + if (_settings_client.gui.time_in_minutes) { + buff = FormatWallClockString(buff, args->GetInt64(SCC_DATE_WALLCLOCK_SHORT), last, _settings_client.gui.date_with_time, next_substr_case_index); + } else { + buff = FormatYmdString(buff, args->GetInt64(SCC_DATE_WALLCLOCK_SHORT) / DAY_TICKS, last, next_substr_case_index); + } + break; + } + + case SCC_DATE_WALLCLOCK_TINY: { // {DATE_WALLCLOCK_TINY} + if (_settings_client.gui.time_in_minutes) { + buff = FormatWallClockString(buff, args->GetInt64(SCC_DATE_WALLCLOCK_TINY), last, false, next_substr_case_index); + } else { + buff = FormatTinyOrISODate(buff, args->GetInt64(SCC_DATE_WALLCLOCK_TINY) / DAY_TICKS, STR_FORMAT_DATE_TINY, last); + } + break; + } + + case SCC_DATE_WALLCLOCK_ISO: { // {DATE_WALLCLOCK_ISO} + if (_settings_client.gui.time_in_minutes) { + buff = FormatWallClockString(buff, args->GetInt64(SCC_DATE_WALLCLOCK_ISO), last, false, next_substr_case_index); + } else { + buff = FormatTinyOrISODate(buff, args->GetInt64(SCC_DATE_WALLCLOCK_ISO) / DAY_TICKS, STR_FORMAT_DATE_ISO, last); + } + break; + } + case SCC_DATE_ISO: // {DATE_ISO} buff = FormatTinyOrISODate(buff, args->GetInt32(), STR_FORMAT_DATE_ISO, last); break; @@ -1825,6 +1884,7 @@ bool ReadLanguagePack(const LanguageMetadata *lang) /* Some lists need to be sorted again after a language change. */ ReconsiderGameScriptLanguage(); InitializeSortedCargoSpecs(); + BuildCargoTypesLegend(); SortIndustryTypes(); BuildIndustriesLegend(); SortNetworkLanguages(); diff --git a/src/subsidy.cpp b/src/subsidy.cpp index c69b2b23d..eb055f4b9 100644 --- a/src/subsidy.cpp +++ b/src/subsidy.cpp @@ -24,6 +24,7 @@ #include "core/random_func.hpp" #include "game/game.hpp" #include "command_func.h" +#include "cargodest_func.h" #include "table/strings.h" @@ -296,13 +297,22 @@ bool FindSubsidyPassengerRoute() { if (!Subsidy::CanAllocateItem()) return false; - const Town *src = Town::GetRandom(); + Town *src = Town::GetRandom(); if (src->cache.population < SUBSIDY_PAX_MIN_POPULATION || src->GetPercentTransported(CT_PASSENGERS) > SUBSIDY_MAX_PCT_TRANSPORTED) { return false; } - const Town *dst = Town::GetRandom(); + const Town *dst = NULL; + if (CargoHasDestinations(CT_PASSENGERS)) { + /* Try to get a town from the demand destinations. */ + CargoLink *link = src->GetRandomLink(CT_PASSENGERS, false); + if (link == src->cargo_links[CT_PASSENGERS].End()) return NULL; + if (link->dest != NULL && link->dest->GetType() != ST_TOWN) return NULL; + dst = static_cast<const Town *>(link->dest); + } + if (dst == NULL) dst = Town::GetRandom(); + if (dst->cache.population < SUBSIDY_PAX_MIN_POPULATION || src == dst) { return false; } @@ -408,11 +418,22 @@ bool FindSubsidyCargoDestination(CargoID cid, SourceType src_type, SourceID src) /* Choose a random destination. Only consider towns if they can accept the cargo. */ SourceType dst_type = (HasBit(_town_cargoes_accepted, cid) && Chance16(1, 2)) ? ST_TOWN : ST_INDUSTRY; + CargoSourceSink *src_sink = (src_type == ST_TOWN) ? (CargoSourceSink *) Town::Get(src) : (CargoSourceSink *) Industry::Get(src); + SourceID dst; switch (dst_type) { case ST_TOWN: { /* Select a random town. */ - const Town *dst_town = Town::GetRandom(); + const Town *dst_town = NULL; + + if (CargoHasDestinations(cid)) { + /* Try to get a town from the demand destinations. */ + CargoLink *link = src_sink->GetRandomLink(cid, false); + if (link == src_sink->cargo_links[cid].End()) return NULL; + if (link->dest != NULL && link->dest->GetType() != dst_type) return NULL; + dst_town = static_cast<const Town *>(link->dest); + } + if (dst_town == NULL) dst_town = Town::GetRandom(); /* Check if the town can accept this cargo. */ if (!HasBit(dst_town->cargo_accepted_total, cid)) return false; @@ -425,15 +446,24 @@ bool FindSubsidyCargoDestination(CargoID cid, SourceType src_type, SourceID src) /* Select a random industry. */ const Industry *dst_ind = Industry::GetRandom(); + if (CargoHasDestinations(cid)) { + /* Try to get a town from the demand destinations. */ + CargoLink *link = src_sink->GetRandomLink(cid, false); + if (link == src_sink->cargo_links[cid].End()) return NULL; + if (link->dest != NULL && link->dest->GetType() != dst_type) return NULL; + dst_ind = static_cast<const Industry *>(link->dest); + } + if (dst_ind == NULL) dst_ind = Industry::GetRandom(); + + dst = dst_ind->index; + /* The industry must accept the cargo */ - if (dst_ind == NULL || + if (dst_ind == NULL || (src_type == dst_type && src == dst) || (cid != dst_ind->accepts_cargo[0] && cid != dst_ind->accepts_cargo[1] && cid != dst_ind->accepts_cargo[2])) { return false; } - - dst = dst_ind->index; break; } diff --git a/src/table/control_codes.h b/src/table/control_codes.h index 50233d5f6..5ab5331f9 100644 --- a/src/table/control_codes.h +++ b/src/table/control_codes.h @@ -68,6 +68,10 @@ enum StringControlCode { SCC_DATE_SHORT, SCC_DATE_LONG, SCC_DATE_ISO, + SCC_DATE_WALLCLOCK_TINY, + SCC_DATE_WALLCLOCK_SHORT, + SCC_DATE_WALLCLOCK_LONG, + SCC_DATE_WALLCLOCK_ISO, /* Must be consecutive */ SCC_STRING1, diff --git a/src/table/settings.ini b/src/table/settings.ini index b39d29a54..897cb5a5d 100644 --- a/src/table/settings.ini +++ b/src/table/settings.ini @@ -40,6 +40,8 @@ static bool RedrawTownAuthority(int32 p1); static bool InvalidateCompanyInfrastructureWindow(int32 p1); static bool InvalidateCompanyWindow(int32 p1); static bool ZoomMinMaxChanged(int32 p1); +static bool TLSettingChanged(int32 p1); +bool CargodestModeChanged(int32 p1); #ifdef ENABLE_NETWORK static bool UpdateClientName(int32 p1); @@ -70,6 +72,7 @@ SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $guiflags, $def, SDTC_STR = SDTC_STR( $var, $type, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat), SDTC_VAR = SDTC_VAR( $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat), SDT_BOOL = SDT_BOOL($base, $var, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat), +SDT_LIST = SDT_LIST($base, $var, $type, $flags, $guiflags, "$def", $str, $strhelp, $strval, $proc, $from, $to, $cat), SDT_OMANY = SDT_OMANY($base, $var, $type, $flags, $guiflags, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $load, $cat), SDT_STR = SDT_STR($base, $var, $type, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat), SDT_VAR = SDT_VAR($base, $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat), @@ -553,6 +556,18 @@ strhelp = STR_CONFIG_SETTING_ALLOW_TOWN_ROADS_HELPTEXT [SDT_VAR] base = GameSettings +var = economy.random_road_construction +type = SLE_UINT8 +from = TL_SV +def = 7 +min = 0 +max = 100 +interval = 1 +str = STR_CONFIG_PATCHES_RANDOM_ROAD_CONSTRUCTION +strval = STR_JUST_INT + +[SDT_VAR] +base = GameSettings var = economy.found_town type = SLE_UINT8 from = 128 @@ -968,6 +983,13 @@ cat = SC_BASIC length = 1 to = 158 +[SDT_BOOL] +base = GameSettings +var = order.automatic_timetable_separation +from = SL_TTSEP_VER +def = true +str = STR_CONFIG_SETTING_TIMETABLE_ENABLE_SEPARATION + [SDTC_BOOL] var = gui.sg_full_load_any from = 22 @@ -1292,6 +1314,242 @@ str = STR_CONFIG_SETTING_MODIFIED_ROAD_REBUILD strhelp = STR_CONFIG_SETTING_MODIFIED_ROAD_REBUILD_HELPTEXT cat = SC_EXPERT +[SDT_VAR] +base = GameSettings +var = economy.cargodest.mode_pax_mail +type = SLE_UINT8 +from = 180 +guiflags = SGF_MULTISTRING +def = 0 +min = 0 +max = 1 +interval = 1 +str = STR_CONFIG_SETTING_CARGODEST_PAX +strhelp = STR_CONFIG_SETTING_CARGODEST_PAX_HELPTEXT +strval = STR_CONFIG_SETTING_CARGODEST_MODE_OFF +proc = CargodestModeChanged +cat = SC_BASIC + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.mode_town_cargo +type = SLE_UINT8 +from = 180 +guiflags = SGF_MULTISTRING +def = 0 +min = 0 +max = 1 +interval = 1 +str = STR_CONFIG_SETTING_CARGODEST_TOWN +strhelp = STR_CONFIG_SETTING_CARGODEST_TOWN_HELPTEXT +strval = STR_CONFIG_SETTING_CARGODEST_MODE_OFF +proc = CargodestModeChanged +cat = SC_BASIC + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.mode_others +type = SLE_UINT8 +from = 180 +guiflags = SGF_MULTISTRING +def = 0 +min = 0 +max = 1 +interval = 1 +str = STR_CONFIG_SETTING_CARGODEST_OTHER +strhelp = STR_CONFIG_SETTING_CARGODEST_OTHER_HELPTEXT +strval = STR_CONFIG_SETTING_CARGODEST_MODE_OFF +proc = CargodestModeChanged +cat = SC_BASIC + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.base_town_links +type = SLE_UINT8 +from = 180 +def = 3,3 +cat = SC_EXPERT + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.base_ind_links +type = SLE_UINT8 +from = 180 +def = 2,4,1 +cat = SC_EXPERT + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.city_town_links +type = SLE_UINT8 +from = 180 +def = 8 +min = 0 +max = 255 +cat = SC_EXPERT + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.town_chances_town +type = SLE_UINT8 +from = 180 +def = 100,100,100,100 +cat = SC_EXPERT + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.town_chances_city +type = SLE_UINT8 +from = 180 +def = 70,100,100,100 +cat = SC_EXPERT + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.ind_chances +type = SLE_UINT8 +from = 180 +def = 90,95,100 +cat = SC_EXPERT + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.random_dest_chance +type = SLE_UINT8 +from = 180 +def = 5 +min = 0 +max = 99 +cat = SC_EXPERT + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.big_town_pop +type = SLE_UINT32 +from = 180 +def = 500,2000 +cat = SC_EXPERT + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.pop_scale_town +type = SLE_UINT16 +from = 180 +def = 100,180,200,1000 +cat = SC_EXPERT + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.cargo_scale_ind +type = SLE_UINT16 +from = 180 +def = 250,200 +cat = SC_EXPERT + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.min_weight_town +type = SLE_UINT16 +from = 180 +def = 5,5 +cat = SC_EXPERT + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.min_weight_ind +type = SLE_UINT16 +from = 180 +def = 10 +min = 0 +max = 1000 +cat = SC_EXPERT + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.weight_scale_town +type = SLE_UINT16 +from = 180 +def = 10,40,20,80 +cat = SC_EXPERT + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.weight_scale_ind +type = SLE_UINT16 +from = 180 +def = 20,50 +cat = SC_EXPERT + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.town_nearby_dist +type = SLE_UINT32 +from = 180 +def = 48*48 +min = 1 +max = UINT32_MAX +cat = SC_EXPERT + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.ind_nearby_dist +type = SLE_UINT32 +from = 180 +def = 64*64 +min = 1 +max = UINT32_MAX +cat = SC_EXPERT + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.max_route_age +type = SLE_UINT16 +from = 180 +def = 2*DAYS_IN_YEAR +min = 0 +max = UINT16_MAX +cat = SC_EXPERT + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.route_recalc_delay +type = SLE_UINT16 +from = 180 +def = 20 +min = 0 +max = UINT16_MAX +cat = SC_EXPERT + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.route_recalc_chunk +type = SLE_UINT16 +from = 180 +def = 15 +min = 0 +max = UINT16_MAX +cat = SC_EXPERT + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.max_route_penalty[0] +type = SLE_UINT16 +from = 180 +def = 200 +min = 0 +max = UINT16_MAX +cat = SC_EXPERT + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.max_route_penalty[1] +type = SLE_UINT16 +from = 180 +def = 150 +min = 0 +max = UINT16_MAX +cat = SC_EXPERT + ; previously ai-new setting. [SDT_NULL] length = 1 @@ -1363,6 +1621,68 @@ strhelp = STR_CONFIG_SETTING_SCRIPT_MAX_OPCODES_HELPTEXT strval = STR_JUST_COMMA cat = SC_EXPERT +[SDT_BOOL] +base = GameSettings +var = construction.traffic_lights +from = TL_SV +def = true +str = STR_CONFIG_SETTING_TRAFFIC_LIGHTS +strhelp = STR_CONFIG_SETTING_TRAFFIC_LIGHTS_HELPTEXT +proc = TLSettingChanged + +[SDT_BOOL] +base = GameSettings +var = construction.towns_build_traffic_lights +from = TL_SV +def = true +str = STR_CONFIG_SETTING_TOWNS_BUILD_TRAFFIC_LIGHTS +strhelp = STR_CONFIG_SETTING_TOWNS_BUILD_TRAFFIC_LIGHTS_HELPTEXT + +[SDT_BOOL] +base = GameSettings +var = construction.allow_building_tls_in_towns +from = TL_SV +def = false +str = STR_CONFIG_SETTING_ALLOW_BUILDING_TLS_ON_TOWN_ROADS +strhelp = STR_CONFIG_SETTING_ALLOW_BUILDING_TLS_ON_TOWN_ROADS_HELPTEXT + +[SDT_VAR] +base = GameSettings +var = construction.traffic_lights_green_phase +type = SLE_UINT8 +from = TL_SV +def = 2 +min = 1 +max = 16 +str = STR_CONFIG_SETTING_TRAFFIC_LIGHTS_GREEN_PHASE +strhelp = STR_CONFIG_SETTING_TRAFFIC_LIGHTS_GREEN_PHASE_HELPTEXT +strval = STR_JUST_INT + +[SDT_VAR] +base = GameSettings +var = construction.max_tlc_size +type = SLE_UINT8 +from = TL_SV +guiflags = SGF_0ISDISABLED +def = 4 +min = 1 +max = 32 +str = STR_CONFIG_SETTING_MAX_TRAFFIC_LIGHT_CONSIST_SIZE +strhelp = STR_CONFIG_SETTING_MAX_TRAFFIC_LIGHT_CONSIST_SIZE_HELPTEXT +strval = STR_JUST_INT + +[SDT_VAR] +base = GameSettings +var = construction.max_tlc_distance +type = SLE_UINT8 +from = TL_SV +def = 1 +min = 0 +max = 4 +str = STR_CONFIG_SETTING_MAX_TRAFFIC_LIGHT_CONSIST_DISTANCE +strhelp = STR_CONFIG_SETTING_MAX_TRAFFIC_LIGHT_CONSIST_DISTANCE_HELPTEXT +strval = STR_CONFIG_SETTING_TILE_LENGTH + ## [SDT_VAR] base = GameSettings @@ -1636,6 +1956,16 @@ cat = SC_EXPERT [SDT_VAR] base = GameSettings +var = pf.npf.npf_road_trafficlight_penalty +type = SLE_UINT +from = TL_SV +def = 2 * NPF_TILE_LENGTH +min = 0 +max = 100000 +str = STR_NULL + +[SDT_VAR] +base = GameSettings var = pf.npf.npf_road_dt_occupied_penalty type = SLE_UINT from = 130 @@ -1682,6 +2012,15 @@ min = 500 max = 1000000 cat = SC_EXPERT +[SDT_VAR] +base = GameSettings +var = game_creation.layers +type = SLE_UINT8 +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +def = 1 +min = MIN_LAYER_COUNT_BITS +max = MAX_LAYER_COUNT_BITS + [SDT_BOOL] base = GameSettings var = pf.yapf.rail_firstred_twoway_eol @@ -1941,6 +2280,15 @@ cat = SC_EXPERT [SDT_VAR] base = GameSettings +var = pf.yapf.road_trafficlight_penalty +type = SLE_UINT +from = TL_SV +def = 2 * YAPF_TILE_LENGTH +min = 0 +max = 1000000 + +[SDT_VAR] +base = GameSettings var = pf.yapf.road_stop_penalty type = SLE_UINT from = 47 @@ -1979,6 +2327,74 @@ min = 0 max = 1000000 cat = SC_EXPERT +[SDT_VAR] +base = GameSettings +var = pf.yapf.route_transfer_cost +type = SLE_UINT +from = 180 +def = 500 +min = 0 +max = 1000000 +cat = SC_EXPERT + +[SDT_VAR] +base = GameSettings +var = pf.yapf.route_max_transfers +type = SLE_UINT16 +from = 180 +def = 5 +min = 0 +max = 100000 +cat = SC_EXPERT + +[SDT_VAR] +base = GameSettings +var = pf.yapf.route_distance_factor +type = SLE_UINT16 +from = 180 +def = 8 +min = 0 +max = 1000 +cat = SC_EXPERT + +[SDT_VAR] +base = GameSettings +var = pf.yapf.route_travel_time_factor +type = SLE_UINT16 +from = 180 +def = 32 +min = 0 +max = UINT16_MAX +cat = SC_EXPERT + +[SDT_VAR] +base = GameSettings +var = pf.yapf.route_station_last_veh_factor +type = SLE_UINT16 +from = 180 +def = 64 +min = 0 +max = UINT16_MAX +cat = SC_EXPERT + +[SDT_VAR] +base = GameSettings +var = pf.yapf.route_station_waiting_factor +type = SLE_UINT16 +from = 180 +def = 128 +min = 0 +max = UINT16_MAX +cat = SC_EXPERT + +[SDT_LIST] +base = GameSettings +var = pf.yapf.route_mode_cost_factor +type = SLE_UINT8 +from = 180 +def = 4,2,1,8 +cat = SC_EXPERT + ## [SDT_VAR] base = GameSettings @@ -2271,6 +2687,7 @@ str = STR_CONFIG_SETTING_SHOWFINANCES strhelp = STR_CONFIG_SETTING_SHOWFINANCES_HELPTEXT cat = SC_BASIC + [SDTC_VAR] var = gui.auto_scrolling type = SLE_UINT8 @@ -2538,6 +2955,59 @@ proc = InvalidateVehTimetableWindow cat = SC_EXPERT [SDTC_BOOL] +var = gui.time_in_minutes +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +def = false +str = STR_CONFIG_SETTING_TIME_IN_MINUTES +strhelp = STR_CONFIG_SETTING_TIME_IN_MINUTES_HELPTEXT +proc = InvalidateVehTimetableWindow + +[SDTC_BOOL] +var = gui.timetable_start_text_entry +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +def = false +str = STR_CONFIG_SETTING_TIMETABLE_START_TEXT_ENTRY +strhelp = STR_CONFIG_SETTING_TIMETABLE_START_TEXT_ENTRY_HELPTEXT + +[SDTC_VAR] +var = gui.ticks_per_minute +type = SLE_UINT8 +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +strval = STR_JUST_INT +def = 74 +min = 1 +max = 255 +str = STR_CONFIG_SETTING_TICKS_PER_MINUTE +strhelp = STR_CONFIG_SETTING_TICKS_PER_MINUTE_HELPTEXT +proc = RedrawScreen + +[SDTC_VAR] +var = gui.date_with_time +type = SLE_UINT8 +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +guiflags = SGF_MULTISTRING +def = 0 +min = 0 +max = 3 +str = STR_CONFIG_SETTING_DATE_WITH_TIME +strval = STR_CONFIG_SETTING_DATE_WITH_TIME_NONE +strhelp = STR_CONFIG_SETTING_DATE_WITH_TIME_HELPTEXT +proc = RedrawScreen + +[SDTC_VAR] +var = gui.clock_offset +type = SLE_UINT16 +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +strval = STR_JUST_INT +def = 0 +min = 0 +max = 1439 +interval = 60 +str = STR_CONFIG_SETTING_CLOCK_OFFSET +strhelp = STR_CONFIG_SETTING_CLOCK_OFFSET_HELPTEXT +proc = RedrawScreen + +[SDTC_BOOL] var = gui.timetable_arrival_departure flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC def = true @@ -2545,6 +3015,139 @@ str = STR_CONFIG_SETTING_TIMETABLE_SHOW_ARRIVAL_DEPARTURE strhelp = STR_CONFIG_SETTING_TIMETABLE_SHOW_ARRIVAL_DEPARTURE_HELPTEXT proc = InvalidateVehTimetableWindow +[SDTC_VAR] +var = gui.max_departures +type = SLE_UINT8 +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +strval = STR_JUST_INT +def = 10 +min = 1 +max = 30 +interval = 1 +str = STR_CONFIG_MAX_DEPARTURES +strhelp = STR_CONFIG_MAX_DEPARTURES_HELPTEXT + +[SDTC_VAR] +var = gui.max_departure_time +type = SLE_UINT16 +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +strval = STR_JUST_INT +def = 120 +min = 30 +max = 240 +interval = 1 +str = STR_CONFIG_MAX_DEPARTURE_TIME +strhelp = STR_CONFIG_MAX_DEPARTURE_TIME_HELPTEXT + +[SDTC_VAR] +var = gui.departure_calc_frequency +type = SLE_UINT16 +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +strval = STR_JUST_INT +def = 10 +min = 1 +max = 120 +interval = 1 +str = STR_CONFIG_DEPARTURE_CALC_FREQUENCY +strhelp = STR_CONFIG_DEPARTURE_CALC_FREQUENCY_HELPTEXT + +[SDTC_BOOL] +var = gui.departure_show_vehicle +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +def = false +str = STR_CONFIG_DEPARTURE_VEHICLE_NAME +strhelp = STR_CONFIG_DEPARTURE_VEHICLE_NAME_HELPTEXT + +[SDTC_BOOL] +var = gui.departure_show_group +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +def = false +str = STR_CONFIG_DEPARTURE_GROUP_NAME +strhelp = STR_CONFIG_DEPARTURE_GROUP_NAME_HELPTEXT + +[SDTC_BOOL] +var = gui.departure_show_company +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +def = false +str = STR_CONFIG_DEPARTURE_COMPANY_NAME +strhelp = STR_CONFIG_DEPARTURE_COMPANY_NAME_HELPTEXT + +[SDTC_BOOL] +var = gui.departure_show_vehicle_type +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +def = false +str = STR_CONFIG_DEPARTURE_VEHICLE_TYPE +strhelp = STR_CONFIG_DEPARTURE_VEHICLE_TYPE_HELPTEXT + +[SDTC_BOOL] +var = gui.departure_show_vehicle_color +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +def = false +str = STR_CONFIG_DEPARTURE_VEHICLE_COLOR +strhelp = STR_CONFIG_DEPARTURE_VEHICLE_COLOR_HELPTEXT + +[SDTC_BOOL] +var = gui.departure_larger_font +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +def = false +str = STR_CONFIG_DEPARTURE_LARGER_FONT +strhelp = STR_CONFIG_DEPARTURE_LARGER_FONT_HELPTEXT + +[SDTC_BOOL] +var = gui.departure_destination_type +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +def = false +str = STR_CONFIG_DEPARTURE_DESTINATION_TYPE +strhelp = STR_CONFIG_DEPARTURE_DESTINATION_TYPE_HELPTEXT + +[SDTC_BOOL] +var = gui.departure_show_both +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +def = false +str = STR_CONFIG_DEPARTURE_SHOW_BOTH +strhelp = STR_CONFIG_DEPARTURE_SHOW_BOTH_HELPTEXT + +[SDTC_BOOL] +var = gui.departure_only_passengers +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +def = false +str = STR_CONFIG_DEPARTURE_ONLY_PASSENGERS +strhelp = STR_CONFIG_DEPARTURE_ONLY_PASSENGERS_HELPTEXT + +[SDTC_BOOL] +var = gui.departure_smart_terminus +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +def = false +str = STR_CONFIG_DEPARTURE_SMART_TERMINUS +strhelp = STR_CONFIG_DEPARTURE_SMART_TERMINUS_HELPTEXT + +[SDTC_BOOL] +var = gui.departure_show_all_stops +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +def = false +str = STR_CONFIG_DEPARTURE_SHOW_ALL_STOPS +strhelp = STR_CONFIG_DEPARTURE_SHOW_ALL_STOPS_HELPTEXT + +[SDTC_BOOL] +var = gui.departure_merge_identical +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +def = false +str = STR_CONFIG_DEPARTURE_MERGE_IDENTICAL +strhelp = STR_CONFIG_DEPARTURE_MERGE_IDENTICAL_HELPTEXT + +[SDTC_VAR] +var = gui.departure_conditionals +type = SLE_UINT8 +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +guiflags = SGF_MULTISTRING +def = 0 +min = 0 +max = 2 +str = STR_CONFIG_DEPARTURE_CONDITIONALS +strval = STR_CONFIG_DEPARTURE_CONDITIONALS_1 +strhelp = STR_CONFIG_DEPARTURE_CONDITIONALS_HELPTEXT +proc = RedrawScreen + [SDTC_BOOL] var = gui.quick_goto flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC @@ -2603,6 +3206,17 @@ strval = STR_JUST_INT cat = SC_EXPERT [SDTC_VAR] +var = gui.simulated_wormhole_signals +type = SLE_UINT8 +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +def = 2 +min = 1 +max = 16 +str = STR_CONFIG_SETTING_SIMULATE_SIGNALS +strval = STR_CONFIG_SETTING_SIMULATE_SIGNALS_VALUE +proc = RedrawScreen + +[SDTC_VAR] var = gui.drag_signals_density type = SLE_UINT8 flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC @@ -2829,6 +3443,15 @@ strhelp = STR_CONFIG_SETTING_GRAPH_LINE_THICKNESS_HELPTEXT strval = STR_JUST_COMMA proc = RedrawScreen +[SDTC_VAR] +var = gui.layer_view_type +type = SLE_UINT32 +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +def = 0 +min = 0 +max = UINT32_MAX +proc = RedrawScreen + ; For the dedicated build we'll enable dates in logs by default. [SDTC_BOOL] ifdef = DEDICATED diff --git a/src/table/sprites.h b/src/table/sprites.h index 463716cb1..916bff3d2 100644 --- a/src/table/sprites.h +++ b/src/table/sprites.h @@ -200,7 +200,10 @@ static const uint16 AUTORAIL_SPRITE_COUNT = 55; static const SpriteID SPR_ELRAIL_BASE = SPR_AUTORAIL_BASE + AUTORAIL_SPRITE_COUNT; static const uint16 ELRAIL_SPRITE_COUNT = 48; -static const SpriteID SPR_2CCMAP_BASE = SPR_ELRAIL_BASE + ELRAIL_SPRITE_COUNT; +static const SpriteID SPR_TRAFFICLIGHTS_BASE = SPR_ELRAIL_BASE + ELRAIL_SPRITE_COUNT; +static const uint16 TRAFFICLIGHTS_SPRITE_COUNT = 22; + +static const SpriteID SPR_2CCMAP_BASE = SPR_TRAFFICLIGHTS_BASE + TRAFFICLIGHTS_SPRITE_COUNT; static const uint16 TWOCCMAP_SPRITE_COUNT = 256; /** shore tiles - action 05-0D */ @@ -1087,6 +1090,34 @@ static const SpriteID SPR_IMG_QUERY = 723; static const SpriteID SPR_IMG_SIGN = 4082; static const SpriteID SPR_IMG_BUY_LAND = 4791; +/* Traffic lights */ +static const SpriteID SPR_IMG_TRAFFIC_LIGHT = SPR_TRAFFICLIGHTS_BASE; +static const SpriteID SPR_CURSOR_TRAFFIC_LIGHT = SPR_TRAFFICLIGHTS_BASE + 1; + +static const SpriteID SPR_TL_SW_RED = SPR_TRAFFICLIGHTS_BASE + 2; +static const SpriteID SPR_TL_SW_RED_YELLOW = SPR_TRAFFICLIGHTS_BASE + 3; +static const SpriteID SPR_TL_SW_GREEN = SPR_TRAFFICLIGHTS_BASE + 4; +static const SpriteID SPR_TL_SW_YELLOW = SPR_TRAFFICLIGHTS_BASE + 5; +static const SpriteID SPR_TL_SW_NONE = SPR_TRAFFICLIGHTS_BASE + 6; + +static const SpriteID SPR_TL_SE_RED = SPR_TRAFFICLIGHTS_BASE + 7; +static const SpriteID SPR_TL_SE_RED_YELLOW = SPR_TRAFFICLIGHTS_BASE + 8; +static const SpriteID SPR_TL_SE_GREEN = SPR_TRAFFICLIGHTS_BASE + 9; +static const SpriteID SPR_TL_SE_YELLOW = SPR_TRAFFICLIGHTS_BASE + 10; +static const SpriteID SPR_TL_SE_NONE = SPR_TRAFFICLIGHTS_BASE + 11; + +static const SpriteID SPR_TL_NW_RED = SPR_TRAFFICLIGHTS_BASE + 12; +static const SpriteID SPR_TL_NW_RED_YELLOW = SPR_TRAFFICLIGHTS_BASE + 13; +static const SpriteID SPR_TL_NW_GREEN = SPR_TRAFFICLIGHTS_BASE + 14; +static const SpriteID SPR_TL_NW_YELLOW = SPR_TRAFFICLIGHTS_BASE + 15; +static const SpriteID SPR_TL_NW_NONE = SPR_TRAFFICLIGHTS_BASE + 16; + +static const SpriteID SPR_TL_NE_RED = SPR_TRAFFICLIGHTS_BASE + 17; +static const SpriteID SPR_TL_NE_RED_YELLOW = SPR_TRAFFICLIGHTS_BASE + 18; +static const SpriteID SPR_TL_NE_GREEN = SPR_TRAFFICLIGHTS_BASE + 19; +static const SpriteID SPR_TL_NE_YELLOW = SPR_TRAFFICLIGHTS_BASE + 20; +static const SpriteID SPR_TL_NE_NONE = SPR_TRAFFICLIGHTS_BASE + 21; + /* OpenTTD in gamescreen */ static const SpriteID SPR_OTTD_O = 4842; static const SpriteID SPR_OTTD_P = 4841; diff --git a/src/table/strgen_tables.h b/src/table/strgen_tables.h index ed405aa48..19f5beb45 100644 --- a/src/table/strgen_tables.h +++ b/src/table/strgen_tables.h @@ -90,6 +90,10 @@ static const CmdStruct _cmd_structs[] = { {"DATE_SHORT", EmitSingleChar, SCC_DATE_SHORT, 1, C_CASE}, {"DATE_LONG", EmitSingleChar, SCC_DATE_LONG, 1, C_CASE}, {"DATE_ISO", EmitSingleChar, SCC_DATE_ISO, 1, C_NONE}, + {"DATE_WALLCLOCK_TINY", EmitSingleChar, SCC_DATE_WALLCLOCK_TINY, 1, C_NONE}, + {"DATE_WALLCLOCK_SHORT", EmitSingleChar, SCC_DATE_WALLCLOCK_SHORT, 1, C_NONE}, + {"DATE_WALLCLOCK_LONG", EmitSingleChar, SCC_DATE_WALLCLOCK_LONG, 1, C_NONE}, + {"DATE_WALLCLOCK_ISO", EmitSingleChar, SCC_DATE_WALLCLOCK_ISO, 1, C_NONE}, {"STRING", EmitSingleChar, SCC_STRING, 1, C_CASE | C_GENDER}, {"RAW_STRING", EmitSingleChar, SCC_RAW_STRING_POINTER, 1, C_NONE | C_GENDER}, diff --git a/src/terraform_cmd.cpp b/src/terraform_cmd.cpp index 8c0c63ac0..42e76ab46 100644 --- a/src/terraform_cmd.cpp +++ b/src/terraform_cmd.cpp @@ -10,6 +10,7 @@ /** @file terraform_cmd.cpp Commands related to terraforming. */ #include "stdafx.h" +#include "layer_func.h" #include "command_func.h" #include "tunnel_map.h" #include "bridge_map.h" @@ -308,6 +309,10 @@ CommandCost CmdTerraformLand(TileIndex tile, DoCommandFlag flags, uint32 p1, uin if (z_N > z_min) tileh |= SLOPE_N; if (pass == 0) { + /* Terrafrom enable only top layer */ + if (IsUnderground(tile)) { + return_cmd_error(STR_ERROR_UNDERGROUND_CAN_T_TERRAFORM); + } /* Check if bridge would take damage */ if (direction == 1 && MayHaveBridgeAbove(tile) && IsBridgeAbove(tile) && GetBridgeHeight(GetSouthernBridgeEnd(tile)) <= z_max) { diff --git a/src/tgp.cpp b/src/tgp.cpp index 6d17fd853..81541dd66 100644 --- a/src/tgp.cpp +++ b/src/tgp.cpp @@ -14,6 +14,7 @@ #include "clear_map.h" #include "void_map.h" #include "genworld.h" +#include "layer_func.h" #include "core/random_func.hpp" #include "landscape_type.h" @@ -165,8 +166,10 @@ struct HeightMap height_t *h; //< array of heights uint dim_x; //< height map size_x MapSizeX() + 1 uint total_size; //< height map total size - uint size_x; //< MapSizeX() - uint size_y; //< MapSizeY() + uint size_x; //< LayerSizeX() + uint size_y; //< LayerSizeY() + uint map_x; //< MapSizeX() + uint map_y; //< MapSizeY() /** * Height map accessor @@ -250,8 +253,11 @@ static inline bool AllocHeightMap() { height_t *h; - _height_map.size_x = MapSizeX(); - _height_map.size_y = MapSizeY(); + _height_map.map_x = MapSizeX(); + _height_map.map_y = MapSizeY(); + + _height_map.size_x = LayerSizeX(); + _height_map.size_y = LayerSizeY(); /* Allocate memory block for height map row pointers */ _height_map.total_size = (_height_map.size_x + 1) * (_height_map.size_y + 1); @@ -980,8 +986,8 @@ void GenerateTerrainPerlin() /* First make sure the tiles at the north border are void tiles if needed. */ if (_settings_game.construction.freeform_edges) { - for (y = 0; y < _height_map.size_y - 1; y++) MakeVoid(_height_map.size_x * y); - for (x = 0; x < _height_map.size_x; x++) MakeVoid(x); + for (y = 0; y < _height_map.map_y - 1; y++) MakeVoid(_height_map.map_x * y); + for (x = 0; x < _height_map.map_x; x++) MakeVoid(x); } /* Transfer height map into OTTD map */ diff --git a/src/tile_map.h b/src/tile_map.h index 2c5cfffbf..459c27ab2 100644 --- a/src/tile_map.h +++ b/src/tile_map.h @@ -95,7 +95,7 @@ static inline void SetTileType(TileIndex tile, TileType type) /* VOID tiles (and no others) are exactly allowed at the lower left and right * edges of the map. If _settings_game.construction.freeform_edges is true, * the upper edges of the map are also VOID tiles. */ - assert((TileX(tile) == MapMaxX() || TileY(tile) == MapMaxY() || (_settings_game.construction.freeform_edges && (TileX(tile) == 0 || TileY(tile) == 0))) == (type == MP_VOID)); +// assert((TileX(tile) == MapMaxX() || TileY(tile) == MapMaxY() || (_settings_game.construction.freeform_edges && (TileX(tile) == 0 || TileY(tile) == 0))) == (type == MP_VOID)); SB(_m[tile].type_height, 4, 4, type); } diff --git a/src/tilearea_type.h b/src/tilearea_type.h index 62d3d75df..88c2928ac 100644 --- a/src/tilearea_type.h +++ b/src/tilearea_type.h @@ -46,6 +46,16 @@ struct TileArea { this->h = 0; } + inline bool IsEmpty() const + { + return (w==0 && h==0); + } + + inline bool IsFinite() const + { + return (w!=0 && h!=0); + } + bool Intersects(const TileArea &ta) const; bool Contains(TileIndex tile) const; diff --git a/src/tilehighlight_func.h b/src/tilehighlight_func.h index 3edef509a..a17571fe3 100644 --- a/src/tilehighlight_func.h +++ b/src/tilehighlight_func.h @@ -14,6 +14,7 @@ #include "gfx_type.h" #include "tilehighlight_type.h" +#include "track_type.h" void PlaceProc_DemolishArea(TileIndex tile); bool GUIPlaceProcDragXY(ViewportDragDropSelectionProcess proc, TileIndex start_tile, TileIndex end_tile); @@ -29,6 +30,13 @@ void VpSetPresizeRange(TileIndex from, TileIndex to); void VpSetPlaceSizingLimit(int limit); void UpdateTileSelection(); +void StoreRailPlacementEndpoints(TileIndex start_tile, TileIndex end_tile, Track start_track, bool bidirectional = true); + +/** Clear all rail track endpoints stored for highlighting purposes. @see StoreRailPlacementEndpoints */ +static inline void ClearRailPlacementEndpoints() +{ + StoreRailPlacementEndpoints(INVALID_TILE, INVALID_TILE, TRACK_BEGIN, false); +} extern TileHighlightData _thd; diff --git a/src/tilehighlight_type.h b/src/tilehighlight_type.h index 3d64248df..838b98186 100644 --- a/src/tilehighlight_type.h +++ b/src/tilehighlight_type.h @@ -28,6 +28,7 @@ enum HighLightStyle { HT_RAIL = 0x080, ///< autorail (one piece), lower bits: direction HT_VEHICLE = 0x100, ///< vehicle is accepted as target as well (bitmask) HT_DIAGONAL = 0x200, ///< Also allow 'diagonal rectangles'. Only usable in combination with #HT_RECT or #HT_POINT. + HT_POLY = 0x400, ///< polyline mode; connect highlighted track with previous one HT_DRAG_MASK = 0x0F8, ///< Mask for the tile drag-type modes. /* lower bits (used with HT_LINE and HT_RAIL): diff --git a/src/timetable.h b/src/timetable.h index fe0848b56..08bce2f32 100644 --- a/src/timetable.h +++ b/src/timetable.h @@ -15,6 +15,8 @@ #include "date_type.h" #include "vehicle_type.h" +#define WALLCLOCK_NETWORK_COMPATIBLE 0 ///< Whether wallclock should preserve network compatibility. If so, then timetable start dates cannot be set exactly using minutes. + void ShowTimetableWindow(const Vehicle *v); void UpdateVehicleTimetable(Vehicle *v, bool travelling); void SetTimetableParams(int param1, int param2, Ticks ticks); diff --git a/src/timetable_cmd.cpp b/src/timetable_cmd.cpp index 166b817eb..560cf7192 100644 --- a/src/timetable_cmd.cpp +++ b/src/timetable_cmd.cpp @@ -16,6 +16,7 @@ #include "window_func.h" #include "vehicle_base.h" #include "cmd_helper.h" +#include "settings_type.h" #include "table/strings.h" @@ -69,6 +70,7 @@ static void ChangeTimetable(Vehicle *v, VehicleOrderID order_number, uint16 val, default: NOT_REACHED(); } + v->MarkSeparationInvalid(); } SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index); } @@ -195,11 +197,16 @@ CommandCost CmdSetTimetableStart(TileIndex tile, DoCommandFlag flags, uint32 p1, CommandCost ret = CheckOwnership(v->owner); if (ret.Failed()) return ret; + DateTicks start_date = (Date)p2 / DAY_TICKS; + +#if WALLCLOCK_NETWORK_COMPATIBLE /* Don't let a timetable start more than 15 years into the future or 1 year in the past. */ - Date start_date = (Date)p2; if (start_date < 0 || start_date > MAX_DAY) return CMD_ERROR; if (start_date - _date > 15 * DAYS_IN_LEAP_YEAR) return CMD_ERROR; if (_date - start_date > DAYS_IN_LEAP_YEAR) return CMD_ERROR; +#else + start_date = ((DateTicks)_date * DAY_TICKS) + _date_fract + (DateTicks)(int32)p2; +#endif if (flags & DC_EXEC) { v->lateness_counter = 0; @@ -268,6 +275,33 @@ CommandCost CmdAutofillTimetable(TileIndex tile, DoCommandFlag flags, uint32 p1, } /** + * Set new separation parameters + * @param tile Not used. + * @param flags Operation to perform. + * @param p1 Order lit id. + * @param p2 + * - p2 = (bit 0-1) - Separation mode (@see TTSepMode) + * - p2 = (bit 2-31) - Separation parameter (Unused if #TTS_MODE_OFF | #TTS_MODE_AUTO, + * Number of vehicles if #TTS_MODE_MAN_N, separation delay in ticks if #TTS_MODE_MAN_T). + * @param text Not used. + * @return The error or cost of the operation. + */ +CommandCost CmdReinitSeparation(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + Vehicle *v = Vehicle::GetIfValid(GB(p1, 0, 20)); + if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR; + + CommandCost ret = CheckOwnership(v->owner); + if (ret.Failed()) return ret; + + if (flags & DC_EXEC) { + v->SetSepSettings((TTSepMode)GB(p2,0,3), GB(p2,2,29)); + } + + return CommandCost(); +} + +/** * Update the timetable for the vehicle. * @param v The vehicle to update the timetable for. * @param travelling Whether we just travelled or waited at a station. @@ -297,7 +331,11 @@ void UpdateVehicleTimetable(Vehicle *v, bool travelling) just_started = !HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED); if (v->timetable_start != 0) { +#if WALLCLOCK_NETWORK_COMPATIBLE v->lateness_counter = (_date - v->timetable_start) * DAY_TICKS + _date_fract; +#else + v->lateness_counter = (_date * DAY_TICKS) + _date_fract - v->timetable_start; +#endif v->timetable_start = 0; } @@ -327,7 +365,7 @@ void UpdateVehicleTimetable(Vehicle *v, bool travelling) * the timetable entry like is done for road vehicles/ships. * Thus always make sure at least one tick is used between the * processing of different orders when filling the timetable. */ - time_taken = CeilDiv(max(time_taken, 1U), DAY_TICKS) * DAY_TICKS; + time_taken = CeilDiv(max(time_taken, 1U), DATE_UNIT_SIZE) * DATE_UNIT_SIZE; ChangeTimetable(v, v->cur_real_order_index, time_taken, travelling ? MTF_TRAVEL_TIME : MTF_WAIT_TIME); } diff --git a/src/timetable_gui.cpp b/src/timetable_gui.cpp index 7fc8da483..dcc850e80 100644 --- a/src/timetable_gui.cpp +++ b/src/timetable_gui.cpp @@ -29,6 +29,18 @@ #include "table/sprites.h" #include "table/strings.h" +#include "widgets/dropdown_func.h" + + +/** Entries for mode selection dropdown list. Order must be identical to the one in #TTSepMode */ +static const StringID TimetableSeparationDropdownOptions[6] = { + STR_TTSEPARATION_AUTO, + STR_TTSEPARATION_OFF, + STR_TTSEPARATION_MAN_TIME, + STR_TTSEPARATION_MAN_NUM, + STR_TTSEPARATION_BUFFERED_AUTO, + INVALID_STRING_ID, +}; /** Container for the arrival/departure dates of a vehicle */ struct TimetableArrivalDeparture { @@ -45,11 +57,14 @@ struct TimetableArrivalDeparture { void SetTimetableParams(int param1, int param2, Ticks ticks) { if (_settings_client.gui.timetable_in_ticks) { - SetDParam(param1, STR_TIMETABLE_TICKS); SetDParam(param2, ticks); + SetDParam(param1, STR_TIMETABLE_TICKS); + } else if (_settings_client.gui.time_in_minutes) { + SetDParam(param2, ticks / DATE_UNIT_SIZE); + SetDParam(param1, STR_TIMETABLE_MINUTES); } else { + SetDParam(param2, ticks / DATE_UNIT_SIZE); SetDParam(param1, STR_TIMETABLE_DAYS); - SetDParam(param2, ticks / DAY_TICKS); } } @@ -61,8 +76,18 @@ void SetTimetableParams(int param1, int param2, Ticks ticks) */ static void SetArrivalDepartParams(int param1, int param2, Ticks ticks) { - SetDParam(param1, STR_JUST_DATE_TINY); - SetDParam(param2, _date + (ticks / DAY_TICKS)); + SetDParam(param1, STR_JUST_DATE_WALLCLOCK_TINY); + SetDParam(param2, ((DateTicks)_date * DAY_TICKS) + ticks); +} + +/** + * Check whether the order's time can be changed manually. + * @param order the order to check + * @return true if the order's time can be changed. + */ +static bool CanChangeTime(const Order *order) +{ + return !(order == NULL || ((!order->IsType(OT_GOTO_STATION) || (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION)) && !order->IsType(OT_CONDITIONAL))); } /** @@ -149,20 +174,28 @@ static void FillTimetableArrivalDepartureTable(const Vehicle *v, VehicleOrderID * @param window the window related to the setting of the date * @param date the actually chosen date */ -static void ChangeTimetableStartCallback(const Window *w, Date date) +static void ChangeTimetableStartCallback(const Window *w, DateTicks date) { - DoCommandP(0, w->window_number, date, CMD_SET_TIMETABLE_START | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE)); +#if WALLCLOCK_NETWORK_COMPATIBLE + DoCommandP(0, w->window_number, (Date)(date / DAY_TICKS), CMD_SET_TIMETABLE_START | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE)); +#else + DoCommandP(0, w->window_number, (Ticks)(date - (((DateTicks)_date * DAY_TICKS) + _date_fract)), CMD_SET_TIMETABLE_START | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE)); +#endif } struct TimetableWindow : Window { int sel_index; - const Vehicle *vehicle; ///< Vehicle monitored by the window. - bool show_expected; ///< Whether we show expected arrival or scheduled - uint deparr_time_width; ///< The width of the departure/arrival time - uint deparr_abbr_width; ///< The width of the departure/arrival abbreviation + const Vehicle *vehicle; ///< Vehicle monitored by the window. + bool show_expected; ///< Whether we show expected arrival or scheduled + uint deparr_time_width; ///< The width of the departure/arrival time + uint deparr_abbr_width; ///< The width of the departure/arrival abbreviation + int ctrl_pressed; // We need this to influence the ShowQueryString result. + int clicked_widget; ///< The widget that was clicked (used to determine what to do in OnQueryTextFinished) Scrollbar *vscroll; bool query_is_speed_query; ///< The currently open query window is a speed query and not a time query. + TTSepSettings new_sep_settings; ///< Contains new separation settings. + VehicleTimetableWidgets query_widget; ///< Required to determinate source of input query TimetableWindow(const WindowDesc *desc, WindowNumber window_number) : Window(), @@ -170,6 +203,7 @@ struct TimetableWindow : Window { vehicle(Vehicle::Get(window_number)), show_expected(true) { + this->new_sep_settings = (vehicle->orders.list != NULL) ? vehicle->orders.list->GetSepSettings() : TTSepSettings(); this->CreateNestedTree(desc); this->vscroll = this->GetScrollbar(WID_VT_SCROLLBAR); this->UpdateSelectionStates(); @@ -200,8 +234,8 @@ struct TimetableWindow : Window { { switch (widget) { case WID_VT_ARRIVAL_DEPARTURE_PANEL: - SetDParamMaxValue(0, MAX_YEAR * DAYS_IN_YEAR); - this->deparr_time_width = GetStringBoundingBox(STR_JUST_DATE_TINY).width; + SetDParamMaxValue(0, _settings_client.gui.time_in_minutes ? 0 : MAX_YEAR * DAYS_IN_YEAR); + this->deparr_time_width = GetStringBoundingBox(STR_JUST_DATE_WALLCLOCK_TINY).width + 4; this->deparr_abbr_width = max(GetStringBoundingBox(STR_TIMETABLE_ARRIVAL_ABBREVIATION).width, GetStringBoundingBox(STR_TIMETABLE_DEPARTURE_ABBREVIATION).width); size->width = WD_FRAMERECT_LEFT + this->deparr_abbr_width + 10 + this->deparr_time_width + WD_FRAMERECT_RIGHT; /* FALL THROUGH */ @@ -317,7 +351,7 @@ struct TimetableWindow : Window { if (selected % 2 == 1) { disable = order != NULL && (order->IsType(OT_CONDITIONAL) || order->IsType(OT_IMPLICIT)); } else { - disable = order == NULL || ((!order->IsType(OT_GOTO_STATION) || (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION)) && !order->IsType(OT_CONDITIONAL)); + disable = !CanChangeTime(order); } } bool disable_speed = disable || selected % 2 != 1 || v->type == VEH_AIRCRAFT; @@ -344,6 +378,20 @@ struct TimetableWindow : Window { this->SetWidgetLoweredState(WID_VT_AUTOFILL, HasBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE)); + bool b; + + if(this->vehicle->orders.list != NULL) { + b = !(_settings_game.order.automatic_timetable_separation && this->vehicle->orders.list->IsCompleteTimetable()); + } else { + b = false; + } + this->SetWidgetsDisabledState(b, WID_VT_TTSEP_SET_PARAMETER, WID_VT_TTSEP_MODE_DROPDOWN, WIDGET_LIST_END); + + /* We can only set parameters if we're in one of the manual modes. */ + bool enabled_state = (this->new_sep_settings.mode == TTS_MODE_MAN_N) || (this->new_sep_settings.mode == TTS_MODE_MAN_T); + + this->SetWidgetDisabledState(WID_VT_TTSEP_SET_PARAMETER, !enabled_state); + this->DrawWidgets(); } @@ -352,6 +400,8 @@ struct TimetableWindow : Window { switch (widget) { case WID_VT_CAPTION: SetDParam(0, this->vehicle->index); break; case WID_VT_EXPECTED: SetDParam(0, this->show_expected ? STR_TIMETABLE_EXPECTED : STR_TIMETABLE_SCHEDULED); break; + case WID_VT_TTSEP_MODE_DROPDOWN: SetDParam(0, TimetableSeparationDropdownOptions[this->new_sep_settings.mode]); break; + case WID_VT_TTSEP_SET_PARAMETER: SetDParam(0, (this->new_sep_settings.mode == TTS_MODE_MAN_N) ? STR_TTSEPARATION_SET_NUM : STR_TTSEPARATION_SET_TIME); break; } } @@ -430,7 +480,7 @@ struct TimetableWindow : Window { int y = r.top + WD_FRAMERECT_TOP; - bool show_late = this->show_expected && v->lateness_counter > DAY_TICKS; + bool show_late = this->show_expected && v->lateness_counter > DATE_UNIT_SIZE; Ticks offset = show_late ? 0 : -v->lateness_counter; bool rtl = _current_text_dir == TD_RTL; @@ -479,14 +529,18 @@ struct TimetableWindow : Window { if (v->timetable_start != 0) { /* We are running towards the first station so we can start the * timetable at the given time. */ - SetDParam(0, STR_JUST_DATE_TINY); + SetDParam(0, STR_JUST_DATE_WALLCLOCK_TINY); +#if WALLCLOCK_NETWORK_COMPATIBLE + SetDParam(1, v->timetable_start * DAY_TICKS); +#else SetDParam(1, v->timetable_start); +#endif DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_STATUS_START_AT); } else if (!HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED)) { /* We aren't running on a timetable yet, so how can we be "on time" * when we aren't even "on service"/"on duty"? */ DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_STATUS_NOT_STARTED); - } else if (v->lateness_counter == 0 || (!_settings_client.gui.timetable_in_ticks && v->lateness_counter / DAY_TICKS == 0)) { + } else if (v->lateness_counter == 0 || (!_settings_client.gui.timetable_in_ticks && v->lateness_counter / DATE_UNIT_SIZE == 0)) { DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_STATUS_ON_TIME); } else { SetTimetableParams(0, 1, abs(v->lateness_counter)); @@ -494,6 +548,75 @@ struct TimetableWindow : Window { } break; } + + case WID_VT_TTSEP_PANEL_TEXT: { + int y = r.top + WD_FRAMERECT_TOP; // Represents the current vertical position + const int left_border = r.left + WD_FRAMERECT_LEFT; // Represents the left border of the separation display frame + const int right_border = r.right - WD_FRAMERECT_RIGHT; // Represents the right border of the separation display frame. + + /* If separation is inactive, we can stop here. */ + if (!_settings_game.order.automatic_timetable_separation || (this->vehicle->orders.list == NULL)) + break; + + /* If the new mode is OFF... */ + if (this->new_sep_settings.mode == TTS_MODE_OFF) { + /* ... skip description lines. */ + int offset = _settings_client.gui.timetable_in_ticks ? GetStringBoundingBox(STR_TTSEPARATION_REQ_TIME_DESC_TICKS).height + : GetStringBoundingBox(STR_TTSEPARATION_REQ_TIME_DESC_DAYS).height; + + y = y + GetStringBoundingBox(STR_TTSEPARATION_REQ_NUM_DESC).height + offset; + + } else { + /* If separation hasn't just been switched off, we need to draw various description lines. + * The first line is the amount of separation which is either saved in the stuct or must + * be calculated on the fly. + */ + uint64 par; + if (this->new_sep_settings.mode == TTS_MODE_MAN_T || this->new_sep_settings.mode == TTS_MODE_AUTO) { + par = this->new_sep_settings.sep_ticks; + } else { + par = this->vehicle->orders.list->GetTimetableTotalDuration() / this->new_sep_settings.num_veh; + } + + /* Depending on the setting for time displays, set up and draw either tick or days string. */ + if (_settings_client.gui.timetable_in_ticks) { + SetDParam(0, par); + DrawString(left_border, right_border, y, STR_TTSEPARATION_REQ_TIME_DESC_TICKS, TC_BLACK); + + y += GetStringBoundingBox(STR_TTSEPARATION_REQ_TIME_DESC_TICKS).height; + } else { + SetDParam(0, par / DAY_TICKS); + DrawString(left_border, right_border, y, STR_TTSEPARATION_REQ_TIME_DESC_DAYS, TC_BLACK); + + y += GetStringBoundingBox(STR_TTSEPARATION_REQ_TIME_DESC_DAYS).height; + } + + /* Print either the chosen amount of vehicles (when in MAN_N mode) or the calculated result... */ + if (this->new_sep_settings.mode == TTS_MODE_MAN_N || this->new_sep_settings.mode == TTS_MODE_AUTO) { + par = this->new_sep_settings.num_veh; + } else { + par = this->vehicle->orders.list->GetTimetableTotalDuration() / this->new_sep_settings.sep_ticks; + } + + SetDParam(0, par); + DrawString(left_border, right_border, y, STR_TTSEPARATION_REQ_NUM_DESC, TC_BLACK); + + y += GetStringBoundingBox(STR_TTSEPARATION_REQ_NUM_DESC).height; + } + + /* If separation is switched on at all... */ + if(this->vehicle->orders.list->IsSeparationOn()) + { + /* ... set displayed status to either "Running" or "Initializing" */ + SetDParam(0, (this->vehicle->orders.list->IsSeparationValid()) ? STR_TTSEPARATION_STATUS_RUNNING : STR_TTSEPARATION_STATUS_INIT); + } else { + /* If separation is switched off, show this instead. */ + SetDParam(0, STR_TTSEPARATION_STATUS_OFF); + } + + /* Print status description. */ + DrawStringMultiLine(left_border, right_border, y, r.bottom - WD_FRAMERECT_BOTTOM, STR_TTSEPARATION_STATUS_DESC); + } } } @@ -510,6 +633,9 @@ struct TimetableWindow : Window { virtual void OnClick(Point pt, int widget, int click_count) { const Vehicle *v = this->vehicle; + ctrl_pressed = _ctrl_pressed; + + this->clicked_widget = widget; switch (widget) { case WID_VT_ORDER_VIEW: // Order view button @@ -518,14 +644,24 @@ struct TimetableWindow : Window { case WID_VT_TIMETABLE_PANEL: { // Main panel. int selected = GetOrderFromTimetableWndPt(pt.y, v); - this->DeleteChildWindows(); this->sel_index = (selected == INVALID_ORDER || selected == this->sel_index) ? -1 : selected; break; } case WID_VT_START_DATE: // Change the date that the timetable starts. - ShowSetDateWindow(this, v->index, _date, _cur_year, _cur_year + 15, ChangeTimetableStartCallback); + if (_settings_client.gui.time_in_minutes && _settings_client.gui.timetable_start_text_entry) { + StringID str = STR_JUST_INT; + uint64 time = ((DateTicks)_date * DAY_TICKS) + _date_fract; + time /= _settings_client.gui.ticks_per_minute; + time += _settings_client.gui.clock_offset; + time %= 24*60; + time = (time % 60) + (((time / 60) % 24) * 100); + SetDParam(0, time); + ShowQueryString(str, STR_TIMETABLE_STARTING_DATE, 31, this, CS_NUMERAL, QSF_ACCEPT_UNCHANGED); + } else { + ShowSetDateWindow(this, v->index, ((DateTicks)_date * DAY_TICKS) + _date_fract, _cur_year, _cur_year + 15, ChangeTimetableStartCallback); + } break; case WID_VT_CHANGE_TIME: { // "Wait For" button. @@ -539,7 +675,7 @@ struct TimetableWindow : Window { if (order != NULL) { uint time = (selected % 2 == 1) ? order->travel_time : order->wait_time; - if (!_settings_client.gui.timetable_in_ticks) time /= DAY_TICKS; + if (!_settings_client.gui.timetable_in_ticks) time /= DATE_UNIT_SIZE; if (time != 0) { SetDParam(0, time); @@ -547,6 +683,7 @@ struct TimetableWindow : Window { } } + this->query_widget = WID_VT_CHANGE_TIME; this->query_is_speed_query = false; ShowQueryString(current, STR_TIMETABLE_CHANGE_TIME, 31, this, CS_NUMERAL, QSF_NONE); break; @@ -603,29 +740,140 @@ struct TimetableWindow : Window { case WID_VT_SHARED_ORDER_LIST: ShowVehicleListWindow(v); break; + + case WID_VT_TTSEP_MODE_DROPDOWN: { + ShowDropDownMenu(this, TimetableSeparationDropdownOptions, this->new_sep_settings.mode, WID_VT_TTSEP_MODE_DROPDOWN, 0, 0); + break; + } + + case WID_VT_TTSEP_SET_PARAMETER: { + this->query_widget = WID_VT_TTSEP_SET_PARAMETER; + SetDParam(0, (this->new_sep_settings.mode == TTS_MODE_MAN_N) ? this->new_sep_settings.num_veh : this->new_sep_settings.sep_ticks); + ShowQueryString(STR_JUST_INT, STR_TIMETABLE_CHANGE_TIME, 31, this, CS_NUMERAL, QSF_NONE); + break; + } } this->SetDirty(); } - virtual void OnQueryTextFinished(char *str) + virtual void OnDropdownSelect(int widget, int index) { - if (str == NULL) return; - - const Vehicle *v = this->vehicle; + assert(widget == WID_VT_TTSEP_MODE_DROPDOWN); - uint32 p1 = PackTimetableArgs(v, this->sel_index, this->query_is_speed_query); + this->new_sep_settings = this->vehicle->orders.list->GetSepSettings(); + this->new_sep_settings.mode = (TTSepMode)index; + this->vehicle->orders.list->SetSepSettings(this->new_sep_settings); + this->InvalidateData(); + } - uint64 val = StrEmpty(str) ? 0 : strtoul(str, NULL, 10); - if (this->query_is_speed_query) { - val = ConvertDisplaySpeedToKmhishSpeed(val); - } else { - if (!_settings_client.gui.timetable_in_ticks) val *= DAY_TICKS; + virtual void OnQueryTextFinished(char *str) + { + if(str == NULL || StrEmpty(str)) + return; + + switch(this->query_widget) { + case WID_VT_CHANGE_TIME: { + const Vehicle *v = this->vehicle; + +// uint32 p1 = PackTimetableArgs(v, this->sel_index, this->query_is_speed_query); + uint64 val = StrEmpty(str) ? 0 : strtoul(str, NULL, 10); + +// uint64 val = StrEmpty(str) ? 0 : strtoul(str, NULL, 10); +// if (this->query_is_speed_query) { +// val = ConvertDisplaySpeedToKmhishSpeed(val); +// } else { +// if (!_settings_client.gui.timetable_in_ticks) val *= DAY_TICKS; +// } + switch (this->clicked_widget) { + default: NOT_REACHED(); + +// uint32 p2 = minu(val, UINT16_MAX); + case WID_VT_CHANGE_SPEED: + case WID_VT_CHANGE_TIME: { + uint32 p1 = PackTimetableArgs(v, this->sel_index, this->query_is_speed_query); + +// DoCommandP(0, p1, p2, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE)); + uint64 val = StrEmpty(str) ? 0 : strtoul(str, NULL, 10); + if (this->query_is_speed_query) { + val = ConvertDisplaySpeedToKmhishSpeed(val); + } else { + if (!_settings_client.gui.timetable_in_ticks) val *= DATE_UNIT_SIZE; + } + + uint32 p2 = minu(val, UINT16_MAX); + +// DoCommandP(0, p1, p2, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE)); + if (! ctrl_pressed) { + /* Do a normal update. */ + uint32 p1 = PackTimetableArgs(v, this->sel_index, this->query_is_speed_query); + DoCommandP(0, p1, p2, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE)); + } else { + /* Update all valid stations. */ + for (int i = 0, num_orders=v->GetNumOrders(); i < num_orders; ++i) { + const Order *order = v->GetOrder(i); + + if (CanChangeTime(order)) { + uint32 p1 = PackTimetableArgs(v, i*2, this->query_is_speed_query); + DoCommandP(0, p1, p2, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE)); + } + } + } + + break; + } + + case WID_VT_START_DATE: { + if (val > 0) { + uint minutes = (val % 100) % 60; + uint hours = (val / 100) % 24; + val = MINUTES_DATE(MINUTES_DAY(CURRENT_MINUTE), hours, minutes); + val -= _settings_client.gui.clock_offset; + + if (val < (CURRENT_MINUTE - 60)) val += 60 * 24; + val *= DATE_UNIT_SIZE; + ChangeTimetableStartCallback(this, val); + } + break; + } + } + + + +// DoCommandP(0, p1, p2, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE)); + break; } + case WID_VT_TTSEP_SET_PARAMETER: { + int value = atoi(str); + + switch (this->new_sep_settings.mode) + { + case TTS_MODE_AUTO: + case TTS_MODE_BUFFERED_AUTO: + case TTS_MODE_OFF: + break; + + case TTS_MODE_MAN_N: + this->new_sep_settings.num_veh = Clamp(value, 1, 65535); + break; + + case TTS_MODE_MAN_T: + this->new_sep_settings.sep_ticks = Clamp(value, 1, 65535); + break; + + default: + NOT_REACHED(); + break; + } - uint32 p2 = minu(val, UINT16_MAX); - - DoCommandP(0, p1, p2, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE)); + this->vehicle->orders.list->SetSepSettings(this->new_sep_settings); + this->InvalidateData(); + break; + } + default: + NOT_REACHED(); + break; + } } virtual void OnResize() @@ -658,6 +906,13 @@ static const NWidgetPart _nested_timetable_widgets[] = { NWidget(WWT_PANEL, COLOUR_GREY, WID_VT_ARRIVAL_DEPARTURE_PANEL), SetMinimalSize(110, 0), SetFill(0, 1), SetDataTip(STR_NULL, STR_TIMETABLE_TOOLTIP), SetScrollbar(WID_VT_SCROLLBAR), EndContainer(), EndContainer(), NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_VT_SCROLLBAR), + NWidget(WWT_PANEL, COLOUR_GREY), + NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_TTSEPARATION_SETTINGS_DESC, STR_NULL), SetPadding(3), + NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_VT_TTSEP_MODE_DROPDOWN), SetDataTip(STR_JUST_STRING, STR_TIMETABLE_TOOLTIP), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_TTSEP_SET_PARAMETER), SetFill(1, 0), SetDataTip(STR_TTSEPARATION_SET_XX, STR_TIMETABLE_TOOLTIP), + NWidget(WWT_PANEL,COLOUR_GREY, WID_VT_TTSEP_PANEL_TEXT), SetFill(1,1), SetResize(0,1), SetMinimalSize(0,44), EndContainer(), + EndContainer(), + EndContainer(), EndContainer(), NWidget(WWT_PANEL, COLOUR_GREY, WID_VT_SUMMARY_PANEL), SetMinimalSize(400, 22), SetResize(1, 0), EndContainer(), NWidget(NWID_HORIZONTAL), diff --git a/src/toolbar_gui.cpp b/src/toolbar_gui.cpp index 7cb18de3d..c583b1506 100644 --- a/src/toolbar_gui.cpp +++ b/src/toolbar_gui.cpp @@ -13,7 +13,10 @@ #include "gui.h" #include "window_gui.h" #include "window_func.h" +#include "layer_gui.h" +#include "layer_func.h" #include "viewport_func.h" +#include "landscape.h" #include "command_func.h" #include "vehicle_gui.h" #include "rail_gui.h" @@ -22,6 +25,7 @@ #include "vehicle_func.h" #include "sound_func.h" #include "terraform_gui.h" +#include "underground_gui.h" #include "strings_func.h" #include "company_func.h" #include "company_gui.h" @@ -874,10 +878,54 @@ static CallBackFunction MenuClickBuildAir(int index) static CallBackFunction ToolbarForestClick(Window *w) { - PopupMainToolbMenu(w, WID_TN_LANDSCAPE, STR_LANDSCAPING_MENU_LANDSCAPING, 3); + // ПОДЗЕМЕЛЬЕ: + // Временно все меню будет храниться здесь. С пункта 3 и выше + PopupMainToolbMenu(w, WID_TN_LANDSCAPE, STR_LANDSCAPING_MENU_LANDSCAPING, 10); return CBF_NONE; } +bool ScrollWindowRel(ViewportData *viewport, int dx, int dy) +{ + Point pt = RemapCoords(dx, dy, 0); + + viewport->dest_scrollpos_x += pt.x; + viewport->dest_scrollpos_y += pt.y; + return true; +} + +void SelectLayer(ViewportData *viewport, int layer) +{ + if (layer < 0) layer = 0; + if (uint(layer) >= LayerCount()) layer = LayerCount() - 1; + + int delta_layer = calculateLayer(viewport) - layer; + bool res = ScrollWindowRel(viewport, 0, -delta_layer * LayerSizeY() * TILE_SIZE); +} + +void SelectLayerInMainWindow(int layer) +{ + Window *w = FindWindowById(WC_MAIN_WINDOW, 0); + SelectLayer(w->viewport, layer); + w->InvalidateData(); +} + +/** + * Handle click on the entry in the underground menu. + * + * @param index Menu entry clicked. + */ +static void ToMenuClickUnderground(int index) +{ + switch (index) { + case 4: ShowUndergroundToolbar(); break; + + case 6: SelectLayerInMainWindow(index-6); break; + case 7: SelectLayerInMainWindow(index-6); break; + case 8: SelectLayerInMainWindow(index-6); break; + case 9: SelectLayerInMainWindow(index-6); break; + } +} + /** * Handle click on the entry in the landscaping menu. * @@ -891,6 +939,7 @@ static CallBackFunction MenuClickForest(int index) case 1: ShowBuildTreesToolbar(); break; case 2: return SelectSignTool(); } + ToMenuClickUnderground(index); return CBF_NONE; } diff --git a/src/town.h b/src/town.h index a672c2cee..9fcb07c5f 100644 --- a/src/town.h +++ b/src/town.h @@ -18,6 +18,7 @@ #include "newgrf_storage.h" #include "cargotype.h" #include "tilematrix_type.hpp" +#include "cargodest_base.h" #include <list> template <typename T> @@ -51,8 +52,9 @@ struct TownCache { }; /** Town data structure. */ -struct Town : TownPool::PoolItem<&_town_pool> { +struct Town : TownPool::PoolItem<&_town_pool>, CargoSourceSink { TileIndex xy; ///< town center tile + TileIndex xy_aligned; ///< NOSAVE: Town centre aligned to the #AcceptanceMatrix grid. TownCache cache; ///< Container for all cacheable data. @@ -105,6 +107,10 @@ struct Town : TownPool::PoolItem<&_town_pool> { std::list<PersistentStorage *> psa_list; + /* Current cargo acceptance and production. */ + uint32 cargo_accepted_weights[NUM_CARGO]; ///< NOSAVE: Weight sum of accepting squares per cargo. + uint32 cargo_accepted_max_weight; ///< NOSAVE: Cached maximum weight for an accepting square. + /** * Creates a new town. * @param tile center tile of the town @@ -116,6 +122,30 @@ struct Town : TownPool::PoolItem<&_town_pool> { void InitializeLayout(TownLayout layout); + /* virtual */ SourceType GetType() const + { + return ST_TOWN; + } + + /* virtual */ SourceID GetID() const + { + return this->index; + } + + /* virtual */ bool AcceptsCargo(CargoID cid) const + { + return HasBit(this->cargo_accepted_total, cid); + } + + /* virtual */ bool SuppliesCargo(CargoID cid) const + { + return HasBit(this->cargo_produced, cid); + } + + /* virtual */ uint GetDestinationWeight(CargoID cid, byte weight_mod) const; + /* virtual */ void CreateSpecialLinks(CargoID cid); + /* virtual */ TileArea GetTileForDestination(CargoID cid); + /** * Calculate the max town noise. * The value is counted using the population divided by the content of the @@ -137,7 +167,10 @@ struct Town : TownPool::PoolItem<&_town_pool> { return Town::Get(GetTownIndex(tile)); } - static Town *GetRandom(); + /** Callback function for #Town::GetRandom. */ + typedef bool (*EnumTownProc)(const Town *t, void *data); + + static Town *GetRandom(EnumTownProc enum_proc = NULL, TownID skip = INVALID_TOWN, void *data = NULL); static void PostDestructor(size_t index); }; diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index abfa18106..6b351b2cd 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -13,6 +13,7 @@ #include "road_internal.h" /* Cleaning up road bits */ #include "road_cmd.h" #include "landscape.h" +#include "layer_func.h" #include "viewport_func.h" #include "cmd_helper.h" #include "command_func.h" @@ -137,17 +138,36 @@ void Town::InitializeLayout(TownLayout layout) } /** - * Return a random valid town. - * @return random town, NULL if there are no towns + * Return a random town that statisfies some criteria specified + * with a callback function. + * + * @param enum_proc Callback function. Return true for a matching town and false to continue iterating. + * @param skip Skip over this town id when searching. + * @param data Optional data passed to the callback function. + * @return A town satisfying the search criteria or NULL if no such town exists. */ -/* static */ Town *Town::GetRandom() +/* static */ Town *Town::GetRandom(EnumTownProc enum_proc, TownID skip, void *data) { - if (Town::GetNumItems() == 0) return NULL; - int num = RandomRange((uint16)Town::GetNumItems()); - size_t index = MAX_UVALUE(size_t); + assert(skip == INVALID_TOWN || Town::IsValidID(skip)); - while (num >= 0) { - num--; + uint16 max_num = 0; + if (enum_proc != NULL) { + /* A callback was given, count all matching towns. */ + Town *t; + FOR_ALL_TOWNS(t) { + if (t->index != skip && enum_proc(t, data)) max_num++; + } + } else { + max_num = (uint16)Town::GetNumItems(); + /* Subtract one if a town to skip was given. max_num is at least + * one here as otherwise skip could not be valid. */ + if (skip != INVALID_TOWN) max_num--; + } + if (max_num == 0) return NULL; + + uint num = RandomRange(max_num) + 1; + size_t index = MAX_UVALUE(size_t); + do { index++; /* Make sure we have a valid town */ @@ -155,7 +175,9 @@ void Town::InitializeLayout(TownLayout layout) index++; assert(index < Town::GetPoolSize()); } - } + + if (index != skip && (enum_proc == NULL || enum_proc(Town::Get(index), data))) num--; + } while (num > 0); return Town::Get(index); } @@ -483,7 +505,7 @@ static void TileLoop_Town(TileIndex tile) uint amt = GB(callback, 0, 8); if (amt == 0) continue; - uint moved = MoveGoodsToStation(cargo, amt, ST_TOWN, t->index, stations.GetStations()); + uint moved = MoveGoodsToStation(cargo, amt, ST_TOWN, t->index, stations.GetStations(), tile); const CargoSpec *cs = CargoSpec::Get(cargo); t->supplied[cs->Index()].new_max += amt; @@ -495,7 +517,7 @@ static void TileLoop_Town(TileIndex tile) if (EconomyIsInRecession()) amt = (amt + 1) >> 1; t->supplied[CT_PASSENGERS].new_max += amt; - t->supplied[CT_PASSENGERS].new_act += MoveGoodsToStation(CT_PASSENGERS, amt, ST_TOWN, t->index, stations.GetStations()); + t->supplied[CT_PASSENGERS].new_act += MoveGoodsToStation(CT_PASSENGERS, amt, ST_TOWN, t->index, stations.GetStations(), tile); } if (GB(r, 8, 8) < hs->mail_generation) { @@ -503,7 +525,7 @@ static void TileLoop_Town(TileIndex tile) if (EconomyIsInRecession()) amt = (amt + 1) >> 1; t->supplied[CT_MAIL].new_max += amt; - t->supplied[CT_MAIL].new_act += MoveGoodsToStation(CT_MAIL, amt, ST_TOWN, t->index, stations.GetStations()); + t->supplied[CT_MAIL].new_act += MoveGoodsToStation(CT_MAIL, amt, ST_TOWN, t->index, stations.GetStations(), tile); } } @@ -680,17 +702,33 @@ static void ChangeTileOwner_Town(TileIndex tile, Owner old_owner, Owner new_owne void UpdateTownCargoTotal(Town *t) { t->cargo_accepted_total = 0; + MemSetT(t->cargo_accepted_weights, 0, lengthof(t->cargo_accepted_weights)); + /* Calculate the maximum weight based on the grid square furthest + * from the town centre. The maximum weight is two times the L-inf + * norm plus 1 so that max weight - furthest square weight == 1. */ const TileArea &area = t->cargo_accepted.GetArea(); + uint max_dist = max(DistanceMax(t->xy_aligned, area.tile), DistanceMax(t->xy_aligned, TILE_ADDXY(area.tile, area.w - 1, area.h - 1))) / AcceptanceMatrix::GRID; + t->cargo_accepted_max_weight = max_dist * 2 + 1; + + /* Collect acceptance from all grid squares. */ TILE_AREA_LOOP(tile, area) { if (TileX(tile) % AcceptanceMatrix::GRID == 0 && TileY(tile) % AcceptanceMatrix::GRID == 0) { - t->cargo_accepted_total |= t->cargo_accepted[tile]; + uint32 acc = t->cargo_accepted[tile]; + t->cargo_accepted_total |= acc; + + CargoID cid; + FOR_EACH_SET_CARGO_ID(cid, acc) { + /* For each accepted cargo, the grid square weight is the maximum weight + * minus two times the L-inf norm between this square and the centre square. */ + t->cargo_accepted_weights[cid] += t->cargo_accepted_max_weight - (DistanceMax(t->xy_aligned, tile) / AcceptanceMatrix::GRID) * 2; + } } } } /** - * Update accepted town cargoes around a specific tile. + * Update accepted and produced town cargoes around a specific tile. * @param t The town to update. * @param start Update the values around this tile. * @param update_total Set to true if the total cargo acceptance should be updated. @@ -700,7 +738,7 @@ static void UpdateTownCargoes(Town *t, TileIndex start, bool update_total = true CargoArray accepted, produced; uint32 dummy; - /* Gather acceptance for all houses in an area around the start tile. + /* Gather acceptance and production for all houses in an area around the start tile. * The area is composed of the square the tile is in, extended one square in all * directions as the coverage area of a single station is bigger than just one square. */ TileArea area = AcceptanceMatrix::GetAreaForTile(start, 1); @@ -1537,6 +1575,11 @@ static void DoCreateTown(Town *t, TileIndex tile, uint32 townnameparts, TownSize t->larger_town = city; + /* Cache the aligned tile index of the centre tile. */ + uint town_x = (TileX(t->xy) / AcceptanceMatrix::GRID) * AcceptanceMatrix::GRID; + uint town_y = (TileY(t->xy) / AcceptanceMatrix::GRID) * AcceptanceMatrix::GRID; + t->xy_aligned= TileXY(town_x, town_y); + int x = (int)size * 16 + 3; if (size == TSZ_RANDOM) x = (Random() & 0xF) + 8; /* Don't create huge cities when founding town in-game */ @@ -1987,6 +2030,9 @@ static inline bool CanBuildHouseHere(TileIndex tile, TownID town, bool noslope) Slope slope = GetTileSlope(tile); if ((noslope && slope != SLOPE_FLAT) || IsSteepSlope(slope)) return false; + /* Недопустимо строительство объекта под землей */ + if (IsUnderground(tile)) return false; + /* building under a bridge? */ if (MayHaveBridgeAbove(tile) && IsBridgeAbove(tile)) return false; @@ -2422,6 +2468,8 @@ CommandCost CmdRenameTown(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 t->UpdateVirtCoord(); InvalidateWindowData(WC_TOWN_DIRECTORY, 0, 1); + InvalidateWindowClassesData(WC_TOWN_VIEW); + InvalidateWindowClassesData(WC_INDUSTRY_VIEW); UpdateAllStationVirtCoords(); } return CommandCost(); diff --git a/src/town_gui.cpp b/src/town_gui.cpp index 624cc7446..696cd5b96 100644 --- a/src/town_gui.cpp +++ b/src/town_gui.cpp @@ -31,6 +31,7 @@ #include "townname_func.h" #include "core/geometry_func.hpp" #include "genworld.h" +#include "cargodest_gui.h" #include "widgets/town_widget.h" @@ -297,10 +298,13 @@ struct TownViewWindow : Window { private: Town *town; ///< Town displayed by the window. + CargoDestinationList dest_list; ///< Sorted list of demand destinations. + uint dest_list_top; ///< Top coordinate of the destination list in the #WID_TV_INFOPANEL widget. + public: static const int WID_TV_HEIGHT_NORMAL = 150; - TownViewWindow(const WindowDesc *desc, WindowNumber window_number) : Window() + TownViewWindow(const WindowDesc *desc, WindowNumber window_number) : Window(), dest_list(Town::Get(window_number)) { this->CreateNestedTree(desc); @@ -400,6 +404,8 @@ public: DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y += FONT_HEIGHT_NORMAL, STR_TOWN_VIEW_NOISE_IN_TOWN); } + y = this->dest_list.DrawList(r.left, r.right, y); + if (this->town->text != NULL) { SetDParamStr(0, this->town->text); DrawStringMultiLine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y += FONT_HEIGHT_NORMAL, UINT16_MAX, STR_JUST_RAW_STRING, TC_BLACK); @@ -442,6 +448,10 @@ public: case WID_TV_DELETE: // delete town - only available on Scenario editor DoCommandP(0, this->window_number, 0, CMD_DELETE_TOWN | CMD_MSG(STR_ERROR_TOWN_CAN_T_DELETE)); break; + + case WID_TV_INFO: // jump to demand destination + this->dest_list.OnClick(pt.y - this->dest_list_top - this->GetWidget<NWidgetBase>(widget)->pos_y); + break; } } @@ -458,7 +468,7 @@ public: * Gets the desired height for the information panel. * @return the desired height in pixels. */ - uint GetDesiredInfoHeight(int width) const + uint GetDesiredInfoHeight(int width) { uint aimed_height = 3 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM; @@ -478,6 +488,9 @@ public: if (_settings_game.economy.station_noise_level) aimed_height += FONT_HEIGHT_NORMAL; + this->dest_list_top = aimed_height - FONT_HEIGHT_NORMAL; + aimed_height += this->dest_list.GetListHeight(); + if (this->town->text != NULL) { SetDParamStr(0, this->town->text); aimed_height += GetStringHeight(STR_JUST_RAW_STRING, width); @@ -516,6 +529,13 @@ public: /* Called when setting station noise or required cargoes have changed, in order to resize the window */ this->SetDirty(); // refresh display for current size. This will allow to avoid glitches when downgrading this->ResizeWindowAsNeeded(); + + /* Rebuild destination list if data is not zero, otherwise just resort. */ + if (data != 0) { + this->dest_list.InvalidateData(); + } else { + this->dest_list.Resort(); + } } virtual void OnQueryTextFinished(char *str) diff --git a/src/track_func.h b/src/track_func.h index 0cd3dcf31..d34d90f88 100644 --- a/src/track_func.h +++ b/src/track_func.h @@ -28,6 +28,17 @@ #define FOR_EACH_SET_TRACK(var, track_bits) FOR_EACH_SET_BIT_EX(Track, var, TrackBits, track_bits) /** + * Iterate through each set Trackdir in a TrackdirBits value. + * For more informations see FOR_EACH_SET_BIT_EX. + * + * @param var Loop index variable that stores fallowing set track. Must be of type Track. + * @param trackdir_bits The value to iterate through (any expression). + * + * @see FOR_EACH_SET_BIT_EX + */ +#define FOR_EACH_SET_TRACKDIR(var, trackdir_bits) FOR_EACH_SET_BIT_EX(Trackdir, var, TrackdirBits, trackdir_bits) + +/** * Convert an Axis to the corresponding Track * AXIS_X -> TRACK_X * AXIS_Y -> TRACK_Y diff --git a/src/track_type.h b/src/track_type.h index c0bfa0887..2a6ed2f2b 100644 --- a/src/track_type.h +++ b/src/track_type.h @@ -92,6 +92,7 @@ enum Trackdir { TRACKDIR_END, ///< Used for iterations INVALID_TRACKDIR = 0xFF, ///< Flag for an invalid trackdir }; +DECLARE_POSTFIX_INCREMENT(Trackdir); /** Define basic enum properties */ template <> struct EnumPropsT<Trackdir> : MakeEnumPropsT<Trackdir, byte, TRACKDIR_BEGIN, TRACKDIR_END, INVALID_TRACKDIR, 4> {}; diff --git a/src/trafficlight.cpp b/src/trafficlight.cpp new file mode 100644 index 000000000..4eeff8fe7 --- /dev/null +++ b/src/trafficlight.cpp @@ -0,0 +1,291 @@ +/* $Id: trafficlight.cpp $ */ + +/* + * 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 trafficlight.cpp Handling of trafficlights. */ + +#include "stdafx.h" +#include "openttd.h" +#include "landscape.h" +#include "sprite.h" +#include "viewport_func.h" +#include "road_map.h" +#include "command_func.h" +#include "cheat_func.h" +#include "animated_tile_func.h" +#include "economy_func.h" +#include "road_cmd.h" +#include "company_func.h" +#include "company_base.h" +#include "settings_type.h" +#include "trafficlight.h" +#include "trafficlight_type.h" +#include "date_func.h" +#include "company_func.h" + +#include "table/sprites.h" +#include "table/strings.h" + +#include <set> + + +/* A traffic light consist (TLC) is a set of adjacent tiles with traffic lights on them. + * They are linked together to form a big traffic light junction. */ +typedef std::set<TileIndex> TLC; + +/** + * Gets the traffic light consist (a set of adjacent tiles with traffic lights). + * If specified by the checkroadworks parameter, it returns 0 instead if road works are found within the consist. + * @param tile Tile of the traffic light consist. + * @param checkroadworks Should we check for roadworks in the consist (and return 0 if found). + * @return visited The traffic light consist (TLC); if checkroadworks == true and roadworks were found, return 0 instead. + */ +TLC *GetTrafficLightConsist(TileIndex tile, bool checkroadworks) +{ + TLC *visited; + TLC *candidates; + + visited = new TLC; + candidates = new TLC; + candidates->insert(tile); + + while (!candidates->empty()) { + TLC::iterator cur = candidates->begin(); + if (checkroadworks && HasRoadWorks(*cur)) { + delete visited; + delete candidates; + return 0; + } + uint8 distance_between_traffic_lights = _tlc_distance[_settings_game.construction.max_tlc_distance]; + for (int i = 0; i < distance_between_traffic_lights; i++) { + TileIndex neighbor = *cur + ToTileIndexDiff(_tl_check_offsets[i]); + if (HasTrafficLights(neighbor) && (visited->find(neighbor) == visited->end())) candidates->insert(neighbor); + } + visited->insert(*cur); + candidates->erase(cur); + } + delete candidates; + return visited; +} + +/** + * Gets the lowest TileIndex of the traffic light consist or 0 if roadworks + * are found in the consist. + * @param tile Tile of the traffic light consist. + * @return lowest TileIndex in the consist or 0 if roadworks were found. + */ +TileIndex GetTLCLowestTileIndexOrRoadWorks(TileIndex tile) +{ + TLC *consist = GetTrafficLightConsist(tile, true); + TileIndex result = 0; + if (consist != 0) result = *(consist->begin()); + delete consist; + return result; +} + +/** + * Returns the state of the trafficlights on a tile. + * @note In the scenario editor trafficlights are disabled. + * @param tile This tile. + * @pre The tile must have trafficlights. + * @return Trafficlights state or disabled state. + */ +TrafficLightState GetTLState(TileIndex tile) +{ + assert(HasTrafficLights(tile)); + if (_game_mode == GM_EDITOR) return TLS_OFF; ///< All lights are off in scenario editor. + tile = GetTLCLowestTileIndexOrRoadWorks(tile); + if (tile == 0) return TLS_OFF; ///< All lights are off when roadworks are in the consist. + + uint16 tl_total = 16 * _settings_game.construction.traffic_lights_green_phase; // There are (16 * patchsetting) "TL ticks". + uint16 tl_tick = ((_tick_counter / 16) + 5 * TileX(tile) + 7 * TileY(tile)) % tl_total; // Each "TL tick" consists of 16 gameticks. + + if (tl_tick < ((tl_total / 2) - 2)) return TLS_X_GREEN_Y_RED; ///< SW and NE are green, NW and SE are red. + if (tl_tick < ((tl_total / 2) - 1)) return TLS_X_YELLOW_Y_RED; ///< SW and NE are yellow, NW and SE are red. + if (tl_tick < (tl_total / 2)) return TLS_X_RED_Y_REDYELLOW; ///< SW and NE are red, NW and SE are red-yellow. + if (tl_tick < (tl_total - 2)) return TLS_X_RED_Y_GREEN; ///< SW and NE are red, NW and SE are green. + if (tl_tick < (tl_total - 1)) return TLS_X_RED_Y_YELLOW; ///< SW and NE are red, NW and SE are yellow. + if (tl_tick < tl_total) return TLS_X_REDYELLOW_Y_RED; ///< SW and NE are red-yellow, NW and SE are red. + + NOT_REACHED(); + return TLS_OFF; +} + +/** + * Which directions are disallowed due to the TLState (red lights..). + */ +static const TrackdirBits _tls_to_trackdir[7] = { + TRACKDIR_BIT_MASK, ///< 0) all directions disallowed + TRACKDIR_BIT_Y_NW | TRACKDIR_BIT_Y_SE | ///< 1) all directions from + TRACKDIR_BIT_UPPER_E | TRACKDIR_BIT_LOWER_W | ///< y sides + TRACKDIR_BIT_LEFT_S | TRACKDIR_BIT_RIGHT_N, ///< are disallowed + TRACKDIR_BIT_MASK, ///< 2) all directions disallowed + TRACKDIR_BIT_MASK, ///< 3) all directions disallowed + TRACKDIR_BIT_X_SW | TRACKDIR_BIT_X_NE | ///< 4) all directions from + TRACKDIR_BIT_UPPER_W | TRACKDIR_BIT_LOWER_E | ///< x sides + TRACKDIR_BIT_LEFT_N | TRACKDIR_BIT_RIGHT_S, ///< are disallowed + TRACKDIR_BIT_MASK, ///< 5) all directions disallowed + TRACKDIR_BIT_MASK, ///< 6) all directions disallowed +}; + +/** + * Which directions in tile are allowed to be taken due to adjacent traffic lights (traffic light consist). + * @param tile Tile to search on. + * @return trackdirbits Bitmask of allowed directions. + */ +TrackdirBits GetIntraTLCAllowedDirections(TileIndex tile) +{ + TrackdirBits trackdirbits = TRACKDIR_BIT_NONE; + + if (HasTrafficLights(tile + TileDiffXY( 1, 0))) // SW. + trackdirbits |= TRACKDIR_BIT_X_NE | TRACKDIR_BIT_LOWER_E | TRACKDIR_BIT_LEFT_N; + if (HasTrafficLights(tile + TileDiffXY( 0, 1))) // SE + trackdirbits |= TRACKDIR_BIT_Y_NW | TRACKDIR_BIT_LOWER_W | TRACKDIR_BIT_RIGHT_N; + if (HasTrafficLights(tile + TileDiffXY( 0, -1))) // NW. + trackdirbits |= TRACKDIR_BIT_Y_SE | TRACKDIR_BIT_UPPER_E | TRACKDIR_BIT_LEFT_S; + if (HasTrafficLights(tile + TileDiffXY(-1, 0))) // NE. + trackdirbits |= TRACKDIR_BIT_X_SW | TRACKDIR_BIT_UPPER_W | TRACKDIR_BIT_RIGHT_S; + + return trackdirbits; +} + +/** + * Get a bitmask of the directions forbidden to drive on due to traffic light(s). + * @param tile Tile to check. + * @return Bitmask of forbidden directions. + */ +TrackdirBits GetTrafficLightDisallowedDirections(TileIndex tile) +{ + return (_tls_to_trackdir[GetTLState(tile)] & ~GetIntraTLCAllowedDirections(tile)); +} + +/** + * Checks if the size of a traffic light consist is within the allowed range. + * @param tile Tile to check (can also be a new tile to be added to the TLC). + * @return result True if the TLC size is within the allowed range, else false. + */ +bool CheckTLCSize(TileIndex tile) +{ + if (_settings_game.construction.max_tlc_size == 0) return true; + TLC *consist = GetTrafficLightConsist(tile, false); + bool result = (consist->size() <= _settings_game.construction.max_tlc_size); + delete consist; + return result; +} + +/** + * Build traffic lights on a crossing. + * @param tile Tile where to place the traffic lights. + * @param flags Operation to perform. + * @param p1 Unused. + * @param p2 Unused. + * @return CommandCost Cost or error. + */ +CommandCost CmdBuildTrafficLights(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + /* Check if traffic lights are enabled. */ + if (!_settings_game.construction.traffic_lights) return CMD_ERROR; // Sanity check. + + /* Check for correct location (road). */ + if (!IsTileType(tile, MP_ROAD) || GetRoadTileType(tile) != ROAD_TILE_NORMAL) return_cmd_error(STR_ERROR_THERE_IS_NO_ROAD); + + /* Check owner only if a valid player is executing this command. */ + if (Company::IsValidID(_current_company)) { + Owner owner = GetTileOwner(tile); + if (owner == OWNER_TOWN) { + if (!_settings_game.construction.allow_building_tls_in_towns) return_cmd_error(STR_ERROR_TRAFFIC_LIGHTS_NOT_ALLOWED_ON_TOWN_ROADS); + } else { + if (owner != OWNER_NONE && !IsTileOwner(tile, _current_company)) return_cmd_error(STR_ERROR_AREA_IS_OWNED_BY_ANOTHER); // Owned by ... already displayed in CheckOwnership. + } + } + + /* Check junction and already built. */ + if (CountBits(GetAllRoadBits(tile)) < 3) return_cmd_error(STR_ERROR_CAN_ONLY_BE_PLACED_ON_ROAD_JUNCTIONS); + if (HasTrafficLights(tile)) return_cmd_error(STR_ERROR_ALREADY_BUILT); + + if (!CheckTLCSize(tile)) return_cmd_error(STR_ERROR_TRAFFIC_LIGHT_CONSIST_TOO_BIG); + + /* Now we may build the traffic lights. */ + if (flags & DC_EXEC) { + MakeTrafficLights(tile); + AddAnimatedTile(tile); + MarkTileDirtyByTile(tile); + } + return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_SIGNALS]); +} + +/** + * Removes traffic lights from a tile. + * @param tile Tile where to remove the traffic lights. + * @param flags Operation to perform. + * @param p1 Unused. + * @param p2 Unused. + * @return CommandCost Cost or error. + */ +CommandCost CmdRemoveTrafficLights(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + /* Check for correct location (road with traffic lights). */ + if (!IsTileType(tile, MP_ROAD) || GetRoadTileType(tile) != ROAD_TILE_NORMAL || !HasTrafficLights(tile)) return CMD_ERROR; + + /* Check owner, but only if a valid player is executing this command. */ + if (Company::IsValidID(_current_company)) { + Owner owner = GetTileOwner(tile); + if (owner == OWNER_TOWN) { + if (!_settings_game.construction.allow_building_tls_in_towns && !_cheats.magic_bulldozer.value) return_cmd_error(STR_ERROR_TRAFFIC_LIGHTS_NOT_ALLOWED_ON_TOWN_ROADS); + } else { + if (owner != OWNER_NONE && !IsTileOwner(tile, _current_company)) return CMD_ERROR; // Owned by ... already displayed in CheckOwnership. + } + } + + /* Now we may remove the traffic lights. */ + if (flags & DC_EXEC) { + DeleteAnimatedTile(tile); + ClearTrafficLights(tile); + MarkTileDirtyByTile(tile); + } + return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_SIGNALS]); +} +/** + * Clear all traffic lights from the map. + */ +void ClearAllTrafficLights() +{ + /* Iterate over the whole map and remove any trafficlights found. */ + for (TileIndex tile = 0; tile < MapSize(); tile++) { + if (HasTrafficLights(tile)) { + DoCommand(tile, 0, 0, DC_EXEC, CMD_REMOVE_TRAFFICLIGHTS); + } + } +} + +/** + * Draws traffic lights on a tile. + * @param ti TileInfo of the tile to draw on. + */ +void DrawTrafficLights(TileInfo* ti) +{ + RoadBits road = GetAllRoadBits(ti->tile); + TrafficLightState state = GetTLState(ti->tile); + + const TileIndex neighbor[4] = { ti->tile + TileDiffXY(1, 0), // SW. + ti->tile + TileDiffXY(0, 1), // SE. + ti->tile + TileDiffXY(0, -1), // NW. + ti->tile + TileDiffXY(-1, 0)}; // NE. + const RoadBits rb[4] = {ROAD_SW, ROAD_SE, ROAD_NW, ROAD_NE}; + + /* Draw the four directions. */ + byte rs = _settings_game.vehicle.road_side; + for (int i = 0; i < 4; i++) { + if (road & rb[i] && !HasTrafficLights(neighbor[i])) { + DisallowedRoadDirections drd = DRD_NONE; + if (IsTileType(neighbor[i], MP_ROAD) && IsNormalRoad(neighbor[i])) drd = GetDisallowedRoadDirections(neighbor[i]); + if (drd != ((i % 2 == 0) ? DRD_SOUTHBOUND : DRD_NORTHBOUND) && drd != DRD_BOTH) + DrawRoadDetail(_tls_to_sprites[state][i], ti, _tl_offsets[rs][i].x, _tl_offsets[rs][i].y, 12); + } + } +} diff --git a/src/trafficlight.h b/src/trafficlight.h new file mode 100644 index 000000000..4563a690c --- /dev/null +++ b/src/trafficlight.h @@ -0,0 +1,133 @@ +/* $Id: trafficlight.h $ */ + +/* + * 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 trafficlight.h variables used for handling trafficlights. */ + + +/** + * Used for synchronising traffic light signals. + * Number below is how far we look into the _tl_check_offsets array when + * placing trafficlights, based on _settings_game.construction.max_tlc_distance. + */ +static const uint8 _tlc_distance[5] = { +0, ///< no synchronizing. +8, ///< adjecant tiles only. +24, ///< 2 tiles away. +48, ///< 3 tiles away. +80 ///< 4 tiles away. +}; + +/** TileDiffs for the adjacent tiles and those a little further away. */ +static const TileIndexDiffC _tl_check_offsets[80] = { + /* Tiles next to this tile (8 tiles). */ + {-1, -1}, + { 0, -1}, + { 1, -1}, + { 1, 0}, + { 1, 1}, + { 0, 1}, + {-1, 1}, + {-1, 0}, + /* Tiles two tiles away from this tile (16 tiles). */ + {-2, -2}, + {-1, -2}, + { 0, -2}, + { 1, -2}, + { 2, -2}, + { 2, -1}, + { 2, 0}, + { 2, 1}, + { 2, 2}, + { 1, 2}, + { 0, 2}, + {-1, 2}, + {-2, 2}, + {-2, 1}, + {-2, 0}, + {-2, -1}, + /* Tiles three tiles away from this tile (24 tiles). */ + {-3, -3}, + {-3, -2}, + {-3, -1}, + {-3, 0}, + {-3, 1}, + {-3, 2}, + {-3, 3}, + {-2, 3}, + {-1, 3}, + { 0, 3}, + { 1, 3}, + { 2, 3}, + { 3, 3}, + { 3, 2}, + { 3, 1}, + { 3, 0}, + { 3, -1}, + { 3, -2}, + { 3, -3}, + { 2, -3}, + { 1, -3}, + { 0, -3}, + {-1, -3}, + {-2, -3}, + /* Tiles four tiles away from this tile (32 tiles). */ + {-4, -4}, + {-3, -4}, + {-2, -4}, + {-1, -4}, + { 0, -4}, + { 1, -4}, + { 2, -4}, + { 3, -4}, + { 4, -4}, + { 4, -3}, + { 4, -2}, + { 4, -1}, + { 4, 0}, + { 4, 1}, + { 4, 2}, + { 4, 3}, + { 4, 4}, + { 3, 4}, + { 2, 4}, + { 1, 4}, + { 0, 4}, + {-1, 4}, + {-2, 4}, + {-3, 4}, + {-4, 4}, + {-4, 3}, + {-4, 2}, + {-4, 1}, + {-4, 0}, + {-4, -1}, + {-4, -2}, + {-4, -3} +}; + +/** + * Drawing offsets for the traffic light posts [roadside (left, right)][direction (SW, SE, NW, NE)]. + */ +static const Point _tl_offsets[2][4] = { + {{15, 1}, {14, 15}, {1, 0}, {0, 14}}, // Left side driving. + {{15, 14}, {1, 15}, {14, 0}, {0, 1}} // Right side driving. +}; + +/** + * Sprites needed for the various states of a TL crossing [state][direction]. + */ +static const SpriteID _tls_to_sprites[7][4] = { + {SPR_TL_SW_NONE, SPR_TL_SE_NONE, SPR_TL_NW_NONE, SPR_TL_NE_NONE}, + {SPR_TL_SW_GREEN, SPR_TL_SE_RED, SPR_TL_NW_RED, SPR_TL_NE_GREEN}, + {SPR_TL_SW_YELLOW, SPR_TL_SE_RED, SPR_TL_NW_RED, SPR_TL_NE_YELLOW}, + {SPR_TL_SW_RED, SPR_TL_SE_RED_YELLOW, SPR_TL_NW_RED_YELLOW, SPR_TL_NE_RED}, + {SPR_TL_SW_RED, SPR_TL_SE_GREEN, SPR_TL_NW_GREEN, SPR_TL_NE_RED}, + {SPR_TL_SW_RED, SPR_TL_SE_YELLOW, SPR_TL_NW_YELLOW, SPR_TL_NE_RED}, + {SPR_TL_SW_RED_YELLOW, SPR_TL_SE_RED, SPR_TL_NW_RED, SPR_TL_NE_RED_YELLOW} +}; diff --git a/src/trafficlight_func.h b/src/trafficlight_func.h new file mode 100644 index 000000000..d72331308 --- /dev/null +++ b/src/trafficlight_func.h @@ -0,0 +1,19 @@ +/* $Id: trafficlight_func.h $ */ + +/* + * 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 trafficlight_func.h Functions related to trafficlights. */ + +#include "road_map.h" + +TrackdirBits GetTrafficLightDisallowedDirections(TileIndex tile); +void DrawTrafficLights(TileInfo* ti); + +CommandCost CmdBuildTrafficLights(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text); +CommandCost CmdRemoveTrafficLights(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text); +void ClearAllTrafficLights(); diff --git a/src/trafficlight_type.h b/src/trafficlight_type.h new file mode 100644 index 000000000..8cddb8be8 --- /dev/null +++ b/src/trafficlight_type.h @@ -0,0 +1,21 @@ +/* $Id: trafficlight_type.h $ */ + +/* + * 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 trafficlight_type.h Types related to the states of trafficlights.*/ + +/* Which state a traffic light is in. */ +enum TrafficLightState { + TLS_OFF, ///< all lights are off (during roadworks; always in scedit) + TLS_X_GREEN_Y_RED, ///< SW and NE are green, NW and SE are red + TLS_X_YELLOW_Y_RED, ///< SW and NE are yellow, NW and SE are red + TLS_X_RED_Y_REDYELLOW, ///< SW and NE are red, NW and SE are red-yellow + TLS_X_RED_Y_GREEN, ///< SW and NE are red, NW and SE are green + TLS_X_RED_Y_YELLOW, ///< SW and NE are red, NW and SE are yellow + TLS_X_REDYELLOW_Y_RED, ///< SW and NE are red-yellow, NW and SE are red +}; diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index a01612511..280643b3c 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -34,6 +34,7 @@ #include "newgrf.h" #include "order_backup.h" #include "zoom_func.h" +#include "cargodest_func.h" #include "table/strings.h" #include "table/train_cmd.h" @@ -156,6 +157,8 @@ void Train::ConsistChanged(bool same_length) u->InvalidateNewGRFCache(); } + uint32 cargo_mask = 0; + for (Train *u = this; u != NULL; u = u->Next()) { const Engine *e_u = u->GetEngine(); const RailVehicleInfo *rvi_u = &e_u->u.rail; @@ -200,7 +203,9 @@ void Train::ConsistChanged(bool same_length) } } + /* Store carried cargo. */ u->cargo_cap = e_u->DetermineCapacity(u); + if (u->cargo_type != INVALID_CARGO && u->cargo_cap > 0) SetBit(cargo_mask, u->cargo_type); u->vcache.cached_cargo_age_period = GetVehicleProperty(u, PROP_TRAIN_CARGO_AGE_PERIOD, e_u->info.cargo_age_period); /* check the vehicle length (callback) */ @@ -231,6 +236,7 @@ void Train::ConsistChanged(bool same_length) } /* store consist weight/max speed in cache */ + this->vcache.cached_cargo_mask = cargo_mask; this->vcache.cached_max_speed = max_speed; this->tcache.cached_tilt = train_can_tilt; this->tcache.cached_max_curve_speed = this->GetCurveSpeedLimit(); @@ -1110,6 +1116,8 @@ static void NormaliseTrainHead(Train *head) /* Not a front engine, i.e. a free wagon chain. No need to do more. */ if (!head->IsFrontEngine()) return; + PrefillRouteLinks(head); + /* Update the refit button and window */ InvalidateWindowData(WC_VEHICLE_REFIT, head->index, VIWD_CONSIST_CHANGED); SetWindowWidgetDirty(WC_VEHICLE_VIEW, head->index, WID_VV_REFIT); @@ -1296,6 +1304,9 @@ CommandCost CmdMoveRailVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, u CheckCargoCapacity(dst_head); } + /* Pre-fill route links after adding a vehicle. */ + if (dst_head != NULL && dst_head->IsFrontEngine()) PrefillRouteLinks(dst_head); + /* We are undoubtedly changing something in the depot and train list. */ InvalidateWindowData(WC_VEHICLE_DEPOT, src->tile); InvalidateWindowClassesData(WC_TRAINS_LIST, 0); @@ -1828,6 +1839,17 @@ void ReverseTrainDirection(Train *v) return; } + /* We are inside tunnel/bidge with signals, reversing will close the entrance. */ + if (HasWormholeSignals(v->tile)) { + /* Flip signal on tunnel entrance tile red. */ + SetBitTunnelBridgeExit(v->tile); + MarkTileDirtyByTile(v->tile); + /* Clear counters. */ + v->wait_counter = 0; + v->load_unload_ticks = 0; + return; + } + /* TrainExitDir does not always produce the desired dir for depots and * tunnels/bridges that is needed for UpdateSignalsOnSegment. */ DiagDirection dir = TrainExitDir(v->direction, v->track); @@ -2164,6 +2186,42 @@ static bool CheckTrainStayInDepot(Train *v) return false; } +static void HandleLastTunnelBridgeSignals(TileIndex tile, TileIndex end, DiagDirection dir, bool free) +{ + if (IsBridge(end) && _m[end].m2 > 0){ + /* Clearing last bridge signal. */ + uint16 m = _m[end].m2; + byte i = 15; + while((m & 0x8000) == 0 && --i > 0) m <<= 1; + ClrBit(_m[end].m2, i); + + uint x = TileX(end)* TILE_SIZE; + uint y = TileY(end)* TILE_SIZE; + uint distance = (TILE_SIZE * _settings_client.gui.simulated_wormhole_signals) * ++i; + switch (dir) { + default: NOT_REACHED(); + case DIAGDIR_NE: MarkTileDirtyByTile(TileVirtXY(x - distance, y)); break; + case DIAGDIR_SE: MarkTileDirtyByTile(TileVirtXY(x, y + distance)); break; + case DIAGDIR_SW: MarkTileDirtyByTile(TileVirtXY(x + distance, y)); break; + case DIAGDIR_NW: MarkTileDirtyByTile(TileVirtXY(x, y - distance)); break; + } + MarkTileDirtyByTile(tile); + } + if (free) { + /* Open up the wormhole and clear m2. */ + _m[tile].m2 = 0; + _m[end].m2 = 0; + + if (IsTunnelBridgeWithSignRed(end)) { + ClrBitTunnelBridgeExit(end); + if (!_settings_client.gui.show_track_reservation) MarkTileDirtyByTile(end); + } else if (IsTunnelBridgeWithSignRed(tile)) { + ClrBitTunnelBridgeExit(tile); + if (!_settings_client.gui.show_track_reservation) MarkTileDirtyByTile(tile); + } + } +} + /** * Clear the reservation of \a tile that was just left by a wagon on \a track_dir. * @param v %Train owning the reservation. @@ -2179,7 +2237,8 @@ static void ClearPathReservation(const Train *v, TileIndex tile, Trackdir track_ if (GetTunnelBridgeDirection(tile) == ReverseDiagDir(dir)) { TileIndex end = GetOtherTunnelBridgeEnd(tile); - if (TunnelBridgeIsFree(tile, end, v).Succeeded()) { + bool free = TunnelBridgeIsFree(tile, end, v).Succeeded(); + if (free) { /* Free the reservation only if no other train is on the tiles. */ SetTunnelBridgeReservation(tile, false); SetTunnelBridgeReservation(end, false); @@ -2189,6 +2248,7 @@ static void ClearPathReservation(const Train *v, TileIndex tile, Trackdir track_ MarkTileDirtyByTile(end); } } + if (HasWormholeSignals(tile)) HandleLastTunnelBridgeSignals(tile, end, dir, free); } } else if (IsRailStationTile(tile)) { TileIndex new_tile = TileAddByDiagDir(tile, dir); @@ -2789,8 +2849,6 @@ int Train::UpdateSpeed() */ static void TrainEnterStation(Train *v, StationID station) { - v->last_station_visited = station; - /* check if a train ever visited this station before */ Station *st = Station::Get(station); if (!(st->had_vehicle_of_type & HVOT_TRAIN)) { @@ -2809,7 +2867,7 @@ static void TrainEnterStation(Train *v, StationID station) v->force_proceed = TFP_NONE; SetWindowDirty(WC_VEHICLE_VIEW, v->index); - v->BeginLoading(); + v->BeginLoading(station); TriggerStationRandomisation(st, v->tile, SRT_TRAIN_ARRIVES); TriggerStationAnimation(st, v->tile, SAT_TRAIN_ARRIVES); @@ -3055,6 +3113,101 @@ static Vehicle *CheckTrainAtSignal(Vehicle *v, void *data) return t; } + + +/** Find train in front and keep distance between trains in tunnel/bridge. */ +static Vehicle *FindSpaceBetweenTrainsEnum(Vehicle *v, void *data) +{ + /* Don't look at wagons between front and back of train. */ + if (v->type != VEH_TRAIN || (v->Previous() != NULL && v->Next() != NULL)) return NULL; + + const Vehicle *u = (Vehicle*)data; + int32 a, b = 0; + + switch (u->direction) { + default: NOT_REACHED(); + case DIR_NE: a = u->x_pos; b = v->x_pos; break; + case DIR_SE: a = v->y_pos; b = u->y_pos; break; + case DIR_SW: a = v->x_pos; b = u->x_pos; break; + case DIR_NW: a = u->y_pos; b = v->y_pos; break; + } + + if (a > b && a <= (b + (int)(Train::From(u)->wait_counter)) + (int)(TILE_SIZE)) return v; + return NULL; +} + +static bool IsToCloseBehindTrain(Vehicle *v, TileIndex tile, bool check_endtile) +{ + Train *t = (Train *)v; + + if (t->force_proceed != 0) return false; + + if (HasVehicleOnPos(t->tile, v, &FindSpaceBetweenTrainsEnum)) { + /* Revert train if not going with tunnel direction. */ + if (DirToDiagDir(t->direction) != GetTunnelBridgeDirection(t->tile)) { + v->cur_speed = 0; + ToggleBit(t->flags, VRF_REVERSING); + } + return true; + } + /* Cover blind spot at end of tunnel bridge. */ + if (check_endtile){ + if (HasVehicleOnPos(GetOtherTunnelBridgeEnd(t->tile), v, &FindSpaceBetweenTrainsEnum)) { + /* Revert train if not going with tunnel direction. */ + if (DirToDiagDir(t->direction) != GetTunnelBridgeDirection(t->tile)) { + v->cur_speed = 0; + ToggleBit(t->flags, VRF_REVERSING); + } + return true; + } + } + + return false; +} + +/** Simulate signals in tunnel - bridge. */ +static bool CheckTrainStayInWormHole(Train *t, TileIndex tile) +{ + if (t->force_proceed != 0) return false; + + /* When not exit reverse train. */ + if (!IsTunnelBridgeExit(tile)) { + t->cur_speed = 0; + ToggleBit(t->flags, VRF_REVERSING); + return true; + } + SigSegState seg_state = _settings_game.pf.reserve_paths ? SIGSEG_PBS : UpdateSignalsOnSegment(tile, INVALID_DIAGDIR, t->owner); + if (seg_state == SIGSEG_FULL || (seg_state == SIGSEG_PBS && !TryPathReserve(t))) { + t->cur_speed = 0; + return true; + } + + return false; +} + +static void HandleSignalBehindTrain(Train *v, uint signal_number) +{ + TileIndex tile; + switch (v->direction) { + default: NOT_REACHED(); + case DIR_NE: tile = TileVirtXY(v->x_pos + (TILE_SIZE * _settings_client.gui.simulated_wormhole_signals), v->y_pos); break; + case DIR_SE: tile = TileVirtXY(v->x_pos, v->y_pos - (TILE_SIZE * _settings_client.gui.simulated_wormhole_signals) ); break; + case DIR_SW: tile = TileVirtXY(v->x_pos - (TILE_SIZE * _settings_client.gui.simulated_wormhole_signals), v->y_pos); break; + case DIR_NW: tile = TileVirtXY(v->x_pos, v->y_pos + (TILE_SIZE * _settings_client.gui.simulated_wormhole_signals)); break; + } + + if(tile == v->tile) { + /* Flip signal on ramp. */ + if (IsTunnelBridgeWithSignRed(tile)) { + ClrBitTunnelBridgeExit(tile); + MarkTileDirtyByTile(tile); + } + } else if (IsBridge(v->tile) && signal_number <= 16) { + ClrBit(_m[v->tile].m2, signal_number); + MarkTileDirtyByTile(tile); + } +} + /** * Move a vehicle chain one movement stop forwards. * @param v First vehicle to move. @@ -3154,7 +3307,9 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse) /* Don't handle stuck trains here. */ if (HasBit(v->flags, VRF_TRAIN_STUCK)) return false; - + /* this codepath seems to be run every 5 ticks, so increase counter twice every 20 ticks */ + IncreaseStuckCounter(v->tile); + if (v->tick_counter % 4 == 0) IncreaseStuckCounter(v->tile); if (!HasSignalOnTrackdir(gp.new_tile, ReverseTrackdir(i))) { v->cur_speed = 0; v->subspeed = 0; @@ -3239,6 +3394,23 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse) goto invalid_rail; } + if (HasWormholeSignals(gp.new_tile)) { + /* If red signal stop. */ + if (v->IsFrontEngine() && v->force_proceed == 0) { + if (IsTunnelBridgeWithSignRed(gp.new_tile)) { + v->cur_speed = 0; + return false; + } + if (IsTunnelBridgeExit(gp.new_tile)) { + v->cur_speed = 0; + goto invalid_rail; + } + /* Flip signal on tunnel entrance tile red. */ + SetBitTunnelBridgeExit(gp.new_tile); + MarkTileDirtyByTile(gp.new_tile); + } + } + if (!HasBit(r, VETS_ENTERED_WORMHOLE)) { Track track = FindFirstTrack(chosen_track); Trackdir tdir = TrackDirectionToTrackdir(track, chosen_dir); @@ -3291,6 +3463,64 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse) } } } else { + /* Handle signal simulation on tunnel/bridge. */ + TileIndex old_tile = TileVirtXY(v->x_pos, v->y_pos); + if (old_tile != gp.new_tile && HasWormholeSignals(v->tile) && (v->IsFrontEngine() || v->Next() == NULL)){ + if (old_tile == v->tile) { + if (v->IsFrontEngine() && v->force_proceed == 0 && IsTunnelBridgeExit(v->tile)) goto invalid_rail; + /* Entered wormhole set counters. */ + v->wait_counter = (TILE_SIZE * _settings_client.gui.simulated_wormhole_signals) - TILE_SIZE; + v->load_unload_ticks = 0; + } + + uint distance = v->wait_counter; + bool leaving = false; + if (distance == 0) v->wait_counter = (TILE_SIZE * _settings_client.gui.simulated_wormhole_signals); + + if (v->IsFrontEngine()) { + /* Check if track in front is free and see if we can leave wormhole. */ + int z = GetSlopePixelZ(gp.x, gp.y) - v->z_pos; + if (IsTileType(gp.new_tile, MP_TUNNELBRIDGE) && !(abs(z) > 2)) { + if (CheckTrainStayInWormHole(v, gp.new_tile)) return false; + leaving = true; + } else { + if (IsToCloseBehindTrain(v, gp.new_tile, distance == 0)) { + if (distance == 0) v->wait_counter = 0; + v->cur_speed = 0; + return false; + } + /* flip signal in front to red on bridges*/ + if (distance == 0 && v->load_unload_ticks <= 15 && IsBridge(v->tile)){ + SetBit(_m[v->tile].m2, v->load_unload_ticks); + MarkTileDirtyByTile(gp.new_tile); + } + } + } + if (v->Next() == NULL) { + if (v->load_unload_ticks > 0 && v->load_unload_ticks <= 16 && distance == (TILE_SIZE * _settings_client.gui.simulated_wormhole_signals) - TILE_SIZE) HandleSignalBehindTrain(v, v->load_unload_ticks - 2); + if (old_tile == v->tile) { + /* We left ramp into wormhole. */ + v->x_pos = gp.x; + v->y_pos = gp.y; + UpdateSignalsOnSegment(old_tile, INVALID_DIAGDIR, v->owner); + } + } + if (distance == 0) v->load_unload_ticks++; + v->wait_counter -= TILE_SIZE; + + if (leaving) { // Reset counters. + v->force_proceed = 0; + v->wait_counter = 0; + v->load_unload_ticks = 0; + v->x_pos = gp.x; + v->y_pos = gp.y; + VehicleUpdatePosition(v); + VehicleUpdateViewport(v, false); + UpdateSignalsOnSegment(gp.new_tile, INVALID_DIAGDIR, v->owner); + continue; + } + } + if (IsTileType(gp.new_tile, MP_TUNNELBRIDGE) && HasBit(VehicleEnterTile(v, gp.new_tile, gp.x, gp.y), VETS_ENTERED_WORMHOLE)) { /* Perform look-ahead on tunnel exit. */ if (v->IsFrontEngine()) { @@ -3761,6 +3991,7 @@ static bool TrainLocoHandler(Train *v, bool mode) /* Handle stuck trains. */ if (!mode && HasBit(v->flags, VRF_TRAIN_STUCK)) { ++v->wait_counter; + if (v->tick_counter % 4 == 0) IncreaseStuckCounter(v->tile); /* Should we try reversing this tick if still stuck? */ bool turn_around = v->wait_counter % (_settings_game.pf.wait_for_pbs_path * DAY_TICKS) == 0 && _settings_game.pf.reverse_at_signals; diff --git a/src/train_gui.cpp b/src/train_gui.cpp index f302d759c..f733d0588 100644 --- a/src/train_gui.cpp +++ b/src/train_gui.cpp @@ -310,9 +310,11 @@ int GetTrainDetailsWndVScroll(VehicleID veh_id, TrainDetailsWindowTabs det_tab) if (det_tab == TDW_TAB_TOTALS) { // Total cargo tab CargoArray act_cargo; CargoArray max_cargo; + CargoDestSummary dests[NUM_CARGO]; for (const Vehicle *v = Vehicle::Get(veh_id); v != NULL; v = v->Next()) { act_cargo[v->cargo_type] += v->cargo.Count(); max_cargo[v->cargo_type] += v->cargo_cap; + AddVehicleCargoDestSummary(v, &dests[v->cargo_type]); } /* Set scroll-amount separately from counting, as to not compute num double @@ -320,8 +322,9 @@ int GetTrainDetailsWndVScroll(VehicleID veh_id, TrainDetailsWindowTabs det_tab) */ for (CargoID i = 0; i < NUM_CARGO; i++) { if (max_cargo[i] > 0) num++; // only count carriages that the train has + num += (int)dests[i].size(); } - num++; // needs one more because first line is description string + num += 2; // needs one more because first line is description string } else { for (const Train *v = Train::Get(veh_id); v != NULL; v = v->GetNextVehicle()) { GetCargoSummaryOfArticulatedVehicle(v, &_cargo_summary); @@ -421,12 +424,15 @@ void DrawTrainDetails(const Train *v, int left, int right, int y, int vscroll_po } else { CargoArray act_cargo; CargoArray max_cargo; + CargoDestSummary dests[NUM_CARGO]; Money feeder_share = 0; for (const Vehicle *u = v; u != NULL; u = u->Next()) { act_cargo[u->cargo_type] += u->cargo.Count(); max_cargo[u->cargo_type] += u->cargo_cap; feeder_share += u->cargo.FeederShare(); + + AddVehicleCargoDestSummary(u, &dests[u->cargo_type]); } /* draw total cargo tab */ @@ -443,6 +449,17 @@ void DrawTrainDetails(const Train *v, int left, int right, int y, int vscroll_po DrawString(left, right, y, FreightWagonMult(i) > 1 ? STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY_MULT : STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY); y += WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM; } + + for (CargoDestSummary::const_iterator row = dests[i].begin(); row != dests[i].end() && vscroll_pos > -vscroll_cap; ++row) { + if (--vscroll_pos < 0) { + SetDParam(0, i); // {SHORTCARGO} #1 + SetDParam(1, row->count); // {SHORTCARGO} #2 + SetDParam(2, row->type == ST_INDUSTRY ? STR_INDUSTRY_NAME : (row->type == ST_TOWN ? STR_TOWN_NAME : STR_COMPANY_NAME)); // {STRING1} + SetDParam(3, row->dest); // Parameter of {STRING1} + DrawString(left + 2 * WD_PAR_VSEP_WIDE, right, y, STR_VEHICLE_DETAILS_CARGO_TO); + y += WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM; + } + } } SetDParam(0, feeder_share); DrawString(left, right, y, STR_VEHICLE_INFO_FEEDER_CARGO_VALUE); diff --git a/src/tree_cmd.cpp b/src/tree_cmd.cpp index c30609e78..e3b036e66 100644 --- a/src/tree_cmd.cpp +++ b/src/tree_cmd.cpp @@ -13,6 +13,7 @@ #include "clear_map.h" #include "landscape.h" #include "tree_map.h" +#include "layer_func.h" #include "viewport_func.h" #include "command_func.h" #include "town.h" @@ -70,7 +71,8 @@ static bool CanPlantTreesOnTile(TileIndex tile, bool allow_desert) case MP_CLEAR: return !IsBridgeAbove(tile) && !IsClearGround(tile, CLEAR_FIELDS) && GetRawClearGround(tile) != CLEAR_ROCKS && - (allow_desert || !IsClearGround(tile, CLEAR_DESERT)); + (allow_desert || !IsClearGround(tile, CLEAR_DESERT)) + && !IsUnderground(tile); default: return false; } @@ -337,6 +339,10 @@ CommandCost CmdPlantTree(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 const byte tree_to_plant = GB(p1, 0, 8); // We cannot use Extract as min and max are climate specific. if (p2 >= MapSize()) return CMD_ERROR; + + /* tree only top layer */ + if (IsUnderground(p2)) return CMD_ERROR; + /* Check the tree type within the current climate */ if (tree_to_plant != TREE_INVALID && !IsInsideBS(tree_to_plant, _tree_base_by_landscape[_settings_game.game_creation.landscape], _tree_count_by_landscape[_settings_game.game_creation.landscape])) return CMD_ERROR; diff --git a/src/tunnelbridge_cmd.cpp b/src/tunnelbridge_cmd.cpp index 1bcbf7163..1055e40da 100644 --- a/src/tunnelbridge_cmd.cpp +++ b/src/tunnelbridge_cmd.cpp @@ -15,6 +15,7 @@ #include "stdafx.h" #include "newgrf_object.h" +#include "layer_func.h" #include "viewport_func.h" #include "cmd_helper.h" #include "command_func.h" @@ -30,6 +31,7 @@ #include "date_func.h" #include "clear_func.h" #include "vehicle_func.h" +#include "vehicle_gui.h" #include "sound_func.h" #include "tunnelbridge.h" #include "cheat_type.h" @@ -260,6 +262,10 @@ CommandCost CmdBuildBridge(TileIndex end_tile, DoCommandFlag flags, uint32 p1, u return_cmd_error(STR_ERROR_CAN_T_START_AND_END_ON); } + if (IsUnderground(tile_start) || IsUnderground(tile_end)) { + return_cmd_error(STR_ERROR_UNDERGROUND_CAN_T_BUILD_UNDER_GROUND); + } + Axis direction; if (TileX(tile_start) == TileX(tile_end)) { direction = AXIS_Y; @@ -610,6 +616,12 @@ CommandCost CmdBuildTunnel(TileIndex start_tile, DoCommandFlag flags, uint32 p1, for (;;) { end_tile += delta; if (!IsValidTile(end_tile)) return_cmd_error(STR_ERROR_TUNNEL_THROUGH_MAP_BORDER); + + + if (IsUnderground(start_tile) || IsUnderground(end_tile)) { + return_cmd_error(STR_ERROR_UNDERGROUND_CAN_T_BUILD_UNDER_GROUND); + } + end_tileh = GetTileSlope(end_tile, &end_z); if (start_z == end_z) break; @@ -1081,6 +1093,103 @@ static void DrawBridgeTramBits(int x, int y, int z, int offset, bool overlay, bo } } +/* Draws a signal on tunnel / bridge entrance tile. */ +static void DrawTunnelBridgeRampSignal(const TileInfo *ti) +{ + bool side = (_settings_game.vehicle.road_side != 0) &&_settings_game.construction.train_signal_side; + + static const Point SignalPositions[2][4] = { + { /* X X Y Y Signals on the left side */ + {13, 3}, { 2, 13}, { 3, 4}, {13, 14} + }, {/* X X Y Y Signals on the right side */ + {14, 13}, { 3, 3}, {13, 2}, { 3, 13} + } + }; + + uint position; + DiagDirection dir = GetTunnelBridgeDirection(ti->tile); + + switch (dir) { + default: NOT_REACHED(); + case DIAGDIR_NE: position = 0; break; + case DIAGDIR_SE: position = 2; break; + case DIAGDIR_SW: position = 1; break; + case DIAGDIR_NW: position = 3; break; + } + + uint x = TileX(ti->tile) * TILE_SIZE + SignalPositions[side][position].x; + uint y = TileY(ti->tile) * TILE_SIZE + SignalPositions[side][position].y; + uint z = ti->z; + + if (ti->tileh != SLOPE_FLAT && IsBridge(ti->tile)) z += 8; // sloped bridge head + SignalVariant variant = (_cur_year < _settings_client.gui.semaphore_build_before ? SIG_SEMAPHORE : SIG_ELECTRIC); + + SpriteID sprite; + if (variant == SIG_ELECTRIC) { + /* Normal electric signals are picked from original sprites. */ + sprite = SPR_ORIGINAL_SIGNALS_BASE + ((position << 1) + IsTunnelBridgeWithSignGreen(ti->tile)); + } else { + /* All other signals are picked from add on sprites. */ + sprite = SPR_SIGNALS_BASE + ((SIGTYPE_NORMAL - 1) * 16 + variant * 64 + (position << 1) + IsTunnelBridgeWithSignGreen(ti->tile)); + } + + AddSortableSpriteToDraw(sprite, PAL_NONE, x, y, 1, 1, TILE_HEIGHT, z, false, 0, 0, BB_Z_SEPARATOR); +} + +/* Draws a signal on tunnel / bridge entrance tile. */ +static void DrawBrigeSignalOnMiddelPart(const TileInfo *ti, TileIndex bridge_start_tile, uint z) +{ + + uint bridge_signal_position = 0; + int m2_position = 0; + + uint bridge_section = GetTunnelBridgeLength(ti->tile, bridge_start_tile) + 1; + + while (bridge_signal_position <= bridge_section) { + bridge_signal_position += _settings_client.gui.simulated_wormhole_signals; + if (bridge_signal_position == bridge_section) { + bool side = (_settings_game.vehicle.road_side != 0) && _settings_game.construction.train_signal_side; + + static const Point SignalPositions[2][4] = { + { /* X X Y Y Signals on the left side */ + {11, 3}, { 4, 13}, { 3, 4}, {11, 13} + }, {/* X X Y Y Signals on the right side */ + {11, 13}, { 4, 3}, {13, 4}, { 3, 11} + } + }; + + uint position; + + switch (GetTunnelBridgeDirection(bridge_start_tile)) { + default: NOT_REACHED(); + case DIAGDIR_NE: position = 0; break; + case DIAGDIR_SE: position = 2; break; + case DIAGDIR_SW: position = 1; break; + case DIAGDIR_NW: position = 3; break; + } + + uint x = TileX(ti->tile) * TILE_SIZE + SignalPositions[side][position].x; + uint y = TileY(ti->tile) * TILE_SIZE + SignalPositions[side][position].y; + z += 5; + + SignalVariant variant = (_cur_year < _settings_client.gui.semaphore_build_before ? SIG_SEMAPHORE : SIG_ELECTRIC); + + SpriteID sprite; + + if (variant == SIG_ELECTRIC) { + /* Normal electric signals are picked from original sprites. */ + sprite = SPR_ORIGINAL_SIGNALS_BASE + ((position << 1) + !HasBit(_m[bridge_start_tile].m2, m2_position)); + } else { + /* All other signals are picked from add on sprites. */ + sprite = SPR_SIGNALS_BASE + ((SIGTYPE_NORMAL - 1) * 16 + variant * 64 + (position << 1) + !HasBit(_m[bridge_start_tile].m2, m2_position)); + } + + AddSortableSpriteToDraw(sprite, PAL_NONE, x, y, 1, 1, TILE_HEIGHT, z, false, 0, 0, BB_Z_SEPARATOR); + } + m2_position++; + } +} + /** * Draws a tunnel of bridge tile. * For tunnels, this is rather simple, as you only need to draw the entrance. @@ -1190,6 +1299,9 @@ static void DrawTile_TunnelBridge(TileInfo *ti) AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, ti->x, ti->y, BB_data[6], BB_data[7], TILE_HEIGHT, ti->z); AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, ti->x + BB_data[4], ti->y + BB_data[5], BB_data[6], BB_data[7], TILE_HEIGHT, ti->z); + /* Draw signals for tunnel. */ + if (IsTunnelBridgeEntrance(ti->tile)) DrawTunnelBridgeRampSignal(ti); + DrawBridgeMiddle(ti); } else { // IsBridge(ti->tile) const PalSpriteID *psid; @@ -1289,6 +1401,9 @@ static void DrawTile_TunnelBridge(TileInfo *ti) } } + /* Draw signals for bridge. */ + if (HasWormholeSignals(ti->tile)) DrawTunnelBridgeRampSignal(ti); + DrawBridgeMiddle(ti); } } @@ -1427,6 +1542,9 @@ void DrawBridgeMiddle(const TileInfo *ti) if (HasCatenaryDrawn(GetRailType(rampsouth))) { DrawCatenaryOnBridge(ti); } + if (HasWormholeSignals(rampsouth)) { + IsTunnelBridgeExit(rampsouth) ? DrawBrigeSignalOnMiddelPart(ti, rampnorth, z): DrawBrigeSignalOnMiddelPart(ti, rampsouth, z); + } } /* draw roof, the component of the bridge which is logically between the vehicle and the camera */ @@ -1515,9 +1633,9 @@ static void GetTileDesc_TunnelBridge(TileIndex tile, TileDesc *td) TransportType tt = GetTunnelBridgeTransportType(tile); if (IsTunnel(tile)) { - td->str = (tt == TRANSPORT_RAIL) ? STR_LAI_TUNNEL_DESCRIPTION_RAILROAD : STR_LAI_TUNNEL_DESCRIPTION_ROAD; - } else { // IsBridge(tile) - td->str = (tt == TRANSPORT_WATER) ? STR_LAI_BRIDGE_DESCRIPTION_AQUEDUCT : GetBridgeSpec(GetBridgeType(tile))->transport_name[tt]; + td->str = (tt == TRANSPORT_RAIL) ? HasWormholeSignals(tile) ? STR_LAI_TUNNEL_DESCRIPTION_RAILROAD_SIGNAL : STR_LAI_TUNNEL_DESCRIPTION_RAILROAD : STR_LAI_TUNNEL_DESCRIPTION_ROAD; + } else { // IsBridge(tile) + td->str = (tt == TRANSPORT_WATER) ? STR_LAI_BRIDGE_DESCRIPTION_AQUEDUCT : HasWormholeSignals(tile) ? STR_LAI_BRIDGE_DESCRIPTION_RAILROAD_SIGNAL : GetBridgeSpec(GetBridgeType(tile))->transport_name[tt]; } td->owner[0] = GetTileOwner(tile); @@ -1584,6 +1702,26 @@ static void TileLoop_TunnelBridge(TileIndex tile) } } +static bool ClickTile_TunnelBridge(TileIndex tile) +{ + /* Show vehicles found in tunnel. */ + if (IsTunnelTile(tile)) { + int count = 0; + const Train *t; + TileIndex tile_end = GetOtherTunnelBridgeEnd(tile); + FOR_ALL_TRAINS(t) { + if (!t->IsFrontEngine()) continue; + if (tile == t->tile || tile_end == t->tile) { + ShowVehicleViewWindow(t); + count++; + } + if (count > 19) break; // no more than 20 windows open + } + if (count > 0) return true; + } + return false; +} + static TrackStatus GetTileTrackStatus_TunnelBridge(TileIndex tile, TransportType mode, uint sub_mode, DiagDirection side) { TransportType transport_type = GetTunnelBridgeTransportType(tile); @@ -1836,7 +1974,7 @@ extern const TileTypeProcs _tile_type_tunnelbridge_procs = { NULL, // add_accepted_cargo_proc GetTileDesc_TunnelBridge, // get_tile_desc_proc GetTileTrackStatus_TunnelBridge, // get_tile_track_status_proc - NULL, // click_tile_proc + ClickTile_TunnelBridge, // click_tile_proc NULL, // animate_tile_proc TileLoop_TunnelBridge, // tile_loop_proc ChangeTileOwner_TunnelBridge, // change_tile_owner_proc diff --git a/src/tunnelbridge_map.h b/src/tunnelbridge_map.h index 0f7f17b3a..57f338b8b 100644 --- a/src/tunnelbridge_map.h +++ b/src/tunnelbridge_map.h @@ -121,4 +121,98 @@ static inline TrackBits GetTunnelBridgeReservationTrackBits(TileIndex t) return HasTunnelBridgeReservation(t) ? DiagDirToDiagTrackBits(GetTunnelBridgeDirection(t)) : TRACK_BIT_NONE; } +/** + * Declare tunnel/bridge with signal simulation. + * @param t the tunnel/bridge tile. + */ +static inline void SetBitTunnelBridgeSignal(TileIndex t) +{ + assert(IsTileType(t, MP_TUNNELBRIDGE)); + SetBit(_m[t].m5, 5); +} + +/** + * Remove tunnel/bridge with signal simulation. + * @param t the tunnel/bridge tile. + */ +static inline void ClrBitTunnelBridgeSignal(TileIndex t) +{ + assert(IsTileType(t, MP_TUNNELBRIDGE)); + ClrBit(_m[t].m5, 5); +} + +/** + * Declare tunnel/bridge exit. + * @param t the tunnel/bridge tile. + */ +static inline void SetBitTunnelBridgeExit(TileIndex t) +{ + assert(IsTileType(t, MP_TUNNELBRIDGE)); + SetBit(_m[t].m5, 6); +} + +/** + * Remove tunnel/bridge exit declaration. + * @param t the tunnel/bridge tile. + */ +static inline void ClrBitTunnelBridgeExit(TileIndex t) +{ + assert(IsTileType(t, MP_TUNNELBRIDGE)); + ClrBit(_m[t].m5, 6); +} + +/** + * Is this a tunnel/bridge pair with signal simulation? + * On tunnel/bridge pair minimal one of the two bits is set. + * @param t the tile that might be a tunnel/bridge. + * @return true if and only if this tile is a tunnel/bridge with signal simulation. + */ +static inline bool HasWormholeSignals(TileIndex t) +{ + return IsTileType(t, MP_TUNNELBRIDGE) && (HasBit(_m[t].m5, 5) || HasBit(_m[t].m5, 6)) ; +} + +/** + * Is this a tunnel/bridge with sign on green? + * @param t the tile that might be a tunnel/bridge with sign set green. + * @pre IsTileType(t, MP_TUNNELBRIDGE) + * @return true if and only if this tile is a tunnel/bridge entrance. + */ +static inline bool IsTunnelBridgeWithSignGreen(TileIndex t) +{ + assert(IsTileType(t, MP_TUNNELBRIDGE)); + return HasBit(_m[t].m5, 5) && !HasBit(_m[t].m5, 6); +} + +static inline bool IsTunnelBridgeWithSignRed(TileIndex t) +{ + assert(IsTileType(t, MP_TUNNELBRIDGE)); + return HasBit(_m[t].m5, 5) && HasBit(_m[t].m5, 6); +} + +/** + * Is this a tunnel/bridge entrance tile with signal? + * Tunnel bridge signal simulation has allways bit 5 on at entrance. + * @param t the tile that might be a tunnel/bridge. + * @return true if and only if this tile is a tunnel/bridge entrance. + */ +static inline bool IsTunnelBridgeEntrance(TileIndex t) +{ + assert(IsTileType(t, MP_TUNNELBRIDGE)); + return HasBit(_m[t].m5, 5) ; +} + +/** + * Is this a tunnel/bridge exit? + * @param t the tile that might be a tunnel/bridge. + * @return true if and only if this tile is a tunnel/bridge exit. + */ +static inline bool IsTunnelBridgeExit(TileIndex t) +{ + assert(IsTileType(t, MP_TUNNELBRIDGE)); + return !HasBit(_m[t].m5, 5) && HasBit(_m[t].m5, 6); +} + + + #endif /* TUNNELBRIDGE_MAP_H */ diff --git a/src/underground_gui.cpp b/src/underground_gui.cpp new file mode 100644 index 000000000..958ddb2b0 --- /dev/null +++ b/src/underground_gui.cpp @@ -0,0 +1,294 @@ +/* $Id: terraform_gui.cpp 23547 2011-12-16 18:21:13Z truebrain $ */ + +/* + * 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 terraform_gui.cpp GUI related to terraforming the map. */ + +#include "stdafx.h" +#include "clear_map.h" +#include "company_func.h" +#include "company_base.h" +#include "gui.h" +#include "window_gui.h" +#include "window_func.h" +#include "layer_func.h" +#include "viewport_func.h" +#include "command_func.h" +#include "signs_func.h" +#include "sound_func.h" +#include "base_station_base.h" +#include "textbuf_gui.h" +#include "genworld.h" +#include "tree_map.h" +#include "landscape_type.h" +#include "tilehighlight_func.h" +#include "strings_func.h" +#include "newgrf_object.h" +#include "newgrf_station.h" +#include "object.h" +#include "hotkeys.h" +#include "engine_base.h" + +#include "widgets/underground_widget.h" + +#include "table/strings.h" +#include "error.h" + +void ShowError(TileIndex tile, CommandCost res, uint32 cmd) +{ + int x = TileX(tile) * TILE_SIZE; + int y = TileY(tile) * TILE_SIZE; + StringID error_part1 = GB(cmd, 16, 16); + + if (IsLocalCompany() && error_part1 != 0) { + ShowErrorMessage(error_part1, res.GetErrorMessage(), WL_INFO, x, y, res.GetTextRefStackSize(), res.GetTextRefStack()); + } +} + + +/** + * Place a escalator. + * @param tile Position to place or start dragging a station. + */ +static void PlaceUnderground_Escalator(TileIndex tile) +{ + RailType railtype = RAILTYPE_RAIL; + Axis orientation = AXIS_X; + StationClassID station_class = STAT_CLASS_DFLT; + byte station_type = 0; + + uint32 p1 = railtype | orientation << 4 | 1 << 8 | 1 << 16 | _ctrl_pressed << 24; + uint32 p2 = station_class | station_type << 8 | INVALID_STATION << 16; + + int w = 1; + int h = 1; + + uint top_tile = TopTile(tile); + uint base_tile = tile; + bool from_top = false; // Строим от верхнего слоя + + if (top_tile == base_tile) + { + from_top = true; + base_tile += LayerSize(); + }; + + uint32 cmdS = CMD_BUILD_RAIL_STATION | CMD_MSG(STR_ERROR_UNDERGROUND_CAN_T_BUILD_PART); + uint32 cmdT = CMD_BUILD_RAIL_STATION | CMD_MSG(STR_ERROR_UNDERGROUND_CAN_T_BUILD_TOP_PART); + uint32 cmdB = CMD_BUILD_RAIL_STATION | CMD_MSG(STR_ERROR_UNDERGROUND_CAN_T_BUILD_BOTTOM_PART); + CommandContainer cmdTop = { top_tile, p1, p2, cmdT, CcStation, "" }; + CommandContainer cmdBase = { base_tile, p1, p2, cmdB, CcStation, "" }; + + DoCommandFlag flags = DC_AUTO | DC_NO_WATER; + CommandCost resTop; + CommandCost res; + + // Проверяем возможность постройки верха и низа: + resTop=DoCommand(&cmdTop, flags | DC_QUERY_COST); + if (resTop.Failed()) + { + ShowError(tile, resTop, cmdT); + return; + } + + res=DoCommand(&cmdBase, flags | DC_QUERY_COST); + if (res.Failed()) + { + ShowError(tile, res, cmdB); + return; + } + + res.AddCost(resTop.GetCost()); + if (_shift_pressed || !CheckCompanyHasMoney(res)) + { + if (res.Failed()) ShowError(tile, res, cmdS); + else ShowEstimatedCostOrIncome(res.GetCost(), TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE); + return; + } + + // Станции могут быть сверху и снизу. + // Чтобы не было конфликта, нужно строить от активного слоя + CommandContainer *cmd1 = from_top ? &cmdTop : &cmdBase; + CommandContainer *cmd2 = from_top ? &cmdBase : &cmdTop; + + // Строим уровень (в активном слое) + res=DoCommand(cmd1, flags | DC_EXEC); + assert(!res.Failed()); + + // Уточняем параметры + StationID station = GetStationIndex(cmd1->tile); + cmd2->p2 = station_class | station_type << 8 | station << 16; + + // Строим уровень (в другом слое) + res=DoCommand(cmd2, flags | DC_EXEC); + assert(!res.Failed()); +} + +/** Underground toolbar managing class. */ +struct UndergroundToolbarWindow : Window { + int last_user_action; ///< Last started user action. + + UndergroundToolbarWindow(const WindowDesc *desc, WindowNumber window_number) : Window() + { + /* This is needed as we like to have the tree available on OnInit. */ + this->CreateNestedTree(desc); + this->FinishInitNested(desc, window_number); + this->last_user_action = WIDGET_LIST_END; + } + + ~UndergroundToolbarWindow() + { + } + + virtual void OnInit() + { + } + + virtual void OnClick(Point pt, int widget, int click_count) + { + switch (widget) { + case WID_UT_BUILD_ESCALATOR: // WID_TT_BUILD_ESCALATOR + HandlePlacePushButton(this, WID_UT_BUILD_ESCALATOR, SPR_CURSOR_RAIL_STATION, HT_RECT); + this->last_user_action = widget; + break; + + case WID_UT_DEMOLISH: // Demolish aka dynamite button + HandlePlacePushButton(this, WID_UT_DEMOLISH, ANIMCURSOR_DEMOLISH, HT_RECT | HT_DIAGONAL); + this->last_user_action = widget; + break; + + default: NOT_REACHED(); + } + } + + virtual void OnTimeout() + { + } + + virtual EventState OnKeyPress(uint16 key, uint16 keycode) + { + int num = CheckHotkeyMatch(underground_hotkeys, keycode, this); + if (num == -1) return ES_NOT_HANDLED; + this->OnClick(Point(), num, 1); + return ES_HANDLED; + } + + virtual void OnPlaceObject(Point pt, TileIndex tile) + { + switch (this->last_user_action) { + case WID_UT_BUILD_ESCALATOR: + PlaceUnderground_Escalator(tile); + break; + + case WID_UT_DEMOLISH: // Demolish aka dynamite button + PlaceProc_DemolishArea(tile); + break; + + default: NOT_REACHED(); + } + } + + virtual void OnPlaceDrag(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt) + { + VpSelectTilesWithMethod(pt.x, pt.y, select_method); + } + + virtual Point OnInitialPosition(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number) + { + Point pt = GetToolbarAlignedWindowPosition(sm_width); + pt.y += sm_height; + return pt; + } + + virtual void OnPlaceMouseUp(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt, TileIndex start_tile, TileIndex end_tile) + { + if (pt.x != -1) { + switch (select_proc) { + default: NOT_REACHED(); + case DDSP_DEMOLISH_AREA: + GUIPlaceProcDragXY(select_proc, start_tile, end_tile); + break; + } + } + } + + virtual void OnPlaceObjectAbort() + { + DeleteWindowById(WC_BUILD_OBJECT, 0); + this->RaiseButtons(); + } + + static Hotkey<UndergroundToolbarWindow> underground_hotkeys[]; +}; + +Hotkey<UndergroundToolbarWindow> UndergroundToolbarWindow::underground_hotkeys[] = { + Hotkey<UndergroundToolbarWindow>('D' | WKC_GLOBAL_HOTKEY, "dynamite", WID_UT_DEMOLISH), + Hotkey<UndergroundToolbarWindow>('0', "placeescalator", WID_UT_BUILD_ESCALATOR), + HOTKEY_LIST_END(UndergroundToolbarWindow) +}; +Hotkey<UndergroundToolbarWindow> *_underground_hotkeys = UndergroundToolbarWindow::underground_hotkeys; + +static const NWidgetPart _nested_underground_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), + NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_UNDERGROUND_BUILD, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_UT_BUILD_ESCALATOR), + SetFill(0, 1), SetMinimalSize(42, 22), SetDataTip(SPR_IMG_RAIL_STATION, STR_UNDERGROUND_TOOLTIP_ESCALATOR), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(64, 22), EndContainer(), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_UT_DEMOLISH), SetMinimalSize(22, 22), + SetFill(0, 1), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC), + EndContainer(), +}; + +static const WindowDesc _underground_desc( + WDP_MANUAL, 0, 0, + WC_UNDERGROUND, WC_NONE, + WDF_CONSTRUCTION, + _nested_underground_widgets, lengthof(_nested_underground_widgets) +); + +/** + * Show the toolbar for terraforming in the game. + * @param link The toolbar we might want to link to. + * @return The allocated toolbar. + */ +Window *ShowUndergroundToolbar(Window *link) +{ + if (!Company::IsValidID(_local_company)) return NULL; + + Window *w; + if (link == NULL) { + w = AllocateWindowDescFront<UndergroundToolbarWindow>(&_underground_desc, 0); + return w; + } + + /* Delete the terraform toolbar to place it again. */ + DeleteWindowById(WC_UNDERGROUND, 0, true); + w = AllocateWindowDescFront<UndergroundToolbarWindow>(&_underground_desc, 0); + /* Align the terraform toolbar under the main toolbar. */ + w->top -= w->height; + w->SetDirty(); + /* Put the linked toolbar to the left / right of it. */ + link->left = w->left + (_current_text_dir == TD_RTL ? w->width : -link->width); + link->top = w->top; + link->SetDirty(); + + return w; +} + +EventState UndergroundToolbarGlobalHotkeys(uint16 key, uint16 keycode) +{ + int num = CheckHotkeyMatch<UndergroundToolbarWindow>(_underground_hotkeys, keycode, NULL, true); + if (num == -1) return ES_NOT_HANDLED; + Window *w = ShowUndergroundToolbar(NULL); + if (w == NULL) return ES_NOT_HANDLED; + return w->OnKeyPress(key, keycode); +} diff --git a/src/underground_gui.h b/src/underground_gui.h new file mode 100644 index 000000000..81b119545 --- /dev/null +++ b/src/underground_gui.h @@ -0,0 +1,19 @@ +/* $Id: underground_gui.h 21608 2012-09-08 1:13:14 constructor $ */ + +/* + * 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 underground_gui.h GUI stuff related to terraforming. */ + +#ifndef UNDERGROUND_GUI_H +#define UNDERGROUND_GUI_H + +#include "window_type.h" + +Window *ShowUndergroundToolbar(Window *link = NULL); + +#endif /* UNDERGROUND_GUI_H */ diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 2f11421d6..b1ee33a1b 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -15,6 +15,7 @@ #include "ship.h" #include "spritecache.h" #include "timetable.h" +#include "layer_func.h" #include "viewport_func.h" #include "news_func.h" #include "command_func.h" @@ -49,6 +50,7 @@ #include "bridge_map.h" #include "tunnel_map.h" #include "depot_map.h" +#include "cargodest_func.h" #include "gamelog.h" #include "table/strings.h" @@ -206,6 +208,17 @@ uint Vehicle::Crash(bool flooded) return RandomRange(pass + 1); // Randomise deceased passengers. } +/** Marks the separation of this vehicle's order list invalid. */ +void Vehicle::MarkSeparationInvalid() +{ + if (this->orders.list != NULL) this->orders.list->MarkSeparationInvalid(); +} + +/** Sets new separation settings for this vehicle's shared orders. */ +void Vehicle::SetSepSettings(TTSepMode Mode, uint Parameter) +{ + if (this->orders.list != NULL) this->orders.list->SetSepSettings(Mode, Parameter); +} /** * Displays a "NewGrf Bug" error message for a engine, and pauses the game if not networking. @@ -268,7 +281,10 @@ Vehicle::Vehicle(VehicleType type) this->fill_percent_te_id = INVALID_TE_ID; this->first = this; this->colourmap = PAL_NONE; + this->last_station_loaded = INVALID_STATION; this->cargo_age_counter = 1; + this->current_order.index = INVALID_ORDER; + this->last_order_id = INVALID_ORDER; } /** @@ -887,9 +903,12 @@ void CallVehicleTicks() } if (v->type == VEH_TRAIN && Train::From(v)->IsWagon()) continue; - if (v->type == VEH_AIRCRAFT && v->subtype != AIR_HELICOPTER) continue; if (v->type == VEH_ROAD && !RoadVehicle::From(v)->IsFrontEngine()) continue; + v->travel_time++; + + if (v->type == VEH_AIRCRAFT && v->subtype != AIR_HELICOPTER) continue; + v->motion_counter += v->cur_speed; /* Play a running sound if the motion counter passes 256 (Do we not skip sounds?) */ if (GB(v->motion_counter, 0, 8) < v->cur_speed) PlayVehicleSound(v, VSE_RUNNING); @@ -1008,8 +1027,8 @@ void ViewportAddVehicles(DrawPixelInfo *dpi) for (int y = yl;; y = (y + (1 << 6)) & (0x3F << 6)) { for (int x = xl;; x = (x + 1) & 0x3F) { const Vehicle *v = _vehicle_viewport_hash[x + y]; // already masked & 0xFFF - while (v != NULL) { + if (LayerIndex(v->tile) == dpi->layer) if (!(v->vehstatus & VS_HIDDEN) && l <= v->coord.right && t <= v->coord.bottom && @@ -1398,6 +1417,7 @@ void VehicleEnterDepot(Vehicle *v) AddVehicleAdviceNewsItem(STR_NEWS_TRAIN_IS_WAITING + v->type, v->index); } AI::NewEvent(v->owner, new ScriptEventVehicleWaitingInDepot(v->index)); + v->MarkSeparationInvalid(); } } } @@ -1869,12 +1889,15 @@ void Vehicle::DeleteUnreachedImplicitOrders() /** * Prepare everything to begin the loading when arriving at a station. + * @param station The station ID of the station. * @pre IsTileType(this->tile, MP_STATION) || this->type == VEH_SHIP. */ -void Vehicle::BeginLoading() +void Vehicle::BeginLoading(StationID station) { assert(IsTileType(this->tile, MP_STATION) || this->type == VEH_SHIP); + this->last_station_visited = station; + if (this->current_order.IsType(OT_GOTO_STATION) && this->current_order.GetDestination() == this->last_station_visited) { this->DeleteUnreachedImplicitOrders(); @@ -1955,6 +1978,7 @@ void Vehicle::BeginLoading() implicit_order->MakeImplicit(this->last_station_visited); InsertOrder(this, implicit_order, this->cur_implicit_order_index); if (this->cur_implicit_order_index > 0) --this->cur_implicit_order_index; + this->current_order.index = implicit_order->index; /* InsertOrder disabled creation of implicit orders for all vehicles with the same implicit order. * Reenable it for this vehicle */ @@ -1966,7 +1990,35 @@ void Vehicle::BeginLoading() this->current_order.MakeLoading(false); } - Station::Get(this->last_station_visited)->loading_vehicles.push_back(this); + UpdateVehicleRouteLinks(this, station); + + /* Save the id of the order which made us arrive here. MakeLoading + * does not overwrite the index so it is still valid here. */ + this->last_order_id = this->current_order.index; + this->last_station_loaded = station; + /* If all requirements for separation are met, we can initialize it. */ + if (_settings_game.order.automatic_timetable_separation + && this->IsOrderListShared() + && this->orders.list->IsCompleteTimetable() + && (this->cur_real_order_index == 0)) { + + if (!this->orders.list->IsSeparationValid()) this->orders.list->InitializeSeparation(); + this->lateness_counter = this->orders.list->SeparateVehicle(); + } + + + Station *last_visited = Station::Get(this->last_station_visited); + last_visited->loading_vehicles.push_back(this); + + /* Update the next hop for waiting cargo. */ + CargoID cid; + FOR_EACH_SET_CARGO_ID(cid, this->vcache.cached_cargo_mask) { + /* Only update if the last update was at least route_recalc_delay ticks earlier. */ + if (CargoHasDestinations(cid) && last_visited->goods[cid].cargo_counter == 0) { + last_visited->goods[cid].cargo.UpdateCargoNextHop(last_visited, cid); + last_visited->goods[cid].cargo_counter = _settings_game.economy.cargodest.route_recalc_delay; + } + } PrepareUnload(this); @@ -1993,6 +2045,9 @@ void Vehicle::LeaveStation() /* Only update the timetable if the vehicle was supposed to stop here. */ if (this->current_order.GetNonStopType() != ONSF_STOP_EVERYWHERE) UpdateVehicleTimetable(this, false); + /* Reset travel time counter. */ + this->travel_time = 0; + this->current_order.MakeLeaveStation(); Station *st = Station::Get(this->last_station_visited); st->loading_vehicles.remove(this); @@ -2381,6 +2436,7 @@ void Vehicle::AddToShared(Vehicle *shared_chain) if (this->next_shared != NULL) this->next_shared->previous_shared = this; shared_chain->orders.list->AddVehicle(this); + shared_chain->orders.list->MarkSeparationInvalid(); } /** @@ -2393,6 +2449,7 @@ void Vehicle::RemoveFromShared() bool were_first = (this->FirstShared() == this); VehicleListIdentifier vli(VL_SHARED_ORDERS, this->type, this->owner, this->FirstShared()->index); + this->orders.list->MarkSeparationInvalid(); this->orders.list->RemoveVehicle(this); if (!were_first) { diff --git a/src/vehicle_base.h b/src/vehicle_base.h index f0502a218..9d34cb6b0 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -21,6 +21,7 @@ #include "order_func.h" #include "transport_type.h" #include "group_type.h" +#include "timetable.h" #include "base_consist.h" /** Vehicle status bits in #Vehicle::vehstatus. */ @@ -103,6 +104,7 @@ enum GroundVehicleSubtypeFlags { /** Cached often queried values common to all vehicles. */ struct VehicleCache { + uint32 cached_cargo_mask; ///< Mask of all cargoes carried by the consist. uint16 cached_max_speed; ///< Maximum speed of the consist (minimum of the max speed of all vehicles in the consist). uint16 cached_cargo_age_period; ///< Number of ticks before carried cargo is aged. @@ -211,6 +213,8 @@ public: byte waiting_triggers; ///< Triggers to be yet matched before rerandomizing the random bits. StationID last_station_visited; ///< The last station we stopped at. + StationID last_station_loaded; ///< Last station the vehicle loaded cargo at. + OrderID last_order_id; ///< Order id which caused the vehicle to arrive at the last loading station. CargoID cargo_type; ///< type of cargo this vehicle is carrying byte cargo_subtype; ///< Used for livery refits (NewGRF variations) @@ -218,6 +222,7 @@ public: VehicleCargoList cargo; ///< The cargo this vehicle is carrying uint16 cargo_age_counter; ///< Ticks till cargo is aged next. + uint32 travel_time; ///< Ticks since last loading byte day_counter; ///< Increased by one for each day byte tick_counter; ///< Increased by one for each tick byte running_ticks; ///< Number of ticks this vehicle was not stopped this day @@ -243,7 +248,7 @@ public: /** We want to 'destruct' the right class. */ virtual ~Vehicle(); - void BeginLoading(); + void BeginLoading(StationID station); void LeaveStation(); GroundVehicleCache *GetGroundVehicleCache(); @@ -761,6 +766,8 @@ public: bool HasEngineType() const; bool HasDepotOrder() const; void HandlePathfindingResult(bool path_found); + void MarkSeparationInvalid(); + void SetSepSettings(TTSepMode Mode, uint Parameter); /** * Check if the vehicle is a front engine. diff --git a/src/vehicle_cmd.cpp b/src/vehicle_cmd.cpp index 7271d27b0..545629108 100644 --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -30,6 +30,7 @@ #include "order_backup.h" #include "ship.h" #include "newgrf.h" +#include "cargodest_func.h" #include "company_base.h" #include "table/strings.h" @@ -147,6 +148,7 @@ CommandCost CmdBuildVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint if (v->IsPrimaryVehicle()) { GroupStatistics::CountVehicle(v, 1); OrderBackup::Restore(v, p2); + PrefillRouteLinks(v); } } @@ -478,6 +480,8 @@ CommandCost CmdRefitVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint default: NOT_REACHED(); } + if (front->IsPrimaryVehicle()) PrefillRouteLinks(front); + InvalidateWindowData(WC_VEHICLE_DETAILS, front->index); SetWindowDirty(WC_VEHICLE_DEPOT, front->tile); InvalidateWindowClassesData(GetWindowClassForVehicleType(v->type), 0); @@ -565,6 +569,8 @@ CommandCost CmdStartStopVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); SetWindowClassesDirty(GetWindowClassForVehicleType(v->type)); + + v->MarkSeparationInvalid(); } return CommandCost(); } diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index d6a1bec67..03ad805c9 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -2730,3 +2730,28 @@ int GetVehicleWidth(Vehicle *v, EngineImageType image_type) return vehicle_width; } + +/** + * Sum the cargo carried by a vehicle by final destination. + * @param v The vehicle. + * @param sum The cargo summary is added to this. + */ +void AddVehicleCargoDestSummary(const Vehicle *v, CargoDestSummary *sum) +{ + const VehicleCargoList::List *packets = v->cargo.Packets(); + for (VehicleCargoList::ConstIterator it = packets->begin(); it != packets->end(); ++it) { + const CargoPacket *cp = *it; + + /* Search for an existing list entry. */ + CargoDestSummary::iterator data; + for (data = sum->begin(); data != sum->end(); ++data) { + if (data->type == cp->DestinationType() && data->dest == cp->DestinationID()) { + data->count += cp->Count(); + break; + } + } + + /* Not found, insert new entry. */ + if (data == sum->end() && cp->DestinationID() != INVALID_SOURCE) sum->push_back(CargoDestSummaryData(cp->DestinationID(), cp->DestinationType(), cp->Count())); + } +} diff --git a/src/vehicle_gui.h b/src/vehicle_gui.h index 83e098dcd..c3b702fc8 100644 --- a/src/vehicle_gui.h +++ b/src/vehicle_gui.h @@ -18,6 +18,7 @@ #include "station_type.h" #include "engine_type.h" #include "company_type.h" +#include <list> void ShowVehicleRefitWindow(const Vehicle *v, VehicleOrderID order, Window *parent, bool auto_refit = false); @@ -37,6 +38,22 @@ enum VehicleInvalidateWindowData { VIWD_AUTOREPLACE = -4, ///< Autoreplace replaced the vehicle. }; +/** List item for one destination. */ +struct CargoDestSummaryData { + SourceID dest; ///< Destination ID + SourceType type; ///< Destination type + uint count; ///< Cargo count + + CargoDestSummaryData(SourceID dest, SourceType type, uint count) + : dest(dest), type(type), count(count) + { } +}; + +/** List of cargo amounts grouped by final destination. */ +typedef std::list<CargoDestSummaryData> CargoDestSummary; + +void AddVehicleCargoDestSummary(const Vehicle *v, CargoDestSummary *sum); + int DrawVehiclePurchaseInfo(int left, int right, int y, EngineID engine_number); void DrawTrainImage(const Train *v, int left, int right, int y, VehicleID selection, EngineImageType image_type, int skip, VehicleID drag_dest = INVALID_VEHICLE); diff --git a/src/vehicle_type.h b/src/vehicle_type.h index 0921b39e3..e66a28aba 100644 --- a/src/vehicle_type.h +++ b/src/vehicle_type.h @@ -72,7 +72,7 @@ enum DepotCommand { DEPOT_COMMAND_MASK = 0xFU << 28, }; -static const uint MAX_LENGTH_VEHICLE_NAME_CHARS = 32; ///< The maximum length of a vehicle name in characters including '\0' +static const uint MAX_LENGTH_VEHICLE_NAME_CHARS = 128; ///< The maximum length of a vehicle name in characters including '\0' /** The length of a vehicle in tile units. */ static const uint VEHICLE_LENGTH = 8; diff --git a/src/viewport.cpp b/src/viewport.cpp index bfcc8d9d1..10cc8f360 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -28,6 +28,8 @@ #include "stdafx.h" #include "landscape.h" +#include "layer_gui.h" +#include "layer_func.h" #include "viewport_func.h" #include "station_base.h" #include "waypoint_base.h" @@ -51,6 +53,13 @@ Point _tile_fract_coords; +struct RailTrackEndpoint { + TileIndex tile; + TrackdirBits dirs; +}; + +RailTrackEndpoint _rail_track_endpoints[4]; + struct StringSpriteToDraw { StringID string; Colours colour; @@ -1080,6 +1089,8 @@ static void ViewportAddLandscape() y_cur < MapMaxY() * TILE_SIZE) { TileIndex tile = TileVirtXY(x_cur, y_cur); + /* Валидны только клетки текущего слоя */ + if (LayerIndex(tile) == _vd.dpi.layer) if (!_settings_game.construction.freeform_edges || (TileX(tile) != 0 && TileY(tile) != 0)) { if (x_cur == ((int)MapMaxX() - 1) * TILE_SIZE || y_cur == ((int)MapMaxY() - 1) * TILE_SIZE) { uint maxh = max<uint>(TileHeight(tile), 1); @@ -1475,6 +1486,9 @@ void ViewportDoDraw(const ViewPort *vp, int left, int top, int right, int bottom _vd.dpi.dst_ptr = BlitterFactoryBase::GetCurrentBlitter()->MoveTo(old_dpi->dst_ptr, x - old_dpi->left, y - old_dpi->top); + /* Определение слоя (который будем рисовать) */ + _vd.dpi.layer = calculateLayer(vp); + ViewportAddLandscape(); ViewportAddVehicles(&_vd.dpi); @@ -1953,7 +1967,7 @@ static void PlaceObject() } -bool HandleViewportClicked(const ViewPort *vp, int x, int y) +bool HandleViewportClicked(const ViewPort *vp, int x, int y, bool double_click) { const Vehicle *v = CheckClickOnVehicle(vp, x, y); @@ -1961,6 +1975,13 @@ bool HandleViewportClicked(const ViewPort *vp, int x, int y) if (v != NULL && VehicleClicked(v)) return true; } + /* Double-clicking finishes current polyline and starts new one. */ + if (double_click && (_thd.place_mode & HT_POLY)) { + ClearRailPlacementEndpoints(); + SetTileSelectSize(1, 1); + return true; + } + /* Vehicle placement mode already handled above. */ if ((_thd.place_mode & HT_DRAG_MASK) != HT_NONE) { PlaceObject(); @@ -2113,7 +2134,7 @@ Window *TileHighlightData::GetCallbackWnd() return FindWindowById(this->window_class, this->window_number); } - +static HighLightStyle CalcPolyrailDrawstyle(Point pt); /** * Updates tile highlighting for all cases. @@ -2170,28 +2191,42 @@ void UpdateTileSelection() y1 += TILE_SIZE / 2; break; case HT_RAIL: - /* Draw one highlighted tile in any direction */ - new_drawstyle = GetAutorailHT(pt.x, pt.y); - break; case HT_LINE: - switch (_thd.place_mode & HT_DIR_MASK) { - case HT_DIR_X: new_drawstyle = HT_LINE | HT_DIR_X; break; - case HT_DIR_Y: new_drawstyle = HT_LINE | HT_DIR_Y; break; - - case HT_DIR_HU: - case HT_DIR_HL: - new_drawstyle = (pt.x & TILE_UNIT_MASK) + (pt.y & TILE_UNIT_MASK) <= TILE_SIZE ? HT_LINE | HT_DIR_HU : HT_LINE | HT_DIR_HL; - break; - - case HT_DIR_VL: - case HT_DIR_VR: - new_drawstyle = (pt.x & TILE_UNIT_MASK) > (pt.y & TILE_UNIT_MASK) ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR; + /* Handle polyline highlight */ + if (_thd.place_mode & HT_POLY) { + new_drawstyle = CalcPolyrailDrawstyle(pt); + if (new_drawstyle != HT_NONE) { + x1 = min(_thd.selstart.x, _thd.selend.x); + y1 = min(_thd.selstart.y, _thd.selend.y); + _thd.new_size.x = abs<int>(_thd.selstart.x - (_thd.selend.x & ~TILE_UNIT_MASK)) + TILE_SIZE; + _thd.new_size.y = abs<int>(_thd.selstart.y - (_thd.selend.y & ~TILE_UNIT_MASK)) + TILE_SIZE; break; - - default: NOT_REACHED(); + } + } + /* Handle regular (non-polyline) highlight */ + if (_thd.place_mode & HT_RAIL) { + /* Draw one highlighted tile in any direction */ + new_drawstyle = GetAutorailHT(pt.x, pt.y); + } else { // HT_LINE + switch (_thd.place_mode & HT_DIR_MASK) { + case HT_DIR_X: new_drawstyle = HT_LINE | HT_DIR_X; break; + case HT_DIR_Y: new_drawstyle = HT_LINE | HT_DIR_Y; break; + + case HT_DIR_HU: + case HT_DIR_HL: + new_drawstyle = (pt.x & TILE_UNIT_MASK) + (pt.y & TILE_UNIT_MASK) <= TILE_SIZE ? HT_LINE | HT_DIR_HU : HT_LINE | HT_DIR_HL; + break; + + case HT_DIR_VL: + case HT_DIR_VR: + new_drawstyle = (pt.x & TILE_UNIT_MASK) > (pt.y & TILE_UNIT_MASK) ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR; + break; + + default: NOT_REACHED(); + } + _thd.selstart.x = x1 & ~TILE_UNIT_MASK; + _thd.selstart.y = y1 & ~TILE_UNIT_MASK; } - _thd.selstart.x = x1 & ~TILE_UNIT_MASK; - _thd.selstart.y = y1 & ~TILE_UNIT_MASK; break; default: NOT_REACHED(); @@ -2455,7 +2490,31 @@ static int CalcHeightdiff(HighLightStyle style, uint distance, TileIndex start_t return (int)(h1 - h0) * TILE_HEIGHT_STEP; } -static const StringID measure_strings_length[] = {STR_NULL, STR_MEASURE_LENGTH, STR_MEASURE_LENGTH_HEIGHTDIFF}; +static void ShowLengthMeasurement(HighLightStyle style, TileIndex start_tile, TileIndex end_tile, TooltipCloseCondition close_cond = TCC_LEFT_CLICK, bool show_single_tile_length = false) +{ + static const StringID measure_strings_length[] = {STR_NULL, STR_MEASURE_LENGTH, STR_MEASURE_LENGTH_HEIGHTDIFF}; + + if (_settings_client.gui.measure_tooltip) { + uint distance = DistanceManhattan(start_tile, end_tile) + 1; + byte index = 0; + uint64 params[2]; + + if (show_single_tile_length || distance != 1) { + int heightdiff = CalcHeightdiff(style, distance, start_tile, end_tile); + /* If we are showing a tooltip for horizontal or vertical drags, + * 2 tiles have a length of 1. To bias towards the ceiling we add + * one before division. It feels more natural to count 3 lengths as 2 */ + if ((style & HT_DIR_MASK) != HT_DIR_X && (style & HT_DIR_MASK) != HT_DIR_Y) { + distance = CeilDiv(distance, 2); + } + + params[index++] = distance; + if (heightdiff != 0) params[index++] = heightdiff; + } + + ShowMeasurementTooltips(measure_strings_length[index], index, params, close_cond); + } +} /** * Check for underflowing the map. @@ -2486,8 +2545,178 @@ static void CheckOverflow(int &test, int &other, int max, int mult) test = max; } +static const struct { + Point start_point_offset; + Point direction; +} +_auto_line_by_trackdir[] = { + { { TILE_SIZE - 2, TILE_SIZE / 2 }, { -1, 0 } }, // TRACKDIR_X_NE + { { TILE_SIZE / 2, 0 }, { 0, +1 } }, // TRACKDIR_Y_SE + { { TILE_SIZE / 2 - 1, 0 }, { -1, +1 } }, // TRACKDIR_UPPER_E + { { TILE_SIZE - 1, TILE_SIZE / 2 }, { -1, +1 } }, // TRACKDIR_LOWER_E + { { TILE_SIZE / 2, 0 }, { +1, +1 } }, // TRACKDIR_LEFT_S + { { 0, TILE_SIZE / 2 }, { +1, +1 } }, // TRACKDIR_RIGHT_S + { { 0, 0 }, { 0, 0 } }, // TRACKDIR_RVREV_NE + { { 0, 0 }, { 0, 0 } }, // TRACKDIR_RVREV_SE + { { 0, TILE_SIZE / 2 }, { +1, 0 } }, // TRACKDIR_X_SW + { { TILE_SIZE / 2, TILE_SIZE - 1 }, { 0, -1 } }, // TRACKDIR_Y_NW + { { 0, TILE_SIZE / 2 - 1 }, { +1, -1 } }, // TRACKDIR_UPPER_W + { { TILE_SIZE / 2, TILE_SIZE - 1 }, { +1, -1 } }, // TRACKDIR_LOWER_W + { { TILE_SIZE - 1, TILE_SIZE / 2 - 1 }, { -1, -1 } }, // TRACKDIR_LEFT_N + { { TILE_SIZE / 2 - 1, TILE_SIZE - 1 }, { -1, -1 } } // TRACKDIR_RIGHT_N +}; + +/** + * Returns the distnce from a given point to a rail line. + * + * @param pt The point to get the distance from. + * @param start_tile Coordinates, in tile "units", of the tile where the line starts. + * @param trackdir The first trackdir of the line. + * @return X/Y coordinates of the vector that connects the 'pt' point with the line at the best path. + */ +static Point GetDistanceToAutoLine(Point pt, Point start_tile, Trackdir trackdir) +{ + assert(IsValidTrackdir(trackdir) && !IsReversingRoadTrackdir(trackdir)); + + /* calculate distance from the given point to the point where the line starts */ + Point d = { + start_tile.x + _auto_line_by_trackdir[trackdir].start_point_offset.x - pt.x, + start_tile.y + _auto_line_by_trackdir[trackdir].start_point_offset.y - pt.y + }; + /* get line direction */ + Point direction = _auto_line_by_trackdir[trackdir].direction; + /* correct the start point for "diagonal" dirs; there are two possible lines, choose the closer one */ + if (direction.x == 0) { // TRACKDIR_Y_SE and TRACKDIR_Y_NW trackdirs + d.x -= (int)(d.x > 0); + } else if (direction.y == 0) { // TRACKDIR_X_NE and TRACKDIR_X_SW trackdirs + d.y -= (int)(d.y > 0); + } + + /* calculate distance to the end of the line */ + int scale = direction.x * direction.x + direction.y * direction.y; + int length = d.x * direction.y - d.y * direction.x; + Point ret = { direction.y, -direction.x }; // 'direction' rotated 90 degree right + if (length > 0) { // is the 'p' point on the left side of the line ("up" is pointed by 'direction') + ret.x -= direction.x; + ret.y -= direction.y; + } else { + ret.x += direction.x; + ret.y += direction.y; + } + ret.x = length * ret.x / scale; + ret.y = length * ret.y / scale; + + /* test if the calculated end point is behind the start point; + * if not return the distance to the start point */ + if (((d.x < ret.x) == (direction.x < 0)) && ((d.y < ret.y) == (direction.y < 0))) return d; + + return ret; +} + +static void ClampAutoLineToMapBorders(Point *line_end, Track line_orientation) +{ + int padding = _settings_game.construction.freeform_edges ? TILE_SIZE : 0; + + Rect borders = { + padding, // left + padding, // top + MapSizeX() * TILE_SIZE - padding - 1, // right + MapSizeY() * TILE_SIZE - padding - 1 // bottom + }; + + switch (line_orientation) { + case TRACK_X: + line_end->y = Clamp(line_end->y, borders.top, borders.bottom); + break; + + case TRACK_Y: + line_end->x = Clamp(line_end->x, borders.left, borders.right); + break; + + case TRACK_UPPER: + case TRACK_LOWER: + if (line_end->x < borders.left) { + line_end->y += borders.left - line_end->x; + line_end->x += borders.left - line_end->x; + } else if (line_end->x > borders.right) { + line_end->y += borders.right - line_end->x; + line_end->x += borders.right - line_end->x; + } + if (line_end->y < borders.top) { + line_end->x += borders.top - line_end->y; + line_end->y += borders.top - line_end->y; + } else if (line_end->y > borders.bottom) { + line_end->x += borders.bottom - line_end->y; + line_end->y += borders.bottom - line_end->y; + } + break; + + case TRACK_LEFT: + case TRACK_RIGHT: + if (line_end->x < borders.left) { + line_end->y -= borders.left - line_end->x; + line_end->x += borders.left - line_end->x; + } else if (line_end->x > borders.right) { + line_end->y -= borders.right - line_end->x; + line_end->x += borders.right - line_end->x; + } + if (line_end->y < borders.top) { + line_end->x -= borders.top - line_end->y; + line_end->y += borders.top - line_end->y; + } else if (line_end->y > borders.bottom) { + line_end->x -= borders.bottom - line_end->y; + line_end->y += borders.bottom - line_end->y; + } + break; + + default: + NOT_REACHED(); + } + + assert(IsInsideMM(line_end->x, borders.left, borders.right + 1) && IsInsideMM(line_end->y, borders.top, borders.bottom + 1)); +} + +static const TrackdirBits _autoline_dirs_allowed_by_highlight_dir[] = { + TRACKDIR_BIT_X_NE | TRACKDIR_BIT_X_SW, // HT_DIR_X + TRACKDIR_BIT_Y_NW | TRACKDIR_BIT_Y_SE, // HT_DIR_Y + TRACKDIR_BIT_UPPER_E | TRACKDIR_BIT_LOWER_E | TRACKDIR_BIT_UPPER_W | TRACKDIR_BIT_LOWER_W, // HT_DIR_HU + TRACKDIR_BIT_UPPER_E | TRACKDIR_BIT_LOWER_E | TRACKDIR_BIT_UPPER_W | TRACKDIR_BIT_LOWER_W, // HT_DIR_HL + TRACKDIR_BIT_LEFT_N | TRACKDIR_BIT_RIGHT_N | TRACKDIR_BIT_LEFT_S | TRACKDIR_BIT_RIGHT_S, // HT_DIR_VL + TRACKDIR_BIT_LEFT_N | TRACKDIR_BIT_RIGHT_N | TRACKDIR_BIT_LEFT_S | TRACKDIR_BIT_RIGHT_S, // HT_DIR_VR +}; + +static Trackdir FindBestAutoLine(const Point &pt, RailTrackEndpoint *start_points, uint num_start_points, TrackdirBits allowed_trackdirs, Point *ret_start_tile, Point *ret_end_pos) +{ + Trackdir ret = INVALID_TRACKDIR; + uint best_distance = UINT_MAX; + + for (; num_start_points-- > 0; start_points++) { + /* skip invalid tiles */ + if (!IsValidTile(start_points->tile)) continue; + + Trackdir trackdir; + FOR_EACH_SET_TRACKDIR(trackdir, start_points->dirs & allowed_trackdirs) { + Point start_tile = { TileX(start_points->tile) * TILE_SIZE, TileY(start_points->tile) * TILE_SIZE }; + Point offset = GetDistanceToAutoLine(pt, start_tile, trackdir); + uint distance = (uint)(offset.x * offset.x + offset.y * offset.y); + if (distance < best_distance) { + *ret_start_tile = start_tile; + ret_end_pos->x = pt.x + offset.x; + ret_end_pos->y = pt.y + offset.y; + best_distance = distance; + ret = trackdir; + } + } + } + + /* cut the line at map borders */ + if (ret != INVALID_TRACKDIR) ClampAutoLineToMapBorders(ret_end_pos, TrackdirToTrack(ret)); + + return ret; +} + /** while dragging */ -static void CalcRaildirsDrawstyle(int x, int y, int method) +static void CalcRaildirsDrawstyle(int x, int y, ViewportPlaceMethod method) { HighLightStyle b; @@ -2672,32 +2901,31 @@ static void CalcRaildirsDrawstyle(int x, int y, int method) } } - if (_settings_client.gui.measure_tooltip) { - TileIndex t0 = TileVirtXY(_thd.selstart.x, _thd.selstart.y); - TileIndex t1 = TileVirtXY(x, y); - uint distance = DistanceManhattan(t0, t1) + 1; - byte index = 0; - uint64 params[2]; + _thd.selend.x = x; + _thd.selend.y = y; + _thd.next_drawstyle = b; - if (distance != 1) { - int heightdiff = CalcHeightdiff(b, distance, t0, t1); - /* If we are showing a tooltip for horizontal or vertical drags, - * 2 tiles have a length of 1. To bias towards the ceiling we add - * one before division. It feels more natural to count 3 lengths as 2 */ - if ((b & HT_DIR_MASK) != HT_DIR_X && (b & HT_DIR_MASK) != HT_DIR_Y) { - distance = CeilDiv(distance, 2); - } + ShowLengthMeasurement(b, TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y)); +} - params[index++] = distance; - if (heightdiff != 0) params[index++] = heightdiff; - } +static HighLightStyle CalcPolyrailDrawstyle(Point pt) +{ + /* directions allowed by highlight method */ + TrackdirBits allowed_trackdirs = (_thd.place_mode & HT_RAIL) ? TRACKDIR_BIT_MASK : _autoline_dirs_allowed_by_highlight_dir[_thd.place_mode & HT_DIR_MASK]; - ShowMeasurementTooltips(measure_strings_length[index], index, params); - } + /* now find the best track */ + Trackdir best_trackdir = FindBestAutoLine(pt, _rail_track_endpoints, lengthof(_rail_track_endpoints), allowed_trackdirs, &_thd.selstart, &_thd.selend); + if (best_trackdir == INVALID_TRACKDIR) return HT_NONE; // no match - _thd.selend.x = x; - _thd.selend.y = y; - _thd.next_drawstyle = b; + TileIndex start_tile = TileVirtXY(_thd.selstart.x, _thd.selstart.y); + TileIndex end_tile = TileVirtXY(_thd.selend.x, _thd.selend.y); + + HighLightStyle ret = HT_POLY | + (HighLightStyle)TrackdirToTrack(best_trackdir) | // cast TRACK_XXX to HT_DIR_XXX + (start_tile == end_tile ? HT_RAIL : HT_LINE); // one tile case or multitile selection + + ShowLengthMeasurement(ret, start_tile, end_tile, TCC_HOVER, true); + return ret; } /** @@ -2769,27 +2997,12 @@ calc_heightdiff_single_direction:; x = sx + Clamp(x - sx, -limit, limit); y = sy + Clamp(y - sy, -limit, limit); } - if (_settings_client.gui.measure_tooltip) { - TileIndex t0 = TileVirtXY(sx, sy); - TileIndex t1 = TileVirtXY(x, y); - uint distance = DistanceManhattan(t0, t1) + 1; - byte index = 0; - uint64 params[2]; - - if (distance != 1) { - /* With current code passing a HT_LINE style to calculate the height - * difference is enough. However if/when a point-tool is created - * with this method, function should be called with new_style (below) - * instead of HT_LINE | style case HT_POINT is handled specially - * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */ - int heightdiff = CalcHeightdiff(HT_LINE | style, 0, t0, t1); - - params[index++] = distance; - if (heightdiff != 0) params[index++] = heightdiff; - } - - ShowMeasurementTooltips(measure_strings_length[index], index, params); - } + /* With current code passing a HT_LINE style to calculate the height + * difference is enough. However if/when a point-tool is created + * with this method, function should be called with new_style (below) + * instead of HT_LINE | style case HT_POINT is handled specially + * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */ + ShowLengthMeasurement(HT_LINE | style, TileVirtXY(sx, sy), TileVirtXY(x, y)); break; case VPM_X_AND_Y_LIMITED: // Drag an X by Y constrained rect area. @@ -2900,7 +3113,7 @@ EventState VpHandlePlaceSizingDrag() } else if (_thd.select_method & VPM_SIGNALDIRS) { _thd.place_mode = HT_RECT | others; } else if (_thd.select_method & VPM_RAILDIRS) { - _thd.place_mode = (_thd.select_method & ~VPM_RAILDIRS) ? _thd.next_drawstyle : (HT_RAIL | others); + _thd.place_mode = (_thd.select_method & ~VPM_RAILDIRS ? _thd.next_drawstyle : HT_RAIL) | others; } else { _thd.place_mode = HT_POINT | others; } @@ -2966,3 +3179,85 @@ void ResetObjectToPlace() { SetObjectToPlace(SPR_CURSOR_MOUSE, PAL_NONE, HT_NONE, WC_MAIN_WINDOW, 0); } + +void RemoveConnectedEndpointDirs(RailTrackEndpoint *a, const RailTrackEndpoint &b) +{ + if (IsValidTile(a->tile) && IsValidTile(b.tile)) { + for (DiagDirection test_dir = DIAGDIR_BEGIN; test_dir < DIAGDIR_END; test_dir++) { + TrackdirBits test_trackdirs = DiagdirReachesTrackdirs(test_dir); + if (test_trackdirs & a->dirs) { + if (a->tile == b.tile) { + if (b.dirs & test_trackdirs) a->dirs &= ~test_trackdirs; + } else if (a->tile - TileOffsByDiagDir(test_dir) == b.tile) { + if (b.dirs & DiagdirReachesTrackdirs(ReverseDiagDir(test_dir))) a->dirs &= ~test_trackdirs; + } + if (a->dirs == TRACKDIR_BIT_NONE) { + a->tile = INVALID_TILE; + return; + } + } + } + } +} + +/** + * Store the position of lastly built rail track; for highlighting purposes. + * + * In "polyline" highlighting mode, the stored end point of the track + * will be used as the start point of a being highlighted track. + * + * @param start_tile tile where the track starts + * @param end_tile tile where the track ends + * @param start_track track piece on the start_tile + * @param bidirectional whether to allow to highlight next track in any direction; otherwise new track will have to fallow the stored one (usefull when placing tunnels and bridges) + */ +void StoreRailPlacementEndpoints(TileIndex start_tile, TileIndex end_tile, Track start_track, bool bidirectional) +{ + const uint NUM_ENDPOINTS = lengthof(_rail_track_endpoints); + + RailTrackEndpoint new_endpoints[NUM_ENDPOINTS] = { + { INVALID_TILE, TRACKDIR_BIT_NONE }, + { INVALID_TILE, TRACKDIR_BIT_NONE }, + { INVALID_TILE, TRACKDIR_BIT_NONE }, + { INVALID_TILE, TRACKDIR_BIT_NONE }, + }; + + if (start_tile != INVALID_TILE && end_tile != INVALID_TILE) { + /* calculate trackdirs at booth ends of the track (pointing toward track middle) */ + Trackdir start_trackdir = TrackToTrackdir(start_track); + Trackdir end_trackdir = ReverseTrackdir(start_trackdir); + if (start_tile != end_tile) { // multi-tile case + /* determine proper direction (toward track middle) */ + uint distance = DistanceManhattan(start_tile, end_tile); + if (distance < DistanceManhattan(TileAddByDiagDir(start_tile, TrackdirToExitdir(start_trackdir)), end_tile)) { + Swap(start_trackdir, end_trackdir); + } + /* determine proper track on the end tile - switch between upper/lower or left/right based on the length */ + if (distance % 2 != 0) end_trackdir = NextTrackdir(end_trackdir); + } + + /* compute new endpoints */ + DiagDirection start_exit_dir = TrackdirToExitdir(ReverseTrackdir(start_trackdir)); + DiagDirection end_exit_dir = TrackdirToExitdir(ReverseTrackdir(end_trackdir)); + new_endpoints[0].tile = TileAddByDiagDir(start_tile, start_exit_dir); + new_endpoints[0].dirs = DiagdirReachesTrackdirs(start_exit_dir); + new_endpoints[1].tile = TileAddByDiagDir(end_tile, end_exit_dir); + new_endpoints[1].dirs = DiagdirReachesTrackdirs(end_exit_dir); + if (bidirectional) { + new_endpoints[2].tile = start_tile; + new_endpoints[2].dirs = DiagdirReachesTrackdirs(ReverseDiagDir(start_exit_dir)); + new_endpoints[3].tile = end_tile; + new_endpoints[3].dirs = DiagdirReachesTrackdirs(ReverseDiagDir(end_exit_dir)); + } + + /* exclude all endpoints stored previously */ + for (uint i = 0; i < NUM_ENDPOINTS; i++) { + for (uint j = 0; j < NUM_ENDPOINTS; j++) { + RemoveConnectedEndpointDirs(&new_endpoints[i], _rail_track_endpoints[j]); + } + } + } + + /* store endpoints */ + MemCpyT(_rail_track_endpoints, new_endpoints, NUM_ENDPOINTS); +} diff --git a/src/viewport_func.h b/src/viewport_func.h index dba28bb9e..7788cba7c 100644 --- a/src/viewport_func.h +++ b/src/viewport_func.h @@ -57,7 +57,7 @@ void ViewportAddString(const DrawPixelInfo *dpi, ZoomLevel small_from, const Vie void StartSpriteCombine(); void EndSpriteCombine(); -bool HandleViewportClicked(const ViewPort *vp, int x, int y); +bool HandleViewportClicked(const ViewPort *vp, int x, int y, bool double_click); void SetRedErrorSquare(TileIndex tile); void SetTileSelectSize(int w, int h); void SetTileSelectBigSize(int ox, int oy, int sx, int sy); diff --git a/src/water_cmd.cpp b/src/water_cmd.cpp index 6b4f41eb9..ad047cfd6 100644 --- a/src/water_cmd.cpp +++ b/src/water_cmd.cpp @@ -12,6 +12,7 @@ #include "stdafx.h" #include "cmd_helper.h" #include "landscape.h" +#include "layer_func.h" #include "viewport_func.h" #include "command_func.h" #include "town.h" @@ -419,6 +420,11 @@ CommandCost CmdBuildCanal(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 /* can't make water of water! */ if (IsTileType(tile, MP_WATER) && (!IsTileOwner(tile, OWNER_WATER) || wc == WATER_CLASS_SEA)) continue; + /* can't make underground water */ + if (IsUnderground(tile)) { + return_cmd_error(STR_ERROR_UNDERGROUND_CAN_T_BUILD_UNDER_GROUND); + } + bool water = IsWaterTile(tile); ret = DoCommand(tile, 0, 0, flags | DC_FORCE_CLEAR_TILE, CMD_LANDSCAPE_CLEAR); if (ret.Failed()) return ret; @@ -1043,6 +1049,9 @@ void DoFloodTile(TileIndex target) { assert(!IsTileType(target, MP_WATER)); + /* Подземная часть карты не заливается */ + if (IsUnderground(target)) return; + bool flooded = false; // Will be set to true if something is changed. Backup<CompanyByte> cur_company(_current_company, OWNER_WATER, FILE_LINE); @@ -1197,7 +1206,7 @@ void ConvertGroundTilesIntoWaterTiles() for (TileIndex tile = 0; tile < MapSize(); ++tile) { Slope slope = GetTileSlope(tile, &z); - if (IsTileType(tile, MP_CLEAR) && z == 0) { + if (IsTileType(tile, MP_CLEAR) && z == 0 && !IsUnderground(tile)) { /* Make both water for tiles at level 0 * and make shore, as that looks much better * during the generation. */ diff --git a/src/waypoint_gui.cpp b/src/waypoint_gui.cpp index cd50a9ca8..513c9ae9e 100644 --- a/src/waypoint_gui.cpp +++ b/src/waypoint_gui.cpp @@ -22,6 +22,7 @@ #include "company_base.h" #include "window_func.h" #include "waypoint_base.h" +#include "departures_gui.h" #include "widgets/waypoint_widget.h" @@ -109,6 +110,10 @@ public: case WID_W_SHOW_VEHICLES: // show list of vehicles having this waypoint in their orders ShowVehicleListWindow(this->wp->owner, this->vt, this->wp->index); break; + + case WID_W_DEPARTURES: // show departure times of vehicles + ShowWaypointDepartures((StationID)this->wp->index); + break; } } @@ -164,6 +169,7 @@ static const NWidgetPart _nested_waypoint_view_widgets[] = { NWidget(NWID_HORIZONTAL), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_W_CENTER_VIEW), SetMinimalSize(100, 12), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_BUTTON_LOCATION, STR_BUOY_VIEW_CENTER_TOOLTIP), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_W_RENAME), SetMinimalSize(100, 12), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_BUTTON_RENAME, STR_BUOY_VIEW_CHANGE_BUOY_NAME), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_W_DEPARTURES), SetMinimalSize(100, 12), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_STATION_VIEW_DEPARTURES_BUTTON, STR_STATION_VIEW_DEPARTURES_TOOLTIP), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_W_SHOW_VEHICLES), SetMinimalSize(15, 12), SetDataTip(STR_SHIP, STR_STATION_VIEW_SCHEDULED_SHIPS_TOOLTIP), NWidget(WWT_RESIZEBOX, COLOUR_GREY), EndContainer(), diff --git a/src/widgets/departures_widget.h b/src/widgets/departures_widget.h new file mode 100644 index 000000000..e39b4d6cd --- /dev/null +++ b/src/widgets/departures_widget.h @@ -0,0 +1,29 @@ +/* $Id$ */ + +/* + * 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 waypoint_widget.h Types related to the waypoint widgets. */ + +#ifndef WIDGETS_DEPARTURES_WIDGET_H +#define WIDGETS_DEPARTURES_WIDGET_H + +/** Widgets of the WC_DEPARTURES_BOARD. */ +enum DeparturesWindowWidgets { + WID_DB_CAPTION, ///< Window caption + WID_DB_LIST, ///< List of departures + WID_DB_SCROLLBAR, ///< List scrollbar + WID_DB_SHOW_DEPS, ///< Toggle departures button + WID_DB_SHOW_ARRS, ///< Toggle arrivals button + WID_DB_SHOW_VIA, ///< Toggle via button + WID_DB_SHOW_TRAINS, ///< Toggle trains button + WID_DB_SHOW_ROADVEHS, ///< Toggle road vehicles button + WID_DB_SHOW_SHIPS, ///< Toggle ships button + WID_DB_SHOW_PLANES, ///< Toggle planes button +}; + +#endif /* WIDGETS_DEPARTURES_WIDGET_H */ diff --git a/src/widgets/genworld_widget.h b/src/widgets/genworld_widget.h index 10727f103..f9cc4e104 100644 --- a/src/widgets/genworld_widget.h +++ b/src/widgets/genworld_widget.h @@ -21,6 +21,7 @@ enum GenerateLandscapeWidgets { WID_GL_MAPSIZE_X_PULLDOWN, ///< Dropdown 'map X size'. WID_GL_MAPSIZE_Y_PULLDOWN, ///< Dropdown 'map Y size'. + WID_GL_LAYER_COUNT_PULLDOWN, ///< Dropdown 'map layer count'. WID_GL_TOWN_PULLDOWN, ///< Dropdown 'No. of towns'. WID_GL_INDUSTRY_PULLDOWN, ///< Dropdown 'No. of industries'. @@ -68,6 +69,7 @@ enum CreateScenarioWidgets { WID_CS_RANDOM_WORLD, ///< Generate random land button WID_CS_MAPSIZE_X_PULLDOWN, ///< Pull-down arrow for x map size. WID_CS_MAPSIZE_Y_PULLDOWN, ///< Pull-down arrow for y map size. + WID_CS_LAYER_COUNT_PULLDOWN, ///< Pull-down arrow for map layer count. WID_CS_START_DATE_DOWN, ///< Decrease start year (start earlier). WID_CS_START_DATE_TEXT, ///< Clickable start date value. WID_CS_START_DATE_UP, ///< Increase start year (start later). diff --git a/src/widgets/road_widget.h b/src/widgets/road_widget.h index f022489e5..d12d870c5 100644 --- a/src/widgets/road_widget.h +++ b/src/widgets/road_widget.h @@ -23,6 +23,7 @@ enum RoadToolbarWidgets { WID_ROT_BUS_STATION, ///< Build bus station. WID_ROT_TRUCK_STATION, ///< Build truck station. WID_ROT_ONE_WAY, ///< Build one-way road. + WID_ROT_TRAFFIC_LIGHT, ///< Build trafficlights. WID_ROT_BUILD_BRIDGE, ///< Build bridge. WID_ROT_BUILD_TUNNEL, ///< Build tunnel. WID_ROT_REMOVE, ///< Remove road. diff --git a/src/widgets/smallmap_widget.h b/src/widgets/smallmap_widget.h index 6476b8f1b..7b7f1ba16 100644 --- a/src/widgets/smallmap_widget.h +++ b/src/widgets/smallmap_widget.h @@ -23,6 +23,7 @@ enum SmallMapWidgets { WID_SM_CONTOUR, ///< Button to select the contour view (height map). WID_SM_VEHICLES, ///< Button to select the vehicles view. WID_SM_INDUSTRIES, ///< Button to select the industries view. + WID_SM_ROUTE_LINKS, ///< Button to select the route link view. WID_SM_ROUTES, ///< Button to select the routes view. WID_SM_VEGETATION, ///< Button to select the vegetation view. WID_SM_OWNERS, ///< Button to select the owners view. diff --git a/src/widgets/station_widget.h b/src/widgets/station_widget.h index e33565ea1..76ad864d9 100644 --- a/src/widgets/station_widget.h +++ b/src/widgets/station_widget.h @@ -20,12 +20,14 @@ enum StationViewWidgets { WID_SV_ACCEPT_RATING_LIST, ///< List of accepted cargoes / rating of cargoes. WID_SV_LOCATION, ///< 'Location' button. WID_SV_ACCEPTS_RATINGS, ///< 'Accepts' / 'Ratings' button. + WID_SV_CARGO_FROM_TO_VIA, ///< 'Source' button WID_SV_RENAME, ///< 'Rename' button. WID_SV_CLOSE_AIRPORT, ///< 'Close airport' button. WID_SV_TRAINS, ///< List of scheduled trains button. WID_SV_ROADVEHS, ///< List of scheduled road vehs button. WID_SV_SHIPS, ///< List of scheduled ships button. WID_SV_PLANES, ///< List of scheduled planes button. + WID_SV_DEPARTURES, ///< Departures button. }; /** Widgets of the #CompanyStationsWindow class. */ diff --git a/src/widgets/timetable_widget.h b/src/widgets/timetable_widget.h index 09beb6167..cce1533c4 100644 --- a/src/widgets/timetable_widget.h +++ b/src/widgets/timetable_widget.h @@ -31,6 +31,9 @@ enum VehicleTimetableWidgets { WID_VT_EXPECTED_SELECTION, ///< Disable/hide the expected selection button. WID_VT_CHANGE_SPEED, ///< Change speed limit button. WID_VT_CLEAR_SPEED, ///< Clear speed limit button. + WID_VT_TTSEP_MODE_DROPDOWN, ///< Select separation mode dropdown + WID_VT_TTSEP_SET_PARAMETER, ///< Set the separation parameter (time / number) + WID_VT_TTSEP_PANEL_TEXT ///< Panel area for separation info text }; #endif /* WIDGETS_TIMETABLE_WIDGET_H */ diff --git a/src/widgets/underground_widget.h b/src/widgets/underground_widget.h new file mode 100644 index 000000000..522c9db8b --- /dev/null +++ b/src/widgets/underground_widget.h @@ -0,0 +1,21 @@ +/* $Id: terraform_widget.h 23600 2011-12-19 20:46:17Z truebrain $ */ + +/* + * 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 terraform_widget.h Types related to the terraform widgets. */ + +#ifndef WIDGETS_UNDERGROUND_WIDGET_H +#define WIDGETS_UNDERGROUND_WIDGET_H + +/** Widgets of the #TerraformToolbarWindow class. */ +enum UndergroundToolbarWidgets { + WID_UT_BUILD_ESCALATOR, ///< Build escalator + WID_UT_DEMOLISH, ///< Demolish aka dynamite button. +}; + +#endif /* WIDGETS_UNDERGROUND_WIDGET_H */ diff --git a/src/widgets/waypoint_widget.h b/src/widgets/waypoint_widget.h index 8fceddaa7..e73f3af6d 100644 --- a/src/widgets/waypoint_widget.h +++ b/src/widgets/waypoint_widget.h @@ -19,6 +19,7 @@ enum WaypointWidgets { WID_W_CENTER_VIEW, ///< Center the main view on this waypoint. WID_W_RENAME, ///< Rename this waypoint. WID_W_SHOW_VEHICLES, ///< Show the vehicles visiting this waypoint. + WID_W_DEPARTURES, ///< Departures button. }; #endif /* WIDGETS_WAYPOINT_WIDGET_H */ diff --git a/src/window.cpp b/src/window.cpp index f818471bd..800906f60 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -2528,7 +2528,7 @@ static void MouseLoop(MouseClick click, int mousewheel) case MC_DOUBLE_LEFT: case MC_LEFT: DEBUG(misc, 2, "Cursor: 0x%X (%d)", _cursor.sprite, _cursor.sprite); - if (!HandleViewportClicked(vp, x, y) && + if (!HandleViewportClicked(vp, x, y, click == MC_DOUBLE_LEFT) && !(w->flags & WF_DISABLE_VP_SCROLL) && _settings_client.gui.left_mouse_btn_scrolling) { _scrolling_viewport = true; diff --git a/src/window_type.h b/src/window_type.h index c69e44cd0..150ce7dfb 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -439,6 +439,12 @@ enum WindowClass { WC_SCEN_LAND_GEN, /** + * Underground (in game); %Window numbers: + * - 0 = #UndergroundToolbarWidgets + */ + WC_UNDERGROUND, + + /** * Generate landscape (newgame); %Window numbers: * - GLWM_SCENARIO = #CreateScenarioWidgets * - #GenenerateLandscapeWindowMode = #GenerateLandscapeWidgets @@ -664,6 +670,11 @@ enum WindowClass { */ WC_SPRITE_ALIGNER, + /** + * Departure boards + */ + WC_DEPARTURES_BOARD, + WC_INVALID = 0xFFFF, ///< Invalid window. }; |