Skip to content

6.11. fejezet: Mission Hookok

Főoldal | << Előző: Központi gazdaság | Mission Hookok | Következő: Akció rendszer >>


Bevezetés

Minden DayZ modnak szüksége van egy belépési pontra --- egy helyre, ahol inicializálja a menedzsereket, regisztrálja az RPC kezelőket, becsatlakozik a játékos-csatlakozási eseményekbe, és leállításkor mindent eltakarít. Ez a belépési pont a Mission osztály. A motor pontosan egy Mission példányt hoz létre, amikor egy szcenárió betöltődik: MissionServer-t dedikált szerveren, MissionGameplay-t kliens oldalon, vagy mindkettőt listen szerveren. Ezek az osztályok garantált sorrendben aktiválódó életciklus-hookokat biztosítanak, így a modoknak megbízható helyet adnak a viselkedés beillesztéséhez.

Ez a fejezet a teljes Mission osztályhierarchiát, minden hookelhető metódust, a helyes modded class kiterjesztési mintát, valamint valós példákat mutat be a vanilla DayZ-ből, a COT-ból és az Expansionből.


Osztályhierarchia

Mission                      // 3_Game/gameplay.c (alap, definiálja az összes hook szignatúrát)
└── MissionBaseWorld         // 4_World/classes/missionbaseworld.c (minimális híd)
    └── MissionBase          // 5_Mission/mission/missionbase.c (közös beállítás: HUD, menük, pluginek)
        ├── MissionServer    // 5_Mission/mission/missionserver.c (szerver oldali)
        └── MissionGameplay  // 5_Mission/mission/missiongameplay.c (kliens oldali)
  • A Mission definiálja az összes hook szignatúrát üres metódusokként: OnInit(), OnUpdate(), OnEvent(), OnMissionStart(), OnMissionFinish(), OnKeyPress(), OnKeyRelease(), stb.
  • A MissionBase inicializálja a plugin menedzsert, a widget eseménykezelőt, a világ adatokat, a dinamikus zenét, a hang készleteket és a beviteli eszköz követést. Ez a közös szülője mind a szerver, mind a kliens oldalnak.
  • A MissionServer kezeli a játékos csatlakozásokat, lekapcsolódásokat, újraszületéseket, holttestek kezelését, tick ütemezést és a tüzérséget.
  • A MissionGameplay kezeli a HUD létrehozást, a csevegést, az akció menüket, a voice-over-network felületet, a felszerelés kezelőt, a bemenet kizárást és a kliens oldali játékos ütemezést.

Életciklus áttekintés

MissionServer életciklus (szerver oldal)

MissionGameplay életciklus (kliens oldal)


Mission alaposztály metódusai

Fájl: 3_Game/gameplay.c

A Mission alaposztály definiálja az összes hookelhető metódust. Mindegyik virtuális, üres alapértelmezett implementációval, hacsak nincs másként jelölve.

Életciklus hookok

MetódusSzignatúraMikor aktiválódik
OnInitvoid OnInit()A konstruktor után, a misszió indulása előtt. Elsődleges beállítási pont.
OnMissionStartvoid OnMissionStart()Az OnInit után. A misszió világ aktív.
OnMissionLoadedvoid OnMissionLoaded()Az OnMissionStart után. Minden vanilla rendszer inicializálva van.
OnGameplayDataHandlerLoadvoid OnGameplayDataHandlerLoad()Szerver: a gameplay adatok (cfggameplay.json) betöltése után.
OnUpdatevoid OnUpdate(float timeslice)Minden képkockánál. A timeslice az előző képkocka óta eltelt idő másodpercben (jellemzően 0.016-0.033).
OnMissionFinishvoid OnMissionFinish()Leállításkor vagy lekapcsolódáskor. Mindent itt kell eltakarítani.

Beviteli hookok (kliens oldal)

MetódusSzignatúraMikor aktiválódik
OnKeyPressvoid OnKeyPress(int key)Fizikai billentyű lenyomva. A key egy KeyCode konstans.
OnKeyReleasevoid OnKeyRelease(int key)Fizikai billentyű elengedve.
OnMouseButtonPressvoid OnMouseButtonPress(int button)Egérgomb lenyomva.
OnMouseButtonReleasevoid OnMouseButtonRelease(int button)Egérgomb elengedve.

Esemény hook

MetódusSzignatúraMikor aktiválódik
OnEventvoid OnEvent(EventType eventTypeId, Param params)Motor események: csevegés, VON, játékos csatlakozás/lekapcsolódás, ablak átméretezés, stb.

Segéd metódusok

MetódusSzignatúraLeírás
GetHudHud GetHud()Visszaadja a HUD példányt (csak kliens).
GetWorldDataWorldData GetWorldData()Visszaadja a világ-specifikus adatokat (hőmérsékleti görbék, stb.).
IsPausedbool IsPaused()Szünetel-e a játék (egyjátékos / listen szerver).
IsServerbool IsServer()true MissionServer esetén, false MissionGameplay esetén.
IsMissionGameplaybool IsMissionGameplay()true MissionGameplay esetén, false MissionServer esetén.
PlayerControlEnablevoid PlayerControlEnable(bool bForceSuppress)Játékos bemenet újra engedélyezése letiltás után.
PlayerControlDisablevoid PlayerControlDisable(int mode)Játékos bemenet letiltása (pl. INPUT_EXCLUDE_ALL).
IsControlDisabledbool IsControlDisabled()Le van-e tiltva jelenleg a játékos vezérlés.
GetControlDisabledModeint GetControlDisabledMode()Visszaadja az aktuális bemenet kizárási módot.

MissionServer hookok (szerver oldal)

Fájl: 5_Mission/mission/missionserver.c

A MissionServer-t a motor hozza létre a dedikált szervereken. Mindent kezel, ami a szerver oldali játékos életciklussal kapcsolatos.

Alapvető vanilla viselkedés

  • Konstruktor: Beállítja a CallQueue-t a játékos statisztikákhoz (30 másodperces intervallum), a halott játékosok tömbjét, a kijelentkezés-követő map-eket és az eső kezelőt.
  • OnInit: Betölti a CfgGameplayHandler-t, PlayerSpawnHandler-t, CfgPlayerRestrictedAreaHandler-t, UndergroundAreaLoader-t és a tüzérségi tüzelési pozíciókat.
  • OnMissionStart: Létrehozza a hatás terület zónákat (szennyezett zónák, stb.).
  • OnUpdate: Futtatja a tick ütemezőt, feldolgozza a kijelentkezési időzítőket, frissíti az alap környezeti hőmérsékletet, az eső kezelőt és a véletlenszerű tüzérséget.

OnEvent --- Játékos csatlakozási események

A szerver OnEvent metódusa a központi diszpécser minden játékos életciklus eseményhez. A motor típusos Param objektumokkal küld eseményeket. A vanilla egy switch blokkal kezeli őket:

EseményParam típusMi történik
ClientPrepareEventTypeIDClientPrepareEventParamsEldönti: adatbázisból vagy friss karakter
ClientNewEventTypeIDClientNewEventParamsLétrehozza és felszereli az új karaktert, meghívja az InvokeOnConnect-et
ClientReadyEventTypeIDClientReadyEventParamsMeglévő karakter betöltve, meghívja az OnClientReadyEvent-et + InvokeOnConnect-et
ClientRespawnEventTypeIDClientRespawnEventParamsJátékos újraszületési kérelem, megöli a régi karaktert, ha eszméletlen
ClientReconnectEventTypeIDClientReconnectEventParamsJátékos újracsatlakozott az élő karakteréhez
ClientDisconnectedEventTypeIDClientDisconnectedEventParamsJátékos lekapcsolódik, elindítja a kijelentkezési időzítőt
LogoutCancelEventTypeIDLogoutCancelEventParamsJátékos megszakította a kijelentkezési visszaszámlálást

Játékos csatlakozási metódusok

Az OnEvent-ből hívódnak meg, amikor játékos-kapcsolódási események aktiválódnak:

MetódusSzignatúraVanilla viselkedés
InvokeOnConnectvoid InvokeOnConnect(PlayerBase player, PlayerIdentity identity)Meghívja a player.OnConnect() metódust. Az elsődleges "játékos csatlakozott" hook.
InvokeOnDisconnectvoid InvokeOnDisconnect(PlayerBase player)Meghívja a player.OnDisconnect() metódust. A játékos teljesen lekapcsolódott.
OnClientReadyEventvoid OnClientReadyEvent(PlayerIdentity identity, PlayerBase player)Meghívja a g_Game.SelectPlayer() metódust. Meglévő karakter betöltve az adatbázisból.
OnClientNewEventPlayerBase OnClientNewEvent(PlayerIdentity identity, vector pos, ParamsReadContext ctx)Létrehozza és felszereli az új karaktert. PlayerBase-t ad vissza.
OnClientRespawnEventvoid OnClientRespawnEvent(PlayerIdentity identity, PlayerBase player)Megöli a régi karaktert, ha eszméletlen/megkötözött.
OnClientReconnectEventvoid OnClientReconnectEvent(PlayerIdentity identity, PlayerBase player)Meghívja a player.OnReconnect() metódust.
PlayerDisconnectedvoid PlayerDisconnected(PlayerBase player, PlayerIdentity identity, string uid)Meghívja az InvokeOnDisconnect-et, elmenti a játékost, kilép a hive-ból, kezeli a testet, eltávolítja a szerverről.

Karakter beállítás

MetódusSzignatúraLeírás
CreateCharacterPlayerBase CreateCharacter(PlayerIdentity identity, vector pos, ParamsReadContext ctx, string characterName)Létrehozza a játékos entitást a g_Game.CreatePlayer() + g_Game.SelectPlayer() segítségével.
EquipCharactervoid EquipCharacter(MenuDefaultCharacterData char_data)Végigmegy az attachment slotokon, véletlenszerűsít, ha az egyéni újraszületés le van tiltva. Meghívja a StartingEquipSetup() metódust.
StartingEquipSetupvoid StartingEquipSetup(PlayerBase player, bool clothesChosen)Üres a vanillában --- ez a te belépési pontod a kezdő készletekhez.

MissionGameplay hookok (kliens oldal)

Fájl: 5_Mission/mission/missiongameplay.c

A MissionGameplay a kliensen jön létre, amikor egy szerverhez csatlakozik vagy egyjátékos módot indít. Minden kliens oldali felületet és bemenetet kezel.

Alapvető vanilla viselkedés

  • Konstruktor: Megsemmisíti a meglévő menüket, létrehozza a Chat-et, ActionMenu-t, IngameHud-ot, VoN állapotot, halványítási időzítőket és a SyncEvents regisztrációt.
  • OnInit: Dupla inicializálás elleni védelem az m_Initialized változóval. Létrehozza a HUD gyökér widgetet a "gui/layouts/day_z_hud.layout" fájlból, a csevegés widgetet, az akció menüt, a mikrofon ikont, a VoN hangerő szint widgeteket és a csevegés csatorna területet. Meghívja a PPEffects.Init() és MapMarkerTypes.Init() metódusokat.
  • OnMissionStart: Elrejti a kurzort, beállítja a misszió állapotát MISSION_STATE_GAME-re, betölti a hatás területeket egyjátékos módban.
  • OnUpdate: Tick ütemező a helyi játékoshoz, hologram frissítések, radiális gyorselérés (konzol), gesztus menü, bemenetkezelés a felszereléshez/csevegéshez/VoN-hoz, debug monitor, szüneteltetés viselkedése.
  • OnMissionFinish: Elrejti a párbeszédablakot, megsemmisíti az összes menüt és csevegést, törli a HUD gyökér widgetet, leállítja az összes PPE effektet, újra engedélyezi az összes bemenetet, beállítja a misszió állapotát MISSION_STATE_FINNISH-re.

Beviteli hookok

c
override void OnKeyPress(int key)
{
    super.OnKeyPress(key);
    // A vanilla továbbítja a Hud.KeyPress(key) metódusnak
    // a key értékek KeyCode konstansok (pl. KeyCode.KC_F1 = 59)
}

override void OnKeyRelease(int key)
{
    super.OnKeyRelease(key);
}

Esemény hook

A vanilla MissionGameplay.OnEvent() kezeli a ChatMessageEventTypeID-t (hozzáadja a csevegés widgethez), ChatChannelEventTypeID-t (frissíti a csatorna jelzőt), WindowsResizeEventTypeID-t (újraépíti a menüket/HUD-ot), SetFreeCameraEventTypeID-t (debug kamera) és VONStateEventTypeID-t (hang állapot). Felülírásnál ugyanazt a switch mintát használd, és mindig hívd meg a super.OnEvent() metódust.

Beviteli vezérlés

A PlayerControlDisable(int mode) aktivál egy bemenet kizárási csoportot (pl. INPUT_EXCLUDE_ALL, INPUT_EXCLUDE_INVENTORY). A PlayerControlEnable(bool bForceSuppress) eltávolítja azt. Ezek a specific.xml-ben definiált kizárási csoportokra térképeződnek. Írd felül, ha a modod egyéni bemenet kizárási viselkedést igényel (ahogy az Expansion teszi a menüinél).


Szerver oldali eseményfolyam: Játékos csatlakozik

A pontos eseménysorrend ismerete, amikor egy játékos csatlakozik, kritikus ahhoz, hogy tudd, hova kell a kódodat hookol ni.

Új karakter (első csatlakozás vagy halál után)

Meglévő karakter (újracsatlakozás lekapcsolódás után)

Játékos lekapcsolódás


Hookolás módja: A modded class minta

A Mission osztályok kiterjesztésének helyes módja a modded class minta. Ez az Enforce Script osztályöröklődési mechanizmusát használja, ahol a modded class kiterjeszti a meglévő osztályt anélkül, hogy lecserélné, így több mod is együtt tud élni.

Alapvető szerver hook

c
// A te modod: Scripts/5_Mission/YourMod/MissionServer.c
modded class MissionServer
{
    ref MyServerManager m_MyManager;

    override void OnInit()
    {
        super.OnInit();  // MINDIG hívd meg először a super-t

        m_MyManager = new MyServerManager();
        m_MyManager.Init();
        Print("[MyMod] Server manager initialized");
    }

    override void OnMissionFinish()
    {
        if (m_MyManager)
        {
            m_MyManager.Cleanup();
            m_MyManager = null;
        }

        super.OnMissionFinish();  // Hívd meg a super-t (a takarítás előtt vagy után)
    }
}

Alapvető kliens hook

c
// A te modod: Scripts/5_Mission/YourMod/MissionGameplay.c
modded class MissionGameplay
{
    ref MyHudWidget m_MyHud;

    override void OnInit()
    {
        super.OnInit();  // MINDIG hívd meg először a super-t

        // Egyéni HUD elemek létrehozása
        m_MyHud = new MyHudWidget();
        m_MyHud.Init();
    }

    override void OnUpdate(float timeslice)
    {
        super.OnUpdate(timeslice);

        // Egyéni HUD frissítése minden képkockánál
        if (m_MyHud)
        {
            m_MyHud.Update(timeslice);
        }
    }

    override void OnMissionFinish()
    {
        if (m_MyHud)
        {
            m_MyHud.Destroy();
            m_MyHud = null;
        }

        super.OnMissionFinish();
    }
}

Játékos csatlakozás hookolása

c
modded class MissionServer
{
    override void InvokeOnConnect(PlayerBase player, PlayerIdentity identity)
    {
        super.InvokeOnConnect(player, identity);

        // A kódod a vanilla és az összes korábbi mod UTÁN fut le
        if (player && identity)
        {
            string uid = identity.GetId();
            string name = identity.GetName();
            Print("[MyMod] Player connected: " + name + " (" + uid + ")");

            // Játékos adat betöltése, beállítások küldése, stb.
            MyPlayerData.Load(uid);
        }
    }

    override void InvokeOnDisconnect(PlayerBase player)
    {
        // Mentsd az adatokat a super ELŐTT (a játékos utána törlődhet)
        if (player && player.GetIdentity())
        {
            string uid = player.GetIdentity().GetId();
            MyPlayerData.Save(uid);
        }

        super.InvokeOnDisconnect(player);
    }
}

Csevegés üzenetek hookolása (szerver oldali OnEvent)

c
modded class MissionServer
{
    override void OnEvent(EventType eventTypeId, Param params)
    {
        // Elfogás a super ELŐTT az események esetleges blokkolásához
        if (eventTypeId == ClientNewEventTypeID)
        {
            ClientNewEventParams newParams;
            Class.CastTo(newParams, params);
            PlayerIdentity identity = newParams.param1;

            if (IsPlayerBanned(identity))
            {
                // A csatlakozás blokkolása a super meghívásának kihagyásával
                return;
            }
        }

        super.OnEvent(eventTypeId, params);
    }
}

Billentyűzet bemenet hookolása (kliens oldal)

c
modded class MissionGameplay
{
    override void OnKeyPress(int key)
    {
        super.OnKeyPress(key);

        // Egyéni menü megnyitása F6-ra
        if (key == KeyCode.KC_F6)
        {
            if (!GetGame().GetUIManager().GetMenu())
            {
                MyCustomMenu.Open();
            }
        }
    }
}

Hol regisztráld az RPC kezelőket

Az RPC kezelőket az OnInit-ben kell regisztrálni, nem a konstruktorban. Az OnInit idejére már minden script modul betöltődött és a hálózati réteg kész.

c
modded class MissionServer
{
    override void OnInit()
    {
        super.OnInit();

        // RPC kezelők regisztrálása itt
        GetDayZGame().Event_OnRPC.Insert(OnMyRPC);
    }

    override void OnMissionFinish()
    {
        GetDayZGame().Event_OnRPC.Remove(OnMyRPC);
        super.OnMissionFinish();
    }

    void OnMyRPC(PlayerIdentity sender, Object target, int rpc_type,
                 ParamsReadContext ctx)
    {
        // Az RPC-id kezelése
    }
}

Gyakori hookok cél szerint

Ezt szeretném...Ezt a metódust hookoldMelyik osztályon
Mod inicializálása szerverenOnInit()MissionServer
Mod inicializálása kliensenOnInit()MissionGameplay
Kód futtatása minden képkockánál (szerver)OnUpdate(float timeslice)MissionServer
Kód futtatása minden képkockánál (kliens)OnUpdate(float timeslice)MissionGameplay
Reagálás játékos csatlakozásraInvokeOnConnect(player, identity)MissionServer
Reagálás játékos távozásraInvokeOnDisconnect(player)MissionServer
Kezdeti adatok küldése új kliensnekOnClientReadyEvent(identity, player)MissionServer
Reagálás új karakter születéséreOnClientNewEvent(identity, pos, ctx)MissionServer
Kezdő felszerelés adásaStartingEquipSetup(player, clothesChosen)MissionServer
Reagálás játékos újraszületésreOnClientRespawnEvent(identity, player)MissionServer
Reagálás játékos újracsatlakozásraOnClientReconnectEvent(identity, player)MissionServer
Lekapcsolódás/kijelentkezés logika kezeléseOnClientDisconnectedEvent(identity, player, logoutTime, authFailed)MissionServer
Szerver események elfogása (csatlakozás, csevegés)OnEvent(eventTypeId, params)MissionServer
Kliens események elfogása (csevegés, VON)OnEvent(eventTypeId, params)MissionGameplay
Billentyűzet bemenet kezeléseOnKeyPress(key) / OnKeyRelease(key)MissionGameplay
HUD elemek létrehozásaOnInit()MissionGameplay
Takarítás szerver leállításkorOnMissionFinish()MissionServer
Takarítás kliens lekapcsolódáskorOnMissionFinish()MissionGameplay
Kód futtatása egyszer, miután minden rendszer betöltődöttOnMissionLoaded()Bármelyik
Játékos bemenet letiltása/engedélyezésePlayerControlDisable(mode) / PlayerControlEnable(bForceSuppress)MissionGameplay

Szerver vs kliens: Melyik hookok aktiválódnak hol

HookSzerverKliensMegjegyzések
KonstruktorIgenIgenKülönböző osztály mindkét oldalon
OnInit()IgenIgen
OnMissionStart()IgenIgen
OnMissionLoaded()IgenIgen
OnGameplayDataHandlerLoad()IgenNemcfggameplay.json betöltve
OnUpdate(timeslice)IgenIgenMindkettő a saját képkocka ciklusát futtatja
OnMissionFinish()IgenIgen
OnEvent()IgenIgenKülönböző eseménytípusok mindkét oldalon
InvokeOnConnect()IgenNemCsak szerver
InvokeOnDisconnect()IgenNemCsak szerver
OnClientReadyEvent()IgenNemCsak szerver
OnClientNewEvent()IgenNemCsak szerver
OnClientRespawnEvent()IgenNemCsak szerver
OnClientReconnectEvent()IgenNemCsak szerver
OnClientDisconnectedEvent()IgenNemCsak szerver
PlayerDisconnected()IgenNemCsak szerver
StartingEquipSetup()IgenNemCsak szerver
EquipCharacter()IgenNemCsak szerver
OnKeyPress()NemIgenCsak kliens
OnKeyRelease()NemIgenCsak kliens
OnMouseButtonPress()NemIgenCsak kliens
OnMouseButtonRelease()NemIgenCsak kliens
PlayerControlDisable()NemIgenCsak kliens
PlayerControlEnable()NemIgenCsak kliens

EventType konstansok referencia

Minden esemény konstans a 3_Game/gameplay.c-ben van definiálva, és az OnEvent()-en keresztül kerül diszpécselésre.

KonstansOldalLeírás
ClientPrepareEventTypeIDSzerverJátékos identitás megérkezett, döntés adatbázisból vagy friss karakter
ClientNewEventTypeIDSzerverÚj karakter létrehozása folyamatban
ClientReadyEventTypeIDSzerverMeglévő karakter betöltve az adatbázisból
ClientRespawnEventTypeIDSzerverJátékos újraszületést kért
ClientReconnectEventTypeIDSzerverJátékos újracsatlakozott az élő karakteréhez
ClientDisconnectedEventTypeIDSzerverJátékos lekapcsolódik
LogoutCancelEventTypeIDSzerverJátékos megszakította a kijelentkezési visszaszámlálást
ChatMessageEventTypeIDKliensCsevegés üzenet érkezett (ChatMessageEventParams)
ChatChannelEventTypeIDKliensCsevegés csatorna megváltozott (ChatChannelEventParams)
VONStateEventTypeIDKliensHang-hálózat állapot megváltozott
VONStartSpeakingEventTypeIDKliensA játékos elkezdett beszélni
VONStopSpeakingEventTypeIDKliensA játékos abbahagyta a beszédet
MPSessionStartEventTypeIDMindkettőTöbbjátékos munkamenet elindult
MPSessionEndEventTypeIDMindkettőTöbbjátékos munkamenet véget ért
MPConnectionLostEventTypeIDKliensKapcsolat megszakadt a szerverrel
PlayerDeathEventTypeIDMindkettőA játékos meghalt
SetFreeCameraEventTypeIDKliensSzabad kamera átkapcsolva (debug)

Valós példák

1. példa: Szerver menedzser inicializálás

Egy tipikus minta egy szerver oldali menedzser inicializálásához, amelynek periodikus feladatokat kell futtatnia.

c
modded class MissionServer
{
    ref MyTraderManager m_TraderManager;
    float m_TraderUpdateTimer;
    const float TRADER_UPDATE_INTERVAL = 5.0; // másodperc

    override void OnInit()
    {
        super.OnInit();

        m_TraderManager = new MyTraderManager();
        m_TraderManager.LoadConfig();
        m_TraderManager.SpawnTraders();
        m_TraderUpdateTimer = 0;

        Print("[MyMod] Trader manager initialized");
    }

    override void OnUpdate(float timeslice)
    {
        super.OnUpdate(timeslice);

        // A kereskedő frissítés korlátozása 5 másodpercenként
        m_TraderUpdateTimer += timeslice;
        if (m_TraderUpdateTimer >= TRADER_UPDATE_INTERVAL)
        {
            m_TraderUpdateTimer = 0;
            m_TraderManager.Update();
        }
    }

    override void OnMissionFinish()
    {
        if (m_TraderManager)
        {
            m_TraderManager.SaveState();
            m_TraderManager.DespawnTraders();
            m_TraderManager = null;
        }

        super.OnMissionFinish();
    }
}

2. példa: Játékos adat betöltés csatlakozáskor

c
modded class MissionServer
{
    override void InvokeOnConnect(PlayerBase player, PlayerIdentity identity)
    {
        super.InvokeOnConnect(player, identity);
        if (!player || !identity)
            return;

        string uid = identity.GetId();
        string path = "$profile:MyMod/Players/" + uid + ".json";
        ref MyPlayerStats stats = new MyPlayerStats();

        if (FileExist(path))
            JsonFileLoader<MyPlayerStats>.JsonLoadFile(path, stats);
        else
            stats.SetDefaults();

        player.m_MyStats = stats;

        // Kezdeti adatok küldése a kliensnek
        ScriptRPC rpc = new ScriptRPC();
        rpc.Write(stats.GetKills());
        rpc.Write(stats.GetDeaths());
        rpc.Send(player, MY_RPC_SYNC_STATS, true, identity);
    }

    override void InvokeOnDisconnect(PlayerBase player)
    {
        if (player && player.GetIdentity() && player.m_MyStats)
        {
            string path = "$profile:MyMod/Players/" + player.GetIdentity().GetId() + ".json";
            JsonFileLoader<MyPlayerStats>.JsonSaveFile(path, player.m_MyStats);
        }
        super.InvokeOnDisconnect(player);
    }
}

3. példa: Kliens HUD létrehozás

Egyéni HUD elem létrehozása, amely minden képkockánál frissül.

c
modded class MissionGameplay
{
    ref Widget m_MyHudRoot;
    ref TextWidget m_MyStatusText;
    float m_HudUpdateTimer;

    override void OnInit()
    {
        super.OnInit();

        // HUD létrehozása layout fájlból
        m_MyHudRoot = GetGame().GetWorkspace().CreateWidgets(
            "MyMod/gui/layouts/my_hud.layout"
        );

        if (m_MyHudRoot)
        {
            m_MyStatusText = TextWidget.Cast(
                m_MyHudRoot.FindAnyWidget("StatusText")
            );
            m_MyHudRoot.Show(true);
        }

        m_HudUpdateTimer = 0;
    }

    override void OnUpdate(float timeslice)
    {
        super.OnUpdate(timeslice);

        // HUD szöveg frissítése másodpercenként kétszer, nem minden képkockánál
        m_HudUpdateTimer += timeslice;
        if (m_HudUpdateTimer >= 0.5)
        {
            m_HudUpdateTimer = 0;
            UpdateMyHud();
        }
    }

    void UpdateMyHud()
    {
        PlayerBase player = PlayerBase.Cast(GetGame().GetPlayer());
        if (!player || !m_MyStatusText)
            return;

        string status = "Health: " + player.GetHealth("", "").ToString();
        m_MyStatusText.SetText(status);
    }

    override void OnMissionFinish()
    {
        if (m_MyHudRoot)
        {
            m_MyHudRoot.Unlink();
            m_MyHudRoot = null;
        }

        super.OnMissionFinish();
    }
}

4. példa: Csevegés parancs elfogás (szerver oldal)

Játékos csatlakozások elfogása egy kitiltási rendszer megvalósításához. Ezt a mintát a COT használja.

c
modded class MissionServer
{
    override void OnEvent(EventType eventTypeId, Param params)
    {
        // Kitiltás ellenőrzése a super ELŐTT a csatlakozás feldolgozásánál
        if (eventTypeId == ClientNewEventTypeID)
        {
            ClientNewEventParams newParams;
            Class.CastTo(newParams, params);
            PlayerIdentity identity = newParams.param1;

            if (identity && IsBanned(identity.GetId()))
            {
                Print("[MyMod] Blocked banned player: " + identity.GetId());
                // Ne hívd meg a super-t --- a csatlakozás blokkolva
                return;
            }
        }

        super.OnEvent(eventTypeId, params);
    }

    bool IsBanned(string uid)
    {
        string path = "$profile:MyMod/Bans/" + uid + ".json";
        return FileExist(path);
    }
}

5. példa: Kezdő készlet a StartingEquipSetup-pal

A legtisztább módja az új játékosok felszerelésének az OnClientNewEvent érintése nélkül.

c
modded class MissionServer
{
    override void StartingEquipSetup(PlayerBase player, bool clothesChosen)
    {
        super.StartingEquipSetup(player, clothesChosen);

        if (!player)
            return;

        // Minden új karakternek kést és kötszert adunk
        EntityAI knife = player.GetInventory().CreateInInventory("KitchenKnife");
        EntityAI bandage = player.GetInventory().CreateInInventory("BandageDressing");

        // Ételt adunk a hátizsákjába (ha van neki)
        EntityAI backpack = player.FindAttachmentBySlotName("Back");
        if (backpack)
        {
            backpack.GetInventory().CreateInInventory("SardinesCan");
            backpack.GetInventory().CreateInInventory("Canteen");
        }
    }
}

Minta: Delegálás központi menedzserhez

Mind a COT, mind az Expansion ugyanazt a mintát követi: a mission hookjaik vékony csomagolók, amelyek egy singleton menedzserhez delegálnak. A COT létrehozza a g_cotBase = new CommunityOnlineTools példányt a konstruktorban, majd a megfelelő hookokból hívja a g_cotBase.OnStart() / OnUpdate() / OnFinish() metódusokat. Az Expansion ugyanezt teszi a GetDayZExpansion().OnStart() / OnLoaded() / OnFinish() metódusokkal. A te modod is kövesse ezt a mintát --- tartsd vékonynak a mission hook kódot, és a logikát tedd dedikált menedzser osztályokba.


OnInit vs OnMissionStart vs OnMissionLoaded

HookMikorMire használd
OnInit()Először. A script modulok betöltődtek, a világ még nem aktív.Menedzserek létrehozása, RPC-k regisztrálása, konfigurációk betöltése.
OnMissionStart()Másodszor. A világ aktív, entitások spawnolhatók.Entitások spawnolása, gameplay rendszerek indítása, triggerek létrehozása.
OnMissionLoaded()Harmadszor. Minden vanilla rendszer teljesen inicializálva.Modok közötti lekérdezések, véglegesítés, ami attól függ, hogy minden kész legyen.

Mindig hívd meg a super-t mind a háromnál. Használd az OnInit-et elsődleges inicializálási pontként. Az OnMissionLoaded-ot csak akkor használd, ha garantálnod kell, hogy más modok már inicializálódtak.


Az aktuális misszió elérése

c
Mission mission = GetGame().GetMission();                                    // Alaposztály
MissionServer serverMission = MissionServer.Cast(GetGame().GetMission());   // Szerver cast
MissionGameplay clientMission = MissionGameplay.Cast(GetGame().GetMission()); // Kliens cast
PlayerBase player = PlayerBase.Cast(GetGame().GetPlayer());                  // CSAK KLIENS (null a szerveren)

Gyakori hibák

1. A super.OnInit() elfelejtése

Minden override-nak kötelezően meg kell hívnia a super-t. Ennek elfelejtése elrontja a vanillát és minden más modot a láncban. Ez az egyetlen leggyakoribb modolási hiba.

c
// HELYTELEN                                 // HELYES
override void OnInit()                      override void OnInit()
{                                           {
    m_MyManager = new MyManager();              super.OnInit();  // Mindig először!
}                                               m_MyManager = new MyManager();
                                            }

2. A GetGame().GetPlayer() használata a szerveren

A GetGame().GetPlayer() mindig null dedikált szerveren. Nincs "helyi" játékos. Használd a GetGame().GetPlayers(array) metódust az összes csatlakozott játékos iterálásához.

c
// HELYES módja a játékosok iterálásának a szerveren
array<Man> players = new array<Man>();
GetGame().GetPlayers(players);
foreach (Man man : players)
{
    PlayerBase player = PlayerBase.Cast(man);
    if (player) { /* feldolgozás */ }
}

3. Takarítás hiánya az OnMissionFinish-ben

Mindig takaríts el widgeteket, callbackeket és referenciákat az OnMissionFinish()-ben. Takarítás nélkül a widgetek átszivárognak a következő misszió betöltésébe (kliens), és az elavult referenciák megmaradnak a szerver újraindítások között.

c
override void OnMissionFinish()
{
    if (m_MyWidget) { m_MyWidget.Unlink(); m_MyWidget = null; }
    super.OnMissionFinish();
}

4. OnUpdate képkocka-korlátozás nélkül

Az OnUpdate minden képkockánál aktiválódik (15-60+ FPS). Használj időzítő akkumulátort minden nem triviális munkához.

c
m_Timer += timeslice;
if (m_Timer >= 10.0)  // 10 másodpercenként
{
    m_Timer = 0;
    DoExpensiveWork();
}

5. RPC-k regisztrálása a konstruktorban

A konstruktor az összes script modul betöltése előtt fut. Regisztráld a callbackeket az OnInit()-ben (a legkorábbi biztonságos pont) és távolítsd el őket az OnMissionFinish()-ben.

6. Identity elérése lekapcsolódó játékosnál

A player.GetIdentity() null-t adhat vissza lekapcsolódás közben. Mindig ellenőrizd null-ra mind a player-t, mind az identity-t hozzáférés előtt.

c
override void InvokeOnDisconnect(PlayerBase player)
{
    if (player)
    {
        PlayerIdentity identity = player.GetIdentity();
        if (identity)
            Print("[MyMod] Disconnected: " + identity.GetId());
    }
    super.InvokeOnDisconnect(player);
}

Összefoglalás

FogalomLényeg
Mission hierarchiaMission > MissionBaseWorld > MissionBase > MissionServer / MissionGameplay
Szerver osztályMissionServer --- kezeli a játékos csatlakozásokat, spawnokat, tick ütemezést
Kliens osztályMissionGameplay --- kezeli a HUD-ot, bemenetet, csevegést, menüket
Életciklus sorrendKonstruktor > OnInit() > OnMissionStart() > OnMissionLoaded() > OnUpdate() ciklus > OnMissionFinish() > Destruktor
Játékos csatlakozás (szerver)OnEvent(ClientNewEventTypeID/ClientReadyEventTypeID) > InvokeOnConnect()
Játékos távozás (szerver)OnEvent(ClientDisconnectedEventTypeID) > PlayerDisconnected() > InvokeOnDisconnect()
Hookolási mintamodded class MissionServer/MissionGameplay az override és super hívásokkal
BemenetkezelésOnKeyPress(key) / OnKeyRelease(key) a MissionGameplay-en (csak kliens)
EseménykezelésOnEvent(EventType, Param) mindkét oldalon, különböző eseménytípusok oldalanként
super hívásokMindig hívd meg a super-t minden felülírásnál, különben az egész mod lánc elromlik
TakarításMindig takaríts az OnMissionFinish()-ben --- távolítsd el az RPC kezelőket, semmisítsd meg a widgeteket, nullázd a referenciákat
Képkocka-korlátozásHasználj időzítő akkumulátorokat az OnUpdate()-ben minden nem triviális munkához
GetPlayer()Csak kliensen működik; mindig null-t ad vissza dedikált szerveren
RPC regisztrációRegisztrálj az OnInit()-ben, ne a konstruktorban; regisztráld ki az OnMissionFinish()-ben

Legjobb gyakorlatok

  • Mindig hívd meg a super-t első sorként minden Mission felülírásban. Ez az egyetlen leggyakoribb DayZ modolási hiba. A super.OnInit() elfelejtése csendben elrontja a vanilla inicializációt és minden más modot a láncban.
  • Tartsd vékonynak a mission hook kódot --- delegálj menedzser osztályokhoz. Hozz létre egy singleton menedzsert (pl. MyModManager) és hívd a manager.Init() / manager.Update() / manager.Cleanup() metódusokat a hookokból. Ez tükrözi a COT és az Expansion által használt mintát.
  • Használj időzítő akkumulátorokat az OnUpdate()-ben minden munkához, aminek nem kell minden képkockánál futnia. Az OnUpdate másodpercenként 15-60+ alkalommal aktiválódik. Adatbázis lekérdezések, fájl I/O vagy játékos iteráció képkocka-sebességen futtatása pazarolja a szerver CPU-t.
  • Regisztráld az RPC-ket és eseménykezelőket az OnInit()-ben, ne a konstruktorban. A konstruktor az összes script modul betöltése előtt fut. A hálózati réteg nem áll készen az OnInit() előtt.
  • Mindig takaríts az OnMissionFinish()-ben. Semmisítsd meg a widgeteket, távolítsd el a CallLater regisztrációkat, regisztráld ki az RPC kezelőket, és nullázd a menedzser referenciákat. A takarítás elmulasztása elavult referenciákat okoz a misszió újratöltések között.

Kompatibilitás és hatás

Mod kompatibilitás: A MissionServer és a MissionGameplay a két leggyakrabban modolt osztály a DayZ-ben. Minden mod, amelynek szerver logikája vagy kliens felülete van, ezekbe hookol.

  • Betöltési sorrend: Az utolsónak betöltött mod modded class felülírása fut a legtávolabb a hívási láncban. Ha egy mod elfelejti a super-t, az csendben blokkolja az előtte betöltött összes modot. Ez a több-modos inkompatibilitás #1 oka.
  • Modded Class ütközések: Az InvokeOnConnect, InvokeOnDisconnect, OnInit, OnUpdate és OnMissionFinish a legvitatottabb felülírási pontok. Az ütközések ritkák, amíg minden mod meghívja a super-t.
  • Teljesítmény hatás: Nehéz logika az OnUpdate()-ben képkocka-korlátozás nélkül közvetlenül csökkenti a szerver/kliens FPS-t. Egyetlen mod, amely GetGame().GetPlayers() iterációt végez minden képkockánál egy 60 játékosos szerveren, mérhető többletterhelést ad.
  • Szerver/Kliens: A MissionServer hookok csak dedikált szervereken aktiválódnak. A MissionGameplay hookok csak klienseken aktiválódnak. Listen szerveren mindkét osztály létezik. A GetGame().GetPlayer() mindig null dedikált szerveren.

Valós modokban megfigyelt minták

Ezeket a mintákat professzionális DayZ modok forráskódjának tanulmányozásával erősítettük meg.

MintaModFájl/Hely
Vékony modded class MissionServer.OnInit() delegálás singleton menedzserhezCOTCommunityOnlineTools init a MissionServer-ben
InvokeOnConnect felülírás játékosonkénti JSON adat betöltéséhezExpansionJátékos beállítások szinkronizálása csatlakozáskor
StartingEquipSetup felülírás egyéni kezdő készletekhezTöbb közösségi modMissionServer kezdő készlet hookok
OnEvent elfogás a super előtt kitiltott játékosok blokkolásáhozCOTKitiltási rendszer a MissionServer-ben
OnMissionFinish takarítás widget Unlink()-kel és null hozzárendelésselExpansionHUD és menü takarítás

<< Előző: Központi gazdaság | Mission Hookok | Következő: Akció rendszer >>

Released under CC BY-SA 4.0 | Code examples under MIT License