Vehicle create (#460)

* Start vehicle_create

Further work on CreateVehicle

Continue create vehicle length check

Further work

* Start actual create body function

Further work

Complete the body creation function

* Fix some small mistakes

Make minor corrections

* Further work

Further

Further works

* Implement create head

* Implement the create vehicle 1, 2, tail functions

Further implementation

* Make compilable and testable

Add nullptr protection to keep ci's happy

clang-format

* Use templates to simplify the creation functions

Change assert to keep ci's happy

* Implement remaining cleanup functions

Fix formatting

* Move member functions to vehicle.cpp file

* Implement initial review comments

* Make suggested changes

* Add further constants. Fix crash due to initial value

* Make review changes and add game command knowledge
This commit is contained in:
Duncan 2020-07-07 20:31:20 +01:00 committed by GitHub
parent 433c35344b
commit 5e2c51539d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1601 additions and 89 deletions

View File

@ -7,6 +7,7 @@
#include "objects/road_object.h"
#include "objects/track_object.h"
#include "stationmgr.h"
#include "things/vehicle.h"
#include "ui/WindowManager.h"
#include <cassert>
@ -42,6 +43,17 @@ namespace openloco::game_commands
registers backup = regs;
auto ebx = do_command(regs.esi, backup);
regs = backup;
regs.ebx = ebx;
return 0;
});
register_hook(
0x004AE5E4,
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
registers backup = regs;
auto ebx = things::vehicle::create(regs.bl, regs.dx, regs.di);
regs = backup;
regs.ebx = ebx;
return 0;
@ -292,4 +304,23 @@ namespace openloco::game_commands
windows::error::openWithCompetitor(gGameCommandErrorTitle, string_ids::error_reason_stringid_belongs_to, _errorCompanyId);
return 0x80000000;
}
// 0x00431E6A
// al : company
// esi : tile
bool sub_431E6A(const company_id_t company, map::tile_element* const tile /*= nullptr*/)
{
if (company == company_id::neutral)
{
return true;
}
if (_updating_company_id == company || _updating_company_id == company_id::neutral)
{
return true;
}
gGameCommandErrorText = -2;
_errorCompanyId = company;
_9C68D0 = tile == nullptr ? reinterpret_cast<map::tile_element*>(-1) : tile;
return false;
}
}

View File

@ -2,6 +2,8 @@
#include "interop/interop.hpp"
#include "localisation/FormatArguments.hpp"
#include "localisation/string_ids.h"
#include "things/thingmgr.h"
#include "ui/WindowManager.h"
#include <algorithm>
#include <array>
#include <map>
@ -41,6 +43,31 @@ namespace openloco
call(0x00430762, regs);
}
// 0x00437ED0
void company::recalculateTransportCounts()
{
// Reset all counts to 0
for (auto& count : transportTypeCount)
{
count = 0;
}
auto companyId = id();
auto v = thingmgr::first<openloco::vehicle>();
while (v != nullptr)
{
auto next = v->next_vehicle();
if (v->owner == companyId)
{
transportTypeCount[static_cast<uint8_t>(v->vehicleType)]++;
}
v = next;
}
ui::WindowManager::invalidate(ui::WindowType::company, companyId);
}
// Converts performance index to rating
// 0x437D60
constexpr CorporateRating performanceToRating(int16_t performanceIndex)

View File

@ -9,7 +9,6 @@
namespace openloco
{
using company_id_t = uint8_t;
namespace company_id
{
@ -101,6 +100,7 @@ namespace openloco
bool empty() const;
void ai_think();
bool isVehicleIndexUnlocked(const uint8_t vehicleIndex) const;
void recalculateTransportCounts();
};
#pragma pack(pop)

View File

@ -19,10 +19,20 @@ namespace openloco::game_commands
flag_6 = 1 << 6, // 0x40
};
enum class GameCommand : uint8_t
{
vehicle_rearange = 0,
vehicle_place = 1,
vehicle_pickup = 2,
vehicle_create = 5,
vehicle_sell = 6,
};
constexpr uint32_t FAILURE = 0x80000000;
void registerHooks();
uint32_t do_command(int esi, const registers& registers);
bool sub_431E6A(const company_id_t company, map::tile_element* const tile = nullptr);
// Build vehicle
inline bool do_5(uint16_t vehicle_type, uint16_t vehicle_id = 0xFFFF)

View File

@ -138,6 +138,7 @@ namespace openloco::string_ids
constexpr string_id menu_underground_view = 145;
constexpr string_id menu_hide_foreground_tracks_roads = 146;
constexpr string_id too_many_objects_in_game = 171;
constexpr string_id menu_rotate_clockwise = 172;
constexpr string_id menu_rotate_anti_clockwise = 173;
@ -228,6 +229,8 @@ namespace openloco::string_ids
constexpr string_id tooltip_bridge_stats = 278;
constexpr string_id tooltip_select_station_type = 279;
constexpr string_id incompatible_vehicle = 335;
constexpr string_id too_many_vehicles = 336;
constexpr string_id buffer_337 = 337;
constexpr string_id buffer_338 = 338;
@ -597,6 +600,10 @@ namespace openloco::string_ids
constexpr string_id tooltip_sort_by_profit = 1154;
constexpr string_id tooltip_sort_by_age = 1155;
constexpr string_id tooltip_sort_by_reliability = 1156;
constexpr string_id vehicle_must_be_stopped = 1157;
constexpr string_id vehicle_has_crashed = 1158;
constexpr string_id vehicle_has_broken_down = 1159;
constexpr string_id vehicle_is_stuck = 1160;
constexpr string_id cant_add_pop_5_string_id_string_id = 1184;
constexpr string_id cant_build_pop_5_string_id = 1185;
@ -630,6 +637,7 @@ namespace openloco::string_ids
constexpr string_id station_cargo = 1211;
constexpr string_id station_cargo_en_route_start = 1212;
constexpr string_id station_cargo_en_route_end = 1213;
constexpr string_id no_space_for_more_vehicle_orders = 1214;
constexpr string_id build_trains = 1240;
constexpr string_id build_buses = 1241;
@ -811,6 +819,7 @@ namespace openloco::string_ids
constexpr string_id tooltip_station_cargo = 1448;
constexpr string_id tooltip_station_cargo_ratings = 1449;
constexpr string_id vehicle_too_long = 1452;
constexpr string_id tooltip_build_or_move_headquarters = 1453;
constexpr string_id tooltip_change_owner_name = 1454;
constexpr string_id not_yet_constructed = 1455;

View File

@ -36,6 +36,13 @@ namespace openloco
diesel_exhaust2,
ship_wake
};
namespace sprite_ind
{
constexpr uint8_t null = 0xFF;
constexpr uint8_t flag_unk7 = (1 << 7); // Set on electric multiple unit
}
#pragma pack(push, 1)
struct vehicle_object_sound_1
{
@ -95,11 +102,22 @@ namespace openloco
struct vehicle_object_unk
{
uint8_t length; // 0x00
uint8_t pad_01[0x04 - 0x01];
uint8_t sprite_ind; // 0x04
uint8_t pad_01;
uint8_t front_bogie_sprite_ind; // 0x02 index of bogie_sprites struct
uint8_t back_bogie_sprite_ind; // 0x03 index of bogie_sprites struct
uint8_t body_sprite_ind; // 0x04 index of a sprites struct
uint8_t var_05;
};
struct vehicle_object_bogie_sprite
{
uint8_t pad_00[0x02 - 0x00];
uint8_t var_02;
uint8_t var_03;
uint8_t var_04;
uint8_t pad_05[0x12 - 0x5];
};
struct vehicle_object_sprite
{
uint8_t num_dir; // 0x00
@ -110,7 +128,9 @@ namespace openloco
uint8_t var_05;
uint8_t bogey_position; // 0x06
uint8_t flags; // 0x07
uint8_t pad_08[0x0B - 0x08];
uint8_t var_08;
uint8_t var_09;
uint8_t var_0A;
uint8_t var_0B;
uint8_t var_0C;
uint8_t pad_0D;
@ -120,12 +140,14 @@ namespace openloco
namespace flags_E0
{
constexpr uint16_t flag_02 = 1 << 2; // rollable? APT Passenger carriage
constexpr uint16_t flag_03 = 1 << 3; // rollable? APT Driving carriage
constexpr uint16_t rack_rail = 1 << 6;
constexpr uint16_t unk_09 = 1 << 9; //anytrack??
constexpr uint16_t unk_11 = 1 << 10; //cancouple??
constexpr uint16_t unk_12 = 1 << 6; //dualhead??
constexpr uint16_t refittable = 1 << 9;
constexpr uint16_t unk_15 = 1 << 10; //noannounce??
constexpr uint16_t unk_09 = 1 << 9; //anytrack??
constexpr uint16_t can_couple = 1 << 11;
constexpr uint16_t unk_12 = 1 << 12; //dualhead??
constexpr uint16_t refittable = 1 << 14;
constexpr uint16_t unk_15 = 1 << 15; //noannounce??
}
struct vehicle_object
@ -133,30 +155,30 @@ namespace openloco
string_id name; // 0x00
TransportMode mode; // 0x02
VehicleType type; // 0x03
uint8_t pad_04;
uint8_t track_type; // 0x05
uint8_t num_mods; // 0x06
uint8_t cost_ind; // 0x07
int16_t cost_fact; // 0x08
uint8_t reliability; // 0x0A
uint8_t run_cost_ind; // 0x0B
int16_t run_cost_fact; // 0x0C
uint8_t colour_type; // 0x0E
uint8_t num_compat; // 0x0F
uint8_t pad_10[0x20 - 0x10];
uint8_t var_04;
uint8_t track_type; // 0x05
uint8_t num_mods; // 0x06
uint8_t cost_index; // 0x07
int16_t cost_factor; // 0x08
uint8_t reliability; // 0x0A
uint8_t run_cost_index; // 0x0B
int16_t run_cost_factor; // 0x0C
uint8_t colour_type; // 0x0E
uint8_t num_compat; // 0x0F
uint16_t compatible_vehicles[8]; // 0x10 array of compatible vehicle_types
uint8_t required_track_extras[4]; // 0x20
vehicle_object_unk var_24[4];
vehicle_object_sprite sprites[4]; // 0x3C
uint8_t pad_B4[0xD8 - 0xB4];
uint16_t power; // 0xD8
uint16_t speed; // 0xDA
uint16_t rack_speed; // 0xDC
uint16_t weight; // 0xDE
uint16_t flags; // 0xE0
uint8_t max_primary_cargo; // 0xE2
uint8_t max_secondary_cargo; // 0xE3
uint32_t primary_cargo_types; // 0xE4
uint32_t secondary_cargo_types; // 0xE8
vehicle_object_sprite sprites[4]; // 0x3C
vehicle_object_bogie_sprite bogie_sprites[2]; // 0xB4
uint16_t power; // 0xD8
uint16_t speed; // 0xDA
uint16_t rack_speed; // 0xDC
uint16_t weight; // 0xDE
uint16_t flags; // 0xE0
uint8_t max_primary_cargo; // 0xE2
uint8_t max_secondary_cargo; // 0xE3
uint32_t primary_cargo_types; // 0xE4
uint32_t secondary_cargo_types; // 0xE8
uint8_t pad_EC[0x10C - 0xEC];
uint8_t num_simultaneous_cargo_types; // 0x10C
simple_animation animation[2]; // 0x10D
@ -176,4 +198,5 @@ namespace openloco
uint8_t var_15B[0x15E - 0x15B]; // sound array size num_sounds/tbc??
};
#pragma pack(pop)
static_assert(sizeof(vehicle_object) == 0x15E);
}

View File

@ -61,6 +61,7 @@
<ClCompile Include="scenariomgr.cpp" />
<ClCompile Include="station.cpp" />
<ClCompile Include="stationmgr.cpp" />
<ClCompile Include="things\CreateVehicle.cpp" />
<ClCompile Include="things\misc.cpp" />
<ClCompile Include="things\thing.cpp" />
<ClCompile Include="things\thingmgr.cpp" />

View File

@ -0,0 +1,952 @@
#include "../company.h"
#include "../companymgr.h"
#include "../core/Optional.hpp"
#include "../date.h"
#include "../game_commands.h"
#include "../management/Expenditures.h"
#include "../map/tile.h"
#include "../objects/objectmgr.h"
#include "../objects/road_object.h"
#include "../objects/track_object.h"
#include "../objects/vehicle_object.h"
#include "../ui/WindowManager.h"
#include "thingmgr.h"
#include "vehicle.h"
#include <numeric>
#include <utility>
using namespace openloco;
using namespace openloco::interop;
using namespace openloco::objectmgr;
using namespace openloco::game_commands;
namespace openloco::things::vehicle
{
constexpr uint32_t max_orders = 256000;
constexpr auto max_ai_vehicles = 500;
constexpr auto max_num_car_components_in_car = 4; // TODO: Move to vehicle_object
constexpr auto num_vehicle_components_in_car_component = 3; // Bogie bogie body
constexpr auto num_vehicle_components_in_base = 4; // head unk_1 unk_2 tail
constexpr auto max_num_vehicle_components_in_car = num_vehicle_components_in_car_component * max_num_car_components_in_car;
constexpr thing_id_t allocated_but_free_routing_station = -2; // Indicates that this array is allocated to a vehicle but no station has been set.
static loco_global<company_id_t, 0x009C68EB> _updating_company_id;
static loco_global<uint16_t, 0x009C68E0> gameCommandMapX;
static loco_global<uint16_t, 0x009C68E2> gameCommandMapY;
static loco_global<uint16_t, 0x009C68E4> gameCommandMapZ;
static loco_global<string_id, 0x009C68E6> gGameCommandErrorText;
static loco_global<uint8_t, 0x009C68EA> gGameCommandExpenditureType; // premultiplied by 4
static loco_global<uint8_t, 0x009C68EE> _errorCompanyId;
static loco_global<map::tile_element*, 0x009C68D0> _9C68D0;
static loco_global<ColourScheme, 0x01136140> _1136140; // primary colour
static loco_global<int32_t, 0x011360FC> _11360FC;
static loco_global<openloco::vehicle_head*, 0x01136240> _backupVeh0;
static loco_global<int16_t, 0x01136248> _backup2E;
static loco_global<int16_t, 0x0113624C> _backup2C;
static loco_global<int16_t, 0x01136250> _backupX;
static loco_global<int16_t, 0x01136254> _backupY;
static loco_global<uint8_t, 0x01136258> _backupZ;
static loco_global<uint16_t, 0x0113642A> _113642A; // used by build window and others
static loco_global<uint32_t[32], 0x00525E5E> currencyMultiplicationFactor;
static loco_global<uint8_t, 0x00525FC5> _525FC5;
static loco_global<uint32_t, 0x00525FB8> _525FB8; // total used length of _987C5C
static loco_global<thing_id_t[64][1000], 0x0096885C> _96885C; // Likely routing related
static loco_global<uint8_t[max_orders], 0x00987C5C> _987C5C; // ?orders? ?routing related?
// 0x004B1D96
static bool aiIsBelowVehicleLimit()
{
if (is_player_company(_updating_company_id))
{
return true;
}
const auto& companies = companymgr::companies();
auto totalAiVehicles = std::accumulate(companies.begin(), companies.end(), 0, [](int32_t& total, const auto& company) {
if (company.empty())
return total;
if (is_player_company(company.id()))
return total;
return total + std::accumulate(std::begin(company.transportTypeCount), std::end(company.transportTypeCount), 0);
});
if (totalAiVehicles > max_ai_vehicles)
{
gGameCommandErrorText = string_ids::too_many_vehicles;
return false;
}
return true;
}
// 0x004B1E44
static bool isEmptyVehicleSlotAvailable()
{
if (!aiIsBelowVehicleLimit())
{
return false;
}
for (auto i = 0; i < 1000; i++)
{
auto id = _96885C[i][0];
if (id == thing_id::null)
{
return true;
}
}
gGameCommandErrorText = string_ids::too_many_vehicles;
return false;
}
static bool sub_4B0BDD(openloco::vehicle_head* const head)
{
switch (head->var_5D)
{
case 8:
gGameCommandErrorText = string_ids::vehicle_has_crashed;
return false;
case 9:
gGameCommandErrorText = string_ids::vehicle_is_stuck;
return false;
case 7:
gGameCommandErrorText = string_ids::vehicle_has_broken_down;
return false;
default:
{
auto veh1 = reinterpret_cast<openloco::vehicle*>(head)->next_car();
auto veh2 = veh1->next_car()->as_vehicle_2();
if (head->vehicleType == VehicleType::plane || head->vehicleType == VehicleType::ship)
{
if (veh2->var_73 & (1 << 0))
{
gGameCommandErrorText = string_ids::vehicle_has_broken_down;
return false;
}
if (head->tile_x == -1)
{
return true;
}
if (head->var_5D != 6 && head->var_5D != 1)
{
gGameCommandErrorText = string_ids::vehicle_must_be_stopped;
return false;
}
if (veh2->var_56 == 0)
{
return true;
}
gGameCommandErrorText = string_ids::vehicle_must_be_stopped;
return false;
}
else
{
if (head->tile_x == -1)
{
return true;
}
if (veh2->var_56 == 0)
{
return true;
}
if (veh1->var_3C <= 13961)
{
return true;
}
gGameCommandErrorText = string_ids::vehicle_must_be_stopped;
return false;
}
}
}
}
// 0x004B08DD
static void liftUpVehicle(openloco::vehicle_head* const head)
{
registers regs{};
regs.esi = reinterpret_cast<uint32_t>(head);
call(0x004B08DD, regs);
}
// 0x00470039
static openloco::vehicle_base* createVehicleThing()
{
registers regs{};
call(0x00470039, regs);
return reinterpret_cast<openloco::vehicle_base*>(regs.esi);
}
template<typename T>
static T* createVehicleThing()
{
auto* const base = createVehicleThing();
base->base_type = thing_base_type::vehicle;
base->type = T::VehicleThingType;
return reinterpret_cast<T*>(base);
}
// 0x004BA873
// esi : vehBogie
static void sub_4BA873(openloco::vehicle_bogie* const vehBogie)
{
vehBogie->var_68 = 0xFFFF;
if (vehBogie->reliability != 0)
{
int32_t reliabilityFactor = vehBogie->reliability / 256;
reliabilityFactor *= reliabilityFactor;
reliabilityFactor /= 16;
auto& prng = gprng();
int32_t randVal = (prng.rand_next(65535) * reliabilityFactor / 2) / 65536;
reliabilityFactor -= reliabilityFactor / 4;
reliabilityFactor += randVal;
vehBogie->var_68 = static_cast<uint16_t>(std::max(4, reliabilityFactor));
}
}
// 0x004AE8F1, 0x004AEA9E
static openloco::vehicle_bogie* createBogie(const thing_id_t head, const uint16_t vehicleTypeId, const vehicle_object& vehObject, const uint8_t bodyNumber, openloco::vehicle* const lastVeh, const ColourScheme colourScheme)
{
auto newBogie = createVehicleThing<vehicle_bogie>();
newBogie->owner = _updating_company_id;
newBogie->head = head;
newBogie->body_index = bodyNumber;
newBogie->track_type = lastVeh->track_type;
newBogie->mode = lastVeh->mode;
newBogie->tile_x = -1;
newBogie->tile_y = 0;
newBogie->tile_base_z = 0;
newBogie->var_2E = 0;
newBogie->var_2C = 0;
newBogie->var_36 = lastVeh->var_36;
newBogie->object_id = vehicleTypeId;
auto& prng = gprng();
newBogie->var_44 = prng.rand_next();
newBogie->creation_day = current_day();
newBogie->var_46 = 0;
newBogie->var_47 = 0;
newBogie->accepted_cargo_types = 0;
newBogie->cargo_type = 0xFF;
newBogie->var_51 = 0;
newBogie->var_5E = 0;
newBogie->var_5F = 0;
newBogie->var_60 = 0; // different to createbody
newBogie->var_61 = 0; // different to createbody
newBogie->var_14 = 1;
newBogie->var_09 = 1;
newBogie->var_15 = 1;
newBogie->colour_scheme = colourScheme;
lastVeh->next_car_id = newBogie->id;
return newBogie;
}
// 0x4AE8F1
static openloco::vehicle_bogie* createFirstBogie(const thing_id_t head, const uint16_t vehicleTypeId, const vehicle_object& vehObject, const uint8_t bodyNumber, openloco::vehicle* const lastVeh, const ColourScheme colourScheme)
{
auto newBogie = createBogie(head, vehicleTypeId, vehObject, bodyNumber, lastVeh, colourScheme);
if (newBogie == nullptr) // Can never happen
{
return nullptr;
}
newBogie->var_38 = 0;
int32_t reliability = vehObject.reliability * 256;
if (current_year() + 2 > vehObject.designed)
{
// Reduce reliability by an eighth after 2 years past design
reliability -= reliability / 8;
if (current_year() + 3 > vehObject.designed)
{
// Reduce reliability by a further eighth (quarter total) after 3 years past design
reliability -= reliability / 8;
}
}
if (reliability != 0)
{
reliability += 255;
}
newBogie->reliability = reliability;
sub_4BA873(newBogie);
// Calculate refund cost == 7/8 * cost
// TODO: use FixedPoint with 6 {(1 << 6) == 64} decimals for cost_index
auto cost = (vehObject.cost_factor * currencyMultiplicationFactor[vehObject.cost_index]) / 64;
newBogie->refund_cost = cost - cost / 8;
if (bodyNumber == 0)
{
// Only front car components front bogie can have cargo
// stores only secondary cargo presumably due to space constraints
// in the front car component body
if (vehObject.num_simultaneous_cargo_types > 1)
{
newBogie->max_cargo = vehObject.max_secondary_cargo;
newBogie->accepted_cargo_types = vehObject.secondary_cargo_types;
auto cargoType = utility::bitscanforward(newBogie->accepted_cargo_types);
if (cargoType != -1)
{
newBogie->cargo_type = cargoType;
}
}
}
newBogie->object_sprite_type = vehObject.var_24[bodyNumber].front_bogie_sprite_ind;
if (newBogie->object_sprite_type != sprite_ind::null)
{
newBogie->var_14 = vehObject.bogie_sprites[newBogie->object_sprite_type].var_02;
newBogie->var_09 = vehObject.bogie_sprites[newBogie->object_sprite_type].var_03;
newBogie->var_15 = vehObject.bogie_sprites[newBogie->object_sprite_type].var_04;
}
return newBogie;
}
// 0x004AEA9E
static openloco::vehicle_bogie* createSecondBogie(const thing_id_t head, const uint16_t vehicleTypeId, const vehicle_object& vehObject, const uint8_t bodyNumber, openloco::vehicle* const lastVeh, const ColourScheme colourScheme)
{
auto newBogie = createBogie(head, vehicleTypeId, vehObject, bodyNumber, lastVeh, colourScheme);
if (newBogie == nullptr) // Can never happen
{
return nullptr;
}
newBogie->var_38 = flags_38::unk_1;
newBogie->object_sprite_type = vehObject.var_24[bodyNumber].back_bogie_sprite_ind;
if (newBogie->object_sprite_type != sprite_ind::null)
{
newBogie->var_14 = vehObject.bogie_sprites[newBogie->object_sprite_type].var_02;
newBogie->var_09 = vehObject.bogie_sprites[newBogie->object_sprite_type].var_03;
newBogie->var_15 = vehObject.bogie_sprites[newBogie->object_sprite_type].var_04;
}
return newBogie;
}
// 0x004AEA9E
static openloco::vehicle_body* createBody(const thing_id_t head, const uint16_t vehicleTypeId, const vehicle_object& vehObject, const uint8_t bodyNumber, openloco::vehicle* const lastVeh, const ColourScheme colourScheme)
{
auto newBody = createVehicleThing<vehicle_body>();
// TODO: move this into the create function somehow
newBody->type = bodyNumber == 0 ? vehicle_thing_type::vehicle_body_start : vehicle_thing_type::vehicle_body_cont;
newBody->owner = _updating_company_id;
newBody->head = head;
newBody->body_index = bodyNumber;
newBody->track_type = lastVeh->track_type;
newBody->mode = lastVeh->mode;
newBody->tile_x = -1;
newBody->tile_y = 0;
newBody->tile_base_z = 0;
newBody->var_2E = 0;
newBody->var_2C = 0;
newBody->var_36 = lastVeh->var_36;
newBody->var_38 = flags_38::unk_0; // different to create bogie
newBody->object_id = vehicleTypeId;
auto& prng = gprng();
newBody->var_44 = prng.rand_next();
newBody->creation_day = current_day();
newBody->var_46 = 0;
newBody->var_47 = 0;
newBody->accepted_cargo_types = 0;
newBody->cargo_type = 0xFF;
newBody->var_51 = 0;
newBody->var_55 = 0; // different to create bogie
newBody->var_5E = 0;
newBody->var_5F = 0;
// different to create bogie
if (bodyNumber == 0)
{
// If the car carries any type of cargo then it will have a primary
// cargo in the first body of the first car component of the car.
// Locomotives do not carry any cargo.
if (vehObject.num_simultaneous_cargo_types != 0)
{
newBody->max_cargo = vehObject.max_primary_cargo;
newBody->accepted_cargo_types = vehObject.primary_cargo_types;
auto cargoType = utility::bitscanforward(newBody->accepted_cargo_types);
if (cargoType != -1)
{
newBody->cargo_type = cargoType;
}
}
}
newBody->var_14 = 1;
newBody->var_09 = 1;
newBody->var_15 = 1;
// different onwards to create bogie
auto spriteType = vehObject.var_24[bodyNumber].body_sprite_ind;
if (spriteType != sprite_ind::null)
{
if (spriteType & sprite_ind::flag_unk7)
{
newBody->var_38 |= flags_38::unk_1;
spriteType &= ~sprite_ind::flag_unk7;
}
}
newBody->object_sprite_type = spriteType;
if (newBody->object_sprite_type != sprite_ind::null)
{
newBody->var_14 = vehObject.sprites[newBody->object_sprite_type].var_08;
newBody->var_09 = vehObject.sprites[newBody->object_sprite_type].var_09;
newBody->var_15 = vehObject.sprites[newBody->object_sprite_type].var_0A;
}
newBody->colour_scheme = colourScheme; // same as create bogie
if (bodyNumber == 0 && vehObject.flags & flags_E0::flag_02)
{
newBody->var_38 |= flags_38::unk_3;
}
if (bodyNumber + 1 == vehObject.var_04 && vehObject.flags & flags_E0::flag_03)
{
newBody->var_38 |= flags_38::unk_3;
}
lastVeh->next_car_id = newBody->id; // same as create bogie
return newBody;
}
static void sub_4B7CC3(openloco::vehicle_head* const head)
{
registers regs{};
regs.esi = reinterpret_cast<int32_t>(head);
call(0x004B7CC3, regs);
}
// 0x004AE86D
static bool createCar(openloco::vehicle_head* const head, const uint16_t vehicleTypeId)
{
if (!thingmgr::checkNumFreeThings(max_num_vehicle_components_in_car))
{
return false;
}
// Get Car insertion location
// lastVeh will point to the vehicle component prior to the tail (head, unk_1, unk_2 *here*, tail) or (... bogie, bogie, body *here*, tail)
openloco::vehicle* lastVeh = nullptr; // will be of type vehicle_body_start or unk_2 at end of loop
openloco::vehicle* tail = reinterpret_cast<openloco::vehicle*>(head); // will be of type vehicle_6 at end of loop
while (tail->type != vehicle_thing_type::vehicle_6)
{
lastVeh = tail;
tail = lastVeh->next_car();
}
const auto vehObject = objectmgr::get<vehicle_object>(vehicleTypeId);
const auto company = companymgr::get(_updating_company_id);
_1136140 = company->mainColours; // Copy to global variable. Can be removed when all global uses confirmed
auto colourScheme = company->mainColours;
if (company->customVehicleColoursSet & (1 << vehObject->colour_type))
{
_1136140 = company->vehicleColours[vehObject->colour_type - 1]; // Copy to global variable. Can be removed when all global uses confirmed
colourScheme = company->vehicleColours[vehObject->colour_type - 1];
}
openloco::vehicle_bogie* newCarStart = nullptr;
for (auto bodyNumber = 0; bodyNumber < vehObject->var_04; ++bodyNumber)
{
auto* const firstBogie = createFirstBogie(head->id, vehicleTypeId, *vehObject, bodyNumber, lastVeh, colourScheme);
lastVeh = reinterpret_cast<openloco::vehicle*>(firstBogie);
auto* const secondBogie = createSecondBogie(head->id, vehicleTypeId, *vehObject, bodyNumber, lastVeh, colourScheme);
lastVeh = reinterpret_cast<openloco::vehicle*>(secondBogie);
auto* const body = createBody(head->id, vehicleTypeId, *vehObject, bodyNumber, lastVeh, colourScheme);
lastVeh = reinterpret_cast<openloco::vehicle*>(body);
if (newCarStart == nullptr)
{
newCarStart = firstBogie;
}
}
if (lastVeh == nullptr) // can never happen
{
return false;
}
lastVeh->next_car_id = tail->id;
sub_4B7CC3(head);
return true;
}
static std::optional<uint16_t> sub_4B1E00()
{
if (!aiIsBelowVehicleLimit())
{
return {};
}
// ?Routing? related. Max 64 routing stops.
for (auto i = 0; i < 1000; i++)
{
auto id = _96885C[i][0];
if (id == thing_id::null)
{
for (auto j = 0; j < 64; ++j)
{
_96885C[i][j] = allocated_but_free_routing_station;
}
return { i };
}
}
gGameCommandErrorText = string_ids::too_many_vehicles;
return {};
}
static void sub_470312(vehicle_head* const newHead)
{
_987C5C[_525FB8] = 0;
newHead->length_of_var_4C = _525FB8;
_525FB8++;
newHead->var_4A = 0;
newHead->var_4C = 1;
}
// 0x004B64F9
static uint16_t createUniqueTypeNumber(const VehicleType type)
{
std::array<bool, 1000> _unkArr{};
auto v = thingmgr::first<openloco::vehicle>();
while (v != nullptr)
{
auto next = v->next_vehicle();
if (v->owner == _updating_company_id && v->vehicleType == type)
{
if (v->var_44 != 0)
{
_unkArr[v->var_44 - 1] = true;
}
}
v = next;
}
uint16_t newNum = 0;
for (; newNum < _unkArr.size(); ++newNum)
{
if (!_unkArr[newNum])
break;
}
return newNum + 1;
}
// 0x004AE34B
static openloco::vehicle_head* createHead(const uint8_t trackType, const TransportMode mode, const uint16_t orderId, const VehicleType vehicleType)
{
auto* const newHead = createVehicleThing<vehicle_head>();
thingmgr::moveSpriteToList(newHead, thingmgr::thing_list::vehicle_head);
newHead->owner = _updating_company_id;
newHead->head = newHead->id;
newHead->var_0C |= (1 << 1);
newHead->track_type = trackType;
newHead->mode = mode;
newHead->tile_x = -1;
newHead->tile_y = 0;
newHead->tile_base_z = 0;
newHead->var_28 = 0;
newHead->var_2E = 0;
newHead->var_2C = 0;
newHead->var_36 = orderId;
newHead->var_14 = 0;
newHead->var_09 = 0;
newHead->var_15 = 0;
newHead->var_38 = 0;
newHead->var_3C = 0;
newHead->vehicleType = vehicleType;
newHead->var_22 = static_cast<uint8_t>(vehicleType) + 4;
newHead->var_44 = 0; // Reset to null value so ignored in next function
newHead->var_44 = createUniqueTypeNumber(vehicleType);
newHead->var_5D = 0;
newHead->var_54 = -1;
newHead->var_5F = 0;
newHead->var_60 = -1;
newHead->var_61 = -1;
newHead->var_69 = 0;
newHead->var_77 = 0;
newHead->var_79 = 0;
sub_470312(newHead);
return newHead;
}
// 0x004AE40E
static openloco::vehicle_1* createVehicle1(const thing_id_t head, openloco::vehicle* const lastVeh)
{
auto* const newVeh1 = createVehicleThing<vehicle_1>();
newVeh1->owner = _updating_company_id;
newVeh1->head = head;
newVeh1->track_type = lastVeh->track_type;
newVeh1->mode = lastVeh->mode;
newVeh1->tile_x = -1;
newVeh1->tile_y = 0;
newVeh1->tile_base_z = 0;
newVeh1->var_28 = 0;
newVeh1->var_2E = 0;
newVeh1->var_2C = 0;
newVeh1->var_36 = lastVeh->var_36;
newVeh1->var_14 = 0;
newVeh1->var_09 = 0;
newVeh1->var_15 = 0;
newVeh1->var_38 = 0;
newVeh1->var_3C = 0;
newVeh1->var_44 = 0;
newVeh1->var_46 = 0;
newVeh1->var_48 = 0;
newVeh1->var_52 = 0;
newVeh1->var_4E = 0;
newVeh1->var_50 = 0;
newVeh1->var_53 = -1;
lastVeh->next_car_id = newVeh1->id;
return newVeh1;
}
// 0x004AE4A0
static openloco::vehicle_2* createVehicle2(const thing_id_t head, openloco::vehicle* const lastVeh)
{
auto* const newVeh2 = createVehicleThing<vehicle_2>();
newVeh2->owner = _updating_company_id;
newVeh2->head = head;
newVeh2->track_type = lastVeh->track_type;
newVeh2->mode = lastVeh->mode;
newVeh2->tile_x = -1;
newVeh2->tile_y = 0;
newVeh2->tile_base_z = 0;
newVeh2->var_28 = 0;
newVeh2->var_2E = 0;
newVeh2->var_2C = 0;
newVeh2->var_36 = lastVeh->var_36;
newVeh2->var_14 = 0;
newVeh2->var_09 = 0;
newVeh2->var_15 = 0;
newVeh2->var_38 = 0;
newVeh2->var_56 = 0;
newVeh2->var_5A = 0;
newVeh2->var_5B = 0;
newVeh2->var_44 = -1;
newVeh2->var_48 = -1;
newVeh2->var_48 = 0;
newVeh2->var_4A = 0;
newVeh2->var_5E = 0;
newVeh2->refund_cost = 0;
newVeh2->var_66 = 0;
newVeh2->var_6A = 0;
newVeh2->var_6E = 0;
newVeh2->var_72 = 0;
newVeh2->var_73 = 0;
lastVeh->next_car_id = newVeh2->id;
return newVeh2;
}
// 0x004AE54E
static openloco::vehicle_tail* createVehicleTail(const thing_id_t head, openloco::vehicle* const lastVeh)
{
auto* const newTail = createVehicleThing<vehicle_tail>();
newTail->owner = _updating_company_id;
newTail->head = head;
newTail->track_type = lastVeh->track_type;
newTail->mode = lastVeh->mode;
newTail->tile_x = -1;
newTail->tile_y = 0;
newTail->tile_base_z = 0;
newTail->var_28 = 0;
newTail->var_2E = 0;
newTail->var_2C = 0;
newTail->var_36 = lastVeh->var_36;
newTail->var_14 = 0;
newTail->var_09 = 0;
newTail->var_15 = 0;
newTail->var_38 = 0;
newTail->var_44 = -1;
newTail->var_48 = -1;
newTail->var_4A = 0;
lastVeh->next_car_id = newTail->id;
newTail->next_car_id = thing_id::null;
return newTail;
}
// 0x004AE318
static std::optional<openloco::vehicle_head*> createBaseVehicle(const TransportMode mode, const VehicleType type, const uint8_t trackType)
{
if (!thingmgr::checkNumFreeThings(num_vehicle_components_in_base))
{
return {};
}
if (_525FB8 >= max_orders)
{
gGameCommandErrorText = string_ids::no_space_for_more_vehicle_orders;
return {};
}
auto orderId = sub_4B1E00();
if (!orderId)
{
return {};
}
auto* const head = createHead(trackType, mode, *orderId, type);
openloco::vehicle* lastVeh = reinterpret_cast<openloco::vehicle*>(head);
if (lastVeh == nullptr) // Can never happen
{
return {};
}
auto* const veh1 = createVehicle1(head->id, lastVeh);
lastVeh = reinterpret_cast<openloco::vehicle*>(veh1);
if (lastVeh == nullptr) // Can never happen
{
return {};
}
auto* const veh2 = createVehicle2(head->id, lastVeh);
lastVeh = reinterpret_cast<openloco::vehicle*>(veh2);
if (lastVeh == nullptr) // Can never happen
{
return {};
}
createVehicleTail(head->id, lastVeh);
sub_4B7CC3(head);
return { head };
}
static void sub_4AF7A4(openloco::vehicle_head* const veh0)
{
registers regs{};
regs.esi = reinterpret_cast<int32_t>(veh0);
call(0x004AF7A4, regs);
}
// 0x004B05E4
static void placeDownVehicle(openloco::vehicle_head* const head, const coord_t x, const coord_t y, const uint8_t baseZ, const uint16_t unk1, const uint16_t unk2)
{
registers regs{};
regs.esi = reinterpret_cast<int32_t>(head);
regs.ax = x;
regs.cx = y;
regs.bx = unk2;
regs.dl = baseZ;
regs.ebp = unk1;
call(0x004B05E4, regs);
}
// 0x004B1E77
// Free orderId?
static void sub_4B1E77(const uint16_t orderId)
{
uint16_t baseOrderId = orderId & ~(0x3F);
for (auto i = 0; i < 64; ++i)
{
_96885C[baseOrderId][i] = thing_id::null;
}
}
static void sub_470795(const uint32_t var46, const int16_t var4C)
{
auto v = thingmgr::first<openloco::vehicle>();
while (v != nullptr)
{
auto next = v->next_vehicle();
auto head = v->as_vehicle_head();
v = next;
if (head == nullptr) // Can never happen
{
continue;
}
if (head->length_of_var_4C >= var46)
{
head->length_of_var_4C += var4C;
}
}
}
// 0x00470334
// Remove vehicle ?orders?
static void sub_470334(openloco::vehicle_head* const head)
{
sub_470795(head->length_of_var_4C, head->var_4C * -1);
auto length = _525FB8 - head->length_of_var_4C - head->var_4C;
memmove(&_987C5C[head->length_of_var_4C], &_987C5C[head->var_4C + head->length_of_var_4C], length);
_525FB8 = _525FB8 - head->var_4C;
}
// 0x0042851C
// Delete related news items??
static void sub_42851C(const thing_id_t id, const uint8_t type)
{
registers regs{};
regs.al = type;
regs.dx = id;
call(0x0042851C, regs);
}
// 0x004AE6DE
static void updateWholeVehicle(vehicle_head* const head)
{
sub_4AF7A4(head);
auto company = companymgr::get(_updating_company_id);
company->recalculateTransportCounts();
if (_backupVeh0 != reinterpret_cast<openloco::vehicle_head*>(-1))
{
placeDownVehicle(_backupVeh0, _backupX, _backupY, _backupZ, _backup2C, _backup2E);
}
ui::WindowManager::invalidate(ui::WindowType::vehicleList, head->owner);
}
// 0x004AE74E
static uint32_t createNewVehicle(const uint8_t flags, const uint16_t vehicleTypeId)
{
gameCommandMapX = location::null;
if (!thingmgr::checkNumFreeThings(max_num_vehicle_components_in_car + num_vehicle_components_in_base))
{
return FAILURE;
}
if (!isEmptyVehicleSlotAvailable())
{
return FAILURE;
}
if (flags & game_commands::GameCommandFlag::apply)
{
auto vehObject = objectmgr::get<vehicle_object>(vehicleTypeId);
auto head = createBaseVehicle(vehObject->mode, vehObject->type, vehObject->track_type);
if (!head)
{
return FAILURE;
}
auto _head = *head;
_113642A = _head->id;
if (createCar(_head, vehicleTypeId))
{
// 0x004AE6DE
updateWholeVehicle(_head);
}
else
{
// Cleanup and delete base vehicle before exit.
sub_4B1E77(_head->var_36);
sub_470334(_head);
sub_42851C(_head->id, 3);
auto veh1 = reinterpret_cast<openloco::vehicle*>(_head)->next_car();
auto veh2 = veh1->next_car();
auto tail = veh2->next_car();
// Get all vehicles before freeing
thingmgr::freeThing(_head);
thingmgr::freeThing(veh1);
thingmgr::freeThing(veh2);
thingmgr::freeThing(tail);
return FAILURE;
}
}
// 0x4AE733
auto vehObject = objectmgr::get<vehicle_object>(vehicleTypeId);
// TODO: use FixedPoint with 6 {(1 << 6) == 64} decimals for cost_index
auto cost = (vehObject->cost_factor * currencyMultiplicationFactor[vehObject->cost_index]) / 64;
return cost;
}
// 0x004AE5FF
static uint32_t addCarToVehicle(const uint8_t flags, const uint16_t vehicleTypeId, const uint16_t vehicleThingId)
{
auto veh0 = thingmgr::get<openloco::vehicle>(vehicleThingId);
auto head = veh0->as_vehicle_head();
auto veh2 = veh0->next_car()->next_car()->as_vehicle_2();
if (veh2 == nullptr || head == nullptr)
{
return FAILURE;
}
gameCommandMapX = veh2->x;
gameCommandMapY = veh2->y;
gameCommandMapZ = veh2->z;
if (!sub_431E6A(head->owner))
{
return FAILURE;
}
if (!sub_4B0BDD(head))
{
return FAILURE;
}
if (!head->isVehicleTypeCompatible(vehicleTypeId))
{
return FAILURE;
}
if (!thingmgr::checkNumFreeThings(max_num_vehicle_components_in_car))
{
return FAILURE;
}
if (flags & game_commands::GameCommandFlag::apply)
{
if (head->tile_x != -1)
{
_backupX = head->tile_x;
_backupY = head->tile_y;
_backupZ = head->tile_base_z;
_backup2C = head->var_2C;
_backup2E = head->var_2E;
_backupVeh0 = head;
liftUpVehicle(head);
}
if (createCar(head, vehicleTypeId))
{
updateWholeVehicle(head);
}
else
{
if (_backupVeh0 == reinterpret_cast<openloco::vehicle_head*>(-1))
{
return FAILURE;
}
vehicle_head* veh0backup = _backupVeh0;
// If it has an existing body
if (reinterpret_cast<openloco::vehicle*>(veh0backup)->next_car()->next_car()->next_car()->type == vehicle_thing_type::vehicle_6)
{
placeDownVehicle(_backupVeh0, _backupX, _backupY, _backupZ, _backup2C, _backup2E);
}
return FAILURE;
}
}
// 0x4AE733
auto vehObject = objectmgr::get<vehicle_object>(vehicleTypeId);
// TODO: use FixedPoint with 6 {(1 << 6) == 64} decimals for cost_index
auto cost = (vehObject->cost_factor * currencyMultiplicationFactor[vehObject->cost_index]) / 64;
return cost;
}
// 0x004AE5E4
uint32_t create(const uint8_t flags, const uint16_t vehicleTypeId, const uint16_t vehicleThingId)
{
gGameCommandExpenditureType = static_cast<uint8_t>(ExpenditureType::VehiclePurchases) * 4;
_backupVeh0 = reinterpret_cast<openloco::vehicle_head*>(-1);
if (vehicleThingId == (uint16_t)-1)
{
return createNewVehicle(flags, vehicleTypeId);
}
else
{
return addCarToVehicle(flags, vehicleTypeId, vehicleThingId);
}
}
}

View File

@ -7,13 +7,20 @@ using namespace openloco::interop;
namespace openloco::thingmgr
{
loco_global<thing_id_t[num_thing_lists], 0x00525E40> _heads;
loco_global<uint16_t[num_thing_lists], 0x00525E4C> _listCounts;
loco_global<Thing[max_things], 0x006DB6DC> _things;
static loco_global<string_id, 0x009C68E6> gGameCommandErrorText;
thing_id_t first_id(thing_list list)
{
return _heads[(size_t)list];
}
uint16_t getListCount(const thing_list list)
{
return _listCounts[static_cast<size_t>(list)];
}
template<>
vehicle* first()
{
@ -39,6 +46,14 @@ namespace openloco::thingmgr
return (thing_base*)regs.esi;
}
// 0x0047024A
void freeThing(thing_base* const thing)
{
registers regs;
regs.esi = reinterpret_cast<uint32_t>(thing);
call(0x0047024A, regs);
}
// 0x004A8826
void update_vehicles()
{
@ -59,4 +74,24 @@ namespace openloco::thingmgr
{
call(0x004402F4);
}
// 0x0047019F
void moveSpriteToList(thing_base* const thing, const thing_list list)
{
registers regs{};
regs.esi = reinterpret_cast<uint32_t>(thing);
regs.ecx = static_cast<int8_t>(list);
call(0x0047019F, regs);
}
// 0x00470188
bool checkNumFreeThings(const size_t numNewThings)
{
if (thingmgr::getListCount(thingmgr::thing_list::null) <= numNewThings)
{
gGameCommandErrorText = string_ids::too_many_objects_in_game;
return false;
}
return true;
}
}

View File

@ -13,7 +13,8 @@ namespace openloco::thingmgr
{
null,
vehicle,
misc = 3
misc = 3,
vehicle_head,
};
template<typename T>
@ -34,7 +35,12 @@ namespace openloco::thingmgr
T* first();
thing_base* create_thing();
void freeThing(thing_base* const thing);
void update_vehicles();
void update_misc_things();
uint16_t getListCount(const thing_list list);
void moveSpriteToList(thing_base* const thing, const thing_list list);
bool checkNumFreeThings(const size_t numNewThings);
}

View File

@ -30,6 +30,7 @@ loco_global<uint16_t[2047], 0x00500B50> vehicle_arr_500B50;
loco_global<int16_t[128], 0x00503B6A> factorXY503B6A;
loco_global<uint8_t[44], 0x004F8A7C> vehicle_arr_4F8A7C; // bools
loco_global<uint8_t, 0x00525FAE> vehicle_var_525FAE; // boolean
static loco_global<string_id, 0x009C68E6> gGameCommandErrorText;
// 0x00503E5C
static constexpr uint8_t vehicleBodyIndexToPitch[] = {
@ -114,7 +115,7 @@ bool vehicle::update()
case vehicle_thing_type::vehicle_bogie:
result = call(0x004AA008, regs);
break;
case vehicle_thing_type::vehicle_body_end:
case vehicle_thing_type::vehicle_body_start:
case vehicle_thing_type::vehicle_body_cont:
result = as_vehicle_body()->update();
break;
@ -142,7 +143,7 @@ void vehicle::sub_4BA8D4()
return;
}
auto v = next_car()->next_car()->next_car();
auto v = next_car()->next_car()->next_car(); // bogie or tail
if (v->type != vehicle_thing_type::vehicle_6)
{
while (true)
@ -151,7 +152,7 @@ void vehicle::sub_4BA8D4()
{
if ((scenario_ticks() & 3) == 0)
{
auto v2 = v->next_car()->next_car();
auto v2 = v->next_car()->next_car(); // body
smoke::create(loc16(v2->x, v2->y, v2->z + 4));
}
}
@ -172,7 +173,7 @@ void vehicle::sub_4BA8D4()
}
}
v = v->next_car()->next_car()->next_car();
v = v->next_car()->next_car()->next_car(); // next bogie
vehicle* u;
do
{
@ -181,9 +182,9 @@ void vehicle::sub_4BA8D4()
return;
}
u = v->next_car()->next_car();
if (u->type != vehicle_thing_type::vehicle_body_end)
if (u->type != vehicle_thing_type::vehicle_body_start)
v = u->next_car();
} while (u->type != vehicle_thing_type::vehicle_body_end);
} while (u->type != vehicle_thing_type::vehicle_body_start);
}
}
}
@ -235,7 +236,7 @@ int32_t openloco::vehicle_body::update()
// 0x004AAC4E
void openloco::vehicle_body::animation_update()
{
if (var_38 & (1 << 4))
if (var_38 & things::vehicle::flags_38::unk_4)
return;
vehicle* veh = vehicle_1136118;
@ -243,7 +244,7 @@ void openloco::vehicle_body::animation_update()
return;
auto vehicleObject = object();
int32_t var_05 = vehicleObject->var_24[var_54].var_05;
int32_t var_05 = vehicleObject->var_24[body_index].var_05;
if (var_05 == 0)
{
return;
@ -286,7 +287,7 @@ void openloco::vehicle_body::animation_update()
void openloco::vehicle_body::sub_4AAB0B()
{
int32_t eax = vehicle_var_1136130 >> 3;
if (var_38 & (1 << 1))
if (var_38 & things::vehicle::flags_38::unk_1)
{
eax = -eax;
}
@ -326,7 +327,7 @@ void openloco::vehicle_body::sub_4AAB0B()
if (ah < 0)
{
if (var_38 & (1 << 1))
if (var_38 & things::vehicle::flags_38::unk_1)
{
ah = 2;
if (al != 0 && al != ah)
@ -345,7 +346,7 @@ void openloco::vehicle_body::sub_4AAB0B()
}
else if (ah > 0)
{
if (var_38 & (1 << 1))
if (var_38 & things::vehicle::flags_38::unk_1)
{
ah = 1;
if (al != 0 && al != ah)
@ -976,7 +977,8 @@ uint8_t openloco::vehicle_body::update_sprite_yaw_4(int16_t x_offset, int16_t y_
void openloco::vehicle_body::secondary_animation_update()
{
auto vehicleObject = object();
int32_t var_05 = vehicleObject->var_24[var_54].var_05;
uint8_t var_05 = vehicleObject->var_24[body_index].var_05;
if (var_05 == 0)
return;
@ -1035,7 +1037,7 @@ void openloco::vehicle_body::steam_puffs_animation_update(uint8_t num, int32_t v
auto _var_44 = var_44;
// Reversing
if (var_38 & (1 << 1))
if (var_38 & things::vehicle::flags_38::unk_1)
{
var_05 = -var_05;
_var_44 = -_var_44;
@ -1189,12 +1191,12 @@ void openloco::vehicle_body::diesel_exhaust1_animation_update(uint8_t num, int32
vehicle* veh_3 = vehicle_1136120;
auto vehicleObject = object();
if (veh->var_5E == 5)
if (veh->vehicleType == VehicleType::ship)
{
if (veh_3->var_56 == 0)
return;
if (var_38 & (1 << 1))
if (var_38 & things::vehicle::flags_38::unk_1)
{
var_05 = -var_05;
}
@ -1219,7 +1221,7 @@ void openloco::vehicle_body::diesel_exhaust1_animation_update(uint8_t num, int32
if (veh_3->var_5A != 1)
return;
if (var_38 & (1 << 1))
if (var_38 & things::vehicle::flags_38::unk_1)
{
var_05 = -var_05;
}
@ -1280,7 +1282,7 @@ void openloco::vehicle_body::diesel_exhaust2_animation_update(uint8_t num, int32
if (veh_3->var_56 > 917504)
return;
if (var_38 & (1 << 1))
if (var_38 & things::vehicle::flags_38::unk_1)
{
var_05 = -var_05;
}
@ -1353,7 +1355,7 @@ void openloco::vehicle_body::electric_spark1_animation_update(uint8_t num, int32
return;
auto _var_44 = var_44;
if (var_38 & (1 << 1))
if (var_38 & things::vehicle::flags_38::unk_1)
{
var_05 = -var_05;
_var_44 = -var_44;
@ -1413,7 +1415,7 @@ void openloco::vehicle_body::electric_spark2_animation_update(uint8_t num, int32
return;
auto _var_44 = var_44;
if (var_38 & (1 << 1))
if (var_38 & things::vehicle::flags_38::unk_1)
{
var_05 = -var_05;
_var_44 = -var_44;
@ -1456,7 +1458,7 @@ void openloco::vehicle_body::electric_spark2_animation_update(uint8_t num, int32
loc.y += yFactor;
auto yaw = (sprite_yaw + 16) & 0x3F;
auto firstBogie = var_38 & (1 << 1) ? backBogie : frontBogie;
auto firstBogie = var_38 & things::vehicle::flags_38::unk_1 ? backBogie : frontBogie;
xyFactor = 5;
if (!(vehicle_arr_4F8A7C[firstBogie->var_2C / 8] & 1))
{
@ -1549,3 +1551,167 @@ void openloco::vehicle_body::ship_wake_animation_update(uint8_t num, int32_t)
exhaust::create(loc, vehicleObject->animation[num].object_id);
}
// 0x004B90F0
// eax : newVehicleTypeId
// ebx : sourceVehicleTypeId;
static bool sub_4B90F0(const uint16_t newVehicleTypeId, const uint16_t sourceVehicleTypeId)
{
auto newObject = objectmgr::get<vehicle_object>(newVehicleTypeId); //edi
auto sourceObject = objectmgr::get<vehicle_object>(sourceVehicleTypeId); // esi
if ((newObject->flags & flags_E0::can_couple) && (sourceObject->flags & flags_E0::can_couple))
{
gGameCommandErrorText = string_ids::incompatible_vehicle;
return false;
}
if (newVehicleTypeId == sourceVehicleTypeId)
{
return true;
}
for (auto i = 0; i < newObject->num_compat; ++i)
{
if (newObject->compatible_vehicles[i] == sourceVehicleTypeId)
{
return true;
}
}
if (sourceObject->num_compat != 0)
{
for (auto i = 0; i < sourceObject->num_compat; ++i)
{
if (sourceObject->compatible_vehicles[i] == newVehicleTypeId)
{
return true;
}
}
}
if ((newObject->num_compat != 0) || (sourceObject->num_compat != 0))
{
gGameCommandErrorText = string_ids::incompatible_vehicle;
return false;
}
return true;
}
// 0x004B9780
// used by road vehicles only maybe??
static uint32_t getVehicleTypeLength(const uint16_t vehicleTypeId)
{
auto vehObject = objectmgr::get<vehicle_object>(vehicleTypeId);
auto length = 0;
for (auto i = 0; i < vehObject->var_04; ++i)
{
if (vehObject->var_24[i].body_sprite_ind == 0xFF)
{
continue;
}
auto unk = vehObject->var_24[i].body_sprite_ind & 0x7F;
length += vehObject->sprites[unk].bogey_position * 2;
}
return length;
}
// 0x004B97B7
// used by road vehicles only maybe??
uint32_t vehicle_head::getVehicleTotalLength() // TODO: const
{
auto totalLength = 0;
auto veh = reinterpret_cast<openloco::vehicle*>(this)->next_car()->next_car()->next_car();
while (veh->type != vehicle_thing_type::vehicle_6)
{
if (veh->type == vehicle_thing_type::vehicle_body_start)
{
totalLength += getVehicleTypeLength(veh->object_id);
}
veh = veh->next_car();
}
return totalLength;
}
// 0x004B8FA2
// esi : self
// ax : vehicleTypeId
bool vehicle_head::isVehicleTypeCompatible(const uint16_t vehicleTypeId) // TODO: const
{
auto newObject = objectmgr::get<vehicle_object>(vehicleTypeId);
if (newObject->mode == TransportMode::air || newObject->mode == TransportMode::water)
{
auto veh = reinterpret_cast<openloco::vehicle*>(this)->next_car()->next_car()->next_car();
if (veh->type != vehicle_thing_type::vehicle_6)
{
gGameCommandErrorText = string_ids::incompatible_vehicle;
return false;
}
}
else
{
if (newObject->track_type != track_type)
{
gGameCommandErrorText = string_ids::incompatible_vehicle;
return false;
}
}
if (newObject->mode != mode)
{
gGameCommandErrorText = string_ids::incompatible_vehicle;
return false;
}
if (newObject->type != vehicleType)
{
gGameCommandErrorText = string_ids::incompatible_vehicle;
return false;
}
{
auto veh = reinterpret_cast<openloco::vehicle*>(this)->next_car()->next_car()->next_car();
if (veh->type != vehicle_thing_type::vehicle_6)
{
while (veh->type != vehicle_thing_type::vehicle_6)
{
if (!sub_4B90F0(vehicleTypeId, veh->object_id))
{
return false;
}
vehicle_body* vehUnk = nullptr;
do
{
veh = veh->next_car()->next_car()->next_car();
if (veh->type == vehicle_thing_type::vehicle_6)
{
break;
}
vehUnk = veh->next_car()->next_car()->as_vehicle_body();
} while (vehUnk != nullptr && vehUnk->type == vehicle_thing_type::vehicle_body_cont);
}
}
}
if (mode != TransportMode::road)
{
return true;
}
if (track_type != 0xFF)
{
return true;
}
auto curTotalLength = getVehicleTotalLength();
auto additionalNewLength = getVehicleTypeLength(vehicleTypeId);
if (curTotalLength + additionalNewLength > openloco::things::vehicle::max_vehicle_length)
{
gGameCommandErrorText = string_ids::vehicle_too_long;
return false;
}
return true;
}

View File

@ -9,8 +9,28 @@
namespace openloco
{
namespace things::vehicle
{
constexpr auto max_vehicle_length = 176; // TODO: Units?
uint32_t create(const uint8_t flags, const uint16_t vehicleTypeId, const uint16_t vehicleThingId);
namespace flags_38
{
constexpr uint8_t unk_0 = 1 << 0;
constexpr uint8_t unk_1 = 1 << 1;
constexpr uint8_t unk_3 = 1 << 3;
constexpr uint8_t unk_4 = 1 << 4;
}
}
struct vehicle_head;
struct vehicle_1;
struct vehicle_2;
struct vehicle_bogie;
struct vehicle_body;
struct vehicle_tail;
struct vehicle_26;
namespace flags_5f
@ -25,7 +45,7 @@ namespace openloco
vehicle_1,
vehicle_2,
vehicle_bogie,
vehicle_body_end,
vehicle_body_start,
vehicle_body_cont,
vehicle_6,
};
@ -57,14 +77,24 @@ namespace openloco
template<typename TType, vehicle_thing_type TClass>
TType* as() const
{
// This can not use reinterpret_cast due to being a const member without considerable more code
return type == TClass ? (TType*)this : nullptr;
}
template<typename TType>
TType* as() const
{
return as<TType, TType::VehicleThingType>();
}
public:
vehicle_bogie* as_vehicle_bogie() const { return as<vehicle_bogie, vehicle_thing_type::vehicle_bogie>(); }
vehicle_head* as_vehicle_head() const { return as<vehicle_head>(); }
vehicle_1* as_vehicle_1() const { return as<vehicle_1>(); }
vehicle_2* as_vehicle_2() const { return as<vehicle_2>(); }
vehicle_bogie* as_vehicle_bogie() const { return as<vehicle_bogie>(); }
vehicle_body* as_vehicle_body() const
{
auto vehicle = as<vehicle_body, vehicle_thing_type::vehicle_body_end>();
auto vehicle = as<vehicle_body, vehicle_thing_type::vehicle_body_start>();
if (vehicle != nullptr)
return vehicle;
return as<vehicle_body, vehicle_thing_type::vehicle_body_cont>();
@ -76,6 +106,7 @@ namespace openloco
return vehicle;
return as<vehicle_26, vehicle_thing_type::vehicle_6>();
}
vehicle_tail* as_vehicle_tail() const { return as<vehicle_tail>(); }
};
struct vehicle : vehicle_base
@ -91,14 +122,14 @@ namespace openloco
int16_t tile_x; // 0x30
int16_t tile_y; // 0x32
uint8_t tile_base_z; // 0x34
uint8_t track_type; // 0x35
uint8_t pad_36[0x38 - 0x36];
uint8_t track_type; // 0x35 field same in all vehicles
uint16_t var_36; // 0x36 field same in all vehicles
uint8_t var_38;
uint8_t pad_39; // 0x39
thing_id_t next_car_id; // 0x3A
uint8_t pad_3C[0x40 - 0x3C];
uint16_t object_id; // 0x40
TransportMode mode; // 0x42
uint32_t var_3C; // veh1 speed?
uint16_t object_id; // 0x40 not used in all vehicles **be careful**
TransportMode mode; // 0x42 field same in all vehicles
uint8_t pad_43;
int16_t var_44; // used for name on vehicle_0 will be unique (for type) number
uint8_t pad_46;
@ -116,8 +147,8 @@ namespace openloco
uint8_t var_5A;
uint8_t pad_5B[0x5D - 0x5B];
uint8_t var_5D;
uint8_t var_5E;
uint8_t var_5F; // 0x5F (bit 1 = can break down)
VehicleType vehicleType; // 0x5E
uint8_t var_5F; // 0x5F (bit 1 = can break down)
uint8_t pad_60[0x6A - 0x60];
uint8_t var_6A;
uint8_t pad_6B[0x73 - 0x6B];
@ -134,20 +165,168 @@ namespace openloco
bool update();
void sub_4BAA76();
};
static_assert(sizeof(vehicle) == 0x74); // Can't use offset_of change this to last field if more found
struct vehicle_26 : vehicle_base
{
uint8_t pad_20[0x44 - 0x20];
uint8_t pad_20[0x40 - 0x20];
uint16_t object_id; // 0x40
uint8_t pad_42[0x44 - 0x42];
uint8_t sound_id; // 0x44
uint8_t pad_45[0x4A - 0x45];
uint16_t var_4A; // sound-related flag(s)
ui::window_number sound_window_number; // 0x4C
ui::WindowType sound_window_type; // 0x4E
uint8_t pad_4F[0x56 - 0x4F];
uint32_t var_56;
uint8_t pad_5A[0x73 - 0x53];
uint8_t var_73;
};
struct vehicle_head : vehicle_base
{
static constexpr auto VehicleThingType = vehicle_thing_type::vehicle_0;
uint8_t pad_20;
company_id_t owner; // 0x21
uint16_t var_22;
uint8_t pad_24[0x26 - 0x24];
thing_id_t head; // 0x26
uint32_t var_28;
uint16_t var_2C;
uint16_t var_2E;
int16_t tile_x; // 0x30
int16_t tile_y; // 0x32
uint8_t tile_base_z; // 0x34
uint8_t track_type; // 0x35 field same in all vehicles
uint16_t var_36; // 0x36 field same in all vehicles
uint8_t var_38;
uint8_t pad_39; // 0x39
thing_id_t next_car_id; // 0x3A
uint32_t var_3C; // 0x3C
uint8_t pad_40[0x2]; // 0x40
TransportMode mode; // 0x42 field same in all vehicles
uint8_t pad_43;
int16_t var_44;
uint32_t length_of_var_4C; // 0x46
uint16_t var_4A;
uint16_t var_4C; // 0x4C index into ?order? array
uint8_t pad_4E[0x2]; // 0x4E
uint8_t pad_50;
uint8_t pad_51; // 0x51
uint8_t var_52;
uint8_t pad_53;
int16_t var_54;
uint8_t pad_56[0x4];
uint8_t pad_5A;
uint8_t pad_5B[0x5D - 0x5B];
uint8_t var_5D;
VehicleType vehicleType; // 0x5E
uint8_t var_5F; // 0x5F
uint8_t var_60;
uint16_t var_61;
uint8_t pad_63[0x69 - 0x63];
uint32_t var_69;
uint8_t pad_6D[0x77 - 0x6D];
uint16_t var_77; //
uint8_t var_79;
public:
bool isVehicleTypeCompatible(const uint16_t vehicleTypeId);
private:
uint32_t getVehicleTotalLength();
};
static_assert(sizeof(vehicle_head) == 0x7A); // Can't use offset_of change this to last field if more found
struct vehicle_1 : vehicle_base
{
static constexpr auto VehicleThingType = vehicle_thing_type::vehicle_1;
uint8_t pad_20;
company_id_t owner; // 0x21
uint8_t pad_22[0x26 - 0x22];
thing_id_t head; // 0x26
uint32_t var_28;
uint16_t var_2C;
uint16_t var_2E;
int16_t tile_x; // 0x30
int16_t tile_y; // 0x32
uint8_t tile_base_z; // 0x34
uint8_t track_type; // 0x35 field same in all vehicles
uint16_t var_36; // 0x36 field same in all vehicles
uint8_t var_38;
uint8_t pad_39; // 0x39
thing_id_t next_car_id; // 0x3A
uint32_t var_3C; // 0x3C
uint8_t pad_40[0x2]; // 0x40
TransportMode mode; // 0x42 field same in all vehicles
uint8_t pad_43;
uint16_t var_44;
uint16_t var_46;
uint8_t var_48;
uint8_t pad_49[0x4E - 0x49];
uint16_t var_4E;
uint16_t var_50;
uint8_t var_52;
int32_t var_53;
};
static_assert(sizeof(vehicle_1) == 0x57); // Can't use offset_of change this to last field if more found
struct vehicle_2 : vehicle_base
{
static constexpr auto VehicleThingType = vehicle_thing_type::vehicle_2;
uint8_t pad_20;
company_id_t owner; // 0x21
uint8_t pad_22[0x26 - 0x22];
thing_id_t head; // 0x26
uint32_t var_28;
uint16_t var_2C;
uint16_t var_2E;
int16_t tile_x; // 0x30
int16_t tile_y; // 0x32
uint8_t tile_base_z; // 0x34
uint8_t track_type; // 0x35 field same in all vehicles
uint16_t var_36; // 0x36 field same in all vehicles
uint8_t var_38;
uint8_t pad_39; // 0x39
thing_id_t next_car_id; // 0x3A
uint8_t pad_3C[0x42 - 0x3C]; // 0x3C
TransportMode mode; // 0x42 field same in all vehicles
uint8_t pad_43;
uint8_t var_44;
uint8_t pad_45[0x48 - 0x45];
int16_t var_48;
uint16_t var_4A;
uint8_t pad_4C[0x56 - 0x4C];
uint32_t var_56;
uint8_t var_5A;
uint8_t var_5B;
uint8_t pad_5C[0x5E - 0x5C];
uint32_t var_5E;
uint32_t refund_cost;
uint32_t var_66;
uint32_t var_6A;
uint32_t var_6E;
uint8_t var_72;
uint8_t var_73; // 0x73 (bit 0 = broken down)
};
static_assert(sizeof(vehicle_2) == 0x74); // Can't use offset_of change this to last field if more found
struct vehicle_body : vehicle_base
{
uint8_t pad_20[0x38 - 0x20];
static constexpr auto VehicleThingType = vehicle_thing_type::vehicle_body_cont;
uint8_t pad_20;
company_id_t owner; // 0x21
uint8_t pad_22[0x24 - 0x22];
ColourScheme colour_scheme; // 0x24
thing_id_t head; // 0x26
uint8_t pad_28[0x2C - 0x28];
uint16_t var_2C;
uint16_t var_2E;
int16_t tile_x; // 0x30
int16_t tile_y; // 0x32
uint8_t tile_base_z; // 0x34
uint8_t track_type; // 0x35 field same in all vehicles
uint16_t var_36; // 0x36 field same in all vehicles
uint8_t var_38;
uint8_t object_sprite_type; // 0x39
thing_id_t next_car_id; // 0x3A
@ -157,11 +336,19 @@ namespace openloco
uint8_t pad_43;
int16_t var_44;
uint8_t var_46;
uint8_t pad_47[0x54 - 0x47];
uint8_t var_54;
uint8_t var_47;
uint32_t accepted_cargo_types; // 0x48
uint8_t cargo_type; // 0x4C
uint8_t max_cargo; // 0x4D
uint8_t pad_4E[0x51 - 0x4E];
uint8_t var_51;
uint8_t pad_52[0x54 - 0x52];
uint8_t body_index; // 0x54
int8_t var_55;
uint8_t pad_56[0x5E - 0x56];
uint32_t creation_day; // 0x56
uint8_t pad_5A[0x5E - 0x5A];
uint8_t var_5E;
uint8_t var_5F;
vehicle_object* object() const;
int32_t update();
@ -185,5 +372,81 @@ namespace openloco
uint8_t update_sprite_yaw_3(int16_t x_offset, int16_t y_offset);
uint8_t update_sprite_yaw_4(int16_t x_offset, int16_t y_offset);
};
static_assert(sizeof(vehicle_body) == 0x60); // Can't use offset_of change this to last field if more found
struct vehicle_bogie : vehicle_base
{
static constexpr auto VehicleThingType = vehicle_thing_type::vehicle_bogie;
uint8_t pad_20;
company_id_t owner; // 0x21
uint8_t pad_22[0x24 - 0x22];
ColourScheme colour_scheme; // 0x24
thing_id_t head; // 0x26
uint8_t pad_28[0x2C - 0x28];
uint16_t var_2C;
uint16_t var_2E;
int16_t tile_x; // 0x30
int16_t tile_y; // 0x32
uint8_t tile_base_z; // 0x34
uint8_t track_type; // 0x35 field same in all vehicles
uint16_t var_36; // 0x36 field same in all vehicles
uint8_t var_38;
uint8_t object_sprite_type; // 0x39
thing_id_t next_car_id; // 0x3A
uint8_t pad_3C[0x40 - 0x3C];
uint16_t object_id; // 0x40
TransportMode mode; // 0x42 field same in all vehicles
uint8_t pad_43;
uint16_t var_44;
uint8_t var_46;
uint8_t var_47;
uint32_t accepted_cargo_types; // 0x48 front car component front bogie only
uint8_t cargo_type; // 0x4C front car component front bogie only
uint8_t max_cargo; // 0x4D front car component front bogie only
uint8_t pad_4E[0x51 - 0x4E];
uint8_t var_51;
uint8_t pad_52[0x54 - 0x52];
uint8_t body_index; // 0x54
uint8_t pad_55;
uint32_t creation_day; // 0x56
uint8_t pad_5A[0x5E - 0x5A];
uint8_t var_5E;
uint8_t var_5F;
uint8_t var_60;
uint8_t var_61;
uint32_t refund_cost; // 0x62 front bogies only
uint16_t reliability; // 0x66 front bogies only
uint16_t var_68;
};
static_assert(sizeof(vehicle_bogie) == 0x6A); // Can't use offset_of change this to last field if more found
struct vehicle_tail : vehicle_base
{
static constexpr auto VehicleThingType = vehicle_thing_type::vehicle_6;
uint8_t pad_20;
company_id_t owner; // 0x21
uint8_t pad_22[0x26 - 0x22];
thing_id_t head; // 0x26
uint32_t var_28;
uint16_t var_2C;
uint16_t var_2E;
int16_t tile_x; // 0x30
int16_t tile_y; // 0x32
uint8_t tile_base_z; // 0x34
uint8_t track_type; // 0x35 field same in all vehicles
uint16_t var_36; // 0x36 field same in all vehicles
uint8_t var_38;
uint8_t pad_39; // 0x39
thing_id_t next_car_id; // 0x3A
uint8_t pad_3C[0x42 - 0x3C]; // 0x3C
TransportMode mode; // 0x42 field same in all vehicles
uint8_t pad_43;
uint8_t var_44;
uint8_t pad_45[0x48 - 0x45];
int16_t var_48;
uint16_t var_4A;
};
static_assert(sizeof(vehicle_tail) == 0x4C); // Can't use offset_of change this to last field if more found
#pragma pack(pop)
}

View File

@ -5,6 +5,7 @@
namespace openloco
{
using coord_t = int16_t;
using company_id_t = uint8_t;
using currency32_t = int32_t;
using station_id_t = uint16_t;
using industry_id_t = uint16_t;

View File

@ -281,7 +281,7 @@ namespace openloco::ui::build_vehicle
if (!tabMode)
{
auto veh = thingmgr::get<openloco::vehicle>(vehicle);
tab += veh->var_5E;
tab += static_cast<uint8_t>(veh->vehicleType);
}
else
{
@ -309,7 +309,7 @@ namespace openloco::ui::build_vehicle
{
_buildTargetVehicle = vehicle;
auto veh = thingmgr::get<openloco::vehicle>(vehicle);
window->current_tab = veh->var_5E;
window->current_tab = static_cast<uint8_t>(veh->vehicleType);
}
else
{
@ -391,19 +391,6 @@ namespace openloco::ui::build_vehicle
});
}
static bool sub_4B8FA2(openloco::vehicle* esi, uint32_t eax)
{
registers regs;
regs.eax = eax;
regs.esi = (uintptr_t)esi;
if (esi == nullptr)
{
regs.esi = -1;
}
return call(0x004B8FA2, regs) & (X86_FLAG_CARRY << 8);
}
/* 0x4B9165
* Works out which vehicles are able to be built for this vehicle_type or vehicle
*/
@ -443,7 +430,8 @@ namespace openloco::ui::build_vehicle
if (vehicle)
{
if (sub_4B8FA2(vehicle, vehicleObjIndex))
auto* const head = vehicle->as_vehicle_head();
if (head && !head->isVehicleTypeCompatible(vehicleObjIndex))
{
continue;
}
@ -943,14 +931,14 @@ namespace openloco::ui::build_vehicle
auto buffer = const_cast<char*>(stringmgr::get_string(string_ids::buffer_1250));
{
auto cost = (vehicleObj->cost_fact * currencyMultiplicationFactor[vehicleObj->cost_ind]) / 64;
auto cost = (vehicleObj->cost_factor * currencyMultiplicationFactor[vehicleObj->cost_index]) / 64;
FormatArguments args{};
args.push(cost);
buffer = stringmgr::format_string(buffer, string_ids::stats_cost, &args);
}
{
auto runningCost = (vehicleObj->run_cost_fact * currencyMultiplicationFactor[vehicleObj->run_cost_ind]) / 1024;
auto runningCost = (vehicleObj->run_cost_factor * currencyMultiplicationFactor[vehicleObj->run_cost_index]) / 1024;
FormatArguments args{};
args.push(runningCost);
buffer = stringmgr::format_string(buffer, string_ids::stats_running_cost, &args);

View File

@ -410,10 +410,10 @@ namespace openloco::ui::windows::toolbar_top::game
if (v->owner != player_company_id)
continue;
if ((v->var_38 & (1 << 4)) != 0)
if ((v->var_38 & things::vehicle::flags_38::unk_4) != 0)
continue;
vehicle_counts[v->var_5E]++;
vehicle_counts[static_cast<uint8_t>(v->vehicleType)]++;
}
uint8_t ddIndex = 0;

View File

@ -68,7 +68,7 @@ namespace openloco::ui::vehicle
auto vehicle = thingmgr::get<openloco::vehicle>(w->number);
if (vehicle->tile_x != -1 && (vehicle->var_38 & (1 << 4)) == 0)
if (vehicle->tile_x != -1 && (vehicle->var_38 & things::vehicle::flags_38::unk_4) == 0)
{
return;
}
@ -119,7 +119,7 @@ namespace openloco::ui::vehicle
}
auto vehicle = thingmgr::get<openloco::vehicle>(w->number);
if (vehicle->tile_x != -1 && (vehicle->var_38 & (1 << 4)) == 0)
if (vehicle->tile_x != -1 && (vehicle->var_38 & things::vehicle::flags_38::unk_4) == 0)
return;
if (!WindowManager::isInFrontAlt(w))