RTD
RTD copied to clipboard
Refactor perk definition system
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);