Hello.
I did write this some time ago, for a friend.
He never used it -- We could not come to an agreement over how to perform some things.
Now it is sitting on my hard drive unused. Which is a pity.
So I share. If you have a use for this, help yourself.
In a nutshell, it is yet another way to award custom XP.
This one takes into account the difference in HD, and not the difference in CR.
It also does a couple other clever things. The comments should explain all.
[edit]This script is long. I must split it in 2 parts.
Both parts are to be merged back into one script.
REMEMBER: Syntax check for wrong cAsE of the word
class.
This forum has a habit to force it lowercase.
PART 1 of 2:
// +----------------+
// | FOX_INC_XP.nss |
// +----------------+
//
// Fox's Custom Experience Award.
//
// [fox - 01 Sep 2010]
// This is an alternate system to use in place of the BioWare standard one.
// Unlike other custom systems, this one takes into account the Hit Dice of the
// dead creature, instead of its Challenge Rating (CR).
// +-----------+
// | CONSTANTS |
// +-----------+
// Default amount of XP awarded per any kill.
// When no other setting is found, this very amount is picked as a last resort.
// But if you want to configure the system to award no XP at all (at some point,
// and for any reason you may have) then this amount is to be zeroed.
// But then it will be your responsibility to ensure that the system will return
// to award XP. Remember it.
// Look in Step #2 to see in which case this amount is queried.
const int nFOX_XP_BASEXP = 40;
// During the very first levels of a character, some classes are objectively
// much weaker than others. So as long as a weak character is of a level lesser
// than or equal to this amount, all the XP he earns is augmented by some %.
// This is an aid for such weak classes so they gain their skills in less time.
// Past this amount of character levels, no more aid is given.
const int nFOX_XP_EARLYLEVELS = 6;
// Name of the Local Variable to set on Creatures, Areas, or the Module, to
// override the default amount of XP awarded per any kill.
// Local Variable type is: int
// Local Variable amount: any positive non-zero.
const string sFox_XP_AWARD = "FOX_XP_AWARD";
// Do not mind this. For internal use only. Do not touch.
const string sFox_XP_PARTYMEMBERn = "FOX_XP_00";
// +------------+
// | PROTOTYPES |
// +------------+
void fox_XP_OnDeath ();
int fox_XP_XpToHd (int nXpAmount);
int fox_XP_HdToXp (int nHitDice);
float fox_XP_PenaltyForInbalance (object oPlayer, float fPerMemberXP, int nPartyAvgHD);
float fox_XP_EarlyLevelsHelp (object oPlayer);
float fox_XP_PenaltyForAssociates (object oPlayer);
// -----------------------------------------------------------------------------
// This is to be called from within the OnDeath event script handler of NPC.
// -----------------------------------------------------------------------------
void fox_XP_OnDeath ()
{
// Get a reference to the dead creature (that is, us).
object oDead = OBJECT_SELF;
//-------------------------------------------------------------------
// Step #1 : Check if this kill should award any XP at all.
// We like to honor the standard BioWare rules here:
// * Local fauna (animals) are worth no XP.
// * Commoners (paesants) are worth no XP.
// * Dead associates (summoned, et cetera) are worth no XP.
//-------------------------------------------------------------------
// If our primary class is Animal -> No XP. Quit.
if (GetclassByPosition (1, oDead) == class_TYPE_ANIMAL) return;
// If our primary class is Commoner -> No XP. Quit.
if (GetclassByPosition (1, oDead) == class_TYPE_COMMONER) return;
// If we are an Associate of any type -> No XP. Quit.
if (GetAssociateType (oDead) != ASSOCIATE_TYPE_NONE) return;
// <Insert more No_XP_Conditions here>
// (empty)
//-------------------------------------------------------------------------
// Step #2 : Retrieve the total XP to award for the kill of this creature.
// Our system allows for hyerarchycal overrides. The XP amount to
// award for this kill may be found in multiple places.
// It is a matter of priorities. The search scans all places in
// search of the first valid value with highest priority.
// Top priority is 1. Bottom priority is 4.
//
// Priority 1 = search on the very dead Creature.
// Priority 2 = search on the Area the Creature died in.
// Priority 3 = search on the Module.
// Priority 4 = Use the base default amount.
//
// We could go wild with this, and prepare a system with a ton of
// little caveats and rules to regulate with surgical precision
// the XP for killing who, when, where, why and how.
// But this script is run for _every_ creature that dies.
// Many creatures may die in a very short timeframe, know it.
// We do not want our system to weight on the CPU by repeatedly
// calling a large amount of instructions for each death.
//
// << Simplicity is Bliss >>
//
// -so sayeth the fox
//-------------------------------------------------------------------------
// Priority 1. Search the XP on the Creature.
int nXP = GetLocalInt (oDead, sFox_XP_AWARD);
if (nXP < 1)
{
// Priority 2. Search the XP on the Area the Creature died in.
nXP = GetLocalInt (GetArea (oDead), sFox_XP_AWARD);
if (nXP < 1)
{
// Priority 3. Search the XP on the Module.
nXP = GetLocalInt (GetModule (), sFox_XP_AWARD);
if (nXP < 1)
{
// Priority 4. Use the base default amount.
nXP = nFOX_XP_BASEXP;
// Chance for early exit. No XP to award? Quit.
// This may not be an error, after all. If the BaseXP is zeroed,
// then it is possible to set the system so that no kill will
// award any XP (for any reason a scripter may have).
if (nXP < 1) return;
}
}
}
//------------------------------------------------------------------------
// Step #3 : Make sure the killer is a valid Player or any Associate of a
// valid Player. Playership is checked with GetIsPC().
// GetIsPC() also incorporates a check for validity. So there is
// no need to explicit a call to GetIsObjectValid().
//------------------------------------------------------------------------
// Get a reference to the one who killed us (we = the dead creature).
object oKiller = GetLastKiller ();
// Is our killer a Trap object?
if (GetTrapBaseType (oKiller) != -1)
{
// Yes it is. Get the Trap Creator. Presume that to be our killer.
oKiller = GetTrapCreator (oKiller);
}
// Is our killer a Player, or any Associate of a Player?
if ( !GetIsPC (oKiller)
&& !GetIsPC (GetMaster (oKiller)))
{
// No and no. This creature has been killed by something which has
// nothing to do with a Player, not even remotely. Just quit.
return;
}
//----------------------------------------------------------------
// Step #4 : Find the Player most closely connected to the killer.
// Yes, the killer itself may be the Player we seek.
//----------------------------------------------------------------
// Assume that the Player we seek is the very killer.
object oPlayer = oKiller;
// Now make sure. If the Player we seek is not directly the killer...
if (!GetIsPC (oKiller))
{
// ... then it has to be his Master.
oPlayer = GetMaster (oKiller); // <-- (*)
///////////////////////////////////////////////////////////////////////
// (*) = If this was not the case, we would not be here in the first
// place. By virtue of the initial check for playership, down at
// this point the killer can only be a Player OR the Associate of
// a Player. There is no chance to mistake.
///////////////////////////////////////////////////////////////////////
}
//--------------------------------------------------------------------------
// Step #5 : Collect all Player Party Members of the killer. We build a list
// out of them. The list will only contain the killer (of course)
// and those Player party members that are found in the same Area
// of the killer and within 25 meters of him.
// Any Player excluded from this list shall not receive XP for
// this kill. Makes sense. You have to be reasonably close to the
// action to earn the right to claim your share of the XP.
//
// Note that every Player in game rapresents a Faction of his own.
// If multiple Players join into the same Party, then all those
// Players temporarily acquire the same Faction ID of the current
// Party Leader. No matter. What matters is that it is possible to
// cycle through all members of a specific Faction. We do not need
// to know the Faction ID. We only need a reference to any one
// member of said Faction.
//
// So yes: here "Party" and "Faction" have the same meaning.
// Cycling through the Faction Members of a Player is like cycling
// through the Party Members of that Player.
//
// NOTE:
// While at it, we also calculate some stuff about these Players.
// We consider the Party as a whole. We are interested in the
// average Character Level of the Party, calculated from the XP
// amount of the whole Party.
//--------------------------------------------------------------------------
int nPartyCount = 0;
int nPartyXP = 0;
object oArea = GetArea (oPlayer);
// Cycle through all Player Party Members of oPlayer.
object oMember = GetFirstFactionMember (oPlayer, TRUE);
while (GetIsObjectValid (oMember))
{
// Is this not a DM...?
// ... AND, is he Alive and Conscious (1 HP at least)?
// ... AND, is he in the same Area of oPlayer (the killer)?
// ... AND, is he within 25 meters of oPlayer?
if ( !GetIsDM (oMember)
&& (GetCurrentHitPoints (oMember) > 0)
&& (GetArea (oMember) == oArea)
&& (GetDistanceBetween (oMember, oPlayer) < 25.0f))
{
// Yes, and yes and yes and yes. Append him to the list.
// (the list is saved onto oPlayer)
SetLocalObject (oPlayer,
sFox_XP_PARTYMEMBERn + IntToString (nPartyCount++),
oMember);
// Get the XP amount of this Member, convert it to the closest HD
// level, then convert the HD back to XP. Doing so we have an exact
// "trimmed" amount. Finally, add this XP to the total of the Party.
nPartyXP += fox_XP_HdToXp (fox_XP_XpToHd (GetXP (oMember)));
}
oMember = GetNextFactionMember (oPlayer, TRUE);
}
// Calculate the average Character Level of the Party.
int nAvgHD = fox_XP_XpToHd (nPartyXP / nPartyCount);
//--------------------------------------------------------------------------
// Step #6 : Now we have all we need. Time to award XP to the killer. If the
// killer is in a Party, the XP is shared among the Party Members
// we saved in the Step #5 list.
//
// Not everyone may receive the same amount of XP. There is a nice
// reward for those able to slay that which is so much stronger
// than them. However, courage and insanity go hand in hand, and
// the line between them is thin.
// If you fight and slay that which is way too much stronger than
// you, do not expect an adeguate reward. Maybe the next time you
// will lower your aim and avoid to needlessly risk your neck.
//
// Although we ignore the CR of the characters involved, we want
// to base our rule on something derived from it.
// According to the NWN online manual, 7 difficulty levels exist:
//
// Impossible = foe is 5 or more levels above your.
// Overpowering = foe is 3 or 4 levels above your.
// Very Dificult = foe is 1 or 2 levels above your.
// Challenging = foe is your same level, or 1 level below your.
// Moderate = foe is 2 or 3 levels below your.
// Easy = foe is 4 or 5 levels below your.
// Effortless = foe is 6 or more levels below your.
//
// We want the XP to quickly scale down (or up) on a squared log10
// scale. Like this:
//
// 200% xp = foe is 6, or more, levels above your.
// ~184% xp = foe is 5 levels above your.
// ~163% xp = foe is 4 levels above your.
// ~145% xp = foe is 3 levels above your.
// ~128% xp = foe is 2 levels above your.
// ~113% xp = foe is 1 level above your.
// 100% xp = foe is your same level.
// ~86% xp = foe is 1 level below your.
// ~71% xp = foe is 2 levels below your.
// ~54% xp = foe is 3 levels below your.
// ~36% xp = foe is 4 levels below your.
// ~15% xp = foe is 5 levels below your.
// 0% xp = foe is 6, or more, levels below your.
//
// For example, if the base XP to award for a kill is 40 points,
// the total to award to a single Player goes like this:
//
// 80 xp = if the foe is 6, or more, levels above your.
// 73 xp = if the foe is 5 levels above your.
// 65 xp = if the foe is 4 levels above your.
// 58 xp = if the foe is 3 levels above your.
// 51 xp = if the foe is 2 levels above your.
// 45 xp = if the foe is 1 level above your.
// 40 xp = if the foe is your same level.
// 34 xp = if the foe is 1 level below your.
// 28 xp = if the foe is 2 levels below your.
// 21 xp = if the foe is 3 levels below your.
// 14 xp = if the foe is 4 levels below your.
// 6 xp = if the foe is 5 levels below your.
// 0 xp = if the foe is 6, or more, levels below your.
//--------------------------------------------------------------------------
///////////////////////////////////////////////////////////////////////////
// NOTE : To help the performance, we prefer to work using the decimal
// datatype: float. In so doing we cut on a number of conversions to
// and from the integer datatype all over the script.
// We shall convert to integer once we have the final XP to award.
///////////////////////////////////////////////////////////////////////////
// Get the HD of the dead creature, sanity capped in range [1 -- 40].
int nDeadHD = GetHitDice (oDead);
if (nDeadHD > 40) nDeadHD = 40;
// Now pick the correct XP scalar, based on the difference in HD between the
// dead creature and the average HD of the Party.
float fScalar;
int nDeltaHD = nAvgHD - nDeadHD;
if (nDeltaHD <= -6) fScalar = 2.0f; // 200% XP cap limit.
else if (nDeltaHD >= 6) fScalar = 0.0f; // 0% XP cap limit.
else
{
//////////////////////////////////////////////////////////////////////
// We here?
// The HD delta between the dead creature and the Party Average varies
// from +5 to -5, passing through 0.
// Both ends of the scale (+positive and -negative) are calculated in
// the same way. Only, the negative end is the mirror of the positive
// end. A delta of 0 is the pivot of the scale, wherein the XP scalar
// becomes 1 (for 100%).
// It takes more to explain than to calculate.
//
// We start by calculating the scalar assuming that the HD delta is on
// the +positive side of the scale.
// Why? Because the most likely case is: the Party killed a weakling.
// In fact the scalar we obtain with this is in range [1.0 -- 0.0].
// It will *decrease* the XP to award.
//////////////////////////////////////////////////////////////////////
// Calculate the log10 for the positive HD delta, then square it.
float fLog = log (10.0f - 1.5f * IntToFloat (abs (nDeltaHD)));
fLog *= fLog;
////////////////////////////////////////////////////////////////////////
// BUT...!
// If the dead creature is stronger than the Party Average, the delta is
// negative. And for negative deltas we mirror the log value onto the
// -negative side of the scale.
// That is: the scalar begins to *increase* on a squared log10 scale.
////////////////////////////////////////////////////////////////////////
// As simple as this:
if (nDeltaHD < 0) fLog = 1.0f - fLog + 1.0f;
// Whatever value we have in fLog is the scalar we are after.
fScalar = fLog;
////////////////////////////////////////////////////////////////////////
// Confused over what we have done?
// Look here. This pseudo-switch is the closest translation of the above
// procedure:
//
// switch (nAvgHD - nDeadHD)
// {
// [OVER] -6: fScalar is capped at 2.0
// case -6: fScalar = 2.0f; break; // 1 - ((log 1.0) ^ 2) + 1
// case -5: fScalar = 1.84164f; break; // 1 - ((log 2.5) ^ 2) + 1
// case -4: fScalar = 1.63752f; break; // 1 - ((log 4.0) ^ 2) + 1
// case -3: fScalar = 1.45186f; break; // 1 - ((log 5.5) ^ 2) + 1
// case -2: fScalar = 1.28581f; break; // 1 - ((log 7.0) ^ 2) + 1
// case -1: fScalar = 1.13618f; break; // 1 - ((log 8.5) ^ 2) + 1
// case 0: fScalar = 1.0f; break; // (log 10.0) ^ 2
// case 1: fScalar = 0.86382f; break; // (log 8.5) ^ 2
// case 2: fScalar = 0.71419f; break; // (log 7.0) ^ 2
// case 3: fScalar = 0.54814f; break; // (log 5.5) ^ 2
// case 4: fScalar = 0.36248f; break; // (log 4.0) ^ 2
// case 5: fScalar = 0.15836f; break; // (log 2.5) ^ 2
// case 6: fScalar = 0.0f; break; // (log 1.0) ^ 2
// [OVER] +6: fScalar is capped at 0.0
// }
//
// -fox
////////////////////////////////////////////////////////////////////////
}
// Chance for early exit. If the picked scalar is 0.0f, the final XP can
// only be 0, right? In which case we just quit because there is no XP to
// award to anybody.
if (fScalar == 0.0f) return;
// Calculate the XP normally awarded to each Player in the Party.
float fPerMemberXP = IntToFloat (nXP);
for (i = 1; i < nPartyCount; ++i)
{
// Each Player Party Member past the 1st, causes a 10% XP loss.
// This is a "diminishing return" formula. The PerMemberXP shall reduce
// and approach to 0, but will never reach it.
fPerMemberXP *= 0.9f;
}
// Scale down (or up) the XP for each Party Member.
fPerMemberXP *= fScalar;
// Cycle through all Party Members.
for (i = 0; i < nPartyCount; ++i)
{
// Get (then delete) a reference to the Party Member in exam.
string sKey = sFox_XP_PARTYMEMBERn + IntToString (i);
object oMember = GetLocalObject (oPlayer, sKey);
DeleteLocalObject (oPlayer, sKey);
////////////////////////////////////////////////////////////////////
// At this point each Party Member may see *his own* share of the XP
// reduced or augmented for a number of reasons.
////////////////////////////////////////////////////////////////////
// PARTY MEMBER PENALTY:
// The farther is your HD from the Party Average, the less XP you earn.
// The closer is your HD to the Party Average, the more XP you earn.
// If your HD matches the Party Average, you retain 100% XP.
float fMemberXP = fox_XP_PenaltyForInbalance (oMember, fPerMemberXP, nAvgHD);
// PARTY MEMBER BONUS:
// Account for Early Levels Help. Not all Player classes can handle
// combat with the same efficiency during their first levels.
fMemberXP *= fox_XP_EarlyLevelsHelp (oMember);
// PARTY MEMBER PENALTY:
// Account for penalty for having any Associate with you.
// The more help you get, the less XP you earn.
fMemberXP *= fox_XP_PenaltyForAssociates (oMember);
// Now convert the final XP to integer.
int nMemberXP = FloatToInt (fMemberXP);
// Sanity check. Do nothing if the final XP turns out to be 0.
if (nMemberXP)
{
////////////////////////////////////
// Here. XP for you. Use it well.
// --------------------------------------------------------
// NOTE : We use SetXP() instead of GiveXPToCreature().
// The latter function automatically accounts for XP
// penalties due to "improper" multiclassing.
// We always thought that penalty to be _retarded_.
// --------------------------------------------------------
SetXP (oMember, GetXP (oMember) + nMemberXP);
}
}
}
(continues on Part 2...)
Modifié par the.gray.fox, 20 mai 2011 - 04:39 .