xWilburCobbx Posted February 10, 2019 Share Posted February 10, 2019 (edited) 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 February 10, 2019 by smashballsx88 Link to comment Share on other sites More sharing options...
Evangela Posted February 10, 2019 Share Posted February 10, 2019 The only way to make the killer of an actor be the player is with the Kill function. The passed in actor will be considered the assailant. Also summons and followers: anyone they kill is counted as player kills. Link to comment Share on other sites More sharing options...
xWilburCobbx Posted February 10, 2019 Author Share Posted February 10, 2019 (edited) 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 February 10, 2019 by smashballsx88 Link to comment Share on other sites More sharing options...
ReDragon2013 Posted February 10, 2019 Share Posted February 10, 2019 (edited) 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 scriptattached 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 February 10, 2019 by ReDragon2013 Link to comment Share on other sites More sharing options...
xWilburCobbx Posted February 10, 2019 Author Share Posted February 10, 2019 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 More sharing options...
xWilburCobbx Posted February 10, 2019 Author Share Posted February 10, 2019 (edited) 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 February 10, 2019 by smashballsx88 Link to comment Share on other sites More sharing options...
ReDragon2013 Posted February 11, 2019 Share Posted February 11, 2019 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 More sharing options...
xWilburCobbx Posted February 12, 2019 Author Share Posted February 12, 2019 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 More sharing options...
xWilburCobbx Posted February 12, 2019 Author Share Posted February 12, 2019 (edited) 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 February 12, 2019 by smashballsx88 Link to comment Share on other sites More sharing options...
ReDragon2013 Posted February 13, 2019 Share Posted February 13, 2019 (edited) 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 - ENDFUNCTIONbasic 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 ENDFUNCTIONbasic, 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 February 13, 2019 by ReDragon2013 Link to comment Share on other sites More sharing options...
Recommended Posts