Skip to content

Instantly share code, notes, and snippets.

@Liareth
Created December 11, 2017 21:38
Show Gist options
  • Save Liareth/d8218197e55b8926c3dbb9e52f9bae99 to your computer and use it in GitHub Desktop.
Save Liareth/d8218197e55b8926c3dbb9e52f9bae99 to your computer and use it in GitHub Desktop.
// 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 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 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);
}
// 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";
}
#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;
}
#include "inc_lgen"
void main()
{
LGEN_SetMinPropertyCount(3);
LGEN_SetMaxPropertyCount(4);
}
#include "inc_lgen"
void main()
{
Tier1_Phase2Common();
}
#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);
}
}
#include "inc_lgen"
void main()
{
LGEN_SetMinPropertyCount(1);
LGEN_SetMaxPropertyCount(3);
}
#include "inc_lgen"
void main()
{
Tier1_Phase2Common();
}
#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);
}
}
#include "inc_lgen"
void main()
{
LGEN_SetMinPropertyCount(2);
LGEN_SetMaxPropertyCount(4);
}
#include "inc_lgen"
void main()
{
Tier1_Phase2Common();
}
#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);
}
}
#include "inc_lgen"
void main()
{
LGEN_SetMinPropertyCount(5);
LGEN_SetMaxPropertyCount(GetBaseItemType(OBJECT_SELF) == BASE_ITEM_RING ? 5 : 6);
}
#include "inc_lgen"
void main()
{
Tier2_Phase2Common();
}
#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);
}
}
#include "inc_lgen"
void main()
{
LGEN_SetMinPropertyCount(3);
LGEN_SetMaxPropertyCount(5);
}
#include "inc_lgen"
void main()
{
Tier2_Phase2Common();
}
#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);
}
}
#include "inc_lgen"
void main()
{
LGEN_SetMinPropertyCount(4);
LGEN_SetMaxPropertyCount(GetBaseItemType(OBJECT_SELF) == BASE_ITEM_RING ? 5 : 6);
}
#include "inc_lgen"
void main()
{
Tier2_Phase2Common();
}
#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