#include "StarTileDamage.hpp" #include "StarDataStreamExtra.hpp" #include "StarRoot.hpp" #include "StarJsonExtra.hpp" #include "StarAssets.hpp" namespace Star { List tileAreaBrush(float range, Vec2F const& centerOffset, bool squareMode) { if (range == 0) return {}; List result; float workingRange = range * (squareMode ? 1 : 2) + (squareMode ? 0 : 1); Vec2F offset = Vec2F::filled(-workingRange / 2.0f); Vec2I intOffset = Vec2I::round(offset + centerOffset); for (int x = 0; x < workingRange; ++x) { for (int y = 0; y < workingRange; ++y) { Vec2F fromCenter = Vec2F(x, y) + Vec2F(0.5, 0.5) + offset; // distance to the center of the tile Vec2I intPos = Vec2I(x, y); if (squareMode || fromCenter.magnitude() <= range) { result.append(intPos + intOffset); } } } sort(result, [](Vec2I const& a, Vec2I const& b) { auto ams = a.magnitudeSquared(); auto bms = b.magnitudeSquared(); return std::tie(ams, a) < std::tie(bms, b); }); return result; } EnumMap const TileDamageTypeNames{ {TileDamageType::Protected, "protected"}, {TileDamageType::Plantish, "plantish"}, {TileDamageType::Blockish, "blockish"}, {TileDamageType::Beamish, "beamish"}, {TileDamageType::Explosive, "explosive"}, {TileDamageType::Fire, "fire"}, {TileDamageType::Tilling, "tilling"} }; bool tileDamageIsPenetrating(TileDamageType damageType) { return damageType == TileDamageType::Explosive; } TileDamage::TileDamage() : type(), amount(), harvestLevel() {} TileDamage::TileDamage(TileDamageType type, float amount, unsigned harvestLevel) : type(type), amount(amount), harvestLevel(harvestLevel) {} DataStream& operator>>(DataStream& ds, TileDamage& tileDamage) { ds.read(tileDamage.type); ds.read(tileDamage.amount); ds.read(tileDamage.harvestLevel); return ds; } DataStream& operator<<(DataStream& ds, TileDamage const& tileDamage) { ds.write(tileDamage.type); ds.write(tileDamage.amount); ds.write(tileDamage.harvestLevel); return ds; } TileDamageParameters::TileDamageParameters() : m_damageRecoveryPerSecond(0.0f), m_maximumEffectTime(0.0f), m_totalHealth(0), m_requiredHarvestLevel(0) {} TileDamageParameters::TileDamageParameters(Json config, Maybe healthOverride, Maybe harvestLevelOverride) { if (config.type() == Json::Type::String) config = Root::singleton().assets()->json(config.toString()); for (auto const& pair : config.getObject("damageFactors")) m_damages[TileDamageTypeNames.getLeft(pair.first)] = pair.second.toFloat(); m_damageRecoveryPerSecond = config.getFloat("damageRecovery"); m_requiredHarvestLevel = config.getUInt("harvestLevel", 1); m_maximumEffectTime = config.getFloat("maximumEffectTime", 1.5); m_totalHealth = config.getFloat("totalHealth", 1.0f); if (healthOverride) m_totalHealth = *healthOverride; if (harvestLevelOverride) m_requiredHarvestLevel = *harvestLevelOverride; } float TileDamageParameters::damageDone(TileDamage const& damage) const { return m_damages.value(damage.type) * damage.amount; } float TileDamageParameters::recoveryPerSecond() const { return m_damageRecoveryPerSecond; } unsigned TileDamageParameters::requiredHarvestLevel() const { return m_requiredHarvestLevel; } float TileDamageParameters::maximumEffectTime() const { return m_maximumEffectTime; } float TileDamageParameters::totalHealth() const { return m_totalHealth; } TileDamageParameters TileDamageParameters::sum(TileDamageParameters const& other) const { TileDamageParameters result; result.m_damageRecoveryPerSecond = m_damageRecoveryPerSecond + other.m_damageRecoveryPerSecond; result.m_totalHealth = m_totalHealth + other.m_totalHealth; result.m_requiredHarvestLevel = max(m_requiredHarvestLevel, other.m_requiredHarvestLevel); result.m_maximumEffectTime = max(m_maximumEffectTime, other.m_maximumEffectTime); for (auto key : m_damages.keys()) { if (other.m_damages.contains(key)) result.m_damages[key] = result.m_totalHealth / ((m_totalHealth / m_damages.value(key, 0)) + (other.m_totalHealth / other.m_damages.value(key, 0))); else result.m_damages[key] = m_damages.value(key, 0); } for (auto key : m_damages.keys()) { if (m_damages.contains(key)) result.m_damages[key] = result.m_totalHealth / ((m_totalHealth / m_damages.value(key, 0)) + (other.m_totalHealth / other.m_damages.value(key, 0))); else result.m_damages[key] = other.m_damages.value(key, 0); } return result; } Json TileDamageParameters::toJson() const { return JsonObject{ {"damageFactors", jsonFromMapK>(m_damages, [](TileDamageType a) { return TileDamageTypeNames.getRight(a); })}, {"damageRecovery", m_damageRecoveryPerSecond}, {"requiredHarvestLevel", m_requiredHarvestLevel}, {"maximumEffectTime", m_maximumEffectTime}, {"totalHealth", m_totalHealth} }; } DataStream& operator>>(DataStream& ds, TileDamageParameters& tileDamage) { ds.readMapContainer(tileDamage.m_damages); ds.read(tileDamage.m_damageRecoveryPerSecond); ds.read(tileDamage.m_requiredHarvestLevel); ds.read(tileDamage.m_maximumEffectTime); ds.read(tileDamage.m_totalHealth); return ds; } DataStream& operator<<(DataStream& ds, TileDamageParameters const& tileDamage) { ds.writeMapContainer(tileDamage.m_damages); ds.write(tileDamage.m_damageRecoveryPerSecond); ds.write(tileDamage.m_requiredHarvestLevel); ds.write(tileDamage.m_maximumEffectTime); ds.write(tileDamage.m_totalHealth); return ds; } TileDamageStatus::TileDamageStatus() { reset(); } void TileDamageStatus::reset() { m_damagePercentage = 0.0f; m_damageEffectTimeFactor = 0.0f; m_harvested = false; m_damageSourcePosition = Vec2F(); m_damageType = TileDamageType::Protected; m_damageEffectPercentage = 0.0f; } void TileDamageStatus::damage(TileDamageParameters const& damageParameters, Vec2F const& sourcePosition, TileDamage const& damage) { auto percentageDelta = damageParameters.damageDone(damage) / damageParameters.totalHealth(); m_damagePercentage = min(1.0f, m_damagePercentage + percentageDelta); m_harvested = damage.harvestLevel >= damageParameters.requiredHarvestLevel(); m_damageSourcePosition = sourcePosition; m_damageType = damage.type; if (percentageDelta > 0) m_damageEffectTimeFactor = damageParameters.maximumEffectTime(); updateDamageEffectPercentage(); } void TileDamageStatus::recover(TileDamageParameters const& damageParameters, float dt) { // Once the tile becomes dead, it should not recover from it if (healthy() || dead()) return; m_damagePercentage -= damageParameters.recoveryPerSecond() * dt / damageParameters.totalHealth(); m_damageEffectTimeFactor -= dt; if (m_damagePercentage <= 0.0f) { m_damagePercentage = 0.0f; m_damageEffectTimeFactor = 0.0f; m_damageType = TileDamageType::Protected; } updateDamageEffectPercentage(); } bool TileDamageStatus::healthy() const { return m_damagePercentage <= 0.0f; } bool TileDamageStatus::damaged() const { return m_damagePercentage > 0.0f; } bool TileDamageStatus::damageProtected() const { return m_damageType == TileDamageType::Protected; } bool TileDamageStatus::dead() const { return m_damagePercentage >= 1.0f && m_damageType != TileDamageType::Protected; } bool TileDamageStatus::harvested() const { return m_harvested; } DataStream& operator>>(DataStream& ds, TileDamageStatus& tileDamageStatus) { ds.read(tileDamageStatus.m_damagePercentage); ds.read(tileDamageStatus.m_damageEffectTimeFactor); ds.read(tileDamageStatus.m_harvested); ds.read(tileDamageStatus.m_damageSourcePosition); ds.read(tileDamageStatus.m_damageType); tileDamageStatus.updateDamageEffectPercentage(); return ds; } DataStream& operator<<(DataStream& ds, TileDamageStatus const& tileDamageStatus) { ds.write(tileDamageStatus.m_damagePercentage); ds.write(tileDamageStatus.m_damageEffectTimeFactor); ds.write(tileDamageStatus.m_harvested); ds.write(tileDamageStatus.m_damageSourcePosition); ds.write(tileDamageStatus.m_damageType); return ds; } void TileDamageStatus::updateDamageEffectPercentage() { m_damageEffectPercentage = clamp(m_damageEffectTimeFactor, 0.0f, 1.0f) * m_damagePercentage; } EntityTileDamageStatus::EntityTileDamageStatus() { m_damagePercentage.setFixedPointBase(0.001f); m_damageEffectTimeFactor.setFixedPointBase(0.001f); m_damagePercentage.setInterpolator(lerp); m_damageEffectTimeFactor.setInterpolator(lerp); addNetElement(&m_damagePercentage); addNetElement(&m_damageEffectTimeFactor); addNetElement(&m_damageHarvested); addNetElement(&m_damageType); } float EntityTileDamageStatus::damagePercentage() const { return m_damagePercentage.get(); } float EntityTileDamageStatus::damageEffectPercentage() const { return clamp(m_damageEffectTimeFactor.get(), 0.0f, 1.0f) * m_damagePercentage.get(); } TileDamageType EntityTileDamageStatus::damageType() const { return m_damageType.get(); } void EntityTileDamageStatus::reset() { m_damagePercentage.set(0.0f); m_damageEffectTimeFactor.set(0.0f); m_damageHarvested.set(false); } void EntityTileDamageStatus::damage(TileDamageParameters const& damageParameters, TileDamage const& damage) { auto percentageDelta = damageParameters.damageDone(damage) / damageParameters.totalHealth(); m_damagePercentage.set(min(1.0f, m_damagePercentage.get() + percentageDelta)); m_damageHarvested.set(damage.harvestLevel >= damageParameters.requiredHarvestLevel()); m_damageType.set(damage.type); if (percentageDelta > 0) m_damageEffectTimeFactor.set(damageParameters.maximumEffectTime()); } void EntityTileDamageStatus::recover(TileDamageParameters const& damageParameters, float dt) { // Once the tile becomes dead, it should not recover from it if (healthy() || dead()) return; m_damagePercentage.set(m_damagePercentage.get() - damageParameters.recoveryPerSecond() * dt / damageParameters.totalHealth()); m_damageEffectTimeFactor.set(m_damageEffectTimeFactor.get() - dt); if (m_damagePercentage.get() <= 0.0f) { m_damagePercentage.set(0.0f); m_damageEffectTimeFactor.set(0.0f); m_damageType.set(TileDamageType::Protected); } } bool EntityTileDamageStatus::healthy() const { return m_damagePercentage.get() <= 0.0f; } bool EntityTileDamageStatus::damaged() const { return m_damagePercentage.get() > 0.0f; } bool EntityTileDamageStatus::damageProtected() const { return m_damageType.get() == TileDamageType::Protected; } bool EntityTileDamageStatus::dead() const { return m_damagePercentage.get() >= 1.0f && m_damageType.get() != TileDamageType::Protected; } bool EntityTileDamageStatus::harvested() const { return m_damageHarvested.get(); } }