osb/source/game/items/StarTools.cpp
Kai Blaschke 431a9c00a5
Fixed a huge amount of Clang warnings
On Linux and macOS, using Clang to compile OpenStarbound produces about 400 MB worth of warnings during the build, making the compiler output unreadable and slowing the build down considerably.

99% of the warnings were unqualified uses of std::move and std::forward, which are now all properly qualified.

Fixed a few other minor warnings about non-virtual destructors and some uses of std::move preventing copy elision on temporary objects.

Most remaining warnings are now unused parameters.
2024-02-19 16:55:19 +01:00

721 lines
26 KiB
C++

#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}", toString(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(float dt, FireMode mode, bool shifting, HashSet<MoveControlType> const& moves) {
SwingableItem::update(dt, mode, shifting, moves);
if (!ready() && !coolingDown())
m_frameTiming = std::fmod((m_frameTiming + dt), 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}", toString(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(float dt, FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) {
SwingableItem::update(dt, fireMode, shifting, moves);
if (!ready() && !coolingDown())
m_frameTiming = std::fmod((m_frameTiming + dt), 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 {std::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(float dt, FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) {
FireableItem::update(dt, fireMode, shifting, moves);
BeamItem::update(dt, 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;
if (auto jRate = instanceValue("scaleRate")) {
if (jRate.canConvert(Json::Type::Float)) {
float rate = jRate.toFloat();
m_tileDamage /= rate;
m_cooldownTime /= rate;
}
}
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::previewTiles(bool shifting) const {
List<PreviewTile> result;
auto ownerp = owner();
auto worldp = world();
if (ownerp && worldp) {
if (ownerp->isAdmin() || ownerp->inToolRange()) {
Color lightColor = Color::rgba(ownerp->favoriteColor());
if (!ready())
lightColor *= Color::rgbaf(0.75f, 0.75f, 0.75f, 1.0f);
Vec3B light = lightColor.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(float dt, FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) {
FireableItem::update(dt, fireMode, shifting, moves);
BeamItem::update(dt, 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}", toString(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(float dt, FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) {
SwingableItem::update(dt, fireMode, shifting, moves);
if (!ready() && !coolingDown())
m_frameTiming = std::fmod((m_frameTiming + dt), 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.directives += m_colorKeys[m_colorIndex];
}
return result;
}
void PaintingBeamTool::setEnd(EndType type) {
_unused(type);
m_endType = EndType::Object;
}
void PaintingBeamTool::update(float dt, FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) {
BeamItem::update(dt, fireMode, shifting, moves);
FireableItem::update(dt, fireMode, shifting, moves);
}
List<PreviewTile> PaintingBeamTool::previewTiles(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);
}
}