Haravikk Posted September 21, 2017 Share Posted September 21, 2017 I've created for myself a simple interior teleport using the College of Winterhold gate stone (the circular college symbol) as a pair of load doors so that I (and NPCs when the nav meshes are connected) can teleport between them easily.This occurs entirely within the same cell, so there's no need to autosave each time the player teleports; is it possible to suppress autosaving on my teleporters, if so, how? I know some people prefer to turn off autosaving entirely, but I actually like having autosaves generated when I exit/enter a cell, so I'd rather not turn it off entirely if I can; I like having those auto-saves so I don't lose what I did in an area just because it turns out a dragon is outside waiting to eat me :) Also, rather than posting a separate topic, what is the easiest way to trigger an effect when someone uses the door? I was thinking of triggering the summon/banish style effects at each end of the teleporter, how easy would that be to do? Is this something that will need to be scripted or is there another way to do it? Link to comment Share on other sites More sharing options...
cdcooley Posted September 22, 2017 Share Posted September 22, 2017 You might need to move the player with a scripted MoveTo function call instead of letting the player use the door normally. If you're in the same cell that should skip the autosave. Leave the door itself for NPCs to use though. You would just add a script to the door which blocks activation then test who's activating it and decide whether to activate the door normally or move the player directly to the destination. Link to comment Share on other sites More sharing options...
Haravikk Posted September 22, 2017 Author Share Posted September 22, 2017 Won't blocking activation also prevent NPC's from being able to use the teleport? Or do you mean attempting to block activation in the OnActivate method? Because on the creation kit wiki it sounds like it may be too late to do it within that event. I'll give it a try anyway when I get a chance, but if slow scripting could result in inconsistent behaviour it may not work all the time. Link to comment Share on other sites More sharing options...
cdcooley Posted September 23, 2017 Share Posted September 23, 2017 Calling BlockActivation disables the default automatic activation and simply passes the event into your script. Then in the OnActivate event you can force the standard activation when you choose to or take some other action instead. Here's the basic script that should work if you attach it to your door and fill the properties. You'll need to explicitly fill the DestinationMarker with a standard heading marker you place near door's own teleport marker. ScriptName DoorForNPCsOnly extends ObjectReference Actor Property PlayerRef Auto ObjectReference Property DestinationMarker Auto Event OnInit() BlockActivation() EndEvent Event OnLoad() BlockActivation() EndEvent Event OnActivate(ObjectReference akActionRef) if akActionRef == PlayerRef PlayerRef.MoveTo(DestinationMarker) else Activate(akActionRef, true) ; forces the activation skipping the script endif EndEvent Link to comment Share on other sites More sharing options...
Haravikk Posted September 23, 2017 Author Share Posted September 23, 2017 (edited) Wow, thanks for the sample script! It's a shame there's no way to use the existing teleport markers, but 16 new markers later and I've got this up and running like a charm! Even added a little bonus by adding a VisualEffect property, assigned to DA07SummonTargetFX (the summoning effect) by default. Calling .Play(akTargetRef) on it just before moving/activating gives a nice and easy teleport effect. Might be a tad overkill but I like it :smile: So my finished script looks like: Scriptname CWINSTeleporter extends ObjectReference ObjectReference Property DestinationMarker Auto VisualEffect Property TeleportEffect Auto Event OnInit() BlockActivation() EndEvent Event OnLoad() BlockActivation() EndEvent Event OnActivate(ObjectReference akActionRef) Actor PlayerRef = Game.GetPlayer() TeleportEffect.Play(akActionRef, 0.1) if akActionRef == PlayerRef PlayerRef.MoveTo(DestinationMarker) else Activate(akActionRef, true) endif EndEvent As an aside; I noticed you store PlayerRef as a property, but I thought pulling from Game.GetPlayer() might be better as it avoids storing it with the script/object, are there any particular advantages/disadvantages either way? Edited September 23, 2017 by Haravikk Link to comment Share on other sites More sharing options...
cdcooley Posted September 23, 2017 Share Posted September 23, 2017 I only post examples using the property method because in the cases where it matters that method is significantly better than what novices end up doing on their own. Using the GetPlayer() function is an extremely slow compared to using a property. Your method saves a little memory but will run slightly slower. For this case the speed difference doesn't matter so your method is better. I do that myself but explaining when it's better to do that instead of using the property is too much detail when people are asking how to fix other problems. Since your problem is already solved and you've asked here's the breakdown. In your script OnActivate won't get called often and timing isn't important. The only use of PlayerRef is also in that single function, so making it a local variable and filling it with GetPlayer() saves a little memory and only slows down the process by one frame. A single frame won't be noticed by the player so everything is good. Technically you don't need the PlayerRef variable. You could have called Game.GetPlayer() twice instead. But that would make your code slower by another frame. Again in this situation that's not really a problem but it's not as elegant and consumes extra resources to support calling the function a second time which is going to slow down other scripts too. It's when people need that value many times or across different functions and events that performance issues make using a property important. As an example, consider this script which is supposed to swap out equipment sets for the player. (I'm writing this without compiling so don't get distracted if there are small errors.) I've actually seen many scripts written in this style (even some of the original game scripts by Bethesda developers). ScriptName SwapEquipSets extends Quest Armor Property Unequipper Auto ; used to fill all equipment slots with a single item Function EquipThese(Armor[] itemset) Game.GetPlayer().AddItem(Unequipper) Game.GetPlayer().EquipItem(Unequipper) Game.GetPlayer().RemoveItem(Unequipper) int i = itemset.length while i > 0 i -= 1 if Game.GetPlayer().GetItemCount(itemset[i]) > 0 Game.GetPlayer().EquipItem(itemset[i]) endif endwhile Function That will take twice as much time to run as a version that uses the property instead. And the player will very likely see each item get equipped in sequence. ScriptName SwapEquipSets extends Quest Armor Property Unequipper Auto ; used to fill all equipment slots with a single item Actor Property PlayerRef Auto Function EquipThese(Armor[] itemset) PlayerRef.AddItem(Unequipper) PlayerRef.EquipItem(Unequipper) PlayerRef.RemoveItem(Unequipper) int i = itemset.length while i > 0 i -= 1 if PlayerRef.GetItemCount(itemset[i]) > 0 PlayerRef.EquipItem(itemset[i]) endif endwhile Function Using your method of declaring PlayerRef as a local variable would save memory at the cost of a single frame so that's still a reasonable option. Even with the improved version the player will still probably notice that not everything is equipped at once but it will still be better since things are happening twice as fast. Now consider scripts which use an OnUpdate event that runs fairly frequently. The overhead of calling GetPlayer() every time becomes a huge problem, and even filling a local variable at the beginning of the event each time is undesirable. For maximum performance you need either a property or a global variable that you initialize in OnInit. But if it's going to be a global variable you're not saving any memory, so if there are any other properties in the script that need to be filled in the CK there's no reason to avoid the PlayerRef property. The real problem is that when one script calls Game.GetPlayer() multiple times in order to save a little memory it's stealing CPU time from other scripts trying to do something productive. When it's in something like an OnActivate block it's no big deal, but if it's in anything that runs repeatedly then it is. Link to comment Share on other sites More sharing options...
foamyesque Posted September 25, 2017 Share Posted September 25, 2017 The big reason to avoid storing something as a property (or a global variable) is issues with persistence; doing so will make any object reference you do it to persistent, which has memory and performance implications. But the player reference is guaranteed to be persistent, so storing it as a property doesn't hurt anything. Link to comment Share on other sites More sharing options...
Haravikk Posted September 26, 2017 Author Share Posted September 26, 2017 Thanks for the detailed explanations! I should have said I am actually a programmer, but it seems I have a lot to learn about what Papyrus does and does not optimise; I guess I've been hideously spoiled by languages where a call to something like Game.GetPlayer() has effectively zero cost ;) I do have another question related to my teleporter though; in my own version of the script I use VisualEffect.Play(akActionRef, 0.1) to play the summoning effect on anyone using the teleporter. This works well enough since NPCs pause after teleporting anyway, so the effect appears to occur at the centre of the teleporter at either end. However, it's actually bound to the actor itself so moves with them, making it a little jarring and also, in the case of the player, will move with them if they move immediately after teleporting. It's not a major issue, but I considered switching to instead play the effect on the markers I'm using as the teleportation targets, so the effect would be at a fixed location at both ends, and play without interruption; unfortunately this doesn't seem to work since the markers are invisible, so the visual effect is likewise invisible (though the sound still plays). I'm wondering whether there might be an alternative? Can I force the visual effect to be visible despite the marker being hidden? Alternatively, is there a way to play a visual effect at a fixed coordinate rather than an object reference? Another alternative might be if I could duplicate the effect and somehow raise its height above the target; this way I could target the teleporters themselves (rather than having to add extra properties, since I already have the target pad as a property, to make it glow), but this isn't an option with the standard effect as the teleporters are at ground height, so the effect is too. But it doesn't look like there's much in the visual effect to customise, as it seems to just link to a .nif file. Link to comment Share on other sites More sharing options...
Recommended Posts