logo logo

 Back to main page

The NWNX Community Forum

 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 
 
Creature->DoDamage hook
Goto page 1, 2  Next
 
Post new topic   Reply to topic    nwnx.org Forum Index -> Windows development
View previous topic :: View next topic  
Author Message
Baaleos



Joined: 02 Sep 2007
Posts: 816

PostPosted: Sat Aug 31, 2013 23:19    Post subject: Creature->DoDamage hook Reply with quote

So ive managed to hook CNWSCreature::DoDamage(int) on windows,
it successfully allows me to bypass, and modify the damage, before it subtracts the health from the player.

Granted - the debug/chat text still displays the original values, but the damage itself is mitigated/changed in the hook.

I was wondering if anyone has experience with this function or hooking DoDamage?

My aim is to get a functional OnCreature damage hook, that works for player characters.


The current issues I am seeing is that
GetDamageDealtByType returns the damage from the damage inflicted before last - which means its one damage event late.

Eg: I inflict 290 Divine Damage
Hook captures the 290 number
I bypass it - set it to 0

Next attack does 300 Divine Damage
Hook captures 300 number
GetDamageDealtByType - Divine : returns 290
Bypass - set the damage to 0


Was wondering if anyone has any insight or a better method/memory location to hook?

Hook location in windows is
0x004A74D0


Code:

int __fastcall CNWSCreature__DoDamage(CNWSCreature * cre, void*, int arg1){

   ResetParameters();

   Cool.frames++;

   Cool.Event = 100;
   Cool.oTarget = cre->obj.obj_generic.obj_id;
   Cool.nData = arg1;

   Cool.Log( 1, "o DoDamage: %08lx\nTarget: %08lx\n", arg1, cre->obj.obj_generic.obj_id);

   Cool.ScriptRunning = true;
   (*NWN_VirtualMachine)->Runscript(&CExoString("nwnx_cool"), cre->obj.obj_generic.obj_id );
   Cool.ScriptRunning = false;

   if( Cool.ByPass ){
      Cool.ByPass = false;
      arg1 = 0;
      //return Cool.nRetn;
   }

   return CNWSCreature__DoDamageNext( cre,NULL, arg1 );
}



NWScript

else if( nEvent == 100){
          if(GetIsPC(OBJECT_SELF))
          {
            PrintString("Damage Inflicted to:"+GetName(OBJECT_SELF)+" - "+IntToString(nData));
            //SpeakString("Damage Dealt"+ IntToString( nData ));
            //ByPass( );
            //SpeakString("Bypassed!!");
            //string sData = GetCurrentCombatData(OBJECT_SELF,15);
            //SpeakString("Divine Damage = "+sData);
          }
    }



Im new to the c++ stuff, so if anyone can suggest improvements - please do.
Back to top
View user's profile Send private message
ShaDoOoW



Joined: 20 Aug 2005
Posts: 543

PostPosted: Sat Aug 31, 2013 23:39    Post subject: Reply with quote

Avoid executing a script if its possible to solve it within c code. Especially in timed situations like this it might change the order of events. Executing script is quite slow I had issues with this in my hook attempts.
_________________
Community Patch / NWNX Patch / NWNX Files / NWNX Connect
Back to top
View user's profile Send private message
Baaleos



Joined: 02 Sep 2007
Posts: 816

PostPosted: Sat Aug 31, 2013 23:44    Post subject: Reply with quote

But how else am I to hook it?

Are you saying I should modify the c code - to perform the check for Player characters, and then run the hook?


Ideally, I would like this hook to run at damage time, and then modify / ignore the damage if requested.

I think the check for object/player type should be easy enough to do in the c code - is that what you were referring to?

Do you have any input on if there would be a better place to hook, to both ignore the damage, but also get accurate damage by type as well as the target of the damage.

I see there is the CombatData layer/class type, I could hook that, which would provide the information in real-time.
But it looks more complex.

PS - I Managed to get possession working - but it is very unstable.
As soon as I possess- I can see all the faction members of the creature, as party members, and it gives me the ability to level up - but levelling up crashes the server, and logging out crashes too - even worse - logging out, will save the creature you are possessing over your BIC.
Highly dangerous - so I stopped working on it for now.

Couldn't get the unpossess to work.
Back to top
View user's profile Send private message
Baaleos



Joined: 02 Sep 2007
Posts: 816

PostPosted: Sat Aug 31, 2013 23:46    Post subject: Reply with quote

This is what my hook looks like now - with the optimization added in to prevent the script running unnecessarily for other creatures.

Code:

int __fastcall CNWSCreature__DoDamage(CNWSCreature * cre, void*, int arg1){

   if(cre->cre_is_pc)
   {
      ResetParameters();

      Cool.frames++;

      Cool.Event = 100;
      Cool.oTarget = cre->obj.obj_generic.obj_id;
      Cool.nData = arg1;

      Cool.Log( 1, "o DoDamage: %08lx\nTarget: %08lx\n", arg1, cre->obj.obj_generic.obj_id);

      Cool.ScriptRunning = true;
      (*NWN_VirtualMachine)->Runscript(&CExoString("nwnx_cool"), cre->obj.obj_generic.obj_id );
      Cool.ScriptRunning = false;

      if( Cool.ByPass ){
         Cool.ByPass = false;
         arg1 = 0;
         //return Cool.nRetn;
      }
   }

   return CNWSCreature__DoDamageNext( cre,NULL, arg1 );
}
Back to top
View user's profile Send private message
leo_x



Joined: 25 Aug 2010
Posts: 75

PostPosted: Sun Sep 01, 2013 13:50    Post subject: Reply with quote

Yo,

Pretty sure the only place CNWSCreature::DoDamage is ever called from is CNWSEffectListHandler::OnApplyDamage and that all damage is applied via EffectDamage effects.[0] The object/creature's last damage done array isn't updated until after the call to DoDamage, so that's why you're GetDamageDealtByType lagging behind.

I don't think CNWSCreature::DoDamage[1] would be a suitable place to hook for what you want unless you wanted to just modify the total damage from an EffectDamage (after all immunities, etc, which will still print in the combat log).

You could hook CNWSEffectListHandler::OnApplyDamage and modify the EffectDamage passed to it, I don't know if it would be easy or not to expose the effect to NWScript via nwnx_cool... but one thing to be aware of is that EffectDamage effects generated from combat contain modified damages and those created from scripts contain the damage amount before modification (in which case immunities, resists, reduction are calculated in this function)

[0] All damages done from a combat attack are contained in one EffectDamage effect, so the value passed into DoDamage in that case is the total damage dealt by the attack.

[1] One thing this function is handy for is if you can expose it to NWScript. you can use it to easily create scripted custom damage types that automagically handle temporary hitpoint effects.
_________________
the awakening (PW Action)
Back to top
View user's profile Send private message
Baaleos



Joined: 02 Sep 2007
Posts: 816

PostPosted: Sun Sep 01, 2013 13:58    Post subject: Will try something else Reply with quote

Thanks 4 the suggestion.
I will try that later tonight.

Some of the ideas I have for this is that some of my subraces are partially energy based, so I want to add a new mechanic, allowing them to convert physical damage etc, into say Divine Damage, which they might have more resistance to.
Or even make some subraces get healed by certain damages.
Back to top
View user's profile Send private message
Baaleos



Joined: 02 Sep 2007
Posts: 816

PostPosted: Sun Sep 01, 2013 20:43    Post subject: Reply with quote

I've done as suggested, and hooked the effect handler instead.

I get this type of output when I output the effect variables.



Quote:

0X1BB66BF0| 00010DC4| 38| 8 | 0 | 0.000000 | 0 | 0 | 7FFFFFFE| 0000033F| 1 | 1 | 0 | -1 | -1 | -1


from

Code:

int __fastcall CNWSEffectListHandler__OnApplyDamage( void * pThis, void*, CNWSObject * obj, CGameEffect * effect, int iArg )
{
   CNWSCreature * cre = (CNWSCreature *)obj;
   if(cre != NULL)
   {
      if(cre->cre_is_pc)
      {
         Cool.Log(1,"\
%#08X\
|\t%08X\
|\t%8i\
|\t%-6i\
|\t%-6i\
|\t%-11.6f\
|\t%-10i\
|\t%-10i\
|\t%08X\
|\t%08X\
|\t%-4i\
|\t%-4i\
|\t%-4i\
|\t%-4i\
|\t%-4i\
|\t%-4i\
\n",
         effect,
         effect->eff_id,
         effect->eff_type,
         effect->eff_dursubtype & 24,
         effect->eff_dursubtype & 7,
         effect->eff_duration,
         effect->eff_expire_day,
         effect->eff_expire_time,
         effect->eff_creator,
         effect->eff_spellid,
         effect->eff_is_exposed,
         effect->eff_is_iconshown,
         effect->eff_skiponload,
         effect->GetInteger(0),
         effect->GetInteger(1),
         effect->GetInteger(2)
         );
         
      }
   }
   return CNWSEffectListHandler__OnApplyDamageNext(pThis,NULL,obj,effect,iArg);
}



I cant see the damage amount anywhere in these numbers -even when converted to decimal.

My guess is that the effect that it picks up is a linked effect, and needs to be split out accordingly?
Will try that
Back to top
View user's profile Send private message
Baaleos



Joined: 02 Sep 2007
Posts: 816

PostPosted: Mon Sep 02, 2013 1:07    Post subject: Reply with quote

Don't suppose anyone can suggest a course of action?

I've examined the effect passed into this function from top to bottom with the methods available - but cant find anything inside the effect to say its damage type or damage amounts.
Back to top
View user's profile Send private message
Baaleos



Joined: 02 Sep 2007
Posts: 816

PostPosted: Mon Sep 02, 2013 1:56    Post subject: Reply with quote

Correction - think I have made progress
I wasn't examining all the ints contained in the effect.

I wrongfully assumed that since the first few ints were coming back as -1 that it meant that there was no more to examine.

Seems the damage before Immunities,reduction and resistances is contained in the 7th int.

I think the Damage Type might be 16 in the int array.
Im going to try and modify it now from ingame with the following methodology.


Code:

int __fastcall CNWSEffectListHandler__OnApplyDamage( void * pThis, void*, CNWSObject * obj, CGameEffect * effect, int iArg )
{
   CNWSCreature * cre = (CNWSCreature *)obj;
   if(cre != NULL)
   {
      if(cre->cre_is_pc == 1)
      {
         ResetParameters();
         Cool.frames++;
         Cool.Event = 101;
         Cool.oTarget = cre->obj.obj_generic.obj_id;
         Cool.nData = effect->eff_integers[6];
         Cool.nData2 = effect->eff_integers[15];
         
         Cool.ScriptRunning = true;
         (*NWN_VirtualMachine)->Runscript(&CExoString("nwnx_cool"), cre->obj.obj_generic.obj_id );
         Cool.ScriptRunning = false;                   

            if( Cool.ByPass )
            {

               int nDamType = obj->obj_vartable.GetInt( CExoString( "overridedamtype" ) );
               int nDamAmount = obj->obj_vartable.GetInt( CExoString( "overridedamamount" ) );
               obj->obj_vartable.SetInt(CExoString( "overridedamamount" ),-1,0);
               obj->obj_vartable.SetInt(CExoString( "overridedamtype" ),-1,0);
               if(nDamType > -1){
                  effect->eff_integers[6] = nDamType;
               
               }
               if(nDamAmount > -1){
                  effect->eff_integers[15] = nDamAmount;
               }
            Cool.ByPass = false;
            //return Cool.nRetn;
            }
            
         
      }

   }
   return CNWSEffectListHandler__OnApplyDamageNext(pThis,NULL,obj,effect,iArg);
}


My hope is that the script will run when the player receives a damage type,
the script can then analyse the specific damage type and amount, and optionally bypass, and at the same time as bypassing, modify the values for the Damage Type and Amount using local vars on the damage recipient.
The vars then get wiped back to -1 after being used.


Can anyone suggest a more effective way of doing this - this is my first big jaunt into c++ coding.


Correction - looks like its not those two indexes I originally thought.
Seems the effect passed into this function contains all damage dealt at the same time.

So in order to override specific damage types and amounts, you need to override about 16+ int values.

When I changed the value of the two ints described above - it allowed me to control the amount of damage before calculations, but it kept the damage type stuck.
When I changed my attack in game, it then added an extra damage type to the attack, when the attack was meant to do a solo damage type.

eg: I told my in game power to deal negative, but because the int value was going into the int array at a certain location, it was stuck doing divine.
The result was divine and negative both being applied with equal values.

I guess I will map the int array tomorrow, to find out which slot does what.
Back to top
View user's profile Send private message
leo_x



Joined: 25 Aug 2010
Posts: 75

PostPosted: Mon Sep 02, 2013 10:18    Post subject: Reply with quote

I'll post my notes about the layout for EffectDamage, hopefully it'll be useful to you:

int 0-12: Damages. -1 indicates no damage, this is distinct from 0, when it's 0 damage it will print in the combat log. Int 12 would be the base weapon damage, i.e. the aggregated physical damages from a combat attack, so this also means an EffectDamage created by combat ints 0, 1, 2 would be -1.

int 13: Always 1 if created via combat. Not set on effects created by script

int 14: Some kind of animation length/delay... Not sure exactly. Always 1000 if created from script.

int 15: Damage Flag. Only set when effect is created from script.

int 16: Damage Power. Only set when effect is created from script.

int 17: Always 1 if created via combat. Not set on effects created by script.

In the case of int 13 and 17 this probably flag that the effect is from damage or already modified by damage reducers.

The reason ints for damage flags and power are only set in the EffectDamages created by script is because they are used to do the immunity, resist, and reduction. Like I mentioned above all the damages in an EffectDamage created by an attack are already modified by immunity, resist, reduction in the damage roll, so they are not needed.
_________________
the awakening (PW Action)
Back to top
View user's profile Send private message
Baaleos



Joined: 02 Sep 2007
Posts: 816

PostPosted: Mon Sep 02, 2013 14:56    Post subject: thanks leo Reply with quote

Thanks Leo,
Im currently away on business, so I wouldnt have got to test the int's out myself for a few days.
You've saved me a bit of work.

I think I will map the specific damage positions by looping through the damage types, and apply a specific damage, and examine the logs to see what position that damage type appears at.

The other ints that you mentioned about power etc - they will be useful too if I want to override them.

Thanks again for the help.
Back to top
View user's profile Send private message
Baaleos



Joined: 02 Sep 2007
Posts: 816

PostPosted: Sun Sep 08, 2013 22:12    Post subject: Reply with quote

Hey guys,
Im not familiar with c++ basics with regards to string and int appending.
Can someone give this a glance over, and confirm if I have the basic gist right?

Basically I am setting the local var on the creature being attacked/hit with damage, for each of the damage types (from 0 to 11)
Then when the event script runs, those int values will be available, the nwscript can then pick those ints up, and decide on whether to modify and bypass, or not.

Have I done the string appending correctly?


Code:

int __fastcall CNWSEffectListHandler__OnApplyDamage( void * pThis, void*, CNWSObject * obj, CGameEffect * effect, int iArg )
{
   CNWSCreature * cre = (CNWSCreature *)obj;
   if(cre != NULL)
   {
      if(cre->cre_is_pc == 1)
      {
         ResetParameters();
         Cool.frames++;
         Cool.Event = 101;
         Cool.oTarget = cre->obj.obj_generic.obj_id;
         Cool.nData = effect->eff_integers[6];
         Cool.nData2 = effect->eff_integers[15];
         
[b]
                  int i;
               for (i=0; i< 12; i++)
               {
                  char * cData = new char[10];
                  //string sVar = "damage_%d";
                  sprintf( cData, "damage_%d", i );
                  int iNum = effect->eff_integers[i];
                  obj->obj_vartable.SetInt(CExoString( cData ),iNum,0);
                  delete cData;
               }[/b]
         Cool.ScriptRunning = true;
         (*NWN_VirtualMachine)->Runscript(&CExoString("nwnx_cool"), cre->obj.obj_generic.obj_id );
         Cool.ScriptRunning = false;                   

            if( Cool.ByPass )
            {
               [b]for (i=0; i< 12; i++)
               {
                  char * cData = new char[10];
                  //string sVar = "damage_%d";
                  sprintf( cData, "damage_%d", i );
                  int nDamAmount = obj->obj_vartable.GetInt( CExoString( cData ) );
                  effect->eff_integers[i] = nDamAmount;
                  delete cData;
               }[/b]
            Cool.ByPass = false;
            //return Cool.nRetn;
            }
            
         
      }

   }
   return CNWSEffectListHandler__OnApplyDamageNext(pThis,NULL,obj,effect,iArg);
}
Back to top
View user's profile Send private message
addicted2rpg



Joined: 01 Aug 2008
Posts: 106

PostPosted: Tue Sep 10, 2013 19:06    Post subject: Reply with quote

If your damage is a three digit number you risk an overflow ( 7 + null + length(%d)).

I might bump it up a little. Also, try to get those declarations outside those loops. What you're doing to the new() operator there is abuse.

Declare the buffer at the start of the function, and re-use it through the whole function, and free it at the end. Some bridge/trampoline hooks overwrite the return address, so the stack may not get properly cleaned (I'm not sure the specifics of yours atm). In general C++ the new() operator will release the memory at the end of the function (assuming it was to a local variable), but it may be different in hooks functions, so there is an advantage to deleting before calling the original.
Back to top
View user's profile Send private message
Terra_777



Joined: 27 Jun 2008
Posts: 216
Location: Sweden

PostPosted: Tue Sep 10, 2013 23:23    Post subject: Reply with quote

Dynamically allocated memory using new must be released with delete, its not automatically released.
_________________
I dun have any signature, I'm happy anyway.
Back to top
View user's profile Send private message Send e-mail MSN Messenger
addicted2rpg



Joined: 01 Aug 2008
Posts: 106

PostPosted: Wed Sep 11, 2013 2:58    Post subject: Reply with quote

Your right. I'm not even sure what I was thinking. Sounds more like a description of references in a garbage collector, lol.
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    nwnx.org Forum Index -> Windows development All times are GMT + 2 Hours
Goto page 1, 2  Next
Page 1 of 2

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group