Jump to content

How To Add A Custom Workshop Menu?


Recommended Posts

 

Though your code will work, it's not a good way to do due to leaving the script in the save game and not giving it a way to exit. And if the mod user ever deactivates the mod (even if they use the uninstall) the code will be there and continue to fail each time the player loads the game (and enabling the mod again won't remedy this). Just adding Stop() at the end of the uninstall() function should remedy this if the user were to use the function (and that's a big if).

 

Absolute nonsense. None of the above functions in the script I posted above will embed in the savegame. There is no recursion, no loops, no captured threads, no embedding. Period. If you think otherwise then you clearly don't understand how the Papyrus threading system works. And any event registered for in a Quest is automatically unregistered when the quest stops OR when the mod is disabled. I have tested this myself and it does not produce a single error in the logs after uninstalling.

 

Edit: also, a simple way to check for embedding is to run the mod and make a save, then make some simple edits to each function in the script (e.g. add a debug statement or something) then load the last save with the new script and check the logs for messages saying that the script differs from the version in the save. If you don't see those messages then you know the script didn't become embedded. I've tested for this also, which is why I am confident that what you said is not correct.

Edited by steve40
Link to comment
Share on other sites

Please, before moving to personal attacks you should:

  1. Actually read what the person not agreeing with you is saying, and understand it (or at least try to, I can be unclear sometimes)
  2. Maybe do another test (there's always a possibility you overlooked something).

So first things first, the case I mainly made was that if the mod user were to disable/remove the mod without uninstalling (which most will do, unless we're disagreeing here too) your code will fail and produce errors. Also, with your current uninstall function, your code will fail and produce errors because you're not stopping the quest (when the game's loaded again).

 

You are correct in that a stopped quest automatically unregistered from all registered events, I'm not arguing with your here. However, the issue that arises has nothing to do with how the threading system works, but all to do with how the saving of scripts works. This is mentioned here if I recall correctly: Save File Notes (Papyrus) (the current site is down now however, and it isn't cached). An active script is saved in the save file when a save is requested at the current position in the file so that it can continue exactly where it was (even mid line) when the game's loaded. And since registering for an event causes the script to be latent, it will be saved. However, if the mod is disabled, the reference to the Quest holding the script is None, and thereby it'll have NO WAY to exit itself. persist and continually reload itself every time (since that's what you've resisted for).

 

So, do this very simple test script attached to a quest:

ScriptName DerpTest extends Quest

Event OnQuestInit()
	RegisterForRemoteEvent(Game.GetPlayer(), "OnPlayerLoadGame")
EndEvent

; This will be called every time the game loads, even if the it's loaded without the mod
Event Actor.OnPlayerLoadGame(Actor akSender)
	If (Game.IsPluginInstalled("DerpTest.esp")) ; <-- Name it to whatever the .esp it named
		Debug.Trace("Game Loaded with the mod active, no worries")
	Else
		Debug.Trace("Loaded WITHOUT mod enabled. Maybe try to stop Quest?")
		;; Uncommment below if you want...
		; Stop()
		; Debug.Trace("Can't call Stop() on None; failed")
	EndIf
EndEvent

Make the quest Start Game Enabled and Read Once (which is default) and do the following.

  1. Start the game with the mod enabled.
  2. Save the game and reload it
  3. See that the "Game Loaded with the mod active, no worries" message shows up in the log
  4. Save again
  5. Close the game and disable the mod (just disable for now, this matters)
  6. Open up the game and see that the "Loaded WITHOUT mod enabled. Maybe try to stop Quest?" message shows up, optionally an error relating to Stop() should you uncomment that
  7. Save again (cus why not?)
  8. Close and open and load it again (same message repeats, because the Papyrus script state is saved)
  9. Close again and delete the .pex file (this is used to recreate the saved Papyrus script state, so no, the script isn't truly saved in the saved game; just instructions on how to create it)
  10. Open and load the game again, (see that it spits out a bunch of errors about not being able to recreate the Papyrus object because of missing class whatever)
  11. Save here and reload (NOW, finally the error messages will go away, but I don't know what damage it might have done in the process. Maybe all script saved stuff has been messed up)

Here is the packed mod with script for the above test: http://s000.tinyupload.com/index.php?file_id=00688330215583553733

 

Edit: Just saw your edit now, and you should really read through the Save File Notes (Papyrus) I linked above. The results of your test situations are mentioned there. And gah! It was just up and now it's down again. You can look at the Skyrim page for it here, which basically say the same thing.

Edited by timtimman
Link to comment
Share on other sites

*sigh* you are completely misinterpreting what the game engine is doing.

 

For starters, you are using loose scripts. This is bad practice and why you are getting errors. Pack up your scripts in a BA2 and the problem goes away. If users neglect to uninstall mods properly that is not my problem :|

 

Secondly, what you are describing is not embedding of the script in the save, it is simply that the remote event registration is recorded in the save. And since you didn't delete the script when you uninstalled the mod, the engine didn't remove the registration because the script could still be read. After you deleted the script, the game found out the hard way that it wasn't there, and by the subsequent save/load the remote event registration had been cleaned up by the engine's garbage collection. It does not damage the save btw. If the script was packed in a BA2 then the game would not be able to read it once the mod was deactivated and it would do the necessary internal cleanups.

 

Also, your argument that the script will embed in the savegame is totally incorrect because:

 

1) It is not the script that gets saved in the save game, it is only the minimal necessary active function code (i.e. functions that have an active thread running in them, and any parts of the calling function or Event (if any) that the thread needs to fall back to and execute before exiting the script).

2) Because of (1) above, it has everything to do with how threading works. If there is no active thread in a function (including recursive calls to other functions) then the function simply will not be stored in the savegame.

3) RegisterForRemoteEvent() is a non-latent and non-delayed native function. So it DOES NOT cause the 'script' (calling function) to become embedded in the save, contrary to what you claim. It is dispatched (and returns) immediately, the thread does not sit around latently waiting for the registered event to fire. The game engine records the registration in a daemon/manager (the registration data will be recorded in any savegame of course) but it will generate a completely fresh thread to run on the target event (and only IF such an event is triggered).

4) If part of a script is embedded in the savegame, it doesn't even need the pex file to be available because the code is in the actual savegame itself rather than read from the pex file. If a missing pex file generates errors it is actually because the code is NOT embedded in the save! For this reason, I am able to make Papyrus code run even when my mods are uninstalled, by deliberately coding in a way that forces the code to be embedded in the save (e.g. by using a while loop with IsPluginInstalled that captures a thread and doesn't let it continue until IsPluginInstalled becomes false).

 

But regardless, if you feel more comfortable using an activemagiceffect, please do so. But you need to realize that it is not the "proper" way to do it, like you assumptively claimed (and you have been very assumptive throughout your posts), it is merely an alternative way :|

 

Edit: from the Papyrus FAQ:

 

Are scripts saved in save games?

According to SmkViper:

Scripts are not saved in your save game. Script variable values and currently running functions are saved so they can be resumed.

Saves operate generally the same way they did with Skyrim. Any running threads are saved so they can be resumed when the save is loaded. This means any while loops running will be saved into the save game, because of course they need to continue running when the save is loading. However because they are saved, any patch to the function that you release in a mod can't be applied to that thread until it exits the function (which means the while loop has to end).

All that being said, F4's version of Papyrus is much more aggressive about cleaning out dead data if things go... missing. Not that any self-respecting modder would do that.

SmkViper being the creator of Papyrus :cool:

 

However, don't take everything in the wiki as gospel, it has plenty of errors, omissions or overly simplistic descriptions that are open to misinterpretation. God knows, I have had to correct quite a bit of it myself.

Edited by steve40
Link to comment
Share on other sites

I didn't think about packing it in a ba2 archive as I assumed it would do the same, which was basing my arguments on faulty knowledge. But as you stated, deactivating the mod makes it unable to access the script, thereby cleaning up.

 

I may have used some terminology the wrong way, and I do apologize my ignorance on the matter. I do appreciate you taking the time to write what you have, just wish you would write it in a slightly kinder way. Anyway, a big thank you!

Link to comment
Share on other sites

I'm trying to find out how to actually add this script.
I'm really fairly new to this...

 

Scriptname <ModName>_Init extends Quest

FormList Property WorkshopMenuMain Auto Const
; Single menu keyword
Keyword Property <ModName_MenuRecipeFilterKeyword> Auto Const
; Menu with sub-menus/custom icon formlist
Formlist Property <ModName_MenuFormlist> Auto Const

Event OnQuestInit()
	WorkshopMenuMain.AddForm(Keyword or Formlist) ; which ever you wish to use
	Stop() ; No use having the quest running anymore
EndEvent

any help would be really appreciated!

Link to comment
Share on other sites

; Save the script as "##########.psc" in /Data/Scripts/Source/User/
; In the CK compile the script: Gameplay>Papyrus Script Manager. Find script, rightclick compile
; Create a new quest. Go to Scripts Tab and add script, find it and add the keyword/formlist you chose to make
ScriptName ########## extends Quest ; Replace this with the name of what you name your script

FormList Property WorkshopMenuMain Auto Const

;;; Want to use a single menu, create a recipe filter keyword ;;;
Keyword Property %%%%%%%%%%%%%% Auto Const ; Replace with the name of the keyword you created

;;; Want to use a menu with submenues, create a formlist ;;;
Formlist Property %%%%%%%%%%%%%% Auto Const ; Replace with the name of the formlist you created

Event OnQuestInit()
	RegisterForRemoteEvent(Game.GetPlayer(), "OnPlayerLoadGame")
	Install()
EndEvent

Event Actor.OnPlayerLoadGame(Actor akActorRef)
	Install()
EndEvent

Function Install()
	WorkshopMenuMain.AddForm(%%%%%%%%%%%%%) ; Name of the created Keyword or Formlist you chose to make above
EndFunction

Function Uninstall()
{Call this uninstall function using your uninstall chem, holotape etc}
	WorkshopMenuMain.RemoveAddedForm(%%%%%%%%%%%%%) ; Name of the created Keyword or Formlist you chose to make above
	Stop()
EndFunction

Edit: A the Install function was wrongly written as an event. Fixed!

Edited by timtimman
Link to comment
Share on other sites

  • 2 weeks later...
; Save the script as "##########.psc" in /Data/Scripts/Source/User/
; In the CK compile the script: Gameplay>Papyrus Script Manager. Find script, rightclick compile
; Create a new quest. Go to Scripts Tab and add script, find it and add the keyword/formlist you chose to make
ScriptName ########## extends Quest ; Replace this with the name of what you name your script

FormList Property WorkshopMenuMain Auto Const

;;; Want to use a single menu, create a recipe filter keyword ;;;
Keyword Property %%%%%%%%%%%%%% Auto Const ; Replace with the name of the keyword you created

;;; Want to use a menu with submenues, create a formlist ;;;
Formlist Property %%%%%%%%%%%%%% Auto Const ; Replace with the name of the formlist you created

Event OnQuestInit()
	RegisterForRemoteEvent(Game.GetPlayer(), "OnPlayerLoadGame")
	Install()
EndEvent

Event Actor.OnPlayerLoadGame(Actor akActorRef)
	Install()
EndEvent

Event Install()
	WorkshopMenuMain.AddForm(%%%%%%%%%%%%%) ; Name of the created Keyword or Formlist you chose to make above
EndEvent

Function Uninstall()
{Call this uninstall function using your uninstall chem, holotape etc}
	WorkshopMenuMain.RemoveAddedForm(%%%%%%%%%%%%%) ; Name of the created Keyword or Formlist you chose to make above
	Stop()
EndFunction

Sorry I'm trying to use this myself but I am getting

 

"new event install cannot be defined because the script is not flagged as native"

 

I did some googling but I couldn't find anything I could understand on how to fix this

 

Link to comment
Share on other sites

  • Recently Browsing   0 members

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