osb/source/game/StarEffectSourceDatabase.cpp
Kae 4b0bc220e4 Support for changing the game's timescale
Context-specific (like per-world) timescales can also be added later
2023-07-21 00:58:49 +10:00

194 lines
6.0 KiB
C++

#include "StarEffectSourceDatabase.hpp"
#include "StarGameTypes.hpp"
#include "StarParticleDatabase.hpp"
#include "StarRoot.hpp"
#include "StarJsonExtra.hpp"
#include "StarRandom.hpp"
#include "StarMixer.hpp"
#include "StarAssets.hpp"
namespace Star {
EffectSourceDatabase::EffectSourceDatabase() {
auto assets = Root::singleton().assets();
auto files = assets->scanExtension("effectsource");
assets->queueJsons(files);
for (auto const& file : files) {
auto sourceConfig = make_shared<EffectSourceConfig>(assets->json(file));
if (m_sourceConfigs.contains(sourceConfig->kind()))
throw StarException(
strf("Duplicate effect source asset kind Name {}. configfile {}", sourceConfig->kind(), file));
auto k = sourceConfig->kind().toLower();
m_sourceConfigs[k] = sourceConfig;
}
}
EffectSourceConfigPtr EffectSourceDatabase::effectSourceConfig(String const& kind) const {
auto k = kind.toLower();
if (!m_sourceConfigs.contains(k))
throw StarException(strf("Unknown effect source definition with kind '{}'.", kind));
return m_sourceConfigs.get(k);
}
EffectSourceConfig::EffectSourceConfig(Json const& config) {
m_kind = config.getString("kind");
m_config = config;
}
String const& EffectSourceConfig::kind() {
return m_kind;
}
EffectSourcePtr EffectSourceConfig::instance(String const& suggestedSpawnLocation) {
return make_shared<EffectSource>(kind(), suggestedSpawnLocation, m_config.getObject("definition"));
}
EffectSource::EffectSource(String const& kind, String suggestedSpawnLocation, Json const& definition) {
m_kind = kind;
m_config = definition;
m_expired = false;
m_loopDuration = m_config.getFloat("duration", 0);
m_durationVariance = m_config.getFloat("durationVariance", 0);
m_loops = m_config.getBool("loops", m_loopDuration != 0);
m_timer = Random::randf() * (m_loopDuration + 0.5 * m_durationVariance);
m_stop = false;
m_initialTick = true;
m_loopTick = false;
m_finalTick = false;
m_effectSpawnLocation = m_config.getString("location", "normal");
m_suggestedSpawnLocation = suggestedSpawnLocation;
}
String const& EffectSource::kind() const {
return m_kind;
}
bool EffectSource::expired() const {
return m_expired;
}
void EffectSource::stop() {
m_stop = true;
}
void EffectSource::tick(float dt) {
m_timer -= dt;
if ((m_timer <= 0) && m_loops) {
m_timer = m_loopDuration + m_durationVariance * Random::randf(-0.5f, 0.5f);
m_loopTick = true;
}
if (m_stop || (m_timer <= 0))
if (!m_expired)
m_finalTick = true;
}
List<String> EffectSource::particles() {
auto pickParticleSources = [](Json const& config, List<String>& particles) {
particles.appendAll(jsonToStringList(Random::randValueFrom(config.toArray(), JsonArray())));
};
List<String> result;
if (m_initialTick)
pickParticleSources(m_config.get("start", JsonObject()).get("particles", JsonArray()), result);
if (m_loopTick)
pickParticleSources(m_config.get("particles", JsonArray()), result);
if (m_finalTick)
pickParticleSources(m_config.get("stop", JsonObject()).get("particles", JsonArray()), result);
return result;
}
List<AudioInstancePtr> EffectSource::sounds(Vec2F offset) {
List<AudioInstancePtr> result;
if (m_initialTick) {
result.appendAll(soundsFromDefinition(m_config.get("start", JsonObject()).get("sounds", Json()), offset));
m_mainSounds = soundsFromDefinition(m_config.get("sounds", Json()), offset);
result.appendAll(m_mainSounds);
}
if (m_finalTick) {
for (auto& s : m_mainSounds)
s->stop();
result.appendAll(soundsFromDefinition(m_config.get("stop", JsonObject()).get("sounds", Json()), offset));
}
return result;
}
void EffectSource::postRender() {
m_initialTick = false;
m_loopTick = false;
if (m_finalTick) {
m_finalTick = false;
m_expired = true;
}
}
String EffectSource::effectSpawnLocation() const {
if ((m_effectSpawnLocation == "normal") && (!m_suggestedSpawnLocation.empty()))
return m_suggestedSpawnLocation;
return m_effectSpawnLocation;
}
String EffectSource::suggestedSpawnLocation() const {
return m_suggestedSpawnLocation;
}
List<Particle> particlesFromDefinition(Json const& config, Vec2F const& position) {
Json particles;
if (config.type() == Json::Type::Array)
particles = Random::randValueFrom(config.toArray(), Json());
else
particles = config;
if (!particles.isNull()) {
if (particles.type() != Json::Type::Array)
particles = JsonArray{particles};
List<Particle> result;
for (auto entry : particles.iterateArray()) {
if (entry.type() != Json::Type::Object) {
result.append(Root::singleton().particleDatabase()->particle(entry.toString()));
} else {
Particle particle(entry.toObject());
Particle variance(entry.getObject("variance", {}));
particle.applyVariance(variance);
particle.position += position;
result.append(particle);
}
}
return result;
}
return {};
}
List<AudioInstancePtr> soundsFromDefinition(Json const& config, Vec2F const& position) {
Json sound;
if (config.type() == Json::Type::Array)
sound = Random::randValueFrom(config.toArray(), Json());
else
sound = config;
if (!sound.isNull()) {
if (sound.type() != Json::Type::Array)
sound = JsonArray{sound};
List<AudioInstancePtr> result;
auto assets = Root::singleton().assets();
for (auto entry : sound.iterateArray()) {
if (entry.type() != Json::Type::Object) {
JsonObject t;
t["resource"] = entry.toString();
entry = t;
}
auto sample = make_shared<AudioInstance>(*assets->audio(entry.getString("resource")));
sample->setLoops(entry.getInt("loops", 0));
sample->setVolume(entry.getFloat("volume", 1.0f));
sample->setPitchMultiplier(entry.getFloat("pitch", 1.0f) + Random::randf(-1, 1) * entry.getFloat("pitchVariability", 0.0f));
sample->setRangeMultiplier(entry.getFloat("audioRangeMultiplier", 1.0f));
sample->setPosition(position);
result.append(move(sample));
}
return result;
}
return {};
}
}