495 lines
20 KiB
Plaintext
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
|
|
// ============================================================================
|