2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
8 /** @file script_vehicle.cpp Implementation of ScriptVehicle. */
10 #include "../../stdafx.h"
11 #include "script_engine.hpp"
12 #include "script_cargo.hpp"
13 #include "script_gamesettings.hpp"
14 #include "script_group.hpp"
15 #include "script_map.hpp"
16 #include "../script_instance.hpp"
17 #include "../../string_func.h"
18 #include "../../strings_func.h"
19 #include "../../command_func.h"
20 #include "../../roadveh.h"
21 #include "../../train.h"
22 #include "../../vehicle_func.h"
23 #include "../../aircraft.h"
24 #include "../../roadveh_cmd.h"
25 #include "../../train_cmd.h"
26 #include "../../vehicle_cmd.h"
27 #include "table/strings.h"
29 #include "../../safeguards.h"
31 /* static */ bool ScriptVehicle::IsValidVehicle(VehicleID vehicle_id
)
33 EnforceDeityOrCompanyModeValid(false);
34 const Vehicle
*v
= ::Vehicle::GetIfValid(vehicle_id
);
35 return v
!= nullptr && (v
->owner
== ScriptObject::GetCompany() || ScriptCompanyMode::IsDeity()) && (v
->IsPrimaryVehicle() || (v
->type
== VEH_TRAIN
&& ::Train::From(v
)->IsFreeWagon()));
38 /* static */ bool ScriptVehicle::IsPrimaryVehicle(VehicleID vehicle_id
)
40 if (!IsValidVehicle(vehicle_id
)) return false;
42 return ::Vehicle::Get(vehicle_id
)->IsPrimaryVehicle();
45 /* static */ ScriptCompany::CompanyID
ScriptVehicle::GetOwner(VehicleID vehicle_id
)
47 if (!IsValidVehicle(vehicle_id
)) return ScriptCompany::COMPANY_INVALID
;
49 return static_cast<ScriptCompany::CompanyID
>((int)::Vehicle::Get(vehicle_id
)->owner
);
52 /* static */ SQInteger
ScriptVehicle::GetNumWagons(VehicleID vehicle_id
)
54 if (!IsValidVehicle(vehicle_id
)) return -1;
58 const Train
*v
= ::Train::GetIfValid(vehicle_id
);
60 while ((v
= v
->GetNextUnit()) != nullptr) num
++;
66 /* static */ SQInteger
ScriptVehicle::GetLength(VehicleID vehicle_id
)
68 if (!IsValidVehicle(vehicle_id
)) return -1;
70 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
71 return v
->IsGroundVehicle() ? v
->GetGroundVehicleCache()->cached_total_length
: -1;
74 /* static */ VehicleID
ScriptVehicle::_BuildVehicleInternal(TileIndex depot
, EngineID engine_id
, CargoID cargo
)
76 EnforceCompanyModeValid(VEHICLE_INVALID
);
77 EnforcePrecondition(VEHICLE_INVALID
, ScriptEngine::IsBuildable(engine_id
));
78 EnforcePrecondition(VEHICLE_INVALID
, !::IsValidCargoID(cargo
) || ScriptCargo::IsValidCargo(cargo
));
80 ::VehicleType type
= ::Engine::Get(engine_id
)->type
;
82 EnforcePreconditionCustomError(VEHICLE_INVALID
, !ScriptGameSettings::IsDisabledVehicleType((ScriptVehicle::VehicleType
)type
), ScriptVehicle::ERR_VEHICLE_BUILD_DISABLED
);
84 if (!ScriptObject::Command
<CMD_BUILD_VEHICLE
>::Do(&ScriptInstance::DoCommandReturnVehicleID
, depot
, engine_id
, true, cargo
, INVALID_CLIENT_ID
)) return VEHICLE_INVALID
;
86 /* In case of test-mode, we return VehicleID 0 */
90 /* static */ VehicleID
ScriptVehicle::BuildVehicle(TileIndex depot
, EngineID engine_id
)
92 return _BuildVehicleInternal(depot
, engine_id
, INVALID_CARGO
);
95 /* static */ VehicleID
ScriptVehicle::BuildVehicleWithRefit(TileIndex depot
, EngineID engine_id
, CargoID cargo
)
97 EnforcePrecondition(VEHICLE_INVALID
, ScriptCargo::IsValidCargo(cargo
));
98 return _BuildVehicleInternal(depot
, engine_id
, cargo
);
101 /* static */ SQInteger
ScriptVehicle::GetBuildWithRefitCapacity(TileIndex depot
, EngineID engine_id
, CargoID cargo
)
103 if (!ScriptEngine::IsBuildable(engine_id
)) return -1;
104 if (!ScriptCargo::IsValidCargo(cargo
)) return -1;
106 auto [res
, veh_id
, refit_capacity
, refit_mail
, cargo_capacities
] = ::Command
<CMD_BUILD_VEHICLE
>::Do(DC_QUERY_COST
, depot
, engine_id
, true, cargo
, INVALID_CLIENT_ID
);
107 return res
.Succeeded() ? refit_capacity
: -1;
110 /* static */ VehicleID
ScriptVehicle::CloneVehicle(TileIndex depot
, VehicleID vehicle_id
, bool share_orders
)
112 EnforceCompanyModeValid(false);
113 EnforcePrecondition(false, IsPrimaryVehicle(vehicle_id
));
115 if (!ScriptObject::Command
<CMD_CLONE_VEHICLE
>::Do(&ScriptInstance::DoCommandReturnVehicleID
, depot
, vehicle_id
, share_orders
)) return VEHICLE_INVALID
;
117 /* In case of test-mode, we return VehicleID 0 */
121 /* static */ bool ScriptVehicle::_MoveWagonInternal(VehicleID source_vehicle_id
, SQInteger source_wagon
, bool move_attached_wagons
, SQInteger dest_vehicle_id
, SQInteger dest_wagon
)
123 EnforceCompanyModeValid(false);
124 EnforcePrecondition(false, IsValidVehicle(source_vehicle_id
) && source_wagon
< GetNumWagons(source_vehicle_id
));
125 EnforcePrecondition(false, dest_vehicle_id
== -1 || (IsValidVehicle(dest_vehicle_id
) && dest_wagon
< GetNumWagons(dest_vehicle_id
)));
126 EnforcePrecondition(false, ::Vehicle::Get(source_vehicle_id
)->type
== VEH_TRAIN
);
127 EnforcePrecondition(false, dest_vehicle_id
== -1 || ::Vehicle::Get(dest_vehicle_id
)->type
== VEH_TRAIN
);
129 const Train
*v
= ::Train::Get(source_vehicle_id
);
130 while (source_wagon
-- > 0) v
= v
->GetNextUnit();
131 const Train
*w
= nullptr;
132 if (dest_vehicle_id
!= -1) {
133 w
= ::Train::Get(dest_vehicle_id
);
134 while (dest_wagon
-- > 0) w
= w
->GetNextUnit();
137 return ScriptObject::Command
<CMD_MOVE_RAIL_VEHICLE
>::Do(v
->index
, w
== nullptr ? ::INVALID_VEHICLE
: w
->index
, move_attached_wagons
);
140 /* static */ bool ScriptVehicle::MoveWagon(VehicleID source_vehicle_id
, SQInteger source_wagon
, SQInteger dest_vehicle_id
, SQInteger dest_wagon
)
142 return _MoveWagonInternal(source_vehicle_id
, source_wagon
, false, dest_vehicle_id
, dest_wagon
);
145 /* static */ bool ScriptVehicle::MoveWagonChain(VehicleID source_vehicle_id
, SQInteger source_wagon
, SQInteger dest_vehicle_id
, SQInteger dest_wagon
)
147 return _MoveWagonInternal(source_vehicle_id
, source_wagon
, true, dest_vehicle_id
, dest_wagon
);
150 /* static */ SQInteger
ScriptVehicle::GetRefitCapacity(VehicleID vehicle_id
, CargoID cargo
)
152 if (!IsValidVehicle(vehicle_id
)) return -1;
153 if (!ScriptCargo::IsValidCargo(cargo
)) return -1;
155 auto [res
, refit_capacity
, refit_mail
, cargo_capacities
] = ::Command
<CMD_REFIT_VEHICLE
>::Do(DC_QUERY_COST
, vehicle_id
, cargo
, 0, false, false, 0);
156 return res
.Succeeded() ? refit_capacity
: -1;
159 /* static */ bool ScriptVehicle::RefitVehicle(VehicleID vehicle_id
, CargoID cargo
)
161 EnforceCompanyModeValid(false);
162 EnforcePrecondition(false, IsValidVehicle(vehicle_id
) && ScriptCargo::IsValidCargo(cargo
));
164 return ScriptObject::Command
<CMD_REFIT_VEHICLE
>::Do(vehicle_id
, cargo
, 0, false, false, 0);
168 /* static */ bool ScriptVehicle::SellVehicle(VehicleID vehicle_id
)
170 EnforceCompanyModeValid(false);
171 EnforcePrecondition(false, IsValidVehicle(vehicle_id
));
173 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
174 return ScriptObject::Command
<CMD_SELL_VEHICLE
>::Do(vehicle_id
, v
->type
== VEH_TRAIN
, false, INVALID_CLIENT_ID
);
177 /* static */ bool ScriptVehicle::_SellWagonInternal(VehicleID vehicle_id
, SQInteger wagon
, bool sell_attached_wagons
)
179 EnforceCompanyModeValid(false);
180 EnforcePrecondition(false, IsValidVehicle(vehicle_id
) && wagon
< GetNumWagons(vehicle_id
));
181 EnforcePrecondition(false, ::Vehicle::Get(vehicle_id
)->type
== VEH_TRAIN
);
183 const Train
*v
= ::Train::Get(vehicle_id
);
184 while (wagon
-- > 0) v
= v
->GetNextUnit();
186 return ScriptObject::Command
<CMD_SELL_VEHICLE
>::Do(v
->index
, sell_attached_wagons
, false, INVALID_CLIENT_ID
);
189 /* static */ bool ScriptVehicle::SellWagon(VehicleID vehicle_id
, SQInteger wagon
)
191 return _SellWagonInternal(vehicle_id
, wagon
, false);
194 /* static */ bool ScriptVehicle::SellWagonChain(VehicleID vehicle_id
, SQInteger wagon
)
196 return _SellWagonInternal(vehicle_id
, wagon
, true);
199 /* static */ bool ScriptVehicle::SendVehicleToDepot(VehicleID vehicle_id
)
201 EnforceCompanyModeValid(false);
202 EnforcePrecondition(false, IsPrimaryVehicle(vehicle_id
));
204 return ScriptObject::Command
<CMD_SEND_VEHICLE_TO_DEPOT
>::Do(vehicle_id
, DepotCommand::None
, {});
207 /* static */ bool ScriptVehicle::SendVehicleToDepotForServicing(VehicleID vehicle_id
)
209 EnforceCompanyModeValid(false);
210 EnforcePrecondition(false, IsPrimaryVehicle(vehicle_id
));
212 return ScriptObject::Command
<CMD_SEND_VEHICLE_TO_DEPOT
>::Do(vehicle_id
, DepotCommand::Service
, {});
215 /* static */ bool ScriptVehicle::IsInDepot(VehicleID vehicle_id
)
217 if (!IsValidVehicle(vehicle_id
)) return false;
218 return ::Vehicle::Get(vehicle_id
)->IsChainInDepot();
221 /* static */ bool ScriptVehicle::IsStoppedInDepot(VehicleID vehicle_id
)
223 if (!IsValidVehicle(vehicle_id
)) return false;
224 return ::Vehicle::Get(vehicle_id
)->IsStoppedInDepot();
227 /* static */ bool ScriptVehicle::StartStopVehicle(VehicleID vehicle_id
)
229 EnforceCompanyModeValid(false);
230 EnforcePrecondition(false, IsPrimaryVehicle(vehicle_id
));
232 return ScriptObject::Command
<CMD_START_STOP_VEHICLE
>::Do(vehicle_id
, false);
235 /* static */ bool ScriptVehicle::ReverseVehicle(VehicleID vehicle_id
)
237 EnforceCompanyModeValid(false);
238 EnforcePrecondition(false, IsPrimaryVehicle(vehicle_id
));
239 EnforcePrecondition(false, ::Vehicle::Get(vehicle_id
)->type
== VEH_ROAD
|| ::Vehicle::Get(vehicle_id
)->type
== VEH_TRAIN
);
241 switch (::Vehicle::Get(vehicle_id
)->type
) {
242 case VEH_ROAD
: return ScriptObject::Command
<CMD_TURN_ROADVEH
>::Do(vehicle_id
);
243 case VEH_TRAIN
: return ScriptObject::Command
<CMD_REVERSE_TRAIN_DIRECTION
>::Do(vehicle_id
, false);
244 default: NOT_REACHED();
248 /* static */ bool ScriptVehicle::SetName(VehicleID vehicle_id
, Text
*name
)
250 CCountedPtr
<Text
> counter(name
);
252 EnforceCompanyModeValid(false);
253 EnforcePrecondition(false, IsPrimaryVehicle(vehicle_id
));
254 EnforcePrecondition(false, name
!= nullptr);
255 const std::string
&text
= name
->GetDecodedText();
256 EnforcePreconditionEncodedText(false, text
);
257 EnforcePreconditionCustomError(false, ::Utf8StringLength(text
) < MAX_LENGTH_VEHICLE_NAME_CHARS
, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG
);
259 return ScriptObject::Command
<CMD_RENAME_VEHICLE
>::Do(vehicle_id
, text
);
262 /* static */ TileIndex
ScriptVehicle::GetLocation(VehicleID vehicle_id
)
264 if (!IsValidVehicle(vehicle_id
)) return INVALID_TILE
;
266 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
267 if (v
->type
== VEH_AIRCRAFT
) {
268 uint x
= Clamp(v
->x_pos
/ TILE_SIZE
, 0, ScriptMap::GetMapSizeX() - 2);
269 uint y
= Clamp(v
->y_pos
/ TILE_SIZE
, 0, ScriptMap::GetMapSizeY() - 2);
270 return ::TileXY(x
, y
);
276 /* static */ EngineID
ScriptVehicle::GetEngineType(VehicleID vehicle_id
)
278 if (!IsValidVehicle(vehicle_id
)) return INVALID_ENGINE
;
280 return ::Vehicle::Get(vehicle_id
)->engine_type
;
283 /* static */ EngineID
ScriptVehicle::GetWagonEngineType(VehicleID vehicle_id
, SQInteger wagon
)
285 if (!IsValidVehicle(vehicle_id
)) return INVALID_ENGINE
;
286 if (wagon
>= GetNumWagons(vehicle_id
)) return INVALID_ENGINE
;
288 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
289 if (v
->type
== VEH_TRAIN
) {
290 while (wagon
-- > 0) v
= ::Train::From(v
)->GetNextUnit();
292 return v
->engine_type
;
295 /* static */ SQInteger
ScriptVehicle::GetUnitNumber(VehicleID vehicle_id
)
297 if (!IsPrimaryVehicle(vehicle_id
)) return -1;
299 return ::Vehicle::Get(vehicle_id
)->unitnumber
;
302 /* static */ std::optional
<std::string
> ScriptVehicle::GetName(VehicleID vehicle_id
)
304 if (!IsPrimaryVehicle(vehicle_id
)) return std::nullopt
;
306 ::SetDParam(0, vehicle_id
);
307 return GetString(STR_VEHICLE_NAME
);
310 /* static */ SQInteger
ScriptVehicle::GetAge(VehicleID vehicle_id
)
312 if (!IsValidVehicle(vehicle_id
)) return -1;
314 return ::Vehicle::Get(vehicle_id
)->age
.base();
317 /* static */ SQInteger
ScriptVehicle::GetWagonAge(VehicleID vehicle_id
, SQInteger wagon
)
319 if (!IsValidVehicle(vehicle_id
)) return -1;
320 if (wagon
>= GetNumWagons(vehicle_id
)) return -1;
322 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
323 if (v
->type
== VEH_TRAIN
) {
324 while (wagon
-- > 0) v
= ::Train::From(v
)->GetNextUnit();
326 return v
->age
.base();
329 /* static */ SQInteger
ScriptVehicle::GetMaxAge(VehicleID vehicle_id
)
331 if (!IsPrimaryVehicle(vehicle_id
)) return -1;
333 return ::Vehicle::Get(vehicle_id
)->max_age
.base();
336 /* static */ SQInteger
ScriptVehicle::GetAgeLeft(VehicleID vehicle_id
)
338 if (!IsPrimaryVehicle(vehicle_id
)) return -1;
340 return (::Vehicle::Get(vehicle_id
)->max_age
- ::Vehicle::Get(vehicle_id
)->age
).base();
343 /* static */ SQInteger
ScriptVehicle::GetCurrentSpeed(VehicleID vehicle_id
)
345 if (!IsPrimaryVehicle(vehicle_id
)) return -1;
347 const ::Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
348 return (v
->vehstatus
& (::VS_STOPPED
| ::VS_CRASHED
)) == 0 ? v
->GetDisplaySpeed() : 0; // km-ish/h
351 /* static */ ScriptVehicle::VehicleState
ScriptVehicle::GetState(VehicleID vehicle_id
)
353 if (!IsValidVehicle(vehicle_id
)) return ScriptVehicle::VS_INVALID
;
355 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
356 uint8_t vehstatus
= v
->vehstatus
;
358 if (vehstatus
& ::VS_CRASHED
) return ScriptVehicle::VS_CRASHED
;
359 if (v
->breakdown_ctr
!= 0) return ScriptVehicle::VS_BROKEN
;
360 if (v
->IsStoppedInDepot()) return ScriptVehicle::VS_IN_DEPOT
;
361 if (vehstatus
& ::VS_STOPPED
) return ScriptVehicle::VS_STOPPED
;
362 if (v
->current_order
.IsType(OT_LOADING
)) return ScriptVehicle::VS_AT_STATION
;
363 return ScriptVehicle::VS_RUNNING
;
366 /* static */ Money
ScriptVehicle::GetRunningCost(VehicleID vehicle_id
)
368 if (!IsPrimaryVehicle(vehicle_id
)) return -1;
370 return ::Vehicle::Get(vehicle_id
)->GetRunningCost() >> 8;
373 /* static */ Money
ScriptVehicle::GetProfitThisYear(VehicleID vehicle_id
)
375 if (!IsPrimaryVehicle(vehicle_id
)) return -1;
377 return ::Vehicle::Get(vehicle_id
)->GetDisplayProfitThisYear();
380 /* static */ Money
ScriptVehicle::GetProfitLastYear(VehicleID vehicle_id
)
382 if (!IsPrimaryVehicle(vehicle_id
)) return -1;
384 return ::Vehicle::Get(vehicle_id
)->GetDisplayProfitLastYear();
387 /* static */ Money
ScriptVehicle::GetCurrentValue(VehicleID vehicle_id
)
389 if (!IsValidVehicle(vehicle_id
)) return -1;
391 return ::Vehicle::Get(vehicle_id
)->value
;
394 /* static */ ScriptVehicle::VehicleType
ScriptVehicle::GetVehicleType(VehicleID vehicle_id
)
396 if (!IsValidVehicle(vehicle_id
)) return VT_INVALID
;
398 switch (::Vehicle::Get(vehicle_id
)->type
) {
399 case VEH_ROAD
: return VT_ROAD
;
400 case VEH_TRAIN
: return VT_RAIL
;
401 case VEH_SHIP
: return VT_WATER
;
402 case VEH_AIRCRAFT
: return VT_AIR
;
403 default: return VT_INVALID
;
407 /* static */ ScriptRoad::RoadType
ScriptVehicle::GetRoadType(VehicleID vehicle_id
)
409 if (!IsValidVehicle(vehicle_id
)) return ScriptRoad::ROADTYPE_INVALID
;
410 if (GetVehicleType(vehicle_id
) != VT_ROAD
) return ScriptRoad::ROADTYPE_INVALID
;
412 return (ScriptRoad::RoadType
)(int)(::RoadVehicle::Get(vehicle_id
))->roadtype
;
415 /* static */ SQInteger
ScriptVehicle::GetCapacity(VehicleID vehicle_id
, CargoID cargo
)
417 if (!IsValidVehicle(vehicle_id
)) return -1;
418 if (!ScriptCargo::IsValidCargo(cargo
)) return -1;
421 for (const Vehicle
*v
= ::Vehicle::Get(vehicle_id
); v
!= nullptr; v
= v
->Next()) {
422 if (v
->cargo_type
== cargo
) amount
+= v
->cargo_cap
;
428 /* static */ SQInteger
ScriptVehicle::GetCargoLoad(VehicleID vehicle_id
, CargoID cargo
)
430 if (!IsValidVehicle(vehicle_id
)) return -1;
431 if (!ScriptCargo::IsValidCargo(cargo
)) return -1;
434 for (const Vehicle
*v
= ::Vehicle::Get(vehicle_id
); v
!= nullptr; v
= v
->Next()) {
435 if (v
->cargo_type
== cargo
) amount
+= v
->cargo
.StoredCount();
441 /* static */ GroupID
ScriptVehicle::GetGroupID(VehicleID vehicle_id
)
443 if (!IsPrimaryVehicle(vehicle_id
)) return ScriptGroup::GROUP_INVALID
;
445 return ::Vehicle::Get(vehicle_id
)->group_id
;
448 /* static */ bool ScriptVehicle::IsArticulated(VehicleID vehicle_id
)
450 if (!IsValidVehicle(vehicle_id
)) return false;
451 if (GetVehicleType(vehicle_id
) != VT_ROAD
&& GetVehicleType(vehicle_id
) != VT_RAIL
) return false;
453 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
455 case VEH_ROAD
: return ::RoadVehicle::From(v
)->HasArticulatedPart();
456 case VEH_TRAIN
: return ::Train::From(v
)->HasArticulatedPart();
457 default: NOT_REACHED();
461 /* static */ bool ScriptVehicle::HasSharedOrders(VehicleID vehicle_id
)
463 if (!IsPrimaryVehicle(vehicle_id
)) return false;
465 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
466 return v
->orders
!= nullptr && v
->orders
->GetNumVehicles() > 1;
469 /* static */ SQInteger
ScriptVehicle::GetReliability(VehicleID vehicle_id
)
471 if (!IsPrimaryVehicle(vehicle_id
)) return -1;
473 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
474 return ::ToPercent16(v
->reliability
);
477 /* static */ SQInteger
ScriptVehicle::GetMaximumOrderDistance(VehicleID vehicle_id
)
479 if (!IsPrimaryVehicle(vehicle_id
)) return 0;
481 const ::Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
482 if (v
->type
!= VEH_AIRCRAFT
) return 0;
483 return ::Aircraft::From(v
)->acache
.cached_max_range_sqr
;