Merge branch 'master' into infrastructure_sharing

Conflicts:
	src/aircraft_cmd.cpp
	src/economy.cpp
	src/lang/english.txt
	src/order_gui.cpp
	src/roadveh_cmd.cpp
	src/saveload/saveload.cpp
	src/settings.cpp
	src/settings_gui.cpp
	src/train_cmd.cpp
This commit is contained in:
Jonathan G Rennison
2015-08-06 22:55:09 +01:00
1106 changed files with 149811 additions and 81548 deletions

View File

@@ -51,9 +51,13 @@
#include "tunnel_map.h"
#include "depot_map.h"
#include "gamelog.h"
#include "linkgraph/linkgraph.h"
#include "linkgraph/refresh.h"
#include "table/strings.h"
#include "safeguards.h"
#define GEN_HASH(x, y) ((GB((y), 6 + ZOOM_LVL_SHIFT, 6) << 6) + GB((x), 7 + ZOOM_LVL_SHIFT, 6))
VehicleID _new_vehicle_id;
@@ -68,9 +72,10 @@ INSTANTIATE_POOL_METHODS(Vehicle)
/**
* Function to tell if a vehicle needs to be autorenewed
* @param *c The vehicle owner
* @param use_renew_setting Should the company renew setting be considered?
* @return true if the vehicle is old enough for replacement
*/
bool Vehicle::NeedsAutorenewing(const Company *c) const
bool Vehicle::NeedsAutorenewing(const Company *c, bool use_renew_setting) const
{
/* We can always generate the Company pointer when we have the vehicle.
* However this takes time and since the Company pointer is often present
@@ -78,7 +83,7 @@ bool Vehicle::NeedsAutorenewing(const Company *c) const
* argument rather than finding it again. */
assert(c == Company::Get(this->owner));
if (!c->settings.engine_renew) return false;
if (use_renew_setting && !c->settings.engine_renew) return false;
if (this->age - this->max_age < (c->settings.engine_renew_months * 30)) return false;
/* Only engines need renewing */
@@ -87,12 +92,24 @@ bool Vehicle::NeedsAutorenewing(const Company *c) const
return true;
}
/**
* Service a vehicle and all subsequent vehicles in the consist
*
* @param *v The vehicle or vehicle chain being serviced
*/
void VehicleServiceInDepot(Vehicle *v)
{
v->date_of_last_service = _date;
v->breakdowns_since_last_service = 0;
v->reliability = v->GetEngine()->reliability;
assert(v != NULL);
SetWindowDirty(WC_VEHICLE_DETAILS, v->index); // ensure that last service date and reliability are updated
do {
v->date_of_last_service = _date;
v->breakdowns_since_last_service = 0;
v->reliability = v->GetEngine()->reliability;
/* Prevent vehicles from breaking down directly after exiting the depot. */
v->breakdown_chance /= 4;
v = v->Next();
} while (v != NULL && v->HasEngineType());
}
/**
@@ -109,9 +126,9 @@ bool Vehicle::NeedsServicing() const
/* Are we ready for the next service cycle? */
const Company *c = Company::Get(this->owner);
if (c->settings.vehicle.servint_ispercent ?
(this->reliability >= this->GetEngine()->reliability * (100 - this->service_interval) / 100) :
(this->date_of_last_service + this->service_interval >= _date)) {
if (this->ServiceIntervalIsPercent() ?
(this->reliability >= this->GetEngine()->reliability * (100 - this->GetServiceInterval()) / 100) :
(this->date_of_last_service + this->GetServiceInterval() >= _date)) {
return false;
}
@@ -130,10 +147,13 @@ bool Vehicle::NeedsServicing() const
if (needed_money > c->money) return false;
for (const Vehicle *v = this; v != NULL; v = (v->type == VEH_TRAIN) ? Train::From(v)->GetNextUnit() : NULL) {
EngineID new_engine = EngineReplacementForCompany(c, v->engine_type, v->group_id);
bool replace_when_old = false;
EngineID new_engine = EngineReplacementForCompany(c, v->engine_type, v->group_id, &replace_when_old);
/* Check engine availability */
if (new_engine == INVALID_ENGINE || !HasBit(Engine::Get(new_engine)->company_avail, v->owner)) continue;
/* Is the vehicle old if we are not always replacing? */
if (replace_when_old && !v->NeedsAutorenewing(c, false)) continue;
/* Check refittability */
uint32 available_cargo_types, union_mask;
@@ -162,9 +182,9 @@ bool Vehicle::NeedsServicing() const
}
/**
* Checks if the current order should be interupted for a service-in-depot-order.
* Checks if the current order should be interrupted for a service-in-depot order.
* @see NeedsServicing()
* @return true if the current order should be interupted.
* @return true if the current order should be interrupted.
*/
bool Vehicle::NeedsAutomaticServicing() const
{
@@ -184,9 +204,10 @@ uint Vehicle::Crash(bool flooded)
if (this->IsPrimaryVehicle()) this->vehstatus |= VS_STOPPED;
/* crash all wagons, and count passengers */
for (Vehicle *v = this; v != NULL; v = v->Next()) {
if (IsCargoInClass(v->cargo_type, CC_PASSENGERS)) pass += v->cargo.Count();
/* We do not transfer reserver cargo back, so TotalCount() instead of StoredCount() */
if (IsCargoInClass(v->cargo_type, CC_PASSENGERS)) pass += v->cargo.TotalCount();
v->vehstatus |= VS_CRASHED;
MarkSingleVehicleDirty(v);
v->MarkAllViewportsDirty();
}
/* Dirty some windows */
@@ -195,7 +216,10 @@ uint Vehicle::Crash(bool flooded)
SetWindowDirty(WC_VEHICLE_DETAILS, this->index);
SetWindowDirty(WC_VEHICLE_DEPOT, this->tile);
return pass;
delete this->cargo_payment;
this->cargo_payment = NULL;
return RandomRange(pass + 1); // Randomise deceased passengers.
}
@@ -212,6 +236,9 @@ void ShowNewGrfVehicleError(EngineID engine, StringID part1, StringID part2, GRF
const Engine *e = Engine::Get(engine);
GRFConfig *grfconfig = GetGRFConfig(e->GetGRFID());
/* Missing GRF. Nothing useful can be done in this situation. */
if (grfconfig == NULL) return;
if (!HasBit(grfconfig->grf_bugs, bug_type)) {
SetBit(grfconfig->grf_bugs, bug_type);
SetDParamStr(0, grfconfig->GetName());
@@ -261,6 +288,8 @@ Vehicle::Vehicle(VehicleType type)
this->first = this;
this->colourmap = PAL_NONE;
this->cargo_age_counter = 1;
this->last_station_visited = INVALID_STATION;
this->last_loading_station = INVALID_STATION;
}
/**
@@ -702,7 +731,7 @@ void Vehicle::HandlePathfindingResult(bool path_found)
AI::NewEvent(this->owner, new ScriptEventVehicleLost(this->index));
if (_settings_client.gui.lost_vehicle_warn && this->owner == _local_company) {
SetDParam(0, this->index);
AddVehicleNewsItem(STR_NEWS_VEHICLE_IS_LOST, NS_ADVICE, this->index);
AddVehicleAdviceNewsItem(STR_NEWS_VEHICLE_IS_LOST, this->index);
}
}
@@ -712,10 +741,11 @@ void Vehicle::PreDestructor()
if (CleaningPool()) return;
if (Station::IsValidID(this->last_station_visited)) {
Station::Get(this->last_station_visited)->loading_vehicles.remove(this);
Station *st = Station::Get(this->last_station_visited);
st->loading_vehicles.remove(this);
HideFillingPercent(&this->fill_percent_te_id);
this->CancelReservation(INVALID_STATION, st);
delete this->cargo_payment;
}
@@ -761,7 +791,7 @@ void Vehicle::PreDestructor()
}
InvalidateWindowClassesData(GetWindowClassForVehicleType(this->type), 0);
this->cargo.Truncate(0);
this->cargo.Truncate();
DeleteVehicleOrders(this);
DeleteDepotHighlightOfVehicle(this);
@@ -773,8 +803,6 @@ void Vehicle::PreDestructor()
Vehicle::~Vehicle()
{
free(this->name);
if (CleaningPool()) {
this->cargo.OnCleanPool();
return;
@@ -782,7 +810,7 @@ Vehicle::~Vehicle()
/* sometimes, eg. for disaster vehicles, when company bankrupts, when removing crashed/flooded vehicles,
* it may happen that vehicle chain is deleted when visible */
if (!(this->vehstatus & VS_HIDDEN)) MarkSingleVehicleDirty(this);
if (!(this->vehstatus & VS_HIDDEN)) this->MarkAllViewportsDirty();
Vehicle *v = this->Next();
this->SetNext(NULL);
@@ -830,8 +858,13 @@ static void RunVehicleDayProc()
if ((v->day_counter & 0x1F) == 0 && v->HasEngineType()) {
uint16 callback = GetVehicleCallback(CBID_VEHICLE_32DAY_CALLBACK, 0, 0, v->engine_type, v);
if (callback != CALLBACK_FAILED) {
if (HasBit(callback, 0)) TriggerVehicle(v, VEHICLE_TRIGGER_CALLBACK_32); // Trigger vehicle trigger 10
if (HasBit(callback, 1)) v->colourmap = PAL_NONE;
if (HasBit(callback, 0)) {
TriggerVehicle(v, VEHICLE_TRIGGER_CALLBACK_32); // Trigger vehicle trigger 10
}
/* After a vehicle trigger, the graphics and properties of the vehicle could change.
* Note: MarkDirty also invalidates the palette, which is the meaning of bit 1. So, nothing special there. */
if (callback != 0) v->First()->MarkDirty();
if (callback & ~3) ErrorUnknownCallbackResult(v->GetGRFID(), CBID_VEHICLE_32DAY_CALLBACK, callback);
}
@@ -867,7 +900,9 @@ void CallVehicleTicks()
case VEH_TRAIN:
case VEH_ROAD:
case VEH_AIRCRAFT:
case VEH_SHIP:
case VEH_SHIP: {
Vehicle *front = v->First();
if (v->vcache.cached_cargo_age_period != 0) {
v->cargo_age_counter = min(v->cargo_age_counter, v->vcache.cached_cargo_age_period);
if (--v->cargo_age_counter == 0) {
@@ -876,16 +911,46 @@ 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;
/* Do not play any sound when crashed */
if (front->vehstatus & VS_CRASHED) continue;
v->motion_counter += v->cur_speed;
/* Do not play any sound when in depot or tunnel */
if (v->vehstatus & VS_HIDDEN) continue;
/* Do not play any sound when stopped */
if ((front->vehstatus & VS_STOPPED) && (front->type != VEH_TRAIN || front->cur_speed == 0)) continue;
/* Check vehicle type specifics */
switch (v->type) {
case VEH_TRAIN:
if (Train::From(v)->IsWagon()) continue;
break;
case VEH_ROAD:
if (!RoadVehicle::From(v)->IsFrontEngine()) continue;
break;
case VEH_AIRCRAFT:
if (!Aircraft::From(v)->IsNormalAircraft()) continue;
break;
default:
break;
}
v->motion_counter += front->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);
if (GB(v->motion_counter, 0, 8) < front->cur_speed) PlayVehicleSound(v, VSE_RUNNING);
/* Play an alterate running sound every 16 ticks */
if (GB(v->tick_counter, 0, 4) == 0) PlayVehicleSound(v, v->cur_speed > 0 ? VSE_RUNNING_16 : VSE_STOPPED_16);
/* Play an alternating running sound every 16 ticks */
if (GB(v->tick_counter, 0, 4) == 0) {
/* Play running sound when speed > 0 and not braking */
bool running = (front->cur_speed > 0) && !(front->vehstatus & (VS_STOPPED | VS_TRAIN_SLOWING));
PlayVehicleSound(v, running ? VSE_RUNNING_16 : VSE_STOPPED_16);
}
break;
}
}
}
@@ -931,7 +996,7 @@ void CallVehicleTicks()
SetDParam(0, v->index);
SetDParam(1, error_message);
AddVehicleNewsItem(message, NS_ADVICE, v->index);
AddVehicleAdviceNewsItem(message, v->index);
}
cur_company.Restore();
@@ -1142,12 +1207,13 @@ bool Vehicle::HandleBreakdown()
this->cur_speed = 0;
if (!PlayVehicleSound(this, VSE_BREAKDOWN)) {
bool train_or_ship = this->type == VEH_TRAIN || this->type == VEH_SHIP;
SndPlayVehicleFx((_settings_game.game_creation.landscape != LT_TOYLAND) ?
(this->type == VEH_TRAIN ? SND_10_TRAIN_BREAKDOWN : SND_0F_VEHICLE_BREAKDOWN) :
(this->type == VEH_TRAIN ? SND_3A_COMEDY_BREAKDOWN_2 : SND_35_COMEDY_BREAKDOWN), this);
(train_or_ship ? SND_10_TRAIN_BREAKDOWN : SND_0F_VEHICLE_BREAKDOWN) :
(train_or_ship ? SND_3A_COMEDY_BREAKDOWN_2 : SND_35_COMEDY_BREAKDOWN), this);
}
if (!(this->vehstatus & VS_HIDDEN)) {
if (!(this->vehstatus & VS_HIDDEN) && !HasBit(EngInfo(this->engine_type)->misc_flags, EF_NO_BREAKDOWN_SMOKE)) {
EffectVehicle *u = CreateEffectVehicleRel(this, 4, 4, 5, EV_BREAKDOWN_SMOKE);
if (u != NULL) u->animation_state = this->breakdown_delay * 2;
}
@@ -1217,16 +1283,16 @@ void AgeVehicle(Vehicle *v)
}
SetDParam(0, v->index);
AddVehicleNewsItem(str, NS_ADVICE, v->index);
AddVehicleAdviceNewsItem(str, v->index);
}
/**
* Calculates how full a vehicle is.
* @param v The Vehicle to check. For trains, use the first engine.
* @param front The front vehicle of the consist to check.
* @param colour The string to show depending on if we are unloading or loading
* @return A percentage of how full the Vehicle is.
*/
uint8 CalcPercentVehicleFilled(const Vehicle *v, StringID *colour)
uint8 CalcPercentVehicleFilled(const Vehicle *front, StringID *colour)
{
int count = 0;
int max = 0;
@@ -1234,18 +1300,24 @@ uint8 CalcPercentVehicleFilled(const Vehicle *v, StringID *colour)
int unloading = 0;
bool loading = false;
const Vehicle *u = v;
bool is_loading = front->current_order.IsType(OT_LOADING);
/* The station may be NULL when the (colour) string does not need to be set. */
const Station *st = Station::GetIfValid(v->last_station_visited);
assert(colour == NULL || st != NULL);
const Station *st = Station::GetIfValid(front->last_station_visited);
assert(colour == NULL || (st != NULL && is_loading));
bool order_no_load = is_loading && (front->current_order.GetLoadType() & OLFB_NO_LOAD);
bool order_full_load = is_loading && (front->current_order.GetLoadType() & OLFB_FULL_LOAD);
/* Count up max and used */
for (; v != NULL; v = v->Next()) {
count += v->cargo.Count();
for (const Vehicle *v = front; v != NULL; v = v->Next()) {
count += v->cargo.StoredCount();
max += v->cargo_cap;
if (v->cargo_cap != 0 && colour != NULL) {
unloading += HasBit(v->vehicle_flags, VF_CARGO_UNLOADING) ? 1 : 0;
loading |= !(u->current_order.GetLoadType() & OLFB_NO_LOAD) && st->goods[v->cargo_type].days_since_pickup != 255;
loading |= !order_no_load &&
(order_full_load || st->goods[v->cargo_type].HasRating()) &&
!HasBit(v->vehicle_flags, VF_LOADING_FINISHED) && !HasBit(v->vehicle_flags, VF_STOP_LOADING);
cars++;
}
}
@@ -1253,6 +1325,8 @@ uint8 CalcPercentVehicleFilled(const Vehicle *v, StringID *colour)
if (colour != NULL) {
if (unloading == 0 && loading) {
*colour = STR_PERCENT_UP;
} else if (unloading == 0 && !loading) {
*colour = STR_PERCENT_NONE;
} else if (cars == unloading || !loading) {
*colour = STR_PERCENT_DOWN;
} else {
@@ -1288,7 +1362,7 @@ void VehicleEnterDepot(Vehicle *v)
t->wait_counter = 0;
t->force_proceed = TFP_NONE;
ClrBit(t->flags, VRF_TOGGLE_REVERSE);
t->ConsistChanged(true);
t->ConsistChanged(CCF_ARRANGE);
break;
}
@@ -1326,27 +1400,27 @@ void VehicleEnterDepot(Vehicle *v)
VehicleServiceInDepot(v);
/* After a vehicle trigger, the graphics and properties of the vehicle could change. */
TriggerVehicle(v, VEHICLE_TRIGGER_DEPOT);
v->MarkDirty();
if (v->current_order.IsType(OT_GOTO_DEPOT)) {
SetWindowDirty(WC_VEHICLE_VIEW, v->index);
const Order *real_order = v->GetOrder(v->cur_real_order_index);
Order t = v->current_order;
v->current_order.MakeDummy();
/* Test whether we are heading for this depot. If not, do nothing.
* Note: The target depot for nearest-/manual-depot-orders is only updated on junctions, but we want to accept every depot. */
if ((t.GetDepotOrderType() & ODTFB_PART_OF_ORDERS) &&
if ((v->current_order.GetDepotOrderType() & ODTFB_PART_OF_ORDERS) &&
real_order != NULL && !(real_order->GetDepotActionType() & ODATFB_NEAREST_DEPOT) &&
(v->type == VEH_AIRCRAFT ? t.GetDestination() != GetStationIndex(v->tile) : v->dest_tile != v->tile)) {
(v->type == VEH_AIRCRAFT ? v->current_order.GetDestination() != GetStationIndex(v->tile) : v->dest_tile != v->tile)) {
/* We are heading for another depot, keep driving. */
return;
}
if (t.IsRefit()) {
if (v->current_order.IsRefit()) {
Backup<CompanyByte> cur_company(_current_company, v->owner, FILE_LINE);
CommandCost cost = DoCommand(v->tile, v->index, t.GetRefitCargo() | t.GetRefitSubtype() << 8, DC_EXEC, GetCmdRefitVeh(v));
CommandCost cost = DoCommand(v->tile, v->index, v->current_order.GetRefitCargo() | 0xFF << 8, DC_EXEC, GetCmdRefitVeh(v));
cur_company.Restore();
if (cost.Failed()) {
@@ -1354,7 +1428,7 @@ void VehicleEnterDepot(Vehicle *v)
if (v->owner == _local_company) {
/* Notify the user that we stopped the vehicle */
SetDParam(0, v->index);
AddVehicleNewsItem(STR_NEWS_ORDER_REFIT_FAILED, NS_ADVICE, v->index);
AddVehicleAdviceNewsItem(STR_NEWS_ORDER_REFIT_FAILED, v->index);
}
} else if (cost.GetCost() != 0) {
v->profit_this_year -= cost.GetCost() << 8;
@@ -1364,21 +1438,26 @@ void VehicleEnterDepot(Vehicle *v)
}
}
if (t.GetDepotOrderType() & ODTFB_PART_OF_ORDERS) {
if (v->current_order.GetDepotOrderType() & ODTFB_PART_OF_ORDERS) {
/* Part of orders */
v->DeleteUnreachedImplicitOrders();
UpdateVehicleTimetable(v, true);
v->IncrementImplicitOrderIndex();
}
if (t.GetDepotActionType() & ODATFB_HALT) {
if (v->current_order.GetDepotActionType() & ODATFB_HALT) {
/* Vehicles are always stopped on entering depots. Do not restart this one. */
_vehicles_to_autoreplace[v] = false;
/* Invalidate last_loading_station. As the link from the station
* before the stop to the station after the stop can't be predicted
* we shouldn't construct it when the vehicle visits the next stop. */
v->last_loading_station = INVALID_STATION;
if (v->owner == _local_company) {
SetDParam(0, v->index);
AddVehicleNewsItem(STR_NEWS_TRAIN_IS_WAITING + v->type, NS_ADVICE, v->index);
AddVehicleAdviceNewsItem(STR_NEWS_TRAIN_IS_WAITING + v->type, v->index);
}
AI::NewEvent(v->owner, new ScriptEventVehicleWaitingInDepot(v->index));
}
v->current_order.MakeDummy();
}
}
@@ -1386,67 +1465,62 @@ void VehicleEnterDepot(Vehicle *v)
/**
* Update the position of the vehicle. This will update the hash that tells
* which vehicles are on a tile.
* @param v The vehicle to update.
*/
void VehicleUpdatePosition(Vehicle *v)
void Vehicle::UpdatePosition()
{
UpdateVehicleTileHash(v, false);
UpdateVehicleTileHash(this, false);
}
/**
* Update the vehicle on the viewport, updating the right hash and setting the
* new coordinates.
* @param v The vehicle to update.
* @param dirty Mark the (new and old) coordinates of the vehicle as dirty.
*/
void VehicleUpdateViewport(Vehicle *v, bool dirty)
void Vehicle::UpdateViewport(bool dirty)
{
int img = v->cur_image;
Point pt = RemapCoords(v->x_pos + v->x_offs, v->y_pos + v->y_offs, v->z_pos);
int img = this->cur_image;
Point pt = RemapCoords(this->x_pos + this->x_offs, this->y_pos + this->y_offs, this->z_pos);
const Sprite *spr = GetSprite(img, ST_NORMAL);
pt.x += spr->x_offs;
pt.y += spr->y_offs;
UpdateVehicleViewportHash(v, pt.x, pt.y);
UpdateVehicleViewportHash(this, pt.x, pt.y);
Rect old_coord = v->coord;
v->coord.left = pt.x;
v->coord.top = pt.y;
v->coord.right = pt.x + spr->width + 2 * ZOOM_LVL_BASE;
v->coord.bottom = pt.y + spr->height + 2 * ZOOM_LVL_BASE;
Rect old_coord = this->coord;
this->coord.left = pt.x;
this->coord.top = pt.y;
this->coord.right = pt.x + spr->width + 2 * ZOOM_LVL_BASE;
this->coord.bottom = pt.y + spr->height + 2 * ZOOM_LVL_BASE;
if (dirty) {
if (old_coord.left == INVALID_COORD) {
MarkSingleVehicleDirty(v);
this->MarkAllViewportsDirty();
} else {
MarkAllViewportsDirty(
min(old_coord.left, v->coord.left),
min(old_coord.top, v->coord.top),
max(old_coord.right, v->coord.right) + 1 * ZOOM_LVL_BASE,
max(old_coord.bottom, v->coord.bottom) + 1 * ZOOM_LVL_BASE
);
::MarkAllViewportsDirty(
min(old_coord.left, this->coord.left),
min(old_coord.top, this->coord.top),
max(old_coord.right, this->coord.right),
max(old_coord.bottom, this->coord.bottom));
}
}
}
/**
* Update the position of the vehicle, and update the viewport.
* @param v The vehicle to update.
*/
void VehicleUpdatePositionAndViewport(Vehicle *v)
void Vehicle::UpdatePositionAndViewport()
{
VehicleUpdatePosition(v);
VehicleUpdateViewport(v, true);
this->UpdatePosition();
this->UpdateViewport(true);
}
/**
* Marks viewports dirty where the vehicle's image is.
* @param v vehicle to mark dirty
*/
void MarkSingleVehicleDirty(const Vehicle *v)
void Vehicle::MarkAllViewportsDirty() const
{
MarkAllViewportsDirty(v->coord.left, v->coord.top, v->coord.right + 1 * ZOOM_LVL_BASE, v->coord.bottom + 1 * ZOOM_LVL_BASE);
::MarkAllViewportsDirty(this->coord.left, this->coord.top, this->coord.right, this->coord.bottom);
}
/**
@@ -1627,10 +1701,10 @@ bool CanBuildVehicleInfrastructure(VehicleType type)
/**
* Determines the #LiveryScheme for a vehicle.
* @param engine_type EngineID of the vehicle
* @param parent_engine_type EngineID of the front vehicle. INVALID_VEHICLE if vehicle is at front itself.
* @param v the vehicle. NULL if in purchase list etc.
* @return livery scheme to use
* @param engine_type Engine of the vehicle.
* @param parent_engine_type Engine of the front vehicle, #INVALID_ENGINE if vehicle is at front itself.
* @param v the vehicle, \c NULL if in purchase list etc.
* @return livery scheme to use.
*/
LiveryScheme GetEngineLiveryScheme(EngineID engine_type, EngineID parent_engine_type, const Vehicle *v)
{
@@ -1755,7 +1829,7 @@ static PaletteID GetEngineColourMap(EngineID engine_type, CompanyID company, Eng
uint16 callback = GetVehicleCallback(CBID_VEHICLE_COLOUR_MAPPING, 0, 0, engine_type, v);
/* Failure means "use the default two-colour" */
if (callback != CALLBACK_FAILED) {
assert_compile(PAL_NONE == 0); // Returning 0x4000 (resp. 0xC000) conincidences with default value (PAL_NONE)
assert_compile(PAL_NONE == 0); // Returning 0x4000 (resp. 0xC000) coincidences with default value (PAL_NONE)
map = GB(callback, 0, 14);
/* If bit 14 is set, then the company colours are applied to the
* map else it's returned as-is. */
@@ -1830,9 +1904,9 @@ void Vehicle::DeleteUnreachedImplicitOrders()
if (this->cur_implicit_order_index == this->cur_real_order_index) break;
if (order->IsType(OT_IMPLICIT)) {
/* Delete order effectively deletes order, so get the next before deleting it. */
order = order->next;
DeleteOrder(this, this->cur_implicit_order_index);
/* DeleteOrder does various magic with order_indices, so resync 'order' with 'cur_implicit_order_index' */
order = this->GetOrder(this->cur_implicit_order_index);
} else {
/* Skip non-implicit orders, e.g. service-orders */
order = order->next;
@@ -1872,13 +1946,12 @@ void Vehicle::BeginLoading()
} else {
/* We weren't scheduled to stop here. Insert an implicit order
* to show that we are stopping here, but only do that if the order
* list isn't empty.
* to show that we are stopping here.
* While only groundvehicles have implicit orders, e.g. aircraft might still enter
* the 'wrong' terminal when skipping orders etc. */
Order *in_list = this->GetOrder(this->cur_implicit_order_index);
if (this->IsGroundVehicle() && in_list != NULL &&
(!in_list->IsType(OT_IMPLICIT) ||
if (this->IsGroundVehicle() &&
(in_list == NULL || !in_list->IsType(OT_IMPLICIT) ||
in_list->GetDestination() != this->last_station_visited)) {
bool suppress_implicit_orders = HasBit(this->GetGroundVehicleFlags(), GVF_SUPPRESS_IMPLICIT_ORDERS);
/* Do not create consecutive duplicates of implicit orders */
@@ -1888,18 +1961,28 @@ void Vehicle::BeginLoading()
prev_order->GetDestination() != this->last_station_visited) {
/* Prefer deleting implicit orders instead of inserting new ones,
* so test whether the right order follows later */
* so test whether the right order follows later. In case of only
* implicit orders treat the last order in the list like an
* explicit one, except if the overall number of orders surpasses
* IMPLICIT_ORDER_ONLY_CAP. */
int target_index = this->cur_implicit_order_index;
bool found = false;
while (target_index != this->cur_real_order_index) {
while (target_index != this->cur_real_order_index || this->GetNumManualOrders() == 0) {
const Order *order = this->GetOrder(target_index);
if (order == NULL) break; // No orders.
if (order->IsType(OT_IMPLICIT) && order->GetDestination() == this->last_station_visited) {
found = true;
break;
}
target_index++;
if (target_index >= this->orders.list->GetNumOrders()) target_index = 0;
assert(target_index != this->cur_implicit_order_index); // infinite loop?
if (target_index >= this->orders.list->GetNumOrders()) {
if (this->GetNumManualOrders() == 0 &&
this->GetNumOrders() < IMPLICIT_ORDER_ONLY_CAP) {
break;
}
target_index = 0;
}
if (target_index == this->cur_implicit_order_index) break; // Avoid infinite loop.
}
if (found) {
@@ -1912,9 +1995,9 @@ void Vehicle::BeginLoading()
const Order *order = this->GetOrder(this->cur_implicit_order_index);
while (!order->IsType(OT_IMPLICIT) || order->GetDestination() != this->last_station_visited) {
if (order->IsType(OT_IMPLICIT)) {
/* Delete order effectively deletes order, so get the next before deleting it. */
order = order->next;
DeleteOrder(this, this->cur_implicit_order_index);
/* DeleteOrder does various magic with order_indices, so resync 'order' with 'cur_implicit_order_index' */
order = this->GetOrder(this->cur_implicit_order_index);
} else {
/* Skip non-implicit orders, e.g. service-orders */
order = order->next;
@@ -1929,7 +2012,9 @@ void Vehicle::BeginLoading()
assert(order != NULL);
}
}
} else if (!suppress_implicit_orders && this->orders.list->GetNumOrders() < MAX_VEH_ORDER_ID && Order::CanAllocateItem()) {
} else if (!suppress_implicit_orders &&
((this->orders.list == NULL ? OrderList::CanAllocateItem() : this->orders.list->GetNumOrders() < MAX_VEH_ORDER_ID)) &&
Order::CanAllocateItem()) {
/* Insert new implicit order */
Order *implicit_order = new Order();
implicit_order->MakeImplicit(this->last_station_visited);
@@ -1946,7 +2031,12 @@ void Vehicle::BeginLoading()
this->current_order.MakeLoading(false);
}
Station::Get(this->last_station_visited)->loading_vehicles.push_back(this);
if (this->last_loading_station != INVALID_STATION &&
this->last_loading_station != this->last_station_visited &&
((this->current_order.GetLoadType() & OLFB_NO_LOAD) == 0 ||
(this->current_order.GetUnloadType() & OUFB_NO_UNLOAD) == 0)) {
IncreaseStats(Station::Get(this->last_loading_station), this, this->last_station_visited);
}
PrepareUnload(this);
@@ -1960,6 +2050,24 @@ void Vehicle::BeginLoading()
this->MarkDirty();
}
/**
* Return all reserved cargo packets to the station and reset all packets
* staged for transfer.
* @param st the station where the reserved packets should go.
*/
void Vehicle::CancelReservation(StationID next, Station *st)
{
for (Vehicle *v = this; v != NULL; v = v->next) {
VehicleCargoList &cargo = v->cargo;
if (cargo.ActionCount(VehicleCargoList::MTA_LOAD) > 0) {
DEBUG(misc, 1, "cancelling cargo reservation");
cargo.Return(UINT_MAX, &st->goods[v->cargo_type].cargo, next);
cargo.SetTransferLoadPlace(st->xy);
}
cargo.KeepAll();
}
}
/**
* Perform all actions when leaving a station.
* @pre this->current_order.IsType(OT_LOADING)
@@ -1973,20 +2081,51 @@ 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);
if ((this->current_order.GetLoadType() & OLFB_NO_LOAD) == 0 ||
(this->current_order.GetUnloadType() & OUFB_NO_UNLOAD) == 0) {
if (this->current_order.CanLeaveWithCargo(this->last_loading_station != INVALID_STATION)) {
/* Refresh next hop stats to make sure we've done that at least once
* during the stop and that refit_cap == cargo_cap for each vehicle in
* the consist. */
this->ResetRefitCaps();
LinkRefresher::Run(this);
/* if the vehicle could load here or could stop with cargo loaded set the last loading station */
this->last_loading_station = this->last_station_visited;
} else {
/* if the vehicle couldn't load and had to unload or transfer everything
* set the last loading station to invalid as it will leave empty. */
this->last_loading_station = INVALID_STATION;
}
}
this->current_order.MakeLeaveStation();
Station *st = Station::Get(this->last_station_visited);
this->CancelReservation(INVALID_STATION, st);
st->loading_vehicles.remove(this);
HideFillingPercent(&this->fill_percent_te_id);
if (this->type == VEH_TRAIN && !(this->vehstatus & VS_CRASHED)) {
/* Trigger station animation (trains only) */
if (IsTileType(this->tile, MP_STATION)) TriggerStationAnimation(st, this->tile, SAT_TRAIN_DEPARTS);
if (IsTileType(this->tile, MP_STATION)) {
TriggerStationRandomisation(st, this->tile, SRT_TRAIN_DEPARTS);
TriggerStationAnimation(st, this->tile, SAT_TRAIN_DEPARTS);
}
SetBit(Train::From(this)->flags, VRF_LEAVING_STATION);
}
this->MarkDirty();
}
/**
* Reset all refit_cap in the consist to cargo_cap.
*/
void Vehicle::ResetRefitCaps()
{
for (Vehicle *v = this; v != NULL; v = v->Next()) v->refit_cap = v->cargo_cap;
}
/**
* Handle the loading of the vehicle; when not it skips through dummy
@@ -1997,7 +2136,7 @@ void Vehicle::HandleLoading(bool mode)
{
switch (this->current_order.GetType()) {
case OT_LOADING: {
uint wait_time = max(this->current_order.wait_time - this->lateness_counter, 0);
uint wait_time = max(this->current_order.GetTimetabledWait() - this->lateness_counter, 0);
/* Pay the loading fee for using someone else's station, if appropriate */
if (!mode && this->type != VEH_TRAIN) PayStationSharingFee(this, Station::Get(this->last_station_visited));
@@ -2027,6 +2166,34 @@ void Vehicle::HandleLoading(bool mode)
this->IncrementImplicitOrderIndex();
}
/**
* Get a map of cargoes and free capacities in the consist.
* @param capacities Map to be filled with cargoes and capacities.
*/
void Vehicle::GetConsistFreeCapacities(SmallMap<CargoID, uint> &capacities) const
{
for (const Vehicle *v = this; v != NULL; v = v->Next()) {
if (v->cargo_cap == 0) continue;
SmallPair<CargoID, uint> *pair = capacities.Find(v->cargo_type);
if (pair == capacities.End()) {
pair = capacities.Append();
pair->first = v->cargo_type;
pair->second = v->cargo_cap - v->cargo.StoredCount();
} else {
pair->second += v->cargo_cap - v->cargo.StoredCount();
}
}
}
uint Vehicle::GetConsistTotalCapacity() const
{
uint result = 0;
for (const Vehicle *v = this; v != NULL; v = v->Next()) {
result += v->cargo_cap;
}
return result;
}
/**
* Send this vehicle to the depot using the given command(s).
* @param flags the command flags (like execute and such).
@@ -2081,7 +2248,7 @@ CommandCost Vehicle::SendToDepot(DoCommandFlag flags, DepotCommand command)
if (flags & DC_EXEC) {
if (this->current_order.IsType(OT_LOADING)) this->LeaveStation();
if (this->IsGroundVehicle()) {
if (this->IsGroundVehicle() && this->GetNumManualOrders() > 0) {
uint16 &gv_flags = this->GetGroundVehicleFlags();
SetBit(gv_flags, GVF_SUPPRESS_IMPLICIT_ORDERS);
}
@@ -2176,6 +2343,61 @@ static const int8 _vehicle_smoke_pos[8] = {
1, 1, 1, 0, -1, -1, -1, 0
};
/**
* Call CBID_VEHICLE_SPAWN_VISUAL_EFFECT and spawn requested effects.
* @param v Vehicle to create effects for.
*/
static void SpawnAdvancedVisualEffect(const Vehicle *v)
{
uint16 callback = GetVehicleCallback(CBID_VEHICLE_SPAWN_VISUAL_EFFECT, 0, Random(), v->engine_type, v);
if (callback == CALLBACK_FAILED) return;
uint count = GB(callback, 0, 2);
bool auto_center = HasBit(callback, 13);
bool auto_rotate = !HasBit(callback, 14);
int8 l_center = 0;
if (auto_center) {
/* For road vehicles: Compute offset from vehicle position to vehicle center */
if (v->type == VEH_ROAD) l_center = -(int)(VEHICLE_LENGTH - RoadVehicle::From(v)->gcache.cached_veh_length) / 2;
} else {
/* For trains: Compute offset from vehicle position to sprite position */
if (v->type == VEH_TRAIN) l_center = (VEHICLE_LENGTH - Train::From(v)->gcache.cached_veh_length) / 2;
}
Direction l_dir = v->direction;
if (v->type == VEH_TRAIN && HasBit(Train::From(v)->flags, VRF_REVERSE_DIRECTION)) l_dir = ReverseDir(l_dir);
Direction t_dir = ChangeDir(l_dir, DIRDIFF_90RIGHT);
int8 x_center = _vehicle_smoke_pos[l_dir] * l_center;
int8 y_center = _vehicle_smoke_pos[t_dir] * l_center;
for (uint i = 0; i < count; i++) {
uint32 reg = GetRegister(0x100 + i);
uint type = GB(reg, 0, 8);
int8 x = GB(reg, 8, 8);
int8 y = GB(reg, 16, 8);
int8 z = GB(reg, 24, 8);
if (auto_rotate) {
int8 l = x;
int8 t = y;
x = _vehicle_smoke_pos[l_dir] * l + _vehicle_smoke_pos[t_dir] * t;
y = _vehicle_smoke_pos[t_dir] * l - _vehicle_smoke_pos[l_dir] * t;
}
if (type >= 0xF0) {
switch (type) {
case 0xF1: CreateEffectVehicleRel(v, x_center + x, y_center + y, z, EV_STEAM_SMOKE); break;
case 0xF2: CreateEffectVehicleRel(v, x_center + x, y_center + y, z, EV_DIESEL_SMOKE); break;
case 0xF3: CreateEffectVehicleRel(v, x_center + x, y_center + y, z, EV_ELECTRIC_SPARK); break;
case 0xFA: CreateEffectVehicleRel(v, x_center + x, y_center + y, z, EV_BREAKDOWN_SMOKE_AIRCRAFT); break;
default: break;
}
}
}
}
/**
* Draw visual effects (smoke and/or sparks) for a vehicle chain.
* @pre this->IsPrimaryVehicle()
@@ -2196,7 +2418,9 @@ void Vehicle::ShowVisualEffect() const
return;
}
uint max_speed = this->vcache.cached_max_speed;
/* Use the speed as limited by underground and orders. */
uint max_speed = this->GetCurrentMaxSpeed();
if (this->type == VEH_TRAIN) {
const Train *t = Train::From(this);
/* For trains, do not show any smoke when:
@@ -2205,21 +2429,28 @@ void Vehicle::ShowVisualEffect() const
*/
if (HasBit(t->flags, VRF_REVERSING) ||
(IsRailStationTile(t->tile) && t->IsFrontEngine() && t->current_order.ShouldStopAtStation(t, GetStationIndex(t->tile)) &&
t->cur_speed >= t->Train::GetCurrentMaxSpeed())) {
t->cur_speed >= max_speed)) {
return;
}
max_speed = min(max_speed, t->gcache.cached_max_track_speed);
max_speed = min(max_speed, this->current_order.max_speed);
}
if (this->type == VEH_ROAD || this->type == VEH_SHIP) max_speed = min(max_speed, this->current_order.max_speed * 2);
const Vehicle *v = this;
do {
bool advanced = HasBit(v->vcache.cached_vis_effect, VE_ADVANCED_EFFECT);
int effect_offset = GB(v->vcache.cached_vis_effect, VE_OFFSET_START, VE_OFFSET_COUNT) - VE_OFFSET_CENTRE;
byte effect_type = GB(v->vcache.cached_vis_effect, VE_TYPE_START, VE_TYPE_COUNT);
bool disable_effect = HasBit(v->vcache.cached_vis_effect, VE_DISABLE_EFFECT);
VisualEffectSpawnModel effect_model = VESM_NONE;
if (advanced) {
effect_offset = VE_OFFSET_CENTRE;
effect_model = (VisualEffectSpawnModel)GB(v->vcache.cached_vis_effect, 0, VE_ADVANCED_EFFECT);
if (effect_model >= VESM_END) effect_model = VESM_NONE; // unknown spawning model
} else {
effect_model = (VisualEffectSpawnModel)GB(v->vcache.cached_vis_effect, VE_TYPE_START, VE_TYPE_COUNT);
assert(effect_model != (VisualEffectSpawnModel)VE_TYPE_DEFAULT); // should have been resolved by UpdateVisualEffect
assert_compile((uint)VESM_STEAM == (uint)VE_TYPE_STEAM);
assert_compile((uint)VESM_DIESEL == (uint)VE_TYPE_DIESEL);
assert_compile((uint)VESM_ELECTRIC == (uint)VE_TYPE_ELECTRIC);
}
/* Show no smoke when:
* - Smoke has been disabled for this vehicle
@@ -2228,9 +2459,9 @@ void Vehicle::ShowVisualEffect() const
* - The vehicle is on a depot tile
* - The vehicle is on a tunnel tile
* - The vehicle is a train engine that is currently unpowered */
if (disable_effect ||
if (effect_model == VESM_NONE ||
v->vehstatus & VS_HIDDEN ||
(MayHaveBridgeAbove(v->tile) && IsBridgeAbove(v->tile)) ||
IsBridgeAbove(v->tile) ||
IsDepotTile(v->tile) ||
IsTunnelTile(v->tile) ||
(v->type == VEH_TRAIN &&
@@ -2238,33 +2469,20 @@ void Vehicle::ShowVisualEffect() const
continue;
}
/* The effect offset is relative to a point 4 units behind the vehicle's
* front (which is the center of an 8/8 vehicle). Shorter vehicles need a
* correction factor. */
if (v->type == VEH_TRAIN) effect_offset += (VEHICLE_LENGTH - Train::From(v)->gcache.cached_veh_length) / 2;
int x = _vehicle_smoke_pos[v->direction] * effect_offset;
int y = _vehicle_smoke_pos[(v->direction + 2) % 8] * effect_offset;
if (v->type == VEH_TRAIN && HasBit(Train::From(v)->flags, VRF_REVERSE_DIRECTION)) {
x = -x;
y = -y;
}
switch (effect_type) {
case VE_TYPE_STEAM:
EffectVehicleType evt = EV_END;
switch (effect_model) {
case VESM_STEAM:
/* Steam smoke - amount is gradually falling until vehicle reaches its maximum speed, after that it's normal.
* Details: while vehicle's current speed is gradually increasing, steam plumes' density decreases by one third each
* third of its maximum speed spectrum. Steam emission finally normalises at very close to vehicle's maximum speed.
* REGULATION:
* - instead of 1, 4 / 2^smoke_amount (max. 2) is used to provide sufficient regulation to steam puffs' amount. */
if (GB(v->tick_counter, 0, ((4 >> _settings_game.vehicle.smoke_amount) + ((this->cur_speed * 3) / max_speed))) == 0) {
CreateEffectVehicleRel(v, x, y, 10, EV_STEAM_SMOKE);
sound = true;
evt = EV_STEAM_SMOKE;
}
break;
case VE_TYPE_DIESEL: {
case VESM_DIESEL: {
/* Diesel smoke - thicker when vehicle is starting, gradually subsiding till it reaches its maximum speed
* when smoke emission stops.
* Details: Vehicle's (max.) speed spectrum is divided into 32 parts. When max. speed is reached, chance for smoke
@@ -2282,28 +2500,48 @@ void Vehicle::ShowVisualEffect() const
}
if (this->cur_speed < (max_speed >> (2 >> _settings_game.vehicle.smoke_amount)) &&
Chance16((64 - ((this->cur_speed << 5) / max_speed) + power_weight_effect), (512 >> _settings_game.vehicle.smoke_amount))) {
CreateEffectVehicleRel(v, x, y, 10, EV_DIESEL_SMOKE);
sound = true;
evt = EV_DIESEL_SMOKE;
}
break;
}
case VE_TYPE_ELECTRIC:
case VESM_ELECTRIC:
/* Electric train's spark - more often occurs when train is departing (more load)
* Details: Electric locomotives are usually at least twice as powerful as their diesel counterparts, so spark
* emissions are kept simple. Only when starting, creating huge force are sparks more likely to happen, but when
* reaching its max. speed, quarter by quarter of it, chance decreases untill the usuall 2,22% at train's top speed.
* reaching its max. speed, quarter by quarter of it, chance decreases until the usual 2,22% at train's top speed.
* REGULATION:
* - in Chance16 the last value is 360 / 2^smoke_amount (max. sparks when 90 = smoke_amount of 2). */
if (GB(v->tick_counter, 0, 2) == 0 &&
Chance16((6 - ((this->cur_speed << 2) / max_speed)), (360 >> _settings_game.vehicle.smoke_amount))) {
CreateEffectVehicleRel(v, x, y, 10, EV_ELECTRIC_SPARK);
sound = true;
evt = EV_ELECTRIC_SPARK;
}
break;
default:
break;
NOT_REACHED();
}
if (evt != EV_END && advanced) {
sound = true;
SpawnAdvancedVisualEffect(v);
} else if (evt != EV_END) {
sound = true;
/* The effect offset is relative to a point 4 units behind the vehicle's
* front (which is the center of an 8/8 vehicle). Shorter vehicles need a
* correction factor. */
if (v->type == VEH_TRAIN) effect_offset += (VEHICLE_LENGTH - Train::From(v)->gcache.cached_veh_length) / 2;
int x = _vehicle_smoke_pos[v->direction] * effect_offset;
int y = _vehicle_smoke_pos[(v->direction + 2) % 8] * effect_offset;
if (v->type == VEH_TRAIN && HasBit(Train::From(v)->flags, VRF_REVERSE_DIRECTION)) {
x = -x;
y = -y;
}
CreateEffectVehicleRel(v, x, y, 10, evt);
}
} while ((v = v->Next()) != NULL);
@@ -2408,11 +2646,7 @@ void VehiclesYearlyLoop()
if (_settings_client.gui.vehicle_income_warn && v->owner == _local_company) {
SetDParam(0, v->index);
SetDParam(1, profit);
AddVehicleNewsItem(
STR_NEWS_VEHICLE_IS_UNPROFITABLE,
NS_ADVICE,
v->index
);
AddVehicleAdviceNewsItem(STR_NEWS_VEHICLE_IS_UNPROFITABLE, v->index);
}
AI::NewEvent(v->owner, new ScriptEventVehicleUnprofitable(v->index));
}