Modding caster level of items (wands, potions, etc.)

Hi!

I would like to mod the caster level of items, and am a bit lost how to…

I want to make it so that items (wands, potions, etc.) always use
max (their set caster level; your own caster level) and
max (their set Save DC; your own save DC).
The goal is to make these items more useful for casters - otherwise, my playstyle is just to rest so I can use my own, more powerful spells. With this change, casters would be a bit more item-reliant and make the “collect lots of loot” part of the game more fun for me.

I have no idea where to start - maybe one of you can help me with a pointer to where the caster level of items is set and how I can change this?

Big thanks in advance,

Aharon

I searched for a solution, and found one for NWN that used ShadoOows CPP:

Blockquote
void main()
{
object oCaster = OBJECT_SELF;
object oItem = GetSpellCastItem();
int nSpellId = GetSpellId();
int casterLevel = GetCasterLevel(oCaster);
if(oItem != OBJECT_INVALID && GetBaseItemType(oItem) != BASE_ITEM_POTIONS)
{//spell cast from item
string sInnate;
int nInnate;
int bestLevel, reqLevel;
nLevel = GetLevelByclass(class_TYPE_WIZARD,oCaster);
if(nLevel > 0)
{
sInnate = Get2DAString(“spells”,“Wiz_Sorc”,nSpellId);
nInnate = StringToInt(sInnate);
if(nInnate == 0 && sInnate == “0” || nInnate > 0)
{
reqLevel = (nInnate*2)-1;
if(nLevel >= reqLevel && nLevel > bestLevel)
{
bestLevel = nLevel;
}
}
}
if(bestLevel > casterLevel)
{
SetLocalInt(oItem,“ITEM_CASTER_LEVEL_OVERRIDE”,bestLevel);
SendMessageToPC(oCaster,"DEBUG: caster level overriden: "+IntToString(bestLevel));
}
}
}

for the caster level and

Blockquote
#include “x2_i0_spells”
int GetDCBonusFromFeats(string sSchool, object oCaster);
void main()
{
object oCaster = OBJECT_SELF;
object oItem = GetSpellCastItem();
int nSpellId = GetSpellId();
int casterLevel = GetCasterLevel(oCaster);
if(oItem != OBJECT_INVALID && GetBaseItemType(oItem) != BASE_ITEM_POTIONS && GetBaseItemType(oItem) != BASE_ITEM_ENCHANTED_POTION)
{//spell cast from item
string sInnate;
int nInnate;
int bestLevel, reqLevel;
nLevel = GetLevelByclass(class_TYPE_WIZARD,oCaster);
if(nLevel > 0)
{
sInnate = Get2DAString(“spells”,“Wiz_Sorc”,nSpellId);
nInnate = StringToInt(sInnate);
if(nInnate == 0 && sInnate == “0” || nInnate > 0)
{
reqLevel = (nInnate*2)-1;
if(nLevel >= reqLevel && nLevel > bestLevel)
{
bestLevel = nLevel;
}
}
}
if(bestLevel > casterLevel)
{
SetLocalInt(oItem,“ITEM_CASTER_LEVEL_OVERRIDE”,bestLevel);
SendMessageToPC(oCaster,"DEBUG: caster level overriden: "+IntToString(bestLevel));
}
int nDC = GetSpellSaveDC()-3;//by default DC is 13+innate, normal DC should be 10+innate+caster ability+feats
nDC+= GetCasterAbilityModifier(oCaster);
string sSchool = Get2DAString(“spells”,“School”,nSpellId);
nDC+= GetDCBonusFromFeats(sSchool,oCaster);
SetLocalInt(oItem,“ITEM_DC_OVERRIDE”,nDC);
SendMessageToPC(oCaster,"DEBUG: spell DC overriden: "+IntToString(nDC));
}
}
int GetDCBonusFromFeats(string sSchool, object oCaster)
{
if(sSchool == “A”)
{
if(GetHasFeat(FEAT_EPIC_SPELL_FOCUS_ABJURATION,oCaster))
{
return 6;
}
else if(GetHasFeat(FEAT_GREATER_SPELL_FOCUS_ABJURATION,oCaster))
{
return 4;
}
else if(GetHasFeat(FEAT_SPELL_FOCUS_ABJURATION,oCaster))
{
return 2;
}
}
else if(sSchool == “C”)
{
if(GetHasFeat(FEAT_EPIC_SPELL_FOCUS_CONJURATION,oCaster))
{
return 6;
}
else if(GetHasFeat(FEAT_GREATER_SPELL_FOCUS_CONJURATION,oCaster))
{
return 4;
}
else if(GetHasFeat(FEAT_SPELL_FOCUS_CONJURATION,oCaster))
{
return 2;
}
}
else if(sSchool == “D”)
{
if(GetHasFeat(FEAT_EPIC_SPELL_FOCUS_DIVINATION,oCaster))
{
return 6;
}
else if(GetHasFeat(FEAT_GREATER_SPELL_FOCUS_DIVINATION,oCaster))
{
return 4;
}
else if(GetHasFeat(FEAT_SPELL_FOCUS_DIVINATION,oCaster))
{
return 2;
}
}
else if(sSchool == “E”)
{
if(GetHasFeat(FEAT_EPIC_SPELL_FOCUS_ENCHANTMENT,oCaster))
{
return 6;
}
else if(GetHasFeat(FEAT_GREATER_SPELL_FOCUS_ENCHANTMENT,oCaster))
{
return 4;
}
else if(GetHasFeat(FEAT_SPELL_FOCUS_ENCHANTMENT,oCaster))
{
return 2;
}
}
else if(sSchool == “V”)
{
if(GetHasFeat(FEAT_EPIC_SPELL_FOCUS_EVOCATION,oCaster))
{
return 6;
}
else if(GetHasFeat(FEAT_GREATER_SPELL_FOCUS_EVOCATION,oCaster))
{
return 4;
}
else if(GetHasFeat(FEAT_SPELL_FOCUS_EVOCATION,oCaster))
{
return 2;
}
}
else if(sSchool == “I”)
{
if(GetHasFeat(FEAT_EPIC_SPELL_FOCUS_ILLUSION,oCaster))
{
return 6;
}
else if(GetHasFeat(FEAT_GREATER_SPELL_FOCUS_ILLUSION,oCaster))
{
return 4;
}
else if(GetHasFeat(FEAT_SPELL_FOCUS_ILLUSION,oCaster))
{
return 2;
}
}
else if(sSchool == “N”)
{
if(GetHasFeat(FEAT_EPIC_SPELL_FOCUS_NECROMANCY,oCaster))
{
return 6;
}
else if(GetHasFeat(FEAT_GREATER_SPELL_FOCUS_NECROMANCY,oCaster))
{
return 4;
}
else if(GetHasFeat(FEAT_SPELL_FOCUS_NECROMANCY,oCaster))
{
return 2;
}
}
else if(sSchool == “T”)
{
if(GetHasFeat(FEAT_EPIC_SPELL_FOCUS_TRANSMUTATION,oCaster))
{
return 6;
}
else if(GetHasFeat(FEAT_GREATER_SPELL_FOCUS_TRANSMUTATION,oCaster))
{
return 4;
}
else if(GetHasFeat(FEAT_SPELL_FOCUS_TRANSMUTATION,oCaster))
{
return 2;
}
}
return 0;
}

for the Save DC. I think this can be adapted to NWN2 using the spellhooks there?
Does anybody have experience with the Community script library (https://www.nexusmods.com/neverwinter2/mods/3) and can tell me wether I might be able to use this to achieve my goal?

The CPP was added as a patch to the game - how do I use the Community Script Library? Do I just add it to my override folder?

Ok, the two scripts you linked have a few issues:

  1. They don’t compile in NWN2 (the second because of a variable spelled wrong) and both because of the lack of a definition of type of some variables. But this one is easily fixable.
  2. Potions and Enchanted Potions are excluded from this, they’ll keep using the standard spell level.
  3. For some reason, the first script applies only to Wizards, and the second one has the caster level component apply only to Wizards (the DC component applies to everyone)
  4. Finally, what they do is setting a local variable on the item, but I’m not sure how this effectively changes the caster level and the DC of the spells cast by the item, those variables must be called by another script somewhere specifically to affect those parameters weather it’s the spell script themselves or a particular item unique power script.

@Aharon
i still haven’t had a decent coffee to day, but

 
Aren’t itemproperty spells limited to what’s in Iprp_Spells.2da

see col “CasterLvl”

basically, those ids -> what can be assigned to items as spells. The casterlevel is set by id. I’d think, offhand, that to change the level/dc of item-spells more dynamically, spellscripts would need to be rewritten … (see Clangeddin’s point #4)

2 Likes

In any case, I don’t really have time to look into this right now, but what I could do was to rewrite the second script (it seemed to be comprehensive of the first one as well, anyway) to compile with NWN2, to remove the Wizard only restriction and to refactor its code a bit to make it a little more readable.

I doubt that on its own it will do anything however, this is probably a better starting point, I think it can be renamed to a subroutine and added into the spellhook somewhere.

#include "x2_i0_spells"

int GetDCBonusFromFeats(string sSchool, object oPC);

void main()
{
	object oITEM = GetSpellCastItem();
	if (oITEM == OBJECT_INVALID) return;
	int nITEM = GetBaseItemType(oITEM);
	if (nITEM == BASE_ITEM_POTIONS) return;
	if (nITEM == BASE_ITEM_ENCHANTED_POTION) return;
	
	object oPC = OBJECT_SELF;
	int nSPELL = GetSpellId();
	int nLEVEL = GetCasterLevel(oPC);
	string sINNATE = Get2DAString("spells", "Innate", nSPELL);
	int nINNATE = StringToInt(sINNATE);
	if (nINNATE < 0) nINNATE = 0;
	
	int nMIN = (nINNATE * 2) - 1;
	if (nMIN < 1) nMIN = 1;
	if (nLEVEL > nMIN)
	{
		SetLocalInt(oITEM, "ITEM_CASTER_LEVEL_OVERRIDE", nLEVEL);
		SendMessageToPC(oPC, "DEBUG: caster level overriden: " + IntToString(nLEVEL));
	}
	
	//by default DC is 13+innate, normal DC should be 10 + innate + caster ability + feats
	string sSchool = Get2DAString("spells", "School", nSPELL);
	int nDC = GetSpellSaveDC() - 3 + GetCasterAbilityModifier(oPC) + GetDCBonusFromFeats(sSchool, oPC);
	SetLocalInt(oITEM, "ITEM_DC_OVERRIDE", nDC);
	SendMessageToPC(oPC, "DEBUG: spell DC overriden: " + IntToString(nDC));
}

int GetDCBonusFromFeats(string sSchool, object oPC)
{
	int nFEAT, nGRET, nEPIC;
	if (sSchool == "A")
	{
		nFEAT = FEAT_SPELL_FOCUS_ABJURATION;
		nGRET = FEAT_GREATER_SPELL_FOCUS_ABJURATION;
		nEPIC = FEAT_EPIC_SPELL_FOCUS_ABJURATION;
	}
	else if (sSchool == "C")
	{
		nFEAT = FEAT_SPELL_FOCUS_CONJURATION;
		nGRET = FEAT_GREATER_SPELL_FOCUS_CONJURATION;
		nEPIC = FEAT_EPIC_SPELL_FOCUS_CONJURATION;
	}
	else if (sSchool == "D")
	{
		nFEAT = FEAT_EPIC_SPELL_FOCUS_DIVINATION;
		nGRET = FEAT_GREATER_SPELL_FOCUS_DIVINIATION;
		nEPIC = FEAT_SPELL_FOCUS_DIVINATION;
	}
	else if (sSchool == "E")
	{
		nFEAT = FEAT_SPELL_FOCUS_ENCHANTMENT;
		nGRET = FEAT_GREATER_SPELL_FOCUS_ENCHANTMENT;
		nEPIC = FEAT_EPIC_SPELL_FOCUS_ENCHANTMENT;
	}
	else if (sSchool == "V")
	{
		nFEAT = FEAT_SPELL_FOCUS_EVOCATION;
		nGRET = FEAT_GREATER_SPELL_FOCUS_EVOCATION;
		nEPIC = FEAT_EPIC_SPELL_FOCUS_EVOCATION;
	}
	else if (sSchool == "I")
	{
		nFEAT = FEAT_SPELL_FOCUS_ILLUSION;
		nGRET = FEAT_GREATER_SPELL_FOCUS_ILLUSION;
		nEPIC = FEAT_EPIC_SPELL_FOCUS_ILLUSION;
	}
	else if (sSchool == "N")
	{
		nFEAT = FEAT_SPELL_FOCUS_NECROMANCY;
		nGRET = FEAT_GREATER_SPELL_FOCUS_NECROMANCY;
		nEPIC = FEAT_EPIC_SPELL_FOCUS_NECROMANCY;
	}
	else if (sSchool == "T")
	{
		nFEAT = FEAT_SPELL_FOCUS_TRANSMUTATION;
		nGRET = FEAT_GREATER_SPELL_FOCUS_TRANSMUTATION;
		nEPIC = FEAT_EPIC_SPELL_FOCUS_TRANSMUTATION;
	}
	else return 0;
	
	if (GetHasFeat(nEPIC, oPC, TRUE) == TRUE) return 6;
	if (GetHasFeat(nGRET, oPC, TRUE) == TRUE) return 4;
	if (GetHasFeat(nFEAT, oPC, TRUE) == TRUE) return 2;
	return 0;
}
1 Like

Thank you! So I add this to x2_inc_spellhook.nss, and then it should work?

Heh… actually I don’t think it will do anything.

Thinking about it right now, perhaps there is an easier alternative method, although I cannot find a way that does not involve recompiling EVERY single spell script of the game (even if you use the spellhook, you must recompile the script for the change to have effect, just saving the spellhook script will not do anything).

I’m pretty sure there is a way out there to compile multiple scripts very fast outside of the toolset, but right now I don’t really remember how to do it.

There is such a way - nwnnsscomp, and later the Neverwinter Script compiler. Apparently, this was updated to an enhanced edition script compiler, which hopefully still works with old scripts: https://neverwintervault.org/project/nwnee/other/tool/nwnsc-nwn-enhanced-edition-script-compiler

So in this case I would add it to x2_inc_spellhook.nss, and then recompile all spells?

No, it would still would not do anything, I’m trying to implement a different method in the spellhook right now and testing it to see if it works properly, I’ll get back to you.

Whew… I finally did it, although there is one restriction, you must have the spell slotted in your spellbook (you will not consume it, but it has to be slotted).

I used a ridiculous workaround that involves using a module spell override script that summon an invisible copy of yourself to cast the spell at your caster level whenever you use an item that casts a spell that you can cast (potions are excluded).

Things could have been much easier (and less restricted) if the CHEAT parameter of ActionCastSpell would not cast the spell at a fixed level of 10, but alas, this is what we have.

With this method there is no need to touch the spellhook and no need to touch the spell scripts themselves at all, which means that it’s all reduced to two scripts, the module load script and the module spell override script.

You will find them at the following link, simply extract the zip file in your Override folder and you should be set (as long as the module uses the x2_mod_def_load script for the OnModuleLoad event)

1 Like

Wow, thank you :smiley:
This is very helpful, and far more elegant than what I found…

Hm… I didn’t get it to work. The module I currently play (Der Fluch der Zwerge) does use the x2_mod_def_load script for the OnModuleLoad event. When I try to use a wand of missiles, I do get the message “Item Caster Level overridden to PC caster level.”, but nothing happens. Any idea what I could be doing wrong?

Yeah, there was an issue that I fixed now. Try downloading it again and see if it works with the wands of missiles this time.
I also added a provisional list of spells that will be excluded from this script, as it’s either not needed (since their effect is fixed and doesn’t change based on caster level) or it might cause issues, these are all the summoning and conjure area of effect spells.

There is a chance that the spell will fail if you cast at maximum distance, that is because the copy is not spawned in your exact location, but close to it, so it might be that the copy is out of range to cast the spell in that case.

Another issue is that in your party window you will see the copy of yourself that will be deleted after a few seconds, the party icon will stay until you save and reload (or maybe change area/module).

Sadly there is no 100% perfect way that does not involve rewriting and recompiling all spell scripts (I’m not doing that :stuck_out_tongue: ) . This is the best workaround with minimal effort that will work for healing spells, instant damage spells, buffs and so on.

I’m also going to add the alternative method, this one has less issues, however it requires more work on your part to implement. Instead of creating the shadow copy that will cast the spell for you whenever you use the item, this method will simply use a different function to retrieve the caster level inside the spell script.
Keep in mind that this method is not compatible with the one I posted above, so if you try this route instead, remember to delete the files of the previous method.

First Step:
You must add the following piece of code at the very bottom of the “x2_inc_spellhook” and SAVE the script.

// Get the level at which this creature cast it's last spell (or spell-like ability)
// Return value on error, or if oCreature has not yet cast a spell: 0;
// This differs from GetCasterLevel, as it will upgrade the caster level of spells cast from items
// to the caster level of the owner, if the spell is known in their spellbook.
// To work properly this requires that the owner stores the caster level in a local variable
// whenever he casts a spell from his spellbook (the spellscript must use this function
// and NOT the standard GetCasterLevel.
// On singleplayer this generally needed only once on multiplayer it might be needed on every relog.
// Do not use outside spell script.
int GetCasterLevelUpgraded(object oCreature)
{
	int nLEVEL = GetCasterLevel(oCreature);
	object oITEM = GetSpellCastItem();
	if (oITEM == OBJECT_INVALID) SetLocalInt(oCreature, "CASTER_LEVEL", nLEVEL);
	else
	{
		int nTYPE = GetBaseItemType(oITEM);
		if (nTYPE == BASE_ITEM_POTIONS) return nLEVEL;
		else if (nTYPE == BASE_ITEM_ENCHANTED_POTION) return nLEVEL;
		
		if (GetSpellKnown(oCreature, GetSpellId()) == TRUE)
		{
			int nTRUE = GetLocalInt(oCreature, "CASTER_LEVEL");
			if (nTRUE > nLEVEL)
			{
				SendMessageToPC(oCreature, "Caster level upgraded from " + IntToString(nLEVEL) + " to " + IntToString(nTRUE));
				nLEVEL = nTRUE;
			}
		}
	}
	return nLEVEL;
}

Second Step:
You now must implement this function by replacing the standard GetCasterLevel, for example, for the magic missile spell the script is “nw_s0_magmiss” at line 52, it needs to be changed to:

int nCasterLvl = GetCasterLevelUpgraded(OBJECT_SELF);

When you’ve done so SAVE and COMPILE the script (if it gives a compilation error, it means you haven’t done properly the first step).
This step has to be repeated for every spellscript you intend to make use of this enhanced caster level when cast from item.

Third Step (In-game):
Now to make this is actually work, you’re gonna have to cast any spell that uses the above function at least once to store a local variable to retrieve the value of your caster level. You will have to recast it when your caster level changes (because you level up, or because you cast a spell from a different class with a lower caster level) or whenever the local variable is erased (for example in multiplayer when you log out).
When you’ve done so, you can now use the item (for example the wand of magic missile) and you should get a message that your casrter level was upgraded if it’s higher than the item caster level.

Potions are still excluded.