Jump to content

Scripting Help Thread


sukeban

Recommended Posts

Greetings all.

 

I've taken the liberty of starting this thread because it seems like something sorely needed on these forums. I've also selfishly got a scripting conundrum, so that motivates me as well :biggrin: Hopefully this thread can become a place where novice scripters and script geniuses can come together to help each other make kickass mods.

 

In any case, I've been having some issues getting OnEvent functions (are they even functions?) to work properly. I have primarily been working in Papyrus fragments, but even within run of the mill scripts they have given me problems.

 

This has given rise to a more fundamental question of, "What even are OnEvent functions?" I know that when certain in-game events happen, the engine sends the info to Papyrus for potential use in scripts. This information is always generated, it is merely the job of our script to tap into it. That much makes sense to me. What is more problematic is how these functions are used, and, specifically, to what objects they are attached for use in the game.

 

--------

 

In my example, I have been trying to get Event OnContainerChanged to work with the PC as the container. However, this (as well as OnItemRemoved and OnItemAdded) seems to be a function written for placement on objects rather than containers, which, to me, seems counter-intuitive. I have used the following to try and obtain a positive condition for whenever the PC adds or drops an item from inventory:

 

Event OnContainerChanged(ObjectReference akNewContainer, ObjectReference akOldContainer)

Debug.Messagebox("Cocaine's one hell of a drug!")

If akNewContainer || akOldContainer == Game.GetPlayer()

Fragment_0()

RegisterforSingleUpdate(5)

Debug.Messagebox("Whatchu talkin' 'bout, Willis?")

EndIf

EndEvent

 

The thing is, neither of the Debugs ever fire, which means that the OnContainerChanged event never fires. Does this have to be attached to EVERY item the game (that would be insane) or do I have to do some other gymnastics with using the PC as a quest alias or something entirely different. Moreover, is Game.GetPlayer() even a container (doubt it)? If not, what IS the PC as a container (inventory)? Does this being a fragment have anything to do with this?

 

Additionally, RegisterForUpdate NEVER works properly for me. Whenever I have used it, it completely ignores he float aiInterval parameter and proceeds to update almost instantaneously, inevitably leading to backlogs and to save-game bloat (i.e. I can never use it in a mod).

 

In any case, I sincerely thank anybody who can answer these questions. I try not to ask anything unless it has stymied me for at least a couple of days, and man alive, this definitely has. Consider this me shining the Bat Symbol into the nighttime Gotham sky.

Link to comment
Share on other sites

You are using OnContainerChanged incorrectly. This event is sent to the item that is being moved into/out of a container. The player will never receive this event unless he/she is added to or removed from a container (which is never going to happen).

 

Instead, you should be using OnItemAdded and OnItemRemoved.

 

I don't know how you are using RegisterForUpdate, so I can't comment on that.

Link to comment
Share on other sites

Greetings fg109.

 

I aplogize, I never got to thank you before the "Need a Script" thread was closed. You wrote a fantastic "Timescale increases while crafting" script that I've found absolutely essential. So anyway, massive THANK YOU(!!) for your past heroics.

 

As for OnContainerChanged, I feared that was indeed the case. So you are saying that I could write something like:

 

Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)

Fragment_0()

RegisterforSingleUpdate(1)

EndEvent

 

Event OnItemRemoved(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer)

Fragment_0()

RegisterforSingleUpdate(1)

EndEvent

 

Would that be legit to not specify any conditional parameters? Basically, I'd want this function Fragment_0() (Papyrus Quest Fragment) to update whenever the PC's inventory changes. I chose RegisterforSingleUpdate because I only want it to happen once per inventory update.

 

(Also, I've never been able to get Events to work within a Papyrus fragment block. I assume this is because it is inside of another function. Is there a way to call the Event from outside of the fragment block, but from inside of the quest script? Or would this require use of the kMyQuest and another script added to the quest?)

 

Previously, the only way I could get this Fragment to update was when I had something like:

 

Fragment_0()

RegisterForUpdate(10)

 

...within the fragment function block of Fragment_0(). When it fired it would update at light-speed, completely ignoring the specified 10 real-time second parameter. Whenever I tried to use it with OnUpdate, something like:

 

Fragment_0()

RegisterForUpdate(10)

 

Event OnUpdate()

Fragment_0()

EndEvent

 

...it wouldn't do anything, i.e. it wouldn't update even once. I just KNOW that I am butchering this, but I tried to emulate what I saw in other Source scripts. The Wiki example scripts always SUCK and are just copy-pasted from the scripts that the functions are defined in. In any case, if you have any advice on using OnUpdate, I would again be immensely grateful :)

Edited by sukeban
Link to comment
Share on other sites

Calling a function like this:

 

Fragment_0()

only works if the function is also in the same script. If it's in a different script (eg your quest script), then you need to call it like this:

 

(MyQuest as MyQuestScript).Fragment_0()

Honestly, this is complicated to explain and I'm not sure how well you know how the scripting works.

 

Let's say you have a quest (eg Quest01). You decided to attach a script to it (eg Quest01Script). Then you decide to add some quest stages, and in those quest stages you write some code in the script fragments.

 

These quest fragment scripts become new functions (eg Fragment_0()) inside the quest fragments script. If you don't already have a quest fragments script, one will be generated for you (eg QF_Quest01_000D64). So at this point, your quest has two scripts on it (Quest01Script and QF_Quest01_000D64).

 

You also decide to add a reference alias to your quest (eg RefAlias01) with its own script (eg RefAlias01Script). In order for this script to use the Fragment_0(), your code has to look something like this:

 

Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)
(GetOwningQuest() as QF_Quest01_000D64).Fragment_0()
RegisterforSingleUpdate(1)
EndEvent

 

In the example, the reference alias script calls the Fragment_0 function (the code for one of the stages in your quest) in the quest fragments script. This actually does the exact same thing as calling SetStage on your quest, except that the stage doesn't change.

 

The reference alias script also calls RegisterForSingleUpdate(1). This tells the reference alias script to run the OnUpdate() in 1 second. This doesn't affect any of the other scripts. They will not run OnUpdate().

 

I hope this clarifies things for you, otherwise I would need to take a look at what you're trying to do in order to tell you how to fix it.

Edited by fg109
Link to comment
Share on other sites

Many thanks for the help so far! I've an okay grasp of how Papyrus should work "in theory," but often find that my actual skill at making it happen often just doesn't cut it. I am a serious scripting lightweight as yet, but I'd like to say that I've gotten a lot better for the three or so weeks that I've been at it :facepalm:

 

In any case, this is the function in question--just a quest fragment from the encumbrance portion of a mod I'm working on. It modifies the PC's speed based how much they are carrying vis-a-vis their max carryweight. It takes race and gender into consideration (ties into another mod of mine giving unique stats to different races/genders).

 

It works well upon load. It also works with the previously mentioned RegisterForUpdate avalanche that backs up Papyrus. I only want it to fire when a player actually modifies their inventory, rather than every arbitrary amount of time. Hence the preoccupation with OnItemAdded etc. Where would be the best place to locate: a) the Event trigger and b) the actual function. I am doubting that the proper place is in the quest fragment, but I am very unfamiliar with aliases, etc. You can see the OnItemAdded portion hanging out outside of the function block, doing absolutely nothing (but making for easy copy-pasta).

 

;BEGIN FRAGMENT CODE - Do not edit anything between this and the end comment
;NEXT FRAGMENT INDEX 2
Scriptname QF_HardcoreEncumbrance_02000D62 Extends Quest Hidden

;BEGIN FRAGMENT Fragment_0
Function Fragment_0()
;BEGIN CODE
;Hardcore Encumbrance

;VARIABLES AND CASTING

	;RACE
		ActorBase PlayerBase = Game.GetPlayer().GetBaseObject() as ActorBase
		Race PlayerRace = PlayerBase.GetRace()
			;Debug.Messagebox("You are a " + PlayerRace)

	;GENDER
		ActorBase PlayerSex = Game.GetPlayer().GetBaseObject() as ActorBase
		int PlayerGender = PlayerSex.GetSex()
			Debug.Messagebox("You are a " + PlayerGender)

	;CARRYWEIGHT + INVENTORY
		int Carryweight = Game.GetPlayer().GetActorValue("Carryweight") as int
			;Debug.Messagebox("Your max carryweight is " + Carryweight + " units")
		int Inventory = Game.GetPlayer().GetActorValue("InventoryWeight") as int
			;Debug.Messagebox("Your current inventory weight is " + Inventory + " units")

	;ENCUMBRANCE MULT
		float EncumbranceMult = Inventory / Carryweight as float
			;Debug.Messagebox("Your current EncumbranceMult is " + EncumbranceMult)
		
	;ENCUMBRANCEMODS																					;RACIAL BASE SPEED (SPEEDMULT)
		float EncumbranceModOrc = EncumbranceMult*8																	;80
		float EncumbranceModNord = EncumbranceMult*12																;85
		float EncumbranceModArgonian = EncumbranceMult*16															;90
		float EncumbranceModImperialAndHighElf = EncumbranceMult*20													;95
		float EncumbranceModBreton = EncumbranceMult*24																;100
		float EncumbranceModRedguardAndDarkElf = EncumbranceMult*28												;105
		float EncumbranceModKhajiit = EncumbranceMult*32																;110
		float EncumbranceModWoodElfMale = EncumbranceMult*36														;115
		float EncumbranceModWoodElfFemale = EncumbranceMult*40														;120

	;MOVESPEEDMODS
		float MovespeedModOrc = 80 - EncumbranceModOrc
		float MovespeedModNord = 85 - EncumbranceModNord
		float MovespeedModArgonian = 90 - EncumbranceModArgonian
		float MovespeedModImperialAndHighElf = 95 - EncumbranceModImperialAndHighElf
		float MovespeedModBreton = 100 - EncumbranceModBreton
		float MovespeedModRedguardAndDarkElf = 105 - EncumbranceModRedguardAndDarkElf
		float MovespeedModKhajiit = 110 - EncumbranceModKhajiit
		float MovespeedModWoodElfMale = 115 - EncumbranceModWoodElfMale
		float MovespeedModWoodElfFemale = 120 - EncumbranceModWoodElfFemale

;WORK
	If EncumbranceMult > 1.25
		Game.GetPlayer().ForceActorValue("SpeedMult", 1.0)
			;Debug.Messagebox("You are too overencumbered to move!")
	Else
		;Debug.Messagebox("You are able to move!")
			If PlayerRace == OrcRace
				If PlayerGender == 0
					Game.GetPlayer().ForceActorValue("SpeedMult", movespeedModOrc)
				Else
					Game.GetPlayer().ForceActorValue("SpeedMult", movespeedModNord)
				EndIf	
			ElseIf PlayerRace == NordRace
				;Debug.Messagebox("You are a Nord, again!")
				If PlayerGender == 0
					Game.GetPlayer().ForceActorValue("SpeedMult", movespeedModNord)
				Else
					Game.GetPlayer().ForceActorValue("SpeedMult", movespeedModArgonian)
					;Debug.Messagebox("You are a Nord female, again!")
				EndIf
			ElseIf PlayerRace == ArgonianRace
				If PlayerGender == 0
					Game.GetPlayer().ForceActorValue("SpeedMult", movespeedModArgonian)
				Else
					Game.GetPlayer().ForceActorValue("SpeedMult", movespeedModImperialAndHighElf)
				EndIf
			ElseIf PlayerRace == ImperialRace || HighElfRace
				If PlayerGender == 0
					Game.GetPlayer().ForceActorValue("SpeedMult", movespeedModImperialAndHighElf)
				Else
					Game.GetPlayer().ForceActorValue("SpeedMult", movespeedModBreton)
				EndIf
			ElseIf PlayerRace == BretonRace
				If PlayerGender == 0
					Game.GetPlayer().ForceActorValue("SpeedMult", movespeedModBreton)
				Else
					Game.GetPlayer().ForceActorValue("SpeedMult", movespeedModRedguardAndDarkElf)
				EndIf
			ElseIf PlayerRace == RedguardRace || DarkElfRace
				If PlayerGender == 0
					Game.GetPlayer().ForceActorValue("SpeedMult", movespeedModRedguardAndDarkElf)
				Else
					Game.GetPlayer().ForceActorValue("SpeedMult", movespeedModKhajiit)
				EndIf
			ElseIf PlayerRace == KhajiitRace
				If PlayerGender == 0
					Game.GetPlayer().ForceActorValue("SpeedMult", movespeedModKhajiit)
				Else
					Game.GetPlayer().ForceActorValue("SpeedMult", movespeedModWoodElfMale)
				EndIf
			Else
				If PlayerGender == 0
					Game.GetPlayer().ForceActorValue("SpeedMult", movespeedModWoodElfMale)
				Else
					Game.GetPlayer().ForceActorValue("SpeedMult", movespeedModWoodElfFemale)
				EndIf
			EndIf
	EndIf

;Debug.messagebox("ACK WTF")

;END CODE
EndFunction
;END FRAGMENT

;END FRAGMENT CODE - Do not edit anything between this and the begin comment

Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)
Fragment_0()
RegisterForSingleUpdate(1)
EndEvent

Event OnItemRemoved(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer)
Fragment_0()
RegisterForSingleUpdate(1)
EndEvent

;PROPERTIES

Race Property OrcRace Auto
Race Property NordRace Auto
Race Property ArgonianRace Auto
Race Property ImperialRace Auto
Race Property RedguardRace Auto
Race Property DarkElfRace Auto
Race Property KhajiitRace Auto
Race Property BretonRace Auto
Race Property HighElfRace Auto
Race Property WoodElfRace Auto

Edited by sukeban
Link to comment
Share on other sites

OnItemAdded and OnItemRemoved are native to the ObjectReference script. This means that only the ObjectReference script and anything that inherits from it can use them (otherwise the events are never received). Inheritance means that a child script will be able to use all the functions that its parent script can use. If script B extends A, then A is the parent of B. If script C extends B, then C has access to all the functions of A and B.

 

Your script extends Quest. Therefore it cannot use the native functions from the ObjectReference script. It wouldn't make sense anyway. It's not as though a quest has an inventory. Instead, to use those functions you need to put a script on the player. One way to do this is through a reference alias.

Link to comment
Share on other sites

Man alive, this is such a headache. I can't believe that I am finding it this horrifically difficult to simply call a function from another script. I have literally copy-pasted your:

 

(MyQuest as MyQuestScript).Fragment_0()

 

Into a script attached to my quest, a script that would like to call the function Fragment_0() from the QF script also attached to the quest. Changing your code to suit my circumstance, I end up with:

 

(HCE as QF_HCE_02000D62).Fragment_0()

 

When I attempt to compile this (just to see if it works) it gives me, "Missing EOF at RPAREN" and specifying the first ( before HCE. When I noobishly obey it and put it within a function or event block, it gets mad and says it was expected the function ID instead of the (. Obviously, I have gravely misunderstood something in your directions.

 

I was reading an article on The Engineering Guild re: calling functions and the author gave the same directions as you, but cryptically mentions that, "Now in the box marked begin or end, we type this [the above code]." In any case, that's totally lost on me. I've never seen such a box in any of the Quest tab menus. But apparently the code doesn't go in the actual script?

 

Massively apologies for being such a moron. I hadn't really thought it necessary to become familiar with how quests work or even with aliases because I hadn't/don't really care to create quests/dialogues/items/npcs, etc. Joke's on me, however, as those concepts seem to crop up everywhere. In any case, if you could direct me as to where to put your above code (modded for circumstance, of course), I would be so much relieved.

Link to comment
Share on other sites

Scriptname ExampleScript extends ReferenceAlias
{Script put on reference alias filled by player}

Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)
(GetOwningQuest() as QF_HCE_02000D62).Fragment_0()
EndEvent

Event OnItemRemoved(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer)
(GetOwningQuest() as QF_HCE_02000D62).Fragment_0()
EndEvent

I don't know what you mean by the Engineering Guild, but AFAIK a box marked begin or end that you can put your code into only appears for topic infos and packages.

Link to comment
Share on other sites

By the Nine, you've done it!

 

I added an Alias to the Player and moved the script from the main quest script area over to the Alias. Works like a charm, updating only when the PC adds/removes an item from inventory. Movement accelerates and decelerates seamlessly.

 

You, Sir, truly ARE the Patron Saint of Scripting. I feel like lighting innumerable hordes of votive candles in an alcove or burning kilos of incense in your honor. Sadly, I can't even honor you with a humble kudo, as I've long since turned one over to you. I guess the mental image of an anonymous fellow modder jumping around and playing air-base to:

 

 

...will have to suffice.

 

In any case, an incomprehensibly enormous "Thank you!" for helping me out. I'm sure I'll run into more problems in the future. It is hugely reassuring to know that scripting geniuses like you are members of the Nexus.

Link to comment
Share on other sites

fg, I've another question that perhaps you may hold the answer to.

 

You know how when the PC is over-encumbered they are unable to run or sprint? How exactly is that coded, and do you know, perchance, where I might find that Source script? I've checked many of the Actor-related scripts and cannot for the life of me find anything that deals with weight or encumbrance.

 

I ask because the sprinting animation looks ridiculous when the PC's movespeed is low. I would like to disable sprinting altogether when the PC dips below a certain SpeedMult threshhold (but is not yet technically over-encumbered).

 

I've also noticed that when modifying the SpeedMult on the PC, that the new speed doesn't register until the animation is altered (like going into sneak or drawing a weapon). This results in somewhat jerky speed transitions. Is there any function I can call after the OnItemAdded that will update the animation --> which would update the speed more smoothly and immediately?

 

Also, do you have any thoughts as to the feasibility of a real-time lockpicking/pickpocket mod? I've always thought that these trees would have been about one million times more useful if time continued to pass when you were busy engaging in these activities. It also just strikes me as a lot of fun, worrying about a real-time passing guard might detect you.

 

I'd imagine that such a mod would be either shockingly simple or stupidly difficult to achieve.

Edited by sukeban
Link to comment
Share on other sites

  • Recently Browsing   0 members

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