4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
10 /** @file script_vehicle.cpp Implementation of ScriptVehicle. */
12 #include "../../stdafx.h"
13 #include "script_engine.hpp"
14 #include "script_cargo.hpp"
15 #include "script_gamesettings.hpp"
16 #include "script_group.hpp"
17 #include "../script_instance.hpp"
18 #include "../../string_func.h"
19 #include "../../strings_func.h"
20 #include "../../command_func.h"
21 #include "../../roadveh.h"
22 #include "../../train.h"
23 #include "../../vehicle_func.h"
24 #include "../../aircraft.h"
25 #include "table/strings.h"
27 #include "../../safeguards.h"
29 /* static */ bool ScriptVehicle::IsValidVehicle(VehicleID vehicle_id
)
31 const Vehicle
*v
= ::Vehicle::GetIfValid(vehicle_id
);
32 return v
!= nullptr && (v
->owner
== ScriptObject::GetCompany() || ScriptObject::GetCompany() == OWNER_DEITY
) && (v
->IsPrimaryVehicle() || (v
->type
== VEH_TRAIN
&& ::Train::From(v
)->IsFreeWagon()));
35 /* static */ ScriptCompany::CompanyID
ScriptVehicle::GetOwner(VehicleID vehicle_id
)
37 if (!IsValidVehicle(vehicle_id
)) return ScriptCompany::COMPANY_INVALID
;
39 return static_cast<ScriptCompany::CompanyID
>((int)::Vehicle::Get(vehicle_id
)->owner
);
42 /* static */ int32
ScriptVehicle::GetNumWagons(VehicleID vehicle_id
)
44 if (!IsValidVehicle(vehicle_id
)) return -1;
48 const Train
*v
= ::Train::GetIfValid(vehicle_id
);
50 while ((v
= v
->GetNextUnit()) != nullptr) num
++;
56 /* static */ int ScriptVehicle::GetLength(VehicleID vehicle_id
)
58 if (!IsValidVehicle(vehicle_id
)) return -1;
60 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
61 return v
->IsGroundVehicle() ? v
->GetGroundVehicleCache()->cached_total_length
: -1;
64 /* static */ VehicleID
ScriptVehicle::_BuildVehicleInternal(TileIndex depot
, EngineID engine_id
, CargoID cargo
)
66 EnforcePrecondition(VEHICLE_INVALID
, ScriptObject::GetCompany() != OWNER_DEITY
);
67 EnforcePrecondition(VEHICLE_INVALID
, ScriptEngine::IsBuildable(engine_id
));
68 EnforcePrecondition(VEHICLE_INVALID
, cargo
== CT_INVALID
|| ScriptCargo::IsValidCargo(cargo
));
70 ::VehicleType type
= ::Engine::Get(engine_id
)->type
;
72 EnforcePreconditionCustomError(VEHICLE_INVALID
, !ScriptGameSettings::IsDisabledVehicleType((ScriptVehicle::VehicleType
)type
), ScriptVehicle::ERR_VEHICLE_BUILD_DISABLED
);
74 if (!ScriptObject::DoCommand(depot
, engine_id
| (cargo
<< 24), 0, ::GetCmdBuildVeh(type
), nullptr, &ScriptInstance::DoCommandReturnVehicleID
)) return VEHICLE_INVALID
;
76 /* In case of test-mode, we return VehicleID 0 */
80 /* static */ VehicleID
ScriptVehicle::BuildVehicle(TileIndex depot
, EngineID engine_id
)
82 return _BuildVehicleInternal(depot
, engine_id
, CT_INVALID
);
85 /* static */ VehicleID
ScriptVehicle::BuildVehicleWithRefit(TileIndex depot
, EngineID engine_id
, CargoID cargo
)
87 EnforcePrecondition(VEHICLE_INVALID
, ScriptCargo::IsValidCargo(cargo
));
88 return _BuildVehicleInternal(depot
, engine_id
, cargo
);
91 /* static */ int ScriptVehicle::GetBuildWithRefitCapacity(TileIndex depot
, EngineID engine_id
, CargoID cargo
)
93 if (!ScriptEngine::IsBuildable(engine_id
)) return -1;
94 if (!ScriptCargo::IsValidCargo(cargo
)) return -1;
96 ::VehicleType type
= ::Engine::Get(engine_id
)->type
;
98 CommandCost res
= ::DoCommand(depot
, engine_id
| (cargo
<< 24), 0, DC_QUERY_COST
, ::GetCmdBuildVeh(type
));
99 return res
.Succeeded() ? _returned_refit_capacity
: -1;
102 /* static */ VehicleID
ScriptVehicle::CloneVehicle(TileIndex depot
, VehicleID vehicle_id
, bool share_orders
)
104 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
105 EnforcePrecondition(false, IsValidVehicle(vehicle_id
));
107 if (!ScriptObject::DoCommand(depot
, vehicle_id
, share_orders
, CMD_CLONE_VEHICLE
, nullptr, &ScriptInstance::DoCommandReturnVehicleID
)) return VEHICLE_INVALID
;
109 /* In case of test-mode, we return VehicleID 0 */
113 /* static */ bool ScriptVehicle::_MoveWagonInternal(VehicleID source_vehicle_id
, int source_wagon
, bool move_attached_wagons
, int dest_vehicle_id
, int dest_wagon
)
115 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
116 EnforcePrecondition(false, IsValidVehicle(source_vehicle_id
) && source_wagon
< GetNumWagons(source_vehicle_id
));
117 EnforcePrecondition(false, dest_vehicle_id
== -1 || (IsValidVehicle(dest_vehicle_id
) && dest_wagon
< GetNumWagons(dest_vehicle_id
)));
118 EnforcePrecondition(false, ::Vehicle::Get(source_vehicle_id
)->type
== VEH_TRAIN
);
119 EnforcePrecondition(false, dest_vehicle_id
== -1 || ::Vehicle::Get(dest_vehicle_id
)->type
== VEH_TRAIN
);
121 const Train
*v
= ::Train::Get(source_vehicle_id
);
122 while (source_wagon
-- > 0) v
= v
->GetNextUnit();
123 const Train
*w
= nullptr;
124 if (dest_vehicle_id
!= -1) {
125 w
= ::Train::Get(dest_vehicle_id
);
126 while (dest_wagon
-- > 0) w
= w
->GetNextUnit();
129 return ScriptObject::DoCommand(0, v
->index
| (move_attached_wagons
? 1 : 0) << 20, w
== nullptr ? ::INVALID_VEHICLE
: w
->index
, CMD_MOVE_RAIL_VEHICLE
);
132 /* static */ bool ScriptVehicle::MoveWagon(VehicleID source_vehicle_id
, int source_wagon
, int dest_vehicle_id
, int dest_wagon
)
134 return _MoveWagonInternal(source_vehicle_id
, source_wagon
, false, dest_vehicle_id
, dest_wagon
);
137 /* static */ bool ScriptVehicle::MoveWagonChain(VehicleID source_vehicle_id
, int source_wagon
, int dest_vehicle_id
, int dest_wagon
)
139 return _MoveWagonInternal(source_vehicle_id
, source_wagon
, true, dest_vehicle_id
, dest_wagon
);
142 /* static */ int ScriptVehicle::GetRefitCapacity(VehicleID vehicle_id
, CargoID cargo
)
144 if (!IsValidVehicle(vehicle_id
)) return -1;
145 if (!ScriptCargo::IsValidCargo(cargo
)) return -1;
147 CommandCost res
= ::DoCommand(0, vehicle_id
, cargo
, DC_QUERY_COST
, GetCmdRefitVeh(::Vehicle::Get(vehicle_id
)));
148 return res
.Succeeded() ? _returned_refit_capacity
: -1;
151 /* static */ bool ScriptVehicle::RefitVehicle(VehicleID vehicle_id
, CargoID cargo
)
153 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
154 EnforcePrecondition(false, IsValidVehicle(vehicle_id
) && ScriptCargo::IsValidCargo(cargo
));
156 return ScriptObject::DoCommand(0, vehicle_id
, cargo
, GetCmdRefitVeh(::Vehicle::Get(vehicle_id
)));
160 /* static */ bool ScriptVehicle::SellVehicle(VehicleID vehicle_id
)
162 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
163 EnforcePrecondition(false, IsValidVehicle(vehicle_id
));
165 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
166 return ScriptObject::DoCommand(0, vehicle_id
| (v
->type
== VEH_TRAIN
? 1 : 0) << 20, 0, GetCmdSellVeh(v
));
169 /* static */ bool ScriptVehicle::_SellWagonInternal(VehicleID vehicle_id
, int wagon
, bool sell_attached_wagons
)
171 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
172 EnforcePrecondition(false, IsValidVehicle(vehicle_id
) && wagon
< GetNumWagons(vehicle_id
));
173 EnforcePrecondition(false, ::Vehicle::Get(vehicle_id
)->type
== VEH_TRAIN
);
175 const Train
*v
= ::Train::Get(vehicle_id
);
176 while (wagon
-- > 0) v
= v
->GetNextUnit();
178 return ScriptObject::DoCommand(0, v
->index
| (sell_attached_wagons
? 1 : 0) << 20, 0, CMD_SELL_VEHICLE
);
181 /* static */ bool ScriptVehicle::SellWagon(VehicleID vehicle_id
, int wagon
)
183 return _SellWagonInternal(vehicle_id
, wagon
, false);
186 /* static */ bool ScriptVehicle::SellWagonChain(VehicleID vehicle_id
, int wagon
)
188 return _SellWagonInternal(vehicle_id
, wagon
, true);
191 /* static */ bool ScriptVehicle::SendVehicleToDepot(VehicleID vehicle_id
)
193 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
194 EnforcePrecondition(false, IsValidVehicle(vehicle_id
));
196 return ScriptObject::DoCommand(0, vehicle_id
, 0, GetCmdSendToDepot(::Vehicle::Get(vehicle_id
)));
199 /* static */ bool ScriptVehicle::SendVehicleToDepotForServicing(VehicleID vehicle_id
)
201 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
202 EnforcePrecondition(false, IsValidVehicle(vehicle_id
));
204 return ScriptObject::DoCommand(0, vehicle_id
| DEPOT_SERVICE
, 0, GetCmdSendToDepot(::Vehicle::Get(vehicle_id
)));
207 /* static */ bool ScriptVehicle::IsInDepot(VehicleID vehicle_id
)
209 if (!IsValidVehicle(vehicle_id
)) return false;
210 return ::Vehicle::Get(vehicle_id
)->IsChainInDepot();
213 /* static */ bool ScriptVehicle::IsStoppedInDepot(VehicleID vehicle_id
)
215 if (!IsValidVehicle(vehicle_id
)) return false;
216 return ::Vehicle::Get(vehicle_id
)->IsStoppedInDepot();
219 /* static */ bool ScriptVehicle::StartStopVehicle(VehicleID vehicle_id
)
221 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
222 EnforcePrecondition(false, IsValidVehicle(vehicle_id
));
224 return ScriptObject::DoCommand(0, vehicle_id
, 0, CMD_START_STOP_VEHICLE
);
227 /* static */ bool ScriptVehicle::ReverseVehicle(VehicleID vehicle_id
)
229 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
230 EnforcePrecondition(false, IsValidVehicle(vehicle_id
));
231 EnforcePrecondition(false, ::Vehicle::Get(vehicle_id
)->type
== VEH_ROAD
|| ::Vehicle::Get(vehicle_id
)->type
== VEH_TRAIN
);
233 switch (::Vehicle::Get(vehicle_id
)->type
) {
234 case VEH_ROAD
: return ScriptObject::DoCommand(0, vehicle_id
, 0, CMD_TURN_ROADVEH
);
235 case VEH_TRAIN
: return ScriptObject::DoCommand(0, vehicle_id
, 0, CMD_REVERSE_TRAIN_DIRECTION
);
236 default: NOT_REACHED();
240 /* static */ bool ScriptVehicle::SetName(VehicleID vehicle_id
, Text
*name
)
242 CCountedPtr
<Text
> counter(name
);
244 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY
);
245 EnforcePrecondition(false, IsValidVehicle(vehicle_id
));
246 EnforcePrecondition(false, name
!= nullptr);
247 const char *text
= name
->GetDecodedText();
248 EnforcePreconditionEncodedText(false, text
);
249 EnforcePreconditionCustomError(false, ::Utf8StringLength(text
) < MAX_LENGTH_VEHICLE_NAME_CHARS
, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG
);
251 return ScriptObject::DoCommand(0, vehicle_id
, 0, CMD_RENAME_VEHICLE
, text
);
254 /* static */ TileIndex
ScriptVehicle::GetLocation(VehicleID vehicle_id
)
256 if (!IsValidVehicle(vehicle_id
)) return INVALID_TILE
;
258 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
259 if (v
->type
== VEH_AIRCRAFT
) {
260 uint x
= Clamp(v
->x_pos
/ TILE_SIZE
, 0, ::MapSizeX() - 2);
261 uint y
= Clamp(v
->y_pos
/ TILE_SIZE
, 0, ::MapSizeY() - 2);
262 return ::TileXY(x
, y
);
268 /* static */ EngineID
ScriptVehicle::GetEngineType(VehicleID vehicle_id
)
270 if (!IsValidVehicle(vehicle_id
)) return INVALID_ENGINE
;
272 return ::Vehicle::Get(vehicle_id
)->engine_type
;
275 /* static */ EngineID
ScriptVehicle::GetWagonEngineType(VehicleID vehicle_id
, int wagon
)
277 if (!IsValidVehicle(vehicle_id
)) return INVALID_ENGINE
;
278 if (wagon
>= GetNumWagons(vehicle_id
)) return INVALID_ENGINE
;
280 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
281 if (v
->type
== VEH_TRAIN
) {
282 while (wagon
-- > 0) v
= ::Train::From(v
)->GetNextUnit();
284 return v
->engine_type
;
287 /* static */ int32
ScriptVehicle::GetUnitNumber(VehicleID vehicle_id
)
289 if (!IsValidVehicle(vehicle_id
)) return -1;
291 return ::Vehicle::Get(vehicle_id
)->unitnumber
;
294 /* static */ char *ScriptVehicle::GetName(VehicleID vehicle_id
)
296 if (!IsValidVehicle(vehicle_id
)) return nullptr;
298 ::SetDParam(0, vehicle_id
);
299 return GetString(STR_VEHICLE_NAME
);
302 /* static */ int32
ScriptVehicle::GetAge(VehicleID vehicle_id
)
304 if (!IsValidVehicle(vehicle_id
)) return -1;
306 return ::Vehicle::Get(vehicle_id
)->age
;
309 /* static */ int32
ScriptVehicle::GetWagonAge(VehicleID vehicle_id
, int wagon
)
311 if (!IsValidVehicle(vehicle_id
)) return -1;
312 if (wagon
>= GetNumWagons(vehicle_id
)) return -1;
314 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
315 if (v
->type
== VEH_TRAIN
) {
316 while (wagon
-- > 0) v
= ::Train::From(v
)->GetNextUnit();
321 /* static */ int32
ScriptVehicle::GetMaxAge(VehicleID vehicle_id
)
323 if (!IsValidVehicle(vehicle_id
)) return -1;
325 return ::Vehicle::Get(vehicle_id
)->max_age
;
328 /* static */ int32
ScriptVehicle::GetAgeLeft(VehicleID vehicle_id
)
330 if (!IsValidVehicle(vehicle_id
)) return -1;
332 return ::Vehicle::Get(vehicle_id
)->max_age
- ::Vehicle::Get(vehicle_id
)->age
;
335 /* static */ int32
ScriptVehicle::GetCurrentSpeed(VehicleID vehicle_id
)
337 if (!IsValidVehicle(vehicle_id
)) return -1;
339 const ::Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
340 return (v
->vehstatus
& (::VS_STOPPED
| ::VS_CRASHED
)) == 0 ? v
->GetDisplaySpeed() : 0; // km-ish/h
343 /* static */ ScriptVehicle::VehicleState
ScriptVehicle::GetState(VehicleID vehicle_id
)
345 if (!IsValidVehicle(vehicle_id
)) return ScriptVehicle::VS_INVALID
;
347 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
348 byte vehstatus
= v
->vehstatus
;
350 if (vehstatus
& ::VS_CRASHED
) return ScriptVehicle::VS_CRASHED
;
351 if (v
->breakdown_ctr
!= 0) return ScriptVehicle::VS_BROKEN
;
352 if (v
->IsStoppedInDepot()) return ScriptVehicle::VS_IN_DEPOT
;
353 if (vehstatus
& ::VS_STOPPED
) return ScriptVehicle::VS_STOPPED
;
354 if (v
->current_order
.IsType(OT_LOADING
)) return ScriptVehicle::VS_AT_STATION
;
355 return ScriptVehicle::VS_RUNNING
;
358 /* static */ Money
ScriptVehicle::GetRunningCost(VehicleID vehicle_id
)
360 if (!IsValidVehicle(vehicle_id
)) return -1;
362 return ::Vehicle::Get(vehicle_id
)->GetRunningCost() >> 8;
365 /* static */ Money
ScriptVehicle::GetProfitThisYear(VehicleID vehicle_id
)
367 if (!IsValidVehicle(vehicle_id
)) return -1;
369 return ::Vehicle::Get(vehicle_id
)->GetDisplayProfitThisYear();
372 /* static */ Money
ScriptVehicle::GetProfitLastYear(VehicleID vehicle_id
)
374 if (!IsValidVehicle(vehicle_id
)) return -1;
376 return ::Vehicle::Get(vehicle_id
)->GetDisplayProfitLastYear();
379 /* static */ Money
ScriptVehicle::GetCurrentValue(VehicleID vehicle_id
)
381 if (!IsValidVehicle(vehicle_id
)) return -1;
383 return ::Vehicle::Get(vehicle_id
)->value
;
386 /* static */ ScriptVehicle::VehicleType
ScriptVehicle::GetVehicleType(VehicleID vehicle_id
)
388 if (!IsValidVehicle(vehicle_id
)) return VT_INVALID
;
390 switch (::Vehicle::Get(vehicle_id
)->type
) {
391 case VEH_ROAD
: return VT_ROAD
;
392 case VEH_TRAIN
: return VT_RAIL
;
393 case VEH_SHIP
: return VT_WATER
;
394 case VEH_AIRCRAFT
: return VT_AIR
;
395 default: return VT_INVALID
;
399 /* static */ ScriptRoad::RoadType
ScriptVehicle::GetRoadType(VehicleID vehicle_id
)
401 if (!IsValidVehicle(vehicle_id
)) return ScriptRoad::ROADTYPE_INVALID
;
402 if (GetVehicleType(vehicle_id
) != VT_ROAD
) return ScriptRoad::ROADTYPE_INVALID
;
404 return (ScriptRoad::RoadType
)(::RoadVehicle::Get(vehicle_id
))->roadtype
;
407 /* static */ int32
ScriptVehicle::GetCapacity(VehicleID vehicle_id
, CargoID cargo
)
409 if (!IsValidVehicle(vehicle_id
)) return -1;
410 if (!ScriptCargo::IsValidCargo(cargo
)) return -1;
413 for (const Vehicle
*v
= ::Vehicle::Get(vehicle_id
); v
!= nullptr; v
= v
->Next()) {
414 if (v
->cargo_type
== cargo
) amount
+= v
->cargo_cap
;
420 /* static */ int32
ScriptVehicle::GetCargoLoad(VehicleID vehicle_id
, CargoID cargo
)
422 if (!IsValidVehicle(vehicle_id
)) return -1;
423 if (!ScriptCargo::IsValidCargo(cargo
)) return -1;
426 for (const Vehicle
*v
= ::Vehicle::Get(vehicle_id
); v
!= nullptr; v
= v
->Next()) {
427 if (v
->cargo_type
== cargo
) amount
+= v
->cargo
.StoredCount();
433 /* static */ GroupID
ScriptVehicle::GetGroupID(VehicleID vehicle_id
)
435 if (!IsValidVehicle(vehicle_id
)) return ScriptGroup::GROUP_INVALID
;
437 return ::Vehicle::Get(vehicle_id
)->group_id
;
440 /* static */ bool ScriptVehicle::IsArticulated(VehicleID vehicle_id
)
442 if (!IsValidVehicle(vehicle_id
)) return false;
443 if (GetVehicleType(vehicle_id
) != VT_ROAD
&& GetVehicleType(vehicle_id
) != VT_RAIL
) return false;
445 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
447 case VEH_ROAD
: return ::RoadVehicle::From(v
)->HasArticulatedPart();
448 case VEH_TRAIN
: return ::Train::From(v
)->HasArticulatedPart();
449 default: NOT_REACHED();
453 /* static */ bool ScriptVehicle::HasSharedOrders(VehicleID vehicle_id
)
455 if (!IsValidVehicle(vehicle_id
)) return false;
457 Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
458 return v
->orders
.list
!= nullptr && v
->orders
.list
->GetNumVehicles() > 1;
461 /* static */ int ScriptVehicle::GetReliability(VehicleID vehicle_id
)
463 if (!IsValidVehicle(vehicle_id
)) return -1;
465 const Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
466 return ::ToPercent16(v
->reliability
);
469 /* static */ uint
ScriptVehicle::GetMaximumOrderDistance(VehicleID vehicle_id
)
471 if (!IsValidVehicle(vehicle_id
)) return 0;
473 const ::Vehicle
*v
= ::Vehicle::Get(vehicle_id
);
476 return ::Aircraft::From(v
)->acache
.cached_max_range_sqr
;