Jump to content

DOWNLOAD MODS

Are you looking for something shiny for your load order? We have many exclusive mods and resources you won't find anywhere else. Start your search now...

LEARN MODDING

Ready to try your hand at making your own mod creations? Visit the Enclave, the original ES/FO modding school, and learn the tricks of the trade from veteran modders...

JOIN THE ALLIANCE

Membership is free and registering unlocks image galleries, project hosting, live chat, unlimited downloads, & more...

Object proximity detection algorithm


syscrusher
 Share

Recommended Posts

I have an existing, working script set that manages player-controllable light sources, and I'm enhancing it in preparation for releasing it as part of a modder's resource. I've run into an algorithm problem, though, and I need a better way to do it.

I'm going to stay in general pseudocode-level detail here, because the help I need is with finding the right algorithm rather than the details of script syntax.

The particular script in question (one of a series) is an object script attached to an invisible "button" Activator that floats in the air near the light to be controlled. The controlled objects are a pure light source (that is, one of the light-bulb-ish objects, not a candelabra or similar) that emits the actual light, plus one of the "fake" light objects (e.g., Chandelier01Fake) to simulate the "on" state, and its "off" counterpart (e.g., Chandelier01Off) to simulate the off state. The script assumes that the Fake and Off statics are almost exactly (within 1 Oblivion unit) overlaid on one another, and toggles them to opposite enabled states. It also assumes that one or the other of them is set initially disabled in the CS. (Yes, I know that EnableParent can be used to force them to opposite states; I may choose to do that in the final version, but it's not relevant to this discussion.)

The actual "light" type object is also toggled. I make the assumption that the modder will set its initial state either to enabled or disabled so it's sensible with the two alternating statics.

Now, it would be trivially easy to do all this if I simply made all three of the controlled objects persistent references, named them, and then called them out by name in my Activator script. But I'd then have to modify that script for each instance of the set, which is a pain.

With my other scripts in this set, the scripts are smart enough to scan all objects in the cell at load time and to find the nearest object or objects that meet the particular script's requirements. For instance, I have a fire toggle version that simply finds the nearest object of type "light" (using GetFirstRef and GetNextRef in a while loop). It then toggles the enable state of that light source, which is a fireplace fire if the modder has done things sensibly in the CS. Since this is a modder's tool and not a player-visible process, I rely on the modder doing things sensibly or fixing it if they are silly enough to put the Activator next to something where it doesn't make sense. (At worst case, it makes a disappearing/reappearing candle or lantern, a cosmetic problem but nothing that will crash the game.)

A second variant finds the nearest Light and the Static object that is closest to that Light, assuming that the Light is one of the "practical" types (that is, in the theatrical sense, such as a chandelier that actually emits light rather than one of the fake ones) and that its nearest Static will be the "off" variant of the same mesh.

I built this variant to do something similar. It finds the nearest Light object and attaches to that, then looks for the nearest two static objects to the Light, and attaches to those. I tested this with a lantern sconce, and it works perfectly. Then I tried it on a Chandelier01[Off|Fake] setup, and the trouble began.

My chandelier happens to hang from a high ceiling via one of the long chain objects, and it turns out that Bethesda made the base point of those objects so it's below the visible bottom of the chain, down amidst the chandelier. So my script detects one of the overlaid chandeliers plus the chain, and dutifully toggles between them, leaving the other chandelier always enabled! No crash, but looks stupid in game. It seems no matter how tightly I set the overlay proximity, the chain is often detected. I think its position is actually the same as the centerpoint of the chandeliers, which makes sense in terms of being able to manually set X,Y,Z coordinates in the CS for perfect placement. But it's not helpful to my detection algorithm.

It also occurs to me that solving this problem will also allow modders, if they wish, to place their Activator further away from the Light and the two Statics, as long as those are either the closest qualified objects or I find some other way to link them (more on this below). So fixing the specific case of this hanging chain isn't really all I want to do -- I want this script to be truly versatile.

Now, I can think of several ways to solve this, such as using OBSE GetName functions to specifically look for names containing "Off" and "Fake" (though I'm not sure Oblivion is smart enough to do partial string matching...have to read up on that). I could make myself a custom hanging chain object with its origin at a higher Z value so it appears further away from the activator than the chandeliers are, but that's not the preferred answer for the reasons explained above and because it requires a non-vanilla mesh.

I also considered using parent/child relationships. I can't make the Activator a child of any of the controlled objects, because the moment it disables its parent it also disables itself. With OBSE, though, I could make all the controlled objects children of the Activator, and it could then safely enable and disable them, because OBSE has functions to obtain child objects instead of just the parent object. This would work great, except that it would make my script dependent on OBSE. I'm willing to do that if I must. This resource is really just one development task amidst my much larger mod, and the larger mod already depends heavily on OBSE for other things. But I had really hoped to make this small resource OBSE-independent if possible.

Now, having bored everyone to tears, here is my actual question: What is the best way to detect filter a list of Static objects to include only those that are static stand-ins for a light source such as a torch, lantern, chandelier, candle, etc.? Or should I use the parent/child relationship and just rely on OBSE?

Or am I wasting my time even implementing this complex version, in thinking that the "pure" light sources are more CPU efficient than the "practical" fixtures?

Someone has probably invented this wheel before, so I appreciate any suggestions. Again, I'm not looking for someone to write the code for me, just to suggest the general filtering algorithm to use. I can take it from there, and I'll be glad to upload the finalized script for others to use.

Thanks!

Syscrusher

Link to comment
Share on other sites

Sorry, I do what you describe for specific instances, but don't use OBSE, so no while loops. The basic idea avoids a sometime problem where, for example, you enable a lite candle and disable an unlit one and the light doesn't appear as it should. If you set the light (candle in this case) to zero output and make the switch, including an "invisible" light source as you suggest, it always works.

Rather than searching for the nearest set of objects that match, how about using a standard naming sequence (eg: uniquenameLightOff, uniquenameLightOn, uniquenamePhantomLight). Then you can just copy you standard "light switch" script and make the changes for the specific instances quite easily (global search and replace with any decent editor). I know it's not as much fun as your approach, but it is a lot simpler. :)

Link to comment
Share on other sites

Sorry, I do what you describe for specific instances, but don't use OBSE, so no while loops. The basic idea avoids a sometime problem where, for example, you enable a lite candle and disable an unlit one and the light doesn't appear as it should. If you set the light (candle in this case) to zero output and make the switch, including an "invisible" light source as you suggest, it always works.

Interesting...I was not aware of this. Can you elaborate on this issue? Is it a known bug?

Link to comment
Share on other sites

Since I discovered the problem several years ago, I use the technique described in my initial post. Here's what I remember:

If you wanted to make a switchable candle, you would think you would just have two candles. One a lit candle light (e.g. CandleFat01Yellow512) and the other a static unlit candle (e.g. CandleFat01Off). You could then make an inverse parent link between them, so enabling the lit candle would disable the unlit one and visa versa. The problem was that when the unlit one was disabled, the lit one was enabled, but the actual light associated with it didn't come on. (This may have been fixed in the 1.2 CS; I didn't check).

So, at least for me, I do the same thing, but with the output of the lit candle set to zero and enable a phantom light at the same time. So off is just the static unlit candle. On is the lit candle with no light output plus a phantom light. Of course, this works for any type of light, not just candles.

Link to comment
Share on other sites

So, at least for me, I do the same thing, but with the output of the lit candle set to zero and enable a phantom light at the same time. So off is just the static unlit candle. On is the lit candle with no light output plus a phantom light. Of course, this works for any type of light, not just candles.

Isn't the lit candle with no light output the same as the "Fake" candle? If so, then this is what I am doing also, hence this script.

Link to comment
Share on other sites

I do not use OBSE, I scrpt them the old fashioned way.

I have a 'blank' candle and its an activator. I then have a 'flame only' that I move 'up into position' or 'down out of sight' to make the light go on or off. I also enable or disable the light-source, all using parentage. You can daisy chain your parentage from one script.

Using the 'move' of the light gives it a realistic 'off' effect instead of disabling it. The FX flicker out when you move the flame down, instead of just suddenly 'going out'.

Link to comment
Share on other sites

I do not use OBSE, I scrpt them the old fashioned way.

I have a 'blank' candle and its an activator. I then have a 'flame only' that I move 'up into position' or 'down out of sight' to make the light go on or off. I also enable or disable the light-source, all using parentage. You can daisy chain your parentage from one script.

Using the 'move' of the light gives it a realistic 'off' effect instead of disabling it. The FX flicker out when you move the flame down, instead of just suddenly 'going out'.

Link to comment
Share on other sites

Its been a long time, but you can find an example of it in a few of my mods.

Mini Cyrodiil

And your right, you cannot parent from the candle to the other objects, its hardcoded doing it this way.

In the shack interior of 'OBGateShackInterior' there are some candles you can check out.

The candle is an activator and it does not disable. This script is on the candle:

scn OBGateCandleController001Script
short myStatus

Begin OnActivate
;***TURN CANDLE ON***
if myStatus == 1
OBGateCandleFlame101REF.SetPos Z 6923.2935
OBGateCandleLight101REF.enable
set myStatus to 0
;***TURN CANDLE OFF***
elseif myStatus == 0
OBGateCandleFlame101REF.SetPos Z 7923.2935
OBGateCandleLight101REF.disable
set myStatus to 1
endif
End[/code]

The flame is a '\fire\firecandleflame.nif' object resided up to a scale of 2.

Link to comment
Share on other sites

I spent most of the night last night (literally) experimenting with this and reading online examples and documentation. I finally decided that to make the Activator script as generic as I wanted it to be, I was simply going to have to rely on OBSE to iterate the cell and find the nearest Light object. The only reason I've hesitated with that was because I wanted to publish my script as a standalone resource that could be used by others, not requiring OBSE. That's out the window. I'll still share it, but I'm simply going to document the dependency.

The way my new script works is this:

  • With the OnLoad hook, it checks the version of OBSE and stores it in a script variable, failing gracefully to version zero if not installed.

  • If OBSE is found during the OnLoad, the script assumes it is on the Activator object and identifies a ref to the nearest Light object.

  • At OnActivate time, if the Light object ref is populated, the script toggles its enable status and plays a sound effect for lighting or extinguishing. If the Light object was not populated because OBSE was not installed, a popup message box is shown to the user so they know why the activation didn't have any effect, and how they can fix it.

  • The Light object is enabled and disabled, and you can make it the parent (with same or opposite states) of as many other objects as you wish -- Fake, Off, or multiple Lights. A typical case is an Off object set to the opposite state of the Light, and a Fake object set to the same state as the Light, both of these being children of the Light. (Sounds like a new religion...)

  • To handle the case where you might want the Activator far from the controlled Light and its child objects, I simply created a no-op token light that is almost a zero light source and has a very tiny radius. You can set my script on that and then make the "real" Light objects same-state children of the token light. Put the token light close to the script Activator, and it will be found like any other.

I put all the iterative processing into OnLoad, whereas my old version had it in OnActivate.

I've got this working in my mod's interior cell with a variety of sources, and it seems to do fine and to eat basically zero CPU. There is a small hit during OnLoad time (which, let's face it, is not very often), but I couldn't measure the impact even in a test cell with about 40 Light objects it had to iterate to find the nearest, plus multiple instances of my Activator object and script all iterating in the same frame. OBSE's ability to have the GetFirstRef call pre-filtered to a specific object category is very helpful here.

Do you see any problems with this approach, other than the need to document dependency on OBSE? My mod was already extensively using OBSE, so this is only an issue for sharing the script and activator as a resource, not for my own current project.

Syscrusher

Link to comment
Share on other sites

In Fallout 3, this is easy to do since you can also 'link' references and the script can enable or disable the linked references without messing with its own state.

And that is why I don't like modding for OB anymore, its just too restricting...

Link to comment
Share on other sites

In Fallout 3, this is easy to do since you can also 'link' references and the script can enable or disable the linked references without messing with its own state.

And that is why I don't like modding for OB anymore, its just too restricting...

Link to comment
Share on other sites

. . . Do you see any problems with this approach, other than the need to document dependency on OBSE?

(a little late, but . . . ) Your approach sounds sound.

One question: do you run the detection every time the cell is loaded? Do you need to? I ask because, under some conditions, the OnLoad block does not trigger. Can't remember the condition, I am afraid.

Link to comment
Share on other sites

(a little late, but . . . ) Your approach sounds sound.

One question: do you run the detection every time the cell is loaded? Do you need to? I ask because, under some conditions, the OnLoad block does not trigger. Can't remember the condition, I am afraid.

I currently run it with each OnLoad block, but I think I'm going to change that behavior. I am finding that my algorithm is pretty efficient, and what I may do instead is run it OnActivate but then cache the result.

At first blush, this sounds less efficient, but there are a number of these in the cell, and most of the time the PC will not activate more than one or two of them in a given visit to that cell. If I use OnLoad, all of them do the calc each time the cell loads. If I do OnActivate, only one can possibly run at a time, and it will only run once per cell visit.

Find the guy in this picture who used to write industrial controller software.... :-)

Link to comment
Share on other sites

Yes, I know what you mean.

As for the switch, do you expect the activator-light pair to change over time?

Cant you do the detection just once? Then it does not really matters if you do it on the OnLoad or the OnActivate block (and I agree that the OnActivate option is better).

Link to comment
Share on other sites

Yes, I know what you mean.

As for the switch, do you expect the activator-light pair to change over time?

Cant you do the detection just once? Then it does not really matters if you do it on the OnLoad or the OnActivate block (and I agree that the OnActivate option is better).

I do not expect it to change over time, but it actually does matter which block I use. The reason I'm changing is that I realized that with a dozen or more of these things in my cell, having them all do their detection on the same initial load might make that load a bit slow. The OnActivate breaks those detection loops into smaller pieces, and has the bonus of not doing the calculation at all for any that the player happens never to use.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...