#548 Darkfire reimplementation

Spells.2da #548 Darkfire is disabled in stock Nwn2 – if you want it …

change MetaMagic to 0x7F (all non-invokation metamagics except Permanent)
change Cleric to 3
change REMOVED to 0

edit Dialog.Tlk #3782

Caster Level(s): Cleric 3
Innate Level: 3
School: Evocation
Descriptor(s): Fire
Component(s): Verbal, Somatic
Range: Touch
Area of Effect / Target: Creature, Melee Weapon
Duration: 1 min. / level
Save: None
Spell Resistance: No

This spell allows you to immolate a melee weapon. In addition to its normal damage, the weapon inflicts 1d6 points of damage, +1 per two caster levels (maximum of +10) of Fire damage. The caster can target either a specific melee weapon in his inventory or a creature to enchant the weapon that it's wielding.

replace the ImpactScript x2_s0_darkfire with this →

//::///////////////////////////////////////////////
//:: Darkfire
//:: 'x2_s0_darkfire'
//:: Copyright (c) 2001 Bioware Corp.
//:://////////////////////////////////////////////
/*
	Caster Level(s): Cleric 3
	Innate Level: 3
	School: Evocation
	Descriptor(s): Fire
	Component(s): Verbal, Somatic
	Range: Touch
	Area of Effect / Target: Creature, Melee Weapon
	Duration: 1 min. / level
	Save: None
	Spell Resistance: No

	This spell allows you to immolate a melee weapon. In addition to its normal
	damage, the weapon inflicts 1d6 points of damage, +1 per two caster levels
	(maximum of +10) of Fire damage. The caster can target either a specific
	melee weapon in his inventory or a creature to enchant the weapon that it's
	wielding.
*/
//:://////////////////////////////////////////////
//:: Created By: Andrew Nobbs
//:: Created On: Dec 04, 2002
//:://////////////////////////////////////////////
//:: Updated by Andrew Nobbs May 08, 2003
//:: 2003-07-29: Rewritten, Georg Zoeller
//:: 2022-10-13: reinstated and rewritten, kevL

#include "x2_inc_spellhook"
#include "nwn2_inc_metmag"

const string sMETA = "ohcs_DarkfireMeta";
const string sCL   = "ohcs_DarkfireCL";

//
void main()
{
	if (!X2PreSpellCastCode())
		return;


	object oCaster = OBJECT_SELF;

	object oTarget = IPGetTargetedOrEquippedMeleeWeapon();
	if (GetIsObjectValid(oTarget))
	{
		int iCasterlevel = GetCasterLevel(oCaster);
		if (iCasterlevel < 1) iCasterlevel = 1;

		float fDur = ApplyMetamagicDurationMods(TurnsToSeconds(iCasterlevel));

		// note if the item is on the ground there is no possessor
		object oPossessor = GetItemPossessor(oTarget);
		if (GetIsObjectValid(oPossessor))
		{
			SignalEvent(oPossessor, EventSpellCastAt(oCaster, GetSpellId(), FALSE));

			effect eHit = EffectVisualEffect(VFX_IMP_PULSE_FIRE);
			effect eHi0 = EffectVisualEffect(VFX_IMP_FLAME_M);
				   eHit = EffectLinkEffects(eHit, eHi0);
			ApplyEffectToObject(DURATION_TYPE_INSTANT, eHit, oPossessor);

			// note that if the item is passed off to another character the DurCessatePositive
			// visual will still affect only the original possessor
			effect eDur = EffectVisualEffect(VFX_DUR_CESSATE_POSITIVE);
			ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eDur, oPossessor, fDur);
		}

		// limit iCasterlevel to 20
		// - this value will appear on the item's Examine sheet
		// but it's halved for damage bonus in the OnHit script (x2_s3_darkfire)
		if (iCasterlevel > 20)
			iCasterlevel = 20;

		itemproperty ip = ItemPropertyOnHitCastSpell(IP_CONST_ONHIT_CASTSPELL_ONHIT_DARKFIRE, iCasterlevel);
		IPSafeAddItemProperty(oTarget, ip, fDur, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING);

		ip = ItemPropertyVisualEffect(ITEM_VISUAL_FIRE);
		IPSafeAddItemProperty(oTarget, ip, fDur, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING, FALSE, TRUE);


		int iMeta = GetMetaMagicFeat();
		if (iMeta == METAMAGIC_EMPOWER || iMeta == METAMAGIC_MAXIMIZE)
		{
			// store Metamagic on the weapon
			SetLocalInt(oTarget, sMETA, iMeta);
			AssignCommand(GetModule(), DelayCommand(fDur, DeleteLocalInt(oTarget, sMETA)));
		}
		else
			DeleteLocalInt(oTarget, sMETA);

		// store Casterlevel halved on the weapon
		SetLocalInt(oTarget, sCL, iCasterlevel / 2);
		AssignCommand(GetModule(), DelayCommand(fDur, DeleteLocalInt(oTarget, sCL)));
	}
	else
		FloatingTextStrRefOnCreature(83615, oCaster); // "* Spell Failed - Target must be a melee weapon or creature with a melee weapon equipped *"
}

 
edit Iprp_OnHitSpell.2da #127 OnHIT_Darkfire
change Name to 3781

replace the ImpactScript x2_s3_darkfire with this →

//::///////////////////////////////////////////////
//:: OnHit Darkfire
//:: 'x2_s3_darkfire'
//:: Copyright (c) 2003 Bioware Corp.
//:://////////////////////////////////////////////
/*
	OnHit Castspell Fire Damage property for the Darkfire weapon spell
	(x2_s0_darkfire).

	We need to use this property because we cannot add both constant and
	variable damage with EffectDamage().

	Behavior:
	The Casterlevel is set as a variable on the weapon. So is Metamagic -
	Empowered or Maximized.
*/
//:://////////////////////////////////////////////
//:: Created By: Georg Zoeller
//:: Created On: 2003-07-17
//:://////////////////////////////////////////////
//:: 2022-10-13: reinstated and rewritten, kevL

const string sMETA = "ohcs_DarkfireMeta";
const string sCL   = "ohcs_DarkfireCL";

// OBJECT_SELF is possessor of the item
// - but note that GetCasterLevel(OBJECT_SELF) returns the level of the
//   originating caster ... go figur (note that level is stored as the CostValue
//   in the applied ItemProperty construct)
// - also note that GetMetaMagicFeat() returns the metamagic with which the
//   application-spell was cast IF the originating spellcaster is using the
//   weapon BUT -1 if a different character uses the weapon ... go figur
// - so store the value of Metamagic in the application-script and check it here
//   for the Empowered or Maximized flags
void main()
{
	object oTarget = GetSpellTargetObject();
	if (GetIsObjectValid(oTarget))
	{
		object oWeapon = GetSpellCastItem();

		int iFire;
		switch (GetLocalInt(oWeapon, sMETA))
		{
			default:
				iFire = d6();
				break;

			case METAMAGIC_EMPOWER:
				iFire = d6();
				iFire += iFire / 2;
				break;

			case METAMAGIC_MAXIMIZE:
				iFire = 6;
				break;
		}
		iFire += GetLocalInt(oWeapon, sCL);

		int iHit;
		if (iFire <= 10) iHit = VFX_IMP_FLAME_S;
		else             iHit = VFX_IMP_FLAME_M;

		effect eHit  = EffectVisualEffect(iHit);
		effect eFire = EffectDamage(iFire, DAMAGE_TYPE_FIRE);
			   eFire = EffectLinkEffects(eFire, eHit);
		ApplyEffectToObject(DURATION_TYPE_INSTANT, eFire, oTarget);
	}
}

 
thanks to @temyankee for getting me interested in this and @Lance_Botelle for just being Lance  :grapes:

 
[edit] 221015 - fix potential bug w/ using GetCasterLevel() → store CL on the weapon
[edit] fix dialog.Tlk description : Duration is 1 min/level
[edit] ensure metamagic gets deleted on reapplication

4 Likes

@kevL_s

Thanks for this info … That just saved some time, which is always appreciated. :+1:

I’ll make the adjustments now, so that the spell is updated for my next module update.

PS: I did not realise that the original version was going to require as much “updating” as you have just done here. So much for simply just re-enabling it in the 2da … I see you did more than just that.

P.P.S: Shouldn’t that duration be 1 minute per level? (A turn is 60 seconds.) (*)

(*) TurnsToSeconds to seconds spells opens up a whole can a worms if you have altered the module time scales. e.g. I have 15 minutes real time equates to one hour game time. So when a 20th level casts the spell, it lasts 20 minutes real time, or 1 hours 5 minutes game time. It gets complicated, but is why I have the term warped time spells in the module. :slight_smile: Just thinking aloud. P.S. That said, I do not worry about durations on items in such cases though - only buffs on PCs.

2 Likes

There may be one issue with the on hit spell script. The GetCasterLevel call gets reset for each spell cast including using items. This is or was an issue for persistent spells that can get the caster level in the heartbeat and on enter scripts well after the original spell is cast. Storing the caster level with the metamagic used in the original spell script is an easy fix.

2 Likes

@TonyK

This sounds similar to when I stored the caster level on the item at time of casting in my own experimentation with the Bless spell storing the caster level on the blessed weapon … which then recalled the caster level from the item as required on each hit.

I think that’s what @kevL_s basically does with …

GetLocalInt(GetSpellCastItem(), sMETA)

Although, what happens if the original caster was not using metamagic feat? I’m not sure whether the following line would retrieve a valid caster level:-

iFire += GetCasterLevel(OBJECT_SELF) / 2;

But KevL does add this note …

//  OBJECT_SELF is possessor of the item
//  but note that GetCasterLevel(OBJECT_SELF) returns the level of the
//  originating caster ... go figur (note that level is stored as the CostValue
//  in the applied ItemProperty construct)

So, :thinking:

UPDATE: OK, so I just tested the GetCasterLevel(OBJECT_SELF) and it did indeed return the correct level for the item, which surprised me. One less variable to keep track of. :slight_smile: (Perhaps that is what KevL means in the question I raise below, which means my query can be ignored if that is the case.)

The following is pending deletion if I have now understood it above:

@kevL_s

QUOTE: “note that level is stored as the CostValue in the applied ItemProperty construct”

I’m a little confused here, because I am not sure where this “CostValue” is. The only “Cost” value I can find is in the iprp_onhitspell.2da, which I noted does affect cost of an item at vendors. (I have edited it and checked this myself when adjusting the economy.) And in the function description for ItemPropertyOnHitCastSpell, it does refer to it as “level”. Can you just run it past me again what this means, thanks, :slight_smile: i.e. If I understand you correctly, I don’t think we can rely on the “Cost” value always directly equating to the caster level, although I suspect a correlation due to “cost” of an item related to its “spell power”. Or, I have totally misunderstood. :speak_no_evil:

1 Like

thanks Tony,

yeh that GetCasterLevel was making me squint a bit but wasn’t sure why …

 
 
( yep it could use a bitgroup in a single int but it’s late here and i want to get a fix up asap :)

The stock stuff should work okay. But i wanted some ‘improvements’ …

The original has 1 hour per level … i compared it against other 3rd Cleric spells and knocked it back to 10 min per level … and yes I was tempted to go with 6 sec per level, but ShieldofFaith is 10 min per and it seemed to balance in my mind …

i try not to think about such things ;) like you say, can o’ worms … but, do what ya gotta do

1 Like

Ah, OK, that’s neat! :slight_smile:

Yes, I agree … But TurnsToSeconds always makes an int of 1 (or level of 1), 60 seconds. i.e. 1 minute. :slight_smile:

i.e. The function returns one minute per level. (You did what I do and remembered a good old PnP turn being ten minutes.)

Function - TurnsToSeconds

Description

Returns the value of nTurns converted into seconds. A single turn is always 60 seconds.

This value never changes due to the game clock - it is real time.

It’s scary … :scream:

no that’s not the point. The point is if there’s another spellcast then the value that the engine stores as “the current caster level” may change … so the accurate value still needs to be stored somewhere/how.

ie. GetCasterLevel() is guaranteed to be accurate only during certain timeframes, like during the execution of a (single) spellscript.

// Returns the Cost Table value (index of the cost table) of the item property.
// See the 2DA files for value definitions.
int GetItemPropertyCostTableValue(itemproperty iProp);

i would use that instead of either GetCasterLevel() or a local_int on the weapon, except that I can’t think of a solid way to get the IP from the spellcast-item offhand … (the IP needs to be passed into the function)

1 Like

oh sheesh. will look at some other spells to see how it’s done there …

[edit]
… that’s odd, Shield of Faith just uses

float fDuration = TurnsToSeconds(GetCasterLevel(OBJECT_SELF)); // * Duration 1 turn/level

but I have to look into it more … later tho, ( i may have changed the SoF description in Dialog.Tlk etc)

[edit2]
yeh, looks like the original description says 1 min/level … curfuffle

1 Like

Apologies in advance if I am still not understanding this …

So … If I understand you correctly, this may not be necessary - I mean, doesn’t the latest version of a spell (if the same as the existing) overwrite the previous anyway? I understand about keeping existing spells, but I think there is room to say that a spell cast over an existing spell may affect the duration if it was of less power. As an idea, maybe the lesser spell taints any existing magic?

I came up against a similar issue when testing a bless potion (3rd) vs a bless spell cast by a higher level PC (>3rd). If a PC took a lower level Bless potion after having a higher level Bless spell cast on them, then they disrupted the original spell duration.

However, from your next point, I think I see more what you mean, as GetCasterLevel(OBJECT_SELF) may return a completely different level if more than one type of spell was cast on the item … hmm, if that’s what you mean, then I guess we would need to track the variables for the level of the specific property in question.

But, if that is the case, then why not simply do what you did with the META variable and what I did with my own Bless spell? i.e. As an example …

In the cast spell … where the target is the target item.

SetLocalInt(oTarget, sSpellID+"CASTLEVEL", nDuration);  // < VAR NAME DIFFERS EACH SPELL eg SpellID

And … from the On Hit Script …

object oBlessedBolt = GetSpellCastItem(); // The Target in the original cast spell.
int iLEVEL = GetLocalInt(oBlessedBolt, sSpellID+"CASTLEVEL");  // < VAR NAME DIFFERS EACH SPELL eg SpellID

Or, have I still missed something … ?

BTW, I realise that you may need to loop the sSpellID when detecting it - but I imagine we are talking limited spells for weapons as they go.

aleady done (scripts updated)

 
re. GetCasterLevel()
I think what Tony is saying (and i believe him) is that the engine calculates and stores the “casterlevel” only at certain points in the execution of hardcode. Think of it as analogous to (pulling this out of my hat) the GetLastDisturbed() function. Its value gets calculated only during an OnDisturbed event.

Sure you can run GetLastDisturbed() anytime you want (in any script you want, it will compile and run okay) but you’re only ever going to get the value that the hardcode determined during the most recent OnDisturbed event …

So if I cast Darkfire (or Bless) the engine stores the last “casterlevel” in some variable that it has internally. But then some other caster casts a spell ; the engine overwrites the “casterlevel” with a new value. Then the OnHitCastSpell script fires … the original casterlevel has been lost and GetCasterLevel() returns the level that was calculated for a subsequent caster’s spellcast …

 
or something somewhat similar to that.

It depends on what GetCasterLevel() is really doing in the hardcode. I mean, it might work okay in scripts that handle the OnHitCastSpell event … but atm I doubt that it’s actually able to trace its way back to the original caster to get an accurate value 100%.

1 Like

This is what I suspected you meant … and I do get that point. It’s also like the GetLastAttacker() type. i.e. It’s prone to change.

I did wonder, actually, that it may store the cast level (from the original spell) on the item itself, which is why the GetCasterLevel using OBJECT_SELF, returns a value we hoped it to be … because it has assigned that level for that spell to the item …

BUT … I can see the potential issue if the function placing the property onto the item is not distinguishing levels by property type, and just assumes the last spell caster level regardless.

Well that’s just dandy! :+1:

That’s what I was trying to say at the beginning … :slight_smile: Yeh, I’m not going mad … yet. :star_struck:

1 Like

constructing and applying the IP does store the cast level on the item. In that CostValue parameter of the IP itself. But I think it’s used only for the item’s Examine sheet

(id love to get and use that value in the OnHit script but id have to think about it)

Ah, OK. Makes sense.

May be more trouble than it’s worth. After all, doing it the local way seems reasonable.

One question though, why the delayed removal of said variables? Won’t they be irrelevant if the property has come to an end anyway? And won’t they be updated with a new casting anyway?

AssignCommand(GetModule(), DelayCommand(fDur, DeleteLocalInt(oTarget, sCL)));

EDIT: Meta ones I get!

1 Like

Because I like to try to keep things clean. But you’re right it shouldn’t matter if they don’t get removed.

(and it wouldn’t surprise me if they don’t if the character with the weapon loads into a different Module before the delayed Delete kicks in)

Yes, this sort of thing bugs me too. Maybe set an initialisation as a default with locals overriding if present and locals stored or deleted at time of casting?

meta? that’s the CasterLevel … metamagic is stored in sMETA

Sorry … I posted the line I thought would have been safe not to delete. However, I “get” the fact that the meta ones may need deleting subject to who next cast the spell … but then, hence, my initialisation idea.

i.e.

int iMeta = GetMetaMagicFeat();
		
		if (iMeta == METAMAGIC_EMPOWER || iMeta == METAMAGIC_MAXIMIZE)
		{
			// store Metamagic on the weapon			
			AssignCommand(GetModule(), DelayCommand(fDur, DeleteLocalInt(oTarget, sMETA)));
		}
		
		SetLocalInt(oTarget, sMETA, iMeta);
1 Like

ah i get it, yep

1 Like

In fact, can’t it even just be … If you don’t want the extra delay zots hanging around…

		int iMeta = GetMetaMagicFeat();
				
		SetLocalInt(oTarget, sMETA, iMeta);

		// store Casterlevel halved on the weapon
		SetLocalInt(oTarget, sCL, iCasterlevel / 2);