368 lines
14 KiB
Plaintext
368 lines
14 KiB
Plaintext
//::///////////////////////////////////////////////
|
|
//:: XP Distribution Script
|
|
//:: pwfxp v1.2
|
|
//:: Copyright (c) 2001 Bioware Corp.
|
|
//:://////////////////////////////////////////////
|
|
/*
|
|
|
|
This is a more sophisticated XP distribution script geared towards PWs.
|
|
It comes with more then a dozen XP modifiers to fine tune XP output and
|
|
is losely based on the old but solid TTV XP script.
|
|
|
|
here is a small example of features, all modifiers are scalable:
|
|
|
|
- PCs suffer XP reduction if their level is not close enough to the average
|
|
party level. level 1 grouping with level 10 is probably not a good idea...
|
|
|
|
- PCs suffer XP reduction if their level is not close enough to the CR of the killed MOB
|
|
(both directions, if a PC kills a mob much stronger then himself = xp reduction, and vice versa)
|
|
|
|
- Adjustable ECL modifiers, easy to sync with any subrace scripts
|
|
|
|
- Group bonus. groups receive a small XP bonus (or big, if you wish) to encourage teamplay
|
|
|
|
- Groupmembers need to be within minimum distance to the killed MOB if they want to receive XP
|
|
|
|
- many more, see the constants...
|
|
|
|
- easy to add new modifiers..
|
|
|
|
all in all, this is pushing the nwn XP system more close to what you get in a MMORPG. You can
|
|
make it very hard to level or easy as hell, with good control of group impact and flexible
|
|
boundaries.
|
|
|
|
system went through extensive beta tests at www.thefold.org - thanks to all the great
|
|
players and staff there...
|
|
|
|
--- USAGE --- --- USAGE --- --- USAGE --- --- USAGE --- --- USAGE ---
|
|
|
|
just add the following line to the onDeath script of your creatures (default: nw_c2_default7):
|
|
|
|
ExecuteScript("tab_xpscript",OBJECT_SELF);
|
|
|
|
Don't forget to set the XP-Scale slider to 0 (module properties)
|
|
|
|
---------------------------------------------------------------------
|
|
|
|
changelog:
|
|
|
|
v1.2 update (10/2003)
|
|
|
|
- killer gets excluded from distance check now if he *is* a PC.
|
|
he gets XP even if his spell kills something far away (e.g. long range spells,
|
|
damage over time spells. maybe traps, not tested so far. this does not include NPCs)
|
|
every other groupmember gets still checked for distance...
|
|
[thanks to telstar for the report/request...]
|
|
|
|
v1.1 initial full release (10/2003)
|
|
|
|
- fine tuned and slightly optimized code
|
|
- added debug toggle
|
|
|
|
v1.0 beta (8/2003):
|
|
|
|
- distance check should now work correctly
|
|
|
|
- minimum XP award (see new PWFXP_MINIMUM_XP constant)
|
|
|
|
- henchman, familiars, animal companions, summoned creatures and other NPCs in a player
|
|
group now take away XP. see PWFXP_XP_DIVISOR_PC and PWFXP_XP_DIVISOR_NPC constants
|
|
|
|
- made it easier to manage ECL modifiers. see PWFXP_ECL_MODIFIERS string constant
|
|
|
|
*/
|
|
//:://////////////////////////////////////////////
|
|
//:: Created By: LasCivious & Knat
|
|
//:: Created On: 7/2003
|
|
//:://////////////////////////////////////////////
|
|
|
|
|
|
// this will modify global xp output
|
|
const float PWFXP_GLOBAL_MODIFIER = 20.0;
|
|
|
|
// displays one-line XP status info after each kill
|
|
// useful while you fine tune the system.
|
|
const int PWFXP_DEBUG = FALSE;
|
|
|
|
// this is where you apply your subrace ECL modifiers
|
|
// add them to the constant in this form "(ECL Modifier)-(Subrace)|...next|...etc"
|
|
const string PWFXP_ECL_MODIFIERS = "1-SHADOWLEGION|1-SHOGUNWARRIOR";
|
|
|
|
//TODO:
|
|
// you can add a modifier to change XP output for every single level (and future levels, like epic)
|
|
// this also enables you to break the linear nature of NWNs default XP output
|
|
//
|
|
// you can make the first few levels easy but make the last a pain to reach.... very flexible now
|
|
//
|
|
// the default setting is as follow:
|
|
//
|
|
// level 1 = 200% xp bonus (triples the xp)
|
|
// level 2 = 150% xp bonus
|
|
// level 3 = 100% xp bonus
|
|
// level 4 = 50% xp bonus
|
|
// add them to the constant in this form "LVL(level)-modifier|...next|...etc"
|
|
const string PWFXP_LEVEL_MODIFIERS = "LVL1-2.0|LVL2-2.0|LVL3-3.0|LVL4-5.0|LVL5-5.0|LVL6-3.0|LVL7-2.0|LVL8-1.9|LVL9-1.9|LVL10-1.8|LVL11-1.5|LVL12-1.0|LVL13-.5|LVL14-.001|LVL15-.001|LVL16-.001|LVL17-.001|LVL18-.001|LVL19-.001|LVL20-.001|LVL21-.001|LVL22-.001|LVL23-.001|LVL24-.001|LVL25-.001|LVL26-.001|LVL27-.001|LVL28-.001|LVL29-.001|LVL30-.001|LVL31-.001|LVL32-.001|LVL33-.001|LVL34-.001|LVL35-.001|LVL36-.001|LVL37-.001|LVL38-.001|LVL39-.001|LVL40-.001";
|
|
|
|
// small bonus for killing blow dealer
|
|
const float PWFXP_KILLINGBLOW_MODIFIER = 0.01; // 10%
|
|
|
|
// PC level gets compared to the average party level.
|
|
// APL = Average Party Level
|
|
//
|
|
// XP gets reduced if PC-level > APL + APL_REDUCTION
|
|
// XP reduction is based on SCALAR, be careful if you change this
|
|
// right now its 0 - 50% (scalar 0.5) for delta 2 (APL_REDUCTION) .. delta 4 (APL_NOXP)
|
|
// delta = abs(APL - PC Level)
|
|
// this means it will switch from 50% reduction to 100% reduction in one leap in case the PC level
|
|
// is greater then APL + APL_NOXP.
|
|
// i did this for a better granularity for the given default values but
|
|
// you can freely change APL_REDUCTION and/or APL_NOXP. XP reduction gets auto-adjusted to the maximum
|
|
// of SCALAR (50% default). if you want a more linear reduction, change scalar to 1
|
|
//
|
|
// XP gets reduced to zero if PC-level > APL + APL_NOXP
|
|
//
|
|
//
|
|
// Example (using default values):
|
|
// PC A = level 7
|
|
// PC B = level 3
|
|
// PC C = level 1
|
|
//
|
|
// average party level (APL) = 3.66
|
|
//
|
|
// Distance PC A = abs(PClevel - AveragePartyLevel) = abs(7 - 3.66) = 3.34
|
|
// PC-A has a final distance of 1.34 (3.34 - APL_REDUCTION)
|
|
// XP reduction = (SCALAR / (APL_NOXP - APL_REDUCTION)) * 1.34 = (0.5 / 2) * 1.34 = 33.5% XP reduction
|
|
//
|
|
// Distance PC B = abs(PClevel - AveragePartyLevel) = abs(3 - 3.66) = 0.66
|
|
// PC-A has a final distance of -1.34 (0.66 - APL_REDUCTION)
|
|
// no XP reduction
|
|
//
|
|
// Distance PC C = abs(PClevel - AveragePartyLevel) = abs(1 - 3.66) = 2.66
|
|
// PC-A has a final distanceof 0.66 (2.66 - APL_REDUCTION)
|
|
// XP reduction = (SCALAR / (APL_NOXP - APL_REDUCTION)) * 0.66 = (0.5 / 2) * 0.66 = 16.5% XP reduction
|
|
//
|
|
// those PCs with the biggest impact to the average party level receive the biggest XP reduction
|
|
// (in the above case PC A)
|
|
//
|
|
// set both to 20 if you don't want any apl_reduction
|
|
//
|
|
// example uses below values
|
|
// const float PWFXP_APL_REDUCTION = 2.0;
|
|
// const float PWFXP_APL_NOXP = 4.0;
|
|
// XP gets reduced to zero if PC-level > APL + APL_NOXP
|
|
// changed default to a bit less harsh values
|
|
const float PWFXP_APL_REDUCTION = 2.0; // levels
|
|
const float PWFXP_APL_NOXP = 4.0;
|
|
|
|
// this works like the APL constants above but it compares
|
|
// PC level vs challenge rating of the dead creature
|
|
// PCs get XP reduction if their level distance is greater then dead creature CR + PWFXP_CR_REDUCTION
|
|
// math is the same as the example above, just exchange
|
|
// AveragePartyLevel with CR of dead creature and use
|
|
// CR_REDUCTION and CR_NOXP as the constants
|
|
// CR of Dead > CR_MAX + CR_REDUCTION
|
|
// set both to PWFXP_CR_MAX if you dont want any cr_reduction
|
|
const float PWFXP_CR_REDUCTION = 5.0; // levels
|
|
const float PWFXP_CR_NOXP = 15.0;
|
|
|
|
// described above
|
|
const float PWFXP_SCALAR = 0.5;
|
|
|
|
// maximum CR cap
|
|
// this stops creatures with sky-high CRs from giving godly XP
|
|
const float PWFXP_CR_MAX = 50.0;
|
|
|
|
// groups get a small xp bonus
|
|
// formular is groupsize-1 * modifier
|
|
// with a default value of 0.1 (10%) a party of 4 receives 30% XP bonus
|
|
// this should encourage grouping
|
|
// set it to 0.0 if you dont like that...
|
|
const float PWFXP_GROUPBONUS_MODIFIER = 0.01;
|
|
|
|
// groub members need to be within this distance to the dead creature
|
|
// if they want to get any XP during fights
|
|
const float PWFXP_MAXIMUM_DISTANCE_TO_GROUP = 25.0; // meters
|
|
|
|
// minimum XP for a kill
|
|
const int PWFXP_MINIMUM_XP = 1;
|
|
|
|
// these two constants determine how XP division works
|
|
// a group with two PCs and one NPC gets a total XP divisor of 2.5 (using default values)
|
|
// if they kill a 1000XP mob, both PCs only reveive 400 XP
|
|
const float PWFXP_XP_DIVISOR_PC = 1.0;
|
|
const float PWFXP_XP_DIVISOR_NPC = 0.5;
|
|
|
|
// don't change these
|
|
float PWFXP_APL_MODIFIER = PWFXP_SCALAR / (PWFXP_APL_NOXP - PWFXP_APL_REDUCTION);
|
|
float PWFXP_CR_MODIFIER = PWFXP_SCALAR / (PWFXP_CR_NOXP - PWFXP_CR_REDUCTION);
|
|
|
|
int PWFXP_GetTotalClassLevel(object oPC)
|
|
{
|
|
return GetLevelByPosition(1,oPC) + GetLevelByPosition(2,oPC) + GetLevelByPosition(3,oPC) + GetLevelByPosition(4,oPC)+ GetLevelByPosition(5,oPC) + GetLevelByPosition(6,oPC);
|
|
}
|
|
|
|
float PWFXP_GetECLModifier(object oPC)
|
|
{
|
|
float fLevel = IntToFloat(GetHitDice(oPC));
|
|
|
|
int nPos = FindSubString(PWFXP_ECL_MODIFIERS, GetStringUpperCase(GetSubRace(oPC)));
|
|
|
|
if(nPos != -1)
|
|
return fLevel / (fLevel + StringToFloat(GetSubString(PWFXP_ECL_MODIFIERS,nPos-2,1)));
|
|
else
|
|
return 1.0;
|
|
}
|
|
|
|
float PWFXP_GetLevelDistanceModifier(float fLevelDistance)
|
|
{
|
|
if( fLevelDistance >= PWFXP_APL_NOXP )
|
|
{
|
|
// level distance greater then maximum allowed > no XP award at all
|
|
return 0.0; // -100%
|
|
}
|
|
else if(fLevelDistance >= PWFXP_APL_REDUCTION)
|
|
{
|
|
// level distance greater then reduction limit ? reduce xp
|
|
return 1 - ((fLevelDistance - PWFXP_APL_REDUCTION) * PWFXP_APL_MODIFIER);
|
|
}
|
|
return 1.0;
|
|
}
|
|
|
|
float PWFXP_GetCRDistanceModifier(float fCRDistance)
|
|
{
|
|
if( fCRDistance >= PWFXP_CR_NOXP )
|
|
{
|
|
// level distance greater then maximum allowed > no XP award at all
|
|
return 0.0; // -100%
|
|
}
|
|
else if(fCRDistance >= PWFXP_CR_REDUCTION)
|
|
{
|
|
// level distance greater then reduction limit ? reduce xp
|
|
return 1 -((fCRDistance - PWFXP_CR_REDUCTION) * PWFXP_CR_MODIFIER);
|
|
}
|
|
return 1.0;
|
|
}
|
|
|
|
float PWFXP_GetMiscModifier(object oPC, object oKiller)
|
|
{
|
|
if(oPC == oKiller && PWFXP_KILLINGBLOW_MODIFIER != 0.0)
|
|
{
|
|
return 1 + PWFXP_KILLINGBLOW_MODIFIER;
|
|
}
|
|
return 1.0;
|
|
}
|
|
|
|
float PWFXP_GetGroupBonusModifier(int nGroupSize)
|
|
{
|
|
return 1 + ((nGroupSize-1) * PWFXP_GROUPBONUS_MODIFIER);
|
|
}
|
|
|
|
int PWFXP_CheckDistance(object oDead, object oGroupMbr)
|
|
{
|
|
return ( GetDistanceBetween(oDead, oGroupMbr) <= PWFXP_MAXIMUM_DISTANCE_TO_GROUP ) && ( GetArea(oDead) == GetArea(oGroupMbr) );
|
|
}
|
|
|
|
void main()
|
|
{
|
|
object oDead = OBJECT_SELF;
|
|
object oKiller = GetLastKiller();
|
|
|
|
// only continue if killer is valid and not from same faction...
|
|
if ((oKiller==OBJECT_INVALID) || (GetFactionEqual(oKiller,OBJECT_SELF))) return;
|
|
|
|
// average party level, xp divisor
|
|
float fAvgLevel, fDivisor;
|
|
// groupsize, only PCs count
|
|
int nGroupSize;
|
|
|
|
// get some basic group data like average PC level , PC group size, and XP divisor
|
|
object oGroupMbr = GetFirstFactionMember(oKiller, FALSE);
|
|
while(oGroupMbr != OBJECT_INVALID)
|
|
{
|
|
if( PWFXP_CheckDistance(oDead, oGroupMbr) || oGroupMbr == oKiller)
|
|
{
|
|
if(GetIsPC(oGroupMbr))
|
|
{
|
|
nGroupSize++;
|
|
fDivisor += PWFXP_XP_DIVISOR_PC;
|
|
fAvgLevel += IntToFloat(PWFXP_GetTotalClassLevel(oGroupMbr));
|
|
}
|
|
else
|
|
fDivisor += PWFXP_XP_DIVISOR_NPC;
|
|
|
|
//SendMessageToPC(GetFirstPC(),"Name: "+GetName(oGroupMbr) + " HitDice: "+IntToString(GetHitDice(oGroupMbr)));
|
|
}
|
|
oGroupMbr = GetNextFactionMember(oKiller, FALSE);
|
|
}
|
|
|
|
if(nGroupSize == 0)
|
|
{
|
|
// NPC (Minion) killed something without a PC (Master) near enough to get XP
|
|
return;
|
|
}
|
|
|
|
// modifiers
|
|
float fDistanceModifier, fCRModifier, fMiscModifier, fFinalModifier, fECLModifier, fGroupBonusModifier;
|
|
// groupmember level
|
|
float fMbrLevel;
|
|
// get creature CR
|
|
float fCR = GetChallengeRating(oDead);
|
|
// reduce CR if greater then maximum CR cap
|
|
if(fCR > PWFXP_CR_MAX) fCR = PWFXP_CR_MAX; // cap CR
|
|
// multiply CR with global XP modifier
|
|
float fModCR = fCR * PWFXP_GLOBAL_MODIFIER;
|
|
|
|
fAvgLevel /= IntToFloat(nGroupSize);
|
|
|
|
//SendMessageToPC(GetFirstPC(),"Average Level: "+FloatToString(fAvgLevel)+" Count: "+IntToString(nGroupSize));
|
|
|
|
// calculate modifiers for each PC individually
|
|
oGroupMbr = GetFirstFactionMember(oKiller, TRUE);
|
|
while(oGroupMbr != OBJECT_INVALID)
|
|
{
|
|
fMbrLevel = IntToFloat(PWFXP_GetTotalClassLevel(oGroupMbr));
|
|
if( PWFXP_CheckDistance(oDead, oGroupMbr) || oGroupMbr == oKiller)
|
|
{
|
|
// get PC-level distance to average group-level and compute modifier
|
|
fDistanceModifier = PWFXP_GetLevelDistanceModifier(fabs(fAvgLevel - fMbrLevel));
|
|
// get PC-level distance to CR of dead creature and compute modifier
|
|
fCRModifier = PWFXP_GetCRDistanceModifier(fabs(fCR - fMbrLevel));
|
|
// get misc modifiers (right now only 10% for killing blow dealer)
|
|
fMiscModifier = PWFXP_GetMiscModifier(oGroupMbr, oKiller);
|
|
// get group bonus modifier
|
|
fGroupBonusModifier = PWFXP_GetGroupBonusModifier(nGroupSize);
|
|
// get subrace ECL modifier
|
|
fECLModifier = PWFXP_GetECLModifier(oGroupMbr);
|
|
// calculate final modifier
|
|
fFinalModifier = fDistanceModifier * fCRModifier * fMiscModifier * fGroupBonusModifier * fECLModifier;
|
|
|
|
// debug
|
|
if(PWFXP_DEBUG)
|
|
SendMessageToPC(oGroupMbr,GetName(oGroupMbr)+"'s XP Modifiers: Distance Penalty ["+IntToString(FloatToInt((fDistanceModifier-1)*100)) +
|
|
"%] Creature Rating ["+IntToString(FloatToInt((fCRModifier-1)*100))+
|
|
"%] Killing Blow Modifier ["+IntToString(FloatToInt((fMiscModifier-1)*100))+
|
|
"%] Group Bonus ["+IntToString(FloatToInt((fGroupBonusModifier-1)*100))+
|
|
|
|
"%] Group Rating ["+IntToString(nGroupSize)+
|
|
"] Divisor ["+GetSubString(FloatToString(fDivisor),6,5) +
|
|
"] Final Modifier ["+IntToString(FloatToInt((fFinalModifier-1)*100))+
|
|
"%]");
|
|
|
|
|
|
// apply XP
|
|
if(fFinalModifier > 0.0)
|
|
{
|
|
int nXP = FloatToInt((fModCR / fDivisor) * fFinalModifier);
|
|
// award minimum xp if needed
|
|
if(nXP < PWFXP_MINIMUM_XP) nXP = PWFXP_MINIMUM_XP;
|
|
GiveXPToCreature(oGroupMbr, nXP);
|
|
}
|
|
else
|
|
if(PWFXP_MINIMUM_XP > 0)
|
|
GiveXPToCreature(oGroupMbr, PWFXP_MINIMUM_XP);
|
|
|
|
}
|
|
oGroupMbr = GetNextFactionMember(oKiller, TRUE);
|
|
}
|
|
}
|