/////////////////////////////////////////////////////////////////////////////// // 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 // ============================================================================