diff --git a/README.md b/README.md new file mode 100644 index 0000000..0c4e4d7 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +## πŸ› οΈ Modding Kit πŸ‡ΊπŸ‡¦ + +### πŸ“„ About +__CS Modding Kit__ is a powerful solutions for Amx Mod X modding that can be used to create complex mods for a short time with flexible solutions using a unified development approach. I continue working on this repository and plan many new solutions in the future as well as move my mods to this solution to create examples and a good code base. Focus on game logic instead of crutches for native stuff. + +### ❔Why Modding Kit? +- Powerful, rasy to use and flexible game systems +- Unified system easy to maintain +- Cross-game development (you can create mods that will work with both **Counter-Strike** and **Half-Life**) + +### πŸ”½ Download latest: +- [Releases](./releases) + +### πŸ”„ Requirements +- Amx Mod X 1.9+ +- RegameDLL + ReAPI (for some APIs) + +### βš™οΈ Available APIs +- [🫸 Advanced Pushing](./api/advanced-pushing) +- [🧸 Custom Entities](./api/custom-entities) +- [πŸ”„ Custom Events](./api/custom-events) +- [πŸ”« Custom Weapons](./api/custom-weapons) +- [🎯 NavSystem](./api/navsystem) +- [πŸ’« Particles](./api/particles) +- [πŸŽ₯ Player Camera](./api/player-camera) +- [🎩 Player Cosmetics](./api/player-cosmetics) +- [πŸ₯΄ Player Dizziness](./api/player-dizziness) +- [πŸƒβ€β™‚οΈ Player Effects](./api/player-effects) +- [πŸŽ’ Player Inventory](./api/player-inventory) +- [🦸 Player Model](./api/player-model) +- [πŸ‘οΈ Player Viewrange](./api/player-viewrange) +- [⏱️ Rounds](./api/rounds) +- [❓ Waypoint Markers](./api/waypoint-markers) diff --git a/api/advanced-pushing/api_advanced_pushing.sma b/api/advanced-pushing/api_advanced_pushing.sma new file mode 100644 index 0000000..ce1ac0c --- /dev/null +++ b/api/advanced-pushing/api_advanced_pushing.sma @@ -0,0 +1,236 @@ +#pragma semicolon 1 + +#include +#include +#include +#include + +#include + +#define PLAYER_PREVENT_CLIMB (1<<5) + +#define IS_PLAYER(%1) (%1 >= 1 && %1 <= MaxClients) + +new g_fwPush; + +new Float:g_flPlayerReleaseClimbBlock[MAX_PLAYERS + 1]; + +public plugin_init() { + register_plugin("[API] Advanced Pushing", "1.0.0", "Hedgehog Fog"); + + RegisterHamPlayer(Ham_Spawn, "HamHook_Player_Spawn_Post", .Post = 1); + RegisterHamPlayer(Ham_Player_PostThink, "HamHook_Player_PostThink_Post", .Post = 1); + + g_fwPush = CreateMultiForward("APS_Fw_Push", ET_IGNORE, FP_CELL, FP_ARRAY, FP_CELL); +} + +public plugin_natives() { + register_library("api_advanced_pushing"); + register_native("APS_Push", "Native_Push"); + register_native("APS_PushFromOrigin", "Native_PushFromOrigin"); + register_native("APS_PushFromBBox", "Native_PushFromBBox"); +} + +public Native_Push(iPluginId, iArgc) { + static pEntity; pEntity = get_param(1); + static Float:vecForce[3]; get_array_f(2, vecForce, sizeof(vecForce)); + static APS_Flags:iFlags; iFlags = APS_Flags:get_param(3); + @Base_Push(pEntity, vecForce, iFlags); +} + +public Native_PushFromOrigin(iPluginId, iArgc) { + static pEntity; pEntity = get_param(1); + static Float:flForce; flForce = get_param_f(2); + static Float:vecPushOrigin[3]; get_array_f(3, vecPushOrigin, sizeof(vecPushOrigin)); + static APS_Flags:iFlags; iFlags = APS_Flags:get_param(4); + + @Base_PushFromOrigin(pEntity, flForce, vecPushOrigin, iFlags); +} + +public Native_PushFromBBox(iPluginId, iArgc) { + static pEntity; pEntity = get_param(1); + static Float:flForce; flForce = get_param_f(2); + static Float:vecAbsMin[3]; get_array_f(3, vecAbsMin, sizeof(vecAbsMin)); + static Float:vecAbsMax[3]; get_array_f(4, vecAbsMax, sizeof(vecAbsMax)); + static Float:flMinDepthRatio; flMinDepthRatio = get_param_f(5); + static Float:flMaxDepthRatio; flMaxDepthRatio = get_param_f(6); + static Float:flDepthInfluenceMin; flDepthInfluenceMin = get_param_f(7); + static Float:flDepthInfluenceMax; flDepthInfluenceMax = get_param_f(8); + static APS_Flags:iFlags; iFlags = APS_Flags:get_param(9); + + @Base_PushFromBBox(pEntity, flForce, vecAbsMin, vecAbsMax, flMinDepthRatio, flMaxDepthRatio, flDepthInfluenceMin, flDepthInfluenceMax, iFlags); +} + +public HamHook_Player_Spawn_Post(pPlayer) { + @Player_ReleaseClimbPrevention(pPlayer); +} + +public HamHook_Player_PostThink_Post(pPlayer) { + if (g_flPlayerReleaseClimbBlock[pPlayer] && g_flPlayerReleaseClimbBlock[pPlayer] <= get_gametime()) { + @Player_ReleaseClimbPrevention(pPlayer); + } +} + +@Player_SetClimbPrevention(pPlayer, bool:bValue) { + new iPlayerFlags = pev(pPlayer, pev_iuser3); + + if (bValue) { + iPlayerFlags |= PLAYER_PREVENT_CLIMB; + } else { + iPlayerFlags &= ~PLAYER_PREVENT_CLIMB; + } + + set_pev(pPlayer, pev_iuser3, iPlayerFlags); +} + +@Player_ReleaseClimbPrevention(this) { + if (g_flPlayerReleaseClimbBlock[this]) { + @Player_SetClimbPrevention(this, false); + g_flPlayerReleaseClimbBlock[this] = 0.0; + } +} + +@Base_Push(this, const Float:vecForce[3], APS_Flags:iFlags) { + static Float:vecVelocity[3]; pev(this, pev_velocity, vecVelocity); + + if (iFlags & APS_Flag_AddForce) { + xs_vec_add(vecVelocity, vecForce, vecVelocity); + } else { + for (new i = 0; i < 3; ++i) { + if (iFlags & APS_Flag_OverlapMode) { + vecVelocity[i] = vecForce[i] ? vecForce[i] : vecVelocity[i]; + } else { + vecVelocity[i] = vecForce[i]; + } + } + } + + static ivecForce; ivecForce = PrepareArray(any:vecForce, 3, 0); + ExecuteForward(g_fwPush, _, this, ivecForce, iFlags); + + set_pev(this, pev_velocity, vecVelocity); + + if (IS_PLAYER(this) && ~pev(this, pev_iuser3) & PLAYER_PREVENT_CLIMB) { + @Player_SetClimbPrevention(this, true); + g_flPlayerReleaseClimbBlock[this] = get_gametime() + 0.1; + } +} + +@Base_PushFromOrigin(this, Float:flForce, Float:vecPushOrigin[3], APS_Flags:iFlags) { + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + + static Float:vecForce[3]; + xs_vec_sub(vecOrigin, vecPushOrigin, vecForce); + xs_vec_normalize(vecForce, vecForce); + xs_vec_mul_scalar(vecForce, flForce, vecForce); + + @Base_Push(this, vecForce, iFlags); +} + +@Base_PushFromBBox( + this, + Float:flForce, + const Float:vecAbsMin[3], + const Float:vecAbsMax[3], + Float:flMinDepthRatio, + Float:flMaxDepthRatio, + Float:flDepthInfluenceMin, + Float:flDepthInfluenceMax, + APS_Flags:iFlags +) { + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + static Float:vecToucherAbsMin[3]; pev(this, pev_absmin, vecToucherAbsMin); + static Float:vecToucherAbsMax[3]; pev(this, pev_absmax, vecToucherAbsMax); + + // Find and check intersection point + for (new iAxis = 0; iAxis < 3; ++iAxis) { + if (vecOrigin[iAxis] < vecAbsMin[iAxis]) { + vecOrigin[iAxis] = vecToucherAbsMax[iAxis]; + } else if (vecOrigin[iAxis] > vecAbsMax[iAxis]) { + vecOrigin[iAxis] = vecToucherAbsMin[iAxis]; + } + + if (vecAbsMin[iAxis] >= vecOrigin[iAxis]) return; + if (vecAbsMax[iAxis] <= vecOrigin[iAxis]) return; + } + + new iClosestAxis = -1; + new pTrace = create_tr2(); + static Float:vecOffset[3]; xs_vec_copy(Float:{0.0, 0.0, 0.0}, vecOffset); + + for (new iAxis = 0; iAxis < 3; ++iAxis) { + // Calculates the toucher's offset relative to the current axis + static Float:flSideOffsets[2]; + flSideOffsets[0] = vecAbsMin[iAxis] - vecOrigin[iAxis]; + flSideOffsets[1] = vecAbsMax[iAxis] - vecOrigin[iAxis]; + + if (iAxis == 2 && iClosestAxis != -1) { + break; + } + + for (new side = 0; side < 2; ++side) { + // Check exit from current side + static Float:vecTarget[3]; + xs_vec_copy(vecOrigin, vecTarget); + vecTarget[iAxis] += flSideOffsets[side]; + engfunc(EngFunc_TraceMonsterHull, this, vecOrigin, vecTarget, IGNORE_MONSTERS | IGNORE_GLASS, this, pTrace); + + static Float:flFraction; + get_tr2(pTrace, TR_flFraction, flFraction); + + // No exit, cannot push this way + if (flFraction != 1.0) { + flSideOffsets[side] = 0.0; + } + + if (iAxis != 2) { + // Save minimum offset, but ignore zero offsets + if (!vecOffset[iAxis] || (flSideOffsets[side] && floatabs(flSideOffsets[side]) < floatabs(vecOffset[iAxis]))) { + vecOffset[iAxis] = flSideOffsets[side]; + } + } else { + // Priority on bottom side + if (flSideOffsets[0]) { + vecOffset[iAxis] = flSideOffsets[0]; + } + } + + // Find closest axis to push + if (vecOffset[iAxis]) { + if (iClosestAxis == -1 || floatabs(vecOffset[iAxis]) < floatabs(vecOffset[iClosestAxis])) { + iClosestAxis = iAxis; + } + } + } + } + + free_tr2(pTrace); + + // Push by closest axis + if (iClosestAxis == -1) return; + + static iPushDir; iPushDir = vecOffset[iClosestAxis] > 0.0 ? 1 : -1; + static Float:vecSize[3]; xs_vec_sub(vecAbsMax, vecAbsMin, vecSize); + static Float:flDepthRatio; flDepthRatio = floatabs(vecOffset[iClosestAxis]) / (vecSize[iClosestAxis] / 2); + + flDepthRatio = floatclamp(flDepthRatio, flMinDepthRatio, flMaxDepthRatio); + + static Float:vecForce[3]; xs_vec_copy(Float:{0.0, 0.0, 0.0}, vecForce); + + static bool:bInInfluence; bInInfluence = ( + flDepthRatio >= flDepthInfluenceMin && + flDepthRatio <= flDepthInfluenceMax + ); + + if (bInInfluence) { + vecForce[iClosestAxis] = flForce * flDepthRatio * iPushDir; + } else { + vecForce[iClosestAxis] = flForce * iPushDir; + + if (iFlags & APS_Flag_AddForceInfluenceMode) { + iFlags &= ~APS_Flag_AddForce; + } + } + + @Base_Push(this, vecForce, iFlags); +} diff --git a/api/advanced-pushing/include/api_advanced_pushing.inc b/api/advanced-pushing/include/api_advanced_pushing.inc new file mode 100644 index 0000000..58ea0c9 --- /dev/null +++ b/api/advanced-pushing/include/api_advanced_pushing.inc @@ -0,0 +1,65 @@ +#if defined _api_advanced_pushing_included + #endinput +#endif +#define _api_advanced_pushing_included + +#pragma reqlib api_advanced_pushing + +enum APS_Flags (<<=1) { + APS_Flag_None, + APS_Flag_AddForce = 1, + APS_Flag_AddForceInfluenceMode, + APS_Flag_OverlapMode +}; + +/** + * Pushes the entity using the given force vector. + * + * @param pEntity The entity to push. + * @param vecForce The force vector to apply. + * @param iFlags The flags for the push operation. + * + * @noreturn + */ +native APS_Push(pEntity, const Float:vecForce[3], APS_Flags:iFlags = APS_Flag_None); + +/** + * Pushes the entity from a specified origin with a force magnitude. + * + * @param pEntity The entity to push. + * @param flForce The force magnitude to apply. + * @param vecOrigin The origin point of the push. + * @param iFlags The flags for the push operation. + * + * @noreturn + */ +native APS_PushFromOrigin(pEntity, Float:flForce, Float:vecOrigin[3], APS_Flags:iFlags = APS_Flag_None); + +/** + * Pushes the entity using a bounding box as the toucher. + * + * @param pEntity The entity to push. + * @param flForce The force magnitude to apply. + * @param vecAbsMin The absolute minimum point of the bounding box. + * @param vecAbsMax The absolute maximum point of the bounding box. + * @param flMinDepthRatio The minimum depth ratio for depth influence. + * @param flMaxDepthRatio The maximum depth ratio for depth influence. + * @param flDepthInfluenceMin The minimum depth influence. + * @param flDepthInfluenceMax The maximum depth influence. + * @param iFlags The flags for the push operation. + * + * @noreturn + */ +native APS_PushFromBBox( + pEntity, + Float:flForce, + const Float:vecAbsMin[3], + const Float:vecAbsMax[3], + Float:flMinDepthRatio = 0.0, + Float:flMaxDepthRatio = 1.0, + Float:flDepthInfluenceMin = 0.0, + Float:flDepthInfluenceMax = 1.0, + APS_Flags:iFlags = APS_Flag_None +); + +forward APS_Fw_Push(pEntity, const Float:vecForce[3], APS_Flags:iFlags); diff --git a/api/custom-entities/README.md b/api/custom-entities/README.md new file mode 100644 index 0000000..2bbb7e9 --- /dev/null +++ b/api/custom-entities/README.md @@ -0,0 +1,225 @@ +# Custom Entities API + +The Custom Entities API provides a flexible framework for managing and creating custom entities. This API allows developers to register, spawn, manipulate, and interact with custom entities, defining their behavior through hooks and methods. + +## API Functions + +### CE_Register + +Register a new custom entity. + +```pawn +native CE:CE_Register(const szName[], CEPreset:iPreset = CEPreset_None); +``` + +### CE_RegisterDerived + +Extend an existing custom entity. + +```pawn +native CE:CE_RegisterDerived(const szName[], const szBase[]); +``` + +### CE_Create + +Spawn a custom entity. + +```pawn +native CE_Create(const szName[], const Float:vecOrigin[3] = {0.0, 0.0, 0.0}, bool:bTemp = true); +``` + +### CE_Restart + +Restart a custom entity. + +```pawn +native bool:CE_Restart(pEntity); +``` + +### CE_Kill + +Kill a custom entity. + +```pawn +native bool:CE_Kill(pEntity, pKiller = 0); +``` + +### CE_Remove + +Remove a custom entity correctly. + +```pawn +native bool:CE_Remove(pEntity); +``` + +### CE_RegisterHook + +Register a new hook for a custom entity. + +```pawn +native CE_RegisterHook(const szName[], CEFunction:function, const szCallback[]); +``` + +### CE_RegisterMethod + +Register a new method for a custom entity. + +```pawn +native CE_RegisterMethod(const szName[], const szMethod[], const szCallback[], any:...); +``` + +### CE_RegisterVirtualMethod + +Register a new virtual method for a custom entity. + +```pawn +native CE_RegisterVirtualMethod(const szName[], const szMethod[], const szCallback[], any:...); +``` + +### CE_GetHandler + +Get the handler of an entity by name. + +```pawn +native CE:CE_GetHandler(const szName[]); +``` + +### CE_GetHandlerByEntity + +Get the handler of an entity by index. + +```pawn +native CE:CE_GetHandlerByEntity(pEntity); +``` + +### CE_IsInstanceOf + +Check if an entity is an instance of a specific custom entity. + +```pawn +native bool:CE_IsInstanceOf(pEntity, const szTargetName[]); +``` + +### CE_HasMember + +Check if an entity has a member. + +```pawn +native bool:CE_HasMember(pEntity, const szMember[]); +``` + +### CE_DeleteMember + +Delete a member of an entity. + +```pawn +native CE_DeleteMember(pEntity, const szMember[]); +``` + +### CE_GetMember + +Get a member of an entity. + +```pawn +native any:CE_GetMember(pEntity, const szMember[]); +``` + +### CE_SetMember + +Set a member of an entity. + +```pawn +native CE_SetMember(pEntity, const szMember[], any:value); +``` + +### CE_GetMemberVec + +Get a vector member of an entity. + +```pawn +native bool:CE_GetMemberVec(pEntity, const szMember[], Float:vecOut[3]); +``` + +### CE_SetMemberVec + +Set a vector member of an entity. + +```pawn +native CE_SetMemberVec(pEntity, const szMember[], const Float:vecValue[3]); +``` + +### CE_GetMemberString + +Get a string member of an entity. + +```pawn +native bool:CE_GetMemberString(pEntity, const szMember[], szOut[], iLen); +``` + +### CE_SetMemberString + +Set a string member of an entity. + +```pawn +native CE_SetMemberString(pEntity, const szMember[], const szValue[]); +``` + +### CE_CallMethod + +Call a method for an entity. + +```pawn +native any:CE_CallMethod(pEntity, const szMethod[], any:...); +``` + +### CE_CallBaseMethod + +Call a base method for an entity. + +```pawn +native any:CE_CallBaseMethod(any:...); +``` + + +## Constants + +### Base Class and Entity Secret + +- **CE_BASE_CLASSNAME**: Base classname for custom entities, typically set to "info_target." +- **CE_ENTITY_SECRET**: A constant identifier ('c'+'e'+'2') used internally for entity verification. + +### Maximum Lengths + +- **CE_MAX_NAME_LENGTH**: Maximum length for the name of an entity. +- **CE_MAX_MEMBER_LENGTH**: Maximum length for a member name. +- **CE_MAX_CALLBACK_LENGTH**: Maximum length for a callback name. +- **CE_MAX_METHOD_NAME_LENGTH**: Maximum length for a method name. + +### Entity Members + +Defines member names commonly used in entities: + +- **CE_MEMBER_ID**: Identifier member. +- **CE_MEMBER_POINTER**: Pointer member. +- **CE_MEMBER_WORLD**: World member. +- **CE_MEMBER_ORIGIN**: Origin member. +- **CE_MEMBER_ANGLES**: Angles member. +- **CE_MEMBER_MASTER**: Master member. +- **CE_MEMBER_MODEL**: Model member. +- **CE_MEMBER_DELAY**: Delay member. +- **CE_MEMBER_NEXTKILL**: Next kill member. +- **CE_MEMBER_NEXTRESPAWN**: Next respawn member. +- **CE_MEMBER_INITIALIZED**: Initialized member. +- **CE_MEMBER_BLOODCOLOR**: Blood color member. +- **CE_MEMBER_LIFETIME**: Lifetime member. +- **CE_MEMBER_IGNOREROUNDS**: Ignore rounds member. +- **CE_MEMBER_RESPAWNTIME**: Respawn time member. +- **CE_MEMBER_MINS**: Mins member. +- **CE_MEMBER_MAXS**: Maxs member. +- **CE_MEMBER_LASTINIT**: Last init member. +- **CE_MEMBER_LASTSPAWN**: Last spawn member. +- **CE_MEMBER_PLUGINID**: Plugin ID member. + +### Enums +- **CEPreset**: Available presets for custom entities. +- **CEFunction**: Available functions to hook. diff --git a/api/custom-entities/api_custom_entities.sma b/api/custom-entities/api_custom_entities.sma new file mode 100644 index 0000000..b8a0690 --- /dev/null +++ b/api/custom-entities/api_custom_entities.sma @@ -0,0 +1,1305 @@ +#pragma semicolon 1 + +#include +#include +#include +#include + +#include +#include + +#include + +#define MAX_HOOK_CALL_HIERARCHY_DEPTH 128 +#define CLASS_METADATA_ID "iId" +#define LOG_PREFIX "[CE]" + +enum _:GLOBALESTATE { GLOBAL_OFF = 0, GLOBAL_ON = 1, GLOBAL_DEAD = 2 }; + +enum EntityFlags (<<=1) { + EntityFlag_None = 0, + EntityFlag_Abstract = 1, +} + +enum Entity { + Array:Entity_Name, + Array:Entity_Preset, + Array:Entity_Flags, + Array:Entity_Hooks[CEFunction], + Array:Entity_Class, + Array:Entity_KeyMemberBindings +}; + +new g_iszBaseClassName; +new bool:g_bIsCStrike = false; + +new Trie:g_itEntityIds = Invalid_Trie; +new Array:g_rgEntities[Entity] = { Invalid_Array, ... }; +new g_iEntitiesNum = 0; + +new ClassInstance:g_pInstance = Invalid_ClassInstance; + +new g_iCallPluginId = -1; + +public plugin_precache() { + g_bIsCStrike = !!cstrike_running(); + g_iszBaseClassName = engfunc(EngFunc_AllocString, CE_BASE_CLASSNAME); + + InitStorages(); + + register_forward(FM_Spawn, "FMHook_Spawn"); + register_forward(FM_KeyValue, "FMHook_KeyValue"); + register_forward(FM_OnFreeEntPrivateData, "FMHook_OnFreeEntPrivateData"); + + RegisterHam(Ham_Spawn, CE_BASE_CLASSNAME, "HamHook_Base_Spawn_Post", .Post = 1); + RegisterHam(Ham_ObjectCaps, CE_BASE_CLASSNAME, "HamHook_Base_ObjectCaps", .Post = 0); + + if (g_bIsCStrike) { + RegisterHam(Ham_CS_Restart, CE_BASE_CLASSNAME, "HamHook_Base_Restart", .Post = 1); + } + + RegisterHam(Ham_Touch, CE_BASE_CLASSNAME, "HamHook_Base_Touch", .Post = 0); + RegisterHam(Ham_Touch, CE_BASE_CLASSNAME, "HamHook_Base_Touch_Post", .Post = 1); + RegisterHam(Ham_Killed, CE_BASE_CLASSNAME, "HamHook_Base_Killed", .Post = 0); + RegisterHam(Ham_Think, CE_BASE_CLASSNAME, "HamHook_Base_Think", .Post = 0); + RegisterHam(Ham_BloodColor, CE_BASE_CLASSNAME, "HamHook_Base_BloodColor", .Post = 0); +} + +public plugin_init() { + register_plugin("[API] Custom Entities", "2.0.0", "Hedgehog Fog"); + + register_concmd("ce_spawn", "Command_Spawn", ADMIN_CVAR); +} + +public plugin_natives() { + register_library("api_custom_entities"); + + register_native("CE_Register", "Native_Register"); + register_native("CE_RegisterDerived", "Native_RegisterDerived"); + register_native("CE_Create", "Native_Create"); + register_native("CE_Kill", "Native_Kill"); + register_native("CE_Remove", "Native_Remove"); + register_native("CE_Restart", "Native_Restart"); + + register_native("CE_RegisterHook", "Native_RegisterHook"); + register_native("CE_RegisterKeyMemberBinding", "Native_RegisterKeyMemberBinding"); + register_native("CE_RemoveKeyMemberBinding", "Native_RemoveKeyMemberBinding"); + register_native("CE_RegisterMethod", "Native_RegisterMethod"); + register_native("CE_RegisterVirtualMethod", "Native_RegisterVirtualMethod"); + + register_native("CE_GetHandler", "Native_GetHandler"); + register_native("CE_GetHandlerByEntity", "Native_GetHandlerByEntity"); + register_native("CE_IsInstanceOf", "Native_IsInstanceOf"); + + register_native("CE_HasMember", "Native_HasMember"); + register_native("CE_GetMember", "Native_GetMember"); + register_native("CE_DeleteMember", "Native_DeleteMember"); + register_native("CE_SetMember", "Native_SetMember"); + register_native("CE_GetMemberVec", "Native_GetMemberVec"); + register_native("CE_SetMemberVec", "Native_SetMemberVec"); + register_native("CE_GetMemberString", "Native_GetMemberString"); + register_native("CE_SetMemberString", "Native_SetMemberString"); + register_native("CE_CallMethod", "Native_CallMethod"); + register_native("CE_CallBaseMethod", "Native_CallBaseMethod"); + register_native("CE_GetCallPluginId", "Native_GetCallPluginId"); +} + +public plugin_end() { + DestroyStorages(); +} + +/*--------------------------------[ Natives ]--------------------------------*/ + +public Native_Register(iPluginId, iArgc) { + new szClassname[CE_MAX_NAME_LENGTH]; get_string(1, szClassname, charsmax(szClassname)); + new CEPreset:iPreset = CEPreset:get_param(2); + new bool:bAbstract = bool:get_param(3); + + new EntityFlags:iFlags = bAbstract ? EntityFlag_Abstract : EntityFlag_None; + + return RegisterEntity(szClassname, iPreset, iFlags); +} + +public Native_RegisterDerived(iPluginId, iArgc) { + new szClassname[CE_MAX_NAME_LENGTH]; get_string(1, szClassname, charsmax(szClassname)); + new szBaseClassName[CE_MAX_NAME_LENGTH]; get_string(2, szBaseClassName, charsmax(szBaseClassName)); + new bool:bAbstract = bool:get_param(3); + + new EntityFlags:iFlags = bAbstract ? EntityFlag_Abstract : EntityFlag_None; + + return RegisterEntity(szClassname, _, iFlags, szBaseClassName); +} + +public Native_Create(iPluginId, iArgc) { + static szClassname[CE_MAX_NAME_LENGTH]; get_string(1, szClassname, charsmax(szClassname)); + static Float:vecOrigin[3]; get_array_f(2, vecOrigin, 3); + static bool:bTemp; bTemp = !!get_param(3); + + new pEntity = @Entity_Create(szClassname, vecOrigin, bTemp); + if (!pEntity) { + log_error(AMX_ERR_NATIVE, "%s Failed to create entity ^"%s^"! Entity is abstract or not registered!", LOG_PREFIX, szClassname); + return 0; + } + + new ClassInstance:pInstance = @Entity_GetClassInstance(pEntity); + ClassInstanceSetMember(pInstance, CE_MEMBER_PLUGINID, iPluginId); + + return pEntity; +} + +public Native_Kill(iPluginId, iArgc) { + new pEntity = get_param(1); + new pKiller = get_param(2); + + if (!@Entity_IsCustom(pEntity)) return; + + @Entity_Kill(pEntity, pKiller, false); +} + +public bool:Native_Remove(iPluginId, iArgc) { + new pEntity = get_param(1); + + if (!@Entity_IsCustom(pEntity)) return; + + set_pev(pEntity, pev_flags, pev(pEntity, pev_flags) | FL_KILLME); + dllfunc(DLLFunc_Think, pEntity); +} + +public Native_Restart(iPluginId, iArgc) { + new pEntity = get_param(1); + + if (!@Entity_IsCustom(pEntity)) return; + + @Entity_Restart(pEntity); +} + +public Native_RegisterHook(iPluginId, iArgc) { + new szClassname[CE_MAX_NAME_LENGTH]; get_string(1, szClassname, charsmax(szClassname)); + new CEFunction:iFunction = CEFunction:get_param(2); + new szCallback[CE_MAX_CALLBACK_NAME_LENGTH]; get_string(3, szCallback, charsmax(szCallback)); + + new Function:fnCallback = get_func_pointer(szCallback, iPluginId); + if (fnCallback == Invalid_FunctionPointer) { + new szFilename[64]; + get_plugin(iPluginId, szFilename, charsmax(szFilename)); + log_error(AMX_ERR_NATIVE, "%s Function ^"%s^" not found in plugin ^"%s^".", LOG_PREFIX, szCallback, szFilename); + return; + } + + RegisterEntityHook(iFunction, szClassname, fnCallback); +} + +public Native_RegisterMethod(iPluginId, iArgc) { + new szClassname[CE_MAX_NAME_LENGTH]; get_string(1, szClassname, charsmax(szClassname)); + new szMethod[CE_MAX_METHOD_NAME_LENGTH]; get_string(2, szMethod, charsmax(szMethod)); + new szCallback[CE_MAX_CALLBACK_NAME_LENGTH]; get_string(3, szCallback, charsmax(szCallback)); + + new iId = GetIdByClassName(szClassname); + new Class:cEntity = ArrayGetCell(g_rgEntities[Entity_Class], iId); + new Array:irgParams = ReadMethodParamsFromNativeCall(4, iArgc); + + ClassAddMethod(cEntity, szMethod, get_func_pointer(szCallback, iPluginId), false, CMP_Cell, CMP_ParamsCellArray, irgParams); + + ArrayDestroy(irgParams); +} + +public Native_RegisterVirtualMethod(iPluginId, iArgc) { + new szClassname[CE_MAX_NAME_LENGTH]; get_string(1, szClassname, charsmax(szClassname)); + new szMethod[CE_MAX_METHOD_NAME_LENGTH]; get_string(2, szMethod, charsmax(szMethod)); + new szCallback[CE_MAX_CALLBACK_NAME_LENGTH]; get_string(3, szCallback, charsmax(szCallback)); + + new iId = GetIdByClassName(szClassname); + new Class:cEntity = ArrayGetCell(g_rgEntities[Entity_Class], iId); + new Array:irgParams = ReadMethodParamsFromNativeCall(4, iArgc); + + ClassAddMethod(cEntity, szMethod, get_func_pointer(szCallback, iPluginId), true, CMP_Cell, CMP_ParamsCellArray, irgParams); + + ArrayDestroy(irgParams); +} + +public Native_RegisterKeyMemberBinding(iPluginId, iArgc) { + new szClassname[CE_MAX_NAME_LENGTH]; get_string(1, szClassname, charsmax(szClassname)); + new szKey[CE_MAX_NAME_LENGTH]; get_string(2, szKey, charsmax(szKey)); + new szMember[CE_MAX_NAME_LENGTH]; get_string(3, szMember, charsmax(szMember)); + new CEMemberType:iType = CEMemberType:get_param(4); + + new iId = GetIdByClassName(szClassname); + if (iId == -1) { + log_error(AMX_ERR_NATIVE, "%s Entity ^"%s^" is not registered.", LOG_PREFIX, szClassname); + return; + } + + RegisterKeyMemberBinding(iId, szKey, szMember, iType); +} + +public Native_RemoveKeyMemberBinding(iPluginId, iArgc) { + new szClassname[CE_MAX_NAME_LENGTH]; get_string(1, szClassname, charsmax(szClassname)); + new szKey[CE_MAX_NAME_LENGTH]; get_string(2, szKey, charsmax(szKey)); + new szMember[CE_MAX_NAME_LENGTH]; get_string(3, szMember, charsmax(szMember)); + + new iId = GetIdByClassName(szClassname); + if (iId == -1) { + log_error(AMX_ERR_NATIVE, "%s Entity ^"%s^" is not registered.", LOG_PREFIX, szClassname); + return; + } + + RemoveKeyMemberBinding(iId, szKey, szMember); +} + +public Native_GetHandler(iPluginId, iArgc) { + static szClassname[CE_MAX_NAME_LENGTH]; get_string(1, szClassname, charsmax(szClassname)); + + return GetIdByClassName(szClassname); +} + +public Native_GetHandlerByEntity(iPluginId, iArgc) { + static pEntity; pEntity = get_param(1); + + if (!@Entity_IsCustom(pEntity)) return -1; + + new ClassInstance:pInstance = @Entity_GetClassInstance(pEntity); + + return ClassInstanceGetMember(pInstance, CE_MEMBER_ID); +} + +public bool:Native_IsInstanceOf(iPluginId, iArgc) { + static pEntity; pEntity = get_param(1); + static szClassname[CE_MAX_NAME_LENGTH]; get_string(2, szClassname, charsmax(szClassname)); + + if (!@Entity_IsCustom(pEntity)) return false; + + static iTargetId; iTargetId = GetIdByClassName(szClassname); + if (iTargetId == -1) return false; + + static Class:cTarget; cTarget = ArrayGetCell(g_rgEntities[Entity_Class], iTargetId); + + new ClassInstance:pInstance = @Entity_GetClassInstance(pEntity); + + return ClassInstanceIsInstanceOf(pInstance, cTarget); +} + +public bool:Native_HasMember(iPluginId, iArgc) { + static pEntity; pEntity = get_param(1); + static szMember[CE_MAX_MEMBER_NAME_LENGTH]; get_string(2, szMember, charsmax(szMember)); + + if (!@Entity_IsCustom(pEntity)) return false; + + new ClassInstance:pInstance = @Entity_GetClassInstance(pEntity); + + return ClassInstanceHasMember(pInstance, szMember); +} + +public any:Native_GetMember(iPluginId, iArgc) { + static pEntity; pEntity = get_param(1); + static szMember[CE_MAX_MEMBER_NAME_LENGTH]; get_string(2, szMember, charsmax(szMember)); + + if (!@Entity_IsCustom(pEntity)) return 0; + + new ClassInstance:pInstance = @Entity_GetClassInstance(pEntity); + + return ClassInstanceGetMember(pInstance, szMember); +} + +public Native_DeleteMember(iPluginId, iArgc) { + static pEntity; pEntity = get_param(1); + static szMember[CE_MAX_MEMBER_NAME_LENGTH]; get_string(2, szMember, charsmax(szMember)); + + if (!@Entity_IsCustom(pEntity)) return; + + new ClassInstance:pInstance = @Entity_GetClassInstance(pEntity); + + ClassInstanceDeleteMember(pInstance, szMember); +} + +public Native_SetMember(iPluginId, iArgc) { + static pEntity; pEntity = get_param(1); + static szMember[CE_MAX_MEMBER_NAME_LENGTH]; get_string(2, szMember, charsmax(szMember)); + static iValue; iValue = get_param(3); + static bool:bReplace; bReplace = bool:get_param(4); + + if (!@Entity_IsCustom(pEntity)) return; + + new ClassInstance:pInstance = @Entity_GetClassInstance(pEntity); + + ClassInstanceSetMember(pInstance, szMember, iValue, bReplace); +} + +public bool:Native_GetMemberVec(iPluginId, iArgc) { + static pEntity; pEntity = get_param(1); + static szMember[CE_MAX_MEMBER_NAME_LENGTH]; get_string(2, szMember, charsmax(szMember)); + + if (!@Entity_IsCustom(pEntity)) return false; + + new ClassInstance:pInstance = @Entity_GetClassInstance(pEntity); + + static Float:vecValue[3]; + if (!ClassInstanceGetMemberArray(pInstance, szMember, vecValue, 3)) return false; + + set_array_f(3, vecValue, sizeof(vecValue)); + + return true; +} + +public Native_SetMemberVec(iPluginId, iArgc) { + static pEntity; pEntity = get_param(1); + static szMember[CE_MAX_MEMBER_NAME_LENGTH]; get_string(2, szMember, charsmax(szMember)); + static Float:vecValue[3]; get_array_f(3, vecValue, sizeof(vecValue)); + static bool:bReplace; bReplace = bool:get_param(4); + + if (!@Entity_IsCustom(pEntity)) return; + + new ClassInstance:pInstance = @Entity_GetClassInstance(pEntity); + ClassInstanceSetMemberArray(pInstance, szMember, vecValue, 3, bReplace); +} + +public bool:Native_GetMemberString(iPluginId, iArgc) { + static pEntity; pEntity = get_param(1); + static szMember[CE_MAX_MEMBER_NAME_LENGTH]; get_string(2, szMember, charsmax(szMember)); + + if (!@Entity_IsCustom(pEntity)) return false; + + new ClassInstance:pInstance = @Entity_GetClassInstance(pEntity); + + static szValue[128]; + if (!ClassInstanceGetMemberString(pInstance, szMember, szValue, charsmax(szValue))) return false; + + set_string(3, szValue, get_param(4)); + + return true; +} + +public Native_SetMemberString(iPluginId, iArgc) { + static pEntity; pEntity = get_param(1); + static szMember[CE_MAX_MEMBER_NAME_LENGTH]; get_string(2, szMember, charsmax(szMember)); + static szValue[128]; get_string(3, szValue, charsmax(szValue)); + static bool:bReplace; bReplace = bool:get_param(4); + + if (!@Entity_IsCustom(pEntity)) return; + + new ClassInstance:pInstance = @Entity_GetClassInstance(pEntity); + ClassInstanceSetMemberString(pInstance, szMember, szValue, bReplace); +} + +public any:Native_CallMethod(iPluginId, iArgc) { + new pEntity = get_param(1); + static szMethod[CE_MAX_METHOD_NAME_LENGTH]; get_string(2, szMethod, charsmax(szMethod)); + + new ClassInstance:pInstance = @Entity_GetClassInstance(pEntity); + + new iOldCallPluginId = g_iCallPluginId; + + g_iCallPluginId = iPluginId; + + ClassInstanceCallMethodBegin(pInstance, szMethod); + + ClassInstanceCallMethodPushParamCell(pEntity); + + for (new iParam = 3; iParam <= iArgc; ++iParam) { + ClassInstanceCallMethodPushNativeParam(iParam); + } + + new any:result = ClassInstanceCallMethodEnd(); + + g_iCallPluginId = iOldCallPluginId; + + return result; +} + +public any:Native_CallBaseMethod(iPluginId, iArgc) { + new ClassInstance:pInstance = ClassInstanceGetCurrent(); + new pEntity = ClassInstanceGetMember(pInstance, CE_MEMBER_POINTER); + + new iOldCallPluginId = g_iCallPluginId; + + g_iCallPluginId = iPluginId; + + ClassInstanceCallMethodBeginBase(); + + ClassInstanceCallMethodPushParamCell(pEntity); + + for (new iParam = 1; iParam <= iArgc; ++iParam) { + ClassInstanceCallMethodPushNativeParam(iParam); + } + + new any:result = ClassInstanceCallMethodEnd(); + + g_iCallPluginId = iOldCallPluginId; + + return result; +} + +public Native_GetCallPluginId(iPluginId, iArgc) { + return g_iCallPluginId; +} + +/*--------------------------------[ Commands ]--------------------------------*/ + +public Command_Spawn(pPlayer, iLevel, iCId) { + if (!cmd_access(pPlayer, iLevel, iCId, 2)) return PLUGIN_HANDLED; + + new szClassname[128]; + read_args(szClassname, charsmax(szClassname)); + remove_quotes(szClassname); + + if (equal(szClassname, NULL_STRING)) return PLUGIN_HANDLED; + + new Float:vecOrigin[3]; pev(pPlayer, pev_origin, vecOrigin); + + new pEntity = @Entity_Create(szClassname, vecOrigin, true); + if (!pEntity) return PLUGIN_HANDLED; + + dllfunc(DLLFunc_Spawn, pEntity); + + return PLUGIN_HANDLED; +} + +/*--------------------------------[ Hooks ]--------------------------------*/ + +public FMHook_OnFreeEntPrivateData(pEntity) { + if (!pev_valid(pEntity)) return; + + if (@Entity_IsCustom(pEntity)) { + @Entity_DestroyClassInstance(pEntity); + } +} + +public FMHook_KeyValue(pEntity, hKVD) { + new szKey[32]; get_kvd(hKVD, KV_KeyName, szKey, charsmax(szKey)); + new szValue[32]; get_kvd(hKVD, KV_Value, szValue, charsmax(szValue)); + + if (equal(szKey, "classname")) { + new iId = GetIdByClassName(szValue); + if (iId != -1) { + // using set_kvd leads to duplicate kvd emit, this check will fix the issue + if (g_pInstance == Invalid_ClassInstance) { + new EntityFlags:iFlags = ArrayGetCell(g_rgEntities[Entity_Flags], iId); + if (~iFlags & EntityFlag_Abstract) { + set_kvd(hKVD, KV_Value, CE_BASE_CLASSNAME); + g_pInstance = AllocPData(iId, pEntity); + } + } + } else { + // if for some reason data was not assigned + if (g_pInstance != Invalid_ClassInstance) { + ClassInstanceDestroy(g_pInstance); + g_pInstance = Invalid_ClassInstance; + } + } + } + + if (g_pInstance != Invalid_ClassInstance) { + if (equal(szKey, "classname")) { + ClassInstanceSetMember(g_pInstance, CE_MEMBER_WORLD, true); + } + + if (ExecuteHookFunction(CEFunction_KeyValue, pEntity, szKey, szValue) == PLUGIN_CONTINUE) { + @Entity_ApplyKeyMemberBindings(pEntity, szKey, szValue); + } + } + + return FMRES_HANDLED; +} + +public FMHook_Spawn(pEntity) { + if (g_pInstance != Invalid_ClassInstance) { + static iId; iId = ClassInstanceGetMember(g_pInstance, CE_MEMBER_ID); + + static szClassname[CE_MAX_NAME_LENGTH]; + ArrayGetString(g_rgEntities[Entity_Name], iId, szClassname, charsmax(szClassname)); + set_pev(pEntity, pev_classname, szClassname); + + @Entity_SetClassInstance(pEntity, g_pInstance); + g_pInstance = Invalid_ClassInstance; + } +} + +public HamHook_Base_Spawn_Post(pEntity) { + if (@Entity_IsCustom(pEntity)) { + @Entity_Spawn(pEntity); + } +} + +public HamHook_Base_ObjectCaps(pEntity) { + if (@Entity_IsCustom(pEntity)) { + new iObjectCaps = @Entity_GetObjectCaps(pEntity); + SetHamReturnInteger(iObjectCaps); + return HAM_OVERRIDE; + } + + return HAM_IGNORED; +} + +public HamHook_Base_Restart(pEntity) { + if (@Entity_IsCustom(pEntity)) { + @Entity_Restart(pEntity); + return HAM_HANDLED; + } + + return HAM_IGNORED; +} + +public HamHook_Base_Touch(pEntity, pToucher) { + if (@Entity_IsCustom(pEntity)) { + @Entity_Touch(pEntity, pToucher); + return HAM_HANDLED; + } + + return HAM_IGNORED; +} + +public HamHook_Base_Touch_Post(pEntity, pToucher) { + if (@Entity_IsCustom(pEntity)) { + @Entity_Touched(pEntity, pToucher); + return HAM_HANDLED; + } + + return HAM_IGNORED; +} + +public HamHook_Base_Killed(pEntity, pKiller, iShouldGib) { + if (@Entity_IsCustom(pEntity)) { + @Entity_Kill(pEntity, pKiller, iShouldGib); + return HAM_SUPERCEDE; + } + + return HAM_IGNORED; +} + +public HamHook_Base_Think(pEntity, pKiller) { + if (@Entity_IsCustom(pEntity)) { + @Entity_Think(pEntity); + return HAM_HANDLED; + } + + return HAM_IGNORED; +} + +public HamHook_Base_BloodColor(pEntity) { + if (@Entity_IsCustom(pEntity)) { + new iBloodColor = @Entity_BloodColor(pEntity); + if (iBloodColor < 0) { + return HAM_HANDLED; + } + + SetHamReturnInteger(iBloodColor); + return HAM_OVERRIDE; + } + + return HAM_IGNORED; +} + +/*--------------------------------[ Methods ]--------------------------------*/ + +@Entity_Create(const szClassname[], const Float:vecOrigin[3], bool:bTemp) { + new iId = GetIdByClassName(szClassname); + if (iId == -1) return 0; + + static EntityFlags:iFlags; iFlags = ArrayGetCell(g_rgEntities[Entity_Flags], iId); + if (iFlags & EntityFlag_Abstract) return 0; + + new this = engfunc(EngFunc_CreateNamedEntity, g_iszBaseClassName); + set_pev(this, pev_classname, szClassname); + engfunc(EngFunc_SetOrigin, this, vecOrigin); + // set_pev(this, pev_flags, pev(this, pev_flags) & ~FL_ONGROUND); + + new ClassInstance:pInstance = @Entity_AllocClassInstance(this, iId); + ClassInstanceSetMemberArray(pInstance, CE_MEMBER_ORIGIN, vecOrigin, 3); + + ClassInstanceSetMember(pInstance, CE_MEMBER_WORLD, !bTemp); + + return this; +} + +bool:@Entity_IsCustom(const &this) { + if (g_pInstance != Invalid_ClassInstance && ClassInstanceGetMember(g_pInstance, CE_MEMBER_POINTER) == this) { + return true; + } + + return pev(this, pev_gaitsequence) == CE_ENTITY_SECRET; +} + +@Entity_Init(&this) { + new ClassInstance:pInstance = @Entity_GetClassInstance(this); + + ClassInstanceSetMember(pInstance, CE_MEMBER_IGNOREROUNDS, false); + + if (ExecuteHookFunction(CEFunction_Init, this) != PLUGIN_CONTINUE) return; + + if (ClassInstanceHasMember(pInstance, CE_MEMBER_MODEL)) { + static szModel[MAX_RESOURCE_PATH_LENGTH]; + ClassInstanceGetMemberString(pInstance, CE_MEMBER_MODEL, szModel, charsmax(szModel)); + } + + ClassInstanceSetMember(pInstance, CE_MEMBER_INITIALIZED, true); + ClassInstanceSetMember(pInstance, CE_MEMBER_LASTINIT, get_gametime()); +} + +@Entity_Spawn(&this) { + if (ExecuteHookFunction(CEFunction_Spawn, this) != PLUGIN_CONTINUE) return; + + new ClassInstance:pInstance; pInstance = @Entity_GetClassInstance(this); + if (!ClassInstanceGetMember(pInstance, CE_MEMBER_INITIALIZED)) { + @Entity_Init(this); + } + + if (!pev_valid(this) || pev(this, pev_flags) & FL_KILLME) return; + + set_pev(this, pev_deadflag, DEAD_NO); + set_pev(this, pev_effects, pev(this, pev_effects) & ~EF_NODRAW); + set_pev(this, pev_flags, pev(this, pev_flags) & ~FL_ONGROUND); + + @Entity_InitPhysics(this); + @Entity_InitModel(this); + @Entity_InitSize(this); + + static iId; iId = ClassInstanceGetMember(pInstance, CE_MEMBER_ID); + static CEPreset:iPreset; iPreset = ArrayGetCell(g_rgEntities[Entity_Preset], iId); + + switch (iPreset) { + case CEPreset_Trigger: { + ClassInstanceSetMember(pInstance, CE_MEMBER_DELAY, 0.1); + } + case CEPreset_NPC: { + set_pev(this, pev_flags, pev(this, pev_flags) | FL_MONSTER); + } + case CEPreset_Item: { + ClassInstanceSetMember(pInstance, CE_MEMBER_PICKED, false); + } + } + + if (ClassInstanceHasMember(pInstance, CE_MEMBER_ORIGIN)) { + static Float:vecOrigin[3]; + ClassInstanceGetMemberArray(pInstance, CE_MEMBER_ORIGIN, vecOrigin, 3); + engfunc(EngFunc_SetOrigin, this, vecOrigin); + } + + if (ClassInstanceHasMember(pInstance, CE_MEMBER_ANGLES)) { + static Float:vecAngles[3]; + ClassInstanceGetMemberArray(pInstance, CE_MEMBER_ANGLES, vecAngles, 3); + set_pev(this, pev_angles, vecAngles); + } + + if (ClassInstanceHasMember(pInstance, CE_MEMBER_TARGETNAME)) { + static szTargetname[32]; + ClassInstanceGetMemberString(pInstance, CE_MEMBER_TARGETNAME, szTargetname, charsmax(szTargetname)); + set_pev(this, pev_targetname, szTargetname); + } + + if (ClassInstanceHasMember(pInstance, CE_MEMBER_TARGET)) { + static szTarget[32]; + ClassInstanceGetMemberString(pInstance, CE_MEMBER_TARGET, szTarget, charsmax(szTarget)); + set_pev(this, pev_target, szTarget); + } + + static bool:bIsWorld; bIsWorld = ClassInstanceGetMember(pInstance, CE_MEMBER_WORLD); + + static Float:flLifeTime; flLifeTime = 0.0; + if (!bIsWorld && ClassInstanceHasMember(pInstance, CE_MEMBER_LIFETIME)) { + flLifeTime = ClassInstanceGetMember(pInstance, CE_MEMBER_LIFETIME); + } + + static Float:flGameTime; flGameTime = get_gametime(); + + if (flLifeTime > 0.0) { + ClassInstanceSetMember(pInstance, CE_MEMBER_NEXTKILL, flGameTime + flLifeTime); + set_pev(this, pev_nextthink, flGameTime + flLifeTime); + } else { + ClassInstanceSetMember(pInstance, CE_MEMBER_NEXTKILL, 0.0); + } + + ClassInstanceSetMember(pInstance, CE_MEMBER_LASTSPAWN, flGameTime); + + ExecuteHookFunction(CEFunction_Spawned, this); +} + +@Entity_Restart(&this) { + ExecuteHookFunction(CEFunction_Restart, this); + + new iObjectCaps = ExecuteHamB(Ham_ObjectCaps, this); + + if (!g_bIsCStrike) { + if (iObjectCaps & FCAP_MUST_RELEASE) { + set_pev(this, pev_globalname, GLOBAL_DEAD); + set_pev(this, pev_solid, SOLID_NOT); + set_pev(this, pev_flags, pev(this, pev_flags) | FL_KILLME); + set_pev(this, pev_targetname, ""); + + return; + } + } + + if (~iObjectCaps & FCAP_ACROSS_TRANSITION) { + dllfunc(DLLFunc_Spawn, this); + } +} + +@Entity_InitPhysics(&this) { + if (ExecuteHookFunction(CEFunction_InitPhysics, this) != PLUGIN_CONTINUE) return; + + static ClassInstance:pInstance; pInstance = @Entity_GetClassInstance(this); + static iId; iId = ClassInstanceGetMember(pInstance, CE_MEMBER_ID); + static CEPreset:iPreset; iPreset = ArrayGetCell(g_rgEntities[Entity_Preset], iId); + + switch (iPreset) { + case CEPreset_Item: { + set_pev(this, pev_solid, SOLID_TRIGGER); + set_pev(this, pev_movetype, MOVETYPE_TOSS); + set_pev(this, pev_takedamage, DAMAGE_NO); + } + case CEPreset_NPC: { + set_pev(this, pev_solid, SOLID_BBOX); + set_pev(this, pev_movetype, MOVETYPE_PUSHSTEP); + set_pev(this, pev_takedamage, DAMAGE_AIM); + + set_pev(this, pev_controller_0, 125); + set_pev(this, pev_controller_1, 125); + set_pev(this, pev_controller_2, 125); + set_pev(this, pev_controller_3, 125); + + set_pev(this, pev_gamestate, 1); + set_pev(this, pev_gravity, 1.0); + set_pev(this, pev_fixangle, 1); + set_pev(this, pev_friction, 0.25); + } + case CEPreset_Prop: { + set_pev(this, pev_solid, SOLID_BBOX); + set_pev(this, pev_movetype, MOVETYPE_FLY); + set_pev(this, pev_takedamage, DAMAGE_NO); + } + case CEPreset_Trigger: { + set_pev(this, pev_solid, SOLID_TRIGGER); + set_pev(this, pev_movetype, MOVETYPE_NONE); + set_pev(this, pev_effects, EF_NODRAW); + } + case CEPreset_BSP: { + set_pev(this, pev_movetype, MOVETYPE_PUSH); + set_pev(this, pev_solid, SOLID_BSP); + set_pev(this, pev_flags, pev(this, pev_flags) | FL_WORLDBRUSH); + } + } +} + +@Entity_InitModel(&this) { + if (ExecuteHookFunction(CEFunction_InitModel, this) != PLUGIN_CONTINUE) return; + + static ClassInstance:pInstance; pInstance = @Entity_GetClassInstance(this); + + if (ClassInstanceHasMember(pInstance, CE_MEMBER_MODEL)) { + static szModel[MAX_RESOURCE_PATH_LENGTH]; + ClassInstanceGetMemberString(pInstance, CE_MEMBER_MODEL, szModel, charsmax(szModel)); + engfunc(EngFunc_SetModel, this, szModel); + } +} + +@Entity_InitSize(&this) { + if (ExecuteHookFunction(CEFunction_InitSize, this) != PLUGIN_CONTINUE) return; + + static ClassInstance:pInstance; pInstance = @Entity_GetClassInstance(this); + + if (ClassInstanceHasMember(pInstance, CE_MEMBER_MINS) && ClassInstanceHasMember(pInstance, CE_MEMBER_MAXS)) { + static Float:vecMins[3]; ClassInstanceGetMemberArray(pInstance, CE_MEMBER_MINS, vecMins, 3); + static Float:vecMaxs[3]; ClassInstanceGetMemberArray(pInstance, CE_MEMBER_MAXS, vecMaxs, 3); + engfunc(EngFunc_SetSize, this, vecMins, vecMaxs); + } +} + +@Entity_Kill(&this, const &pKiller, iShouldGib) { + if (ExecuteHookFunction(CEFunction_Kill, this, pKiller, iShouldGib) != PLUGIN_CONTINUE) return; + + static ClassInstance:pInstance; pInstance = @Entity_GetClassInstance(this); + + ClassInstanceSetMember(pInstance, CE_MEMBER_NEXTKILL, 0.0); + + set_pev(this, pev_takedamage, DAMAGE_NO); + set_pev(this, pev_effects, pev(this, pev_effects) | EF_NODRAW); + set_pev(this, pev_solid, SOLID_NOT); + set_pev(this, pev_movetype, MOVETYPE_NONE); + set_pev(this, pev_flags, pev(this, pev_flags) & ~FL_ONGROUND); + + new bool:bIsWorld = ClassInstanceGetMember(pInstance, CE_MEMBER_WORLD); + + if (bIsWorld) { + if (ClassInstanceHasMember(pInstance, CE_MEMBER_RESPAWNTIME)) { + new Float:flRespawnTime = ClassInstanceGetMember(pInstance, CE_MEMBER_RESPAWNTIME); + new Float:flGameTime = get_gametime(); + + ClassInstanceSetMember(pInstance, CE_MEMBER_NEXTRESPAWN, flGameTime + flRespawnTime); + set_pev(this, pev_deadflag, DEAD_RESPAWNABLE); + set_pev(this, pev_nextthink, flGameTime + flRespawnTime); + } else { + set_pev(this, pev_deadflag, DEAD_DEAD); + } + } else { + set_pev(this, pev_deadflag, DEAD_DISCARDBODY); + set_pev(this, pev_flags, pev(this, pev_flags) | FL_KILLME); + } + + ExecuteHookFunction(CEFunction_Killed, this, pKiller, iShouldGib); +} + +@Entity_Think(&this) { + if (pev(this, pev_flags) & FL_KILLME) return; + + ExecuteHookFunction(CEFunction_Think, this); + + static ClassInstance:pInstance; pInstance = @Entity_GetClassInstance(this); + + static Float:flGameTime; flGameTime = get_gametime(); + static iDeadFlag; iDeadFlag = pev(this, pev_deadflag); + + switch (iDeadFlag) { + case DEAD_NO: { + static Float:flNextKill; flNextKill = ClassInstanceGetMember(pInstance, CE_MEMBER_NEXTKILL); + if (flNextKill > 0.0 && flNextKill <= flGameTime) { + ExecuteHamB(Ham_Killed, this, 0, 0); + } + } + case DEAD_RESPAWNABLE: { + static Float:flNextRespawn; flNextRespawn = ClassInstanceGetMember(pInstance, CE_MEMBER_NEXTRESPAWN); + if (flNextRespawn <= flGameTime) { + dllfunc(DLLFunc_Spawn, this); + } + } + } +} + +@Entity_Touch(&this, const &pToucher) { + if (ExecuteHookFunction(CEFunction_Touch, this, pToucher)) return; + + static ClassInstance:pInstance; pInstance = @Entity_GetClassInstance(this); + + static iId; iId = ClassInstanceGetMember(pInstance, CE_MEMBER_ID); + static CEPreset:iPreset; iPreset = ArrayGetCell(g_rgEntities[Entity_Preset], iId); + + switch (iPreset) { + case CEPreset_Item: { + if (is_user_alive(pToucher)) { + @Entity_Pickup(this, pToucher); + } + } + case CEPreset_Trigger: { + @Entity_Trigger(this, pToucher); + } + } +} + +@Entity_Touched(&this, const &pToucher) { + ExecuteHookFunction(CEFunction_Touched, this, pToucher); +} + +@Entity_GetObjectCaps(const &this) { + new ClassInstance:pInstance = @Entity_GetClassInstance(this); + new bool:bIgnoreRound = ClassInstanceGetMember(pInstance, CE_MEMBER_IGNOREROUNDS); + new bool:bIsWorld = ClassInstanceGetMember(pInstance, CE_MEMBER_WORLD); + + new iObjectCaps = 0; + + if (bIgnoreRound) { + iObjectCaps |= FCAP_ACROSS_TRANSITION; + } else { + iObjectCaps |= bIsWorld ? FCAP_MUST_RESET : FCAP_MUST_RELEASE; + } + + return iObjectCaps; +} + +@Entity_Pickup(&this, const &pToucher) { + if (~pev(this, pev_flags) & FL_ONGROUND) return; + + if (ExecuteHookFunction(CEFunction_Pickup, this, pToucher)) { + new ClassInstance:pInstance; pInstance = @Entity_GetClassInstance(this); + + ClassInstanceSetMember(pInstance, CE_MEMBER_PICKED, true); + ExecuteHookFunction(CEFunction_Picked, this, pToucher); + @Entity_Kill(this, pToucher, 0); + } +} + +bool:@Entity_CanActivate(const &this, const &pTarget) { + new ClassInstance:pInstance = @Entity_GetClassInstance(this); + + static Float:flNextThink; pev(this, pev_nextthink, flNextThink); + if (flNextThink > get_gametime()) return false; + + if (!ExecuteHookFunction(CEFunction_Activate, this, pTarget)) return false; + + static szMaster[32]; copy(szMaster, charsmax(szMaster), NULL_STRING); + + ClassInstanceGetMemberString(pInstance, CE_MEMBER_MASTER, szMaster, charsmax(szMaster)); + + return UTIL_IsMasterTriggered(szMaster, pTarget); +} + +@Entity_Trigger(&this, const &pActivator) { + if (!@Entity_CanActivate(this, pActivator)) return; + + new ClassInstance:pInstance = @Entity_GetClassInstance(this); + + static Float:flDelay; flDelay = ClassInstanceGetMember(pInstance, CE_MEMBER_DELAY); + + set_pev(this, pev_nextthink, get_gametime() + flDelay); + ExecuteHookFunction(CEFunction_Activated, this, pActivator); +} + +@Entity_BloodColor(const &this) { + new ClassInstance:pInstance = @Entity_GetClassInstance(this); + + if (!ClassInstanceHasMember(pInstance, CE_MEMBER_BLOODCOLOR)) return -1; + + return ClassInstanceGetMember(pInstance, CE_MEMBER_BLOODCOLOR); +} + +@Entity_ApplyKeyMemberBindings(&this, const szKey[], const szValue[]) { + new ClassInstance:pInstance = @Entity_GetClassInstance(this); + + new Class:rgHierarchy[MAX_HOOK_CALL_HIERARCHY_DEPTH], iHierarchySize; + GetInstanceHierarchy(pInstance, rgHierarchy, iHierarchySize); + + for (new i = iHierarchySize - 1; i >= 0; --i) { + new iCurrentId; iCurrentId = ClassGetMetadata(rgHierarchy[i], CLASS_METADATA_ID); + + new Trie:itKeyMemberBindings = ArrayGetCell(g_rgEntities[Entity_KeyMemberBindings], iCurrentId); + new Trie:itMemberTypes = Invalid_Trie; + + if (!TrieGetCell(itKeyMemberBindings, szKey, itMemberTypes)) return; + + new TrieIter:itMemberTypesIter = TrieIterCreate(itMemberTypes); + + while (!TrieIterEnded(itMemberTypesIter)) { + new szMember[32]; TrieIterGetKey(itMemberTypesIter, szMember, charsmax(szMember)); + new CEMemberType:iType; TrieIterGetCell(itMemberTypesIter, iType); + + switch (iType) { + case CEMemberType_Cell: { + ClassInstanceSetMember(g_pInstance, szMember, str_to_num(szValue)); + } + case CEMemberType_Float: { + ClassInstanceSetMember(g_pInstance, szMember, str_to_float(szValue)); + } + case CEMemberType_String: { + ClassInstanceSetMemberString(g_pInstance, szMember, szValue); + } + case CEMemberType_Vector: { + new Float:vecValue[3]; + UTIL_ParseVector(szValue, vecValue); + ClassInstanceSetMemberArray(g_pInstance, szMember, vecValue, 3); + } + } + + TrieIterNext(itMemberTypesIter); + } + } +} + +ClassInstance:@Entity_GetClassInstance(const &this) { + // Return the current allocated data if the entity is at the initialization stage + if (g_pInstance != Invalid_ClassInstance && ClassInstanceGetMember(g_pInstance, CE_MEMBER_POINTER) == this) { + return g_pInstance; + } + + return ClassInstance:pev(this, pev_iStepLeft); +} + +@Entity_SetClassInstance(&this, ClassInstance:pInstance) { + set_pev(this, pev_gaitsequence, CE_ENTITY_SECRET); + set_pev(this, pev_iStepLeft, pInstance); +} + +ClassInstance:@Entity_AllocClassInstance(&this, iId) { + new ClassInstance:pInstance = AllocPData(iId, this); + + @Entity_SetClassInstance(this, pInstance); + + return pInstance; +} + +@Entity_DestroyClassInstance(&this) { + ExecuteHookFunction(CEFunction_Remove, this); + + new ClassInstance:pInstance = @Entity_GetClassInstance(this); + ClassInstanceDestroy(pInstance); + + set_pev(this, pev_gaitsequence, 0); + set_pev(this, pev_iStepLeft, 0); +} + +/*--------------------------------[ Functions ]--------------------------------*/ + +InitStorages() { + g_itEntityIds = TrieCreate(); + g_rgEntities[Entity_Name] = ArrayCreate(CE_MAX_NAME_LENGTH); + g_rgEntities[Entity_Preset] = ArrayCreate(); + g_rgEntities[Entity_Class] = ArrayCreate(); + g_rgEntities[Entity_Flags] = ArrayCreate(); + g_rgEntities[Entity_KeyMemberBindings] = ArrayCreate(); + + for (new CEFunction:iFunction = CEFunction:0; iFunction < CEFunction; ++iFunction) { + g_rgEntities[Entity_Hooks][iFunction] = ArrayCreate(); + } +} + +DestroyStorages() { + for (new iId = 0; iId < g_iEntitiesNum; ++iId) { + FreeRegisteredEntity(iId); + } + + for (new CEFunction:iFunction = CEFunction:0; iFunction < CEFunction; ++iFunction) { + ArrayDestroy(g_rgEntities[Entity_Hooks][iFunction]); + } + + ArrayDestroy(g_rgEntities[Entity_KeyMemberBindings]); + ArrayDestroy(g_rgEntities[Entity_Class]); + ArrayDestroy(g_rgEntities[Entity_Flags]); + ArrayDestroy(g_rgEntities[Entity_Preset]); + ArrayDestroy(g_rgEntities[Entity_Name]); + + TrieDestroy(g_itEntityIds); +} + +RegisterEntity(const szClassname[], CEPreset:iPreset = CEPreset_None, const EntityFlags:iFlags = EntityFlag_None, const szParent[] = "") { + new iId = g_iEntitiesNum; + + new Class:cParent = Invalid_Class; + if (!equal(szParent, NULL_STRING)) { + new iParentId = -1; + if (!TrieGetCell(g_itEntityIds, szParent, iParentId)) { + log_error(AMX_ERR_NATIVE, "%s Cannot extend entity class ^"%s^". The class is not exists!", LOG_PREFIX, szParent); + return -1; + } + + iPreset = ArrayGetCell(g_rgEntities[Entity_Preset], iParentId); + cParent = ArrayGetCell(g_rgEntities[Entity_Class], iParentId); + } + + new Class:cEntity = ClassCreate(cParent); + ClassSetMetadataString(cEntity, "__NAME", szClassname); + ClassSetMetadata(cEntity, CLASS_METADATA_ID, iId); + + TrieSetCell(g_itEntityIds, szClassname, iId); + ArrayPushString(g_rgEntities[Entity_Name], szClassname); + ArrayPushCell(g_rgEntities[Entity_Preset], iPreset); + ArrayPushCell(g_rgEntities[Entity_Class], cEntity); + ArrayPushCell(g_rgEntities[Entity_Flags], iFlags); + ArrayPushCell(g_rgEntities[Entity_KeyMemberBindings], TrieCreate()); + + for (new CEFunction:iFunction = CEFunction:0; iFunction < CEFunction; ++iFunction) { + ArrayPushCell(g_rgEntities[Entity_Hooks][iFunction], ArrayCreate()); + } + + g_iEntitiesNum++; + + RegisterKeyMemberBinding(iId, "origin", CE_MEMBER_ORIGIN, CEMemberType_Vector); + RegisterKeyMemberBinding(iId, "angles", CE_MEMBER_ANGLES, CEMemberType_Vector); + RegisterKeyMemberBinding(iId, "master", CE_MEMBER_MASTER, CEMemberType_String); + RegisterKeyMemberBinding(iId, "targetname", CE_MEMBER_TARGETNAME, CEMemberType_String); + RegisterKeyMemberBinding(iId, "target", CE_MEMBER_TARGET, CEMemberType_String); + RegisterKeyMemberBinding(iId, "model", CE_MEMBER_MODEL, CEMemberType_String); + + log_amx("%s Entity ^"%s^" successfully registred.", LOG_PREFIX, szClassname); + + return iId; +} + +FreeRegisteredEntity(iId) { + for (new CEFunction:iFunction = CEFunction:0; iFunction < CEFunction; ++iFunction) { + new Array:irgHooks = ArrayGetCell(g_rgEntities[Entity_Hooks][iFunction], iId); + ArrayDestroy(irgHooks); + ArraySetCell(g_rgEntities[Entity_Hooks][iFunction], iId, Invalid_Array); + } + + FreeEntityKeyMemberBindings(iId); + + new Class:cEntity = ArrayGetCell(g_rgEntities[Entity_Class], iId); + ClassDestroy(cEntity); +} + +FreeEntityKeyMemberBindings(iId) { + new Trie:itKeyMemberBindings = ArrayGetCell(g_rgEntities[Entity_KeyMemberBindings], iId); + + new TrieIter:itKeyMemberBindingsIter = TrieIterCreate(itKeyMemberBindings); + + while (!TrieIterEnded(itKeyMemberBindingsIter)) { + new Trie:itMemberTypes; TrieIterGetCell(itKeyMemberBindingsIter, itMemberTypes); + TrieDestroy(itMemberTypes); + TrieIterNext(itKeyMemberBindingsIter); + } + + TrieIterDestroy(itKeyMemberBindingsIter); + + TrieDestroy(itKeyMemberBindings); +} + +GetIdByClassName(const szClassname[]) { + static iId; iId = -1; + TrieGetCell(g_itEntityIds, szClassname, iId); + + return iId; +} + +RegisterEntityHook(CEFunction:iFunction, const szClassname[], const Function:fnCallback) { + new iId = GetIdByClassName(szClassname); + if (iId == -1) { + log_error(AMX_ERR_NATIVE, "%s Entity ^"%s^" is not registered.", LOG_PREFIX, szClassname); + return -1; + } + + new Array:irgHooks = ArrayGetCell(g_rgEntities[Entity_Hooks][iFunction], iId); + new iHookId = ArrayPushCell(irgHooks, fnCallback); + + return iHookId; +} + +RegisterKeyMemberBinding(iId, const szKey[], const szMember[], CEMemberType:iType) { + new Trie:itKeyMemberBindings = ArrayGetCell(g_rgEntities[Entity_KeyMemberBindings], iId); + + new Trie:itMemberTypes = Invalid_Trie; + if (!TrieGetCell(itKeyMemberBindings, szKey, itMemberTypes)) { + itMemberTypes = TrieCreate(); + TrieSetCell(itKeyMemberBindings, szKey, itMemberTypes); + } + + TrieSetCell(itMemberTypes, szMember, iType); +} + +RemoveKeyMemberBinding(iId, const szKey[], const szMember[]) { + new Trie:itKeyMemberBindings = ArrayGetCell(g_rgEntities[Entity_KeyMemberBindings], iId); + + new Trie:itMemberTypes = Invalid_Trie; + if (!TrieGetCell(itKeyMemberBindings, szKey, itMemberTypes)) return; + + TrieDeleteKey(itMemberTypes, szMember); +} + +ClassInstance:AllocPData(iId, pEntity) { + static Class:cEntity; cEntity = ArrayGetCell(g_rgEntities[Entity_Class], iId); + static ClassInstance:pInstance; pInstance = ClassInstanceCreate(cEntity); + + ClassInstanceSetMember(pInstance, CE_MEMBER_ID, iId); + ClassInstanceSetMember(pInstance, CE_MEMBER_WORLD, false); + ClassInstanceSetMember(pInstance, CE_MEMBER_POINTER, pEntity); + ClassInstanceSetMember(pInstance, CE_MEMBER_INITIALIZED, false); + + return pInstance; +} + +ExecuteHookFunction(CEFunction:iFunction, pEntity, any:...) { + new iResult = 0; + + new ClassInstance:pInstance = @Entity_GetClassInstance(pEntity); + + new Class:rgHierarchy[MAX_HOOK_CALL_HIERARCHY_DEPTH], iHierarchySize; + GetInstanceHierarchy(pInstance, rgHierarchy, iHierarchySize); + + for (new i = iHierarchySize - 1; i >= 0; --i) { + new iCurrentId; iCurrentId = ClassGetMetadata(rgHierarchy[i], CLASS_METADATA_ID); + new Array:irgHooks; irgHooks = ArrayGetCell(g_rgEntities[Entity_Hooks][iFunction], iCurrentId); + new iHooksNum; iHooksNum = ArraySize(irgHooks); + + for (new iHookId = 0; iHookId < iHooksNum; ++iHookId) { + static Function:fnCallback; fnCallback = ArrayGetCell(irgHooks, iHookId); + + if (callfunc_begin_p(fnCallback) == 1) { + callfunc_push_int(pEntity); + + switch (iFunction) { + case CEFunction_Touch, CEFunction_Touched: { + static pToucher; pToucher = getarg(2); + callfunc_push_int(pToucher); + } + case CEFunction_Kill, CEFunction_Killed: { + static pKiller; pKiller = getarg(2); + static iShouldGib; iShouldGib = getarg(3); + callfunc_push_int(pKiller); + callfunc_push_int(iShouldGib); + } + case CEFunction_Pickup, CEFunction_Picked: { + static pPlayer; pPlayer = getarg(2); + callfunc_push_int(pPlayer); + } + case CEFunction_Activate, CEFunction_Activated: { + static pPlayer; pPlayer = getarg(2); + callfunc_push_int(pPlayer); + } + case CEFunction_KeyValue: { + static szKey[32]; + for (new i = 0; i < charsmax(szKey); ++i) { + szKey[i] = getarg(2, i); + + if (szKey[i] == '^0') break; + } + + static szValue[32]; + for (new i = 0; i < charsmax(szValue); ++i) { + szValue[i] = getarg(3, i); + + if (szValue[i] == '^0') break; + } + + callfunc_push_str(szKey); + callfunc_push_str(szValue); + } + } + + iResult = max(iResult, callfunc_end()); + } + } + } + + return iResult; +} + +GetInstanceHierarchy(ClassInstance:pInstance, Class:rgValue[MAX_HOOK_CALL_HIERARCHY_DEPTH], &iSize) { + iSize = 0; + + for (new Class:cStart = ClassInstanceGetClass(pInstance); cStart != Invalid_Class; cStart = ClassGetBaseClass(cStart)) { + rgValue[iSize] = cStart; + iSize++; + } +} + +Array:ReadMethodParamsFromNativeCall(iStartArg, iArgc) { + static Array:irgParams; irgParams = ArrayCreate(); + + for (new iParam = iStartArg; iParam <= iArgc; ++iParam) { + static iType; iType = get_param_byref(iParam); + + switch (iType) { + case CE_MP_Cell, CE_MP_Float: { + ArrayPushCell(irgParams, CMP_Cell); + } + case CE_MP_String: { + ArrayPushCell(irgParams, CMP_String); + } + case CE_MP_Array, CE_MP_FloatArray: { + ArrayPushCell(irgParams, CMP_Array); + ArrayPushCell(irgParams, get_param_byref(iParam + 1)); + iParam++; + } + } + } + + return irgParams; +} + +/*--------------------------------[ Stocks ]--------------------------------*/ + +stock bool:UTIL_IsMasterTriggered(const szMaster[], pActivator) { + if (!equal(szMaster, NULL_STRING)) { + new pMaster = engfunc(EngFunc_FindEntityByString, 0, "targetname", szMaster); + + if (pMaster && (ExecuteHam(Ham_ObjectCaps, pMaster) & FCAP_MASTER)) { + return !!ExecuteHamB(Ham_IsTriggered, pMaster, pActivator); + } + } + + return true; +} + +stock UTIL_ParseVector(const szBuffer[], Float:vecOut[3]) { + static rgszOrigin[3][8]; + parse(szBuffer, rgszOrigin[0], charsmax(rgszOrigin[]), rgszOrigin[1], charsmax(rgszOrigin[]), rgszOrigin[2], charsmax(rgszOrigin[])); + + for (new i = 0; i < 3; ++i) { + vecOut[i] = str_to_float(rgszOrigin[i]); + } +} diff --git a/api/custom-entities/future/README.md b/api/custom-entities/future/README.md new file mode 100644 index 0000000..f83ec1f --- /dev/null +++ b/api/custom-entities/future/README.md @@ -0,0 +1,279 @@ +# Custom Entities API + +The Custom Entities API provides a flexible framework for managing and creating custom entities. This API allows developers to register, spawn, manipulate, and interact with custom entities, defining their behavior through hooks and methods. + +## Implementing a Custom Entity + +### πŸ“š Registering a New Entity Class + +To implement a custom entity, the first thing you need to do is register a new entity class using the `CE_RegisterClass` native function. This can be done in the `plugin_precache` function, allowing you to place your entities directly on the map using the registered class as the `classname`. + +Let's create a `key` item entity: + +```pawn +#include +#include +#include + +public plugin_precache() { + CE_RegisterClass("item_key", CEPreset_Item); +} +``` + +In this example, the `CEPreset_Item` preset class is used to implement the item. It inherits logic for items such as pickup methods. + +### βš™οΈ Setting Entity Members + +The entity currently lacks a model and size, so let's provide them by implementing the `Allocate` method for the entity to supply all the necessary members: + +```pawn +public plugin_precache() { + // Precaching key model + precache_model("models/w_security.mdl"); + + CE_RegisterClass("item_key", CEPreset_Item); + + CE_ImplementClassMethod("item_key", CEMethod_Allocate, "@KeyItem_Allocate"); +} + +@KeyItem_Allocate(const this) { + CE_CallBaseMethod(); // Calling the base Allocate method + + CE_SetMemberString(this, CE_MEMBER_MODEL, "models/w_security.mdl"); + CE_SetMemberVec(this, CE_MEMBER_MINS, Float:{-8.0, -8.0, 0.0}); + CE_SetMemberVec(this, CE_MEMBER_MAXS, Float:{8.0, 8.0, 8.0}); +} +``` + +In the implementation of the `Allocate` method, the `CE_CallBaseMethod()` call allows us to invoke the base `Allocate` method of the `CEPreset_Item` preset class, allowing it to handle its own allocation logic before executing custom logic. Make sure to include this call in every implemented or overridden method unless you need to fully rewrite the implementation. + +>[!CAUTION] +> +> The `Allocate` method is called during entity initialization. Modifying entity variables or invoking engine functions on the entity within this method may lead to unexpected results. Use this method only for initializing custom entity members! + +>[!CAUTION] +> +> When calling `CE_CallBaseMethod`, you need to pass all method arguments to ensure the base method receives the necessary context for its operations. + +Natives like `CE_SetMemberString` and `CE_SetMemberVec` are used to set members/properties for the entity instance. Constants such as `CE_MEMBER_*` are used to specify the property names that will set the model each time the entity is spawned or its variables are reset. For example, `CE_MEMBER_MODEL` sets `pev->model` of the entity every respawn. Similarly, `CE_MEMBER_MINS` and `CE_MEMBER_MAXS` specify the entity's bounding box. + +### πŸ’‘ Writing Logic for the Entity + +Our `item_key` entity is functional, allowing you to place the entity with the classname `item_key` on your map. It will spawn in the game and can be picked up. + +However, we still need to add some logic to the entity, as it currently does not perform any specific actions. Let's implement the `Pickup` and `CanPickup` methods in the same way we implemented `Allocate`: + +```pawn +new g_rgbPlayerHasKey[MAX_PLAYERS + 1]; + +public plugin_precache() { + CE_RegisterClass("item_key", CEPreset_Item); + + CE_ImplementClassMethod("item_key", CEMethod_Allocate, "@KeyItem_Allocate"); + CE_ImplementClassMethod("item_key", CEMethod_CanPickup, "@KeyItem_CanPickup"); + CE_ImplementClassMethod("item_key", CEMethod_Pickup, "@KeyItem_Pickup"); +} + +@KeyItem_Allocate(const this) { ... } + +@KeyItem_CanPickup(const this, const pPlayer) { + // Base implementation returns false if the item is not on the ground + if (!CE_CallBaseMethod(pPlayer)) return false; + + // Can't pick up if already holding a key + if (g_rgbPlayerHasKey[pPlayer]) return false; + + return true; +} + +@KeyItem_Pickup(const this, const pPlayer) { + CE_CallBaseMethod(pPlayer); + + client_print(pPlayer, print_center, "You have found a key!"); + + g_rgbPlayerHasKey[pPlayer] = true; +} +``` + +This simple implementation will display the text `"You have found a key!"` to the player who picks up the key and mark that the player has picked up a key. + +### 🧩 Custom Members + +If you want to implement different key types, you can use custom members. Let's update our logic and improve the code: + +```pawn +#include +#include + +#include + +#define ENTITY_CLASSNAME "item_key" + +#define m_iType "iType" + +enum KeyType { + KeyType_Red = 0, + KeyType_Yellow, + KeyType_Green, + KeyType_Blue +}; + +new const KEY_NAMES[KeyType][] = { "red", "yellow", "green", "blue" }; + +new const Float:KEY_COLORS_F[KeyType][3] = { + {255.0, 0.0, 0.0}, + {255.0, 255.0, 0.0}, + {0.0, 255.0, 0.0}, + {0.0, 0.0, 255.0}, +}; + +new const g_szModel[] = "models/w_security.mdl"; + +new bool:g_rgbPlayerHasKey[MAX_PLAYERS + 1][KeyType]; + +public plugin_precache() { + precache_model(g_szModel); + + CE_RegisterClass(ENTITY_CLASSNAME, CEPreset_Item); + + CE_ImplementClassMethod(ENTITY_CLASSNAME, CEMethod_Allocate, "@KeyItem_Allocate"); + CE_ImplementClassMethod(ENTITY_CLASSNAME, CEMethod_Spawn, "@KeyItem_Spawn"); + CE_ImplementClassMethod(ENTITY_CLASSNAME, CEMethod_CanPickup, "@KeyItem_CanPickup"); + CE_ImplementClassMethod(ENTITY_CLASSNAME, CEMethod_Pickup, "@KeyItem_Pickup"); + + // Bind the "type" entity key to the "m_iType" entity member + CE_RegisterClassKeyMemberBinding(ENTITY_CLASSNAME, "type", m_iType, CEMemberType_Cell); +} + +@KeyItem_Allocate(const this) { + CE_CallBaseMethod(); + + CE_SetMemberString(this, CE_MEMBER_MODEL, g_szModel); + CE_SetMemberVec(this, CE_MEMBER_MINS, Float:{-8.0, -8.0, 0.0}); + CE_SetMemberVec(this, CE_MEMBER_MAXS, Float:{8.0, 8.0, 8.0}); + + CE_SetMember(this, m_iType, KeyType_Red); // Default key type +} + +@KeyItem_Spawn(const this) { + CE_CallBaseMethod(); + + new KeyType:iType = CE_GetMember(this, m_iType); + + // Adding rendering effect based on key type + set_pev(this, pev_renderfx, kRenderFxGlowShell); + set_pev(this, pev_renderamt, 1.0); + set_pev(this, pev_rendercolor, KEY_COLORS_F[iType]); +} + +@KeyItem_CanPickup(const this, const pPlayer) { + if (!CE_CallBaseMethod(pPlayer)) return false; + + new KeyType:iType = CE_GetMember(this, m_iType); + + if (g_rgbPlayerHasKey[pPlayer][iType]) return false; + + return true; +} + +@KeyItem_Pickup(const this, const pPlayer) { + CE_CallBaseMethod(pPlayer); + + new KeyType:iType = CE_GetMember(this, m_iType); + + client_print(pPlayer, print_center, "You have found a %s key!", KEY_NAMES[iType]); + + g_rgbPlayerHasKey[pPlayer][iType] = true; +} +``` + +Here, we added `KeyType` constants to represent different key types and implemented the `Spawn` method to set rendering effects based on the key type. + +You may have noticed the constant `m_iType`, which is a string constant used for the custom member we work with using `CE_GetMember` and `CE_SetMember` natives. We also use `CE_RegisterClassKeyMemberBinding` to bind this member to the entity key `type`, allowing us to change the key type by setting the `type` key-value on the map. + +### πŸ“¦ Custom Methods + +This implementation has a small issue: changing the `iType` member does not immediately update the entity's color until the entity respawns. To resolve this, let's add `SetType` and `UpdateColor` methods to change the type correctly and update the entity's color in real time: + +```pawn +#define SetType "SetType" +#define UpdateColor "UpdateColor" + +public plugin_precache() { + ... + + // Registering new class methods + CE_RegisterClassMethod(ENTITY_CLASSNAME, SetType, "@KeyItem_SetType", CE_MP_Cell); + CE_RegisterClassMethod(ENTITY_CLASSNAME, UpdateColor, "@KeyItem_UpdateColor"); + + ... +} + +@KeyItem_Allocate(const this) { ... } + +@KeyItem_Spawn(const this) { + CE_CallBaseMethod(); + + // Calling UpdateColor method to set color + CE_CallMethod(this, UpdateColor); +} + +@KeyItem_CanPickup(const this, const pPlayer) { ... } +@KeyItem_Pickup(const this, const pPlayer) { ... } + +@KeyItem_SetType(const this, const iType) { + CE_SetMember(this, m_iType, iType); + + // Calling UpdateColor method to apply color change + CE_CallMethod(this, UpdateColor); +} + +@KeyItem_UpdateColor(const this) { + new KeyType:iType = CE_GetMember(this, m_iType); + + set_pev(this, pev_renderfx, kRenderFxGlowShell); + set_pev(this, pev_renderamt, 1.0); + set_pev(this, pev_rendercolor, KEY_COLORS_F[iType]); +} +``` + +### πŸ•΅οΈβ€β™‚οΈ Testing and Debugging + +> What if we don't have a map yet to test it? Is there another way to spawn our entity? + +Yes, there are a few ways to do it! + +#### Spawning an Entity Using the Console + +You can spawn an entity using the console command `ce_spawn [...members]`. The `` parameter is the `classname` of the registered entity, and `[...members]` are optional parameters to set members before spawning. Let's spawn a `"Green"` key: + +```bash +ce_spawn "item_key" "iType" 3 +``` + +You can also invoke methods using the console command `ce_call_method [...params]`. In this command, the `` parameter is the entity index, `` is the entity class, `` is the method to call, and `[...params]` are optional parameters for method arguments. For example, to use the `SetType` method to change the key type to `Blue`, use: + +```bash +ce_call_method "item_key" "SetType" 3 +``` + +Replace `` with the desired entity index, which you can obtain from the console message after executing `ce_spawn` or by running `ce_list` to get a list of custom entities. + + +#### Spawning an Entity with Code + +You can also create the entity using the `CE_Create` native function and then call the engine `Spawn` function on it: + +```pawn +new pKey = CE_Create("item_key", vecOrigin); + +if (pKey != FM_NULLENT) { + CE_SetMember(pKey, "iType", 3); + dllfunc(DLLFunc_Spawn, pKey); +} +``` + +You can also call the `SetType` method to change the key type to `Blue`: +```pawn +CE_CallMethod(pKey, "SetType", 3); +``` diff --git a/api/custom-entities/future/api_custom_entities_future.sma b/api/custom-entities/future/api_custom_entities_future.sma new file mode 100644 index 0000000..c67cc62 --- /dev/null +++ b/api/custom-entities/future/api_custom_entities_future.sma @@ -0,0 +1,2154 @@ +#pragma semicolon 1 + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define IS_PLAYER(%1) (%1 >= 1 && %1 <= MaxClients) + +#define ERROR_IS_ALREADY_REGISTERED "%s Entity with class ^"%s^" is already registered." +#define ERROR_IS_NOT_REGISTERED "%s Entity ^"%s^" is not registered." +#define ERROR_FUNCTION_NOT_FOUND "%s Function ^"%s^" not found in plugin ^"%s^"." +#define ERROR_IS_NOT_REGISTERED_BASE "%s Cannot extend entity class ^"%s^". The class is not exists!" +#define ERROR_CANNOT_CREATE_UNREGISTERED "%s Failed to create entity ^"%s^"! Entity is not registered!" +#define ERROR_CANNOT_CREATE_ABSTRACT "%s Failed to create entity ^"%s^"! Entity is abstract!" + +#define LOG_PREFIX "[CE]" + +#define MAX_ENTITIES 2048 +#define MAX_ENTITY_CLASSES 512 + +#define CLASS_METADATA_NAME "__NAME" +#define CLASS_METADATA_CE_ID "__CE_ID" + +#define CE_INVALID_ID -1 +#define CE_INVALID_HOOK_ID -1 + +enum _:GLOBALESTATE { GLOBAL_OFF = 0, GLOBAL_ON = 1, GLOBAL_DEAD = 2 }; + +enum EntityFlags (<<=1) { + EntityFlag_None = 0, + EntityFlag_Abstract = 1, +} + +enum Entity { + Entity_Id, + Class:Entity_Class, + EntityFlags:Entity_Flags, + Trie:Entity_KeyMemberBindings, + Array:Entity_Hierarchy, + Array:Entity_MethodPreHooks[CEMethod], + Array:Entity_MethodPostHooks[CEMethod], + Entity_TotalHooksCounter[CEMethod] // Used as cache to increase hook call performance +}; + +enum EntityMethodPointer { + EntityMethodPointer_Think, + EntityMethodPointer_Touch, + EntityMethodPointer_Use, + EntityMethodPointer_Blocked +}; + +enum EntityMethodParams { + EntityMethodParams_Num, + EntityMethodParams_Types[6] +}; + +STACK_DEFINE(METHOD_PLUGIN); +STACK_DEFINE(METHOD_RETURN); +STACK_DEFINE(PREHOOK_RETURN); + +new const g_rgEntityMethodParams[CEMethod][EntityMethodParams] = { + /* Allocate */ {0}, + /* Free */ {0}, + /* KeyValue */ {2, {CMP_String, CMP_String}}, + /* SpawnInit */ {0}, + /* Spawn */ {0}, + /* ResetVariables */ {0}, + /* UpdatePhysics */ {0}, + /* UpdateModel */ {0}, + /* UpdateSize */ {0}, + /* Touch */ {1, {CMP_Cell}}, + /* Think */ {0}, + /* CanPickup */ {1, {CMP_Cell}}, + /* Pickup */ {1, {CMP_Cell}}, + /* CanTrigger */ {1, {CMP_Cell}}, + /* Trigger */ {1, {CMP_Cell}}, + /* Restart */ {0}, + /* Kill */ {2, {CMP_Cell, CMP_Cell}}, + /* IsMasterTriggered */ {1, {CMP_Cell}}, + /* ObjectCaps */ {0}, + /* BloodColor */ {0}, + /* Use */ {4, {CMP_Cell, CMP_Cell, CMP_Cell, CMP_Cell}}, + /* Blocked */ {1, {CMP_Cell}}, + /* GetDelay */ {0}, + /* Classify */ {0}, + /* IsTriggered */ {1, {CMP_Cell}}, + /* GetToggleState */ {0}, + /* SetToggleState */ {1, {CMP_Cell}}, + /* Respawn */ {0}, + /* TraceAttack */ {6, {CMP_Cell, CMP_Cell, CMP_Array, 3, CMP_Cell, CMP_Cell}} +}; + +new g_iszBaseClassName; +new bool:g_bIsCStrike = false; + +new g_rgPresetEntityIds[CEPreset]; +new Trie:g_itEntityIds = Invalid_Trie; + +new g_rgEntities[MAX_ENTITY_CLASSES][Entity]; +new g_iEntityClassesNum = 0; + +new Struct:g_rgEntityMethodPointers[MAX_ENTITIES][EntityMethodPointer]; +new ClassInstance:g_rgEntityClassInstances[MAX_ENTITIES]; + +new HamHook:g_rgMethodHamHooks[CEMethod]; + +public plugin_precache() { + g_bIsCStrike = !!cstrike_running(); + g_iszBaseClassName = engfunc(EngFunc_AllocString, CE_BASE_CLASSNAME); + + InitStorages(); + InitBaseClasses(); + InitHooks(); +} + +public plugin_init() { + register_plugin("[API] Custom Entities", "2.0.0", "Hedgehog Fog"); + + register_concmd("ce_spawn", "Command_Spawn", ADMIN_CVAR); + register_concmd("ce_get_member", "Command_GetMember", ADMIN_CVAR); + register_concmd("ce_get_member_float", "Command_GetMemberFloat", ADMIN_CVAR); + register_concmd("ce_get_member_string", "Command_GetMemberString", ADMIN_CVAR); + register_concmd("ce_set_member", "Command_SetMember", ADMIN_CVAR); + register_concmd("ce_call_method", "Command_CallMethod", ADMIN_CVAR); + register_concmd("ce_list", "Command_List", ADMIN_CVAR); +} + +public plugin_natives() { + register_library("api_custom_entities"); + + register_native("CE_RegisterClass", "Native_Register"); + register_native("CE_RegisterClassDerived", "Native_RegisterDerived"); + register_native("CE_RegisterClassAlias", "Native_RegisterAlias"); + register_native("CE_GetClassHandler", "Native_GetHandler"); + + register_native("CE_RegisterClassKeyMemberBinding", "Native_RegisterKeyMemberBinding"); + register_native("CE_RemoveClassKeyMemberBinding", "Native_RemoveMemberBinding"); + + register_native("CE_RegisterClassMethod", "Native_RegisterMethod"); + register_native("CE_ImplementClassMethod", "Native_ImplementMethod"); + register_native("CE_RegisterClassVirtualMethod", "Native_RegisterVirtualMethod"); + + register_native("CE_RegisterClassHook", "Native_RegisterHook"); + register_native("CE_GetMethodReturn", "Native_GetMethodReturn"); + register_native("CE_SetMethodReturn", "Native_SetMethodReturn"); + + register_native("CE_Create", "Native_Create"); + register_native("CE_Kill", "Native_Kill"); + register_native("CE_Remove", "Native_Remove"); + register_native("CE_Restart", "Native_Restart"); + register_native("CE_GetHandler", "Native_GetHandlerByEntity"); + register_native("CE_IsInstanceOf", "Native_IsInstanceOf"); + + register_native("CE_SetThink", "Native_SetThink"); + register_native("CE_SetTouch", "Native_SetTouch"); + register_native("CE_SetUse", "Native_SetUse"); + register_native("CE_SetBlocked", "Native_SetBlocked"); + + register_native("CE_HasMember", "Native_HasMember"); + register_native("CE_GetMember", "Native_GetMember"); + register_native("CE_DeleteMember", "Native_DeleteMember"); + register_native("CE_SetMember", "Native_SetMember"); + register_native("CE_GetMemberVec", "Native_GetMemberVec"); + register_native("CE_SetMemberVec", "Native_SetMemberVec"); + register_native("CE_GetMemberString", "Native_GetMemberString"); + register_native("CE_SetMemberString", "Native_SetMemberString"); + + register_native("CE_CallMethod", "Native_CallMethod"); + register_native("CE_CallBaseMethod", "Native_CallBaseMethod"); + register_native("CE_GetCallerPlugin", "Native_GetCallPluginId"); +} + +public plugin_end() { + DestroyRegisteredClasses(); + DestroyStorages(); +} + +/*--------------------------------[ Natives ]--------------------------------*/ + +public Native_Register(iPluginId, iArgc) { + new szClassname[CE_MAX_NAME_LENGTH]; get_string(1, szClassname, charsmax(szClassname)); + new CEPreset:iPreset = CEPreset:get_param(2); + new bool:bAbstract = bool:get_param(3); + + if (iPreset == CEPreset_Invalid) { + log_amx("Cannot register entity without preset!"); + return -1; + } + + new EntityFlags:iFlags = bAbstract ? EntityFlag_Abstract : EntityFlag_None; + + return RegisterEntityClass(szClassname, iPreset, iFlags); +} + +public Native_RegisterDerived(iPluginId, iArgc) { + new szClassname[CE_MAX_NAME_LENGTH]; get_string(1, szClassname, charsmax(szClassname)); + new szBaseClassName[CE_MAX_NAME_LENGTH]; get_string(2, szBaseClassName, charsmax(szBaseClassName)); + new bool:bAbstract = bool:get_param(3); + + new EntityFlags:iFlags = bAbstract ? EntityFlag_Abstract : EntityFlag_None; + + return RegisterEntityClass(szClassname, _, iFlags, szBaseClassName); +} + +public Native_RegisterAlias(iPluginId, iArgc) { + new szAlias[CE_MAX_NAME_LENGTH]; get_string(1, szAlias, charsmax(szAlias)); + new szClassname[CE_MAX_NAME_LENGTH]; get_string(2, szClassname, charsmax(szClassname)); + + if (GetIdByClassName(szAlias) != CE_INVALID_ID) { + log_error(AMX_ERR_NATIVE, ERROR_IS_ALREADY_REGISTERED, LOG_PREFIX, szAlias); + return; + } + + new iId = GetIdByClassName(szClassname); + if (iId == CE_INVALID_ID) { + log_error(AMX_ERR_NATIVE, ERROR_IS_NOT_REGISTERED, LOG_PREFIX, szClassname); + return; + } + + TrieSetCell(g_itEntityIds, szAlias, iId); +} + +public Native_Create(iPluginId, iArgc) { + static szClassname[CE_MAX_NAME_LENGTH]; get_string(1, szClassname, charsmax(szClassname)); + static Float:vecOrigin[3]; get_array_f(2, vecOrigin, 3); + static bool:bTemp; bTemp = !!get_param(3); + + new pEntity = CreateEntity(szClassname, vecOrigin, bTemp); + if (pEntity == FM_NULLENT) return FM_NULLENT; + + new ClassInstance:pInstance = @Entity_GetInstance(pEntity); + ClassInstanceSetMember(pInstance, CE_MEMBER_PLUGINID, iPluginId); + + return pEntity; +} + +public Native_Kill(iPluginId, iArgc) { + new pEntity = get_param_byref(1); + new pKiller = get_param_byref(2); + + if (!@Entity_IsCustom(pEntity)) return; + + ExecuteMethod(CEMethod_Killed, pEntity, pKiller, false); +} + +public bool:Native_Remove(iPluginId, iArgc) { + new pEntity = get_param_byref(1); + + if (!@Entity_IsCustom(pEntity)) return; + + set_pev(pEntity, pev_flags, pev(pEntity, pev_flags) | FL_KILLME); + dllfunc(DLLFunc_Think, pEntity); +} + +public Native_Restart(iPluginId, iArgc) { + new pEntity = get_param_byref(1); + + if (!@Entity_IsCustom(pEntity)) return; + + ExecuteMethod(CEMethod_Restart, pEntity); +} + +public Native_RegisterHook(iPluginId, iArgc) { + new szClassname[CE_MAX_NAME_LENGTH]; get_string(1, szClassname, charsmax(szClassname)); + new CEMethod:iMethod = CEMethod:get_param(2); + new szCallback[CE_MAX_CALLBACK_NAME_LENGTH]; get_string(3, szCallback, charsmax(szCallback)); + new bool:bPost = bool:get_param(4); + + new Function:fnCallback = get_func_pointer(szCallback, iPluginId); + if (fnCallback == Invalid_FunctionPointer) { + new szFilename[64]; + get_plugin(iPluginId, szFilename, charsmax(szFilename)); + log_error(AMX_ERR_NATIVE, ERROR_FUNCTION_NOT_FOUND, LOG_PREFIX, szCallback, szFilename); + return; + } + + RegisterEntityClassHook(szClassname, iMethod, fnCallback, bool:bPost); +} + +public Native_RegisterMethod(iPluginId, iArgc) { + new szClassname[CE_MAX_NAME_LENGTH]; get_string(1, szClassname, charsmax(szClassname)); + new szMethod[CE_MAX_METHOD_NAME_LENGTH]; get_string(2, szMethod, charsmax(szMethod)); + new szCallback[CE_MAX_CALLBACK_NAME_LENGTH]; get_string(3, szCallback, charsmax(szCallback)); + + new Function:fnCallback = get_func_pointer(szCallback, iPluginId); + + if (fnCallback == Invalid_FunctionPointer) { + new szFilename[64]; + get_plugin(iPluginId, szFilename, charsmax(szFilename)); + log_error(AMX_ERR_NATIVE, ERROR_FUNCTION_NOT_FOUND, LOG_PREFIX, szCallback, szFilename); + return; + } + + new Array:irgParamsTypes = ReadMethodParamsFromNativeCall(4, iArgc); + AddEntityClassMethod(szClassname, szMethod, fnCallback, irgParamsTypes, false); + ArrayDestroy(irgParamsTypes); +} + +public Native_RegisterVirtualMethod(iPluginId, iArgc) { + new szClassname[CE_MAX_NAME_LENGTH]; get_string(1, szClassname, charsmax(szClassname)); + new szMethod[CE_MAX_METHOD_NAME_LENGTH]; get_string(2, szMethod, charsmax(szMethod)); + new szCallback[CE_MAX_CALLBACK_NAME_LENGTH]; get_string(3, szCallback, charsmax(szCallback)); + + new Function:fnCallback = get_func_pointer(szCallback, iPluginId); + + if (fnCallback == Invalid_FunctionPointer) { + new szFilename[64]; + get_plugin(iPluginId, szFilename, charsmax(szFilename)); + log_error(AMX_ERR_NATIVE, ERROR_FUNCTION_NOT_FOUND, LOG_PREFIX, szCallback, szFilename); + return; + } + + new Array:irgParamsTypes = ReadMethodParamsFromNativeCall(4, iArgc); + AddEntityClassMethod(szClassname, szMethod, fnCallback, irgParamsTypes, true); + ArrayDestroy(irgParamsTypes); +} + +public Native_ImplementMethod(iPluginId, iArgc) { + new szClassname[CE_MAX_NAME_LENGTH]; get_string(1, szClassname, charsmax(szClassname)); + new CEMethod:iMethod = CEMethod:get_param(2); + new szCallback[CE_MAX_CALLBACK_NAME_LENGTH]; get_string(3, szCallback, charsmax(szCallback)); + + new Function:fnCallback = get_func_pointer(szCallback, iPluginId); + + if (fnCallback == Invalid_FunctionPointer) { + new szFilename[64]; + get_plugin(iPluginId, szFilename, charsmax(szFilename)); + log_error(AMX_ERR_NATIVE, ERROR_FUNCTION_NOT_FOUND, LOG_PREFIX, szCallback, szFilename); + return; + } + + ImplementEntityClassMethod(szClassname, iMethod, fnCallback); +} + +public Native_RegisterKeyMemberBinding(iPluginId, iArgc) { + new szClassname[CE_MAX_NAME_LENGTH]; get_string(1, szClassname, charsmax(szClassname)); + new szKey[CE_MAX_NAME_LENGTH]; get_string(2, szKey, charsmax(szKey)); + new szMember[CE_MAX_NAME_LENGTH]; get_string(3, szMember, charsmax(szMember)); + new CEMemberType:iType = CEMemberType:get_param(4); + + RegisterEntityClassKeyMemberBinding(szClassname, szKey, szMember, iType); +} + +public Native_RemoveMemberBinding(iPluginId, iArgc) { + new szClassname[CE_MAX_NAME_LENGTH]; get_string(1, szClassname, charsmax(szClassname)); + new szKey[CE_MAX_NAME_LENGTH]; get_string(2, szKey, charsmax(szKey)); + new szMember[CE_MAX_NAME_LENGTH]; get_string(3, szMember, charsmax(szMember)); + + RemoveEntityClassKeyMemberBinding(szClassname, szKey, szMember); +} + +public Native_GetHandler(iPluginId, iArgc) { + static szClassname[CE_MAX_NAME_LENGTH]; get_string(1, szClassname, charsmax(szClassname)); + + return GetIdByClassName(szClassname); +} + +public Native_GetHandlerByEntity(iPluginId, iArgc) { + static pEntity; pEntity = get_param_byref(1); + + static ClassInstance:pInstance; pInstance = @Entity_GetInstance(pEntity); + if (pInstance == Invalid_ClassInstance) return CE_INVALID_ID; + + return ClassInstanceGetMember(pInstance, CE_MEMBER_ID); +} + +public bool:Native_IsInstanceOf(iPluginId, iArgc) { + static pEntity; pEntity = get_param_byref(1); + static szClassname[CE_MAX_NAME_LENGTH]; get_string(2, szClassname, charsmax(szClassname)); + + if (!@Entity_IsCustom(pEntity)) return false; + + static iTargetId; iTargetId = GetIdByClassName(szClassname); + if (iTargetId == CE_INVALID_ID) return false; + + new ClassInstance:pInstance = @Entity_GetInstance(pEntity); + + return ClassInstanceIsInstanceOf(pInstance, g_rgEntities[iTargetId][Entity_Class]); +} + +public bool:Native_HasMember(iPluginId, iArgc) { + static pEntity; pEntity = get_param_byref(1); + static szMember[CE_MAX_MEMBER_NAME_LENGTH]; get_string(2, szMember, charsmax(szMember)); + + if (!@Entity_IsCustom(pEntity)) return false; + + new ClassInstance:pInstance = @Entity_GetInstance(pEntity); + + return ClassInstanceHasMember(pInstance, szMember); +} + +public any:Native_GetMember(iPluginId, iArgc) { + static pEntity; pEntity = get_param_byref(1); + static szMember[CE_MAX_MEMBER_NAME_LENGTH]; get_string(2, szMember, charsmax(szMember)); + + if (!@Entity_IsCustom(pEntity)) return 0; + + new ClassInstance:pInstance = @Entity_GetInstance(pEntity); + + return ClassInstanceGetMember(pInstance, szMember); +} + +public Native_DeleteMember(iPluginId, iArgc) { + static pEntity; pEntity = get_param_byref(1); + static szMember[CE_MAX_MEMBER_NAME_LENGTH]; get_string(2, szMember, charsmax(szMember)); + + if (!@Entity_IsCustom(pEntity)) return; + + new ClassInstance:pInstance = @Entity_GetInstance(pEntity); + + ClassInstanceDeleteMember(pInstance, szMember); +} + +public Native_SetMember(iPluginId, iArgc) { + static pEntity; pEntity = get_param_byref(1); + static szMember[CE_MAX_MEMBER_NAME_LENGTH]; get_string(2, szMember, charsmax(szMember)); + static iValue; iValue = get_param(3); + static bool:bReplace; bReplace = bool:get_param(4); + + if (!@Entity_IsCustom(pEntity)) return; + + new ClassInstance:pInstance = @Entity_GetInstance(pEntity); + + ClassInstanceSetMember(pInstance, szMember, iValue, bReplace); +} + +public bool:Native_GetMemberVec(iPluginId, iArgc) { + static pEntity; pEntity = get_param_byref(1); + static szMember[CE_MAX_MEMBER_NAME_LENGTH]; get_string(2, szMember, charsmax(szMember)); + + if (!@Entity_IsCustom(pEntity)) return false; + + new ClassInstance:pInstance = @Entity_GetInstance(pEntity); + + static Float:vecValue[3]; + if (!ClassInstanceGetMemberArray(pInstance, szMember, vecValue, 3)) return false; + + set_array_f(3, vecValue, sizeof(vecValue)); + + return true; +} + +public Native_SetMemberVec(iPluginId, iArgc) { + static pEntity; pEntity = get_param_byref(1); + static szMember[CE_MAX_MEMBER_NAME_LENGTH]; get_string(2, szMember, charsmax(szMember)); + static Float:vecValue[3]; get_array_f(3, vecValue, sizeof(vecValue)); + static bool:bReplace; bReplace = bool:get_param(4); + + if (!@Entity_IsCustom(pEntity)) return; + + new ClassInstance:pInstance = @Entity_GetInstance(pEntity); + ClassInstanceSetMemberArray(pInstance, szMember, vecValue, 3, bReplace); +} + +public bool:Native_GetMemberString(iPluginId, iArgc) { + static pEntity; pEntity = get_param_byref(1); + static szMember[CE_MAX_MEMBER_NAME_LENGTH]; get_string(2, szMember, charsmax(szMember)); + + if (!@Entity_IsCustom(pEntity)) return false; + + new ClassInstance:pInstance = @Entity_GetInstance(pEntity); + + static szValue[128]; + if (!ClassInstanceGetMemberString(pInstance, szMember, szValue, charsmax(szValue))) return false; + + set_string(3, szValue, get_param(4)); + + return true; +} + +public Native_SetMemberString(iPluginId, iArgc) { + static pEntity; pEntity = get_param_byref(1); + static szMember[CE_MAX_MEMBER_NAME_LENGTH]; get_string(2, szMember, charsmax(szMember)); + static szValue[128]; get_string(3, szValue, charsmax(szValue)); + static bool:bReplace; bReplace = bool:get_param(4); + + if (!@Entity_IsCustom(pEntity)) return; + + new ClassInstance:pInstance = @Entity_GetInstance(pEntity); + ClassInstanceSetMemberString(pInstance, szMember, szValue, bReplace); +} + +public any:Native_CallMethod(iPluginId, iArgc) { + static pEntity; pEntity = get_param_byref(1); + static szMethod[CE_MAX_METHOD_NAME_LENGTH]; get_string(2, szMethod, charsmax(szMethod)); + + static ClassInstance:pInstance; pInstance = @Entity_GetInstance(pEntity); + + STACK_PUSH(METHOD_PLUGIN, iPluginId); + + ClassInstanceCallMethodBegin(pInstance, szMethod); + + ClassInstanceCallMethodPushParamCell(pEntity); + + static iParam; + for (iParam = 3; iParam <= iArgc; ++iParam) { + ClassInstanceCallMethodPushNativeParam(iParam); + } + + static any:result; result = ClassInstanceCallMethodEnd(); + + STACK_POP(METHOD_PLUGIN); + + return result; +} + +public any:Native_CallBaseMethod(iPluginId, iArgc) { + static ClassInstance:pInstance; pInstance = ClassInstanceGetCurrent(); + static pEntity; pEntity = ClassInstanceGetMember(pInstance, CE_MEMBER_POINTER); + + STACK_PUSH(METHOD_PLUGIN, iPluginId); + + ClassInstanceCallMethodBeginBase(); + + ClassInstanceCallMethodPushParamCell(pEntity); + + static iParam; + for (iParam = 1; iParam <= iArgc; ++iParam) { + ClassInstanceCallMethodPushNativeParam(iParam); + } + + static any:result; result = ClassInstanceCallMethodEnd(); + + STACK_POP(METHOD_PLUGIN); + + return result; +} + +public Native_GetCallPluginId(iPluginId, iArgc) { + return STACK_READ(METHOD_PLUGIN); +} + +Class:ResolveEntityCallClass(const &pEntity, const szClassname[]) { + if (!equal(szClassname, NULL_STRING)) { + static iId; iId = GetIdByClassName(szClassname); + return g_rgEntities[iId][Entity_Class]; + } + + static Class:cEntity; cEntity = ClassInstanceGetCurrentClass(); + if (cEntity != Invalid_Class) return cEntity; + + static ClassInstance:pInstance; pInstance = @Entity_GetInstance(pEntity); + return ClassInstanceGetClass(pInstance); +} + +public Native_SetThink(iPluginId, iArgc) { + static pEntity; pEntity = get_param_byref(1); + static szMethod[CE_MAX_METHOD_NAME_LENGTH]; get_string(2, szMethod, charsmax(szMethod)); + static szClassname[CE_MAX_NAME_LENGTH]; get_string(3, szClassname, charsmax(szClassname)); + + static Class:cCurrent; cCurrent = ResolveEntityCallClass(pEntity, szClassname); + + if (!equal(szMethod, NULL_STRING)) { + g_rgEntityMethodPointers[pEntity][EntityMethodPointer_Think] = ClassGetMethodPointer(cCurrent, szMethod); + } else { + g_rgEntityMethodPointers[pEntity][EntityMethodPointer_Think] = Invalid_Struct; + } + + ClassGetMetadataString(cCurrent, CLASS_METADATA_NAME, szClassname, charsmax(szClassname)); +} + +public Native_SetTouch(iPluginId, iArgc) { + static pEntity; pEntity = get_param_byref(1); + static szMethod[CE_MAX_METHOD_NAME_LENGTH]; get_string(2, szMethod, charsmax(szMethod)); + static szClassname[CE_MAX_NAME_LENGTH]; get_string(3, szClassname, charsmax(szClassname)); + + static Class:cCurrent; cCurrent = ResolveEntityCallClass(pEntity, szClassname); + + if (!equal(szMethod, NULL_STRING)) { + g_rgEntityMethodPointers[pEntity][EntityMethodPointer_Touch] = ClassGetMethodPointer(cCurrent, szMethod); + } else { + g_rgEntityMethodPointers[pEntity][EntityMethodPointer_Touch] = Invalid_Struct; + } +} + +public Native_SetUse(iPluginId, iArgc) { + static pEntity; pEntity = get_param_byref(1); + static szMethod[CE_MAX_METHOD_NAME_LENGTH]; get_string(2, szMethod, charsmax(szMethod)); + static szClassname[CE_MAX_NAME_LENGTH]; get_string(3, szClassname, charsmax(szClassname)); + + static Class:cCurrent; cCurrent = ResolveEntityCallClass(pEntity, szClassname); + + if (!equal(szMethod, NULL_STRING)) { + g_rgEntityMethodPointers[pEntity][EntityMethodPointer_Use] = ClassGetMethodPointer(cCurrent, szMethod); + } else { + g_rgEntityMethodPointers[pEntity][EntityMethodPointer_Use] = Invalid_Struct; + } +} + +public Native_SetBlocked(iPluginId, iArgc) { + static pEntity; pEntity = get_param_byref(1); + static szMethod[CE_MAX_METHOD_NAME_LENGTH]; get_string(2, szMethod, charsmax(szMethod)); + static szClassname[CE_MAX_NAME_LENGTH]; get_string(3, szClassname, charsmax(szClassname)); + + static Class:cCurrent; cCurrent = ResolveEntityCallClass(pEntity, szClassname); + + if (!equal(szMethod, NULL_STRING)) { + g_rgEntityMethodPointers[pEntity][EntityMethodPointer_Blocked] = ClassGetMethodPointer(cCurrent, szMethod); + } else { + g_rgEntityMethodPointers[pEntity][EntityMethodPointer_Blocked] = Invalid_Struct; + } +} + +public any:Native_GetMethodReturn(iPluginId, iArgc) { + return STACK_READ(METHOD_RETURN); +} + +public any:Native_SetMethodReturn(iPluginId, iArgc) { + STACK_PATCH(METHOD_RETURN, any:get_param(1)); +} + +/*--------------------------------[ Commands ]--------------------------------*/ + +public Command_Spawn(pPlayer, iLevel, iCId) { + if (!cmd_access(pPlayer, iLevel, iCId, 2)) return PLUGIN_HANDLED; + + static szClassname[32]; read_argv(1, szClassname, charsmax(szClassname)); + + if (equal(szClassname, NULL_STRING)) return PLUGIN_HANDLED; + + new Float:vecOrigin[3]; pev(pPlayer, pev_origin, vecOrigin); + + new pEntity = CreateEntity(szClassname, vecOrigin, true); + if (pEntity == FM_NULLENT) return PLUGIN_HANDLED; + + new iArgsNum = read_argc(); + if (iArgsNum > 2) { + new ClassInstance:pInstance = @Entity_GetInstance(pEntity); + + for (new iArg = 2; iArg < iArgsNum; iArg += 2) { + static szMember[32]; read_argv(iArg, szMember, charsmax(szMember)); + static szValue[32]; read_argv(iArg + 1, szValue, charsmax(szValue)); + static iType; iType = UTIL_GetStringType(szValue); + + switch (iType) { + case 'i': ClassInstanceSetMember(pInstance, szMember, str_to_num(szValue)); + case 'f': ClassInstanceSetMember(pInstance, szMember, str_to_float(szValue)); + case 's': ClassInstanceSetMemberString(pInstance, szMember, szValue); + } + } + } + + dllfunc(DLLFunc_Spawn, pEntity); + + console_print(pPlayer, "Entity ^"%s^" successfully spawned! Entity index: %d", szClassname, pEntity); + + return PLUGIN_HANDLED; +} + +public Command_GetMember(pPlayer, iLevel, iCId) { + if (!cmd_access(pPlayer, iLevel, iCId, 3)) return PLUGIN_HANDLED; + + new pEntity = read_argv_int(1); + + new ClassInstance:pInstance = @Entity_GetInstance(pEntity); + if (pInstance == Invalid_ClassInstance) { + console_print(pPlayer, "Entity %d is not a custom entity", pEntity); + return PLUGIN_HANDLED; + } + + static szMember[CLASS_METHOD_MAX_NAME_LENGTH]; read_argv(2, szMember, charsmax(szMember)); + + console_print(pPlayer, "Member ^"%s^" value: %d", szMember, ClassInstanceGetMember(pInstance, szMember)); + + return PLUGIN_HANDLED; +} + +public Command_GetMemberFloat(pPlayer, iLevel, iCId) { + if (!cmd_access(pPlayer, iLevel, iCId, 3)) return PLUGIN_HANDLED; + + new pEntity = read_argv_int(1); + + new ClassInstance:pInstance = @Entity_GetInstance(pEntity); + if (pInstance == Invalid_ClassInstance) { + console_print(pPlayer, "Entity %d is not a custom entity", pEntity); + return PLUGIN_HANDLED; + } + + static szMember[CLASS_METHOD_MAX_NAME_LENGTH]; read_argv(2, szMember, charsmax(szMember)); + + console_print(pPlayer, "Member ^"%s^" value: %f", szMember, Float:ClassInstanceGetMember(pInstance, szMember)); + + return PLUGIN_HANDLED; +} + +public Command_GetMemberString(pPlayer, iLevel, iCId) { + if (!cmd_access(pPlayer, iLevel, iCId, 3)) return PLUGIN_HANDLED; + + new pEntity = read_argv_int(1); + + new ClassInstance:pInstance = @Entity_GetInstance(pEntity); + if (pInstance == Invalid_ClassInstance) { + console_print(pPlayer, "Entity %d is not a custom entity", pEntity); + return PLUGIN_HANDLED; + } + + static szMember[CLASS_METHOD_MAX_NAME_LENGTH]; read_argv(2, szMember, charsmax(szMember)); + + static szValue[64]; ClassInstanceGetMemberString(pInstance, szMember, szValue, charsmax(szValue)); + console_print(pPlayer, "Member ^"%s^" value: ^"%s^"", szMember, szValue); + + return PLUGIN_HANDLED; +} + +public Command_SetMember(pPlayer, iLevel, iCId) { + if (!cmd_access(pPlayer, iLevel, iCId, 3)) return PLUGIN_HANDLED; + + new pEntity = read_argv_int(1); + + new ClassInstance:pInstance = @Entity_GetInstance(pEntity); + if (pInstance == Invalid_ClassInstance) { + console_print(pPlayer, "Entity %d is not a custom entity", pEntity); + return PLUGIN_HANDLED; + } + + static szMember[CLASS_METHOD_MAX_NAME_LENGTH]; read_argv(3, szMember, charsmax(szMember)); + + static szValue[32]; read_argv(3, szValue, charsmax(szValue)); + static iType; iType = UTIL_GetStringType(szValue); + + switch (iType) { + case 'i': ClassInstanceSetMember(pInstance, szMember, str_to_num(szValue)); + case 'f': ClassInstanceSetMember(pInstance, szMember, str_to_float(szValue)); + case 's': ClassInstanceSetMemberString(pInstance, szMember, szValue); + } + + switch (iType) { + case 'i', 'f': console_print(pPlayer, "^"%s^" member set to %s", szMember, szValue); + case 's': console_print(pPlayer, "^"%s^" member set to ^"%s^"", szMember, szValue); + } + + return PLUGIN_HANDLED; +} + +public Command_CallMethod(pPlayer, iLevel, iCId) { + if (!cmd_access(pPlayer, iLevel, iCId, 3)) return PLUGIN_HANDLED; + + new pEntity = read_argv_int(1); + + new ClassInstance:pInstance = @Entity_GetInstance(pEntity); + if (pInstance == Invalid_ClassInstance) { + console_print(pPlayer, "Entity %d is not a custom entity", pEntity); + return PLUGIN_HANDLED; + } + + static szClassname[32]; read_argv(2, szClassname, charsmax(szClassname)); + + if (equal(szClassname, NULL_STRING)) return PLUGIN_HANDLED; + + new iId = GetIdByClassName(szClassname); + if (iId == CE_INVALID_ID) return PLUGIN_HANDLED; + + if (!ClassInstanceIsInstanceOf(pInstance, g_rgEntities[iId][Entity_Class])) { + console_print(pPlayer, "Entity %d is not instance of ^"%s^"", pEntity, szClassname); + return PLUGIN_HANDLED; + } + + static szMethod[CLASS_METHOD_MAX_NAME_LENGTH]; read_argv(3, szMethod, charsmax(szMethod)); + + ClassInstanceCallMethodBegin(pInstance, szMethod, g_rgEntities[iId][Entity_Class]); + + ClassInstanceCallMethodPushParamCell(pEntity); + + new iArgsNum = read_argc(); + if (iArgsNum > 4) { + for (new iArg = 4; iArg < iArgsNum; ++iArg) { + static szArg[32]; read_argv(iArg, szArg, charsmax(szArg)); + static iType; iType = UTIL_GetStringType(szArg); + + switch (iType) { + case 'i': ClassInstanceCallMethodPushParamCell(str_to_num(szArg)); + case 'f': ClassInstanceCallMethodPushParamCell(str_to_float(szArg)); + case 's': ClassInstanceCallMethodPushParamString(szArg); + } + } + } + + new any:result = ClassInstanceCallMethodEnd(); + + console_print(pPlayer, "Call ^"%s^" result: (int)%d (float)%f", szMethod, result, result); + + return PLUGIN_HANDLED; +} + +public Command_List(pPlayer, iLevel, iCId) { + if (!cmd_access(pPlayer, iLevel, iCId, 1)) return PLUGIN_HANDLED; + + new iArgsNum = read_argc(); + + static szFilter[32]; + + if (iArgsNum >= 2) { + read_argv(1, szFilter, charsmax(szFilter)); + } else { + copy(szFilter, charsmax(szFilter), "*"); + } + + new iStart = iArgsNum >= 3 ? read_argv_int(2) : 0; + new iLimit = iArgsNum >= 4 ? read_argv_int(3) : 10; + + new iShowedEntitiesNum = 0; + new iEntitiesNum = 0; + + // console_print(pPlayer, "Finding entities { Start: %d; Limit: %d; Filter: ^"%s^" }", iStart, iLimit, szFilter); + // console_print(pPlayer, "---- Found entities ----"); + + for (new pEntity = iStart; pEntity < sizeof(g_rgEntityClassInstances); ++pEntity) { + if (g_rgEntityClassInstances[pEntity] == Invalid_ClassInstance) continue; + + static ClassInstance:pInstance; pInstance = g_rgEntityClassInstances[pEntity]; + static Class:class; class = ClassInstanceGetClass(pInstance); + // static iId; iId = ClassGetMetadata(class, CLASS_METADATA_CE_ID); + static szClassname[CE_MAX_NAME_LENGTH]; ClassGetMetadataString(class, CLASS_METADATA_NAME, szClassname, charsmax(szClassname)); + + if (!equal(szFilter, "*") && strfind(szClassname, szFilter, true) == -1) continue; + + static Float:vecOrigin[3]; pev(pEntity, pev_origin, vecOrigin); + + if (iShowedEntitiesNum < iLimit) { + console_print(pPlayer, "[%d]^t%s^t{%.3f, %.3f, %.3f}", pEntity, szClassname, vecOrigin[0], vecOrigin[1], vecOrigin[2]); + iShowedEntitiesNum++; + } + + iEntitiesNum++; + } + + // console_print(pPlayer, "Found %d entities. %d of %d are entities showed.", iEntitiesNum, iShowedEntitiesNum, iEntitiesNum); + + return PLUGIN_HANDLED; +} + +/*--------------------------------[ Hooks ]--------------------------------*/ + +public FMHook_OnFreeEntPrivateData(pEntity) { + if (!pev_valid(pEntity)) return; + + if (@Entity_IsCustom(pEntity)) { + ExecuteMethod(CEMethod_Free, pEntity); + } +} + +public FMHook_KeyValue(pEntity, hKVD) { + @Entity_KeyValue(pEntity, hKVD); + + return FMRES_HANDLED; +} + +public FMHook_Spawn(pEntity) { + new ClassInstance:pInstance = @Entity_GetInstance(pEntity); + + // Update entity classname (in case entity spawned by the engine) + if (pInstance != Invalid_ClassInstance) { + static Class:class; class = ClassInstanceGetClass(pInstance); + + static szClassname[CE_MAX_NAME_LENGTH]; + ClassGetMetadataString(class, CLASS_METADATA_NAME, szClassname, charsmax(szClassname)); + set_pev(pEntity, pev_classname, szClassname); + } +} + +public HamHook_Base_Spawn(pEntity) { + if (@Entity_IsCustom(pEntity)) { + ExecuteMethod(CEMethod_Spawn, pEntity); + + return HAM_SUPERCEDE; + } + + return HAM_IGNORED; +} + +public HamHook_Base_ObjectCaps(pEntity) { + if (@Entity_IsCustom(pEntity)) { + new iObjectCaps = ExecuteMethod(CEMethod_ObjectCaps, pEntity); + SetHamReturnInteger(iObjectCaps); + + return HAM_OVERRIDE; + } + + return HAM_IGNORED; +} + +public HamHook_Base_Restart_Post(pEntity) { + if (@Entity_IsCustom(pEntity)) { + ExecuteMethod(CEMethod_Restart, pEntity); + + return HAM_HANDLED; + } + + return HAM_IGNORED; +} + +public HamHook_Base_Touch_Post(pEntity, pToucher) { + if (@Entity_IsCustom(pEntity)) { + ExecuteMethod(CEMethod_Touch, pEntity, pToucher); + + return HAM_HANDLED; + } + + return HAM_IGNORED; +} + +public HamHook_Base_Use_Post(pEntity, pCaller, pActivator, iUseType, Float:flValue) { + if (@Entity_IsCustom(pEntity)) { + ExecuteMethod(CEMethod_Use, pEntity, pCaller, pActivator, iUseType, flValue); + + return HAM_HANDLED; + } + + return HAM_IGNORED; +} + +public HamHook_Base_Blocked_Post(pEntity, pOther) { + if (@Entity_IsCustom(pEntity)) { + ExecuteMethod(CEMethod_Blocked, pEntity, pOther); + + return HAM_HANDLED; + } + + return HAM_IGNORED; +} + +public HamHook_Base_Killed(pEntity, pKiller, iShouldGib) { + if (@Entity_IsCustom(pEntity)) { + ExecuteMethod(CEMethod_Killed, pEntity, pKiller, iShouldGib); + + return HAM_SUPERCEDE; + } + + return HAM_IGNORED; +} + +public HamHook_Base_Think(pEntity) { + if (@Entity_IsCustom(pEntity)) { + ExecuteMethod(CEMethod_Think, pEntity); + + return HAM_HANDLED; + } + + return HAM_IGNORED; +} + +public HamHook_Base_BloodColor(pEntity) { + if (@Entity_IsCustom(pEntity)) { + static iBloodColor; iBloodColor = ExecuteMethod(CEMethod_BloodColor, pEntity); + SetHamReturnInteger(iBloodColor); + + return HAM_OVERRIDE; + } + + return HAM_IGNORED; +} + +public HamHook_Base_GetDelay(pEntity) { + if (@Entity_IsCustom(pEntity)) { + static Float:flDelay; flDelay = ExecuteMethod(CEMethod_GetDelay, pEntity); + SetHamReturnFloat(flDelay); + + return HAM_OVERRIDE; + } + + return HAM_IGNORED; +} + +public HamHook_Base_Classify(pEntity) { + if (@Entity_IsCustom(pEntity)) { + static iClass; iClass = ExecuteMethod(CEMethod_Classify, pEntity); + SetHamReturnInteger(iClass); + + return HAM_OVERRIDE; + } + + return HAM_IGNORED; +} + +public HamHook_Base_IsTriggered(pEntity, pActivator) { + if (@Entity_IsCustom(pEntity)) { + static iTriggered; iTriggered = ExecuteMethod(CEMethod_IsTriggered, pEntity, pActivator); + SetHamReturnInteger(iTriggered); + + return HAM_OVERRIDE; + } + + return HAM_IGNORED; +} + +public HamHook_Base_GetToggleState(pEntity) { + if (@Entity_IsCustom(pEntity)) { + static iState; iState = ExecuteMethod(CEMethod_GetToggleState, pEntity); + SetHamReturnInteger(iState); + + return HAM_OVERRIDE; + } + + return HAM_IGNORED; +} + +public HamHook_Base_SetToggleState(pEntity, iState) { + if (@Entity_IsCustom(pEntity)) { + ExecuteMethod(CEMethod_SetToggleState, pEntity, iState); + + return HAM_HANDLED; + } + + return HAM_IGNORED; +} + +public HamHook_Base_Respawn_Post(pEntity) { + if (@Entity_IsCustom(pEntity)) { + ExecuteMethod(CEMethod_Respawn, pEntity); + + return HAM_HANDLED; + } + + return HAM_IGNORED; +} + +public HamHook_Base_TraceAttack_Post(pEntity, pAttacker, Float:flDamage, const Float:vecDirection[3], pTrace, iDamageBits) { + if (@Entity_IsCustom(pEntity)) { + ExecuteMethod(CEMethod_TraceAttack, pEntity, pAttacker, flDamage, vecDirection, pTrace, iDamageBits); + + return HAM_HANDLED; + } + + return HAM_IGNORED; +} + +/*--------------------------------[ Entity Hookable Methods ]--------------------------------*/ + +@Entity_Allocate(const &this, iId, bool:bTemp) { + g_rgEntityClassInstances[this] = ClassInstanceCreate(g_rgEntities[iId][Entity_Class]); + ClassInstanceSetMember(g_rgEntityClassInstances[this], CE_MEMBER_ID, iId); + ClassInstanceSetMember(g_rgEntityClassInstances[this], CE_MEMBER_POINTER, this); + ClassInstanceSetMember(g_rgEntityClassInstances[this], CE_MEMBER_IGNOREROUNDS, false); + ClassInstanceSetMember(g_rgEntityClassInstances[this], CE_MEMBER_WORLD, !bTemp); + + ExecuteMethod(CEMethod_Allocate, this); +} + +@Entity_KeyValue(const &this, const &hKVD) { + new szKey[32]; get_kvd(hKVD, KV_KeyName, szKey, charsmax(szKey)); + new szValue[32]; get_kvd(hKVD, KV_Value, szValue, charsmax(szValue)); + + if (equal(szKey, "classname")) { + new iId = GetIdByClassName(szValue); + if (iId != CE_INVALID_ID) { + // using set_kvd leads to duplicate kvd emit, this check will fix the issue + if (@Entity_GetInstance(this) == Invalid_ClassInstance) { + if (~g_rgEntities[iId][Entity_Flags] & EntityFlag_Abstract) { + set_kvd(hKVD, KV_Value, CE_BASE_CLASSNAME); + + @Entity_Allocate(this, iId, false); + } + } + } else { + // if for some reason data was not assigned + if (@Entity_GetInstance(this) != Invalid_ClassInstance) { + ExecuteMethod(CEMethod_Free, this); + } + } + } + + if (@Entity_GetInstance(this) != Invalid_ClassInstance) { + @Entity_HandleKeyMemberBinding(this, szKey, szValue); + } +} + +@Entity_HandleKeyMemberBinding(const &this, const szKey[], const szValue[]) { + new iResult = CE_IGNORED; + + new ClassInstance:pInstance = @Entity_GetInstance(this); + + new Array:irgHierarchy = Invalid_Array; + new iHierarchySize = 0; + + { + static Class:cClass; cClass = ClassInstanceGetClass(pInstance); + + static iId; iId = ClassGetMetadata(cClass, CLASS_METADATA_CE_ID); + irgHierarchy = g_rgEntities[iId][Entity_Hierarchy]; + iHierarchySize = ArraySize(irgHierarchy); + } + + for (new iHierarchyPos = 0; iHierarchyPos < iHierarchySize; ++iHierarchyPos) { + static iId; iId = ArrayGetCell(irgHierarchy, iHierarchyPos); + + if (g_rgEntities[iId][Entity_KeyMemberBindings] == Invalid_Trie) continue; + + static Trie:itMemberTypes; itMemberTypes = Invalid_Trie; + if (!TrieGetCell(g_rgEntities[iId][Entity_KeyMemberBindings], szKey, itMemberTypes)) continue; + + static TrieIter:itMemberTypesIter; + + for (itMemberTypesIter = TrieIterCreate(itMemberTypes); !TrieIterEnded(itMemberTypesIter); TrieIterNext(itMemberTypesIter)) { + new szMember[32]; TrieIterGetKey(itMemberTypesIter, szMember, charsmax(szMember)); + new CEMemberType:iType; TrieIterGetCell(itMemberTypesIter, iType); + + // log_amx("%s => %s (%s, %d)", szKey, szMember, szValue, iType); + + switch (iType) { + case CEMemberType_Cell: { + ClassInstanceSetMember(pInstance, szMember, str_to_num(szValue)); + } + case CEMemberType_Float: { + ClassInstanceSetMember(pInstance, szMember, str_to_float(szValue)); + } + case CEMemberType_String: { + ClassInstanceSetMemberString(pInstance, szMember, szValue); + } + case CEMemberType_Vector: { + new Float:vecValue[3]; + UTIL_ParseVector(szValue, vecValue); + ClassInstanceSetMemberArray(pInstance, szMember, vecValue, 3); + } + } + } + + TrieIterDestroy(itMemberTypesIter); + } + + return iResult; +} + +ClassInstance:@Entity_GetInstance(const &this) { + return g_rgEntityClassInstances[this]; +} + +@Entity_IsCustom(const &this) { + return g_rgEntityClassInstances[this] != Invalid_ClassInstance; +} + +/*--------------------------------[ Base Class Methods ]--------------------------------*/ + +@Base_Allocate(const this) {} + +@Base_Free(const this) {} + +@Base_Spawn(const this) { + set_pev(this, pev_deadflag, DEAD_NO); + set_pev(this, pev_effects, pev(this, pev_effects) & ~EF_NODRAW); + set_pev(this, pev_flags, pev(this, pev_flags) & ~FL_ONGROUND); +} + +@Base_Respawn(const this) { + dllfunc(DLLFunc_Spawn, this); + + return this; +} + +@Base_TraceAttack(const this, const pAttacker, Float:flDamage, const Float:vecDirection[3], pTrace, iDamageBits) {} + +@Base_Restart(this) { + new iObjectCaps = ExecuteHamB(Ham_ObjectCaps, this); + + if (!g_bIsCStrike) { + if (iObjectCaps & FCAP_MUST_RELEASE) { + set_pev(this, pev_globalname, GLOBAL_DEAD); + set_pev(this, pev_solid, SOLID_NOT); + set_pev(this, pev_flags, pev(this, pev_flags) | FL_KILLME); + set_pev(this, pev_targetname, ""); + + return; + } + } + + if (~iObjectCaps & FCAP_ACROSS_TRANSITION) { + ExecuteHamB(Ham_Respawn, this); + } +} + +@Base_ResetVariables(const this) { + new ClassInstance:pInstance = ClassInstanceGetCurrent(); + + if (ClassInstanceHasMember(pInstance, CE_MEMBER_ORIGIN)) { + static Float:vecOrigin[3]; + ClassInstanceGetMemberArray(pInstance, CE_MEMBER_ORIGIN, vecOrigin, 3); + engfunc(EngFunc_SetOrigin, this, vecOrigin); + } + + if (ClassInstanceHasMember(pInstance, CE_MEMBER_ANGLES)) { + static Float:vecAngles[3]; + ClassInstanceGetMemberArray(pInstance, CE_MEMBER_ANGLES, vecAngles, 3); + set_pev(this, pev_angles, vecAngles); + } + + if (ClassInstanceHasMember(pInstance, CE_MEMBER_TARGETNAME)) { + static szTargetname[32]; + ClassInstanceGetMemberString(pInstance, CE_MEMBER_TARGETNAME, szTargetname, charsmax(szTargetname)); + set_pev(this, pev_targetname, szTargetname); + } + + if (ClassInstanceHasMember(pInstance, CE_MEMBER_TARGET)) { + static szTarget[32]; + ClassInstanceGetMemberString(pInstance, CE_MEMBER_TARGET, szTarget, charsmax(szTarget)); + set_pev(this, pev_target, szTarget); + } + + ClassInstanceCallMethod(pInstance, CE_METHOD_NAMES[CEMethod_UpdatePhysics], this); + ClassInstanceCallMethod(pInstance, CE_METHOD_NAMES[CEMethod_UpdateModel], this); + ClassInstanceCallMethod(pInstance, CE_METHOD_NAMES[CEMethod_UpdateSize], this); + + static bool:bIsWorld; bIsWorld = ClassInstanceGetMember(pInstance, CE_MEMBER_WORLD); + + static Float:flLifeTime; flLifeTime = 0.0; + if (!bIsWorld && ClassInstanceHasMember(pInstance, CE_MEMBER_LIFETIME)) { + flLifeTime = ClassInstanceGetMember(pInstance, CE_MEMBER_LIFETIME); + } + + if (flLifeTime > 0.0) { + static Float:flGameTime; flGameTime = get_gametime(); + ClassInstanceSetMember(pInstance, CE_MEMBER_NEXTKILL, flGameTime + flLifeTime); + set_pev(this, pev_nextthink, flGameTime + flLifeTime); + } else { + ClassInstanceSetMember(pInstance, CE_MEMBER_NEXTKILL, 0.0); + } +} + +@Base_UpdatePhysics(const this) {} + +@Base_UpdateModel(this) { + static ClassInstance:pInstance; pInstance = ClassInstanceGetCurrent(); + + if (ClassInstanceHasMember(pInstance, CE_MEMBER_MODEL)) { + static szModel[MAX_RESOURCE_PATH_LENGTH]; + ClassInstanceGetMemberString(pInstance, CE_MEMBER_MODEL, szModel, charsmax(szModel)); + engfunc(EngFunc_SetModel, this, szModel); + } +} + +@Base_UpdateSize(this) { + static ClassInstance:pInstance; pInstance = ClassInstanceGetCurrent(); + + if (ClassInstanceHasMember(pInstance, CE_MEMBER_MINS) && ClassInstanceHasMember(pInstance, CE_MEMBER_MAXS)) { + static Float:vecMins[3]; ClassInstanceGetMemberArray(pInstance, CE_MEMBER_MINS, vecMins, 3); + static Float:vecMaxs[3]; ClassInstanceGetMemberArray(pInstance, CE_MEMBER_MAXS, vecMaxs, 3); + engfunc(EngFunc_SetSize, this, vecMins, vecMaxs); + } +} + +@Base_Killed(this, const &pKiller, iShouldGib) { + static ClassInstance:pInstance; pInstance = ClassInstanceGetCurrent(); + + ClassInstanceSetMember(pInstance, CE_MEMBER_NEXTKILL, 0.0); + + set_pev(this, pev_takedamage, DAMAGE_NO); + set_pev(this, pev_effects, pev(this, pev_effects) | EF_NODRAW); + set_pev(this, pev_solid, SOLID_NOT); + set_pev(this, pev_movetype, MOVETYPE_NONE); + set_pev(this, pev_flags, pev(this, pev_flags) & ~FL_ONGROUND); + + new bool:bIsWorld = ClassInstanceGetMember(pInstance, CE_MEMBER_WORLD); + + if (bIsWorld) { + if (ClassInstanceHasMember(pInstance, CE_MEMBER_RESPAWNTIME)) { + new Float:flRespawnTime = ClassInstanceGetMember(pInstance, CE_MEMBER_RESPAWNTIME); + new Float:flGameTime = get_gametime(); + + ClassInstanceSetMember(pInstance, CE_MEMBER_NEXTRESPAWN, flGameTime + flRespawnTime); + set_pev(this, pev_deadflag, DEAD_RESPAWNABLE); + set_pev(this, pev_nextthink, flGameTime + flRespawnTime); + } else { + set_pev(this, pev_deadflag, DEAD_DEAD); + } + } else { + set_pev(this, pev_deadflag, DEAD_DISCARDBODY); + set_pev(this, pev_flags, pev(this, pev_flags) | FL_KILLME); + } +} + +@Base_Think(this) { + if (Struct:g_rgEntityMethodPointers[this][EntityMethodPointer_Think] != Invalid_Struct) { + static ClassInstance:pInstance; pInstance = @Entity_GetInstance(this); + ClassInstanceCallMethodByPointer(pInstance, Struct:g_rgEntityMethodPointers[this][EntityMethodPointer_Think], this); + } +} + +@Base_Touch(const this, pToucher) { + if (Struct:g_rgEntityMethodPointers[this][EntityMethodPointer_Touch] != Invalid_Struct) { + static ClassInstance:pInstance; pInstance = @Entity_GetInstance(this); + ClassInstanceCallMethodByPointer(pInstance, Struct:g_rgEntityMethodPointers[this][EntityMethodPointer_Touch], this, pToucher); + } +} + +@Base_Use(const this, const pCaller, const pActivator, iUseType, Float:flValue) { + if (Struct:g_rgEntityMethodPointers[this][EntityMethodPointer_Use] != Invalid_Struct) { + static ClassInstance:pInstance; pInstance = @Entity_GetInstance(this); + ClassInstanceCallMethodByPointer(pInstance, Struct:g_rgEntityMethodPointers[this][EntityMethodPointer_Use], this, pCaller, pActivator, iUseType, flValue); + } +} + +@Base_Blocked(const this, const pBlocker) { + if (Struct:g_rgEntityMethodPointers[this][EntityMethodPointer_Blocked] != Invalid_Struct) { + static ClassInstance:pInstance; pInstance = @Entity_GetInstance(this); + ClassInstanceCallMethodByPointer(pInstance, Struct:g_rgEntityMethodPointers[this][EntityMethodPointer_Blocked], this, pBlocker); + } +} + +bool:@Base_IsMasterTriggered(const this, pActivator) { + new ClassInstance:pInstance = ClassInstanceGetCurrent(); + + static szMaster[32]; ClassInstanceGetMemberString(pInstance, CE_MEMBER_MASTER, szMaster, charsmax(szMaster)); + + return UTIL_IsMasterTriggered(szMaster, pActivator); +} + +@Base_ObjectCaps(const this) { + new ClassInstance:pInstance = ClassInstanceGetCurrent(); + + new bool:bIgnoreRound = ClassInstanceGetMember(pInstance, CE_MEMBER_IGNOREROUNDS); + new bool:bIsWorld = ClassInstanceGetMember(pInstance, CE_MEMBER_WORLD); + + new iObjectCaps = 0; + + if (bIgnoreRound) { + iObjectCaps |= FCAP_ACROSS_TRANSITION; + } else { + iObjectCaps |= bIsWorld ? FCAP_MUST_RESET : FCAP_MUST_RELEASE; + } + + return iObjectCaps; +} + +@Base_BloodColor(const this) { + new ClassInstance:pInstance = ClassInstanceGetCurrent(); + + if (!ClassInstanceHasMember(pInstance, CE_MEMBER_BLOODCOLOR)) return -1; + + return ClassInstanceGetMember(pInstance, CE_MEMBER_BLOODCOLOR); +} + +Float:@Base_GetDelay(const this) { + static ClassInstance:pInstance; pInstance = ClassInstanceGetCurrent(); + + return Float:ClassInstanceGetMember(pInstance, CE_MEMBER_DELAY); +} + +@Base_Classify(const this) { + return 0; +} + +bool:@Base_IsTriggered(const this, const pActivator) { + return true; +} + +@Base_GetToggleState(const this) { + static ClassInstance:pInstance; pInstance = ClassInstanceGetCurrent(); + + return ClassInstanceGetMember(pInstance, CE_MEMBER_TOGGLESTATE); +} + +@Base_SetToggleState(const this, iState) { + static ClassInstance:pInstance; pInstance = ClassInstanceGetCurrent(); + + ClassInstanceSetMember(pInstance, CE_MEMBER_TOGGLESTATE, iState); +} + +/*--------------------------------[ BaseItem Class Methods ]--------------------------------*/ + +@BaseItem_Spawn(const this) { + ClassInstanceCallBaseMethod(this); + + new ClassInstance:pInstance = ClassInstanceGetCurrent(); + ClassInstanceSetMember(pInstance, CE_MEMBER_PICKED, false); +} + +@BaseItem_UpdatePhysics(const this) { + ClassInstanceCallBaseMethod(this); + + set_pev(this, pev_solid, SOLID_TRIGGER); + set_pev(this, pev_movetype, MOVETYPE_TOSS); + set_pev(this, pev_takedamage, DAMAGE_NO); +} + +@BaseItem_Touch(const this, const pToucher) { + if (!IS_PLAYER(pToucher)) return; + + if (!ExecuteMethod(CEMethod_CanPickup, this, pToucher)) return; + + ExecuteMethod(CEMethod_Pickup, this, pToucher); + + static ClassInstance:pInstance; pInstance = ClassInstanceGetCurrent(); + ClassInstanceSetMember(pInstance, CE_MEMBER_PICKED, true); + ExecuteHamB(Ham_Killed, this, pToucher, 0); +} + +bool:@BaseItem_CanPickup(const this, const pToucher) { + if (pev(this, pev_deadflag) != DEAD_NO) return false; + if (~pev(this, pev_flags) & FL_ONGROUND) return false; + + return true; +} + +@BaseItem_Pickup(const this, const pToucher) {} + +/*--------------------------------[ BaseProp Class Methods ]--------------------------------*/ + +@BaseProp_UpdatePhysics(const this) { + ClassInstanceCallBaseMethod(this); + + set_pev(this, pev_solid, SOLID_BBOX); + set_pev(this, pev_movetype, MOVETYPE_FLY); + set_pev(this, pev_takedamage, DAMAGE_NO); +} + +/*--------------------------------[ BaseMonster Class Methods ]--------------------------------*/ + +@BaseMonster_Spawn(const this) { + ClassInstanceCallBaseMethod(this); + + set_pev(this, pev_flags, pev(this, pev_flags) | FL_MONSTER); +} + +@BaseMonster_UpdatePhysics(const this) { + ClassInstanceCallBaseMethod(this); + + set_pev(this, pev_solid, SOLID_BBOX); + set_pev(this, pev_movetype, MOVETYPE_PUSHSTEP); + set_pev(this, pev_takedamage, DAMAGE_AIM); + + set_pev(this, pev_controller_0, 125); + set_pev(this, pev_controller_1, 125); + set_pev(this, pev_controller_2, 125); + set_pev(this, pev_controller_3, 125); + + set_pev(this, pev_gamestate, 1); + set_pev(this, pev_gravity, 1.0); + set_pev(this, pev_fixangle, 1); + set_pev(this, pev_friction, 0.25); +} + +/*--------------------------------[ BaseTrigger Class Methods ]--------------------------------*/ + +@BaseTrigger_Spawn(const this) { + ClassInstanceCallBaseMethod(this); + + new ClassInstance:pInstance = ClassInstanceGetCurrent(); + ClassInstanceSetMember(pInstance, CE_MEMBER_DELAY, 0.1); +} + +@BaseTrigger_UpdatePhysics(const this) { + ClassInstanceCallBaseMethod(this); + + set_pev(this, pev_solid, SOLID_TRIGGER); + set_pev(this, pev_movetype, MOVETYPE_NONE); + set_pev(this, pev_effects, EF_NODRAW); +} + +bool:@BaseTrigger_IsTriggered(const this, const pActivator) { + static ClassInstance:pInstance; pInstance = ClassInstanceGetCurrent(); + + return ClassInstanceGetMember(pInstance, CE_MEMBER_TRIGGERED); +} + +@BaseTrigger_Touch(const this, const pToucher) { + if (ExecuteMethod(CEMethod_CanTrigger, this, pToucher)) { + ExecuteMethod(CEMethod_Trigger, this, pToucher); + } +} + +bool:@BaseTrigger_CanTrigger(const this, const pActivator) { + static Float:flNextThink; pev(this, pev_nextthink, flNextThink); + + if (flNextThink > get_gametime()) return false; + + if (!ExecuteMethod(CEMethod_IsMasterTriggered, this, pActivator)) return false; + + return true; +} + +bool:@BaseTrigger_Trigger(const this, const pActivator) { + static Float:flDelay; ExecuteHamB(Ham_GetDelay, this, flDelay); + + set_pev(this, pev_nextthink, get_gametime() + flDelay); + + return true; +} + +/*--------------------------------[ BaseBSP Class Methods ]--------------------------------*/ + +@BaseBSP_UpdatePhysics(const this) { + ClassInstanceCallBaseMethod(this); + + set_pev(this, pev_movetype, MOVETYPE_PUSH); + set_pev(this, pev_solid, SOLID_BSP); + set_pev(this, pev_flags, pev(this, pev_flags) | FL_WORLDBRUSH); +} + +/*--------------------------------[ Functions ]--------------------------------*/ + +InitStorages() { + g_itEntityIds = TrieCreate(); + + for (new pEntity = 0; pEntity < sizeof(g_rgEntityClassInstances); ++pEntity) { + g_rgEntityClassInstances[pEntity] = Invalid_ClassInstance; + + for (new EntityMethodPointer:iFunctionPointer = EntityMethodPointer:0; iFunctionPointer < EntityMethodPointer; ++iFunctionPointer) { + g_rgEntityMethodPointers[pEntity][iFunctionPointer] = Invalid_Struct; + } + } +} + +DestroyStorages() { + TrieDestroy(g_itEntityIds); +} + +InitBaseClasses() { + new const BASE_ENTITY_NAMES[CEPreset][] = { + "ce_base", + "ce_baseitem", + "ce_basemonster", + "ce_baseprop", + "ce_basetrigger", + "ce_basebsp" + }; + + g_rgPresetEntityIds[CEPreset_Base] = RegisterEntityClass(BASE_ENTITY_NAMES[CEPreset_Base], CEPreset_Invalid, EntityFlag_Abstract); + + for (new CEPreset:iPreset = CEPreset:0; iPreset < CEPreset; ++iPreset) { + if (iPreset == CEPreset_Base) continue; + + g_rgPresetEntityIds[iPreset] = RegisterEntityClass(BASE_ENTITY_NAMES[iPreset], CEPreset_Base, EntityFlag_Abstract); + } + + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_Allocate, get_func_pointer("@Base_Allocate")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_Free, get_func_pointer("@Base_Free")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_KeyValue, get_func_pointer("@Base_KeyValue")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_Spawn, get_func_pointer("@Base_Spawn")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_ResetVariables, get_func_pointer("@Base_ResetVariables")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_UpdatePhysics, get_func_pointer("@Base_UpdatePhysics")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_UpdateModel, get_func_pointer("@Base_UpdateModel")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_UpdateSize, get_func_pointer("@Base_UpdateSize")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_Touch, get_func_pointer("@Base_Touch")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_Think, get_func_pointer("@Base_Think")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_Restart, get_func_pointer("@Base_Restart")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_Killed, get_func_pointer("@Base_Killed")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_IsMasterTriggered, get_func_pointer("@Base_IsMasterTriggered")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_ObjectCaps, get_func_pointer("@Base_ObjectCaps")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_BloodColor, get_func_pointer("@Base_BloodColor")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_Use, get_func_pointer("@Base_Use")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_Blocked, get_func_pointer("@Base_Blocked")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_GetDelay, get_func_pointer("@Base_GetDelay")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_Classify, get_func_pointer("@Base_Classify")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_IsTriggered, get_func_pointer("@Base_IsTriggered")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_GetToggleState, get_func_pointer("@Base_GetToggleState")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_SetToggleState, get_func_pointer("@Base_SetToggleState")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_Respawn, get_func_pointer("@Base_Respawn")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Base], CEMethod_TraceAttack, get_func_pointer("@Base_TraceAttack")); + + RegisterEntityClassKeyMemberBinding(BASE_ENTITY_NAMES[CEPreset_Base], "origin", CE_MEMBER_ORIGIN, CEMemberType_Vector); + RegisterEntityClassKeyMemberBinding(BASE_ENTITY_NAMES[CEPreset_Base], "angles", CE_MEMBER_ANGLES, CEMemberType_Vector); + RegisterEntityClassKeyMemberBinding(BASE_ENTITY_NAMES[CEPreset_Base], "master", CE_MEMBER_MASTER, CEMemberType_String); + RegisterEntityClassKeyMemberBinding(BASE_ENTITY_NAMES[CEPreset_Base], "targetname", CE_MEMBER_TARGETNAME, CEMemberType_String); + RegisterEntityClassKeyMemberBinding(BASE_ENTITY_NAMES[CEPreset_Base], "target", CE_MEMBER_TARGET, CEMemberType_String); + RegisterEntityClassKeyMemberBinding(BASE_ENTITY_NAMES[CEPreset_Base], "model", CE_MEMBER_MODEL, CEMemberType_String); + + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Prop], CEMethod_UpdatePhysics, get_func_pointer("@BaseProp_UpdatePhysics")); + + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Item], CEMethod_Spawn, get_func_pointer("@BaseItem_Spawn")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Item], CEMethod_Touch, get_func_pointer("@BaseItem_Touch")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Item], CEMethod_CanPickup, get_func_pointer("@BaseItem_CanPickup")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Item], CEMethod_Pickup, get_func_pointer("@BaseItem_Pickup")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Item], CEMethod_UpdatePhysics, get_func_pointer("@BaseItem_UpdatePhysics")); + + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Monster], CEMethod_Spawn, get_func_pointer("@BaseMonster_Spawn")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Monster], CEMethod_UpdatePhysics, get_func_pointer("@BaseMonster_UpdatePhysics")); + + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Trigger], CEMethod_Spawn, get_func_pointer("@BaseTrigger_Spawn")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Trigger], CEMethod_Touch, get_func_pointer("@BaseTrigger_Touch")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Trigger], CEMethod_CanTrigger, get_func_pointer("@BaseTrigger_CanTrigger")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Trigger], CEMethod_Trigger, get_func_pointer("@BaseTrigger_Trigger")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Trigger], CEMethod_UpdatePhysics, get_func_pointer("@BaseTrigger_UpdatePhysics")); + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_Trigger], CEMethod_IsTriggered, get_func_pointer("@BaseTrigger_IsTriggered")); + + ImplementEntityClassMethod(BASE_ENTITY_NAMES[CEPreset_BSP], CEMethod_UpdatePhysics, get_func_pointer("@BaseBSP_UpdatePhysics")); +} + +InitHooks() { + register_forward(FM_Spawn, "FMHook_Spawn"); + register_forward(FM_KeyValue, "FMHook_KeyValue"); + register_forward(FM_OnFreeEntPrivateData, "FMHook_OnFreeEntPrivateData"); +} + +DestroyRegisteredClasses() { + for (new iId = 0; iId < g_iEntityClassesNum; ++iId) { + FreeEntityClass(g_rgEntities[iId][Entity_Class]); + } +} + +RegisterEntityClass(const szClassname[], CEPreset:iPreset = CEPreset_Invalid, const EntityFlags:iFlags = EntityFlag_None, const szParent[] = "") { + new iId = g_iEntityClassesNum; + + new Class:cParent = Invalid_Class; + + if (!equal(szParent, NULL_STRING)) { + new iParentId = CE_INVALID_ID; + if (!TrieGetCell(g_itEntityIds, szParent, iParentId)) { + log_error(AMX_ERR_NATIVE, ERROR_IS_NOT_REGISTERED_BASE, LOG_PREFIX, szParent); + return CE_INVALID_ID; + } + + cParent = g_rgEntities[iParentId][Entity_Class]; + } else if (iPreset != CEPreset_Invalid) { + new iPresetEntityId = g_rgPresetEntityIds[iPreset]; + cParent = g_rgEntities[iPresetEntityId][Entity_Class]; + } + + new Class:cEntity = ClassCreate(cParent); + ClassSetMetadataString(cEntity, CLASS_METADATA_NAME, szClassname); + + ClassSetMetadata(cEntity, CLASS_METADATA_CE_ID, iId); + g_rgEntities[iId][Entity_Id] = iId; + g_rgEntities[iId][Entity_Class] = cEntity; + g_rgEntities[iId][Entity_Flags] = iFlags; + g_rgEntities[iId][Entity_Hierarchy] = CreateClassHierarchyList(cEntity); + g_rgEntities[iId][Entity_KeyMemberBindings] = Invalid_Trie; + + for (new CEMethod:iMethod = CEMethod:0; iMethod < CEMethod; ++iMethod) { + g_rgEntities[iId][Entity_MethodPreHooks][iMethod] = Invalid_Array; + g_rgEntities[iId][Entity_MethodPostHooks][iMethod] = Invalid_Array; + g_rgEntities[iId][Entity_TotalHooksCounter][iMethod] = 0; + } + + TrieSetCell(g_itEntityIds, szClassname, iId); + + g_iEntityClassesNum++; + + log_amx("%s Entity ^"%s^" successfully registred.", LOG_PREFIX, szClassname); + + return iId; +} + +FreeEntityClass(&Class:cEntity) { + new iId = ClassGetMetadata(cEntity, CLASS_METADATA_CE_ID); + + for (new CEMethod:iMethod = CEMethod:0; iMethod < CEMethod; ++iMethod) { + if (g_rgEntities[iId][Entity_MethodPreHooks][iMethod] != Invalid_Array) { + ArrayDestroy(g_rgEntities[iId][Entity_MethodPreHooks][iMethod]); + } + + if (g_rgEntities[iId][Entity_MethodPostHooks][iMethod] != Invalid_Array) { + ArrayDestroy(g_rgEntities[iId][Entity_MethodPostHooks][iMethod]); + } + } + + ArrayDestroy(g_rgEntities[iId][Entity_Hierarchy]); + + if (g_rgEntities[iId][Entity_KeyMemberBindings] != Invalid_Trie) { + new TrieIter:itKeyMemberBindingsIter = TrieIterCreate(g_rgEntities[iId][Entity_KeyMemberBindings]); + + while (!TrieIterEnded(itKeyMemberBindingsIter)) { + new Trie:itMemberTypes; TrieIterGetCell(itKeyMemberBindingsIter, itMemberTypes); + TrieDestroy(itMemberTypes); + TrieIterNext(itKeyMemberBindingsIter); + } + + TrieIterDestroy(itKeyMemberBindingsIter); + + TrieDestroy(g_rgEntities[iId][Entity_KeyMemberBindings]); + } + + ClassDestroy(cEntity); +} + +InitMethodHamHook(CEMethod:iMethod) { + if (!g_rgMethodHamHooks[iMethod]) { + g_rgMethodHamHooks[iMethod] = RegisterMethodHamHook(iMethod); + } +} + +HamHook:RegisterMethodHamHook(CEMethod:iMethod) { + switch (iMethod) { + case CEMethod_Spawn: return RegisterHam(Ham_Spawn, CE_BASE_CLASSNAME, "HamHook_Base_Spawn", .Post = 0); + case CEMethod_ObjectCaps: return RegisterHam(Ham_ObjectCaps, CE_BASE_CLASSNAME, "HamHook_Base_ObjectCaps", .Post = 0); + case CEMethod_Touch: return RegisterHam(Ham_Touch, CE_BASE_CLASSNAME, "HamHook_Base_Touch_Post", .Post = 1); + case CEMethod_Use: return RegisterHam(Ham_Use, CE_BASE_CLASSNAME, "HamHook_Base_Use_Post", .Post = 1); + case CEMethod_Blocked: return RegisterHam(Ham_Blocked, CE_BASE_CLASSNAME, "HamHook_Base_Blocked_Post", .Post = 1); + case CEMethod_Killed: return RegisterHam(Ham_Killed, CE_BASE_CLASSNAME, "HamHook_Base_Killed", .Post = 0); + case CEMethod_Think: return RegisterHam(Ham_Think, CE_BASE_CLASSNAME, "HamHook_Base_Think", .Post = 0); + case CEMethod_BloodColor: return RegisterHam(Ham_BloodColor, CE_BASE_CLASSNAME, "HamHook_Base_BloodColor", .Post = 0); + case CEMethod_GetDelay: return RegisterHam(Ham_GetDelay, CE_BASE_CLASSNAME, "HamHook_Base_GetDelay", .Post = 0); + case CEMethod_Classify: return RegisterHam(Ham_Classify, CE_BASE_CLASSNAME, "HamHook_Base_Classify", .Post = 0); + case CEMethod_IsTriggered: return RegisterHam(Ham_IsTriggered, CE_BASE_CLASSNAME, "HamHook_Base_IsTriggered", .Post = 0); + case CEMethod_GetToggleState: return RegisterHam(Ham_GetToggleState, CE_BASE_CLASSNAME, "HamHook_Base_GetToggleState", .Post = 0); + case CEMethod_SetToggleState: return RegisterHam(Ham_SetToggleState, CE_BASE_CLASSNAME, "HamHook_Base_SetToggleState", .Post = 0); + case CEMethod_Respawn: return RegisterHam(Ham_Respawn, CE_BASE_CLASSNAME, "HamHook_Base_Respawn_Post", .Post = 1); + case CEMethod_TraceAttack: return RegisterHam(Ham_TraceAttack, CE_BASE_CLASSNAME, "HamHook_Base_TraceAttack_Post", .Post = 1); + case CEMethod_Restart: { + if (g_bIsCStrike) { + return RegisterHam(Ham_CS_Restart, CE_BASE_CLASSNAME, "HamHook_Base_Restart_Post", .Post = 1); + } + } + } + + return HamHook:0; +} + +AddEntityClassMethod(const szClassname[], const szMethod[], const Function:fnCallback, Array:irgParamTypes, bool:bVirtual) { + new iId = GetIdByClassName(szClassname); + + ClassAddMethod(g_rgEntities[iId][Entity_Class], szMethod, fnCallback, bVirtual, CMP_Cell, CMP_ParamsCellArray, irgParamTypes); +} + +AddEntityClassNativeMethod(const &Class:class, CEMethod:iMethod, Function:fnCallback) { + new Array:irgParams = ArrayCreate(_, 8); + + for (new iParam = 0; iParam < g_rgEntityMethodParams[iMethod][EntityMethodParams_Num]; ++iParam) { + ArrayPushCell(irgParams, g_rgEntityMethodParams[iMethod][EntityMethodParams_Types][iParam]); + } + + ClassAddMethod(class, CE_METHOD_NAMES[iMethod], fnCallback, true, CMP_Cell, CMP_ParamsCellArray, irgParams); + + ArrayDestroy(irgParams); + + InitMethodHamHook(iMethod); +} + +ImplementEntityClassMethod(const szClassname[], const CEMethod:iMethod, const Function:fnCallback) { + new iId = GetIdByClassName(szClassname); + if (iId == CE_INVALID_ID) { + log_error(AMX_ERR_NATIVE, ERROR_IS_NOT_REGISTERED, LOG_PREFIX, szClassname); + return; + } + + AddEntityClassNativeMethod(g_rgEntities[iId][Entity_Class], iMethod, fnCallback); +} + +RegisterEntityClassKeyMemberBinding(const szClassname[], const szKey[], const szMember[], CEMemberType:iType) { + new iId = GetIdByClassName(szClassname); + if (iId == CE_INVALID_ID) { + log_error(AMX_ERR_NATIVE, ERROR_IS_NOT_REGISTERED, LOG_PREFIX, szClassname); + return; + } + + if (g_rgEntities[iId][Entity_KeyMemberBindings] == Invalid_Trie) { + g_rgEntities[iId][Entity_KeyMemberBindings] = TrieCreate(); + } + + new Trie:itMemberTypes = Invalid_Trie; + if (!TrieGetCell(g_rgEntities[iId][Entity_KeyMemberBindings], szKey, itMemberTypes)) { + itMemberTypes = TrieCreate(); + TrieSetCell(g_rgEntities[iId][Entity_KeyMemberBindings], szKey, itMemberTypes); + } + + TrieSetCell(itMemberTypes, szMember, iType); +} + +RemoveEntityClassKeyMemberBinding(const szClassname[], const szKey[], const szMember[]) { + new iId = GetIdByClassName(szClassname); + if (iId == CE_INVALID_ID) { + log_error(AMX_ERR_NATIVE, ERROR_IS_NOT_REGISTERED, LOG_PREFIX, szClassname); + return; + } + + if (g_rgEntities[iId][Entity_KeyMemberBindings] == Invalid_Trie) return; + + new Trie:itMemberTypes = Invalid_Trie; + if (!TrieGetCell(g_rgEntities[iId][Entity_KeyMemberBindings], szKey, itMemberTypes)) return; + + TrieDeleteKey(itMemberTypes, szMember); +} + +RegisterEntityClassHook(const szClassname[], CEMethod:iMethod, const Function:fnCallback, bool:bPost) { + new iId = GetIdByClassName(szClassname); + if (iId == CE_INVALID_ID) { + log_error(AMX_ERR_NATIVE, ERROR_IS_NOT_REGISTERED, LOG_PREFIX, szClassname); + return CE_INVALID_HOOK_ID; + } + + new Array:irgHooks = Invalid_Array; + if (bPost) { + if (g_rgEntities[iId][Entity_MethodPostHooks][iMethod] == Invalid_Array) { + g_rgEntities[iId][Entity_MethodPostHooks][iMethod] = ArrayCreate(); + } + + irgHooks = g_rgEntities[iId][Entity_MethodPostHooks][iMethod]; + } else { + if (g_rgEntities[iId][Entity_MethodPreHooks][iMethod] == Invalid_Array) { + g_rgEntities[iId][Entity_MethodPreHooks][iMethod] = ArrayCreate(); + } + + irgHooks = g_rgEntities[iId][Entity_MethodPreHooks][iMethod]; + } + + // Incrementing hook counter for the class and all child classes + { + g_rgEntities[iId][Entity_TotalHooksCounter][iMethod]++; + + for (new iOtherId = iId + 1; iOtherId < g_iEntityClassesNum; ++iOtherId) { + ClassIsChildOf(g_rgEntities[iOtherId][Entity_Class], g_rgEntities[iId][Entity_Class]); + g_rgEntities[iOtherId][Entity_TotalHooksCounter][iMethod]++; + } + } + + new iHookId = ArrayPushCell(irgHooks, fnCallback); + + return iHookId; +} + +CreateEntity(const szClassname[], const Float:vecOrigin[3], bool:bTemp) { + new iId = GetIdByClassName(szClassname); + if (iId == CE_INVALID_ID) { + log_error(AMX_ERR_NATIVE, ERROR_CANNOT_CREATE_UNREGISTERED, LOG_PREFIX, szClassname); + return FM_NULLENT; + } + + static EntityFlags:iFlags; iFlags = g_rgEntities[iId][Entity_Flags]; + if (iFlags & EntityFlag_Abstract) { + log_error(AMX_ERR_NATIVE, ERROR_CANNOT_CREATE_ABSTRACT, LOG_PREFIX, szClassname); + return FM_NULLENT; + } + + // szClassname can be an alias, so we need to get the classname from the metadata to make sure the classname is real + static szRealClassname[CE_MAX_NAME_LENGTH]; ClassGetMetadataString(g_rgEntities[iId][Entity_Class], CLASS_METADATA_NAME, szRealClassname, charsmax(szRealClassname)); + + new this = engfunc(EngFunc_CreateNamedEntity, g_iszBaseClassName); + set_pev(this, pev_classname, szRealClassname); + engfunc(EngFunc_SetOrigin, this, vecOrigin); + // set_pev(this, pev_flags, pev(this, pev_flags) & ~FL_ONGROUND); + + @Entity_Allocate(this, iId, bTemp); + + new ClassInstance:pInstance = @Entity_GetInstance(this); + ClassInstanceSetMemberArray(pInstance, CE_MEMBER_ORIGIN, vecOrigin, 3); + + return this; +} + +GetIdByClassName(const szClassname[]) { + static iId; + if (!TrieGetCell(g_itEntityIds, szClassname, iId)) return CE_INVALID_ID; + + return iId; +} + +/* + price - 0.0011 + engine func price - 0.0004 + hooks price - 0.0003 ms + method call price - 0.0005 ms + CallEntityMethodHook price - 0.00015 ms +*/ +#define HOOKABLE_METHOD_IMPLEMENTATION(%1,%2,%0) {\ + static iPreHookResult; iPreHookResult = CallEntityMethodHook(%2, %1, false, %0);\ + STACK_PUSH(PREHOOK_RETURN, iPreHookResult); \ + \ + static ClassInstance:pInstance; pInstance = @Entity_GetInstance(%2);\ + \ + static any:result;\ + if (STACK_READ(PREHOOK_RETURN) != CE_SUPERCEDE) result = ClassInstanceCallMethod(pInstance, CE_METHOD_NAMES[%1], %2, %0);\ + if (STACK_POP(PREHOOK_RETURN) <= CE_HANDLED) STACK_PATCH(METHOD_RETURN, result);\ + \ + CallEntityMethodHook(%2, %1, true, %0);\ +} + +any:ExecuteMethod(CEMethod:iMethod, const &pEntity, any:...) { + STACK_PUSH(METHOD_RETURN, 0); + + switch (iMethod) { + case CEMethod_Allocate: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_Allocate, pEntity, 0) + } + case CEMethod_Free: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_Free, pEntity, 0) + + ClassInstanceDestroy(g_rgEntityClassInstances[pEntity]); + g_rgEntityClassInstances[pEntity] = Invalid_ClassInstance; + } + case CEMethod_KeyValue: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_KeyValue, pEntity, getarg(2)) + } + case CEMethod_SpawnInit: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_SpawnInit, pEntity, 0) + } + case CEMethod_Spawn: { + if (!pev_valid(pEntity) || pev(pEntity, pev_flags) & FL_KILLME) return 0; + + ExecuteMethod(CEMethod_ResetVariables, pEntity); + + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_Spawn, pEntity, 0) + + new ClassInstance:pInstance = @Entity_GetInstance(pEntity); + ClassInstanceSetMember(pInstance, CE_MEMBER_LASTSPAWN, get_gametime()); + } + case CEMethod_ResetVariables: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_ResetVariables, pEntity, 0) + } + case CEMethod_UpdatePhysics: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_UpdatePhysics, pEntity, 0) + } + case CEMethod_UpdateModel: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_UpdateModel, pEntity, 0) + } + case CEMethod_UpdateSize: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_UpdateSize, pEntity, 0) + } + case CEMethod_Touch: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_Touch, pEntity, getarg(2)) + } + case CEMethod_Think: { + if (pev(pEntity, pev_flags) & FL_KILLME) return 0; + + static iDeadFlag; iDeadFlag = pev(pEntity, pev_deadflag); + + switch (iDeadFlag) { + case DEAD_NO: { + static ClassInstance:pInstance; pInstance = @Entity_GetInstance(pEntity); + static Float:flNextKill; flNextKill = ClassInstanceGetMember(pInstance, CE_MEMBER_NEXTKILL); + if (flNextKill > 0.0 && flNextKill <= get_gametime()) { + ExecuteHamB(Ham_Killed, pEntity, 0, 0); + } + } + case DEAD_RESPAWNABLE: { + static ClassInstance:pInstance; pInstance = @Entity_GetInstance(pEntity); + static Float:flNextRespawn; flNextRespawn = ClassInstanceGetMember(pInstance, CE_MEMBER_NEXTRESPAWN); + if (flNextRespawn <= get_gametime()) { + ExecuteHamB(Ham_Respawn, pEntity); + } + } + } + + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_Think, pEntity, 0) + } + case CEMethod_CanPickup: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_CanPickup, pEntity, getarg(2)) + } + case CEMethod_Pickup: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_Pickup, pEntity, getarg(2)) + } + case CEMethod_CanTrigger: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_CanTrigger, pEntity, getarg(2)) + } + case CEMethod_Trigger: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_Trigger, pEntity, getarg(2)) + } + case CEMethod_Restart: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_Restart, pEntity, 0) + } + case CEMethod_Killed: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_Killed, pEntity, getarg(2), getarg(3)) + } + case CEMethod_IsMasterTriggered: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_IsMasterTriggered, pEntity, getarg(2)) + } + case CEMethod_ObjectCaps: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_ObjectCaps, pEntity, 0) + } + case CEMethod_BloodColor: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_BloodColor, pEntity, 0) + } + case CEMethod_Use: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_Use, pEntity, getarg(2), getarg(3), getarg(4), Float:getarg(5)) + } + case CEMethod_Blocked: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_Blocked, pEntity, getarg(2)) + } + case CEMethod_GetDelay: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_GetDelay, pEntity, 0) + } + case CEMethod_Classify: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_Classify, pEntity, 0) + } + case CEMethod_IsTriggered: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_IsTriggered, pEntity, getarg(2)) + } + case CEMethod_GetToggleState: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_GetToggleState, pEntity, 0) + } + case CEMethod_SetToggleState: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_SetToggleState, pEntity, getarg(2)) + } + case CEMethod_Respawn: { + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_Respawn, pEntity, 0) + } + case CEMethod_TraceAttack: { + new Float:vecDirection[3]; xs_vec_set(vecDirection, Float:getarg(4, 0), Float:getarg(4, 1), Float:getarg(4, 2)); + HOOKABLE_METHOD_IMPLEMENTATION(CEMethod_TraceAttack, pEntity, getarg(2), Float:getarg(3), vecDirection, getarg(5), getarg(6)) + } + } + + return STACK_POP(METHOD_RETURN); +} + +CallEntityMethodHook(const &pEntity, CEMethod:iMethod, const bool:bPost, any:...) { + static const iParamOffset = 3; + + static ClassInstance:pInstance; pInstance = @Entity_GetInstance(pEntity); + static Class:cClass; cClass = ClassInstanceGetClass(pInstance); + + static iId; iId = ClassGetMetadata(cClass, CLASS_METADATA_CE_ID); + if (!g_rgEntities[iId][Entity_TotalHooksCounter][iMethod]) return CE_IGNORED; + + new iResult = CE_IGNORED; + + new Array:irgHierarchy = g_rgEntities[iId][Entity_Hierarchy]; + new iHierarchySize = ArraySize(irgHierarchy); + + new Array:irgHooks = Invalid_Array; + new irgHooksNum = 0; + + for (new iHierarchyPos = 0; iHierarchyPos < iHierarchySize; ++iHierarchyPos) { + static iId; iId = ArrayGetCell(irgHierarchy, iHierarchyPos); + + irgHooks = ( + bPost + ? g_rgEntities[iId][Entity_MethodPostHooks][iMethod] + : g_rgEntities[iId][Entity_MethodPreHooks][iMethod] + ); + + if (irgHooks == Invalid_Array) continue; + + irgHooksNum = ArraySize(irgHooks); + + for (new iHookId = 0; iHookId < irgHooksNum; ++iHookId) { + static Function:fnCallback; fnCallback = ArrayGetCell(irgHooks, iHookId); + + if (callfunc_begin_p(fnCallback) == 1) { + callfunc_push_int(pEntity); + + static iParam; + for (iParam = 0; iParam < g_rgEntityMethodParams[iMethod][EntityMethodParams_Num]; ++iParam) { + switch (g_rgEntityMethodParams[iMethod][EntityMethodParams_Types][iParam]) { + case CMP_Cell: { + callfunc_push_int(getarg(iParam + iParamOffset)); + } + case CMP_String: { + static szBuffer[MAX_STRING_LENGTH]; + + static iPos; + for (iPos = 0; iPos < charsmax(szBuffer); ++iPos) { + szBuffer[iPos] = getarg(iParam + iParamOffset, iPos); + if (szBuffer[iPos] == '^0') break; + } + + callfunc_push_str(szBuffer, false); + } + case CMP_Array: { + static iSize; iSize = g_rgEntityMethodParams[iMethod][EntityMethodParams_Types][++iParam]; + + for (new iIndex = 0; iIndex < iSize; ++iIndex) { + callfunc_push_int(any:getarg(iParam + iParamOffset, iIndex)); + } + } + // TODO: Implement other types + } + } + + iResult = max(callfunc_end(), iResult); + } + } + } + + return iResult; +} + +Array:CreateClassHierarchyList(const &Class:class) { + new Array:irgHierarchy = ArrayCreate(); + + new iSize = 0; + + for (new Class:cCurrent = class; cCurrent != Invalid_Class; cCurrent = ClassGetBaseClass(cCurrent)) { + new iId = ClassGetMetadata(cCurrent, CLASS_METADATA_CE_ID); + if (iId == CE_INVALID_ID) continue; + + if (iSize) { + ArrayInsertCellBefore(irgHierarchy, 0, iId); + } else { + ArrayPushCell(irgHierarchy, iId); + } + + iSize++; + } + + return irgHierarchy; +} + +Array:ReadMethodParamsFromNativeCall(iStartArg, iArgc) { + static Array:irgParams; irgParams = ArrayCreate(); + + static iParam; + for (iParam = iStartArg; iParam <= iArgc; ++iParam) { + static iType; iType = get_param_byref(iParam); + + switch (iType) { + case CE_MP_Cell: { + ArrayPushCell(irgParams, CMP_Cell); + } + case CE_MP_String: { + ArrayPushCell(irgParams, CMP_String); + } + case CE_MP_Array: { + ArrayPushCell(irgParams, CMP_Array); + ArrayPushCell(irgParams, get_param_byref(iParam + 1)); + iParam++; + } + case CE_MP_Vector: { + ArrayPushCell(irgParams, CMP_Array); + ArrayPushCell(irgParams, 3); + } + } + } + + return irgParams; +} + +/*--------------------------------[ Stocks ]--------------------------------*/ + +stock UTIL_ParseVector(const szBuffer[], Float:vecOut[3]) { + static rgszOrigin[3][8]; + parse(szBuffer, rgszOrigin[0], charsmax(rgszOrigin[]), rgszOrigin[1], charsmax(rgszOrigin[]), rgszOrigin[2], charsmax(rgszOrigin[])); + + for (new i = 0; i < 3; ++i) { + vecOut[i] = str_to_float(rgszOrigin[i]); + } +} + +stock bool:UTIL_IsMasterTriggered(const szMaster[], const &pActivator) { + if (equal(szMaster, NULL_STRING)) return false; + + new pMaster = engfunc(EngFunc_FindEntityByString, 0, "targetname", szMaster); + if (pMaster && (ExecuteHam(Ham_ObjectCaps, pMaster) & FCAP_MASTER)) { + return !!ExecuteHamB(Ham_IsTriggered, pMaster, pActivator); + } + + return true; +} + +stock UTIL_GetStringType(const szString[]) { + enum { + string_type = 's', + integer_type = 'i', + float_type = 'f' + }; + + static bool:bIsFloat; bIsFloat = false; + + for (new i = 0; szString[i] != '^0'; ++i) { + if (isalpha(szString[i])) return string_type; + + if (szString[i] == '.') { + if (bIsFloat) return string_type; + + bIsFloat = true; + } + } + + return bIsFloat ? float_type : integer_type; +} diff --git a/api/custom-entities/future/include/api_custom_entities_future.inc b/api/custom-entities/future/include/api_custom_entities_future.inc new file mode 100644 index 0000000..1e78266 --- /dev/null +++ b/api/custom-entities/future/include/api_custom_entities_future.inc @@ -0,0 +1,249 @@ +#if defined _api_custom_entities_included + #endinput +#endif +#define _api_custom_entities_included + +#pragma reqlib api_custom_entities + +#include + +/** + * Register entity + * + * @param szClassname Name of an entity + * @param iPreset Preset for an entity + * + * @return Handler of the registered entity + */ +native CE:CE_RegisterClass(const szClassname[], CEPreset:iPreset = CEPreset_Base, bool:bAbstract = false); + +/** + * Extend entity + * + * @param szClassname Name of an entity + * @param szBase Name of a parent entity + * @param bAbstract Abstract flag + * + * @return Handler of the registered entity + */ +native CE:CE_RegisterClassDerived(const szClassname[], const szBase[], bool:bAbstract = false); + +/** + * Extend entity + * + * @param szAlias Name of an alias + * @param szClassname Name of an entity + * + * @return Handler of the registered entity + */ +native CE:CE_RegisterClassAlias(const szAlias[], const szClassname[]); + +/** + * Spawn entity + * + * @param szClassname Name of entity + * @param vecOrigin Spawn origin + * @param bTemp Mark entity as an temporary entity + * + * @return Entity index + */ +native CE_Create(const szClassname[], const Float:vecOrigin[] = {0.0, 0.0, 0.0}, bool:bTemp = true); +/** + * Restart entity + * + * @param pEntity Entity index + */ +native bool:CE_Restart(const &pEntity); + +/** + * Kill entity + * + * @param pEntity Entity index + * @param pKiller Index of killer + */ +native bool:CE_Kill(const &pEntity, const &pKiller = 0); + +/** + * Remove entity correctly + * + * @param pEntity Entity index + * + * @return Result true/false + */ +native bool:CE_Remove(const &pEntity); + +/** + * Register new hook for entity + * + * @param method Function handler + * @param szClassname Name of entity + * @param szCallback Callback + */ +native CE_RegisterClassHook(const szClassname[], CEMethod:method, const szCallback[], bool:bPost); + +native any:CE_GetMethodReturn(); +native CE_SetMethodReturn(any:value); + +native CE_RegisterClassKeyMemberBinding(const szClassname[], const szKey[], const any:szMember[], CEMemberType:iType); + +native CE_RemoveClassKeyMemberBinding(const szClassname[], const szKey[], const any:szMember[]); + +/** + * Registers a new method for entity. + * + * @param szClassname Name of entity + * @param szMethod Name of method + * @param szCallback Callback + * + * @noreturn +*/ +native CE_RegisterClassMethod(const szClassname[], const szMethod[], const szCallback[], any:...); + +/** + * Implements a native method for entity. + * + * @param szClassname Name of entity + * @param iMethod Method to implement + * @param szCallback Callback + * + * @noreturn +*/ +native CE_ImplementClassMethod(const szClassname[], CEMethod:iMethod, const szCallback[]); + +/** + * Registers a new virtual method for entity. + * + * @param szClassname Name of entity + * @param szMethod Name of method + * @param szCallback Callback + * + * @noreturn +*/ +native CE_RegisterClassVirtualMethod(const szClassname[], const szMethod[], const szCallback[], any:...); + +/** + * Gets handler of entity by name + * + * @param szClassname Name of entity + * + * @return Handler of the registered entity or -1 otherwise + */ +native CE:CE_GetClassHandler(const szClassname[]); + +/** + * Gets handler of entity by index + * + * @param pEntity Entity index + * + * @return Handler of the entity or -1 otherwise + */ +native CE:CE_GetHandler(const &pEntity); + +/** + * Checks if entity is an instance of specific custom entity + * + * @param pEntity Entity index + * @param szTargetName Name of target entity to check + * + * @return Result true/false + */ +native bool:CE_IsInstanceOf(const &pEntity, const szTargetName[]); + +/** + * Checks if entity has member + * + * @param pEntity Entity index + * @param szMember Member name + */ +native bool:CE_HasMember(const &pEntity, const any:szMember[]); + +/** + * Deletes member of an entity + * + * @param pEntity Entity index + * @param szMember Member name + */ +native CE_DeleteMember(const &pEntity, const any:szMember[]); + +/** + * Gets member of an entity + * + * @param pEntity Entity index + * @param szMember Member name + * + * @return Member value + */ +native any:CE_GetMember(const &pEntity, const any:szMember[]); + +/** + * Sets member of an entity + * + * @param pEntity Entity index + * @param szMember Member name + * @param value Value to set + */ +native CE_SetMember(const &pEntity, const any:szMember[], any:value, bool:bReplace = true); + +/** + * Gets vector member of an entity + * + * @param pEntity Entity index + * @param szMember Member name + * @param vecOut Output vector + */ +native bool:CE_GetMemberVec(const &pEntity, const any:szMember[], Float:vecOut[3]); + +/** + * Sets vector member of an entity + * + * @param pEntity Entity index + * @param szMember Member name + * @param vecValue Vector to set + */ +native CE_SetMemberVec(const &pEntity, const any:szMember[], const Float:vecValue[], bool:bReplace = true); + +/** + * Gets string member of an entity + * + * @param pEntity Entity index + * @param szMember Member name + * @param szOut Buffer to copy the value + * @param iLen Maximum size of buffer + */ +native bool:CE_GetMemberString(const &pEntity, const any:szMember[], szOut[], iLen); + +/** + * Sets string member of an entity + * + * @param pEntity Entity index + * @param szMember Member name + * @param szValue String value to set + */ +native CE_SetMemberString(const &pEntity, const any:szMember[], const szValue[], bool:bReplace = true); + +/** + * Call a method for entity. + * + * @param pEntity Entity index + * @param szMethod Name of method + * + * @return Method return value +*/ +native any:CE_CallMethod(const &pEntity, const szMethod[], any:...); + +/** + * Call a base method for entity. + * + * @param pEntity Entity index + * @param szMethod Name of method + * + * @return Method return value +*/ +native any:CE_CallBaseMethod(any:...); + +native CE_GetCallerPlugin(); + +native CE_SetThink(const &pEntity, const szMethod[], const szClassname[] = NULL_STRING); +native CE_SetTouch(const &pEntity, const szMethod[], const szClassname[] = NULL_STRING); +native CE_SetUse(const &pEntity, const szMethod[], const szClassname[] = NULL_STRING); +native CE_SetBlocked(const &pEntity, const szMethod[], const szClassname[] = NULL_STRING); diff --git a/api/custom-entities/future/include/api_custom_entities_future_const.inc b/api/custom-entities/future/include/api_custom_entities_future_const.inc new file mode 100644 index 0000000..3fad722 --- /dev/null +++ b/api/custom-entities/future/include/api_custom_entities_future_const.inc @@ -0,0 +1,136 @@ +#if defined _api_custom_entities_const_included + #endinput +#endif +#define _api_custom_entities_const_included + +#define CE_BASE_CLASSNAME "info_target" +#define CE_ENTITY_SECRET ('c'+'e'+'2') + +#define CE_MAX_NAME_LENGTH 64 +#define CE_MAX_MEMBER_NAME_LENGTH 64 +#define CE_MAX_CALLBACK_NAME_LENGTH 64 +#define CE_MAX_METHOD_NAME_LENGTH 64 + +#define CE_MEMBER_ID "_id" +#define CE_MEMBER_POINTER "_ptr" +#define CE_MEMBER_WORLD "_bWorld" +#define CE_MEMBER_ORIGIN "_vecOrigin" +#define CE_MEMBER_ANGLES "_vecAngles" +#define CE_MEMBER_MASTER "_szMaster" +#define CE_MEMBER_MODEL "_szModel" +#define CE_MEMBER_DELAY "_flDelay" +#define CE_MEMBER_NEXTKILL "_flNextKill" +#define CE_MEMBER_NEXTRESPAWN "_flNextRespawn" +#define CE_MEMBER_BLOODCOLOR "_iBloodColor" +#define CE_MEMBER_LIFETIME "_flLifeTime" +#define CE_MEMBER_IGNOREROUNDS "_bIgnoreRounds" +#define CE_MEMBER_RESPAWNTIME "_flRespawnTime" +#define CE_MEMBER_MINS "_vecMins" +#define CE_MEMBER_MAXS "_vecMaxs" +#define CE_MEMBER_LASTSPAWN "_flLastSpawn" +#define CE_MEMBER_PLUGINID "_iPluginId" +#define CE_MEMBER_TARGETNAME "_szTargetname" +#define CE_MEMBER_TARGET "_szTarget" +#define CE_MEMBER_PICKED "_bPicked" +#define CE_MEMBER_TOGGLESTATE "_iToggleState" +#define CE_MEMBER_TRIGGERED "_bTriggered" + +#define CE_IGNORED 0 +#define CE_HANDLED 1 +#define CE_OVERRIDE 2 +#define CE_SUPERCEDE 3 + +enum CE { + CE_InvalidHandler = -1 +}; + +enum CEPreset { + CEPreset_Invalid = -1, + CEPreset_Base, + CEPreset_Item, + CEPreset_Monster, + CEPreset_Prop, + CEPreset_Trigger, + CEPreset_BSP +}; + +enum CEMethod { + CEMethod_Invalid = -1, + CEMethod_Allocate, // Calls when entity instance allocated + CEMethod_Free, // Called when an instance of an object is about to be destroyed + CEMethod_KeyValue, // Calls when new key value obtained + CEMethod_SpawnInit, // Calls when entity is initialized (on first spawn) + CEMethod_Spawn, // Calls during entity spawn + CEMethod_ResetVariables, // Calls when entity is initialized (on first spawn) + CEMethod_UpdatePhysics, // Calls during on entity physics initialization + CEMethod_UpdateModel, // Calls during on entity model initialization + CEMethod_UpdateSize, // Calls during on entity size initialization + CEMethod_Touch, // Calls during entity touch + CEMethod_Think, // Calls when entity thinking + CEMethod_CanPickup, + CEMethod_Pickup, // Calls when player touch item. Should return PLUGIN_HANDLED if picked + CEMethod_CanTrigger, // Calls every trigger activation check + CEMethod_Trigger, // Calls every trigger activation check + CEMethod_Restart, // Calls when entity is restarting + CEMethod_Killed, // Calls when something killing entity. return PLUGIN_HANDLED to block the kill. + CEMethod_IsMasterTriggered, // Calls when entity is initialized (on first spawn) + CEMethod_ObjectCaps, + CEMethod_BloodColor, + CEMethod_Use, + CEMethod_Blocked, + CEMethod_GetDelay, + CEMethod_Classify, + CEMethod_IsTriggered, + CEMethod_GetToggleState, + CEMethod_SetToggleState, + CEMethod_Respawn, + CEMethod_TraceAttack +}; + +stock const CE_METHOD_NAMES[CEMethod][] = { + "Allocate", + "Free", + "KeyValue", + "SpawnInit", + "Spawn", + "UpdateVariables", + "UpdatePhysics", + "UpdateModel", + "UpdateSize", + "Touch", + "Think", + "CanPickup", + "Pickup", + "CanTrigger", + "Trigger", + "Restart", + "Killed", + "IsMasterTriggered", + "ObjectCaps", + "BloodColor", + "Use", + "Blocked", + "GetDelay", + "Classify", + "IsTriggered", + "GetToggleState", + "SetToggleState", + "Respawn", + "TraceAttack" +}; + +enum CEMemberType { + CEMemberType_Invalid = -1, + CEMemberType_Cell, + CEMemberType_Float, + CEMemberType_String, + CEMemberType_Vector +}; + +enum { + CE_MP_Invalid = -1, + CE_MP_Cell, + CE_MP_String, + CE_MP_Array, + CE_MP_Vector +}; diff --git a/api/custom-entities/include/api_custom_entities.inc b/api/custom-entities/include/api_custom_entities.inc new file mode 100644 index 0000000..4617f61 --- /dev/null +++ b/api/custom-entities/include/api_custom_entities.inc @@ -0,0 +1,220 @@ +#if defined _api_custom_entities_included + #endinput +#endif +#define _api_custom_entities_included + +#pragma reqlib api_custom_entities + +#include + +/** + * Register entity + * + * @param szClassname Name of an entity + * @param iPreset Preset for an entity + * + * @return Handler of the registered entity + */ +native CE:CE_Register(const szClassname[], CEPreset:iPreset = CEPreset_None, bool:bAbstract = false); + +/** + * Extend entity + * + * @param szClassname Name of an entity + * @param iPreset Preset for an entity + * + * @return Handler of the registered entity + */ +native CE:CE_RegisterDerived(const szClassname[], const szBase[], bool:bAbstract = false); + +/** + * Spawn entity + * + * @param szClassname Name of entity + * @param vecOrigin Spawn origin + * @param bTemp Mark entity as an temporary entity + * + * @return Entity index + */ +native CE_Create(const szClassname[], const Float:vecOrigin[] = {0.0, 0.0, 0.0}, bool:bTemp = true); + +/** + * Restart entity + * + * @param pEntity Entity index + */ +native bool:CE_Restart(pEntity); + +/** + * Kill entity + * + * @param pEntity Entity index + * @param pKiller Index of killer + */ +native bool:CE_Kill(pEntity, pKiller = 0); + +/** + * Remove entity correctly + * + * @param pEntity Entity index + * + * @return Result true/false + */ +native bool:CE_Remove(pEntity); + +/** + * Register new hook for entity + * + * @param function Function handler + * @param szClassname Name of entity + * @param szCallback Callback + */ +native CE_RegisterHook(const szClassname[], CEFunction:function, const szCallback[]); + +native CE_RegisterKeyMemberBinding(const szClassname[], const szKey[], const szMember[], CEMemberType:iType); + +native CE_RemoveKeyMemberBinding(const szClassname[], const szKey[], const szMember[]); + +/** + * Registers a new method for entity. + * + * @param szClassname Name of entity + * @param szMethod Name of method + * @param szCallback Callback + * + * @noreturn +*/ +native CE_RegisterMethod(const szClassname[], const szMethod[], const szCallback[], any:...); + +/** + * Registers a new virtual method for entity. + * + * @param szClassname Name of entity + * @param szMethod Name of method + * @param szCallback Callback + * + * @noreturn +*/ +native CE_RegisterVirtualMethod(const szClassname[], const szMethod[], const szCallback[], any:...); + +/** + * Gets handler of entity by name + * + * @param szClassname Name of entity + * + * @return Handler of the registered entity or -1 otherwise + */ +native CE:CE_GetHandler(const szClassname[]); + +/** + * Gets handler of entity by index + * + * @param pEntity Entity index + * + * @return Handler of the entity or -1 otherwise + */ +native CE:CE_GetHandlerByEntity(pEntity); + +/** + * Checks if entity is an instance of specific custom entity + * + * @param pEntity Entity index + * @param szTargetName Name of target entity to check + * + * @return Result true/false + */ +native bool:CE_IsInstanceOf(pEntity, const szTargetName[]); + +/** + * Checks if entity has member + * + * @param pEntity Entity index + * @param szMember Member name + */ +native bool:CE_HasMember(pEntity, const szMember[]); + +/** + * Deletes member of an entity + * + * @param pEntity Entity index + * @param szMember Member name + */ +native CE_DeleteMember(pEntity, const szMember[]); + +/** + * Gets member of an entity + * + * @param pEntity Entity index + * @param szMember Member name + * + * @return Member value + */ +native any:CE_GetMember(pEntity, const szMember[]); + +/** + * Sets member of an entity + * + * @param pEntity Entity index + * @param szMember Member name + * @param value Value to set + */ +native CE_SetMember(pEntity, const szMember[], any:value, bool:bReplace = true); + +/** + * Gets vector member of an entity + * + * @param pEntity Entity index + * @param szMember Member name + * @param vecOut Output vector + */ +native bool:CE_GetMemberVec(pEntity, const szMember[], Float:vecOut[]); + +/** + * Sets vector member of an entity + * + * @param pEntity Entity index + * @param szMember Member name + * @param vecValue Vector to set + */ +native CE_SetMemberVec(pEntity, const szMember[], const Float:vecValue[], bool:bReplace = true); + +/** + * Gets string member of an entity + * + * @param pEntity Entity index + * @param szMember Member name + * @param szOut Buffer to copy the value + * @param iLen Maximum size of buffer + */ +native bool:CE_GetMemberString(pEntity, const szMember[], szOut[], iLen); + +/** + * Sets string member of an entity + * + * @param pEntity Entity index + * @param szMember Member name + * @param szValue String value to set + */ +native CE_SetMemberString(pEntity, const szMember[], const szValue[], bool:bReplace = true); + +/** + * Call a method for entity. + * + * @param pEntity Entity index + * @param szMethod Name of method + * + * @return Method return value +*/ +native any:CE_CallMethod(pEntity, const szMethod[], any:...); + +/** + * Call a base method for entity. + * + * @param pEntity Entity index + * @param szMethod Name of method + * + * @return Method return value +*/ +native any:CE_CallBaseMethod(any:...); + +native CE_GetCallPluginId(); diff --git a/api/custom-entities/include/api_custom_entities_const.inc b/api/custom-entities/include/api_custom_entities_const.inc new file mode 100644 index 0000000..d3cda78 --- /dev/null +++ b/api/custom-entities/include/api_custom_entities_const.inc @@ -0,0 +1,88 @@ +#if defined _api_custom_entities_const_included + #endinput +#endif +#define _api_custom_entities_const_included + +#define CE_BASE_CLASSNAME "info_target" +#define CE_ENTITY_SECRET ('c'+'e'+'2') + +#define CE_MAX_NAME_LENGTH 64 +#define CE_MAX_MEMBER_NAME_LENGTH 64 +#define CE_MAX_CALLBACK_NAME_LENGTH 64 +#define CE_MAX_METHOD_NAME_LENGTH 64 + +#define CE_MEMBER_ID "_id" +#define CE_MEMBER_POINTER "_ptr" +#define CE_MEMBER_WORLD "_bWorld" +#define CE_MEMBER_ORIGIN "_vecOrigin" +#define CE_MEMBER_ANGLES "_vecAngles" +#define CE_MEMBER_MASTER "_szMaster" +#define CE_MEMBER_MODEL "_szModel" +#define CE_MEMBER_DELAY "_flDelay" +#define CE_MEMBER_NEXTKILL "_flNextKill" +#define CE_MEMBER_NEXTRESPAWN "_flNextRespawn" +#define CE_MEMBER_INITIALIZED "_bInitialized" +#define CE_MEMBER_BLOODCOLOR "_iBloodColor" +#define CE_MEMBER_LIFETIME "_flLifeTime" +#define CE_MEMBER_IGNOREROUNDS "_bIgnoreRounds" +#define CE_MEMBER_RESPAWNTIME "_flRespawnTime" +#define CE_MEMBER_MINS "_vecMins" +#define CE_MEMBER_MAXS "_vecMaxs" +#define CE_MEMBER_LASTINIT "_flLastInit" +#define CE_MEMBER_LASTSPAWN "_flLastSpawn" +#define CE_MEMBER_PLUGINID "_iPluginId" +#define CE_MEMBER_TARGETNAME "_szTargetname" +#define CE_MEMBER_TARGET "_szTarget" +#define CE_MEMBER_PICKED "_bPicked" + +enum CE { + CE_InvalidHandler = -1 +}; + +enum CEPreset { + CEPreset_None = 0, + CEPreset_Item, + CEPreset_NPC, + CEPreset_Prop, + CEPreset_Trigger, + CEPreset_BSP +}; + +enum CEFunction { + CEFunction_Invalid = -1, + CEFunction_KeyValue, // Calls when new key value obtained + CEFunction_Spawn, // Calls during entity spawn + CEFunction_Init, // Calls when entity is initialized (on first spawn) + CEFunction_InitPhysics, // Calls during on entity physics initialization + CEFunction_InitModel, // Calls during on entity model initialization + CEFunction_InitSize, // Calls during on entity size initialization + CEFunction_Spawned, // Calls when entity spawned + CEFunction_Touch, // Calls during entity touch + CEFunction_Touched, // Calls when entity touched + CEFunction_Think, // Calls when entity thinking + CEFunction_Pickup, // Calls when player touch item. Should return PLUGIN_HANDLED if picked + CEFunction_Picked, // Calls when player pick item + CEFunction_Activate, // Calls every trigger activation check + CEFunction_Activated, // Calls when player activates trigger + CEFunction_Restart, // Calls when entity is restarting + CEFunction_Kill, // Calls when something killing entity. return PLUGIN_HANDLED to block the kill. + CEFunction_Killed, // Calls when entity killed + CEFunction_Remove, // Calls before entity remove +}; + +enum CEMemberType { + CEMemberType_Invalid = -1, + CEMemberType_Cell, + CEMemberType_Float, + CEMemberType_String, + CEMemberType_Vector +}; + +enum { + CE_MP_Invalid = -1, + CE_MP_Cell, + CE_MP_Float, + CE_MP_String, + CE_MP_Array, + CE_MP_FloatArray +}; diff --git a/api/custom-events/README.md b/api/custom-events/README.md new file mode 100644 index 0000000..772aa5c --- /dev/null +++ b/api/custom-events/README.md @@ -0,0 +1,133 @@ +# Custom Events API +The Custom Events API provides a flexible event pattern implementation that allows you to emit and subscribe to events using string keys. With this API, there is no need to explicitly register events. You can simply emit and subscribe to events on the fly. + +## Emitting an Event +To emit an event, call the CustomEvent_Emit function and pass the event key as a parameter. + +```cpp +CustomEvent_Emit("my-event"); +``` + +## Subscribing to an Event +To subscribe to a specific event, use the CustomEvent_Subscribe function. Provide the event key and the callback function as parameters. + +Subscribe to an event: + +```cpp +CustomEvent_Subscribe("my-event", "EventSubscriber_MyEvent"); +``` + +Event subscriber function: + +```cpp +public EventSubscriber_MyEvent() { + log_amx("Subscriber"); +} +``` + +## Registering an Event with Parameters +You can register an event with parameters using the `CustomEvent_Register` function. Provide the event key, parameter types, and the number of parameters. + +```cpp +CustomEvent_Register("my-event", EP_Cell, EP_Float, EP_Array, 3, EP_FloatArray, 3, EP_String, EP_Cell); +``` + +There are two ways to obtain event parameters. + +Using arguments in the subscriber function: +```cpp +public EventSubscriber_MyEvent(iCell, Float:flFloat, const rgiArray[3], const Float:rgflArray[3], const szString[]) { + log_amx("Subscriber"); + + log_amx("iCell: %d", iCell); + log_amx("flFloat: %f", flFloat); + log_amx("rgiArray: {%d, %d, %d}", rgiArray[0], rgiArray[1], rgiArray[2]); + log_amx("rgflArray: {%f, %f, %f}", rgflArray[0], rgflArray[1], rgflArray[2]); + log_amx("szString: %s", szString); +} +``` + +Using getter functions in the subscriber function: + +```cpp +public EventSubscriber_MyEvent() { + log_amx("Subscriber"); + + new iCell = CustomEvent_GetParam(1); + new Float:flFloat = CustomEvent_GetParamFloat(2); + new rgiArray[3]; CustomEvent_GetParamArray(3, rgiArray, sizeof(rgiArray)); + new Float:rgflArray[3]; CustomEvent_GetParamFloatArray(4, rgflArray, sizeof(rgflArray)); + new szString[128]; CustomEvent_GetParamString(5, szString, charsmax(szString)); + + log_amx("iCell: %d", iCell); + log_amx("flFloat: %f", flFloat); + log_amx("rgiArray: {%d, %d, %d}", rgiArray[0], rgiArray[1], rgiArray[2]); + log_amx("rgflArray: {%f, %f, %f}", rgflArray[0], rgflArray[1], rgflArray[2]); + log_amx("szString: %s", szString); +} +``` + +## Using a Global Forward to Handle Events +You can use a forward declaration to handle all emitted events. Define a public CustomEvent_Fw_Emit function in your plugin. + +```cpp +public CustomEvent_Fw_Emit(const szEvent[]) { + log_amx("Event Forward %s", szEvent); + + if (equal(szEvent, "my-event")) { + new iCell = CustomEvent_GetParam(1); + new Float:flFloat = CustomEvent_GetParamFloat(2); + new rgiArray[3]; CustomEvent_GetParamArray(3, rgiArray, sizeof(rgiArray)); + new Float:rgflArray[3]; CustomEvent_GetParamFloatArray(4, rgflArray, sizeof(rgflArray)); + new szString[128]; CustomEvent_GetParamString(5, szString, charsmax(szString)); + + log_amx("iCell: %d", iCell); + log_amx("flFloat: %f", flFloat); + log_amx("rgiArray: {%d, %d, %d}", rgiArray[0], rgiArray[1], rgiArray[2]); + log_amx("rgflArray: {%f, %f, %f}", rgflArray[0], rgflArray[1], rgflArray[2]); + log_amx("szString: %s", szString); + } +} +``` + +To block the event emit, return `PLUGIN_HANDLED` from the forward function. It will stop subscribers from being called. + +```cpp +public CustomEvent_Fw_Emit(const szEvent[]) { + if (equal(szEvent, "authorize")) { + new szUsername[128]; CustomEvent_GetParamString(1, szUsername, charsmax(szUsername)); + new szPassword[128]; CustomEvent_GetParamString(2, szPassword, charsmax(szPassword)); + + if (!equal(szUsername, "admin") || !equal(szPassword, "admin")) { + return PLUGIN_HANDLED; + } + + return PLUGIN_CONTINUE; + } + + return PLUGIN_CONTINUE; +} +``` + +# Using activator entity +Custom events support the activator entity. It is useful if you need to emit the event from a specific entity and want to handle the activator in the forward or subscriber function. + +```cpp +public plugin_init() { + register_plugin(PLUGIN, VERSION, AUTHOR); + + register_clcmd("boo", "Command_Boo"); +} + +public Command_Boo(pPlayer) { + CustomEvent_SetActivator(pPlayer); + CustomEvent_Emit("boo-event"); +} + +public CustomEvent_Fw_Emit(const szEvent[]) { + if (equal(szEvent, "boo-event")) { + new pActivator = CustomEvent_GetActivator(); + client_print(pActivator, print_center, "Boo!"); + } +} +``` diff --git a/api/custom-events/api_custom_events.sma b/api/custom-events/api_custom_events.sma new file mode 100644 index 0000000..30791e0 --- /dev/null +++ b/api/custom-events/api_custom_events.sma @@ -0,0 +1,417 @@ +#pragma semicolon 1 + +#include +#include + +#include + +#include + +#define PLUGIN "[API] Custom Events" +#define VERSION "1.0.0" +#define AUTHOR "Hedgehog Fog" + +#define LOG_PREFIX "[Custom Events] " + +#define DEFAULT_CELL_VALUE 0 +#define DEFAULT_FLOAT_VALUE 0.0 +#define DEFAULT_STRING_VALUE NULL_STRING + +enum EventParam { + EventParam_Type = 0, + EventParam_Size +}; + +enum EventSubscriber { + EventSubscriber_PluginId = 0, + EventSubscriber_FunctionId +}; + +new g_fwEmit; + +new g_szBuffer[MAX_STRING_LENGTH]; +new g_rgiBuffer[MAX_STRING_LENGTH]; +new Float:g_rgflBuffer[MAX_STRING_LENGTH]; + +new Trie:g_itEventParamTypes; +new Trie:g_itEventSubscribers; + +new g_pCurrentActivator = 0; +new DataPack:g_dpCurrentParamData = Invalid_DataPack; +new Array:g_irgCurrentParamOffsets = Invalid_Array; + +public plugin_precache() { + g_itEventParamTypes = TrieCreate(); + g_itEventSubscribers = TrieCreate(); +} + +public plugin_init() { + register_plugin(PLUGIN, VERSION, AUTHOR); + + g_fwEmit = CreateMultiForward("CustomEvent_Fw_Emit", ET_STOP, FP_STRING, FP_CELL); +} + +public plugin_end() { + for (new TrieIter:iIterator = TrieIterCreate(g_itEventParamTypes); !TrieIterEnded(iIterator); TrieIterNext(iIterator)) { + new Array:irgParamTypes = Invalid_Array; + if (!TrieIterGetCell(iIterator, irgParamTypes)) continue; + if (irgParamTypes != Invalid_Array) ArrayDestroy(irgParamTypes); + } + + for (new TrieIter:iIterator = TrieIterCreate(g_itEventSubscribers); !TrieIterEnded(iIterator); TrieIterNext(iIterator)) { + new Array:irgSubscribers = Invalid_Array; + if (!TrieIterGetCell(iIterator, irgSubscribers)) continue; + if (irgSubscribers != Invalid_Array) ArrayDestroy(irgSubscribers); + } + + TrieDestroy(g_itEventParamTypes); + TrieDestroy(g_itEventSubscribers); +} + +public plugin_natives() { + register_library("api_custom_events"); + register_native("CustomEvent_Register", "Native_RegisterEvent"); + register_native("CustomEvent_Subscribe", "Native_SubscribeEvent"); + register_native("CustomEvent_Emit", "Native_EmitEvent"); + register_native("CustomEvent_GetParamsNum", "Native_GetParamsNum"); + register_native("CustomEvent_GetParamType", "Native_GetParamType"); + register_native("CustomEvent_GetParam", "Native_GetParam"); + register_native("CustomEvent_GetParamFloat", "Native_GetParamFloat"); + register_native("CustomEvent_GetParamString", "Native_GetParamString"); + register_native("CustomEvent_GetParamArray", "Native_GetParamArray"); + register_native("CustomEvent_GetParamFloatArray", "Native_GetParamFloatArray"); + register_native("CustomEvent_GetActivator", "Native_GetActivator"); + register_native("CustomEvent_SetActivator", "Native_SetActivator"); +} + +public Native_RegisterEvent(iPluginId, iArgc) { + static szEvent[MAX_CUSTOM_EVENT_KEY_LENGTH]; get_string(1, szEvent, charsmax(szEvent)); + + if (TrieKeyExists(g_itEventParamTypes, szEvent)) { + log_error(AMX_ERR_NATIVE, "%sEvent ^"%s^" is already registered.", LOG_PREFIX); + return; + } + + static Array:irgParamTypes; irgParamTypes = ArrayCreate(_:EventParam, iArgc - 1); + + for (new iParam = 2; iParam <= iArgc; ++iParam) { + static rgParam[EventParam]; + rgParam[EventParam_Type] = get_param_byref(iParam); + rgParam[EventParam_Size] = 1; + + switch (rgParam[EventParam_Type]) { + case EP_Array, EP_FloatArray: { + rgParam[EventParam_Size] = get_param_byref(iParam + 1); + iParam++; + } + } + + ArrayPushArray(irgParamTypes, rgParam[any:0], _:EventParam); + } + + TrieSetCell(g_itEventParamTypes, szEvent, irgParamTypes); +} + +public Native_SubscribeEvent(iPluginId, iArgc) { + static szEvent[MAX_CUSTOM_EVENT_KEY_LENGTH]; get_string(1, szEvent, charsmax(szEvent)); + static szCallback[64]; get_string(2, szCallback, charsmax(szCallback)); + + if (!TrieKeyExists(g_itEventSubscribers, szEvent)) { + TrieSetCell(g_itEventSubscribers, szEvent, ArrayCreate(_:EventSubscriber)); + } + + static Array:irgSubscribers; TrieGetCell(g_itEventSubscribers, szEvent, irgSubscribers); + + static rgSubscriber[EventSubscriber]; + rgSubscriber[EventSubscriber_PluginId] = iPluginId; + rgSubscriber[EventSubscriber_FunctionId] = get_func_id(szCallback, iPluginId); + ArrayPushArray(irgSubscribers, rgSubscriber[any:0], _:EventSubscriber); +} + +public Native_EmitEvent(iPluginId, iArgc) { + static szEvent[MAX_CUSTOM_EVENT_KEY_LENGTH]; get_string(1, szEvent, charsmax(szEvent)); + + static DataPack:dpParams; dpParams = CreateDataPack(); + + if (TrieKeyExists(g_itEventParamTypes, szEvent)) { + static Array:irgParamTypes; TrieGetCell(g_itEventParamTypes, szEvent, irgParamTypes); + + static iParamsNum; iParamsNum = ArraySize(irgParamTypes); + for (new iEventParam = 0; iEventParam < iParamsNum; ++iEventParam) { + static iParam; iParam = 2 + iEventParam; + static iType; iType = ArrayGetCell(irgParamTypes, iEventParam, _:EventParam_Type); + static iSize; iSize = ArrayGetCell(irgParamTypes, iEventParam, _:EventParam_Size); + static bool:bUseDefault; bUseDefault = iParam > iArgc; + + switch (iType) { + case EP_Cell: { + WritePackCell(dpParams, bUseDefault ? DEFAULT_CELL_VALUE : get_param_byref(iParam)); + } + case EP_Float: { + WritePackFloat(dpParams, bUseDefault ? DEFAULT_FLOAT_VALUE : Float:get_param_byref(iParam)); + } + case EP_String: { + if (bUseDefault) { + copy(g_szBuffer, sizeof(g_szBuffer), DEFAULT_STRING_VALUE); + } else { + get_string(iParam, g_szBuffer, charsmax(g_szBuffer)); + } + + WritePackString(dpParams, g_szBuffer); + } + case EP_Array: { + if (bUseDefault) { + arrayset(g_rgiBuffer, DEFAULT_FLOAT_VALUE, iSize); + } else { + get_array(iParam, g_rgiBuffer, iSize); + } + + WritePackArray(dpParams, g_rgiBuffer, iSize); + } + case EP_FloatArray: { + if (bUseDefault) { + arrayset(g_rgflBuffer, DEFAULT_FLOAT_VALUE, iSize); + } else { + get_array_f(iParam, g_rgflBuffer, iSize); + } + + WritePackFloatArray(dpParams, g_rgflBuffer, iSize); + } + } + } + } + + ResetPack(dpParams); + EmitEvent(szEvent, dpParams); + + DestroyDataPack(dpParams); +} + +public Native_SetActivator(iPluginId, iArgc) { + static pActivator; pActivator = get_param(1); + + if (pActivator && !pev_valid(pActivator)) { + log_error(AMX_ERR_NATIVE, "%sCannot set emitter. %d is not valid entity.", LOG_PREFIX, pActivator); + return; + } + + g_pCurrentActivator = pActivator; +} + +public Native_GetActivator(iPluginId, iArgc) { + return g_pCurrentActivator; +} + +public Native_GetParamsNum(iPluginId, iArgc) { + static szEvent[MAX_CUSTOM_EVENT_KEY_LENGTH]; get_string(1, szEvent, charsmax(szEvent)); + + return GetEventParamsNum(szEvent); +} + +public Native_GetParamType(iPluginId, iArgc) { + static szEvent[MAX_CUSTOM_EVENT_KEY_LENGTH]; get_string(1, szEvent, charsmax(szEvent)); + static iParam; iParam = get_param(2); + + return GetEventParamType(szEvent, iParam); +} + +public any:Native_GetParam(iPluginId, iArgc) { + static iParam; iParam = get_param(1); + return GetCurrentEventParam(iParam - 1); +} + +public Float:Native_GetParamFloat(iPluginId, iArgc) { + static iParam; iParam = get_param(1); + return GetCurrentEventParamFloat(iParam - 1); +} + +public Float:Native_GetParamString(iPluginId, iArgc) { + static iParam; iParam = get_param(1); + static iLen; iLen = get_param(3); + GetCurrentEventParamString(iParam - 1, g_szBuffer, iLen); + set_string(2, g_szBuffer, iLen); +} + +public Native_GetParamArray(iPluginId, iArgc) { + static iParam; iParam = get_param(1); + static iLen; iLen = get_param(3); + GetCurrentEventParamArray(iParam - 1, g_rgiBuffer, iLen); + set_array(2, g_rgiBuffer, iLen); +} + +public Native_GetParamFloatArray(iPluginId, iArgc) { + static iParam; iParam = get_param(1); + static iLen; iLen = get_param(3); + GetCurrentEventParamFloatArray(iParam - 1, g_rgflBuffer, iLen); + set_array_f(2, g_rgflBuffer, iLen); +} + +GetEventParamsNum(const szEvent[]) { + static Array:irgParamTypes; + if (!TrieGetCell(g_itEventParamTypes, szEvent, irgParamTypes)) return 0; + + return ArraySize(irgParamTypes); +} + +GetEventParamType(const szEvent[], iParam) { + static Array:irgParamTypes; + if (!TrieGetCell(g_itEventParamTypes, szEvent, irgParamTypes)) return EP_Invalid; + if (iParam <= ArraySize(irgParamTypes)) return EP_Invalid; + + return ArrayGetCell(irgParamTypes, iParam, _:EventParam_Type); +} + +any:GetCurrentEventParam(iParam) { + if (g_irgCurrentParamOffsets == Invalid_Array) return DEFAULT_CELL_VALUE; + + SetPackPosition(g_dpCurrentParamData, GetCurrentOffset(iParam)); + + return ReadPackCell(g_dpCurrentParamData); +} + +Float:GetCurrentEventParamFloat(iParam) { + if (g_irgCurrentParamOffsets == Invalid_Array) return DEFAULT_FLOAT_VALUE; + + SetPackPosition(g_dpCurrentParamData, GetCurrentOffset(iParam)); + + return ReadPackFloat(g_dpCurrentParamData); +} + +GetCurrentEventParamString(iParam, szOut[], iLen) { + if (g_irgCurrentParamOffsets == Invalid_Array) { + copy(szOut, iLen, DEFAULT_STRING_VALUE); + return; + } + + SetPackPosition(g_dpCurrentParamData, GetCurrentOffset(iParam)); + + ReadPackString(g_dpCurrentParamData, szOut, iLen); +} + +GetCurrentEventParamArray(iParam, rgiOut[], iLen) { + if (g_irgCurrentParamOffsets == Invalid_Array) { + arrayset(rgiOut, DEFAULT_CELL_VALUE, iLen); + return; + } + + SetPackPosition(g_dpCurrentParamData, GetCurrentOffset(iParam)); + + ReadPackArray(g_dpCurrentParamData, rgiOut, iLen); +} + +GetCurrentEventParamFloatArray(iParam, Float:rgflOut[], iLen) { + if (g_irgCurrentParamOffsets == Invalid_Array) { + arrayset(rgflOut, DEFAULT_FLOAT_VALUE, iLen); + return; + } + + SetPackPosition(g_dpCurrentParamData, GetCurrentOffset(iParam)); + + ReadPackFloatArray(g_dpCurrentParamData, rgflOut, iLen); +} + +DataPackPos:GetCurrentOffset(iParam) { + return ArrayGetCell(g_irgCurrentParamOffsets, iParam); +} + +EmitEvent(const szEvent[], DataPack:dpParams) { + if (g_pCurrentActivator && !pev_valid(g_pCurrentActivator)) { + log_error(AMX_ERR_NATIVE, "%sCannot emit event ^"%s^" from entity! Invalid entity %d.", LOG_PREFIX, szEvent, g_pCurrentActivator); + return; + } + + g_dpCurrentParamData = dpParams; + g_irgCurrentParamOffsets = Invalid_Array; + + static Array:irgParamTypes; irgParamTypes = Invalid_Array; + + if (TrieKeyExists(g_itEventParamTypes, szEvent)) { + TrieGetCell(g_itEventParamTypes, szEvent, irgParamTypes); + g_irgCurrentParamOffsets = GetEventParamOffsets(dpParams, irgParamTypes); + } + + static iForwardReturn; ExecuteForward(g_fwEmit, iForwardReturn, szEvent, g_pCurrentActivator); + + if (iForwardReturn == PLUGIN_CONTINUE && TrieKeyExists(g_itEventSubscribers, szEvent)) { + static Array:irgSubscribers; TrieGetCell(g_itEventSubscribers, szEvent, irgSubscribers); + + static iSubscribersNum; iSubscribersNum = ArraySize(irgSubscribers); + for (new iSubscriber = 0; iSubscriber < iSubscribersNum; ++iSubscriber) { + ResetPack(dpParams); + + static iPluginId; iPluginId = ArrayGetCell(irgSubscribers, iSubscriber, _:EventSubscriber_PluginId); + static iFunctionId; iFunctionId = ArrayGetCell(irgSubscribers, iSubscriber, _:EventSubscriber_FunctionId); + CallEventCallback(iPluginId, iFunctionId, dpParams, irgParamTypes); + } + } + + if (g_irgCurrentParamOffsets != Invalid_Array) { + ArrayDestroy(g_irgCurrentParamOffsets); + } + + g_pCurrentActivator = 0; + g_irgCurrentParamOffsets = Invalid_Array; + g_dpCurrentParamData = Invalid_DataPack; +} + +Array:GetEventParamOffsets(DataPack:dpParams, Array:irgParamTypes) { + static Array:irgOffsets; irgOffsets = ArrayCreate(); + + static DataPackPos:iPos; iPos = GetPackPosition(dpParams); + static iParamsNum; iParamsNum = ArraySize(irgParamTypes); + + for (new iParam = 0; iParam < iParamsNum; ++iParam) { + ArrayPushCell(irgOffsets, GetPackPosition(dpParams)); + + static iType; iType = ArrayGetCell(irgParamTypes, iParam, _:EventParam_Type); + + switch (iType) { + case EP_Cell: ReadPackCell(dpParams); + case EP_Float: ReadPackFloat(dpParams); + case EP_String: ReadPackString(dpParams, g_szBuffer, charsmax(g_szBuffer)); + case EP_Array: ReadPackArray(dpParams, g_rgiBuffer, 0); + case EP_FloatArray: ReadPackFloatArray(dpParams, g_rgflBuffer, 0); + } + } + + SetPackPosition(dpParams, iPos); + + return irgOffsets; +} + +CallEventCallback(iPluginId, iFunctionId, DataPack:dpParams, Array:irgParamTypes) { + callfunc_begin_i(iFunctionId, iPluginId); + + if (irgParamTypes != Invalid_Array) { + static iParamsNum; iParamsNum = ArraySize(irgParamTypes); + + for (new iParam = 0; iParam < iParamsNum; ++iParam) { + static iType; iType = ArrayGetCell(irgParamTypes, iParam, _:EventParam_Type); + + switch (iType) { + case EP_Cell: { + static iValue; iValue = ReadPackCell(dpParams); + callfunc_push_int(iValue); + } + case EP_Float: { + static Float:flValue; flValue = ReadPackFloat(dpParams); + callfunc_push_float(flValue); + } + case EP_String: { + ReadPackString(dpParams, g_szBuffer, charsmax(g_szBuffer)); + callfunc_push_str(g_szBuffer); + } + case EP_Array: { + static iLen; iLen = ReadPackArray(dpParams, g_rgiBuffer); + callfunc_push_array(g_rgiBuffer, iLen, false); + } + case EP_FloatArray: { + static iLen; iLen = ReadPackFloatArray(dpParams, g_rgflBuffer); + callfunc_push_array(_:g_rgflBuffer, iLen, false); + } + } + } + } + + callfunc_end(); +} diff --git a/api/custom-events/include/api_custom_events.inc b/api/custom-events/include/api_custom_events.inc new file mode 100644 index 0000000..3952d21 --- /dev/null +++ b/api/custom-events/include/api_custom_events.inc @@ -0,0 +1,133 @@ +#if defined _api_custom_events_included + #endinput +#endif +#define _api_custom_events_included + +#pragma reqlib api_custom_events + +#include + +/** + * Registers a custom event with the specified ID and parameter types. + * + * @param szEvent The ID of the custom event. + * @param any The parameter types for the custom event. + * + * @noreturn +*/ +native CustomEvent_Register(const szEvent[], any:...); + +/** + * Subscribes to the specified custom event with the given callback function. + * + * @param szEvent The ID of the custom event. + * @param szCallback The name of the callback function. + * + * @noreturn + */ +native CustomEvent_Subscribe(const szEvent[], const szCallback[]); + +/** + * Emits the specified custom event with the given parameters. + * + * @param szEvent The ID of the custom event. + * @param any The parameters to be emitted. + * + * @noreturn + */ +native CustomEvent_Emit(const szEvent[], any:...); + +/** + * Gets the number of parameters for the specified custom event. + * @param szEvent The ID of the custom event. + * + * @return The number of parameters for the custom event. + */ +native CustomEvent_GetParamsNum(const szEvent[]); + +/** + * Gets the type of the specified parameter for the custom event. + * + * @param szEvent The ID of the custom event. + * @param iParam The index of the parameter, starting from 1. + * + * @return The type of the parameter. + */ +native CustomEvent_GetParamType(const szEvent[], iParam); + +/** + * Gets the value of the specified parameter for the custom event. + * + * @param iParam The index of the parameter, starting from 1. + * + * @return The value of the parameter. + */ +native any:CustomEvent_GetParam(iParam); + +/** + * Gets the floating-point value of the specified parameter for the custom event. + * + * @param iParam The index of the parameter, starting from 1. + * + * @return The floating-point value of the parameter. + */ +native Float:CustomEvent_GetParamFloat(iParam); + +/** + * Gets the string value of the specified parameter for the custom event. + * + * @param iParam The index of the parameter, starting from 1. + * @param szOut Buffer to store the string value. + * @param iLen The maximum length of the output buffer. + * + * @noreturn + */ +native CustomEvent_GetParamString(iParam, szOut[], iLen); + +/** + * Gets the integer array value of the specified parameter for the custom event. + * + * @param iParam The index of the parameter, starting from 1. + * @param rgiOut Buffer to store the integer array value. + * @param iLen The maximum length of the output buffer. + * + * @noreturn + */ +native CustomEvent_GetParamArray(iParam, rgiOut[], iLen); + +/** + * Gets the floating-point array value of the specified parameter for the custom event. + * + * @param iParam The index of the parameter, starting from 1. + * @param rgflOut Buffer to store the floating-point array value. + * @param iLen The maximum length of the output buffer. + * + * @noreturn + */ +native CustomEvent_GetParamFloatArray(iParam, Float:rgflOut[], iLen); + +/** + * Sets the activator for the custom event. + * + * @param pEntity The entity to set as the activator. + * + * @noreturn + */ +native CustomEvent_SetActivator(pEntity); + +/** + * Retrieves the activator of the custom event. + * + * @return The activator entity of the custom event. + */ +native CustomEvent_GetActivator(); + +/** + * Forwards the specified custom event with the given parameters and activator. + * + * @param szEvent The ID of the custom event. + * @param pActivator The activator of the event. Pass 0 if no activator is needed. + * + * @noreturn + */ +forward CustomEvent_Fw_Emit(const szEvent[], pActivator); diff --git a/api/custom-events/include/api_custom_events_const.inc b/api/custom-events/include/api_custom_events_const.inc new file mode 100644 index 0000000..16a453e --- /dev/null +++ b/api/custom-events/include/api_custom_events_const.inc @@ -0,0 +1,15 @@ +#if defined _api_custom_events_const_included + #endinput +#endif +#define _api_custom_events_const_included + +#define MAX_CUSTOM_EVENT_KEY_LENGTH 64 + +enum { + EP_Invalid = -1, + EP_Cell, + EP_Float, + EP_String, + EP_Array, + EP_FloatArray +}; \ No newline at end of file diff --git a/api/custom-weapons/README.md b/api/custom-weapons/README.md new file mode 100644 index 0000000..7b6727f --- /dev/null +++ b/api/custom-weapons/README.md @@ -0,0 +1,158 @@ +# Custom Weapons API + +## Simple 9mm handgun +Example of simple handgun from Half-Life. + +```cpp +#pragma semicolon 1 + +#include +#include +#include +#include + +#include + +#define PLUGIN "[Weapon] 9mm Handgun" +#define VERSION "1.0.0" +#define AUTHOR "Hedgehog Fog" + +#define WEAPON_NAME "weapon_9mmhandgun" +#define WEAPON_ID CSW_FIVESEVEN +#define WEAPON_AMMO_ID 10 +#define WEAPON_SLOT_ID 1 +#define WEAPON_SLOT_POS 6 +#define WEAPON_CLIP_SIZE 7 +#define WEAPON_ICON "fiveseven" +#define WEAPON_DAMAGE 30.0 +#define WEAPON_SPREAD_MODIFIER 0.75 +#define WEAPON_RATE 0.125 +#define WEAPON_RELOAD_DURATION 1.68 + +new const g_szHudTxt[] = "sprites/weapon_9mmhandgun.txt"; + +new const g_szWeaponModelV[] = "models/v_9mmhandgun.mdl"; +new const g_szWeaponModelP[] = "models/p_9mmhandgun.mdl"; +new const g_szWeaponModelW[] = "models/w_9mmhandgun.mdl"; +new const g_szShellModel[] = "models/shell.mdl"; + +new const g_szShotSound[] = "weapons/pl_gun3.wav"; +new const g_szReloadStartSound[] = "items/9mmclip1.wav"; +new const g_szReloadEndSound[] = "items/9mmclip2.wav"; + +new CW:g_iCwHandler; + +public plugin_precache() { + precache_generic(g_szHudTxt); + + precache_model(g_szWeaponModelV); + precache_model(g_szWeaponModelP); + precache_model(g_szWeaponModelW); + precache_model(g_szShellModel); + + precache_sound(g_szShotSound); + precache_sound(g_szReloadStartSound); + precache_sound(g_szReloadEndSound); + + g_iCwHandler = CW_Register(WEAPON_NAME, WEAPON_ID, WEAPON_CLIP_SIZE, WEAPON_AMMO_ID, 120, _, _, WEAPON_SLOT_ID, WEAPON_SLOT_POS, _, WEAPON_ICON, CWF_NoBulletSmoke); + CW_Bind(g_iCwHandler, CWB_Idle, "@Weapon_Idle"); + CW_Bind(g_iCwHandler, CWB_PrimaryAttack, "@Weapon_PrimaryAttack"); + CW_Bind(g_iCwHandler, CWB_Reload, "@Weapon_Reload"); + CW_Bind(g_iCwHandler, CWB_Deploy, "@Weapon_Deploy"); + CW_Bind(g_iCwHandler, CWB_GetMaxSpeed, "@Weapon_GetMaxSpeed"); + CW_Bind(g_iCwHandler, CWB_Spawn, "@Weapon_Spawn"); + CW_Bind(g_iCwHandler, CWB_WeaponBoxModelUpdate, "@Weapon_WeaponBoxSpawn"); + CW_Bind(g_iCwHandler, CWB_Holster, "@Weapon_Holster"); +} + +public plugin_init() { + register_plugin(PLUGIN, VERSION, AUTHOR); +} + +@Weapon_Idle(this) { + switch (random(3)) { + case 0: CW_PlayAnimation(this, 0, 61.0 / 16.0); + case 1: CW_PlayAnimation(this, 1, 61.0 / 16.0); + case 2: CW_PlayAnimation(this, 2, 61.0 / 14.0); + } +} + +@Weapon_PrimaryAttack(this) { + if (get_member(this, m_Weapon_iShotsFired) > 0) { + return; + } + + static Float:vecSpread[3]; + UTIL_CalculateWeaponSpread(this, Float:VECTOR_CONE_3DEGREES, 3.0, 0.1, 0.95, 3.5, vecSpread); + + if (CW_DefaultShot(this, WEAPON_DAMAGE, WEAPON_SPREAD_MODIFIER, WEAPON_RATE, vecSpread)) { + CW_PlayAnimation(this, 3, 0.71); + + new pPlayer = CW_GetPlayer(this); + + emit_sound(pPlayer, CHAN_WEAPON, g_szShotSound, VOL_NORM, ATTN_NORM, 0, PITCH_NORM); + + static Float:vecPunchAngle[3]; + pev(pPlayer, pev_punchangle, vecPunchAngle); + xs_vec_add(vecPunchAngle, Float:{-2.5, 0.0, 0.0}, vecPunchAngle); + + if (xs_vec_len(vecPunchAngle) > 0.0) { + set_pev(pPlayer, pev_punchangle, vecPunchAngle); + } + + CW_EjectWeaponBrass(this, engfunc(EngFunc_ModelIndex, g_szShellModel), 1); + } +} + +@Weapon_Reload(this) { + if (CW_DefaultReload(this, 5, WEAPON_RELOAD_DURATION)) { + new pPlayer = CW_GetPlayer(this); + emit_sound(pPlayer, CHAN_WEAPON, g_szReloadStartSound, VOL_NORM, ATTN_NORM, 0, PITCH_NORM); + } +} + +@Weapon_DefaultReloadEnd(this) { + new pPlayer = CW_GetPlayer(this); + emit_sound(pPlayer, CHAN_WEAPON, g_szReloadEndSound, VOL_NORM, ATTN_NORM, 0, PITCH_NORM); +} + +@Weapon_Deploy(this) { + CW_DefaultDeploy(this, g_szWeaponModelV, g_szWeaponModelP, 7, "onehanded"); +} + +Float:@Weapon_GetMaxSpeed(this) { + return 250.0; +} + +@Weapon_Spawn(this) { + engfunc(EngFunc_SetModel, this, g_szWeaponModelW); +} + +@Weapon_WeaponBoxSpawn(this, pWeaponBox) { + engfunc(EngFunc_SetModel, pWeaponBox, g_szWeaponModelW); +} + +@Weapon_Holster(this) { + CW_PlayAnimation(this, 8, 16.0 / 20.0); +} + +stock Float:UTIL_CalculateWeaponSpread(pWeapon, const Float:vecSpread[3], Float:flMovementFactor, Float:flFirstShotModifier, Float:flDuckFactor, Float:flAirFactor, Float:vecOut[3]) { + new Float:flSpreadRatio = 1.0; + + new pPlayer = get_member(pWeapon, m_pPlayer); + + static Float:vecVelocity[3]; pev(pPlayer, pev_velocity, vecVelocity); + if (xs_vec_len(vecVelocity) > 0) flSpreadRatio *= flMovementFactor; + + new iPlayerFlags = pev(pPlayer, pev_flags); + if (iPlayerFlags & FL_DUCKING) flSpreadRatio *= flDuckFactor; + if (~iPlayerFlags & FL_ONGROUND) flSpreadRatio *= flAirFactor; + + new iShotsFired = get_member(pWeapon, m_Weapon_iShotsFired); + if (!iShotsFired) flSpreadRatio *= flFirstShotModifier; + + xs_vec_mul_scalar(vecSpread, flSpreadRatio, vecOut); + + return flSpreadRatio; +} +``` diff --git a/api/custom-weapons/api_custom_weapons.sma b/api/custom-weapons/api_custom_weapons.sma new file mode 100644 index 0000000..6c22825 --- /dev/null +++ b/api/custom-weapons/api_custom_weapons.sma @@ -0,0 +1,2608 @@ +#pragma semicolon 1 + +#include +#include +#include +#include +#include +#include + +#include + +#define PLUGIN "[API] Custom Weapons" +#define VERSION "0.8.0" +#define AUTHOR "Hedgehog Fog" + +#define WALL_PUFF_SPRITE "sprites/wall_puff1.spr" + +#define VEC_DUCK_HULL_MIN Float:{-16.0, -16.0, -18.0} +#define VEC_DUCK_HULL_MAX Float:{16.0, 16.0, 18.0} + +#define IS_PLAYER(%1) (%1 > 0 && %1 <= MaxClients) + +#define TOKEN 743647146 + +enum _:WeaponListMessage { + WL_WeaponName[32], + WL_PrimaryAmmoType, + WL_PrimaryAmmoMaxAmount, + WL_SecondaryAmmoType, + WL_SecondaryAmmoMaxAmount, + WL_SlotId, + WL_NumberInSlot, + WL_WeaponId, + WL_Flags +}; + +enum _:Function { + Function_PluginId, + Function_FunctionId +}; + +new const g_rgszWeaponNames[CSW_LAST_WEAPON + 1][] = { + "", + "weapon_p228", + "weapon_shield", + "weapon_scout", + "weapon_hegrenade", + "weapon_xm1014", + "weapon_c4", + "weapon_mac10", + "weapon_aug", + "weapon_smokegrenade", + "weapon_elite", + "weapon_fiveseven", + "weapon_ump45", + "weapon_sg550", + "weapon_galil", + "weapon_famas", + "weapon_usp", + "weapon_glock18", + "weapon_awp", + "weapon_mp5navy", + "weapon_m249", + "weapon_m3", + "weapon_m4a1", + "weapon_tmp", + "weapon_g3sg1", + "weapon_flashbang", + "weapon_deagle", + "weapon_sg552", + "weapon_ak47", + "weapon_knife", + "weapon_p90" +}; + +new gmsgWeaponList; +new gmsgDeathMsg; + +new bool:g_bWeaponHooks[CSW_LAST_WEAPON + 1]; +new g_weaponListDefaults[CSW_LAST_WEAPON + 1][WeaponListMessage]; + +new Array:g_rgWeapons[CW_Data]; +new Trie:g_rgWeaponsMap; +new g_iWeaponCount; + +new Float:g_flNextPredictionUpdate[MAX_PLAYERS + 1]; +new bool:g_bKnifeHolstered[MAX_PLAYERS + 1]; + +new g_pNewWeaponboxEnt = -1; +new g_pKillerItem = -1; +new bool:g_bSupercede; +new bool:g_bPrecache; + +new Array:g_irgDecals; + +public plugin_precache() { + g_bPrecache = true; + + InitStorages(); + + register_forward(FM_UpdateClientData, "OnUpdateClientData_Post", 1); + register_forward(FM_PrecacheEvent, "OnPrecacheEvent_Post", 1); + register_forward(FM_SetModel, "OnSetModel_Post", 1); + register_forward(FM_DecalIndex, "OnDecalIndex_Post", 1); + + RegisterHam(Ham_Spawn, "weaponbox", "OnWeaponboxSpawn", .Post = 0); + RegisterHamPlayer(Ham_Player_PreThink, "OnPlayerPreThink_Post", .Post = 1); + RegisterHamPlayer(Ham_TakeDamage, "OnPlayerTakeDamage", .Post = 0); + RegisterHamPlayer(Ham_TakeDamage, "OnPlayerTakeDamage_Post", .Post = 1); + + precache_model(WALL_PUFF_SPRITE); +} + +public plugin_init() { + g_bPrecache = false; + + register_plugin(PLUGIN, VERSION, AUTHOR); + + gmsgWeaponList = get_user_msgid("WeaponList"); + gmsgDeathMsg = get_user_msgid("DeathMsg"); + + register_message(gmsgWeaponList, "OnMessage_WeaponList"); + register_message(gmsgDeathMsg, "OnMessage_DeathMsg"); +} + +public plugin_cfg() { + InitWeaponHooks(); +} + +public plugin_natives() { + register_library("api_custom_weapons"); + + register_native("CW_Register", "Native_Register"); + register_native("CW_GetHandlerByEntity", "Native_GetHandlerByEntity"); + register_native("CW_GetHandler", "Native_GetHandler"); + register_native("CW_GetWeaponData", "Native_GetWeaponData"); + register_native("CW_GetWeaponStringData", "Native_GetWeaponStringData"); + register_native("CW_GiveWeapon", "Native_GiveWeapon"); + register_native("CW_HasWeapon", "Native_HasWeapon"); + register_native("CW_SpawnWeapon", "Native_SpawnWeapon"); + register_native("CW_SpawnWeaponBox", "Native_SpawnWeaponBox"); + + register_native("CW_Deploy", "Native_Deploy"); + register_native("CW_Holster", "Native_Holster"); + register_native("CW_ItemPostFrame", "Native_ItemPostFrame"); + register_native("CW_Idle", "Native_Idle"); + register_native("CW_Reload", "Native_Reload"); + register_native("CW_PrimaryAttack", "Native_PrimaryAttack"); + register_native("CW_SecondaryAttack", "Native_SecondaryAttack"); + + register_native("CW_FireBulletsPlayer", "Native_FireBulletsPlayer"); + register_native("CW_EjectWeaponBrass", "Native_EjectWeaponBrass"); + register_native("CW_PlayAnimation", "Native_PlayAnimation"); + register_native("CW_GetPlayer", "Native_GetPlayer"); + + register_native("CW_DefaultDeploy", "Native_DefaultDeploy"); + register_native("CW_DefaultShot", "Native_DefaultShot"); + register_native("CW_DefaultShotgunShot", "Native_DefaultShotgunShot"); + register_native("CW_DefaultSwing", "Native_DefaultSwing"); + register_native("CW_DefaultReload", "Native_DefaultReload"); + register_native("CW_DefaultShotgunReload", "Native_DefaultShotgunReload"); + register_native("CW_DefaultShotgunIdle", "Native_DefaultShotgunIdle"); + + register_native("CW_GrenadeDetonate", "Native_GrenadeDetonate"); + register_native("CW_GrenadeSmoke", "Native_GrenadeSmoke"); + register_native("CW_RemovePlayerItem", "Native_RemovePlayerItem"); + + register_native("CW_Bind", "Native_Bind"); +} + +public plugin_end() { + DestroyStorages(); +} + +// ANCHOR: Natives + +public Native_Bind(iPluginId, iArgc) { + new CW:iHandler = CW:get_param(1); + new iBinding = get_param(2); + + new szFunctionName[32]; + get_string(3, szFunctionName, charsmax(szFunctionName)); + + Bind(iHandler, iBinding, iPluginId, get_func_id(szFunctionName, iPluginId)); +} + +public CW:Native_GetHandlerByEntity(iPluginId, iArgc) { + new pEntity = get_param(1); + return GetHandlerByEntity(pEntity); +} + +public CW:Native_GetHandler(iPluginId, iArgc) { + static szName[64]; + get_string(1, szName, charsmax(szName)); + + return GetHandler(szName); +} + +public any:Native_GetWeaponData(iPluginId, iArgc) { + new CW:iHandler = CW:get_param(1); + new CW_Data:iParam = CW_Data:get_param(2); + + return GetData(iHandler, iParam); +} + +public Native_GetWeaponStringData(iPluginId, iArgc) { + new CW:iHandler = CW:get_param(1); + new CW_Data:iParam = CW_Data:get_param(2); + + static szValue[128]; + GetStringData(iHandler, iParam, szValue, charsmax(szValue)); + + new iLen = get_param(4); + set_string(3, szValue, iLen); +} + +public CW:Native_Register(iPluginId, iArgc) { + new szName[64]; + get_string(1, szName, charsmax(szName)); + + new iWeaponId = get_param(2); + new iClipSize = get_param(3); + new iPrimaryAmmoType = get_param(4); + new iPrimaryAmmoMaxAmount = get_param(5); + new iSecondaryAmmoType = get_param(6); + new iSecondaryAmmoMaxAmount = get_param(7); + new iSlotId = get_param(8); + new iPosition = get_param(9); + new iWeaponFlags = get_param(10); + + new szIcon[16]; + get_string(11, szIcon, charsmax(szIcon)); + + new CW_Flags:iFlags = CW_Flags:get_param(12); + + return RegisterWeapon(iPluginId, szName, iWeaponId, iClipSize, iPrimaryAmmoType, iPrimaryAmmoMaxAmount, iSecondaryAmmoType, iSecondaryAmmoMaxAmount, iSlotId, iPosition, iWeaponFlags, szIcon, iFlags); +} + +public Native_GiveWeapon(iPluginId, iArgc) { + new pPlayer = get_param(1); + + static szWeapon[64]; + get_string(2, szWeapon, charsmax(szWeapon)); + + new CW:iHandler; + if (TrieGetCell(g_rgWeaponsMap, szWeapon, iHandler)) { + GiveWeapon(pPlayer, iHandler); + } +} + +public bool:Native_HasWeapon(iPluginId, iArgc) { + new pPlayer = get_param(1); + + static szWeapon[64]; + get_string(2, szWeapon, charsmax(szWeapon)); + + new CW:iHandler; + if (TrieGetCell(g_rgWeaponsMap, szWeapon, iHandler)) { + return HasWeapon(pPlayer, iHandler); + } + + return false; +} + +public Native_SpawnWeapon(iPluginId, iArgc) { + new CW:iHandler = CW:get_param(1); + return SpawnWeapon(iHandler); +} + +public Native_SpawnWeaponBox(iPluginId, iArgc) { + new CW:iHandler = CW:get_param(1); + return SpawnWeaponBox(iHandler); +} + +public bool:Native_DefaultDeploy(iPluginId, iArgc) { + new pWeapon = get_param(1); + + static szViewModel[64]; + get_string(2, szViewModel, charsmax(szViewModel)); + + static szWeaponModel[64]; + get_string(3, szWeaponModel, charsmax(szWeaponModel)); + + new iAnim = get_param(4); + + static szAnimExt[16]; + get_string(5, szAnimExt, charsmax(szAnimExt)); + + return DefaultDeploy(pWeapon, szViewModel, szWeaponModel, iAnim, szAnimExt); +} + +public Native_FireBulletsPlayer(iPluginId, iArgc) { + new pWeapon = get_param(1); + new iShots = get_param(2); + + static Float:vecSrc[3]; + get_array_f(3, vecSrc, sizeof(vecSrc)); + + static Float:vecDirShooting[3]; + get_array_f(4, vecDirShooting, sizeof(vecDirShooting)); + + static Float:vecSpread[3]; + get_array_f(5, vecSpread, sizeof(vecSpread)); + + new Float:flDistance = get_param_f(6); + new Float:flDamage = get_param_f(7); + new Float:flRangeModifier = get_param_f(8); + new pevAttacker = get_param(9); + + static Float:vecOut[3]; + + FireBulletsPlayer(pWeapon, iShots, vecSrc, vecDirShooting, vecSpread, flDistance, flDamage, flRangeModifier, pevAttacker, vecOut); + + set_array_f(10, vecOut, sizeof(vecOut)); +} + +public bool:Native_EjectWeaponBrass(iPluginId, iArgc) { + new pItem = get_param(1); + new iModelIndex = get_param(2); + new iSoundType = get_param(3); + + return EjectWeaponBrass(pItem, iModelIndex, iSoundType); +} + +public bool:Native_DefaultShot(iPluginId, iArgc) { + new pItem = get_param(1); + new Float:flDamage = get_param_f(2); + new Float:flRangeModifier = get_param_f(3); + new Float:flRate = get_param_f(4); + + static Float:vecSpread[3]; + get_array_f(5, vecSpread, sizeof(vecSpread)); + + new iShots = get_param(6); + new Float:flDistance = get_param_f(7); + + return DefaultShot(pItem, flDamage, flRangeModifier, flRate, vecSpread, iShots, flDistance); +} + +public bool:Native_DefaultShotgunShot(iPluginId, iArgc) { + new pItem = get_param(1); + new Float:flDamage = get_param_f(2); + new Float:flRangeModifier = get_param_f(3); + new Float:flRate = get_param_f(4); + new Float:flPumpDelay = get_param_f(5); + + static Float:vecSpread[3]; + get_array_f(6, vecSpread, sizeof(vecSpread)); + + new iShots = get_param(7); + new Float:flDistance = get_param_f(8); + + return DefaultShotgunShot(pItem, flDamage, flRangeModifier, flRate, flPumpDelay, vecSpread, iShots, flDistance); +} + +public Native_DefaultSwing(iPluginId, iArgc) { + new pItem = get_param(1); + new Float:flDamage = get_param_f(2); + new Float:flRate = get_param_f(3); + new Float:flDistance = get_param_f(4); + + return DefaultSwing(pItem, flDamage, flRate, flDistance); +} + +public Native_PlayAnimation(iPluginID, argc) { + new pItem = get_param(1); + new iSequence = get_param(2); + new Float:flDuration = get_param_f(3); + + PlayWeaponAnim(pItem, iSequence, flDuration); +} + +public Native_GetPlayer(iPluginID, argc) { + new pItem = get_param(1); + return GetPlayer(pItem); +} + +public bool:Native_DefaultReload(iPluginId, iArgc) { + new pItem = get_param(1); + new iAnim = get_param(2); + new Float:flDelay = get_param_f(3); + + return DefaultReload(pItem, iAnim, flDelay); +} + +public bool:Native_DefaultShotgunReload(iPluginId, iArgc) { + new pItem = get_param(1); + new iStartAnim = get_param(2); + new iEndAnim = get_param(3); + new Float:flDelay = get_param_f(4); + new Float:flDuration = get_param_f(5); + + return DefaultShotgunReload(pItem, iStartAnim, iEndAnim, flDelay, flDuration); +} + +public bool:Native_DefaultShotgunIdle(iPluginId, iArgc) { + new pItem = get_param(1); + new iAnim = get_param(2); + new iReloadEndAnim = get_param(3); + new Float:flDuration = get_param_f(4); + new Float:flReloadEndDuration = get_param_f(5); + + static szPumpSound[64]; + get_string(6, szPumpSound, charsmax(szPumpSound)); + + return DefaultShotgunIdle(pItem, iAnim, iReloadEndAnim, flDuration, flReloadEndDuration, szPumpSound); +} + +public Native_Deploy(iPluginId, iArgc) { + new pItem = get_param(1); + WeaponDeploy(pItem); +} + +public Native_Holster(iPluginId, iArgc) { + new pItem = get_param(1); + WeaponHolster(pItem); +} + +public Native_ItemPostFrame(iPluginId, iArgc) { + new pItem = get_param(1); + ItemPostFrame(pItem); +} + +public Native_Idle(iPluginId, iArgc) { + new pItem = get_param(1); + WeaponIdle(pItem); +} + +public Native_Reload(iPluginId, iArgc) { + new pItem = get_param(1); + Reload(pItem); +} + +public Native_PrimaryAttack(iPluginId, iArgc) { + new pItem = get_param(1); + PrimaryAttack(pItem); +} +public Native_SecondaryAttack(iPluginId, iArgc) { + new pItem = get_param(1); + SecondaryAttack(pItem); +} + +public Native_GrenadeDetonate(iPluginId, iArgc) { + new pGrenade = get_param(1); + new Float:flRadius = get_param_f(2); + new Float:flMagnitude = get_param_f(3); + GrenadeDetonate(pGrenade, flRadius, flMagnitude); +} + +public Native_GrenadeSmoke(iPluginId, iArgc) { + new pGrenade = get_param(1); + GrenadeSmoke(pGrenade); +} + +public Native_RemovePlayerItem(iPluginId, iArgc) { + new pItem = get_param(1); + RemovePlayerItem(pItem); +} + +// ANCHOR: Forwards + +public client_connect(pPlayer) { + g_bKnifeHolstered[pPlayer] = true; +} + +public client_disconnected(pPlayer) { + SetWeaponPrediction(pPlayer, true); +} + +// ANCHOR: Hook Callbacks + +public OnItemDeploy(this) { + new CW:iHandler = GetHandlerByEntity(this); + if (iHandler == CW_INVALID_HANDLER) { + return HAM_IGNORED; + } + + WeaponDeploy(this); + + return HAM_SUPERCEDE; +} + +public OnItemHolster(this) { + new pPlayer = GetPlayer(this); + g_bKnifeHolstered[pPlayer] = IsWeaponKnife(this); + + new CW:iHandler = GetHandlerByEntity(this); + if (iHandler == CW_INVALID_HANDLER) { + return HAM_IGNORED; + } + + WeaponHolster(this); + + return HAM_SUPERCEDE; +} + +public OnItemPostFrame(this) { + new CW:iHandler = GetHandlerByEntity(this); + if (iHandler == CW_INVALID_HANDLER) { + return HAM_IGNORED; + } + + ItemPostFrame(this); + + return HAM_SUPERCEDE; +} + +public OnWeaponPrimaryAttack(this) { + new CW:iHandler = GetHandlerByEntity(this); + if (iHandler == CW_INVALID_HANDLER) { + return HAM_IGNORED; + } + + g_bSupercede = GetHamReturnStatus() >= HAM_SUPERCEDE; + + return HAM_SUPERCEDE; +} + +public OnWeaponSecondaryAttack(this) { + new CW:iHandler = GetHandlerByEntity(this); + if (iHandler == CW_INVALID_HANDLER) { + return HAM_IGNORED; + } + + g_bSupercede = GetHamReturnStatus() >= HAM_SUPERCEDE; + + return HAM_SUPERCEDE; +} + +public OnWeaponReload(this) { + new CW:iHandler = GetHandlerByEntity(this); + if (iHandler == CW_INVALID_HANDLER) { + return HAM_IGNORED; + } + + g_bSupercede = GetHamReturnStatus() >= HAM_SUPERCEDE; + + return HAM_SUPERCEDE; +} + +public OnWeaponIdle(this) { + new CW:iHandler = GetHandlerByEntity(this); + if (iHandler == CW_INVALID_HANDLER) { + return HAM_IGNORED; + } + + g_bSupercede = GetHamReturnStatus() >= HAM_SUPERCEDE; + + return HAM_SUPERCEDE; +} + +public OnUpdateClientData_Post(pPlayer, iSendWeapons, pCdHandle) { + if (!is_user_alive(pPlayer)) { + return FMRES_IGNORED; + } + + new pItem = get_member(pPlayer, m_pActiveItem); + if (pItem == -1) { + return FMRES_IGNORED; + } + + new CW:iHandler = GetHandlerByEntity(pItem); + if (iHandler == CW_INVALID_HANDLER) { + return FMRES_IGNORED; + } + + set_cd(pCdHandle, CD_flNextAttack, get_gametime() + 0.001); // block default animation + + return FMRES_HANDLED; +} + +public OnItemSlot(this) { + new CW:iHandler = GetHandlerByEntity(this); + if (iHandler == CW_INVALID_HANDLER) { + return HAM_IGNORED; + } + + new iSlot = GetData(iHandler, CW_Data_SlotId); + SetHamReturnInteger(iSlot + 1); + + return HAM_SUPERCEDE; +} + +public OnCSItemGetMaxSpeed(this) { + new CW:iHandler = GetHandlerByEntity(this); + if (iHandler == CW_INVALID_HANDLER) { + return HAM_IGNORED; + } + + new Float:flMaxSpeed = ExecuteBindedFunction(CWB_GetMaxSpeed, this); + if (_:flMaxSpeed != PLUGIN_CONTINUE) { + SetHamReturnFloat(flMaxSpeed); + return HAM_OVERRIDE; + } + + return HAM_IGNORED; +} + +public OnItemAddToPlayer_Post(this, pPlayer) { + new pPlayer = GetPlayer(this); + if (!ExecuteHam(Ham_IsPlayer, pPlayer)) { + return HAM_IGNORED; + } + + new CW:iHandler = GetHandlerByEntity(this); + if (iHandler == CW_INVALID_HANDLER) { + new iWeaponId = get_member(this, m_iId); + ResetWeaponList(pPlayer, iWeaponId); + } else { + set_member(this, m_Weapon_iPrimaryAmmoType, GetData(iHandler, CW_Data_PrimaryAmmoType)); + UpdateWeaponList(pPlayer, iHandler); + } + + return HAM_HANDLED; +} + +public OnSpawn_Post(this) { + new CW:iHandler = GetHandlerByEntity(this); + if (iHandler == CW_INVALID_HANDLER) { + return HAM_IGNORED; + } + + ExecuteBindedFunction(CWB_Spawn, this); + + return HAM_IGNORED; +} + +public OnWeaponboxSpawn(this) { + g_pNewWeaponboxEnt = this; +} + +public OnPlayerPreThink_Post(pPlayer) { + if (get_gametime() < g_flNextPredictionUpdate[pPlayer]) { + return HAM_IGNORED; + } + + new iObsMode = pev(pPlayer, pev_iuser1); + new pObsTarget = pev(pPlayer, pev_iuser2); + + new pActiveItem = iObsMode == OBS_IN_EYE + ? IS_PLAYER(pObsTarget) ? get_member(pObsTarget, m_pActiveItem) : -1 + : get_member(pPlayer, m_pActiveItem); + + if (pActiveItem == -1) { + SetWeaponPrediction(pPlayer, false); + return HAM_IGNORED; + } + + new CW:iHandler = GetHandlerByEntity(pActiveItem); + SetWeaponPrediction(pPlayer, iHandler == CW_INVALID_HANDLER); + + return HAM_HANDLED; +} + +public OnPlayerTakeDamage(pPlayer, pInflictor, pAttacker) { + if (pAttacker && ExecuteHam(Ham_IsPlayer, pAttacker) && pInflictor == pAttacker) { + g_pKillerItem = get_member(pAttacker, m_pActiveItem); + } else { + g_pKillerItem = pInflictor; + } +} + +public OnPlayerTakeDamage_Post() { + g_pKillerItem = -1; +} + +public OnMessage_DeathMsg(iMsgId, iDest, pPlayer) { + if (g_pKillerItem == -1) { + return PLUGIN_CONTINUE; + } + + new pKiller = get_msg_arg_int(1); + if (!pKiller) { + return PLUGIN_CONTINUE; + } + + if (!ExecuteHam(Ham_IsPlayer, pKiller)) { + return PLUGIN_CONTINUE; + } + + if (!is_user_alive(pKiller)) { + return PLUGIN_CONTINUE; + } + + new CW:iHandler = GetHandlerByEntity(g_pKillerItem); + if (iHandler == CW_INVALID_HANDLER) { + return PLUGIN_CONTINUE; + } + + static szIcon[64]; + GetStringData(iHandler, CW_Data_Icon, szIcon, charsmax(szIcon)); + + if (szIcon[0] == '^0') { + GetStringData(iHandler, CW_Data_Name, szIcon, charsmax(szIcon)); + } + + set_msg_arg_string(4, szIcon); + + return PLUGIN_CONTINUE; +} + +public OnSetModel_Post(this, const szModel[]) { + if (!pev_valid(this)) { + return FMRES_IGNORED; + } + + if (g_pNewWeaponboxEnt == -1) { + return FMRES_IGNORED; + } + + if (this != g_pNewWeaponboxEnt) { + return FMRES_IGNORED; + } + + static szClassname[32]; + pev(this, pev_classname, szClassname, charsmax(szClassname)); + + if (!equal(szClassname, "weaponbox")) { + g_pNewWeaponboxEnt = -1; + return FMRES_IGNORED; + } + + new pItem = FindWeaponBoxSingleItem(this); + if (pItem == -1) { + return FMRES_IGNORED; + } + + new CW:iHandler = GetHandlerByEntity(pItem); + if (iHandler == CW_INVALID_HANDLER) { + return FMRES_IGNORED; + } + + ExecuteBindedFunction(CWB_WeaponBoxModelUpdate, pItem, this); + g_pNewWeaponboxEnt = -1; + + if (!g_bPrecache) { + if (!ExecuteHamB(Ham_CS_Item_CanDrop, pItem)) { + set_pev(this, pev_flags, pev(this, pev_flags) | FL_KILLME); + dllfunc(DLLFunc_Think, this); + } + } + + return FMRES_HANDLED; +} + +public OnDecalIndex_Post() { + if (!g_bPrecache) { + return; + } + + ArrayPushCell(g_irgDecals, get_orig_retval()); +} + +public OnWeaponClCmd(pPlayer) { + static szName[64]; + read_argv(0, szName, charsmax(szName)); + + new CW:iHandler; + TrieGetCell(g_rgWeaponsMap, szName, iHandler); + + new iWeaponId = GetData(iHandler, CW_Data_Id); + + static szBaseName[32]; + get_weaponname(iWeaponId, szBaseName, charsmax(szBaseName)); + client_cmd(pPlayer, szBaseName); + + return PLUGIN_HANDLED; +} + +public OnCanDrop(this) { + new CW:iHandler = GetHandlerByEntity(this); + if (iHandler == CW_INVALID_HANDLER) { + return HAM_IGNORED; + } + + if (GetHamReturnStatus() >= HAM_OVERRIDE) { + return GetHamReturnStatus(); + } + + SetHamReturnInteger( + ExecuteBindedFunction(CWB_CanDrop, this) == PLUGIN_CONTINUE ? 1 : 0 + ); + + return HAM_OVERRIDE; +} + +public OnMessage_WeaponList(iMsgId, iMsgDest, pPlayer) { + new iWeaponId = get_msg_arg_int(8); + + if (g_weaponListDefaults[iWeaponId][WL_WeaponId] == iWeaponId) { + return PLUGIN_CONTINUE; // already initialized + } + + get_msg_arg_string(1, g_weaponListDefaults[iWeaponId][WL_WeaponName], 31); + g_weaponListDefaults[iWeaponId][WL_PrimaryAmmoType] = get_msg_arg_int(2); + g_weaponListDefaults[iWeaponId][WL_PrimaryAmmoMaxAmount] = get_msg_arg_int(3); + g_weaponListDefaults[iWeaponId][WL_SecondaryAmmoType] = get_msg_arg_int(4); + g_weaponListDefaults[iWeaponId][WL_SecondaryAmmoMaxAmount] = get_msg_arg_int(5); + g_weaponListDefaults[iWeaponId][WL_SlotId] = get_msg_arg_int(6); + g_weaponListDefaults[iWeaponId][WL_NumberInSlot] = get_msg_arg_int(7); + g_weaponListDefaults[iWeaponId][WL_WeaponId] = iWeaponId; + g_weaponListDefaults[iWeaponId][WL_Flags] = get_msg_arg_int(9); + + return PLUGIN_CONTINUE; +} + +// ANCHOR: Weapon Entity Methods + +CompleteReload(this) { + new CW:iHandler = GetHandlerByEntity(this); + new CW_Flags:iFlags = GetData(iHandler, CW_Data_Flags); + + if (~iFlags & CWF_CustomReload) { + new pPlayer = GetPlayer(this); + new iMaxClip = GetData(iHandler, CW_Data_ClipSize); + new iClip = get_member(this, m_Weapon_iClip); + new iPrimaryAmmoIndex = get_member(this, m_Weapon_iPrimaryAmmoType); + new iBpAmmo = get_member(pPlayer, m_rgAmmo, iPrimaryAmmoIndex); + new iSize = min(iMaxClip - iClip, iBpAmmo); + + set_member(this, m_Weapon_iClip, iClip + iSize); + set_member(pPlayer, m_rgAmmo, iBpAmmo - iSize, iPrimaryAmmoIndex); + } + + set_member(this, m_Weapon_fInReload, 0); + + ExecuteBindedFunction(CWB_DefaultReloadEnd, this); +} + +ItemPostFrame(this) { + new CW:iHandler = GetHandlerByEntity(this); + new pPlayer = GetPlayer(this); + new flInReload = get_member(this, m_Weapon_fInReload); + new iMaxClip = GetData(iHandler, CW_Data_ClipSize); + new iWeaponFlags = GetData(iHandler, CW_Data_WeaponFlags); + new Float:flNextAttack = get_member(pPlayer, m_flNextAttack); + new button = pev(pPlayer, pev_button); + new iPrimaryAmmoIndex = get_member(this, m_Weapon_iPrimaryAmmoType); + new iSecondaryAmmoIndex = 0; + new Float:flNextPrimaryAttack = get_member(this, m_Weapon_flNextPrimaryAttack); + new Float:flNextSecondaryAttack = get_member(this, m_Weapon_flNextSecondaryAttack); + new iPrimaryAmmoAmount = get_member(pPlayer, m_rgAmmo, iPrimaryAmmoIndex); + new iSecondaryAmmoAmount = get_member(pPlayer, m_rgAmmo, iSecondaryAmmoIndex); + + new Float:flReloadEndTime = get_member(this, m_Weapon_flNextReload); + if (flReloadEndTime && flReloadEndTime < get_gametime()) { + set_member(this, m_Weapon_flNextReload, 0.0); + ExecuteBindedFunction(CWB_Pump, this); + } + + if (flInReload && flNextAttack <= 0.0) { + CompleteReload(this); + } + + if ((button & IN_ATTACK2) && flNextSecondaryAttack <= 0) { + if (iSecondaryAmmoIndex > 0 && !iSecondaryAmmoAmount) { + set_member(this, m_Weapon_fFireOnEmpty, 1); + } + + SecondaryAttack(this); + } else if ((button & IN_ATTACK) && flNextPrimaryAttack <= 0) { + if ((!get_member(this, m_Weapon_iClip) && iPrimaryAmmoIndex > 0) || (iMaxClip == -1 && !iPrimaryAmmoAmount)) { + set_member(this, m_Weapon_fFireOnEmpty, 1); + } + + PrimaryAttack(this); + } else if ((button & IN_RELOAD) && iMaxClip != WEAPON_NOCLIP && !flInReload) { + Reload(this); + } else if (!(button & (IN_ATTACK|IN_ATTACK2))) { + set_member(this, m_Weapon_fFireOnEmpty, 0); + + if (!IsUseable(this) && flNextPrimaryAttack < 0.0) { + // if (!(iWeaponFlags & ITEM_FLAG_NOAUTOSWITCHEMPTY) && g_pGameRules->GetNextBestWeapon(m_pPlayer, this)) { + // set_member(this, m_Weapon_flNextPrimaryAttack, 0.3); + // return; + // } + } else { + if (!get_member(this, m_Weapon_iClip) && !(iWeaponFlags & ITEM_FLAG_NOAUTORELOAD) && flNextPrimaryAttack < 0.0) { + Reload(this); + return; + } + } + + set_member(this, m_Weapon_iShotsFired, 0); + WeaponIdle(this); + return; + } + + if (ShouldWeaponIdle(this)) { + WeaponIdle(this); + } +} + +SecondaryAttack(this) { + if (get_member_game(m_bFreezePeriod)) { + return; + } + + ExecuteHamB(Ham_Weapon_SecondaryAttack, this); + + if (g_bSupercede) { + return; + } + + if (ExecuteBindedFunction(CWB_SecondaryAttack, this) > PLUGIN_CONTINUE) { + return; + } +} + +PrimaryAttack(this) { + if (get_member_game(m_bFreezePeriod)) { + return; + } + + ExecuteHamB(Ham_Weapon_PrimaryAttack, this); + + if (g_bSupercede) { + return; + } + + if (ExecuteBindedFunction(CWB_PrimaryAttack, this) > PLUGIN_CONTINUE) { + return; + } +} + +Reload(this) { + ExecuteHamB(Ham_Weapon_Reload, this); + + if (g_bSupercede) { + return; + } + + if (ExecuteBindedFunction(CWB_Reload, this) > PLUGIN_CONTINUE) { + return; + } +} + +WeaponIdle(this) { + if (get_member(this, m_Weapon_flTimeWeaponIdle) > 0.0) { + return; + } + + ExecuteHamB(Ham_Weapon_WeaponIdle, this); + + if (g_bSupercede) { + return; + } + + if (ExecuteBindedFunction(CWB_Idle, this) > PLUGIN_CONTINUE) { + return; + } +} + +WeaponHolster(this) { + new pPlayer = GetPlayer(this); + + SetWeaponPrediction(pPlayer, true); + set_member(this, m_Weapon_fInReload, 0); + set_member(this, m_Weapon_fInSpecialReload, 0); + set_member(this, m_Weapon_flNextReload, 0.0); + + if (ExecuteBindedFunction(CWB_Holster, this) > PLUGIN_CONTINUE) { + return; + } +} + +WeaponDeploy(this) { + if (ExecuteBindedFunction(CWB_Deploy, this) > PLUGIN_CONTINUE) { + return; + } + + new pPlayer = GetPlayer(this); + + if (g_bKnifeHolstered[pPlayer]) { + g_flNextPredictionUpdate[pPlayer] = get_gametime() + 1.0; + } else if (get_member(this, m_iId) == CSW_KNIFE) { + SetWeaponPrediction(pPlayer, false); + } + + // SetThink(this, "DisablePrediction"); + // set_pev(this, pev_nextthink, get_gametime() + 0.1); +} + +bool:ShouldWeaponIdle(this) { + #pragma unused this + return false; +} + +bool:IsUseable(this) { + new CW:iHandler = GetHandlerByEntity(this); + new pPlayer = GetPlayer(this); + new iPrimaryAmmoIndex = get_member(this, m_Weapon_iPrimaryAmmoType); + new iMaxAmmo1 = GetData(iHandler, CW_Data_PrimaryAmmoMaxAmount); + new iClip = get_member(this, m_Weapon_iClip); + new iBpAmmo = get_member(pPlayer, m_rgAmmo, iPrimaryAmmoIndex); + + if (iClip <= 0) { + if (iBpAmmo <= 0 && iMaxAmmo1 != -1) { + return false; + } + } + + return true; +} + +PlayWeaponAnim(this, iSequence, Float:flDuration) { + SendWeaponAnim(this, iSequence); + set_member(this, m_Weapon_flTimeWeaponIdle, flDuration); +} + +SendWeaponAnim(this, iAnim) { + new pPlayer = GetPlayer(this); + + SendPlayerWeaponAnim(pPlayer, this, iAnim); + + for (new pSpectator = 1; pSpectator <= MaxClients; pSpectator++) { + if (pSpectator == pPlayer) { + continue; + } + + if (!is_user_connected(pSpectator)) { + continue; + } + + if (pev(pSpectator, pev_iuser1) != OBS_IN_EYE) { + continue; + } + + if (pev(pSpectator, pev_iuser2) != pPlayer) { + continue; + } + + SendPlayerWeaponAnim(pSpectator, this, iAnim); + } +} + +SendPlayerWeaponAnim(pPlayer, pWeapon, iAnim) { + new iBody = pev(pWeapon, pev_body); + + set_pev(pPlayer, pev_weaponanim, iAnim); + + if (!is_user_bot(pPlayer)) { + emessage_begin(MSG_ONE, SVC_WEAPONANIM, _, pPlayer); + ewrite_byte(iAnim); + ewrite_byte(iBody); + emessage_end(); + } +} + +GetPlayer(this) { + return get_member(this, m_pPlayer); +} + +FireBulletsPlayer(this, cShots, Float:vecSrc[3], Float:vecDirShooting[3], Float:vecSpread[3], Float:flDistance, Float:flDamage, Float:flRangeModifier, pAttacker, Float:vecOut[3]) { + new CW:iHandler = GetHandlerByEntity(this); + if (iHandler == CW_INVALID_HANDLER) { + return; + } + + new pPlayer = GetPlayer(this); + new shared_rand = pPlayer > 0 ? get_member(pPlayer, random_seed) : 0; + new CW_Flags:iFlags = GetData(iHandler, CW_Data_Flags); + + new pTr = create_tr2(); + + static Float:vecRight[3]; + get_global_vector(GL_v_right, vecRight); + + static Float:vecUp[3]; + get_global_vector(GL_v_up, vecUp); + + static Float:vecMultiplier[3]; + + if (!pAttacker) { + pAttacker = this; // the default attacker is ourselves + } + + // ClearMultiDamage(); + // gMultiDamage.type = DMG_BULLET | DMG_NEVERGIB; + + for (new iShot = 1; iShot <= cShots; iShot++) { + //Use player's random seed. + // get circular gaussian spread + vecMultiplier[0] = SharedRandomFloat( shared_rand + iShot, -0.5, 0.5 ) + SharedRandomFloat( shared_rand + ( 1 + iShot ) , -0.5, 0.5 ); + vecMultiplier[1] = SharedRandomFloat( shared_rand + ( 2 + iShot ), -0.5, 0.5 ) + SharedRandomFloat( shared_rand + ( 3 + iShot ), -0.5, 0.5 ); + vecMultiplier[2] = vecMultiplier[0] * vecMultiplier[0] + vecMultiplier[1] * vecMultiplier[1]; + + static Float:vecDir[3]; + for (new i = 0; i < 3; ++i) { + vecDir[i] = vecDirShooting[i] + (vecMultiplier[0] * vecSpread[0] * vecRight[i]) + (vecMultiplier[1] * vecSpread[1] * vecUp[i]); + } + + static Float:vecEnd[3]; + for (new i = 0; i < 3; ++i) { + vecEnd[i] = vecSrc[i] + (vecDir[i] * flDistance); + } + + engfunc(EngFunc_TraceLine, vecSrc, vecEnd, DONT_IGNORE_MONSTERS, this, pTr); + + new Float:flFraction; + get_tr2(pTr, TR_flFraction, flFraction); + + // do damage, paint decals + if (flFraction != 1.0) { + new pHit = get_tr2(pTr, TR_pHit); + + if (pHit < 0) { + pHit = 0; + } + + new Float:flCurrentDistance = flDistance * flFraction; + new Float:flCurrentDamage = flDamage * floatpower(flRangeModifier, flCurrentDistance / 500.0); + + rg_multidmg_clear(); + ExecuteHamB(Ham_TraceAttack, pHit, pAttacker, flCurrentDamage, vecDir, pTr, DMG_BULLET | DMG_NEVERGIB); + rg_multidmg_apply(this, pAttacker); + + // TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + // DecalGunshot( &tr, iBulletType ); + + // new iDecalIndex = ExecuteHam(Ham_DamageDecal, pHit, DMG_BULLET); + // DecalTrace2(pTr, iDecalIndex); + + if (!ExecuteHam(Ham_IsPlayer, pHit)) { + if (~iFlags & CWF_NoBulletSmoke) { + BulletSmoke(pTr); + } + + if (~iFlags & CWF_NoBulletDecal) { + new iDecalIndex = GetDecalIndex(pHit); + if (iDecalIndex >= 0) { + MakeDecal(pTr, pHit, iDecalIndex); + } + } + } + } + + // make bullet trails + static Float:vecEndPos[3]; + get_tr2(pTr, TR_vecEndPos, vecEndPos); + + BubbleTrail(vecSrc, vecEndPos, floatround((flDistance * flFraction) / 64.0)); + } + + vecOut[0] = vecMultiplier[0] * vecSpread[0]; + vecOut[1] = vecMultiplier[1] * vecSpread[1]; + vecOut[2] = 0.0; + + free_tr2(pTr); +} + +GrenadeDetonate(this, Float:flRadius, Float:flMagnitude) { + static Float:vecStart[3]; + pev(this, pev_origin, vecStart); + vecStart[2] += 8.0; + + static Float:vecEnd[3]; + xs_vec_copy(vecStart, vecEnd); + vecEnd[2] -= 40.0; + + new pTr = create_tr2(); + engfunc(EngFunc_TraceLine, vecStart, vecEnd, IGNORE_MONSTERS, this, pTr); + GrenadeExplode(this, pTr, DMG_GRENADE | DMG_ALWAYSGIB, flRadius, flMagnitude); + free_tr2(pTr); +} + +GrenadeExplode(this, pTr, iDamageBits, Float:flRadius, Float:flMagnitude) { + new Float:flDamage; + pev(this, pev_dmg, flDamage); + + set_pev(this, pev_model, NULL_STRING); + set_pev(this, pev_solid, SOLID_NOT); + set_pev(this, pev_takedamage, DAMAGE_NO); + + new Float:flFraction; + get_tr2(pTr, TR_Fraction, flFraction); + + static Float:vecPlaneNormal[3]; + get_tr2(pTr, TR_vecPlaneNormal, vecPlaneNormal); + + static Float:vecOrigin[3]; + pev(this, pev_origin, vecOrigin); + + if (flFraction != 1.0) { + get_tr2(pTr, TR_vecEndPos, vecOrigin); + + for (new i = 0; i < 3; ++i) { + vecOrigin[i] += (vecPlaneNormal[i] * (flMagnitude ? flMagnitude : flDamage - 24.0) * 0.6); + } + + set_pev(this, pev_origin, vecOrigin); + } + + GrenadeExplosion(vecOrigin, flDamage); + + // CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, NORMAL_EXPLOSION_VOLUME, 3.0 ); + new iOwner = pev(this, pev_owner); + set_pev(this, pev_owner, 0); + + _RadiusDamage(vecOrigin, this, iOwner, flDamage, flRadius ? flRadius : flDamage * 3.5, CLASS_NONE, iDamageBits); + + ExplosionDecalTrace(pTr); + DebrisSound(this); + + set_pev(this, pev_effects, pev(this, pev_effects) | EF_NODRAW); + + // SetThink( &CGrenade::Smoke ); + // GrenadeSmoke(vecOrigin, flDamage); + + set_pev(this, pev_velocity, NULL_VECTOR); + set_pev(this, pev_nextthink, get_gametime() + 0.1); + + if (PointContents(vecOrigin) != CONTENTS_WATER) { + new iSparkCount = random(4); + for (new i = 0; i < iSparkCount; ++i) { + SparkShower(vecOrigin, vecPlaneNormal, 0); + } + } +} + +bool:IsWeaponKnife(pWeapon) { + if (GetHandlerByEntity(pWeapon) != CW_INVALID_HANDLER) { + return false; + } + + if (get_member(pWeapon, m_iId) != CSW_KNIFE) { + return false; + } + + return true; +} + +// ANCHOR: Weapon Callbacks + +public Smack(this) { + new CW:iHandler = GetHandlerByEntity(this); + new CW_Flags:iFlags = GetData(iHandler, CW_Data_Flags); + + new pTr = pev(this, pev_iuser1); + new pHit = get_tr2(pTr, TR_pHit); + if (pHit < 0) { + pHit = 0; + } + + if (~iFlags & CWF_NoBulletDecal) { + new iDecalIndex = GetDecalIndex(pHit); + if (iDecalIndex >= 0) { + MakeDecal(pTr, pHit, iDecalIndex, false); + } + } + + free_tr2(pTr); + + SetThink(this, NULL_STRING); +} + +// public DisablePrediction(this) { +// new pPlayer = GetPlayer(this); +// SetWeaponPrediction(pPlayer, false); +// SetThink(this, NULL_STRING); +// } + +// ANCHOR: Weapon Entity Default Methods + +bool:DefaultReload(this, iAnim, Float:flDelay) { + new CW:iHandler = GetHandlerByEntity(this); + new pPlayer = GetPlayer(this); + new iPrimaryAmmoIndex = get_member(this, m_Weapon_iPrimaryAmmoType); + new iPrimaryAmmoAmount = get_member(pPlayer, m_rgAmmo, iPrimaryAmmoIndex); + + if (iPrimaryAmmoAmount <= 0) { + return false; + } + + new iClip = get_member(this, m_Weapon_iClip); + new iClipSize = GetData(iHandler, CW_Data_ClipSize); + + new size = min(iClipSize - iClip, iPrimaryAmmoAmount); + if (size == 0) { + return false; + } + + if (get_member(this, m_Weapon_fInReload)) { + return false; + } + + set_member(pPlayer, m_flNextAttack, flDelay); + set_member(this, m_Weapon_fInReload, 1); + + PlayWeaponAnim(this, iAnim, 3.0); + rg_set_animation(pPlayer, PLAYER_RELOAD); + + return true; +} + +bool:DefaultShotgunReload(this, iStartAnim, iEndAnim, Float:flDelay, Float:flDuration) { + new pPlayer = GetPlayer(this); + new iClip = get_member(this, m_Weapon_iClip); + new iPrimaryAmmoType = get_member(this, m_Weapon_iPrimaryAmmoType); + new CW:iHandler = GetHandlerByEntity(this); + new iClipSize = GetData(iHandler, CW_Data_ClipSize); + + if (get_member(pPlayer, m_rgAmmo, iPrimaryAmmoType) <= 0 || iClip == iClipSize) { + return false; + } + + // don't reload until recoil is done + new Float:flNextPrimaryAttack = get_member(this, m_Weapon_flNextPrimaryAttack); + new flInSpecialReload = get_member(this, m_Weapon_fInSpecialReload); + if (flNextPrimaryAttack > 0.0) { + return false; + } + + new Float:flTimeWeaponIdle = get_member(this, m_Weapon_flTimeWeaponIdle); + // check to see if we're ready to reload + if (flInSpecialReload == 0) { + rg_set_animation(pPlayer, PLAYER_RELOAD); + PlayWeaponAnim(this, iStartAnim, flDelay); + + set_member(this, m_Weapon_fInSpecialReload, 1); + set_member(pPlayer, m_flNextAttack, flDelay); + set_member(this, m_Weapon_flNextPrimaryAttack, 1.0); + set_member(this, m_Weapon_flNextSecondaryAttack, 1.0); + } else if (flInSpecialReload == 1) { + if (flTimeWeaponIdle > 0.0) { + return false; + } + + set_member(this, m_Weapon_fInSpecialReload, 2); + + // if (RANDOM_LONG(0,1)) + // EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/reload1.wav", 1, ATTN_NORM, 0, 85 + RANDOM_LONG(0,0x1f)); + // else + // EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/reload3.wav", 1, ATTN_NORM, 0, 85 + RANDOM_LONG(0,0x1f)); + + PlayWeaponAnim(this, iEndAnim, flDuration); + } else { + // Add them to the clip + set_member(this, m_Weapon_iClip, ++iClip); + set_member(this, m_Weapon_fInSpecialReload, 1); + set_member(pPlayer, m_rgAmmo, get_member(pPlayer, m_rgAmmo, iPrimaryAmmoType) - 1, iPrimaryAmmoType); + } + + return true; +} + +bool:DefaultShotgunIdle(this, iAnim, iReloadEndAnim, Float:flDuration, Float:flReloadEndDuration, const szPumpSound[]) { + new CW:iHandler = GetHandlerByEntity(this); + if (iHandler == CW_INVALID_HANDLER) { + return false; + } + + new Float:flTimeWeaponIdle = get_member(this, m_Weapon_flTimeWeaponIdle); + if (flTimeWeaponIdle < 0.0) { + new pPlayer = get_member(this, m_pPlayer); + new iPrimaryAmmoType = get_member(this, m_Weapon_iPrimaryAmmoType); + new iPrimaryAmmoAmount = get_member(pPlayer, m_rgAmmo, iPrimaryAmmoType); + new flInSpecialReload = get_member(this, m_Weapon_fInSpecialReload); + new iClip = get_member(this, m_Weapon_iClip); + + if (!iClip && flInSpecialReload == 0 && iPrimaryAmmoAmount) { + Reload(this); + } else if (flInSpecialReload != 0) { + new iClipSize = GetData(iHandler, CW_Data_ClipSize); + if (iClip < iClipSize && iPrimaryAmmoAmount) { + Reload(this); + } else { + set_member(this, m_Weapon_fInSpecialReload, 0); + emit_sound(pPlayer, CHAN_ITEM, szPumpSound, VOL_NORM, ATTN_NORM, 0, PITCH_NORM); + PlayWeaponAnim(this, iReloadEndAnim, flReloadEndDuration); + } + } else { + PlayWeaponAnim(this, iAnim, flDuration); + } + } + + return true; +} + +bool:DefaultDeploy(this, const szViewModel[], const szWeaponModel[], iAnim, const szAnimExt[]) { + // if (!CanDeploy(this)) { + // return false; + // } + + // new CW:iHandler = GetHandlerByEntity(this); + new pPlayer = GetPlayer(this); + set_pev(pPlayer, pev_viewmodel2, szViewModel); + set_pev(pPlayer, pev_weaponmodel2, szWeaponModel); + + // strcpy( m_pPlayer->m_szAnimExtention, szAnimExt ); + SendWeaponAnim(this, iAnim); + + if (szAnimExt[0] != '^0') { + set_member(pPlayer, m_szAnimExtention, szAnimExt); + } + + set_member(this, m_Weapon_iShotsFired, 0); + set_member(this, m_Weapon_flTimeWeaponIdle, 1.0); + set_member(this, m_Weapon_flLastFireTime, 0.0); + set_member(this, m_Weapon_flDecreaseShotsFired, get_gametime()); + + set_member(pPlayer, m_flNextAttack, 0.5); + set_member(pPlayer, m_iFOV, DEFAULT_FOV); + set_member(pPlayer, m_iLastZoom, DEFAULT_FOV); + set_member(pPlayer, m_bResumeZoom, 0); + set_pev(pPlayer, pev_fov, float(DEFAULT_FOV)); + + return true; +} + +bool:DefaultShot(this, Float:flDamage, Float:flRangeModifier, Float:flRate, Float:flSpread[3], iShots, Float:flDistance) { + new iClip = get_member(this, m_Weapon_iClip); + if (iClip <= 0) { + return false; + } + + new pPlayer = GetPlayer(this); + + static Float:vecDirShooting[3]; + MakeAimDir(pPlayer, 1.0, vecDirShooting); + + static Float:vecSrc[3]; + ExecuteHam(Ham_Player_GetGunPosition, pPlayer, vecSrc); + + static Float:vecOut[3]; + FireBulletsPlayer(this, iShots, vecSrc, vecDirShooting, flSpread, flDistance, flDamage, flRangeModifier, pPlayer, vecOut); + + set_member(this, m_Weapon_iClip, --iClip); + + set_member(this, m_Weapon_flNextPrimaryAttack, flRate); + set_member(this, m_Weapon_flNextSecondaryAttack, flRate); + + new iShotsFired = get_member(this, m_Weapon_iShotsFired); + set_member(this, m_Weapon_iShotsFired, ++iShotsFired); + + rg_set_animation(pPlayer, PLAYER_ATTACK1); + + return true; +} + +bool:DefaultShotgunShot(this, Float:flDamage, Float:flRangeModifier, Float:flRate, Float:flPumpDelay, Float:flSpread[3], iShots, Float:flDistance) { + new iClip = get_member(this, m_Weapon_iClip); + if (iClip <= 0) { + Reload(this); + if (iClip == 0) { + // PlayEmptySound(); + } + + return false; + } + + // m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + // m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + // m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + if (!DefaultShot(this, flDamage, flRangeModifier, flRate, flSpread, iShots, flDistance)) { + return false; + } + + set_member(this, m_Weapon_fInSpecialReload, 0); + + if (iClip != 0) { + set_member(this, m_Weapon_flNextReload, get_gametime() + flPumpDelay); + } + + return true; +} + +DefaultSwing(this, Float:flDamage, Float:flRate, Float:flDistance) { + new CW:iHandler = GetHandlerByEntity(this); + if (iHandler == CW_INVALID_HANDLER) { + return -1; + } + + new pPlayer = GetPlayer(this); + + static Float:vecSrc[3]; + ExecuteHam(Ham_Player_GetGunPosition, pPlayer, vecSrc); + + static Float:vecEnd[3]; + MakeAimDir(pPlayer, flDistance, vecEnd); + xs_vec_add(vecSrc, vecEnd, vecEnd); + + new pTr = create_tr2(); + engfunc(EngFunc_TraceLine, vecSrc, vecEnd, DONT_IGNORE_MONSTERS, this, pTr); + + new Float:flFraction; + get_tr2(pTr, TR_flFraction, flFraction); + + if (flFraction >= 1.0) { + engfunc(EngFunc_TraceHull, vecSrc, vecEnd, DONT_IGNORE_MONSTERS, HULL_HEAD, this, pTr); + get_tr2(pTr, TR_flFraction, flFraction); + + if (flFraction < 1.0) { + // Calculate the point of interANCHOR of the line (or hull) and the object we hit + // This is and approximation of the "best" interANCHOR + new pHit = get_tr2(pTr, TR_pHit); + if (pHit == -1 || ExecuteHamB(Ham_IsBSPModel, pHit)) { + FindHullIntersection(vecSrc, pTr, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, this); + } + + get_tr2(pTr, TR_vecEndPos, vecEnd); // This is the point on the actual surface (the hull could have hit space) + get_tr2(pTr, TR_flFraction, flFraction); + } + } + + new iShotsFired = get_member(this, m_Weapon_iShotsFired); + set_member(this, m_Weapon_iShotsFired, iShotsFired + 1); + + set_member(this, m_Weapon_flNextPrimaryAttack, flRate); + + rg_set_animation(pPlayer, PLAYER_ATTACK1); + + if (flFraction >= 1.0) { + free_tr2(pTr); + return -1; + } + + new pHit = get_tr2(pTr, TR_pHit); + if (pHit < 0) { + set_tr2(pTr, TR_pHit, 0); + pHit = 0; + } + + + // if (get_member(this, m_Weapon_flNextPrimaryAttack) + 1.0 < 0.0) { + // first swing does full damage + static Float:vecDir[3]; + xs_vec_sub(vecSrc, vecEnd, vecDir); + xs_vec_normalize(vecDir, vecDir); + + rg_multidmg_clear(); + ExecuteHamB(Ham_TraceAttack, pHit, pPlayer, flDamage, vecDir, pTr, DMG_CLUB); + rg_multidmg_apply(pPlayer, pPlayer); + // } + + + set_pev(this, pev_iuser1, pTr); + SetThink(this, "Smack"); + set_pev(this, pev_nextthink, get_gametime() + (flRate * 0.5)); + + return pHit; +} + +// ANCHOR: Weapon Methods + +CW:RegisterWeapon(iPluginId, const szName[], iWeaponId, iClipSize, iPrimaryAmmoType, iPrimaryAmmoMaxAmount, iSecondaryAmmoType, iSecondaryAmmoMaxAmount, iSlotId, iPosition, iWeaponFlags, const szIcon[], CW_Flags:iFlags) { + new CW:iHandler = CreateWeaponData(szName); + SetData(iHandler, CW_Data_PluginId, iPluginId); + SetStringData(iHandler, CW_Data_Name, szName); + SetData(iHandler, CW_Data_Id, iWeaponId); + SetData(iHandler, CW_Data_ClipSize, iClipSize); + SetData(iHandler, CW_Data_PrimaryAmmoType, iPrimaryAmmoType); + SetData(iHandler, CW_Data_PrimaryAmmoMaxAmount, iPrimaryAmmoMaxAmount); + SetData(iHandler, CW_Data_SecondaryAmmoType, iSecondaryAmmoType); + SetData(iHandler, CW_Data_SecondaryAmmoMaxAmount, iSecondaryAmmoMaxAmount); + SetData(iHandler, CW_Data_SlotId, iSlotId); + SetData(iHandler, CW_Data_Position, iPosition); + SetData(iHandler, CW_Data_WeaponFlags, iWeaponFlags); + SetStringData(iHandler, CW_Data_Icon, szIcon); + SetData(iHandler, CW_Data_Flags, iFlags); + + if (!g_bPrecache && !g_bWeaponHooks[iWeaponId]) { // we are not able to get weapon name in precache state + RegisterWeaponHooks(iWeaponId); + } + + register_clcmd(szName, "OnWeaponClCmd"); + + return iHandler; +} + +CW:GetHandler(const szName[]) { + new CW:iHandler; + if (!TrieGetCell(g_rgWeaponsMap, szName, iHandler)) { + return CW_INVALID_HANDLER; + } + + return iHandler; +} + +CW:GetHandlerByEntity(pEntity) { + new iToken = pev(pEntity, pev_impulse); + + if (iToken >= TOKEN && iToken < TOKEN + g_iWeaponCount) { + return CW:(iToken - TOKEN); + } + + return CW_INVALID_HANDLER; +} + +SpawnWeapon(CW:iHandler) { + new iWeaponId = GetData(iHandler, CW_Data_Id); + + new pEntity = rg_create_entity(g_rgszWeaponNames[iWeaponId], true); + if (!pEntity) { + return 0; + } + + set_pev(pEntity, pev_impulse, TOKEN + _:iHandler); + dllfunc(DLLFunc_Spawn, pEntity); + + new iPrimaryAmmoType = GetData(iHandler, CW_Data_PrimaryAmmoType); + + set_member(pEntity, m_Weapon_iClip, GetData(iHandler, CW_Data_ClipSize)); + set_member(pEntity, m_Weapon_iPrimaryAmmoType, iPrimaryAmmoType); + set_member(pEntity, m_Weapon_iDefaultAmmo, 0); + // set_member(pEntity, m_Weapon_iShell, 0); + // set_member(pEntity, m_Weapon_bDelayFire, true); + // set_member(pEntity, m_Weapon_fFireOnEmpty, true); + + ExecuteBindedFunction(CWB_Spawn, pEntity); + + return pEntity; +} + +SpawnWeaponBox(CW:iHandler) { + new pItem = SpawnWeapon(iHandler); + if (!pItem) { + return 0; + } + + set_pev(pItem, pev_spawnflags, pev(pItem, pev_spawnflags) | SF_NORESPAWN); + set_pev(pItem, pev_effects, EF_NODRAW); + set_pev(pItem, pev_movetype, MOVETYPE_NONE); + set_pev(pItem, pev_solid, SOLID_NOT); + set_pev(pItem, pev_model, 0); + set_pev(pItem, pev_modelindex, 0); + + new pWeaponBox = rg_create_entity("weaponbox", true); + if (!pWeaponBox) { + set_pev(pItem, pev_flags, pev(pItem, pev_flags) | FL_KILLME); + dllfunc(DLLFunc_Think, pItem); + return 0; + } + + dllfunc(DLLFunc_Spawn, pWeaponBox); + set_pev(pItem, pev_owner, pWeaponBox); + + new iSlot = GetData(iHandler, CW_Data_SlotId); + set_member(pWeaponBox, m_WeaponBox_rgpPlayerItems, pItem, iSlot + 1); + + dllfunc(DLLFunc_Spawn, pWeaponBox); + + // engfunc(EngFunc_SetSize, pWeaponBox, {-8.0, -8.0, 0.0}, {8.0, 8.0, 4.0}); + + return pWeaponBox; +} + +// ANCHOR: Player Methods + +GiveWeapon(pPlayer, CW:iHandler) { + new pWeapon = SpawnWeapon(iHandler); + if (!pWeapon) { + return; + } + + if (ExecuteHamB(Ham_AddPlayerItem, pPlayer, pWeapon)) { + ExecuteHamB(Ham_Item_AttachToPlayer, pWeapon, pPlayer); + emit_sound(pPlayer, CHAN_ITEM, "items/gunpickup2.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM); + } + + new CW_Flags:iFlags = GetData(iHandler, CW_Data_Flags); + + if (~iFlags & CWF_NotRefillable) { + new iClipSize = GetData(iHandler, CW_Data_ClipSize); + new iPrimaryAmmoIndex = GetData(iHandler, CW_Data_PrimaryAmmoType); + if (iClipSize == WEAPON_NOCLIP && iPrimaryAmmoIndex != -1) { + set_member(pPlayer, m_rgAmmo, get_member(pPlayer, m_rgAmmo, iPrimaryAmmoIndex) + 1, iPrimaryAmmoIndex); + } + } +} + +bool:HasWeapon(pPlayer, CW:iHandler) { + new iSlot = GetData(iHandler, CW_Data_SlotId); + + new pItem = get_member(pPlayer, m_rgpPlayerItems, iSlot); + while (pItem != -1) { + new pNextItem = get_member(pItem, m_pNext); + + if (CW_GetHandlerByEntity(pItem) == iHandler) { + return true; + } + + pItem = pNextItem; + } + + return false; +} + +UpdateWeaponList(pPlayer, CW:iHandler) { + if (is_user_bot(pPlayer)) { + return; + } + + new iWeaponId = GetData(iHandler, CW_Data_Id); + + static szName[64]; + GetStringData(iHandler, CW_Data_Name, szName, charsmax(szName)); + + new iPrimaryAmmoType = GetData(iHandler, CW_Data_PrimaryAmmoType); + new iPrimaryAmmoMaxCount = GetData(iHandler, CW_Data_PrimaryAmmoMaxAmount); + new iSecondaryAmmoType = GetData(iHandler, CW_Data_SecondaryAmmoType); + new iSecondaryAmmoMaxCount = GetData(iHandler, CW_Data_SecondaryAmmoMaxAmount); + new iSlotId = GetData(iHandler, CW_Data_SlotId); + new iPosition = GetData(iHandler, CW_Data_Position); + new iWeaponFlags = GetData(iHandler, CW_Data_WeaponFlags); + + emessage_begin(MSG_ONE, gmsgWeaponList, _, pPlayer); + ewrite_string(szName); + ewrite_byte(iPrimaryAmmoType); + ewrite_byte(iPrimaryAmmoMaxCount); + ewrite_byte(iSecondaryAmmoType); + ewrite_byte(iSecondaryAmmoMaxCount); + ewrite_byte(iSlotId); + ewrite_byte(iPosition); + ewrite_byte(iWeaponId); + ewrite_byte(iWeaponFlags); + emessage_end(); +} + +ResetWeaponList(pPlayer, iWeaponId) { + if (is_user_bot(pPlayer)) { + return; + } + + message_begin(MSG_ONE, gmsgWeaponList, _, pPlayer); + write_string(g_weaponListDefaults[iWeaponId][WL_WeaponName]); + write_byte(g_weaponListDefaults[iWeaponId][WL_PrimaryAmmoType]); + write_byte(g_weaponListDefaults[iWeaponId][WL_PrimaryAmmoMaxAmount]); + write_byte(g_weaponListDefaults[iWeaponId][WL_SecondaryAmmoType]); + write_byte(g_weaponListDefaults[iWeaponId][WL_SecondaryAmmoMaxAmount]); + write_byte(g_weaponListDefaults[iWeaponId][WL_SlotId]); + write_byte(g_weaponListDefaults[iWeaponId][WL_NumberInSlot]); + write_byte(g_weaponListDefaults[iWeaponId][WL_WeaponId]); + write_byte(g_weaponListDefaults[iWeaponId][WL_Flags]); + message_end(); +} + +SetWeaponPrediction(pPlayer, bool:bValue) { + if (is_user_bot(pPlayer)) { + return; + } + + new pszInfoBuffer = engfunc(EngFunc_GetInfoKeyBuffer, pPlayer); + engfunc(EngFunc_SetClientKeyValue, pPlayer, pszInfoBuffer, "cl_lw", bValue ? "1" : "0"); + + for (new pSpectator = 1; pSpectator <= MaxClients; pSpectator++) { + if (pSpectator == pPlayer) { + continue; + } + + if (!is_user_connected(pSpectator)) { + continue; + } + + if (pev(pSpectator, pev_iuser1) != OBS_IN_EYE) { + continue; + } + + if (pev(pSpectator, pev_iuser2) != pPlayer) { + continue; + } + + SetWeaponPrediction(pSpectator, false); + } +} + +RemovePlayerItem(pItem) { + new pPlayer = GetPlayer(pItem); + + new iWeaponId = get_member(pItem, m_iId); + + if (pItem == get_member(pPlayer, m_pActiveItem)) { + ExecuteHamB(Ham_Weapon_RetireWeapon, pItem); + } + + ExecuteHamB(Ham_RemovePlayerItem, pPlayer, pItem); + ExecuteHamB(Ham_Item_Kill, pItem); + set_pev(pPlayer, pev_weapons, pev(pPlayer, pev_weapons) & ~(1< 1.0) { + vecMidUp[2] = flMinZ + (flDiff / 2.0); + + if (PointContents(vecMidUp) == CONTENTS_WATER) { + flMinZ = vecMidUp[2]; + } else { + flMaxZ = vecMidUp[2]; + } + + flDiff = flMaxZ - flMinZ; + } + + return vecMidUp[2]; +} + +FindHullIntersection(const Float:vecSrc[3], &pTr, const Float:vecMins[3], const Float:vecMaxs[3], pEntity) { + new Float:flDistance = 8192.0; + + static Float:rgvecMinsMaxs[2][3]; + for (new i = 0; i < 3; ++i) { + rgvecMinsMaxs[0][i] = vecMins[i]; + rgvecMinsMaxs[1][i] = vecMaxs[i]; + } + + static Float:vecHullEnd[3]; + get_tr2(pTr, TR_vecEndPos, vecHullEnd); + + for (new i = 0; i < 3; ++i) { + vecHullEnd[i] = vecSrc[i] + ((vecHullEnd[i] - vecSrc[i]) * 2.0); + } + + new tmpTrace = create_tr2(); + engfunc(EngFunc_TraceLine, vecSrc, vecHullEnd, DONT_IGNORE_MONSTERS, pEntity, tmpTrace); + + new Float:flFraction; + get_tr2(tmpTrace, TR_flFraction, flFraction); + + if (flFraction < 1.0) { + free_tr2(pTr); + pTr = tmpTrace; + return; + } + + static Float:vecEnd[3]; + for (new i = 0; i < 2; i++) { + for (new j = 0; j < 2; j++) { + for (new k = 0; k < 2; k++) { + vecEnd[0] = vecHullEnd[0] + rgvecMinsMaxs[i][0]; + vecEnd[1] = vecHullEnd[1] + rgvecMinsMaxs[j][1]; + vecEnd[2] = vecHullEnd[2] + rgvecMinsMaxs[k][2]; + + engfunc(EngFunc_TraceLine, vecSrc, vecEnd, DONT_IGNORE_MONSTERS, pEntity, tmpTrace); + get_tr2(tmpTrace, TR_flFraction, flFraction); + + new Float:vecEndPos[3]; + get_tr2(tmpTrace, TR_vecEndPos, vecEndPos); + + if (flFraction < 1.0) { + new Float:flThisDistance = get_distance_f(vecEndPos, vecSrc); + if (flThisDistance < flDistance) { + free_tr2(pTr); + pTr = tmpTrace; + flDistance = flThisDistance; + } + } + } + } + } +} + +_RadiusDamage(const Float:vecOrigin[3], iInflictor, pAttacker, Float:flDamage, Float:flRadius, iClassIgnore, iDamageBits) { + #pragma unused iClassIgnore + + static Float:vecSrc[3]; + xs_vec_copy(vecOrigin, vecSrc); + + new Float:flFalloff = flRadius ? (flDamage / flRadius) : 1.0; + new bool:bInWater = (PointContents(vecSrc) == CONTENTS_WATER); + + vecSrc[2] += 1.0; // in case grenade is lying on the ground + + if (!pAttacker) { + pAttacker = iInflictor; + } + + new pTr = create_tr2(); + + new pEntity; + new pPrevEntity; + while ((pEntity = engfunc(EngFunc_FindEntityInSphere, pEntity, vecSrc, flRadius)) != 0) { + if (pPrevEntity >= pEntity) { + break; + } + + pPrevEntity = pEntity; + + if (!pev_valid(pEntity)) { + continue; + } + + if (ExecuteHam(Ham_IsPlayer, pEntity) && !rg_is_player_can_takedamage(pEntity, pAttacker)) { + continue; + } + + if (pev(pEntity, pev_takedamage) == DAMAGE_NO) { + continue; + } + + static szClassname[32]; + pev(pEntity, pev_classname, szClassname, charsmax(szClassname)); + + // UNDONE: this should check a damage mask, not an ignore + // if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore ) {// houndeyes don't hurt other houndeyes with their attack + // continue; + // } + + new iWaterLevel = pev(pEntity, pev_waterlevel); + + if (bInWater && iWaterLevel == 0) { + continue; + } + + if (!bInWater && iWaterLevel == 3) { + continue; + } + + static Float:vecSpot[3]; + ExecuteHamB(Ham_BodyTarget, pEntity, vecSrc, vecSpot); + engfunc(EngFunc_TraceLine, vecSrc, vecSpot, IGNORE_MONSTERS, iInflictor, pTr); + + static Float:flFraction; + get_tr2(pTr, TR_flFraction, flFraction); + + if (flFraction != 1.0 && get_tr2(pTr, TR_pHit) != pEntity) { + continue; + } + + if (get_tr2(pTr, TR_StartSolid)) { + set_tr2(pTr, TR_vecEndPos, vecSrc); + set_tr2(pTr, TR_flFraction, 0.0); + flFraction = 0.0; + } + + static Float:vecEnd[3]; + get_tr2(pTr, TR_vecEndPos, vecEnd); + + new Float:flAdjustedDamage = flDamage - (get_distance_f(vecSrc, vecEnd) * flFalloff); + + if (flAdjustedDamage < 0.0) { + flAdjustedDamage = 0.0; + } + + if (flFraction != 1.0) { + static Float:vecDir[3]; + xs_vec_sub(vecEnd, vecSrc, vecDir); + xs_vec_normalize(vecDir, vecDir); + + rg_multidmg_clear(); + ExecuteHamB(Ham_TraceAttack, pEntity, iInflictor, flAdjustedDamage, vecDir, pTr, iDamageBits); + rg_multidmg_apply(iInflictor, pAttacker); + } else { + ExecuteHamB(Ham_TakeDamage, pEntity, iInflictor, pAttacker, flAdjustedDamage, iDamageBits); + } + } + + free_tr2(pTr); +} + +MakeAimDir(pPlayer, Float:flDistance, Float:vecOut[3]) { + static Float:vecAngles[3]; + pev(pPlayer, pev_v_angle, vecAngles); + engfunc(EngFunc_MakeVectors, vecAngles); + + get_global_vector(GL_v_forward, vecOut); + xs_vec_mul_scalar(vecOut, flDistance, vecOut); +} + +GetDecalIndex(pEntity) { + new iDecalIndex = ExecuteHamB(Ham_DamageDecal, pEntity, 0); + if (iDecalIndex < 0) { + return -1; + } + + iDecalIndex = ArrayGetCell(g_irgDecals, iDecalIndex); + + if (iDecalIndex == engfunc(EngFunc_DecalIndex, "{break1") + || iDecalIndex == engfunc(EngFunc_DecalIndex, "{break2") + || iDecalIndex == engfunc(EngFunc_DecalIndex, "{break3")) { + return engfunc(EngFunc_DecalIndex, "{bproof1"); + } + + return iDecalIndex; +} + +// ANCHOR: Storages + +InitStorages() { + g_rgWeapons[CW_Data_Name] = ArrayCreate(64, 1); + g_rgWeapons[CW_Data_Icon] = ArrayCreate(16, 1); + + for (new i = 0; i < _:CW_Data; ++i) { + if (!g_rgWeapons[CW_Data:i]) { + g_rgWeapons[CW_Data:i] = ArrayCreate(1, 1); + } + } + + g_rgWeaponsMap = TrieCreate(); + + g_irgDecals = ArrayCreate(); +} + +DestroyStorages() { + for (new CW:iHandler = CW:0; _:iHandler < g_iWeaponCount; ++iHandler) { + DestroyWeaponData(iHandler); + } + + for (new i = 0; i < _:CW_Data; ++i) { + ArrayDestroy(Array:g_rgWeapons[CW_Data:i]); + } + + TrieDestroy(g_rgWeaponsMap); + + ArrayDestroy(g_irgDecals); +} + +// ANCHOR: Weapon Data + +CW:CreateWeaponData(const szName[]) { + new CW:iHandler = CW:g_iWeaponCount; + + for (new iParam = 0; iParam < _:CW_Data; ++iParam) { + ArrayPushCell(Array:g_rgWeapons[CW_Data:iParam], 0); + } + + TrieSetCell(g_rgWeaponsMap, szName, iHandler); + + InitBindings(iHandler); + + g_iWeaponCount++; + + return iHandler; +} + +DestroyWeaponData(CW:iHandler) { + DestroyBindings(iHandler); +} + +any:GetData(CW:iHandler, CW_Data:iParam) { + return ArrayGetCell(Array:g_rgWeapons[iParam], _:iHandler); +} + +GetStringData(CW:iHandler, CW_Data:iParam, szOut[], iLen) { + ArrayGetString(Array:g_rgWeapons[iParam], _:iHandler, szOut, iLen); +} + +SetData(CW:iHandler, CW_Data:iParam, any:value) { + ArraySetCell(Array:g_rgWeapons[iParam], _:iHandler, value); +} + +SetStringData(CW:iHandler, CW_Data:iParam, const szValue[]) { + ArraySetString(Array:g_rgWeapons[iParam], _:iHandler, szValue); +} + +// ANCHOR: Weapon Bindings + +Array:InitBindings(CW:iHandler) { + new Array:irgBindings = ArrayCreate(Function, _:CW_Binding); + for (new i = 0; i < _:CW_Binding; ++i) { + new rgBinding[Function]= {-1, -1}; + ArrayPushArray(irgBindings, rgBinding); + } + + SetData(iHandler, CW_Data_Bindings, irgBindings); +} + +DestroyBindings(CW:iHandler) { + new Array:irgBindings = GetData(iHandler, CW_Data_Bindings); + ArrayDestroy(irgBindings); +} + +Bind(CW:iHandler, iBinding, iPluginId, iFunctionid) { + new rgBinding[Function]; + rgBinding[Function_PluginId] = iPluginId; + rgBinding[Function_FunctionId] = iFunctionid; + + new Array:irgBindings = GetData(iHandler, CW_Data_Bindings); + ArraySetArray(irgBindings, iBinding, rgBinding); +} + +GetBinding(CW:iHandler, CW_Binding:iBinding, &iPluginId, &iFunctionId) { + new Array:iszBindings = GetData(iHandler, CW_Data_Bindings); + + static rgBinding[Function]; + ArrayGetArray(iszBindings, _:iBinding, rgBinding, sizeof(rgBinding)); + + if (rgBinding[Function_PluginId] == -1) { + return false; + } + + if (rgBinding[Function_FunctionId] == -1) { + return false; + } + + iPluginId = rgBinding[Function_PluginId]; + iFunctionId = rgBinding[Function_FunctionId]; + + return true; +} + +any:ExecuteBindedFunction(CW_Binding:iBinding, this, any:...) { + new CW:iHandler = GetHandlerByEntity(this); + + new iPluginId, iFunctionId; + if (!GetBinding(iHandler, iBinding, iPluginId, iFunctionId)) { + return PLUGIN_CONTINUE; + } + + if (callfunc_begin_i(iFunctionId, iPluginId) == 1) { + callfunc_push_int(this); + + if (iBinding == CWB_WeaponBoxModelUpdate) { + new pWeaponBox = getarg(2); + callfunc_push_int(pWeaponBox); + } + + return callfunc_end(); + } + + return PLUGIN_CONTINUE; +} + +// ANCHOR: Weapon Hooks + +InitWeaponHooks() { + for (new CW:iHandler = CW:0; _:iHandler < g_iWeaponCount; ++iHandler) { + new iWeaponId = GetData(iHandler, CW_Data_Id); + if (!g_bWeaponHooks[iWeaponId]) { + RegisterWeaponHooks(iWeaponId); + } + } +} + +RegisterWeaponHooks(iWeaponId) { + new szClassname[32]; + get_weaponname(iWeaponId, szClassname, charsmax(szClassname)); + + RegisterHam(Ham_Item_PostFrame, szClassname, "OnItemPostFrame", .Post = 0); + RegisterHam(Ham_Item_ItemSlot, szClassname, "OnItemSlot", .Post = 0); + RegisterHam(Ham_Item_Holster, szClassname, "OnItemHolster", .Post = 0); + RegisterHam(Ham_Item_Deploy, szClassname, "OnItemDeploy", .Post = 0); + RegisterHam(Ham_CS_Item_GetMaxSpeed, szClassname, "OnCSItemGetMaxSpeed", .Post = 0); + // RegisterHam(Ham_Weapon_PlayEmptySound, szClassname, "OnWeaponPlayEmptySound", .Post = 0); + RegisterHam(Ham_Item_AddToPlayer, szClassname, "OnItemAddToPlayer_Post", .Post = 1); + RegisterHam(Ham_Spawn, szClassname, "OnSpawn_Post", .Post = 1); + RegisterHam(Ham_CS_Item_CanDrop, szClassname, "OnCanDrop"); + // RegisterHam(Ham_Item_GetItemInfo, szClassname, "OnItemGetItemInfo", .Post = 1); + RegisterHam(Ham_Weapon_PrimaryAttack, szClassname, "OnWeaponPrimaryAttack"); + RegisterHam(Ham_Weapon_SecondaryAttack, szClassname, "OnWeaponSecondaryAttack"); + RegisterHam(Ham_Weapon_Reload, szClassname, "OnWeaponReload"); + RegisterHam(Ham_Weapon_WeaponIdle, szClassname, "OnWeaponIdle"); + + g_bWeaponHooks[iWeaponId] = true; +} + +// ANCHOR: Effects + +SparkShower(const Float:vecOrigin[3], const Float:vecAngles[3], iOwner) { + new pSparkShower = engfunc(EngFunc_CreateNamedEntity, engfunc(EngFunc_AllocString, "spark_shower")); + if (!pSparkShower) { + return; + } + + engfunc(EngFunc_SetOrigin, pSparkShower, vecOrigin); + set_pev(pSparkShower, pev_angles, vecAngles); + set_pev(pSparkShower, pev_owner, iOwner); + dllfunc(DLLFunc_Spawn, pSparkShower); +} + +GrenadeExplosion(const Float:vecOrigin[3], Float:flDamage) { + new iModelIndex = PointContents(vecOrigin) != CONTENTS_WATER + ? engfunc(EngFunc_ModelIndex, "sprites/zerogxplode.spr") + : engfunc(EngFunc_ModelIndex, "sprites/WXplo1.spr"); + + new iScale = floatround((flDamage - 50.0) * 0.60); + + if (iScale < 8) { + iScale = 8; + } + + if (iScale > 255) { + iScale = 255; + } + + engfunc(EngFunc_MessageBegin, MSG_PAS, SVC_TEMPENTITY, vecOrigin, 0); + write_byte(TE_EXPLOSION); + engfunc(EngFunc_WriteCoord, vecOrigin[0]); + engfunc(EngFunc_WriteCoord, vecOrigin[1]); + engfunc(EngFunc_WriteCoord, vecOrigin[2]); + write_short(iModelIndex); + write_byte(iScale); + write_byte(15); + write_byte(TE_EXPLFLAG_NONE); + message_end(); +} + +GrenadeSmoke(pGrenade) { + static Float:vecOrigin[3]; + pev(pGrenade, pev_origin, vecOrigin); + + static Float:flDamage; + pev(pGrenade, pev_dmg, flDamage); + + if (PointContents(vecOrigin) == CONTENTS_WATER) { + static Float:vecSize[3] = {64.0, 64.0, 64.0}; + + static Float:vecMins[3]; + xs_vec_sub(vecOrigin, vecSize, vecMins); + + static Float:vecMaxs[3]; + xs_vec_add(vecOrigin, vecSize, vecMaxs); + + Bubbles(vecMins, vecMaxs, 100); + } else { + new iModelIndex = engfunc(EngFunc_ModelIndex, "sprites/steam1.spr"); + + new Float:flRadius = (flDamage - 50.0) * 0.80; + if (flRadius < 8.0) { + flRadius = 9.0; + } + + engfunc(EngFunc_MessageBegin, MSG_PAS, SVC_TEMPENTITY, vecOrigin, 0); + write_byte(TE_SMOKE); + engfunc(EngFunc_WriteCoord, vecOrigin[0]); + engfunc(EngFunc_WriteCoord, vecOrigin[1]); + engfunc(EngFunc_WriteCoord, vecOrigin[2]); + write_short(iModelIndex); + write_byte(floatround(flRadius)); // scale * 10 + write_byte(12); // framerate + message_end(); + } +} + +Bubbles(const Float:vecMins[3], const Float:vecMaxs[3], iCount) { + static Float:vecMid[3]; + for (new i = 0; i < 3; ++i) { + vecMid[i] = (vecMins[i] + vecMaxs[i]) * 0.5; + } + + new Float:flHeight = WaterLevel(vecMid, vecMid[2], vecMid[2] + 1024.0) - vecMins[2]; + new iModelIndex = engfunc(EngFunc_ModelIndex, "sprites/bubble.spr"); + + engfunc(EngFunc_MessageBegin, MSG_PAS, SVC_TEMPENTITY, vecMid, 0); + write_byte(TE_BUBBLES); + engfunc(EngFunc_WriteCoord, vecMins[0]); + engfunc(EngFunc_WriteCoord, vecMins[1]); + engfunc(EngFunc_WriteCoord, vecMins[2]); + engfunc(EngFunc_WriteCoord, vecMaxs[0]); + engfunc(EngFunc_WriteCoord, vecMaxs[1]); + engfunc(EngFunc_WriteCoord, vecMaxs[2]); + engfunc(EngFunc_WriteCoord, flHeight); // height + write_short(iModelIndex); + write_byte(iCount); // count + write_coord(8); // speed + message_end(); +} + +DecalTrace(pTr, iDecal) { + if (iDecal < 0) { + return; + } + + new Float:flFraction; + get_tr2(pTr, TR_flFraction, flFraction); + + if (flFraction == 1.0) { + return; + } + + // Only decal BSP models + new pHit = get_tr2(pTr, TR_pHit); + if (pHit != -1) { + if (pHit && !ExecuteHam(Ham_IsBSPModel, pHit)) { + return; + } + } else { + pHit = 0; + } + + new iMessage = TE_DECAL; + if (pHit != 0) { + if (iDecal > 255) { + iMessage = TE_DECALHIGH; + iDecal -= 256; + } + } else { + iMessage = TE_WORLDDECAL; + if (iDecal > 255) { + iMessage = TE_WORLDDECALHIGH; + iDecal -= 256; + } + } + + static Float:vecEndPos[3]; + get_tr2(pTr, TR_vecEndPos, vecEndPos); + + engfunc(EngFunc_MessageBegin, MSG_BROADCAST, SVC_TEMPENTITY, vecEndPos, 0); + write_byte(iMessage); + engfunc(EngFunc_WriteCoord, vecEndPos[0]); + engfunc(EngFunc_WriteCoord, vecEndPos[1]); + engfunc(EngFunc_WriteCoord, vecEndPos[2]); + write_byte(iDecal); + if (pHit) { + write_short(pHit); + } + message_end(); +} + +BulletSmoke(pTr) { + static Float:vecSrc[3]; + get_tr2(pTr, TR_vecEndPos, vecSrc); + + static Float:vecEnd[3]; + get_tr2(pTr, TR_vecPlaneNormal, vecEnd); + xs_vec_mul_scalar(vecEnd, 2.5, vecEnd); + xs_vec_add(vecSrc, vecEnd, vecEnd); + + static iModelIndex; + if (!iModelIndex) { + iModelIndex = engfunc(EngFunc_ModelIndex, WALL_PUFF_SPRITE); + } + + engfunc(EngFunc_MessageBegin, MSG_PAS, SVC_TEMPENTITY, vecEnd, 0); + write_byte(TE_EXPLOSION); + engfunc(EngFunc_WriteCoord, vecEnd[0]); + engfunc(EngFunc_WriteCoord, vecEnd[1]); + engfunc(EngFunc_WriteCoord, vecEnd[2] - 10.0); + write_short(iModelIndex); + write_byte(5); + write_byte(50); + write_byte(TE_EXPLFLAG_NODLIGHTS | TE_EXPLFLAG_NOSOUND | TE_EXPLFLAG_NOPARTICLES); + message_end(); +} + +MakeDecal(pTr, pEntity, iDecalIndex, bool:bGunshotDecal = true) { + static vecOrigin[3]; + get_tr2(pTr, TR_vecEndPos, vecOrigin); + + new pHit; + get_tr2(pTr, TR_pHit, pHit); + + if(pHit) { + emessage_begin(MSG_BROADCAST, SVC_TEMPENTITY); + ewrite_byte(TE_DECAL); + engfunc(EngFunc_WriteCoord, vecOrigin[0]); + engfunc(EngFunc_WriteCoord, vecOrigin[1]); + engfunc(EngFunc_WriteCoord, vecOrigin[2]); + ewrite_byte(iDecalIndex); + ewrite_short(pHit); + emessage_end(); + } else { + emessage_begin(MSG_BROADCAST, SVC_TEMPENTITY); + ewrite_byte(TE_WORLDDECAL); + engfunc(EngFunc_WriteCoord, vecOrigin[0]); + engfunc(EngFunc_WriteCoord, vecOrigin[1]); + engfunc(EngFunc_WriteCoord, vecOrigin[2]); + ewrite_byte(iDecalIndex); + emessage_end(); + } + + if (bGunshotDecal) { + message_begin(MSG_BROADCAST, SVC_TEMPENTITY); + write_byte(TE_GUNSHOTDECAL); + engfunc(EngFunc_WriteCoord, vecOrigin[0]); + engfunc(EngFunc_WriteCoord, vecOrigin[1]); + engfunc(EngFunc_WriteCoord, vecOrigin[2]); + write_short(pEntity); + write_byte(iDecalIndex); + message_end(); + } +} + +BubbleTrail(const Float:from[3], const Float:to[3], count) { + new Float:flHeight = WaterLevel(from, from[2], from[2] + 256); + flHeight = flHeight - from[2]; + + if (flHeight < 8) { + flHeight = WaterLevel(to, to[2], to[2] + 256.0); + flHeight = flHeight - to[2]; + if (flHeight < 8) { + return; + } + + // UNDONE: do a ploink sound + flHeight = flHeight + to[2] - from[2]; + } + + if (count > 255) { + count = 255; + } + + static g_sModelIndexBubbles; + if (!g_sModelIndexBubbles) { + g_sModelIndexBubbles = engfunc(EngFunc_ModelIndex, "sprites/bubble.spr"); + } + + engfunc(EngFunc_MessageBegin, MSG_BROADCAST, SVC_TEMPENTITY, from, 0); + write_byte(TE_BUBBLETRAIL); + engfunc(EngFunc_WriteCoord, from[0]); + engfunc(EngFunc_WriteCoord, from[1]); + engfunc(EngFunc_WriteCoord, from[2]); + engfunc(EngFunc_WriteCoord, to[0]); + engfunc(EngFunc_WriteCoord, to[1]); + engfunc(EngFunc_WriteCoord, to[2]); + engfunc(EngFunc_WriteCoord, flHeight); + write_short(g_sModelIndexBubbles); + write_byte(count); + write_coord(8); + message_end(); +} + +ExplosionDecalTrace(pTr) { + switch (random(3)) { + case 0: { + DecalTrace(pTr, engfunc(EngFunc_DecalIndex, "{scorch1")); + } + case 1: { + DecalTrace(pTr, engfunc(EngFunc_DecalIndex, "{scorch2")); + } + case 2: { + DecalTrace(pTr, engfunc(EngFunc_DecalIndex, "{scorch3")); + } + } +} + +DebrisSound(pEntity) { + switch (random(3)) { + case 0: { + emit_sound(pEntity, CHAN_VOICE, "weapons/debris1.wav", 0.55, ATTN_NORM, 0, PITCH_NORM); + } + case 1: { + emit_sound(pEntity, CHAN_VOICE, "weapons/debris2.wav", 0.55, ATTN_NORM, 0, PITCH_NORM); + } + case 2: { + emit_sound(pEntity, CHAN_VOICE, "weapons/debris3.wav", 0.55, ATTN_NORM, 0, PITCH_NORM); + } + } +} + +bool:EjectWeaponBrass(this, iModelIndex, iSoundType) { + new pPlayer = GetPlayer(this); + + if (!iModelIndex) { + return false; + } + + static Float:vecViewOfs[3]; + pev(pPlayer, pev_view_ofs, vecViewOfs); + + static Float:vecAngles[3]; + pev(pPlayer, pev_angles, vecAngles); + + static Float:vecUp[3]; + angle_vector(vecAngles, ANGLEVECTOR_UP, vecUp); + + static Float:vecForward[3]; + angle_vector(vecAngles, ANGLEVECTOR_FORWARD, vecForward); + + static Float:vecRight[3]; + angle_vector(vecAngles, ANGLEVECTOR_RIGHT, vecRight); + + static Float:vecOrigin[3]; + pev(pPlayer, pev_origin, vecOrigin); + + for (new i = 0; i < 3; ++i) { + vecOrigin[i] = vecOrigin[i] + vecViewOfs[i] + (vecUp[i] * -9.0) + (vecForward[i] * 16.0); + } + + static Float:vecVelocity[3]; + pev(pPlayer, pev_velocity, vecVelocity); + + for (new i = 0; i < 3; ++i) { + vecVelocity[i] = vecVelocity[i] + (vecRight[i] * random_float(50.0, 70.0)) + (vecUp[i] * random_float(100.0, 150.0)) + (vecForward[i] * 25.0); + } + + engfunc(EngFunc_MessageBegin, MSG_PVS, SVC_TEMPENTITY, vecOrigin, 0); + write_byte(TE_MODEL); + engfunc(EngFunc_WriteCoord, vecOrigin[0]); + engfunc(EngFunc_WriteCoord, vecOrigin[1]); + engfunc(EngFunc_WriteCoord, vecOrigin[2]); + engfunc(EngFunc_WriteCoord, vecVelocity[0]); + engfunc(EngFunc_WriteCoord, vecVelocity[1]); + engfunc(EngFunc_WriteCoord, vecVelocity[2]); + write_angle(floatround(vecAngles[1])); + write_short(iModelIndex); + write_byte(iSoundType); + write_byte(25); + message_end(); + + return true; +} + +// ANCHOR: Random + +new const seed_table[256] = { + 28985, 27138, 26457, 9451, 17764, 10909, 28790, 8716, 6361, 4853, 17798, 21977, 19643, 20662, 10834, 20103, + 27067, 28634, 18623, 25849, 8576, 26234, 23887, 18228, 32587, 4836, 3306, 1811, 3035, 24559, 18399, 315, + 26766, 907, 24102, 12370, 9674, 2972, 10472, 16492, 22683, 11529, 27968, 30406, 13213, 2319, 23620, 16823, + 10013, 23772, 21567, 1251, 19579, 20313, 18241, 30130, 8402, 20807, 27354, 7169, 21211, 17293, 5410, 19223, + 10255, 22480, 27388, 9946, 15628, 24389, 17308, 2370, 9530, 31683, 25927, 23567, 11694, 26397, 32602, 15031, + 18255, 17582, 1422, 28835, 23607, 12597, 20602, 10138, 5212, 1252, 10074, 23166, 19823, 31667, 5902, 24630, + 18948, 14330, 14950, 8939, 23540, 21311, 22428, 22391, 3583, 29004, 30498, 18714, 4278, 2437, 22430, 3439, + 28313, 23161, 25396, 13471, 19324, 15287, 2563, 18901, 13103, 16867, 9714, 14322, 15197, 26889, 19372, 26241, + 31925, 14640, 11497, 8941, 10056, 6451, 28656, 10737, 13874, 17356, 8281, 25937, 1661, 4850, 7448, 12744, + 21826, 5477, 10167, 16705, 26897, 8839, 30947, 27978, 27283, 24685, 32298, 3525, 12398, 28726, 9475, 10208, + 617, 13467, 22287, 2376, 6097, 26312, 2974, 9114, 21787, 28010, 4725, 15387, 3274, 10762, 31695, 17320, + 18324, 12441, 16801, 27376, 22464, 7500, 5666, 18144, 15314, 31914, 31627, 6495, 5226, 31203, 2331, 4668, + 12650, 18275, 351, 7268, 31319, 30119, 7600, 2905, 13826, 11343, 13053, 15583, 30055, 31093, 5067, 761, + 9685, 11070, 21369, 27155, 3663, 26542, 20169, 12161, 15411, 30401, 7580, 31784, 8985, 29367, 20989, 14203, + 29694, 21167, 10337, 1706, 28578, 887, 3373, 19477, 14382, 675, 7033, 15111, 26138, 12252, 30996, 21409, + 25678, 18555, 13256, 23316, 22407, 16727, 991, 9236, 5373, 29402, 6117, 15241, 27715, 19291, 19888, 19847 +}; + +Float:SharedRandomFloat(seed, Float:low, Float:high) { + new Float:range = high - low; + if (!range) { + return low; + } + + new glSeed = U_Srand(seed + floatround(low) + floatround(high)); + U_Random(glSeed); + U_Random(glSeed); + + new tensixrand = U_Random(glSeed) & 65535; + new Float:offset = float(tensixrand) / 65536.0; + + return (low + offset * range ); +} + +U_Random(&glSeed) { + glSeed *= 69069; + glSeed += seed_table[glSeed & 0xff]; + + return (++glSeed & 0x0fffffff); +} + +U_Srand(seed) { + return seed_table[seed & 0xff]; +} + +// FireEvent(pTr, const szSnd[], const szShellModel[]) { +// static Float:flFraction; +// get_tr2(pTr, TR_flFraction, flFraction); + +// new pHit = get_tr2(pTr, TR_pHit); + +// if (flFraction != 1.0) { +// // Native_PlaySoundAtPosition( $origin = $trace_endpos, $sound = weapons/bullet_hit1.wav ); +// // Native_ImpactParticles( $origin = $trace_endpos ); +// // Native_PlaceDecal( $origin = $trace_endpos, $decal = "{shot2", $trace_entity ); + +// new iDecalIndex = random_num(get_decal_index("{shot1"), get_decal_index("{shot5") + 1); +// MakeDecal(pTr, pHit, iDecalIndex); +// } +// } + +// BeamPoints(const Float:vecStart[3], const Float:vecEnd[3], const color[3]) { +// message_begin(MSG_BROADCAST ,SVC_TEMPENTITY); +// write_byte(TE_BEAMPOINTS); +// write_coord(floatround(vecStart[0])); // start position +// write_coord(floatround(vecStart[1])); +// write_coord(floatround(vecStart[2])); +// write_coord(floatround(vecEnd[0])); // end position +// write_coord(floatround(vecEnd[1])); +// write_coord(floatround(vecEnd[2])); +// write_short(engfunc(EngFunc_ModelIndex, "sprites/laserbeam.spr")); // sprite index +// write_byte(0); // starting frame +// write_byte(10); // frame rate in 0.1's +// write_byte(30); // life in 0.1's +// write_byte(2); // line width in 0.1's +// write_byte(1); // noise amplitude in 0.01's +// write_byte(color[0]); // Red +// write_byte(color[1]); // Green +// write_byte(color[2]); // Blue +// write_byte(127); // brightness +// write_byte(10); // scroll speed in 0.1's +// message_end(); +// } diff --git a/include/api_custom_weapons.inc b/api/custom-weapons/include/api_custom_weapons.inc similarity index 99% rename from include/api_custom_weapons.inc rename to api/custom-weapons/include/api_custom_weapons.inc index 9287073..9871d72 100644 --- a/include/api_custom_weapons.inc +++ b/api/custom-weapons/include/api_custom_weapons.inc @@ -42,7 +42,7 @@ enum CW_Data { CW_Data_WeaponFlags, CW_Data_Bindings, CW_Data_Flags -} +}; enum CW_Binding { CWB_Idle, @@ -58,7 +58,7 @@ enum CW_Binding { CWB_WeaponBoxModelUpdate, CWB_Pump, CWB_GrenadeThrow -} +}; enum CW_Flags (<<=1) { CWF_None, @@ -66,7 +66,7 @@ enum CW_Flags (<<=1) { CWF_NoBulletDecal, CWF_CustomReload, CWF_NotRefillable -} +}; native CW:CW_Register(const szName[], iWeaponId, iClipSize = WEAPON_NOCLIP, iPrimaryAmmoType = -1, iPrimaryAmmoMaxAmount = -1, iSecondaryAmmoType = -1, iSecondaryAmmoMaxAmount = -1, iSlotId = 0, iPosition = 0, iWeaponFlags = 0, const szIcon[] = "", CW_Flags:iFlags = CWF_None); native CW_Bind(CW:iHandler, CW_Binding:iBinding, const szFunctionName[]); diff --git a/api/entity-selection/README.md b/api/entity-selection/README.md new file mode 100644 index 0000000..f51ff77 --- /dev/null +++ b/api/entity-selection/README.md @@ -0,0 +1,191 @@ +# Entity Selection API +This API provides in-game select entities functionality using a virtual cursor. + +## Making simple strategy system +Here is a simple example of how to make something like an RTS system using the API. + +![Simple Strategy Mode](../../images/example-entity-selection.gif) + +```pawn +#include +#include +#include +#include +#include + +#include +#include + +#define ENTITY_BASE_MONSTER_CLASS "monster_base" + +new const SelectionColor[3] = {255, 0, 0}; + +new bool:g_rgbPlayerInStrategyMode[MAX_PLAYERS + 1]; +new Selection:g_rgiPlayerSelection[MAX_PLAYERS + 1]; + +public plugin_init() { + register_plugin("Simple Strategy System", "1.0.0", "Hedgehog Fog"); + + RegisterHamPlayer(Ham_Player_PreThink, "HamHook_Player_PreThink"); + + register_concmd("strategy_mode", "Command_StrategyMode"); +} + +public client_connect(pPlayer) { + g_rgbPlayerInStrategyMode[pPlayer] = false; +} + +public client_disconnected(pPlayer) { + @Player_SetStrategyMode(pPlayer, false); +} + +public Command_StrategyMode(pPlayer) { + new bool:bValue = !!read_argv_int(1); + + @Player_SetStrategyMode(pPlayer, bValue); + + return PLUGIN_HANDLED; +} + +public HamHook_Player_PreThink(pPlayer) { + if (g_rgbPlayerInStrategyMode[pPlayer]) { + @Player_StrategyModeThink(pPlayer); + } +} + +@Player_SetStrategyMode(this, bool:bValue) { + if (bValue == g_rgbPlayerInStrategyMode[this]) return; + + if (bValue) { + new Selection:iSelection = EntitySelection_Create(this); + EntitySelection_SetFilter(iSelection, "Callback_SelectionMonstersFilter"); + EntitySelection_SetColor(iSelection, SelectionColor); + + console_print(this, "Entered strategy mode!"); + } else { + EntitySelection_Destroy(g_rgiPlayerSelection[this]); + + console_print(this, "Left strategy mode!"); + } + + g_rgbPlayerInStrategyMode[this] = bValue; +} + +@Player_StrategyModeThink(this) { + static iButtons; iButtons = pev(this, pev_button); + static iOldButtons; iOldButtons = pev(this, pev_oldbuttons); + + if (iButtons & IN_ATTACK && ~iOldButtons & IN_ATTACK) { + EntitySelection_Start(g_rgiPlayerSelection[this]); + } else if (~iButtons & IN_ATTACK && iOldButtons & IN_ATTACK) { + EntitySelection_End(g_rgiPlayerSelection[this]); + @Player_HighlightSelectedMonsters(this); + } + + if (~iButtons & IN_ATTACK2 && iOldButtons & IN_ATTACK2) { + static Float:vecTarget[3]; EntitySelection_GetCursorPos(g_rgiPlayerSelection[this], vecTarget); + + if (@Player_MoveSelectedMonsters(this, vecTarget)) { + @Player_DrawTarget(this, vecTarget, 16.0); + } + } + + // Block observer input for spectators + if (!is_user_alive(this)) { + set_member(this, m_flNextObserverInput, get_gametime() + 1.0); + } +} + +@Player_HighlightSelectedMonsters(this) { + new iMonstersNum = EntitySelection_GetSize(g_rgiPlayerSelection[this]); + if (!iMonstersNum) return; + + for (new i = 0; i < iMonstersNum; ++i) { + new pMonster = EntitySelection_GetEntity(g_rgiPlayerSelection[this], i); + @Monster_Highlight(pMonster, this); + } +} + +@Player_MoveSelectedMonsters(this, const Float:vecGoal[3]) { + new iMonstersNum = EntitySelection_GetSize(g_rgiPlayerSelection[this]); + if (!iMonstersNum) return false; + + for (new i = 0; i < iMonstersNum; ++i) { + new pMonster = EntitySelection_GetEntity(g_rgiPlayerSelection[this], i); + @Monster_SetGoal(pMonster, vecGoal); + } + + return true; +} + +@Player_DrawTarget(this, const Float:vecTarget[3], Float:flRadius) { + static iModelIndex; iModelIndex = engfunc(EngFunc_ModelIndex, "sprites/zbeam2.spr"); + static const iLifeTime = 5; + static Float:flRadiusRatio; flRadiusRatio = 1.0 / (float(iLifeTime) / 10); + + engfunc(EngFunc_MessageBegin, MSG_ONE, SVC_TEMPENTITY, vecTarget, this); + write_byte(TE_BEAMCYLINDER); + engfunc(EngFunc_WriteCoord, vecTarget[0]); + engfunc(EngFunc_WriteCoord, vecTarget[1]); + engfunc(EngFunc_WriteCoord, vecTarget[2]); + engfunc(EngFunc_WriteCoord, 0.0); + engfunc(EngFunc_WriteCoord, 0.0); + engfunc(EngFunc_WriteCoord, vecTarget[2] + (flRadius * flRadiusRatio)); + write_short(iModelIndex); + write_byte(0); + write_byte(0); + write_byte(iLifeTime); + write_byte(8); + write_byte(0); + write_byte(SelectionColor[0]); + write_byte(SelectionColor[1]); + write_byte(SelectionColor[2]); + write_byte(255); + write_byte(0); + message_end(); +} + +@Monster_SetGoal(this, const Float:vecGoal[3]) { + set_pev(this, pev_enemy, 0); + CE_SetMemberVec(this, "vecGoal", vecGoal); + CE_SetMember(this, "flNextEnemyUpdate", get_gametime() + 5.0); +} + +@Monster_Highlight(this, pPlayer) { + static Float:vecTarget[3]; pev(this, pev_origin, vecTarget); + vecTarget[2] = UTIL_TraceGroundPosition(vecTarget, this) + 1.0; + + static Float:flRadius; flRadius = @Entity_GetSelectionRadius(this); + + @Player_DrawTarget(pPlayer, vecTarget, flRadius); +} + +Float:@Entity_GetSelectionRadius(this) { + static const Float:flRadiusBorder = 8.0; + + static Float:vecMins[3]; pev(this, pev_mins, vecMins); + static Float:vecMaxs[3]; pev(this, pev_maxs, vecMaxs); + static Float:flTargetRadius; flTargetRadius = floatmax(vecMaxs[0] - vecMins[0], vecMaxs[1] - vecMins[1]) / 2; + static Float:flRadius; flRadius = flTargetRadius + flRadiusBorder; + + return flRadius; +} + +public bool:Callback_SelectionMonstersFilter(pPlayer, pEntity) { + return CE_IsInstanceOf(pEntity, ENTITY_BASE_MONSTER_CLASS); +} + +stock Float:UTIL_TraceGroundPosition(const Float:vecOrigin[], pIgnoreEnt) { + static pTrace; pTrace = create_tr2(); + + static Float:vecTarget[3]; xs_vec_set(vecTarget, vecOrigin[0], vecOrigin[1], vecOrigin[2] - 8192.0); + + engfunc(EngFunc_TraceLine, vecOrigin, vecTarget, IGNORE_MONSTERS, pIgnoreEnt, pTrace); + + get_tr2(pTrace, TR_vecEndPos, vecTarget); + + free_tr2(pTrace); + + return vecTarget[2]; +} +``` \ No newline at end of file diff --git a/api/entity-selection/api_entity_selection.sma b/api/entity-selection/api_entity_selection.sma new file mode 100644 index 0000000..a33d2aa --- /dev/null +++ b/api/entity-selection/api_entity_selection.sma @@ -0,0 +1,584 @@ +#pragma semicolon 1 + +#include +#include +#include +#include + +#include + +#define IS_PLAYER(%1) (%1 >= 1 && %1 <= MaxClients) + +#define MAX_SELECTIONS 256 + +#define NOT_VALID_SELECTION_ERR "Selection %d is not valid selection handle!" + +const Float:SelectionGroundOffset = 1.0; + +new const g_szTrailModel[] = "sprites/zbeam2.spr"; + +enum Callback { + Callback_PluginId, + Callback_FunctionId +} + +enum Selection { + Selection_Index, + bool:Selection_Active, + bool:Selection_Free, + Selection_Player, + Selection_CursorEntity, + Selection_FilterCallback[Callback], + Array:Selection_Entities, + Float:Selection_Cursor[3], + Float:Selection_Start[3], + Float:Selection_End[3], + Selection_Color[3], + Selection_Brightness, + Float:Selection_NextDraw +}; + +new g_pTrace; +new g_iMaxEntities = 0; + +new g_rgSelections[MAX_SELECTIONS][Selection]; + +public plugin_precache() { + g_pTrace = create_tr2(); + g_iMaxEntities = global_get(glb_maxEntities); + + for (new iSelection = 0; iSelection < MAX_SELECTIONS; ++iSelection) { + g_rgSelections[iSelection][Selection_Free] = true; + } +} + +public plugin_init() { + register_plugin("[API] Entity Selection", "1.0.0", "Hedgehog Fog"); + + RegisterHamPlayer(Ham_Player_PostThink, "HamHook_Player_PostThink_Post", .Post = 1); + + register_forward(FM_OnFreeEntPrivateData, "FMHook_OnFreeEntPrivateData"); +} + +public plugin_end() { + free_tr2(g_pTrace); + + for (new iSelection = 0; iSelection < MAX_SELECTIONS; ++iSelection) { + if (g_rgSelections[iSelection][Selection_Free]) continue; + + @Selection_Free(g_rgSelections[iSelection]); + } +} + +public plugin_natives() { + register_library("api_entity_selection"); + register_native("EntitySelection_Create", "Native_CreateSelection"); + register_native("EntitySelection_Destroy", "Native_DestroySelection"); + register_native("EntitySelection_SetFilter", "Native_SetSelectionFilter"); + register_native("EntitySelection_SetColor", "Native_SetSelectionColor"); + register_native("EntitySelection_SetBrightness", "Native_SetSelectionBrightness"); + register_native("EntitySelection_GetPlayer", "Native_GetSelectionPlayer"); + register_native("EntitySelection_SetCursorEntity", "Native_SetSelectionCursorEntity"); + register_native("EntitySelection_GetCursorEntity", "Native_GetSelectionCursorEntity"); + register_native("EntitySelection_Start", "Native_StartSelection"); + register_native("EntitySelection_End", "Native_EndSelection"); + register_native("EntitySelection_GetEntity", "Native_GetSelectionEntity"); + register_native("EntitySelection_GetSize", "Native_GetSelectionSize"); + register_native("EntitySelection_GetCursorPos", "Native_GetSelectionCursorPos"); + register_native("EntitySelection_SetCursorPos", "Native_SetSelectionCursorPos"); + register_native("EntitySelection_GetStartPos", "Native_GetSelectionStartPos"); + register_native("EntitySelection_GetEndPos", "Native_GetSelectionEndPos"); +} + +/*--------------------------------[ Natives ]--------------------------------*/ + +public Native_CreateSelection(iPluginId, iArgc) { + static pPlayer; pPlayer = get_param(1); + + new iSelection = FindFreeSelection(); + if (iSelection == -1) { + log_error(AMX_ERR_NATIVE, "Failed to allocate new selection!"); + return -1; + } + + @Selection_Init(g_rgSelections[iSelection], pPlayer, iSelection); + + return iSelection; +} + +public Native_DestroySelection(iPluginId, iArgc) { + static iSelection; iSelection = get_param_byref(1); + + if (!@Selection_IsValid(g_rgSelections[iSelection])) { + log_error(AMX_ERR_NATIVE, NOT_VALID_SELECTION_ERR, iSelection); + return; + } + + @Selection_Free(g_rgSelections[iSelection]); + + set_param_byref(1, -1); +} + +public Native_SetSelectionFilter(iPluginId, iArgc) { + static iSelection; iSelection = get_param_byref(1); + static szCallback[64]; get_string(2, szCallback, charsmax(szCallback)); + + if (!@Selection_IsValid(g_rgSelections[iSelection])) { + log_error(AMX_ERR_NATIVE, NOT_VALID_SELECTION_ERR, iSelection); + return; + } + + static iFunctionId; iFunctionId = get_func_id(szCallback, iPluginId); + if (iFunctionId == -1) { + log_error(AMX_ERR_NATIVE, "Cannot find function ^"%s^" in plugin %d!", szCallback, iPluginId); + return; + } + + @Selection_SetFilterFunction(g_rgSelections[iSelection], iFunctionId, iPluginId); +} + +public Native_SetSelectionColor(iPluginId, iArgc) { + static iSelection; iSelection = get_param_byref(1); + static rgiColor[3]; get_array(2, rgiColor, sizeof(rgiColor)); + + @Selection_SetColor(g_rgSelections[iSelection], rgiColor); +} + +public Native_SetSelectionBrightness(iPluginId, iArgc) { + static iSelection; iSelection = get_param_byref(1); + static iBrightness; iBrightness = get_param(2); + + @Selection_SetBrightness(g_rgSelections[iSelection], iBrightness); +} + +public Native_GetSelectionPlayer(iPluginId, iArgc) { + static iSelection; iSelection = get_param_byref(1); + + if (!@Selection_IsValid(g_rgSelections[iSelection])) { + log_error(AMX_ERR_NATIVE, NOT_VALID_SELECTION_ERR, iSelection); + return 0; + } + + return g_rgSelections[iSelection][Selection_Player]; +} + +public Native_GetSelectionCursorEntity(iPluginId, iArgc) { + static iSelection; iSelection = get_param_byref(1); + + if (!@Selection_IsValid(g_rgSelections[iSelection])) { + log_error(AMX_ERR_NATIVE, NOT_VALID_SELECTION_ERR, iSelection); + return 0; + } + + return g_rgSelections[iSelection][Selection_CursorEntity]; +} + +public Native_SetSelectionCursorEntity(iPluginId, iArgc) { + static iSelection; iSelection = get_param_byref(1); + static pCursor; pCursor = get_param(2); + + if (!@Selection_IsValid(g_rgSelections[iSelection])) { + log_error(AMX_ERR_NATIVE, NOT_VALID_SELECTION_ERR, iSelection); + return; + } + + g_rgSelections[iSelection][Selection_CursorEntity] = pCursor; +} + +public Native_StartSelection(iPluginId, iArgc) { + static iSelection; iSelection = get_param_byref(1); + + if (!@Selection_IsValid(g_rgSelections[iSelection])) { + log_error(AMX_ERR_NATIVE, NOT_VALID_SELECTION_ERR, iSelection); + return; + } + + if (g_rgSelections[iSelection][Selection_Active]) { + log_error(AMX_ERR_NATIVE, "Cannot start selection! Selection is already started!"); + return; + } + + @Selection_Start(g_rgSelections[iSelection]); +} + +public Native_EndSelection(iPluginId, iArgc) { + static iSelection; iSelection = get_param_byref(1); + + if (!@Selection_IsValid(g_rgSelections[iSelection])) { + log_error(AMX_ERR_NATIVE, NOT_VALID_SELECTION_ERR, iSelection); + return; + } + + if (!g_rgSelections[iSelection][Selection_Active]) { + log_error(AMX_ERR_NATIVE, "Cannot end selection! Selection is not started!"); + return; + } + + @Selection_End(g_rgSelections[iSelection]); +} + +public Native_GetSelectionEntity(iPluginId, iArgc) { + static iSelection; iSelection = get_param_byref(1); + static iIndex; iIndex = get_param(2); + + if (!@Selection_IsValid(g_rgSelections[iSelection])) { + log_error(AMX_ERR_NATIVE, NOT_VALID_SELECTION_ERR, iSelection); + return -1; + } + + if (g_rgSelections[iSelection][Selection_Entities] == Invalid_Array) return -1; + + return ArrayGetCell(g_rgSelections[iSelection][Selection_Entities], iIndex); +} + +public Native_GetSelectionSize(iPluginId, iArgc) { + static iSelection; iSelection = get_param_byref(1); + + if (!@Selection_IsValid(g_rgSelections[iSelection])) { + log_error(AMX_ERR_NATIVE, NOT_VALID_SELECTION_ERR, iSelection); + return 0; + } + + if (g_rgSelections[iSelection][Selection_Entities] == Invalid_Array) return 0; + + return ArraySize(g_rgSelections[iSelection][Selection_Entities]); +} + +public Native_GetSelectionStartPos(iPluginId, iArgc) { + static iSelection; iSelection = get_param_byref(1); + + if (!@Selection_IsValid(g_rgSelections[iSelection])) { + log_error(AMX_ERR_NATIVE, NOT_VALID_SELECTION_ERR, iSelection); + return; + } + + set_array_f(2, g_rgSelections[iSelection][Selection_Start], 3); +} + +public Native_GetSelectionEndPos(iPluginId, iArgc) { + static iSelection; iSelection = get_param_byref(1); + + if (!@Selection_IsValid(g_rgSelections[iSelection])) { + log_error(AMX_ERR_NATIVE, NOT_VALID_SELECTION_ERR, iSelection); + return; + } + + set_array_f(2, g_rgSelections[iSelection][Selection_End], 3); +} + +public Native_GetSelectionCursorPos(iPluginId, iArgc) { + static iSelection; iSelection = get_param_byref(1); + + if (!@Selection_IsValid(g_rgSelections[iSelection])) { + log_error(AMX_ERR_NATIVE, NOT_VALID_SELECTION_ERR, iSelection); + return; + } + + @Selection_CalculateCursorPos(g_rgSelections[iSelection]); + + set_array_f(2, g_rgSelections[iSelection][Selection_Cursor], 3); +} + +public Native_SetSelectionCursorPos(iPluginId, iArgc) { + static iSelection; iSelection = get_param_byref(1); + static Float:vecOrigin[3]; get_array_f(2, vecOrigin, 3); + + if (!@Selection_IsValid(g_rgSelections[iSelection])) { + log_error(AMX_ERR_NATIVE, NOT_VALID_SELECTION_ERR, iSelection); + return; + } + + xs_vec_copy(vecOrigin, g_rgSelections[iSelection][Selection_Cursor]); +} + +/*--------------------------------[ Hooks ]--------------------------------*/ + +public HamHook_Player_PostThink_Post(pPlayer) { + @Player_SelectionsThink(pPlayer); +} + +public FMHook_OnFreeEntPrivateData(pEntity) { + if (!pev_valid(pEntity)) return; + + for (new iSelection = 0; iSelection < MAX_SELECTIONS; ++iSelection) { + if (g_rgSelections[iSelection][Selection_Free]) continue; + + @Selection_RemoveEntity(g_rgSelections[iSelection], pEntity); + } +} + +/*--------------------------------[ Methods ]--------------------------------*/ + +@Player_SelectionsThink(this) { + for (new iSelection = 0; iSelection < MAX_SELECTIONS; ++iSelection) { + if (g_rgSelections[iSelection][Selection_Player] != this) continue; + if (!g_rgSelections[iSelection][Selection_Active]) continue; + + @Selection_Think(g_rgSelections[iSelection]); + } +} + +bool:@Selection_IsValid(rgSelection[Selection]) { + return !rgSelection[Selection_Free]; +} + +@Selection_Init(rgSelection[Selection], pPlayer, iIndex) { + rgSelection[Selection_Index] = iIndex; + rgSelection[Selection_Active] = false; + rgSelection[Selection_Free] = false; + rgSelection[Selection_Player] = pPlayer; + rgSelection[Selection_CursorEntity] = pPlayer; + rgSelection[Selection_FilterCallback][Callback_PluginId] = -1; + rgSelection[Selection_FilterCallback][Callback_FunctionId] = -1; + rgSelection[Selection_Color][0] = 255; + rgSelection[Selection_Color][1] = 255; + rgSelection[Selection_Color][2] = 255; + rgSelection[Selection_Brightness] = 255; + rgSelection[Selection_Entities] = ArrayCreate(); + rgSelection[Selection_NextDraw] = get_gametime(); +} + +@Selection_Free(rgSelection[Selection]) { + if (rgSelection[Selection_Entities] != Invalid_Array) { + ArrayDestroy(rgSelection[Selection_Entities]); + } + + rgSelection[Selection_Index] = -1; + rgSelection[Selection_Active] = false; + rgSelection[Selection_Free] = true; + rgSelection[Selection_Player] = 0; + rgSelection[Selection_CursorEntity] = 0; + rgSelection[Selection_FilterCallback][Callback_PluginId] = -1; + rgSelection[Selection_FilterCallback][Callback_FunctionId] = -1; + rgSelection[Selection_Entities] = Invalid_Array; + rgSelection[Selection_NextDraw] = 0.0; +} + +@Selection_SetFilterFunction(rgSelection[Selection], iFunctionId, iPluginId) { + rgSelection[Selection_FilterCallback][Callback_FunctionId] = iFunctionId; + rgSelection[Selection_FilterCallback][Callback_PluginId] = iPluginId; +} + +@Selection_SetColor(rgSelection[Selection], const rgiColor[3]) { + rgSelection[Selection_Color][0] = rgiColor[0]; + rgSelection[Selection_Color][1] = rgiColor[1]; + rgSelection[Selection_Color][2] = rgiColor[2]; +} + +@Selection_SetBrightness(rgSelection[Selection], iBrightness) { + rgSelection[Selection_Brightness] = iBrightness; +} + +@Selection_Start(rgSelection[Selection]) { + @Selection_CalculateCursorPos(rgSelection); + + xs_vec_copy(rgSelection[Selection_Cursor], rgSelection[Selection_Start]); + + ArrayClear(rgSelection[Selection_Entities]); + + rgSelection[Selection_Active] = true; + rgSelection[Selection_NextDraw] = get_gametime(); +} + +@Selection_End(rgSelection[Selection]) { + if (!rgSelection[Selection_Active]) return; + + xs_vec_copy(rgSelection[Selection_Cursor], rgSelection[Selection_End]); + + UTIL_NormalizeBox(rgSelection[Selection_Start], rgSelection[Selection_End]); + + static Float:flMinz; flMinz = floatmin( + TracePointHeight(rgSelection[Selection_End], -8192.0), + TracePointHeight(rgSelection[Selection_Start], -8192.0) + ); + + static Float:flMaxZ; flMaxZ = floatmax( + TracePointHeight(rgSelection[Selection_End], 8192.0), + TracePointHeight(rgSelection[Selection_Start], 8192.0) + ); + + rgSelection[Selection_Start][2] = flMinz; + rgSelection[Selection_End][2] = flMaxZ; + + @Selection_FindEntities(rgSelection); + + rgSelection[Selection_Active] = false; +} + +@Selection_Think(rgSelection[Selection]) { + if (rgSelection[Selection_NextDraw] <= get_gametime()) { + @Selection_CalculateCursorPos(rgSelection); + @Selection_DrawSelection(rgSelection); + rgSelection[Selection_NextDraw] = get_gametime() + 0.1; + } +} + +bool:@Selection_CalculateCursorPos(rgSelection[Selection]) { + static pCursor; pCursor = rgSelection[Selection_CursorEntity]; + + if (pCursor <= 0) return false; + + static Float:vecOrigin[3]; + static Float:vecAngles[3]; + + if (IS_PLAYER(pCursor)) { + ExecuteHamB(Ham_EyePosition, pCursor, vecOrigin); + pev(pCursor, pev_v_angle, vecAngles); + } else { + pev(pCursor, pev_origin, vecOrigin); + pev(pCursor, pev_angles, vecAngles); + } + + static Float:vecForward[3]; angle_vector(vecAngles, ANGLEVECTOR_FORWARD, vecForward); + static Float:vecEnd[3]; xs_vec_add_scaled(vecOrigin, vecForward, 8192.0, vecEnd); + + engfunc(EngFunc_TraceLine, vecOrigin, vecEnd, DONT_IGNORE_MONSTERS, pCursor, g_pTrace); + + get_tr2(g_pTrace, TR_vecEndPos, rgSelection[Selection_Cursor]); + + return true; +} + +Array:@Selection_FindEntities(rgSelection[Selection]) { + for (new pEntity = 1; pEntity < g_iMaxEntities; ++pEntity) { + if (!pev_valid(pEntity)) continue; + if (!UTIL_IsEntityInBox(pEntity, rgSelection[Selection_Start], rgSelection[Selection_End])) continue; + + static bResult; bResult = true; + + if (rgSelection[Selection_FilterCallback][Callback_FunctionId] != -1) { + callfunc_begin_i(rgSelection[Selection_FilterCallback][Callback_FunctionId], rgSelection[Selection_FilterCallback][Callback_PluginId]); + callfunc_push_int(rgSelection[Selection_Index]); + callfunc_push_int(pEntity); + bResult = callfunc_end(); + } + + if (bResult) { + ArrayPushCell(rgSelection[Selection_Entities], pEntity); + } + } +} + +@Selection_RemoveEntity(rgSelection[Selection], pEntity) { + if (rgSelection[Selection_Entities] == Invalid_Array) return; + + static iIndex; iIndex = ArrayFindValue(rgSelection[Selection_Entities], pEntity); + if (iIndex == -1) return; + + ArrayDeleteItem(rgSelection[Selection_Entities], iIndex); +} + +@Selection_DrawSelection(rgSelection[Selection]) { + static pPlayer; pPlayer = rgSelection[Selection_Player]; + static iModelIndex; iModelIndex = engfunc(EngFunc_ModelIndex, g_szTrailModel); + static Float:flHeight; flHeight = floatmax(rgSelection[Selection_Start][2], rgSelection[Selection_Cursor][2]) + SelectionGroundOffset; + + for (new i = 0; i < 4; ++i) { + engfunc(EngFunc_MessageBegin, MSG_ONE, SVC_TEMPENTITY, Float:{0.0, 0.0, 0.0}, pPlayer); + write_byte(TE_BEAMPOINTS); + + switch (i) { + case 0: { + engfunc(EngFunc_WriteCoord, rgSelection[Selection_Start][0]); + engfunc(EngFunc_WriteCoord, rgSelection[Selection_Start][1]); + } + case 1: { + engfunc(EngFunc_WriteCoord, rgSelection[Selection_Start][0]); + engfunc(EngFunc_WriteCoord, rgSelection[Selection_Start][1]); + } + case 2: { + engfunc(EngFunc_WriteCoord, rgSelection[Selection_Cursor][0]); + engfunc(EngFunc_WriteCoord, rgSelection[Selection_Start][1]); + } + case 3: { + engfunc(EngFunc_WriteCoord, rgSelection[Selection_Start][0]); + engfunc(EngFunc_WriteCoord, rgSelection[Selection_Cursor][1]); + } + } + + engfunc(EngFunc_WriteCoord, flHeight); + + switch (i) { + case 0: { + engfunc(EngFunc_WriteCoord, rgSelection[Selection_Start][0]); + engfunc(EngFunc_WriteCoord, rgSelection[Selection_Cursor][1]); + } + case 1: { + engfunc(EngFunc_WriteCoord, rgSelection[Selection_Cursor][0]); + engfunc(EngFunc_WriteCoord, rgSelection[Selection_Start][1]); + } + case 2: { + engfunc(EngFunc_WriteCoord, rgSelection[Selection_Cursor][0]); + engfunc(EngFunc_WriteCoord, rgSelection[Selection_Cursor][1]); + } + case 3: { + engfunc(EngFunc_WriteCoord, rgSelection[Selection_Cursor][0]); + engfunc(EngFunc_WriteCoord, rgSelection[Selection_Cursor][1]); + } + } + + engfunc(EngFunc_WriteCoord, flHeight); + + write_short(iModelIndex); + write_byte(0); + write_byte(0); + write_byte(1); + write_byte(16); + write_byte(0); + write_byte(rgSelection[Selection_Color][0]); + write_byte(rgSelection[Selection_Color][1]); + write_byte(rgSelection[Selection_Color][2]); + write_byte(rgSelection[Selection_Brightness]); + write_byte(0); + message_end(); + } +} + +/*--------------------------------[ Functions ]--------------------------------*/ + +FindFreeSelection() { + for (new i = 0; i < MAX_SELECTIONS; ++i) { + if (g_rgSelections[i][Selection_Free]) return i; + } + + return -1; +} + +Float:TracePointHeight(const Float:vecOrigin[], Float:flMaxDistance) { + static Float:vecTarget[3]; + xs_vec_set(vecTarget, vecOrigin[0], vecOrigin[1], vecOrigin[2] + flMaxDistance); + + engfunc(EngFunc_TraceLine, vecOrigin, vecTarget, IGNORE_MONSTERS, 0, g_pTrace); + + get_tr2(g_pTrace, TR_vecEndPos, vecTarget); + + return vecTarget[2]; +} + +/*--------------------------------[ Stocks ]--------------------------------*/ + +stock UTIL_IsEntityInBox(pEntity, const Float:vecBoxMin[], const Float:vecBoxMax[]) { + static Float:vecAbsMin[3]; pev(pEntity, pev_absmin, vecAbsMin); + static Float:vecAbsMax[3]; pev(pEntity, pev_absmax, vecAbsMax); + + for (new i = 0; i < 3; ++i) { + if (vecAbsMin[i] > vecBoxMax[i]) return false; + if (vecAbsMax[i] < vecBoxMin[i]) return false; + } + + return true; +} + +stock UTIL_NormalizeBox(Float:vecMin[], Float:vecMax[]) { + for (new i = 0; i < 3; ++i) { + if (vecMin[i] > vecMax[i]) UTIL_FloatSwap(vecMin[i], vecMax[i]); + } +} + +stock UTIL_FloatSwap(&Float:flValue, &Float:flOther) { + static Float:flTemp; + + flTemp = flValue; + flValue = flOther; + flOther = flTemp; +} diff --git a/api/entity-selection/include/api_entity_selection.inc b/api/entity-selection/include/api_entity_selection.inc new file mode 100644 index 0000000..3357f8e --- /dev/null +++ b/api/entity-selection/include/api_entity_selection.inc @@ -0,0 +1,23 @@ +#if defined _api_entity_selection_included + #endinput +#endif +#define _api_entity_selection_included + +#pragma reqlib api_entity_selection + +native Selection:EntitySelection_Create(pPlayer); +native EntitySelection_Destroy(&Selection:selection); +native EntitySelection_Start(const &Selection:selection); +native EntitySelection_SetFilter(const &Selection:selection, const szCallback[]); +native EntitySelection_SetColor(const &Selection:selection, const rgiColor[3]); +native EntitySelection_SetBrightness(const &Selection:selection, iBrightness); +native EntitySelection_End(const &Selection:selection); +native EntitySelection_GetEntity(const &Selection:selection, iIndex); +native EntitySelection_GetSize(const &Selection:selection); +native EntitySelection_GetPlayer(const &Selection:selection); +native EntitySelection_SetCursorEntity(const &Selection:selection, pCursor); +native EntitySelection_GetCursorEntity(const &Selection:selection); +native EntitySelection_SetCursorPos(const &Selection:selection, const Float:vecOrigin[]); +native EntitySelection_GetCursorPos(const &Selection:selection, Float:vecOut[]); +native EntitySelection_GetStartPos(const &Selection:selection, Float:vecOut[]); +native EntitySelection_GetEndPos(const &Selection:selection, Float:vecOut[]); diff --git a/api/navsystem/README.md b/api/navsystem/README.md new file mode 100644 index 0000000..00ddc3f --- /dev/null +++ b/api/navsystem/README.md @@ -0,0 +1,47 @@ +# Example + +```pawn +@Entity_FindPath(this, Float:vecTarget[3]) { + static Float:vecOrigin[3]; + pev(this, pev_origin, vecOrigin); + + new NavBuildPathTask:pTask = Nav_Path_Find(vecOrigin, vecTarget, "NavPathCallback", this, this, "NavPathCost"); + CE_SetMember(this, m_pFindPathTask, pTask); +} + +public NavPathCallback(NavBuildPathTask:pTask) { + new pEntity = Nav_Path_FindTask_GetUserToken(pTask); + new NavPath:pPath = Nav_Path_FindTask_GetPath(pTask); + + new Array:irgPath = CE_GetMember(pEntity, m_irgPath); + ArrayClear(irgPath); + + static iSegmentsNum; iSegmentsNum = Nav_Path_GetSegmentCount(pPath); + + for (new iSegment = 0; iSegment < iSegmentsNum; ++iSegment) { + static Float:vecPos[3]; Nav_Path_GetSegmentPos(pPath, iSegment, vecPos); + + ArrayPushArray(irgPath, vecPos, sizeof(vecPos)); + } + + CE_SetMember(pEntity, m_pFindPathTask, Invalid_NavBuildPathTask); +} + + +public Float:NavPathCost(NavBuildPathTask:pTask, NavArea:newArea, NavArea:prevArea) { + // No jump + if (Nav_Area_GetAttributes(newArea) & NAV_JUMP) return -1.0; + + // No crouch + if (Nav_Area_GetAttributes(newArea) & NAV_CROUCH) return -1.0; + + // Don't go ladders + if (prevArea != Invalid_NavArea) { + new NavTraverseType:iTraverseType = Nav_Area_GetParentHow(prevArea); + if (iTraverseType == GO_LADDER_UP) return -1.0; + if (iTraverseType == GO_LADDER_DOWN) return -1.0; + } + + return 1.0; +} +``` diff --git a/api/navsystem/api_navsystem.sma b/api/navsystem/api_navsystem.sma new file mode 100644 index 0000000..b58db18 --- /dev/null +++ b/api/navsystem/api_navsystem.sma @@ -0,0 +1,3099 @@ +/* + Nav system implementation based on ReGameDLL implementation. + + Nav File Parser + https://github.com/s1lentq/ReGameDLL_CS/blob/master/regamedll/game_shared/bot/nav_file.h + https://github.com/s1lentq/ReGameDLL_CS/blob/master/regamedll/game_shared/bot/nav_file.cpp + + Nav Area Members and Methods + https://github.com/s1lentq/ReGameDLL_CS/blob/master/regamedll/game_shared/bot/nav_area.h + https://github.com/s1lentq/ReGameDLL_CS/blob/master/regamedll/game_shared/bot/nav_area.cpp + + Nav Path Members and Methods + https://github.com/s1lentq/ReGameDLL_CS/blob/master/regamedll/game_shared/bot/nav_path.h + https://github.com/s1lentq/ReGameDLL_CS/blob/master/regamedll/game_shared/bot/nav_path.cpp +*/ + +#pragma semicolon 1 + +#include +#include +#include + +#include + +#include + +#define INVALID_NAV_AREA -1 +#define INVALID_BUILD_PATH_TASK -1 + +enum _:LADDER_TOP_DIR { + LADDER_TOP_DIR_AHEAD = 0, + LADDER_TOP_DIR_LEFT, + LADDER_TOP_DIR_RIGHT, + LADDER_TOP_DIR_BEHIND, + NUM_TOP_DIRECTIONS +}; + +enum Extent { Float:Extent_Lo[3], Float:Extent_Hi[3] }; +enum Ray { Float:Ray_From[3], Float:Ray_To[3]}; +enum NavConnect { NavConnect_Id, NavConnect_Area }; +enum SpotOrder { SpotOrder_Id, Float:SpotOrder_T, Struct:SpotOrder_Spot }; +enum PathSegment { PathSegment_Area, NavTraverseType:PathSegment_How, Float:PathSegment_Pos[3] }; +enum NavPath { Array:NavPath_Segments, NavPath_SegmentCount }; + +enum ApproachInfo { + ApproachInfo_Here[NavConnect], // the approach area + ApproachInfo_Prev[NavConnect], // the area just before the approach area on the path + NavTraverseType:ApproachInfo_PrevToHereHow, + ApproachInfo_Next[NavConnect], // the area just after the approach area on the path + NavTraverseType:ApproachInfo_HereToNextHow +}; + +enum SpotEncounter { + SpotEncounter_From[NavConnect], + NavDirType:SpotEncounter_FromDir, + SpotEncounter_To[NavConnect], + NavDirType:SpotEncounter_ToDir, + SpotEncounter_Path[Ray], // the path segment + Array:SpotEncounter_SpotList // Array[SpotOrder] // list of spots to look at, in order of occurrence +}; + +enum NavAreaGrid { + Float:NavAreaGrid_CellSize, + Array:NavAreaGrid_Grid, + NavAreaGrid_GridSizeX, + NavAreaGrid_GridSizeY, + Float:NavAreaGrid_MinX, + Float:NavAreaGrid_MinY, + NavAreaGrid_AreaCount, + NavAreaGrid_HashTable[HASH_TABLE_SIZE] +}; + +enum NavArea { + NavArea_Index, + NavArea_Id, // unique area ID + NavArea_Extent[Extent], // extents of area in world coords (NOTE: lo[2] is not necessarily the minimum Z, but corresponds to Z at point (lo[0], lo[1]), etc + Float:NavArea_Center[3], // centroid of area + NavAttributeType:NavArea_AttributeFlags, // set of attribute bit flags (see NavAttributeType) + + // height of the implicit corners + Float:NavArea_NeZ, + Float:NavArea_SwZ, + + // encounter spots + Array:NavArea_SpotEncounterList, // Array[SpotEncounter] // list of possible ways to move thru this area, and the spots to look at as we do + + // approach areas + Array:NavArea_Approach, // Array[ApproachInfo] + + // connections to adjacent areas + Array:NavArea_Connect[NUM_DIRECTIONS], // a list of adjacent areas for each direction + Array:NavArea_Ladder[NUM_LADDER_DIRECTIONS], // a list of adjacent areas for each direction + + Array:NavArea_OverlapList, // list of areas that overlap this area + + // connections for grid hash table + NavArea_PrevHash, + NavArea_NextHash, + + NavArea_NextOpen, // only valid if m_openMarker == m_masterMarker + NavArea_PrevOpen, + NavArea_OpenMarker, // if this equals the current marker value, we are on the open list + + // A* pathfinding algorithm + NavArea_Marker, // used to flag the area as visited + NavArea_Parent, // the area just prior to this on in the search path + NavTraverseType:NavArea_ParentHow, // how we get from parent to us + Float:NavArea_TotalCost, // the distance so far plus an estimate of the distance left + Float:NavArea_CostSoFar, // distance travelled so far +}; + +enum Callback { Callback_PluginId, Callback_FunctionId }; + +enum BuildPathTask { + BuildPathTask_Index, + bool:BuildPathTask_IsFree, + Float:BuildPathTask_StartPos[3], + Float:BuildPathTask_GoalPos[3], + Float:BuildPathTask_ActualGoalPos[3], + BuildPathTask_StartArea, + BuildPathTask_GoalArea, + BuildPathTask_CostCallback[Callback], + BuildPathTask_FinishCallback[Callback], + BuildPathTask_IgnoreEntity, + BuildPathTask_UserToken, + Struct:BuildPathTask_Path, + bool:BuildPathTask_IsFinished, + bool:BuildPathTask_IsSuccessed, + bool:BuildPathTask_IsTerminated, + BuildPathTask_IterationsNum +}; + +enum BuildPathJob { + BuildPathJob_Task, + Float:BuildPathJob_ClosestAreaDist, + BuildPathJob_ClosestArea, + bool:BuildPathJob_Finished, + bool:BuildPathJob_Released, + BuildPathJob_MaxIterations +}; + +enum NavLadder { + Float:NavLadder_Top[3], + Float:NavLadder_Bottom[3], + Float:NavLadder_Length, + NavDirType:NavLadder_Dir, + Float:NavLadder_DirVector[2], + NavLadder_Entity, + NavLadder_TopForwardArea, + NavLadder_TopLeftArea, + NavLadder_TopRightArea, + NavLadder_TopBehindArea, + NavLadder_BottomArea, + bool:NavLadder_IsDangling +}; + +const Float:GenerationStepSize = 25.0; +// const Float:StepHeight = 18.0; +const Float:HalfHumanWidth = 16.0; +const Float:HalfHumanHeight = 36.0; +const Float:HumanHeight = 72.0; + +new g_rgGrid[NavAreaGrid]; + +new g_iNavAreaNextId = 0; +new g_iNavAreaMasterMarker = 1; +new g_iNavAreaOpenList = INVALID_NAV_AREA; + +new g_rgBuildPathJob[BuildPathJob]; +new Array:g_irgBuildPathTasksQueue = Invalid_Array; + +new g_pTrace; + +new bool:b_bInitStage = false; +new bool:g_bPrecached = false; +new g_iArrowModelIndex; + +new g_rgNavAreas[MAX_NAV_AREAS][NavArea]; +new g_iNavAreasNum = 0; + +new g_rgBuildPathTasks[MAX_NAV_PATH_TASKS][BuildPathTask]; + +new g_iMaxIterationsPerFrame = 0; +new bool:g_bDebug = false; + +#define NAVAREA_PTR(%1) g_rgNavAreas[%1] +#define NAVAREA_INDEX(%1) %1[NavArea_Index] +#define TASK_PTR(%1) g_rgBuildPathTasks[%1] + +public plugin_precache() { + g_pTrace = create_tr2(); + g_iArrowModelIndex = precache_model("sprites/arrow1.spr"); + + for (new i = 0; i < sizeof(g_rgNavAreas); ++i) { + NAVAREA_PTR(i)[NavArea_Index] = i; + } + + for (new i = 0; i < sizeof(g_rgBuildPathTasks); ++i) { + g_rgBuildPathTasks[i][BuildPathTask_Index] = i; + g_rgBuildPathTasks[i][BuildPathTask_IsFree] = true; + } + + g_rgBuildPathJob[BuildPathJob_Task] = INVALID_BUILD_PATH_TASK; +} + +public plugin_init() { + register_plugin("Nav System", "0.1.0", "Hedgehog Fog"); + + b_bInitStage = true; + + if (g_bPrecached) BuildLadders(); + + bind_pcvar_num(register_cvar("nav_max_iterations_per_frame", "100"), g_iMaxIterationsPerFrame); + bind_pcvar_num(register_cvar("nav_debug", "0"), g_bDebug); +} + +public plugin_end() { + for (new i = 0; i < sizeof(g_rgBuildPathTasks); ++i) { + if (g_rgBuildPathTasks[i][BuildPathTask_Path] != Invalid_Struct) { + @NavPath_Destroy(g_rgBuildPathTasks[i][BuildPathTask_Path]); + } + } + + for (new iArea = 0; iArea < g_iNavAreasNum; ++iArea) { + if (iArea == INVALID_NAV_AREA) continue; + @NavArea_Free(NAVAREA_PTR(iArea)); + } + + NavAreaGrid_Free(); + + if (g_irgBuildPathTasksQueue != Invalid_Array) { + ArrayDestroy(g_irgBuildPathTasksQueue); + } + + free_tr2(g_pTrace); +} + +public plugin_natives() { + register_library("api_navsystem"); + + register_native("Nav_Precache", "Native_Precache"); + + register_native("Nav_GetAreaCount", "Native_GetAreaCount"); + register_native("Nav_GetArea", "Native_GetArea"); + register_native("Nav_GetAreaById", "Native_GetAreaById"); + register_native("Nav_GetAreaFromGrid", "Native_GetAreaFromGrid"); + register_native("Nav_WorldToGridX", "Native_WorldToGridX"); + register_native("Nav_WorldToGridY", "Native_WorldToGridY"); + register_native("Nav_FindFirstAreaInDirection", "Native_FindFirstAreaInDirection"); + register_native("Nav_IsAreaVisible", "Native_IsAreaVisible"); + register_native("Nav_GetNearestArea", "Native_GetNearestNavArea"); + + register_native("Nav_Area_GetAttributes", "Native_Area_GetAttributes"); + register_native("Nav_Area_GetParentHow", "Native_Area_GetParentHow"); + register_native("Nav_Area_GetCenter", "Native_Area_GetCenter"); + register_native("Nav_Area_GetId", "Native_Area_GetId"); + register_native("Nav_Area_Contains", "Native_Area_Contains"); + register_native("Nav_Area_IsCoplanar", "Native_Area_IsCoplanar"); + register_native("Nav_Area_GetZ", "Native_Area_GetZ"); + register_native("Nav_Area_GetClosestPointOnArea", "Native_Area_GetClosestPointOnArea"); + register_native("Nav_Area_GetDistanceSquaredToPoint", "Native_Area_GetDistanceSquaredToPoint"); + register_native("Nav_Area_GetRandomAdjacentArea", "Native_Area_GetRandomAdjacentArea"); + register_native("Nav_Area_IsEdge", "Native_Area_IsEdge"); + register_native("Nav_Area_IsConnected", "Native_Area_IsConnected"); + register_native("Nav_Area_GetCorner", "Native_Area_GetCorner"); + register_native("Nav_Area_ComputeDirection", "Native_Area_ComputeDirection"); + register_native("Nav_Area_ComputePortal", "Native_Area_ComputePortal"); + register_native("Nav_Area_IsOverlapping", "Native_Area_IsOverlapping"); + register_native("Nav_Area_IsOverlappingPoint", "Native_Area_IsOverlappingPoint"); + register_native("Nav_Area_GetCostSoFar", "Native_Area_GetCostSoFar"); + + register_native("Nav_Path_IsValid", "Native_Path_IsValid"); + register_native("Nav_Path_GetSegmentCount", "Native_Path_GetSegmentCount"); + register_native("Nav_Path_GetSegmentPos", "Native_Path_GetSegmentPos"); + register_native("Nav_Path_GetSegmentHow", "Native_Path_GetSegmentHow"); + register_native("Nav_Path_GetSegmentArea", "Native_Path_GetSegmentArea"); + register_native("Nav_Path_FindClosestPoint", "Native_Path_FindClosestPoint"); + + register_native("Nav_Path_Find", "Native_Path_Find"); + register_native("Nav_Path_FindTask_Await", "Native_Path_FindTask_Await"); + register_native("Nav_Path_FindTask_GetUserToken", "Native_Path_FindTask_GetUserToken"); + register_native("Nav_Path_FindTask_Abort", "Native_Path_FindTask_Abort"); + register_native("Nav_Path_FindTask_GetPath", "Native_Path_FindTask_GetPath"); + register_native("Nav_Path_FindTask_IsFinished", "Native_Path_FindTask_IsFinished"); + register_native("Nav_Path_FindTask_IsSuccessed", "Native_Path_FindTask_IsSuccessed"); + register_native("Nav_Path_FindTask_IsTerminated", "Native_Path_FindTask_IsTerminated"); + register_native("Nav_Path_FindTask_GetIterationsNum", "Native_Path_FindTask_GetIterationsNum"); +} + +public server_frame() { + NavAreaBuildPathFrame(); +} + +public Native_Precache(iPluginId, iArgc) { + if (g_bPrecached) { + return; + } + + LoadNavigationMap(); + + g_bPrecached = true; +} + +public Native_GetAreaCount(iPluginId, iArgc) { + return g_iNavAreasNum; +} + +public Native_GetArea(iPluginId, iArgc) { + new iArea = get_param(1); + + return iArea; +} + +public Native_GetAreaById(iPluginId, iArgc) { + new iId = get_param(1); + + return NavAreaGrid_GetNavAreaById(iId); +} + +public Native_GetAreaFromGrid(iPluginId, iArgc) { + static Float:vecPos[3]; get_array_f(1, vecPos, sizeof(vecPos)); + new Float:flBeneathLimit = get_param_f(2); + + return NavAreaGrid_GetNavArea(vecPos, flBeneathLimit); +} + +public Native_WorldToGridX(iPluginId, iArgc) { + new Float:flValue = get_param_f(1); + + return NavAreaGrid_WorldToGridX(flValue); +} + +public Native_WorldToGridY(iPluginId, iArgc) { + new Float:flValue = get_param_f(1); + + return NavAreaGrid_WorldToGridY(flValue); +} +public Native_FindFirstAreaInDirection(iPluginId, iArgc) { + static Float:vecStart[3]; get_array_f(1, vecStart, sizeof(vecStart)); + + new NavDirType:iDir = NavDirType:get_param(2); + new Float:flRange = get_param_f(3); + new Float:flBeneathLimit = get_param_f(4); + new pIgnoreEnt = get_param(5); + + static Float:vecClosePos[3]; + new iArea = FindFirstAreaInDirection(vecStart, iDir, flRange, flBeneathLimit, pIgnoreEnt, vecClosePos); + set_array_f(6, vecClosePos, sizeof(vecClosePos)); + + return iArea; +} + +public bool:Native_IsAreaVisible(iPluginId, iArgc) { + static Float:vecPos[3]; get_array_f(1, vecPos, sizeof(vecPos)); + + new iArea = get_param(2); + + return IsAreaVisible(vecPos, NAVAREA_PTR(iArea)); +} + +public Native_GetNearestNavArea(iPluginId, iArgc) { + static Float:vecPos[3]; get_array_f(1, vecPos, sizeof(vecPos)); + static bool:bAnyZ; bAnyZ = bool:get_param(2); + static pIgnoreEnt; pIgnoreEnt = get_param(3); + static iIgnoreArea; iIgnoreArea = get_param(4); + + return NavAreaGrid_GetNearestNavArea(vecPos, bAnyZ, pIgnoreEnt, iIgnoreArea); +} + +public Native_Path_Find(iPluginId, iArgc) { + static Float:vecStart[3]; get_array_f(1, vecStart, sizeof(vecStart)); + static Float:vecGoal[3]; get_array_f(2, vecGoal, sizeof(vecGoal)); + static szCbFunction[32]; get_string(3, szCbFunction, charsmax(szCbFunction)); + static pIgnoreEnt; pIgnoreEnt = get_param(4); + static iUserToken; iUserToken = get_param(5); + static szCostFunction[32]; get_string(6, szCostFunction, charsmax(szCostFunction)); + + static iCostFuncId; iCostFuncId = equal(szCostFunction, NULL_STRING) ? -1 : get_func_id(szCostFunction, iPluginId); + static iCbFuncId; iCbFuncId = equal(szCbFunction, NULL_STRING) ? -1 : get_func_id(szCbFunction, iPluginId); + + return NavAreaBuildPath(vecStart, vecGoal, iCbFuncId, iPluginId, pIgnoreEnt, iUserToken, iCostFuncId, iPluginId); +} + +public NavAttributeType:Native_Area_GetAttributes(iPluginId, iArgc) { + new iArea = get_param(1); + + return @NavArea_GetAttributes(NAVAREA_PTR(iArea)); +} + +public NavTraverseType:Native_Area_GetParentHow(iPluginId, iArgc) { + new iArea = get_param(1); + + return @NavArea_GetParentHow(NAVAREA_PTR(iArea)); +} + +public Native_Area_GetCenter(iPluginId, iArgc) { + new iArea = get_param(1); + static Float:vecCenter[3]; @NavArea_GetCenter(NAVAREA_PTR(iArea), vecCenter); + + set_array_f(2, vecCenter, sizeof(vecCenter)); +} + +public Native_Path_FindTask_Await(iPluginId, iArgc) { + static iTask; iTask = get_param(1); + + if (g_rgBuildPathTasks[iTask][BuildPathTask_IsFree]) { + return; + } + + while ( + g_rgBuildPathJob[BuildPathJob_Task] != iTask || + !g_rgBuildPathJob[BuildPathJob_Finished] + ) { + NavAreaBuildPathFrame(); + } + + // g_rgBuildPathTasks[iTask][BuildPathTask_IsFree] = true; +} + +public bool:Native_Path_FindTask_IsFinished(iPluginId, iArgc) { + new iTask = get_param(1); + + return g_rgBuildPathTasks[iTask][BuildPathTask_IsFinished]; +} + +public Native_Path_FindTask_GetUserToken(iPluginId, iArgc) { + new iTask = get_param(1); + + return g_rgBuildPathTasks[iTask][BuildPathTask_UserToken]; +} + +public bool:Native_Path_FindTask_Abort(iPluginId, iArgc) { + new iTask = get_param(1); + + return NavAreaBuildPathAbortTask(TASK_PTR(iTask)); +} + +public Struct:Native_Path_FindTask_GetPath(iPluginId, iArgc) { + new iTask = get_param(1); + + return g_rgBuildPathTasks[iTask][BuildPathTask_Path]; +} + +public bool:Native_Path_FindTask_IsSuccessed(iPluginId, iArgc) { + new iTask = get_param(1); + + return g_rgBuildPathTasks[iTask][BuildPathTask_IsSuccessed]; +} + +public bool:Native_Path_FindTask_IsTerminated(iPluginId, iArgc) { + new iTask = get_param(1); + + return g_rgBuildPathTasks[iTask][BuildPathTask_IsTerminated]; +} + +public Native_Path_FindTask_GetIterationsNum(iPluginId, iArgc) { + new iTask = get_param(1); + + return g_rgBuildPathTasks[iTask][BuildPathTask_IterationsNum]; +} + +public bool:Native_Path_IsValid(iPluginId, iArgc) { + static Struct:sNavPath; sNavPath = Struct:get_param(1); + + return @NavPath_IsValid(sNavPath); +} + +public Array:Native_Path_GetSegmentCount(iPluginId, iArgc) { + static Struct:sNavPath; sNavPath = Struct:get_param(1); + + return StructGetCell(sNavPath, NavPath_SegmentCount); +} + +public Native_Path_GetSegmentPos(iPluginId, iArgc) { + static Struct:sNavPath; sNavPath = Struct:get_param(1); + static iSegment; iSegment = get_param(2); + + static Array:irgSegments; irgSegments = StructGetCell(sNavPath, NavPath_Segments); + + static Float:vecPos[3]; ArrayGetArray2(irgSegments, iSegment, vecPos, 3, PathSegment_Pos); + + set_array_f(3, vecPos, sizeof(vecPos)); +} + +public NavTraverseType:Native_Path_GetSegmentHow(iPluginId, iArgc) { + static Struct:sNavPath; sNavPath = Struct:get_param(1); + static iSegment; iSegment = get_param(2); + + static Array:irgSegments; irgSegments = StructGetCell(sNavPath, NavPath_Segments); + + return ArrayGetCell(irgSegments, iSegment, _:PathSegment_How); +} + +public NavTraverseType:Native_Path_GetSegmentArea(iPluginId, iArgc) { + static Struct:sNavPath; sNavPath = Struct:get_param(1); + static iSegment; iSegment = get_param(2); + + static Array:irgSegments; irgSegments = StructGetCell(sNavPath, NavPath_Segments); + + return ArrayGetCell(irgSegments, iSegment, _:PathSegment_Area); +} + +public Native_Path_FindClosestPoint(iPluginId, iArgc) { + static Struct:sNavPath; sNavPath = Struct:get_param(1); + static Float:vecWorldPos[3]; get_array_f(2, vecWorldPos, sizeof(vecWorldPos)); + static iStartIndex; iStartIndex = get_param(3); + static iEndIndex; iEndIndex = get_param(4); + + static Float:vecClose[3]; + if (!@NavPath_FindClosestPointOnPath(sNavPath, vecWorldPos, iStartIndex, iEndIndex, vecClose)) { + return false; + } + + set_array_f(5, vecClose, sizeof(vecClose)); + + return true; +} + +public Native_Area_GetId(iPluginId, iArgc) { + new iArea = get_param(1); + + return @NavArea_GetId(NAVAREA_PTR(iArea)); +} + +public bool:Native_Area_Contains(iPluginId, iArgc) { + new iArea = get_param(1); + static Float:vecPoint[3]; get_array_f(2, vecPoint, sizeof(vecPoint)); + + return @NavArea_Contains(NAVAREA_PTR(iArea), vecPoint); +} + +public bool:Native_Area_IsCoplanar(iPluginId, iArgc) { + new iArea = get_param(1); + new iOtherArea = get_param(2); + + return @NavArea_IsCoplanar(NAVAREA_PTR(iArea), iOtherArea); +} + +public Float:Native_Area_GetZ(iPluginId, iArgc) { + new iArea = get_param(1); + static Float:vecPos[3]; get_array_f(2, vecPos, sizeof(vecPos)); + + return @NavArea_GetZ(NAVAREA_PTR(iArea), vecPos); +} + +public Native_Area_GetClosestPointOnArea(iPluginId, iArgc) { + new iArea = get_param(1); + static Float:vecPos[3]; get_array_f(2, vecPos, sizeof(vecPos)); + + static Float:vecClose[3]; + @NavArea_GetClosestPointOnArea(NAVAREA_PTR(iArea), vecPos, vecClose); + + set_array_f(3, vecClose, sizeof(vecClose)); +} + +public Float:Native_Area_GetDistanceSquaredToPoint(iPluginId, iArgc) { + new iArea = get_param(1); + static Float:vecPoint[3]; get_array_f(2, vecPoint, sizeof(vecPoint)); + + return @NavArea_GetDistanceSquaredToPoint(NAVAREA_PTR(iArea), vecPoint); +} + +public Native_Area_GetRandomAdjacentArea(iPluginId, iArgc) { + new iArea = get_param(1); + new NavDirType:iDir = NavDirType:get_param(2); + + return @NavArea_GetRandomAdjacentArea(NAVAREA_PTR(iArea), iDir); +} + +public bool:Native_Area_IsEdge(iPluginId, iArgc) { + new iArea = get_param(1); + new NavDirType:iDir = NavDirType:get_param(2); + + return @NavArea_IsEdge(NAVAREA_PTR(iArea), iDir); +} + +public bool:Native_Area_IsConnected(iPluginId, iArgc) { + new iArea = get_param(1); + new iOtherArea = get_param(2); + new NavDirType:iDir = NavDirType:get_param(3); + + return @NavArea_IsConnected(NAVAREA_PTR(iArea), NAVAREA_PTR(iOtherArea), iDir); +} + +public Native_Area_GetCorner(iPluginId, iArgc) { + new iArea = get_param(1); + new NavCornerType:iCorner = NavCornerType:get_param(2); + static Float:vecPos[3]; get_array_f(3, vecPos, sizeof(vecPos)); + + @NavArea_GetCorner(NAVAREA_PTR(iArea), iCorner, vecPos); + + set_array_f(3, vecPos, sizeof(vecPos)); +} + +public NavDirType:Native_Area_ComputeDirection(iPluginId, iArgc) { + new iArea = get_param(1); + static Float:vecPoint[3]; get_array_f(2, vecPoint, sizeof(vecPoint)); + + return @NavArea_ComputeDirection(NAVAREA_PTR(iArea), vecPoint); +} + +public Native_Area_ComputePortal(iPluginId, iArgc) { + new iArea = get_param(1); + new iOtherArea = get_param(2); + new NavDirType:iDir = NavDirType:get_param(3); + + static Float:vecCenter[3]; + static Float:flHalfWidth; + + @NavArea_ComputePortal(NAVAREA_PTR(iArea), NAVAREA_PTR(iOtherArea), iDir, vecCenter, flHalfWidth); + + set_array_f(4, vecCenter, sizeof(vecCenter)); + set_float_byref(5, flHalfWidth); +} + +public bool:Native_Area_IsOverlapping(iPluginId, iArgc) { + new iArea = get_param(1); + new iOtherArea = get_param(2); + + return @NavArea_IsOverlapping(NAVAREA_PTR(iArea), iOtherArea); +} + +public bool:Native_Area_IsOverlappingPoint(iPluginId, iArgc) { + new iArea = get_param(1); + static Float:vecPoint[3]; get_array_f(3, vecPoint, sizeof(vecPoint)); + + return @NavArea_IsOverlappingPoint(NAVAREA_PTR(iArea), vecPoint); +} + +public Float:Native_Area_GetCostSoFar(iPluginId, iArgc) { + new iArea = get_param(1); + + return @NavArea_GetCostSoFar(NAVAREA_PTR(iArea)); +} + +@NavArea_Allocate(this[NavArea]) { + this[NavArea_PrevHash] = INVALID_NAV_AREA; + this[NavArea_NextHash] = INVALID_NAV_AREA; + this[NavArea_PrevOpen] = INVALID_NAV_AREA; + this[NavArea_NextOpen] = INVALID_NAV_AREA; + this[NavArea_Parent] = INVALID_NAV_AREA; + this[NavArea_SpotEncounterList] = ArrayCreate(_:SpotEncounter); + this[NavArea_Approach] = ArrayCreate(_:ApproachInfo); + this[NavArea_OverlapList] = ArrayCreate(); + + for (new NavDirType:d = NORTH; d < NUM_DIRECTIONS; d++) { + this[NavArea_Connect][d] = ArrayCreate(_:NavConnect); + } + + for (new LadderDirectionType:d = LADDER_UP; d < NUM_LADDER_DIRECTIONS; d++) { + this[NavArea_Ladder][d] = ArrayCreate(_:NavLadder); + } +} + +@NavArea_Free(this[NavArea]) { + new Array:irgSpotEncounterList = this[NavArea_SpotEncounterList]; + + new iSpotEncounterListSize = ArraySize(irgSpotEncounterList); + for (new i = 0; i < iSpotEncounterListSize; ++i) { + new Array:irgSpotList = ArrayGetCell(irgSpotEncounterList, i, _:SpotEncounter_SpotList); + ArrayDestroy(irgSpotList); + } + + ArrayDestroy(irgSpotEncounterList); + + ArrayDestroy(this[NavArea_Approach]); + ArrayDestroy(this[NavArea_OverlapList]); + + for (new NavDirType:d = NORTH; d < NUM_DIRECTIONS; d++) { + ArrayDestroy(this[NavArea_Connect][d]); + } + + for (new LadderDirectionType:d = LADDER_UP; d < NUM_LADDER_DIRECTIONS; d++) { + ArrayDestroy(this[NavArea_Ladder][d]); + } +} + +@NavArea_Load(this[NavArea], iFile, iVersion, bool:bDebug) { + new Array:irgSpotEncounterList = this[NavArea_SpotEncounterList]; + new Array:irgApproachList = this[NavArea_Approach]; + + // load ID + new iId; + FileReadInt32(iFile, iId); + this[NavArea_Id] = iId; + + // update nextID to avoid collisions + if (iId >= g_iNavAreaNextId) { + g_iNavAreaNextId = iId + 1; + } + + // load attribute flags + FileReadUint8(iFile, this[NavArea_AttributeFlags]); + + // load extent of area + fread_blocks(iFile, this[NavArea_Extent][Extent_Lo], 3, BLOCK_INT); + fread_blocks(iFile, this[NavArea_Extent][Extent_Hi], 3, BLOCK_INT); + + for (new i = 0; i < 3; ++i) { + this[NavArea_Center][i] = (this[NavArea_Extent][Extent_Lo][i] + this[NavArea_Extent][Extent_Hi][i]) / 2.0; + } + + + // load heights of implicit corners + + FileReadInt32(iFile, this[NavArea_NeZ]); + + FileReadInt32(iFile, this[NavArea_SwZ]); + + // load connections (IDs) to adjacent areas + // in the enum order NORTH, EAST, SOUTH, WEST + for (new NavDirType:d = NORTH; d < NUM_DIRECTIONS; d++) { + // load number of connections for this direction + new iConnectionCount; FileReadInt32(iFile, iConnectionCount); + + for (new i = 0; i < iConnectionCount; i++) { + new rgConnect[NavConnect]; + FileReadInt32(iFile, rgConnect[NavConnect_Id]); + ArrayPushArray(Array:this[NavArea_Connect][d], rgConnect[any:0]); + } + } + + // load number of hiding spots + new iHidingSpotCount; + FileReadUint8(iFile, iHidingSpotCount); + + // skip hiding spots + if (iVersion == 1) { + fseek(iFile, iHidingSpotCount * (3 * BLOCK_INT), SEEK_CUR); + } else { + fseek(iFile, iHidingSpotCount * (BLOCK_INT + (3 * BLOCK_INT) + BLOCK_CHAR), SEEK_CUR); + } + + // Load number of approach areas + new iApproachCount; + FileReadUint8(iFile, iApproachCount); + + // load approach area info (IDs) + for (new a = 0; a < iApproachCount; a++) { + new rgApproach[ApproachInfo]; + FileReadInt32(iFile, rgApproach[ApproachInfo_Here][NavConnect_Id]); + FileReadInt32(iFile, rgApproach[ApproachInfo_Prev][NavConnect_Id]); + FileReadUint8(iFile, rgApproach[ApproachInfo_PrevToHereHow]); + FileReadInt32(iFile, rgApproach[ApproachInfo_Next][NavConnect_Id]); + FileReadUint8(iFile, rgApproach[ApproachInfo_HereToNextHow]); + ArrayPushArray(irgApproachList, rgApproach[any:0]); + } + + // Load encounter paths for this area + new iEncounterCount; + FileReadInt32(iFile, iEncounterCount); + + for (new e = 0; e < iEncounterCount; e++) { + new rgEncounter[SpotEncounter]; + rgEncounter[SpotEncounter_SpotList] = ArrayCreate(_:SpotOrder); + + FileReadInt32(iFile, rgEncounter[SpotEncounter_From][NavConnect_Id]); + + if (iVersion < 3) { + FileReadInt32(iFile, rgEncounter[SpotEncounter_To][NavConnect_Id]); + fread_blocks(iFile, rgEncounter[SpotEncounter_Path][Ray_From], 3, BLOCK_INT); + fread_blocks(iFile, rgEncounter[SpotEncounter_Path][Ray_To], 3, BLOCK_INT); + } else { + FileReadUint8(iFile, rgEncounter[SpotEncounter_FromDir]); + FileReadInt32(iFile, rgEncounter[SpotEncounter_To][NavConnect_Id]); + FileReadUint8(iFile, rgEncounter[SpotEncounter_ToDir]); + } + + // read list of spots along this path + new iSpotCount; + FileReadUint8(iFile, iSpotCount); + + if (iVersion < 3) { + for (new s = 0; s < iSpotCount; s++) { + static Float:vecPos[3]; + fread_blocks(iFile, vecPos, 3, BLOCK_INT); + FileReadInt32(iFile, vecPos[0]); + } + } else { + for (new s = 0; s < iSpotCount; s++) { + new rgOrder[SpotOrder]; + FileReadInt32(iFile, rgOrder[SpotOrder_Id]); + + FileReadUint8(iFile, rgOrder[SpotOrder_T]); + rgOrder[SpotOrder_T] /= 255.0; + ArrayPushCell(rgEncounter[SpotEncounter_SpotList], rgOrder[any:0]); + } + } + + // old data, discard + if (iVersion >= 3) { + ArrayPushArray(irgSpotEncounterList, rgEncounter[any:0]); + } + } + + if (iVersion < NAV_VERSION) { + return; + } + + fseek(iFile, BLOCK_SHORT, SEEK_CUR); +} + +NavErrorType:@NavArea_PostLoadArea(const this[NavArea]) { + new NavErrorType:error = NAV_OK; + + // connect areas together + for (new NavDirType:d = NORTH; d < NUM_DIRECTIONS; d++) { + new Array:irgConnections = Array:this[NavArea_Connect][d]; + new iConnectionCount = ArraySize(irgConnections); + + for (new i = 0; i < iConnectionCount; ++i) { + new iConnectId = ArrayGetCell(irgConnections, i, _:NavConnect_Id); + new iArea = NavAreaGrid_GetNavAreaById(iConnectId); + ArraySetCell(irgConnections, i, iArea, _:NavConnect_Area); + + if (iConnectId && iArea == INVALID_NAV_AREA) { + log_amx("ERROR: Corrupt navigation data. Cannot connect Navigation Areas.^n"); + error = NAV_CORRUPT_DATA; + } + } + } + + // resolve approach area IDs + new Array:irgApproachList = this[NavArea_Approach]; + new iApproachCount = ArraySize(irgApproachList); + for (new a = 0; a < iApproachCount; a++) { + new iApproachHereId = ArrayGetCell(irgApproachList, a, _:ApproachInfo_Here + _:NavConnect_Id); + new iApproachHereArea = NavAreaGrid_GetNavAreaById(iApproachHereId); + ArraySetCell(irgApproachList, a, iApproachHereArea, _:ApproachInfo_Here + _:NavConnect_Area); + if (iApproachHereId && iApproachHereArea == INVALID_NAV_AREA) { + log_amx("ERROR: Corrupt navigation data. Missing Approach Area (here).^n"); + error = NAV_CORRUPT_DATA; + } + + new iApproachPrevId = ArrayGetCell(irgApproachList, a, _:ApproachInfo_Prev + _:NavConnect_Id); + new iApproachPrevArea = NavAreaGrid_GetNavAreaById(iApproachPrevId); + ArraySetCell(irgApproachList, a, iApproachPrevArea, _:ApproachInfo_Prev + _:NavConnect_Area); + if (iApproachPrevId && iApproachPrevArea == INVALID_NAV_AREA) { + log_amx("ERROR: Corrupt navigation data. Missing Approach Area (prev).^n"); + error = NAV_CORRUPT_DATA; + } + + new iApproachNextId = ArrayGetCell(irgApproachList, a, _:ApproachInfo_Next + _:NavConnect_Id); + new iApproachNextArea = NavAreaGrid_GetNavAreaById(iApproachNextId); + ArraySetCell(irgApproachList, a, iApproachNextArea, _:ApproachInfo_Next + _:NavConnect_Area); + if (iApproachNextId && iApproachNextArea == INVALID_NAV_AREA) { + log_amx("ERROR: Corrupt navigation data. Missing Approach Area (next).^n"); + error = NAV_CORRUPT_DATA; + } + } + + // resolve spot encounter IDs + new Array:irgSpotEncounterList = this[NavArea_SpotEncounterList]; + new iSpotEncounterCount = ArraySize(irgSpotEncounterList); + for (new e = 0; e < iSpotEncounterCount; e++) { + new rgSpot[SpotEncounter]; ArrayGetArray(irgSpotEncounterList, e, rgSpot[any:0]); + + rgSpot[SpotEncounter_From][NavConnect_Area] = NavAreaGrid_GetNavAreaById(rgSpot[SpotEncounter_From][NavConnect_Id]); + if (rgSpot[SpotEncounter_From][NavConnect_Area] == INVALID_NAV_AREA) { + log_amx("ERROR: Corrupt navigation data. Missing ^"from^" Navigation Area for Encounter Spot.^n"); + error = NAV_CORRUPT_DATA; + } + + rgSpot[SpotEncounter_To][NavConnect_Area] = NavAreaGrid_GetNavAreaById(rgSpot[SpotEncounter_To][NavConnect_Id]); + if (rgSpot[SpotEncounter_To][NavConnect_Area] == INVALID_NAV_AREA) { + log_amx("ERROR: Corrupt navigation data. Missing ^"to^" Navigation Area for Encounter Spot.^n"); + error = NAV_CORRUPT_DATA; + } + + if (rgSpot[SpotEncounter_From][NavConnect_Area] != INVALID_NAV_AREA && rgSpot[SpotEncounter_To][NavConnect_Area] != INVALID_NAV_AREA) { + // compute path + new Float:flHalfWidth; + @NavArea_ComputePortal(this, NAVAREA_PTR(rgSpot[SpotEncounter_To][NavConnect_Area]), rgSpot[SpotEncounter_ToDir], rgSpot[SpotEncounter_Path][Ray_To], flHalfWidth); + @NavArea_ComputePortal(this, NAVAREA_PTR(rgSpot[SpotEncounter_From][NavConnect_Area]), rgSpot[SpotEncounter_FromDir], rgSpot[SpotEncounter_Path][Ray_From], flHalfWidth); + + new Float:eyeHeight = HalfHumanHeight; + rgSpot[SpotEncounter_Path][Ray_From][2] = @NavArea_GetZ( + g_rgNavAreas[rgSpot[SpotEncounter_From][NavConnect_Area]], + rgSpot[SpotEncounter_Path][Ray_From] + ) + eyeHeight; + + rgSpot[SpotEncounter_Path][Ray_To][2] = @NavArea_GetZ( + g_rgNavAreas[rgSpot[SpotEncounter_To][NavConnect_Area]], + rgSpot[SpotEncounter_Path][Ray_To] + ) + eyeHeight; + } + + ArraySetArray(irgSpotEncounterList, e, rgSpot[any:0]); + } + + // build overlap list + new Array:irgOverlapList = this[NavArea_OverlapList]; + + for (new iArea = 0; iArea < g_iNavAreasNum; ++iArea) { + if (iArea == NAVAREA_INDEX(this)) continue; + + if (@NavArea_IsOverlapping(this, iArea)) { + ArrayPushCell(irgOverlapList, iArea); + } + } + + return error; +} + +@NavArea_GetId(const this[NavArea]) { + return this[NavArea_Id]; +} + +NavAttributeType:@NavArea_GetAttributes(const this[NavArea]) { + return this[NavArea_AttributeFlags]; +} + +@NavArea_GetParent(const this[NavArea]) { + return this[NavArea_Parent]; +} + +NavTraverseType:@NavArea_GetParentHow(const this[NavArea]) { + return this[NavArea_ParentHow]; +} + +bool:@NavArea_IsClosed(const this[NavArea]) { + return @NavArea_IsMarked(this) && !@NavArea_IsOpen(this); +} + +bool:@NavArea_IsOpen(const this[NavArea]) { + return this[NavArea_OpenMarker] == g_iNavAreaMasterMarker; +} + +@NavArea_Mark(this[NavArea]) { + this[NavArea_Marker] = g_iNavAreaMasterMarker; +} + +bool:@NavArea_IsMarked(const this[NavArea]) { + return this[NavArea_Marker] == g_iNavAreaMasterMarker; +} + +@NavArea_GetCenter(const this[NavArea], Float:vecCenter[]) { + xs_vec_copy(this[NavArea_Center], vecCenter); +} + +@NavArea_SetTotalCost(this[NavArea], Float:flTotalCost) { + this[NavArea_TotalCost] = flTotalCost; +} + +Float:@NavArea_GetTotalCost(const this[NavArea]) { + return this[NavArea_TotalCost]; +} + +@NavArea_SetCostSoFar(this[NavArea], Float:flTotalCost) { + this[NavArea_CostSoFar] = flTotalCost; +} + +Float:@NavArea_GetCostSoFar(const this[NavArea]) { + return this[NavArea_CostSoFar]; +} + +@NavArea_AddToClosedList(this[NavArea]) { + @NavArea_Mark(this); +} + +@NavArea_RemoveFromClosedList(const this[NavArea]) { + // since "closed" is defined as visited (marked) and not on open list, do nothing +} + +@NavArea_AddLadderUp(const this[NavArea], const rgNavLadder[NavLadder]) { + ArrayPushArray(Array:this[NavArea_Ladder][LADDER_UP], rgNavLadder[any:0]); +} + +@NavArea_AddLadderDown(const this[NavArea], const rgNavLadder[NavLadder]) { + ArrayPushArray(Array:this[NavArea_Ladder][LADDER_DOWN], rgNavLadder[any:0]); +} + +@NavArea_SetParent(this[NavArea], iParentArea, NavTraverseType:how) { + this[NavArea_Parent] = iParentArea; + this[NavArea_ParentHow] = how; +} + +Array:@NavArea_GetAdjacentList(const this[NavArea], NavDirType:iDir) { + return this[NavArea_Connect][iDir]; +} + +@NavArea_AddToOpenList(this[NavArea]) { + // mark as being on open list for quick check + this[NavArea_OpenMarker] = g_iNavAreaMasterMarker; + + // if list is empty, add and return + if (g_iNavAreaOpenList == INVALID_NAV_AREA) { + g_iNavAreaOpenList = NAVAREA_INDEX(this); + this[NavArea_PrevOpen] = INVALID_NAV_AREA; + this[NavArea_NextOpen] = INVALID_NAV_AREA; + return; + } + + // insert self in ascending cost order + static iArea; iArea = INVALID_NAV_AREA; + static iLastArea; iLastArea = INVALID_NAV_AREA; + + for (iArea = g_iNavAreaOpenList; iArea != INVALID_NAV_AREA; iArea = NAVAREA_PTR(iArea)[NavArea_NextOpen]) { + if (@NavArea_GetTotalCost(this) < @NavArea_GetTotalCost(NAVAREA_PTR(iArea))) { + break; + } + + iLastArea = iArea; + } + + if (iArea != INVALID_NAV_AREA) { + // insert before this area + static iPrevOpenArea; iPrevOpenArea = NAVAREA_PTR(iArea)[NavArea_PrevOpen]; + this[NavArea_PrevOpen] = iPrevOpenArea; + + if (iPrevOpenArea != INVALID_NAV_AREA) { + NAVAREA_PTR(iPrevOpenArea)[NavArea_NextOpen] = NAVAREA_INDEX(this); + } else { + g_iNavAreaOpenList = NAVAREA_INDEX(this); + } + + this[NavArea_NextOpen] = iArea; + NAVAREA_PTR(iArea)[NavArea_PrevOpen] = NAVAREA_INDEX(this); + } else { + // append to end of list + NAVAREA_PTR(iLastArea)[NavArea_NextOpen] = NAVAREA_INDEX(this); + this[NavArea_PrevOpen] = iLastArea; + this[NavArea_NextOpen] = INVALID_NAV_AREA; + } +} + +@NavArea_UpdateOnOpenList(this[NavArea]) { + // since value can only decrease, bubble this area up from current spot + static iPrevOpenArea; iPrevOpenArea = this[NavArea_PrevOpen]; + + while (this[NavArea_PrevOpen] != INVALID_NAV_AREA) { + if (@NavArea_GetTotalCost(this) >= @NavArea_GetTotalCost(NAVAREA_PTR(iPrevOpenArea))) break; + + // swap position with predecessor + static iOtherArea; iOtherArea = this[NavArea_PrevOpen]; + static iBeforeArea; iBeforeArea = NAVAREA_PTR(iOtherArea)[NavArea_PrevOpen]; + static iAfterArea; iAfterArea = this[NavArea_NextOpen]; + + this[NavArea_NextOpen] = iOtherArea; + this[NavArea_PrevOpen] = iBeforeArea; + + NAVAREA_PTR(iOtherArea)[NavArea_PrevOpen] = NAVAREA_INDEX(this); + NAVAREA_PTR(iOtherArea)[NavArea_NextOpen] = iAfterArea; + + if (iBeforeArea != INVALID_NAV_AREA) { + NAVAREA_PTR(iBeforeArea)[NavArea_NextOpen] = NAVAREA_INDEX(this); + } else { + g_iNavAreaOpenList = NAVAREA_INDEX(this); + } + + if (iAfterArea != INVALID_NAV_AREA) { + NAVAREA_PTR(iAfterArea)[NavArea_PrevOpen] = iOtherArea; + } + } +} + +@NavArea_RemoveFromOpenList(this[NavArea]) { + static iPrevOpenArea; iPrevOpenArea = this[NavArea_PrevOpen]; + static iNextOpenArea; iNextOpenArea = this[NavArea_NextOpen]; + + if (iPrevOpenArea != INVALID_NAV_AREA) { + NAVAREA_PTR(iPrevOpenArea)[NavArea_NextOpen] = iNextOpenArea; + } else { + g_iNavAreaOpenList = iNextOpenArea; + } + + if (iNextOpenArea != INVALID_NAV_AREA) { + NAVAREA_PTR(iNextOpenArea)[NavArea_PrevOpen] = iPrevOpenArea; + } + + // zero is an invalid marker + this[NavArea_OpenMarker] = 0; +} + +bool:@NavArea_IsOverlappingPoint(const this[NavArea], const Float:vecPoint[]) { + if ( + vecPoint[0] >= this[NavArea_Extent][Extent_Lo][0] && + vecPoint[0] <= this[NavArea_Extent][Extent_Hi][0] && + vecPoint[1] >= this[NavArea_Extent][Extent_Lo][1] && + vecPoint[1] <= this[NavArea_Extent][Extent_Hi][1] + ) { + return true; + } + + return false; +} + +// Return true if 'area' overlaps our 2D extents +bool:@NavArea_IsOverlapping(const this[NavArea], iArea) { + if ( + NAVAREA_PTR(iArea)[NavArea_Extent][Extent_Lo][0] < this[NavArea_Extent][Extent_Hi][0] && + NAVAREA_PTR(iArea)[NavArea_Extent][Extent_Hi][0] > this[NavArea_Extent][Extent_Lo][0] && + NAVAREA_PTR(iArea)[NavArea_Extent][Extent_Lo][1] < this[NavArea_Extent][Extent_Hi][1] && + NAVAREA_PTR(iArea)[NavArea_Extent][Extent_Hi][1] > this[NavArea_Extent][Extent_Lo][1] + ) { + return true; + } + + return false; +} + +// Return true if given point is on or above this area, but no others +bool:@NavArea_Contains(const this[NavArea], const Float:vecPos[]) { + // check 2D overlap + if (!@NavArea_IsOverlappingPoint(this, vecPos)) return false; + + // the point overlaps us, check that it is above us, but not above any areas that overlap us + new Float:flOurZ = @NavArea_GetZ(this, vecPos); + + // if we are above this point, fail + if (flOurZ > vecPos[2]) return false; + + new Array:irgOverlapList = this[NavArea_OverlapList]; + new iOverlapListSize = ArraySize(irgOverlapList); + + for (new i = 0; i < iOverlapListSize; ++i) { + new iOtherArea = ArrayGetCell(irgOverlapList, i); + + // skip self + if (iOtherArea == NAVAREA_INDEX(this)) continue; + + // check 2D overlap + if (!@NavArea_IsOverlappingPoint(NAVAREA_PTR(iOtherArea), vecPos)) continue; + + new Float:flTheirZ = @NavArea_GetZ(NAVAREA_PTR(iOtherArea), vecPos); + if (flTheirZ > vecPos[2]) continue; + + if (flTheirZ > flOurZ) return false; + } + + return true; +} + +// Return true if this area and given area are approximately co-planar +bool:@NavArea_IsCoplanar(const this[NavArea], iArea) { + static Float:u[3]; + static Float:v[3]; + + // compute our unit surface normal + u[0] = this[NavArea_Extent][Extent_Hi][0] - this[NavArea_Extent][Extent_Lo][0]; + u[1] = 0.0; + u[2] = this[NavArea_NeZ] - this[NavArea_Extent][Extent_Lo][2]; + + v[0] = 0.0; + v[1] = this[NavArea_Extent][Extent_Hi][1] - this[NavArea_Extent][Extent_Lo][1]; + v[2] = this[NavArea_SwZ] - this[NavArea_Extent][Extent_Lo][2]; + + static Float:vecNormal[3]; + xs_vec_cross(u, v, vecNormal); + NormalizeInPlace(vecNormal, vecNormal); + + // compute their unit surface normal + u[0] = NAVAREA_PTR(iArea)[NavArea_Extent][Extent_Hi][0] - NAVAREA_PTR(iArea)[NavArea_Extent][Extent_Lo][0]; + u[1] = 0.0; + u[2] = NAVAREA_PTR(iArea)[NavArea_NeZ] - NAVAREA_PTR(iArea)[NavArea_Extent][Extent_Lo][2]; + + v[0] = 0.0; + v[1] = NAVAREA_PTR(iArea)[NavArea_Extent][Extent_Hi][1] - NAVAREA_PTR(iArea)[NavArea_Extent][Extent_Lo][1]; + v[2] = NAVAREA_PTR(iArea)[NavArea_SwZ] - NAVAREA_PTR(iArea)[NavArea_Extent][Extent_Lo][2]; + + static Float:vecOtherNormal[3]; + xs_vec_cross(u, v, vecOtherNormal); + NormalizeInPlace(vecOtherNormal, vecOtherNormal); + + // can only merge areas that are nearly planar, to ensure areas do not differ from underlying geometry much + static const Float:flTolerance = 0.99; + if (xs_vec_dot(vecNormal, vecOtherNormal) > flTolerance) return true; + + return false; +} + +// Return Z of area at (x,y) of 'vecPos' +// Trilinear interpolation of Z values at quad edges. +// NOTE: vecPos[2] is not used. +Float:@NavArea_GetZ(const this[NavArea], const Float:vecPos[]) { + static Float:dx; dx = this[NavArea_Extent][Extent_Hi][0] - this[NavArea_Extent][Extent_Lo][0]; + static Float:dy; dy = this[NavArea_Extent][Extent_Hi][1] - this[NavArea_Extent][Extent_Lo][1]; + + static Float:flNeZ; flNeZ = this[NavArea_NeZ]; + static Float:flSwZ; flSwZ = this[NavArea_SwZ]; + + // guard against division by zero due to degenerate areas + if (dx == 0.0 || dy == 0.0) return flNeZ; + + static Float:u; u = floatclamp((vecPos[0] - this[NavArea_Extent][Extent_Lo][0]) / dx, 0.0, 1.0); + static Float:v; v = floatclamp((vecPos[1] - this[NavArea_Extent][Extent_Lo][1]) / dy, 0.0, 1.0); + + static Float:northZ; northZ = this[NavArea_Extent][Extent_Lo][2] + u * (flNeZ - this[NavArea_Extent][Extent_Lo][2]); + static Float:southZ; southZ = flSwZ + u * (this[NavArea_Extent][Extent_Hi][2] - flSwZ); + + return northZ + v * (southZ - northZ); +} + +// new Float:@NavArea_GetZ(const this[NavArea], new Float:x, new Float:y) { +// static Float:vecPos[3](x, y, 0.0); +// return GetZ(&vecPos); +// } + +// Return closest point to 'vecPos' on 'area'. +// Returned point is in 'vecClose'. +@NavArea_GetClosestPointOnArea(const this[NavArea], const Float:vecPos[], Float:vecClose[]) { + if (vecPos[0] < this[NavArea_Extent][Extent_Lo][0]) { + if (vecPos[1] < this[NavArea_Extent][Extent_Lo][1]) { + // position is north-west of area + xs_vec_copy(this[NavArea_Extent][Extent_Lo], vecClose); + } else if (vecPos[1] > this[NavArea_Extent][Extent_Hi][1]) { + // position is south-west of area + vecClose[0] = this[NavArea_Extent][Extent_Lo][0]; + vecClose[1] = this[NavArea_Extent][Extent_Hi][1]; + } else { + // position is west of area + vecClose[0] = this[NavArea_Extent][Extent_Lo][0]; + vecClose[1] = vecPos[1]; + } + } else if (vecPos[0] > this[NavArea_Extent][Extent_Hi][0]) { + if (vecPos[1] < this[NavArea_Extent][Extent_Lo][1]) { + // position is north-east of area + vecClose[0] = this[NavArea_Extent][Extent_Hi][0]; + vecClose[1] = this[NavArea_Extent][Extent_Lo][1]; + } else if (vecPos[1] > this[NavArea_Extent][Extent_Hi][1]) { + // position is south-east of area + xs_vec_copy(this[NavArea_Extent][Extent_Hi], vecClose); + } else { + // position is east of area + vecClose[0] = this[NavArea_Extent][Extent_Hi][0]; + vecClose[1] = vecPos[1]; + } + } else if (vecPos[1] < this[NavArea_Extent][Extent_Lo][1]) { + // position is north of area + vecClose[0] = vecPos[0]; + vecClose[1] = this[NavArea_Extent][Extent_Lo][1]; + } else if (vecPos[1] > this[NavArea_Extent][Extent_Hi][1]) { + // position is south of area + vecClose[0] = vecPos[0]; + vecClose[1] = this[NavArea_Extent][Extent_Hi][1]; + } else { + // position is inside of area - it is the 'closest point' to itself + xs_vec_copy(vecPos, vecClose); + } + + vecClose[2] = @NavArea_GetZ(this, vecClose); +} + +// Return shortest distance squared between point and this area +Float:@NavArea_GetDistanceSquaredToPoint(const this[NavArea], const Float:vecPos[]) { + if (vecPos[0] < this[NavArea_Extent][Extent_Lo][0]) { + if (vecPos[1] < this[NavArea_Extent][Extent_Lo][1]) { + // position is north-west of area + return floatpower(xs_vec_distance(this[NavArea_Extent][Extent_Lo], vecPos), 2.0); + } else if (vecPos[1] > this[NavArea_Extent][Extent_Hi][1]) { + new Float:flSwZ = this[NavArea_SwZ]; + + // position is south-west of area + static Float:d[3]; + d[0] = this[NavArea_Extent][Extent_Lo][0] - vecPos[0]; + d[1] = this[NavArea_Extent][Extent_Hi][1] - vecPos[1]; + d[2] = flSwZ - vecPos[2]; + + return floatpower(xs_vec_len(d), 2.0); + } else { + // position is west of area + new Float:d = this[NavArea_Extent][Extent_Lo][0] - vecPos[0]; + + return d * d; + } + } else if (vecPos[0] > this[NavArea_Extent][Extent_Hi][0]) { + if (vecPos[1] < this[NavArea_Extent][Extent_Lo][1]) { + new Float:flNeZ = this[NavArea_NeZ]; + + // position is north-east of area + static Float:d[3]; + d[0] = this[NavArea_Extent][Extent_Hi][0] - vecPos[0]; + d[1] = this[NavArea_Extent][Extent_Lo][1] - vecPos[1]; + d[2] = flNeZ - vecPos[2]; + + return floatpower(xs_vec_len(d), 2.0); + } else if (vecPos[1] > this[NavArea_Extent][Extent_Hi][1]) { + // position is south-east of area + return floatpower(xs_vec_distance(this[NavArea_Extent][Extent_Hi], vecPos), 2.0); + } else { + // position is east of area + new Float:d = vecPos[2] - this[NavArea_Extent][Extent_Hi][0]; + + return d * d; + } + } else if (vecPos[1] < this[NavArea_Extent][Extent_Lo][1]) { + // position is north of area + new Float:d = this[NavArea_Extent][Extent_Lo][1] - vecPos[1]; + + return d * d; + } else if (vecPos[1] > this[NavArea_Extent][Extent_Hi][1]) { + // position is south of area + new Float:d = vecPos[1] - this[NavArea_Extent][Extent_Hi][1]; + + return d * d; + } else { // position is inside of 2D extent of area - find delta Z + new Float:z = @NavArea_GetZ(this, vecPos); + new Float:d = z - vecPos[2]; + + return d * d; + } +} + +@NavArea_GetRandomAdjacentArea(const this[NavArea], NavDirType:iDir) { + static Array:irgConnections; irgConnections = this[NavArea_Connect][iDir]; + + static iConnectionCount; iConnectionCount = ArraySize(irgConnections); + if (!iConnectionCount) return INVALID_NAV_AREA; + + static iWhich; iWhich = random(iConnectionCount); + + return ArrayGetCell(irgConnections, iWhich, _:NavConnect_Area); +} + +// Compute "portal" between to adjacent areas. +// Return center of portal opening, and half-width defining sides of portal from center. +// NOTE: center[2] is unset. +@NavArea_ComputePortal(const this[NavArea], const other[NavArea], NavDirType:iDir, Float:vecCenter[], &Float:flHalfWidth) { + if (iDir == NORTH || iDir == SOUTH) { + if (iDir == NORTH) { + vecCenter[1] = this[NavArea_Extent][Extent_Lo][1]; + } else { + vecCenter[1] = this[NavArea_Extent][Extent_Hi][1]; + } + + new Float:flLeft = floatmax( + this[NavArea_Extent][Extent_Lo][0], + other[NavArea_Extent][Extent_Lo][0] + ); + + new Float:flRight = floatmin( + this[NavArea_Extent][Extent_Hi][0], + other[NavArea_Extent][Extent_Hi][0] + ); + + // clamp to our extent in case areas are disjoint + if (flLeft < this[NavArea_Extent][Extent_Lo][0]) { + flLeft = this[NavArea_Extent][Extent_Lo][0]; + } else if (flLeft > this[NavArea_Extent][Extent_Hi][0]) { + flLeft = this[NavArea_Extent][Extent_Hi][0]; + } + + if (flRight < this[NavArea_Extent][Extent_Lo][0]) { + flRight = this[NavArea_Extent][Extent_Lo][0]; + } else if (flRight > this[NavArea_Extent][Extent_Hi][0]) { + flRight = this[NavArea_Extent][Extent_Hi][0]; + } + + vecCenter[0] = (flLeft + flRight) / 2.0; + flHalfWidth = (flRight - flLeft) / 2.0; + } else { // EAST or WEST + if (iDir == WEST) { + vecCenter[0] = this[NavArea_Extent][Extent_Lo][0]; + } else { + vecCenter[0] = this[NavArea_Extent][Extent_Hi][0]; + } + + new Float:flTop = floatmax( + this[NavArea_Extent][Extent_Lo][1], + other[NavArea_Extent][Extent_Lo][1] + ); + + new Float:flBottom = floatmin( + this[NavArea_Extent][Extent_Hi][1], + other[NavArea_Extent][Extent_Hi][1] + ); + + // clamp to our extent in case areas are disjoint + if (flTop < this[NavArea_Extent][Extent_Lo][1]) { + flTop = this[NavArea_Extent][Extent_Lo][1]; + } else if (flTop > this[NavArea_Extent][Extent_Hi][1]) { + flTop = this[NavArea_Extent][Extent_Hi][1]; + } + + if (flBottom < this[NavArea_Extent][Extent_Lo][1]) { + flBottom = this[NavArea_Extent][Extent_Lo][1]; + } else if (flBottom > this[NavArea_Extent][Extent_Hi][1]) { + flBottom = this[NavArea_Extent][Extent_Hi][1]; + } + + vecCenter[1] = (flTop + flBottom) / 2.0; + flHalfWidth = (flBottom - flTop) / 2.0; + } +} + +// Compute closest point within the "portal" between to adjacent areas. +@NavArea_ComputeClosestPointInPortal(const this[NavArea], const area[NavArea], NavDirType:iDir, const Float:vecFromPos[], Float:vecClosePos[]) { + static Float:flMargin; flMargin = GenerationStepSize / 2.0; + + if (iDir == NORTH || iDir == SOUTH) { + if (iDir == NORTH) { + vecClosePos[1] = this[NavArea_Extent][Extent_Lo][1]; + } else { + vecClosePos[1] = this[NavArea_Extent][Extent_Hi][1]; + } + + static Float:flLeft; flLeft = floatmax( + this[NavArea_Extent][Extent_Lo][0], + area[NavArea_Extent][Extent_Lo][0] + ); + + static Float:flRight; flRight = floatmin( + this[NavArea_Extent][Extent_Hi][0], + area[NavArea_Extent][Extent_Hi][0] + ); + + // clamp to our extent in case areas are disjoint + if (flLeft < this[NavArea_Extent][Extent_Lo][0]) { + flLeft = this[NavArea_Extent][Extent_Lo][0]; + } else if (flLeft > this[NavArea_Extent][Extent_Hi][0]) { + flLeft = this[NavArea_Extent][Extent_Hi][0]; + } + + if (flRight < this[NavArea_Extent][Extent_Lo][0]) { + flRight = this[NavArea_Extent][Extent_Lo][0]; + } else if (flRight > this[NavArea_Extent][Extent_Hi][0]) { + flRight = this[NavArea_Extent][Extent_Hi][0]; + } + + // keep margin if against edge + static Float:flLeftMargin; flLeftMargin = (@NavArea_IsEdge(area, WEST)) ? (flLeft + flMargin) : flLeft; + static Float:flRightMargin; flRightMargin = (@NavArea_IsEdge(area, EAST)) ? (flRight - flMargin) : flRight; + + // limit x to within portal + if (vecFromPos[0] < flLeftMargin) { + vecClosePos[0] = flLeftMargin; + } else if (vecFromPos[0] > flRightMargin) { + vecClosePos[0] = flRightMargin; + } else { + vecClosePos[0] = vecFromPos[0]; + } + + } else { // EAST or WEST + if (iDir == WEST) { + vecClosePos[0] = this[NavArea_Extent][Extent_Lo][0]; + } else { + vecClosePos[0] = this[NavArea_Extent][Extent_Hi][0]; + } + + static Float:flTop; flTop = floatmax( + this[NavArea_Extent][Extent_Lo][1], + area[NavArea_Extent][Extent_Lo][1] + ); + + static Float:flBottom; flBottom = floatmin( + this[NavArea_Extent][Extent_Hi][1], + area[NavArea_Extent][Extent_Hi][1] + ); + + // clamp to our extent in case areas are disjoint + if (flTop < this[NavArea_Extent][Extent_Lo][1]) { + flTop = this[NavArea_Extent][Extent_Lo][1]; + } else if (flTop > this[NavArea_Extent][Extent_Hi][1]) { + flTop = this[NavArea_Extent][Extent_Hi][1]; + } + + if (flBottom < this[NavArea_Extent][Extent_Lo][1]) { + flBottom = this[NavArea_Extent][Extent_Lo][1]; + } else if (flBottom > this[NavArea_Extent][Extent_Hi][1]) { + flBottom = this[NavArea_Extent][Extent_Hi][1]; + } + + // keep margin if against edge + static Float:flTopMargin; flTopMargin = (@NavArea_IsEdge(area, NORTH)) ? (flTop + flMargin) : flTop; + static Float:flBottomMargin; flBottomMargin = (@NavArea_IsEdge(area, SOUTH)) ? (flBottom - flMargin) : flBottom; + + // limit y to within portal + if (vecFromPos[1] < flTopMargin) { + vecClosePos[1] = flTopMargin; + } else if (vecFromPos[1] > flBottomMargin) { + vecClosePos[1] = flBottomMargin; + } else { + vecClosePos[1] = vecFromPos[1]; + } + } +} + +// Return true if there are no bi-directional links on the given side +bool:@NavArea_IsEdge(const this[NavArea], NavDirType:iDir) { + static Array:irgConnections; irgConnections = this[NavArea_Connect][iDir]; + static iConnectionCount; iConnectionCount = ArraySize(irgConnections); + + for (new i = 0; i < iConnectionCount; ++i) { + static iConnectArea; iConnectArea = ArrayGetCell(irgConnections, i, _:NavConnect_Area); + if (@NavArea_IsConnected(NAVAREA_PTR(iConnectArea), this, OppositeDirection(iDir))) { + return false; + } + } + + return true; +} + +bool:@NavArea_IsConnected(const this[NavArea], const area[NavArea], NavDirType:iDir) { + // we are connected to ourself + if (NAVAREA_INDEX(area) == NAVAREA_INDEX(this)) return true; + + static iArea; iArea = NAVAREA_INDEX(area); + + if (iDir == NUM_DIRECTIONS) { + // search all directions + for (new NavDirType:iDir = NORTH; iDir < NUM_DIRECTIONS; ++iDir) { + if (@NavArea_IsConnected(this, area, iDir)) { + return true; + } + } + + // check ladder connections + { + static Array:irgLadderList; irgLadderList = @NavArea_GetLadder(this, LADDER_UP); + static iListSize; iListSize = ArraySize(irgLadderList); + + for (new iLadder = 0; iLadder < iListSize; ++iLadder) { + if ( + ArrayGetCell(irgLadderList, iLadder, _:NavLadder_TopBehindArea) == iArea || + ArrayGetCell(irgLadderList, iLadder, _:NavLadder_TopForwardArea) == iArea || + ArrayGetCell(irgLadderList, iLadder, _:NavLadder_TopLeftArea) == iArea || + ArrayGetCell(irgLadderList, iLadder, _:NavLadder_TopRightArea) == iArea + ) { + return true; + } + } + } + + { + static Array:irgLadderList; irgLadderList = @NavArea_GetLadder(this, LADDER_DOWN); + static iListSize; iListSize = ArraySize(irgLadderList); + + for (new iLadder = 0; iLadder < iListSize; ++iLadder) { + if (ArrayGetCell(irgLadderList, iLadder, _:NavLadder_BottomArea) == iArea) { + return true; + } + } + } + } else { + // check specific direction + static Array:irgConnections; irgConnections = this[NavArea_Connect][iDir]; + static iConnectionCount; iConnectionCount = ArraySize(irgConnections); + + for (new iConnection = 0; iConnection < iConnectionCount; ++iConnection) { + if (NAVAREA_INDEX(area) == ArrayGetCell(irgConnections, iConnection, _:NavConnect_Area)) { + return true; + } + } + } + + return false; +} + +// Return direction from this area to the given point +NavDirType:@NavArea_ComputeDirection(const this[NavArea], const Float:vecPoint[]) { + if (vecPoint[0] >= this[NavArea_Extent][Extent_Lo][0] && vecPoint[0] <= this[NavArea_Extent][Extent_Hi][0]) { + if (vecPoint[1] < this[NavArea_Extent][Extent_Lo][1]) { + return NORTH; + } else if (vecPoint[1] > this[NavArea_Extent][Extent_Hi][1]) { + return SOUTH; + } + } else if (vecPoint[1] >= this[NavArea_Extent][Extent_Lo][1] && vecPoint[1] <= this[NavArea_Extent][Extent_Hi][1]) { + if (vecPoint[0] < this[NavArea_Extent][Extent_Lo][0]) { + return WEST; + } else if (vecPoint[0] > this[NavArea_Extent][Extent_Hi][0]) { + return EAST; + } + } + + // find closest direction + static Float:vecTo[3]; + @NavArea_GetCenter(this, vecTo); + xs_vec_sub(vecPoint, vecTo, vecTo); + + if (floatabs(vecTo[0]) > floatabs(vecTo[1])) { + return vecTo[0] > 0.0 ? EAST : WEST; + } else { + return vecTo[1] > 0.0 ? SOUTH : NORTH; + } +} + +@NavArea_GetCorner(const this[NavArea], NavCornerType:corner, Float:vecOut[]) { + switch (corner) { + case NORTH_WEST: { + xs_vec_copy(this[NavArea_Extent][Extent_Lo], vecOut); + } + case NORTH_EAST: { + vecOut[0] = this[NavArea_Extent][Extent_Hi][0]; + vecOut[1] = this[NavArea_Extent][Extent_Lo][1]; + vecOut[2] = this[NavArea_NeZ]; + } + case SOUTH_WEST: { + vecOut[0] = this[NavArea_Extent][Extent_Lo][0]; + vecOut[1] = this[NavArea_Extent][Extent_Hi][1]; + vecOut[2] = this[NavArea_SwZ]; + } + case SOUTH_EAST: { + xs_vec_copy(this[NavArea_Extent][Extent_Hi], vecOut); + } + } +} + +Array:@NavArea_GetLadder(const this[NavArea], LadderDirectionType:iDir) { + return Array:this[NavArea_Ladder][iDir]; +} + +Struct:@NavPath_Create() { + static Struct:this; this = StructCreate(NavPath); + StructSetCell(this, NavPath_Segments, ArrayCreate(_:PathSegment)); + + return this; +} + +@NavPath_Destroy(&Struct:this) { + StructDestroy(this); +} + +// Build trivial path when start and goal are in the same nav area +bool:@NavPath_BuildTrivialPath(const &Struct:this, const Float:vecStart[], const Float:vecGoal[]) { + static Array:irgSegments; irgSegments = StructGetCell(this, NavPath_Segments); + ArrayClear(irgSegments); + + StructSetCell(this, NavPath_SegmentCount, 0); + + static iStartArea; iStartArea = NavAreaGrid_GetNearestNavArea(vecStart, false, nullptr); + if (iStartArea == INVALID_NAV_AREA) return false; + + static iGoalArea; iGoalArea = NavAreaGrid_GetNearestNavArea(vecGoal, false, nullptr); + if (iGoalArea == INVALID_NAV_AREA) return false; + + static rgStartSegment[PathSegment]; + rgStartSegment[PathSegment_Area] = iStartArea; + rgStartSegment[PathSegment_How] = NUM_TRAVERSE_TYPES; + xs_vec_set(rgStartSegment[PathSegment_Pos], vecStart[0], vecStart[1], @NavArea_GetZ(NAVAREA_PTR(iStartArea), vecStart)); + @NavPath_PushSegment(this, rgStartSegment); + + static rgGoalSegment[PathSegment]; + rgGoalSegment[PathSegment_Area] = iGoalArea; + rgGoalSegment[PathSegment_How] = NUM_TRAVERSE_TYPES; + xs_vec_set(rgGoalSegment[PathSegment_Pos], vecGoal[0], vecGoal[1], @NavArea_GetZ(NAVAREA_PTR(iGoalArea), vecGoal)); + @NavPath_PushSegment(this, rgGoalSegment); + + return true; +} + +@NavPath_PushSegment(const &Struct:this, const segment[PathSegment]) { + ArrayPushArray(StructGetCell(this, NavPath_Segments), segment[any:0]); + StructSetCell(this, NavPath_SegmentCount, StructGetCell(this, NavPath_SegmentCount) + 1); +} + +@NavPath_ComputePathPositions(const &Struct:this) { + static iSegmentsNum; iSegmentsNum = StructGetCell(this, NavPath_SegmentCount); + if (!iSegmentsNum) return false; + + static Array:irgSegments; irgSegments = StructGetCell(this, NavPath_Segments); + static iStartArea; iStartArea = ArrayGetCell(irgSegments, 0, _:PathSegment_Area); + + // start in first area's center + ArraySetArray2(irgSegments, 0, NAVAREA_PTR(iStartArea)[NavArea_Center], 3, PathSegment_Pos); + ArraySetCell(irgSegments, 0, NUM_TRAVERSE_TYPES, _:PathSegment_How); + + for (new iSegment = 1; iSegment < iSegmentsNum; iSegment++) { + static iFromArea; iFromArea = ArrayGetCell(irgSegments, iSegment - 1, _:PathSegment_Area); + static iToArea; iToArea = ArrayGetCell(irgSegments, iSegment, _:PathSegment_Area); + + // walk along the floor to the next area + static NavTraverseType:toHow; toHow = ArrayGetCell(irgSegments, iSegment, _:PathSegment_How); + if (toHow <= GO_WEST) { + static Float:vecFromPos[3]; ArrayGetArray2(irgSegments, iSegment - 1, vecFromPos, 3, PathSegment_Pos); + static Float:vecToPos[3]; ArrayGetArray2(irgSegments, iSegment, vecToPos, 3, PathSegment_Pos); + + // compute next point, keeping path as straight as possible + @NavArea_ComputeClosestPointInPortal(NAVAREA_PTR(iFromArea), NAVAREA_PTR(iToArea), NavDirType:toHow, vecFromPos, vecToPos); + + // move goal position into the goal area a bit + // how far to "step into" an area - must be less than min area size + static const Float:flStepInDist = 5.0; + AddDirectionVector(vecToPos, NavDirType:toHow, flStepInDist); + + // we need to walk out of "from" area, so keep Z where we can reach it + vecToPos[2] = @NavArea_GetZ(NAVAREA_PTR(iFromArea), vecToPos); + + ArraySetArray2(irgSegments, iSegment, vecToPos, 3, PathSegment_Pos); + + // if this is a "jump down" connection, we must insert an additional point on the path + if (!@NavArea_IsConnected(NAVAREA_PTR(iToArea), NAVAREA_PTR(iFromArea), NUM_DIRECTIONS)) { + // this is a "jump down" link + // compute direction of path just prior to "jump down" + static Float:flDir[2]; DirectionToVector2D(NavDirType:toHow, flDir); + + // shift top of "jump down" out a bit to "get over the ledge" + static const Float:flPushDist = 25.0; + ArraySetCell(irgSegments, iSegment, Float:ArrayGetCell(irgSegments, iSegment, _:PathSegment_Pos + 0) + (flPushDist * flDir[0]), _:PathSegment_Pos + 0); + ArraySetCell(irgSegments, iSegment, Float:ArrayGetCell(irgSegments, iSegment, _:PathSegment_Pos + 1) + (flPushDist * flDir[1]), _:PathSegment_Pos + 1); + + // insert a duplicate node to represent the bottom of the fall + if (iSegmentsNum < MAX_PATH_SEGMENTS - 1) { + static rgSegment[PathSegment]; + rgSegment[PathSegment_Area] = ArrayGetCell(irgSegments, iSegment, _:PathSegment_Area); + rgSegment[PathSegment_How] = ArrayGetCell(irgSegments, iSegment, _:PathSegment_How); + rgSegment[PathSegment_Pos][0] = vecToPos[0] + flPushDist * flDir[0]; + rgSegment[PathSegment_Pos][1] = vecToPos[1] + flPushDist * flDir[1]; + rgSegment[PathSegment_Pos][2] = ArrayGetCell(irgSegments, iSegment, _:PathSegment_Pos + 2); + rgSegment[PathSegment_Pos][2] = @NavArea_GetZ(NAVAREA_PTR(iToArea), rgSegment[PathSegment_Pos]); + + ArrayInsertArrayAfter(irgSegments, iSegment, rgSegment[any:0]); + + // path is one node longer + StructSetCell(this, NavPath_SegmentCount, ++iSegmentsNum); + + // move index ahead into the new node we just duplicated + iSegment++; + } + } + } else if (toHow == GO_LADDER_UP) { // to get to next area, must go up a ladder + // find our ladder + static bool:bFound; bFound = false; + static Array:irgLadderList; irgLadderList = @NavArea_GetLadder(NAVAREA_PTR(iFromArea), LADDER_UP); + + static iListSize; iListSize = ArraySize(irgLadderList); + for (new iLadder = 0; iLadder < iListSize; ++iLadder) { + // can't use "behind" area when ascending... + if ( + ArrayGetCell(irgLadderList, iLadder, _:NavLadder_TopForwardArea) == iToArea || + ArrayGetCell(irgLadderList, iLadder, _:NavLadder_TopLeftArea) == iToArea || + ArrayGetCell(irgLadderList, iLadder, _:NavLadder_TopRightArea) == iToArea + ) { + // to->ladder = ladder; + static Float:vecPos[3]; ArrayGetArray2(irgLadderList, iLadder, vecPos, 3, NavLadder_Bottom); + AddDirectionVector(vecPos, ArrayGetCell(irgLadderList, iLadder, _:NavLadder_Dir), 2.0 * HalfHumanWidth); + ArraySetArray2(irgSegments, iSegment, vecPos, 3, PathSegment_Pos); + bFound = true; + break; + } + } + + if (!bFound) return false; + } else if (toHow == GO_LADDER_DOWN) { // to get to next area, must go down a ladder + // find our ladder + static bool:bFound; bFound = false; + static Array:irgLadderList; irgLadderList = @NavArea_GetLadder(NAVAREA_PTR(iFromArea), LADDER_DOWN); + + static iListSize; iListSize = ArraySize(irgLadderList); + for (new iLadder = 0; iLadder < iListSize; ++iLadder) { + if (ArrayGetCell(irgLadderList, iLadder, _:NavLadder_BottomArea) == iToArea) { + // to->ladder = ladder; + static Float:vecPos[3]; ArrayGetArray2(irgLadderList, iLadder, vecPos, 3, NavLadder_Top); + AddDirectionVector(vecPos, OppositeDirection(ArrayGetCell(irgLadderList, iLadder, _:NavLadder_Dir)), 2.0 * HalfHumanWidth); + ArraySetArray2(irgSegments, iSegment, vecPos, 3, PathSegment_Pos); + bFound = true; + break; + } + } + + if (!bFound) return false; + } + } + + return true; +} + +bool:@NavPath_IsValid(const &Struct:this) { + return StructGetCell(this, NavPath_SegmentCount) > 0; +} + +@NavPath_GetSegmentCount(const &Struct:this) { + return StructGetCell(this, NavPath_SegmentCount); +} + +@NavPath_Invalidate(const &Struct:this) { + StructSetCell(this, NavPath_SegmentCount, 0); +} + +@NavPath_Clear(const &Struct:this) { + static Array:irgSegments; irgSegments = StructGetCell(this, NavPath_Segments); + ArrayClear(irgSegments); + + StructSetCell(this, NavPath_SegmentCount, 0); +} + +@NavPath_GetEndpoint(const &Struct:this, Float:vecEndpoint[]) { + static iSegmentCount; iSegmentCount = StructGetCell(this, NavPath_SegmentCount); + static Array:irgSegments; irgSegments = StructGetCell(this, NavPath_Segments); + + ArrayGetArray2(irgSegments, iSegmentCount - 1, vecEndpoint, 3, PathSegment_Pos); +} + +// Return true if position is at the end of the path +bool:@NavPath_IsAtEnd(const &Struct:this, const Float:vecPos[]) { + if (!@NavPath_IsValid(this)) return false; + + static Float:vecEndpoint[3]; @NavPath_GetEndpoint(this, vecEndpoint); + + return xs_vec_distance(vecPos, vecEndpoint) < 20.0; +} + +// Return point a given distance along the path - if distance is out of path bounds, point is clamped to start/end +// TODO: Be careful of returning "positions" along one-way drops, ladders, etc. +bool:@NavPath_GetPointAlongPath(const &Struct:this, Float:flDistAlong, Float:vecPointOnPath[]) { + if (!@NavPath_IsValid(this)) { + return false; + } + + static Array:irgSegments; irgSegments = StructGetCell(this, NavPath_Segments); + + if (flDistAlong <= 0.0) { + ArrayGetArray2(irgSegments, 0, vecPointOnPath, 3, PathSegment_Pos); + return true; + } + + static Float:flLengthSoFar; flLengthSoFar = 0.0; + + static iSegmentsNum; iSegmentsNum = @NavPath_GetSegmentCount(this); + + for (new i = 1; i < iSegmentsNum; i++) { + static Float:vecFrom[3]; ArrayGetArray2(irgSegments, i - 1, vecFrom, 3, PathSegment_Pos); + static Float:vecTo[3]; ArrayGetArray2(irgSegments, i, vecTo, 3, PathSegment_Pos); + static Float:vecDir[3]; xs_vec_sub(vecTo, vecFrom, vecDir); + static Float:flSegmentLength; flSegmentLength = xs_vec_len(vecDir); + + if (flSegmentLength + flLengthSoFar >= flDistAlong) { + // desired point is on this segment of the path + for (new j = 0; j < 3; ++j) { + vecPointOnPath[j] = vecTo[j] + ((flDistAlong - flLengthSoFar) / flSegmentLength) * vecDir[j]; + } + + return true; + } + + flLengthSoFar += flSegmentLength; + } + + @NavPath_GetEndpoint(this, vecPointOnPath); + + return true; +} + +// Compute closest point on path to given point +// NOTE: This does not do line-of-sight tests, so closest point may be thru the floor, etc +bool:@NavPath_FindClosestPointOnPath(const &Struct:this, const Float:vecWorldPos[], iStartIndex, iEndIndex, Float:vecClose[]) { + if (!@NavPath_IsValid(this)) return false; + + static Array:irgSegments; irgSegments = StructGetCell(this, NavPath_Segments); + + static Float:flCloseDistSq = 8192.0; + + for (new iSegment = iStartIndex; iSegment <= iEndIndex; iSegment++) { + static Float:vecFrom[3]; ArrayGetArray2(irgSegments, iSegment - 1, vecFrom, 3, PathSegment_Pos); + static Float:vecTo[3]; ArrayGetArray2(irgSegments, iSegment, vecTo, 3, PathSegment_Pos); + static Float:vecAlong[3]; xs_vec_sub(vecTo, vecFrom, vecAlong); + static Float:flLength; flLength = NormalizeInPlace(vecAlong, vecAlong); + static Float:vecToWorldPos[3]; xs_vec_sub(vecWorldPos, vecFrom, vecToWorldPos); + static Float:flCloseLength; flCloseLength = xs_vec_dot(vecToWorldPos, vecAlong); + + // constrain point to be on path segment + static Float:vecPos[3]; + if (flCloseLength <= 0.0) { + xs_vec_copy(vecFrom, vecPos); + } else if (flCloseLength >= flLength) { + xs_vec_copy(vecTo, vecPos); + } else { + xs_vec_add_scaled(vecFrom, vecAlong, flCloseLength, vecPos); + } + + static Float:flDistSq; flDistSq = floatpower(xs_vec_distance(vecPos, vecWorldPos), 2.0); + + // keep the closest point so far + if (flDistSq < flCloseDistSq) { + flCloseDistSq = flDistSq; + xs_vec_copy(vecPos, vecClose); + } + } + + return true; +} + +@BuildPathTask_Allocate(this[BuildPathTask], iUserToken, const &iStartArea, const &iGoalArea, const Float:vecStart[], const Float:vecGoal[], pIgnoreEnt, iCbFuncPluginId, iCbFuncId, iCostFuncPluginId, iCostFuncId) { + if (this[BuildPathTask_Path] == Invalid_Struct) { + this[BuildPathTask_Path] = @NavPath_Create(); + } else { + @NavPath_Clear(this[BuildPathTask_Path]); + } + + this[BuildPathTask_StartArea] = iStartArea; + this[BuildPathTask_GoalArea] = iGoalArea; + this[BuildPathTask_FinishCallback][Callback_PluginId] = iCbFuncPluginId; + this[BuildPathTask_FinishCallback][Callback_FunctionId] = iCbFuncId; + this[BuildPathTask_CostCallback][Callback_PluginId] = iCostFuncPluginId; + this[BuildPathTask_CostCallback][Callback_FunctionId] = iCostFuncId; + this[BuildPathTask_IgnoreEntity] = pIgnoreEnt; + this[BuildPathTask_UserToken] = iUserToken; + this[BuildPathTask_IsFinished] = false; + this[BuildPathTask_IsSuccessed] = false; + this[BuildPathTask_IsTerminated] = false; + this[BuildPathTask_IsFree] = false; + this[BuildPathTask_IterationsNum] = 0; + xs_vec_copy(vecStart, this[BuildPathTask_StartPos]); + xs_vec_copy(vecGoal, this[BuildPathTask_GoalPos]); +} + +@BuildPathTask_Free(this[BuildPathTask]) { + this[BuildPathTask_IsFree] = true; +} + +// Struct:@PathSegment_Create() { +// new Struct:this = StructCreate(PathSegment); + +// return this; +// } + +// @PathSegment_Destroy(&Struct:this) { +// StructDestroy(this); +// } + +NavAreaGrid_Init(Float:flMinX, Float:flMaxX, Float:flMinY, Float:flMaxY) { + g_rgGrid[NavAreaGrid_CellSize] = 300.0; + g_rgGrid[NavAreaGrid_MinX] = flMinX; + g_rgGrid[NavAreaGrid_MinY] = flMinY; + g_rgGrid[NavAreaGrid_GridSizeX] = floatround((flMaxX - flMinX) / g_rgGrid[NavAreaGrid_CellSize] + 1); + g_rgGrid[NavAreaGrid_GridSizeY] = floatround((flMaxY - flMinY) / g_rgGrid[NavAreaGrid_CellSize] + 1); + g_rgGrid[NavAreaGrid_AreaCount] = 0; + + for (new i = 0; i < HASH_TABLE_SIZE; ++i) { + g_rgGrid[NavAreaGrid_HashTable][i] = INVALID_NAV_AREA; + } + + new iGridSize = g_rgGrid[NavAreaGrid_GridSizeX] * g_rgGrid[NavAreaGrid_GridSizeY]; + + g_rgGrid[NavAreaGrid_Grid] = ArrayCreate(_, iGridSize); + + for (new i = 0; i < iGridSize; ++i) { + ArrayPushCell(g_rgGrid[NavAreaGrid_Grid], ArrayCreate()); + } +} + +NavAreaGrid_Free() { + if (g_rgGrid[NavAreaGrid_Grid] != Invalid_Array) { + new iGridSize = ArraySize(g_rgGrid[NavAreaGrid_Grid]); + + for (new i = 0; i < iGridSize; ++i) { + new Array:irgAreas = ArrayGetCell(g_rgGrid[NavAreaGrid_Grid], i); + ArrayDestroy(irgAreas); + } + + ArrayDestroy(g_rgGrid[NavAreaGrid_Grid]); + } +} + +NavAreaGrid_GetNavAreaById(iAreaId) { + if (iAreaId == 0) return INVALID_NAV_AREA; + + static iKey; iKey = NavAreaGrid_ComputeHashKey(iAreaId); + + for (new iArea = g_rgGrid[NavAreaGrid_HashTable][iKey]; iArea != INVALID_NAV_AREA; iArea = NAVAREA_PTR(iArea)[NavArea_NextHash]) { + if (@NavArea_GetId(NAVAREA_PTR(iArea)) == iAreaId) return iArea; + } + + return INVALID_NAV_AREA; +} + +NavAreaGrid_AddNavArea(area[NavArea]) { + // add to grid + new iLoX = NavAreaGrid_WorldToGridX(area[NavArea_Extent][Extent_Lo][0]); + new iLoY = NavAreaGrid_WorldToGridY(area[NavArea_Extent][Extent_Lo][1]); + new iHiX = NavAreaGrid_WorldToGridX(area[NavArea_Extent][Extent_Hi][0]); + new iHiY = NavAreaGrid_WorldToGridY(area[NavArea_Extent][Extent_Hi][1]); + + for (new y = iLoY; y <= iHiY; y++) { + for (new x = iLoX; x <= iHiX; x++) { + new Array:irgAreas = ArrayGetCell(g_rgGrid[NavAreaGrid_Grid], x + y * g_rgGrid[NavAreaGrid_GridSizeX]); + ArrayPushCell(irgAreas, NAVAREA_INDEX(area)); + } + } + + new iAreaId = area[NavArea_Id]; + + // add to hash table + new iKey = NavAreaGrid_ComputeHashKey(iAreaId); + + if (g_rgGrid[NavAreaGrid_HashTable][iKey] != INVALID_NAV_AREA) { + // add to head of list in this slot + area[NavArea_PrevHash] = INVALID_NAV_AREA; + area[NavArea_NextHash] = g_rgGrid[NavAreaGrid_HashTable][iKey]; + g_rgNavAreas[g_rgGrid[NavAreaGrid_HashTable][iKey]][NavArea_PrevHash] = NAVAREA_INDEX(area); + g_rgGrid[NavAreaGrid_HashTable][iKey] = NAVAREA_INDEX(area); + } else { + // first entry in this slot + g_rgGrid[NavAreaGrid_HashTable][iKey] = NAVAREA_INDEX(area); + area[NavArea_NextHash] = INVALID_NAV_AREA; + area[NavArea_PrevHash] = INVALID_NAV_AREA; + } + + g_rgGrid[NavAreaGrid_AreaCount]++; +} + +// Given a position, return the nav area that IsOverlapping and is *immediately* beneath it +NavAreaGrid_GetNavArea(const Float:vecPos[], Float:flBeneathLimit) { + if (g_rgGrid[NavAreaGrid_Grid] == Invalid_Array) return INVALID_NAV_AREA; + + // get list in cell that contains position + static x; x = NavAreaGrid_WorldToGridX(vecPos[0]); + static y; y = NavAreaGrid_WorldToGridY(vecPos[1]); + static Float:vecTestPos[3]; xs_vec_add(vecPos, Float:{0, 0, 5}, vecTestPos); + + static Array:irgList; irgList = ArrayGetCell(g_rgGrid[NavAreaGrid_Grid], x + y * g_rgGrid[NavAreaGrid_GridSizeX]); + + // search cell list to find correct area + + static iListSize; iListSize = ArraySize(irgList); + static iUseArea; iUseArea = INVALID_NAV_AREA; + static Float:useZ; useZ = -99999999.9; + + for (new i = 0; i < iListSize; ++i) { + static iArea; iArea = ArrayGetCell(irgList, i); + + // check if position is within 2D boundaries of this area + if (@NavArea_IsOverlappingPoint(NAVAREA_PTR(iArea), vecTestPos)) { + // project position onto area to get Z + static Float:z; z = @NavArea_GetZ(NAVAREA_PTR(iArea), vecTestPos); + + // if area is above us, skip it + if (z > vecTestPos[2]) continue; + + // if area is too far below us, skip it + if (z < vecPos[2] - flBeneathLimit) continue; + + // if area is higher than the one we have, use this instead + if (z > useZ) { + iUseArea = iArea; + useZ = z; + } + } + } + + return iUseArea; +} + +NavAreaGrid_WorldToGridX(Float:wx) { + return clamp( + floatround((wx - g_rgGrid[NavAreaGrid_MinX]) / g_rgGrid[NavAreaGrid_CellSize], floatround_ceil), + 0, + g_rgGrid[NavAreaGrid_GridSizeX] - 1 + ); +} + +NavAreaGrid_WorldToGridY(Float:wy) { + return clamp( + floatround((wy - g_rgGrid[NavAreaGrid_MinY]) / g_rgGrid[NavAreaGrid_CellSize], floatround_ceil), + 0, + g_rgGrid[NavAreaGrid_GridSizeY] - 1 + ); +} + +// Given a position in the world, return the nav area that is closest +// and at the same height, or beneath it. +// Used to find initial area if we start off of the mesh. +NavAreaGrid_GetNearestNavArea(const Float:vecPos[], bool:bAnyZ = false, pIgnoreEnt = nullptr, iIgnoreArea = INVALID_NAV_AREA) { + if (g_rgGrid[NavAreaGrid_Grid] == Invalid_Array) return INVALID_NAV_AREA; + + static Float:flGroundHeight; flGroundHeight = GetGroundHeight(vecPos, pIgnoreEnt); + if (flGroundHeight == -1.0) return INVALID_NAV_AREA; + + static iCurrentArea; iCurrentArea = NavAreaGrid_GetNavArea(vecPos, 120.0); + if (iCurrentArea != INVALID_NAV_AREA && iCurrentArea != iIgnoreArea) return iCurrentArea; + + // ensure source position is well behaved + static Float:vecSource[3]; xs_vec_set(vecSource, vecPos[0], vecPos[1], flGroundHeight + HalfHumanHeight); + + static sCloseArea; sCloseArea = INVALID_NAV_AREA; + static Float:flCloseDistSq; flCloseDistSq = 100000000.0; + + // TODO: Step incrementally using grid for speed + // find closest nav area + for (new iArea = 0; iArea < g_iNavAreasNum; ++iArea) { + if (iArea == iIgnoreArea) continue; + + static Float:vecAreaPos[3]; @NavArea_GetClosestPointOnArea(NAVAREA_PTR(iArea), vecSource, vecAreaPos); + static Float:flDistSq; flDistSq = floatpower(xs_vec_distance(vecAreaPos, vecSource), 2.0); + + // keep the closest area + if (flDistSq < flCloseDistSq) { + // check LOS to area + if (!bAnyZ) { + static Float:vecEnd[3]; xs_vec_set(vecEnd, vecAreaPos[0], vecAreaPos[1], vecAreaPos[2] + HalfHumanHeight); + + engfunc(EngFunc_TraceLine, vecSource, vecEnd, IGNORE_MONSTERS | IGNORE_GLASS, pIgnoreEnt, g_pTrace); + + static Float:flFraction; get_tr2(g_pTrace, TR_flFraction, flFraction); + + if (flFraction != 1.0) continue; + } + + flCloseDistSq = flDistSq; + sCloseArea = iArea; + } + } + + return sCloseArea; +} + +NavAreaGrid_ComputeHashKey(iId) { + return iId & 0xFF; +} + +NavErrorType:LoadNavigationMap() { + new szMapName[32]; get_mapname(szMapName, charsmax(szMapName)); + new szFilePath[256]; format(szFilePath, charsmax(szFilePath), "maps/%s.nav", szMapName); + + if (!file_exists(szFilePath)) { + log_amx("File ^"%s^" not found!", szFilePath); + return NAV_CANT_ACCESS_FILE; + } + + // g_irgNavLadderList = ArrayCreate(); + + new iFile = fopen(szFilePath, "rb"); + g_iNavAreaNextId = 1; + + new iMagic; + if (!FileReadInt32(iFile, iMagic)) return NAV_INVALID_FILE; + + if (iMagic != NAV_MAGIC_NUMBER) { + log_amx("Wrong magic number %d. Should be %d.", iMagic, NAV_MAGIC_NUMBER); + return NAV_INVALID_FILE; + } + + new iVersion; + if (!FileReadInt32(iFile, iVersion)) return NAV_BAD_FILE_VERSION; + + if (iVersion > NAV_VERSION) { + log_amx("Wrong version %d. Should be less then %d.", iVersion, NAV_VERSION); + return NAV_BAD_FILE_VERSION; + } + + log_amx("Nav version: %d", iVersion); + + if (iVersion >= 4) { + fseek(iFile, BLOCK_INT, SEEK_CUR); + } + + // load Place directory + if (iVersion >= NAV_VERSION) { + new iPlaceCount; + FileReadUint16(iFile, iPlaceCount); + + for (new i = 0; i < iPlaceCount; ++i) { + new iLen; + FileReadUint16(iFile, iLen); + fseek(iFile, iLen, SEEK_CUR); + } + } + + // get number of areas + FileReadInt32(iFile, g_iNavAreasNum); + log_amx("Found %d areas", g_iNavAreasNum); + + new rgExtent[Extent]; + rgExtent[Extent_Lo][0] = 9999999999.9; + rgExtent[Extent_Lo][1] = 9999999999.9; + rgExtent[Extent_Hi][0] = -9999999999.9; + rgExtent[Extent_Hi][1] = -9999999999.9; + + log_amx("Loading areas..."); + + // load the areas and compute total extent + for (new iArea = 0; iArea < g_iNavAreasNum; iArea++) { + @NavArea_Allocate(NAVAREA_PTR(iArea)); + @NavArea_Load(NAVAREA_PTR(iArea), iFile, iVersion, false); + AdjustExtentWithArea(rgExtent, NAVAREA_PTR(iArea)); + } + + log_amx("All areas loaded!"); + + // add the areas to the grid + NavAreaGrid_Init(rgExtent[Extent_Lo][0], rgExtent[Extent_Hi][0], rgExtent[Extent_Lo][1], rgExtent[Extent_Hi][1]); + + log_amx("Grid initialized!"); + + for (new iArea = 0; iArea < g_iNavAreasNum; iArea++) { + NavAreaGrid_AddNavArea(NAVAREA_PTR(iArea)); + } + + log_amx("All areas added to the grid!"); + + // allow areas to connect to each iOtherArea, etc + for (new iArea = 0; iArea < g_iNavAreasNum; iArea++) { + @NavArea_PostLoadArea(NAVAREA_PTR(iArea)); + } + + log_amx("Loaded areas post processing complete!"); + + fclose(iFile); + + if (b_bInitStage) BuildLadders(); + + return NAV_OK; +} + +AdjustExtentWithArea(rgExtent[Extent], const area[NavArea]) { + rgExtent[Extent_Lo][0] = floatmin(area[NavArea_Extent][Extent_Lo][0], rgExtent[Extent_Lo][0]); + rgExtent[Extent_Lo][1] = floatmin(area[NavArea_Extent][Extent_Lo][1], rgExtent[Extent_Lo][1]); + rgExtent[Extent_Hi][0] = floatmax(area[NavArea_Extent][Extent_Hi][0], rgExtent[Extent_Hi][0]); + rgExtent[Extent_Hi][1] = floatmax(area[NavArea_Extent][Extent_Hi][1], rgExtent[Extent_Hi][1]); +} + +// For each ladder in the map, create a navigation representation of it. +BuildLadders() { + log_amx("Building ladders..."); + + new pEntity = 0; + while ((pEntity = engfunc(EngFunc_FindEntityByString, pEntity, "classname", "func_ladder")) != 0) { + BuildLadder(pEntity); + } + + log_amx("All ladders built!"); +} + +BuildLadder(pEntity) { + new Float:vecAbsMin[3]; pev(pEntity, pev_absmin, vecAbsMin); + new Float:vecAbsMax[3]; pev(pEntity, pev_absmax, vecAbsMax); + + new rgNavLadder[NavLadder]; + rgNavLadder[NavLadder_Entity] = pEntity; + + // compute top & bottom of ladder + xs_vec_set(rgNavLadder[NavLadder_Top], (vecAbsMin[0] + vecAbsMax[0]) / 2.0, (vecAbsMin[1] + vecAbsMax[1]) / 2.0, vecAbsMax[2]); + xs_vec_set(rgNavLadder[NavLadder_Bottom], rgNavLadder[NavLadder_Top][0], rgNavLadder[NavLadder_Top][1], vecAbsMin[2]); + + // determine facing - assumes "normal" runged ladder + new Float:xSize = vecAbsMax[0] - vecAbsMin[0]; + new Float:ySize = vecAbsMax[1] - vecAbsMin[1]; + + if (xSize > ySize) { + // ladder is facing north or south - determine which way + // "pull in" traceline from bottom and top in case ladder abuts floor and/or ceiling + new Float:vecFrom[3]; xs_vec_add(rgNavLadder[NavLadder_Bottom], Float:{0.0, GenerationStepSize, GenerationStepSize}, vecFrom); + new Float:vecTo[3]; xs_vec_add(rgNavLadder[NavLadder_Top], Float:{0.0, GenerationStepSize, -GenerationStepSize}, vecTo); + + engfunc(EngFunc_TraceLine, vecFrom, vecTo, IGNORE_MONSTERS, pEntity, g_pTrace); + + new Float:flFraction; get_tr2(g_pTrace, TR_flFraction, flFraction); + + if (flFraction != 1.0 || get_tr2(g_pTrace, TR_StartSolid)) { + rgNavLadder[NavLadder_Dir] = NORTH; + } else { + rgNavLadder[NavLadder_Dir] = SOUTH; + } + } else { + // ladder is facing east or west - determine which way + new Float:vecFrom[3]; xs_vec_add(rgNavLadder[NavLadder_Bottom], Float:{GenerationStepSize, 0.0, GenerationStepSize}, vecFrom); + new Float:vecTo[3]; xs_vec_add(rgNavLadder[NavLadder_Top], Float:{GenerationStepSize, 0.0, -GenerationStepSize}, vecTo); + + engfunc(EngFunc_TraceLine, vecFrom, vecTo, IGNORE_MONSTERS, pEntity, g_pTrace); + + new Float:flFraction; get_tr2(g_pTrace, TR_flFraction, flFraction); + + if (flFraction != 1.0 || get_tr2(g_pTrace, TR_StartSolid)) { + rgNavLadder[NavLadder_Dir] = WEST; + } else { + rgNavLadder[NavLadder_Dir] = EAST; + } + } + + // adjust top and bottom of ladder to make sure they are reachable + // (cs_office has a crate right in front of the base of a ladder) + new Float:vecAlong[3]; xs_vec_sub(rgNavLadder[NavLadder_Top], rgNavLadder[NavLadder_Bottom], vecAlong); + + // adjust bottom to bypass blockages + AdjustLadderPositionToBypassBlockages(pEntity, rgNavLadder[NavLadder_Bottom], rgNavLadder[NavLadder_Dir], vecAlong); + + // adjust top to bypass blockages + AdjustLadderPositionToBypassBlockages(pEntity, rgNavLadder[NavLadder_Top], rgNavLadder[NavLadder_Dir], vecAlong); + + rgNavLadder[NavLadder_Length] = xs_vec_distance(rgNavLadder[NavLadder_Top], rgNavLadder[NavLadder_Bottom]); + + DirectionToVector2D(rgNavLadder[NavLadder_Dir], rgNavLadder[NavLadder_DirVector]); + + new Float:vecCenter[3]; + + // Find naviagtion area at bottom of ladder + // get approximate postion of player on ladder + xs_vec_add(rgNavLadder[NavLadder_Bottom], Float:{0.0, 0.0, GenerationStepSize}, vecCenter); + AddDirectionVector(vecCenter, rgNavLadder[NavLadder_Dir], HalfHumanWidth); + + rgNavLadder[NavLadder_BottomArea] = NavAreaGrid_GetNearestNavArea(vecCenter, true, nullptr); + + // Find adjacent navigation areas at the top of the ladder + // get approximate postion of player on ladder + xs_vec_add(rgNavLadder[NavLadder_Top], Float:{0.0, 0.0, GenerationStepSize}, vecCenter); + AddDirectionVector(vecCenter, rgNavLadder[NavLadder_Dir], HalfHumanWidth); + + static const Float:flNearLadderRange = 75.0; + + // find "ahead" area + rgNavLadder[NavLadder_TopForwardArea] = FindFirstAreaInDirection(vecCenter, OppositeDirection(rgNavLadder[NavLadder_Dir]), flNearLadderRange, 120.0, pEntity); + if (rgNavLadder[NavLadder_TopForwardArea] == rgNavLadder[NavLadder_BottomArea]) { + rgNavLadder[NavLadder_TopForwardArea] = INVALID_NAV_AREA; + } + + // find "left" area + rgNavLadder[NavLadder_TopLeftArea] = FindFirstAreaInDirection(vecCenter, DirectionLeft(rgNavLadder[NavLadder_Dir]), flNearLadderRange, 120.0, pEntity); + if (rgNavLadder[NavLadder_TopLeftArea] == rgNavLadder[NavLadder_BottomArea]) { + rgNavLadder[NavLadder_TopLeftArea] = INVALID_NAV_AREA; + } + + // find "right" area + rgNavLadder[NavLadder_TopRightArea] = FindFirstAreaInDirection(vecCenter, DirectionRight(rgNavLadder[NavLadder_Dir]), flNearLadderRange, 120.0, pEntity); + if (rgNavLadder[NavLadder_TopRightArea] == rgNavLadder[NavLadder_BottomArea]) { + rgNavLadder[NavLadder_TopRightArea] = INVALID_NAV_AREA; + } + + // find "behind" area - must look farther, since ladder is against the wall away from this area + rgNavLadder[NavLadder_TopBehindArea] = FindFirstAreaInDirection(vecCenter, rgNavLadder[NavLadder_Dir], 2.0 * flNearLadderRange, 120.0, pEntity); + if (rgNavLadder[NavLadder_TopBehindArea] == rgNavLadder[NavLadder_BottomArea]) { + rgNavLadder[NavLadder_TopBehindArea] = INVALID_NAV_AREA; + } + + // can't include behind area, since it is not used when going up a ladder + if (rgNavLadder[NavLadder_BottomArea] == INVALID_NAV_AREA) { + log_amx("ERROR: Unconnected ladder bottom at (%f, %f, %f)", rgNavLadder[NavLadder_Bottom][0], rgNavLadder[NavLadder_Bottom][1], rgNavLadder[NavLadder_Bottom][2]); + return; + } + + if (rgNavLadder[NavLadder_TopForwardArea] == INVALID_NAV_AREA && rgNavLadder[NavLadder_TopLeftArea] == INVALID_NAV_AREA && rgNavLadder[NavLadder_TopRightArea] == INVALID_NAV_AREA) { + log_amx("ERROR: Unconnected ladder top at (%f, %f, %f)", rgNavLadder[NavLadder_Top][0], rgNavLadder[NavLadder_Top][1], rgNavLadder[NavLadder_Top][2]); + return; + } + + // store reference to ladder in the area + if (rgNavLadder[NavLadder_BottomArea] != INVALID_NAV_AREA) { + @NavArea_AddLadderUp(NAVAREA_PTR(rgNavLadder[NavLadder_BottomArea]), rgNavLadder); + } + + // store reference to ladder in the area(s) + if (rgNavLadder[NavLadder_TopForwardArea] != INVALID_NAV_AREA) { + @NavArea_AddLadderDown(NAVAREA_PTR(rgNavLadder[NavLadder_TopForwardArea]), rgNavLadder); + } + + if (rgNavLadder[NavLadder_TopLeftArea] != INVALID_NAV_AREA) { + @NavArea_AddLadderDown(NAVAREA_PTR(rgNavLadder[NavLadder_TopLeftArea]), rgNavLadder); + } + + if (rgNavLadder[NavLadder_TopRightArea] != INVALID_NAV_AREA) { + @NavArea_AddLadderDown(NAVAREA_PTR(rgNavLadder[NavLadder_TopRightArea]), rgNavLadder); + } + + if (rgNavLadder[NavLadder_TopBehindArea] != INVALID_NAV_AREA) { + @NavArea_AddLadderDown(NAVAREA_PTR(rgNavLadder[NavLadder_TopBehindArea]), rgNavLadder); + } + + // adjust top of ladder to highest connected area + new Float:flTopZ = -99999.9; + new bool:bTopAdjusted = false; + + new rgsTopAreaList[NUM_CORNERS]; + rgsTopAreaList[NORTH_WEST] = rgNavLadder[NavLadder_TopForwardArea]; + rgsTopAreaList[NORTH_EAST] = rgNavLadder[NavLadder_TopLeftArea]; + rgsTopAreaList[SOUTH_EAST] = rgNavLadder[NavLadder_TopRightArea]; + rgsTopAreaList[SOUTH_WEST] = rgNavLadder[NavLadder_TopBehindArea]; + + for (new NavCornerType:iCorner = NORTH_WEST; iCorner < NUM_CORNERS; iCorner++) { + new iTopArea = rgsTopAreaList[iCorner]; + if (iTopArea == INVALID_NAV_AREA) continue; + + new Float:vecClose[3]; @NavArea_GetClosestPointOnArea(NAVAREA_PTR(iTopArea), rgNavLadder[NavLadder_Top], vecClose); + + if (flTopZ < vecClose[2]) { + flTopZ = vecClose[2]; + bTopAdjusted = true; + } + } + + if (bTopAdjusted) rgNavLadder[NavLadder_Top][2] = flTopZ; + + // Determine whether this ladder is "dangling" or not + // "Dangling" ladders are too high to go up + rgNavLadder[NavLadder_IsDangling] = false; + if (rgNavLadder[NavLadder_BottomArea]) { + new Float:vecBottomSpot[3]; @NavArea_GetClosestPointOnArea(NAVAREA_PTR(rgNavLadder[NavLadder_BottomArea]), rgNavLadder[NavLadder_Bottom], vecBottomSpot); + if (rgNavLadder[NavLadder_Bottom][2] - vecBottomSpot[2] > HumanHeight) { + rgNavLadder[NavLadder_IsDangling] = true; + } + } + + // add ladder to global list + // new Struct:sNavLadder = @NavLadder_Create(); + // StructSetArray(sNavLadder, 0, rgNavLadder, _:NavLadder); + // ArrayPushCell(g_irgNavLadderList, sNavLadder); +} + +AdjustLadderPositionToBypassBlockages(pEntity, Float:vecPosition[], NavDirType:iDir, const Float:vecAlong[]) { + static const Float:flMinLadderClearance = 32.0; + static const Float:flLadderStep = 10.0; + + new Float:flPathLength = xs_vec_len(vecAlong); + + new Float:vecOn[3]; + new Float:vecOut[3]; + + for (new Float:flPath = 0.0; flPath <= flPathLength; flPath += flLadderStep) { + xs_vec_sub_scaled(vecPosition, vecAlong, flPath, vecOn); + + xs_vec_copy(vecOn, vecOut); + AddDirectionVector(vecOut, iDir, flMinLadderClearance); + + engfunc(EngFunc_TraceLine, vecOn, vecOut, IGNORE_MONSTERS, pEntity, g_pTrace); + + new Float:flFraction; get_tr2(g_pTrace, TR_flFraction, flFraction); + + if (flFraction == 1.0 && !get_tr2(g_pTrace, TR_StartSolid)) { + // found viable ladder pos + xs_vec_copy(vecOn, vecPosition); + break; + } + } +} + +FindFirstAreaInDirection(const Float:vecStart[], NavDirType:iDir, Float:flRange, Float:flBeneathLimit, pIgnoreEnt, Float:vecClosePos[3] = 0.0) { + static iArea; iArea = INVALID_NAV_AREA; + + static Float:vecPos[3]; xs_vec_copy(vecStart, vecPos); + + static iEnd; iEnd = floatround((flRange / GenerationStepSize) + 0.5); + + for (new i = 1; i <= iEnd; i++) { + AddDirectionVector(vecPos, iDir, GenerationStepSize); + + // make sure we dont look thru the wall + engfunc(EngFunc_TraceLine, vecStart, vecPos, IGNORE_MONSTERS, pIgnoreEnt, g_pTrace); + + static Float:flFraction; get_tr2(g_pTrace, TR_flFraction, flFraction); + if (flFraction != 1.0) break; + + iArea = NavAreaGrid_GetNavArea(vecPos, flBeneathLimit); + + if (iArea != INVALID_NAV_AREA) { + xs_vec_set(vecClosePos, vecPos[0], vecPos[1], @NavArea_GetZ(NAVAREA_PTR(iArea), vecPos)); + break; + } + } + + return iArea; +} + +NavAreaPopOpenList() { + if (g_iNavAreaOpenList != INVALID_NAV_AREA) { + static iArea; iArea = g_iNavAreaOpenList; + @NavArea_RemoveFromOpenList(NAVAREA_PTR(iArea)); + return iArea; + } + + return INVALID_NAV_AREA; +} + +NavAreaMakeNewMarker() { + if (++g_iNavAreaMasterMarker == 0) { + g_iNavAreaMasterMarker = 1; + } +} + +NavAreaClearSearchLists() { + NavAreaMakeNewMarker(); + g_iNavAreaOpenList = INVALID_NAV_AREA; +} + +NavAreaBuildPath(const Float:vecStart[], const Float:vecGoal[], iCbFuncId = -1, iCbFuncPluginId = -1, pIgnoreEnt, iUserToken, iCostFuncId = -1, iCostFuncPluginId = -1) { + if (!g_bPrecached) return INVALID_BUILD_PATH_TASK; + + static iStartArea; iStartArea = NavAreaGrid_GetNearestNavArea(vecStart, false, pIgnoreEnt); + if (iStartArea == INVALID_NAV_AREA) return INVALID_BUILD_PATH_TASK; + + static iGoalArea; iGoalArea = NavAreaGrid_GetNearestNavArea(vecGoal, false, pIgnoreEnt); + if (iGoalArea == INVALID_NAV_AREA) return INVALID_BUILD_PATH_TASK; + + static iTask; iTask = FindFreeTaskSlot(); + + if (iTask == INVALID_BUILD_PATH_TASK) return INVALID_BUILD_PATH_TASK; + + @BuildPathTask_Allocate( + TASK_PTR(iTask), + iUserToken, + iStartArea, + iGoalArea, + vecStart, + vecGoal, + pIgnoreEnt, + iCbFuncPluginId, + iCbFuncId, + iCostFuncPluginId, + iCostFuncId + ); + + if (g_irgBuildPathTasksQueue == Invalid_Array) { + g_irgBuildPathTasksQueue = ArrayCreate(); + } + + ArrayPushCell(g_irgBuildPathTasksQueue, iTask); + + return iTask; +} + +bool:NavAreaBuildPathAbortTask(task[BuildPathTask]) { + // if task already in progress + if (g_rgBuildPathJob[BuildPathJob_Task] == task[BuildPathTask_Index]) { + g_rgBuildPathJob[BuildPathJob_Finished] = true; + task[BuildPathTask_IsTerminated] = true; + + // finish task in the same frame + NavAreaBuildPathFinish(); + + return true; + } + + if (g_irgBuildPathTasksQueue == Invalid_Array) return false; + + // remove task from the queue + static iTaskQueueIndex; iTaskQueueIndex = ArrayFindValue(g_irgBuildPathTasksQueue, task[BuildPathTask_Index]); + if (iTaskQueueIndex != -1) { + @BuildPathTask_Free(task); + ArrayDeleteItem(g_irgBuildPathTasksQueue, iTaskQueueIndex); + return true; + } + + return false; +} + +bool:NavAreaBuildPathRunTask(task[BuildPathTask]) { + static iStartArea; iStartArea = task[BuildPathTask_StartArea]; + + g_rgBuildPathJob[BuildPathJob_Task] = task[BuildPathTask_Index]; + g_rgBuildPathJob[BuildPathJob_Finished] = false; + g_rgBuildPathJob[BuildPathJob_Released] = false; + g_rgBuildPathJob[BuildPathJob_MaxIterations] = g_iMaxIterationsPerFrame; + g_rgBuildPathJob[BuildPathJob_ClosestAreaDist] = 999999.0; + g_rgBuildPathJob[BuildPathJob_ClosestArea] = INVALID_NAV_AREA; + + @NavArea_SetParent(NAVAREA_PTR(iStartArea), INVALID_NAV_AREA, NUM_TRAVERSE_TYPES); + + // if we are already in the goal area, build trivial path + if (iStartArea == task[BuildPathTask_GoalArea]) { + @NavArea_SetParent(NAVAREA_PTR(task[BuildPathTask_GoalArea]), INVALID_NAV_AREA, NUM_TRAVERSE_TYPES); + g_rgBuildPathJob[BuildPathJob_ClosestArea] = task[BuildPathTask_GoalArea]; + task[BuildPathTask_IsSuccessed] = true; + g_rgBuildPathJob[BuildPathJob_Finished] = true; + + return true; + } + + // determine actual goal position + if (xs_vec_len(task[BuildPathTask_GoalPos]) > 0.0) { + xs_vec_copy(task[BuildPathTask_GoalPos], task[BuildPathTask_ActualGoalPos]); + } else { + @NavArea_GetCenter(NAVAREA_PTR(task[BuildPathTask_GoalArea]), task[BuildPathTask_ActualGoalPos]); + } + + // start search + NavAreaClearSearchLists(); + + // compute estimate of path length + // TODO: Cost might work as "manhattan distance" + static Float:vecStartAreaCenter[3]; @NavArea_GetCenter(NAVAREA_PTR(iStartArea), vecStartAreaCenter); + @NavArea_SetTotalCost(NAVAREA_PTR(iStartArea), xs_vec_distance(vecStartAreaCenter, task[BuildPathTask_ActualGoalPos])); + + static Float:flInitCost; flInitCost = 0.0; + + if (task[BuildPathTask_CostCallback][Callback_FunctionId] != -1) { + if (callfunc_begin_i(task[BuildPathTask_CostCallback][Callback_FunctionId], task[BuildPathTask_CostCallback][Callback_PluginId])) { + callfunc_push_int(g_rgBuildPathJob[BuildPathJob_Task]); + callfunc_push_int(iStartArea); + callfunc_push_int(INVALID_NAV_AREA); + flInitCost = Float:callfunc_end(); + } + } + + if (flInitCost < 0.0) { + g_rgBuildPathJob[BuildPathJob_Finished] = true; + // task[BuildPathTask_IsTerminated] = true; + return false; + } + + @NavArea_SetCostSoFar(NAVAREA_PTR(iStartArea), flInitCost); + @NavArea_AddToOpenList(NAVAREA_PTR(iStartArea)); + + // keep track of the area we visit that is closest to the goal + g_rgBuildPathJob[BuildPathJob_ClosestArea] = iStartArea; + g_rgBuildPathJob[BuildPathJob_ClosestAreaDist] = @NavArea_GetTotalCost(NAVAREA_PTR(iStartArea)); + + return true; +} + +NavAreaBuildPathFinish() { + new iTask = g_rgBuildPathJob[BuildPathJob_Task]; + g_rgBuildPathTasks[iTask][BuildPathTask_IsFinished] = true; + + // @NavPath_Invalidate(g_rgBuildPathTasks[iTask][BuildPathTask_Path]); + + if (!g_rgBuildPathTasks[iTask][BuildPathTask_IsTerminated]) { + NavAreaBuildPathSegments(); + } + + if (g_rgBuildPathTasks[iTask][BuildPathTask_FinishCallback][Callback_FunctionId] != -1) { + if (callfunc_begin_i(g_rgBuildPathTasks[iTask][BuildPathTask_FinishCallback][Callback_FunctionId], g_rgBuildPathTasks[iTask][BuildPathTask_FinishCallback][Callback_PluginId])) { + callfunc_push_int(iTask); + callfunc_end(); + } + } + + g_rgBuildPathJob[BuildPathJob_Released] = true; +} + +NavAreaBuildPathIteration() { + static iTask; iTask = g_rgBuildPathJob[BuildPathJob_Task]; + + if (g_iNavAreaOpenList == INVALID_NAV_AREA) { + g_rgBuildPathJob[BuildPathJob_Finished] = true; + return; + } + + // get next area to check + static iArea; iArea = NavAreaPopOpenList(); + + // check if we have found the goal area + if (iArea == g_rgBuildPathTasks[iTask][BuildPathTask_GoalArea]) { + if (g_rgBuildPathJob[BuildPathJob_ClosestArea] != INVALID_NAV_AREA) { + g_rgBuildPathJob[BuildPathJob_ClosestArea] = g_rgBuildPathTasks[iTask][BuildPathTask_GoalArea]; + } + + g_rgBuildPathJob[BuildPathJob_Finished] = true; + g_rgBuildPathTasks[iTask][BuildPathTask_IsSuccessed] = true; + + return; + } + + NavAreaBuildPathFloorIteration(TASK_PTR(iTask), NAVAREA_PTR(iArea)); + NavAreaBuildPathLadderUpIteration(TASK_PTR(iTask), NAVAREA_PTR(iArea)); + NavAreaBuildPathLadderDownIteration(TASK_PTR(iTask), NAVAREA_PTR(iArea)); + + // we have searched this area + @NavArea_AddToClosedList(NAVAREA_PTR(iArea)); + + g_rgBuildPathTasks[iTask][BuildPathTask_IterationsNum]++; +} + +NavAreaBuildPathFloorIteration(const task[BuildPathTask], const area[NavArea]) { + static NavDirType:iDir; + + for (iDir = NORTH; iDir < NUM_DIRECTIONS; ++iDir) { + static Array:irgFloorList; irgFloorList = @NavArea_GetAdjacentList(area, iDir); + static iFloorListSize; iFloorListSize = ArraySize(irgFloorList); + + static iFloor; + for (iFloor = 0; iFloor < iFloorListSize; ++iFloor) { + static iNewArea; iNewArea = ArrayGetCell(irgFloorList, iFloor, _:NavConnect_Area); + + if (iNewArea == INVALID_NAV_AREA) continue; + + NavAreaBuildPathProcessNewArea(task, area, NAVAREA_PTR(iNewArea), NavTraverseType:iDir); + } + } +} + +NavAreaBuildPathLadderUpIteration(const task[BuildPathTask], const area[NavArea]) { + static Array:irgUpLadderList; irgUpLadderList = @NavArea_GetLadder(area, LADDER_UP); + static iUpLadderListSize; iUpLadderListSize = ArraySize(irgUpLadderList); + + static iLadder; + for (iLadder = 0; iLadder < iUpLadderListSize; ++iLadder) { + if (ArrayGetCell(irgUpLadderList, iLadder, _:NavLadder_IsDangling)) continue; + + static iLadderTopDir; + for (iLadderTopDir = LADDER_TOP_DIR_AHEAD; iLadderTopDir < NUM_TOP_DIRECTIONS; ++iLadderTopDir) { + static iNewArea; iNewArea = INVALID_NAV_AREA; + + switch (iLadderTopDir) { + case LADDER_TOP_DIR_AHEAD: { + iNewArea = ArrayGetCell(irgUpLadderList, iLadder, _:NavLadder_TopForwardArea); + } + case LADDER_TOP_DIR_LEFT: { + iNewArea = ArrayGetCell(irgUpLadderList, iLadder, _:NavLadder_TopLeftArea); + } + case LADDER_TOP_DIR_RIGHT: { + iNewArea = ArrayGetCell(irgUpLadderList, iLadder, _:NavLadder_TopRightArea); + } + } + + if (iNewArea == INVALID_NAV_AREA) continue; + + NavAreaBuildPathProcessNewArea(task, area, NAVAREA_PTR(iNewArea), GO_LADDER_UP); + } + } +} + +NavAreaBuildPathLadderDownIteration(const task[BuildPathTask], const area[NavArea]) { + static Array:irgDownLadderList; irgDownLadderList = @NavArea_GetLadder(area, LADDER_DOWN); + static iDownLadderListSize; iDownLadderListSize = ArraySize(irgDownLadderList); + + static iLadder; + for (iLadder = 0; iLadder < iDownLadderListSize; ++iLadder) { + static iNewArea; iNewArea = ArrayGetCell(irgDownLadderList, iLadder, _:NavLadder_BottomArea); + + if (iNewArea == INVALID_NAV_AREA) continue; + + NavAreaBuildPathProcessNewArea(task, area, NAVAREA_PTR(iNewArea), GO_LADDER_DOWN); + } +} + +bool:NavAreaBuildPathProcessNewArea(const task[BuildPathTask], const area[NavArea], newArea[NavArea], const NavTraverseType:iHow) { + if (NAVAREA_INDEX(newArea) == NAVAREA_INDEX(area)) return false; + + static Float:flCost; flCost = 0.0; + + if (task[BuildPathTask_CostCallback][Callback_FunctionId] != -1) { + if (callfunc_begin_i(task[BuildPathTask_CostCallback][Callback_FunctionId], task[BuildPathTask_CostCallback][Callback_PluginId])) { + callfunc_push_int(task[BuildPathTask_Index]); + callfunc_push_int(NAVAREA_INDEX(newArea)); + callfunc_push_int(NAVAREA_INDEX(area)); + flCost = Float:callfunc_end(); + } + } + + // check if cost functor says this newArea is a dead-end + if (flCost < 0.0) return false; + + static Float:flNewCostSoFar; flNewCostSoFar = @NavArea_GetCostSoFar(area) + flCost; + + if ((@NavArea_IsOpen(newArea) || @NavArea_IsClosed(newArea)) && @NavArea_GetCostSoFar(newArea) <= flNewCostSoFar) { + // this is a worse path - skip it + return false; + } + + // compute estimate of distance left to go + static Float:vecNewAreaCenter[3]; @NavArea_GetCenter(newArea, vecNewAreaCenter); + static Float:flNewCostRemaining; flNewCostRemaining = xs_vec_distance(vecNewAreaCenter, task[BuildPathTask_ActualGoalPos]); + + // track closest area to goal in case path fails + if (g_rgBuildPathJob[BuildPathJob_ClosestArea] != INVALID_NAV_AREA && flNewCostRemaining < g_rgBuildPathJob[BuildPathJob_ClosestAreaDist]) { + g_rgBuildPathJob[BuildPathJob_ClosestArea] = NAVAREA_INDEX(newArea); + g_rgBuildPathJob[BuildPathJob_ClosestAreaDist] = flNewCostRemaining; + } + + @NavArea_SetParent(newArea, NAVAREA_INDEX(area), iHow); + @NavArea_SetCostSoFar(newArea, flNewCostSoFar); + @NavArea_SetTotalCost(newArea, flNewCostSoFar + flNewCostRemaining); + + if (@NavArea_IsClosed(newArea)) { + @NavArea_RemoveFromClosedList(newArea); + } + + if (@NavArea_IsOpen(newArea)) { + // area already on open list, update the list order to keep costs sorted + @NavArea_UpdateOnOpenList(newArea); + } else { + @NavArea_AddToOpenList(newArea); + } + + return true; +} + +NavAreaBuildPathFrame() { + if (g_rgBuildPathJob[BuildPathJob_Task] != INVALID_BUILD_PATH_TASK) { + if (g_rgBuildPathJob[BuildPathJob_Finished] && g_rgBuildPathJob[BuildPathJob_Released]) { + @BuildPathTask_Free(TASK_PTR(g_rgBuildPathJob[BuildPathJob_Task])); + g_rgBuildPathJob[BuildPathJob_Task] = INVALID_BUILD_PATH_TASK; + } + } + + // if no job in progress then find new task to start + if (g_rgBuildPathJob[BuildPathJob_Task] == INVALID_BUILD_PATH_TASK) { + if (g_irgBuildPathTasksQueue != Invalid_Array && ArraySize(g_irgBuildPathTasksQueue)) { + static iTask; iTask = ArrayGetCell(g_irgBuildPathTasksQueue, 0); + ArrayDeleteItem(g_irgBuildPathTasksQueue, 0); + NavAreaBuildPathRunTask(TASK_PTR(iTask)); + } + + return; + } + + // do path finding iterations + static iIterationsNum; iIterationsNum = g_rgBuildPathJob[BuildPathJob_MaxIterations]; + for (new i = 0; i < iIterationsNum && !g_rgBuildPathJob[BuildPathJob_Finished]; ++i) { + NavAreaBuildPathIteration(); + } + + // current job finished, process + if (g_rgBuildPathJob[BuildPathJob_Finished]) { + NavAreaBuildPathFinish(); + } +} + +NavAreaBuildPathSegments() { + static iTask; iTask = g_rgBuildPathJob[BuildPathJob_Task]; + static Struct:sNavPath; sNavPath = g_rgBuildPathTasks[iTask][BuildPathTask_Path]; + + static iSegmentCount; iSegmentCount = 0; + + static iEffectiveGoalArea; iEffectiveGoalArea = ( + g_rgBuildPathTasks[iTask][BuildPathTask_IsSuccessed] + ? g_rgBuildPathTasks[iTask][BuildPathTask_GoalArea] + : g_rgBuildPathJob[BuildPathJob_ClosestArea] + ); + + if (g_rgBuildPathTasks[iTask][BuildPathTask_StartArea] != g_rgBuildPathTasks[iTask][BuildPathTask_GoalArea]) { + // save room for endpoint + if (iEffectiveGoalArea != INVALID_NAV_AREA) { + iSegmentCount = NavAreaCalculateSegmentCount(NAVAREA_PTR(iEffectiveGoalArea)); + iSegmentCount = min(iSegmentCount, MAX_PATH_SEGMENTS - 1); + } + } else { + iSegmentCount = 1; + } + + if (iSegmentCount == 0) { + @NavPath_Invalidate(sNavPath); + return false; + } + + static Array:irgSegments; irgSegments = StructGetCell(sNavPath, NavPath_Segments); + ArrayResize(irgSegments, iSegmentCount); + StructSetCell(sNavPath, NavPath_SegmentCount, iSegmentCount); + + if (iSegmentCount > 1) { + // Prepare segments + static iArea; iArea = iEffectiveGoalArea; + + for (new iSegment = iSegmentCount - 1; iSegment >= 0; --iSegment) { + ArraySetCell(irgSegments, iSegment, iArea, _:PathSegment_Area); + ArraySetCell(irgSegments, iSegment, NAVAREA_PTR(iArea)[NavArea_ParentHow], _:PathSegment_How); + + iArea = NAVAREA_PTR(iArea)[NavArea_Parent]; + } + + if (!@NavPath_ComputePathPositions(sNavPath)) { + @NavPath_Invalidate(sNavPath); + return false; + } + + // append path end position + static rgEndSegment[PathSegment]; + rgEndSegment[PathSegment_Area] = iEffectiveGoalArea; + rgEndSegment[PathSegment_How] = NUM_TRAVERSE_TYPES; + xs_vec_set(rgEndSegment[PathSegment_Pos], g_rgBuildPathTasks[iTask][BuildPathTask_GoalPos][0], g_rgBuildPathTasks[iTask][BuildPathTask_GoalPos][1], @NavArea_GetZ(NAVAREA_PTR(iEffectiveGoalArea), g_rgBuildPathTasks[iTask][BuildPathTask_GoalPos])); + @NavPath_PushSegment(sNavPath, rgEndSegment); + iSegmentCount++; + } else { + @NavPath_BuildTrivialPath(sNavPath, g_rgBuildPathTasks[iTask][BuildPathTask_StartPos], g_rgBuildPathTasks[iTask][BuildPathTask_GoalPos]); + } + + if (g_bDebug) { + for (new iSegment = 1; iSegment < iSegmentCount; ++iSegment) { + static Float:vecSrc[3]; ArrayGetArray2(irgSegments, iSegment - 1, vecSrc, 3, PathSegment_Pos); + static Float:vecNext[3]; ArrayGetArray2(irgSegments, iSegment, vecNext, 3, PathSegment_Pos); + + static irgColor[3]; + irgColor[0] = floatround(255.0 * (1.0 - (float(iSegment) / iSegmentCount))); + irgColor[1] = floatround(255.0 * (float(iSegment) / iSegmentCount)); + irgColor[2] = 0; + + UTIL_DrawArrow(0, vecSrc, vecNext, irgColor, 255, 30); + } + } + + return true; +} + +NavAreaCalculateSegmentCount(const goalArea[NavArea]) { + static iCount; iCount = 0; + + static iArea; iArea = NAVAREA_INDEX(goalArea); + + while (iArea != INVALID_NAV_AREA) { + iCount++; + iArea = NAVAREA_PTR(iArea)[NavArea_Parent]; + } + + return iCount; +} + +FindFreeTaskSlot() { + for (new iTask; iTask < MAX_NAV_PATH_TASKS; ++iTask) { + if (g_rgBuildPathTasks[iTask][BuildPathTask_IsFree]) { + return iTask; + } + } + + return INVALID_BUILD_PATH_TASK; +} + +// Can we see this area? +// For now, if we can see any corner, we can see the area +// TODO: Need to check LOS to more than the corners for large and/or long areas +stock bool:IsAreaVisible(const Float:vecPos[], const area[NavArea]) { + for (new NavCornerType:iCorner = NORTH_WEST; iCorner < NUM_CORNERS; iCorner++) { + static Float:vecCorner[3]; + @NavArea_GetCorner(area, iCorner, vecCorner); + vecCorner[2] += 0.75 * HumanHeight; + + engfunc(EngFunc_TraceLine, vecPos, vecCorner, IGNORE_MONSTERS, nullptr, g_pTrace); + + static Float:flFraction; get_tr2(g_pTrace, TR_flFraction, flFraction); + if (flFraction == 1.0) return true; + } + + return false; +} + +stock bool:IsEntityWalkable(pEntity, iFlags) { + static szClassName[32]; pev(pEntity, pev_classname, szClassName, charsmax(szClassName)); + + // if we hit a door, assume its walkable because it will open when we touch it + if (equal(szClassName, "func_door") || equal(szClassName, "func_door_rotating")) { + return !!(iFlags & WALK_THRU_DOORS); + } + + if (equal(szClassName, "func_breakable")) { + // if we hit a breakable object, assume its walkable because we will shoot it when we touch it + static Float:flTakeDamage; pev(pEntity, pev_takedamage, flTakeDamage); + if (flTakeDamage == DAMAGE_YES) { + return !!(iFlags & WALK_THRU_BREAKABLES); + } + } + + return false; +} + +stock Float:GetGroundHeight(const Float:vecPos[], pIgnoreEnt, Float:vecNormal[] = {0.0, 0.0, 0.0}) { + enum GroundLayerInfo { + Float:GroundLayerInfo_Ground, + Float:GroundLayerInfo_Normal[3] + }; + + static Float:vecFrom[3]; xs_vec_copy(vecPos, vecFrom); + static Float:vecTo[3]; xs_vec_set(vecTo, vecPos[0], vecPos[1], -8192.0); + + static const Float:flMaxOffset = 100.0; + static const Float:flInc = 10.0; + + static rgLayer[MAX_NAV_GROUND_LAYERS][GroundLayerInfo]; + static iLayerCount; iLayerCount = 0; + + static pIgnore; pIgnore = pIgnoreEnt; + static Float:flOffset; + + for (flOffset = 1.0; flOffset < flMaxOffset; flOffset += flInc) { + vecFrom[2] = vecPos[2] + flOffset; + + engfunc(EngFunc_TraceLine, vecFrom, vecTo, IGNORE_MONSTERS, pIgnore, g_pTrace); + + static Float:flFraction; get_tr2(g_pTrace, TR_flFraction, flFraction); + + if (flFraction != 1.0) { + static pHit; pHit = get_tr2(g_pTrace, TR_pHit); + if (pHit > 0) { + // ignoring any entities that we can walk through + if (IsEntityWalkable(pHit, WALK_THRU_DOORS | WALK_THRU_BREAKABLES)) { + pIgnore = pHit; + continue; + } + } + } + + static bool:bStartSolid; bStartSolid = bool:get_tr2(g_pTrace, TR_StartSolid); + + if (!bStartSolid) { + static Float:vecEndPos[3]; get_tr2(g_pTrace, TR_vecEndPos, vecEndPos); + + if (iLayerCount == 0 || vecEndPos[2] > rgLayer[iLayerCount - 1][GroundLayerInfo_Ground]) { + static Float:vecPlaneNormal[3]; get_tr2(g_pTrace, TR_vecPlaneNormal, vecPlaneNormal); + + rgLayer[iLayerCount][GroundLayerInfo_Ground] = vecEndPos[2]; + xs_vec_copy(vecPlaneNormal, rgLayer[iLayerCount][GroundLayerInfo_Normal]); + iLayerCount++; + + if (iLayerCount == MAX_NAV_GROUND_LAYERS) break; + } + } + } + + if (!iLayerCount) return -1.0; + + static i; + for (i = 0; i < iLayerCount - 1; i++) { + if (rgLayer[i + 1][GroundLayerInfo_Ground] - rgLayer[i][GroundLayerInfo_Ground] >= HalfHumanHeight) { + break; + } + } + + xs_vec_copy(rgLayer[i][GroundLayerInfo_Normal], vecNormal); + + return rgLayer[i][GroundLayerInfo_Ground]; +} + +stock NavDirType:OppositeDirection(NavDirType:iDir) { + switch (iDir) { + case NORTH: return SOUTH; + case EAST: return WEST; + case SOUTH: return NORTH; + case WEST: return EAST; + } + + return NORTH; +} + +stock NavDirType:DirectionLeft(NavDirType:iDir) { + switch (iDir) { + case NORTH: return WEST; + case SOUTH: return EAST; + case EAST: return NORTH; + case WEST: return SOUTH; + } + + return NORTH; +} + +stock NavDirType:DirectionRight(NavDirType:iDir) { + switch (iDir) { + case NORTH: return EAST; + case SOUTH: return WEST; + case EAST: return SOUTH; + case WEST: return NORTH; + } + + return NORTH; +} + +stock AddDirectionVector(Float:vecInput[], NavDirType:iDir, Float:flAmount) { + switch (iDir) { + case NORTH: vecInput[1] -= flAmount; + case SOUTH: vecInput[1] += flAmount; + case EAST: vecInput[0] += flAmount; + case WEST: vecInput[0] -= flAmount; + } +} + +stock DirectionToVector2D(NavDirType:iDir, Float:vecOutput[]) { + switch (iDir) { + case NORTH: { + vecOutput[0] = 0.0; + vecOutput[1] = -1.0; + } + case SOUTH: { + vecOutput[0] = 0.0; + vecOutput[1] = 1.0; + } + case EAST: { + vecOutput[0] = 1.0; + vecOutput[1] = 0.0; + } + case WEST: { + vecOutput[0] = -1.0; + vecOutput[1] = 0.0; + } + } +} + +stock Float:NormalizeInPlace(const Float:vecSrc[], Float:vecOut[]) { + static Float:flLen; flLen = xs_vec_len(vecSrc); + + if (flLen > 0) { + vecOut[0] = vecSrc[0] / flLen; + vecOut[1] = vecSrc[1] / flLen; + vecOut[2] = vecSrc[2] / flLen; + } else { + vecOut[0] = 0.0; + vecOut[1] = 0.0; + vecOut[2] = 1.0; + } + + return flLen; +} + +stock UTIL_DrawArrow(pPlayer, const Float:vecSrc[], const Float:vecTarget[], const irgColor[3] = {255, 255, 255}, iBrightness = 255, iLifeTime = 10, iWidth = 64) { + engfunc(EngFunc_MessageBegin, pPlayer ? MSG_ONE : MSG_ALL, SVC_TEMPENTITY, vecSrc, pPlayer); + write_byte(TE_BEAMPOINTS); + engfunc(EngFunc_WriteCoord, vecTarget[0]); + engfunc(EngFunc_WriteCoord, vecTarget[1]); + engfunc(EngFunc_WriteCoord, vecTarget[2] + 16.0); + engfunc(EngFunc_WriteCoord, vecSrc[0]); + engfunc(EngFunc_WriteCoord, vecSrc[1]); + engfunc(EngFunc_WriteCoord, vecSrc[2] + 16.0); + write_short(g_iArrowModelIndex); + write_byte(0); + write_byte(0); + write_byte(iLifeTime); + write_byte(iWidth); + write_byte(0); + write_byte(irgColor[0]); + write_byte(irgColor[1]); + write_byte(irgColor[2]); + write_byte(iBrightness); + write_byte(0); + message_end(); +} + +stock ArrayGetArray2(const &Array:irgArray, any:iItem, any:rgOut[], any:iSize, any:iBlock) { + for (new i = 0; i < iSize; ++i) { + rgOut[i] = ArrayGetCell(irgArray, iItem, iBlock + i); + } +} + +stock ArraySetArray2(const &Array:irgArray, any:iItem, const any:rgValue[], any:iSize, any:iBlock) { + for (new i = 0; i < iSize; ++i) { + ArraySetCell(irgArray, iItem, rgValue[i], iBlock + i); + } +} diff --git a/api/navsystem/include/api_navsystem.inc b/api/navsystem/include/api_navsystem.inc new file mode 100644 index 0000000..b00b8c9 --- /dev/null +++ b/api/navsystem/include/api_navsystem.inc @@ -0,0 +1,95 @@ +#if defined _api_navsystem_included + #endinput +#endif +#define _api_navsystem_included + +#pragma reqlib api_navsystem + +#include + +enum NavArea { + Invalid_NavArea = -1 +}; + +enum NavBuildPathTask { + Invalid_NavBuildPathTask = -1 +}; + +enum NavPath { + Invalid_NavPath = 0 +}; + +native Nav_Precache(); + +native Nav_GetAreaCount(); +native Nav_GetArea(iIndex); +native Nav_GetAreaById(iId); +native NavArea:Nav_GetAreaFromGrid(Float:vecPos[3], Float:flBeneathLimit = 120.0); +native Nav_WorldToGridX(Float:flValue); +native Nav_WorldToGridY(Float:flValue); +native NavArea:Nav_FindFirstAreaInDirection(const Float:vecStart[3], NavDirType:iDir, Float:flRange, Float:flBeneathLimit, pIgnoreEnt, Float:vecClosePos[3]); +native bool:Nav_IsAreaVisible(const Float:vecPos[3], NavArea:pArea); +native NavArea:Nav_GetNearestArea(const Float:vecPos[3], bool:bAnyZ = false, pIgnoreEnt = nullptr, NavArea:pIgnoreArea = Invalid_NavArea); + +native Nav_Area_GetId(NavArea:pArea); +native NavAttributeType:Nav_Area_GetAttributes(NavArea:pArea); +native NavTraverseType:Nav_Area_GetParentHow(NavArea:pArea); +native Nav_Area_GetCenter(NavArea:pArea, Float:vecOut[3]); +native bool:Nav_Area_Contains(NavArea:pArea, const Float:vecPoint[3]); +native bool:Nav_Area_IsCoplanar(NavArea:pArea, NavArea:pOther); +native Float:Nav_Area_GetZ(NavArea:pArea); +native Nav_Area_GetClosestPointOnArea(NavArea:pArea, const Float:vecPoint[3], Float:vecOut[3]); +native Float:Nav_Area_GetDistanceSquaredToPoint(NavArea:pArea, const Float:vecPoint[3]); +native NavArea:Nav_Area_GetRandomAdjacentArea(NavArea:pArea, NavDirType:iDir); +native bool:Nav_Area_IsEdge(NavArea:pArea, NavDirType:iDir); +native bool:Nav_Area_IsConnected(NavArea:pArea, NavArea:pOther, NavDirType:iDir); +native Nav_Area_GetCorner(NavArea:pArea, NavCornerType:iCorner, Float:vecPos[3]); +native NavDirType:Nav_Area_ComputeDirection(NavArea:pArea, const Float:vecPoint[3]); +native Nav_Area_ComputePortal(NavArea:pAreas, NavArea:pOther, NavDirType:iDir, Float:vecCenter[3], &Float:flHalfWidth); +native bool:Nav_Area_IsOverlapping(NavArea:pArea, NavArea:pOther); +native bool:Nav_Area_IsOverlappingPoint(NavArea:pArea, const Float:vecPoint[3]); +native Float:Nav_Area_GetCostSoFar(NavArea:pArea); + +native bool:Nav_Path_IsValid(NavPath:pPath); +native Nav_Path_GetSegmentCount(NavPath:pPath); +native Nav_Path_GetSegmentPos(NavPath:pPath, iSegment, Float:vecOut[3]); +native NavTraverseType:Nav_Path_GetSegmentHow(NavPath:pPath, iSegment); +native NavArea:Nav_Path_GetSegmentArea(NavPath:pPath, iSegment); +native bool:Nav_Path_FindClosestPoint(NavPath:pPath, const Float:vecWorldPos[3], iStartIndex, iEndIndex, Float:vecClose[3]); + +/** + * Use the A* algorithm to find the shortest path asynchronous + * + * The callback function should be prototyped as: + * + * public Float:(NavBuildPathTask:pTask) + * NavBuildPathTask:pTask - The handler of the current task + * + * + * The cost function should be prototyped as: + * + * public Float:(NavBuildPathTask:pTask, NavArea:nextArea, NavArea:prevArea) + * NavBuildPathTask:pTask - The handler of the current task + * NavArea:nextArea - Next area to check + * NavArea:prevArea - Previous area (Invalid_NavArea on fist call) + * The cost function should return a floating value as an estimatie of the step's cost + * If the cost function returns -1.0 for an area, that area is considered a dead end. + * + * @param Float:vecStart[3] Start origin + * @param Float:vecGoal[3] Goal origin + * @param szCbFunc[] Callback function + * @param pIgnoreEntity Ignore entity for tracelines + * @param iUserToken User integer value to identify the task in the future + * @param szCostFunc[] Cost function + * + * @return Task handler +*/ +native NavBuildPathTask:Nav_Path_Find(const Float:vecStart[3], const Float:vecGoal[3], const szCbFunc[] = "", pIgnoreEntity = 0, any:iUserToken = 0, const szCostFunc[] = ""); +native bool:Nav_Path_FindTask_Await(NavBuildPathTask:pTask); +native bool:Nav_Path_FindTask_Abort(NavBuildPathTask:pTask); +native NavPath:Nav_Path_FindTask_GetPath(NavBuildPathTask:pTask); +native bool:Nav_Path_FindTask_IsFinished(NavBuildPathTask:pTask); +native bool:Nav_Path_FindTask_IsSuccessed(NavBuildPathTask:pTask); +native bool:Nav_Path_FindTask_IsTerminated(NavBuildPathTask:pTask); +native any:Nav_Path_FindTask_GetUserToken(NavBuildPathTask:pTask); +native any:Nav_Path_FindTask_GetIterationsNum(NavBuildPathTask:pTask); diff --git a/api/navsystem/include/api_navsystem_const.inc b/api/navsystem/include/api_navsystem_const.inc new file mode 100644 index 0000000..f42ea59 --- /dev/null +++ b/api/navsystem/include/api_navsystem_const.inc @@ -0,0 +1,86 @@ +#if defined _api_navsystem_const_included + #endinput +#endif +#define _api_navsystem_const_included + +#define NAV_MAGIC_NUMBER 0xFEEDFACE +#define NAV_VERSION 5 +#define MAX_AREA_TEAMS 2 +#define MAX_APPROACH_AREAS 16 +#define nullptr -1 +#define HASH_TABLE_SIZE 256 +#define MAX_PATH_SEGMENTS 256 +#define MAX_NAV_AREAS 4096 +#define MAX_NAV_PATH_TASKS 1024 +#define MAX_NAV_GROUND_LAYERS 16 + +#define UNDEFINED_PLACE 0 +#define ANY_PLACE 0xFFFF + +#define WALK_THRU_DOORS 0x01 +#define WALK_THRU_BREAKABLES 0x02 +#define WALK_THRU_EVERYTHING (WALK_THRU_DOORS | WALK_THRU_BREAKABLES) + +enum NavErrorType { + NAV_OK, + NAV_CANT_ACCESS_FILE, + NAV_INVALID_FILE, + NAV_BAD_FILE_VERSION, + NAV_CORRUPT_DATA, +}; + +enum NavAttributeType { + NAV_CROUCH = 0x01, // must crouch to use this node/area + NAV_JUMP = 0x02, // must jump to traverse this area + NAV_PRECISE = 0x04, // do not adjust for obstacles, just move along area + NAV_NO_JUMP = 0x08, // inhibit discontinuity jumping +}; + +enum NavDirType { + NORTH = 0, + EAST, + SOUTH, + WEST, + + NUM_DIRECTIONS +}; + +// Defines possible ways to move from one area to another +enum NavTraverseType { + // NOTE: First 4 directions MUST match NavDirType + GO_NORTH = 0, + GO_EAST, + GO_SOUTH, + GO_WEST, + GO_LADDER_UP, + GO_LADDER_DOWN, + GO_JUMP, + + NUM_TRAVERSE_TYPES +}; + +enum NavCornerType { + NORTH_WEST = 0, + NORTH_EAST, + SOUTH_EAST, + SOUTH_WEST, + + NUM_CORNERS +}; + +enum NavRelativeDirType { + FORWARD = 0, + RIGHT, + BACKWARD, + LEFT, + UP, + DOWN, + + NUM_RELATIVE_DIRECTIONS +}; + +enum LadderDirectionType { + LADDER_UP = 0, + LADDER_DOWN, + NUM_LADDER_DIRECTIONS +}; diff --git a/api/particles/README.md b/api/particles/README.md new file mode 100644 index 0000000..9fc2b8a --- /dev/null +++ b/api/particles/README.md @@ -0,0 +1,149 @@ +# Particles API +This Particle System API is designed to implement particle effects. The API provides functions to register particle effects, create and manipulate particle systems, and work with individual particles. + +## Registering a New Particle Effect + +To register a new particle effect, use the `ParticleEffect_Register` function. + +```pawn +ParticleEffect_Register("my-effect", EMIT_RATE, PARTICLE_LIFETIME, MAX_PARTICLES); +``` + +- `EMIT_RATE`: Emit rate of particles per second. +- `PARTICLE_LIFETIME`: Lifetime of each particle in seconds. +- `MAX_PARTICLES`: Maximum number of particles in the effect. + +## Controlling Particle Effects + +To control your effect you need hooks. Use the `ParticleEffect_RegisterHook` function with `ParticleEffectHook_` constants to hook events. + +```pawn +ParticleEffect_RegisterHook("my-effect", ParticleEffectHook_Particle_Think, "@Effect_Particle_Think"); +``` + +## Spawning the Particle Effect System + +To spawn the system at a specific origin use the `ParticleSystem_Create` function. + +```pawn +new ParticleSystem:sParticleSystem = ParticleSystem_Create("my-effect", vecOrigin); +``` + +## Removing the Particle Effect System + +To remove and free memory, destroy the system using `ParticleSystem_Destroy`. + +```pawn +ParticleSystem_Destroy(sParticleSystem); +``` + +## Enabling/Disabling the Particle Effect System + +Enable or disable the particle system with `ParticleSystem_Activate` and `ParticleSystem_Deactivate` methods. + +```pawn +ParticleSystem_Activate(sParticleSystem); +ParticleSystem_Deactivate(sParticleSystem); +``` + +## Simple Particle Effect Example + +Here is a simple example demonstrating API functionality: + +![Simple Particle Effect](../../images/example-particle-effect.gif) + +```pawn +#include +#include +#include + +#include + +#define EFFECT_PARTICLE_LIFETIME 1.5 +#define EFFECT_EMIT_RATE 0.01 +#define EFFECT_MAX_PARTICLES floatround(EFFECT_PARTICLE_LIFETIME / EFFECT_EMIT_RATE, floatround_ceil) + +// Particle model for the effect +new g_szParticleModel[] = "sprites/animglow01.spr"; + +public plugin_precache() { + // Precache the particle model + precache_model(g_szParticleModel); + + // Register the particle effect + ParticleEffect_Register("colored-circle", EFFECT_EMIT_RATE, EFFECT_PARTICLE_LIFETIME, EFFECT_MAX_PARTICLES); + + // Register hooks for the particle effect + ParticleEffect_RegisterHook("colored-circle", ParticleEffectHook_System_Init, "@Effect_System_Init"); + ParticleEffect_RegisterHook("colored-circle", ParticleEffectHook_Particle_Think, "@Effect_Particle_Think"); + ParticleEffect_RegisterHook("colored-circle", ParticleEffectHook_Particle_EntityInit, "@Effect_Particle_EntityInit"); +} + +public plugin_init() { + // Plugin initialization + register_plugin("[Particle] Colored Circle", "1.0.0", "Hedgehog Fog"); +} + +// Hook callback for system initialization +@Effect_System_Init(ParticleSystem:this) { + // Set additional system parameters + ParticleSystem_SetMember(this, "flRadius", 48.0); +} + +// Hook callback for particle thinking +@Effect_Particle_Think(Particle:this) { + // Get relevant time and system information + static Float:flGameTime; flGameTime = get_gametime(); + static ParticleSystem:sSystem; sSystem = Particle_GetSystem(this); + static Float:flCreatedTime; flCreatedTime = Particle_GetCreatedTime(this); + static Float:flKillTime; flKillTime = Particle_GetKillTime(this); + static Float:flTimeRatio; flTimeRatio = (flGameTime - flCreatedTime) / (flKillTime - flCreatedTime); + + // Calculate particle velocity in a circular pattern + static Float:flAngle; flAngle = 2 * M_PI * flTimeRatio + static Float:flRadius; flRadius = ParticleSystem_GetMember(sSystem, "flRadius"); + + static Float:vecVelocity[3]; + vecVelocity[0] = flRadius * floatcos(flAngle); + vecVelocity[1] = flRadius * floatsin(flAngle); + vecVelocity[2] = 0.0; + + // Set the calculated velocity to create circular motion + Particle_SetVelocity(this, vecVelocity); +} + +// Hook callback for particle entity initialization +@Effect_Particle_EntityInit(Particle:this, pEntity) { + // Set up rendering properties for each particle entity + static iModelIndex; iModelIndex = engfunc(EngFunc_ModelIndex, g_szParticleModel); + + set_pev(pEntity, pev_rendermode, kRenderTransAdd); + set_pev(pEntity, pev_renderfx, kRenderFxLightMultiplier); + set_pev(pEntity, pev_scale, 0.065); + set_pev(pEntity, pev_modelindex, iModelIndex); + set_pev(pEntity, pev_renderamt, 220.0); + set_pev(pEntity, pev_framerate, 1.0); + + engfunc(EngFunc_SetModel, pEntity, g_szParticleModel); + + // Randomize color for each particle entity + static Float:rgflColor[3]; + for (new i = 0; i < 3; ++i) rgflColor[i] = random_float(0.0, 255.0); + + set_pev(pEntity, pev_rendercolor, rgflColor); +} +``` + + +Certainly! Here's an improved version: + +## Testing Your Effect In-Game + +To evaluate and visualize your particle effect within the game environment, you can use the `particle_create` console command, specifying the name of your effect as the first argument. + +**Example:** +```bash +particle_create "colored-circle" +``` + +This command triggers the creation and rendering of the "colored-circle" particle effect, allowing you to assess its appearance and behavior in real-time. diff --git a/api/particles/api_particles.sma b/api/particles/api_particles.sma new file mode 100644 index 0000000..a75c98f --- /dev/null +++ b/api/particles/api_particles.sma @@ -0,0 +1,1114 @@ +#pragma semicolon 1 + +#include +#include +#include +#include +#include + +#include + +#include + +#define PLUGIN "[API] Particles" +#define VERSION "1.0.0" +#define AUTHOR "Hedgehog Fog" + +#define BIT(%0) (1<<(%0)) + +#define PARTICLE_CLASSNAME "_particle" + +#define UPDATE_RATE 0.01 +#define VISIBILITY_UPDATE_RATE 0.25 + +enum Callback { + Callback_PluginId, + Callback_FunctionId +}; + +enum PositionVars { + Float:PositionVars_Origin[3], + Float:PositionVars_Angles[3], + Float:PositionVars_Velocity[3] +}; + +enum ParticleEffect { + ParticleEffect_Id[32], + ParticleEffect_EmitAmount, + Float:ParticleEffect_EmitRate, + Float:ParticleEffect_ParticleLifeTime, + ParticleEffect_VisibilityDistance, + ParticleEffect_MaxParticles, + ParticleEffectFlag:ParticleEffect_Flags, + Array:ParticleEffect_Hooks[ParticleEffectHook] +}; + +enum ParticleSystem { + Struct:ParticleSystem_Effect, + bool:ParticleSystem_Active, + Float:ParticleSystem_EffectSpeed, + ParticleSystem_ParentEntity, + Float:ParticleSystem_CreatedTime, + ParticleSystem_VisibilityBits, + Float:ParticleSystem_KillTime, + Array:ParticleSystem_Particles, + Float:ParticleSystem_NextEmit, + Float:ParticleSystem_NextVisibilityUpdate, + Float:ParticleSystem_LastThink, + Trie:ParticleSystem_Members, + ParticleSystem_PositionVars[PositionVars] +}; + +enum Particle { + Particle_Index, + Particle_BatchIndex, + Particle_Entity, + Float:Particle_CreatedTime, + Float:Particle_KillTime, + Float:Particle_LastThink, + Struct:Particle_System, + bool:Particle_Attached, + Particle_PositionVars[PositionVars], + Particle_AbsPositionVars[PositionVars] +}; + +new g_pCvarEnabled; +new bool:g_bEnabled; + +new g_iszParticleClassName; +new g_pTrace; + +new Float:g_flNextSystemsUpdate; + +new Array:g_irgSystems; +new Trie:g_tParticleEffects; + +public plugin_precache() { + g_flNextSystemsUpdate = 0.0; + g_irgSystems = ArrayCreate(); + g_tParticleEffects = TrieCreate(); + g_iszParticleClassName = engfunc(EngFunc_AllocString, "info_target"); + g_pTrace = create_tr2(); +} + +public plugin_init() { + register_plugin(PLUGIN, VERSION, AUTHOR); + + register_forward(FM_AddToFullPack, "FMHook_AddToFullPack", 0); + + g_pCvarEnabled = register_cvar("particles", "1"); + bind_pcvar_num(g_pCvarEnabled, g_bEnabled); + hook_cvar_change(g_pCvarEnabled, "CvarHook_Enabled"); + + register_concmd("particle_create", "Command_Create", ADMIN_CVAR); +} + +public plugin_natives() { + register_library("api_particles"); + + register_native("ParticleEffect_Register", "Native_RegisterParticleEffect"); + register_native("ParticleEffect_RegisterHook", "Native_RegisterParticleEffectHook"); + + register_native("ParticleSystem_Create", "Native_CreateParticleSystem"); + register_native("ParticleSystem_Destroy", "Native_DestroyParticleSystem"); + register_native("ParticleSystem_Activate", "Native_ActivateParticleSystem"); + register_native("ParticleSystem_Deactivate", "Native_DeactivateParticleSystem"); + register_native("ParticleSystem_GetEffectSpeed", "Native_GetParticleSystemEffectSpeed"); + register_native("ParticleSystem_SetEffectSpeed", "Native_SetParticleSystemEffectSpeed"); + register_native("ParticleSystem_GetCreatedTime", "Native_GetParticleSystemCreatedTime"); + register_native("ParticleSystem_GetKillTime", "Native_GetParticleSystemKillTime"); + register_native("ParticleSystem_GetLastThinkTime", "Native_GetParticleSystemLastThink"); + register_native("ParticleSystem_GetVisibilityBits", "Native_GetParticleSystemVisibilityBits"); + register_native("ParticleSystem_GetOrigin", "Native_GetParticleSystemOrigin"); + register_native("ParticleSystem_SetOrigin", "Native_SetParticleSystemOrigin"); + register_native("ParticleSystem_GetParentEntity", "Native_GetParticleSystemParentEntity"); + register_native("ParticleSystem_SetParentEntity", "Native_SetParticleSystemParentEntity"); + register_native("ParticleSystem_GetEffect", "Native_GetParticleSystemEffect"); + register_native("ParticleSystem_SetEffect", "Native_SetParticleSystemEffect"); + register_native("ParticleSystem_HasMember", "Native_HasMember"); + register_native("ParticleSystem_DeleteMember", "Native_DeleteMember"); + register_native("ParticleSystem_GetMember", "Native_GetMember"); + register_native("ParticleSystem_SetMember", "Native_SetMember"); + register_native("ParticleSystem_GetMemberVec", "Native_GetMemberVec"); + register_native("ParticleSystem_SetMemberVec", "Native_SetMemberVec"); + register_native("ParticleSystem_GetMemberString", "Native_GetMemberString"); + register_native("ParticleSystem_SetMemberString", "Native_SetMemberString"); + + register_native("Particle_GetIndex", "Native_GetParticleIndex"); + register_native("Particle_GetBatchIndex", "Native_GetParticleBatchIndex"); + register_native("Particle_GetEntity", "Native_GetParticleEntity"); + register_native("Particle_GetSystem", "Native_GetParticleSystem"); + register_native("Particle_GetCreatedTime", "Native_GetParticleCreatedTime"); + register_native("Particle_GetKillTime", "Native_GetParticleKillTime"); + register_native("Particle_GetLastThink", "Native_GetParticleLastThink"); + register_native("Particle_GetOrigin", "Native_GetParticleOrigin"); + register_native("Particle_SetOrigin", "Native_SetParticleOrigin"); + register_native("Particle_GetAngles", "Native_GetParticleAngles"); + register_native("Particle_SetAngles", "Native_SetParticleAngles"); + register_native("Particle_GetVelocity", "Native_GetParticleVelocity"); + register_native("Particle_SetVelocity", "Native_SetParticleVelocity"); +} + +public plugin_end() { + static irgSystemsNum; irgSystemsNum = ArraySize(g_irgSystems); + for (new iSystem = 0; iSystem < irgSystemsNum; ++iSystem) { + static Struct:sSystem; sSystem = ArrayGetCell(g_irgSystems, iSystem); + if (sSystem == Invalid_Struct) continue; + + @ParticleSystem_Destroy(sSystem); + } + + ArrayDestroy(g_irgSystems); + TrieDestroy(g_tParticleEffects); + free_tr2(g_pTrace); +} + +/*--------------------------------[ Natives ]--------------------------------*/ + +public Native_RegisterParticleEffect(iPluginId, iArgc) { + new szName[32]; get_string(1, szName, charsmax(szName)); + new Float:flEmitRate = get_param_f(2); + new Float:flParticleLifeTime = get_param_f(3); + new iMaxParticles = get_param(4); + new iEmitAmount = get_param(5); + new Float:flVisibilityDistance = get_param_f(6); + new ParticleEffectFlag:iFlags = ParticleEffectFlag:get_param(7); + + if (TrieKeyExists(g_tParticleEffects, szName)) { + log_error(AMX_ERR_NATIVE, "Particle effect ^"%s^" is already registered.", szName); + return; + } + + new Struct:sEffect = @ParticleEffect_Create(szName, flEmitRate, flParticleLifeTime, flVisibilityDistance, iMaxParticles, iEmitAmount, iFlags); + + TrieSetCell(g_tParticleEffects, szName, sEffect); +} +public Native_RegisterParticleEffectHook(iPluginId, iArgc) { + new szName[32]; get_string(1, szName, charsmax(szName)); + new ParticleEffectHook:iHookId = ParticleEffectHook:get_param(2); + new szCallback[64]; get_string(3, szCallback, charsmax(szCallback)); + + static Struct:sEffect; + if (!TrieGetCell(g_tParticleEffects, szName, sEffect)) { + log_error(AMX_ERR_NATIVE, "[Particles] Effect ^"%s^" is not registered!", szName); + return; + } + + new Array:irgHooks = StructGetCell(sEffect, ParticleEffect_Hooks, iHookId); + + new rgCallback[Callback]; + rgCallback[Callback_PluginId] = iPluginId; + rgCallback[Callback_FunctionId] = get_func_id(szCallback, iPluginId); + + if (rgCallback[Callback_FunctionId] == -1) { + log_error(AMX_ERR_NATIVE, "[Particles] Function ^"%s^" is not found!", szCallback); + return; + } + + ArrayPushArray(irgHooks, rgCallback[any:0], sizeof(rgCallback)); +} + +public Struct:Native_CreateParticleSystem(iPluginId, iArgc) { + new szName[32]; get_string(1, szName, charsmax(szName)); + new Float:vecOrigin[3]; get_array_f(2, vecOrigin, sizeof(vecOrigin)); + new Float:vecAngles[3]; get_array_f(3, vecAngles, sizeof(vecAngles)); + new pParent; pParent = get_param(4); + + new Struct:sEffect; + if (!TrieGetCell(g_tParticleEffects, szName, sEffect)) { + log_error(AMX_ERR_NATIVE, "[Particles] Effect ^"%s^" is not registered!", szName); + return Invalid_Struct; + } + + new Struct:sSystem; sSystem = @ParticleSystem_Create(sEffect, vecOrigin, vecAngles, pParent); + + return sSystem; +} + +public Native_DestroyParticleSystem(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + + StructSetCell(sSystem, ParticleSystem_KillTime, get_gametime()); + + set_param_byref(1, _:Invalid_Struct); +} + +public Float:Native_ActivateParticleSystem(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + + StructSetCell(sSystem, ParticleSystem_Active, true); +} + +public Float:Native_DeactivateParticleSystem(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + + StructSetCell(sSystem, ParticleSystem_Active, false); +} + +public Float:Native_GetParticleSystemEffectSpeed(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + + return StructGetCell(sSystem, ParticleSystem_EffectSpeed); +} + +public Native_SetParticleSystemEffectSpeed(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + static Float:flSpeed; flSpeed = get_param_f(2); + + StructSetCell(sSystem, ParticleSystem_EffectSpeed, flSpeed); +} + +public Float:Native_GetParticleSystemCreatedTime(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + + return Float:StructGetCell(sSystem, ParticleSystem_CreatedTime); +} + +public Float:Native_GetParticleSystemKillTime(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + + return Float:StructGetCell(sSystem, ParticleSystem_KillTime); +} + +public Native_GetParticleSystemLastThink(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + + return StructGetCell(sSystem, ParticleSystem_LastThink); +} + +public Native_GetParticleSystemVisibilityBits(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + + return StructGetCell(sSystem, ParticleSystem_VisibilityBits); +} + +public Native_GetParticleSystemOrigin(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + + static Float:vecOrigin[3]; StructGetArray(sSystem, ParticleSystem_PositionVars, vecOrigin, 3, PositionVars_Origin); + + set_array_f(2, vecOrigin, sizeof(vecOrigin)); +} + +public Native_SetParticleSystemOrigin(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + static Float:vecOrigin[3]; get_array_f(2, vecOrigin, sizeof(vecOrigin)); + + StructSetArray(sSystem, ParticleSystem_PositionVars, vecOrigin, 3, PositionVars_Origin); +} + +public Native_GetParticleSystemParentEntity(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + + return StructGetCell(sSystem, ParticleSystem_ParentEntity); +} + +public Native_SetParticleSystemParentEntity(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + static pParent; pParent = get_param(2); + + StructSetCell(sSystem, ParticleSystem_ParentEntity, pParent); +} + +public Native_GetParticleSystemEffect(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + + static Struct:sEffect; sEffect = StructGetCell(sSystem, ParticleSystem_Effect); + static szName[32]; StructGetString(sEffect, ParticleEffect_Id, szName, charsmax(szName)); + + set_string(2, szName, get_param(3)); +} + +public Native_SetParticleSystemEffect(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + static szName[32]; get_string(2, szName, charsmax(szName)); + + static Struct:sEffect; + if (!TrieGetCell(g_tParticleEffects, szName, sEffect)) { + log_error(AMX_ERR_NATIVE, "[Particles] Effect ^"%s^" is not registered!", szName); + return; + } + + StructSetCell(sSystem, ParticleSystem_Effect, sEffect); +} + +public bool:Native_HasMember(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + static szMember[PARTICLE_MAX_MEMBER_LENGTH]; get_string(2, szMember, charsmax(szMember)); + + static Trie:itMembers; itMembers = StructGetCell(sSystem, ParticleSystem_Members); + + return TrieKeyExists(itMembers, szMember); +} + +public Native_DeleteMember(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + static szMember[PARTICLE_MAX_MEMBER_LENGTH]; get_string(2, szMember, charsmax(szMember)); + + static Trie:itMembers; itMembers = StructGetCell(sSystem, ParticleSystem_Members); + + TrieDeleteKey(itMembers, szMember); +} + +public any:Native_GetMember(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + static szMember[PARTICLE_MAX_MEMBER_LENGTH]; get_string(2, szMember, charsmax(szMember)); + + static Trie:itMembers; itMembers = StructGetCell(sSystem, ParticleSystem_Members); + + static iValue; + if (!TrieGetCell(itMembers, szMember, iValue)) return 0; + + return iValue; +} + +public Native_SetMember(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + static szMember[PARTICLE_MAX_MEMBER_LENGTH]; get_string(2, szMember, charsmax(szMember)); + static iValue; iValue = get_param(3); + + static Trie:itMembers; itMembers = StructGetCell(sSystem, ParticleSystem_Members); + + TrieSetCell(itMembers, szMember, iValue); +} + +public bool:Native_GetMemberVec(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + static szMember[PARTICLE_MAX_MEMBER_LENGTH]; get_string(2, szMember, charsmax(szMember)); + + static Trie:itMembers; itMembers = StructGetCell(sSystem, ParticleSystem_Members); + + static Float:vecValue[3]; + if (!TrieGetArray(itMembers, szMember, vecValue, 3)) return false; + + set_array_f(3, vecValue, sizeof(vecValue)); + + return true; +} + +public Native_SetMemberVec(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + static szMember[PARTICLE_MAX_MEMBER_LENGTH]; get_string(2, szMember, charsmax(szMember)); + static Float:vecValue[3]; get_array_f(3, vecValue, sizeof(vecValue)); + + static Trie:itMembers; itMembers = StructGetCell(sSystem, ParticleSystem_Members); + TrieSetArray(itMembers, szMember, vecValue, 3); +} + +public bool:Native_GetMemberString(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + static szMember[PARTICLE_MAX_MEMBER_LENGTH]; get_string(2, szMember, charsmax(szMember)); + + static Trie:itMembers; itMembers = StructGetCell(sSystem, ParticleSystem_Members); + + static szValue[128]; + if (!TrieGetString(itMembers, szMember, szValue, charsmax(szValue))) return false; + + set_string(3, szValue, get_param(4)); + + return true; +} + +public Native_SetMemberString(iPluginId, iArgc) { + static Struct:sSystem; sSystem = Struct:get_param_byref(1); + static szMember[PARTICLE_MAX_MEMBER_LENGTH]; get_string(2, szMember, charsmax(szMember)); + static szValue[128]; get_string(3, szValue, charsmax(szValue)); + + static Trie:itMembers; itMembers = StructGetCell(sSystem, ParticleSystem_Members); + TrieSetString(itMembers, szMember, szValue); +} + +public Native_GetParticleOrigin(iPluginId, iArgc) { + static Struct:sParticle; sParticle = Struct:get_param_byref(1); + + static Float:vecOrigin[3]; StructGetArray(sParticle, Particle_PositionVars, vecOrigin, 3, PositionVars_Origin); + + set_array_f(2, vecOrigin, sizeof(vecOrigin)); +} + +public Native_SetParticleOrigin(iPluginId, iArgc) { + static Struct:sParticle; sParticle = Struct:get_param_byref(1); + static Float:vecOrigin[3]; get_array_f(2, vecOrigin, sizeof(vecOrigin)); + + StructSetArray(sParticle, Particle_PositionVars, vecOrigin, 3, PositionVars_Origin); +} + +public Native_GetParticleAngles(iPluginId, iArgc) { + static Struct:sParticle; sParticle = Struct:get_param_byref(1); + + static Float:vecAngles[3]; StructGetArray(sParticle, Particle_PositionVars, vecAngles, 3, PositionVars_Angles); + + set_array_f(2, vecAngles, sizeof(vecAngles)); +} + +public Native_SetParticleAngles(iPluginId, iArgc) { + static Struct:sParticle; sParticle = Struct:get_param_byref(1); + static Float:vecAngles[3]; get_array_f(2, vecAngles, sizeof(vecAngles)); + + StructSetArray(sParticle, Particle_PositionVars, vecAngles, 3, PositionVars_Angles); +} + +public Native_GetParticleVelocity(iPluginId, iArgc) { + static Struct:sParticle; sParticle = Struct:get_param_byref(1); + + static Float:vecVelocity[3]; StructGetArray(sParticle, Particle_PositionVars, vecVelocity, 3, PositionVars_Velocity); + + set_array_f(2, vecVelocity, sizeof(vecVelocity)); +} + +public Native_SetParticleVelocity(iPluginId, iArgc) { + static Struct:sParticle; sParticle = Struct:get_param_byref(1); + + static Float:vecVelocity[3]; get_array_f(2, vecVelocity, sizeof(vecVelocity)); + + StructSetArray(sParticle, Particle_PositionVars, vecVelocity, 3, PositionVars_Velocity); +} + +public Struct:Native_GetParticleIndex(iPluginId, iArgc) { + static Struct:sParticle; sParticle = Struct:get_param_byref(1); + + return StructGetCell(sParticle, Particle_Index); +} + +public Struct:Native_GetParticleBatchIndex(iPluginId, iArgc) { + static Struct:sParticle; sParticle = Struct:get_param_byref(1); + + return StructGetCell(sParticle, Particle_BatchIndex); +} + +public Struct:Native_GetParticleEntity(iPluginId, iArgc) { + static Struct:sParticle; sParticle = Struct:get_param_byref(1); + + return StructGetCell(sParticle, Particle_Entity); +} + +public Struct:Native_GetParticleSystem(iPluginId, iArgc) { + static Struct:sParticle; sParticle = Struct:get_param_byref(1); + + return StructGetCell(sParticle, Particle_System); +} + +public Float:Native_GetParticleCreatedTime(iPluginId, iArgc) { + static Struct:sParticle; sParticle = Struct:get_param_byref(1); + + return Float:StructGetCell(sParticle, Particle_CreatedTime); +} + +public Float:Native_GetParticleKillTime(iPluginId, iArgc) { + static Struct:sParticle; sParticle = Struct:get_param_byref(1); + + return Float:StructGetCell(sParticle, Particle_KillTime); +} + +public Float:Native_GetParticleLastThink(iPluginId, iArgc) { + static Struct:sParticle; sParticle = Struct:get_param_byref(1); + + return Float:StructGetCell(sParticle, Particle_LastThink); +} + +/*--------------------------------[ Forwards ]--------------------------------*/ + +public server_frame() { + if (g_bEnabled) { + static Float:flGameTime; flGameTime = get_gametime(); + + if (g_flNextSystemsUpdate <= flGameTime) { + UpdateSystems(); + g_flNextSystemsUpdate = flGameTime + UPDATE_RATE; + } + } +} + +/*--------------------------------[ Hooks ]--------------------------------*/ + +public Command_Create(pPlayer, iLevel, iCId) { + if (!cmd_access(pPlayer, iLevel, iCId, 2)) { + return PLUGIN_HANDLED; + } + + static szName[32]; read_argv(1, szName, charsmax(szName)); + + if (equal(szName, NULL_STRING)) return PLUGIN_HANDLED; + + static Float:vecOrigin[3]; pev(pPlayer, pev_origin, vecOrigin); + static Float:vecAngles[3]; pev(pPlayer, pev_angles, vecAngles); + + static Struct:sEffect; + if (!TrieGetCell(g_tParticleEffects, szName, sEffect)) return PLUGIN_HANDLED; + + static Struct:sSystem; sSystem = @ParticleSystem_Create(sEffect, vecOrigin, vecAngles, 0); + StructSetCell(sSystem, ParticleSystem_Active, true); + + return PLUGIN_HANDLED; +} + +public FMHook_AddToFullPack(es, e, pEntity, pHost, hostflags, player, pSet) { + if (!pev_valid(pEntity)) return FMRES_IGNORED; + + static szClassName[32]; pev(pEntity, pev_classname, szClassName, charsmax(szClassName)); + + if (equal(szClassName, PARTICLE_CLASSNAME)) { + static Struct:sParticle; sParticle = Struct:pev(pEntity, pev_iuser1); + static Struct:sSystem; sSystem = StructGetCell(sParticle, Particle_System); + static iVisibilityBits; iVisibilityBits = StructGetCell(sSystem, ParticleSystem_VisibilityBits); + + if (~iVisibilityBits & BIT(pHost & 31)) return FMRES_SUPERCEDE; + + return FMRES_IGNORED; + } + + return FMRES_IGNORED; +} + +public CvarHook_Enabled() { + if (!get_pcvar_num(g_pCvarEnabled)) { + FreeParticles(); + } +} + +/*--------------------------------[ ParticleEffect Methods ]--------------------------------*/ + +Struct:@ParticleEffect_Create(const szName[], Float:flEmitRate, Float:flParticleLifeTime, Float:flVisibilityDistance, iMaxParticles, iEmitAmount, ParticleEffectFlag:iFlags) { + static Struct:this; this = StructCreate(ParticleEffect); + + StructSetString(this, ParticleEffect_Id, szName); + StructSetCell(this, ParticleEffect_EmitRate, flEmitRate); + StructSetCell(this, ParticleEffect_EmitAmount, iEmitAmount); + StructSetCell(this, ParticleEffect_VisibilityDistance, flVisibilityDistance); + StructSetCell(this, ParticleEffect_ParticleLifeTime, flParticleLifeTime); + StructSetCell(this, ParticleEffect_MaxParticles, iMaxParticles); + StructSetCell(this, ParticleEffect_Flags, iFlags); + + for (new ParticleEffectHook:iHookId = ParticleEffectHook:0; iHookId < ParticleEffectHook; ++iHookId) { + StructSetCell(this, ParticleEffect_Hooks, ArrayCreate(_:Callback, 1), iHookId); + } + + return this; +} + +@ParticleEffect_Destroy(&Struct:this) { + for (new ParticleEffectHook:iHookId = ParticleEffectHook:0; iHookId < ParticleEffectHook; ++iHookId) { + new Array:irgHooks = StructGetCell(this, ParticleEffect_Hooks, iHookId); + ArrayDestroy(irgHooks); + } + + StructDestroy(this); +} + +static @ParticleEffect_ExecuteHook(const &Struct:this, ParticleEffectHook:iHook, const &Struct:sInstance, any:...) { + new iResult = 0; + + new Array:irgHooks = StructGetCell(this, ParticleEffect_Hooks, iHook); + + new iHooksNum = ArraySize(irgHooks); + for (new iHookId = 0; iHookId < iHooksNum; ++iHookId) { + new iPluginId = ArrayGetCell(irgHooks, iHookId, _:Callback_PluginId); + new iFunctionId = ArrayGetCell(irgHooks, iHookId, _:Callback_FunctionId); + + if (callfunc_begin_i(iFunctionId, iPluginId) == 1) { + callfunc_push_int(_:sInstance); + + switch (iHook) { + case ParticleEffectHook_Particle_EntityInit: { + callfunc_push_int(getarg(3)); + } + } + + iResult = max(iResult, callfunc_end()); + } + } + + + return iResult; +} + +/*--------------------------------[ ParticleSystem Methods ]--------------------------------*/ + +Struct:@ParticleSystem_Create(const &Struct:sEffect, const Float:vecOrigin[3], const Float:vecAngles[3], pParent) { + static Struct:this; this = StructCreate(ParticleSystem); + + static iMaxParticles; iMaxParticles = StructGetCell(sEffect, ParticleEffect_MaxParticles); + + static Array:irgParticles; irgParticles = ArrayCreate(iMaxParticles); + for (new i = 0; i < iMaxParticles; ++i) ArrayPushCell(irgParticles, Invalid_Struct); + + StructSetCell(this, ParticleSystem_Effect, sEffect); + StructSetArray(this, ParticleSystem_PositionVars, vecOrigin, 3, PositionVars_Origin); + StructSetArray(this, ParticleSystem_PositionVars, vecAngles, 3, PositionVars_Angles); + StructSetArray(this, ParticleSystem_PositionVars, Float:{0.0, 0.0, 0.0}, 3, PositionVars_Velocity); + StructSetCell(this, ParticleSystem_ParentEntity, pParent); + StructSetCell(this, ParticleSystem_Particles, irgParticles); + StructSetCell(this, ParticleSystem_CreatedTime, get_gametime()); + StructSetCell(this, ParticleSystem_KillTime, 0.0); + StructSetCell(this, ParticleSystem_EffectSpeed, 1.0); + StructSetCell(this, ParticleSystem_Active, false); + StructSetCell(this, ParticleSystem_NextEmit, 0.0); + StructSetCell(this, ParticleSystem_NextVisibilityUpdate, 0.0); + StructSetCell(this, ParticleSystem_Members, TrieCreate()); + + ArrayPushCell(g_irgSystems, this); + + @ParticleEffect_ExecuteHook(sEffect, ParticleEffectHook_System_Init, this); + + return this; +} + +@ParticleSystem_Destroy(&Struct:this) { + static Array:irgParticles; irgParticles = StructGetCell(this, ParticleSystem_Particles); + static iParticlesNum; iParticlesNum = ArraySize(irgParticles); + static Struct:sEffect; sEffect = StructGetCell(this, ParticleSystem_Effect); + static Trie:itMembers; itMembers = StructGetCell(this, ParticleSystem_Members); + + @ParticleEffect_ExecuteHook(sEffect, ParticleEffectHook_System_Destroy, this); + + for (new iParticle = 0; iParticle < iParticlesNum; ++iParticle) { + static Struct:sParticle; sParticle = ArrayGetCell(irgParticles, iParticle); + if (sParticle == Invalid_Struct) continue; + + @Particle_Destroy(sParticle); + } + + ArrayDestroy(irgParticles); + TrieDestroy(itMembers); + StructDestroy(this); +} + +@ParticleSystem_FreeParticles(const &Struct:this) { + static Array:irgParticles; irgParticles = StructGetCell(this, ParticleSystem_Particles); + static iParticlesNum; iParticlesNum = ArraySize(irgParticles); + + for (new iParticle = 0; iParticle < iParticlesNum; ++iParticle) { + static Struct:sParticle; sParticle = ArrayGetCell(irgParticles, iParticle); + if (sParticle == Invalid_Struct) continue; + @Particle_Destroy(sParticle); + ArraySetCell(irgParticles, iParticle, Invalid_Struct); + } +} + +@ParticleSystem_Update(const &Struct:this) { + static Float:flGameTime; flGameTime = get_gametime(); + + static Struct:sEffect; sEffect = StructGetCell(this, ParticleSystem_Effect); + static Array:irgParticles; irgParticles = StructGetCell(this, ParticleSystem_Particles); + static iVisibilityBits; iVisibilityBits = StructGetCell(this, ParticleSystem_VisibilityBits); + static bool:bActive; bActive = StructGetCell(this, ParticleSystem_Active); + static iParticlesNum; iParticlesNum = ArraySize(irgParticles); + static Float:flLastThink; flLastThink = StructGetCell(this, ParticleSystem_LastThink); + static Float:flSpeed; flSpeed = StructGetCell(this, ParticleSystem_EffectSpeed); + + static Float:flDelta; flDelta = flGameTime - flLastThink; + + static Float:vecOrigin[3]; StructGetArray(this, ParticleSystem_PositionVars, vecOrigin, 3, PositionVars_Origin); + static Float:vecVelocity[3]; StructGetArray(this, ParticleSystem_PositionVars, vecVelocity, 3, PositionVars_Velocity); + static Float:vecAngles[3]; StructGetArray(this, ParticleSystem_PositionVars, vecAngles, 3, PositionVars_Angles); + + xs_vec_add_scaled(vecOrigin, vecVelocity, flDelta * flSpeed, vecOrigin); + StructSetArray(this, ParticleSystem_PositionVars, vecOrigin, 3, PositionVars_Origin); + + @ParticleEffect_ExecuteHook(sEffect, ParticleEffectHook_System_Think, this); + + // Emit particles + if (bActive) { + static Float:flNextEmit; flNextEmit = StructGetCell(this, ParticleSystem_NextEmit); + if (iVisibilityBits && flNextEmit <= flGameTime) { + static Float:flEmitRate; flEmitRate = StructGetCell(sEffect, ParticleEffect_EmitRate); + static iEmitAmount; iEmitAmount = StructGetCell(sEffect, ParticleEffect_EmitAmount); + + if (flEmitRate || !iParticlesNum) { + for (new iBatchIndex = 0; iBatchIndex < iEmitAmount; ++iBatchIndex) { + @ParticleSystem_Emit(this, iBatchIndex); + } + } + + StructSetCell(this, ParticleSystem_NextEmit, flGameTime + (flEmitRate / flSpeed)); + } + } + + for (new iParticle = 0; iParticle < iParticlesNum; ++iParticle) { + static Struct:sParticle; sParticle = ArrayGetCell(irgParticles, iParticle); + if (sParticle == Invalid_Struct) continue; + + // Destroy expired particle and skip (also destroy all particles in case no one see the system or the system is deactivated) + static Float:flKillTime; flKillTime = StructGetCell(sParticle, Particle_KillTime); + if (!iVisibilityBits || !bActive || (flKillTime > 0.0 && flKillTime <= flGameTime)) { + ArraySetCell(irgParticles, iParticle, Invalid_Struct); + @Particle_Destroy(sParticle); + continue; + } + + static bool:bAttached; bAttached = StructGetCell(sParticle, Particle_Attached); + static Float:vecParticleOrigin[3]; StructGetArray(sParticle, Particle_PositionVars, vecParticleOrigin, 3, PositionVars_Origin); + static Float:vecParticleVelocity[3]; StructGetArray(sParticle, Particle_PositionVars, vecParticleVelocity, 3, PositionVars_Velocity); + static Float:vecParticleAngles[3]; StructGetArray(sParticle, Particle_PositionVars, vecParticleAngles, 3, PositionVars_Angles); + + xs_vec_add_scaled(vecParticleOrigin, vecParticleVelocity, flDelta * flSpeed, vecParticleOrigin); + StructSetArray(sParticle, Particle_PositionVars, vecParticleOrigin, 3, PositionVars_Origin); + + @ParticleEffect_ExecuteHook(sEffect, ParticleEffectHook_Particle_Think, sParticle); + + if (bAttached) { + @ParticleSystem_UpdateParticleAbsPosition(this, sParticle); + } + + @ParticleSystem_SyncParticleVars(this, sParticle); + + StructSetCell(sParticle, Particle_LastThink, flGameTime); + } + + StructSetCell(this, ParticleSystem_LastThink, flGameTime); +} + +@ParticleSystem_Emit(const &Struct:this, iBatchIndex) { + static iVisibilityBits; iVisibilityBits = StructGetCell(this, ParticleSystem_VisibilityBits); + if (!iVisibilityBits) return; + + static Float:flGameTime; flGameTime = get_gametime(); + static Struct:sEffect; sEffect = StructGetCell(this, ParticleSystem_Effect); + static Float:flSpeed; flSpeed = StructGetCell(this, ParticleSystem_EffectSpeed); + static ParticleEffectFlag:iEffectFlags; iEffectFlags = StructGetCell(sEffect, ParticleEffect_Flags); + + static Struct:sParticle; sParticle = @Particle_Create(this, !!(iEffectFlags & ParticleEffectFlag_AttachParticles)); + + static Float:vecAbsOrigin[3]; @ParticleSystem_GetAbsPositionVar(this, PositionVars_Origin, vecAbsOrigin); + static Float:vecAbsAngles[3]; @ParticleSystem_GetAbsPositionVar(this, PositionVars_Angles, vecAbsAngles); + static Float:vecAbsVelocity[3]; @ParticleSystem_GetAbsPositionVar(this, PositionVars_Velocity, vecAbsVelocity); + + StructSetArray(sParticle, Particle_AbsPositionVars, vecAbsOrigin, 3, PositionVars_Origin); + StructSetArray(sParticle, Particle_AbsPositionVars, vecAbsAngles, 3, PositionVars_Angles); + StructSetArray(sParticle, Particle_AbsPositionVars, vecAbsVelocity, 3, PositionVars_Velocity); + + StructSetCell(sParticle, Particle_BatchIndex, iBatchIndex); + + static Float:flLifeTime; flLifeTime = StructGetCell(sEffect, ParticleEffect_ParticleLifeTime); + if (flLifeTime > 0.0) { + StructSetCell(sParticle, Particle_KillTime, flGameTime + (flLifeTime / flSpeed)); + } + + @ParticleSystem_AddParticle(this, sParticle); + + @ParticleEffect_ExecuteHook(sEffect, ParticleEffectHook_Particle_Init, sParticle); + + @Particle_InitEntity(sParticle); + + static Float:flEmitRate; flEmitRate = StructGetCell(sEffect, ParticleEffect_EmitRate); + StructSetCell(this, ParticleSystem_NextEmit, flGameTime + (flEmitRate * flSpeed)); + + @ParticleSystem_SyncParticleVars(this, sParticle); +} + +@ParticleSystem_AddParticle(const &Struct:this, const &Struct:sNewParticle) { + static Array:irgParticles; irgParticles = StructGetCell(this, ParticleSystem_Particles); + static iParticlesNum; iParticlesNum = ArraySize(irgParticles); + + static iIndex; iIndex = -1; + static Struct:sOldParticle; sOldParticle = Invalid_Struct; + + for (new iParticle = 0; iParticle < iParticlesNum; ++iParticle) { + static Struct:sParticle; sParticle = ArrayGetCell(irgParticles, iParticle); + if (sParticle == Invalid_Struct) { + sOldParticle = Invalid_Struct; + iIndex = iParticle; + break; + } + + static Float:flKillTime; flKillTime = StructGetCell(sParticle, Particle_KillTime); + if (iIndex == -1 || flKillTime < StructGetCell(sOldParticle, Particle_KillTime)) { + iIndex = iParticle; + sOldParticle = sParticle; + } + } + + if (sOldParticle != Invalid_Struct) { + @Particle_Destroy(sOldParticle); + } + + ArraySetCell(irgParticles, iIndex, sNewParticle); + StructSetCell(sNewParticle, Particle_Index, iIndex); +} + +@ParticleSystem_GetAbsPositionVar(const &Struct:this, PositionVars:iVariable, Float:vecOut[]) { + static pParent; pParent = StructGetCell(this, ParticleSystem_ParentEntity); + + if (pParent > 0) { + pev(pParent, PositionVarsToPevMemberVec(iVariable), vecOut); + + for (new i = 0; i < 3; ++i) { + vecOut[i] += Float:StructGetCell(this, ParticleSystem_PositionVars, _:iVariable + i); + } + } else { + StructGetArray(this, ParticleSystem_PositionVars, vecOut, 3, iVariable); + } +} + +@ParticleSystem_SetAbsVectorVar(const &Struct:this, PositionVars:iVariable, const Float:vecValue[3]) { + static Float:vecAbsValue[3]; + + static pParent; pParent = StructGetCell(this, ParticleSystem_ParentEntity); + if (pParent > 0) { + pev(pParent, PositionVarsToPevMemberVec(iVariable), vecAbsValue); + xs_vec_sub(vecValue, vecAbsValue, vecAbsValue); + } else { + xs_vec_copy(vecValue, vecAbsValue); + } + + StructSetArray(this, ParticleSystem_PositionVars, vecAbsValue, 3, iVariable); +} + +@ParticleSystem_UpdateVisibilityBits(const &Struct:this) { + static Struct:sEffect; sEffect = StructGetCell(this, ParticleSystem_Effect); + + static Float:flVisibleDistance; flVisibleDistance = StructGetCell(sEffect, ParticleEffect_VisibilityDistance); + static Float:vecAbsOrigin[3]; @ParticleSystem_GetAbsPositionVar(this, PositionVars_Origin, vecAbsOrigin); + + new iVisibilityBits = 0; + for (new pPlayer = 1; pPlayer <= MaxClients; ++pPlayer) { + if (!is_user_connected(pPlayer)) continue; + + static Float:vecPlayerOrigin[3]; ExecuteHamB(Ham_EyePosition, pPlayer, vecPlayerOrigin); + static Float:flDistance; flDistance = get_distance_f(vecAbsOrigin, vecPlayerOrigin); + static Float:flFOV; pev(pPlayer, pev_fov, flFOV); + + if (flDistance > 32.0 && !UTIL_IsInViewCone(pPlayer, vecAbsOrigin, flFOV / 2)) continue; + if (flDistance > flVisibleDistance) continue; + + engfunc(EngFunc_TraceLine, vecPlayerOrigin, vecAbsOrigin, IGNORE_MONSTERS, pPlayer, g_pTrace); + + static Float:flFraction; get_tr2(g_pTrace, TR_flFraction, flFraction); + if (flFraction == 1.0) { + iVisibilityBits |= BIT(pPlayer & 31); + } + } + + StructSetCell(this, ParticleSystem_VisibilityBits, iVisibilityBits); +} + +@ParticleSystem_UpdateParticleAbsPosition(const &Struct:this, const &Struct:sParticle) { + static Float:vecAbsOrigin[3]; @ParticleSystem_GetAbsPositionVar(this, PositionVars_Origin, vecAbsOrigin); + static Float:vecAbsAngles[3]; @ParticleSystem_GetAbsPositionVar(this, PositionVars_Angles, vecAbsAngles); + static Float:vecAbsVelocity[3]; @ParticleSystem_GetAbsPositionVar(this, PositionVars_Velocity, vecAbsVelocity); + + StructSetArray(sParticle, Particle_AbsPositionVars, vecAbsOrigin, 3, PositionVars_Origin); + StructSetArray(sParticle, Particle_AbsPositionVars, vecAbsAngles, 3, PositionVars_Angles); + StructSetArray(sParticle, Particle_AbsPositionVars, vecAbsVelocity, 3, PositionVars_Velocity); +} + +@ParticleSystem_SyncParticleVars(const &Struct:this, const &Struct:sParticle) { + static Float:flSpeed; flSpeed = StructGetCell(this, ParticleSystem_EffectSpeed); + + static pEntity; pEntity = StructGetCell(sParticle, Particle_Entity); + static bool:bAttached; bAttached = StructGetCell(sParticle, Particle_Attached); + + static Float:vecAbsOrigin[3]; StructGetArray(sParticle, Particle_AbsPositionVars, vecAbsOrigin, 3, PositionVars_Origin); + static Float:vecAbsAngles[3]; StructGetArray(sParticle, Particle_AbsPositionVars, vecAbsAngles, 3, PositionVars_Angles); + static Float:vecAbsVelocity[3]; StructGetArray(sParticle, Particle_AbsPositionVars, vecAbsVelocity, 3, PositionVars_Velocity); + + static Float:vecOrigin[3]; StructGetArray(sParticle, Particle_PositionVars, vecOrigin, 3, PositionVars_Origin); + static Float:vecAngles[3]; StructGetArray(sParticle, Particle_PositionVars, vecAngles, 3, PositionVars_Angles); + static Float:vecVelocity[3]; StructGetArray(sParticle, Particle_PositionVars, vecVelocity, 3, PositionVars_Velocity); + + if (bAttached) { + static Float:rgAngleMatrix[3][4]; UTIL_AngleMatrix(vecAbsAngles, rgAngleMatrix); + + UTIL_RotateVectorByMatrix(vecOrigin, rgAngleMatrix, vecOrigin); + UTIL_RotateVectorByMatrix(vecVelocity, rgAngleMatrix, vecVelocity); + } + + xs_vec_add(vecAbsOrigin, vecOrigin, vecAbsOrigin); + xs_vec_add(vecAbsAngles, vecAngles, vecAbsAngles); + xs_vec_add(vecAbsVelocity, vecVelocity, vecAbsVelocity); + + if (flSpeed != 1.0) { + xs_vec_mul_scalar(vecVelocity, flSpeed, vecVelocity); + } + + set_pev(pEntity, pev_angles, vecAbsAngles); + set_pev(pEntity, pev_origin, vecAbsOrigin); + set_pev(pEntity, pev_velocity, vecAbsVelocity); +} + +/*--------------------------------[ Particle Methods ]--------------------------------*/ + +Struct:@Particle_Create(const &Struct:sSystem, bool:bAttached) { + static Struct:this; this = StructCreate(Particle); + + StructSetCell(this, Particle_System, sSystem); + StructSetCell(this, Particle_Index, -1); + StructSetCell(this, Particle_BatchIndex, 0); + StructSetCell(this, Particle_Entity, -1); + StructSetCell(this, Particle_CreatedTime, get_gametime()); + StructSetCell(this, Particle_LastThink, get_gametime()); + StructSetCell(this, Particle_KillTime, 0.0); + StructSetCell(this, Particle_Attached, bAttached); + + StructSetArray(this, Particle_PositionVars, Float:{0.0, 0.0, 0.0}, 3, PositionVars_Origin); + StructSetArray(this, Particle_PositionVars, Float:{0.0, 0.0, 0.0}, 3, PositionVars_Angles); + StructSetArray(this, Particle_PositionVars, Float:{0.0, 0.0, 0.0}, 3, PositionVars_Velocity); + + return this; +} + +@Particle_InitEntity(const &Struct:this) { + static Struct:sSystem; sSystem = StructGetCell(this, Particle_System); + static Struct:sEffect; sEffect = StructGetCell(sSystem, ParticleSystem_Effect); + + static pParticle; pParticle = CreateParticleEnity(Float:{0.0, 0.0, 0.0}, Float:{0.0, 0.0, 0.0}); + set_pev(pParticle, pev_iuser1, this); + + StructSetCell(this, Particle_Entity, pParticle); + + @ParticleEffect_ExecuteHook(sEffect, ParticleEffectHook_Particle_EntityInit, this, pParticle); +} + +@Particle_Destroy(&Struct:this) { + static Struct:sSystem; sSystem = StructGetCell(this, Particle_System); + static Struct:sEffect; sEffect = StructGetCell(sSystem, ParticleSystem_Effect); + @ParticleEffect_ExecuteHook(sEffect, ParticleEffectHook_Particle_Destroy, this); + + static pParticle; pParticle = StructGetCell(this, Particle_Entity); + engfunc(EngFunc_RemoveEntity, pParticle); + + StructDestroy(this); +} + +/*--------------------------------[ Functions ]--------------------------------*/ + +CreateParticleEnity(const Float:vecOrigin[3], const Float:vecAngles[3]) { + static pEntity; pEntity = engfunc(EngFunc_CreateNamedEntity, g_iszParticleClassName); + dllfunc(DLLFunc_Spawn, pEntity); + + set_pev(pEntity, pev_classname, PARTICLE_CLASSNAME); + set_pev(pEntity, pev_solid, SOLID_NOT); + set_pev(pEntity, pev_movetype, MOVETYPE_NOCLIP); + set_pev(pEntity, pev_angles, vecAngles); + + engfunc(EngFunc_SetOrigin, pEntity, vecOrigin); + + return pEntity; +} + +UpdateSystems() { + static Float:flGameTime; flGameTime = get_gametime(); + static irgSystemsNum; irgSystemsNum = ArraySize(g_irgSystems); + + for (new iSystem = 0; iSystem < irgSystemsNum; ++iSystem) { + static Struct:sSystem; sSystem = ArrayGetCell(g_irgSystems, iSystem); + if (sSystem == Invalid_Struct) continue; + + // Destroy expired system and skip + static Float:flKillTime; flKillTime = StructGetCell(sSystem, ParticleSystem_KillTime); + if (flKillTime && flKillTime <= flGameTime) { + ArraySetCell(g_irgSystems, iSystem, Invalid_Struct); + @ParticleSystem_Destroy(sSystem); + continue; + } + + @ParticleSystem_Update(sSystem); + + static Float:flNextVisibilityUpdate; flNextVisibilityUpdate = StructGetCell(sSystem, ParticleSystem_NextVisibilityUpdate); + if (flNextVisibilityUpdate <= flGameTime) { + @ParticleSystem_UpdateVisibilityBits(sSystem); + StructSetCell(sSystem, ParticleSystem_NextVisibilityUpdate, flGameTime + VISIBILITY_UPDATE_RATE); + } + } + + UTIL_ArrayFindAndDelete(g_irgSystems, Invalid_Struct); +} + +FreeParticles() { + static irgSystemsNum; irgSystemsNum = ArraySize(g_irgSystems); + + for (new iSystem = 0; iSystem < irgSystemsNum; ++iSystem) { + static Struct:sSystem; sSystem = ArrayGetCell(g_irgSystems, iSystem); + if (sSystem == Invalid_Struct) continue; + @ParticleSystem_FreeParticles(sSystem); + } +} + +PositionVarsToPevMemberVec(PositionVars:iVariable) { + switch (iVariable) { + case PositionVars_Origin: return pev_origin; + case PositionVars_Angles: return pev_angles; + case PositionVars_Velocity: return pev_velocity; + } + + return -1; +} + +/*--------------------------------[ Stocks ]--------------------------------*/ + +stock UTIL_ArrayFindAndDelete(const &Array:irgArray, any:iValue) { + static iSize; iSize = ArraySize(irgArray); + + for (new i = 0; i < iSize; ++i) { + if (ArrayGetCell(irgArray, i) == iValue) { + ArrayDeleteItem(irgArray, i); + i--; + iSize--; + } + } +} + +stock UTIL_RotateVectorByMatrix(const Float:vecValue[3], Float:rgAngleMatrix[3][4], Float:vecOut[3]) { + static Float:vecTemp[3]; + + for (new i = 0; i < 3; ++i) { + vecTemp[i] = (vecValue[0] * rgAngleMatrix[0][i]) + (vecValue[1] * rgAngleMatrix[1][i]) + (vecValue[2] * rgAngleMatrix[2][i]); + } + + xs_vec_copy(vecTemp, vecOut); +} + +stock UTIL_AngleMatrix(const Float:vecAngles[3], Float:rgMatrix[3][4]) { + static Float:cp; cp = floatcos(vecAngles[0], degrees); + static Float:sp; sp = floatsin(vecAngles[0], degrees); + static Float:cy; cy = floatcos(vecAngles[1], degrees); + static Float:sy; sy = floatsin(vecAngles[1], degrees); + static Float:cr; cr = floatcos(-vecAngles[2], degrees); + static Float:sr; sr = floatsin(-vecAngles[2], degrees); + static Float:crcy; crcy = cr * cy; + static Float:crsy; crsy = cr * sy; + static Float:srcy; srcy = sr * cy; + static Float:srsy; srsy = sr * sy; + + // matrix = (YAW * PITCH) * ROLL + + rgMatrix[0][0] = cp * cy; + rgMatrix[1][0] = cp * sy; + rgMatrix[2][0] = -sp; + + rgMatrix[0][1] = (sp * srcy) + crsy; + rgMatrix[1][1] = (sp * srsy) - crcy; + rgMatrix[2][1] = sr * cp; + + rgMatrix[0][2] = (sp * crcy) - srsy; + rgMatrix[1][2] = (sp * crsy) + srcy; + rgMatrix[2][2] = cr * cp; + + rgMatrix[0][3] = 0.0; + rgMatrix[1][3] = 0.0; + rgMatrix[2][3] = 0.0; +} + +stock V_swap(&Float:v1, &Float:v2) { + static Float:tmp; + tmp = v1; + v1 = v2; + v2 = tmp; +} + +stock bool:UTIL_IsInViewCone(pEntity, const Float:vecTarget[3], Float:fMaxAngle) { + static Float:vecOrigin[3]; + ExecuteHamB(Ham_EyePosition, pEntity, vecOrigin); + + static Float:vecDir[3]; + xs_vec_sub(vecTarget, vecOrigin, vecDir); + xs_vec_normalize(vecDir, vecDir); + + static Float:vecForward[3]; + pev(pEntity, pev_v_angle, vecForward); + angle_vector(vecForward, ANGLEVECTOR_FORWARD, vecForward); + + new Float:flAngle = xs_rad2deg(xs_acos((vecDir[0] * vecForward[0]) + (vecDir[1] * vecForward[1]), radian)); + + return flAngle < fMaxAngle; +} diff --git a/api/particles/include/api_particles.inc b/api/particles/include/api_particles.inc new file mode 100644 index 0000000..f5120b0 --- /dev/null +++ b/api/particles/include/api_particles.inc @@ -0,0 +1,380 @@ +#if defined _api_particles_included + #endinput +#endif +#define _api_particles_included + +#include + +/** + * Registers a particle effect. + * + * @param szName The name of the effect. + * @param flEmitRate The rate at which particles are emitted. + * @param flParticleLifeTime The lifetime of each particle. + * @param iMaxParticles The maximum number of particles. + * @param iEmitAmount The amount of emitted particles. + * @param flVisibilityDistance Max visible distance. + * @param iFlags Effect flags. + * + * @noreturn + */ +native ParticleEffect_Register(const szName[], Float:flEmitRate = 0.1, Float:flParticleLifeTime = 1.0, iMaxParticles = 10, iEmitAmount = 1, Float:flVisibilityDistance = 1024.0, ParticleEffectFlag:iFlags = ParticleEffectFlag_None); + +/** + * Registers a particle effect hook. + * + * @param iHook Hook to handle. + * @param szName Name of the particle effect. + * @param szCallback Callback function to be called during particle effects. + * + * @noreturn + */ +native ParticleEffect_RegisterHook(const szName[], ParticleEffectHook:iHook, const szCallback[]); + +/** + * Creates a particle system. + * + * @param szEffect The name of the effect. + * @param vecOrigin The origin of the system. + * @param vecAngles The angles of the system. + * @param pParent The parent System. + * + * @return The particle system structure pointer. + */ +native ParticleSystem:ParticleSystem_Create(const szEffect[], const Float:vecOrigin[3] = {0.0, 0.0, 0.0}, const Float:vecAngles[3] = {0.0, 0.0, 0.0}, pParent = 0); + +/** + * Destroys the particle system. + * + * @param sSystem The particle system structure pointer. + * + * @noreturn + */ +native ParticleSystem_Destroy(&ParticleSystem:sSystem); + +/** + * Activates the particle system. + * + * @param sSystem The particle system structure pointer. + * + * @noreturn + */ +native ParticleSystem_Activate(const &ParticleSystem:sSystem); + +/** + * Deactivates the particle system. + * + * @param sSystem The particle system structure pointer. + * + * @noreturn + */ +native ParticleSystem_Deactivate(const &ParticleSystem:sSystem); + +/** + * Gets particle effect speed. + * + * @param sSystem The particle system structure pointer. + * + * @return The particle system effect speed. + */ +native Float:ParticleSystem_GetEffectSpeed(const &ParticleSystem:sSystem); + +/** + * Sets particle effect speed. + * + * @param sSystem The particle system structure pointer. + * @param flSpeed The particle system effect speed. + * + * @noreturn + */ +native ParticleSystem_SetEffectSpeed(const &ParticleSystem:sSystem, Float:flSpeed); + +/** + * Retrieves the time at which a particle system was created. + * + * @param sSystem The particle system structure pointer. + * + * @return The creation time of the particle system. + */ +native Float:ParticleSystem_GetCreatedTime(const &ParticleSystem:sSystem); + +/** + * Retrieves the time at which a particle system will killed. + * + * @param sSystem The particle system structure pointer. + * + * @return The kill time of the particle system. + */ +native Float:ParticleSystem_GetKillTime(const &ParticleSystem:sSystem); + +/** + * Retrieves the last time a particle system was thinked. + * + * @param sSystem The particle system structure pointer. + * + * @return The last think time of the particle system. + */ +native Float:ParticleSystem_GetLastThink(const &ParticleSystem:sSystem); + +/** + * Retrieves the visibility bits of a particle. + * + * @param sSystem The particle system structure pointer. + * + * @return The visibility bits of the particle system. + */ +native Float:ParticleSystem_GetVisibilityBits(const &ParticleSystem:sSystem); + +/** + * Retrieves the origin of the particle system. + * + * @param sSystem The particle system structure pointer. + * @param vecOut The vector to set. + * + * @noreturn + */ +native ParticleSystem_GetOrigin(const &ParticleSystem:sSystem, const vecOut[]); + +/** + * Sets the origin of the particle system. + * + * @param sSystem The particle system structure pointer. + * @param vecOrigin The origin vector. + * + * @noreturn + */ +native ParticleSystem_SetOrigin(const &ParticleSystem:sSystem, const Float:vecOrigin[3]); + +/** + * Retrieves the parent System of the particle system. + * @param sSystem The particle system structure pointer. + * @param pParent The parent System. + * + * @noreturn + */ +native ParticleSystem_GetParentSystem(const &ParticleSystem:sSystem, pParent); + +/** + * Sets the parent System of the particle system. + * + * @param sSystem The particle system structure pointer. + * + * @return The parent System. + */ +native ParticleSystem_SetParentSystem(const &ParticleSystem:sSystem); + +/** + * Retrieves the effect name of the particle system. + * + * @param sSystem The particle system structure pointer. + * @param szOut The buffer to copy the value. + * @param iMaxLen Maximum size of buffer. + * + * @noreturn + */ +native ParticleSystem_GetEffect(const &ParticleSystem:sSystem, szOut[], iMaxLen); + +/** + * Sets the effect of the particle system. + * + * @param sSystem The particle system structure pointer. + * @param szName The name of the particle effect. + * + * @noreturn + */ +native ParticleSystem_SetEffect(const &ParticleSystem:sSystem, const szName[]); + +/** + * Checks if System has member + * + * @param sSystem System index + * @param szMember Member name + */ +native ParticleSystem_HasMember(const &ParticleSystem:sSystem, const szMember[]); + +/** + * Deletes member of an System + * + * @param sSystem System index + * @param szMember Member name + */ +native ParticleSystem_DeleteMember(const &ParticleSystem:sSystem, const szMember[]); + +/** + * Gets member of an System + * + * @param sSystem System index + * @param szMember Member name + * + * @return Member value + */ +native any:ParticleSystem_GetMember(const &ParticleSystem:sSystem, const szMember[]); + +/** + * Sets member of an System + * + * @param sSystem System index + * @param szMember Member name + * @param value Value to set + */ +native ParticleSystem_SetMember(const &ParticleSystem:sSystem, const szMember[], any:value); + +/** + * Gets vector member of an System + * + * @param sSystem System index + * @param szMember Member name + * @param vecOut Output vector + */ +native bool:ParticleSystem_GetMemberVec(const &ParticleSystem:sSystem, const szMember[], Float:vecOut[3]); + +/** + * Sets vector member of an System + * + * @param sSystem System index + * @param szMember Member name + * @param vecValue Vector to set + */ +native ParticleSystem_SetMemberVec(const &ParticleSystem:sSystem, const szMember[], const Float:vecValue[3]); + +/** + * Gets string member of an System + * + * @param sSystem System index + * @param szMember Member name + * @param szOut Buffer to copy the value + * @param iLen Maximum size of buffer + */ +native bool:ParticleSystem_GetMemberString(const &ParticleSystem:sSystem, const szMember[], szOut[], iLen); + +/** + * Sets string member of an System + * + * @param sSystem System index + * @param szMember Member name + * @param szValue String value to set + */ +native ParticleSystem_SetMemberString(const &ParticleSystem:sSystem, const szMember[], const szValue[]); + +/** + * Gets particle index by particle pointer. + * + * @param sParticle The particle pointer. + * + * @return The index of the particle. + */ +native Particle_GetIndex(const &Particle:sParticle); + +/** + * Gets particle batch index by particle pointer. + * + * @param sParticle The particle pointer. + * + * @return The batch index of the particle. + */ +native Particle_GetBatchIndex(const &Particle:sParticle); + +/** + * Gets particle entity by particle pointer. + * + * @param sParticle The particle pointer. + * + * @return The entity of the particle. + */ +native Particle_GetEntity(const &Particle:sParticle); + +/** + * Gets particle system pointer by particle pointer. + * + * @param sParticle The particle pointer. + * + * @return The particle system pointer. + */ +native ParticleSystem:Particle_GetSystem(const &Particle:sParticle); + +/** + * Retrieves the time at which a particle was created. + * + * @param sParticle The particle pointer. + * + * @return The creation time of the particle. + */ +native Float:Particle_GetCreatedTime(const &Particle:sParticle); + +/** + * Retrieves the time at which a particle will killed. + * + * @param sParticle The particle pointer. + * + * @return The kill time of the particle. + */ +native Float:Particle_GetKillTime(const &Particle:sParticle); + +/** + * Retrieves the time at which a particle was thinked last. + * + * @param sParticle The particle pointer. + * + * @return The last think time of the particle. + */ +native Float:Particle_GetLastThink(const &Particle:sParticle); + +/** + * Retrieves the origin of the particle. + * + * @param sSystem The particle structure pointer. + * @param vecOut The vector to set. + * + * @noreturn + */ +native Particle_GetOrigin(const &Particle:sParticle, Float:vecOut[3]); + +/** + * Sets the origin of the particle. + * + * @param sSystem The particle structure pointer. + * @param vecOrigin The origin vector. + * + * @noreturn + */ +native Particle_SetOrigin(const &Particle:sParticle, const Float:vecOrigin[3]); + +/** + * Retrieves the angles of the particle. + * + * @param sSystem The particle structure pointer. + * @param vecOut The vector to set. + * + * @noreturn + */ +native Particle_GetAngles(const &Particle:sParticle, Float:vecOut[3]); + +/** + * Sets the angles of the particle. + * + * @param sSystem The particle structure pointer. + * @param vecAngles The angles vector. + * + * @noreturn + */ +native Particle_SetAngles(const &Particle:sParticle, const Float:vecAngles[3]); + +/** + * Retrieves the velocity of the particle. + * + * @param sSystem The particle structure pointer. + * @param vecOut The vector to set. + * + * @noreturn + */ +native Particle_GetVelocity(const &Particle:sParticle, Float:vecOut[3]); + +/** + * Sets the velocity of the particle. + * + * @param sSystem The particle structure pointer. + * @param vecVelocity The origin velocity. + * + * @noreturn + */ +native Particle_SetVelocity(const &Particle:sParticle, const Float:vecVelocity[3]); diff --git a/api/particles/include/api_particles_const.inc b/api/particles/include/api_particles_const.inc new file mode 100644 index 0000000..7190a1d --- /dev/null +++ b/api/particles/include/api_particles_const.inc @@ -0,0 +1,23 @@ +#if defined _api_particles_const_included + #endinput +#endif +#define _api_particles_const_included + +#define PARTICLE_MAX_MEMBER_LENGTH 64 + +enum ParticleEffectHook { + ParticleEffectHook_System_Init, + ParticleEffectHook_System_Destroy, + ParticleEffectHook_System_Think, + ParticleEffectHook_System_Activated, + ParticleEffectHook_System_Deactivated, + ParticleEffectHook_Particle_Init, + ParticleEffectHook_Particle_Destroy, + ParticleEffectHook_Particle_Think, + ParticleEffectHook_Particle_EntityInit +}; + +enum ParticleEffectFlag (<<=1) { + ParticleEffectFlag_None = 0, + ParticleEffectFlag_AttachParticles = 1 +}; diff --git a/api/player-camera/api_player_camera.sma b/api/player-camera/api_player_camera.sma new file mode 100644 index 0000000..5ba3161 --- /dev/null +++ b/api/player-camera/api_player_camera.sma @@ -0,0 +1,289 @@ +#pragma semicolon 1 + +#include +#include +#include +#include + +new g_rgpPlayerCamera[MAX_PLAYERS + 1]; +new Float:g_rgflPlayerCameraDistance[MAX_PLAYERS + 1]; +new Float:g_rgflPlayerCameraAngles[MAX_PLAYERS + 1][3]; +new Float:g_rgflPlayerCameraOffset[MAX_PLAYERS + 1][3]; +new bool:g_rgbPlayerCameraAxisLock[MAX_PLAYERS + 1][3]; +new Float:g_rgflPlayerCameraThinkDelay[MAX_PLAYERS + 1]; +new Float:g_rgflPlayerCameraNextThink[MAX_PLAYERS + 1]; +new g_pPlayerTargetEntity[MAX_PLAYERS + 1]; + +new g_fwActivate; +new g_fwDeactivate; +new g_fwActivated; +new g_fwDeactivated; + +new g_iszTriggerCameraClassname; + +new g_iCameraModelIndex; + +public plugin_precache() { + g_iszTriggerCameraClassname = engfunc(EngFunc_AllocString, "trigger_camera"); + + g_iCameraModelIndex = precache_model("models/rpgrocket.mdl"); +} + +public plugin_init() { + register_plugin("[API] Player Camera", "1.0.0", "Hedgehog Fog"); + + RegisterHamPlayer(Ham_Spawn, "HamHook_Player_Spawn_Post", .Post = 1); + RegisterHamPlayer(Ham_Player_PreThink, "HamHook_Player_PreThink_Post", .Post = 1); + + g_fwActivate = CreateMultiForward("PlayerCamera_Fw_Activate", ET_STOP, FP_CELL); + g_fwDeactivate = CreateMultiForward("PlayerCamera_Fw_Deactivate", ET_STOP, FP_CELL); + g_fwActivated = CreateMultiForward("PlayerCamera_Fw_Activated", ET_IGNORE, FP_CELL); + g_fwDeactivated = CreateMultiForward("PlayerCamera_Fw_Deactivated", ET_IGNORE, FP_CELL); +} + +public plugin_natives() { + register_library("api_player_camera"); + register_native("PlayerCamera_Activate", "Native_Activate"); + register_native("PlayerCamera_Deactivate", "Native_Deactivate"); + register_native("PlayerCamera_IsActive", "Native_IsActive"); + register_native("PlayerCamera_SetOffset", "Native_SetOffset"); + register_native("PlayerCamera_SetAngles", "Native_SetAngles"); + register_native("PlayerCamera_SetDistance", "Native_SetDistance"); + register_native("PlayerCamera_SetAxisLock", "Native_SetAxisLock"); + register_native("PlayerCamera_SetThinkDelay", "Native_SetThinkDelay"); + register_native("PlayerCamera_SetTargetEntity", "Native_SetTargetEntity"); +} + +public bool:Native_Activate(iPluginId, iArgc) { + new pPlayer = get_param(1); + + ActivatePlayerCamera(pPlayer); +} + +public Native_Deactivate(iPluginId, iArgc) { + new pPlayer = get_param(1); + + DeactivatePlayerCamera(pPlayer); +} + +public Native_IsActive(iPluginId, iArgc) { + new pPlayer = get_param(1); + + return g_rgpPlayerCamera[pPlayer] != -1; +} + +public Native_SetOffset(iPluginId, iArgc) { + new pPlayer = get_param(1); + + static Float:vecOffset[3]; + get_array_f(2, vecOffset, 3); + + SetCameraOffset(pPlayer, vecOffset); +} + +public Native_SetAngles(iPluginId, iArgc) { + new pPlayer = get_param(1); + + static Float:vecAngles[3]; + get_array_f(2, vecAngles, 3); + + SetCameraAngles(pPlayer, vecAngles); +} + +public Native_SetDistance(iPluginId, iArgc) { + new pPlayer = get_param(1); + new Float:flDistance = get_param_f(2); + + SetCameraDistance(pPlayer, flDistance); +} + +public Native_SetAxisLock(iPluginId, iArgc) { + new pPlayer = get_param(1); + new bool:bLockPitch = bool:get_param(2); + new bool:bLockYaw = bool:get_param(3); + new bool:bLockRoll = bool:get_param(4); + + SetAxisLock(pPlayer, bLockPitch, bLockYaw, bLockRoll); +} + +public Native_SetThinkDelay(iPluginId, iArgc) { + new pPlayer = get_param(1); + new Float:flThinkDelay = get_param_f(2); + + SetCameraThinkDelay(pPlayer, flThinkDelay); +} + +public Native_SetTargetEntity(iPluginId, iArgc) { + new pPlayer = get_param(1); + new pTarget = get_param(2); + + SetCameraTarget(pPlayer, pTarget); +} + +public HamHook_Player_Spawn_Post(pPlayer) { + ReattachCamera(pPlayer); +} + +public HamHook_Player_PreThink_Post(pPlayer) { + PlayerCameraThink(pPlayer); +} + +public client_connect(pPlayer) { + g_rgpPlayerCamera[pPlayer] = -1; + SetCameraTarget(pPlayer, pPlayer); + SetCameraDistance(pPlayer, 200.0); + SetAxisLock(pPlayer, false, false, false); + SetCameraAngles(pPlayer, Float:{0.0, 0.0, 0.0}); + SetCameraOffset(pPlayer, Float:{0.0, 0.0, 0.0}); + SetCameraThinkDelay(pPlayer, 0.01); +} + +public client_disconnected(pPlayer) { + DeactivatePlayerCamera(pPlayer); +} + +ActivatePlayerCamera(pPlayer) { + if (g_rgpPlayerCamera[pPlayer] != -1) { + return; + } + + new iResult = 0; ExecuteForward(g_fwActivate, iResult, pPlayer); + if (iResult != PLUGIN_CONTINUE) return; + + g_rgpPlayerCamera[pPlayer] = CreatePlayerCamera(pPlayer); + g_rgflPlayerCameraNextThink[pPlayer] = 0.0; + + engfunc(EngFunc_SetView, pPlayer, g_rgpPlayerCamera[pPlayer]); + + static Float:vecOrigin[3]; + pev(g_pPlayerTargetEntity[pPlayer], pev_origin, vecOrigin); + engfunc(EngFunc_SetOrigin, g_rgpPlayerCamera[pPlayer], vecOrigin); + + ExecuteForward(g_fwActivated, _, pPlayer); +} + +DeactivatePlayerCamera(pPlayer) { + if (g_rgpPlayerCamera[pPlayer] == -1) { + return; + } + + new iResult = 0; ExecuteForward(g_fwDeactivate, iResult, pPlayer); + if (iResult != PLUGIN_CONTINUE) return; + + engfunc(EngFunc_RemoveEntity, g_rgpPlayerCamera[pPlayer]); + g_rgpPlayerCamera[pPlayer] = -1; + g_pPlayerTargetEntity[pPlayer] = pPlayer; + + if (is_user_connected(pPlayer)) { + engfunc(EngFunc_SetView, pPlayer, pPlayer); + } + + ExecuteForward(g_fwDeactivated, _, pPlayer); +} + +SetCameraOffset(pPlayer, const Float:vecOffset[3]) { + xs_vec_copy(vecOffset, g_rgflPlayerCameraOffset[pPlayer]); +} + +SetCameraAngles(pPlayer, const Float:vecAngles[3]) { + xs_vec_copy(vecAngles, g_rgflPlayerCameraAngles[pPlayer]); +} + +SetCameraDistance(pPlayer, Float:flDistance) { + g_rgflPlayerCameraDistance[pPlayer] = flDistance; +} + +SetAxisLock(pPlayer, bool:bLockPitch, bool:bLockYaw, bool:bLockRoll) { + g_rgbPlayerCameraAxisLock[pPlayer][0] = bLockPitch; + g_rgbPlayerCameraAxisLock[pPlayer][1] = bLockYaw; + g_rgbPlayerCameraAxisLock[pPlayer][2] = bLockRoll; +} + +SetCameraThinkDelay(pPlayer, Float:flThinkDelay) { + g_rgflPlayerCameraThinkDelay[pPlayer] = flThinkDelay; +} + +SetCameraTarget(pPlayer, pTarget) { + g_pPlayerTargetEntity[pPlayer] = pTarget; +} + +CreatePlayerCamera(pPlayer) { + new pCamera = engfunc(EngFunc_CreateNamedEntity, g_iszTriggerCameraClassname); + + set_pev(pCamera, pev_classname, "trigger_camera"); + set_pev(pCamera, pev_modelindex, g_iCameraModelIndex); + set_pev(pCamera, pev_owner, pPlayer); + set_pev(pCamera, pev_solid, SOLID_NOT); + set_pev(pCamera, pev_movetype, MOVETYPE_FLY); + set_pev(pCamera, pev_rendermode, kRenderTransTexture); + + return pCamera; +} + +PlayerCameraThink(pPlayer) { + if (g_rgflPlayerCameraNextThink[pPlayer] > get_gametime()) { + return; + } + + g_rgflPlayerCameraNextThink[pPlayer] = get_gametime() + g_rgflPlayerCameraThinkDelay[pPlayer]; + + if (g_rgpPlayerCamera[pPlayer] == -1) { + return; + } + + if (!is_user_alive(pPlayer)) { + return; + } + + static Float:vecOrigin[3]; + pev(g_pPlayerTargetEntity[pPlayer], pev_origin, vecOrigin); + xs_vec_add(vecOrigin, g_rgflPlayerCameraOffset[pPlayer], vecOrigin); + + static Float:vecAngles[3]; + pev(g_pPlayerTargetEntity[pPlayer], pev_v_angle, vecAngles); + for (new iAxis = 0; iAxis < 3; ++iAxis) { + if (g_rgbPlayerCameraAxisLock[pPlayer][iAxis]) { + vecAngles[iAxis] = 0.0; + } + } + + xs_vec_add(vecAngles, g_rgflPlayerCameraAngles[pPlayer], vecAngles); + + static Float:vecBack[3]; + angle_vector(vecAngles, ANGLEVECTOR_FORWARD, vecBack); + xs_vec_neg(vecBack, vecBack); + + static Float:vecVelocity[3]; + pev(g_pPlayerTargetEntity[pPlayer], pev_velocity, vecVelocity); + + static Float:vecCameraOrigin[3]; + for (new i = 0; i < 3; ++i) { + vecCameraOrigin[i] = vecOrigin[i] + (vecBack[i] * g_rgflPlayerCameraDistance[pPlayer]); + } + + new pTr = create_tr2(); + engfunc(EngFunc_TraceLine, vecOrigin, vecCameraOrigin, IGNORE_MONSTERS, pPlayer, pTr); + + static Float:flFraction; + get_tr2(pTr, TR_flFraction, flFraction); + + free_tr2(pTr); + + if(flFraction != 1.0) { + for (new i = 0; i < 3; ++i) { + vecCameraOrigin[i] = vecOrigin[i] + (vecBack[i] * (g_rgflPlayerCameraDistance[pPlayer] * flFraction)); + } + } + + set_pev(g_rgpPlayerCamera[pPlayer], pev_origin, vecCameraOrigin); + set_pev(g_rgpPlayerCamera[pPlayer], pev_angles, vecAngles); + set_pev(g_rgpPlayerCamera[pPlayer], pev_velocity, vecVelocity); +} + +ReattachCamera(pPlayer) { + if (g_rgpPlayerCamera[pPlayer] == -1) { + return; + } + + engfunc(EngFunc_SetView, pPlayer, g_rgpPlayerCamera[pPlayer]); +} diff --git a/api/player-camera/include/api_player_camera.inc b/api/player-camera/include/api_player_camera.inc new file mode 100644 index 0000000..cf4d5fc --- /dev/null +++ b/api/player-camera/include/api_player_camera.inc @@ -0,0 +1,131 @@ +#if defined _api_player_camera_included + #endinput +#endif +#define _api_player_camera_included + +#pragma reqlib api_player_camera + +/** + * Activates the player's camera. + * + * @param pPlayer The player entity. + * + * @noreturn + */ +native PlayerCamera_Activate(pPlayer); + +/** + * Deactivates the player's camera. + * + * @param pPlayer The player entity. + * + * @noreturn + */ +native PlayerCamera_Deactivate(pPlayer); + +/** + * Checks if the player's camera is active. + * + * @param pPlayer The player entity. + * + * @return True if the camera is active, false otherwise. + */ +native bool:PlayerCamera_IsActive(pPlayer); + +/** + * Sets the offset of the player's camera. + * + * @param pPlayer The player entity. + * @param vecOffset The offset vector. + * + * @noreturn + */ +native PlayerCamera_SetOffset(pPlayer, const Float:vecOffset[3]); + +/** + * Sets the angles of the player's camera. + * + * @param pPlayer The player entity. + * @param vecAngles The angles vector. + * + * @noreturn + */ +native PlayerCamera_SetAngles(pPlayer, const Float:vecAngles[3]); + +/** + * Sets the distance of the player's camera. + * + * @param pPlayer The player entity. + * @param flDistance The distance value. + * + * @noreturn + */ +native PlayerCamera_SetDistance(pPlayer, Float:flDistance); + +/** + * Sets the axis lock of the player's camera. + * + * @param pPlayer The player entity. + * @param bLockPitch Lock Pitch axis. + * @param bLockYaw Lock Yaw axis. + * @param bLockRoll Lock Roll axis. + * + * @noreturn + */ +native PlayerCamera_SetAxisLock(pPlayer, bool:bLockPitch, bool:bLockYaw, bool:bLockRoll); + +/** + * Sets the target entity of the player's camera. + * + * @param pPlayer The player entity. + * @param pTarget The target entity. + * + * @noreturn + */ +native PlayerCamera_SetTargetEntity(pPlayer, pTarget); + +/** + * Sets the thinking delay of the player's camera. + * + * @param pPlayer The player entity. + * @param flThinkDelay The thinking delay value. + * + * @noreturn + */ +native PlayerCamera_SetThinkDelay(pPlayer, Float:flThinkDelay); + +/** + * Function called when the player's camera is activated. + * + * @param pPlayer The player entity. + * + * @noreturn + */ +forward PlayerCamera_Fw_Activate(pPlayer); + +/** + * Function called when the player's camera is deactivated. + * + * @param pPlayer The player entity. + * + * @noreturn + */ +forward PlayerCamera_Fw_Deactivate(pPlayer); + +/** + * Function called when the player's camera is activated. + * + * @param pPlayer The player entity. + * + * @noreturn + */ +forward PlayerCamera_Fw_Activated(pPlayer); + +/** + * Function called when the player's camera is deactivated. + * + * @param pPlayer The player entity. + * + * @noreturn + */ +forward PlayerCamera_Fw_Deactivated(pPlayer); diff --git a/api/player-cosmetics/api_player_cosmetics.sma b/api/player-cosmetics/api_player_cosmetics.sma new file mode 100644 index 0000000..c436267 --- /dev/null +++ b/api/player-cosmetics/api_player_cosmetics.sma @@ -0,0 +1,237 @@ +#pragma semicolon 1 + +#include +#include +#include +#include + +#include + +#define PLUGIN "[API] Player Cosmetics" +#define VERSION "1.0.0" +#define AUTHOR "Hedgehog Fog" + +#define COSMETIC_CLASSNAME "_cosmetic" + +new Trie:g_itPlayerCosmetics[MAX_PLAYERS + 1]; +new g_iszCosmeticClassName; + +new Float:g_rgflPlayerNextRenderingUpdate[MAX_PLAYERS + 1]; + +public plugin_precache() { + g_iszCosmeticClassName = engfunc(EngFunc_AllocString, "info_target"); +} + +public plugin_init() { + register_plugin(PLUGIN, VERSION, AUTHOR); + + RegisterHam(Ham_Think, "info_target", "HamHook_Target_Think", .Post = 0); + + register_concmd("player_cosmetic_equip", "Command_Equip", ADMIN_CVAR); + register_concmd("player_cosmetic_unequip", "Command_Unequip", ADMIN_CVAR); +} + +public plugin_natives() { + register_library("api_player_cosmetic"); + register_native("PlayerCosmetic_Equip", "Native_Equip"); + register_native("PlayerCosmetic_Unequip", "Native_Unquip"); + register_native("PlayerCosmetic_IsEquiped", "Native_IsEquiped"); + register_native("PlayerCosmetic_GetEntity", "Native_GetEntity"); +} + +public client_connect(pPlayer) { + g_itPlayerCosmetics[pPlayer] = TrieCreate(); + g_rgflPlayerNextRenderingUpdate[pPlayer] = get_gametime(); +} + +public client_disconnected(pPlayer) { + for (new TrieIter:iIterator = TrieIterCreate(g_itPlayerCosmetics[pPlayer]); !TrieIterEnded(iIterator); TrieIterNext(iIterator)) { + static pCosmetic; + TrieIterGetCell(iIterator, pCosmetic); + @PlayerCosmetic_Destroy(pCosmetic); + } + + TrieDestroy(g_itPlayerCosmetics[pPlayer]); +} + +public Native_Equip(iPluginId, iArgc) { + new pPlayer = get_param(1); + new iModelIndex = get_param(2); + + return @Player_EquipCosmetic(pPlayer, iModelIndex); +} + +public Native_Unquip(iPluginId, iArgc) { + new pPlayer = get_param(1); + new iModelIndex = get_param(2); + + return @Player_UnequipCosmetic(pPlayer, iModelIndex); +} + +public Native_IsEquiped(iPluginId, iArgc) { + new pPlayer = get_param(1); + new iModelIndex = get_param(2); + + return @Player_IsCosmeticEquiped(pPlayer, iModelIndex); +} + +public Native_GetEntity(iPluginId, iArgc) { + new pPlayer = get_param(1); + new iModelIndex = get_param(2); + + return @Player_GetCosmeticEntity(pPlayer, iModelIndex); +} + +public Command_Equip(pPlayer, iLevel, iCId) { + if (!cmd_access(pPlayer, iLevel, iCId, 2)) { + return PLUGIN_HANDLED; + } + + static szTarget[32]; + read_argv(1, szTarget, charsmax(szTarget)); + + static szModel[256]; + read_argv(2, szModel, charsmax(szModel)); + + new iTarget = CMD_RESOLVE_TARGET(szTarget); + new iModelIndex = engfunc(EngFunc_ModelIndex, szModel); + + for (new pTarget = 1; pTarget <= MaxClients; ++pTarget) { + if (CMD_SHOULD_TARGET_PLAYER(pTarget, iTarget, pPlayer)) { + @Player_EquipCosmetic(pTarget, iModelIndex); + } + } + + return PLUGIN_HANDLED; +} + +public Command_Unequip(pPlayer, iLevel, iCId) { + if (!cmd_access(pPlayer, iLevel, iCId, 2)) { + return PLUGIN_HANDLED; + } + + static szTarget[32]; read_argv(1, szTarget, charsmax(szTarget)); + static szModel[256]; read_argv(2, szModel, charsmax(szModel)); + + new iTarget = CMD_RESOLVE_TARGET(szTarget); + new iModelIndex = engfunc(EngFunc_ModelIndex, szModel); + + for (new pTarget = 1; pTarget <= MaxClients; ++pTarget) { + if (CMD_SHOULD_TARGET_PLAYER(pTarget, iTarget, pPlayer)) { + @Player_UnequipCosmetic(pTarget, iModelIndex); + } + } + + return PLUGIN_HANDLED; +} + +public HamHook_Target_Think(pEntity) { + static szClassName[32]; + pev(pEntity, pev_classname, szClassName, charsmax(szClassName)); + + if (equal(szClassName, COSMETIC_CLASSNAME)) { + @PlayerCosmetic_Think(pEntity); + } +} + +@Player_EquipCosmetic(this, iModelIndex) { + if (g_itPlayerCosmetics[this] == Invalid_Trie) { + return -1; + } + + static szModelIndex[8]; + num_to_str(iModelIndex, szModelIndex, charsmax(szModelIndex)); + + new pCosmetic = -1; + if (TrieKeyExists(g_itPlayerCosmetics[this], szModelIndex)) { + TrieGetCell(g_itPlayerCosmetics[this], szModelIndex, pCosmetic); + } else { + pCosmetic = @PlayerCosmetic_Create(this, iModelIndex); + TrieSetCell(g_itPlayerCosmetics[this], szModelIndex, pCosmetic); + } + + return pCosmetic; +} + +bool:@Player_UnequipCosmetic(this, iModelIndex) { + if (g_itPlayerCosmetics[this] == Invalid_Trie) { + return false; + } + + static szModelIndex[8]; + num_to_str(iModelIndex, szModelIndex, charsmax(szModelIndex)); + + static pCosmetic; + if (!TrieGetCell(g_itPlayerCosmetics[this], szModelIndex, pCosmetic)) { + return false; + } + + @PlayerCosmetic_Destroy(pCosmetic); + TrieDeleteKey(g_itPlayerCosmetics[this], szModelIndex); + + return true; +} + +bool:@Player_IsCosmeticEquiped(this, iModelIndex) { + if (g_itPlayerCosmetics[this] == Invalid_Trie) { + return false; + } + + static szModelIndex[8]; + num_to_str(iModelIndex, szModelIndex, charsmax(szModelIndex)); + + return TrieKeyExists(g_itPlayerCosmetics[this], szModelIndex); +} + +@Player_GetCosmeticEntity(this, iModelIndex) { + if (g_itPlayerCosmetics[this] == Invalid_Trie) { + return -1; + } + + static szModelIndex[8]; + num_to_str(iModelIndex, szModelIndex, charsmax(szModelIndex)); + + static pCosmetic; + if (!TrieGetCell(g_itPlayerCosmetics[this], szModelIndex, pCosmetic)) { + return -1; + } + + return pCosmetic; +} + +@PlayerCosmetic_Create(pPlayer, iModelIndex) { + new this = engfunc(EngFunc_CreateNamedEntity, g_iszCosmeticClassName); + + set_pev(this, pev_classname, COSMETIC_CLASSNAME); + set_pev(this, pev_movetype, MOVETYPE_FOLLOW); + set_pev(this, pev_aiment, pPlayer); + set_pev(this, pev_owner, pPlayer); + set_pev(this, pev_modelindex, iModelIndex); + + set_pev(this, pev_nextthink, get_gametime()); + + return this; +} + +@PlayerCosmetic_Think(this) { + new pOwner = pev(this, pev_owner); + + static iRenderMode; iRenderMode = pev(pOwner, pev_rendermode); + static iRenderFx; iRenderFx = pev(pOwner, pev_renderfx); + static Float:flRenderAmt; pev(pOwner, pev_renderamt, flRenderAmt); + static Float:rgflRenderColor[3]; pev(pOwner, pev_rendercolor, rgflRenderColor); + + set_pev(this, pev_rendermode, iRenderMode); + set_pev(this, pev_renderamt, flRenderAmt); + set_pev(this, pev_renderfx, iRenderFx); + set_pev(this, pev_rendercolor, rgflRenderColor); + + set_pev(this, pev_nextthink, get_gametime() + 0.1); +} + +@PlayerCosmetic_Destroy(this) { + set_pev(this, pev_movetype, MOVETYPE_NONE); + set_pev(this, pev_aiment, 0); + set_pev(this, pev_flags, pev(this, pev_flags) | FL_KILLME); + dllfunc(DLLFunc_Think, this); +} diff --git a/api/player-cosmetics/include/api_player_cosmetics.inc b/api/player-cosmetics/include/api_player_cosmetics.inc new file mode 100644 index 0000000..05e1e31 --- /dev/null +++ b/api/player-cosmetics/include/api_player_cosmetics.inc @@ -0,0 +1,46 @@ +#if defined _api_player_cosmetic_included + #endinput +#endif +#define _api_player_cosmetic_included + +#pragma reqlib api_player_cosmetic + +/** + * Equip a player with a cosmetic item. + * + * @param pPlayer The player to equip the item to. + * @param iModelIndex The index of the cosmetic item model. + * + * @noreturn + */ +native PlayerCosmetic_Equip(pPlayer, iModelIndex); + +/** + * Unequip a cosmetic item from a player. + * + * @param pPlayer The player to unequip the item from. + * @param iModelIndex The index of the cosmetic item model. + * + * @return True if the item was successfully unequipped, false otherwise. + */ +native bool:PlayerCosmetic_Unequip(pPlayer, iModelIndex); + +/** + * Check if a player has equipped a specific cosmetic item. + * + * @param pPlayer The player to check. + * @param iModelIndex The index of the cosmetic item model. + * + * @return True if the player has the item equipped, false otherwise. + */ +native bool:PlayerCosmetic_IsEquiped(pPlayer, iModelIndex); + +/** + * Get the entity of the equipped cosmetic item for a player. + * + * @param pPlayer The player whose equipped item entity to get. + * @param iModelIndex The index of the cosmetic item model. + * + * @return The entity of the equipped cosmetic item, or 0 if not equipped. + */ +native PlayerCosmetic_GetEntity(pPlayer, iModelIndex); diff --git a/api/player-dizziness/api_player_dizziness.sma b/api/player-dizziness/api_player_dizziness.sma new file mode 100644 index 0000000..4d1daff --- /dev/null +++ b/api/player-dizziness/api_player_dizziness.sma @@ -0,0 +1,237 @@ +#pragma semicolon 1 + +#include +#include +#include +#include + +#define DIZZINESS_THINK_RATE 0.01 +#define DIZZINESS_RANDOM_PUSH_RATE 1.0 +#define DIZZINESS_JUMP_SPEED 200.0 +#define DIZZINESS_ANGLE_HANDLE_SPEED 100.0 +#define DIZZINESS_ANGLE_HANDLE_SPEED_MIN 50.0 +#define DIZZINESS_ANGLE_HANDLE_SPEED_MAX 150.0 +#define DIZZINESS_PUSH_SPEED 30.0 +#define DIZZINESS_PUSH_SPEED_MIN 0.0 +#define DIZZINESS_PUSH_SPEED_MAX 40.0 +#define DIZZINESS_PUNCH_ANGLE 45.0 +#define DIZZINESS_PUNCH_ANGLE_MIN 20.0 +#define DIZZINESS_PUNCH_ANGLE_MAX 75.0 +#define DIZZINESS_BLINK_DURATION 0.1 +#define DIZZINESS_BLINK_DURATION_MIN 0.1 +#define DIZZINESS_BLINK_DURATION_MAX 1.0 +#define DIZZINESS_BLINK_TRANSITION_DURATION 0.75 +#define DIZZINESS_BLINK_TRANSITION_DURATION_MIN 0.25 +#define DIZZINESS_BLINK_TRANSITION_DURATION_MAX 1.0 +#define DIZZINESS_RANDOM_BLINK_RATE 3.0 +#define DIZZINESS_RANDOM_BLINK_RATE_MIN 1.0 +#define DIZZINESS_RANDOM_BLINK_RATE_MAX 10.0 +#define DIZZINESS_MIN_STRENGTH_TO_BLINK 0.5 + +new gmsgScreenFade; + +new Float:g_rgflPlayerDizzinessNextThink[MAX_PLAYERS + 1]; +new Float:g_rgflPlayerDizzinessStrength[MAX_PLAYERS + 1]; +new Float:g_rgflPlayerNextPush[MAX_PLAYERS + 1]; +new Float:g_rgvecPlayerPushVelocityTarget[MAX_PLAYERS + 1][3]; +new Float:g_rgvecPlayerPushVelocityAcc[MAX_PLAYERS + 1][3]; +new Float:g_rgflPlayerLastPushThink[MAX_PLAYERS + 1]; +new Float:g_rgflPlayerNextPushThink[MAX_PLAYERS + 1]; +new Float:g_rgflPlayerNextBlink[MAX_PLAYERS + 1]; + +public plugin_init() { + register_plugin("[API] Player Dizziness", "1.0.0", "Hedgehog Fog"); + + RegisterHamPlayer(Ham_Player_Jump, "HamHook_Player_Jump_Post", .Post = 1); + RegisterHamPlayer(Ham_Player_PreThink, "HamHook_Player_PreThink_Post", .Post = 1); + + gmsgScreenFade = get_user_msgid("ScreenFade"); +} + +public plugin_natives() { + register_library("api_player_dizziness"); + register_native("PlayerDizziness_Set", "Native_SetPlayerDizziness"); + register_native("PlayerDizziness_Get", "Native_GetPlayerDizziness"); +} + +public Native_SetPlayerDizziness(iPluginId, iArgc) { + new pPlayer = get_param(1); + new Float:flValue = get_param_f(2); + + g_rgflPlayerDizzinessStrength[pPlayer] = floatclamp(flValue, 0.0, 10.0); +} + +public Float:Native_GetPlayerDizziness(iPluginId, iArgc) { + new pPlayer = get_param(1); + + return g_rgflPlayerDizzinessStrength[pPlayer]; +} + +public client_connect(pPlayer) { + g_rgflPlayerDizzinessNextThink[pPlayer] = 0.0; + g_rgflPlayerDizzinessStrength[pPlayer] = 0.0; + g_rgflPlayerNextPush[pPlayer] = 0.0; + g_rgflPlayerLastPushThink[pPlayer] = 0.0; + g_rgflPlayerNextPushThink[pPlayer] = 0.0; + g_rgflPlayerNextBlink[pPlayer] = 0.0; + + xs_vec_copy(Float:{0.0, 0.0, 0.0}, g_rgvecPlayerPushVelocityTarget[pPlayer]); + xs_vec_copy(Float:{0.0, 0.0, 0.0}, g_rgvecPlayerPushVelocityAcc[pPlayer]); +} + +public HamHook_Player_PreThink_Post(pPlayer) { + new Float:flGameTime = get_gametime(); + + if (flGameTime >= g_rgflPlayerDizzinessNextThink[pPlayer]) { + @Player_Think(pPlayer); + g_rgflPlayerDizzinessNextThink[pPlayer] = flGameTime + DIZZINESS_THINK_RATE; + } +} + +public HamHook_Player_Jump_Post(pPlayer) { + if (pev(pPlayer, pev_flags) & FL_ONGROUND && ~pev(pPlayer, pev_oldbuttons) & IN_JUMP) { + @Player_Jump(pPlayer); + } +} + +@Player_Think(this) { + new Float:flDizzinessStrength = g_rgflPlayerDizzinessStrength[this]; + if (flDizzinessStrength <= 0.0) { + return; + } + + if (pev(this, pev_flags) & FL_FROZEN) { + return; + } + + new Float:flGameTime = get_gametime(); + + if (is_user_alive(this)) { + static Float:vecVelocity[3]; + pev(this, pev_velocity, vecVelocity); + + + new Float:flMaxPushForce = floatclamp(DIZZINESS_PUSH_SPEED * flDizzinessStrength, DIZZINESS_PUSH_SPEED_MIN, DIZZINESS_PUSH_SPEED_MAX); + if (g_rgflPlayerNextPush[this] <= flGameTime) { + @Player_Push(this, flMaxPushForce); + g_rgflPlayerNextPush[this] = flGameTime + DIZZINESS_RANDOM_PUSH_RATE; + } + + if (flDizzinessStrength >= DIZZINESS_MIN_STRENGTH_TO_BLINK) { + if (g_rgflPlayerNextBlink[this] <= flGameTime) { + new Float:flBlinkTransitionDuration = floatclamp(DIZZINESS_BLINK_TRANSITION_DURATION * flDizzinessStrength, DIZZINESS_BLINK_TRANSITION_DURATION_MIN, DIZZINESS_BLINK_TRANSITION_DURATION_MAX); + new Float:flBlinkDuration = floatclamp(DIZZINESS_BLINK_DURATION * flDizzinessStrength, DIZZINESS_BLINK_DURATION_MIN, DIZZINESS_BLINK_DURATION_MAX); + new Float:flBlinkRate = floatclamp(DIZZINESS_RANDOM_BLINK_RATE / flDizzinessStrength, DIZZINESS_RANDOM_BLINK_RATE_MIN, DIZZINESS_RANDOM_BLINK_RATE_MAX); + + @Player_Blink(this, flBlinkDuration, flBlinkTransitionDuration); + g_rgflPlayerNextBlink[this] = flGameTime + flBlinkRate + flBlinkDuration; + } + } + + @Player_PushThink(this); + + new Float:flMaxPunchAngle = floatclamp(DIZZINESS_PUNCH_ANGLE * flDizzinessStrength, DIZZINESS_PUNCH_ANGLE_MIN, DIZZINESS_PUNCH_ANGLE_MAX); + new Float:flAngleHandleSpeed = floatclamp(DIZZINESS_ANGLE_HANDLE_SPEED / flDizzinessStrength, DIZZINESS_ANGLE_HANDLE_SPEED_MIN, DIZZINESS_ANGLE_HANDLE_SPEED_MAX); + @Player_CameraThink(this, flMaxPunchAngle, flAngleHandleSpeed); + } +} + +@Player_PushThink(this) { + new Float:flGameTime = get_gametime(); + + if (pev(this, pev_flags) & FL_ONGROUND) { + new Float:flDelta = flGameTime - g_rgflPlayerLastPushThink[this]; + + for (new i = 0; i < 3; ++i) { + g_rgvecPlayerPushVelocityAcc[this][i] += (g_rgvecPlayerPushVelocityTarget[this][i] - g_rgvecPlayerPushVelocityAcc[this][i]) * flDelta; + } + + static Float:vecVelocity[3]; + pev(this, pev_velocity, vecVelocity); + xs_vec_add(vecVelocity, g_rgvecPlayerPushVelocityAcc[this], vecVelocity); + set_pev(this, pev_velocity, vecVelocity); + } + + g_rgflPlayerLastPushThink[this] = get_gametime(); +} + +@Player_CameraThink(this, Float:flMaxPunchAngle, Float:flAngleHandleSpeed) { + static Float:vecAngles[3]; + pev(this, pev_v_angle, vecAngles); + vecAngles[0] = 0.0; + vecAngles[2] = 0.0; + + static Float:vecForward[3]; + angle_vector(vecAngles, ANGLEVECTOR_FORWARD, vecForward); + + static Float:vecRight[3]; + angle_vector(vecAngles, ANGLEVECTOR_RIGHT, vecRight); + + static Float:vecVelocity[3]; + pev(this, pev_velocity, vecVelocity); + + static Float:flMaxSpeed; + pev(this, pev_maxspeed, flMaxSpeed); + flMaxSpeed = floatmin(flMaxSpeed, flAngleHandleSpeed); + + static Float:vecPunchAngle[3]; + pev(this, pev_punchangle, vecPunchAngle); + vecPunchAngle[0] += floatclamp(xs_vec_dot(vecVelocity, vecForward), -flMaxSpeed, flMaxSpeed) / flMaxSpeed * flMaxPunchAngle * DIZZINESS_THINK_RATE; + vecPunchAngle[2] += floatclamp(xs_vec_dot(vecVelocity, vecRight), -flMaxSpeed, flMaxSpeed) / flMaxSpeed * flMaxPunchAngle * DIZZINESS_THINK_RATE; + + if (xs_vec_len(vecPunchAngle) > 0.0) { + set_pev(this, pev_punchangle, vecPunchAngle); + } +} + +@Player_Push(this, Float:flMaxForce) { + new Float:flGameTime = get_gametime(); + + xs_vec_copy(Float:{0.0, 0.0, 0.0}, g_rgvecPlayerPushVelocityAcc[this]); + + g_rgvecPlayerPushVelocityTarget[this][0] = random_float(-flMaxForce, flMaxForce); + g_rgvecPlayerPushVelocityTarget[this][1] = random_float(-flMaxForce, flMaxForce); + g_rgvecPlayerPushVelocityTarget[this][2] = 0.0; + + g_rgflPlayerLastPushThink[this] = flGameTime; +} + +@Player_Blink(this, Float:flDuration, Float:flTransitionDuration) { + static const iFlags = 0; + static const rgiColor[3] = {0, 0, 0}; + static const iAlpha = 255; + + new iFadeTime = FixedUnsigned16(flTransitionDuration , 1<<12); + new iHoldTime = FixedUnsigned16(flDuration , 1<<12); + + emessage_begin(MSG_ONE, gmsgScreenFade, _, this); + ewrite_short(iFadeTime); + ewrite_short(iHoldTime); + ewrite_short(iFlags); + ewrite_byte(rgiColor[0]); + ewrite_byte(rgiColor[1]); + ewrite_byte(rgiColor[2]); + ewrite_byte(iAlpha); + emessage_end(); +} + +@Player_Jump(this) { + if (g_rgflPlayerDizzinessStrength[this] < 1.0) { + return; + } + + static Float:vecVelocity[3]; + vecVelocity[0] = random_float(-1.0, 1.0); + vecVelocity[1] = random_float(-1.0, 1.0); + vecVelocity[2] = 0.0; + + xs_vec_normalize(vecVelocity, vecVelocity); + xs_vec_mul_scalar(vecVelocity, random_float(80.0, 100.0), vecVelocity); + vecVelocity[2] = DIZZINESS_JUMP_SPEED; + + set_pev(this, pev_velocity, vecVelocity); +} + +stock FixedUnsigned16(Float:flValue, iScale) { + return clamp(floatround(flValue * iScale), 0, 0xFFFF); +} diff --git a/api/player-dizziness/include/api_player_dizziness.inc b/api/player-dizziness/include/api_player_dizziness.inc new file mode 100644 index 0000000..c36ddde --- /dev/null +++ b/api/player-dizziness/include/api_player_dizziness.inc @@ -0,0 +1,25 @@ +#if defined _api_player_dizziness_included + #endinput +#endif +#define _api_player_dizziness_included + +#pragma reqlib api_player_dizziness + +/** + * Sets the dizziness strength for a player. + * + * @param pPlayer The player ID. + * @param flStrength The strength of the dizziness. + * + * @noreturn + */ +native PlayerDizziness_Set(pPlayer, Float:flStrength); + +/** + * Gets the current dizziness strength for a player. + * + * @param pPlayer The player ID. + * + * @return The current dizziness strength as a float. + */ +native Float:PlayerDizziness_Get(pPlayer); diff --git a/api/player-effects/api_player_effects.sma b/api/player-effects/api_player_effects.sma new file mode 100644 index 0000000..1b2939f --- /dev/null +++ b/api/player-effects/api_player_effects.sma @@ -0,0 +1,312 @@ +#pragma semicolon 1 + +#include +#include +#include +#include + +#tryinclude +#include + +#include + +#define PLUGIN "[API] Player Effects" +#define VERSION "1.0.0" +#define AUTHOR "Hedgehog Fog" + +#define BIT(%0) (1<<(%0)) + +enum PEffectData { + Array:PEffectData_Id, + Array:PEffectData_InvokeFunctionId, + Array:PEffectData_RevokeFunctionId, + Array:PEffectData_PluginId, + Array:PEffectData_Icon, + Array:PEffectData_IconColor, + Array:PEffectData_Players, + Array:PEffectData_PlayerEffectDuration, + Array:PEffectData_PlayerEffectEnd +}; + +new gmsgStatusIcon; + +new Trie:g_itEffectsIds = Invalid_Trie; +new g_rgPEffectData[PEffectData] = { Invalid_Array, ... }; +new g_iEffectssNum = 0; + +public plugin_precache() { + g_itEffectsIds = TrieCreate(); + + g_rgPEffectData[PEffectData_Id] = ArrayCreate(32); + g_rgPEffectData[PEffectData_InvokeFunctionId] = ArrayCreate(); + g_rgPEffectData[PEffectData_RevokeFunctionId] = ArrayCreate(); + g_rgPEffectData[PEffectData_PluginId] = ArrayCreate(); + g_rgPEffectData[PEffectData_Icon] = ArrayCreate(32); + g_rgPEffectData[PEffectData_IconColor] = ArrayCreate(3); + g_rgPEffectData[PEffectData_Players] = ArrayCreate(); + g_rgPEffectData[PEffectData_PlayerEffectEnd] = ArrayCreate(MAX_PLAYERS + 1); + g_rgPEffectData[PEffectData_PlayerEffectDuration] = ArrayCreate(MAX_PLAYERS + 1); +} + +public plugin_init() { + register_plugin(PLUGIN, VERSION, AUTHOR); + + RegisterHamPlayer(Ham_Player_PostThink, "HamHook_Player_PostThink_Post", .Post = 1); + RegisterHamPlayer(Ham_Killed, "HamHook_Player_Killed", .Post = 0); + + register_concmd("player_effect_set", "Command_Set", ADMIN_CVAR); + + gmsgStatusIcon = get_user_msgid("StatusIcon"); +} + +public plugin_end() { + TrieDestroy(g_itEffectsIds); + + for (new PEffectData:iEffectData = PEffectData:0; iEffectData < PEffectData; ++iEffectData) { + ArrayDestroy(Array:g_rgPEffectData[iEffectData]); + } +} + +public plugin_natives() { + register_library("api_player_effects"); + register_native("PlayerEffect_Register", "Native_Register"); + register_native("PlayerEffect_Set", "Native_SetPlayerEffect"); + register_native("PlayerEffect_Get", "Native_GetPlayerEffect"); + register_native("PlayerEffect_GetEndtime", "Native_GetPlayerEffectEndTime"); + register_native("PlayerEffect_GetDuration", "Native_GetPlayerEffectDuration"); +} + +public Native_Register(iPluginId, iArgc) { + new szId[32]; get_string(1, szId, charsmax(szId)); + new szInvokeFunction[32]; get_string(2, szInvokeFunction, charsmax(szInvokeFunction)); + new szRevokeFunction[32]; get_string(3, szRevokeFunction, charsmax(szRevokeFunction)); + new szIcon[32]; get_string(4, szIcon, charsmax(szIcon)); + new rgiIconColor[3]; get_array(5, rgiIconColor, sizeof(rgiIconColor)); + + new iInvokeFunctionId = get_func_id(szInvokeFunction, iPluginId); + new iRevokeFunctionId = get_func_id(szRevokeFunction, iPluginId); + + return Register(szId, iInvokeFunctionId, iRevokeFunctionId, iPluginId, szIcon, rgiIconColor); +} + +public bool:Native_SetPlayerEffect(iPluginId, iArgc) { + new pPlayer = get_param(1); + new szEffectId[32]; get_string(2, szEffectId, charsmax(szEffectId)); + + new iEffectId = -1; + if (!TrieGetCell(g_itEffectsIds, szEffectId, iEffectId)) return false; + + new bool:bValue = bool:get_param(3); + new Float:flDuration = get_param_f(4); + new bool:bExtend = bool:get_param(5); + + return @Player_SetEffect(pPlayer, iEffectId, bValue, flDuration, bExtend); +} + +public bool:Native_GetPlayerEffect(iPluginId, iArgc) { + new pPlayer = get_param(1); + new szEffectId[32]; get_string(2, szEffectId, charsmax(szEffectId)); + + new iEffectId = -1; + if (!TrieGetCell(g_itEffectsIds, szEffectId, iEffectId)) return false; + + return @Player_GetEffect(pPlayer, iEffectId); +} + +public Float:Native_GetPlayerEffectEndTime(iPluginId, iArgc) { + new pPlayer = get_param(1); + new szEffectId[32]; get_string(2, szEffectId, charsmax(szEffectId)); + + new iEffectId = -1; + if (!TrieGetCell(g_itEffectsIds, szEffectId, iEffectId)) return 0.0; + + return Float:ArrayGetCell(g_rgPEffectData[PEffectData_PlayerEffectEnd], iEffectId, pPlayer); +} + +public Float:Native_GetPlayerEffectDuration(iPluginId, iArgc) { + new pPlayer = get_param(1); + new szEffectId[32]; get_string(2, szEffectId, charsmax(szEffectId)); + + new iEffectId = -1; + if (!TrieGetCell(g_itEffectsIds, szEffectId, iEffectId)) return 0.0; + + return Float:ArrayGetCell(g_rgPEffectData[PEffectData_PlayerEffectDuration], iEffectId, pPlayer); +} + +/*--------------------------------[ Forwards ]--------------------------------*/ + +public client_disconnected(pPlayer) { + @Player_RevokeEffects(pPlayer); +} + +public Round_Fw_NewRound() { + for (new pPlayer = 1; pPlayer <= MaxClients; ++pPlayer) @Player_RevokeEffects(pPlayer); +} + +/*--------------------------------[ Hooks ]--------------------------------*/ + +public Command_Set(pPlayer, iLevel, iCId) { + if (!cmd_access(pPlayer, iLevel, iCId, 1)) return PLUGIN_HANDLED; + + static szTarget[32]; read_argv(1, szTarget, charsmax(szTarget)); + static szEffectId[32]; read_argv(2, szEffectId, charsmax(szEffectId)); + static szValue[32]; read_argv(3, szValue, charsmax(szValue)); + static szDuration[32]; read_argv(4, szDuration, charsmax(szDuration)); + + new iEffectId = -1; + if (!TrieGetCell(g_itEffectsIds, szEffectId, iEffectId)) return PLUGIN_HANDLED; + + new iTarget = CMD_RESOLVE_TARGET(szTarget); + new bool:bValue = equal(szValue, NULL_STRING) ? true : bool:str_to_num(szValue); + new Float:flDuration = equal(szDuration, NULL_STRING) ? -1.0 : str_to_float(szDuration); + + for (new pTarget = 1; pTarget <= MaxClients; ++pTarget) { + if (!CMD_SHOULD_TARGET_PLAYER(pTarget, iTarget, pPlayer)) continue; + @Player_SetEffect(pTarget, iEffectId, bValue, flDuration, false); + } + + return PLUGIN_HANDLED; +} + +public HamHook_Player_Killed(pPlayer) { + @Player_RevokeEffects(pPlayer); +} + +public HamHook_Player_PostThink_Post(pPlayer) { + static Float:flGameTime; flGameTime = get_gametime(); + + for (new iEffectId = 0; iEffectId < g_iEffectssNum; ++iEffectId) { + static iPlayers; iPlayers = ArrayGetCell(g_rgPEffectData[PEffectData_Players], iEffectId); + if (~iPlayers & BIT(pPlayer & 31)) continue; + + static Float:flEndTime; flEndTime = ArrayGetCell(g_rgPEffectData[PEffectData_PlayerEffectEnd], iEffectId, pPlayer); + if (!flEndTime || flEndTime > flGameTime) continue; + + @Player_SetEffect(pPlayer, iEffectId, false, -1.0, true); + } +} + +/*--------------------------------[ Methods ]--------------------------------*/ + +bool:@Player_GetEffect(pPlayer, iEffectId) { + new iPlayers = ArrayGetCell(g_rgPEffectData[PEffectData_Players], iEffectId); + + return !!(iPlayers & BIT(pPlayer & 31)); +} + +bool:@Player_SetEffect(pPlayer, iEffectId, bool:bValue, Float:flDuration, bool:bExtend) { + if (bValue && !is_user_alive(pPlayer)) return false; + + new iPlayers = ArrayGetCell(g_rgPEffectData[PEffectData_Players], iEffectId); + new bool:bCurrentValue = !!(iPlayers & BIT(pPlayer & 31)); + + if (bValue == bCurrentValue && (!bValue || !bExtend)) return false; + + if (bValue) { + ArraySetCell(g_rgPEffectData[PEffectData_Players], iEffectId, iPlayers | BIT(pPlayer & 31)); + } else { + ArraySetCell(g_rgPEffectData[PEffectData_Players], iEffectId, iPlayers & ~BIT(pPlayer & 31)); + } + + new bool:bResult = ( + bValue + ? CallInvokeFunction(pPlayer, iEffectId, flDuration) + : CallRevokeFunction(pPlayer, iEffectId) + ); + + if (!bResult) { + ArraySetCell(g_rgPEffectData[PEffectData_Players], iEffectId, iPlayers); + return false; + } + + if (bValue) { + if (bCurrentValue && bExtend && flDuration >= 0.0) { + new Float:flEndTime = ArrayGetCell(g_rgPEffectData[PEffectData_PlayerEffectEnd], iEffectId, pPlayer); + if (flEndTime) { + new Float:flPrevDuration = ArrayGetCell(g_rgPEffectData[PEffectData_PlayerEffectDuration], iEffectId, pPlayer); + ArraySetCell(g_rgPEffectData[PEffectData_PlayerEffectEnd], iEffectId, flEndTime + flDuration, pPlayer); + ArraySetCell(g_rgPEffectData[PEffectData_PlayerEffectDuration], iEffectId, flPrevDuration + flDuration, pPlayer); + } + } else { + new Float:flEndTime = flDuration < 0.0 ? 0.0 : get_gametime() + flDuration; + ArraySetCell(g_rgPEffectData[PEffectData_PlayerEffectEnd], iEffectId, flEndTime, pPlayer); + ArraySetCell(g_rgPEffectData[PEffectData_PlayerEffectDuration], iEffectId, flDuration, pPlayer); + } + } + + static szIcon[32]; ArrayGetString(g_rgPEffectData[PEffectData_Icon], iEffectId, szIcon, charsmax(szIcon)); + + if (!equal(szIcon, NULL_STRING)) { + new irgIconColor[3]; + ArrayGetArray(g_rgPEffectData[PEffectData_IconColor], iEffectId, irgIconColor, sizeof(irgIconColor)); + + message_begin(MSG_ONE, gmsgStatusIcon, _, pPlayer); + write_byte(bValue); + write_string(szIcon); + + if (bValue) { + write_byte(irgIconColor[0]); + write_byte(irgIconColor[1]); + write_byte(irgIconColor[2]); + } + + message_end(); + } + + return true; +} + +@Player_RevokeEffects(pPlayer) { + for (new iEffectId = 0; iEffectId < g_iEffectssNum; ++iEffectId) { + @Player_SetEffect(pPlayer, iEffectId, false, -1.0, true); + } +} + +/*--------------------------------[ Functions ]--------------------------------*/ + +Register(const szId[], iInvokeFunctionId, iRevokeFunctionId, iPluginId, const szIcon[], const rgiIconColor[3]) { + new iEffectId = g_iEffectssNum; + + ArrayPushString(g_rgPEffectData[PEffectData_Id], szId); + ArrayPushCell(g_rgPEffectData[PEffectData_InvokeFunctionId], iInvokeFunctionId); + ArrayPushCell(g_rgPEffectData[PEffectData_RevokeFunctionId], iRevokeFunctionId); + ArrayPushCell(g_rgPEffectData[PEffectData_PluginId], iPluginId); + ArrayPushString(g_rgPEffectData[PEffectData_Icon], szIcon); + ArrayPushArray(g_rgPEffectData[PEffectData_IconColor], rgiIconColor); + ArrayPushCell(g_rgPEffectData[PEffectData_Players], 0); + ArrayPushCell(g_rgPEffectData[PEffectData_PlayerEffectEnd], 0); + ArrayPushCell(g_rgPEffectData[PEffectData_PlayerEffectDuration], 0); + + TrieSetCell(g_itEffectsIds, szId, iEffectId); + + g_iEffectssNum++; + + return iEffectId; +} + +bool:CallInvokeFunction(pPlayer, iEffectId, Float:flDuration) { + new iPluginId = ArrayGetCell(g_rgPEffectData[PEffectData_PluginId], iEffectId); + new iFunctionId = ArrayGetCell(g_rgPEffectData[PEffectData_InvokeFunctionId], iEffectId); + + callfunc_begin_i(iFunctionId, iPluginId); + callfunc_push_int(pPlayer); + callfunc_push_float(flDuration); + new iResult = callfunc_end(); + + if (iResult >= PLUGIN_HANDLED) return false; + + return true; +} + +bool:CallRevokeFunction(pPlayer, iEffectId) { + new iPluginId = ArrayGetCell(g_rgPEffectData[PEffectData_PluginId], iEffectId); + new iFunctionId = ArrayGetCell(g_rgPEffectData[PEffectData_RevokeFunctionId], iEffectId); + + callfunc_begin_i(iFunctionId, iPluginId); + callfunc_push_int(pPlayer); + new iResult = callfunc_end(); + + if (iResult >= PLUGIN_HANDLED) return false; + + return true; +} diff --git a/api/player-effects/include/api_player_effects.inc b/api/player-effects/include/api_player_effects.inc new file mode 100644 index 0000000..3ec22b8 --- /dev/null +++ b/api/player-effects/include/api_player_effects.inc @@ -0,0 +1,14 @@ +#if defined _api_player_effects_included + #endinput +#endif +#define _api_player_effects_included + +#pragma reqlib api_player_effects + +#include + +native PlayerEffect_Register(const szId[], const szInvokeFunction[], const szRevokeFunction[], const szIcon[] = "", const irgIconColor[3] = {255, 255, 255}); +native PlayerEffect_Set(pPlayer, const szId[], bool:bValue, Float:flDuration = -1.0, bool:bExtend = true); +native bool:PlayerEffect_Get(pPlayer, const szId[]); +native Float:PlayerEffect_GetEndtime(pPlayer, const szId[]); +native Float:PlayerEffect_GetDuration(pPlayer, const szId[]); diff --git a/api/player-effects/include/api_player_effects_const.inc b/api/player-effects/include/api_player_effects_const.inc new file mode 100644 index 0000000..142dd97 --- /dev/null +++ b/api/player-effects/include/api_player_effects_const.inc @@ -0,0 +1,4 @@ +#if defined _api_player_effects_const_included + #endinput +#endif +#define _api_player_effects_const_included diff --git a/api/player-inventory/api_player_inventory.sma b/api/player-inventory/api_player_inventory.sma new file mode 100644 index 0000000..633ab41 --- /dev/null +++ b/api/player-inventory/api_player_inventory.sma @@ -0,0 +1,388 @@ +#pragma semicolon 1 + +#include +#include +#include + +#include + +#define VAULT_NAME "api_player_inventory" +#define VAULT_VERSION 1 + +#define PLUGIN "[API] Player Inventory" +#define VERSION "1.0.0" +#define AUTHOR "Hedgehog Fog" + +enum InventorySlot { InventorySlot_Item, InventorySlot_Type[32] }; + +new g_fwInitialized; +new g_fwLoad; +new g_fwLoaded; +new g_fwSave; +new g_fwSaved; +new g_fwSlotCreated; +new g_fwSlotRemoved; +new g_fwSlotLoad; +new g_fwSlotLoaded; +new g_fwSlotSave; +new g_fwSlotSaved; + +new g_hVault; + +new g_rgszPlayerAuthId[MAX_PLAYERS + 1][32]; +new Array:g_irgPlayerInventories[MAX_PLAYERS + 1] = { Invalid_Array, ... }; + +public plugin_precache() { + g_hVault = OpenVault(); +} + +public plugin_init() { + register_plugin(PLUGIN, VERSION, AUTHOR); + + g_fwInitialized = CreateMultiForward("PlayerInventory_Fw_Initialized", ET_STOP, FP_CELL); + g_fwLoad = CreateMultiForward("PlayerInventory_Fw_Load", ET_STOP, FP_CELL); + g_fwLoaded = CreateMultiForward("PlayerInventory_Fw_Loaded", ET_IGNORE, FP_CELL); + g_fwSave = CreateMultiForward("PlayerInventory_Fw_Save", ET_STOP, FP_CELL); + g_fwSaved = CreateMultiForward("PlayerInventory_Fw_Saved", ET_IGNORE, FP_CELL); + g_fwSlotCreated = CreateMultiForward("PlayerInventory_Fw_SlotCreated", ET_IGNORE, FP_CELL, FP_CELL); + g_fwSlotRemoved = CreateMultiForward("PlayerInventory_Fw_SlotRemoved", ET_IGNORE, FP_CELL, FP_CELL); + g_fwSlotLoad = CreateMultiForward("PlayerInventory_Fw_SlotLoad", ET_STOP, FP_CELL, FP_CELL); + g_fwSlotLoaded = CreateMultiForward("PlayerInventory_Fw_SlotLoaded", ET_IGNORE, FP_CELL, FP_CELL); + g_fwSlotSave = CreateMultiForward("PlayerInventory_Fw_SlotSave", ET_STOP, FP_CELL, FP_CELL); + g_fwSlotSaved = CreateMultiForward("PlayerInventory_Fw_SlotSaved", ET_IGNORE, FP_CELL, FP_CELL); +} + +public plugin_end() { + nvault_close(g_hVault); +} + +public plugin_natives() { + register_library("api_player_inventory"); + + register_native("PlayerInventory_GetItem", "Native_GetItem"); + register_native("PlayerInventory_CheckItemType", "Native_CheckItemType"); + register_native("PlayerInventory_GetItemType", "Native_GetItemType"); + register_native("PlayerInventory_GiveItem", "Native_GiveItem"); + register_native("PlayerInventory_SetItem", "Native_SetItem"); + register_native("PlayerInventory_TakeItem", "Native_TakeItem"); + register_native("PlayerInventory_Size", "Native_Size"); +} + +/*--------------------------------[ Natives ]--------------------------------*/ + +public Struct:Native_GetItem(iPluginId, iArgc) { + new pPlayer = get_param(1); + new iSlot = get_param(2); + + new Struct:sSlot = ArrayGetCell(g_irgPlayerInventories[pPlayer], iSlot); + if (sSlot == Invalid_Struct) { + return Invalid_Struct; + } + + return StructGetCell(sSlot, InventorySlot_Item); +} + +public bool:Native_CheckItemType(iPluginId, iArgc) { + new pPlayer = get_param(1); + new iSlot = get_param(2); + + static szType[32]; + get_string(3, szType, charsmax(szType)); + + new Struct:sSlot = ArrayGetCell(g_irgPlayerInventories[pPlayer], iSlot); + if (sSlot == Invalid_Struct) { + return false; + } + + static szSlotType[32]; + StructGetString(sSlot, InventorySlot_Type, szSlotType, charsmax(szSlotType)); + + return !!equal(szType, szSlotType); +} + +public bool:Native_GetItemType(iPluginId, iArgc) { + new pPlayer = get_param(1); + new iSlot = get_param(2); + + new Struct:sSlot = ArrayGetCell(g_irgPlayerInventories[pPlayer], iSlot); + if (sSlot == Invalid_Struct) { + return false; + } + + static szType[32]; + StructGetString(sSlot, InventorySlot_Type, szType, charsmax(szType)); + + set_string(3, szType, get_param(4)); + + return true; +} + +public Native_GiveItem(iPluginId, iArgc) { + new pPlayer = get_param(1); + + static szType[32]; + get_string(2, szType, charsmax(szType)); + + new Struct:sItem = Struct:get_param(3); + + return @Player_GiveItem(pPlayer, szType, sItem); +} + +public Native_SetItem(iPluginId, iArgc) { + new pPlayer = get_param(1); + new iSlot = get_param(2); + + static szType[32]; + get_string(3, szType, charsmax(szType)); + + new Struct:sItem = Struct:get_param(4); + + @Player_SetItem(pPlayer, iSlot, szType, sItem); +} + +public Native_TakeItem(iPluginId, iArgc) { + new pPlayer = get_param(1); + new iSlot = get_param(2); + + return @Player_TakeItem(pPlayer, iSlot); +} + +public Native_Size(iPluginId, iArgc) { + new pPlayer = get_param(1); + + return ArraySize(g_irgPlayerInventories[pPlayer]); +} + +/*--------------------------------[ Forwards ]--------------------------------*/ + +public client_connect(pPlayer) { + if (g_irgPlayerInventories[pPlayer] != Invalid_Array) { + ArrayDestroy(g_irgPlayerInventories[pPlayer]); + } + + g_irgPlayerInventories[pPlayer] = ArrayCreate(); + + ExecuteForward(g_fwInitialized, _, pPlayer); +} + +public client_authorized(pPlayer) { + get_user_authid(pPlayer, g_rgszPlayerAuthId[pPlayer], charsmax(g_rgszPlayerAuthId[])); + LoadPlayerInventory(pPlayer); +} + +public client_disconnected(pPlayer) { + SavePlayerInventory(pPlayer); +} + +/*--------------------------------[ Player Methods ]--------------------------------*/ + +@Player_GiveItem(this, const szType[], Struct:sItem) { + new Struct:sSlot = @InventorySlot_Create(this, szType, sItem); + + static szSavedType[32]; + StructGetString(sSlot, InventorySlot_Type, szSavedType, charsmax(szSavedType)); + + new iSlot = ArrayPushCell(g_irgPlayerInventories[this], sSlot); + ExecuteForward(g_fwSlotCreated, _, this, iSlot); + + return iSlot; +} + +@Player_SetItem(this, iSlot, const szType[], Struct:sItem) { + new Struct:sSlot = ArrayGetCell(g_irgPlayerInventories[this], iSlot); + if (sSlot == Invalid_Struct) { + sSlot = @InventorySlot_Create(this, szType, sItem); + } else { + StructSetString(sSlot, InventorySlot_Type, szType); + StructSetCell(sSlot, InventorySlot_Item, sItem); + } +} + +@Player_TakeItem(this, iSlot) { + new Struct:sSlot = ArrayGetCell(g_irgPlayerInventories[this], iSlot); + if (sSlot == Invalid_Struct) { + return false; + } + + static iResult; + ExecuteForward(g_fwSlotRemoved, iResult, this, iSlot); + + if (iResult != PLUGIN_CONTINUE) { + return false; + } + + @InventorySlot_Destroy(sSlot); + ArraySetCell(g_irgPlayerInventories[this], iSlot, Invalid_Struct); + + return true; +} + +/*--------------------------------[ Inventory Methods ]--------------------------------*/ + +Struct:@InventorySlot_Create(pPlayer, const szType[], Struct:sItem) { + new Struct:this = StructCreate(InventorySlot); + StructSetString(this, InventorySlot_Type, szType); + StructSetCell(this, InventorySlot_Item, sItem); + + return this; +} + +@InventorySlot_Destroy(&Struct:this) { + StructDestroy(this); +} + +/*--------------------------------[ Vault ]--------------------------------*/ + +OpenVault() { + new szVaultDir[256]; + get_datadir(szVaultDir, charsmax(szVaultDir)); + format(szVaultDir, charsmax(szVaultDir), "%s/vault", szVaultDir); + + static szVaultFilePath[256]; + format(szVaultFilePath, charsmax(szVaultFilePath), "%s/%s.vault", szVaultDir, VAULT_NAME); + + new bool:bNew = true; + + if (file_exists(szVaultFilePath)) { + new hVault = nvault_open(VAULT_NAME); + new iVersion = nvault_get(hVault, "_version"); + nvault_close(hVault); + + if (iVersion < VAULT_VERSION) { + log_amx("Invalid vault file. The vault file will be replaced!"); + + static szBacukupVaultFilePath[256]; + format(szBacukupVaultFilePath, charsmax(szBacukupVaultFilePath), "%s/%s.vault.backup%d", szVaultDir, VAULT_NAME, get_systime()); + + rename_file(szVaultFilePath, szBacukupVaultFilePath, 1); + } else { + bNew = false; + } + } + + new hVault = nvault_open(VAULT_NAME); + + if (bNew) { + static szVersion[4]; + num_to_str(VAULT_VERSION, szVersion, charsmax(szVersion)); + nvault_pset(hVault, "_version", szVersion); + } + + return hVault; +} + +LoadPlayerInventory(pPlayer) { + if (g_rgszPlayerAuthId[pPlayer][0] == '^0') { + return; + } + + static iLoadResult; + ExecuteForward(g_fwLoad, iLoadResult, pPlayer); + if (iLoadResult != PLUGIN_CONTINUE) { + return; + } + + ArrayClear(g_irgPlayerInventories[pPlayer]); + + new szKey[32]; + new szValue[1024]; + + format(szKey, charsmax(szKey), "%s_size", g_rgszPlayerAuthId[pPlayer]); + new iInventorySize = nvault_get(g_hVault, szKey); + + //Save items + for (new iSlot = 0; iSlot < iInventorySize; ++iSlot) { + static iSlotLoadResult; + ExecuteForward(g_fwSlotLoad, iSlotLoadResult, pPlayer, iSlot); + if (iSlotLoadResult != PLUGIN_CONTINUE) { + continue; + } + + // item type + format(szKey, charsmax(szKey), "%s_item_%i_type", g_rgszPlayerAuthId[pPlayer], iSlot); + + new szType[32]; + nvault_get(g_hVault, szKey, szType, charsmax(szType)); + + // item struct + format(szKey, charsmax(szKey), "%s_item_%i", g_rgszPlayerAuthId[pPlayer], iSlot); + nvault_get(g_hVault, szKey, szValue, charsmax(szValue)); + new Struct:sItem = StructFromString(sItem, szValue); + + @Player_GiveItem(pPlayer, szType, sItem); + + ExecuteForward(g_fwSlotLoaded, _, pPlayer, iSlot); + } + + ExecuteForward(g_fwLoaded, _, pPlayer); +} + +SavePlayerInventory(pPlayer) { + if (g_rgszPlayerAuthId[pPlayer][0] == '^0') { + return; + } + + new Array:irgInventory = g_irgPlayerInventories[pPlayer]; + + new iInventorySize = ArraySize(irgInventory); + if (!iInventorySize) { + return; + } + + new iSaveResult = 0; + ExecuteForward(g_fwSave, iSaveResult, pPlayer); + if (iSaveResult != PLUGIN_CONTINUE) { + return; + } + + new szKey[32]; + new szValue[1024]; + + //Save items + new iNewInventorySize = 0; + for (new iSlot = 0; iSlot < iInventorySize; ++iSlot) { + new Struct:sSlot = ArrayGetCell(irgInventory, iSlot); + + if (sSlot == Invalid_Struct) { + continue; + } + + static iSlotSaveResult; + ExecuteForward(g_fwSlotSave, iSlotSaveResult, pPlayer, iSlot); + if (iSlotSaveResult != PLUGIN_CONTINUE) { + continue; + } + + new Struct:sItem = StructGetCell(sSlot, InventorySlot_Item); + + // item type + format(szKey, charsmax(szKey), "%s_item_%i_type", g_rgszPlayerAuthId[pPlayer], iNewInventorySize); + StructGetString(sSlot, InventorySlot_Type, szValue, charsmax(szValue)); + nvault_set(g_hVault, szKey, szValue); + + // item struct + format(szKey, charsmax(szKey), "%s_item_%i", g_rgszPlayerAuthId[pPlayer], iNewInventorySize); + StructStringify(sItem, szValue, charsmax(szValue)); + nvault_set(g_hVault, szKey, szValue); + + iNewInventorySize++; + + ExecuteForward(g_fwSlotSaved, _, pPlayer, iSlot); + } + + for (new iSlot = iNewInventorySize; iSlot < iInventorySize; ++iSlot) { + format(szKey, charsmax(szKey), "%s_item_%i", g_rgszPlayerAuthId[pPlayer], iSlot); + nvault_remove(g_hVault, szKey); + + format(szKey, charsmax(szKey), "%s_item_%i_type", g_rgszPlayerAuthId[pPlayer], iSlot); + nvault_remove(g_hVault, szKey); + } + + // save inventory size + format(szKey, charsmax(szKey), "%s_size", g_rgszPlayerAuthId[pPlayer]); + format(szValue, charsmax(szValue), "%i", iNewInventorySize); + + nvault_set(g_hVault, szKey, szValue); + + ExecuteForward(g_fwSaved, _, pPlayer); +} diff --git a/api/player-inventory/include/api_player_inventory.inc b/api/player-inventory/include/api_player_inventory.inc new file mode 100644 index 0000000..d953905 --- /dev/null +++ b/api/player-inventory/include/api_player_inventory.inc @@ -0,0 +1,93 @@ +#if defined _api_player_inventory_included + #endinput +#endif +#define _api_player_inventory_included + +#pragma reqlib api_player_inventory + +/** + * Retrieves an item from a player's inventory. + * + * @param pPlayer The player for which to get the item. + * @param iSlot The slot index of the item to retrieve. + * + * @return Returns a structure containing the structure of the item. + */ +native Struct:PlayerInventory_GetItem(pPlayer, iSlot); + +/** + * Retrieves the type of an item in a player's inventory. + * + * @param pPlayer The player for which to check the item type. + * @param iSlot The slot index of the item. + * @param szType The type of the item is copied into this string. + * @param iLen The maximum length of the string. + * + * @noreturn + */ +native PlayerInventory_GetItemType(pPlayer, iSlot, szType[], iLen); + +/** + * Checks if an item in a player's inventory matches the specified type. + * + * @param pPlayer The player for which to check the item type. + * @param iSlot The slot index of the item. + * @param szType The type of the item to check. + * + * @return Returns 1 if the item matches the type, 0 otherwise. + */ +native PlayerInventory_CheckItemType(pPlayer, iSlot, const szType[]); + +/** + * Gives an item to a player's inventory. + * + * @param pPlayer The player to give the item to. + * @param szType The type of the item to give. + * @param sItem The structure of the item to give. + * + * @noreturn + */ +native PlayerInventory_GiveItem(pPlayer, const szType[], Struct:sItem); + +/** + * Sets the details of an item in a player's inventory. + * + * @param pPlayer The player for which to set the item. + * @param iSlot The slot index of the item to set. + * @param szType The type of the item to set. + * @param sItem The structure of the item to set. + * + * @noreturn + */ +native PlayerInventory_SetItem(pPlayer, iSlot, const szType[], Struct:sItem); + +/** + * Takes an item from a player's inventory. + * + * @param pPlayer The player from which to take the item. + * @param iSlot The slot index of the item to take. + * + * @noreturn + */ +native PlayerInventory_TakeItem(pPlayer, iSlot); + +/** + * Retrieves the number of slots in a player's inventory. + * + * @param pPlayer The player for which to get the inventory size. + * + * @return The number of slots in the player's inventory. + */ +native PlayerInventory_Size(pPlayer); + +forward PlayerInventory_Fw_Initialized(pPlayer); +forward PlayerInventory_Fw_Load(pPlayer); +forward PlayerInventory_Fw_Loaded(pPlayer); +forward PlayerInventory_Fw_Save(pPlayer); +forward PlayerInventory_Fw_Saved(pPlayer); +forward PlayerInventory_Fw_SlotCreated(pPlayer, iSlot); +forward PlayerInventory_Fw_SlotRemoved(pPlayer, iSlot); +forward PlayerInventory_Fw_SlotLoad(pPlayer, iSlot); +forward PlayerInventory_Fw_SlotLoaded(pPlayer, iSlot); +forward PlayerInventory_Fw_SlotSave(pPlayer, iSlot); +forward PlayerInventory_Fw_SlotSaved(pPlayer, iSlot); diff --git a/api/player-model/README.md b/api/player-model/README.md new file mode 100644 index 0000000..62e14ea --- /dev/null +++ b/api/player-model/README.md @@ -0,0 +1,382 @@ + +# Player Model API + +## 🧍Working With Models + +To change player model use `PlayerModel_Set` and `PlayerModel_Update` natives. +`PlayerModel_Set` is using to set player model, calling `PlayerModel_Update` will force update model. +```cpp +PlayerModel_Set(pPlayer, "player/model/vip/vip.mdl"); // set current player model +PlayerModel_Update(pPlayer); // force update player model +``` + +## πŸ•ΊWorking With Animations + +The API supports loading custom animations, including additional weapon animations from separate files. + +### Precaching Animation + +To precache the animation use `PlayerModel_PrecacheAnimation` native, it will precache animation from `animations` directory. This example will precache animation from `cstrike/animations/my-mod/player.mdl`: +```cpp +PlayerModel_PrecacheAnimation("my-mod/player.mdl"); +``` + +### Set Custom Weapon Animation + +Customize weapon animations by set `m_szAnimExtension` member. The following example sets the animation to `ref_aim_myweapon`: +```cpp +static const szCustonWeaponExt[] = "myweapon"; + +set_ent_data_string(pPlayer, "CBasePlayer", "m_szAnimExtention", szCustonWeaponExt); +set_ent_data(pPlayer, "CBaseMonster", "m_Activity", ACT_IDLE); +rg_set_animation(pPlayer, PLAYER_IDLE); +``` + +## πŸ“„ Making Animations File + +### Creating Animation Files + +#### Basic Animations +Fist of all you have to provide basic player sequences like `walk`, `run`, `flitch`, etc. + +
+ Example + + $sequence "dummy" { + "anims/dummy" + fps 24 + loop + } + $sequence "idle1" { + "anims/idle1" + ACT_IDLE 1 + fps 15 + loop + } + $sequence "crouch_idle" { + "anims/crouch_idle" + ACT_CROUCHIDLE 1 + fps 10 + loop + } + $sequence "walk" { + "anims/walk" + ACT_WALK 1 + fps 30 + loop + LX + } + $sequence "run" { + "anims/run" + ACT_RUN 1 + fps 60 + loop + LX + } + $sequence "crouchrun" { + "anims/crouchrun" + ACT_CROUCH 1 + fps 30 + loop + LX + } + $sequence "jump" { + "anims/jump" + ACT_HOP 1 + fps 36 + } + $sequence "longjump" { + "anims/longjump" + ACT_LEAP 1 + fps 36 + } + $sequence "swim" { + "anims/swim" + ACT_SWIM 1 + fps 30 + loop + } + $sequence "treadwater" { "anims/treadwater" + ACT_HOVER 1 + fps 24 + loop + } + $sequence "gut_flinch" { + "anims/gut_flinch_blend01" + "anims/gut_flinch_blend02" + "anims/gut_flinch_blend03" + "anims/gut_flinch_blend04" + "anims/gut_flinch_blend05" + "anims/gut_flinch_blend06" + "anims/gut_flinch_blend07" + "anims/gut_flinch_blend08" + "anims/gut_flinch_blend09" + blend XR -90 90 + fps 30 + } + $sequence "head_flinch" { + "anims/head_flinch_blend01" + "anims/head_flinch_blend02" + "anims/head_flinch_blend03" + "anims/head_flinch_blend04" + "anims/head_flinch_blend05" + "anims/head_flinch_blend06" + "anims/head_flinch_blend07" + "anims/head_flinch_blend08" + "anims/head_flinch_blend09" + blend XR -90 90 + fps 30 + } +
+ + +#### Fake Reference +Ensure your animation model includes at least one polygon. Here's an example SMD file for a fake reference: + +
+ animreference.smd + + version 1 + nodes + 0 "Bip01" -1 + 1 "Bip01 Pelvis" 0 + 2 "Bip01 Spine" 1 + 3 "Bip01 Spine1" 2 + 4 "Bip01 Spine2" 3 + 5 "Bip01 Spine3" 4 + 6 "Bip01 Neck" 5 + 7 "Bip01 Head" 6 + 8 "Bone01" 7 + 9 "Bip01 L Clavicle" 6 + 10 "Bip01 L UpperArm" 9 + 11 "Bip01 L Forearm" 10 + 12 "Bip01 L Hand" 11 + 13 "Bip01 L Finger0" 12 + 14 "Bip01 L Finger01" 13 + 15 "Bip01 L Finger1" 12 + 16 "Bip01 L Finger11" 15 + 17 "-- L knuckle" 15 + 18 "-- L Forearm twist" 11 + 19 "-- L wrist" 11 + 20 "-- L Elbow" 10 + 21 "-- L bicep twist" 10 + 22 "-- L shoulder outside" 9 + 23 "-- L Shoulder inside" 9 + 24 "Bip01 R Clavicle" 6 + 25 "Bip01 R UpperArm" 24 + 26 "Bip01 R Forearm" 25 + 27 "Bip01 R Hand" 26 + 28 "Bip01 R Finger0" 27 + 29 "Bip01 R Finger01" 28 + 30 "Bip01 R Finger1" 27 + 31 "Bip01 R Finger11" 30 + 32 "-- R knuckle" 30 + 33 "-- R wrist" 26 + 34 "-- R forearm twist" 26 + 35 "-- R Elbow" 25 + 36 "-- R bicep twist" 25 + 37 "-- R Shoulder inside" 24 + 38 "-- R shoulder outside" 24 + 39 "-- Neck smooth" 5 + 40 "-- R Butt" 1 + 41 "-- L butt" 1 + 42 "Bip01 L Thigh" 1 + 43 "Bip01 L Calf" 42 + 44 "Bip01 L Foot" 43 + 45 "Bip01 L Toe0" 44 + 46 "-- L ankle" 43 + 47 "-- L Knee" 42 + 48 "Bip01 R Thigh" 1 + 49 "Bip01 R Calf" 48 + 50 "Bip01 R Foot" 49 + 51 "Bip01 R Toe0" 50 + 52 "-- R Ankle" 49 + end + skeleton + time 0 + 0 0.233849 -2.251689 38.192150 0.000000 0.000000 -1.570795 + 1 -2.276935 0.000003 -1.238186 -1.570795 -1.570451 0.000000 + 2 1.797145 0.711796 -0.000002 -0.000004 -0.000001 0.000739 + 3 4.118605 -0.003279 0.000000 0.000000 0.000000 0.000035 + 4 4.118601 -0.003280 0.000000 0.000000 0.000000 0.000049 + 5 4.118600 -0.003280 0.000000 0.000000 0.000000 -0.000009 + 6 4.118531 -0.003538 0.000000 0.000000 0.000000 -0.019437 + 7 4.443601 0.000000 0.000000 0.000000 -0.000001 0.201740 + 8 1.426626 0.072724 0.002913 2.958476 -1.570796 0.000000 + 9 0.000004 0.003534 1.732721 -0.000040 -1.501696 -3.122911 + 10 6.384776 0.000000 0.000001 0.025648 -0.046980 0.004099 + 11 10.242682 0.000000 -0.000002 0.000000 0.000000 -0.008014 + 12 11.375562 0.000000 0.000005 -1.580468 -0.132234 0.009455 + 13 0.728679 0.023429 -1.008292 1.705251 0.347372 0.567022 + 14 2.136497 0.000000 0.000001 0.000000 0.000000 0.287979 + 15 3.115505 -0.886041 -0.021431 -0.000782 0.000152 0.191986 + 16 2.011151 0.000000 0.000000 0.000000 0.000000 0.659566 + 17 1.734173 0.000003 0.000000 0.000000 0.000000 0.330185 + 18 6.000001 0.000000 0.000000 -1.578050 0.000000 0.000000 + 19 11.375562 0.000000 0.000005 -1.575523 -0.074433 0.005312 + 20 10.510129 0.000000 0.000000 0.000000 0.000000 -1.574800 + 21 5.500000 0.000000 -0.000001 -0.011814 0.000000 0.000000 + 22 6.551491 0.000000 -0.000001 0.019708 -0.055281 0.008457 + 23 6.551491 0.000000 -0.000001 0.010051 -0.028020 0.004425 + 24 0.000004 0.003543 -1.732721 -0.000040 1.501696 -3.122992 + 25 6.384777 0.000000 -0.000001 -0.025648 0.046980 0.004099 + 26 10.242681 0.000000 0.000002 0.000000 0.000000 -0.008014 + 27 11.375562 0.000000 -0.000002 1.580468 0.132234 0.009455 + 28 0.728679 0.023429 1.008292 -1.705251 -0.347372 0.567022 + 29 2.136496 -0.000001 0.000000 0.000000 0.000000 0.287979 + 30 3.115505 -0.886040 0.021431 0.000782 -0.000152 0.191986 + 31 2.011151 0.000000 0.000000 0.000000 0.000000 0.659566 + 32 1.734173 -0.000001 0.000000 0.000000 0.000000 0.330185 + 33 11.389366 -0.000819 0.158785 1.575523 0.074433 0.005312 + 34 6.000001 0.000000 0.000003 1.575523 0.000000 0.000000 + 35 10.510130 0.000000 -0.000001 0.000000 0.000000 -1.574800 + 36 5.499999 0.000000 0.000001 0.010051 0.000000 0.000000 + 37 6.551491 0.000000 0.000001 -0.010051 0.028020 0.004424 + 38 6.551491 0.000000 0.000001 -0.010051 0.028020 0.004424 + 39 4.226144 -0.003201 0.000000 0.000000 0.000000 0.000000 + 40 0.000005 -0.000005 -3.713579 -0.006814 3.056983 -0.063787 + 41 -0.000005 0.000005 3.713579 0.005299 -3.071082 -0.053070 + 42 -0.000005 0.000007 3.619081 0.011132 -3.002289 -0.086533 + 43 16.573919 0.000000 0.000000 0.000000 0.000000 -0.162458 + 44 15.128179 0.000000 0.000001 0.000920 -0.139743 0.076637 + 45 5.758665 4.244730 0.000000 0.000000 0.000000 1.570796 + 46 15.708952 0.000001 0.000000 0.001562 -0.077726 0.040998 + 47 17.210194 0.000000 0.000001 0.000000 0.000000 -1.486823 + 48 0.000005 -0.000003 -3.619081 -0.011130 3.002286 -0.086533 + 49 16.573919 0.000000 0.000000 0.000000 0.000000 -0.162458 + 50 15.128179 0.000000 0.000000 -0.000920 0.139743 0.076637 + 51 5.758665 4.244731 0.000000 0.000000 0.000000 1.570796 + 52 15.708952 0.000000 0.000000 -0.001364 0.104219 0.055002 + end + triangles + black.bmp + 0 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 + 0 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 + 0 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 + end +
+ +Use the `$body` command in your QC file to add this reference: + +```cpp +$body "studio" "animreference" +``` + +#### Protecting Bones + +Use a compiler like `DoomMusic's StudioMDL` to protect bones. Add `$protection` for each animation bone before references and sequences in the `.qc`: + +
+ Example + + $protected "Bip01" + $protected "Bip01 Pelvis" + $protected "Bip01 Spine" + $protected "Bip01 Spine1" + $protected "Bip01 Spine2" + $protected "Bip01 Spine3" + $protected "Bip01 Neck" + $protected "Bip01 Head" + $protected "Bone01" + $protected "Bip01 L Clavicle" + $protected "Bip01 L UpperArm" + $protected "Bip01 L Forearm" + $protected "Bip01 L Hand" + $protected "Bip01 L Finger0" + $protected "Bip01 L Finger01" + $protected "Bip01 L Finger1" + $protected "Bip01 L Finger11" + $protected "-- L knuckle" + $protected "-- L Forearm twist" + $protected "-- L wrist" + $protected "-- L Elbow" + $protected "-- L bicep twist" + $protected "-- L shoulder outside" + $protected "-- L Shoulder inside" + $protected "Bip01 R Clavicle" + $protected "Bip01 R UpperArm" + $protected "Bip01 R Forearm" + $protected "Bip01 R Hand" + $protected "Bip01 R Finger0" + $protected "Bip01 R Finger01" + $protected "Bip01 R Finger1" + $protected "Bip01 R Finger11" + $protected "-- R knuckle" + $protected "-- R wrist" + $protected "-- R forearm twist" + $protected "-- R Elbow" + $protected "-- R bicep twist" + $protected "-- R Shoulder inside" + $protected "-- R shoulder outside" + $protected "-- Neck smooth" + $protected "-- R Butt" + $protected "-- L butt" + $protected "Bip01 L Thigh" + $protected "Bip01 L Calf" + $protected "Bip01 L Foot" + $protected "Bip01 L Toe0" + $protected "-- L ankle" + $protected "-- L Knee" + $protected "Bip01 R Thigh" + $protected "Bip01 R Calf" + $protected "Bip01 R Foot" + $protected "Bip01 R Toe0" + $protected "-- R Ankle" +
+ +#### Custom Animation Sequences + +Add your custom weapon animations: + +
+ Example + + $sequence "crouch_aim_myweapon" { + "crouch_aim_myweapon_blend1" + "crouch_aim_myweapon_blend2" + "crouch_aim_myweapon_blend3" + "crouch_aim_myweapon_blend4" + "crouch_aim_myweapon_blend5" + "crouch_aim_myweapon_blend6" + "crouch_aim_myweapon_blend7" + "crouch_aim_myweapon_blend8" + "crouch_aim_myweapon_blend9" + blend XR -90 90 fps 30 loop + } + $sequence "crouch_shoot_myweapon" { + "crouch_shoot_grenade_blend1" + "crouch_shoot_grenade_blend2" + "crouch_shoot_grenade_blend3" + "crouch_shoot_grenade_blend4" + "crouch_shoot_grenade_blend5" + "crouch_shoot_grenade_blend6" + "crouch_shoot_grenade_blend7" + "crouch_shoot_grenade_blend8" + "crouch_shoot_grenade_blend9" + blend XR -90 90 fps 30 + } + $sequence "ref_aim_myweapon" { + "ref_aim_myweapon_blend1" + "ref_aim_myweapon_blend2" + "ref_aim_myweapon_blend3" + "ref_aim_myweapon_blend4" + "ref_aim_myweapon_blend5" + "ref_aim_myweapon_blend6" + "ref_aim_myweapon_blend7" + "ref_aim_myweapon_blend8" + "ref_aim_myweapon_blend9" + blend XR -90 90 fps 30 loop + } + $sequence "ref_shoot_myweapon" { + "ref_shoot_grenade_blend1" + "ref_shoot_grenade_blend2" + "ref_shoot_grenade_blend3" + "ref_shoot_grenade_blend4" + "ref_shoot_grenade_blend5" + "ref_shoot_grenade_blend6" + "ref_shoot_grenade_blend7" + "ref_shoot_grenade_blend8" + "ref_shoot_grenade_blend9" + blend XR -90 90 fps 30 + } +
diff --git a/api/player-model/api_player_model.sma b/api/player-model/api_player_model.sma new file mode 100644 index 0000000..74dacb8 --- /dev/null +++ b/api/player-model/api_player_model.sma @@ -0,0 +1,464 @@ +#pragma semicolon 1 + +#include +#include +#include +#include +#tryinclude + +#define PLUGIN "[API] Player Model" +#define VERSION "1.0.1" +#define AUTHOR "Hedgehog Fog" + +#define NATIVE_ERROR_NOT_CONNECTED(%1) log_error(AMX_ERR_NATIVE, "User %d is not connected", %1) + +#define MAX_SEQUENCES 101 + +new g_iszSubModelClassname; + +new bool:g_bIsCStrike; + +new g_rgszDefaultPlayerModel[MAX_PLAYERS + 1][32]; +new g_rgszCurrentPlayerModel[MAX_PLAYERS + 1][256]; +new g_rgszCustomPlayerModel[MAX_PLAYERS + 1][256]; +new g_rgiPlayerAnimationIndex[MAX_PLAYERS + 1]; +new g_rgpPlayerSubModel[MAX_PLAYERS + 1]; +new bool:g_rgbPlayerUseCustomModel[MAX_PLAYERS + 1]; + +new Trie:g_itPlayerSequenceModelIndexes = Invalid_Trie; +new Trie:g_itPlayerSequences = Invalid_Trie; + +public plugin_precache() { + g_bIsCStrike = !!cstrike_running(); + g_iszSubModelClassname = engfunc(EngFunc_AllocString, "info_target"); + g_itPlayerSequenceModelIndexes = TrieCreate(); + g_itPlayerSequences = TrieCreate(); +} + +public plugin_init() { + register_plugin(PLUGIN, VERSION, AUTHOR); + + register_forward(FM_SetClientKeyValue, "FMHook_SetClientKeyValue"); + + RegisterHamPlayer(Ham_Spawn, "HamHook_Player_Spawn_Post", .Post = 1); + RegisterHamPlayer(Ham_Player_PostThink, "HamHook_Player_PostThink_Post", .Post = 1); + + #if defined _reapi_included + RegisterHookChain(RG_CBasePlayer_SetAnimation, "HC_Player_SetAnimation"); + #endif + + register_message(get_user_msgid("ClCorpse"), "Message_ClCorpse"); +} + +public plugin_natives() { + register_library("api_player_model"); + register_native("PlayerModel_Get", "Native_GetPlayerModel"); + register_native("PlayerModel_GetCurrent", "Native_GetCurrentPlayerModel"); + register_native("PlayerModel_GetEntity", "Native_GetPlayerEntity"); + register_native("PlayerModel_HasCustom", "Native_HasCustomPlayerModel"); + register_native("PlayerModel_Set", "Native_SetPlayerModel"); + register_native("PlayerModel_Reset", "Native_ResetPlayerModel"); + register_native("PlayerModel_Update", "Native_UpdatePlayerModel"); + register_native("PlayerModel_UpdateAnimation", "Native_UpdatePlayerAnimation"); + register_native("PlayerModel_SetSequence", "Native_SetPlayerSequence"); + register_native("PlayerModel_PrecacheAnimation", "Native_PrecacheAnimation"); +} + +public plugin_end() { + TrieDestroy(g_itPlayerSequenceModelIndexes); + TrieDestroy(g_itPlayerSequences); +} + +// ANCHOR: Natives + +public Native_GetPlayerModel(iPluginId, iArgc) { + new pPlayer = get_param(1); + + if (!is_user_connected(pPlayer)) { + NATIVE_ERROR_NOT_CONNECTED(pPlayer); + } + + set_string(2, g_rgszCustomPlayerModel[pPlayer], get_param(3)); +} + +public Native_GetCurrentPlayerModel(iPluginId, iArgc) { + new pPlayer = get_param(1); + + if (!is_user_connected(pPlayer)) { + NATIVE_ERROR_NOT_CONNECTED(pPlayer); + } + + set_string(2, g_rgszCurrentPlayerModel[pPlayer], get_param(3)); +} + +public Native_GetPlayerEntity(iPluginId, iArgc) { + new pPlayer = get_param(1); + + if (!is_user_connected(pPlayer)) { + NATIVE_ERROR_NOT_CONNECTED(pPlayer); + } + + if (g_rgpPlayerSubModel[pPlayer] && @PlayerSubModel_IsActive(g_rgpPlayerSubModel[pPlayer])) { + return g_rgpPlayerSubModel[pPlayer]; + } + + return pPlayer; +} + +public bool:Native_HasCustomPlayerModel(iPluginId, iArgc) { + new pPlayer = get_param(1); + + if (!is_user_connected(pPlayer)) { + NATIVE_ERROR_NOT_CONNECTED(pPlayer); + } + + return g_rgbPlayerUseCustomModel[pPlayer]; +} + +public Native_SetPlayerModel(iPluginId, iArgc) { + new pPlayer = get_param(1); + + if (!is_user_connected(pPlayer)) { + NATIVE_ERROR_NOT_CONNECTED(pPlayer); + } + + get_string(2, g_rgszCustomPlayerModel[pPlayer], charsmax(g_rgszCustomPlayerModel[])); +} + +public Native_ResetPlayerModel(iPluginId, iArgc) { + new pPlayer = get_param(1); + + if (!is_user_connected(pPlayer)) { + NATIVE_ERROR_NOT_CONNECTED(pPlayer); + } + + @Player_ResetModel(pPlayer); +} + +public Native_UpdatePlayerModel(iPluginId, iArgc) { + new pPlayer = get_param(1); + + if (!is_user_connected(pPlayer)) { + NATIVE_ERROR_NOT_CONNECTED(pPlayer); + } + + @Player_UpdateCurrentModel(pPlayer); +} + +public Native_UpdatePlayerAnimation(iPluginId, iArgc) { + new pPlayer = get_param(1); + + if (!is_user_connected(pPlayer)) { + NATIVE_ERROR_NOT_CONNECTED(pPlayer); + } + + @Player_UpdateAnimationModel(pPlayer); +} + +public Native_SetPlayerSequence(iPluginId, iArgc) { + new pPlayer = get_param(1); + + if (!is_user_connected(pPlayer)) { + NATIVE_ERROR_NOT_CONNECTED(pPlayer); + } + + static szSequence[MAX_RESOURCE_PATH_LENGTH]; + get_string(2, szSequence, charsmax(szSequence)); + + return @Player_SetSequence(pPlayer, szSequence); +} + +public Native_PrecacheAnimation(iPluginId, iArgc) { + static szAnimation[MAX_RESOURCE_PATH_LENGTH]; + get_string(1, szAnimation, charsmax(szAnimation)); + return PrecachePlayerAnimation(szAnimation); +} + +// ANCHOR: Hooks and Forwards + +public client_connect(pPlayer) { + copy(g_rgszCustomPlayerModel[pPlayer], charsmax(g_rgszCustomPlayerModel[]), NULL_STRING); + copy(g_rgszDefaultPlayerModel[pPlayer], charsmax(g_rgszDefaultPlayerModel[]), NULL_STRING); + copy(g_rgszCurrentPlayerModel[pPlayer], charsmax(g_rgszCurrentPlayerModel[]), NULL_STRING); + g_rgiPlayerAnimationIndex[pPlayer] = 0; + g_rgbPlayerUseCustomModel[pPlayer] = false; +} + +public client_disconnected(pPlayer) { + if (g_rgpPlayerSubModel[pPlayer]) { + @PlayerSubModel_Destroy(g_rgpPlayerSubModel[pPlayer]); + g_rgpPlayerSubModel[pPlayer] = 0; + } +} + +public FMHook_SetClientKeyValue(pPlayer, const szInfoBuffer[], const szKey[], const szValue[]) { + if (equal(szKey, "model")) { + copy(g_rgszDefaultPlayerModel[pPlayer], charsmax(g_rgszDefaultPlayerModel[]), szValue); + + if (@Player_ShouldUseCurrentModel(pPlayer)) { + return FMRES_SUPERCEDE; + } + + return FMRES_HANDLED; + } + + return FMRES_IGNORED; +} + +public HamHook_Player_Spawn_Post(pPlayer) { + @Player_UpdateCurrentModel(pPlayer); + + return HAM_HANDLED; +} + +public HamHook_Player_PostThink_Post(pPlayer) { + if (g_rgpPlayerSubModel[pPlayer]) { + @PlayerSubModel_Think(g_rgpPlayerSubModel[pPlayer]); + } + + if (@Player_ShouldUseCurrentModel(pPlayer)) { + static szModel[MAX_RESOURCE_PATH_LENGTH]; + get_user_info(pPlayer, "model", szModel, charsmax(szModel)); + + if (!equal(szModel, NULL_STRING)) { + set_user_info(pPlayer, "model", ""); + } + } + + if (!g_bIsCStrike) { + static szModel[MAX_RESOURCE_PATH_LENGTH]; + get_user_info(pPlayer, "model", szModel, charsmax(szModel)); + if (!equal(szModel, NULL_STRING)) { + copy(g_rgszDefaultPlayerModel[pPlayer], charsmax(g_rgszDefaultPlayerModel[]), szModel); + } + + @Player_UpdateModel(pPlayer, false); + } + + return HAM_HANDLED; +} + +#if defined _reapi_included + public HC_Player_SetAnimation(pPlayer) { + @Player_UpdateAnimationModel(pPlayer); + } +#endif + +public Message_ClCorpse(iMsgId, iMsgDest, pPlayer) { + new pTargetPlayer = get_msg_arg_int(12); + if (@Player_ShouldUseCurrentModel(pTargetPlayer)) { + set_msg_arg_string(1, g_rgszCurrentPlayerModel[pTargetPlayer]); + } +} + +// ANCHOR: Methods + +@Player_UpdateAnimationModel(this) { + new iAnimationIndex = 0; + + if (is_user_alive(this)) { + static szAnimExt[32]; get_ent_data_string(this, "CBasePlayer", "m_szAnimExtention", szAnimExt, charsmax(szAnimExt)); + iAnimationIndex = GetAnimationIndexByAnimExt(szAnimExt); + } + + if (iAnimationIndex != g_rgiPlayerAnimationIndex[this]) { + g_rgiPlayerAnimationIndex[this] = iAnimationIndex; + @Player_UpdateModel(this, !iAnimationIndex); + } +} + +@Player_UpdateCurrentModel(this) { + new bool:bUsedCustom = g_rgbPlayerUseCustomModel[this]; + new bool:bSetDefaultModel = false; + new bool:bReset = !!equal(g_rgszCurrentPlayerModel[this], NULL_STRING); + + g_rgbPlayerUseCustomModel[this] = !equal(g_rgszCustomPlayerModel[this], NULL_STRING); + + if (g_rgbPlayerUseCustomModel[this]) { + copy(g_rgszCurrentPlayerModel[this], charsmax(g_rgszCurrentPlayerModel[]), g_rgszCustomPlayerModel[this]); + } else if (!equal(g_rgszDefaultPlayerModel[this], NULL_STRING)) { + format(g_rgszCurrentPlayerModel[this], charsmax(g_rgszCurrentPlayerModel[]), "models/player/%s/%s.mdl", g_rgszDefaultPlayerModel[this], g_rgszDefaultPlayerModel[this]); + bSetDefaultModel = true; + } + + if (!g_bIsCStrike && bSetDefaultModel) { + set_user_info(this, "model", g_rgszDefaultPlayerModel[this]); + } else { + @Player_UpdateModel(this, bReset || bUsedCustom && !g_rgbPlayerUseCustomModel[this]); + } +} + +@Player_UpdateModel(this, bool:bForceUpdate) { + new iSubModelModelIndex = 0; + + if (bForceUpdate || @Player_ShouldUseCurrentModel(this)) { + new iAnimationIndex = g_rgiPlayerAnimationIndex[this]; + new iModelIndex = engfunc(EngFunc_ModelIndex, g_rgszCurrentPlayerModel[this]); + @Player_SetModelIndex(this, iAnimationIndex ? iAnimationIndex : iModelIndex); + iSubModelModelIndex = iAnimationIndex ? iModelIndex : 0; + } + + if (iSubModelModelIndex && !g_rgpPlayerSubModel[this]) { + g_rgpPlayerSubModel[this] = @PlayerSubModel_Create(this); + } + + if (g_rgpPlayerSubModel[this]) { + set_pev(g_rgpPlayerSubModel[this], pev_modelindex, iSubModelModelIndex); + } +} + +bool:@Player_ShouldUseCurrentModel(this) { + return g_rgbPlayerUseCustomModel[this] || g_rgiPlayerAnimationIndex[this]; +} + +@Player_ResetModel(this) { + if (equal(g_rgszDefaultPlayerModel[this], NULL_STRING)) { + return; + } + + copy(g_rgszCustomPlayerModel[this], charsmax(g_rgszCustomPlayerModel[]), NULL_STRING); + copy(g_rgszCurrentPlayerModel[this], charsmax(g_rgszCurrentPlayerModel[]), NULL_STRING); + g_rgiPlayerAnimationIndex[this] = 0; + + @Player_UpdateCurrentModel(this); +} + +@Player_SetModelIndex(this, iModelIndex) { + set_user_info(this, "model", ""); + set_pev(this, pev_modelindex, iModelIndex); + + if (g_bIsCStrike) { + set_ent_data(this, "CBasePlayer", "m_modelIndexPlayer", iModelIndex); + } +} + +@Player_SetSequence(this, const szSequence[]) { + new iAnimationIndex = GetAnimationIndexBySequence(szSequence); + if (!iAnimationIndex) { + return -1; + } + + g_rgiPlayerAnimationIndex[this] = iAnimationIndex; + @Player_UpdateModel(this, false); + + new iSequence = GetSequenceIndex(szSequence); + set_pev(this, pev_sequence, iSequence); + return iSequence; +} + +@PlayerSubModel_Create(pPlayer) { + new this = engfunc(EngFunc_CreateNamedEntity, g_iszSubModelClassname); + set_pev(this, pev_movetype, MOVETYPE_FOLLOW); + set_pev(this, pev_aiment, pPlayer); + set_pev(this, pev_owner, pPlayer); + + return this; +} + +@PlayerSubModel_Destroy(this) { + set_pev(this, pev_modelindex, 0); + set_pev(this, pev_flags, pev(this, pev_flags) | FL_KILLME); + dllfunc(DLLFunc_Think, this); +} + +@PlayerSubModel_Think(this) { + if (!@PlayerSubModel_IsActive(this)) { + set_pev(this, pev_effects, pev(this, pev_effects) | EF_NODRAW); + return; + } + + new pOwner = pev(this, pev_owner); + + set_pev(this, pev_skin, pev(pOwner, pev_skin)); + set_pev(this, pev_body, pev(pOwner, pev_body)); + set_pev(this, pev_colormap, pev(pOwner, pev_colormap)); + set_pev(this, pev_rendermode, pev(pOwner, pev_rendermode)); + set_pev(this, pev_renderfx, pev(pOwner, pev_renderfx)); + set_pev(this, pev_effects, pev(this, pev_effects) & ~EF_NODRAW); + + static Float:flRenderAmt; pev(pOwner, pev_renderamt, flRenderAmt); + set_pev(this, pev_renderamt, flRenderAmt); + + static rgflColor[3]; pev(pOwner, pev_rendercolor, rgflColor); + set_pev(this, pev_rendercolor, rgflColor); +} + +@PlayerSubModel_IsActive(this) { + return (pev(this, pev_modelindex) > 0); +} + +// ANCHOR: Functions + +GetAnimationIndexByAnimExt(const szAnimExt[]) { + if (equal(szAnimExt, NULL_STRING)) return 0; + + static szSequence[32]; format(szSequence, charsmax(szSequence), "ref_aim_%s", szAnimExt); + + return GetAnimationIndexBySequence(szSequence); +} + +GetAnimationIndexBySequence(const szSequence[]) { + static iAnimationIndex; + if (!TrieGetCell(g_itPlayerSequenceModelIndexes, szSequence, iAnimationIndex)) { + return 0; + } + + return iAnimationIndex; +} + +GetSequenceIndex(const szSequence[]) { + static iSequence; + if (!TrieGetCell(g_itPlayerSequences, szSequence, iSequence)) { + return -1; + } + + return iSequence; +} + +// Credis: HamletEagle +PrecachePlayerAnimation(const szAnim[]) { + new szFilePath[MAX_RESOURCE_PATH_LENGTH]; + format(szFilePath, charsmax(szFilePath), "animations/%s", szAnim); + + new iModelIndex = precache_model(szFilePath); + + new iFile = fopen(szFilePath, "rb"); + if (!iFile) { + return 0; + } + + // Got to "numseq" position of the studiohdr_t structure + // https://github.com/dreamstalker/rehlds/blob/65c6ce593b5eabf13e92b03352e4b429d0d797b0/rehlds/public/rehlds/studio.h#L68 + fseek(iFile, 164, SEEK_SET); + + new iSeqNum; + fread(iFile, iSeqNum, BLOCK_INT); + + if (!iSeqNum) { + return 0; + } + + new iSeqIndex; + fread(iFile, iSeqIndex, BLOCK_INT); + fseek(iFile, iSeqIndex, SEEK_SET); + + new szLabel[32]; + for (new i = 0; i < iSeqNum; i++) { + if (i >= MAX_SEQUENCES) { + log_amx("Warning! Sequence limit reached for ^"%s^". Max sequences %d.", szFilePath, MAX_SEQUENCES); + break; + } + + fread_blocks(iFile, szLabel, sizeof(szLabel), BLOCK_CHAR); + TrieSetCell(g_itPlayerSequenceModelIndexes, szLabel, iModelIndex); + TrieSetCell(g_itPlayerSequences, szLabel, i); + + // jump to the end of the studiohdr_t structure + // https://github.com/dreamstalker/rehlds/blob/65c6ce593b5eabf13e92b03352e4b429d0d797b0/rehlds/public/rehlds/studio.h#L95 + fseek(iFile, 176 - sizeof(szLabel), SEEK_CUR); + } + + fclose(iFile); + + return iModelIndex; +} diff --git a/api/player-model/include/api_player_model.inc b/api/player-model/include/api_player_model.inc new file mode 100644 index 0000000..994acd4 --- /dev/null +++ b/api/player-model/include/api_player_model.inc @@ -0,0 +1,17 @@ +#if defined _api_player_model_included + #endinput +#endif +#define _api_player_model_included + +#pragma reqlib api_player_model + +native PlayerModel_Get(pPlayer, szOut[], iLen); +native PlayerModel_GetCurrent(pPlayer, szOut[], iLen); +native PlayerModel_GetEntity(pPlayer); +native bool:PlayerModel_HasCustom(pPlayer); +native PlayerModel_Set(pPlayer, const szModel[]); +native PlayerModel_Update(pPlayer); +native PlayerModel_UpdateAnimation(pPlayer); +native PlayerModel_Reset(pPlayer); +native PlayerModel_SetSequence(pPlayer, const szSequence[]); +native PlayerModel_PrecacheAnimation(const szAnimation[]); diff --git a/api/player-viewrange/api_player_viewrange.sma b/api/player-viewrange/api_player_viewrange.sma new file mode 100644 index 0000000..479a01f --- /dev/null +++ b/api/player-viewrange/api_player_viewrange.sma @@ -0,0 +1,104 @@ +#pragma semicolon 1 + +#include + +#define PLUGIN "[API] Player View Range" +#define VERSION "0.9.0" +#define AUTHOR "Hedgehog Fog" + +new Float:g_rgflPlayerViewRange[MAX_PLAYERS + 1]; +new g_rgiPlayerNativeFogColor[MAX_PLAYERS + 1][3]; +new Float:g_flPlayerNativeFogDensity[MAX_PLAYERS + 1]; + +new gmsgFog; + +public plugin_init() { + register_plugin(PLUGIN, VERSION, AUTHOR); + + gmsgFog = get_user_msgid("Fog"); + + register_message(gmsgFog, "Message_Fog"); +} + +public plugin_natives() { + register_library("api_player_viewrange"); + register_native("PlayerViewRange_Get", "Native_GetPlayerViewRange"); + register_native("PlayerViewRange_Set", "Native_SetPlayerViewRange"); + register_native("PlayerViewRange_Reset", "Native_ResetPlayerViewRange"); + register_native("PlayerViewRange_Update", "Native_UpdatePlayerViewRange"); +} + +public Float:Native_GetPlayerViewRange(iPluginId, iArgc) { + new pPlayer = get_param(1); + + return g_rgflPlayerViewRange[pPlayer]; +} + +public Native_SetPlayerViewRange(iPluginId, iArgc) { + new pPlayer = get_param(1); + new Float:flValue = get_param_f(2); + + @Player_SetViewRange(pPlayer, flValue); +} + +public Native_ResetPlayerViewRange(iPluginId, iArgc) { + new pPlayer = get_param(1); + + @Player_SetViewRange(pPlayer, -1.0); +} + +public Native_UpdatePlayerViewRange(iPluginId, iArgc) { + new pPlayer = get_param(1); + + @Player_UpdateViewRange(pPlayer); +} + +public client_connect(pPlayer) { + g_rgflPlayerViewRange[pPlayer] = 0.0; + g_rgiPlayerNativeFogColor[pPlayer][0] = 0; + g_rgiPlayerNativeFogColor[pPlayer][1] = 0; + g_rgiPlayerNativeFogColor[pPlayer][2] = 0; + g_flPlayerNativeFogDensity[pPlayer] = 0.0; +} + +public Message_Fog(iMsgId, iMsgDest, pPlayer) { + g_rgiPlayerNativeFogColor[pPlayer][0] = get_msg_arg_int(1); + g_rgiPlayerNativeFogColor[pPlayer][1] = get_msg_arg_int(2); + g_rgiPlayerNativeFogColor[pPlayer][2] = get_msg_arg_int(3); + g_flPlayerNativeFogDensity[pPlayer] = Float:( + get_msg_arg_int(4) | + (get_msg_arg_int(5) << 8) | + (get_msg_arg_int(6) << 16) | + (get_msg_arg_int(7) << 24) + ); +} + +public @Player_SetViewRange(this, Float:flViewRange) { + if (g_rgflPlayerViewRange[this] == flViewRange) { + return; + } + + g_rgflPlayerViewRange[this] = flViewRange; + + @Player_UpdateViewRange(this); +} + +public @Player_UpdateViewRange(this) { + if (g_rgflPlayerViewRange[this] >= 0.0) { + new Float:flDensity = g_rgflPlayerViewRange[this] < 0 ? 0.0 : (1.0 / g_rgflPlayerViewRange[this]); + + message_begin(MSG_ONE, gmsgFog, {0, 0, 0}, this); + write_byte(0); + write_byte(0); + write_byte(0); + write_long(_:flDensity); + message_end(); + } else { // reset to engine fog + message_begin(MSG_ONE, gmsgFog, {0, 0, 0}, this); + write_byte(g_rgiPlayerNativeFogColor[this][0]); + write_byte(g_rgiPlayerNativeFogColor[this][1]); + write_byte(g_rgiPlayerNativeFogColor[this][2]); + write_long(_:g_flPlayerNativeFogDensity[this]); + message_end(); + } +} diff --git a/include/api_player_viewrange.inc b/api/player-viewrange/include/api_player_viewrange.inc similarity index 100% rename from include/api_player_viewrange.inc rename to api/player-viewrange/include/api_player_viewrange.inc diff --git a/api/rounds/api_rounds.sma b/api/rounds/api_rounds.sma new file mode 100644 index 0000000..9aedf7e --- /dev/null +++ b/api/rounds/api_rounds.sma @@ -0,0 +1,804 @@ +#pragma semicolon 1 + +#include +#include +#include +#include +#tryinclude + +#define MAX_TEAMS 8 + +enum GameState { + GameState_NewRound, + GameState_RoundStarted, + GameState_RoundEnd +}; + +enum _:Hook { + Hook_PluginId, + Hook_FunctionId +}; + +new GameState:g_iGameState; +new bool:g_bIsCStrike; + +new g_iFwNewRound; +new g_iFwRoundStart; +new g_iFwRoundEnd; +new g_iFwRoundExpired; +new g_iFwRoundRestart; +new g_iFwRoundTimerTick; +new g_iFwUpdateTimer; +new g_iFwCheckWinConditions; + +new g_pCvarRoundEndDelay; + +new g_pCvarRoundTime; +new g_pCvarFreezeTime; +new g_pCvarMaxRounds; +new g_pCvarWinLimits; +new g_pCvarRestartRound; +new g_pCvarRestart; + +new bool:g_bUseCustomRounds = false; +new g_iIntroRoundTime = 2; +new g_iRoundWinTeam = 0; +new g_iRoundTime = 0; +new g_iRoundTimeSecs = 2; +new g_iTotalRoundsPlayed = 0; +new g_iMaxRounds = 0; +new g_iMaxRoundsWon = 0; +new Float:g_flRoundStartTime = 0.0; +new Float:g_flRoundStartTimeReal = 0.0; +new Float:g_flRestartRoundTime = 0.0; +new Float:g_flNextPeriodicThink = 0.0; +new Float:g_flNextThink = 0.0; +new bool:g_bRoundTerminating = false; +new bool:g_bFreezePeriod = true; +new bool:g_bGameStarted = false; +new bool:g_bCompleteReset = false; +new bool:g_bNeededPlayers = false; +new g_iSpawnablePlayersNum = 0; +new g_rgiWinsNum[MAX_TEAMS]; + +public plugin_precache() { + g_bIsCStrike = !!cstrike_running(); +} + +public plugin_init() { + register_plugin("[API] Rounds", "2.1.0", "Hedgehog Fog"); + + if (g_bIsCStrike) { + register_event("HLTV", "Event_NewRound", "a", "1=0", "2=0"); + } + + #if defined _reapi_included + RegisterHookChain(RG_CSGameRules_RestartRound, "HC_RestartRound", .post = 0); + RegisterHookChain(RG_CSGameRules_OnRoundFreezeEnd, "HC_OnRoundFreezeEnd_Post", .post = 1); + RegisterHookChain(RG_RoundEnd, "HC_RoundEnd", .post = 1); + RegisterHookChain(RG_CSGameRules_CheckWinConditions, "HC_CheckWinConditions", .post = 0); + + g_pCvarRoundEndDelay = get_cvar_pointer("mp_round_restart_delay"); + #endif + + g_iFwNewRound = CreateMultiForward("Round_Fw_NewRound", ET_IGNORE); + g_iFwRoundStart = CreateMultiForward("Round_Fw_RoundStart", ET_IGNORE); + g_iFwRoundEnd = CreateMultiForward("Round_Fw_RoundEnd", ET_IGNORE, FP_CELL); + g_iFwRoundExpired = CreateMultiForward("Round_Fw_RoundExpired", ET_IGNORE); + g_iFwRoundRestart = CreateMultiForward("Round_Fw_RoundRestart", ET_IGNORE); + g_iFwRoundTimerTick = CreateMultiForward("Round_Fw_RoundTimerTick", ET_IGNORE); + g_iFwUpdateTimer = CreateMultiForward("Round_Fw_UpdateTimer", ET_IGNORE, FP_CELL); + g_iFwCheckWinConditions = CreateMultiForward("Round_Fw_CheckWinConditions", ET_STOP); +} + +public plugin_natives() { + register_library("api_rounds"); + register_native("Round_UseCustomRounds", "Native_UseCustomRounds"); + register_native("Round_DispatchWin", "Native_DispatchWin"); + register_native("Round_TerminateRound", "Native_TerminateRound"); + register_native("Round_GetTime", "Native_GetTime"); + register_native("Round_SetTime", "Native_SetTime"); + register_native("Round_GetIntroTime", "Native_GetIntroTime"); + register_native("Round_GetStartTime", "Native_GetStartTime"); + register_native("Round_GetRestartRoundTime", "Native_GetRestartRoundTime"); + register_native("Round_GetRemainingTime", "Native_GetRemainingTime"); + register_native("Round_IsFreezePeriod", "Native_IsFreezePeriod"); + register_native("Round_IsRoundStarted", "Native_IsRoundStarted"); + register_native("Round_IsRoundEnd", "Native_IsRoundEnd"); + register_native("Round_IsRoundTerminating", "Native_IsRoundTerminating"); + register_native("Round_IsPlayersNeeded", "Native_IsPlayersNeeded"); + register_native("Round_IsCompleteReset", "Native_IsCompleteReset"); + register_native("Round_CheckWinConditions", "Native_CheckWinConditions"); +} + +public client_putinserver(pPlayer) { + if (!g_bUseCustomRounds) return; + + CheckWinConditions(); +} + +public client_disconnected(pPlayer) { + if (!g_bUseCustomRounds) return; + + CheckWinConditions(); +} + +public HamHook_Player_Spawn_Post(pPlayer) { + if (!g_bUseCustomRounds) return; + if (!is_user_alive(pPlayer)) return; + + if (g_bFreezePeriod) { + set_pev(pPlayer, pev_flags, pev(pPlayer, pev_flags) | FL_FROZEN); + } else { + set_pev(pPlayer, pev_flags, pev(pPlayer, pev_flags) & ~FL_FROZEN); + } +} + +public HamHook_Player_Killed(pPlayer) { + if (!g_bUseCustomRounds) return; + + set_pev(pPlayer, pev_flags, pev(pPlayer, pev_flags) & ~FL_FROZEN); +} + +public HamHook_Player_Killed_Post(pPlayer) { + if (!g_bUseCustomRounds) return; + + CheckWinConditions(); +} + +public server_frame() { + static Float:flGameTime; flGameTime = get_gametime(); + static Float:flNextPeriodicThink; + + if (g_bUseCustomRounds) { + flNextPeriodicThink = g_flNextPeriodicThink; + } else if (g_bIsCStrike) { + #if defined _reapi_included + flNextPeriodicThink = get_member_game(m_tmNextPeriodicThink); + #else + flNextPeriodicThink = get_gamerules_float("CHalfLifeMultiplay", "m_tmNextPeriodicThink"); + #endif + } else { + return; + } + + if (g_bUseCustomRounds) { + if (g_flNextThink <= flGameTime) { + RoundThink(); + g_flNextThink = flGameTime + 0.1; + } + } + + if (flNextPeriodicThink <= flGameTime) { + ExecuteForward(g_iFwRoundTimerTick); + + static iRoundTimeSecs; + static Float:flStartTime; + static bool:bFreezePeriod; + + if (g_bUseCustomRounds) { + iRoundTimeSecs = g_iRoundTimeSecs; + flStartTime = g_flRoundStartTimeReal; + bFreezePeriod = g_bFreezePeriod; + } else if (g_bIsCStrike) { + #if defined _reapi_included + iRoundTimeSecs = get_member_game(m_iRoundTimeSecs); + flStartTime = get_member_game(m_fRoundStartTimeReal); + bFreezePeriod = get_member_game(m_bFreezePeriod); + #else + iRoundTimeSecs = get_gamerules_int("CHalfLifeMultiplay", "m_iRoundTimeSecs"); + flStartTime = get_gamerules_float("CHalfLifeMultiplay", "m_fIntroRoundCount"); + bFreezePeriod = get_gamerules_int("CGameRules", "m_bFreezePeriod"); + #endif + } + + if (!bFreezePeriod && flGameTime >= flStartTime + float(iRoundTimeSecs)) { + ExecuteForward(g_iFwRoundExpired); + } + } +} + +#if defined _reapi_included + public HC_RestartRound() { + if (g_bUseCustomRounds) return; + + ExecuteForward(g_iFwRoundRestart); + } + + public HC_OnRoundFreezeEnd_Post() { + if (g_bUseCustomRounds) return; + + g_iGameState = GameState_RoundStarted; + ExecuteForward(g_iFwRoundStart); + } + + public Event_NewRound() { + if (g_bUseCustomRounds) return; + + g_iGameState = GameState_NewRound; + ExecuteForward(g_iFwNewRound); + } + + public HC_RoundEnd(WinStatus:iStatus, ScenarioEventEndRound:iEvent, Float:flDelay) { + if (g_bUseCustomRounds) return; + + new iTeam; + + switch (iStatus) { + case WINSTATUS_TERRORISTS: iTeam = 1; + case WINSTATUS_CTS: iTeam = 2; + case WINSTATUS_DRAW: iTeam = 3; + } + + g_iGameState = GameState_RoundEnd; + ExecuteForward(g_iFwRoundEnd, _, iTeam); + } + + public HC_CheckWinConditions() { + if (g_bUseCustomRounds) return HC_CONTINUE; + + static iReturn; + + ExecuteForward(g_iFwCheckWinConditions, iReturn); + if (iReturn != PLUGIN_CONTINUE) return HC_SUPERCEDE; + + return HC_CONTINUE; + } +#endif + +StartCustomRounds() { + if (g_bUseCustomRounds) return; + + RegisterHamPlayer(Ham_Spawn, "HamHook_Player_Spawn_Post", .Post = 1); + RegisterHamPlayer(Ham_Killed, "HamHook_Player_Killed", .Post = 0); + RegisterHamPlayer(Ham_Killed, "HamHook_Player_Killed_Post", .Post = 1); + + if (!cvar_exists("mp_roundtime")) register_cvar("mp_roundtime", "5.0"); + if (!cvar_exists("mp_freezetime")) register_cvar("mp_freezetime", "6.0"); + if (!cvar_exists("mp_maxrounds")) register_cvar("mp_maxrounds", "0"); + if (!cvar_exists("mp_winlimit")) register_cvar("mp_winlimit", "0"); + if (!cvar_exists("sv_restart")) register_cvar("sv_restart", "0"); + if (!cvar_exists("sv_restartround")) register_cvar("sv_restartround", "0"); + if (!cvar_exists("mp_round_restart_delay")) register_cvar("mp_round_restart_delay", "5.0"); + + g_pCvarRoundTime = get_cvar_pointer("mp_roundtime"); + g_pCvarFreezeTime = get_cvar_pointer("mp_freezetime"); + g_pCvarMaxRounds = get_cvar_pointer("mp_maxrounds"); + g_pCvarWinLimits = get_cvar_pointer("mp_winlimit"); + g_pCvarRestart = get_cvar_pointer("sv_restart"); + g_pCvarRestartRound = get_cvar_pointer("sv_restartround"); + g_pCvarRoundEndDelay = get_cvar_pointer("mp_round_restart_delay"); + + g_iMaxRounds = max(get_pcvar_num(g_pCvarMaxRounds), 0); + g_iMaxRoundsWon = max(get_pcvar_num(g_pCvarWinLimits), 0); + + ReadMultiplayCvars(); + + g_bUseCustomRounds = true; +} + +public Native_UseCustomRounds(iPluginId, iArgc) { + StartCustomRounds(); +} + +public Native_DispatchWin(iPluginId, iArgc) { + new iTeam = get_param(1); + new Float:flDelay = get_param_f(2); + + DispatchWin(iTeam, flDelay); +} + +public Native_TerminateRound(iPluginId, iArgc) { + new Float:flDelay = get_param_f(1); + new iTeam = get_param(2); + + if (g_bUseCustomRounds) { + TerminateRound(flDelay, iTeam); + } else { + DispatchWin(iTeam, flDelay); + } +} + +public Native_GetTime(iPluginId, iArgc) { + if (g_bUseCustomRounds) { + return g_iRoundTimeSecs; + } else if (g_bIsCStrike) { + #if defined _reapi_included + return get_member_game(m_iRoundTimeSecs); + #else + return get_gamerules_int("CHalfLifeMultiplay", "m_iRoundTimeSecs"); + #endif + } + + return 0; +} + +public Native_SetTime(iPluginId, iArgc) { + new iTime = get_param(1); + + SetTime(iTime); +} + +public Native_GetIntroTime(iPluginId, iArgc) { + if (g_bUseCustomRounds) { + return g_iIntroRoundTime; + } else if (g_bIsCStrike) { + #if defined _reapi_included + return get_member_game(m_iIntroRoundTime); + #else + return get_gamerules_int("CHalfLifeMultiplay", "m_iIntroRoundTime"); + #endif + } + + return 0; +} + +public Float:Native_GetStartTime(iPluginId, iArgc) { + if (g_bUseCustomRounds) { + return g_flRoundStartTime; + } else if (g_bIsCStrike) { + #if defined _reapi_included + return get_member_game(m_fRoundStartTime); + #else + return get_gamerules_float("CHalfLifeMultiplay", "m_fRoundCount"); + #endif + } + + return 0.0; +} + +public Float:Native_GetRestartRoundTime(iPluginId, iArgc) { + if (g_bUseCustomRounds) { + return g_flRestartRoundTime; + } else if (g_bIsCStrike) { + #if defined _reapi_included + return get_member_game(m_flRestartRoundTime); + #else + return get_gamerules_float("CHalfLifeMultiplay", "m_flRestartRoundTime"); + #endif + } + + return 0.0; +} + +public Float:Native_GetRemainingTime(iPluginId, iArgc) { + return GetRoundRemainingTime(); +} + +public bool:Native_IsFreezePeriod(iPluginId, iArgc) { + if (g_bUseCustomRounds) { + return g_bFreezePeriod; + } else if (g_bIsCStrike) { + #if defined _reapi_included + return get_member_game(m_bFreezePeriod); + #else + return get_gamerules_int("CHalfLifeMultiplay", "m_bFreezePeriod"); + #endif + } + + return false; +} + +public bool:Native_IsRoundStarted(iPluginId, iArgc) { + return g_iGameState > GameState_NewRound; +} + +public bool:Native_IsRoundEnd(iPluginId, iArgc) { + return g_iGameState == GameState_RoundEnd; +} + +public bool:Native_IsRoundTerminating(iPluginId, iArgc) { + if (g_bUseCustomRounds) { + return g_bRoundTerminating; + } else if (g_bIsCStrike) { + #if defined _reapi_included + return get_member_game(m_bRoundTerminating); + #else + return get_gamerules_int("CHalfLifeMultiplay", "m_bRoundTerminating"); + #endif + } + + return false; +} + +public bool:Native_IsPlayersNeeded(iPluginId, iArgc) { + if (g_bUseCustomRounds) { + return g_bNeededPlayers; + } else if (g_bIsCStrike) { + #if defined _reapi_included + return get_member_game(m_bNeededPlayers); + #else + return get_gamerules_int("CHalfLifeMultiplay", "m_bNeededPlayers"); + #endif + } + + return false; +} + +public bool:Native_IsCompleteReset(iPluginId, iArgc) { + if (g_bUseCustomRounds) { + return g_bCompleteReset; + } else if (g_bIsCStrike) { + #if defined _reapi_included + return get_member_game(m_bCompleteReset); + #else + return get_gamerules_int("CHalfLifeMultiplay", "m_bCompleteReset"); + #endif + } + + return false; +} + +public bool:Native_CheckWinConditions(iPluginId, iArgc) { + if (g_bUseCustomRounds) { + CheckWinConditions(); + } else { + #if defined _reapi_included + rg_check_win_conditions(); + #endif + } +} + +DispatchWin(iTeam, Float:flDelay = -1.0) { + if (g_iGameState == GameState_RoundEnd) return; + + if (flDelay < 0.0) { + flDelay = g_pCvarRoundEndDelay ? get_pcvar_float(g_pCvarRoundEndDelay) : 5.0; + } + + if (!iTeam) return; + + if (!g_bUseCustomRounds) { + #if defined _reapi_included + if (iTeam > 3) return; + + new WinStatus:iWinstatus = WINSTATUS_DRAW; + if (iTeam == 1) { + iWinstatus = WINSTATUS_TERRORISTS; + } else if (iTeam == 2) { + iWinstatus = WINSTATUS_CTS; + } + + new ScenarioEventEndRound:iEvent = ROUND_END_DRAW; + if (iTeam == 1) { + iEvent = ROUND_TERRORISTS_WIN; + } else if (iTeam == 2) { + iEvent = ROUND_CTS_WIN; + } + + rg_round_end(flDelay, iWinstatus, iEvent, _, _, true); + rg_update_teamscores(iTeam == 2 ? 1 : 0, iTeam == 1 ? 1 : 0); + #endif + } else { + EndRound(flDelay, iTeam); + } +} + +SetTime(iTime) { + if (g_bUseCustomRounds) { + g_iRoundTime = iTime; + g_iRoundTimeSecs = iTime; + g_flRoundStartTime = g_flRoundStartTimeReal; + } else if (g_bIsCStrike) { + #if defined _reapi_included + new Float:flStartTime = get_member_game(m_fRoundStartTimeReal); + set_member_game(m_iRoundTime, iTime); + set_member_game(m_iRoundTimeSecs, iTime); + set_member_game(m_fRoundStartTime, flStartTime); + #else + new Float:flStartTime = get_gamerules_float("CHalfLifeMultiplay", "m_fIntroRoundCount"); + set_gamerules_int("CHalfLifeMultiplay", "m_iRoundTime", iTime); + set_gamerules_int("CHalfLifeMultiplay", "m_iRoundTimeSecs", iTime); + set_gamerules_float("CHalfLifeMultiplay", "m_fRoundCount", flStartTime); + #endif + } + + UpdateTimer(); +} + +UpdateTimer() { + static iRemainingTime; iRemainingTime = floatround(GetRoundRemainingTime(), floatround_floor); + + if (g_bIsCStrike) { + static iMsgId = 0; + if(!iMsgId) iMsgId = get_user_msgid("RoundTime"); + + message_begin(MSG_ALL, iMsgId); + write_short(iRemainingTime); + message_end(); + } + + ExecuteForward(g_iFwUpdateTimer, _, iRemainingTime); +} + +EndRound(const Float:flDelay, iTeam, const szMessage[] = "") { + EndRoundMessage(szMessage); + TerminateRound(flDelay, iTeam); +} + +CheckWinConditions() { + static iReturn; ExecuteForward(g_iFwCheckWinConditions, iReturn); + + if (g_iRoundWinTeam) { + InitializePlayerCounts(); + return; + } + + if (iReturn != PLUGIN_CONTINUE) return; + if (g_bGameStarted && g_iRoundWinTeam) return; + + InitializePlayerCounts(); + + g_bNeededPlayers = false; + + if (NeededPlayersCheck()) return; +} + +RestartRound() { + if (!g_bCompleteReset) { + g_iTotalRoundsPlayed++; + } + + if (g_bCompleteReset) { + g_iTotalRoundsPlayed = 0; + g_iMaxRounds = max(get_pcvar_num(g_pCvarMaxRounds), 0); + g_iMaxRoundsWon = max(get_pcvar_num(g_pCvarWinLimits), 0); + + for (new i = 0; i < sizeof(g_rgiWinsNum); ++i) { + g_rgiWinsNum[i] = 0; + } + } + + ExecuteForward(g_iFwRoundRestart); + + g_bFreezePeriod = true; + g_bRoundTerminating = false; + + ReadMultiplayCvars(); + + g_iRoundTimeSecs = g_iIntroRoundTime; + g_flRoundStartTime = g_flRoundStartTimeReal = get_gametime(); + + CleanUpMap(); + + for (new pPlayer = 1; pPlayer <= MaxClients; ++pPlayer) { + if (!is_user_connected(pPlayer)) continue; + if (pev(pPlayer, pev_flags) == FL_DORMANT) continue; + + PlayerRoundRespawn(pPlayer); + } + + CleanUpMap(); + + g_flRestartRoundTime = 0.0; + g_iRoundWinTeam = 0; + g_bCompleteReset = false; + + g_iGameState = GameState_NewRound; + ExecuteForward(g_iFwNewRound); +} + +RoundThink() { + if (!g_flRoundStartTime) { + g_flRoundStartTime = g_flRoundStartTimeReal = get_gametime(); + } + + if (CheckMaxRounds()) return; + if (CheckWinLimit()) return; + + if (g_bFreezePeriod) { + CheckFreezePeriodExpired(); + } else { + CheckRoundTimeExpired(); + } + + if (g_flRestartRoundTime > 0.0 && g_flRestartRoundTime <= get_gametime()) { + RestartRound(); + } + + if (g_flNextPeriodicThink <= get_gametime()) { + CheckRestartRound(); + + g_iMaxRounds = get_pcvar_num(g_pCvarMaxRounds); + g_iMaxRoundsWon = get_pcvar_num(g_pCvarWinLimits); + g_flNextPeriodicThink = get_gametime() + 1.0; + } +} + +bool:CheckMaxRounds() { + if (g_iMaxRounds && g_iTotalRoundsPlayed >= g_iMaxRounds) { + GoToIntermission(); + return true; + } + + return false; +} + +bool:CheckWinLimit() { + if (g_iMaxRoundsWon) { + new iMaxWins = 0; + for (new i = 0; i < sizeof(g_rgiWinsNum); ++i) { + if (g_rgiWinsNum[i] > iMaxWins) iMaxWins = g_rgiWinsNum[i]; + } + + if (iMaxWins >= g_iMaxRoundsWon) { + GoToIntermission(); + return true; + } + } + + return false; +} + +CheckFreezePeriodExpired() { + if (GetRoundRemainingTime() > 0.0) return; + + log_message("World triggered ^"Round_Start^"\n"); + + g_bFreezePeriod = false; + g_flRoundStartTimeReal = g_flRoundStartTime = get_gametime(); + g_iRoundTimeSecs = g_iRoundTime; + + // for (new pPlayer = 1; pPlayer <= MaxClients; ++pPlayer) { + // if (!is_user_connected(pPlayer)) continue; + // if (pev(pPlayer, pev_flags) == FL_DORMANT) continue; + + // if (get_ent_data(pPlayer, "CBasePlayer", "m_iJoiningState") == JOINED) { + + // } + // } + + for (new pPlayer = 1; pPlayer <= MaxClients; ++pPlayer) { + if (!is_user_connected(pPlayer)) continue; + if (!is_user_alive(pPlayer)) continue; + set_pev(pPlayer, pev_flags, pev(pPlayer, pev_flags) & ~FL_FROZEN); + } + + g_iGameState = GameState_RoundStarted; + ExecuteForward(g_iFwRoundStart); +} + +CheckRoundTimeExpired() { + if (!g_iRoundTime) return; + if (!HasRoundTimeExpired()) return; + + g_flRoundStartTime = get_gametime() + 60.0; +} + +HasRoundTimeExpired() { + if (!g_iRoundTime) return false; + if (GetRoundRemainingTime() > 0 || g_iRoundWinTeam != 0) return false; + + return true; +} + +// CheckLevelInitialized() {} + +RestartRoundCheck(Float:flDelay) { + log_message("World triggered ^"Restart_Round_(%d_%s)^"^n", floatround(flDelay, floatround_floor), (flDelay == 1.0) ? "second" : "seconds"); + + // let the players know + client_print(0, print_center, "The game will restart in %d %s", floatround(flDelay, floatround_floor), (flDelay == 1.0) ? "SECOND" : "SECONDS"); + client_print(0, print_console, "The game will restart in %d %s", floatround(flDelay, floatround_floor), (flDelay == 1.0) ? "SECOND" : "SECONDS"); + + g_flRestartRoundTime = get_gametime() + flDelay; + g_bCompleteReset = true; + + set_pcvar_num(g_pCvarRestartRound, 0); + set_pcvar_num(g_pCvarRestart, 0); +} + +CheckRestartRound() { + new iRestartDelay = get_pcvar_num(g_pCvarRestartRound); + + if (!iRestartDelay) { + iRestartDelay = get_pcvar_num(g_pCvarRestart); + } + + if (iRestartDelay) { + RestartRoundCheck(float(iRestartDelay)); + } +} + +// FPlayerCanRespawn() { +// return true; +// } + +GoToIntermission() { + message_begin(MSG_ALL, SVC_INTERMISSION); + message_end(); +} + +PlayerRoundRespawn(pPlayer) { + #pragma unused pPlayer +} + +CleanUpMap() {} + +ReadMultiplayCvars() { + g_iRoundTime = floatround(get_pcvar_float(g_pCvarRoundTime) * 60, floatround_floor); + g_iIntroRoundTime = floatround(get_pcvar_float(g_pCvarFreezeTime), floatround_floor); +} + +NeededPlayersCheck() { + if (!g_iSpawnablePlayersNum) { + // log_message("#Game_scoring"); + g_bNeededPlayers = true; + g_bGameStarted = false; + } + + if (!g_bGameStarted && g_iSpawnablePlayersNum) { + g_bFreezePeriod = false; + g_bCompleteReset = true; + + EndRoundMessage("Game Commencing!"); + TerminateRound(3.0, 0); + + g_bGameStarted = true; + + return true; + } + + return false; +} + +TerminateRound(Float:flDelay, iTeam) { + g_iRoundWinTeam = iTeam; + g_flRestartRoundTime = get_gametime() + flDelay; + g_bRoundTerminating = true; + g_iGameState = GameState_RoundEnd; + + ExecuteForward(g_iFwRoundEnd, _, iTeam); +} + +EndRoundMessage(const szSentence[]) { + static szMessage[64]; + + if (szSentence[0] == '#') { + copy(szMessage, charsmax(szMessage), szSentence[1]); + } else { + copy(szMessage, charsmax(szMessage), szSentence); + } + + if (!equal(szSentence, NULL_STRING)) { + client_print(0, print_center, szSentence); + log_message("World triggered ^"%s^"^n", szMessage); + } + + log_message("World triggered ^"Round_End^"^n"); +} + +// GetRoundRemainingTimeReal() { +// return float(g_iRoundTimeSecs) - get_gametime() + g_flRoundStartTimeReal; +// } + +InitializePlayerCounts() { + g_iSpawnablePlayersNum = 0; + + for (new pPlayer = 1; pPlayer <= MaxClients; ++pPlayer) { + if (!is_user_connected(pPlayer)) continue; + g_iSpawnablePlayersNum++; + } +} + +Float:GetRoundRemainingTime() { + static Float:flStartTime; + static iTime; + + if (g_bUseCustomRounds) { + flStartTime = g_flRoundStartTimeReal; + iTime = g_iRoundTimeSecs; + } else if (g_bIsCStrike) { + #if defined _reapi_included + flStartTime = get_member_game(m_fRoundStartTimeReal); + iTime = get_member_game(m_iRoundTimeSecs); + #else + flStartTime = get_gamerules_float("CHalfLifeMultiplay", "m_fIntroRoundCount"); + iTime = get_gamerules_int("CHalfLifeMultiplay", "m_iRoundTimeSecs"); + #endif + } else { + return 0.0; + } + + return float(iTime) - get_gametime() + flStartTime; +} diff --git a/api/rounds/include/api_rounds.inc b/api/rounds/include/api_rounds.inc new file mode 100644 index 0000000..a15c186 --- /dev/null +++ b/api/rounds/include/api_rounds.inc @@ -0,0 +1,31 @@ +#if defined _api_rounds_included + #endinput +#endif +#define _api_rounds_included + +#pragma reqlib api_rounds + +native Round_UseCustomRounds(); +native Round_DispatchWin(iTeam, Float:fDelay = -1.0); +native Round_TerminateRound(Float:fDelay = 0.0, iTeam = 0); +native Round_SetTime(iTime); +native Round_GetTime(); +native Round_GetIntroTime(); +native Float:Round_GetStartTime(); +native Float:Round_GetRestartRoundTime(); +native Float:Round_GetRemainingTime(); +native bool:Round_IsFreezePeriod(); +native bool:Round_IsRoundStarted(); +native bool:Round_IsRoundEnd(); +native bool:Round_IsPlayersNeeded(); +native bool:Round_IsCompleteReset(); +native Round_CheckWinConditions(); + +forward Round_Fw_NewRound(); +forward Round_Fw_RoundStart(); +forward Round_Fw_RoundEnd(iTeam); +forward Round_Fw_RoundExpired(); +forward Round_Fw_RoundRestart(); +forward Round_Fw_RoundTimerTick(); +forward Round_Fw_UpdateTimer(iRemainingTime); +forward Round_Fw_CheckWinConditions(); diff --git a/api/states/README.md b/api/states/README.md new file mode 100644 index 0000000..180443f --- /dev/null +++ b/api/states/README.md @@ -0,0 +1,119 @@ +# States API + +The **States API** provides a flexible and efficient way to manage different states. This API allows you to define states, register hooks for transitions, and manage the state lifecycle of the game or entities, such as players or game objects. + +## Key Features: +- **State Contexts**: Group and manage related states under a single context. +- **Hooks**: Register callbacks for state changes, entries, exits, and transitions. +- **State Manager**: Create and control state managers for individual entities. +- **State Transitions**: Schedule and manage timed transitions between states. + +--- + +## Usage Example: Player Health State + +In this example, we'll demonstrate how to use the **States API** to handle the health states of a player in a game. + +### πŸ“ƒ Creating a States Enum + +First, we need to define three state constants: `Healthy`, `Injured`, and `Critical`. + +```pawn +enum HealthState { + HealthState_Healthy, + HealthState_Injured, + HealthState_Critical +}; +``` + +### πŸͺͺ Registering a State Context + +Next, we'll register a context for the player's health states. + +```pawn +#include + +public plugin_precache() { + State_Context_Register("player_health", HealthState_Healthy); +} +``` + +### πŸͺ Registering Hooks + +Now it's time to register hooks to handle entering and exiting different health states. + +```pawn +public plugin_precache() { + // Register the health context + State_Context_Register("player_health", HealthState_Healthy); + + // Register enter hooks for different health states + State_Context_RegisterEnterHook("player_health", HealthState_Healthy, "@State_Healthy_Enter"); + State_Context_RegisterEnterHook("player_health", HealthState_Injured, "@State_Injured_Enter"); + State_Context_RegisterEnterHook("player_health", HealthState_Critical, "@State_Critical_Enter"); + + // Register exit hook for the 'Critical' state + State_Context_RegisterExitHook("player_health", HealthState_Critical, "@State_Critical_Exit"); +} + +@State_Healthy_Enter(const StateManager:this) { + static pPlayer; pPlayer = State_Manager_GetUserToken(this); + client_print(pPlayer, print_center, "You are healthy!"); +} + +@State_Injured_Enter(const StateManager:this) { + static pPlayer; pPlayer = State_Manager_GetUserToken(this); + client_print(pPlayer, print_center, "You are injured. Be careful!"); +} + +@State_Critical_Enter(const StateManager:this) { + static pPlayer; pPlayer = State_Manager_GetUserToken(this); + client_print(pPlayer, print_center, "You are in critical condition! Find a medkit!"); +} + +@State_Critical_Exit(const StateManager:this) { + static pPlayer; pPlayer = State_Manager_GetUserToken(this); + client_print(pPlayer, print_center, "You have recovered from critical condition."); +} +``` + +### πŸ”§ Setting Up the State Manager + +To use the state manager in your game logic, you need to create a manager instance for each player. + +```pawn +new StateManager:g_rgpPlayerStateManagers[MAX_PLAYERS + 1]; + +public client_connect(pPlayer) { + g_rgpPlayerStateManagers[pPlayer] = State_Manager_Create("player_health", pPlayer); +} + +public client_disconnected(pPlayer) { + State_Manager_Destroy(g_rgpPlayerStateManagers[pPlayer]); +} +``` + +### πŸ”„ Managing State Transitions + +Finally, let's manage transitions between these states based on the player's health. + +```pawn +@Player_UpdateState(const this) { + static StateManager:pManager; pManager = g_rgpPlayerStateManagers[this]; + static Float:flHealth; pev(this, pev_health, flHealth); + + if (flHealth < 10.0) { + State_Manager_SetState(pManager, HealthState_Critical); + } else if (flHealth < 50.0) { + State_Manager_SetState(pManager, HealthState_Injured); + } else { + State_Manager_SetState(pManager, HealthState_Healthy); + } +} +``` + +--- + +## Conclusion + +The **States API** simplifies the handling of complex state transitions and ensures your game logic remains clean and maintainable. By following the example above, you can easily integrate this API into your game and manage various states of the game. diff --git a/api/states/api_states.sma b/api/states/api_states.sma new file mode 100644 index 0000000..8b86ad0 --- /dev/null +++ b/api/states/api_states.sma @@ -0,0 +1,493 @@ +#pragma semicolon 1 + +#include +#include +#include + +#include + +enum StateContext { + StateContext_Id, + StateContext_HooksNum, + StateContext_GuardsNum, + StateContext_InitialState, + StateContext_Name[STATE_CONTEXT_MAX_NAME_LEN], + Function:StateContext_Guards[STATE_MAX_CONTEXT_GUARDS], + StateContext_Hooks[STATE_MAX_CONTEXT_HOOKS], +}; + +enum StateManager { + StateManager_ContextId, + bool:StateManager_Free, + any:StateManager_State, + any:StateManager_NextState, + any:StateManager_PrevState, + Float:StateManager_StartChangeTime, + Float:StateManager_ChangeTime, + any:StateManager_UserToken +}; + +enum StateHookType { + StateHookType_Change = 0, + StateHookType_Enter, + StateHookType_Exit, + StateHookType_Transition +}; + +enum StateHook { + StateHookType:StateHook_Type, + any:StateHook_From, + any:StateHook_To, + Function:StateHook_Function +}; + +enum StateChange { + bool:StateChange_Scheduled, + any:StateChange_Value, + Float:StateChange_TransitionTime, + bool:StateChange_Force +}; + +new g_rgStateHooks[STATE_MAX_HOOKS][StateHook]; +new g_iStateHooksNum = 0; + +new Trie:g_itStateContexts = Invalid_Trie; +new g_rgStateContexts[STATE_MAX_CONTEXTS][StateContext]; +new g_iStateContextsNum = 0; + +new g_rgStateManagers[STATE_MAX_MANAGERS][StateManager]; +new g_iStateManagersNum = 0; + +new g_iFreeStateManagersNum = 0; + +// Used to correctly handle state changes during hook calls +new bool:g_bProcessingStateChange = false; +new g_rgScheduledChange[StateChange] = { false, 0, 0.0, false }; + +new bool:g_bDebug = false; + +/*--------------------------------[ Initialization ]--------------------------------*/ + +public plugin_precache() { + g_itStateContexts = TrieCreate(); +} + +public plugin_init() { + register_plugin("[API] States", "1.0.0", "Hedgehog Fog"); + + #if AMXX_VERSION_NUM < 183 + g_bDebug = !!get_cvar_num("developer"); + #else + bind_pcvar_num(get_cvar_pointer("developer"), g_bDebug); + #endif +} + +public plugin_natives() { + register_library("api_states"); + + register_native("State_Context_Register", "Native_RegisterContext"); + register_native("State_Context_RegisterChangeGuard", "Native_RegisterContextChangeGuard"); + register_native("State_Context_RegisterChangeHook", "Native_RegisterContextChangeHook"); + register_native("State_Context_RegisterEnterHook", "Native_RegisterContextEnterHook"); + register_native("State_Context_RegisterExitHook", "Native_RegisterContextExitHook"); + register_native("State_Context_RegisterTransitionHook", "Native_RegisterContextTransitionHook"); + + register_native("State_Manager_Create", "Native_CreateManager"); + register_native("State_Manager_Destroy", "Native_DestroyManager"); + register_native("State_Manager_ResetState", "Native_ResetManagerState"); + register_native("State_Manager_SetState", "Native_SetManagerState"); + register_native("State_Manager_GetState", "Native_GetManagerState"); + register_native("State_Manager_GetPrevState", "Native_GetManagerPrevState"); + register_native("State_Manager_GetNextState", "Native_GetManagerNextState"); + register_native("State_Manager_GetUserToken", "Native_GetManagerUserToken"); + + register_native("State_Manager_IsInTransition", "Native_IsManagerInTransition"); + register_native("State_Manager_EndTransition", "Native_EndManagerTransition"); + register_native("State_Manager_CancelTransition", "Native_CancelManagerTransition"); + register_native("State_Manager_GetTransitionProgress", "Native_GetManagerTransitionProgress"); +} + +/*--------------------------------[ Natives ]--------------------------------*/ + +public Native_RegisterContext(iPluginId, iArgc) { + new szContext[STATE_CONTEXT_MAX_NAME_LEN]; get_string(1, szContext, charsmax(szContext)); + new any:initialState = any:get_param(2); + + return State_RegisterContext(szContext, initialState); +} + +public Native_RegisterContextChangeGuard(iPluginId, iArgc) { + new szContext[STATE_CONTEXT_MAX_NAME_LEN]; get_string(1, szContext, charsmax(szContext)); + new szFunction[STATE_CONTEXT_MAX_NAME_LEN]; get_string(2, szFunction, charsmax(szFunction)); + + return State_RegisterGuard(szContext, get_func_pointer(szFunction, iPluginId)); +} + +public Native_RegisterContextChangeHook(iPluginId, iArgc) { + new szContext[STATE_CONTEXT_MAX_NAME_LEN]; get_string(1, szContext, charsmax(szContext)); + new szFunction[STATE_CONTEXT_MAX_NAME_LEN]; get_string(2, szFunction, charsmax(szFunction)); + + return State_RegisterHook(szContext, StateHookType_Change, _, _, get_func_pointer(szFunction, iPluginId)); +} + +public Native_RegisterContextEnterHook(iPluginId, iArgc) { + new szContext[STATE_CONTEXT_MAX_NAME_LEN]; get_string(1, szContext, charsmax(szContext)); + new any:toState = any:get_param(2); + new szFunction[STATE_CONTEXT_MAX_NAME_LEN]; get_string(3, szFunction, charsmax(szFunction)); + + return State_RegisterHook(szContext, StateHookType_Enter, _, toState, get_func_pointer(szFunction, iPluginId)); +} + +public Native_RegisterContextExitHook(iPluginId, iArgc) { + new szContext[STATE_CONTEXT_MAX_NAME_LEN]; get_string(1, szContext, charsmax(szContext)); + new any:fromState = any:get_param(2); + new szFunction[STATE_CONTEXT_MAX_NAME_LEN]; get_string(3, szFunction, charsmax(szFunction)); + + return State_RegisterHook(szContext, StateHookType_Exit, fromState, _, get_func_pointer(szFunction, iPluginId)); +} + +public Native_RegisterContextTransitionHook(iPluginId, iArgc) { + new szContext[STATE_CONTEXT_MAX_NAME_LEN]; get_string(1, szContext, charsmax(szContext)); + new any:fromState = any:get_param(2); + new any:toState = any:get_param(3); + new szFunction[STATE_CONTEXT_MAX_NAME_LEN]; get_string(4, szFunction, charsmax(szFunction)); + + return State_RegisterHook(szContext, StateHookType_Transition, fromState, toState, get_func_pointer(szFunction, iPluginId)); +} + +public Native_CreateManager(iPluginId, iArgc) { + new szContext[STATE_CONTEXT_MAX_NAME_LEN]; get_string(1, szContext, charsmax(szContext)); + new any:userToken = any:get_param(2); + + return State_Manager_Create(szContext, userToken); +} + +public Native_DestroyManager(iPluginId, iArgc) { + static iManagerId; iManagerId = get_param_byref(1); + + State_Manager_Destroy(iManagerId); +} + +public Native_ResetManagerState(iPluginId, iArgc) { + static iManagerId; iManagerId = get_param_byref(1); + + State_Manager_ResetState(iManagerId); +} + +public Native_SetManagerState(iPluginId, iArgc) { + static iManagerId; iManagerId = get_param_byref(1); + static any:newState; newState = any:get_param(2); + static Float:flTransitionTime; flTransitionTime = get_param_f(3); + static bool:bForce; bForce = bool:get_param(4); + + State_Manager_SetState(iManagerId, newState, flTransitionTime, bForce); +} + +public any:Native_GetManagerState(iPluginId, iArgc) { + static iManagerId; iManagerId = get_param_byref(1); + + return g_rgStateManagers[iManagerId][StateManager_State]; +} + +public any:Native_GetManagerPrevState(iPluginId, iArgc) { + static iManagerId; iManagerId = get_param_byref(1); + + return g_rgStateManagers[iManagerId][StateManager_PrevState]; +} + +public any:Native_GetManagerNextState(iPluginId, iArgc) { + static iManagerId; iManagerId = get_param_byref(1); + + return g_rgStateManagers[iManagerId][StateManager_NextState]; +} + +public any:Native_GetManagerUserToken(iPluginId, iArgc) { + static iManagerId; iManagerId = get_param_byref(1); + + return g_rgStateManagers[iManagerId][StateManager_UserToken]; +} + +public bool:Native_IsManagerInTransition(iPluginId, iArgc) { + static iManagerId; iManagerId = get_param_byref(1); + + return State_Manager_IsInTransition(iManagerId); +} + +public Native_CancelManagerTransition(iPluginId, iArgc) { + static iManagerId; iManagerId = get_param_byref(1); + + State_Manager_CancelTransition(iManagerId); +} + +public Native_EndManagerTransition(iPluginId, iArgc) { + static iManagerId; iManagerId = get_param_byref(1); + + State_Manager_EndTransition(iManagerId); +} + +public Float:Native_GetManagerTransitionProgress(iPluginId, iArgc) { + static iManagerId; iManagerId = get_param_byref(1); + + return State_Manager_GetTransitionProgress(iManagerId); +} + +/*--------------------------------[ Functions ]--------------------------------*/ + +State_RegisterContext(const szContext[], any:initialState) { + new iId = g_iStateContextsNum; + + g_rgStateContexts[iId][StateContext_Id] = iId; + g_rgStateContexts[iId][StateContext_InitialState] = initialState; + copy(g_rgStateContexts[iId][StateContext_Name], charsmax(g_rgStateContexts[][StateContext_Name]), szContext); + + TrieSetCell(g_itStateContexts, szContext, iId); + + g_iStateContextsNum++; + + return iId; +} + +State_RegisterGuard(const szContext[], Function:fnCallback) { + new iContextId; TrieGetCell(g_itStateContexts, szContext, iContextId); + new iContextGuardId = g_rgStateContexts[iContextId][StateContext_GuardsNum]; + + g_rgStateContexts[iContextId][StateContext_Guards][iContextGuardId] = fnCallback; + g_rgStateContexts[iContextId][StateContext_GuardsNum]++; + + return iContextGuardId; +} + +State_RegisterHook(const szContext[], StateHookType:iType, any:fromState = 0, any:toState = 0, Function:fnCallback) { + new iId = g_iStateHooksNum; + + g_rgStateHooks[iId][StateHook_From] = fromState; + g_rgStateHooks[iId][StateHook_To] = toState; + g_rgStateHooks[iId][StateHook_Function] = fnCallback; + g_rgStateHooks[iId][StateHook_Type] = iType; + + new iContextId; TrieGetCell(g_itStateContexts, szContext, iContextId); + new iContextHookId = g_rgStateContexts[iContextId][StateContext_HooksNum]; + + g_rgStateContexts[iContextId][StateContext_Hooks][iContextHookId] = iId; + g_rgStateContexts[iContextId][StateContext_HooksNum]++; + + g_iStateHooksNum++; + + return iId; +} + +State_Manager_Create(const szContext[], any:userToken) { + new iId = State_Manager_AllocateId(); + + new iContextId; TrieGetCell(g_itStateContexts, szContext, iContextId); + + g_rgStateManagers[iId][StateManager_ContextId] = iContextId; + g_rgStateManagers[iId][StateManager_Free] = false; + g_rgStateManagers[iId][StateManager_UserToken] = userToken; + + State_Manager_ResetState(iId); + + g_iStateManagersNum++; + + return iId; +} + +State_Manager_AllocateId() { + if (g_iFreeStateManagersNum) { + for (new iId = 0; iId < g_iStateManagersNum; ++iId) { + if (g_rgStateManagers[iId][StateManager_Free]) { + g_rgStateManagers[iId][StateManager_Free] = false; + g_iFreeStateManagersNum--; + return iId; + } + } + } + + return g_iStateManagersNum < STATE_MAX_MANAGERS ? g_iStateManagersNum : -1; +} + +State_Manager_Destroy(const iManagerId) { + if (iManagerId == g_iStateManagersNum - 1) { + g_iStateManagersNum--; + return; + } + + g_rgStateManagers[iManagerId][StateManager_Free] = true; + g_iFreeStateManagersNum++; +} + +State_Manager_ResetState(const iManagerId) { + static iContextId; iContextId = g_rgStateManagers[iManagerId][StateManager_ContextId]; + + g_rgStateManagers[iManagerId][StateManager_State] = g_rgStateContexts[iContextId][StateContext_InitialState]; + g_rgStateManagers[iManagerId][StateManager_PrevState] = g_rgStateContexts[iContextId][StateContext_InitialState]; + g_rgStateManagers[iManagerId][StateManager_NextState] = g_rgStateContexts[iContextId][StateContext_InitialState]; + g_rgStateManagers[iManagerId][StateManager_StartChangeTime] = 0.0; + g_rgStateManagers[iManagerId][StateManager_ChangeTime] = 0.0; + + remove_task(iManagerId); + + if (g_bDebug) { + engfunc(EngFunc_AlertMessage, at_aiconsole, "Reset state of context ^"%s^". User Token: ^"%d^".^n", g_rgStateContexts[iContextId][StateContext_Name], g_rgStateManagers[iManagerId][StateManager_UserToken]); + } +} + +bool:State_Manager_CanChangeState(const iManagerId, any:fromState, any:toState) { + static iContextId; iContextId = g_rgStateManagers[iManagerId][StateManager_ContextId]; + static iGuardsNum; iGuardsNum = g_rgStateContexts[iContextId][StateContext_GuardsNum]; + + for (new iContextGuardId = 0; iContextGuardId < iGuardsNum; ++iContextGuardId) { + callfunc_begin_p(g_rgStateContexts[iContextId][StateContext_Guards][iContextGuardId]); + callfunc_push_int(iManagerId); + callfunc_push_int(fromState); + callfunc_push_int(toState); + if (callfunc_end() == STATE_GUARD_BLOCK) return false; + } + + return true; +} + +bool:State_Manager_SetState(const iManagerId, any:newState, Float:flTransitionTime, bool:bForce = false) { + if (g_bProcessingStateChange) { + if (g_bDebug && g_rgScheduledChange[StateChange_Scheduled]) { + static iContextId; iContextId = g_rgStateManagers[iManagerId][StateManager_ContextId]; + engfunc(EngFunc_AlertMessage, at_aiconsole, "An override of a scheduled change was detected for the state of context ^"%s^"!^n", g_rgStateContexts[iContextId][StateContext_Name]); + } + + g_rgScheduledChange[StateChange_Value] = newState; + g_rgScheduledChange[StateChange_TransitionTime] = flTransitionTime; + g_rgScheduledChange[StateChange_Force] = bForce; + g_rgScheduledChange[StateChange_Scheduled] = true; + + if (g_bDebug) { + static iContextId; iContextId = g_rgStateManagers[iManagerId][StateManager_ContextId]; + engfunc(EngFunc_AlertMessage, at_aiconsole, "State of context ^"%s^" change scheduled. Change to ^"%d^". User Token: ^"%d^".^n", g_rgStateContexts[iContextId][StateContext_Name], newState, g_rgStateManagers[iManagerId][StateManager_UserToken]); + } + + return true; + } + + static any:currentState; currentState = g_rgStateManagers[iManagerId][StateManager_State]; + if (currentState == newState) return false; + + if (!State_Manager_CanChangeState(iManagerId, currentState, newState)) { + if (g_bDebug) { + static iContextId; iContextId = g_rgStateManagers[iManagerId][StateManager_ContextId]; + engfunc(EngFunc_AlertMessage, at_aiconsole, "State of context ^"%s^" change blocked by guard. Change from ^"%d^" to ^"%d^". User Token: ^"%d^".^n", g_rgStateContexts[iContextId][StateContext_Name], currentState, newState, g_rgStateManagers[iManagerId][StateManager_UserToken]); + } + + return false; + } + + static Float:flGameTime; flGameTime = get_gametime(); + + if (g_rgStateManagers[iManagerId][StateManager_ChangeTime] > flGameTime) { + if (!bForce) return false; + State_Manager_CancelTransition(iManagerId); + } + + g_rgStateManagers[iManagerId][StateManager_NextState] = newState; + g_rgStateManagers[iManagerId][StateManager_StartChangeTime] = flGameTime; + g_rgStateManagers[iManagerId][StateManager_ChangeTime] = flGameTime + flTransitionTime; + + if (flTransitionTime > 0.0) { + set_task(flTransitionTime, "Task_UpdateManagerState", iManagerId); + } else { + g_bProcessingStateChange = true; + State_Manager_Update(iManagerId); + g_bProcessingStateChange = false; + State_Manager_ProcessScheduledChange(iManagerId); + } + + return true; +} + +bool:State_Manager_IsInTransition(const iManagerId) { + return g_rgStateManagers[iManagerId][StateManager_ChangeTime] > get_gametime(); +} + +Float:State_Manager_GetTransitionProgress(const iManagerId) { + static Float:flStartTime; flStartTime = g_rgStateManagers[iManagerId][StateManager_StartChangeTime]; + static Float:flChangeTime; flChangeTime = g_rgStateManagers[iManagerId][StateManager_ChangeTime]; + static Float:flDuration; flDuration = floatmax(flChangeTime - flStartTime, 0.0); + + if (!flDuration) return 1.0; + + static Float:flTimeLeft; flTimeLeft = floatmax(flChangeTime - get_gametime(), 0.0); + + return (1.0 - (flTimeLeft / flDuration)); +} + +State_Manager_EndTransition(const iManagerId) { + remove_task(iManagerId); + g_rgStateManagers[iManagerId][StateManager_ChangeTime] = get_gametime(); + State_Manager_Update(iManagerId); +} + +State_Manager_CancelTransition(const iManagerId) { + remove_task(iManagerId); + g_rgStateManagers[iManagerId][StateManager_NextState] = g_rgStateManagers[iManagerId][StateManager_State]; + g_rgStateManagers[iManagerId][StateManager_ChangeTime] = get_gametime(); +} + +State_Manager_Update(const iManagerId) { + static any:currentState; currentState = g_rgStateManagers[iManagerId][StateManager_State]; + static any:nextState; nextState = g_rgStateManagers[iManagerId][StateManager_NextState]; + + if (currentState == nextState) return; + if (State_Manager_IsInTransition(iManagerId)) return; + + g_rgStateManagers[iManagerId][StateManager_State] = nextState; + g_rgStateManagers[iManagerId][StateManager_PrevState] = currentState; + + static iContextId; iContextId = g_rgStateManagers[iManagerId][StateManager_ContextId]; + static iHooksNum; iHooksNum = g_rgStateContexts[iContextId][StateContext_HooksNum]; + + if (g_bDebug) { + engfunc(EngFunc_AlertMessage, at_aiconsole, "State of context ^"%s^" changed from ^"%d^" to ^"%d^". User Token: ^"%d^".^n", g_rgStateContexts[iContextId][StateContext_Name], currentState, nextState, g_rgStateManagers[iManagerId][StateManager_UserToken]); + } + + for (new iHook = 0; iHook < iHooksNum; ++iHook) { + static iHookId; iHookId = g_rgStateContexts[iContextId][StateContext_Hooks][iHook]; + + switch (g_rgStateHooks[iHookId][StateHook_Type]) { + case StateHookType_Transition: { + if (g_rgStateHooks[iHookId][StateHook_From] != currentState) continue; + if (g_rgStateHooks[iHookId][StateHook_To] != nextState) continue; + } + case StateHookType_Enter: { + if (g_rgStateHooks[iHookId][StateHook_To] != nextState) continue; + } + case StateHookType_Exit: { + if (g_rgStateHooks[iHookId][StateHook_From] != currentState) continue; + } + } + + callfunc_begin_p(g_rgStateHooks[iHookId][StateHook_Function]); + callfunc_push_int(iManagerId); + callfunc_push_int(currentState); + callfunc_push_int(nextState); + callfunc_end(); + } +} + +State_Manager_ProcessScheduledChange(const iManagerId) { + if (!g_rgScheduledChange[StateChange_Scheduled]) return; + + static any:currentState; currentState = g_rgStateManagers[iManagerId][StateManager_State]; + static iContextId; iContextId = g_rgStateManagers[iManagerId][StateManager_ContextId]; + + if (g_bDebug) { + engfunc(EngFunc_AlertMessage, at_aiconsole, "Processing scheduled change of context ^"%s^". Change from ^"%d^" to ^"%d^". Transition Time: %0.3f User Token: ^"%d^".^n", g_rgStateContexts[iContextId][StateContext_Name], currentState, g_rgScheduledChange[StateChange_Value], g_rgScheduledChange[StateChange_TransitionTime], g_rgStateManagers[iManagerId][StateManager_UserToken]); + } + + g_rgScheduledChange[StateChange_Scheduled] = false; + + State_Manager_SetState(iManagerId, g_rgScheduledChange[StateChange_Value], g_rgScheduledChange[StateChange_TransitionTime], g_rgScheduledChange[StateChange_Force]); +} + +public Task_UpdateManagerState(iTaskId) { + static iManagerId; iManagerId = iTaskId; + + State_Manager_Update(iManagerId); +} diff --git a/api/states/include/api_states.inc b/api/states/include/api_states.inc new file mode 100644 index 0000000..a1c3ee3 --- /dev/null +++ b/api/states/include/api_states.inc @@ -0,0 +1,166 @@ +#if defined _api_states_included + #endinput +#endif +#define _api_states_included + +#pragma reqlib api_states + +#include + +/** + * Registers a new state context with an initial state. + * + * @param szContext The name of the state context. + * @param initialState The initial state to set for the context. + * @noreturn + */ +native State_Context_Register(const szContext[], any:initialState = 0); + +/** + * Registers a guard callback to be invoked when any state changes within the context. + * + * @param szContext The name of the state context. + * @param szCallback The name of the callback function to invoke on state change. + * @noreturn + */ +native State_Context_RegisterChangeGuard(const szContext[], const szCallback[]); + +/** + * Registers a callback to be invoked when any state changes within the context. + * + * @param szContext The name of the state context. + * @param szCallback The name of the callback function to invoke on state change. + * @noreturn + */ +native State_Context_RegisterChangeHook(const szContext[], const szCallback[]); + +/** + * Registers a callback to be invoked when entering a specific state within the context. + * + * @param szContext The name of the state context. + * @param toState The state that triggers the callback when entered. + * @param szCallback The name of the callback function to invoke on entering the specified state. + * @noreturn + */ +native State_Context_RegisterEnterHook(const szContext[], any:toState, const szCallback[]); + +/** + * Registers a callback to be invoked when exiting a specific state within the context. + * + * @param szContext The name of the state context. + * @param fromState The state that triggers the callback when exited. + * @param szCallback The name of the callback function to invoke on exiting the specified state. + * @noreturn + */ +native State_Context_RegisterExitHook(const szContext[], any:fromState, const szCallback[]); + +/** + * Registers a callback to be invoked when transitioning from one state to another within the context. + * + * @param szContext The name of the state context. + * @param fromState The state from which the transition begins. + * @param toState The state to which the transition occurs. + * @param szCallback The name of the callback function to invoke during the transition. + * @noreturn + */ +native State_Context_RegisterTransitionHook(const szContext[], any:fromState, any:toState, const szCallback[]); + +/** + * Creates a new state manager for a given context and user token. + * + * @param szContext The name of the state context. + * @param userToken A unique identifier associated with the user or object being managed. + * @return A handle to the created state manager. + */ +native StateManager:State_Manager_Create(const szContext[], any:userToken = 0); + +/** + * Destroys a state manager and releases associated resources. + * + * @param pManager The state manager handle. + * @noreturn + */ +native State_Manager_Destroy(const &StateManager:pManager); + +/** + * Resets the state of the state manager to its initial state. + * + * @param pManager The state manager handle. + * @noreturn + */ +native State_Manager_ResetState(const &StateManager:pManager); + +/** + * Sets a new state for the state manager with optional transition time. + * + * @param pManager The state manager handle. + * @param newState The new state to set. + * @param flTransitionTime The time in seconds for the transition to occur. Default is 0.0. + * @param bForce If true, forces the state transition even if a transition is already in progress. Default is false. + * @noreturn + */ +native bool:State_Manager_SetState(const &StateManager:pManager, any:newState, Float:flTransitionTime = 0.0, bool:bForce = false); + +/** + * Retrieves the current state from the state manager. + * + * @param pManager The state manager handle. + * @return The current state. + */ +native any:State_Manager_GetState(const &StateManager:pManager); + +/** + * Retrieves the previous state from the state manager. + * + * @param pManager The state manager handle. + * @return The previous state. + */ +native any:State_Manager_GetPrevState(const &StateManager:pManager); + +/** + * Retrieves the next state from the state manager. + * + * @param pManager The state manager handle. + * @return The next state. + */ +native any:State_Manager_GetNextState(const &StateManager:pManager); + +/** + * Retrieves the user token associated with the state manager. + * + * @param pManager The state manager handle. + * @return The user token. + */ +native any:State_Manager_GetUserToken(const &StateManager:pManager); + +/** + * Checks if the state manager is currently in a state transition. + * + * @param pManager The state manager handle. + * @return True if a transition is in progress, false otherwise. + */ +native bool:State_Manager_IsInTransition(const &StateManager:pManager); + +/** + * Ends the pending state transition immediately. + * + * @param pManager The state manager handle. + * @noreturn + */ +native bool:State_Manager_EndTransition(const &StateManager:pManager); + +/** + * Cancels the scheduled state transition, if any. + * + * @param pManager The state manager handle. + * @noreturn + */ +native bool:State_Manager_CancelTransition(const &StateManager:pManager); + +/** + * Retrieves the current progress of the state transition. + * + * @param pManager The state manager handle. + * @return The progress of the transition, expressed as a float value between 0.0 and 1.0. + */ +native Float:State_Manager_GetTransitionProgress(const &StateManager:pManager); diff --git a/api/states/include/api_states_const.inc b/api/states/include/api_states_const.inc new file mode 100644 index 0000000..926e12e --- /dev/null +++ b/api/states/include/api_states_const.inc @@ -0,0 +1,16 @@ +#if defined _api_states_const_included + #endinput +#endif +#define _api_states_const_included + +#define STATE_CONTEXT_MAX_NAME_LEN 64 +#define STATE_MAX_CONTEXTS 32 +#define STATE_MAX_CONTEXT_HOOKS 64 +#define STATE_MAX_CONTEXT_GUARDS 64 +#define STATE_MAX_HOOKS (STATE_MAX_CONTEXTS * STATE_MAX_CONTEXT_HOOKS) +#define STATE_MAX_MANAGERS 256 + +#define STATE_GUARD_CONTINUE 0 +#define STATE_GUARD_BLOCK 1 + +enum StateManager { StateManager_Invalid = -1 }; diff --git a/api/waypoint-markers/api_waypoint_markers.sma b/api/waypoint-markers/api_waypoint_markers.sma new file mode 100644 index 0000000..379d316 --- /dev/null +++ b/api/waypoint-markers/api_waypoint_markers.sma @@ -0,0 +1,405 @@ +#pragma semicolon 1 + +#include +#include +#include +#include +#include + +#include + +#define IS_PLAYER(%1) (%1 >= 1 && %1 <= MaxClients) + +#define MARKER_CLASSNAME "_wpmarker" +#define MARKER_UPDATE_RATE 0.01 +#define TRACE_IGNORE_FLAGS (IGNORE_GLASS | IGNORE_MONSTERS) +#define SCREEN_SIZE_FACTOR 1024.0 +#define SPRITE_MIN_SCALE 0.004 + +enum _:Frame { + Frame_TopLeft, + Frame_TopRight, + Frame_Center, + Frame_BottomLeft, + Frame_BottomRight +}; + +enum MarkerPlayerData { + Float:MarkerPlayerData_Origin[3], + Float:MarkerPlayerData_Angles[3], + Float:MarkerPlayerData_Scale, + Float:MarkerPlayerData_LastUpdate, + Float:MarkerPlayerData_IsVisible, + Float:MarkerPlayerData_ShouldHide, + Float:MarkerPlayerData_NextUpdate +}; + +new g_pCvarCompensation; + +new g_fwCreated; +new g_fwDestroy; + +new g_pTrace; +new g_iszInfoTargetClassname; +new Array:g_irgpMarkers; +new bool:g_bCompensation; + +new Float:g_rgflPlayerDelay[MAX_PLAYERS + 1]; +new Float:g_rgflPlayerNextDelayUpdate[MAX_PLAYERS + 1]; + +public plugin_precache() { + g_pTrace = create_tr2(); + g_irgpMarkers = ArrayCreate(); + g_iszInfoTargetClassname = engfunc(EngFunc_AllocString, "info_target"); + + g_fwCreated = CreateMultiForward("WaypointMarker_Fw_Created", ET_IGNORE, FP_CELL); + g_fwDestroy = CreateMultiForward("WaypointMarker_Fw_Destroy", ET_IGNORE, FP_CELL); +} + +public plugin_init() { + register_plugin("[API] Waypoint Markers", "1.0.0", "Hedgehog Fog"); + + RegisterHamPlayer(Ham_Player_PostThink, "HamHook_Player_PostThink_Post", 1); + + register_forward(FM_AddToFullPack, "FMHook_AddToFullPack", 0); + register_forward(FM_AddToFullPack, "FMHook_AddToFullPack_Post", 1); + register_forward(FM_CheckVisibility, "FMHook_CheckVisibility", 0); + register_forward(FM_OnFreeEntPrivateData, "FMHook_OnFreeEntPrivateData", 0); + + g_pCvarCompensation = create_cvar("waypoint_marker_compensation", "1"); + + bind_pcvar_num(g_pCvarCompensation, g_bCompensation); +} + +public plugin_end() { + free_tr2(g_pTrace); + ArrayDestroy(g_irgpMarkers); +} + +public plugin_natives() { + register_library("api_waypoint_markers"); + register_native("WaypointMarker_Create", "Native_CreateMarker"); + register_native("WaypointMarker_SetVisible", "Native_SetVisible"); +} + +public Native_CreateMarker(iPluginId, iArgc) { + new szModel[MAX_RESOURCE_PATH_LENGTH]; get_string(1, szModel, charsmax(szModel)); + new Float:vecOrigin[3]; get_array_f(2, vecOrigin, sizeof(vecOrigin)); + new Float:flScale = get_param_f(3); + new Float:vecSize[3]; get_array_f(4, vecSize, 2); + + vecSize[2] = floatmax(vecSize[0], vecSize[1]); + + new pMarker = @Marker_Create(); + dllfunc(DLLFunc_Spawn, pMarker); + engfunc(EngFunc_SetModel, pMarker, szModel); + engfunc(EngFunc_SetOrigin, pMarker, vecOrigin); + set_pev(pMarker, pev_scale, flScale); + set_pev(pMarker, pev_size, vecSize); + + return pMarker; +} + +public Native_SetVisible(iPluginId, iArgc) { + new pMarker = get_param(1); + new pPlayer = get_param(2); + new bool:bValue = bool:get_param(3); + + @Marker_SetVisible(pMarker, pPlayer, bValue); +} + +public HamHook_Player_PostThink_Post(pPlayer) { + static Float:flGameTime; flGameTime = get_gametime(); + + if (g_rgflPlayerNextDelayUpdate[pPlayer] <= flGameTime) { + if (g_bCompensation) { + static iPing, iLoss; get_user_ping(pPlayer, iPing, iLoss); + g_rgflPlayerDelay[pPlayer] = float(iPing) / 1000.0; + } else { + g_rgflPlayerDelay[pPlayer] = 0.0; + } + + g_rgflPlayerNextDelayUpdate[pPlayer] = flGameTime + 0.1; + } + + static iMarkersNum; iMarkersNum = ArraySize(g_irgpMarkers); + for (new iMarker = 0; iMarker < iMarkersNum; ++iMarker) { + static pMarker; pMarker = ArrayGetCell(g_irgpMarkers, iMarker); + @Marker_Calculate(pMarker, pPlayer, g_rgflPlayerDelay[pPlayer]); + } +} + +public FMHook_AddToFullPack(es, e, pEntity, pHost, pHostFlags, iPlayer, pSet) { + if (!IS_PLAYER(pHost)) return FMRES_IGNORED; + if (!pev_valid(pEntity)) return FMRES_IGNORED; + + if (@Base_IsMarker(pEntity)) { + if (!is_user_alive(pHost)) return FMRES_SUPERCEDE; + if (is_user_bot(pHost)) return FMRES_SUPERCEDE; + + static Struct:sPlayerData; sPlayerData = @Marker_GetPlayerData(pEntity, pHost); + + if (!StructGetCell(sPlayerData, MarkerPlayerData_IsVisible)) return FMRES_SUPERCEDE; + if (StructGetCell(sPlayerData, MarkerPlayerData_ShouldHide)) return FMRES_SUPERCEDE; + } + + return FMRES_IGNORED; +} + +public FMHook_AddToFullPack_Post(es, e, pEntity, pHost, pHostFlags, iPlayer, pSet) { + if (!IS_PLAYER(pHost)) return FMRES_IGNORED; + if (!pev_valid(pEntity)) return FMRES_IGNORED; + + if (@Base_IsMarker(pEntity)) { + if (!is_user_alive(pHost)) return FMRES_SUPERCEDE; + if (is_user_bot(pHost)) return FMRES_SUPERCEDE; + + static Struct:sPlayerData; sPlayerData = @Marker_GetPlayerData(pEntity, pHost); + + if (!StructGetCell(sPlayerData, MarkerPlayerData_IsVisible)) return FMRES_SUPERCEDE; + if (StructGetCell(sPlayerData, MarkerPlayerData_ShouldHide)) return FMRES_SUPERCEDE; + + static Float:vecOrigin[3]; StructGetArray(sPlayerData, MarkerPlayerData_Origin, vecOrigin, sizeof(vecOrigin)); + static Float:vecAngles[3]; StructGetArray(sPlayerData, MarkerPlayerData_Angles, vecAngles, sizeof(vecAngles)); + static Float:flScale; flScale = StructGetCell(sPlayerData, MarkerPlayerData_Scale); + + set_es(es, ES_Origin, vecOrigin); + set_es(es, ES_Angles, vecAngles); + set_es(es, ES_Scale, flScale); + set_es(es, ES_AimEnt, 0); + set_es(es, ES_MoveType, MOVETYPE_NONE); + } + + return FMRES_HANDLED; +} + +public FMHook_CheckVisibility(pEntity) { + if (!pev_valid(pEntity)) return FMRES_IGNORED; + + if (@Base_IsMarker(pEntity)) { + forward_return(FMV_CELL, 1); + return FMRES_SUPERCEDE; + } + + return FMRES_IGNORED; +} + +public FMHook_OnFreeEntPrivateData(pEntity) { + if (@Base_IsMarker(pEntity)) { + @Marker_Free(pEntity); + } +} + +@Base_IsMarker(this) { + static szClassName[32]; + pev(this, pev_classname, szClassName, charsmax(szClassName)); + + return equal(szClassName, MARKER_CLASSNAME); +} + +@Marker_Create() { + new this = engfunc(EngFunc_CreateNamedEntity, g_iszInfoTargetClassname); + + set_pev(this, pev_classname, MARKER_CLASSNAME); + set_pev(this, pev_scale, 1.0); + set_pev(this, pev_rendermode, kRenderTransAdd); + set_pev(this, pev_renderamt, 255.0); + set_pev(this, pev_movetype, MOVETYPE_NONE); + set_pev(this, pev_solid, SOLID_NOT); + set_pev(this, pev_spawnflags, SF_SPRITE_STARTON); + + new Array:irgsPlayersData = ArrayCreate(1, MaxClients + 1); + for (new pPlayer = 0; pPlayer <= MaxClients; ++pPlayer) { + ArrayPushCell(irgsPlayersData, Invalid_Struct); + } + + set_pev(this, pev_iuser1, irgsPlayersData); + + ArrayPushCell(g_irgpMarkers, this); + + ExecuteForward(g_fwCreated, _, this); + + return this; +} + +@Marker_Free(this) { + ExecuteForward(g_fwDestroy, _, this); + + static Array:irgsPlayersData; irgsPlayersData = Array:pev(this, pev_iuser1); + + for (new pPlayer = 1; pPlayer <= MaxClients; ++pPlayer) { + static Struct:sPlayerData; sPlayerData = ArrayGetCell(irgsPlayersData, pPlayer); + if (sPlayerData == Invalid_Struct) continue; + StructDestroy(sPlayerData); + } + + ArrayDestroy(irgsPlayersData); + + new iGlobalId = ArrayFindValue(g_irgpMarkers, this); + if (iGlobalId != -1) { + ArrayDeleteItem(g_irgpMarkers, iGlobalId); + } +} + +@Marker_SetVisible(this, pPlayer, bool:bValue) { + if (!pPlayer) { + for (new pPlayer = 0; pPlayer <= MaxClients; ++pPlayer) { + @Marker_SetVisible(this, pPlayer, bValue); + } + + return; + } + + if (!IS_PLAYER(pPlayer)) return; + + static Struct:sPlayerData; sPlayerData = @Marker_GetPlayerData(this, pPlayer); + StructSetCell(sPlayerData, MarkerPlayerData_IsVisible, bValue); +} + +@Marker_Calculate(this, pPlayer, Float:flDelay) { + static Float:flGameTime; flGameTime = get_gametime(); + static Struct:sPlayerData; sPlayerData = @Marker_GetPlayerData(this, pPlayer); + + if (!StructGetCell(sPlayerData, MarkerPlayerData_IsVisible)) return; + if (StructGetCell(sPlayerData, MarkerPlayerData_NextUpdate) > flGameTime) return; + + static Float:flLastUpdate; flLastUpdate = StructGetCell(sPlayerData, MarkerPlayerData_LastUpdate); + static Float:flDelta; flDelta = flGameTime - flLastUpdate; + + static Float:vecViewOrigin[3]; ExecuteHam(Ham_EyePosition, pPlayer, vecViewOrigin); + + if (g_bCompensation) { + static Float:vecVelocity[3]; + pev(pPlayer, pev_velocity, vecVelocity); + xs_vec_add_scaled(vecViewOrigin, vecVelocity, flDelay * flDelta, vecViewOrigin); + } + + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + + static iFov; iFov = get_ent_data(pPlayer, "CBasePlayer", "m_iFOV"); + + // We have to fix default HL FOV, because it equal to 0 and we can't use it in calculations + if (!iFov) iFov = 90; + + // Default view cone test functions not working for Half-Life, so we have to use this stock + if (!UTIL_IsInViewCone(pPlayer, vecOrigin, float(iFov) / 2)) { + StructSetCell(sPlayerData, MarkerPlayerData_ShouldHide, true); + return; + } + + static Float:vecAngles[3]; + xs_vec_sub(vecOrigin, vecViewOrigin, vecAngles); + xs_vec_normalize(vecAngles, vecAngles); + vector_to_angle(vecAngles, vecAngles); + vecAngles[0] = -vecAngles[0]; + + static Float:flDistance; flDistance = xs_vec_distance(vecViewOrigin, vecOrigin); + static Float:vecSize[3]; pev(this, pev_size, vecSize); + static Float:vecForward[3]; angle_vector(vecAngles, ANGLEVECTOR_FORWARD, vecForward); + static Float:vecUp[3]; angle_vector(vecAngles, ANGLEVECTOR_UP, vecUp); + static Float:vecRight[3]; angle_vector(vecAngles, ANGLEVECTOR_RIGHT, vecRight); + static Float:flFrameScale; flFrameScale = CalculateDistanceScaleFactor(flDistance, iFov); + + static Float:rgFrame[Frame][3]; + CreateFrame(vecOrigin, vecSize[0] * flFrameScale, vecSize[1] * flFrameScale, vecUp, vecRight, rgFrame); + TraceFrame(vecViewOrigin, rgFrame, pPlayer, rgFrame); + + static Float:flProjectionDistance; flProjectionDistance = xs_vec_distance(rgFrame[Frame_Center], vecViewOrigin); + + static Float:flScale; pev(this, pev_scale, flScale); + flScale *= CalculateDistanceScaleFactor(flProjectionDistance, iFov); + flScale = floatmax(flScale, SPRITE_MIN_SCALE); + + static Float:flDepth; flDepth = floatmin((vecSize[2] / 2) * flScale, flProjectionDistance); + MoveFrame(rgFrame, vecForward, -flDepth, rgFrame); + + StructSetCell(sPlayerData, MarkerPlayerData_ShouldHide, false); + StructSetCell(sPlayerData, MarkerPlayerData_Scale, flScale); + StructSetArray(sPlayerData, MarkerPlayerData_Origin, rgFrame[Frame_Center], sizeof(rgFrame[])); + StructSetArray(sPlayerData, MarkerPlayerData_Angles, vecAngles, sizeof(vecAngles)); + StructSetCell(sPlayerData, MarkerPlayerData_LastUpdate, flGameTime); + StructSetCell(sPlayerData, MarkerPlayerData_NextUpdate, flGameTime + MARKER_UPDATE_RATE); +} + +Struct:@Marker_GetPlayerData(this, pPlayer) { + static Array:irgsPlayersData; irgsPlayersData = Array:pev(this, pev_iuser1); + static Struct:sPlayerData; sPlayerData = ArrayGetCell(irgsPlayersData, pPlayer); + + if (sPlayerData == Invalid_Struct) { + sPlayerData = StructCreate(MarkerPlayerData); + StructSetCell(sPlayerData, MarkerPlayerData_IsVisible, true); + StructSetCell(sPlayerData, MarkerPlayerData_ShouldHide, false); + StructSetCell(sPlayerData, MarkerPlayerData_LastUpdate, get_gametime()); + StructSetCell(sPlayerData, MarkerPlayerData_NextUpdate, get_gametime()); + ArraySetCell(irgsPlayersData, pPlayer, sPlayerData); + } + + return sPlayerData; +} + +CreateFrame(const Float:vecOrigin[3], Float:flWidth, Float:flHeight, const Float:vecUp[3], const Float:vecRight[3], Float:rgFrameOut[Frame][3]) { + static Float:flHalfWidth; flHalfWidth = flWidth / 2; + static Float:flHalfHeight; flHalfHeight = flHeight / 2; + + for (new iAxis = 0; iAxis < 3; ++iAxis) { + rgFrameOut[Frame_TopLeft][iAxis] = vecOrigin[iAxis] + (-vecRight[iAxis] * flHalfWidth) + (vecUp[iAxis] * flHalfHeight); + rgFrameOut[Frame_TopRight][iAxis] = vecOrigin[iAxis] + (vecRight[iAxis] * flHalfWidth) + (vecUp[iAxis] * flHalfHeight); + rgFrameOut[Frame_BottomLeft][iAxis] = vecOrigin[iAxis] + (-vecRight[iAxis] * flHalfWidth) + (-vecUp[iAxis] * flHalfHeight); + rgFrameOut[Frame_BottomRight][iAxis] = vecOrigin[iAxis] + (vecRight[iAxis] * flHalfWidth) + (-vecUp[iAxis] * flHalfHeight); + rgFrameOut[Frame_Center][iAxis] = vecOrigin[iAxis]; + } +} + +Float:TraceFrame(const Float:vecViewOrigin[3], const Float:rgFrame[Frame][3], pIgnore, Float:rgFrameOut[Frame][3]) { + static Float:flMinFraction; flMinFraction = 1.0; + + for (new iFramePoint = 0; iFramePoint < Frame; ++iFramePoint) { + engfunc(EngFunc_TraceLine, vecViewOrigin, rgFrame[iFramePoint], TRACE_IGNORE_FLAGS, pIgnore, g_pTrace); + + static Float:flFraction; get_tr2(g_pTrace, TR_flFraction, flFraction); + if (flFraction < flMinFraction) { + flMinFraction = flFraction; + } + } + + if (flMinFraction < 1.0) { + for (new iFramePoint = 0; iFramePoint < Frame; ++iFramePoint) { + for (new iAxis = 0; iAxis < 3; ++iAxis) { + rgFrameOut[iFramePoint][iAxis] = vecViewOrigin[iAxis] + ((rgFrame[iFramePoint][iAxis] - vecViewOrigin[iAxis]) * flMinFraction); + } + } + } + + return flMinFraction; +} + +MoveFrame(const Float:rgFrame[Frame][3], const Float:vecDirection[3], Float:flDistance, Float:rgFrameOut[Frame][3]) { + for (new iFramePoint = 0; iFramePoint < Frame; ++iFramePoint) { + for (new iAxis = 0; iAxis < 3; ++iAxis) { + rgFrameOut[iFramePoint][iAxis] = rgFrame[iFramePoint][iAxis] + (vecDirection[iAxis] * flDistance); + } + } +} + +Float:CalculateDistanceScaleFactor(Float:flDistance, iFov = 90) { + static Float:flAngle; flAngle = floattan(xs_deg2rad(float(iFov) / 2)); + static Float:flScaleFactor; flScaleFactor = ((2 * flAngle) / SCREEN_SIZE_FACTOR) * flDistance; + + return flScaleFactor; +} + +stock bool:UTIL_IsInViewCone(pEntity, const Float:vecTarget[3], Float:fMaxAngle) { + static Float:vecOrigin[3]; ExecuteHamB(Ham_EyePosition, pEntity, vecOrigin); + + static Float:vecDir[3]; + xs_vec_sub(vecTarget, vecOrigin, vecDir); + xs_vec_normalize(vecDir, vecDir); + + static Float:vecForward[3]; + pev(pEntity, pev_v_angle, vecForward); + angle_vector(vecForward, ANGLEVECTOR_FORWARD, vecForward); + + new Float:flAngle = xs_rad2deg(xs_acos((vecDir[0] * vecForward[0]) + (vecDir[1] * vecForward[1]), radian)); + + return flAngle <= fMaxAngle; +} diff --git a/api/waypoint-markers/include/api_waypoint_markers.inc b/api/waypoint-markers/include/api_waypoint_markers.inc new file mode 100644 index 0000000..655e316 --- /dev/null +++ b/api/waypoint-markers/include/api_waypoint_markers.inc @@ -0,0 +1,17 @@ +/* + Credits: + joaquimandrade +*/ + +#if defined _api_waypoint_markers_included + #endinput +#endif +#define _api_waypoint_markers_included + +#pragma reqlib api_waypoint_markers + +native WaypointMarker_Create(const szModel[], const Float:vecOrigin[3] = {0.0, 0.0, 0.0}, Float:flScale = 1.0, const Float:rgflSize[2] = {64.0, 64.0}); +native WaypointMarker_SetVisible(pMarker, pPlayer, bool:bValue); + +forward WaypointMarker_Fw_Created(pMarker); +forward WaypointMarker_Fw_Destroy(pMarker); diff --git a/api_custom_entities.sma b/api_custom_entities.sma deleted file mode 100644 index cf8286e..0000000 --- a/api_custom_entities.sma +++ /dev/null @@ -1,1048 +0,0 @@ -#pragma semicolon 1 - -#include -#include - -#include -#include -#include - -#include -#include - -#include - -#define PLUGIN "[API] Custom Entities" -#define VERSION "1.2.3" -#define AUTHOR "Hedgehog Fog" - -#define CE_BASE_CLASSNAME "info_target" -#define LOG_PREFIX "[CE]" - -#define TASKID_SUM_DISAPPEAR 1000 -#define TASKID_SUM_RESPAWN 2000 -#define TASKID_SUM_REMOVE 3000 - -/*--------------------------------[ Constants ]--------------------------------*/ - -enum CEPreset -{ - CEPreset_None = 0, - CEPreset_Item, - CEPreset_NPC, - CEPreset_Prop -}; - -enum CEFunction -{ - CEFunction_Spawn, - CEFunction_Kill, - CEFunction_Killed, - CEFunction_Remove, - CEFunction_Picked, - CEFunction_Pickup, - CEFunction_KVD -}; - -enum CEHookData -{ - CEHookData_PluginID, - CEHookData_FuncID -}; - -enum _:RegisterArgs -{ - RegisterArg_Name = 1, - RegisterArg_ModelIndex, - RegisterArg_Mins, - RegisterArg_Maxs, - RegisterArg_LifeTime, - RegisterArg_RespawnTime, - RegisterArg_IgnoreRounds, - RegisterArg_Preset -}; - -enum _:CEData -{ - CEData_Handler, - CEData_TempIndex, - CEData_WorldIndex, - CEData_StartOrigin -}; - -enum _:KVD { - KVD_Key[64], - KVD_Value[64] -} - -/*--------------------------------[ Variables ]--------------------------------*/ - -new g_ptrBaseClassname; - -new Trie:g_entityHandlers = Invalid_Trie; -new Array:g_entityName = Invalid_Array; -new Array:g_entityPluginID = Invalid_Array; -new Array:g_entityModelIndex = Invalid_Array; -new Array:g_entityMins = Invalid_Array; -new Array:g_entityMaxs = Invalid_Array; -new Array:g_entityLifeTime = Invalid_Array; -new Array:g_entityRespawnTime = Invalid_Array; -new Array:g_entityPreset = Invalid_Array; -new Array:g_entityIgnoreRounds = Invalid_Array; -new Array:g_entityHooks = Invalid_Array; - -new g_entityCount = 0; - -new Array:g_worldEntities = Invalid_Array; -new Array:g_tmpEntities = Invalid_Array; - -new g_lastCEIdx = 0; -new g_lastCEEnt = 0; - -new Array:g_ceKvd = Invalid_Array; - -public plugin_init() -{ - register_plugin(PLUGIN, VERSION, AUTHOR); - - SpawnLatestCe(); - - RegisterHam(Ham_Touch, CE_BASE_CLASSNAME, "OnTouch", .Post = 1); - RegisterHam(Ham_Killed, CE_BASE_CLASSNAME, "OnKilled", .Post = 0); - - register_event("HLTV", "OnNewRound", "a", "1=0", "2=0"); - - register_concmd("ce_spawn", "OnClCmd_CESpawn", ADMIN_CVAR); -} - -public plugin_end() -{ - for (new i = 0; i < g_entityCount; ++i) { - DestroyFunctions(i); - } - - if (g_entityCount) { - TrieDestroy(g_entityHandlers); - ArrayDestroy(g_entityName); - ArrayDestroy(g_entityPluginID); - ArrayDestroy(g_entityModelIndex); - ArrayDestroy(g_entityMins); - ArrayDestroy(g_entityMaxs); - ArrayDestroy(g_entityLifeTime); - ArrayDestroy(g_entityRespawnTime); - ArrayDestroy(g_entityIgnoreRounds); - ArrayDestroy(g_entityPreset); - ArrayDestroy(g_entityHooks); - } - - if (g_tmpEntities) { - ArrayDestroy(g_tmpEntities); - } - - if (g_worldEntities) { - ArrayDestroy(g_worldEntities); - } - - if (g_ceKvd != Invalid_Array) { - ArrayDestroy(g_ceKvd); - } -} - -public plugin_precache() -{ - g_ptrBaseClassname = engfunc(EngFunc_AllocString, CE_BASE_CLASSNAME); - - register_forward(FM_KeyValue, "OnKeyValue", 1); - RegisterHam(Ham_Spawn, CE_BASE_CLASSNAME, "OnSpawn", .Post = 1); -} - -public plugin_natives() -{ - register_library("api_custom_entities"); - - register_native("CE_Register", "Native_Register"); - register_native("CE_Create", "Native_Create"); - register_native("CE_Kill", "Native_Kill"); - register_native("CE_Remove", "Native_Remove"); - - register_native("CE_GetSize", "Native_GetSize"); - register_native("CE_GetModelIndex", "Native_GetModelIndex"); - - register_native("CE_RegisterHook", "Native_RegisterHook"); - - register_native("CE_CheckAssociation_", "Native_CheckAssociation"); - - register_native("CE_GetHandler", "Native_GetHandler"); - register_native("CE_GetHandlerByEntity", "Native_GetHandlerByEntity"); -} - -/*--------------------------------[ Natives ]--------------------------------*/ - -public Native_Register(pluginID, argc) -{ - static szClassName[32]; - get_string(RegisterArg_Name, szClassName, charsmax(szClassName)); - - new modelIndex = get_param(RegisterArg_ModelIndex); - new Float:fLifeTime = get_param_f(RegisterArg_LifeTime); - new Float:fRespawnTime = get_param_f(RegisterArg_RespawnTime); - new bool:ignoreRounds = bool:get_param(RegisterArg_IgnoreRounds); - new CEPreset:preset = CEPreset:get_param(RegisterArg_Preset); - - new Float:vMins[3]; - get_array_f(RegisterArg_Mins, vMins, 3); - - new Float:vMaxs[3]; - get_array_f(RegisterArg_Maxs, vMaxs, 3); - - return Register(szClassName, pluginID, modelIndex, vMins, vMaxs, fLifeTime, fRespawnTime, ignoreRounds, preset); -} - -public Native_Create(pluginID, argc) -{ - new szClassName[32]; - get_string(1, szClassName, charsmax(szClassName)); - - new Float:vOrigin[3]; - get_array_f(2, vOrigin, 3); - - new bool:temp = !!get_param(3); - - return Create(szClassName, vOrigin, temp); -} - -public Native_Kill(pluginID, argc) -{ - new ent = get_param(1); - new killer = get_param(2); - - Kill(ent, killer); -} - -public bool:Native_Remove(pluginID, argc) -{ - new ent = get_param(1); - return Remove(ent); -} - -public Native_GetSize(pluginID, argc) -{ - if (!g_entityCount) { - return false; - } - - new szClassName[32]; - get_string(1, szClassName, charsmax(szClassName)); - - new ceIdx; - if (!TrieGetCell(g_entityHandlers, szClassName, ceIdx)) { - return false; - } - - new Float:vMins[3]; - ArrayGetArray(g_entityMins, ceIdx, vMins); - - new Float:vMaxs[3]; - ArrayGetArray(g_entityMaxs, ceIdx, vMaxs); - - set_array_f(2, vMins, 3); - set_array_f(3, vMaxs, 3); - - return true; -} - -public Native_GetModelIndex(pluginID, argc) -{ - if (!g_entityCount) { - return false; - } - - new szClassName[32]; - get_string(1, szClassName, charsmax(szClassName)); - - new ceIdx; - if (!TrieGetCell(g_entityHandlers, szClassName, ceIdx)) { - return false; - } - - return ArrayGetCell(g_entityModelIndex, ceIdx); -} - -public Native_RegisterHook(pluginID, argc) -{ - new CEFunction:function = CEFunction:get_param(1); - - new szClassname[32]; - get_string(2, szClassname, charsmax(szClassname)); - - new szCallback[32]; - get_string(3, szCallback, charsmax(szCallback)); - - RegisterHook(function, szClassname, szCallback, pluginID); -} - -public Native_CheckAssociation(pluginID, argc) -{ - new ent = get_param(1); - new ceIdx = GetHandlerByEntity(ent); - - return (ceIdx > 0 && pluginID == ArrayGetCell(g_entityPluginID, ceIdx)); -} - -public Native_GetHandler(pluginID, argc) -{ - new szClassname[32]; - get_string(1, szClassname, charsmax(szClassname)); - - return GetHandler(szClassname); -} - -public Native_GetHandlerByEntity(pluginID, argc) -{ - new ent = get_param(1); - - return GetHandlerByEntity(ent); -} - -/*--------------------------------[ Hooks ]--------------------------------*/ - -public OnClCmd_CESpawn(id, level, cid) -{ - if(!cmd_access(id, level, cid, 2)) { - return PLUGIN_HANDLED; - } - - static szClassname[128]; - read_args(szClassname, charsmax(szClassname)); - remove_quotes(szClassname); - - if(szClassname[0] == '^0') { - return PLUGIN_HANDLED; - } - - static Float:vOrigin[3]; - pev(id, pev_origin, vOrigin); - - new ent = Create(szClassname, vOrigin); - if (ent) { - dllfunc(DLLFunc_Spawn, ent); - } - - return PLUGIN_HANDLED; -} - -public OnKeyValue(ent, kvd) -{ - if (!g_entityCount) { - return; - } - - static szKey[32]; - get_kvd(kvd, KV_KeyName, szKey, charsmax(szKey)); - - static szValue[32]; - get_kvd(kvd, KV_Value, szValue, charsmax(szValue)); - - if (equal(szKey, "classname")) { - SpawnLatestCe(); - - if (TrieGetCell(g_entityHandlers, szValue, g_lastCEIdx)) { - g_lastCEEnt = Create (szValue, .temp = false); // clone entity - } - } - - if (g_lastCEEnt) { - AddKvd(szKey, szValue); - } -} - -public OnSpawn(ent) -{ - if (!Check(ent)) { - return; - } - - new Array:ceData = GetPData(ent); - new ceIdx = ArrayGetCell(ceData, CEData_Handler); - - //Save start origin - if (ArrayGetCell(ceData, CEData_StartOrigin) == Invalid_Array) { - new Float:vOrigin[3]; - pev(ent, pev_origin, vOrigin); - - new Array:startOrigin = ArrayCreate(3, 1); - ArrayPushArray(startOrigin, vOrigin); - - ArraySetCell(ceData, CEData_StartOrigin, startOrigin); - } - - new tmpIdx = ArrayGetCell(ceData, CEData_TempIndex); - InitEntity(ent, ceIdx, (tmpIdx >= 0)); - - ExecuteFunction(CEFunction_Spawn, ceIdx, ent); -} - -public OnTouch(ent, id) -{ - // if (pev(ent, pev_flags) & ~FL_ONGROUND) { - // return; - // } - - if (!is_user_connected(id)) { - return; - } - - if (!is_user_alive(id)) { - return; - } - - if (!g_entityCount) { - return; - } - - static szClassname[32]; - pev(ent, pev_classname, szClassname, charsmax(szClassname)); - - new ceIdx; - if (!TrieGetCell(g_entityHandlers, szClassname, ceIdx)) { - return; - } - - new CEPreset:preset = ArrayGetCell(g_entityPreset, ceIdx); - if (preset != CEPreset_Item) { - return; - } - - if (ExecuteFunction(CEFunction_Pickup, ceIdx, ent, id) > 0) { - ExecuteFunction(CEFunction_Picked, ceIdx, ent, id); - Kill(ent, id, .picked = true); - } -} - -public OnKilled(ent, killer) -{ - if (!Check(ent)) { - return HAM_IGNORED; - } - - Kill(ent, killer); - return HAM_SUPERCEDE; -} - -public OnNewRound() -{ - Cleanup(); - set_task(0.1, "TaskRespawnEntities"); -} - -/*--------------------------------[ Methods ]--------------------------------*/ - -Register( - const szClassname[], - pluginID, - modelIndex, - const Float:vMins[3], - const Float:vMaxs[3], - Float:fLifeTime, - Float:fRespawnTime, - bool:ignoreRounds, - CEPreset:preset -) -{ - if (!g_entityCount) { - g_entityHandlers = TrieCreate(); - g_entityName = ArrayCreate(32); - g_entityPluginID = ArrayCreate(); - g_entityModelIndex = ArrayCreate(); - g_entityMaxs = ArrayCreate(3); - g_entityMins = ArrayCreate(3); - g_entityLifeTime = ArrayCreate(); - g_entityRespawnTime = ArrayCreate(); - g_entityIgnoreRounds = ArrayCreate(); - g_entityPreset = ArrayCreate(); - g_entityHooks = ArrayCreate(); - } - - new index = g_entityCount; - - TrieSetCell(g_entityHandlers, szClassname, index); - - ArrayPushString(g_entityName, szClassname); - ArrayPushCell(g_entityPluginID, pluginID); - ArrayPushCell(g_entityModelIndex, modelIndex); - ArrayPushArray(g_entityMins, vMins); - ArrayPushArray(g_entityMaxs, vMaxs); - ArrayPushCell(g_entityLifeTime, fLifeTime); - ArrayPushCell(g_entityRespawnTime, fRespawnTime); - ArrayPushCell(g_entityIgnoreRounds, ignoreRounds); - ArrayPushCell(g_entityPreset, preset); - ArrayPushCell(g_entityHooks, 0); - - g_entityCount++; - - log_amx("%s Entity %s successfully registred.", LOG_PREFIX, szClassname); - - return index; -} - -Create(const szClassname[], const Float:vOrigin[3] = {0.0, 0.0, 0.0}, bool:temp = true) -{ - new ceIdx; - if (!g_entityCount || !TrieGetCell(g_entityHandlers, szClassname, ceIdx)) { - log_error(0, "%s Entity %s is not registered as custom entity.", LOG_PREFIX, szClassname); - return -1; - } - - new ent = engfunc(EngFunc_CreateNamedEntity, g_ptrBaseClassname); - set_pev(ent, pev_classname, szClassname, strlen(szClassname)); - - engfunc(EngFunc_SetOrigin, ent, vOrigin); - - new tmpIdx = -1; - new worldIdx = -1; - if (temp) { - if (!g_tmpEntities) { - g_tmpEntities = ArrayCreate(); - } - - tmpIdx = ArraySize(g_tmpEntities); - ArrayPushCell(g_tmpEntities, ent); - } else { - if (!g_worldEntities) { - g_worldEntities = ArrayCreate(); - } - - worldIdx = ArraySize(g_worldEntities); - ArrayPushCell(g_worldEntities, ent); - } - - new Array:ceData = CreatePData(ent); - - ArraySetCell(ceData, CEData_Handler, ceIdx); - ArraySetCell(ceData, CEData_TempIndex, tmpIdx); - ArraySetCell(ceData, CEData_WorldIndex, worldIdx); - ArraySetCell(ceData, CEData_StartOrigin, Invalid_Array); - - set_pev(ent, pev_deadflag, DEAD_NO); - - return ent; -} - -Respawn(ent) -{ - remove_task(ent+TASKID_SUM_RESPAWN); - - new Array:ceData = GetPData(ent); - - new Array:startOrigin = ArrayGetCell(ceData, CEData_StartOrigin); - if (startOrigin != Invalid_Array) { - static Float:vOrigin[3]; - ArrayGetArray(startOrigin, 0, vOrigin); - engfunc(EngFunc_SetOrigin, ent, vOrigin); - } - - set_pev(ent, pev_deadflag, DEAD_NO); - set_pev(ent, pev_effects, pev(ent, pev_effects) & ~EF_NODRAW); - - set_pev(ent, pev_flags, pev(ent, pev_flags) & ~FL_ONGROUND); - dllfunc(DLLFunc_Spawn, ent); -} - -Kill(ent, killer = 0, bool:picked = false) -{ - new Array:ceData = GetPData(ent); - - new ceIdx = ArrayGetCell(ceData, CEData_Handler); - - if (ExecuteFunction(CEFunction_Kill, ceIdx, ent, killer, picked) != PLUGIN_CONTINUE) { - return; - } - - set_pev(ent, pev_takedamage, DAMAGE_NO); - set_pev(ent, pev_effects, pev(ent, pev_effects) | EF_NODRAW); - set_pev(ent, pev_solid, SOLID_NOT); - set_pev(ent, pev_movetype, MOVETYPE_NONE); - set_pev(ent, pev_flags, pev(ent, pev_flags) & ~FL_ONGROUND); - - //Get temp index - new tmpIdx = ArrayGetCell(ceData, CEData_TempIndex); - - //Check if entity is temp - if (tmpIdx < 0) { - new Float:fRespawnTime = ArrayGetCell(g_entityRespawnTime, ceIdx); - if (fRespawnTime > 0.0) { - set_pev(ent, pev_deadflag, DEAD_RESPAWNABLE); - set_task(fRespawnTime, "TaskRespawn", ent+TASKID_SUM_RESPAWN); - } else { - set_pev(ent, pev_deadflag, DEAD_DEAD); - } - } else { - set_pev(ent, pev_deadflag, DEAD_DISCARDBODY); - } - - remove_task(ent+TASKID_SUM_DISAPPEAR); - - ExecuteFunction(CEFunction_Killed, ceIdx, ent, killer, picked); - - if (tmpIdx >= 0) { - set_task(0.0, "TaskRemove", ent+TASKID_SUM_REMOVE); - } -} - -bool:Remove(ent) -{ - if (!Check(ent)) { - log_error(0, "%s Entity %i is not a custom entity.", LOG_PREFIX, ent); - return false; - } - - new Array:ceData = GetPData(ent); - - //Get temp index - new tmpIdx = ArrayGetCell(ceData, CEData_TempIndex); - - //Check if entity is temp - if (tmpIdx >= 0) { - //Remove entity from storage of temp entities - ArraySetCell(g_tmpEntities, tmpIdx, 0); - } else { - //Get world index - new worldIdx = ArrayGetCell(ceData, CEData_WorldIndex); - if (worldIdx >= 0) { - ArraySetCell(g_worldEntities, worldIdx, 0); - } - } - - ClearTasks(ent); - - //Get handler - new ceIdx = ArrayGetCell(ceData, CEData_Handler); - - //Execute remove function - ExecuteFunction(CEFunction_Remove, ceIdx, ent); - - DestroyPData(ent); - - //Remove entity - set_pev(ent, pev_flags, pev(ent, pev_flags) | FL_KILLME); - dllfunc(DLLFunc_Think, ent); - - return true; -} - -Check(ent) -{ - return (pev(ent, pev_gaitsequence) == 'c'+'e'); -} - -ClearTasks(ent) -{ - remove_task(ent+TASKID_SUM_DISAPPEAR); - remove_task(ent+TASKID_SUM_RESPAWN); - remove_task(ent+TASKID_SUM_REMOVE); -} - -GetHandlerByEntity(ent) -{ - if (!Check(ent)) { - return -1; - } - - new Array:ceData = GetPData(ent); - - return ArrayGetCell(ceData, CEData_Handler); -} - -GetHandler(const szClassname[]) -{ - new ceIdx; - if (!g_entityCount || !TrieGetCell(g_entityHandlers, szClassname, ceIdx)) { - return -1; - } - - return ceIdx; -} - -Cleanup() -{ - if (!g_tmpEntities) { - return; - } - - new size = ArraySize(g_tmpEntities); - - for (new i = size - 1; i >= 0; --i) - { - new ent = ArrayGetCell(g_tmpEntities, i); - - if (!ent || !pev_valid(ent)) { - continue; - } - - new ceIdx = GetHandlerByEntity(ent); - if (ceIdx == -1) { - log_error(0, "%s Entity %i is not a custom entity.", LOG_PREFIX, ent); - continue; - } - - new ignoreRounds = ArrayGetCell(g_entityIgnoreRounds, ceIdx); - if (!ignoreRounds) { - Remove(ent); - ArrayDeleteItem(g_tmpEntities, i); - } - } - - // update temp entities refs - new newSize = ArraySize(g_tmpEntities); - for (new i = 0; i < newSize; ++i) { - new ent = ArrayGetCell(g_tmpEntities, i); - - if (!ent || !pev_valid(ent)) { - continue; - } - - new Array:ceData = GetPData(ent); - ArraySetCell(ceData, CEData_TempIndex, i); - } -} - -RespawnEntities() -{ - if (!g_worldEntities) { - return; - } - - new size = ArraySize(g_worldEntities); - for (new i = 0; i < size; ++i) { - new ent = ArrayGetCell(g_worldEntities, i); - if (ent) { - Respawn(ent); - - new szModel[64]; - pev(ent, pev_model, szModel, charsmax(szModel)); - if (szModel[0] == '*') { - engfunc(EngFunc_SetModel, ent, szModel); - } - } - } -} - -bool:InitEntity(ent, ceIdx, bool:temp) -{ - static Float:vMins[3]; - ArrayGetArray(g_entityMins, ceIdx, vMins); - - static Float:vMaxs[3]; - ArrayGetArray(g_entityMaxs, ceIdx, vMaxs); - - static Float:vOrigin[3]; - pev(ent, pev_origin, vOrigin); - - engfunc(EngFunc_SetSize, ent, vMins, vMaxs); - engfunc(EngFunc_SetOrigin, ent, vOrigin); - - { - new preset = ArrayGetCell(g_entityPreset, ceIdx); - ApplyPreset(ent, preset); - } - - new modelIndex = ArrayGetCell(g_entityModelIndex, ceIdx); - if (modelIndex > 0) { - set_pev(ent, pev_modelindex, modelIndex); - } - - if (temp) { - new Float:fLifeTime = ArrayGetCell(g_entityLifeTime, ceIdx); - if (fLifeTime > 0.0) { - set_task(fLifeTime, "TaskDisappear", ent+TASKID_SUM_DISAPPEAR); - } - } - - return true; -} - -ApplyPreset(ent, preset) -{ - switch (preset) - { - case CEPreset_Item: - { - set_pev(ent, pev_solid, SOLID_TRIGGER); - set_pev(ent, pev_movetype, MOVETYPE_TOSS); - set_pev(ent, pev_takedamage, DAMAGE_NO); - } - case CEPreset_NPC: - { - set_pev(ent, pev_solid, SOLID_BBOX); - set_pev(ent, pev_movetype, MOVETYPE_PUSHSTEP); - set_pev(ent, pev_takedamage, DAMAGE_AIM); - - set_pev(ent, pev_controller_0, 125); - set_pev(ent, pev_controller_1, 125); - set_pev(ent, pev_controller_2, 125); - set_pev(ent, pev_controller_3, 125); - - set_pev(ent, pev_gamestate, 1); - set_pev(ent, pev_gravity, 1.0); - set_pev(ent, pev_flags, pev(ent, pev_flags) | FL_MONSTER); - set_pev(ent, pev_fixangle, 1); - set_pev(ent, pev_friction, 0.25); - } - case CEPreset_Prop: - { - set_pev(ent, pev_solid, SOLID_BBOX); - set_pev(ent, pev_movetype, MOVETYPE_FLY); - set_pev(ent, pev_takedamage, DAMAGE_NO); - } - } -} - -AddKvd(const szKey[], const szValue[]) -{ - if (g_ceKvd == Invalid_Array) { - g_ceKvd = ArrayCreate(KVD); - } - - new kvd[KVD]; - copy(kvd[KVD_Key], charsmax(kvd[KVD_Key]), szKey); - copy(kvd[KVD_Value], charsmax(kvd[KVD_Value]), szValue); - ArrayPushArray(g_ceKvd, kvd); -} - -SpawnLatestCe() -{ - if (!g_lastCEEnt) { - return; - } - - if (g_ceKvd != Invalid_Array) { - new size = ArraySize(g_ceKvd); - for (new i = 0; i < size; ++i) { - new kvd[KVD]; - ArrayGetArray(g_ceKvd, i, kvd); - DispatchKeyValue(g_lastCEEnt, kvd[KVD_Key], kvd[KVD_Value]); // dispatch kvd to cloned entity - ExecuteFunction(CEFunction_KVD, g_lastCEIdx, g_lastCEEnt, kvd[KVD_Key], kvd[KVD_Value]); - } - } - - dllfunc(DLLFunc_Spawn, g_lastCEEnt); // spawn last handled entity - - if (g_ceKvd != Invalid_Array) { - new size = ArraySize(g_ceKvd); - for (new i = 0; i < size; ++i) { - new kvd[KVD]; - ArrayGetArray(g_ceKvd, i, kvd); - - if (equal(kvd[KVD_Key], "model") && kvd[KVD_Value][0] == '*') { - engfunc(EngFunc_SetModel, g_lastCEEnt, kvd[KVD_Value]); - } - } - } - - g_lastCEEnt = 0; - - if (g_ceKvd != Invalid_Array) { - ArrayDestroy(g_ceKvd); - g_ceKvd = Invalid_Array; - } -} - -RegisterHook(CEFunction:function, const szClassname[], const szCallback[], pluginID = -1) -{ - new ceIdx; - if (!g_entityCount || !TrieGetCell(g_entityHandlers, szClassname, ceIdx)) { - log_error(0, "%s Entity %s is not registered.", LOG_PREFIX, szClassname); - return -1; - } - - new funcID = get_func_id(szCallback, pluginID); - if (funcID < 0) { - new szFilename[32]; - get_plugin(pluginID, szFilename, charsmax(szFilename)); - log_error(0, "Function %s not found in plugin %s.", szCallback, szFilename); - return -1; - } - - new Array:functions = ArrayGetCell(g_entityHooks, ceIdx); - if (!functions) { - functions = InitializeFunctions(ceIdx); - } - - new Array:functionHooks = ArrayGetCell(functions, _:function); - - new Array:hookData = CreateHookData(pluginID, funcID); - return ArrayPushCell(functionHooks, hookData); -} - -Array:InitializeFunctions(ceIdx) -{ - new Array:functions = ArrayCreate(1, _:CEFunction); - - for (new i = 0; i < _:CEFunction; ++i) { - new Array:functionHooks = ArrayCreate(); - ArrayPushCell(functions, functionHooks); - } - - ArraySetCell(g_entityHooks, ceIdx, functions); - - return functions; -} - -DestroyFunctions(ceIdx) -{ - new Array:functions = ArrayGetCell(g_entityHooks, ceIdx); - - if (!functions) { - return; - } - - for (new i = 0; i < _:CEFunction; ++i) { - new Array:functionHooks = ArrayGetCell(functions, i); - ArrayDestroy(functionHooks); - } - - ArrayDestroy(functions); - ArraySetCell(g_entityHooks, ceIdx, 0); -} - -Array:CreateHookData(pluginID, funcID) -{ - new Array:hookData = ArrayCreate(1, _:CEHookData); - ArrayPushCell(hookData, pluginID); - ArrayPushCell(hookData, funcID); - - return hookData; -} - -ExecuteFunction(CEFunction:function, ceIdx, any:...) -{ - new result = 0; - new ent = getarg(2); - - new Array:functions = ArrayGetCell(g_entityHooks, ceIdx); - if (functions == Invalid_Array) { - return 0; - } - - new Array:functionHooks = ArrayGetCell(functions, _:function); - - new count = ArraySize(functionHooks); - for (new i = 0; i < count; ++i) - { - new Array:hookData = ArrayGetCell(functionHooks, i); - new pluginID = ArrayGetCell(hookData, _:CEHookData_PluginID); - new funcID = ArrayGetCell(hookData, _:CEHookData_FuncID); - - if (funcID < 0) { - continue; - } - - if (callfunc_begin_i(funcID, pluginID) == 1) - { - callfunc_push_int(ent); - - switch (function) - { - case CEFunction_Kill, CEFunction_Killed: { - new killer = getarg(3); - new bool:picked = bool:getarg(4); - callfunc_push_int(killer); - callfunc_push_int(picked); - } - case CEFunction_Pickup, CEFunction_Picked: { - new id = getarg(3); - callfunc_push_int(id); - } - case CEFunction_KVD: { - static szKey[32]; - for (new i = 0; i < charsmax(szKey); ++i) { - szKey[i] = getarg(3, i); - - if (szKey[i] == '^0') { - break; - } - } - - static szValue[32]; - for (new i = 0; i < charsmax(szValue); ++i) { - szValue[i] = getarg(4, i); - - if (szValue[i] == '^0') { - break; - } - } - - callfunc_push_str(szKey); - callfunc_push_str(szValue); - } - } - - result += callfunc_end(); - } - } - - return result; -} - -Array:CreatePData(ent) -{ - new Array:ceData = ArrayCreate(1, CEData); - for (new i = 0; i < CEData; ++i) { - ArrayPushCell(ceData, 0); - } - - set_pev(ent, pev_gaitsequence, 'c'+'e'); - set_pev(ent, pev_iStepLeft, ceData); - - return ceData; -} - -DestroyPData(ent) -{ - //Destroy data array - new Array:ceData = GetPData(ent); - { - new Array:startOrigin = ArrayGetCell(ceData, CEData_StartOrigin); - if (startOrigin != Invalid_Array) { - ArrayDestroy(startOrigin); - } - } ArrayDestroy(ceData); - - set_pev(ent, pev_gaitsequence, 0); - set_pev(ent, pev_iStepLeft, 0); -} - -Array:GetPData(ent) -{ - new Array:ceData = any:pev(ent, pev_iStepLeft); - if (ceData == Invalid_Array) { - log_error(0, "%s Invalid Custom Entity data provided for %i.", LOG_PREFIX, ent); - } - - return ceData; -} - -/*--------------------------------[ Tasks ]--------------------------------*/ - -public TaskDisappear(taskID) -{ - new ent = taskID - TASKID_SUM_DISAPPEAR; - Kill(ent, 0); -} - -public TaskRespawn(taskID) -{ - new ent = taskID - TASKID_SUM_RESPAWN; - Respawn(ent); -} - -public TaskRemove(taskID) -{ - new ent = taskID - TASKID_SUM_REMOVE; - Remove(ent); -} - -public TaskRespawnEntities() -{ - RespawnEntities(); -} diff --git a/api_custom_weapons.sma b/api_custom_weapons.sma deleted file mode 100644 index 1e41324..0000000 --- a/api_custom_weapons.sma +++ /dev/null @@ -1,2623 +0,0 @@ -#pragma semicolon 1 - -#include -#include -#include -#include -#include -#include - -#include - -#define PLUGIN "[API] Custom Weapons" -#define VERSION "0.7.9" -#define AUTHOR "Hedgehog Fog" - -#define WALL_PUFF_SPRITE "sprites/wall_puff1.spr" - -#define VEC_DUCK_HULL_MIN Float:{-16.0, -16.0, -18.0} -#define VEC_DUCK_HULL_MAX Float:{16.0, 16.0, 18.0} - -#define IS_PLAYER(%1) (%1 > 0 && %1 <= MaxClients) - -#define TOKEN 743647146 - -enum _:WeaponListMessage { - WL_WeaponName[32], - WL_PrimaryAmmoType, - WL_PrimaryAmmoMaxAmount, - WL_SecondaryAmmoType, - WL_SecondaryAmmoMaxAmount, - WL_SlotId, - WL_NumberInSlot, - WL_WeaponId, - WL_Flags -} - -enum _:Function { - Function_PluginId, - Function_FunctionId -} - -new const g_rgszWeaponNames[CSW_LAST_WEAPON + 1][] = { - "", - "weapon_p228", - "weapon_shield", - "weapon_scout", - "weapon_hegrenade", - "weapon_xm1014", - "weapon_c4", - "weapon_mac10", - "weapon_aug", - "weapon_smokegrenade", - "weapon_elite", - "weapon_fiveseven", - "weapon_ump45", - "weapon_sg550", - "weapon_galil", - "weapon_famas", - "weapon_usp", - "weapon_glock18", - "weapon_awp", - "weapon_mp5navy", - "weapon_m249", - "weapon_m3", - "weapon_m4a1", - "weapon_tmp", - "weapon_g3sg1", - "weapon_flashbang", - "weapon_deagle", - "weapon_sg552", - "weapon_ak47", - "weapon_knife", - "weapon_p90" -}; - -new gmsgWeaponList; -new gmsgDeathMsg; - -new g_iszWeaponNames[CSW_LAST_WEAPON + 1]; -new bool:g_bWeaponHooks[CSW_LAST_WEAPON + 1]; -new g_weaponListDefaults[CSW_LAST_WEAPON + 1][WeaponListMessage]; - -new Array:g_rgWeapons[CW_Data]; -new Trie:g_rgWeaponsMap; -new g_iWeaponCount; - -new Float:g_flNextPredictionUpdate[MAX_PLAYERS + 1]; -new bool:g_bKnifeHolstered[MAX_PLAYERS + 1]; - -new g_iszWeaponBox; -new g_pNewWeaponboxEnt = -1; -new g_pKillerItem = -1; -new bool:g_bSupercede; -new bool:g_bPrecache; - -new Array:g_irgDecals; - -public plugin_precache() { - g_bPrecache = true; - - AllocateStrings(); - InitStorages(); - - register_forward(FM_UpdateClientData, "OnUpdateClientData_Post", 1); - register_forward(FM_PrecacheEvent, "OnPrecacheEvent_Post", 1); - register_forward(FM_SetModel, "OnSetModel_Post", 1); - register_forward(FM_DecalIndex, "OnDecalIndex_Post", 1); - - RegisterHam(Ham_Spawn, "weaponbox", "OnWeaponboxSpawn", .Post = 0); - RegisterHamPlayer(Ham_Player_PreThink, "OnPlayerPreThink_Post", .Post = 1); - RegisterHamPlayer(Ham_TakeDamage, "OnPlayerTakeDamage", .Post = 0); - RegisterHamPlayer(Ham_TakeDamage, "OnPlayerTakeDamage_Post", .Post = 1); - - precache_model(WALL_PUFF_SPRITE); -} - -public plugin_init() { - g_bPrecache = false; - - register_plugin(PLUGIN, VERSION, AUTHOR); - - gmsgWeaponList = get_user_msgid("WeaponList"); - gmsgDeathMsg = get_user_msgid("DeathMsg"); - - register_message(gmsgWeaponList, "OnMessage_WeaponList"); - register_message(gmsgDeathMsg, "OnMessage_DeathMsg"); -} - -public plugin_cfg() { - InitWeaponHooks(); -} - -public plugin_natives() { - register_library("api_custom_weapons"); - - register_native("CW_Register", "Native_Register"); - register_native("CW_GetHandlerByEntity", "Native_GetHandlerByEntity"); - register_native("CW_GetHandler", "Native_GetHandler"); - register_native("CW_GetWeaponData", "Native_GetWeaponData"); - register_native("CW_GetWeaponStringData", "Native_GetWeaponStringData"); - register_native("CW_GiveWeapon", "Native_GiveWeapon"); - register_native("CW_HasWeapon", "Native_HasWeapon"); - register_native("CW_SpawnWeapon", "Native_SpawnWeapon"); - register_native("CW_SpawnWeaponBox", "Native_SpawnWeaponBox"); - - register_native("CW_Deploy", "Native_Deploy"); - register_native("CW_Holster", "Native_Holster"); - register_native("CW_ItemPostFrame", "Native_ItemPostFrame"); - register_native("CW_Idle", "Native_Idle"); - register_native("CW_Reload", "Native_Reload"); - register_native("CW_PrimaryAttack", "Native_PrimaryAttack"); - register_native("CW_SecondaryAttack", "Native_SecondaryAttack"); - - register_native("CW_FireBulletsPlayer", "Native_FireBulletsPlayer"); - register_native("CW_EjectWeaponBrass", "Native_EjectWeaponBrass"); - register_native("CW_PlayAnimation", "Native_PlayAnimation"); - register_native("CW_GetPlayer", "Native_GetPlayer"); - - register_native("CW_DefaultDeploy", "Native_DefaultDeploy"); - register_native("CW_DefaultShot", "Native_DefaultShot"); - register_native("CW_DefaultShotgunShot", "Native_DefaultShotgunShot"); - register_native("CW_DefaultSwing", "Native_DefaultSwing"); - register_native("CW_DefaultReload", "Native_DefaultReload"); - register_native("CW_DefaultShotgunReload", "Native_DefaultShotgunReload"); - register_native("CW_DefaultShotgunIdle", "Native_DefaultShotgunIdle"); - - register_native("CW_GrenadeDetonate", "Native_GrenadeDetonate"); - register_native("CW_GrenadeSmoke", "Native_GrenadeSmoke"); - register_native("CW_RemovePlayerItem", "Native_RemovePlayerItem"); - - register_native("CW_Bind", "Native_Bind"); -} - -public plugin_end() { - DestroyStorages(); -} - -// ANCHOR: Natives - -public Native_Bind(iPluginId, iArgc) { - new CW:iHandler = CW:get_param(1); - new iBinding = get_param(2); - - new szFunctionName[32]; - get_string(3, szFunctionName, charsmax(szFunctionName)); - - Bind(iHandler, iBinding, iPluginId, get_func_id(szFunctionName, iPluginId)); -} - -public CW:Native_GetHandlerByEntity(iPluginId, iArgc) { - new pEntity = get_param(1); - return GetHandlerByEntity(pEntity); -} - -public CW:Native_GetHandler(iPluginId, iArgc) { - static szName[64]; - get_string(1, szName, charsmax(szName)); - - return GetHandler(szName); -} - -public any:Native_GetWeaponData(iPluginId, iArgc) { - new CW:iHandler = CW:get_param(1); - new CW_Data:iParam = CW_Data:get_param(2); - - return GetData(iHandler, iParam); -} - -public Native_GetWeaponStringData(iPluginId, iArgc) { - new CW:iHandler = CW:get_param(1); - new CW_Data:iParam = CW_Data:get_param(2); - - static szValue[128]; - GetStringData(iHandler, iParam, szValue, charsmax(szValue)); - - new iLen = get_param(4); - set_string(3, szValue, iLen); -} - -public CW:Native_Register(iPluginId, iArgc) { - new szName[64]; - get_string(1, szName, charsmax(szName)); - - new iWeaponId = get_param(2); - new iClipSize = get_param(3); - new iPrimaryAmmoType = get_param(4); - new iPrimaryAmmoMaxAmount = get_param(5); - new iSecondaryAmmoType = get_param(6); - new iSecondaryAmmoMaxAmount = get_param(7); - new iSlotId = get_param(8); - new iPosition = get_param(9); - new iWeaponFlags = get_param(10); - - new szIcon[16]; - get_string(11, szIcon, charsmax(szIcon)); - - new CW_Flags:iFlags = CW_Flags:get_param(12); - - return RegisterWeapon(iPluginId, szName, iWeaponId, iClipSize, iPrimaryAmmoType, iPrimaryAmmoMaxAmount, iSecondaryAmmoType, iSecondaryAmmoMaxAmount, iSlotId, iPosition, iWeaponFlags, szIcon, iFlags); -} - -public Native_GiveWeapon(iPluginId, iArgc) { - new pPlayer = get_param(1); - - static szWeapon[64]; - get_string(2, szWeapon, charsmax(szWeapon)); - - new CW:iHandler; - if (TrieGetCell(g_rgWeaponsMap, szWeapon, iHandler)) { - GiveWeapon(pPlayer, iHandler); - } -} - -public bool:Native_HasWeapon(iPluginId, iArgc) { - new pPlayer = get_param(1); - - static szWeapon[64]; - get_string(2, szWeapon, charsmax(szWeapon)); - - new CW:iHandler; - if (TrieGetCell(g_rgWeaponsMap, szWeapon, iHandler)) { - return HasWeapon(pPlayer, iHandler); - } - - return false; -} - -public Native_SpawnWeapon(iPluginId, iArgc) { - new CW:iHandler = CW:get_param(1); - return SpawnWeapon(iHandler); -} - -public Native_SpawnWeaponBox(iPluginId, iArgc) { - new CW:iHandler = CW:get_param(1); - return SpawnWeaponBox(iHandler); -} - -public bool:Native_DefaultDeploy(iPluginId, iArgc) { - new pWeapon = get_param(1); - - static szViewModel[64]; - get_string(2, szViewModel, charsmax(szViewModel)); - - static szWeaponModel[64]; - get_string(3, szWeaponModel, charsmax(szWeaponModel)); - - new iAnim = get_param(4); - - static szAnimExt[16]; - get_string(5, szAnimExt, charsmax(szAnimExt)); - - return DefaultDeploy(pWeapon, szViewModel, szWeaponModel, iAnim, szAnimExt); -} - -public Native_FireBulletsPlayer(iPluginId, iArgc) { - new pWeapon = get_param(1); - new iShots = get_param(2); - - static Float:vecSrc[3]; - get_array_f(3, vecSrc, sizeof(vecSrc)); - - static Float:vecDirShooting[3]; - get_array_f(4, vecDirShooting, sizeof(vecDirShooting)); - - static Float:vecSpread[3]; - get_array_f(5, vecSpread, sizeof(vecSpread)); - - new Float:flDistance = get_param_f(6); - new Float:flDamage = get_param_f(7); - new Float:flRangeModifier = get_param_f(8); - new pevAttacker = get_param(9); - - static Float:vecOut[3]; - - FireBulletsPlayer(pWeapon, iShots, vecSrc, vecDirShooting, vecSpread, flDistance, flDamage, flRangeModifier, pevAttacker, vecOut); - - set_array_f(10, vecOut, sizeof(vecOut)); -} - -public bool:Native_EjectWeaponBrass(iPluginId, iArgc) { - new pItem = get_param(1); - new iModelIndex = get_param(2); - new iSoundType = get_param(3); - - return EjectWeaponBrass(pItem, iModelIndex, iSoundType); -} - -public bool:Native_DefaultShot(iPluginId, iArgc) { - new pItem = get_param(1); - new Float:flDamage = get_param_f(2); - new Float:flRangeModifier = get_param_f(3); - new Float:flRate = get_param_f(4); - - static Float:vecSpread[3]; - get_array_f(5, vecSpread, sizeof(vecSpread)); - - new iShots = get_param(6); - new Float:flDistance = get_param_f(7); - - return DefaultShot(pItem, flDamage, flRangeModifier, flRate, vecSpread, iShots, flDistance); -} - -public bool:Native_DefaultShotgunShot(iPluginId, iArgc) { - new pItem = get_param(1); - new Float:flDamage = get_param_f(2); - new Float:flRangeModifier = get_param_f(3); - new Float:flRate = get_param_f(4); - new Float:flPumpDelay = get_param_f(5); - - static Float:vecSpread[3]; - get_array_f(6, vecSpread, sizeof(vecSpread)); - - new iShots = get_param(7); - new Float:flDistance = get_param_f(8); - - return DefaultShotgunShot(pItem, flDamage, flRangeModifier, flRate, flPumpDelay, vecSpread, iShots, flDistance); -} - -public Native_DefaultSwing(iPluginId, iArgc) { - new pItem = get_param(1); - new Float:flDamage = get_param_f(2); - new Float:flRate = get_param_f(3); - new Float:flDistance = get_param_f(4); - - return DefaultSwing(pItem, flDamage, flRate, flDistance); -} - -public Native_PlayAnimation(iPluginID, argc) { - new pItem = get_param(1); - new iSequence = get_param(2); - new Float:flDuration = get_param_f(3); - - PlayWeaponAnim(pItem, iSequence, flDuration); -} - -public Native_GetPlayer(iPluginID, argc) { - new pItem = get_param(1); - return GetPlayer(pItem); -} - -public bool:Native_DefaultReload(iPluginId, iArgc) { - new pItem = get_param(1); - new iAnim = get_param(2); - new Float:flDelay = get_param_f(3); - - return DefaultReload(pItem, iAnim, flDelay); -} - -public bool:Native_DefaultShotgunReload(iPluginId, iArgc) { - new pItem = get_param(1); - new iStartAnim = get_param(2); - new iEndAnim = get_param(3); - new Float:flDelay = get_param_f(4); - new Float:flDuration = get_param_f(5); - - return DefaultShotgunReload(pItem, iStartAnim, iEndAnim, flDelay, flDuration); -} - -public bool:Native_DefaultShotgunIdle(iPluginId, iArgc) { - new pItem = get_param(1); - new iAnim = get_param(2); - new iReloadEndAnim = get_param(3); - new Float:flDuration = get_param_f(4); - new Float:flReloadEndDuration = get_param_f(5); - - static szPumpSound[64]; - get_string(6, szPumpSound, charsmax(szPumpSound)); - - return DefaultShotgunIdle(pItem, iAnim, iReloadEndAnim, flDuration, flReloadEndDuration, szPumpSound); -} - -public Native_Deploy(iPluginId, iArgc) { - new pItem = get_param(1); - WeaponDeploy(pItem); -} - -public Native_Holster(iPluginId, iArgc) { - new pItem = get_param(1); - WeaponHolster(pItem); -} - -public Native_ItemPostFrame(iPluginId, iArgc) { - new pItem = get_param(1); - ItemPostFrame(pItem); -} - -public Native_Idle(iPluginId, iArgc) { - new pItem = get_param(1); - WeaponIdle(pItem); -} - -public Native_Reload(iPluginId, iArgc) { - new pItem = get_param(1); - Reload(pItem); -} - -public Native_PrimaryAttack(iPluginId, iArgc) { - new pItem = get_param(1); - PrimaryAttack(pItem); -} -public Native_SecondaryAttack(iPluginId, iArgc) { - new pItem = get_param(1); - SecondaryAttack(pItem); -} - -public Native_GrenadeDetonate(iPluginId, iArgc) { - new pGrenade = get_param(1); - new Float:flRadius = get_param_f(2); - new Float:flMagnitude = get_param_f(3); - GrenadeDetonate(pGrenade, flRadius, flMagnitude); -} - -public Native_GrenadeSmoke(iPluginId, iArgc) { - new pGrenade = get_param(1); - GrenadeSmoke(pGrenade); -} - -public Native_RemovePlayerItem(iPluginId, iArgc) { - new pItem = get_param(1); - RemovePlayerItem(pItem); -} - -// ANCHOR: Forwards - -public client_connect(pPlayer) { - g_bKnifeHolstered[pPlayer] = true; -} - -public client_disconnected(pPlayer) { - SetWeaponPrediction(pPlayer, true); -} - -// ANCHOR: Hook Callbacks - -public OnItemDeploy(this) { - new CW:iHandler = GetHandlerByEntity(this); - if (iHandler == CW_INVALID_HANDLER) { - return HAM_IGNORED; - } - - WeaponDeploy(this); - - return HAM_SUPERCEDE; -} - -public OnItemHolster(this) { - new pPlayer = GetPlayer(this); - g_bKnifeHolstered[pPlayer] = IsWeaponKnife(this); - - new CW:iHandler = GetHandlerByEntity(this); - if (iHandler == CW_INVALID_HANDLER) { - return HAM_IGNORED; - } - - WeaponHolster(this); - - return HAM_SUPERCEDE; -} - -public OnItemPostFrame(this) { - new CW:iHandler = GetHandlerByEntity(this); - if (iHandler == CW_INVALID_HANDLER) { - return HAM_IGNORED; - } - - ItemPostFrame(this); - - return HAM_SUPERCEDE; -} - -public OnWeaponPrimaryAttack(this) { - new CW:iHandler = GetHandlerByEntity(this); - if (iHandler == CW_INVALID_HANDLER) { - return HAM_IGNORED; - } - - g_bSupercede = GetHamReturnStatus() >= HAM_SUPERCEDE; - - return HAM_SUPERCEDE; -} - -public OnWeaponSecondaryAttack(this) { - new CW:iHandler = GetHandlerByEntity(this); - if (iHandler == CW_INVALID_HANDLER) { - return HAM_IGNORED; - } - - g_bSupercede = GetHamReturnStatus() >= HAM_SUPERCEDE; - - return HAM_SUPERCEDE; -} - -public OnWeaponReload(this) { - new CW:iHandler = GetHandlerByEntity(this); - if (iHandler == CW_INVALID_HANDLER) { - return HAM_IGNORED; - } - - g_bSupercede = GetHamReturnStatus() >= HAM_SUPERCEDE; - - return HAM_SUPERCEDE; -} - -public OnWeaponIdle(this) { - new CW:iHandler = GetHandlerByEntity(this); - if (iHandler == CW_INVALID_HANDLER) { - return HAM_IGNORED; - } - - g_bSupercede = GetHamReturnStatus() >= HAM_SUPERCEDE; - - return HAM_SUPERCEDE; -} - -public OnUpdateClientData_Post(pPlayer, iSendWeapons, pCdHandle) { - if (!is_user_alive(pPlayer)) { - return FMRES_IGNORED; - } - - new pItem = get_member(pPlayer, m_pActiveItem); - if (pItem == -1) { - return FMRES_IGNORED; - } - - new CW:iHandler = GetHandlerByEntity(pItem); - if (iHandler == CW_INVALID_HANDLER) { - return FMRES_IGNORED; - } - - set_cd(pCdHandle, CD_flNextAttack, get_gametime() + 0.001); // block default animation - - return FMRES_HANDLED; -} - -public OnItemSlot(this) { - new CW:iHandler = GetHandlerByEntity(this); - if (iHandler == CW_INVALID_HANDLER) { - return HAM_IGNORED; - } - - new iSlot = GetData(iHandler, CW_Data_SlotId); - SetHamReturnInteger(iSlot + 1); - - return HAM_SUPERCEDE; -} - -public OnCSItemGetMaxSpeed(this) { - new CW:iHandler = GetHandlerByEntity(this); - if (iHandler == CW_INVALID_HANDLER) { - return HAM_IGNORED; - } - - new Float:flMaxSpeed = ExecuteBindedFunction(CWB_GetMaxSpeed, this); - if (_:flMaxSpeed != PLUGIN_CONTINUE) { - SetHamReturnFloat(flMaxSpeed); - return HAM_OVERRIDE; - } - - return HAM_IGNORED; -} - -public OnItemAddToPlayer_Post(this, pPlayer) { - new pPlayer = GetPlayer(this); - if (!ExecuteHam(Ham_IsPlayer, pPlayer)) { - return HAM_IGNORED; - } - - new CW:iHandler = GetHandlerByEntity(this); - if (iHandler == CW_INVALID_HANDLER) { - new iWeaponId = get_member(this, m_iId); - ResetWeaponList(pPlayer, iWeaponId); - } else { - set_member(this, m_Weapon_iPrimaryAmmoType, GetData(iHandler, CW_Data_PrimaryAmmoType)); - UpdateWeaponList(pPlayer, iHandler); - } - - return HAM_HANDLED; -} - -public OnSpawn_Post(this) { - new CW:iHandler = GetHandlerByEntity(this); - if (iHandler == CW_INVALID_HANDLER) { - return HAM_IGNORED; - } - - ExecuteBindedFunction(CWB_Spawn, this); - - return HAM_IGNORED; -} - -public OnWeaponboxSpawn(this) { - g_pNewWeaponboxEnt = this; -} - -public OnPlayerPreThink_Post(pPlayer) { - if (get_gametime() < g_flNextPredictionUpdate[pPlayer]) { - return HAM_IGNORED; - } - - new iObsMode = pev(pPlayer, pev_iuser1); - new pObsTarget = pev(pPlayer, pev_iuser2); - - new pActiveItem = iObsMode == OBS_IN_EYE - ? IS_PLAYER(pObsTarget) ? get_member(pObsTarget, m_pActiveItem) : -1 - : get_member(pPlayer, m_pActiveItem); - - if (pActiveItem == -1) { - SetWeaponPrediction(pPlayer, false); - return HAM_IGNORED; - } - - new CW:iHandler = GetHandlerByEntity(pActiveItem); - SetWeaponPrediction(pPlayer, iHandler == CW_INVALID_HANDLER); - - return HAM_HANDLED; -} - -public OnPlayerTakeDamage(pPlayer, pInflictor, pAttacker) { - if (pAttacker && ExecuteHam(Ham_IsPlayer, pAttacker) && pInflictor == pAttacker) { - g_pKillerItem = get_member(pAttacker, m_pActiveItem); - } else { - g_pKillerItem = pInflictor; - } -} - -public OnPlayerTakeDamage_Post() { - g_pKillerItem = -1; -} - -public OnMessage_DeathMsg(iMsgId, iDest, pPlayer) { - if (g_pKillerItem == -1) { - return PLUGIN_CONTINUE; - } - - new pKiller = get_msg_arg_int(1); - if (!pKiller) { - return PLUGIN_CONTINUE; - } - - if (!ExecuteHam(Ham_IsPlayer, pKiller)) { - return PLUGIN_CONTINUE; - } - - if (!is_user_alive(pKiller)) { - return PLUGIN_CONTINUE; - } - - new CW:iHandler = GetHandlerByEntity(g_pKillerItem); - if (iHandler == CW_INVALID_HANDLER) { - return PLUGIN_CONTINUE; - } - - static szIcon[64]; - GetStringData(iHandler, CW_Data_Icon, szIcon, charsmax(szIcon)); - - if (szIcon[0] == '^0') { - GetStringData(iHandler, CW_Data_Name, szIcon, charsmax(szIcon)); - } - - set_msg_arg_string(4, szIcon); - - return PLUGIN_CONTINUE; -} - -public OnSetModel_Post(this, const szModel[]) { - if (!pev_valid(this)) { - return FMRES_IGNORED; - } - - if (g_pNewWeaponboxEnt == -1) { - return FMRES_IGNORED; - } - - if (this != g_pNewWeaponboxEnt) { - return FMRES_IGNORED; - } - - static szClassname[32]; - pev(this, pev_classname, szClassname, charsmax(szClassname)); - - if (!equal(szClassname, "weaponbox")) { - g_pNewWeaponboxEnt = -1; - return FMRES_IGNORED; - } - - new pItem = FindWeaponBoxSingleItem(this); - if (pItem == -1) { - return FMRES_IGNORED; - } - - new CW:iHandler = GetHandlerByEntity(pItem); - if (iHandler == CW_INVALID_HANDLER) { - return FMRES_IGNORED; - } - - ExecuteBindedFunction(CWB_WeaponBoxModelUpdate, pItem, this); - g_pNewWeaponboxEnt = -1; - - if (!g_bPrecache) { - if (!ExecuteHamB(Ham_CS_Item_CanDrop, pItem)) { - set_pev(this, pev_flags, pev(this, pev_flags) | FL_KILLME); - dllfunc(DLLFunc_Think, this); - } - } - - return FMRES_HANDLED; -} - -public OnDecalIndex_Post() { - if (!g_bPrecache) { - return; - } - - ArrayPushCell(g_irgDecals, get_orig_retval()); -} - -public OnWeaponClCmd(pPlayer) { - static szName[64]; - read_argv(0, szName, charsmax(szName)); - - new CW:iHandler; - TrieGetCell(g_rgWeaponsMap, szName, iHandler); - - new iWeaponId = GetData(iHandler, CW_Data_Id); - - static szBaseName[32]; - get_weaponname(iWeaponId, szBaseName, charsmax(szBaseName)); - client_cmd(pPlayer, szBaseName); - - return PLUGIN_HANDLED; -} - -public OnCanDrop(this) { - new CW:iHandler = GetHandlerByEntity(this); - if (iHandler == CW_INVALID_HANDLER) { - return HAM_IGNORED; - } - - if (GetHamReturnStatus() >= HAM_OVERRIDE) { - return GetHamReturnStatus(); - } - - SetHamReturnInteger( - ExecuteBindedFunction(CWB_CanDrop, this) == PLUGIN_CONTINUE ? 1 : 0 - ); - - return HAM_OVERRIDE; -} - -public OnMessage_WeaponList(iMsgId, iMsgDest, pPlayer) { - new iWeaponId = get_msg_arg_int(8); - - if (g_weaponListDefaults[iWeaponId][WL_WeaponId] == iWeaponId) { - return PLUGIN_CONTINUE; // already initialized - } - - get_msg_arg_string(1, g_weaponListDefaults[iWeaponId][WL_WeaponName], 31); - g_weaponListDefaults[iWeaponId][WL_PrimaryAmmoType] = get_msg_arg_int(2); - g_weaponListDefaults[iWeaponId][WL_PrimaryAmmoMaxAmount] = get_msg_arg_int(3); - g_weaponListDefaults[iWeaponId][WL_SecondaryAmmoType] = get_msg_arg_int(4); - g_weaponListDefaults[iWeaponId][WL_SecondaryAmmoMaxAmount] = get_msg_arg_int(5); - g_weaponListDefaults[iWeaponId][WL_SlotId] = get_msg_arg_int(6); - g_weaponListDefaults[iWeaponId][WL_NumberInSlot] = get_msg_arg_int(7); - g_weaponListDefaults[iWeaponId][WL_WeaponId] = iWeaponId; - g_weaponListDefaults[iWeaponId][WL_Flags] = get_msg_arg_int(9); - - return PLUGIN_CONTINUE; -} - -// ANCHOR: Weapon Entity Methods - -CompleteReload(this) { - new CW:iHandler = GetHandlerByEntity(this); - new CW_Flags:iFlags = GetData(iHandler, CW_Data_Flags); - - if (~iFlags & CWF_CustomReload) { - new pPlayer = GetPlayer(this); - new iMaxClip = GetData(iHandler, CW_Data_ClipSize); - new iClip = get_member(this, m_Weapon_iClip); - new iPrimaryAmmoIndex = get_member(this, m_Weapon_iPrimaryAmmoType); - new iBpAmmo = get_member(pPlayer, m_rgAmmo, iPrimaryAmmoIndex); - new iSize = min(iMaxClip - iClip, iBpAmmo); - - set_member(this, m_Weapon_iClip, iClip + iSize); - set_member(pPlayer, m_rgAmmo, iBpAmmo - iSize, iPrimaryAmmoIndex); - } - - set_member(this, m_Weapon_fInReload, 0); - - ExecuteBindedFunction(CWB_DefaultReloadEnd, this); -} - -ItemPostFrame(this) { - new CW:iHandler = GetHandlerByEntity(this); - new pPlayer = GetPlayer(this); - new flInReload = get_member(this, m_Weapon_fInReload); - new iMaxClip = GetData(iHandler, CW_Data_ClipSize); - new iWeaponFlags = GetData(iHandler, CW_Data_WeaponFlags); - new Float:flNextAttack = get_member(pPlayer, m_flNextAttack); - new button = pev(pPlayer, pev_button); - new iPrimaryAmmoIndex = get_member(this, m_Weapon_iPrimaryAmmoType); - new iSecondaryAmmoIndex = 0; - new Float:flNextPrimaryAttack = get_member(this, m_Weapon_flNextPrimaryAttack); - new Float:flNextSecondaryAttack = get_member(this, m_Weapon_flNextSecondaryAttack); - new iPrimaryAmmoAmount = get_member(pPlayer, m_rgAmmo, iPrimaryAmmoIndex); - new iSecondaryAmmoAmount = get_member(pPlayer, m_rgAmmo, iSecondaryAmmoIndex); - - new Float:flReloadEndTime = get_member(this, m_Weapon_flNextReload); - if (flReloadEndTime && flReloadEndTime < get_gametime()) { - set_member(this, m_Weapon_flNextReload, 0.0); - ExecuteBindedFunction(CWB_Pump, this); - } - - if (flInReload && flNextAttack <= 0.0) { - CompleteReload(this); - } - - if ((button & IN_ATTACK2) && flNextSecondaryAttack <= 0) { - if (iSecondaryAmmoIndex > 0 && !iSecondaryAmmoAmount) { - set_member(this, m_Weapon_fFireOnEmpty, 1); - } - - SecondaryAttack(this); - } else if ((button & IN_ATTACK) && flNextPrimaryAttack <= 0) { - if ((!get_member(this, m_Weapon_iClip) && iPrimaryAmmoIndex > 0) || (iMaxClip == -1 && !iPrimaryAmmoAmount)) { - set_member(this, m_Weapon_fFireOnEmpty, 1); - } - - PrimaryAttack(this); - } else if ((button & IN_RELOAD) && iMaxClip != WEAPON_NOCLIP && !flInReload) { - Reload(this); - } else if (!(button & (IN_ATTACK|IN_ATTACK2))) { - set_member(this, m_Weapon_fFireOnEmpty, 0); - - if (!IsUseable(this) && flNextPrimaryAttack < 0.0) { - // if (!(iWeaponFlags & ITEM_FLAG_NOAUTOSWITCHEMPTY) && g_pGameRules->GetNextBestWeapon(m_pPlayer, this)) { - // set_member(this, m_Weapon_flNextPrimaryAttack, 0.3); - // return; - // } - } else { - if (!get_member(this, m_Weapon_iClip) && !(iWeaponFlags & ITEM_FLAG_NOAUTORELOAD) && flNextPrimaryAttack < 0.0) { - Reload(this); - return; - } - } - - set_member(this, m_Weapon_iShotsFired, 0); - WeaponIdle(this); - return; - } - - if (ShouldWeaponIdle(this)) { - WeaponIdle(this); - } -} - -SecondaryAttack(this) { - if (get_member_game(m_bFreezePeriod)) { - return; - } - - ExecuteHamB(Ham_Weapon_SecondaryAttack, this); - - if (g_bSupercede) { - return; - } - - if (ExecuteBindedFunction(CWB_SecondaryAttack, this) > PLUGIN_CONTINUE) { - return; - } -} - -PrimaryAttack(this) { - if (get_member_game(m_bFreezePeriod)) { - return; - } - - ExecuteHamB(Ham_Weapon_PrimaryAttack, this); - - if (g_bSupercede) { - return; - } - - if (ExecuteBindedFunction(CWB_PrimaryAttack, this) > PLUGIN_CONTINUE) { - return; - } -} - -Reload(this) { - ExecuteHamB(Ham_Weapon_Reload, this); - - if (g_bSupercede) { - return; - } - - if (ExecuteBindedFunction(CWB_Reload, this) > PLUGIN_CONTINUE) { - return; - } -} - -WeaponIdle(this) { - if (get_member(this, m_Weapon_flTimeWeaponIdle) > 0.0) { - return; - } - - ExecuteHamB(Ham_Weapon_WeaponIdle, this); - - if (g_bSupercede) { - return; - } - - if (ExecuteBindedFunction(CWB_Idle, this) > PLUGIN_CONTINUE) { - return; - } -} - -WeaponHolster(this) { - new pPlayer = GetPlayer(this); - - SetWeaponPrediction(pPlayer, true); - set_member(this, m_Weapon_fInReload, 0); - set_member(this, m_Weapon_fInSpecialReload, 0); - set_member(this, m_Weapon_flNextReload, 0.0); - - if (ExecuteBindedFunction(CWB_Holster, this) > PLUGIN_CONTINUE) { - return; - } -} - -WeaponDeploy(this) { - if (ExecuteBindedFunction(CWB_Deploy, this) > PLUGIN_CONTINUE) { - return; - } - - new pPlayer = GetPlayer(this); - - if (g_bKnifeHolstered[pPlayer]) { - g_flNextPredictionUpdate[pPlayer] = get_gametime() + 1.0; - } else if (get_member(this, m_iId) == CSW_KNIFE) { - SetWeaponPrediction(pPlayer, false); - } - - // SetThink(this, "DisablePrediction"); - // set_pev(this, pev_nextthink, get_gametime() + 0.1); -} - -bool:ShouldWeaponIdle(this) { - #pragma unused this - return false; -} - -bool:IsUseable(this) { - new CW:iHandler = GetHandlerByEntity(this); - new pPlayer = GetPlayer(this); - new iPrimaryAmmoIndex = get_member(this, m_Weapon_iPrimaryAmmoType); - new iMaxAmmo1 = GetData(iHandler, CW_Data_PrimaryAmmoMaxAmount); - new iClip = get_member(this, m_Weapon_iClip); - new iBpAmmo = get_member(pPlayer, m_rgAmmo, iPrimaryAmmoIndex); - - if (iClip <= 0) { - if (iBpAmmo <= 0 && iMaxAmmo1 != -1) { - return false; - } - } - - return true; -} - -PlayWeaponAnim(this, iSequence, Float:flDuration) { - SendWeaponAnim(this, iSequence); - set_member(this, m_Weapon_flTimeWeaponIdle, flDuration); -} - -SendWeaponAnim(this, iAnim) { - new pPlayer = GetPlayer(this); - - SendPlayerWeaponAnim(pPlayer, this, iAnim); - - for (new pSpectator = 1; pSpectator <= MaxClients; pSpectator++) { - if (pSpectator == pPlayer) { - continue; - } - - if (!is_user_connected(pSpectator)) { - continue; - } - - if (pev(pSpectator, pev_iuser1) != OBS_IN_EYE) { - continue; - } - - if (pev(pSpectator, pev_iuser2) != pPlayer) { - continue; - } - - SendPlayerWeaponAnim(pSpectator, this, iAnim); - } -} - -SendPlayerWeaponAnim(pPlayer, pWeapon, iAnim) { - new iBody = pev(pWeapon, pev_body); - - set_pev(pPlayer, pev_weaponanim, iAnim); - - if (!is_user_bot(pPlayer)) { - emessage_begin(MSG_ONE, SVC_WEAPONANIM, _, pPlayer); - ewrite_byte(iAnim); - ewrite_byte(iBody); - emessage_end(); - } -} - -GetPlayer(this) { - return get_member(this, m_pPlayer); -} - -FireBulletsPlayer(this, cShots, Float:vecSrc[3], Float:vecDirShooting[3], Float:vecSpread[3], Float:flDistance, Float:flDamage, Float:flRangeModifier, pAttacker, Float:vecOut[3]) { - new CW:iHandler = GetHandlerByEntity(this); - if (iHandler == CW_INVALID_HANDLER) { - return; - } - - new pPlayer = GetPlayer(this); - new shared_rand = pPlayer > 0 ? get_member(pPlayer, random_seed) : 0; - new CW_Flags:iFlags = GetData(iHandler, CW_Data_Flags); - - new pTr = create_tr2(); - - static Float:vecRight[3]; - get_global_vector(GL_v_right, vecRight); - - static Float:vecUp[3]; - get_global_vector(GL_v_up, vecUp); - - static Float:vecMultiplier[3]; - - if (!pAttacker) { - pAttacker = this; // the default attacker is ourselves - } - - // ClearMultiDamage(); - // gMultiDamage.type = DMG_BULLET | DMG_NEVERGIB; - - for (new iShot = 1; iShot <= cShots; iShot++) { - //Use player's random seed. - // get circular gaussian spread - vecMultiplier[0] = SharedRandomFloat( shared_rand + iShot, -0.5, 0.5 ) + SharedRandomFloat( shared_rand + ( 1 + iShot ) , -0.5, 0.5 ); - vecMultiplier[1] = SharedRandomFloat( shared_rand + ( 2 + iShot ), -0.5, 0.5 ) + SharedRandomFloat( shared_rand + ( 3 + iShot ), -0.5, 0.5 ); - vecMultiplier[2] = vecMultiplier[0] * vecMultiplier[0] + vecMultiplier[1] * vecMultiplier[1]; - - static Float:vecDir[3]; - for (new i = 0; i < 3; ++i) { - vecDir[i] = vecDirShooting[i] + (vecMultiplier[0] * vecSpread[0] * vecRight[i]) + (vecMultiplier[1] * vecSpread[1] * vecUp[i]); - } - - static Float:vecEnd[3]; - for (new i = 0; i < 3; ++i) { - vecEnd[i] = vecSrc[i] + (vecDir[i] * flDistance); - } - - engfunc(EngFunc_TraceLine, vecSrc, vecEnd, DONT_IGNORE_MONSTERS, this, pTr); - - new Float:flFraction; - get_tr2(pTr, TR_flFraction, flFraction); - - // do damage, paint decals - if (flFraction != 1.0) { - new pHit = get_tr2(pTr, TR_pHit); - - if (pHit < 0) { - pHit = 0; - } - - new Float:flCurrentDistance = flDistance * flFraction; - new Float:flCurrentDamage = flDamage * floatpower(flRangeModifier, flCurrentDistance / 500.0); - - rg_multidmg_clear(); - ExecuteHamB(Ham_TraceAttack, pHit, pAttacker, flCurrentDamage, vecDir, pTr, DMG_BULLET | DMG_NEVERGIB); - rg_multidmg_apply(this, pAttacker); - - // TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); - // DecalGunshot( &tr, iBulletType ); - - // new iDecalIndex = ExecuteHam(Ham_DamageDecal, pHit, DMG_BULLET); - // DecalTrace2(pTr, iDecalIndex); - - if (!ExecuteHam(Ham_IsPlayer, pHit)) { - if (~iFlags & CWF_NoBulletSmoke) { - BulletSmoke(pTr); - } - - if (~iFlags & CWF_NoBulletDecal) { - new iDecalIndex = GetDecalIndex(pHit); - if (iDecalIndex >= 0) { - MakeDecal(pTr, pHit, iDecalIndex); - } - } - } - } - - // make bullet trails - static Float:vecEndPos[3]; - get_tr2(pTr, TR_vecEndPos, vecEndPos); - - BubbleTrail(vecSrc, vecEndPos, floatround((flDistance * flFraction) / 64.0)); - } - - vecOut[0] = vecMultiplier[0] * vecSpread[0]; - vecOut[1] = vecMultiplier[1] * vecSpread[1]; - vecOut[2] = 0.0; - - free_tr2(pTr); -} - -GrenadeDetonate(this, Float:flRadius, Float:flMagnitude) { - static Float:vecStart[3]; - pev(this, pev_origin, vecStart); - vecStart[2] += 8.0; - - static Float:vecEnd[3]; - xs_vec_copy(vecStart, vecEnd); - vecEnd[2] -= 40.0; - - new pTr = create_tr2(); - engfunc(EngFunc_TraceLine, vecStart, vecEnd, IGNORE_MONSTERS, this, pTr); - GrenadeExplode(this, pTr, DMG_GRENADE | DMG_ALWAYSGIB, flRadius, flMagnitude); - free_tr2(pTr); -} - -GrenadeExplode(this, pTr, iDamageBits, Float:flRadius, Float:flMagnitude) { - new Float:flDamage; - pev(this, pev_dmg, flDamage); - - set_pev(this, pev_model, NULL_STRING); - set_pev(this, pev_solid, SOLID_NOT); - set_pev(this, pev_takedamage, DAMAGE_NO); - - new Float:flFraction; - get_tr2(pTr, TR_Fraction, flFraction); - - static Float:vecPlaneNormal[3]; - get_tr2(pTr, TR_vecPlaneNormal, vecPlaneNormal); - - static Float:vecOrigin[3]; - pev(this, pev_origin, vecOrigin); - - if (flFraction != 1.0) { - get_tr2(pTr, TR_vecEndPos, vecOrigin); - - for (new i = 0; i < 3; ++i) { - vecOrigin[i] += (vecPlaneNormal[i] * (flMagnitude ? flMagnitude : flDamage - 24.0) * 0.6); - } - - set_pev(this, pev_origin, vecOrigin); - } - - GrenadeExplosion(vecOrigin, flDamage); - - // CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, NORMAL_EXPLOSION_VOLUME, 3.0 ); - new iOwner = pev(this, pev_owner); - set_pev(this, pev_owner, 0); - - _RadiusDamage(vecOrigin, this, iOwner, flDamage, flRadius ? flRadius : flDamage * 3.5, CLASS_NONE, iDamageBits); - - ExplosionDecalTrace(pTr); - DebrisSound(this); - - set_pev(this, pev_effects, pev(this, pev_effects) | EF_NODRAW); - - // SetThink( &CGrenade::Smoke ); - // GrenadeSmoke(vecOrigin, flDamage); - - set_pev(this, pev_velocity, NULL_VECTOR); - set_pev(this, pev_nextthink, get_gametime() + 0.1); - - if (PointContents(vecOrigin) != CONTENTS_WATER) { - new iSparkCount = random(4); - for (new i = 0; i < iSparkCount; ++i) { - SparkShower(vecOrigin, vecPlaneNormal, 0); - } - } -} - -bool:IsWeaponKnife(pWeapon) { - if (GetHandlerByEntity(pWeapon) != CW_INVALID_HANDLER) { - return false; - } - - if (get_member(pWeapon, m_iId) != CSW_KNIFE) { - return false; - } - - return true; -} - -// ANCHOR: Weapon Callbacks - -public Smack(this) { - new CW:iHandler = GetHandlerByEntity(this); - new CW_Flags:iFlags = GetData(iHandler, CW_Data_Flags); - - new pTr = pev(this, pev_iuser1); - new pHit = get_tr2(pTr, TR_pHit); - if (pHit < 0) { - pHit = 0; - } - - if (~iFlags & CWF_NoBulletDecal) { - new iDecalIndex = GetDecalIndex(pHit); - if (iDecalIndex >= 0) { - MakeDecal(pTr, pHit, iDecalIndex, false); - } - } - - free_tr2(pTr); - - SetThink(this, NULL_STRING); -} - -// public DisablePrediction(this) { -// new pPlayer = GetPlayer(this); -// SetWeaponPrediction(pPlayer, false); -// SetThink(this, NULL_STRING); -// } - -// ANCHOR: Weapon Entity Default Methods - -bool:DefaultReload(this, iAnim, Float:flDelay) { - new CW:iHandler = GetHandlerByEntity(this); - new pPlayer = GetPlayer(this); - new iPrimaryAmmoIndex = get_member(this, m_Weapon_iPrimaryAmmoType); - new iPrimaryAmmoAmount = get_member(pPlayer, m_rgAmmo, iPrimaryAmmoIndex); - - if (iPrimaryAmmoAmount <= 0) { - return false; - } - - new iClip = get_member(this, m_Weapon_iClip); - new iClipSize = GetData(iHandler, CW_Data_ClipSize); - - new size = min(iClipSize - iClip, iPrimaryAmmoAmount); - if (size == 0) { - return false; - } - - if (get_member(this, m_Weapon_fInReload)) { - return false; - } - - set_member(pPlayer, m_flNextAttack, flDelay); - set_member(this, m_Weapon_fInReload, 1); - - PlayWeaponAnim(this, iAnim, 3.0); - rg_set_animation(pPlayer, PLAYER_RELOAD); - - return true; -} - -bool:DefaultShotgunReload(this, iStartAnim, iEndAnim, Float:flDelay, Float:flDuration) { - new pPlayer = GetPlayer(this); - new iClip = get_member(this, m_Weapon_iClip); - new iPrimaryAmmoType = get_member(this, m_Weapon_iPrimaryAmmoType); - new CW:iHandler = GetHandlerByEntity(this); - new iClipSize = GetData(iHandler, CW_Data_ClipSize); - - if (get_member(pPlayer, m_rgAmmo, iPrimaryAmmoType) <= 0 || iClip == iClipSize) { - return false; - } - - // don't reload until recoil is done - new Float:flNextPrimaryAttack = get_member(this, m_Weapon_flNextPrimaryAttack); - new flInSpecialReload = get_member(this, m_Weapon_fInSpecialReload); - if (flNextPrimaryAttack > 0.0) { - return false; - } - - new Float:flTimeWeaponIdle = get_member(this, m_Weapon_flTimeWeaponIdle); - // check to see if we're ready to reload - if (flInSpecialReload == 0) { - rg_set_animation(pPlayer, PLAYER_RELOAD); - PlayWeaponAnim(this, iStartAnim, flDelay); - - set_member(this, m_Weapon_fInSpecialReload, 1); - set_member(pPlayer, m_flNextAttack, flDelay); - set_member(this, m_Weapon_flNextPrimaryAttack, 1.0); - set_member(this, m_Weapon_flNextSecondaryAttack, 1.0); - } else if (flInSpecialReload == 1) { - if (flTimeWeaponIdle > 0.0) { - return false; - } - - set_member(this, m_Weapon_fInSpecialReload, 2); - - // if (RANDOM_LONG(0,1)) - // EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/reload1.wav", 1, ATTN_NORM, 0, 85 + RANDOM_LONG(0,0x1f)); - // else - // EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/reload3.wav", 1, ATTN_NORM, 0, 85 + RANDOM_LONG(0,0x1f)); - - PlayWeaponAnim(this, iEndAnim, flDuration); - } else { - // Add them to the clip - set_member(this, m_Weapon_iClip, ++iClip); - set_member(this, m_Weapon_fInSpecialReload, 1); - set_member(pPlayer, m_rgAmmo, get_member(pPlayer, m_rgAmmo, iPrimaryAmmoType) - 1, iPrimaryAmmoType); - } - - return true; -} - -bool:DefaultShotgunIdle(this, iAnim, iReloadEndAnim, Float:flDuration, Float:flReloadEndDuration, const szPumpSound[]) { - new CW:iHandler = GetHandlerByEntity(this); - if (iHandler == CW_INVALID_HANDLER) { - return false; - } - - new Float:flTimeWeaponIdle = get_member(this, m_Weapon_flTimeWeaponIdle); - if (flTimeWeaponIdle < 0.0) { - new pPlayer = get_member(this, m_pPlayer); - new iPrimaryAmmoType = get_member(this, m_Weapon_iPrimaryAmmoType); - new iPrimaryAmmoAmount = get_member(pPlayer, m_rgAmmo, iPrimaryAmmoType); - new flInSpecialReload = get_member(this, m_Weapon_fInSpecialReload); - new iClip = get_member(this, m_Weapon_iClip); - - if (!iClip && flInSpecialReload == 0 && iPrimaryAmmoAmount) { - Reload(this); - } else if (flInSpecialReload != 0) { - new iClipSize = GetData(iHandler, CW_Data_ClipSize); - if (iClip < iClipSize && iPrimaryAmmoAmount) { - Reload(this); - } else { - set_member(this, m_Weapon_fInSpecialReload, 0); - emit_sound(pPlayer, CHAN_ITEM, szPumpSound, VOL_NORM, ATTN_NORM, 0, PITCH_NORM); - PlayWeaponAnim(this, iReloadEndAnim, flReloadEndDuration); - } - } else { - PlayWeaponAnim(this, iAnim, flDuration); - } - } - - return true; -} - -bool:DefaultDeploy(this, const szViewModel[], const szWeaponModel[], iAnim, const szAnimExt[]) { - // if (!CanDeploy(this)) { - // return false; - // } - - // new CW:iHandler = GetHandlerByEntity(this); - new pPlayer = GetPlayer(this); - set_pev(pPlayer, pev_viewmodel2, szViewModel); - set_pev(pPlayer, pev_weaponmodel2, szWeaponModel); - - // strcpy( m_pPlayer->m_szAnimExtention, szAnimExt ); - SendWeaponAnim(this, iAnim); - - if (szAnimExt[0] != '^0') { - set_member(pPlayer, m_szAnimExtention, szAnimExt); - } - - set_member(this, m_Weapon_iShotsFired, 0); - set_member(this, m_Weapon_flTimeWeaponIdle, 1.0); - set_member(this, m_Weapon_flLastFireTime, 0.0); - set_member(this, m_Weapon_flDecreaseShotsFired, get_gametime()); - - set_member(pPlayer, m_flNextAttack, 0.5); - set_member(pPlayer, m_iFOV, DEFAULT_FOV); - set_member(pPlayer, m_iLastZoom, DEFAULT_FOV); - set_member(pPlayer, m_bResumeZoom, 0); - set_pev(pPlayer, pev_fov, float(DEFAULT_FOV)); - - return true; -} - -bool:DefaultShot(this, Float:flDamage, Float:flRangeModifier, Float:flRate, Float:flSpread[3], iShots, Float:flDistance) { - new iClip = get_member(this, m_Weapon_iClip); - if (iClip <= 0) { - return false; - } - - new pPlayer = GetPlayer(this); - - static Float:vecDirShooting[3]; - MakeAimDir(pPlayer, 1.0, vecDirShooting); - - static Float:vecSrc[3]; - ExecuteHam(Ham_Player_GetGunPosition, pPlayer, vecSrc); - - static Float:vecOut[3]; - FireBulletsPlayer(this, iShots, vecSrc, vecDirShooting, flSpread, flDistance, flDamage, flRangeModifier, pPlayer, vecOut); - - set_member(this, m_Weapon_iClip, --iClip); - - set_member(this, m_Weapon_flNextPrimaryAttack, flRate); - set_member(this, m_Weapon_flNextSecondaryAttack, flRate); - - new iShotsFired = get_member(this, m_Weapon_iShotsFired); - set_member(this, m_Weapon_iShotsFired, ++iShotsFired); - - rg_set_animation(pPlayer, PLAYER_ATTACK1); - - return true; -} - -bool:DefaultShotgunShot(this, Float:flDamage, Float:flRangeModifier, Float:flRate, Float:flPumpDelay, Float:flSpread[3], iShots, Float:flDistance) { - new iClip = get_member(this, m_Weapon_iClip); - if (iClip <= 0) { - Reload(this); - if (iClip == 0) { - // PlayEmptySound(); - } - - return false; - } - - // m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; - // m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; - - // m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; - - if (!DefaultShot(this, flDamage, flRangeModifier, flRate, flSpread, iShots, flDistance)) { - return false; - } - - set_member(this, m_Weapon_fInSpecialReload, 0); - - if (iClip != 0) { - set_member(this, m_Weapon_flNextReload, get_gametime() + flPumpDelay); - } - - return true; -} - -DefaultSwing(this, Float:flDamage, Float:flRate, Float:flDistance) { - new CW:iHandler = GetHandlerByEntity(this); - if (iHandler == CW_INVALID_HANDLER) { - return -1; - } - - new pPlayer = GetPlayer(this); - - static Float:vecSrc[3]; - ExecuteHam(Ham_Player_GetGunPosition, pPlayer, vecSrc); - - static Float:vecEnd[3]; - MakeAimDir(pPlayer, flDistance, vecEnd); - xs_vec_add(vecSrc, vecEnd, vecEnd); - - new pTr = create_tr2(); - engfunc(EngFunc_TraceLine, vecSrc, vecEnd, DONT_IGNORE_MONSTERS, this, pTr); - - new Float:flFraction; - get_tr2(pTr, TR_flFraction, flFraction); - - if (flFraction >= 1.0) { - engfunc(EngFunc_TraceHull, vecSrc, vecEnd, DONT_IGNORE_MONSTERS, HULL_HEAD, this, pTr); - get_tr2(pTr, TR_flFraction, flFraction); - - if (flFraction < 1.0) { - // Calculate the point of interANCHOR of the line (or hull) and the object we hit - // This is and approximation of the "best" interANCHOR - new pHit = get_tr2(pTr, TR_pHit); - if (pHit == -1 || ExecuteHamB(Ham_IsBSPModel, pHit)) { - FindHullIntersection(vecSrc, pTr, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, this); - } - - get_tr2(pTr, TR_vecEndPos, vecEnd); // This is the point on the actual surface (the hull could have hit space) - get_tr2(pTr, TR_flFraction, flFraction); - } - } - - new iShotsFired = get_member(this, m_Weapon_iShotsFired); - set_member(this, m_Weapon_iShotsFired, iShotsFired + 1); - - set_member(this, m_Weapon_flNextPrimaryAttack, flRate); - - rg_set_animation(pPlayer, PLAYER_ATTACK1); - - if (flFraction >= 1.0) { - free_tr2(pTr); - return -1; - } - - new pHit = get_tr2(pTr, TR_pHit); - if (pHit < 0) { - set_tr2(pTr, TR_pHit, 0); - pHit = 0; - } - - - // if (get_member(this, m_Weapon_flNextPrimaryAttack) + 1.0 < 0.0) { - // first swing does full damage - static Float:vecDir[3]; - xs_vec_sub(vecSrc, vecEnd, vecDir); - xs_vec_normalize(vecDir, vecDir); - - rg_multidmg_clear(); - ExecuteHamB(Ham_TraceAttack, pHit, pPlayer, flDamage, vecDir, pTr, DMG_CLUB); - rg_multidmg_apply(pPlayer, pPlayer); - // } - - - set_pev(this, pev_iuser1, pTr); - SetThink(this, "Smack"); - set_pev(this, pev_nextthink, get_gametime() + (flRate * 0.5)); - - return pHit; -} - -// ANCHOR: Weapon Methods - -CW:RegisterWeapon(iPluginId, const szName[], iWeaponId, iClipSize, iPrimaryAmmoType, iPrimaryAmmoMaxAmount, iSecondaryAmmoType, iSecondaryAmmoMaxAmount, iSlotId, iPosition, iWeaponFlags, const szIcon[], CW_Flags:iFlags) { - new CW:iHandler = CreateWeaponData(szName); - SetData(iHandler, CW_Data_PluginId, iPluginId); - SetStringData(iHandler, CW_Data_Name, szName); - SetData(iHandler, CW_Data_Id, iWeaponId); - SetData(iHandler, CW_Data_ClipSize, iClipSize); - SetData(iHandler, CW_Data_PrimaryAmmoType, iPrimaryAmmoType); - SetData(iHandler, CW_Data_PrimaryAmmoMaxAmount, iPrimaryAmmoMaxAmount); - SetData(iHandler, CW_Data_SecondaryAmmoType, iSecondaryAmmoType); - SetData(iHandler, CW_Data_SecondaryAmmoMaxAmount, iSecondaryAmmoMaxAmount); - SetData(iHandler, CW_Data_SlotId, iSlotId); - SetData(iHandler, CW_Data_Position, iPosition); - SetData(iHandler, CW_Data_WeaponFlags, iWeaponFlags); - SetStringData(iHandler, CW_Data_Icon, szIcon); - SetData(iHandler, CW_Data_Flags, iFlags); - - if (!g_bPrecache && !g_bWeaponHooks[iWeaponId]) { // we are not able to get weapon name in precache state - RegisterWeaponHooks(iWeaponId); - } - - register_clcmd(szName, "OnWeaponClCmd"); - - return iHandler; -} - -CW:GetHandler(const szName[]) { - new CW:iHandler; - if (!TrieGetCell(g_rgWeaponsMap, szName, iHandler)) { - return CW_INVALID_HANDLER; - } - - return iHandler; -} - -CW:GetHandlerByEntity(pEntity) { - new iToken = pev(pEntity, pev_impulse); - - if (iToken >= TOKEN && iToken < TOKEN + g_iWeaponCount) { - return CW:(iToken - TOKEN); - } - - return CW_INVALID_HANDLER; -} - -SpawnWeapon(CW:iHandler) { - new iWeaponId = GetData(iHandler, CW_Data_Id); - - new pEntity = engfunc(EngFunc_CreateNamedEntity, g_iszWeaponNames[iWeaponId]); - if (!pEntity) { - return 0; - } - - set_pev(pEntity, pev_impulse, TOKEN + _:iHandler); - dllfunc(DLLFunc_Spawn, pEntity); - - new iPrimaryAmmoType = GetData(iHandler, CW_Data_PrimaryAmmoType); - - set_member(pEntity, m_Weapon_iClip, GetData(iHandler, CW_Data_ClipSize)); - set_member(pEntity, m_Weapon_iPrimaryAmmoType, iPrimaryAmmoType); - set_member(pEntity, m_Weapon_iDefaultAmmo, 0); - // set_member(pEntity, m_Weapon_iShell, 0); - // set_member(pEntity, m_Weapon_bDelayFire, true); - // set_member(pEntity, m_Weapon_fFireOnEmpty, true); - - ExecuteBindedFunction(CWB_Spawn, pEntity); - - return pEntity; -} - -SpawnWeaponBox(CW:iHandler) { - new pItem = SpawnWeapon(iHandler); - if (!pItem) { - return 0; - } - - set_pev(pItem, pev_spawnflags, pev(pItem, pev_spawnflags) | SF_NORESPAWN); - set_pev(pItem, pev_effects, EF_NODRAW); - set_pev(pItem, pev_movetype, MOVETYPE_NONE); - set_pev(pItem, pev_solid, SOLID_NOT); - set_pev(pItem, pev_model, 0); - set_pev(pItem, pev_modelindex, 0); - - new pWeaponBox = engfunc(EngFunc_CreateNamedEntity, g_iszWeaponBox); - if (!pWeaponBox) { - set_pev(pItem, pev_flags, pev(pItem, pev_flags) | FL_KILLME); - dllfunc(DLLFunc_Think, pItem); - return 0; - } - - dllfunc(DLLFunc_Spawn, pWeaponBox); - set_pev(pItem, pev_owner, pWeaponBox); - - new iSlot = GetData(iHandler, CW_Data_SlotId); - set_member(pWeaponBox, m_WeaponBox_rgpPlayerItems, pItem, iSlot + 1); - - dllfunc(DLLFunc_Spawn, pWeaponBox); - - // engfunc(EngFunc_SetSize, pWeaponBox, {-8.0, -8.0, 0.0}, {8.0, 8.0, 4.0}); - - return pWeaponBox; -} - -// ANCHOR: Player Methods - -GiveWeapon(pPlayer, CW:iHandler) { - new pWeapon = SpawnWeapon(iHandler); - if (!pWeapon) { - return; - } - - if (ExecuteHamB(Ham_AddPlayerItem, pPlayer, pWeapon)) { - ExecuteHamB(Ham_Item_AttachToPlayer, pWeapon, pPlayer); - emit_sound(pPlayer, CHAN_ITEM, "items/gunpickup2.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM); - } - - new CW_Flags:iFlags = GetData(iHandler, CW_Data_Flags); - - if (~iFlags & CWF_NotRefillable) { - new iClipSize = GetData(iHandler, CW_Data_ClipSize); - new iPrimaryAmmoIndex = GetData(iHandler, CW_Data_PrimaryAmmoType); - if (iClipSize == WEAPON_NOCLIP && iPrimaryAmmoIndex != -1) { - set_member(pPlayer, m_rgAmmo, get_member(pPlayer, m_rgAmmo, iPrimaryAmmoIndex) + 1, iPrimaryAmmoIndex); - } - } -} - -bool:HasWeapon(pPlayer, CW:iHandler) { - new iSlot = GetData(iHandler, CW_Data_SlotId); - - new pItem = get_member(pPlayer, m_rgpPlayerItems, iSlot); - while (pItem != -1) { - new pNextItem = get_member(pItem, m_pNext); - - if (CW_GetHandlerByEntity(pItem) == iHandler) { - return true; - } - - pItem = pNextItem; - } - - return false; -} - -UpdateWeaponList(pPlayer, CW:iHandler) { - if (is_user_bot(pPlayer)) { - return; - } - - new iWeaponId = GetData(iHandler, CW_Data_Id); - - static szName[64]; - GetStringData(iHandler, CW_Data_Name, szName, charsmax(szName)); - - new iPrimaryAmmoType = GetData(iHandler, CW_Data_PrimaryAmmoType); - new iPrimaryAmmoMaxCount = GetData(iHandler, CW_Data_PrimaryAmmoMaxAmount); - new iSecondaryAmmoType = GetData(iHandler, CW_Data_SecondaryAmmoType); - new iSecondaryAmmoMaxCount = GetData(iHandler, CW_Data_SecondaryAmmoMaxAmount); - new iSlotId = GetData(iHandler, CW_Data_SlotId); - new iPosition = GetData(iHandler, CW_Data_Position); - new iWeaponFlags = GetData(iHandler, CW_Data_WeaponFlags); - - emessage_begin(MSG_ONE, gmsgWeaponList, _, pPlayer); - ewrite_string(szName); - ewrite_byte(iPrimaryAmmoType); - ewrite_byte(iPrimaryAmmoMaxCount); - ewrite_byte(iSecondaryAmmoType); - ewrite_byte(iSecondaryAmmoMaxCount); - ewrite_byte(iSlotId); - ewrite_byte(iPosition); - ewrite_byte(iWeaponId); - ewrite_byte(iWeaponFlags); - emessage_end(); -} - -ResetWeaponList(pPlayer, iWeaponId) { - if (is_user_bot(pPlayer)) { - return; - } - - message_begin(MSG_ONE, gmsgWeaponList, _, pPlayer); - write_string(g_weaponListDefaults[iWeaponId][WL_WeaponName]); - write_byte(g_weaponListDefaults[iWeaponId][WL_PrimaryAmmoType]); - write_byte(g_weaponListDefaults[iWeaponId][WL_PrimaryAmmoMaxAmount]); - write_byte(g_weaponListDefaults[iWeaponId][WL_SecondaryAmmoType]); - write_byte(g_weaponListDefaults[iWeaponId][WL_SecondaryAmmoMaxAmount]); - write_byte(g_weaponListDefaults[iWeaponId][WL_SlotId]); - write_byte(g_weaponListDefaults[iWeaponId][WL_NumberInSlot]); - write_byte(g_weaponListDefaults[iWeaponId][WL_WeaponId]); - write_byte(g_weaponListDefaults[iWeaponId][WL_Flags]); - message_end(); -} - -SetWeaponPrediction(pPlayer, bool:bValue) { - if (is_user_bot(pPlayer)) { - return; - } - - new pszInfoBuffer = engfunc(EngFunc_GetInfoKeyBuffer, pPlayer); - engfunc(EngFunc_SetClientKeyValue, pPlayer, pszInfoBuffer, "cl_lw", bValue ? "1" : "0"); - - for (new pSpectator = 1; pSpectator <= MaxClients; pSpectator++) { - if (pSpectator == pPlayer) { - continue; - } - - if (!is_user_connected(pSpectator)) { - continue; - } - - if (pev(pSpectator, pev_iuser1) != OBS_IN_EYE) { - continue; - } - - if (pev(pSpectator, pev_iuser2) != pPlayer) { - continue; - } - - SetWeaponPrediction(pSpectator, false); - } -} - -RemovePlayerItem(pItem) { - new pPlayer = GetPlayer(pItem); - - new iWeaponId = get_member(pItem, m_iId); - - if (pItem == get_member(pPlayer, m_pActiveItem)) { - ExecuteHamB(Ham_Weapon_RetireWeapon, pItem); - } - - ExecuteHamB(Ham_RemovePlayerItem, pPlayer, pItem); - ExecuteHamB(Ham_Item_Kill, pItem); - set_pev(pPlayer, pev_weapons, pev(pPlayer, pev_weapons) & ~(1< 1.0) { - vecMidUp[2] = flMinZ + (flDiff / 2.0); - - if (PointContents(vecMidUp) == CONTENTS_WATER) { - flMinZ = vecMidUp[2]; - } else { - flMaxZ = vecMidUp[2]; - } - - flDiff = flMaxZ - flMinZ; - } - - return vecMidUp[2]; -} - -FindHullIntersection(const Float:vecSrc[3], &pTr, const Float:vecMins[3], const Float:vecMaxs[3], pEntity) { - new Float:flDistance = 8192.0; - - static Float:rgvecMinsMaxs[2][3]; - for (new i = 0; i < 3; ++i) { - rgvecMinsMaxs[0][i] = vecMins[i]; - rgvecMinsMaxs[1][i] = vecMaxs[i]; - } - - static Float:vecHullEnd[3]; - get_tr2(pTr, TR_vecEndPos, vecHullEnd); - - for (new i = 0; i < 3; ++i) { - vecHullEnd[i] = vecSrc[i] + ((vecHullEnd[i] - vecSrc[i]) * 2.0); - } - - new tmpTrace = create_tr2(); - engfunc(EngFunc_TraceLine, vecSrc, vecHullEnd, DONT_IGNORE_MONSTERS, pEntity, tmpTrace); - - new Float:flFraction; - get_tr2(tmpTrace, TR_flFraction, flFraction); - - if (flFraction < 1.0) { - free_tr2(pTr); - pTr = tmpTrace; - return; - } - - static Float:vecEnd[3]; - for (new i = 0; i < 2; i++) { - for (new j = 0; j < 2; j++) { - for (new k = 0; k < 2; k++) { - vecEnd[0] = vecHullEnd[0] + rgvecMinsMaxs[i][0]; - vecEnd[1] = vecHullEnd[1] + rgvecMinsMaxs[j][1]; - vecEnd[2] = vecHullEnd[2] + rgvecMinsMaxs[k][2]; - - engfunc(EngFunc_TraceLine, vecSrc, vecEnd, DONT_IGNORE_MONSTERS, pEntity, tmpTrace); - get_tr2(tmpTrace, TR_flFraction, flFraction); - - new Float:vecEndPos[3]; - get_tr2(tmpTrace, TR_vecEndPos, vecEndPos); - - if (flFraction < 1.0) { - new Float:flThisDistance = get_distance_f(vecEndPos, vecSrc); - if (flThisDistance < flDistance) { - free_tr2(pTr); - pTr = tmpTrace; - flDistance = flThisDistance; - } - } - } - } - } -} - -_RadiusDamage(const Float:vecOrigin[3], iInflictor, pAttacker, Float:flDamage, Float:flRadius, iClassIgnore, iDamageBits) { - #pragma unused iClassIgnore - - static Float:vecSrc[3]; - xs_vec_copy(vecOrigin, vecSrc); - - new Float:flFalloff = flRadius ? (flDamage / flRadius) : 1.0; - new bool:bInWater = (PointContents(vecSrc) == CONTENTS_WATER); - - vecSrc[2] += 1.0; // in case grenade is lying on the ground - - if (!pAttacker) { - pAttacker = iInflictor; - } - - new pTr = create_tr2(); - - new pEntity; - new pPrevEntity; - while ((pEntity = engfunc(EngFunc_FindEntityInSphere, pEntity, vecSrc, flRadius)) != 0) { - if (pPrevEntity >= pEntity) { - break; - } - - pPrevEntity = pEntity; - - if (!pev_valid(pEntity)) { - continue; - } - - if (ExecuteHam(Ham_IsPlayer, pEntity) && !rg_is_player_can_takedamage(pEntity, pAttacker)) { - continue; - } - - if (pev(pEntity, pev_takedamage) == DAMAGE_NO) { - continue; - } - - static szClassname[32]; - pev(pEntity, pev_classname, szClassname, charsmax(szClassname)); - - // UNDONE: this should check a damage mask, not an ignore - // if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore ) {// houndeyes don't hurt other houndeyes with their attack - // continue; - // } - - new iWaterLevel = pev(pEntity, pev_waterlevel); - - if (bInWater && iWaterLevel == 0) { - continue; - } - - if (!bInWater && iWaterLevel == 3) { - continue; - } - - static Float:vecSpot[3]; - ExecuteHamB(Ham_BodyTarget, pEntity, vecSrc, vecSpot); - engfunc(EngFunc_TraceLine, vecSrc, vecSpot, IGNORE_MONSTERS, iInflictor, pTr); - - static Float:flFraction; - get_tr2(pTr, TR_flFraction, flFraction); - - if (flFraction != 1.0 && get_tr2(pTr, TR_pHit) != pEntity) { - continue; - } - - if (get_tr2(pTr, TR_StartSolid)) { - set_tr2(pTr, TR_vecEndPos, vecSrc); - set_tr2(pTr, TR_flFraction, 0.0); - flFraction = 0.0; - } - - static Float:vecEnd[3]; - get_tr2(pTr, TR_vecEndPos, vecEnd); - - new Float:flAdjustedDamage = flDamage - (get_distance_f(vecSrc, vecEnd) * flFalloff); - - if (flAdjustedDamage < 0.0) { - flAdjustedDamage = 0.0; - } - - if (flFraction != 1.0) { - static Float:vecDir[3]; - xs_vec_sub(vecEnd, vecSrc, vecDir); - xs_vec_normalize(vecDir, vecDir); - - rg_multidmg_clear(); - ExecuteHamB(Ham_TraceAttack, pEntity, iInflictor, flAdjustedDamage, vecDir, pTr, iDamageBits); - rg_multidmg_apply(iInflictor, pAttacker); - } else { - ExecuteHamB(Ham_TakeDamage, pEntity, iInflictor, pAttacker, flAdjustedDamage, iDamageBits); - } - } - - free_tr2(pTr); -} - -MakeAimDir(pPlayer, Float:flDistance, Float:vecOut[3]) { - static Float:vecAngles[3]; - pev(pPlayer, pev_v_angle, vecAngles); - engfunc(EngFunc_MakeVectors, vecAngles); - - get_global_vector(GL_v_forward, vecOut); - xs_vec_mul_scalar(vecOut, flDistance, vecOut); -} - -GetDecalIndex(pEntity) { - new iDecalIndex = ExecuteHamB(Ham_DamageDecal, pEntity, 0); - if (iDecalIndex < 0) { - return -1; - } - - iDecalIndex = ArrayGetCell(g_irgDecals, iDecalIndex); - - if (iDecalIndex == engfunc(EngFunc_DecalIndex, "{break1") - || iDecalIndex == engfunc(EngFunc_DecalIndex, "{break2") - || iDecalIndex == engfunc(EngFunc_DecalIndex, "{break3")) { - return engfunc(EngFunc_DecalIndex, "{bproof1"); - } - - return iDecalIndex; -} - -// ANCHOR: Storages - -AllocateStrings() { - g_iszWeaponBox = engfunc(EngFunc_AllocString, "weaponbox"); - - for (new iWeaponId = 0; iWeaponId <= CSW_LAST_WEAPON; ++iWeaponId) { - if (g_rgszWeaponNames[iWeaponId][0] == '^0') { - continue; - } - - g_iszWeaponNames[iWeaponId] = engfunc(EngFunc_AllocString, g_rgszWeaponNames[iWeaponId]); - } -} - -InitStorages() { - g_rgWeapons[CW_Data_Name] = ArrayCreate(64, 1); - g_rgWeapons[CW_Data_Icon] = ArrayCreate(16, 1); - - for (new i = 0; i < _:CW_Data; ++i) { - if (!g_rgWeapons[CW_Data:i]) { - g_rgWeapons[CW_Data:i] = ArrayCreate(1, 1); - } - } - - g_rgWeaponsMap = TrieCreate(); - - g_irgDecals = ArrayCreate(); -} - -DestroyStorages() { - for (new CW:iHandler = CW:0; _:iHandler < g_iWeaponCount; ++iHandler) { - DestroyWeaponData(iHandler); - } - - for (new i = 0; i < _:CW_Data; ++i) { - ArrayDestroy(Array:g_rgWeapons[CW_Data:i]); - } - - TrieDestroy(g_rgWeaponsMap); - - ArrayDestroy(g_irgDecals); -} - -// ANCHOR: Weapon Data - -CW:CreateWeaponData(const szName[]) { - new CW:iHandler = CW:g_iWeaponCount; - - for (new iParam = 0; iParam < _:CW_Data; ++iParam) { - ArrayPushCell(Array:g_rgWeapons[CW_Data:iParam], 0); - } - - TrieSetCell(g_rgWeaponsMap, szName, iHandler); - - InitBindings(iHandler); - - g_iWeaponCount++; - - return iHandler; -} - -DestroyWeaponData(CW:iHandler) { - DestroyBindings(iHandler); -} - -any:GetData(CW:iHandler, CW_Data:iParam) { - return ArrayGetCell(Array:g_rgWeapons[iParam], _:iHandler); -} - -GetStringData(CW:iHandler, CW_Data:iParam, szOut[], iLen) { - ArrayGetString(Array:g_rgWeapons[iParam], _:iHandler, szOut, iLen); -} - -SetData(CW:iHandler, CW_Data:iParam, any:value) { - ArraySetCell(Array:g_rgWeapons[iParam], _:iHandler, value); -} - -SetStringData(CW:iHandler, CW_Data:iParam, const szValue[]) { - ArraySetString(Array:g_rgWeapons[iParam], _:iHandler, szValue); -} - -// ANCHOR: Weapon Bindings - -Array:InitBindings(CW:iHandler) { - new Array:irgBindings = ArrayCreate(Function, _:CW_Binding); - for (new i = 0; i < _:CW_Binding; ++i) { - new rgBinding[Function]= {-1, -1}; - ArrayPushArray(irgBindings, rgBinding); - } - - SetData(iHandler, CW_Data_Bindings, irgBindings); -} - -DestroyBindings(CW:iHandler) { - new Array:irgBindings = GetData(iHandler, CW_Data_Bindings); - ArrayDestroy(irgBindings); -} - -Bind(CW:iHandler, iBinding, iPluginId, iFunctionid) { - new rgBinding[Function]; - rgBinding[Function_PluginId] = iPluginId; - rgBinding[Function_FunctionId] = iFunctionid; - - new Array:irgBindings = GetData(iHandler, CW_Data_Bindings); - ArraySetArray(irgBindings, iBinding, rgBinding); -} - -GetBinding(CW:iHandler, CW_Binding:iBinding, &iPluginId, &iFunctionId) { - new Array:iszBindings = GetData(iHandler, CW_Data_Bindings); - - static rgBinding[Function]; - ArrayGetArray(iszBindings, _:iBinding, rgBinding, sizeof(rgBinding)); - - if (rgBinding[Function_PluginId] == -1) { - return false; - } - - if (rgBinding[Function_FunctionId] == -1) { - return false; - } - - iPluginId = rgBinding[Function_PluginId]; - iFunctionId = rgBinding[Function_FunctionId]; - - return true; -} - -any:ExecuteBindedFunction(CW_Binding:iBinding, this, any:...) { - new CW:iHandler = GetHandlerByEntity(this); - - new iPluginId, iFunctionId; - if (!GetBinding(iHandler, iBinding, iPluginId, iFunctionId)) { - return PLUGIN_CONTINUE; - } - - if (callfunc_begin_i(iFunctionId, iPluginId) == 1) { - callfunc_push_int(this); - - if (iBinding == CWB_WeaponBoxModelUpdate) { - new pWeaponBox = getarg(2); - callfunc_push_int(pWeaponBox); - } - - return callfunc_end(); - } - - return PLUGIN_CONTINUE; -} - -// ANCHOR: Weapon Hooks - -InitWeaponHooks() { - for (new CW:iHandler = CW:0; _:iHandler < g_iWeaponCount; ++iHandler) { - new iWeaponId = GetData(iHandler, CW_Data_Id); - if (!g_bWeaponHooks[iWeaponId]) { - RegisterWeaponHooks(iWeaponId); - } - } -} - -RegisterWeaponHooks(iWeaponId) { - new szClassname[32]; - get_weaponname(iWeaponId, szClassname, charsmax(szClassname)); - - RegisterHam(Ham_Item_PostFrame, szClassname, "OnItemPostFrame", .Post = 0); - RegisterHam(Ham_Item_ItemSlot, szClassname, "OnItemSlot", .Post = 0); - RegisterHam(Ham_Item_Holster, szClassname, "OnItemHolster", .Post = 0); - RegisterHam(Ham_Item_Deploy, szClassname, "OnItemDeploy", .Post = 0); - RegisterHam(Ham_CS_Item_GetMaxSpeed, szClassname, "OnCSItemGetMaxSpeed", .Post = 0); - // RegisterHam(Ham_Weapon_PlayEmptySound, szClassname, "OnWeaponPlayEmptySound", .Post = 0); - RegisterHam(Ham_Item_AddToPlayer, szClassname, "OnItemAddToPlayer_Post", .Post = 1); - RegisterHam(Ham_Spawn, szClassname, "OnSpawn_Post", .Post = 1); - RegisterHam(Ham_CS_Item_CanDrop, szClassname, "OnCanDrop"); - // RegisterHam(Ham_Item_GetItemInfo, szClassname, "OnItemGetItemInfo", .Post = 1); - RegisterHam(Ham_Weapon_PrimaryAttack, szClassname, "OnWeaponPrimaryAttack"); - RegisterHam(Ham_Weapon_SecondaryAttack, szClassname, "OnWeaponSecondaryAttack"); - RegisterHam(Ham_Weapon_Reload, szClassname, "OnWeaponReload"); - RegisterHam(Ham_Weapon_WeaponIdle, szClassname, "OnWeaponIdle"); - - g_bWeaponHooks[iWeaponId] = true; -} - -// ANCHOR: Effects - -SparkShower(const Float:vecOrigin[3], const Float:vecAngles[3], iOwner) { - new pSparkShower = engfunc(EngFunc_CreateNamedEntity, engfunc(EngFunc_AllocString, "spark_shower")); - if (!pSparkShower) { - return; - } - - engfunc(EngFunc_SetOrigin, pSparkShower, vecOrigin); - set_pev(pSparkShower, pev_angles, vecAngles); - set_pev(pSparkShower, pev_owner, iOwner); - dllfunc(DLLFunc_Spawn, pSparkShower); -} - -GrenadeExplosion(const Float:vecOrigin[3], Float:flDamage) { - new iModelIndex = PointContents(vecOrigin) != CONTENTS_WATER - ? engfunc(EngFunc_ModelIndex, "sprites/zerogxplode.spr") - : engfunc(EngFunc_ModelIndex, "sprites/WXplo1.spr"); - - new iScale = floatround((flDamage - 50.0) * 0.60); - - if (iScale < 8) { - iScale = 8; - } - - if (iScale > 255) { - iScale = 255; - } - - engfunc(EngFunc_MessageBegin, MSG_PAS, SVC_TEMPENTITY, vecOrigin, 0); - write_byte(TE_EXPLOSION); - engfunc(EngFunc_WriteCoord, vecOrigin[0]); - engfunc(EngFunc_WriteCoord, vecOrigin[1]); - engfunc(EngFunc_WriteCoord, vecOrigin[2]); - write_short(iModelIndex); - write_byte(iScale); - write_byte(15); - write_byte(TE_EXPLFLAG_NONE); - message_end(); -} - -GrenadeSmoke(pGrenade) { - static Float:vecOrigin[3]; - pev(pGrenade, pev_origin, vecOrigin); - - static Float:flDamage; - pev(pGrenade, pev_dmg, flDamage); - - if (PointContents(vecOrigin) == CONTENTS_WATER) { - static Float:vecSize[3] = {64.0, 64.0, 64.0}; - - static Float:vecMins[3]; - xs_vec_sub(vecOrigin, vecSize, vecMins); - - static Float:vecMaxs[3]; - xs_vec_add(vecOrigin, vecSize, vecMaxs); - - Bubbles(vecMins, vecMaxs, 100); - } else { - new iModelIndex = engfunc(EngFunc_ModelIndex, "sprites/steam1.spr"); - - new Float:flRadius = (flDamage - 50.0) * 0.80; - if (flRadius < 8.0) { - flRadius = 9.0; - } - - engfunc(EngFunc_MessageBegin, MSG_PAS, SVC_TEMPENTITY, vecOrigin, 0); - write_byte(TE_SMOKE); - engfunc(EngFunc_WriteCoord, vecOrigin[0]); - engfunc(EngFunc_WriteCoord, vecOrigin[1]); - engfunc(EngFunc_WriteCoord, vecOrigin[2]); - write_short(iModelIndex); - write_byte(floatround(flRadius)); // scale * 10 - write_byte(12); // framerate - message_end(); - } -} - -Bubbles(const Float:vecMins[3], const Float:vecMaxs[3], iCount) { - static Float:vecMid[3]; - for (new i = 0; i < 3; ++i) { - vecMid[i] = (vecMins[i] + vecMaxs[i]) * 0.5; - } - - new Float:flHeight = WaterLevel(vecMid, vecMid[2], vecMid[2] + 1024.0) - vecMins[2]; - new iModelIndex = engfunc(EngFunc_ModelIndex, "sprites/bubble.spr"); - - engfunc(EngFunc_MessageBegin, MSG_PAS, SVC_TEMPENTITY, vecMid, 0); - write_byte(TE_BUBBLES); - engfunc(EngFunc_WriteCoord, vecMins[0]); - engfunc(EngFunc_WriteCoord, vecMins[1]); - engfunc(EngFunc_WriteCoord, vecMins[2]); - engfunc(EngFunc_WriteCoord, vecMaxs[0]); - engfunc(EngFunc_WriteCoord, vecMaxs[1]); - engfunc(EngFunc_WriteCoord, vecMaxs[2]); - engfunc(EngFunc_WriteCoord, flHeight); // height - write_short(iModelIndex); - write_byte(iCount); // count - write_coord(8); // speed - message_end(); -} - -DecalTrace(pTr, iDecal) { - if (iDecal < 0) { - return; - } - - new Float:flFraction; - get_tr2(pTr, TR_flFraction, flFraction); - - if (flFraction == 1.0) { - return; - } - - // Only decal BSP models - new pHit = get_tr2(pTr, TR_pHit); - if (pHit != -1) { - if (pHit && !ExecuteHam(Ham_IsBSPModel, pHit)) { - return; - } - } else { - pHit = 0; - } - - new iMessage = TE_DECAL; - if (pHit != 0) { - if (iDecal > 255) { - iMessage = TE_DECALHIGH; - iDecal -= 256; - } - } else { - iMessage = TE_WORLDDECAL; - if (iDecal > 255) { - iMessage = TE_WORLDDECALHIGH; - iDecal -= 256; - } - } - - static Float:vecEndPos[3]; - get_tr2(pTr, TR_vecEndPos, vecEndPos); - - engfunc(EngFunc_MessageBegin, MSG_BROADCAST, SVC_TEMPENTITY, vecEndPos, 0); - write_byte(iMessage); - engfunc(EngFunc_WriteCoord, vecEndPos[0]); - engfunc(EngFunc_WriteCoord, vecEndPos[1]); - engfunc(EngFunc_WriteCoord, vecEndPos[2]); - write_byte(iDecal); - if (pHit) { - write_short(pHit); - } - message_end(); -} - -BulletSmoke(pTr) { - static Float:vecSrc[3]; - get_tr2(pTr, TR_vecEndPos, vecSrc); - - static Float:vecEnd[3]; - get_tr2(pTr, TR_vecPlaneNormal, vecEnd); - xs_vec_mul_scalar(vecEnd, 2.5, vecEnd); - xs_vec_add(vecSrc, vecEnd, vecEnd); - - static iModelIndex; - if (!iModelIndex) { - iModelIndex = engfunc(EngFunc_ModelIndex, WALL_PUFF_SPRITE); - } - - engfunc(EngFunc_MessageBegin, MSG_PAS, SVC_TEMPENTITY, vecEnd, 0); - write_byte(TE_EXPLOSION); - engfunc(EngFunc_WriteCoord, vecEnd[0]); - engfunc(EngFunc_WriteCoord, vecEnd[1]); - engfunc(EngFunc_WriteCoord, vecEnd[2] - 10.0); - write_short(iModelIndex); - write_byte(5); - write_byte(50); - write_byte(TE_EXPLFLAG_NODLIGHTS | TE_EXPLFLAG_NOSOUND | TE_EXPLFLAG_NOPARTICLES); - message_end(); -} - -MakeDecal(pTr, pEntity, iDecalIndex, bool:bGunshotDecal = true) { - static vecOrigin[3]; - get_tr2(pTr, TR_vecEndPos, vecOrigin); - - new pHit; - get_tr2(pTr, TR_pHit, pHit); - - if(pHit) { - emessage_begin(MSG_BROADCAST, SVC_TEMPENTITY); - ewrite_byte(TE_DECAL); - engfunc(EngFunc_WriteCoord, vecOrigin[0]); - engfunc(EngFunc_WriteCoord, vecOrigin[1]); - engfunc(EngFunc_WriteCoord, vecOrigin[2]); - ewrite_byte(iDecalIndex); - ewrite_short(pHit); - emessage_end(); - } else { - emessage_begin(MSG_BROADCAST, SVC_TEMPENTITY); - ewrite_byte(TE_WORLDDECAL); - engfunc(EngFunc_WriteCoord, vecOrigin[0]); - engfunc(EngFunc_WriteCoord, vecOrigin[1]); - engfunc(EngFunc_WriteCoord, vecOrigin[2]); - ewrite_byte(iDecalIndex); - emessage_end(); - } - - if (bGunshotDecal) { - message_begin(MSG_BROADCAST, SVC_TEMPENTITY); - write_byte(TE_GUNSHOTDECAL); - engfunc(EngFunc_WriteCoord, vecOrigin[0]); - engfunc(EngFunc_WriteCoord, vecOrigin[1]); - engfunc(EngFunc_WriteCoord, vecOrigin[2]); - write_short(pEntity); - write_byte(iDecalIndex); - message_end(); - } -} - -BubbleTrail(const Float:from[3], const Float:to[3], count) { - new Float:flHeight = WaterLevel(from, from[2], from[2] + 256); - flHeight = flHeight - from[2]; - - if (flHeight < 8) { - flHeight = WaterLevel(to, to[2], to[2] + 256.0); - flHeight = flHeight - to[2]; - if (flHeight < 8) { - return; - } - - // UNDONE: do a ploink sound - flHeight = flHeight + to[2] - from[2]; - } - - if (count > 255) { - count = 255; - } - - static g_sModelIndexBubbles; - if (!g_sModelIndexBubbles) { - g_sModelIndexBubbles = engfunc(EngFunc_ModelIndex, "sprites/bubble.spr"); - } - - engfunc(EngFunc_MessageBegin, MSG_BROADCAST, SVC_TEMPENTITY, from, 0); - write_byte(TE_BUBBLETRAIL); - engfunc(EngFunc_WriteCoord, from[0]); - engfunc(EngFunc_WriteCoord, from[1]); - engfunc(EngFunc_WriteCoord, from[2]); - engfunc(EngFunc_WriteCoord, to[0]); - engfunc(EngFunc_WriteCoord, to[1]); - engfunc(EngFunc_WriteCoord, to[2]); - engfunc(EngFunc_WriteCoord, flHeight); - write_short(g_sModelIndexBubbles); - write_byte(count); - write_coord(8); - message_end(); -} - -ExplosionDecalTrace(pTr) { - switch (random(3)) { - case 0: { - DecalTrace(pTr, engfunc(EngFunc_DecalIndex, "{scorch1")); - } - case 1: { - DecalTrace(pTr, engfunc(EngFunc_DecalIndex, "{scorch2")); - } - case 2: { - DecalTrace(pTr, engfunc(EngFunc_DecalIndex, "{scorch3")); - } - } -} - -DebrisSound(pEntity) { - switch (random(3)) { - case 0: { - emit_sound(pEntity, CHAN_VOICE, "weapons/debris1.wav", 0.55, ATTN_NORM, 0, PITCH_NORM); - } - case 1: { - emit_sound(pEntity, CHAN_VOICE, "weapons/debris2.wav", 0.55, ATTN_NORM, 0, PITCH_NORM); - } - case 2: { - emit_sound(pEntity, CHAN_VOICE, "weapons/debris3.wav", 0.55, ATTN_NORM, 0, PITCH_NORM); - } - } -} - -bool:EjectWeaponBrass(this, iModelIndex, iSoundType) { - new pPlayer = GetPlayer(this); - - if (!iModelIndex) { - return false; - } - - static Float:vecViewOfs[3]; - pev(pPlayer, pev_view_ofs, vecViewOfs); - - static Float:vecAngles[3]; - pev(pPlayer, pev_angles, vecAngles); - - static Float:vecUp[3]; - angle_vector(vecAngles, ANGLEVECTOR_UP, vecUp); - - static Float:vecForward[3]; - angle_vector(vecAngles, ANGLEVECTOR_FORWARD, vecForward); - - static Float:vecRight[3]; - angle_vector(vecAngles, ANGLEVECTOR_RIGHT, vecRight); - - static Float:vecOrigin[3]; - pev(pPlayer, pev_origin, vecOrigin); - - for (new i = 0; i < 3; ++i) { - vecOrigin[i] = vecOrigin[i] + vecViewOfs[i] + (vecUp[i] * -9.0) + (vecForward[i] * 16.0); - } - - static Float:vecVelocity[3]; - pev(pPlayer, pev_velocity, vecVelocity); - - for (new i = 0; i < 3; ++i) { - vecVelocity[i] = vecVelocity[i] + (vecRight[i] * random_float(50.0, 70.0)) + (vecUp[i] * random_float(100.0, 150.0)) + (vecForward[i] * 25.0); - } - - engfunc(EngFunc_MessageBegin, MSG_PVS, SVC_TEMPENTITY, vecOrigin, 0); - write_byte(TE_MODEL); - engfunc(EngFunc_WriteCoord, vecOrigin[0]); - engfunc(EngFunc_WriteCoord, vecOrigin[1]); - engfunc(EngFunc_WriteCoord, vecOrigin[2]); - engfunc(EngFunc_WriteCoord, vecVelocity[0]); - engfunc(EngFunc_WriteCoord, vecVelocity[1]); - engfunc(EngFunc_WriteCoord, vecVelocity[2]); - write_angle(floatround(vecAngles[1])); - write_short(iModelIndex); - write_byte(iSoundType); - write_byte(25); - message_end(); - - return true; -} - -// ANCHOR: Random - -new const seed_table[256] = { - 28985, 27138, 26457, 9451, 17764, 10909, 28790, 8716, 6361, 4853, 17798, 21977, 19643, 20662, 10834, 20103, - 27067, 28634, 18623, 25849, 8576, 26234, 23887, 18228, 32587, 4836, 3306, 1811, 3035, 24559, 18399, 315, - 26766, 907, 24102, 12370, 9674, 2972, 10472, 16492, 22683, 11529, 27968, 30406, 13213, 2319, 23620, 16823, - 10013, 23772, 21567, 1251, 19579, 20313, 18241, 30130, 8402, 20807, 27354, 7169, 21211, 17293, 5410, 19223, - 10255, 22480, 27388, 9946, 15628, 24389, 17308, 2370, 9530, 31683, 25927, 23567, 11694, 26397, 32602, 15031, - 18255, 17582, 1422, 28835, 23607, 12597, 20602, 10138, 5212, 1252, 10074, 23166, 19823, 31667, 5902, 24630, - 18948, 14330, 14950, 8939, 23540, 21311, 22428, 22391, 3583, 29004, 30498, 18714, 4278, 2437, 22430, 3439, - 28313, 23161, 25396, 13471, 19324, 15287, 2563, 18901, 13103, 16867, 9714, 14322, 15197, 26889, 19372, 26241, - 31925, 14640, 11497, 8941, 10056, 6451, 28656, 10737, 13874, 17356, 8281, 25937, 1661, 4850, 7448, 12744, - 21826, 5477, 10167, 16705, 26897, 8839, 30947, 27978, 27283, 24685, 32298, 3525, 12398, 28726, 9475, 10208, - 617, 13467, 22287, 2376, 6097, 26312, 2974, 9114, 21787, 28010, 4725, 15387, 3274, 10762, 31695, 17320, - 18324, 12441, 16801, 27376, 22464, 7500, 5666, 18144, 15314, 31914, 31627, 6495, 5226, 31203, 2331, 4668, - 12650, 18275, 351, 7268, 31319, 30119, 7600, 2905, 13826, 11343, 13053, 15583, 30055, 31093, 5067, 761, - 9685, 11070, 21369, 27155, 3663, 26542, 20169, 12161, 15411, 30401, 7580, 31784, 8985, 29367, 20989, 14203, - 29694, 21167, 10337, 1706, 28578, 887, 3373, 19477, 14382, 675, 7033, 15111, 26138, 12252, 30996, 21409, - 25678, 18555, 13256, 23316, 22407, 16727, 991, 9236, 5373, 29402, 6117, 15241, 27715, 19291, 19888, 19847 -}; - -Float:SharedRandomFloat(seed, Float:low, Float:high) { - new Float:range = high - low; - if (!range) { - return low; - } - - new glSeed = U_Srand(seed + floatround(low) + floatround(high)); - U_Random(glSeed); - U_Random(glSeed); - - new tensixrand = U_Random(glSeed) & 65535; - new Float:offset = float(tensixrand) / 65536.0; - - return (low + offset * range ); -} - -U_Random(&glSeed) { - glSeed *= 69069; - glSeed += seed_table[glSeed & 0xff]; - - return (++glSeed & 0x0fffffff); -} - -U_Srand(seed) { - return seed_table[seed & 0xff]; -} - -// FireEvent(pTr, const szSnd[], const szShellModel[]) { -// static Float:flFraction; -// get_tr2(pTr, TR_flFraction, flFraction); - -// new pHit = get_tr2(pTr, TR_pHit); - -// if (flFraction != 1.0) { -// // Native_PlaySoundAtPosition( $origin = $trace_endpos, $sound = weapons/bullet_hit1.wav ); -// // Native_ImpactParticles( $origin = $trace_endpos ); -// // Native_PlaceDecal( $origin = $trace_endpos, $decal = "{shot2", $trace_entity ); - -// new iDecalIndex = random_num(get_decal_index("{shot1"), get_decal_index("{shot5") + 1); -// MakeDecal(pTr, pHit, iDecalIndex); -// } -// } - -// BeamPoints(const Float:vecStart[3], const Float:vecEnd[3], const color[3]) { -// message_begin(MSG_BROADCAST ,SVC_TEMPENTITY); -// write_byte(TE_BEAMPOINTS); -// write_coord(floatround(vecStart[0])); // start position -// write_coord(floatround(vecStart[1])); -// write_coord(floatround(vecStart[2])); -// write_coord(floatround(vecEnd[0])); // end position -// write_coord(floatround(vecEnd[1])); -// write_coord(floatround(vecEnd[2])); -// write_short(engfunc(EngFunc_ModelIndex, "sprites/laserbeam.spr")); // sprite index -// write_byte(0); // starting frame -// write_byte(10); // frame rate in 0.1's -// write_byte(30); // life in 0.1's -// write_byte(2); // line width in 0.1's -// write_byte(1); // noise amplitude in 0.01's -// write_byte(color[0]); // Red -// write_byte(color[1]); // Green -// write_byte(color[2]); // Blue -// write_byte(127); // brightness -// write_byte(10); // scroll speed in 0.1's -// message_end(); -// } diff --git a/api_player_camera.sma b/api_player_camera.sma deleted file mode 100644 index 11fe8fe..0000000 --- a/api_player_camera.sma +++ /dev/null @@ -1,227 +0,0 @@ -#pragma semicolon 1 - -#include -#include -#include -#include - -new g_rgpPlayerCamera[MAX_PLAYERS]; -new Float:g_rgflPlayerCameraDistance[MAX_PLAYERS]; -new Float:g_rgflPlayerCameraAngles[MAX_PLAYERS][3]; -new Float:g_rgflPlayerCamerOffset[MAX_PLAYERS][3]; -new Float:g_rgflPlayerCameraThinkDelay[MAX_PLAYERS]; -new Float:g_rgflPlayerCameraNextThink[MAX_PLAYERS]; - -new g_iszTriggerCameraClassname; - -new g_iCameraModelIndex; - -public plugin_precache() { - g_iszTriggerCameraClassname = engfunc(EngFunc_AllocString, "trigger_camera"); - - g_iCameraModelIndex = precache_model("models/rpgrocket.mdl"); -} - -public plugin_init() { - register_plugin("[API] Player Camerea", "1.0.0", "Hedgehog Fog"); - - RegisterHamPlayer(Ham_Spawn, "HamHook_Player_Spawn_Post", .Post = 1); - RegisterHamPlayer(Ham_Player_PreThink, "HamHook_Player_PreThink_Post", .Post = 1); -} - -public plugin_natives() { - register_library("api_player_camera"); - register_native("PlayerCamera_Activate", "Native_Activate"); - register_native("PlayerCamera_Deactivate", "Native_Deactivate"); - register_native("PlayerCamera_IsActive", "Native_IsActive"); - register_native("PlayerCamera_SetOffset", "Native_SetOffset"); - register_native("PlayerCamera_SetAngles", "Native_SetAngles"); - register_native("PlayerCamera_SetDistance", "Native_SetDistance"); - register_native("PlayerCamera_SetThinkDelay", "Native_SetThinkDelay"); -} - -public bool:Native_Activate(iPluginId, iArgc) { - new pPlayer = get_param(1); - - ActivatePlayerCamera(pPlayer); -} - -public Native_Deactivate(iPluginId, iArgc) { - new pPlayer = get_param(1); - - DeactivatePlayerCamera(pPlayer); -} - -public Native_IsActive(iPluginId, iArgc) { - new pPlayer = get_param(1); - - return g_rgpPlayerCamera[pPlayer] != -1; -} - -public Native_SetOffset(iPluginId, iArgc) { - new pPlayer = get_param(1); - - static Float:vecOffset[3]; - get_array_f(2, vecOffset, 3); - - SetCameraOffset(pPlayer, vecOffset); -} - -public Native_SetAngles(iPluginId, iArgc) { - new pPlayer = get_param(1); - - static Float:vecAngles[3]; - get_array_f(2, vecAngles, 3); - - SetCameraAngles(pPlayer, vecAngles); -} - -public Native_SetDistance(iPluginId, iArgc) { - new pPlayer = get_param(1); - new Float:flDistance = get_param_f(2); - - SetCameraDistance(pPlayer, flDistance); -} - -public Native_SetThinkDelay(iPluginId, iArgc) { - new pPlayer = get_param(1); - new Float:flThinkDelay = get_param_f(2); - - SetCameraThinkDelay(pPlayer, flThinkDelay); -} - -public HamHook_Player_Spawn_Post(pPlayer) { - ReattachCamera(pPlayer); -} - -public HamHook_Player_PreThink_Post(pPlayer) { - PlayerCameraThink(pPlayer); -} - -public client_connect(pPlayer) { - g_rgpPlayerCamera[pPlayer] = -1; - SetCameraDistance(pPlayer, 200.0); - SetCameraAngles(pPlayer, Float:{0.0, 0.0, 0.0}); - SetCameraOffset(pPlayer, Float:{0.0, 0.0, 0.0}); - SetCameraThinkDelay(pPlayer, 0.01); -} - -public client_disconnected(pPlayer) { - DeactivatePlayerCamera(pPlayer); -} - -ActivatePlayerCamera(pPlayer) { - if (g_rgpPlayerCamera[pPlayer] != -1) { - return; - } - - g_rgpPlayerCamera[pPlayer] = CreatePlayerCamera(pPlayer); - g_rgflPlayerCameraNextThink[pPlayer] = 0.0; - - engfunc(EngFunc_SetView, pPlayer, g_rgpPlayerCamera[pPlayer]); -} - -DeactivatePlayerCamera(pPlayer) { - if (g_rgpPlayerCamera[pPlayer] == -1) { - return; - } - - engfunc(EngFunc_RemoveEntity, g_rgpPlayerCamera[pPlayer]); - g_rgpPlayerCamera[pPlayer] = -1; - - if (is_user_connected(pPlayer)) { - engfunc(EngFunc_SetView, pPlayer, pPlayer); - } -} - -SetCameraOffset(pPlayer, const Float:vecOffset[3]) { - xs_vec_copy(vecOffset, g_rgflPlayerCamerOffset[pPlayer]); -} - -SetCameraAngles(pPlayer, const Float:vecAngles[3]) { - xs_vec_copy(vecAngles, g_rgflPlayerCameraAngles[pPlayer]); -} - -SetCameraDistance(pPlayer, Float:flDistance) { - g_rgflPlayerCameraDistance[pPlayer] = flDistance; -} - -SetCameraThinkDelay(pPlayer, Float:flThinkDelay) { - g_rgflPlayerCameraThinkDelay[pPlayer] = flThinkDelay; -} - -CreatePlayerCamera(pPlayer) { - new pCamera = engfunc(EngFunc_CreateNamedEntity, g_iszTriggerCameraClassname); - - set_pev(pCamera, pev_classname, "trigger_camera"); - set_pev(pCamera, pev_modelindex, g_iCameraModelIndex); - set_pev(pCamera, pev_owner, pPlayer); - set_pev(pCamera, pev_solid, SOLID_NOT); - set_pev(pCamera, pev_movetype, MOVETYPE_FLY); - set_pev(pCamera, pev_rendermode, kRenderTransTexture); - - return pCamera; -} - -PlayerCameraThink(pPlayer) { - if (g_rgflPlayerCameraNextThink[pPlayer] > get_gametime()) { - return; - } - - g_rgflPlayerCameraNextThink[pPlayer] = get_gametime() + g_rgflPlayerCameraThinkDelay[pPlayer]; - - if (g_rgpPlayerCamera[pPlayer] == -1) { - return; - } - - if (!is_user_alive(pPlayer)) { - return; - } - - static Float:vecOrigin[3]; - pev(pPlayer, pev_origin, vecOrigin); - xs_vec_add(vecOrigin, g_rgflPlayerCamerOffset[pPlayer], vecOrigin); - - static Float:vecAngles[3]; - pev(pPlayer, pev_angles, vecAngles); - vecAngles[0] = vecAngles[2] = 0.0; - xs_vec_add(vecAngles, g_rgflPlayerCameraAngles[pPlayer], vecAngles); - - static Float:vecBack[3]; - angle_vector(vecAngles, ANGLEVECTOR_FORWARD, vecBack); - xs_vec_neg(vecBack, vecBack); - - static Float:vecVelocity[3]; - pev(pPlayer, pev_velocity, vecVelocity); - - static Float:vecCameraOrigin[3]; - for (new i = 0; i < 3; ++i) { - vecCameraOrigin[i] = vecOrigin[i] + (vecBack[i] * g_rgflPlayerCameraDistance[pPlayer]); - } - - new pTr = create_tr2(); - engfunc(EngFunc_TraceLine, vecOrigin, vecCameraOrigin, IGNORE_MONSTERS, pPlayer, pTr); - - static Float:flFraction; - get_tr2(pTr, TR_flFraction, flFraction); - - free_tr2(pTr); - - if(flFraction != 1.0) { - for (new i = 0; i < 3; ++i) { - vecCameraOrigin[i] = vecOrigin[i] + (vecBack[i] * (g_rgflPlayerCameraDistance[pPlayer] * flFraction)); - } - } - - set_pev(g_rgpPlayerCamera[pPlayer], pev_origin, vecCameraOrigin); - set_pev(g_rgpPlayerCamera[pPlayer], pev_angles, vecAngles); - set_pev(g_rgpPlayerCamera[pPlayer], pev_velocity, vecVelocity); -} - -ReattachCamera(pPlayer) { - if (g_rgpPlayerCamera[pPlayer] == -1) { - return; - } - - engfunc(EngFunc_SetView, pPlayer, g_rgpPlayerCamera[pPlayer]); -} diff --git a/api_player_model.sma b/api_player_model.sma deleted file mode 100644 index e814cb7..0000000 --- a/api_player_model.sma +++ /dev/null @@ -1,97 +0,0 @@ -#include -#include -#include -#include - -#define PLUGIN "[API] Player Model" -#define VERSION "0.9.0" -#define AUTHOR "Hedgehog Fog" - -new g_rgszPlayerModel[MAX_PLAYERS + 1][32]; -new g_rgszCustomPlayerModel[MAX_PLAYERS + 1][256]; - -public plugin_init() { - register_plugin(PLUGIN, VERSION, AUTHOR); - - RegisterHamPlayer(Ham_Spawn, "HamHook_Player_Spawn_Post", .Post = 1); - - register_forward(FM_SetClientKeyValue, "FMHook_SetClientKeyValue"); -} - -public plugin_natives() { - register_library("api_player_model"); - register_native("PlayerModel_Get", "Native_GetPlayerModel"); - register_native("PlayerModel_Set", "Native_SetPlayerModel"); - register_native("PlayerModel_Reset", "Native_ResetPlayerModel"); - register_native("PlayerModel_Update", "Native_UpdatePlayerModel"); -} - -public Native_GetPlayerModel(iPluginId, iArgc) { - new pPlayer = get_param(1); - - set_string(2, g_rgszCustomPlayerModel[pPlayer], get_param(3)); -} - -public Native_SetPlayerModel(iPluginId, iArgc) { - new pPlayer = get_param(1); - get_string(2, g_rgszCustomPlayerModel[pPlayer], charsmax(g_rgszCustomPlayerModel[])); -} - -public Native_ResetPlayerModel(iPluginId, iArgc) { - new pPlayer = get_param(1); - @Player_ResetModel(pPlayer); -} - -public Native_UpdatePlayerModel(iPluginId, iArgc) { - new pPlayer = get_param(1); - @Player_UpdateModel(pPlayer); -} - -public client_connect(pPlayer) { - copy(g_rgszCustomPlayerModel[pPlayer], charsmax(g_rgszCustomPlayerModel[]), NULL_STRING); - copy(g_rgszPlayerModel[pPlayer], charsmax(g_rgszPlayerModel[]), NULL_STRING); -} - -public HamHook_Player_Spawn_Post(pPlayer) { - @Player_UpdateModel(pPlayer); -} - -public FMHook_SetClientKeyValue(pPlayer, const szInfoBuffer[], const szKey[], const szValue[]) { - if (equal(szKey, "model")) { - copy(g_rgszPlayerModel[pPlayer], charsmax(g_rgszPlayerModel[]), szValue); - - if (!equal(g_rgszCustomPlayerModel[pPlayer], NULL_STRING)) { - return FMRES_SUPERCEDE; - } - - return FMRES_IGNORED; - } - - return FMRES_IGNORED; -} - -public @Player_UpdateModel(this) { - if (!equal(g_rgszCustomPlayerModel[this], NULL_STRING)) { - new iModelIndex = engfunc(EngFunc_ModelIndex, g_rgszCustomPlayerModel[this]); - set_user_info(this, "model", ""); - set_pev(this, pev_modelindex, iModelIndex); - set_member(this, m_modelIndexPlayer, iModelIndex); - } else { - @Player_ResetModel(this); - } -} - -public @Player_ResetModel(this) { - if (equal(g_rgszPlayerModel[this], NULL_STRING)) { - return; - } - - static szPath[MAX_RESOURCE_PATH_LENGTH]; - format(szPath, charsmax(szPath), "models/player/%s/%s.mdl", g_rgszPlayerModel[this], g_rgszPlayerModel[this]); - - new iModelIndex = engfunc(EngFunc_ModelIndex, szPath); - set_user_info(this, "model", g_rgszPlayerModel[this]); - set_pev(this, pev_modelindex, iModelIndex); - set_member(this, m_modelIndexPlayer, iModelIndex); - copy(g_rgszCustomPlayerModel[this], charsmax(g_rgszCustomPlayerModel[]), NULL_STRING); -} diff --git a/api_player_viewrange.sma b/api_player_viewrange.sma deleted file mode 100644 index 975b197..0000000 --- a/api_player_viewrange.sma +++ /dev/null @@ -1,102 +0,0 @@ -#include - -#define PLUGIN "[API] Player View Range" -#define VERSION "0.9.0" -#define AUTHOR "Hedgehog Fog" - -new Float:g_rgflPlayerViewRange[MAX_PLAYERS + 1]; -new g_rgiPlayerNativeFogColor[MAX_PLAYERS + 1][3]; -new Float:g_flPlayerNativeFogDensity[MAX_PLAYERS + 1]; - -new gmsgFog; - -public plugin_init() { - register_plugin(PLUGIN, VERSION, AUTHOR); - - gmsgFog = get_user_msgid("Fog"); - - register_message(gmsgFog, "Message_Fog"); -} - -public plugin_natives() { - register_library("api_player_viewrange"); - register_native("PlayerViewRange_Get", "Native_GetPlayerViewRange"); - register_native("PlayerViewRange_Set", "Native_SetPlayerViewRange"); - register_native("PlayerViewRange_Reset", "Native_ResetPlayerViewRange"); - register_native("PlayerViewRange_Update", "Native_UpdatePlayerViewRange"); -} - -public Float:Native_GetPlayerViewRange(iPluginId, iArgc) { - new pPlayer = get_param(1); - - return g_rgflPlayerViewRange[pPlayer]; -} - -public Native_SetPlayerViewRange(iPluginId, iArgc) { - new pPlayer = get_param(1); - new Float:flValue = get_param_f(2); - - @Player_SetViewRange(pPlayer, flValue); -} - -public Native_ResetPlayerViewRange(iPluginId, iArgc) { - new pPlayer = get_param(1); - - @Player_SetViewRange(pPlayer, -1.0); -} - -public Native_UpdatePlayerViewRange(iPluginId, iArgc) { - new pPlayer = get_param(1); - - @Player_UpdateViewRange(pPlayer); -} - -public client_connect(pPlayer) { - g_rgflPlayerViewRange[pPlayer] = 0.0; - g_rgiPlayerNativeFogColor[pPlayer][0] = 0; - g_rgiPlayerNativeFogColor[pPlayer][1] = 0; - g_rgiPlayerNativeFogColor[pPlayer][2] = 0; - g_flPlayerNativeFogDensity[pPlayer] = 0.0; -} - -public Message_Fog(iMsgId, iMsgDest, pPlayer) { - g_rgiPlayerNativeFogColor[pPlayer][0] = get_msg_arg_int(1); - g_rgiPlayerNativeFogColor[pPlayer][1] = get_msg_arg_int(2); - g_rgiPlayerNativeFogColor[pPlayer][2] = get_msg_arg_int(3); - g_flPlayerNativeFogDensity[pPlayer] = Float:( - get_msg_arg_int(4) | - (get_msg_arg_int(5) << 8) | - (get_msg_arg_int(6) << 16) | - (get_msg_arg_int(7) << 24) - ); -} - -public @Player_SetViewRange(this, Float:flViewRange) { - if (g_rgflPlayerViewRange[this] == flViewRange) { - return; - } - - g_rgflPlayerViewRange[this] = flViewRange; - - @Player_UpdateViewRange(this); -} - -public @Player_UpdateViewRange(this) { - if (g_rgflPlayerViewRange[this] >= 0.0) { - new Float:flDensity = g_rgflPlayerViewRange[this] < 0 ? 0.0 : (1.0 / g_rgflPlayerViewRange[this]); - - message_begin(MSG_ONE, gmsgFog, {0, 0, 0}, this); - write_byte(0); - write_byte(0); - write_byte(0); - write_long(_:flDensity); - message_end(); - } else { // reset to engine fog - message_begin(MSG_ONE, gmsgFog, {0, 0, 0}, this); - write_byte(g_rgiPlayerNativeFogColor[this][0]); - write_byte(g_rgiPlayerNativeFogColor[this][1]); - write_byte(g_rgiPlayerNativeFogColor[this][2]); - write_long(_:g_flPlayerNativeFogDensity[this]); - message_end(); - } -} diff --git a/api_rounds.sma b/api_rounds.sma deleted file mode 100644 index 2ca3bfe..0000000 --- a/api_rounds.sma +++ /dev/null @@ -1,248 +0,0 @@ -#pragma semicolon 1 - -#include -#include - -enum GameState { - GameState_NewRound, - GameState_RoundStarted, - GameState_RoundEnd -}; - -enum _:Hook { - Hook_PluginId, - Hook_FunctionId -}; - -new GameState:g_iGameState; - -new g_iFwNewRound; -new g_iFwRoundStart; -new g_iFwRoundEnd; -new g_iFwRoundExpired; -new g_iFwRoundRestart; -new g_iFwRoundTimerTick; -new g_iFwCheckWinConditions; - -new Array:g_irgCheckWinConditionHooks; - -new g_pCvarRoundEndDelay; - -public plugin_init() { - register_plugin("[API] Rounds", "2.1.0", "Hedgehog Fog"); - - register_event("HLTV", "Event_NewRound", "a", "1=0", "2=0"); - RegisterHookChain(RG_CSGameRules_RestartRound, "HC_RestartRound", .post = 0); - RegisterHookChain(RG_CSGameRules_OnRoundFreezeEnd, "HC_OnRoundFreezeEnd_Post", .post = 1); - RegisterHookChain(RG_RoundEnd, "HC_RoundEnd", .post = 1); - RegisterHookChain(RG_CSGameRules_CheckWinConditions, "HC_CheckWinConditions", .post = 0); - - g_iFwNewRound = CreateMultiForward("Round_Fw_NewRound", ET_IGNORE); - g_iFwRoundStart = CreateMultiForward("Round_Fw_RoundStart", ET_IGNORE); - g_iFwRoundEnd = CreateMultiForward("Round_Fw_RoundEnd", ET_IGNORE, FP_CELL); - g_iFwRoundExpired = CreateMultiForward("Round_Fw_RoundExpired", ET_IGNORE); - g_iFwRoundRestart = CreateMultiForward("Round_Fw_RoundRestart", ET_IGNORE); - g_iFwRoundTimerTick = CreateMultiForward("Round_Fw_RoundTimerTick", ET_IGNORE); - g_iFwCheckWinConditions = CreateMultiForward("Round_Fw_CheckWinCondition", ET_STOP); - - g_irgCheckWinConditionHooks = ArrayCreate(Hook); - - g_pCvarRoundEndDelay = get_cvar_pointer("mp_round_restart_delay"); -} - -public plugin_natives() { - register_library("api_rounds"); - register_native("Round_DispatchWin", "Native_DispatchWin"); - register_native("Round_GetTime", "Native_GetTime"); - register_native("Round_SetTime", "Native_SetTime"); - register_native("Round_GetTimeLeft", "Native_GetTimeLeft"); - register_native("Round_IsRoundStarted", "Native_IsRoundStarted"); - register_native("Round_IsRoundEnd", "Native_IsRoundEnd"); - register_native("Round_HookCheckWinConditions", "Native_HookCheckWinConditions"); -} - -public plugin_destroy() { - ArrayDestroy(g_irgCheckWinConditionHooks); -} - -public server_frame() { - static Float:flTime; - flTime = get_gametime(); - - static Float:flNextPeriodicThink; - flNextPeriodicThink = get_member_game(m_tmNextPeriodicThink); - - if (flNextPeriodicThink < flTime) { - static bool:bFreezePeriod; - bFreezePeriod = get_member_game(m_bFreezePeriod); - - ExecuteForward(g_iFwRoundTimerTick); - - static iRoundTimeSecs; - iRoundTimeSecs = get_member_game(m_iRoundTimeSecs); - - static Float:flStartTime; - flStartTime = get_member_game(m_fRoundStartTimeReal); - - static Float:flEndTime; - flEndTime = flStartTime + float(iRoundTimeSecs); - - if (!bFreezePeriod) { - if (flTime >= flEndTime) { - ExecuteForward(g_iFwRoundExpired); - } - } - } -} - -public HC_RestartRound() { - if (!get_member_game(m_bCompleteReset)) { - // g_iGameState = GameState_NewRound; - // ExecuteForward(g_iFwNewRound); - } else { - ExecuteForward(g_iFwRoundRestart); - } -} - -public HC_OnRoundFreezeEnd_Post() { - g_iGameState = GameState_RoundStarted; - ExecuteForward(g_iFwRoundStart); -} - -public Event_NewRound() { - g_iGameState = GameState_NewRound; - ExecuteForward(g_iFwNewRound); -} - -public HC_RoundEnd(WinStatus:iStatus, ScenarioEventEndRound:iEvent, Float:flDelay) { - new TeamName:iWinTeam = TEAM_UNASSIGNED; - - switch (iStatus) { - case WINSTATUS_TERRORISTS: iWinTeam = TEAM_TERRORIST; - case WINSTATUS_CTS: iWinTeam = TEAM_CT; - case WINSTATUS_DRAW: iWinTeam = TEAM_SPECTATOR; - } - - g_iGameState = GameState_RoundEnd; - - ExecuteForward(g_iFwRoundEnd, _, _:iWinTeam); -} - -public HC_CheckWinConditions() { - new iSize = ArraySize(g_irgCheckWinConditionHooks); - - for (new i = 0; i < iSize; ++i) { - static hook[_:Hook]; - ArrayGetArray(g_irgCheckWinConditionHooks, i, hook); - - if (callfunc_begin_i(hook[Hook_FunctionId], hook[Hook_PluginId]) == 1) { - if (callfunc_end() > PLUGIN_CONTINUE) { - return HC_SUPERCEDE; - } - } - } - - static iReturn; - ExecuteForward(g_iFwCheckWinConditions, iReturn); - if (iReturn != PLUGIN_CONTINUE) { - return HC_SUPERCEDE; - } - - return HC_CONTINUE; -} - -public Native_DispatchWin(iPluginId, iArgc) { - new iTeam = get_param(1); - new Float:flDelay = get_param_f(2); - DispatchWin(iTeam, flDelay); -} - -public Native_GetTime(iPluginId, iArgc) { - return get_member_game(m_iRoundTimeSecs); -} - -public Native_SetTime(iPluginId, iArgc) { - new iTime = get_param(1); - SetTime(iTime); -} - -public Native_GetTimeLeft(iPluginId, iArgc) { - return GetTimeLeft(); -} - -public bool:Native_IsRoundStarted(iPluginId, iArgc) { - return g_iGameState > GameState_NewRound; -} - -public bool:Native_IsRoundEnd(iPluginId, iArgc) { - return g_iGameState == GameState_RoundEnd; -} - -public Native_HookCheckWinConditions(iPluginId, iArgc) { - new szFunctionName[32]; - get_string(1, szFunctionName, charsmax(szFunctionName)); - - new hook[Hook]; - hook[Hook_PluginId] = iPluginId; - hook[Hook_FunctionId] = get_func_id(szFunctionName, iPluginId); - - ArrayPushArray(g_irgCheckWinConditionHooks, hook); -} - -DispatchWin(iTeam, Float:flDelay = -1.0) { - if (g_iGameState == GameState_RoundEnd) { - return; - } - - if (iTeam < 1 || iTeam > 3) { - return; - } - - if (flDelay < 0.0) { - flDelay = g_pCvarRoundEndDelay ? get_pcvar_float(g_pCvarRoundEndDelay) : 5.0; - } - - new WinStatus:iWinstatus = WINSTATUS_DRAW; - if (iTeam == 1) { - iWinstatus = WINSTATUS_TERRORISTS; - } else if (iTeam == 2) { - iWinstatus = WINSTATUS_CTS; - } - - new ScenarioEventEndRound:iEvent = ROUND_END_DRAW; - if (iTeam == 1) { - iEvent = ROUND_TERRORISTS_WIN; - } else if (iTeam == 2) { - iEvent = ROUND_CTS_WIN; - } - - rg_round_end(flDelay, iWinstatus, iEvent, _, _, true); - rg_update_teamscores(iTeam == 2 ? 1 : 0, iTeam == 1 ? 1 : 0); -} - -SetTime(iTime) { - new Float:flStartTime = get_member_game(m_fRoundStartTimeReal); - - set_member_game(m_iRoundTime, iTime); - set_member_game(m_iRoundTimeSecs, iTime); - set_member_game(m_fRoundStartTime, flStartTime); - - UpdateTimer(0, GetTimeLeft()); -} - -GetTimeLeft() { - new Float:flStartTime = get_member_game(m_fRoundStartTimeReal); - new iTime = get_member_game(m_iRoundTimeSecs); - return floatround(flStartTime + float(iTime) - get_gametime()); -} - -UpdateTimer(iClient, iTime) { - static iMsgId = 0; - if(!iMsgId) { - iMsgId = get_user_msgid("RoundTime"); - } - - message_begin(iClient ? MSG_ONE : MSG_ALL, iMsgId); - write_short(iTime); - message_end(); -} diff --git a/entities/docs/entity_base_monster.md b/entities/docs/entity_base_monster.md new file mode 100644 index 0000000..fe8c162 --- /dev/null +++ b/entities/docs/entity_base_monster.md @@ -0,0 +1,216 @@ +Here is an example how to implement HL Zombie using monster_base entity. You can test zombies on map `c1a1a`. + +```cpp +#include +#include +#include +#include + +#include + +#include + +#define ENTITY_NAME "monster_zombie" + +#define ZOMBIE_AE_ATTACK_RIGHT 0x01 +#define ZOMBIE_AE_ATTACK_LEFT 0x02 +#define ZOMBIE_AE_ATTACK_BOTH 0x03 + +#define ZOMBIE_FLINCH_DELAY 2.0 + +#define m_flNextFlinch "flNextFlinch" + +new const g_szModel[] = "models/zombie.mdl"; + +new const g_rgpAttackHitSounds[][] = { + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +new const g_rgpAttackMissSounds[][] = { + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +new const g_rgpAttackSounds[][] = { + "zombie/zo_attack1.wav", + "zombie/zo_attack2.wav", +}; + +new const g_rgpIdleSounds[][] = { + "zombie/zo_idle1.wav", + "zombie/zo_idle2.wav", + "zombie/zo_idle3.wav", + "zombie/zo_idle4.wav", +}; + +new const g_rgpAlertSounds[][] = { + "zombie/zo_alert10.wav", + "zombie/zo_alert20.wav", + "zombie/zo_alert30.wav", +}; + +new const g_rgpPainSounds[][] = { + "zombie/zo_pain1.wav", + "zombie/zo_pain2.wav", +}; + +public plugin_precache() { + precache_model(g_szModel); + + for (new i = 0; i < sizeof(g_rgpAttackHitSounds); ++i) precache_sound(g_rgpAttackHitSounds[i]); + for (new i = 0; i < sizeof(g_rgpAttackMissSounds); ++i) precache_sound(g_rgpAttackMissSounds[i]); + for (new i = 0; i < sizeof(g_rgpAttackSounds); ++i) precache_sound(g_rgpAttackSounds[i]); + for (new i = 0; i < sizeof(g_rgpIdleSounds); ++i) precache_sound(g_rgpIdleSounds[i]); + for (new i = 0; i < sizeof(g_rgpAlertSounds); ++i) precache_sound(g_rgpAlertSounds[i]); + for (new i = 0; i < sizeof(g_rgpPainSounds); ++i) precache_sound(g_rgpPainSounds[i]); + + CE_RegisterDerived(ENTITY_NAME, BASE_MONSTER_ENTITY_NAME); + + CE_RegisterHook(ENTITY_NAME, CEFunction_Init, "@Entity_Init"); + CE_RegisterHook(ENTITY_NAME, CEFunction_InitPhysics, "@Entity_InitPhysics"); + CE_RegisterHook(ENTITY_NAME, CEFunction_Spawned, "@Entity_Spawned"); + + CE_RegisterVirtualMethod(ENTITY_NAME, IgnoreConditions, "@Entity_IgnoreConditions"); + CE_RegisterVirtualMethod(ENTITY_NAME, SetYawSpeed, "@Entity_SetYawSpeed"); + CE_RegisterVirtualMethod(ENTITY_NAME, HandleAnimEvent, "@Entity_HandleAnimEvent", CE_MP_Cell, CE_MP_Array, 64); + + CE_RegisterMethod(ENTITY_NAME, AlertSound, "@Entity_AlertSound"); + CE_RegisterMethod(ENTITY_NAME, IdleSound, "@Entity_IdleSound"); + CE_RegisterMethod(ENTITY_NAME, PainSound, "@Entity_PainSound"); + + CE_RegisterVirtualMethod(ENTITY_NAME, Classify, "@Entity_Classify"); +} + +public plugin_init() { + register_plugin("[Entity] Zombie Monster", "1.0.0", "Hedgehog Fog"); +} + +@Entity_Init(this) { + CE_SetMemberVec(this, CE_MEMBER_MINS, Float:{-16.0, -16.0, 0.0}); + CE_SetMemberVec(this, CE_MEMBER_MAXS, Float:{16.0, 16.0, 72.0}); + CE_SetMemberString(this, CE_MEMBER_MODEL, g_szModel, false); + CE_SetMember(this, CE_MEMBER_BLOODCOLOR, 195); +} + +@Entity_InitPhysics(this) { + set_pev(this, pev_solid, SOLID_SLIDEBOX); + set_pev(this, pev_movetype, MOVETYPE_STEP); +} + +@Entity_Spawned(this) { + set_pev(this, pev_spawnflags, pev(this, pev_spawnflags) | SF_MONSTER_FADECORPSE); + set_pev(this, pev_health, 100.0); + set_pev(this, pev_view_ofs, Float:{0.0, 0.0, 28.0}); + CE_SetMember(this, m_flFieldOfView, 0.5); + CE_SetMember(this, m_iMonsterState, MONSTER_STATE_NONE); + + CE_SetMember(this, m_flMeleeAttack1Range, 70.0); + CE_SetMember(this, m_flMeleeAttack1Damage, 10.0); + + CE_SetMember(this, m_flMeleeAttack2Range, 70.0); + CE_SetMember(this, m_flMeleeAttack2Damage, 24.0); +} + +@Entity_AlertSound(this) { + emit_sound(this, CHAN_WEAPON, g_rgpAlertSounds[random(sizeof(g_rgpAlertSounds))], VOL_NORM, ATTN_NORM, 0, 100 + random_num(-5, 5)); +} + +@Entity_IdleSound(this) { + emit_sound(this, CHAN_WEAPON, g_rgpIdleSounds[random(sizeof(g_rgpIdleSounds))], VOL_NORM, ATTN_NORM, 0, 100 + random_num(-5, 5)); +} + +@Entity_PainSound(this) { + emit_sound(this, CHAN_WEAPON, g_rgpPainSounds[random(sizeof(g_rgpPainSounds))], VOL_NORM, ATTN_NORM, 0, 100 + random_num(-5, 5)); +} + +@Entity_SetYawSpeed(this) { + set_pev(this, pev_yaw_speed, 120.0); +} + +@Entity_HandleAnimEvent(this, iEventId, const rgOptions[]) { + CE_CallBaseMethod(iEventId, rgOptions); + + static Float:vecAimAngles[3]; + pev(this, pev_angles, vecAimAngles); + vecAimAngles[0] = -vecAimAngles[0]; + + static Float:vecForward[3]; angle_vector(vecAimAngles, ANGLEVECTOR_FORWARD, vecForward); + static Float:vecRight[3]; angle_vector(vecAimAngles, ANGLEVECTOR_RIGHT, vecRight); + + switch (iEventId) { + case ZOMBIE_AE_ATTACK_RIGHT, ZOMBIE_AE_ATTACK_LEFT, ZOMBIE_AE_ATTACK_BOTH: { + static pHurt; pHurt = CE_CallMethod(this, ZOMBIE_AE_ATTACK_BOTH ? MeleeAttack2 : MeleeAttack1); + + if (pHurt != FM_NULLENT && pev(pHurt, pev_flags) & (FL_MONSTER | FL_CLIENT)) { + static Float:vecVictimPunchAngle[3]; pev(pHurt, pev_punchangle, vecVictimPunchAngle); + static Float:vecVictimVelocity[3]; pev(pHurt, pev_velocity, vecVictimVelocity); + + vecVictimPunchAngle[0] = 5.0; + + switch (iEventId) { + case ZOMBIE_AE_ATTACK_RIGHT, ZOMBIE_AE_ATTACK_LEFT: { + static iDirection; iDirection = (iEventId == ZOMBIE_AE_ATTACK_RIGHT ? -1 : 1); + + vecVictimPunchAngle[2] = (18.0 * iDirection); + xs_vec_add_scaled(vecVictimVelocity, vecRight, 100.0 * iDirection, vecVictimVelocity); + } + case ZOMBIE_AE_ATTACK_BOTH: { + xs_vec_add_scaled(vecVictimVelocity, vecForward, 100.0, vecVictimVelocity); + } + } + + set_pev(pHurt, pev_punchangle, vecVictimPunchAngle); + set_pev(pHurt, pev_velocity, vecVictimVelocity); + } + + if (pHurt != FM_NULLENT) { + emit_sound(this, CHAN_WEAPON, g_rgpAttackHitSounds[random(sizeof(g_rgpAttackHitSounds))], VOL_NORM, ATTN_NORM, 0, 100 + random_num(-5, 5)); + } else { + emit_sound(this, CHAN_WEAPON, g_rgpAttackMissSounds[random(sizeof(g_rgpAttackMissSounds))], VOL_NORM, ATTN_NORM, 0, 100 + random_num(-5, 5)); + } + + if (random(2)) @Entity_AttackSound(this); + } + case SCRIPT_EVENT_SOUND: { + if (equal(rgOptions, "common/npc_step", 15)) { + CE_CallMethod(this, StepSound); + } + } + default: { + // CE_CallBaseMethod(iEventId, rgOptions); + } + } +} + +@Entity_AttackSound(this) { + emit_sound(this, CHAN_VOICE, g_rgpAttackSounds[random(sizeof(g_rgpAttackSounds))], VOL_NORM, ATTN_NORM, 0, 100 + random_num(-5, 5)); +} + +@Entity_Classify(this) { + return CLASS_ALIEN_MONSTER; +} + +@Entity_IgnoreConditions(this) { + new iIgnore = CE_CallBaseMethod(); + + static Float:flGameTime; flGameTime = get_gametime(); + static Float:flNextFlinch; flNextFlinch = CE_GetMember(this, m_flNextFlinch); + static Activity:iActivity; iActivity = CE_GetMember(this, m_iActivity); + + if ((iActivity == ACT_MELEE_ATTACK1) || (iActivity == ACT_MELEE_ATTACK1)) { + if (flNextFlinch >= flGameTime) { + iIgnore |= (COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE); + } + } + + if ((iActivity == ACT_SMALL_FLINCH) || (iActivity == ACT_BIG_FLINCH)) { + if (flNextFlinch < flGameTime) { + flNextFlinch = flGameTime + ZOMBIE_FLINCH_DELAY; + } + } + + return iIgnore; +} +``` \ No newline at end of file diff --git a/entities/entity_base_monster.sma b/entities/entity_base_monster.sma new file mode 100644 index 0000000..7e3f314 --- /dev/null +++ b/entities/entity_base_monster.sma @@ -0,0 +1,5002 @@ +#pragma semicolon 1 + +#include +#include +#include +#include + +#include +#tryinclude +#include + +#include + +#define PLUGIN "[Entity] Base Monster" +#define VERSION "1.0.0" +#define AUTHOR "Hedgehog Fog" + +#define IS_PLAYER(%1) (%1 >= 1 && %1 <= MaxClients) +#define IS_MONSTER(%1) (!!(pev(%1, pev_flags) & FL_MONSTER)) + +#define ENTITY_NAME BASE_MONSTER_ENTITY_NAME + +#define EVENT_CLIENT 5000 +#define STUDIO_LOOPING 0x0001 +#define DIST_TO_CHECK 200.0 +#define MONSTER_CUT_CORNER_DIST 8.0 +#define MAX_WORLD_SOUNDS 64 + +#define CHAR_TEX_CONCRETE 'C' +#define CHAR_TEX_METAL 'M' +#define CHAR_TEX_DIRT 'D' +#define CHAR_TEX_VENT 'V' +#define CHAR_TEX_GRATE 'G' +#define CHAR_TEX_TILE 'T' +#define CHAR_TEX_SLOSH 'S' +#define CHAR_TEX_WOOD 'W' +#define CHAR_TEX_COMPUTER 'P' +#define CHAR_TEX_GRASS 'X' +#define CHAR_TEX_GLASS 'Y' +#define CHAR_TEX_FLESH 'F' +#define CHAR_TEX_SNOW 'N' + +enum Sound { + Sound_Emitter, + Sound_Type, + Sound_Volume, + Float:Sound_ExpiredTime, + Float:Sound_Origin[3] +}; + +enum ModelEvent { + ModelEvent_Frame, + ModelEvent_Event, + ModelEvent_Options[64] +}; + +enum Model { + Float:Model_EyePosition[3], + Array:Model_Sequences +}; + +enum Sequence { + Sequence_FramesNum, + Float:Sequence_FPS, + Sequence_Flags, + Sequence_Activity, + Sequence_ActivityWeight, + Array:Sequence_Events, + Float:Sequence_LinearMovement[3] +}; + +enum { + STEP_CONCRETE = 0, + STEP_METAL, + STEP_DIRT, + STEP_VENT, + STEP_GRATE, + STEP_TILE, + STEP_SLOSH, + STEP_WADE, + STEP_LADDER, + STEP_SNOW +}; + +new const g_tlFail[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_SET_ACTIVITY, ACT_IDLE }, + { TASK_WAIT, 2.0 }, + { TASK_WAIT_PVS, 0.0 } +}; + +new const g_tlIdleStand1[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_SET_ACTIVITY, ACT_IDLE }, + { TASK_WAIT, 5.0 } +}; + +new const g_tlIdleWalk1[][MONSTER_TASK_DATA] = { + { TASK_WALK_PATH, 9999.0 }, + { TASK_WAIT_FOR_MOVEMENT, 0.0 } +}; + +new const g_tlAmbush[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_SET_ACTIVITY, ACT_IDLE }, + { TASK_WAIT_INDEFINITE, 0.0 } +}; + +new const g_tlActiveIdle[][MONSTER_TASK_DATA] = { + { TASK_FIND_HINTNODE, 0.0 }, + { TASK_GET_PATH_TO_HINTNODE, 0.0 }, + { TASK_STORE_LASTPOSITION, 0.0 }, + { TASK_WALK_PATH, 0.0 }, + { TASK_WAIT_FOR_MOVEMENT, 0.0 }, + { TASK_FACE_HINTNODE, 0.0 }, + { TASK_PLAY_ACTIVE_IDLE, 0.0 }, + { TASK_GET_PATH_TO_LASTPOSITION, }, + { TASK_WALK_PATH, 0.0 }, + { TASK_WAIT_FOR_MOVEMENT, 0.0 }, + { TASK_CLEAR_LASTPOSITION, 0.0 }, + { TASK_CLEAR_HINTNODE, 0.0 } +}; + +new const g_tlWakeAngry1[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_SET_ACTIVITY, ACT_IDLE }, + { TASK_SOUND_WAKE, 0.0 }, + { TASK_FACE_IDEAL, 0.0 } +}; + +new const g_tlAlertFace1[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_SET_ACTIVITY, ACT_IDLE }, + { TASK_FACE_IDEAL, 0.0 } +}; + +new const g_tlAlertSmallFlinch[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_REMEMBER, MEMORY_FLINCHED }, + { TASK_SMALL_FLINCH, 0.0 }, + { TASK_SET_SCHEDULE, MONSTER_SCHED_ALERT_FACE } +}; + +new const g_tlAlertStand1[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_SET_ACTIVITY, ACT_IDLE }, + { TASK_WAIT, 20.0 }, + { TASK_SUGGEST_STATE, MONSTER_STATE_IDLE } +}; + +new const g_tlInvestigateSound[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_STORE_LASTPOSITION, 0.0 }, + { TASK_GET_PATH_TO_BESTSOUND, 0.0 }, + { TASK_FACE_IDEAL, 0.0 }, + { TASK_WALK_PATH, 0.0 }, + { TASK_WAIT_FOR_MOVEMENT, 0.0 }, + { TASK_PLAY_SEQUENCE, ACT_IDLE }, + { TASK_WAIT, 10.0 }, + { TASK_GET_PATH_TO_LASTPOSITION, }, + { TASK_WALK_PATH, 0.0 }, + { TASK_WAIT_FOR_MOVEMENT, 0.0 }, + { TASK_CLEAR_LASTPOSITION, 0.0 } +}; + +new const g_tlCombatStand1[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_SET_ACTIVITY, ACT_IDLE }, + { TASK_WAIT_INDEFINITE, 0.0 } +}; + +new const g_tlCombatFace1[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_SET_ACTIVITY, ACT_IDLE }, + { TASK_FACE_ENEMY, 0.0 } +}; + +new const g_tlStandoff[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_SET_ACTIVITY, ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, 2.0 } +}; + +new const g_tlArmWeapon[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_PLAY_SEQUENCE, ACT_ARM } +}; + +new const g_tlReload[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_PLAY_SEQUENCE, ACT_RELOAD } +}; + +new const g_tlRangeAttack1[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_FACE_ENEMY, 0.0 }, + { TASK_RANGE_ATTACK1, 0.0 } +}; + +new const g_tlRangeAttack2[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_FACE_ENEMY, 0.0 }, + { TASK_RANGE_ATTACK2, 0.0 } +}; + +new const g_tlPrimaryMeleeAttack1[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_FACE_ENEMY, 0.0 }, + { TASK_MELEE_ATTACK1, 0.0 } +}; + +new const g_tlSecondaryMeleeAttack1[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_FACE_ENEMY, 0.0 }, + { TASK_MELEE_ATTACK2, 0.0 } +}; + +new const g_tlSpecialAttack1[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_FACE_ENEMY, 0.0 }, + { TASK_SPECIAL_ATTACK1, 0.0 } +}; + +new const g_tlSpecialAttack2[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_FACE_ENEMY, 0.0 }, + { TASK_SPECIAL_ATTACK2, 0.0 } +}; + +new const g_tlChaseEnemy1[][MONSTER_TASK_DATA] = { + { TASK_SET_FAIL_SCHEDULE, MONSTER_SCHED_CHASE_ENEMY_FAILED }, + { TASK_GET_PATH_TO_ENEMY, 0.0 }, + { TASK_RUN_PATH, 0.0 }, + { TASK_WAIT_FOR_MOVEMENT, 0.0 } +}; + +new const g_tlChaseEnemyFailed[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_WAIT, 0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, 0.0 }, + { TASK_RUN_PATH, 0.0 }, + { TASK_WAIT_FOR_MOVEMENT, 0.0 }, + { TASK_REMEMBER, MEMORY_INCOVER }, + { TASK_FACE_ENEMY, 0.0 }, + { TASK_WAIT, 1.0 } +}; + +new const g_tlSmallFlinch[][MONSTER_TASK_DATA] = { + { TASK_REMEMBER, MEMORY_FLINCHED }, + { TASK_STOP_MOVING, 0.0 }, + { TASK_SMALL_FLINCH, 0.0 } +}; + +new const g_tlDie1[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_SOUND_DIE, 0.0 }, + { TASK_DIE, 0.0 } +}; + +new const g_tlVictoryDance[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_PLAY_SEQUENCE, ACT_VICTORY_DANCE }, + { TASK_WAIT, 0.0 } +}; + +new const g_tlBarnacleVictimGrab[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_PLAY_SEQUENCE, ACT_BARNACLE_HIT }, + { TASK_SET_ACTIVITY, ACT_BARNACLE_PULL }, + { TASK_WAIT_INDEFINITE, 0.0 },// just cycle barnacle pull anim while barnacle hoists. +}; + +new const g_tlBarnacleVictimChomp[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_PLAY_SEQUENCE, ACT_BARNACLE_CHOMP }, + { TASK_SET_ACTIVITY, ACT_BARNACLE_CHEW }, + { TASK_WAIT_INDEFINITE, 0.0 },// just cycle barnacle pull anim while barnacle hoists. +}; + +new const g_tlError[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_WAIT_INDEFINITE, 0.0} +}; + +new const g_tlScriptedWalk[][MONSTER_TASK_DATA] = { + { TASK_WALK_TO_TARGET, MONSTER_TARGET_MOVE_SCRIPTED }, + { TASK_WAIT_FOR_MOVEMENT, 0.0 }, + { TASK_PLANT_ON_SCRIPT, 0.0 }, + { TASK_FACE_SCRIPT, 0.0 }, + { TASK_FACE_IDEAL, 0.0 }, + { TASK_ENABLE_SCRIPT, 0.0 }, + { TASK_WAIT_FOR_SCRIPT, 0.0 }, + { TASK_PLAY_SCRIPT, 0.0 } +}; + +new const g_tlScriptedRun[][MONSTER_TASK_DATA] = { + { TASK_RUN_TO_TARGET, MONSTER_TARGET_MOVE_SCRIPTED }, + { TASK_WAIT_FOR_MOVEMENT, 0.0 }, + { TASK_PLANT_ON_SCRIPT, 0.0 }, + { TASK_FACE_SCRIPT, 0.0 }, + { TASK_FACE_IDEAL, 0.0 }, + { TASK_ENABLE_SCRIPT, 0.0 }, + { TASK_WAIT_FOR_SCRIPT, 0.0 }, + { TASK_PLAY_SCRIPT, 0.0 } +}; + +new const g_tlScriptedWait[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_WAIT_FOR_SCRIPT, 0.0 }, + { TASK_PLAY_SCRIPT, 0.0 } +}; + +new const g_tlScriptedFace[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_FACE_SCRIPT, 0.0 }, + { TASK_FACE_IDEAL, 0.0 }, + { TASK_WAIT_FOR_SCRIPT, 0.0 }, + { TASK_PLAY_SCRIPT, 0.0 } +}; + +new const g_tlTakeCoverFromOrigin[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_FIND_COVER_FROM_ORIGIN, 0.0 }, + { TASK_RUN_PATH, 0.0 }, + { TASK_WAIT_FOR_MOVEMENT, 0.0 }, + { TASK_REMEMBER, MEMORY_INCOVER }, + { TASK_TURN_LEFT, 179.0 } +}; + +new const g_tlTakeCoverFromBestSound[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_FIND_COVER_FROM_BEST_SOUND, 0.0 }, + { TASK_RUN_PATH, 0.0 }, + { TASK_WAIT_FOR_MOVEMENT, 0.0 }, + { TASK_REMEMBER, MEMORY_INCOVER }, + { TASK_TURN_LEFT, 179.0 } +}; + +new const g_tlTakeCoverFromEnemy[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_WAIT, 0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, 0.0 }, + { TASK_RUN_PATH, 0.0 }, + { TASK_WAIT_FOR_MOVEMENT, 0.0 }, + { TASK_REMEMBER, MEMORY_INCOVER }, + { TASK_FACE_ENEMY, 0.0 }, + { TASK_WAIT, 1.0 } +}; + +new const g_tlCower[][MONSTER_TASK_DATA] = { + { TASK_STOP_MOVING, 0.0 }, + { TASK_PLAY_SEQUENCE, ACT_COWER } +}; + +new Trie:g_itModelEyePosition = Invalid_Trie; +new Trie:g_itModelSequences = Invalid_Trie; + +new g_pCvarUseAstar; +new g_pCvarStepSize; + +new bool:g_bUseAstar; + +new g_pTrace; +new g_pHit = FM_NULLENT; + +new Float:g_vecAttackDir[3]; + +new Float:g_flGameTime = 0.0; + +new Struct:g_rgSharedSchedules[MONSTER_SHARED_SCHED] = { _:Invalid_Struct, ... }; + +new g_rgSounds[MAX_WORLD_SOUNDS][Sound]; + +public plugin_precache() { + #if defined _api_navsystem_included + Nav_Precache(); + #endif + + g_pCvarUseAstar = register_cvar("monster_use_astar", "1"); + g_pCvarStepSize = get_cvar_pointer("sv_stepsize"); + + InitSharedSchedules(); + + g_pTrace = create_tr2(); + + CE_Register(ENTITY_NAME, CEPreset_NPC, true); + + CE_RegisterHook(ENTITY_NAME, CEFunction_Init, "@Monster_Init"); + CE_RegisterHook(ENTITY_NAME, CEFunction_Spawned, "@Monster_Spawned"); + CE_RegisterHook(ENTITY_NAME, CEFunction_Remove, "@Monster_Remove"); + CE_RegisterHook(ENTITY_NAME, CEFunction_Kill, "@Monster_Kill"); + CE_RegisterHook(ENTITY_NAME, CEFunction_Think, "@Monster_Think"); + + CE_RegisterVirtualMethod(ENTITY_NAME, TakeDamage, "@Monster_TakeDamage", CE_MP_Cell, CE_MP_Cell, CE_MP_Float, CE_MP_Cell); + CE_RegisterVirtualMethod(ENTITY_NAME, IgnoreConditions, "@Monster_IgnoreConditions"); + CE_RegisterVirtualMethod(ENTITY_NAME, HandleAnimEvent, "@Monster_HandleAnimEvent", CE_MP_Cell, CE_MP_Array, 64); + CE_RegisterVirtualMethod(ENTITY_NAME, Classify, "@Monster_Classify"); + CE_RegisterVirtualMethod(ENTITY_NAME, SetState, "@Monster_SetState", CE_MP_Cell); + CE_RegisterVirtualMethod(ENTITY_NAME, MonsterInit, "@Monster_MonsterInit"); + CE_RegisterVirtualMethod(ENTITY_NAME, SetYawSpeed, "@Monster_SetYawSpeed"); + CE_RegisterMethod(ENTITY_NAME, SetThink, "@Monster_SetThink", CE_MP_String); + CE_RegisterMethod(ENTITY_NAME, CheckTraceHullAttack, "@Monster_CheckTraceHullAttack", CE_MP_Float, CE_MP_Float, CE_MP_Cell); + + CE_RegisterVirtualMethod(ENTITY_NAME, AlertSound, "@Monster_AlertSound"); + CE_RegisterVirtualMethod(ENTITY_NAME, DeathSound, "@Monster_DeathSound"); + CE_RegisterVirtualMethod(ENTITY_NAME, IdleSound, "@Monster_IdleSound"); + CE_RegisterVirtualMethod(ENTITY_NAME, PainSound, "@Monster_PainSound"); + CE_RegisterVirtualMethod(ENTITY_NAME, ShouldGibMonster, "@Monster_ShouldGibMonster", CE_MP_Cell); + CE_RegisterVirtualMethod(ENTITY_NAME, CallGibMonster, "@Monster_CallGibMonster"); + CE_RegisterVirtualMethod(ENTITY_NAME, GibMonster, "@Monster_GibMonster"); + CE_RegisterVirtualMethod(ENTITY_NAME, CalculateHitGroupDamage, "@Monster_CalculateHitGroupDamage", CE_MP_Float, CE_MP_Cell); + CE_RegisterVirtualMethod(ENTITY_NAME, EmitSound, "@Monster_EmitSound", CE_MP_Cell, CE_MP_FloatArray, 3, CE_MP_Cell, CE_MP_Float); + + CE_RegisterVirtualMethod(ENTITY_NAME, MeleeAttack1, "@Monster_MeleeAttack1"); + CE_RegisterVirtualMethod(ENTITY_NAME, MeleeAttack2, "@Monster_MeleeAttack2"); + + CE_RegisterVirtualMethod(ENTITY_NAME, IsCurTaskContinuousMove, "@Monster_IsCurTaskContinuousMove"); + CE_RegisterVirtualMethod(ENTITY_NAME, GetScheduleOfType, "@Monster_GetScheduleOfType", CE_MP_Cell); + CE_RegisterVirtualMethod(ENTITY_NAME, GetSchedule, "@Monster_GetSchedule"); + CE_RegisterVirtualMethod(ENTITY_NAME, SetActivity, "@Monster_SetActivity", CE_MP_Cell); + CE_RegisterVirtualMethod(ENTITY_NAME, ChangeSchedule, "@Monster_ChangeSchedule", CE_MP_Cell); + CE_RegisterVirtualMethod(ENTITY_NAME, RunTask, "@Monster_RunTask", CE_MP_Cell, CE_MP_Cell); + CE_RegisterVirtualMethod(ENTITY_NAME, StartTask, "@Monster_StartTask", CE_MP_Cell, CE_MP_Cell); + + CE_RegisterVirtualMethod(ENTITY_NAME, HandlePathTask, "@Monster_HandlePathTask"); + CE_RegisterVirtualMethod(ENTITY_NAME, MoveExecute, "@Monster_MoveExecute", CE_MP_Cell, CE_MP_FloatArray, 3, CE_MP_Float); + + CE_RegisterMethod(ENTITY_NAME, SetConditions, "@Monster_SetConditions", CE_MP_Cell); + CE_RegisterMethod(ENTITY_NAME, ClearConditions, "@Monster_ClearConditions", CE_MP_Cell); + CE_RegisterMethod(ENTITY_NAME, HasConditions, "@Monster_HasConditions", CE_MP_Cell); + CE_RegisterMethod(ENTITY_NAME, HasAllConditions, "@Monster_HasAllConditions", CE_MP_Cell); + CE_RegisterMethod(ENTITY_NAME, Remember, "@Monster_Remember", CE_MP_Cell); + CE_RegisterMethod(ENTITY_NAME, Forget, "@Monster_Forget", CE_MP_Cell); + CE_RegisterMethod(ENTITY_NAME, HasMemory, "@Monster_HasMemory", CE_MP_Cell); + CE_RegisterMethod(ENTITY_NAME, HasAllMemory, "@Monster_HasAllMemory", CE_MP_Cell); + + CE_RegisterMethod(ENTITY_NAME, GetSharedSchedule, "@Monster_GetSharedSchedule", CE_MP_Cell); + CE_RegisterMethod(ENTITY_NAME, MoveToEnemy, "@Monster_MoveToEnemy", CE_MP_Cell, CE_MP_Float); + CE_RegisterMethod(ENTITY_NAME, MoveToTarget, "@Monster_MoveToTarget", CE_MP_Cell, CE_MP_Float); + CE_RegisterMethod(ENTITY_NAME, MoveToLocation, "@Monster_MoveToLocation", CE_MP_Cell, CE_MP_Float, CE_MP_FloatArray, 3); + CE_RegisterMethod(ENTITY_NAME, StepSound, "@Monster_StepSound"); +} + +@Monster_SetThink(this, const szCallback[]) { + new iPluginId = -1; + new iFunctionId = -1; + + if (!equal(szCallback, NULL_STRING)) { + iPluginId = CE_GetCallPluginId(); + iFunctionId = get_func_id(szCallback, iPluginId); + } + + CE_SetMember(this, m_iThinkFunctionId, iFunctionId); + CE_SetMember(this, m_iThinkPluginId, iPluginId); +} + +public plugin_init() { + register_plugin(PLUGIN, VERSION, AUTHOR); + + RegisterHam(Ham_TakeDamage, CE_BASE_CLASSNAME, "HamHook_Base_TakeDamage_Post", .Post = 1); + + bind_pcvar_num(g_pCvarUseAstar, g_bUseAstar); + + RegisterHam(Ham_Classify, CE_BASE_CLASSNAME, "HamHook_Base_Classify", .Post = 0); + RegisterHam(Ham_TraceAttack, CE_BASE_CLASSNAME, "HamHook_Base_TraceAttack", .Post = 0); +} + +public HamHook_Base_Classify(pEntity) { + if (CE_IsInstanceOf(pEntity, ENTITY_NAME)) { + new iClass = CE_CallMethod(pEntity, Classify); + SetHamReturnInteger(iClass); + return HAM_SUPERCEDE; + } + + return HAM_IGNORED; +} + +@Monster_TraceAttack(this, pAttacker, Float:flDamage, Float:vecDirection[3], pTrace, iDamageBits) { + static Float:flTakeDamage; pev(this, pev_takedamage, flTakeDamage); + if (flTakeDamage == DAMAGE_NO) return; + + static iHitGroup; iHitGroup = get_tr2(pTrace, TR_iHitgroup); + CE_SetMember(this, m_iLastHitGroup, iHitGroup); +} + +Float:@Monster_CalculateHitGroupDamage(this, Float:flDamage, iHitGroup) { + switch (iHitGroup) { + case HITGROUP_HEAD: return flDamage * 3; + } + + return flDamage; +} + +public HamHook_Base_TraceAttack(pEntity, pAttacker, Float:flDamage, Float:vecDirection[3], pTrace, iDamageBits) { + if (CE_IsInstanceOf(pEntity, ENTITY_NAME)) { + @Monster_TraceAttack(pEntity, pAttacker, flDamage, vecDirection, pTrace, iDamageBits); + + static iHitGroup; iHitGroup = get_tr2(pTrace, TR_iHitgroup); + + flDamage = CE_CallMethod(pEntity, CalculateHitGroupDamage, flDamage, iHitGroup); + + SetHamParamFloat(3, flDamage); + + return HAM_HANDLED; + } + + return HAM_IGNORED; +} + +public plugin_end() { + DestroySharedSchedule(); + free_tr2(g_pTrace); + + if (g_itModelSequences != Invalid_Trie) { + TrieDestroy(g_itModelSequences); + } + + if (g_itModelEyePosition != Invalid_Trie) { + TrieDestroy(g_itModelEyePosition); + } +} + +/*--------------------------------[ Methods ]--------------------------------*/ + +@Monster_Init(this) { + g_flGameTime = get_gametime(); + + CE_SetMemberVec(this, CE_MEMBER_MINS, Float:{-12.0, -12.0, -32.0}); + CE_SetMemberVec(this, CE_MEMBER_MAXS, Float:{12.0, 12.0, 32.0}); + + CE_SetMember(this, m_irgRoute, ArrayCreate(_:MONSTER_WAYPOINT, 8)); + CE_SetMember(this, m_irgOldEnemies, ArrayCreate(_:MONSTER_ENEMY, 8)); + CE_SetMember(this, m_irgSequences, Invalid_Array); + + CE_SetMember(this, m_iHintNode, NO_NODE); + CE_SetMember(this, m_iMemory, MEMORY_CLEAR); + CE_SetMember(this, m_pEnemy, FM_NULLENT); + CE_SetMember(this, m_flDistTooFar, 1024.0); + CE_SetMember(this, m_flDistLook, 2048.0); + CE_SetMember(this, m_iTaskStatus, 0); + CE_SetMember(this, m_iScheduleIndex, 0); + CE_SetMember(this, m_iDamageType, 0); + CE_SetMember(this, m_iMovementActivity, 0); + CE_SetMember(this, m_iRouteIndex, 0); + CE_SetMember(this, m_iScriptState, 0); + CE_SetMemberVec(this, m_vecLastPosition, Float:{0.0, 0.0, 0.0}); + CE_SetMember(this, m_flMoveWaitFinished, 0.0); + CE_SetMember(this, m_flWaitFinished, 0.0); + CE_SetMember(this, m_pTargetEnt, FM_NULLENT); + CE_SetMemberVec(this, m_vecMoveGoal, Float:{0.0, 0.0, 0.0}); + CE_SetMemberVec(this, m_vecEnemyLKP, Float:{0.0, 0.0, 0.0}); + CE_SetMember(this, m_pCine, FM_NULLENT); + CE_SetMember(this, m_iFailSchedule, MONSTER_SCHED_NONE); + CE_SetMember(this, m_bSequenceFinished, false); + CE_SetMember(this, m_iActivity, 0); + CE_SetMember(this, m_iMovementGoal, 0); + CE_SetMember(this, m_pPathTask, Invalid_NavBuildPathTask); + CE_SetMember(this, m_flMoveWaitTime, 0.0); + CE_SetMember(this, m_flHungryTime, 0.0); + CE_SetMember(this, m_pGoalEnt, FM_NULLENT); + + CE_SetMember(this, m_flFieldOfView, 0.5); + CE_SetMember(this, m_bSequenceLoops, false); + CE_SetMember(this, m_flFrameRate, 0.0); + CE_SetMember(this, m_flGroundSpeed, 0.0); + CE_SetMember(this, m_sSchedule, Invalid_Struct); + CE_SetMember(this, m_iCapability, 0); + CE_SetMember(this, m_irgSequences, Invalid_Array); + + CE_SetMember(this, m_flRangeAttack1Range, 784.0); + CE_SetMember(this, m_flRangeAttack2Range, 512.0); + CE_SetMember(this, m_flMeleeAttack1Range, 64.0); + CE_SetMember(this, m_flMeleeAttack2Range, 64.0); + CE_SetMember(this, m_flMeleeAttack1Damage, 0.0); + CE_SetMember(this, m_flMeleeAttack2Damage, 0.0); + + CE_SetMember(this, m_flStepSize, 16.0); + + new iStepSize = max(get_pcvar_num(g_pCvarStepSize), 16); + CE_SetMember(this, m_flStepHeight, float(iStepSize)); +} + +@Monster_Spawned(this) { + g_flGameTime = get_gametime(); + + static Array:irgSequences; irgSequences = Invalid_Array; + static Float:vecEyePosition[3]; + + + static szModel[MAX_RESOURCE_PATH_LENGTH]; CE_GetMemberString(this, CE_MEMBER_MODEL, szModel, charsmax(szModel)); + if (!equal(szModel, NULL_STRING)) { + LoadModel(szModel, irgSequences, vecEyePosition); + } + + CE_SetMember(this, m_irgSequences, irgSequences); + CE_SetMemberVec(this, m_vecEyePosition, vecEyePosition); + + CE_CallMethod(this, MonsterInit); +} + +@Monster_Kill(this, pKiller, iGib) { + g_flGameTime = get_gametime(); + + if (@Monster_HasMemory(this, MEMORY_KILLED)) { + if (CE_CallMethod(this, ShouldGibMonster, iGib)) { + CE_CallMethod(this, CallGibMonster); + } + + return PLUGIN_HANDLED; + } + + @Monster_Remember(this, MEMORY_KILLED); + + emit_sound(this, CHAN_WEAPON, "common/null.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM); + CE_SetMember(this, m_iIdealMonsterState, MONSTER_STATE_DEAD); + @Monster_SetConditions(this, COND_LIGHT_DAMAGE); + + // tell owner ( if any ) that we're dead.This is mostly for MonsterMaker functionality. + // CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + // if ( pOwner ) { + // pOwner->DeathNotice( pev ); + // } + + if (CE_CallMethod(this, ShouldGibMonster, iGib)) { + CE_CallMethod(this, CallGibMonster); + return PLUGIN_HANDLED; + } else if (pev(this, pev_flags) & FL_MONSTER) { + // SetTouch( NULL ); + @Monster_BecomeDead(this); + } + + CE_SetMember(this, m_iIdealMonsterState, MONSTER_STATE_DEAD); + + return PLUGIN_HANDLED; +} + +bool:@Monster_ShouldGibMonster(this, iGib) { + return false; +} + +@Monster_Remove(this) { + @Monster_RouteNew(this); + + new Array:irgRoute = CE_GetMember(this, m_irgRoute); + ArrayDestroy(irgRoute); + + new Array:irgOldEnemies = CE_GetMember(this, m_irgOldEnemies); + ArrayDestroy(irgOldEnemies); +} + +@Monster_Think(this) { + g_flGameTime = get_gametime(); + + new iFunctionId = CE_GetMember(this, m_iThinkFunctionId); + new iPluginId = CE_GetMember(this, m_iThinkPluginId); + + if (iFunctionId == -1 || iPluginId == -1) return; + + callfunc_begin_i(iFunctionId, iPluginId); + callfunc_push_int(this); + callfunc_end(); +} + +@Monster_ResetSequenceInfo(this) { + static Float:flFrameRate; flFrameRate = 0.0; + static Float:flGroundSpeed; flGroundSpeed = 0.0; + + @Monster_GetSequenceInfo(this, flFrameRate, flGroundSpeed); + CE_SetMember(this, m_flFrameRate, flFrameRate); + CE_SetMember(this, m_flGroundSpeed, flGroundSpeed); + + set_pev(this, pev_animtime, g_flGameTime); + set_pev(this, pev_framerate, 1.0); + CE_SetMember(this, m_bSequenceLoops, !!(@Monster_GetSequenceFlags(this) & STUDIO_LOOPING)); + CE_SetMember(this, m_bSequenceFinished, false); + CE_SetMember(this, m_flLastEventCheck, g_flGameTime); +} + +@Monster_GetSequenceInfo(this, &Float:flFrameRate, &Float:flGroundSpeed) { + static iSequence; iSequence = pev(this, pev_sequence); + + static Array:irgSequences; irgSequences = CE_GetMember(this, m_irgSequences); + if (irgSequences == Invalid_Array) return; + + if (iSequence >= ArraySize(irgSequences)) { + flFrameRate = 0.0; + flGroundSpeed = 0.0; + return; + } + + static Float:flFPS; flFPS = ArrayGetCell(irgSequences, iSequence, _:Sequence_FPS); + static iFramesNum; iFramesNum = ArrayGetCell(irgSequences, iSequence, _:Sequence_FramesNum); + + if (iFramesNum > 1) { + static Float:vecLinearMovement[3]; + + for (new i = 0; i < 3; ++i) { + vecLinearMovement[i] = ArrayGetCell(irgSequences, iSequence, _:Sequence_LinearMovement + i); + } + + flFrameRate = UTIL_FrameRateToFrameRatioRate(flFPS, iFramesNum); + flGroundSpeed = xs_vec_len(vecLinearMovement); + flGroundSpeed = flGroundSpeed * (flFPS / iFramesNum); + } else { + flFrameRate = 255.0; + flGroundSpeed = 0.0; + } +} + +Float:@Monster_FrameAdvance(this, Float:flInterval) { + + static Float:flAnimTime; pev(this, pev_animtime, flAnimTime); + static Float:flFrameRate; pev(this, pev_framerate, flFrameRate); + static Float:flFrame; pev(this, pev_frame, flFrame); + + static Float:flSequenceFrameRate; flSequenceFrameRate = CE_GetMember(this, m_flFrameRate); + static bool:bSequenceLoops; bSequenceLoops = CE_GetMember(this, m_bSequenceLoops); + + if (!flInterval && flAnimTime) { + flInterval = (g_flGameTime - flAnimTime); + } + + flFrame += (flSequenceFrameRate * flFrameRate) * flInterval; + + if (flFrame < 0.0 || flFrame > 255.0) { + if (bSequenceLoops) { + flFrame = UTIL_FloatMod(flFrame, 255.0); + } else { + flFrame = floatclamp(flFrame, 0.0, 255.0); + } + + CE_SetMember(this, m_bSequenceFinished, true); + } + + set_pev(this, pev_frame, flFrame); + set_pev(this, pev_animtime, g_flGameTime); + + return flInterval; +} + +@Monster_DispatchAnimEvents(this, Float:flInterval) { + static Array:irgSequences; irgSequences = CE_GetMember(this, m_irgSequences); + if (irgSequences == Invalid_Array) return; + + + static iSequence; iSequence = pev(this, pev_sequence); + + static Array:irgEvents; irgEvents = Array:ArrayGetCell(irgSequences, iSequence, _:Sequence_Events); + if (irgEvents == Invalid_Array) return; + + static Float:flFrame; pev(this, pev_frame, flFrame); + static Float:flFrameRate; pev(this, pev_framerate, flFrameRate); + static Float:flSequenceFrameRate; flSequenceFrameRate = CE_GetMember(this, m_flFrameRate); + + static Float:flStart; flStart = flFrame - (flSequenceFrameRate * flFrameRate * flInterval); + static Float:flEnd; flEnd = flFrame; + + CE_SetMember(this, m_flLastEventCheck, g_flGameTime); + + static rgEvent[ModelEvent]; + + new iEvent = 0; + while ((iEvent = @Monster_GetAnimationEvent(this, rgEvent, flStart, flEnd, iEvent)) != 0) { + CE_CallMethod(this, HandleAnimEvent, rgEvent[ModelEvent_Event], rgEvent[ModelEvent_Options]); + } +} + +@Monster_GetAnimationEvent(this, rgEvent[ModelEvent], Float:flStart, Float:flEnd, iStartOffset) { + static iSequence; iSequence = pev(this, pev_sequence); + static Array:irgSequences; irgSequences = CE_GetMember(this, m_irgSequences); + + static Array:irgEvents; irgEvents = Array:ArrayGetCell(irgSequences, iSequence, _:Sequence_Events); + if (irgEvents == Invalid_Array) return 0; + + static iEventsNum; iEventsNum = ArraySize(irgEvents); + if (iEventsNum == 0 || iStartOffset > iEventsNum) return 0; + + static iFlags; iFlags = ArrayGetCell(irgSequences, iSequence, _:Sequence_Flags); + static iFramesNum; iFramesNum = ArrayGetCell(irgSequences, iSequence, _:Sequence_FramesNum); + static Float:flFramesNum; flFramesNum = float(iFramesNum); + + flStart = UTIL_FrameRatioToFrame(flStart, iFramesNum); + flEnd = UTIL_FrameRatioToFrame(flEnd, iFramesNum); + + if (iFlags & STUDIO_LOOPING) { + static Float:flOffset; flOffset = UTIL_FloatMod(floatabs(flStart), (flFramesNum - 1.0)); + static Float:flFixedStart; flFixedStart = flStart; + + if (flStart < 0) { + flFixedStart = (flFramesNum - 1.0) - flOffset; + } else if (flStart > (flFramesNum - 1.0)) { + flFixedStart = 0.0 + flOffset; + } + + flEnd += (flFixedStart - flStart); + flStart = flFixedStart; + } else { + // flStart = floatclamp(flStart, 0.0, flFramesNum - 1.0); + // flEnd = floatclamp(flStart, 0.0, flFramesNum - 1.0); + } + + static Float:flCurrentFrame; flCurrentFrame = flStart; + + do { + static Float:flNormalizedStart; flNormalizedStart = UTIL_FloatMod(flCurrentFrame, (flFramesNum - 1.0)); + static Float:flNormalizedEnd; flNormalizedEnd = floatmin(flNormalizedStart + (flEnd - flCurrentFrame), flFramesNum - 1.0); + + for (new iEvent = iStartOffset; iEvent < iEventsNum; ++iEvent) { + static iEventId; iEventId = ArrayGetCell(irgEvents, iEvent, _:ModelEvent_Event); + if (iEventId >= EVENT_CLIENT) continue; + + static Float:flFrame; flFrame = float(ArrayGetCell(irgEvents, iEvent, _:ModelEvent_Frame)); + + if (flFrame < flNormalizedStart) continue; + if (flFrame > flNormalizedEnd) continue; + + ArrayGetArray(irgEvents, iEvent, rgEvent[any:0], _:ModelEvent); + return iEvent + 1; + } + + flCurrentFrame += flNormalizedEnd - flNormalizedStart; + } while (flCurrentFrame < flEnd); + + return 0; +} + +stock Float:UTIL_FrameRateToFrameRatioRate(Float:flFrameRate, iFramesNum) { + return (flFrameRate / iFramesNum) * 255.0; +} + +stock Float:UTIL_FrameToFrameRatio(iFrame, iFramesNum) { + return 255.0 * ((float(iFrame) + 1.0) / (iFramesNum)); +} + +stock Float:UTIL_FrameRatioToFrame(Float:flRatio, iFramesNum) { + return floatmax((flRatio * (float(iFramesNum) / 255.0)) - 1.0, 0.0); +} + +@Monster_HandleAnimEvent(this, iEventId, const rgOptions[]) { + switch (iEventId) { + case SCRIPT_EVENT_DEAD: { + if (CE_GetMember(this, m_iMonsterState) == MONSTER_STATE_SCRIPT) { + set_pev(this, pev_deadflag, DEAD_DYING); + set_pev(this, pev_health, 0.0); + } + } + case SCRIPT_EVENT_NOT_DEAD: { + static Float:flMaxHealth; pev(this, pev_max_health, flMaxHealth); + + if (CE_GetMember(this, m_iMonsterState) == MONSTER_STATE_SCRIPT) { + set_pev(this, pev_deadflag, DEAD_NO); + set_pev(this, pev_health, flMaxHealth); + } + } + case SCRIPT_EVENT_SOUND: { + if (!equal(rgOptions, NULL_STRING)) { + emit_sound(this, CHAN_BODY, rgOptions, VOL_NORM, ATTN_IDLE, 0, PITCH_NORM); + } + } + + case SCRIPT_EVENT_SOUND_VOICE: { + if (!equal(rgOptions, NULL_STRING)) { + emit_sound(this, CHAN_VOICE, rgOptions, VOL_NORM, ATTN_IDLE, 0, PITCH_NORM); + } + } + case SCRIPT_EVENT_SENTENCE: { + // TODO: Implement + if (!equal(rgOptions, NULL_STRING)) { + // SENTENCEG_PlayRndSz( edict(), rgOptions, VOL_NORM, ATTN_IDLE, 0, PITCH_NORM); + } + } + case SCRIPT_EVENT_FIREEVENT: { + if (!equal(rgOptions, NULL_STRING)) { + FireTargets(rgOptions, this, this, USE_TOGGLE, 0.0); + } + } + case SCRIPT_EVENT_NOINTERRUPT: { + static pCine; pCine = CE_GetMember(this, m_pCine); + + if (pCine != FM_NULLENT) { + CE_CallMethod(pCine, "AllowInterrupt", false); + } + } + case SCRIPT_EVENT_CANINTERRUPT: { + static pCine; pCine = CE_GetMember(this, m_pCine); + + if (pCine != FM_NULLENT) { + CE_CallMethod(pCine, "AllowInterrupt", true); + } + } + case MONSTER_EVENT_BODYDROP_HEAVY: + if (pev(this, pev_flags) & FL_ONGROUND) { + if (!random(2)) { + emit_sound(this, CHAN_BODY, "common/bodydrop3.wav", VOL_NORM, ATTN_NORM, 0, 90); + } else { + emit_sound(this, CHAN_BODY, "common/bodydrop4.wav", VOL_NORM, ATTN_NORM, 0, 90); + } + } + + case MONSTER_EVENT_BODYDROP_LIGHT: + if (pev(this, pev_flags) & FL_ONGROUND) { + if (!random(2)) { + emit_sound(this, CHAN_BODY, "common/bodydrop3.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM); + } else { + emit_sound(this, CHAN_BODY, "common/bodydrop4.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM); + } + } + + case MONSTER_EVENT_SWISHSOUND: { + emit_sound(this, CHAN_BODY, "zombie/claw_miss2.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM); + } + } +} + +Float:@Monster_GetPathCost(this, NavArea:newArea, NavArea:prevArea, iMoveFlags) { + if (prevArea == Invalid_NavArea) return 1.0; + + static Float:vecMins[3]; pev(this, pev_mins, vecMins); + + static pTargetEnt; pTargetEnt = FM_NULLENT; + if (iMoveFlags & MF_TO_ENEMY) { + pTargetEnt = CE_GetMember(this, m_pEnemy); + } else if (iMoveFlags & MF_TO_TARGETENT) { + pTargetEnt = CE_GetMember(this, m_pTargetEnt); + } + + static Float:vecSrc[3]; + Nav_Area_GetCenter(prevArea, vecSrc); + vecSrc[2] += -vecMins[2]; + + static Float:vecMiddle[3]; + Nav_Area_GetClosestPointOnArea(newArea, vecSrc, vecMiddle); + vecMiddle[2] += -vecMins[2]; + + static Float:vecTarget[3]; + Nav_Area_GetCenter(newArea, vecTarget); + vecTarget[2] += -vecMins[2]; + + static Float:flDist; + if (@Monster_CheckLocalMove(this, vecSrc, vecMiddle, pTargetEnt, false, flDist) != MONSTER_LOCALMOVE_VALID) { + return -1.0; + } + + if (@Monster_CheckLocalMove(this, vecMiddle, vecTarget, pTargetEnt, false, flDist) != MONSTER_LOCALMOVE_VALID) { + return -1.0; + } + + static Float:flCost; flCost = get_distance_f(vecSrc, vecTarget); + + return flCost; +} + +/*--------------------------------[ Function ]--------------------------------*/ + +LoadModel(const szModel[], &Array:irgSequences, Float:vecEyePosition[3]) { + g_itModelSequences = g_itModelSequences == Invalid_Trie ? TrieCreate() : g_itModelSequences; + g_itModelEyePosition = g_itModelEyePosition == Invalid_Trie ? TrieCreate() : g_itModelEyePosition; + + if (!TrieKeyExists(g_itModelSequences, szModel)) { + new rgModel[Model]; UTIL_LoadModel(szModel, rgModel); + TrieSetCell(g_itModelSequences, szModel, rgModel[Model_Sequences]); + TrieSetArray(g_itModelEyePosition, szModel, rgModel[Model_EyePosition], 3); + } + + TrieGetCell(g_itModelSequences, szModel, irgSequences); + TrieGetArray(g_itModelEyePosition, szModel, vecEyePosition, 3); +} + +/*--------------------------------[ Hooks ]--------------------------------*/ + +public HamHook_Base_TakeDamage_Post(pEntity, pInflictor, pAttacker, Float:flDamage, iDamageBits) { + if (CE_IsInstanceOf(pEntity, ENTITY_NAME)) { + CE_CallMethod(pEntity, TakeDamage, pInflictor, pAttacker, flDamage, iDamageBits); + return HAM_SUPERCEDE; + } + + return HAM_IGNORED; +} + +/*--------------------------------[ Callbacks ]--------------------------------*/ + +#if defined _api_navsystem_included + public Float:NavPathCost(NavBuildPathTask:pTask, NavArea:newArea, NavArea:prevArea) { + static pEntity; pEntity = Nav_Path_FindTask_GetUserToken(pTask); + if (!pEntity) return 1.0; + + return @Monster_GetPathCost(pEntity, newArea, prevArea, MF_TO_ENEMY); + // return CE_CallMethod(pEntity, GetPathCost, newArea, prevArea); + } + + public NavPathCallback(NavBuildPathTask:pTask) { + new pEntity = Nav_Path_FindTask_GetUserToken(pTask); + + return CE_CallMethod(pEntity, HandlePathTask); + } +#endif + +/*--------------------------------[ Stocks ]--------------------------------*/ + +stock bool:UTIL_SetSequence(pEntity, iSequence) { + if (pev(pEntity, pev_sequence) == iSequence) return false; + + set_pev(pEntity, pev_frame, 0); + set_pev(pEntity, pev_framerate, 1.0); + set_pev(pEntity, pev_animtime, get_gametime()); + set_pev(pEntity, pev_sequence, iSequence); + + return true; +} + +stock Float:UTIL_ApproachAngle(Float:flTarget, Float:flValue, Float:flSpeed) { + flTarget = UTIL_AngleMod(flTarget); + flValue = UTIL_AngleMod(flValue); + flSpeed = floatabs(flSpeed); + + static Float:flDelta; flDelta = UTIL_AngleDiff(flTarget, flValue); + + flValue += floatclamp(flDelta, -flSpeed, flSpeed); + + return UTIL_AngleMod(flValue); +} + +stock Float:UTIL_AngleDiff(Float:flDestAngle, Float:flSrcAngle) { + static Float:flDelta; flDelta = flDestAngle - flSrcAngle; + + if (flDestAngle > flSrcAngle) { + if (flDelta >= 180.0) { + flDelta -= 360.0; + } + } else { + if (flDelta <= -180.0) { + flDelta += 360.0; + } + } + + return flDelta; +} + +stock Float:UTIL_AngleMod(Float:flAngle) { + return (360.0/65536) * (floatround(flAngle * (65536.0/360.0), floatround_floor) & 65535); +} + +stock bool:UTIL_CheckEntitiesLevel(pEntity, pOther) { + static Float:vecAbsMin[3]; pev(pEntity, pev_absmin, vecAbsMin); + static Float:vecAbsMax[3]; pev(pEntity, pev_absmax, vecAbsMax); + static Float:vecOtherAbsMin[3]; pev(pOther, pev_absmin, vecOtherAbsMin); + static Float:vecOtherAbsMax[3]; pev(pOther, pev_absmax, vecOtherAbsMax); + + if (vecAbsMax[2] < vecOtherAbsMin[2]) return false; + if (vecAbsMin[2] > vecOtherAbsMax[2]) return false; + + return true; +} + +stock Float:UTIL_FloatMod(Float:flValue, Float:flDelimiter) { + return flValue - (float(floatround(flValue / flDelimiter, floatround_floor)) * flDelimiter); +} + +UTIL_LoadModel(const szModel[], rgModel[Model]) { + new iFile = fopen(szModel, "rb", true, "GAME"); + + if (!iFile) { + iFile = fopen(szModel, "rb", true, "DEFAULTGAME"); + } + + if (!iFile) return 0; + + // https://github.com/dreamstalker/rehlds/blob/65c6ce593b5eabf13e92b03352e4b429d0d797b0/rehlds/public/rehlds/studio.h#L68 + + fseek(iFile, (BLOCK_INT * 3) + (BLOCK_CHAR * 64), SEEK_SET); + + fread_blocks(iFile, rgModel[Model_EyePosition], 3, BLOCK_INT); + + // Got to "numseq" position of the studiohdr_t structure + fseek(iFile, 164, SEEK_SET); + + new iSeqNum; fread(iFile, iSeqNum, BLOCK_INT); + if (!iSeqNum) return 0; + + new iSeqIndex; fread(iFile, iSeqIndex, BLOCK_INT); + fseek(iFile, iSeqIndex, SEEK_SET); + + rgModel[Model_Sequences] = ArrayCreate(_:Sequence); + + for (new iSequence = 0; iSequence < iSeqNum; iSequence++) { + new rgSequence[Sequence]; + rgSequence[Sequence_Events] = Invalid_Array; + + fseek(iFile, iSeqIndex + (iSequence * 176) + (BLOCK_CHAR * 32), SEEK_SET); + fread(iFile, rgSequence[Sequence_FPS], BLOCK_INT); + fread(iFile, rgSequence[Sequence_Flags], BLOCK_INT); + fread(iFile, rgSequence[Sequence_Activity], BLOCK_INT); + fread(iFile, rgSequence[Sequence_ActivityWeight], BLOCK_INT); + + new iNumEvents; fread(iFile, iNumEvents, BLOCK_INT); + new iEventindex; fread(iFile, iEventindex, BLOCK_INT); + + fread(iFile, rgSequence[Sequence_FramesNum], BLOCK_INT); + + fseek(iFile, BLOCK_INT * 4, SEEK_CUR); + + fread_blocks(iFile, rgSequence[Sequence_LinearMovement], 3, BLOCK_INT); + + if (iNumEvents) { + rgSequence[Sequence_Events] = ArrayCreate(_:ModelEvent); + + fseek(iFile, iEventindex, SEEK_SET); + + for (new iEvent = 0; iEvent < iNumEvents; iEvent++) { + new rgEvent[ModelEvent]; + fread(iFile, rgEvent[ModelEvent_Frame], BLOCK_INT); + fread(iFile, rgEvent[ModelEvent_Event], BLOCK_INT); + fseek(iFile, BLOCK_INT, SEEK_CUR); + fread_blocks(iFile, rgEvent[ModelEvent_Options], sizeof(rgEvent[ModelEvent_Options]), BLOCK_CHAR); + + ArrayPushArray(rgSequence[Sequence_Events], rgEvent[any:0]); + } + } + + ArrayPushArray(rgModel[Model_Sequences], rgSequence[any:0]); + } + + fclose(iFile); + + return 1; +} + +InitSharedSchedules() { + g_rgSharedSchedules[MONSTER_SHARED_SCHED_WAIT_SCRIPT] = CreateSchedule(g_tlScriptedWait, sizeof(g_tlScriptedWait), SCRIPT_BREAK_CONDITIONS, 0, MONSTER_SHARED_SCHED_WAIT_SCRIPT); + g_rgSharedSchedules[MONSTER_SHARED_SCHED_WALK_TO_SCRIPT] = CreateSchedule(g_tlScriptedWalk, sizeof(g_tlScriptedWalk), SCRIPT_BREAK_CONDITIONS, 0, MONSTER_SHARED_SCHED_WALK_TO_SCRIPT); + g_rgSharedSchedules[MONSTER_SHARED_SCHED_RUN_TO_SCRIPT] = CreateSchedule(g_tlScriptedRun, sizeof(g_tlScriptedRun), SCRIPT_BREAK_CONDITIONS, 0, MONSTER_SHARED_SCHED_RUN_TO_SCRIPT); + g_rgSharedSchedules[MONSTER_SHARED_SCHED_FACE_SCRIPT] = CreateSchedule(g_tlScriptedFace, sizeof(g_tlScriptedFace), SCRIPT_BREAK_CONDITIONS, 0, MONSTER_SHARED_SCHED_FACE_SCRIPT); + g_rgSharedSchedules[MONSTER_SHARED_SCHED_ERROR] = CreateSchedule(g_tlError, sizeof(g_tlError), 0, 0, MONSTER_SHARED_SCHED_ERROR); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_ACTIVE_IDLE] = CreateSchedule( + g_tlActiveIdle, + sizeof(g_tlActiveIdle), + (COND_NEW_ENEMY | COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE | COND_PROVOKED | COND_HEAR_SOUND), + (SOUND_COMBAT | SOUND_WORLD | SOUND_PLAYER | SOUND_DANGER), + MONSTER_SHARED_SCHED_ACTIVE_IDLE + ); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_IDLE_STAND] = CreateSchedule( + g_tlIdleStand1, + sizeof(g_tlIdleStand1), + (COND_NEW_ENEMY | COND_SEE_FEAR | COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE | COND_HEAR_SOUND | COND_SMELL_FOOD | COND_SMELL | COND_PROVOKED), + (SOUND_COMBAT | SOUND_WORLD | SOUND_PLAYER | SOUND_DANGER | SOUND_MEAT | SOUND_CARCASS | SOUND_GARBAGE), + MONSTER_SHARED_SCHED_IDLE_STAND + ); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_IDLE_WALK] = CreateSchedule( + g_tlIdleWalk1, + sizeof(g_tlIdleWalk1), + (COND_NEW_ENEMY | COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE | COND_HEAR_SOUND | COND_SMELL_FOOD | COND_SMELL | COND_PROVOKED), + (SOUND_COMBAT | SOUND_MEAT | SOUND_CARCASS | SOUND_GARBAGE), + MONSTER_SHARED_SCHED_IDLE_WALK + ); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_WAIT_TRIGGER] = CreateSchedule( + g_tlIdleStand1, + sizeof(g_tlIdleStand1), + (COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE), + 0, + MONSTER_SHARED_SCHED_WAIT_TRIGGER + ); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_WAKE_ANGRY] = CreateSchedule(g_tlWakeAngry1, sizeof(g_tlWakeAngry1), 0, 0, MONSTER_SHARED_SCHED_WAKE_ANGRY); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_ALERT_FACE] = CreateSchedule( + g_tlAlertFace1, + sizeof(g_tlAlertFace1), + (COND_NEW_ENEMY | COND_SEE_FEAR | COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE | COND_PROVOKED), + 0, + MONSTER_SHARED_SCHED_ALERT_FACE + ); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_ALERT_STAND] = CreateSchedule( + g_tlAlertStand1, + sizeof(g_tlAlertStand1), + ( + COND_NEW_ENEMY | + COND_SEE_ENEMY | + COND_SEE_FEAR | + COND_LIGHT_DAMAGE | + COND_HEAVY_DAMAGE | + COND_PROVOKED | + COND_SMELL | + COND_SMELL_FOOD | + COND_HEAR_SOUND + ), + ( + SOUND_COMBAT | + SOUND_WORLD | + SOUND_PLAYER | + SOUND_DANGER | + SOUND_MEAT | + SOUND_CARCASS | + SOUND_GARBAGE + ), + MONSTER_SHARED_SCHED_ALERT_STAND + ); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_COMBAT_STAND] = CreateSchedule( + g_tlCombatStand1, + sizeof(g_tlCombatStand1), + (COND_NEW_ENEMY | COND_ENEMY_DEAD | COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE | COND_CAN_ATTACK), + 0, + MONSTER_SHARED_SCHED_COMBAT_STAND + ); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_COMBAT_FACE] = CreateSchedule( + g_tlCombatFace1, + sizeof(g_tlCombatFace1), + (COND_CAN_ATTACK | COND_NEW_ENEMY | COND_ENEMY_DEAD), + 0, + MONSTER_SHARED_SCHED_COMBAT_FACE + ); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_CHASE_ENEMY] = CreateSchedule( + g_tlChaseEnemy1, + sizeof(g_tlChaseEnemy1), + ( + COND_NEW_ENEMY | + COND_CAN_RANGE_ATTACK1 | + COND_CAN_MELEE_ATTACK1 | + COND_CAN_RANGE_ATTACK2 | + COND_CAN_MELEE_ATTACK2 | + COND_TASK_FAILED | + COND_HEAR_SOUND + ), + SOUND_DANGER, + MONSTER_SHARED_SCHED_CHASE_ENEMY + ); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_FAIL] = CreateSchedule(g_tlFail, sizeof(g_tlFail), COND_CAN_ATTACK, 0, MONSTER_SHARED_SCHED_FAIL); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_SMALL_FLINCH] = CreateSchedule(g_tlSmallFlinch, sizeof(g_tlSmallFlinch), 0, 0, MONSTER_SHARED_SCHED_SMALL_FLINCH); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_ALERT_SMALL_FLINCH] = CreateSchedule(g_tlAlertSmallFlinch, sizeof(g_tlAlertSmallFlinch), 0, 0, MONSTER_SHARED_SCHED_ALERT_SMALL_FLINCH); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_RELOAD] = CreateSchedule(g_tlReload, sizeof(g_tlReload), COND_HEAVY_DAMAGE, 0, MONSTER_SHARED_SCHED_RELOAD); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_ARM_WEAPON] = CreateSchedule(g_tlArmWeapon, sizeof(g_tlArmWeapon), 0, 0, MONSTER_SHARED_SCHED_ARM_WEAPON); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_STANDOFF] = CreateSchedule( + g_tlStandoff, + sizeof(g_tlStandoff), + (COND_CAN_RANGE_ATTACK1 | COND_CAN_RANGE_ATTACK2 | COND_ENEMY_DEAD | COND_NEW_ENEMY | COND_HEAR_SOUND), + SOUND_DANGER, + MONSTER_SHARED_SCHED_STANDOFF + ); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_RANGE_ATTACK1] = CreateSchedule( + g_tlRangeAttack1, + sizeof(g_tlRangeAttack1), + (COND_NEW_ENEMY | COND_ENEMY_DEAD | COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE | COND_ENEMY_OCCLUDED | COND_NO_AMMO_LOADED | COND_HEAR_SOUND), + SOUND_DANGER, + MONSTER_SHARED_SCHED_RANGE_ATTACK1 + ); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_RANGE_ATTACK2] = CreateSchedule( + g_tlRangeAttack2, + sizeof(g_tlRangeAttack2), + (COND_NEW_ENEMY | COND_ENEMY_DEAD | COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE | COND_ENEMY_OCCLUDED | COND_HEAR_SOUND), + SOUND_DANGER, + MONSTER_SHARED_SCHED_RANGE_ATTACK2 + ); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_MELEE_ATTACK1] = CreateSchedule( + g_tlPrimaryMeleeAttack1, + sizeof(g_tlPrimaryMeleeAttack1), + (COND_NEW_ENEMY | COND_ENEMY_DEAD | COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE | COND_ENEMY_OCCLUDED), + 0, + MONSTER_SHARED_SCHED_MELEE_ATTACK1 + ); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_MELEE_ATTACK2] = CreateSchedule( + g_tlSecondaryMeleeAttack1, + sizeof(g_tlSecondaryMeleeAttack1), + (COND_NEW_ENEMY | COND_ENEMY_DEAD | COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE | COND_ENEMY_OCCLUDED), + 0, + MONSTER_SHARED_SCHED_MELEE_ATTACK2 + ); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_SPECIAL_ATTACK1] = CreateSchedule( + g_tlSpecialAttack1, + sizeof(g_tlSpecialAttack1), + (COND_NEW_ENEMY | COND_ENEMY_DEAD | COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE | COND_ENEMY_OCCLUDED | COND_NO_AMMO_LOADED | COND_HEAR_SOUND), + SOUND_DANGER, + MONSTER_SHARED_SCHED_SPECIAL_ATTACK1 + ); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_SPECIAL_ATTACK2] = CreateSchedule( + g_tlSpecialAttack2, + sizeof(g_tlSpecialAttack2), + (COND_NEW_ENEMY | COND_ENEMY_DEAD | COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE | COND_ENEMY_OCCLUDED | COND_NO_AMMO_LOADED | COND_HEAR_SOUND), + SOUND_DANGER, + MONSTER_SHARED_SCHED_SPECIAL_ATTACK2 + ); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_TAKE_COVER_FROM_BEST_SOUND] = CreateSchedule(g_tlTakeCoverFromBestSound, sizeof(g_tlTakeCoverFromBestSound), COND_NEW_ENEMY, 0, MONSTER_SHARED_SCHED_TAKE_COVER_FROM_BEST_SOUND); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_TAKE_COVER_FROM_ENEMY] = CreateSchedule(g_tlTakeCoverFromEnemy, sizeof(g_tlTakeCoverFromEnemy), COND_NEW_ENEMY, SOUND_DANGER, MONSTER_SHARED_SCHED_TAKE_COVER_FROM_ENEMY); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_COWER] = CreateSchedule(g_tlCower, sizeof(g_tlCower), 0, 0, MONSTER_SHARED_SCHED_COWER); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_AMBUSH] = CreateSchedule( + g_tlAmbush, + sizeof(g_tlAmbush), + (COND_NEW_ENEMY | COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE | COND_PROVOKED), + 0, + MONSTER_SHARED_SCHED_AMBUSH + ); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_BARNACLE_VICTIM_GRAB] = CreateSchedule(g_tlBarnacleVictimGrab, sizeof(g_tlBarnacleVictimGrab), 0, 0, MONSTER_SHARED_SCHED_BARNACLE_VICTIM_GRAB); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_BARNACLE_VICTIM_CHOMP] = CreateSchedule(g_tlBarnacleVictimChomp, sizeof(g_tlBarnacleVictimChomp), 0, 0, MONSTER_SHARED_SCHED_BARNACLE_VICTIM_CHOMP); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_INVESTIGATE_SOUND] = CreateSchedule( + g_tlInvestigateSound, + sizeof(g_tlInvestigateSound), + (COND_NEW_ENEMY | COND_SEE_FEAR | COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE | COND_HEAR_SOUND), + SOUND_DANGER, + MONSTER_SHARED_SCHED_INVESTIGATE_SOUND + ); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_DIE] = CreateSchedule(g_tlDie1, sizeof(g_tlDie1), 0, 0, MONSTER_SHARED_SCHED_DIE); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_TAKE_COVER_FROM_ORIGIN] = CreateSchedule(g_tlTakeCoverFromOrigin, sizeof(g_tlTakeCoverFromOrigin), COND_NEW_ENEMY, 0, MONSTER_SHARED_SCHED_TAKE_COVER_FROM_ORIGIN); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_VICTORY_DANCE] = CreateSchedule(g_tlVictoryDance, sizeof(g_tlVictoryDance), 0, 0, MONSTER_SHARED_SCHED_VICTORY_DANCE); + + g_rgSharedSchedules[MONSTER_SHARED_SCHED_CHASE_ENEMY_FAILED] = CreateSchedule( + g_tlChaseEnemyFailed, + sizeof(g_tlChaseEnemyFailed), + (COND_NEW_ENEMY | COND_CAN_RANGE_ATTACK1 | COND_CAN_MELEE_ATTACK1 | COND_CAN_RANGE_ATTACK2 | COND_CAN_MELEE_ATTACK2 | COND_HEAR_SOUND), + 0, + MONSTER_SHARED_SCHED_CHASE_ENEMY_FAILED + ); +} + +DestroySharedSchedule() { + for (new MONSTER_SHARED_SCHED:iSchedule = MONSTER_SHARED_SCHED:0; iSchedule < MONSTER_SHARED_SCHED; ++iSchedule) { + if (g_rgSharedSchedules[iSchedule] == Invalid_Struct) continue; + StructDestroy(g_rgSharedSchedules[iSchedule]); + } +} + +Struct:_GetSharedSchedule(MONSTER_SHARED_SCHED:iSchedule) { + return g_rgSharedSchedules[iSchedule]; +} + +Struct:@Monster_GetSharedSchedule(this, MONSTER_SHARED_SCHED:iSchedule) { + return _GetSharedSchedule(iSchedule); +} + +Struct:CreateSchedule(const rgTask[][MONSTER_TASK_DATA], iSize, iInterruptMask, iSoundMask, MONSTER_SHARED_SCHED:iSharedId = MONSTER_SHARED_SCHED_INVALID) { + new Struct:sSchedule = StructCreate(MONSTER_SCHEDULE_DATA); + StructSetCell(sSchedule, MONSTER_SCHEDULE_DATA_SHARED_ID, iSharedId); + StructSetArray(sSchedule, MONSTER_SCHEDULE_DATA_TASK, rgTask[0], _:MONSTER_TASK_DATA * iSize); + StructSetCell(sSchedule, MONSTER_SCHEDULE_DATA_TASK_SIZE, iSize); + StructSetCell(sSchedule, MONSTER_SCHEDULE_DATA_INTERRUPT_MASK, iInterruptMask); + StructSetCell(sSchedule, MONSTER_SCHEDULE_DATA_SOUND_MASK, iSoundMask); + + return sSchedule; +} + +@Monster_RunAI(this) { + static MONSTER_STATE:iMonsterState; iMonsterState = CE_GetMember(this, m_iMonsterState); + + if ( + (iMonsterState == MONSTER_STATE_IDLE || iMonsterState == MONSTER_STATE_ALERT) && + !random(99) && !(pev(this, pev_flags) & SF_MONSTER_GAG) + ) { + CE_CallMethod(this, IdleSound); + } + + if ( + iMonsterState != MONSTER_STATE_NONE && + iMonsterState != MONSTER_STATE_PRONE && + iMonsterState != MONSTER_STATE_DEAD + ) { + if (engfunc(EngFunc_FindClientInPVS, this) || (iMonsterState == MONSTER_STATE_COMBAT)) { + @Monster_Look(this, Float:CE_GetMember(this, m_flDistLook)); + @Monster_Listen(this); + @Monster_ClearConditions(this, CE_CallMethod(this, IgnoreConditions)); + @Monster_GetEnemy(this); + } + + static pEnemy; pEnemy = CE_GetMember(this, m_pEnemy); + if (pEnemy != FM_NULLENT) { + @Monster_CheckEnemy(this, pEnemy); + } + + @Monster_CheckAmmo(this); + } + + @Monster_CheckAITrigger(this); + @Monster_PrescheduleThink(this); + @Monster_MaintainSchedule(this); + + CE_SetMember(this, m_iConditions, CE_GetMember(this, m_iConditions) & ~(COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE)); +} + +@Monster_CheckAmmo(this) { + // Nothing +} + +@Monster_MaintainSchedule(this) { + for (new i = 0; i < 10; ++i) { + static Struct:sSchedule; sSchedule = CE_GetMember(this, m_sSchedule); + + if (sSchedule != Invalid_Struct && @Monster_TaskIsComplete(this)) { + @Monster_NextScheduledTask(this); + } + + static MONSTER_STATE:iMonsterState; iMonsterState = CE_GetMember(this, m_iMonsterState); + static MONSTER_STATE:iIdealMonsterState; iIdealMonsterState = CE_GetMember(this, m_iIdealMonsterState); + + if (!@Monster_ScheduleValid(this) || iMonsterState != iIdealMonsterState) { + @Monster_ScheduleChange(this); + + static pEnemy; pEnemy = CE_GetMember(this, m_pEnemy); + static iConditions; iConditions = @Monster_ScheduleFlags(this); + + if ( + iIdealMonsterState != MONSTER_STATE_DEAD && + (iIdealMonsterState != MONSTER_STATE_SCRIPT || iIdealMonsterState == iMonsterState) + ) { + if ( + (iConditions && !@Monster_HasConditions(this, COND_SCHEDULE_DONE)) || + (sSchedule && (StructGetCell(sSchedule, MONSTER_SCHEDULE_DATA_INTERRUPT_MASK) & COND_SCHEDULE_DONE)) || + ((iMonsterState == MONSTER_STATE_COMBAT) && (pEnemy == FM_NULLENT)) + ) { + iIdealMonsterState = @Monster_GetIdealState(this); + } + } + + static Struct:pNewSchedule; pNewSchedule = Invalid_Struct; + + if (@Monster_HasConditions(this, COND_TASK_FAILED) && iMonsterState == iIdealMonsterState) { + static MONSTER_SCHEDULE_TYPE:iFailSchedule; iFailSchedule = CE_GetMember(this, m_iFailSchedule); + pNewSchedule = CE_CallMethod(this, GetScheduleOfType, iFailSchedule != MONSTER_SCHED_NONE ? iFailSchedule : MONSTER_SCHED_FAIL); + } else { + @Monster_SetState(this, iIdealMonsterState); + + pNewSchedule = CE_CallMethod(this, GetSchedule); + } + + CE_CallMethod(this, ChangeSchedule, pNewSchedule); + } + + static MONSTER_TASK_STATUS:iTaskStatus; iTaskStatus = CE_GetMember(this, m_iTaskStatus); + + if (iTaskStatus == MONSTER_TASK_STATUS_NEW) { + static rgTask[MONSTER_TASK_DATA]; + if (@Monster_GetTask(this, rgTask) == -1) return; + + @Monster_TaskBegin(this); + CE_CallMethod(this, StartTask, rgTask[MONSTER_TASK_DATA_ID], rgTask[MONSTER_TASK_DATA_DATA]); + } + + static Activity:iActivity; iActivity = CE_GetMember(this, m_iActivity); + static Activity:iIdealActivity; iIdealActivity = CE_GetMember(this, m_iIdealActivity); + + if (iActivity != iIdealActivity) { + CE_CallMethod(this, SetActivity, iIdealActivity); + } + + if (!@Monster_TaskIsComplete(this) && iTaskStatus != MONSTER_TASK_STATUS_NEW) break; + } + + if (@Monster_TaskIsRunning(this)) { + static rgTask[MONSTER_TASK_DATA]; + if (@Monster_GetTask(this, rgTask) == -1) return; + + CE_CallMethod(this, RunTask, rgTask[MONSTER_TASK_DATA_ID], rgTask[MONSTER_TASK_DATA_DATA]); + + // CE_CallMethod(this, RunTaskOverlay); + } + + static Activity:iActivity; iActivity = CE_GetMember(this, m_iActivity); + static Activity:iIdealActivity; iIdealActivity = CE_GetMember(this, m_iIdealActivity); + + if (iActivity != iIdealActivity) { + CE_CallMethod(this, SetActivity, iIdealActivity); + } +} + +// @Monster_StartTaskOverlay(this) {} +// @Monster_RunTaskOverlay(this) {} + +@Monster_MonsterInit(this) { + static Float:vecAngles[3]; pev(this, pev_angles, vecAngles); + static Float:flHealth; pev(this, pev_health, flHealth); + + set_pev(this, pev_rendermode, kRenderNormal); + set_pev(this, pev_renderamt, 255.0); + set_pev(this, pev_effects, 0); + set_pev(this, pev_takedamage, DAMAGE_AIM); + set_pev(this, pev_ideal_yaw, vecAngles[1]); + set_pev(this, pev_max_health, flHealth); + set_pev(this, pev_deadflag, DEAD_NO); + set_pev(this, pev_flags, pev(this, pev_flags) | FL_MONSTER); + + if (pev(this, pev_spawnflags) & SF_MONSTER_HITMONSTERCLIP) { + set_pev(this, pev_flags, pev(this, pev_flags) | FL_MONSTERCLIP); + } + + CE_SetMember(this, m_iIdealMonsterState, MONSTER_STATE_IDLE); + CE_SetMember(this, m_iIdealActivity, ACT_IDLE); + CE_SetMember(this, m_iHintNode, NO_NODE); + CE_SetMember(this, m_iMemory, MEMORY_CLEAR); + CE_SetMember(this, m_pEnemy, FM_NULLENT); + CE_SetMember(this, m_iDamageType, 0); + CE_SetMember(this, m_iMovementActivity, 0); + CE_SetMember(this, m_iScriptState, 0); + CE_SetMemberVec(this, m_vecLastPosition, Float:{0.0, 0.0, 0.0}); + CE_SetMember(this, m_flMoveWaitFinished, 0.0); + CE_SetMember(this, m_flWaitFinished, 0.0); + CE_SetMember(this, m_pTargetEnt, FM_NULLENT); + CE_SetMemberVec(this, m_vecMoveGoal, Float:{0.0, 0.0, 0.0}); + CE_SetMemberVec(this, m_vecEnemyLKP, Float:{0.0, 0.0, 0.0}); + CE_SetMember(this, m_pCine, FM_NULLENT); + CE_SetMember(this, m_bSequenceFinished, false); + CE_SetMember(this, m_iActivity, 0); + CE_SetMember(this, m_iMovementGoal, MOVEGOAL_NONE); + CE_SetMember(this, m_flMoveWaitTime, 0.0); + CE_SetMember(this, m_flHungryTime, 0.0); + CE_SetMember(this, m_pGoalEnt, FM_NULLENT); + CE_SetMember(this, m_bSequenceLoops, false); + CE_SetMember(this, m_flFrameRate, 0.0); + CE_SetMember(this, m_flGroundSpeed, 0.0); + CE_SetMember(this, m_iCapability, 0); + + @Monster_ClearSchedule(this); + @Monster_RouteClear(this); + @Monster_InitBoneControllers(this); + + @Monster_SetEyePosition(this); + + set_pev(this, pev_nextthink, g_flGameTime + 0.1); + // SetUse ( &CBaseMonster::MonsterUse ); + + CE_CallMethod(this, SetThink, "@Monster_MonsterInitThink"); +} + +@Monster_MonsterInitThink(this) { + @Monster_StartMonster(this); +} + +@Monster_InitBoneControllers(this) { + set_controller(this, 0, 0.0); + set_controller(this, 1, 0.0); + set_controller(this, 2, 0.0); + set_controller(this, 3, 0.0); +} + + +@Monster_SetEyePosition(this) { + static Float:vecEyePosition[3]; CE_GetMemberVec(this, m_vecEyePosition, vecEyePosition); + + set_pev(this, pev_view_ofs, vecEyePosition); +} + +@Monster_StartMonster(this) { + new iCapability = CE_GetMember(this, m_iCapability); + + if (@Monster_LookupActivity(this, ACT_RANGE_ATTACK1) != ACTIVITY_NOT_AVAILABLE) { + CE_SetMember(this, m_iCapability, iCapability | CAP_RANGE_ATTACK1); + } + + if (@Monster_LookupActivity(this, ACT_RANGE_ATTACK2) != ACTIVITY_NOT_AVAILABLE) { + CE_SetMember(this, m_iCapability, iCapability | CAP_RANGE_ATTACK2); + } + + if (@Monster_LookupActivity(this, ACT_MELEE_ATTACK1) != ACTIVITY_NOT_AVAILABLE) { + CE_SetMember(this, m_iCapability, iCapability | CAP_MELEE_ATTACK1); + } + + if (@Monster_LookupActivity(this, ACT_MELEE_ATTACK2) != ACTIVITY_NOT_AVAILABLE) { + CE_SetMember(this, m_iCapability, iCapability | CAP_MELEE_ATTACK2); + } + + if (pev(this, pev_movetype) != MOVETYPE_FLY && !(pev(this, pev_spawnflags) & SF_MONSTER_FALL_TO_GROUND)) { + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + + vecOrigin[2] += 1.0; + set_pev(this, pev_origin, vecOrigin); + + engfunc(EngFunc_DropToFloor, this); + + // if (!engfunc(EngFunc_WalkMove, this, 0.0, 0.0, WALKMOVE_NORMAL)) { + // set_pev(this, pev_effects, EF_BRIGHTFIELD); + // } + } else { + set_pev(this, pev_flags, pev(this, pev_flags) & ~FL_ONGROUND); + } + + static szTarget[32]; pev(this, pev_target, szTarget, charsmax(szTarget)); + + if (!equal(szTarget, NULL_STRING)) { + new pGoalEnt = engfunc(EngFunc_FindEntityByString, 0, "targetname", szTarget); + + CE_SetMember(this, m_pGoalEnt, pGoalEnt); + + if (pGoalEnt) { + new Float:vecGoal[3]; pev(this, pev_origin, vecGoal); + @Monster_MakeIdealYaw(this, vecGoal); + + CE_SetMember(this, m_iMovementGoal, MOVEGOAL_PATHCORNER); + + if (pev(this, pev_movetype) == MOVETYPE_FLY) { + CE_SetMember(this, m_iMovementActivity, ACT_FLY); + } else { + CE_SetMember(this, m_iMovementActivity, ACT_WALK); + } + + @Monster_RefreshRoute(this); + + @Monster_SetState(this, MONSTER_STATE_IDLE); + CE_CallMethod(this, ChangeSchedule, CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_IDLE_WALK)); + } + } + + CE_CallMethod(this, SetThink, "@Monster_MonsterThink"); + + static szTargetname[32]; pev(this, pev_targetname, szTargetname, charsmax(szTargetname)); + + if (!equal(szTargetname, NULL_STRING)) { + @Monster_SetState(this, MONSTER_STATE_IDLE); + CE_CallMethod(this, SetActivity, ACT_IDLE); + CE_CallMethod(this, ChangeSchedule, CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_WAIT_TRIGGER)); + } + + set_pev(this, pev_nextthink, g_flGameTime + random_float(0.1, 0.4)); +} + +@Monster_MonsterInitDead(this) { + @Monster_InitBoneControllers(this); + + set_pev(this, pev_solid, SOLID_BBOX); + set_pev(this, pev_movetype, MOVETYPE_TOSS); + + set_pev(this, pev_frame, 0.0); + @Monster_ResetSequenceInfo(this); + set_pev(this, pev_framerate, 0.0); + + static Float:flHealth; pev(this, pev_health, flHealth); + set_pev(this, pev_max_health, flHealth); + + set_pev(this, pev_deadflag, DEAD_DEAD); + + engfunc(EngFunc_SetSize, this, Float:{0.0, 0.0, 0.0}, Float:{0.0, 0.0, 0.0}); + + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + engfunc(EngFunc_SetOrigin, this, vecOrigin); + + @Monster_BecomeDead(this); + + CE_CallMethod(this, SetThink, "@Monster_CorpseFallThink"); + + set_pev(this, pev_nextthink, g_flGameTime + 0.5); +} + +@Monster_BecomeDead(this) { + static Float:flMaxHealth; pev(this, pev_max_health, flMaxHealth); + + set_pev(this, pev_takedamage, DAMAGE_YES); + + set_pev(this, pev_health, flMaxHealth / 2); + set_pev(this, pev_max_health, 5.0); + set_pev(this, pev_movetype, MOVETYPE_TOSS); +} + +@Monster_CorpseFallThink(this) { + if (pev(this, pev_flags) & FL_ONGROUND) { + CE_CallMethod(this, SetThink, NULL_STRING); + + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + engfunc(EngFunc_SetOrigin, this, vecOrigin); + } else { + set_pev(this, pev_nextthink, g_flGameTime + 0.1); + } +} + +@Monster_MonsterThink(this) { + static Float:flLTime; pev(this, pev_ltime, flLTime); + + set_pev(this, pev_nextthink, g_flGameTime + 0.01); + set_pev(this, pev_ltime, g_flGameTime); + + @Monster_RunAI(this); + + static Float:flInterval; flInterval = @Monster_FrameAdvance(this, 0.0); + static MONSTER_STATE:iMonsterState; iMonsterState = CE_GetMember(this, m_iMonsterState); + static Activity:iActivity; iActivity = CE_GetMember(this, m_iActivity); + static bool:bSequenceFinished; bSequenceFinished = CE_GetMember(this, m_bSequenceFinished); + static bool:bSequenceLoops; bSequenceLoops = CE_GetMember(this, m_bSequenceLoops); + + if (iMonsterState != MONSTER_STATE_SCRIPT && iMonsterState != MONSTER_STATE_DEAD && iActivity == ACT_IDLE && bSequenceFinished) { + static Activity:iSequence; iSequence = ACTIVITY_NOT_AVAILABLE; + + if (bSequenceLoops) { + iSequence = @Monster_LookupActivity(this, iActivity); + } else { + iSequence = @Monster_LookupActivityHeaviest(this, iActivity); + } + + if (iSequence != ACTIVITY_NOT_AVAILABLE) { + set_pev(this, pev_sequence, iSequence); + @Monster_ResetSequenceInfo(this); + } + } + + @Monster_DispatchAnimEvents(this, flInterval); + + if (!@Monster_MovementIsComplete(this)) { + @Monster_Move(this, flInterval); + } +} + +stock bool:UTIL_BoxIntersects(const Float:vecMin[3], const Float:vecMax[3], const Float:vecOtherMin[3], const Float:vecOtherMax[3]) { + for (new i = 0; i < 3; ++i) { + if (vecMin[i] > vecOtherMax[i]) return false; + if (vecMax[i] < vecOtherMin[i]) return false; + } + + return true; +} + +stock UTIL_FindEntityInBox(pStartEdict, const Float:vecBoxMin[3], const Float:vecBoxMax[3]) { + static iMaxEntities = 0; + if (!iMaxEntities) { + iMaxEntities = global_get(glb_maxEntities); + } + + for (new pEntity = max(pStartEdict + 1, 1); pEntity < iMaxEntities; ++pEntity) { + if (pEntity <= 0) continue; + if (!pev_valid(pEntity)) continue; + + static Float:vecAbsMin[3]; pev(pEntity, pev_absmin, vecAbsMin); + static Float:vecAbsMax[3]; pev(pEntity, pev_absmax, vecAbsMax); + + if (UTIL_BoxIntersects(vecBoxMin, vecBoxMax, vecAbsMin, vecAbsMax)) { + return pEntity; + } + } + + return FM_NULLENT; +} + +@Monster_Look(this, Float:flDistance) { + new iSighted = 0; + + @Monster_ClearConditions(this, COND_SEE_HATE | COND_SEE_DISLIKE | COND_SEE_ENEMY | COND_SEE_FEAR | COND_SEE_NEMESIS | COND_SEE_CLIENT); + + static pLink; pLink = FM_NULLENT; + + if (~pev(this, pev_spawnflags) & SF_MONSTER_PRISONER) { + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + static Float:vecDelta[3]; xs_vec_set(vecDelta, flDistance, flDistance, flDistance); + static Float:vecAbsMin[3]; xs_vec_sub(vecOrigin, vecDelta, vecAbsMin); + static Float:vecAbsMax[3]; xs_vec_add(vecOrigin, vecDelta, vecAbsMax); + + static pSightEnt; pSightEnt = FM_NULLENT; + while ((pSightEnt = UTIL_FindEntityInBox(pSightEnt, vecAbsMin, vecAbsMax)) != FM_NULLENT) { + if (pSightEnt == this) continue; + if (!(pev(pSightEnt, pev_flags) & (FL_CLIENT | FL_MONSTER))) continue; + if (pev(pSightEnt, pev_spawnflags) & SF_MONSTER_PRISONER) continue; + + static Float:flSightHealth; pev(pSightEnt, pev_health, flSightHealth); + if (flSightHealth <= 0) continue; + + if (@Monster_Relationship(this, pSightEnt) == R_NO) continue; + if (!ExecuteHamB(Ham_FInViewCone, this, pSightEnt)) continue; + if (pev(pSightEnt, pev_flags) & FL_NOTARGET) continue; + if (!ExecuteHamB(Ham_FVisible, this, pSightEnt)) continue; + + if (ExecuteHamB(Ham_IsPlayer, pSightEnt)) { + static iSpawnFlags; iSpawnFlags = pev(this, pev_spawnflags); + + if (iSpawnFlags & SF_MONSTER_WAIT_TILL_SEEN) { + if (pSightEnt && !ExecuteHamB(Ham_FInViewCone, pSightEnt, this)) continue; + + set_pev(this, pev_spawnflags, iSpawnFlags & ~SF_MONSTER_WAIT_TILL_SEEN); + } + + iSighted |= COND_SEE_CLIENT; + } + + set_ent_data_entity(pSightEnt, "CBaseEntity", "m_pLink", pLink); + set_ent_data_entity(this, "CBaseEntity", "m_pLink", pSightEnt); + + if (pSightEnt == CE_GetMember(this, m_pEnemy)) { + iSighted |= COND_SEE_ENEMY; + } + + switch (@Monster_Relationship(this, pSightEnt)) { + case R_NM: iSighted |= COND_SEE_NEMESIS; + case R_HT: iSighted |= COND_SEE_HATE; + case R_DL: iSighted |= COND_SEE_DISLIKE; + case R_FR: iSighted |= COND_SEE_FEAR; + } + } + } + + @Monster_SetConditions(this, iSighted); +} + +@Monster_Listen(this) { + @Monster_ClearConditions(this, COND_HEAR_SOUND | COND_SMELL | COND_SMELL_FOOD); + + static Float:vecEarPosition[3]; ExecuteHamB(Ham_EarPosition, this, vecEarPosition); + static Float:flHearingSensitivity; flHearingSensitivity = @Monster_HearingSensitivity(this); + + static iMySounds; iMySounds = @Monster_SoundMask(this); + + static Struct:sSchedule; sSchedule = CE_GetMember(this, m_sSchedule); + if (sSchedule != Invalid_Struct) { + iMySounds &= StructGetCell(sSchedule, MONSTER_SCHEDULE_DATA_SOUND_MASK); + } + + new iSoundTypes = 0; + + for (new iSound = 0; iSound < sizeof(g_rgSounds); ++iSound) { + if (g_rgSounds[iSound][Sound_ExpiredTime] <= g_flGameTime) continue; + + if ( + (g_rgSounds[iSound][Sound_Type] & iMySounds) && + xs_vec_distance(vecEarPosition, g_rgSounds[iSound][Sound_Origin]) <= (g_rgSounds[iSound][Sound_Volume] * flHearingSensitivity) + ) { + if (@Sound_IsSound(iSound)) { + @Monster_SetConditions(this, COND_HEAR_SOUND); + } else { + if (g_rgSounds[iSound][Sound_Type] & (SOUND_MEAT | SOUND_CARCASS)) { + @Monster_SetConditions(this, COND_SMELL_FOOD); + @Monster_SetConditions(this, COND_SMELL); + } else { + @Monster_SetConditions(this, COND_SMELL); + } + } + + iSoundTypes |= g_rgSounds[iSound][Sound_Type]; + } + } + + CE_SetMember(this, m_iSoundTypes, iSoundTypes); +} + +Float:@Monster_HearingSensitivity(this) { + return 1.0; +} + +@Monster_BestSound(this) { + static Float:vecEarPosition[3]; ExecuteHamB(Ham_EarPosition, this, vecEarPosition); + + static iBestSound; iBestSound = -1; + static Float:flBestDist; flBestDist = 8192.0; + + for (new iSound = 0; iSound < sizeof(g_rgSounds); ++iSound) { + if (g_rgSounds[iSound][Sound_ExpiredTime] <= g_flGameTime) continue; + + if (@Sound_IsSound(iSound)) { + static Float:flDist; flDist = xs_vec_distance(vecEarPosition, g_rgSounds[iSound][Sound_Origin]); + + if (flDist < flBestDist) { + iBestSound = iSound; + flBestDist = flDist; + } + } + } + + return iBestSound; +} + +@Monster_BestScent(this) { + static Float:vecEarPosition[3]; ExecuteHamB(Ham_EarPosition, this, vecEarPosition); + + static iBestSound; iBestSound = -1; + static Float:flBestDist; flBestDist = 8192.0; + + for (new iSound = 0; iSound < sizeof(g_rgSounds); ++iSound) { + if (g_rgSounds[iSound][Sound_ExpiredTime] <= g_flGameTime) continue; + + if (@Sound_IsScent(iSound)) { + static Float:flDist; flDist = xs_vec_distance(vecEarPosition, g_rgSounds[iSound][Sound_Origin]); + + if (flDist < flBestDist) { + iBestSound = iSound; + flBestDist = flDist; + } + } + } + + return iBestSound; +} + +@Monster_MonsterUse(this) { + CE_SetMember(this, m_iIdealMonsterState, MONSTER_STATE_ALERT); +} + +@Monster_SoundMask(this) { + return (SOUND_WORLD | SOUND_COMBAT | SOUND_PLAYER); +} + +bool:@Monster_CheckEnemy(this, pEnemy) { + new bool:iUpdatedLKP = false; + + @Monster_ClearConditions(this, COND_ENEMY_FACING_ME); + + if (!ExecuteHamB(Ham_FVisible, this, pEnemy)) { + @Monster_SetConditions(this, COND_ENEMY_OCCLUDED); + } else { + @Monster_ClearConditions(this, COND_ENEMY_OCCLUDED); + } + + if (!ExecuteHamB(Ham_IsAlive, pEnemy)) { + @Monster_SetConditions(this, COND_ENEMY_DEAD); + @Monster_ClearConditions(this, COND_SEE_ENEMY | COND_ENEMY_OCCLUDED); + return false; + } + + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + static Float:vecEnemyOrigin[3]; pev(pEnemy, pev_origin, vecEnemyOrigin); + static Float:vecEnemySize[3]; pev(pEnemy, pev_size, vecEnemySize); + + static Float:vecEnemyHeadOrigin[3]; + xs_vec_copy(vecEnemyOrigin, vecEnemyHeadOrigin); + vecEnemyHeadOrigin[2] += vecEnemySize[2] * 0.5; + + static Float:flDistToEnemy; flDistToEnemy = xs_vec_distance(vecOrigin, vecEnemyOrigin); + static Float:flDistToEnemyHead; flDistToEnemyHead = xs_vec_distance(vecOrigin, vecEnemyHeadOrigin); + + if (flDistToEnemyHead < flDistToEnemy) { + flDistToEnemy = flDistToEnemyHead; + } else { + static Float:vecEnemyFeetOrigin[3]; + xs_vec_copy(vecEnemyOrigin, vecEnemyFeetOrigin); + vecEnemyFeetOrigin[2] -= vecEnemySize[2]; + + static Float:flDistToEnemyFeet; flDistToEnemyFeet = xs_vec_distance(vecOrigin, vecEnemyFeetOrigin); + if (flDistToEnemyFeet < flDistToEnemy) { + flDistToEnemy = flDistToEnemyFeet; + } + } + + if (@Monster_HasConditions(this, COND_SEE_ENEMY)) { + iUpdatedLKP = true; + + CE_SetMemberVec(this, m_vecEnemyLKP, vecEnemyOrigin); + + if (pEnemy) { + if (ExecuteHamB(Ham_FInViewCone, pEnemy, this)) { + @Monster_SetConditions(this, COND_ENEMY_FACING_ME); + } else { + @Monster_ClearConditions(this, COND_ENEMY_FACING_ME); + } + } + + static Float:vecEnemyVelocity[3]; pev(this, pev_velocity, vecEnemyVelocity); + if (xs_vec_len(vecEnemyVelocity)) { + static Float:vecEnemyLKP[3]; CE_GetMemberVec(this, m_vecEnemyLKP, vecEnemyLKP); + + xs_vec_sub_scaled(vecEnemyLKP, vecEnemyVelocity, random_float(-0.05, 0.0), vecEnemyLKP); + + CE_SetMemberVec(this, m_vecEnemyLKP, vecEnemyLKP); + } + } else if (!@Monster_HasConditions(this, COND_ENEMY_OCCLUDED | COND_SEE_ENEMY) && (flDistToEnemy <= 256.0)) { + iUpdatedLKP = true; + CE_SetMemberVec(this, m_vecEnemyLKP, vecEnemyOrigin); + } + + if (flDistToEnemy >= Float:CE_GetMember(this, m_flDistTooFar)) { + @Monster_SetConditions(this, COND_ENEMY_TOOFAR ); + } else { + @Monster_ClearConditions(this, COND_ENEMY_TOOFAR); + } + + if (@Monster_CanCheckAttacks(this)) { + @Monster_CheckAttacks(this, CE_GetMember(this, m_pEnemy), flDistToEnemy); + } + + if (CE_GetMember(this, m_iMovementGoal) == MOVEGOAL_ENEMY) { + static Float:vecEnemyLKP[3]; CE_GetMemberVec(this, m_vecEnemyLKP, vecEnemyLKP); + static Array:irgRoute; irgRoute = CE_GetMember(this, m_irgRoute); + static iRouteSize; iRouteSize = ArraySize(irgRoute); + + for (new i = CE_GetMember(this, m_iRouteIndex); i < iRouteSize; ++i) { + static rgWaypoint[MONSTER_WAYPOINT]; ArrayGetArray(irgRoute, i, rgWaypoint[any:0], _:MONSTER_WAYPOINT); + + if (rgWaypoint[MONSTER_WAYPOINT_TYPE] == (MF_IS_GOAL | MF_TO_ENEMY)) { + if (xs_vec_distance(rgWaypoint[MONSTER_WAYPOINT_LOCATION], vecEnemyLKP) > 80.0) { + @Monster_RefreshRoute(this); + return iUpdatedLKP; + } + } + } + } + + return iUpdatedLKP; +} + +bool:@Monster_CheckAITrigger(this) { + return false; +} + +bool:@Monster_CanCheckAttacks(this) { + return @Monster_HasConditions(this, COND_SEE_ENEMY) && !@Monster_HasConditions(this, COND_ENEMY_TOOFAR); +} + +@Monster_CallGibMonster(this) { + new bool:bFade = false; + + if (@Monster_HasHumanGibs(this)) { + bFade = (get_cvar_float("violence_hgibs") == 0); + } else if (@Monster_HasAlienGibs(this)) { + bFade = get_cvar_float("violence_agibs") == 0; + } + + set_pev(this, pev_takedamage, DAMAGE_NO); + set_pev(this, pev_solid, SOLID_NOT); + + if (bFade) { + @Monster_FadeMonster(this); + } else { + set_pev(this, pev_effects, EF_NODRAW); + CE_CallMethod(this, GibMonster); + } + + set_pev(this, pev_deadflag, DEAD_DEAD); + + static Float:flHealth; pev(this, pev_health, flHealth); + + // don't let the status bar glitch for players.with <0 health. + if (flHealth < 99.0) { + flHealth = 0.0; + set_pev(this, pev_health, flHealth); + } + + if (@Monster_ShouldFadeOnDeath(this) && !bFade) { + CE_Kill(this); + } +} + +@Monster_GibMonster(this) { + emit_sound(this, CHAN_WEAPON, "common/bodysplat.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM); + + static bool:bGibbed; bGibbed = false; + + // only humans throw skulls !!!UNDONE - eventually monsters will have their own sets of gibs + if (@Monster_HasHumanGibs(this)) { + if (get_cvar_float("violence_hgibs") != 0.0) { + // TODO: Implement gibs + // @Monster_SpawnHeadGib(this); + // @Monster_SpawnRandomGibs(this, 4, 1); // throw some human gibs. + } + + bGibbed = true; + } else if (@Monster_HasAlienGibs(this)) { + if (get_cvar_float("violence_agibs") != 0.0) { + // TODO: Implement gibs + // @Monster_SpawnRandomGibs(this, 4, 0); // Throw alien gibs + } + + bGibbed = true; + } + + + if (bGibbed) { + CE_CallMethod(this, SetThink, "@Monster_SUB_Remove"); + set_pev(this, pev_nextthink, g_flGameTime); + } else { + @Monster_FadeMonster(this); + } +} + +@Monster_FadeMonster(this) { + @Monster_StopAnimation(this); + set_pev(this, pev_velocity, Float:{0.0, 0.0, 0.0}); + set_pev(this, pev_movetype, MOVETYPE_NONE); + set_pev(this, pev_avelocity, Float:{0.0, 0.0, 0.0}); + set_pev(this, pev_animtime, g_flGameTime); + set_pev(this, pev_effects, pev(this, pev_effects) | EF_NOINTERP); + @Monster_SUB_StartFadeOut(this); +} + +@Monster_SUB_StartFadeOut(this) { + if (pev(this, pev_rendermode) == kRenderNormal) { + set_pev(this, pev_renderamt, 255.0); + set_pev(this, pev_rendermode, kRenderTransTexture); + } + + set_pev(this, pev_solid, SOLID_NOT); + set_pev(this, pev_avelocity, Float:{0.0, 0.0, 0.0}); + + set_pev(this, pev_nextthink, g_flGameTime + 0.1); + + CE_CallMethod(this, SetThink, "@Monster_SUB_FadeOut"); +} + +@Monster_SUB_FadeOut(this) { + static Float:flRenderAmt; pev(this, pev_renderamt, flRenderAmt); + + if (flRenderAmt > 7.0) { + set_pev(this, pev_renderamt, flRenderAmt - 7.0); + set_pev(this, pev_nextthink, g_flGameTime + 0.1); + } else { + set_pev(this, pev_renderamt, 0.0); + set_pev(this, pev_nextthink, g_flGameTime + 0.1); + + CE_CallMethod(this, SetThink, "@Monster_SUB_Remove"); + } +} + +@Monster_SUB_Remove(this) { + CE_Kill(this); +} + +@Monster_HasHumanGibs(this) { + new myClass = ExecuteHamB(Ham_Classify, this); + + return ( + myClass == CLASS_HUMAN_MILITARY || + myClass == CLASS_PLAYER_ALLY || + myClass == CLASS_HUMAN_PASSIVE || + myClass == CLASS_PLAYER + ); +} + +@Monster_HasAlienGibs(this) { + new myClass = ExecuteHamB(Ham_Classify, this); + + return ( + myClass == CLASS_ALIEN_MILITARY || + myClass == CLASS_ALIEN_MONSTER || + myClass == CLASS_ALIEN_PASSIVE || + myClass == CLASS_INSECT || + myClass == CLASS_ALIEN_PREDATOR || + myClass == CLASS_ALIEN_PREY + ); +} + +@Monster_CheckAttacks(this, pTarget, Float:flDistance) { + @Monster_ClearConditions(this, COND_CAN_RANGE_ATTACK1 | COND_CAN_RANGE_ATTACK2 | COND_CAN_MELEE_ATTACK1 | COND_CAN_MELEE_ATTACK2); + + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + static Float:vecTarget[3]; pev(pTarget, pev_origin, vecTarget); + + static Float:vecDirection[3]; + xs_vec_set(vecDirection, vecTarget[0] - vecOrigin[0], vecTarget[1] - vecOrigin[1], 0.0); + xs_vec_normalize(vecDirection, vecDirection); + + static Float:vecAngles[3]; pev(this, pev_angles, vecAngles); + static Float:vecForward[3]; angle_vector(vecAngles, ANGLEVECTOR_FORWARD, vecForward); + + // TODO: Check for forward ROLL + static Float:flDot; flDot = xs_vec_dot(vecDirection, vecForward); + + static iCapability; iCapability = CE_GetMember(this, m_iCapability); + + if (iCapability & CAP_RANGE_ATTACK1) { + if (@Monster_CheckRangeAttack1(this, flDot, flDistance)) { + @Monster_SetConditions(this, COND_CAN_RANGE_ATTACK1); + } + } + + if (iCapability & CAP_RANGE_ATTACK2) { + if (@Monster_CheckRangeAttack2(this, flDot, flDistance)) { + @Monster_SetConditions(this, COND_CAN_RANGE_ATTACK2); + } + } + + if (iCapability & CAP_MELEE_ATTACK1) { + if (@Monster_CheckMeleeAttack1(this, flDot, flDistance)) { + @Monster_SetConditions(this, COND_CAN_MELEE_ATTACK1); + } + } + + if (iCapability & CAP_MELEE_ATTACK2) { + if (@Monster_CheckMeleeAttack2(this, flDot, flDistance)) { + @Monster_SetConditions(this, COND_CAN_MELEE_ATTACK2); + } + } +} + +bool:@Monster_CheckRangeAttack1(this, Float:flDot, Float:flDistance) { + static Float:flMeleeRange1; flMeleeRange1 = CE_GetMember(this, m_flMeleeAttack1Range); + static Float:flMeleeRange2; flMeleeRange2 = CE_GetMember(this, m_flMeleeAttack2Range); + static Float:flMeleeRange; flMeleeRange = floatmax(flMeleeRange1, flMeleeRange2); + static Float:flRange; flRange = CE_GetMember(this, m_flRangeAttack1Range); + + return flRange > 0.0 && (flDistance > flMeleeRange && flDistance <= flRange && flDot >= 0.5); +} + +bool:@Monster_CheckRangeAttack2(this, Float:flDot, Float:flDistance) { + static Float:flMeleeRange1; flMeleeRange1 = CE_GetMember(this, m_flMeleeAttack1Range); + static Float:flMeleeRange2; flMeleeRange2 = CE_GetMember(this, m_flMeleeAttack2Range); + static Float:flMeleeRange; flMeleeRange = floatmax(flMeleeRange1, flMeleeRange2); + static Float:flRange; flRange = CE_GetMember(this, m_flRangeAttack2Range); + + return flRange > 0.0 && (flDistance > flMeleeRange && flDistance <= flRange && flDot >= 0.5); +} + +bool:@Monster_CheckMeleeAttack1(this, Float:flDot, Float:flDistance) { + static pEnemy; pEnemy = CE_GetMember(this, m_pEnemy); + static Float:flRange; flRange = CE_GetMember(this, m_flMeleeAttack1Range); + + return flRange > 0.0 && (flDistance <= flRange && flDot >= 0.7 && pEnemy != FM_NULLENT && (pev(pEnemy, pev_flags) & FL_ONGROUND)); +} + +bool:@Monster_CheckMeleeAttack2(this, Float:flDot, Float:flDistance) { + static Float:flRange; flRange = CE_GetMember(this, m_flMeleeAttack2Range); + + return flRange > 0.0 && flDistance <= flRange && flDot >= 0.7; +} + +@Monster_GetEnemy(this) { + static Struct:sSchedule; sSchedule = CE_GetMember(this, m_sSchedule); + + static pEnemy; pEnemy = CE_GetMember(this, m_pEnemy); + + if (@Monster_HasConditions(this, COND_SEE_HATE | COND_SEE_DISLIKE | COND_SEE_NEMESIS)) { + new pNewEnemy = @Monster_BestVisibleEnemy(this); + + if (pNewEnemy != pEnemy && pNewEnemy != FM_NULLENT) { + if (sSchedule != Invalid_Struct) { + static Float:vecEnemyLKP[3]; CE_GetMemberVec(this, m_vecEnemyLKP, vecEnemyLKP); + + if (StructGetCell(sSchedule, MONSTER_SCHEDULE_DATA_INTERRUPT_MASK) & COND_NEW_ENEMY) { + @Monster_PushEnemy(this, pEnemy, vecEnemyLKP); + @Monster_SetConditions(this, COND_NEW_ENEMY); + CE_SetMember(this, m_pEnemy, pNewEnemy); + + pev(pNewEnemy, pev_origin, vecEnemyLKP); + CE_SetMemberVec(this, m_vecEnemyLKP, vecEnemyLKP); + } + + new pOwner = pev(pNewEnemy, pev_owner); + if (pOwner) { + if (pOwner && (pev(pOwner, pev_flags) & FL_MONSTER) && @Monster_Relationship(this, pOwner) != R_NO) { + @Monster_PushEnemy(this, pOwner, vecEnemyLKP); + } + } + } + } + } + + if (CE_GetMember(this, m_pEnemy) != FM_NULLENT) return true; + + if (@Monster_PopEnemy(this)) { + if (sSchedule != Invalid_Struct) { + if (StructGetCell(sSchedule, MONSTER_SCHEDULE_DATA_INTERRUPT_MASK) & COND_NEW_ENEMY) { + @Monster_SetConditions(this, COND_NEW_ENEMY); + } + } + } + + return false; +} + +@Monster_PrescheduleThink(this) {} + +@Monster_Move(this, Float:flInterval) { + if (@Monster_HasConditions(this, COND_WAIT_FOR_PATH)) return; + + if (@Monster_IsRouteClear(this)) { + if (CE_GetMember(this, m_iMovementGoal) == MOVEGOAL_NONE || !@Monster_RefreshRoute(this)) { + @Monster_TaskFail(this); + return; + } + } + + static Float:flMoveWaitFinished; flMoveWaitFinished = CE_GetMember(this, m_flMoveWaitFinished); + + if (flMoveWaitFinished > g_flGameTime) return; + + static Float:flMoveWaitTime; flMoveWaitTime = CE_GetMember(this, m_flMoveWaitTime); + static Float:flGroundSpeed; flGroundSpeed = CE_GetMember(this, m_flGroundSpeed); + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + + static Array:irgRoute; irgRoute = CE_GetMember(this, m_irgRoute); + if (!ArraySize(irgRoute)) return; + + static rgWaypoint[MONSTER_WAYPOINT]; ArrayGetArray(irgRoute, CE_GetMember(this, m_iRouteIndex), rgWaypoint[any:0], _:MONSTER_WAYPOINT); + static Float:flWaypointDist; flWaypointDist = xs_vec_distance_2d(vecOrigin, rgWaypoint[MONSTER_WAYPOINT_LOCATION]); + static Float:flCheckDist; flCheckDist = floatmin(flWaypointDist, DIST_TO_CHECK); + + static Float:vecDir[3]; + xs_vec_sub(rgWaypoint[MONSTER_WAYPOINT_LOCATION], vecOrigin, vecDir); + xs_vec_normalize(vecDir, vecDir); + + static Float:vecTarget[3]; xs_vec_add_scaled(vecOrigin, vecDir, flCheckDist, vecTarget); + + @Monster_MakeIdealYaw(this, rgWaypoint[MONSTER_WAYPOINT_LOCATION]); + + static Float:flYawSpeed; pev(this, pev_yaw_speed, flYawSpeed); + @Monster_ChangeYaw(this, flYawSpeed); + + static pTargetEnt; pTargetEnt = FM_NULLENT; + if ((rgWaypoint[MONSTER_WAYPOINT_TYPE] & (~MF_NOT_TO_MASK)) == MF_TO_ENEMY) { + pTargetEnt = CE_GetMember(this, m_pEnemy); + } else if ((rgWaypoint[MONSTER_WAYPOINT_TYPE] & ~MF_NOT_TO_MASK) == MF_TO_TARGETENT) { + pTargetEnt = CE_GetMember(this, m_pTargetEnt); + } + + static Float:flDist; flDist = 0.0; + + if (!(rgWaypoint[MONSTER_WAYPOINT_TYPE] & MF_TO_NAV) && @Monster_CheckLocalMove(this, vecOrigin, vecTarget, pTargetEnt, false, flDist) != MONSTER_LOCALMOVE_VALID) { + @Monster_Stop(this); + + static pBlocker; pBlocker = global_get(glb_trace_ent); + if (pBlocker) { + ExecuteHamB(Ham_Blocked, this, pBlocker); + } + + if (pBlocker && flMoveWaitTime > 0.0 && ExecuteHamB(Ham_IsMoving, pBlocker) && !ExecuteHamB(Ham_IsPlayer, pBlocker) && g_flGameTime - flMoveWaitFinished > 3.0) { + if (flDist < flGroundSpeed) { + flMoveWaitFinished = g_flGameTime + flMoveWaitTime; + CE_SetMember(this, m_flMoveWaitFinished, flMoveWaitFinished); + return; + } + } else { + static Float:vecApex[3]; + if (@Monster_Triangulate(this, vecOrigin, rgWaypoint[MONSTER_WAYPOINT_LOCATION], flDist, pTargetEnt, vecApex)) { + @Monster_RouteSimplify(this, pTargetEnt); + @Monster_InsertWaypoint(this, vecApex, MF_TO_DETOUR); + } else { + @Monster_Stop(this); + + if (flMoveWaitTime > 0.0 && !(CE_GetMember(this, m_iMemory) & MEMORY_MOVE_FAILED)) { + @Monster_RefreshRoute(this); + + if (@Monster_IsRouteClear(this)) { + @Monster_TaskFail(this); + } else { + if (g_flGameTime - flMoveWaitFinished < 0.2) { + @Monster_Remember(this, MEMORY_MOVE_FAILED); + } + + flMoveWaitFinished = g_flGameTime + 0.1; + CE_SetMember(this, m_flMoveWaitFinished, flMoveWaitFinished); + } + } else { + @Monster_TaskFail(this); + } + + return; + } + } + } + + if (@Monster_ShouldAdvanceRoute(this, flWaypointDist)) { + @Monster_AdvanceRoute(this, flWaypointDist); + } + + if (flMoveWaitFinished > g_flGameTime) { + @Monster_Stop(this); + return; + } + + if (flCheckDist < flGroundSpeed * flInterval) { + flInterval = flCheckDist / flGroundSpeed; + } + + CE_CallMethod(this, MoveExecute, pTargetEnt, vecDir, flInterval); + + if (@Monster_MovementIsComplete(this)) { + @Monster_Stop(this); + @Monster_RouteClear(this); + } +} + +@Monster_InsertWaypoint(this, const Float:vecLocation[3], iMoveFlags) { + static rgWaypoint[MONSTER_WAYPOINT]; + xs_vec_copy(vecLocation, rgWaypoint[MONSTER_WAYPOINT_LOCATION]); + rgWaypoint[MONSTER_WAYPOINT_TYPE] = iMoveFlags; + + static Array:irgRoute; irgRoute = CE_GetMember(this, m_irgRoute); + + if (!ArraySize(irgRoute)) { + ArrayPushArray(irgRoute, rgWaypoint[any:0]); + return; + } + + static iRouteIndex; iRouteIndex = CE_GetMember(this, m_iRouteIndex); + + static rgCurrentWaypoint[MONSTER_WAYPOINT]; + ArrayGetArray(irgRoute, iRouteIndex, rgCurrentWaypoint[any:0], _:MONSTER_WAYPOINT); + + rgWaypoint[MONSTER_WAYPOINT_TYPE] |= (rgCurrentWaypoint[MONSTER_WAYPOINT_TYPE] & ~MF_NOT_TO_MASK); + + ArrayInsertArrayBefore(irgRoute, iRouteIndex, rgWaypoint[any:0]); +} + +@Monster_PushWaypoint(this, const Float:vecLocation[3], iMoveFlag) { + new rgWaypoint[MONSTER_WAYPOINT]; + xs_vec_copy(vecLocation, rgWaypoint[MONSTER_WAYPOINT_LOCATION]); + rgWaypoint[MONSTER_WAYPOINT_TYPE] = iMoveFlag | MF_IS_GOAL; + + static Array:irgRoute; irgRoute = CE_GetMember(this, m_irgRoute); + ArrayPushArray(irgRoute, rgWaypoint[any:0]); +} + +bool:@Monster_ShouldAdvanceRoute(this, Float:flWaypointDist) { + return flWaypointDist <= MONSTER_CUT_CORNER_DIST; +} + +@Monster_AdvanceRoute(this, Float:flDistance) { + static iRouteIndex; iRouteIndex = CE_GetMember(this, m_iRouteIndex); + static Array:irgRoute; irgRoute = CE_GetMember(this, m_irgRoute); + + if (iRouteIndex == ROUTE_SIZE - 1) { + @Monster_RefreshRoute(this); + return; + } + + static rgCurrentWaypoint[MONSTER_WAYPOINT]; ArrayGetArray(irgRoute, iRouteIndex, rgCurrentWaypoint[any:0], _:MONSTER_WAYPOINT); + + if (rgCurrentWaypoint[MONSTER_WAYPOINT_TYPE] & MF_IS_GOAL) { + if (flDistance < Float:CE_GetMember(this, m_flGroundSpeed) * 0.2) { + @Monster_MovementComplete(this); + } + + return; + } + + if ((rgCurrentWaypoint[MONSTER_WAYPOINT_TYPE] & ~MF_NOT_TO_MASK) == MF_TO_PATHCORNER) { + new pNextTarget = ExecuteHamB(Ham_GetNextTarget, CE_GetMember(this, m_pGoalEnt)); + CE_SetMember(this, m_pGoalEnt, pNextTarget); + } + + // Check if both waypoints are nodes and there is a link for a door + static rgNextWaypoint[MONSTER_WAYPOINT]; ArrayGetArray(irgRoute, iRouteIndex + 1, rgNextWaypoint[any:0], _:MONSTER_WAYPOINT); + if ((rgCurrentWaypoint[MONSTER_WAYPOINT_TYPE] & MF_TO_NODE) && (rgNextWaypoint[MONSTER_WAYPOINT_TYPE] & MF_TO_NODE)) { + engfunc(EngFunc_TraceLine, rgCurrentWaypoint[MONSTER_WAYPOINT_LOCATION], rgNextWaypoint[MONSTER_WAYPOINT_LOCATION], DONT_IGNORE_MONSTERS, this, g_pTrace); + + static pHit; pHit = get_tr2(g_pTrace, TR_pHit); + + if (pHit > 0) { + if (UTIL_IsDoor(pHit)) { + static Float:flMoveWaitFinished; flMoveWaitFinished = @Monster_OpenDoorAndWait(this, pHit); + CE_SetMember(this, m_flMoveWaitFinished, flMoveWaitFinished); + } + } + } + + CE_SetMember(this, m_iRouteIndex, iRouteIndex + 1); +} + +Float:@Monster_OpenDoorAndWait(this, pDoor) { + if (!UTIL_IsUsableEntity(pDoor, this)) return 0.0; + + ExecuteHamB(Ham_Use, pDoor, this, this, USE_ON, 0.0); + + static Float:flDoorNextThink; pev(pDoor, pev_nextthink, flDoorNextThink); + static Float:flDoorLastThink; pev(pDoor, pev_ltime, flDoorLastThink); + static Float:flTravelTime; flTravelTime = flDoorNextThink - flDoorLastThink; + + static szTargetName[32]; pev(pDoor, pev_targetname, szTargetName, charsmax(szTargetName)); + + if (equal(szTargetName, NULL_STRING)) { + + static pTarget; pTarget = FM_NULLENT; + while ((pTarget = engfunc(EngFunc_FindEntityByString, pTarget, "targetname", szTargetName)) != 0) { + if (pTarget == pDoor) continue; + + static szTargetClassname[32]; pev(pTarget, pev_classname, szTargetClassname, charsmax(szTargetClassname)); + + if (UTIL_IsDoor(pTarget)) { + ExecuteHamB(Ham_Use, pTarget, this, this, USE_ON, 0.0); + } + } + } + + return g_flGameTime + flTravelTime; +} + +@Monster_Stop(this) { + CE_SetMember(this, m_iIdealActivity, @Monster_GetStoppedActivity(this)); +} + +@Monster_Classify() { + return CLASS_NONE; +} + +@Monster_CineCleanup(this) {} + +@Monster_BestVisibleEnemy(this) { + new Float:flNearest = 8192.0; + new pNextEnt = get_ent_data_entity(this, "CBaseEntity", "m_pLink"); + new pReturn = FM_NULLENT; + new iBestRelationship = R_NO; + + // TODO: Check stop condition + while (pNextEnt != FM_NULLENT) { + if (ExecuteHamB(Ham_IsAlive, pNextEnt)) { + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + static Float:vecTarget[3]; pev(pNextEnt, pev_origin, vecTarget); + + if (@Monster_Relationship(this, pNextEnt) > iBestRelationship) { + iBestRelationship = @Monster_Relationship(this, pNextEnt); + flNearest = xs_vec_distance(vecOrigin, vecTarget); + pReturn = pNextEnt; + } else if (@Monster_Relationship(this, pNextEnt) == iBestRelationship) { + static Float:flDistance; flDistance = xs_vec_distance(vecOrigin, vecTarget); + + if (flDistance <= flNearest) { + flNearest = flDistance; + iBestRelationship = @Monster_Relationship(this, pNextEnt); + pReturn = pNextEnt; + } + } + } + + pNextEnt = get_ent_data_entity(pNextEnt, "CBaseEntity", "m_pLink"); + } + + return pReturn; +} + +@Monster_Relationship(this, pTarget) { + static const rgClassificationTable[14][14] = { + /* NONE MACH PLYR HPASS HMIL AMIL APASS AMONST APREY APRED INSECT PLRALY PBWPN ABWPN */ + /*NONE*/ { R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO }, + /*MACHINE*/ { R_NO, R_NO, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL }, + /*PLAYER*/ { R_NO, R_DL, R_NO, R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_NO, R_DL, R_DL }, + /*HUMANPASSIVE*/ { R_NO, R_NO, R_AL, R_AL, R_HT, R_FR, R_NO, R_HT, R_DL, R_FR, R_NO, R_AL, R_NO, R_NO }, + /*HUMANMILITAR*/ { R_NO, R_NO, R_HT, R_DL, R_NO, R_HT, R_DL, R_DL, R_DL, R_DL, R_NO, R_HT, R_NO, R_NO }, + /*ALIENMILITAR*/ { R_NO, R_DL, R_HT, R_DL, R_HT, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_DL, R_NO, R_NO }, + /*ALIENPASSIVE*/ { R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO }, + /*ALIENMONSTER*/ { R_NO, R_DL, R_DL, R_DL, R_DL, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_DL, R_NO, R_NO }, + /*ALIENPREY*/ { R_NO, R_NO, R_DL, R_DL, R_DL, R_NO, R_NO, R_NO, R_NO, R_FR, R_NO, R_DL, R_NO, R_NO }, + /*ALIENPREDATO*/ { R_NO, R_NO, R_DL, R_DL, R_DL, R_NO, R_NO, R_NO, R_HT, R_DL, R_NO, R_DL, R_NO, R_NO }, + /*INSECT*/ { R_FR, R_FR, R_FR, R_FR, R_FR, R_NO, R_FR, R_FR, R_FR, R_FR, R_NO, R_FR, R_NO, R_NO }, + /*PLAYERALLY*/ { R_NO, R_DL, R_AL, R_AL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_NO, R_NO, R_NO }, + /*PBIOWEAPON*/ { R_NO, R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_NO, R_DL }, + /*ABIOWEAPON*/ { R_NO, R_NO, R_DL, R_DL, R_DL, R_AL, R_NO, R_DL, R_DL, R_NO, R_NO, R_DL, R_DL, R_NO } + }; + + return rgClassificationTable[ExecuteHamB(Ham_Classify, this)][ExecuteHamB(Ham_Classify, pTarget)]; +} + +@Monster_PushEnemy(this, pEnemy, const Float:vecLastKnownPos[3]) { + if (pEnemy == FM_NULLENT) return; + + new Array:irgOldEnemies = CE_GetMember(this, m_irgOldEnemies); + new iOldEnemiesNum = ArraySize(irgOldEnemies); + + for (new i = 0; i < iOldEnemiesNum; ++i) { + if (ArrayGetCell(irgOldEnemies, i, _:MONSTER_ENEMY_ENTITY) == pEnemy) return; + } + + new rgEnemy[MONSTER_ENEMY]; + rgEnemy[MONSTER_ENEMY_ENTITY] = pEnemy; + rgEnemy[MONSTER_ENEMY_LOCATION] = vecLastKnownPos; + + ArrayPushArray(irgOldEnemies, rgEnemy[any:0]); +} + +bool:@Monster_PopEnemy(this) { + new Array:irgOldEnemies = CE_GetMember(this, m_irgOldEnemies); + new iOldEnemiesNum = ArraySize(irgOldEnemies); + + for (new i = iOldEnemiesNum - 1; i >= 0; --i) { + static rgEnemy[MONSTER_ENEMY]; ArrayGetArray(irgOldEnemies, i, rgEnemy[any:0]); + + if (ExecuteHamB(Ham_IsAlive, rgEnemy[MONSTER_ENEMY_ENTITY])) { + CE_SetMember(this, m_pEnemy, rgEnemy[MONSTER_ENEMY_ENTITY]); + CE_SetMemberVec(this, m_vecEnemyLKP, rgEnemy[MONSTER_ENEMY_LOCATION]); + return true; + } else { + ArrayDeleteItem(irgOldEnemies, i); + } + } + + return false; +} + +@Monster_GetTask(this, rgTask[MONSTER_TASK_DATA]) { + static Struct:sSchedule; sSchedule = CE_GetMember(this, m_sSchedule); + static iScheduleIndex; iScheduleIndex = CE_GetMember(this, m_iScheduleIndex); + + if (iScheduleIndex < 0) return -1; + if (iScheduleIndex >= StructGetCell(sSchedule, MONSTER_SCHEDULE_DATA_TASK_SIZE)) return -1; + + StructGetArray(sSchedule, MONSTER_SCHEDULE_DATA_TASK, rgTask, MONSTER_TASK_DATA, _:MONSTER_TASK_DATA * iScheduleIndex); + + return rgTask[MONSTER_TASK_DATA_ID]; +} + +@Monster_IsCurTaskContinuousMove(this) { + static rgTask[MONSTER_TASK_DATA]; + if (@Monster_GetTask(this, rgTask) == -1) return false; + + switch (rgTask[MONSTER_TASK_DATA_ID]) { + case TASK_WAIT_FOR_MOVEMENT: { + return true; + } + } + + return false; +} + +bool:@Monster_CanActiveIdle(this) { + return false; +} + +@Monster_MovementComplete(this) { + new iTaskStatus; CE_GetMember(this, m_iTaskStatus); + + switch (iTaskStatus) { + case MONSTER_TASK_STATUS_NEW, MONSTER_TASK_STATUS_RUNNING: { + CE_SetMember(this, m_iTaskStatus, MONSTER_TASK_STATUS_RUNNING_TASK); + } + case MONSTER_TASK_STATUS_RUNNING_MOVEMENT: { + @Monster_TaskComplete(this); + } + } + + CE_SetMember(this, m_iMovementGoal, MOVEGOAL_NONE); +} + +@Monster_MoveExecute(this, pTargetEnt, const Float:vecDir[3], Float:flInterval) { + static iMovementActivity; iMovementActivity = CE_GetMember(this, m_iMovementActivity); + static iIdealActivity; iIdealActivity = CE_GetMember(this, m_iIdealActivity); + + if (iIdealActivity != iMovementActivity) { + CE_SetMember(this, m_iIdealActivity, iMovementActivity); + } + + static Float:flStepSize; flStepSize = CE_GetMember(this, m_flStepSize); + static Float:flFrameRate; pev(this, pev_framerate, flFrameRate); + static Float:flTotal; flTotal = Float:CE_GetMember(this, m_flGroundSpeed) * flFrameRate * flInterval; + + while (flTotal > 0.001) { + static Float:flStep; flStep = floatmin(flStepSize, flTotal); + static Float:vecAngles[3]; pev(this, pev_angles, vecAngles); + @Monster_WalkMove(this, vecAngles[1], flStep, WALKMOVE_NORMAL); + flTotal -= flStep; + } +} + +Struct:@Monster_GetSchedule(this) { + static MONSTER_STATE:iMonsterState; iMonsterState = CE_GetMember(this, m_iMonsterState); + + switch (iMonsterState) { + case MONSTER_STATE_PRONE: { + return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_BARNACLE_VICTIM_GRAB); + } + case MONSTER_STATE_IDLE: { + if (@Monster_HasConditions(this, COND_HEAR_SOUND)) { + return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_ALERT_FACE); + } else if (!@Monster_IsRouteClear(this)) { + return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_IDLE_WALK); + } else { + return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_IDLE_STAND); + } + } + case MONSTER_STATE_ALERT: { + if (@Monster_HasConditions(this, COND_ENEMY_DEAD) && @Monster_LookupActivity(this, ACT_VICTORY_DANCE) != ACTIVITY_NOT_AVAILABLE) { + return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_VICTORY_DANCE); + } + + if (@Monster_HasConditions(this, COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE)) { + static Float:flFieldOfView; flFieldOfView = CE_GetMember(this, m_flFieldOfView); + + if (floatabs(@Monster_YawDiff(this)) < (1.0 - flFieldOfView) * 60) { + return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_TAKE_COVER_FROM_ORIGIN); + } + + return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_ALERT_SMALL_FLINCH); + } + + if (@Monster_HasConditions(this, COND_HEAR_SOUND)) { + return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_ALERT_FACE); + } + + return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_ALERT_STAND); + } + case MONSTER_STATE_COMBAT: { + if (@Monster_HasConditions(this, COND_ENEMY_DEAD)) { + CE_SetMember(this, m_pEnemy, FM_NULLENT); + + if (@Monster_GetEnemy(this)) { + @Monster_ClearConditions(this, COND_ENEMY_DEAD); + } else { + @Monster_SetState(this, MONSTER_STATE_ALERT); + } + + return CE_CallMethod(this, GetSchedule); + } + + if (@Monster_HasConditions(this, COND_NEW_ENEMY)) { + return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_WAKE_ANGRY); + } + + if (@Monster_HasConditions(this, COND_LIGHT_DAMAGE) && !@Monster_HasMemory(this, MEMORY_FLINCHED)) { + return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_SMALL_FLINCH); + } + + if (!@Monster_HasConditions(this, COND_SEE_ENEMY)) { + if (!@Monster_HasConditions(this, COND_ENEMY_OCCLUDED)) { + return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_COMBAT_FACE); + } + + return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_CHASE_ENEMY); + } + + if (@Monster_HasConditions(this, COND_CAN_RANGE_ATTACK1)) return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_RANGE_ATTACK1); + if (@Monster_HasConditions(this, COND_CAN_RANGE_ATTACK2)) return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_RANGE_ATTACK2); + if (@Monster_HasConditions(this, COND_CAN_MELEE_ATTACK1)) return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_MELEE_ATTACK1); + if (@Monster_HasConditions(this, COND_CAN_MELEE_ATTACK2)) return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_MELEE_ATTACK2); + if (!@Monster_HasConditions(this, COND_CAN_RANGE_ATTACK1 | COND_CAN_MELEE_ATTACK1)) return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_CHASE_ENEMY); + if (!@Monster_FacingIdeal(this)) return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_COMBAT_FACE); + } + case MONSTER_STATE_DEAD: { + return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_DIE); + } + case MONSTER_STATE_SCRIPT: { + new pCine = CE_GetMember(this, m_pCine); + + if (!pCine) { + @Monster_CineCleanup(this); + return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_IDLE_STAND); + } + + return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_AISCRIPT); + } + } + + return _GetSharedSchedule(MONSTER_SHARED_SCHED_ERROR); +} + +Struct:@Monster_GetScheduleOfType(this, MONSTER_SCHEDULE_TYPE:iType) { + switch (iType) { + case MONSTER_SCHED_AISCRIPT: { + new pCine = CE_GetMember(this, m_pCine); + + if (!pCine) { + @Monster_CineCleanup(this); + return CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHED_IDLE_STAND); + } + + new iMoveTo = CE_GetMember(pCine, "iMoveTo"); + + switch (iMoveTo) { + case 0, 4: return _GetSharedSchedule(MONSTER_SHARED_SCHED_WAIT_SCRIPT); + case 1: return _GetSharedSchedule(MONSTER_SHARED_SCHED_WALK_TO_SCRIPT); + case 2: return _GetSharedSchedule(MONSTER_SHARED_SCHED_RUN_TO_SCRIPT); + case 5: return _GetSharedSchedule(MONSTER_SHARED_SCHED_FACE_SCRIPT); + } + } + case MONSTER_SCHED_IDLE_STAND: { + if (random(14) == 0 && @Monster_CanActiveIdle(this)) { + return _GetSharedSchedule(MONSTER_SHARED_SCHED_ACTIVE_IDLE); + } + + return _GetSharedSchedule(MONSTER_SHARED_SCHED_IDLE_STAND); + } + case MONSTER_SCHED_IDLE_WALK: return _GetSharedSchedule(MONSTER_SHARED_SCHED_IDLE_WALK); + case MONSTER_SCHED_WAIT_TRIGGER: return _GetSharedSchedule(MONSTER_SHARED_SCHED_WAIT_TRIGGER); + case MONSTER_SCHED_WAKE_ANGRY: return _GetSharedSchedule(MONSTER_SHARED_SCHED_WAKE_ANGRY); + case MONSTER_SCHED_ALERT_FACE: return _GetSharedSchedule(MONSTER_SHARED_SCHED_ALERT_FACE); + case MONSTER_SCHED_ALERT_STAND: return _GetSharedSchedule(MONSTER_SHARED_SCHED_ALERT_STAND); + case MONSTER_SCHED_COMBAT_STAND: return _GetSharedSchedule(MONSTER_SHARED_SCHED_COMBAT_STAND); + case MONSTER_SCHED_COMBAT_FACE: return _GetSharedSchedule(MONSTER_SHARED_SCHED_COMBAT_FACE); + case MONSTER_SCHED_CHASE_ENEMY: return _GetSharedSchedule(MONSTER_SHARED_SCHED_CHASE_ENEMY); + case MONSTER_SCHED_CHASE_ENEMY_FAILED: return _GetSharedSchedule(MONSTER_SHARED_SCHED_FAIL); + case MONSTER_SCHED_SMALL_FLINCH: return _GetSharedSchedule(MONSTER_SHARED_SCHED_SMALL_FLINCH); + case MONSTER_SCHED_ALERT_SMALL_FLINCH: return _GetSharedSchedule(MONSTER_SHARED_SCHED_ALERT_SMALL_FLINCH); + case MONSTER_SCHED_RELOAD: return _GetSharedSchedule(MONSTER_SHARED_SCHED_RELOAD); + case MONSTER_SCHED_ARM_WEAPON: return _GetSharedSchedule(MONSTER_SHARED_SCHED_ARM_WEAPON); + case MONSTER_SCHED_STANDOFF: return _GetSharedSchedule(MONSTER_SHARED_SCHED_STANDOFF); + case MONSTER_SCHED_RANGE_ATTACK1: return _GetSharedSchedule(MONSTER_SHARED_SCHED_RANGE_ATTACK1); + case MONSTER_SCHED_RANGE_ATTACK2: return _GetSharedSchedule(MONSTER_SHARED_SCHED_RANGE_ATTACK2); + case MONSTER_SCHED_MELEE_ATTACK1: return _GetSharedSchedule(MONSTER_SHARED_SCHED_MELEE_ATTACK1); + case MONSTER_SCHED_MELEE_ATTACK2: return _GetSharedSchedule(MONSTER_SHARED_SCHED_MELEE_ATTACK2); + case MONSTER_SCHED_SPECIAL_ATTACK1: return _GetSharedSchedule(MONSTER_SHARED_SCHED_SPECIAL_ATTACK1); + case MONSTER_SCHED_SPECIAL_ATTACK2: return _GetSharedSchedule(MONSTER_SHARED_SCHED_SPECIAL_ATTACK2); + case MONSTER_SCHED_TAKE_COVER_FROM_BEST_SOUND: return _GetSharedSchedule(MONSTER_SHARED_SCHED_TAKE_COVER_FROM_BEST_SOUND); + case MONSTER_SCHED_TAKE_COVER_FROM_ENEMY: return _GetSharedSchedule(MONSTER_SHARED_SCHED_TAKE_COVER_FROM_ENEMY); + case MONSTER_SCHED_COWER: return _GetSharedSchedule(MONSTER_SHARED_SCHED_COWER); + case MONSTER_SCHED_AMBUSH: return _GetSharedSchedule(MONSTER_SHARED_SCHED_AMBUSH); + case MONSTER_SCHED_BARNACLE_VICTIM_GRAB: return _GetSharedSchedule(MONSTER_SHARED_SCHED_BARNACLE_VICTIM_GRAB); + case MONSTER_SCHED_BARNACLE_VICTIM_CHOMP: return _GetSharedSchedule(MONSTER_SHARED_SCHED_BARNACLE_VICTIM_CHOMP); + case MONSTER_SCHED_INVESTIGATE_SOUND: return _GetSharedSchedule(MONSTER_SHARED_SCHED_INVESTIGATE_SOUND); + case MONSTER_SCHED_DIE: return _GetSharedSchedule(MONSTER_SHARED_SCHED_DIE); + case MONSTER_SCHED_TAKE_COVER_FROM_ORIGIN: return _GetSharedSchedule(MONSTER_SHARED_SCHED_TAKE_COVER_FROM_ORIGIN); + case MONSTER_SCHED_VICTORY_DANCE: return _GetSharedSchedule(MONSTER_SHARED_SCHED_VICTORY_DANCE); + case MONSTER_SCHED_FAIL: return _GetSharedSchedule(MONSTER_SHARED_SCHED_FAIL); + default: return _GetSharedSchedule(MONSTER_SHARED_SCHED_IDLE_STAND); + } + + return Invalid_Struct; +} + +@Monster_ClearSchedule(this) { + new Struct:sSchedule = CE_GetMember(this, m_sSchedule); + if (sSchedule != Invalid_Struct && StructGetCell(sSchedule, MONSTER_SCHEDULE_DATA_SHARED_ID) == MONSTER_SHARED_SCHED_INVALID) { + StructDestroy(sSchedule); + } + + CE_SetMember(this, m_iTaskStatus, MONSTER_TASK_STATUS_NEW); + CE_SetMember(this, m_sSchedule, Invalid_Struct); + CE_SetMember(this, m_iScheduleIndex, 0); + CE_SetMember(this, m_iFailSchedule, MONSTER_SCHED_NONE); +} + +@Monster_ChangeSchedule(this, Struct:sNewSchedule) { + new Struct:sSchedule = CE_GetMember(this, m_sSchedule); + if (sSchedule != Invalid_Struct && StructGetCell(sSchedule, MONSTER_SCHEDULE_DATA_SHARED_ID) == MONSTER_SHARED_SCHED_INVALID) { + StructDestroy(sSchedule); + } + + CE_SetMember(this, m_iTaskStatus, MONSTER_TASK_STATUS_NEW); + CE_SetMember(this, m_sSchedule, sNewSchedule); + CE_SetMember(this, m_iScheduleIndex, 0); + CE_SetMember(this, m_iConditions, 0); + CE_SetMember(this, m_iFailSchedule, MONSTER_SCHED_NONE); +} + +@Monster_TaskBegin(this) { + CE_SetMember(this, m_iTaskStatus, MONSTER_TASK_STATUS_RUNNING); +} + +@Monster_TaskComplete(this) { + CE_SetMember(this, m_iTaskStatus, MONSTER_TASK_STATUS_COMPLETE); +} + +@Monster_TaskFail(this) { + @Monster_SetConditions(this, COND_TASK_FAILED); +} + +bool:@Monster_TaskIsComplete(this) { + return CE_GetMember(this, m_iTaskStatus) == MONSTER_TASK_STATUS_COMPLETE; +} + +@Monster_SetTurnActivity(this) { + static Float:flYD; flYD = @Monster_YawDiff(this); + + if (flYD <= -45.0 && @Monster_LookupActivity(this, ACT_TURN_RIGHT) != ACTIVITY_NOT_AVAILABLE) { + CE_SetMember(this, m_iIdealActivity, ACT_TURN_RIGHT); + } else if (flYD > 45.0 && @Monster_LookupActivity(this, ACT_TURN_LEFT) != ACTIVITY_NOT_AVAILABLE) { + CE_SetMember(this, m_iIdealActivity, ACT_TURN_LEFT); + } +} + +Float:@Monster_YawDiff(this) { + static Float:vecAngles[3]; pev(this, pev_angles, vecAngles); + static Float:flIdealYaw; pev(this, pev_ideal_yaw, flIdealYaw); + static Float:flCurrentYaw; flCurrentYaw = UTIL_AngleMod(vecAngles[1]); + + if (flCurrentYaw == flIdealYaw) return 0.0; + + return UTIL_AngleDiff(flIdealYaw, flCurrentYaw); +} + +@Monster_Remember(this, iMemory) { + CE_SetMember(this, m_iMemory, CE_GetMember(this, m_iMemory) | iMemory); +} + +@Monster_Forget(this, iMemory) { + CE_SetMember(this, m_iMemory, CE_GetMember(this, m_iMemory) & ~iMemory); +} + +bool:@Monster_HasMemory(this, iMemory) { + return !!(CE_GetMember(this, m_iMemory) & iMemory); +} + +bool:@Monster_HasAllMemory(this, iMemory) { + return (CE_GetMember(this, m_iMemory) & iMemory) == iMemory; +} + +@Monster_SetConditions(this, iConditions) { + CE_SetMember(this, m_iConditions, CE_GetMember(this, m_iConditions) | iConditions); +} + +@Monster_ClearConditions(this, iConditions) { + CE_SetMember(this, m_iConditions, CE_GetMember(this, m_iConditions) & ~iConditions); +} + +bool:@Monster_HasConditions(this, iConditions) { + return !!(CE_GetMember(this, m_iConditions) & iConditions); +} + +bool:@Monster_HasAllConditions(this, iConditions) { + return (CE_GetMember(this, m_iConditions) & iConditions) == iConditions; +} + +@Monster_IgnoreConditions(this) { + new iIgnoreConditions; iIgnoreConditions = 0; + + if (!@Monster_ShouldEat(this)) { + iIgnoreConditions |= COND_SMELL_FOOD; + } + + if (CE_GetMember(this, m_iMonsterState) == MONSTER_STATE_SCRIPT) { + static pCine; pCine = CE_GetMember(this, m_pCine); + + if (pCine != FM_NULLENT) { + iIgnoreConditions |= CE_CallMethod(pCine, IgnoreConditions); + } + } + + return iIgnoreConditions; +} + +bool:@Monster_ShouldEat(this) { + return Float:CE_GetMember(this, m_flHungryTime) <= g_flGameTime; +} + +@Monster_MakeIdealYaw(this, const Float:vecTarget[]) { + static iMovementActivity; iMovementActivity = CE_GetMember(this, m_iMovementActivity); + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + + static Float:vecProjection[3]; + + switch (iMovementActivity) { + case ACT_STRAFE_LEFT: { + xs_vec_set(vecProjection, -vecTarget[1], vecTarget[0], 0.0); + } + case ACT_STRAFE_RIGHT: { + xs_vec_set(vecProjection, vecTarget[1], vecTarget[0], 0.0); + } + default: { + xs_vec_copy(vecTarget, vecProjection); + } + } + + static Float:vecDirection[3]; xs_vec_sub(vecProjection, vecOrigin, vecDirection); + static Float:vecAngles[3]; vector_to_angle(vecDirection, vecAngles); + + set_pev(this, pev_ideal_yaw, vecAngles[1]); +} + +Float:@Monster_ChangeYaw(this, Float:flYawSpeed) { + static Float:vecAngles[3]; pev(this, pev_angles, vecAngles); + static Float:flCurrent; flCurrent = vecAngles[1]; + static Float:flIdealYaw; pev(this, pev_ideal_yaw, flIdealYaw); + + if (flCurrent == flIdealYaw) return 0.0; + + static Float:flFrameTime; global_get(glb_frametime, flFrameTime); + flFrameTime *= 10.0; + + vecAngles[1] = UTIL_ApproachAngle(flIdealYaw, flCurrent, flYawSpeed * flFrameTime); + + set_pev(this, pev_angles, vecAngles); + + static Float:flDiff; flDiff = UTIL_AngleDiff(flIdealYaw, vecAngles[1]); + + // turn head in desired direction only if they have a turnable head + if (CE_GetMember(this, m_iCapability) & CAP_TURN_HEAD) { + set_controller(this, 0, flDiff); + } + + return flDiff; +} + +bool:@Monster_FacingIdeal(this) { + return floatabs(@Monster_YawDiff(this)) <= 0.006; +} + +bool:@Monster_MoveToEnemy(this, Activity:iActivity, Float:flWaitTime) { + CE_SetMember(this, m_iMovementActivity, iActivity); + CE_SetMember(this, m_flMoveWaitTime, flWaitTime); + CE_SetMember(this, m_iMovementGoal, MOVEGOAL_ENEMY); + + return @Monster_RefreshRoute(this); +} + +bool:@Monster_MoveToTarget(this, Activity:iActivity, Float:flWaitTime) { + CE_SetMember(this, m_iMovementActivity, iActivity); + CE_SetMember(this, m_flMoveWaitTime, flWaitTime); + CE_SetMember(this, m_iMovementGoal, MOVEGOAL_TARGETENT); + + return @Monster_RefreshRoute(this); +} + +bool:@Monster_MoveToLocation(this, Activity:iActivity, Float:flWaitTime, const Float:vecGoal[]) { + CE_SetMember(this, m_iMovementActivity, iActivity); + CE_SetMember(this, m_flMoveWaitTime, flWaitTime); + CE_SetMember(this, m_iMovementGoal, MOVEGOAL_LOCATION); + CE_SetMemberVec(this, m_vecMoveGoal, vecGoal); + + return @Monster_RefreshRoute(this); +} + +@Monster_FindHintNode(this) { return 0; } + +@Monster_NextScheduledTask(this) { + if (CE_GetMember(this, m_sSchedule) == Invalid_Struct) return; + + CE_SetMember(this, m_iTaskStatus, MONSTER_TASK_STATUS_NEW); + CE_SetMember(this, m_iScheduleIndex, CE_GetMember(this, m_iScheduleIndex) + 1); + + if (@Monster_ScheduleDone(this)) { + @Monster_SetConditions(this, COND_SCHEDULE_DONE); + } +} + +@Monster_ScheduleDone(this) { + static Struct:sSchedule; sSchedule = CE_GetMember(this, m_sSchedule); + + return CE_GetMember(this, m_iScheduleIndex) == StructGetCell(sSchedule, MONSTER_SCHEDULE_DATA_TASK_SIZE); +} + +@Monster_ScheduleValid(this) { + static Struct:sSchedule; sSchedule = CE_GetMember(this, m_sSchedule); + + if (sSchedule == Invalid_Struct) return false; + + static iInterruptMask; iInterruptMask = StructGetCell(sSchedule, MONSTER_SCHEDULE_DATA_INTERRUPT_MASK); + if (@Monster_HasConditions(this, iInterruptMask | COND_SCHEDULE_DONE | COND_TASK_FAILED)) { + return false; + } + + return true; +} + +@Monster_ScheduleChange(this) {} + +@Monster_ScheduleFlags(this) { + static Struct:sSchedule; sSchedule = CE_GetMember(this, m_sSchedule); + + if (sSchedule == Invalid_Struct) return 0; + + static iConditions; iConditions = CE_GetMember(this, m_iConditions); + static iInterruptMask; iInterruptMask = StructGetCell(sSchedule, MONSTER_SCHEDULE_DATA_INTERRUPT_MASK); + + return iConditions & iInterruptMask; +} + +MONSTER_STATE:@Monster_GetIdealState(this) { + static iConditions; iConditions = @Monster_ScheduleFlags(this); + static MONSTER_STATE:iMonsterState; iMonsterState = CE_GetMember(this, m_iMonsterState); + + switch (iMonsterState) { + case MONSTER_STATE_IDLE: { + new Float:vecEnemyLKP[3]; CE_GetMemberVec(this, m_vecEnemyLKP, vecEnemyLKP); + + if (iConditions & COND_NEW_ENEMY) { + CE_SetMember(this, m_iIdealMonsterState, MONSTER_STATE_COMBAT); + } else if (iConditions & COND_LIGHT_DAMAGE) { + @Monster_MakeIdealYaw(this, vecEnemyLKP); + CE_SetMember(this, m_iIdealMonsterState, MONSTER_STATE_ALERT); + } else if (iConditions & COND_HEAVY_DAMAGE) { + @Monster_MakeIdealYaw(this, vecEnemyLKP); + CE_SetMember(this, m_iIdealMonsterState, MONSTER_STATE_ALERT); + } else if (iConditions & COND_HEAR_SOUND) { + new iSound = @Monster_BestSound(this); + if (iSound != -1) { + @Monster_MakeIdealYaw(this, g_rgSounds[iSound][Sound_Origin]); + + static iType; iType = g_rgSounds[iSound][Sound_Type]; + if (iType & (SOUND_COMBAT | SOUND_DANGER)) { + CE_SetMember(this, m_iIdealMonsterState, MONSTER_STATE_ALERT); + } + } + } else if (iConditions & (COND_SMELL | COND_SMELL_FOOD)) { + CE_SetMember(this, m_iIdealMonsterState, MONSTER_STATE_ALERT); + } + } + case MONSTER_STATE_ALERT: { + if (iConditions & (COND_NEW_ENEMY | COND_SEE_ENEMY)) { + CE_SetMember(this, m_iIdealMonsterState, MONSTER_STATE_COMBAT); + } else if (iConditions & COND_HEAR_SOUND) { + CE_SetMember(this, m_iIdealMonsterState, MONSTER_STATE_ALERT); + + new iSound = @Monster_BestSound(this); + if (iSound != -1) { + @Monster_MakeIdealYaw(this, g_rgSounds[iSound][Sound_Origin]); + } + } + } + case MONSTER_STATE_COMBAT: { + if (CE_GetMember(this, m_pEnemy) == FM_NULLENT) { + CE_SetMember(this, m_iIdealMonsterState, MONSTER_STATE_ALERT); + } + } + case MONSTER_STATE_SCRIPT: { + if (iConditions & (COND_TASK_FAILED | COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE)) { + @Monster_ExitScriptedSequence(this); + } + } + case MONSTER_STATE_DEAD: { + CE_SetMember(this, m_iIdealMonsterState, MONSTER_STATE_DEAD); + } + } + + return CE_GetMember(this, m_iIdealMonsterState); +} + +bool:@Monster_ExitScriptedSequence(this) { + if (pev(this, pev_deadflag) == DEAD_DYING) { + CE_SetMember(this, m_iIdealMonsterState, MONSTER_STATE_DEAD); + return false; + } + + new pCine = CE_GetMember(this, m_pCine); + + if (pCine) { + CE_CallMethod(pCine, "CancelScript"); + } + + return true; +} + +@Monster_GetSequenceFlags(this) { + static iSequence; iSequence = pev(this, pev_sequence); + + static Array:irgSequences; irgSequences = CE_GetMember(this, m_irgSequences); + if (irgSequences == Invalid_Array) return 0; + + return ArrayGetCell(irgSequences, iSequence, _:Sequence_Flags); +} + +@Monster_SetYawSpeed(this) { + set_pev(this, pev_yaw_speed, 180.0); +} + +@Monster_SetActivity(this, Activity:iNewActivity) { + static Activity:iSequence; iSequence = @Monster_LookupActivity(this, iNewActivity); + static bool:bSequenceLoops; bSequenceLoops = CE_GetMember(this, m_bSequenceLoops); + static Activity:iActivity; iActivity = CE_GetMember(this, m_iActivity); + + // Set to the desired anim, or default anim if the desired is not present + if (iSequence > ACTIVITY_NOT_AVAILABLE) { + if (pev(this, pev_sequence) != _:iSequence || !bSequenceLoops) { + if (!(iActivity == ACT_WALK || iActivity == ACT_RUN) || !(iNewActivity == ACT_WALK || iNewActivity == ACT_RUN)) { + set_pev(this, pev_frame, 0); + } + } + + set_pev(this, pev_sequence, iSequence); + + @Monster_ResetSequenceInfo(this); + CE_CallMethod(this, SetYawSpeed); + } else { + set_pev(this, pev_sequence, 0); + } + + CE_SetMember(this, m_iActivity, iNewActivity); + CE_SetMember(this, m_iIdealActivity, iNewActivity); +} + +bool:@Monster_TaskIsRunning(this) { + static MONSTER_TASK_STATUS:iTaskStatus; iTaskStatus = CE_GetMember(this, m_iTaskStatus); + + return (iTaskStatus != MONSTER_TASK_STATUS_COMPLETE && iTaskStatus != MONSTER_TASK_STATUS_RUNNING_MOVEMENT); +} + +@Monster_SetState(this, MONSTER_STATE:iState) { + switch (iState) { + case MONSTER_STATE_IDLE: { + if (CE_GetMember(this, m_pEnemy) != FM_NULLENT) { + CE_SetMember(this, m_pEnemy, FM_NULLENT); + } + } + } + + CE_SetMember(this, m_iMonsterState, iState); + CE_SetMember(this, m_iIdealMonsterState, iState); +} + +@Monster_RouteClassify(this, iMoveFlag) { + if (iMoveFlag & MF_TO_TARGETENT) return MOVEGOAL_TARGETENT; + if (iMoveFlag & MF_TO_ENEMY) return MOVEGOAL_ENEMY; + if (iMoveFlag & MF_TO_PATHCORNER) return MOVEGOAL_PATHCORNER; + if (iMoveFlag & MF_TO_NODE) return MOVEGOAL_NODE; + if (iMoveFlag & MF_TO_NAV) return MOVEGOAL_NAV; + if (iMoveFlag & MF_TO_LOCATION) return MOVEGOAL_LOCATION; + + return MOVEGOAL_NONE; +} + +MONSTER_LOCALMOVE:@Monster_CheckLocalMove(this, const Float:vecStart[3], const Float:vecEnd[3], pTarget, bool:bValidateZ, &Float:flOutDist) { + new MONSTER_LOCALMOVE:iReturn = MONSTER_LOCALMOVE_VALID; + + static Float:flStepSize; flStepSize = CE_GetMember(this, m_flStepSize); + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + static Float:vecMins[3]; pev(this, pev_mins, vecMins); + + static Float:vecDirection[3]; + xs_vec_sub(vecEnd, vecStart, vecDirection); + xs_vec_normalize(vecDirection, vecDirection); + + static Float:vecAngles[3]; vector_to_angle(vecDirection, vecAngles); + + static Float:flDist; flDist = xs_vec_distance(vecStart, vecEnd); + static iFlags; iFlags = pev(this, pev_flags); + + engfunc(EngFunc_SetOrigin, this, vecStart); + + if (~iFlags & (FL_FLY | FL_SWIM)) { + engfunc(EngFunc_DropToFloor, this); + } + + for (new Float:flStep = 0.0; flStep < flDist; flStep += flStepSize) { + static Float:flCurrentStepSize; flCurrentStepSize = flStepSize; + + if ((flStep + flCurrentStepSize) >= (flDist - 1.0)) { + flCurrentStepSize = (flDist - flStep) - 1.0; + } + + if (!@Monster_WalkMove(this, vecAngles[1], flCurrentStepSize, WALKMOVE_CHECKONLY)) { + flOutDist = flStep; + + if ((pTarget != FM_NULLENT && (pTarget == g_pHit))) { + iReturn = MONSTER_LOCALMOVE_VALID; + } else { + iReturn = MONSTER_LOCALMOVE_INVALID; + } + + break; + } + } + + if (bValidateZ && iReturn == MONSTER_LOCALMOVE_VALID) { + if (!(iFlags & (FL_FLY | FL_SWIM)) && (pTarget == FM_NULLENT || (pev(pTarget, pev_flags) & FL_ONGROUND))) { + static Float:vecAbsMin[3]; pev(this, pev_absmin, vecAbsMin); + static Float:vecAbsMax[3]; pev(this, pev_absmax, vecAbsMax); + + static Float:vecTargetAbsMin[3]; + static Float:vecTargetAbsMax[3]; + + if (pTarget == FM_NULLENT) { + xs_vec_copy(vecEnd, vecTargetAbsMin); + xs_vec_copy(vecEnd, vecTargetAbsMax); + } else { + pev(pTarget, pev_absmin, vecTargetAbsMin); + pev(pTarget, pev_absmax, vecTargetAbsMax); + } + + if (vecTargetAbsMax[2] < vecAbsMin[2] || vecTargetAbsMin[2] > vecAbsMax[2]) { + iReturn = MONSTER_LOCALMOVE_INVALID_DONT_TRIANGULATE; + } + } + } + + engfunc(EngFunc_SetOrigin, this, vecOrigin); + + return iReturn; +} + +bool:@Monster_WalkMove(this, Float:flYaw, Float:flStep, iMode) { + // static iFlags; iFlags = pev(this, pev_flags); + // static bool:bMonsterClip; bMonsterClip = !!(iFlags & FL_MONSTERCLIP); + static Float:vecMove[3]; xs_vec_set(vecMove, floatcos(flYaw, degrees) * flStep, floatsin(flYaw, degrees) * flStep, 0.0); + + switch (iMode) { + // case WALKMOVE_WORLDONLY: @Monster_MoveTest(this, vecMove, true); + case WALKMOVE_NORMAL: return @Monster_MoveStep(this, vecMove, true); + case WALKMOVE_CHECKONLY: return @Monster_MoveStep(this, vecMove, false); + } + + return false; +} + +bool:@Monster_MoveStep(this, const Float:vecMove[3], bool:bRelink) { + static bool:bSuccessed; bSuccessed = false; + + static iFlags; iFlags = pev(this, pev_flags); + static Float:flStepHeight; flStepHeight = CE_GetMember(this, m_flStepHeight); + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + + if (iFlags & (FL_SWIM | FL_FLY)) { + // TODO: Implement + } else { + static Float:vecSrc[3]; xs_vec_copy(vecOrigin, vecSrc); + static Float:vecEnd[3]; xs_vec_add(vecOrigin, vecMove, vecEnd); + + bSuccessed = @Monster_Trace(this, vecSrc, vecEnd, DONT_IGNORE_MONSTERS, this, g_pTrace); + + // If move forward failed then try to make height step and repeat + if (!bSuccessed) { + xs_vec_set(vecEnd, vecSrc[0], vecSrc[1], vecSrc[2] + flStepHeight); + + @Monster_Trace(this, vecSrc, vecEnd, DONT_IGNORE_MONSTERS, this, g_pTrace); + get_tr2(g_pTrace, TR_vecEndPos, vecSrc); + xs_vec_add(vecSrc, vecMove, vecEnd); + + bSuccessed = @Monster_Trace(this, vecSrc, vecEnd, DONT_IGNORE_MONSTERS, this, g_pTrace); + + // TODO: Investigate ability to allow monsters go through the doors + } + + g_pHit = get_tr2(g_pTrace, TR_pHit); + + // Do step down + if (bSuccessed) { + #define OFFSET_TO_HIT_GROUND 0.1 + + get_tr2(g_pTrace, TR_vecEndPos, vecSrc); + xs_vec_set(vecEnd, vecSrc[0], vecSrc[1], vecSrc[2] - flStepHeight - OFFSET_TO_HIT_GROUND); + + if (!@Monster_Trace(this, vecSrc, vecEnd, DONT_IGNORE_MONSTERS, this, g_pTrace)) { + get_tr2(g_pTrace, TR_vecEndPos, vecOrigin); + } else { + xs_vec_copy(vecEnd, vecOrigin); + } + + // static Float:vecPlaneNormal[3]; get_tr2(g_pTrace, TR_vecPlaneNormal, vecPlaneNormal); + + // bSuccessed = vecPlaneNormal[2] > 0.5; + + // if (vecPlaneNormal[2] <= 0.5) { + // log_amx("vecPlaneNormal[2] %f", vecPlaneNormal[2]); + // } + } + + // TODO: Investigate ability to allow monsters to fall + } + + get_tr2(g_pTrace, TR_vecEndPos, vecOrigin); + + if (bSuccessed) { + if (bRelink) { + engfunc(EngFunc_SetOrigin, this, vecOrigin); + } else { + set_pev(this, pev_origin, vecOrigin); + } + } + + return bSuccessed; +} + +bool:@Monster_Trace(this, const Float:vecSrc[3], const Float:vecEnd[3], iTraceFlags, pIgnoreEnt, pTrace) { + engfunc(EngFunc_TraceMonsterHull, this, vecSrc, vecEnd, iTraceFlags, pIgnoreEnt, pTrace); + + if (get_tr2(pTrace, TR_AllSolid)) return false; + + static Float:flFraction; get_tr2(pTrace, TR_flFraction, flFraction); + if (flFraction != 1.0) return false; + + return true; +} + +@Monster_RouteNew(this) { + new Array:irgRoute = CE_GetMember(this, m_irgRoute); + ArrayClear(irgRoute); + + CE_SetMember(this, m_iRouteIndex, 0); + + if (@Monster_HasConditions(this, COND_WAIT_FOR_PATH)) { + static NavBuildPathTask:pTask; pTask = CE_GetMember(this, m_pPathTask); + Nav_Path_FindTask_Abort(pTask); + + @Monster_ClearConditions(this, COND_WAIT_FOR_PATH); + CE_SetMember(this, m_pPathTask, Invalid_NavBuildPathTask); + } +} + +@Monster_RouteSimplify(this, pTarget) {} + +@Monster_HullIndex(this) { + return HULL_HUMAN; +} + +bool:@Monster_GetNodeRoute(this, const Float:vecDest[3]) { + if (!g_bUseAstar) return false; + + #if defined _api_navsystem_included + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + + static NavBuildPathTask:pTask; pTask = Nav_Path_Find(vecOrigin, vecDest, "NavPathCallback", this, this, "NavPathCost"); + if (pTask == Invalid_NavBuildPathTask) return false; + + CE_SetMember(this, m_pPathTask, pTask); + @Monster_SetConditions(this, COND_WAIT_FOR_PATH); + + return true; + #else + return false; + #endif +} + +@Monster_HandlePathTask(this) { + if (!@Monster_HasConditions(this, COND_WAIT_FOR_PATH)) return; + + @Monster_ClearConditions(this, COND_WAIT_FOR_PATH); + + static NavBuildPathTask:pTask; pTask = CE_GetMember(this, m_pPathTask); + + if (Nav_Path_FindTask_IsSuccessed(pTask)) { + new NavPath:pPath = Nav_Path_FindTask_GetPath(pTask); + if (Nav_Path_IsValid(pPath)) { + @Monster_MoveNavPath(this, pPath); + } else { + @Monster_TaskFail(this); + } + } else { + @Monster_TaskFail(this); + } + + CE_SetMember(this, m_pPathTask, Invalid_NavBuildPathTask); +} + +@Monster_MoveNavPath(this, NavPath:pPath) { + @Monster_RouteNew(this); + + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + + static Array:irgRoute; irgRoute = CE_GetMember(this, m_irgRoute); + + static iSegmentsNum; iSegmentsNum = Nav_Path_GetSegmentCount(pPath); + + for (new iSegment = 0; iSegment < iSegmentsNum; ++iSegment) { + static Float:vecPos[3]; Nav_Path_GetSegmentPos(pPath, iSegment, vecPos); + + static rgWaypoint[MONSTER_WAYPOINT]; + xs_vec_copy(vecPos, rgWaypoint[MONSTER_WAYPOINT_LOCATION]); + rgWaypoint[MONSTER_WAYPOINT_TYPE] = MF_TO_NODE; + + if (iSegment == iSegmentsNum - 1) { + rgWaypoint[MONSTER_WAYPOINT_TYPE] = MF_IS_GOAL; + } + + ArrayPushArray(irgRoute, rgWaypoint[any:0]); + } +} + +bool:@Monster_MoveToNode(this, Activity:iActivity, Float:flWaitTime, const Float:vecGoal[3]) { + CE_SetMember(this, m_iMovementActivity, iActivity); + CE_SetMember(this, m_flMoveWaitTime, flWaitTime); + CE_SetMember(this, m_iMovementGoal, MOVEGOAL_NODE); + CE_SetMemberVec(this, m_vecMoveGoal, vecGoal); + + return @Monster_RefreshRoute(this); +} + +bool:@Monster_MovementIsComplete(this) { + return CE_GetMember(this, m_iMovementGoal) == MOVEGOAL_NONE; +} + +bool:@Monster_Triangulate(this, const Float:vecStart[], const Float:vecEnd[], Float:flDist, pTargetEnt, Float:vecApex[3]) { + static iMoveType; iMoveType = pev(this, pev_movetype); + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + static Float:vecSize[3]; pev(this, pev_size, vecSize); + + static Float:flSizeX; flSizeX = floatclamp(vecSize[0], 24.0, 48.0); + static Float:flSizeZ; flSizeZ = vecSize[2]; + + static Float:vecForward[3]; + xs_vec_sub(vecEnd, vecStart, vecForward); + xs_vec_normalize(vecForward, vecForward); + + static Float:vecDirUp[3]; xs_vec_set(vecDirUp, 0.0, 0.0, 1.0); + static Float:vecDir[3]; xs_vec_cross(vecForward, vecDirUp, vecDir); + + static Float:vecLeft[3]; + xs_vec_add_scaled(vecOrigin, vecForward, (flDist + flSizeX), vecLeft); + xs_vec_sub_scaled(vecOrigin, vecDir, (flSizeX * 2), vecLeft); + + static Float:vecRight[3]; + xs_vec_add_scaled(vecOrigin, vecForward, (flDist + flSizeX), vecRight); + xs_vec_add_scaled(vecOrigin, vecDir, (flSizeX * 2), vecRight); + + static Float:vecTop[3]; + static Float:vecBottom[3]; + + if (iMoveType == MOVETYPE_FLY) { + xs_vec_add_scaled(vecOrigin, vecForward, flDist, vecTop); + xs_vec_add_scaled(vecOrigin, vecDirUp, (flSizeZ * 3), vecTop); + + xs_vec_add_scaled(vecOrigin, vecForward, flDist, vecBottom); + xs_vec_sub_scaled(vecOrigin, vecDirUp, (flSizeZ * 3), vecBottom); + } + + static Float:vecFarSide[3]; + + static Array:irgRoute; irgRoute = CE_GetMember(this, m_irgRoute); + if (ArraySize(irgRoute)) { + static rgWaypoint[MONSTER_WAYPOINT]; ArrayGetArray(irgRoute, CE_GetMember(this, m_iRouteIndex), rgWaypoint[any:0], _:MONSTER_WAYPOINT); + xs_vec_copy(rgWaypoint[MONSTER_WAYPOINT_LOCATION], vecFarSide); + } else { + xs_vec_copy(vecEnd, vecFarSide); + } + + for (new i = 0; i < 8; i++) { + static Float:flDistance; + + if (@Monster_CheckLocalMove(this, vecOrigin, vecRight, pTargetEnt, true, flDistance) == MONSTER_LOCALMOVE_VALID) { + if (@Monster_CheckLocalMove(this, vecRight, vecFarSide, pTargetEnt, true, flDistance) == MONSTER_LOCALMOVE_VALID) { + xs_vec_copy(vecRight, vecApex); + return true; + } + } + + if (@Monster_CheckLocalMove(this, vecOrigin, vecLeft, pTargetEnt, true, flDistance) == MONSTER_LOCALMOVE_VALID) { + if (@Monster_CheckLocalMove(this, vecLeft, vecFarSide, pTargetEnt, true, flDistance) == MONSTER_LOCALMOVE_VALID) { + xs_vec_copy(vecLeft, vecApex); + return true; + } + } + + if (iMoveType == MOVETYPE_FLY) { + if (@Monster_CheckLocalMove(this, vecOrigin, vecTop, pTargetEnt, true, flDistance) == MONSTER_LOCALMOVE_VALID) { + if (@Monster_CheckLocalMove(this, vecTop, vecFarSide, pTargetEnt, true, flDistance) == MONSTER_LOCALMOVE_VALID) { + xs_vec_copy(vecTop, vecApex); + return true; + } + } + + if (@Monster_CheckLocalMove(this, vecOrigin, vecBottom, pTargetEnt, true, flDistance) == MONSTER_LOCALMOVE_VALID) { + if (@Monster_CheckLocalMove(this, vecBottom, vecFarSide, pTargetEnt, true, flDistance) == MONSTER_LOCALMOVE_VALID) { + xs_vec_copy(vecBottom, vecApex); + return true; + } + } + } + + xs_vec_add_scaled(vecRight, vecDir, flSizeX * 2, vecRight); + xs_vec_sub_scaled(vecLeft, vecDir, flSizeX * 2, vecLeft); + + if (iMoveType == MOVETYPE_FLY) { + xs_vec_add_scaled(vecTop, vecDirUp, flSizeZ * 2, vecTop); + xs_vec_sub_scaled(vecBottom, vecDirUp, flSizeZ * 2, vecBottom); + } + } + + return false; +} + +bool:@Monster_BuildRoute(this, const Float:vecGoal[3], iMoveFlag, pTarget) { + @Monster_RouteNew(this); + + static iMovementGoal; iMovementGoal = @Monster_RouteClassify(this, iMoveFlag); + CE_SetMember(this, m_iMovementGoal, iMovementGoal); + + if (@Monster_BuildSimpleRoute(this, vecGoal, iMoveFlag, pTarget)) { + return true; + } + + if (@Monster_GetNodeRoute(this, vecGoal)) { + CE_SetMemberVec(this, m_vecMoveGoal, vecGoal); + @Monster_RouteSimplify(this, pTarget); + return true; + } + + return false; +} + +@Monster_BuildSimpleRoute(this, const Float:vecGoal[3], iMoveFlag, pTarget) { + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + + static Float:flDistance; flDistance = 0.0; + static MONSTER_LOCALMOVE:iLocalMove; iLocalMove = @Monster_CheckLocalMove(this, vecOrigin, vecGoal, pTarget, true, flDistance); + + if (iLocalMove == MONSTER_LOCALMOVE_VALID) { + @Monster_PushWaypoint(this, vecGoal, iMoveFlag | MF_IS_GOAL); + return true; + } + + if (iLocalMove != MONSTER_LOCALMOVE_INVALID_DONT_TRIANGULATE) { + static Float:vecApex[3]; + if (@Monster_Triangulate(this, vecOrigin, vecGoal, flDistance, pTarget, vecApex)) { + @Monster_PushWaypoint(this, vecGoal, iMoveFlag | MF_IS_GOAL); + @Monster_InsertWaypoint(this, vecApex, iMoveFlag | MF_TO_DETOUR); + @Monster_RouteSimplify(this, pTarget); + + return true; + } + } + + return false; +} + +bool:@Monster_BuildNearestRoute(this, const Float:vecThreat[3], const Float:vecViewOfs[3], Float:flMinDist, Float:flMaxDist) { + #if defined _api_navsystem_included + static Float:vecLookersOffset[3]; xs_vec_add(vecThreat, vecViewOfs, vecLookersOffset); + + static NavArea:pArea; pArea = Nav_GetAreaFromGrid(vecLookersOffset); + static NavArea:pNearestArea; pNearestArea = Nav_GetNearestArea(vecLookersOffset, false, this, pArea); + + if (pNearestArea == Invalid_NavArea) return false; + + static Float:vecGoal[3]; + Nav_Area_GetCenter(pNearestArea, vecGoal); + xs_vec_add(vecGoal, vecViewOfs, vecGoal); + + return @Monster_BuildRoute(this, vecGoal, MF_TO_LOCATION, FM_NULLENT); + #else + return false; + #endif +} + +@Monster_RouteClear(this) { + @Monster_RouteNew(this); + CE_SetMember(this, m_iMovementGoal, MOVEGOAL_NONE); + CE_SetMember(this, m_iMovementActivity, ACT_IDLE); + @Monster_Forget(this, MEMORY_MOVE_FAILED); +} + +@Monster_IsRouteClear(this) { + if (CE_GetMember(this, m_iMovementGoal) == MOVEGOAL_NONE) return true; + if (@Monster_HasConditions(this, COND_WAIT_FOR_PATH)) return false; + + static Array:irgRoute; irgRoute = CE_GetMember(this, m_irgRoute); + if (!ArraySize(irgRoute)) return true; + + return false; +} + +bool:@Monster_RefreshRoute(this) { + @Monster_RouteNew(this); + + new iMovementGoal = CE_GetMember(this, m_iMovementGoal); + + switch (iMovementGoal) { + case MOVEGOAL_PATHCORNER: { + static Array:irgRoute; irgRoute = CE_GetMember(this, m_irgRoute); + + new pPathCorner = CE_GetMember(this, m_pGoalEnt); + + while (pPathCorner) { + static Float:vecTarget[3]; pev(pPathCorner, pev_origin, vecTarget); + + static rgWaypoint[MONSTER_WAYPOINT]; + rgWaypoint[MONSTER_WAYPOINT_TYPE] = MF_TO_PATHCORNER; + xs_vec_copy(vecTarget, rgWaypoint[MONSTER_WAYPOINT_LOCATION]); + + pPathCorner = ExecuteHamB(Ham_GetNextTarget, pPathCorner); + + if (!pPathCorner) { + rgWaypoint[MONSTER_WAYPOINT_TYPE] |= MF_IS_GOAL; + } + + ArrayPushArray(irgRoute, rgWaypoint[any:0]); + } + + return true; + } + case MOVEGOAL_ENEMY: { + new pEnemy = CE_GetMember(this, m_pEnemy); + new Float:vecEnemyLKP[3]; CE_GetMemberVec(this, m_vecEnemyLKP, vecEnemyLKP); + return @Monster_BuildRoute(this, vecEnemyLKP, MF_TO_ENEMY, pEnemy); + } + case MOVEGOAL_LOCATION: { + new Float:vecMoveGoal[3]; CE_GetMemberVec(this, m_vecMoveGoal, vecMoveGoal); + return @Monster_BuildRoute(this, vecMoveGoal, MF_TO_LOCATION, FM_NULLENT); + } + case MOVEGOAL_TARGETENT: { + new pTarget = CE_GetMember(this, m_pTargetEnt); + new Float:vecTarget[3]; pev(pTarget, pev_origin, vecTarget); + + if (pTarget != FM_NULLENT) { + return @Monster_BuildRoute(this, vecTarget, MF_TO_TARGETENT, pTarget); + } + } + case MOVEGOAL_NODE: { + new Float:vecMoveGoal[3]; CE_GetMemberVec(this, m_vecMoveGoal, vecMoveGoal); + return @Monster_GetNodeRoute(this, vecMoveGoal); + } + } + + return false; +} + +Float:@Monster_CoverRadius(this) { + return 784.0; +} + +bool:@Monster_FindCover(this, const Float:vecThreat[], const Float:vecViewOfs[], const Float:flMinDistance, Float:flData) { return false; } +bool:@Monster_FindLateralCover(this, const Float:vecThreat[3], const Float:vecViewOfs[3]) { return false; } + +@Monster_StopAnimation(this) { + set_pev(this, pev_framerate, 0.0); +} + +bool:@Monster_ShouldFadeOnDeath(this) { + if (pev(this, pev_spawnflags) & SF_MONSTER_FADECORPSE) return true; + if (pev(this, pev_owner)) return true; + + return false; +} + +bool:@Monster_BBoxFlat(this) { return false; } + +@Monster_Eat(this, Float:flDuration) { + CE_SetMember(this, m_flHungryTime, g_flGameTime + flDuration); +} + +Activity:@Monster_LookupActivity(this, Activity:iActivity) { + static Array:irgSequences; irgSequences = CE_GetMember(this, m_irgSequences); + if (irgSequences == Invalid_Array) return ACTIVITY_NOT_AVAILABLE; + + static Activity:iActivitySeq; iActivitySeq = ACTIVITY_NOT_AVAILABLE; + static iSequencesNum; iSequencesNum = ArraySize(irgSequences); + + static iTotalWeight; iTotalWeight = 0; + + for (new iSequence = 0; iSequence < iSequencesNum; ++iSequence) { + static Activity:iSeqActivity; iSeqActivity = ArrayGetCell(irgSequences, iSequence, _:Sequence_Activity); + + if (iActivity != iSeqActivity) continue; + + static iActivityWeight; iActivityWeight = ArrayGetCell(irgSequences, iSequence, _:Sequence_ActivityWeight); + + if (!iTotalWeight || random(iTotalWeight - 1) < iActivityWeight) { + iActivitySeq = Activity:iSequence; + } + + iTotalWeight += iActivityWeight; + } + + return iActivitySeq; +} + +Activity:@Monster_LookupActivityHeaviest(this, Activity:iActivity) { + static Array:irgSequences; irgSequences = CE_GetMember(this, m_irgSequences); + if (irgSequences == Invalid_Array) return ACTIVITY_NOT_AVAILABLE; + + static iWeight; iWeight = 0; + + new Activity:iActivitySeq = ACTIVITY_NOT_AVAILABLE; + + new iSequencesNum = ArraySize(irgSequences); + for (new iSequence = 0; iSequence <= iSequencesNum; ++iSequence) { + static Activity:iSeqActivity; iSeqActivity = ArrayGetCell(irgSequences, iSequence, _:Sequence_Activity); + if (iActivity != iSeqActivity) continue; + + static iActivityWeight; iActivityWeight = ArrayGetCell(irgSequences, iSequence, _:Sequence_ActivityWeight); + + if (iActivityWeight > iWeight) { + iWeight = iActivityWeight; + iActivitySeq = Activity:iSequence; + } + } + + return iActivitySeq; +} + +Activity:@Monster_GetStoppedActivity(this) { + return ACT_IDLE; +} + +Activity:@Monster_GetSmallFlinchActivity(this) { + return ACTIVITY_NOT_AVAILABLE; +} + +Activity:@Monster_GetDeathActivity(this) { + if (pev(this, pev_deadflag) != DEAD_NO) { + return CE_GetMember(this, m_iIdealActivity); + } + + new bool:fTriedDirection = false; + new Activity:iDeathActivity = ACT_DIESIMPLE; + + static Float:vecAngles[3]; pev(this, pev_angles, vecAngles); + + static Float:vecForward[3]; angle_vector(vecAngles, ANGLEVECTOR_FORWARD, vecForward); + static Float:vecNegAttackDir[3]; xs_vec_neg(g_vecAttackDir, vecNegAttackDir); + + static Float:flDot; flDot = xs_vec_dot(vecForward, vecNegAttackDir); + + switch (CE_GetMember(this, m_iLastHitGroup)) { + case HITGROUP_HEAD: { + iDeathActivity = ACT_DIE_HEADSHOT; + } + case HITGROUP_STOMACH: { + iDeathActivity = ACT_DIE_GUTSHOT; + } + default: { + fTriedDirection = true; + + if (flDot > 0.3) { + iDeathActivity = ACT_DIEFORWARD; + } else if (flDot <= -0.3) { + iDeathActivity = ACT_DIEBACKWARD; + } + } + } + + if (@Monster_LookupActivity(this, iDeathActivity) == ACTIVITY_NOT_AVAILABLE) { + if (fTriedDirection) { + iDeathActivity = ACT_DIESIMPLE; + } else { + if (flDot > 0.3) { + iDeathActivity = ACT_DIEFORWARD; + } else if (flDot <= -0.3) { + iDeathActivity = ACT_DIEBACKWARD; + } + } + } + + if (@Monster_LookupActivity(this, iDeathActivity) == ACTIVITY_NOT_AVAILABLE) { + iDeathActivity = ACT_DIESIMPLE; + } + + static Float:vecSrc[3]; ExecuteHamB(Ham_Center, this, vecSrc); + + if (iDeathActivity == ACT_DIEFORWARD || iDeathActivity == ACT_DIEBACKWARD) { + static iDir; iDir = (iDeathActivity == ACT_DIEFORWARD ? 1 : -1); + + static Float:vecEnd[3]; xs_vec_add_scaled(vecSrc, vecForward, 64.0 * iDir, vecEnd); + + engfunc(EngFunc_TraceHull, vecSrc, vecEnd, DONT_IGNORE_MONSTERS, HULL_HEAD, this, g_pTrace); + + static Float:flFraction; get_tr2(g_pTrace, TR_flFraction, flFraction); + + if (flFraction != 1.0){ + iDeathActivity = ACT_DIESIMPLE; + } + } + + return iDeathActivity; +} + +@Monster_TakeDamage(this, pInflictor, pAttacker, Float:flDamage, iDamageBits) { + static Float:flTakeDamage; pev(this, pev_takedamage, flTakeDamage); + + if (flTakeDamage == DAMAGE_NO) return 0; + + if (!ExecuteHamB(Ham_IsAlive, this)) { + return @Monster_DeadTakeDamage(this, pInflictor, pAttacker, flDamage, iDamageBits); + } + + if (pev(this, pev_deadflag) == DEAD_NO) { + CE_CallMethod(this, PainSound); + } + + static Float:vecDir[3]; xs_vec_set(vecDir, 0.0, 0.0, 0.0); + if (pInflictor > 0) { + static Float:vecCenter[3]; ExecuteHamB(Ham_Center, this, vecCenter); + static Float:vecInflictorCenter[3]; ExecuteHamB(Ham_Center, pInflictor, vecInflictorCenter); + + xs_vec_sub(vecInflictorCenter, vecCenter, vecDir); + xs_vec_sub(vecDir, Float:{0.0, 0.0, 16.0}, vecDir); + xs_vec_normalize(vecDir, vecDir); + + xs_vec_copy(vecDir, g_vecAttackDir); + } + + static Float:flHealth; pev(this, pev_health, flHealth); + + CE_SetMember(this, m_iDamageType, CE_GetMember(this, m_iDamageType) | iDamageBits); + + if (pInflictor && pev(this, pev_movetype) == MOVETYPE_WALK && (!pAttacker || pev(pAttacker, pev_solid) != SOLID_TRIGGER)) { + static Float:vecVelocity[3]; pev(this, pev_velocity, vecVelocity); + xs_vec_add_scaled(vecVelocity, vecDir, -@Monster_DamageForce(this, flDamage), vecVelocity); + } + + pev(this, pev_health, flHealth - flDamage); + + if (CE_GetMember(this, m_iMonsterState) == MONSTER_STATE_SCRIPT) { + @Monster_SetConditions(this, COND_LIGHT_DAMAGE); + return 0; + } + + if (flHealth <= 0) { + if (iDamageBits & DMG_ALWAYSGIB) { + ExecuteHamB(Ham_Killed, this, pAttacker, GIB_ALWAYS); + } else if (iDamageBits & DMG_NEVERGIB) { + ExecuteHamB(Ham_Killed, this, pAttacker, GIB_NEVER); + } else { + ExecuteHamB(Ham_Killed, this, pAttacker, GIB_NORMAL); + } + + return 0; + } + + if ((pev(this, pev_flags) & FL_MONSTER) && pAttacker && (pev(pAttacker, pev_flags) & (FL_MONSTER | FL_CLIENT))) { + static Float:vecEnemyLKP[3]; CE_GetMemberVec(this, m_vecEnemyLKP, vecEnemyLKP); + + static pEnemy; pEnemy = CE_GetMember(this, m_pEnemy); + + if (pInflictor) { + if (!pEnemy || pInflictor == pEnemy || !@Monster_HasConditions(this, COND_SEE_ENEMY)) { + pev(pInflictor, pev_origin, vecEnemyLKP); + CE_SetMemberVec(this, m_vecEnemyLKP, vecEnemyLKP); + } + } else { + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + xs_vec_add_scaled(vecOrigin, g_vecAttackDir, 64.0, vecEnemyLKP); + CE_SetMemberVec(this, m_vecEnemyLKP, vecEnemyLKP); + } + + @Monster_MakeIdealYaw(this, vecEnemyLKP); + + if (flDamage > 0.0) @Monster_SetConditions(this, COND_LIGHT_DAMAGE); + if (flDamage >= 20.0) @Monster_SetConditions(this, COND_HEAVY_DAMAGE); + } + + return 1; +} + +Float:@Monster_DamageForce(this, Float:flDamage) { + static Float:vecSize[3]; pev(this, pev_size, vecSize); + + static Float:flForce; flForce = flDamage * ((32.0 * 32.0 * 72.0) / (vecSize[0] * vecSize[1] * vecSize[2])) * 5; + + return floatmin(flForce, 1000.0); +} + + +@Monster_DeadTakeDamage(this, pInflictor, pAttacker, Float:flDamage, iDamageBits) { + static Float:vecDir[3]; xs_vec_set(vecDir, 0.0, 0.0, 0.0); + + if (pInflictor > 0) { + static Float:vecCenter[3]; ExecuteHamB(Ham_Center, this, vecCenter); + static Float:vecInflictorCenter[3]; ExecuteHamB(Ham_Center, pInflictor, vecInflictorCenter); + + xs_vec_sub(vecInflictorCenter, vecCenter, g_vecAttackDir); + xs_vec_sub(g_vecAttackDir, Float:{0.0, 0.0, 10.0}, g_vecAttackDir); + xs_vec_normalize(g_vecAttackDir, g_vecAttackDir); + } + + if (iDamageBits & DMG_GIB_CORPSE) { + static Float:flHealth; pev(this, pev_health, flHealth); + + if (flHealth <= flDamage) { + flHealth = -50.0; + ExecuteHamB(Ham_Killed, this, pAttacker, GIB_ALWAYS); + return 0; + } + + flHealth -= flDamage * 0.1; + + set_pev(this, pev_health, flHealth); + } + + return 1; +} + +@Monster_StartTask(this, iTask, any:data) { + switch (iTask) { + case TASK_TURN_RIGHT: { + new Float:vecAngles[3]; pev(this, pev_angles, vecAngles); + new Float:flCurrentYaw; flCurrentYaw = UTIL_AngleMod(vecAngles[1]); + set_pev(this, pev_ideal_yaw, UTIL_AngleMod(flCurrentYaw - Float:data)); + @Monster_SetTurnActivity(this); + } + case TASK_TURN_LEFT: { + new Float:vecAngles[3]; pev(this, pev_angles, vecAngles); + new Float:flCurrentYaw; flCurrentYaw = UTIL_AngleMod(vecAngles[1]); + set_pev(this, pev_ideal_yaw, UTIL_AngleMod(flCurrentYaw + Float:data)); + @Monster_SetTurnActivity(this); + } + case TASK_REMEMBER: { + @Monster_Remember(this, data); + @Monster_TaskComplete(this); + } + case TASK_FORGET: { + @Monster_Forget(this, data); + @Monster_TaskComplete(this); + } + case TASK_FIND_HINTNODE: { + new iHintNode = @Monster_FindHintNode(this); + + CE_SetMember(this, m_iHintNode, iHintNode); + + if (iHintNode != NO_NODE) { + @Monster_TaskComplete(this); + } else { + @Monster_TaskFail(this); + } + } + case TASK_STORE_LASTPOSITION: { + new Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + CE_SetMemberVec(this, m_vecLastPosition, vecOrigin); + @Monster_TaskComplete(this); + } + case TASK_CLEAR_LASTPOSITION: { + CE_SetMemberVec(this, m_vecLastPosition, Float:{0.0, 0.0, 0.0}); + @Monster_TaskComplete(this); + } + case TASK_CLEAR_HINTNODE: { + CE_SetMember(this, m_iHintNode, NO_NODE); + @Monster_TaskComplete(this); + } + case TASK_STOP_MOVING: { + if (CE_GetMember(this, m_iIdealActivity) == CE_GetMember(this, m_iMovementActivity)) { + CE_SetMember(this, m_iIdealActivity, @Monster_GetStoppedActivity(this)); + } + + @Monster_RouteClear(this); + @Monster_TaskComplete(this); + } + case TASK_PLAY_SEQUENCE_FACE_ENEMY, TASK_PLAY_SEQUENCE_FACE_TARGET, TASK_PLAY_SEQUENCE: { + CE_SetMember(this, m_iIdealActivity, data); + } + case TASK_PLAY_ACTIVE_IDLE: { + // TODO: Implement + // new iHintNode = CE_GetMember(this, m_iHintNode); + // new iActivity = g_pWorldGraphNodes[iHintNode][Nodes_HintActivity]; + // CE_SetMember(this, m_iIdealActivity, iActivity); + } + case TASK_SET_SCHEDULE: { + new Struct:sNewSchedule = CE_CallMethod(this, GetScheduleOfType, MONSTER_SCHEDULE_TYPE:data); + + if (sNewSchedule != Invalid_Struct) { + CE_CallMethod(this, ChangeSchedule, sNewSchedule); + } else { + @Monster_TaskFail(this); + } + } + case TASK_FIND_NEAR_NODE_COVER_FROM_ENEMY: { + new pEnemy = CE_GetMember(this, m_pEnemy); + if (pEnemy == FM_NULLENT) { + @Monster_TaskFail(this); + return; + } + + new Float:vecEnemyOrigin[3]; pev(pEnemy, pev_origin, vecEnemyOrigin); + new Float:vecEnemyViewOfs[3]; pev(pEnemy, pev_view_ofs, vecEnemyViewOfs); + + if (@Monster_FindCover(this, vecEnemyOrigin, vecEnemyViewOfs, 0.0, Float:data)) { + @Monster_TaskComplete(this); + } else { + @Monster_TaskFail(this); + } + } + case TASK_FIND_FAR_NODE_COVER_FROM_ENEMY: { + new pEnemy = CE_GetMember(this, m_pEnemy); + if (pEnemy == FM_NULLENT) { + @Monster_TaskFail(this); + return; + } + + new Float:vecEnemyOrigin[3]; pev(pEnemy, pev_origin, vecEnemyOrigin); + new Float:vecEnemyViewOfs[3]; pev(pEnemy, pev_view_ofs, vecEnemyViewOfs); + + if (@Monster_FindCover(this, vecEnemyOrigin, vecEnemyViewOfs, Float:data, @Monster_CoverRadius(this))) { + @Monster_TaskComplete(this); + } else { + @Monster_TaskFail(this); + } + } + case TASK_FIND_NODE_COVER_FROM_ENEMY: { + new pEnemy = CE_GetMember(this, m_pEnemy); + if (pEnemy == FM_NULLENT) { + @Monster_TaskFail(this); + return; + } + + new Float:vecEnemyOrigin[3]; pev(pEnemy, pev_origin, vecEnemyOrigin); + new Float:vecEnemyViewOfs[3]; pev(pEnemy, pev_view_ofs, vecEnemyViewOfs); + + if (@Monster_FindCover(this, vecEnemyOrigin, vecEnemyViewOfs, 0.0, @Monster_CoverRadius(this))) { + @Monster_TaskComplete(this); + } else { + @Monster_TaskFail(this); + } + } + case TASK_FIND_COVER_FROM_ENEMY: { + new pEnemy = CE_GetMember(this, m_pEnemy); + new pCover; pCover = pEnemy == FM_NULLENT ? this : pEnemy; + new Float:vecCoverOrigin[3]; pev(pCover, pev_origin, vecCoverOrigin); + new Float:vecCoverViewOfs[3]; pev(pCover, pev_view_ofs, vecCoverViewOfs); + + if (@Monster_FindLateralCover(this, vecCoverOrigin, vecCoverViewOfs)) { + CE_SetMember(this, m_flMoveWaitFinished, g_flGameTime + Float:data); + @Monster_TaskComplete(this); + } else if (@Monster_FindCover(this, vecCoverOrigin, vecCoverViewOfs, 0.0, @Monster_CoverRadius(this))) { + CE_SetMember(this, m_flMoveWaitFinished, g_flGameTime + Float:data); + @Monster_TaskComplete(this); + } else { + @Monster_TaskFail(this); + } + } + case TASK_FIND_COVER_FROM_ORIGIN: { + new Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + new Float:vecViewOfs[3]; pev(this, pev_view_ofs, vecViewOfs); + + if (@Monster_FindCover(this, vecOrigin, vecViewOfs, 0.0, @Monster_CoverRadius(this))) { + CE_SetMember(this, m_flMoveWaitFinished, g_flGameTime + Float:data); + @Monster_TaskComplete(this); + } else { + @Monster_TaskFail(this); + } + } + case TASK_FIND_COVER_FROM_BEST_SOUND: { + new iBestSound = @Monster_BestSound(this); + + if (iBestSound != -1 && @Monster_FindCover(this, g_rgSounds[iBestSound][Sound_Origin], Float:{0.0, 0.0, 0.0}, float(g_rgSounds[iBestSound][Sound_Volume]), @Monster_CoverRadius(this))) { + CE_SetMember(this, m_flMoveWaitFinished, g_flGameTime + Float:data); + @Monster_TaskComplete(this); + } else { + @Monster_TaskFail(this); + } + } + case TASK_FACE_HINTNODE: { + // TODO: Implement + // new iHintNode = CE_GetMember(this, m_iHintNode); + // new Float:flHintYaw = g_pWorldGraphNodes[iHintNode][Nodes_HintYaw]; + + // set_pev(this, pev_ideal_yaw, flHintYaw); + // @Monster_SetTurnActivity(this); + } + case TASK_FACE_LASTPOSITION: { + static Float:vecLastPosition[3]; CE_GetMemberVec(this, m_vecLastPosition, vecLastPosition); + @Monster_MakeIdealYaw(this, vecLastPosition); + @Monster_SetTurnActivity(this); + } + case TASK_FACE_TARGET: { + new pTargetEnt = CE_GetMember(this, m_pTargetEnt); + + if (pTargetEnt != FM_NULLENT) { + new Float:vecTarget[3]; pev(pTargetEnt, pev_origin, vecTarget); + @Monster_MakeIdealYaw(this, vecTarget); + @Monster_SetTurnActivity(this); + } else { + @Monster_TaskFail(this); + } + } + case TASK_FACE_ENEMY: { + new Float:vecEnemyLKP[3]; CE_GetMemberVec(this, m_vecEnemyLKP, vecEnemyLKP); + + @Monster_MakeIdealYaw(this, vecEnemyLKP); + @Monster_SetTurnActivity(this); + } + case TASK_FACE_IDEAL: { + @Monster_SetTurnActivity(this); + } + case TASK_FACE_ROUTE: { + if (@Monster_IsRouteClear(this)) { + @Monster_TaskFail(this); + } else { + static Array:irgRoute; irgRoute = CE_GetMember(this, m_irgRoute); + static rgWaypoint[MONSTER_WAYPOINT]; ArrayGetArray(irgRoute, CE_GetMember(this, m_iRouteIndex), rgWaypoint[any:0], _:MONSTER_WAYPOINT); + @Monster_MakeIdealYaw(this, rgWaypoint[MONSTER_WAYPOINT_LOCATION]); + @Monster_SetTurnActivity(this); + } + } + case TASK_WAIT, TASK_WAIT_FACE_ENEMY: { + CE_SetMember(this, m_flWaitFinished, g_flGameTime + Float:data); + } + case TASK_WAIT_RANDOM: { + CE_SetMember(this, m_flWaitFinished, g_flGameTime + random_float(0.1, Float:data)); + } + case TASK_MOVE_TO_TARGET_RANGE: { + new pTargetEnt = CE_GetMember(this, m_pTargetEnt); + new Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + new Float:vecTarget[3]; pev(pTargetEnt, pev_origin, vecTarget); + + if (xs_vec_distance(vecOrigin, vecTarget) < 1.0) { + @Monster_TaskComplete(this); + } else { + CE_SetMemberVec(this, m_vecMoveGoal, vecTarget); + + if (!@Monster_MoveToTarget(this, ACT_WALK, 2.0)) { + @Monster_TaskFail(this); + } + } + } + case TASK_RUN_TO_TARGET, TASK_WALK_TO_TARGET: { + new pTargetEnt = CE_GetMember(this, m_pTargetEnt); + + new Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + new Float:vecTarget[3]; pev(pTargetEnt, pev_origin, vecTarget); + + if (xs_vec_distance(vecOrigin, vecTarget) < 1.0) { + @Monster_TaskComplete(this); + } else { + new Activity:iNewActivity = iTask == TASK_WALK_TO_TARGET ? ACT_WALK : ACT_RUN; + + if (@Monster_LookupActivity(this, iNewActivity) == ACTIVITY_NOT_AVAILABLE) { + @Monster_TaskComplete(this); + } else { + if (pTargetEnt == FM_NULLENT || !@Monster_MoveToTarget(this, iNewActivity, 2.0)) { + @Monster_TaskFail(this); + @Monster_RouteClear(this); + } + } + } + + @Monster_TaskComplete(this); + } + case TASK_CLEAR_MOVE_WAIT: { + CE_SetMember(this, m_flMoveWaitFinished, g_flGameTime); + @Monster_TaskComplete(this); + } + case TASK_MELEE_ATTACK1_NOTURN, TASK_MELEE_ATTACK1: { + CE_SetMember(this, m_iIdealActivity, ACT_MELEE_ATTACK1); + } + case TASK_MELEE_ATTACK2_NOTURN, TASK_MELEE_ATTACK2: { + CE_SetMember(this, m_iIdealActivity, ACT_MELEE_ATTACK2); + } + case TASK_RANGE_ATTACK1_NOTURN, TASK_RANGE_ATTACK1: { + CE_SetMember(this, m_iIdealActivity, ACT_RANGE_ATTACK1); + } + case TASK_RANGE_ATTACK2_NOTURN, TASK_RANGE_ATTACK2: { + CE_SetMember(this, m_iIdealActivity, ACT_RANGE_ATTACK2); + } + case TASK_RELOAD_NOTURN, TASK_RELOAD: { + CE_SetMember(this, m_iIdealActivity, ACT_RELOAD); + } + case TASK_SPECIAL_ATTACK1: { + CE_SetMember(this, m_iIdealActivity, ACT_SPECIAL_ATTACK1); + } + case TASK_SPECIAL_ATTACK2: { + CE_SetMember(this, m_iIdealActivity, ACT_SPECIAL_ATTACK2); + } + case TASK_SET_ACTIVITY: { + CE_SetMember(this, m_iIdealActivity, data); + @Monster_TaskComplete(this); + } + case TASK_GET_PATH_TO_ENEMY_LKP: { + new Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + new Float:vecViewOfs[3]; pev(this, pev_view_ofs, vecViewOfs); + new Float:vecEnemyLKP[3]; CE_GetMemberVec(this, m_vecEnemyLKP, vecEnemyLKP); + + if (@Monster_BuildRoute(this, vecEnemyLKP, MF_TO_LOCATION, FM_NULLENT)) { + @Monster_TaskComplete(this); + } else if (@Monster_BuildNearestRoute(this, vecEnemyLKP, vecViewOfs, 0.0, xs_vec_distance(vecOrigin, vecEnemyLKP))) { + @Monster_TaskComplete(this); + } else { + @Monster_TaskFail(this); + } + } + case TASK_GET_PATH_TO_ENEMY: { + new Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + + new pEnemy = CE_GetMember(this, m_pEnemy); + new Float:vecEnemyOrigin[3]; pev(pEnemy, pev_origin, vecEnemyOrigin); + new Float:vecEnemyViewOfs[3]; pev(pEnemy, pev_view_ofs, vecEnemyViewOfs); + + if (pEnemy == FM_NULLENT) { + @Monster_TaskFail(this); + return; + } + + if (@Monster_BuildRoute(this, vecEnemyOrigin, MF_TO_ENEMY, pEnemy)) { + @Monster_TaskComplete(this); + } else if (@Monster_BuildNearestRoute(this, vecEnemyOrigin, vecEnemyViewOfs, 0.0, xs_vec_distance(vecOrigin, vecEnemyOrigin))) { + @Monster_TaskComplete(this); + } else { + @Monster_TaskFail(this); + } + } + case TASK_GET_PATH_TO_ENEMY_CORPSE: { + new Float:vecEnemyLKP[3]; CE_GetMemberVec(this, m_vecEnemyLKP, vecEnemyLKP); + new Float:vecAngles[3]; pev(this, pev_angles, vecAngles); + new Float:vecForward[3]; angle_vector(vecAngles, ANGLEVECTOR_FORWARD, vecForward); + new Float:vecTarget[3]; xs_vec_sub_scaled(vecEnemyLKP, vecForward, 64.0, vecTarget); + + if (@Monster_BuildRoute(this, vecTarget, MF_TO_LOCATION, FM_NULLENT)) { + @Monster_TaskComplete(this); + } else { + @Monster_TaskFail(this); + } + } + case TASK_GET_PATH_TO_SPOT: { + // TODO: Fix for multiplayer (find best player) + new pPlayer = find_player_ex(FindPlayer_ExcludeDead); + new Float:vecMoveGoal[3]; CE_GetMemberVec(this, m_vecLastPosition, vecMoveGoal); + + if (@Monster_BuildRoute(this, vecMoveGoal, MF_TO_LOCATION, pPlayer)) { + @Monster_TaskComplete(this); + } else { + @Monster_TaskFail(this); + } + } + case TASK_GET_PATH_TO_TARGET: { + new Activity:iMovementActivity = CE_GetMember(this, m_iMovementActivity); + new pTargetEnt = CE_GetMember(this, m_pTargetEnt); + + @Monster_RouteClear(this); + + if (pTargetEnt != FM_NULLENT && @Monster_MoveToTarget(this, iMovementActivity, 1.0)) { + @Monster_TaskComplete(this); + } else { + @Monster_TaskFail(this); + } + } + case TASK_GET_PATH_TO_HINTNODE: { + // TODO: Implement + // if (CE_GetMember(this, m_pPathTask) != Invalid_NavBuildPathTask) return; + // new Activity:iMovementActivity = CE_GetMember(this, m_iMovementActivity); + + // if (@Monster_MoveToLocation(this, iMovementActivity, 2, WorldGraph.m_pNodes[ m_iHintNode ].m_vecOrigin)) { + // @Monster_TaskComplete(this); + // } else { + // @Monster_TaskFail(this); + // } + } + case TASK_GET_PATH_TO_LASTPOSITION: { + // if (CE_GetMember(this, m_pPathTask) != Invalid_NavBuildPathTask) return; + + new Activity:iMovementActivity = CE_GetMember(this, m_iMovementActivity); + new Float:vecMoveGoal[3]; CE_GetMemberVec(this, m_vecLastPosition, vecMoveGoal); + + if (@Monster_MoveToLocation(this, iMovementActivity, 2.0, vecMoveGoal)) { + @Monster_TaskComplete(this); + } else { + @Monster_TaskFail(this); + } + } + case TASK_GET_PATH_TO_BESTSOUND, TASK_GET_PATH_TO_BESTSCENT: { + new iSound = -1; + + switch (iTask) { + case TASK_GET_PATH_TO_BESTSOUND: iSound = @Monster_BestSound(this); + case TASK_GET_PATH_TO_BESTSCENT: iSound = @Monster_BestScent(this); + } + + if (iSound != -1) { + new Activity:iMovementActivity = CE_GetMember(this, m_iMovementActivity); + + if (@Monster_MoveToLocation(this, iMovementActivity, 2.0, g_rgSounds[iSound][Sound_Origin])) { + @Monster_TaskComplete(this); + } else { + @Monster_TaskFail(this); + } + } else { + @Monster_TaskFail(this); + } + } + case TASK_RUN_PATH: { + if (@Monster_LookupActivity(this, ACT_RUN) != ACTIVITY_NOT_AVAILABLE) { + CE_SetMember(this, m_iMovementActivity, ACT_RUN); + } else { + CE_SetMember(this, m_iMovementActivity, ACT_WALK); + } + + @Monster_TaskComplete(this); + } + case TASK_WALK_PATH: { + if (pev(this, pev_movetype) == MOVETYPE_FLY) { + CE_SetMember(this, m_iMovementActivity, ACT_FLY); + } + + if (@Monster_LookupActivity(this, ACT_WALK) != ACTIVITY_NOT_AVAILABLE) { + CE_SetMember(this, m_iMovementActivity, ACT_WALK); + } else { + CE_SetMember(this, m_iMovementActivity, ACT_RUN); + } + + @Monster_TaskComplete(this); + } + case TASK_STRAFE_PATH: { + static Array:irgRoute; irgRoute = CE_GetMember(this, m_irgRoute); + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + static Float:vecAngles[3]; pev(this, pev_angles, vecAngles); + static Float:vecRight[3]; angle_vector(vecAngles, ANGLEVECTOR_RIGHT, vecRight); + + static rgWaypoint[MONSTER_WAYPOINT]; ArrayGetArray(irgRoute, CE_GetMember(this, m_iRouteIndex), rgWaypoint[any:0], _:MONSTER_WAYPOINT); + @Monster_MakeIdealYaw(this, rgWaypoint[MONSTER_WAYPOINT_LOCATION]); + + new Float:vecDirection[3]; + xs_vec_set(vecDirection, rgWaypoint[MONSTER_WAYPOINT_LOCATION][0] - vecOrigin[0], rgWaypoint[MONSTER_WAYPOINT_LOCATION][1] - vecOrigin[1], 0.0); + xs_vec_normalize(vecDirection, vecDirection); + + if (xs_vec_dot(vecDirection, vecRight) > 0.0) { + CE_SetMember(this, m_iMovementActivity, ACT_STRAFE_RIGHT); + } else { + CE_SetMember(this, m_iMovementActivity, ACT_STRAFE_LEFT); + } + + @Monster_TaskComplete(this); + } + case TASK_WAIT_FOR_MOVEMENT: { + if (@Monster_IsRouteClear(this)) { + @Monster_TaskComplete(this); + } + } + case TASK_EAT: { + @Monster_Eat(this, Float:data); + @Monster_TaskComplete(this); + } + case TASK_SMALL_FLINCH: { + CE_SetMember(this, m_iIdealActivity, @Monster_GetSmallFlinchActivity(this)); + } + case TASK_DIE: { + @Monster_RouteClear(this); + CE_SetMember(this, m_iIdealActivity, @Monster_GetDeathActivity(this)); + set_pev(this, pev_deadflag, DEAD_DYING); + } + case TASK_SOUND_WAKE: { + CE_CallMethod(this, AlertSound); + @Monster_TaskComplete(this); + } + case TASK_SOUND_DIE: { + CE_CallMethod(this, DeathSound); + @Monster_TaskComplete(this); + } + case TASK_SOUND_IDLE: { + CE_CallMethod(this, IdleSound); + @Monster_TaskComplete(this); + } + case TASK_SOUND_PAIN: { + CE_CallMethod(this, PainSound); + @Monster_TaskComplete(this); + } + case TASK_SOUND_DEATH: { + CE_CallMethod(this, DeathSound); + @Monster_TaskComplete(this); + } + case TASK_SOUND_ANGRY: { + @Monster_TaskComplete(this); + } + case TASK_WAIT_FOR_SCRIPT: { + static pCine; pCine = CE_GetMember(this, m_pCine); + if (pCine != FM_NULLENT) { + static iszIdle; iszIdle = CE_GetMember(pCine, "iszIdle"); + static iszPlay; iszPlay = CE_GetMember(pCine, "iszPlay"); + + if (iszIdle) { + static szIdle[32]; engfunc(EngFunc_SzFromIndex, iszIdle, szIdle, charsmax(szIdle)); + static szPlay[32]; engfunc(EngFunc_SzFromIndex, iszPlay, szPlay, charsmax(szPlay)); + + CE_CallMethod(pCine, StartSequence, iszIdle, false); + + if (equal(szIdle, szPlay)) { + set_pev(this, pev_framerate, 0.0); + } + } else { + CE_SetMember(this, m_iIdealActivity, ACT_IDLE); + } + } + } + case TASK_PLAY_SCRIPT: { + set_pev(this, pev_movetype, MOVETYPE_FLY); + set_pev(this, pev_flags, pev(this, pev_flags) & ~FL_ONGROUND); + CE_SetMember(this, m_iScriptState, SCRIPT_PLAYING); + } + case TASK_ENABLE_SCRIPT: { + new pCine = CE_GetMember(this, m_pCine); + CE_CallMethod(pCine, "DelayStart", 0); + @Monster_TaskComplete(this); + } + case TASK_PLANT_ON_SCRIPT: { + new pTargetEnt = CE_GetMember(this, m_pTargetEnt); + + if (pTargetEnt != FM_NULLENT) { + new Float:vecTarget[3]; pev(pTargetEnt, pev_origin, vecTarget); + + set_pev(this, pev_origin, vecTarget); + } + + @Monster_TaskComplete(this); + } + case TASK_FACE_SCRIPT: { + new pTargetEnt = CE_GetMember(this, m_pTargetEnt); + + if (pTargetEnt != FM_NULLENT) { + new Float:vecAngles[3]; pev(pTargetEnt, pev_angles, vecAngles); + set_pev(this, pev_ideal_yaw, UTIL_AngleMod(vecAngles[1])); + } + + @Monster_TaskComplete(this); + CE_SetMember(this, m_iIdealActivity, ACT_IDLE); + @Monster_RouteClear(this); + } + case TASK_SUGGEST_STATE: { + CE_SetMember(this, m_iIdealMonsterState, data); + @Monster_TaskComplete(this); + } + case TASK_SET_FAIL_SCHEDULE: { + CE_SetMember(this, m_iFailSchedule, data); + @Monster_TaskComplete(this); + } + case TASK_CLEAR_FAIL_SCHEDULE: { + CE_SetMember(this, m_iFailSchedule, MONSTER_SCHED_NONE); + @Monster_TaskComplete(this); + } + } +} + +@Monster_EmitSound(this, iType, Float:vecOrigin[3], iVolume, Float:flDuration) { + new iSound = @Sound_FindFree(); + + g_rgSounds[iSound][Sound_Emitter] = this; + g_rgSounds[iSound][Sound_Type] = iType; + g_rgSounds[iSound][Sound_Volume] = iVolume; + g_rgSounds[iSound][Sound_ExpiredTime] = g_flGameTime + flDuration; + pev(this, pev_origin, g_rgSounds[iSound][Sound_Origin]); +} + +@Sound_FindFree() { + static iBestSoundToForget; iBestSoundToForget = -1; + + for (new iSound = 0; iSound < sizeof(g_rgSounds); ++iSound) { + if (g_rgSounds[iSound][Sound_ExpiredTime] <= g_flGameTime) { + return iSound; + } + + if (iBestSoundToForget == -1 || g_rgSounds[iSound][Sound_ExpiredTime] < g_rgSounds[iBestSoundToForget][Sound_ExpiredTime]) { + iBestSoundToForget = iSound; + } + } + + return iBestSoundToForget; +} + +bool:@Sound_IsSound(this) { + return !!(g_rgSounds[this][Sound_Type] & (SOUND_COMBAT | SOUND_WORLD | SOUND_PLAYER | SOUND_DANGER)); +} + +bool:@Sound_IsScent(this) { + return !!(g_rgSounds[this][Sound_Type] & (SOUND_CARCASS | SOUND_MEAT | SOUND_GARBAGE)); +} + +@Monster_RunTask(this, iTask, any:data) { + static Float:flYawSpeed; pev(this, pev_yaw_speed, flYawSpeed); + static bool:bSequenceFinished; bSequenceFinished = CE_GetMember(this, m_bSequenceFinished); + + switch (iTask) { + case TASK_TURN_RIGHT, TASK_TURN_LEFT: { + @Monster_ChangeYaw(this, flYawSpeed); + + if (@Monster_FacingIdeal(this)) { + @Monster_TaskComplete(this); + } + } + case TASK_PLAY_SEQUENCE_FACE_ENEMY, TASK_PLAY_SEQUENCE_FACE_TARGET: { + new pEnemy = CE_GetMember(this, m_pEnemy); + new pTargetEnt = CE_GetMember(this, m_pTargetEnt); + new pTarget = iTask == TASK_PLAY_SEQUENCE_FACE_TARGET ? pTargetEnt : pEnemy; + + if (pTarget) { + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + static Float:vecTarget[3]; pev(pTarget, pev_origin, vecTarget); + + static Float:vecDirection[3]; + xs_vec_sub(vecTarget, vecOrigin, vecDirection); + xs_vec_normalize(vecDirection, vecDirection); + + static Float:vecAngles[3]; vector_to_angle(vecDirection, vecAngles); + + set_pev(this, pev_ideal_yaw, vecAngles[1]); + + @Monster_ChangeYaw(this, flYawSpeed); + } + + if (bSequenceFinished) { + @Monster_TaskComplete(this); + } + } + case TASK_PLAY_SEQUENCE, TASK_PLAY_ACTIVE_IDLE: { + if (bSequenceFinished) { + @Monster_TaskComplete(this); + } + } + case TASK_FACE_ENEMY: { + new Float:vecEnemyLKP[3]; CE_GetMemberVec(this, m_vecEnemyLKP, vecEnemyLKP); + @Monster_MakeIdealYaw(this, vecEnemyLKP); + + @Monster_ChangeYaw(this, flYawSpeed); + + if (@Monster_FacingIdeal(this)) { + @Monster_TaskComplete(this); + } + } + case TASK_FACE_HINTNODE, TASK_FACE_LASTPOSITION, TASK_FACE_TARGET, TASK_FACE_IDEAL, TASK_FACE_ROUTE: { + @Monster_ChangeYaw(this, flYawSpeed); + + if (@Monster_FacingIdeal(this)) { + @Monster_TaskComplete(this); + } + } + case TASK_WAIT_PVS: { + if (engfunc(EngFunc_FindClientInPVS, this) != FM_NULLENT) { + @Monster_TaskComplete(this); + } + } + case TASK_WAIT_INDEFINITE: {} + case TASK_WAIT, TASK_WAIT_RANDOM: { + new Float:flWaitFinished = CE_GetMember(this, m_flWaitFinished); + + if (g_flGameTime >= flWaitFinished) { + @Monster_TaskComplete(this); + } + } + case TASK_WAIT_FACE_ENEMY: { + new Float:vecEnemyLKP[3]; CE_GetMemberVec(this, m_vecEnemyLKP, vecEnemyLKP); + new Float:flWaitFinished = CE_GetMember(this, m_flWaitFinished); + + @Monster_MakeIdealYaw(this, vecEnemyLKP); + @Monster_ChangeYaw(this, flYawSpeed); + + if (g_flGameTime >= flWaitFinished) { + @Monster_TaskComplete(this); + } + } + case TASK_MOVE_TO_TARGET_RANGE: { + new pTarget = CE_GetMember(this, m_pTargetEnt); + + if (pTarget == FM_NULLENT) { + @Monster_TaskFail(this); + } else { // if (CE_GetMember(this, m_pPathTask) == Invalid_NavBuildPathTask) { + new Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + new Float:vecTarget[3]; pev(pTarget, pev_origin, vecTarget); + new Float:vecMoveGoal[3]; CE_GetMemberVec(this, m_vecMoveGoal, vecMoveGoal); + + new Float:flDistance = xs_vec_distance_2d(vecOrigin, vecMoveGoal); + + if ((flDistance < Float:data) || xs_vec_distance(vecTarget, vecMoveGoal) > (Float:data * 0.5)) { + pev(pTarget, pev_origin, vecMoveGoal); + flDistance = xs_vec_distance_2d(vecOrigin, vecMoveGoal); + @Monster_RefreshRoute(this); + } + + new Activity:iMovementActivity = CE_GetMember(this, m_iMovementActivity); + + if (flDistance < Float:data) { + @Monster_TaskComplete(this); + @Monster_RouteClear(this); + } else if (flDistance < 190.0 && iMovementActivity != ACT_WALK) { + iMovementActivity = ACT_WALK; + } else if (flDistance >= 270.0 && iMovementActivity != ACT_RUN) { + iMovementActivity = ACT_RUN; + } + } + } + case TASK_WAIT_FOR_MOVEMENT: { + if (@Monster_MovementIsComplete(this)) { + @Monster_TaskComplete(this); + @Monster_RouteClear(this); + } + } + case TASK_DIE: { + static Float:flFrame; pev(this, pev_frame, flFrame); + if (bSequenceFinished && flFrame >= 255.0) { + set_pev(this, pev_deadflag, DEAD_DEAD); + + CE_CallMethod(this, SetThink, NULL_STRING); + @Monster_StopAnimation(this); + + if (!@Monster_BBoxFlat(this)) { + engfunc(EngFunc_SetSize, this, Float:{-4.0, -4.0, 0.0}, Float:{4.0, 4.0, 1.0}); + } else { + new Float:vecMins[3]; pev(this, pev_mins, vecMins); + new Float:vecMaxs[3]; pev(this, pev_maxs, vecMaxs); + + vecMaxs[2] = vecMins[1] + 1.0; + + engfunc(EngFunc_SetSize, this, vecMins, vecMaxs); + } + + if (@Monster_ShouldFadeOnDeath(this)) { + @Monster_SUB_StartFadeOut(this); + } else { + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + CE_CallMethod(this, EmitSound, SOUND_CARCASS, vecOrigin, 384, 30.0); + } + } + } + case TASK_RANGE_ATTACK1_NOTURN, TASK_MELEE_ATTACK1_NOTURN, TASK_MELEE_ATTACK2_NOTURN, TASK_RANGE_ATTACK2_NOTURN, TASK_RELOAD_NOTURN: { + if (bSequenceFinished) { + CE_SetMember(this, m_iActivity, ACT_RESET); + @Monster_TaskComplete(this); + } + } + case TASK_RANGE_ATTACK1, TASK_MELEE_ATTACK1, TASK_MELEE_ATTACK2, TASK_RANGE_ATTACK2, TASK_SPECIAL_ATTACK1, TASK_SPECIAL_ATTACK2, TASK_RELOAD: { + new Float:vecEnemyLKP[3]; CE_GetMemberVec(this, m_vecEnemyLKP, vecEnemyLKP); + + @Monster_MakeIdealYaw(this, vecEnemyLKP); + @Monster_ChangeYaw(this, flYawSpeed); + + if (bSequenceFinished) { + CE_SetMember(this, m_iActivity, ACT_RESET); + @Monster_TaskComplete(this); + } + } + case TASK_SMALL_FLINCH: { + if (bSequenceFinished) { + @Monster_TaskComplete(this); + } + } + case TASK_WAIT_FOR_SCRIPT: { + new pCine = CE_GetMember(this, m_pCine); + new iDelay = CE_GetMember(pCine, "iDelay"); + new iszPlay = CE_GetMember(pCine, "iszPlay"); + new Float:flStartTime = CE_GetMember(pCine, "iStartTime"); + + if (iDelay <= 0 && g_flGameTime >= flStartTime) { + @Monster_TaskComplete(this); + + CE_CallMethod(pCine, StartSequence, iszPlay, true); + + if (bSequenceFinished) { + @Monster_ClearSchedule(this); + } + + set_pev(this, pev_framerate, 1.0); + } + } + case TASK_PLAY_SCRIPT: { + new pCine = CE_GetMember(this, m_pCine); + + if (bSequenceFinished) { + CE_CallMethod(pCine, SequenceDone); + } + } + } +} + +@Monster_AlertSound(this) {} +@Monster_DeathSound(this) {} +@Monster_IdleSound(this) {} +@Monster_PainSound(this) {} + +stock UTIL_DrawLine(const Float:vecSrc[], const Float:vecTarget[], const rgiColor[3] = {255, 0, 0}) { + new iLifeTime = 30; + new iWidth = 8; + new iBrightness = 255; + + new iModelIndex = engfunc(EngFunc_ModelIndex, "sprites/smoke.spr"); + + engfunc(EngFunc_MessageBegin, MSG_PVS, SVC_TEMPENTITY, vecSrc, 0); + write_byte(TE_BEAMPOINTS); + engfunc(EngFunc_WriteCoord, vecTarget[0]); + engfunc(EngFunc_WriteCoord, vecTarget[1]); + engfunc(EngFunc_WriteCoord, vecTarget[2]); + engfunc(EngFunc_WriteCoord, vecSrc[0]); + engfunc(EngFunc_WriteCoord, vecSrc[1]); + engfunc(EngFunc_WriteCoord, vecSrc[2]); + write_short(iModelIndex); + write_byte(0); + write_byte(0); + write_byte(iLifeTime); + write_byte(iWidth); + write_byte(0); + write_byte(rgiColor[0]); + write_byte(rgiColor[1]); + write_byte(rgiColor[2]); + write_byte(iBrightness); + write_byte(0); + message_end(); +} + +@Monster_CheckTraceHullAttack(this, Float:flDist, Float:flDamage, iDamageBits) { + static Float:vecAimAngles[3]; + pev(this, pev_angles, vecAimAngles); + vecAimAngles[0] = -vecAimAngles[0]; + + static Float:vecForward[3]; angle_vector(vecAimAngles, ANGLEVECTOR_FORWARD, vecForward); + + static Float:vecSize[3]; pev(this, pev_size, vecSize); + + static Float:vecStart[3]; + pev(this, pev_origin, vecStart); + vecStart[2] += vecSize[2] / 2; + + static Float:vecEnd[3]; xs_vec_add_scaled(vecStart, vecForward, flDist, vecEnd); + + engfunc(EngFunc_TraceHull, vecStart, vecEnd, DONT_IGNORE_MONSTERS, HULL_HEAD, this, g_pTrace); + static pHit; pHit = get_tr2(g_pTrace, TR_pHit); + + if (pHit > 0) { + if (flDamage > 0.0) { + ExecuteHamB(Ham_TakeDamage, pHit, this, this, flDamage, iDamageBits); + } + + return pHit; + } + + return FM_NULLENT; +} + +@Monster_MeleeAttack1(this) { + static Float:flRange; flRange = CE_GetMember(this, m_flMeleeAttack1Range); + static Float:flDamage; flDamage = CE_GetMember(this, m_flMeleeAttack1Damage); + + return CE_CallMethod(this, CheckTraceHullAttack, flRange, flDamage, DMG_SLASH); +} + +@Monster_MeleeAttack2(this) { + static Float:flRange; flRange = CE_GetMember(this, m_flMeleeAttack2Range); + static Float:flDamage; flDamage = CE_GetMember(this, m_flMeleeAttack2Damage); + + return CE_CallMethod(this, CheckTraceHullAttack, flRange, flDamage, DMG_SLASH); +} + +FireTargets(const szTargetName[], pActivator, pCaller, iUseType, Float:flValue) { + if (equal(szTargetName, NULL_STRING)) return; + + static pTarget; pTarget = FM_NULLENT; + while ((pTarget = engfunc(EngFunc_FindEntityByString, pTarget, "targetname", szTargetName)) != 0) { + if (pTarget && !(pev(pTarget, pev_flags) & FL_KILLME)) { + ExecuteHamB(Ham_Use, pTarget, pCaller, pActivator, iUseType, flValue); + } + } +} + +MapTextureTypeStepType(chTextureType) { + switch (chTextureType) { + case CHAR_TEX_CONCRETE: return STEP_CONCRETE; + case CHAR_TEX_METAL: return STEP_METAL; + case CHAR_TEX_DIRT: return STEP_DIRT; + case CHAR_TEX_VENT: return STEP_VENT; + case CHAR_TEX_GRATE: return STEP_GRATE; + case CHAR_TEX_TILE: return STEP_TILE; + case CHAR_TEX_SLOSH: return STEP_SLOSH; + case CHAR_TEX_SNOW: return STEP_SNOW; + } + + return STEP_CONCRETE; +} + +Float:MapTextureTypeVolume(chTextureType) { + switch (chTextureType) { + case CHAR_TEX_CONCRETE: return 0.5; + case CHAR_TEX_METAL: return 0.5; + case CHAR_TEX_DIRT: return 0.55; + case CHAR_TEX_VENT: return 0.7; + case CHAR_TEX_GRATE: return 0.5; + case CHAR_TEX_TILE: return 0.5; + case CHAR_TEX_SLOSH: return 0.5; + } + + return 0.0; +} + +@Monster_CatagorizeTextureType(this) { + static Float:vecSrc[3]; pev(this, pev_origin, vecSrc); + static Float:vecEnd[3]; xs_vec_set(vecEnd, vecSrc[0], vecSrc[1], -8192.0); + + static iGroundEntity; iGroundEntity = pev(this, pev_groundentity); + if (iGroundEntity == FM_NULLENT) return CHAR_TEX_CONCRETE; + + static szTexture[32]; engfunc(EngFunc_TraceTexture, iGroundEntity, vecSrc, vecEnd, szTexture, charsmax(szTexture)); + + if (szTexture[0] == '-' || szTexture[0] == '+') { + format(szTexture, charsmax(szTexture), "%s", szTexture[2]); + } + + if (szTexture[0] == '{' || szTexture[0] == '!' || szTexture[0] == '~' || szTexture[0] == ' ') { + format(szTexture, charsmax(szTexture), "%s", szTexture[1]); + } + + return dllfunc(DLLFunc_PM_FindTextureType, szTexture); +} + +@Monster_StepSound(this) { + if (~pev(this, pev_flags) & FL_ONGROUND) return; + + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + static Float:vecMins[3]; pev(this, pev_mins, vecMins); + static Float:flStepHeight; flStepHeight = CE_GetMember(this, m_flStepHeight); + static Float:vecKnee[3]; xs_vec_set(vecKnee, vecOrigin[0], vecOrigin[1], vecOrigin[2] - vecMins[2] + flStepHeight); + static Float:vecFeet[3]; xs_vec_set(vecFeet, vecOrigin[0], vecOrigin[1], vecOrigin[2] - vecMins[2] + (flStepHeight / 2)); + + static iStep; iStep = STEP_CONCRETE; + static Float:fVolume; fVolume = 0.0; + static bool:bOnLadder; bOnLadder = false; + + if (bOnLadder) { + iStep = STEP_LADDER; + fVolume = 0.35; + } else if (engfunc(EngFunc_PointContents, vecKnee) == CONTENTS_WATER) { + iStep = STEP_WADE; + fVolume = 0.65; + } else if (engfunc(EngFunc_PointContents, vecFeet) == CONTENTS_WATER) { + iStep = STEP_SLOSH; + fVolume = 0.5; + } else { + static chTextureType; chTextureType = @Monster_CatagorizeTextureType(this); + + iStep = MapTextureTypeStepType(chTextureType); + fVolume = MapTextureTypeVolume(chTextureType); + } + + if ((pev(this, pev_flags) & FL_DUCKING)) { + fVolume *= 0.35; + } + + @Monster_PlayStepSound(this, iStep, fVolume); +} + +@Monster_PlayStepSound(this, iStep, Float:flVolume) { + static iStepLeft; iStepLeft = CE_GetMember(this, m_iStepLeft); + static iRand; iRand = random(2) + (iStepLeft * 2); + static iSkipStep; iSkipStep = 0; + + switch (iStep) { + case STEP_CONCRETE: { + switch (iRand) { + case 0: emit_sound(this, CHAN_BODY, "player/pl_step1.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 1: emit_sound(this, CHAN_BODY, "player/pl_step3.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 2: emit_sound(this, CHAN_BODY, "player/pl_step2.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 3: emit_sound(this, CHAN_BODY, "player/pl_step4.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + } + } + case STEP_METAL: { + switch (iRand) { + case 0: emit_sound(this, CHAN_BODY, "player/pl_metal1.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 1: emit_sound(this, CHAN_BODY, "player/pl_metal3.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 2: emit_sound(this, CHAN_BODY, "player/pl_metal2.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 3: emit_sound(this, CHAN_BODY, "player/pl_metal4.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + } + } + case STEP_DIRT: { + switch (iRand) { + case 0: emit_sound(this, CHAN_BODY, "player/pl_dirt1.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 1: emit_sound(this, CHAN_BODY, "player/pl_dirt3.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 2: emit_sound(this, CHAN_BODY, "player/pl_dirt2.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 3: emit_sound(this, CHAN_BODY, "player/pl_dirt4.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + } + } + case STEP_VENT: { + switch (iRand) { + case 0: emit_sound(this, CHAN_BODY, "player/pl_duct1.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 1: emit_sound(this, CHAN_BODY, "player/pl_duct3.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 2: emit_sound(this, CHAN_BODY, "player/pl_duct2.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 3: emit_sound(this, CHAN_BODY, "player/pl_duct4.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + } + } + case STEP_GRATE: { + switch (iRand) { + case 0: emit_sound(this, CHAN_BODY, "player/pl_grate1.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 1: emit_sound(this, CHAN_BODY, "player/pl_grate3.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 2: emit_sound(this, CHAN_BODY, "player/pl_grate2.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 3: emit_sound(this, CHAN_BODY, "player/pl_grate4.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + } + } + case STEP_TILE: { + if (!random(5)) { + iRand = 4; + } + + switch (iRand) { + case 0: emit_sound(this, CHAN_BODY, "player/pl_tile1.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 1: emit_sound(this, CHAN_BODY, "player/pl_tile3.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 2: emit_sound(this, CHAN_BODY, "player/pl_tile2.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 3: emit_sound(this, CHAN_BODY, "player/pl_tile4.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 4: emit_sound(this, CHAN_BODY, "player/pl_tile5.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + } + } + case STEP_SLOSH: { + switch (iRand) { + case 0: emit_sound(this, CHAN_BODY, "player/pl_slosh1.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 1: emit_sound(this, CHAN_BODY, "player/pl_slosh3.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 2: emit_sound(this, CHAN_BODY, "player/pl_slosh2.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 3: emit_sound(this, CHAN_BODY, "player/pl_slosh4.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + } + } + case STEP_WADE: { + if (iSkipStep == 0) { + iSkipStep++; + } + + if (iSkipStep++ == 3) { + iSkipStep = 0; + } + + switch (iRand) { + case 0: emit_sound(this, CHAN_BODY, "player/pl_wade1.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 1: emit_sound(this, CHAN_BODY, "player/pl_wade2.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 2: emit_sound(this, CHAN_BODY, "player/pl_wade3.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 3: emit_sound(this, CHAN_BODY, "player/pl_wade4.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + } + + } + case STEP_LADDER: { + switch (iRand) { + case 0: emit_sound(this, CHAN_BODY, "player/pl_ladder1.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 1: emit_sound(this, CHAN_BODY, "player/pl_ladder3.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 2: emit_sound(this, CHAN_BODY, "player/pl_ladder2.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 3: emit_sound(this, CHAN_BODY, "player/pl_ladder4.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + } + } + case STEP_SNOW: { + switch (iRand) { + case 0: emit_sound(this, CHAN_BODY, "player/pl_snow1.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 1: emit_sound(this, CHAN_BODY, "player/pl_snow3.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 2: emit_sound(this, CHAN_BODY, "player/pl_snow2.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + case 3: emit_sound(this, CHAN_BODY, "player/pl_snow4.wav", flVolume, ATTN_NORM, 0, PITCH_NORM); + } + } + } + + CE_SetMember(this, m_iStepLeft, !iStepLeft); +} + +stock bool:UTIL_IsMasterTriggered(const szMaster[], pActivator) { + if (!equal(szMaster, NULL_STRING)) { + new pMaster = engfunc(EngFunc_FindEntityByString, 0, "targetname", szMaster); + + if (pMaster && (ExecuteHam(Ham_ObjectCaps, pMaster) & FCAP_MASTER)) { + return !!ExecuteHamB(Ham_IsTriggered, pMaster, pActivator); + } + } + + return true; +} + +stock bool:UTIL_IsDoor(pEntity) { + if (pEntity == FM_NULLENT) return false; + if (!pEntity) return false; + + static szClassname[32]; pev(pEntity, pev_classname, szClassname, charsmax(szClassname)); + + return !!equal(szClassname, "func_door", 9); +} + +stock UTIL_IsUsableEntity(pEntity, pWalker) { + static iszMaster; iszMaster = get_ent_data(pEntity, "CBaseToggle", "m_sMaster"); + + if (iszMaster) { + static szMaster[32]; engfunc(EngFunc_SzFromIndex, iszMaster, szMaster, charsmax(szMaster)); + + if (!UTIL_IsMasterTriggered(szMaster, pWalker)) { + return false; + } + } + + return true; +} diff --git a/entities/entity_base_npc.sma b/entities/entity_base_npc.sma new file mode 100644 index 0000000..958b63c --- /dev/null +++ b/entities/entity_base_npc.sma @@ -0,0 +1,1095 @@ +#pragma semicolon 1 + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#define PLUGIN "[Entity] Base NPC" +#define VERSION "1.0.0" +#define AUTHOR "Hedgehog Fog" + +#define IS_PLAYER(%1) (%1 >= 1 && %1 <= MaxClients) +#define IS_MONSTER(%1) (!!(pev(%1, pev_flags) & FL_MONSTER)) + +#define ENTITY_NAME BASE_NPC_ENTITY_NAME + +new g_pCvarUseAstar; + +new bool:g_bUseAstar; + +new g_pTrace; + +public plugin_precache() { + Nav_Precache(); + + g_pTrace = create_tr2(); + + CE_Register(ENTITY_NAME, CEPreset_NPC, true); + + CE_RegisterHook(ENTITY_NAME, CEFunction_Init, "@Entity_Init"); + CE_RegisterHook(ENTITY_NAME, CEFunction_Restart, "@Entity_Restart"); + CE_RegisterHook(ENTITY_NAME, CEFunction_Spawned, "@Entity_Spawned"); + CE_RegisterHook(ENTITY_NAME, CEFunction_Remove, "@Entity_Remove"); + CE_RegisterHook(ENTITY_NAME, CEFunction_Kill, "@Entity_Kill"); + CE_RegisterHook(ENTITY_NAME, CEFunction_Killed, "@Entity_Killed"); + CE_RegisterHook(ENTITY_NAME, CEFunction_Think, "@Entity_Think"); + + CE_RegisterMethod(ENTITY_NAME, PlayAction, "@Entity_PlayAction", CE_MP_Cell, CE_MP_Cell, CE_MP_Float, CE_MP_Cell); + CE_RegisterMethod(ENTITY_NAME, EmitVoice, "@Entity_EmitVoice", CE_MP_String, CE_MP_Cell); + + CE_RegisterVirtualMethod(ENTITY_NAME, ResetPath, "@Entity_ResetPath"); + CE_RegisterVirtualMethod(ENTITY_NAME, AttackThink, "@Entity_AttackThink"); + CE_RegisterVirtualMethod(ENTITY_NAME, ProcessPath, "@Entity_ProcessPath"); + CE_RegisterVirtualMethod(ENTITY_NAME, ProcessTarget, "@Entity_ProcessTarget"); + CE_RegisterVirtualMethod(ENTITY_NAME, ProcessGoal, "@Entity_ProcessGoal"); + CE_RegisterVirtualMethod(ENTITY_NAME, SetTarget, "@Entity_SetTarget", CE_MP_FloatArray, 3); + CE_RegisterVirtualMethod(ENTITY_NAME, AIThink, "@Entity_AIThink"); + CE_RegisterVirtualMethod(ENTITY_NAME, GetEnemy, "@Entity_GetEnemy"); + CE_RegisterVirtualMethod(ENTITY_NAME, IsEnemy, "@Entity_IsEnemy", CE_MP_Cell); + CE_RegisterVirtualMethod(ENTITY_NAME, IsValidEnemy, "@Entity_IsValidEnemy", CE_MP_Cell); + CE_RegisterVirtualMethod(ENTITY_NAME, UpdateEnemy, "@Entity_UpdateEnemy"); + CE_RegisterVirtualMethod(ENTITY_NAME, UpdateGoal, "@Entity_UpdateGoal"); + CE_RegisterVirtualMethod(ENTITY_NAME, CanAttack, "@Entity_CanAttack", CE_MP_Cell); + CE_RegisterVirtualMethod(ENTITY_NAME, StartAttack, "@Entity_StartAttack"); + CE_RegisterVirtualMethod(ENTITY_NAME, ReleaseAttack, "@Entity_ReleaseAttack"); + CE_RegisterVirtualMethod(ENTITY_NAME, Hit, "@Entity_Hit"); + CE_RegisterVirtualMethod(ENTITY_NAME, HandlePath, "@Entity_HandlePath", CE_MP_Cell); + CE_RegisterVirtualMethod(ENTITY_NAME, UpdateTarget, "@Entity_UpdateTarget"); + CE_RegisterVirtualMethod(ENTITY_NAME, Dying, "@Entity_Dying"); + CE_RegisterVirtualMethod(ENTITY_NAME, GetPathCost, "@Entity_GetPathCost", CE_MP_Cell, CE_MP_Cell); + CE_RegisterVirtualMethod(ENTITY_NAME, FindPath, "@Entity_FindPath", CE_MP_FloatArray, 3); + CE_RegisterVirtualMethod(ENTITY_NAME, MoveTo, "@Entity_MoveTo", CE_MP_FloatArray, 3); + CE_RegisterVirtualMethod(ENTITY_NAME, TakeDamage, "@Entity_TakeDamage", CE_MP_Cell, CE_MP_Cell, CE_MP_Float, CE_MP_Cell); + CE_RegisterVirtualMethod(ENTITY_NAME, IsVisible, "@Entity_IsVisible", CE_MP_FloatArray, 3, CE_MP_Cell); + CE_RegisterVirtualMethod(ENTITY_NAME, FindEnemy, "@Entity_FindEnemy", CE_MP_Float, CE_MP_Float, CE_MP_Cell, CE_MP_Cell, CE_MP_Cell); + CE_RegisterVirtualMethod(ENTITY_NAME, GetEnemyPriority, "@Entity_GetEnemyPriority", CE_MP_Cell); + CE_RegisterVirtualMethod(ENTITY_NAME, IsReachable, "@Entity_IsReachable", CE_MP_FloatArray, 3, CE_MP_Cell, CE_MP_Float); + CE_RegisterVirtualMethod(ENTITY_NAME, TestStep, "@Entity_TestStep", CE_MP_FloatArray, 3, CE_MP_FloatArray, 3, CE_MP_FloatArray, 3); + CE_RegisterVirtualMethod(ENTITY_NAME, MoveForward, "@Entity_MoveForward"); + CE_RegisterVirtualMethod(ENTITY_NAME, StopMovement, "@Entity_StopMovement"); + CE_RegisterVirtualMethod(ENTITY_NAME, MovementThink, "@Entity_MovementThink"); + CE_RegisterVirtualMethod(ENTITY_NAME, IsInViewCone, "@Entity_IsInViewCone", CE_MP_FloatArray, 3); + CE_RegisterVirtualMethod(ENTITY_NAME, Pain, "@Entity_Pain"); +} + +public plugin_init() { + register_plugin(PLUGIN, VERSION, AUTHOR); + + RegisterHam(Ham_TakeDamage, CE_BASE_CLASSNAME, "HamHook_Base_TakeDamage_Post", .Post = 1); + + g_pCvarUseAstar = register_cvar("npc_use_astar", "1"); + + bind_pcvar_num(g_pCvarUseAstar, g_bUseAstar); +} + +public plugin_end() { + free_tr2(g_pTrace); +} + +/*--------------------------------[ Methods ]--------------------------------*/ + +@Entity_Init(this) { + CE_SetMemberVec(this, CE_MEMBER_MINS, Float:{-12.0, -12.0, -32.0}); + CE_SetMemberVec(this, CE_MEMBER_MAXS, Float:{12.0, 12.0, 32.0}); + CE_SetMember(this, m_flAIThinkRate, 0.1); + CE_SetMember(this, m_irgPath, ArrayCreate(3)); + CE_SetMember(this, m_flAttackRange, 18.0); + CE_SetMember(this, m_flHitRange, 24.0); + CE_SetMember(this, m_flAttackRate, 1.0); + CE_SetMember(this, m_flAttackDuration, 1.0); + CE_SetMember(this, m_flHitDelay, 0.0); + CE_SetMember(this, m_iRevengeChance, 10); + CE_SetMember(this, m_flStepHeight, 18.0); + CE_SetMember(this, m_flDamage, 0.0); + CE_SetMember(this, m_flPathSearchDelay, 5.0); + CE_SetMember(this, m_flDieDuration, 0.1); + CE_SetMember(this, m_flPainRate, 0.25); + CE_SetMember(this, m_pBuildPathTask, Invalid_NavBuildPathTask); + CE_SetMemberVec(this, m_vecHitAngle, Float:{0.0, 0.0, 0.0}); + CE_SetMemberVec(this, m_vecWeaponOffset, Float:{0.0, 0.0, 0.0}); +} + +@Entity_Restart(this) { + CE_CallMethod(this, ResetPath); +} + +@Entity_Spawned(this) { + static Float:flGameTime; flGameTime = get_gametime(); + + CE_SetMember(this, m_flNextAttack, flGameTime); + CE_SetMember(this, m_flNextAIThink, flGameTime); + CE_SetMember(this, m_flLastAIThink, flGameTime); + CE_SetMember(this, m_flNextAction, flGameTime); + CE_SetMember(this, m_flNextPathSearch, flGameTime); + CE_SetMember(this, m_flNextPain, flGameTime); + CE_SetMember(this, m_flNextGoalUpdate, flGameTime); + CE_SetMember(this, m_flTargetArrivalTime, 0.0); + CE_SetMember(this, m_flReleaseAttack, 0.0); + CE_SetMember(this, m_flReleaseHit, 0.0); + CE_DeleteMember(this, m_vecGoal); + CE_DeleteMember(this, m_vecInput); + CE_DeleteMember(this, m_vecTarget); + CE_SetMember(this, m_pKiller, 0); + + set_pev(this, pev_rendermode, kRenderNormal); + set_pev(this, pev_renderfx, kRenderFxGlowShell); + set_pev(this, pev_renderamt, 4.0); + set_pev(this, pev_rendercolor, Float:{0.0, 0.0, 0.0}); + set_pev(this, pev_health, 1.0); + set_pev(this, pev_takedamage, DAMAGE_AIM); + set_pev(this, pev_view_ofs, Float:{0.0, 0.0, 32.0}); + set_pev(this, pev_maxspeed, 0.0); + set_pev(this, pev_enemy, 0); + set_pev(this, pev_fov, 90.0); + set_pev(this, pev_gravity, 1.0); + + engfunc(EngFunc_DropToFloor, this); + + set_pev(this, pev_nextthink, flGameTime + 0.1); +} + +@Entity_Kill(this, pKiller) { + CE_SetMember(this, m_pKiller, pKiller); + + new iDeadFlag = pev(this, pev_deadflag); + + if (pKiller && iDeadFlag == DEAD_NO) { + CE_CallMethod(this, StopMovement); + + new Float:flGameTime = get_gametime(); + new Float:flDieDuration = CE_GetMember(this, m_flDieDuration); + + set_pev(this, pev_takedamage, DAMAGE_NO); + set_pev(this, pev_deadflag, DEAD_DYING); + set_pev(this, pev_nextthink, flGameTime + flDieDuration); + + CE_SetMember(this, m_flNextAIThink, flGameTime + flDieDuration); + + // cancel first kill function to play duing animation + CE_CallMethod(this, Dying); + + return PLUGIN_HANDLED; + } + + return PLUGIN_CONTINUE; +} + +@Entity_Killed(this) { + CE_CallMethod(this, ResetPath); +} + +@Entity_Dying(this) {} + +@Entity_Remove(this) { + CE_CallMethod(this, ResetPath); + + new Array:irgPath = CE_GetMember(this, m_irgPath); + ArrayDestroy(irgPath); +} + +@Entity_Think(this) { + static Float:flGameTime; flGameTime = get_gametime(); + static iDeadFlag; iDeadFlag = pev(this, pev_deadflag); + + switch (iDeadFlag) { + case DEAD_NO: { + static Float:flNextAIThink; flNextAIThink = CE_GetMember(this, m_flNextAIThink); + + if (flNextAIThink <= flGameTime) { + CE_CallMethod(this, AIThink); + + static Float:flAIThinkRate; flAIThinkRate = CE_GetMember(this, m_flAIThinkRate); + CE_SetMember(this, m_flNextAIThink, flGameTime + flAIThinkRate); + } + + // update velocity at high rate to avoid inconsistent velocity + CE_CallMethod(this, MovementThink); + } + case DEAD_DYING: { + // TODO: Implement dying think + CE_Kill(this, CE_GetMember(this, m_pKiller)); + return; + } + case DEAD_DEAD, DEAD_RESPAWNABLE: { + return; + } + } + + set_pev(this, pev_nextthink, flGameTime + 0.01); +} + +@Entity_AIThink(this) { + CE_CallMethod(this, AttackThink); + + if (Float:CE_GetMember(this, m_flNextGoalUpdate) <= get_gametime()) { + CE_CallMethod(this, UpdateGoal); + CE_SetMember(this, m_flNextGoalUpdate, get_gametime() + 0.1); + } + + CE_CallMethod(this, UpdateTarget); + + if (CE_HasMember(this, m_vecTarget)) { + static Float:vecTarget[3]; CE_GetMemberVec(this, m_vecTarget, vecTarget); + CE_CallMethod(this, MoveTo, vecTarget); + } else if (CE_HasMember(this, m_vecInput)) { + CE_CallMethod(this, StopMovement); + } + + CE_SetMember(this, m_flLastAIThink, get_gametime()); +} + +@Entity_MovementThink(this) { + if (!CE_HasMember(this, m_vecInput)) return; + + static Float:vecInput[3]; CE_GetMemberVec(this, m_vecInput, vecInput); + + static Float:flSpeed; pev(this, pev_maxspeed, flSpeed); + static bool:bIsFlying; bIsFlying = @Entity_IsFlying(this); + static Float:vecVelocity[3]; pev(this, pev_velocity, vecVelocity); + + vecVelocity[0] = vecInput[0] * flSpeed; + vecVelocity[1] = vecInput[1] * flSpeed; + if (bIsFlying) vecVelocity[2] = vecInput[2] * flSpeed; + + static Float:vecAngles[3]; vector_to_angle(vecInput, vecAngles); + + if (!bIsFlying) { + engfunc(EngFunc_WalkMove, this, vecAngles[1], 0.01, WALKMOVE_NORMAL); + } + + set_pev(this, pev_speed, flSpeed); + set_pev(this, pev_velocity, vecVelocity); + + if (!xs_vec_len(vecInput)) { + CE_DeleteMember(this, m_vecInput); + } +} + +@Entity_AttackThink(this) { + static Float:flGameTime; flGameTime = get_gametime(); + + static pEnemy; pEnemy = CE_CallMethod(this, GetEnemy); + static Float:vecTarget[3]; pev(pEnemy, pev_origin, vecTarget); + + if (CE_CallMethod(this, IsInViewCone, vecTarget)) { + static Float:flReleaseAttack; flReleaseAttack = CE_GetMember(this, m_flReleaseAttack); + if (!flReleaseAttack) { + if (pEnemy && CE_CallMethod(this, CanAttack, pEnemy)) { + CE_CallMethod(this, StartAttack); + } + } else if (flReleaseAttack <= flGameTime) { + CE_CallMethod(this, ReleaseAttack); + } + + static Float:flReleaseHit; flReleaseHit = CE_GetMember(this, m_flReleaseHit); + if (flReleaseHit && flReleaseHit <= flGameTime) { + CE_CallMethod(this, Hit); + CE_SetMember(this, m_flReleaseHit, 0.0); + } + } + + @Entity_TurnTo(this, vecTarget); +} + +@Entity_TakeDamage(this, pInflictor, pAttacker, Float:flDamage, iDamageBits) { + static Float:flGameTime; flGameTime = get_gametime(); + + if (IS_PLAYER(pAttacker) && CE_CallMethod(this, IsEnemy, pAttacker)) { + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + static Float:vecTarget[3]; pev(pAttacker, pev_origin, vecTarget); + static Float:flAttackRange; flAttackRange = CE_GetMember(this, m_flAttackRange); + + if (get_distance_f(vecOrigin, vecTarget) <= flAttackRange && CE_CallMethod(this, IsVisible, vecTarget, 0)) { + if (random(100) < CE_GetMember(this, m_iRevengeChance)) { + set_pev(this, pev_enemy, pAttacker); + } + } + } + + static Float:flNextPain; flNextPain = CE_GetMember(this, m_flNextPain); + + if (flNextPain <= flGameTime) { + CE_CallMethod(this, Pain); + + static Float:flPainRate; flPainRate = CE_GetMember(this, m_flPainRate); + CE_SetMember(this, m_flNextPain, flGameTime + flPainRate); + } +} + +@Entity_Pain(this) {} + +@Entity_CanAttack(this, pEnemy) { + static Float:flGameTime; flGameTime = get_gametime(); + static Float:flNextAttack; flNextAttack = CE_GetMember(this, m_flNextAttack); + if (flNextAttack > flGameTime) return false; + + if (!UTIL_CheckEntitiesLevel(this, pEnemy)) return false; + + static Float:flAttackRange; flAttackRange = CE_GetMember(this, m_flAttackRange); + static Float:vecTarget[3]; pev(pEnemy, pev_origin, vecTarget); + + static Float:vecOrigin[3]; @Entity_GetWeaponOrigin(this, vecOrigin); + if (xs_vec_distance_2d(vecOrigin, vecTarget) > flAttackRange) return false; + + // if (!@Entity_IsInViewCone(this, vecTarget, 60.0)) return false; + + engfunc(EngFunc_TraceLine, vecOrigin, vecTarget, DONT_IGNORE_MONSTERS, this, g_pTrace); + + static Float:flFraction; get_tr2(g_pTrace, TR_flFraction, flFraction); + + if (flFraction != 1.0) { + static pHit; pHit = get_tr2(g_pTrace, TR_pHit); + + if (pHit != pEnemy) return false; + } + + return true; +} + +@Entity_StartAttack(this) { + static Float:flGameTime; flGameTime = get_gametime(); + static Float:flAttackRange; flAttackRange = CE_GetMember(this, m_flAttackRange); + static Float:flAttackDuration; flAttackDuration = CE_GetMember(this, m_flAttackDuration); + static Float:flAttackRate; flAttackRate = CE_GetMember(this, m_flAttackRate); + static Float:flHitDelay; flHitDelay = CE_GetMember(this, m_flHitDelay); + static pEnemy; pEnemy = CE_CallMethod(this, GetEnemy); + + if (pEnemy) { + static Float:vecTargetVelocity[3]; pev(pEnemy, pev_velocity, vecTargetVelocity); + + if (xs_vec_len(vecTargetVelocity) < flAttackRange) { + CE_CallMethod(this, StopMovement); + } + } + + CE_SetMember(this, m_flReleaseHit, flGameTime + flHitDelay); + CE_SetMember(this, m_flReleaseAttack, flGameTime + flAttackDuration); + CE_SetMember(this, m_flNextAttack, flGameTime + flAttackDuration + flAttackRate); +} + +@Entity_ReleaseAttack(this) { + CE_SetMember(this, m_flReleaseAttack, 0.0); +} + +@Entity_Hit(this) { + static Float:flDamage; flDamage = CE_GetMember(this, m_flDamage); + static Float:flHitRange; flHitRange = CE_GetMember(this, m_flHitRange); + static Float:vecHitAngle[3]; CE_GetMemberVec(this, m_vecHitAngle, vecHitAngle); + + static Float:vecAngles[3]; + pev(this, pev_angles, vecAngles); + xs_vec_add(vecAngles, vecHitAngle, vecAngles); + vecAngles[0] = -vecAngles[0]; + + static Float:vecDirection[3]; angle_vector(vecAngles, ANGLEVECTOR_FORWARD, vecDirection); + static Float:vecOrigin[3]; @Entity_GetWeaponOrigin(this, vecOrigin); + static Float:vecTarget[3]; xs_vec_add_scaled(vecOrigin, vecDirection, flHitRange, vecTarget); + + xs_vec_sub(vecTarget, vecOrigin, vecDirection); + xs_vec_normalize(vecDirection, vecDirection); + + engfunc(EngFunc_TraceLine, vecOrigin, vecTarget, DONT_IGNORE_MONSTERS, this, g_pTrace); + + static pTarget; pTarget = get_tr2(g_pTrace, TR_pHit); + + if (pTarget == -1) { + engfunc(EngFunc_TraceHull, vecOrigin, vecTarget, DONT_IGNORE_MONSTERS, HULL_HEAD, this, g_pTrace); + pTarget = get_tr2(g_pTrace, TR_pHit); + } + + if (pTarget != -1) { + get_tr2(g_pTrace, TR_vecEndPos, vecTarget); + xs_vec_sub(vecTarget, vecOrigin, vecDirection); + xs_vec_normalize(vecDirection, vecDirection); + + rg_multidmg_clear(); + ExecuteHamB(Ham_TraceAttack, pTarget, this, flDamage, vecDirection, g_pTrace, DMG_GENERIC); + rg_multidmg_apply(this, this); + } + + return pTarget; +} + +@Entity_GetDirectionVector(this, Float:vecOut[3]) { + static Float:vecAngles[3]; + pev(this, pev_angles, vecAngles); + vecAngles[0] = -vecAngles[0]; + + angle_vector(vecAngles, ANGLEVECTOR_FORWARD, vecOut); + xs_vec_normalize(vecOut, vecOut); +} + +@Entity_MoveTo(this, const Float:vecTarget[3]) { + @Entity_TurnTo(this, vecTarget); + + if (CE_CallMethod(this, IsInViewCone, vecTarget)) { + static Float:flMaxSpeed; pev(this, pev_maxspeed, flMaxSpeed); + + if (flMaxSpeed > 0.0) { + CE_CallMethod(this, MoveForward); + } + } +} + +@Entity_EmitVoice(this, const szSound[], Float:flDuration) { + emit_sound(this, CHAN_VOICE, szSound, VOL_NORM, ATTN_NORM, 0, PITCH_NORM); +} + +@Entity_UpdateEnemy(this) { + static Float:flViewRange; flViewRange = CE_GetMember(this, m_flViewRange); + if (CE_CallMethod(this, FindEnemy, flViewRange, 0.0, false, true, true)) return true; + + static Float:flFindRange; flFindRange = CE_GetMember(this, m_flFindRange); + if (CE_CallMethod(this, FindEnemy, flFindRange, 0.0, false, false, false)) return true; + + return false; +} + +@Entity_UpdateGoal(this) { + static Float:flGameTime; flGameTime = get_gametime(); + + new pEnemy = CE_CallMethod(this, GetEnemy); + if (pEnemy) { + CE_DeleteMember(this, m_vecGoal); + } + + if (Float:CE_GetMember(this, m_flNextEnemyUpdate) <= flGameTime) { + if (CE_CallMethod(this, UpdateEnemy)) { + pEnemy = pev(this, pev_enemy); + } + + CE_SetMember(this, m_flNextEnemyUpdate, flGameTime + 0.1); + } + + if (pEnemy) { + static Float:vecGoal[3]; pev(pEnemy, pev_origin, vecGoal); + CE_SetMemberVec(this, m_vecGoal, vecGoal); + } +} + +@Entity_UpdateTarget(this) { + CE_CallMethod(this, ProcessPath); + CE_CallMethod(this, ProcessGoal); + CE_CallMethod(this, ProcessTarget); +} + +@Entity_ProcessTarget(this) { + if (!CE_HasMember(this, m_vecTarget)) return; + + // static Float:flGameTime; flGameTime = get_gametime(); + // static Float:flArrivalTime; flArrivalTime = CE_GetMember(this, m_flTargetArrivalTime); + + if (@Entity_IsTargetReached(this)) { + CE_DeleteMember(this, m_vecTarget); + } +} + +@Entity_IsTargetReached(this) { + if (!CE_HasMember(this, m_vecTarget)) return true; + + static Float:vecTarget[3]; CE_GetMemberVec(this, m_vecTarget, vecTarget); + + static Float:vecAbsMin[3]; pev(this, pev_absmin, vecAbsMin); + static Float:vecAbsMax[3]; pev(this, pev_absmax, vecAbsMax); + + if (vecTarget[2] < vecAbsMin[2]) return false; + if (vecTarget[2] > vecAbsMax[2]) return false; + + static Float:flMaxDistance; flMaxDistance = 0.0; + + static pEnemy; pEnemy = CE_CallMethod(this, GetEnemy); + static Array:irgPath; irgPath = CE_GetMember(this, m_irgPath); + if (pEnemy && !ArraySize(irgPath)) { + flMaxDistance = CE_GetMember(this, m_flAttackRange); + } else { + flMaxDistance = floatmin(vecAbsMax[0] - vecAbsMin[0], vecAbsMax[0] - vecAbsMin[0]) / 2; + } + + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + + if (xs_vec_distance_2d(vecOrigin, vecTarget) > flMaxDistance) return false; + + return true; +} + +@Entity_ProcessGoal(this) { + static Float:flGameTime; flGameTime = get_gametime(); + + if (CE_HasMember(this, m_vecGoal)) { + static Float:vecGoal[3]; CE_GetMemberVec(this, m_vecGoal, vecGoal); + + if (!CE_CallMethod(this, IsReachable, vecGoal, pev(this, pev_enemy), 32.0)) { + if (g_bUseAstar) { + if (Float:CE_GetMember(this, m_flNextPathSearch) <= flGameTime) { + CE_CallMethod(this, FindPath, vecGoal); + CE_SetMember(this, m_flNextPathSearch, flGameTime + Float:CE_GetMember(this, m_flPathSearchDelay)); + CE_DeleteMember(this, m_vecTarget); + CE_DeleteMember(this, m_vecGoal); + } + } else { + CE_DeleteMember(this, m_vecGoal); + CE_DeleteMember(this, m_vecTarget); + } + } else { + CE_DeleteMember(this, m_vecGoal); + CE_CallMethod(this, SetTarget, vecGoal); + } + } +} + +@Entity_SetTarget(this, const Float:vecTarget[3]) { + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + static Float:flMaxSpeed; pev(this, pev_maxspeed, flMaxSpeed); + static Float:flDuration; flDuration = xs_vec_distance(vecOrigin, vecTarget) / flMaxSpeed; + + CE_SetMemberVec(this, m_vecTarget, vecTarget); + CE_SetMember(this, m_flTargetArrivalTime, get_gametime() + flDuration); +} + +@Entity_PlayAction(this, iStartSequence, iEndSequence, Float:flDuration, bSupercede) { + static Float:flGametime; flGametime = get_gametime(); + if (!bSupercede && flGametime < Float:CE_GetMember(this, m_flNextAction)) return false; + + static iSequence; iSequence = random_num(iStartSequence, iEndSequence); + if (!UTIL_SetSequence(this, iSequence)) return false; + + CE_SetMember(this, m_flNextAction, flGametime + flDuration); + + return true; +} + +@Entity_FindPath(this, Float:vecTarget[3]) { + CE_CallMethod(this, ResetPath); + + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + + new NavBuildPathTask:pTask = Nav_Path_Find(vecOrigin, vecTarget, "NavPathCallback", this, this, "NavPathCost"); + CE_SetMember(this, m_pBuildPathTask, pTask); +} + +@Entity_ResetPath(this) { + new Array:irgPath = CE_GetMember(this, m_irgPath); + ArrayClear(irgPath); + + new NavBuildPathTask:pTask = CE_GetMember(this, m_pBuildPathTask); + if (pTask != Invalid_NavBuildPathTask) { + Nav_Path_FindTask_Abort(pTask); + CE_SetMember(this, m_pBuildPathTask, Invalid_NavBuildPathTask); + } + + // CE_DeleteMember(this, m_vecGoal); + CE_DeleteMember(this, m_vecTarget); +} + +bool:@Entity_ProcessPath(this) { + if (CE_HasMember(this, m_vecTarget)) return true; + + new Array:irgPath = CE_GetMember(this, m_irgPath); + if (!ArraySize(irgPath)) return false; + + static Float:flStepHeight; flStepHeight = CE_GetMember(this, m_flStepHeight); + static Float:vecMins[3]; pev(this, pev_mins, vecMins); + + static Float:vecTarget[3]; + ArrayGetArray(irgPath, 0, vecTarget); + ArrayDeleteItem(irgPath, 0); + + vecTarget[2] += -vecMins[2] + flStepHeight; + + CE_CallMethod(this, SetTarget, vecTarget); + + return true; +} + +@Entity_HandlePath(this, NavPath:pPath) { + if (Nav_Path_IsValid(pPath)) { + static Array:irgPath; irgPath = CE_GetMember(this, m_irgPath); + ArrayClear(irgPath); + + static iSegmentsNum; iSegmentsNum = Nav_Path_GetSegmentCount(pPath); + + for (new iSegment = 0; iSegment < iSegmentsNum; ++iSegment) { + static Float:vecPos[3]; Nav_Path_GetSegmentPos(pPath, iSegment, vecPos); + + ArrayPushArray(irgPath, vecPos, sizeof(vecPos)); + } + } else { + set_pev(this, pev_enemy, 0); + } + + CE_SetMember(this, m_pBuildPathTask, Invalid_NavBuildPathTask); +} + +@Entity_GetEnemy(this) { + new pEnemy = pev(this, pev_enemy); + + if (!CE_CallMethod(this, IsValidEnemy, pEnemy)) return 0; + + return pEnemy; +} + +bool:@Entity_IsEnemy(this, pEnemy) { + if (pEnemy <= 0) return false; + if (!pev_valid(pEnemy)) return false; + + static iTeam; iTeam = pev(this, pev_team); + + new iEnemyTeam = 0; + if (IS_PLAYER(pEnemy)) { + iEnemyTeam = get_ent_data(pEnemy, "CBasePlayer", "m_iTeam"); + } else if (IS_MONSTER(pEnemy)) { + iEnemyTeam = pev(pEnemy, pev_team); + } else { + return false; + } + + if (iTeam == iEnemyTeam) return false; + if (pev(pEnemy, pev_takedamage) == DAMAGE_NO) return false; + if (pev(pEnemy, pev_solid) < SOLID_BBOX) return false; + if (IsInvisible(pEnemy)) return false; + + return true; +} + +bool:@Entity_IsValidEnemy(this, pEnemy) { + if (pEnemy <= 0) return false; + if (!pev_valid(pEnemy)) return false; + + if (IS_PLAYER(pEnemy)) { + if (!is_user_alive(pEnemy)) return false; + } else if (IS_MONSTER(pEnemy)) { + if (pev(pEnemy, pev_deadflag) != DEAD_NO) return false; + } else { + return false; + } + + if (pev(pEnemy, pev_takedamage) == DAMAGE_NO) return false; + if (pev(pEnemy, pev_solid) < SOLID_BBOX) return false; + if (IsInvisible(pEnemy)) return false; + + return true; +} + +Float:@Entity_GetEnemyPriority(this, pEnemy) { + if (IS_PLAYER(pEnemy)) return 1.0; + if (IS_MONSTER(pEnemy)) return 0.075; + + return 0.0; +} + +@Entity_FindEnemy(this, Float:flMaxDistance, Float:flMinPriority, bool:bVisibleOnly, bool:bReachableOnly, bool:bAllowMonsters) { + new pEnemy = pev(this, pev_enemy); + if (!CE_CallMethod(this, IsValidEnemy, pEnemy)) { + set_pev(this, pev_enemy, 0); + } + + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + static pClosestTarget; pClosestTarget = 0; + static Float:flClosestTargetPriority; flClosestTargetPriority = 0.0; + + new pTarget = 0; + while ((pTarget = engfunc(EngFunc_FindEntityInSphere, pTarget, vecOrigin, flMaxDistance)) > 0) { + if (this == pTarget) continue; + + if (!CE_CallMethod(this, IsEnemy, pTarget)) continue; + if (!CE_CallMethod(this, IsValidEnemy, pTarget)) continue; + + static Float:vecTarget[3]; pev(pTarget, pev_origin, vecTarget); + + if (bVisibleOnly && !CE_CallMethod(this, IsVisible, vecTarget, pTarget)) continue; + + static Float:flDistance; flDistance = xs_vec_distance(vecOrigin, vecTarget); + static Float:flTargetPriority; flTargetPriority = 1.0 - (flDistance / flMaxDistance); + + if (!bAllowMonsters && IS_MONSTER(pTarget)) { + flTargetPriority *= 0.0; + } else { + flTargetPriority *= Float:CE_CallMethod(this, GetEnemyPriority, pTarget); + } + + if (flTargetPriority >= flMinPriority && bReachableOnly && !CE_CallMethod(this, IsReachable, vecTarget, pTarget)) { + flTargetPriority *= 0.1; + } + + if (flTargetPriority >= flMinPriority && flTargetPriority > flClosestTargetPriority) { + pClosestTarget = pTarget; + flClosestTargetPriority = flTargetPriority; + } + } + + if (pClosestTarget) { + set_pev(this, pev_enemy, pClosestTarget); + } + + return pClosestTarget; +} + +bool:@Entity_IsVisible(this, const Float:vecTarget[3], pIgnoreEnt) { + static Float:vecOrigin[3]; ExecuteHamB(Ham_EyePosition, this, vecOrigin); + + static iIgnoreEntSolidType; iIgnoreEntSolidType = SOLID_NOT; + if (pIgnoreEnt) { + iIgnoreEntSolidType = pev(pIgnoreEnt, pev_solid); + set_pev(pIgnoreEnt, pev_solid, SOLID_NOT); + } + + static bool:bIsOpen; bIsOpen = IsOpen(vecOrigin, vecTarget, this); + + if (pIgnoreEnt) { + set_pev(pIgnoreEnt, pev_solid, iIgnoreEntSolidType); + } + + return bIsOpen; +} + +bool:@Entity_IsReachable(this, const Float:vecTarget[3], pIgnoreEnt, Float:flStepLength) { + if ((~pev(this, pev_flags) & FL_ONGROUND) && !@Entity_IsFlying(this)) return false; + + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + static Float:vecTargetFixed[3]; xs_vec_set(vecTargetFixed, vecTarget[0], vecTarget[1], floatmax(vecTarget[2], vecOrigin[2])); + + static iIgnoreEntSolidType; iIgnoreEntSolidType = SOLID_NOT; + if (pIgnoreEnt) { + iIgnoreEntSolidType = pev(pIgnoreEnt, pev_solid); + set_pev(pIgnoreEnt, pev_solid, SOLID_NOT); + } + + static bool:bIsReachable; bIsReachable = true; + + if (bIsReachable) { + bIsReachable = IsOpen(vecOrigin, vecTargetFixed, this); + } + + if (bIsReachable) { + static Float:vecMins[3]; pev(this, pev_mins, vecMins); + static Float:vecLeftSide[3]; xs_vec_set(vecLeftSide, vecOrigin[0] + vecMins[0], vecOrigin[1] + vecMins[1], vecOrigin[2]); + static Float:vecTargetLeftSide[3]; xs_vec_set(vecTargetLeftSide, vecTargetFixed[0] + vecMins[0], vecTargetFixed[1] + vecMins[1], vecTargetFixed[2]); + + bIsReachable = IsOpen(vecLeftSide, vecTargetLeftSide, this); + } + + if (bIsReachable) { + static Float:vecMaxs[3]; pev(this, pev_maxs, vecMaxs); + static Float:vecRightSide[3]; xs_vec_set(vecRightSide, vecOrigin[0] + vecMaxs[0], vecOrigin[1] + vecMaxs[1], vecOrigin[2]); + static Float:vecTargetRightSide[3]; xs_vec_set(vecTargetRightSide, vecTargetFixed[0] + vecMaxs[0], vecTargetFixed[1] + vecMaxs[1], vecTargetFixed[2]); + + bIsReachable = IsOpen(vecRightSide, vecTargetRightSide, this); + } + + if (pev(this, pev_movetype) != MOVETYPE_FLY) { + static Float:vecStepOrigin[3]; + + if (bIsReachable) { + static Float:flDistance; flDistance = get_distance_f(vecOrigin, vecTargetFixed); + static iStepsNum; iStepsNum = floatround(flDistance / flStepLength); + + if (iStepsNum) { + // Get direction vector + static Float:vecStep[3]; + xs_vec_sub(vecTargetFixed, vecOrigin, vecStep); + xs_vec_normalize(vecStep, vecStep); + xs_vec_mul_scalar(vecStep, flStepLength, vecStep); + + xs_vec_copy(vecOrigin, vecStepOrigin); + + static iStep; iStep = 0; + + do { + bIsReachable = @Entity_TestStep(this, vecStepOrigin, vecStep, vecStepOrigin); + iStep++; + } while (bIsReachable && iStep < iStepsNum); + } + } + + if (bIsReachable) { + bIsReachable = (vecTarget[2] - vecStepOrigin[2]) < 72.0; + } + } + + if (pIgnoreEnt) { + set_pev(pIgnoreEnt, pev_solid, iIgnoreEntSolidType); + } + + return bIsReachable; +} + +bool:@Entity_TestStep(this, const Float:vecOrigin[3], const Float:vecStep[3], Float:vecStepOrigin[3]) { + static Float:vecMins[3]; pev(this, pev_mins, vecMins); + static Float:flStepHeight; flStepHeight = CE_GetMember(this, m_flStepHeight); + + // Check wall + static Float:vecStepStart[3]; xs_vec_set(vecStepStart, vecOrigin[0], vecOrigin[1], vecOrigin[2] + vecMins[2] + flStepHeight); + static Float:vecStepEnd[3]; xs_vec_add(vecStepStart, vecStep, vecStepEnd); + + if (!IsOpen(vecStepStart, vecStepEnd, this)) return false; + + static Float:vecNextOrigin[3]; xs_vec_set(vecNextOrigin, vecStepEnd[0], vecStepEnd[1], vecStepEnd[2] - vecMins[2]); + + // Check if falling or solid + new Float:flDistanceToFloor = GetDistanceToFloor(this, vecNextOrigin); + if (flDistanceToFloor < 0.0) return false; + + vecNextOrigin[2] -= flDistanceToFloor; // apply possible height change + + xs_vec_copy(vecNextOrigin, vecStepOrigin); // copy result + + return true; +} + +@Entity_MoveForward(this) { + static Float:vecInput[3]; @Entity_GetDirectionVector(this, vecInput); + + if (!@Entity_IsFlying(this)) { + vecInput[2] = 0.0; + xs_vec_normalize(vecInput, vecInput); + } + + CE_SetMemberVec(this, m_vecInput, vecInput); +} + +@Entity_StopMovement(this) { + if (CE_HasMember(this, m_vecInput)) { + CE_SetMemberVec(this, m_vecInput, Float:{0.0, 0.0, 0.0}); + } +} + +@Entity_GetWeaponOrigin(this, Float:vecOut[3]) { + static Float:vecWeaponOffset[3]; CE_GetMemberVec(this, m_vecWeaponOffset, vecWeaponOffset); + + pev(this, pev_origin, vecOut); + xs_vec_add(vecOut, vecWeaponOffset, vecOut); +} + +bool:@Entity_IsFlying(this) { + static iMoveType; iMoveType = pev(this, pev_movetype); + + return (iMoveType == MOVETYPE_FLY || iMoveType == MOVETYPE_NOCLIP); +} + +bool:@Entity_IsInViewCone(this, const Float:vecTarget[3]) { + static Float:vecOrigin[3]; ExecuteHamB(Ham_EyePosition, this, vecOrigin); + static Float:flFOV; pev(this, pev_fov, flFOV); + + static Float:vecDirection[3]; + xs_vec_sub(vecTarget, vecOrigin, vecDirection); + xs_vec_normalize(vecDirection, vecDirection); + + static Float:vecForward[3]; + pev(this, pev_angles, vecForward); + angle_vector(vecForward, ANGLEVECTOR_FORWARD, vecForward); + + static Float:flAngle; flAngle = xs_rad2deg(xs_acos((vecDirection[0] * vecForward[0]) + (vecDirection[1] * vecForward[1]), radian)); + + return flAngle <= (flFOV / 2); +} + +Float:@Entity_GetPathCost(this, NavArea:nextArea, NavArea:prevArea) { + static NavAttributeType:iAttributes; iAttributes = Nav_Area_GetAttributes(nextArea); + + // NPCs can't jump or crouch + if (iAttributes & NAV_JUMP || iAttributes & NAV_CROUCH) return -1.0; + + // NPCs can't go ladders + if (prevArea != Invalid_NavArea) { + static NavTraverseType:iTraverseType; iTraverseType = Nav_Area_GetParentHow(prevArea); + if (iTraverseType == GO_LADDER_UP) return -1.0; + // if (iTraverseType == GO_LADDER_DOWN) return -1.0; + } + + static Float:flStepHeight; flStepHeight = CE_GetMember(this, m_flStepHeight); + + static Float:vecOrigin[3]; + + if (prevArea != Invalid_NavArea) { + Nav_Area_GetCenter(prevArea, vecOrigin); + } else { + pev(this, pev_origin, vecOrigin); + } + + vecOrigin[2] += flStepHeight; + + static Float:vecTarget[3]; + Nav_Area_GetCenter(nextArea, vecTarget); + vecTarget[2] = Nav_Area_GetZ(nextArea) + flStepHeight; + + engfunc(EngFunc_TraceLine, vecOrigin, vecTarget, IGNORE_MONSTERS, 0, g_pTrace); + + static pHit; pHit = get_tr2(g_pTrace, TR_pHit); + + // cancel if there is a wall + if (!pHit) return -1.0; + + // cancel path if there is a obstacle + if (pHit != -1 && !IS_PLAYER(pHit) && !IS_MONSTER(pHit)) return -1.0; + + static pTarget; pTarget = 0; + while ((pTarget = engfunc(EngFunc_FindEntityInSphere, pTarget, vecTarget, 4.0)) > 0) { + static szClassName[32]; pev(pTarget, pev_classname, szClassName, charsmax(szClassName)); + + // don't go through the hurt entities + if (equal(szClassName, "trigger_hurt")) return -1.0; + } + + return 1.0; +} + +@Entity_TurnTo(this, const Float:vecTarget[3]) { + static Float:flGameTime; flGameTime = get_gametime(); + static Float:flLastAIThink; flLastAIThink = CE_GetMember(this, m_flLastAIThink); + static Float:flDelta; flDelta = floatmin(flGameTime - flLastAIThink, 1.0); + static Float:flSpeed; flSpeed = 180.0 * flDelta; + static bool:rgbLockAxis[3]; rgbLockAxis = bool:{true, false, true}; + + static iMoveType; iMoveType = pev(this, pev_movetype); + if (iMoveType == MOVETYPE_FLY || iMoveType == MOVETYPE_NOCLIP) { + rgbLockAxis[0] = false; + } + + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + static Float:vecAngles[3]; pev(this, pev_angles, vecAngles); + + static Float:vecTargetAngles[3]; + xs_vec_sub(vecTarget, vecOrigin, vecTargetAngles); + engfunc(EngFunc_VecToAngles, vecTargetAngles, vecTargetAngles); + + for (new i = 0; i < 3; ++i) { + if (rgbLockAxis[i]) continue; + + if (flSpeed >= 0.0) { + vecAngles[i] = UTIL_ApproachAngle(vecTargetAngles[i], vecAngles[i], flSpeed); + } else { + vecAngles[i] = vecTargetAngles[i]; + } + } + + static Float:vecViewAngles[3]; + xs_vec_copy(vecAngles, vecViewAngles); + vecViewAngles[0] = -vecViewAngles[0]; + + set_pev(this, pev_angles, vecAngles); + set_pev(this, pev_v_angle, vecViewAngles); + set_pev(this, pev_ideal_yaw, vecAngles[1]); +} + +/*--------------------------------[ Function ]--------------------------------*/ + +bool:IsOpen(const Float:vecSrc[3], const Float:vecEnd[3], pIgnoreEnt = 0, iIgnoreFlags = IGNORE_MONSTERS) { + engfunc(EngFunc_TraceLine, vecSrc, vecEnd, iIgnoreFlags, pIgnoreEnt, g_pTrace); + + if (get_tr2(g_pTrace, TR_AllSolid)) return false; + if (get_tr2(g_pTrace, TR_StartSolid)) return false; + + static Float:flFraction; get_tr2(g_pTrace, TR_flFraction, flFraction); + if (flFraction < 1.0) return false; + + return true; +} + +Float:GetDistanceToFloor(pEntity, const Float:vecOrigin[3]) { + static Float:vecTarget[3]; xs_vec_set(vecTarget, vecOrigin[0], vecOrigin[1], -8192.0); + + engfunc(EngFunc_TraceMonsterHull, pEntity, vecOrigin, vecTarget, IGNORE_MONSTERS, pEntity, g_pTrace); + + static Float:flFraction; get_tr2(g_pTrace, TR_flFraction, flFraction); + if (flFraction == 1.0) return -1.0; + + static Float:vecEnd[3]; get_tr2(g_pTrace, TR_vecEndPos, vecEnd); + + return vecOrigin[2] - vecEnd[2]; +} + +IsInvisible(pEntity) { + if (!pev_valid(pEntity)) return true; + if (pev(pEntity, pev_rendermode) == kRenderNormal) return false; + + static Float:flRenderAmt; pev(pEntity, pev_renderamt, flRenderAmt); + + return (flRenderAmt < 50.0); +} + +/*--------------------------------[ Hooks ]--------------------------------*/ + +public HamHook_Base_TakeDamage_Post(pEntity, pInflictor, pAttacker, Float:flDamage, iDamageBits) { + if (CE_IsInstanceOf(pEntity, ENTITY_NAME)) { + CE_CallMethod(pEntity, TakeDamage, pInflictor, pAttacker, flDamage, iDamageBits); + return HAM_HANDLED; + } + + return HAM_IGNORED; +} + +/*--------------------------------[ Callbacks ]--------------------------------*/ + +public Float:NavPathCost(NavBuildPathTask:pTask, NavArea:newArea, NavArea:prevArea) { + static pEntity; pEntity = Nav_Path_FindTask_GetUserToken(pTask); + if (!pEntity) return 1.0; + + return CE_CallMethod(pEntity, GetPathCost, newArea, prevArea); +} + +public NavPathCallback(NavBuildPathTask:pTask) { + new pEntity = Nav_Path_FindTask_GetUserToken(pTask); + new NavPath:pPath = Nav_Path_FindTask_GetPath(pTask); + + return CE_CallMethod(pEntity, HandlePath, pPath); +} + +/*--------------------------------[ Stocks ]--------------------------------*/ + +stock bool:UTIL_SetSequence(pEntity, iSequence) { + if (pev(pEntity, pev_sequence) == iSequence) return false; + + set_pev(pEntity, pev_frame, 0); + set_pev(pEntity, pev_framerate, 1.0); + set_pev(pEntity, pev_animtime, get_gametime()); + set_pev(pEntity, pev_sequence, iSequence); + + return true; +} + +stock Float:UTIL_ApproachAngle(Float:flTarget, Float:flValue, Float:flSpeed) { + flTarget = UTIL_AngleMod(flTarget); + flValue = UTIL_AngleMod(flValue); + flSpeed = floatabs(flSpeed); + + static Float:flDelta; flDelta = flTarget - flValue; + + if (flDelta < -180.0) { + flDelta += 360.0; + } else if (flDelta > 180.0) { + flDelta -= 360.0; + } + + if (flDelta > flSpeed) { + flValue += flSpeed; + } else if (flDelta < -flSpeed) { + flValue -= flSpeed; + } else { + flValue = flTarget; + } + + return flValue; +} + +stock Float:UTIL_AngleMod(Float:flAngle) { + return (360.0/65536) * (floatround(flAngle * (65536.0/360.0), floatround_floor) & 65535); +} + +stock bool:UTIL_CheckEntitiesLevel(pEntity, pOther) { + static Float:vecAbsMin[3]; pev(pEntity, pev_absmin, vecAbsMin); + static Float:vecAbsMax[3]; pev(pEntity, pev_absmax, vecAbsMax); + static Float:vecOtherAbsMin[3]; pev(pOther, pev_absmin, vecOtherAbsMin); + static Float:vecOtherAbsMax[3]; pev(pOther, pev_absmax, vecOtherAbsMax); + + if (vecAbsMax[2] < vecOtherAbsMin[2]) return false; + if (vecAbsMin[2] > vecOtherAbsMax[2]) return false; + + return true; +} \ No newline at end of file diff --git a/entities/entity_custom_events_handler.sma b/entities/entity_custom_events_handler.sma new file mode 100644 index 0000000..0aaddda --- /dev/null +++ b/entities/entity_custom_events_handler.sma @@ -0,0 +1,81 @@ +#pragma semicolon 1 + +#include +#include +#include +#include + +#include +#include + +#define PLUGIN "[Entity] Custom Events Handler" +#define VERSION "1.0.0" +#define AUTHOR "Hedgehog Fog" + +#define m_pActivator "pActivator" +#define m_szEvent "szEvent" +#define m_szTarget "szTarget" + +#define ENTITY_NAME "custom_events_handler" + +new Array:g_irgpEntities; + +public plugin_precache() { + g_irgpEntities = ArrayCreate(); + + CE_Register(ENTITY_NAME); + CE_RegisterHook(ENTITY_NAME, CEFunction_Init, "@Entity_Init"); + CE_RegisterHook(ENTITY_NAME, CEFunction_KeyValue, "@Entity_KeyValue"); + CE_RegisterHook(ENTITY_NAME, CEFunction_Think, "@Entity_Think"); + CE_RegisterHook(ENTITY_NAME, CEFunction_Remove, "@Entity_Remove"); +} + +public plugin_init() { + register_plugin(PLUGIN, VERSION, AUTHOR); +} + +public plugin_end() { + ArrayDestroy(g_irgpEntities); +} + +public CustomEvent_Fw_Emit(const szEvent[], pActivator) { + new iSize = ArraySize(g_irgpEntities); + for (new iGlobalId = 0; iGlobalId < iSize; ++iGlobalId) { + static pEntity; pEntity = ArrayGetCell(g_irgpEntities, iGlobalId); + + static szEntityEvent[64]; CE_GetMemberString(pEntity, m_szEvent, szEntityEvent, charsmax(szEntityEvent)); + if (!equal(szEntityEvent, szEvent)) continue; + + CE_SetMember(pEntity, m_pActivator, pActivator); + dllfunc(DLLFunc_Think, pEntity); + } +} + +@Entity_KeyValue(this, const szKey[], const szValue[]) { + if (equal(szKey, "event")) { + CE_SetMemberString(this, m_szEvent, szValue); + } else if (equal(szKey, "target")) { + CE_SetMemberString(this, m_szTarget, szValue); + } +} + +@Entity_Init(this) { + ArrayPushCell(g_irgpEntities, this); +} + +@Entity_Remove(this) { + new iGlobalIndex = ArrayFindValue(g_irgpEntities, this); + if (iGlobalIndex != -1) { + ArrayDeleteItem(g_irgpEntities, iGlobalIndex); + } +} + +@Entity_Think(this) { + static pActivator; pActivator = CE_GetMember(this, m_pActivator); + static szTarget[64]; CE_GetMemberString(this, m_szTarget, szTarget, charsmax(szTarget)); + + new pTarget = 0; + while ((pTarget = engfunc(EngFunc_FindEntityByString, pTarget, "targetname", szTarget)) != 0) { + ExecuteHamB(Ham_Use, pTarget, pActivator, this, 2, 1.0); + } +} diff --git a/entities/entity_fire.sma b/entities/entity_fire.sma new file mode 100644 index 0000000..c2b435b --- /dev/null +++ b/entities/entity_fire.sma @@ -0,0 +1,601 @@ +#pragma semicolon 1 + +#include +#include +#include +#include +#include + +#include + +#define PLUGIN "[Entity] Fire" +#define VERSION "1.0.0" +#define AUTHOR "Hedgehog Fog" + +#define IS_PLAYER(%1) (%1 >= 1 && %1 <= MaxClients) + +#define ENTITY_NAME "fire" + +#define FIRE_BORDERS 2.0 +#define FIRE_PADDING (FIRE_BORDERS + 16.0) +#define FIRE_THINK_RATE 0.01 +#define FIRE_DAMAGE_RATE 0.5 +#define FIRE_WATER_CHECK_RATE 1.0 +#define FIRE_SPREAD_THINK_RATE 1.0 +#define FIRE_PARTICLES_EFFECT_RATE 0.025 +#define FIRE_LIGHT_EFFECT_RATE 0.05 +#define FIRE_SOUND_RATE 1.0 +#define FIRE_SIZE_UPDATE_RATE 1.0 + +#define m_flNextParticlesEffect "flNextParticlesEffect" +#define m_flNextLightEffect "flNextLightEffect" +#define m_flNextSound "flNextSound" +#define m_flNextDamage "flNextDamage" +#define m_flNextSizeUpdate "flNextSizeUpdate" +#define m_flNextWaterCheck "flNextWaterCheck" +#define m_flNextSpreadThink "flNextSpreadThink" +#define m_flDamage "flDamage" +#define m_vecEffectOrigin "vecEffectOrigin" +#define m_bAllowSpread "bAllowSpread" +#define m_bDamaged "bDamaged" +#define m_flSpreadRange "flSpreadRange" +#define m_flChildrenLifeTime "flChildrenLifeTime" + +new g_rgszFlameSprites[][] = { + "sprites/bexplo.spr", + "sprites/cexplo.spr" +}; + +new const g_rgszSmokeSprites[][] = { + "sprites/black_smoke1.spr", + "sprites/black_smoke2.spr", + "sprites/black_smoke3.spr", + "sprites/black_smoke4.spr" +}; + +new const g_rgszBurningSounds[][] = { + "ambience/burning1.wav", + "ambience/burning2.wav", + "ambience/burning3.wav" +}; + +new g_rgiFlameModelIndex[sizeof(g_rgszFlameSprites)]; +new g_rgiFlameModelFramesNum[sizeof(g_rgszFlameSprites)]; +new g_rgiSmokeModelIndex[sizeof(g_rgszSmokeSprites)]; +new g_rgiSmokeModelFramesNum[sizeof(g_rgszSmokeSprites)]; +new Array:g_irgFireEntities; + +new g_pCvarDamage; +new g_pCvarSpread; +new g_pCvarSpreadRange; +new g_pCvarLifeTime; + +new CE:g_iCeHandler; + +public plugin_precache() { + g_irgFireEntities = ArrayCreate(); + + for (new i = 0; i < sizeof(g_rgszFlameSprites); ++i) { + g_rgiFlameModelIndex[i] = precache_model(g_rgszFlameSprites[i]); + g_rgiFlameModelFramesNum[i] = engfunc(EngFunc_ModelFrames, g_rgiFlameModelIndex[i]); + } + + for (new i = 0; i < sizeof(g_rgszSmokeSprites); ++i) { + g_rgiSmokeModelIndex[i] = precache_model(g_rgszSmokeSprites[i]); + g_rgiSmokeModelFramesNum[i] = engfunc(EngFunc_ModelFrames, g_rgiSmokeModelIndex[i]); + } + + for (new i = 0; i < sizeof(g_rgszBurningSounds); ++i) { + precache_sound(g_rgszBurningSounds[i]); + } + + g_iCeHandler = CE_Register(ENTITY_NAME); + CE_RegisterHook(ENTITY_NAME, CEFunction_Init, "@Entity_Init"); + CE_RegisterHook(ENTITY_NAME, CEFunction_Spawned, "@Entity_Spawned"); + CE_RegisterHook(ENTITY_NAME, CEFunction_Touch, "@Entity_Touch"); + CE_RegisterHook(ENTITY_NAME, CEFunction_Think, "@Entity_Think"); + CE_RegisterHook(ENTITY_NAME, CEFunction_Killed, "@Entity_Killed"); + CE_RegisterHook(ENTITY_NAME, CEFunction_Remove, "@Entity_Remove"); + + CE_RegisterKeyMemberBinding(ENTITY_NAME, "damage", m_flDamage, CEMemberType_Float); + CE_RegisterKeyMemberBinding(ENTITY_NAME, "lifetime", m_flChildrenLifeTime, CEMemberType_Float); + CE_RegisterKeyMemberBinding(ENTITY_NAME, "range", m_flSpreadRange, CEMemberType_Float); + CE_RegisterKeyMemberBinding(ENTITY_NAME, "spread", m_bAllowSpread, CEMemberType_Cell); + + register_forward(FM_OnFreeEntPrivateData, "FMHook_OnFreeEntPrivateData"); +} + +public plugin_init() { + register_plugin(PLUGIN, VERSION, AUTHOR); + + g_pCvarDamage = register_cvar("fire_damage", "5.0"); + g_pCvarSpread = register_cvar("fire_spread", "1"); + g_pCvarSpreadRange = register_cvar("fire_spread_range", "16.0"); + g_pCvarLifeTime = register_cvar("fire_life_time", "10.0"); +} + +public plugin_end() { + ArrayDestroy(g_irgFireEntities); +} + +@Entity_Init(this) { + CE_SetMemberVec(this, CE_MEMBER_MINS, Float:{-16.0, -16.0, -16.0}); + CE_SetMemberVec(this, CE_MEMBER_MAXS, Float:{16.0, 16.0, 16.0}); + + CE_SetMemberVec(this, m_vecEffectOrigin, NULL_VECTOR); + + if (!CE_HasMember(this, m_flDamage)) { + CE_SetMember(this, m_flDamage, get_pcvar_float(g_pCvarDamage)); + } + + if (!CE_HasMember(this, m_flChildrenLifeTime)) { + CE_SetMember(this, m_flChildrenLifeTime, get_pcvar_float(g_pCvarLifeTime)); + } + + if (!CE_HasMember(this, m_flSpreadRange)) { + CE_SetMember(this, m_flSpreadRange, get_pcvar_float(g_pCvarSpreadRange)); + } + + if (!CE_HasMember(this, m_bAllowSpread)) { + CE_SetMember(this, m_bAllowSpread, false); + } + + ArrayPushCell(g_irgFireEntities, this); +} + +@Entity_Spawned(this) { + new Float:flGameTime = get_gametime(); + + CE_SetMember(this, m_flNextParticlesEffect, flGameTime); + CE_SetMember(this, m_flNextLightEffect, flGameTime); + CE_SetMember(this, m_flNextSound, flGameTime); + CE_SetMember(this, m_flNextDamage, flGameTime); + CE_SetMember(this, m_flNextSizeUpdate, flGameTime); + CE_SetMember(this, m_flNextWaterCheck, flGameTime); + CE_SetMember(this, m_flNextSpreadThink, flGameTime); + CE_SetMember(this, m_flDamage, Float:CE_GetMember(this, m_flDamage)); + CE_SetMember(this, m_bDamaged, false); + + set_pev(this, pev_takedamage, DAMAGE_NO); + set_pev(this, pev_solid, SOLID_TRIGGER); + set_pev(this, pev_movetype, MOVETYPE_TOSS); + + set_pev(this, pev_nextthink, flGameTime); + + // Limited lifetime for the real-time spawned entity + if (!CE_GetMember(this, CE_MEMBER_WORLD)) { + CE_SetMember(this, CE_MEMBER_NEXTKILL, flGameTime + get_pcvar_float(g_pCvarLifeTime)); + } +} + +@Entity_Killed(this) { + @Entity_StopSound(this); +} + +@Entity_Remove(this) { + @Entity_StopSound(this); + + new iIndex = ArrayFindValue(g_irgFireEntities, this); + if (iIndex != -1) { + ArrayDeleteItem(g_irgFireEntities, iIndex); + } +} + +@Entity_Touch(this, pToucher) { + @Entity_Damage(this, pToucher); +} + +@Entity_Think(this) { + static Float:flGameTime; flGameTime = get_gametime(); + static iMoveType; iMoveType = pev(this, pev_movetype); + static pAimEnt; pAimEnt = pev(this, pev_aiment); + + if (iMoveType == MOVETYPE_FOLLOW) { + if (!pev_valid(pAimEnt) || pev(pAimEnt, pev_flags) & FL_KILLME || pev(pAimEnt, pev_deadflag) != DEAD_NO) { + CE_Kill(this); + return; + } + } + + if (CE_GetMember(this, m_flNextWaterCheck) <= flGameTime) { + if (@Entity_InWater(this)) { + CE_Kill(this); + return; + } + + CE_SetMember(this, m_flNextWaterCheck, flGameTime + FIRE_WATER_CHECK_RATE); + } + + if (CE_GetMember(this, m_flNextSpreadThink) <= flGameTime) { + @Entity_SpreadThink(this); + CE_SetMember(this, m_flNextSpreadThink, flGameTime + FIRE_SPREAD_THINK_RATE); + } + + /* + Since all non-moving entities, except players, don't handle touch, + we force a touch event the for burning entity. + */ + if (iMoveType == MOVETYPE_FOLLOW && !IS_PLAYER(pAimEnt)) { + static Float:vecVelocity[3]; pev(pAimEnt, pev_velocity, vecVelocity); + if (!vector_length(vecVelocity)) { + dllfunc(DLLFunc_Touch, this, pAimEnt); + } + } + + /* + After fire has damaged to all entities we add delay before fire can deal damage to touched entities again. + By using m_bDamaged, we avoid the problems with issue when m_flNextDamage updates before the touch. + */ + if (CE_GetMember(this, m_bDamaged)) { + static Float:flNextDamage; flNextDamage = CE_GetMember(this, m_flNextDamage); + if (flNextDamage && flNextDamage <= flGameTime) { + CE_SetMember(this, m_flNextDamage, flGameTime + FIRE_DAMAGE_RATE); + } + + CE_SetMember(this, m_bDamaged, false); + } + + if (CE_GetMember(this, m_flNextSound) <= flGameTime) { + @Entity_Sound(this); + CE_SetMember(this, m_flNextSound, flGameTime + FIRE_SOUND_RATE); + } + + if (CE_GetMember(this, m_flNextSizeUpdate) <= flGameTime) { + @Entity_UpdateSize(this); + CE_SetMember(this, m_flNextSizeUpdate, flGameTime + FIRE_SIZE_UPDATE_RATE); + } + + if (CE_GetMember(this, m_flNextParticlesEffect) <= flGameTime) { + // Particle effect has higher update rate, so we update effect vars before each particle effect + @Entity_UpdateEffectVars(this); + @Entity_ParticlesEffect(this); + CE_SetMember(this, m_flNextParticlesEffect, flGameTime + FIRE_PARTICLES_EFFECT_RATE); + } + + if (CE_GetMember(this, m_flNextLightEffect) <= flGameTime) { + @Entity_LightEffect(this); + CE_SetMember(this, m_flNextLightEffect, flGameTime + FIRE_LIGHT_EFFECT_RATE); + } + + if (iMoveType == MOVETYPE_FOLLOW) { + static Float:vecVelocity[3]; + pev(pAimEnt, pev_velocity, vecVelocity); + set_pev(this, pev_velocity, vecVelocity); + } + + set_pev(this, pev_nextthink, flGameTime + FIRE_THINK_RATE); +} + +@Entity_SpreadThink(this) { + if (!@Entity_CanSpread(this)) return; + + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + new Float:flRange = CE_GetMember(this, m_flSpreadRange); + + if (pev(this, pev_movetype) == MOVETYPE_FOLLOW) { + new pAimEnt = pev(this, pev_aiment); + static Float:vecMins[3]; pev(pAimEnt, pev_mins, vecMins); + static Float:vecMaxs[3]; pev(pAimEnt, pev_maxs, vecMaxs); + + flRange = floatmax( + vecMaxs[2] - vecMins[2], + floatmax(vecMaxs[0] - vecMins[0], vecMaxs[1] - vecMins[1]) + ) / 2; + } + + new Array:irgNearbyEntities = ArrayCreate(); + + new pTarget = 0; + while ((pTarget = engfunc(EngFunc_FindEntityInSphere, pTarget, vecOrigin, flRange)) > 0) { + if (pev(pTarget, pev_takedamage) == DAMAGE_NO) { + continue; + } + + ArrayPushCell(irgNearbyEntities, pTarget); + } + + new iNearbyEntitiesNum = ArraySize(irgNearbyEntities); + for (new i = 0; i < iNearbyEntitiesNum; ++i) { + new pTarget = ArrayGetCell(irgNearbyEntities, i); + @Entity_Spread(this, pTarget); + } + + ArrayDestroy(irgNearbyEntities); +} + +@Entity_UpdateSize(this) { + if (pev(this, pev_movetype) != MOVETYPE_FOLLOW) return; + + static pAimEnt; pAimEnt = pev(this, pev_aiment); + + static szModel[256]; pev(pAimEnt, pev_model, szModel, charsmax(szModel)); + static iModelStrLen; iModelStrLen = strlen(szModel); + static bool:bHasModel; bHasModel = !!iModelStrLen; + static bool:bIsBspModel; bIsBspModel = bHasModel && szModel[0] == '*'; + static bool:bIsSprite; bIsSprite = iModelStrLen > 5 && equal(szModel[iModelStrLen - 5], ".spr"); + + static Float:vecMins[3]; xs_vec_set(vecMins, 0.0, 0.0, 0.0); + static Float:vecMaxs[3]; xs_vec_set(vecMaxs, 0.0, 0.0, 0.0); + + if (bHasModel && bIsBspModel && bIsSprite) { + GetModelBoundingBox(pAimEnt, vecMins, vecMaxs, Model_CurrentSequence); + } + + if (!xs_vec_distance(vecMins, vecMaxs)) { + pev(pAimEnt, pev_mins, vecMins); + pev(pAimEnt, pev_maxs, vecMaxs); + } + + // Add fire borders (useful for fire spread) + for (new i = 0; i < 3; ++i) { + vecMins[i] -= FIRE_BORDERS; + vecMaxs[i] += FIRE_BORDERS; + } + + engfunc(EngFunc_SetSize, this, vecMins, vecMaxs); +} + +bool:@Entity_CanSpread(this) { + if (!get_pcvar_bool(g_pCvarSpread)) return false; + if (!CE_GetMember(this, m_bAllowSpread)) return false; + + return true; +} + +@Entity_InWater(this) { + static Float:vecOrigin[3]; + pev(this, pev_origin, vecOrigin); + + new pTarget = 0; + while ((pTarget = engfunc(EngFunc_FindEntityInSphere, pTarget, vecOrigin, 1.0)) > 0) { + static szTargetClassName[32]; + pev(pTarget, pev_classname, szTargetClassName, charsmax(szTargetClassName)); + + if (equal(szTargetClassName, "func_water")) return true; + } + + return false; +} + +bool:@Entity_Damage(this, pTarget) { + if (!pTarget) return false; + if (pev(pTarget, pev_takedamage) == DAMAGE_NO) return false; + // if (pev(pTarget, pev_solid) <= SOLID_TRIGGER) return false; + + static Float:flGameTime; flGameTime = get_gametime(); + static Float:flNextDamage; flNextDamage = CE_GetMember(this, m_flNextDamage); + + if (flNextDamage > flGameTime) return false; + + static Float:flDamage; flDamage = Float:CE_GetMember(this, m_flDamage) * FIRE_DAMAGE_RATE; + if (flDamage) { + static pOwner; pOwner = pev(this, pev_owner); + static pAttacker; pAttacker = pOwner && pOwner != pTarget ? pOwner : this; + static iDamageBits; iDamageBits = DMG_NEVERGIB | DMG_BURN; + + if (cstrike_running() && IS_PLAYER(pTarget)) { + new Float:flVelocityModifier = get_ent_data_float(pTarget, "CBasePlayer", "m_flVelocityModifier"); + ExecuteHamB(Ham_TakeDamage, pTarget, this, pAttacker, flDamage, iDamageBits); + set_ent_data_float(pTarget, "CBasePlayer", "m_flVelocityModifier", flVelocityModifier); + } else { + ExecuteHamB(Ham_TakeDamage, pTarget, this, pAttacker, flDamage, iDamageBits); + } + } + + // if (pev(this, pev_movetype) != MOVETYPE_FOLLOW) { + // if (@Entity_CanIgnite(this, pTarget)) { + // // Attach fire to the entity we damaged + // set_pev(this, pev_movetype, MOVETYPE_FOLLOW); + // set_pev(this, pev_aiment, pTarget); + // } + // } + + if (@Entity_CanSpread(this)) { + @Entity_Spread(this, pTarget); + } + + CE_SetMember(this, m_bDamaged, true); + + return true; +} + +@Entity_Spread(this, pTarget) { + if (!@Entity_CanIgnite(this, pTarget)) return 0; + + new pChild = @Entity_CreateChild(this); + if (!pChild) return 0; + + set_pev(pChild, pev_aiment, pTarget); + set_pev(pChild, pev_movetype, MOVETYPE_FOLLOW); + + return pChild; +} + +@Entity_CreateChild(this) { + static Float:vecOrigin[3]; pev(this, pev_origin, vecOrigin); + + new pChild = CE_Create(ENTITY_NAME, vecOrigin); + if (!pChild) return 0; + + dllfunc(DLLFunc_Spawn, pChild); + + new Float:flLifeTime = CE_GetMember(this, m_flChildrenLifeTime); + + CE_SetMember(pChild, m_flDamage, Float:CE_GetMember(this, m_flDamage)); + CE_SetMember(pChild, m_bAllowSpread, CE_GetMember(this, m_bAllowSpread)); + CE_SetMember(pChild, m_flSpreadRange, Float:CE_GetMember(this, m_flSpreadRange)); + CE_SetMember(pChild, m_flChildrenLifeTime, flLifeTime); + CE_SetMember(pChild, CE_MEMBER_NEXTKILL, get_gametime() + flLifeTime); + + new pOwner = pev(this, pev_owner); + set_pev(pChild, pev_owner, pOwner); + + return pChild; +} + +@Entity_CanIgnite(this, pTarget) { + if (pev(pTarget, pev_takedamage) == DAMAGE_NO) return false; + if (pev(pTarget, pev_deadflag) != DEAD_NO) return false; + + // Fire entity cannot be ignited + if (CE_GetHandlerByEntity(pTarget) == g_iCeHandler) return false; + + static iMoveType; iMoveType = pev(this, pev_movetype); + static pAimEnt; pAimEnt = pev(this, pev_aiment); + if (iMoveType == MOVETYPE_FOLLOW && pAimEnt == pTarget) return false; + + if (@Base_IsOnFire(pTarget)) return false; + + return true; +} + +@Entity_Sound(this) { + emit_sound(this, CHAN_BODY, g_rgszBurningSounds[random(sizeof(g_rgszBurningSounds))], VOL_NORM, ATTN_NORM, 0, PITCH_NORM); +} + +@Entity_StopSound(this) { + emit_sound(this, CHAN_BODY, "common/null.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM); +} + +@Entity_UpdateEffectVars(this) { + static Float:vecAbsMin[3]; + pev(this, pev_absmin, vecAbsMin); + + static Float:vecAbsMax[3]; + pev(this, pev_absmax, vecAbsMax); + + static Float:vecVelocity[3]; + pev(this, pev_velocity, vecVelocity); + + static Float:vecOrigin[3]; + for (new i = 0; i < sizeof(vecOrigin); ++i) { + vecOrigin[i] = ( + random_float( + floatmin(vecAbsMin[i] + FIRE_PADDING, vecAbsMax[i]), + floatmax(vecAbsMax[i] - FIRE_PADDING, vecAbsMin[i]) + ) + (vecVelocity[i] * FIRE_THINK_RATE) + ); + } + + CE_SetMemberVec(this, m_vecEffectOrigin, vecOrigin); +} + +@Entity_ParticlesEffect(this) { + static Float:vecOrigin[3]; CE_GetMemberVec(this, m_vecEffectOrigin, vecOrigin); + + static Float:vecMins[3]; pev(this, pev_absmin, vecMins); + static Float:vecMaxs[3]; pev(this, pev_absmax, vecMaxs); + + static Float:flAvgSize; flAvgSize = ( + ((vecMaxs[0] - FIRE_PADDING) - (vecMins[0] + FIRE_PADDING)) + + ((vecMaxs[1] - FIRE_PADDING) - (vecMins[1] + FIRE_PADDING)) + + ((vecMaxs[2] - FIRE_PADDING) - (vecMins[2] + FIRE_PADDING)) + ) / 3; + + static iScale; iScale = clamp(floatround(flAvgSize * random_float(0.0975, 0.275)), 4, 80); + + static iSmokeIndex; iSmokeIndex = random(sizeof(g_rgiFlameModelIndex)); + static iSmokeFrameRate; iSmokeFrameRate = floatround( + g_rgiSmokeModelFramesNum[iSmokeIndex] * random_float(0.75, 1.25), + floatround_ceil + ); + + static iFlameIndex; iFlameIndex = random(sizeof(g_rgiFlameModelIndex)); + static iFlameFrameRate; iFlameFrameRate = floatround( + g_rgiFlameModelFramesNum[iFlameIndex] * random_float(2.975, 3.125), + floatround_ceil + ); + + engfunc(EngFunc_MessageBegin, MSG_PAS, SVC_TEMPENTITY, vecOrigin, 0); + write_byte(TE_EXPLOSION); + engfunc(EngFunc_WriteCoord, vecOrigin[0]); + engfunc(EngFunc_WriteCoord, vecOrigin[1]); + engfunc(EngFunc_WriteCoord, vecOrigin[2]); + write_short(g_rgiFlameModelIndex[iFlameIndex]); + write_byte(iScale); + write_byte(iFlameFrameRate); + write_byte(TE_EXPLFLAG_NODLIGHTS | TE_EXPLFLAG_NOSOUND | TE_EXPLFLAG_NOPARTICLES); + message_end(); + + engfunc(EngFunc_MessageBegin, MSG_PAS, SVC_TEMPENTITY, vecOrigin, 0); + write_byte(TE_SMOKE); + engfunc(EngFunc_WriteCoord, vecOrigin[0]); + engfunc(EngFunc_WriteCoord, vecOrigin[1]); + engfunc(EngFunc_WriteCoord, vecOrigin[2]); + write_short(g_rgiSmokeModelIndex[iSmokeIndex]); + write_byte(iScale * 2); + write_byte(iSmokeFrameRate); + message_end(); +} + +@Entity_LightEffect(this) { + static const irgColor[3] = {128, 64, 0}; + static Float:vecOrigin[3]; CE_GetMemberVec(this, m_vecEffectOrigin, vecOrigin); + static Float:vecMins[3]; pev(this, pev_absmin, vecMins); + static Float:vecMaxs[3]; pev(this, pev_absmax, vecMaxs); + static iLifeTime; iLifeTime = 1; + + static Float:flRadius; flRadius = 0.25 * floatmax( + vecMaxs[2] - vecMins[2], + floatmax(vecMaxs[0] - vecMins[0], vecMaxs[1] - vecMins[1]) + ) / 2; + + static iDecayRate; iDecayRate = floatround(flRadius); + + engfunc(EngFunc_MessageBegin, MSG_PVS, SVC_TEMPENTITY, vecOrigin, 0); + write_byte(TE_ELIGHT); + write_short(0); + engfunc(EngFunc_WriteCoord, vecOrigin[0]); + engfunc(EngFunc_WriteCoord, vecOrigin[1]); + engfunc(EngFunc_WriteCoord, vecOrigin[2]); + engfunc(EngFunc_WriteCoord, flRadius); + write_byte(irgColor[0]); + write_byte(irgColor[1]); + write_byte(irgColor[2]); + write_byte(iLifeTime); + write_coord(iDecayRate); + message_end(); + + engfunc(EngFunc_MessageBegin, MSG_PVS, SVC_TEMPENTITY, vecOrigin, 0); + write_byte(TE_DLIGHT); + engfunc(EngFunc_WriteCoord, vecOrigin[0]); + engfunc(EngFunc_WriteCoord, vecOrigin[1]); + engfunc(EngFunc_WriteCoord, vecOrigin[2]); + write_byte(floatround(flRadius)); + write_byte(irgColor[0]); + write_byte(irgColor[1]); + write_byte(irgColor[2]); + write_byte(iLifeTime); + write_byte(iDecayRate); + message_end(); +} + +bool:@Base_IsOnFire(this) { + new iSize = ArraySize(g_irgFireEntities); + + for (new i = 0; i < iSize; ++i) { + new pFire = ArrayGetCell(g_irgFireEntities, i); + + if (pev(pFire, pev_movetype) == MOVETYPE_FOLLOW && pev(pFire, pev_aiment) == this) { + return true; + } + } + + return false; +} + +@Base_Extinguish(this) { + new iSize = ArraySize(g_irgFireEntities); + + for (new i = 0; i < iSize; ++i) { + new pFire = ArrayGetCell(g_irgFireEntities, i); + + if (pev(pFire, pev_movetype) == MOVETYPE_FOLLOW && pev(pFire, pev_aiment) == this) { + CE_Kill(pFire); + } + } +} + +public FMHook_OnFreeEntPrivateData(pEntity) { + @Base_Extinguish(pEntity); +} diff --git a/entities/include/entity_base_monster_const.inc b/entities/include/entity_base_monster_const.inc new file mode 100644 index 0000000..617d6af --- /dev/null +++ b/entities/include/entity_base_monster_const.inc @@ -0,0 +1,507 @@ +#if defined _entity_monster_base_const_included + #endinput +#endif +#define _entity_monster_base_const_included + +#include + +#define BASE_MONSTER_ENTITY_NAME "monster_base" + +#define MAX_MONSTER_SCHED_TASKS 32 + +enum MONSTER_TASK_DATA { + MONSTER_TASK_DATA_ID, + any:MONSTER_TASK_DATA_DATA +}; + +enum MONSTER_SCHEDULE_DATA { + MONSTER_SCHEDULE_DATA_SHARED_ID, + MONSTER_SCHEDULE_DATA_TASK[_:MONSTER_TASK_DATA * MAX_MONSTER_SCHED_TASKS], + MONSTER_SCHEDULE_DATA_TASK_SIZE, + MONSTER_SCHEDULE_DATA_INTERRUPT_MASK,// a bit mask of conditions that can interrupt this schedule + MONSTER_SCHEDULE_DATA_SOUND_MASK // a more specific mask that indicates which TYPES of sounds will interrupt the schedule in the event that the schedule is broken by COND_HEAR_SOUND + // MONSTER_SCHEDULE_NAME[32] +}; + +enum MONSTER_STATE { + MONSTER_STATE_NONE, + MONSTER_STATE_IDLE, + MONSTER_STATE_COMBAT, + MONSTER_STATE_ALERT, + MONSTER_STATE_HUNT, + MONSTER_STATE_PRONE, + MONSTER_STATE_SCRIPT, + MONSTER_STATE_PLAYDEAD, + MONSTER_STATE_DEAD +}; + +enum MONSTER_WAYPOINT { + Float:MONSTER_WAYPOINT_LOCATION[3], + MONSTER_WAYPOINT_TYPE +} + +enum MONSTER_ENEMY { + MONSTER_ENEMY_ENTITY, + Float:MONSTER_ENEMY_LOCATION[3] +}; + +enum MONSTER_TARGET_MOVE { + MONSTER_TARGET_MOVE_NORMAL, + MONSTER_TARGET_MOVE_SCRIPTED +}; + +enum MONSTER_TASK_STATUS { + MONSTER_TASK_STATUS_NEW, // Just started + MONSTER_TASK_STATUS_RUNNING, // Running task & movement + MONSTER_TASK_STATUS_RUNNING_MOVEMENT, // Just running movement + MONSTER_TASK_STATUS_RUNNING_TASK, // Just running task + MONSTER_TASK_STATUS_COMPLETE // Completed, get next task +}; + +enum MONSTER_LOCALMOVE { + MONSTER_LOCALMOVE_INVALID, + MONSTER_LOCALMOVE_INVALID_DONT_TRIANGULATE, // move is not possible, don't try to triangulate + MONSTER_LOCALMOVE_VALID // move is possible +}; + +enum MONSTER_SCHEDULE_TYPE { + MONSTER_SCHED_NONE = 0, + MONSTER_SCHED_IDLE_STAND, + MONSTER_SCHED_IDLE_WALK, + MONSTER_SCHED_WAKE_ANGRY, + MONSTER_SCHED_WAKE_CALLED, + MONSTER_SCHED_ALERT_FACE, + MONSTER_SCHED_ALERT_SMALL_FLINCH, + MONSTER_SCHED_ALERT_BIG_FLINCH, + MONSTER_SCHED_ALERT_STAND, + MONSTER_SCHED_INVESTIGATE_SOUND, + MONSTER_SCHED_COMBAT_FACE, + MONSTER_SCHED_COMBAT_STAND, + MONSTER_SCHED_CHASE_ENEMY, + MONSTER_SCHED_CHASE_ENEMY_FAILED, + MONSTER_SCHED_VICTORY_DANCE, + MONSTER_SCHED_TARGET_FACE, + MONSTER_SCHED_TARGET_CHASE, + MONSTER_SCHED_SMALL_FLINCH, + MONSTER_SCHED_TAKE_COVER_FROM_ENEMY, + MONSTER_SCHED_TAKE_COVER_FROM_BEST_SOUND, + MONSTER_SCHED_TAKE_COVER_FROM_ORIGIN, + MONSTER_SCHED_COWER, // usually a last resort! + MONSTER_SCHED_MELEE_ATTACK1, + MONSTER_SCHED_MELEE_ATTACK2, + MONSTER_SCHED_RANGE_ATTACK1, + MONSTER_SCHED_RANGE_ATTACK2, + MONSTER_SCHED_SPECIAL_ATTACK1, + MONSTER_SCHED_SPECIAL_ATTACK2, + MONSTER_SCHED_STANDOFF, + MONSTER_SCHED_ARM_WEAPON, + MONSTER_SCHED_RELOAD, + MONSTER_SCHED_GUARD, + MONSTER_SCHED_AMBUSH, + MONSTER_SCHED_DIE, + MONSTER_SCHED_WAIT_TRIGGER, + MONSTER_SCHED_FOLLOW, + MONSTER_SCHED_SLEEP, + MONSTER_SCHED_WAKE, + MONSTER_SCHED_BARNACLE_VICTIM_GRAB, + MONSTER_SCHED_BARNACLE_VICTIM_CHOMP, + MONSTER_SCHED_AISCRIPT, + MONSTER_SCHED_FAIL, + + MONSTER_LAST_COMMON_SCHEDULE // Leave this at the bottom +}; + +enum MONSTER_SHARED_SCHED { + MONSTER_SHARED_SCHED_INVALID = -1, + MONSTER_SHARED_SCHED_ACTIVE_IDLE, + MONSTER_SHARED_SCHED_IDLE_STAND, + MONSTER_SHARED_SCHED_IDLE_WALK, + MONSTER_SHARED_SCHED_WAKE_ANGRY, + MONSTER_SHARED_SCHED_ALERT_FACE, + MONSTER_SHARED_SCHED_ALERT_SMALL_FLINCH, + MONSTER_SHARED_SCHED_ALERT_STAND, + MONSTER_SHARED_SCHED_INVESTIGATE_SOUND, + MONSTER_SHARED_SCHED_COMBAT_FACE, + MONSTER_SHARED_SCHED_COMBAT_STAND, + MONSTER_SHARED_SCHED_CHASE_ENEMY, + MONSTER_SHARED_SCHED_CHASE_ENEMY_FAILED, + MONSTER_SHARED_SCHED_VICTORY_DANCE, + MONSTER_SHARED_SCHED_SMALL_FLINCH, + MONSTER_SHARED_SCHED_TAKE_COVER_FROM_ENEMY, + MONSTER_SHARED_SCHED_TAKE_COVER_FROM_BEST_SOUND, + MONSTER_SHARED_SCHED_TAKE_COVER_FROM_ORIGIN, + MONSTER_SHARED_SCHED_COWER, + MONSTER_SHARED_SCHED_MELEE_ATTACK1, + MONSTER_SHARED_SCHED_MELEE_ATTACK2, + MONSTER_SHARED_SCHED_RANGE_ATTACK1, + MONSTER_SHARED_SCHED_RANGE_ATTACK2, + MONSTER_SHARED_SCHED_SPECIAL_ATTACK1, + MONSTER_SHARED_SCHED_SPECIAL_ATTACK2, + MONSTER_SHARED_SCHED_STANDOFF, + MONSTER_SHARED_SCHED_ARM_WEAPON, + MONSTER_SHARED_SCHED_RELOAD, + MONSTER_SHARED_SCHED_AMBUSH, + MONSTER_SHARED_SCHED_DIE, + MONSTER_SHARED_SCHED_WAIT_TRIGGER, + MONSTER_SHARED_SCHED_BARNACLE_VICTIM_GRAB, + MONSTER_SHARED_SCHED_BARNACLE_VICTIM_CHOMP, + MONSTER_SHARED_SCHED_WAIT_SCRIPT, + MONSTER_SHARED_SCHED_WALK_TO_SCRIPT, + MONSTER_SHARED_SCHED_RUN_TO_SCRIPT, + MONSTER_SHARED_SCHED_FACE_SCRIPT, + MONSTER_SHARED_SCHED_FAIL, + MONSTER_SHARED_SCHED_ERROR +}; + +enum { + SCRIPT_PLAYING, // Playing the sequence + SCRIPT_WAIT, // Waiting on everyone in the script to be ready + SCRIPT_CLEANUP, // Cancelling the script / cleaning up + SCRIPT_WALK_TO_MARK, + SCRIPT_RUN_TO_MARK +}; + +enum { + TASK_WAIT, + TASK_WAIT_FACE_ENEMY, + TASK_WAIT_PVS, + TASK_SUGGEST_STATE, + TASK_WALK_TO_TARGET, + TASK_RUN_TO_TARGET, + TASK_MOVE_TO_TARGET_RANGE, + TASK_GET_PATH_TO_ENEMY, + TASK_GET_PATH_TO_ENEMY_LKP, + TASK_GET_PATH_TO_ENEMY_CORPSE, + TASK_GET_PATH_TO_LEADER, + TASK_GET_PATH_TO_SPOT, + TASK_GET_PATH_TO_TARGET, + TASK_GET_PATH_TO_HINTNODE, + TASK_GET_PATH_TO_LASTPOSITION, + TASK_GET_PATH_TO_BESTSOUND, + TASK_GET_PATH_TO_BESTSCENT, + TASK_RUN_PATH, + TASK_WALK_PATH, + TASK_STRAFE_PATH, + TASK_CLEAR_MOVE_WAIT, + TASK_STORE_LASTPOSITION, + TASK_CLEAR_LASTPOSITION, + TASK_PLAY_ACTIVE_IDLE, + TASK_FIND_HINTNODE, + TASK_CLEAR_HINTNODE, + TASK_SMALL_FLINCH, + TASK_FACE_IDEAL, + TASK_FACE_ROUTE, + TASK_FACE_ENEMY, + TASK_FACE_HINTNODE, + TASK_FACE_TARGET, + TASK_FACE_LASTPOSITION, + TASK_RANGE_ATTACK1, + TASK_RANGE_ATTACK2, + TASK_MELEE_ATTACK1, + TASK_MELEE_ATTACK2, + TASK_RELOAD, + TASK_RANGE_ATTACK1_NOTURN, + TASK_RANGE_ATTACK2_NOTURN, + TASK_MELEE_ATTACK1_NOTURN, + TASK_MELEE_ATTACK2_NOTURN, + TASK_RELOAD_NOTURN, + TASK_SPECIAL_ATTACK1, + TASK_SPECIAL_ATTACK2, + TASK_CROUCH, + TASK_STAND, + TASK_GUARD, + TASK_STEP_LEFT, + TASK_STEP_RIGHT, + TASK_STEP_FORWARD, + TASK_STEP_BACK, + TASK_DODGE_LEFT, + TASK_DODGE_RIGHT, + TASK_SOUND_ANGRY, + TASK_SOUND_DEATH, + TASK_SET_ACTIVITY, + TASK_SET_SCHEDULE, + TASK_SET_FAIL_SCHEDULE, + TASK_CLEAR_FAIL_SCHEDULE, + TASK_PLAY_SEQUENCE, + TASK_PLAY_SEQUENCE_FACE_ENEMY, + TASK_PLAY_SEQUENCE_FACE_TARGET, + TASK_SOUND_IDLE, + TASK_SOUND_WAKE, + TASK_SOUND_PAIN, + TASK_SOUND_DIE, + TASK_FIND_COVER_FROM_BEST_SOUND,// tries lateral cover first, then node cover + TASK_FIND_COVER_FROM_ENEMY,// tries lateral cover first, then node cover + TASK_FIND_LATERAL_COVER_FROM_ENEMY, + TASK_FIND_NODE_COVER_FROM_ENEMY, + TASK_FIND_NEAR_NODE_COVER_FROM_ENEMY,// data for this one is the MAXIMUM acceptable distance to the cover. + TASK_FIND_FAR_NODE_COVER_FROM_ENEMY,// data for this one is there MINIMUM aceptable distance to the cover. + TASK_FIND_COVER_FROM_ORIGIN, + TASK_EAT, + TASK_DIE, + TASK_WAIT_FOR_SCRIPT, + TASK_PLAY_SCRIPT, + TASK_ENABLE_SCRIPT, + TASK_PLANT_ON_SCRIPT, + TASK_FACE_SCRIPT, + TASK_WAIT_RANDOM, + TASK_WAIT_INDEFINITE, + TASK_STOP_MOVING, + TASK_TURN_LEFT, + TASK_TURN_RIGHT, + TASK_REMEMBER, + TASK_FORGET, + TASK_WAIT_FOR_MOVEMENT, // wait until MovementIsComplete() + TASK_WAIT_FOR_ROUTE_BUILD, + LAST_COMMON_TASK // LEAVE THIS AT THE BOTTOM!! (sjb) +}; + +enum (<<= 1) { + COND_NO_AMMO_LOADED = (1 << 0), // weapon needs to be reloaded! + COND_SEE_HATE, // see something that you hate + COND_SEE_FEAR, // see something that you are afraid of + COND_SEE_DISLIKE, // see something that you dislike + COND_SEE_ENEMY, // target entity is in full view. + COND_ENEMY_OCCLUDED, // target entity occluded by the world + COND_SMELL_FOOD, + COND_ENEMY_TOOFAR, + COND_LIGHT_DAMAGE, // hurt a little + COND_HEAVY_DAMAGE, // hurt a lot + COND_CAN_RANGE_ATTACK1, + COND_CAN_MELEE_ATTACK1, + COND_CAN_RANGE_ATTACK2, + COND_CAN_MELEE_ATTACK2, + COND_PROVOKED, + COND_NEW_ENEMY, + COND_HEAR_SOUND, // there is an interesting sound + COND_SMELL, // there is an interesting scent + COND_ENEMY_FACING_ME, // enemy is facing me + COND_ENEMY_DEAD, // enemy was killed. If you get this in combat, try to find another enemy. If you get it in alert, victory dance. + COND_SEE_CLIENT, // see a client + COND_SEE_NEMESIS, // see my nemesis + COND_WAIT_FOR_PATH = (1 << 27), + COND_SPECIAL1 = (1 << 28), // Defined by individual monster + COND_SPECIAL2, // Defined by individual monster + COND_TASK_FAILED, + COND_SCHEDULE_DONE +}; + +enum (<<= 1) { + MF_TO_TARGETENT = (1 << 0), // local move to targetent. + MF_TO_ENEMY, // local move to enemy + MF_TO_COVER, // local move to a hiding place + MF_TO_DETOUR, // local move to detour point. + MF_TO_PATHCORNER, // local move to a path corner + MF_TO_NODE, // local move to a node + MF_TO_LOCATION, // local move to an arbitrary point + MF_IS_GOAL, // this MONSTER_WAYPOINT is the goal of the whole move. + MF_DONT_SIMPLIFY, // Don't let the route code simplify this MONSTER_WAYPOINT + MF_TO_NAV +}; + +enum (<<= 1) { + SOUND_NONE = 0, + SOUND_COMBAT = (1 << 0), // gunshots, explosions + SOUND_WORLD, // door opening/closing, glass breaking + SOUND_PLAYER, // all noises generated by player. walking, shooting, falling, splashing + SOUND_CARCASS, // dead body + SOUND_MEAT, // gib or pork chop + SOUND_DANGER, // pending danger. Grenade that is about to explode, explosive barrel that is damaged, falling crate + SOUND_GARBAGE // trash cans, banana peels, old fast food bags. +}; + +enum (<<= 1) { + CAP_NONE = 0, + CAP_DUCK = (1 << 0), + CAP_JUMP, + CAP_STRAFE, + CAP_SQUAD, + CAP_SWIM, + CAP_CLIMB, + CAP_USE, + CAP_HEAR, + CAP_AUTO_DOORS, + CAP_OPEN_DOORS, + CAP_TURN_HEAD, + CAP_RANGE_ATTACK1, + CAP_RANGE_ATTACK2, + CAP_MELEE_ATTACK1, + CAP_MELEE_ATTACK2, + CAP_FLY +}; + +enum (<<= 1) { + MEMORY_CLEAR = 0, + MEMORY_PROVOKED = ( 1 << 0 ), // right now only used for houndeyes. + MEMORY_INCOVER, // monster knows it is in a covered position. + MEMORY_SUSPICIOUS, // Ally is suspicious of the player, and will move to provoked more easily + MEMORY_PATH_FINISHED, // Finished monster path (just used by big momma for now) + MEMORY_ON_PATH, // Moving on a path + MEMORY_MOVE_FAILED, // Movement has already failed + MEMORY_FLINCHED, // Has already flinched + MEMORY_KILLED, // HACKHACK -- remember that I've already called my Killed() + MEMORY_CUSTOM4 = ( 1 << 28 ), // Monster-specific memory + MEMORY_CUSTOM3, // Monster-specific memory + MEMORY_CUSTOM2, // Monster-specific memory + MEMORY_CUSTOM1 // Monster-specific memory +}; + +enum { + EVENT_SPECIFIC = 0, + SCRIPT_EVENT_DEAD = 1000, + SCRIPT_EVENT_NOINTERRUPT, + SCRIPT_EVENT_CANINTERRUPT, + SCRIPT_EVENT_FIREEVENT, + SCRIPT_EVENT_SOUND, + SCRIPT_EVENT_SENTENCE, + SCRIPT_EVENT_INAIR, + SCRIPT_EVENT_ENDANIMATION, + SCRIPT_EVENT_SOUND_VOICE, + SCRIPT_EVENT_SENTENCE_RND1, + SCRIPT_EVENT_NOT_DEAD, + MONSTER_EVENT_BODYDROP_LIGHT = 2001, + MONSTER_EVENT_BODYDROP_HEAVY, + MONSTER_EVENT_SWISHSOUND = 2010, +}; + +#define EVENT_SPECIFIC 0 +#define EVENT_SCRIPTED 1000 +#define EVENT_SHARED 2000 +#define EVENT_CLIENT 5000 + +#define CAP_DOORS_GROUP (CAP_USE | CAP_AUTO_DOORS | CAP_OPEN_DOORS) + +#define MF_NOT_TO_MASK (MF_IS_GOAL | MF_DONT_SIMPLIFY) + +#define MOVEGOAL_NONE 0 +#define MOVEGOAL_TARGETENT MF_TO_TARGETENT +#define MOVEGOAL_ENEMY MF_TO_ENEMY +#define MOVEGOAL_PATHCORNER MF_TO_PATHCORNER +#define MOVEGOAL_LOCATION MF_TO_LOCATION +#define MOVEGOAL_NODE MF_TO_NODE +#define MOVEGOAL_NAV MF_TO_NAV + +#define SCRIPT_BREAK_CONDITIONS (COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE) +#define COND_CAN_ATTACK (COND_CAN_RANGE_ATTACK1 | COND_CAN_MELEE_ATTACK1 | COND_CAN_RANGE_ATTACK2 | COND_CAN_MELEE_ATTACK2) + +#define R_AL -2 // (ALLY) pals. Good alternative to R_NO when applicable. +#define R_FR -1 // (FEAR)will run +#define R_NO 0 // (NO RELATIONSHIP) disregard +#define R_DL 1 // (DISLIKE) will attack +#define R_HT 2 // (HATE)will attack this character instead of any visible DISLIKEd characters +#define R_NM 3 // (NEMESIS) A monster Will ALWAYS attack its nemsis, no matter what + +#define NO_NODE -1 +#define SOUNDLIST_EMPTY -1 +#define ACTIVITY_NOT_AVAILABLE Activity:-1 +#define ROUTE_SIZE 8 + +#define m_iTaskStatus "iTaskStatus" +#define m_iScheduleIndex "iScheduleIndex" +#define m_iMonsterState "iMonsterState" +#define m_iIdealMonsterState "iIdealMonsterState" +#define m_iDelay "iDelay" +#define m_iszPlay "iszPlay" +#define m_iIdealActivity "iIdealActivity" +#define m_iHintNode "iHintNode" +#define m_iMovementActivity "iMovementActivity" +#define m_iVolume "iVolume" +#define m_iRouteIndex "iRouteIndex" +#define m_iszIdle "iszIdle" +#define m_iScriptState "iScriptState" +#define m_pEnemy "pEnemy" +#define m_vecLastPosition "vecLastPosition" +#define m_flMoveWaitFinished "flMoveWaitFinished" +#define m_flWaitFinished "flWaitFinished" +#define m_pTargetEnt "pTarget" +#define m_vecMoveGoal "vecMoveGoal" +#define m_vecEnemyLKP "vecEnemyLKP" +#define m_pCine "pCine" +#define m_iFailSchedule "iFailSchedule" +#define m_bSequenceFinished "bSequenceFinished" +#define m_iActivity "iActivity" +#define m_iMemory "iMemory" +#define m_iMovementGoal "iMovementGoal" +#define m_flMoveWaitTime "flMoveWaitTime" +#define m_flHungryTime "flHungryTime" +#define m_pGoalEnt "pGoalEnt" +#define m_irgRoute "irgRoute" +#define m_flFieldOfView "flFieldOfView" +#define m_bSequenceLoops "bSequenceLoops" +#define m_flFrameRate "flFrameRate" +#define m_flGroundSpeed "flGroundSpeed" +#define m_flDistLook "flDistLook" +#define m_iCapability "iCapability" +#define m_flDistTooFar "flDistTooFar" +#define m_irgOldEnemies "irgOldEnemies" +#define m_iLastHitGroup "iLastHitGroup" +#define m_iDamageType "m_iDamageType" +#define m_vecEyePosition "m_vecEyePosition" +#define m_irgSequences "irgSequences" +#define m_flLastEventCheck "flLastEventCheck" +#define m_iConditions "iConditions" +#define m_sSchedule "sSchedule" +#define m_pPathTask "pPathTask" + +#define m_iAudibleList "iAudibleList" +#define m_iSoundTypes "iSoundTypes" +#define m_iThinkPluginId "iThinkPluginId" +#define m_iThinkFunctionId "iThinkFunctionId" +#define m_flRangeAttack1Range "flRangeAttack1Range" +#define m_flRangeAttack2Range "flRangeAttack2Range" +#define m_flMeleeAttack1Range "flMeleeAttack1Range" +#define m_flMeleeAttack2Range "flMeleeAttack2Range" +#define m_flMeleeAttack1Damage "flMeleeAttack1Damage" +#define m_flMeleeAttack2Damage "flMeleeAttack2Damage" +#define m_flStepSize "flStepSize" +#define m_flStepHeight "flStepHeight" +#define m_iStepLeft "iStepLeft" + +#define StartSequence "StartSequence" +#define SequenceDone "SequenceDone" +#define Classify "Classify" +#define MonsterInit "MonsterInit" +#define SetState "SetState" +#define SetYawSpeed "SetYawSpeed" +#define SetThink "SetThink" +#define TakeDamage "TakeDamage" +#define AlertSound "AlertSound" +#define DeathSound "DeathSound" +#define IdleSound "IdleSound" +#define PainSound "PainSound" +#define ShouldGibMonster "ShouldGibMonster" +#define CallGibMonster "CallGibMonster" +#define GibMonster "GibMonster" +#define EmitSound "EmitSound" +#define MoveToEnemy "MoveToEnemy" +#define MoveToTarget "MoveToTarget" +#define MoveToLocation "MoveToLocation" +#define HandleAnimEvent "HandleAnimEvent" +#define CheckTraceHullAttack "CheckTraceHullAttack" +#define MeleeAttack1 "MeleeAttack1" +#define MeleeAttack2 "MeleeAttack2" +#define StepSound "StepSound" +#define CalculateHitGroupDamage "CalculateHitGroupDamage" + +#define SetSchedule "SetSchedule" + +#define SetConditions "SetConditions" +#define ClearConditions "ClearConditions" +#define HasConditions "HasConditions" +#define HasAllConditions "HasAllConditions" +#define IgnoreConditions "IgnoreConditions" +#define GetScheduleOfType "GetScheduleOfType" +#define GetSchedule "GetSchedule" +#define GetSharedSchedule "GetSharedSchedule" +#define IsCurTaskContinuousMove "IsCurTaskContinuousMove" +#define RunTask "RunTask" +#define StartTask "StartTask" +#define MoveExecute "MoveExecute" + +#define SetActivity "SetActivity" +#define ChangeSchedule "ChangeSchedule" +#define HandlePathTask "HandlePathTask" + +#define Remember "Remember" +#define Forget "Forget" +#define HasMemory "HasMemory" +#define HasAllMemory "HasAllMemory" diff --git a/entities/include/entity_base_npc_const.inc b/entities/include/entity_base_npc_const.inc new file mode 100644 index 0000000..ff365a0 --- /dev/null +++ b/entities/include/entity_base_npc_const.inc @@ -0,0 +1,77 @@ +#if defined _entity_npc_base_const_included + #endinput +#endif +#define _entity_npc_base_const_included + +#define BASE_NPC_ENTITY_NAME "npc_base" + +#define m_flDamage "flDamage" +#define m_irgPath "irgPath" +#define m_vecGoal "vecGoal" +#define m_vecTarget "vecTarget" +#define m_pBuildPathTask "pBuildPathTask" +#define m_flReleaseAttack "flReleaseAttack" +#define m_flTargetArrivalTime "flTargetArrivalTime" +#define m_flNextAIThink "flNextAIThink" +#define m_flNextAction "flNextAction" +#define m_flNextAttack "flNextAttack" +#define m_flNextPathSearch "flNextPathSearch" +#define m_pKiller "pKiller" +#define m_flMinTargetDistance "flMinTargetDistance" +#define m_flAttackRate "flAttackRate" +#define m_flAttackRange "flAttackRange" +#define m_flHitRange "flHitRange" +#define m_flAttackDuration "flAttackDuration" +#define m_flHitDelay "flHitDelay" +#define m_flReleaseHit "flReleaseHit" +#define m_flViewRange "flViewRange" +#define m_flFindRange "flFindRange" +#define m_vecWeaponOffset "vecWeaponOffset" +#define m_vecHitAngle "vecHitAngle" +#define m_flDieDuration "flDieDuration" +#define m_flNextGoalUpdate "flNextGoalUpdate" +#define m_flNextEnemyUpdate "flNextEnemyUpdate" +#define m_flPathSearchDelay "flPathSearchDelay" +#define m_iRevengeChance "iRevengeChance" +#define m_flStepHeight "flStepHeight" +#define m_vecInput "vecInput" +#define m_flLastAIThink "flLastAIThink" +#define m_flAIThinkRate "flAIThinkRate" +#define m_flPainRate "flPainRate" +#define m_flNextPain "flNextPain" + +#define EmitVoice "EmitVoice" +#define ResetPath "ResetPath" +#define AIThink "AIThink" +#define UpdateEnemy "UpdateEnemy" +#define UpdateGoal "UpdateGoal" +#define TakeDamage "TakeDamage" +#define ProcessPath "ProcessPath" +#define ProcessTarget "ProcessTarget" +#define ProcessGoal "ProcessGoal" +#define SetTarget "SetTarget" +#define MoveTo "MoveTo" +#define UpdateTarget "UpdateTarget" +#define FindPath "FindPath" +#define GetPathCost "GetPathCost" +#define HandlePath "HandlePath" +#define Hit "Hit" +#define StartAttack "StartAttack" +#define GetEnemy "GetEnemy" +#define IsEnemy "IsEnemy" +#define IsValidEnemy "IsValidEnemy" +#define ReleaseAttack "ReleaseAttack" +#define CanAttack "CanAttack" +#define PlayAction "PlayAction" +#define AttackThink "AttackThink" +#define IsVisible "IsVisible" +#define IsReachable "IsReachable" +#define FindEnemy "FindEnemy" +#define GetEnemyPriority "GetEnemyPriority" +#define TestStep "TestStep" +#define MoveForward "MoveForward" +#define StopMovement "StopMovement" +#define IsInViewCone "IsInViewCone" +#define Dying "Dying" +#define MovementThink "MovementThink" +#define Pain "Pain" diff --git a/images/example-entity-selection.gif b/images/example-entity-selection.gif new file mode 100644 index 0000000..ae0d780 Binary files /dev/null and b/images/example-entity-selection.gif differ diff --git a/images/example-particle-effect.gif b/images/example-particle-effect.gif new file mode 100644 index 0000000..9c68c0b Binary files /dev/null and b/images/example-particle-effect.gif differ diff --git a/include/api_custom_entities.inc b/include/api_custom_entities.inc deleted file mode 100644 index 7e1de2a..0000000 --- a/include/api_custom_entities.inc +++ /dev/null @@ -1,122 +0,0 @@ -#if defined _api_custom_entities_included - #endinput -#endif -#define _api_custom_entities_included - -#pragma reqlib api_custom_entities - -#define CE_LOG_PREFIX "[CE]" -#define CE_BASE_CLASSNAME "info_target" - -enum CEPreset -{ - CEPreset_None = 0, - CEPreset_Item, // For items - CEPreset_NPC, // For NPC - CEPreset_Prop // For static props -}; - -enum CEFunction -{ - CEFunction_Spawn, // Call when entity spawned - CEFunction_Kill, // Call when some plugin try to kill entity. return PLUGIN_HANDLED to discard kill. - CEFunction_Killed, // Call when entity killed - CEFunction_Remove, // Call when entity removed - CEFunction_Picked, // Call when player pick item - CEFunction_Pickup, // Call when player touch item. Should return PLUGIN_HANDLED if picked. - CEFunction_KVD // Call when new key value obtained -}; -/* - * Register entity. - * - * @param szName Name of entity. - * @param modelIndex Precached model index. - * @param size Size of entity. - * @param offset Offset of entity origin. - * @param lifeTime Life time of entity. - * @param preset Preset for entity. - * @return Handler of registered entity. - */ -native CE_Register -( - const szName[], - modelIndex = 0, - const Float:vMins[3] = {-8.0, -8.0, -8.0}, - const Float:vMaxs[3] = {8.0, 8.0, 8.0}, - Float:fLifeTime = 0.0, - Float:fRespawnTime = 10.0, - bool:ignoreRounds = false, - CEPreset:preset = CEPreset_None -); - -/* - * Spawn entity. - * - * @param szName Name of entity. - * @param vOrigin Spawn origin. - * @return Entity index. - */ -native CE_Create(const szName[], const Float:vOrigin[3], bool:temp = true); - -/* - * Kill entity. - * - * @param ent Index of entity. - * @param killer Index of killer. - */ -native bool:CE_Kill(ent, killer = 0); - - -/* - * Gets size of entity. - * - * @param szClassname Classname of entity. - * @param vSize Output vector. - */ -native CE_GetSize(const szName[], Float:vMins[3], Float:vMaxs[3]); - -/* - * Gets modelindex of entity. - * - * @param szClassname Classname of entity. - * @return Modelindex of entity - */ -native CE_GetModelIndex(const szName[]); - -/* - * Remove entity correctly. - * - * @param ent Index of entity. - * @return Result true/false - */ -native bool:CE_Remove(ent); - -/* - * Register new hook for entity. - * - * @param function Function handler - * @param szClassname Classname of entity - * @param szCallback Callback - */ -native CE_RegisterHook(CEFunction:function, const szClassname[], const szCallback[]); - -/* - * Check if entity is associated with current plugin. - * - * @param ent Index of entity. - * @return Result true/false - */ -native CE_CheckAssociation_(ent); - -stock bool:CE_CheckAssociation(ent) { - static bool:notified = false; - if (!notified) { - log_amx("%s function ^"CE_CheckAssociation^" is deprecated. Check ^"CE_GetHandlerByEntity^" function.", CE_LOG_PREFIX); - notified = true; - } - - return CE_CheckAssociation_(ent); -} - -native CE_GetHandler(const szClassname[]); -native CE_GetHandlerByEntity(ent); \ No newline at end of file diff --git a/include/api_player_camera.inc b/include/api_player_camera.inc deleted file mode 100644 index 1ea1459..0000000 --- a/include/api_player_camera.inc +++ /dev/null @@ -1,12 +0,0 @@ -#if defined _api_player_camera_included - #endinput -#endif -#define _api_player_camera_included - -native PlayerCamera_Activate(pPlayer); -native PlayerCamera_Deactivate(pPlayer); -native bool:PlayerCamera_IsActive(pPlayer); -native PlayerCamera_SetOffset(pPlayer, const Float:vecOffset[3]); -native PlayerCamera_SetAngles(pPlayer, const Float:vecAngles[3]); -native PlayerCamera_SetDistance(pPlayer, Float:flDistance); -native PlayerCamera_SetThinkDelay(pPlayer, Float:flThinkDelay); diff --git a/include/api_player_model.inc b/include/api_player_model.inc deleted file mode 100644 index 2e88578..0000000 --- a/include/api_player_model.inc +++ /dev/null @@ -1,13 +0,0 @@ -// This is a beta version, some functions may be unstable. Use at your own risk. - -#if defined _api_player_model_included - #endinput -#endif -#define _api_player_model_included - -#pragma reqlib api_player_model - -native PlayerModel_Get(pPlayer, szOut[]); -native PlayerModel_Set(pPlayer, const szModel[]); -native PlayerModel_Update(pPlayer); -native PlayerModel_Reset(pPlayer); diff --git a/include/api_rounds.inc b/include/api_rounds.inc deleted file mode 100644 index 36a5c07..0000000 --- a/include/api_rounds.inc +++ /dev/null @@ -1,26 +0,0 @@ -#if defined _api_rounds_included - #endinput -#endif -#define _api_rounds_included - -#pragma reqlib api_rounds - -native Round_DispatchWin(iTeam, Float:fDelay); -native Round_SetTime(iTime); -native Round_GetTime(); -native Round_GetTimeLeft(); -native bool:Round_IsRoundStarted(); -native bool:Round_IsRoundEnd(); - -forward Round_Fw_NewRound(); -forward Round_Fw_RoundStart(); -forward Round_Fw_RoundEnd(iWinnerTeam); -forward Round_Fw_RoundExpired(); -forward Round_Fw_RoundRestart(); -forward Round_Fw_RoundTimerTick(); -forward Round_Fw_CheckWinCondition(); - -/** - * @deprecated This function is deprecated, use Round_Fw_CheckWinCondition forward instead. - */ -native Round_HookCheckWinConditions(const szFunction[]); diff --git a/util/cellclass.inc b/util/cellclass.inc new file mode 100644 index 0000000..d043b3e --- /dev/null +++ b/util/cellclass.inc @@ -0,0 +1,1297 @@ +#if defined _classes_included + #endinput +#endif +#define _classes_included + +#include +#include +#include + +#if !defined CLASS_METHOD_CALL_STACK_SIZE + #define CLASS_METHOD_CALL_STACK_SIZE 1024 +#endif + +#if !defined CLASS_METHOD_MAX_NAME_LENGTH + #define CLASS_METHOD_MAX_NAME_LENGTH 64 +#endif + +#if !defined CLASS_METHOD_MAX_ARGUMENTS + #define CLASS_METHOD_MAX_ARGUMENTS 32 +#endif + +#if !defined CLASS_PARAM_BUFFER_LENGTH + #define CLASS_PARAM_BUFFER_LENGTH 16384 +#endif + +// Default argument values + +#define __cls_DefaultValue_Cell 0 +#define __cls_DefaultValue_Float 0.0 +#define __cls_DefaultValue_String NULL_STRING + +// Error messages + +#define __cls_err_NotImplementedMethod "Method ^"%s^" is not implemented!" +#define __cls_err_AddMethodWithoutFunctionPointer "Cannot add unimplemented method ^"%s^"!" +#define __cls_err_MethodAlreadyRegistered "Method ^"%s^" is already registered for the class!" +#define __cls_err_VMethodArgumentsMismatch "Arguments mismatch in the overridden virtual method ^"%s^"!" +#define __cls_err_ClassIdNotFound "Class with id %d is not registered!" +#define __cls_err_MethodNotFound "Method ^"%s^" is not registered for the instance (%d)!" +#define __cls_err_CallMethodOutsideContext "Calling a method is not allowed outside of the execution context!" +#define __cls_err_ClassHasNoBaseClass "Cannot call base method of class without base class!" +#define __cls_err_MethodNotFoundInBaseClass "Cannot find method ^"%s^" in base classes!" +#define __cls_err_MaxCallStackSizeExceeded "Maximum call stack size exceeded!" +#define __cls_err_ClassMethodCallEndWithoutStart "Call ClassInstanceCallMethodEnd but method call is not started!" +#define __cls_err_NumberOfParametersExceeded "Number of argument(s) to call ^"%s^" exceeded! Expected %d, got %d." +#define __cls_err_MissingRequiredArguments "Missing required argument(s) to call ^"%s^"! Expected %d, got %d." +#define __cls_err_InvalidParamType "Invalid parameter type at position %d for call method %s. Expected: ^"%s^" got ^"%s^"!" +#define __cls_err_FailedToCallMethodFunction "Failed to call method ^"%s^" function!" + +#define __cls_MethodParamOffset(%1) (%1 * _:__cls_MethodParamData) + +enum Class { + Invalid_Class = -1 +}; + +enum ClassInstance { + Invalid_ClassInstance = -1 +}; + +enum _:__cls_ParamTypes { + CMP_Invalid = -1, + CMP_Cell, + CMP_String, + CMP_Array, + CMP_CellRef, + CMP_StringRef, + CMP_ArrayRef, + CMP_ParamsCellArray, + CMP_Variadic +}; + +stock const __cls_ParamTypeNames[__cls_ParamTypes][64] = { + "Cell", + "String", + "Array", + "CellRef", + "StringRef", + "ArrayRef", + "ParamsCellArray", + "..." +}; + +// Internal structures + +enum __cls_MethodType { + __cls_MethodType_Invalid = -1, + __cls_MethodType_Method, + __cls_MethodType_Virtual, + __cls_MethodType_Getter, + __cls_MethodType_Setter +}; + +enum __cls_Data { + Class:__cls_Data_Base, + Trie:__cls_Data_Members, + Trie:__cls_Data_Methods, + Trie:__cls_Data_Setters, + Trie:__cls_Data_Getters +}; + +enum __cls_MethodParamData { + __cls_MethodParamData_Type = 0, + __cls_MethodParamData_Size +}; + +enum __cls_MethodData { + __cls_MethodData_Name[CLASS_METHOD_MAX_NAME_LENGTH], + Class:__cls_MethodData_Class, + __cls_MethodData_Function, + __cls_MethodData_VariadicParams, + __cls_MethodType:__cls_MethodData_Type, + __cls_MethodData_ParamTypesNum, + __cls_MethodData_ParamTypes[__cls_MethodParamOffset(CLASS_METHOD_MAX_ARGUMENTS + 1)] +}; + +enum __cls_InstanceData { + Class:__cls_InstanceData_Class, + Trie:__cls_InstanceData_Members +}; + +enum __cls_InstanceCache { + ClassInstance:__cls_InstanceCache_Instance, + any:__cls_InstanceCache_Value +}; + +// Fast pseudo-stack implementation +stock ClassInstance:__cls_callstack_pInstance[CLASS_METHOD_CALL_STACK_SIZE]; +stock Class:__cls_callstack_class[CLASS_METHOD_CALL_STACK_SIZE]; +stock Struct:__cls_callstack_sMethod[CLASS_METHOD_CALL_STACK_SIZE]; +stock __cls_callstack_iBufferPos[CLASS_METHOD_CALL_STACK_SIZE]; +stock __cls_callstack_iLastRefArg[CLASS_METHOD_CALL_STACK_SIZE]; +stock __cls_callstack_size = 0; + +#define __cls_callstack_iLastItem (__cls_callstack_size - 1) + +// Variables to store info between Begin and End method calls +stock Class:__cls_call_class = Invalid_Class; +stock ClassInstance:__cls_call_pInstance = Invalid_ClassInstance; +stock Struct:__cls_call_sMethod = Invalid_Struct; +stock __cls_call_iParamTypesNum = 0; +stock __cls_call_iRequiredParamsNum = 0; +stock __cls_call_iParamsNum = 0; +stock __cls_call_iBufferPos = 0; +stock __cls_call_iLastRefArg = 0; + +// Cache +stock __cls_cache_InstanceClass[__cls_InstanceCache] = { Invalid_ClassInstance, Invalid_Class }; +stock __cls_cache_InstanceMethods[__cls_InstanceCache] = { Invalid_ClassInstance, Invalid_Trie }; +stock __cls_cache_InstanceMembers[__cls_InstanceCache] = { Invalid_ClassInstance, Invalid_Trie }; +stock __cls_cache_InstanceGetters[__cls_InstanceCache] = { Invalid_ClassInstance, Invalid_Trie }; +stock __cls_cache_InstanceSetters[__cls_InstanceCache] = { Invalid_ClassInstance, Invalid_Trie }; + +// Buffers +stock any:__cls_rgBuffer[CLASS_PARAM_BUFFER_LENGTH]; + +/*--------------------------------[ Class Functions] --------------------------------*/ + +stock Class:ClassCreate(const &Class:sBaseClass = Invalid_Class) { + new Struct:class = StructCreate(__cls_Data); + StructSetCell(class, __cls_Data_Base, sBaseClass); + StructSetCell(class, __cls_Data_Members, Invalid_Trie); + StructSetCell(class, __cls_Data_Methods, Invalid_Trie); + StructSetCell(class, __cls_Data_Setters, Invalid_Trie); + StructSetCell(class, __cls_Data_Getters, Invalid_Trie); + + return Class:class; +} + +stock ClassDestroy(&Class:class) { + new Trie:itMethods = StructGetCell(Struct:class, __cls_Data_Methods); + if (itMethods != Invalid_Trie) { + __cls_DestroyMethodsTrie(itMethods); + } + + new Trie:itGetters = StructGetCell(Struct:class, __cls_Data_Getters); + if (itGetters != Invalid_Trie) { + __cls_DestroyMethodsTrie(itGetters); + } + + new Trie:itSetters = StructGetCell(Struct:class, __cls_Data_Setters); + if (itSetters != Invalid_Trie) { + __cls_DestroyMethodsTrie(itSetters); + } + + new Trie:itMembers = StructGetCell(Struct:class, __cls_Data_Members); + if (itMembers != Invalid_Trie) { + TrieDestroy(itMembers); + } + + StructDestroy(Struct:class); + + class = Invalid_Class; +} + +stock Class:ClassAddMethod(const &Class:class, const szMethod[], const Function:func, bool:bVirtual = false, any:...) { + static const iArgOffset = 4; + + new iArgc = numargs(); + + new Class:sBaseClass = StructGetCell(Struct:class, __cls_Data_Base); + new Trie:itMethods = __cls_GetClassMethodsTrie(class, __cls_MethodType_Method, true); + + if (TrieKeyExists(itMethods, szMethod)) { + set_fail_state(__cls_err_MethodAlreadyRegistered, szMethod); + return; + } + + if (func == Invalid_FunctionPointer && !bVirtual) { + set_fail_state(__cls_err_AddMethodWithoutFunctionPointer, szMethod); + return; + } + + new Array:irgArgs = ArrayCreate(); + new bool:bVariadicParams = false; + + // Some arguments are used to decribe type properties, so we need to skip type check for these arguments + new bool:bSkipTypeCheck = false; + + for (new iArg = iArgOffset; iArg < iArgc; ++iArg) { + new any:arg = getarg(iArg); + + if (!bSkipTypeCheck) { + switch (arg) { + case CMP_Array, CMP_ArrayRef, CMP_StringRef, CMP_ParamsCellArray: { + bSkipTypeCheck = true; + } + case CMP_Variadic: { + iArgc = iArg; + bVariadicParams = true; + break; + } + } + } else { + bSkipTypeCheck = false; + } + + ArrayPushCell(irgArgs, arg); + } + + new Array:irgParamTypes; irgParamTypes = ArrayCreate(_:__cls_MethodParamData, iArgc - iArgOffset); + __cls_ParseParamTypes(irgArgs, irgParamTypes); + + ArrayDestroy(irgArgs); + + if (sBaseClass != Invalid_Class) { + new Struct:sBaseMethod = __cls_FindClassMethodInHierarchy(sBaseClass, szMethod, __cls_MethodType_Method); + if (sBaseMethod != Invalid_Struct) { + if (StructGetCell(sBaseMethod, __cls_MethodData_Type) == __cls_MethodType_Virtual) { + if (!__cls_CompareParamTypes(sBaseMethod, irgParamTypes)) { + set_fail_state(__cls_err_VMethodArgumentsMismatch, szMethod); + return; + } + } + } + } + + new Struct:sMethod = __cls_CreateMethod(class, szMethod, func, irgParamTypes, bVirtual ? __cls_MethodType_Virtual : __cls_MethodType_Method, bVariadicParams); + + ArrayDestroy(irgParamTypes); + + TrieSetCell(itMethods, szMethod, sMethod); +} + +stock Function:ClassGetMethodFunction(const &Class:class, const szMethod[]) { + static Trie:itMethods; itMethods = __cls_GetClassMethodsTrie(class, __cls_MethodType_Method, false); + + if (itMethods == Invalid_Trie) return Invalid_FunctionPointer; + + static Struct:sMethod; + if (!TrieGetCell(itMethods, szMethod, sMethod)) return Invalid_FunctionPointer; + + return StructGetCell(sMethod, __cls_MethodData_Function); +} + +stock Struct:ClassGetMethodPointer(const &Class:class, const szMethod[]) { + static Trie:itMethods; itMethods = __cls_GetClassMethodsTrie(class, __cls_MethodType_Method, false); + + if (itMethods == Invalid_Trie) return Invalid_Struct; + + static Struct:sMethod; + if (!TrieGetCell(itMethods, szMethod, sMethod)) return Invalid_Struct; + + return sMethod; +} + +stock ClassAddSetter(const &Class:class, const szMember[], const Function:func, iType, iSize = 1) { + __cls_AddMemberAccessorMethod(class, szMember, func, __cls_MethodType_Setter, iType, iSize); +} + +stock ClassAddGetter(const &Class:class, const szMember[], const Function:func, iType, iSize = 1) { + __cls_AddMemberAccessorMethod(class, szMember, func, __cls_MethodType_Getter, iType, iSize); +} + +stock Class:ClassGetBaseClass(const &Class:class) { + return StructGetCell(Struct:class, __cls_Data_Base); +} + +stock bool:ClassHasMetadata(const &Class:class, const szMember[]) { + static Trie:itMembers; itMembers = __cls_GetClassMembersTrie(class, false); + + if (itMembers == Invalid_Trie) return false; + + return TrieKeyExists(itMembers, szMember); +} + +stock ClassDeleteMetadata(const &Class:class, const szMember[]) { + static Trie:itMembers; itMembers = __cls_GetClassMembersTrie(class, false); + + if (itMembers == Invalid_Trie) return; + + TrieDeleteKey(itMembers, szMember); +} + +stock any:ClassGetMetadata(const &Class:class, const szMember[]) { + static Trie:itMembers; itMembers = __cls_GetClassMembersTrie(class, false); + + if (itMembers == Invalid_Trie) return 0; + + static any:value; + return TrieGetCell(itMembers, szMember, value) ? value : 0; +} + +stock ClassSetMetadata(const &Class:class, const szMember[], any:value, bool:bReplace = true) { + static Trie:itMembers; itMembers = __cls_GetClassMembersTrie(class, true); + + TrieSetCell(itMembers, szMember, value, bReplace); +} + +stock bool:ClassGetMetadataString(const &Class:class, const szMember[], szOut[], iMaxLen) { + static Trie:itMembers; itMembers = __cls_GetClassMembersTrie(class, false); + + if (itMembers == Invalid_Trie) return false; + + copy(szOut, iMaxLen, NULL_STRING); + return !!TrieGetString(itMembers, szMember, szOut, iMaxLen); +} + +stock ClassSetMetadataString(const &Class:class, const szMember[], const szValue[], bool:bReplace = true) { + static Trie:itMembers; itMembers = __cls_GetClassMembersTrie(class, true); + + TrieSetString(itMembers, szMember, szValue, bReplace); +} + +stock bool:ClassGetMetadataArray(const &Class:class, const szMember[], any:rgOut[], iLen) { + static Trie:itMembers; itMembers = __cls_GetClassMembersTrie(class, false); + + if (itMembers == Invalid_Trie) return false; + + return !!TrieGetArray(itMembers, szMember, rgOut, iLen); +} + +stock ClassSetMetadataArray(const &Class:class, const szMember[], const any:rgValue[], iLen, bool:bReplace = true) { + static Trie:itMembers; itMembers = __cls_GetClassMembersTrie(class, true); + + TrieSetArray(itMembers, szMember, rgValue, iLen, bReplace); +} + +/*--------------------------------[ Class Instance Functions] --------------------------------*/ + +stock ClassInstance:ClassInstanceCreate(const &Class:class) { + static Struct:sInstance; sInstance = StructCreate(__cls_InstanceData); + StructSetCell(sInstance, __cls_InstanceData_Class, class); + StructSetCell(sInstance, __cls_InstanceData_Members, Invalid_Trie); + + return ClassInstance:sInstance; +} + +stock ClassInstanceDestroy(&ClassInstance:pInstance) { + if (pInstance == __cls_cache_InstanceClass[__cls_InstanceCache_Instance]) { + __cls_cache_InstanceClass[__cls_InstanceCache_Instance] = Invalid_ClassInstance; + } + + if (pInstance == __cls_cache_InstanceMembers[__cls_InstanceCache_Instance]) { + __cls_cache_InstanceMembers[__cls_InstanceCache_Instance] = Invalid_ClassInstance; + } + + if (pInstance == __cls_cache_InstanceGetters[__cls_InstanceCache_Instance]) { + __cls_cache_InstanceGetters[__cls_InstanceCache_Instance] = Invalid_ClassInstance; + } + + if (pInstance == __cls_cache_InstanceSetters[__cls_InstanceCache_Instance]) { + __cls_cache_InstanceSetters[__cls_InstanceCache_Instance] = Invalid_ClassInstance; + } + + static Trie:itMembers; itMembers = StructGetCell(Struct:pInstance, __cls_InstanceData_Members); + if (itMembers != Invalid_Trie) { + TrieDestroy(itMembers); + } + + StructDestroy(Struct:pInstance); + + pInstance = Invalid_ClassInstance; +} + +stock ClassInstance:ClassInstanceGetCurrent() { + if (!__cls_callstack_size) { + set_fail_state(__cls_err_CallMethodOutsideContext); + return Invalid_ClassInstance; + } + + return __cls_callstack_pInstance[__cls_callstack_iLastItem]; +} + +stock Class:ClassInstanceGetCurrentClass() { + if (!__cls_callstack_size) { + set_fail_state(__cls_err_CallMethodOutsideContext); + return Invalid_Class; + } + + return __cls_callstack_class[__cls_callstack_iLastItem]; +} + +#define __cls_READ_METHOD_CALL_PARAMS(%1) {\ + static iParamsNum; iParamsNum = min(__cls_call_iParamTypesNum, numargs() - %1);\ +\ + for (new iMethodParam = 0; iMethodParam < iParamsNum; ++iMethodParam) {\ + static iArgument; iArgument = %1 + iMethodParam;\ + static iType; iType = StructGetCell(__cls_call_sMethod, __cls_MethodData_ParamTypes, __cls_MethodParamOffset(iMethodParam) + _:__cls_MethodParamData_Type);\ +\ + if (__cls_IsRefType(iType)) __cls_call_iLastRefArg = iMethodParam;\ +\ + switch (iType) {\ + case CMP_Cell: {\ + __cls_rgBuffer[__cls_call_iBufferPos] = any:getarg(iArgument);\ + ClassInstanceCallMethodPushParamCell(__cls_rgBuffer[__cls_call_iBufferPos]);\ + __cls_call_iBufferPos++;\ + }\ + case CMP_CellRef: {\ + __cls_rgBuffer[__cls_call_iBufferPos] = any:getarg(iArgument);\ + ClassInstanceCallMethodPushParamCellRef(__cls_rgBuffer[__cls_call_iBufferPos]);\ + __cls_call_iBufferPos++;\ + }\ + case CMP_String: {\ + static iPos; iPos = __cls_call_iBufferPos;\ + static iSize; iSize = 0;\ + do\ + __cls_rgBuffer[iPos] = getarg(iArgument, iSize++);\ + while (__cls_rgBuffer[iPos++] != '^0' && iPos < charsmax(__cls_rgBuffer));\ +\ + ClassInstanceCallMethodPushParamString(__cls_rgBuffer[__cls_call_iBufferPos]);\ +\ + __cls_call_iBufferPos += iSize;\ + }\ + case CMP_Array, CMP_ArrayRef, CMP_StringRef: {\ + static iSize; iSize = StructGetCell(__cls_call_sMethod, __cls_MethodData_ParamTypes, __cls_MethodParamOffset(iMethodParam) + _:__cls_MethodParamData_Size);\ +\ + static i;\ + for (i = 0; i < iSize; ++i) __cls_rgBuffer[__cls_call_iBufferPos + i] = any:getarg(iArgument, i);\ +\ + switch (iType) {\ + case CMP_Array: ClassInstanceCallMethodPushParamArray(__cls_rgBuffer[__cls_call_iBufferPos], iSize);\ + case CMP_ArrayRef: ClassInstanceCallMethodPushParamArrayRef(__cls_rgBuffer[__cls_call_iBufferPos], iSize);\ + case CMP_StringRef: ClassInstanceCallMethodPushParamStringRef(__cls_rgBuffer[__cls_call_iBufferPos], iSize);\ + }\ +\ + __cls_call_iBufferPos += iSize;\ + }\ + }\ + }\ +} + +#define __cls_UPDATE_METHOD_CALL_REF_PARAMS(%1) {\ + static iBufferOffset; iBufferOffset = __cls_call_iBufferPos;\ +\ + for (new iMethodParam = 0; iMethodParam <= __cls_call_iLastRefArg; ++iMethodParam) {\ + static iArgument; iArgument = %1 + iMethodParam;\ + static iType; iType = StructGetCell(__cls_call_sMethod, __cls_MethodData_ParamTypes, __cls_MethodParamOffset(iMethodParam) + _:__cls_MethodParamData_Type);\ +\ + switch (iType) {\ + case CMP_Cell: {\ + iBufferOffset++;\ + }\ + case CMP_CellRef: {\ + setarg(iArgument, _, __cls_rgBuffer[iBufferOffset++]);\ + }\ + case CMP_String: {\ + while (__cls_rgBuffer[iBufferOffset] != '^0') iBufferOffset++;\ + iBufferOffset++;\ + }\ + case CMP_Array, CMP_ArrayRef, CMP_StringRef: {\ + static iSize; iSize = StructGetCell(__cls_call_sMethod, __cls_MethodData_ParamTypes, __cls_MethodParamOffset(iMethodParam) + _:__cls_MethodParamData_Size);\ +\ + static i;\ + if (iType == CMP_ArrayRef)\ + for (i = 0; i < iSize; ++i) setarg(iArgument, i, __cls_rgBuffer[iBufferOffset + i]);\ + else if (iType == CMP_StringRef)\ + for (i = 0; i < (iSize - 1) && __cls_rgBuffer[iBufferOffset + i] != '^0'; ++i)\ + setarg(iArgument, i, __cls_rgBuffer[iBufferOffset + i]);\ +\ + if (iType == CMP_StringRef) setarg(iArgument, i, '^0');\ +\ + iBufferOffset += iSize;\ + }\ + }\ + }\ +} + +stock any:ClassInstanceCallMethod(const &ClassInstance:pInstance, const szMethod[], any:...) { + __cls_CallMethodBegin(pInstance, szMethod, _, __cls_MethodType_Method); + + __cls_READ_METHOD_CALL_PARAMS(2) + + static any:result; result = ClassInstanceCallMethodEnd(); + + __cls_UPDATE_METHOD_CALL_REF_PARAMS(2) + + return result; +} + +stock ClassInstanceCallMethodOf(const &ClassInstance:pInstance, const szMethod[], const &Class:class = Invalid_Class, any:...) { + __cls_CallMethodBegin(pInstance, szMethod, class, __cls_MethodType_Method); + + __cls_READ_METHOD_CALL_PARAMS(3) + + static any:result; result = ClassInstanceCallMethodEnd(); + + __cls_UPDATE_METHOD_CALL_REF_PARAMS(3) + + return result; +} + +stock ClassInstanceCallMethodByPointer(const &ClassInstance:pInstance, const &Struct:sMethod, any:...) { + __cls_CallMethodByPointerBegin(pInstance, sMethod); + + __cls_READ_METHOD_CALL_PARAMS(2) + + static any:result; result = ClassInstanceCallMethodEnd(); + + __cls_UPDATE_METHOD_CALL_REF_PARAMS(2) + + return result; +} + +stock ClassInstanceCallBaseMethod(any:...) { + ClassInstanceCallMethodBeginBase(); + + __cls_READ_METHOD_CALL_PARAMS(0) + + static any:result; result = ClassInstanceCallMethodEnd(); + + __cls_UPDATE_METHOD_CALL_REF_PARAMS(0) + + return result; +} + +stock ClassInstanceCallMethodBegin(const &ClassInstance:pInstance, const szMethod[], const &Class:class = Invalid_Class) { + __cls_CallMethodBegin(pInstance, szMethod, class, __cls_MethodType_Method); +} + +stock ClassInstanceCallMethodBeginBase(any:...) { + if (!__cls_callstack_size) { + set_fail_state(__cls_err_CallMethodOutsideContext); + return; + } + + static Class:class; class = StructGetCell(Struct:__cls_callstack_class[__cls_callstack_iLastItem], __cls_Data_Base); + if (class == Invalid_Class) { + set_fail_state(__cls_err_ClassHasNoBaseClass); + return; + } + + static szMethod[CLASS_METHOD_MAX_NAME_LENGTH]; StructGetString(__cls_callstack_sMethod[__cls_callstack_iLastItem], __cls_MethodData_Name, szMethod, charsmax(szMethod)); + static __cls_MethodType:iMethodType; iMethodType = StructGetCell(__cls_callstack_sMethod[__cls_callstack_iLastItem], __cls_MethodData_Type); + + __cls_InitMethodCall(__cls_callstack_pInstance[__cls_callstack_iLastItem], szMethod, class, iMethodType); + + static Function:func; func = __cls_GetCallMethodFunctionPointer(); + + if (callfunc_begin_p(func) != 1) { + static szMethod[CLASS_METHOD_MAX_NAME_LENGTH]; StructGetString(__cls_call_sMethod, __cls_MethodData_Name, szMethod, charsmax(szMethod)); + set_fail_state(__cls_err_FailedToCallMethodFunction, szMethod); + return; + } +} + +stock any:ClassInstanceCallMethodEnd() { + if (__cls_call_sMethod == Invalid_Struct) { + set_fail_state(__cls_err_ClassMethodCallEndWithoutStart); + return 0; + } + + if (__cls_call_iParamsNum < __cls_call_iRequiredParamsNum) { + static szMethod[CLASS_METHOD_MAX_NAME_LENGTH]; StructGetString(__cls_call_sMethod, __cls_MethodData_Name, szMethod, charsmax(szMethod)); + set_fail_state(__cls_err_MissingRequiredArguments, szMethod, __cls_call_iRequiredParamsNum, __cls_call_iParamsNum); + return 0; + } + + while (__cls_call_iParamsNum < __cls_call_iParamTypesNum) { + callfunc_push_int(0); + __cls_call_iParamsNum++; + } + + static any:result; result = __cls_ExecuteMethod(); + + if (!__cls_callstack_size) { + // __cls_FreeMethodCall(); + } + + return result; +} + +stock __cls_ValidateParamPush(iType) { + if (__cls_call_iParamsNum >= __cls_call_iParamTypesNum) { + if (StructGetCell(__cls_call_sMethod, __cls_MethodData_VariadicParams)) { + return 1; + } + + static szMethod[CLASS_METHOD_MAX_NAME_LENGTH]; StructGetString(__cls_call_sMethod, __cls_MethodData_Name, szMethod, charsmax(szMethod)); + set_fail_state(__cls_err_NumberOfParametersExceeded, szMethod, __cls_call_iParamTypesNum, __cls_call_iParamsNum); + return 0; + } + + static iExpectedType; iExpectedType = StructGetCell(__cls_call_sMethod, __cls_MethodData_ParamTypes, __cls_MethodParamOffset(__cls_call_iParamsNum) + _:__cls_MethodParamData_Type); + + if (iType != iExpectedType) { + static szMethodName[CLASS_METHOD_MAX_NAME_LENGTH]; StructGetString(__cls_call_sMethod, __cls_MethodData_Name, szMethodName, charsmax(szMethodName)); + set_fail_state(__cls_err_InvalidParamType, __cls_call_iParamsNum, szMethodName, __cls_ParamTypeNames[iExpectedType], __cls_ParamTypeNames[iType]); + return 0; + } + + return 1; +} + +stock ClassInstanceCallMethodPushParamCell(any:value) { + if (!__cls_ValidateParamPush(CMP_Cell)) return; + + callfunc_push_int(value); + + __cls_call_iParamsNum++; +} + +stock ClassInstanceCallMethodPushParamCellRef(&any:value) { + if (!__cls_ValidateParamPush(CMP_CellRef)) return; + + callfunc_push_intrf(value); + + __cls_call_iParamsNum++; +} + +stock ClassInstanceCallMethodPushParamString(const szValue[]) { + if (!__cls_ValidateParamPush(CMP_String)) return; + + callfunc_push_str(szValue, false); + + __cls_call_iParamsNum++; +} + +stock ClassInstanceCallMethodPushParamArray(const any:rgValue[], iSize) { + if (!__cls_ValidateParamPush(CMP_Array)) return; + + callfunc_push_array(rgValue, iSize, false); + + __cls_call_iParamsNum++; +} + +stock ClassInstanceCallMethodPushParamArrayRef(rgValue[], iSize) { + if (!__cls_ValidateParamPush(CMP_ArrayRef)) return; + + callfunc_push_array(rgValue, iSize, true); + + __cls_call_iParamsNum++; +} + +stock ClassInstanceCallMethodPushParamStringRef(rgValue[], iMaxLen) { + if (!__cls_ValidateParamPush(CMP_StringRef)) return; + + // arrayset(rgValue, 1, iMaxLen); + rgValue[iMaxLen + 1] = '^0'; + callfunc_push_array(rgValue, iMaxLen, true); + + __cls_call_iParamsNum++; +} + +stock ClassInstanceCallMethodPushNativeParam(iParam) { + if (__cls_call_iParamsNum >= __cls_call_iParamTypesNum) return; + + static iType; iType = StructGetCell(__cls_call_sMethod, __cls_MethodData_ParamTypes, __cls_MethodParamOffset(__cls_call_iParamsNum) + _:__cls_MethodParamData_Type); + static iSize; iSize = StructGetCell(__cls_call_sMethod, __cls_MethodData_ParamTypes, __cls_MethodParamOffset(__cls_call_iParamsNum) + _:__cls_MethodParamData_Size); + + if (__cls_IsRefType(iType)) __cls_call_iLastRefArg = __cls_call_iParamsNum; + + switch (iType) { + case CMP_Cell: { + __cls_rgBuffer[__cls_call_iBufferPos] = any:get_param_byref(iParam); + callfunc_push_int(__cls_rgBuffer[__cls_call_iBufferPos]); + __cls_call_iBufferPos++; + } + case CMP_CellRef: { + __cls_rgBuffer[__cls_call_iBufferPos] = any:get_param_byref(iParam); + callfunc_push_intrf(__cls_rgBuffer[__cls_call_iBufferPos]); + __cls_call_iBufferPos++; + } + case CMP_String: { + static iSize; iSize = get_string(iParam, __cls_rgBuffer[__cls_call_iBufferPos], charsmax(__cls_rgBuffer) - __cls_call_iBufferPos); + callfunc_push_str(__cls_rgBuffer[__cls_call_iBufferPos]); + __cls_call_iBufferPos += iSize; + } + case CMP_Array, CMP_ArrayRef, CMP_StringRef: { + get_array(iParam, __cls_rgBuffer[__cls_call_iBufferPos], iSize); + callfunc_push_array(__cls_rgBuffer[__cls_call_iBufferPos], iSize, iType == CMP_ArrayRef); + __cls_call_iBufferPos += iSize; + } + } + + __cls_call_iParamsNum++; +} + +stock Class:ClassInstanceGetClass(const &ClassInstance:pInstance) { + return __cls_GetInstanceClass(pInstance); +} + +stock bool:ClassInstanceHasMember(const &ClassInstance:pInstance, const szMember[]) { + static Trie:itMembers; itMembers = __cls_GetInstanceMembersTrie(pInstance, false); + + if (itMembers == Invalid_Trie) return false; + + return TrieKeyExists(itMembers, szMember); +} + +stock ClassInstanceDeleteMember(const &ClassInstance:pInstance, const szMember[]) { + static Trie:itMembers; itMembers = __cls_GetInstanceMembersTrie(pInstance, false); + + if (itMembers == Invalid_Trie) return; + + TrieDeleteKey(itMembers, szMember); +} + +stock any:ClassInstanceGetMember(const &ClassInstance:pInstance, const szMember[]) { + static Trie:itGetters; itGetters = __cls_GetInstanceGettersTrie(pInstance); + + if (itGetters != Invalid_Trie && TrieKeyExists(itGetters, szMember)) { + return __cls_CallCellGetter(pInstance, szMember); + } + + static Trie:itMembers; itMembers = __cls_GetInstanceMembersTrie(pInstance, false); + + if (itMembers == Invalid_Trie) return 0; + + static any:value; + return TrieGetCell(itMembers, szMember, value) ? value : 0; +} + +stock ClassInstanceSetMember(const &ClassInstance:pInstance, const szMember[], any:value, bool:bReplace = true) { + static Trie:itSetters; itSetters = __cls_GetInstanceSettersTrie(pInstance); + + if (itSetters != Invalid_Trie && TrieKeyExists(itSetters, szMember)) { + __cls_CallCellSetter(pInstance, szMember, value); + return; + } + + static Trie:itMembers; itMembers = __cls_GetInstanceMembersTrie(pInstance, true); + + TrieSetCell(itMembers, szMember, value, bReplace); +} + +stock bool:ClassInstanceGetMemberString(const &ClassInstance:pInstance, const szMember[], szOut[], iMaxLen) { + static Trie:itGetters; itGetters = __cls_GetInstanceGettersTrie(pInstance); + + if (itGetters != Invalid_Trie && TrieKeyExists(itGetters, szMember)) { + return __cls_CallStringGetter(pInstance, szMember, szOut, iMaxLen); + } + + static Trie:itMembers; itMembers = __cls_GetInstanceMembersTrie(pInstance, false); + + if (itMembers == Invalid_Trie) return false; + + copy(szOut, iMaxLen, NULL_STRING); + return !!TrieGetString(itMembers, szMember, szOut, iMaxLen); +} + +stock ClassInstanceSetMemberString(const &ClassInstance:pInstance, const szMember[], const szValue[], bool:bReplace = true) { + static Trie:itSetters; itSetters = __cls_GetInstanceSettersTrie(pInstance); + + if (itSetters != Invalid_Trie && TrieKeyExists(itSetters, szMember)) { + __cls_CallStringSetter(pInstance, szMember, szValue); + return; + } + + static Trie:itMembers; itMembers = __cls_GetInstanceMembersTrie(pInstance, true); + + TrieSetString(itMembers, szMember, szValue, bReplace); +} + +stock bool:ClassInstanceGetMemberArray(const &ClassInstance:pInstance, const szMember[], any:rgOut[], iLen) { + static Trie:itGetters; itGetters = __cls_GetInstanceGettersTrie(pInstance); + + if (itGetters != Invalid_Trie && TrieKeyExists(itGetters, szMember)) { + return __cls_CallArrayGetter(pInstance, szMember, rgOut, iLen); + } + + static Trie:itMembers; itMembers = __cls_GetInstanceMembersTrie(pInstance, false); + + if (itMembers == Invalid_Trie) return false; + + return !!TrieGetArray(itMembers, szMember, rgOut, iLen); +} + +stock ClassInstanceSetMemberArray(const &ClassInstance:pInstance, const szMember[], const any:rgValue[], iLen, bool:bReplace = true) { + static Trie:itSetters; itSetters = __cls_GetInstanceSettersTrie(pInstance); + + if (itSetters != Invalid_Trie && TrieKeyExists(itSetters, szMember)) { + __cls_CallArraySetter(pInstance, szMember, rgValue, iLen); + return; + } + + static Trie:itMembers; itMembers = __cls_GetInstanceMembersTrie(pInstance, true); + + TrieSetArray(itMembers, szMember, rgValue, iLen, bReplace); +} + +stock bool:ClassInstanceIsInstanceOf(const &ClassInstance:pInstance, const &Class:class) { + static Class:cInstance; cInstance = __cls_GetInstanceClass(pInstance); + + return ClassIsChildOf(cInstance, class); +} + +stock bool:ClassIsChildOf(const &Class:class, const &Class:cOther) { + static Class:sCurrentClass; sCurrentClass = class; + + do { + if (sCurrentClass == cOther) return true; + sCurrentClass = StructGetCell(Struct:sCurrentClass, __cls_Data_Base); + } while (sCurrentClass != Invalid_Class); + + return false; +} + +/*--------------------------------[ Internal Functions] --------------------------------*/ + +stock Class:__cls_GetInstanceClass(const &ClassInstance:pInstance) { + if (pInstance != __cls_cache_InstanceClass[__cls_InstanceCache_Instance]) { + __cls_cache_InstanceClass[__cls_InstanceCache_Value] = StructGetCell(Struct:pInstance, __cls_InstanceData_Class); + __cls_cache_InstanceClass[__cls_InstanceCache_Instance] = pInstance; + } + + return __cls_cache_InstanceClass[__cls_InstanceCache_Value]; +} + +stock Trie:__cls_GetInstanceMembersTrie(const &ClassInstance:pInstance, bool:bWrite = false) { + if ( + pInstance != __cls_cache_InstanceMembers[__cls_InstanceCache_Instance] || + (__cls_cache_InstanceMembers[__cls_InstanceCache_Value] == Invalid_Trie && bWrite) + ) { + __cls_cache_InstanceMembers[__cls_InstanceCache_Value] = StructGetCell(Struct:pInstance, __cls_InstanceData_Members); + + if (__cls_cache_InstanceMembers[__cls_InstanceCache_Value] == Invalid_Trie && bWrite) { + __cls_cache_InstanceMembers[__cls_InstanceCache_Value] = TrieCreate(); + StructSetCell(Struct:pInstance, __cls_InstanceData_Members, __cls_cache_InstanceMembers[__cls_InstanceCache_Value]); + } + + __cls_cache_InstanceMembers[__cls_InstanceCache_Instance] = pInstance; + } + + return __cls_cache_InstanceMembers[__cls_InstanceCache_Value]; +} + +stock Trie:__cls_GetInstanceGettersTrie(const &ClassInstance:pInstance) { + if (pInstance != __cls_cache_InstanceGetters[__cls_InstanceCache_Instance]) { + static Class:class; class = __cls_GetInstanceClass(pInstance); + __cls_cache_InstanceGetters[__cls_InstanceCache_Value] = __cls_GetClassMethodsTrie(class, __cls_MethodType_Getter, false); + __cls_cache_InstanceGetters[__cls_InstanceCache_Instance] = pInstance; + } + + return __cls_cache_InstanceGetters[__cls_InstanceCache_Value]; +} + +stock Trie:__cls_GetInstanceSettersTrie(const &ClassInstance:pInstance) { + if (pInstance != __cls_cache_InstanceSetters[__cls_InstanceCache_Instance]) { + static Class:class; class = __cls_GetInstanceClass(pInstance); + __cls_cache_InstanceSetters[__cls_InstanceCache_Value] = __cls_GetClassMethodsTrie(class, __cls_MethodType_Setter, false); + __cls_cache_InstanceSetters[__cls_InstanceCache_Instance] = pInstance; + } + + return __cls_cache_InstanceSetters[__cls_InstanceCache_Value]; +} + +stock __cls_ParseParamTypes(&Array:irgArgs, &Array:irgParamTypes) { + new iArgc = ArraySize(irgArgs); + + new rgParam[__cls_MethodParamData]; + + for (new iArg = 0; iArg < iArgc; ++iArg) { + rgParam[__cls_MethodParamData_Type] = ArrayGetCell(irgArgs, iArg); + rgParam[__cls_MethodParamData_Size] = 1; + + switch (rgParam[__cls_MethodParamData_Type]) { + case CMP_Array, CMP_ArrayRef, CMP_StringRef: { + rgParam[__cls_MethodParamData_Size] = ArrayGetCell(irgArgs, ++iArg); + } + case CMP_ParamsCellArray: { + rgParam[__cls_MethodParamData_Size] = 0; + + new Array:irgExtraParamTypes = ArrayGetCell(irgArgs, ++iArg); + __cls_ParseParamTypes(irgExtraParamTypes, irgParamTypes); + } + } + + if (rgParam[__cls_MethodParamData_Size]) { + ArrayPushArray(irgParamTypes, rgParam[any:0], _:__cls_MethodParamData); + } + } +} + +stock __cls_InitMethodCall(const &ClassInstance:pInstance, const szMethod[], const &Class:class = Invalid_Class, __cls_MethodType:iMethodType) { + __cls_call_class = class == Invalid_Class ? __cls_GetCallMethodClass(pInstance) : class; + __cls_call_pInstance = pInstance; + __cls_call_sMethod = __cls_FindClassMethodInHierarchy(__cls_call_class, szMethod, iMethodType); + __cls_call_iParamsNum = 0; + __cls_call_iLastRefArg = -1; + + if (__cls_call_sMethod == Invalid_Struct) { + set_fail_state(__cls_err_MethodNotFoundInBaseClass, szMethod); + return; + } + + __cls_call_iParamTypesNum = StructGetCell(__cls_call_sMethod, __cls_MethodData_ParamTypesNum); + __cls_call_iRequiredParamsNum = __cls_call_iParamTypesNum; +} + +stock __cls_InitMethodCallByPointer(const &ClassInstance:pInstance, const &Struct:sMethod) { + __cls_call_class = StructGetCell(sMethod, __cls_MethodData_Class); + __cls_call_pInstance = pInstance; + __cls_call_sMethod = sMethod; + __cls_call_iParamsNum = 0; + __cls_call_iLastRefArg = -1; + __cls_call_iParamTypesNum = StructGetCell(__cls_call_sMethod, __cls_MethodData_ParamTypesNum); + __cls_call_iRequiredParamsNum = __cls_call_iParamTypesNum; +} + +stock __cls_FreeMethodCall() { + __cls_call_class = Invalid_Class; + __cls_call_pInstance = Invalid_ClassInstance; + __cls_call_sMethod = Invalid_Struct; + __cls_call_iParamsNum = 0; + __cls_call_iBufferPos = 0; + __cls_call_iLastRefArg = -1; + __cls_call_iParamTypesNum = 0; + __cls_call_iRequiredParamsNum = 0; +} + +stock Function:__cls_GetCallMethodFunctionPointer() { + static Function:func; func = StructGetCell(__cls_call_sMethod, __cls_MethodData_Function); + + if (func == Invalid_FunctionPointer) { + static szMethod[CLASS_METHOD_MAX_NAME_LENGTH]; StructGetString(__cls_call_sMethod, __cls_MethodData_Name, szMethod, charsmax(szMethod)); + set_fail_state(__cls_err_NotImplementedMethod, szMethod); + } + + return func; +} + +stock __cls_CallMethodBegin(const &ClassInstance:pInstance, const szMethod[], const &Class:class = Invalid_Class, __cls_MethodType:iMethodType = __cls_MethodType_Method) { + __cls_InitMethodCall(pInstance, szMethod, class, iMethodType); + + // Check for virtual method call + // If we are already in the execution context and the method is virual then jump to top level context + // All Getters and Setter works like virtual methods + if (__cls_callstack_size) { + if (StructGetCell(__cls_call_sMethod, __cls_MethodData_Type) != __cls_MethodType_Method) { + static Class:sInstanceClass; sInstanceClass = __cls_GetInstanceClass(pInstance); + if (sInstanceClass != StructGetCell(__cls_call_sMethod, __cls_MethodData_Class)) { + __cls_call_sMethod = __cls_FindClassMethodInHierarchy(sInstanceClass, szMethod, iMethodType); + } + } + } + + if (__cls_call_sMethod == Invalid_Struct) { + set_fail_state(__cls_err_MethodNotFound, szMethod, pInstance); + return; + } + + static Function:func; func = __cls_GetCallMethodFunctionPointer(); + + if (callfunc_begin_p(func) != 1) { + static szMethod[CLASS_METHOD_MAX_NAME_LENGTH]; StructGetString(__cls_call_sMethod, __cls_MethodData_Name, szMethod, charsmax(szMethod)); + set_fail_state(__cls_err_FailedToCallMethodFunction, szMethod); + return; + } +} + +stock __cls_CallMethodByPointerBegin(const &ClassInstance:pInstance, const &Struct:sMethod) { + __cls_InitMethodCallByPointer(pInstance, sMethod); + + static Function:func; func = __cls_GetCallMethodFunctionPointer(); + + if (callfunc_begin_p(func) != 1) { + static szMethod[CLASS_METHOD_MAX_NAME_LENGTH]; StructGetString(__cls_call_sMethod, __cls_MethodData_Name, szMethod, charsmax(szMethod)); + set_fail_state(__cls_err_FailedToCallMethodFunction, szMethod); + return; + } +} + +stock any:__cls_CallCellGetter(const &ClassInstance:pInstance, const szMember[]) { + __cls_CallMethodBegin(pInstance, szMember, _, __cls_MethodType_Getter); + + return ClassInstanceCallMethodEnd(); +} + +stock any:__cls_CallArrayGetter(const &ClassInstance:pInstance, const szMember[], any:rgOut[], iSize = -1) { + __cls_CallMethodBegin(pInstance, szMember, _, __cls_MethodType_Getter); + + if (iSize == -1) { + iSize = StructGetCell(__cls_call_sMethod, __cls_MethodData_ParamTypes, _:__cls_MethodParamData_Size); + } + + ClassInstanceCallMethodPushParamArrayRef(rgOut, iSize); + ClassInstanceCallMethodPushParamCell(iSize); + + return ClassInstanceCallMethodEnd(); +} + +stock any:__cls_CallStringGetter(const &ClassInstance:pInstance, const szMember[], any:rgOut[], iMaxLen) { + __cls_CallMethodBegin(pInstance, szMember, _, __cls_MethodType_Getter); + + ClassInstanceCallMethodPushParamStringRef(rgOut, iMaxLen); + ClassInstanceCallMethodPushParamCell(iMaxLen); + rgOut[iMaxLen + 1] = '^0'; + + return ClassInstanceCallMethodEnd(); +} + +stock __cls_CallCellSetter(const &ClassInstance:pInstance, const szMember[], any:value) { + __cls_CallMethodBegin(pInstance, szMember, _, __cls_MethodType_Setter); + + ClassInstanceCallMethodPushParamCell(value); + + return ClassInstanceCallMethodEnd(); +} + +stock __cls_CallArraySetter(const &ClassInstance:pInstance, const szMember[], const any:rgValue[], iSize = -1) { + __cls_CallMethodBegin(pInstance, szMember, _, __cls_MethodType_Setter); + + if (iSize == -1) { + iSize = StructGetCell(__cls_call_sMethod, __cls_MethodData_ParamTypes, _:__cls_MethodParamData_Size); + } + + ClassInstanceCallMethodPushParamArray(rgValue, iSize); + ClassInstanceCallMethodPushParamCell(iSize); + + return ClassInstanceCallMethodEnd(); +} + +stock __cls_CallStringSetter(const &ClassInstance:pInstance, const szMember[], const szValue[]) { + __cls_CallMethodBegin(pInstance, szMember, _, __cls_MethodType_Setter); + + ClassInstanceCallMethodPushParamString(szValue); + + return ClassInstanceCallMethodEnd(); +} + +stock __cls_DestroyMethod(&Struct:sMethod) { + new Array:irgParamTypes; StructGetCell(sMethod, __cls_MethodData_ParamTypes); + + if (irgParamTypes != Invalid_Array) { + ArrayDestroy(irgParamTypes); + } + + StructDestroy(sMethod); +} + +stock __cls_DestroyMethodsTrie(&Trie:itMethods) { + new TrieIter:iMethodsIter = TrieIterCreate(itMethods); + + while (!TrieIterEnded(iMethodsIter)) { + new Struct:sMethod; TrieIterGetCell(iMethodsIter, sMethod); + __cls_DestroyMethod(sMethod); + TrieIterNext(iMethodsIter); + } + + TrieIterDestroy(iMethodsIter); + + TrieDestroy(itMethods); +} + +stock __cls_AddMemberAccessorMethod(const &Class:class, const szMember[], const Function:func, __cls_MethodType:iMethodType, iType, iSize) { + if (iMethodType != __cls_MethodType_Getter && iMethodType != __cls_MethodType_Setter) return; + + new Trie:itMethods = __cls_GetClassMethodsTrie(class, iMethodType, true); + + new Array:irgParamTypes; irgParamTypes = ArrayCreate(_:__cls_MethodParamData, 1); + + // Input value or output reference + if (iMethodType == __cls_MethodType_Setter || (iType == CMP_String || iType == CMP_Array)) { + new rgParam[__cls_MethodParamData]; + rgParam[__cls_MethodParamData_Type] = __cls_ResolveAccessorParamType(iType, iMethodType); + rgParam[__cls_MethodParamData_Size] = iSize; + + ArrayPushArray(irgParamTypes, rgParam[any:0], _:__cls_MethodParamData); + } + + // Max size for arrays and string getters + if (iType == CMP_Array || (iType == CMP_String && iMethodType == __cls_MethodType_Getter)) { + new rgSizeParam[__cls_MethodParamData]; + rgSizeParam[__cls_MethodParamData_Type] = CMP_Cell; + rgSizeParam[__cls_MethodParamData_Size] = 1; + + ArrayPushArray(irgParamTypes, rgSizeParam[any:0], _:__cls_MethodParamData); + } + + new Struct:sMethod = __cls_CreateMethod(class, szMember, func, irgParamTypes, iMethodType); + + ArrayDestroy(irgParamTypes); + + TrieSetCell(itMethods, szMember, sMethod); +} + +stock __cls_ResolveAccessorParamType(iType, __cls_MethodType:iMethodType) { + if (iMethodType == __cls_MethodType_Getter) { + switch (iType) { + case CMP_String: return CMP_StringRef; + case CMP_Array: return CMP_ArrayRef; + } + } + + return iType; +} + +stock __cls_IsRefType(iType) { + switch (iType) { + case CMP_CellRef, CMP_ArrayRef, CMP_StringRef: return true; + } + + return false; +} + +stock Struct:__cls_CreateMethod(Class:class, const szName[], const Function:func, Array:irgParamTypes, __cls_MethodType:iType = __cls_MethodType_Method, bool:bVariadicParams = false) { + new Struct:sMethod = StructCreate(__cls_MethodData); + StructSetCell(sMethod, __cls_MethodData_Class, class); + StructSetString(sMethod, __cls_MethodData_Name, szName); + StructSetCell(sMethod, __cls_MethodData_Function, func); + StructSetCell(sMethod, __cls_MethodData_Type, iType); + StructSetCell(sMethod, __cls_MethodData_VariadicParams, bVariadicParams); + + new iParamTypesNum = ArraySize(irgParamTypes); + for (new iParam = 0; iParam < iParamTypesNum; ++iParam) { + StructSetCell(sMethod, __cls_MethodData_ParamTypes, ArrayGetCell(irgParamTypes, iParam, __cls_MethodParamData_Type), __cls_MethodParamOffset(iParam) + _:__cls_MethodParamData_Type); + StructSetCell(sMethod, __cls_MethodData_ParamTypes, ArrayGetCell(irgParamTypes, iParam, __cls_MethodParamData_Size), __cls_MethodParamOffset(iParam) + _:__cls_MethodParamData_Size); + } + + StructSetCell(sMethod, __cls_MethodData_ParamTypesNum, iParamTypesNum); + + return sMethod; +} + +stock __cls_CompareParamTypes(const &Struct:sMethod, const &Array:irgParams) { + new iParamsNum = StructGetCell(sMethod, __cls_MethodData_ParamTypesNum); + new iParamsSize = ArraySize(irgParams); + + if (iParamsNum != iParamsSize) return false; + + for (new iParam = 0; iParam < iParamsNum; ++iParam) { + new iType = StructGetCell(sMethod, __cls_MethodData_ParamTypes, __cls_MethodParamOffset(iParam) + _:__cls_MethodParamData_Type); + if (iType != ArrayGetCell(irgParams, iParam, _:__cls_MethodParamData_Type)) return false; + + new iSize = StructGetCell(sMethod, __cls_MethodData_ParamTypes, __cls_MethodParamOffset(iParam) + _:__cls_MethodParamData_Size); + if (iSize != ArrayGetCell(irgParams, iParam, _:__cls_MethodParamData_Size)) return false; + } + + return true; +} + +stock any:__cls_ExecuteMethod() { + __cls_PushMethodCallToCallStack(); + + __cls_LogExecution(); + + new any:result = callfunc_end(); + + __cls_PopMethodFromCallStack(); + + return result; +} + +stock Struct:__cls_FindClassMethodInHierarchy(const &Class:class, const szMethod[], __cls_MethodType:iMethodType) { + new Class:sCurrentClass = class; + + static iStructMethodsField = __cls_MethodType_Invalid; + switch (iMethodType) { + case __cls_MethodType_Method, __cls_MethodType_Virtual: { + iStructMethodsField = __cls_Data_Methods; + } + case __cls_MethodType_Getter: { + iStructMethodsField = __cls_Data_Getters; + } + case __cls_MethodType_Setter: { + iStructMethodsField = __cls_Data_Setters; + } + } + + do { + static Trie:itMethods; itMethods = StructGetCell(Struct:sCurrentClass, iStructMethodsField); + + if (itMethods != Invalid_Trie) { + static Struct:sMethod; + if (TrieGetCell(itMethods, szMethod, sMethod)) return sMethod; + } + + sCurrentClass = StructGetCell(Struct:sCurrentClass, __cls_Data_Base); + } while (sCurrentClass != Invalid_Class); + + return Invalid_Struct; +} + +stock __cls_PushMethodCallToCallStack() { + if (__cls_callstack_size >= CLASS_METHOD_CALL_STACK_SIZE) { + set_fail_state(__cls_err_MaxCallStackSizeExceeded); + return; + } + + __cls_callstack_pInstance[__cls_callstack_size] = any:__cls_call_pInstance; + __cls_callstack_class[__cls_callstack_size] = any:StructGetCell(__cls_call_sMethod, __cls_MethodData_Class); + __cls_callstack_sMethod[__cls_callstack_size] = any:__cls_call_sMethod; + __cls_callstack_iBufferPos[__cls_callstack_size] = __cls_call_iBufferPos; + __cls_callstack_iLastRefArg[__cls_callstack_size] = __cls_call_iLastRefArg; + + __cls_callstack_size++; +} + +stock __cls_PopMethodFromCallStack() { + __cls_callstack_size--; + + if (__cls_callstack_size) { + __cls_call_pInstance = __cls_callstack_pInstance[__cls_callstack_iLastItem]; + __cls_call_sMethod = __cls_callstack_sMethod[__cls_callstack_iLastItem]; + __cls_call_iBufferPos = __cls_callstack_iBufferPos[__cls_callstack_iLastItem]; + __cls_call_iLastRefArg = __cls_callstack_iLastRefArg[__cls_callstack_iLastItem]; + } else { + __cls_call_iBufferPos = 0; + } +} + +stock Class:__cls_GetCallMethodClass(const &ClassInstance:pInstance) { + if (__cls_callstack_size) { + if (__cls_callstack_pInstance[__cls_callstack_iLastItem] == pInstance) { + return __cls_callstack_class[__cls_callstack_iLastItem]; + } + } + + return __cls_GetInstanceClass(pInstance); +} + +stock Trie:__cls_GetClassMembersTrie(const &Class:class, bool:bWrite = false) { + static Trie:itMembers; itMembers = StructGetCell(Struct:class, __cls_Data_Members); + + if (itMembers == Invalid_Trie && bWrite) { + itMembers = TrieCreate(); + StructSetCell(Struct:class, __cls_Data_Members, itMembers); + } + + return itMembers; +} + +stock Trie:__cls_GetClassMethodsTrie(const &Class:class, __cls_MethodType:iMethodType, bool:bWrite = false) { + static iStructMethodsField = __cls_MethodType_Invalid; + switch (iMethodType) { + case __cls_MethodType_Method, __cls_MethodType_Virtual: { + iStructMethodsField = __cls_Data_Methods; + } + case __cls_MethodType_Getter: { + iStructMethodsField = __cls_Data_Getters; + } + case __cls_MethodType_Setter: { + iStructMethodsField = __cls_Data_Setters; + } + } + + static Trie:itMethods; itMethods = StructGetCell(Struct:class, iStructMethodsField); + + if (itMethods == Invalid_Trie && bWrite) { + itMethods = TrieCreate(); + StructSetCell(Struct:class, iStructMethodsField, itMethods); + } + + return itMethods; +} + +stock __cls_LogExecution() { + #if defined __cls_DEBUG + static Struct:sClass; sClass = StructGetCell(__cls_call_sMethod, __cls_MethodData_Class); + static szName[32]; ClassGetMetadataString(Class:sClass, "__NAME", szName, charsmax(szName)); + static szMethodName[CLASS_METHOD_MAX_NAME_LENGTH]; StructGetString(__cls_call_sMethod, __cls_MethodData_Name, szMethodName, charsmax(szMethodName)); + + if (equal(szName, NULL_STRING)) { + format(szName, charsmax(szName), "CLASS_%d", sClass); + } + + __cls_DebugMessage("[%d] %s::%s(...)", __cls_call_pInstance, szName, szMethodName); + #endif +} + +stock __cls_DebugMessage(const szFormat[], any:...) { + #if defined __cls_DEBUG + static szMessage[MAX_FMT_LENGTH]; + vformat(szMessage, charsmax(szMessage), szFormat, 2); + engfunc(EngFunc_AlertMessage, at_aiconsole, "%s^n", szMessage); + #endif +} diff --git a/util/cellstruct.inc b/util/cellstruct.inc new file mode 100644 index 0000000..5f6a2ed --- /dev/null +++ b/util/cellstruct.inc @@ -0,0 +1,117 @@ +#if defined _cellstruct_included + #endinput +#endif + +#define _cellstruct_included + +#include +#include + +enum Struct { + Invalid_Struct = 0 +}; + +stock Struct:StructCreate(any:interface) { + new Struct:struct = Struct:ArrayCreate(_, interface); + ArrayResize(Array:struct, interface); + return struct; +} + +stock Struct:StructFromArray(any:interface, const any:data[any:0]) { + new Struct:struct = StructCreate(interface); + StructSetArray(struct, 0, data, interface); + return struct; +} + +stock Struct:StructClone(const &Struct:which) { + return Struct:ArrayClone(Array:which); +} + +stock StructSize(const &Struct:which) { + return ArraySize(Array:which); +} + +stock StructCopy(const &Struct:which, &Struct:other) { + new size = min(StructSize(which), StructSize(other)); + + for (new i = 0; i < size; ++i) { + StructSetCell(other, i, StructGetCell(which, i)); + } +} + +stock StructDestroy(&Struct:which) { + ArrayDestroy(Array:which); + which = Invalid_Struct; +} + +stock any:StructGetCell(const &Struct:which, any:item, any:block = 0, bool:asChar = false) { + return ArrayGetCell(Array:which, item + block, _, asChar); +} + +stock StructSetCell(const &Struct:which, any:item, any:value, any:block = 0, bool:asChar = false) { + ArraySetCell(Array:which, item + block, value, _, asChar); +} + +stock StructGetArray(const &Struct:which, any:item, any:output[any:0], any:size, any:block = 0) { + for (new i = 0; i < size; ++i) { + output[i] = StructGetCell(which, item, block + i); + } +} + +stock StructSetArray(const &Struct:which, any:item, const any:input[any:0], any:size, any:block = 0) { + for (new i = 0; i < size; ++i) { + StructSetCell(which, item, input[i], block + i); + } +} + +stock StructGetString(const &Struct:which, any:item, output[], any:len, any:block = 0) { + for (new i = 0; i < len; ++i) { + output[i] = StructGetCell(which, item, block + i); + + if (output[i] == '^0') { + break; + } + } +} + +stock DoNotUse:StructGetStringHandle(const &Struct:which, any:item, any:block = 0) { + return ArrayGetStringHandle(Array:which, item + block); +} + +stock StructSetString(const &Struct:which, any:item, const input[], any:len = -1, any:block = 0) { + for (new i = 0; i < len || len == -1; ++i) { + StructSetCell(which, item, input[i], block + i); + + if (input[i] == '^0') { + break; + } + } +} + +stock StructStringify(const &Struct:which, output[], len) { + new size = StructSize(which); + new JSON:jsonData = json_init_array(); + + for (new i = 0; i < size; ++i) { + json_array_append_number(jsonData, StructGetCell(which, i)); + } + + json_serial_to_string(jsonData, output, len); + + json_free(jsonData); +} + +stock Struct:StructFromString(const &Struct:which, const input[]) { + new JSON:jsonData = json_parse(input); + new size = json_array_get_count(jsonData); + + new Struct:sStruct = StructCreate(size); + + for (new i = 0; i < size; ++i) { + StructSetCell(sStruct, i, json_array_get_number(jsonData, i)); + } + + json_free(jsonData); + + return sStruct; +} diff --git a/util/command_util.inc b/util/command_util.inc new file mode 100644 index 0000000..5a028b8 --- /dev/null +++ b/util/command_util.inc @@ -0,0 +1,122 @@ +#if defined _command_util_included + #endinput +#endif +#define _command_util_included + +#include +#include + +enum (<<=1) { + CMD_TARGET_ALL, + CMD_TARGET_NO_ONE = 1, + CMD_TARGET_HAS_CALLER, + CMD_TARGET_CALLER, + CMD_TARGET_IGNORE_CALLER, + CMD_TARGET_ALIVE, + CMD_TARGET_DEAD, + CMD_TARGET_HUMAN, + CMD_TARGET_BOT, + CMD_TARGET_ADMIN, + CMD_TARGET_VIEWCONE, + CMD_TARGET_VISIBLE, + CMD_TARGET_TEAM_T, + CMD_TARGET_TEAM_CT, + CMD_TARGET_TEAM_SPEC +}; + +stock CMD_RESOLVE_TARGET(const szTarget[]) { + if (szTarget[0] == '@') { + return -CMD_RESOLVE_ALIASES_BITS(szTarget[1]); + } else if (szTarget[0] == '#') { + static pTarget; pTarget = find_player("k", str_to_num(szTarget[1])); + if (pTarget) return pTarget; + } else { + static pTarget; pTarget = find_player("b", szTarget); + if (pTarget) return pTarget; + } + + return CMD_TARGET_NO_ONE; +} + +stock CMD_RESOLVE_ALIASES_BITS(const szAliases[]) { + static iBits; iBits = 0; + static iAliasStartPos; iAliasStartPos = 0; + static iCursor; iCursor = 0; + + do { + if (iCursor && (szAliases[iCursor] == '_' || szAliases[iCursor] == '^0')) { + if (iCursor > iAliasStartPos && szAliases[iAliasStartPos] != '_') { + iBits |= CMD_RESOLVE_ALIAS_BITS(szAliases[iAliasStartPos], iCursor - iAliasStartPos); + static szSubAlias[32]; copy(szSubAlias, iCursor - iAliasStartPos, szAliases[iAliasStartPos]); + } + + iAliasStartPos = iCursor + 1; + } + + iCursor++; + } while (szAliases[iCursor - 1] != '^0'); + + return iBits; +} + +stock CMD_RESOLVE_ALIAS_BITS(const szAlias[], iMaxLen = 0) { + if (equal(szAlias, "all", iMaxLen)) return CMD_TARGET_ALL; + else if (equal(szAlias, "alive", iMaxLen)) return CMD_TARGET_ALIVE; + else if (equal(szAlias, "dead", iMaxLen)) return CMD_TARGET_DEAD; + else if (equal(szAlias, "human", iMaxLen)) return CMD_TARGET_HUMAN; + else if (equal(szAlias, "bot", iMaxLen)) return CMD_TARGET_BOT; + else if (equal(szAlias, "admin", iMaxLen)) return CMD_TARGET_ADMIN; + else if (equal(szAlias, "t", iMaxLen)) return CMD_TARGET_TEAM_T; + else if (equal(szAlias, "ct", iMaxLen)) return CMD_TARGET_TEAM_CT; + else if (equal(szAlias, "spec", iMaxLen)) return CMD_TARGET_TEAM_SPEC; + else if (equal(szAlias, "me", iMaxLen)) return CMD_TARGET_HAS_CALLER | CMD_TARGET_CALLER; + else if (equal(szAlias, "notme", iMaxLen)) return CMD_TARGET_HAS_CALLER | CMD_TARGET_IGNORE_CALLER; + else if (equal(szAlias, "view", iMaxLen)) return CMD_TARGET_HAS_CALLER | CMD_TARGET_VIEWCONE | CMD_TARGET_VISIBLE | CMD_TARGET_ALIVE; + + return CMD_TARGET_NO_ONE; +} + +stock bool:CMD_SHOULD_TARGET_PLAYER(pPlayer, iTarget, pCaller = 0) { + if (!is_user_connected(pPlayer)) return false; + if (iTarget == pPlayer) return true; + if (iTarget > 0) return false; + + static iTargetBits; iTargetBits = -iTarget; + + if (!iTargetBits) return true; + if (iTargetBits & CMD_TARGET_ALL) return true; + if (iTargetBits & CMD_TARGET_NO_ONE) return false; + if (iTargetBits & CMD_TARGET_HAS_CALLER && !pCaller) return false; + if (iTargetBits & CMD_TARGET_CALLER && pPlayer != pCaller) return false; + if (iTargetBits & CMD_TARGET_IGNORE_CALLER && pPlayer == pCaller) return false; + + static bool:bAlive; bAlive = !!is_user_alive(pPlayer); + if (iTargetBits & CMD_TARGET_ALIVE && !bAlive) return false; + if (iTargetBits & CMD_TARGET_DEAD && bAlive) return false; + + static bool:bBot; bBot = !!is_user_bot(pPlayer); + if (iTargetBits & CMD_TARGET_HUMAN && bBot) return false; + if (iTargetBits & CMD_TARGET_BOT && !bBot) return false; + + static iFlags; iFlags = get_user_flags(pPlayer); + if (iTargetBits & CMD_TARGET_ADMIN && ~iFlags & ADMIN_ADMIN) return false; + + if (cstrike_running()) { + #if AMXX_VERSION_NUM > 182 + static iTeam; iTeam = get_ent_data(pPlayer, "CBasePlayer", "m_iTeam"); + + if (iTargetBits & CMD_TARGET_TEAM_T && iTeam != 1) return false; + if (iTargetBits & CMD_TARGET_TEAM_CT && iTeam != 2) return false; + if (iTargetBits & CMD_TARGET_TEAM_SPEC && iTeam != 0 && iTeam != 3) return false; + #endif + } + + if (iTargetBits & CMD_TARGET_VISIBLE && !is_visible(pCaller, pPlayer)) return false; + + if (iTargetBits & CMD_TARGET_VIEWCONE) { + static Float:vecOrigin[3]; pev(pPlayer, pev_origin, vecOrigin); + if (!is_in_viewcone(pCaller, vecOrigin)) return false; + } + + return true; +} diff --git a/util/datapack_stocks.inc b/util/datapack_stocks.inc new file mode 100644 index 0000000..d662bc1 --- /dev/null +++ b/util/datapack_stocks.inc @@ -0,0 +1,78 @@ +#if defined _datapack_stocks_included + #endinput +#endif +#define _datapack_stocks_included + +#if !defined _datapack_included + #tryinclude +#endif + +#if !defined _datapack_included + #endinput +#endif + +stock ReadPackArray(DataPack:pack, buffer[], maxlen = -1) { + static len; len = ReadPackCell(pack); + + if (maxlen != -1) len = min(maxlen, len); + + for (new i = 0; i < len; ++i) { + static value; value = ReadPackCell(pack); + buffer[i] = value; + } + + return len; +} + +stock ReadPackFloatArray(DataPack:pack, Float:buffer[], maxlen = -1) { + static len; len = ReadPackCell(pack); + + if (maxlen != -1) len = min(maxlen, len); + + for (new i = 0; i < len; ++i) { + static Float:value; value = ReadPackFloat(pack); + buffer[i] = value; + } + + return len; +} + +stock WritePackArray(DataPack:pack, const array[], maxlen) { + WritePackCell(pack, maxlen); + + for (new i = 0; i < maxlen; ++i) { + WritePackCell(pack, array[i]); + } +} + +stock WritePackFloatArray(DataPack:pack, const Float:array[], maxlen) { + WritePackCell(pack, maxlen); + + for (new i = 0; i < maxlen; ++i) { + WritePackFloat(pack, array[i]); + } +} + +stock GetDataPackOffsets(&cellOffset = 0, &floatOffset = 0, &stringOffset = 0, &charOffset = 0, &arrayOffset = 0) { + new DataPack:pack = CreateDataPack(); + + SetPackPosition(pack, DataPackPos:0); + WritePackCell(pack, DataPackPos:0); + cellOffset = _:GetPackPosition(pack); + + SetPackPosition(pack, DataPackPos:0); + WritePackCell(pack, DataPackPos:0); + floatOffset = _:GetPackPosition(pack); + + SetPackPosition(pack, DataPackPos:0); + WritePackString(pack, ""); + stringOffset = _:GetPackPosition(pack); + + SetPackPosition(pack, DataPackPos:0); + WritePackString(pack, "_"); + charOffset = _:GetPackPosition(pack) - stringOffset; + + arrayOffset = cellOffset; + + DestroyDataPack(pack); +} diff --git a/util/function_pointer.inc b/util/function_pointer.inc new file mode 100644 index 0000000..3279f20 --- /dev/null +++ b/util/function_pointer.inc @@ -0,0 +1,37 @@ +#if defined _function_pointer_included + #endinput +#endif +#define _function_pointer_included + +enum Function { + Invalid_FunctionPointer = 0 +}; + +stock Function:get_func_pointer(const szFunction[], iPluginId = -1) { + static iFunctionId; iFunctionId = get_func_id(szFunction, iPluginId); + + if (iPluginId == -1) { + iPluginId = get_plugin(-1); + } + + if (iPluginId >= 0xFFFF) return Invalid_FunctionPointer; + if (iFunctionId > 0xFFFF) return Invalid_FunctionPointer; + + return Function:(((iPluginId + 1) << 16) | (iFunctionId + 1)); +} + +stock get_pfunc_function(const &Function:function) { + return (_:function & 0xFFFF) - 1; +} + +stock get_pfunc_plugin(const &Function:function) { + return ((_:function >> 16) & 0xFFFF) - 1; +} + +stock bool:is_pfunc_local(const &Function:function) { + return get_pfunc_plugin(function) == -1; +} + +stock callfunc_begin_p(const &Function:function) { + return callfunc_begin_i(get_pfunc_function(function), get_pfunc_plugin(function)); +} diff --git a/util/stack.inc b/util/stack.inc new file mode 100644 index 0000000..9e1ce05 --- /dev/null +++ b/util/stack.inc @@ -0,0 +1,14 @@ +#if defined _stack_included + #endinput +#endif +#define _stack_included + +#if !defined STACK_MAX_SIZE + #define STACK_MAX_SIZE 256 +#endif + +#define STACK_DEFINE(%1) static __stack_%1_size = 0; static __stack_%1_data[STACK_MAX_SIZE] +#define STACK_PUSH(%1,%2) __stack_%1_data[__stack_%1_size++] = %2 +#define STACK_POP(%1) __stack_%1_data[--__stack_%1_size] +#define STACK_READ(%1) __stack_%1_data[__stack_%1_size - 1] +#define STACK_PATCH(%1,%2) __stack_%1_data[__stack_%1_size - 1] = %2