Implement most of loading S5

This commit is contained in:
Ted John 2021-01-03 01:23:34 +00:00 committed by duncanspumpkin
parent 1a3e1902f4
commit 00cdd64fef
36 changed files with 732 additions and 26 deletions

View File

@ -1131,6 +1131,14 @@ namespace OpenLoco::Audio
}
}
// 0x0048AAD2
void resetMusic()
{
stopBackgroundMusic();
_currentSong = no_song;
_lastSong = no_song;
}
// 0x0048AAE8
void stopBackgroundMusic()
{

View File

@ -120,6 +120,7 @@ namespace OpenLoco::Audio
void revalidateCurrentTrack();
void resetMusic();
void playBackgroundMusic();
void stopBackgroundMusic();
void playTitleScreenMusic();

View File

@ -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(

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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)
{

View File

@ -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; }

View File

@ -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);

View File

@ -51,6 +51,7 @@ namespace OpenLoco::EntityManager
EntityId_t firstQuadrantId(const Map::Pos2& loc);
void resetSpatialIndex();
void updateSpatialIndex();
EntityBase* createEntityMisc();
EntityBase* createEntityMoney();

View File

@ -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++;
}
}
}
}
}
}
}
}

View File

@ -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);

View File

@ -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();
}
}
}
}

View File

@ -13,4 +13,5 @@ namespace OpenLoco::IndustryManager
Industry* get(IndustryId_t id);
void update();
void updateMonthly();
void createAllMapAnimations();
}

View File

@ -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();

View File

@ -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;

View File

@ -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
{
/**

View File

@ -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)

View File

@ -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;
});
}
}

View File

@ -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();
}

View File

@ -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)
}

View File

@ -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);

View File

@ -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);

View File

@ -449,6 +449,12 @@ namespace OpenLoco
call(0x00428E47);
}
// 0x00444387
void sub_444387()
{
call(0x00444387);
}
// 0x0046E388
static void sub_46E388()
{

View File

@ -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();
}

View File

@ -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;
});
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -111,4 +111,5 @@ namespace OpenLoco::Scenario
void start(const char* filename = nullptr);
void registerHooks();
void formatChallengeArguments(FormatArguments& args);
void sub_46115C();
}

View File

@ -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()
{

View File

@ -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);

View File

@ -52,7 +52,13 @@ namespace OpenLoco::StationManager
// 0x0048DDC3
void updateLabels()
{
call(0x0048DDC3);
for (auto& station : stations())
{
if (!station.empty())
{
station.updateLabel();
}
}
}
// 0x00437F29

View File

@ -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;
});
}
}

View File

@ -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.
*/

View File

@ -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)

View File

@ -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();