// ZDoomRL - Gameplay Modifications for DoomRL Arsenal
// Author: Simon Volpert <simon@simonvolpert.com>
// Project page: https://simonvolpert.com/zdoomrl/
// This program is free software, released under the MIT license. See the LICENSE file for more information
#library "AssemblyFlow"
#include "zcommon.acs"
#include "Assemblies.acs"
#define CVAR_KNOWN_ONLY "DRLA_AssemblyLearning"
str Modpacks[8] = {"AgilityMod", "BulkMod", "TechnicalMod", "PowerMod", "FirestormMod", "SniperMod", "NanoMod", "ArtiMod"};
str ModName[7] = {"\cdA", "\cnB", "\ckT", "\cgP", "\ciF", "\ctS", "\ccN"};
// Utility: return the greater of two integers
function int max(int a, int b) {
if (a > b) {
return a;
}
return b;
}
// Return the number of required mods for an assembly
function int modreq(int AssemblyIndex, int ModType) {
return GetChar(AssemblyDB[AssemblyIndex][ModType], 0) - 48;
}
// Watch for potential assemblies to auto-replace
script "AssemblyWatch" Enter {
str UsedMod, HeldItem, ResultItem, BaseItem; // Set to empty string immediately before use
int AMods, BMods, TMods, PMods, FMods, SMods, NMods, ModLimit, AsmSize;
if (GetCvar("Debug")) {
Log(s:"fluid assembly watch ready");
}
while (TRUE) {
Delay(1);
// Wait for a modpack activation
UsedMod = "";
for (int m = 0; m < 8; m++) {
if (CheckInventory(StrParam(s:"RLUse", s:Modpacks[m]))) {
UsedMod = StrParam(s:"RLUse", s:Modpacks[m]);
if (GetCvar("Debug")) {
Log(s:"used ", s:Modpacks[m]);
}
break;
}
}
if (UsedMod == "") {
continue;
}
// Wait for the modpack to deactibate
while (CheckInventory(UsedMod)) {
Delay(1);
}
HeldItem = "";
BaseItem = "";
// Detect the item being modified
for (int i = 0; i < ASSEMBLIES; i++) {
if (CheckInventory(StrParam(s:AssemblyDB[i][ASSEMBLY_BASE], s:"Selected"))) {
HeldItem = AssemblyDB[i][ASSEMBLY_BASE];
BaseItem = HeldItem;
break;
}
else if (CheckInventory(StrParam(s:AssemblyDB[i][ASSEMBLY_RESULT], s:"Selected"))) {
HeldItem = AssemblyDB[i][ASSEMBLY_RESULT];
BaseItem = AssemblyDB[i][ASSEMBLY_BASE];
break;
}
}
if (HeldItem == "") {
continue;
}
// Count applied modpacks
if (GetCvar("Debug")) {
Log(s:"checking modification of ", s:HeldItem);
}
AMods = CheckInventory(StrParam(s:HeldItem, s:"AgilityMod"));
BMods = CheckInventory(StrParam(s:HeldItem, s:"BulkMod"));
TMods = CheckInventory(StrParam(s:HeldItem, s:"TechnicalMod"));
PMods = CheckInventory(StrParam(s:HeldItem, s:"PowerMod"));
FMods = CheckInventory(StrParam(s:HeldItem, s:"FirestormMod"));
SMods = CheckInventory(StrParam(s:HeldItem, s:"SniperMod"));
NMods = CheckInventory(StrParam(s:HeldItem, s:"NanoMod"));
ModLimit = AMods + BMods + TMods + PMods + FMods + SMods + NMods;
// Don't allow the blaster to have more than one technical mod (upstream bug workaround)
if (TMods == 2 && HeldItem == "RLBlaster") {
Print(s:"This weapon cannot support further modification.");
TakeInventory("RLBlasterTechnicalMod", 1);
GiveInventory("RLTechnicalModItem", 1);
StopSound(0, CHAN_ITEM);
continue;
}
// Decompose assembly if applicable
AsmSize = 0;
if (HeldItem != BaseItem) {
AMods += modreq(i, MOD_A);
BMods += modreq(i, MOD_B);
TMods += modreq(i, MOD_T);
PMods += modreq(i, MOD_P);
FMods += modreq(i, MOD_F);
SMods += modreq(i, MOD_S);
NMods += modreq(i, MOD_N);
AsmSize = modreq(i, ASSEMBLY_SIZE);
ModLimit += AsmSize;
}
if (GetCvar("Debug")) {
Log(i:ModLimit, s:" mods: ", i:AMods, s:"A ", i:BMods, s:"B ", i:TMods, s:"T ", i:PMods, s:"P ", i:FMods, s:"F ", i:SMods, s:"S ", i:NMods, s:"N ");
}
// Check assembly eligibility
ResultItem = "";
for (int a = 0; a < ASSEMBLIES; a++) {
if (BaseItem != AssemblyDB[a][ASSEMBLY_BASE]) {
continue;
}
if (AMods < modreq(a, MOD_A) || BMods < modreq(a, MOD_B) || TMods < modreq(a, MOD_T) || PMods < modreq(a, MOD_P) ||
FMods < modreq(a, MOD_F) || SMods < modreq(a, MOD_S) || NMods < modreq(a, MOD_N)) {
continue;
}
// Ignore assemblies of the same or lower tier
if (AsmSize >= modreq(a, ASSEMBLY_SIZE)) {
continue;
}
ResultItem = AssemblyDB[a][ASSEMBLY_RESULT];
// Ignore unknown assemblies if learning option is set
if (GetCVar(CVAR_KNOWN_ONLY) && !CheckInventory(AssemblyDB[a][ASSEMBLY_RECIPE])) {
if (GetCvar("Debug")) {
Log(s:"recipe for ", s:ResultItem, s:" is unknown");
}
ResultItem = "";
continue;
}
if (GetCvar("Debug")) {
Log(s:"enough mods to complete ", s:ResultItem);
}
break;
}
if (ResultItem == "") {
continue;
}
// Clear inventory of existing item and subtract assembly cost from total mod counts
TakeInventory(HeldItem, 1);
TakeInventory(StrParam(s:HeldItem, s:"AgilityMod"), 4);
AMods -= modreq(a, MOD_A);
TakeInventory(StrParam(s:HeldItem, s:"BulkMod"), 4);
BMods -= modreq(a, MOD_B);
TakeInventory(StrParam(s:HeldItem, s:"TechnicalMod"), 4);
TMods -= modreq(a, MOD_T);
TakeInventory(StrParam(s:HeldItem, s:"PowerMod"), 4);
PMods -= modreq(a, MOD_P);
TakeInventory(StrParam(s:HeldItem, s:"FirestormMod"), 4);
FMods -= modreq(a, MOD_F);
TakeInventory(StrParam(s:HeldItem, s:"SniperMod"), 4);
SMods -= modreq(a, MOD_S);
TakeInventory(StrParam(s:HeldItem, s:"NanoMod"), 4);
NMods -= modreq(a, MOD_N);
TakeInventory(StrParam(s:HeldItem, s:"ModLimit"), 4);
// Give new assembled item, preserving any extra mods
GiveInventory(ResultItem, 1);
GiveInventory("RLDeselectionFunction", 1);
GiveInventory("RLMisfireSpamPreventionCooldown", 1);
GiveInventory("RLRarityTokenRemover", 1);
// Clear assembly confirmation tokens
for (int c = 0; c < ASSEMBLIES; c++) {
TakeInventory(StrParam(s:AssemblyDB[c][ASSEMBLY_RESULT], s:"Confirm"), 1);
}
// Apply leftover mods to resulting assembly
GiveInventory(StrParam(s:ResultItem, s:"AgilityMod"), AMods);
GiveInventory(StrParam(s:ResultItem, s:"BulkMod"), BMods);
GiveInventory(StrParam(s:ResultItem, s:"TechnicalMod"), TMods);
GiveInventory(StrParam(s:ResultItem, s:"PowerMod"), PMods);
GiveInventory(StrParam(s:ResultItem, s:"FirestormMod"), FMods);
GiveInventory(StrParam(s:ResultItem, s:"SniperMod"), SMods);
GiveInventory(StrParam(s:ResultItem, s:"NanoMod"), NMods);
GiveInventory(StrParam(s:ResultItem, s:"ModLimit"), AMods + BMods + TMods + PMods + FMods + SMods + NMods);
SetWeapon(ResultItem);
// Report the result
str strangemessage = "";
if (UsedMod == "RLUseArtiMod") {
strangemessage = "\n\nThe strange sentient mod pack seems satisfied with the\nbizarre addons it somehow put on your weapon.";
}
Print(s:"\cv", s:AssemblyDB[a][ASSEMBLY_NAME], s:"\c- assembled.", s:strangemessage);
// Play a relevant sound effect
if (modreq(a, ASSEMBLY_SIZE) == 4) {
if (CheckInventory("RLScavengerPerk")) {
PlaySound(0, "technician/laugh", CHAN_VOICE);
}
PlaySound(0, "weapons/masterassembly", CHAN_ITEM);
PlaySound(0, "weapons/masterassemblyfanfare", CHAN_AUTO);
}
else {
PlaySound(0, "weapons/assembly", CHAN_ITEM);
}
}
}
// Print available assemblies for the held weapon
script "ShowPossibleAssemblies" (void) {
int AMods, BMods, TMods, PMods, FMods, SMods, NMods, ModLimit, AsmSize, NeededMods;
int ModQuota = 4;
if (CheckInventory("RLSuperiorWeaponToken")) {
ModQuota = 2;
}
str HeldItem = "";
str BaseItem = "";
str ResultTable = "Possible Assemblies:\n\cu-------------------------";
bool Results = FALSE;
// Detect the currently held item and its base
for (int i = 0; i < ASSEMBLIES; i++) {
if (CheckInventory(StrParam(s:AssemblyDB[i][ASSEMBLY_BASE], s:"Selected"))) {
HeldItem = AssemblyDB[i][ASSEMBLY_BASE];
BaseItem = HeldItem;
break;
}
else if (CheckInventory(StrParam(s:AssemblyDB[i][ASSEMBLY_RESULT], s:"Selected"))) {
HeldItem = AssemblyDB[i][ASSEMBLY_RESULT];
BaseItem = AssemblyDB[i][ASSEMBLY_BASE];
break;
}
}
if (HeldItem != "") {
// Count applied modpacks
if (GetCvar("Debug")) {
Log(s:"checking modification of ", s:HeldItem);
}
AMods = CheckInventory(StrParam(s:HeldItem, s:"AgilityMod"));
BMods = CheckInventory(StrParam(s:HeldItem, s:"BulkMod"));
TMods = CheckInventory(StrParam(s:HeldItem, s:"TechnicalMod"));
PMods = CheckInventory(StrParam(s:HeldItem, s:"PowerMod"));
FMods = CheckInventory(StrParam(s:HeldItem, s:"FirestormMod"));
SMods = CheckInventory(StrParam(s:HeldItem, s:"SniperMod"));
NMods = CheckInventory(StrParam(s:HeldItem, s:"NanoMod"));
ModLimit = AMods + BMods + TMods + PMods + FMods + SMods + NMods;
// Decompose assembly if applicable
AsmSize = 0;
if (HeldItem != BaseItem) {
AMods += modreq(i, MOD_A);
BMods += modreq(i, MOD_B);
TMods += modreq(i, MOD_T);
PMods += modreq(i, MOD_P);
FMods += modreq(i, MOD_F);
SMods += modreq(i, MOD_S);
NMods += modreq(i, MOD_N);
AsmSize = modreq(i, ASSEMBLY_SIZE);
ModLimit += AsmSize;
}
if (GetCvar("Debug")) {
Log(i:ModLimit, s:" mods: ", i:AMods, s:"A ", i:BMods, s:"B ", i:TMods, s:"T ", i:PMods, s:"P ", i:FMods, s:"F ", i:SMods, s:"S ", i:NMods, s:"N ");
}
// Look for matching assemblies
for (int a = 0; a < ASSEMBLIES; a++) {
if (BaseItem != AssemblyDB[a][ASSEMBLY_BASE]) {
continue;
}
// Skip assemblies of the same or lower tier than the currently held
if (AsmSize > 0 && AsmSize >= modreq(a, ASSEMBLY_SIZE)) {
continue;
}
// Calculate remaining mods to complete assembly
NeededMods = 0;
NeededMods += max(0, modreq(a, MOD_A) - AMods);
NeededMods += max(0, modreq(a, MOD_B) - BMods);
NeededMods += max(0, modreq(a, MOD_T) - TMods);
NeededMods += max(0, modreq(a, MOD_P) - PMods);
NeededMods += max(0, modreq(a, MOD_S) - SMods);
NeededMods += max(0, modreq(a, MOD_F) - FMods);
NeededMods += max(0, modreq(a, MOD_N) - NMods);
if (NeededMods > ModQuota - ModLimit) {
continue;
}
// Ignore unknown assemblies if learning option is set
if (GetCVar(CVAR_KNOWN_ONLY) && !CheckInventory(AssemblyDB[a][ASSEMBLY_RECIPE])) {
if (GetCvar("Debug")) {
Log(s:"recipe for ", s:AssemblyDB[a][ASSEMBLY_RESULT], s:" is unknown");
}
continue;
}
if (GetCvar("Debug")) {
Log(s:"possible to complete ", s:AssemblyDB[a][ASSEMBLY_RESULT]);
}
Results = TRUE;
// Build the result entry
ResultTable = StrParam(s:ResultTable, s:"\n\cv", s:AssemblyDB[a][ASSEMBLY_NAME], s:"\cu: ");
for (int m = 0; m < 7; m++) {
for (int r = 0; r < modreq(a, m + MOD_A); r++) {
ResultTable = StrParam(s:ResultTable, s:ModName[m]);
}
}
}
}
// Be explicit about no results
if (!Results) {
ResultTable = StrParam(s:ResultTable, s:"\n\ccNothing");
PlaySound(0, "hud/error", CHAN_AUTO);
}
else {
PlaySound(0, "UI/PDA/Offline", CHAN_ITEM);
}
// Print the results
Print(s:ResultTable);
}