Implement Tree and Wall toolUpdate Down (#1078)
* Implement Wall toolUpdate Down * Start implementation of tree tool Update Down * Continue * Finish scatter down * Refactor trig function into Math file * Implement second cluster tool function * Name tree flags where possible * Add land object flags * Try alternative constant * Add missing error titles and widget checks * Add missing error title from industrylist * Clang format * Simplify sine wave
This commit is contained in:
parent
c82281540f
commit
b756c2e440
|
@ -69,7 +69,7 @@ namespace OpenLoco::GameCommands
|
|||
{ GameCommand::pauseGame, togglePause, 0x00431E32, false },
|
||||
{ GameCommand::loadSaveQuitGame, loadSaveQuit, 0x0043BFCB, false },
|
||||
{ GameCommand::removeTree, nullptr, 0x004BB392, true },
|
||||
{ GameCommand::gc_unk_23, nullptr, 0x004BB138, true },
|
||||
{ GameCommand::createTree, nullptr, 0x004BB138, true },
|
||||
{ GameCommand::changeLandMaterial, nullptr, 0x00468EDD, true },
|
||||
{ GameCommand::raiseLand, nullptr, 0x00463702, true },
|
||||
{ GameCommand::lowerLand, nullptr, 0x004638C6, true },
|
||||
|
@ -78,7 +78,7 @@ namespace OpenLoco::GameCommands
|
|||
{ GameCommand::lowerWater, nullptr, 0x004C5126, true },
|
||||
{ GameCommand::changeCompanyName, nullptr, 0x00434914, false },
|
||||
{ GameCommand::changeCompanyOwnerName, nullptr, 0x00434A58, false },
|
||||
{ GameCommand::gc_unk_32, nullptr, 0x004C436C, true },
|
||||
{ GameCommand::createWall, nullptr, 0x004C436C, true },
|
||||
{ GameCommand::removeWall, nullptr, 0x004C466C, true },
|
||||
{ GameCommand::gc_unk_34, nullptr, 0x004C4717, false },
|
||||
{ GameCommand::vehicleOrderInsert, nullptr, 0x0047036E, false },
|
||||
|
|
|
@ -56,7 +56,7 @@ namespace OpenLoco::GameCommands
|
|||
pauseGame = 20,
|
||||
loadSaveQuitGame = 21,
|
||||
removeTree = 22,
|
||||
gc_unk_23 = 23,
|
||||
createTree = 23,
|
||||
changeLandMaterial = 24,
|
||||
raiseLand = 25,
|
||||
lowerLand = 26,
|
||||
|
@ -65,7 +65,7 @@ namespace OpenLoco::GameCommands
|
|||
lowerWater = 29,
|
||||
changeCompanyName = 30,
|
||||
changeCompanyOwnerName = 31,
|
||||
gc_unk_32 = 32,
|
||||
createWall = 32,
|
||||
removeWall = 33,
|
||||
gc_unk_34 = 34,
|
||||
vehicleOrderInsert = 35,
|
||||
|
@ -433,6 +433,48 @@ namespace OpenLoco::GameCommands
|
|||
doCommand(GameCommand::removeTree, regs);
|
||||
}
|
||||
|
||||
struct TreePlacementArgs
|
||||
{
|
||||
TreePlacementArgs() = default;
|
||||
explicit TreePlacementArgs(const registers& regs)
|
||||
: pos(regs.ax, regs.cx)
|
||||
, rotation(regs.di & 0x3)
|
||||
, type(regs.bh)
|
||||
, quadrant(regs.dl)
|
||||
, colour(regs.dh)
|
||||
, buildImmediately(regs.di & 0x8000)
|
||||
, requiresFullClearance(regs.di & 0x4000)
|
||||
{
|
||||
}
|
||||
|
||||
Map::Pos2 pos;
|
||||
uint8_t rotation;
|
||||
uint8_t type;
|
||||
uint8_t quadrant;
|
||||
Colour_t colour;
|
||||
bool buildImmediately = false;
|
||||
bool requiresFullClearance = false;
|
||||
|
||||
explicit operator registers() const
|
||||
{
|
||||
registers regs;
|
||||
regs.ax = pos.x;
|
||||
regs.cx = pos.y;
|
||||
regs.dl = quadrant;
|
||||
regs.dh = colour;
|
||||
regs.di = rotation | (buildImmediately ? 0x8000 : 0) | (requiresFullClearance ? 0x4000 : 0);
|
||||
regs.bh = type;
|
||||
return regs;
|
||||
}
|
||||
};
|
||||
|
||||
inline uint32_t do_23(uint8_t flags, const TreePlacementArgs& args)
|
||||
{
|
||||
registers regs = registers(args);
|
||||
regs.bl = flags;
|
||||
return doCommand(GameCommand::createTree, regs);
|
||||
}
|
||||
|
||||
// Change Land Material
|
||||
inline void do_24(Map::Pos2 pointA, Map::Pos2 pointB, uint8_t landType, uint8_t flags)
|
||||
{
|
||||
|
@ -535,10 +577,51 @@ namespace OpenLoco::GameCommands
|
|||
return doCommand(GameCommand::changeCompanyOwnerName, regs) != FAILURE;
|
||||
}
|
||||
|
||||
struct WallPlacementArgs
|
||||
{
|
||||
WallPlacementArgs() = default;
|
||||
explicit WallPlacementArgs(const registers& regs)
|
||||
: pos(regs.ax, regs.cx, regs.di)
|
||||
, rotation(regs.dl)
|
||||
, type(regs.bh)
|
||||
, unk(regs.dh)
|
||||
, primaryColour(regs.bp & 0xFF)
|
||||
, secondaryColour((regs.bp >> 8) & 0xFF)
|
||||
{
|
||||
}
|
||||
|
||||
Map::Pos3 pos;
|
||||
uint8_t rotation;
|
||||
uint8_t type;
|
||||
uint8_t unk;
|
||||
Colour_t primaryColour;
|
||||
Colour_t secondaryColour;
|
||||
|
||||
explicit operator registers() const
|
||||
{
|
||||
registers regs;
|
||||
regs.ax = pos.x;
|
||||
regs.cx = pos.y;
|
||||
regs.dl = rotation;
|
||||
regs.dh = unk;
|
||||
regs.di = pos.z;
|
||||
regs.bp = primaryColour | (secondaryColour << 8);
|
||||
regs.bh = type;
|
||||
return regs;
|
||||
}
|
||||
};
|
||||
|
||||
inline bool do_32(uint8_t flags, const WallPlacementArgs& args)
|
||||
{
|
||||
registers regs = registers(args);
|
||||
regs.bl = flags;
|
||||
return doCommand(GameCommand::createWall, regs) != FAILURE;
|
||||
}
|
||||
|
||||
struct WallRemovalArgs
|
||||
{
|
||||
WallRemovalArgs() = default;
|
||||
explicit WallRemovalArgs(const registers regs)
|
||||
explicit WallRemovalArgs(const registers& regs)
|
||||
: pos(regs.ax, regs.cx, regs.dh * 4)
|
||||
, rotation(regs.dl)
|
||||
{
|
||||
|
|
|
@ -380,6 +380,7 @@ namespace OpenLoco::StringIds
|
|||
constexpr string_id title_build_walls = 446;
|
||||
constexpr string_id title_plant_trees = 447;
|
||||
|
||||
constexpr string_id cant_plant_this_here = 449;
|
||||
constexpr string_id outlined_wcolour2_stringid = 450;
|
||||
|
||||
constexpr string_id black_tiny_font = 453;
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
#include "Trigonometry.hpp"
|
||||
#include "../Interop/Interop.hpp"
|
||||
|
||||
namespace OpenLoco::Math::Trigonometry
|
||||
{
|
||||
// Excel Function =SIN((A1/16384)*2*PI())*32768
|
||||
// Where A1 is 0 : 4095
|
||||
static Interop::loco_global<int16_t[directionPrecisionHigh / 4], 0x00501B50> _quarterSine;
|
||||
|
||||
int32_t integerSinePrecisionHigh(uint16_t direction, int32_t magnitude)
|
||||
{
|
||||
// Build the full sine wave from the quarter wave by subtraction of direction/magnitude
|
||||
const auto sineIndex = ((direction & (1 << 12)) ? -direction : direction) & 0xFFF;
|
||||
const auto value = (direction & (1 << 13)) ? -_quarterSine[sineIndex] : _quarterSine[sineIndex];
|
||||
return value * magnitude / 0x8000;
|
||||
}
|
||||
|
||||
int32_t integerCosinePrecisionHigh(uint16_t direction, int32_t magnitude)
|
||||
{
|
||||
// Cosine is Sine plus pi/2
|
||||
return integerSinePrecisionHigh(direction + directionPrecisionHigh / 4, magnitude);
|
||||
}
|
||||
}
|
|
@ -113,4 +113,8 @@ namespace OpenLoco::Math::Trigonometry
|
|||
{
|
||||
return computeXYVector(computeXYMagnitude(height, pitch), yaw);
|
||||
}
|
||||
|
||||
constexpr auto directionPrecisionHigh = 0x4000;
|
||||
int32_t integerSinePrecisionHigh(uint16_t direction, int32_t magnitude);
|
||||
int32_t integerCosinePrecisionHigh(uint16_t direction, int32_t magnitude);
|
||||
}
|
||||
|
|
|
@ -9,13 +9,23 @@ namespace OpenLoco
|
|||
struct Context;
|
||||
}
|
||||
|
||||
namespace LandObjectFlags
|
||||
{
|
||||
constexpr uint8_t unk0 = (1 << 0);
|
||||
constexpr uint8_t unk1 = (1 << 1);
|
||||
constexpr uint8_t isDesert = (1 << 2);
|
||||
constexpr uint8_t noTrees = (1 << 3);
|
||||
}
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct LandObject
|
||||
{
|
||||
string_id name;
|
||||
uint8_t cost_index; // 0x02
|
||||
uint8_t var_03;
|
||||
uint8_t pad_04[0x8 - 0x04];
|
||||
uint8_t pad_04;
|
||||
uint8_t flags; //0x05
|
||||
uint8_t pad_06[0x8 - 0x6];
|
||||
uint8_t cost_factor; // 0x08
|
||||
uint8_t pad_09[0x0A - 0x09];
|
||||
uint32_t image; // 0x0A
|
||||
|
|
|
@ -28,7 +28,7 @@ namespace OpenLoco
|
|||
|
||||
Gfx::point_t treePos = Gfx::point_t{ x, y } + Gfx::point_t{ 0, 48 };
|
||||
|
||||
if (var_08 & (1 << 0))
|
||||
if (flags & TreeObjectFlags::hasSnowVariation)
|
||||
{
|
||||
auto snowImage = treeGrowth[growth] * num_rotations;
|
||||
snowImage += rotation;
|
||||
|
|
|
@ -24,6 +24,18 @@ namespace OpenLoco
|
|||
0,
|
||||
} };
|
||||
|
||||
namespace TreeObjectFlags
|
||||
{
|
||||
constexpr uint16_t hasSnowVariation = (1 << 0);
|
||||
constexpr uint16_t unk1 = (1 << 1);
|
||||
constexpr uint16_t veryHighAltitude = (1 << 2);
|
||||
constexpr uint16_t highAltitude = (1 << 3);
|
||||
constexpr uint16_t requiresWater = (1 << 4);
|
||||
constexpr uint16_t unk5 = (1 << 5);
|
||||
constexpr uint16_t droughtResistant = (1 << 6);
|
||||
constexpr uint16_t unk7 = (1 << 7);
|
||||
}
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct TreeObject
|
||||
{
|
||||
|
@ -34,7 +46,7 @@ namespace OpenLoco
|
|||
uint8_t var_05;
|
||||
uint8_t num_rotations; // 0x06 (1,2,4)
|
||||
uint8_t growth; // 0x07 (number of tree size images)
|
||||
uint16_t var_08; // 0x08
|
||||
uint16_t flags; // 0x08
|
||||
uint32_t sprites[12]; // 0x0A
|
||||
uint8_t pad_3A[0x3D - 0x3A];
|
||||
uint8_t season_state; // 0x3D (index for sprites, seasons + dying)
|
||||
|
|
|
@ -217,6 +217,8 @@ namespace OpenLoco::Ui
|
|||
std::pair<ViewportInteraction::InteractionArg, Ui::Viewport*> getMapCoordinatesFromPos(int32_t screenX, int32_t screenY, int32_t flags);
|
||||
std::optional<Map::Pos2> getSurfaceOrWaterLocFromUi(const xy32& screenCoords);
|
||||
uint8_t getQuadrantOrCentreFromPos(const Map::Pos2& loc);
|
||||
uint8_t getQuadrantFromPos(const Map::Pos2& loc);
|
||||
uint8_t getSideFromPos(const Map::Pos2& loc);
|
||||
std::optional<std::pair<Map::Pos2, Ui::Viewport*>> getSurfaceLocFromUi(const xy32& screenCoords);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -551,17 +551,8 @@ namespace OpenLoco::Ui::ViewportInteraction
|
|||
}
|
||||
|
||||
// Determine to which edge the cursor is closest
|
||||
[[maybe_unused]] uint32_t closestEdge = 0; // ecx
|
||||
const auto xNibble = mapPos.x & 0x1F;
|
||||
const auto yNibble = mapPos.y & 0x1F;
|
||||
if (xNibble < yNibble)
|
||||
{
|
||||
closestEdge = (xNibble + yNibble < 32) ? 0 : 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
closestEdge = (xNibble + yNibble < 32) ? 3 : 2;
|
||||
}
|
||||
[[maybe_unused]] uint32_t closestEdge = getSideFromPos(mapPos); // ecx
|
||||
|
||||
return { Pos2(mapPos.x & 0xFFE0, mapPos.y & 0xFFE0) };
|
||||
}
|
||||
|
||||
|
@ -595,7 +586,7 @@ namespace OpenLoco::Ui::ViewportInteraction
|
|||
// NOTE: Original call getSurfaceLocFromUi within this function
|
||||
// instead OpenLoco has split it in two. Also note that result of original
|
||||
// was a Pos2 start i.e. (& 0xFFE0) both components
|
||||
static uint8_t getQuadrantFromPos(const Map::Pos2& loc)
|
||||
uint8_t getQuadrantFromPos(const Map::Pos2& loc)
|
||||
{
|
||||
const auto xNibble = loc.x & 0x1F;
|
||||
const auto yNibble = loc.y & 0x1F;
|
||||
|
@ -609,6 +600,24 @@ namespace OpenLoco::Ui::ViewportInteraction
|
|||
}
|
||||
}
|
||||
|
||||
// 0x0045FE4C
|
||||
// NOTE: Original call getSurfaceLocFromUi within this function
|
||||
// instead OpenLoco has split it in two. Also note that result of original
|
||||
// was a Pos2 start i.e. (& 0xFFE0) both components
|
||||
uint8_t getSideFromPos(const Map::Pos2& loc)
|
||||
{
|
||||
const auto xNibble = loc.x & 0x1F;
|
||||
const auto yNibble = loc.y & 0x1F;
|
||||
if (xNibble < yNibble)
|
||||
{
|
||||
return (xNibble + yNibble < 32) ? 0 : 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (xNibble + yNibble < 32) ? 3 : 2;
|
||||
}
|
||||
}
|
||||
|
||||
// 0x0045FD8E
|
||||
// NOTE: Original call getSurfaceLocFromUi within this function
|
||||
// instead OpenLoco has split it in two. Also note that result of original
|
||||
|
|
|
@ -1023,6 +1023,7 @@ namespace OpenLoco::Ui::Windows::IndustryList
|
|||
auto placementArgs = getIndustryPlacementArgsFromCursor(x, y);
|
||||
if (placementArgs)
|
||||
{
|
||||
GameCommands::setErrorTitle(StringIds::error_cant_build_this_here);
|
||||
if (GameCommands::do_47(GameCommands::Flags::apply, *placementArgs) != GameCommands::FAILURE)
|
||||
{
|
||||
Audio::playSound(Audio::SoundId::construct, GameCommands::getPosition());
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "../Audio/Audio.h"
|
||||
#include "../CompanyManager.h"
|
||||
#include "../Economy/Economy.h"
|
||||
#include "../GameCommands/GameCommands.h"
|
||||
#include "../Graphics/Colour.h"
|
||||
|
@ -10,6 +11,7 @@
|
|||
#include "../Map/Map.hpp"
|
||||
#include "../Map/Tile.h"
|
||||
#include "../Map/TileManager.h"
|
||||
#include "../Math/Trigonometry.hpp"
|
||||
#include "../Objects/InterfaceSkinObject.h"
|
||||
#include "../Objects/LandObject.h"
|
||||
#include "../Objects/ObjectManager.h"
|
||||
|
@ -41,18 +43,22 @@ namespace OpenLoco::Ui::Windows::Terraform
|
|||
static loco_global<uint8_t, 0x00F003D2> _lastSelectedLand;
|
||||
static loco_global<uint8_t, 0x01136496> _treeRotation;
|
||||
static loco_global<uint8_t, 0x01136497> _treeColour;
|
||||
static loco_global<uint8_t, 0x0113649A> _byte_113649A;
|
||||
static loco_global<uint8_t, 0x0113649A> _terraformGhostPlaced;
|
||||
static loco_global<uint8_t, 0x0113649E> _treeClusterType;
|
||||
static loco_global<int16_t, 0x0050A000> _adjustToolSize;
|
||||
static loco_global<uint32_t, 0x00F2530C> _raiseLandCost;
|
||||
static loco_global<uint32_t, 0x00F25310> _lowerLandCost;
|
||||
static loco_global<uint32_t, 0x01136484> _lastTreeCost;
|
||||
static loco_global<uint16_t, 0x01136488> _word_1136488;
|
||||
static loco_global<uint16_t, 0x0113648A> _word_113648A;
|
||||
static loco_global<Map::TileElement*, 0x01136470> _lastPlacedWall;
|
||||
static loco_global<Map::TileElement*, 0x01136470> _lastPlacedTree;
|
||||
static loco_global<Map::Pos2, 0x01136488> _terraformGhostPos;
|
||||
static loco_global<uint16_t, 0x01136490> _lastTreeColourFlag;
|
||||
static loco_global<uint8_t, 0x01136499> _byte_1136499;
|
||||
static loco_global<uint8_t, 0x0113649B> _byte_113649B;
|
||||
static loco_global<uint8_t, 0x0113649C> _byte_113649C;
|
||||
static loco_global<uint16_t, 0x01136492> _terraformGhostTreeRotationFlag;
|
||||
static loco_global<uint8_t, 0x01136499> _terraformGhostBaseZ;
|
||||
static loco_global<uint8_t, 0x0113649B> _terraformGhostTreeElementType;
|
||||
static loco_global<uint8_t, 0x0113649C> _terraformGhostType;
|
||||
static loco_global<uint8_t, 0x0113649D> _terraformGhostRotation; // wall
|
||||
static loco_global<uint8_t, 0x0113649D> _terraformGhostQuadrant; // tree
|
||||
static loco_global<uint32_t, 0x0113652C> _raiseWaterCost;
|
||||
static loco_global<uint32_t, 0x01136528> _lowerWaterCost;
|
||||
|
||||
|
@ -87,7 +93,6 @@ namespace OpenLoco::Ui::Windows::Terraform
|
|||
static WindowEventList _events;
|
||||
|
||||
static void initEvents();
|
||||
static void sub_4BD297();
|
||||
static void switchTab(Window* self, WidgetIndex_t widgetIndex);
|
||||
static void repositionTabs(Window* self);
|
||||
static void drawTabs(Window* self, Gfx::Context* context);
|
||||
|
@ -96,6 +101,12 @@ namespace OpenLoco::Ui::Windows::Terraform
|
|||
static void onResize(Window* self, uint8_t height);
|
||||
static void onMouseUp(Window* self, WidgetIndex_t widgetIndex);
|
||||
static void sub_4A69DD();
|
||||
|
||||
namespace GhostPlaced
|
||||
{
|
||||
constexpr uint8_t tree = (1 << 0);
|
||||
constexpr uint8_t wall = (1 << 1);
|
||||
}
|
||||
}
|
||||
|
||||
namespace PlantTrees
|
||||
|
@ -213,10 +224,12 @@ namespace OpenLoco::Ui::Windows::Terraform
|
|||
updateTreeColours(self);
|
||||
}
|
||||
|
||||
static void removeTreeGhost();
|
||||
|
||||
// 0x004BBB0A
|
||||
static void onClose(Window* self)
|
||||
{
|
||||
Common::sub_4BD297();
|
||||
removeTreeGhost();
|
||||
Ui::Windows::hideGridlines();
|
||||
}
|
||||
|
||||
|
@ -225,7 +238,7 @@ namespace OpenLoco::Ui::Windows::Terraform
|
|||
{
|
||||
Input::toolSet(self, Common::widx::panel, CursorId::plantTree);
|
||||
Input::setFlag(Input::Flags::flag6);
|
||||
_byte_113649A = 0;
|
||||
_terraformGhostPlaced = 0;
|
||||
_lastTreeCost = 0x80000000;
|
||||
self->var_83C = 0;
|
||||
self->row_hover = -1;
|
||||
|
@ -394,27 +407,306 @@ namespace OpenLoco::Ui::Windows::Terraform
|
|||
self->invalidate();
|
||||
}
|
||||
}
|
||||
// 0x004BD297 (bits of)
|
||||
static void removeTreeGhost()
|
||||
{
|
||||
if (_terraformGhostPlaced & Common::GhostPlaced::tree)
|
||||
{
|
||||
_terraformGhostPlaced = _terraformGhostPlaced & ~Common::GhostPlaced::tree;
|
||||
GameCommands::TreeRemovalArgs args;
|
||||
args.pos = Map::Pos3((*_terraformGhostPos).x, (*_terraformGhostPos).y, _terraformGhostBaseZ * 4);
|
||||
args.type = _terraformGhostType;
|
||||
args.elementType = _terraformGhostTreeElementType;
|
||||
GameCommands::do_22(GameCommands::Flags::apply | GameCommands::Flags::flag_3 | GameCommands::Flags::flag_5 | GameCommands::Flags::flag_6, args);
|
||||
}
|
||||
}
|
||||
|
||||
// 0x004BD237
|
||||
static currency32_t placeTreeGhost(const GameCommands::TreePlacementArgs& placementArgs)
|
||||
{
|
||||
removeTreeGhost();
|
||||
|
||||
auto res = GameCommands::do_23(GameCommands::Flags::apply | GameCommands::Flags::flag_3 | GameCommands::Flags::flag_5 | GameCommands::Flags::flag_6, placementArgs);
|
||||
if (res != GameCommands::FAILURE)
|
||||
{
|
||||
_terraformGhostPos = placementArgs.pos;
|
||||
_terraformGhostTreeElementType = (*_lastPlacedTree)->rawData()[0];
|
||||
_terraformGhostType = placementArgs.type;
|
||||
_terraformGhostBaseZ = (*_lastPlacedTree)->baseZ();
|
||||
_terraformGhostPlaced |= Common::GhostPlaced::tree;
|
||||
|
||||
_terraformGhostQuadrant = placementArgs.quadrant;
|
||||
_terraformGhostTreeRotationFlag = placementArgs.rotation | (placementArgs.buildImmediately ? 0x8000 : 0);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// 0x004BD1D9
|
||||
static std::optional<GameCommands::TreePlacementArgs> getTreePlacementArgsFromCursor(const int16_t x, const int16_t y)
|
||||
{
|
||||
auto* self = WindowManager::find(WindowType::terraform);
|
||||
if (self == nullptr)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto res = ViewportInteraction::getSurfaceLocFromUi({ x, y });
|
||||
if (!res)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
if (self->row_hover == -1)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
// TODO: modify getSurfaceOrWaterLocFromUi to return the viewport then use its rotation
|
||||
static loco_global<int32_t, 0x00E3F0B8> gCurrentRotation;
|
||||
|
||||
GameCommands::TreePlacementArgs args;
|
||||
// 0 for Z value means game command finds first available height
|
||||
args.pos = Map::Pos3(res->first.x & 0xFFE0, res->first.y & 0xFFE0, 0);
|
||||
args.type = self->row_hover;
|
||||
args.quadrant = ViewportInteraction::getQuadrantFromPos(res->first) ^ (1 << 1);
|
||||
args.colour = _treeColour;
|
||||
args.rotation = (_treeRotation - gCurrentRotation) & 0x3;
|
||||
if (isEditorMode())
|
||||
{
|
||||
args.buildImmediately = true;
|
||||
}
|
||||
return { args };
|
||||
}
|
||||
|
||||
// 0x004BBB15
|
||||
static void onToolUpdate(Window& self, const WidgetIndex_t widgetIndex, const int16_t x, const int16_t y)
|
||||
{
|
||||
registers regs;
|
||||
regs.esi = X86Pointer(&self);
|
||||
regs.dx = widgetIndex;
|
||||
regs.ax = x;
|
||||
regs.bx = y;
|
||||
call(0x004BBB15, regs);
|
||||
if (widgetIndex != Common::widx::panel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Map::TileManager::mapInvalidateSelectionRect();
|
||||
Input::resetMapSelectionFlag(Input::MapSelectionFlags::enable);
|
||||
auto placementArgs = getTreePlacementArgsFromCursor(x, y);
|
||||
if (!placementArgs)
|
||||
{
|
||||
removeTreeGhost();
|
||||
return;
|
||||
}
|
||||
|
||||
Input::setMapSelectionFlags(Input::MapSelectionFlags::enable);
|
||||
Map::TileManager::setMapSelectionCorner((placementArgs->quadrant ^ (1 << 1)) + 6);
|
||||
Map::TileManager::setMapSelectionArea(placementArgs->pos, placementArgs->pos);
|
||||
Map::TileManager::mapInvalidateSelectionRect();
|
||||
|
||||
if (_terraformGhostPlaced & Common::GhostPlaced::tree)
|
||||
{
|
||||
if (*_terraformGhostPos == placementArgs->pos
|
||||
&& _terraformGhostQuadrant == placementArgs->quadrant
|
||||
&& _terraformGhostType == placementArgs->type
|
||||
&& _terraformGhostTreeRotationFlag == (placementArgs->rotation | (placementArgs->buildImmediately ? 0x8000 : 0)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
removeTreeGhost();
|
||||
_terraformGhostQuadrant = placementArgs->quadrant;
|
||||
_terraformGhostTreeRotationFlag = placementArgs->rotation | (placementArgs->buildImmediately ? 0x8000 : 0);
|
||||
_lastTreeCost = placeTreeGhost(*placementArgs);
|
||||
}
|
||||
|
||||
// 0x004BDDC6
|
||||
static void clusterSelectedTreeToolDown(const GameCommands::TreePlacementArgs& baseArgs, const uint16_t range, const uint16_t density)
|
||||
{
|
||||
const auto numPlacements = (range * range * density) / 8192;
|
||||
for (auto i = 0; i < numPlacements; ++i)
|
||||
{
|
||||
// Choose a random offset in a circle
|
||||
auto& rng = gPrng();
|
||||
auto randomMagnitude = rng.randNext(std::numeric_limits<uint16_t>::max()) * range / 65536;
|
||||
auto randomDirection = rng.randNext(Math::Trigonometry::directionPrecisionHigh - 1);
|
||||
Map::Pos2 randomOffset(
|
||||
Math::Trigonometry::integerSinePrecisionHigh(randomDirection, randomMagnitude),
|
||||
Math::Trigonometry::integerCosinePrecisionHigh(randomDirection, randomMagnitude));
|
||||
|
||||
GameCommands::TreePlacementArgs args;
|
||||
Map::Pos2 newLoc = randomOffset + baseArgs.pos;
|
||||
args.quadrant = ViewportInteraction::getQuadrantFromPos(newLoc);
|
||||
args.pos = Map::Pos2(newLoc.x & 0xFFE0, newLoc.y & 0xFFE0);
|
||||
// Note: this is not the same as the randomDirection above as it is the trees rotation
|
||||
args.rotation = rng.randNext(3);
|
||||
args.colour = 0;
|
||||
args.type = baseArgs.type;
|
||||
args.buildImmediately = true;
|
||||
args.requiresFullClearance = true;
|
||||
do_23(GameCommands::Flags::apply, args);
|
||||
}
|
||||
}
|
||||
|
||||
static loco_global<uint8_t, 0x00525FB4> _currentSnowLine;
|
||||
|
||||
// 0x004BDF19
|
||||
static std::optional<uint8_t> getRandomTreeTypeFromSurface(const Map::TilePos2& loc, bool unk)
|
||||
{
|
||||
if (!Map::validCoords(loc))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto* surface = Map::TileManager::get(loc).surface();
|
||||
if (surface == nullptr)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
uint16_t mustNotTreeFlags = 0;
|
||||
if (unk)
|
||||
{
|
||||
mustNotTreeFlags |= TreeObjectFlags::unk1;
|
||||
}
|
||||
|
||||
uint16_t mustTreeFlags = 0;
|
||||
if (surface->baseZ() - 4 > _currentSnowLine)
|
||||
{
|
||||
mustTreeFlags |= TreeObjectFlags::hasSnowVariation;
|
||||
}
|
||||
if (surface->baseZ() > 68)
|
||||
{
|
||||
mustTreeFlags |= TreeObjectFlags::veryHighAltitude;
|
||||
}
|
||||
if (surface->baseZ() > 48)
|
||||
{
|
||||
mustTreeFlags |= TreeObjectFlags::highAltitude;
|
||||
}
|
||||
|
||||
auto* landObj = ObjectManager::get<LandObject>(surface->terrain());
|
||||
mustNotTreeFlags |= TreeObjectFlags::droughtResistant;
|
||||
if (landObj->flags & LandObjectFlags::isDesert)
|
||||
{
|
||||
mustTreeFlags |= TreeObjectFlags::droughtResistant;
|
||||
mustNotTreeFlags &= ~TreeObjectFlags::droughtResistant;
|
||||
}
|
||||
|
||||
if (landObj->flags & LandObjectFlags::noTrees)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
mustNotTreeFlags |= TreeObjectFlags::requiresWater;
|
||||
const uint16_t numSameTypeSurfaces = TileManager::countSurroundingWaterTiles(loc);
|
||||
if (numSameTypeSurfaces >= 8)
|
||||
{
|
||||
mustNotTreeFlags &= ~TreeObjectFlags::requiresWater;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> selectableTrees;
|
||||
for (uint8_t i = 0; i < ObjectManager::getMaxObjects(ObjectType::tree); ++i)
|
||||
{
|
||||
auto* treeObj = ObjectManager::get<TreeObject>(i);
|
||||
if (treeObj == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (treeObj->flags & mustNotTreeFlags)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((treeObj->flags & mustTreeFlags) != mustTreeFlags)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
selectableTrees.push_back(i);
|
||||
}
|
||||
|
||||
if (selectableTrees.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto& rng = gPrng();
|
||||
return { selectableTrees[rng.randNext(selectableTrees.size() - 1)] };
|
||||
}
|
||||
|
||||
// 0x004BDC67
|
||||
static void clusterSurfaceTypeTreeToolDown(const GameCommands::TreePlacementArgs& baseArgs, const uint16_t range, const uint16_t density)
|
||||
{
|
||||
const auto numPlacements = (range * range * density) / 8192;
|
||||
for (auto i = 0; i < numPlacements; ++i)
|
||||
{
|
||||
// Choose a random offset in a circle
|
||||
auto& rng = gPrng();
|
||||
auto randomMagnitude = rng.randNext(std::numeric_limits<uint16_t>::max()) * range / 65536;
|
||||
auto randomDirection = rng.randNext(Math::Trigonometry::directionPrecisionHigh - 1);
|
||||
Map::Pos2 randomOffset(
|
||||
Math::Trigonometry::integerSinePrecisionHigh(randomDirection, randomMagnitude),
|
||||
Math::Trigonometry::integerCosinePrecisionHigh(randomDirection, randomMagnitude));
|
||||
|
||||
GameCommands::TreePlacementArgs args;
|
||||
Map::Pos2 newLoc = randomOffset + baseArgs.pos;
|
||||
args.quadrant = ViewportInteraction::getQuadrantFromPos(newLoc);
|
||||
args.pos = Map::Pos2(newLoc.x & 0xFFE0, newLoc.y & 0xFFE0);
|
||||
// Note: this is not the same as the randomDirection above as it is the trees rotation
|
||||
args.rotation = rng.randNext(3);
|
||||
args.colour = 0;
|
||||
auto type = getRandomTreeTypeFromSurface(newLoc, false);
|
||||
if (type)
|
||||
{
|
||||
args.type = *type;
|
||||
args.buildImmediately = true;
|
||||
args.requiresFullClearance = true;
|
||||
do_23(GameCommands::Flags::apply, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 0x004BBB20
|
||||
static void onToolDown(Window& self, const WidgetIndex_t widgetIndex, const int16_t x, const int16_t y)
|
||||
{
|
||||
registers regs;
|
||||
regs.esi = X86Pointer(&self);
|
||||
regs.dx = widgetIndex;
|
||||
regs.ax = x;
|
||||
regs.bx = y;
|
||||
call(0x004BBB20, regs);
|
||||
if (widgetIndex != Common::widx::panel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
removeTreeGhost();
|
||||
auto placementArgs = getTreePlacementArgsFromCursor(x, y);
|
||||
if (placementArgs)
|
||||
{
|
||||
switch (_treeClusterType)
|
||||
{
|
||||
case treeCluster::none:
|
||||
GameCommands::setErrorTitle(StringIds::cant_plant_this_here);
|
||||
if (GameCommands::do_23(GameCommands::Flags::apply, *placementArgs) != GameCommands::FAILURE)
|
||||
{
|
||||
Audio::playSound(Audio::SoundId::construct, GameCommands::getPosition());
|
||||
}
|
||||
break;
|
||||
case treeCluster::selected:
|
||||
{
|
||||
// TODO: Lock behind sandbox/editor
|
||||
auto previousId = CompanyManager::updatingCompanyId();
|
||||
CompanyManager::updatingCompanyId(CompanyId::neutral);
|
||||
|
||||
auto height = TileManager::getHeight(placementArgs->pos);
|
||||
Audio::playSound(Audio::SoundId::construct, Map::Pos3{ placementArgs->pos.x, placementArgs->pos.y, height.landHeight });
|
||||
|
||||
clusterSelectedTreeToolDown(*placementArgs, 320, 3);
|
||||
// TODO: Lock behind sandbox/editor
|
||||
CompanyManager::updatingCompanyId(previousId);
|
||||
break;
|
||||
}
|
||||
case treeCluster::random:
|
||||
// TODO: Lock behind sandbox/editor
|
||||
auto previousId = CompanyManager::updatingCompanyId();
|
||||
CompanyManager::updatingCompanyId(CompanyId::neutral);
|
||||
|
||||
auto height = TileManager::getHeight(placementArgs->pos);
|
||||
Audio::playSound(Audio::SoundId::construct, Map::Pos3{ placementArgs->pos.x, placementArgs->pos.y, height.landHeight });
|
||||
|
||||
clusterSurfaceTypeTreeToolDown(*placementArgs, 384, 4);
|
||||
// TODO: Lock behind sandbox/editor
|
||||
CompanyManager::updatingCompanyId(previousId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 0x004BBEC1
|
||||
|
@ -689,7 +981,7 @@ namespace OpenLoco::Ui::Windows::Terraform
|
|||
window->number = 0;
|
||||
window->current_tab = Common::widx::tab_plant_trees - Common::widx::tab_clear_area;
|
||||
window->frame_no = 0;
|
||||
_byte_113649A = 0;
|
||||
_terraformGhostPlaced = 0;
|
||||
_lastTreeCost = 0x80000000;
|
||||
window->owner = _player_company;
|
||||
window->var_846 = 0xFFFF;
|
||||
|
@ -1648,10 +1940,12 @@ namespace OpenLoco::Ui::Windows::Terraform
|
|||
updateActiveThumb(self);
|
||||
}
|
||||
|
||||
static void removeWallGhost();
|
||||
|
||||
// 0x004BC21C
|
||||
static void onClose(Window* self)
|
||||
{
|
||||
Common::sub_4BD297();
|
||||
removeWallGhost();
|
||||
Ui::Windows::hideGridlines();
|
||||
}
|
||||
|
||||
|
@ -1660,7 +1954,7 @@ namespace OpenLoco::Ui::Windows::Terraform
|
|||
{
|
||||
Input::toolSet(self, Common::widx::panel, CursorId::placeFence);
|
||||
Input::setFlag(Input::Flags::flag6);
|
||||
_byte_113649A = 0;
|
||||
_terraformGhostPlaced = 0;
|
||||
self->var_83C = 0;
|
||||
self->row_hover = -1;
|
||||
refreshWallList(self);
|
||||
|
@ -1760,26 +2054,117 @@ namespace OpenLoco::Ui::Windows::Terraform
|
|||
}
|
||||
}
|
||||
|
||||
// 0x004BD297 (bits of)
|
||||
static void removeWallGhost()
|
||||
{
|
||||
if (_terraformGhostPlaced & Common::GhostPlaced::wall)
|
||||
{
|
||||
_terraformGhostPlaced = _terraformGhostPlaced & ~Common::GhostPlaced::wall;
|
||||
GameCommands::WallRemovalArgs args;
|
||||
args.pos = Map::Pos3((*_terraformGhostPos).x, (*_terraformGhostPos).y, _terraformGhostBaseZ * 4);
|
||||
args.rotation = _terraformGhostRotation;
|
||||
GameCommands::do_33(GameCommands::Flags::apply | GameCommands::Flags::flag_3 | GameCommands::Flags::flag_5 | GameCommands::Flags::flag_6, args);
|
||||
}
|
||||
}
|
||||
|
||||
// 0x004BD4C8
|
||||
static void placeWallGhost(const GameCommands::WallPlacementArgs& placementArgs)
|
||||
{
|
||||
removeWallGhost();
|
||||
|
||||
if (GameCommands::do_32(GameCommands::Flags::apply | GameCommands::Flags::flag_3 | GameCommands::Flags::flag_5 | GameCommands::Flags::flag_6, placementArgs))
|
||||
{
|
||||
_terraformGhostPos = placementArgs.pos;
|
||||
_terraformGhostRotation = placementArgs.rotation;
|
||||
_terraformGhostTreeElementType = placementArgs.rotation; // Unsure why duplicated not used
|
||||
_terraformGhostType = placementArgs.type;
|
||||
_terraformGhostBaseZ = (*_lastPlacedWall)->baseZ();
|
||||
_terraformGhostPlaced |= Common::GhostPlaced::wall;
|
||||
}
|
||||
}
|
||||
|
||||
// 0x004BD48E
|
||||
static std::optional<GameCommands::WallPlacementArgs> getWallPlacementArgsFromCursor(const int16_t x, const int16_t y)
|
||||
{
|
||||
auto* self = WindowManager::find(WindowType::terraform);
|
||||
if (self == nullptr)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto res = ViewportInteraction::getSurfaceLocFromUi({ x, y });
|
||||
if (!res)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
if (self->row_hover == -1)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
GameCommands::WallPlacementArgs args;
|
||||
// 0 for Z value means game command finds first available height
|
||||
args.pos = Map::Pos3(res->first.x & 0xFFE0, res->first.y & 0xFFE0, 0);
|
||||
args.type = self->row_hover;
|
||||
args.rotation = ViewportInteraction::getSideFromPos(res->first);
|
||||
args.primaryColour = Colour::black;
|
||||
args.secondaryColour = Colour::black;
|
||||
args.unk = 0;
|
||||
return { args };
|
||||
}
|
||||
|
||||
// 0x004BC227
|
||||
static void onToolUpdate(Window& self, const WidgetIndex_t widgetIndex, const int16_t x, const int16_t y)
|
||||
{
|
||||
registers regs;
|
||||
regs.esi = X86Pointer(&self);
|
||||
regs.dx = widgetIndex;
|
||||
regs.ax = x;
|
||||
regs.bx = y;
|
||||
call(0x004BC227, regs);
|
||||
if (widgetIndex != Common::widx::panel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Map::TileManager::mapInvalidateSelectionRect();
|
||||
Input::resetMapSelectionFlag(Input::MapSelectionFlags::enable);
|
||||
auto placementArgs = getWallPlacementArgsFromCursor(x, y);
|
||||
if (!placementArgs)
|
||||
{
|
||||
removeWallGhost();
|
||||
return;
|
||||
}
|
||||
|
||||
Input::setMapSelectionFlags(Input::MapSelectionFlags::enable);
|
||||
Map::TileManager::setMapSelectionCorner(placementArgs->rotation + 10);
|
||||
Map::TileManager::setMapSelectionArea(placementArgs->pos, placementArgs->pos);
|
||||
Map::TileManager::mapInvalidateSelectionRect();
|
||||
|
||||
if (_terraformGhostPlaced & Common::GhostPlaced::wall)
|
||||
{
|
||||
if (*_terraformGhostPos == placementArgs->pos && _terraformGhostRotation == placementArgs->rotation && _terraformGhostType == placementArgs->type)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
removeWallGhost();
|
||||
_terraformGhostRotation = placementArgs->rotation;
|
||||
placeWallGhost(*placementArgs);
|
||||
}
|
||||
|
||||
// 0x004BC232
|
||||
static void onToolDown(Window& self, const WidgetIndex_t widgetIndex, const int16_t x, const int16_t y)
|
||||
{
|
||||
registers regs;
|
||||
regs.esi = X86Pointer(&self);
|
||||
regs.dx = widgetIndex;
|
||||
regs.ax = x;
|
||||
regs.bx = y;
|
||||
call(0x004BC232, regs);
|
||||
if (widgetIndex != Common::widx::panel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
removeWallGhost();
|
||||
auto placementArgs = getWallPlacementArgsFromCursor(x, y);
|
||||
if (placementArgs)
|
||||
{
|
||||
GameCommands::setErrorTitle(StringIds::error_cant_build_this_here);
|
||||
if (GameCommands::do_32(GameCommands::Flags::apply, *placementArgs))
|
||||
{
|
||||
Audio::playSound(Audio::SoundId::construct, GameCommands::getPosition());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 0x004BC359
|
||||
|
@ -2097,13 +2482,6 @@ namespace OpenLoco::Ui::Windows::Terraform
|
|||
}
|
||||
}
|
||||
|
||||
// 0x004BD297
|
||||
static void sub_4BD297()
|
||||
{
|
||||
registers regs;
|
||||
call(0x004BD297, regs);
|
||||
}
|
||||
|
||||
// 0x004BBB2B
|
||||
static void switchTab(Window* self, WidgetIndex_t widgetIndex)
|
||||
{
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
<ClCompile Include="Map\SurfaceTile.cpp" />
|
||||
<ClCompile Include="Map\Tile.cpp" />
|
||||
<ClCompile Include="Map\TileManager.cpp" />
|
||||
<ClCompile Include="Math\Trigonometry.cpp" />
|
||||
<ClCompile Include="Math\Vector.cpp" />
|
||||
<ClCompile Include="MessageManager.cpp" />
|
||||
<ClCompile Include="MultiPlayer.cpp" />
|
||||
|
|
Loading…
Reference in New Issue