Guest ffabris Posted October 13, 2015 Posted October 13, 2015 My question here has two related aims. First is how to achieve what I am about to describe, and the second is more specifically, how to correctly use ForceRefTo. Here's the scenario I am trying to achieve.I have a number of actors scattered round the worldspace. They have a basic sandbox package. When Player enters the area (OnCellAttach), the actor should travel to the player and trigger an action. On having completed the action, the actor should resume the default sandbox.In order to prevent this happening too often, there are two timers: one global one, which limits how often the action can happen overall, and a second more specific one, which prevents the same actor from triggering the action again until 24 game hours have passed. The timers, however, are not relevant to my problem, so I will omit them from the code snippets below.Here is how I have tried to implement this. I have a controlling quest (let's call it ControllerQuest), and a small script attached to each of these actors. That script looks something like this: Scriptname MyActor extends Actor ControllerQuest Property aQuest Auto Event OnCellAttach() aQuest.RegisterActor(self) Endevent ControllerQuest has an alias, CustomActor, of type Specific Reference, left empty, and Optional enabled. The function called looks something like this: Function RegisterActor(actor anActor) Alias_CustomActor.ForeceRefTo(anActor) If(Alias_CustomActor == none) Debug.Notification("ForeceRefTo failed!") Else Alias_CustomActor.GetActorReference().EvaluatePackage() EndIf EndFunction The alias has a Travel package, destination set to Alias_Player, and an OnEnd fragment to trigger the action.When testing this in game, I get "ForeceRefTo failed!", so I am clearly doing something wrong (The fact that the Travel package never starts is another big clue. ). I have looked at example code, but have not been able to figure it out.So my question: how can I achieve the goal outlined above? I guess I could create explicit aliases for each of the actors scattered round, and then use conditions on the travel package to prevent it from being used every time the Player enters the cell. I'm not quite sure which conditions as I haven't explored that option yet.However, I also want to learn how to use ForceRefTo correctly, as I may need it at some future point for something else.Thanks in advance for any help.
Guest Posted October 13, 2015 Posted October 13, 2015 You are using an alias that is not defined as property. So it will always fail because the object is undefined. Scriptname MyActor extends Actor ControllerQuest Property aQuest Auto ReferenceAlias Property Alias_CustomActor Auto Event OnCellAttach() Alias_CustomActor.ForeceRefTo(self as Actor) (self as Actor).ResetAI() (self as Actor).EvaluatePackage() EndFunction I will not rely on "OnCellAttach", it is not safe to understand if the player entered a cell.
Guest ffabris Posted October 13, 2015 Posted October 13, 2015 You are using an alias that is not defined as property. So it will always fail because the object is undefined. Scriptname MyActor extends Actor ControllerQuest Property aQuest Auto ReferenceAlias Property Alias_CustomActor Auto Event OnCellAttach() Alias_CustomActor.ForeceRefTo(self as Actor) (self as Actor).ResetAI() (self as Actor).EvaluatePackage() EndFunction I will not rely on "OnCellAttach", it is not safe to understand if the player entered a cell. Aha! OK, I'll try that a bit later (I'm working on part of the main project ATM). But questions on the above: The OnCellAttach event is in the script on the actor, not in the quest, so I doesn't need aliases. Still, it should work in the Register function in the quest script, right? What do you recommend using instead of OfCellAttach? Thanks for the reply - much appreciated!
Guest Posted October 13, 2015 Posted October 13, 2015 Aha! OK, I'll try that a bit later (I'm working on part of the main project ATM). But questions on the above: The OnCellAttach event is in the script on the actor, not in the quest, so I doesn't need aliases. Still, it should work in the Register function in the quest script, right? What do you recommend using instead of OfCellAttach? Thanks for the reply - much appreciated! You need the RefAlias. It is not got by magic. And about "OnCellAttach" the best solution depends on where your actors are. Because you are attaching a script to actors, then probably the actors are well defined and placed somewhere in the world. At this point probably you can add some triggers in some places to handle this. There is a really advanced way, to have an invisible object always attached to the player, and handle the events on this object. I used it a couple of times and it works just fine, and it is light weight. You can check how it is done inside my CSB mod.
Guest ffabris Posted October 13, 2015 Posted October 13, 2015 Aha! OK, I'll try that a bit later (I'm working on part of the main project ATM). But questions on the above: The OnCellAttach event is in the script on the actor, not in the quest, so I doesn't need aliases. Still, it should work in the Register function in the quest script, right? What do you recommend using instead of OfCellAttach? Thanks for the reply - much appreciated! You need the RefAlias. It is not got by magic. Agreed - but in the quest script, not the actor script. Or rather, in the script that's using ForeceRefTo. And about "OnCellAttach" the best solution depends on where your actors are. Because you are attaching a script to actors, then probably the actors are well defined and placed somewhere in the world. At this point probably you can add some triggers in some places to handle this. There is a really advanced way, to have an invisible object always attached to the player, and handle the events on this object. I used it a couple of times and it works just fine, and it is light weight. You can check how it is done inside my CSB mod. OK, thanks. It isn't vital for the actor to respond. If memory serves, there are cases when OnCellAttach won't be triggered, and that's fine, for what I need. Anyway, thanks again, this helped a great deal! I knew I was overlooking something very basic! [headdesk]
Guest Posted October 13, 2015 Posted October 13, 2015 Agreed - but in the quest script, not the actor script. Or rather, in the script that's using ForeceRefTo. You need it. It does not matter if you are in a quest script or in another script. Aliases are not filled by magic. Now, doing the function in the quest script is possible, but I don't see the reason of doing it there. You will need to reference the quest script inside the actor script and then call a function of the quest script and passing the current actor to it (the "self") You just need the refalias (as property.) Everything else is not useful.
Guest ffabris Posted October 13, 2015 Posted October 13, 2015 Agreed - but in the quest script, not the actor script. Or rather, in the script that's using ForeceRefTo. You need it. It does not matter if you are in a quest script or in another script. Aliases are not filled by magic. Now, doing the function in the quest script is possible, but I don't see the reason of doing it there. You will need to reference the quest script inside the actor script and then call a function of the quest script and passing the current actor to it (the "self") You just need the refalias (as property.) Everything else is not useful. Hum. OK, let's go back to the scenario. Part of what the quest script does, is to check a timer to see if the action was already triggered within the last TimeOut hours. So the function is more like this (using the initial code snippet from the start): Function RegisterActor(actor anActor) if(! AlreadyTriggered) Alias_CustomActor.ForeceRefTo(anActor) If(Alias_CustomActor == none) Debug.Notification("ForeceRefTo failed!") Else Alias_CustomActor.GetActorReference().EvaluatePackage() alreadyTriggered = true ; [do the action here....] RegisterForSingleUpdateGameTime(TimeOut) EndIf EndIF EndFunction Event OnUpdateGameTime() UnregisterForUpdateGameTime() alreadyTriggered = false EndEvent In other words, the actor script has to at least first check with the quest script to see whether the action can be started or not. No need to fill the alias (and thus start the Travel package) if the action doesn't need to start. So I figured it was easier to just handle the entire thing in the quest.
Guest Posted October 13, 2015 Posted October 13, 2015 Clean up your script like this: bool alreadyTriggered ReferenceAlias Property Alias_CustomActor Auto Event OnInit() alreadyTriggered = false EndEvent Function RegisterActor(actor anActor) if !alreadyTriggered && anActor alreadyTriggered = true Alias_CustomActor.ForeceRefTo(anActor) anActor.ResetAI() anActor.EvaluatePackage() RegisterForSingleUpdateGameTime(TimeOut) ; "Do absolutely nothing here. Do it in the package that the actor will run. Or in a function that is called when the package ends." EndIF EndFunction Event OnUpdateGameTime() alreadyTriggered = false ; "No need to unregister, you registered for a SINGLE update" EndEvent
Guest ffabris Posted October 14, 2015 Posted October 14, 2015 Yes, I'm executing the action by calling a question function from the package. OK, this is what I have - tons of logging code included to try to track problems: Scriptname ContollerQuest extends Quest CPGUtility Property MyUtils Auto Float Property Timeout = 0.25 Auto ReferenceAlias Property Alias_CustomActor Auto Actor actPlayer bool CalledAlready = false Event OnInit() actPlayer = Game.GetPlayer() CalledAlready = false EndEvent Function RegisterActor(actor anActor) MyUtils.Log("RegisterActor Entered....") if(!anActor) MyUtils.Log("ContollerQuest.RegisterActor: anActor is NULL!") EndIf if(!CalledAlready && anActor) MyUtils.Log("ContollerQuest: About to force alias.....") Utility.Wait(1.0) Alias_CustomActor.ForceRefTo(anAnimal) if(Alias_CustomActor == none) MyUtils.Log("Alias_CustomActor not filled!") Else Alias_CustomActor.GetActorReference().ResetAI() Alias_CustomActor.GetActorReference().EvaluatePackage() EndIf EndIf EndFunction MyUtils.Log just calls Debug.Notification and Debug.Trace, if an internal bool is true. Skyrim crashes on trying to force the alias - I can tell because "ContollerQuest: About to force alias....." is logged. Alias_CustomActor is set as a property. The property is filled. The alias in the aliases tab is CustomActor. At least before, when I didn't have it as a property, it simply didn't work, now it crashes. LOL I am sure I am missing something totally obvious, and not seeing the forest for the trees.
Guest Posted October 14, 2015 Posted October 14, 2015 OK. A few questions. And a few thing you HAVE to fix. Did you set the alias to "Specific Alias", set to nothing, and made it optional? If not this is the problem. Then DO NOT!!!!!!! use Game.GetPlayer() NEVER. BANNED. FORBIDDEN. DON'T DO IT (simple and plain.) Add another property: Actor Property PlayerRef Auto And live happy. Then. DO NOT grab the actor from the alias just to calculate the packages. You already have the actor, for god sake! Call it directly to the actor! "if(Alias_CustomActor == none)" Does not tell you that the alias is not filled. It may tell you that you forgot to fill the property. Do not use unnecessary parentheses for conditions (if you are not optimizing by compiling the scripts with a custom compile bat that enable compiler optimizations.) They will slow down your code.
Guest ffabris Posted October 14, 2015 Posted October 14, 2015 OK. A few questions. And a few thing you HAVE to fix. Did you set the alias to "Specific Alias", set to nothing, and made it optional? If not this is the problem. Yes to "Specific Alias", and "Optional". To right of "Specific Alias" is "Set Forced actor" - the window that opens is empty. So the Aliases tab shows: Forced [NONE] "if(Alias_CustomActor == none)" Does not tell you that the alias is not filled. It may tell you that you forgot to fill the property. It shows as filled in the script properties window. Do not use unnecessary parentheses for conditions (if you are not optimizing by compiling the scripts with a custom compile bat that enable compiler optimizations.) They will slow down your code. Habit from my C++ days, to make things more legible, nothing more. Added: Update. The "meat" of the function is now: if(!CalledAlready && anActor) MyUtils.Log("ContollerQuest: About to force alias.....") Utility.Wait(1.0) Alias_CustomActor.ForceRefTo(anAnimal) if(Alias_CustomActor == none) MyUtils.Log("Alias_CustomActor not filled!") Else MyUtils.Log("About to Reset AI....") anActor.ResetAI() MyUtils.Log("About to Evaluate Package....") anActor.EvaluatePackage() EndIf EndIf And the two in the else block get logged. So I guess using the actor directly fixed that. I think. I didn't have Log code in there before to be sure. My crash is now at (or right after) "TravelToPlayer Package Start" (which is an OnBegin fragment in the Travel package). The Travel package belongs to the quest, as has Destination set to Alias: Player. The OnEnd fragment doesn't seem to be called since the corresponding Log isn't called.
Guest Posted October 14, 2015 Posted October 14, 2015 That is useless if you use a property: if(Alias_CustomActor == none) MyUtils.Log("Alias_CustomActor not filled!") Else MyUtils.Log("About to Reset AI....") anActor.ResetAI() MyUtils.Log("About to Evaluate Package....") anActor.EvaluatePackage() EndIf[code] This because you are checking if the property is empty, and if it is It is just because you committed an error by don't filling it.You cannot test the content of an alias by checking if the reference property is filled. So, if you get the logs from the ELSE clause to be logged then you DID NOT filled the property.(And Properties do not fill themselves by magic.)
Guest ffabris Posted October 14, 2015 Posted October 14, 2015 That is useless if you use a property: if(Alias_CustomActor == none) MyUtils.Log("Alias_CustomActor not filled!") Else MyUtils.Log("About to Reset AI....") anActor.ResetAI() MyUtils.Log("About to Evaluate Package....") anActor.EvaluatePackage() EndIf[code=auto:0] This because you are checking if the property is empty, and if it is It is just because you committed an error by don't filling it.You cannot test the content of an alias by checking if the reference property is filled. So, if you get the logs from the ELSE clause to be logged then you DID NOT filled the property.(And Properties do not fill themselves by magic.) Huh? The else code runs if the alias is not empty. And clearly, since the code in that block is called, it isn't empty. I know the check is useless, but frankly, it does no harm, and my focus now is to get the whole thing to work; I'll clean up the mess after.
Guest ffabris Posted October 14, 2015 Posted October 14, 2015 Note to self: a crash will cause the papyrus log to be truncated, thus leading me to look in the wrong place for problems. Insert Wait() code after each output to log to try to ensure it gets written. Anyway, I can confirm that... My crash is now at (or right after) "TravelToPlayer Package Start" (which is an OnBegin fragment in the Travel package). The Travel package belongs to the quest, as has Destination set to Alias: Player. The OnEnd fragment doesn't seem to be called since the corresponding Log isn't called. In other words, the Alias is filled correctly since the code in the associated package is invoked. But the package itself dies, as the code I have set for when the package ends, is not called. Since writing the above quoted bit, I changed the package to a standard Travel, destination PlayerRef (cell any). I just did a test, to verify. I reinstated this "bad" line: Alias_Actor.GetActorReference().ResetAI() and that works (no crash). So the alias is filled. What makes me laugh about this, is that this issue is fluff, it isn't part of the main quest at all, and it is meant to be nothing more than an occasional distraction to the player. But now I have the challenge of trying to figure it out - and learn from it. Added: Last tests (for tonight): I removed the travel package altogether, and now it crashed before. Last log entry I have is right before ResetAI. [headdesks repeatedly] The last thing I did was to revert to before I started using aliases, and that still works fine. The only reason I went this route was to be able to have the actor approach the player naturally rather than suddenly appear there. And that apparently won't work in any way other than via a package. PathToReference() fails, for example. Bah... enough for today.
Guest ffabris Posted October 14, 2015 Posted October 14, 2015 Removing the call to ResetAI() fixed the crashing. No idea why, but it did. Added: OK, confirmed. I cleaned up the code, removed all the logging calls, the waits, etc and tested again. it works flawlessly now. So my error was omitting the alias, as you said from the start. The rest was at least functionally correct, if inelegant. Thanks very much for the help!
Recommended Posts
Archived
This topic is now archived and is closed to further replies.