// 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 "ShopSys"
#include "zcommon.acs"
// Databases
#include "Items1.acs"
#include "Knowledge.acs"
#include "Monsters.acs"
// Historical databases
#include "Items0.acs"
// Global constants
#define VERSION "Zeta-Dimensional Shopping System v2.9 for DoomRL Arsenal v1.1.5"
#define CVAR_CREDITS "DRLZS_Credits"
#define CVAR_INSURANCE "DRLZS_Insurance"
#define CVAR_KILLS "DRLZS_KillBounty"
#define CVAR_ITEMS "DRLZS_ItemBounty"
#define CVAR_SECRETS "DRLZS_SecretBounty"
#define CVAR_EQUIPMENT "DRLZS_Equipment"
#define CVAR_EQUIPBONUS "DRLZS_EquipmentBonus"
#define CVAR_REVISION "DRLZS_Revision"
#define CVAR_KNOWLEDGE "DRLZS_Knowledge"
#define CVAR_CATALOG "DRLZS_Catalog"
#define CVAR_BESTIARY "DRLZS_Bestiary"
#define CVAR_AUTOSAVE "DRLZS_Autosave"
#define CVAR_AUTOINSURE "DRLZS_Autoinsure"
#define CVAR_SHOP_STYLE "DRLZS_ShopStyle"
#define CVAR_PERFORMANCE_BONUSES "DRLZS_PerformanceBonuses"
#define CVAR_CREDIT_CARRYOVER "DRLZS_CreditCarryOver"
#define CVAR_PRICING "DRLZS_Pricing"
#define KILL_BOUNTY 200
#define ITEM_BOUNTY 100
#define SECRET_BOUNTY 20
#define SKULL_QUOTA 5
#define PHASE_QUOTA 5
// GZDoom constant
#define BT_RUN 1<<25
// Shop states
#define SHOP_NONE 0
#define SHOP_BUY 1
#define SHOP_FAIL 2
#define SHOP_UNKNOWN 3
#define SHOP_WIDTH 960
#define SHOP_HEIGHT 720
// Shop styles
// 0: Direct Neural Interface
// 1: PDA
int ShopBackgroundWidth[2] = {160, 320};
int ShopBackgroundHeight[2] = {120, 200};
str ShopBackground[2] = {"ZSBG", "RLZPDA2"};
int ShopBackgroundTranslation[2] = {CR_BLACK, CR_UNTRANSLATED};
int ShopBackgroundAlpha[2] = {0.5, 1.0};
int ShopTextOffset[2] = {480.4, 440.4};
int ShopTitleOffset[2] = {36.1, 81.1};
int ShopInstruction1Offset[2] = {72.1, 107.1};
int ShopInstruction2Offset[2] = {86.1, 121.1};
int ShopCreditsOffset[2] = {122.1, 148.1};
int ShopStatusOffset[2] = {158.1, 178.1};
int ShopSpriteOffsetX[2] = {270.1, 230.1};
int ShopSpriteOffsetY[2] = {240.1, 245.1};
str ShopOpenSound[2] = {"hud/assemblyalert", "UI/PDA/Online"};
str ShopCloseSound[2] = {"misc/shopclose", "UI/PDA/Offline"};
// Backpack removal
str AmmoTypes[4] = {"Clip", "Shell", "RocketAmmo", "Cell"};
int AmmoPacks[4][2] = {{50, 10}, {20, 4}, {5, 1}, {100, 20}};
str AmmoSpawns[4][2] = {{"DroppedRLClipBox", "DroppedRLClip"}, {"DroppedRLShellBox", "DroppedRLShell"}, {"DroppedRLRocketBox", "DroppedRLRocket"}, {"DroppedRLCellPack", "DroppedRLCell"}};
/* Color reference:
a brick red h blue p olive
b tan i orange q dark green
c gray j white r dark red
d green k yellow s dark brown
e brown m black t purple
f gold n light blue u dark gray
g red o cream v cyan
*/
int Player, pulse, InsuranceID, ItemUnlocks;
bool newgame;
// Utility: compute x in the power of n
function int pow (int x, int n) {
if (n < 1) {
return 1;
}
int y = x;
while (--n) {
y *= x;
}
return y;
}
// Utility: cast a string into an integer
function int str2int(str string) {
int result, digit, power;
for (int c = StrLen(string) - 1; c >= 0; c--) {
digit = GetChar(string, c) - 48;
if (digit == -3 && c == 0) {
return result * -1;
}
else if (digit < 0 || digit > 9) {
return 0;
}
result += digit * pow(10, power);
power++;
}
return result;
}
// Utility: return the lesser of two integers
function int min(int a, int b) {
if (a < b) {
return a;
}
return b;
}
// Utility: return the greater of two integers
function int max(int a, int b) {
if (a > b) {
return a;
}
return b;
}
// Utility: return the absolute value of an integer
function int abs(int n) {
if (n < 0) {
return n * -1;
}
return n;
}
// Utility: encode a boolean array into a compressed string
function str EncodeBitArray(bool b1, bool b2, bool b3, bool b4, bool b5, bool b6) {
// Calculate the sum of the boolean array
int sum = b1 + 2 * b2 + 4 * b3 + 8 * b4 + 16 * b5 + 32 * b6;
// Map sums to their characters from the ASCII table
if (sum == 1) {
return "+";
}
else if (sum >= 2 && sum < 12) {
return StrParam(c:sum + 48 - 2); // 0-9
}
else if (sum >= 12 && sum < 38) {
return StrParam(c:sum + 65 - 12); // A-Z
}
else if (sum >= 38 && sum < 64) {
return StrParam(c:sum + 97 - 38); // a-z
}
return "-";
}
// Utility: decode a compressed string into a boolean array
function str DecodeBitArray(str word, int pos) {
int sum = GetChar(word, pos);
// Map characters to their sum from the ASCII table
if (sum == 43) { // +
sum = 1;
}
else if (sum >= 48 && sum < 58) { // 0-9
sum += 2 - 48;
}
else if (sum >= 65 && sum < 91) { // A-Z
sum += 12 - 65;
}
else if (sum >= 97 && sum < 123) { // a-z
sum += 38 - 97;
}
else { // - or anything else
sum = 0;
}
// Map the sum into a boolean array
int multiplier = 32;
str result = "";
for (int i = 0; i < 6; i++) {
if (sum < multiplier) {
result = StrParam(i:0, s:result);
}
else {
result = StrParam(i:1, s:result);
sum -= multiplier;
}
multiplier /= 2;
}
if (GetCvar("Debug")) {
Log(s:"bits ", c:GetChar(word, pos), s:" ", s:result);
}
return result;
}
// Utility: extract a substring from a string
function str substr(str string, int start, int end) {
if (start < 0) {
start = StrLen(string) + start;
if ( start < 0) {
start = 0;
}
}
else if (start > StrLen(string)) {
return "";
}
if (end == 0) {
end = StrLen(string);
}
else if (end > StrLen(string)) {
end = StrLen(string);
}
else if (end < 0) {
end = StrLen(string) + end;
}
else {
end++;
}
str result = "";
for (int i = start; i < end; i++) {
result = StrParam(s:result, c:GetChar(string, i));
}
return result;
}
// Utility: Check if a string ends with another string
function bool endswith(str string1, str string2) {
for (int i = 1; i < StrLen(string2) + 1; i++) {
if (GetChar(string1, StrLen(string1) - i) != GetChar(string2, StrLen(string2) - i)) {
return FALSE;
}
}
return TRUE;
}
// Utility: Get item data from the correct database version
function str ItemDB(int itemid, int index) {
if (GetCvar(CVAR_REVISION) == 0) {
return ItemDB0[itemid][index];
}
return ItemDB1[itemid][index];
}
// Utility: Strip any common suffix from an item name in the ItemDB
function str GetBaseItem(int itemid) {
str item = ItemDB(itemid, ITEM_SPAWN);
if (endswith(item, "Pickup")) {
return substr(item, 0, -6);
}
else if (endswith(item, "Item")) {
return substr(item, 0, -4);
}
return item;
}
// Utility: return an item known token
function str GetItemToken(int itemid) {
if (ItemDB(itemid, ITEM_SPAWN) == "") {
return "";
}
return StrParam(s:"ZS", s:substr(GetBaseItem(itemid), 2, 0), s:"KnownToken");
}
// Utility: return a monster killed token
function str GetMonsterToken(int uid) {
if (MonsterDB[uid][MONSTER_ID] == "") {
return "";
}
return StrParam(s:MonsterDB[uid][MONSTER_ID], s:"KillCounter");
}
// Utility: Return the price of an item by name, or 0 if not found
function int GetItemPrice(str item) {
for (int uid; uid < ITEMS; uid++) {
if (ItemDB(uid, ITEM_SPAWN) == item) {
return str2int(ItemDB(uid, ITEM_PRICE));
}
}
return 0;
}
// Utility: Return the mod bonus from the item name
function int ModBonus(str item) {
int bonus;
int i;
str modpacks[8] = {"Agility", "Bulk", "Power", "Technical", "Sniper", "Firestorm", "Nano", "Onyx"};
// Cybernetic Armor mod bonus
if (item == "RLCyberneticArmor") {
if (!CheckInventory("RLCyberneticArmorModdedToken")) {
return 0;
}
for (i = 0; i < 4; i++) {
if (CheckInventory(StrParam(s:"RLCyberneticArmor", s:modpacks[i], s:"Token"))) {
return GetItemPrice(StrParam(s:"RL", s:modpacks[i], s:"ModItem")) * 625 / 10000;
}
}
for (i = 4; i < 8; i++) {
if (CheckInventory(StrParam(s:"RLCyberneticArmor", s:modpacks[i], s:"Token"))) {
return GetItemPrice(StrParam(s:"RL", s:modpacks[i], s:"ModItem")) / 20;
}
}
}
// Increase the price for upgraded weapons
if (CheckInventory(StrParam(s:item, s:"DemonArtifacts")) && item != "RLMarathonShotguns" && item != "RLAntiFreakJackal") {
bonus += CheckInventory(StrParam(s:item, s:"DemonArtifacts")) * GetItemPrice("RLDemonArtifactItem") / 10;
}
// Increase the price for modded weapons
if (!CheckInventory(StrParam(s:item, s:"ModLimit"))) {
return bonus;
}
for (i = 0; i < 4; i++) {
bonus += CheckInventory(StrParam(s:item, s:modpacks[i], s:"Mod")) * GetItemPrice(StrParam(s:"RL", s:modpacks[i], s:"ModItem"));
}
bonus = bonus * 625 / 1000; // 62.5% of mod price
for (i = 4; i < 7; i++) {
bonus += CheckInventory(StrParam(s:item, s:modpacks[i], s:"Mod")) * GetItemPrice(StrParam(s:"RL", s:modpacks[i], s:"ModItem")) / 2;
}
bonus /= 10;
return bonus;
}
// Utility: check if an item has been sold before
function bool ItemKnown(int itemid) {
if (ItemDB(itemid, ITEM_UNLOCK) == "always") {
return TRUE;
}
else if (CheckInventory(GetItemToken(itemid))) {
return TRUE;
}
return FALSE;
}
// Utility: check if a monster has been killed before
function bool MonsterKnown(int uid) {
if (CheckInventory(GetMonsterToken(uid))) {
return TRUE;
}
return FALSE;
}
// Upgrade saved data between breaking revisions
function void UpgradeSaveData(void) {
switch (GetCvar(CVAR_REVISION)) {
case 0:
// Parse and unlock knowledge from previous versions
int cluster, bit;
str item = "";
str result = "";
if (GetCvar("Debug")) {
Log(s:"load knowledge ", s:GetCvarString(CVAR_KNOWLEDGE));
}
for (int uid = 0; uid < KNOWLEDGE; uid = uid + 6) {
if (GetCvar("Debug")) {
Log(s:"decoding cluster ", i:cluster + 1);
}
result = DecodeBitArray(GetCvarString(CVAR_KNOWLEDGE), cluster);
cluster++;
for (bit = 0; bit < 6; bit++){
if (GetChar(result, bit) == 49) {
item = KnowledgeDB[uid + bit];
if (item != "") {
GiveInventory(item, 1);
if (GetCvar("Debug")) {
Log(s:"giving ", s:item);
}
}
}
}
}
// Remove invalidated knowledge
TakeInventory("RLPlasmaInfusionAssemblyLearntToken", 1);
TakeInventory("RLAntiMaterielRifleAssemblyLearntToken", 1);
TakeInventory("RLConquerorShotgunAssemblyLearntToken", 1);
TakeInventory("RLZeusCannonAssemblyLearntToken", 1);
TakeInventory("RLSussGunFirestormLearntToken", 1);
TakeInventory("RLSussGunSniperLearntToken", 1);
TakeInventory("RLSussGunNanoLearntToken", 1);
TakeInventory("RLJackhammerFirestormLearntToken", 1);
TakeInventory("RLJackhammerSniperLearntToken", 1);
TakeInventory("RLJackhammerNanoLearntToken", 1);
TakeInventory("RLTantrumCannonFirestormLearntToken", 1);
TakeInventory("RLTantrumCannonSniperLearntToken", 1);
TakeInventory("RLTantrumCannonNanoLearntToken", 1);
TakeInventory("RLFragShotgunFirestormLearntToken", 1);
TakeInventory("RLFragShotgunSniperLearntToken", 1);
TakeInventory("RLFragShotgunNanoLearntToken", 1);
TakeInventory("RLParticleBeamCannonFirestormLearntToken", 1);
TakeInventory("RLParticleBeamCannonSniperLearntToken", 1);
TakeInventory("RLParticleBeamCannonNanoLearntToken", 1);
TakeInventory("RLRevenantsLauncherFirestormLearntToken", 1);
TakeInventory("RLRevenantsLauncherSniperLearntToken", 1);
TakeInventory("RLRevenantsLauncherNanoLearntToken", 1);
Log(s:"upgraded save data to revision 1");
SetCvar(CVAR_REVISION, 1);
case 1:
// Remove invalidated knowledge
TakeInventory("RLNuclearPlasmaShotgunAssemblyLearntToken", 1);
Log(s:"upgraded save data to revision 2");
SetCvar(CVAR_REVISION, 2);
}
}
// Award equipment bonus asynchronously
script "PerformanceEquipmentBonus" (void) {
// Lock nuke notification until all level start messages are clear
GiveInventory("ZSNukeWarningDelay", 1);
int equipment = 0;
bool carryover = GetCvar(CVAR_CREDIT_CARRYOVER);
if (carryover) {
equipment = GetCvar(CVAR_EQUIPBONUS);
}
int bounty;
int ratio = GetCvar(CVAR_PERFORMANCE_BONUSES);
if (!ratio) {
TakeInventory("ZSKillBountyToken", 1000000000);
TakeInventory("ZSItemBountyToken", 1000000000);
TakeInventory("ZSSecretBountyToken", 1000000000);
ratio = 1; // Division by zero protection
}
// Calculate performance bonus
while (CheckInventory("ZSKillBountyToken") >= KILL_BOUNTY / ratio) {
bounty++;
TakeInventory("ZSKillBountyToken", KILL_BOUNTY / ratio);
}
while (CheckInventory("ZSItemBountyToken") >= ITEM_BOUNTY / ratio) {
bounty++;
TakeInventory("ZSItemBountyToken", ITEM_BOUNTY / ratio);
}
while (CheckInventory("ZSSecretBountyToken") >= SECRET_BOUNTY / ratio) {
bounty++;
TakeInventory("ZSSecretBountyToken", SECRET_BOUNTY / ratio);
}
// Automatically purchase life insurance if enabled
if (GetCvar(CVAR_AUTOINSURE)) {
ACS_NamedExecuteAlways("BuyLifeInsurance", 0);
Delay(70);
}
// Award performance bonus
if (GetCvar("Debug")) {
Log(s:"bounties k:", i:CheckInventory("ZSKillBountyToken"), s:" i:", i:CheckInventory("ZSItemBountyToken"), s:" s:", i:CheckInventory("ZSSecretBountyToken"));
}
if (newgame && !carryover) {
bounty = 0;
}
if (bounty) {
AddCredits(bounty, "Performance bonus awarded!", "");
Delay(70);
}
// Award equipment bonus
if (newgame && equipment) {
newgame = FALSE;
AddCredits(equipment, "Equipment bonus awarded!", "");
Delay(70);
}
// Unlock nuke notification
TakeInventory("ZSNukeWarningDelay", 1);
}
// Initialize the shopping module and load all saved data
script "LevelInit" Enter {
newgame = FALSE;
// Set player TID, exactly as DRLA does it
Player = 30000 + PlayerNumber();
// Set the correct Life Insurance index in the ItemDB
for (int t = 0; t < ITEMS; t++) {
if (ItemDB(t, ITEM_SPAWN) == "ZSLifeInsurance") {
if (GetCvar("Debug")) {
Log(s:"life insurance index is ", i:t);
}
InsuranceID = t;
break;
}
}
// Deactivate life insurance
SetPlayerProperty(0, OFF, PROP_BUDDHA);
TakeInventory("ZSLifeInsuranceToken", 1);
TakeInventory("ZSUtilityTranslocatorTargeting", 1);
// Load saved state
if (!CheckInventory("ZSPocketShopToken")) {
newgame = TRUE;
// Print the package version
Log(s:VERSION);
// Give proper ammounts of things
if (GetCvar(CVAR_CREDIT_CARRYOVER)) {
GiveInventory("ZSCreditChip", GetCvar(CVAR_CREDITS));
}
// Give initial credits as per clean start option
else {
GiveInventory("ZSCreditChip", 10 * GetCvar(CVAR_PERFORMANCE_BONUSES / 2));
}
GiveInventory("ZSLifeInsurancePremium", GetCvar(CVAR_INSURANCE));
if (GetCvar("Debug")) {
Log(s:"load c:", i:GetCvar(CVAR_CREDITS), s:" p:", i:GetCvar(CVAR_INSURANCE), s:" k:", i:GetCvar(CVAR_KILLS), s:" i:", i:GetCvar(CVAR_ITEMS), s:" s:", i:GetCvar(CVAR_SECRETS));
}
GiveInventory("ZSKillBountyToken", GetCvar(CVAR_KILLS));
GiveInventory("ZSItemBountyToken", GetCvar(CVAR_ITEMS));
GiveInventory("ZSSecretBountyToken", GetCvar(CVAR_SECRETS));
GiveInventory("ZSPocketShopToken", 1);
int cluster, bit;
str result = "";
str item = "";
// Parse and unlock shop catalog items
if (GetCvar("Debug")) {
Log(s:"load catalog ", s:GetCvarString(CVAR_CATALOG));
}
cluster = 0;
int skip;
int inum = ITEMS;
if (GetCvar(CVAR_REVISION) == 0) {
skip = ITEMSKIP;
inum = ITEMS0;
}
for (int uid = skip; uid < inum; uid = uid + 6) {
if (GetCvar("Debug")) {
Log(s:"decoding cluster ", i:cluster + 1);
}
result = DecodeBitArray(GetCvarString(CVAR_CATALOG), cluster);
cluster++;
for (bit = 0; bit < 6; bit++){
if (GetChar(result, bit) == 49) {
if (ItemDB(uid + bit, ITEM_UNLOCK) == "always") {
continue;
}
item = GetItemToken(uid + bit);
if (item != "") {
GiveInventory(item, 1);
if (GetCvar("Debug")) {
Log(s:"giving ", s:item);
}
}
}
}
}
if (GetCvar("Debug")) {
Log(s:"load endgame equipment");
}
int item_id;
str EquipString = GetCvarString(CVAR_EQUIPMENT);
for (uid = 0; uid < StrLen(EquipString); uid = uid + 3) {
item_id = str2int(substr(EquipString, uid, uid + 2));
item = GetItemToken(item_id);
if (!CheckInventory(item) && ItemDB(item_id, ITEM_UNLOCK) != "always") {
if (GetCvar("Debug")) {
Log(s:"giving ", s:item);
}
ItemUnlocks++;
GiveInventory(item, 1);
}
}
// Parse and unlock bestiary entries
if (GetCvar("Debug")) {
Log(s:"load bestiary ", s:GetCvarString(CVAR_BESTIARY));
}
cluster = 0;
for (uid = 0; uid < MONSTERS; uid = uid + 6) {
if (GetCvar("Debug")) {
Log(s:"decoding cluster ", i:cluster + 1);
}
result = DecodeBitArray(GetCvarString(CVAR_BESTIARY), cluster);
cluster++;
for (bit = 0; bit < 6; bit++){
if (GetChar(result, bit) == 49) {
item = GetMonsterToken(uid + bit);
if (item != "") {
GiveInventory(item, 1);
if (GetCvar("Debug")) {
Log(s:"giving ", s:item);
}
}
}
}
}
// Run a player data upgrade
Delay(1);
UpgradeSaveData();
}
// Award performance and equipment bonuses
ACS_NamedExecute("PerformanceEquipmentBonus", 0);
// Keep track of ongoing performance bonuses
int NewKills, LevelKills, NewItems, LevelItems, NewSecrets, LevelSecrets;
bool LevelClear;
while (TRUE) {
// Save player data
if (GetCvar(CVAR_AUTOSAVE)) {
ACS_NamedExecute("SavePlayer", 0);
}
// Skip updating level stats if the player is dead
if (GetActorProperty(0, APROP_Health) <= 0) {
Delay(1);
continue;
}
NewKills = GetLevelInfo(LEVELINFO_KILLED_MONSTERS) - LevelKills;
if (NewKills) {
GiveInventory("ZSKillBountyToken", NewKills);
// Blink a notification on kill
if (CheckInventory("PowerScanner")) {
HUDMessage(s:"^"; HUDMSG_PLAIN, 0, CR_BRICK, 0.5, 0.5, 1.0 / 4 + 1);
}
if (GetCvar("Debug")) {
Log(s:"bounty k:", i:NewKills, s:" total:", i:CheckInventory("ZSKillBountyToken"));
}
LevelKills = GetLevelInfo(LEVELINFO_KILLED_MONSTERS);
}
if (! LevelClear && (GetLevelInfo(LEVELINFO_KILLED_MONSTERS) == GetLevelInfo(LEVELINFO_TOTAL_MONSTERS))) {
LevelClear = TRUE;
Print(s:"\cnYou feel relatively safe now.");
}
NewItems = GetLevelInfo(LEVELINFO_FOUND_ITEMS) - LevelItems;
if (NewItems) {
GiveInventory("ZSItemBountyToken", NewItems);
if (GetCvar("Debug")) {
Log(s:"bounty i:", i:NewItems, s:" total:", i:CheckInventory("ZSItemBountyToken"));
}
LevelItems = GetLevelInfo(LEVELINFO_FOUND_ITEMS);
}
NewSecrets = GetLevelInfo(LEVELINFO_FOUND_SECRETS) - LevelSecrets;
if (NewSecrets) {
GiveInventory("ZSSecretBountyToken", NewSecrets);
if (GetCvar("Debug")) {
Log(s:"bounty s:", i:NewSecrets, s:" total:", i:CheckInventory("ZSSecretBountyToken"));
}
LevelSecrets = GetLevelInfo(LEVELINFO_FOUND_SECRETS);
}
Delay(1);
}
}
// Utility: format and return an arbitrary credit amount
function str FormatCredits(int credits) {
str zeroes = "";
if (credits) {
zeroes = "00";
}
return StrParam(i:credits, s:zeroes);
}
// Utility: return the current formatted amount of carried credits
function str ShowCredits(void) {
return FormatCredits(CheckInventory("ZSCreditChip"));
}
// Add credits to the credit pool
function void AddCredits(int credits, str description1, str description2) {
str unlock_msg = "";
if (!newgame) {
if (ItemUnlocks == 1) {
unlock_msg = "\n\n\ckNew item unlocked!";
}
else if (ItemUnlocks > 1) {
unlock_msg = "\n\n\ckNew items unlocked!";
}
ItemUnlocks = 0;
}
GiveInventory("ZSCreditChip", credits);
Print(s:description1, s:description2 , s:"\c-\n\nCredits: \ca", s:ShowCredits(), s:"\c- (\cd+", s:FormatCredits(credits), s:"\c-)", s:unlock_msg);
if (GetCvar("Debug")) {
Log(s:description1, s:description2, s:"\c- +", i:credits, s:" = ", i:CheckInventory("ZSCreditChip"));
}
PlaySound(0, "misc/cash", CHAN_AUTO);
}
// Deduct credits from the credit pool
function bool TakeCredits(int credits, str description) {
if (CheckInventory("ZSCreditChip") < credits) {
return FALSE;
}
TakeInventory("ZSCreditChip", credits);
Print(s:"Bought the ", s:description, s:"\c-\n\nCredits: \ca", s:ShowCredits(), s:"\c- (\cn-", s:FormatCredits(credits), s:"\c-)");
if (GetCvar("Debug")) {
Log(s:"Bought the ", s:description, s:"\c- -", i:credits, s:" = ", i:CheckInventory("ZSCreditChip"));
}
PlaySound(0, "misc/cash", CHAN_AUTO);
return TRUE;
}
// Process item sale
function void CreditSoldItem(int itemid, int bonus) {
if (ItemDB(itemid, ITEM_UNLOCK) == "") {
str item = GetItemToken(itemid);
if (!CheckInventory(item)) {
ItemUnlocks++;
GiveInventory(item, 1);
}
}
AddCredits(str2int(ItemDB(itemid, ITEM_PRICE)) / 10 + bonus, "Sold the ", ItemDB(itemid, ITEM_NAME));
}
// Clean up everything that normally clears on weapon change
function void DeselectItem(void) {
TakeInventory("RLWeaponDrop", 1);
TakeInventory("RLScavengerDrop", 1);
GiveInventory("RLDeselectionFunction", 1);
}
// Sell the activated item
script "SellItem" (void) {
SetResultValue(FALSE);
for (int itemid = 0; itemid < ITEMS; itemid++) {
if (CheckInventory(ItemDB(itemid, ITEM_USE))) {
str itemtype = ItemDB(itemid, ITEM_TYPE);
// Skip held weapons if not set for dropping
if (itemtype == "weapon" && !CheckInventory("RLWeaponDrop") && !CheckInventory("RLScavengerDrop")) {
continue;
}
// Phase device special case
if (itemtype == "phase") {
str PhaseTypes[2] = {"RLPhaseDevice", "RLHomingPhaseDevice"};
int sold_phases;
if (!CheckInventory("RLUsePhaseDevice") && !CheckInventory("RLUseHomingPhaseDevice")) {
continue;
}
DeselectItem();
// Count phases in inventory
if (CheckInventory("RLPhaseDevice") + CheckInventory("RLHomingPhaseDevice") < PHASE_QUOTA) {
Print(s:"Not enough \cbphase devices\c- to warrant a sale.");
terminate;
}
// Take sold phases
str device = "RLPhaseDevice";
while (sold_phases < PHASE_QUOTA ) {
if (device == "RLHomingPhaseDevice") {
device = "RLPhaseDevice";
}
else
{
device = "RLHomingPhaseDevice";
}
if (CheckInventory(device)) {
TakeInventory(device, 1);
sold_phases++;
}
}
TakeInventory("RLPhaseDeviceLimit", PHASE_QUOTA);
AddCredits(str2int(ItemDB(itemid, ITEM_PRICE)) / 2, "Sold \cb", StrParam(i:PHASE_QUOTA, s:" Phase Devices"));
terminate;
}
// Recall phase device special case
if (itemtype == "recall") {
DeselectItem();
// Count phases in inventory
if (CheckInventory("RLRecallPhaseDevice") < PHASE_QUOTA) {
Print(s:"Not enough \cbRecall Phase Devices\c- to warrant a sale.");
terminate;
}
// Take the sold items
TakeInventory("RLRecallPhaseDevice", PHASE_QUOTA);
TakeInventory("RLPhaseDeviceLimit", PHASE_QUOTA);
AddCredits(str2int(ItemDB(itemid, ITEM_PRICE)) / 2, "Sold \cb", StrParam(i:PHASE_QUOTA, s:" Recall Phase Devices"));
terminate;
}
// Skull activation special case
if (itemtype == "skull") {
str SkullTypes[3] = {"RLBloodSkull", "RLFireSkull", "RLHatredSkull" };
int sold_skulls;
// Check for an activated skull
if (!CheckInventory("RLUseBloodSkull") && !CheckInventory("RLUseFireSkull") && !CheckInventory("RLUseHatredSkull")) {
continue;
}
DeselectItem();
// Count skulls in inventory
if (CheckInventory("RLSkullLimit") < SKULL_QUOTA) {
Print(s:"Not enough \cbSkulls\c- to warrant a sale.");
terminate;
}
// Take the sold skulls
int s = 0;
while (sold_skulls < SKULL_QUOTA) {
if (CheckInventory(SkullTypes[s])) {
TakeInventory(SkullTypes[s], 1);
sold_skulls++;
}
s++;
if (s == 3) {
s = 0;
}
}
TakeInventory("RLSkullLimit", SKULL_QUOTA);
if (!CheckInventory("ZSRandomSkullSpawnerKnownToken")) {
GiveInventory("ZSRandomSkullSpawnerKnownToken", 1);
ItemUnlocks++;
}
AddCredits(str2int(ItemDB(itemid, ITEM_PRICE)) / 2, "Sold \cb", StrParam(i:SKULL_QUOTA, s:" Skulls"));
terminate;
}
DeselectItem();
// Get the base item name and mod bonus
str BaseItem = GetBaseItem(itemid);
int bonus = ModBonus(BaseItem);
bool akimbo = FALSE;
// Deny zero-value sales
if (str2int(ItemDB(itemid, ITEM_PRICE)) / 10 + bonus == 0) {
Print(s:"This item is not valuable enough.");
PlaySound(0, "hud/error", CHAN_AUTO);
terminate;
}
// Item-specific inventory cleanup
if (itemtype == "armormod") {
// Armor mod inventory cleanup
TakeInventory("RLArmorModItemBlue", 1);
TakeInventory("RLArmorModItemRed", 1);
TakeInventory("RLArmorModItemInInventory", 1);
}
else if (itemtype == "demart") {
// Demon artifact inventory cleanup
TakeInventory("RLDemonArtifactLimit", 1);
}
else if (itemtype == "modpack") {
// Mod pack inventory cleanup
TakeInventory("RLModLimit", 1);
TakeInventory("RLScavengerModLimit", 1);
}
else if (itemtype == "armor") {
// Armor and boot inventory cleanup
TakeInventory("RLArmorInInventory", 1);
}
else if (itemtype == "exphase") {
TakeInventory("RLPhaseDeviceLimit", 1);
}
else if (itemtype == "weapon") {
GiveInventory("RLMisfireSpamPreventionCooldown", 1);
// Dual wielded Jackal & Casull special case
if (BaseItem == "RLAntiFreakJackal" && CheckInventory("RLAntiFreakJackalDemonArtifacts")) {
BaseItem = "RLHellsingARMSCasull";
itemid++;
TakeInventory("RLAntiFreakJackalDemonArtifacts", 1);
// Those two guns count as one, so selling one should not reduce the number of carried weapons
akimbo = TRUE;
}
// Dual wielded Marathon Shotguns special case
else if (BaseItem == "RLMarathonShotguns" && CheckInventory("RLMarathonShotgunsDemonArtifacts")) {
TakeInventory("RLMarathonShotgunsDemonArtifacts", 1);
// Re-select the Marathon Shotgun
TakeInventory("RLMarathonShotguns", 1);
GiveInventory("RLMarathonShotguns", 1);
SetWeapon("RLMarathonShotguns");
// Pay for the removed item
CreditSoldItem(itemid, bonus);
SetResultValue(TRUE);
terminate;
}
if (GetCvar("Debug")) {
Log(s:"removing base ", s:BaseItem, s:" mods ", i:CheckInventory(StrParam(s:BaseItem, s:"ModLimit")),
s:" artifacts ", i:CheckInventory(StrParam(s:BaseItem, s:"DemonArtifacts")));
}
TakeInventory(BaseItem, 1);
// Held weapon inventory cleanup
GiveInventory("RLRarityTokenRemover", 1);
// Assembly confirmation prompt cleamup
for (int c = 0; c < ITEMS; c++) {
if (GetChar(ItemDB(c, ITEM_NAME), 2) == "v") {
TakeInventory(StrParam(s:BaseItem, s:"Confirm"), 1);
}
}
GiveInventory("ZSNuclearSetBonusRemover", 1);
if (!akimbo) {
TakeInventory("RLWeaponLimit", 1);
}
TakeInventory(StrParam(s:BaseItem, s:"Zoom"), 1);
// Class-specific assembly tokens
TakeInventory("PowerRLTechnicianBasicDamage", 1);
TakeInventory("PowerRLTechnicianAdvancedDamage", 1);
TakeInventory("PowerRLTechnicianMasterDamage", 1);
// Mods and artifacts
TakeInventory(StrParam(s:BaseItem, s:"ModLimit"), 4);
TakeInventory(StrParam(s:BaseItem, s:"AgilityMod"), 4);
TakeInventory(StrParam(s:BaseItem, s:"PowerMod"), 4);
TakeInventory(StrParam(s:BaseItem, s:"BulkMod"), 4);
TakeInventory(StrParam(s:BaseItem, s:"TechnicalMod"), 4);
TakeInventory(StrParam(s:BaseItem, s:"SniperMod"), 4);
TakeInventory(StrParam(s:BaseItem, s:"FirestormMod"), 4);
TakeInventory(StrParam(s:BaseItem, s:"NanoMod"), 4);
TakeInventory(StrParam(s:BaseItem, s:"DemonArtifacts"), 3);
// Item-specific cleanup rules
if (BaseItem == "RLRCP120") {
TakeInventory("PowerRLRCP120Cloak", 1);
TakeInventory("RLRCP120CloakActive", 1);
}
else if (BaseItem == "RLBFG10K") {
TakeInventory("RLBFG10KFiringState",4);
}
else if (BaseItem == "RLHighPowerNuclearBFG9000") {
TakeInventory("RLHighPowerNuclearBFG9000Firing", 1);
}
else if (BaseItem == "RLTrigun") {
for (int ti = 1; ti < 6; ti++) {
TakeInventory(StrParam(s:"PowerRLTrigunDamage", i:ti), 1);
}
TakeInventory("PowerRLTrigunDamageX", 1);
}
else if (BaseItem == "RLAntiFreakJackal") {
TakeInventory("RLAntiFreakJackalNanoFiring", 1);
if (CheckInventory("RLAntiFreakJackalClip") > 6) {
TakeInventory("RLAntiFreakJackalClip", 666);
GiveInventory("RLAntiFreakJackalClip", 6);
}
}
else if (BaseItem == "RLHellsingARMSCasull") {
TakeInventory("RLHellsingARMSCasullNanoFiring", 1);
if (CheckInventory("RLHellsingARMSCasullClip") > 6) {
TakeInventory("RLHellsingARMSCasullClip", 666);
GiveInventory("RLHellsingARMSCasullClip", 6);
}
// Re-select the Anti-Freak Jackal
if (CheckInventory("RLAntiFreakJackal")) {
TakeInventory("RLAntiFreakJackal", 1);
GiveInventory("RLAntiFreakJackal", 1);
SetWeapon("RLAntiFreakJackal");
}
}
else if (BaseItem == "RLChameleonRifle") {
TakeInventory("RLChameleonRifleSniperModActive", 1);
TakeInventory("RLChameleonRifleFirestormModActive", 1);
TakeInventory("RLChameleonRifleNanoModActive", 1);
TakeInventory("RLChameleonRifleNanoSpeed", 52);
}
else if (BaseItem == "RLNanomachicArmamentGenerator") {
TakeInventory("RLNanomachicArmamentGeneratorClip", 999);
TakeInventory("RLNanomachicArmamentGeneratorGunType", 99);
}
else if (BaseItem == "RLTristarBlaster") {
TakeInventory("RLCerberusSetBonusTristarBlaster", 1);
TakeInventory("RLCerberusSetBonusActive", 1);
}
else if (BaseItem == "RLRealityDistortionArray") {
TakeInventory("RLRainbowSetBonusWeapon", 1);
TakeInventory("RLRainbowSetBonusActive", 1);
}
else if (BaseItem == "RLVoltgun") {
TakeInventory("RLTeslaboltSetBonusWeapon", 1);
TakeInventory("RLTeslaboltSetBonusActive", 1);
}
else if (BaseItem == "RLDuke2Rifle") {
TakeInventory("RLDuke2RifleLaserAmmo", 1);
TakeInventory("RLDuke2RifleRocketAmmo", 1);
TakeInventory("RLDuke2RifleFlamethrowerAmmo", 1);
TakeInventory("RLDuke2RiflePermanentLaserActive", 1);
TakeInventory("RLDuke2RiflePermanentRocketActive", 1);
TakeInventory("RLDuke2RiflePermanentFlamethrowerActive", 1);
}
}
// Remove the item from inventory
TakeInventory(ItemDB(itemid, ITEM_USE), 1);
TakeInventory(ItemDB(itemid, ITEM_SPAWN), 1);
// Pay for the removed item
CreditSoldItem(itemid, bonus);
SetResultValue(TRUE);
terminate;
}
if (ItemDB(itemid, ITEM_TYPE) == "backpack") {
int bpackskip = itemid - 1;
}
}
itemid = bpackskip + CheckInventory("RLBackpackType");
if (itemid != bpackskip) {
Print(s:"Press \ca", k:"+zoom", s:"\c- to sell your ", s:ItemDB(itemid, ITEM_NAME), s:"\c-.\n\nPress \ca",
k:"use ZSPocketShopToken", s:"\c- again to open the shop screen.");
// Give the player a second or two to confirm their choice
GiveInventory("ZSSpecialBackpackUse", 1);
for (int i = 0; i < 70; i++) {
if (!CheckInventory("ZSSpecialBackpackUse")) {
break;
}
// Cancel on player death
if (GetActorProperty(0, APROP_Health) <= 0) {
break;
}
if (GetPlayerInput(-1, INPUT_BUTTONS) == BT_ZOOM || GetPlayerInput(-1, INPUT_BUTTONS) == BT_ZOOM + BT_RUN) {
// Cancel weapon drop
TakeInventory("RLWeaponDrop", 1);
TakeInventory("RLScavengerDrop", 1);
// Reset ammo counts and capacities
int AmmoCapacities[4] = {400, 100, 100, 600};
for (int ammo = 0; ammo < 4; ammo++) {
SetAmmoCapacity(AmmoTypes[ammo], AmmoCapacities[ammo]);
if (CheckInventory(AmmoTypes[ammo]) > AmmoCapacities[ammo]) {
TakeInventory(AmmoTypes[ammo], CheckInventory(AmmoTypes[ammo]) - AmmoCapacities[ammo]);
}
}
// Remove the item from inventory
TakeInventory(StrParam(s:GetBaseItem(itemid), s:"Token"), 1);
TakeInventory("RLBackpackClipChosen",1);
TakeInventory("RLBackpackShellChosen",1);
TakeInventory("RLBackpackRocketChosen",1);
TakeInventory("RLBackpackCellChosen",1);
TakeInventory("RLBackpackType", 5);
// Pay for the removed item
CreditSoldItem(itemid, 0);
SetResultValue(TRUE);
break;
}
Delay(1);
}
TakeInventory("ZSSpecialBackpackUse", 1);
terminate;
}
Print(s:"Activate an item, then press \ca", k:"use ZSPocketShopToken", s:"\c- to sell it.\n\nPress \ca",
k:"use ZSPocketShopToken", s:"\c- again to open the shop screen.");
}
// Detect backpack removal to drop any extraneous ammo
script "BackpackWatch" Enter {
// Cache ammo counts and capacities
int BulletAmount, ShellAmount, RocketAmount, CellAmount;
int BulletCapacity = GetAmmoCapacity("Clip");
int ShellCapacity = GetAmmoCapacity("Shell");
int RocketCapacity = GetAmmoCapacity("RocketAmmo");
int CellCapacity = GetAmmoCapacity("Cell");
int amount, multiplier, large, small, capacity;
int SelectedType = -1;
// Cache special backpack type (dedicated backpack only)
str BackpackTypes[4] = {"RLBackpackClipChosen", "RLBackpackShellChosen", "RLBackpackRocketChosen", "RLBackpackCellChosen"};
if (CheckInventory("RLBackpackType") == 4) {
for (SelectedType = 0; SelectedType < 4; SelectedType++) {
if (CheckInventory(BackpackTypes[SelectedType])) {
if (GetCvar("Debug")) {
Log(s:"dedicated backpack set to ", s:AmmoTypes[SelectedType]);
}
break;
}
}
}
while (TRUE) {
// Capacities unchanged, update ammo counts
if (BulletCapacity == GetAmmoCapacity("Clip") && ShellCapacity == GetAmmoCapacity("Shell") && RocketCapacity == GetAmmoCapacity("RocketAmmo") && CellCapacity == GetAmmoCapacity("Cell")) {
BulletAmount = CheckInventory("Clip");
ShellAmount = CheckInventory("Shell");
RocketAmount = CheckInventory("RocketAmmo");
CellAmount = CheckInventory("Cell");
Delay(1);
continue;
}
// Lower capacity backpack, drop any extraneous ammo
else if (BulletCapacity > GetAmmoCapacity("Clip") || ShellCapacity > GetAmmoCapacity("Shell") || RocketCapacity > GetAmmoCapacity("RocketAmmo") || CellCapacity > GetAmmoCapacity("Cell")) {
for (int ammo = 0; ammo < 4; ammo++) {
large = 0;
small = 0;
switch (ammo) {
case 0:
amount = BulletAmount;
break;
case 1:
amount = ShellAmount;
break;
case 2:
amount = RocketAmount;
break;
case 3:
amount = CellAmount;
break;
}
multiplier = 1;
if (SelectedType == ammo) {
multiplier = 2;
Log(s:"double ammo for ", s:AmmoTypes[Ammo]);
}
// Drop ammo packs until within maximum capacity
capacity = GetAmmoCapacity(AmmoTypes[ammo]);
while (amount > capacity) {
// Large packs
if (amount - capacity >= AmmoPacks[ammo][0] * multiplier) {
SpawnProjectile(0, AmmoSpawns[ammo][0], random(0, 255), random(10, 40), random(40, 60), 1.0, 0);
amount -= AmmoPacks[ammo][0] * multiplier;
large++;
if (GetCvar("Debug")) {
Log(s:"dropped ", i:AmmoPacks[ammo][0] * multiplier, s:" ", s:AmmoTypes[ammo], s:" ", i:amount, s:" left");
}
}
// Small packs
else if (amount - capacity >= AmmoPacks[ammo][1] * multiplier) {
SpawnProjectile(0, AmmoSpawns[ammo][1], random(0, 255), random(10, 40), random(40, 60), 1.0, 0);
amount -= AmmoPacks[ammo][1] * multiplier;
small++;
if (GetCvar("Debug")) {
Log(s:"dropped ", i:AmmoPacks[ammo][0] * multiplier, s:" ", s:AmmoTypes[ammo], s:" ", i:amount, s:" left");
}
}
// Discard any leftovers
else {
if (GetCvar("Debug")) {
Log(s:"discarded ", i:amount - capacity, s:" ", s:AmmoTypes[ammo]);
}
amount = capacity;
}
Delay(1);
}
if (large + small) {
if (GetCvar("Debug")) {
Log(s:"dropped ", i:large, s:" large, ", i:small, s:" small ", s:AmmoTypes[ammo]);
}
}
}
// All done, start over
restart;
}
// Higher capacity backpack, start over
restart;
}
}
// Summon the selected item in front of the player in a teleport fog
script "SpawnItem" (int itemid) {
int ThingPosition = UniqueTID(0);
while (TRUE) {
SpawnProjectile(0, "MapSpot", GetActorAngle(0) >> 8, 128, 0, 0, ThingPosition);
Delay(3);
if (SpawnSpot(ItemDB(itemid, ITEM_SPAWN), ThingPosition)) {
break;
}
Thing_Remove(ThingPosition);
}
SpawnSpot("TeleportFog", ThingPosition);
Thing_Remove(ThingPosition);
PlaySound(0, "misc/teleport", CHAN_AUTO);
}
// Utility: Adjust item database offset
function int pos2itemid(int pos, int page) {
return pos + page * 36;
}
// Draw an item image on the shop screen
function void DrawItem(int pos, int page, bool highlight, int ShopStyle) {
// Calculate image positions and parameters
int row = pos / 6 + 1;
int col = pos % 6 + 1;
int pic = pos + 7;
int flags = HUDMSG_PLAIN | HUDMSG_ALPHA;
// Calculate proper highlight
int fade;
if (highlight) {
pulse++;
if (pulse == 10) {
pulse *= -1;
}
fade = abs(pulse);
// Draw an overlay if highlighting
pic = 6;
flags = HUDMSG_PLAIN | HUDMSG_ALPHA | HUDMSG_ADDBLEND;
}
else {
fade = 10;
}
int itemid = pos2itemid(pos, page);
// If this item cannot be bought, skip the rendering
if (itemid > ITEMS - 1 || ItemDB(itemid, ITEM_SPAWN) == "") {
HUDMessage(s:""; HUDMSG_PLAIN, pic, CR_UNTRANSLATED, 0.0, 0.0, 0.03);
return;
}
// Set the font to the item sprite
SetHudSize(640, 480, TRUE);
if (highlight) {
SetFont("ZSBOX");
}
else if (ItemKnown(itemid)) {
SetFont(ItemDB(itemid, ITEM_PIC));
}
else {
SetFont("ZSDOT");
}
// Display the sprite
HUDMessage(s:"A"; flags, pic,
CR_UNTRANSLATED, ShopSpriteOffsetX[ShopStyle] + 70.0 * (col - 3), ShopSpriteOffsetY[ShopStyle] + 50.0 * (row - 3), 0, 0.0 + 0.1 * fade);
SetHudSize(0, 0, FALSE);
}
// Display the shopping screen
script "OpenShop" (void) {
int ShopStyle = GetCvar(CVAR_SHOP_STYLE);
PlaySound(0, ShopOpenSound[ShopStyle], CHAN_AUTO);
SetPlayerProperty(0, ON, PROP_TOTALLYFROZEN);
Print(s:""); // Kill last print message
// Display the opening animation
if (ShopStyle == 0) {
SetHudSize(160, 120, TRUE);
SetFont("ZSBAR");
for (int r = 0; r < 120; r += 9) {
HUDMessage(s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 0, CR_UNTRANSLATED, 80.0, 1.0 * r + 0.1, 0.03, 0.5);
Delay(1);
}
SetFont("ZSFLASH");
for (r = -3; r < 3; r++) {
HUDMessage(s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 0, CR_UNTRANSLATED, 80.0, 60.0, 0.03, 0.1 * (10 - abs(r * 3)));
Delay(1);
}
}
// Set the background
SetHudSize(ShopBackgroundWidth[ShopStyle], ShopBackgroundHeight[ShopStyle], TRUE);
SetFont(ShopBackground[ShopStyle]);
HUDMessage(s:"A"; HUDMSG_PLAIN | HUDMSG_ALPHA, 99, ShopBackgroundTranslation[ShopStyle], ShopBackgroundWidth[ShopStyle] / 2 << 16, ShopBackgroundHeight[ShopStyle] / 2 << 16, 0, ShopBackgroundAlpha[ShopStyle]);
SetHudSize(SHOP_WIDTH, SHOP_HEIGHT, TRUE);
// Display shop headers
SetFont("BIGFONT");
HUDMessage(s:"Navigate with \ck", k:"+forward", s:" ", k:"+moveleft", s:" ", k:"+back",
s:" ", k:"+moveright", s:"\c-, change pages with \ck", k:"+use", s:" ", k:"+reload";
HUDMSG_PLAIN, 2, CR_GRAY, ShopTextOffset[ShopStyle], ShopInstruction1Offset[ShopStyle], 0);
HUDMessage(s:"Press \ck", k:"+jump", s:"\c- to confirm, \ck", k:"+zoom", s:"\c- to cancel"; HUDMSG_PLAIN, 3, CR_GRAY, ShopTextOffset[ShopStyle], ShopInstruction2Offset[ShopStyle], 0);
int pic, row, col, page, input, pos, itemid, result;
str price = "";
do {
// Display item name and price
SetHudSize(SHOP_WIDTH, SHOP_HEIGHT, TRUE);
SetFont("BIGFONT");
HUDMessage(s:"Zeta-Dimensional Shop \cu page ", i:page + 1, s:"/", i:ITEMS / 36; HUDMSG_PLAIN, 1, CR_GOLD, ShopTextOffset[ShopStyle], ShopTitleOffset[ShopStyle], 0);
HUDMessage(s:"Credits: \cf", s:ShowCredits(); HUDMSG_PLAIN, 4, CR_WHITE, ShopTextOffset[ShopStyle], ShopCreditsOffset[ShopStyle], 0);
itemid = pos2itemid(pos, page);
// Close the shop on death
if (GetActorProperty(0, APROP_Health) <= 0) {
result = SHOP_FAIL;
input = BT_ZOOM;
break;
}
switch (result) {
// Replace with warning if not enough credits
case SHOP_FAIL:
HUDMessage(s:"Not enough credits"; HUDMSG_PLAIN, 5, CR_RED, ShopTextOffset[ShopStyle], ShopStatusOffset[ShopStyle], 0);
result = SHOP_NONE;
break;
case SHOP_UNKNOWN:
HUDMessage(s:"Unknown item"; HUDMSG_PLAIN, 5, CR_RED, ShopTextOffset[ShopStyle], ShopStatusOffset[ShopStyle], 0);
result = SHOP_NONE;
break;
case SHOP_NONE:
if (ItemDB(itemid, ITEM_PRICE) == "0" || ItemDB(itemid, ITEM_PRICE) == "")
price = "";
else if (ItemDB(itemid, ITEM_SPAWN) == "ZSLifeInsurance")
if (CheckInventory("ZSLifeInsuranceToken"))
price = "\cu - \cdAlready active";
else
price = StrParam(s:"\cu - \cf", s:FormatCredits(str2int(ItemDB(itemid, ITEM_PRICE)) * GetCvar(CVAR_PRICING) + CheckInventory("ZSLifeInsurancePremium") + 1));
else
price = StrParam(s:"\cu - \cf", s:FormatCredits(str2int(ItemDB(itemid, ITEM_PRICE)) * GetCvar(CVAR_PRICING)));
if (ItemKnown(itemid))
HUDMessage(s:ItemDB(itemid, ITEM_NAME), s:price; HUDMSG_PLAIN, 5, CR_WHITE, ShopTextOffset[ShopStyle], ShopStatusOffset[ShopStyle], 0);
else
HUDMessage(s:""; HUDMSG_PLAIN, 5, CR_WHITE, ShopTextOffset[ShopStyle], ShopStatusOffset[ShopStyle], 0);
break;
}
pic = 7;
// Draw the item grid
for (row = 1; row < 7; row++) {
for (col = 1; col < 7; col++) {
DrawItem(row * 6 + col - 7, page, FALSE, ShopStyle);
pic++;
}
}
// Wait for input release
while (GetPlayerInput(-1, INPUT_BUTTONS) && GetPlayerInput(-1, INPUT_BUTTONS) != BT_RUN) {
DrawItem(pos, page, TRUE, ShopStyle);
// Skip on player death
if (GetActorProperty(0, APROP_Health) <= 0) {
break;
}
Delay(1);
}
// Get player input
while (TRUE) {
// Skip on player death
if (GetActorProperty(0, APROP_Health) <= 0) {
break;
}
// Terminate on opening the PDA
if (CheckInventory("RLSynthfireDisabled")) {
input = BT_ZOOM;
break;
}
input = GetPlayerInput(-1, INPUT_BUTTONS);
if (input & BT_RUN) {
input -= BT_RUN;
}
// Movement around the item grid
if (input == BT_FORWARD) {
PlaySound(0, "hud/nextpage", CHAN_AUTO);
do {
pos -= 6;
if (pos < 0) {
pos += 36;
}
} until (pos2itemid(pos, page) < ITEMS && ItemDB(pos2itemid(pos, page), ITEM_SPAWN) != "");
break;
}
else if (input == BT_BACK) {
PlaySound(0, "hud/nextpage", CHAN_AUTO);
do {
pos += 6;
if (pos > 35) {
pos -= 36;
}
} until (pos2itemid(pos, page) < ITEMS && ItemDB(pos2itemid(pos, page), ITEM_SPAWN) != "");
break;
}
else if (input == BT_MOVELEFT) {
PlaySound(0, "hud/nextpage", CHAN_AUTO);
do {
if (pos % 6 == 0) {
pos += 5;
}
else {
pos -= 1;
}
} until (pos2itemid(pos, page) < ITEMS && ItemDB(pos2itemid(pos, page), ITEM_SPAWN) != "");
break;
}
else if (input == BT_MOVERIGHT) {
PlaySound(0, "hud/nextpage", CHAN_AUTO);
do {
if (pos % 6 == 5) {
pos -= 5;
}
else {
pos += 1;
}
} until (pos2itemid(pos, page) < ITEMS && ItemDB(pos2itemid(pos, page), ITEM_SPAWN) != "");
break;
}
// Changing shop pages
else if (input == BT_RELOAD) {
PlaySound(0, "hud/nextpage", CHAN_AUTO);
page++;
if (page == ITEMS / 36) {
page = 0;
}
pos = 0;
break;
}
else if (input == BT_USE) {
PlaySound(0, "hud/nextpage", CHAN_AUTO);
page--;
if (page < 0) {
page = ITEMS / 36 - 1;
}
pos = 0;
break;
}
// Processing purchases
else if (input == BT_JUMP) {
if (ItemDB(itemid, ITEM_SPAWN) == "ZSLifeInsurance") {
if (CheckInventory("ZSLifeInsuranceToken")) {
PlaySound(0, "hud/error", CHAN_AUTO);
}
else if (CheckInventory("ZSCreditChip") < str2int(ItemDB(InsuranceID, ITEM_PRICE)) * GetCvar(CVAR_PRICING) + CheckInventory("ZSLifeInsurancePremium") + 1) {
PlaySound(0, "hud/error", CHAN_AUTO);
result = SHOP_FAIL;
}
else {
result = SHOP_BUY;
}
}
else if (!ItemKnown(itemid)) {
PlaySound(0, "hud/error", CHAN_AUTO);
result = SHOP_UNKNOWN;
}
else if (str2int(ItemDB(itemid, ITEM_PRICE)) * GetCvar(CVAR_PRICING) > CheckInventory("ZSCreditChip")) {
PlaySound(0, "hud/error", CHAN_AUTO);
result = SHOP_FAIL;
}
else {
result = SHOP_BUY;
}
break;
}
// Quitting the shop
else if (input == BT_ZOOM) {
break;
}
DrawItem(pos, page, TRUE, ShopStyle);
Delay(1);
}
} until (result == SHOP_BUY || input == BT_ZOOM);
// Reset the font and erase the screen
SetFont("SMALLFONT");
for (int i = 1; i <= pic; i++) {
HUDMessage(s:""; HUDMSG_PLAIN, i, CR_UNTRANSLATED, 0.0, 0.0, 0.03);
}
HUDMessage(s:""; HUDMSG_PLAIN, 99, CR_UNTRANSLATED, 0.0, 0.0, 0.03);
// Perform the purchase
if (result == SHOP_BUY) {
// Life insurance wanted
if (ItemDB(itemid, ITEM_SPAWN) == "ZSLifeInsurance") {
ACS_NamedExecuteAlways("BuyLifeInsurance", 0);
}
// Ordinary item wanted
else {
if (TakeCredits(str2int(ItemDB(itemid, ITEM_PRICE)) * GetCvar(CVAR_PRICING), ItemDB(itemid, ITEM_NAME))) {
ACS_NamedExecuteAlways("SpawnItem", 0, itemid);
}
}
}
else if (result == SHOP_NONE) {
PlaySound(0, ShopCloseSound[ShopStyle], CHAN_AUTO, 0.75);
}
// Wait for input release
while (GetPlayerInput(-1, INPUT_BUTTONS) && GetPlayerInput(-1, INPUT_BUTTONS) != BT_RUN) {
// Skip on player death
if (GetActorProperty(0, APROP_Health) <= 0) {
break;
}
Delay(1);
}
SetPlayerProperty(0, OFF, PROP_TOTALLYFROZEN);
}
// Protect the player from untimely death
script "BuyLifeInsurance" (void) {
// Check if the player already has an active life insurance
if (CheckInventory("ZSLifeInsuranceToken")) {
Print(s:"You already have life insurance.");
terminate;
}
// Perform the purchase
if (!TakeCredits(str2int(ItemDB(InsuranceID, ITEM_PRICE)) * GetCvar(CVAR_PRICING) + CheckInventory("ZSLifeInsurancePremium") + 1, ItemDB(InsuranceID, ITEM_NAME))) {
Print(s:"\cgNot enough credits for life insurance renewal.");
PlaySound(0, "hud/error", CHAN_AUTO);
terminate;
}
// Decrease the insurance premium
TakeInventory("ZSLifeInsurancePremium", 1);
if (GetCvar("Debug")) {
Log(s:"insurance p:", i:CheckInventory("ZSLifeInsurancePremium"));
}
// Activate insurance
GiveInventory("ZSLifeInsuranceToken", 1);
SetPlayerProperty(0, ON, PROP_BUDDHA);
while (!CheckActorProperty(0, APROP_Health, 1)) {
// Allow deactivating life insurance by taking away the token
if (!CheckInventory("ZSLifeInsuranceToken")) {
if (GetCvar("Debug")) {
Log(s:"life insurance deactivated");
}
SetPlayerProperty(0, OFF, PROP_BUDDHA);
terminate;
}
Delay(1);
}
// Evacuate the player
GiveInventory("ArtiTeleport", 1);
UseInventory("ArtiTeleport");
PlaySound(0, "misc/teleport", CHAN_AUTO);
TakeInventory("ZSLifeInsuranceToken", 1);
Print(s:"Life insurance activated.\n\nThank you for choosing our life insurance services.");
// Increase the insurance premium
if (CheckInventory("ZSLifeInsurancePremium")) {
GiveInventory("ZSLifeInsurancePremium", 1);
}
GiveInventory("ZSLifeInsurancePremium", str2int(ItemDB(InsuranceID, ITEM_PRICE)));
if (GetCvar("Debug")) {
Log(s:"insurance p:", i:CheckInventory("ZSLifeInsurancePremium"));
}
// Refill health
while (GetActorProperty(0, APROP_Health) < 100) {
SetActorProperty(0, APROP_Health, min(GetActorProperty(0, APROP_Health) + 2, 100));
Delay(1);
}
// Deactivate insurance
SetPlayerProperty(0, OFF, PROP_BUDDHA);
// Automatically renew insurance if enabled
if (GetCvar(CVAR_AUTOINSURE)) {
restart;
}
}
// Throttle player data scan
int flush_timer;
function bool Throttle(int flush_delay) {
flush_timer += 1;
if (flush_timer >= flush_delay) {
flush_timer = 0;
return TRUE;
}
return FALSE;
}
// Save player character data
script "SavePlayer" (void) {
// No saving on test map
if (StrParam(n:PRINTNAME_LEVEL) == "test" || StrParam(n:PRINTNAME_LEVEL) == "rl_titem") {
if (!GetCvar("Debug")) {
terminate;
}
}
// Save inventories to variables
SetCvar(CVAR_CREDITS, CheckInventory("ZSCreditChip"));
SetCvar(CVAR_INSURANCE, CheckInventory("ZSLifeInsurancePremium"));
SetCvar(CVAR_KILLS, CheckInventory("ZSKillBountyToken"));
SetCvar(CVAR_ITEMS, CheckInventory("ZSItemBountyToken"));
SetCvar(CVAR_SECRETS, CheckInventory("ZSSecretBountyToken"));
// Calculate equipment bonus
int equipment, bonus;
str BaseItem = "";
str EquipString = "";
for (int itemid = 0; itemid < ITEMS; itemid++) {
// Void equipment bonus on player death
if (GetActorProperty(0, APROP_Health) <= 0) {
EquipString = "";
equipment = 0;
break;
}
BaseItem = GetBaseItem(itemid);
// Item in inventory
if (CheckInventory(ItemDB(itemid, ITEM_SPAWN))) {
bonus = CheckInventory(ItemDB(itemid, ITEM_SPAWN)) * (str2int(ItemDB(itemid, ITEM_PRICE)) / 10);
}
// Equipped item
else if (CheckInventory(BaseItem) || CheckInventory(StrParam(s:BaseItem, s:"Token"))) {
bonus = str2int(ItemDB(itemid, ITEM_PRICE)) / 10 + ModBonus(BaseItem);
}
else {
bonus = 0;
}
if (bonus) {
// Store unlockable item IDs
EquipString = StrParam(s:EquipString, s:substr(StrParam(s:"000", i:itemid), -3, 0));
equipment += bonus;
}
if (Throttle(9)) {
Delay(1);
}
}
// Save equipment bonus
if (EquipString != GetCvarString(CVAR_EQUIPMENT) && StrLen(EquipString < 241)) {
if (GetCvar("Debug")) {
Log(s:"save equipment ", s:EquipString, s:" =", i:equipment);
}
SetCvar(CVAR_EQUIPBONUS, equipment);
SetCvarString(CVAR_EQUIPMENT, EquipString);
}
// Save catalog
str knowstring = "";
for (int uid = 0; uid < ITEMS; uid = uid + 6) {
knowstring = StrParam(s:knowstring, s:EncodeBitArray(
ItemKnown(uid), ItemKnown(uid + 1), ItemKnown(uid + 2),
ItemKnown(uid + 3), ItemKnown(uid + 4), ItemKnown(uid + 5)
));
if (Throttle(18)) {
Delay(1);
}
}
if (knowstring != GetCvarString(CVAR_CATALOG)) {
if (GetCvar("Debug")) {
Log(s:"save catalog ", s:knowstring);
}
SetCvarString(CVAR_CATALOG, knowstring);
}
// Save bestiary
knowstring = "";
for (uid = 0; uid < MONSTERS; uid = uid + 6) {
knowstring = StrParam(s:knowstring, s:EncodeBitArray(
MonsterKnown(uid), MonsterKnown(uid + 1), MonsterKnown(uid + 2),
MonsterKnown(uid + 3), MonsterKnown(uid + 4), MonsterKnown(uid + 5)
));
if (Throttle(18)) {
Delay(1);
}
}
if (knowstring != GetCvarString(CVAR_BESTIARY)) {
if (GetCvar("Debug")) {
Log(s:"save bestiary ", s:knowstring);
}
SetCvarString(CVAR_BESTIARY, knowstring);
}
}
// Debugging script: grant all knowledge and test databases
script "DebugFullUnlock" (void) {
if (!GetCvar("Debug")) {
terminate;
}
int uid;
int tid = UniqueTID();
str BaseItem = "";
for (uid = 0; uid < ITEMS; uid++) {
BaseItem = GetBaseItem(uid);
if (BaseItem != "" && ItemDB(uid, ITEM_UNLOCK) != "always") {
GiveInventory(GetItemToken(uid), 1);
}
SpawnSpotForced(ItemDB(uid, ITEM_SPAWN), Player, tid, 0);
SpawnSpotForced(ItemDB(uid, ITEM_USE), Player, tid, 0);
Thing_Remove(tid);
}
for (uid = 0; uid < MONSTERS; uid++) {
if (MonsterDB[uid][MONSTER_ID] != "") {
GiveInventory(GetMonsterToken(uid), 1);
SpawnSpotForced(MonsterDB[uid][MONSTER_ID], Player, tid, 0);
Thing_Remove(tid);
}
}
}
// Reset player character info to the initial state
script "ResetPlayer" (void) {
if (GetCvar(CVAR_EQUIPBONUS)) {
Log(s:"\caWarning:\c- You have ", i:GetCvar(CVAR_EQUIPBONUS), s:"00 credits worth of equipment");
}
TakeInventory("ZSCreditChip", 1000000000);
ACS_NamedTerminate("BuyLifeInsurance", 0);
SetPlayerProperty(0, OFF, PROP_BUDDHA);
TakeInventory("ZSLifeInsuranceToken", 1);
TakeInventory("ZSLifeInsurancePremium", 1000000000);
GiveInventory("ZSLifeInsurancePremium", 3);
TakeInventory("ZSKillBountyToken", 1000000000);
TakeInventory("ZSItemBountyToken", 1000000000);
TakeInventory("ZSSecretBountyToken", 1000000000);
int uid;
for (uid = 0; uid < ITEMS; uid++) {
TakeInventory(GetItemToken(uid), 1);
}
for (uid = 0; uid < MONSTERS; uid++) {
if (MonsterDB[uid][MONSTER_ID] != "") {
TakeInventory(GetMonsterToken(uid), 1000000000);
}
}
Log(s:"Player data has been reset");
}
// Pull an object to the player
script "ForcePull" (void) {
// Deactivate if activated again
if (CheckInventory("ZSUtilityTranslocatorTargeting")) {
TakeInventory("ZSUtilityTranslocatorTargeting", 1);
terminate;
}
if (GetCvar("Debug")) {
Log(s:"translocator targeting activated");
}
GiveInventory("ZSUtilityTranslocatorTargeting", 1);
// Remove item manipulation tokens
DeselectItem();
int tid, targeted, lockon;
int TeleportDestination = UniqueTID();
while (lockon < 216) {
// Stop if deactivated or sold
if (!CheckInventory("ZSUtilityTranslocatorTargeting") || !CheckInventory("ZSUtilityTranslocator")) {
if (GetCvar("Debug")) {
Log(s:"translocator targeting aborted");
}
terminate;
}
// Check for signs of trying to trigger an item drop
if (CheckInventory("RLWeaponDrop") || CheckInventory("RLScavengerDrop") || CheckInventory("RLFistDropAttempt")) {
DeselectItem();
GiveInventory("ZSUtilityTranslocatorDrop", 1);
if (GetCvar("Debug")) {
Log(s:"translocator tossed");
}
terminate;
}
// Attempt to target a non-living object and assign it a ThingID if needed
tid = PickActor(0, GetActorAngle(0), GetActorPitch(0), 1024.0, 0, MF_SPECIAL | MF_CORPSE, 0, PICKAF_RETURNTID);
if (tid == 0) {
tid = UniqueTID();
}
// Target acquired
if (PickActor(0, GetActorAngle(0), GetActorPitch(0), 1024.0, tid, MF_SPECIAL | MF_CORPSE, 0, PICKAF_FORCETID)) {
// Not the current target, initialize lock-on
if (targeted != tid) {
targeted = tid;
if (lockon > 0) {
lockon = 0;
}
if (GetCvar("Debug")) {
Log(s:"targeted ", s:GetActorClass(tid), s:" tid ", i:tid);
}
}
// Current target, advance lock-on
else {
lockon++;
if (lockon >= 0 && lockon % 18 == 0) {
PlaySound(0, "weapons/combattranslocatorbeep", CHAN_AUTO);
}
else if (lockon > 144 && lockon % 9 == 0) {
PlaySound(0, "weapons/combattranslocatorbeep", CHAN_AUTO);
}
}
}
// Target not acquired
else {
// Remove target, reset lock-on
targeted = 0;
if (lockon > 0) {
lockon = -18;
if (GetCvar("Debug")) {
Log(s:"target lost");
}
}
// Normal beep
else if (lockon == 0) {
PlaySound(0, "weapons/combattranslocatorbeep", CHAN_AUTO);
lockon = -36;
}
lockon++;
}
// Colorize the seeking indicator
if (lockon < -31) {
HUDMessage(s:"[ ]"; HUDMSG_PLAIN, 0, CR_GOLD, 0.5, 0.5, 1.0 / 35 + 1);
}
else if (lockon < 0) {
HUDMessage(s:"[ ]"; HUDMSG_PLAIN, 0, CR_GRAY, 0.5, 0.5, 1.0 / 35 + 1);
}
else if (lockon >= 0 && lockon % 18 < 4) {
HUDMessage(s:"[ ]"; HUDMSG_PLAIN, 0, CR_GOLD, 0.5, 0.5, 1.0 / 35 + 1);
}
else if (lockon > 144 && lockon % 9 < 4) {
HUDMessage(s:"[ ]"; HUDMSG_PLAIN, 0, CR_GOLD, 0.5, 0.5, 1.0 / 35 + 1);
}
else {
HUDMessage(s:"[ ]"; HUDMSG_PLAIN, 0, CR_GREEN, 0.5, 0.5, 1.0 / 35 + 1);
}
delay(1);
}
// Teleport the targeted object to the player
PlaySound(0, "weapons/combattranslocatoractivate", CHAN_AUTO);
if (GetCvar("Debug")) {
Log(s:"lock-on complete");
}
// Stop targeting
TakeInventory("ZSUtilityTranslocatorTargeting", 1);
// Enforce a unique ThingID, just in case
tid = UniqueTID();
SpawnProjectile(0, "MapSpot", GetActorAngle(0) >> 8, 128, 0, 0, TeleportDestination);
Delay(3);
if (PickActor(0, GetActorAngle(0), GetActorPitch(0), 1024.0, tid, MF_SPECIAL | MF_CORPSE, 0, PICKAF_FORCETID) &&
SetActorPosition(tid, GetActorX(TeleportDestination), GetActorY(TeleportDestination), GetActorZ(TeleportDestination), TRUE)) {
// Reset ThingID
Thing_ChangeTID(tid, targeted);
Thing_Remove(TeleportDestination);
// Reload the translocator with a phase device
if (CheckInventory("RLPhaseDevice")) {
if (GetCvar("Debug")) {
Log(s:"translocator recharged with phase device");
}
Print(s:"\cdUtility Translocator\c- recharged with a \ctPhase Device\c-");
TakeInventory("RLPhaseDevice", 1);
}
else if (CheckInventory("RLHomingPhaseDevice")) {
if (GetCvar("Debug")) {
Log(s:"translocator recharged with homing phase device");
}
Print(s:"\cdUtility Translocator\c- recharged with a \ctHoming Phase Device\c-");
TakeInventory("RLHomingPhaseDevice", 1);
}
else if (CheckInventory("RLRecallPhaseDevice")) {
if (GetCvar("Debug")) {
Log(s:"translocator recharged with recall phase device");
}
Print(s:"\cdUtility Translocator\c- recharged with a \ctRecall Phase Device\c-");
TakeInventory("RLRecallPhaseDevice", 1);
}
// Or use up the translocator itself
else {
if (GetCvar("Debug")) {
Log(s:"utility translocator used up");
}
Print(s:"\cdUtility Translocator\cg charge used up.");
TakeInventory("ZSUtilityTranslocator", 1);
}
// Play reloading sound effects
if (CheckInventory("ZSUtilityTranslocator")) {
TakeInventory("RLPhaseDeviceLimit", 1);
Delay(9);
PlaySound(0, "weapons/plasmarifleload", CHAN_AUTO);
Delay(16);
PlaySound(0, "misc/infraredpickup", CHAN_AUTO);
Delay(61);
PlaySound(0, "misc/shopclose", CHAN_AUTO);
}
else {
Delay(9);
PlaySound(0, "weapons/nuclearonslaughtnanoimpact", CHAN_AUTO);
Delay(16);
PlaySound(0, "misc/jetpackoff", CHAN_AUTO);
}
}
// Teleport failed
else {
Thing_Remove(TeleportDestination);
Delay(9);
PlaySound(0, "hud/error", CHAN_AUTO);
}
}