mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2025-12-23 07:43:01 +01:00
* Fix #20486: Placing track designs with scenery disabled desyncs * Remove pointless error logging, too much log spam * Don't entirely disable all scenery when one element is obstructed * Update changelog.txt * Bump up network version * clang-format
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
0.4.28 (in development)
|
0.4.28 (in development)
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
- Change: [#25089] Peep actions and animations that cause them to stop moving no longer trigger when they are on a level crossing.
|
- Change: [#25089] Peep actions and animations that cause them to stop moving no longer trigger when they are on a level crossing.
|
||||||
|
- Change: [#25337] Placing track designs with scenery that is obstructed no longer disables all of the scenery.
|
||||||
|
- Fix: [#20486] Multiplayer desync when placing track designs without any scenery.
|
||||||
- Fix: [#22779, #25330] Incorrect queue paths in Nevermore Park and Six Flags Holland scenarios (bug in the original scenarios).
|
- Fix: [#22779, #25330] Incorrect queue paths in Nevermore Park and Six Flags Holland scenarios (bug in the original scenarios).
|
||||||
- Fix: [#25190] Inserting a block brake while a coaster is simulating will cause the simulation to behave strangely.
|
- Fix: [#25190] Inserting a block brake while a coaster is simulating will cause the simulation to behave strangely.
|
||||||
- Fix: [#25272] Text colour dropdown in the Banner window is too narrow, resulting in truncated labels.
|
- Fix: [#25272] Text colour dropdown in the Banner window is too narrow, resulting in truncated labels.
|
||||||
|
|||||||
@@ -356,7 +356,7 @@ namespace OpenRCT2::Ui::Windows
|
|||||||
private:
|
private:
|
||||||
void UpdatePreview()
|
void UpdatePreview()
|
||||||
{
|
{
|
||||||
TrackDesignDrawPreview(*_trackDesign, _trackDesignPreviewPixels.data());
|
TrackDesignDrawPreview(*_trackDesign, _trackDesignPreviewPixels.data(), !gTrackDesignSceneryToggle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstallTrackDesign()
|
void InstallTrackDesign()
|
||||||
|
|||||||
@@ -203,7 +203,8 @@ namespace OpenRCT2::Ui::Windows
|
|||||||
if (trackLoc == _placementLoc)
|
if (trackLoc == _placementLoc)
|
||||||
{
|
{
|
||||||
TrackDesignPreviewDrawOutlines(
|
TrackDesignPreviewDrawOutlines(
|
||||||
tds, *_trackDesign, RideGetTemporaryForPreview(), { mapCoords, 0, _currentTrackPieceDirection });
|
tds, *_trackDesign, RideGetTemporaryForPreview(), { mapCoords, 0, _currentTrackPieceDirection },
|
||||||
|
!gTrackDesignSceneryToggle);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +217,7 @@ namespace OpenRCT2::Ui::Windows
|
|||||||
if (res.Error == GameActions::Status::Ok)
|
if (res.Error == GameActions::Status::Ok)
|
||||||
{
|
{
|
||||||
// Valid location found. Place the ghost at the location.
|
// Valid location found. Place the ghost at the location.
|
||||||
auto tdAction = GameActions::TrackDesignAction(trackLoc, *_trackDesign);
|
auto tdAction = GameActions::TrackDesignAction(trackLoc, *_trackDesign, !gTrackDesignSceneryToggle);
|
||||||
tdAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST);
|
tdAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST);
|
||||||
tdAction.SetCallback([&](const GameActions::GameAction*, const GameActions::Result* result) {
|
tdAction.SetCallback([&](const GameActions::GameAction*, const GameActions::Result* result) {
|
||||||
if (result->Error == GameActions::Status::Ok)
|
if (result->Error == GameActions::Status::Ok)
|
||||||
@@ -240,7 +241,8 @@ namespace OpenRCT2::Ui::Windows
|
|||||||
invalidateWidget(WIDX_PRICE);
|
invalidateWidget(WIDX_PRICE);
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackDesignPreviewDrawOutlines(tds, *_trackDesign, RideGetTemporaryForPreview(), trackLoc);
|
TrackDesignPreviewDrawOutlines(
|
||||||
|
tds, *_trackDesign, RideGetTemporaryForPreview(), trackLoc, !gTrackDesignSceneryToggle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onToolDown(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override
|
void onToolDown(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override
|
||||||
@@ -285,7 +287,8 @@ namespace OpenRCT2::Ui::Windows
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto tdAction = GameActions::TrackDesignAction({ trackLoc, _currentTrackPieceDirection }, *_trackDesign);
|
auto tdAction = GameActions::TrackDesignAction(
|
||||||
|
{ trackLoc, _currentTrackPieceDirection }, *_trackDesign, !gTrackDesignSceneryToggle);
|
||||||
tdAction.SetCallback([&](const GameActions::GameAction*, const GameActions::Result* result) {
|
tdAction.SetCallback([&](const GameActions::GameAction*, const GameActions::Result* result) {
|
||||||
if (result->Error != GameActions::Status::Ok)
|
if (result->Error != GameActions::Status::Ok)
|
||||||
{
|
{
|
||||||
@@ -381,7 +384,8 @@ namespace OpenRCT2::Ui::Windows
|
|||||||
{
|
{
|
||||||
if (_hasPlacementGhost)
|
if (_hasPlacementGhost)
|
||||||
{
|
{
|
||||||
auto tdAction = GameActions::TrackDesignAction({ _placementGhostLoc }, *_trackDesign);
|
auto tdAction = GameActions::TrackDesignAction(
|
||||||
|
{ _placementGhostLoc }, *_trackDesign, !gTrackDesignSceneryToggle);
|
||||||
tdAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST);
|
tdAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST);
|
||||||
auto res = GameActions::Execute(&tdAction, getGameState());
|
auto res = GameActions::Execute(&tdAction, getGameState());
|
||||||
if (res.Error != GameActions::Status::Ok)
|
if (res.Error != GameActions::Status::Ok)
|
||||||
@@ -728,7 +732,7 @@ namespace OpenRCT2::Ui::Windows
|
|||||||
for (int32_t i = 0; i < 7; i++, loc.z += kCoordsZStep)
|
for (int32_t i = 0; i < 7; i++, loc.z += kCoordsZStep)
|
||||||
{
|
{
|
||||||
auto tdAction = GameActions::TrackDesignAction(
|
auto tdAction = GameActions::TrackDesignAction(
|
||||||
CoordsXYZD{ loc.x, loc.y, loc.z, _currentTrackPieceDirection }, *_trackDesign);
|
CoordsXYZD{ loc.x, loc.y, loc.z, _currentTrackPieceDirection }, *_trackDesign, !gTrackDesignSceneryToggle);
|
||||||
tdAction.SetFlags(newFlags);
|
tdAction.SetFlags(newFlags);
|
||||||
res = GameActions::Query(&tdAction, getGameState());
|
res = GameActions::Query(&tdAction, getGameState());
|
||||||
|
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ namespace OpenRCT2::Ui::Windows
|
|||||||
_loadedTrackDesign = TrackDesignImport(path.c_str());
|
_loadedTrackDesign = TrackDesignImport(path.c_str());
|
||||||
if (_loadedTrackDesign != nullptr)
|
if (_loadedTrackDesign != nullptr)
|
||||||
{
|
{
|
||||||
TrackDesignDrawPreview(*_loadedTrackDesign, _trackDesignPreviewPixels.data());
|
TrackDesignDrawPreview(*_loadedTrackDesign, _trackDesignPreviewPixels.data(), !gTrackDesignSceneryToggle);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ namespace OpenRCT2::GameActions
|
|||||||
auto* entry = OpenRCT2::ObjectManager::GetObjectEntry<SmallSceneryEntry>(_sceneryType);
|
auto* entry = OpenRCT2::ObjectManager::GetObjectEntry<SmallSceneryEntry>(_sceneryType);
|
||||||
if (entry == nullptr)
|
if (entry == nullptr)
|
||||||
{
|
{
|
||||||
LOG_ERROR("Invalid small scenery type %u", _sceneryType);
|
|
||||||
return Result(Status::InvalidParameters, STR_CANT_REMOVE_THIS, STR_INVALID_SELECTION_OF_OBJECTS);
|
return Result(Status::InvalidParameters, STR_CANT_REMOVE_THIS, STR_INVALID_SELECTION_OF_OBJECTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +102,6 @@ namespace OpenRCT2::GameActions
|
|||||||
TileElement* tileElement = FindSceneryElement();
|
TileElement* tileElement = FindSceneryElement();
|
||||||
if (tileElement == nullptr)
|
if (tileElement == nullptr)
|
||||||
{
|
{
|
||||||
LOG_ERROR("Small scenery of type %u not found at x = %d, y = %d, z = &d", _sceneryType, _loc.x, _loc.y, _loc.z);
|
|
||||||
return Result(Status::InvalidParameters, STR_CANT_REMOVE_THIS, STR_INVALID_SELECTION_OF_OBJECTS);
|
return Result(Status::InvalidParameters, STR_CANT_REMOVE_THIS, STR_INVALID_SELECTION_OF_OBJECTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,10 @@
|
|||||||
|
|
||||||
namespace OpenRCT2::GameActions
|
namespace OpenRCT2::GameActions
|
||||||
{
|
{
|
||||||
TrackDesignAction::TrackDesignAction(const CoordsXYZD& location, const TrackDesign& td)
|
TrackDesignAction::TrackDesignAction(const CoordsXYZD& location, const TrackDesign& td, bool placeScenery)
|
||||||
: _loc(location)
|
: _loc(location)
|
||||||
, _td(td)
|
, _td(td)
|
||||||
|
, _placeScenery(placeScenery)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +51,7 @@ namespace OpenRCT2::GameActions
|
|||||||
|
|
||||||
stream << DS_TAG(_loc);
|
stream << DS_TAG(_loc);
|
||||||
_td.Serialise(stream);
|
_td.Serialise(stream);
|
||||||
|
stream << DS_TAG(_placeScenery);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result TrackDesignAction::Query(GameState_t& gameState) const
|
Result TrackDesignAction::Query(GameState_t& gameState) const
|
||||||
@@ -94,7 +96,7 @@ namespace OpenRCT2::GameActions
|
|||||||
return Result(Status::Unknown, STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE, STR_ERR_RIDE_NOT_FOUND);
|
return Result(Status::Unknown, STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE, STR_ERR_RIDE_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool placeScenery = true;
|
bool placeScenery = _placeScenery;
|
||||||
|
|
||||||
uint32_t flags = 0;
|
uint32_t flags = 0;
|
||||||
if (GetFlags() & GAME_COMMAND_FLAG_GHOST)
|
if (GetFlags() & GAME_COMMAND_FLAG_GHOST)
|
||||||
@@ -169,7 +171,7 @@ namespace OpenRCT2::GameActions
|
|||||||
// Query first, this is required again to determine if scenery is available.
|
// Query first, this is required again to determine if scenery is available.
|
||||||
uint32_t flags = GetFlags() & ~GAME_COMMAND_FLAG_APPLY;
|
uint32_t flags = GetFlags() & ~GAME_COMMAND_FLAG_APPLY;
|
||||||
|
|
||||||
bool placeScenery = true;
|
bool placeScenery = _placeScenery;
|
||||||
|
|
||||||
auto queryRes = TrackDesignPlace(_td, flags, placeScenery, *ride, _loc);
|
auto queryRes = TrackDesignPlace(_td, flags, placeScenery, *ride, _loc);
|
||||||
if (_trackDesignPlaceStateSceneryUnavailable)
|
if (_trackDesignPlaceStateSceneryUnavailable)
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ namespace OpenRCT2::GameActions
|
|||||||
private:
|
private:
|
||||||
CoordsXYZD _loc;
|
CoordsXYZD _loc;
|
||||||
TrackDesign _td;
|
TrackDesign _td;
|
||||||
|
bool _placeScenery{ false };
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TrackDesignAction() = default;
|
TrackDesignAction() = default;
|
||||||
TrackDesignAction(const CoordsXYZD& location, const TrackDesign& td);
|
TrackDesignAction(const CoordsXYZD& location, const TrackDesign& td, bool placeScenery);
|
||||||
|
|
||||||
void AcceptParameters(GameActionParameterVisitor&) final;
|
void AcceptParameters(GameActionParameterVisitor&) final;
|
||||||
|
|
||||||
|
|||||||
@@ -340,7 +340,8 @@ ResultWithMessage TrackDesign::CreateTrackDesignTrack(TrackDesignState& tds, con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackDesignPreviewDrawOutlines(tds, *this, RideGetTemporaryForPreview(), { 4096, 4096, 0, _currentTrackPieceDirection });
|
TrackDesignPreviewDrawOutlines(
|
||||||
|
tds, *this, RideGetTemporaryForPreview(), { 4096, 4096, 0, _currentTrackPieceDirection }, false);
|
||||||
|
|
||||||
// Resave global vars for scenery reasons.
|
// Resave global vars for scenery reasons.
|
||||||
tds.origin = startPos;
|
tds.origin = startPos;
|
||||||
@@ -454,7 +455,8 @@ ResultWithMessage TrackDesign::CreateTrackDesignMaze(TrackDesignState& tds, cons
|
|||||||
|
|
||||||
// Save global vars as they are still used by scenery????
|
// Save global vars as they are still used by scenery????
|
||||||
int32_t startZ = tds.origin.z;
|
int32_t startZ = tds.origin.z;
|
||||||
TrackDesignPreviewDrawOutlines(tds, *this, RideGetTemporaryForPreview(), { 4096, 4096, 0, _currentTrackPieceDirection });
|
TrackDesignPreviewDrawOutlines(
|
||||||
|
tds, *this, RideGetTemporaryForPreview(), { 4096, 4096, 0, _currentTrackPieceDirection }, false);
|
||||||
tds.origin = { startLoc.x, startLoc.y, startZ };
|
tds.origin = { startLoc.x, startLoc.y, startZ };
|
||||||
|
|
||||||
gMapSelectFlags.unset(MapSelectFlag::enableConstruct);
|
gMapSelectFlags.unset(MapSelectFlag::enableConstruct);
|
||||||
@@ -1299,11 +1301,17 @@ static GameActions::Result TrackDesignPlaceAllScenery(
|
|||||||
auto placementRes = TrackDesignPlaceSceneryElement(tds, mapCoord, mode, scenery, rotation, origin.z);
|
auto placementRes = TrackDesignPlaceSceneryElement(tds, mapCoord, mode, scenery, rotation, origin.z);
|
||||||
if (placementRes.Error != GameActions::Status::Ok)
|
if (placementRes.Error != GameActions::Status::Ok)
|
||||||
{
|
{
|
||||||
// Allow operation to fail when its removing ghosts.
|
|
||||||
if (tds.placeOperation != TrackPlaceOperation::removeGhost)
|
if (tds.placeOperation != TrackPlaceOperation::removeGhost)
|
||||||
{
|
{
|
||||||
|
// Allow operation to fail when its removing ghosts.
|
||||||
return placementRes;
|
return placementRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (placementRes.Error == GameActions::Status::NoClearance)
|
||||||
|
{
|
||||||
|
// Some scenery might be obstructed, don't abort the entire operation.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cost += placementRes.Cost;
|
cost += placementRes.Cost;
|
||||||
}
|
}
|
||||||
@@ -1768,11 +1776,6 @@ static GameActions::Result TrackDesignPlaceVirtual(
|
|||||||
tds.previewMax = coords;
|
tds.previewMax = coords;
|
||||||
tds.placeSceneryZ = 0;
|
tds.placeSceneryZ = 0;
|
||||||
|
|
||||||
if (gTrackDesignSceneryToggle)
|
|
||||||
{
|
|
||||||
tds.placeScenery = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: We need to save this, in networked games this would affect all clients otherwise.
|
// NOTE: We need to save this, in networked games this would affect all clients otherwise.
|
||||||
auto savedRideId = _currentRideIndex;
|
auto savedRideId = _currentRideIndex;
|
||||||
auto savedTrackPieceDirection = _currentTrackPieceDirection;
|
auto savedTrackPieceDirection = _currentTrackPieceDirection;
|
||||||
@@ -1840,9 +1843,10 @@ void TrackDesignPreviewRemoveGhosts(const TrackDesign& td, Ride& ride, const Coo
|
|||||||
TrackDesignPlaceVirtual(tds, td, TrackPlaceOperation::removeGhost, true, ride, coords);
|
TrackDesignPlaceVirtual(tds, td, TrackPlaceOperation::removeGhost, true, ride, coords);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrackDesignPreviewDrawOutlines(TrackDesignState& tds, const TrackDesign& td, Ride& ride, const CoordsXYZD& coords)
|
void TrackDesignPreviewDrawOutlines(
|
||||||
|
TrackDesignState& tds, const TrackDesign& td, Ride& ride, const CoordsXYZD& coords, bool placeScenery)
|
||||||
{
|
{
|
||||||
TrackDesignPlaceVirtual(tds, td, TrackPlaceOperation::drawOutlines, true, ride, coords);
|
TrackDesignPlaceVirtual(tds, td, TrackPlaceOperation::drawOutlines, placeScenery, ride, coords);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int32_t TrackDesignGetZPlacement(TrackDesignState& tds, const TrackDesign& td, Ride& ride, const CoordsXYZD& coords)
|
static int32_t TrackDesignGetZPlacement(TrackDesignState& tds, const TrackDesign& td, Ride& ride, const CoordsXYZD& coords)
|
||||||
@@ -1888,7 +1892,7 @@ static money64 TrackDesignCreateRide(int32_t type, int32_t subType, int32_t flag
|
|||||||
* cost = edi
|
* cost = edi
|
||||||
*/
|
*/
|
||||||
static bool TrackDesignPlacePreview(
|
static bool TrackDesignPlacePreview(
|
||||||
TrackDesignState& tds, const TrackDesign& td, Ride** outRide, TrackDesignGameStateData& gameStateData)
|
TrackDesignState& tds, const TrackDesign& td, Ride** outRide, TrackDesignGameStateData& gameStateData, bool placeScenery)
|
||||||
{
|
{
|
||||||
*outRide = nullptr;
|
*outRide = nullptr;
|
||||||
gameStateData.flags = 0;
|
gameStateData.flags = 0;
|
||||||
@@ -1948,7 +1952,6 @@ static bool TrackDesignPlacePreview(
|
|||||||
|
|
||||||
z += 16 - tds.placeSceneryZ;
|
z += 16 - tds.placeSceneryZ;
|
||||||
|
|
||||||
bool placeScenery = true;
|
|
||||||
if (_trackDesignPlaceStateSceneryUnavailable)
|
if (_trackDesignPlaceStateSceneryUnavailable)
|
||||||
{
|
{
|
||||||
placeScenery = false;
|
placeScenery = false;
|
||||||
@@ -2073,7 +2076,7 @@ bool TrackDesignSceneryElement::operator!=(const TrackDesignSceneryElement& rhs)
|
|||||||
*
|
*
|
||||||
* rct2: 0x006D1EF0
|
* rct2: 0x006D1EF0
|
||||||
*/
|
*/
|
||||||
void TrackDesignDrawPreview(TrackDesign& td, uint8_t* pixels)
|
void TrackDesignDrawPreview(TrackDesign& td, uint8_t* pixels, bool placeScenery)
|
||||||
{
|
{
|
||||||
StashMap();
|
StashMap();
|
||||||
TrackDesignPreviewClearMap();
|
TrackDesignPreviewClearMap();
|
||||||
@@ -2087,7 +2090,7 @@ void TrackDesignDrawPreview(TrackDesign& td, uint8_t* pixels)
|
|||||||
|
|
||||||
Ride* ride;
|
Ride* ride;
|
||||||
TrackDesignGameStateData updatedGameStateData = td.gameStateData;
|
TrackDesignGameStateData updatedGameStateData = td.gameStateData;
|
||||||
if (!TrackDesignPlacePreview(tds, td, &ride, updatedGameStateData))
|
if (!TrackDesignPlacePreview(tds, td, &ride, updatedGameStateData, !gTrackDesignSceneryToggle))
|
||||||
{
|
{
|
||||||
std::fill_n(pixels, kTrackPreviewImageSize * 4, 0x00);
|
std::fill_n(pixels, kTrackPreviewImageSize * 4, 0x00);
|
||||||
UnstashMap();
|
UnstashMap();
|
||||||
|
|||||||
@@ -240,13 +240,14 @@ void TrackDesignMirror(TrackDesign& td);
|
|||||||
OpenRCT2::GameActions::Result TrackDesignPlace(
|
OpenRCT2::GameActions::Result TrackDesignPlace(
|
||||||
const TrackDesign& td, uint32_t flags, bool placeScenery, Ride& ride, const CoordsXYZD& coords);
|
const TrackDesign& td, uint32_t flags, bool placeScenery, Ride& ride, const CoordsXYZD& coords);
|
||||||
void TrackDesignPreviewRemoveGhosts(const TrackDesign& td, Ride& ride, const CoordsXYZD& coords);
|
void TrackDesignPreviewRemoveGhosts(const TrackDesign& td, Ride& ride, const CoordsXYZD& coords);
|
||||||
void TrackDesignPreviewDrawOutlines(TrackDesignState& tds, const TrackDesign& td, Ride& ride, const CoordsXYZD& coords);
|
void TrackDesignPreviewDrawOutlines(
|
||||||
|
TrackDesignState& tds, const TrackDesign& td, Ride& ride, const CoordsXYZD& coords, bool placeScenery);
|
||||||
int32_t TrackDesignGetZPlacement(const TrackDesign& td, Ride& ride, const CoordsXYZD& coords);
|
int32_t TrackDesignGetZPlacement(const TrackDesign& td, Ride& ride, const CoordsXYZD& coords);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// Track design preview
|
// Track design preview
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
void TrackDesignDrawPreview(TrackDesign& td, uint8_t* pixels);
|
void TrackDesignDrawPreview(TrackDesign& td, uint8_t* pixels, bool placeScenery);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// Track design saving
|
// Track design saving
|
||||||
|
|||||||
Reference in New Issue
Block a user