|
@@ -50,106 +50,106 @@ const StringID _veh_build_msg_table[] =
|
|
|
STR_ERROR_CAN_T_BUY_ROAD_VEHICLE,
|
|
|
STR_ERROR_CAN_T_BUY_SHIP,
|
|
|
STR_ERROR_CAN_T_BUY_AIRCRAFT,
|
|
|
};
|
|
|
|
|
|
const StringID _veh_sell_msg_table[] = {
|
|
|
STR_ERROR_CAN_T_SELL_TRAIN,
|
|
|
STR_ERROR_CAN_T_SELL_ROAD_VEHICLE,
|
|
|
STR_ERROR_CAN_T_SELL_SHIP,
|
|
|
STR_ERROR_CAN_T_SELL_AIRCRAFT,
|
|
|
};
|
|
|
|
|
|
const StringID _veh_refit_msg_table[] = {
|
|
|
STR_ERROR_CAN_T_REFIT_TRAIN,
|
|
|
STR_ERROR_CAN_T_REFIT_ROAD_VEHICLE,
|
|
|
STR_ERROR_CAN_T_REFIT_SHIP,
|
|
|
STR_ERROR_CAN_T_REFIT_AIRCRAFT,
|
|
|
};
|
|
|
|
|
|
const StringID _send_to_depot_msg_table[] = {
|
|
|
STR_ERROR_CAN_T_SEND_TRAIN_TO_DEPOT,
|
|
|
STR_ERROR_CAN_T_SEND_ROAD_VEHICLE_TO_DEPOT,
|
|
|
STR_ERROR_CAN_T_SEND_SHIP_TO_DEPOT,
|
|
|
STR_ERROR_CAN_T_SEND_AIRCRAFT_TO_HANGAR,
|
|
|
};
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Build a vehicle.
|
|
|
* @param flags for command
|
|
|
* @param tile tile of depot where the vehicle is built
|
|
|
* @param eid vehicle type being built.
|
|
|
* @param use_free_vehicles use free vehicles when building the vehicle.
|
|
|
* @param cargo refit cargo type.
|
|
|
* @param client_id User
|
|
|
* @return the cost of this operation + the new vehicle ID + the refitted capacity + the refitted mail capacity (aircraft) or an error
|
|
|
*/
|
|
|
std::tuple<CommandCost, VehicleID, uint, uint16, CargoArray> CmdBuildVehicle(DoCommandFlag flags, TileIndex tile, EngineID eid, bool use_free_vehicles, CargoID cargo, ClientID client_id)
|
|
|
{
|
|
|
/* Elementary check for valid location. */
|
|
|
if (!IsDepotTile(tile) || !IsTileOwner(tile, _current_company)) return { CMD_ERROR, INVALID_VEHICLE, 0, 0, {} };
|
|
|
|
|
|
VehicleType type = GetDepotVehicleType(tile);
|
|
|
|
|
|
/* Validate the engine type. */
|
|
|
if (!IsEngineBuildable(eid, type, _current_company)) return { CommandCost(STR_ERROR_RAIL_VEHICLE_NOT_AVAILABLE + type), INVALID_VEHICLE, 0, 0, {} };
|
|
|
|
|
|
/* Validate the cargo type. */
|
|
|
if (cargo >= NUM_CARGO && cargo != CT_INVALID) return { CMD_ERROR, INVALID_VEHICLE, 0, 0, {} };
|
|
|
if (cargo >= NUM_CARGO && IsValidCargoID(cargo)) return { CMD_ERROR, INVALID_VEHICLE, 0, 0, {} };
|
|
|
|
|
|
const Engine *e = Engine::Get(eid);
|
|
|
CommandCost value(EXPENSES_NEW_VEHICLES, e->GetCost());
|
|
|
|
|
|
/* Engines without valid cargo should not be available */
|
|
|
CargoID default_cargo = e->GetDefaultCargoType();
|
|
|
if (default_cargo == CT_INVALID) return { CMD_ERROR, INVALID_VEHICLE, 0, 0, {} };
|
|
|
if (!IsValidCargoID(default_cargo)) return { CMD_ERROR, INVALID_VEHICLE, 0, 0, {} };
|
|
|
|
|
|
bool refitting = cargo != CT_INVALID && cargo != default_cargo;
|
|
|
bool refitting = IsValidCargoID(cargo) && cargo != default_cargo;
|
|
|
|
|
|
/* Check whether the number of vehicles we need to build can be built according to pool space. */
|
|
|
uint num_vehicles;
|
|
|
switch (type) {
|
|
|
case VEH_TRAIN: num_vehicles = (e->u.rail.railveh_type == RAILVEH_MULTIHEAD ? 2 : 1) + CountArticulatedParts(eid, false); break;
|
|
|
case VEH_ROAD: num_vehicles = 1 + CountArticulatedParts(eid, false); break;
|
|
|
case VEH_SHIP: num_vehicles = 1; break;
|
|
|
case VEH_AIRCRAFT: num_vehicles = e->u.air.subtype & AIR_CTOL ? 2 : 3; break;
|
|
|
default: NOT_REACHED(); // Safe due to IsDepotTile()
|
|
|
}
|
|
|
if (!Vehicle::CanAllocateItem(num_vehicles)) return { CommandCost(STR_ERROR_TOO_MANY_VEHICLES_IN_GAME), INVALID_VEHICLE, 0, 0, {} };
|
|
|
|
|
|
/* Check whether we can allocate a unit number. Autoreplace does not allocate
|
|
|
* an unit number as it will (always) reuse the one of the replaced vehicle
|
|
|
* and (train) wagons don't have an unit number in any scenario. */
|
|
|
UnitID unit_num = (flags & DC_QUERY_COST || flags & DC_AUTOREPLACE || (type == VEH_TRAIN && e->u.rail.railveh_type == RAILVEH_WAGON)) ? 0 : GetFreeUnitNumber(type);
|
|
|
if (unit_num == UINT16_MAX) return { CommandCost(STR_ERROR_TOO_MANY_VEHICLES_IN_GAME), INVALID_VEHICLE, 0, 0, {} };
|
|
|
|
|
|
/* If we are refitting we need to temporarily purchase the vehicle to be able to
|
|
|
* test it. */
|
|
|
DoCommandFlag subflags = flags;
|
|
|
if (refitting && !(flags & DC_EXEC)) subflags |= DC_EXEC | DC_AUTOREPLACE;
|
|
|
|
|
|
/* Vehicle construction needs random bits, so we have to save the random
|
|
|
* seeds to prevent desyncs. */
|
|
|
SavedRandomSeeds saved_seeds;
|
|
|
SaveRandomSeeds(&saved_seeds);
|
|
|
|
|
|
Vehicle *v = nullptr;
|
|
|
switch (type) {
|
|
|
case VEH_TRAIN: value.AddCost(CmdBuildRailVehicle(subflags, tile, e, use_free_vehicles, &v)); break;
|
|
|
case VEH_ROAD: value.AddCost(CmdBuildRoadVehicle(subflags, tile, e, &v)); break;
|
|
|
case VEH_SHIP: value.AddCost(CmdBuildShip (subflags, tile, e, &v)); break;
|
|
|
case VEH_AIRCRAFT: value.AddCost(CmdBuildAircraft (subflags, tile, e, &v)); break;
|
|
|
default: NOT_REACHED(); // Safe due to IsDepotTile()
|
|
|
}
|
|
|
|
|
|
VehicleID veh_id = INVALID_VEHICLE;
|
|
|
uint refitted_capacity = 0;
|
|
|
uint16 refitted_mail_capacity = 0;
|
|
|
CargoArray cargo_capacities;
|
|
|
if (value.Succeeded()) {
|
|
|
if (subflags & DC_EXEC) {
|
|
|
v->unitnumber = unit_num;
|
|
|
v->value = value.GetCost();
|
|
|
veh_id = v->index;
|
|
|
}
|
|
|
|
|
@@ -906,97 +906,97 @@ std::tuple<CommandCost, VehicleID> CmdCl
|
|
|
w->SetServiceIntervalIsPercent(v->ServiceIntervalIsPercent());
|
|
|
}
|
|
|
w_rear = w; // trains needs to know the last car in the train, so they can add more in next loop
|
|
|
}
|
|
|
} while (v->type == VEH_TRAIN && (v = v->GetNextVehicle()) != nullptr);
|
|
|
|
|
|
if ((flags & DC_EXEC) && v_front->type == VEH_TRAIN) {
|
|
|
/* for trains this needs to be the front engine due to the callback function */
|
|
|
new_veh_id = w_front->index;
|
|
|
}
|
|
|
|
|
|
if (flags & DC_EXEC) {
|
|
|
/* Cloned vehicles belong to the same group */
|
|
|
Command<CMD_ADD_VEHICLE_GROUP>::Do(flags, v_front->group_id, w_front->index, false);
|
|
|
}
|
|
|
|
|
|
|
|
|
/* Take care of refitting. */
|
|
|
w = w_front;
|
|
|
v = v_front;
|
|
|
|
|
|
/* Both building and refitting are influenced by newgrf callbacks, which
|
|
|
* makes it impossible to accurately estimate the cloning costs. In
|
|
|
* particular, it is possible for engines of the same type to be built with
|
|
|
* different numbers of articulated parts, so when refitting we have to
|
|
|
* loop over real vehicles first, and then the articulated parts of those
|
|
|
* vehicles in a different loop. */
|
|
|
do {
|
|
|
do {
|
|
|
if (flags & DC_EXEC) {
|
|
|
assert(w != nullptr);
|
|
|
|
|
|
/* Find out what's the best sub type */
|
|
|
byte subtype = GetBestFittingSubType(v, w, v->cargo_type);
|
|
|
if (w->cargo_type != v->cargo_type || w->cargo_subtype != subtype) {
|
|
|
CommandCost cost = std::get<0>(Command<CMD_REFIT_VEHICLE>::Do(flags, w->index, v->cargo_type, subtype, false, true, 0));
|
|
|
if (cost.Succeeded()) total_cost.AddCost(cost);
|
|
|
}
|
|
|
|
|
|
if (w->IsGroundVehicle() && w->HasArticulatedPart()) {
|
|
|
w = w->GetNextArticulatedPart();
|
|
|
} else {
|
|
|
break;
|
|
|
}
|
|
|
} else {
|
|
|
const Engine *e = v->GetEngine();
|
|
|
CargoID initial_cargo = (e->CanCarryCargo() ? e->GetDefaultCargoType() : (CargoID)CT_INVALID);
|
|
|
|
|
|
if (v->cargo_type != initial_cargo && initial_cargo != CT_INVALID) {
|
|
|
if (v->cargo_type != initial_cargo && IsValidCargoID(initial_cargo)) {
|
|
|
bool dummy;
|
|
|
total_cost.AddCost(GetRefitCost(nullptr, v->engine_type, v->cargo_type, v->cargo_subtype, &dummy));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (v->IsGroundVehicle() && v->HasArticulatedPart()) {
|
|
|
v = v->GetNextArticulatedPart();
|
|
|
} else {
|
|
|
break;
|
|
|
}
|
|
|
} while (v != nullptr);
|
|
|
|
|
|
if ((flags & DC_EXEC) && v->type == VEH_TRAIN) w = w->GetNextVehicle();
|
|
|
} while (v->type == VEH_TRAIN && (v = v->GetNextVehicle()) != nullptr);
|
|
|
|
|
|
if (flags & DC_EXEC) {
|
|
|
/*
|
|
|
* Set the orders of the vehicle. Cannot do it earlier as we need
|
|
|
* the vehicle refitted before doing this, otherwise the moved
|
|
|
* cargo types might not match (passenger vs non-passenger)
|
|
|
*/
|
|
|
CommandCost result = Command<CMD_CLONE_ORDER>::Do(flags, (share_orders ? CO_SHARE : CO_COPY), w_front->index, v_front->index);
|
|
|
if (result.Failed()) {
|
|
|
/* The vehicle has already been bought, so now it must be sold again. */
|
|
|
Command<CMD_SELL_VEHICLE>::Do(flags, w_front->index, true, false, INVALID_CLIENT_ID);
|
|
|
return { result, INVALID_VEHICLE };
|
|
|
}
|
|
|
|
|
|
/* Now clone the vehicle's name, if it has one. */
|
|
|
if (!v_front->name.empty()) CloneVehicleName(v_front, w_front);
|
|
|
|
|
|
/* Since we can't estimate the cost of cloning a vehicle accurately we must
|
|
|
* check whether the company has enough money manually. */
|
|
|
if (!CheckCompanyHasMoney(total_cost)) {
|
|
|
/* The vehicle has already been bought, so now it must be sold again. */
|
|
|
Command<CMD_SELL_VEHICLE>::Do(flags, w_front->index, true, false, INVALID_CLIENT_ID);
|
|
|
return { total_cost, INVALID_VEHICLE };
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return { total_cost, new_veh_id };
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Send all vehicles of type to depots
|
|
|
* @param flags the flags used for DoCommand()
|
|
|
* @param service should the vehicles only get service in the depots
|
|
|
* @param vli identifier of the vehicle list
|