Phil25 / RTD

Roll The Dice SourceMod plugin for Team Fortress 2
GNU General Public License v3.0
55 stars 20 forks source link

Refactor perk definition system #85

Open Phil25 opened 10 months ago

Phil25 commented 10 months ago

Overview

Perks define the call method and now the init method which correspond to the function names in code. With the upcoming programmatic preconditions (#71), there will be a third such function, and probably even more upcoming. This can be restructured into a single very generic function which accepts a enum of an action call, to which perk implementation will respond accordingly. This should also be exposed via API.

I will skip over a lot of thoughts I had about this refactor, mostly because I don't want to bother formalizing them, but also because this is a huge undertaking and many things will need to be investigated during implementation. For now, I needed a place to store the PoC below, so, if anyone interested happens to read it, hopefully it will clear some things up at least.

Proof of Concept

Include file

enum RTDPerkCall
{
    RTDPerkCall_Init,
    RTDPerkCall_Apply,
    RTDPerkCall_Remove,
    RTDPerkCall_IsValidForClient,
    RTDPerkCall_GetSettingType,
    RTDPerkCall_VerifySettingValue,
}

enum RTDSettingType
{
    RTDSettingType_Unknown,
    RTDSettingType_Any,
    RTDSettingType_Integer,
    RTDSettingType_Float,
    RTDSettingType_String
}

enum RTDSettingVerification
{
    RTDSettingVerification_OK,
    RTDSettingVerification_Warning,
    RTDSettingVerification_Fatal
}

methodmap RTDPerkBase
{
    public void Apply(const int client)
    {
        char sCaller[32];
        GetCallerName(sCaller, sizeof(sCaller));
        LogStackTrace("Perk \"%s\" has no Apply method defined", sCaller);
    }

    public void Remove(const int client)
    {
        char sCaller[32];
        GetCallerName(sCaller, sizeof(sCaller));
        LogStackTrace("Perk \"%s\" has no Remove method defined", sCaller);
    }

    public bool IsValidForClient(const int client, const TFClassType eClass)
    {
        return true;
    }

    public RTDSettingType GetSettingType(const char[] sSetting)
    {
        return RTDSettingType_Any;
    }

    public bool VerifySettingValue(const char[] sSetting, const char[] sValue, char[] sError, const int iErrorLen)
    {
        return true;
    }
}

#define RTD2_DECLARE_PERK_TYPE(%1) \
    int %1(RTDPerkCall ePerkCall, const int client, const TFClassType eClass, char[] sFeedback, const int iFeedbackLen, const char[] sString1="", const char[] sString2="") \
    { \
        switch (ePerkCall) \
        { \
        case RTDPerkCall_Apply: view_as<%1_>(0).Apply(client); \
        case RTDPerkCall_Remove: view_as<%1_>(0).Remove(client); \
        case RTDPerkCall_IsValidForClient: return view_as<%1_>(0).IsValidForClient(client, eClass); \
        case RTDPerkCall_GetSettingType: return view_as<int>(view_as<%1_>(0).GetSettingType(sString1)); \
        case RTDPerkCall_VerifySettingValue: return view_as<%1_>(0).VerifySettingValue(sString1, sString2, sFeedback, iFeedbackLen); \
        } \
        return 0; \
    } \
    methodmap %1_ < RTDPerkBase

Perk implementation

RTD2_DECLARE_PERK_TYPE(MyCustomPerk)
{
    public void Apply(const int client)
    {
        PrintToServer("Applying MyCustomPerk on %d", client);
    }

    public void Remove(const int client)
    {
        PrintToServer("Removing MyCustomPerk from %d", client);
    }

    public bool IsValidForClient(const int client, const TFClassType eClass)
    {
        return eClass == TFClass_Scout;
    }

    public RTDSettingType GetSettingType(const char[] sSetting)
    {
        if (StrEqual(sSetting, "amount"))
        {
            return RTDSettingType_Integer;
        }

        return RTDSettingType_Unknown;
    }

    public bool VerifySettingValue(const char[] sSetting, const char[] sValue, char[] sError, const int iErrorLen)
    {
        if (StrEqual(sSetting, "amount") && !(1 <= StringToInt(sValue) <= 3))
        {
            Format(sError, iErrorLen, "amount should be either 1, 2 or 3 (is: %s)", sValue);
            return false;
        }

        return true;
    }
}

Calls from RTD source

char sFeedback[128];
MyCustomPerk(RTDPerkCall_Apply, 4, TFClass_DemoMan, sFeedback, sizeof(sFeedback));
MyCustomPerk(RTDPerkCall_Remove, 5, TFClass_DemoMan, sFeedback, sizeof(sFeedback));
MyCustomPerk(RTDPerkCall_IsValidForClient, 5, TFClass_DemoMan, sFeedback, sizeof(sFeedback));
MyCustomPerk(RTDPerkCall_GetSettingType, 5, TFClass_DemoMan, sFeedback, sizeof(sFeedback), "amount");
MyCustomPerk(RTDPerkCall_VerifySettingValue, 5, TFClass_DemoMan, sFeedback, sizeof(sFeedback), "amount", "3");
PrintToServer("Feedback: %s", sFeedback);