csbx Posted June 1 Posted June 1 I'm playing with a dynamic spawning system, but having trouble finding an approach to test spawn points to reject / move them prior to spawning. the most obnoxious issue for me is that if the player is near water, I see no good way to reject spawn points landing in the water. Ideally, the xmarker placed dynamically (x,y offset from some point) should see if it's in water and try other spots (rotate 30 degree increments e.g.). But the obvious issue is that water is a giant horizontal slab so using PO3's extended isRefInWater check doesn't help because the shore near the water also has the water volume beneath it and at the same height (ie. false positive for water) I tried thinking about an approach like: place xmarker at x,y offset from point, move z value to height of navmesh, then test isRefInWater(), but there's no way I can see to 'move z value to height of navmesh'. Anyone have any suggestions here ?
Qvorvm Posted June 1 Posted June 1 Traditionally, finding the navmesh Z is done by placing an object or marker and using havok to make it settle to ground level. In some other context, I need dynamic spots where to safely teleport NPCs. I mark previous positions of the player for that purpose. They are all obviously appropriate for my purpose, and you can easily add a isSwimming check in your case. Though, admittedly, it's not ideal for "randomly" placing encounters.
csbx Posted June 1 Author Posted June 1 On 6/1/2025 at 10:37 AM, Qvorvm said: Traditionally, finding the navmesh Z is done by placing an object or marker and using havok to make it settle to ground level. In some other context, I need dynamic spots where to safely teleport NPCs. I mark previous positions of the player for that purpose. They are all obviously appropriate for my purpose, and you can easily add a isSwimming check in your case. Though, admittedly, it's not ideal for "randomly" placing encounters. Expand Really appreciate the ideas. I had no idea about using havok in that way ! The direction I think I'm going is the following: use my normal method (random angle, random distance between a min and max = x,y offsets from a center point) UNLESS the player's position -500 on the z is in water (PO3's function). if it's in water -- ie. if the player is proximate to a lake or the sea -- revert to the method employed by Tonycubed (genesis spawns), meaning: spawnPoint = Game.FindRandomReferenceOfAnyTypeInListFromRef(LandSpawnFallbacks, PlayerREF, maxDist) where landspawnfallbacks is a formlist of tree/bush types. This very effectively avoids spawning in the water for obvious reasons. I then WHILE through 5 such checks until finding a valid one far enough from the player (getdistance(playerREF). If nothing found, no spawn, which is fine. This method is a little hinky, though it only runs on a (chunky) edge case. In most cases the player isn't standing on the shore of a lake or the sea of ghosts.
csbx Posted June 2 Author Posted June 2 i'll just leave this for anyone looking for a solution re spawning. The problem I encountered with the approach I outlined above is that Game.FindRandomReferenceOfAnyTypeInListFromRef isn't as 'random' as it's name suggests. It seems to be so efficient it's apt to find the same REF (tree, bush in world) in multiple searches, rather than it choosing from the REFs randomly. This meant that spawns were generally biasing to one side, correlating to 'hits' it was constantly getting from wherever it started its search. My solution: make an imaginary circle around the player out some distance (2700u radius in my case), and create points at 45-degrees along the circle (8 points). Each one is then the source for actually independent FindRandomRef searches. Then I store a set of known valid spawn points (where it found a tree/bush). Then i choose among them randomly when ready to call the spawn. Result: fairly natural spawns in interesting spots, never in the water. Note: my context is a peculiar one--so may not be useful to many. I'm generating spawns around a fixed objects and creating this array of valid spots only when the player puts down a tent. More dynamic spawning--eg. around the player as they're walking around--would require a different approach probably.
PeterMartyr Posted June 4 Posted June 4 On 6/2/2025 at 6:59 PM, csbx said: Game.FindRandomReferenceOfAnyTypeInListFromRef isn't as 'random' as it's name suggests. Expand There no such thing as random on a PC mate, it just smoke and mirrors, and clever math! This is just for fun, not for actual use as an example demonstrating the math using papyrus for you Careful the magic will gone for forever.. if you look Reveal hidden contents Int Function RandomInt(int min = 0, int max = 100) ; process our inputs int range = max - min + 1 ; use centiseconds to increase the "random" precision int time = (Utility.GetCurrentRealTime() * 100) as int ; calculate the random number hahahaha return time % range + min EndFunction As time move forwards the output random number "randomly" changes disappointing is it not? Reveal hidden contents If my code runs repeatedly in milliseconds , it will return the same number, or in centiseconds its outputs will be grouped closely together, probably something similar was happening to you, the game is old, it needs a new random int with better precision in the math for better randomness. But calling it or even my code, once in a blue moon, is 100% OK absolutely random I like your solution to increase the randomness.. too
csbx Posted June 6 Author Posted June 6 On 6/4/2025 at 2:11 AM, PeterMartyr said: There no such thing as random on a PC mate, it just smoke and mirrors, and clever math! This is just for fun, not for actual use as an example demonstrating the math using papyrus for you Careful the magic will gone for forever.. if you look Reveal hidden contents Int Function RandomInt(int min = 0, int max = 100) ; process our inputs int range = max - min + 1 ; use centiseconds to increase the "random" precision int time = (Utility.GetCurrentRealTime() * 100) as int ; calculate the random number hahahaha return time % range + min EndFunction As time move forwards the output random number "randomly" changes disappointing is it not? Reveal hidden contents If my code runs repeatedly in milliseconds , it will return the same number, or in centiseconds its outputs will be grouped closely together, probably something similar was happening to you, the game is old, it needs a new random int with better precision in the math for better randomness. But calling it or even my code, once in a blue moon, is 100% OK absolutely random I like your solution to increase the randomness.. too Expand Peter, I would be a weepy mess right now EXCEPT for the fact that when I was a wee lad I looked under the Wizard's robe when learning to script in Pascal. 7th grade, I think. So yeah, you're right, but also pedantic in a sense, because the effect is good enough to work as random in most situations. I know you know this ! Seriously, though, knowing what you know, do you actually employ strategies when needing a 'random' number ?
PeterMartyr Posted June 7 Posted June 7 I have never had the need in papyrus to fix it, plus the Utility Script RandomInt function would use epoch time https://en.wikipedia.org/wiki/Epoch_(computing) not real game time, plus all the math of C++, so it already its is better than anything we could do with papyrus, all we have is real game time, and basic math https://ck.uesp.net/wiki/GetCurrentRealTime_-_Utility it does not compare, like I said in my post, it is not for actual use, just a math demo, and I stand by that We would also need not only a Random but an Array of refs to select from, thinking.... OGM this is Youtube algorithm OK idea cell location = player.getParentCell() https://ck.uesp.net/wiki/GetNumRefs_-_Cell the example look on the wiki is auto harvest, but the gist is there https://ck.uesp.net/wiki/GetType_-_Form using SKSE make a super form array of all the refs in your list, if you get over 128 refs from all your forms https://ck.uesp.net/w/index.php?title=CreateFormArray_-_Utility&action=edit&redlink=1 https://ck.uesp.net/w/index.php?title=ResizeFormArray_-_Utility&action=edit&redlink=1 You may break the Array 128 limit here, welcome to the club brother Shuffle it like deck of cards mixing them up real nice, pretend it is blackjack game, and they are a deck of cards then Waste some elements like dealer at the casino to stop blackjack player counting cards, and increase the randomness Stopping here but there is more, like handling removing a ref from the array if it used to place object, so it is not selected again. FYI to keep the Blackjack I do not what overwhelm you, basically we going to handle everything, you sure you want this? When You have working code, it might not be much better in the end? baby steps, some questions, how many forms are being checked for, I could see tree and flower in the form list, plus there is int MyType = akForm.GetType() to debug it too, But I am just brainstorming, not saying it is going to work... but make our own array of Form Refs and handle everything, but is GetNumRefs this Form going to work for you? step one is simply debug the forms in your list to see if you get a return that not... f*#@ Stupid Wikki does not say, let's assume -1 or 0 ¯\_(ツ)_/¯ is a negative result..
PeterMartyr Posted June 7 Posted June 7 Just for fun I prototype above and included the debug code for int MyType = akForm.GetType() in the OnInit Event, I personally would preferred to test that before I prototype anything, but is Saturday and I had nothing to do.. It compiled, but it is untested in every way Reveal hidden contents ScriptName ProtoTypeForCBS Extends Quest { NOTE THIS IS AN ARRAY LIST: FYI AN OBJECT THAT HAS A PRIVATE ARRAY } FormList Property YourList Auto {List of Forms to check for} int[] typeArray form[] arrayOfRefs int capacity = 128 int size = 0 Event OnInit() Form[] myArray = YourList.ToArray() typeArray = Utility.CreateIntArray(myArray.Length) Int i = 0 While i < typeArray.Length typeArray[i] = myArray[i].GetType() Debug.Trace(Self+", Form: "+myArray[i]+", Type: "+typeArray[i]) i+=1 EndWhile EndEvent Int Function GetCapacity() {for debugging} Return capacity EndFunction Int Function GetSize() {for debugging} return size EndFunction ; ================================================== Function SetUpCamp(actor player) {pass the player as a parameter} SetArrayOfRefs(player) Shuffle() EndFunction ; ================================================== Function SetArrayOfRefs(actor player) {pass the player as a parameter} arrayOfRefs = Utility.CreateFormArray(capacity) int i = 0 int j ; not initialized OK pointless int refs ; not initialized OK pointless too Cell iCell = player.GetParentCell() While i < typeArray.Length j = 0 refs = iCell.GetNumRefs(typeArray[i]) While j < refs - 1 SetRef(iCell.GetNthRef(refs, typeArray[i])) j += 1 EndWhile i += 1 EndWhile arrayOfRefs = Utility.ResizeFormArray(arrayOfRefs, size) EndFunction Function SetRef(form aiForm) if size == capacity capacity*=2 arrayOfRefs = Utility.ResizeFormArray(arrayOfRefs, capacity) endif arrayOfRefs[size] = aiForm size += 1 EndFunction Function Shuffle() int random Int max = arrayOfRefs.Length form temp Int i = 3 ; now many times we shuffle it. Is 3 times OK? int j While i > 0 i -= 1 J = 0 While j < size random = Utility.RandomInt(0, max) temp = arrayOfRefs[j] arrayOfRefs[j] = arrayOfRefs[random] arrayOfRefs[random] = temp j+=1 EndWhile EndWhile ; wasted 30% size = size * 0.7 as int EndFunction ; ================================================== ; SOME EXTRA FUNCTIONS AS PREEMPTIVE STRIKE ; ================================================== Function Destroy() {WIP it needs the camp objective references to delete when able} arrayOfRefs = Utility.CreateFormArray(0) capacity = 128 size = 0 EndFunction Function ResetForDirtySave() { Call this if the save is dirty Just fill out the hex and esp name for GetFormFromFile Correctly } YourList = Game.GetFormFromFile(0xABCE, "cbsCamping.esp") as FormList OnInit() Destroy() EndFunction Function RemoveAt(int index) If index < 0 || index >= size Debug.Trace(Self+": RemoveAt(Int32): Out Of Bounds Error") Return EndIf size -= 1 While index < size arrayOfRefs[index] = arrayOfRefs[index + 1] index += 1 EndWhile EndFunction
Recommended Posts