Jump to content

niston

Premium Member
  • Posts

    1381
  • Joined

  • Days Won

    1

Everything posted by niston

  1. I've put a little parting gift. Posted it to the wrong forum becuz Im a dumbass, lmao. Here goes: https://forums.nexusmods.com/topic/13511359-how-to-rename-any-reference-with-just-f4se-and-no-renameanything/
  2. Here's how your mod can rename any reference in the game, with just F4SE and no need for RenameAnything. You're going to need: 1) a MISC Item. Give it any name you want. 2) a MESSAGE. Name it exactly like so: <Alias=NameItem> and set it's owner quest to the QUEST below. Do not check the Message Box flag. 3) a QUEST like so: Also create two quest stages, 10 and 20. Check "Run on start?" for stage 10 and "Run on stop?" for stage 20. Ensure that both stages have "Keep Instance Data From Here On" checked - Or else it won't work. For the quest itself, ensure that only "Repeated Stages" is checked, or it wont work. Then give the quest two reference aliases: Pay attention to the alias flags, they are very imporant! The first alias will be for the Target reference you want to rename, it's called Bot in the picture above. The second alias will be for the MISC item. 4) Create a Papyrus script like so: Scriptname NISTRONRobotAssembler:Renamer extends ObjectReference MiscObject Property nra_RenamingItem Auto Const Mandatory Message Property nra_RenamingMessage Auto Const Mandatory Quest Property nra_RenamerQuest Auto Const Mandatory ReferenceAlias Property RenamingItem Auto Const Mandatory ReferenceAlias Property Bot Auto Const Mandatory Bool bIsRenaming = false Bool Function Rename(ObjectReference refTarget, string newName) ; we must use a lock so concurrent calls will not confuse the algorithm Int limit = 100 While (bIsRenaming && (limit > 0)) ; wait for lock release for <limit> retries Utility.WaitMenuMode(0.1) limit -= 1 EndWhile If (bIsRenaming) ; lock not released in time Debug.Trace(Self + ": ERROR - Rename() failed with lock timeout; Unable to rename reference (" + refTarget +").") Return False EndIf bIsRenaming = true Debug.Trace(Self + ": DEBUG - Renaming (" + refTarget + ") to (" + newName + ")...") ; set name on renaming item form nra_RenamingItem.SetName(newName) ; start quest Bool success = nra_RenamerQuest.SetStage(10) If (!success) Debug.Trace(Self + ": ERROR - Quest failed; Unable to rename reference (" + refTarget +").") bIsRenaming = false Return False Else ;Debug.Trace(Self + ": DEBUG - Quest stage set.") EndIf ; create renaming item reference ObjectReference refRenamingItem = PlaceAtMe(nra_RenamingItem, 1, false, true, true) If (refRenamingItem == none) Debug.Trace(Self + ": ERROR - Rename() failed to create nra_RenamingItem instance; Unable to rename reference (" + refTarget +").") bIsRenaming = false Return False EndIf ; add renaming item to NameItem alias RenamingItem.ForceRefTo(refRenamingItem) ; give some time for alias logic to process Utility.WaitMenuMode(0.3) ; add target reference to Bot alias Bot.ForceRefTo(refTarget) ; wait max 5x0.3 seconds for renaming to happen String actualName = refTarget.GetDisplayName() limit = 5 Utility.WaitMenuMode(0.3) While ((actualName != newName) && (limit > 0)) Utility.WaitMenuMode(0.3) actualName = refTarget.GetDisplayName() limit -= 1 EndWhile ; shutdown quest nra_RenamerQuest.Stop() ;Debug.Trace(Self + ": DEBUG - Quest stopped.") ; cleanup refRenamingItem.Delete() refRenamingItem = none ; did it actually rename? If (refTarget.GetDisplayName() != newName) Debug.Trace(Self + ": ERROR - Failed to rename reference (" + refTarget + ") to (" + newName + "); Actual name is (" + actualName + ").") bIsRenaming = false Return False EndIf Debug.Trace(Self + ": DEBUG - Reference (" + refTarget + ") renamed.") bIsRenaming = false Return True EndFunction 5) Attach the renamer script to something, configure the script properties. Obtain a reference to the object to which the renamer script is attached (named rnr below), and then invoke the renaming script from another script in your mod: Bool renamerResult = rnr.Rename(theTargetRefToRename, "The New Name") If the result is true, the renaming worked. If false, it didn't and an ERROR message will have been written to the Papyrus log (if enabled). 6) That's all folks! If something is unclear, you can investigate my NISTRON Robot Assembler mod and look there. It uses this technique. Consider this a farewell gift Happy gaming and modding to all of you!
  3. My final uploads. I’ve just finished uploading all my unfinished mods. Some are rough around the edges, some incomplete — but maybe they’ll inspire someone, or simply find a home on someone’s load order. I want to say thank you to everyone who supported my mods over the years. Thanks to all of you, I will be making an extra meaningful donation to my local cat shelter this year — something that means a lot to me. I’m stepping away from modding now, but I hope some of the things I made brought a little joy to someone out there. And who knows, maybe I'll come back some day. Stay curious, keep building, and take care of each other. Goodbye.
  4. I've already created a networking system for Fallout 4. So then I wanted to make a rackmount holotape library and looked into this. Result: It isn't possible to do without F4SE plugin magic and some Scaleform customization.
  5. I found this interesting because I made two pieces, one using Ground object type and the other using Terrain. This is for Collision Group in 3ds btw. Both use Dirt as the phys material. Exported with PE Static Art profile to NIF. Both pieces show white collision when dragged into CK, so I was confuzzled for a moment about the red collision you mentioned @pepperman35 However, I then made a small mod that adds the two pieces without any keywords or AVs as statics. Lo and behold: I didn't bother to add textures or anything, but as you can see I was able to build chairs on both of those pieces and plant carrots. I'm not sure the Dirt material has anything to do with it. I chose it because it was mentioned, but it probably works with any material - except you cant plant food if its not dirt. Files attached; You'll need to adjust the NIF paths in the esp if you want to try it. I've also included the NIFs before passing them through Elric (_noelric), for reference. gtp.7z So maybe this a more definitive answer yet @SKKmods
  6. Interesting: If I drag this CaveRmFloor512Mid01 piece onto CK .exe, the collision in preview shows white. When I use preview in CK with loaded Fallout4.esm, it shows red.
  7. no you need something that can be spawned with PlaceAtMe
  8. Here's how to create a Papyrus Array of arbitrary size and type, using just vanilla capabilities: ; declare desired array type (example: a struct called MyDesiredType) and it's size (example: 500 elements) MyDesiredType[] myArray Int size = 500 ; for each element in the desired array... ObjectReference newRef Int i = 0 While (i < size) ; ...place dummy object at xmarker in helper cell ; (force persistence as, ideally, the helper cell is not in loaded area) newRef = markerInHelperCell.PlaceAtMe(dummyForm, 1, true, true, false) ; ...reflink the placed dummy to the marker with whatever keyword newRef.SetLinkedRef(markerInHelperCell, kwdWorkshopItem) i += 1 EndWhile newRef = none ; ask the engine to hand us an objectreference array with the 500 dummies we linked to the marker ObjectReference[] linkedRefs = markerInHelperCell.GetRefsLinkedToMe(kwdWorkshopItem) ; ask the engine to create a copy of the array and to recast the copy into the desired type myArray = (linkedRefs as Var[]) as MyDesiredType[] ; clean up all the dummy refs i = linkedRefs.Lenght - 1 While (i > -1) linkedRefs[i].Delete() i -= 1 EndWhile ; done: myArray has 500 elements. Papyrus VM may prohibit you from creating large Arrays - but you can coerce the engine to create one via GetLinkedRefs() and then leverage Var[] casting magic to change the type. It wont be fast due to the PlaceAtMe() call, but it works. NOTE: If MyDesiredType is incompatible with ObjectReference, every element in myArray will be initialized to <none>. If the types are compatible, then the elements will contain the dummy references. You definitely don't want that, so add an intermediary cast through an incompatible type in this case: ; using Location as the incompatible type here myObjectReferenceArray = (((linkedRefs as Var[]) as Location[]) as Var[]) as ObjectReference[] Enjoy.
  9. Flenarn is working on a F4SE DLL to fix the issue.
  10. For Virtual Circuit Module SOUL Virtual Circuit State Table State Value Description VC_STATE_NC 0 No Virtual Circuit exists (not valid circuit data). VC_STATE_REQUESTING 1 Requesting a Virtual Circuit to be established. VC_STATE_ACCEPTING 2 Accepting a Virtual Circuit request. VC_STATE_ESTABLISHED 10 Virtual Circuit is successfully established. VC_STATE_TEARDOWN 13 Signaling removal of an established Virtual Circuit. VC_STATE_ACTIVE 21 The Virtual Circuit is powered, valid, and ready for use. VC_STATE_SELECTED 22 The Virtual Circuit is actively exchanging data (e.g., selected listening source). VC_STATE_ORPHANED 99 The Virtual Circuit has been inactive for a long time, but still exists until cleared. SOUL Virtual Circuit State Machine Current State Event Trigger Next State Description VC_STATE_NC (0) Device requests a VC (PKT_SOUL_VC_REQ) VC_STATE_REQUESTING (1) A device initiates a Virtual Circuit connection. VC_STATE_REQUESTING (1) Target device accepts (PKT_SOUL_VC_ACPT) VC_STATE_ACCEPTING (2) The receiving device acknowledges the request. VC_STATE_ACCEPTING (2) Requester confirms (PKT_SOUL_VC_ACK) VC_STATE_ESTABLISHED (10) The Virtual Circuit is now fully established. VC_STATE_ESTABLISHED (10) Both devices powered & responding VC_STATE_ACTIVE (21) The Virtual Circuit is powered and ready for use. VC_STATE_ACTIVE (21) Virtual Circuit is selected for transmission VC_STATE_SELECTED (22) The Virtual Circuit is actively exchanging data (e.g., selected listening source). VC_STATE_SELECTED (22) Device signals transition to idle (PKT_SOUL_VC_HOLD) VC_STATE_ACTIVE (21) The VC remains active but is no longer exchanging data. VC_STATE_ACTIVE (21) Device loses power / disconnects VC_STATE_ESTABLISHED (10) The connection remains established but inactive. VC_STATE_ESTABLISHED (10) Device missing for an extended period VC_STATE_ORPHANED (99) The Virtual Circuit exists, but the other device has been missing too long. VC_STATE_ORPHANED (99) Device returns to network VC_STATE_ESTABLISHED (10) The missing device is back; VC can be resumed. VC_STATE_SELECTED (22) A device requests teardown (PKT_SOUL_VC_TRD) VC_STATE_TEARDOWN (13) The Virtual Circuit is waiting for disconnect confirmation. VC_STATE_ACTIVE (21) A device requests teardown (PKT_SOUL_VC_TRD) VC_STATE_TEARDOWN (13) The Virtual Circuit is waiting for disconnect confirmation. VC_STATE_TEARDOWN (13) Teardown is acknowledged (PKT_SOUL_VC_BRK) VC_STATE_NC (0) The Virtual Circuit is completely removed. VC_STATE_TEARDOWN (13) No response received (timeout) VC_STATE_NC (0) The Virtual Circuit is forcefully removed. SOUL Virtual Circuit PACKET Struct PKT_SOUL_VC Int Operation ; Specifies the action (VCC_OP_*) String VCID ; Unique Virtual Circuit identifier Int PayloadType ; Type of payload (DATA, SYNC, etc.) Var Payload ; Actual payload (passed to receiver, not interpreted by VCM) Int ReasonCode ; Reason for teardown (only used for VCC_OP_TRD) Bool FlagACPT ; Acceptance flag (only used for VCC_OP_ACPT) Bool FlagACK ; Acknowledge flag (only used for VCC_OP_ACK) Bool FlagREQ ; Request flag (used in VCC_OP_BEAT to request a reply) ; Only used during connection establishment Int SourcePort ; (REQ, ACPT, ACK only) Int DestPort ; (REQ, ACPT, ACK only) EndStruct SOUL Virtual Circuit Control (VCC) Operations Operation Value Purpose Required Fields VCC_OP_REQ 1 Request establishment of a Virtual Circuit. VCID, SourcePort, DestPort VCC_OP_ACPT 2 Accept a Virtual Circuit request. VCID, SourcePort, DestPort, FlagACPT VCC_OP_ACK 3 Acknowledge Virtual Circuit request (accepted or rejected). VCID, FlagACK, ReasonCode (if rejected) VCC_OP_DATA 4 Transmit data over the Virtual Circuit. VCID, PayloadType, Payload VCC_OP_BEAT 5 Keep the Virtual Circuit alive (heartbeat check). VCID, FlagREQ (requests BEAT reply) VCC_OP_HOLD 6 Transition VC from SELECTED → ACTIVE (stopping data exchange but keeping VC established). VCID, ReasonCode VCC_OP_SELECT 7 Transition VC from ACTIVE to SELECTED -> (VC is actively exchanging data) VCID, ReasonCode VCC_OP_TRD 8 Request Virtual Circuit teardown (disconnect). VCID, ReasonCode VCC_OP_BRK 9 Acknowledge that a Virtual Circuit has been removed. VCID
  11. Has anyone tried creating collision that uses the "Ground" object type in 3ds, for the Collision Group?
  12. Say you have a input Array that you've obtained through some native code means and which has or might have more than 128 entries: ObjectReference[] linkedRefs = refSettlementWorkshop.GetRefsLinkedToMe() Say you also have an output Array of some data type other than ObjectReference: ; the struct type Struct ObjectDescriptor ObjectReference TheRef Int Status EndStruct ; array of struct ObjectDescriptor[] objectsToProcess I use a Struct here because I can. You could however use any type here, other than ObjectReference. Now assume that, for every element in the input Array, you want to put a new instance of the ObjectDescrptor Struct into the output Array. The traditional approach would be to write a loop that reads each element of the input Array, creates a new Struct and fills it, and finally adds the new Struct to the output Array: Int i = 0 ObjectReference[] linkedRefs = refSettlementWorkshop.GetRefsLinkedToMe() ObjectDescriptor[] objectsToProcess = new ObjectDescriptor[0] While (i < linkedRefs.Length) ObjectDescriptor newDescriptor = new ObjectDescriptor newDescriptor.TheRef = linkedRefs[i] newDescriptor.Status = 1 objectsToProcess.Add(newDescriptor) i += 1 EndWhile BUT: The Array.Add() will fail after 128 elements, because Papyrus VM doesn't allow it! And the same is, unfortunately, true if we try to create the Array at a size >128 directly - Papyrus VM won't allow it. =( =( =( Now what??? You could rely on the Large Array Patch in LLFourplay.dll of course. Or, you could use MergeArrays() in SUP F4SE. But what if you want or need to work without any F4SE magic at all? How can you do this then? Is there a simple solution? Yes. Here's what you do instead: Int i = 0 ObjectReference[] linkedRefs = refSettlementWorkshop.GetRefsLinkedToMe() ObjectDescriptor[] objectsToProcess = (linkedRefs as Var[]) as ObjectDescriptor[] While (i < linkedRefs.Length) ObjectDescriptor newDescriptor = new ObjectDescriptor newDescriptor.TheRef = linkedRefs[i] newDescriptor.Status = 1 objectsToProcess[i] = newDescriptor i += 1 EndWhile The intermediate cast of ObjectReference[] through Var[] into ObjectDescriptor[] will result in a new Array of type ObjectDescriptor being created, with the same number of entries as the ObjectReference Array has. However, because the data types (ObjectReference vs ObjectDescriptor) are not compatible, all the entries in the resulting Array will be... none. Which coincidentally just so happens to be the exact sort of thing you need here, of course: A properly sized Array, with empty entries you can fill. Safe and effective. Enjoy! NB: If your output Array is of the same datatype as the input Array, search for my SOLUTION for creating copies of Arrays here in the forum.
  13. openAI's chat transformers -both the free and the paid versions- are not good at Papyrus. 4o is really, really bad. o1 is slightly better, manages to come up with working solutions after lots of nudging and correction. Whereas 4o will just straight embark on an LSD trip to lala land and never come back. It apparently doesn't have enough high quality data about the subject (which should not come as a surprise to anyone), so it will hallucinate a lot of stuff that has no basis in reality. It will give explanations ("Why this works") that are ludicrously wrong and, as it admits itself, it'll often mix up Skyrim and FO4. It'll make up FormIDs with a straight face, give Keywords "new" functionality as it sees fit and it'll confabulate native Papyrus functions out of thin air. When confronted with the fact that it just made up a nonexistent function, it'll apologize and provide an empty Papyrus method for the native function it made up. Not without a sense of humor, it seems. It'll be very enthusiastic about your mod project however, and it'll cheer you up if you rant about the development environment, Bugthesda and Todd Howard - So it can be useful, indeed. It's also proven helpful in giving ideas why something does not work. In any case, Pickysaurus published the page I made: https://modding.wiki/en/fallout4/developers/papyrus/helloworld Thanks Picky!
  14. I made a page on the new Wiki. Apparently there's two of them but only the new one works. I don't have the slightest idea how to publish the page.
  15. No, after 5+ years of modding I was genuinely unaware that nexus has a wiki. LOL. Thanks. I will probably add the article.
  16. I came across this a while ago on xedit discord: Food for thought.
  17. The activators per settlement limit is due to the 128 elements array limit. Specifically, this applies to resource objects counted by the workshop and likely to settlers as well, ie things managed by the Workshop scripts. There seems to be no discernible limit in the number of reflinks (WorkshopItemKeyword to settlement workbench). I think SKK tested with 30k reflinks or something and then gave up. LLFourPlay DLL has a patch for the array limit, which will also let you have more than 128 settlements (workshops array) in your game. I've accidentally spawned around 500 settlers in Concord once. It just works!
  18. I have found that decals in the exterior vanish after entering an interior and exiting it again. It is a very annoying bug. Reminds me I've got to replace some of the street marking decals in Concord mod with custom NIFs, because of that issue.
  19. Hi and welcome to Papyrus development! The nearest "HTML equivalent" would be plugin records. For example, assume you want to display a message to the user from your script - In effect a classic "Hello World" application. While there is Debug.Notification, where you could do: Debug.Notification("Hello World!") But. this only works on PC! Why? Because it uses the Debug script, which is not available on console. The "proper" way to do this is therefore with a Message record in a plugin: Once defined, you then need to make the record accessible to your script. This can be done in two ways: 1) A script property of type Message 2) By using Game.GetFormFromFile() For simplicity, we use the property approach. Our script might look like this then: Scriptname Test:MyTestScript extends ObjectReference Message Property tst_HelloMessage Auto Const Mandatory Event OnActivate(ObjectReference activatedBy) tst_HelloMessage.Show() EndEvent We have the Scriptname line defining the namespace and the name of the script. It also defines what other script our script extends (inherits, or derives from). In this case, our script is in the Test namespace. The namespace corresponds to a folder in your Scripts\Source\User folder. Inside that folder is the script file and it must be named MyTestScript.psc - If the filename does not correspond to the name given by Scriptname, the Papyrus compiler will complain and refuse to compile the script. Furthermore, our script derives from ObjectReference script. This means that our script inherits all the methods and properties of the ObjectReference script, and that we'll be able to attach the script to any ObjectReference. But, more on attaching scripts later. Next, we have said Message property. I called it tst_HelloMessage, so it corresponds with the name of the record in the plugin. This makes our life easier as it allows CK to auto-fill the script property at the click of a button, instead of us manually having to find and assign the Message record to the Message property with a drop down box. The property is furthermore being declared Auto Const Mandatory. The Auto means you don't have to write setter/getter methods for the property, nor define a backing variable - The compiler will do it for you. The Const means two things: 1) The property value cannot be changed at runtime (our script can't assign a different message to it, or set it to none) and, crucially, 2) the property value will not bake into our saves. Finally, Mandatory will cause CK to prompt us to fill out the property when we attach the script. As you can see, there is an event handler for OnActivate event at last. This event is available to our script because we derive the script from ObjectReference (extends ObjectReference) and OnActivate is an event handler on ObjectReference script. Our script therefore overrides the base event handler in ObjectReference (which is declared native and has no method body). The game will invoke this event handler on any script that is attached to an ObjectReference, whenever the reference is activated in game. Our particular script with it's OnActivate event handler will usually attach to a reference of type Activator, but it could work with some other types as well. In any case, the event handler references the tst_HelloMessage property and invokes the Show() method on it. This will cause the Message record referenced by the property to be displayed on screen, in game. Compile the script. Now, to exist in the game world, a script basically needs to be attached to something. In this case, we'll use an Activator so that player may... activate it. Heh. I copied an existing Activator, named it tst_TestButton and attached our script to it: Remember the script property needs assignment though (Mandatory), so CK prompts us to fill it when attaching the script: Now, because we named the Message property the same as the actual Message record, we can just click the "Auto-Fill All" button and CK will fill the property for us: See, the tst_HelloMessage record has been automatically assigned to our property. So now that we've defined an Activator making use of our little script, it is time to place this Activator into the game world. This is easiest to do in CK, where we can load any worldspace (or interior) cell and simply drag and drop the Activator into the render window. We'll put this one in Sanctuary, so that we may easily find it: And now is time to save our plugin, activate it in the load order and test in game: Lo and behold: It just works! Hope you'll find this useful to get a head start into Papyrus development. Papyrus is surprisingly powerful for a "toy" language, even though the development process can be a bit contrived. I mean, all the above does is display a fixed string in game. And it gets much more complicated if the string shouldn't be fixed, but assembled at runtime - But that's a topic for another post. Cheers and happy gaming modding!
  20. Will return the plugin-relative record ID for references defined in plugins. For references created at runtime, the full form ID is returned. The record ID is the form's ID without the plugin index, ie FE001003 becomes 3. Likewise, 4C006481 becomes 6481. However, FF040205 will remain FF040205. Int Function GetRecordIdFromForm(ObjectReference queryForm) Int formId = queryForm.GetFormID() Int msByte = Math.RightShift(formId, 24) If (msByte == 0xff) ; not a plugin defined reference Return formId EndIf Int recordId = 0 If (msByte == 0xfe) ; light plugin recordId = Math.LeftShift(formId, 20) recordId = Math.RightShift(recordId, 20) Else ; regular plugin recordId = Math.LeftShift(formId, 8) recordId = Math.RightShift(recordId, 8) EndIf Return recordId EndFunction Requires F4SE.
  21. This is sort of the reverse of GetFormFromFile(). Gives back the plugin filename a reference comes from, or "RUNTIME" if it was created at runtime: String Function GetFileFromForm(ObjectReference queryForm) int formId = queryForm.GetFormID() int msByte = Math.RightShift(formId, 24) if (msByte == 0xff) ; runtime form return "RUNTIME" endif Game:PluginInfo[] pluginsList int pluginIndex = 0 if (msByte == 0xfe) ; light plugin pluginsList = Game.GetInstalledLightPlugins() pluginIndex = Math.LeftShift(formId, 8) pluginIndex = Math.RightShift(pluginIndex, 20) else ; regular plugin pluginsList = Game.GetInstalledPlugins() pluginIndex = msByte endIf return pluginsList[pluginIndex].name EndFunction In case anyone else needs it. Requires F4SE.
×
×
  • Create New...