Aller au contenu

Photo

SpellHooking (for PC OnSpellCastAt events)...how?


  • Veuillez vous connecter pour répondre
26 réponses à ce sujet

#1
Darin

Darin
  • Members
  • 282 messages
 Specifically, how do I do a "OnSpellCastAt" script for the player?  Is it the module script "On User Defined Event" and then in the case: EVENT_SEPLL_CAST_AT?  Would that handle all, or just the PC?

**EDIT**
Looks like the way to do this is Spell-hooking... but the tutorial/nwnlexicon recently went down... so... how does one do this?

Modifié par EpicFetus, 19 juin 2013 - 07:48 .


#2
diophant

diophant
  • Members
  • 116 messages
The module script should be called when an event is sent to the module, not the player, so this won't work.
I can think of two possible ways:
First, you could use spellhooking, and check there wether the PC is targeted. However, it will be difficult to get it to work with AOE spells.
Second, in NWN1,you could write a script and call it "default". It was triggered on any PC event. So you had to check whether the event was a SpellCastAt event, and then you could run your code. However, I'm not sure if this still works in NWN2.

What exactly are you tryind to do? Maybe we can tailor a special solution for your use case.

#3
Darin

Darin
  • Members
  • 282 messages
Trying to script a magestar for Haunted Halls (and various other things, but that's the one that prompted the question).

Basic idea: item script on acquire has the item activate (activates by touch) assuming you have a caster level (not perfect if you never cast the spells you have, but still)

item does the following (in 2nd edition) (3rd ed convert here: http://oracle.wizard...0modern-l&P=278 but I'm sticking to the looser 2nd ed rules to allow freedom in scripting)

-Absorbs spells cast at you (if that were all could run spellabsoprtion event on the PC and be done with it, but...)
-keeps the "levels" it absorbs as energy
-uses these levels to heal you if you fall unconscious (1hp per level)
-can pass from person to person

At least, the above is what I'll have it do (all hostile instead of at will, when you drop instead of at will, etc.)

So I need an OnSpellCastAt and OnDamage for the Player (for NPCs this is easy, since they have those)

#4
Kaldor Silverwand

Kaldor Silverwand
  • Members
  • 1 585 messages
Would events on an equipped skin work? I don't know, just a thought.

#5
Darin

Darin
  • Members
  • 282 messages
So, equip a skin to the player...weird.

#6
diophant

diophant
  • Members
  • 116 messages
Yes, a skin seems to be the easiest solution (if they have an OnSpellCastAt slot - I'm not at my toolset to check that). How could I forget about it!

#7
Darin

Darin
  • Members
  • 282 messages
They seem to have an OnCastSpellAtItem slot...generally speaking, you target the wearer, not the skin...seems like this wouldn't work for this sort of thing.


// i_temp_ci
/*
Template for Casting Spell on item script.
This script runs when a spell is cast on an item.

How to use this script:
Replace the word "temp" (in line 1) with the tag of the item. Rename the script with this name.

Additional Info:
In general, all the item "tag-based" scripts will be named as follows:
- a prefix ("i_" by defualt)
- the tag of the item
- a postfix indicating the item event.

This script will be called automatically (by defualt) whether it exists or not. If if does not exist, nothing happens.

Note: this script runs on the character who cast the spell on the item.
-ChazM
*/
// Name_Date

#include "x2_inc_switches"

void main()
{
object oPC = OBJECT_SELF; // The player who cast the spell
object oItem = GetSpellTargetObject(); // The item targeted by the spell
int iSpell = GetSpellId(); // The id of the spell that was cast
// See the list of SPELL_* constants
//Your code goes here


SetExecutedScriptReturnValue(X2_EXECUTE_SCRIPT_END); // prevent spell that was cast from taking effect
//SetExecutedScriptReturnValue(X2_EXECUTE_SCRIPT_CONTINUE); // this will allow spell to take effect
}

Modifié par EpicFetus, 19 juin 2013 - 07:28 .


#8
Darin

Darin
  • Members
  • 282 messages
Looks like SpellHooking is the way to do it... but the tutorial on that is down...

#9
Dann-J

Dann-J
  • Members
  • 3 161 messages
You can add an OnHit item property to the skin, and use a tag-based OnHit script. That approach works when you damage something physically (I've successfully tested it). I don't know if spell damage counts as an OnHit event though (that's not something I've tested). Even then, spells that don't do any actual damage might not fire the OnHit event.

Whether or not you can determine from that script if the damage was caused by a spell or a physical attack is another question entirely.

#10
Dann-J

Dann-J
  • Members
  • 3 161 messages
Also; if the player can have their own user-defined event script (they usually start with 'ud_'), the OnSpellCastAt event is number 1011.

#11
Darin

Darin
  • Members
  • 282 messages
Spell-Hooking seems to be the way to go; copied the script to my campaign scripts and added this:

object oPC = oTarget;
int nSpell = GetLastSpell();
int nHarm = GetLastSpellHarmful();
int nSpellLevel = GetSpellLevel(nSpell); //L-E-V-E-L

//Magestar System
/*
Blocks the spell, gives the Magestar it's props
*/
if(GetLocalInt(oPC,"nMagestar")==1)
{
if(nHarm)
{
//Block it, get the spell level, apply to Magestar
object oMagestar = GetLocalObject(oPC,"oMagestar");
int nPool = GetLocalInt(oMagestar,"nPool");
SetLocalInt(oMagestar,"nPool",nPool+nSpellLevel);
Magestar(oMagestar);
return FALSE;
}
}

where "Magestar is in my include (and checks to see how many charges the magestar has; if too many causes an explosion, if too few it fades away); T

he Aquire and Unaquire set up the vfx and variables on the creature.

Had if(GetIsPlayerOwned(oTarget)), but realized this should work for NPCs as well (this bit of script is before it checks for Player or DM control and passes through if not).

Haven't tested the spell-effect yet, but the fade, heal, and vfx work well enough (didn'tdo the ondeath bits yet)

#12
diophant

diophant
  • Members
  • 116 messages
You should also add support for AoE spells. Check the 2da whether the spell is an WoE spell. If so, loop over all creatures in the AoE (like it is e.g. also done in the fireball spell). If any of them has a magestar, do your stuff and abort the script. Yes, this protects creatures close to the magestar-wearer from AoE spells, but this fits its description.

I'm not sure whether GetLastSpellHarmful returns what you expect, or if it just returns whether the last spell script did any damage. The spell script didn't even start, so maybe it returns the result of the previous spell.However, you could use the HostileSetting entry from the 2da.

#13
Darin

Darin
  • Members
  • 282 messages
Interesting... can the aoe thing be done in the spell-hooking script, or would it be a mod to all spells?

#14
diophant

diophant
  • Members
  • 116 messages
You can do everything in the spellhooking script. Unfortunately, I realized that the 2da file does not tell you whether a spell is an AoE spell or not; it's all coded in the spell scripts. Moreover, AoE spell is not the correct check, because also spells like fireball and cone of cold must be considered, which are not AoE spells. Thus, you must write two functions where you return for each spell the spell shape and the shape size. To get these values (especially the size), check the original spell scripts.
I implemented the main functions, but you still have to complete the functions for spell shape and size.

#include "x2_inc_switches"

void Magestar(object oMagestar)
{
	// !!TODO
}


// checks whether oTarget has a Magestar; if so, does the magestar stuff and returns TRUE.
int checkMagestar(object oTarget, int nSpellLevel)
{
	if(GetLocalInt(oTarget,"nMagestar")==1)
	{
		//Block it, get the spell level, apply to Magestar
		object oMagestar = GetLocalObject(oTarget,"oMagestar");
		int nPool = GetLocalInt(oMagestar,"nPool");
		SetLocalInt(oMagestar,"nPool",nPool+nSpellLevel);
		Magestar(oMagestar);
		return TRUE;
	}
	return FALSE;
}


int getSpellShape(int nSpell)
{
	switch (nSpell)
	{
	// !!TODO: add all spell with a circular shape here
	case SPELL_FIREBALL:
	case SPELL_STINKING_CLOUD:
		return SHAPE_SPHERE;
	// !!TODO: add all spells with a cone shape here 
	case SPELL_CONE_OF_COLD:
		return SHAPE_CONE;
	default: return -1;       // single target spell
	}
	return -1;
}


float getShapeSize(int nSpell)
{
	switch (nSpell)
	{
	case SPELL_FIREBALL: return RADIUS_SIZE_HUGE;
	// !!TODO: return for each spell which is not single targeted its shape size
	default: return 0.0;    
	}	
	return 0.0;
}


void main()
{

	object oTarget = GetSpellTargetObject();
	location lTarget;
	int nSpell = GetLastSpell();
	int nHarm = StringToInt(Get2DAString("spells", "HostileSetting", nSpell));
	int nSpellLevel = GetSpellLevel(nSpell); //L-E-V-E-L
	int nShape = getSpellShape(nSpell);
	float fShapeSize = getShapeSize(nSpell);
	int bMagestarFound = FALSE;
	
	//Magestar System
	/*
	Blocks the spell, gives the Magestar it's props
	*/
	if (nHarm)
	{
		if (nShape == -1)   // single target
		{
			bMagestarFound = checkMagestar(oTarget, nSpellLevel);
		}
		else
		{
			if (GetIsObjectValid(oTarget))  // spell was cast on an object
			{
				lTarget = GetLocation(oTarget);
			}
			else                             // spell was cast at the ground
			{
				lTarget = GetSpellTargetLocation();
			}
			oTarget = GetFirstObjectInShape(nShape, fShapeSize, lTarget);
			while (GetIsObjectValid(oTarget))   // loop over all creatures in spell shape
			{
				// if oTarget has a magestar, switch bMagestarFound to TRUE, otherwise let it unchanged
				bMagestarFound |= checkMagestar(oTarget, nSpellLevel);			
				oTarget =GetNextObjectInShape(nShape, fShapeSize, lTarget);
			}
		}
		if (bMagestarFound)     // at least one creature had a magestar
		{
			SetModuleOverrideSpellScriptFinished();
			return;
		}
	}	
}


EDIT: what the heck happened to my indentions! :crying:

Modifié par diophant, 24 juin 2013 - 04:33 .


#15
MasterChanger

MasterChanger
  • Members
  • 686 messages

diophant wrote...

You can do everything in the spellhooking script. Unfortunately, I realized that the 2da file does not tell you whether a spell is an AoE spell or not; it's all coded in the spell scripts. Moreover, AoE spell is not the correct check, because also spells like fireball and cone of cold must be considered, which are not AoE spells. Thus, you must write two functions where you return for each spell the spell shape and the shape size. To get these values (especially the size), check the original spell scripts.
I implemented the main functions, but you still have to complete the functions for spell shape and size.


Dio, you're overlooking something. While, yes, the true shape of the spell is defined in the script, the 2da does have a convenient way to figure out the target area. The field is called TargetingUI, which links to spelltarget.2da (an under-analyzed 2da that I have a bit of experience with). While there isn't anything to say that the targeting UI shape has to be the same as whatever is set in the script, it should really be good enough for these purposes.

#16
Dann-J

Dann-J
  • Members
  • 3 161 messages
I used the TargetingUI column in spells.2da in a wild magic script of mine, to determine how many targets to affect when spells go pear-shaped. You really take a chance throwing an AOE spell in that area - you could end up accidentally healing your enemies instead, or polymorphing them into much bigger and nastier creatures.

#17
diophant

diophant
  • Members
  • 116 messages

MasterChanger wrote...

Dio, you're overlooking something. While, yes, the true shape of the spell is defined in the script, the 2da does have a convenient way to figure out the target area. The field is called TargetingUI, which links to spelltarget.2da (an under-analyzed 2da that I have a bit of experience with). While there isn't anything to say that the targeting UI shape has to be the same as whatever is set in the script, it should really be good enough for these purposes.


You never stop learning :-) That simplifies getSpellShape and getSpellSize a lot.

int getSpellShape(int nSpell)
{
        int nShape = StringToInt(Get2DAString("spells", "TargetingUI", nSpell));
        string sShapeName = Get2DAString("spelltarget", "Shape", nShape);

        if (sShapeName == "point")
                return -1;
        if (sShapeName == "circle")
                return SHAPE_SPHERE;
        if (sShapeName == "cone")
                return SHAPE_CONE;
// what should we do in the following cases?
//      if (sShapeName == "rectangle")
//              return -2;
//      if (sShapeName == "bolt")
//              return -2;
        return -1;
}


float getShapeSize(int nSpell)
{
        int nShape = StringToInt(Get2DAString("spells", "TargetingUI", nSpell));
        return StringToFloat(Get2DAString("spelltarget", "Width", nShape));
}

Is there an easy way to handle spell shapes "bolt" and "rectangle"? 

#18
Darin

Darin
  • Members
  • 282 messages

This may seem like necroposting, but I've been adding to my spellhooking script...and for some reason the nSpell is -1...no matter what I cast or who casts is.

void main()
{
	   	
	object oTarget = GetSpellTargetObject();
	int nSpell = GetLastSpell();
	int nHarm = GetLastSpellHarmful();
	int nSpellLevel = GetSpellLevel(nSpell); //L-E-V-E-L
	
	SendMessageToPC(OBJECT_SELF,"Spell is "+IntToString(nSpell));
		

I get back "Spell is -1", for Light and Resistance.



#19
Darin

Darin
  • Members
  • 282 messages

okay, unless I aim the spell at an object covered in the hook, then it's 100, but only for the PC (for light)



#20
Darin

Darin
  • Members
  • 282 messages

Solved...don't use GetLastSpellCast(); Use GetSpellId().

 

Last gives weird results...sometimes what you want, sometimes not.

 

Id gives the spell being cast...so, if you're using if(nSpell==....) to determine what to run, use Id.



#21
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages

Hi All,

!!!!!!!!!! NEVER ADD SKINS TO PCS !!!!!!!!!!!!
 
I have not read every post in this thread yet, but NEVER add skins to PCs. Here's why:-

http://worldofalthea...k/search?q=skin :-
 
11 October 2010: A short while ago, I discovered that using a skin in a PC's creature armour slot caused problems when the PC later equipped or unequipped armour: The armour penalty figures would eventually start to miscalculate and give errors with skill scores. At the time, I thought I resolved the problem by using an adapted creature bite object instead. Unfortunately, only this week, I discovered that this adapted item also caused problems, but this time with unarmed combat: If a PC with the adapted bite item on them attacked an object with unarmed combat, then only bonus strength damage would be applied. This was obviously not going to work and I came to the conclusion that skin items did not work anymore with NWN2.

Cheers,
Lance.

EDIT: Spellhooking is the way to go ... It fires prior to a spell being cast as long as a spell has this line at the beginning:-
 
if (!X2PreSpellCastCode()){return;} 
 
Here is the first few lines of my own alb_spellcast spell-hook script to give you some info, (which I use quite extensively in spells and crafting). I think the comments are still valid:-

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SPELL HOOKING (THIS SCRIPT) DOES NOT APPEAR TO FIRE IF THE PLAYER IS NOT POSSESSING THE PC AT THE TIME THE
// SPELL IS BEING CAST. i.e. IF THE COMPANION CASTS A SPELL USING ITS AI THIS SCRIPT IS NOT CHECKED.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void main()
{
// OBJECT_SELF is the PC in this script by default. (LB)

object oPC = OBJECT_SELF; //CASTER
// SendMessageToAllPCs("CASTER >> " + GetName(oPC));

object oItem = GetSpellCastItem();

object oTarget = GetSpellTargetObject();

location lLocation = GetSpellTargetLocation();
int SpellID = GetSpellId();
int iSpellLevel = GetSpellLevel(SpellID);
int nLevel = CIGetSpellInnateLevel(SpellID,TRUE);
string sTag = GetTag(oItem);
string sRes = GetResRef(oItem);
int Charges = GetItemCharges(oItem);
string sName = GetFirstName(oItem); // FOR RECOVERY
int CASTSET = GetLocalInt(oItem, "CASTLEVEL"); // FOR RECOVERY

int nCasterLvl = GetCasterLevel(OBJECT_SELF);


  • Loki_999 aime ceci

#22
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages

Would events on an equipped skin work? I don't know, just a thought.

 
 

So, equip a skin to the player...weird.

 
 

Yes, a skin seems to be the easiest solution (if they have an OnSpellCastAt slot - I'm not at my toolset to check that). How could I forget about it!


You can add an OnHit item property to the skin, and use a tag-based OnHit script. <SNIP>


See my last post ... Unless you guys can tell me it is possible in some way to get around these skin issues?

Cheers,
Lance.



#23
Dann-J

Dann-J
  • Members
  • 3 161 messages

The ability penalties applied to the player at the beginning of Mask of the Betrayer are achieved by equipping a 'creature hide' on the PC. That prevents resting or restoration spells from removing the penalties. It also has the side effect of causing an exported character to continue to have the penalties, unless you wait until the cutscene that destroys the 'creature hide'.



#24
kevL

kevL
  • Members
  • 4 056 messages

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SPELL HOOKING (THIS SCRIPT) DOES NOT APPEAR TO FIRE IF THE PLAYER IS NOT POSSESSING THE PC AT THE TIME THE
// SPELL IS BEING CAST. i.e. IF THE COMPANION CASTS A SPELL USING ITS AI THIS SCRIPT IS NOT CHECKED.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////


Lance,
to get spellhooking to work on non-player controlled characters, set

X2_L_WILD_MAGIC = TRUE

on the area-object.

 

 

ps. Here's some more goodies for spellhooks

GetSpellId(); // returns the SPELL_* constant of the spell cast
GetSpellTargetObject(); // returns the targeted object of the spell, if valid
GetSpellTargetLocation(); // returns the targeted location of the spell, if valid
GetLastSpellCastClass(); // gets the class the PC cast the spell as
GetSpellCastItem(); // if an item cast the spell, this function gets that item
GetSpellSaveDC(); // gets the DC required to save against the effects of the spell
GetCasterLevel(OBJECT_SELF); // gets the level the PC cast the spell as


#25
Loki_999

Loki_999
  • Members
  • 430 messages

re: Never add skins to PCs... erm, ooops. Oh well, i'm screwed then. I've used creature hides in a number of places for different effects over the years.

 

It certainly may explain some strange things that happen with our Lycanthrope playable races : https://github.com/A...rs-PW/issues/86