Implement most of loading S5
This commit is contained in:
parent
1a3e1902f4
commit
00cdd64fef
|
@ -1131,6 +1131,14 @@ namespace OpenLoco::Audio
|
|||
}
|
||||
}
|
||||
|
||||
// 0x0048AAD2
|
||||
void resetMusic()
|
||||
{
|
||||
stopBackgroundMusic();
|
||||
_currentSong = no_song;
|
||||
_lastSong = no_song;
|
||||
}
|
||||
|
||||
// 0x0048AAE8
|
||||
void stopBackgroundMusic()
|
||||
{
|
||||
|
|
|
@ -120,6 +120,7 @@ namespace OpenLoco::Audio
|
|||
|
||||
void revalidateCurrentTrack();
|
||||
|
||||
void resetMusic();
|
||||
void playBackgroundMusic();
|
||||
void stopBackgroundMusic();
|
||||
void playTitleScreenMusic();
|
||||
|
|
|
@ -15,6 +15,11 @@ namespace OpenLoco
|
|||
{
|
||||
static loco_global<CompanyId_t[2], 0x00525E3C> _playerCompanies;
|
||||
|
||||
void setPlayerCompany(CompanyId_t id)
|
||||
{
|
||||
_playerCompanies[0] = id;
|
||||
}
|
||||
|
||||
bool isPlayerCompany(CompanyId_t id)
|
||||
{
|
||||
auto findResult = std::find(
|
||||
|
|
|
@ -127,6 +127,7 @@ namespace OpenLoco
|
|||
static_assert(offsetof(Company, var_8BB0) == 0x8BB0);
|
||||
|
||||
bool isPlayerCompany(CompanyId_t id);
|
||||
void setPlayerCompany(CompanyId_t id);
|
||||
constexpr CorporateRating performanceToRating(int16_t performanceIndex);
|
||||
void formatPerformanceIndex(const int16_t performanceIndex, FormatArguments& args);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ namespace OpenLoco::CompanyManager
|
|||
static loco_global<Company[max_companies], 0x00531784> _companies;
|
||||
static loco_global<uint8_t[max_companies + 1], 0x009C645C> _company_colours;
|
||||
static loco_global<CompanyId_t, 0x009C68EB> _updating_company_id;
|
||||
static loco_global<uint8_t, 0x009C646B> _byte_9C646B;
|
||||
|
||||
static void produceCompanies();
|
||||
|
||||
|
@ -334,4 +335,21 @@ namespace OpenLoco::CompanyManager
|
|||
company->cash -= cost;
|
||||
company->expenditures[0][static_cast<uint8_t>(type)] -= payment;
|
||||
}
|
||||
|
||||
// 0x004302EF
|
||||
void updateColours()
|
||||
{
|
||||
size_t index = 0;
|
||||
for (auto& company : companies())
|
||||
{
|
||||
_company_colours[index] = company.mainColours.primary;
|
||||
index++;
|
||||
}
|
||||
_byte_9C646B = 1;
|
||||
}
|
||||
|
||||
void set_525E3D(uint8_t value)
|
||||
{
|
||||
_byte_525E3D = value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,9 @@ namespace OpenLoco::CompanyManager
|
|||
string_id getOwnerStatus(CompanyId_t id, FormatArguments& args);
|
||||
OwnerStatus getOwnerStatus(CompanyId_t id);
|
||||
void updateOwnerStatus();
|
||||
void updateColours();
|
||||
|
||||
void spendMoneyEffect(const Map::Pos3& loc, const CompanyId_t company, const currency32_t amount);
|
||||
void applyPaymentToCompany(const CompanyId_t id, const currency32_t payment, const ExpenditureType type);
|
||||
void set_525E3D(uint8_t value);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,11 @@
|
|||
using namespace OpenLoco;
|
||||
using namespace OpenLoco::Interop;
|
||||
|
||||
bool EntityBase::isEmpty() const
|
||||
{
|
||||
return base_type == EntityBaseType::null;
|
||||
}
|
||||
|
||||
// 0x0046FC83
|
||||
void EntityBase::moveTo(const Map::Pos3& loc)
|
||||
{
|
||||
|
|
|
@ -79,6 +79,7 @@ namespace OpenLoco
|
|||
|
||||
Vehicles::VehicleBase* asVehicle() const { return asBase<Vehicles::VehicleBase, EntityBaseType::vehicle>(); }
|
||||
MiscBase* asMisc() const { return asBase<MiscBase, EntityBaseType::misc>(); }
|
||||
bool isEmpty() const;
|
||||
|
||||
protected:
|
||||
constexpr uint8_t getSubType() const { return type; }
|
||||
|
|
|
@ -143,6 +143,18 @@ namespace OpenLoco::EntityManager
|
|||
call(0x0046FF54);
|
||||
}
|
||||
|
||||
// 0x0046FC57
|
||||
void updateSpatialIndex()
|
||||
{
|
||||
for (auto& ent : _entities)
|
||||
{
|
||||
if (!ent.isEmpty())
|
||||
{
|
||||
ent.moveTo({ ent.x, ent.y, ent.z });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static EntityBase* createEntity(EntityId_t id, EntityListType list)
|
||||
{
|
||||
auto* newEntity = get<EntityBase>(id);
|
||||
|
|
|
@ -51,6 +51,7 @@ namespace OpenLoco::EntityManager
|
|||
|
||||
EntityId_t firstQuadrantId(const Map::Pos2& loc);
|
||||
void resetSpatialIndex();
|
||||
void updateSpatialIndex();
|
||||
|
||||
EntityBase* createEntityMisc();
|
||||
EntityBase* createEntityMoney();
|
||||
|
|
|
@ -13,6 +13,18 @@ using namespace OpenLoco::Map;
|
|||
|
||||
namespace OpenLoco
|
||||
{
|
||||
static const map_pos3 word_4F9274[] = {
|
||||
{ 0, 0, 0 },
|
||||
{ Location::null, 0, 0 }
|
||||
};
|
||||
static const map_pos3 word_4F927C[] = {
|
||||
{ 0, 0, 0 },
|
||||
{ 0, 32, 1 },
|
||||
{ 32, 32, 2 },
|
||||
{ 32, 0, 3 },
|
||||
{ Location::null, 0, 0 }
|
||||
};
|
||||
|
||||
IndustryId_t Industry::id() const
|
||||
{
|
||||
auto first = (Industry*)0x005C455C;
|
||||
|
@ -213,4 +225,40 @@ namespace OpenLoco
|
|||
regs.dh = id();
|
||||
call(0x00454A43, regs);
|
||||
}
|
||||
|
||||
// 0x00459D43
|
||||
void Industry::createMapAnimations()
|
||||
{
|
||||
for (size_t i = 0; i < numTiles; i++)
|
||||
{
|
||||
auto& tilePos = tiles[i];
|
||||
auto baseZ = (tilePos.z & ~Location::null) / 4;
|
||||
auto tile = TileManager::get(tilePos.x, tilePos.y);
|
||||
for (auto& el : tile)
|
||||
{
|
||||
auto industryEl = el.asIndustry();
|
||||
if (industryEl != nullptr && industryEl->baseZ() == baseZ)
|
||||
{
|
||||
auto tileIndustry = industryEl->industry();
|
||||
if (tileIndustry != nullptr)
|
||||
{
|
||||
auto industryObject = tileIndustry->object();
|
||||
if (industryObject != nullptr)
|
||||
{
|
||||
auto offsets = word_4F9274;
|
||||
if (industryObject->var_C6 & (1 << industryEl->var_6_1F()))
|
||||
{
|
||||
offsets = word_4F927C;
|
||||
}
|
||||
while (offsets[0].x != Location::null)
|
||||
{
|
||||
TileManager::createAnimation(3, tilePos.x + offsets->x, tilePos.y + offsets->y, baseZ);
|
||||
offsets++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,9 @@ namespace OpenLoco
|
|||
Utility::prng prng; // 0x08
|
||||
uint8_t object_id; // 0x10
|
||||
uint8_t under_construction; // 0x11 (0xFF = Finished)
|
||||
uint8_t pad_12[0xD5 - 0x12];
|
||||
uint16_t pad_12;
|
||||
uint8_t numTiles; // 0x14
|
||||
Map::Pos3 tiles[32]; // 0x15
|
||||
TownId_t town; // 0xD5
|
||||
Map::TileLoop tile_loop; // 0xD7
|
||||
int16_t var_DB;
|
||||
|
@ -65,7 +67,9 @@ namespace OpenLoco
|
|||
void sub_45329B(const Map::Pos2& pos);
|
||||
void sub_453354();
|
||||
void sub_454A43(const Map::Pos2& pos, uint8_t bl, uint8_t bh, uint8_t dl);
|
||||
void createMapAnimations();
|
||||
};
|
||||
static_assert(sizeof(industry) == 0x453);
|
||||
#pragma pack(pop)
|
||||
|
||||
static_assert(sizeof(Industry) == 0x453);
|
||||
|
|
|
@ -52,4 +52,18 @@ namespace OpenLoco::IndustryManager
|
|||
call(0x0045383B);
|
||||
}
|
||||
|
||||
// 0x00459D2D
|
||||
void createAllMapAnimations()
|
||||
{
|
||||
if (!(addr<0x00525E28, uint32_t>() & (1 << 0)))
|
||||
return;
|
||||
|
||||
for (auto& industry : industries())
|
||||
{
|
||||
if (!industry.empty())
|
||||
{
|
||||
industry.createMapAnimations();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,4 +13,5 @@ namespace OpenLoco::IndustryManager
|
|||
Industry* get(IndustryId_t id);
|
||||
void update();
|
||||
void updateMonthly();
|
||||
void createAllMapAnimations();
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "../Gui.h"
|
||||
#include "../Input.h"
|
||||
#include "../Map/Tile.h"
|
||||
#include "../Map/TileManager.h"
|
||||
#include "../Paint/Paint.h"
|
||||
#include "../Platform/Platform.h"
|
||||
#include "../S5/S5.h"
|
||||
|
@ -765,6 +766,7 @@ void OpenLoco::Interop::registerHooks()
|
|||
});
|
||||
|
||||
Ui::ProgressBar::registerHooks();
|
||||
OpenLoco::Map::TileManager::registerHooks();
|
||||
Ui::Windows::PromptBrowse::registerHooks();
|
||||
Ui::Windows::TextInput::registerHooks();
|
||||
Ui::Windows::ToolTip::registerHooks();
|
||||
|
|
|
@ -630,6 +630,9 @@ namespace OpenLoco::StringIds
|
|||
constexpr string_id sandy_track_blues = 1080;
|
||||
constexpr string_id sandy_track_blues_credit = 1081;
|
||||
constexpr string_id error_unable_to_load_saved_game = 1082;
|
||||
constexpr string_id error_file_contains_invalid_data = 1083;
|
||||
constexpr string_id error_file_is_not_single_player_save = 1084;
|
||||
constexpr string_id error_file_is_not_two_player_save = 1085;
|
||||
|
||||
constexpr string_id loading = 1088;
|
||||
|
||||
|
@ -1497,6 +1500,7 @@ namespace OpenLoco::StringIds
|
|||
constexpr string_id object_company_owners = 2085;
|
||||
constexpr string_id object_scenario_descriptions = 2086;
|
||||
constexpr string_id tooltip_object_list = 2087;
|
||||
constexpr string_id missing_object_data_id_x = 2088;
|
||||
|
||||
constexpr string_id export_plugin_objects = 2089;
|
||||
constexpr string_id export_plugin_objects_tip = 2090;
|
||||
|
@ -1505,6 +1509,8 @@ namespace OpenLoco::StringIds
|
|||
constexpr string_id object_selection_advanced_tooltip = 2094;
|
||||
constexpr string_id object_currency_big_font = 2095;
|
||||
|
||||
constexpr string_id new_objects_installed_successfully = 2096;
|
||||
|
||||
constexpr string_id unit_mph = 2113;
|
||||
constexpr string_id unit_kmh = 2114;
|
||||
constexpr string_id unit_hour = 2115;
|
||||
|
|
|
@ -177,6 +177,11 @@ OpenLoco::Industry* IndustryElement::industry() const
|
|||
|
||||
OpenLoco::StationType StationElement::stationType() const { return OpenLoco::StationType(_5 >> 5); }
|
||||
|
||||
uint8_t IndustryElement::var_6_1F() const
|
||||
{
|
||||
return (_6 >> 6) & 0x1F;
|
||||
}
|
||||
|
||||
namespace OpenLoco::Map
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -81,6 +81,7 @@ namespace OpenLoco::Map
|
|||
uint8_t baseZ() const { return _base_z; }
|
||||
uint8_t clearZ() const { return _clear_z; }
|
||||
|
||||
uint8_t direction() const { return _type & 0x03; }
|
||||
bool hasHighTypeFlag() const { return _type & 0x80; }
|
||||
void setHighTypeFlag(bool state)
|
||||
{
|
||||
|
@ -94,6 +95,7 @@ namespace OpenLoco::Map
|
|||
_flags &= ~ElementFlags::flag_6;
|
||||
_flags |= state == true ? ElementFlags::flag_6 : 0;
|
||||
}
|
||||
void setClearZ(uint8_t value) { _clear_z = value; }
|
||||
bool isLast() const;
|
||||
|
||||
std::array<uint8_t, 8>& rawData()
|
||||
|
@ -301,12 +303,12 @@ namespace OpenLoco::Map
|
|||
private:
|
||||
IndustryId_t _industryId;
|
||||
uint8_t _5;
|
||||
uint8_t _6;
|
||||
uint8_t _7;
|
||||
uint16_t _6;
|
||||
|
||||
public:
|
||||
OpenLoco::IndustryId_t industryId() const { return _industryId; }
|
||||
OpenLoco::Industry* industry() const;
|
||||
uint8_t var_6_1F() const;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
|
|
|
@ -8,6 +8,20 @@ using namespace OpenLoco::Interop;
|
|||
|
||||
namespace OpenLoco::Map::TileManager
|
||||
{
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct TileAnimation
|
||||
{
|
||||
uint8_t baseZ;
|
||||
uint8_t type;
|
||||
coord_t x;
|
||||
coord_t y;
|
||||
};
|
||||
static_assert(sizeof(TileAnimation) == 6);
|
||||
#pragma pack(pop)
|
||||
|
||||
constexpr size_t maxAnimations = 0x2000;
|
||||
|
||||
static loco_global<TileElement*, 0x005230C8> _elements;
|
||||
static loco_global<TileElement* [0x30004], 0x00E40134> _tiles;
|
||||
static loco_global<TileElement*, 0x00F00134> _elementsEnd;
|
||||
|
@ -18,6 +32,8 @@ namespace OpenLoco::Map::TileManager
|
|||
static loco_global<coord_t, 0x00F2448C> _mapSelectionBY;
|
||||
static loco_global<uint16_t, 0x00F2448E> _word_F2448E;
|
||||
static loco_global<int16_t, 0x0050A000> _adjustToolSize;
|
||||
static loco_global<uint16_t, 0x00525F6C> _numAnimations;
|
||||
static loco_global<TileAnimation[maxAnimations], 0x0094C6DC> _animations;
|
||||
|
||||
constexpr uint16_t mapSelectedTilesSize = 300;
|
||||
static loco_global<Pos2[mapSelectedTilesSize], 0x00F24490> _mapSelectedTiles;
|
||||
|
@ -40,6 +56,14 @@ namespace OpenLoco::Map::TileManager
|
|||
return _elementsEnd;
|
||||
}
|
||||
|
||||
void setElements(stdx::span<TileElement> elements)
|
||||
{
|
||||
TileElement* dst = _elements;
|
||||
std::memset(dst, 0, maxElements * sizeof(TileElement));
|
||||
std::memcpy(dst, elements.data(), elements.size_bytes());
|
||||
TileManager::updateTilePointers();
|
||||
}
|
||||
|
||||
TileElement** getElementIndex()
|
||||
{
|
||||
return _tiles.get();
|
||||
|
@ -482,4 +506,53 @@ namespace OpenLoco::Map::TileManager
|
|||
mapInvalidateTileFull(position);
|
||||
}
|
||||
}
|
||||
|
||||
// 0x0046A747
|
||||
void resetSurfaceClearance()
|
||||
{
|
||||
for (coord_t y = 0; y < map_height; y += tile_size)
|
||||
{
|
||||
for (coord_t x = 0; x < map_width; x += tile_size)
|
||||
{
|
||||
auto tile = get(x, y);
|
||||
auto surface = tile.surface();
|
||||
if (surface != nullptr && surface->slope() == 0)
|
||||
{
|
||||
surface->setClearZ(surface->baseZ());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 0x004612A6
|
||||
void createAnimation(uint8_t type, coord_t x, coord_t y, tile_coord_t baseZ)
|
||||
{
|
||||
if (_numAnimations >= maxAnimations)
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < _numAnimations; i++)
|
||||
{
|
||||
auto& animation = _animations[i];
|
||||
if (animation.type == type && animation.x == x && animation.y == y && animation.baseZ == baseZ)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto& newAnimation = _animations[_numAnimations++];
|
||||
newAnimation.baseZ = baseZ;
|
||||
newAnimation.type = type;
|
||||
newAnimation.x = x;
|
||||
newAnimation.y = y;
|
||||
}
|
||||
|
||||
void registerHooks()
|
||||
{
|
||||
registerHook(
|
||||
0x004612A6,
|
||||
[](registers& regs) -> uint8_t {
|
||||
createAnimation(regs.dh, regs.ax, regs.cx, regs.dl);
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ namespace OpenLoco::Map::TileManager
|
|||
Tile get(TilePos2 pos);
|
||||
Tile get(Pos2 pos);
|
||||
Tile get(coord_t x, coord_t y);
|
||||
void setElements(stdx::span<TileElement> elements);
|
||||
TileHeight getHeight(const Pos2& pos);
|
||||
void updateTilePointers();
|
||||
void reorganise();
|
||||
|
@ -32,4 +33,7 @@ namespace OpenLoco::Map::TileManager
|
|||
void mapInvalidateSelectionRect();
|
||||
void mapInvalidateTileFull(Map::Pos2 pos);
|
||||
void mapInvalidateMapSelectionTiles();
|
||||
void resetSurfaceClearance();
|
||||
void createAnimation(uint8_t type, coord_t x, coord_t y, tile_coord_t baseZ);
|
||||
void registerHooks();
|
||||
}
|
||||
|
|
|
@ -23,7 +23,8 @@ namespace OpenLoco
|
|||
uint8_t pad_02[0x0A - 0x02];
|
||||
uint16_t nameSingular; // 0x0A
|
||||
uint16_t namePlural; // 0x0C
|
||||
uint8_t pad_0E[0xCA - 0x0E];
|
||||
uint8_t pad_0E[0xC6 - 0x0E];
|
||||
uint32_t var_C6;
|
||||
uint16_t designedYear; // 0xCA start year
|
||||
uint16_t obsoleteYear; // 0xCC end year
|
||||
uint8_t var_CE;
|
||||
|
@ -50,5 +51,6 @@ namespace OpenLoco
|
|||
void drawPreviewImage(Gfx::Context& context, const int16_t x, const int16_t y) const;
|
||||
void drawIndustry(Gfx::Context* clipped, int16_t x, int16_t y) const;
|
||||
};
|
||||
static_assert(sizeof(industry_object) == 0xF1);
|
||||
#pragma pack(pop)
|
||||
}
|
||||
|
|
|
@ -492,6 +492,60 @@ namespace OpenLoco::ObjectManager
|
|||
throw std::runtime_error("Object not loaded at this index");
|
||||
}
|
||||
|
||||
// 0x00471BC5
|
||||
static bool load(const ObjectHeader& header, LoadedObjectId id)
|
||||
{
|
||||
registers regs;
|
||||
regs.ebp = reinterpret_cast<uint32_t>(&header);
|
||||
regs.ecx = static_cast<int32_t>(id);
|
||||
return (call(0x00471BC5, regs) & X86_FLAG_CARRY) == 0;
|
||||
}
|
||||
|
||||
static LoadedObjectId getObjectId(LoadedObjectIndex index)
|
||||
{
|
||||
size_t objectType = 0;
|
||||
while (objectType < maxObjectTypes)
|
||||
{
|
||||
auto count = getMaxObjects(static_cast<object_type>(objectType));
|
||||
if (index < count)
|
||||
{
|
||||
return static_cast<LoadedObjectId>(index);
|
||||
}
|
||||
index -= count;
|
||||
objectType++;
|
||||
}
|
||||
return std::numeric_limits<LoadedObjectId>::max();
|
||||
}
|
||||
|
||||
LoadObjectsResult loadAll(stdx::span<ObjectHeader> objects)
|
||||
{
|
||||
LoadObjectsResult result;
|
||||
result.Success = true;
|
||||
|
||||
unloadAll();
|
||||
|
||||
LoadedObjectIndex index = 0;
|
||||
for (const auto& header : objects)
|
||||
{
|
||||
auto id = getObjectId(index);
|
||||
if (!load(header, id))
|
||||
{
|
||||
result.Success = false;
|
||||
result.ProblemObject = header;
|
||||
unloadAll();
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 0x00472031
|
||||
void unloadAll()
|
||||
{
|
||||
call(0x00472031);
|
||||
}
|
||||
|
||||
void unload(LoadedObjectIndex index)
|
||||
{
|
||||
callObjectFunction(index, ObjectProcedure::unload);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "../Core/Optional.hpp"
|
||||
|
||||
#include "../Core/Span.hpp"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
@ -141,6 +141,11 @@ namespace OpenLoco
|
|||
* a specific object type.
|
||||
*/
|
||||
using LoadedObjectIndex = size_t;
|
||||
|
||||
/**
|
||||
* Represents an index / ID of a specific object type.
|
||||
*/
|
||||
using LoadedObjectId = size_t;
|
||||
#pragma pack(pop)
|
||||
}
|
||||
|
||||
|
@ -150,6 +155,7 @@ namespace OpenLoco::ObjectManager
|
|||
|
||||
constexpr size_t getMaxObjects(object_type type)
|
||||
{
|
||||
// 0x004FE250
|
||||
constexpr size_t counts[] = {
|
||||
1, // interface,
|
||||
128, // sound,
|
||||
|
@ -275,6 +281,12 @@ namespace OpenLoco::ObjectManager
|
|||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct LoadObjectsResult
|
||||
{
|
||||
bool Success{};
|
||||
ObjectHeader ProblemObject;
|
||||
};
|
||||
|
||||
uint32_t getNumInstalledObjects();
|
||||
std::vector<std::pair<uint32_t, object_index_entry>> getAvailableObjects(object_type type);
|
||||
void freeScenarioText();
|
||||
|
@ -286,6 +298,9 @@ namespace OpenLoco::ObjectManager
|
|||
ObjectHeader* getHeader(LoadedObjectIndex id);
|
||||
std::vector<ObjectHeader> getHeaders();
|
||||
|
||||
LoadObjectsResult loadAll(stdx::span<ObjectHeader> objects);
|
||||
|
||||
void unloadAll();
|
||||
void unload(LoadedObjectIndex index);
|
||||
|
||||
size_t getByteLength(LoadedObjectIndex id);
|
||||
|
|
|
@ -449,6 +449,12 @@ namespace OpenLoco
|
|||
call(0x00428E47);
|
||||
}
|
||||
|
||||
// 0x00444387
|
||||
void sub_444387()
|
||||
{
|
||||
call(0x00444387);
|
||||
}
|
||||
|
||||
// 0x0046E388
|
||||
static void sub_46E388()
|
||||
{
|
||||
|
|
|
@ -67,4 +67,6 @@ namespace OpenLoco
|
|||
void promptTickLoop(std::function<bool()> tickAction);
|
||||
[[noreturn]] void exitCleanly();
|
||||
void exitWithError(OpenLoco::string_id message, uint32_t errorCode);
|
||||
|
||||
void sub_444387();
|
||||
}
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
#define DO_TITLE_SEQUENCE_CHECKS
|
||||
|
||||
#include "S5.h"
|
||||
#include "../Audio/Audio.h"
|
||||
#include "../CompanyManager.h"
|
||||
#include "../Entities/EntityManager.h"
|
||||
#include "../Gui.h"
|
||||
#include "../IndustryManager.h"
|
||||
#include "../Interop/Interop.hpp"
|
||||
#include "../Localisation/StringManager.h"
|
||||
#include "../Map/TileManager.h"
|
||||
#include "../Objects/ObjectManager.h"
|
||||
#include "../StationManager.h"
|
||||
#include "../TownManager.h"
|
||||
#include "../Ui/WindowManager.h"
|
||||
#include "../Utility/Exception.hpp"
|
||||
#include "../Vehicles/Orders.h"
|
||||
|
@ -26,6 +33,9 @@ namespace OpenLoco::S5
|
|||
static loco_global<Header, 0x009CCA34> _header;
|
||||
static loco_global<Options, 0x009CCA54> _previewOptions;
|
||||
static loco_global<char[512], 0x0112CE04> _savePath;
|
||||
static loco_global<uint8_t, 0x00508F1A> _gameSpeed;
|
||||
static loco_global<uint8_t, 0x0050C197> _loadErrorCode;
|
||||
static loco_global<string_id, 0x0050C198> _loadErrorMessage;
|
||||
|
||||
static bool save(const fs::path& path, const S5File& file, const std::vector<ObjectHeader>& packedObjects);
|
||||
|
||||
|
@ -362,6 +372,290 @@ namespace OpenLoco::S5
|
|||
}
|
||||
}
|
||||
|
||||
// 0x00445A4A
|
||||
static void fixState(GameState& state)
|
||||
{
|
||||
if (state.fixFlags & S5FixFlags::fixFlag0)
|
||||
{
|
||||
state.fixFlags |= S5FixFlags::fixFlag1;
|
||||
}
|
||||
if (!(state.fixFlags & S5FixFlags::fixFlag1))
|
||||
{
|
||||
// Shift data after companies to correct location
|
||||
auto src = reinterpret_cast<uint8_t*>(&state) + 0x49EA24;
|
||||
auto dst = src + 0x1C20;
|
||||
for (size_t i = 0; i < 0x40E200; i++)
|
||||
{
|
||||
*--dst = *--src;
|
||||
}
|
||||
|
||||
// Convert each company format from old to new
|
||||
for (size_t i = 0; i < std::size(state.companies); i++)
|
||||
{
|
||||
for (size_t j = 0; j < 372; j++)
|
||||
{
|
||||
*--dst = *--src;
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < 480; j++)
|
||||
{
|
||||
*--dst = 0;
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < 35924; j++)
|
||||
{
|
||||
*--dst = *--src;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::unique_ptr<S5File> load(const fs::path& path)
|
||||
{
|
||||
SawyerStreamReader fs(path);
|
||||
if (!fs.validateChecksum())
|
||||
{
|
||||
throw std::runtime_error("Invalid checksum");
|
||||
}
|
||||
|
||||
auto file = std::make_unique<S5File>();
|
||||
|
||||
// Read header
|
||||
fs.readChunk(&file->header, sizeof(file->header));
|
||||
|
||||
// Read saved details
|
||||
if (file->header.flags & S5Flags::hasSaveDetails)
|
||||
{
|
||||
file->saveDetails = std::make_unique<SaveDetails>();
|
||||
fs.readChunk(file->saveDetails.get(), sizeof(file->saveDetails));
|
||||
}
|
||||
|
||||
// Read packed objects
|
||||
if (file->header.numPackedObjects > 0)
|
||||
{
|
||||
}
|
||||
|
||||
if (file->header.type == S5Type::objects)
|
||||
{
|
||||
// new_objects_installed_successfully
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load required objects
|
||||
fs.readChunk(file->requiredObjects, sizeof(file->requiredObjects));
|
||||
|
||||
// Load game state
|
||||
fs.readChunk(&file->gameState, sizeof(file->gameState));
|
||||
fixState(file->gameState);
|
||||
|
||||
// Load tile elements
|
||||
auto tileElements = fs.readChunk();
|
||||
auto numTileElements = tileElements.size() / sizeof(TileElement);
|
||||
file->tileElements.resize(numTileElements);
|
||||
std::memcpy(file->tileElements.data(), tileElements.data(), numTileElements * sizeof(TileElement));
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
// 0x00473BC7
|
||||
static void object_create_identifier_name(char* dst, const ObjectHeader& header)
|
||||
{
|
||||
registers regs;
|
||||
regs.edi = reinterpret_cast<int32_t>(dst);
|
||||
regs.ebp = reinterpret_cast<int32_t>(&header);
|
||||
call(0x00473BC7, regs);
|
||||
}
|
||||
|
||||
// 0x00444D76
|
||||
static void setObjectErrorMessage(const ObjectHeader& header)
|
||||
{
|
||||
auto buffer = const_cast<char*>(StringManager::getString(StringIds::buffer_2040));
|
||||
StringManager::formatString(buffer, sizeof(buffer), StringIds::missing_object_data_id_x);
|
||||
object_create_identifier_name(strchr(buffer, 0), header);
|
||||
_loadErrorCode = 255;
|
||||
_loadErrorMessage = StringIds::buffer_2040;
|
||||
}
|
||||
|
||||
class LoadException : public std::runtime_error
|
||||
{
|
||||
private:
|
||||
string_id _localisedMessage;
|
||||
|
||||
public:
|
||||
LoadException(const char* message, string_id localisedMessage)
|
||||
: std::runtime_error(message)
|
||||
, _localisedMessage(localisedMessage)
|
||||
{
|
||||
}
|
||||
|
||||
string_id getLocalisedMessage() const
|
||||
{
|
||||
return _localisedMessage;
|
||||
}
|
||||
};
|
||||
|
||||
static void sub_42F7F8()
|
||||
{
|
||||
call(0x0042F7F8);
|
||||
}
|
||||
|
||||
static void sub_4BAEC4()
|
||||
{
|
||||
addr<0x001136496, uint8_t>() = 2;
|
||||
addr<0x00525FB1, uint8_t>() = 255;
|
||||
addr<0x00525FCA, uint8_t>() = 255;
|
||||
}
|
||||
|
||||
// 0x00441FA7
|
||||
bool load(const fs::path& path, LoadFlags flags)
|
||||
{
|
||||
_gameSpeed = 0;
|
||||
if (!(flags & LoadFlags::titleSequence) && !(flags & LoadFlags::twoPlayer))
|
||||
{
|
||||
WindowManager::closeConstructionWindows();
|
||||
WindowManager::closeAllFloatingWindows();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto file = load(path);
|
||||
|
||||
if (file->header.version != currentVersion)
|
||||
{
|
||||
throw LoadException("Unsupported S5 version", StringIds::error_file_contains_invalid_data);
|
||||
}
|
||||
|
||||
#ifdef DO_TITLE_SEQUENCE_CHECKS
|
||||
if (flags & LoadFlags::titleSequence)
|
||||
{
|
||||
if (!(file->header.flags & S5Flags::isTitleSequence))
|
||||
{
|
||||
throw LoadException("File was not a title sequence", StringIds::error_file_contains_invalid_data);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (file->header.flags & S5Flags::isTitleSequence)
|
||||
{
|
||||
throw LoadException("File is a title sequence", StringIds::error_file_contains_invalid_data);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (file->header.type == S5Type::scenario)
|
||||
{
|
||||
throw LoadException("File is a scenario, not a saved game", StringIds::error_file_contains_invalid_data);
|
||||
}
|
||||
|
||||
if ((file->header.flags & S5Flags::isRaw) || (file->header.flags & S5Flags::isDump))
|
||||
{
|
||||
throw LoadException("Unsupported S5 format", StringIds::error_file_contains_invalid_data);
|
||||
}
|
||||
|
||||
if (flags & LoadFlags::twoPlayer)
|
||||
{
|
||||
if (file->header.type != S5Type::landscape)
|
||||
{
|
||||
throw LoadException("Not a two player saved game", StringIds::error_file_is_not_two_player_save);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (file->header.type != S5Type::savedGame)
|
||||
{
|
||||
throw LoadException("Not a single player saved game", StringIds::error_file_is_not_single_player_save);
|
||||
}
|
||||
}
|
||||
|
||||
auto loadObjectResult = ObjectManager::loadAll(file->requiredObjects);
|
||||
if (!loadObjectResult.Success)
|
||||
{
|
||||
setObjectErrorMessage(loadObjectResult.ProblemObject);
|
||||
if (flags & LoadFlags::twoPlayer)
|
||||
{
|
||||
sub_42F7F8();
|
||||
addr<0x00525F62, uint16_t>() = 0;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
call(0x0043C0FD); // Important, should implement this
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ObjectManager::reloadAll();
|
||||
|
||||
_gameState = file->gameState;
|
||||
TileManager::setElements(stdx::span<tile_element>(reinterpret_cast<tile_element*>(file->tileElements.data()), file->tileElements.size()));
|
||||
|
||||
call(0x0046FF54);
|
||||
CompanyManager::updateColours();
|
||||
call(0x004748FA);
|
||||
TileManager::resetSurfaceClearance();
|
||||
IndustryManager::createAllMapAnimations();
|
||||
|
||||
if (!(flags & LoadFlags::titleSequence))
|
||||
{
|
||||
clearScreenFlag(ScreenFlags::title);
|
||||
initialiseViewports();
|
||||
Gui::init();
|
||||
Audio::resetMusic();
|
||||
}
|
||||
|
||||
auto mainWindow = WindowManager::getMainWindow();
|
||||
if (mainWindow != nullptr)
|
||||
{
|
||||
SavedViewSimple savedView;
|
||||
savedView.mapX = file->gameState.savedViewX;
|
||||
savedView.mapY = file->gameState.savedViewY;
|
||||
savedView.zoomLevel = static_cast<ZoomLevel>(file->gameState.savedViewZoom);
|
||||
savedView.rotation = file->gameState.savedViewRotation;
|
||||
mainWindow->viewportFromSavedView(savedView);
|
||||
}
|
||||
|
||||
ThingManager::updateSpatialIndex();
|
||||
TownManager::updateLabels();
|
||||
StationManager::updateLabels();
|
||||
sub_4BAEC4();
|
||||
addr<0x0052334E, uint16_t>() = 0;
|
||||
Gfx::invalidateScreen();
|
||||
call(0x004C153B);
|
||||
call(0x0046E07B);
|
||||
addr<0x00525F62, uint16_t>() = 0;
|
||||
|
||||
if (flags & LoadFlags::titleSequence)
|
||||
{
|
||||
addr<0x00525F5E, uint32_t>()--;
|
||||
addr<0x00525F64, uint32_t>()--;
|
||||
addr<0x0050BF6C, uint8_t>() = 1;
|
||||
}
|
||||
|
||||
if (!(flags & LoadFlags::titleSequence) && !(flags & LoadFlags::twoPlayer))
|
||||
{
|
||||
resetScreenAge();
|
||||
// longjmp
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (const LoadException& e)
|
||||
{
|
||||
std::fprintf(stderr, "Unable to load S5: %s\n", e.what());
|
||||
_loadErrorCode = 255;
|
||||
_loadErrorMessage = e.getLocalisedMessage();
|
||||
return false;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
std::fprintf(stderr, "Unable to load S5: %s\n", e.what());
|
||||
_loadErrorCode = 255;
|
||||
_loadErrorMessage = StringIds::null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void registerHooks()
|
||||
{
|
||||
registerHook(
|
||||
|
@ -370,5 +664,11 @@ namespace OpenLoco::S5
|
|||
auto path = fs::u8path(std::string(_savePath));
|
||||
return save(path, static_cast<SaveFlags>(regs.eax)) ? 0 : X86_FLAG_CARRY;
|
||||
});
|
||||
registerHook(
|
||||
0x00441FA7,
|
||||
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
||||
auto path = fs::u8path(std::string_view(_savePath));
|
||||
return load(path, static_cast<LoadFlags>(regs.eax)) ? 0x100 : 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace OpenLoco::S5
|
|||
{
|
||||
savedGame = 0,
|
||||
scenario = 1,
|
||||
objects = 2,
|
||||
landscape = 3,
|
||||
};
|
||||
|
||||
|
@ -18,6 +19,7 @@ namespace OpenLoco::S5
|
|||
{
|
||||
isRaw = 1 << 0,
|
||||
isDump = 1 << 1,
|
||||
isTitleSequence = 1 << 2,
|
||||
hasSaveDetails = 1 << 3,
|
||||
};
|
||||
|
||||
|
@ -216,6 +218,12 @@ namespace OpenLoco::S5
|
|||
};
|
||||
static_assert(sizeof(TileElement) == 8);
|
||||
|
||||
namespace S5FixFlags
|
||||
{
|
||||
constexpr uint16_t fixFlag0 = 1 << 0;
|
||||
constexpr uint16_t fixFlag1 = 1 << 1;
|
||||
}
|
||||
|
||||
struct GameState
|
||||
{
|
||||
uint32_t rng[2]; // 0x000000 (0x00525E18)
|
||||
|
@ -239,7 +247,9 @@ namespace OpenLoco::S5
|
|||
uint8_t pad_0156[0x02BC - 0x156]; // 0x000156
|
||||
char scenarioName[64]; // 0x0002BC (0x005260D4)
|
||||
char scenarioDetails[256]; // 0x0002FC (0x00526114)
|
||||
uint8_t pad_03FC[0xB96C - 0x3FC]; // 0x0003FC
|
||||
uint8_t pad_03FC[0x434 - 0x3FC]; // 0x0003FC
|
||||
uint16_t fixFlags; // 0x000434 (0x0052624C)
|
||||
uint8_t pad_0436[0xB96C - 0x436]; // 0x0003FC
|
||||
Company companies[15]; // 0x00B96C (0x00531784)
|
||||
Town towns[80]; // 0x092444 (0x005B825C)
|
||||
Industry industries[128]; // 0x09E744 (0x005C455C)
|
||||
|
@ -261,6 +271,12 @@ namespace OpenLoco::S5
|
|||
std::vector<TileElement> tileElements;
|
||||
};
|
||||
|
||||
enum LoadFlags : uint32_t
|
||||
{
|
||||
titleSequence = 1 << 0,
|
||||
twoPlayer = 1 << 1,
|
||||
};
|
||||
|
||||
enum SaveFlags : uint32_t
|
||||
{
|
||||
packCustomObjects = 1 << 0,
|
||||
|
@ -281,4 +297,6 @@ namespace OpenLoco::S5
|
|||
Options& getPreviewOptions();
|
||||
bool save(const fs::path& path, SaveFlags flags);
|
||||
void registerHooks();
|
||||
|
||||
bool load(const fs::path& path, LoadFlags flags);
|
||||
}
|
||||
|
|
|
@ -352,4 +352,11 @@ namespace OpenLoco::Scenario
|
|||
args.push<uint16_t>(0);
|
||||
args.push<uint16_t>(0);
|
||||
}
|
||||
|
||||
void sub_46115C()
|
||||
{
|
||||
addr<0x00525E28, uint32_t>() = 0;
|
||||
addr<0x00525F6C, uint16_t>() = 0;
|
||||
addr<0x0052624C, uint16_t>() = 3;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,4 +111,5 @@ namespace OpenLoco::Scenario
|
|||
void start(const char* filename = nullptr);
|
||||
void registerHooks();
|
||||
void formatChallengeArguments(FormatArguments& args);
|
||||
void sub_46115C();
|
||||
}
|
||||
|
|
|
@ -742,6 +742,14 @@ namespace OpenLoco
|
|||
}
|
||||
}
|
||||
|
||||
// 0x0048DCA5
|
||||
void Station::updateLabel()
|
||||
{
|
||||
registers regs;
|
||||
regs.esi = reinterpret_cast<int32_t>(this);
|
||||
call(0x0048DCA5, regs);
|
||||
}
|
||||
|
||||
// 0x004CBA2D
|
||||
void Station::invalidate()
|
||||
{
|
||||
|
|
|
@ -107,6 +107,7 @@ namespace OpenLoco
|
|||
char* getStatusString(char* buffer);
|
||||
bool updateCargo();
|
||||
int32_t calculateCargoRating(const StationCargoStats& cargo) const;
|
||||
void updateLabel();
|
||||
void invalidate();
|
||||
void invalidateWindow();
|
||||
void setCatchmentDisplay(uint8_t flags);
|
||||
|
|
|
@ -52,7 +52,13 @@ namespace OpenLoco::StationManager
|
|||
// 0x0048DDC3
|
||||
void updateLabels()
|
||||
{
|
||||
call(0x0048DDC3);
|
||||
for (auto& station : stations())
|
||||
{
|
||||
if (!station.empty())
|
||||
{
|
||||
station.updateLabel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 0x00437F29
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
#include "Interop/Interop.hpp"
|
||||
#include "Map/TileManager.h"
|
||||
#include "OpenLoco.h"
|
||||
#include "Intro.h"
|
||||
#include "OpenLoco.h"
|
||||
#include "S5/S5.h"
|
||||
#include "Scenario.h"
|
||||
#include "Ui/WindowManager.h"
|
||||
|
||||
|
@ -82,25 +85,13 @@ namespace OpenLoco::Title
|
|||
static TitleSequence::const_iterator _sequenceIterator;
|
||||
static uint16_t _waitCounter;
|
||||
|
||||
|
||||
static loco_global<uint8_t, 0x00508F1A> _gameSpeed;
|
||||
static loco_global<uint16_t, 0x0050C19A> _50C19A;
|
||||
static loco_global<uint16_t, 0x00525F62> _525F62;
|
||||
|
||||
static void sub_473A95(int32_t eax);
|
||||
|
||||
void registerHooks()
|
||||
{
|
||||
registerHook(
|
||||
0x0046AD7D,
|
||||
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
||||
start();
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
// 0x00472031
|
||||
// ?unload all objects?
|
||||
static void sub_472031()
|
||||
{
|
||||
call(0x00472031);
|
||||
}
|
||||
|
||||
// 0x00474874
|
||||
// ?load selected objects?
|
||||
static void sub_474874()
|
||||
|
@ -126,6 +117,33 @@ namespace OpenLoco::Title
|
|||
call(0x004442C4);
|
||||
}
|
||||
|
||||
// 0x004442C4
|
||||
static void loadTitle()
|
||||
{
|
||||
Scenario::sub_46115C();
|
||||
if (Intro::state() == Intro::intro_state::none)
|
||||
{
|
||||
auto backupWord = _525F62;
|
||||
auto titlePath = Environment::getPath(Environment::path_id::title);
|
||||
clearScreenFlag(ScreenFlags::networked);
|
||||
S5::load(titlePath, S5::LoadFlags::titleSequence);
|
||||
|
||||
setPlayerCompany(0);
|
||||
CompanyManager::set_525E3D(255);
|
||||
if (!isNetworked())
|
||||
{
|
||||
CompanyManager::set_525E3D(1);
|
||||
if (!isTrackUpgradeMode())
|
||||
{
|
||||
setPlayerCompany(1);
|
||||
CompanyManager::set_525E3D(0);
|
||||
}
|
||||
}
|
||||
|
||||
_525F62 = backupWord;
|
||||
}
|
||||
}
|
||||
|
||||
// 0x00444357
|
||||
static void reset()
|
||||
{
|
||||
|
@ -133,7 +151,7 @@ namespace OpenLoco::Title
|
|||
_waitCounter = 0;
|
||||
loadTitle();
|
||||
resetScreenAge();
|
||||
addr<0x50C19A, uint16_t>() = 55000;
|
||||
_50C19A = 55000;
|
||||
update();
|
||||
}
|
||||
|
||||
|
@ -152,7 +170,7 @@ namespace OpenLoco::Title
|
|||
setAllScreenFlags(currentScreenFlags);
|
||||
setScreenFlag(ScreenFlags::title);
|
||||
setGameSpeed(0);
|
||||
sub_472031();
|
||||
ObjectManager::unloadAll();
|
||||
sub_473A95(1);
|
||||
sub_474874();
|
||||
sub_473B91();
|
||||
|
@ -240,4 +258,20 @@ namespace OpenLoco::Title
|
|||
regs.eax = eax;
|
||||
call(0x00473A95, regs);
|
||||
}
|
||||
|
||||
void registerHooks()
|
||||
{
|
||||
registerHook(
|
||||
0x0046AD7D,
|
||||
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
||||
start();
|
||||
return 0;
|
||||
});
|
||||
registerHook(
|
||||
0x004442C4,
|
||||
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
||||
loadTitle();
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,6 +117,11 @@ namespace OpenLoco::Ui
|
|||
return Interop::addr<0x00e3f0b8, int32_t>();
|
||||
}
|
||||
|
||||
void setRotation(int32_t value)
|
||||
{
|
||||
Interop::addr<0x00e3f0b8, int32_t>() = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a 2D viewport position to a UI (screen) position.
|
||||
*/
|
||||
|
|
|
@ -764,6 +764,39 @@ namespace OpenLoco::Ui
|
|||
}
|
||||
}
|
||||
|
||||
void window::viewportFromSavedView(const SavedViewSimple& savedView)
|
||||
{
|
||||
auto viewport = viewports[0];
|
||||
if (viewport != nullptr)
|
||||
{
|
||||
auto& config = viewport_configurations[0];
|
||||
config.viewport_target_sprite = ThingId::null;
|
||||
config.saved_view_x = savedView.mapX;
|
||||
config.saved_view_y = savedView.mapY;
|
||||
|
||||
auto zoom = static_cast<int32_t>(savedView.zoomLevel) - viewport->zoom;
|
||||
if (zoom != 0)
|
||||
{
|
||||
if (viewport->zoom < 0)
|
||||
{
|
||||
zoom = -zoom;
|
||||
viewport->view_width >>= zoom;
|
||||
viewport->view_height >>= zoom;
|
||||
}
|
||||
else
|
||||
{
|
||||
viewport->view_width <<= zoom;
|
||||
viewport->view_height <<= zoom;
|
||||
}
|
||||
}
|
||||
viewport->zoom = zoom;
|
||||
viewport->setRotation(savedView.rotation);
|
||||
|
||||
config.saved_view_x -= viewport->view_width / 2;
|
||||
config.saved_view_y -= viewport->view_height / 2;
|
||||
}
|
||||
}
|
||||
|
||||
bool window::move(int16_t dx, int16_t dy)
|
||||
{
|
||||
if (dx == 0 && dy == 0)
|
||||
|
|
|
@ -483,6 +483,7 @@ namespace OpenLoco::Ui
|
|||
void viewportRotateRight();
|
||||
void viewportRotateLeft();
|
||||
void viewportRemove(const uint8_t viewportId);
|
||||
void viewportFromSavedView(const SavedViewSimple& savedView);
|
||||
|
||||
bool move(int16_t dx, int16_t dy);
|
||||
void moveInsideScreenEdges();
|
||||
|
|
Loading…
Reference in New Issue