I’m looking for some script help with this one. A script that will send anything (NPC or Placeable) detected within an painted trigger zone to be moved to another random spot in the area. In short I want to paint areas over non-walkable areas like trees, water, ravines, where some mobs/objects get spawned but are unreachable. And keep moving them until they are no longer in these painted trigger zones.
I may not have fully understood what you’re trying to do, but here goes…
Creatures can only be at a walkable location.
If you try to spawn or move a creature to an unwalkable location, it will appear at the nearest walkable place.
If you want to keep creatures out of certain spots, use a trigger’s OnEnter script.
Best to keep them moving, as the Law of Unintended Consequences dictates that they will sometimes spawn in places you don’t want to block.
If you want to generate random locations, you will need to tell the script what the maximum x and y coordinates of the area are, via local variables, perhaps.
Not sure if that helps…
another possibility, and what they did in one of the Nwn2 campaigns, is to have a hundred or so pre-placed waypoints in an area, then write a script that simply shuffles things about amongst the wps
I could do that but it does take away from the actual randomness that I love. You start to learn where the WPs are. You always know where to look once you have gone through those areas a few times. I honestly have to use that method for some tilesets because the objects get generated underground if placed randomly. If there was a way to ‘on placing’ of the object to find the tileset floor for where it’s being placed and move it there that would be awesome. But haven’t figured that out yet.
I think instead of building it from scratch (which is never pretty) I should pull in how Sparky Spawners is finding and using the randomness. Then figure how to specify all objects and NPCs that enter the triggers.
Scripting is not my strong suite. I appreciate you all very much for the help
You can also add a numeral variable to the waypont.
In the script where you sort the waypoint at which to spawn you can look for that variable, get the location of the waypoint and modify it by that variable and rebuild the location before spawning.
Example:
location GenerateNewLocationFromLocation(
location lTarget,
float fDistance,
float fAngle,
float fOrientation
);
location newloc = GenerateNewLocationFromLocation(
oldLocation,
IntToFloat (Random(5)),
IntToFloat (Random(360)),
IntToFloat (Random(360)),
);
Requires #include " x0_i0_position "
Best way to do this in my view is to have waypoints that are safe recovery spots, assuming you’re trying to enforce “out of bounds” areas that the tileset doesn’t. Then if they enter these spots, find the nearest waypoint in that list of recovery spots, and move them there. Shouldn’t be too hard, but as @proleric suggests, the Law of Unintended Consequences very much is a thing
I think this is how Sparky finds the area’s random location.
int iAreaX = GetAreaSize(AREA_WIDTH, oArea);
int iAreaY = GetAreaSize(AREA_HEIGHT, oArea);
float fRandX = IntToFloat(Random(iAreaX * 10)) + (IntToFloat(Random(90)) / 100) + 0.05f;
float fRandY = IntToFloat(Random(iAreaY * 10)) + (IntToFloat(Random(90)) / 100) + 0.05f;
lLoc = Location(oArea, Vector(fRandX, fRandY, 0.0f), fAngle);
SetLocalLocation(oArea, "lLastSpawn", lLoc);
return lLoc;
99% of my modules spawns including chests, mobs, objects, tradeskill resources, are all completely random (with the above location script) without waypoints. I’m trying not to rely on waypoints for anything if I can help it. I use them for bosses or tougher mobs so they don’t greet the PC at the entrance. Or for tilesets that just don’t have standard settings and cause things to spawn below the map.
None of the trigger areas I would be painting to cause anything within them to be moved out to a random location would be the PC. The triggers painted are all unreachable areas. If it gets tossed to another bad area the intention is for the script to just run again and keep moving everything until they are all outside of those trigger areas. If I just send them to a WP it’s going to be a obvious location to look for these spawns.
Sorry there’s more to that script. These are all within the sparky_inc script. I think I can just include it in the script I’m trying to create:
location ProcessLocation(string sLoc, string sOffset, int iSpawn, int iTotalSpawns, object oArea)
{ string sAngle = RestWords(sLoc, "@");
sLoc = FirstWord(sLoc, "@");
location lLoc;
float fAngle;
if (sAngle == "")
{ fAngle = IntToFloat(Random(360)); }
else if (sAngle == "last")
{ lLoc = GetLocalLocation(oArea, "lLastSpawn");
fAngle = GetFacingFromLocation(lLoc);
}
else
{ fAngle = StringToFloat(sAngle); }
string sType = GetStringLowerCase(FirstWord(sLoc, ":"));
string sParameter = RestWords(sLoc, ":");
float fOffset = StringToFloat(sOffset);
vector vVector;
float fROffset;
float fFacingOffset = 0.0f;
if ((sType == "circle") || (sType == "circleflip"))
{ if (iSpawn > 0)
{ lLoc = GetLocalLocation(oArea, "lLastSpawn"); }
else
{ lLoc = ProcessLocation(sParameter, "", 0, 0, oArea);
SetLocalLocation(oArea, "lLastSpawn", lLoc);
}
vVector = GetPositionFromLocation(lLoc);
fAngle = (360.0f / IntToFloat(iTotalSpawns)) * iSpawn;
if (sType == "circleflip")
{ fFacingOffset = 180.0f; }
lLoc = Location(oArea, GetChangedPosition(vVector, fOffset, fAngle), fFacingOffset + fAngle);
return lLoc;
}
if ((sType == "loc") || (sType == "location"))
{ sParameter = GetStringLowerCase(sParameter);
float fLocX = StringToFloat(FirstWord(sParameter, "x"));
sParameter = RestWords(sParameter, "x");
float fLocY = StringToFloat(FirstWord(sParameter, "y"));
sParameter = RestWords(sParameter, "y");
float fLocZ = 0.0f;
if (sParameter != "")
{ fLocZ = StringToFloat(FirstWord(sParameter, "z")); }
lLoc = Location(oArea, Vector(fLocX, fLocY, fLocZ), fAngle);
SetLocalLocation(oArea, "lLastSpawn", lLoc);
if (sOffset != "")
{ vVector = GetPositionFromLocation(lLoc);
fROffset = IntToFloat(Random(FloatToInt(fOffset * 100.0f))) / 100.0f;
lLoc = Location(oArea, GetChangedPosition(vVector, fROffset, fAngle), fAngle);
}
return lLoc;
}
if ((sType == "wp") || (sType == "waypoint"))
{ lLoc = GetLocation(GetWaypointByTag(sParameter));
SetLocalLocation(oArea, "lLastSpawn", lLoc);
if (sOffset != "")
{ vVector = GetPositionFromLocation(lLoc);
fROffset = IntToFloat(Random(FloatToInt(fOffset * 100.0f))) / 100.0f;
lLoc = Location(oArea, GetChangedPosition(vVector, fROffset, fAngle), fAngle);
}
return lLoc;
}
if ((sType == "object") || (sType == "obj"))
{ lLoc = GetLocation(GetNearestObjectByTag(sParameter, GetFirstObjectInArea(oArea)));
SetLocalLocation(oArea, "lLastSpawn", lLoc);
if (sOffset != "")
{ vVector = GetPositionFromLocation(lLoc);
fROffset = IntToFloat(Random(FloatToInt(fOffset * 100.0f))) / 100.0f;
lLoc = Location(oArea, GetChangedPosition(vVector, fROffset, fAngle), fAngle);
}
return lLoc;
}
if (sType == "last")
{ lLoc = GetLocalLocation(oArea, "lLastSpawn");
return lLoc;
}
// Return a random location. Do not use the edges (.05 width).
int iAreaX = GetAreaSize(AREA_WIDTH, oArea);
int iAreaY = GetAreaSize(AREA_HEIGHT, oArea);
float fRandX = IntToFloat(Random(iAreaX * 10)) + (IntToFloat(Random(90)) / 100) + 0.05f;
float fRandY = IntToFloat(Random(iAreaY * 10)) + (IntToFloat(Random(90)) / 100) + 0.05f;
lLoc = Location(oArea, Vector(fRandX, fRandY, 0.0f), fAngle);
SetLocalLocation(oArea, "lLastSpawn", lLoc);
return lLoc;
So getting closer, I’m using some AI to suggest a starting point. Would this be correct or am I on the right path?
#include sparky_inc
//
void sparky_random_respawn(object trigger_object)
{
// Get a list of all NPC and placeable objects in the trigger area.
object[] objects = GetObjectsInArea(trigger_object.GetLocation(), OBJECT_TYPE_NPC | OBJECT_TYPE_PLACEABLE);
// Loop through the list of objects and respawn them to a random location.
for (int i = 0; i < objects.Length; i++)
{
// Get a random location within the trigger area.
object random_location = GetPointInCircle(trigger_object.GetLocation(), 10.0f);
// Respawn the object to the random location.
objects[i].Respawn(random_location);
}
}
This function will take a trigger object as input and respawn all NPC and placeable objects in the trigger area to a random location within the trigger area.
Add the following event handler to your new script file:
void OnObjectEnter(object trigger_object, object object)
{
// If the object entering the trigger is a player, call the sparky_random_respawn function.
if (object.IsPlayer())
{
sparky_random_respawn(trigger_object);
}
}
Honestly it sounds like you’re over-complicating this for the sake of randomness. You’re much better served having fixed spawn points that the creatures then move from naturally. Trying to make it completely random is laudable but you’re going to run into a lot of edge cases that will be frustrating to deal with.
For instance, right now you’re looking for a 10 unit circle. If you say “I’m just going to run it again until it finds a good position”, then what happens if you exhaust entropy? Not hard to do on resource-constrained environments such as VMs or chromebooks/etc since not much if any hardware entropy is available.
Moreover another thing you’re going to have to design around is if the spawns are just random within an area, this is going to make difficulty very variable in some instances; you might end up with very tightly bound groups in some cases that will be much more challenging, whereas in others they will be sparse and easier. You can of course design around this, but if you’re making the areas tight enough where this isn’t a possibility, you are, frankly, better served using waypoints.
I feel I cannot overemphasise that trying to brute force it by just running the script continuously until it finds a good area is very likely a bad idea.
Another thing I should say, as someone with experience in cryptology: nothing you generate in NWN is going to even approach true random. It is pseudorandom at best. Since it uses the system random() implementation, you can get darned close if you have a hardware entropy generator and are using Linux (especially Devuan, which has given this some special attention), but otherwise, you are going to notice patterns over playtimes, especially on weaker machines, and especially on Windows (where the provided system entropy has always been weak).
The question then becomes: what is the best way to create as best a facismilie of random generation as we can? Well, the short answer is to do some cryptology heavy lifting of your own, but there’s a reason people say not to roll your own crypto; security issues that are irrelevant aside, it’s a lot of work. Any game randomization is about finding the degree of pseudorandomness you seek to achieve. As to helping you achieve it, better defining exactly what you’re trying to achieve, what it would look and feel like, how variable you want the dungeons to be, is a good starting point for us to help you. One counter-intuitive thing: stronger more truly random algorithms can actually result in less variation from iteration to iteration; that is part of what makes them stronger (more distinctness is more predictive),
PS I hate the drafts feature on whatever forum software this is.
Is it really that taxing on the system for he script to run whenever something is in the trigger? I’ve never noticed much in terms of CPU usage for anything I’ve done so far in NWN. I can imagine some instance where the moved object may land in a bad area over and over. But I’m not painting 50% of the area. Most of the areas I would use this would be maybe 5% of the map at most. I would imagine they would get moved to a good area after a few script cycles.
The clumps of mobs vs empty areas is exactly what works for me. I’m aware it’s not truly random, but it’s random enough for what I’m intending to do.
The Sparky Spawner does exactly what I’m trying to do with it’s spawn system. It’s even edge aware in picking a “random” location after finding out the area size.
I’m just trying to figure out how to use/modify it for the OnEnter for a trigger.
I struggle enough with reading & modifying scripts. I’m not planning on tampering with cryptology. lol
That AI generated script is garbage That is not the right path… You are much better off taking examples from actual working scripts as a starting point than that.
It sounds like that spawn system is achieving your goal then. Forgive me if I didn’t glean from your initial post, is there some problem you’re trying to resolve with it, or? From the above scripts, you seem to be going in the right direction, but if you have problems I’m happy to try to help.
What Sparky’s does is basically just what a lot of people call “fuzzing”; its essentially taking the spawn point and adding some gentle randomness to it, that’s indeed likely more than enough for most.
To explain my previous warning: The risk you can have is if you overuse it (“painting large areas” in your terms), you’re going to have a lot of script ticks that are going to find bad places, and then have to recalculate. For one spawn? This is fairly harmless, as you say. But if you are doing this over say 50, 100 spawns, especially as you mention that you have chests and items random as well, then it can become cause for concern. However, if you’re only doing these over relatively small areas, the potential for this problem seems pretty low.
I hear ya, so do i … but depending on what your area is like, as suggested, some dragons are best left sleeping …
but yeh, hey try it in the name of !!science!! hehe
Oh i intend to as soon as I get some time to explode my brain all over this.