Created
December 11, 2017 21:38
-
-
Save Liareth/d8218197e55b8926c3dbb9e52f9bae99 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This header includes everything that is necessary for spawning loot on Arelith. | |
#include "gs_inc_pc" | |
#include "inc_lootdist" | |
#include "inc_lootgen" | |
#include "inc_lootname" | |
#include "inc_lootresref" | |
#include "mi_inc_database" | |
#include "mi_log" | |
#include "x3_inc_string" | |
// ----- PUBLIC API ------ | |
// TIER ONE describes basic adventuring gear. | |
// LOW [tier1l]: // 1 -> 3, 1 +1 stat, 60%/40% +1/+2 skill | |
// MEDIUM [tier1m]: // 2 -> 4, 1 +1 stat, 50%/50% +1/+2 skill | |
// HIGH [tier1h]: // 3 -> 4, 1 +1 stat, 40%/60% +1/+2 skill | |
const string LOOT_TEMPLATE_TIER_1 = "tier1"; | |
// TIER TWO describes advanced adventuring gear. | |
// LOW [tier2l]: // 3 -> 5, 2 +1 stats, 60%/40% +1/+2 skill | |
// MEDIUM [tier2m]: // 4 -> 6, 2 +1 stats, 50%/50% +1/+2 skill | |
// HIGH [tier2h]: // 5 -> 6, 2 +1 stats, 40%/60% +1/+2 skill | |
const string LOOT_TEMPLATE_TIER_2 = "tier2"; | |
// These constants describe contexts for which we're distributing loot. | |
const int LOOT_CONTEXT_MOB_LOW = 0; | |
const int LOOT_CONTEXT_MOB_MEDIUM = 1; | |
const int LOOT_CONTEXT_MOB_HIGH = 2; | |
const int LOOT_CONTEXT_BOSS_LOW = 3; | |
const int LOOT_CONTEXT_BOSS_MEDIUM = 4; | |
const int LOOT_CONTEXT_BOSS_HIGH = 5; | |
const int LOOT_CONTEXT_CHEST_LOW = 6; | |
const int LOOT_CONTEXT_CHEST_MEDIUM = 7; | |
const int LOOT_CONTEXT_CHEST_HIGH = 8; | |
// Called once by gs_m_load to initialise the loot system. | |
void InitialiseLootSystem(); | |
// Called once for each time we wish to spawn loot. | |
// This function may create one loot item, many loot items, or no loot items. | |
void CreateLoot(int context, object container, object creature); | |
// ----- INTERNAL API ------ | |
// These postfixes combine to form the loot template name passed to inc_lootgen. | |
// e.g. tier1h, tier2l. | |
const string INTERNAL_POSTFIX_LOW = "l"; | |
const string INTERNAL_POSTFIX_MEDIUM = "m"; | |
const string INTERNAL_POSTFIX_HIGH = "h"; | |
const string INTERNAL_LOOT_BUCKET_MOB = "mob"; | |
const string INTERNAL_LOOT_BUCKET_BOSS = "boss"; | |
const string INTERNAL_LOOT_BUCKET_CHEST = "chest"; | |
void INTERNAL_CreateProceduralLoot(string template, int context, object container, object creature); | |
string INTERNAL_LootBucketFromContext(int context); | |
string INTERNAL_GetPostfixFromContext(int context); | |
// | |
// ----- | |
// | |
void InitialiseLootSystem() | |
{ | |
// Set up the resref arrays. We select one of these based on what we want to spawn. | |
// These use the expanded base item set in inc_baseitem. | |
AddBaseItemResRef(BASE_ITEM_AMULET, "gs_item209"); | |
//TODO: AddBaseItemResRef(BASE_ITEM_ARMOR_AC0, "TODO"); | |
//TODO: AddBaseItemResRef(BASE_ITEM_ARMOR_AC1, "TODO"); | |
//TODO: AddBaseItemResRef(BASE_ITEM_ARMOR_AC2, "TODO"); | |
//TODO: AddBaseItemResRef(BASE_ITEM_ARMOR_AC3, "TODO"); | |
//TODO: AddBaseItemResRef(BASE_ITEM_ARMOR_AC4, "TODO"); | |
//TODO: AddBaseItemResRef(BASE_ITEM_ARMOR_AC5, "TODO"); | |
//TODO: AddBaseItemResRef(BASE_ITEM_ARMOR_AC6, "TODO"); | |
//TODO: AddBaseItemResRef(BASE_ITEM_ARMOR_AC7, "TODO"); | |
//TODO: AddBaseItemResRef(BASE_ITEM_ARMOR_AC8, "TODO"); | |
AddBaseItemResRef(BASE_ITEM_BASTARDSWORD, "nw_wswbs001"); | |
AddBaseItemResRef(BASE_ITEM_BATTLEAXE, "nw_waxbt001"); | |
AddBaseItemResRef(BASE_ITEM_BELT, "gs_item258"); | |
AddBaseItemResRef(BASE_ITEM_BOOTS, "gs_item311"); | |
AddBaseItemResRef(BASE_ITEM_BRACER, "gs_item270"); | |
AddBaseItemResRef(BASE_ITEM_CLOAK, "gs_item284"); | |
AddBaseItemResRef(BASE_ITEM_CLUB, "nw_wblcl001"); | |
AddBaseItemResRef(BASE_ITEM_DAGGER, "nw_wswdg001"); | |
AddBaseItemResRef(BASE_ITEM_DIREMACE, "nw_wdbma001"); | |
AddBaseItemResRef(BASE_ITEM_DOUBLEAXE, "nw_wdbax001"); | |
AddBaseItemResRef(BASE_ITEM_DWARVENWARAXE, "x2_wdwraxe001"); | |
AddBaseItemResRef(BASE_ITEM_GLOVES, "gs_item294"); | |
AddBaseItemResRef(BASE_ITEM_GREATAXE, "nw_waxgr001"); | |
AddBaseItemResRef(BASE_ITEM_GREATSWORD, "nw_wswgs001"); | |
AddBaseItemResRef(BASE_ITEM_HALBERD, "nw_wplhb001"); | |
AddBaseItemResRef(BASE_ITEM_HANDAXE, "nw_waxhn001"); | |
AddBaseItemResRef(BASE_ITEM_HEAVYCROSSBOW, "nw_wbwxh001"); | |
AddBaseItemResRef(BASE_ITEM_HEAVYFLAIL, "nw_wblfh001"); | |
AddBaseItemResRef(BASE_ITEM_HELMET, "x2_it_arhelm03"); | |
AddBaseItemResRef(BASE_ITEM_KAMA, "nw_wspka001"); | |
AddBaseItemResRef(BASE_ITEM_KATANA, "nw_wswka001"); | |
AddBaseItemResRef(BASE_ITEM_KUKRI, "nw_wspku001"); | |
AddBaseItemResRef(BASE_ITEM_LARGESHIELD, "nw_ashlw001"); | |
AddBaseItemResRef(BASE_ITEM_LIGHTCROSSBOW, "nw_wbwxl001"); | |
AddBaseItemResRef(BASE_ITEM_LIGHTFLAIL, "nw_wblfl001"); | |
AddBaseItemResRef(BASE_ITEM_LIGHTHAMMER, "nw_wblhl001"); | |
AddBaseItemResRef(BASE_ITEM_LIGHTMACE, "nw_wblml001"); | |
AddBaseItemResRef(BASE_ITEM_LONGBOW, "nw_wbwln001"); | |
AddBaseItemResRef(BASE_ITEM_LONGSWORD, "nw_wswls001"); | |
AddBaseItemResRef(BASE_ITEM_MORNINGSTAR, "nw_wblms001"); | |
AddBaseItemResRef(BASE_ITEM_QUARTERSTAFF, "nw_wdbqs001"); | |
AddBaseItemResRef(BASE_ITEM_RAPIER, "nw_wswrp001"); | |
AddBaseItemResRef(BASE_ITEM_RING, "gs_item252"); | |
AddBaseItemResRef(BASE_ITEM_SCIMITAR, "nw_wswsc001"); | |
AddBaseItemResRef(BASE_ITEM_SCYTHE, "nw_wplsc001"); | |
AddBaseItemResRef(BASE_ITEM_SHORTBOW, "nw_wbwsh001"); | |
AddBaseItemResRef(BASE_ITEM_SHORTSPEAR, "nw_wplss001"); | |
AddBaseItemResRef(BASE_ITEM_SHORTSWORD, "nw_wswss001"); | |
AddBaseItemResRef(BASE_ITEM_SICKLE, "nw_wspsc001"); | |
AddBaseItemResRef(BASE_ITEM_SLING, "nw_wbwsl001"); | |
AddBaseItemResRef(BASE_ITEM_SMALLSHIELD, "nw_ashsw001"); | |
AddBaseItemResRef(BASE_ITEM_TOWERSHIELD, "nw_ashto001"); | |
AddBaseItemResRef(BASE_ITEM_TRIDENT, "nw_wpltr001"); | |
AddBaseItemResRef(BASE_ITEM_TWOBLADEDSWORD, "nw_wdbsw001"); | |
AddBaseItemResRef(BASE_ITEM_WARHAMMER, "nw_wblhw001"); | |
AddBaseItemResRef(BASE_ITEM_WHIP, "x2_it_wpwhip"); | |
// Tier 1 loot describes "basic adventuring gear". | |
// The accelerated timeout on this type of loot is 2 days. | |
SetLootCategoryTimeout(LOOT_TEMPLATE_TIER_1, 1 * 60 * 60 * 24 * 2); // 2 days | |
// Tier 1 loot items will have a 0.25% chance of dropping from mobs. | |
AddLootBucketChance(LOOT_TEMPLATE_TIER_1, INTERNAL_LOOT_BUCKET_MOB, 0.25f); | |
AddLootBucketChance(LOOT_TEMPLATE_TIER_1, INTERNAL_LOOT_BUCKET_MOB, 0.25f); | |
AddLootBucketChance(LOOT_TEMPLATE_TIER_1, INTERNAL_LOOT_BUCKET_MOB, 0.25f); | |
// Tier 1 loot items will have a 15% chance of dropping from bosses. | |
// For the first and second tier 1 drops per timeout, this chance is increased to 25%. | |
AddLootBucketChance(LOOT_TEMPLATE_TIER_1, INTERNAL_LOOT_BUCKET_BOSS, 25.0f); | |
AddLootBucketChance(LOOT_TEMPLATE_TIER_1, INTERNAL_LOOT_BUCKET_BOSS, 25.0f); | |
AddLootBucketChance(LOOT_TEMPLATE_TIER_1, INTERNAL_LOOT_BUCKET_BOSS, 15.0f); | |
// Tier 1 loot items will have a 25% chance of dropping from chests. | |
// For the first and second tier 1 drops per timeout, this chance is increased to 50%. | |
AddLootBucketChance(LOOT_TEMPLATE_TIER_1, INTERNAL_LOOT_BUCKET_CHEST, 50.0f); | |
AddLootBucketChance(LOOT_TEMPLATE_TIER_1, INTERNAL_LOOT_BUCKET_CHEST, 50.0f); | |
AddLootBucketChance(LOOT_TEMPLATE_TIER_1, INTERNAL_LOOT_BUCKET_CHEST, 25.0f); | |
// Tier 2 loot describes "advanced adventuring gear". | |
// The accelerated timeout on this type of loot is 1 week. | |
SetLootCategoryTimeout(LOOT_TEMPLATE_TIER_2, 1 * 60 * 60 * 24 * 7); // 1 week | |
// Tier 2 loot items will have a 0.05% chance of dropping from mobs. | |
AddLootBucketChance(LOOT_TEMPLATE_TIER_2, INTERNAL_LOOT_BUCKET_MOB, 0.05f); | |
AddLootBucketChance(LOOT_TEMPLATE_TIER_2, INTERNAL_LOOT_BUCKET_MOB, 0.05f); | |
// Tier 2 loot items will have a 1.25% chance of dropping from bosses. | |
// For the first tier 2 drop per timeout, this chance is increased to 2.5%. | |
AddLootBucketChance(LOOT_TEMPLATE_TIER_2, INTERNAL_LOOT_BUCKET_BOSS, 2.5f); | |
AddLootBucketChance(LOOT_TEMPLATE_TIER_2, INTERNAL_LOOT_BUCKET_BOSS, 1.25f); | |
// Tier 2 loot items will have a 2.5% chance of dropping from chests. | |
// For the first tier 2 drop per timeout, this chance is increased to 5%. | |
AddLootBucketChance(LOOT_TEMPLATE_TIER_2, INTERNAL_LOOT_BUCKET_CHEST, 5.0f); | |
AddLootBucketChance(LOOT_TEMPLATE_TIER_2, INTERNAL_LOOT_BUCKET_CHEST, 2.5f); | |
} | |
void CreateLoot(int context, object container, object creature) | |
{ | |
if (!GetIsObjectValid(creature) || !GetIsPC(creature)) | |
{ | |
return; | |
} | |
INTERNAL_CreateProceduralLoot(LOOT_TEMPLATE_TIER_1, context, container, creature); | |
INTERNAL_CreateProceduralLoot(LOOT_TEMPLATE_TIER_2, context, container, creature); | |
} | |
void INTERNAL_CreateProceduralLoot(string template, int context, object container, object creature) | |
{ | |
int bestChanceTimestamp = -1; | |
int bestChanceDrops = 0; | |
object bestChanceObject = OBJECT_INVALID; | |
object bestChanceObjectHide = OBJECT_INVALID; | |
object partyMember = GetFirstFactionMember(creature); | |
while (GetIsObjectValid(partyMember)) | |
{ | |
object hide = gsPCGetCreatureHide(partyMember); | |
struct LootDistributionHistory history = GetLootDistributionHistory(hide, template); | |
if (bestChanceObject == OBJECT_INVALID || history.drops < bestChanceDrops || | |
(history.drops == bestChanceDrops && history.timestamp < bestChanceTimestamp)) | |
{ | |
bestChanceTimestamp = history.timestamp; | |
bestChanceDrops = history.drops; | |
bestChanceObject = partyMember; | |
bestChanceObjectHide = hide; | |
} | |
partyMember = GetNextFactionMember(creature); | |
} | |
string bucket = INTERNAL_LootBucketFromContext(context); | |
struct LootDistrbutionResults results = GetLootDistributionResults(bestChanceObjectHide, template, bucket); | |
if (results.createDrop) | |
{ | |
object generatedLoot = OBJECT_INVALID; | |
string resref = GetRandomResRefFromItemType(ITEM_TYPE_GEAR); | |
// To allow for different loot generation per loot difficulty (low, med, high), we actually | |
// have three loot scripts per tier. | |
// We want everything to be shared between the three difficulties *except* for the loot | |
// generation script, so we add the postfix to the template name here. | |
string postfixedTemplate = template + INTERNAL_GetPostfixFromContext(context); | |
if (results.acceleratedDrop) | |
{ | |
generatedLoot = GenerateTailoredLootInContainer(container, bestChanceObject, postfixedTemplate, resref); | |
} | |
else | |
{ | |
generatedLoot = GenerateLootInContainer(container, postfixedTemplate, resref); | |
} | |
if (GetIsObjectValid(generatedLoot)) | |
{ | |
// Now apply the naming scheme. | |
ApplyLootNamingScheme(generatedLoot, bestChanceObject); | |
int itemValue = GetGoldPieceValue(generatedLoot); | |
// Items have a certain percentage chance to be runic, depending on item value. | |
// At 0 value, the percentage is 25%. | |
// At 10000 value or above, the percentage is 2%. | |
float percentageChance = (itemValue < 10000 ? (23.0 / 10000.0) * (10000.0 - itemValue) : 0.0f) + 2.0f; | |
int runic = PercentageRandom(percentageChance); | |
if (runic) | |
{ | |
// Runic items have an equal percentage chance to apply to elf/dwarf/all races. | |
int runicRace; | |
switch (Random(3)) | |
{ | |
case 0: runicRace = RACIAL_TYPE_ELF; break; | |
case 1: runicRace = RACIAL_TYPE_DWARF; break; | |
case 2: runicRace = RACIAL_TYPE_ALL; break; | |
} | |
SetLocalInt(generatedLoot, "RUNIC", 1); | |
SetLocalInt(generatedLoot, "RUNIC_TYPE", runicRace); | |
// Runic items have blue names because those are mystical! Like runic items! | |
SetName(generatedLoot, StringToRGBString(GetName(generatedLoot), "339")); | |
} | |
string area = GetName(GetArea(bestChanceObject)); | |
string propertyCount = IntToString(GetLocalInt(generatedLoot, "GENERATED_LOOT_ITEM_PROPERTIES")); | |
string itemValueAsStr = IntToString(itemValue); | |
// Insert the data we need into the database ... | |
SQLExecStatement("INSERT INTO " + | |
"procedural_loot(gs_pc_data_id, area, template, bucket, tailored, runic, properties, value) " + | |
"VALUES(?, ?, ?, ?, ?, ?, ?, ?)", | |
gsPCGetPlayerID(bestChanceObject), | |
area, | |
postfixedTemplate, | |
bucket, | |
results.acceleratedDrop ? "1" : "0", | |
runic ? "1" : "0", | |
propertyCount, | |
itemValueAsStr); | |
// And log it out! | |
Log("LOOT", "Creating loot " + | |
"(" + postfixedTemplate + " in bucket " + bucket + ") " + (results.acceleratedDrop ? "(tailored) " : "") + | |
"for " + GetName(bestChanceObject) + "'s party " + | |
"in area " + area + " " + | |
"with property count " + propertyCount + " " + | |
"and value " + itemValueAsStr + "."); | |
} | |
} | |
AcceptLootDistributionResults(bestChanceObjectHide, results); | |
} | |
string INTERNAL_LootBucketFromContext(int context) | |
{ | |
switch (context) | |
{ | |
case LOOT_CONTEXT_MOB_LOW: | |
case LOOT_CONTEXT_MOB_MEDIUM: | |
case LOOT_CONTEXT_MOB_HIGH: | |
return INTERNAL_LOOT_BUCKET_MOB; | |
case LOOT_CONTEXT_BOSS_LOW: | |
case LOOT_CONTEXT_BOSS_MEDIUM: | |
case LOOT_CONTEXT_BOSS_HIGH: | |
return INTERNAL_LOOT_BUCKET_BOSS; | |
case LOOT_CONTEXT_CHEST_LOW: | |
case LOOT_CONTEXT_CHEST_MEDIUM: | |
case LOOT_CONTEXT_CHEST_HIGH: | |
return INTERNAL_LOOT_BUCKET_CHEST; | |
} | |
return ""; | |
} | |
string INTERNAL_GetPostfixFromContext(int context) | |
{ | |
switch (context) | |
{ | |
case LOOT_CONTEXT_MOB_LOW: | |
case LOOT_CONTEXT_BOSS_LOW: | |
case LOOT_CONTEXT_CHEST_LOW: | |
return INTERNAL_POSTFIX_LOW; | |
case LOOT_CONTEXT_MOB_MEDIUM: | |
case LOOT_CONTEXT_BOSS_MEDIUM: | |
case LOOT_CONTEXT_CHEST_MEDIUM: | |
return INTERNAL_POSTFIX_MEDIUM; | |
case LOOT_CONTEXT_MOB_HIGH: | |
case LOOT_CONTEXT_BOSS_HIGH: | |
case LOOT_CONTEXT_CHEST_HIGH: | |
return INTERNAL_POSTFIX_HIGH; | |
} | |
return ""; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This file governs loot distribution chance. | |
// | |
// Broadly speaking, loot distribution is set up based on CATEGORIES and BUCKETS. | |
// A category is something overarching, whereas a bucket is something more refined. | |
// One might think of a category as "epicloot" and a bucket as "chest" or "boss". | |
// | |
#include "inc_data_arr" | |
#include "inc_random" | |
#include "inc_time" | |
// ----- PUBLIC API ------ | |
struct LootDistributionHistory | |
{ | |
// The number of drops since timeout. | |
int drops; | |
// The timestamp of the first time this object was operated on. | |
// This is in seconds, server time. | |
int timestamp; | |
}; | |
struct LootDistrbutionResults | |
{ | |
string category; | |
string bucket; | |
// Has the system decided to create a drop? | |
int createDrop; | |
// Is the drop an accelerated drop? (e.g. did it pull from any chance except the last?) | |
int acceleratedDrop; | |
}; | |
// Sets the timeout for the loot category. This timeout is in seconds. | |
// Every timeout period, the drop count will reset. | |
void SetLootCategoryTimeout(string category, int timeout); | |
// Adds a drop chance (percentage, from 0.0f to 100.0f) to the bucket. | |
// If adding more than one drop chance to a bucket, the amount of loot drops | |
// will determine which chance is used. | |
void AddLootBucketChance(string category, string bucket, float chance); | |
// Returns the loot distribution history for the object in the category. | |
struct LootDistributionHistory GetLootDistributionHistory(object obj, string category); | |
// Rolls for loot in the bucket for the object. Returns the results. | |
// Note that the results are NOT accepted until you call AcceptLootDistributionResults. | |
struct LootDistrbutionResults GetLootDistributionResults(object obj, string category, string bucket); | |
// Accepts the loot distribution results. This will handle incrementing the drop count. | |
void AcceptLootDistributionResults(object obj, struct LootDistrbutionResults results); | |
// ------ INTERNAL API ------ | |
string INTERNAL_GetArrayName(string category, string bucket); | |
void INTERNAL_RefreshHistory(object obj, string category); | |
int INTERNAL_GetDrops(object obj, string category); | |
int INTERNAL_GetTimestamp(object obj, string category); | |
void INTERNAL_SetDrops(object obj, string category, int drops); | |
void INTERNAL_SetTimestamp(object obj, string category, int timestamp); | |
// | |
// ----- | |
// | |
void SetLootCategoryTimeout(string category, int timeout) | |
{ | |
SetLocalInt(GetModule(), "LOOTDIST_" + category + "_TIMEOUT", timeout); | |
} | |
void AddLootBucketChance(string category, string bucket, float chance) | |
{ | |
FloatArray_PushBack(GetModule(), INTERNAL_GetArrayName(category, bucket), chance > 100.0f ? 100.0f : chance); | |
} | |
struct LootDistributionHistory GetLootDistributionHistory(object obj, string category) | |
{ | |
INTERNAL_RefreshHistory(obj, category); | |
struct LootDistributionHistory history; | |
history.drops = INTERNAL_GetDrops(obj, category); | |
history.timestamp = INTERNAL_GetTimestamp(obj, category); | |
return history; | |
} | |
struct LootDistrbutionResults GetLootDistributionResults(object obj, string category, string bucket) | |
{ | |
INTERNAL_RefreshHistory(obj, category); | |
struct LootDistrbutionResults results; | |
results.createDrop = FALSE; | |
object arrayObj = GetModule(); | |
string arrayName = INTERNAL_GetArrayName(category, bucket); | |
int ruleCount = FloatArray_Size(arrayObj, arrayName); | |
if (ruleCount > 0) | |
{ | |
int dropCount = INTERNAL_GetDrops(obj, category); | |
int ruleIndex = ruleCount > dropCount ? dropCount : ruleCount - 1; | |
float rule = FloatArray_At(arrayObj, arrayName, ruleIndex); | |
if (PercentageRandom(rule)) | |
{ | |
results.category = category; | |
results.bucket = bucket; | |
results.createDrop = TRUE; | |
results.acceleratedDrop = dropCount + 1 < ruleCount; | |
} | |
} | |
return results; | |
} | |
void AcceptLootDistributionResults(object obj, struct LootDistrbutionResults results) | |
{ | |
if (results.createDrop) | |
{ | |
int drops = INTERNAL_GetDrops(obj, results.category); | |
INTERNAL_SetDrops(obj, results.category, drops + 1); | |
} | |
} | |
string INTERNAL_GetArrayName(string category, string bucket) | |
{ | |
return "LOOSTDIST_" + category + "_" + bucket; | |
} | |
void INTERNAL_RefreshHistory(object obj, string category) | |
{ | |
int categoryTimeout = GetLocalInt(GetModule(), "LOOTDIST_" + category + "_TIMEOUT"); | |
int objectTime = INTERNAL_GetTimestamp(obj, category); | |
int actualTime = GetModuleTime(); | |
if (abs(actualTime - objectTime) >= categoryTimeout) | |
{ | |
INTERNAL_SetTimestamp(obj, category, actualTime); | |
INTERNAL_SetDrops(obj, category, 0); | |
} | |
} | |
int INTERNAL_GetDrops(object obj, string category) | |
{ | |
return GetLocalInt(obj, "LOOTDIST_" + category + "_DROPS"); | |
} | |
int INTERNAL_GetTimestamp(object obj, string category) | |
{ | |
return GetLocalInt(obj, "LOOTDIST_" + category + "_TIMEOUT"); | |
} | |
void INTERNAL_SetDrops(object obj, string category, int drops) | |
{ | |
SetLocalInt(obj, "LOOTDIST_" + category + "_DROPS", drops); | |
} | |
void INTERNAL_SetTimestamp(object obj, string category, int timestamp) | |
{ | |
SetLocalInt(obj, "LOOTDIST_" + category + "_TIMEOUT", timestamp); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This file governs loot generation. | |
// To use this, you need to do a few things ... | |
// | |
// First of all, each the loot system works based on "loot scripts". | |
// | |
// Each loot script "tag" needs a number of corresponding scripts: | |
// | |
// [mandatory] [called once] | |
// - lgen_tag_1: The first phase of loot generation is simple. The lgen script must call two functions: | |
// | |
// 1. LGEN_SetMinPropertyCount(int min) | |
// 1. LGEN_SetMaxPropertyCount(int max) | |
// | |
// [optional] [called once per property] | |
// - lgen_tag_2: The second phase of loot generation is OPTIONAL. | |
// If the lgen script desires, it can set up a subset of properties that can be chosen by | |
// using the API exposed in inc_data_arr, as such ... | |
// | |
// 1. IntArray_PushBack(OBJECT_SELF, LGEN_PROPERTY_ARRAY_TAG, 1) | |
// | |
// If a set is not provided, the loot generation system will use all available properties. | |
// | |
// [mandatory] [called once per property] | |
// - lgen_tag_3: The third phase of loot generation requires the lgen script to do two things. | |
// | |
// First, the loot lgen script should call LGEN_GetChosenProperty(). | |
// This will return the PROPERTY_* constant which the loot generation system has chosen based | |
// on the provided random set from the previous phase, or containing all properties if none. | |
// | |
// Second, the lgen script should call four functions -- | |
// | |
// 1. LGEN_SetMinProperty1Value(int min) | |
// 2. LGEN_SetMaxProperty1Value(int max) | |
// 3. LGEN_SetMinProperty2Value(int min) | |
// 4. LGEN_SetMaxProperty2Value(int max) | |
// | |
// These functions will set the minimum and maximum extents for mod1 and mod2. | |
// See the below comments which outline mod1 and mo2 for each property. | |
// | |
// TODO: Set property cost in terms of 'slots' (see phase 1), so we can make one property occupy | |
// more than one slot. | |
// | |
// ALTERNATIVELY, the lgen script may instead set up a random set using the functionality | |
// exposed by inc_data_arr (as in stage 2) for one or both of the mods, as such ... | |
// | |
// 1. IntArray_PushBack(OBJECT_SELF, LGEN_MOD1_ARRAY_TAG, 1) | |
// 2. IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1) | |
// | |
// If one or more values are present in the sets named above, the min and max property values | |
// will be ignored for the respective category. | |
// | |
// Note that this script may be called more than the property count times. This is because | |
// the loot generation algorithm may fail during the population of mod1 and mod2, e.g. due | |
// to the useless property protection. | |
// | |
// [optional] [called once] | |
// - lgen_tag_4: This is an optional script which is called once lot generation has finished but before the | |
// object has been cleaned up -- e.g., all variables are still available for inspection. | |
// It can be used to add any additional properties that need more fine-grained control outside | |
// of the generic loot generation system. | |
// | |
// [optional] [called once] | |
// - lgen_tag_5: This is an optional script which is called once loot generation has finished completely. | |
// All variables have been purged, and the generic name and description have been applied to the item. | |
// This script can be used to name and describe items. | |
// | |
// The mod-map is as follows: | |
// | |
// [id: PROPERTY_AC_BONUS] [mod1: bonus] | |
// [id: PROPERTY_AC_BONUS_VS_RACE] [mod1: race] [mod2: bonus] | |
// [id: PROPERTY_SAVE_BONUS] [mod1: bonus] | |
// [id: PROPERTY_SPECIFIC_SAVE_BONUS] [mod1: type] [mod2: bonus] | |
// [id: PROPERTY_ELEMENTAL_DR] [mod1: type] [mod2: bonus] | |
// [id: PROPERTY_ABILITY_BONUS] [mod1: ability] [mod2: bonus] | |
// [id: PROPERTY_SKILL_BONUS] [mod1: skill] [mod2: bonus] | |
// [id: PROPERTY_USAGE_RESTRICTION_CLASS] [mod1: class] | |
// [id: PROPERTY_USAGE_RESTRICTION_RACE] [mod1: race] | |
// [id: PROPERTY_WEAPON_ENH_BONUS] [mod1: bonus] | |
// [id: PROPERTY_WEAPON_AB_BONUS] [mod1: bonus] | |
// [id: PROPERTY_WEAPON_ENH_BONUS_VS_RACE] [mod1: race] [mod2: bonus] | |
// [id: PROPERTY_WEAPON_ELEMENTAL_DAMAGE] [mod1: element] [mod2: damage] | |
// [id: PROPERTY_WEAPON_MASSIVE_CRITICALS] [mod1: damage] | |
// [id: PROPERTY_WEAPON_VISUAL] [mod1: effect] | |
// [id: PROPERTY_WEAPON_FREEDOM] | |
// [id: PROPERTY_WEAPON_FEAR] | |
// [id: PROPERTY_WEAPON_LIGHT] [mod1: brightness] [mod2: colour] | |
// [id: PROPERTY_WEAPON_AMMO_OR_VAMP_REGEN] [mod1: ammo type/vamp regen] | |
// | |
#include "inc_data_arr" | |
#include "inc_random" | |
// ----- PUBLIC API ------ | |
const int PROPERTY_INVALID = 0; | |
const int PROPERTY_AC_BONUS = 1; | |
const int PROPERTY_AC_BONUS_VS_RACE = 2; | |
const int PROPERTY_SAVE_BONUS = 3; | |
const int PROPERTY_SPECIFIC_SAVE_BONUS = 4; | |
const int PROPERTY_ELEMENTAL_DR = 5; | |
const int PROPERTY_ABILITY_BONUS = 6; | |
const int PROPERTY_SKILL_BONUS = 7; | |
const int PROPERTY_USAGE_RESTRICTION_CLASS = 8; | |
const int PROPERTY_USAGE_RESTRICTION_RACE = 9; | |
const int PROPERTY_WEAPON_ENH_BONUS = 10; | |
const int PROPERTY_WEAPON_AB_BONUS = 11; | |
const int PROPERTY_WEAPON_ENH_BONUS_VS_RACE = 12; | |
const int PROPERTY_WEAPON_ELEMENTAL_DAMAGE = 13; | |
const int PROPERTY_WEAPON_MASSIVE_CRITICALS = 14; | |
const int PROPERTY_WEAPON_VISUAL = 15; | |
const int PROPERTY_WEAPON_FREEDOM = 16; | |
const int PROPERTY_WEAPON_FEAR = 17; | |
const int PROPERTY_WEAPON_LIGHT = 18; | |
const int PROPERTY_WEAPON_AMMO_OR_VAMP_REGEN = 19; | |
const int PROPERTY_COUNT = PROPERTY_USAGE_RESTRICTION_RACE; | |
const int PROPERTY_WEAPON_COUNT = PROPERTY_WEAPON_AMMO_OR_VAMP_REGEN; | |
const int ITEM_TYPE_GEAR = 0; | |
const int ITEM_TYPE_ARMOUR = 1; | |
const int ITEM_TYPE_WEAPON = 2; | |
// Generates a random item of the provided template inside the provided container. | |
// Type should be one of the ITEM_TYPE_* constants. | |
object GenerateLootInContainer(object container, string template, string resref); | |
// Generates a random item of the provided template, and tailors it to the provided creature. | |
// The process of tailoring ensures that the item will always be useful to the creature in question. | |
// Type should be one of the ITEM_TYPE_* constants. | |
object GenerateTailoredLootInContainer(object container, object creature, string template, string resref); | |
// ------ LGEN SCRIPT API ------ | |
// The LGEN_PropertyStruct is used to pass information about properties between functions. | |
struct LGEN_PropertyStruct | |
{ | |
// The id corresponds to one of the PROPERY_* constants, depending on gear type. | |
int id; | |
// The modifiers are used to store each property value. | |
// The first modifier is usually the type of property, e.g. cold resistance. | |
int mod1; | |
// The second modifier is usually the amount of the property, e.g. +3. | |
int mod2; | |
}; | |
const string LGEN_PROPERTY_ARRAY_TAG = "LGEN_PROPS"; | |
const string LGEN_MOD1_ARRAY_TAG = "LGEN_MOD1"; | |
const string LGEN_MOD2_ARRAY_TAG = "LGEN_MOD2"; | |
// lgen scripts may call this to find the item type that is currenty being generated. | |
int LGEN_GetGenType(); | |
// lgen scripts should call these functions during the first phase to select the min and max property count. | |
void LGEN_SetMinPropertyCount(int min); | |
void LGEN_SetMaxPropertyCount(int max); | |
// lgen scripts should call this function during the third phase to fetch the chosen property. | |
int LGEN_GetChosenProperty(); | |
// lgen scripts should call these functions on the third phase to select the extent of the properties. | |
void LGEN_SetMinProperty1Value(int min); | |
void LGEN_SetMaxProperty1Value(int max); | |
void LGEN_SetMinProperty2Value(int min); | |
void LGEN_SetMaxProperty2Value(int max); | |
// lgen scripts may use these functions to query properties on the item and even manually overwrite | |
// properties during later phases of loot generation if it wishes to. | |
struct LGEN_PropertyStruct LGEN_GetProperty(int index); | |
void LGEN_SetProperty(int index, struct LGEN_PropertyStruct property); | |
struct LGEN_PropertyStruct LGEN_FindProperty(int propertyId, int nth = 0); | |
int LGEN_GetPropertyCount(int propertyId); | |
// ------ INTERNAL LGEN API ------ | |
// Various opposites/complements of the above LGEN public API functions for use within the loot generation library. | |
int INTERNAL_LGEN_GetGenType(object item); | |
void INTERNAL_LGEN_SetGenType(object item, int type); | |
int INTERNAL_LGEN_GetMinPropertyCount(object item); | |
int INTERNAL_LGEN_GetMaxPropertyCount(object item); | |
void INTERNAL_LGEN_SetChosenProperty(object item, int property); | |
int INTERNAL_LGEN_GetMinProperty1Value(object item); | |
int INTERNAL_LGEN_GetMaxProperty1Value(object item); | |
int INTERNAL_LGEN_GetMinProperty2Value(object item); | |
int INTERNAL_LGEN_GetMaxProperty2Value(object item); | |
struct LGEN_PropertyStruct INTERNAL_LGEN_GetProperty(object item, int index); | |
void INTERNAL_LGEN_SetProperty(object item, int index, struct LGEN_PropertyStruct property); | |
struct LGEN_PropertyStruct INTERNAL_LGEN_FindProperty(object item, int propertyId, int nth = 0); | |
int INTERNAL_LGEN_GetPropertyCount(object item, int propertyId); | |
// ------ INTERNAL API ------ | |
// The ProperyVarNameStruct is used to pass the string names for each index so that | |
// they can be accesses in a consistent way using Get/SetLocalVariable. | |
struct INTERNAL_PropertyVarNameStruct | |
{ | |
string id; | |
string mod1; | |
string mod2; | |
}; | |
// This value will allow this system and other systems to modify items in the event | |
// of a breaking change. For example, converting items from version 1 to version 2. | |
const int INTERNAL_LOOTGEN_VERSION = 1; | |
// This is a hard-coded limit to keep things sane. | |
const int INTERNAL_MAX_PROPERTY_COUNT = 8; | |
const int INTERNAL_LOOT_GEN_PHASE_1 = 1; | |
const int INTERNAL_LOOT_GEN_PHASE_2 = 2; | |
const int INTERNAL_LOOT_GEN_PHASE_3 = 3; | |
const int INTERNAL_LOOT_GEN_PHASE_4 = 4; | |
const int INTERNAL_LOOT_GEN_PHASE_5 = 5; | |
// Does exactly what it says on the tin. | |
object INTERNAL_GenerateLootInContainer(object container, object creature, string template, string resref); | |
// "Executes a phase", e.g. calls the correct script for the provided phase. | |
void INTERNAL_ExecutePhase(object item, int phase, string template); | |
// Generates and applies a series of random properties to an item. | |
// The properties generated are determined by the lgen script for the provided template. | |
// If the provided creature is not OBJECT_INVALID, the properties will be tailored to the creature. | |
// Returns the count of properties applied. | |
int INTERNAL_GenerateAndApplyProperties(object item, object creature, int type, string template); | |
// Generates a unique property and returns the struct describing it. | |
// Doesn't apply it! INTERNAL_PopulateItemWithProperty needs to apply it! | |
struct LGEN_PropertyStruct INTERNAL_GenerateUniqueProperty(object item, object creature, string template); | |
// Returns the tailored mod1/mod2 value of the property provided. | |
// If creature is OBJECT_INVALID, doesn't tailor. | |
int INTERNAL_TailorMod1(object item, object creature, int property); | |
int INTERNAL_TailorMod2(object item, object creature, int property); | |
// Creates the Mod1 array of tailored items. | |
int INTERNAL_TailorMod1Array(object item, object creature, string tag, int property); | |
// Finalises an item. This handles tracking, logging, cleaning up variables, etc. | |
void INTERNAL_FinaliseGeneratedItem(object item, object container, object creature, int propertyCount, string template); | |
// Applies a generated property to an item. | |
void INTERNAL_PopulateItemWithProperty(object item, struct LGEN_PropertyStruct property); | |
// Returns whether the property is valid. This checks for duplicates etc. | |
int INTERNAL_IsPropertyValid(object item, struct LGEN_PropertyStruct property); | |
// Returns a structure which contains the string names for each variable field. | |
// This is a convenience function. | |
struct INTERNAL_PropertyVarNameStruct INTERNAL_GetPropertyVarNames(int index); | |
// Returns the ITEM_TYPE_* constant of the provided item. | |
int INTERNAL_GetItemTypeFromItem(object item); | |
// If the array has any elements in it, select from it. | |
// Else, return a random number in the provided range. | |
int INTERNAL_RandomFromArrayOrRange(object item, string array, int min, int max); | |
// | |
// ----- | |
// | |
object GenerateLootInContainer(object container, string template, string resref) | |
{ | |
return GenerateTailoredLootInContainer(container, OBJECT_INVALID, template, resref); | |
} | |
object GenerateTailoredLootInContainer(object container, object creature, string template, string resref) | |
{ | |
return INTERNAL_GenerateLootInContainer(container, creature, template, resref); | |
} | |
int LGEN_GetGenType() | |
{ | |
return INTERNAL_LGEN_GetGenType(OBJECT_SELF); | |
} | |
void LGEN_SetMinPropertyCount(int min) | |
{ | |
SetLocalInt(OBJECT_SELF, "LGEN_MIN_PROP_COUNT", min); | |
} | |
void LGEN_SetMaxPropertyCount(int max) | |
{ | |
SetLocalInt(OBJECT_SELF, "LGEN_MAX_PROP_COUNT", max); | |
} | |
int LGEN_GetChosenProperty() | |
{ | |
return GetLocalInt(OBJECT_SELF, "LGEN_PROPERTY"); | |
} | |
void LGEN_SetMinProperty1Value(int min) | |
{ | |
SetLocalInt(OBJECT_SELF, "LGEN_MIN_PROP1", min); | |
} | |
void LGEN_SetMaxProperty1Value(int max) | |
{ | |
SetLocalInt(OBJECT_SELF, "LGEN_MAX_PROP1", max); | |
} | |
void LGEN_SetMinProperty2Value(int min) | |
{ | |
SetLocalInt(OBJECT_SELF, "LGEN_MIN_PROP2", min); | |
} | |
void LGEN_SetMaxProperty2Value(int max) | |
{ | |
SetLocalInt(OBJECT_SELF, "LGEN_MAX_PROP2", max); | |
} | |
struct LGEN_PropertyStruct LGEN_GetProperty(int index) | |
{ | |
return INTERNAL_LGEN_GetProperty(OBJECT_SELF, index); | |
} | |
void LGEN_SetProperty(int index, struct LGEN_PropertyStruct property) | |
{ | |
INTERNAL_LGEN_SetProperty(OBJECT_SELF, index, property); | |
} | |
struct LGEN_PropertyStruct LGEN_FindProperty(int propertyId, int nth) | |
{ | |
return INTERNAL_LGEN_FindProperty(OBJECT_SELF, propertyId, nth); | |
} | |
int LGEN_GetPropertyCount(int propertyId) | |
{ | |
return INTERNAL_LGEN_GetPropertyCount(OBJECT_SELF, propertyId); | |
} | |
int INTERNAL_LGEN_GetGenType(object item) | |
{ | |
return GetLocalInt(item, "LGEN_TYPE"); | |
} | |
void INTERNAL_LGEN_SetGenType(object item, int type) | |
{ | |
SetLocalInt(item, "LGEN_TYPE", type); | |
} | |
int INTERNAL_LGEN_GetMinPropertyCount(object item) | |
{ | |
return GetLocalInt(item, "LGEN_MIN_PROP_COUNT"); | |
} | |
int INTERNAL_LGEN_GetMaxPropertyCount(object item) | |
{ | |
return GetLocalInt(item, "LGEN_MAX_PROP_COUNT"); | |
} | |
void INTERNAL_LGEN_SetChosenProperty(object item, int property) | |
{ | |
SetLocalInt(item, "LGEN_PROPERTY", property); | |
} | |
int INTERNAL_LGEN_GetMinProperty1Value(object item) | |
{ | |
return GetLocalInt(item, "LGEN_MIN_PROP1"); | |
} | |
int INTERNAL_LGEN_GetMaxProperty1Value(object item) | |
{ | |
return GetLocalInt(item, "LGEN_MAX_PROP1"); | |
} | |
int INTERNAL_LGEN_GetMinProperty2Value(object item) | |
{ | |
return GetLocalInt(item, "LGEN_MIN_PROP2"); | |
} | |
int INTERNAL_LGEN_GetMaxProperty2Value(object item) | |
{ | |
return GetLocalInt(item, "LGEN_MAX_PROP2"); | |
} | |
struct LGEN_PropertyStruct INTERNAL_LGEN_GetProperty(object item, int index) | |
{ | |
struct INTERNAL_PropertyVarNameStruct varNames = INTERNAL_GetPropertyVarNames(index); | |
struct LGEN_PropertyStruct property; | |
property.id = GetLocalInt(item, varNames.id); | |
property.mod1 = GetLocalInt(item, varNames.mod1); | |
property.mod2 = GetLocalInt(item, varNames.mod2); | |
return property; | |
} | |
void INTERNAL_LGEN_SetProperty(object item, int index, struct LGEN_PropertyStruct property) | |
{ | |
if (index >= INTERNAL_MAX_PROPERTY_COUNT) | |
{ | |
return; | |
} | |
struct INTERNAL_PropertyVarNameStruct varNames = INTERNAL_GetPropertyVarNames(index); | |
SetLocalInt(item, varNames.id, property.id); | |
SetLocalInt(item, varNames.mod1, property.mod1); | |
SetLocalInt(item, varNames.mod2, property.mod2); | |
} | |
struct LGEN_PropertyStruct INTERNAL_LGEN_FindProperty(object item, int propertyId, int nth) | |
{ | |
int hit = 0; | |
struct LGEN_PropertyStruct property; | |
int i; | |
for (i = 0; i < INTERNAL_MAX_PROPERTY_COUNT; ++i) | |
{ | |
property = INTERNAL_LGEN_GetProperty(item, i); | |
if (property.id == PROPERTY_INVALID) | |
{ | |
// No more properties to search ... | |
break; | |
} | |
if (property.id == propertyId) | |
{ | |
if (hit == nth) | |
{ | |
return property; | |
} | |
else | |
{ | |
++hit; | |
} | |
} | |
} | |
property.id = PROPERTY_INVALID; | |
return property; | |
} | |
int INTERNAL_LGEN_GetPropertyCount(object item, int propertyId) | |
{ | |
int count = 0; | |
int i; | |
for (i = 0; i < INTERNAL_MAX_PROPERTY_COUNT; ++i) | |
{ | |
struct LGEN_PropertyStruct property = INTERNAL_LGEN_GetProperty(item, i); | |
if (property.id == PROPERTY_INVALID) | |
{ | |
// No more properties to search ... | |
break; | |
} | |
if (property.id == propertyId) | |
{ | |
++count; | |
} | |
} | |
return count; | |
} | |
object INTERNAL_GenerateLootInContainer(object container, object creature, string template, string resref) | |
{ | |
object item = CreateItemOnObject(resref, container); | |
if (GetIsObjectValid(item)) | |
{ | |
SetName(item, template + " Generated Loot Item"); | |
SetDescription(item, "If you see this name or description, please report this bug to the development team."); | |
int itemType = INTERNAL_GetItemTypeFromItem(item); | |
int propertyCount = INTERNAL_GenerateAndApplyProperties(item, creature, itemType, template); | |
INTERNAL_FinaliseGeneratedItem(item, container, creature, propertyCount, template); | |
INTERNAL_ExecutePhase(item, INTERNAL_LOOT_GEN_PHASE_5, template); | |
} | |
return item; | |
} | |
void INTERNAL_ExecutePhase(object item, int phase, string template) | |
{ | |
ExecuteScript("lgen_" + GetStringLowerCase(template) + "_" + IntToString(phase), item); | |
} | |
int INTERNAL_GenerateAndApplyProperties(object item, object creature, int type, string template) | |
{ | |
INTERNAL_LGEN_SetGenType(item, type); | |
INTERNAL_ExecutePhase(item, INTERNAL_LOOT_GEN_PHASE_1, template); | |
int minPropertyCount = INTERNAL_LGEN_GetMinPropertyCount(item); | |
int maxPropertyCount = INTERNAL_LGEN_GetMaxPropertyCount(item); | |
int desiredPropertyCount = ClampedRandom(minPropertyCount, maxPropertyCount); | |
int i; | |
for (i = 0; i < desiredPropertyCount; ++i) | |
{ | |
// Generate the properties, but don't apply them yet. | |
struct LGEN_PropertyStruct property = INTERNAL_GenerateUniqueProperty(item, creature, template); | |
if (property.id == PROPERTY_INVALID) | |
{ | |
// We couldn't generate a unique property. | |
// This could be because we've run out of skills, and the stacking code does not allow duplicates. | |
// In this case, we just leave the loop here -- the properties will be recounted lower down anyway. | |
break; | |
} | |
INTERNAL_LGEN_SetProperty(item, i, property); | |
} | |
INTERNAL_ExecutePhase(item, INTERNAL_LOOT_GEN_PHASE_4, template); | |
// Now we count the valid properties again ... we do this because the phase 4 generation could have | |
// added properties that we did not generate ourselves, and we want to report them correctly. | |
for (i = 0; i < INTERNAL_MAX_PROPERTY_COUNT; ++i) | |
{ | |
// Apply all of the properties. | |
struct LGEN_PropertyStruct property = INTERNAL_LGEN_GetProperty(item, i); | |
if (property.id == PROPERTY_INVALID) | |
{ | |
// We've counted all the properties. | |
break; | |
} | |
// Actually apply the property to the item. | |
INTERNAL_PopulateItemWithProperty(item, property); | |
} | |
return i; | |
} | |
struct LGEN_PropertyStruct INTERNAL_GenerateUniqueProperty(object item, object creature, string template) | |
{ | |
INTERNAL_ExecutePhase(item, INTERNAL_LOOT_GEN_PHASE_2, template); | |
struct LGEN_PropertyStruct property; | |
// We have a maximum cap of 10 attempts when attempting to generate a new property. | |
// This cap could be hit by faulty lgen logic (not enough properties to fill slots), | |
// or by a character so weird that it confuses the loot tailoring algorithm. | |
int attempts; | |
for (attempts = 0; attempts < 10; ++attempts) | |
{ | |
int isWeapon = INTERNAL_LGEN_GetGenType(item) == ITEM_TYPE_WEAPON; | |
int potentialProperty = INTERNAL_RandomFromArrayOrRange(item, | |
LGEN_PROPERTY_ARRAY_TAG, | |
(isWeapon ? PROPERTY_COUNT : 0) + 1, | |
(isWeapon ? PROPERTY_WEAPON_COUNT : PROPERTY_COUNT)); | |
INTERNAL_LGEN_SetChosenProperty(item, potentialProperty); | |
INTERNAL_ExecutePhase(item, INTERNAL_LOOT_GEN_PHASE_3, template); | |
property.id = potentialProperty; | |
property.mod1 = INTERNAL_TailorMod1(item, creature, property.id); | |
property.mod2 = INTERNAL_TailorMod2(item, creature, property.id); | |
IntArray_Clear(item, LGEN_MOD1_ARRAY_TAG); | |
IntArray_Clear(item, LGEN_MOD2_ARRAY_TAG); | |
if (INTERNAL_IsPropertyValid(item, property)) | |
{ | |
// We've got a good property. We can bail out of this loop now. | |
break; | |
} | |
else | |
{ | |
// We failed to generate a valid property, so let's update the ID to reflect that. | |
property.id = PROPERTY_INVALID; | |
} | |
} | |
IntArray_Clear(item, LGEN_PROPERTY_ARRAY_TAG); | |
return property; | |
} | |
int INTERNAL_TailorMod1(object item, object creature, int property) | |
{ | |
string arrayTag = "LGEN_MOD1_TAILORING"; | |
if (INTERNAL_TailorMod1Array(item, creature, arrayTag, property)) | |
{ | |
int subsetSize = IntArray_Size(item, LGEN_MOD1_ARRAY_TAG); | |
if (subsetSize) | |
{ | |
// We already have a subset. In this case ... we need to strip out anything that doesn't fit our tailoring rules. | |
int i; | |
for (i = subsetSize - 1; i >= 0; --i) | |
{ | |
int element = IntArray_At(item, LGEN_MOD1_ARRAY_TAG, i); | |
if (!IntArray_Contains(item, arrayTag, element)) | |
{ | |
// We have a quick check here. Don't tailor away heal or discipline. | |
if (property == PROPERTY_SKILL_BONUS && (element == SKILL_HEAL || element == SKILL_DISCIPLINE)) | |
{ | |
// TODO: Move tailoring to a separate script. This isn't generic, it's Arelith-specific. | |
continue; | |
} | |
// Our array of allowable values doesn't include this value. We need to remove it. | |
IntArray_Erase(item, LGEN_MOD1_ARRAY_TAG, i); | |
} | |
} | |
} | |
else | |
{ | |
// No subset, so let's just copy the tailoring array to the mod1 array. | |
IntArray_Copy(item, LGEN_MOD1_ARRAY_TAG, arrayTag); | |
} | |
IntArray_Clear(item, arrayTag); | |
} | |
// At this point, we either have a fully tailored subset, or a partial subset which has been tailored, or no subset at | |
// all, in which case we use the ranged generation logic instead. | |
return INTERNAL_RandomFromArrayOrRange(item, | |
LGEN_MOD1_ARRAY_TAG, | |
INTERNAL_LGEN_GetMinProperty1Value(item), | |
INTERNAL_LGEN_GetMaxProperty1Value(item)); | |
} | |
int INTERNAL_TailorMod2(object item, object creature, int property) | |
{ | |
// We don't need to do anything special yet for mod 2. | |
return INTERNAL_RandomFromArrayOrRange(item, | |
LGEN_MOD2_ARRAY_TAG, | |
INTERNAL_LGEN_GetMinProperty2Value(item), | |
INTERNAL_LGEN_GetMaxProperty2Value(item)); | |
} | |
int INTERNAL_TailorMod1Array(object item, object creature, string tag, int property) | |
{ | |
if (!GetIsObjectValid(creature)) | |
{ | |
// The creature isn't valid, so we couldn't possibly tailor to match it. | |
return 0; | |
} | |
if (property == PROPERTY_ABILITY_BONUS) | |
{ | |
// We want to allow a draw of three stats here. Take the highest two stats + constitution. | |
// These four variables track them! | |
int highestStatType = 0; | |
int highestStatValue = 0; | |
int secondHighestStatType = 0; | |
int secondHighestStatValue = 0; | |
// A lot of builds might want to pump INT -- but won't want it on their items unless they | |
// could actually use it. So we do a quick sanity check later on using this. | |
int hasIntUsingClass = GetLevelByClass(CLASS_TYPE_WIZARD, creature) || GetLevelByClass(CLASS_TYPE_ASSASSIN, creature); | |
int i; | |
for (i = 0; i < 6; ++i) | |
{ | |
if (i == ABILITY_CONSTITUTION) | |
{ | |
// We will always include CON; skip it! | |
continue; | |
} | |
if (i == ABILITY_INTELLIGENCE && !hasIntUsingClass) | |
{ | |
// No wizard or assassin ... so we probably don't want int loot. | |
continue; | |
} | |
int statType = i; | |
int statValue = GetAbilityScore(creature, i, TRUE); | |
if (statValue > highestStatValue) | |
{ | |
secondHighestStatType = highestStatType; | |
secondHighestStatValue = highestStatValue; | |
highestStatType = statType; | |
highestStatValue = statValue; | |
} | |
else if (statValue > secondHighestStatValue) | |
{ | |
secondHighestStatType = statType; | |
secondHighestStatValue = statValue; | |
} | |
} | |
IntArray_PushBack(item, tag, highestStatType); | |
IntArray_PushBack(item, tag, secondHighestStatType); | |
IntArray_PushBack(item, tag, ABILITY_CONSTITUTION); | |
} | |
else if (property == PROPERTY_SKILL_BONUS) | |
{ | |
// We want to allow generation of items for any skills that the character has invested in. | |
int i; | |
for (i = 0; i < 28; ++i) | |
{ | |
int ranks = GetSkillRank(i, creature, TRUE); | |
if (ranks) | |
{ | |
IntArray_PushBack(item, tag, i); | |
} | |
} | |
// Also, add discipline and heal, because those are universally useful. | |
if (!IntArray_Contains(item, tag, SKILL_DISCIPLINE)) | |
{ | |
IntArray_PushBack(item, tag, SKILL_DISCIPLINE); | |
} | |
if (!IntArray_Contains(item, tag, SKILL_HEAL)) | |
{ | |
IntArray_PushBack(item, tag, SKILL_HEAL); | |
} | |
} | |
return IntArray_Size(item, tag); | |
} | |
void INTERNAL_PopulateItemWithProperty(object item, struct LGEN_PropertyStruct property) | |
{ | |
switch (property.id) | |
{ | |
case PROPERTY_AC_BONUS: | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyACBonus(property.mod1), item); | |
break; | |
case PROPERTY_AC_BONUS_VS_RACE: | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyACBonusVsRace(property.mod1, property.mod2), item); | |
break; | |
case PROPERTY_SAVE_BONUS: | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyBonusSavingThrowVsX(property.mod1, property.mod2), item); | |
break; | |
case PROPERTY_SPECIFIC_SAVE_BONUS: | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyBonusSavingThrow(property.mod1, property.mod2), item); | |
break; | |
case PROPERTY_ELEMENTAL_DR: | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyDamageResistance(property.mod1, property.mod2), item); | |
break; | |
case PROPERTY_ABILITY_BONUS: | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyAbilityBonus(property.mod1, property.mod2), item); | |
break; | |
case PROPERTY_SKILL_BONUS: | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertySkillBonus(property.mod1, property.mod2), item); | |
break; | |
case PROPERTY_USAGE_RESTRICTION_CLASS: | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyLimitUseByClass(property.mod1), item); | |
break; | |
case PROPERTY_USAGE_RESTRICTION_RACE: | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyLimitUseByRace(property.mod1), item); | |
break; | |
case PROPERTY_WEAPON_ENH_BONUS: | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyEnhancementBonus(property.mod1), item); | |
break; | |
case PROPERTY_WEAPON_AB_BONUS: | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyAttackBonus(property.mod1), item); | |
break; | |
case PROPERTY_WEAPON_ENH_BONUS_VS_RACE: | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyEnhancementBonusVsRace(property.mod1, property.mod2), item); | |
break; | |
case PROPERTY_WEAPON_ELEMENTAL_DAMAGE: | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyDamageBonus(property.mod1, property.mod2), item); | |
break; | |
case PROPERTY_WEAPON_MASSIVE_CRITICALS: | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyMassiveCritical(property.mod1), item); | |
break; | |
case PROPERTY_WEAPON_VISUAL: | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyVisualEffect(property.mod1), item); | |
break; | |
case PROPERTY_WEAPON_FREEDOM: | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyFreeAction(), item); | |
break; | |
case PROPERTY_WEAPON_FEAR: | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyImmunityMisc(IP_CONST_IMMUNITYMISC_FEAR), item); | |
break; | |
case PROPERTY_WEAPON_LIGHT: | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyLight(property.mod1, property.mod2), item); | |
break; | |
case PROPERTY_WEAPON_AMMO_OR_VAMP_REGEN: | |
{ | |
int itemType = GetBaseItemType(item); | |
// Only count ranged weapons that consume separate ammunition as ranged. | |
int ranged = itemType == BASE_ITEM_HEAVYCROSSBOW || | |
itemType == BASE_ITEM_LIGHTCROSSBOW || | |
itemType == BASE_ITEM_LONGBOW || | |
itemType == BASE_ITEM_SHORTBOW || | |
itemType == BASE_ITEM_SLING; | |
if (ranged) | |
{ | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyUnlimitedAmmo(property.mod1), item); | |
} | |
else | |
{ | |
AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyVampiricRegeneration(property.mod1), item); | |
} | |
break; | |
} | |
default: break; | |
} | |
} | |
void INTERNAL_FinaliseGeneratedItem(object item, object container, object creature, int propertyCount, string template) | |
{ | |
// Add tracking variables to the item. | |
SetLocalInt(item, "GENERATED_LOOT_ITEM", 1); | |
SetLocalInt(item, "GENERATED_LOOT_ITEM_PROPERTIES", propertyCount); | |
SetLocalInt(item, "GENERATED_LOOT_ITEM_VERSION", INTERNAL_LOOTGEN_VERSION); | |
SetLocalString(item, "GENERATED_LOOT_ITEM_TEMPLATE", template); | |
// Clean up the generation variables. | |
int i; | |
for (i = 0; i < INTERNAL_MAX_PROPERTY_COUNT; ++i) | |
{ | |
struct INTERNAL_PropertyVarNameStruct varNames = INTERNAL_GetPropertyVarNames(i); | |
DeleteLocalInt(item, varNames.id); | |
DeleteLocalInt(item, varNames.mod1); | |
DeleteLocalInt(item, varNames.mod2); | |
} | |
DeleteLocalInt(item, "LGEN_TYPE"); | |
DeleteLocalInt(item, "LGEN_BASE_ITEM_TYPE"); | |
DeleteLocalInt(item, "LGEN_MIN_PROP_COUNT"); | |
DeleteLocalInt(item, "LGEN_MAX_PROP_COUNT"); | |
DeleteLocalInt(item, "LGEN_PROPERTY"); | |
DeleteLocalInt(item, "LGEN_MIN_PROP1"); | |
DeleteLocalInt(item, "LGEN_MAX_PROP1"); | |
DeleteLocalInt(item, "LGEN_MIN_PROP2"); | |
DeleteLocalInt(item, "LGEN_MAX_PROP2"); | |
} | |
int INTERNAL_IsPropertyValid(object item, struct LGEN_PropertyStruct property) | |
{ | |
if (property.id == PROPERTY_INVALID) | |
{ | |
return FALSE; | |
} | |
int i; | |
for (i = 0; i < INTERNAL_MAX_PROPERTY_COUNT; ++i) | |
{ | |
struct LGEN_PropertyStruct otherProperty = INTERNAL_LGEN_GetProperty(item, i); | |
// If we have a property that is the same type as any other property ... | |
if (property.id == otherProperty.id) | |
{ | |
// ... We need to check a series of basic rules about stacking. | |
// Generally, nothing can stack. | |
// Custom rules to allow stacking go here. | |
switch (property.id) | |
{ | |
case PROPERTY_AC_BONUS_VS_RACE: if (property.mod1 == otherProperty.mod1) return FALSE; break; | |
case PROPERTY_SPECIFIC_SAVE_BONUS: if (property.mod1 == otherProperty.mod1) return FALSE; break; | |
case PROPERTY_ELEMENTAL_DR: if (property.mod1 == otherProperty.mod1) return FALSE; break; | |
case PROPERTY_ABILITY_BONUS: if (property.mod1 == otherProperty.mod1) return FALSE; break; | |
case PROPERTY_SKILL_BONUS: if (property.mod1 == otherProperty.mod1) return FALSE; break; | |
case PROPERTY_USAGE_RESTRICTION_CLASS: if (property.mod1 == otherProperty.mod1) return FALSE; break; | |
case PROPERTY_USAGE_RESTRICTION_RACE: if (property.mod1 == otherProperty.mod1) return FALSE; break; | |
case PROPERTY_WEAPON_ENH_BONUS_VS_RACE: if (property.mod1 == otherProperty.mod1) return FALSE; break; | |
case PROPERTY_WEAPON_ELEMENTAL_DAMAGE: if (property.mod1 == otherProperty.mod1) return FALSE; break; | |
default: return FALSE; | |
} | |
} | |
// At this point, we've passed the basic stacking sanity checks. | |
// Now we need to complete more detailed checks that handle stacking oddities. | |
int propertyTypeToFind = -1; | |
switch (property.id) | |
{ | |
case PROPERTY_AC_BONUS: propertyTypeToFind = PROPERTY_AC_BONUS_VS_RACE; break; | |
case PROPERTY_AC_BONUS_VS_RACE: propertyTypeToFind = PROPERTY_AC_BONUS; break; | |
case PROPERTY_SAVE_BONUS: propertyTypeToFind = PROPERTY_SPECIFIC_SAVE_BONUS; break; | |
case PROPERTY_SPECIFIC_SAVE_BONUS: propertyTypeToFind = PROPERTY_SAVE_BONUS; break; | |
case PROPERTY_WEAPON_AB_BONUS: propertyTypeToFind = PROPERTY_WEAPON_ENH_BONUS; break; | |
case PROPERTY_WEAPON_ENH_BONUS: propertyTypeToFind = PROPERTY_WEAPON_ENH_BONUS_VS_RACE; break; | |
case PROPERTY_WEAPON_ENH_BONUS_VS_RACE: propertyTypeToFind = PROPERTY_WEAPON_ENH_BONUS; break; | |
default: break; | |
} | |
if (propertyTypeToFind != -1) | |
{ | |
int j = 0; | |
while (TRUE) | |
{ | |
otherProperty = INTERNAL_LGEN_FindProperty(item, propertyTypeToFind, j++); | |
if (otherProperty.id == PROPERTY_INVALID) | |
{ | |
break; | |
} | |
if (property.id == PROPERTY_AC_BONUS) | |
{ | |
int acBonus = property.mod1; | |
int acBonusVsRace = otherProperty.mod2; | |
if (acBonusVsRace >= acBonus) | |
{ | |
return FALSE; | |
} | |
} | |
else if (property.id == PROPERTY_AC_BONUS_VS_RACE) | |
{ | |
int acBonus = otherProperty.mod1; | |
int acBonusVsRace = property.mod2; | |
if (acBonus >= acBonusVsRace) | |
{ | |
return FALSE; | |
} | |
} | |
else if (property.id == PROPERTY_SAVE_BONUS) | |
{ | |
int uniSave = property.mod2; | |
int specificSave = otherProperty.mod2; | |
if (specificSave >= uniSave) | |
{ | |
return FALSE; | |
} | |
} | |
else if (property.id == PROPERTY_SPECIFIC_SAVE_BONUS) | |
{ | |
int uniSave = otherProperty.mod2; | |
int specificSave = property.mod2; | |
if (uniSave >= specificSave) | |
{ | |
return FALSE; | |
} | |
} | |
else if (property.id == PROPERTY_WEAPON_AB_BONUS) | |
{ | |
int abBonus = property.mod1; | |
int enhBonus = otherProperty.mod1; | |
if (enhBonus >= abBonus) | |
{ | |
return FALSE; | |
} | |
} | |
else if (property.id == PROPERTY_WEAPON_ENH_BONUS) | |
{ | |
int enhBonus = property.mod1; | |
int enhBonusVsRace = otherProperty.mod2; | |
if (enhBonusVsRace >= enhBonus) | |
{ | |
return FALSE; | |
} | |
} | |
else if (property.id == PROPERTY_WEAPON_ENH_BONUS_VS_RACE) | |
{ | |
int enhBonus = otherProperty.mod1; | |
int enhBonusVsRace = property.mod2; | |
if (enhBonus >= enhBonusVsRace) | |
{ | |
return FALSE; | |
} | |
} | |
} | |
} | |
} | |
return TRUE; | |
} | |
struct INTERNAL_PropertyVarNameStruct INTERNAL_GetPropertyVarNames(int index) | |
{ | |
string propertyArrBase = "PROPERTY_ARR_" + IntToString(index); | |
struct INTERNAL_PropertyVarNameStruct varNames; | |
varNames.id = propertyArrBase + "_ID"; | |
varNames.mod1 = propertyArrBase + "_MOD1"; | |
varNames.mod2 = propertyArrBase + "_MOD2"; | |
return varNames; | |
} | |
int INTERNAL_GetItemTypeFromItem(object item) | |
{ | |
int itemType = GetBaseItemType(item); | |
switch (itemType) | |
{ | |
case BASE_ITEM_AMULET: | |
case BASE_ITEM_BELT: | |
case BASE_ITEM_BOOTS: | |
case BASE_ITEM_BRACER: | |
case BASE_ITEM_CLOAK: | |
case BASE_ITEM_GLOVES: | |
case BASE_ITEM_RING: | |
return ITEM_TYPE_GEAR; | |
case BASE_ITEM_ARMOR: | |
case BASE_ITEM_HELMET: | |
case BASE_ITEM_LARGESHIELD: | |
case BASE_ITEM_SMALLSHIELD: | |
case BASE_ITEM_TOWERSHIELD: | |
return ITEM_TYPE_ARMOUR; | |
case BASE_ITEM_BASTARDSWORD: | |
case BASE_ITEM_BATTLEAXE: | |
case BASE_ITEM_CLUB: | |
case BASE_ITEM_DAGGER: | |
case BASE_ITEM_DIREMACE: | |
case BASE_ITEM_DOUBLEAXE: | |
case BASE_ITEM_DWARVENWARAXE: | |
case BASE_ITEM_GREATAXE: | |
case BASE_ITEM_GREATSWORD: | |
case BASE_ITEM_HALBERD: | |
case BASE_ITEM_HANDAXE: | |
case BASE_ITEM_HEAVYCROSSBOW: | |
case BASE_ITEM_HEAVYFLAIL: | |
case BASE_ITEM_KAMA: | |
case BASE_ITEM_KATANA: | |
case BASE_ITEM_KUKRI: | |
case BASE_ITEM_LIGHTCROSSBOW: | |
case BASE_ITEM_LIGHTFLAIL: | |
case BASE_ITEM_LIGHTHAMMER: | |
case BASE_ITEM_LIGHTMACE: | |
case BASE_ITEM_LONGBOW: | |
case BASE_ITEM_LONGSWORD: | |
case BASE_ITEM_MORNINGSTAR: | |
case BASE_ITEM_QUARTERSTAFF: | |
case BASE_ITEM_RAPIER: | |
case BASE_ITEM_SCIMITAR: | |
case BASE_ITEM_SCYTHE: | |
case BASE_ITEM_SHORTBOW: | |
case BASE_ITEM_SHORTSPEAR: | |
case BASE_ITEM_SHORTSWORD: | |
case BASE_ITEM_SICKLE: | |
case BASE_ITEM_SLING: | |
case BASE_ITEM_TRIDENT: | |
case BASE_ITEM_TWOBLADEDSWORD: | |
case BASE_ITEM_WARHAMMER: | |
case BASE_ITEM_WHIP: | |
return ITEM_TYPE_WEAPON; | |
} | |
return -1; | |
} | |
int INTERNAL_RandomFromArrayOrRange(object item, string array, int min, int max) | |
{ | |
int size = IntArray_Size(item, array); | |
return size ? IntArray_At(item, array, Random(size)) : ClampedRandom(min, max); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// TODO: To be massively expanded to handle procedurally generated loot naming and descriptions. | |
// ----- PUBLIC API ------ | |
void ApplyLootNamingScheme(object loot, object finder); | |
// ----- INTERNAL API ------ | |
string INTERNAL_GetGenericNameFromItemType(int itemType); | |
// | |
// ----- | |
// | |
void ApplyLootNamingScheme(object loot, object finder) | |
{ | |
string name = INTERNAL_GetGenericNameFromItemType(GetBaseItemType(loot)); | |
SetName(loot, name); | |
string description = "A magical item"; | |
if (GetIsObjectValid(finder)) | |
{ | |
string areaName = GetSubString(GetName(GetArea(finder)), 0, FindSubString(GetName(GetArea(finder)), "(") -1); | |
description += " which was discovered in the " + areaName; | |
} | |
description += " region."; | |
SetDescription(loot, description); | |
} | |
string INTERNAL_GetGenericNameFromItemType(int itemType) | |
{ | |
switch (itemType) | |
{ | |
case BASE_ITEM_AMULET: | |
return "Magical Amulet"; | |
case BASE_ITEM_ARMOR: | |
return "Magical Armour"; | |
case BASE_ITEM_BELT: | |
return "Magical Belt"; | |
case BASE_ITEM_BOOTS: | |
return "Magical Boots"; | |
case BASE_ITEM_BRACER: | |
return "Magical Bracers"; | |
case BASE_ITEM_CLOAK: | |
return "Magical Cloak"; | |
case BASE_ITEM_GLOVES: | |
return "Magical Gloves"; | |
case BASE_ITEM_HELMET: | |
return "Magical Helmet"; | |
case BASE_ITEM_RING: | |
return "Magical Ring"; | |
case BASE_ITEM_LARGESHIELD: | |
case BASE_ITEM_SMALLSHIELD: | |
case BASE_ITEM_TOWERSHIELD: | |
return "Magical Shield"; | |
case BASE_ITEM_BASTARDSWORD: | |
case BASE_ITEM_BATTLEAXE: | |
case BASE_ITEM_CLUB: | |
case BASE_ITEM_DAGGER: | |
case BASE_ITEM_DIREMACE: | |
case BASE_ITEM_DOUBLEAXE: | |
case BASE_ITEM_DWARVENWARAXE: | |
case BASE_ITEM_GREATAXE: | |
case BASE_ITEM_GREATSWORD: | |
case BASE_ITEM_HALBERD: | |
case BASE_ITEM_HANDAXE: | |
case BASE_ITEM_HEAVYCROSSBOW: | |
case BASE_ITEM_HEAVYFLAIL: | |
case BASE_ITEM_KAMA: | |
case BASE_ITEM_KATANA: | |
case BASE_ITEM_KUKRI: | |
case BASE_ITEM_LIGHTCROSSBOW: | |
case BASE_ITEM_LIGHTFLAIL: | |
case BASE_ITEM_LIGHTHAMMER: | |
case BASE_ITEM_LIGHTMACE: | |
case BASE_ITEM_LONGBOW: | |
case BASE_ITEM_LONGSWORD: | |
case BASE_ITEM_MORNINGSTAR: | |
case BASE_ITEM_QUARTERSTAFF: | |
case BASE_ITEM_RAPIER: | |
case BASE_ITEM_SCIMITAR: | |
case BASE_ITEM_SCYTHE: | |
case BASE_ITEM_SHORTBOW: | |
case BASE_ITEM_SHORTSPEAR: | |
case BASE_ITEM_SHORTSWORD: | |
case BASE_ITEM_SICKLE: | |
case BASE_ITEM_SLING: | |
case BASE_ITEM_TRIDENT: | |
case BASE_ITEM_TWOBLADEDSWORD: | |
case BASE_ITEM_WARHAMMER: | |
case BASE_ITEM_WHIP: | |
return "Magical Weapon"; | |
} | |
return "Magical Item"; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "inc_baseitem" | |
#include "inc_lootgen" | |
// ------ PUBLIC API ------ | |
// Adds a resref into the pool of allowed resrefs for the provided base item type. | |
// NOTE: Uses the expanded list of base item types from inc_baseitem. | |
void AddBaseItemResRef(int baseItemType, string resref); | |
// Gets a random resref from the pool of allowed resrefs for the provided ITEM_TYPE_* constant. | |
string GetRandomResRefFromItemType(int itemType); | |
// ------ INTERNAL API ------ | |
const string INTERNAL_RESREF_ARRAY_TAG = "LOOTGEN_RESREF_ARRAY"; | |
// Returns a base item type from the provide ITEM_TYPE_* constant. | |
// NOTE: May return an expanded base item type from inc_baseitem. | |
int INTERNAL_GetRandomBaseItemTypeFromItemType(int itemType); | |
void AddBaseItemResRef(int baseItemType, string resref) | |
{ | |
StringArray_PushBack(GetModule(), INTERNAL_RESREF_ARRAY_TAG + IntToString(baseItemType), resref); | |
} | |
string GetRandomResRefFromItemType(int itemType) | |
{ | |
int baseItemType = INTERNAL_GetRandomBaseItemTypeFromItemType(itemType); | |
string tag = INTERNAL_RESREF_ARRAY_TAG + IntToString(baseItemType); | |
int count = StringArray_Size(GetModule(), tag); | |
return count > 0 ? StringArray_At(GetModule(), tag, Random(count)) : ""; | |
} | |
int INTERNAL_GetRandomBaseItemTypeFromItemType(int itemType) | |
{ | |
if (itemType == ITEM_TYPE_GEAR) | |
{ | |
switch (Random(8)) | |
{ | |
case 0: return BASE_ITEM_AMULET; | |
case 1: return BASE_ITEM_BELT; | |
case 2: return BASE_ITEM_BRACER; | |
case 3: return BASE_ITEM_CLOAK; | |
case 4: return BASE_ITEM_GLOVES; | |
case 5: | |
case 6: return BASE_ITEM_RING; | |
case 7: return BASE_ITEM_BOOTS; | |
} | |
} | |
else if (itemType == ITEM_TYPE_ARMOUR) | |
{ | |
switch (Random(13)) | |
{ | |
case 0: return BASE_ITEM_ARMOR_AC0; | |
case 1: return BASE_ITEM_ARMOR_AC1; | |
case 2: return BASE_ITEM_ARMOR_AC2; | |
case 3: return BASE_ITEM_ARMOR_AC3; | |
case 4: return BASE_ITEM_ARMOR_AC4; | |
case 5: return BASE_ITEM_ARMOR_AC5; | |
case 6: return BASE_ITEM_ARMOR_AC6; | |
case 7: return BASE_ITEM_ARMOR_AC7; | |
case 8: return BASE_ITEM_ARMOR_AC8; | |
case 9: return BASE_ITEM_HELMET; | |
case 10: return BASE_ITEM_SMALLSHIELD; | |
case 11: return BASE_ITEM_LARGESHIELD; | |
case 12: return BASE_ITEM_TOWERSHIELD; | |
} | |
} | |
else if (itemType == ITEM_TYPE_WEAPON) | |
{ | |
switch(Random(36)) | |
{ | |
case 0: return BASE_ITEM_SHORTSWORD; | |
case 1: return BASE_ITEM_LONGSWORD; | |
case 2: return BASE_ITEM_BATTLEAXE; | |
case 3: return BASE_ITEM_BASTARDSWORD; | |
case 4: return BASE_ITEM_LIGHTFLAIL; | |
case 5: return BASE_ITEM_WARHAMMER; | |
case 6: return BASE_ITEM_HEAVYCROSSBOW; | |
case 7: return BASE_ITEM_LIGHTCROSSBOW; | |
case 8: return BASE_ITEM_LONGBOW; | |
case 9: return BASE_ITEM_LIGHTMACE; | |
case 10: return BASE_ITEM_HALBERD; | |
case 11: return BASE_ITEM_SHORTBOW; | |
case 12: return BASE_ITEM_TWOBLADEDSWORD; | |
case 13: return BASE_ITEM_GREATSWORD; | |
case 14: return BASE_ITEM_GREATAXE; | |
case 15: return BASE_ITEM_DAGGER; | |
case 16: return BASE_ITEM_CLUB; | |
case 17: return BASE_ITEM_DIREMACE; | |
case 18: return BASE_ITEM_DOUBLEAXE; | |
case 19: return BASE_ITEM_HEAVYFLAIL; | |
case 20: return BASE_ITEM_LIGHTHAMMER; | |
case 21: return BASE_ITEM_HANDAXE; | |
case 22: return BASE_ITEM_KAMA; | |
case 23: return BASE_ITEM_KATANA; | |
case 24: return BASE_ITEM_KUKRI; | |
case 25: return BASE_ITEM_MORNINGSTAR; | |
case 26: return BASE_ITEM_QUARTERSTAFF; | |
case 27: return BASE_ITEM_RAPIER; | |
case 28: return BASE_ITEM_SCIMITAR; | |
case 29: return BASE_ITEM_SCYTHE; | |
case 30: return BASE_ITEM_SHORTSPEAR; | |
case 31: return BASE_ITEM_SICKLE; | |
case 32: return BASE_ITEM_SLING; | |
case 33: return BASE_ITEM_TRIDENT; | |
case 34: return BASE_ITEM_DWARVENWARAXE; | |
case 35: return BASE_ITEM_WHIP; | |
} | |
} | |
return -1; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "inc_lgen" | |
void main() | |
{ | |
LGEN_SetMinPropertyCount(3); | |
LGEN_SetMaxPropertyCount(4); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "inc_lgen" | |
void main() | |
{ | |
Tier1_Phase2Common(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "inc_lgen" | |
void main() | |
{ | |
Tier1_Phase3Common(); | |
if (LGEN_GetChosenProperty() == PROPERTY_SKILL_BONUS) | |
{ | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "inc_lgen" | |
void main() | |
{ | |
LGEN_SetMinPropertyCount(1); | |
LGEN_SetMaxPropertyCount(3); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "inc_lgen" | |
void main() | |
{ | |
Tier1_Phase2Common(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "inc_lgen" | |
void main() | |
{ | |
Tier1_Phase3Common(); | |
if (LGEN_GetChosenProperty() == PROPERTY_SKILL_BONUS) | |
{ | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "inc_lgen" | |
void main() | |
{ | |
LGEN_SetMinPropertyCount(2); | |
LGEN_SetMaxPropertyCount(4); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "inc_lgen" | |
void main() | |
{ | |
Tier1_Phase2Common(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "inc_lgen" | |
void main() | |
{ | |
Tier1_Phase3Common(); | |
if (LGEN_GetChosenProperty() == PROPERTY_SKILL_BONUS) | |
{ | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "inc_lgen" | |
void main() | |
{ | |
LGEN_SetMinPropertyCount(5); | |
LGEN_SetMaxPropertyCount(GetBaseItemType(OBJECT_SELF) == BASE_ITEM_RING ? 5 : 6); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "inc_lgen" | |
void main() | |
{ | |
Tier2_Phase2Common(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "inc_lgen" | |
void main() | |
{ | |
Tier2_Phase3Common(); | |
if (LGEN_GetChosenProperty() == PROPERTY_SKILL_BONUS) | |
{ | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "inc_lgen" | |
void main() | |
{ | |
LGEN_SetMinPropertyCount(3); | |
LGEN_SetMaxPropertyCount(5); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "inc_lgen" | |
void main() | |
{ | |
Tier2_Phase2Common(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "inc_lgen" | |
void main() | |
{ | |
Tier2_Phase3Common(); | |
if (LGEN_GetChosenProperty() == PROPERTY_SKILL_BONUS) | |
{ | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "inc_lgen" | |
void main() | |
{ | |
LGEN_SetMinPropertyCount(4); | |
LGEN_SetMaxPropertyCount(GetBaseItemType(OBJECT_SELF) == BASE_ITEM_RING ? 5 : 6); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "inc_lgen" | |
void main() | |
{ | |
Tier2_Phase2Common(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "inc_lgen" | |
void main() | |
{ | |
Tier2_Phase3Common(); | |
if (LGEN_GetChosenProperty() == PROPERTY_SKILL_BONUS) | |
{ | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 1); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
IntArray_PushBack(OBJECT_SELF, LGEN_MOD2_ARRAY_TAG, 2); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment