Script: q_frk_inc
// q_frk_inc.nss -- "The Forbidden Realms" (roadmap: forbidden-realms-key-tier)
//
// The Forbidden Realms Key has always been stealable from Summanus in
// Numenor: Noirinan (falseheaven) but opened nothing. This gives it a door.
//
// The chain:
// * Acquire the key (Tag "ForbiddenRealmsKey", any route -- pickpocket,
// loot, trade). acquireditem_tag fires FRK_OnKeyAcquired: journal entry 1.
// Characters who stole it before this shipped are caught retroactively by
// FRK_LoginCheck, called from hgll_cliententer.
// * In Noirinan a sealed barrow-gate (placeable q_frk_gate) stands at the
// admin-placed waypoint AP_forbiddenrealmskeytier_1. Using it without the
// key does nothing but describe the seal; with the key it opens and jumps
// the user to AP_forbiddenrealmskeytier_2 -- journal entry 2, then 3.
// * That waypoint is in gravesofthelostk (Tombs of the Lost Souls), an area
// with no connections at all today. Its OnEnter wrapper (q_frk_tomb)
// script-spawns the barrow-court -- the Weathertop King, Queen and Archer,
// three of the module's unspawned blueprints -- at waypoints _3/_4/_5, and
// spawns a return gate at _2 so the tomb is not a one-way trip.
// * Killing a court member runs q_frk_death, which records the kill; all
// three down completes the journal (entry 10, End).
//
// Persistence: campaign DB "forbiddendb", per character (oPC-keyed
// Get/SetCampaignInt), mirroring the fighterlinedb / prestige idiom.
// "frk_stage" 0 none / 1 key acquired / 2 gate opened / 3 tombs entered
// "frk_king", "frk_queen", "frk_archer" 1 once that court member is killed
//
// EVERYTHING here no-ops gracefully while the waypoints are unplaced: no gate
// spawns, no court spawns, and the quest simply sits at entry 1. See the
// roadmap item's manual_steps for the five waypoints.
//
// All calls are base NWScript builtins -- no framework include.
const string FRK_DB = "forbiddendb";
const string FRK_STAGE = "frk_stage";
const string FRK_QUEST = "frk_tombs"; // journal category Tag
const string FRK_KEYTAG = "ForbiddenRealmsKey"; // item Tag on the key
// Admin-placed waypoints (tag = AP_ + roadmap id with hyphens stripped + index)
const string FRK_WP_GATE = "AP_forbiddenrealmskeytier_1"; // Noirinan, the seal
const string FRK_WP_TOMB = "AP_forbiddenrealmskeytier_2"; // Tombs, arrival + exit
const string FRK_WP_KING = "AP_forbiddenrealmskeytier_3";
const string FRK_WP_QUEEN = "AP_forbiddenrealmskeytier_4";
const string FRK_WP_ARCH = "AP_forbiddenrealmskeytier_5";
// Barrow-court blueprints (all previously unspawned). Swapping a member for a
// different CR variant is a one-line change here -- see the roadmap item.
const string FRK_RR_KING = "weathertopkin003"; // Weathertop King, CR 454
const string FRK_RR_QUEEN = "weathertopque003"; // Weathertop Queen, CR 594
const string FRK_RR_ARCHER = "weathertoparc002"; // Weathertop Archer, CR 337
const string FRK_GATE_RR = "q_frk_gate"; // gate placeable blueprint
const string FRK_GATE_DEST = "FRK_DEST"; // local string: dest WP tag
// ------------------------------------------------------------
// Stage accessors (persistent, per character). The stage only advances.
int FRK_GetStage(object oPC)
{
return GetCampaignInt(FRK_DB, FRK_STAGE, oPC);
}
void FRK_SetStage(object oPC, int nStage)
{
if (GetCampaignInt(FRK_DB, FRK_STAGE, oPC) >= nStage) return;
SetCampaignInt(FRK_DB, FRK_STAGE, nStage, oPC);
}
int FRK_HasKey(object oPC)
{
return GetIsObjectValid(GetItemPossessedBy(oPC, FRK_KEYTAG));
}
// ------------------------------------------------------------
// Stage 1: the key is in hand. Called from acquireditem_tag (fresh pickup) and
// from FRK_LoginCheck (characters who already had it before this shipped).
void FRK_OnKeyAcquired(object oPC)
{
if (!GetIsPC(oPC)) return;
if (FRK_GetStage(oPC) >= 1) return;
FRK_SetStage(oPC, 1);
AddJournalQuestEntry(FRK_QUEST, 1, oPC, FALSE, FALSE, TRUE);
}
// Retroactive catch-up on login: anyone already carrying the key gets the
// quest. Idempotent -- FRK_OnKeyAcquired bails once the stage is set.
void FRK_LoginCheck(object oPC)
{
if (!GetIsPC(oPC)) return;
if (FRK_GetStage(oPC) >= 1) return;
if (!FRK_HasKey(oPC)) return;
FRK_OnKeyAcquired(oPC);
}
// ------------------------------------------------------------
// Gate spawning. One blueprint serves both ends of the passage; which way it
// leads is a local string set at spawn time, so there is no second blueprint
// to keep in sync.
// TRUE if a gate is already standing at oWP's area near enough to count. We
// guard per-waypoint rather than by module-wide tag, because both gates share
// the blueprint's tag.
int FRK_GateExistsAt(object oWP)
{
object oArea = GetArea(oWP);
object oObj = GetFirstObjectInArea(oArea);
while (GetIsObjectValid(oObj))
{
if (GetObjectType(oObj) == OBJECT_TYPE_PLACEABLE
&& GetResRef(oObj) == FRK_GATE_RR)
return TRUE;
oObj = GetNextObjectInArea(oArea);
}
return FALSE;
}
// Spawn the gate at sWP leading to sDest. No-ops if sWP is not placed yet or a
// gate is already standing there.
void FRK_SpawnGate(string sWP, string sDest)
{
object oWP = GetWaypointByTag(sWP);
if (!GetIsObjectValid(oWP)) return;
if (FRK_GateExistsAt(oWP)) return;
object oGate = CreateObject(OBJECT_TYPE_PLACEABLE, FRK_GATE_RR,
GetLocation(oWP));
if (GetIsObjectValid(oGate))
SetLocalString(oGate, FRK_GATE_DEST, sDest);
}
// ------------------------------------------------------------
// Using a gate. Outbound (Noirinan -> Tombs) demands the key; the return gate
// inside the tombs never does, so a player who loses the key is not entombed.
void FRK_UseGate(object oGate, object oPC)
{
if (!GetIsPC(oPC)) return;
string sDest = GetLocalString(oGate, FRK_GATE_DEST);
if (sDest == "") return;
object oWP = GetWaypointByTag(sDest);
if (!GetIsObjectValid(oWP)) return;
int bOutbound = (sDest == FRK_WP_TOMB);
if (bOutbound && !FRK_HasKey(oPC))
{
SendMessageToPC(oPC, "The barrow-gate is sealed. Seven marks are cut "
+ "into the stone, and no hand of yours will move it without the "
+ "key that answers them.");
return;
}
if (bOutbound)
{
FRK_SetStage(oPC, 2);
AddJournalQuestEntry(FRK_QUEST, 2, oPC, FALSE, FALSE, TRUE);
SendMessageToPC(oPC, "The Forbidden Realms Key turns in a lock you "
+ "cannot see, and the seal gives way.");
}
AssignCommand(oPC, ActionJumpToLocation(GetLocation(oWP)));
}
// ------------------------------------------------------------
// Tomb arrival: stage 3 + journal entry 3, once per character.
void FRK_OnTombEntered(object oPC)
{
if (!GetIsPC(oPC)) return;
if (FRK_GetStage(oPC) >= 3) return;
FRK_SetStage(oPC, 3);
AddJournalQuestEntry(FRK_QUEST, 3, oPC, FALSE, FALSE, TRUE);
}
// ------------------------------------------------------------
// Court spawning. Each member is spawned only if no creature of that blueprint
// is currently standing in the tombs, so the court re-forms once it has been
// cleared and the area has emptied -- and never doubles up on a party trickling
// in one player at a time.
int FRK_CourtMemberPresent(object oArea, string sResRef)
{
object oObj = GetFirstObjectInArea(oArea);
while (GetIsObjectValid(oObj))
{
if (GetObjectType(oObj) == OBJECT_TYPE_CREATURE
&& GetResRef(oObj) == sResRef
&& !GetIsDead(oObj))
return TRUE;
oObj = GetNextObjectInArea(oArea);
}
return FALSE;
}
void FRK_SpawnCourtMember(object oArea, string sWP, string sResRef)
{
object oWP = GetWaypointByTag(sWP);
if (!GetIsObjectValid(oWP)) return;
if (GetArea(oWP) != oArea) return;
if (FRK_CourtMemberPresent(oArea, sResRef)) return;
CreateObject(OBJECT_TYPE_CREATURE, sResRef, GetLocation(oWP));
}
// Spawn the whole court in oArea (the Tombs). Graceful no-op per member while
// its waypoint is unplaced, so a partially placed tomb still works.
void FRK_SpawnCourt(object oArea)
{
FRK_SpawnCourtMember(oArea, FRK_WP_KING, FRK_RR_KING);
FRK_SpawnCourtMember(oArea, FRK_WP_QUEEN, FRK_RR_QUEEN);
FRK_SpawnCourtMember(oArea, FRK_WP_ARCH, FRK_RR_ARCHER);
}
// ------------------------------------------------------------
// Kill bookkeeping. Called from q_frk_death with the killer's party; the
// journal completes when one character has all three recorded.
void FRK_RecordKill(object oPC, string sKey)
{
if (!GetIsPC(oPC)) return;
if (FRK_GetStage(oPC) < 1) return; // never touched the quest
SetCampaignInt(FRK_DB, sKey, 1, oPC);
if (GetCampaignInt(FRK_DB, "frk_king", oPC) == 1
&& GetCampaignInt(FRK_DB, "frk_queen", oPC) == 1
&& GetCampaignInt(FRK_DB, "frk_archer", oPC) == 1)
{
AddJournalQuestEntry(FRK_QUEST, 10, oPC, FALSE, FALSE, TRUE);
}
}