mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-30 10:15:36 +01:00
464 lines
14 KiB
C++
464 lines
14 KiB
C++
/*****************************************************************************
|
|
* Copyright (c) 2014-2020 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.
|
|
*****************************************************************************/
|
|
|
|
#include "Scenery.h"
|
|
|
|
#include "../Cheats.h"
|
|
#include "../Context.h"
|
|
#include "../Game.h"
|
|
#include "../OpenRCT2.h"
|
|
#include "../actions/BannerRemoveAction.h"
|
|
#include "../actions/FootpathAdditionRemoveAction.h"
|
|
#include "../actions/LargeSceneryRemoveAction.h"
|
|
#include "../actions/SmallSceneryRemoveAction.h"
|
|
#include "../actions/WallRemoveAction.h"
|
|
#include "../common.h"
|
|
#include "../entity/Fountain.h"
|
|
#include "../localisation/Localisation.h"
|
|
#include "../network/network.h"
|
|
#include "../object/ObjectList.h"
|
|
#include "../object/ObjectManager.h"
|
|
#include "../scenario/Scenario.h"
|
|
#include "Climate.h"
|
|
#include "Footpath.h"
|
|
#include "LargeScenery.h"
|
|
#include "Map.h"
|
|
#include "Park.h"
|
|
#include "SmallScenery.h"
|
|
#include "Wall.h"
|
|
|
|
uint8_t gSceneryQuadrant;
|
|
|
|
money32 gSceneryPlaceCost;
|
|
ScenerySelection gSceneryPlaceObject;
|
|
int16_t gSceneryPlaceZ;
|
|
uint8_t gSceneryPlaceRotation;
|
|
|
|
uint8_t gSceneryGhostType;
|
|
CoordsXYZ gSceneryGhostPosition;
|
|
uint8_t gSceneryGhostWallRotation;
|
|
|
|
int16_t gSceneryShiftPressed;
|
|
int16_t gSceneryShiftPressX;
|
|
int16_t gSceneryShiftPressY;
|
|
int16_t gSceneryShiftPressZOffset;
|
|
|
|
int16_t gSceneryCtrlPressed;
|
|
int16_t gSceneryCtrlPressZ;
|
|
|
|
money64 gClearSceneryCost;
|
|
|
|
static std::vector<ScenerySelection> _restrictedScenery;
|
|
|
|
// rct2: 0x009A3E74
|
|
const CoordsXY SceneryQuadrantOffsets[] = {
|
|
{ 7, 7 },
|
|
{ 7, 23 },
|
|
{ 23, 23 },
|
|
{ 23, 7 },
|
|
};
|
|
|
|
void scenery_update_tile(const CoordsXY& sceneryPos)
|
|
{
|
|
TileElement* tileElement;
|
|
|
|
tileElement = map_get_first_element_at(sceneryPos);
|
|
if (tileElement == nullptr)
|
|
return;
|
|
do
|
|
{
|
|
// Ghosts are purely this-client-side and should not cause any interaction,
|
|
// as that may lead to a desync.
|
|
if (network_get_mode() != NETWORK_MODE_NONE)
|
|
{
|
|
if (tileElement->IsGhost())
|
|
continue;
|
|
}
|
|
|
|
if (tileElement->GetTypeN() == TileElementTypeN::SmallScenery)
|
|
{
|
|
tileElement->AsSmallScenery()->UpdateAge(sceneryPos);
|
|
}
|
|
else if (tileElement->GetTypeN() == TileElementTypeN::Path)
|
|
{
|
|
if (tileElement->AsPath()->HasAddition() && !tileElement->AsPath()->AdditionIsGhost())
|
|
{
|
|
auto* pathAddEntry = tileElement->AsPath()->GetAdditionEntry();
|
|
if (pathAddEntry != nullptr)
|
|
{
|
|
if (pathAddEntry->flags & PATH_BIT_FLAG_JUMPING_FOUNTAIN_WATER)
|
|
{
|
|
JumpingFountain::StartAnimation(JumpingFountainType::Water, sceneryPos, tileElement);
|
|
}
|
|
else if (pathAddEntry->flags & PATH_BIT_FLAG_JUMPING_FOUNTAIN_SNOW)
|
|
{
|
|
JumpingFountain::StartAnimation(JumpingFountainType::Snow, sceneryPos, tileElement);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} while (!(tileElement++)->IsLastForTile());
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006E33D9
|
|
*/
|
|
void SmallSceneryElement::UpdateAge(const CoordsXY& sceneryPos)
|
|
{
|
|
auto* sceneryEntry = GetEntry();
|
|
if (sceneryEntry == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (gCheatsDisablePlantAging && sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_CAN_BE_WATERED))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_CAN_BE_WATERED) || WeatherIsDry(gClimateCurrent.Weather) || GetAge() < 5)
|
|
{
|
|
IncreaseAge(sceneryPos);
|
|
return;
|
|
}
|
|
|
|
// Check map elements above, presumably to see if map element is blocked from weather
|
|
TileElement* tileElementAbove = reinterpret_cast<TileElement*>(this);
|
|
// Change from original: RCT2 only checked for the first three quadrants, which was very likely to be a bug.
|
|
while (!(tileElementAbove->GetOccupiedQuadrants()))
|
|
{
|
|
tileElementAbove++;
|
|
|
|
// Ghosts are purely this-client-side and should not cause any interaction,
|
|
// as that may lead to a desync.
|
|
if (tileElementAbove->IsGhost())
|
|
continue;
|
|
|
|
switch (tileElementAbove->GetTypeN())
|
|
{
|
|
case TileElementTypeN::LargeScenery:
|
|
case TileElementTypeN::Entrance:
|
|
case TileElementTypeN::Path:
|
|
map_invalidate_tile_zoom1({ sceneryPos, tileElementAbove->GetBaseZ(), tileElementAbove->GetClearanceZ() });
|
|
IncreaseAge(sceneryPos);
|
|
return;
|
|
case TileElementTypeN::SmallScenery:
|
|
sceneryEntry = tileElementAbove->AsSmallScenery()->GetEntry();
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_VOFFSET_CENTRE))
|
|
{
|
|
IncreaseAge(sceneryPos);
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Reset age / water plant
|
|
SetAge(0);
|
|
map_invalidate_tile_zoom1({ sceneryPos, GetBaseZ(), GetClearanceZ() });
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006E2712
|
|
*/
|
|
void scenery_remove_ghost_tool_placement()
|
|
{
|
|
if (gSceneryGhostType & SCENERY_GHOST_FLAG_0)
|
|
{
|
|
gSceneryGhostType &= ~SCENERY_GHOST_FLAG_0;
|
|
|
|
auto removeSceneryAction = SmallSceneryRemoveAction(
|
|
gSceneryGhostPosition, gSceneryQuadrant, gSceneryPlaceObject.EntryIndex);
|
|
removeSceneryAction.SetFlags(
|
|
GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST);
|
|
removeSceneryAction.Execute();
|
|
}
|
|
|
|
if (gSceneryGhostType & SCENERY_GHOST_FLAG_1)
|
|
{
|
|
gSceneryGhostType &= ~SCENERY_GHOST_FLAG_1;
|
|
TileElement* tileElement = map_get_first_element_at(gSceneryGhostPosition);
|
|
|
|
do
|
|
{
|
|
if (tileElement == nullptr)
|
|
break;
|
|
|
|
if (tileElement->GetTypeN() != TileElementTypeN::Path)
|
|
continue;
|
|
|
|
if (tileElement->GetBaseZ() != gSceneryGhostPosition.z)
|
|
continue;
|
|
|
|
auto footpathAdditionRemoveAction = FootpathAdditionRemoveAction(gSceneryGhostPosition);
|
|
footpathAdditionRemoveAction.SetFlags(GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_GHOST);
|
|
GameActions::Execute(&footpathAdditionRemoveAction);
|
|
break;
|
|
} while (!(tileElement++)->IsLastForTile());
|
|
}
|
|
|
|
if (gSceneryGhostType & SCENERY_GHOST_FLAG_2)
|
|
{
|
|
gSceneryGhostType &= ~SCENERY_GHOST_FLAG_2;
|
|
|
|
CoordsXYZD wallLocation = { gSceneryGhostPosition, gSceneryGhostWallRotation };
|
|
auto wallRemoveAction = WallRemoveAction(wallLocation);
|
|
wallRemoveAction.SetFlags(GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_PATH_SCENERY);
|
|
wallRemoveAction.Execute();
|
|
}
|
|
|
|
if (gSceneryGhostType & SCENERY_GHOST_FLAG_3)
|
|
{
|
|
gSceneryGhostType &= ~SCENERY_GHOST_FLAG_3;
|
|
|
|
auto removeSceneryAction = LargeSceneryRemoveAction({ gSceneryGhostPosition, gSceneryPlaceRotation }, 0);
|
|
removeSceneryAction.SetFlags(
|
|
GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED
|
|
| GAME_COMMAND_FLAG_NO_SPEND);
|
|
removeSceneryAction.Execute();
|
|
}
|
|
|
|
if (gSceneryGhostType & SCENERY_GHOST_FLAG_4)
|
|
{
|
|
gSceneryGhostType &= ~SCENERY_GHOST_FLAG_4;
|
|
|
|
auto removeSceneryAction = BannerRemoveAction({ gSceneryGhostPosition, gSceneryPlaceRotation });
|
|
removeSceneryAction.SetFlags(
|
|
GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND);
|
|
GameActions::Execute(&removeSceneryAction);
|
|
}
|
|
}
|
|
|
|
WallSceneryEntry* get_wall_entry(ObjectEntryIndex entryIndex)
|
|
{
|
|
WallSceneryEntry* result = nullptr;
|
|
auto& objMgr = OpenRCT2::GetContext()->GetObjectManager();
|
|
auto obj = objMgr.GetLoadedObject(ObjectType::Walls, entryIndex);
|
|
if (obj != nullptr)
|
|
{
|
|
result = static_cast<WallSceneryEntry*>(obj->GetLegacyData());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
BannerSceneryEntry* get_banner_entry(ObjectEntryIndex entryIndex)
|
|
{
|
|
BannerSceneryEntry* result = nullptr;
|
|
auto& objMgr = OpenRCT2::GetContext()->GetObjectManager();
|
|
auto obj = objMgr.GetLoadedObject(ObjectType::Banners, entryIndex);
|
|
if (obj != nullptr)
|
|
{
|
|
result = static_cast<BannerSceneryEntry*>(obj->GetLegacyData());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
PathBitEntry* get_footpath_item_entry(ObjectEntryIndex entryIndex)
|
|
{
|
|
PathBitEntry* result = nullptr;
|
|
auto& objMgr = OpenRCT2::GetContext()->GetObjectManager();
|
|
auto obj = objMgr.GetLoadedObject(ObjectType::PathBits, entryIndex);
|
|
if (obj != nullptr)
|
|
{
|
|
result = static_cast<PathBitEntry*>(obj->GetLegacyData());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
rct_scenery_group_entry* get_scenery_group_entry(ObjectEntryIndex entryIndex)
|
|
{
|
|
rct_scenery_group_entry* result = nullptr;
|
|
auto& objMgr = OpenRCT2::GetContext()->GetObjectManager();
|
|
auto obj = objMgr.GetLoadedObject(ObjectType::SceneryGroup, entryIndex);
|
|
if (obj != nullptr)
|
|
{
|
|
result = static_cast<rct_scenery_group_entry*>(obj->GetLegacyData());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int32_t wall_entry_get_door_sound(const WallSceneryEntry* wallEntry)
|
|
{
|
|
return (wallEntry->flags2 & WALL_SCENERY_2_DOOR_SOUND_MASK) >> WALL_SCENERY_2_DOOR_SOUND_SHIFT;
|
|
}
|
|
|
|
bool IsSceneryAvailableToBuild(const ScenerySelection& item)
|
|
{
|
|
// All scenery can be built when in the scenario editor
|
|
if (gScreenFlags & SCREEN_FLAGS_EDITOR)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (!gCheatsIgnoreResearchStatus)
|
|
{
|
|
if (!scenery_is_invented(item))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!gCheatsSandboxMode && !(gScreenFlags & SCREEN_FLAGS_EDITOR))
|
|
{
|
|
if (IsSceneryItemRestricted(item))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static size_t GetMaxObjectsForSceneryType(const uint8_t sceneryType)
|
|
{
|
|
switch (sceneryType)
|
|
{
|
|
case SCENERY_TYPE_SMALL:
|
|
return MAX_SMALL_SCENERY_OBJECTS;
|
|
case SCENERY_TYPE_PATH_ITEM:
|
|
return MAX_PATH_ADDITION_OBJECTS;
|
|
case SCENERY_TYPE_WALL:
|
|
return MAX_WALL_SCENERY_OBJECTS;
|
|
case SCENERY_TYPE_LARGE:
|
|
return MAX_LARGE_SCENERY_OBJECTS;
|
|
case SCENERY_TYPE_BANNER:
|
|
return MAX_BANNER_OBJECTS;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static SceneryEntryBase* GetSceneryEntry(const ScenerySelection& item)
|
|
{
|
|
switch (item.SceneryType)
|
|
{
|
|
case SCENERY_TYPE_SMALL:
|
|
return get_small_scenery_entry(item.EntryIndex);
|
|
case SCENERY_TYPE_PATH_ITEM:
|
|
return get_footpath_item_entry(item.EntryIndex);
|
|
case SCENERY_TYPE_WALL:
|
|
return get_wall_entry(item.EntryIndex);
|
|
case SCENERY_TYPE_LARGE:
|
|
return get_large_scenery_entry(item.EntryIndex);
|
|
case SCENERY_TYPE_BANNER:
|
|
return get_banner_entry(item.EntryIndex);
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
bool IsSceneryItemRestricted(const ScenerySelection& item)
|
|
{
|
|
return std::find(std::begin(_restrictedScenery), std::end(_restrictedScenery), item) != std::end(_restrictedScenery);
|
|
}
|
|
|
|
void ClearRestrictedScenery()
|
|
{
|
|
_restrictedScenery.clear();
|
|
}
|
|
|
|
std::vector<ScenerySelection>& GetRestrictedScenery()
|
|
{
|
|
return _restrictedScenery;
|
|
}
|
|
|
|
static std::vector<ScenerySelection> GetAllMiscScenery()
|
|
{
|
|
std::vector<ScenerySelection> miscScenery;
|
|
std::vector<ScenerySelection> nonMiscScenery;
|
|
for (ObjectEntryIndex i = 0; i < MAX_SCENERY_GROUP_OBJECTS; i++)
|
|
{
|
|
const auto* sgEntry = get_scenery_group_entry(i);
|
|
if (sgEntry != nullptr)
|
|
{
|
|
for (size_t j = 0; j < sgEntry->entry_count; j++)
|
|
{
|
|
nonMiscScenery.push_back(sgEntry->scenery_entries[j]);
|
|
}
|
|
}
|
|
}
|
|
for (uint8_t sceneryType = SCENERY_TYPE_SMALL; sceneryType < SCENERY_TYPE_COUNT; sceneryType++)
|
|
{
|
|
const auto maxObjects = GetMaxObjectsForSceneryType(sceneryType);
|
|
for (ObjectEntryIndex i = 0; i < maxObjects; i++)
|
|
{
|
|
const ScenerySelection sceneryItem = { sceneryType, i };
|
|
const auto* sceneryEntry = GetSceneryEntry(sceneryItem);
|
|
if (sceneryEntry != nullptr)
|
|
{
|
|
if (std::find(std::begin(nonMiscScenery), std::end(nonMiscScenery), sceneryItem) == std::end(nonMiscScenery))
|
|
{
|
|
miscScenery.push_back(sceneryItem);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return miscScenery;
|
|
}
|
|
|
|
void RestrictAllMiscScenery()
|
|
{
|
|
auto miscScenery = GetAllMiscScenery();
|
|
_restrictedScenery.insert(_restrictedScenery.begin(), miscScenery.begin(), miscScenery.end());
|
|
}
|
|
|
|
void MarkAllUnrestrictedSceneryAsInvented()
|
|
{
|
|
auto miscScenery = GetAllMiscScenery();
|
|
for (const auto& sceneryItem : miscScenery)
|
|
{
|
|
if (std::find(_restrictedScenery.begin(), _restrictedScenery.end(), sceneryItem) == _restrictedScenery.end())
|
|
{
|
|
scenery_set_invented(sceneryItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
ObjectType GetObjectTypeFromSceneryType(uint8_t type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case SCENERY_TYPE_SMALL:
|
|
return ObjectType::SmallScenery;
|
|
case SCENERY_TYPE_PATH_ITEM:
|
|
return ObjectType::PathBits;
|
|
case SCENERY_TYPE_WALL:
|
|
return ObjectType::Walls;
|
|
case SCENERY_TYPE_LARGE:
|
|
return ObjectType::LargeScenery;
|
|
case SCENERY_TYPE_BANNER:
|
|
return ObjectType::Banners;
|
|
default:
|
|
throw std::runtime_error("Invalid scenery type");
|
|
}
|
|
}
|
|
|
|
uint8_t GetSceneryTypeFromObjectType(ObjectType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case ObjectType::SmallScenery:
|
|
return SCENERY_TYPE_SMALL;
|
|
case ObjectType::PathBits:
|
|
return SCENERY_TYPE_PATH_ITEM;
|
|
case ObjectType::Walls:
|
|
return SCENERY_TYPE_WALL;
|
|
case ObjectType::LargeScenery:
|
|
return SCENERY_TYPE_LARGE;
|
|
case ObjectType::Banners:
|
|
return SCENERY_TYPE_BANNER;
|
|
default:
|
|
throw std::runtime_error("Invalid object type");
|
|
}
|
|
}
|