From 4d1867a4481dfc7f40fff21d9f368b803a2cf226 Mon Sep 17 00:00:00 2001 From: jenz Date: Sun, 20 Oct 2024 23:20:20 +0200 Subject: [PATCH] added vscripts core and the moving npc from that asian dude. added includes as well because we already have something called vscripts.inc i guess --- VScripts/scripting/MovingNPC.sp | 506 ++++++++++++++++ VScripts/scripting/README.MD | 1 + VScripts/scripting/Vscripts_Core.sp | 553 ++++++++++++++++++ VScripts/scripting/include/MovingNPC.inc | 331 +++++++++++ .../scripting/include/vscripts_moving_npc.inc | 193 ++++++ 5 files changed, 1584 insertions(+) create mode 100644 VScripts/scripting/MovingNPC.sp create mode 100644 VScripts/scripting/README.MD create mode 100644 VScripts/scripting/Vscripts_Core.sp create mode 100644 VScripts/scripting/include/MovingNPC.inc create mode 100644 VScripts/scripting/include/vscripts_moving_npc.inc diff --git a/VScripts/scripting/MovingNPC.sp b/VScripts/scripting/MovingNPC.sp new file mode 100644 index 00000000..1b3b0bb0 --- /dev/null +++ b/VScripts/scripting/MovingNPC.sp @@ -0,0 +1,506 @@ +#pragma semicolon 1 + +#define PLUGIN_AUTHOR "Cloud Strife" +#define PLUGIN_VERSION "1.0b" + +#include +#include + +#pragma newdecls required + +bool enabled = false; + +ArrayList g_aMovingNpc = null; +ArrayList g_aNpcConfigNT = null; +StringMap g_mNpcConfig = null; + +public Plugin myinfo = +{ + name = "MovingNPC vscripts", + author = PLUGIN_AUTHOR, + description = "MovingNPC vscripts", + version = PLUGIN_VERSION, + url = "https://steamcommunity.com/id/cloudstrifeua/" +}; + +//TODO: Add start and stop triggers + +methodmap MovingNpcConfig < Basic +{ + public MovingNpcConfig() + { + Basic myclass = new Basic(); + myclass.SetFloat("fRate", 0.1); + myclass.SetFloat("fDistance", 5000.0); + myclass.SetFloat("fRetarget", 7.5); + myclass.SetFloat("fForward", 1.0); + myclass.SetFloat("fTurning", 0.5); + myclass.SetFloat("fLifetime", 0.0); + myclass.SetString("sThrusterFwd", ""); + myclass.SetString("sThrusterSide", ""); + myclass.SetString("sAttachment", ""); + myclass.SetString("sTemplate", ""); + return view_as(myclass); + } + property float lifetime + { + public get() + { + return this.GetFloat("fLifetime"); + } + public set(float val) + { + this.SetFloat("fLifetime", val); + } + } + property float rate + { + public get() + { + return this.GetFloat("fRate"); + } + public set(float val) + { + this.SetFloat("fRate", val); + } + } + property float distance + { + public get() + { + return this.GetFloat("fDistance"); + } + public set(float val) + { + this.SetFloat("fDistance", val); + } + } + property float retarget + { + public get() + { + return this.GetFloat("fRetarget"); + } + public set(float val) + { + this.SetFloat("fRetarget", val); + } + } + property float forward_factor + { + public get() + { + return this.GetFloat("fForward"); + } + public set(float val) + { + this.SetFloat("fForward", val); + } + } + property float turning_factor + { + public get() + { + return this.GetFloat("fTurning"); + } + public set(float val) + { + this.SetFloat("fTurning", val); + } + } + public int GetThrusterFwd(char[] buffer, int size) + { + return this.GetString("sThrusterFwd", buffer, size); + } + public void SetThrusterFwd(const char[] sThruster) + { + this.SetString("sThrusterFwd", sThruster); + } + public int GetThrusterSide(char[] buffer, int size) + { + return this.GetString("sThrusterSide", buffer, size); + } + public void SetThrusterSide(const char[] sThruster) + { + this.SetString("sThrusterSide", sThruster); + } + public int GetAttachment(char[] buffer, int size) + { + return this.GetString("sAttachment", buffer, size); + } + public void SetAttachment(const char[] sAttachment) + { + this.SetString("sAttachment", sAttachment); + } + public int GetTemplate(char[] buffer, int size) + { + return this.GetString("sTemplate", buffer, size); + } + public void SetTemplate(const char[] sTemplate) + { + this.SetString("sTemplate", sTemplate); + } + public void Delete() + { + delete this; + } +} + +stock int GetEntityIndex(int entity, const char[] name, const char[] classname = "*") +{ + if (!enabled) + { + return 0; + } + if(name[0] == '#') + { + return Vscripts_GetEntityIndexByHammerID(StringToInt(name[1]), classname, entity); + } + else + { + return Vscripts_GetEntityIndexByName(name, classname, entity); + } +} + +public bool IsMovingNpcExists(int entity) +{ + if (!enabled) + { + return false; + } + for(int i = 0; i < g_aMovingNpc.Length; ++i) + { + MovingNpc npc = g_aMovingNpc.Get(i); + if(npc.entity == entity || npc.tf == entity || npc.ts == entity) + return true; + } + return false; +} + +public void KillNpc(MovingNpc npc) +{ + if (!enabled) + { + return; + } + for(int i = 0; i < g_aMovingNpc.Length; ++i) + { + MovingNpc cur = g_aMovingNpc.Get(i); + if(cur.entity == npc.entity) + { + npc.Stop(); + g_aMovingNpc.Erase(i); + break; + } + } + npc.kill = true; +} + +public Action OnMovingNpcTimeout(Handle timer, MovingNpc npc) +{ + KillTimer(timer); + npc.lifetimer = null; + KillNpc(npc); + return Plugin_Stop; +} + +public void StartNpc(MovingNpc npc) +{ + if (!enabled) + { + return; + } + npc.Start(); + if(npc.lifetime > 0 && !npc.lifetimer) + npc.lifetimer = CreateTimer(npc.lifetime, OnMovingNpcTimeout, npc, TIMER_FLAG_NO_MAPCHANGE); +} + +public int GetEntName(int entity, char[] buffer, int size) +{ + return GetEntPropString(entity, Prop_Data, "m_iName", buffer, size); +} + +public int GetEntHammerID(int entity) +{ + return GetEntProp(entity, Prop_Data, "m_iHammerID"); +} + + +stock bool MatchTrigger(int entity, const char[] sTrigger, bool namefixup = false) +{ + if (!enabled) + { + return false; + } + if(sTrigger[0] == '#') + { + return StringToInt(sTrigger[1]) == GetEntHammerID(entity); + } else + { + char name[MAX_ENT_NAME]; + GetEntName(entity, name, sizeof(name)); + if(!name[0]) + return false; + if(namefixup) + { + int c = FindCharInString(name, '&', true); + if(c != -1) + { + name[c] = '\0'; + } + } + return StrEqual(name, sTrigger); + } +} + +public void OnMapStart() +{ + enabled = false; + char sConfigPath[PLATFORM_MAX_PATH]; + char sCurMap[PLATFORM_MAX_PATH]; + GetCurrentMap(sCurMap, sizeof(sCurMap)); + BuildPath(Path_SM, sConfigPath, sizeof(sConfigPath), "configs/movingnpc/%s.cfg", sCurMap); + if(!FileExists(sConfigPath)) + { + return; + } + KeyValues Config = new KeyValues("npc"); + if(!Config.ImportFromFile(sConfigPath)) + { + LogMessage("ImportFromFile() failed for map %s!", sCurMap); + return; + } + Config.Rewind(); + if(!Config.GotoFirstSubKey(true)) + { + LogMessage("The current map does not have any moving npcs configured."); + return; + } + enabled = true; + g_mNpcConfig = new StringMap(); + g_aNpcConfigNT = new ArrayList(); + do + { + MovingNpcConfig NpcConf = new MovingNpcConfig(); + char buffer[MAX_ENT_NAME + MAX_INPUT_NAME]; + Config.GetString("thruster_forward", buffer, sizeof(buffer), ""); + if(!buffer[0]) + { + delete NpcConf; + LogMessage("Could not find \"thruster_forward\" in config for map %s", sCurMap); + continue; + } + NpcConf.SetThrusterFwd(buffer); + + Config.GetString("thruster_side", buffer, sizeof(buffer), ""); + if(!buffer[0]) + { + delete NpcConf; + LogMessage("Could not find \"thruster_side\" in config for map %s", sCurMap); + continue; + } + NpcConf.SetThrusterSide(buffer); + + Config.GetString("attachment", buffer, sizeof(buffer), ""); + if(!buffer[0]) + { + delete NpcConf; + LogMessage("Could not find \"attachment\" in config for map %s", sCurMap); + continue; + } + NpcConf.SetAttachment(buffer); + + char sTemplate[MAX_ENT_NAME]; + Config.GetString("template", sTemplate, sizeof(sTemplate), ""); + NpcConf.SetTemplate(sTemplate); + NpcConf.rate = Config.GetFloat("tickrate", 0.1); + NpcConf.distance = Config.GetFloat("distance", 5000.0); + NpcConf.retarget = Config.GetFloat("retarget", 7.5); + NpcConf.forward_factor = Config.GetFloat("forward_factor", 1.0); + NpcConf.turning_factor = Config.GetFloat("turning_factor", 0.5); + NpcConf.lifetime = Config.GetFloat("lifetime", 0.0); + if(sTemplate[0]) + { + ArrayList tmp; + if(!g_mNpcConfig.GetValue(sTemplate, tmp)) + { + tmp = new ArrayList(); + } + tmp.Push(NpcConf); + g_mNpcConfig.SetValue(sTemplate, tmp, true); + } else + { + g_aNpcConfigNT.Push(NpcConf); + } + } while(Config.GotoNextKey(true)); + delete Config; + g_aMovingNpc = new ArrayList(); + HookEvent("round_start", OnRoundStart, EventHookMode_PostNoCopy); +} + +public void OnRoundStart(Event event, const char[] name, bool dontBroadcast) +{ + if (!enabled) + { + return; + } + for(int i = 0; i < g_aNpcConfigNT.Length; ++i) + { + MovingNpcConfig NpcConf = g_aNpcConfigNT.Get(i); + char sThrusterFwd[MAX_ENT_NAME], sThrusterSide[MAX_ENT_NAME], sAttachment[MAX_ENT_NAME]; + NpcConf.GetThrusterFwd(sThrusterFwd, sizeof(sThrusterFwd)); + NpcConf.GetThrusterSide(sThrusterSide, sizeof(sThrusterSide)); + NpcConf.GetAttachment(sAttachment, sizeof(sAttachment)); + int thruster_fwd = -1, thruster_side = -1, attachment = -1; + while((thruster_fwd = GetEntityIndex(thruster_fwd, sThrusterFwd, "phys_thruster")) != -1) + { + if(IsMovingNpcExists(thruster_fwd)) + continue; + do { + thruster_side = GetEntityIndex(thruster_side, sThrusterSide, "phys_thruster"); + } while (thruster_side != -1 && IsMovingNpcExists(thruster_side)); + + do { + attachment = GetEntityIndex(attachment, sAttachment); + } while (attachment != -1 && IsMovingNpcExists(attachment)); + + if(thruster_side != -1 && attachment != -1) + { + NewMovingNpc(NpcConf, attachment, thruster_fwd, thruster_side); + } + } + } +} + +stock void NewMovingNpc(MovingNpcConfig NpcConf, int attachment, int thruster_fwd, int thruster_side) +{ + if (!enabled) + { + return; + } + MovingNpc npc = new MovingNpc(attachment, NpcConf.rate, NpcConf.distance, NpcConf.retarget, NpcConf.forward_factor, NpcConf.turning_factor, NpcConf.lifetime); + npc.SetThruster(true, thruster_fwd); + npc.SetThruster(false, thruster_side); + StartNpc(npc); + g_aMovingNpc.Push(npc); +} + +public void Vscritps_OnTemplateInstanceCreated(int template, const int[] createdEntities, int size) +{ + if (!enabled) + { + return; + } + if(!g_mNpcConfig) + return; + + char sTemplate[MAX_ENT_NAME]; + GetEntName(template, sTemplate, sizeof(sTemplate)); + ArrayList configs; + if(!g_mNpcConfig.GetValue(sTemplate, configs)) + { + Format(sTemplate, sizeof(sTemplate), "#%d", GetEntHammerID(template)); + if(!g_mNpcConfig.GetValue(sTemplate, configs)) + { + return; + } + } + for(int i = 0; i < configs.Length; ++i) + { + int attachment = -1, thruster_fwd = -1, thruster_side = -1; + MovingNpcConfig npcConf = configs.Get(i); + char sAttachment[MAX_ENT_NAME], sThrusterFwd[MAX_ENT_NAME], sThrusterSide[MAX_ENT_NAME]; + npcConf.GetAttachment(sAttachment, sizeof(sAttachment)); + npcConf.GetThrusterFwd(sThrusterFwd, sizeof(sThrusterFwd)); + npcConf.GetThrusterSide(sThrusterSide, sizeof(sThrusterSide)); + for(int e = 0; e < size; ++e) + { + int entity = createdEntities[e]; + if(MatchTrigger(entity, sAttachment, true)) + { + attachment = entity; + } else if(MatchTrigger(entity, sThrusterFwd, true)) + { + thruster_fwd = entity; + } else if(MatchTrigger(entity, sThrusterSide, true)) + { + thruster_side = entity; + } + } + if(attachment != -1 && thruster_fwd != -1 && thruster_side != -1) + { + NewMovingNpc(npcConf, attachment, thruster_fwd, thruster_side); + break; + } + } +} + +public void OnEntityDestroyed(int entity) +{ + if (!enabled) + { + return; + } + if(!g_aMovingNpc) + return; + for (int i = 0; i < g_aMovingNpc.Length; ++i) + { + MovingNpc npc = g_aMovingNpc.Get(i); + if(npc.entity == entity || npc.tf == entity || npc.ts == entity) + { + npc.Stop(); + npc.kill = true; + g_aMovingNpc.Erase(i); + break; + } + } +} + + +public void OnMapEnd() +{ + UnhookEvent("round_start", OnRoundStart, EventHookMode_PostNoCopy); + if (!enabled) + { + return; + } + if(g_aMovingNpc) + { + for (int i = 0; i < g_aMovingNpc.Length; ++i) + { + MovingNpc npc = g_aMovingNpc.Get(i); + npc.Stop(); + npc.kill = true; + } + delete g_aMovingNpc; + } + if(g_aNpcConfigNT) + { + for (int i = 0; i < g_aNpcConfigNT.Length; ++i) + { + MovingNpcConfig NpcConf = g_aNpcConfigNT.Get(i); + NpcConf.Delete(); + } + delete g_aNpcConfigNT; + } + if(g_mNpcConfig) + { + StringMapSnapshot ms = g_mNpcConfig.Snapshot(); + char sTemplate[MAX_ENT_NAME]; + for(int i = 0; i < ms.Length; ++i) + { + ms.GetKey(i, sTemplate, sizeof(sTemplate)); + ArrayList configs; + g_mNpcConfig.GetValue(sTemplate, configs); + for(int c = 0; c < configs.Length; ++c) + { + MovingNpcConfig NpcConf = configs.Get(c); + NpcConf.Delete(); + } + delete configs; + } + delete ms; + g_mNpcConfig.Clear(); + delete g_mNpcConfig; + } +} diff --git a/VScripts/scripting/README.MD b/VScripts/scripting/README.MD new file mode 100644 index 00000000..00a7e239 --- /dev/null +++ b/VScripts/scripting/README.MD @@ -0,0 +1 @@ +moving npc requires following extension to run: https://github.com/srcdslab/sm-ext-eventqueue diff --git a/VScripts/scripting/Vscripts_Core.sp b/VScripts/scripting/Vscripts_Core.sp new file mode 100644 index 00000000..a75a41aa --- /dev/null +++ b/VScripts/scripting/Vscripts_Core.sp @@ -0,0 +1,553 @@ +#pragma semicolon 1 + +#define PLUGIN_AUTHOR "Cloud Strife" +#define PLUGIN_VERSION "1.00" + +#include +#include +#include + +#pragma newdecls required + +public Plugin myinfo = +{ + name = "Vscripts Core", + author = PLUGIN_AUTHOR, + description = "", + version = PLUGIN_VERSION, + url = "https://steamcommunity.com/id/cloudstrifeua/" +}; + +#define nullptr Address_Null + +ArrayList g_aVscriptTimers = null; + +bool g_bEventQueue = false; + +Handle g_hGetModel = null, g_hGetModelType = null; +Handle g_hTemplateCreateInstace = null; + +Address g_pModelInfo = nullptr; + +int g_iSolidType, g_iOwnerEntity, g_iCollisionGroup; +int g_iUtlVectorSize = -1; + +GlobalForward g_fwdTemplateCreateInstace; + +enum modtype_t +{ + mod_bad = 0, + mod_brush, + mod_sprite, + mod_studio +} + +enum SolidType_t +{ + SOLID_NONE = 0, // no solid model + SOLID_BSP = 1, // a BSP tree + SOLID_BBOX = 2, // an AABB + SOLID_OBB = 3, // an OBB (not implemented yet) + SOLID_OBB_YAW = 4, // an OBB, constrained so that it can only yaw + SOLID_CUSTOM = 5, // Always call into the entity for tests + SOLID_VPHYSICS = 6, // solid vphysics object, get vcollide from the model and collide with that + SOLID_LAST, +} + +enum Collision_Group_t +{ + COLLISION_GROUP_NONE = 0, + COLLISION_GROUP_DEBRIS, // Collides with nothing but world and static stuff + COLLISION_GROUP_DEBRIS_TRIGGER, // Same as debris, but hits triggers + COLLISION_GROUP_INTERACTIVE_DEBRIS, // Collides with everything except other interactive debris or debris + COLLISION_GROUP_INTERACTIVE, // Collides with everything except interactive debris or debris + COLLISION_GROUP_PLAYER, + COLLISION_GROUP_BREAKABLE_GLASS, + COLLISION_GROUP_VEHICLE, + COLLISION_GROUP_PLAYER_MOVEMENT, // For HL2, same as Collision_Group_Player, for + // TF2, this filters out other players and CBaseObjects + COLLISION_GROUP_NPC, // Generic NPC group + COLLISION_GROUP_IN_VEHICLE, // for any entity inside a vehicle + COLLISION_GROUP_WEAPON, // for any weapons that need collision detection + COLLISION_GROUP_VEHICLE_CLIP, // vehicle clip brush to restrict vehicle movement + COLLISION_GROUP_PROJECTILE, // Projectiles! + COLLISION_GROUP_DOOR_BLOCKER, // Blocks entities not permitted to get near moving doors + COLLISION_GROUP_PASSABLE_DOOR, // Doors that the player shouldn't collide with + COLLISION_GROUP_DISSOLVING, // Things that are dissolving are in this group + COLLISION_GROUP_PUSHAWAY, // Nonsolid on client and server, pushaway in player code + COLLISION_GROUP_NPC_ACTOR, // Used so NPCs in scripts ignore the player. + COLLISION_GROUP_NPC_SCRIPTED, // USed for NPCs in scripts that should not collide with each other + LAST_SHARED_COLLISION_GROUP +} + +methodmap VscriptTimer < StringMap +{ + public VscriptTimer(Handle plugin, float time, Function cb, any data) + { + StringMap myclass = new StringMap(); + float trigger_time = GetGameTime() + time; + myclass.SetValue("m_fTriggerTime", trigger_time); + PrivateForward cb_fwd = new PrivateForward(ET_Ignore, Param_Any); + cb_fwd.AddFunction(plugin, cb); + myclass.SetValue("m_hCallback", cb_fwd); + myclass.SetValue("m_Data", data); + + bool inserted = false; + for(int i = 0; i < g_aVscriptTimers.Length; ++i) + { + VscriptTimer cur = g_aVscriptTimers.Get(i); + float cur_t; + cur.GetValue("m_fTriggerTime", cur_t); + if(cur_t > trigger_time) + { + inserted = true; + g_aVscriptTimers.ShiftUp(i); + g_aVscriptTimers.Set(i, myclass); + break; + } + } + if(!inserted) + g_aVscriptTimers.Push(myclass); + + return view_as(myclass); + } + + property float time + { + public get() + { + float t; + this.GetValue("m_fTriggerTime", t); + return t; + } + } + + property any data + { + public get() + { + any data; + this.GetValue("m_Data", data); + return data; + } + } + + public void Kill() + { + PrivateForward cb_fwd; + this.GetValue("m_hCallback", cb_fwd); + delete cb_fwd; + delete this; + } + + public void Trigger() + { + PrivateForward cb_fwd; + this.GetValue("m_hCallback", cb_fwd); + any data = this.data; + delete this; + + Call_StartForward(cb_fwd); + Call_PushCell(data); + Call_Finish(); + + delete cb_fwd; + } +} + +public void OnGameFrame() +{ + for(int i = 0; i < g_aVscriptTimers.Length; ++i) + { + VscriptTimer cur = g_aVscriptTimers.Get(i); + if(cur.time <= GetGameTime()) + { + g_aVscriptTimers.Erase(i--); + cur.Trigger(); + } + else break; + } +} + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + CreateNative("Vscripts_CreateTimer", Native_CreateTimer); + CreateNative("Vscripts_IsEventQueueLoaded", Native_IsEventQueueLoaded); + CreateNative("Vscripts_TraceFilterSimple", Native_TraceFilterSimple); + RegPluginLibrary("vscripts"); + return APLRes_Success; +} + +public any Native_CreateTimer(Handle plugin, int numParams) +{ + float f = GetNativeCell(1); + new VscriptTimer(plugin, f, GetNativeFunction(2), GetNativeCell(3)); + //new VscriptTimer(plugin, f, view_as(GetNativeFunction(2)), GetNativeCell(3)); + return Plugin_Handled; +} + +public int Native_IsEventQueueLoaded(Handle plugin, int numParams) +{ + return g_bEventQueue; +} + +public void OnAllPluginsLoaded() +{ + g_bEventQueue = LibraryExists("Entity Events Queue"); +} + +//public void OnLibraryAdded(const char[] name) +//{ + //if(StrEqual(name, "Entity Events Queue")) + //{ + //g_bEventQueue = true; + //} +//} + +public void OnLibraryRemoved(const char[] name) +{ + if(StrEqual(name, "Entity Events Queue")) + { + g_bEventQueue = false; + } +} + +public void OnPluginStart() +{ + g_aVscriptTimers = new ArrayList(); + + Handle hGameData = LoadGameConfigFile("Vscripts_Core.games"); + if(!hGameData) + SetFailState("Failed to load Vscripts_Core gamedata."); + + StartPrepSDKCall(SDKCall_Entity); + if(!PrepSDKCall_SetFromConf(hGameData, SDKConf_Signature, "CBaseEntity::GetModel")) + { + delete hGameData; + SetFailState("PrepSDKCall_SetFromConf(hGameData, SDKConf_Signature, \"CBaseEntity::GetModel\") failed!"); + } + PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); + g_hGetModel = EndPrepSDKCall(); + + StartPrepSDKCall(SDKCall_Raw); + if(!PrepSDKCall_SetFromConf(hGameData, SDKConf_Signature, "CModelInfo::GetModelType")) + { + delete hGameData; + SetFailState("PrepSDKCall_SetFromConf(hGameData, SDKConf_Signature, \"CModelInfo::GetModelType\") failed!"); + } + PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + g_hGetModelType = EndPrepSDKCall(); + + g_pModelInfo = GameConfGetAddress(hGameData, "modelinfo"); + if(!g_pModelInfo) + { + delete hGameData; + SetFailState("Couldn't load modelinfo address!"); + } + + g_iUtlVectorSize = GameConfGetOffset(hGameData, "CUtlVector::m_iSize"); + if(g_iUtlVectorSize == -1) + { + delete hGameData; + SetFailState("Couldn't load CUtlVector::m_iSize offset!"); + } + + Address pCreateInstance = GameConfGetAddress(hGameData, "CPointTemplate::CreateInstance"); + if(!pCreateInstance) + { + delete hGameData; + SetFailState("Couldn't load CPointTemplate::CreateInstance address!"); + } + + g_hTemplateCreateInstace = DHookCreateDetour(pCreateInstance, CallConv_THISCALL, ReturnType_Bool, ThisPointer_CBaseEntity); + DHookAddParam(g_hTemplateCreateInstace, HookParamType_VectorPtr); + DHookAddParam(g_hTemplateCreateInstace, HookParamType_VectorPtr); + DHookAddParam(g_hTemplateCreateInstace, HookParamType_ObjectPtr); + if(!DHookEnableDetour(g_hTemplateCreateInstace, true, CPointTemplate_CreateInstance)) + { + delete hGameData; + SetFailState("Couldn't create detour on CPointTemplate::CreateInstance!"); + } + + delete hGameData; + + g_fwdTemplateCreateInstace = new GlobalForward("Vscritps_OnTemplateInstanceCreated", ET_Ignore, Param_Cell, Param_Array, Param_Cell); +} + +public MRESReturn CPointTemplate_CreateInstance(int entity, DHookReturn hReturn, DHookParam hParams) +{ + if( view_as(DHookGetReturn(hReturn)) == false ) + { + return MRES_Ignored; + } + int iSize = DHookGetParamObjectPtrVar(hParams, 3, g_iUtlVectorSize, ObjectValueType_Int); + Address pBegin = view_as
(DHookGetParamObjectPtrVar(hParams, 3, 0, ObjectValueType_Int)); + int[] createdEntities = new int[iSize]; + for(int i = 0; i < iSize; ++i) + { + createdEntities[i] = GetEntityFromAddress(view_as
(LoadFromAddress(pBegin + view_as
(i*4), NumberType_Int32))); + } + Call_StartForward(g_fwdTemplateCreateInstace); + Call_PushCell(entity); + Call_PushArray(createdEntities, iSize); + Call_PushCell(iSize); + Call_Finish(); + return MRES_Ignored; +} + +public void OnMapStart() +{ + g_iSolidType = FindDataMapInfo(0, "m_nSolidType"); + g_iOwnerEntity = FindDataMapInfo(0, "m_hOwnerEntity"); + g_iCollisionGroup = FindDataMapInfo(0, "m_CollisionGroup"); +} + +public modtype_t GetModelType(int entity) +{ + Address pModel = view_as
(SDKCall(g_hGetModel, entity)); + return view_as(SDKCall(g_hGetModelType, LoadFromAddress(g_pModelInfo, NumberType_Int32), pModel)); +} + +public bool StandardFilterRules(int entity, int contentsMask) +{ + if(!IsValidEntity(entity)) + return true; + + SolidType_t solid = view_as(GetEntData(entity, g_iSolidType)); + modtype_t model = GetModelType(entity); + + if((model != mod_brush) || (solid != SOLID_BSP && solid != SOLID_VPHYSICS)) + { + if((contentsMask & CONTENTS_MONSTER) == 0) + return false; + } + + if(!(contentsMask & CONTENTS_WINDOW) && (GetEntityRenderMode(entity) != RENDER_NORMAL)) + return false; + + if(!(contentsMask & CONTENTS_MOVEABLE) && (GetEntityMoveType(entity) == MOVETYPE_PUSH)) + return false; + + return true; +} + +public bool PassServerEntityFilter(int entity, int pass) +{ + if(!IsValidEntity(pass) || !IsValidEntity(entity)) + return true; + + if(entity == pass) + return false; + + int owner = GetEntDataEnt2(entity, g_iOwnerEntity); + if(owner == pass) + return false; + + owner = GetEntDataEnt2(pass, g_iOwnerEntity); + if(owner == entity) + return false; + + return true; +} + +public bool GameRules_ShouldCollide(Collision_Group_t collisionGroup0, Collision_Group_t collisionGroup1) +{ + if ( collisionGroup0 > collisionGroup1 ) + { + // swap so that lowest is always first + Collision_Group_t tmp = collisionGroup0; + collisionGroup0 = collisionGroup1; + collisionGroup1 = tmp; + } + + if ( (collisionGroup0 == COLLISION_GROUP_PLAYER || collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT) && + collisionGroup1 == COLLISION_GROUP_PUSHAWAY ) + { + return false; + } + + if ( collisionGroup0 == COLLISION_GROUP_DEBRIS && collisionGroup1 == COLLISION_GROUP_PUSHAWAY ) + { + // let debris and multiplayer objects collide + return true; + } + + // -------------------------------------------------------------------------- + // NOTE: All of this code assumes the collision groups have been sorted!!!! + // NOTE: Don't change their order without rewriting this code !!! + // -------------------------------------------------------------------------- + // Don't bother if either is in a vehicle... + if (( collisionGroup0 == COLLISION_GROUP_IN_VEHICLE ) || ( collisionGroup1 == COLLISION_GROUP_IN_VEHICLE )) + return false; + + if ( ( collisionGroup1 == COLLISION_GROUP_DOOR_BLOCKER ) && ( collisionGroup0 != COLLISION_GROUP_NPC ) ) + return false; + + if ( ( collisionGroup0 == COLLISION_GROUP_PLAYER ) && ( collisionGroup1 == COLLISION_GROUP_PASSABLE_DOOR ) ) + return false; + + if ( collisionGroup0 == COLLISION_GROUP_DEBRIS || collisionGroup0 == COLLISION_GROUP_DEBRIS_TRIGGER ) + { + // put exceptions here, right now this will only collide with COLLISION_GROUP_NONE + return false; + } + + // Dissolving guys only collide with COLLISION_GROUP_NONE + if ( (collisionGroup0 == COLLISION_GROUP_DISSOLVING) || (collisionGroup1 == COLLISION_GROUP_DISSOLVING) ) + { + if ( collisionGroup0 != COLLISION_GROUP_NONE ) + return false; + } + + // doesn't collide with other members of this group + // or debris, but that's handled above + if ( collisionGroup0 == COLLISION_GROUP_INTERACTIVE_DEBRIS && collisionGroup1 == COLLISION_GROUP_INTERACTIVE_DEBRIS ) + return false; + + // This change was breaking HL2DM + // Adrian: TEST! Interactive Debris doesn't collide with the player. + if ( collisionGroup0 == COLLISION_GROUP_INTERACTIVE_DEBRIS && ( collisionGroup1 == COLLISION_GROUP_PLAYER_MOVEMENT || collisionGroup1 == COLLISION_GROUP_PLAYER ) ) + return false; + + if ( collisionGroup0 == COLLISION_GROUP_BREAKABLE_GLASS && collisionGroup1 == COLLISION_GROUP_BREAKABLE_GLASS ) + return false; + + // interactive objects collide with everything except debris & interactive debris + if ( collisionGroup1 == COLLISION_GROUP_INTERACTIVE && collisionGroup0 != COLLISION_GROUP_NONE ) + return false; + + // Projectiles hit everything but debris, weapons, + other projectiles + if ( collisionGroup1 == COLLISION_GROUP_PROJECTILE ) + { + if ( collisionGroup0 == COLLISION_GROUP_DEBRIS || + collisionGroup0 == COLLISION_GROUP_WEAPON || + collisionGroup0 == COLLISION_GROUP_PROJECTILE ) + { + return false; + } + } + + // Don't let vehicles collide with weapons + // Don't let players collide with weapons... + // Don't let NPCs collide with weapons + // Weapons are triggers, too, so they should still touch because of that + if ( collisionGroup1 == COLLISION_GROUP_WEAPON ) + { + if ( collisionGroup0 == COLLISION_GROUP_VEHICLE || + collisionGroup0 == COLLISION_GROUP_PLAYER || + collisionGroup0 == COLLISION_GROUP_NPC ) + { + return false; + } + } + + // collision with vehicle clip entity?? + if ( collisionGroup0 == COLLISION_GROUP_VEHICLE_CLIP || collisionGroup1 == COLLISION_GROUP_VEHICLE_CLIP ) + { + // yes then if it's a vehicle, collide, otherwise no collision + // vehicle sorts lower than vehicle clip, so must be in 0 + if ( collisionGroup0 == COLLISION_GROUP_VEHICLE ) + return true; + // vehicle clip against non-vehicle, no collision + return false; + } + + return true; +} + +public bool ShouldCollide(int entity, Collision_Group_t collisionGroup, int contentsMask) +{ + Collision_Group_t m_collisionGroup = view_as(GetEntData(entity, g_iCollisionGroup)); + if(m_collisionGroup == COLLISION_GROUP_DEBRIS) + { + if(!(contentsMask & CONTENTS_DEBRIS)) + return false; + } + return true; +} + +public int Native_TraceFilterSimple(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + int contentsMask = GetNativeCell(2); + int ignore = GetNativeCell(3); + + if(!IsValidEntity(entity)) + return false; + + if(!StandardFilterRules(entity, contentsMask)) + return false; + + if(ignore != -1) + { + if(!PassServerEntityFilter(entity, ignore)) + return false; + } + + if(!ShouldCollide(entity, COLLISION_GROUP_NONE, contentsMask)) + return false; + if(!GameRules_ShouldCollide(COLLISION_GROUP_NONE, view_as(GetEntData(entity, g_iCollisionGroup)))) + return false; + + return true; +} + +public void OnMapEnd() +{ + for(int i = 0; i < g_aVscriptTimers.Length; ++i) + { + VscriptTimer cur = g_aVscriptTimers.Get(i); + cur.Kill(); + } + g_aVscriptTimers.Clear(); +} + +//Copied from https://github.com/nosoop/stocksoup/blob/master/memory.inc +/** + * Retrieves an entity index from a raw entity handle address. + * + * Note that SourceMod's entity conversion routine is an implementation detail that may change. + * + * @param addr Address to a memory location. + * @return Entity index, or -1 if not valid. + */ +stock int LoadEntityHandleFromAddress(Address addr) { + return EntRefToEntIndex(LoadFromAddress(addr, NumberType_Int32) | (1 << 31)); +} + +/** + * Returns an entity index from its address by attempting to read the + * CBaseEntity::m_RefEHandle member. This assumes the address of a CBaseEntity is + * passed in. + * + * @param pEntity Address of an entity. + * @return Entity index, or -1 if not valid. + */ +stock int GetEntityFromAddress(Address pEntity) { + static int offs_RefEHandle; + if (offs_RefEHandle) { + return LoadEntityHandleFromAddress(pEntity + view_as
(offs_RefEHandle)); + } + + // if we don't have it already, attempt to lookup offset based on SDK information + // CWorld is derived from CBaseEntity so it should have both offsets + int offs_angRotation = FindDataMapInfo(0, "m_angRotation"), + offs_vecViewOffset = FindDataMapInfo(0, "m_vecViewOffset"); + if (offs_angRotation == -1) { + ThrowError("Could not find offset for ((CBaseEntity) CWorld)::m_angRotation"); + } else if (offs_vecViewOffset == -1) { + ThrowError("Could not find offset for ((CBaseEntity) CWorld)::m_vecViewOffset"); + } else if ((offs_angRotation + 0x0C) != (offs_vecViewOffset - 0x04)) { + char game[32]; + GetGameFolderName(game, sizeof(game)); + ThrowError("Could not confirm offset of CBaseEntity::m_RefEHandle " + ... "(incorrect assumption for game '%s'?)", game); + } + + // offset seems right, cache it for the next call + offs_RefEHandle = offs_angRotation + 0x0C; + return GetEntityFromAddress(pEntity); +} + + diff --git a/VScripts/scripting/include/MovingNPC.inc b/VScripts/scripting/include/MovingNPC.inc new file mode 100644 index 00000000..9a6c10fa --- /dev/null +++ b/VScripts/scripting/include/MovingNPC.inc @@ -0,0 +1,331 @@ +#if defined _MovingNPC_included + #endinput +#endif +#define _MovingNPC_included +#include +//#include +#include +#include + +stock float operator%(float oper1, float oper2) +{ + return FloatMod(oper1, oper2); +} + +public bool IsValidPlayer(int player) +{ + return player >= 1 && player <= MAXPLAYERS && IsValidEntity(player) && IsPlayerAlive(player); +} + +public float GetDistance(const float v1[3], const float v2[3]) +{ + return SquareRoot((v1[0] - v2[0]) * (v1[0] - v2[0]) + (v1[1] - v2[1]) * (v1[1] - v2[1]) + (v1[2] - v2[2]) * (v1[2] - v2[2])); +} + +methodmap MovingNpc < Basic +{ + public MovingNpc(int entity, float tickrate = 0.1, float distance = 5000.0, float retarget = 7.5, float forward_factor = 1.0, float turning_factor = 0.5, float lifetime = 0.0) + { + Basic myclass = new Basic(); + myclass.SetFloat("fRate", tickrate); + myclass.SetFloat("fDistance", distance); + myclass.SetFloat("fRetarget", retarget); + myclass.SetFloat("fForward", forward_factor); + myclass.SetFloat("fTurning", turning_factor); + myclass.SetFloat("fLifetime", lifetime); + myclass.SetInt("iEntity", entity); + myclass.SetInt("iTarget", -1); + myclass.SetInt("iTf", -1); + myclass.SetInt("iTs", -1); + myclass.SetFloat("fTtime", 0.0); + myclass.SetBool("bTicking", false); + myclass.SetHandle("hLifeTimer", null); + myclass.SetBool("bKill", false); + return view_as(myclass); + } + property bool kill + { + public get() + { + return this.GetBool("bKill"); + } + public set(bool val) + { + this.SetBool("bKill", val); + } + } + property Handle lifetimer + { + public get() + { + return this.GetHandle("hLifeTimer"); + } + public set(Handle val) + { + this.SetHandle("hLifeTimer", val); + } + } + property float lifetime + { + public get() + { + return this.GetFloat("fLifetime"); + } + public set(float val) + { + this.SetFloat("fLifetime", val); + } + } + property float rate + { + public get() + { + return this.GetFloat("fRate"); + } + public set(float val) + { + this.SetFloat("fRate", val); + } + } + property float distance + { + public get() + { + return this.GetFloat("fDistance"); + } + public set(float val) + { + this.SetFloat("fDistance", val); + } + } + property float retarget + { + public get() + { + return this.GetFloat("fRetarget"); + } + public set(float val) + { + this.SetFloat("fRetarget", val); + } + } + property float forward_factor + { + public get() + { + return this.GetFloat("fForward"); + } + public set(float val) + { + this.SetFloat("fForward", val); + } + } + property float turning_factor + { + public get() + { + return this.GetFloat("fTurning"); + } + public set(float val) + { + this.SetFloat("fTurning", val); + } + } + property int entity + { + public get() + { + return this.GetInt("iEntity"); + } + public set(int val) + { + this.SetInt("iEntity", val); + } + } + property int target + { + public get() + { + return this.GetInt("iTarget"); + } + public set(int val) + { + this.SetInt("iTarget", val); + } + } + property int tf + { + public get() + { + return this.GetInt("iTf"); + } + public set(int val) + { + this.SetInt("iTf", val); + } + } + property int ts + { + public get() + { + return this.GetInt("iTs"); + } + public set(int val) + { + this.SetInt("iTs", val); + } + } + property float ttime + { + public get() + { + return this.GetFloat("fTtime"); + } + public set(float val) + { + this.SetFloat("fTtime", val); + } + } + property bool ticking + { + public get() + { + return this.GetBool("bTicking"); + } + public set(bool val) + { + this.SetBool("bTicking", val); + } + } + + public void Start() + { + if(!this.ticking) + { + this.ticking = true; + Vscripts_CreateTimer(this.rate, Tick_Cb, this); + } + } + + public void Stop() + { + if(this.ticking) + { + this.ticking = false; + } + } + + public float GetTargetYaw(const float start[3], const float target[3]) + { + float yaw = 0.00; + float v[3]; + SubtractVectors(start, target, v); + float vl = SquareRoot(v[0] * v[0] + v[1] * v[1]); + yaw = 180.0 * ArcCosine(v[0] / vl) / 3.14159; + if (v[1] < 0.0) + yaw = -yaw; + return yaw; + } + + public void SetThruster(bool fwd, int caller) + { + if(fwd) + this.tf = caller; + else + this.ts = caller; + } + + public void SearchTarget() + { + this.ttime = 0.00; + this.target = -1; + int h = -1; + ArrayList candidates = new ArrayList(); + float orig[3]; + Vscripts_GetOrigin(this.entity, orig); + while (-1 != (h = Vscripts_FindEntityByClassnameWithin(h, "player", orig, this.distance))) + { + //check if target is a valid player + CT team(3) + health above 0 (not dead) + if (GetClientTeam(h) == 3 && IsPlayerAlive(h)) + { + //check if the target is in sight of the npc (this physbox origin+48 height) + float t_orig[3]; + Vscripts_GetOrigin(this.entity, orig); + orig[2] += 40.0; + Vscripts_GetOrigin(h, t_orig); + t_orig[2] += 48.0; + if (Vscripts_TraceLine(orig, t_orig, this.entity) == 1.00) + candidates.Push(h); //if everything required is OK, add the target to the list of candidates + } + } + if(candidates.Length == 0) + { + delete candidates; + return; + } + this.target = candidates.Get(GetRandomInt(0, candidates.Length - 1)); + + delete candidates; + } + + public void Tick() + { + Vscripts_EntFireByIndex(this.tf, "Deactivate", "", 0.0, -1); + Vscripts_EntFireByIndex(this.ts, "Deactivate", "", 0.0, -1); + if (!IsValidPlayer(this.target) || GetClientTeam(this.target) != 3 || this.ttime >= this.retarget) + { + this.SearchTarget(); + } + this.ttime+=this.rate; + Vscripts_EntFireByIndex(this.tf, "Activate", "", 0.02, -1); + Vscripts_EntFireByIndex(this.ts, "Activate", "", 0.02, -1); + if(!IsValidPlayer(this.target)) + { + Vscripts_CreateTimer(this.rate, Tick_Cb, this); + return; + } + float angl[3], s_orig[3], t_orig[3]; + Vscripts_GetAngles(this.entity, angl); + Vscripts_GetOrigin(this.entity, s_orig); + Vscripts_GetOrigin(this.target, t_orig); + float sa = angl[1]; + float ta = this.GetTargetYaw(s_orig, t_orig); + float ang = FloatAbs((sa - ta + 360.0) % 360.0); + if (ang >= 180.0) + Vscripts_EntFireByIndex(this.ts, "AddOutput", "angles 0 270 0", 0.0, -1); + else + Vscripts_EntFireByIndex(this.ts, "AddOutput", "angles 0 90 0", 0.0, -1); + float angdif = (sa - ta - 180.0); + while (angdif > 360.0) { angdif -= 180.0; } + while (angdif < -180.0) { angdif += 360.0; } + angdif = FloatAbs(angdif); + char input[MAX_INPUT_NAME]; + Format(input, sizeof(input), "force %.4f", 3000.0 * this.forward_factor); + Vscripts_EntFireByIndex(this.tf, "AddOutput", input, 0.0, -1); + Format(input, sizeof(input), "force %.4f", (3.0 * this.turning_factor) * angdif); + Vscripts_EntFireByIndex(this.ts, "AddOutput", input, 0.0, -1); + Vscripts_CreateTimer(this.rate, Tick_Cb, this); + } +} + +public void Tick_Cb(MovingNpc npc) +{ + if(npc.kill) + { + if(npc.lifetimer) + KillTimer(npc.lifetimer); + delete npc; + return; + } + + if(npc.ticking) + { + npc.Tick(); + } + else + { + Vscripts_EntFireByIndex(npc.tf, "Deactivate", "", 0.0, -1); + Vscripts_EntFireByIndex(npc.ts, "Deactivate", "", 0.0, -1); + } +} + diff --git a/VScripts/scripting/include/vscripts_moving_npc.inc b/VScripts/scripting/include/vscripts_moving_npc.inc new file mode 100644 index 00000000..3f18d756 --- /dev/null +++ b/VScripts/scripting/include/vscripts_moving_npc.inc @@ -0,0 +1,193 @@ +/* +** +*/ +#if defined _VSCRIPTS_included + #endinput +#endif +#define _VSCRIPTS_included +#include +#undef REQUIRE_EXTENSIONS +#include +#define REQUIRE_EXTENSIONS + +#define MAX_ENT_NAME 32 +#define MAX_INPUT_NAME 32 + +typedef VscriptTimerCallback = function void(any data); + +native void Vscripts_CreateTimer(float interval, VscriptTimerCallback func, any data = INVALID_HANDLE); +native bool Vscripts_IsEventQueueLoaded(); +native bool Vscripts_TraceFilterSimple(int entity, int contentsMask, int ignore = -1); + +forward void Vscritps_OnTemplateInstanceCreated(int template, const int[] createdEntities, int size); + +stock int Vscripts_GetEntityIndexByHammerID(int HammerID, const char[] classname = "*", int startEnt = -1) +{ + while((startEnt = FindEntityByClassname(startEnt,classname))!= -1) { + if (GetEntProp(startEnt, Prop_Data, "m_iHammerID") == HammerID) + return startEnt; + } + return -1; +} + +stock int Vscripts_GetEntityIndexByName(const char[] name, const char[] classname = "*", int startEnt = -1) +{ + char buffer[MAX_ENT_NAME]; + int ch = FindCharInString(name, '*', true); + while((startEnt = FindEntityByClassname(startEnt, classname))!= -1){ + GetEntityClassname(startEnt, buffer, sizeof(buffer)); + if (strcmp(name, buffer) == 0) + return startEnt; + + GetEntPropString(startEnt, Prop_Data, "m_iName", buffer, sizeof(buffer)); + if(strncmp(name, buffer, ch) == 0) + return startEnt; + } + return -1; +} + +public int Vscripts_FindEntityByClassnameWithin(int startEnt, const char[] classname, const float origin[3], const float radius) +{ + float torigin[3]; + while((startEnt = FindEntityByClassname(startEnt,classname))!= -1){ + Vscripts_GetOrigin(startEnt, torigin); + if(GetVectorDistance(torigin, origin) <= radius) return startEnt; + } + return -1; +} + +public float Vscripts_TraceLine(const float origin[3], const float v2[3], int entity) +{ + TR_TraceRayFilter(origin, v2, MASK_NPCWORLDSTATIC, RayType_EndPoint, TraceEntityFilterSelf, entity); + float fraction_left = TR_GetFractionLeftSolid(INVALID_HANDLE), fraction = 0.0; + if (fraction_left && TR_StartSolid(INVALID_HANDLE)) { + fraction = 1.0 - fraction_left; + } + else { + fraction = TR_GetFraction(INVALID_HANDLE); + } + return fraction; +} + +bool TraceEntityFilterSelf(int entity, int contentsMask, int data) +{ + return Vscripts_TraceFilterSimple(entity, contentsMask, data); +} + +public void Vscripts_GetOrigin(int entity, float buffer[3]) +{ + GetEntPropVector(entity, Prop_Send, "m_vecOrigin", buffer); +} + +public void Vscripts_GetAngles(int entity, float buffer[3]) +{ + GetEntPropVector(entity, Prop_Send, "m_angRotation", buffer); +} + +public void Vscripts_SetAngles(int entity, const float buffer[3]) +{ + TeleportEntity(entity, NULL_VECTOR, buffer, NULL_VECTOR); +} + +public void Vscripts_SetOrigin(int entity, const float buffer[3]) +{ + TeleportEntity(entity, buffer, NULL_VECTOR, NULL_VECTOR); +} + +public void Vscripts_GetForwardVector(int entity, float buffer[3]) +{ + float tmp[3]; + Vscripts_GetAngles(entity, tmp); + GetAngleVectors(tmp, buffer, NULL_VECTOR, NULL_VECTOR); +} + +public void Vscripts_SetForwardVector(int entity, const float buffer[3]) +{ + float tmp[3]; + GetVectorAngles(buffer, tmp); + Vscripts_SetAngles(entity, tmp); +} + +stock void Vscripts_EntFire(const char[] target, const char[] input, const char[] parametr, float delay, int activator = -1) +{ + if(Vscripts_IsEventQueueLoaded()) + EQ_AddEventByName(target, input, parametr, delay, activator); + else + { + DataPack data = CreateDataPack(); + data.WriteString(input); + data.WriteString(parametr); + data.WriteCell(activator >= 0 ? EntIndexToEntRef(activator) : -1); + data.WriteCell(true); + data.WriteString(target); + Vscripts_CreateTimer(delay, InputDelay, data); + } +} + +stock void Vscripts_EntFireByIndex(int target, const char[] input, const char[] parametr, float delay, int activator = -1) +{ + if(Vscripts_IsEventQueueLoaded()) + EQ_AddEvent(target, input, parametr, delay, activator); + else + { + DataPack data = CreateDataPack(); + data.WriteString(input); + data.WriteString(parametr); + data.WriteCell(activator >= 0 ? EntIndexToEntRef(activator) : -1); + data.WriteCell(false); + data.WriteCell(EntIndexToEntRef(target)); + Vscripts_CreateTimer(delay, InputDelay, data); + } +} + +void InputDelay(DataPack data) +{ + char input[MAX_INPUT_NAME], parametr[2*MAX_ENT_NAME]; + data.Reset(); + data.ReadString(input, sizeof(input)); + data.ReadString(parametr, sizeof(parametr)); + int activator = data.ReadCell(); + int target = -1; + if(view_as(data.ReadCell()) == true) + { + char name[MAX_ENT_NAME]; + data.ReadString(name, sizeof(name)); + while((target = Vscripts_GetEntityIndexByName(name, _, target)) != -1) + { + SetVariantString(parametr); + AcceptEntityInput(target, input, activator != -1 ? EntRefToEntIndex(activator) : -1); + } + } + else + { + target = EntRefToEntIndex(data.ReadCell()); + if(IsValidEntity(target)) + { + SetVariantString(parametr); + AcceptEntityInput(target, input, activator != -1 ? EntRefToEntIndex(activator) : -1); + } + } + data.Reset(true); + delete data; +} + +public SharedPlugin __pl_vscripts= +{ + name = "vscripts", + file = "Vscripts_Core.smx", +#if defined REQUIRE_PLUGIN + required = 1 +#else + required = 0 +#endif +}; + +#if !defined REQUIRE_PLUGIN +public void __pl_vscripts_SetNTVOptional() +{ + MarkNativeAsOptional("Vscripts_CreateTimer"); + MarkNativeAsOptional("Vscripts_IsEventQueueLoaded"); + MarkNativeAsOptional("Vscripts_TraceFilterSimple"); +} +#endif +