283 lines
13 KiB
C++
283 lines
13 KiB
C++
#include "game/effects/tech_effects.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
|
|
namespace sots::effects {
|
|
|
|
namespace {
|
|
|
|
using sots::sim::Species;
|
|
|
|
constexpr int F(PlayerFlag f) { return static_cast<int>(f); }
|
|
constexpr int Sp(Species s) { return static_cast<int>(s); }
|
|
|
|
// Plague-cure bits: one per plague type in vaccine order; the universal antidote covers
|
|
// the first four.
|
|
constexpr unsigned kCurePlague = 0x01;
|
|
constexpr unsigned kCureRetroPlague = 0x02;
|
|
constexpr unsigned kCureBeastPlague = 0x04;
|
|
constexpr unsigned kCureAssimilationPlague = 0x08;
|
|
constexpr unsigned kCureNaniteVirus = 0x10;
|
|
constexpr unsigned kCureUniversal = 0x0f;
|
|
|
|
// Zuul node-bore parameter rows, lowest to highest drive.
|
|
constexpr int kNodeBoreRows[3][3] = {{45, 15, 3}, {65, 35, 4}, {95, 60, 5}};
|
|
|
|
struct TableEntry {
|
|
TechId id;
|
|
std::vector<TechEffect> effects;
|
|
};
|
|
|
|
const std::vector<TableEntry>& Table() {
|
|
using K = EffectKind;
|
|
static const std::vector<TableEntry> table = {
|
|
{TechId::CCC_AdvSens, {{K::SetFlag, F(PlayerFlag::AdvancedSensors)}}},
|
|
{TechId::IND_Waldo, {{K::AddConstructionMod, 0, -0.10}, {K::AddOutputMod, 0, 0.15}}},
|
|
{TechId::IND_CyberInt, {{K::AddConstructionMod, 0, -0.05}, {K::AddOutputMod, 0, 0.20}}},
|
|
{TechId::IND_ExpSys, {{K::AddConstructionMod, 0, -0.10}, {K::AddOutputMod, 0, 0.15}}},
|
|
{TechId::IND_OrbFound, {{K::AddSavingsMod, 0, -0.05}}},
|
|
{TechId::IND_OrbDry, {{K::AddConstructionModClass, 1, -0.05}, {K::AddConstructionModClass, 2, -0.05}}},
|
|
{TechId::IND_GravCon, {{K::AddOutputMod, 0, 0.30}}},
|
|
{TechId::IND_HvyPlat, {{K::AddOutputMod, 0, 0.10}}},
|
|
{TechId::IND_AstMine, {{K::SetFlag, F(PlayerFlag::AsteroidMining)}}},
|
|
{TechId::IND_MsMine, {{K::RaiseMaxOverharvest, 0, 0.1}, {K::AddMiningRate, 0, 1.0}}},
|
|
{TechId::BIO_GnMod, {{K::AddPopulationMod, 0, 0.10}}},
|
|
{TechId::BIO_AtmoAd, {{K::AddSuitTolerance, 0, 0.75}, {K::AddPopulationMod, 0, 0.06}, {K::AddTerraformMod, 0, 0.35}}},
|
|
{TechId::BIO_EnvTail, {{K::AddPopulationMod, 0, 0.20}, {K::AddTerraformMod, 0, 0.45}}},
|
|
{TechId::BIO_GrvAdpt, {{K::AddSuitTolerance, 0, 1.50}, {K::AddPopulationMod, 0, 0.10}, {K::AddTerraformMod, 0, 0.35}}},
|
|
{TechId::IND_ArcCon, {{K::SetFlag, F(PlayerFlag::Arcology)}, {K::AddPopulationMod, 0, 0.15}, {K::ReevaluateCivilianCaps}}},
|
|
{TechId::IND_EleNans, {{K::AddTerraformMod, 0, 0.60}}},
|
|
{TechId::BIO_TerBac, {{K::AddTerraformMod, 0, 0.45}}},
|
|
{TechId::IND_AtProc, {{K::AddTerraformMod, 0, 0.50}}},
|
|
{TechId::DRV_TpGate, {{K::RaiseGateTraffic, 0}}},
|
|
{TechId::DRV_GatAmp, {{K::RaiseGateTraffic, 1}}},
|
|
{TechId::DRV_FarCast, {{K::SetFarCasting}}},
|
|
{TechId::CCC_AI, {{K::AiTechBonus, static_cast<int>(AiBonusSlot::Research)}}},
|
|
{TechId::CCC_AIAdmin, {{K::AiTechBonus, static_cast<int>(AiBonusSlot::Admin)}}},
|
|
{TechId::CCC_AIFac, {{K::AiTechBonus, static_cast<int>(AiBonusSlot::Factory)}}},
|
|
{TechId::CCC_AIVrus, {{K::FlagSystemsAI}}},
|
|
{TechId::CCC_AISlv, {{K::FlagSystemsAI}, {K::EnableAiBenefit}}},
|
|
{TechId::CCC_FtlEcon, {{K::SetFlagUnlessRebel, F(PlayerFlag::TradeAllowed)}}},
|
|
{TechId::CCC_ComRaid, {{K::SetFlag, F(PlayerFlag::CommerceRaiding)}}},
|
|
{TechId::DRV_GrvSyn, {{K::SetFlag, F(PlayerFlag::GravSynth)}}},
|
|
{TechId::CCC_DatCor, {{K::SetFlag, F(PlayerFlag::ViewIntel)}}},
|
|
{TechId::IND_HrdStrct, {{K::MulDefenceDamageMod, 0, 0.25}, {K::MulOutputMod, 0, 0.90}}},
|
|
{TechId::DRN_AdvRob, {{K::AddConstructionMod, 0, -0.05}}},
|
|
{TechId::IND_CruisCon, {{K::GrantTechIfSpecies, Sp(Species::Zuul), static_cast<double>(static_cast<int>(TechId::IND_BrdPod))}}},
|
|
{TechId::CCC_SpyBm, {{K::CaptureDesignsWith, static_cast<int>(TechId::IND_SlvgTech)}}},
|
|
{TechId::IND_SlvgTech, {{K::CaptureDesignsWith, static_cast<int>(TechId::CCC_SpyBm)}}},
|
|
{TechId::BIO_PLGVAC, {{K::CurePlague, static_cast<int>(kCurePlague)}}},
|
|
{TechId::BIO_RTPLGVAC, {{K::CurePlague, static_cast<int>(kCureRetroPlague)}}},
|
|
{TechId::BIO_BSTVAC, {{K::CurePlague, static_cast<int>(kCureBeastPlague)}}},
|
|
{TechId::BIO_ASPLGVAC, {{K::CurePlague, static_cast<int>(kCureAssimilationPlague)}}},
|
|
{TechId::BIO_CONNAN, {{K::CurePlague, static_cast<int>(kCureNaniteVirus)}}},
|
|
{TechId::BIO_UNIANTI, {{K::CurePlague, static_cast<int>(kCureUniversal)}}},
|
|
{TechId::DRV_RIP, {{K::NodeBoreParams, 0}}},
|
|
{TechId::DRV_REND, {{K::NodeBoreParams, 1}}},
|
|
{TechId::DRV_RAD, {{K::NodeBoreParams, 2}}},
|
|
};
|
|
return table;
|
|
}
|
|
|
|
const std::vector<TechEffect>& NoEffects() {
|
|
static const std::vector<TechEffect> none;
|
|
return none;
|
|
}
|
|
|
|
double AiBonusFor(AiBonusSlot slot, const AiBonusValues& v) {
|
|
switch (slot) {
|
|
case AiBonusSlot::Research: return v.research;
|
|
case AiBonusSlot::Admin: return v.admin;
|
|
case AiBonusSlot::Factory: return v.factory;
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
void AddAiBonus(PlayerEconomyState& s, AiBonusSlot slot, double amount) {
|
|
switch (slot) {
|
|
case AiBonusSlot::Research: s.resMod += amount; break;
|
|
case AiBonusSlot::Admin: s.incMod += amount; break;
|
|
case AiBonusSlot::Factory: s.outMod += amount; break;
|
|
}
|
|
}
|
|
|
|
void ApplyOne(PlayerEconomyState& s, const TechEffect& e, const ApplyContext& ctx, TechApplyOutcome& out) {
|
|
using K = EffectKind;
|
|
switch (e.kind) {
|
|
case K::AddConstructionMod:
|
|
for (double& c : s.conMod) c += e.value;
|
|
break;
|
|
case K::AddConstructionModClass:
|
|
if (e.index >= 0 && e.index < 3) s.conMod[e.index] += e.value;
|
|
break;
|
|
case K::AddSavingsMod:
|
|
for (double& m : s.savMod) m += e.value;
|
|
break;
|
|
case K::AddOutputMod: s.outMod += e.value; break;
|
|
case K::MulOutputMod: s.outMod *= e.value; break;
|
|
case K::AddPopulationMod: s.popMod += e.value; break;
|
|
case K::AddTerraformMod: s.terraMod += e.value; break;
|
|
case K::AddSuitTolerance: s.suitTol += e.value; break;
|
|
case K::RaiseMaxOverharvest: s.maxOverharvest = std::max(s.maxOverharvest, e.value); break;
|
|
case K::AddMiningRate: s.miningRate += e.value; break;
|
|
case K::MulDefenceDamageMod: s.defenceDamageMod *= e.value; break;
|
|
case K::SetFlag:
|
|
if (e.index >= 0 && e.index < kPlayerFlagCount) s.flags[e.index] = true;
|
|
break;
|
|
case K::SetFlagUnlessRebel:
|
|
if (!s.rebelAI && e.index >= 0 && e.index < kPlayerFlagCount) s.flags[e.index] = true;
|
|
break;
|
|
case K::RaiseGateTraffic: {
|
|
double v = 0.0;
|
|
if (ctx.tuning) v = e.index == 0 ? ctx.tuning->PERGATETRAFFIC_DRV_TpGate : ctx.tuning->PERGATETRAFFIC_DRV_GatAmp;
|
|
s.perGateTraffic = std::max(s.perGateTraffic, v);
|
|
break;
|
|
}
|
|
case K::SetFarCasting:
|
|
s.castRange = 10.0;
|
|
s.castEfficiency = 2.0;
|
|
s.castThreshold = 1.0;
|
|
break;
|
|
case K::AiTechBonus:
|
|
if (s.aiBenefit) AddAiBonus(s, static_cast<AiBonusSlot>(e.index), AiBonusFor(static_cast<AiBonusSlot>(e.index), ctx.aiBonus));
|
|
break;
|
|
case K::FlagSystemsAI: out.flagSystemsAI = true; break;
|
|
case K::EnableAiBenefit: SetAiBenefit(s, true, ctx); break;
|
|
case K::CurePlague: {
|
|
const unsigned mask = static_cast<unsigned>(e.index);
|
|
s.hasVaccine |= mask;
|
|
s.hasImmunity |= mask;
|
|
out.plagueCuredMask |= mask;
|
|
break;
|
|
}
|
|
case K::GrantTechIfSpecies:
|
|
if (Sp(s.species) == e.index) out.grantedTech = static_cast<TechId>(static_cast<int>(e.value));
|
|
break;
|
|
case K::CaptureDesignsWith:
|
|
if (s.HasResearched(static_cast<TechId>(e.index))) s.flags[F(PlayerFlag::CaptureDesigns)] = true;
|
|
break;
|
|
case K::NodeBoreParams:
|
|
if (e.index >= 0 && e.index < 3 && kNodeBoreRows[e.index][0] > s.nodeBoreParams[0]) {
|
|
for (int i = 0; i < 3; ++i) s.nodeBoreParams[i] = kNodeBoreRows[e.index][i];
|
|
out.nodeBoreParamsChanged = true;
|
|
}
|
|
break;
|
|
case K::ReevaluateCivilianCaps: out.reevaluateCivilianCaps = true; break;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
const std::vector<TechEffect>& EffectsOf(TechId id) {
|
|
for (const TableEntry& t : Table()) {
|
|
if (t.id == id) return t.effects;
|
|
}
|
|
return NoEffects();
|
|
}
|
|
|
|
unsigned PlagueCureMask(TechId id) {
|
|
for (const TechEffect& e : EffectsOf(id)) {
|
|
if (e.kind == EffectKind::CurePlague) return static_cast<unsigned>(e.index);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void RebuildSpeciesTechFlags(PlayerEconomyState& s) {
|
|
for (int sp = 0; sp < sots::sim::kSpeciesCount; ++sp) {
|
|
unsigned bits = 0;
|
|
for (int k = 0; k < kXenoLevelCount; ++k) {
|
|
const TechId id = XenoTechId(static_cast<XenoLevel>(k), static_cast<Species>(sp));
|
|
if (s.HasResearched(id)) bits |= 1u << k;
|
|
}
|
|
s.speciesFlags[sp] = bits;
|
|
}
|
|
}
|
|
|
|
bool AiRebellionPossible(const PlayerEconomyState& s) {
|
|
return !s.HasResearched(TechId::CCC_AISlv) && !sots::sim::IsNpcSpecies(s.species);
|
|
}
|
|
|
|
void SetAiBenefit(PlayerEconomyState& s, bool on, const ApplyContext& ctx) {
|
|
if (s.aiBenefit == on) return;
|
|
s.aiBenefit = on;
|
|
const double sign = on ? 1.0 : -1.0;
|
|
const TechId aiTechs[3] = {TechId::CCC_AI, TechId::CCC_AIAdmin, TechId::CCC_AIFac};
|
|
for (TechId id : aiTechs) {
|
|
if (!s.HasResearched(id)) continue;
|
|
for (const TechEffect& e : EffectsOf(id)) {
|
|
if (e.kind != EffectKind::AiTechBonus) continue;
|
|
const AiBonusSlot slot = static_cast<AiBonusSlot>(e.index);
|
|
AddAiBonus(s, slot, sign * AiBonusFor(slot, ctx.aiBonus));
|
|
}
|
|
}
|
|
}
|
|
|
|
TechApplyOutcome ApplyTechEffect(PlayerEconomyState& s, TechId id, const ApplyContext& ctx) {
|
|
TechApplyOutcome out;
|
|
if (!IsValidTechId(id) || s.HasResearched(id)) return out;
|
|
s.researched.set(static_cast<std::size_t>(TechIdIndex(id)));
|
|
out.applied = true;
|
|
|
|
for (const TechEffect& e : EffectsOf(id)) ApplyOne(s, e, ctx, out);
|
|
|
|
// Every completion: re-derive the per-species xenotech bits and report the species
|
|
// whose temperance is held (their addiction is cured on owned systems).
|
|
RebuildSpeciesTechFlags(s);
|
|
for (int sp = 0; sp < sots::sim::kSpeciesCount; ++sp) {
|
|
if (s.speciesFlags[sp] & (1u << static_cast<int>(XenoLevel::Temperance))) out.temperanceSpeciesMask |= 1u << sp;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
TechApplyOutcome ApplyTechEffectByName(PlayerEconomyState& s, std::string_view name, const ApplyContext& ctx) {
|
|
// Node-track techs are keyed by name in the species table, not by id.
|
|
for (int sp = 0; sp < sots::sim::kSpeciesCount; ++sp) {
|
|
const char* track = NodeTrackTechName(static_cast<Species>(sp));
|
|
if (track == nullptr) continue;
|
|
std::string_view t(track);
|
|
if (t.size() != name.size()) continue;
|
|
bool same = true;
|
|
for (std::size_t i = 0; i < t.size() && same; ++i) {
|
|
same = std::tolower(static_cast<unsigned char>(t[i])) == std::tolower(static_cast<unsigned char>(name[i]));
|
|
}
|
|
if (same) {
|
|
s.nodeTrackMask |= 1u << sp;
|
|
TechApplyOutcome out;
|
|
out.applied = true;
|
|
return out;
|
|
}
|
|
}
|
|
const TechId id = TechIdFromName(name);
|
|
if (id == TechId::None) return TechApplyOutcome{};
|
|
return ApplyTechEffect(s, id, ctx);
|
|
}
|
|
|
|
const char* const kDesignOptionNamesA[kDesignOptionCountA] = {
|
|
"IND_REFCOAT", "IND_IMPRFCT", "IND_PLYALLOY", "IND_MAGLAT", "IND_QRKRES", "IND_ADMALY",
|
|
"IND_PREDGUN", "SLD_DEF", "SLD_ERGAB", "SLD_MKONE", "SLD_MKTWO", "SLD_MKTHREE", "SLD_MKFOUR",
|
|
"SLD_CLK", "SLD_IMPCLK", "SLD_INTANG", "WEP_VRFTECH", "WEP_NUKEWHD", "WEP_GMAWHD", "WEP_FUSWHD",
|
|
"WEP_AMWHD", "WEP_NEUTRND", "CCC_INTSENS", "CCC_SNSJAM", "CCC_QNTCHAF", "CCC_CMBTALG",
|
|
"CCC_HOLOTAC", "CCC_ADVCNC", "CCC_AdvSens", "DRV_MCROFUS", "DRV_INCTHRST", "DRV_SMLFUS",
|
|
};
|
|
|
|
const char* const kDesignOptionNamesB[kDesignOptionCountB] = {
|
|
"DRV_NODE", "DRV_NODFOC", "DRV_NODPATH", "DRV_STRWRP", "DRV_IMPSTWRP", "DRV_FLICKER", "DRV_HYPER",
|
|
"DRV_HYPRFLD", "DRV_WARP", "SLD_MESSHLD", "SLD_GRVSHLD", "CCC_AIFRCON", "DRV_RIP", "DRV_REND",
|
|
"DRV_RAD", "BIO_CONNAN", "SLD_DISR", "SLD_MAGNI", "DRV_QNTCAP", "CCC_FCCOM", "IND_HRDELEC",
|
|
"IND_TRKSTL", "DRV_VDCTR", "DRV_VDCRV", "DRV_VDMSTR", "BIO_SMRTNAN", "WEP_ACCAMP", "WEP_MwMsl",
|
|
"WEP_HvyPmsl",
|
|
};
|
|
|
|
DesignOptionMasks ComputeDesignOptionMasks(const std::function<bool(std::string_view)>& hasResearchedByName) {
|
|
DesignOptionMasks m;
|
|
for (int i = 0; i < kDesignOptionCountA; ++i) {
|
|
if (hasResearchedByName(kDesignOptionNamesA[i])) m.a |= 1u << i;
|
|
}
|
|
for (int i = 0; i < kDesignOptionCountB; ++i) {
|
|
if (hasResearchedByName(kDesignOptionNamesB[i])) m.b |= 1u << i;
|
|
}
|
|
return m;
|
|
}
|
|
|
|
} // namespace sots::effects
|