Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

research.json: Add support for new "calculationMode" option, "Improved" calculation mode #3796

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions data/mods/campaign/wz2100_camclassic/stats/research.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"_config_": {
"calculationMode": "improved"
},
"ADVANCEDRESEARCH": {
"iconID": "IMAGE_RES_COMPUTERTECH",
"id": "ADVANCEDRESEARCH",
Expand Down
111 changes: 109 additions & 2 deletions src/research.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,13 @@

// The stores for the research stats
std::vector<RESEARCH> asResearch;
optional<ResearchUpgradeCalculationMode> researchUpgradeCalcMode;
nlohmann::json cachedStatsObject = nlohmann::json(nullptr);
std::vector<wzapi::PerPlayerUpgrades> cachedPerPlayerUpgrades;

typedef std::unordered_map<std::string, std::unordered_map<std::string, int64_t>> RawResearchUpgradeChangeValues;
std::array<RawResearchUpgradeChangeValues, MAX_PLAYERS> cachedPerPlayerRawUpgradeChange;

//used for Callbacks to say which topic was last researched
RESEARCH *psCBLastResearch;
STRUCTURE *psCBLastResStructure;
Expand Down Expand Up @@ -105,6 +109,7 @@ bool researchInitVars()
psCBLastResStructure = nullptr;
CBResFacilityOwner = -1;
asResearch.clear();
researchUpgradeCalcMode = nullopt;
cachedStatsObject = nlohmann::json(nullptr);
cachedPerPlayerUpgrades.clear();
playerUpgradeCounts = std::vector<PlayerUpgradeCounts>(MAX_PLAYERS);
Expand All @@ -120,6 +125,12 @@ bool researchInitVars()
return true;
}

ResearchUpgradeCalculationMode getResearchUpgradeCalcMode()
{
// Default to ResearchUpgradeCalculationMode::Compat, unless otherwise specified
return researchUpgradeCalcMode.value_or(ResearchUpgradeCalculationMode::Compat);
}

uint32_t PlayerUpgradeCounts::getNumWeaponImpactClassUpgrades(WEAPON_SUBCLASS subClass)
{
auto subClassStr = getWeaponSubClass(subClass);
Expand Down Expand Up @@ -240,17 +251,80 @@ class CycleDetection
}
};

static optional<ResearchUpgradeCalculationMode> resCalcModeStringToValue(const WzString& calcModeStr)
{
if (calcModeStr.compare("compat") == 0)
{
return ResearchUpgradeCalculationMode::Compat;
}
else if (calcModeStr.compare("improved") == 0)
{
return ResearchUpgradeCalculationMode::Improved;
}
else
{
return nullopt;
}
}

static const char* resCalcModeToString(ResearchUpgradeCalculationMode mode)
{
switch (mode)
{
case ResearchUpgradeCalculationMode::Compat:
return "compat";
case ResearchUpgradeCalculationMode::Improved:
return "improved";
}
return "invalid";
}

#define RESEARCH_JSON_CONFIG_DICT_KEY "_config_"

/** Load the research stats */
bool loadResearch(WzConfig &ini)
{
ASSERT(ini.isAtDocumentRoot(), "WzConfig instance is in the middle of traversal");
const WzString CONFIG_DICT_KEY_STR = RESEARCH_JSON_CONFIG_DICT_KEY;
std::vector<WzString> list = ini.childGroups();
PLAYER_RESEARCH dummy;
memset(&dummy, 0, sizeof(dummy));
std::vector<std::vector<WzString>> preResearch;
preResearch.resize(list.size());
for (size_t inc = 0; inc < list.size(); ++inc)
{
if (list[inc] == CONFIG_DICT_KEY_STR)
{
// handle the special config dict
ini.beginGroup(list[inc]);

// calculationMode
auto calcModeStr = ini.value("calculationMode", resCalcModeToString(ResearchUpgradeCalculationMode::Compat)).toWzString();
auto calcModeParsed = resCalcModeStringToValue(calcModeStr);
if (calcModeParsed.has_value())
{
if (!researchUpgradeCalcMode.has_value())
{
researchUpgradeCalcMode = calcModeParsed.value();
}
else
{
if (researchUpgradeCalcMode.value() != calcModeParsed.value())
{
debug(LOG_ERROR, "Non-matching research JSON calculationModes");
debug(LOG_INFO, "Research JSON file \"%s\" has specified a calculationMode (\"%s\") that does not match the first loaded research JSON's calculationMode (\"%s\")", ini.fileName().toUtf8().c_str(), calcModeStr.toUtf8().c_str(), resCalcModeToString(researchUpgradeCalcMode.value()));
}
}
}
else
{
ASSERT_OR_RETURN(false, false, "Invalid _config_ \"calculationMode\" value: \"%s\"", calcModeStr.toUtf8().c_str());
}

ini.endGroup();
continue;
}

// HACK FIXME: the code assumes we have empty PLAYER_RESEARCH entries to throw around
for (auto &j : asPlayerResList)
{
Expand Down Expand Up @@ -501,6 +575,12 @@ bool loadResearch(WzConfig &ini)
return false;
}

// If the first research json file does not explicitly set calculationMode, default to compat
if (!researchUpgradeCalcMode.has_value())
{
researchUpgradeCalcMode = ResearchUpgradeCalculationMode::Compat;
}

return true;
}

Expand Down Expand Up @@ -680,6 +760,8 @@ static void eventResearchedHandleUpgrades(const RESEARCH *psResearch, const STRU
debug(LOG_RESEARCH, "RESEARCH : %s(%s) for %d", psResearch->name.toUtf8().c_str(), psResearch->id.toUtf8().c_str(), player);

ASSERT_OR_RETURN(, player >= 0 && player < cachedPerPlayerUpgrades.size(), "Player %d does not exist in per-player upgrades?", player);
auto &playerRawUpgradeChangeTotals = cachedPerPlayerRawUpgradeChange[player];
const auto upgradeCalcMode = getResearchUpgradeCalcMode();

PlayerUpgradeCounts tempStats;

Expand Down Expand Up @@ -830,8 +912,28 @@ static void eventResearchedHandleUpgrades(const RESEARCH *psResearch, const STRU
}
int64_t currentUpgradesValue = currentUpgradesValue_json.get<int64_t>();
int64_t scaledChange = (statsOriginalValue * value);
int64_t newUpgradesChange = (value < 0) ? iDivFloor(scaledChange, 100) : iDivCeil(scaledChange, 100);
int64_t newUpgradesValue = (currentUpgradesValue + newUpgradesChange);
int64_t newUpgradesChange = 0;
int64_t newUpgradesValue = 0;
switch (upgradeCalcMode)
{
case ResearchUpgradeCalculationMode::Compat:
// Default / compat cumulative upgrade handling (the only option for many years - from at least 3.x/(3.2+?)-4.4.2?)
// This can accumulate noticeable error, especially if repeatedly upgrading small values by small percentages (commonly impacted: armour, thermal)
// However, research.json created and tested during this long period may be expecting this outcome / behavior
newUpgradesChange = (value < 0) ? iDivFloor(scaledChange, 100) : iDivCeil(scaledChange, 100);
newUpgradesValue = (currentUpgradesValue + newUpgradesChange);
break;
case ResearchUpgradeCalculationMode::Improved:
{
// "Improved" cumulative upgrade handling (significantly reduces accumulated errors)
auto& compUpgradeTotals = playerRawUpgradeChangeTotals[cname.first];
auto& cumulativeUpgradeScaledChange = compUpgradeTotals[parameter];
cumulativeUpgradeScaledChange += scaledChange;
newUpgradesValue = statsOriginalValue + ((cumulativeUpgradeScaledChange < 0) ? iDivFloor(cumulativeUpgradeScaledChange, 100) : iDivCeil(cumulativeUpgradeScaledChange, 100));
newUpgradesChange = newUpgradesValue - currentUpgradesValue;
break;
}
}
if (currentUpgradesValue_json.is_number_unsigned())
{
// original was unsigned integer - round anything less than 0 up to 0
Expand Down Expand Up @@ -1075,12 +1177,17 @@ bool ResearchShutDown()
void ResearchRelease()
{
asResearch.clear();
researchUpgradeCalcMode = nullopt;
for (auto &i : asPlayerResList)
{
i.clear();
}
cachedStatsObject = nlohmann::json(nullptr);
cachedPerPlayerUpgrades.clear();
for (auto &p : cachedPerPlayerRawUpgradeChange)
{
p.clear();
}
playerUpgradeCounts = std::vector<PlayerUpgradeCounts>(MAX_PLAYERS);
}

Expand Down
13 changes: 13 additions & 0 deletions src/research.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ enum
RID_MAXRID
};

enum class ResearchUpgradeCalculationMode
{
// Default / compat cumulative upgrade handling (the only option for many years - from at least 3.x/(3.2+?)-4.4.2?)
// This can accumulate noticeable error, especially if repeatedly upgrading small values by small percentages (commonly impacted: armour, thermal)
// However, research.json created and tested during this long period may be expecting this outcome / behavior
Compat,

// "Improved" cumulative upgrade handling (significantly reduces accumulated errors)
Improved
};

/* The store for the research stats */
extern std::vector<RESEARCH> asResearch;

Expand All @@ -85,6 +96,8 @@ extern UDWORD aDefaultSensor[MAX_PLAYERS];
extern UDWORD aDefaultECM[MAX_PLAYERS];
extern UDWORD aDefaultRepair[MAX_PLAYERS];

ResearchUpgradeCalculationMode getResearchUpgradeCalcMode();

bool loadResearch(WzConfig &ini);

/*function to check what can be researched for a particular player at any one
Expand Down
Loading