Jump to content

[LE] Make player responsible for actor death.


xWilburCobbx

Recommended Posts

I, for the first time with papyrus, am actually so stumped on something I am not entirely sure where to begin. I know for a fact I want to avoid SKSE for my first mod, but share your solution in SKSE if that's ALL you have.

 

My script chain is activated when the weapon is picked and equipped, that adds a Cloak ability to me, that adds a Spell too all nearby NPCs, which adds an ability that lasts for 5 min.

(So basically it is a dynamic script adding system, that only runs when the player has this weapon equipped, and cleans itself to prevent any bloating issues)

 

This ability is supposed to enhance a weapon with a global variable using the OnHit event. It looks like this:

Scriptname AAAxWCxWeapCorruptingDoomScript extends activemagiceffect  

GlobalVariable property SoulsGlobal auto

Actor property PlayerRef auto

Weapon property CorDoom auto

Event OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, bool abPowerAttack, bool abSneakAttack, bool abBashAttack, bool abHitBlocked)
  
  	Actor target = self.GetTargetActor()
	float DamageMult = SoulsGlobal.GetValue()
	
	if (akSource as weapon) == CorDoom
		utility.wait(0.3) ; This just makes sure the actual applied weapon enchantment has enough time to apply force
		if (abPowerAttack == true || abSneakAttack == true) && abHitBlocked != true
			if DamageMult < 100000
				DamageMult *= 0.02
			else
				DamageMult = 2000
			endif
			target.DamageActorValue("Health", DamageMult)
		elseif abHitBlocked == true && abPowerAttack != true
			if DamageMult < 100000
				DamageMult *= 0.005
			else
				DamageMult = 500
			endif
			target.DamageActorValue("Health", DamageMult)
		elseif abHitBlocked != true || (abHitBlocked == true && abPowerAttack == true)
			if DamageMult < 100000
				DamageMult *= 0.01
			else
				DamageMult = 1000
			endif
			target.DamageActorValue("Health", DamageMult)
		endif
	endif
endEvent

It works perfectly for me, except that it doesn't make the player responsible for the death of every actor it kills.

 

I would appreciate it if I could perhaps get an example of what needs to be done, or just point me in the right direction. So far, I tend to find my way around pretty well.

 

Also, if you have anything you would like to point out, please do, that's why I gave all the details.

Edited by smashballsx88
Link to comment
Share on other sites

I appreciate the response.

I feared as much, but I never thought to dissect summons and followers, I will give that a try a little later. Here is what I have found though.

ObjectThatThePlayerIsResponsibleFor.SetActorCause(PlayerRef)

I thought this one was interesting, but as far as I know, you can't cast ActiveMagicEffects as ObjectReferences. Not according this nifty little map anyways Script Objects Map. So I had no way to apply it, if only this worked backwards and could be used with ActiveMagicEffects!

So the next thing I have in mind is this code I found:

 

 

Actor Property PlayerRef auto
Int Property Bounty auto


Event OnEffectStart(Actor aktarget, Actor akCaster)

	If akTarget.HasLOS(PlayerRef) ; if the target can see the player while he commited the crime
	
		SentencePlayer(PlayerRef, Bounty) ;increase the player's bounty
	
	EndIf

EndEvent

 

 

 

This will probably my general solution, I would have to figure out how to properly set the LOS and then put my bounty. This wouldn't account for crime severity, nor would it utilize the witness system. I would have to create a whole custom scripted crime system for the ONE Frickin weapon that could use it! So either there is another way, or I am gonna just deal. It's not the end of the world, plus its an OP weapon, maybe leaving the kills under the radar isn't such a bad idea, hehehe. That might cause quest problems though, particularly if there is any such quest that requires, you alone, kill the target.

 

So ... Anyone else want to give it a go?

Edited by smashballsx88
Link to comment
Share on other sites

AAAxWCxWeapCorruptingDoomScript

 

Scriptname AAAxWCxWeapCorruptingDoomScript extends ActiveMagicEffect  
; This ability is supposed to enhance a weapon with a global variable using the OnHit event.

; https://forums.nexusmods.com/index.php?/topic/7386786-make-player-responsible-for-actor-death/
; smashballsx88 wrote: "My script chain is activated when the weapon is picked and equipped (that only runs when the player has this weapon equipped)"
; effect should clean up, if player unequip the weapon
; the effect is a Cloak ability to me and part of added spell too all nearby NPCs, which runs at least for 5 min

  GlobalVariable PROPERTY SoulsGlobal auto        ; Mandatory: GlobalVar to make weapon hits stronger
  Weapon         PROPERTY CorDoom     auto        ; Mandatory

  Actor target

; -- EVENTs -- 4

EVENT OnEffectStart(Actor akTarget, Actor akCaster)
    Debug.Trace(" OnEffectStart() - target = " +akTarget+ ", caster = " +akCaster)
    target = akTarget
ENDEVENT


EVENT OnEffectFinish(Actor akTarget, Actor akCaster)
IF ( target )
    target = None
    Debug.Trace(" OnEffectFinish() - target = " +akTarget+ ", caster = " +akCaster+ "  " +self)
ENDIF
ENDEVENT


EVENT OnDeath(Actor akKiller)
IF (akKiller == Game.GetPlayer())
    int i = target.GetFormID() % 0x01000000            ; formID without leading byte    0xAA123456 -> 0x00123456
    Debug.Notification("Player has killed " +i)        ; but output is decimal not hex
ENDIF
ENDEVENT


EVENT OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, Bool b1, Bool b2, Bool b3, Bool b4)
; b1 = abPowerAttack
; b2 = abSneakAttack
; b3 = abBashAttack
; b4 = abHitBlocked

IF ( akProjectile )
    RETURN    ; - STOP - /0    hit by arrow (almost)
ENDIF
;---------------------
IF (akSource == CorDoom as Form)
ELSE
    RETURN    ; - STOP - /1    actor which wears this ability, was not hit by the special weapon
ENDIF
;---------------------
IF ( b4 )
    RETURN    ; - STOP - /2    weapon hit was blocked
ENDIF
;---------------------
IF ( b1 )
    myF_Hit(4.0)
    RETURN    ; - STOP - /3    power attack strength
ENDIF
;---------------------
IF ( b2 )
    myF_Hit(2.0)
    RETURN    ; - STOP - /4    sneak attack strength
ENDIF
;---------------------
    myF_Hit(1.0)    ; normal attack
ENDEVENT


; -- FUNCTION --

;------------------------------
FUNCTION myF_Hit(Float fAttack)
;------------------------------
    float f = SoulsGlobal.GetValue()        ; f = DamageMult

; This just makes sure the actual applied weapon enchantment has enough time to apply force
    Utility.Wait(0.3)               ; !?

    IF (f < 100000.0)
        f = f * (0.01 * fAttack)    ; 0.02, 0.01, 0.005
    ELSE
        f = 1000.0 * fAttack        ; 2000, 1000, 500
    ENDIF

    SoulsGlobal.SetValue(f)                    ; make sure to store the value of our new multiplier
    target.DamageActorValue("Health", f)
ENDFUNCTION

 

 

 

The way you want to go with "ObjectThatThePlayerIsResponsibleFor.SetActorCause(PlayerRef)" is only possible (if it possible) by using an objectReference script

attached to weapon you want to to make 'super duper'.

; Event received when this object is equipped by an actor
Event OnEquipped(Actor akActor)
IF (akActor == Game.GetPlayer())
    self.SetActorCause(akActor)
ENDIF
EndEvent

; Event received when this object is unequipped by an actor
Event OnUnequipped(Actor akActor)
    self.SetActorCause(None)
EndEvent
Edited by ReDragon2013
Link to comment
Share on other sites

Thanks, this is all great!

 

So with your redesign of my script, I have never thought of using my own functions before. You inspired me to further research and I came across this wiki page. I would like to gather as much knowledge as I can, so if you know anything about custom functions, let me know. I have a menu script that repeats this segment 18 times!:

 

 

-----Snippet----- 

if aiButton == 0 ;Archery
	while abMenu3
		aiButton = SubMenu.Show()
			if aiButton == 0
				if ReqSouls <= CarriedSouls && PlayerRef.GetActorValue("Marksman") < 100
					if ReqSouls < SoulCap
						ReqSouls *= AmntToEncrRqSouls
					endif
					CarriedSouls -= ReqSouls
					ReqSoulsGlobal.SetValue(ReqSouls)
					CarriedSoulsGlobal.SetValue(CarriedSouls)
					UpdateSoulTxt.UpdateCurrentInstanceGlobal(ReqSoulsGlobal)
					UpdateSoulTxt.UpdateCurrentInstanceGlobal(CarriedSoulsGlobal)
					Game.IncrementSkill("Marksman")
				else
					FailSFX.play(PlayerRef)
					if ReqSouls > CarriedSouls
						Debug.Notification(Lacking)
					else
						Debug.Notification(Maxed)
					endif
				endif
				
			elseif aiButton == 1
				abMenu3 = false
				abMenu2 = true
			endif
	endwhile
	
-----Snippet----- 

 

 

Could probably use an optimization, hehehe.

 

Anyways, self.SetActorCause(akActor) this is pointless to put on the weapon itself, since Actors are already inherently responsible for all their equipped weapons, fired spells, and all that stuff. Not to say I didn't at least give it a go, but I can confirm this is ineffective.

 

I probably didn't specify very well, but what really happens, is the Cloak spell, has an Adding spell as its Assoc. Item 1 (This is because you can't associate abilities type spells). The Adding spell has a script that fires this akTarget.AddSpell(SpellToAdd) , which adds the AAAxWCxWeapCorruptingDoomScript ... and this is why the game won't make the connection between the kills and the player.

 

Hopefully this clears it up a bit more, I greatly appreciate all the help!

Link to comment
Share on other sites

WHAT!? I have been doing research on Papyrus all this time ... And never once have I even glanced at creating functions! :facepalm:

 

For practice I did this:

(This script is the one that runs on the weapon itself, its the one that adds that nifty little cloak spell)

Old

 

 

Scriptname AAAxWCxWeapCorruptingDoomEnchApply extends ObjectReference  

Spell property CloakSpell auto

Actor property PlayerRef auto

Weapon property CorDoom auto

Event OnContainerChanged(ObjectReference akNewContainer, ObjectReference akOldContainer)
	if PlayerRef.IsEquipped(CorDoom)
		PlayerRef.AddSpell(CloakSpell, false)
		self.SetActorCause(PlayerRef)
	endif
endEvent

Event OnEquipped(Actor akActor)
	if akActor == PlayerRef
		PlayerRef.AddSpell(CloakSpell, false)
		self.SetActorCause(PlayerRef)
	endif
endEvent

Event OnUnequipped(Actor akActor)
	PlayerRef.RemoveSpell(CloakSpell)
	self.SetActorCause(none)
endEvent

 

 

 

NEW

 

 

Scriptname AAAxWCxWeapCorruptingDoomEnchApply extends ObjectReference  

Spell property CloakSpell auto

Actor property PlayerRef auto

Weapon property CorDoom auto

Event OnContainerChanged(ObjectReference akNewContainer, ObjectReference akOldContainer)
	CheckIfEqByPlayer()
endEvent

Event OnEquipped(Actor akActor)
	CheckIfEqByPlayer()
endEvent

Event OnUnequipped(Actor akActor)
	debug.notification("Player removed the weapon")
	PlayerRef.RemoveSpell(CloakSpell)
	self.SetActorCause(none)
endEvent

Function CheckIfEqByPlayer()
	if PlayerRef.IsEquipped(CorDoom)
		debug.notification("Player equipped the weapon")
		PlayerRef.AddSpell(CloakSpell, false)
		self.SetActorCause(PlayerRef)
	endif
endFunction 

 

 

 

Oh well, time to get busy on doing some optimizations! Luckily I choose a smaller project to release as my very first mod, lol. Only 3 scripts need this thankfully.

 

This is why I always ask for ANY advice that could possibly be given to me, great contribution ReDragon2013! You have my deepest gratitude!

Edited by smashballsx88
Link to comment
Share on other sites

I believe you are smart enough to understand the different to your version. By the way It is always a good idea to use unique script names, but yours is a bit uncomfortable.

Why not this AAAxWCxDoomWeaponScript only?

 

AAAxWCxWeapCorruptingDoomEnchApply

 

Scriptname AAAxWCxWeapCorruptingDoomEnchApply extends ObjectReference  
; https://forums.nexusmods.com/index.php?/topic/7386786-make-player-responsible-for-actor-death/

  Spell  property CloakSpell auto
 ;Weapon property CorDoom    auto        ; UnUSED, same as self


; -- EVENTs -- 3

EVENT OnContainerChanged(ObjectReference akNewContainer, ObjectReference akOldContainer)
; if this event is triggered, player cannot have the weapon equipped (exception: another weapon with the same script exists ingame)

IF (akNewContainer == Game.GetPlayer() as ObjectReference)
    gotoState("Waiting")        ; ### STATE ###    to track player equip/unequip events
ELSE
    gotoState("")                ; ### STATE ###    player throw this weapon away, after unequip
ENDIF
ENDEVENT


;======================
state Waiting    ; only the player has it, it means akActor == Game.GetPlayer()
;============
    EVENT OnEquipped(Actor akActor)
        akActor.AddSpell(CloakSpell, false)
        self.SetActorCause(akActor)
        debug.notification("Player equipped myself.")
    ENDEVENT

    EVENT OnUnequipped(Actor akActor)
        akActor.RemoveSpell(CloakSpell)
        self.SetActorCause(None)
        debug.notification("Player unequipped doom weapon!")
    ENDEVENT
;=======
endState

 

 

Link to comment
Share on other sites

I really appreciate all the help!

 

I see what you did with my AAAxWCxWeapCorruptingDoomEnchApply . According to my understanding of the wiki, a state doesn't change, regardless of how many instances run. So if its in that "waiting" state, it will check for the equipped and unequipped states. Until it is removed from the player's inventory.

 

 

 


EVENT OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, Bool b1, Bool b2, Bool b3, Bool b4)
; b1 = abPowerAttack
; b2 = abSneakAttack
; b3 = abBashAttack
; b4 = abHitBlocked

IF ( akProjectile )
    RETURN    ; - STOP - /0    hit by arrow (almost)
ENDIF
;---------------------
IF (akSource == CorDoom as Form)
ELSE
    RETURN    ; - STOP - /1    actor which wears this ability, was not hit by the special weapon
ENDIF
;---------------------
IF ( b4 )
    RETURN    ; - STOP - /2    weapon hit was blocked
ENDIF
;---------------------
IF ( b1 )
    myF_Hit(4.0)
    RETURN    ; - STOP - /3    power attack strength
ENDIF
;---------------------
IF ( b2 )
    myF_Hit(2.0)
    RETURN    ; - STOP - /4    sneak attack strength
ENDIF
;---------------------
    myF_Hit(1.0)    ; normal attack
ENDEVENT


; -- FUNCTION --

;------------------------------
FUNCTION myF_Hit(Float fAttack)
;------------------------------
    float f = SoulsGlobal.GetValue()        ; f = DamageMult

; This just makes sure the actual applied weapon enchantment has enough time to apply force
    Utility.Wait(0.3)               ; !?

    IF (f < 100000.0)
        f = f * (0.01 * fAttack)    ; 0.02, 0.01, 0.005
    ELSE
        f = 1000.0 * fAttack        ; 2000, 1000, 500
    ENDIF

    SoulsGlobal.SetValue(f)                    ; make sure to store the value of our new multiplier
    target.DamageActorValue("Health", f)
ENDFUNCTION

 

 

 

My only real question here, is how does return work exactly? Does return stop the current function where it is at, and move on to the next?

Link to comment
Share on other sites

After working on this weapon for a considerable amount of time, I think I got it exactly how I want it. If there is anyone who can think of an idea to make the player responsible, still go for it.

 

This is how everything looks so far. I added comments regarding the reason why I omitted a lot of things, and wrote my own versions of each correction.

 

I also scrapped an enchantment that once made the weapon apply force to everyone it hit, once the weapon got strong enough. So the weapon has no official enchantments.

 

This is the function that adds a cloak ability. The cloak ability applies an adder spell to all nearby NPCs.

 

 

Scriptname AAAxWCxWeapCorruptingDoomEnchApply extends ObjectReference 

Spell property CloakSpell auto
Spell property DoomSpell auto

Actor property PlayerRef auto

Weapon property CorDoom auto

Event OnContainerChanged(ObjectReference akNewContainer, ObjectReference akOldContainer) ;OnEquipped will not fire if equipped right out of a container, it needs 2 checks.
		CheckIfEqByPlayer()                                                      ;SideNote You dont have to cast Actors as an ObjectReference
endEvent                                                                                 ;they are already an extension of an ObjRef

Event OnEquipped(Actor akActor) ;This checks if equipped normally
		CheckIfEqByPlayer()
endEvent

Event OnUnequipped(Actor akActor)
	if akActor == PlayerRef
		PlayerRef.RemoveSpell(CloakSpell)
	endif
endEvent

Function CheckIfEqByPlayer()
	if PlayerRef.IsEquipped(CorDoom)
		PlayerRef.AddSpell(CloakSpell, false)
	endif
endFunction 

 

 

 

I didn't see a point in setting a state if it had to be checked twice anyways. This will fire every time its move from a container, but I don't see this causing issues since its not possible for the player to do it in rapid succession. If another mod does that, then it probably could also cause issues with item gathering type side quests anyways.

 

The adder spell attached to the cloak ability uses a universal script that adds the doom weapon's ability to the NPCs.

 

 

Scriptname AAAxWCxSoulSuckScriptADD extends activemagiceffect

Spell Property SpellToAdd Auto
 
Event OnEffectStart(Actor akTarget, Actor akCaster)
	akTarget.AddSpell(SpellToAdd)
EndEvent

 

 

 

The doom ability runs this script on all the NPCs it attaches to.

 

 

Scriptname AAAxWCxWeapCorruptingDoomScript extends activemagiceffect  

GlobalVariable property SoulsGlobal auto

Actor property PlayerRef auto

Weapon property CorDoom auto

Actor target

Sound property Exp auto

Event OnEffectStart(Actor akTarget, Actor akCaster) ;I dont see a reason to need more then this, since the target is a local value, and should clear once the ability dispels
	target = akTarget ;Assigns the NPC the ability runs on, to an Actor reference that is used in the script
endEvent

Event OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, bool pA, bool sA, bool abBashAttack, bool hB)

	if (akSource) == CorDoom ;No need to have returns, since it already stops as soon as one of the IF conditions is met
		if (pA || sA) && !hB ;I wanted this to be conditional to recreate a sort of normalish combat calculation system
			TheResult(0.02) ;Max is 2000 damage if its a sneak or power attack
		elseif hB && !pA
			TheResult(0.005) ;Max is 500 damage if its a blocked regular hit
		elseif !hB || (hB && pA)
			TheResult(0.01) ;Max is 1000 if its a regular hit or a blocked power attack
		endif
	endif
	
endEvent

Function TheResult(float aMod) ;This starts the calculation with the passed in hit result
	float s = SoulsGlobal.GetValue() ;It gets the total value of my global count
	
	if s < 100000 ;This is a cap to make sure the weapon cant get infinitely powerful
		s *= aMod ;2%, 1%, or 0.5% of any soul amount between 0 and a 100,000
	else
		s = 100000 * aMod ;Enforces a hard cap, to prevent over calculating
	endif
	
	target.DamageActorValue("Health", s) ;I dont actually need to store my new value as a global, since the global is apart of another system, that this weapon is the beneficiary of
endFunction

 

 

 

Now the reason why the actual doom ability is distributed in this system, is to have it on actors in advance. This prevents form every first hit being a dud hit, and with a 5 min timer, it cleans it self up.

 

So, does it all look good? I put in a lot of hours of research today, so I think this is the best way to set everything up.

Edited by smashballsx88
Link to comment
Share on other sites

You asked: "My only real question here, is how does return work exactly? Does return stop the current function where it is at, and move on to the next?"

"return" does the same as "endevent" or "endfunction", but within the event or function body.

Exception is a function with return value, you need "return" here followed by number or variable or False/TRUE (depends on type of function return)

 

basic, which do nothing

EVENT OnCellLoad()
    RETURN   ; - STOP -
ENDEVENT

FUNCTION TestA()
    RETURN   ; - STOP -
ENDFUNCTION

basic with condition

FUNCTION TestB1(Int i)
IF (i > 0)
    Debug.Trace("i below Zero")
    RETURN   ; - STOP -
ENDIF
   Debug.Trace("i above Zero or equal")
ENDFUNCTION

FUNCTION TestB2(Int i)
IF (i > 0)
    Debug.Trace("i below Zero")
ELSE
   Debug.Trace("i above Zero or equal")
ENDIF
ENDFUNCTION

basic, but function has return value

 

Int FUNCTION TestC1(Int i)
IF (i > 0)
   RETURN i
ENDIF
   RETURN -1
ENDFUNCTION

Int FUNCTION TestC2(Int i)
IF (i > 0)
   RETURN i
ELSE
   RETURN -1
ENDIF
ENDFUNCTION

Bool FUNCTION TestD1(Int i)
IF (i > 0)
   Return TRUE
ENDIF
   Return False
ENDFUNCTION

Bool FUNCTION TestD2(Int i)
IF (i > 0)
   Return TRUE
ELSE
   Return False
ENDIF
ENDFUNCTION

 

 

Edited by ReDragon2013
Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...