/* * ============================================================================ * * Zombie:Reloaded * * File: antistick.inc * Type: Module * Description: Antistick system. * * Copyright (C) 2009-2013 Greyscale, Richard Helgeby * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * ============================================================================ */ /** * @section Collision values. */ #define COLLISION_GROUP_NONE 0 /** Default; collides with static and dynamic objects. */ #define COLLISION_GROUP_DEBRIS 1 /** Collides with nothing but world and static stuff. */ #define COLLISION_GROUP_DEBRIS_TRIGGER 2 /** Same as debris, but hits triggers. */ #define COLLISION_GROUP_INTERACTIVE_DEBRIS 3 /** Collides with everything except other interactive debris or debris. */ #define COLLISION_GROUP_INTERACTIVE 4 /** Collides with everything except interactive debris or debris. */ #define COLLISION_GROUP_PLAYER 5 /** This is the default behavior expected for most prop_physics. */ #define COLLISION_GROUP_BREAKABLE_GLASS 6 /** Special group for glass debris. */ #define COLLISION_GROUP_VEHICLE 7 /** Collision group for driveable vehicles. */ #define COLLISION_GROUP_PLAYER_MOVEMENT 8 /** For HL2, same as Collision_Group_Player. */ #define COLLISION_GROUP_NPC 9 /** Generic NPC group. */ #define COLLISION_GROUP_IN_VEHICLE 10 /** For any entity inside a vehicle. */ #define COLLISION_GROUP_WEAPON 11 /** For any weapons that need collision detection. */ #define COLLISION_GROUP_VEHICLE_CLIP 12 /** Vehicle clip brush to restrict vehicle movement. */ #define COLLISION_GROUP_PROJECTILE 13 /** Projectiles. */ #define COLLISION_GROUP_DOOR_BLOCKER 14 /** Blocks entities not permitted to get near moving doors. */ #define COLLISION_GROUP_PASSABLE_DOOR 15 /** Doors that the player shouldn't collide with. */ #define COLLISION_GROUP_DISSOLVING 16 /** Things that are dissolving are in this group. */ #define COLLISION_GROUP_PUSHAWAY 17 /** Nonsolid on client and server, pushaway in player code. */ #define COLLISION_GROUP_NPC_ACTOR 18 /** Used so NPCs in scripts ignore the player. */ #define ANTISTICK_COLLISIONS_OFF COLLISION_GROUP_DEBRIS_TRIGGER #define ANTISTICK_COLLISIONS_ON COLLISION_GROUP_PLAYER /** * @endsection */ /** * Default player hull width. */ #define ANTISTICK_DEFAULT_HULL_WIDTH 32.0 /** * List of components that make up the model's rectangular boundaries. * * F = Front * B = Back * L = Left * R = Right * U = Upper * D = Down */ enum AntiStickBoxBound { BoxBound_FUR = 0, /** Front upper right */ BoxBound_FUL, /** etc.. */ BoxBound_FDR, BoxBound_FDL, BoxBound_BUR, BoxBound_BUL, BoxBound_BDR, BoxBound_BDL, } /** * Create commands related to antistick here. */ AntiStickOnCommandsCreate() { RegConsoleCmd("zr_antistick_dump_group", AntiStickDumpGroupCommand, "Dumps collision group data on one or more players. Usage zr_antistick_dump_group [#userid|name]"); } /** * Client is joining the server. * * @param client The client index. */ AntiStickClientInit(client) { SDKHook(client, SDKHook_StartTouch, AntiStickStartTouch); } /** * Unhook StartTouch and EndTouch function on a client. * * @param client The client index. */ AntiStickOnClientDisconnect(client) { SDKUnhook(client, SDKHook_StartTouch, AntiStickStartTouch); } /** * Callback function for StartTouch. * * @param client The client index. * @param entity The entity index of the entity being touched. */ public AntiStickStartTouch(client, entity) { // If antistick is disabled, then stop. new bool:antistick = GetConVarBool(g_hCvarsList[CVAR_ANTISTICK]); if (!antistick) { return; } // If client isn't in-game, then stop. if (!IsClientInGame(client)) { return; } // If client is touching themselves, then leave them alone :P if (client == entity) { return; } // If touched entity isn't a valid client, then stop. if (!ZRIsClientValid(entity)) { return; } // If the clients aren't colliding, then stop. if (!AntiStickIsModelBoxColliding(client, entity)) { return; } // From this point we know that client and entity is more or less within eachother. LogEvent(false, LogType_Normal, LOG_DEBUG, LogModule_AntiStick, "Collision", "Player \"%N\" and \"%N\" are intersecting. Removing collisions.", client, entity); // Get current collision groups of client and entity. new clientcollisiongroup = AntiStickGetCollisionGroup(client); // Note: If zombies get stuck on infection or stuck in a teleport, they'll // get the COLLISION_GROUP_PUSHAWAY collision group, so check this // one too. // If the client is in any other collision group than "off", than we must set them to off, to unstick. if (clientcollisiongroup != ANTISTICK_COLLISIONS_OFF) { // Disable collisions to unstick, and start timers to re-solidify. AntiStickSetCollisionGroup(client, ANTISTICK_COLLISIONS_OFF); CreateTimer(0.0, AntiStickSolidifyTimer, client, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT); } } /** * Callback for solidify timer. * * @param client The client index. */ public Action:AntiStickSolidifyTimer(Handle:timer, any:client) { // If client has left, then stop. if (!IsClientInGame(client)) { return Plugin_Stop; } // If the client is dead, then stop. if (!IsPlayerAlive(client)) { return Plugin_Stop; } // If the client's collisions are already on, then stop. if (AntiStickGetCollisionGroup(client) == ANTISTICK_COLLISIONS_ON) { return Plugin_Stop; } // Loop through all clients and check if client is stuck in them. for (new x = 1; x <= MaxClients; x++) { // If client isn't connected or in-game, then skip it. if (!IsClientConnected(x) || !IsClientInGame(x)) { continue; } // If the client is dead, then skip it. if (!IsPlayerAlive(x)) { continue; } // Don't compare the same clients. if (client == x) { continue; } // If the client is colliding with a client, then allow timer to continue. if (AntiStickIsModelBoxColliding(client, x)) { return Plugin_Continue; } } // Change collisions back to normal. AntiStickSetCollisionGroup(client, ANTISTICK_COLLISIONS_ON); // Debug message. May be useful when calibrating antistick. LogEvent(false, LogType_Normal, LOG_DEBUG, LogModule_AntiStick, "Collision", "Player \"%N\" is no longer intersecting anyone. Applying normal collisions.", client); return Plugin_Stop; } /** * Build the model box by finding all vertices. * * @param client The client index. * @param boundaries Array with 'AntiStickBoxBounds' for indexes to return bounds into. * @param width The width of the model box. */ stock AntiStickBuildModelBox(client, Float:boundaries[AntiStickBoxBound][3], Float:width) { new Float:clientloc[3]; new Float:twistang[3]; new Float:cornerang[3]; new Float:sideloc[3]; new Float:finalloc[4][3]; // Get needed vector info. GetClientAbsOrigin(client, clientloc); // Set the pitch to 0. twistang[1] = 90.0; cornerang[1] = 0.0; for (new x = 0; x < 4; x++) { // Jump to point on player's left side. AntiStickJumpToPoint(clientloc, twistang, width / 2, sideloc); // From this point, jump to the corner, which would be half the width from the middle of a side. AntiStickJumpToPoint(sideloc, cornerang, width / 2, finalloc[x]); // Twist 90 degrees to find next side/corner. twistang[1] += 90.0; cornerang[1] += 90.0; // Fix angles. if (twistang[1] > 180.0) { twistang[1] -= 360.0; } if (cornerang[1] > 180.0) { cornerang[1] -= 360.0; } } // Copy all horizontal model box data to array. boundaries[BoxBound_FUR][0] = finalloc[3][0]; boundaries[BoxBound_FUR][1] = finalloc[3][1]; boundaries[BoxBound_FUL][0] = finalloc[0][0]; boundaries[BoxBound_FUL][1] = finalloc[0][1]; boundaries[BoxBound_FDR][0] = finalloc[3][0]; boundaries[BoxBound_FDR][1] = finalloc[3][1]; boundaries[BoxBound_FDL][0] = finalloc[0][0]; boundaries[BoxBound_FDL][1] = finalloc[0][1]; boundaries[BoxBound_BUR][0] = finalloc[2][0]; boundaries[BoxBound_BUR][1] = finalloc[2][1]; boundaries[BoxBound_BUL][0] = finalloc[1][0]; boundaries[BoxBound_BUL][1] = finalloc[1][1]; boundaries[BoxBound_BDR][0] = finalloc[2][0]; boundaries[BoxBound_BDR][1] = finalloc[2][1]; boundaries[BoxBound_BDL][0] = finalloc[1][0]; boundaries[BoxBound_BDL][1] = finalloc[1][1]; // Set Z bounds. new Float:eyeloc[3]; GetClientEyePosition(client, eyeloc); boundaries[BoxBound_FUR][2] = eyeloc[2]; boundaries[BoxBound_FUL][2] = eyeloc[2]; boundaries[BoxBound_FDR][2] = clientloc[2] + 15.0; boundaries[BoxBound_FDL][2] = clientloc[2] + 15.0; boundaries[BoxBound_BUR][2] = eyeloc[2]; boundaries[BoxBound_BUL][2] = eyeloc[2]; boundaries[BoxBound_BDR][2] = clientloc[2] + 15.0; boundaries[BoxBound_BDL][2] = clientloc[2] + 15.0; } /** * Jumps from a point to another based off angle and distance. * * @param vec Point to jump from. * @param ang Angle to base jump off of. * @param distance Distance to jump * @param result Resultant point. */ stock AntiStickJumpToPoint(const Float:vec[3], const Float:ang[3], Float:distance, Float:result[3]) { new Float:viewvec[3]; // Turn client angle, into a vector. GetAngleVectors(ang, viewvec, NULL_VECTOR, NULL_VECTOR); // Normalize vector. NormalizeVector(viewvec, viewvec); // Scale to the given distance. ScaleVector(viewvec, distance); // Add the vectors together. AddVectors(vec, viewvec, result); } /** * Get the max/min value of a 3D box on any axis. * * @param axis The axis to check. * @param boundaries The boundaries to check. * @param min Return the min value instead. */ stock Float:AntiStickGetBoxMaxBoundary(axis, Float:boundaries[AntiStickBoxBound][3], bool:min = false) { // Create 'outlier' with initial value of first boundary. new Float:outlier = boundaries[0][axis]; // x = Boundary index. (Start at 1 because we initialized 'outlier' with the 0 index's value) new size = sizeof(boundaries); for (new x = 1; x < size; x++) { if (!min && boundaries[x][axis] > outlier) { outlier = boundaries[x][axis]; } else if (min && boundaries[x][axis] < outlier) { outlier = boundaries[x][axis]; } } // Return value. return outlier; } /** * Checks if a player is currently stuck within another player. * * @param client1 The first client index. * @param client2 The second client index. * @return True if they are stuck together, false if not. */ stock bool:AntiStickIsModelBoxColliding(client1, client2) { new Float:client1modelbox[AntiStickBoxBound][3]; new Float:client2modelbox[AntiStickBoxBound][3]; // Build model boxes for each client. AntiStickBuildModelBox(client1, client1modelbox, ANTISTICK_DEFAULT_HULL_WIDTH); AntiStickBuildModelBox(client2, client2modelbox, ANTISTICK_DEFAULT_HULL_WIDTH); // Compare x values. new Float:max1x = AntiStickGetBoxMaxBoundary(0, client1modelbox); new Float:max2x = AntiStickGetBoxMaxBoundary(0, client2modelbox); new Float:min1x = AntiStickGetBoxMaxBoundary(0, client1modelbox, true); new Float:min2x = AntiStickGetBoxMaxBoundary(0, client2modelbox, true); if (max1x < min2x || min1x > max2x) { return false; } // Compare y values. new Float:max1y = AntiStickGetBoxMaxBoundary(1, client1modelbox); new Float:max2y = AntiStickGetBoxMaxBoundary(1, client2modelbox); new Float:min1y = AntiStickGetBoxMaxBoundary(1, client1modelbox, true); new Float:min2y = AntiStickGetBoxMaxBoundary(1, client2modelbox, true); if (max1y < min2y || min1y > max2y) { return false; } // Compare z values. new Float:max1z = AntiStickGetBoxMaxBoundary(2, client1modelbox); new Float:max2z = AntiStickGetBoxMaxBoundary(2, client2modelbox); new Float:min1z = AntiStickGetBoxMaxBoundary(2, client1modelbox, true); new Float:min2z = AntiStickGetBoxMaxBoundary(2, client2modelbox, true); if (max1z < min2z || min1z > max2z) { return false; } // They are intersecting. return true; } /** * Gets the collision group on a client. * * @param client The client index. * @return The collision group on the client. */ AntiStickGetCollisionGroup(client) { return GetEntProp(client, Prop_Data, "m_CollisionGroup"); } /** * Sets the collision group on a client. * * @param client The client index. * @param collisiongroup Collision group flag. */ AntiStickSetCollisionGroup(client, collisiongroup) { SetEntProp(client, Prop_Data, "m_CollisionGroup", collisiongroup); } /** * Converts a collision group value into a name. * * @param collisiongroup The collision group to convert. * @param buffer Destination string buffer. * @param maxlen Size of destination buffer. * @return Number of cells written. */ AntiStickCollisionGroupToString(collisiongroup, String:buffer[], maxlen) { switch (collisiongroup) { case COLLISION_GROUP_NONE: { return strcopy(buffer, maxlen, "COLLISION_GROUP_NONE"); } case COLLISION_GROUP_DEBRIS: { return strcopy(buffer, maxlen, "COLLISION_GROUP_DEBRIS"); } case COLLISION_GROUP_DEBRIS_TRIGGER: { return strcopy(buffer, maxlen, "COLLISION_GROUP_DEBRIS_TRIGGER"); } case COLLISION_GROUP_INTERACTIVE_DEBRIS: { return strcopy(buffer, maxlen, "COLLISION_GROUP_INTERACTIVE_DEBRIS"); } case COLLISION_GROUP_INTERACTIVE: { return strcopy(buffer, maxlen, "COLLISION_GROUP_INTERACTIVE"); } case COLLISION_GROUP_PLAYER: { return strcopy(buffer, maxlen, "COLLISION_GROUP_PLAYER"); } case COLLISION_GROUP_BREAKABLE_GLASS: { return strcopy(buffer, maxlen, "COLLISION_GROUP_BREAKABLE_GLASS"); } case COLLISION_GROUP_VEHICLE: { return strcopy(buffer, maxlen, "COLLISION_GROUP_VEHICLE"); } case COLLISION_GROUP_PLAYER_MOVEMENT: { return strcopy(buffer, maxlen, "COLLISION_GROUP_PLAYER_MOVEMENT"); } case COLLISION_GROUP_NPC: { return strcopy(buffer, maxlen, "COLLISION_GROUP_NPC"); } case COLLISION_GROUP_IN_VEHICLE: { return strcopy(buffer, maxlen, "COLLISION_GROUP_IN_VEHICLE"); } case COLLISION_GROUP_WEAPON: { return strcopy(buffer, maxlen, "COLLISION_GROUP_WEAPON"); } case COLLISION_GROUP_VEHICLE_CLIP: { return strcopy(buffer, maxlen, "COLLISION_GROUP_VEHICLE_CLIP"); } case COLLISION_GROUP_PROJECTILE: { return strcopy(buffer, maxlen, "COLLISION_GROUP_PROJECTILE"); } case COLLISION_GROUP_DOOR_BLOCKER: { return strcopy(buffer, maxlen, "COLLISION_GROUP_DOOR_BLOCKER"); } case COLLISION_GROUP_PASSABLE_DOOR: { return strcopy(buffer, maxlen, "COLLISION_GROUP_PASSABLE_DOOR"); } case COLLISION_GROUP_DISSOLVING: { return strcopy(buffer, maxlen, "COLLISION_GROUP_DISSOLVING"); } case COLLISION_GROUP_PUSHAWAY: { return strcopy(buffer, maxlen, "COLLISION_GROUP_PUSHAWAY"); } case COLLISION_GROUP_NPC_ACTOR: { return strcopy(buffer, maxlen, "COLLISION_GROUP_NPC_ACTOR"); } } // No match. Write a blank string. return strcopy(buffer, maxlen, ""); } /** * Command callback (zr_antistick_dump_group) * Dumps collision group data. * * @param client The client index. * @param argc Argument count. */ public Action:AntiStickDumpGroupCommand(client, argc) { new collisiongroup; new target; decl String:groupname[64]; decl String:arg[96]; // Write header. ReplyToCommand(client, "Player: Collision group:\n--------------------------------------------------------------------------------"); if (argc < 1) { // Dump collision groups on all players. // Loop through all alive players. for (target = 1; target <= MaxClients; target++) { // Validate client state. if (!IsClientConnected(target) || !IsClientInGame(target) || !IsPlayerAlive(target)) { continue; } // Get collision group name. collisiongroup = AntiStickGetCollisionGroup(target); AntiStickCollisionGroupToString(collisiongroup, groupname, sizeof(groupname)); // List player name and collision group. ReplyToCommand(client, "%-35N %s", target, groupname); } } else { // Get the target. GetCmdArg(1, arg, sizeof(arg)); target = FindTarget(client, arg); // Validate target. if (ZRIsClientValid(target)) { // Get collision group name. collisiongroup = AntiStickGetCollisionGroup(target); AntiStickCollisionGroupToString(collisiongroup, groupname, sizeof(groupname)); // List player name and collision group. ReplyToCommand(client, "%-35N %s", target, groupname); } } return Plugin_Handled; }