Skip to content

Commit

Permalink
Merge branch 'Warzone2100:master' into make_unavailable
Browse files Browse the repository at this point in the history
  • Loading branch information
Arithyce committed May 8, 2024
2 parents 90a23b7 + 1c20240 commit 9dcf2bb
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 3 deletions.
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
2 changes: 2 additions & 0 deletions src/quickjs_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3198,6 +3198,7 @@ static JSValue js_removeBeacon(JSContext *ctx, JSValueConst this_val, int argc,

IMPL_JS_FUNC(chat, wzapi::chat)
IMPL_JS_FUNC(quickChat, wzapi::quickChat)
IMPL_JS_FUNC(getDroidPath, wzapi::getDroidPath)
IMPL_JS_FUNC(setAlliance, wzapi::setAlliance)
IMPL_JS_FUNC(sendAllianceRequest, wzapi::sendAllianceRequest)
IMPL_JS_FUNC(setAssemblyPoint, wzapi::setAssemblyPoint)
Expand Down Expand Up @@ -3469,6 +3470,7 @@ bool quickjs_scripting_instance::registerFunctions(const std::string& scriptName
JS_REGISTER_FUNC2(activateStructure, 1, 2); // WZAPI
JS_REGISTER_FUNC(chat, 2); // WZAPI
JS_REGISTER_FUNC(quickChat, 2); // WZAPI
JS_REGISTER_FUNC(getDroidPath, 1); // WZAPI
JS_REGISTER_FUNC2(addBeacon, 3, 4); // WZAPI
JS_REGISTER_FUNC(removeBeacon, 1); // WZAPI
JS_REGISTER_FUNC(getDroidProduction, 1); // WZAPI
Expand Down
123 changes: 120 additions & 3 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 @@ -531,6 +605,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 @@ -693,6 +773,14 @@ static inline int64_t iDivCeil(int64_t dividend, int64_t divisor)
return (dividend / divisor) + static_cast<int64_t>((dividend % divisor != 0 && hasPosQuotient));
}

static inline int64_t iDivFloor(int64_t dividend, int64_t divisor)
{
ASSERT_OR_RETURN(0, divisor != 0, "Divide by 0");
bool hasNegQuotient = (dividend >= 0) != (divisor >= 0);
// C++11 defines the behavior of % to be truncated
return (dividend / divisor) - static_cast<int64_t>((dividend % divisor != 0 && hasNegQuotient));
}

static void eventResearchedHandleUpgrades(const RESEARCH *psResearch, const STRUCTURE *psStruct, int player)
{
if (cachedStatsObject.is_null()) { cachedStatsObject = wzapi::constructStatsObject(); }
Expand All @@ -702,6 +790,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 @@ -819,7 +909,8 @@ static void eventResearchedHandleUpgrades(const RESEARCH *psResearch, const STRU
continue;
}
int64_t currentUpgradesValue = currentUpgradesValue_json.get<int64_t>();
int64_t newUpgradesChange = iDivCeil((statsOriginalValueX.get<int64_t>() * value), 100);
int64_t scaledChange = (statsOriginalValueX.get<int64_t>() * value);
int64_t newUpgradesChange = (value < 0) ? iDivFloor(scaledChange, 100) : iDivCeil(scaledChange, 100);
int64_t newUpgradesValue = (currentUpgradesValue + newUpgradesChange);
if (currentUpgradesValue_json.is_number_unsigned())
{
Expand Down Expand Up @@ -850,8 +941,29 @@ static void eventResearchedHandleUpgrades(const RESEARCH *psResearch, const STRU
continue;
}
int64_t currentUpgradesValue = currentUpgradesValue_json.get<int64_t>();
int64_t newUpgradesChange = iDivCeil((statsOriginalValue * value), 100);
int64_t newUpgradesValue = (currentUpgradesValue + newUpgradesChange);
int64_t scaledChange = (statsOriginalValue * value);
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 @@ -1127,12 +1239,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 @@ -70,6 +70,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 @@ -86,6 +97,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
22 changes: 22 additions & 0 deletions src/wzapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2086,6 +2086,28 @@ bool wzapi::quickChat(WZAPI_PARAMS(int playerFilter, int messageEnum))
return true;
}

//-- ## getDroidPath(droid)
//--
//-- Get path of a droid.
//-- Returns an array of positions.
//--
std::vector<scr_position> wzapi::getDroidPath(WZAPI_PARAMS(const DROID *psDroid))
{
SCRIPT_ASSERT({}, context, psDroid, "No valid droid provided");
std::vector<scr_position> result;

const size_t startPos = std::max(psDroid->sMove.pathIndex - 1, 0), len = psDroid->sMove.asPath.size();
result.reserve(len - startPos);
for (size_t i = startPos; i < len; i++)
{
const auto& pathCoords = psDroid->sMove.asPath[i];
ASSERT(worldOnMap(pathCoords.x, pathCoords.y), "Path off map!");
result.emplace_back(scr_position {map_coord(pathCoords.x), map_coord(pathCoords.y)});
}

return result;
}

//-- ## addBeacon(x, y, playerFilter[, message])
//--
//-- Send a beacon message to target player. Target may also be ```ALLIES```.
Expand Down
1 change: 1 addition & 0 deletions src/wzapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,7 @@ namespace wzapi
bool activateStructure(WZAPI_PARAMS(STRUCTURE *psStruct, optional<BASE_OBJECT *> _psTarget));
bool chat(WZAPI_PARAMS(int playerFilter, std::string message));
bool quickChat(WZAPI_PARAMS(int playerFilter, int messageEnum));
std::vector<scr_position> getDroidPath(WZAPI_PARAMS(const DROID *psDroid));
bool addBeacon(WZAPI_PARAMS(int x, int y, int playerFilter, optional<std::string> _message));
bool removeBeacon(WZAPI_PARAMS(int playerFilter));
std::unique_ptr<const DROID> getDroidProduction(WZAPI_PARAMS(const STRUCTURE *_psFactory));
Expand Down

0 comments on commit 9dcf2bb

Please sign in to comment.