Custom NPCs mit Trigger

» Siedler Map Source Forum » Siedler DEdK Script Forum » Custom NPCs mit Trigger

Seiten: 1

Messoras
#1
07.12.2016 23:35
Beiträge: 84

Custom NPCs mit Trigger

Hallo Leute,
ich habe für meine neue Map eine neue Comfort Funktion geschrieben, damit auch einfache Einheiten NPCs auslösen können.
Falls es jemand nützlich findet:

------------------------------------
-- == CUSTOM NPCS von Messoras == --
------------------------------------

--[[   //  Custom NPCs  //  by Messoras
Dieses Comfortset ermöglicht das Erstellen von NPCs mit individuellen Triggern ( müssen nicht durch Helden
angesprochen werden ) und das Bewegen von Einheiten durch ( und aus ) Entities.
Folgende Funktionen sind enthalten:

    1. CreateCustomNPC( string name, string/table/function trigger, function callback )
        - Aufrufen um Custom NPC zu erstellen, der mit Trigger ausgelöst wird
        - Erstellt normalen NPC + Trigger ( NPC auch mit Helden ansprechbar )
        
        == Parameter ==
        - name: string            -- Skriptname des NPC
        - callback: function      -- Callbackfunktion des NPC ( Löst aus, wenn mit dem NPC gesprochen wird )
        - trigger:
            -> string:            -- Entity mit dem Skriptnamen trigger löst Callback aus, wenn nah beim NPC
            -> table:             -- Alle Entities, denen in Table enthaltene Skriptnamen zugeordnet sind,
                                  -- fungieren als Auslöser, wenn nah beim NPC
            -> function:          -- Callback wird ausgelöst, wenn Triggerfunktion true liefert
    --

    2. GhostMove( string unit, string target)
        - Aufrufen um Einheit ohne Berücksichtigung der Entityblockings zum Zielpunkt zu bewegen
        - Sobald das Ziel erreicht ist und die Einheit sich innerhalb eines Blockings befindet, verschwindet
          sie ( ohne Todesanimation )
        - Gleiche Nutzung, wie Main.Move

        == Parameter ==
        - unit: string            -- Skriptname der Einheit, die sich bewegen soll
        - target: string          -- Zielpunkt der Einheit
    --

    3. NewNPCFromHouse(string npcName, number entityType, number playerID, string housePos, string targetPos)
        - Aufrufen um eine neue Einheit zu erstellen und aus dem Gebäude kommen zu lassen
        - Es werden sowohl eine Entity ( mit Blocking ), als auch ein Zielpunkt außerhalb des geblockten 
          Bereichs benötigt

        == Parameter ==
        - npcName: string         -- Skriptname der Einheit, die aus dem Gebäude kommen soll
        - entityType: number      -- Der gewünschte Entitätentyp der erstellten Einheit ( aus Entities )
                                  -- zB. Entities.CU_Princess
        - playerID: number        -- ID des Spielers zu dem die Einheit gehört ( 1-8 )
        - housePos: string        -- Skriptname des Gebäudes, das verlassen werden soll
                                  -- Auch Erstellungsort für die Einheit
        - targetPos: string       -- Zielpunkt der Einheit ( vor dem Gebäude auf freier Fläche )
    --
]]
-------------------------------------------------------------------------------------------------------------
function CreateCustomNPC(_name,_trigger,_callback)

    -- callback prüfen
    assert(type(_callback)=="function","CustomNPC: Falsche oder keine Callbackfunktion. Funktion erlaubt")

    -- name prüfen
    assert(type(_name)=="string","CustomNPC: Falscher oder kein Name. String erlaubt")

    -- trigger prüfen
    assert(type(_trigger)=="string" or type(_trigger)=="table" or type(_trigger)=="function", 
        "CustomNPC: Falscher oder kein Trigger. String, Funktion oder Table erlaubt")

    -- NPC erstellen ( Ausrufezeichen )
    local npc  = { name = _name, callback = _callback }
    CreateNPC(npc)

    -- Implementierung des Triggers ( Entity/Entities oder Funktion )
    TriggerNPCJob = function(_nameloc, _triggerloc, _callbackloc, _npcloc)
        if type(_triggerloc) == "table" then
            for i = 1, _triggerloc.n do
                if IsNear(_triggerloc[i],_nameloc,300) then
                    _callbackloc()
                    DestroyNPC(_npcloc)
                    return true
                end
            end
        elseif type(_triggerloc) == "string" then
            if IsNear(_triggerloc,_nameloc,300) then
                _callbackloc()
                DestroyNPC(_npcloc)
                return true
            end
        elseif type(_triggerloc) == "function" then
            if _triggerloc() then
                _callbackloc()
                DestroyNPC(_npcloc)
                return true
            end
        elseif not _triggerloc then
            Message("Trigger is nil")
            return true
        end
    end

    StartSimpleJob("TriggerNPCJob",_name,_trigger,_callback,npc)

end
function GhostMove(_unit, _target)
    
    -- unit prüfen
    assert(type(_unit)=="string","CustomNPC: Falscher oder kein Einheitenname. String erlaubt.")

    -- target prüfen
    assert(type(_target)=="string","CustomNPC: Falscher oder kein Zielname. String erlaubt.")

    -- Blocking deaktivieren
    Logic.SetTaskList(GetID(_unit), TaskLists.TL_LEAVE_KEEP)
    local p = GetPosition(_target)

    -- Zielposition überschreiben
    Logic.MoveEntity(GetID(_unit), p.X, p.Y)

    -- Blocking wieder aktivieren
    StartSimpleJob("ArrivedJob",_unit,_target)

end
function NewNPCFromHouse(_npcName, _entityType, _playerID, _housePos, _targetPos)

    -- npcName prüfen
    assert(type(_npcName)=="string","CustomNPC: Falscher oder kein NPCname. String erlaubt.")

    -- entityType prüfen
    assert(type(_entityType)=="number","CustomNPC: Falscher oder kein Zielname. EntityType ( -> Nummer ) erlaubt.")

    -- playerID prüfen
    assert(type(_playerID)=="number","CustomNPC: Falsche oder keine PlayerID. Number erlaubt.")

    -- housePos prüfen
    assert(type(_housePos)=="string","CustomNPC: Falscher oder kein Gebäudename. String erlaubt.")

    -- target prüfen
    assert(type(_targetPos)=="string","CustomNPC: Falscher oder kein Zielname. String erlaubt.")

    -- Entity erstellen
    local pos = GetPosition(_housePos)
    Logic.SetEntityName( Logic.CreateEntity( _entityType, pos.X, pos.Y, 0, _playerID),_npcName )

    -- bewegen
    GhostMove(_npcName, _targetPos)
end
function ArrivedJob (_unit, _target)
    if IsNear(_unit,_target,50) then
        -- Blocking aktivieren und NPC abwarten lassen
        Logic.SetTaskList(GetID(_unit), TaskLists.TL_NPC_IDLE)
    end
end

----------------------------
-- == ENDE CUSTOM NPCS == --
----------------------------





Achtung:
Diese Comfortfunktion greift auf mcbs TriggerFix zu. Damit sie funktioniert fügt erst diesen eurem Skript hinzu ( MCBs TriggerFix )




Ist die erste Comfort, die ich hier poste und mit Assertions, also gebt mir bitte Rückmeldung, falls was nicht funktioniert, oder ihr Verbesserungsvorschläge habt.

LG,
Messoras

____________________
Six feet of earth make us all equal.

Spielt Siedler 5 online mit mir, dank des neuen Siedler 5 MP Projekts von Kimichura.

Dieser Beitrag wurde von Messoras am 15.12.2016 um 18:55 editiert.

mcb
#2
08.12.2016 00:03
Beiträge: 1472

Ich finds gut das du sowas hier postest, nur sehe ich beim ersten Drüberschauen ein paar Fehler

Von dem ersten hast du wahrscheinlich noch nie gehört: Upvalues.
Ein Upvalue ist eine lokale Variable einer Funktion, die in einer anderen Funktion als globale Variable verwendet wird. z.B. so:

function foo()
   local t = 1 -- hier eine lokale Variable
   local function bar()
      t = t + 1 -- hier eine globale Variable/upvalue
   end
   bar()
   Message(t) -- Ausgabe ist 2
   bar()
   Message(t) -- Ausgabe ist 3
end


Upvalues sind ein spezielles Sprachfeature von Lua (Meines Wissens nach gibt es das in keiner anderen Sprache so) .
Leider bekommt es die Speicherfunktion von Siedler nicht hin sowas zu speichern Beim Laden bleibt nur eine normale Globale Variable zurück, was die meisten nützlichen Verwendungen von Upvalues verhindert.

Bei dir sind npc, talkingNPC, _trigger und _callback upvalues, die dir beim Speichern Probleme bereiten werden.

Außerdem hast du noch Probleme damit, mehrere NPCs gleichzeitig zu erstellen, da die Wichtigen Funktionen wieder überschrieben werden.

Messoras
#3
08.12.2016 00:27
Beiträge: 84

Ja Schande über den Java Programmierer hier
Ich dachte ich mache einfach alles local und kann das beliebig oft aufrufen "

Wie kann ich das denn besser machen?

Gruß,
Messoras

____________________
Six feet of earth make us all equal.

Spielt Siedler 5 online mit mir, dank des neuen Siedler 5 MP Projekts von Kimichura.

mcb
#4
08.12.2016 00:40
Beiträge: 1472

Zitat von Messoras:

Ich dachte ich mache einfach alles local und kann das beliebig oft aufrufen "


In normalem Lua wäre das auch kein Problem, dann hättest du die Funktionen auch noch lokal gemacht und es würde funktionieren...

In den Originalscripten von BB läuft alles auf ein riesiges globales table raus, mit einem index als Parameter beim Job. Nicht besonders schön, aber es funktioniert (irgendwie).
Was ich immer mache, ist die ganzen Variablen als Parameter dem Job zu übergeben. Das funktioniert zwar nur mit Trigger-Fix, sieht dann aber relativ aufgeräumt aus. (Das riesige globale table gibts allerdings trotzdem noch, nur sieht man es nicht mehr direkt...)

Ich hab damals auch mit Java angefangen, jetzt schreib ich Mapscripte in Lua, Raytracer in C++ und Formelableitung in Prolog. Da kann also noch viel passieren

Messoras
#5
08.12.2016 08:44
Beiträge: 84

Zitat von mcb:

Zitat von Messoras:

Ich dachte ich mache einfach alles local und kann das beliebig oft aufrufen "


In normalem Lua wäre das auch kein Problem, dann hättest du die Funktionen auch noch lokal gemacht und es würde funktionieren...

In den Originalscripten von BB läuft alles auf ein riesiges globales table raus, mit einem index als Parameter beim Job. Nicht besonders schön, aber es funktioniert (irgendwie).
Was ich immer mache, ist die ganzen Variablen als Parameter dem Job zu übergeben. Das funktioniert zwar nur mit Trigger-Fix, sieht dann aber relativ aufgeräumt aus. (Das riesige globale table gibts allerdings trotzdem noch, nur sieht man es nicht mehr direkt...)

Ich hab damals auch mit Java angefangen, jetzt schreib ich Mapscripte in Lua, Raytracer in C++ und Formelableitung in Prolog. Da kann also noch viel passieren



Schau mal, ist das so besser?

Gruß,
Messoras

____________________
Six feet of earth make us all equal.

Spielt Siedler 5 online mit mir, dank des neuen Siedler 5 MP Projekts von Kimichura.

mcb
#6
08.12.2016 12:25
Beiträge: 1472

Ja, es sind zummindest wesentlich weniger upvalues

Was mir noch auffällt:
- du verwendest talkingNPC um den Job "abzuschalten" (talkingNPC st immer noch ein upvalue)
Eventuell wäre EndJob besser geeignet.

- Du entfernst den NPC mit DisableNPCMarker, was nur den Marker entfernt, nicht die Daten die CreateNPC angelengt hat.
Du solltest DestroyNPC(npcTable) verwenden (das NPCtable dann auch als Parameter verwenden) (_name ist immer noch ein upvalue)

- Wenn der NPC von einem Helden angesprochen wird (normal über den callback) kann er danach immer noch über den Trigger angesprochen werden.

Messoras
#7
08.12.2016 15:11
Beiträge: 84

Wie starte ich einen Job mit Parametern?

StartSimpleJob("Job",{Parameters})

funktioniert nicht!

Muss ich da irgendwas mit Trigger.RequestTrigger() machen?
Wenn ja, wie? "

Hab's bis jetzt mit

Trigger.RequestTrigger( Events.LOGIC_EVENT_EVERY_SECOND, "", "TriggerNPCJob" , 1, {_name, _trigger, _callback, npc})

versucht, aber ist das gleiche Problem.
-> Alle Parameter sind nil

Gruß,
Messoras

____________________
Six feet of earth make us all equal.

Spielt Siedler 5 online mit mir, dank des neuen Siedler 5 MP Projekts von Kimichura.

Dieser Beitrag wurde von Messoras am 08.12.2016 um 17:49 editiert.

mcb
#8
08.12.2016 18:42
Beiträge: 1472

Damit das funktioniert brauchst du einen Trigger-Fix wie den hier: http://www.siedler-maps.de/forum.php?action=showthread&threadid=19074 oder den von Chromix.
Dann einfach StartSimpleJob(func, param1, param2, ...)
Das das mit dem Trigger nicht funktioniert, liegt am falschen Aufruf:
Trigger.RequestTrigger(event, condition, action, active, condParams, actParams)
Du hast also die Parameterliste für condition und action getrennt.

Der Trigger-Fix sorgt auch dafür, das du Funktionen und tables als Parameter übergeben kannst. Ohne geht also definitiv nicht.

Messoras
#9
09.12.2016 00:02
Beiträge: 84

Also ich habe jetzt deinen Trigger-Fix mcb 1.1 eingefügt.
Mit der Implementierung, die ich im ersten Post aktualisiert habe, sind allerdings immer noch alle Parameter nil.
Muss ich den Trigger-Fix auch erst initialisieren?

#EDIT: Auch für den Aufruf

Trigger.RequestTrigger( Events.LOGIC_EVENT_EVERY_SECOND, "", "TriggerNPCJob", 1,nil,{_name,_trigger,_callback,npc} )

sind alle Parameter nil...

Gruß,
Messoras

____________________
Six feet of earth make us all equal.

Spielt Siedler 5 online mit mir, dank des neuen Siedler 5 MP Projekts von Kimichura.

Dieser Beitrag wurde von Messoras am 09.12.2016 um 00:09 editiert.

mcb
#10
09.12.2016 10:54
Beiträge: 1472

Die Initialisierung wird in der letzten Zeile aufgerufen: mcbTrigger.init()
Wenn du das ganze so kopiert hast, wie es in meimem Post steht, müsste alles funktionieren.
Hast du den Lua-Debugger? Wenn ja, starte mal

function triggerTest()
   LuaDebugger.Break()
end


als SimpleJob. Sobald der aufgerufen wird, landest du im Debugger. Send mir dann mal den call stack (Aufrufreihenfolge der Funktionen, zwischen der Scriptanzeige und der Konsolenausgabe) .

Messoras
#11
09.12.2016 14:44
Beiträge: 84

Der Lua Debugger funktioniert bei mir leider nicht, denn sobald er irgendeinen Fehler bekommt tabbt er mich aus dem Spiel raus und dann kriege ich nen Blackscreen, so dass ich das Spiel nur noch auf dem zweiten Monitor mit Taskmanager beenden kann... Das Problem habe ich immer, wenn ich aus Siedler raustabbe "

Außerdem verschiebt das Spiel alle Fenster und Icons auf dem zweiten Monitor immer nach rechts, so dass ich nur die linke Hälfte des Debuggers sehe, wenn ich ihn auf dem zweiten Monitor anzeige.
Ich muss das mal irgendwie reparieren xD

Gruß,
Messoras

____________________
Six feet of earth make us all equal.

Spielt Siedler 5 online mit mir, dank des neuen Siedler 5 MP Projekts von Kimichura.

mcb
#12
09.12.2016 16:44
Beiträge: 1472

Seltsam...
Dann halt ohne Debugger: Platzier mal Message(tostring(mcbTrigger_action or "nil" )) in der FMA.

Wenn dan sowas wie function: und ein HEX-Wert auftauchen, wurde init ausgeführt, wenn da nil steht funktioniert irgend was überhaupt nicht.

Settlerman
#13
09.12.2016 22:17
Beiträge: 238

http://thesettlers.tk/registry.html
Damit sollte das Raustabben kein Problem sein und du kannst den Debugger verwenden.

Messoras
#14
13.12.2016 00:05
Beiträge: 84

Zitat von mcb:
Die Initialisierung wird in der letzten Zeile aufgerufen: mcbTrigger.init()
Wenn du das ganze so kopiert hast, wie es in meimem Post steht, müsste alles funktionieren.
Hast du den Lua-Debugger? Wenn ja, starte mal

function triggerTest()
   LuaDebugger.Break()
end


als SimpleJob. Sobald der aufgerufen wird, landest du im Debugger. Send mir dann mal den call stack (Aufrufreihenfolge der Funktionen, zwischen der Scriptanzeige und der Konsolenausgabe) .



Warum wolltest du das als SimpleJob? Der springt da doch jetzt ständig wieder rein /o
Der Call Stack sieht jedenfalls normal aus:

Function                Source                Line
global triggerTest()    Map Script            139



Zitat von Settlerman:

http://thesettlers.tk/registry.html
Damit sollte das Raustabben kein Problem sein und du kannst den Debugger verwenden.


Danke für den Tipp! Klappt super.


Aber auch mit funktionierendem Debugger bekomme ich keine Fehlermeldung beim SimpleJob Aufruf mit Parametern. Es sind nur einfach sämtliche Parameter nil.

LG,
Messoras

____________________
Six feet of earth make us all equal.

Spielt Siedler 5 online mit mir, dank des neuen Siedler 5 MP Projekts von Kimichura.

Dieser Beitrag wurde von Messoras am 13.12.2016 um 00:15 editiert.

mcb
#15
13.12.2016 00:21
Beiträge: 1472

Der call stack verrät mir, welche Funktion aus C heraus aufgerufen wurde. Bei dir ist es triggerTest.
Bei mir sieht der stack so aus:

global triggerTest()
global mcbTrigger_action()


Das heißt, mcbTrigger_action wird aus C heraus aufgerufen, und die ruft triggerTest auf.
Ich weiß jetzt also, das anscheinend die init funktion nicht richtig ausgeführt wurde, da der Trigger keine dazwischengeschaltete mcbTrigger_action hat.
Tipp mal unten im Debugger mcbTrigger_action ein und drück auf Enter, es sollte eine Ausgabe wie <Function, defined in D:\wksp\TestNeu\src\triggercorot.lua:412> auftauchen (zummindest, wenn alles funktioniert)

Messoras
#16
13.12.2016 00:45
Beiträge: 84

mcb_Trigger_action

im Debugger gibt nil aus.

Mit dem Aufruf

Trigger.RequestTrigger( Events.LOGIC_EVENT_EVERY_SECOND,"","TriggerNPCJob",1,{},{_name,_trigger,_callback,npc} )

statt

StartSimpleJob("TriggerNPCJob",_name,_trigger,_callback,npc)

bleiben die Parameter jedoch auch nil...

Gruß,
Messoras

____________________
Six feet of earth make us all equal.

Spielt Siedler 5 online mit mir, dank des neuen Siedler 5 MP Projekts von Kimichura.

mcb
#17
13.12.2016 10:23
Beiträge: 1472

Anscheinend wird init überhaupt nicht ausgeführt. Kannst du mal nachsehen, ob du die letzte Zeile vom Trigger-Fix kopiert hast? Ansonsten kann ich mir nur noch vorstellen, das Siedler vorher wegen einem Fehler abbricht... Dann müsste ich mal das komplette Script sehen, um den Fehler zu finden.

Messoras
#18
13.12.2016 11:05
Beiträge: 84

Ah tut mir leid dich unnötig genervt zu haben. Eine der anderen Comfort Funktionen versucht nämlich auf eine Methode zuzugreifen, die nicht existiert. EntityFind meine ich, sucht nach einer Methode "IstDrin", wodurch der TriggerFix nicht aufgerufen wird.
Habe das ganze mal in einer neuen Testmap nur mit den beiden Comforts ausprobiert und es klappt super.

Ich frage mich allerdings immernoch, warum der RequestTrigger Aufruf nicht mit den Parametern funktioniert, der sollte doch auch ohne den TriggerFix Parameter übernehmen können, oder nicht?

Gruß,
Messoras

____________________
Six feet of earth make us all equal.

Spielt Siedler 5 online mit mir, dank des neuen Siedler 5 MP Projekts von Kimichura.

mcb
#19
13.12.2016 14:05
Beiträge: 1472

Genau sowas habe ich befürchetet und nach dem kompletten Script gefragt. Zum Glück hast du es ja noch selber gefunden.

Der normale Trigger.RequestTrigger kann keine Funktionen und tables als callbacks übergeben. Wahrscheinlich werden dann einfach alle Argumente ignoriert.

Messoras
#20
13.12.2016 19:37
Beiträge: 84

Soo jetzt ist eine einfache Haus-verlassen Funktion drin, die nichts weiter benötigt und fehlerfrei funktioniert.

Gruß,
Messoras

____________________
Six feet of earth make us all equal.

Spielt Siedler 5 online mit mir, dank des neuen Siedler 5 MP Projekts von Kimichura.

Seiten: 1

SiteEngine v1.5.0 by nevermind, ©2005-2007
Design by SpiderFive (www.siedler-games.de) - English translation by juja

Impressum