diff --git a/plugins/l4d2_hats.smx b/plugins/l4d2_hats.smx index b09f088..afe40e6 100644 Binary files a/plugins/l4d2_hats.smx and b/plugins/l4d2_hats.smx differ diff --git a/scripting/include/hats/hats.sp b/scripting/include/hats/hats.sp new file mode 100644 index 0000000..7705061 --- /dev/null +++ b/scripting/include/hats/hats.sp @@ -0,0 +1,727 @@ +enum hatFlags { + HAT_NONE = 0, + HAT_POCKET = 1, + HAT_REVERSED = 2, + HAT_COMMANDABLE = 4, + HAT_RAINBOW = 8 +} +enum struct HatData { + int entity; // The entity REFERENCE + int visibleEntity; // Thee visible entity REF + Handle yeetGroundTimer; + + // Original data for entity + float orgPos[3]; + float orgAng[3]; + float offset[3]; + float angles[3]; + int collisionGroup; + int solidType; + int moveType; + + float scale; + int flags; + float rainbowColor[3]; + int rainbowTicks; + bool rainbowReverse; +} +enum hatFeatures { + HatConfig_None = 0, + HatConfig_PlayerHats = 1, + HatConfig_RespectAdminImmunity = 2, + HatConfig_FakeHat = 4, + HatConfig_NoSaferoomHats = 8, + HatConfig_PlayerHatConsent = 16, + HatConfig_InfectedHats = 32, + HatConfig_ReversedHats = 64, + HatConfig_DeleteThrownHats = 128 +} + +HatData hatData[MAXPLAYERS+1]; +int lastHatRequestTime[MAXPLAYERS+1]; + +#define MAX_FORBIDDEN_CLASSNAMES 13 +char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = { + "prop_door_rotating_checkpoint", + "env_physics_blocker", + "env_player_blocker", + "func_brush", + "func_simpleladder", + "prop_door_rotating", + "func_button", + "func_elevator", + "func_button_timed", + "func_tracktrain", + // "func_movelinear", + // "infected", + "func_lod", + "func_door", + "prop_ragdoll" +}; + +#define MAX_REVERSE_CLASSNAMES 2 +static char REVERSE_CLASSNAMES[MAX_REVERSE_CLASSNAMES][] = { + "infected", + "func_movelinear" +}; + +public Action Command_DoAHat(int client, int args) { + int hatter = GetHatter(client); + if(hatter > 0) { + ClearHat(hatter, HasFlag(hatter, HAT_REVERSED)); + PrintToChat(hatter, "[Hats] %N has unhatted themselves", client); + return Plugin_Handled; + } + + AdminId adminId = GetUserAdmin(client); + if(cvar_sm_hats_enabled.IntValue == 1) { + if(adminId == INVALID_ADMIN_ID) { + PrintToChat(client, "[Hats] Hats are for admins only"); + return Plugin_Handled; + } + } else if(!adminId.HasFlag(Admin_Cheats)) { + PrintToChat(client, "[Hats] You do not have permission"); + return Plugin_Handled; + } + if(cvar_sm_hats_enabled.IntValue == 0) { + ReplyToCommand(client, "[Hats] Hats are disabled"); + return Plugin_Handled; + } else if(GetClientTeam(client) != 2 && ~cvar_sm_hats_flags.IntValue & view_as(HatConfig_InfectedHats)) { + PrintToChat(client, "[Hats] Hats are only available for survivors."); + return Plugin_Handled; + } + + int oldVisible = EntRefToEntIndex(hatData[client].visibleEntity); + if(oldVisible > 0) { + RemoveEntity(oldVisible); + hatData[client].visibleEntity = INVALID_ENT_REFERENCE; + } + + int entity = GetHat(client); + if(entity > 0) { + char arg[4]; + GetCmdArg(1, arg, sizeof(arg)); + // int orgEntity = entity; + if(HasFlag(client, HAT_REVERSED)) { + entity = client; + } + ClearParent(entity); + + if(arg[0] == 's') { + char sizeStr[4]; + GetCmdArg(2, sizeStr, sizeof(sizeStr)); + float size = StringToFloat(sizeStr); + if(size == 0.0) { + ReplyToCommand(client, "[Hats] Invalid size"); + return Plugin_Handled; + } + if(HasEntProp(entity, Prop_Send, "m_flModelScale")) + SetEntPropFloat(entity, Prop_Send, "m_flModelScale", size); + else + PrintHintText(client, "Hat does not support scaling"); + // Change the size of it's parent instead + int child = -1; + while((child = FindEntityByClassname(child, "*")) != INVALID_ENT_REFERENCE ) + { + int parent = GetEntPropEnt(child, Prop_Data, "m_pParent"); + if(parent == entity) { + if(HasEntProp(child, Prop_Send, "m_flModelScale")) { + PrintToConsole(client, "found child %d for %d", child, entity); + SetEntPropFloat(child, Prop_Send, "m_flModelScale", size); + } else { + PrintToChat(client, "Child %d for %d cannot be scaled", child, entity); + } + break; + } + } + // Reattach entity: + EquipHat(client, entity); + return Plugin_Handled; + } else if(arg[0] == 'r' && arg[1] == 'a') { + SetFlag(client, HAT_RAINBOW); + hatData[client].rainbowTicks = 0; + hatData[client].rainbowReverse = false; + hatData[client].rainbowColor[0] = 0.0; + hatData[client].rainbowColor[1] = 255.0; + hatData[client].rainbowColor[2] = 255.0; + EquipHat(client, entity); + ReplyToCommand(client, "Rainbow hats enabled"); + return Plugin_Handled; + } + + // Re-enable physics and restore collision/solidity + AcceptEntityInput(entity, "EnableMotion"); + SetEntProp(entity, Prop_Send, "m_CollisionGroup", hatData[client].collisionGroup); + SetEntProp(entity, Prop_Send, "m_nSolidType", hatData[client].solidType); + + // Remove frozen flag (only "infected" and "witch" are frozen, but just incase:) + int flags = GetEntityFlags(entity) & ~FL_FROZEN; + SetEntityFlags(entity, flags); + + // Clear visible hats (HatConfig_FakeHat is enabled) + int visibleEntity = EntRefToEntIndex(hatData[client].visibleEntity); + SDKUnhook(entity, SDKHook_SetTransmit, OnRealTransmit); + if(visibleEntity > 0) { + SDKUnhook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); + RemoveEntity(visibleEntity); + hatData[client].visibleEntity = INVALID_ENT_REFERENCE; + } + // Grant temp god & remove after time + tempGod[client] = true; + if(client <= MaxClients) { + SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + CreateTimer(2.0, Timer_RemoveGod, GetClientUserId(client)); + } + if(entity <= MaxClients) { + SDKHook(entity, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + CreateTimer(2.0, Timer_RemoveGod, GetClientUserId(entity)); + } + + // Restore movement: + if(entity <= MaxClients) { + // If player, remove roll & and just default to WALK movetype + hatData[client].orgAng[2] = 0.0; + SetEntityMoveType(entity, MOVETYPE_WALK); + } else { + // If not player, then just use whatever they were pre-hat + SetEntProp(entity, Prop_Send, "movetype", hatData[client].moveType); + } + + if(arg[0] == 'y') { // Hat yeeting: + GetClientEyeAngles(client, hatData[client].orgAng); + GetClientAbsOrigin(client, hatData[client].orgPos); + hatData[client].orgPos[2] += 45.0; + float ang[3], vel[3]; + + // Calculate the angle to throw at + GetClientEyeAngles(client, ang); + ang[2] = 0.0; + if(ang[0] > 0.0) ang[0] = -ang[0]; + // ang[0] = -45.0; + + // Calculate velocity to throw based on direction + vel[0] = Cosine(DegToRad(ang[1])) * GetRandomFloat(1300.0, 1700.0); + vel[1] = Sine(DegToRad(ang[1])) * GetRandomFloat(1300.0, 1700.0); + vel[2] = GetRandomFloat(700.0, 900.0); + if(entity <= MaxClients) { + // For players, use the built in fling function + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); + L4D2_CTerrorPlayer_Fling(entity, client, vel); + } /*else if(visibleEntity > 0) { + PrintToChat(client, "Yeeting fake car..."); + ClearParent(visibleEntity); + + SetEntProp(visibleEntity, Prop_Send, "movetype", 6); + + AcceptEntityInput(visibleEntity, "EnableMotion"); + + TeleportEntity(entity, OUT_OF_BOUNDS, hatData[client].orgAng, NULL_VECTOR); + TeleportEntity(visibleEntity, hatData[client].orgPos, hatData[client].orgAng, vel); + DataPack pack; + CreateDataTimer(4.0, Timer_PropYeetEnd, pack); + pack.WriteCell(hatData[client].entity); + pack.WriteCell(hatData[client].visibleEntity); + pack.WriteCell(hatData[client].collisionGroup); + pack.WriteCell(hatData[client].solidType); + pack.WriteCell(hatData[client].moveType); + hatData[client].visibleEntity = INVALID_ENT_REFERENCE; + hatData[client].entity = INVALID_ENT_REFERENCE; + } */ else { + // For actual props, offset it 35 units above and 80 units infront to reduce collision-incaps and then throw + GetHorizontalPositionFromClient(client, 80.0, hatData[client].orgPos); + hatData[client].orgPos[2] += 35.0; + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, vel); + // Sleep the physics after enoug time for it to most likely have landed + if(hatData[client].yeetGroundTimer != null) { + delete hatData[client].yeetGroundTimer; + } + DataPack pack1; + hatData[client].yeetGroundTimer = CreateDataTimer(0.5, Timer_GroundKill, pack1, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + pack1.WriteCell(hatData[client].entity); + pack1.WriteCell(GetClientUserId(client)); + DataPack pack2; + CreateDataTimer(7.7, Timer_PropSleep, pack2); + pack2.WriteCell(hatData[client].entity); + pack2.WriteCell(GetClientUserId(client)); + } + PrintToChat(client, "[Hats] Yeeted hat"); + hatData[client].entity = INVALID_ENT_REFERENCE; + return Plugin_Handled; + } else if(arg[0] == 'c') { + float pos[3]; + // Grabs a cursor position with some checks to prevent placing into (in)visible walls + if(GetSmartCursorLocation(client, pos)) { + if(CanHatBePlaced(client, pos)) { + if(entity <= MaxClients) + L4D_WarpToValidPositionIfStuck(entity); + hatData[client].orgPos = pos; + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); + PrintToChat(client, "[Hats] Placed hat on cursor."); + } + } else { + PrintToChat(client, "[Hats] Could not find cursor position."); + } + } else if(arg[0] == 'p' || (entity <= MaxClients && arg[0] != 'r')) { + // Place the hat down on the cursor if specified OR if entity is hat + float pos[3], ang[3]; + if(HasFlag(client, HAT_REVERSED)) { + // If we are reversed, then place ourselves where our "hatter" is + GetClientEyePosition(entity, hatData[client].orgPos); + GetClientEyeAngles(entity, hatData[client].orgAng); + TeleportEntity(client, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); + PrintToChat(entity, "[Hats] Placed hat in front of you."); + } else { + // If we are normal, then get position infront of us, offset by model size + GetClientEyePosition(client, pos); + GetClientEyeAngles(client, ang); + GetHorizontalPositionFromOrigin(pos, ang, 80.0, pos); + ang[0] = 0.0; + float mins[3]; + GetEntPropVector(entity, Prop_Data, "m_vecMins", mins); + pos[2] += mins[2]; + // Find the nearest ground (raytrace bottom->up) + FindGround(pos, pos); + // Check if the destination is acceptable (not saferooms if enabled) + if(CanHatBePlaced(client, pos)) { + hatData[client].orgPos = pos; + hatData[client].orgAng = ang; + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); + PrintToChat(client, "[Hats] Placed hat in front of you."); + } + } + } else if(arg[0] == 'd') { + // Use the new wall editor + WallBuilder[client].Reset(); + WallBuilder[client].entity = EntIndexToEntRef(entity); + WallBuilder[client].canScale = false; + WallBuilder[client].SetMode(MOVE_ORIGIN); + PrintToChat(client, "\x04[Hats] \x01Beta Prop Mover active for \x04%d", entity); + } else { + PrintToChat(client, "[Hats] Restored hat to its original position."); + } + + // Restore the scale pre-hat + if(hatData[client].scale > 0 && HasEntProp(entity, Prop_Send, "m_flModelScale")) + SetEntPropFloat(entity, Prop_Send, "m_flModelScale", hatData[client].scale); + + // If no other options performed, then restore to original position and remove our reference + AcceptEntityInput(entity, "Sleep"); + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); + hatData[client].entity = INVALID_ENT_REFERENCE; + } else { + // Find a new hatable entity + int flags = 0; + entity = GetLookingEntity(client, Filter_ValidHats); + if(entity <= 0) { + PrintCenterText(client, "[Hats] No entity found"); + return Plugin_Handled; + } else if(entity == EntRefToEntIndex(WallBuilder[client].entity)) { + // Prevent making an entity you editing a hat + return Plugin_Handled; + } + + // Make hat reversed if 'r' passed in + if(args > 0) { + char arg[4]; + GetCmdArg(1, arg, sizeof(arg)); + if(arg[0] == 'r') { + flags |= view_as(HAT_REVERSED); + } + } + + int parent = GetEntPropEnt(entity, Prop_Data, "m_hParent"); + if(parent > 0 && entity > MaxClients) { + PrintToConsole(client, "[Hats] Selected a child entity, selecting parent (child %d -> parent %d)", entity, parent); + entity = parent; + } else if(entity <= MaxClients) { // Checks for hatting a player entity + if(GetClientTeam(entity) != 2 && ~cvar_sm_hats_flags.IntValue & view_as(HatConfig_InfectedHats)) { + PrintToChat(client, "[Hats] Cannot make enemy a hat... it's dangerous"); + return Plugin_Handled; + } else if(entity == EntRefToEntIndex(WallBuilder[client].entity)) { + // Old check left in in case you hatting child entity + PrintToChat(client, "[Hats] You are currently editing this entity"); + return Plugin_Handled; + } else if(inSaferoom[client] && cvar_sm_hats_flags.IntValue & view_as(HatConfig_NoSaferoomHats)) { + PrintToChat(client, "[Hats] Hats are not allowed in the saferoom"); + return Plugin_Handled; + } else if(!IsPlayerAlive(entity) || GetEntProp(entity, Prop_Send, "m_isHangingFromLedge") || L4D_IsPlayerCapped(entity)) { + PrintToChat(client, "[Hats] Player is either dead, hanging, or in the process of dying."); + return Plugin_Handled; + } else if(EntRefToEntIndex(hatData[entity].entity) == client) { + PrintToChat(client, "[Hats] Woah you can't be making a black hole, jesus be careful."); + return Plugin_Handled; + } else if(~cvar_sm_hats_flags.IntValue & view_as(HatConfig_PlayerHats)) { + PrintToChat(client, "[Hats] Player hats are disabled"); + return Plugin_Handled; + } else if(!CanTarget(entity)) { + PrintToChat(client, "[Hats] Player has disabled player hats for themselves."); + return Plugin_Handled; + } else if(!CanTarget(client)) { + PrintToChat(client, "[Hats] Cannot hat a player when you have player hats turned off"); + return Plugin_Handled; + } else if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_RespectAdminImmunity)) { + AdminId targetAdmin = GetUserAdmin(entity); + AdminId clientAdmin = GetUserAdmin(client); + if(targetAdmin != INVALID_ADMIN_ID && clientAdmin == INVALID_ADMIN_ID) { + PrintToChat(client, "[Hats] Cannot target an admin"); + return Plugin_Handled; + } else if(targetAdmin != INVALID_ADMIN_ID && targetAdmin.ImmunityLevel > clientAdmin.ImmunityLevel) { + PrintToChat(client, "[Hats] Cannot target %N, they are immune to you", entity); + return Plugin_Handled; + } + } + if(!IsFakeClient(entity) && cvar_sm_hats_flags.IntValue & view_as(HatConfig_PlayerHatConsent) && ~flags & view_as(HAT_REVERSED)) { + int lastRequestDiff = GetTime() - lastHatRequestTime[client]; + if(lastRequestDiff < PLAYER_HAT_REQUEST_COOLDOWN) { + PrintToChat(client, "[Hats] Player hat under %d seconds cooldown", lastRequestDiff); + return Plugin_Handled; + } + + Menu menu = new Menu(HatConsentHandler); + menu.SetTitle("%N: Requests to hat you", client); + char id[8]; + Format(id, sizeof(id), "%d|1", GetClientUserId(client)); + menu.AddItem(id, "Accept"); + Format(id, sizeof(id), "%d|0", GetClientUserId(client)); + menu.AddItem(id, "Reject"); + menu.Display(entity, 12); + PrintHintText(client, "Sent hat request to %N", entity); + PrintToChat(entity, "[Hats] %N requests to hat you, 1 to Accept, 2 to Reject. Expires in 12 seconds.", client); + return Plugin_Handled; + } + } + + + char classname[64]; + GetEntityClassname(entity, classname, sizeof(classname)); + // Check is pretty redudant as the traceray itself shouldn't grab it, but just in case: + if(cvar_sm_hats_blacklist_enabled.BoolValue) { + for(int i = 0; i < MAX_FORBIDDEN_CLASSNAMES; i++) { + if(StrEqual(FORBIDDEN_CLASSNAMES[i], classname)) { + PrintToChat(client, "[Hats] Entity (%s) is a blacklisted entity. Naughty.", classname); + return Plugin_Handled; + } + } + } + + // Check for any class that should always be reversed + if(~flags & view_as(HAT_REVERSED)) { + for(int i = 0; i < MAX_REVERSE_CLASSNAMES; i++) { + if(StrEqual(REVERSE_CLASSNAMES[i], classname)) { + flags |= view_as(HAT_REVERSED); + break; + } + } + } + + EquipHat(client, entity, classname, flags); + } + return Plugin_Handled; +} + +// Handles consent that a person to be hatted by another player +public int HatConsentHandler(Menu menu, MenuAction action, int target, int param2) { + if (action == MenuAction_Select) { + static char info[8]; + menu.GetItem(param2, info, sizeof(info)); + static char str[2][8]; + ExplodeString(info, "|", str, 2, 8, false); + int activator = GetClientOfUserId(StringToInt(str[0])); + int hatAction = StringToInt(str[1]); + if(activator == 0) { + ReplyToCommand(target, "Player has disconnected"); + return 0; + } else if(hatAction == 1) { + EquipHat(activator, target, "player", 0); + } else { + ClientCommand(activator, "play player/orch_hit_csharp_short.wav"); + PrintHintText(activator, "%N refused your request", target); + lastHatRequestTime[activator] = GetTime(); + } + } else if (action == MenuAction_End) + delete menu; + return 0; +} + +bool IsHatsEnabled(int client) { + return (cvar_sm_hats_enabled.IntValue == 1 && GetUserAdmin(client) != INVALID_ADMIN_ID) || cvar_sm_hats_enabled.IntValue == 2 +} + +void ClearHats() { + for(int i = 1; i <= MaxClients; i++) { + if(HasHat(i)) { + ClearHat(i, false); + } + if(IsClientConnected(i) && IsClientInGame(i)) SetEntityMoveType(i, MOVETYPE_WALK); + } +} +void ClearHat(int i, bool restore = false) { + + int entity = EntRefToEntIndex(hatData[i].entity); + int visibleEntity = EntRefToEntIndex(hatData[i].visibleEntity); + int modifyEntity = HasFlag(i, HAT_REVERSED) ? i : entity; + + if(visibleEntity > 0) { + SDKUnhook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); + RemoveEntity(visibleEntity); + } + if(modifyEntity > 0) { + SDKUnhook(modifyEntity, SDKHook_SetTransmit, OnRealTransmit); + ClearParent(modifyEntity); + } else { + return; + } + + int flags = GetEntityFlags(entity) & ~FL_FROZEN; + SetEntityFlags(entity, flags); + // if(HasEntProp(entity, Prop_Send, "m_flModelScale")) + // SetEntPropFloat(entity, Prop_Send, "m_flModelScale", 1.0); + SetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup", hatData[i].collisionGroup); + SetEntProp(modifyEntity, Prop_Send, "m_nSolidType", hatData[i].solidType); + SetEntProp(modifyEntity, Prop_Send, "movetype", hatData[i].moveType); + + hatData[i].entity = INVALID_ENT_REFERENCE; + hatData[i].visibleEntity = INVALID_ENT_REFERENCE; + + if(HasFlag(i, HAT_REVERSED)) { + entity = i; + i = modifyEntity; + } + + if(entity <= MAXPLAYERS) { + AcceptEntityInput(entity, "EnableLedgeHang"); + } + if(restore) { + // If hat is a player, override original position to hat wearer's + if(entity <= MAXPLAYERS && HasEntProp(i, Prop_Send, "m_vecOrigin")) { + GetEntPropVector(i, Prop_Send, "m_vecOrigin", hatData[i].orgPos); + } + // Restore to original position + if(HasFlag(i, HAT_REVERSED)) { + TeleportEntity(i, hatData[i].orgPos, hatData[i].orgAng, NULL_VECTOR); + } else { + TeleportEntity(entity, hatData[i].orgPos, hatData[i].orgAng, NULL_VECTOR); + } + } +} + +bool HasHat(int client) { + return GetHat(client) > 0; +} + +int GetHat(int client) { + if(hatData[client].entity == INVALID_ENT_REFERENCE) return -1; + int index = EntRefToEntIndex(hatData[client].entity); + if(index <= 0) return -1; + if(!IsValidEntity(index)) return -1; + return index; +} + +int GetHatter(int client) { + for(int i = 1; i <= MaxClients; i++) { + if(EntRefToEntIndex(hatData[client].entity) == client) { + return i; + } + } + return -1; +} + +bool CanTarget(int victim) { + static char buf[2]; + noHatVictimCookie.Get(victim, buf, sizeof(buf)); + return StringToInt(buf) == 0; +} + +bool IsHatAllowed(int client) { + char name[32]; + GetEntityClassname(hatData[client].entity, name, sizeof(name)); + // Don't allow non-weapons in saferoom + if(StrEqual(name, "prop_physics")) { + GetEntPropString(hatData[client].entity, Prop_Data, "m_ModelName", name, sizeof(name)); + if(StrContains(name, "gnome") != -1) { + return true; + } + PrintToConsole(client, "Dropping hat: prop_physics (%s)", name); + return false; + } + else if(StrEqual(name, "player") || StrContains(name, "weapon_") > -1 || StrContains(name, "upgrade_") > -1) { + return true; + } + PrintToConsole(client, "Dropping hat: %s", name); + return false; +} + +bool CanHatBePlaced(int client, const float pos[3]) { + if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_NoSaferoomHats)) { + Address nav = L4D_GetNearestNavArea(pos, 200.0); + if(nav != Address_Null) { + int spawnFlags = L4D_GetNavArea_SpawnAttributes(nav) ; + if(spawnFlags & NAV_SPAWN_CHECKPOINT) { + PrintToServer("\"%L\" tried to place hat in saferoom, denied.", client); + PrintToChat(client, "[Hats] Hats are not allowed in saferoom and has been returned."); + return false; + } + } + } + return true; +} + +void SetFlag(int client, hatFlags flag) { + hatData[client].flags |= view_as(flag); +} + +bool HasFlag(int client, hatFlags flag) { + return hatData[client].flags & view_as(flag) != 0; +} + +void EquipHat(int client, int entity, const char[] classname = "", int flags = HAT_NONE) { + if(HasHat(client)) + ClearHat(client, true); + + // Player specific tweaks + int visibleEntity; + if(entity == 0) { + ThrowError("Attempted to equip world (client = %d)", client); + return; + } + + hatData[client].entity = EntIndexToEntRef(entity); + int modifyEntity = HasFlag(client, HAT_REVERSED) ? client : entity; + hatData[client].collisionGroup = GetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup"); + hatData[client].solidType = GetEntProp(modifyEntity, Prop_Send, "m_nSolidType"); + hatData[client].moveType = GetEntProp(modifyEntity, Prop_Send, "movetype"); + + if(client <= MaxClients) SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + if(entity <= MaxClients) SDKHook(entity, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + + if(modifyEntity <= MaxClients) { + + AcceptEntityInput(modifyEntity, "DisableLedgeHang"); + } else if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_FakeHat)) { + return; + // char model[64]; + // GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); + // visibleEntity = CreateEntityByName("prop_dynamic"); + // DispatchKeyValue(visibleEntity, "model", model); + // DispatchKeyValue(visibleEntity, "disableshadows", "1"); + // DispatchSpawn(visibleEntity); + // SetEntProp(visibleEntity, Prop_Send, "m_CollisionGroup", 1); + // hatData[client].visibleEntity = EntIndexToEntRef(visibleEntity); + // SDKHook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); + // SDKHook(entity, SDKHook_SetTransmit, OnRealTransmit); + } + SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + // Temp remove the hat to be yoinked by another player + for(int i = 1; i <= MaxClients; i++) { + if(i != client && EntRefToEntIndex(hatData[i].entity) == entity) { + ClearHat(i); + } + } + + // Called on initial hat + if(classname[0] != '\0') { + if(entity <= MaxClients && !IsFakeClient(entity)) { + PrintToChat(entity, "[Hats] %N has hatted you, type /hat to dismount at any time", client); + } + + // Reset things: + hatData[client].flags = 0; + hatData[client].offset[0] = hatData[client].offset[1] = hatData[client].offset[2] = 0.0; + hatData[client].angles[0] = hatData[client].angles[1] = hatData[client].angles[2] = 0.0; + + if(modifyEntity <= MaxClients) { + if(HasFlag(client, HAT_REVERSED)) { + hatData[client].offset[2] += 7.2; + } else { + hatData[client].offset[2] += 4.2; + } + } else { + float mins[3]; + GetEntPropVector(modifyEntity, Prop_Send, "m_vecMins", mins); + hatData[client].offset[2] += mins[2]; + } + + if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_ReversedHats) && flags & view_as(HAT_REVERSED)) { + SetFlag(client, HAT_REVERSED); + if(StrEqual(classname, "infected") || (entity <= MaxClients && IsFakeClient(entity))) { + SetFlag(client, HAT_COMMANDABLE); + } + PrintToChat(client, "[Hats] Set yourself as %s (%d)'s hat", classname, entity); + if(entity <= MaxClients) { + LogAction(client, entity, "\"%L\" made themselves \"%L\" (%s)'s hat (%d, %d)", client, entity, classname, entity, visibleEntity); + PrintToChat(entity, "[Hats] %N has set themselves as your hat", client); + } + } else { + if(StrEqual(classname, "infected") || StrEqual(classname, "witch")) { + int eflags = GetEntityFlags(entity) | FL_FROZEN; + SetEntityFlags(entity, eflags); + hatData[client].offset[2] = 36.0; + } + if(entity <= MaxClients) + PrintToChat(client, "[Hats] Set %N (%d) as a hat", entity, entity); + else + PrintToChat(client, "[Hats] Set %s (%d) as a hat", classname, entity); + if(entity <= MaxClients) + LogAction(client, entity, "\"%L\" picked up \"%L\" (%s) as a hat (%d, %d)", client, entity, classname, entity, visibleEntity); + else + LogAction(client, -1, "\"%L\" picked up %s as a hat (%d, %d)", client, classname, entity, visibleEntity); + } + hatData[client].scale = -1.0; + + } + AcceptEntityInput(modifyEntity, "DisableMotion"); + + // Get the data (position, angle, movement shit) + + GetEntPropVector(modifyEntity, Prop_Send, "m_vecOrigin", hatData[client].orgPos); + GetEntPropVector(modifyEntity, Prop_Send, "m_angRotation", hatData[client].orgAng); + hatData[client].collisionGroup = GetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup"); + hatData[client].solidType = GetEntProp(modifyEntity, Prop_Send, "m_nSolidType"); + hatData[client].moveType = GetEntProp(modifyEntity, Prop_Send, "movetype"); + + + if(!HasFlag(client, HAT_POCKET)) { + // TeleportEntity(entity, EMPTY_ANG, EMPTY_ANG, NULL_VECTOR); + if(HasFlag(client, HAT_REVERSED)) { + SetParent(client, entity); + if(StrEqual(classname, "infected")) { + SetParentAttachment(modifyEntity, "head", true); + TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); + SetParentAttachment(modifyEntity, "head", true); + } else { + SetParentAttachment(modifyEntity, "eyes", true); + TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); + SetParentAttachment(modifyEntity, "eyes", true); + } + + if(HasFlag(client, HAT_COMMANDABLE)) { + ChooseRandomPosition(hatData[client].offset); + L4D2_CommandABot(entity, client, BOT_CMD_MOVE, hatData[client].offset); + } + } else { + SetParent(entity, client); + SetParentAttachment(modifyEntity, "eyes", true); + TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); + SetParentAttachment(modifyEntity, "eyes", true); + } + + if(visibleEntity > 0) { + SetParent(visibleEntity, client); + SetParentAttachment(visibleEntity, "eyes", true); + hatData[client].offset[2] += 10.0; + TeleportEntity(visibleEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); + SetParentAttachment(visibleEntity, "eyes", true); + #if defined DEBUG_HAT_SHOW_FAKE + L4D2_SetEntityGlow(visibleEntity, L4D2Glow_Constant, 0, 0, color2, false); + #endif + } + + #if defined DEBUG_HAT_SHOW_FAKE + L4D2_SetEntityGlow(modifyEntity, L4D2Glow_Constant, 0, 0, color, false); + #endif + + SetEntProp(modifyEntity, Prop_Send, "m_nSolidType", 0); + SetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup", 1); + SetEntProp(modifyEntity, Prop_Send, "movetype", MOVETYPE_NONE); + } +} diff --git a/scripting/include/hats/walls.sp b/scripting/include/hats/walls.sp new file mode 100644 index 0000000..6a2f5a0 --- /dev/null +++ b/scripting/include/hats/walls.sp @@ -0,0 +1,609 @@ +int BUILDER_COLOR[4] = { 0, 255, 0, 235 }; +int WALL_COLOR[4] = { 255, 0, 0, 235 }; +float ORIGIN_SIZE[3] = { 2.0, 2.0, 2.0 }; + +enum wallMode { + INACTIVE = 0, + MOVE_ORIGIN, + SCALE, + FREELOOK +} + +ArrayList createdWalls; + +enum struct WallBuilderData { + float origin[3]; + float mins[3]; + float angles[3]; + float size[3]; + wallMode mode; + int axis; + int snapAngle; + int moveSpeed; + float moveDistance; + int entity; + bool canScale; + bool hasCollision; + + void Reset(bool initial = false) { + this.size[0] = this.size[1] = this.size[2] = 5.0; + this.angles[0] = this.angles[1] = this.angles[2] = 0.0; + this.axis = 1; + this.canScale = true; + this.moveDistance = 200.0; + this.entity = INVALID_ENT_REFERENCE; + this.hasCollision = true; + this.CalculateMins(); + this.SetMode(INACTIVE); + if(initial) { + this.moveSpeed = 1; + this.snapAngle = 30; + } + } + + void CalculateMins() { + this.mins[0] = -this.size[0]; + this.mins[1] = -this.size[1]; + this.mins[2] = -this.size[2]; + } + + void Draw(int color[4], float lifetime, float amplitude = 0.1) { + if(!this.canScale && this.entity != INVALID_ENT_REFERENCE) { + TeleportEntity(this.entity, this.origin, this.angles, NULL_VECTOR); + } else { + Effect_DrawBeamBoxRotatableToAll(this.origin, this.mins, this.size, this.angles, g_iLaserIndex, 0, 0, 30, lifetime, 0.4, 0.4, 0, amplitude, color, 0); + } + Effect_DrawAxisOfRotationToAll(this.origin, this.angles, ORIGIN_SIZE, g_iLaserIndex, 0, 0, 30, 0.2, 0.1, 0.1, 0, 0.0, 0); + } + + bool CheckEntity(int client) { + if(this.entity != INVALID_ENT_REFERENCE) { + if(!IsValidEntity(this.entity)) { + PrintToChat(client, "\x04[Hats]\x01 Entity has vanished, editing cancelled."); + return false; + } + } + return true; + } + + bool IsActive() { + return this.mode != INACTIVE; + } + + void SetMode(wallMode mode) { + this.mode = mode; + } + + void CycleMode(int client, float tick) { + if(tick - cmdThrottle[client] <= 0.25) return; + int flags = GetEntityFlags(client) & ~FL_FROZEN; + SetEntityFlags(client, flags); + switch(this.mode) { + // MODES: + // - MOVE (cursor point) + // - ROTATE + // - SCALE + // - FREECAM + case MOVE_ORIGIN: { + if(this.canScale) { + this.mode = SCALE; + PrintToChat(client, "\x04[Hats]\x01 Mode: \x05Scale\x01 (Press \x04RELOAD\x01 to change mode)"); + } else { + this.mode = FREELOOK; + PrintToChat(client, "\x04[Hats]\x01 Mode: \x05Freelook\x01 (Press \x04RELOAD\x01 to change mode)"); + } + } + case SCALE: { + this.mode = FREELOOK; + PrintToChat(client, "\x04[Hats]\x01 Mode: \x05Freelook\x01 (Press \x04RELOAD\x01 to change mode)"); + } + case FREELOOK: { + this.mode = MOVE_ORIGIN; + PrintToChat(client, "\x04[Hats]\x01 Mode: \x05Move & Rotate\x01 (Press \x04RELOAD\x01 to change mode)"); + // PrintToChat(client, "Hold \x04USE (E)\x01 to rotate, \x04WALK (SHIFT)\x01 to change speed"); + } + } + cmdThrottle[client] = tick; + } + + void ToggleCollision(int client, float tick) { + if(tick - cmdThrottle[client] <= 0.15) return; + this.hasCollision = !this.hasCollision + if(this.hasCollision) + PrintToChat(client, "\x04[Hats]\x01 Collision: \x05ON\x01"); + else + PrintToChat(client, "\x04[Hats]\x01 Collision: \x04OFF\x01"); + cmdThrottle[client] = tick; + } + + void CycleAxis(int client, float tick) { + if(tick - cmdThrottle[client] <= 0.15) return; + if(this.axis == 0) { + this.axis = 1; + PrintToChat(client, "\x04[Hats]\x01 Rotate Axis: \x05HEADING (Y)\x01"); + } else if(this.axis == 1) { + this.axis = 2; + PrintToChat(client, "\x04[Hats]\x01 Rotate Axis: \x05PITCH (X)\x01"); + } else { + this.axis = 0; + PrintToChat(client, "\x04[Hats]\x01 Rotate Axis: \x05ROLL (Z)\x01"); + } + cmdThrottle[client] = tick; + } + + void CycleSnapAngle(int client, float tick) { + if(tick - cmdThrottle[client] <= 0.15) return; + switch(this.snapAngle) { + case 1: this.snapAngle = 15; + case 15: this.snapAngle = 30; + case 30: this.snapAngle = 45; + case 45: this.snapAngle = 90; + case 90: this.snapAngle = 1; + } + + this.angles[0] = SnapTo(this.angles[0], float(this.snapAngle)); + this.angles[1] = SnapTo(this.angles[1], float(this.snapAngle)); + this.angles[2] = SnapTo(this.angles[2], float(this.snapAngle)); + + if(this.snapAngle == 1) + PrintToChat(client, "\x04[Hats]\x01 Rotate Snap Degrees: \x04(OFF)\x01", this.snapAngle); + else + PrintToChat(client, "\x04[Hats]\x01 Rotate Snap Degrees: \x05%d\x01", this.snapAngle); + cmdThrottle[client] = tick; + } + + void CycleSpeed(int client, float tick) { + if(tick - cmdThrottle[client] <= 0.25) return; + this.moveSpeed++; + if(this.moveSpeed > 10) this.moveSpeed = 1; + PrintToChat(client, "\x04[Hats]\x01 Scale Speed: \x05%d\x01", this.moveSpeed); + // if(this.movetype == 0) { + // this.movetype = 1; + // PrintToChat(client, "\x04[SM]\x01 Move Type: \x05HEADING (Y)\x01"); + // } else { + // this.movetype = 0; + // PrintToChat(client, "\x04[SM]\x01 Rotate Axis: \x05PITCH (X)\x01"); + // } + cmdThrottle[client] = tick; + } + + int Build() { + if(!this.canScale) { + this.Reset(); + return -3; + } + // Don't need to build a new one if we editing: + int blocker = this.entity; + bool isEdit = false; + if(blocker != INVALID_ENT_REFERENCE) { + RemoveEntity(this.entity); + isEdit = true; + } + blocker = CreateEntityByName("func_brush"); + if(blocker == -1) return -1; + DispatchKeyValueVector(blocker, "mins", this.mins); + DispatchKeyValueVector(blocker, "maxs", this.size); + DispatchKeyValueVector(blocker, "boxmins", this.mins); + DispatchKeyValueVector(blocker, "boxmaxs", this.size); + + DispatchKeyValueVector(blocker, "angles", this.angles); + DispatchKeyValue(blocker, "model", DUMMY_MODEL); + DispatchKeyValue(blocker, "intialstate", "1"); + // DispatchKeyValueVector(blocker, "angles", this.angles); + DispatchKeyValue(blocker, "BlockType", "4"); + char name[32]; + Format(name, sizeof(name), "l4d2_hats_%d", createdWalls.Length); + DispatchKeyValue(blocker, "targetname", name); + // DispatchKeyValue(blocker, "excludednpc", "player"); + TeleportEntity(blocker, this.origin, this.angles, NULL_VECTOR); + if(!DispatchSpawn(blocker)) return -1; + SetEntPropVector(blocker, Prop_Send, "m_vecMins", this.mins); + SetEntPropVector(blocker, Prop_Send, "m_vecMaxs", this.size); + SetEntProp(blocker, Prop_Send, "m_nSolidType", 2); + int enteffects = GetEntProp(blocker, Prop_Send, "m_fEffects"); + enteffects |= 32; //EF_NODRAW + SetEntProp(blocker, Prop_Send, "m_fEffects", enteffects); + AcceptEntityInput(blocker, "Enable"); + + this.Draw(WALL_COLOR, 5.0, 1.0); + this.Reset(); + return isEdit ? -2 : createdWalls.Push(EntIndexToEntRef(blocker)); + } + + int Copy() { + if(this.entity == INVALID_ENT_REFERENCE) return -1; + char classname[64]; + GetEntityClassname(this.entity, classname, sizeof(classname)); + + int entity = CreateEntityByName(classname); + PrintToServer("Created %s: %d", classname, entity); + if(entity == -1) return -1; + GetEntPropString(this.entity, Prop_Data, "m_ModelName", classname, sizeof(classname)); + DispatchKeyValue(entity, "model", classname); + PrintToServer("Set model %s: %d", classname, entity); + DispatchSpawn(entity); + TeleportEntity(entity, this.origin, this.angles, NULL_VECTOR); + this.entity = entity; + return entity; + } + + void Import(int entity, bool makeCopy = false, wallMode mode = SCALE) { + this.Reset(); + GetEntPropVector(entity, Prop_Send, "m_vecOrigin", this.origin); + GetEntPropVector(entity, Prop_Send, "m_angRotation", this.angles); + GetEntPropVector(entity, Prop_Send, "m_vecMins", this.mins); + GetEntPropVector(entity, Prop_Send, "m_vecMaxs", this.size); + if(!makeCopy) { + this.entity = entity; + } + this.SetMode(mode); + } +} + +WallBuilderData WallBuilder[MAXPLAYERS+1]; + + +// TODO: Stacker, copy tool, new command? +public Action Command_MakeWall(int client, int args) { + if(WallBuilder[client].IsActive()) { + ReplyToCommand(client, "\x04[Hats]\x01 You are already editing/building, either finish with \x05/wall build\x01 or cancel with \x04/wall cancel\x01"); + } else { + WallBuilder[client].Reset(); + if(args > 0) { + // Get values for X, Y and Z axis (defaulting to 1.0): + char arg2[8]; + for(int i = 0; i < 3; i++) { + GetCmdArg(i + 1, arg2, sizeof(arg2)); + float value; + if(StringToFloatEx(arg2, value) == 0) { + value = 1.0; + } + WallBuilder[client].size[i] = value; + } + + float rot[3]; + GetClientEyeAngles(client, rot); + // Flip X and Y depending on rotation + // TODO: Validate + if(rot[2] > 45 && rot[2] < 135 || rot[2] > -135 && rot[2] < -45) { + float temp = WallBuilder[client].size[0]; + WallBuilder[client].size[0] = WallBuilder[client].size[1]; + WallBuilder[client].size[1] = temp; + } + + WallBuilder[client].CalculateMins(); + } + + WallBuilder[client].SetMode(SCALE); + GetCursorLimited(client, 100.0, WallBuilder[client].origin, Filter_IgnorePlayer); + PrintToChat(client, "\x04[Hats]\x01 New Wall Started. End with \x05/wall build\x01 or \x04/wall cancel\x01"); + PrintToChat(client, "\x04[Hats]\x01 Mode: \x05Scale\x01"); + } + return Plugin_Handled; +} + +public Action Command_ManageWalls(int client, int args) { + if(args == 0) { + PrintToChat(client, "\x04[Hats]\x01 Created Walls: \x05%d\x01", createdWalls.Length); + for(int i = 1; i <= createdWalls.Length; i++) { + GlowWall(i, 20.0); + } + return Plugin_Handled; + } + char arg1[16], arg2[16]; + GetCmdArg(1, arg1, sizeof(arg1)); + GetCmdArg(2, arg2, sizeof(arg2)); + if(StrEqual(arg1, "build") || StrEqual(arg1, "done")) { + // Remove frozen flag from user, as some modes use this + int flags = GetEntityFlags(client) & ~FL_FROZEN; + SetEntityFlags(client, flags); + + int id = WallBuilder[client].Build(); + if(id == -1) { + PrintToChat(client, "\x04[Hats]\x01 Wall Creation: \x04Error\x01"); + } else if(id == -2) { + PrintToChat(client, "\x04[Hats]\x01 Wall Edit: \x04Complete\x01"); + } else if(id == -3) { + PrintToChat(client, "\x04[Hats]\x01 Entity Edit: \x04Complete\x01"); + } else { + PrintToChat(client, "\x04[Hats]\x01 Wall Creation: \x05Wall #%d Created\x01", id + 1); + } + } else if(StrEqual(arg1, "cancel")) { + int flags = GetEntityFlags(client) & ~FL_FROZEN; + SetEntityFlags(client, flags); + WallBuilder[client].SetMode(INACTIVE); + PrintToChat(client, "\x04[Hats]\x01 Wall Creation: \x04Cancelled\x01"); + } else if(StrEqual(arg1, "export")) { + // TODO: support exp #id + float origin[3], angles[3], size[3]; + if(WallBuilder[client].IsActive()) { + origin = WallBuilder[client].origin; + angles = WallBuilder[client].angles; + size = WallBuilder[client].size; + Export(client, arg2, WallBuilder[client].entity, origin, angles, size); + } else { + int id = GetWallId(client, arg2); + if(id == -1) return Plugin_Handled; + int entity = GetWallEntity(id); + GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin); + GetEntPropVector(entity, Prop_Send, "m_vecAngles", angles); + GetEntPropVector(entity, Prop_Send, "m_vecMaxs", size); + Export(client, arg2, entity, origin, angles, size); + } + + + } else if(StrEqual(arg1, "delete")) { + if(WallBuilder[client].IsActive() && args == 1) { + int entity = WallBuilder[client].entity; + if(IsValidEntity(entity)) { + PrintToChat(client, "\x04[Hats]\x01 You are not editing any existing entity, use \x05/wall cancel\x01 to stop or \x05/wall delete "); + } else if(entity > MaxClients) { + RemoveEntity(entity); + WallBuilder[client].Reset(); + PrintToChat(client, "\x04[Hats]\x01 Deleted current entity"); + } else { + PrintToChat(client, "\x04[Hats]\x01 Cannot delete player entities."); + } + } else if(StrEqual(arg2, "all")) { + int walls = createdWalls.Length; + for(int i = 1; i <= createdWalls.Length; i++) { + DeleteWall(i); + } + PrintToChat(client, "\x04[Hats]\x01 Deleted \x05%d\x01 Walls", walls); + } else { + int id = GetWallId(client, arg2); + if(id > -1) { + DeleteWall(id); + PrintToChat(client, "\x04[Hats]\x01 Deleted Wall: \x05#%d\x01", id); + } + } + } else if(StrEqual(arg1, "create")) { + ReplyToCommand(client, "\x04[Hats]\x01 Syntax: /mkwall [size x] [size y] [size z]"); + } else if(StrEqual(arg1, "toggle")) { + if(StrEqual(arg2, "all")) { + int walls = createdWalls.Length; + for(int i = 1; i <= createdWalls.Length; i++) { + int entity = GetWallEntity(i); + AcceptEntityInput(entity, "Toggle"); + GlowWall(i); + } + PrintToChat(client, "\x04[Hats]\x01 Toggled \x05%d\x01 walls", walls); + } else { + int id = GetWallId(client, arg2); + if(id > -1) { + int entity = GetWallEntity(id); + AcceptEntityInput(entity, "Toggle"); + GlowWall(id); + PrintToChat(client, "\x04[Hats]\x01 Toggled Wall: \x05#%d\x01", id); + } + } + } else if(StrEqual(arg1, "filter")) { + if(args < 3) { + ReplyToCommand(client, "\x04[Hats]\x01 Syntax: \x05/walls filter \x04"); + ReplyToCommand(client, "\x04[Hats]\x01 Valid filters: \x05player"); + return Plugin_Handled; + } + + char arg3[32]; + GetCmdArg(3, arg3, sizeof(arg3)); + + SetVariantString(arg3); + if(StrEqual(arg2, "all")) { + int walls = createdWalls.Length; + for(int i = 1; i <= createdWalls.Length; i++) { + int entity = GetWallEntity(i); + AcceptEntityInput(entity, "SetExcluded"); + } + PrintToChat(client, "\x04[Hats]\x01 Set %d walls' filter to \x05%s\x01", walls, arg3); + } else { + int id = GetWallId(client, arg2); + if(id > -1) { + int entity = GetWallEntity(id); + AcceptEntityInput(entity, "SetExcluded"); + PrintToChat(client, "\x04[Hats]\x01 Set wall #%d filter to \x05%s\x01", id, arg3); + } + } + } else if(StrEqual(arg1, "edit")) { + int id = GetWallId(client, arg2); + if(id > -1) { + int entity = GetWallEntity(id); + WallBuilder[client].Import(entity); + PrintToChat(client, "\x04[Hats]\x01 Editing wall \x05%d\x01. End with \x05/wall done\x01 or \x04/wall cancel\x01", id); + PrintToChat(client, "\x04[Hats]\x01 Mode: \x05Scale\x01"); + } + } else if(StrEqual(arg1, "edite") || arg1[0] == 'c') { + int index = GetLookingEntity(client, Filter_ValidHats); //GetClientAimTarget(client, false); + if(index > 0) { + WallBuilder[client].Import(index, false, MOVE_ORIGIN); + WallBuilder[client].canScale = false; + char classname[32]; + char targetname[32]; + GetEntityClassname(index, classname, sizeof(classname)); + GetEntPropString(index, Prop_Data, "m_target", targetname, sizeof(targetname)); + PrintToChat(client, "\x04[Hats]\x01 Editing entity \x05%d (%s) [%s]\x01. End with \x05/wall done\x01", index, classname, targetname); + PrintToChat(client, "\x04[Hats]\x01 Mode: \x05Move & Rotate\x01"); + } else { + ReplyToCommand(client, "\x04[Hats]\x01 Invalid or non existent entity"); + } + } else if(StrEqual(arg1, "copy")) { + if(WallBuilder[client].IsActive()) { + int oldEntity = WallBuilder[client].entity; + if(oldEntity == INVALID_ENT_REFERENCE) { + PrintToChat(client, "\x04[Hats]\x01 Finish editing your wall first: \x05/wall done\x01 or \x04/wall cancel\x01"); + } else { + int entity = WallBuilder[client].Copy(); + PrintToChat(client, "\x04[Hats]\x01 Editing copy \x05%d\x01 of entity \x05%d\x01. End with \x05/wall done\x01 or \x04/wall cancel\x01", entity, oldEntity); + } + } else { + int id = GetWallId(client, arg2); + if(id > -1) { + int entity = GetWallEntity(id); + WallBuilder[client].Import(entity, true); + GetCursorLimited(client, 100.0, WallBuilder[client].origin, Filter_IgnorePlayer); + PrintToChat(client, "\x04[Hats]\x01 Editing copy of wall \x05%d\x01. End with \x05/wall build\x01 or \x04/wall cancel\x01", id); + PrintToChat(client, "\x04[Hats]\x01 Mode: \x05Scale\x01"); + } + } + } else if(StrEqual(arg1, "list")) { + for(int i = 1; i <= createdWalls.Length; i++) { + int entity = GetWallEntity(i); + ReplyToCommand(client, "Wall #%d - EntIndex: %d", i, EntRefToEntIndex(entity)); + } + } else { + ReplyToCommand(client, "\x04[Hats]\x01 See console for list of commands"); + GetCmdArg(0, arg1, sizeof(arg1)); + PrintToConsole(client, "%s done / build - Finishes editing, creates wall if making wall", arg1); + PrintToConsole(client, "%s cancel - Cancels editing (for entity edits is same as done)", arg1); + PrintToConsole(client, "%s list - Lists all walls", arg1); + PrintToConsole(client, "%s filter - Sets classname filter for walls, doesnt really work", arg1); + PrintToConsole(client, "%s toggle - Toggles if wall is active (collides)", arg1); + PrintToConsole(client, "%s delete - Deletes the wall(s)", arg1); + PrintToConsole(client, "%s edit - Edits wall id", arg1); + PrintToConsole(client, "%s copy [id] - If editing creates a new copy of wall/entity, else copies wall id", arg1); + PrintToConsole(client, "%s cursor - Starts editing the entity you looking at", arg1); + } + return Plugin_Handled; +} + +int GetWallId(int client, const char[] arg) { + int id; + if(StringToIntEx(arg, id) > 0 && id > 0 && id <= createdWalls.Length) { + int entity = GetWallEntity(id); + if(!IsValidEntity(entity)) { + ReplyToCommand(client, "\x04[Hats]\x01 The wall with specified id no longer exists."); + createdWalls.Erase(id); + return -2; + } + return id; + } else { + ReplyToCommand(client, "\x04[Hats]\x01 Invalid wall id, must be between 0 - %d", createdWalls.Length - 1 ); + return -1; + } +} + +int GetWallEntity(int id) { + if(id <= 0 || id > createdWalls.Length) { + ThrowError("Invalid wall id (%d)", id); + } + return createdWalls.Get(id - 1); +} + +void GlowWall(int id, float lifetime = 5.0) { + int ref = GetWallEntity(id); + if(IsValidEntity(ref)) { + float pos[3], mins[3], maxs[3], angles[3]; + GetEntPropVector(ref, Prop_Send, "m_angRotation", angles); + GetEntPropVector(ref, Prop_Send, "m_vecOrigin", pos); + GetEntPropVector(ref, Prop_Send, "m_vecMins", mins); + GetEntPropVector(ref, Prop_Send, "m_vecMaxs", maxs); + Effect_DrawBeamBoxRotatableToAll(pos, mins, maxs, angles, g_iLaserIndex, 0, 0, 30, lifetime, 0.4, 0.4, 0, 1.0, WALL_COLOR, 0); + } +} + +void DeleteWall(int id) { + GlowWall(id); + int ref = GetWallEntity(id); + if(IsValidEntity(ref)) { + RemoveEntity(ref); + } + createdWalls.Erase(id - 1); +} + + void Export(int client, const char[] expType, int entity, const float origin[3], const float angles[3], const float size[3]) { + char sPath[PLATFORM_MAX_PATH]; + char currentMap[64]; + GetCurrentMap(currentMap, sizeof(currentMap)); + + BuildPath(Path_SM, sPath, sizeof(sPath), "data/exports"); + CreateDirectory(sPath, 1406); + BuildPath(Path_SM, sPath, sizeof(sPath), "data/exports/%s.cfg", currentMap); + File file = OpenFile(sPath, "w"); + if(file == null) { + PrintToServer("[Hats] Export: Cannot open \"%s\", cant write", sPath); + } + + PrintWriteLine(client, file, "{"); + if(entity != INVALID_ENT_REFERENCE) { + char model[64]; + GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); + if(StrEqual(expType, "json")) { + PrintWriteLine(client, file, "\t\"model\": \"%s\",", model); + } else{ + PrintWriteLine(client, file, "\t\"model\" \"%s\"", model); + } + } + + if(StrEqual(expType, "json")) { + PrintWriteLine(client, file, "\t\"origin\": [%.2f, %.2f, %.2f],", origin[0], origin[1], origin[2]); + PrintWriteLine(client, file, "\t\"angles\": [%.2f, %.2f, %.2f],", angles[0], angles[1], angles[2]); + PrintWriteLine(client, file, "\t\"size\": [%.2f, %.2f, %.2f]", size[0], size[1], size[2]); + } else { + PrintWriteLine(client, file, "\t\"origin\" \"%.2f %.2f %.2f\"", origin[0], origin[1], origin[2]); + PrintWriteLine(client, file, "\t\"angles\" \"%.2f %.2f %.2f\"", angles[0], angles[1], angles[2]); + PrintWriteLine(client, file, "\t\"size\" \"%.2f %.2f %.2f\"", size[0], size[1], size[2]); + } + PrintWriteLine(client, file, "}"); + delete file; +} + +void PrintWriteLine(int client, File file, const char[] format, any ...) { + char line[100]; + VFormat(line, sizeof(line), format, 4); + if(file != null) + file.WriteLine(line); + PrintToChat(client, line); +} + +enum struct WallModelSizeEntry { + char name[32]; + char model[64]; +} +enum struct WallModelEntry { + char name[32]; + + WallModelSizeEntry size1; + WallModelSizeEntry size2; + WallModelSizeEntry size3; +} +ArrayList wallModels; + +void LoadModels() { + if(wallModels != null) delete wallModels; + wallModels = new ArrayList(sizeof(WallModelEntry)); + KeyValues kv = new KeyValues("WallData"); + + char sPath[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, sPath, sizeof(sPath), "data/walls_data.cfg"); + + if(!FileExists(sPath) || !kv.ImportFromFile(sPath)) { + delete kv; + PrintToServer("[FTT] Could not load phrase list from data/walls_data.cfg"); + return; + } + char name[32]; + // Go through all the words: + // kv.GotoFirstSubKey(); + // int i = 0; + // char buffer[4]; + // do { + // kv.GetSectionName(name, sizeof(name)); + // for(;;) { + // IntToString(++i, buffer, sizeof(buffer)); + // kv.GetString(buffer, phrase, MAX_PHRASE_LENGTH, "_null"); + // if(strcmp(phrase, "_null") == 0) break; + // phrases.PushString(phrase); + // } + // i = 0; + // if(StrEqual(word, "_full message phrases")) { + // fullMessagePhraseList = phrases.Clone(); + // continue; + // } + // #if defined DEBUG_PHRASE_LOAD + // PrintToServer("Loaded %d phrases for word \"%s\"", phrases.Length, word); + // #endif + // REPLACEMENT_PHRASES.SetValue(word, phrases.Clone(), true); + // } while (kv.GotoNextKey(false)); + + delete kv; +} + \ No newline at end of file diff --git a/scripting/l4d2_hats.sp b/scripting/l4d2_hats.sp index c88018b..12247ff 100644 --- a/scripting/l4d2_hats.sp +++ b/scripting/l4d2_hats.sp @@ -16,285 +16,27 @@ static float EMPTY_ANG[3] = { 0.0, 0.0, 0.0 }; #include #include -enum hatFlags { - HAT_NONE = 0, - HAT_POCKET = 1, - HAT_REVERSED = 2, - HAT_COMMANDABLE = 4, - HAT_RAINBOW = 8 -} -enum struct HatData { - int entity; // The entity REFERENCE - int visibleEntity; // Thee visible entity REF - - // Original data for entity - float orgPos[3]; - float orgAng[3]; - float offset[3]; - float angles[3]; - int collisionGroup; - int solidType; - int moveType; - - float scale; - int flags; - float rainbowColor[3]; - int rainbowTicks; - bool rainbowReverse; -} -enum hatFeatures { - HatConfig_None = 0, - HatConfig_PlayerHats = 1, - HatConfig_RespectAdminImmunity = 2, - HatConfig_FakeHat = 4, - HatConfig_NoSaferoomHats = 8, - HatConfig_PlayerHatConsent = 16, - HatConfig_InfectedHats = 32, - HatConfig_ReversedHats = 64, - HatConfig_DeleteThrownHats = 128 -} -HatData hatData[MAXPLAYERS+1]; -int lastHatRequestTime[MAXPLAYERS+1], g_iLaserIndex; bool tempGod[MAXPLAYERS+1]; bool inSaferoom[MAXPLAYERS+1]; -static float cmdThrottle[MAXPLAYERS+1]; +int g_iLaserIndex; + +float cmdThrottle[MAXPLAYERS+1]; static bool onLadder[MAXPLAYERS+1]; float lastAng[MAXPLAYERS+1][3]; Cookie noHatVictimCookie; + ConVar cvar_sm_hats_enabled; ConVar cvar_sm_hats_flags; ConVar cvar_sm_hat_rainbow_speed; ConVar cvar_sm_hats_blacklist_enabled; -#define MAX_FORBIDDEN_CLASSNAMES 12 -static char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = { - "prop_door_rotating_checkpoint", - "env_physics_blocker", - "env_player_blocker", - "func_brush", - "func_simpleladder", - "prop_door_rotating", - "func_button", - "func_elevator", - "func_button_timed", - // "func_movelinear", - // "infected", - "func_lod", - "func_door", - "prop_ragdoll" -}; -#define MAX_REVERSE_CLASSNAMES 2 -static char REVERSE_CLASSNAMES[MAX_REVERSE_CLASSNAMES][] = { - "infected", - "func_movelinear" -}; -int BUILDER_COLOR[4] = { 0, 255, 0, 235 }; -int WALL_COLOR[4] = { 255, 0, 0, 235 }; -float ORIGIN_SIZE[3] = { 2.0, 2.0, 2.0 }; - -enum wallMode { - INACTIVE = 0, - MOVE_ORIGIN, - SCALE, - FREELOOK -} - -ArrayList createdWalls; - -enum struct WallBuilderData { - float origin[3]; - float mins[3]; - float angles[3]; - float size[3]; - wallMode mode; - int axis; - int snapAngle; - int moveSpeed; - float moveDistance; - int entity; - bool canScale; - - void Reset() { - this.size[0] = this.size[1] = this.size[2] = 5.0; - this.angles[0] = this.angles[1] = this.angles[2] = 0.0; - this.axis = 1; - this.canScale = true; - this.moveDistance = 200.0; - this.moveSpeed = 1; - this.snapAngle = 30; - this.entity = INVALID_ENT_REFERENCE; - this.CalculateMins(); - this.SetMode(INACTIVE); - } - - void CalculateMins() { - this.mins[0] = -this.size[0]; - this.mins[1] = -this.size[1]; - this.mins[2] = -this.size[2]; - } - - void Draw(int color[4], float lifetime, float amplitude = 0.1) { - if(!this.canScale && this.entity != INVALID_ENT_REFERENCE) { - TeleportEntity(this.entity, this.origin, this.angles, NULL_VECTOR); - } else { - Effect_DrawBeamBoxRotatableToAll(this.origin, this.mins, this.size, this.angles, g_iLaserIndex, 0, 0, 30, lifetime, 0.4, 0.4, 0, amplitude, color, 0); - } - Effect_DrawAxisOfRotationToAll(this.origin, this.angles, ORIGIN_SIZE, g_iLaserIndex, 0, 0, 30, 0.2, 0.1, 0.1, 0, 0.0, 0); - } - - bool IsActive() { - return this.mode != INACTIVE; - } - - void SetMode(wallMode mode) { - this.mode = mode; - } - - void CycleMode(int client, float tick) { - if(tick - cmdThrottle[client] <= 0.25) return; - int flags = GetEntityFlags(client) & ~FL_FROZEN; - SetEntityFlags(client, flags); - switch(this.mode) { - // MODES: - // - MOVE (cursor point) - // - ROTATE - // - SCALE - // - FREECAM - case MOVE_ORIGIN: { - if(this.canScale) { - this.mode = SCALE; - PrintToChat(client, "\x04[Walls]\x01 Mode: \x05Scale\x01 (Press \x04RELOAD\x01 to change mode)"); - } else { - this.mode = FREELOOK; - PrintToChat(client, "\x04[Walls]\x01 Mode: \x05Freelook\x01 (Press \x04RELOAD\x01 to change mode)"); - } - } - case SCALE: { - this.mode = FREELOOK; - PrintToChat(client, "\x04[Walls]\x01 Mode: \x05Freelook\x01 (Press \x04RELOAD\x01 to change mode)"); - } - case FREELOOK: { - this.mode = MOVE_ORIGIN; - PrintToChat(client, "\x04[Walls]\x01 Mode: \x05Move & Rotate\x01 (Press \x04RELOAD\x01 to change mode)"); - // PrintToChat(client, "Hold \x04USE (E)\x01 to rotate, \x04WALK (SHIFT)\x01 to change speed"); - } - } - cmdThrottle[client] = tick; - } - - void CycleAxis(int client, float tick) { - if(tick - cmdThrottle[client] <= 0.15) return; - if(this.axis == 0) { - this.axis = 1; - PrintToChat(client, "\x04[Walls]\x01 Rotate Axis: \x05HEADING (Y)\x01"); - } else if(this.axis == 1) { - this.axis = 2; - PrintToChat(client, "\x04[Walls]\x01 Rotate Axis: \x05PITCH (X)\x01"); - } else { - this.axis = 0; - PrintToChat(client, "\x04[Walls]\x01 Rotate Axis: \x05ROLL (Z)\x01"); - } - cmdThrottle[client] = tick; - } - - void CycleSnapAngle(int client, float tick) { - if(tick - cmdThrottle[client] <= 0.15) return; - switch(this.snapAngle) { - case 1: this.snapAngle = 15; - case 15: this.snapAngle = 30; - case 30: this.snapAngle = 45; - case 45: this.snapAngle = 90; - case 90: this.snapAngle = 1; - } - - this.angles[0] = SnapTo(this.angles[0], float(this.snapAngle)); - this.angles[1] = SnapTo(this.angles[1], float(this.snapAngle)); - this.angles[2] = SnapTo(this.angles[2], float(this.snapAngle)); - - if(this.snapAngle == 1) - PrintToChat(client, "\x04[Walls]\x01 Rotate Snap Degrees: \x04(OFF)\x01", this.snapAngle); - else - PrintToChat(client, "\x04[Walls]\x01 Rotate Snap Degrees: \x05%d\x01", this.snapAngle); - cmdThrottle[client] = tick; - } - - void CycleSpeed(int client, float tick) { - if(tick - cmdThrottle[client] <= 0.25) return; - this.moveSpeed++; - if(this.moveSpeed > 10) this.moveSpeed = 1; - PrintToChat(client, "\x04[Walls]\x01 Scale Speed: \x05%d\x01", this.moveSpeed); - // if(this.movetype == 0) { - // this.movetype = 1; - // PrintToChat(client, "\x04[SM]\x01 Move Type: \x05HEADING (Y)\x01"); - // } else { - // this.movetype = 0; - // PrintToChat(client, "\x04[SM]\x01 Rotate Axis: \x05PITCH (X)\x01"); - // } - cmdThrottle[client] = tick; - } - - int Build() { - if(!this.canScale) { - this.Reset(); - return -3; - } - // Don't need to build a new one if we editing: - int blocker = this.entity; - bool isEdit = false; - if(blocker != INVALID_ENT_REFERENCE) { - AcceptEntityInput(this.entity, "Kill"); - isEdit = true; - } - blocker = CreateEntityByName("func_brush"); - if(blocker == -1) return -1; - DispatchKeyValueVector(blocker, "mins", this.mins); - DispatchKeyValueVector(blocker, "maxs", this.size); - DispatchKeyValueVector(blocker, "boxmins", this.mins); - DispatchKeyValueVector(blocker, "boxmaxs", this.size); - - DispatchKeyValueVector(blocker, "angles", this.angles); - DispatchKeyValue(blocker, "model", DUMMY_MODEL); - DispatchKeyValue(blocker, "intialstate", "1"); - // DispatchKeyValueVector(blocker, "angles", this.angles); - DispatchKeyValue(blocker, "BlockType", "4"); - char name[32]; - Format(name, sizeof(name), "l4d2_hats_%d", createdWalls.Length); - DispatchKeyValue(blocker, "targetname", name); - // DispatchKeyValue(blocker, "excludednpc", "player"); - TeleportEntity(blocker, this.origin, this.angles, NULL_VECTOR); - if(!DispatchSpawn(blocker)) return -1; - SetEntPropVector(blocker, Prop_Send, "m_vecMins", this.mins); - SetEntPropVector(blocker, Prop_Send, "m_vecMaxs", this.size); - SetEntProp(blocker, Prop_Send, "m_nSolidType", 2); - int enteffects = GetEntProp(blocker, Prop_Send, "m_fEffects"); - enteffects |= 32; //EF_NODRAW - SetEntProp(blocker, Prop_Send, "m_fEffects", enteffects); - AcceptEntityInput(blocker, "Enable"); - - this.Draw(WALL_COLOR, 5.0, 1.0); - this.Reset(); - return isEdit ? -2 : createdWalls.Push(EntIndexToEntRef(blocker)); - } - - void Import(int entity, bool makeCopy = false) { - this.Reset(); - GetEntPropVector(entity, Prop_Send, "m_vecOrigin", this.origin); - GetEntPropVector(entity, Prop_Send, "m_angRotation", this.angles); - GetEntPropVector(entity, Prop_Send, "m_vecMins", this.mins); - GetEntPropVector(entity, Prop_Send, "m_vecMaxs", this.size); - if(!makeCopy) { - this.entity = entity; - } - this.SetMode(SCALE); - } -} - -WallBuilderData WallBuilder[MAXPLAYERS+1]; +#include +#include public Plugin myinfo = { @@ -307,6 +49,7 @@ public Plugin myinfo = ArrayList NavAreas; + public void OnPluginStart() { EngineVersion g_Game = GetEngineVersion(); if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) { @@ -325,11 +68,12 @@ public void OnPluginStart() { RegAdminCmd("sm_mkwall", Command_MakeWall, ADMFLAG_CHEATS); RegAdminCmd("sm_walls", Command_ManageWalls, ADMFLAG_CHEATS); RegAdminCmd("sm_wall", Command_ManageWalls, ADMFLAG_CHEATS); + RegAdminCmd("sm_edit", Command_ManageWalls, ADMFLAG_CHEATS); cvar_sm_hats_blacklist_enabled = CreateConVar("sm_hats_blacklist_enabled", "1", "Is the prop blacklist enabled", FCVAR_NONE, true, 0.0, true, 1.0); cvar_sm_hats_enabled = CreateConVar("sm_hats_enabled", "1.0", "Enable hats.\n0=OFF, 1=Admins Only, 2=Any", FCVAR_NONE, true, 0.0, true, 2.0); cvar_sm_hats_enabled.AddChangeHook(Event_HatsEnableChanged); - cvar_sm_hats_flags = CreateConVar("sm_hats_features", "27", "Toggle certain features. Add bits together\n1 = Player Hats\n2 = Respect Admin Immunity\n4 = Create a fake hat for hat wearer to view instead, and for yeeting\n8 = No saferoom hats\n16 = Player hatting requires victim consent\n32 = Infected Hats\n64 = Reverse hats", FCVAR_CHEAT, true, 0.0); + cvar_sm_hats_flags = CreateConVar("sm_hats_features", "155", "Toggle certain features. Add bits together\n1 = Player Hats\n2 = Respect Admin Immunity\n4 = Create a fake hat for hat wearer to view instead, and for yeeting\n8 = No saferoom hats\n16 = Player hatting requires victim consent\n32 = Infected Hats\n64 = Reverse hats", FCVAR_CHEAT, true, 0.0); cvar_sm_hat_rainbow_speed = CreateConVar("sm_hats_rainbow_speed", "1", "Speed of rainbow", FCVAR_NONE, true, 0.0); noHatVictimCookie = new Cookie("hats_no_target", "Disables other players from making you their hat", CookieAccess_Public); @@ -343,569 +87,18 @@ public void OnPluginStart() { createdWalls.Push(EntIndexToEntRef(entity)); } } + + for(int i = 1; i <= MaxClients; i++) { + WallBuilder[i].Reset(true); + } } //////////////////////////////////////////////////////////////// -public Action Command_DoAHat(int client, int args) { - int hatter = GetHatter(client); - if(hatter > 0) { - ClearHat(hatter, HasFlag(hatter, HAT_REVERSED)); - PrintToChat(hatter, "[Hats] %N has unhatted themselves", client); - return Plugin_Handled; - } - - AdminId adminId = GetUserAdmin(client); - if(cvar_sm_hats_enabled.IntValue == 1) { - if(adminId == INVALID_ADMIN_ID) { - PrintToChat(client, "[Hats] Hats are for admins only"); - return Plugin_Handled; - } - } else if(!adminId.HasFlag(Admin_Cheats)) { - PrintToChat(client, "[Hats] You do not have permission"); - return Plugin_Handled; - } - if(cvar_sm_hats_enabled.IntValue == 0) { - ReplyToCommand(client, "[Hats] Hats are disabled"); - return Plugin_Handled; - } else if(GetClientTeam(client) != 2 && ~cvar_sm_hats_flags.IntValue & view_as(HatConfig_InfectedHats)) { - PrintToChat(client, "[Hats] Hats are only available for survivors."); - return Plugin_Handled; - } - - int oldVisible = EntRefToEntIndex(hatData[client].visibleEntity); - if(oldVisible > 0) { - AcceptEntityInput(oldVisible, "Kill"); - hatData[client].visibleEntity = INVALID_ENT_REFERENCE; - } - - int entity = GetHat(client); - if(entity > 0) { - char arg[4]; - GetCmdArg(1, arg, sizeof(arg)); - // int orgEntity = entity; - if(HasFlag(client, HAT_REVERSED)) { - entity = client; - } - ClearParent(entity); - - if(arg[0] == 's') { - char sizeStr[4]; - GetCmdArg(2, sizeStr, sizeof(sizeStr)); - float size = StringToFloat(sizeStr); - if(size == 0.0) { - ReplyToCommand(client, "[Hats] Invalid size"); - return Plugin_Handled; - } - if(HasEntProp(entity, Prop_Send, "m_flModelScale")) - SetEntPropFloat(entity, Prop_Send, "m_flModelScale", size); - else - PrintHintText(client, "Hat does not support scaling"); - // Change the size of it's parent instead - int child = -1; - while((child = FindEntityByClassname(child, "*")) != INVALID_ENT_REFERENCE ) - { - int parent = GetEntPropEnt(child, Prop_Data, "m_pParent"); - if(parent == entity) { - if(HasEntProp(child, Prop_Send, "m_flModelScale")) { - PrintToConsole(client, "found child %d for %d", child, entity); - SetEntPropFloat(child, Prop_Send, "m_flModelScale", size); - } else { - PrintToChat(client, "Child %d for %d cannot be scaled", child, entity); - } - break; - } - } - // Reattach entity: - EquipHat(client, entity); - return Plugin_Handled; - } else if(arg[0] == 'r' && arg[1] == 'a') { - SetFlag(client, HAT_RAINBOW); - hatData[client].rainbowTicks = 0; - hatData[client].rainbowReverse = false; - hatData[client].rainbowColor[0] = 0.0; - hatData[client].rainbowColor[1] = 255.0; - hatData[client].rainbowColor[2] = 255.0; - EquipHat(client, entity); - ReplyToCommand(client, "Rainbow hats enabled"); - return Plugin_Handled; - } - - // Re-enable physics and restore collision/solidity - AcceptEntityInput(entity, "EnableMotion"); - SetEntProp(entity, Prop_Send, "m_CollisionGroup", hatData[client].collisionGroup); - SetEntProp(entity, Prop_Send, "m_nSolidType", hatData[client].solidType); - - // Remove frozen flag (only "infected" and "witch" are frozen, but just incase:) - int flags = GetEntityFlags(entity) & ~FL_FROZEN; - SetEntityFlags(entity, flags); - - // Clear visible hats (HatConfig_FakeHat is enabled) - int visibleEntity = EntRefToEntIndex(hatData[client].visibleEntity); - SDKUnhook(entity, SDKHook_SetTransmit, OnRealTransmit); - if(visibleEntity > 0) { - SDKUnhook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); - AcceptEntityInput(visibleEntity, "Kill"); - hatData[client].visibleEntity = INVALID_ENT_REFERENCE; - } - // Grant temp god & remove after time - tempGod[client] = true; - if(client <= MaxClients) CreateTimer(2.0, Timer_RemoveGod, GetClientUserId(client)); - if(entity <= MaxClients) CreateTimer(2.0, Timer_RemoveGod, GetClientUserId(entity)); - - // Restore movement: - if(entity <= MaxClients) { - // If player, remove roll & and just default to WALK movetype - hatData[client].orgAng[2] = 0.0; - SetEntityMoveType(entity, MOVETYPE_WALK); - } else { - // If not player, then just use whatever they were pre-hat - SetEntProp(entity, Prop_Send, "movetype", hatData[client].moveType); - } - - if(arg[0] == 'y') { // Hat yeeting: - GetClientEyeAngles(client, hatData[client].orgAng); - GetClientAbsOrigin(client, hatData[client].orgPos); - hatData[client].orgPos[2] += 45.0; - float ang[3], vel[3]; - - // Calculate the angle to throw at - GetClientEyeAngles(client, ang); - ang[2] = 0.0; - if(ang[0] > 0.0) ang[0] = -ang[0]; - // ang[0] = -45.0; - - // Calculate velocity to throw based on direction - vel[0] = Cosine(DegToRad(ang[1])) * 1500.0; - vel[1] = Sine(DegToRad(ang[1])) * 1500.0; - vel[2] = 700.0; - if(entity <= MaxClients) { - // For players, use the built in fling function - TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); - L4D2_CTerrorPlayer_Fling(entity, client, vel); - } /*else if(visibleEntity > 0) { - PrintToChat(client, "Yeeting fake car..."); - ClearParent(visibleEntity); - - SetEntProp(visibleEntity, Prop_Send, "movetype", 6); - - AcceptEntityInput(visibleEntity, "EnableMotion"); - - TeleportEntity(entity, OUT_OF_BOUNDS, hatData[client].orgAng, NULL_VECTOR); - TeleportEntity(visibleEntity, hatData[client].orgPos, hatData[client].orgAng, vel); - DataPack pack; - CreateDataTimer(4.0, Timer_PropYeetEnd, pack); - pack.WriteCell(hatData[client].entity); - pack.WriteCell(hatData[client].visibleEntity); - pack.WriteCell(hatData[client].collisionGroup); - pack.WriteCell(hatData[client].solidType); - pack.WriteCell(hatData[client].moveType); - hatData[client].visibleEntity = INVALID_ENT_REFERENCE; - hatData[client].entity = INVALID_ENT_REFERENCE; - } */ else { - // For actual props, offset it 35 units above and 80 units infront to reduce collision-incaps and then throw - GetHorizontalPositionFromClient(client, 80.0, hatData[client].orgPos); - hatData[client].orgPos[2] += 35.0; - TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, vel); - // Sleep the physics after enoug time for it to most likely have landed - CreateTimer(5.7, Timer_PropSleep, hatData[client].entity); - } - PrintToChat(client, "[Hats] Yeeted hat"); - hatData[client].entity = INVALID_ENT_REFERENCE; - return Plugin_Handled; - } else if(arg[0] == 'c') { - float pos[3]; - // Grabs a cursor position with some checks to prevent placing into (in)visible walls - if(GetSmartCursorLocation(client, pos)) { - if(CanHatBePlaced(client, pos)) { - if(entity <= MaxClients) - L4D_WarpToValidPositionIfStuck(entity); - hatData[client].orgPos = pos; - TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); - PrintToChat(client, "[Hats] Placed hat on cursor."); - } - } else { - PrintToChat(client, "[Hats] Could not find cursor position."); - } - } else if(arg[0] == 'p' || (entity <= MaxClients && arg[0] != 'r')) { - // Place the hat down on the cursor if specified OR if entity is hat - float pos[3], ang[3]; - if(HasFlag(client, HAT_REVERSED)) { - // If we are reversed, then place ourselves where our "hatter" is - GetClientEyePosition(entity, hatData[client].orgPos); - GetClientEyeAngles(entity, hatData[client].orgAng); - TeleportEntity(client, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); - PrintToChat(entity, "[Hats] Placed hat in front of you."); - } else { - // If we are normal, then get position infront of us, offset by model size - GetClientEyePosition(client, pos); - GetClientEyeAngles(client, ang); - GetHorizontalPositionFromOrigin(pos, ang, 80.0, pos); - ang[0] = 0.0; - float mins[3]; - GetEntPropVector(entity, Prop_Data, "m_vecMins", mins); - pos[2] += mins[2]; - // Find the nearest ground (raytrace bottom->up) - FindGround(pos, pos); - // Check if the destination is acceptable (not saferooms if enabled) - if(CanHatBePlaced(client, pos)) { - hatData[client].orgPos = pos; - hatData[client].orgAng = ang; - TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); - PrintToChat(client, "[Hats] Placed hat in front of you."); - } - } - } else if(arg[0] == 'd') { - // Use the new wall editor - WallBuilder[client].Reset(); - WallBuilder[client].entity = EntIndexToEntRef(entity); - WallBuilder[client].canScale = false; - WallBuilder[client].SetMode(MOVE_ORIGIN); - PrintToChat(client, "\x04[Walls] \x01Beta Prop Mover active for \x04%d", entity); - } else { - PrintToChat(client, "[Hats] Restored hat to its original position."); - } - - // Restore the scale pre-hat - if(hatData[client].scale > 0 && HasEntProp(entity, Prop_Send, "m_flModelScale")) - SetEntPropFloat(entity, Prop_Send, "m_flModelScale", hatData[client].scale); - - // If no other options performed, then restore to original position and remove our reference - AcceptEntityInput(entity, "Sleep"); - TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); - hatData[client].entity = INVALID_ENT_REFERENCE; - } else { - // Find a new hatable entity - int flags = 0; - entity = GetLookingEntity(client, Filter_ValidHats); - if(entity <= 0) { - PrintCenterText(client, "[Hats] No entity found"); - return Plugin_Handled; - } else if(entity == EntRefToEntIndex(WallBuilder[client].entity)) { - // Prevent making an entity you editing a hat - return Plugin_Handled; - } - - // Make hat reversed if 'r' passed in - if(args > 0) { - char arg[4]; - GetCmdArg(1, arg, sizeof(arg)); - if(arg[0] == 'r') { - flags |= view_as(HAT_REVERSED); - } - } - - int parent = GetEntPropEnt(entity, Prop_Data, "m_hParent"); - if(parent > 0 && entity > MaxClients) { - PrintToConsole(client, "[Hats] Selected a child entity, selecting parent (child %d -> parent %d)", entity, parent); - entity = parent; - } else if(entity <= MaxClients) { // Checks for hatting a player entity - if(GetClientTeam(entity) != 2 && ~cvar_sm_hats_flags.IntValue & view_as(HatConfig_InfectedHats)) { - PrintToChat(client, "[Hats] Cannot make enemy a hat... it's dangerous"); - return Plugin_Handled; - } else if(entity == EntRefToEntIndex(WallBuilder[client].entity)) { - // Old check left in in case you hatting child entity - PrintToChat(client, "[Hats] You are currently editing this entity"); - return Plugin_Handled; - } else if(inSaferoom[client] && cvar_sm_hats_flags.IntValue & view_as(HatConfig_NoSaferoomHats)) { - PrintToChat(client, "[Hats] Hats are not allowed in the saferoom"); - return Plugin_Handled; - } else if(!IsPlayerAlive(entity) || GetEntProp(entity, Prop_Send, "m_isHangingFromLedge") || L4D_IsPlayerCapped(entity)) { - PrintToChat(client, "[Hats] Player is either dead, hanging, or in the process of dying."); - return Plugin_Handled; - } else if(EntRefToEntIndex(hatData[entity].entity) == client) { - PrintToChat(client, "[Hats] Woah you can't be making a black hole, jesus be careful."); - return Plugin_Handled; - } else if(~cvar_sm_hats_flags.IntValue & view_as(HatConfig_PlayerHats)) { - PrintToChat(client, "[Hats] Player hats are disabled"); - return Plugin_Handled; - } else if(!CanTarget(entity)) { - PrintToChat(client, "[Hats] Player has disabled player hats for themselves."); - return Plugin_Handled; - } else if(!CanTarget(client)) { - PrintToChat(client, "[Hats] Cannot hat a player when you have player hats turned off"); - return Plugin_Handled; - } else if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_RespectAdminImmunity)) { - AdminId targetAdmin = GetUserAdmin(entity); - AdminId clientAdmin = GetUserAdmin(client); - if(targetAdmin != INVALID_ADMIN_ID && clientAdmin == INVALID_ADMIN_ID) { - PrintToChat(client, "[Hats] Cannot target an admin"); - return Plugin_Handled; - } else if(targetAdmin != INVALID_ADMIN_ID && targetAdmin.ImmunityLevel > clientAdmin.ImmunityLevel) { - PrintToChat(client, "[Hats] Cannot target %N, they are immune to you", entity); - return Plugin_Handled; - } - } - if(!IsFakeClient(entity) && cvar_sm_hats_flags.IntValue & view_as(HatConfig_PlayerHatConsent) && ~flags & view_as(HAT_REVERSED)) { - int lastRequestDiff = GetTime() - lastHatRequestTime[client]; - if(lastRequestDiff < PLAYER_HAT_REQUEST_COOLDOWN) { - PrintToChat(client, "[Hats] Player hat under %d seconds cooldown", lastRequestDiff); - return Plugin_Handled; - } - - Menu menu = new Menu(HatConsentHandler); - menu.SetTitle("%N: Requests to hat you", client); - char id[8]; - Format(id, sizeof(id), "%d|1", GetClientUserId(client)); - menu.AddItem(id, "Accept"); - Format(id, sizeof(id), "%d|0", GetClientUserId(client)); - menu.AddItem(id, "Reject"); - menu.Display(entity, 12); - PrintHintText(client, "Sent hat request to %N", entity); - PrintToChat(entity, "[Hats] %N requests to hat you, 1 to Accept, 2 to Reject. Expires in 12 seconds.", client); - return Plugin_Handled; - } - } - char classname[64]; - GetEntityClassname(entity, classname, sizeof(classname)); - // Check is pretty redudant as the traceray itself shouldn't grab it, but just in case: - if(cvar_sm_hats_blacklist_enabled.BoolValue) { - for(int i = 0; i < MAX_FORBIDDEN_CLASSNAMES; i++) { - if(StrEqual(FORBIDDEN_CLASSNAMES[i], classname)) { - PrintToChat(client, "[Hats] Entity (%s) is a blacklisted entity. Naughty.", classname); - return Plugin_Handled; - } - } - } - // Check for any class that should always be reversed - if(~flags & view_as(HAT_REVERSED)) { - for(int i = 0; i < MAX_REVERSE_CLASSNAMES; i++) { - if(StrEqual(REVERSE_CLASSNAMES[i], classname)) { - flags |= view_as(HAT_REVERSED); - break; - } - } - } - EquipHat(client, entity, classname, flags); - } - return Plugin_Handled; -} - -public Action Command_MakeWall(int client, int args) { - if(WallBuilder[client].IsActive()) { - ReplyToCommand(client, "\x04[Walls]\x01 You are already editing/building, either finish with \x05/wall build\x01 or cancel with \x04/wall cancel\x01"); - } else { - WallBuilder[client].Reset(); - if(args > 0) { - // Get values for X, Y and Z axis (defaulting to 1.0): - char arg2[8]; - for(int i = 0; i < 3; i++) { - GetCmdArg(i + 1, arg2, sizeof(arg2)); - float value; - if(StringToFloatEx(arg2, value) == 0) { - value = 1.0; - } - WallBuilder[client].size[i] = value; - } - - float rot[3]; - GetClientEyeAngles(client, rot); - // Flip X and Y depending on rotation - // TODO: Validate - if(rot[2] > 45 && rot[2] < 135 || rot[2] > -135 && rot[2] < -45) { - float temp = WallBuilder[client].size[0]; - WallBuilder[client].size[0] = WallBuilder[client].size[1]; - WallBuilder[client].size[1] = temp; - } - - WallBuilder[client].CalculateMins(); - } - - WallBuilder[client].SetMode(SCALE); - GetCursorLimited(client, 100.0, WallBuilder[client].origin, Filter_IgnorePlayer); - PrintToChat(client, "\x04[Walls]\x01 New Wall Started. End with \x05/wall build\x01 or \x04/wall cancel\x01"); - PrintToChat(client, "\x04[Walls]\x01 Mode: \x05Scale\x01"); - } - return Plugin_Handled; -} - -public Action Command_ManageWalls(int client, int args) { - if(args == 0) { - PrintToChat(client, "\x04[Walls]\x01 Created Walls: \x05%d\x01", createdWalls.Length); - for(int i = 1; i <= createdWalls.Length; i++) { - GlowWall(i, 20.0); - } - return Plugin_Handled; - } - char arg1[16], arg2[16]; - GetCmdArg(1, arg1, sizeof(arg1)); - GetCmdArg(2, arg2, sizeof(arg2)); - if(StrEqual(arg1, "build") || StrEqual(arg1, "done")) { - // Remove frozen flag from user, as some modes use this - int flags = GetEntityFlags(client) & ~FL_FROZEN; - SetEntityFlags(client, flags); - - int id = WallBuilder[client].Build(); - if(id == -1) { - PrintToChat(client, "\x04[Walls]\x01 Wall Creation: \x04Error\x01"); - } else if(id == -2) { - PrintToChat(client, "\x04[Walls]\x01 Wall Edit: \x04Complete\x01"); - } else if(id == -3) { - PrintToChat(client, "\x04[Walls]\x01 Entity Edit: \x04Complete\x01"); - } else { - PrintToChat(client, "\x04[Walls]\x01 Wall Creation: \x05Wall #%d Created\x01", id + 1); - } - } else if(StrEqual(arg1, "cancel")) { - int flags = GetEntityFlags(client) & ~FL_FROZEN; - SetEntityFlags(client, flags); - WallBuilder[client].SetMode(INACTIVE); - PrintToChat(client, "\x04[Walls]\x01 Wall Creation: \x04Cancelled\x01"); - } else if(StrEqual(arg1, "export")) { - // TODO: support exp #id - // int wallId = GetwallId(client, arg2, false); - PrintToChat(client, "{"); - PrintToChat(client, "\t\"origin\" \"%.2f %.2f %.2f\"", WallBuilder[client].origin[0], WallBuilder[client].origin[1], WallBuilder[client].origin[2]); - PrintToChat(client, "\t\"angles\" \"%.2f %.2f %.2f\"", WallBuilder[client].angles[0], WallBuilder[client].angles[1], WallBuilder[client].angles[2]); - PrintToChat(client, "\t\"size\" \"%.2f %.2f %.2f\"", WallBuilder[client].size[0], WallBuilder[client].size[1], WallBuilder[client].size[2]); - PrintToChat(client, "}"); - } else if(StrEqual(arg1, "delete")) { - if(StrEqual(arg2, "all")) { - int walls = createdWalls.Length; - for(int i = 1; i <= createdWalls.Length; i++) { - DeleteWall(i); - } - PrintToChat(client, "\x04[Walls]\x01 Deleted \x05%d\x01 Walls", walls); - } else { - int id = GetWallId(client, arg2); - if(id > -1) { - DeleteWall(id); - PrintToChat(client, "\x04[Walls]\x01 Deleted Wall: \x05#%d\x01", id); - } - } - } else if(StrEqual(arg1, "create")) { - ReplyToCommand(client, "\x04[Walls]\x01 Syntax: /mkwall [size x] [size y] [size z]"); - } else if(StrEqual(arg1, "toggle")) { - if(StrEqual(arg2, "all")) { - int walls = createdWalls.Length; - for(int i = 1; i <= createdWalls.Length; i++) { - int entity = GetWallEntity(i); - AcceptEntityInput(entity, "Toggle"); - GlowWall(i); - } - PrintToChat(client, "\x04[Walls]\x01 Toggled \x05%d\x01 walls", walls); - } else { - int id = GetWallId(client, arg2); - if(id > -1) { - int entity = GetWallEntity(id); - AcceptEntityInput(entity, "Toggle"); - GlowWall(id); - PrintToChat(client, "\x04[Walls]\x01 Toggled Wall: \x05#%d\x01", id); - } - } - } else if(StrEqual(arg1, "filter")) { - if(args < 3) { - ReplyToCommand(client, "\x04[Walls]\x01 Syntax: \x05/walls filter \x04"); - ReplyToCommand(client, "\x04[Walls]\x01 Valid filters: \x05player"); - return Plugin_Handled; - } - - char arg3[32]; - GetCmdArg(3, arg3, sizeof(arg3)); - - SetVariantString(arg3); - if(StrEqual(arg2, "all")) { - int walls = createdWalls.Length; - for(int i = 1; i <= createdWalls.Length; i++) { - int entity = GetWallEntity(i); - AcceptEntityInput(entity, "SetExcluded"); - } - PrintToChat(client, "\x04[Walls]\x01 Set %d walls' filter to \x05%s\x01", walls, arg3); - } else { - int id = GetWallId(client, arg2); - if(id > -1) { - int entity = GetWallEntity(id); - AcceptEntityInput(entity, "SetExcluded"); - PrintToChat(client, "\x04[Walls]\x01 Set wall #%d filter to \x05%s\x01", id, arg3); - } - } - } else if(StrEqual(arg1, "edit")) { - int id = GetWallId(client, arg2); - if(id > -1) { - int entity = GetWallEntity(id); - WallBuilder[client].Import(entity); - PrintToChat(client, "\x04[Walls]\x01 Editing wall \x05%d\x01. End with \x05/wall done\x01 or \x04/wall cancel\x01", id); - PrintToChat(client, "\x04[Walls]\x01 Mode: \x05Scale\x01"); - } - } else if(StrEqual(arg1, "edite")) { - int index = GetLookingEntity(client, Filter_ValidHats); //GetClientAimTarget(client, false); - if(index > 0) { - WallBuilder[client].Reset(); - WallBuilder[client].entity = EntIndexToEntRef(index); - WallBuilder[client].canScale = false; - WallBuilder[client].SetMode(MOVE_ORIGIN); - PrintToChat(client, "\x04[Walls]\x01 Editing entity \x05%d\x01. End with \x05/wall done\x01", index); - PrintToChat(client, "\x04[Walls]\x01 Mode: \x05Move & Rotate\x01"); - } else { - ReplyToCommand(client, "\x04[Walls]\x01 Invalid or non existent entity"); - } - } else if(StrEqual(arg1, "copy")) { - int id = GetWallId(client, arg2); - if(id > -1) { - int entity = GetWallEntity(id); - WallBuilder[client].Import(entity, true); - GetCursorLimited(client, 100.0, WallBuilder[client].origin, Filter_IgnorePlayer); - PrintToChat(client, "\x04[Walls]\x01 Editing copy of wall \x05%d\x01. End with \x05/wall build\x01 or \x04/wall cancel\x01", id); - PrintToChat(client, "\x04[Walls]\x01 Mode: \x05Scale\x01"); - } - } else if(StrEqual(arg1, "list")) { - for(int i = 1; i <= createdWalls.Length; i++) { - int entity = GetWallEntity(i); - ReplyToCommand(client, "Wall #%d - EntIndex: %d", i, EntRefToEntIndex(entity)); - } - } - return Plugin_Handled; -} - -int GetWallId(int client, const char[] arg) { - int id; - if(StringToIntEx(arg, id) > 0 && id > 0 && id <= createdWalls.Length) { - int entity = GetWallEntity(id); - if(!IsValidEntity(entity)) { - ReplyToCommand(client, "\x04[Walls]\x01 The wall with specified id no longer exists."); - createdWalls.Erase(id); - return -2; - } - return id; - } else { - ReplyToCommand(client, "\x04[Walls]\x01 Invalid wall id, must be between 0 - %d", createdWalls.Length - 1 ); - return -1; - } -} - -int GetWallEntity(int id) { - if(id <= 0 || id > createdWalls.Length) { - ThrowError("Invalid wall id (%d)", id); - } - return createdWalls.Get(id - 1); -} - -// Handles consent that a person to be hatted by another player -public int HatConsentHandler(Menu menu, MenuAction action, int target, int param2) { - if (action == MenuAction_Select) { - static char info[8]; - menu.GetItem(param2, info, sizeof(info)); - static char str[2][8]; - ExplodeString(info, "|", str, 2, 8, false); - int activator = GetClientOfUserId(StringToInt(str[0])); - int hatAction = StringToInt(str[1]); - if(activator == 0) { - ReplyToCommand(target, "Player has disconnected"); - return 0; - } else if(hatAction == 1) { - EquipHat(activator, target, "player", 0); - } else { - ClientCommand(activator, "play player/orch_hit_csharp_short.wav"); - PrintHintText(activator, "%N refused your request", target); - lastHatRequestTime[activator] = GetTime(); - } - } else if (action == MenuAction_End) - delete menu; - return 0; -} /////////////////////////////////////////////////////////////////////////////////////////////// @@ -978,7 +171,7 @@ void GlowPoint(const float pos[3], float lifetime = 5.0) { Action Timer_Kill(Handle h, int entity) { if(IsValidEntity(entity)) - AcceptEntityInput(entity, "Kill"); + RemoveEntity(entity); return Plugin_Handled; } @@ -1046,7 +239,7 @@ Action Timer_RemountHats(Handle h) { RequestFrame(Frame_Remount, i); } } else if(visibleEntity > 0) { - AcceptEntityInput(visibleEntity, "Kill"); + RemoveEntity(visibleEntity); hatData[i].visibleEntity = INVALID_ENT_REFERENCE; } } @@ -1072,28 +265,59 @@ void Frame_Remount(int i) { // Handles making a prop sleep after a set amount of time (called after hat yeet) -Action Timer_PropSleep(Handle h, int ref) { - if(IsValidEntity(ref)) { - // Check if we should delete thrown hat objects, such as physic props - if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_DeleteThrownHats)) { - // Don't delete if someone has hatted it (including ourself): - for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && IsClientInGame(i) && hatData[i].entity == ref) { - return Plugin_Handled; - } +Action Timer_PropSleep(Handle h, DataPack pack) { + pack.Reset(); + int ref = pack.ReadCell(); + int client = GetClientOfUserId(pack.ReadCell()); + if(client > 0 && IsValidEntity(ref)) { + CheckKill(ref); + if(hatData[client].yeetGroundTimer != null) { + delete hatData[client].yeetGroundTimer; + } + } + return Plugin_Continue; +} +Action Timer_GroundKill(Handle h, DataPack pack) { + pack.Reset(); + int ref = pack.ReadCell(); + int client = GetClientOfUserId(pack.ReadCell()); + if(client > 0 && IsValidEntity(ref)) { + float vel[3]; + GetEntPropVector(ref, Prop_Data, "m_vecVelocity", vel); + if(FloatAbs(vel[2]) < 0.2 || IsNearGround(ref)) { + CheckKill(ref); + hatData[client].yeetGroundTimer = null; + return Plugin_Stop; + } + return Plugin_Continue; + } + return Plugin_Stop; +} + + + +void CheckKill(int ref) { + // Check if we should delete thrown hat objects, such as physic props + if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_DeleteThrownHats)) { + // Don't delete if someone has hatted it (including ourself): + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i) && hatData[i].entity == ref) { + return; } - // Check for prop_ class, only yeetable non-player entity we care as they may be large/collidabl - // Things like weapons aren't a problem as you can't "collide" and get thrown + } + + // Check for prop_ class, only yeetable non-player entity we care as they may be large/collidabl + // Things like weapons aren't a problem as you can't "collide" and get thrown + if(EntRefToEntIndex(ref) > MaxClients) { char classname[64]; GetEntityClassname(ref, classname, sizeof(classname)); if(StrContains(classname, "prop_") > -1) { - AcceptEntityInput(ref, "Kill"); - return Plugin_Handled; + RemoveEntity(ref); + return; } } - AcceptEntityInput(ref, "Sleep"); } - return Plugin_Handled; + AcceptEntityInput(ref, "Sleep"); } Action Timer_PropYeetEnd(Handle h, DataPack pack) { @@ -1240,93 +464,96 @@ void ChooseRandomPosition(float pos[3], int ignoreClient = 0) { public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3], float angles[3], int& weapon, int& subtype, int& cmdnum, int& tickcount, int& seed, int mouse[2]) { float tick = GetGameTime(); - if(cvar_sm_hats_enabled.IntValue == 0 || (GetUserAdmin(client) == INVALID_ADMIN_ID && cvar_sm_hats_enabled.IntValue == 1)) return Plugin_Continue; - int entity = GetHat(client); - int visibleEntity = EntRefToEntIndex(hatData[client].visibleEntity); - ///#HAT PROCESS - if(entity > 0) { - // try to tp hat to its own pos - if(!onLadder[client] && GetEntityMoveType(client) == MOVETYPE_LADDER) { - onLadder[client] = true; - ClearParent(entity); - // Hide hat temporarily in void: - TeleportEntity(entity, EMPTY_ANG, NULL_VECTOR, NULL_VECTOR); - if(visibleEntity > 0) { - hatData[client].visibleEntity = INVALID_ENT_REFERENCE; - AcceptEntityInput(visibleEntity, "Kill"); - } - } else if(onLadder[client] && GetEntityMoveType(client) != MOVETYPE_LADDER) { - onLadder[client] = false; - EquipHat(client, entity); - } - - if(HasFlag(client, HAT_RAINBOW)) { - // Decrement and flip, possibly when rainbowticks - if(hatData[client].rainbowReverse) { - hatData[client].rainbowColor[0] -= cvar_sm_hat_rainbow_speed.FloatValue; - } else { - hatData[client].rainbowColor[0] += cvar_sm_hat_rainbow_speed.FloatValue; - } - - if(hatData[client].rainbowColor[0] > 360.0) { - hatData[client].rainbowReverse = true; - hatData[client].rainbowColor[0] = 360.0; - } else if(hatData[client].rainbowColor[0] < 0.0) { - hatData[client].rainbowReverse = false; - hatData[client].rainbowColor[0] = 0.0; - } - - static int rgb[3]; - HSVToRGBInt(hatData[client].rainbowColor, rgb); - SetEntityRenderColor(entity, rgb[0], rgb[1], rgb[2]); - hatData[client].rainbowTicks = -cvar_sm_hat_rainbow_speed.IntValue; - EquipHat(client, entity); - } - - if(entity <= MaxClients) { - if(!onLadder[entity] && GetEntityMoveType(entity) == MOVETYPE_LADDER) { - onLadder[entity] = true; + if(IsHatsEnabled(client)) { + int entity = GetHat(client); + int visibleEntity = EntRefToEntIndex(hatData[client].visibleEntity); + ///#HAT PROCESS + if(entity > 0) { + // try to tp hat to its own pos + if(!onLadder[client] && GetEntityMoveType(client) == MOVETYPE_LADDER) { + onLadder[client] = true; ClearParent(entity); - } else if(onLadder[entity] && GetEntityMoveType(entity) != MOVETYPE_LADDER) { - onLadder[entity] = false; + // Hide hat temporarily in void: + if(entity >= MaxClients) + + TeleportEntity(entity, EMPTY_ANG, NULL_VECTOR, NULL_VECTOR); + if(visibleEntity > 0) { + hatData[client].visibleEntity = INVALID_ENT_REFERENCE; + RemoveEntity(visibleEntity); + } + } else if(onLadder[client] && GetEntityMoveType(client) != MOVETYPE_LADDER) { + onLadder[client] = false; EquipHat(client, entity); } - } - if(HasFlag(client, HAT_COMMANDABLE | HAT_REVERSED) && tickcount % 200 == 0) { - float pos[3]; - ChooseRandomPosition(pos, client); - L4D2_CommandABot(entity, client, BOT_CMD_MOVE, pos); - } - } - if(buttons & IN_USE && buttons & IN_RELOAD) { - if(entity > 0) { - if(buttons & IN_ZOOM) { - if(buttons & IN_JUMP) hatData[client].offset[2] += 1.0; - if(buttons & IN_DUCK) hatData[client].offset[2] -= 1.0; - if(buttons & IN_FORWARD) hatData[client].offset[0] += 1.0; - if(buttons & IN_BACK) hatData[client].offset[0] -= 1.0; - if(buttons & IN_MOVELEFT) hatData[client].offset[1] += 1.0; - if(buttons & IN_MOVERIGHT) hatData[client].offset[1] -= 1.0; - TeleportEntity(entity, hatData[client].offset, angles, vel); - return Plugin_Handled; - } else if(tick - cmdThrottle[client] > 0.25) { - if(buttons & IN_ATTACK) { - ClientCommand(client, "sm_hat y"); - } else if(buttons & IN_DUCK) { - ClientCommand(client, "sm_hat p"); + + if(HasFlag(client, HAT_RAINBOW)) { + // Decrement and flip, possibly when rainbowticks + if(hatData[client].rainbowReverse) { + hatData[client].rainbowColor[0] -= cvar_sm_hat_rainbow_speed.FloatValue; + } else { + hatData[client].rainbowColor[0] += cvar_sm_hat_rainbow_speed.FloatValue; + } + + if(hatData[client].rainbowColor[0] > 360.0) { + hatData[client].rainbowReverse = true; + hatData[client].rainbowColor[0] = 360.0; + } else if(hatData[client].rainbowColor[0] < 0.0) { + hatData[client].rainbowReverse = false; + hatData[client].rainbowColor[0] = 0.0; + } + + static int rgb[3]; + HSVToRGBInt(hatData[client].rainbowColor, rgb); + SetEntityRenderColor(entity, rgb[0], rgb[1], rgb[2]); + hatData[client].rainbowTicks = -cvar_sm_hat_rainbow_speed.IntValue; + EquipHat(client, entity); + } + + if(entity <= MaxClients) { + if(!onLadder[entity] && GetEntityMoveType(entity) == MOVETYPE_LADDER) { + onLadder[entity] = true; + ClearParent(entity); + } else if(onLadder[entity] && GetEntityMoveType(entity) != MOVETYPE_LADDER) { + onLadder[entity] = false; + EquipHat(client, entity); } } - } else if(tick - cmdThrottle[client] > 0.25 && L4D2_GetPlayerUseAction(client) == L4D2UseAction_None) { - ClientCommand(client, "sm_hat"); + if(HasFlag(client, HAT_COMMANDABLE | HAT_REVERSED) && tickcount % 200 == 0) { + float pos[3]; + ChooseRandomPosition(pos, client); + L4D2_CommandABot(entity, client, BOT_CMD_MOVE, pos); + } + } + if(buttons & IN_USE && buttons & IN_RELOAD) { + if(entity > 0) { + if(buttons & IN_ZOOM) { + if(buttons & IN_JUMP) hatData[client].offset[2] += 1.0; + if(buttons & IN_DUCK) hatData[client].offset[2] -= 1.0; + if(buttons & IN_FORWARD) hatData[client].offset[0] += 1.0; + if(buttons & IN_BACK) hatData[client].offset[0] -= 1.0; + if(buttons & IN_MOVELEFT) hatData[client].offset[1] += 1.0; + if(buttons & IN_MOVERIGHT) hatData[client].offset[1] -= 1.0; + TeleportEntity(entity, hatData[client].offset, angles, vel); + return Plugin_Handled; + } else if(tick - cmdThrottle[client] > 0.25) { + if(buttons & IN_ATTACK) { + ClientCommand(client, "sm_hat y"); + } else if(buttons & IN_DUCK) { + ClientCommand(client, "sm_hat p"); + } + } + } else if(tick - cmdThrottle[client] > 0.25 && L4D2_GetPlayerUseAction(client) == L4D2UseAction_None) { + ClientCommand(client, "sm_hat"); + } + cmdThrottle[client] = tick; + lastAng[client] = angles; + hatData[client].angles = angles; + return Plugin_Handled; } - cmdThrottle[client] = tick; - lastAng[client] = angles; - hatData[client].angles = angles; - return Plugin_Handled; } ///#WALL BUILDER PROCESS - if(WallBuilder[client].IsActive()) { + if(WallBuilder[client].IsActive() && WallBuilder[client].CheckEntity(client)) { if(buttons & IN_USE && buttons & IN_RELOAD) { ClientCommand(client, "sm_wall done"); return Plugin_Handled; @@ -1369,8 +596,11 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3 flags = flags & ~FL_FROZEN; SetEntityFlags(client, flags); } + if(buttons & IN_SPEED) { + WallBuilder[client].ToggleCollision(client, tick); + } - GetCursorLimited2(client, WallBuilder[client].moveDistance, WallBuilder[client].origin, Filter_IgnorePlayerAndWall, buttons & IN_SPEED == 0); + GetCursorLimited2(client, WallBuilder[client].moveDistance, WallBuilder[client].origin, Filter_IgnorePlayerAndWall, WallBuilder[client].hasCollision); } case SCALE: { SetWeaponDelay(client, 0.5); @@ -1514,7 +744,7 @@ int GetLookingEntity(int client, TraceEntityFilter filter) { static float pos[3], ang[3]; GetClientEyePosition(client, pos); GetClientEyeAngles(client, ang); - TR_TraceRayFilter(pos, ang, MASK_SHOT, RayType_Infinite, filter, client); + TR_TraceRayFilter(pos, ang, MASK_SOLID, RayType_Infinite, filter, client); if(TR_DidHit()) { return TR_GetEntityIndex(); } @@ -1557,304 +787,7 @@ bool Filter_ValidHats(int entity, int mask, int data) { //////////////////////////////// -void ClearHats() { - for(int i = 1; i <= MaxClients; i++) { - if(HasHat(i)) { - ClearHat(i, false); - } - if(IsClientConnected(i) && IsClientInGame(i)) SetEntityMoveType(i, MOVETYPE_WALK); - } -} -void ClearHat(int i, bool restore = false) { - int entity = EntRefToEntIndex(hatData[i].entity); - int visibleEntity = EntRefToEntIndex(hatData[i].visibleEntity); - int modifyEntity = HasFlag(i, HAT_REVERSED) ? i : entity; - - if(visibleEntity > 0) { - SDKUnhook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); - AcceptEntityInput(visibleEntity, "Kill"); - } - if(modifyEntity > 0) { - SDKUnhook(modifyEntity, SDKHook_SetTransmit, OnRealTransmit); - ClearParent(modifyEntity); - } else { - return; - } - - int flags = GetEntityFlags(entity) & ~FL_FROZEN; - SetEntityFlags(entity, flags); - // if(HasEntProp(entity, Prop_Send, "m_flModelScale")) - // SetEntPropFloat(entity, Prop_Send, "m_flModelScale", 1.0); - SetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup", hatData[i].collisionGroup); - SetEntProp(modifyEntity, Prop_Send, "m_nSolidType", hatData[i].solidType); - SetEntProp(modifyEntity, Prop_Send, "movetype", hatData[i].moveType); - - hatData[i].entity = INVALID_ENT_REFERENCE; - hatData[i].visibleEntity = INVALID_ENT_REFERENCE; - - if(HasFlag(i, HAT_REVERSED)) { - entity = i; - i = modifyEntity; - } - - if(entity <= MAXPLAYERS) { - AcceptEntityInput(entity, "EnableLedgeHang"); - } - if(restore) { - // If hat is a player, override original position to hat wearer's - if(entity <= MAXPLAYERS && HasEntProp(i, Prop_Send, "m_vecOrigin")) { - GetEntPropVector(i, Prop_Send, "m_vecOrigin", hatData[i].orgPos); - } - // Restore to original position - if(HasFlag(i, HAT_REVERSED)) { - TeleportEntity(i, hatData[i].orgPos, hatData[i].orgAng, NULL_VECTOR); - } else { - TeleportEntity(entity, hatData[i].orgPos, hatData[i].orgAng, NULL_VECTOR); - } - } -} - -bool HasHat(int client) { - return GetHat(client) > 0; -} - -int GetHat(int client) { - if(hatData[client].entity == INVALID_ENT_REFERENCE) return -1; - int index = EntRefToEntIndex(hatData[client].entity); - if(index <= 0) return -1; - if(!IsValidEntity(index)) return -1; - return index; -} - -int GetHatter(int client) { - for(int i = 1; i <= MaxClients; i++) { - if(EntRefToEntIndex(hatData[client].entity) == client) { - return i; - } - } - return -1; -} - -bool CanTarget(int victim) { - static char buf[2]; - noHatVictimCookie.Get(victim, buf, sizeof(buf)); - return StringToInt(buf) == 0; -} - -bool IsHatAllowed(int client) { - char name[32]; - GetEntityClassname(hatData[client].entity, name, sizeof(name)); - // Don't allow non-weapons in saferoom - if(StrEqual(name, "prop_physics")) { - GetEntPropString(hatData[client].entity, Prop_Data, "m_ModelName", name, sizeof(name)); - if(StrContains(name, "gnome") != -1) { - return true; - } - PrintToConsole(client, "Dropping hat: prop_physics (%s)", name); - return false; - } - else if(StrEqual(name, "player") || StrContains(name, "weapon_") > -1 || StrContains(name, "upgrade_") > -1) { - return true; - } - PrintToConsole(client, "Dropping hat: %s", name); - return false; -} - -bool CanHatBePlaced(int client, const float pos[3]) { - if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_NoSaferoomHats)) { - Address nav = L4D_GetNearestNavArea(pos, 200.0); - if(nav != Address_Null) { - int spawnFlags = L4D_GetNavArea_SpawnAttributes(nav) ; - if(spawnFlags & NAV_SPAWN_CHECKPOINT) { - PrintToServer("\"%L\" tried to place hat in saferoom, denied.", client); - PrintToChat(client, "[Hats] Hats are not allowed in saferoom and has been returned."); - return false; - } - } - } - return true; -} - -void SetFlag(int client, hatFlags flag) { - hatData[client].flags |= view_as(flag); -} - -bool HasFlag(int client, hatFlags flag) { - return hatData[client].flags & view_as(flag) != 0; -} - -void EquipHat(int client, int entity, const char[] classname = "", int flags = HAT_NONE) { - if(HasHat(client)) - ClearHat(client, true); - - // Player specific tweaks - int visibleEntity; - if(entity == 0) { - ThrowError("Attempted to equip world (client = %d)", client); - return; - } - - hatData[client].entity = EntIndexToEntRef(entity); - int modifyEntity = HasFlag(client, HAT_REVERSED) ? client : entity; - hatData[client].collisionGroup = GetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup"); - hatData[client].solidType = GetEntProp(modifyEntity, Prop_Send, "m_nSolidType"); - hatData[client].moveType = GetEntProp(modifyEntity, Prop_Send, "movetype"); - - if(client <= MaxClients) SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); - if(entity <= MaxClients) SDKHook(entity, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); - - if(modifyEntity <= MaxClients) { - - AcceptEntityInput(modifyEntity, "DisableLedgeHang"); - } else if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_FakeHat)) { - return; - // char model[64]; - // GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); - // visibleEntity = CreateEntityByName("prop_dynamic"); - // DispatchKeyValue(visibleEntity, "model", model); - // DispatchKeyValue(visibleEntity, "disableshadows", "1"); - // DispatchSpawn(visibleEntity); - // SetEntProp(visibleEntity, Prop_Send, "m_CollisionGroup", 1); - // hatData[client].visibleEntity = EntIndexToEntRef(visibleEntity); - // SDKHook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); - // SDKHook(entity, SDKHook_SetTransmit, OnRealTransmit); - } - SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); - // Temp remove the hat to be yoinked by another player - for(int i = 1; i <= MaxClients; i++) { - if(i != client && EntRefToEntIndex(hatData[i].entity) == entity) { - ClearHat(i); - } - } - - // Called on initial hat - if(classname[0] != '\0') { - if(entity <= MaxClients && !IsFakeClient(entity)) { - PrintToChat(entity, "[Hats] %N has hatted you, type /hat to dismount at any time", client); - } - - // Reset things: - hatData[client].flags = 0; - hatData[client].offset[0] = hatData[client].offset[1] = hatData[client].offset[2] = 0.0; - hatData[client].angles[0] = hatData[client].angles[1] = hatData[client].angles[2] = 0.0; - - if(modifyEntity <= MaxClients) { - if(HasFlag(client, HAT_REVERSED)) { - hatData[client].offset[2] += 7.2; - } else { - hatData[client].offset[2] += 4.2; - } - } else { - float mins[3]; - GetEntPropVector(modifyEntity, Prop_Send, "m_vecMins", mins); - hatData[client].offset[2] += mins[2]; - } - - if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_ReversedHats) && flags & view_as(HAT_REVERSED)) { - SetFlag(client, HAT_REVERSED); - if(StrEqual(classname, "infected") || (entity <= MaxClients && IsFakeClient(entity))) { - SetFlag(client, HAT_COMMANDABLE); - } - PrintToChat(client, "[Hats] Set yourself as %s (%d)'s hat", classname, entity); - if(entity <= MaxClients) { - LogAction(client, entity, "\"%L\" made themselves \"%L\" (%s)'s hat (%d, %d)", client, entity, classname, entity, visibleEntity); - PrintToChat(entity, "[Hats] %N has set themselves as your hat", client); - } - } else { - if(StrEqual(classname, "infected") || StrEqual(classname, "witch")) { - int eflags = GetEntityFlags(entity) | FL_FROZEN; - SetEntityFlags(entity, eflags); - hatData[client].offset[2] = 36.0; - } - if(entity <= MaxClients) - PrintToChat(client, "[Hats] Set %N (%d) as a hat", entity, entity); - else - PrintToChat(client, "[Hats] Set %s (%d) as a hat", classname, entity); - if(entity <= MaxClients) - LogAction(client, entity, "\"%L\" picked up \"%L\" (%s) as a hat (%d, %d)", client, entity, classname, entity, visibleEntity); - else - LogAction(client, -1, "\"%L\" picked up %s as a hat (%d, %d)", client, classname, entity, visibleEntity); - } - hatData[client].scale = -1.0; - - } - AcceptEntityInput(modifyEntity, "DisableMotion"); - - // Get the data (position, angle, movement shit) - - GetEntPropVector(modifyEntity, Prop_Send, "m_vecOrigin", hatData[client].orgPos); - GetEntPropVector(modifyEntity, Prop_Send, "m_angRotation", hatData[client].orgAng); - hatData[client].collisionGroup = GetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup"); - hatData[client].solidType = GetEntProp(modifyEntity, Prop_Send, "m_nSolidType"); - hatData[client].moveType = GetEntProp(modifyEntity, Prop_Send, "movetype"); - - - if(!HasFlag(client, HAT_POCKET)) { - // TeleportEntity(entity, EMPTY_ANG, EMPTY_ANG, NULL_VECTOR); - if(HasFlag(client, HAT_REVERSED)) { - SetParent(client, entity); - if(StrEqual(classname, "infected")) { - SetParentAttachment(modifyEntity, "head", true); - TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); - SetParentAttachment(modifyEntity, "head", true); - } else { - SetParentAttachment(modifyEntity, "eyes", true); - TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); - SetParentAttachment(modifyEntity, "eyes", true); - } - - if(HasFlag(client, HAT_COMMANDABLE)) { - ChooseRandomPosition(hatData[client].offset); - L4D2_CommandABot(entity, client, BOT_CMD_MOVE, hatData[client].offset); - } - } else { - SetParent(entity, client); - SetParentAttachment(modifyEntity, "eyes", true); - TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); - SetParentAttachment(modifyEntity, "eyes", true); - } - - if(visibleEntity > 0) { - SetParent(visibleEntity, client); - SetParentAttachment(visibleEntity, "eyes", true); - hatData[client].offset[2] += 10.0; - TeleportEntity(visibleEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); - SetParentAttachment(visibleEntity, "eyes", true); - #if defined DEBUG_HAT_SHOW_FAKE - L4D2_SetEntityGlow(visibleEntity, L4D2Glow_Constant, 0, 0, color2, false); - #endif - } - - #if defined DEBUG_HAT_SHOW_FAKE - L4D2_SetEntityGlow(modifyEntity, L4D2Glow_Constant, 0, 0, color, false); - #endif - - SetEntProp(modifyEntity, Prop_Send, "m_nSolidType", 0); - SetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup", 1); - SetEntProp(modifyEntity, Prop_Send, "movetype", MOVETYPE_NONE); - } -} - - -void GlowWall(int id, float lifetime = 5.0) { - int ref = GetWallEntity(id); - float pos[3], mins[3], maxs[3], angles[3]; - GetEntPropVector(ref, Prop_Send, "m_angRotation", angles); - GetEntPropVector(ref, Prop_Send, "m_vecOrigin", pos); - GetEntPropVector(ref, Prop_Send, "m_vecMins", mins); - GetEntPropVector(ref, Prop_Send, "m_vecMaxs", maxs); - Effect_DrawBeamBoxRotatableToAll(pos, mins, maxs, angles, g_iLaserIndex, 0, 0, 30, lifetime, 0.4, 0.4, 0, 1.0, WALL_COLOR, 0); -} - -void DeleteWall(int id) { - GlowWall(id); - int ref = GetWallEntity(id); - if(IsValidEntity(ref)) { - AcceptEntityInput(ref, "Kill"); - } - createdWalls.Erase(id - 1); -} stock bool FindGround(const float start[3], float end[3]) {