diff options
author | chililisoup <rebot333gaming@gmail.com> | 2025-03-20 15:48:46 -0600 |
---|---|---|
committer | chililisoup <rebot333gaming@gmail.com> | 2025-03-20 15:48:46 -0600 |
commit | 3130381b1c23e42af6fc14e9de951638f5c275b1 (patch) | |
tree | 7a0b7696ad29d594a84d76f3a190a26ffed65ba1 | |
parent | 3f761123e939ece5c8805c165dc625756b950f8b (diff) |
block swapping
-rw-r--r-- | assets/opensb/binds/opensb.binds | 5 | ||||
-rw-r--r-- | source/game/StarNetPackets.cpp | 11 | ||||
-rw-r--r-- | source/game/StarNetPackets.hpp | 11 | ||||
-rw-r--r-- | source/game/StarPlayer.cpp | 4 | ||||
-rw-r--r-- | source/game/StarPlayer.hpp | 1 | ||||
-rw-r--r-- | source/game/StarWorldClient.cpp | 23 | ||||
-rw-r--r-- | source/game/StarWorldClient.hpp | 2 | ||||
-rw-r--r-- | source/game/StarWorldImpl.hpp | 50 | ||||
-rw-r--r-- | source/game/StarWorldServer.cpp | 75 | ||||
-rw-r--r-- | source/game/StarWorldServer.hpp | 7 | ||||
-rw-r--r-- | source/game/interfaces/StarWorld.hpp | 5 | ||||
-rw-r--r-- | source/game/items/StarMaterialItem.cpp | 101 | ||||
-rw-r--r-- | source/game/items/StarMaterialItem.hpp | 2 |
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; |