HiddenTradition_PRC8/_module/nss/no_c2_default7.nss
2024-06-20 15:47:42 -04:00

495 lines
20 KiB
Plaintext

///////////////////////////////////////////////////////////////////////////////
// no_c2_default7
///////////////////////////////////////////////////////////////////////////////
//
// DMG experience rewards modification
/*
ScrewTape's script mimicking the XP Rewards from pp 36-39, DMG v3.5
- base awards use Bioware's xptable.2da but are applied using DMG rules
*/
//:://////////////////////////////////////////////////
//:: Modifier: ScrewTape
//:: Original Modification: 02/19/04
//:: Latest Revision: 05/19/04
//:://////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// *****INSTRUCTIONS*****
// This script encapsulates all of the DMG 2DA XP functionality in a
// script that can be called from a custom OnDeath script by adding a call,
// ExecuteScript("no_c2_default7", OBJECT_SELF); If your script already has a
// return for oKiller == OBJECT_SELF, then
// ExecuteScript("no_c2_default7", OBJECT_SELF);
// is all you need to add (like Jassper's AI).
// For any other custom OnDeath scripts that DO NOT have the return on
// OBJECT_SELF, after object oKiller = GetLastKiller();,
// add (without the /* */)
/*
// ============================================================================
// Start of modified behavior
// Per discussions with Sotae, we don't want to run this script the second
// time we get here (since we killed ourself a second time to bypass
// Bioware's XP system).
if (oKiller == OBJECT_SELF)
{
return;
}
// End of modified behavior
// ============================================================================
*/
// There is also a trap exploit fix in OnDeath that you may want.
/*
// ============================================================================
// Start of modified behavior
// penalize trap creators for killing commoners
// Thanks to *** arQon *** for this idea
// Update - modified so any associate of the trap creator will get the
// alignment shift
if (GetIsObjectValid(GetTrapCreator(oKiller)))
AdjustAlignment(GetTrapCreator(oKiller), ALIGNMENT_EVIL, 5);
// End of modified behavior
// ============================================================================
*/
// And towards the end (any where after early returns is ok, but your custom
// OnDeath may recommend the proper place) add
/*
// ============================================================================
// Start of modified behavior
ExecuteScript("no_c2_default7", OBJECT_SELF);
// End of modified behavior
// ============================================================================
*/
// All three modifications are preceded by
// ============================================================================
// Start of modified behavior
// [modified code]
// and followed by
// End of modified behavior
// ============================================================================
// just to make it obvious what is Bioware code and what is my (ScrewTape's)
// modifications
// below is an example of Bioware's OnDeath using
// ExecuteScript("no_c2_default7", OBJECT_SELF); to award XP
/*
// ============================================================================
// NW_C2_DEFAULT7 default behavior
#include "x2_inc_compon"
#include "x0_i0_spawncond"
void main()
{
int nClass = GetLevelByClass(CLASS_TYPE_COMMONER);
int nAlign = GetAlignmentGoodEvil(OBJECT_SELF);
object oKiller = GetLastKiller();
// ============================================================================
// Start of modified behavior
// Per discussions with Sotae, we don't want to run this script the second
// time we get here (since we killed ourself a second time to bypass
// Bioware's XP system).
if (oKiller == OBJECT_SELF)
{
return;
}
// End of modified behavior
// ============================================================================
// If we're a good/neutral commoner,
// adjust the killer's alignment evil
if(nClass > 0 && (nAlign == ALIGNMENT_GOOD || nAlign == ALIGNMENT_NEUTRAL))
{
AdjustAlignment(oKiller, ALIGNMENT_EVIL, 5);
// ============================================================================
// Start of modified behavior
// penalize trap creators for killing commoners
// Thanks to *** arQon *** for this idea
// Update - modified so any associate of the trap creator will get the
// alignment shift
if (GetIsObjectValid(GetTrapCreator(oKiller)))
AdjustAlignment(GetTrapCreator(oKiller), ALIGNMENT_EVIL, 5);
// End of modified behavior
// ============================================================================
}
// Call to allies to let them know we're dead
SpeakString("NW_I_AM_DEAD", TALKVOLUME_SILENT_TALK);
//Shout Attack my target, only works with the On Spawn In setup
SpeakString("NW_ATTACK_MY_TARGET", TALKVOLUME_SILENT_TALK);
// NOTE: the OnDeath user-defined event does not
// trigger reliably and should probably be removed
if(GetSpawnInCondition(NW_FLAG_DEATH_EVENT))
{
SignalEvent(OBJECT_SELF, EventUserDefined(1007));
}
craft_drop_items(oKiller);
// ============================================================================
// Start of modified behavior
ExecuteScript("no_c2_default7", OBJECT_SELF);
// End of modified behavior
// ============================================================================
}
// end of NW_C2_DEFAULT7 default behavior
*/
///////////////////////////////////////////////////////////////////////////////
// no_c2_default7
///////////////////////////////////////////////////////////////////////////////
//
// DMG experience rewards modification
/*
ScrewTape's script mimicking the XP Rewards from pp 36-39, DMG v3.5
- base awards use Bioware's xptable.2da but are applied using DMG rules
*/
//:://////////////////////////////////////////////////
//:: Modifier: ScrewTape
//:: Original Modification: 02/19/04
//:: Latest Revision: 05/19/04
//:://////////////////////////////////////////////////
// ============================================================================
// Start of modified behavior
// Below are the tweaking modifiers, used to adjust the amount of XP awarded.
// Recommended N_MODULE_SLIDER settings are 17 for 1st level modules, 10-15
// for 2nd-5th, and 7-10 for higher level modules. That should keep you in the
// ballpark of the Bioware awards. NOTE: the actual module slider has
// NO EFFECT on these awards and consequently does NOT need to be set to zero.
// (Setting is irrelevant - see below).
// B_BYPASS_BIOWARE_XP -
// It is not neccessary to set the module's XP slider to zero. I incorporated
// an idea by TethyrDarknight to bypass both the slider and the double
// message for xp. Note: you won't be able to compare this system with the
// default Bioware awards with this implemented. If you want to do that,
// set both module sliders the same (the variable below and the actual module
// slider) and comment out the next line of code and uncomment the one
// following it.
const int B_BYPASS_BIOWARE_XP = TRUE; // recommended setting
// const int B_BYPASS_BIOWARE_XP = FALSE; // for comparison
// N_MODULE_SLIDER -
// Use this just as you would use a module's xp slider
// set to 10 for normal xp - when using 2da's this will be exactly the
// same, barring other considerations like henchmen, multiplayer, multiclass,
// etc. If your using the DMG tables, 10 equates to 10%, which will be
// slightly higher than the Bioware awards. 100 will match the DMG tables.
const int N_MODULE_SLIDER = 10;
// B_USE_MULTICLASS_PENALTY -
// This is a boolean (set to TRUE (1) or FALSE (O))
// to decide whether or not to enforce Bioware's multi class penalty.
// Idea taken from Bioware scripting forum post by Ima Dufus
// FALSE would be like the DMG
const int B_USE_MULTICLASS_PENALTY = FALSE; // DMG accurate setting
// const int B_USE_MULTICLASS_PENALTY = TRUE; // alternate setting
// B_COUNT_ASSOCIATES -
// Per Trickster's request, I included an option to count the players' other
// associates, summoned/familiar/companion/dominated. In doing so, I realized
// I wasn't adhering to the DMG's notes regarding "creatures that enemies
// summon or otherwise add to their forces with magic powers. An ememy's
// ability to summon or add these creatures is a part of the enemy's CR
// already." pp 37, just below the steps to determine the XP award. So I
// included that in the option. These two options seemed to be tied together,
// that is, if I count the party's associate types when determining total
// party members to divide experience by, I should also award XP for enemies'
// associates and on the other hand, if I don't count the party's associates
// (except henchmen), I shouldn't award XP for enemies' associates.
const int B_COUNT_ASSOCIATES = FALSE; // DMG Accurate
// const int B_COUNT_ASSOCIATES = TRUE; // alternate setting
// B_COUNT_HENCHMEN -
// And why not allow the same option for henchmen
const int B_COUNT_HENCHMEN = TRUE; // recommended setting
// const int B_COUNT_HENCHMEN = FALSE; // alternate setting
// B_AWARD_DEAD_PLAYERS
// Allow dead/dying players to receive awards (per DMG, pp 41 'DEATH AND
// EXPERIENCE POINTS') but leave the option to disable.
// const int B_AWARD_DEAD_PLAYERS = TRUE; // DMG accurate setting
const int B_AWARD_DEAD_PLAYERS = FALSE; // alternate setting
///////////////////////////////////////////////////////////////////////////////
// GetXPFrom2DATable, inputs: level and CR, outputs: experience points
int GetXPFrom2DATable(int nLevel, float fCR);
///////////////////////////////////////////////////////////////////////////////
// IsPlayerEligible, input: player object, output: TRUE or FALSE
int IsPlayerEligible(object oPlayer)
{
if (B_AWARD_DEAD_PLAYERS)
return GetArea(GetObjectByTag("NW_DEATH_TEMPLE")) == GetArea(oPlayer) ||
GetDistanceBetween(OBJECT_SELF, oPlayer) < 50.0f;
return GetArea(OBJECT_SELF) == GetArea(oPlayer) &&
GetIsInCombat(oPlayer) && GetCurrentHitPoints(oPlayer) > 0;
}
///////////////////////////////////////////////////////////////////////////////
// GetHitDiceByXP
// Solve for hd from xp = hd * (hd - 1) * 500
// hd = 1/50 * (sqrt(5) * sqrt(xp + 125) + 25)
int GetHitDiceByXP(float fXP)
{
return FloatToInt(0.02 * (sqrt(5.0f) * sqrt(fXP + 125.0f) + 25.0f));
}
///////////////////////////////////////////////////////////////////////////////
// no_c2_default7, inputs: the killing object, outputs: none (returned)
void main()
{
// We'll start by declaring (and sometimes getting) our variables
// OBJECT_SELF is initialized by the call to ExecuteScript
object oKiller = GetLastKiller();
int nPartyMembers = 0;
int nIndex = 0;
int nLevel = 0;
int nXP = 0;
int nHoldXP = 0;
int nAssociateType = 0;
float fTotalXP = 0.0f;
float fScale = 1.0f;
float fCR = GetChallengeRating(OBJECT_SELF);
object oMaster = GetMaster(oKiller);
object oMember;
// Update 5/19/04 - make sure we check the actual master in case we have a
// henchman or other associate who also has an associate (for example,
// henchman is a wizard and has a familiar or has summoned a creature).
// Update 07/12/04 thanks to ***RodneyOrpheus*** fixed nasty bug here
while (GetIsObjectValid(oMaster))
{
oKiller = oMaster;
oMaster = GetMaster(oKiller);
}
// This gets rid of the double experience awards (will also override
// module's XP slider, so it doesn't need to be set to zero)
// Thanks to *** TethyrDarknight *** for this idea
if (B_BYPASS_BIOWARE_XP)
{
if (GetIsPC(oKiller) || GetIsPC(GetTrapCreator(oKiller)))
{
ApplyEffectToObject(DURATION_TYPE_INSTANT,
EffectResurrection(), OBJECT_SELF);
ApplyEffectToObject(DURATION_TYPE_INSTANT,
EffectDamage((GetCurrentHitPoints() * 2)), OBJECT_SELF);
}
}
// Sanity check - should only happen if creature is invalid
// No point in calculating any more if CR is less than or = to 0
// or if the script's module slider is set to 0
if (fCR <= 0.0 || N_MODULE_SLIDER == 0)
return;
// Before we start calculating XP, let's check to see if we are a controlled
// type* of someone else. * summoned/familiar/companion/dominated
// Update 8/5/04 - thanks ***Belial Prime*** - we were hitting this check on
// DM controlled enemies, where we should still be awarding xp.
// Used GetAssociateType instead of GetMaster.
if (B_COUNT_ASSOCIATES == FALSE &&
GetAssociateType(OBJECT_SELF) != ASSOCIATE_TYPE_NONE)
return;
// This will also ensure we award XP for traps.
// Thanks to *** Sotae *** for this idea
if (GetIsObjectValid(GetTrapCreator(oKiller)))
oKiller = GetTrapCreator(oKiller);
// First let's determine the number of members in the party. The values of
// B_COUNT_HENCHMEN and B_COUNT_ASSOCIATES will determine whether or not
// we count henchman and other associates when determining the total number
// of party members to divide the Total XP award by. Although the DMG
// doesn't specify for players, the DMG does specify for that for enemies,
// we don't count xp separately for their creatures added to their forces.
// Since henchman can play such a big role, I recommend counting them.
// Note: I don't award XP to associates, I just use them in determining
// the factor to divide the total XP if specified.
oMember = GetFirstFactionMember(oKiller); // this only returns PC's
while (GetIsObjectValid(oMember))
{
// sanity check - first let's see if the PC member is in the same area as
// the creature just killed and if the member was in combat. This way,
// if the member's in the area, but not in combat - we won't include
// him. It should be noted, there's a delay after an enemy is killed and
// before a PC is no longer in combat. (Try resting immediately after
// you've killed an enemy, and also note the music). This delay is
// sufficient for us to count XP. Handy! Note: this delay does NOT occur
// if the player is dead, so dead members will NOT be awarded XP.
if (IsPlayerEligible(oMember))
{
// add the PC him/herself
nPartyMembers++;
// We'll treat henchmen separately from the other associates, as,
// since version 1.59, you can have more than one henchman.
if (B_COUNT_HENCHMEN)
{
// Loop through the available henchmen and see if he's got 'em
// Update 08/05/04 - fixed off by one error in the comparison
for (nIndex = 1; nIndex <= GetMaxHenchmen(); nIndex++)
{
if (GetIsObjectValid(GetHenchman(oMember, nIndex)))
nPartyMembers++; // increment by number of henchmen
}
}
// According to the lexicon, henchman is the only associate type
// that the PC's can have more than one of, so we'll make a simple
// check once for each type. I looked in the nwscript.nss file, and
// the associate types range from 0 - 5 (where 0 is none, 1 is
// henchmen and 2 - 5 are the one's we are interested in)
if (B_COUNT_ASSOCIATES)
{
// loop through each associate type, not including henchmen
for (nAssociateType = ASSOCIATE_TYPE_ANIMALCOMPANION;
nAssociateType <= ASSOCIATE_TYPE_DOMINATED; nAssociateType++)
{
if (GetIsObjectValid(GetAssociate(nAssociateType, oMember)))
nPartyMembers++; // increment by number of associates
}
}
} // end if (IsPlayerEligible(oMember))
// get the next guy in the PC's party
oMember = GetNextFactionMember(oMember);
} // end while (GetIsObjectValid(oMember))
// Sanity check - this shouldn't happen, but in case it does...
// this will also prevent a divide by zero below
if (nPartyMembers < 1)
return;
// According to the DMG, there are 6 steps. The 6th step is a loop, so
// we'll start with that. (why didn't we start yet - because we needed to
// know the total party members first)
oMember = GetFirstFactionMember(oKiller);
while (GetIsObjectValid(oMember))
{
// same check as before
if (IsPlayerEligible(oMember))
{
// Step 1. determine character level
// Update 08/05/04 - thanks ***Belial Prime*** We want to get level
// by xp, to discourage pcs from waiting to level
nLevel = GetHitDiceByXP(IntToFloat(GetXP(oMember)));
// Step 2. get the monster's fCR
// fCR = GetChallengeRating(OBJECT_SELF); // we've already done this
// Step 3. consult the table and adjust using N_MODULE_SLIDER
// Update 08/05/04 - go back to using rounding instead of truncation
fScale = IntToFloat(N_MODULE_SLIDER) / 10.0f;
fTotalXP = IntToFloat(GetXPFrom2DATable(nLevel, fCR)) * fScale;
// Step 4. divide the XP by the number of party members
// we did the sanity check above, so we can't get DIV/0
nXP = FloatToInt((fTotalXP / IntToFloat(nPartyMembers)) + 0.5f);
// Step 5 is taken care of by calling this script when each
// monster's OnDeath script gets called (this one)
// Award the XP to the PC - always award at least 1 xp so we know the
// awards are working
if (nXP <= 0)
nXP = 1;
// use the standard bioware penalty for multi-class characters
if (B_USE_MULTICLASS_PENALTY)
GiveXPToCreature(oMember, nXP);
// or use get and set to bypass the multi-class penalty
// Thanks to *** Ima Dufus *** for this idea
else
{
// we'll get the current xp
nHoldXP = GetXP(oMember);
// add them to our newly calculated xp
nXP += nHoldXP;
// and re-assign the total,
// thus bypassing the multi-class penalty
SetXP(oMember, nXP);
}
} // end if (IsPlayerEligible(oMember))
// Step 6. get the next guy
oMember = GetNextFactionMember(oMember);
} // end while (GetIsObjectValid(oMember))
} // end DMGRewardXP
///////////////////////////////////////////////////////////////////////////////
// GetXPFrom2DATable, inputs: level and CR, outputs: experience points
int GetXPFrom2DATable(int nLevel, float fCR)
{
// The 2da table goes from CR C0 to C40 and from level 0 - level 39
// (off by one). Reading from the 2da table in a loop is bad because it is
// slow - so what this function does is check a local int on the module
// For this to work properly, the 2da read in OnModuleLoad must be
// successful. In multiplayer, even with less than 8 enemies, with 4
// players, doing the reads here was entirely unreliable. In single player,
// I was able to get it to choke with mass kills (wilting, even greater
// cleave). Reading the entire table in OnModuleLoad has presented NO
// errors in more than 25 hours of testing with various modules in
// multiplayer and single player.
int nCR = FloatToInt(fCR);
int nXP = 0;
// Update 08/07/04 thanks to ***RodneyOrpheus***
// make sure we hanlde CR > 40
if (nCR > 40)
{
// The pattern from level 27 through 32 looks like this
// 400 425 450 475 500 525 550 575 600
// this simply extends it out to level 40, based on CR - HD
// once it's over 8, everyone will receive 600
nXP = 400 + (25 * (nCR - nLevel));
if (nXP > 600)
nXP = 600;
}
// otherwise, use the ints stored read in from the 2da table during
// OnModuleLoad
else
{
// This is the string name we're going to use to retrieve each entry stored,
// for example, 2DA_XP_10v10 is where we'd store level 10 vs CR 10
string sXpPerLvl = "2DA_XP_" + IntToString(nLevel) + "v" + IntToString(nCR);
nXP = GetLocalInt(GetModule(), sXpPerLvl);
}
// We should only ever get an error if the reads were unsuccessful in
// OnModuleLoad. Spam warning message around.
if (nXP <= 0 || nXP > 600)
{
string sError = "WARNING! Error during xp read - check xptable.2da reads"
+ " in OnModuleLoad.";
SendMessageToPC(GetFirstPC(), sError);
SendMessageToAllDMs(sError);
PrintString(sError);
}
// otherwise, return the xp
return nXP;
}
// End of no_c2_default7
// ============================================================================