Script: mw_unlock_inc
//::///////////////////////////////////////////////
//:: mw_unlock_inc -- MeaningWave guide roster, persistence, and summoning.
//::
//:: Per-PC unlock flags live in the "meaningwave" campaign DB (scoped per
//:: player by passing oPC to GetCampaignInt/SetCampaignInt). Roster is
//:: 7 named figures plus Akira the Don as Hall curator.
//::
//:: Meta-quest stages on "MW Path of Meaning":
//:: 1 = intro whisper (added on first shrine touch or guide encounter)
//:: 2..8 = stage advances as guides 1..7 are unlocked
//:: 9 = finale (added by Akira's dialogue when mixtape is granted)
//:://////////////////////////////////////////////
#include "x2_inc_itemprop"
const string MW_DB = "meaningwave";
const string MW_META_QUEST = "MW Path of Meaning";
const int MW_ROSTER_SIZE = 7;
string MW_GuideAt(int i)
{
switch (i)
{
case 0: return "peterson";
case 1: return "watts";
case 2: return "campbell";
case 3: return "mckenna";
case 4: return "jocko";
case 5: return "jung";
case 6: return "aurelius";
}
return "";
}
string MW_GuideDisplayName(string sGuide)
{
if (sGuide == "peterson") return "Jordan Peterson";
if (sGuide == "watts") return "Alan Watts";
if (sGuide == "campbell") return "Joseph Campbell";
if (sGuide == "mckenna") return "Terence McKenna";
if (sGuide == "jocko") return "Jocko Willink";
if (sGuide == "jung") return "Carl Jung";
if (sGuide == "aurelius") return "Marcus Aurelius";
return sGuide;
}
string MW_GuideQuestTag(string sGuide)
{
return "MW " + MW_GuideDisplayName(sGuide);
}
int MW_IsUnlocked(object oPC, string sGuide)
{
return GetCampaignInt(MW_DB, "u_" + sGuide, oPC);
}
int MW_UnlockCount(object oPC)
{
int n = 0;
int i;
for (i = 0; i < MW_ROSTER_SIZE; i++)
if (MW_IsUnlocked(oPC, MW_GuideAt(i))) n++;
return n;
}
void MW_IntroJournal(object oPC)
{
if (GetCampaignInt(MW_DB, "jq_intro", oPC)) return;
SetCampaignInt(MW_DB, "jq_intro", 1, oPC);
AddJournalQuestEntry(MW_META_QUEST, 1, oPC, FALSE, FALSE);
}
void MW_EncounterJournal(object oPC, string sGuide)
{
MW_IntroJournal(oPC);
if (MW_IsUnlocked(oPC, sGuide)) return;
string sKey = "jq_enc_" + sGuide;
if (GetCampaignInt(MW_DB, sKey, oPC)) return;
SetCampaignInt(MW_DB, sKey, 1, oPC);
AddJournalQuestEntry(MW_GuideQuestTag(sGuide), 1, oPC, FALSE, FALSE);
}
void MW_SyncJournal(object oPC)
{
int nCount = MW_UnlockCount(oPC);
if (nCount == 0) return;
AddJournalQuestEntry(MW_META_QUEST, 1, oPC, FALSE, FALSE);
AddJournalQuestEntry(MW_META_QUEST, nCount + 1, oPC, FALSE, FALSE);
if (GetCampaignInt(MW_DB, "finale", oPC))
AddJournalQuestEntry(MW_META_QUEST, MW_ROSTER_SIZE + 2, oPC, FALSE, FALSE);
int i;
for (i = 0; i < MW_ROSTER_SIZE; i++)
{
string sGuide = MW_GuideAt(i);
if (MW_IsUnlocked(oPC, sGuide))
AddJournalQuestEntry(MW_GuideQuestTag(sGuide), 2, oPC, FALSE, FALSE);
}
}
void MW_Unlock(object oPC, string sGuide)
{
if (MW_IsUnlocked(oPC, sGuide)) return;
SetCampaignInt(MW_DB, "u_" + sGuide, 1, oPC);
int nCount = MW_UnlockCount(oPC);
AddJournalQuestEntry(MW_META_QUEST, nCount + 1, oPC, TRUE, FALSE);
AddJournalQuestEntry(MW_GuideQuestTag(sGuide), 2, oPC, TRUE, FALSE);
FloatingTextStringOnCreature(
"You have gained the wisdom of " + MW_GuideDisplayName(sGuide) +
". Rest to summon them as a guide.",
oPC, FALSE);
GiveXPToCreature(oPC, 500);
ApplyEffectToObject(DURATION_TYPE_INSTANT,
EffectVisualEffect(VFX_IMP_HEALING_X), oPC);
}
void MW_DismissActiveGuide(object oPC)
{
int i;
for (i = 1; i <= 5; i++)
{
object oH = GetHenchman(oPC, i);
if (!GetIsObjectValid(oH)) break;
if (GetStringLeft(GetTag(oH), 3) == "mw_")
{
RemoveHenchman(oPC, oH);
AssignCommand(oH, ClearAllActions());
ApplyEffectToObject(DURATION_TYPE_INSTANT,
EffectDisappear(), oH);
}
}
DeleteLocalString(oPC, "mw_current");
}
// Map a flat damage amount (+N) to its iprp_damagecost.2da row index.
// This module's hak extends the table to +20 (rows 21..30 = +11..+20), so we
// pass the raw row index rather than the stock IP_CONST_DAMAGEBONUS_* constants
// (which only reach +10). Layout: +1..+5 = rows 1..5; +6..+20 = rows 16..30.
int MW_DmgBonusConst(int n)
{
if (n < 1) n = 1;
if (n > 20) n = 20; // hak maximum is +20
if (n <= 5) return n; // rows 1..5 -> +1..+5
return n + 10; // rows 16..30 -> +6..+20
}
// Add scaled +divine / +positive damage to a weapon (or unarmed gloves/bracers).
void MW_AddWeaponDamage(object oItem, int nDivine, int nPositive)
{
if (!GetIsObjectValid(oItem)) return;
IPSafeAddItemProperty(oItem,
ItemPropertyDamageBonus(IP_CONST_DAMAGETYPE_DIVINE, MW_DmgBonusConst(nDivine)));
IPSafeAddItemProperty(oItem,
ItemPropertyDamageBonus(IP_CONST_DAMAGETYPE_POSITIVE, MW_DmgBonusConst(nPositive)));
}
// Ward the aegis ring against a single damage type: +15/- resist & 25% immunity.
void MW_WardRing(object oRing, int nType)
{
IPSafeAddItemProperty(oRing,
ItemPropertyDamageResistance(nType, IP_CONST_DAMAGERESIST_15));
IPSafeAddItemProperty(oRing,
ItemPropertyDamageImmunity(nType, IP_CONST_DAMAGEIMMUNITY_25_PERCENT));
}
// Scale a freshly-summoned guide by how many MeaningWave figures oPC has unlocked.
// Re-summoning makes a fresh CreateObject, so these bonuses never stack across summons.
void MW_ScaleGuide(object oGuide, object oPC)
{
int nCount = MW_UnlockCount(oPC);
if (nCount < 1) nCount = 1; // you must own a guide to summon it
// --- Aegis ring: resistance + immunity to every damage type ---
object oRing = GetItemInSlot(INVENTORY_SLOT_LEFTRING, oGuide);
if (GetIsObjectValid(oRing))
{
MW_WardRing(oRing, IP_CONST_DAMAGETYPE_BLUDGEONING);
MW_WardRing(oRing, IP_CONST_DAMAGETYPE_PIERCING);
MW_WardRing(oRing, IP_CONST_DAMAGETYPE_SLASHING);
MW_WardRing(oRing, IP_CONST_DAMAGETYPE_MAGICAL);
MW_WardRing(oRing, IP_CONST_DAMAGETYPE_ACID);
MW_WardRing(oRing, IP_CONST_DAMAGETYPE_COLD);
MW_WardRing(oRing, IP_CONST_DAMAGETYPE_DIVINE);
MW_WardRing(oRing, IP_CONST_DAMAGETYPE_ELECTRICAL);
MW_WardRing(oRing, IP_CONST_DAMAGETYPE_FIRE);
MW_WardRing(oRing, IP_CONST_DAMAGETYPE_NEGATIVE);
MW_WardRing(oRing, IP_CONST_DAMAGETYPE_POSITIVE);
MW_WardRing(oRing, IP_CONST_DAMAGETYPE_SONIC);
}
// --- Ability scaling: +6 to every ability per unlocked guide ---
// Stacked supernatural effects bypass the per-effect cap, so this is uncapped.
int i;
for (i = 0; i < nCount; i++)
{
int a;
for (a = ABILITY_STRENGTH; a <= ABILITY_CHARISMA; a++)
ApplyEffectToObject(DURATION_TYPE_PERMANENT,
SupernaturalEffect(EffectAbilityIncrease(a, 6)), oGuide);
}
// --- Weapon scaling: +2 divine & +2 positive damage per unlocked guide ---
int nDmg = 2 * nCount;
object oWpnR = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oGuide);
object oWpnL = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oGuide);
int bArmed = FALSE;
if (GetIsObjectValid(oWpnR)) { MW_AddWeaponDamage(oWpnR, nDmg, nDmg); bArmed = TRUE; }
if (GetIsObjectValid(oWpnL)) { MW_AddWeaponDamage(oWpnL, nDmg, nDmg); bArmed = TRUE; }
if (!bArmed)
{
// Unarmed monks (Watts, Jocko): bonus rides on their gloves/bracers.
object oArms = GetItemInSlot(INVENTORY_SLOT_ARMS, oGuide);
MW_AddWeaponDamage(oArms, nDmg, nDmg);
}
// --- Spellcraft so casters succeed at the Counterspell combat mode ---
ApplyEffectToObject(DURATION_TYPE_PERMANENT,
SupernaturalEffect(EffectSkillIncrease(SKILL_SPELLCRAFT, 30)), oGuide);
}
void MW_SummonGuide(object oPC, string sGuide)
{
if (!MW_IsUnlocked(oPC, sGuide))
{
FloatingTextStringOnCreature(
MW_GuideDisplayName(sGuide) + " is not yet known to you.",
oPC, FALSE);
return;
}
MW_DismissActiveGuide(oPC);
location lLoc = GetLocation(oPC);
object oGuide = CreateObject(OBJECT_TYPE_CREATURE,
"mw_" + sGuide, lLoc);
if (!GetIsObjectValid(oGuide))
{
FloatingTextStringOnCreature(
"Failed to summon " + MW_GuideDisplayName(sGuide) +
" (blueprint mw_" + sGuide + " missing).",
oPC, FALSE);
return;
}
AddHenchman(oPC, oGuide);
SetLocalString(oPC, "mw_current", sGuide);
ApplyEffectToObject(DURATION_TYPE_INSTANT,
EffectVisualEffect(VFX_FNF_SUMMON_MONSTER_3), oGuide);
// Scale gear/stats to the number of guides oPC has unlocked, and apply the
// aegis ring's resistance/immunity to the freshly-equipped instance.
MW_ScaleGuide(oGuide, oPC);
}