Great Tester of Whatever
|
02-15-2022
, 07:11
[TF2 & L4D & L4D2] Actions
|
#1
|
ConVars
PHP Code:
// Logs every action transitions // 1 - Enable debug, 0 - Disable debug ext_actions_debug "0"
// Logs every action/behavior/component allocation or deallocation // Used to track memory leaks // 1 - Enable memory debug, 0 - Disable memory debug ext_actions_debug_memory "0"
Commands
PHP Code:
ext_actions_dump - Dumps every nextbot actions to console.
Guide Samples
Action Handlers
You can hook action handler by setting it's callback function (or unhook with INVALID_FUNCTION)
PHP Code:
action.OnUpdate = OnUpdate;
Simple example, if you need to hook "Update" handler of every "whatever" action
PHP Code:
/* * OnActionCreated notifies whenever entity got a new action but without actual transition yet. * That means action is constructed by the corresponding constructor and still hasn't been updated or * started yet. */ public void OnActionCreated(BehaviorAction action, int actor, const char[] name) { if (strcmp(name, "whatever") = 0) { action.Update = OnWhateverUpdate; } }
public Action OnWhateverUpdate(BehaviorAction action, int actor, float interval, ActionResult) { // unhook it action.Update = INVALID_FUNCTION; return Plugin_Continue; }
Every action handler has it's own callback prototype and you must use the right one
All action handler function prototypes https://github.com/Vinillia/actions....s.inc#L74-L183
Getting"whatever" action of specific entity
PHP Code:
BehaviorAction action = ActionsManager.GetAction(entity, "whatever"); if (action != INVALID_ACTION) { // }
Action Userdata
You can store any data in action with and without identity
Data stored without identity is shared between plugins and persists until action is deleted
Data stored with identity is NOT shared between plugins (multiple plugins can use same keys) and persists until plugin who owns the data is unloaded
Usually you want to store data without identity, the latter is for specific use cases where you want to store handles or something important that has to be accessible even if action is destroyed
Example:
PHP Code:
public void OnActionCreated(BehaviorAction action, int actor, const char[] name) { // to store with identity add "Identity" after Data // action.SetUserDataIdentity("my_int", 2); // action.SetUserDataIdentityVector("my_vector", { -89.0, 125.0, -5324.0 }); action.SetUserData("my_int", 2); action.SetUserData("my_float", 2.25); action.SetUserDataVector("my_vector", { -89.0, 125.0, -5324.0 }); action.SetUserDataString("my_string", "Test test test test"); }
public void OnActionDestroyed(BehaviorAction action, int actor, const char[] name) { int ivalue, fvalue; float vector[3]; char buffer[64];
ivalue = action.GetUserData("my_int"); fvalue = action.GetUserData("my_float"); action.GetUserDataVector("my_vector", vector); action.GetUserDataString("my_string", buffer, sizeof buffer);
PrintToServer("ivalue: %i", ivalue); PrintToServer("fvalue: %f", fvalue); PrintToServer("vector: {%f %f %f}", vector[0], vector[1], vector[2]); PrintToServer("buffer: %s", buffer); }
Action Constructor
If you want to create internal game actions then you need to create SDKCall then call it on allocated memory.
Even if this approach works, I still think there should be a way to hide SDKCall and memory allocation into gamedata.
So there is ActionConstructor
PHP Code:
ActionConstructor g_SampleActionConstructor;
public void OnPluginStart() { GameData data = new GameData("");
g_SampleActionConstructor = ActionConstructor.SetupFromConf(data, "SampleAction");
delete data; int entity = FindEntityByClassname(-1, "player"); g_SampleActionConstructor.Execute(entity); }
PHP Code:
"Games" { "left4dead2" { "ActionConstructors" { "SampleAction" { /* Signature/address must point to action constructor */ // "signature" "whatever_signature" // "address" "whatever_address" /* Size of desired action */ // "size" "123" // "size" "0x123" // "size" "123h" "params" { "target" { /* * basic - plain generic data * float - floating point data * object - raw struct/class/array? */ "type" "basic" /* * byval - pass by val * byref - pass by ref * dtor - object has destructor * ctor - object has constructor * assignop - object has assignment operator * unalign - object contains unaligned fields */ "flags" "byval" // You can specify encoder to transform arbitary data to valve params (eg. entity index -> CBaseEntity) /* * entity - entity index to CBaseEntity * vector - raw array to Vector struct */ "encoder" "entity" } } } } } }
Action Component
All actions are eventually stored in the behavior and behavior is stored in IIntention component.
You can create your own IIntention component called "ActionComponent"
The created component works exactly the same as the other components but you can also delete it at any time
If the plugin is unloaded all its components will be deleted automatically
PHP Code:
#include <actions>
public void OnPluginStart() { HookEvent("player_spawn", player_spawn); }
void player_spawn(Event event, const char[] name, bool dontBroadcast) { int client = GetClientOfUserId(event.GetInt("userid"));
if (!client || !IsClientInGame(client)) return;
PrintToChatAll("player_spawn");
ActionComponent component = new ActionComponent(client, SampleComponent_InitialAction, SampleComponent_Update, SampleComponent_Upkeep, SampleComponent_Reset, "SampleComponent");
CreateTimer(10.0, timer_delete_component, component, TIMER_FLAG_NO_MAPCHANGE); }
void timer_delete_component(Handle timer, ActionComponent component) { delete component; LogMessage("Deleted component"); }
void SampleComponent_Update(ActionComponent component, int entity) { LogMessage("SampleComponent_Update"); }
void SampleComponent_Upkeep(ActionComponent component, int entity) { LogMessage("SampleComponent_Upkeep"); }
void SampleComponent_Reset(ActionComponent component, int entity) { LogMessage("SampleComponent_Reset"); }
BehaviorAction SampleComponent_InitialAction(ActionComponent component, int entity) { BehaviorAction action = ActionsManager.Create("MySampleAction");
action.OnStart = MySampleAction_OnStart;
return action; }
Action MySampleAction_OnStart(any action, int actor, float interval, ActionResult result) { LogMessage("MySampleAction_OnStart"); return Plugin_Continue; }
Left 4 Dead 2 Usage Example
PHP Code:
#pragma semicolon 1 #pragma newdecls required
#include <sourcemod> #include <sdktools>
#include <actions>
ActionConstructor g_hApproachVector; ActionConstructor g_hApproachEntity;
methodmap SurvivorApproach < BehaviorAction { public static bool ApproachVector(int bot, const float goal[3]) { BehaviorAction action = GetBestSuspendebale(bot);
if (action == INVALID_ACTION) return false;
action.SetUserDataVector("approach_goal", goal); action.Update = Suspendable_Update; }
public static bool ApproachEntity(int bot, int entity) { BehaviorAction action = GetBestSuspendebale(bot);
if (action == INVALID_ACTION) return false;
action.SetUserData("approach_target", EntIndexToEntRef(entity)); action.Update = Suspendable_Update; } }
public Action Suspendable_Update(BehaviorAction action, int actor, float interval, ActionResult result) { action.Update = INVALID_FUNCTION;
int approach_target; if (ActionsManager.GetActionUserData(action, "approach_target", approach_target)) { if ((approach_target = EntRefToEntIndex(approach_target)) <= 0) return Plugin_Continue;
return action.SuspendFor(SetupAndReturn(g_hApproachEntity, approach_target)); } else { float goal[3]; action.GetUserDataVector("approach_goal", goal); return action.SuspendFor(SetupAndReturn(g_hApproachVector, .goal = goal)); } }
BehaviorAction SetupAndReturn(ActionConstructor constructor, int target = 0, const float goal[3] = NULL_VECTOR) { BehaviorAction action = INVALID_ACTION;
if (constructor == g_hApproachVector) { action = g_hApproachVector.Execute(goal); action.SetUserDataVector("survivor_approach_goal", goal); } else { action = g_hApproachEntity.Execute(target); action.SetUserData("survivor_approach_target", EntIndexToEntRef(target)); }
action.OnUpdate = SurvivorApproach_Update; return action; }
public Action SurvivorApproach_Update(BehaviorAction action, int actor, float interval, ActionResult result) { float origin[3], goal[3]; GetClientAbsOrigin(actor, origin);
if (!ActionsManager.GetActionUserDataVector(action, "survivor_approach_goal", goal)) { int target = EntRefToEntIndex(action.GetUserData("survivor_approach_target"));
if (target <= 0) { return action.Done(); }
GetEntPropVector(target, Prop_Send, "m_vecOrigin", goal); }
float distance = GetVectorDistance(origin, goal, true); if (distance < 150.0 * 150.0) { return action.Done(); }
return action.Continue(); }
public void OnPluginStart() { GameData data = new GameData("l4d2_bot_move");
g_hApproachVector = ActionConstructor.SetupFromConf(data, "SurvivorLegsApproach::SurvivorLegsApproachVector"); g_hApproachEntity = ActionConstructor.SetupFromConf(data, "SurvivorLegsApproach::SurvivorLegsApproachEntity");
delete data;
RegAdminCmd("sm_bot_move_entity", sm_bot_move_entity, ADMFLAG_ROOT); RegAdminCmd("sm_bot_move_vector", sm_bot_move_vector, ADMFLAG_ROOT); }
Action sm_bot_move_vector(int client, int args) { if (args < 1) { ReplyToCommand(client, "Usage: sm_bot_move_vector <bot>"); return Plugin_Handled; }
float origin[3], angle[3]; float goal[3];
GetClientEyePosition(client, origin); GetClientEyeAngles(client, angle);
TR_TraceRayFilter(origin, angle, MASK_SHOT, RayType_Infinite, TraceFilter); if (!TR_DidHit()) { ReplyToCommand(client, "TraceRayFilter didn't hit"); return Plugin_Handled; }
TR_GetEndPosition(goal);
char sArg[32], target_name[MAX_TARGET_LENGTH]; GetCmdArg(1, sArg, sizeof(sArg));
int target_list[MAXPLAYERS], target_count; bool tn_is_ml;
if ((target_count = ProcessTargetString( sArg, client, target_list, MAXPLAYERS, COMMAND_FILTER_ALIVE, /* Only allow alive players */ target_name, sizeof(target_name), tn_is_ml)) <= 0) { ReplyToTargetError(client, target_count); return Plugin_Handled; }
for (int i = 0; i < target_count; i++) { if (IsFakeClient(target_list[i])) SurvivorApproach.ApproachVector(target_list[i], goal); }
return Plugin_Handled; }
Action sm_bot_move_entity(int client, int args) { if (args < 1) { ReplyToCommand(client, "Usage: sm_bot_move_entity <bot>"); return Plugin_Handled; }
int target = GetClientAimTarget(client, false);
if (target == -1) { ReplyToCommand(client, "Invalid target entity"); return Plugin_Handled; }
char sArg[32], target_name[MAX_TARGET_LENGTH]; GetCmdArg(1, sArg, sizeof(sArg));
int target_list[MAXPLAYERS], target_count; bool tn_is_ml;
if ((target_count = ProcessTargetString( sArg, client, target_list, MAXPLAYERS, COMMAND_FILTER_ALIVE, /* Only allow alive players */ target_name, sizeof(target_name), tn_is_ml)) <= 0) { ReplyToTargetError(client, target_count); return Plugin_Handled; } for (int i = 0; i < target_count; i++) { if (IsFakeClient(target_list[i])) SurvivorApproach.ApproachEntity(target_list[i], target); }
return Plugin_Handled; }
stock BehaviorAction GetBestSuspendebale(int bot) { BehaviorAction action = ActionsManager.GetAction(bot, "SurvivorLegsApproach");
if (action == INVALID_ACTION) action = ActionsManager.GetAction(bot, "SurvivorLegsStayClose"); if (action != INVALID_ACTION) return action;
action = ActionsManager.GetAction(bot, "SurvivorBehavior");
if (action == INVALID_ACTION) return INVALID_ACTION;
if (action.IsSuspended) { action = action.Above; } else { action = action.Child; }
return action; }
bool TraceFilter(int entity, int contentsMask) { return entity <= 0; }
gamedata
Source code
Last edited by BHaType; 04-24-2024 at 21:06.
|
|