diff options
Diffstat (limited to 'source/game/items/StarTools.cpp')
-rw-r--r-- | source/game/items/StarTools.cpp | 709 |
1 files changed, 709 insertions, 0 deletions
diff --git a/source/game/items/StarTools.cpp b/source/game/items/StarTools.cpp new file mode 100644 index 0000000..aef26a5 --- /dev/null +++ b/source/game/items/StarTools.cpp @@ -0,0 +1,709 @@ +#include "StarTools.hpp" +#include "StarRoot.hpp" +#include "StarMaterialDatabase.hpp" +#include "StarJsonExtra.hpp" +#include "StarAssets.hpp" +#include "StarWiring.hpp" +#include "StarWorld.hpp" +#include "StarWorldClient.hpp" +#include "StarParticleDatabase.hpp" + +namespace Star { + +MiningTool::MiningTool(Json const& config, String const& directory, Json const& parameters) + : Item(config, directory, parameters), SwingableItem(config) { + auto assets = Root::singleton().assets(); + + m_image = AssetPath::relativeTo(directory, instanceValue("image").toString()); + m_frames = instanceValue("frames", 1).toInt(); + m_frameCycle = instanceValue("animationCycle", 1.0f).toFloat(); + m_frameTiming = 0; + for (size_t i = 0; i < (size_t)m_frames; i++) + m_animationFrame.append(m_image.replace("{frame}", strf("%s", i))); + m_idleFrame = m_image.replace("{frame}", "idle"); + m_handPosition = jsonToVec2F(instanceValue("handPosition")); + m_blockRadius = instanceValue("blockRadius").toFloat(); + m_altBlockRadius = instanceValue("altBlockRadius").toFloat(); + m_strikeSounds = jsonToStringList(instanceValue("strikeSounds")); + m_breakSound = instanceValue("breakSound", "").toString(); + m_pointable = instanceValue("pointable", false).toBool(); + + m_toolVolume = assets->json("/sfx.config:miningToolVolume").toFloat(); + m_blockVolume = assets->json("/sfx.config:miningBlockVolume").toFloat(); +} + +ItemPtr MiningTool::clone() const { + return make_shared<MiningTool>(*this); +} + +List<Drawable> MiningTool::drawables() const { + if (m_frameTiming == 0) { + return {Drawable::makeImage(m_idleFrame, 1.0f / TilePixels, true, -handPosition() / TilePixels)}; + } else { + int frame = std::max(0, std::min(m_frames - 1, (int)std::floor((m_frameTiming / m_frameCycle) * m_frames))); + return {Drawable::makeImage(m_animationFrame[frame], 1.0f / TilePixels, true, -handPosition() / TilePixels)}; + } +} + +Vec2F MiningTool::handPosition() const { + return m_handPosition; +} + +void MiningTool::fire(FireMode mode, bool shifting, bool edgeTriggered) { + if (!ready()) + return; + + auto materialDatabase = Root::singleton().materialDatabase(); + + if (initialized()) { + bool used = false; + int radius = !shifting ? m_blockRadius : m_altBlockRadius; + String blockSound; + List<Vec2I> brushArea; + + auto layer = (mode == FireMode::Primary ? TileLayer::Foreground : TileLayer::Background); + if (owner()->isAdmin() || owner()->inToolRange()) { + brushArea = tileAreaBrush(radius, owner()->aimPosition(), true); + for (auto pos : brushArea) { + blockSound = materialDatabase->miningSound(world()->material(pos, layer), world()->mod(pos, layer)); + if (!blockSound.empty()) + break; + } + if (blockSound.empty()) { + for (auto pos : brushArea) { + blockSound = materialDatabase->footstepSound(world()->material(pos, layer), world()->mod(pos, layer)); + if (!blockSound.empty() + && blockSound != Root::singleton().assets()->json("/client.config:defaultFootstepSound").toString()) + break; + } + } + + TileDamage damage; + damage.type = TileDamageTypeNames.getLeft(instanceValue("tileDamageType", "blockish").toString()); + + if (durabilityStatus() == 0) + damage.amount = instanceValue("tileDamageBlunted", 0.1f).toFloat(); + else + damage.amount = instanceValue("tileDamage", 1.0f).toFloat(); + + damage.harvestLevel = instanceValue("harvestLevel", 1).toUInt(); + + auto damageResult = world()->damageTiles(brushArea, layer, owner()->position(), damage, owner()->entityId()); + + if (damageResult != TileDamageResult::None) { + used = true; + if (!owner()->isAdmin()) + changeDurability(instanceValue("durabilityPerUse", 1.0f).toFloat()); + } + + if (damageResult == TileDamageResult::Protected) { + blockSound = Root::singleton().assets()->json("/client.config:defaultDingSound").toString(); + } + } + + if (used) { + owner()->addSound(Random::randValueFrom(m_strikeSounds), m_toolVolume); + owner()->addSound(blockSound, m_blockVolume); + List<Particle> miningParticles; + for (auto pos : brushArea) { + if (auto miningParticleConfig = materialDatabase->miningParticle(world()->material(pos, layer), world()->mod(pos, layer))) { + auto miningParticle = miningParticleConfig->instance(); + miningParticle.position += (Vec2F)pos; + miningParticles.append(miningParticle); + } + } + owner()->addParticles(miningParticles); + SwingableItem::fire(mode, shifting, edgeTriggered); + } + } +} + +void MiningTool::update(FireMode mode, bool shifting, HashSet<MoveControlType> const& moves) { + SwingableItem::update(mode, shifting, moves); + + if (!ready() && !coolingDown()) + m_frameTiming = std::fmod((m_frameTiming + WorldTimestep), m_frameCycle); + else + m_frameTiming = 0; +} + +float MiningTool::durabilityStatus() { + return clamp( + 1.0f - instanceValue("durabilityHit", 0.0f).toFloat() / instanceValue("durability").toFloat(), 0.0f, 1.0f); +} + +float MiningTool::getAngle(float aimAngle) { + if ((!ready() && !coolingDown()) || !m_pointable) + return SwingableItem::getAngle(aimAngle); + return aimAngle; +} + +void MiningTool::changeDurability(float amount) { + setInstanceValue("durabilityHit", clamp(instanceValue("durabilityHit", 0.0f).toFloat() + amount, 0.0f, instanceValue("durability").toFloat())); + if (durabilityStatus() == 0.0f && !instanceValue("canBeRepaired", false).toBool()) { + owner()->addSound(m_breakSound); + consume(1); + } +} + +HarvestingTool::HarvestingTool(Json const& config, String const& directory, Json const& parameters) + : Item(config, directory, parameters), SwingableItem(config) { + auto assets = Root::singleton().assets(); + + m_image = AssetPath::relativeTo(directory, instanceValue("image").toString()); + m_frames = instanceValue("frames", 1).toInt(); + m_frameCycle = instanceValue("animationCycle", 1.0f).toFloat(); + for (size_t i = 0; i < (size_t)m_frames; i++) + m_animationFrame.append(m_image.replace("{frame}", strf("%s", i))); + m_idleFrame = m_image.replace("{frame}", "idle"); + + m_handPosition = jsonToVec2F(instanceValue("handPosition")); + m_strikeSounds = jsonToStringList(instanceValue("strikeSounds")); + m_toolVolume = assets->json("/sfx.config:harvestToolVolume").toFloat(); + m_harvestPower = instanceValue("harvestPower", 1.0f).toFloat(); + m_frameTiming = 0; +} + +ItemPtr HarvestingTool::clone() const { + return make_shared<HarvestingTool>(*this); +} + +List<Drawable> HarvestingTool::drawables() const { + if (m_frameTiming == 0) + return {Drawable::makeImage(m_idleFrame, 1.0f / TilePixels, true, -handPosition() / TilePixels)}; + else { + int frame = std::max(0, std::min(m_frames - 1, (int)std::floor((m_frameTiming / m_frameCycle) * m_frames))); + return {Drawable::makeImage(m_animationFrame[frame], 1.0f / TilePixels, true, -handPosition() / TilePixels)}; + } +} + +Vec2F HarvestingTool::handPosition() const { + return m_handPosition; +} + +void HarvestingTool::fire(FireMode mode, bool shifting, bool edgeTriggered) { + if (!ready()) + return; + + if (owner()) { + bool used = false; + + if (owner()->isAdmin() || owner()->inToolRange()) { + auto layer = (mode == FireMode::Primary ? TileLayer::Foreground : TileLayer::Background); + used = world()->damageTile(Vec2I::floor(owner()->aimPosition()), layer, owner()->position(), {TileDamageType::Plantish, m_harvestPower}) != TileDamageResult::None; + } + + if (used) { + owner()->addSound(Random::randValueFrom(m_strikeSounds), m_toolVolume); + SwingableItem::fire(mode, shifting, edgeTriggered); + } + } +} + +void HarvestingTool::update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) { + SwingableItem::update(fireMode, shifting, moves); + + if (!ready() && !coolingDown()) + m_frameTiming = std::fmod((m_frameTiming + WorldTimestep), m_frameCycle); + else + m_frameTiming = 0; +} + +float HarvestingTool::getAngle(float aimAngle) { + if (!ready() && !coolingDown()) + return SwingableItem::getAngle(aimAngle); + return aimAngle; +} + +Flashlight::Flashlight(Json const& config, String const& directory, Json const& parameters) + : Item(config, directory, parameters) { + m_image = AssetPath::relativeTo(directory, instanceValue("image").toString()); + m_handPosition = jsonToVec2F(instanceValue("handPosition")); + m_lightPosition = jsonToVec2F(instanceValue("lightPosition")); + m_lightColor = jsonToColor(instanceValue("lightColor")); + m_beamWidth = instanceValue("beamLevel").toFloat(); + m_ambientFactor = instanceValue("beamAmbience").toFloat(); +} + +ItemPtr Flashlight::clone() const { + return make_shared<Flashlight>(*this); +} + +List<Drawable> Flashlight::drawables() const { + return {Drawable::makeImage(m_image, 1.0f / TilePixels, true, -m_handPosition / TilePixels)}; +} + +List<LightSource> Flashlight::lightSources() const { + if (!initialized()) + return {}; + + float angle = world()->geometry().diff(owner()->aimPosition(), owner()->position()).angle(); + LightSource lightSource; + lightSource.pointLight = true; + lightSource.position = owner()->position() + owner()->handPosition(hand(), (m_lightPosition - m_handPosition) / TilePixels); + lightSource.color = m_lightColor.toRgb(); + lightSource.pointBeam = m_beamWidth; + lightSource.beamAngle = angle; + lightSource.beamAmbience = m_ambientFactor; + return {move(lightSource)}; +} + +WireTool::WireTool(Json const& config, String const& directory, Json const& parameters) + : Item(config, directory, parameters), FireableItem(config), BeamItem(config.setAll(parameters.toObject())) { + auto assets = Root::singleton().assets(); + + m_handPosition = jsonToVec2F(instanceValue("handPosition")); + m_strikeSounds = jsonToStringList(instanceValue("strikeSounds")); + m_toolVolume = assets->json("/sfx.config:miningToolVolume").toFloat(); + m_wireConnector = 0; + m_endType = EndType::Wire; +} + +ItemPtr WireTool::clone() const { + return make_shared<WireTool>(*this); +} + +void WireTool::init(ToolUserEntity* owner, ToolHand hand) { + FireableItem::init(owner, hand); + BeamItem::init(owner, hand); + m_wireConnector = 0; +} + +void WireTool::update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) { + FireableItem::update(fireMode, shifting, moves); + BeamItem::update(fireMode, shifting, moves); +} + +List<Drawable> WireTool::drawables() const { + return BeamItem::drawables(); +} + +List<Drawable> WireTool::nonRotatedDrawables() const { + if (m_wireConnector && m_wireConnector->connecting()) + return BeamItem::nonRotatedDrawables(); + return {}; +} + +void WireTool::setEnd(EndType) { + m_endType = EndType::Wire; +} + +Vec2F WireTool::handPosition() const { + return m_handPosition; +} + +void WireTool::fire(FireMode mode, bool shifting, bool edgeTriggered) { + if (!ready()) + return; + + auto ownerp = owner(); + auto worldp = world(); + + if (ownerp && worldp && m_wireConnector) { + Vec2F pos(ownerp->aimPosition()); + if (ownerp->isAdmin() || ownerp->inToolRange()) { + auto swingResult = m_wireConnector->swing(worldp->geometry(), pos, mode); + if (swingResult == WireConnector::Connect) { + ownerp->addSound(Random::randValueFrom(m_strikeSounds), m_toolVolume); + FireableItem::fire(mode, shifting, edgeTriggered); + } else if (swingResult == WireConnector::Mismatch || swingResult == WireConnector::Protected) { + auto wireErrorSound = Root::singleton().assets()->json("/client.config:wireFailSound").toString(); + ownerp->addSound(wireErrorSound, m_toolVolume); + FireableItem::fire(mode, shifting, edgeTriggered); + } + } + } +} + +float WireTool::getAngle(float aimAngle) { + return BeamItem::getAngle(aimAngle); +} + +void WireTool::setConnector(WireConnector* connector) { + m_wireConnector = connector; +} + +BeamMiningTool::BeamMiningTool(Json const& config, String const& directory, Json const& parameters) + : Item(config, directory, parameters), FireableItem(config), BeamItem(config.setAll(parameters.toObject())) { + auto assets = Root::singleton().assets(); + + m_blockRadius = instanceValue("blockRadius").toFloat(); + m_altBlockRadius = instanceValue("altBlockRadius").toFloat(); + m_tileDamage = instanceValue("tileDamage", 1.0f).toFloat(); + m_harvestLevel = instanceValue("harvestLevel", 1).toUInt(); + m_canCollectLiquid = instanceValue("canCollectLiquid", false).toBool(); + m_strikeSounds = jsonToStringList(instanceValue("strikeSounds")); + m_toolVolume = assets->json("/sfx.config:miningToolVolume").toFloat(); + m_blockVolume = assets->json("/sfx.config:miningBlockVolume").toFloat(); + m_endType = EndType::Object; + + m_inhandStatusEffects = instanceValue("inhandStatusEffects", JsonArray()).toArray().transformed(jsonToPersistentStatusEffect); +} + +ItemPtr BeamMiningTool::clone() const { + return make_shared<BeamMiningTool>(*this); +} + +List<Drawable> BeamMiningTool::drawables() const { + return BeamItem::drawables(); +} + +void BeamMiningTool::setEnd(EndType) { + m_endType = EndType::Object; +} + +List<PreviewTile> BeamMiningTool::preview(bool shifting) const { + List<PreviewTile> result; + auto ownerp = owner(); + auto worldp = world(); + + if (ownerp && worldp) { + if (ownerp->isAdmin() || ownerp->inToolRange()) { + Vec3B light = Color::rgba(ownerp->favoriteColor()).toRgb(); + int radius = !shifting ? m_blockRadius : m_altBlockRadius; + for (auto pos : tileAreaBrush(radius, ownerp->aimPosition(), true)) { + if (worldp->tileIsOccupied(pos, TileLayer::Foreground, true)) { + result.append({pos, true, light, true}); + } else if (worldp->tileIsOccupied(pos, TileLayer::Background, true)) { + result.append({pos, false, light, true}); + } + } + } + } + return result; +} + +void BeamMiningTool::init(ToolUserEntity* owner, ToolHand hand) { + FireableItem::init(owner, hand); + BeamItem::init(owner, hand); +} + +void BeamMiningTool::update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) { + FireableItem::update(fireMode, shifting, moves); + BeamItem::update(fireMode, shifting, moves); +} + +List<PersistentStatusEffect> BeamMiningTool::statusEffects() const { + return m_inhandStatusEffects; +} + +List<Drawable> BeamMiningTool::nonRotatedDrawables() const { + if (!ready() && !coolingDown()) + return BeamItem::nonRotatedDrawables(); + return {}; +} + +void BeamMiningTool::fire(FireMode mode, bool shifting, bool edgeTriggered) { + if (!ready()) + return; + + auto materialDatabase = Root::singleton().materialDatabase(); + + auto worldp = world(); + auto ownerp = owner(); + if (ownerp && worldp) { + bool used = false; + int radius = !shifting ? m_blockRadius : m_altBlockRadius; + String blockSound; + List<Vec2I> brushArea; + + auto layer = (mode == FireMode::Primary ? TileLayer::Foreground : TileLayer::Background); + if (ownerp->isAdmin() || ownerp->inToolRange()) { + brushArea = tileAreaBrush(radius, ownerp->aimPosition(), true); + auto aimPosition = Vec2I(ownerp->aimPosition()); + + for (auto pos : brushArea) { + blockSound = materialDatabase->miningSound(worldp->material(pos, layer), worldp->mod(pos, layer)); + if (!blockSound.empty()) + break; + } + if (blockSound.empty()) { + for (auto pos : brushArea) { + blockSound = materialDatabase->footstepSound(worldp->material(pos, layer), worldp->mod(pos, layer)); + if (!blockSound.empty() + && blockSound != Root::singleton().assets()->json("/client.config:defaultFootstepSound").toString()) + break; + } + } + + auto damageResult = worldp->damageTiles(List<Vec2I>{brushArea}, layer, ownerp->position(), {TileDamageType::Beamish, m_tileDamage, m_harvestLevel}, ownerp->entityId()); + used = damageResult != TileDamageResult::None; + + if (damageResult == TileDamageResult::Protected) { + blockSound = Root::singleton().assets()->json("/client.config:defaultDingSound").toString(); + } + + if (!used && m_canCollectLiquid && layer == TileLayer::Foreground && worldp->material(aimPosition, TileLayer::Foreground) == EmptyMaterialId) { + auto targetLiquid = worldp->liquidLevel(aimPosition).liquid; + List<Vec2I> drainTiles; + float totalLiquid = 0; + for (auto pos : brushArea) { + if (worldp->isTileProtected(pos)) + continue; + + auto liquid = worldp->liquidLevel(pos); + if (liquid.liquid != EmptyLiquidId) { + if (targetLiquid == EmptyLiquidId) + targetLiquid = liquid.liquid; + + if (liquid.liquid == targetLiquid) { + totalLiquid += liquid.level; + drainTiles.append(pos); + } + } + } + + float bucketSize = Root::singleton().assets()->json("/items/defaultParameters.config:liquidItems.bucketSize").toUInt(); + if (totalLiquid >= bucketSize) { + if (auto clientWorld = as<WorldClient>(worldp)) + clientWorld->collectLiquid(drainTiles, targetLiquid); + + blockSound = Root::singleton().assets()->json("/items/defaultParameters.config:liquidBlockSound").toString(); + + used = true; + } + } + } + + if (used) { + ownerp->addSound(Random::randValueFrom(m_strikeSounds), m_toolVolume); + ownerp->addSound(blockSound, m_blockVolume); + List<Particle> miningParticles; + for (auto pos : brushArea) { + if (auto miningParticleConfig = materialDatabase->miningParticle(worldp->material(pos, layer), worldp->mod(pos, layer))) { + auto miningParticle = miningParticleConfig->instance(); + miningParticle.position += (Vec2F)pos; + miningParticles.append(miningParticle); + } + } + ownerp->addParticles(miningParticles); + FireableItem::fire(mode, shifting, edgeTriggered); + } + } +} + +float BeamMiningTool::getAngle(float angle) { + return BeamItem::getAngle(angle); +} + +TillingTool::TillingTool(Json const& config, String const& directory, Json const& parameters) + : Item(config, directory, parameters), SwingableItem(config) { + auto assets = Root::singleton().assets(); + + m_image = AssetPath::relativeTo(directory, instanceValue("image").toString()); + m_frames = instanceValue("frames", 1).toInt(); + m_frameCycle = instanceValue("animationCycle", 1.0f).toFloat(); + for (size_t i = 0; i < (size_t)m_frames; i++) + m_animationFrame.append(m_image.replace("{frame}", strf("%s", i))); + m_idleFrame = m_image.replace("{frame}", "idle"); + + m_handPosition = jsonToVec2F(instanceValue("handPosition")); + m_strikeSounds = jsonToStringList(instanceValue("strikeSounds")); + m_toolVolume = assets->json("/sfx.config:harvestToolVolume").toFloat(); + m_frameTiming = 0; +} + +ItemPtr TillingTool::clone() const { + return make_shared<TillingTool>(*this); +} + +List<Drawable> TillingTool::drawables() const { + if (m_frameTiming == 0) + return {Drawable::makeImage(m_idleFrame, 1.0f / TilePixels, true, -handPosition() / TilePixels)}; + else { + int frame = std::max(0, std::min(m_frames - 1, (int)std::floor((m_frameTiming / m_frameCycle) * m_frames))); + return {Drawable::makeImage(m_animationFrame[frame], 1.0f / TilePixels, true, -handPosition() / TilePixels)}; + } +} + +Vec2F TillingTool::handPosition() const { + return m_handPosition; +} + +void TillingTool::fire(FireMode mode, bool shifting, bool edgeTriggered) { + if (!ready()) + return; + + auto strikeSound = Random::randValueFrom(m_strikeSounds); + + if (owner() && world()) { + auto materialDatabase = Root::singleton().materialDatabase(); + Vec2I pos(owner()->aimPosition().floor()); + + if (world()->material(pos + Vec2I(0, 1), TileLayer::Foreground) != EmptyMaterialId) + return; + + bool used = false; + for (auto layer : {TileLayer::Foreground, TileLayer::Background}) { + if (world()->material(pos, layer) == EmptyMaterialId) + pos = pos - Vec2I(0, 1); + + if ((layer == TileLayer::Background) + && world()->material(pos + Vec2I(0, 1), TileLayer::Background) != EmptyMaterialId) + continue; + + if (owner()->isAdmin() || owner()->inToolRange()) { + auto currentMod = world()->mod(pos, layer); + auto material = world()->material(pos, layer); + auto tilledMod = materialDatabase->tilledModFor(material); + + if (tilledMod != NoModId && currentMod == NoModId) { + if (world()->modifyTile(pos, PlaceMod{layer, tilledMod, MaterialHue()}, true)) + used = true; + } else if (currentMod != tilledMod) { + auto damageResult = world()->damageTile(pos, layer, owner()->position(), {TileDamageType::Tilling, 1.0f}); + used = damageResult != TileDamageResult::None; + if (damageResult == TileDamageResult::Protected) { + strikeSound = Root::singleton().assets()->json("/client.config:defaultDingSound").toString(); + } + } + } + } + + if (used) { + owner()->addSound(strikeSound, m_toolVolume); + SwingableItem::fire(mode, shifting, edgeTriggered); + } + } +} + +void TillingTool::update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) { + SwingableItem::update(fireMode, shifting, moves); + + if (!ready() && !coolingDown()) + m_frameTiming = std::fmod((m_frameTiming + WorldTimestep), m_frameCycle); + else + m_frameTiming = 0; +} + +float TillingTool::getAngle(float aimAngle) { + if (!ready() && !coolingDown()) + return SwingableItem::getAngle(aimAngle); + return aimAngle; +} + +PaintingBeamTool::PaintingBeamTool(Json const& config, String const& directory, Json const& parameters) + : Item(config, directory, parameters), FireableItem(config), BeamItem(config) { + auto assets = Root::singleton().assets(); + + m_blockRadius = instanceValue("blockRadius").toFloat(); + m_altBlockRadius = instanceValue("altBlockRadius").toFloat(); + m_strikeSounds = jsonToStringList(instanceValue("strikeSounds")); + m_toolVolume = assets->json("/sfx.config:miningToolVolume").toFloat(); + m_blockVolume = assets->json("/sfx.config:miningBlockVolume").toFloat(); + m_endType = EndType::Object; + + for (auto color : instanceValue("colorNumbers").toArray()) + m_colors.append(jsonToColor(color)); + + m_colorKeys = jsonToStringList(instanceValue("colorKeys")); + + m_colorIndex = instanceValue("colorIndex", 0).toInt(); + m_color = m_colors[m_colorIndex].toRgba(); +} + +ItemPtr PaintingBeamTool::clone() const { + return make_shared<PaintingBeamTool>(*this); +} + +List<Drawable> PaintingBeamTool::drawables() const { + auto result = BeamItem::drawables(); + for (auto& entry : result) { + if (entry.isImage()) + entry.imagePart().image = entry.imagePart().image + m_colorKeys[m_colorIndex]; + } + return result; +} + +void PaintingBeamTool::setEnd(EndType type) { + _unused(type); + m_endType = EndType::Object; +} + +void PaintingBeamTool::update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) { + BeamItem::update(fireMode, shifting, moves); + FireableItem::update(fireMode, shifting, moves); +} + +List<PreviewTile> PaintingBeamTool::preview(bool shifting) const { + List<PreviewTile> result; + auto ownerp = owner(); + auto worldp = world(); + if (ownerp && worldp) { + Vec3B light = Color::White.toRgb(); + + if (ownerp->isAdmin() || ownerp->inToolRange()) { + int radius = !shifting ? m_blockRadius : m_altBlockRadius; + + for (auto pos : tileAreaBrush(radius, ownerp->aimPosition(), true)) { + if (worldp->canModifyTile(pos, PlaceMaterialColor{TileLayer::Foreground, (MaterialColorVariant)m_colorIndex}, true)) { + result.append({pos, true, NullMaterialId, MaterialHue(), false, light, true, (MaterialColorVariant)m_colorIndex}); + } else if (worldp->canModifyTile(pos, PlaceMaterialColor{TileLayer::Background, (MaterialColorVariant)m_colorIndex}, true)) { + result.append({pos, false, NullMaterialId, MaterialHue(), false, light, true, (MaterialColorVariant)m_colorIndex}); + } else if (worldp->canModifyTile(pos, PlaceMaterialColor{TileLayer::Foreground, DefaultMaterialColorVariant}, true)) { + result.append({pos, true, NullMaterialId, MaterialHue(), false, light, true, DefaultMaterialColorVariant}); + } else if (worldp->canModifyTile(pos, PlaceMaterialColor{TileLayer::Background, DefaultMaterialColorVariant}, true)) { + result.append({pos, false, NullMaterialId, MaterialHue(), false, light, true, DefaultMaterialColorVariant}); + } + } + } + } + + return result; +} + +void PaintingBeamTool::init(ToolUserEntity* owner, ToolHand hand) { + FireableItem::init(owner, hand); + BeamItem::init(owner, hand); + m_color = m_colors[m_colorIndex].toRgba(); +} + +List<Drawable> PaintingBeamTool::nonRotatedDrawables() const { + if (!coolingDown()) + return BeamItem::nonRotatedDrawables(); + return {}; +} + +void PaintingBeamTool::fire(FireMode mode, bool shifting, bool edgeTriggered) { + if (!ready()) + return; + + if (mode == FireMode::Alt && edgeTriggered) { + m_colorIndex = (m_colorIndex + 1) % m_colors.size(); + m_color = m_colors[m_colorIndex].toRgba(); + setInstanceValue("colorIndex", m_colorIndex); + return; + } + + if (mode == FireMode::Primary) { + auto worldp = world(); + auto ownerp = owner(); + if (ownerp && worldp) { + bool used = false; + int radius = !shifting ? m_blockRadius : m_altBlockRadius; + + if (ownerp->isAdmin() || ownerp->inToolRange()) { + for (auto pos : tileAreaBrush(radius, ownerp->aimPosition(), true)) { + TileModificationList modifications = { + {pos, PlaceMaterialColor{TileLayer::Foreground, (MaterialColorVariant)m_colorIndex}}, + {pos, PlaceMaterialColor{TileLayer::Background, (MaterialColorVariant)m_colorIndex}} + }; + auto failed = worldp->applyTileModifications(modifications, true); + if (failed.count() < 2) + used = true; + } + } + + if (used) { + ownerp->addSound(Random::randValueFrom(m_strikeSounds), m_toolVolume); + FireableItem::fire(mode, shifting, edgeTriggered); + } + } + } +} + +float PaintingBeamTool::getAngle(float angle) { + return BeamItem::getAngle(angle); +} + +} |