355 lines
10 KiB
C++
355 lines
10 KiB
C++
#include "Scenario.h"
|
|
#include "CompanyManager.h"
|
|
#include "Date.h"
|
|
#include "Economy/Economy.h"
|
|
#include "Entities/EntityManager.h"
|
|
#include "Graphics/Gfx.h"
|
|
#include "IndustryManager.h"
|
|
#include "Interop/Interop.hpp"
|
|
#include "Localisation/StringIds.h"
|
|
#include "Map/AnimationManager.h"
|
|
#include "Map/MapGenerator.h"
|
|
#include "Map/TileManager.h"
|
|
#include "Map/WaveManager.h"
|
|
#include "Objects/CargoObject.h"
|
|
#include "Objects/ClimateObject.h"
|
|
#include "S5/S5.h"
|
|
#include "StationManager.h"
|
|
#include "Title.h"
|
|
#include "TownManager.h"
|
|
#include "Ui/WindowManager.h"
|
|
#include "Windows/Construction/Construction.h"
|
|
|
|
using namespace OpenLoco::Interop;
|
|
using namespace OpenLoco::Map;
|
|
using namespace OpenLoco::Ui;
|
|
|
|
namespace OpenLoco::Scenario
|
|
{
|
|
static loco_global<CargoObject*, 0x0050D15C> _50D15C;
|
|
|
|
static loco_global<uint32_t, 0x00525F5E> _scenario_ticks;
|
|
|
|
static loco_global<uint8_t, 0x00525FB4> _currentSnowLine;
|
|
static loco_global<Season, 0x00525FB5> _currentSeason;
|
|
|
|
static loco_global<uint16_t, 0x0052622E> _52622E; // tick-related?
|
|
|
|
static loco_global<uint8_t, 0x00526230> objectiveType;
|
|
static loco_global<uint8_t, 0x00526231> objectiveFlags;
|
|
static loco_global<uint32_t, 0x00526232> objectiveCompanyValue;
|
|
static loco_global<uint32_t, 0x00526236> objectiveMonthlyVehicleProfit;
|
|
static loco_global<uint8_t, 0x0052623A> objectivePerformanceIndex;
|
|
static loco_global<uint8_t, 0x0052623B> objectiveDeliveredCargoType;
|
|
static loco_global<uint32_t, 0x0052623C> objectiveDeliveredCargoAmount;
|
|
static loco_global<uint8_t, 0x00526240> objectiveTimeLimitYears;
|
|
static loco_global<uint16_t, 0x00526241> objectiveTimeLimitUntilYear;
|
|
|
|
// 0x0046115C
|
|
void sub_46115C()
|
|
{
|
|
addr<0x00525E28, uint32_t>() = 0;
|
|
AnimationManager::reset();
|
|
addr<0x0052624C, uint16_t>() = S5::S5FixFlags::fixFlag0 | S5::S5FixFlags::fixFlag1;
|
|
}
|
|
|
|
Season nextSeason(Season season)
|
|
{
|
|
switch (season)
|
|
{
|
|
case Season::autumn:
|
|
return Season::winter;
|
|
case Season::winter:
|
|
return Season::spring;
|
|
case Season::spring:
|
|
return Season::summer;
|
|
case Season::summer:
|
|
default:
|
|
return Season::autumn;
|
|
}
|
|
}
|
|
|
|
// 0x00496A18, 0x00496A84 (adapted)
|
|
static void updateSeason(int32_t currentDayOfYear, const ClimateObject* climateObj)
|
|
{
|
|
Season season = static_cast<Season>(climateObj->firstSeason);
|
|
|
|
int32_t dayOffset = currentDayOfYear;
|
|
for (size_t i = 0; i < std::size(climateObj->seasonLength); i++)
|
|
{
|
|
dayOffset -= climateObj->seasonLength[i];
|
|
if (dayOffset < 0)
|
|
break;
|
|
|
|
season = nextSeason(season);
|
|
}
|
|
|
|
_currentSeason = season;
|
|
}
|
|
|
|
// 0x00496A18
|
|
void initialiseSnowLine()
|
|
{
|
|
auto today = calcDate(getCurrentDay());
|
|
int32_t currentDayOfYear = today.day_of_olympiad;
|
|
|
|
auto* climateObj = ObjectManager::get<ClimateObject>();
|
|
if (climateObj == nullptr)
|
|
return;
|
|
|
|
updateSeason(currentDayOfYear, climateObj);
|
|
|
|
if (_currentSeason == Season::winter)
|
|
{
|
|
_currentSnowLine = climateObj->winterSnowLine;
|
|
}
|
|
else
|
|
{
|
|
_currentSnowLine = climateObj->summerSnowLine;
|
|
}
|
|
}
|
|
|
|
// 0x00496A84
|
|
void updateSnowLine(int32_t currentDayOfYear)
|
|
{
|
|
auto* climateObj = ObjectManager::get<ClimateObject>();
|
|
if (climateObj == nullptr)
|
|
return;
|
|
|
|
updateSeason(currentDayOfYear, climateObj);
|
|
|
|
if (_currentSeason == Season::winter)
|
|
{
|
|
if (_currentSnowLine != climateObj->winterSnowLine)
|
|
_currentSnowLine--;
|
|
}
|
|
else
|
|
{
|
|
if (_currentSnowLine != climateObj->summerSnowLine)
|
|
_currentSnowLine++;
|
|
}
|
|
}
|
|
|
|
// 0x00475988
|
|
static void sub_475988()
|
|
{
|
|
call(0x00475988);
|
|
}
|
|
|
|
// 0x004A8810
|
|
static void sub_4A8810()
|
|
{
|
|
call(0x004A8810);
|
|
}
|
|
|
|
// TODO: Move to OrderManager::reset
|
|
// 0x004702EC
|
|
static void sub_4702EC()
|
|
{
|
|
call(0x004702EC);
|
|
}
|
|
|
|
// TODO: Move to Terraform::reset
|
|
// 0x004BAEC4
|
|
static void sub_4BAEC4()
|
|
{
|
|
call(0x004BAEC4);
|
|
}
|
|
|
|
// 0x0043C8FD
|
|
static void sub_43C8FD()
|
|
{
|
|
call(0x0043C8FD);
|
|
}
|
|
|
|
// 0x0043C88C
|
|
void reset()
|
|
{
|
|
WindowManager::closeConstructionWindows();
|
|
|
|
CompanyManager::updatingCompanyId(0x0F);
|
|
WindowManager::setCurrentRotation(0);
|
|
|
|
CompanyManager::reset();
|
|
StringManager::reset();
|
|
EntityManager::reset();
|
|
|
|
Ui::Windows::Construction::Construction::reset();
|
|
sub_46115C();
|
|
WaveManager::reset();
|
|
|
|
initialiseDate(1900);
|
|
initialiseSnowLine();
|
|
sub_475988();
|
|
TownManager::reset();
|
|
IndustryManager::reset();
|
|
StationManager::reset();
|
|
|
|
sub_4A8810();
|
|
sub_4702EC();
|
|
sub_4BAEC4();
|
|
sub_43C8FD();
|
|
Title::sub_4284C8();
|
|
}
|
|
|
|
// 0x004748D4
|
|
// ?Set default types for building. Like the initial track type and vehicle type and such.?
|
|
void sub_4748D4()
|
|
{
|
|
call(0x004748D4);
|
|
}
|
|
|
|
// 0x0043EDAD
|
|
void eraseLandscape()
|
|
{
|
|
S5::getOptions().scenarioFlags &= ~(Scenario::flags::landscape_generation_done);
|
|
Ui::WindowManager::invalidate(Ui::WindowType::landscapeGeneration, 0);
|
|
call(0x0043C88C);
|
|
S5::getOptions().madeAnyChanges = 0;
|
|
addr<0x00F25374, uint8_t>() = 0;
|
|
Gfx::invalidateScreen();
|
|
}
|
|
|
|
// 0x0043C90C
|
|
void generateLandscape()
|
|
{
|
|
auto& options = S5::getOptions();
|
|
MapGenerator::generate(options);
|
|
options.madeAnyChanges = 0;
|
|
addr<0x00F25374, uint8_t>() = 0;
|
|
}
|
|
|
|
// 0x0049685C
|
|
void initialiseDate(uint16_t year)
|
|
{
|
|
// NB: this base value was already 1800 in Locomotion.
|
|
uint32_t dayCount = 0;
|
|
for (int y = 1800; y < year; y++)
|
|
{
|
|
dayCount += 365;
|
|
if (isLeapYear(y))
|
|
dayCount += 1;
|
|
}
|
|
|
|
setDate(date(year, month_id::january, 1));
|
|
setCurrentDay(dayCount);
|
|
setDayProgression(0);
|
|
|
|
_scenario_ticks = 0;
|
|
_52622E = 0;
|
|
_currentSeason = Season::winter;
|
|
|
|
CompanyManager::determineAvailableVehicles();
|
|
|
|
Economy::sub_46E2C0(getCurrentYear());
|
|
}
|
|
|
|
void registerHooks()
|
|
{
|
|
registerHook(
|
|
0x0043C88C,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
registers backup = regs;
|
|
reset();
|
|
regs = backup;
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x0043C90C,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
registers backup = regs;
|
|
generateLandscape();
|
|
regs = backup;
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x0049685C,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
registers backup = regs;
|
|
initialiseDate(regs.ax);
|
|
regs = backup;
|
|
return 0;
|
|
});
|
|
}
|
|
|
|
// 0x0044400C
|
|
void start(const char* filename)
|
|
{
|
|
if (filename == nullptr)
|
|
filename = reinterpret_cast<const char*>(-1);
|
|
|
|
registers regs;
|
|
regs.ebx = X86Pointer(filename);
|
|
call(0x0044400C, regs);
|
|
}
|
|
|
|
// this will prepare _commonFormatArgs array before drawing the StringIds::challenge_value
|
|
// after that for example it will draw this string: Achieve a performance index of 10.0% ("Engineer")
|
|
// Note: no input and output parameters are in the original assembly, update is done in the memory
|
|
// in this implementation we return FormatArguments so in the future it will be not depending on global variables
|
|
// 0x004384E9
|
|
void formatChallengeArguments(FormatArguments& args)
|
|
{
|
|
switch (objectiveType)
|
|
{
|
|
case Scenario::objective_type::company_value:
|
|
args.push(StringIds::achieve_a_company_value_of);
|
|
args.push(*objectiveCompanyValue);
|
|
break;
|
|
|
|
case Scenario::objective_type::vehicle_profit:
|
|
args.push(StringIds::achieve_a_monthly_profit_from_vehicles_of);
|
|
args.push(*objectiveMonthlyVehicleProfit);
|
|
break;
|
|
|
|
case Scenario::objective_type::performance_index:
|
|
{
|
|
args.push(StringIds::achieve_a_performance_index_of);
|
|
int16_t performanceIndex = objectivePerformanceIndex * 10;
|
|
formatPerformanceIndex(performanceIndex, args);
|
|
break;
|
|
}
|
|
|
|
case Scenario::objective_type::cargo_delivery:
|
|
{
|
|
args.push(StringIds::deliver);
|
|
CargoObject* cargoObject = _50D15C;
|
|
if (objectiveDeliveredCargoType != 0xFF)
|
|
{
|
|
cargoObject = ObjectManager::get<CargoObject>(objectiveDeliveredCargoType);
|
|
}
|
|
args.push(cargoObject->unit_name_plural);
|
|
args.push(*objectiveDeliveredCargoAmount);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((objectiveFlags & Scenario::objective_flags::be_top_company) != 0)
|
|
{
|
|
args.push(StringIds::and_be_the_top_performing_company);
|
|
}
|
|
if ((objectiveFlags & Scenario::objective_flags::be_within_top_three_companies) != 0)
|
|
{
|
|
args.push(StringIds::and_be_one_of_the_top_3_performing_companies);
|
|
}
|
|
if ((objectiveFlags & Scenario::objective_flags::within_time_limit) != 0)
|
|
{
|
|
if (isTitleMode() || isEditorMode())
|
|
{
|
|
args.push(StringIds::within_years);
|
|
args.push<uint16_t>(*objectiveTimeLimitYears);
|
|
}
|
|
else
|
|
{
|
|
args.push(StringIds::by_the_end_of);
|
|
args.push(*objectiveTimeLimitUntilYear);
|
|
}
|
|
}
|
|
|
|
args.push<uint16_t>(0);
|
|
args.push<uint16_t>(0);
|
|
}
|
|
}
|