Merge pull request #8773 from duncanspumpkin/scenery_place_ga

Implement SmallSceneryPlaceAction
Fix #8793, Fix #8787
This commit is contained in:
Duncan 2019-02-28 08:21:02 +00:00 committed by GitHub
commit 8ed824f285
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 508 additions and 334 deletions

View File

@ -24,6 +24,9 @@
2A1F4FE0221FF4B0003CA045 /* Twitch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C840F1EC4E7CC00FA49E2 /* Twitch.cpp */; };
2A1F4FE1221FF4B0003CA045 /* Audio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C83571EC4E7CC00FA49E2 /* Audio.cpp */; };
2A1F4FE2221FF4B0003CA045 /* macos.mm in Sources */ = {isa = PBXBuildFile; fileRef = F76C845D1EC4E7CC00FA49E2 /* macos.mm */; };
2A43D2BA2225B8D900E8F73B /* RideSetVehiclesAction.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 2A43D2B72225B8D900E8F73B /* RideSetVehiclesAction.hpp */; };
2A43D2BB2225B8D900E8F73B /* SmallSceneryPlaceAction.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 2A43D2B82225B8D900E8F73B /* SmallSceneryPlaceAction.hpp */; };
2A43D2BC2225B8D900E8F73B /* LoadOrQuitAction.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 2A43D2B92225B8D900E8F73B /* LoadOrQuitAction.hpp */; };
2A5354E922099C4F00A5440F /* Network.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2A5354E822099C4F00A5440F /* Network.cpp */; };
2A5C1368221E9F9000F8C245 /* TrackRemoveAction.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 2A5C1367221E9F9000F8C245 /* TrackRemoveAction.hpp */; };
2AA050322209A8E300D3A922 /* StaffSetCostumeAction.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 2AA050302209A8E300D3A922 /* StaffSetCostumeAction.hpp */; };
@ -622,6 +625,9 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
2A43D2B72225B8D900E8F73B /* RideSetVehiclesAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RideSetVehiclesAction.hpp; sourceTree = "<group>"; };
2A43D2B82225B8D900E8F73B /* SmallSceneryPlaceAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SmallSceneryPlaceAction.hpp; sourceTree = "<group>"; };
2A43D2B92225B8D900E8F73B /* LoadOrQuitAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LoadOrQuitAction.hpp; sourceTree = "<group>"; };
2A5354E822099C4F00A5440F /* Network.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Network.cpp; sourceTree = "<group>"; };
2A5354EA22099C7200A5440F /* CircularBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CircularBuffer.h; sourceTree = "<group>"; };
2A5354EB22099D7700A5440F /* SignSetStyleAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SignSetStyleAction.hpp; sourceTree = "<group>"; };
@ -2013,6 +2019,9 @@
isa = PBXGroup;
children = (
2ACBAB162226850A0034FB91 /* RideSetSetting.hpp */,
2A43D2B92225B8D900E8F73B /* LoadOrQuitAction.hpp */,
2A43D2B72225B8D900E8F73B /* RideSetVehiclesAction.hpp */,
2A43D2B82225B8D900E8F73B /* SmallSceneryPlaceAction.hpp */,
2A5C1367221E9F9000F8C245 /* TrackRemoveAction.hpp */,
2AAFD7FF220DD3D2002461A4 /* LandSetHeightAction.hpp */,
2AAFD7FB220DD336002461A4 /* RideSetPriceAction.hpp */,
@ -3332,8 +3341,10 @@
2AAFD7FE220DD374002461A4 /* PauseToggleAction.hpp in Headers */,
C6352B941F477032006CCEE3 /* PlaceParkEntranceAction.hpp in Headers */,
C6352B911F477032006CCEE3 /* GameAction.h in Headers */,
2A43D2BA2225B8D900E8F73B /* RideSetVehiclesAction.hpp in Headers */,
2AA050322209A8E300D3A922 /* StaffSetCostumeAction.hpp in Headers */,
C62D838B1FD36D6F008C04F1 /* EditorObjectSelectionSession.h in Headers */,
2A43D2BC2225B8D900E8F73B /* LoadOrQuitAction.hpp in Headers */,
9344BEF920C1E6180047D165 /* Crypt.h in Headers */,
939A35A220C12FFD00630B3F /* InteractiveConsole.h in Headers */,
2ACBAB172226850A0034FB91 /* RideSetSetting.hpp in Headers */,
@ -3346,6 +3357,7 @@
2AAFD7FC220DD336002461A4 /* RideSetPriceAction.hpp in Headers */,
C67B28162002D67A00109C93 /* Window.h in Headers */,
C6352B961F477032006CCEE3 /* RideSetStatus.hpp in Headers */,
2A43D2BB2225B8D900E8F73B /* SmallSceneryPlaceAction.hpp in Headers */,
2AAFD800220DD3D2002461A4 /* LandSetHeightAction.hpp in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@ -28,6 +28,7 @@
#include <openrct2/actions/ClearAction.hpp>
#include <openrct2/actions/LoadOrQuitAction.hpp>
#include <openrct2/actions/PauseToggleAction.hpp>
#include <openrct2/actions/SmallSceneryPlaceAction.hpp>
#include <openrct2/audio/audio.h>
#include <openrct2/config/Config.h>
#include <openrct2/interface/Chat.h>
@ -1723,7 +1724,8 @@ static void window_top_toolbar_scenery_tool_down(int16_t x, int16_t y, rct_windo
{
quantity = 35;
}
int32_t successfulPlacements = 0;
bool forceError = true;
for (int32_t q = 0; q < quantity; q++)
{
int32_t zCoordinate = gSceneryPlaceZ;
@ -1755,58 +1757,61 @@ static void window_top_toolbar_scenery_tool_down(int16_t x, int16_t y, rct_windo
zAttemptRange = 20;
}
bool success = false;
uint8_t quadrant = parameter_2 & 0xFF;
uint8_t primaryColour = (parameter_2 >> 8) & 0xFF;
uint8_t secondaryColour = (parameter_3 >> 16) & 0xFF;
uint8_t type = (parameter_1 >> 8) & 0xFF;
auto success = GA_ERROR::UNKNOWN;
// Try find a valid z coordinate
for (; zAttemptRange != 0; zAttemptRange--)
{
int32_t flags = GAME_COMMAND_FLAG_APPLY | (parameter_1 & 0xFF00);
gDisableErrorWindowSound = true;
gGameCommandErrorTitle = STR_CANT_POSITION_THIS_HERE;
int32_t cost = game_do_command(
cur_grid_x, flags, cur_grid_y, parameter_2, GAME_COMMAND_PLACE_SCENERY,
gSceneryPlaceRotation | (parameter_3 & 0xFFFF0000), gSceneryPlaceZ);
gDisableErrorWindowSound = false;
if (cost != MONEY32_UNDEFINED)
{
window_close_by_class(WC_ERROR);
audio_play_sound_at_location(
SOUND_PLACE_ITEM, gCommandPosition.x, gCommandPosition.y, gCommandPosition.z);
success = true;
break;
}
if (gGameCommandErrorText == STR_NOT_ENOUGH_CASH_REQUIRES
|| gGameCommandErrorText == STR_CAN_ONLY_BUILD_THIS_ON_WATER)
auto smallSceneryPlaceAction = SmallSceneryPlaceAction(
{ cur_grid_x, cur_grid_y, gSceneryPlaceZ, gSceneryPlaceRotation }, quadrant, type, primaryColour,
secondaryColour);
auto res = GameActions::Query(&smallSceneryPlaceAction);
success = res->Error;
if (res->Error == GA_ERROR::OK)
{
break;
}
gSceneryPlaceZ += 8;
if (res->Error == GA_ERROR::INSUFFICIENT_FUNDS)
{
break;
}
if (zAttemptRange != 1)
{
gSceneryPlaceZ += 8;
}
}
if (success)
// Actually place
if (success == GA_ERROR::OK || ((q + 1 == quantity) && (forceError == true)))
{
successfulPlacements++;
}
else
{
if (gGameCommandErrorText == STR_NOT_ENOUGH_CASH_REQUIRES)
auto smallSceneryPlaceAction = SmallSceneryPlaceAction(
{ cur_grid_x, cur_grid_y, gSceneryPlaceZ, gSceneryPlaceRotation }, quadrant, type, primaryColour,
secondaryColour);
smallSceneryPlaceAction.SetCallback([=](const GameAction* ga, const GameActionResult* result) {
if (result->Error == GA_ERROR::OK)
{
audio_play_sound_at_location(
SOUND_PLACE_ITEM, result->Position.x, result->Position.y, result->Position.z);
}
});
auto res = GameActions::Execute(&smallSceneryPlaceAction);
if (res->Error == GA_ERROR::OK)
{
forceError = false;
}
if (res->Error == GA_ERROR::INSUFFICIENT_FUNDS)
{
break;
}
}
gSceneryPlaceZ = zCoordinate;
}
if (successfulPlacements > 0)
{
window_close_by_class(WC_ERROR);
}
else
{
audio_play_sound_at_location(SOUND_ERROR, gCommandPosition.x, gCommandPosition.y, gCommandPosition.z);
}
break;
}
case SCENERY_TYPE_PATH_ITEM:
@ -2444,16 +2449,22 @@ static money32 try_place_ghost_scenery(
switch (scenery_type)
{
case 0:
{
// Small Scenery
// 6e252b
cost = game_do_command(
map_tile.x,
parameter_1 | GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_5
| GAME_COMMAND_FLAG_GHOST,
map_tile.y, parameter_2, GAME_COMMAND_PLACE_SCENERY, parameter_3, gSceneryPlaceZ);
uint8_t quadrant = parameter_2 & 0xFF;
uint8_t primaryColour = (parameter_2 >> 8) & 0xFF;
uint8_t secondaryColour = (parameter_3 >> 16) & 0xFF;
uint8_t type = (parameter_1 >> 8) & 0xFF;
uint8_t rotation = parameter_3 & 0xFF;
auto smallSceneryPlaceAction = SmallSceneryPlaceAction(
{ map_tile.x, map_tile.y, gSceneryPlaceZ, rotation }, quadrant, type, primaryColour, secondaryColour);
smallSceneryPlaceAction.SetFlags(GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED);
auto res = GameActions::Execute(&smallSceneryPlaceAction);
if (cost == MONEY32_UNDEFINED)
return cost;
cost = res->Cost;
if (res->Error != GA_ERROR::OK)
return MONEY32_UNDEFINED;
gSceneryGhostPosition.x = map_tile.x;
gSceneryGhostPosition.y = map_tile.y;
@ -2476,6 +2487,7 @@ static money32 try_place_ghost_scenery(
gSceneryGhostType |= SCENERY_GHOST_FLAG_0;
break;
}
case 1:
// Path Bits
// 6e265b

View File

@ -630,8 +630,8 @@ void game_log_multiplayer_command(int command, const int* eax, const int* ebx, c
network_append_server_log(log_msg);
}
else if (
command == GAME_COMMAND_PLACE_SCENERY || command == GAME_COMMAND_PLACE_WALL
|| command == GAME_COMMAND_PLACE_LARGE_SCENERY || command == GAME_COMMAND_PLACE_BANNER)
command == GAME_COMMAND_PLACE_WALL || command == GAME_COMMAND_PLACE_LARGE_SCENERY
|| command == GAME_COMMAND_PLACE_BANNER)
{
uint8_t flags = *ebx & 0xFF;
if (flags & GAME_COMMAND_FLAG_GHOST)
@ -1275,7 +1275,7 @@ GAME_COMMAND_POINTER* new_game_command_table[GAME_COMMAND_COUNT] = {
game_command_place_ride_entrance_or_exit,
game_command_remove_ride_entrance_or_exit,
nullptr,
game_command_place_scenery,
nullptr,
game_command_set_water_height,
game_command_place_footpath,
game_command_place_footpath_from_track,

View File

@ -33,7 +33,7 @@ enum GAME_COMMAND
GAME_COMMAND_PLACE_RIDE_ENTRANCE_OR_EXIT,
GAME_COMMAND_REMOVE_RIDE_ENTRANCE_OR_EXIT,
GAME_COMMAND_REMOVE_SCENERY, // GA
GAME_COMMAND_PLACE_SCENERY,
GAME_COMMAND_PLACE_SCENERY, // GA
GAME_COMMAND_SET_WATER_HEIGHT,
GAME_COMMAND_PLACE_PATH,
GAME_COMMAND_PLACE_PATH_FROM_TRACK,

View File

@ -36,6 +36,7 @@
#include "SetParkEntranceFeeAction.hpp"
#include "SignSetNameAction.hpp"
#include "SignSetStyleAction.hpp"
#include "SmallSceneryPlaceAction.hpp"
#include "SmallSceneryRemoveAction.hpp"
#include "StaffSetColourAction.hpp"
#include "StaffSetCostumeAction.hpp"
@ -77,6 +78,7 @@ namespace GameActions
Register<StaffSetOrdersAction>();
Register<StaffSetCostumeAction>();
Register<WallRemoveAction>();
Register<SmallSceneryPlaceAction>();
Register<SmallSceneryRemoveAction>();
Register<LargeSceneryRemoveAction>();
Register<LandSetHeightAction>();

View File

@ -0,0 +1,420 @@
/*****************************************************************************
* Copyright (c) 2014-2019 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#pragma once
#include "../Cheats.h"
#include "../OpenRCT2.h"
#include "../common.h"
#include "../core/MemoryStream.h"
#include "../interface/Window.h"
#include "../localisation/Localisation.h"
#include "../localisation/StringIds.h"
#include "../management/Finance.h"
#include "../ride/Ride.h"
#include "../ride/TrackDesign.h"
#include "../world/MapAnimation.h"
#include "../world/Park.h"
#include "../world/SmallScenery.h"
#include "../world/Sprite.h"
#include "../world/Surface.h"
#include "../world/TileElement.h"
#include "GameAction.h"
DEFINE_GAME_ACTION(SmallSceneryPlaceAction, GAME_COMMAND_PLACE_SCENERY, GameActionResult)
{
private:
CoordsXYZD _loc;
uint8_t _quadrant;
uint8_t _sceneryType;
uint8_t _primaryColour;
uint8_t _secondaryColour;
public:
SmallSceneryPlaceAction() = default;
SmallSceneryPlaceAction(
CoordsXYZD loc, uint8_t quadrant, uint8_t sceneryType, uint8_t primaryColour, uint8_t secondaryColour)
: _loc(loc)
, _quadrant(quadrant)
, _sceneryType(sceneryType)
, _primaryColour(primaryColour)
, _secondaryColour(secondaryColour)
{
}
uint16_t GetActionFlags() const override
{
return GameAction::GetActionFlags();
}
void Serialise(DataSerialiser & stream) override
{
GameAction::Serialise(stream);
stream << DS_TAG(_loc) << DS_TAG(_quadrant) << DS_TAG(_sceneryType) << DS_TAG(_primaryColour)
<< DS_TAG(_secondaryColour);
}
GameActionResult::Ptr Query() const override
{
bool isOnWater = false;
bool supportsRequired = false;
if (_loc.z != 0)
{
supportsRequired = true;
}
int32_t baseHeight = tile_element_height(_loc.x, _loc.y);
// If on water
if (baseHeight & 0xFFFF0000)
{
baseHeight >>= 16;
}
auto res = MakeResult();
res->Position.x = _loc.x + 16;
res->Position.y = _loc.y + 16;
res->Position.z = baseHeight;
if (_loc.z != 0)
{
baseHeight = _loc.z;
res->Position.z = baseHeight;
}
if (!map_check_free_elements_and_reorganise(1))
{
return MakeResult(GA_ERROR::NO_FREE_ELEMENTS, STR_CANT_POSITION_THIS_HERE);
}
if (!byte_9D8150 && (_loc.x > gMapSizeMaxXY || _loc.y > gMapSizeMaxXY))
{
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_POSITION_THIS_HERE);
}
rct_scenery_entry* sceneryEntry = get_small_scenery_entry(_sceneryType);
if (sceneryEntry == nullptr)
{
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_POSITION_THIS_HERE);
}
auto quadrant = _quadrant;
if (scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_FULL_TILE)
|| !scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_DIAGONAL))
{
if (scenery_small_entry_has_flag(
sceneryEntry,
SMALL_SCENERY_FLAG_DIAGONAL | SMALL_SCENERY_FLAG_HALF_SPACE | SMALL_SCENERY_FLAG_THREE_QUARTERS))
{
quadrant = 0;
}
}
// Check if sub tile height is any different compared to actual surface tile height
int32_t x2 = _loc.x;
int32_t y2 = _loc.y;
if (scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_FULL_TILE))
{
x2 += 16;
y2 += 16;
}
else
{
x2 += ScenerySubTileOffsets[quadrant & 3].x - 1;
y2 += ScenerySubTileOffsets[quadrant & 3].y - 1;
}
baseHeight = tile_element_height(x2, y2);
// If on water
if (baseHeight & 0xFFFF0000)
{
// base_height2 is now the water height
baseHeight >>= 16;
if (_loc.z == 0)
{
isOnWater = true;
}
}
auto targetHeight = _loc.z;
if (_loc.z == 0)
{
targetHeight = baseHeight;
}
if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !gCheatsSandboxMode
&& !map_is_location_owned(_loc.x, _loc.y, targetHeight))
{
return MakeResult(GA_ERROR::NOT_OWNED, STR_CANT_POSITION_THIS_HERE, STR_LAND_NOT_OWNED_BY_PARK);
}
TileElement* surfaceElement = map_get_surface_element_at({ _loc.x, _loc.y });
if (surfaceElement != nullptr && !gCheatsDisableClearanceChecks && surfaceElement->AsSurface()->GetWaterHeight() > 0)
{
int32_t water_height = (surfaceElement->AsSurface()->GetWaterHeight() * 16) - 1;
if (water_height > targetHeight)
{
return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_POSITION_THIS_HERE, STR_CANT_BUILD_THIS_UNDERWATER);
}
}
if (!gCheatsDisableClearanceChecks && !(scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_STACKABLE)))
{
if (isOnWater)
{
return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_POSITION_THIS_HERE, STR_CAN_ONLY_BUILD_THIS_ON_LAND);
}
if (surfaceElement != nullptr && surfaceElement->AsSurface()->GetWaterHeight() > 0)
{
if (static_cast<int32_t>((surfaceElement->AsSurface()->GetWaterHeight() * 16)) > targetHeight)
{
return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_POSITION_THIS_HERE, STR_CAN_ONLY_BUILD_THIS_ON_LAND);
}
}
}
if (!gCheatsDisableClearanceChecks
&& (scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_REQUIRE_FLAT_SURFACE)) && !supportsRequired
&& !isOnWater && surfaceElement != nullptr && (surfaceElement->AsSurface()->GetSlope() != TILE_ELEMENT_SLOPE_FLAT))
{
return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_POSITION_THIS_HERE, STR_LEVEL_LAND_REQUIRED);
}
if (!gCheatsDisableSupportLimits && !(scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_STACKABLE))
&& supportsRequired)
{
if (!isOnWater)
{
if (surfaceElement != nullptr)
{
if (surfaceElement->AsSurface()->GetWaterHeight() || (surfaceElement->base_height * 8) != targetHeight)
{
return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_POSITION_THIS_HERE, STR_LEVEL_LAND_REQUIRED);
}
}
}
else
{
return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_POSITION_THIS_HERE, STR_CAN_ONLY_BUILD_THIS_ON_LAND);
}
}
int32_t zLow = targetHeight / 8;
int32_t zHigh = zLow + ceil2(sceneryEntry->small_scenery.height, 8) / 8;
uint8_t collisionQuadrants = 0b1111;
auto quadRotation{ 0 };
if (!(scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_FULL_TILE)))
{
quadRotation = (quadrant ^ 2);
collisionQuadrants = 0b0001;
}
if (!(scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_HALF_SPACE)))
{
if (scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_DIAGONAL)
&& scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_FULL_TILE))
{
if (scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_THREE_QUARTERS))
{
quadRotation = ((quadrant ^ 2) + _loc.direction) & 3;
collisionQuadrants = 0b1011;
}
else
{
quadRotation = (quadrant + _loc.direction) & 1;
collisionQuadrants = 0b1010;
}
}
}
else
{
quadRotation = ((quadrant ^ 2) + _loc.direction) & 3;
collisionQuadrants = 0b0011;
}
uint8_t supports = 0;
if (!supportsRequired)
{
supports = 0b1111;
}
QuarterTile quarterTile = QuarterTile{ collisionQuadrants, supports }.Rotate(quadRotation);
money32 clearCost = 0;
if (!map_can_construct_with_clear_at(
_loc.x, _loc.y, zLow, zHigh, &map_place_scenery_clear_func, quarterTile, GetFlags(), &clearCost,
CREATE_CROSSING_MODE_NONE))
{
return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_POSITION_THIS_HERE, gGameCommandErrorText, gCommonFormatArgs);
}
gSceneryGroundFlags = gMapGroundFlags & (ELEMENT_IS_ABOVE_GROUND | ELEMENT_IS_UNDERGROUND);
res->ExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
res->Cost = (sceneryEntry->small_scenery.price * 10) + clearCost;
return res;
}
GameActionResult::Ptr Execute() const override
{
bool supportsRequired = false;
if (_loc.z != 0)
{
supportsRequired = true;
}
int32_t baseHeight = tile_element_height(_loc.x, _loc.y);
// If on water
if (baseHeight & 0xFFFF0000)
{
baseHeight >>= 16;
}
auto res = MakeResult();
res->Position.x = _loc.x + 16;
res->Position.y = _loc.y + 16;
res->Position.z = baseHeight;
if (_loc.z != 0)
{
baseHeight = _loc.z;
res->Position.z = baseHeight;
}
rct_scenery_entry* sceneryEntry = get_small_scenery_entry(_sceneryType);
if (sceneryEntry == nullptr)
{
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_POSITION_THIS_HERE);
}
auto quadrant = _quadrant;
if (scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_FULL_TILE)
|| !scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_DIAGONAL))
{
if (scenery_small_entry_has_flag(
sceneryEntry,
SMALL_SCENERY_FLAG_DIAGONAL | SMALL_SCENERY_FLAG_HALF_SPACE | SMALL_SCENERY_FLAG_THREE_QUARTERS))
{
quadrant = 0;
}
}
// Check if sub tile height is any different compared to actual surface tile height
int32_t x2 = _loc.x;
int32_t y2 = _loc.y;
if (scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_FULL_TILE))
{
x2 += 16;
y2 += 16;
}
else
{
x2 += ScenerySubTileOffsets[quadrant & 3].x - 1;
y2 += ScenerySubTileOffsets[quadrant & 3].y - 1;
}
baseHeight = tile_element_height(x2, y2);
// If on water
if (baseHeight & 0xFFFF0000)
{
// base_height2 is now the water height
baseHeight >>= 16;
}
auto targetHeight = _loc.z;
if (_loc.z == 0)
{
targetHeight = baseHeight;
}
if (!(GetFlags() & GAME_COMMAND_FLAG_GHOST))
{
footpath_remove_litter(_loc.x, _loc.y, targetHeight);
if (!gCheatsDisableClearanceChecks && (scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_NO_WALLS)))
{
wall_remove_at(_loc.x, _loc.y, targetHeight, targetHeight + sceneryEntry->small_scenery.height);
}
}
int32_t zLow = targetHeight / 8;
int32_t zHigh = zLow + ceil2(sceneryEntry->small_scenery.height, 8) / 8;
uint8_t collisionQuadrants = 0b1111;
auto quadRotation{ 0 };
if (!(scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_FULL_TILE)))
{
quadRotation = (quadrant ^ 2);
collisionQuadrants = 0b0001;
}
if (!(scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_HALF_SPACE)))
{
if (scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_DIAGONAL)
&& scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_FULL_TILE))
{
if (scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_THREE_QUARTERS))
{
quadRotation = ((quadrant ^ 2) + _loc.direction) & 3;
collisionQuadrants = 0b1011;
}
else
{
quadRotation = (quadrant + _loc.direction) & 1;
collisionQuadrants = 0b1010;
}
}
}
else
{
quadRotation = ((quadrant ^ 2) + _loc.direction) & 3;
collisionQuadrants = 0b0011;
}
uint8_t supports = 0;
if (!supportsRequired)
{
supports = 0b1111;
}
QuarterTile quarterTile = QuarterTile{ collisionQuadrants, supports }.Rotate(quadRotation);
money32 clearCost = 0;
if (!map_can_construct_with_clear_at(
_loc.x, _loc.y, zLow, zHigh, &map_place_scenery_clear_func, quarterTile, GetFlags(), &clearCost,
CREATE_CROSSING_MODE_NONE))
{
return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_POSITION_THIS_HERE, gGameCommandErrorText, gCommonFormatArgs);
}
gSceneryGroundFlags = gMapGroundFlags & (ELEMENT_IS_ABOVE_GROUND | ELEMENT_IS_UNDERGROUND);
res->ExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
res->Cost = (sceneryEntry->small_scenery.price * 10) + clearCost;
TileElement* newElement = tile_element_insert(_loc.x / 32, _loc.y / 32, zLow, quarterTile.GetBaseQuarterOccupied());
assert(newElement != nullptr);
gSceneryTileElement = newElement;
newElement->SetType(TILE_ELEMENT_TYPE_SMALL_SCENERY);
newElement->SetDirection(_loc.direction);
SmallSceneryElement* sceneryElement = newElement->AsSmallScenery();
sceneryElement->SetSceneryQuadrant(quadrant);
sceneryElement->SetEntryIndex(_sceneryType);
sceneryElement->SetAge(0);
sceneryElement->SetPrimaryColour(_primaryColour);
sceneryElement->SetSecondaryColour(_secondaryColour);
sceneryElement->clearance_height = sceneryElement->base_height + ((sceneryEntry->small_scenery.height + 7) / 8);
if (supportsRequired)
{
sceneryElement->SetNeedsSupports();
}
if (GetFlags() & GAME_COMMAND_FLAG_GHOST)
{
sceneryElement->SetGhost(true);
}
map_invalidate_tile_full(_loc.x, _loc.y);
if (scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_ANIMATED))
{
map_animation_create(2, _loc.x, _loc.y, sceneryElement->base_height);
}
return res;
}
};

View File

@ -31,7 +31,7 @@
// This string specifies which version of network stream current build uses.
// It is used for making sure only compatible builds get connected, even within
// single OpenRCT2 version.
#define NETWORK_STREAM_VERSION "47"
#define NETWORK_STREAM_VERSION "48"
#define NETWORK_STREAM_ID OPENRCT2_VERSION "-" NETWORK_STREAM_VERSION
static rct_peep* _pickup_peep = nullptr;
@ -1958,6 +1958,7 @@ void Network::ProcessGameCommands()
if (mode == NETWORK_MODE_SERVER)
{
// Note these are currently not reached as both commands are ported to GameActions
if (command == GAME_COMMAND_PLACE_SCENERY)
{
player->LastPlaceSceneryTime = player->LastActionTime;

View File

@ -15,6 +15,7 @@
#include "../actions/LargeSceneryRemoveAction.hpp"
#include "../actions/RideSetSetting.hpp"
#include "../actions/RideSetVehiclesAction.hpp"
#include "../actions/SmallSceneryPlaceAction.hpp"
#include "../actions/SmallSceneryRemoveAction.hpp"
#include "../actions/TrackPlaceAction.hpp"
#include "../actions/TrackRemoveAction.hpp"
@ -932,6 +933,7 @@ static int32_t track_design_place_scenery(
switch (entry_type)
{
case OBJECT_TYPE_SMALL_SCENERY:
{
if (mode != 0)
{
continue;
@ -964,15 +966,17 @@ static int32_t track_design_place_scenery(
gGameCommandErrorTitle = STR_CANT_POSITION_THIS_HERE;
cost = game_do_command(
mapCoord.x, flags | (entry_index << 8), mapCoord.y, quadrant | (scenery->primary_colour << 8),
GAME_COMMAND_PLACE_SCENERY, rotation | (scenery->secondary_colour << 16), z);
auto smallSceneryPlace = SmallSceneryPlaceAction(
{ mapCoord.x, mapCoord.y, z, rotation }, quadrant, entry_index, scenery->primary_colour,
scenery->secondary_colour);
if (cost == MONEY32_UNDEFINED)
{
cost = 0;
}
smallSceneryPlace.SetFlags(flags);
auto res = flags & GAME_COMMAND_FLAG_APPLY ? GameActions::ExecuteNested(&smallSceneryPlace)
: GameActions::QueryNested(&smallSceneryPlace);
cost = res->Error == GA_ERROR::OK ? res->Cost : 0;
break;
}
case OBJECT_TYPE_LARGE_SCENERY:
if (mode != 0)
{

View File

@ -206,8 +206,6 @@ void game_command_set_water_height(
int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, int32_t* esi, int32_t* edi, int32_t* ebp);
void game_command_place_banner(
int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, int32_t* esi, int32_t* edi, int32_t* ebp);
void game_command_place_scenery(
int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, int32_t* esi, int32_t* edi, int32_t* ebp);
void game_command_place_wall(int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, int32_t* esi, int32_t* edi, int32_t* ebp);
void game_command_place_large_scenery(
int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, int32_t* esi, int32_t* edi, int32_t* ebp);

View File

@ -65,269 +65,6 @@ static money32 SmallScenerySetColour(
return 0;
}
static money32 SmallSceneryPlace(
int16_t x, int16_t y, uint16_t targetHeight, uint8_t quadrant, uint8_t rotation, uint8_t sceneryType, uint8_t primaryColour,
uint8_t secondaryColour, uint8_t flags)
{
gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
money32 clearCost = 0;
bool isOnWater = false;
bool supportsRequired = false;
if (targetHeight != 0)
{
supportsRequired = true;
}
int32_t baseHeight = tile_element_height(x, y);
// If on water
if (baseHeight & 0xFFFF0000)
{
baseHeight >>= 16;
}
gCommandPosition.x = x;
gCommandPosition.y = y;
gCommandPosition.z = baseHeight;
if (targetHeight != 0)
{
baseHeight = targetHeight;
gCommandPosition.z = baseHeight;
}
gCommandPosition.x += 16;
gCommandPosition.y += 16;
if (game_is_paused() && !gCheatsBuildInPauseMode)
{
gGameCommandErrorText = STR_CONSTRUCTION_NOT_POSSIBLE_WHILE_GAME_IS_PAUSED;
return MONEY32_UNDEFINED;
}
if (!map_check_free_elements_and_reorganise(1))
{
return MONEY32_UNDEFINED;
}
if (!byte_9D8150 && (x > gMapSizeMaxXY || y > gMapSizeMaxXY))
{
return MONEY32_UNDEFINED;
}
rct_scenery_entry* sceneryEntry = get_small_scenery_entry(sceneryType);
if (sceneryEntry == nullptr)
{
return MONEY32_UNDEFINED;
}
if (scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_FULL_TILE)
|| !scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_DIAGONAL))
{
if (scenery_small_entry_has_flag(
sceneryEntry, SMALL_SCENERY_FLAG_DIAGONAL | SMALL_SCENERY_FLAG_HALF_SPACE | SMALL_SCENERY_FLAG_THREE_QUARTERS))
{
quadrant = 0;
}
}
// Check if sub tile height is any different compared to actual surface tile height
int32_t x2 = x;
int32_t y2 = y;
if (scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_FULL_TILE))
{
x2 += 16;
y2 += 16;
}
else
{
x2 += ScenerySubTileOffsets[quadrant & 3].x - 1;
y2 += ScenerySubTileOffsets[quadrant & 3].y - 1;
}
baseHeight = tile_element_height(x2, y2);
// If on water
if (baseHeight & 0xFFFF0000)
{
// base_height2 is now the water height
baseHeight >>= 16;
if (targetHeight == 0)
{
isOnWater = true;
}
}
if (targetHeight == 0)
{
targetHeight = baseHeight;
}
if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !gCheatsSandboxMode && !map_is_location_owned(x, y, targetHeight))
{
return MONEY32_UNDEFINED;
}
if (flags & GAME_COMMAND_FLAG_APPLY && !(flags & GAME_COMMAND_FLAG_GHOST))
{
footpath_remove_litter(x, y, targetHeight);
if (!gCheatsDisableClearanceChecks && (scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_NO_WALLS)))
{
wall_remove_at(x, y, targetHeight, targetHeight + sceneryEntry->small_scenery.height);
}
}
TileElement* surfaceElement = map_get_surface_element_at({ x, y });
if (surfaceElement != nullptr && !gCheatsDisableClearanceChecks && surfaceElement->AsSurface()->GetWaterHeight() > 0)
{
int32_t water_height = (surfaceElement->AsSurface()->GetWaterHeight() * 16) - 1;
if (water_height > targetHeight)
{
gGameCommandErrorText = STR_CANT_BUILD_THIS_UNDERWATER;
return MONEY32_UNDEFINED;
}
}
if (!gCheatsDisableClearanceChecks && !(scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_STACKABLE)))
{
if (isOnWater)
{
gGameCommandErrorText = STR_CAN_ONLY_BUILD_THIS_ON_LAND;
return MONEY32_UNDEFINED;
}
if (surfaceElement != nullptr && surfaceElement->AsSurface()->GetWaterHeight() > 0)
{
if ((surfaceElement->AsSurface()->GetWaterHeight() * 16) > targetHeight)
{
gGameCommandErrorText = STR_CAN_ONLY_BUILD_THIS_ON_LAND;
return MONEY32_UNDEFINED;
}
}
}
if (!gCheatsDisableClearanceChecks && (scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_REQUIRE_FLAT_SURFACE))
&& !supportsRequired && !isOnWater && surfaceElement != nullptr
&& (surfaceElement->AsSurface()->GetSlope() != TILE_ELEMENT_SLOPE_FLAT))
{
gGameCommandErrorText = STR_LEVEL_LAND_REQUIRED;
return MONEY32_UNDEFINED;
}
if (!gCheatsDisableSupportLimits && !(scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_STACKABLE))
&& supportsRequired)
{
if (!isOnWater)
{
if (surfaceElement != nullptr)
{
if (surfaceElement->AsSurface()->GetWaterHeight() || (surfaceElement->base_height * 8) != targetHeight)
{
gGameCommandErrorText = STR_LEVEL_LAND_REQUIRED;
return MONEY32_UNDEFINED;
}
}
}
else
{
gGameCommandErrorText = STR_CAN_ONLY_BUILD_THIS_ON_LAND;
return MONEY32_UNDEFINED;
}
}
int32_t zLow = targetHeight / 8;
int32_t zHigh = zLow + ceil2(sceneryEntry->small_scenery.height, 8) / 8;
uint8_t collisionQuadrants = 0b1111;
auto quadRotation{ 0 };
if (!(scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_FULL_TILE)))
{
quadRotation = (quadrant ^ 2);
collisionQuadrants = 0b0001;
}
if (!(scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_HALF_SPACE)))
{
if (scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_DIAGONAL)
&& scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_FULL_TILE))
{
if (scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_THREE_QUARTERS))
{
quadRotation = ((quadrant ^ 2) + rotation) & 3;
collisionQuadrants = 0b1011;
}
else
{
quadRotation = (quadrant + rotation) & 1;
collisionQuadrants = 0b1010;
}
}
}
else
{
quadRotation = ((quadrant ^ 2) + rotation) & 3;
collisionQuadrants = 0b0011;
}
uint8_t supports = 0;
if (!supportsRequired)
{
supports |= 0xF0;
}
QuarterTile quarterTile = QuarterTile{ collisionQuadrants, supports }.Rotate(quadRotation);
if (!map_can_construct_with_clear_at(
x, y, zLow, zHigh, &map_place_scenery_clear_func, quarterTile, flags, &clearCost, CREATE_CROSSING_MODE_NONE))
{
return MONEY32_UNDEFINED;
}
gSceneryGroundFlags = gMapGroundFlags & (ELEMENT_IS_ABOVE_GROUND | ELEMENT_IS_UNDERGROUND);
money32 cost = (sceneryEntry->small_scenery.price * 10) + clearCost;
if (gParkFlags & PARK_FLAGS_NO_MONEY)
{
cost = 0;
}
if (!(flags & GAME_COMMAND_FLAG_APPLY))
{
return cost;
}
if (gGameCommandNestLevel == 1 && !(flags & GAME_COMMAND_FLAG_GHOST))
{
LocationXYZ16 coord;
coord.x = x + 16;
coord.y = y + 16;
coord.z = tile_element_height(coord.x, coord.y);
network_set_player_last_action_coord(network_get_player_index(game_command_playerid), coord);
}
TileElement* newElement = tile_element_insert(x / 32, y / 32, zLow, collisionQuadrants);
assert(newElement != nullptr);
gSceneryTileElement = newElement;
newElement->SetType(TILE_ELEMENT_TYPE_SMALL_SCENERY);
newElement->SetDirection(rotation);
SmallSceneryElement* sceneryElement = newElement->AsSmallScenery();
sceneryElement->SetSceneryQuadrant(quadrant);
sceneryElement->SetEntryIndex(sceneryType);
sceneryElement->SetAge(0);
sceneryElement->SetPrimaryColour(primaryColour);
sceneryElement->SetSecondaryColour(secondaryColour);
newElement->clearance_height = newElement->base_height + ((sceneryEntry->small_scenery.height + 7) / 8);
if (supportsRequired)
{
sceneryElement->SetNeedsSupports();
}
if (flags & GAME_COMMAND_FLAG_GHOST)
{
newElement->SetGhost(true);
}
map_invalidate_tile_full(x, y);
if (scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_ANIMATED))
{
map_animation_create(2, x, y, newElement->base_height);
}
return cost;
}
/**
*
* rct2: 0x006E0F26
@ -412,18 +149,6 @@ int32_t map_place_non_scenery_clear_func(TileElement** tile_element, int32_t x,
return 0;
}
/**
*
* rct2: 0x006E08F4
*/
void game_command_place_scenery(
int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, [[maybe_unused]] int32_t* esi, int32_t* edi, int32_t* ebp)
{
*ebx = SmallSceneryPlace(
*eax & 0xFFFF, *ecx & 0xFFFF, *ebp & 0xFFFF, *edx & 0xFF, *edi & 0xFF, (*ebx >> 8) & 0xFF, (*edx >> 8) & 0xFF,
(*edi >> 16) & 0xFF, *ebx & 0xFF);
}
bool scenery_small_entry_has_flag(const rct_scenery_entry* sceneryEntry, uint32_t flags)
{
return (bool)(sceneryEntry->small_scenery.flags & flags);