Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorchililisoup <rebot333gaming@gmail.com>2025-03-20 15:48:46 -0600
committerchililisoup <rebot333gaming@gmail.com>2025-03-20 15:48:46 -0600
commit3130381b1c23e42af6fc14e9de951638f5c275b1 (patch)
tree7a0b7696ad29d594a84d76f3a190a26ffed65ba1
parent3f761123e939ece5c8805c165dc625756b950f8b (diff)
block swapping
-rw-r--r--assets/opensb/binds/opensb.binds5
-rw-r--r--source/game/StarNetPackets.cpp11
-rw-r--r--source/game/StarNetPackets.hpp11
-rw-r--r--source/game/StarPlayer.cpp4
-rw-r--r--source/game/StarPlayer.hpp1
-rw-r--r--source/game/StarWorldClient.cpp23
-rw-r--r--source/game/StarWorldClient.hpp2
-rw-r--r--source/game/StarWorldImpl.hpp50
-rw-r--r--source/game/StarWorldServer.cpp75
-rw-r--r--source/game/StarWorldServer.hpp7
-rw-r--r--source/game/interfaces/StarWorld.hpp5
-rw-r--r--source/game/items/StarMaterialItem.cpp101
-rw-r--r--source/game/items/StarMaterialItem.hpp2
13 files changed, 287 insertions, 10 deletions
diff --git a/assets/opensb/binds/opensb.binds b/assets/opensb/binds/opensb.binds
index d06b6f4..381bbc9 100644
--- a/assets/opensb/binds/opensb.binds
+++ b/assets/opensb/binds/opensb.binds
@@ -50,6 +50,11 @@
"group": "building",
"name": "Shrink Building Radius"
},
+ "blockSwapToggle": {
+ "default": [],
+ "group": "building",
+ "name": "Toggle Block Swap"
+ },
"editingCopyItemJson": {
"default": [], // [{"type": "key", "value": "C","mods": ["LShift"]}],
"name": "Copy Item in Cursor",
diff --git a/source/game/StarNetPackets.cpp b/source/game/StarNetPackets.cpp
index 2e64eee..54e7825 100644
--- a/source/game/StarNetPackets.cpp
+++ b/source/game/StarNetPackets.cpp
@@ -747,6 +747,17 @@ void ModifyTileListPacket::write(DataStream& ds) const {
ds.write(allowEntityOverlap);
}
+ReplaceTileListPacket::ReplaceTileListPacket(TileModificationList modifications)
+ : modifications(modifications) {}
+
+void ReplaceTileListPacket::read(DataStream& ds) {
+ ds.readContainer(modifications);
+}
+
+void ReplaceTileListPacket::write(DataStream& ds) const {
+ ds.writeContainer(modifications);
+}
+
DamageTileGroupPacket::DamageTileGroupPacket() : layer(TileLayer::Foreground) {}
DamageTileGroupPacket::DamageTileGroupPacket(
diff --git a/source/game/StarNetPackets.hpp b/source/game/StarNetPackets.hpp
index 4002e90..a06045b 100644
--- a/source/game/StarNetPackets.hpp
+++ b/source/game/StarNetPackets.hpp
@@ -79,6 +79,7 @@ enum class PacketType : uint8_t {
// Packets sent world client -> world server
ModifyTileList,
+ ReplaceTileList,
DamageTileGroup,
CollectLiquid,
RequestDrop,
@@ -627,6 +628,16 @@ struct ModifyTileListPacket : PacketBase<PacketType::ModifyTileList> {
bool allowEntityOverlap;
};
+struct ReplaceTileListPacket : PacketBase<PacketType::ReplaceTileList> {
+ ReplaceTileListPacket();
+ ReplaceTileListPacket(TileModificationList modifications);
+
+ void read(DataStream& ds) override;
+ void write(DataStream& ds) const override;
+
+ TileModificationList modifications;
+};
+
struct DamageTileGroupPacket : PacketBase<PacketType::DamageTileGroup> {
DamageTileGroupPacket();
DamageTileGroupPacket(List<Vec2I> tilePositions, TileLayer layer, Vec2F sourcePosition, TileDamage tileDamage, Maybe<EntityId> sourceEntity);
diff --git a/source/game/StarPlayer.cpp b/source/game/StarPlayer.cpp
index af85cce..43add67 100644
--- a/source/game/StarPlayer.cpp
+++ b/source/game/StarPlayer.cpp
@@ -1273,6 +1273,10 @@ void Player::triggerPickupEvents(ItemPtr const& item) {
}
}
+ItemPtr Player::essentialItem(EssentialItem essentialItem) const {
+ return m_inventory->essentialItem(essentialItem);
+}
+
bool Player::hasItem(ItemDescriptor const& descriptor, bool exactMatch) const {
return m_inventory->hasItem(descriptor, exactMatch);
}
diff --git a/source/game/StarPlayer.hpp b/source/game/StarPlayer.hpp
index 1a6a5c2..5cc6f8f 100644
--- a/source/game/StarPlayer.hpp
+++ b/source/game/StarPlayer.hpp
@@ -223,6 +223,7 @@ public:
void triggerPickupEvents(ItemPtr const& item);
+ ItemPtr essentialItem(EssentialItem essentialItem) const;
bool hasItem(ItemDescriptor const& descriptor, bool exactMatch = false) const;
uint64_t hasCountOfItem(ItemDescriptor const& descriptor, bool exactMatch = false) const;
// altough multiple entries may match, they might have different
diff --git a/source/game/StarWorldClient.cpp b/source/game/StarWorldClient.cpp
index 82c09ae..79c3817 100644
--- a/source/game/StarWorldClient.cpp
+++ b/source/game/StarWorldClient.cpp
@@ -375,6 +375,29 @@ TileModificationList WorldClient::applyTileModifications(TileModificationList co
return failures;
}
+TileModificationList WorldClient::replaceTiles(TileModificationList const& modificationList) {
+ if (!inWorld())
+ return {};
+
+ TileModificationList success, failures;
+ for (auto pair : modificationList) {
+ if (!isTileProtected(pair.first) && WorldImpl::validateTileReplacement(pair.first, pair.second))
+ success.append(pair);
+ else
+ failures.append(pair);
+ }
+
+ m_outgoingPackets.append(make_shared<ReplaceTileListPacket>(std::move(success)));
+
+ return failures;
+}
+
+bool WorldClient::damageWouldDestroy(Vec2I const& pos, TileLayer layer, TileDamage const& tileDamage) const {
+ if (!inWorld())
+ return false;
+ return WorldImpl::damageWouldDestroy(m_tileArray, pos, layer, tileDamage);
+}
+
float WorldClient::gravity(Vec2F const& pos) const {
if (!inWorld())
return 0.0f;
diff --git a/source/game/StarWorldClient.hpp b/source/game/StarWorldClient.hpp
index 3c8728d..b19e987 100644
--- a/source/game/StarWorldClient.hpp
+++ b/source/game/StarWorldClient.hpp
@@ -53,6 +53,8 @@ public:
LiquidLevel liquidLevel(RectF const& region) const override;
TileModificationList validTileModifications(TileModificationList const& modificationList, bool allowEntityOverlap) const override;
TileModificationList applyTileModifications(TileModificationList const& modificationList, bool allowEntityOverlap) override;
+ TileModificationList replaceTiles(TileModificationList const& modificationList) override;
+ bool damageWouldDestroy(Vec2I const& pos, TileLayer layer, TileDamage const& tileDamage) const override;
EntityPtr entity(EntityId entityId) const override;
void addEntity(EntityPtr const& entity, EntityId entityId = NullEntityId) override;
EntityPtr closestEntity(Vec2F const& center, float radius, EntityFilter selector = EntityFilter()) const override;
diff --git a/source/game/StarWorldImpl.hpp b/source/game/StarWorldImpl.hpp
index a567ae6..a54f78d 100644
--- a/source/game/StarWorldImpl.hpp
+++ b/source/game/StarWorldImpl.hpp
@@ -39,6 +39,9 @@ namespace WorldImpl {
List<Vec2I> collidingTilesAlongLine(WorldGeometry const& worldGeometry, shared_ptr<TileSectorArray> const& tileSectorArray,
Vec2F const& begin, Vec2F const& end, CollisionSet const& collisionSet, size_t maxSize, bool includeEdges);
+ template <typename TileSectorArray>
+ bool damageWouldDestroy(shared_ptr<TileSectorArray> const& tileSectorArray, Vec2I pos, TileLayer layer, TileDamage const& tileDamage);
+
template <typename GetTileFunction>
bool canPlaceMaterial(EntityMapPtr const& entityMap,
Vec2I const& pos, TileLayer layer, MaterialId material, bool allowEntityOverlap, bool allowTileOverlap, GetTileFunction& getTile);
@@ -54,6 +57,7 @@ namespace WorldImpl {
bool canPlaceMod(Vec2I const& pos, TileLayer layer, ModId mod, GetTileFunction& getTile);
template <typename GetTileFunction>
pair<bool, bool> validateTileModification(EntityMapPtr const& entityMap, Vec2I const& pos, TileModification const& modification, bool allowEntityOverlap, GetTileFunction& getTile);
+ bool validateTileReplacement(Vec2I const& pos, TileModification const& modification);
// Split modification list into good and bad
template <typename GetTileFunction>
pair<TileModificationList, TileModificationList> splitTileModifications(EntityMapPtr const& entityMap, TileModificationList const& modificationList,
@@ -218,6 +222,37 @@ namespace WorldImpl {
return res;
}
+ template <typename TileSectorArray>
+ bool damageWouldDestroy(shared_ptr<TileSectorArray> const& tileSectorArray, Vec2I pos, TileLayer layer, TileDamage const& tileDamage) {
+ if (auto tile = tileSectorArray->modifyTile(pos)) {
+ bool foreground = layer == TileLayer::Foreground;
+ auto damage = foreground ? tile->foregroundDamage : tile->backgroundDamage;
+ if (damage.damageProtected())
+ return false;
+
+ auto materialDatabase = Root::singleton().materialDatabase();
+ auto target = foreground ? tile->foreground : tile->background;
+ auto mod = foreground ? tile->foregroundMod : tile->backgroundMod;
+ TileDamageParameters damageParameters;
+
+ if (isRealMod(mod)) {
+ if (tileDamageIsPenetrating(tileDamage.type))
+ damageParameters = materialDatabase->materialDamageParameters(target);
+ else if (materialDatabase->modBreaksWithTile(mod))
+ damageParameters = materialDatabase->modDamageParameters(mod).sum(materialDatabase->materialDamageParameters(target));
+ else
+ damageParameters = materialDatabase->modDamageParameters(mod);
+ } else {
+ damageParameters = materialDatabase->materialDamageParameters(target);
+ }
+
+ float percentageDelta = damageParameters.damageDone(tileDamage) / damageParameters.totalHealth();
+ return percentageDelta + damage.damagePercentage() >= 1.0f;
+ }
+
+ return false;
+ }
+
template <typename GetTileFunction>
bool canPlaceMaterial(EntityMapPtr const& entityMap,
Vec2I const& pos, TileLayer layer, MaterialId material, bool allowEntityOverlap, bool allowTileOverlap, GetTileFunction& getTile) {
@@ -349,6 +384,21 @@ namespace WorldImpl {
return { good, perhaps };
}
+ bool validateTileReplacement(Vec2I const& pos, TileModification const& modification) {
+ if (auto placeMaterial = modification.ptr<PlaceMaterial>()) {
+ if (!isRealMaterial(placeMaterial->material))
+ return false;
+
+ auto materialDatabase = Root::singleton().materialDatabase();
+ if (!materialDatabase->canPlaceInLayer(placeMaterial->material, placeMaterial->layer))
+ return false;
+
+ return true;
+ }
+
+ return false;
+ }
+
template <typename GetTileFunction>
pair<TileModificationList, TileModificationList> splitTileModifications(EntityMapPtr const& entityMap, TileModificationList const& modificationList,
bool allowEntityOverlap, GetTileFunction& getTile, function<bool(Vec2I pos, TileModification modification)> extraCheck) {
diff --git a/source/game/StarWorldServer.cpp b/source/game/StarWorldServer.cpp
index 0c2a089..830a6ca 100644
--- a/source/game/StarWorldServer.cpp
+++ b/source/game/StarWorldServer.cpp
@@ -373,6 +373,11 @@ void WorldServer::handleIncomingPackets(ConnectionId clientId, List<PacketPtr> c
if (!unappliedModifications.empty())
clientInfo->outgoingPackets.append(make_shared<TileModificationFailurePacket>(unappliedModifications));
+ } else if (auto rtpacket = as<ReplaceTileListPacket>(packet)) {
+ auto unappliedModifications = replaceTiles(rtpacket->modifications);
+ if (!unappliedModifications.empty())
+ clientInfo->outgoingPackets.append(make_shared<TileModificationFailurePacket>(unappliedModifications));
+
} else if (auto dtgpacket = as<DamageTileGroupPacket>(packet)) {
damageTiles(dtgpacket->tilePositions, dtgpacket->layer, dtgpacket->sourcePosition, dtgpacket->tileDamage, dtgpacket->sourceEntity);
@@ -875,6 +880,55 @@ TileModificationList WorldServer::forceApplyTileModifications(TileModificationLi
return doApplyTileModifications(modificationList, allowEntityOverlap, true);
}
+bool WorldServer::replaceTile(Vec2I const& pos, TileModification const& modification) {
+ if (isTileProtected(pos))
+ return false;
+
+ if (!WorldImpl::validateTileReplacement(pos, modification))
+ return false;
+
+ if (auto placeMaterial = modification.ptr<PlaceMaterial>()) {
+ if (!isTileConnectable(pos, placeMaterial->layer, true))
+ return false;
+
+ if (auto tile = m_tileArray->modifyTile(pos)) {
+ Vec2F dropPosition = centerOfTile(pos);
+ auto damage = placeMaterial->layer == TileLayer::Foreground ? tile->foregroundDamage : tile->backgroundDamage;
+ for (auto drop : destroyBlock(placeMaterial->layer, pos, damage.harvested(), !tileDamageIsPenetrating(damage.damageType()), false))
+ addEntity(ItemDrop::createRandomizedDrop(drop, dropPosition));
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+TileModificationList WorldServer::replaceTiles(TileModificationList const& modificationList) {
+ TileModificationList success, failures;
+
+ for (auto pair : modificationList) {
+ if (replaceTile(pair.first, pair.second))
+ success.append(pair);
+ else
+ failures.append(pair);
+ }
+
+ failures.appendAll(doApplyTileModifications(success, true, false, false));
+
+ for (auto pair : success) {
+ checkEntityBreaks(RectF::withSize(Vec2F(pair.first), Vec2F(1, 1)));
+ m_liquidEngine->visitLocation(pair.first);
+ m_fallingBlocksAgent->visitLocation(pair.first);
+ }
+
+ return failures;
+}
+
+bool WorldServer::damageWouldDestroy(Vec2I const& pos, TileLayer layer, TileDamage const& tileDamage) const {
+ return WorldImpl::damageWouldDestroy(m_tileArray, pos, layer, tileDamage);
+}
+
TileDamageResult WorldServer::damageTiles(List<Vec2I> const& positions, TileLayer layer, Vec2F const& sourcePosition, TileDamage const& damage, Maybe<EntityId> sourceEntity) {
Set<Vec2I> positionSet;
for (auto const& pos : positions)
@@ -1394,7 +1448,7 @@ Maybe<unsigned> WorldServer::shouldRunThisStep(String const& timingConfiguration
return {};
}
-TileModificationList WorldServer::doApplyTileModifications(TileModificationList const& modificationList, bool allowEntityOverlap, bool ignoreTileProtection) {
+TileModificationList WorldServer::doApplyTileModifications(TileModificationList const& modificationList, bool allowEntityOverlap, bool ignoreTileProtection, bool updateNeighbors) {
auto materialDatabase = Root::singleton().materialDatabase();
TileModificationList unapplied = modificationList;
@@ -1454,9 +1508,12 @@ TileModificationList WorldServer::doApplyTileModifications(TileModificationList
tile->dungeonId = ConstructionDungeonId;
- checkEntityBreaks(RectF::withSize(Vec2F(pos), Vec2F(1, 1)));
- m_liquidEngine->visitLocation(pos);
- m_fallingBlocksAgent->visitLocation(pos);
+ if (updateNeighbors) {
+ checkEntityBreaks(RectF::withSize(Vec2F(pos), Vec2F(1, 1)));
+ m_liquidEngine->visitLocation(pos);
+ m_fallingBlocksAgent->visitLocation(pos);
+ }
+
if (placeMaterial->layer == TileLayer::Foreground)
dirtyCollision(RectI::withSize(pos, {1, 1}));
queueTileUpdates(pos);
@@ -1737,7 +1794,7 @@ void WorldServer::setLiquid(Vec2I const& pos, LiquidId liquid, float level, floa
}
}
-List<ItemDescriptor> WorldServer::destroyBlock(TileLayer layer, Vec2I const& pos, bool genItems, bool destroyModFirst) {
+List<ItemDescriptor> WorldServer::destroyBlock(TileLayer layer, Vec2I const& pos, bool genItems, bool destroyModFirst, bool updateNeighbors) {
auto materialDatabase = Root::singleton().materialDatabase();
auto* tile = m_tileArray->modifyTile(pos);
@@ -1803,9 +1860,11 @@ List<ItemDescriptor> WorldServer::destroyBlock(TileLayer layer, Vec2I const& pos
tile->dungeonId = DestroyedBlockDungeonId;
- checkEntityBreaks(RectF::withSize(Vec2F(pos), Vec2F(1, 1)));
- m_liquidEngine->visitLocation(pos);
- m_fallingBlocksAgent->visitLocation(pos);
+ if (updateNeighbors) {
+ checkEntityBreaks(RectF::withSize(Vec2F(pos), Vec2F(1, 1)));
+ m_liquidEngine->visitLocation(pos);
+ m_fallingBlocksAgent->visitLocation(pos);
+ }
queueTileUpdates(pos);
queueTileDamageUpdates(pos, layer);
diff --git a/source/game/StarWorldServer.hpp b/source/game/StarWorldServer.hpp
index 63414f7..14080d7 100644
--- a/source/game/StarWorldServer.hpp
+++ b/source/game/StarWorldServer.hpp
@@ -135,6 +135,9 @@ public:
TileModificationList validTileModifications(TileModificationList const& modificationList, bool allowEntityOverlap) const override;
TileModificationList applyTileModifications(TileModificationList const& modificationList, bool allowEntityOverlap) override;
+ bool replaceTile(Vec2I const& pos, TileModification const& modification);
+ TileModificationList replaceTiles(TileModificationList const& modificationList) override;
+ bool damageWouldDestroy(Vec2I const& pos, TileLayer layer, TileDamage const& tileDamage) const override;
EntityPtr entity(EntityId entityId) const override;
void addEntity(EntityPtr const& entity, EntityId entityId = NullEntityId) override;
EntityPtr closestEntity(Vec2F const& center, float radius, EntityFilter selector = EntityFilter()) const override;
@@ -217,7 +220,7 @@ public:
SkyPtr sky() const;
void modifyLiquid(Vec2I const& pos, LiquidId liquid, float quantity, bool additive = false);
void setLiquid(Vec2I const& pos, LiquidId liquid, float level, float pressure);
- List<ItemDescriptor> destroyBlock(TileLayer layer, Vec2I const& pos, bool genItems, bool destroyModFirst);
+ List<ItemDescriptor> destroyBlock(TileLayer layer, Vec2I const& pos, bool genItems, bool destroyModFirst, bool updateNeighbors = true);
void removeEntity(EntityId entityId, bool andDie);
void updateTileEntityTiles(TileEntityPtr const& object, bool removing = false, bool checkBreaks = true);
@@ -314,7 +317,7 @@ private:
// of ticks since the last run.
Maybe<unsigned> shouldRunThisStep(String const& timingConfiguration);
- TileModificationList doApplyTileModifications(TileModificationList const& modificationList, bool allowEntityOverlap, bool ignoreTileProtection = false);
+ TileModificationList doApplyTileModifications(TileModificationList const& modificationList, bool allowEntityOverlap, bool ignoreTileProtection = false, bool updateNeighbors = true);
// Queues pending (step based) updates to the given player
void queueUpdatePackets(ConnectionId clientId, bool sendRemoteUpdates);
diff --git a/source/game/interfaces/StarWorld.hpp b/source/game/interfaces/StarWorld.hpp
index d718786..6b6cae8 100644
--- a/source/game/interfaces/StarWorld.hpp
+++ b/source/game/interfaces/StarWorld.hpp
@@ -46,6 +46,11 @@ public:
// Apply a list of tile modifications in the best order to apply as many
// possible, and returns the modifications that could not be applied.
virtual TileModificationList applyTileModifications(TileModificationList const& modificationList, bool allowEntityOverlap) = 0;
+ // Swap existing tiles for ones defined in the modification list,
+ // and returns the modifications that could not be applied.
+ virtual TileModificationList replaceTiles(TileModificationList const& modificationList) = 0;
+ // If an applied damage would destroy a tile
+ virtual bool damageWouldDestroy(Vec2I const& pos, TileLayer layer, TileDamage const& tileDamage) const = 0;
virtual bool isTileProtected(Vec2I const& pos) const = 0;
diff --git a/source/game/items/StarMaterialItem.cpp b/source/game/items/StarMaterialItem.cpp
index bd42bac..6d862ff 100644
--- a/source/game/items/StarMaterialItem.cpp
+++ b/source/game/items/StarMaterialItem.cpp
@@ -10,6 +10,7 @@
#include "StarInput.hpp"
#include "StarTileDrawer.hpp"
#include "StarPlayer.hpp"
+#include "StarTools.hpp"
namespace Star {
@@ -17,6 +18,7 @@ constexpr int BlockRadiusLimit = 16;
const String BlockRadiusPropertyKey = "building.blockRadius";
const String AltBlockRadiusPropertyKey = "building.altBlockRadius";
const String CollisionOverridePropertyKey = "building.collisionOverride";
+const String BlockSwapPropertyKey = "building.blockSwap";
MaterialItem::MaterialItem(Json const& config, String const& directory, Json const& settings)
: Item(config, directory, settings), FireableItem(config), BeamItem(config) {
@@ -42,6 +44,7 @@ MaterialItem::MaterialItem(Json const& config, String const& directory, Json con
m_blockRadius = config.getFloat("blockRadius", defaultParameters.getFloat("blockRadius"));
m_altBlockRadius = config.getFloat("altBlockRadius", defaultParameters.getFloat("altBlockRadius"));
m_collisionOverride = TileCollisionOverrideNames.maybeLeft(config.getString("collisionOverride", "None")).value(TileCollisionOverride::None);
+ m_blockSwap = false;
m_multiplace = config.getBool("allowMultiplace", BlockCollisionSet.contains(materialDatabase->materialCollisionKind(m_material)));
m_placeSounds = jsonToStringList(config.get("placeSounds", JsonArray()));
@@ -114,6 +117,13 @@ void MaterialItem::update(float dt, FireMode fireMode, bool shifting, HashSet<Mo
player->setSecretProperty(BlockRadiusPropertyKey, m_blockRadius);
owner()->addSound("/sfx/tools/buildradiusshrink.wav", 1.0f, 1.0f + m_blockRadius / BlockRadiusLimit);
}
+
+ if (auto presses = input.bindDown("opensb", "blockSwapToggle")) {
+ if (*presses % 2 != 0)
+ m_blockSwap = !m_blockSwap;
+ player->setSecretProperty(BlockSwapPropertyKey, m_blockSwap);
+ owner()->addSound(m_blockSwap ? "/sfx/interface/button/click.wav" : "/sfx/interface/button/release.wav", 1.0f, Random::randf(0.9f, 1.1f));
+ }
}
else
updatePropertiesFromPlayer(player);
@@ -190,6 +200,9 @@ void MaterialItem::fire(FireMode mode, bool shifting, bool edgeTriggered) {
? collisionKindFromOverride(m_collisionOverride)
: Root::singleton().materialDatabase()->materialCollisionKind(m_material);
+ if (m_blockSwap && owner()->inToolRange(aimPosition))
+ blockSwap(radius, layer);
+
size_t total = 0;
for (unsigned i = 0; i != steps; ++i) {
auto placementOrigin = aimPosition + diff * (1.0f - (static_cast<float>(i) / steps));
@@ -223,6 +236,90 @@ void MaterialItem::endFire(FireMode, bool) {
m_lastAimPosition.reset();
}
+void MaterialItem::blockSwap(float radius, TileLayer layer) {
+ Player* player = as<Player>(owner());
+ if (!player)
+ return;
+
+ ItemPtr beamAxePtr = player->essentialItem(EssentialItem::BeamAxe);
+ if (!beamAxePtr)
+ return;
+
+ Item* beamAxe = beamAxePtr.get();
+ BeamMiningTool* tool = as<BeamMiningTool>(beamAxe);
+ if (!tool)
+ return;
+
+ List<Vec2I> swapPositions;
+ for (Vec2I& pos : tileArea(radius, owner()->aimPosition())) {
+ if (!world()->isTileConnectable(pos, layer, true))
+ continue;
+ if (world()->material(pos, layer) == materialId())
+ continue;
+ swapPositions.append(pos);
+ }
+
+ if (swapPositions.empty())
+ return;
+
+ auto materialDatabase = Root::singleton().materialDatabase();
+ auto assets = Root::singleton().assets();
+ String blockSound;
+
+ for (auto pos : swapPositions) {
+ blockSound = materialDatabase->miningSound(world()->material(pos, layer), world()->mod(pos, layer));
+ if (!blockSound.empty())
+ break;
+ }
+ if (blockSound.empty()) {
+ for (auto pos : swapPositions) {
+ blockSound = materialDatabase->footstepSound(world()->material(pos, layer), world()->mod(pos, layer));
+ if (!blockSound.empty()
+ && blockSound != assets->json("/client.config:defaultFootstepSound").toString())
+ break;
+ }
+ }
+
+ TileDamage damage;
+ damage.type = TileDamageType::Beamish;
+ damage.amount = beamAxe->instanceValue("tileDamage", 1.0f).toFloat();
+ damage.harvestLevel = beamAxe->instanceValue("harvestLevel", 1).toUInt();
+
+ TileModificationList toSwap;
+ List<Vec2I> toDamage;
+ for (auto pos : swapPositions) {
+ if (world()->damageWouldDestroy(pos, layer, damage))
+ toSwap.emplaceAppend(pos, PlaceMaterial{layer, materialId(), placementHueShift(pos), m_collisionOverride});
+ else
+ toDamage.append(pos);
+ }
+
+ if (toSwap.size() > count())
+ toSwap.resize(count());
+ if (toDamage.size() + toSwap.size() > count())
+ toDamage.resize(count() - toSwap.size());
+
+ size_t failed = world()->replaceTiles(toSwap).size();
+ if (failed < toSwap.size())
+ consume(toSwap.size() - failed);
+
+ auto damageResult = world()->damageTiles(toDamage, layer, owner()->position(), damage, owner()->entityId());
+
+ if (damageResult == TileDamageResult::None)
+ return;
+
+ if (damageResult == TileDamageResult::Protected) {
+ blockSound = assets->json("/client.config:defaultDingSound").toString();
+ }
+
+ owner()->addSound(
+ Random::randValueFrom(jsonToStringList(beamAxe->instanceValue("strikeSounds"))),
+ assets->json("/sfx.config:miningToolVolume").toFloat()
+ );
+ owner()->addSound(blockSound, assets->json("/sfx.config:miningBlockVolume").toFloat());
+ setFireTimer(tool->windupTime() + tool->cooldownTime());
+}
+
MaterialId MaterialItem::materialId() const {
return m_material;
}
@@ -280,6 +377,10 @@ void MaterialItem::updatePropertiesFromPlayer(Player* player) {
auto collisionOverride = player->getSecretProperty(CollisionOverridePropertyKey);
if (collisionOverride.isType(Json::Type::String))
m_collisionOverride = TileCollisionOverrideNames.maybeLeft(collisionOverride.toString()).value(TileCollisionOverride::None);
+
+ auto blockSwap = player->getSecretProperty(BlockSwapPropertyKey);
+ if (blockSwap.isType(Json::Type::Bool))
+ m_blockSwap = blockSwap.toBool();
}
float MaterialItem::calcRadius(bool shifting) const {
diff --git a/source/game/items/StarMaterialItem.hpp b/source/game/items/StarMaterialItem.hpp
index 3bbb14c..cb423be 100644
--- a/source/game/items/StarMaterialItem.hpp
+++ b/source/game/items/StarMaterialItem.hpp
@@ -46,6 +46,7 @@ public:
List<PreviewTile> previewTiles(bool shifting) const override;
List<Drawable> const& generatedPreview(Vec2I position = {}) const;
private:
+ void blockSwap(float radius, TileLayer layer);
void updatePropertiesFromPlayer(Player* player);
float calcRadius(bool shifting) const;
List<Vec2I>& tileArea(float radius, Vec2F const& position) const;
@@ -56,6 +57,7 @@ private:
float m_blockRadius;
float m_altBlockRadius;
+ bool m_blockSwap;
bool m_shifting;
bool m_multiplace;
StringList m_placeSounds;