Script: onmoduleload

//On module load
//Example of OnLoad Script


//***************************************************************************
// CONSTANTS



//***************************************************************************


//PCs Autosaving
#include "pc_export_inc"
#include "season_prof_inc"
#include "color"
#include "nwnx_admin"
#include "nwnx_events"
#include "nwnx_damage"
#include "x2_inc_switches"
#include "ru_db"
#include "brd_db"
#include "admin_db"
#include "boost_db"
#include "quest_cd_inc"
#include "faction_db"
#include "worldstate_inc"
#include "warmeter_inc"
#include "ammorep_db"
#include "cbd_db"
#include "pw_inc"
#include "fat_inc"


void main()
{

//****************************************************************************
//PCs Autosaving function
pc_export_onmoduleload();

// Force max HP on every level-up, server-wide.
NWNX_Administration_SetPlayOption(NWNX_ADMINISTRATION_OPTION_USE_MAX_HITPOINTS, TRUE);
//----------------------------------------------------------------------------

// Enable tag-based scripting for item events (e.g. Rod of Fast Buffing)
SetModuleSwitch(MODULE_SWITCH_ENABLE_TAGBASED_SCRIPTS, TRUE);

// Module override spellscript: run stop_spellcheat before every spell impact
// script (counterspell anti-cheat + soul-fatigue on Heal/Mass Heal via
// fat_inc.nss, roadmap: heal-soul-fatigue). This MUST be installed here - the
// old on_module_load.nss that carried these lines was never wired to
// Mod_OnModLoad (the hook is this script, onmoduleload), so the override
// never ran. x2_inc_spellhook's X2RunUserDefinedSpellScript() only fires when
// the module local X2_S_UD_SPELLSCRIPT is set.
SetModuleSwitch(MODULE_VAR_OVERRIDE_SPELLSCRIPT, TRUE);
SetModuleOverrideSpellscript("stop_spellcheat");

// Spawn Meaningwave NPCs at their designated waypoints
ExecuteScript("mw_spawn", GetModule());

// Gwathdor Labyrinth (area025): re-roll the maze wiring for this reboot. Sets a
// "MAZE_DEST" local on every maze door/trigger; the doors/triggers teleport the
// PC there via gwathlab_door / gwathlab_trig. A restart = a fresh maze layout.
ExecuteScript("gwathlab_wire", GetModule());

// Double the duration of every temporary effect a player creates (eff_dur_x2).
NWNX_Events_SubscribeEvent(NWNX_ON_EFFECT_APPLIED_AFTER, "eff_dur_x2");

// Devastating Critical rework (roadmap: devcrit-roll). Two halves, because the
// engine uses two mechanisms: devcrit_atk adds the bonus physical damage in the
// NWNX Damage attack event (the only place iAttackResult == 10 is visible), and
// devcrit_eff refuses the EffectDeath the engine applies separately. The rule is
// symmetric - no oOwner on the attack script, so it covers NPCs as well as
// players. Both handlers return immediately on anything that is not their case;
// see the warnings in their headers before editing either, and note that
// NWNX_DAMAGE_SKIP=n in server.env is the other half of the plugin being
// available at all. tests/check_devcrit.py gates all of this.
NWNX_Damage_SetAttackEventScript("devcrit_atk");
NWNX_Events_SubscribeEvent(NWNX_ON_EFFECT_APPLIED_BEFORE, "devcrit_eff");

// Attack/damage bonus ledger (bonus_pool_inc): recalculate when a bonus ENDS.
// Registration and login are not enough - a song that ended early left its
// attack bonus behind, and a respawn's wholesale RemoveEffects took the
// permanent feat bonuses away with nothing to put them back. bpool_eff is
// guarded by a local that only buffed creatures carry, so an effect removal on
// anyone else costs one GetLocalInt. See its header before editing: rebuilding
// the ledger strips our own effect and fires this very event.
NWNX_Events_SubscribeEvent(NWNX_ON_EFFECT_REMOVED_AFTER, "bpool_eff");

// Premium 2x gold/XP boost: multiply positive XP gains for players with an active
// boost (merit redemptions 201-204). Engine combat XP is not script-granted, so
// XP is intercepted centrally here rather than at kill sites. The event name has
// no const in nwnx_events.nss, so it is passed as a literal string.
NWNX_Events_SubscribeEvent("NWNX_ON_SET_EXPERIENCE_BEFORE", "boost_xp_evt");

// "Next Level" character-sheet fix for levels 40-60. The NWNX MaxLevel plugin
// leaves the sheet's next-level XP figure wrong past 40; nextlvl_inc.nss
// overrides strref 315 per player with the real requirement out of
// exptable.2da. Login is handled in mod_cliententer.nss; these two keep it
// current when the level moves (death XP loss can drain back below 40).
NWNX_Events_SubscribeEvent(NWNX_ON_LEVEL_UP_AFTER,   "nextlvl_evt");
NWNX_Events_SubscribeEvent(NWNX_ON_LEVEL_DOWN_AFTER, "nextlvl_evt");

// Caster spell picker: past CLASS level 40 the game client's own level-up spell
// page offers nothing but cantrips and then spends the picks anyway, so a
// wizard silently loses 2 spells a level. The tabs are disabled by a pass in
// CLIENT code, so no 2DA, hak or plugin option can reach it (roadmap
// legendary-caster-spells-on-level-up, tests A-D). The module hands the picks
// out itself and opens its own window instead; see csp_inc.nss.
//
// This line replaces the two sk_probe_* subscriptions that established the
// diagnosis - those scripts are deleted.
NWNX_Events_SubscribeEvent(NWNX_ON_LEVEL_UP_AFTER,   "csp_lvl");

// Legendary Feats: fire the level-60 picker the moment a character reaches 60.
// The picks cannot come from the engine's own level-up page - that page grants
// exactly one general feat to everybody, and legendary feats are deliberately
// invisible to it (ALLCLASSESCANUSE = 0). See CLAUDE-legendary-feats.md.
//
// LEVEL_DOWN matters as much as LEVEL_UP: a character that drops below 60 (death
// XP loss) or changes its class makeup has its picks revoked, and legfeat_lvl is
// where that is noticed. Energy drain is not a level loss and does not reach it.
NWNX_Events_SubscribeEvent(NWNX_ON_LEVEL_UP_AFTER,   "legfeat_lvl");
NWNX_Events_SubscribeEvent(NWNX_ON_LEVEL_DOWN_AFTER, "legfeat_lvl");

// Some legendary feats are CONDITIONAL on what the character is holding -
// Legendary Onslaught grants its extra attack for melee and unarmed but not for
// a bow. legfeat_equip rebuilds those on every weapon swap; without it the
// effect goes stale silently, and an archer keeps a melee-only bonus attack.
NWNX_Events_SubscribeEvent(NWNX_ON_ITEM_EQUIP_AFTER,   "legfeat_equip");
NWNX_Events_SubscribeEvent(NWNX_ON_ITEM_UNEQUIP_AFTER, "legfeat_equip");

// Party loot: announce current roll settings when a player joins a party or the
// party leadership changes (pl_party_evt broadcasts to the whole party).
NWNX_Events_SubscribeEvent(NWNX_ON_PARTY_ACCEPT_INVITATION_AFTER, "pl_party_evt");
NWNX_Events_SubscribeEvent(NWNX_ON_PARTY_TRANSFER_LEADERSHIP_AFTER, "pl_party_evt");

// Disarm catch: when an NPC disarms a PC, deposit the weapon into the PC's pack
// instead of dropping it on the ground (where it can despawn / be grabbed by the
// NPC). BEFORE snapshots the wielded weapon; AFTER moves it. PvP disarms are left
// vanilla. See disarm_catch.nss.
NWNX_Events_SubscribeEvent(NWNX_ON_DISARM_BEFORE, "disarm_catch");
NWNX_Events_SubscribeEvent(NWNX_ON_DISARM_AFTER,  "disarm_catch");

// Legendary Juggernaut's disarm immunity. The engine has no IMMUNITY_TYPE_ for
// disarm, so the only way to make a weapon unstrikable is to refuse the event.
// A second subscriber on the same event is fine - NWNX_Events runs them all.
NWNX_Events_SubscribeEvent(NWNX_ON_DISARM_BEFORE, "legfeat_disarm");

// Color tokens for dialogue text (used in bank XP retirement warnings)
// CUSTOM6100 = red, CUSTOM6101 = yellow, CUSTOM6102 = close
SetCustomToken(6100, COLOR_RED);
SetCustomToken(6101, COLOR_YELLOW);
SetCustomToken(6102, COLOR_END);

// Recent Updates sign (Well of Eru): ensure the roadmapdb table exists before
// any read. It is populated externally by the roadmap editor's Publish button.
RU_InitDb();

// Roll of the Fallen board (Well of Eru): reseed the boss registry and clear
// stale death rows - a restart revives every boss, so the board starts empty.
BRD_InitDb();

// Admin whitelist (rest-menu Admin/Homeless options, cheat chest): ensure the
// admins table exists before any read. Seeded externally by bin/seed-admindb.sh.
Admin_InitDb();

// Premium 2x gold/XP boost subscriptions (merit redemptions 201-204): ensure the
// boostdb tables exist before any kill/quest reward reads them.
Boost_InitDb();

// Quest cooldowns (daily/weekly repeatable quests): ensure the quest_cd table
// exists before any dialogue conditional reads it. See quest_cd_inc.nss.
QCD_InitDb();

// Faction allegiance (Good/Evil, persisted per-character): ensure the
// faction_standing table exists before any adjuster write or login re-apply.
// See faction_db.nss (roadmap: faction-scaffolding).
Faction_InitDb();

// World-state globals (server-wide contested control / timed buffs / weekly
// claims): ensure the world_state + world_state_rule tables exist before any
// read or before the heartbeat's WS_Tick() applies a decay/weekly rule.
// See worldstate_inc.nss (roadmap: lumber-ent-tugofwar).
WS_InitDb();

// Fangorn tug-of-war meter (isengard_warmachine): seed the neutral value on a
// virgin DB and register its 1-point-per-real-hour decay back toward neutral,
// so the rule exists on a fresh server. Idempotent - safe on every load. The
// decay itself is applied by WS_Tick() off the heartbeat. Meter layer only;
// the quest pair that pushes it is still design-queued.
// See warmeter_inc.nss (roadmap: lumber-ent-tugofwar).
WM_Init();

// Quiver of Endless Flight (ammo replicator, Legolas/Angmar drop): ensure the
// replicators table exists before the first activation reads a use count. Uses
// are keyed on the ITEM's UUID so they follow the quiver when it changes hands.
// See ammorep_db.nss (roadmap: Ammo-shortage).
AmmoRep_InitDb();

// Combat Dummy leaderboard ("Hall of Champions" sign): ensure the sessions
// table exists before the first trial finishes or the first sign read. The
// dummy's own OnSpawn calls this too, so a dummy placed in a module whose load
// order never reached here still records. See cbd_db.nss (roadmap: combat-dummy).
Cbd_InitDb();

// Concerning Pipeweed: the pipe-weed high is stored per character (strain +
// real-world expiry) rather than applied as a plain temporary effect, so that
// resting cannot scrub the penalty half of the trade. Ensure the table exists
// before the first pipe is lit or the first login reapply reads it.
// See pw_inc.nss (roadmap: concerning-pipeweed).
PW_InitDb();

// Soul-fatigue on the full-heal spells: stacks now persist per character and
// their decay pauses while the player is offline, so the queue lives in a
// campaign DB rather than a local int. Ensure the table exists before the
// first heal lands or the first login tries to restore a queue.
// See fat_inc.nss (roadmap: heal-soul-fatigue-rebalance).
FAT_InitDb();

// Dev-only NPCs. SP_DEV_TOOLS is generated from SEASON_ROLE by
// bin/season-profile.py - on for the dev and early-access realms, off for a
// live or archived season.
//
// The NPC is DESTROYED here rather than removed from thewelloferu.git.json,
// deliberately: dev and production share one source tree and bin/season-
// promote.sh copies dev's over production's on every release, so an area file
// that differed between them would be reverted by the next deploy. Behaviour
// that differs between environments has to be a runtime decision, not a
// content difference. (Same reason the Donations Chest cheat stock and the
// early-access login notice are flags now - see season-profile.py.)
//
// BUTCHA is "Ping Pong", the Ultimate PC Builder: set any level 1-60, hand out
// gold, destroy equipment. Its dialog _pc_builder_v1 must hold nothing
// player-facing for this to be safe to remove.
if (!SP_DEV_TOOLS)
{
    int nNth = 0;
    object oDev = GetObjectByTag("BUTCHA", nNth);
    while (GetIsObjectValid(oDev))
    {
        DestroyObject(oDev);
        // DestroyObject is deferred to the end of this script, so the NPC is
        // still enumerable and the index must advance past it.
        oDev = GetObjectByTag("BUTCHA", ++nNth);
    }
}

}   //end of main