Kasuromi / GTFO-API

GNU General Public License v3.0
8 stars 7 forks source link

Include Custom Objective Support #10

Open Panthr75 opened 2 years ago

Panthr75 commented 2 years ago

Using WardenObjectiveManager and IWardenObjectiveItem

Dexusan commented 2 years ago

Explain in more detail

Panthr75 commented 2 years ago

All Warden Objectives have a class that handles them which implements the IWardenObjectiveItem interface. As such, trying to make your own objectives is not only a pain in the ass, it requires rewriting some of the WardenObjectiveManager code.

Panthr75 commented 2 years ago

Mono reference IWardenObjectiveItem:

using System;
using AIGraph;
using LevelGeneration;
using Player;
using UnityEngine;

public interface iWardenObjectiveItem
{
    void ActivateWardenObjectiveItem();
    void DeactivateWardenObjectiveItem();
    AIG_CourseNode SpawnNode { get; }
    string PublicName { get; }
    Transform transform { get; }
    bool ObjectiveItemSolved { get; }
    ePickupItemStatus PickupItemStatus { get; }
    PlayerAgent PickedUpByPlayer { get; }
}
Kasuromi commented 2 years ago

If you have sample code of how you think this can be achieved that would be great, as of right now I can provide wrappers for iWardenObjectiveItem to make it easier to implement the interface.

Anything else sounds out of scope for the project(?)

Kasuromi commented 2 years ago

7069a22c2d195b7c0d9472748a57d3a84ab1f9e5 adds a wrapper for iWardenObjectiveItem

Panthr75 commented 2 years ago

This would be fine if I could actually override LG_FactoryJob.

Take for example somewhat of how Rundown Experiment B2 works: I create a custom objective that hooks into their system (which fucking sucks btw) to add a custom terminal puzzle objective.

To this day I still have problems with the system I made, and it would be better if it was handled for me.

For example, take a look at this snippet:

namespace Temp
{
    public partial class B2TerminalPuzzle : TerminalPuzzle, ICustomObjectiveItem
    {
        public override void Setup()
        {
            this.SetupObjective();

            // ...
        }

        public void SetupObjective()
        {
            CustomObjectiveManager.RegisterObjectiveItemForCollection(this.m_terminal.SpawnNode.LayerType, this);
            WardenObjectiveManager.SetObjectiveTextFragment(this.m_terminal.SpawnNode.LayerType, eWardenTextFragment.ITEM_SERIAL, this.m_terminal.ItemKey);
            WardenObjectiveManager.SetObjectiveTextFragment(this.m_terminal.SpawnNode.LayerType, eWardenTextFragment.ITEM_ZONE, this.m_terminal.SpawnNode.m_zone.NavInfo.GetFormattedText(LG_NavInfoFormat.Full_And_Number_No_Formatting));

            base.OnPuzzleSolved += () =>
            {
                this.m_isSolved = true;
                if (!SNetwork.SNet.IsMaster)
                    return;

                CustomObjectiveManager.OnLocalPlayerSolvedObjectiveItem(this.m_terminal.SpawnNode.LayerType, this);
             };
         }
    }
}

Making this system was a pain in the ass, and even still it doesn't work completely, and the patches I've had to do here are ridiculous. Please make it easier and an API so Mccad can make an objective where you have to build a railgun and blow up a reactor.

Panthr75 commented 2 years ago

SCREAMING

using AK;
using Flaff.CustomTerminalPuzzles;
using GameData;
using LevelGeneration;
using System.Collections.Generic;
using UnityEngine;

namespace Flaff.RundownExperiment.Objectives
{
    public class CustomObjectiveManager
    {
        private static Dictionary<LG_LayerType, List<ICustomObjectiveItem>> m_objectiveItemCollection = new Dictionary<LG_LayerType, List<ICustomObjectiveItem>>();
        private static bool m_forceFail;
        private static bool m_forceSuccess;

        public static void ForceExpeditionFail()
        {
            if (SNetwork.SNet.IsMaster)
            {
                m_forceFail = true;
            }
        }

        public static void ForceExpeditionSuccess()
        {
            if (SNetwork.SNet.IsMaster)
            {
                m_forceSuccess = true;
            }
        }

        public static bool CheckExpeditionFailed()
        {
            if (m_forceFail)
            {
                m_forceFail = false;
                return true;
            }

            return false;
        }

        public static void OnLevelCleanup()
        {
            m_objectiveItemCollection.Clear();
            m_forceSuccess = false;
            m_forceFail = false;
        }

        public static bool CheckWardenObjectiveCompleted(LG_LayerType layer)
        {
            eWardenObjectiveStatus status = WardenObjectiveManager.CurrentState.main_status;

            switch (layer)
            {
                case LG_LayerType.SecondaryLayer:
                    status = WardenObjectiveManager.CurrentState.second_status;
                    break;
                case LG_LayerType.ThirdLayer:
                    status = WardenObjectiveManager.CurrentState.third_status;
                    break;
            }

            if (status != eWardenObjectiveStatus.WardenObjectiveItemSolved && WardenObjectiveManager.TryGetWardenObjectiveDataForLayer(layer, out WardenObjectiveDataBlock data))
            {
                switch ((eCustomObjectiveType)(byte)data.Type)
                {
                    case eCustomObjectiveType.RealisticReactor:
                        break;
                    case eCustomObjectiveType.TerminalPuzzle:
                        var items = m_objectiveItemCollection[layer];
                        var baseItems = WardenObjectiveManager.Current.m_objectiveItemCollection[layer];
                        for (int index = 0; index < items.Count; index++)
                        {
                            var item = items[index];
                            if (!(items[index]?.ObjectiveItemSolved ?? baseItems[index]?.ObjectiveItemSolved ?? true))
                            {
                                return false;
                            }
                        }
                        return true;
                }
            }

            return false;
        }

        public static void OnLocalPlayerSolvedObjectiveItem(LG_LayerType layer, ICustomObjectiveItem item = null)
        {
            Debug.Log("CustomObjectiveManager.OnLocalPlayerSolvedObjectiveItem layer: " + layer + " CurrentState: " + WardenObjectiveManager.CurrentState + " ObjectiveType: " + WardenObjectiveManager.Current.m_activeWardenObjectives[layer].Type);

            eWardenObjectiveStatus wardenObjectiveStatus = WardenObjectiveManager.CurrentState.main_status;
            switch (layer)
            {
                case LG_LayerType.SecondaryLayer:
                    wardenObjectiveStatus = WardenObjectiveManager.CurrentState.second_status;
                    break;
                case LG_LayerType.ThirdLayer:
                    wardenObjectiveStatus = WardenObjectiveManager.CurrentState.third_status;
                    break;
            }
            switch ((eCustomObjectiveType)(byte)WardenObjectiveManager.Current.m_activeWardenObjectives[layer].Type)
            {
                case eCustomObjectiveType.RealisticReactor:
                    if (wardenObjectiveStatus != eWardenObjectiveStatus.Started)
                        break;
                    WardenObjectiveManager.Current.AttemptInteract(new pWardenObjectiveInteraction()
                    {
                        inLayer = layer,
                        type = eWardenObjectiveInteractionType.SolveWardenObjectiveItem
                    });
                    break;
                case eCustomObjectiveType.TerminalPuzzle:
                    if (wardenObjectiveStatus == eWardenObjectiveStatus.WardenObjectiveItemSolved)
                        break;

                    break;
            }
        }

        public static void RegisterObjectiveItemForCollection(LG_LayerType layer, ICustomObjectiveItem item, bool fromWardenObjectiveManager = false)
        {
            if (!m_objectiveItemCollection.ContainsKey(layer))
                m_objectiveItemCollection.Add(layer, new List<ICustomObjectiveItem>());

            if (fromWardenObjectiveManager)
            {
                m_objectiveItemCollection[layer].Add(null);
            }
            else
            {
                Debug.Log("CustomObjectiveManager.RegisterObjectiveItemForCollection " + m_objectiveItemCollection[layer].Count + " item: " + item.PublicName);

                var baseItems = WardenObjectiveManager.Current.m_objectiveItemCollection[layer];

                m_objectiveItemCollection[layer].Add(item);
                WardenObjectiveManager.Current.m_objectiveItemCollection[layer].Add(null);

                WardenObjectiveManager.UpdateObjectiveItemCollectionTextFragment(layer);
                if ((baseItems.Count + m_objectiveItemCollection[layer].Count) != 1)
                    return;

                WardenObjectiveManager.SetObjectiveTextFragment(layer, eWardenTextFragment.RETRIEVE_FIRST_ITEM, baseItems.Count == 0 ? m_objectiveItemCollection[layer][0].PublicName : baseItems[0].PublicName);
            }
        }

        public static void UpdateObjectiveItemCollectionTextFragment(LG_LayerType layer)
        {
            if (!WardenObjectiveManager.Current.m_objectiveItemCollection.ContainsKey(layer))
            {
                WardenObjectiveManager.Current.m_objectiveItemCollection.Add(layer, new Il2CppSystem.Collections.Generic.List<iWardenObjectiveItem>());
            }
            if (!m_objectiveItemCollection.ContainsKey(layer))
            {
                m_objectiveItemCollection.Add(layer, new List<ICustomObjectiveItem>());
            }

            var baseItems = WardenObjectiveManager.Current.m_objectiveItemCollection[layer];
            List<string> nameList = new List<string>();
            var customItems = m_objectiveItemCollection[layer];

            int solvedCount = 0;
            for (int index = 0; index < baseItems.Count; index++)
            {
                string name = null;
                bool solved = false;
                if (baseItems[index] != null)
                {
                    name = baseItems[index].PublicName;
                    solved = baseItems[index].ObjectiveItemSolved;
                }
                else if (customItems[index] != null)
                {
                    name = customItems[index].PublicName;
                    solved = customItems[index].ObjectiveItemSolved;
                }

                if (name != null)
                {

                    if (solved)
                    {
                        solvedCount++;
                        nameList.Add("<s>" + name + "</s>");
                    }
                    else
                    {
                        nameList.Add(name);
                    }
                }
            }

            string text = "";
            bool commas = nameList.Count > 2;
            for (int index = 0; index < nameList.Count; index++)
            {
                if (index > 0)
                {
                    if (commas)
                        text += ", ";
                    else
                        text += " ";

                    if (index + 1 >= nameList.Count)
                        text += "and ";
                }

                text += nameList[index];
            }

            WardenObjectiveManager.SetObjectiveTextFragment(layer, eWardenTextFragment.COUNT_CURRENT, solvedCount.ToString());
            WardenObjectiveManager.SetObjectiveTextFragment(layer, eWardenTextFragment.ALL_ITEMS, text);
        }

        public static void CheckWardenObjectiveStatus(WardenObjectiveManager instance, bool isRecall, LG_LayerType layer, pWardenObjectiveState newState, eWardenObjectiveStatus oldStatus, eWardenObjectiveStatus newStatus, int oldIndex, int newIndex, eWardenSubObjectiveStatus oldSub, eWardenSubObjectiveStatus newSub)
        {
            instance.SetObjectiveVisibility(layer, newStatus);
            WardenObjectiveDataBlock data;
            if ((isRecall || oldStatus != newStatus) && WardenObjectiveManager.TryGetWardenObjectiveDataForLayer(layer, out data))
            {
                switch (newStatus)
                {
                    case eWardenObjectiveStatus.Discovered:
                        Debug.Log("OBJECTIVE DISCOVERED!");
                        break;
                    case eWardenObjectiveStatus.Started:
                        Debug.Log("OBJECTIVE STARTED / ENTERED!");
                        WardenObjectiveManager.Current.m_sound.Post(EVENTS.STINGER_SUBOBJECTIVE_COMPLETE);
                        instance.UpdateObjectiveGUI(layer, newState);
                        break;
                    case eWardenObjectiveStatus.WardenObjectivePartiallySolved:
                        bool isMainObjective = true;
                        if (data.Type == eWardenObjectiveType.RetrieveBigItems)
                        {
                            if (layer == LG_LayerType.MainLayer)
                                instance.ActivateWinCondition();
                            WardenObjectiveManager.Current.m_sound.Post(EVENTS.STINGER_OBJECTIVE_COMPLETE);
                            isMainObjective = false;
                        }

                        if (isMainObjective)
                        {
                            WardenObjectiveManager.Current.m_sound.Post(EVENTS.STINGER_SUBOBJECTIVE_COMPLETE);
                            break;
                        }
                        break;
                    case eWardenObjectiveStatus.WardenObjectiveItemSolved:
                        if (instance.m_wardenObjectiveItem.ContainsKey(layer))
                            instance.m_wardenObjectiveItem[layer]?.DeactivateWardenObjectiveItem();

                        if (layer == LG_LayerType.MainLayer)
                            instance.ActivateWinCondition();

                        else if (WardenObjectiveManager.Current.m_activeWardenObjectives[layer].Type == eWardenObjectiveType.RetrieveBigItems)
                        {
                            iWardenObjectiveItem[] objectiveItemCollection = WardenObjectiveManager.GetObjectiveItemCollection(layer);
                            Debug.Log("WardenObjectiveManager, register required items from layer " + layer + " to exit scan, reqItems: " + objectiveItemCollection);
                            if (WardenObjectiveManager.m_elevatorExitWinConditionItem != null)
                                WardenObjectiveManager.m_elevatorExitWinConditionItem.AddRequiredScanItems(objectiveItemCollection);
                            if (WardenObjectiveManager.m_customGeoExitWinConditionItem != null)
                                WardenObjectiveManager.m_customGeoExitWinConditionItem.AddRequiredScanItems(objectiveItemCollection);
                        }
                        WardenObjectiveManager.Current.m_sound.Post(EVENTS.STINGER_OBJECTIVE_COMPLETE);

                        if (data.StopAllWavesBeforeGotoWin)
                        {
                            WardenObjectiveManager.StopAllWardenObjectiveEnemyWaves();
                            ElevatorShaftLanding.StopAmbientAlarm();
                        }

                        if (!isRecall)
                        {
                            Debug.Log("WardenObjectiveManager.OnStateChange, GoToWinCondition, WaveOnGotoWinTrigger: " + data.WaveOnGotoWinTrigger);
                            if (data.WaveOnGotoWinTrigger == eRetrieveExitWaveTrigger.OnObjectiveCompleted && WardenObjectiveManager.HasValidWaveSettings(data.WavesOnGotoWin))
                                instance.TriggerGotoWinWaveInCorrectCourseNode(layer);
                            WardenObjectiveManager.CheckAndExecuteEventsOnTrigger(data.EventsOnGotoWin, eWardenObjectiveEventTrigger.None, true);
                            if (SNetwork.SNet.IsMaster)
                            {
                                WardenObjectiveManager.Current.AttemptInteract(new pWardenObjectiveInteraction()
                                {
                                    inLayer = layer,
                                    type = eWardenObjectiveInteractionType.UpdateSubObjective,
                                    newSubObj = eWardenSubObjectiveStatus.GoToWinCondition
                                });
                                break;
                            }
                            break;
                        }
                        break;
                }
            }
            bool flag2 = newIndex > 0 && oldIndex != newIndex;
            if (flag2)
            {
                int index1 = newIndex - 1;
                if (instance.m_objectiveItemCollection[layer].Count > index1)
                {
                    for (int index2 = 0; index2 < instance.m_objectiveItemCollection[layer].Count; ++index2)
                    {
                        if (instance.m_objectiveItemCollection[layer][index2] != null)
                            instance.m_objectiveItemCollection[layer][index2].DeactivateWardenObjectiveItem();
                        else
                        {
                            m_objectiveItemCollection[layer][index2]?.DeactivateWardenObjectiveItem();
                        }
                    }
                    if (instance.m_objectiveItemCollection[layer][index1] != null)
                        instance.m_objectiveItemCollection[layer][index1].ActivateWardenObjectiveItem();
                    else
                        m_objectiveItemCollection[layer][index1]?.ActivateWardenObjectiveItem();
                }
                else
                    Debug.LogError("WardenObjectiveManager.OnStateChange, layer: " + layer + " item Index is OUTSIDE the range of m_objectiveItemCollection! count: " + instance.m_objectiveItemCollection[layer].Count + " itemIndex: " + index1);
            }
            bool flag3 = newState.forceUpdate && WardenObjectiveManager.m_localLayer == newState.forceUpdateFromLayer;
            if (!(oldSub != newSub | flag3 | flag2 | isRecall))
                return;
            WardenObjectiveManager.m_lastSubObjectiveUpdateTime[layer] = Clock.Time;
            instance.UpdateObjectiveGUI(layer, newState);
        }

        public static void OnLocalPlayerFoundObjectiveItem(LG_LayerType layer, ICustomObjectiveItem item)
        {
            Debug.Log("CustomObjectiveManager.OnLocalPlayerFoundObjectiveItem CurrentState: " + WardenObjectiveManager.CurrentState);
            if (!WardenObjectiveManager.CompareToCurrentState(layer, eWardenObjectiveStatus.Started, eWardenSubObjectiveStatus.FindLocationInfo, eWardenSubObjectiveStatus.FindLocationInfoHelp) && !WardenObjectiveManager.CompareToCurrentState(layer, eWardenObjectiveStatus.Started, eWardenSubObjectiveStatus.GoToZone, eWardenSubObjectiveStatus.GoToZoneHelp) && (!WardenObjectiveManager.CompareToCurrentState(layer, eWardenObjectiveStatus.Started, eWardenSubObjectiveStatus.SolveItem, eWardenSubObjectiveStatus.SolveItemHelp) && !WardenObjectiveManager.CompareToCurrentState(layer, eWardenObjectiveStatus.Started, eWardenSubObjectiveStatus.InZoneFindItem, eWardenSubObjectiveStatus.InZoneFindItemHelp)))
                return;
            int index = m_objectiveItemCollection[layer].IndexOf(item) + 1;
            Debug.Log("OnLocalPlayerFoundObjectiveItem attempting interact, itemIndexPlusOne: " + index);
            WardenObjectiveManager.Current.AttemptInteract(new pWardenObjectiveInteraction()
            {
                inLayer = layer,
                type = eWardenObjectiveInteractionType.UpdateSubObjective,
                newSubObj = eWardenSubObjectiveStatus.SolveItem,
                itemIndexPlusOne = (byte)index
            });
        }
    }
}