osb/source/game/StarNpcDatabase.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

395 lines
16 KiB
C++

#include "StarNpcDatabase.hpp"
#include "StarEncode.hpp"
#include "StarRandom.hpp"
#include "StarJsonExtra.hpp"
#include "StarNpc.hpp"
#include "StarRoot.hpp"
#include "StarItemDatabase.hpp"
#include "StarSpeciesDatabase.hpp"
#include "StarNameGenerator.hpp"
#include "StarStoredFunctions.hpp"
#include "StarAssets.hpp"
#include "StarEncode.hpp"
#include "StarArmors.hpp"
namespace Star {
NpcDatabase::NpcDatabase() {
auto assets = Root::singleton().assets();
auto files = assets->scanExtension("npctype");
assets->queueJsons(files);
for (auto file : files) {
try {
auto config = assets->json(file);
String typeName = config.getString("type");
if (m_npcTypes.contains(typeName))
throw NpcException(strf("Repeat npc type name '{}'", typeName));
m_npcTypes[typeName] = config;
} catch (StarException const& e) {
throw NpcException(strf("Error loading npc type '{}'", file), e);
}
}
}
NpcVariant NpcDatabase::generateNpcVariant(String const& species, String const& typeName, float level) const {
return generateNpcVariant(species, typeName, level, Random::randu64(), {});
}
NpcVariant NpcDatabase::generateNpcVariant(
String const& species, String const& typeName, float level, uint64_t seed, Json const& overrides) const {
NpcVariant variant;
variant.species = species;
variant.typeName = typeName;
variant.seed = seed;
variant.overrides = overrides;
RandomSource randSource(seed);
auto config = buildConfig(typeName, overrides);
auto levelVariance = jsonToVec2F(config.getArray("levelVariance", {0, 0}));
variant.level = max(randSource.randf(level + levelVariance[0], level + levelVariance[1]), 0.0f);
variant.scripts = jsonToStringList(config.get("scripts"));
variant.initialScriptDelta = config.getUInt("initialScriptDelta", 5);
variant.scriptConfig = config.get("scriptConfig");
HumanoidIdentity identity;
auto speciesDefinition = Root::singleton().speciesDatabase()->species(species);
if (config.contains("humanoidConfig"))
variant.humanoidConfig = Root::singleton().assets()->json(config.getString("humanoidConfig"));
else
variant.humanoidConfig = speciesDefinition->humanoidConfig();
speciesDefinition->generateHumanoid(identity, seed);
if (config.contains("npcname"))
identity.name = config.getString("npcname");
else if (config.contains("nameGen")) {
identity.name = Root::singleton().nameGenerator()->generateName(
jsonToStringList(config.get("nameGen"))[(int)identity.gender], randSource);
}
identity.personality = parsePersonalityArray(randSource.randFrom(variant.humanoidConfig.getArray("personalities")));
if (config.contains("identity"))
identity = HumanoidIdentity(jsonMerge(identity.toJson(), config.get("identity")));
variant.humanoidIdentity = identity;
variant.movementParameters =
jsonMerge(variant.humanoidConfig.get("movementParameters"), config.get("movementParameters", {}));
variant.statusControllerSettings = config.get("statusControllerSettings");
auto functionDatabase = Root::singleton().functionDatabase();
float powerMultiplierModifier = functionDatabase->function("npcLevelPowerMultiplierModifier")->evaluate(variant.level);
float protectionMultiplier = functionDatabase->function("npcLevelProtectionMultiplier")->evaluate(variant.level);
float maxHealthMultiplier = functionDatabase->function("npcLevelHealthMultiplier")->evaluate(variant.level);
float maxEnergyMultiplier = functionDatabase->function("npcLevelEnergyMultiplier")->evaluate(variant.level);
variant.innateStatusEffects = config.get("innateStatusEffects", JsonArray()).toArray().transformed(jsonToPersistentStatusEffect);
variant.innateStatusEffects.append(StatModifier(StatValueModifier{"powerMultiplier", powerMultiplierModifier}));
variant.innateStatusEffects.append(StatModifier(StatBaseMultiplier{"protection", protectionMultiplier}));
variant.innateStatusEffects.append(StatModifier(StatBaseMultiplier{"maxHealth", maxHealthMultiplier}));
variant.innateStatusEffects.append(StatModifier(StatBaseMultiplier{"maxEnergy", maxEnergyMultiplier}));
variant.touchDamageConfig = config.get("touchDamage", {});
auto itemsConfig = config.get("items", {});
if (!itemsConfig.isNull()) {
auto speciesItemsConfig = itemsConfig.get("override", {});
if (speciesItemsConfig.isNull())
speciesItemsConfig = itemsConfig.get(species, {});
if (speciesItemsConfig.isNull())
speciesItemsConfig = itemsConfig.get("default", {});
if (!speciesItemsConfig.isNull()) {
Json highestLevelItemsConfig;
for (auto levelItemsConfig : speciesItemsConfig.toArray()) {
if (variant.level >= levelItemsConfig.getFloat(0)) {
highestLevelItemsConfig = levelItemsConfig.get(1);
}
}
if (!highestLevelItemsConfig.isNull()) {
int randomColorIndex = -1;
bool matchColorIndices = config.getBool("matchColorIndices", false);
for (auto itemSlotConfig : randSource.randFrom(highestLevelItemsConfig.toArray()).toObject()) {
ItemDescriptor item = ItemDescriptor(randSource.randFrom(itemSlotConfig.second.toArray()));
// Randomize color index if colorIndex is an array
if (item.parameters().contains("colorIndex")) {
auto colorIndex = item.parameters().get("colorIndex");
if (colorIndex.isType(Json::Type::Array)) {
if (!matchColorIndices || randomColorIndex == -1)
randomColorIndex = randSource.randFrom(colorIndex.toArray()).toInt();
item = item.applyParameters({{"colorIndex", randomColorIndex}});
}
}
variant.items[itemSlotConfig.first] = std::move(item);
}
}
}
}
variant.disableWornArmor = config.getBool("disableWornArmor", true);
variant.dropPools = jsonToStringList(config.get("dropPools", JsonArray{}));
variant.persistent = config.getBool("persistent", false);
variant.keepAlive = config.getBool("keepAlive", false);
variant.damageTeam = config.getUInt("damageTeam", 0);
variant.damageTeamType = TeamTypeNames.getLeft(config.getString("damageTeamType", "enemy"));
variant.nametagColor = jsonToVec3B(config.get("nametagColor", JsonArray{255, 255, 255}));
variant.splashConfig = EntitySplashConfig(config.get("splashConfig"));
return variant;
}
ByteArray NpcDatabase::writeNpcVariant(NpcVariant const& variant) const {
DataStreamBuffer ds;
ds.write(variant.species);
ds.write(variant.typeName);
ds.write(variant.level);
ds.write(variant.seed);
ds.write(variant.overrides);
ds.write(variant.initialScriptDelta);
ds.write(variant.humanoidIdentity);
ds.writeMapContainer(variant.items);
ds.write(variant.persistent);
ds.write(variant.keepAlive);
ds.write(variant.damageTeam);
ds.write(variant.damageTeamType);
return ds.data();
}
NpcVariant NpcDatabase::readNpcVariant(ByteArray const& data) const {
DataStreamBuffer ds(data);
NpcVariant variant;
ds.read(variant.species);
ds.read(variant.typeName);
ds.read(variant.level);
ds.read(variant.seed);
ds.read(variant.overrides);
auto config = buildConfig(variant.typeName, variant.overrides);
variant.scripts = jsonToStringList(config.get("scripts"));
variant.scriptConfig = config.get("scriptConfig");
ds.read(variant.initialScriptDelta);
ds.read(variant.humanoidIdentity);
auto speciesDefinition = Root::singleton().speciesDatabase()->species(variant.species);
if (config.contains("humanoidConfig"))
variant.humanoidConfig = Root::singleton().assets()->json(config.getString("humanoidConfig"));
else
variant.humanoidConfig = speciesDefinition->humanoidConfig();
variant.movementParameters =
jsonMerge(variant.humanoidConfig.get("movementParameters"), config.get("movementParameters", {}));
variant.statusControllerSettings = config.get("statusControllerSettings");
auto functionDatabase = Root::singleton().functionDatabase();
float powerMultiplierModifier =
functionDatabase->function("npcLevelPowerMultiplierModifier")->evaluate(variant.level);
float protectionMultiplier = functionDatabase->function("npcLevelProtectionMultiplier")->evaluate(variant.level);
float maxHealthMultiplier = functionDatabase->function("npcLevelHealthMultiplier")->evaluate(variant.level);
float maxEnergyMultiplier = functionDatabase->function("npcLevelEnergyMultiplier")->evaluate(variant.level);
variant.innateStatusEffects =
config.get("innateStatusEffects", JsonArray()).toArray().transformed(jsonToPersistentStatusEffect);
variant.innateStatusEffects.append(StatModifier(StatValueModifier{"powerMultiplier", powerMultiplierModifier}));
variant.innateStatusEffects.append(StatModifier(StatBaseMultiplier{"protection", protectionMultiplier}));
variant.innateStatusEffects.append(StatModifier(StatBaseMultiplier{"maxHealth", maxHealthMultiplier}));
variant.innateStatusEffects.append(StatModifier(StatBaseMultiplier{"maxEnergy", maxEnergyMultiplier}));
variant.touchDamageConfig = config.get("touchDamage", {});
ds.readMapContainer(variant.items);
variant.disableWornArmor = config.getBool("disableWornArmor", true);
variant.dropPools = jsonToStringList(config.get("dropPools", JsonArray{}));
ds.read(variant.persistent);
ds.read(variant.keepAlive);
ds.read(variant.damageTeam);
ds.read(variant.damageTeamType);
variant.nametagColor = jsonToVec3B(config.get("nametagColor", JsonArray{255, 255, 255}));
variant.splashConfig = EntitySplashConfig(config.get("splashConfig"));
return variant;
}
Json NpcDatabase::writeNpcVariantToJson(NpcVariant const& variant) const {
return JsonObject{{"species", variant.species},
{"typeName", variant.typeName},
{"level", variant.level},
{"seed", variant.seed},
{"overrides", variant.overrides},
{"initialScriptDelta", variant.initialScriptDelta},
{"humanoidIdentity", variant.humanoidIdentity.toJson()},
{"items", jsonFromMapV<StringMap<ItemDescriptor>>(variant.items, mem_fn(&ItemDescriptor::diskStore))},
{"persistent", variant.persistent},
{"keepAlive", variant.keepAlive},
{"damageTeam", variant.damageTeam},
{"damageTeamType", TeamTypeNames.getRight(variant.damageTeamType)}};
}
NpcVariant NpcDatabase::readNpcVariantFromJson(Json const& data) const {
NpcVariant variant;
variant.species = data.getString("species");
variant.typeName = data.getString("typeName");
variant.level = data.getFloat("level");
variant.seed = data.getUInt("seed");
variant.overrides = data.get("overrides");
auto config = buildConfig(variant.typeName, variant.overrides);
variant.scripts = jsonToStringList(config.get("scripts"));
variant.scriptConfig = config.get("scriptConfig");
variant.initialScriptDelta = data.getInt("initialScriptDelta");
variant.humanoidIdentity = HumanoidIdentity(data.get("humanoidIdentity"));
auto speciesDefinition = Root::singleton().speciesDatabase()->species(variant.species);
if (config.contains("humanoidConfig"))
variant.humanoidConfig = Root::singleton().assets()->json(config.getString("humanoidConfig"));
else
variant.humanoidConfig = speciesDefinition->humanoidConfig();
variant.movementParameters =
jsonMerge(variant.humanoidConfig.get("movementParameters"), config.get("movementParameters", {}));
variant.statusControllerSettings = config.get("statusControllerSettings", {});
auto functionDatabase = Root::singleton().functionDatabase();
float powerMultiplierModifier =
functionDatabase->function("npcLevelPowerMultiplierModifier")->evaluate(variant.level);
float protectionMultiplier = functionDatabase->function("npcLevelProtectionMultiplier")->evaluate(variant.level);
float maxHealthMultiplier = functionDatabase->function("npcLevelHealthMultiplier")->evaluate(variant.level);
float maxEnergyMultiplier = functionDatabase->function("npcLevelEnergyMultiplier")->evaluate(variant.level);
variant.innateStatusEffects =
config.get("innateStatusEffects", JsonArray()).toArray().transformed(jsonToPersistentStatusEffect);
variant.innateStatusEffects.append(StatModifier(StatValueModifier{"powerMultiplier", powerMultiplierModifier}));
variant.innateStatusEffects.append(StatModifier(StatBaseMultiplier{"protection", protectionMultiplier}));
variant.innateStatusEffects.append(StatModifier(StatBaseMultiplier{"maxHealth", maxHealthMultiplier}));
variant.innateStatusEffects.append(StatModifier(StatBaseMultiplier{"maxEnergy", maxEnergyMultiplier}));
variant.touchDamageConfig = config.get("touchDamage", {});
variant.items = jsonToMapV<StringMap<ItemDescriptor>>(data.get("items"), ItemDescriptor::loadStore);
variant.disableWornArmor = config.getBool("disableWornArmor", true);
variant.dropPools = jsonToStringList(config.get("dropPools", JsonArray{}));
variant.persistent = data.getBool("persistent");
variant.keepAlive = data.getBool("keepAlive");
variant.damageTeam = data.getUInt("damageTeam");
variant.damageTeamType = TeamTypeNames.getLeft(data.getString("damageTeamType"));
variant.nametagColor = jsonToVec3B(config.get("nametagColor", JsonArray{255, 255, 255}));
variant.splashConfig = EntitySplashConfig(config.get("splashConfig"));
return variant;
}
NpcPtr NpcDatabase::createNpc(NpcVariant const& npcVariant) const {
return make_shared<Npc>(npcVariant);
}
NpcPtr NpcDatabase::diskLoadNpc(Json const& diskStore) const {
auto npcVariant = readNpcVariantFromJson(diskStore.get("npcVariant"));
return make_shared<Npc>(npcVariant, diskStore);
}
NpcPtr NpcDatabase::netLoadNpc(ByteArray const& netStore) const {
return make_shared<Npc>(readNpcVariant(netStore));
}
List<Drawable> NpcDatabase::npcPortrait(NpcVariant const& npcVariant, PortraitMode mode) const {
Humanoid humanoid(npcVariant.humanoidConfig);
humanoid.setIdentity(npcVariant.humanoidIdentity);
auto itemDatabase = Root::singleton().itemDatabase();
auto items = StringMap<ItemDescriptor, CaseInsensitiveStringHash, CaseInsensitiveStringCompare>::from(npcVariant.items);
auto makeItem = [&npcVariant, &itemDatabase](ItemDescriptor itemDescriptor) -> ItemPtr {
return itemDatabase->item(itemDescriptor, npcVariant.level, npcVariant.seed);
};
ArmorWearer armor;
if (items.contains("head"))
armor.setHeadItem(as<HeadArmor>(makeItem(items["head"])));
if (items.contains("headCosmetic"))
armor.setHeadItem(as<HeadArmor>(makeItem(items["headCosmetic"])));
if (items.contains("chest"))
armor.setChestItem(as<ChestArmor>(makeItem(items["chest"])));
if (items.contains("chestCosmetic"))
armor.setChestCosmeticItem(as<ChestArmor>(makeItem(items["chestCosmetic"])));
if (items.contains("legs"))
armor.setLegsItem(as<LegsArmor>(makeItem(items["legs"])));
if (items.contains("legsCosmetic"))
armor.setLegsCosmeticItem(as<LegsArmor>(makeItem(items["legsCosmetic"])));
if (items.contains("back"))
armor.setBackItem(as<BackArmor>(makeItem(items["back"])));
if (items.contains("backCosmetic"))
armor.setBackCosmeticItem(as<BackArmor>(makeItem(items["backCosmetic"])));
armor.setupHumanoidClothingDrawables(humanoid, false);
return humanoid.renderPortrait(mode);
}
Json NpcDatabase::buildConfig(String const& typeName, Json const& overrides) const {
auto const& baseConfig = m_npcTypes.get(typeName);
auto config = mergeConfigValues(baseConfig, overrides);
String baseTypeName = baseConfig.getString("baseType", "");
if (baseTypeName.empty()) {
return config;
} else {
return buildConfig(baseTypeName, config);
}
}
Json NpcDatabase::mergeConfigValues(Json const& base, Json const& merger) const {
if (base.type() == Json::Type::Object && merger.type() == Json::Type::Object) {
auto map = base.toObject();
for (auto const& entry : merger.iterateObject()) {
if (!map.insert(entry.first, entry.second).second) {
map[entry.first] = mergeConfigValues(map[entry.first], entry.second);
}
}
return map;
} else if (merger.isNull()) {
return base;
} else {
return merger;
}
}
}