Jump to content

[LE] Noob papyrus questions


candlepin

Recommended Posts

  • Replies 89
  • Created
  • Last Reply

Top Posters In This Topic

So I will try to get us back on topic.

 

The reason that I say to skip the perk for compatibility reasons is that the perk will block the old item from ever entering the inventory. That means that if any mod has Story Manager quests triggered by acquiring that item or has their own aliases/magic effects set up to detect it, then those mods' events will never be triggered. Of course that may be desirable or it may be undesirable – that would depend on exactly what kind of items you are replacing.

 

Another concern MAY be whether or not some of these items are used as quest aliases or otherwise referenced in scripts. In order to avoid breaking those quests/scripts, you may wish to ignore items that are acquired if they are persistent objects, in which case the script would look something like:

formlist property OldItems auto
formlist property NewItems auto

Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)
	if(akItemReference == none)
		actor PlayerREF = GetReference() as actor
		int i = OldItems.Find(akBaseItem)
		PlayerREF.RemoveItem(akBaseItem, aiItemCount)
		PlayerREF.Additem(NewItems.GetAt(i), aiItemCount)
	endif

endEvent

But again, that would depend on what exactly these items are and the relative risk that they would be used by other mods in this manner.

 

Some of the debates here were in my opinion well out into the weeds, but still relevant to the topic at hand.

 

In my opinion the difference between using GetReference() to get the actor each time the function is called, using it once when the alias is initialized, or storing it in a property is so negligible it is barely worth debating. Use whichever is more comfortable for you. Personally I use GetReference() each time the function is called because as a habit I try not to store extra variables. But seriously, it's not even worth worrying about.

 

The only other thing I would say is that I try to avoid OnInit() blocks whenever possible. I don't feel a need to get into the weeds yet again here, but I have Reasons. Much like the debate between PlayerREF as a property or using GetReference(), we're talking about probably meaningless differences here so it's not worth getting hung up on. However I would move the OnInit() function of this script to a stage of the quest with the Start Up stage box checked and execute the functions that way.

Link to comment
Share on other sites

First, I'd really like to thank all the constructive feedback. It's helped greatly with my understanding of Papyrus syntax and what can & can't be done. Sorry it's taken me a while to respond; there was a lot of reading & learning going on while I processed the thoughtful responses.

 

I would also like to kindly request that responses to this thread stick to topic. It isn't very helpful to have back-and-forth sniping that can only serve to get this thread locked. I'm ok with constructive feedback (including between those who are responding to my thread), with the emphasis on constructive feedback. Also, please don't reference old threads or bring up old grudges here. It won't help anyone.

 

I still have a few questions & clarifications that I'd love to get feedback on. Specifically, about the two scripts that were posted:

 

The perk script replaces normal activation of gourds (conditioned on the perk) with deleting the old and adding the new, instead of just taking the old. The script on the player alias fires when an old gourd (filtered oninit) enters your inventory, removes it and adds the ingredient version, for the cases in which you aquire one by any means not involving taking them from thw world.

 

Having more than one item doesn't really complicate it, just make a couple matched formlists for your old and new items.

 

Can it be made better? who knows, i would have done it this way:

Scriptname ItemReplacer extends ReferenceAlias
{Script on a player alias, OldItems and NewItems must be matched by index, i.e. The 5th item on one is replaced by 5th in the other and so on.}
 
FormList property OldItems auto
FormList property NewItems auto
ObjectReference property PlayerRef auto

Event OnInit()
	If PlayerRef.GetItemCount(OldItems)
		Int i = OldItems.GetSize() 
		while i
			i -= 1
			Form OldForm = OldItems.GetAt(i) As Form
			int Count = PlayerRef.GetItemCount(OldForm)
			If Count
				Form NewForm = NewItems.GetAt(i) As Form
				PlayerRef.RemoveItem(OldForm, Count, True)
				PlayerRef.AddItem(NewForm, Count, True)			
			Endif
		EndWhile
	EndIf
	AddInventoryEventFilter(OldItems)
EndEvent

Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)
	Int i = OldItems.Find(akBaseItem)
	If i > -1
		Form NewForm = NewItems.GetAt(i) As Form
		PlayerRef.RemoveItem(akBaseItem, aiItemCount, true)
		PlayerRef.AddItem(NewForm, aiItemCount, true)
	Endif
EndEvent

Making it reversible should be possible, can't help with the MCM, no experience, but the script could be easily modified to swap the formlists and do the item swaping the other way around.

Thanks for your feedback, FrankFamily. If I understand your script properly, the first Event would swap out all the items from the OldItems list within the player's inventory with the corresponding items from the NewItems list when the script is initialized - OnInit(). Which basically would mean when this script is activated (e.g. upon loading in the game). And the second Event would perform the same function but would do so whenever the player adds an item to their inventory. Am I understanding your code correctly?

 

@FrankFamily, I would have gone with the formlist approach too.

 

As far as reversible it is possible. If you make the MCM option a toggle and use it to set a global variable you could do something like the following:

 

 

Scriptname ItemReplacer extends ReferenceAlias
{Script on a player alias, OldItems and NewItems must be matched by index, i.e. The 5th item on one is replaced by 5th in the other and so on.}
 
FormList property OldItems auto
FormList property NewItems auto
ObjectReference property PlayerRef auto
GlobalVariable Property SwapGV auto
;0=old->new 1=new->old
Bool Swap = false

Event OnInit()
	If SwapGV.GetValue() == 0 && Swap == false
		Swap = true
		RemoveInventoryEventFilter(NewItems)
		AddInventoryEventFilter(OldItems)
		SwapItemMaint(OldItems,NewItems)
	ElseIf SwapGV.GetValue() == 1 && Swap == true
		Swap = false
		RemoveInventoryEventFilter(OldItems)
		AddInventoryEventFilter(NewItems)
		SwapItemMaint(NewItems,OldItems)
	EndIf
EndEvent

Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)
	If SwapGV.GetValue() == 0 
		If Swap == false
			Swap = true
			RemoveInventoryEventFilter(NewItems)
			AddInventoryEventFilter(OldItems)
			SwapItemMaint(OldItems,NewItems)
		EndIf
		SwapItem(akBaseItem, aiItemCount, OldItems, NewItems)
	ElseIf SwapGV.GetValue() == 1 
		If Swap == true
			Swap = false
			RemoveInventoryEventFilter(OldItems)
			AddInventoryEventFilter(NewItems)
			SwapItemMaint(NewItems,OldItems)
		EndIf
		SwapItem(akBaseItem, aiItemCount, NewItems, OldItems)
	EndIf
EndEvent

Function SwapItem(Form TheItem, Int Num, FormList OldList, FormList NewList)
	Int i = OldList.Find(TheItem)
	If i > -1
		Form NewForm = NewList.GetAt(i) As Form
		PlayerRef.RemoveItem(TheItem, Num, true)
		PlayerRef.AddItem(NewForm, Num, true)
	Endif
EndFunction

Function SwapItemMaint(FormList OldList, FormList NewList)
	If PlayerRef.GetItemCount(OldList)
		Int i = OldList.GetSize() 
		while i
			i -= 1
			Form OldForm = OldList.GetAt(i) As Form
			int Count = PlayerRef.GetItemCount(OldForm)
			If Count
				Form NewForm = NewList.GetAt(i) As Form
				PlayerRef.RemoveItem(OldForm, Count, True)
				PlayerRef.AddItem(NewForm, Count, True)			
			Endif
		EndWhile
	EndIf
EndFunction
  • This is the exact same approach as FrankFamily. The main code work was moved into functions so that they could be reused rather than rewritten with slight differences.
  • The global variable tells the script which formlist to use in the filter and removes the previous one. It also dictates which order the lists are used when swapping items as they are obtained.
  • The OnInit event includes ability to set up filters depending upon the value of the global variable, this gives flexibility in starting the quest containing the alias both before or after the player makes their choice in the MCM menu.
  • The filter swap in the OnItemAdded event does have a drawback in that the reverse won't take effect right away. Instead it will wait until the next instance of an item on the current list is added and then the lists will be swapped and current inventory reversed. However, this shouldn't be too much of an issue.
  • The Swap bool is included in order to prevent some unnecessary processing. There is no need to set filters and convert all items every time a single item on the list is added.

 

 

All this said, the formlist approach only works if you want to swap an entire group of items. If you wish to make each individual item swap-able on its own without changing any other item, then that will have to be done differently. It is not impossible, just handled in a different manner.

Thank you very much for your feedback, IsharaMeradin. I found your notations in the script and at the bottom extremely helpful in deciphering your script. I'll admit, though, that this is still a bit over my head.

 

Let me know if I'm thinking about your script correctly: the SwapGV variable would be set in the MCM menu. The first option would be old->new and would set SwapGV to 0 (default value) and the second option would be new->old and would set SwapGV to 1. And the value of SwapGV (0 or 1) tells the Event scripts (via the Else-If scripting) which order the swap lists are input into the swap Functions (defined below), dictating which list is swapped out for which.

 

You also maintained the two Events that were in FrankFamily's script; OnInit and OnItemAdded, which would allow for this script to function on items in the player's inventory already (OnInit) and when they pick them up (OnItemAdded).

 

Could you explain how the Swap bool prevents unnecessary processing? Along those lines, I'm unclear how during the OnItemAdded Event you could have SwapGV == 0 and Swap == true (and vice versa).

 

And finally the Funcions. The SwapItem Function is used only for the OnItemAdded Event, taking its variables: akBaseItem (item being added to inventory), aiItemCount (number of this item being added to inventory), Form list 1, Form list 2 - where the two form lists are dependent upon SwapGV (e.g. if SwapGV == 0 then Form 1 = OldList, Form 2 = NewList). Not really sure why the "If i > -1" argument is included (i.e. how could you have a negative value from a list)? Anyway, within SwapItem Function, you would remove the number (Num = aiItemCount) of TheItem (aka akBaseItem) from the inventory and add the number (Num = aiItemCount, for a 1:1 swap which is what I want) of NewForm, with NewForm being the equivalent item from NewList (the second list output from OnItemAdded, which will depend upon SwapGV value. If SwapGV == 0 this would be NewList, but if SwapGV == 1 this would be OldList). I think for me it would be less confusing to start this function like this:

 

Function SwapItem(Form TheItem, Int Num, FormList List1, FormList List2)

 

That way, you wouldn't have the situation where OldItems from the OnItemAdded Event (if SwapGV == 1) were seen as NewList by the SwapItemFunction. Maybe that's just me though?

 

 

TheSwapItemMaint Function is called by the OnInit Event as well as the OnItemAdded Event when SwapGV == 0 and Swap == true or SwapGV == 1 and Swap == false (still not sure how that happens). Thus I'll ignore it's use in OnItemAdded for now. Regardless, for the OnInit Event, you have to use a different function for the OnItemAdded because instead of only looking at a single input item (akBaseItem), you are looking through the entire inventory. Correct? So this Function takes your OldList and NewList (which may be NewItems or OldItems, depending upon SwapGV) and counts first for the number of items to be switched out (OldList.GetSize) and sets this number to i (not number of items, but number of item types from list). while i --> does this mean for every item in the list? You then reduce i by 1 (i -= 1) since form lists start with 0 (first item = 0, second item = 1, etc, right?). OldForm is defined within this Function as the return value (e.g. the item name) of the (i-1)^th item on the list (if its the first item on list i = 0). Not really sure why "As Form" is added at the end of this line. Anyway, Count is then defined as the number of items in the player's inventory (PlayerRef.GetItemCount(OldForm)). If Count --> not sure what this argument means. If Count > 0? NewForm is defined as the i^th item on the list (i -= 1 has already been applied, so this should be the equivalent item number on the NewList as the OldForm is on the OldList). Then the OldForm is removed and the NewForm is added (number of items added/removed defined by Count of OldForm).

 

 

Initially, at least, I do want to swap one entire group of items with another (with the possibility of swapping the entire group back). That could potentially change in the future, but I think it's easier for me to focus on what I'd like to do at first.

 

Whew! That was a lot. Thanks again for everyone who contributed constructively to responses. I'm sure I'll have more questions and look forward to any responses & and additional feedback!

Link to comment
Share on other sites

Thanks for your feedback, FrankFamily. If I understand your script properly, the first Event would swap out all the items from the OldItems list within the player's inventory with the corresponding items from the NewItems list when the script is initialized - OnInit(). Which basically would mean when this script is activated (e.g. upon loading in the game). And the second Event would perform the same function but would do so whenever the player adds an item to their inventory. Am I understanding your code correctly?

 

 

Yes, that's the idea, if you could be certain that the player won't have already any item in his inventory then you could skip the initial inventory check. Or maybe, just something i've just thought, it could be compacted by removing all items in player inventory then putting them back triggering the onitemadded as needed. Not sure if that could cause issues, some people have reported crashes apparently related to removallitems function on my paladin mod, so it might cause more trouble than it solves.

 

Actually i think you could do this and save going though the whole formlist.

PlayerRef.RemoveItem(OldItems, 999, true, SomeContainer) ; remove all items of those in the formlist and send them to the container
SomeContainer.RemoveAllItems(PlayerRef)

Basically send all items of the formlist to a temporary container and then back to the player triggering the onitemadded event, since it only moves the items that are going to be swapped it shouldn't cause as many trouble as removeallitems in the player would.

 

Also as lofgren has pointed out it could be better to put the init stuff on a start up stage of the quest. I believe the reason to avoid oninit is the quest being resetted? the wiki mentions it will run twice but i'm not too sure that's it.

 

And, sorry for the derailing.

Link to comment
Share on other sites

Basically, I'm trying to extend a private mod I had made, which was, itself, an extension of my first mod - Gourds as Ingredients. In his/her mod GourdIngredient, egocarib's pointed out correctly that my initial mod was really messy because I replaced all food versions of gourds with the ingredient version of gourds via replace all in the CK, which resulted in a lot of cells being modified. Also, new gourds added by other mods would still be the food version. egocarib's GourdIngredient mod is much cleaner than my first mod because it instead created a perk that swapped out the food version of gourds in the player's inventory and when items were added to the player's inventory. The advantage of that mod is that it altered many fewer cells and would be compatible with more mods. That mod, however, has it's own drawbacks; it isn't compatible with mods that alter cooking recipes without needing to make a bunch of patches (can't cook a gourd pie with ingredient gourd if the other mod calls for food gourd in the recipe). Also, it isn't compatible with mods that include hunger (e.g. Realistic Needs); eating ingredient gourds doesn't give the same effect as eating food gourds.

 

The mod I'd like to make is one where many items that were intended to be ingredients (e.g. gourds) and items that were ingredients in previous versions of TES (e.g. apples, beef, ash yams etc in morrowind/oblivion) are able to be transformed automatically into the ingredient version of those items in a reversible manner. That way, the mod would be compatible with other mods because you could always revert your inventory to vanilla versions of the items.

 

lofgren; you bring up a good point about the use of these items in quests. The only thing I can think of is that some farm items (gourds, leeks, etc) can be sold to certain individuals for 1 gold each. Not sure if this is script-based. I would imagine, though, that if you were using this mod-to-be and had the items converted to ingredients you just wouldn't be able to sell them unless you switched back (another example of why I'd like the mod to be reversible).

Edited by candlepin
Link to comment
Share on other sites

I would guess that potions and food are very rarely stored as persistent objects and you could probably skip the check. I have a storage mod that excludes persistent objects in order to avoid breaking quests and I have encountered less than 10 examples of ingredients, food, or potions being persistent in the vanilla game + mods. Obviously I can't be sure what other mods that I don't use might do with these items, but just as a guess it's probably quite rare.

Link to comment
Share on other sites

I would guess that potions and food are very rarely stored as persistent objects and you could probably skip the check. I have a storage mod that excludes persistent objects in order to avoid breaking quests and I have encountered less than 10 examples of ingredients, food, or potions being persistent in the vanilla game + mods. Obviously I can't be sure what other mods that I don't use might do with these items, but just as a guess it's probably quite rare.

Great. Your feedback is much appreciated!

Link to comment
Share on other sites

One thing you might consider doing rather than making the mod reversible is to mark the food items you are removing as unplayable and reduce their weight to 0. Then you could simply leave them in the player's inventory as invisible tokens (like cut logs used by Hearthfire). That way you would not interfere with any quests or scripts (should they exist) that mark the items as persistent, and all recipes would automatically work.

 

If you do go that route, you will require a more complex script because you will have to detect the removal or addition of both the old items and the new items in order to ensure that the player always has identical numbers of both, while avoiding a feedback loop. I will try to go into more detail later but I'm also sure IsharaMeriden or FrankFamily could help you with it if they get here first.

Link to comment
Share on other sites

If you decide to make the existing items into invisible tokens, you will want to use AddInventoryFilter on your alias twice, once for each formlist. Then, I think this will give you the results you want. There is a user called cdcooley who hangs around these parts who is an expert on threading, so you might want him to double check and make sure that you won't miss any events this way. By going to an empty state while adding/removing items that our alias is looking for, it will not process the events related to those items and thus prevent an infinite feedback loop.

scriptname LFGReplaceItemsTest extends ReferenceAlias

formlist property NewItems auto
formlist property OldItems auto

auto state ready
	event OnItemAdded(form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)
		int i = OldItems.Find(akBaseItem)
		actor PlayerREF = GetReference() as actor
		if(i >= 0)
			gotostate("busy")
			PlayerREF.AddItem(NewItems.GetAt(i), aiItemCount, true)
			gotostate("ready")
		else
			i = NewItems.Find(akBaseItem)
			gotostate("busy")
			PlayerREF.AddItem(OldItems.GetAt(i), aiItemCount, true)
			gotostate("ready")
		endif
	endEvent

	event OnItemRemoved(form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer)
		int i = OldItems.Find(akBaseItem)
		actor PlayerREF = GetReference() as actor
		if(i >= 0)
			gotostate("busy")
			PlayerREF.RemoveItem(NewItems.GetAt(i), aiItemCount, true)
			gotostate("ready")
		else
			i = NewItems.Find(akBaseItem)
			gotostate("busy")
			PlayerREF.RemoveItem(OldItems.GetAt(i), aiItemCount, true)
			gotostate("ready")
		endif

	endEvent
endState

state busy
	
endState

Link to comment
Share on other sites

Inventory events are extremely taxing on the system.

 

You really should use an OnInit() event to apply an AddInventoryFilter for the items you will be replacing. If the player uses Take All on a container you can end up with hundreds of events firing at the same time which can temporarily overload the system. The filters help solve that problem by blocking most of the events.

 

This is also the case where using a PlayerRef property is the best option because you want to avoid even the small amount of time it takes to do a GetReference call. (I also like to avoid the PlayerRef property but sometimes a space-for-speed trade-off is required and this is one of those times.)

 

I don't think keeping the original items as invisible tokens will actually work. You would be forcing all such items in the world to have a weight of zero and it would also mean the player couldn't see or take items of that type out of containers. Personally I would just implement the basic version of the mod, alter the conditions on those farmer's dialogue lines to see your new versions of things, and fix any quests that break when and if you find out about them. I suspect there are so few quests that involve these items that it be easier to patch individual quests than create some generic system to avoid problems.

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.

×
×
  • Create New...