osb/source/game/StarTreasure.cpp

220 lines
9.1 KiB
C++
Raw Normal View History

2023-06-20 04:33:09 +00:00
#include "StarTreasure.hpp"
#include "StarObjectDatabase.hpp"
#include "StarRoot.hpp"
#include "StarItemDatabase.hpp"
#include "StarAssets.hpp"
#include "StarItemBag.hpp"
#include "StarWorld.hpp"
#include "StarContainerObject.hpp"
#include "StarJsonExtra.hpp"
namespace Star {
TreasureDatabase::TreasureDatabase() {
auto assets = Root::singleton().assets();
2024-03-15 10:28:11 +00:00
auto& treasurePools = assets->scanExtension("treasurepools");
auto& treasureChests = assets->scanExtension("treasurechests");
2023-06-20 04:33:09 +00:00
assets->queueJsons(treasurePools);
assets->queueJsons(treasureChests);
2024-03-15 10:28:11 +00:00
for (auto& file : treasurePools) {
2023-06-20 04:33:09 +00:00
for (auto const& pair : assets->json(file).iterateObject()) {
if (m_treasurePools.contains(pair.first))
2023-06-27 10:23:44 +00:00
throw TreasureException(strf("Duplicate TreasurePool config '{}' from file '{}'", pair.first, file));
2023-06-20 04:33:09 +00:00
auto& treasurePool = m_treasurePools[pair.first];
for (auto const& entry : pair.second.iterateArray()) {
if (entry.size() != 2)
throw TreasureException("Wrong size for TreasurePool entry, list must be 2");
float startLevel = entry.getFloat(0);
auto config = entry.get(1);
ItemPool itemPool;
for (auto const& entry : config.getArray("fill", {}))
if (entry.contains("pool"))
itemPool.fill.append(entry.getString("pool"));
else if (entry.contains("item"))
itemPool.fill.append(ItemDescriptor(entry.get("item")));
else
2023-06-27 10:23:44 +00:00
throw TreasureException(strf("TreasurePool entry '{}' did not specify a valid 'item' or 'pool'", entry));
2023-06-20 04:33:09 +00:00
for (auto const& entry : config.getArray("pool", {})) {
if (!entry.contains("weight"))
2023-06-27 10:23:44 +00:00
throw TreasureException(strf("TreasurePool entry '{}' did not specify a weight", entry));
2023-06-20 04:33:09 +00:00
if (entry.contains("pool"))
itemPool.pool.add(entry.getFloat("weight"), entry.getString("pool"));
else if (entry.contains("item"))
itemPool.pool.add(entry.getFloat("weight"), ItemDescriptor(entry.get("item")));
else
2023-06-27 10:23:44 +00:00
throw TreasureException(strf("TreasurePool entry '{}' did not specify a valid 'item' or 'pool'", entry));
2023-06-20 04:33:09 +00:00
}
auto poolRounds = config.get("poolRounds", 1);
if (poolRounds.canConvert(Json::Type::Float)) {
itemPool.poolRounds = WeightedPool<int>(List<std::pair<double, int>>{{1.0, poolRounds.toFloat()}});
} else {
for (auto const& pair : poolRounds.iterateArray())
itemPool.poolRounds.add(pair.getDouble(0), pair.getInt(1));
}
itemPool.levelVariance = jsonToVec2F(config.get("levelVariance", JsonArray{0, 0}));
itemPool.allowDuplication = config.getBool("allowDuplication", true);
treasurePool.addPoint(startLevel, std::move(itemPool));
2023-06-20 04:33:09 +00:00
}
}
}
2024-03-15 10:28:11 +00:00
for (auto& file : treasureChests) {
2023-06-20 04:33:09 +00:00
for (auto const& pair : assets->json(file).iterateObject()) {
if (m_treasureChestSets.contains(pair.first))
2023-06-27 10:23:44 +00:00
throw TreasureException(strf("Duplicate TreasureChestSet config '{}' from file '{}'", pair.first, file));
2023-06-20 04:33:09 +00:00
auto& treasureChestSet = m_treasureChestSets[pair.first];
for (auto const& entry : pair.second.iterateArray()) {
TreasureChest treasureChest;
treasureChest.containers = jsonToStringList(entry.get("containers"));
treasureChest.treasurePool = entry.getString("treasurePool");
treasureChest.minimumLevel = entry.getFloat("minimumLevel", 0);
if (!m_treasurePools.contains(treasureChest.treasurePool))
2023-06-27 10:23:44 +00:00
throw TreasureException(strf("No such TreasurePool '{}' for TreasureChestSet named '{}' in file '{}'", treasureChest.treasurePool, pair.first, file));
2023-06-20 04:33:09 +00:00
treasureChestSet.append(treasureChest);
}
}
}
}
StringList TreasureDatabase::treasurePools() const {
return m_treasurePools.keys();
}
bool TreasureDatabase::isTreasurePool(String const& treasurePool) const {
return m_treasurePools.contains(treasurePool);
}
StringList TreasureDatabase::treasureChestSets() const {
return m_treasureChestSets.keys();
}
bool TreasureDatabase::isTreasureChestSet(String const& treasurePool) const {
return m_treasureChestSets.contains(treasurePool);
}
TreasureDatabase::ItemPool::ItemPool() : allowDuplication() {}
TreasureDatabase::TreasureChest::TreasureChest() : minimumLevel() {}
List<ItemPtr> TreasureDatabase::createTreasure(String const& treasurePool, float level) const {
return createTreasure(treasurePool, level, Random::randu64());
}
List<ItemPtr> TreasureDatabase::createTreasure(String const& treasurePool, float level, uint64_t seed) const {
return createTreasure(treasurePool, level, seed, StringSet());
}
List<ItemPtr> TreasureDatabase::createTreasure(String const& treasurePool, float level, uint64_t seed, StringSet visitedPools) const {
if (!m_treasurePools.contains(treasurePool))
2023-06-27 10:23:44 +00:00
throw TreasureException(strf("Unknown treasure pool '{}'", treasurePool));
2023-06-20 04:33:09 +00:00
if (!visitedPools.add(treasurePool))
2023-06-27 10:23:44 +00:00
throw TreasureException(strf("Loop detected in treasure pool generation - set '{}' already contains '{}'", visitedPools, treasurePool));
2023-06-20 04:33:09 +00:00
auto itemDatabase = Root::singleton().itemDatabase();
List<ItemPtr> treasureItems;
HashSet<ItemDescriptor> previousDescriptors;
auto itemPool = m_treasurePools.get(treasurePool).get(level);
int mix = 0;
for (auto const& fillEntry : itemPool.fill) {
if (fillEntry.is<String>()) {
auto poolContents = createTreasure(fillEntry.get<String>(), level, seed + ++mix, visitedPools);
for (auto item : poolContents) {
if (itemPool.allowDuplication || previousDescriptors.add(item->descriptor().singular()))
treasureItems.append(item);
}
} else {
float itemLevel = level + itemPool.levelVariance[0] + staticRandomFloat(seed, ++mix, "FillLevelVariance") * (itemPool.levelVariance[1] - itemPool.levelVariance[0]);
auto fillItem = itemDatabase->item(fillEntry.get<ItemDescriptor>(), itemLevel, seed + ++mix);
if (itemPool.allowDuplication || previousDescriptors.add(fillItem->descriptor().singular()))
treasureItems.append(fillItem);
}
}
if (!itemPool.pool.empty()) {
int poolRounds = itemPool.poolRounds.select(staticRandomU64(seed, "TreasurePoolRounds"));
for (int i = 0; i < poolRounds; ++i) {
auto poolEntry = itemPool.pool.select(staticRandomU64(seed, i, "TreasureItem"));
if (poolEntry.is<String>()) {
auto poolContents = createTreasure(poolEntry.get<String>(), level, staticRandomU64(seed, i, "TreasureSeedRecursion"), visitedPools);
for (auto item : poolContents) {
if (itemPool.allowDuplication || previousDescriptors.add(item->descriptor().singular()))
treasureItems.append(item);
}
} else {
float itemLevel = level + itemPool.levelVariance[0] + staticRandomFloat(staticRandomU64(seed, i, "TreasureLevelSeedMixer"), "PoolLevelVariance") * (itemPool.levelVariance[1] - itemPool.levelVariance[0]);
auto roundItem = poolEntry.get<ItemDescriptor>();
if (itemPool.allowDuplication || previousDescriptors.add(roundItem.singular()))
treasureItems.append(itemDatabase->item(roundItem, itemLevel, seed + ++mix));
}
}
}
return treasureItems;
}
List<ItemPtr> TreasureDatabase::fillWithTreasure(
ItemBagPtr const& itemBag, String const& treasurePool, float level) const {
return fillWithTreasure(itemBag, treasurePool, level, Random::randu64());
}
List<ItemPtr> TreasureDatabase::fillWithTreasure(
ItemBagPtr const& itemBag, String const& treasurePool, float level, uint64_t seed) const {
List<ItemPtr> overflowItems;
for (auto const& treasureItem : createTreasure(treasurePool, level, seed)) {
if (auto overflow = itemBag->addItems(treasureItem))
overflowItems.append(overflow);
}
return overflowItems;
}
ContainerObjectPtr TreasureDatabase::createTreasureChest(World* world, String const& treasureChestSet, Vec2I const& position, Direction direction) const {
return createTreasureChest(world, treasureChestSet, position, direction, Random::randu64());
}
ContainerObjectPtr TreasureDatabase::createTreasureChest(World* world, String const& treasureChestSet, Vec2I const& position, Direction direction, uint64_t seed) const {
auto objectDatabase = Root::singleton().objectDatabase();
if (!m_treasureChestSets.contains(treasureChestSet))
2023-06-27 10:23:44 +00:00
throw StarException(strf("Unknown treasure chest set '{}'", treasureChestSet));
2023-06-20 04:33:09 +00:00
auto level = world->threatLevel();
auto boxSet = m_treasureChestSets.get(treasureChestSet);
eraseWhere(boxSet, [level](TreasureChest const& treasureChest) { return level < treasureChest.minimumLevel; });
if (boxSet.empty())
return {};
auto const& treasureChest = staticRandomFrom(boxSet, seed, "TreasureChest");
auto const& containerName = staticRandomFrom(treasureChest.containers, seed, "ContainerName");
ContainerObjectPtr containerObject;
auto parameters = JsonObject{{"treasurePools", JsonArray{treasureChest.treasurePool}}, {"treasureSeed", seed}};
if (auto object = objectDatabase->createForPlacement(world, containerName, position, direction, parameters))
containerObject = convert<ContainerObject>(object);
return containerObject;
}
}