[Papyrus] Wert, den eine Function beim Aufrufen ausgibt in eine Variable übergeben

E

Ehemaliger Benutzer 14796

Gast
Hallo allerseits,

ich beschäftige mich zur Zeit mit der Questerstellung und damit auch mit der Erstellung verschiedener Scripts. Grds. ist Erfahrung im Scripting-Bereich vorhanden, wenn hauptsächlich auch von x³ Terran Conflict (sowie html und ein wenig php, aber das tut hier nichts zur Sache).

Ich habe eine Quest, mit zwei zusätzlichen Scripts, im Script-Abteil des Quest-Fensters (vgl. Quest CW -> der Bürgerkrieg).

Das erste Script ist ein Beispiel vom CK-Wiki und soll die aktuelle Ingame-Zeit, bzw. den Tag, auslesen und dann ausgeben.

Code:
Scriptname CWEquipCurrentTime extends TopicInfo  

Import Utility
Import Math
 
;/++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+    GetPassedGameDays() returns the number of fully passed ingame days
+    as int.
+
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++/;
 
Int Function GetPassedGameDays() Global
    Float GameTime
    Float GameDaysPassed
 
    GameTime = GetCurrentGameTime()
    GameDaysPassed = Floor(GameTime)
    return GameDaysPassed as Int
EndFunction

Die Frage, die sich mir stellt ist, wie ich den ausgegebenen Wert in eine Variable übergeben kann, wenn ich die oben gezeigte Funktion in einem zweiten Script aufrufe. Zur Zeit sieht das zweite Script wie folgt aus:
Code:
Scriptname CWEquipLegatTime extends Quest Conditional

int Property timereached Auto Conditional Hidden    

function setfinaltime()

int finaltime

finaltime = (CWEquipCurrentTime.GetPassedGameDays() + 2)


while finaltime >= CWEquipCurrentTime.GetPassedGameDays()
If finaltime == CWEquipCurrentTime.GetPassedGameDays()
timereached = 1

EndIf
EndWhile
EndFunction

Ob das so auch funktioniert, muss ich gleich noch testen, was ich aber gerne wüsste ist, ob es nicht wirklich einen Weg gibt, das, was die aufgerufene Funktion ausgibt, in eine Variable zu übergeben. Ich hatte das so versucht:
Code:
int currenttime
CWEquipCurrentTime.GetPassedGameDays(currenttime)
Wenn ich es so mache, erhalte ich aber die Fehlermeldung, "too many arguments passed to function".

Ich habe auch folgendes versucht:
Code:
currenttime = CWEquipCurrentTime.GetPassedGameDays()
Das gibt zwar keine Fehlermeldung, aber die Variable scheint keinen Wert zu kennen. Wenn ich additionen durchführe, oder vergleiche (==) sagt mir der Compiler, dass das nicht geht, weil die Variable currenttome keinen Wert hat, nur "none".
 
Zuletzt bearbeitet von einem Moderator:
Code:
int currenttime CWEquipCurrentTime.GetPassedGameDays(currenttime)
-> Das kann nicht funktionieren. Hier versuchst du der Function die Variable "currenttime" zu übergeben. Die Function kann aber gar keine Parameter empfangen.
Code:
currenttime = CWEquipCurrentTime.GetPassedGameDays()
Das sollte funktionieren, wenn currenttime ein int ist...


Versuchs mal so:

Code:
  int GameDaysPassed  
GameDaysPassed = Math.Floor(Utility.GetCurrentGameTime())  
return GameDaysPassed



-> Fehler tritt wahrscheinlich auf, weil du "Utility" vergessen hast...
Außerdem gibt Floor() bereits ein int zurück. Ist also nicht ratsam den Rückgabewert von Floor in ein float zu speichern und das Float dann wieder in ein int umzuwandeln...
 
Zuletzt bearbeitet:
Versuchs mal so:
Code:
  int GameDaysPassed  
GameDaysPassed = Floor(Utility.GetCurrentGameTime())  
return GameDaysPassed

-> Fehler tritt wahrscheinlich auf, weil du "Utility" vergessen hast...
Außerdem gibt Floor() bereits ein int zurück. Ist also nicht ratsam den Rückgabewert von Floor in ein float zu speichern und das Float dann wieder in ein int umzuwandeln...

Danke für die Rückmeldung.

Den Code, den ich oben als zweites Script aufgeschrieben hatte, funktioniert - ist jetzt getestet (da benutze ich keine Variable. Ist bei dem, was ich gemacht habe, gar nicht nötig).
Entsprechend funktioniert auch das erste Script.

Ich dachte, da dort direkt unter dem Script-Namen "Import Utility" stand, muss ich "Utility." nicht mehr davor schreiben?

Wenn bei return GameDaysPassed das "as int" dahinter weg nehme, erhalte ich die Fehlermeldung "cannot return a float from getpassedgamedays, the types do not match (cast missing or types unrelated)".
Deshalb werde ich das mal so lassen.

Gibt es eine Liste mit solchen Standardbefehlen - wie Floor() - im Wiki? Bisher habe ich nichts gefunden. Wäre zB auch interessant, wenn man einen STRING haben will.
 
Zuletzt bearbeitet von einem Moderator:
Danke für die Rückmeldung.
Ich dachte, da dort direkt unter dem Script-Namen "Import Utility" stand, muss ich "Utility." nicht mehr davor schreiben?

Sry hab auf Arbeit in der Eile das Import übersehen, du musst natürlich den Scriptnamen nicht beim Aufruf einer Funktion mit angeben, wenn du das Script importiert hast...

Wenn bei return GameDaysPassed das "as int" dahinter weg nehme, erhalte ich die Fehlermeldung "cannot return a float from getpassedgamedays, the types do not match (cast missing or types unrelated)".
Deshalb werde ich das mal so lassen.

Die Fehlermeldung kommt nur, weil du den int-Rückgabewert von Floor() in eine float-Variable schreibst. Diese Variable wandelst du dann vor dem return wieder in ein int um. Das ist nicht notwendig.
(siehe Definition von Floor() - http://www.creationkit.com/Floor_-_Math )

Wenn du noch mehr Code sparen willst kannst du auch einfach die Function in einen Einzeiler verwandeln:

return Floor(GetCurrentGameTime())


Es ist zu überlegen, ob du überhaupt die Function benötigst, eingentlich kannst du auch an den betreffenden Stellen
Code:
int myVar = Floor(GetCurrentGameTime())
aufrufen.
Viel hast du durch die Function nicht gewonnen, im Gegenteil - dein Code wird imho schlechter lesbar, weil man evtl. erst an die Stelle der Function springen musst, um zu verstehen, was diese genau macht...


edit:
Eine komplette Liste mit Befehlen und so weiter findest Du hier.

ein paar hilfreiche Dokumentationen, zusätzlich zur Befehlsliste:

http://www.creationkit.com/Arrays_(Papyrus)
http://www.creationkit.com/Cast_Reference
http://www.creationkit.com/Differences_from_Previous_Scripting


Methoden zum Verarbeiten von strings (einen String mit Hilfe von Trennzeichen zerlegen, Zeichen am Anfang oder Ende des Strings löschen...) suche ich auch schon länger vergebens. Man kann nichtmal eigene Methoden schreiben, weil man einen String nicht zu einem Array casten kann wie in anderen Sprachen (jedes Zeichen im String belegt einen Index).
Langsam glaube ich, dass das mit Papyrus nicht möglich ist.
Wenn jemand etwas Gegenteiliges weiß, immer her damit ;-)
 
Zuletzt bearbeitet:
Sry hab auf Arbeit in der Eile das Import übersehen, du musst natürlich den Scriptnamen nicht beim Aufruf einer Funktion mit angeben, wenn du das Script importiert hast...

Kein Thema. ;)


Es ist zu überlegen, ob du überhaupt die Function benötigst, eingentlich kannst du auch an den betreffenden Stellen
Code:
int myVar = Floor(GetCurrentGameTime())
aufrufen.
Viel hast du durch die Function nicht gewonnen, im Gegenteil - dein Code wird imho schlechter lesbar, weil man evtl. erst an die Stelle der Function springen musst, um zu verstehen, was diese genau macht...

Hey, ein Erfolgserlebnis! Im Grunde genommen hab ich das inzwischen gemacht.
Ich hatte den den Code für die Function ja aus dem Wiki und hab den erstmal nur stumpf kopiert. Bin nach deinem letzten Post alles nochmal mit Sinn und Verstand durchgegangen und habe aus zwei Scripts eins gemacht:

Code:
Scriptname CWEquipLegatTime extends Quest Conditional

int Property timereached Auto Conditional Hidden    

function setfinaltime()

int currenttime = Math.Floor(Utility.GetCurrentGameTime())

int randomnr = Utility.RandomInt(1, 3)

int finaltime = (currenttime + randomnr)

while finaltime >= currenttime
currenttime = Math.Floor(Utility.GetCurrentGameTime())
If finaltime == currenttime
timereached = 1
CWEquipLegatQuest.SetStage(30)
EndIf
EndWhile

EndFunction

Quest Property CWEquipLegatQuest  Auto

Die Sache funktioniert ingame wie geplant. (Hängt mir einer Quest zusammen, bei der Spieler einem Schmied einen Auftrag gibt. Die Sache soll dann zwischen zwei und vier Tagen fertig werden. Dauert also ein wenig, aber es will ja auch erstmal hergestellt werden - zumindest so als ob. Danach kann das Zeug dann abgeholt und bezahlt werden. Ich hoffe es gab keinen einfacheren Weg das zu tun.)

Meine Frage, wie man das, was die Funktion ausgibt, in eine Variable übergibt, hat sich durch die ganze Sache auch geklärt. Vielen Dank!

Jetzt muss ich mla rausfinden, warum kein Archiv erstellt wird, wenn ich mit dem CK eins erstelle ... trotz Speichern ist da nichts unter Data/ ...
 
Zuletzt bearbeitet von einem Moderator:
Die Sache funktioniert ingame wie geplant. (Hängt mir einer Quest zusammen, bei der Spieler einem Schmied einen Auftrag gibt. Die Sache soll dann zwischen zwei und vier Tagen fertig werden. Dauert also ein wenig, aber es will ja auch erstmal hergestellt werden - zumindest so als ob. Danach kann das Zeug dann abgeholt und bezahlt werden. Ich hoffe es gab keinen einfacheren Weg das zu tun.)

Ah jetzt weiß ich was du machen willst.
Hier noch 2 Tipps:

1. Ich würde CurrentGameTime nicht zu einem Int runden. Vor allem da immer abgerundet wird kann es durchaus sein, dass du in Wirklichkeit nicht die korrekte Zeit warten musst, bis der Schmied fertig ist. Warum addierst du nicht die Zufallszahl auf das float, so wäre berechnung der Wartezeit viel genauer?

2. Deine (while) Schleife läuft Tagelang ununterbrochen durch und prüft - ka wie oft aber sehr häufig, so oft wie es der Rechner halt schafft - in der Sekunde unnötig die Zeit. Das verbraucht sehr viel Rechenleistung!! Wenn mehrere Mods so vorgehen würden, wäre Skyrim ziemlich schnell unspielbar.
Ich rate dir dringend die Schleife zu entfernen und dafür mit RegisterForUpdateGameTime() (in deinem Fall besser RegisterForSingleUpdateGameTime() ) zu arbeiten und nur alle paar (ingame) stunden zu prüfen.
Hier mal ein Link zur Doku: http://www.creationkit.com/RegisterForSingleUpdateGameTime_-_Form
 
Ich werde beides ändern, danke! ;) Ich hätte versucht das mit Wait abzuschwächen, aber dann müsste man sehr viele Sekunden einstellen, das ist suboptimal. Ob das die Performace verbessert hätte, weiß ich dabei dann auch nicht.

EDIT:
Funktioniert:
Code:
Scriptname CWEquipLegatTime extends Quest Conditional

int Property timereached Auto Conditional Hidden    

; getting an random number of days passed, till the armor is ready
function setfinaltime()
int randomnr = Utility.RandomInt(1, 3)
RegisterForSingleUpdateGameTime(24 * randomnr)
EndFunction

; if time has passed, send player to get his armor
Event OnUpdateGameTime() ; because of the registeration this event occurs after 2 till 4 Days
    timereached = 1
    CWEquipLegatQuest.SetStage(30)
    UnregisterForUpdateGameTime()
endEvent

Quest Property CWEquipLegatQuest  Auto

Gibt es sowas auch für Sammelquests? Z.B. wenn ich einen Eisgeisterzahn zu jemanden bringen soll und sich die Questobjectives jedesmal ändern, je nachdem ob ich den Zahn aus dem Inventar nehme oder nicht.

Oder lässt man ein script auf dem Spieler laufen, mit nem Event, dass bei aufnehmen oder droppen auslöst?

EDIT 2:
Gefunden, AddInventoryEventFilter: http://www.creationkit.com/AddInventoryEventFilter_-_ObjectReference
 
Zuletzt bearbeitet von einem Moderator:
Das sieht doch schon ganz gut aus :)

Nur ein Tipp noch, du musst bei ...SingleUpdateGameTime nicht unregisterforUpdate... aufrufen, da eh nur ein Update geschickt wird.

Gruß, AZ
 
Habs korrigiert, danke!

Ich habe noch ein wenig rumgespielt. Vorweg: Ich vermute Gold ist suboptimal, weil es vrsl. sehr häufig bewegt wird, was jedesmal das Script, bzw. Event, in Aktion bringt.

Zum Plan/Ziel/Vorhaben:
Der Schmied in der Quest will Gold sehen, wenn die Rüstung abgeholt wird. Wenn der Spieler das nicht hat, scheitert das QuestObjective.
Danach (darum geht es jetzt hier) wird ein neues angezeigt, das den Spieler anweist, erst einmal das Gold zu besorgen.
Wenn er das dann hat, ist letztgenanntes Objective erfüllt und ein wieder neues wird angezeigt, das sagt, dass der Spieler genug Gold hat und die Rüstung abholen kann.
Wenn sich der Goldbetrag ändert und der Spieler wieder zu wenig Gold hätte, werden die QuestObjectives wieder entsprechend umgestellt und umgekehrt.

Um das zu erreichen habe ich versucht mit dem AddInventoryEventFilter und OnItemAdded/OnItemRemoved zu arbeiten. Ich hab mir angesehen, wie Bethesda das bei der Quest mit der ID "FreeformRiften10" (Öl ins Feuer) gemacht hat. Ist prinzipiell vergleichbar, glaube ich.
Die haben dort den Spieler als Quest-Alias eingesetzt (Specific Reference, any, PlayerRef) und lassen auf dem Alias ein Script laufen, dass das macht. Ich habe den Aufbau größtenteils übernommen, aber wenn ich das dann in die Original-Quest von mir einbaue, funktioniert es nicht.
Es passiert gar nichts, wenn der Spieler Gold aus dem Inventar ablegt oder welches aufnimmt, die Objectives werden also nicht aktualisiert.

Das Quest, das die Zeit überwacht (obige Posts), ist im Reiter "Scripts" der Quest und als Conditional gesetzt. Auch das Script auf dem Player-Alias ist Conditional, liegt aber eben auf ihm, bzw. dem Alias. Liegt es vielleicht daran, dass zwei Scripts am Werk sind, die Conditional sind?

Ich bekomme das nur korrekt zum Laufen (es funktioniert also), wenn ich eine zweite Quest erstelle und lediglich das Player-Alias mit dem Script dort laufen lasse. Was QuestObjectives und Co angeht, verweise ich natürlich auf die eigentliche Quest und nicht auf die Hilfskonstruktion.

Es folgt das Script, dass auf dem Spieler läuft (das Spieler-Alias soll übrigend nur/erst aktiviert werden, wenn die enstprechende Stage der Quest läuft. Dazu habe ich die erforderliche Stage bei dem Alias als Condition gesetzt). Falls man performance-technisch noch was verbessern kann, wäre ich auch neugierig. ;)


Code:
Scriptname CWEquipLegatChGold extends ReferenceAlias  Conditional

Quest Property CWEquipLegat  Auto  Conditional
MiscObject Property Gold  Auto  Conditional
Quest Property HelperQuest  Auto  

Event OnInit()
    AddInventoryEventFilter(gold)
endEvent

Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)
    if Game.GetPlayer().GetGoldAmount() >= 2000
           if CWEquipLegat.IsObjectiveDisplayed(34)
               CWEquipLegat.SetObjectiveCompleted(34)
               CWEquipLegat.SetObjectiveDisplayed(36)
           endIf
     endif
if CWEquipLegat.IsCompleted() == 1
     HelperQuest.SetStage(10)
     RemoveInventoryEventFilter(gold)
endIf
endEvent


Event OnItemRemoved(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer)
    if Game.GetPlayer().GetGoldAmount() < 2000
         if CWEquipLegat.IsObjectiveDisplayed(36)
              CWEquipLegat.SetObjectiveCompleted(34, false)
              CWEquipLegat.SetObjectiveDisplayed(36, false)
              CWEquipLegat.SetObjectiveDisplayed(34, abForce = false)    
        endIf
    endif
if CWEquipLegat.IsCompleted() == 1
     HelperQuest.SetStage(10)
     RemoveInventoryEventFilter(gold)
endIf
endEvent
 
Zuletzt bearbeitet von einem Moderator: