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_instance.hpp"
16 #include "../../string_func.h"
17 #include "../../strings_func.h"
18 #include "../../command_func.h"
19 #include "../../roadveh.h"
20 #include "../../train.h"
21 #include "../../vehicle_func.h"
22 #include "../../aircraft.h"
23 #include "../../roadveh_cmd.h"
24 #include "../../train_cmd.h"
25 #include "../../vehicle_cmd.h"
26 #include "table/strings.h"
28 #include "../../safeguards.h"
30 /* static */ bool ScriptVehicle::IsValidVehicle(VehicleID vehicle_id
)
32 const Vehicle
*v
= ::Vehicle::GetIfValid(vehicle_id
);
33 return v
!= nullptr && (v
->owner
== ScriptObject::GetCompany() || ScriptObject::GetCompany() == OWNER_DEITY
) && (v
->IsPrimaryVehicle() || (v
->type
== VEH_TRAIN
&& ::Train::From(v
)->IsFreeWagon()));
36 /* static */ ScriptCompany::CompanyID
ScriptVehicle::GetOwner(VehicleID vehicle_id
)
38 if (!IsValidVehicle(vehicle_id
)) return ScriptCompany::COMPANY_INVALID
;
40 return static_cast<ScriptCompany::CompanyID
>((int)::Vehicle::Get(vehicle_id
)->owner
);
43 /* static */ int32
ScriptVehicle::GetNumWagons(VehicleID vehicle_id
)
45 if (!IsValidVehicle(vehicle_id
)) return -1;
49 const Train
*v
= ::Train::GetIfValid(vehicle_id
);
51 while ((v
= v
->GetNextUnit()) != nullptr) num
++;
57 /* static */ int ScriptVehicle::GetLength(VehicleID vehicle_id
)
59 if (!IsValidVehicle(vehicle_id
)) return -1;
61 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
62 return v
->IsGroundVehicle() ? v
->GetGroundVehicleCache()->cached_total_length
: -1;
65 /* static */ VehicleID
ScriptVehicle::_BuildVehicleInternal(TileIndex depot
, EngineID engine_id
, CargoID cargo
)
67 EnforcePrecondition(VEHICLE_INVALID
, ScriptObject::GetCompany() != OWNER_DEITY
);
68 EnforcePrecondition(VEHICLE_INVALID
, ScriptEngine::IsBuildable(engine_id
));
69 EnforcePrecondition(VEHICLE_INVALID
, cargo
== CT_INVALID
|| ScriptCargo::IsValidCargo(cargo
));
71 ::VehicleType type
= ::Engine::Get(engine_id
)->type
;
73 EnforcePreconditionCustomError(VEHICLE_INVALID
, !ScriptGameSettings::IsDisabledVehicleType((ScriptVehicle::VehicleType
)type
), ScriptVehicle::ERR_VEHICLE_BUILD_DISABLED
);
75 if (!ScriptObject::Command
<CMD_BUILD_VEHICLE
>::Do(&ScriptInstance::DoCommandReturnVehicleID
, depot
, engine_id
, true, cargo
, INVALID_CLIENT_ID
)) return VEHICLE_INVALID
;
77 /* In case of test-mode, we return VehicleID 0 */
81 /* static */ VehicleID
ScriptVehicle::BuildVehicle(TileIndex depot
, EngineID engine_id
)
83 return _BuildVehicleInternal(depot
, engine_id
, CT_INVALID
);
86 /* static */ VehicleID
ScriptVehicle::BuildVehicleWithRefit(TileIndex depot
, EngineID engine_id
, CargoID cargo
)
88 EnforcePrecondition(VEHICLE_INVALID
, ScriptCargo::IsValidCargo(cargo
));
89 return _BuildVehicleInternal(depot
, engine_id
, cargo
);
92 /* static */ int ScriptVehicle::GetBuildWithRefitCapacity(TileIndex depot
, EngineID engine_id
, CargoID cargo
)
94 if (!ScriptEngine::IsBuildable(engine_id
)) return -1;
95 if (!ScriptCargo::IsValidCargo(cargo
)) return -1;
97 CommandCost res
= ::Command
<CMD_BUILD_VEHICLE
>::Do(DC_QUERY_COST
, depot
, engine_id
, true, cargo
, INVALID_CLIENT_ID
);
98 return res
.Succeeded() ? _returned_refit_capacity
: -1;
101 /* static */ VehicleID
ScriptVehicle::CloneVehicle(TileIndex depot
, VehicleID vehicle_id
, bool share_orders
)
103 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
104 EnforcePrecondition(false, IsValidVehicle(vehicle_id
));
106 if (!ScriptObject::Command
<CMD_CLONE_VEHICLE
>::Do(&ScriptInstance::DoCommandReturnVehicleID
, depot
, vehicle_id
, share_orders
, {})) return VEHICLE_INVALID
;
108 /* In case of test-mode, we return VehicleID 0 */
112 /* static */ bool ScriptVehicle::_MoveWagonInternal(VehicleID source_vehicle_id
, int source_wagon
, bool move_attached_wagons
, int dest_vehicle_id
, int dest_wagon
)
114 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
115 EnforcePrecondition(false, IsValidVehicle(source_vehicle_id
) && source_wagon
< GetNumWagons(source_vehicle_id
));
116 EnforcePrecondition(false, dest_vehicle_id
== -1 || (IsValidVehicle(dest_vehicle_id
) && dest_wagon
< GetNumWagons(dest_vehicle_id
)));
117 EnforcePrecondition(false, ::Vehicle::Get(source_vehicle_id
)->type
== VEH_TRAIN
);
118 EnforcePrecondition(false, dest_vehicle_id
== -1 || ::Vehicle::Get(dest_vehicle_id
)->type
== VEH_TRAIN
);
120 const Train
*v
= ::Train::Get(source_vehicle_id
);
121 while (source_wagon
-- > 0) v
= v
->GetNextUnit();
122 const Train
*w
= nullptr;
123 if (dest_vehicle_id
!= -1) {
124 w
= ::Train::Get(dest_vehicle_id
);
125 while (dest_wagon
-- > 0) w
= w
->GetNextUnit();
128 return ScriptObject::Command
<CMD_MOVE_RAIL_VEHICLE
>::Do(0, v
->index
| (move_attached_wagons
? 1 : 0) << 20, w
== nullptr ? ::INVALID_VEHICLE
: w
->index
, {});
131 /* static */ bool ScriptVehicle::MoveWagon(VehicleID source_vehicle_id
, int source_wagon
, int dest_vehicle_id
, int dest_wagon
)
133 return _MoveWagonInternal(source_vehicle_id
, source_wagon
, false, dest_vehicle_id
, dest_wagon
);
136 /* static */ bool ScriptVehicle::MoveWagonChain(VehicleID source_vehicle_id
, int source_wagon
, int dest_vehicle_id
, int dest_wagon
)
138 return _MoveWagonInternal(source_vehicle_id
, source_wagon
, true, dest_vehicle_id
, dest_wagon
);
141 /* static */ int ScriptVehicle::GetRefitCapacity(VehicleID vehicle_id
, CargoID cargo
)
143 if (!IsValidVehicle(vehicle_id
)) return -1;
144 if (!ScriptCargo::IsValidCargo(cargo
)) return -1;
146 CommandCost res
= ::Command
<CMD_REFIT_VEHICLE
>::Do(DC_QUERY_COST
, 0, vehicle_id
, cargo
, {});
147 return res
.Succeeded() ? _returned_refit_capacity
: -1;
150 /* static */ bool ScriptVehicle::RefitVehicle(VehicleID vehicle_id
, CargoID cargo
)
152 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
153 EnforcePrecondition(false, IsValidVehicle(vehicle_id
) && ScriptCargo::IsValidCargo(cargo
));
155 return ScriptObject::Command
<CMD_REFIT_VEHICLE
>::Do(0, vehicle_id
, cargo
, {});
159 /* static */ bool ScriptVehicle::SellVehicle(VehicleID vehicle_id
)
161 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
162 EnforcePrecondition(false, IsValidVehicle(vehicle_id
));
164 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
165 return ScriptObject::Command
<CMD_SELL_VEHICLE
>::Do(0, vehicle_id
, v
->type
== VEH_TRAIN
, false, INVALID_CLIENT_ID
);
168 /* static */ bool ScriptVehicle::_SellWagonInternal(VehicleID vehicle_id
, int wagon
, bool sell_attached_wagons
)
170 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
171 EnforcePrecondition(false, IsValidVehicle(vehicle_id
) && wagon
< GetNumWagons(vehicle_id
));
172 EnforcePrecondition(false, ::Vehicle::Get(vehicle_id
)->type
== VEH_TRAIN
);
174 const Train
*v
= ::Train::Get(vehicle_id
);
175 while (wagon
-- > 0) v
= v
->GetNextUnit();
177 return ScriptObject::Command
<CMD_SELL_VEHICLE
>::Do(0, v
->index
, sell_attached_wagons
, false, INVALID_CLIENT_ID
);
180 /* static */ bool ScriptVehicle::SellWagon(VehicleID vehicle_id
, int wagon
)
182 return _SellWagonInternal(vehicle_id
, wagon
, false);
185 /* static */ bool ScriptVehicle::SellWagonChain(VehicleID vehicle_id
, int wagon
)
187 return _SellWagonInternal(vehicle_id
, wagon
, true);
190 /* static */ bool ScriptVehicle::SendVehicleToDepot(VehicleID vehicle_id
)
192 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
193 EnforcePrecondition(false, IsValidVehicle(vehicle_id
));
195 return ScriptObject::Command
<CMD_SEND_VEHICLE_TO_DEPOT
>::Do(0, vehicle_id
, 0, {});
198 /* static */ bool ScriptVehicle::SendVehicleToDepotForServicing(VehicleID vehicle_id
)
200 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
201 EnforcePrecondition(false, IsValidVehicle(vehicle_id
));
203 return ScriptObject::Command
<CMD_SEND_VEHICLE_TO_DEPOT
>::Do(0, vehicle_id
| DEPOT_SERVICE
, 0, {});
206 /* static */ bool ScriptVehicle::IsInDepot(VehicleID vehicle_id
)
208 if (!IsValidVehicle(vehicle_id
)) return false;
209 return ::Vehicle::Get(vehicle_id
)->IsChainInDepot();
212 /* static */ bool ScriptVehicle::IsStoppedInDepot(VehicleID vehicle_id
)
214 if (!IsValidVehicle(vehicle_id
)) return false;
215 return ::Vehicle::Get(vehicle_id
)->IsStoppedInDepot();
218 /* static */ bool ScriptVehicle::StartStopVehicle(VehicleID vehicle_id
)
220 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
221 EnforcePrecondition(false, IsValidVehicle(vehicle_id
));
223 return ScriptObject::Command
<CMD_START_STOP_VEHICLE
>::Do(0, vehicle_id
, 0, {});
226 /* static */ bool ScriptVehicle::ReverseVehicle(VehicleID vehicle_id
)
228 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
229 EnforcePrecondition(false, IsValidVehicle(vehicle_id
));
230 EnforcePrecondition(false, ::Vehicle::Get(vehicle_id
)->type
== VEH_ROAD
|| ::Vehicle::Get(vehicle_id
)->type
== VEH_TRAIN
);
232 switch (::Vehicle::Get(vehicle_id
)->type
) {
233 case VEH_ROAD
: return ScriptObject::Command
<CMD_TURN_ROADVEH
>::Do(0, vehicle_id
, 0, {});
234 case VEH_TRAIN
: return ScriptObject::Command
<CMD_REVERSE_TRAIN_DIRECTION
>::Do(0, vehicle_id
, 0, {});
235 default: NOT_REACHED();
239 /* static */ bool ScriptVehicle::SetName(VehicleID vehicle_id
, Text
*name
)
241 CCountedPtr
<Text
> counter(name
);
243 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
244 EnforcePrecondition(false, IsValidVehicle(vehicle_id
));
245 EnforcePrecondition(false, name
!= nullptr);
246 const char *text
= name
->GetDecodedText();
247 EnforcePreconditionEncodedText(false, text
);
248 EnforcePreconditionCustomError(false, ::Utf8StringLength(text
) < MAX_LENGTH_VEHICLE_NAME_CHARS
, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG
);
250 return ScriptObject::Command
<CMD_RENAME_VEHICLE
>::Do(0, vehicle_id
, 0, text
);
253 /* static */ TileIndex
ScriptVehicle::GetLocation(VehicleID vehicle_id
)
255 if (!IsValidVehicle(vehicle_id
)) return INVALID_TILE
;
257 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
258 if (v
->type
== VEH_AIRCRAFT
) {
259 uint x
= Clamp(v
->x_pos
/ TILE_SIZE
, 0, ::MapSizeX() - 2);
260 uint y
= Clamp(v
->y_pos
/ TILE_SIZE
, 0, ::MapSizeY() - 2);
261 return ::TileXY(x
, y
);
267 /* static */ EngineID
ScriptVehicle::GetEngineType(VehicleID vehicle_id
)
269 if (!IsValidVehicle(vehicle_id
)) return INVALID_ENGINE
;
271 return ::Vehicle::Get(vehicle_id
)->engine_type
;
274 /* static */ EngineID
ScriptVehicle::GetWagonEngineType(VehicleID vehicle_id
, int wagon
)
276 if (!IsValidVehicle(vehicle_id
)) return INVALID_ENGINE
;
277 if (wagon
>= GetNumWagons(vehicle_id
)) return INVALID_ENGINE
;
279 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
280 if (v
->type
== VEH_TRAIN
) {
281 while (wagon
-- > 0) v
= ::Train::From(v
)->GetNextUnit();
283 return v
->engine_type
;
286 /* static */ int32
ScriptVehicle::GetUnitNumber(VehicleID vehicle_id
)
288 if (!IsValidVehicle(vehicle_id
)) return -1;
290 return ::Vehicle::Get(vehicle_id
)->unitnumber
;
293 /* static */ char *ScriptVehicle::GetName(VehicleID vehicle_id
)
295 if (!IsValidVehicle(vehicle_id
)) return nullptr;
297 ::SetDParam(0, vehicle_id
);
298 return GetString(STR_VEHICLE_NAME
);
301 /* static */ int32
ScriptVehicle::GetAge(VehicleID vehicle_id
)
303 if (!IsValidVehicle(vehicle_id
)) return -1;
305 return ::Vehicle::Get(vehicle_id
)->age
;
308 /* static */ int32
ScriptVehicle::GetWagonAge(VehicleID vehicle_id
, int wagon
)
310 if (!IsValidVehicle(vehicle_id
)) return -1;
311 if (wagon
>= GetNumWagons(vehicle_id
)) return -1;
313 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
314 if (v
->type
== VEH_TRAIN
) {
315 while (wagon
-- > 0) v
= ::Train::From(v
)->GetNextUnit();
320 /* static */ int32
ScriptVehicle::GetMaxAge(VehicleID vehicle_id
)
322 if (!IsValidVehicle(vehicle_id
)) return -1;
324 return ::Vehicle::Get(vehicle_id
)->max_age
;
327 /* static */ int32
ScriptVehicle::GetAgeLeft(VehicleID vehicle_id
)
329 if (!IsValidVehicle(vehicle_id
)) return -1;
331 return ::Vehicle::Get(vehicle_id
)->max_age
- ::Vehicle::Get(vehicle_id
)->age
;
334 /* static */ int32
ScriptVehicle::GetCurrentSpeed(VehicleID vehicle_id
)
336 if (!IsValidVehicle(vehicle_id
)) return -1;
338 const ::Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
339 return (v
->vehstatus
& (::VS_STOPPED
| ::VS_CRASHED
)) == 0 ? v
->GetDisplaySpeed() : 0; // km-ish/h
342 /* static */ ScriptVehicle::VehicleState
ScriptVehicle::GetState(VehicleID vehicle_id
)
344 if (!IsValidVehicle(vehicle_id
)) return ScriptVehicle::VS_INVALID
;
346 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
347 byte vehstatus
= v
->vehstatus
;
349 if (vehstatus
& ::VS_CRASHED
) return ScriptVehicle::VS_CRASHED
;
350 if (v
->breakdown_ctr
!= 0) return ScriptVehicle::VS_BROKEN
;
351 if (v
->IsStoppedInDepot()) return ScriptVehicle::VS_IN_DEPOT
;
352 if (vehstatus
& ::VS_STOPPED
) return ScriptVehicle::VS_STOPPED
;
353 if (v
->current_order
.IsType(OT_LOADING
)) return ScriptVehicle::VS_AT_STATION
;
354 return ScriptVehicle::VS_RUNNING
;
357 /* static */ Money
ScriptVehicle::GetRunningCost(VehicleID vehicle_id
)
359 if (!IsValidVehicle(vehicle_id
)) return -1;
361 return ::Vehicle::Get(vehicle_id
)->GetRunningCost() >> 8;
364 /* static */ Money
ScriptVehicle::GetProfitThisYear(VehicleID vehicle_id
)
366 if (!IsValidVehicle(vehicle_id
)) return -1;
368 return ::Vehicle::Get(vehicle_id
)->GetDisplayProfitThisYear();
371 /* static */ Money
ScriptVehicle::GetProfitLastYear(VehicleID vehicle_id
)
373 if (!IsValidVehicle(vehicle_id
)) return -1;
375 return ::Vehicle::Get(vehicle_id
)->GetDisplayProfitLastYear();
378 /* static */ Money
ScriptVehicle::GetCurrentValue(VehicleID vehicle_id
)
380 if (!IsValidVehicle(vehicle_id
)) return -1;
382 return ::Vehicle::Get(vehicle_id
)->value
;
385 /* static */ ScriptVehicle::VehicleType
ScriptVehicle::GetVehicleType(VehicleID vehicle_id
)
387 if (!IsValidVehicle(vehicle_id
)) return VT_INVALID
;
389 switch (::Vehicle::Get(vehicle_id
)->type
) {
390 case VEH_ROAD
: return VT_ROAD
;
391 case VEH_TRAIN
: return VT_RAIL
;
392 case VEH_SHIP
: return VT_WATER
;
393 case VEH_AIRCRAFT
: return VT_AIR
;
394 default: return VT_INVALID
;
398 /* static */ ScriptRoad::RoadType
ScriptVehicle::GetRoadType(VehicleID vehicle_id
)
400 if (!IsValidVehicle(vehicle_id
)) return ScriptRoad::ROADTYPE_INVALID
;
401 if (GetVehicleType(vehicle_id
) != VT_ROAD
) return ScriptRoad::ROADTYPE_INVALID
;
403 return (ScriptRoad::RoadType
)(int)(::RoadVehicle::Get(vehicle_id
))->roadtype
;
406 /* static */ int32
ScriptVehicle::GetCapacity(VehicleID vehicle_id
, CargoID cargo
)
408 if (!IsValidVehicle(vehicle_id
)) return -1;
409 if (!ScriptCargo::IsValidCargo(cargo
)) return -1;
412 for (const Vehicle
*v
= ::Vehicle::Get(vehicle_id
); v
!= nullptr; v
= v
->Next()) {
413 if (v
->cargo_type
== cargo
) amount
+= v
->cargo_cap
;
419 /* static */ int32
ScriptVehicle::GetCargoLoad(VehicleID vehicle_id
, CargoID cargo
)
421 if (!IsValidVehicle(vehicle_id
)) return -1;
422 if (!ScriptCargo::IsValidCargo(cargo
)) return -1;
425 for (const Vehicle
*v
= ::Vehicle::Get(vehicle_id
); v
!= nullptr; v
= v
->Next()) {
426 if (v
->cargo_type
== cargo
) amount
+= v
->cargo
.StoredCount();
432 /* static */ GroupID
ScriptVehicle::GetGroupID(VehicleID vehicle_id
)
434 if (!IsValidVehicle(vehicle_id
)) return ScriptGroup::GROUP_INVALID
;
436 return ::Vehicle::Get(vehicle_id
)->group_id
;
439 /* static */ bool ScriptVehicle::IsArticulated(VehicleID vehicle_id
)
441 if (!IsValidVehicle(vehicle_id
)) return false;
442 if (GetVehicleType(vehicle_id
) != VT_ROAD
&& GetVehicleType(vehicle_id
) != VT_RAIL
) return false;
444 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
446 case VEH_ROAD
: return ::RoadVehicle::From(v
)->HasArticulatedPart();
447 case VEH_TRAIN
: return ::Train::From(v
)->HasArticulatedPart();
448 default: NOT_REACHED();
452 /* static */ bool ScriptVehicle::HasSharedOrders(VehicleID vehicle_id
)
454 if (!IsValidVehicle(vehicle_id
)) return false;
456 Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
457 return v
->orders
.list
!= nullptr && v
->orders
.list
->GetNumVehicles() > 1;
460 /* static */ int ScriptVehicle::GetReliability(VehicleID vehicle_id
)
462 if (!IsValidVehicle(vehicle_id
)) return -1;
464 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
465 return ::ToPercent16(v
->reliability
);
468 /* static */ uint
ScriptVehicle::GetMaximumOrderDistance(VehicleID vehicle_id
)
470 if (!IsValidVehicle(vehicle_id
)) return 0;
472 const ::Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
475 return ::Aircraft::From(v
)->acache
.cached_max_range_sqr
;