Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/game/StarBiomePlacement.cpp
diff options
context:
space:
mode:
authorKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
committerKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
commit6352e8e3196f78388b6c771073f9e03eaa612673 (patch)
treee23772f79a7fbc41bc9108951e9e136857484bf4 /source/game/StarBiomePlacement.cpp
parent6741a057e5639280d85d0f88ba26f000baa58f61 (diff)
everything everywhere
all at once
Diffstat (limited to 'source/game/StarBiomePlacement.cpp')
-rw-r--r--source/game/StarBiomePlacement.cpp298
1 files changed, 298 insertions, 0 deletions
diff --git a/source/game/StarBiomePlacement.cpp b/source/game/StarBiomePlacement.cpp
new file mode 100644
index 0000000..50786a8
--- /dev/null
+++ b/source/game/StarBiomePlacement.cpp
@@ -0,0 +1,298 @@
+#include "StarBiomePlacement.hpp"
+#include "StarJsonExtra.hpp"
+#include "StarLogging.hpp"
+#include "StarRoot.hpp"
+#include "StarAssets.hpp"
+
+namespace Star {
+
+BiomeItem variantToBiomeItem(Json const& store) {
+ auto type = store.get(0);
+ if (type == "grass") {
+ return GrassVariant(store.get(1));
+ } else if (type == "bush") {
+ return BushVariant(store.get(1));
+ } else if (type == "treePair") {
+ return TreePair(TreeVariant(store.get(1).get(0)), TreeVariant(store.get(1).get(1)));
+ } else if (type == "objectPool") {
+ return ObjectPool(store.getArray(1).transformed([](Json const& pair) {
+ return make_pair(pair.getFloat(0), make_pair(pair.get(1).getString(0), pair.get(1).get(1)));
+ }));
+ } else if (type == "treasureBoxSet") {
+ return TreasureBoxSet(store.getString(1));
+ } else if (type == "microDungeon") {
+ return MicroDungeonNames(jsonToStringSet(store.get(1)));
+ } else {
+ throw BiomeException(strf("Unrecognized biome item type '%s'", type));
+ }
+}
+
+Json variantFromBiomeItem(BiomeItem const& biomeItem) {
+ if (auto grassVariant = biomeItem.ptr<GrassVariant>()) {
+ return JsonArray{"grass", grassVariant->toJson()};
+ } else if (auto bushVariant = biomeItem.ptr<BushVariant>()) {
+ return JsonArray{"bush", bushVariant->toJson()};
+ } else if (auto treePair = biomeItem.ptr<TreePair>()) {
+ return JsonArray{"treePair", JsonArray{treePair->first.toJson(), treePair->second.toJson()}};
+ } else if (auto objectPool = biomeItem.ptr<ObjectPool>()) {
+ return JsonArray{"objectPool", transform<JsonArray>(objectPool->items(), [](pair<double, pair<String, Json>> const& p) {
+ return JsonArray{p.first, JsonArray{p.second.first, p.second.second}};
+ })};
+ } else if (auto treasureBoxSet = biomeItem.ptr<TreasureBoxSet>()) {
+ return JsonArray{"treasureBoxSet", String(*treasureBoxSet)};
+ } else if (auto microDungeonNames = biomeItem.ptr<MicroDungeonNames>()) {
+ return JsonArray{"microDungeon", jsonFromStringSet(*microDungeonNames)};
+ } else {
+ throw BiomeException(strf("Unrecognized biome item type"));
+ }
+}
+
+EnumMap<BiomePlacementMode> const BiomePlacementModeNames{
+ {BiomePlacementMode::Floor, "floor"},
+ {BiomePlacementMode::Ceiling, "ceiling"},
+ {BiomePlacementMode::Background, "background"},
+ {BiomePlacementMode::Ocean, "ocean"}
+};
+
+BiomeItemPlacement::BiomeItemPlacement(BiomeItem item, Vec2I position, float priority)
+ : item(move(item)), position(position), priority(priority) {}
+
+bool BiomeItemPlacement::operator<(BiomeItemPlacement const& rhs) const {
+ return priority < rhs.priority;
+}
+
+Maybe<BiomeItem> BiomeItemDistribution::createItem(Json const& config, RandomSource& rand, float biomeHueShift) {
+ auto& root = Root::singleton();
+
+ auto type = config.getString("type");
+ if (type.equalsIgnoreCase("grass")) {
+ auto grassList = jsonToStringList(config.get("grasses"));
+ return BiomeItem{root.plantDatabase()->buildGrassVariant(rand.randFrom(grassList), biomeHueShift)};
+
+ } else if (type.equalsIgnoreCase("bush")) {
+ auto bushList = config.getArray("bushes", {});
+ auto bushSettings = rand.randValueFrom(bushList);
+
+ auto bushName = bushSettings.getString("name");
+ auto bushMod = rand.randValueFrom(root.plantDatabase()->bushMods(bushName));
+ float bushBaseHueShift = rand.randf(-1.0f, 1.0f) * bushSettings.getFloat("baseHueShiftMax");
+ float bushModHueShift = rand.randf(-1.0f, 1.0f) * bushSettings.getFloat("modHueShiftMax");
+
+ return BiomeItem{root.plantDatabase()->buildBushVariant(bushName, bushBaseHueShift, bushMod, bushModHueShift)};
+
+ } else if (type.equalsIgnoreCase("tree")) {
+ auto stemList = jsonToStringList(config.get("treeStemList", JsonArray()));
+ auto foliageList = jsonToStringList(config.get("treeFoliageList", JsonArray()));
+
+ // Find matching pairs of stem / foliage (that have the same shape)
+ List<pair<String, String>> matchingPairs;
+ for (auto stem : stemList) {
+ for (auto foliage : foliageList) {
+ if (foliage.empty() || root.plantDatabase()->treeStemShape(stem) == root.plantDatabase()->treeFoliageShape(foliage))
+ matchingPairs.append({stem, foliage});
+ }
+ }
+
+ if (matchingPairs.empty() && !stemList.empty() && !foliageList.empty())
+ Logger::warn("Specified stemList and foliageList, but no matching pairs found.");
+
+ auto chosenPair = rand.randValueFrom(matchingPairs);
+ float treeStemHueShift = rand.randf(-1.0f, 1.0f) * config.getFloat("treeStemHueShiftMax", 0);
+ float treeFoliageHueShift = rand.randf(-1.0f, 1.0f) * config.getFloat("treeFoliageHueShiftMax", 0);
+ float treeAltFoliageHueShift = rand.randf(-1.0f, 1.0f) * config.getFloat("treeFoliageHueShiftMax", 0);
+
+ if (!chosenPair.first.empty()) {
+ TreeVariant primaryTree;
+ TreeVariant altTree;
+ if (chosenPair.second.empty()) {
+ // Foliage-less trees
+ primaryTree = root.plantDatabase()->buildTreeVariant(chosenPair.first, treeStemHueShift);
+ altTree = root.plantDatabase()->buildTreeVariant(chosenPair.first, treeStemHueShift);
+ } else {
+ primaryTree = root.plantDatabase()->buildTreeVariant(
+ chosenPair.first, treeStemHueShift, chosenPair.second, treeFoliageHueShift);
+ altTree = root.plantDatabase()->buildTreeVariant(
+ chosenPair.first, treeStemHueShift, chosenPair.second, treeAltFoliageHueShift);
+ }
+ return BiomeItem{TreePair{primaryTree, altTree}};
+ }
+
+ } else if (type.equalsIgnoreCase("object")) {
+ Json objectPoolConfig = rand.randValueFrom(config.getArray("objectSets"));
+ ObjectPool objectPool;
+
+ Json objectParameters = objectPoolConfig.get("parameters", JsonObject());
+ for (auto const& pair : objectPoolConfig.getArray("pool")) {
+ if (pair.size() != 2)
+ throw BiomeException("Wrong size for objects weight / list pair in biome items");
+
+ objectPool.add(pair.getFloat(0), {pair.getString(1), objectParameters});
+ }
+
+ return BiomeItem{objectPool};
+
+ } else if (type.equalsIgnoreCase("treasureBox")) {
+ return BiomeItem{TreasureBoxSet(rand.randValueFrom(config.getArray("treasureBoxSets")).toString())};
+
+ } else if (type.equalsIgnoreCase("microdungeon")) {
+ return BiomeItem{MicroDungeonNames(jsonToStringSet(config.get("microdungeons", JsonArray())))};
+
+ } else {
+ throw BiomeException(strf("No such item type '%s' in item distribution", type));
+ }
+
+ return {};
+}
+
+BiomeItemDistribution::BiomeItemDistribution() {
+ m_mode = BiomePlacementMode::Floor;
+ m_distribution = DistributionType::Random;
+ m_modulus = 1;
+ m_modulusOffset = 0;
+ m_blockSeed = 0;
+ m_blockProbability = 0.0f;
+ m_priority = 0.0f;
+}
+
+BiomeItemDistribution::BiomeItemDistribution(Json const& config, uint64_t seed, float biomeHueShift) {
+ RandomSource rand(seed);
+
+ m_mode = BiomePlacementModeNames.getLeft(config.getString("mode", "floor"));
+
+ m_priority = config.getFloat("priority", 0.0f);
+ int variants = config.getInt("variants", 1);
+
+ m_modulus = 1;
+ m_modulusOffset = 0;
+ m_blockSeed = 0;
+ m_blockProbability = 0.0f;
+
+ // If distribution settings are string type, it should point to another asset
+ // variant.
+ auto distributionSettings = config.get("distribution", JsonObject());
+ if (distributionSettings.type() == Json::Type::String) {
+ auto assets = Root::singleton().assets();
+ distributionSettings = assets->json(distributionSettings.toString());
+ }
+
+ m_distribution = DistributionTypeNames.getLeft(distributionSettings.getString("type"));
+ if (m_distribution == DistributionType::Random) {
+ m_blockProbability = distributionSettings.getFloat("blockProbability");
+ m_blockSeed = rand.randu64();
+ for (int i = 0; i < variants; ++i) {
+ if (auto item = createItem(config, rand, biomeHueShift))
+ m_randomItems.append(item.take());
+ }
+
+ } else if (m_distribution == DistributionType::Periodic) {
+ unsigned octaves = distributionSettings.getInt("octaves", 1);
+ float alpha = distributionSettings.getFloat("alpha", 2.0);
+ float beta = distributionSettings.getFloat("beta", 2.0);
+
+ float modulusVariance = distributionSettings.getFloat("modulusVariance", 0.0);
+
+ // If density period / offset are not set, just offset a lot to get an even
+ // distribution with no free spaces.
+ float densityPeriod = distributionSettings.getFloat("densityPeriod", 10);
+ float densityOffset = distributionSettings.getFloat("densityOffset", 2.0);
+
+ float typePeriod = distributionSettings.getFloat("typePeriod", 10);
+
+ m_modulus = distributionSettings.getInt("modulus", 1);
+ m_modulusOffset = rand.randInt(-m_modulus, m_modulus);
+ m_densityFunction = PerlinF(octaves, 1.0f / densityPeriod, 1.0, densityOffset, alpha, beta, rand.randu64());
+ m_modulusDistortion = PerlinF(octaves, 1.0f / m_modulus, modulusVariance, modulusVariance * 2, alpha, beta, rand.randu64());
+
+ for (int i = 0; i < variants; ++i) {
+ if (auto item = createItem(config, rand, biomeHueShift)) {
+ PerlinF weight(octaves, 1.0f / typePeriod, 1.0, 0.0, alpha, beta, rand.randu64());
+ m_weightedItems.append({item.take(), weight});
+ }
+ }
+ }
+}
+
+BiomeItemDistribution::BiomeItemDistribution(Json const& store) {
+ m_mode = BiomePlacementModeNames.getLeft(store.getString("mode"));
+ m_distribution = DistributionTypeNames.getLeft(store.getString("distribution"));
+ m_priority = store.getFloat("priority");
+ m_blockProbability = store.getFloat("blockProbability");
+ m_blockSeed = store.getUInt("blockSeed");
+ m_randomItems = store.getArray("randomItems").transformed(variantToBiomeItem);
+ m_densityFunction = PerlinF(store.get("densityFunction"));
+ m_modulusDistortion = PerlinF(store.get("modulusDistortion"));
+ m_modulus = store.getInt("modulus");
+ m_modulusOffset = store.getInt("modulusOffset");
+ m_weightedItems = store.getArray("weightedItems") .transformed([](Json const& v) {
+ return make_pair(variantToBiomeItem(v.get(0)), PerlinF(v.get(1)));
+ });
+}
+
+Json BiomeItemDistribution::toJson() const {
+ return JsonObject{
+ {"mode", BiomePlacementModeNames.getRight(m_mode)},
+ {"distribution", DistributionTypeNames.getRight(m_distribution)},
+ {"priority", m_priority},
+ {"blockProbability", m_blockProbability},
+ {"blockSeed", m_blockSeed},
+ {"randomItems", m_randomItems.transformed(variantFromBiomeItem)},
+ {"densityFunction", m_densityFunction.toJson()},
+ {"modulusDistortion", m_modulusDistortion.toJson()},
+ {"modulus", m_modulus},
+ {"modulusOffset", m_modulusOffset},
+ {"weightedItems", m_weightedItems.transformed([](pair<BiomeItem, PerlinF> const& p) -> Json {
+ return JsonArray{variantFromBiomeItem(p.first), p.second.toJson()};
+ })},
+ };
+}
+
+BiomePlacementMode BiomeItemDistribution::mode() const {
+ return m_mode;
+}
+
+List<BiomeItem> BiomeItemDistribution::allItems() const {
+ if (m_distribution == DistributionType::Random) {
+ return m_randomItems;
+ } else if (m_distribution == DistributionType::Periodic) {
+ List<BiomeItem> items;
+ for (auto const& pair : m_weightedItems)
+ items.append(pair.first);
+ return items;
+ } else {
+ return {};
+ }
+}
+
+Maybe<BiomeItemPlacement> BiomeItemDistribution::itemToPlace(int x, int y) const {
+ if (m_distribution == DistributionType::Random) {
+ if (staticRandomFloat(x, y, m_blockSeed) <= m_blockProbability)
+ return BiomeItemPlacement{staticRandomValueFrom(m_randomItems, x, y, m_blockSeed), Vec2I(x, y), m_priority};
+
+ } else if (m_distribution == DistributionType::Periodic) {
+ BiomeItem const* biomeItem = nullptr;
+ if (m_densityFunction.get(x, y) > 0) {
+ if ((int)(x + m_modulusOffset + m_modulusDistortion.get(x, y)) % m_modulus == 0) {
+ float maxWeight = lowest<float>();
+ for (auto const& weightedItem : m_weightedItems) {
+ float weight = weightedItem.second.get(x, y);
+ if (weight > maxWeight) {
+ maxWeight = weight;
+ biomeItem = &weightedItem.first;
+ }
+ }
+ }
+ }
+
+ if (biomeItem)
+ return BiomeItemPlacement{*biomeItem, Vec2I(x, y), m_priority};
+ }
+
+ return {};
+}
+
+EnumMap<BiomeItemDistribution::DistributionType> const BiomeItemDistribution::DistributionTypeNames{
+ {DistributionType::Random, "random"},
+ {DistributionType::Periodic, "periodic"}
+};
+
+}