Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #include <sourcemod>
- #include <sdktools>
- #include <sdkhooks>
- #include <left4dhooks>
- #define MA_FILE_SYSTEM_IMPL
- #include "ma_file_system.inc"
- #define MA_DEBUG 1
- #pragma newdecls required
- #pragma semicolon 1
- // ========================================================================
- // CONFIGURACIÓN AUTOMATIZADA
- // ========================================================================
- ConVar g_hCvarForceFile;
- ConVar g_hCvarPlay;
- ConVar g_hCvarStopPlayer;
- ConVar g_hCvarAutoSwitch;
- // CVARs para grupos de movimiento
- ConVar g_hCvarGroupPM;
- ConVar g_hCvarGroupBile;
- ConVar g_hCvarGroupGL;
- ConVar g_hCvarGroupRun;
- ConVar g_hCvarGroupDual;
- ConVar g_hCvarGroupsEnable;
- // CVAR de depuración
- ConVar g_hCvarDebug;
- ConVar g_hCvarMA_Debug;
- // ========================================================================
- // SISTEMA DE CONTADOR REGRESIVO INDEPENDIENTE
- // ========================================================================
- // Timers individuales por jugador
- Handle g_hClientCountdownTimer[MAXPLAYERS + 1];
- Handle g_hClientMovementTimer[MAXPLAYERS + 1];
- // Información de movimiento por jugador
- float g_fClientMovementEndTime[MAXPLAYERS + 1];
- float g_fClientMovementDuration[MAXPLAYERS + 1];
- int g_iClientMovementGroup[MAXPLAYERS + 1]; // Grupo del movimiento actual
- char g_sClientMovementFileName[MAXPLAYERS + 1][128];
- // Control de mensajes del contador (para evitar spam)
- float g_fLastCountdownUpdate[MAXPLAYERS + 1];
- int g_iLastCountdownSeconds[MAXPLAYERS + 1];
- // Estados del plugin
- bool g_bClientInMovement[MAXPLAYERS + 1];
- char g_sClientMovementFile[MAXPLAYERS + 1][128];
- Handle g_hClientTimer[MAXPLAYERS + 1];
- // Estado de detención manual
- bool g_bManualStop[MAXPLAYERS + 1];
- // Sistema automático de detección
- ArrayList g_hBoostPositions;
- ArrayList g_hMovementFiles;
- StringMap g_hMovementTriggers;
- StringMap g_hFileExistence;
- StringMap g_hMovementDurations;
- ArrayList g_hMovementFileInfos; // <-- Ahora es ArrayList de MovementFileInfo
- // Para eventos de mapa
- char g_sCurrentMap[64];
- char g_sMapFolder[64];
- // Variables globales para triggers
- int g_iTriggers[2048] = {INVALID_ENT_REFERENCE, ...};
- int g_iVisualModels[2048] = {INVALID_ENT_REFERENCE, ...};
- // Control de mensajes de espera
- float g_fLastWaitMessage[MAXPLAYERS + 1][100]; // [client][group] - tiempo del último mensaje
- bool g_bWaitMessageShown[MAXPLAYERS + 1][100]; // [client][group] - si ya se mostró mensaje en esta activación
- // Mapa de mapas oficiales
- StringMap g_hOfficialMaps;
- // Timer para verificación de grupos
- Handle g_hGroupCheckTimer;
- // Para almacenar posiciones de grupos
- StringMap g_hGroupPositions;
- // ========================================================================
- // SISTEMA DE TRACKING POR TRIGGER - MEJORADO CON LÓGICA V15
- // ========================================================================
- #define MAX_TRIGGERS 2048
- #define REFIRE_TIME 3.0
- #define MAX_RETRY_ATTEMPTS 8
- // Tracking de jugadores por trigger
- bool g_bPlayerInTrigger[MAXPLAYERS + 1][MAX_TRIGGERS]; // true = dentro del trigger
- bool g_bPlayerReady[MAXPLAYERS + 1][MAX_TRIGGERS]; // true = cumple condiciones para ese trigger
- float g_fPlayerLastCheck[MAXPLAYERS + 1][MAX_TRIGGERS]; // timestamp de última verificación
- int g_iPlayerRetryCount[MAXPLAYERS + 1][MAX_TRIGGERS]; // contador de reintentos
- // Información de cada trigger
- int g_iTriggerGroup[MAX_TRIGGERS] = {-1, ...}; // Grupo al que pertenece
- char g_sTriggerType[MAX_TRIGGERS][16]; // Tipo de movimiento
- char g_sTriggerFile[MAX_TRIGGERS][128]; // Archivo de movimiento
- int g_iTriggerFileIndex[MAX_TRIGGERS]; // Índice en g_hMovementFiles
- float g_fLastActivation[MAX_TRIGGERS]; // Última vez que se activó este trigger
- // Mapeo grupo → lista de triggers del grupo
- StringMap g_hGroupTriggers; // "1" → ArrayList de índices de triggers
- StringMap g_hGroupTypes; // "1" → ArrayList de tipos únicos en el grupo
- // Capacidad de activación por tipo
- int g_iTriggerCapacity[MAX_TRIGGERS]; // Capacidad de runners para este trigger
- // Sistema de verificación continua
- bool g_bTriggerEnabled[MAX_TRIGGERS]; // Trigger está habilitado
- float g_fNextActivationTime[MAX_TRIGGERS]; // Próximo tiempo de activación permitido
- // Timers de verificación por jugador
- Handle g_hRetryTimer[MAXPLAYERS + 1][MAX_TRIGGERS]; // Timer de reintento por jugador por trigger
- bool g_bWaitingForWeapon[MAXPLAYERS + 1][MAX_TRIGGERS]; // Si el jugador está esperando por arma
- // ========================================================================
- // ENUMS Y ESTRUCTURAS (LO QUE NO ESTÁ EN EL INCLUDE)
- // ========================================================================
- enum MovementMessageType {
- MSG_INDIVIDUAL = 0,
- MSG_DUAL,
- MSG_GROUP,
- MSG_GROUP_DUAL,
- MSG_GROUP_MULTI
- };
- // ========================================================================
- // NUEVAS ESTRUCTURAS PARA GRUPOS MÚLTIPLES
- // ========================================================================
- StringMap g_hGroupsByNumber; // Grupos por número: "1" -> ArrayList de MovementFileInfo
- StringMap g_hFilesByGroupAndType; // Archivos por grupo y tipo: "1_gl" -> MovementFileInfo
- StringMap g_hIndividualFiles; // Archivos individuales (sin grupo)
- // Sistema de activación de grupos
- StringMap g_hActiveGroups; // Grupos que están activos actualmente
- StringMap g_hWaitingGroups; // Jugadores esperando por grupo
- // NUEVO: Control de jugadores por grupo - PARA EVITAR DOBLE ACTIVACIÓN
- StringMap g_hPlayersInGroupActivation; // "grupo_jugador" -> true/false
- // Límites de runners por tipo
- #define MAX_RUNNERS_PM 32
- #define MAX_RUNNERS_BILE 5
- #define MAX_RUNNERS_GL 1
- // ========================================================================
- // NUEVA ESTRUCTURA PARA ANÁLISIS DE ARCHIVOS ACTIVADOS
- // ========================================================================
- enum struct GroupAnalysis {
- bool hasPureRunner;
- int pureRunnerCount;
- int hybridPMCount;
- int hybridBileCount;
- int hybridGLCount;
- int boosterPMCount;
- int boosterBileCount;
- int boosterGLCount;
- ArrayList uniqueFileNames;
- ArrayList uniqueFileDurations;
- }
- // ========================================================================
- // PLUGIN INFO
- // ========================================================================
- public Plugin myinfo =
- {
- name = "Sistema de Movimientos Automático V21q (Nuevo Sistema de Nombres)",
- author = "Tu nombre",
- description = "Detección automática con nuevo sistema de nombres para grupos",
- version = "21.0.2",
- url = ""
- };
- // ========================================================================
- // FORWARDS Y DECLARACIONES DE FUNCIONES PÚBLICAS
- // ========================================================================
- // Funciones públicas (callbacks de SourceMod)
- public void OnGroupCvarChanged(ConVar convar, const char[] oldValue, const char[] newValue);
- public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast);
- public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast);
- public void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast);
- public void Event_WeaponFire(Event event, const char[] name, bool dontBroadcast);
- public Action Command_ForceMovement(int client, int args);
- public Action Command_StopMovement(int client, int args);
- public Action Command_ReloadMovements(int client, int args);
- public Action Command_ForceDual(int client, int args);
- public Action Command_ShowModels(int client, int args);
- public Action Command_HideModels(int client, int args);
- public Action Command_Groups(int client, int args);
- public Action Command_ToggleGroup(int client, int args);
- public Action Command_GlobalStop(int client, const char[] command, int argc);
- public void OnStartTouch(const char[] output, int caller, int activator, float delay);
- public void OnEndTouch(const char[] output, int caller, int activator, float delay);
- public Action Timer_RoundStartLoad(Handle timer);
- public Action Timer_MapStartLoad(Handle timer);
- public Action Timer_StopMovement(Handle timer, DataPack pack);
- public Action Timer_EnableTrigger(Handle timer, any index);
- public Action Timer_EnableGroupTriggers(Handle timer, DataPack pack);
- public Action Timer_StartCountdown(Handle timer, DataPack pack);
- public Action Timer_UpdateCountdown(Handle timer, DataPack pack);
- public Action Timer_ResetGroup(Handle timer, any groupNumber);
- public Action Timer_RecheckWeapon(Handle timer, DataPack pack);
- public Action Timer_RemoveVisualModel(Handle timer, any ref);
- public Action Timer_InitialLoad(Handle timer);
- public Action Timer_DelayedLoad(Handle timer);
- public Action Timer_CheckTriggerPresence(Handle timer, any client);
- public Action Timer_RetryCheck(Handle timer, DataPack pack);
- public Action Command_StopMe(int client, int args);
- // ========================================================================
- // NUEVAS FUNCIONES PARA ANÁLISIS DE ARCHIVOS
- // ========================================================================
- /**
- * Analiza los archivos que se activarán en un grupo
- */
- bool AnalyzeActivatedFiles(int groupNumber, ArrayList files, GroupAnalysis analysis);
- /**
- * Valida si la combinación de archivos es válida
- */
- bool ValidateGroupComposition(const GroupAnalysis analysis, char[] errorMsg, int errorLen);
- /**
- * Genera el nombre para mostrar del grupo
- */
- void GenerateGroupDisplayName(const GroupAnalysis analysis, int playerCount, char[] buffer, int bufferLen);
- /**
- * Ordena archivos para mostrar en mensaje público
- */
- void SortFilesForDisplay(ArrayList files, ArrayList durations, ArrayList sortedFiles, ArrayList sortedDurations);
- /**
- * Formatea el mensaje público con archivos y duraciones
- */
- void FormatPublicMessage(const char[] groupName, int groupNumber, ArrayList files,
- ArrayList durations, char[] buffer, int bufferLen);
- /**
- * Obtiene el nombre base del archivo (sin path ni extensión)
- */
- void GetBaseFileName(const char[] fullPath, char[] baseName, int maxlen);
- /**
- * Obtiene la capacidad de un booster
- */
- int GetBoosterCapacityNew(const char[] boosterType);
- // ========================================================================
- // FUNCIONES MODIFICADAS PARA EL NUEVO SISTEMA
- // ========================================================================
- /**
- * Muestra resumen de activación grupal con nuevo sistema de nombres
- */
- void ShowGroupActivationSummary(int groupNumber, ArrayList players, ArrayList files);
- /**
- * Activa grupo en orden y recopila archivos activados
- */
- void ActivateGroupInOrder(int groupNumber, int runnerLimit, ArrayList &activatedFiles);
- // ========================================================================
- // MACROS PARA DEPURACIÓN
- // ========================================================================
- #define DEBUG_PREFIX "[MOVEMENTS] "
- stock void DebugPrint(const char[] format, any ...)
- {
- if (g_hCvarMA_Debug != null && g_hCvarMA_Debug.BoolValue)
- {
- char buffer[256];
- VFormat(buffer, sizeof(buffer), format, 2);
- PrintToServer("%s%s", DEBUG_PREFIX, buffer);
- }
- }
- // ========================================================================
- // INICIALIZACIÓN DEL PLUGIN
- // ========================================================================
- public void OnPluginStart()
- {
- g_hCvarForceFile = FindConVar("st_mr_force_file");
- g_hCvarPlay = FindConVar("st_mr_play");
- g_hCvarStopPlayer = FindConVar("st_mr_stop_player");
- g_hCvarAutoSwitch = CreateConVar("st_mr_auto_switch", "1",
- "Cambia automáticamente al arma requerida cuando activas un trigger: 1=Activado, 0=Desactivado",
- FCVAR_NOTIFY, true, 0.0, true, 1.0);
- // CVAR de depuración
- g_hCvarDebug = CreateConVar("st_mr_debug", "0",
- "Activa/desactiva los mensajes de depuración: 1=Activado, 0=Desactivado",
- FCVAR_NOTIFY, true, 0.0, true, 1.0);
- g_hCvarMA_Debug = CreateConVar("ma_debug", "0",
- "Activa/desactiva la depuración del plugin de Movimientos Automáticos: 1=Activado, 0=Desactivado",
- FCVAR_NOTIFY, true, 0.0, true, 1.0);
- // CVARs para grupos de movimiento
- g_hCvarGroupsEnable = CreateConVar("st_mr_groups_enable", "1",
- "Activa/desactiva el sistema de grupos de movimiento: 1=Activado, 0=Desactivado",
- FCVAR_NOTIFY, true, 0.0, true, 1.0);
- g_hCvarGroupPM = CreateConVar("st_mr_group_pm", "1",
- "Activa/desactiva el grupo de movimientos P/M: 1=Activado, 0=Desactivado",
- FCVAR_NOTIFY, true, 0.0, true, 1.0);
- g_hCvarGroupBile = CreateConVar("st_mr_group_bile", "1",
- "Activa/desactiva el grupo de movimientos Bile: 1=Activado, 0=Desactivado",
- FCVAR_NOTIFY, true, 0.0, true, 1.0);
- g_hCvarGroupGL = CreateConVar("st_mr_group_gl", "1",
- "Activa/desactiva el grupo de movimientos GL: 1=Activado, 0=Desactivado",
- FCVAR_NOTIFY, true, 0.0, true, 1.0);
- g_hCvarGroupRun = CreateConVar("st_mr_group_run", "1",
- "Activa/desactiva el grupo de movimientos Run: 1=Activado, 0=Desactivado",
- FCVAR_NOTIFY, true, 0.0, true, 1.0);
- g_hCvarGroupDual = CreateConVar("st_mr_group_dual", "1",
- "Activa/desactiva el grupo de movimientos Dual: 1=Activado, 0=Desactivado",
- FCVAR_NOTIFY, true, 0.0, true, 1.0);
- // Hook para detectar cambios en CVARs de grupos
- HookConVarChange(g_hCvarGroupsEnable, OnGroupCvarChanged);
- HookConVarChange(g_hCvarGroupPM, OnGroupCvarChanged);
- HookConVarChange(g_hCvarGroupBile, OnGroupCvarChanged);
- HookConVarChange(g_hCvarGroupGL, OnGroupCvarChanged);
- HookConVarChange(g_hCvarGroupRun, OnGroupCvarChanged);
- HookConVarChange(g_hCvarGroupDual, OnGroupCvarChanged);
- // Comandos básicos necesarios
- RegConsoleCmd("sm_move", Command_ForceMovement);
- RegConsoleCmd("sm_stopmove", Command_StopMovement);
- RegConsoleCmd("sm_reloadmovements", Command_ReloadMovements);
- RegConsoleCmd("sm_forcedual", Command_ForceDual);
- RegConsoleCmd("ma_stopme", Command_StopMe, "Detiene tu movimiento actual");
- // Comandos de visualización (solo admin)
- RegAdminCmd("sm_show_models", Command_ShowModels, ADMFLAG_GENERIC, "Muestra los modelos de cono en los triggers");
- RegAdminCmd("sm_hide_models", Command_HideModels, ADMFLAG_GENERIC, "Esconde los modelos de cono");
- // Comandos para grupos (solo admin)
- RegAdminCmd("sm_groups", Command_Groups, ADMFLAG_GENERIC, "Muestra el estado de los grupos de movimiento");
- RegAdminCmd("sm_togglegroup", Command_ToggleGroup, ADMFLAG_GENERIC, "Activa/desactiva un grupo: sm_togglegroup <pm|bile|gl|run|dual|all>");
- // Hookear comandos del plugin movement reader
- AddCommandListener(Command_GlobalStop, "st_mr_stop");
- AddCommandListener(Command_GlobalStop, "sm_stop_all_movements");
- // Eventos
- HookEvent("round_start", Event_RoundStart);
- HookEvent("round_end", Event_RoundEnd);
- HookEvent("player_spawn", Event_PlayerSpawn);
- HookEvent("weapon_fire", Event_WeaponFire);
- // Inicializar arrays
- g_hBoostPositions = new ArrayList(8);
- g_hMovementFiles = new ArrayList(ByteCountToCells(128));
- g_hMovementTriggers = new StringMap();
- g_hFileExistence = new StringMap();
- g_hMovementDurations = new StringMap();
- g_hMovementFileInfos = new ArrayList(sizeof(MovementFileInfo));
- // Inicializar nuevo sistema de grupos
- InitializeGroupSystem();
- // Inicializar mapa de mapas oficiales USANDO EL INCLUDE
- g_hOfficialMaps = new StringMap();
- MA_InitializeOfficialMaps(g_hOfficialMaps);
- // Inicializar sistema de tracking mejorado
- InitializeTrackingSystem();
- // Inicializar sistema de refire_time mejorado
- for (int i = 0; i < MAX_TRIGGERS; i++)
- {
- g_iTriggerGroup[i] = -1;
- g_sTriggerType[i][0] = '\0';
- g_sTriggerFile[i][0] = '\0';
- g_iTriggerFileIndex[i] = -1;
- g_iTriggerCapacity[i] = 0;
- g_fLastActivation[i] = 0.0;
- g_bTriggerEnabled[i] = true;
- g_fNextActivationTime[i] = 0.0;
- }
- // Inicializar sistema de contador regresivo
- for (int i = 0; i <= MaxClients; i++)
- {
- g_hClientCountdownTimer[i] = null;
- g_fClientMovementEndTime[i] = 0.0;
- ClearPlayerTriggerStates(i);
- for (int j = 0; j < MAX_TRIGGERS; j++)
- {
- g_hRetryTimer[i][j] = null;
- g_bWaitingForWeapon[i][j] = false;
- }
- }
- // Inicializar timer de grupos
- g_hGroupCheckTimer = null;
- DebugPrint("Sistema Automático V21.0.2 cargado - Nuevo Sistema de Nombres");
- DebugPrint("CVAR st_mr_auto_switch: %d", g_hCvarAutoSwitch.IntValue);
- DebugPrint("Sistema de grupos: %s", g_hCvarGroupsEnable.BoolValue ? "ACTIVADO" : "DESACTIVADO");
- DebugPrint("Activación cíclica: cada %.1f segundos", REFIRE_TIME);
- DebugPrint("Sistema de reintentos: %d intentos máximos", MAX_RETRY_ATTEMPTS);
- // Carga inicial con timer para asegurar que el plugin esté completamente inicializado
- CreateTimer(2.0, Timer_UpdateTriggers, _, TIMER_REPEAT);
- CreateTimer(1.0, Timer_InitialLoad, _, TIMER_FLAG_NO_MAPCHANGE);
- }
- // ========================================================================
- // FUNCIONES DE INICIALIZACIÓN Y LIMPIEZA
- // ========================================================================
- void InitializeGroupSystem()
- {
- g_hGroupsByNumber = new StringMap();
- g_hFilesByGroupAndType = new StringMap();
- g_hIndividualFiles = new StringMap();
- g_hActiveGroups = new StringMap();
- g_hWaitingGroups = new StringMap();
- g_hGroupPositions = new StringMap();
- }
- void ClearGroupSystem()
- {
- // Limpiar grupos por número
- if (g_hGroupsByNumber != null)
- {
- StringMapSnapshot snapshot = g_hGroupsByNumber.Snapshot();
- char key[16];
- for (int i = 0; i < snapshot.Length; i++)
- {
- snapshot.GetKey(i, key, sizeof(key));
- ArrayList list;
- if (g_hGroupsByNumber.GetValue(key, list))
- {
- delete list;
- }
- }
- delete snapshot;
- g_hGroupsByNumber.Clear();
- }
- if (g_hFilesByGroupAndType != null) g_hFilesByGroupAndType.Clear();
- if (g_hIndividualFiles != null) g_hIndividualFiles.Clear();
- if (g_hActiveGroups != null) g_hActiveGroups.Clear();
- if (g_hWaitingGroups != null) g_hWaitingGroups.Clear();
- if (g_hGroupPositions != null) g_hGroupPositions.Clear();
- }
- void InitializeTrackingSystem()
- {
- g_hGroupTriggers = new StringMap();
- g_hGroupTypes = new StringMap();
- g_hPlayersInGroupActivation = new StringMap();
- // Inicializar estados de tracking (más simple que antes)
- for (int client = 0; client <= MaxClients; client++)
- {
- ClearPlayerTriggerStates(client);
- }
- }
- void ClearTrackingSystem()
- {
- // Limpiar arrays de tracking
- for (int i = 0; i <= MaxClients; i++)
- {
- ClearPlayerTriggerStates(i);
- }
- // Limpiar información de triggers
- for (int i = 0; i < MAX_TRIGGERS; i++)
- {
- g_iTriggerGroup[i] = -1;
- g_sTriggerType[i][0] = '\0';
- g_sTriggerFile[i][0] = '\0';
- g_iTriggerFileIndex[i] = -1;
- g_iTriggerCapacity[i] = 0;
- g_fLastActivation[i] = 0.0;
- g_bTriggerEnabled[i] = true;
- g_fNextActivationTime[i] = 0.0;
- }
- // Limpiar mapa de control de jugadores por grupo
- if (g_hPlayersInGroupActivation != null)
- {
- g_hPlayersInGroupActivation.Clear();
- }
- // Limpiar mapas de grupos
- if (g_hGroupTriggers != null)
- {
- StringMapSnapshot snapshot = g_hGroupTriggers.Snapshot();
- char key[16];
- for (int i = 0; i < snapshot.Length; i++)
- {
- snapshot.GetKey(i, key, sizeof(key));
- ArrayList list;
- if (g_hGroupTriggers.GetValue(key, list))
- {
- delete list;
- }
- }
- delete snapshot;
- g_hGroupTriggers.Clear();
- }
- if (g_hGroupTypes != null)
- {
- StringMapSnapshot snapshot = g_hGroupTypes.Snapshot();
- char key[16];
- for (int i = 0; i < snapshot.Length; i++)
- {
- snapshot.GetKey(i, key, sizeof(key));
- ArrayList list;
- if (g_hGroupTypes.GetValue(key, list))
- {
- delete list;
- }
- }
- delete snapshot;
- g_hGroupTypes.Clear();
- }
- }
- void ClearPlayerTriggerStates(int client)
- {
- for (int i = 0; i < MAX_TRIGGERS; i++)
- {
- g_bPlayerInTrigger[client][i] = false;
- g_bPlayerReady[client][i] = false;
- g_fPlayerLastCheck[client][i] = 0.0;
- g_iPlayerRetryCount[client][i] = 0;
- g_bWaitingForWeapon[client][i] = false;
- // Cancelar timers de reintento
- if (g_hRetryTimer[client][i] != null)
- {
- KillTimer(g_hRetryTimer[client][i]);
- g_hRetryTimer[client][i] = null;
- }
- }
- // Limpiar control de mensajes de espera
- for (int group = 0; group < 100; group++)
- {
- g_fLastWaitMessage[client][group] = 0.0;
- g_bWaitMessageShown[client][group] = false;
- }
- }
- // ========================================================================
- // FUNCIONES PARA DETENER MOVIMIENTO DE CLIENTE
- // ========================================================================
- void ClientStopMovement(int client)
- {
- // Redirigir a la nueva función mejorada
- ForceStopClientMovement(client, false);
- }
- // ========================================================================
- // DETERMINAR CARPETA DEL MAPA - USANDO EL INCLUDE
- // ========================================================================
- void DetermineMapFolder()
- {
- char mapBuffer[64];
- strcopy(mapBuffer, sizeof(mapBuffer), g_sCurrentMap);
- DebugPrint("DetermineMapFolder - Mapa original: %s", g_sCurrentMap);
- // Usar la función del include
- MA_DetermineMapFolder(g_sCurrentMap, g_sMapFolder, sizeof(g_sMapFolder), g_hOfficialMaps);
- DebugPrint("DetermineMapFolder - Carpeta final: %s", g_sMapFolder);
- }
- // ========================================================================
- // TIMER DE CARGA INICIAL
- // ========================================================================
- public Action Timer_InitialLoad(Handle timer)
- {
- DebugPrint("Timer_InitialLoad - Verificando estado del plugin...");
- // Si ya estamos en un mapa, cargar los movimientos
- if (strlen(g_sCurrentMap) > 0 && strlen(g_sMapFolder) > 0)
- {
- DebugPrint("Mapa ya cargado: %s - Forzando recarga...", g_sCurrentMap);
- LoadMovementFilesAutomatically();
- }
- return Plugin_Stop;
- }
- // ========================================================================
- // ON MAP START - CORREGIDA CON TIMERS
- // ========================================================================
- public void OnMapStart()
- {
- GetCurrentMap(g_sCurrentMap, sizeof(g_sCurrentMap));
- DetermineMapFolder();
- DebugPrint("===== INICIO DE MAPA =====");
- DebugPrint("Mapa: %s", g_sCurrentMap);
- DebugPrint("Carpeta: %s", g_sMapFolder);
- // Limpiar estados
- for (int i = 1; i <= MaxClients; i++)
- {
- ClientStopMovement(i);
- }
- // Precache de modelos - CON FIRMEZA
- char models[][PLATFORM_MAX_PATH] = {
- "models/props_fortifications/orange_cone001_reference.mdl",
- "models/props/cs_assault/money.mdl",
- "models/props_junk/glassbottle01a.mdl",
- "models/props_junk/cardboard_box001a.mdl",
- "models/props/cs_militia/silo_01.mdl"
- };
- for (int i = 0; i < sizeof(models); i++)
- {
- if (!IsModelPrecached(models[i]))
- {
- PrecacheModel(models[i]);
- DebugPrint("Modelo precargado: %s", models[i]);
- }
- }
- if (g_hGroupCheckTimer != null)
- {
- KillTimer(g_hGroupCheckTimer);
- g_hGroupCheckTimer = null;
- }
- // CAMBIO CRÍTICO: Usar Timer como en V18 original
- CreateTimer(2.0, Timer_MapStartLoad, _, TIMER_FLAG_NO_MAPCHANGE);
- CreateTimer(3.0, Timer_CheckTriggers);
- }
- public Action Timer_MapStartLoad(Handle timer)
- {
- DebugPrint("Timer_MapStartLoad - Cargando movimientos...");
- LoadMovementFilesAutomatically();
- return Plugin_Stop;
- }
- // ========================================================================
- // EVENTOS DE RONDA - CORREGIDOS
- // ========================================================================
- public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast)
- {
- DebugPrint("Ronda iniciada - Recargando triggers...");
- for (int i = 1; i <= MaxClients; i++)
- {
- ClientStopMovement(i);
- }
- if (g_hActiveGroups != null)
- g_hActiveGroups.Clear();
- if (g_hWaitingGroups != null)
- g_hWaitingGroups.Clear();
- if (g_hGroupPositions != null)
- g_hGroupPositions.Clear();
- // Limpiar control de jugadores por grupo
- if (g_hPlayersInGroupActivation != null)
- {
- g_hPlayersInGroupActivation.Clear();
- }
- // Resetear tiempos de activación de todos los triggers
- for (int i = 0; i < MAX_TRIGGERS; i++)
- {
- g_fLastActivation[i] = 0.0;
- g_bTriggerEnabled[i] = true;
- g_fNextActivationTime[i] = 0.0;
- }
- // CAMBIO CRÍTICO: Usar Timer igual que en V18
- CreateTimer(2.0, Timer_RoundStartLoad, _, TIMER_FLAG_NO_MAPCHANGE);
- CreateTimer(3.0, Timer_CheckTriggers);
- }
- public Action Timer_RoundStartLoad(Handle timer)
- {
- DebugPrint("Timer_RoundStartLoad - Recargando triggers...");
- LoadMovementFilesAutomatically();
- return Plugin_Stop;
- }
- public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast)
- {
- DebugPrint("Ronda terminada - Deteniendo movimientos");
- for (int i = 1; i <= MaxClients; i++)
- {
- ClientStopMovement(i);
- }
- if (g_hActiveGroups != null)
- g_hActiveGroups.Clear();
- if (g_hWaitingGroups != null)
- g_hWaitingGroups.Clear();
- // Limpiar control de jugadores por grupo
- if (g_hPlayersInGroupActivation != null)
- {
- g_hPlayersInGroupActivation.Clear();
- }
- }
- // ========================================================================
- // EVENTOS Y TIMERS
- // ========================================================================
- public void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast)
- {
- int client = GetClientOfUserId(event.GetInt("userid"));
- if (client > 0 && client <= MaxClients && IsClientInGame(client))
- {
- // **IMPORTANTE: NUNCA detener movimiento por spawn**
- // Esto incluye spawns por idle
- // El movimiento solo debe detenerse cuando termine su duración
- // o por comando explícito
- DebugPrint("Event_PlayerSpawn - %N ha reaparecido (posible idle), manteniendo movimiento si está activo", client);
- // Verificar si el jugador tenía un movimiento activo antes del idle
- // y restaurarlo si es necesario
- if (g_bClientInMovement[client] && strlen(g_sClientMovementFile[client]) > 0)
- {
- DebugPrint("Event_PlayerSpawn - %N mantiene movimiento activo: %s",
- client, g_sClientMovementFile[client]);
- }
- }
- }
- public Action Timer_StopMovement(Handle timer, DataPack pack)
- {
- pack.Reset();
- int userid = pack.ReadCell();
- char movementFile[128];
- pack.ReadString(movementFile, sizeof(movementFile));
- int groupNumber = pack.ReadCell();
- delete pack;
- int client = GetClientOfUserId(userid);
- if (client > 0 && IsClientInGame(client))
- {
- if (g_bClientInMovement[client] && StrEqual(g_sClientMovementFile[client], movementFile))
- {
- // NUEVO: Mostrar mensaje de completado
- MovementFileInfo info;
- bool foundInfo = MA_GetMovementFileInfo(movementFile, g_hMovementFileInfos, info);
- // IMPORTANTE: Liberar al jugador del grupo INMEDIATAMENTE
- if (groupNumber > 0)
- {
- DebugPrint("Timer_StopMovement - %N completó movimiento del grupo %d - liberando del grupo", client, groupNumber);
- MarkPlayerInGroupActivation(groupNumber, client, false);
- // Mostrar mensaje al jugador
- PrintToChat(client, "[\x04MA Grupo (%d)\x01] ¡Completado! (%.1fs)", groupNumber, g_fClientMovementDuration[client]);
- // Verificar si el grupo está vacío
- if (IsGroupEmpty(groupNumber))
- {
- char groupKey[16];
- IntToString(groupNumber, groupKey, sizeof(groupKey));
- if (g_hActiveGroups != null)
- {
- g_hActiveGroups.Remove(groupKey);
- DebugPrint("Timer_StopMovement - Grupo %d completamente liberado (todos terminaron)", groupNumber);
- }
- }
- }
- else
- {
- PrintToChat(client, "[\x04MA Movimiento\x01] ¡Completado! (%.1fs)", g_fClientMovementDuration[client]);
- DebugPrint("Timer_StopMovement - %N completó movimiento individual", client);
- }
- // Detener movimiento
- ForceStopClientMovement(client, false);
- }
- }
- // Limpiar timer
- g_hClientMovementTimer[client] = null;
- return Plugin_Stop;
- }
- bool IsGroupEmpty(int groupNumber)
- {
- // Verificar si algún jugador todavía está marcado en este grupo
- for (int client = 1; client <= MaxClients; client++)
- {
- if (IsValidSurvivor(client) && IsPlayerAlreadyInGroupActivation(groupNumber, client))
- {
- return false;
- }
- }
- return true;
- }
- public void Event_WeaponFire(Event event, const char[] name, bool dontBroadcast)
- {
- // Esta función puede usarse para detectar cuándo un jugador dispara
- // y actualizar su estado de "arma lista"
- }
- // ========================================================================
- // EVENTO DE CLIENTE DESCONECTADO
- // ========================================================================
- public void OnClientDisconnect(int client)
- {
- // Solo procesar si es un cliente real (no bot)
- if (IsClientInGame(client) && !IsFakeClient(client))
- {
- DebugPrint("OnClientDisconnect - %N se desconectó", client);
- // Detener movimiento solo para desconexiones reales
- ClientStopMovement(client);
- RemoveFromGroupWaitingLists(client);
- ClearPlayerTriggerStates(client);
- // Remover del control de grupos
- for (int groupNumber = 1; groupNumber <= 100; groupNumber++)
- {
- MarkPlayerInGroupActivation(groupNumber, client, false);
- }
- }
- }
- void RemoveFromGroupWaitingLists(int client)
- {
- if (g_hWaitingGroups == null)
- return;
- int userid = GetClientUserId(client);
- StringMapSnapshot snapshot = g_hWaitingGroups.Snapshot();
- for (int i = 0; i < snapshot.Length; i++)
- {
- char groupKey[16];
- snapshot.GetKey(i, groupKey, sizeof(groupKey));
- ArrayList waitingList;
- if (g_hWaitingGroups.GetValue(groupKey, waitingList))
- {
- int index = waitingList.FindValue(userid);
- if (index != -1)
- {
- waitingList.Erase(index);
- if (waitingList.Length == 0)
- {
- delete waitingList;
- g_hWaitingGroups.Remove(groupKey);
- }
- }
- }
- }
- delete snapshot;
- }
- // ========================================================================
- // FUNCIONES AUXILIARES ADICIONALES
- // ========================================================================
- void GetFileNameFromPath(const char[] path, char[] filename, int maxlen)
- {
- int pos = FindCharInString(path, '/', true);
- if (pos == -1)
- pos = FindCharInString(path, '\\', true);
- if (pos != -1)
- strcopy(filename, maxlen, path[pos + 1]);
- else
- strcopy(filename, maxlen, path);
- }
- bool IsValidSurvivor(int client)
- {
- return (client > 0 && client <= MaxClients && IsClientInGame(client) && GetClientTeam(client) == 2);
- }
- bool IsValidEntRef(int ref)
- {
- return (ref != INVALID_ENT_REFERENCE && EntRefToEntIndex(ref) != INVALID_ENT_REFERENCE);
- }
- // ========================================================================
- // NUEVAS FUNCIONES PARA CONTROL DE JUGADORES ÚNICOS POR GRUPO
- // ========================================================================
- bool IsPlayerAlreadyInGroupActivation(int groupNumber, int client)
- {
- char key[32];
- Format(key, sizeof(key), "%d_%d", groupNumber, GetClientUserId(client));
- bool value;
- if (g_hPlayersInGroupActivation.GetValue(key, value))
- {
- return value;
- }
- return false;
- }
- void MarkPlayerInGroupActivation(int groupNumber, int client, bool inGroup)
- {
- char key[32];
- Format(key, sizeof(key), "%d_%d", groupNumber, GetClientUserId(client));
- g_hPlayersInGroupActivation.SetValue(key, inGroup);
- DebugPrint("MarkPlayerInGroupActivation - Jugador %N %s en activación del grupo %d",
- client, inGroup ? "agregado" : "removido", groupNumber);
- }
- void ClearGroupActivationPlayers(int groupNumber)
- {
- if (g_hPlayersInGroupActivation == null) return;
- StringMapSnapshot snapshot = g_hPlayersInGroupActivation.Snapshot();
- char key[32];
- for (int i = 0; i < snapshot.Length; i++)
- {
- snapshot.GetKey(i, key, sizeof(key));
- // Verificar si la clave pertenece a este grupo
- char parts[2][16];
- if (ExplodeString(key, "_", parts, 2, 16) == 2)
- {
- int keyGroup = StringToInt(parts[0]);
- if (keyGroup == groupNumber)
- {
- g_hPlayersInGroupActivation.Remove(key);
- }
- }
- }
- delete snapshot;
- }
- // ========================================================================
- // SISTEMA DE GRUPOS DE MOVIMIENTO
- // ========================================================================
- public void OnGroupCvarChanged(ConVar convar, const char[] oldValue, const char[] newValue)
- {
- // Solo recargar si hay datos cargados
- if (g_hMovementFiles.Length > 0)
- {
- DebugPrint("OnGroupCvarChanged - CVAR cambiada - Recargando triggers...");
- RecreateTriggers();
- // Mostrar estado actualizado
- PrintGroupStatus();
- }
- }
- void PrintGroupStatus()
- {
- DebugPrint("=== Estado de Grupos ===");
- DebugPrint("Sistema: %s", g_hCvarGroupsEnable.BoolValue ? "ACTIVADO" : "DESACTIVADO");
- DebugPrint("P/M: %s", g_hCvarGroupPM.BoolValue ? "ACTIVADO" : "DESACTIVADO");
- DebugPrint("Bile: %s", g_hCvarGroupBile.BoolValue ? "ACTIVADO" : "DESACTIVADO");
- DebugPrint("GL: %s", g_hCvarGroupGL.BoolValue ? "ACTIVADO" : "DESACTIVADO");
- DebugPrint("Run: %s", g_hCvarGroupRun.BoolValue ? "ACTIVADO" : "DESACTIVADO");
- DebugPrint("Dual: %s", g_hCvarGroupDual.BoolValue ? "ACTIVADO" : "DESACTIVADO");
- }
- bool IsGroupEnabledByType(MovementType movType)
- {
- if (!g_hCvarGroupsEnable.BoolValue)
- return false;
- if (movType == MOVEMENT_PM) return g_hCvarGroupPM.BoolValue;
- else if (movType == MOVEMENT_BILE) return g_hCvarGroupBile.BoolValue;
- else if (movType == MOVEMENT_GL) return g_hCvarGroupGL.BoolValue;
- else if (movType == MOVEMENT_RUN) return g_hCvarGroupRun.BoolValue;
- else if (movType == MOVEMENT_DUAL) return g_hCvarGroupDual.BoolValue;
- else if (movType == MOVEMENT_RUNPM) return g_hCvarGroupPM.BoolValue && g_hCvarGroupRun.BoolValue;
- else if (movType == MOVEMENT_RUNBILE) return g_hCvarGroupBile.BoolValue && g_hCvarGroupRun.BoolValue;
- else if (movType == MOVEMENT_RUNGL) return g_hCvarGroupGL.BoolValue && g_hCvarGroupRun.BoolValue;
- return false;
- }
- // ========================================================================
- // SISTEMA DE MODELOS VISUALES
- // ========================================================================
- void CreateVisualModels()
- {
- RemoveVisualModels();
- for (int i = 0; i < g_hBoostPositions.Length; i++)
- {
- CreateVisualModelForTrigger(i, 0.0);
- }
- DebugPrint("CreateVisualModels - Modelos visuales creados para %d triggers", g_hBoostPositions.Length);
- }
- void CreateVisualModelForTrigger(int index, float duration)
- {
- if (index < 0 || index >= g_hBoostPositions.Length)
- return;
- int data[8];
- g_hBoostPositions.GetArray(index, data, sizeof(data));
- float pos[3];
- pos[0] = view_as<float>(data[0]);
- pos[1] = view_as<float>(data[1]);
- pos[2] = view_as<float>(data[2]);
- char file[128];
- g_hMovementFiles.GetString(data[6], file, sizeof(file));
- MovementType movType = MA_GetMovementType(file, g_hMovementFileInfos);
- int visualModel = CreateEntityByName("prop_dynamic");
- if (visualModel == -1)
- return;
- // Verificar modelo
- char model[] = "models/props_fortifications/orange_cone001_reference.mdl";
- if (!IsModelPrecached(model))
- {
- PrecacheModel(model);
- }
- SetEntityModel(visualModel, model);
- float adjustedPos[3];
- adjustedPos[0] = pos[0];
- adjustedPos[1] = pos[1];
- adjustedPos[2] = pos[2] + 16.0;
- DispatchKeyValue(visualModel, "targetname", "movement_visual_model");
- DispatchKeyValue(visualModel, "disableshadows", "1");
- DispatchKeyValue(visualModel, "solid", "0");
- char color[32];
- if (movType == MOVEMENT_PM)
- Format(color, sizeof(color), "255 0 0");
- else if (movType == MOVEMENT_BILE)
- Format(color, sizeof(color), "0 255 0");
- else if (movType == MOVEMENT_GL)
- Format(color, sizeof(color), "0 0 255");
- else if (movType == MOVEMENT_RUN)
- Format(color, sizeof(color), "255 255 0");
- else if (movType == MOVEMENT_RUNPM)
- Format(color, sizeof(color), "255 128 0");
- else if (movType == MOVEMENT_RUNBILE)
- Format(color, sizeof(color), "128 255 0");
- else if (movType == MOVEMENT_RUNGL)
- Format(color, sizeof(color), "0 128 255");
- else
- Format(color, sizeof(color), "255 255 255");
- DispatchKeyValue(visualModel, "rendercolor", color);
- DispatchKeyValue(visualModel, "renderamt", "200");
- DispatchSpawn(visualModel);
- TeleportEntity(visualModel, adjustedPos, NULL_VECTOR, NULL_VECTOR);
- SetEntityRenderMode(visualModel, RENDER_NORMAL);
- g_iVisualModels[index] = EntIndexToEntRef(visualModel);
- if (duration > 0)
- CreateTimer(duration, Timer_RemoveVisualModel, EntIndexToEntRef(visualModel));
- }
- public Action Timer_RemoveVisualModel(Handle timer, any ref)
- {
- int model = EntRefToEntIndex(ref);
- if (model != INVALID_ENT_REFERENCE)
- RemoveEntity(model);
- return Plugin_Stop;
- }
- void RemoveVisualModels()
- {
- for (int i = 0; i < MAX_TRIGGERS; i++)
- {
- if (IsValidEntRef(g_iVisualModels[i]))
- {
- int model = EntRefToEntIndex(g_iVisualModels[i]);
- if (model != INVALID_ENT_REFERENCE)
- RemoveEntity(model);
- g_iVisualModels[i] = INVALID_ENT_REFERENCE;
- }
- }
- DebugPrint("RemoveVisualModels - Todos los modelos visuales eliminados");
- }
- // ========================================================================
- // SISTEMA DE CONTADOR REGRESIVO
- // ========================================================================
- void StartMovementCountdown(int client, float duration, int groupNumber = 0, const char[] movementFile = "")
- {
- // Cancelar timer anterior si existe
- if (g_hClientCountdownTimer[client] != null)
- {
- KillTimer(g_hClientCountdownTimer[client]);
- g_hClientCountdownTimer[client] = null;
- }
- if (duration <= 0) return;
- // Guardar información del movimiento
- g_fClientMovementDuration[client] = duration;
- g_fClientMovementEndTime[client] = GetGameTime() + duration;
- g_iClientMovementGroup[client] = groupNumber;
- if (strlen(movementFile) > 0)
- {
- strcopy(g_sClientMovementFileName[client], sizeof(g_sClientMovementFileName[]), movementFile);
- }
- DebugPrint("StartMovementCountdown - %N - Iniciando contador: %.1fs, Grupo: %d, Archivo: %s",
- client, duration, groupNumber, movementFile);
- // Crear DataPack con el userid del cliente
- DataPack countdownPack = new DataPack();
- countdownPack.WriteCell(GetClientUserId(client));
- // Iniciar contador regresivo
- g_hClientCountdownTimer[client] = CreateTimer(0.1, Timer_UpdateCountdown, countdownPack, TIMER_REPEAT);
- // Mostrar primer mensaje inmediatamente
- DisplayCountdownMessage(client, duration, true);
- }
- public Action Timer_StartCountdown(Handle timer, DataPack pack)
- {
- pack.Reset();
- int userid = pack.ReadCell();
- float countdownDuration = pack.ReadFloat();
- float interval = pack.ReadFloat();
- delete pack;
- int client = GetClientOfUserId(userid);
- if (!IsValidSurvivor(client) || !g_bClientInMovement[client])
- return Plugin_Stop;
- // Iniciar timer recurrente para el contador
- DataPack countdownPack = new DataPack();
- countdownPack.WriteCell(userid);
- countdownPack.WriteFloat(countdownDuration);
- countdownPack.WriteFloat(interval);
- g_hClientCountdownTimer[client] = CreateTimer(interval, Timer_UpdateCountdown, countdownPack, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE);
- // Mostrar primer mensaje inmediatamente
- DisplayCountdownMessage(client, countdownDuration);
- return Plugin_Stop;
- }
- public Action Timer_UpdateCountdown(Handle timer, DataPack pack)
- {
- pack.Reset();
- int userid = pack.ReadCell();
- int client = GetClientOfUserId(userid);
- // Verificar si el cliente es válido
- if (!IsValidSurvivor(client) || !g_bClientInMovement[client])
- {
- // Limpiar timer
- if (g_hClientCountdownTimer[client] != null)
- {
- KillTimer(g_hClientCountdownTimer[client]);
- g_hClientCountdownTimer[client] = null;
- }
- // Limpiar el DataPack
- delete pack;
- return Plugin_Stop;
- }
- // Calcular tiempo restante
- float currentTime = GetGameTime();
- float remainingTime = g_fClientMovementEndTime[client] - currentTime;
- // Si el tiempo se acabó
- if (remainingTime <= 0)
- {
- // Completado - no mostrar nada, el Timer_StopMovement mostrará el mensaje
- PrintHintText(client, " ");
- // Detener este timer
- if (g_hClientCountdownTimer[client] != null)
- {
- KillTimer(g_hClientCountdownTimer[client]);
- g_hClientCountdownTimer[client] = null;
- }
- // Limpiar el DataPack
- delete pack;
- return Plugin_Stop;
- }
- // Actualizar mensaje del contador
- DisplayCountdownMessage(client, remainingTime, false);
- // IMPORTANTE: NO borrar el DataPack aquí porque el timer se repite
- return Plugin_Continue;
- }
- void DisplayCountdownMessage(int client, float remaining, bool forceShow = false)
- {
- // Solo mostrar si es un número entero o si es muy poco tiempo
- int seconds = RoundToFloor(remaining);
- // Control de spam: solo mostrar si cambió el segundo o si es forzado
- float currentTime = GetGameTime();
- bool shouldShow = forceShow ||
- (seconds != g_iLastCountdownSeconds[client] && seconds <= 10) ||
- (currentTime - g_fLastCountdownUpdate[client] > 1.0);
- if (!shouldShow) return;
- // Actualizar control
- g_iLastCountdownSeconds[client] = seconds;
- g_fLastCountdownUpdate[client] = currentTime;
- // Solo mostrar mensajes para 10 segundos o menos
- if (seconds > 10 && !forceShow) return;
- char message[64];
- if (remaining < 0.5)
- {
- Format(message, sizeof(message), "¡Completado!");
- }
- else if (seconds == 1 && remaining >= 0.5)
- {
- Format(message, sizeof(message), "Finaliza en: \x041\x01 segundo");
- }
- else
- {
- switch(seconds)
- {
- case 10: Format(message, sizeof(message), "Finaliza en: \x0410\x01 segundos");
- case 9: Format(message, sizeof(message), "Finaliza en: \x049\x01 segundos");
- case 8: Format(message, sizeof(message), "Finaliza en: \x048\x01 segundos");
- case 7: Format(message, sizeof(message), "Finaliza en: \x047\x01 segundos");
- case 6: Format(message, sizeof(message), "Finaliza en: \x046\x01 segundos");
- case 5: Format(message, sizeof(message), "Finaliza en: \x045\x01 segundos");
- case 4: Format(message, sizeof(message), "Finaliza en: \x044\x01 segundos");
- case 3: Format(message, sizeof(message), "Finaliza en: \x043\x01 segundos");
- case 2: Format(message, sizeof(message), "Finaliza en: \x042\x01 segundos");
- default:
- {
- if (remaining < 1.0) {
- Format(message, sizeof(message), "¡Completado!");
- } else {
- Format(message, sizeof(message), "Finaliza en: \x04%.1f\x01 segundos", remaining);
- }
- }
- }
- }
- PrintHintText(client, message);
- }
- // ========================================================================
- // CARGA AUTOMÁTICA DE ARCHIVOS DE MOVIMIENTO - USANDO EL INCLUDE
- // ========================================================================
- void LoadMovementFilesAutomatically()
- {
- DebugPrint("===== INICIANDO CARGA AUTOMÁTICA DE MOVIMIENTOS =====");
- // Limpiar datos existentes
- g_hBoostPositions.Clear();
- g_hMovementFiles.Clear();
- g_hMovementTriggers.Clear();
- g_hFileExistence.Clear();
- g_hMovementDurations.Clear();
- RemoveVisualModels();
- // Limpiar estructuras de parsing
- g_hMovementFileInfos.Clear();
- // Limpiar mapas de archivos
- ClearGroupSystem();
- // Limpiar sistema de tracking
- ClearTrackingSystem();
- // Imprimir información de depuración
- DebugPrint("Mapa actual: %s", g_sCurrentMap);
- DebugPrint("Carpeta de mapa: %s", g_sMapFolder);
- char movementsDir[PLATFORM_MAX_PATH];
- BuildPath(Path_SM, movementsDir, sizeof(movementsDir), "plugins/disabled/movements/%s", g_sMapFolder);
- DebugPrint("Buscando directorio: %s", movementsDir);
- if (!DirExists(movementsDir))
- {
- DebugPrint("ERROR: No existe directorio: %s", movementsDir);
- // Intentar crear directorio alternativo
- char altDir[PLATFORM_MAX_PATH];
- BuildPath(Path_SM, altDir, sizeof(altDir), "plugins/disabled/movements");
- DebugPrint("Buscando directorio raíz: %s", altDir);
- if (DirExists(altDir))
- {
- // Listar subdirectorios disponibles
- DirectoryListing rootDir = OpenDirectory(altDir);
- if (rootDir != null)
- {
- char subDir[64];
- FileType type;
- DebugPrint("Subdirectorios disponibles en movements:");
- while (rootDir.GetNext(subDir, sizeof(subDir), type))
- {
- if (type == FileType_Directory)
- {
- DebugPrint(" - %s", subDir);
- }
- }
- delete rootDir;
- }
- }
- // Crear el directorio si no existe
- if (CreateDirectory(movementsDir, 511))
- {
- DebugPrint("Directorio creado: %s", movementsDir);
- }
- else
- {
- DebugPrint("ERROR: No se pudo crear directorio");
- }
- return;
- }
- // USAR LA FUNCIÓN DEL INCLUDE PARA CARGAR ARCHIVOS
- int loadedCount = MA_LoadMovementFilesAutomatically(
- g_sMapFolder,
- g_hBoostPositions,
- g_hMovementFiles,
- g_hMovementTriggers,
- g_hFileExistence,
- g_hMovementDurations,
- g_hMovementFileInfos,
- g_hGroupsByNumber,
- g_hFilesByGroupAndType,
- g_hIndividualFiles
- );
- // Mostrar resumen detallado
- DebugPrint("===== RESUMEN DE CARGA =====");
- DebugPrint("Archivos cargados exitosamente: %d", loadedCount);
- DebugPrint("Posiciones totales: %d", g_hBoostPositions.Length);
- DebugPrint("Archivos en lista: %d", g_hMovementFiles.Length);
- if (loadedCount > 0)
- {
- // Mostrar estructura de archivos
- DebugMovementFilesStructure();
- // Mostrar estado de grupos
- PrintGroupStatus();
- // Crear triggers con filtro de grupos
- RecreateTriggers();
- CreateTimer(0.5, Timer_CheckTriggers);
- // Verificar que los triggers se crearon
- int triggerCount = 0;
- for (int i = 0; i < MAX_TRIGGERS; i++)
- {
- if (IsValidEntRef(g_iTriggers[i]))
- triggerCount++;
- }
- DebugPrint("Triggers creados: %d", triggerCount);
- if (triggerCount == 0)
- {
- DebugPrint("ADVERTENCIA: No se crearon triggers!");
- DebugPrint("Verificar: 1) Modelos precargados 2) Posiciones válidas 3) Grupos activos");
- }
- }
- else
- {
- DebugPrint("ERROR: No se cargaron archivos de movimiento");
- DebugPrint("Verificar que los archivos .txt estén en: %s", movementsDir);
- }
- }
- // ========================================================================
- // RECREAR TRIGGERS - FUNCIÓN COMPLETA Y CORREGIDA
- // ========================================================================
- void RecreateTriggers()
- {
- DebugPrint("===== RECREANDO TRIGGERS =====");
- DebugPrint("Total de posiciones: %d", g_hBoostPositions.Length);
- if (g_hBoostPositions.Length == 0)
- {
- DebugPrint("ADVERTENCIA: No hay posiciones para crear triggers!");
- return;
- }
- // Limpiar sistema de tracking
- ClearTrackingSystem();
- // Destruir triggers existentes
- int destroyed = 0;
- for (int i = 0; i < MAX_TRIGGERS; i++)
- {
- if (IsValidEntRef(g_iTriggers[i]))
- {
- int entity = EntRefToEntIndex(g_iTriggers[i]);
- if (entity != INVALID_ENT_REFERENCE)
- {
- AcceptEntityInput(entity, "Kill");
- destroyed++;
- }
- g_iTriggers[i] = INVALID_ENT_REFERENCE;
- }
- }
- if (destroyed > 0)
- {
- DebugPrint("Triggers anteriores eliminados: %d", destroyed);
- }
- // Limpiar posiciones de grupos
- g_hGroupPositions.Clear();
- // Crear nuevos triggers respetando los grupos activos
- int created = 0;
- int skipped = 0;
- for (int i = 0; i < g_hBoostPositions.Length; i++)
- {
- int data[8];
- g_hBoostPositions.GetArray(i, data, sizeof(data));
- char file[128];
- g_hMovementFiles.GetString(data[6], file, sizeof(file));
- // Verificar que el archivo no esté vacío
- if (strlen(file) == 0)
- {
- DebugPrint("ADVERTENCIA: Archivo vacío en posición %d", i);
- skipped++;
- continue;
- }
- // Obtener tipo de movimiento USANDO EL INCLUDE
- MovementType movType = MA_GetMovementType(file, g_hMovementFileInfos);
- // Verificar si el grupo está activo
- if (!IsGroupEnabledByType(movType))
- {
- skipped++;
- continue;
- }
- float boostPos[3];
- boostPos[0] = view_as<float>(data[0]);
- boostPos[1] = view_as<float>(data[1]);
- boostPos[2] = view_as<float>(data[2]);
- DebugPrint("Creando trigger %d en (%.1f, %.1f, %.1f) para archivo: %s",
- i, boostPos[0], boostPos[1], boostPos[2], file);
- // Registrar información del trigger
- g_iTriggerGroup[i] = -1;
- g_iTriggerFileIndex[i] = data[6];
- g_fLastActivation[i] = 0.0;
- g_bTriggerEnabled[i] = true;
- g_fNextActivationTime[i] = 0.0;
- // Obtener información del archivo para determinar grupo USANDO EL INCLUDE
- MovementFileInfo fileInfo;
- if (MA_GetMovementFileInfo(file, g_hMovementFileInfos, fileInfo))
- {
- g_iTriggerGroup[i] = fileInfo.iNumber;
- strcopy(g_sTriggerType[i], sizeof(g_sTriggerType[]), fileInfo.sTypeString);
- strcopy(g_sTriggerFile[i], sizeof(g_sTriggerFile[]), file);
- g_iTriggerCapacity[i] = MA_GetBoosterCapacity(fileInfo.sTypeString);
- DebugPrint("Trigger %d -> Grupo: %d, Tipo: %s, Capacidad: %d",
- i, fileInfo.iNumber, fileInfo.sTypeString, g_iTriggerCapacity[i]);
- // Si es parte de un grupo, agregar al sistema de tracking
- if (fileInfo.iNumber > 0)
- {
- char groupKey[16];
- IntToString(fileInfo.iNumber, groupKey, sizeof(groupKey));
- // Agregar trigger al grupo
- ArrayList groupTriggers = GetGroupTriggers(fileInfo.iNumber);
- groupTriggers.Push(i);
- // Agregar tipo al grupo si no existe
- ArrayList groupTypes = GetGroupTypes(fileInfo.iNumber);
- bool typeExists = false;
- for (int j = 0; j < groupTypes.Length; j++)
- {
- char existingType[16];
- groupTypes.GetString(j, existingType, sizeof(existingType));
- if (StrEqual(existingType, fileInfo.sTypeString))
- {
- typeExists = true;
- break;
- }
- }
- if (!typeExists)
- {
- groupTypes.PushString(fileInfo.sTypeString);
- }
- }
- }
- else
- {
- DebugPrint("ADVERTENCIA: No se pudo obtener info del archivo %s", file);
- }
- float vMaxs[3] = {2.5, 2.5, 5.0};
- float vMins[3] = {-2.5, -2.5, 0.0};
- if (CreateMovementTrigger(i, boostPos, vMaxs, vMins, file, data[7], true))
- {
- created++;
- }
- else
- {
- DebugPrint("ERROR: No se pudo crear trigger para posición %d", i);
- skipped++;
- }
- }
- DebugPrint("===== RESUMEN DE TRIGGERS =====");
- DebugPrint("Creados: %d, Omitidos: %d, Total: %d", created, skipped, g_hBoostPositions.Length);
- if (created == 0)
- {
- DebugPrint("ERROR CRÍTICO: No se creó ningún trigger!");
- DebugPrint("Posibles causas:");
- DebugPrint("1. Todos los grupos están desactivados");
- DebugPrint("2. Las posiciones son inválidas");
- DebugPrint("3. Error al crear entidades");
- }
- }
- // ========================================================================
- // CREAR TRIGGER DE MOVIMIENTO - FUNCIÓN CRÍTICA CORREGIDA
- // ========================================================================
- bool CreateMovementTrigger(int index, float vPos[3], float vMaxs[3], float vMins[3], const char[] movementFile, int triggerType, bool autoload)
- {
- // Verificar posición válida
- if (vPos[0] == 0.0 && vPos[1] == 0.0 && vPos[2] == 0.0)
- {
- DebugPrint("ERROR: Posición inválida (0,0,0) para trigger %d", index);
- return false;
- }
- // Verificar si el sistema de grupos está activo
- if (g_hCvarGroupsEnable.BoolValue)
- {
- // Obtener tipo de movimiento USANDO EL INCLUDE
- MovementType movType = MA_GetMovementType(movementFile, g_hMovementFileInfos);
- // Verificar si el grupo específico está activo
- if (!IsGroupEnabledByType(movType))
- {
- DebugPrint("Grupo no activo para archivo: %s", movementFile);
- return false;
- }
- }
- int trigger = CreateEntityByName("trigger_multiple");
- if (trigger == -1 || !IsValidEntity(trigger))
- {
- DebugPrint("ERROR: No se pudo crear entidad trigger_multiple");
- return false;
- }
- DebugPrint("Entidad creada con índice: %d", trigger);
- DispatchKeyValue(trigger, "StartDisabled", "0"); // Siempre habilitado al inicio
- DispatchKeyValue(trigger, "spawnflags", "1");
- // MODELO CORRECTO: Usar el modelo de cono naranja
- char model[] = "models/props_fortifications/orange_cone001_reference.mdl";
- // Verificar que el modelo esté precargado
- if (!IsModelPrecached(model))
- {
- PrecacheModel(model);
- DebugPrint("Modelo precargado: %s", model);
- }
- SetEntityModel(trigger, model);
- float adjustedPos[3];
- adjustedPos[0] = vPos[0];
- adjustedPos[1] = vPos[1];
- adjustedPos[2] = vPos[2] + 16.0;
- TeleportEntity(trigger, adjustedPos, NULL_VECTOR, NULL_VECTOR);
- DispatchSpawn(trigger);
- SetEntPropVector(trigger, Prop_Send, "m_vecMaxs", vMaxs);
- SetEntPropVector(trigger, Prop_Send, "m_vecMins", vMins);
- SetEntProp(trigger, Prop_Send, "m_nSolidType", 2);
- SetEntityRenderMode(trigger, RENDER_NORMAL);
- SetEntityRenderColor(trigger, 255, 255, 255, 50);
- char targetname[128];
- Format(targetname, sizeof(targetname), "movement_trigger_%s_%d", movementFile, triggerType);
- DispatchKeyValue(trigger, "targetname", targetname);
- DebugPrint("Trigger configurado: %s en (%.1f, %.1f, %.1f)",
- targetname, adjustedPos[0], adjustedPos[1], adjustedPos[2]);
- if (autoload)
- {
- AcceptEntityInput(trigger, "Enable");
- DebugPrint("Trigger habilitado: %d", trigger);
- }
- HookSingleEntityOutput(trigger, "OnStartTouch", OnStartTouch);
- HookSingleEntityOutput(trigger, "OnEndTouch", OnEndTouch);
- g_iTriggers[index] = EntIndexToEntRef(trigger);
- return true;
- }
- // ========================================================================
- // FUNCIONES DE ACTIVACIÓN RÁPIDA
- // ========================================================================
- void TriggerMovement(int client, int triggerIndex)
- {
- DebugPrint("TriggerMovement - Activando trigger %d para %N", triggerIndex, client);
- // PASO 1: Verificar condiciones individuales INMEDIATAMENTE
- bool conditionsMet = CheckPlayerConditions(client, triggerIndex);
- DebugPrint("TriggerMovement - Condiciones cumplidas: %d", conditionsMet);
- if (!conditionsMet)
- {
- char type[16];
- strcopy(type, sizeof(type), g_sTriggerType[triggerIndex]);
- DebugPrint("TriggerMovement - Tipo de trigger: %s", type);
- // Solo mostrar mensaje si NO es runner puro
- if (!StrEqual(type, "run"))
- {
- MovementType movType = GetMovementTypeFromString(type);
- if (movType == MOVEMENT_UNKNOWN)
- {
- DebugPrint("TriggerMovement - Tipo de movimiento desconocido");
- PrintToChat(client, "[\x04Trigger\x01] Error: Tipo de movimiento desconocido: %s", type);
- }
- else
- {
- char weaponName[32];
- GetWeaponNameForType(movType, weaponName, sizeof(weaponName));
- // Verificar si tiene el arma pero no está lista
- if (HasRequiredWeapon(client, type))
- {
- DebugPrint("TriggerMovement - %N tiene el arma %s pero NO está lista", client, weaponName);
- PrintToChat(client, "[\x04Trigger\x01] Tu %s no está lista. Espera a que termine la animación.", weaponName);
- }
- else
- {
- DebugPrint("TriggerMovement - %N NO tiene el arma %s", client, weaponName);
- PrintToChat(client, "[\x04Trigger\x01] Necesitas %s para este movimiento", weaponName);
- }
- }
- }
- // Marcar como NO listo - el timer cíclico seguirá verificando
- MarkPlayerReady(client, triggerIndex, false);
- return;
- }
- // PASO 2: Marcar jugador como listo (CONDICIONES CUMPLIDAS)
- MarkPlayerReady(client, triggerIndex, true);
- DebugPrint("TriggerMovement - %N marcado como LISTO para trigger %d", client, triggerIndex);
- // PASO 3: Verificar si es parte de un grupo
- int groupNumber = g_iTriggerGroup[triggerIndex];
- if (groupNumber > 0)
- {
- DebugPrint("TriggerMovement - Trigger pertenece al grupo %d", groupNumber);
- // NUEVA VALIDACIÓN: Verificar si el jugador ya está en activación de este grupo
- if (IsPlayerAlreadyInGroupActivation(groupNumber, client))
- {
- DebugPrint("TriggerMovement - %N ya está en activación del grupo %d - ignorando", client, groupNumber);
- PrintToChat(client, "[\x04MA Error\x01] Ya estás participando en este grupo. No puedes activar múltiples roles.");
- MarkPlayerReady(client, triggerIndex, false);
- return;
- }
- // Es parte de un grupo - determinar si es Dual o Grupo
- ArrayList groupTypes = GetGroupTypes(groupNumber);
- bool isDual = (groupTypes != null && groupTypes.Length == 2);
- char boosterType[16] = "";
- if (isDual)
- {
- // Obtener tipo de booster para Dual
- for (int i = 0; i < groupTypes.Length; i++)
- {
- char typeGroup[16];
- groupTypes.GetString(i, typeGroup, sizeof(typeGroup));
- if (StrEqual(typeGroup, "pm") || StrEqual(typeGroup, "bile") || StrEqual(typeGroup, "gl"))
- {
- if (StrEqual(typeGroup, "pm")) boosterType = "PM";
- else if (StrEqual(typeGroup, "bile")) boosterType = "Bile";
- else if (StrEqual(typeGroup, "gl")) boosterType = "GL";
- break;
- }
- }
- }
- // Mostrar mensaje SOLO UNA VEZ
- if (!g_bWaitMessageShown[client][groupNumber])
- {
- if (isDual && strlen(boosterType) > 0)
- {
- PrintToChat(client, "[\x04MA Dual-%s (%d)\x01] Listo. Esperando otros participantes...",
- boosterType, groupNumber);
- }
- else
- {
- PrintToChat(client, "[\x04MA Grupo (%d)\x01] Listo. Esperando otros participantes...", groupNumber);
- }
- g_bWaitMessageShown[client][groupNumber] = true;
- }
- // Verificar si el grupo está completo INMEDIATAMENTE
- AttemptGroupActivation(groupNumber, triggerIndex, client);
- }
- else
- {
- DebugPrint("TriggerMovement - Trigger NO es parte de grupo - ejecutando SOLO");
- // NO es parte de un grupo - ejecutar individualmente INMEDIATAMENTE
- ExecuteSoloMovementFromTrigger(client, triggerIndex);
- }
- }
- void ExecuteSoloMovementFromTrigger(int client, int triggerIndex)
- {
- DebugPrint("ExecuteSoloMovementFromTrigger - Ejecutando movimiento SOLO para %N desde trigger %d",
- client, triggerIndex);
- // Obtener archivo de movimiento del trigger
- char movementFile[128];
- GetMovementFileForTrigger(triggerIndex, movementFile, sizeof(movementFile));
- DebugPrint("ExecuteSoloMovementFromTrigger - Archivo: %s", movementFile);
- // Ejecutar movimiento individual
- ExecutePlayerMovementImmediate(client, movementFile, MSG_INDIVIDUAL);
- // Aplicar refire_time después de la activación exitosa
- ApplyRefireTime(triggerIndex);
- }
- // ========================================================================
- // NUEVA FUNCIÓN: EJECUCIÓN INMEDIATA DE MOVIMIENTO
- // ========================================================================
- void ExecutePlayerMovementImmediate(int client, const char[] movementFile, MovementMessageType msgType = MSG_INDIVIDUAL, int groupNumber = 0)
- {
- g_bManualStop[client] = false;
- DebugPrint("ExecutePlayerMovementImmediate - Intentando ejecutar movimiento %s para %N (msgType: %d, group: %d)",
- movementFile, client, msgType, groupNumber);
- if (!IsValidSurvivor(client) || g_bClientInMovement[client])
- {
- DebugPrint("ExecutePlayerMovementImmediate - Cliente no válido o ya en movimiento");
- return;
- }
- char filePath[PLATFORM_MAX_PATH];
- BuildPath(Path_SM, filePath, sizeof(filePath), "plugins/disabled/movements/%s.txt", movementFile);
- if (!FileExists(filePath))
- {
- DebugPrint("ExecutePlayerMovementImmediate - Archivo NO existe");
- PrintToChat(client, "[\x04Error\x01] Archivo no encontrado: %s", movementFile);
- return;
- }
- DebugPrint("ExecutePlayerMovementImmediate - Archivo EXISTE");
- // Buscar información del archivo
- MovementFileInfo info;
- bool found = MA_GetMovementFileInfo(movementFile, g_hMovementFileInfos, info);
- if (!found)
- {
- DebugPrint("ExecutePlayerMovementImmediate - No se pudo obtener info del archivo");
- PrintToChat(client, "[\x04Error\x01] No se pudo obtener información del archivo");
- return;
- }
- g_bClientInMovement[client] = true;
- // Guardar información del movimiento
- strcopy(g_sClientMovementFile[client], sizeof(g_sClientMovementFile[]), movementFile);
- strcopy(g_sClientMovementFileName[client], sizeof(g_sClientMovementFileName[]), movementFile);
- g_iClientMovementGroup[client] = groupNumber;
- if (groupNumber > 0)
- {
- DebugPrint("ExecutePlayerMovementImmediate - %N iniciando movimiento del grupo %d", client, groupNumber);
- MarkPlayerInGroupActivation(groupNumber, client, true);
- }
- // PASO 1: Forzar el archivo de movimiento
- if (g_hCvarForceFile != null)
- {
- g_hCvarForceFile.SetString(movementFile);
- DebugPrint("ExecutePlayerMovementImmediate - st_mr_force_file: %s", movementFile);
- }
- // PASO 2: Inmediatamente después, reproducir para el cliente específico
- if (g_hCvarPlay != null)
- {
- char clientIndex[12];
- IntToString(client, clientIndex, sizeof(clientIndex));
- g_hCvarPlay.SetString(clientIndex);
- DebugPrint("ExecutePlayerMovementImmediate - st_mr_play: %s", clientIndex);
- }
- // Obtener duración REAL del archivo específico
- float duration = 10.0;
- if (g_hMovementDurations != null)
- {
- g_hMovementDurations.GetValue(movementFile, duration);
- }
- // Guardar duración
- g_fClientMovementDuration[client] = duration;
- // Mostrar mensajes según el tipo
- if (msgType == MSG_INDIVIDUAL)
- {
- char author[32];
- strcopy(author, sizeof(author), info.sAuthor);
- if (strlen(author) == 0) strcopy(author, sizeof(author), "Desconocido");
- // Preparar nombre de visualización
- char displayName[64];
- MA_GetDisplayFileName(movementFile, g_hMovementFileInfos, displayName, sizeof(displayName));
- // Obtener nombre base sin autor
- char baseName[64];
- GetBaseFileNameWithoutAuthor(displayName, baseName, sizeof(baseName));
- char typeName[16];
- if (info.movType == MOVEMENT_RUN) typeName = "Solo";
- else if (info.movType == MOVEMENT_PM) typeName = "PM";
- else if (info.movType == MOVEMENT_BILE) typeName = "Bile";
- else if (info.movType == MOVEMENT_GL) typeName = "GL";
- else if (info.movType == MOVEMENT_RUNPM) typeName = "RunPM";
- else if (info.movType == MOVEMENT_RUNBILE) typeName = "RunBile";
- else if (info.movType == MOVEMENT_RUNGL) typeName = "RunGL";
- else typeName = "Solo";
- // Mostrar autor si existe
- if (!StrEqual(author, "Desconocido"))
- {
- PrintToChatAll("[\x04Movimiento\x01] Creado por '\x04%s\x01'", author);
- }
- PrintToChatAll("[\x04M. %s\x01] %s(%.1fs)", typeName, baseName, duration);
- PrintToChat(client, "[\x04M. %s\x01] %N", typeName, client);
- }
- if (msgType == MSG_GROUP_MULTI)
- {
- // Para grupos múltiples, NO mostrar mensajes individuales aquí
- DebugPrint("ExecutePlayerMovementImmediate - Movimiento múltiple para %N en grupo %d", client, groupNumber);
- }
- DebugPrint("ExecutePlayerMovementImmediate - %N ejecutando movimiento: %s (%.1fs)", client, movementFile, duration);
- // Iniciar contador regresivo INDEPENDIENTE
- StartMovementCountdown(client, duration, groupNumber, movementFile);
- // Timer para detener automáticamente
- DataPack pack = new DataPack();
- pack.WriteCell(GetClientUserId(client));
- pack.WriteString(movementFile);
- pack.WriteCell(groupNumber);
- // Guardar el timer para poder cancelarlo si es necesario
- g_hClientMovementTimer[client] = CreateTimer(duration + 0.5, Timer_StopMovement, pack, TIMER_FLAG_NO_MAPCHANGE);
- DebugPrint("ExecutePlayerMovementImmediate - MOVIMIENTO INICIADO EXITOSAMENTE para %N", client);
- }
- public void GetMovementFileForTrigger(int triggerIndex, char[] buffer, int size)
- {
- if (triggerIndex >= 0 && triggerIndex < g_hBoostPositions.Length)
- {
- int data[8];
- g_hBoostPositions.GetArray(triggerIndex, data, sizeof(data));
- g_hMovementFiles.GetString(data[6], buffer, size);
- }
- else
- {
- buffer[0] = '\0';
- }
- }
- bool CheckPlayerHasRequiredWeapon(int client, MovementType movType)
- {
- if (!IsValidSurvivor(client))
- return false;
- MovementType baseType = GetBaseMovementType(movType);
- // Búsqueda rápida en todas las ranuras
- for (int slot = 0; slot <= 4; slot++)
- {
- int weapon = GetPlayerWeaponSlot(client, slot);
- if (weapon > 0 && IsValidEntity(weapon))
- {
- char classname[32];
- GetEntityClassname(weapon, classname, sizeof(classname));
- if (baseType == MOVEMENT_PM && (StrEqual(classname, "weapon_pipe_bomb") || StrEqual(classname, "weapon_molotov")))
- return true;
- else if (baseType == MOVEMENT_BILE && StrEqual(classname, "weapon_vomitjar"))
- return true;
- else if (baseType == MOVEMENT_GL && StrEqual(classname, "weapon_grenade_launcher"))
- return true;
- }
- }
- // Para RUN y DUAL, siempre retorna true
- if (baseType == MOVEMENT_RUN || baseType == MOVEMENT_DUAL)
- return true;
- return false;
- }
- // ========================================================================
- // FUNCIONES DEL SISTEMA DE GRUPOS
- // ========================================================================
- ArrayList GetGroupTriggers(int groupNumber)
- {
- char groupKey[16];
- IntToString(groupNumber, groupKey, sizeof(groupKey));
- ArrayList groupTriggers;
- if (!g_hGroupTriggers.GetValue(groupKey, groupTriggers))
- {
- groupTriggers = new ArrayList();
- g_hGroupTriggers.SetValue(groupKey, groupTriggers);
- }
- return groupTriggers;
- }
- ArrayList GetGroupTypes(int groupNumber)
- {
- char groupKey[16];
- IntToString(groupNumber, groupKey, sizeof(groupKey));
- ArrayList groupTypes;
- if (!g_hGroupTypes.GetValue(groupKey, groupTypes))
- {
- groupTypes = new ArrayList(ByteCountToCells(16));
- g_hGroupTypes.SetValue(groupKey, groupTypes);
- }
- return groupTypes;
- }
- // ========================================================================
- // FUNCIONES DE VERIFICACIÓN DE CONDICIONES (MEJORADAS)
- // ========================================================================
- bool CheckPlayerConditions(int client, int triggerIndex)
- {
- DebugPrint("CheckPlayerConditions - Verificando condiciones para el jugador %N en trigger %d", client, triggerIndex);
- // 1. Verificar si es superviviente válido
- if (!IsValidSurvivor(client))
- {
- DebugPrint("CheckPlayerConditions - %N no es survivor válido", client);
- return false;
- }
- // 2. Verificar si está vivo, no incapacitado, no colgado
- if (!IsPlayerAlive(client) || IsPlayerIncapacitated(client) || IsPlayerHanging(client))
- {
- DebugPrint("CheckPlayerConditions - %N no está en condiciones válidas (muerto/incapacitado/colgado)", client);
- PrintToChat(client, "[\x04Error\x01] Debes estar vivo, no incapacitado y no colgado");
- return false;
- }
- // 3. LÓGICA MEJORADA: Verificar salud con mensajes anti-spam
- int permanentHealth = GetClientHealth(client);
- int tempHealth = GetPlayerTempHealth(client);
- int totalHealth = permanentHealth + tempHealth;
- // DEBUG EXTRA: Mostrar valores exactos
- DebugPrint("CheckPlayerConditions - %N - Base: %d, Temporal: %d, Total: %d",
- client, permanentHealth, tempHealth, totalHealth);
- bool rule1 = (permanentHealth >= 40);
- bool rule2 = (!rule1 && totalHealth >= 45);
- DebugPrint("CheckPlayerConditions - %N - Regla 1 (Base>=40): %s, Regla 2 (Total>=45): %s",
- client, rule1 ? "SÍ" : "NO", rule2 ? "SÍ" : "NO");
- if (rule1)
- {
- DebugPrint("CheckPlayerConditions - %N tiene 40+ de salud base (%d) - VÁLIDO por Regla 1",
- client, permanentHealth);
- }
- else if (rule2)
- {
- DebugPrint("CheckPlayerConditions - %N tiene <40 base pero 45+ total (%d) - VÁLIDO por Regla 2",
- client, totalHealth);
- }
- else
- {
- DebugPrint("CheckPlayerConditions - %N NO cumple con las reglas de salud", client);
- DebugPrint("CheckPlayerConditions - Base: %d (necesita >=40) | Total: %d (necesita >=45 si base<40)",
- permanentHealth, totalHealth);
- // SISTEMA ANTI-SPAM: Solo mostrar mensaje cada 2 segundos
- static float fLastHealthMessage[MAXPLAYERS + 1];
- float fCurrentTime = GetGameTime();
- if (fCurrentTime - fLastHealthMessage[client] > 2.0) // Mensaje cada 2 segundos máximo
- {
- // Mostrar mensaje específico MEJORADO
- if (permanentHealth >= 30 && permanentHealth < 40)
- {
- int neededTemp = 45 - permanentHealth;
- PrintToChat(client, "[\x04Error\x01] Salud base: \x04%d\x01 (necesita 40+). \x04O añade %d de salud temporal\x01 para llegar a 45 total",
- permanentHealth, neededTemp);
- }
- else
- {
- // Mensaje MÁS CLARO con colores
- if (tempHealth > 0)
- {
- PrintToChat(client, "[\x04Error\x01] Necesitas \x0440+ de salud base\x01 O \x0445+ de salud total\x01. Tienes: \x04%d base\x01 + \x04%d temporal\x01 = \x04%d total",
- permanentHealth, tempHealth, totalHealth);
- }
- else
- {
- PrintToChat(client, "[\x04Error\x01] Necesitas \x0440+ de salud base\x01 O \x0445+ de salud total\x01. Tienes: \x04%d base\x01 (sin salud temporal)",
- permanentHealth);
- }
- }
- fLastHealthMessage[client] = fCurrentTime;
- }
- return false;
- }
- // 4. Verificar si el jugador ya está en movimiento
- if (g_bClientInMovement[client])
- {
- DebugPrint("CheckPlayerConditions - %N ya está en movimiento", client);
- return false;
- }
- // 5. Verificar si el trigger tiene tipo válido
- if (strlen(g_sTriggerType[triggerIndex]) == 0)
- {
- DebugPrint("CheckPlayerConditions - Trigger %d no tiene tipo válido", triggerIndex);
- return false;
- }
- char type[16];
- strcopy(type, sizeof(type), g_sTriggerType[triggerIndex]);
- DebugPrint("CheckPlayerConditions - Tipo de trigger: %s", type);
- // 6. Verificar condiciones específicas por tipo
- // RUN (puro): siempre válido
- if (StrEqual(type, "run"))
- {
- DebugPrint("CheckPlayerConditions - Trigger es de tipo RUN, condiciones cumplidas");
- return true;
- }
- // HÍBRIDOS (runpm, runbile, rungl): necesitan arma correspondiente
- if (StrContains(type, "runpm") != -1)
- {
- DebugPrint("CheckPlayerConditions - Trigger es de tipo RUNPM, verificando arma");
- return CheckWeaponReady(client, "pm");
- }
- if (StrContains(type, "runbile") != -1)
- {
- DebugPrint("CheckPlayerConditions - Trigger es de tipo RUNBILE, verificando arma");
- return CheckWeaponReady(client, "bile");
- }
- if (StrContains(type, "rungl") != -1)
- {
- DebugPrint("CheckPlayerConditions - Trigger es de tipo RUNGL, verificando arma");
- return CheckWeaponReady(client, "gl");
- }
- // BOOSTERS PUROS (pm, bile, gl): necesitan arma correspondiente
- if (StrEqual(type, "pm") || StrEqual(type, "bile") || StrEqual(type, "gl"))
- {
- DebugPrint("CheckPlayerConditions - Trigger es de tipo %s, verificando arma", type);
- return CheckWeaponReady(client, type);
- }
- DebugPrint("CheckPlayerConditions - Tipo de trigger desconocido: %s", type);
- return false;
- }
- // ========================================================================
- // FUNCIÓN AUXILIAR PARA VERIFICAR SI EL JUGADOR CUMPLE LAS REGLAS DE SALUD
- // ========================================================================
- bool CheckPlayerHealthRules(int client, char[] errorMsg = "", int errorLen = 0)
- {
- int permanentHealth = GetClientHealth(client);
- int tempHealth = GetPlayerTempHealth(client);
- int totalHealth = permanentHealth + tempHealth;
- // REGLAS MEJORADAS:
- // 1. Salud base ≥ 40 -> VÁLIDO
- // 2. Si salud base < 40, entonces salud total ≥ 45 -> VÁLIDO
- // 3. Si no cumple ninguna -> NO VÁLIDO
- bool isValid = (permanentHealth >= 40) || (totalHealth >= 45);
- if (!isValid && errorLen > 0)
- {
- // Mensajes MÁS CLAROS y ÚTILES
- if (permanentHealth >= 30 && permanentHealth < 40)
- {
- int neededTemp = 45 - permanentHealth;
- Format(errorMsg, errorLen, "Salud base: \x04%d\x01 (necesitas 40+). \x04O añade %d de salud temporal\x01 para llegar a 45 total",
- permanentHealth, neededTemp);
- }
- else
- {
- // Mostrar información específica
- if (tempHealth > 0)
- {
- Format(errorMsg, errorLen, "Necesitas \x0440+ de salud base\x01 O \x0445+ de salud total\x01. Tienes: \x04%d base\x01 + \x04%d temporal\x01 = \x04%d total",
- permanentHealth, tempHealth, totalHealth);
- }
- else
- {
- Format(errorMsg, errorLen, "Necesitas \x0440+ de salud base\x01 O \x0445+ de salud total\x01. Tienes: \x04%d base\x01 (sin salud temporal)",
- permanentHealth);
- }
- }
- }
- // DEBUG opcional (solo si hay problemas)
- if (g_hCvarDebug.BoolValue && !isValid)
- {
- DebugPrint("CheckPlayerHealthRules - %N - Base: %d, Temporal: %d, Total: %d | Regla1(Base>=40): %s, Regla2(Total>=45): %s",
- client, permanentHealth, tempHealth, totalHealth,
- (permanentHealth >= 40) ? "SÍ" : "NO",
- (totalHealth >= 45) ? "SÍ" : "NO");
- }
- return isValid;
- }
- // ========================================================================
- // FUNCIÓN MEJORADA DE GetPlayerTempHealth (PARA L4D2)
- // ========================================================================
- int GetPlayerTempHealth(int client)
- {
- if (!IsValidSurvivor(client))
- return 0;
- float fGameTime = GetGameTime();
- float fHealthBuffer = GetEntPropFloat(client, Prop_Send, "m_healthBuffer");
- float fHealthBufferTime = GetEntPropFloat(client, Prop_Send, "m_healthBufferTime");
- // Si no hay buffer, no hay salud temporal
- if (fHealthBuffer <= 0.0)
- return 0;
- // Si el tiempo del buffer es inválido (0 o futuro), usar el buffer actual
- if (fHealthBufferTime <= 0.0 || fHealthBufferTime > fGameTime)
- {
- int tempHealth = RoundToFloor(fHealthBuffer);
- return (tempHealth > 100) ? 100 : tempHealth;
- }
- // Cálculo CORRECTO según Valve
- float fTempHealth = fHealthBuffer - (fGameTime - fHealthBufferTime);
- // La salud temporal nunca puede ser negativa
- if (fTempHealth < 0.0)
- fTempHealth = 0.0;
- // Redondear hacia abajo para ser conservador (como hace el juego)
- int tempHealth = RoundToFloor(fTempHealth);
- // Límite máximo de 100 (máximo en L4D2)
- if (tempHealth > 100)
- tempHealth = 100;
- return tempHealth;
- }
- bool IsPlayerIncapacitated(int client)
- {
- return view_as<bool>(GetEntProp(client, Prop_Send, "m_isIncapacitated"));
- }
- bool IsPlayerHanging(int client)
- {
- return view_as<bool>(GetEntProp(client, Prop_Send, "m_isHangingFromLedge") ||
- GetEntProp(client, Prop_Send, "m_isFallingFromLedge"));
- }
- bool CheckWeaponReady(int client, const char[] weaponType)
- {
- DebugPrint("CheckWeaponReady - Verificando arma %s para %N", weaponType, client);
- // Si el arma está en la mano y lista, retornar true
- if (HasRequiredWeaponInHand(client, weaponType) && IsWeaponReadyForUse(client, GetMovementTypeFromString(weaponType)))
- {
- DebugPrint("CheckWeaponReady - %N tiene el arma lista", client);
- return true;
- }
- // Si tiene el arma en inventario, cambiarla
- if (HasRequiredWeapon(client, weaponType))
- {
- if (g_hCvarAutoSwitch.BoolValue)
- {
- DebugPrint("CheckWeaponReady - %N tiene arma en inventario, cambiando...", client);
- MovementType movType = GetMovementTypeFromString(weaponType);
- SwitchToRequiredWeapon(client, movType);
- int triggerIndex = -1;
- for (int i = 0; i < MAX_TRIGGERS; i++)
- {
- if (g_bPlayerInTrigger[client][i])
- {
- triggerIndex = i;
- break;
- }
- }
- if (triggerIndex != -1)
- {
- g_bWaitingForWeapon[client][triggerIndex] = true;
- g_iPlayerRetryCount[client][triggerIndex] = 0;
- DataPack pack = new DataPack();
- pack.WriteCell(GetClientUserId(client));
- pack.WriteString(weaponType);
- pack.WriteCell(triggerIndex);
- g_hRetryTimer[client][triggerIndex] = CreateTimer(2.0, Timer_RecheckWeapon, pack);
- DebugPrint("CheckWeaponReady - Timer de recheck programado para %N", client);
- return false;
- }
- }
- else
- {
- DebugPrint("CheckWeaponReady - Auto-switch deshabilitado");
- PrintToChat(client, "[\x04Error\x01] Auto-cambio desactivado. Activa 'st_mr_auto_switch 1'");
- }
- return false;
- }
- // Si no tiene el arma, esperar a que la recoja (timer)
- if (g_hCvarAutoSwitch.BoolValue)
- {
- DebugPrint("CheckWeaponReady - %N no tiene %s, esperando a que la recoja...", client, weaponType);
- int triggerIndex = -1;
- for (int i = 0; i < MAX_TRIGGERS; i++)
- {
- if (g_bPlayerInTrigger[client][i])
- {
- triggerIndex = i;
- break;
- }
- }
- if (triggerIndex != -1)
- {
- g_bWaitingForWeapon[client][triggerIndex] = true;
- g_iPlayerRetryCount[client][triggerIndex] = 0;
- DataPack pack = new DataPack();
- pack.WriteCell(GetClientUserId(client));
- pack.WriteString(weaponType);
- pack.WriteCell(triggerIndex);
- g_hRetryTimer[client][triggerIndex] = CreateTimer(2.0, Timer_RecheckWeapon, pack);
- return false;
- }
- }
- DebugPrint("CheckWeaponReady - %N NO tiene el arma %s", client, weaponType);
- return false;
- }
- int FindTriggerForPlayer(int client)
- {
- // Buscar el primer trigger donde el jugador esté presente
- for (int i = 0; i < MAX_TRIGGERS; i++)
- {
- if (g_bPlayerInTrigger[client][i])
- {
- return i;
- }
- }
- return -1;
- }
- // ========================================================================
- // ACTUALIZACIÓN EN Timer_RecheckWeapon PARA USAR LA LÓGICA CORREGIDA
- // ========================================================================
- public Action Timer_RecheckWeapon(Handle timer, DataPack pack)
- {
- pack.Reset();
- int userid = pack.ReadCell();
- char weaponType[16];
- pack.ReadString(weaponType, sizeof(weaponType));
- int triggerIndex = pack.ReadCell();
- delete pack;
- int client = GetClientOfUserId(userid);
- if (!IsValidSurvivor(client))
- return Plugin_Stop;
- g_hRetryTimer[client][triggerIndex] = null;
- // Si el jugador ya no está en el trigger, no hacer nada (no limpiar)
- if (!g_bPlayerInTrigger[client][triggerIndex])
- {
- DebugPrint("Timer_RecheckWeapon - %N ya no está en el trigger, no se limpia", client);
- g_bWaitingForWeapon[client][triggerIndex] = false;
- return Plugin_Stop;
- }
- g_iPlayerRetryCount[client][triggerIndex]++;
- DebugPrint("Timer_RecheckWeapon - %N - Intento %d/%d - Arma: %s",
- client, g_iPlayerRetryCount[client][triggerIndex], MAX_RETRY_ATTEMPTS, weaponType);
- MovementType movType = GetMovementTypeFromString(weaponType);
- bool hasInHand = HasRequiredWeaponInHand(client, weaponType);
- bool isReady = IsWeaponReadyForUse(client, movType);
- DebugPrint("Timer_RecheckWeapon - %N - En mano: %s, Lista: %s",
- client, hasInHand ? "SÍ" : "NO", isReady ? "SÍ" : "NO");
- // CASO 1: Tiene el arma en mano y lista -> ACTIVAR
- if (hasInHand && isReady)
- {
- DebugPrint("Timer_RecheckWeapon - %N ahora tiene el arma lista", client);
- g_bWaitingForWeapon[client][triggerIndex] = false;
- g_iPlayerRetryCount[client][triggerIndex] = 0;
- g_bPlayerReady[client][triggerIndex] = true;
- int groupNumber = g_iTriggerGroup[triggerIndex];
- if (groupNumber > 0)
- {
- PrintToChat(client, "[\x04MA\x01] Arma lista, activando grupo %d...", groupNumber);
- AttemptGroupActivation(groupNumber, triggerIndex, client);
- }
- else
- {
- ExecuteSoloMovementFromTrigger(client, triggerIndex);
- }
- return Plugin_Stop;
- }
- // CASO 2: Tiene el arma en mano pero NO lista -> esperar
- if (hasInHand && !isReady)
- {
- DebugPrint("Timer_RecheckWeapon - %N tiene arma en mano pero no lista, esperando...", client);
- if (g_iPlayerRetryCount[client][triggerIndex] < MAX_RETRY_ATTEMPTS)
- {
- DataPack retryPack = new DataPack();
- retryPack.WriteCell(userid);
- retryPack.WriteString(weaponType);
- retryPack.WriteCell(triggerIndex);
- g_hRetryTimer[client][triggerIndex] = CreateTimer(2.0, Timer_RecheckWeapon, retryPack);
- return Plugin_Stop;
- }
- else
- {
- PrintToChat(client, "[\x04Error\x01] Tiempo de espera agotado. Vuelve a intentarlo.");
- g_bWaitingForWeapon[client][triggerIndex] = false;
- g_iPlayerRetryCount[client][triggerIndex] = 0;
- // NO eliminar del trigger
- return Plugin_Stop;
- }
- }
- // CASO 3: Tiene arma en inventario pero no en mano -> cambiar de nuevo
- if (HasRequiredWeapon(client, weaponType))
- {
- DebugPrint("Timer_RecheckWeapon - %N tiene arma en inventario, cambiando...", client);
- SwitchToRequiredWeapon(client, movType);
- if (g_iPlayerRetryCount[client][triggerIndex] < MAX_RETRY_ATTEMPTS)
- {
- DataPack retryPack = new DataPack();
- retryPack.WriteCell(userid);
- retryPack.WriteString(weaponType);
- retryPack.WriteCell(triggerIndex);
- g_hRetryTimer[client][triggerIndex] = CreateTimer(2.0, Timer_RecheckWeapon, retryPack);
- return Plugin_Stop;
- }
- else
- {
- PrintToChat(client, "[\x04Error\x01] No se pudo cambiar al arma requerida.");
- g_bWaitingForWeapon[client][triggerIndex] = false;
- g_iPlayerRetryCount[client][triggerIndex] = 0;
- // NO eliminar del trigger
- return Plugin_Stop;
- }
- }
- // CASO 4: No tiene el arma en absoluto -> NO ELIMINAR, solo esperar o finalizar sin limpiar
- DebugPrint("Timer_RecheckWeapon - %N no tiene arma %s, se mantiene en el trigger", client, weaponType);
- if (g_iPlayerRetryCount[client][triggerIndex] < MAX_RETRY_ATTEMPTS)
- {
- // Si aún hay intentos, seguir esperando (quizás la recoja después)
- DataPack retryPack = new DataPack();
- retryPack.WriteCell(userid);
- retryPack.WriteString(weaponType);
- retryPack.WriteCell(triggerIndex);
- g_hRetryTimer[client][triggerIndex] = CreateTimer(2.0, Timer_RecheckWeapon, retryPack);
- return Plugin_Stop;
- }
- else
- {
- // Agotados los intentos, ya no esperamos, pero no eliminamos
- PrintToChat(client, "[\x04Error\x01] No tienes %s para este movimiento.", weaponType);
- g_bWaitingForWeapon[client][triggerIndex] = false;
- g_iPlayerRetryCount[client][triggerIndex] = 0;
- // NO eliminar del trigger
- return Plugin_Stop;
- }
- return Plugin_Stop;
- }
- bool IsRequiredWeaponActive(int client, const char[] weaponType)
- {
- int activeWeapon = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon");
- if (activeWeapon <= 0 || !IsValidEntity(activeWeapon))
- return false;
- char classname[32];
- GetEntityClassname(activeWeapon, classname, sizeof(classname));
- if (StrEqual(weaponType, "pm", false))
- {
- return StrEqual(classname, "weapon_pipe_bomb", false) || StrEqual(classname, "weapon_molotov", false);
- }
- else if (StrEqual(weaponType, "bile", false))
- {
- return StrEqual(classname, "weapon_vomitjar", false);
- }
- else if (StrEqual(weaponType, "gl", false))
- {
- return StrEqual(classname, "weapon_grenade_launcher", false);
- }
- return false;
- }
- public bool SwitchToRequiredWeapon(int client, MovementType movType)
- {
- DebugPrint("SwitchToRequiredWeapon - Cambiando arma para %N (movType: %d)", client, movType);
- if (!IsValidSurvivor(client))
- {
- DebugPrint("SwitchToRequiredWeapon - Cliente no válido");
- return false;
- }
- char weaponClassname[32];
- bool foundWeapon = false;
- if (movType == MOVEMENT_PM || movType == MOVEMENT_RUNPM)
- {
- DebugPrint("SwitchToRequiredWeapon - Buscando arma PM para %N", client);
- // Buscar pipebomb primero
- if (FindWeaponByClassname(client, "weapon_pipe_bomb") != -1)
- {
- weaponClassname = "weapon_pipe_bomb";
- foundWeapon = true;
- DebugPrint("SwitchToRequiredWeapon - Encontrado pipe_bomb");
- }
- else if (FindWeaponByClassname(client, "weapon_molotov") != -1)
- {
- weaponClassname = "weapon_molotov";
- foundWeapon = true;
- DebugPrint("SwitchToRequiredWeapon - Encontrado molotov");
- }
- }
- else if (movType == MOVEMENT_BILE || movType == MOVEMENT_RUNBILE)
- {
- DebugPrint("SwitchToRequiredWeapon - Buscando arma BILE para %N", client);
- if (FindWeaponByClassname(client, "weapon_vomitjar") != -1)
- {
- weaponClassname = "weapon_vomitjar";
- foundWeapon = true;
- DebugPrint("SwitchToRequiredWeapon - Encontrado vomitjar");
- }
- }
- else if (movType == MOVEMENT_GL || movType == MOVEMENT_RUNGL)
- {
- DebugPrint("SwitchToRequiredWeapon - Buscando arma GL para %N", client);
- if (FindWeaponByClassname(client, "weapon_grenade_launcher") != -1)
- {
- weaponClassname = "weapon_grenade_launcher";
- foundWeapon = true;
- DebugPrint("SwitchToRequiredWeapon - Encontrado grenade_launcher");
- }
- }
- if (!foundWeapon)
- {
- DebugPrint("SwitchToRequiredWeapon - No se encontró arma requerida para %N", client);
- return false;
- }
- int currentWeapon = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon");
- int targetWeapon = FindWeaponByClassname(client, weaponClassname);
- DebugPrint("SwitchToRequiredWeapon - Arma actual: %d, Arma objetivo: %d", currentWeapon, targetWeapon);
- if (targetWeapon == -1)
- {
- DebugPrint("SwitchToRequiredWeapon - Error: arma objetivo no encontrada");
- return false;
- }
- if (currentWeapon != targetWeapon)
- {
- DebugPrint("SwitchToRequiredWeapon - Cambiando arma de %d a %d para %N",
- currentWeapon, targetWeapon, client);
- // Método natural: usar el comando de cambio de arma del juego
- FakeClientCommandEx(client, "use %s", weaponClassname);
- // Obtener nombre de arma para mostrar
- char displayWeaponName[32];
- GetWeaponNameForType(movType, displayWeaponName, sizeof(displayWeaponName));
- // NUEVO: Mostrar mensaje CORREGIDO solo al jugador
- PrintToChat(client, "[\x04MA Auto-Cambio\x01] Cambiando a %s...", displayWeaponName);
- DebugPrint("SwitchToRequiredWeapon - Cambio de arma EXITOSO para %N", client);
- return true;
- }
- DebugPrint("SwitchToRequiredWeapon - Ya tenía el arma en la mano");
- return false;
- }
- public int FindWeaponByClassname(int client, const char[] classname)
- {
- for (int slot = 0; slot <= 4; slot++)
- {
- int weapon = GetPlayerWeaponSlot(client, slot);
- if (weapon > 0 && IsValidEntity(weapon))
- {
- char weaponClass[32];
- GetEntityClassname(weapon, weaponClass, sizeof(weaponClass));
- if (StrEqual(weaponClass, classname, false))
- {
- return weapon;
- }
- }
- }
- return -1;
- }
- bool HasRequiredWeapon(int client, const char[] weaponType)
- {
- if (!IsValidSurvivor(client))
- return false;
- // Búsqueda rápida en todas las ranuras
- for (int slot = 0; slot <= 4; slot++)
- {
- int weapon = GetPlayerWeaponSlot(client, slot);
- if (weapon > 0 && IsValidEntity(weapon))
- {
- char classname[32];
- GetEntityClassname(weapon, classname, sizeof(classname));
- if (StrEqual(weaponType, "pm") &&
- (StrEqual(classname, "weapon_pipe_bomb") || StrEqual(classname, "weapon_molotov")))
- return true;
- else if (StrEqual(weaponType, "bile") && StrEqual(classname, "weapon_vomitjar"))
- return true;
- else if (StrEqual(weaponType, "gl") && StrEqual(classname, "weapon_grenade_launcher"))
- return true;
- }
- }
- return false;
- }
- bool HasRequiredWeaponInHand(int client, const char[] weaponType)
- {
- if (!IsValidSurvivor(client))
- return false;
- int activeWeapon = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon");
- if (activeWeapon <= 0 || !IsValidEntity(activeWeapon))
- return false;
- char classname[32];
- GetEntityClassname(activeWeapon, classname, sizeof(classname));
- if (StrEqual(weaponType, "pm", false) &&
- (StrEqual(classname, "weapon_pipe_bomb", false) || StrEqual(classname, "weapon_molotov", false)))
- return true;
- else if (StrEqual(weaponType, "bile", false) && StrEqual(classname, "weapon_vomitjar", false))
- return true;
- else if (StrEqual(weaponType, "gl", false) && StrEqual(classname, "weapon_grenade_launcher", false))
- return true;
- return false;
- }
- bool IsWeaponReadyForUse(int client, MovementType movType)
- {
- if (!IsValidSurvivor(client))
- return false;
- int activeWeapon = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon");
- if (activeWeapon <= 0 || !IsValidEntity(activeWeapon))
- return false;
- float nextAttack = GetEntPropFloat(client, Prop_Send, "m_flNextAttack");
- if (nextAttack > GetGameTime() + 0.1)
- {
- DebugPrint("IsWeaponReadyForUse - %N no listo, nextAttack=%.2f", client, nextAttack - GetGameTime());
- return false;
- }
- DebugPrint("IsWeaponReadyForUse - %N listo", client);
- return true;
- }
- MovementType GetMovementTypeFromString(const char[] typeStr)
- {
- DebugPrint("GetMovementTypeFromString - Convirtiendo: %s", typeStr);
- if (StrEqual(typeStr, "pm", false) || StrEqual(typeStr, "runpm", false))
- {
- DebugPrint("GetMovementTypeFromString - %s -> MOVEMENT_PM (1)", typeStr);
- return MOVEMENT_PM;
- }
- if (StrEqual(typeStr, "bile", false) || StrEqual(typeStr, "runbile", false))
- {
- DebugPrint("GetMovementTypeFromString - %s -> MOVEMENT_BILE (2)", typeStr);
- return MOVEMENT_BILE;
- }
- if (StrEqual(typeStr, "gl", false) || StrEqual(typeStr, "rungl", false))
- {
- DebugPrint("GetMovementTypeFromString - %s -> MOVEMENT_GL (3)", typeStr);
- return MOVEMENT_GL;
- }
- if (StrEqual(typeStr, "run", false))
- {
- DebugPrint("GetMovementTypeFromString - %s -> MOVEMENT_RUN (4)", typeStr);
- return MOVEMENT_RUN;
- }
- if (StrEqual(typeStr, "dual", false))
- {
- DebugPrint("GetMovementTypeFromString - %s -> MOVEMENT_DUAL (5)", typeStr);
- return MOVEMENT_DUAL;
- }
- DebugPrint("GetMovementTypeFromString - %s -> MOVEMENT_UNKNOWN (0)", typeStr);
- return MOVEMENT_UNKNOWN;
- }
- // ========================================================================
- // FUNCIONES AUXILIARES
- // ========================================================================
- MovementType GetBaseMovementType(MovementType movType)
- {
- DebugPrint("GetBaseMovementType - Convirtiendo %d", movType);
- if (movType == MOVEMENT_RUNPM)
- {
- DebugPrint("GetBaseMovementType - %d -> MOVEMENT_PM (1)", movType);
- return MOVEMENT_PM;
- }
- else if (movType == MOVEMENT_RUNBILE)
- {
- DebugPrint("GetBaseMovementType - %d -> MOVEMENT_BILE (2)", movType);
- return MOVEMENT_BILE;
- }
- else if (movType == MOVEMENT_RUNGL)
- {
- DebugPrint("GetBaseMovementType - %d -> MOVEMENT_GL (3)", movType);
- return MOVEMENT_GL;
- }
- DebugPrint("GetBaseMovementType - %d -> %d (mismo)", movType, movType);
- return movType;
- }
- void GetWeaponNameForType(MovementType movType, char[] buffer, int size)
- {
- if (movType == MOVEMENT_PM || movType == MOVEMENT_RUNPM)
- strcopy(buffer, size, "pipebomb o molotov");
- else if (movType == MOVEMENT_BILE || movType == MOVEMENT_RUNBILE)
- strcopy(buffer, size, "bilejar");
- else if (movType == MOVEMENT_GL || movType == MOVEMENT_RUNGL)
- strcopy(buffer, size, "lanzagranadas");
- else if (movType == MOVEMENT_RUN)
- strcopy(buffer, size, "ninguna (solo correr)");
- else
- strcopy(buffer, size, "arma desconocida");
- }
- void GetIndividualTypeString(MovementType movType, char[] buffer, int size)
- {
- if (movType == MOVEMENT_PM)
- strcopy(buffer, size, "PM");
- else if (movType == MOVEMENT_BILE)
- strcopy(buffer, size, "Bile");
- else if (movType == MOVEMENT_GL)
- strcopy(buffer, size, "GL");
- else if (movType == MOVEMENT_RUN)
- strcopy(buffer, size, "Run");
- else if (movType == MOVEMENT_DUAL)
- strcopy(buffer, size, "Dual");
- else if (movType == MOVEMENT_RUNPM)
- strcopy(buffer, size, "RunPM");
- else if (movType == MOVEMENT_RUNBILE)
- strcopy(buffer, size, "RunBile");
- else if (movType == MOVEMENT_RUNGL)
- strcopy(buffer, size, "RunGL");
- else
- strcopy(buffer, size, "Unknown");
- }
- // ========================================================================
- // CALLBACKS DE TRIGGERS CON LÓGICA V15 MEJORADA
- // ========================================================================
- public void OnStartTouch(const char[] output, int caller, int activator, float delay)
- {
- DebugPrint("OnStartTouch - Trigger tocado - caller: %d, activator: %N", caller, activator);
- if (activator < 1 || activator > MaxClients || !IsClientInGame(activator) || !IsValidSurvivor(activator))
- {
- DebugPrint("OnStartTouch - Activator no válido");
- return;
- }
- // CRÍTICO: Verificar si el jugador ya está en movimiento
- if (g_bClientInMovement[activator])
- {
- DebugPrint("OnStartTouch - %N ya está en movimiento - ignorando trigger", activator);
- return;
- }
- caller = EntIndexToEntRef(caller);
- // PRIMERO: Limpiar al jugador de TODOS los triggers antes de agregarlo a uno nuevo
- // Esto evita que un jugador esté en múltiples triggers simultáneamente
- for (int i = 0; i < MAX_TRIGGERS; i++)
- {
- if (g_bPlayerInTrigger[activator][i])
- {
- DebugPrint("OnStartTouch - %N ya estaba en trigger %d - limpiando primero", activator, i);
- CompletelyCleanPlayerFromTrigger(activator, i);
- }
- }
- // SEGUNDO: Encontrar el trigger y agregar al jugador
- for (int i = 0; i < MAX_TRIGGERS; i++)
- {
- if (caller == g_iTriggers[i])
- {
- DebugPrint("OnStartTouch - Trigger encontrado en índice %d", i);
- // Agregar jugador al tracking
- g_bPlayerInTrigger[activator][i] = true;
- g_bPlayerReady[activator][i] = false;
- g_iPlayerRetryCount[activator][i] = 0;
- g_bWaitingForWeapon[activator][i] = false;
- // Iniciar verificación inmediata pero con validación extra
- // Esperar 0.2 segundos para asegurar que el jugador está estable en el trigger
- CreateTimer(0.2, Timer_DelayedCheck, activator);
- break;
- }
- }
- }
- public void OnEndTouch(const char[] output, int caller, int activator, float delay)
- {
- if (activator < 1 || activator > MaxClients || !IsClientInGame(activator))
- return;
- DebugPrint("OnEndTouch - %N salió de un trigger", activator);
- caller = EntIndexToEntRef(caller);
- for (int i = 0; i < MAX_TRIGGERS; i++)
- {
- if (caller == g_iTriggers[i])
- {
- DebugPrint("OnEndTouch - %N salió del trigger %d", activator, i);
- // Limpieza COMPLETA del jugador de este trigger
- CompletelyCleanPlayerFromTrigger(activator, i);
- // También remover de todos los grupos de espera
- RemovePlayerFromAllWaitingLists(activator);
- break;
- }
- }
- }
- bool VerifyPlayersStillInTriggers(int groupNumber, ArrayList players)
- {
- if (players == null) return false;
- bool allValid = true;
- for (int i = 0; i < players.Length; i++)
- {
- int client = players.Get(i);
- if (!IsValidSurvivor(client))
- {
- DebugPrint("VerifyPlayersStillInTriggers - %N ya no es válido", client);
- allValid = false;
- continue;
- }
- // Verificar que el jugador esté en ALGÚN trigger del grupo
- bool foundInGroupTrigger = false;
- ArrayList groupTriggers = GetGroupTriggers(groupNumber);
- if (groupTriggers != null)
- {
- for (int j = 0; j < groupTriggers.Length; j++)
- {
- int triggerIndex = groupTriggers.Get(j);
- if (g_bPlayerInTrigger[client][triggerIndex] && g_bPlayerReady[client][triggerIndex])
- {
- foundInGroupTrigger = true;
- break;
- }
- }
- }
- if (!foundInGroupTrigger)
- {
- DebugPrint("VerifyPlayersStillInTriggers - %N ya no está en un trigger del grupo %d", client, groupNumber);
- allValid = false;
- }
- }
- return allValid;
- }
- // ========================================================================
- // TIMER DE VERIFICACIÓN CONTINUA (LÓGICA V15 MEJORADA)
- // ========================================================================
- public Action Timer_CheckTriggerPresence(Handle timer, any client)
- {
- // Verificar si el cliente es válido
- if (!IsValidSurvivor(client) || !IsPlayerAlive(client) || IsPlayerIncapacitated(client))
- return Plugin_Stop;
- // Verificar en qué triggers está el jugador
- for (int triggerIndex = 0; triggerIndex < MAX_TRIGGERS; triggerIndex++)
- {
- if (g_bPlayerInTrigger[client][triggerIndex])
- {
- // Verificar si el trigger puede activarse (pasado el refire_time)
- if (CanTriggerActivate(triggerIndex))
- {
- // Verificar condiciones del jugador
- if (CheckPlayerConditions(client, triggerIndex))
- {
- DebugPrint("Timer_CheckTriggerPresence - %N cumple condiciones en trigger %d", client, triggerIndex);
- // Marcar como listo
- g_bPlayerReady[client][triggerIndex] = true;
- // Intentar activar según si es grupo o individual
- int groupNumber = g_iTriggerGroup[triggerIndex];
- if (groupNumber > 0)
- {
- // Es parte de un grupo
- if (!IsPlayerAlreadyInGroupActivation(groupNumber, client))
- {
- PrintToChat(client, "[\x04Grupo %d\x01] Listo. Esperando otros participantes...", groupNumber);
- AttemptGroupActivation(groupNumber, triggerIndex, client);
- }
- }
- else
- {
- // Es individual - activar inmediatamente
- ExecuteSoloMovementFromTrigger(client, triggerIndex);
- }
- }
- else
- {
- // Si no cumple condiciones, programar otro chequeo (como en V15)
- CreateTimer(0.5, Timer_CheckTriggerPresence, client);
- }
- }
- else
- {
- // Trigger en cooldown, esperar y volver a verificar
- float timeLeft = g_fNextActivationTime[triggerIndex] - GetGameTime();
- if (timeLeft > 0)
- {
- PrintHintText(client, "Trigger en cooldown: %.1fs", timeLeft);
- CreateTimer(timeLeft + 0.1, Timer_CheckTriggerPresence, client);
- }
- }
- // Solo procesar el primer trigger donde está el jugador
- break;
- }
- }
- return Plugin_Stop;
- }
- // ========================================================================
- // TIMER DE VERIFICACIÓN CONTINUA MEJORADO (PARA SALUD)
- // ========================================================================
- public Action Timer_DelayedCheck(Handle timer, any client)
- {
- if (!IsValidSurvivor(client) || g_bClientInMovement[client])
- {
- DebugPrint("Timer_DelayedCheck - %N ya no es válido o está en movimiento", client);
- return Plugin_Stop;
- }
- // Verificar en qué triggers está el jugador (debería ser solo uno)
- bool foundTrigger = false;
- for (int triggerIndex = 0; triggerIndex < MAX_TRIGGERS; triggerIndex++)
- {
- if (g_bPlayerInTrigger[client][triggerIndex])
- {
- foundTrigger = true;
- // Verificar si el trigger puede activarse
- if (CanTriggerActivate(triggerIndex))
- {
- // Verificar condiciones del jugador (incluyendo salud actualizada)
- if (CheckPlayerConditions(client, triggerIndex))
- {
- DebugPrint("Timer_DelayedCheck - %N cumple condiciones en trigger %d", client, triggerIndex);
- // Marcar como listo
- g_bPlayerReady[client][triggerIndex] = true;
- // Intentar activar según si es grupo o individual
- int groupNumber = g_iTriggerGroup[triggerIndex];
- if (groupNumber > 0)
- {
- // Es parte de un grupo
- if (!IsPlayerAlreadyInGroupActivation(groupNumber, client))
- {
- // Verificar que el jugador SIGA en el trigger
- if (g_bPlayerInTrigger[client][triggerIndex])
- {
- PrintToChat(client, "[\x04Grupo %d\x01] Listo. Esperando otros participantes...", groupNumber);
- AttemptGroupActivation(groupNumber, triggerIndex, client);
- }
- else
- {
- DebugPrint("Timer_DelayedCheck - %N ya no está en el trigger %d", client, triggerIndex);
- }
- }
- }
- else
- {
- // Es individual - activar inmediatamente si sigue en el trigger
- if (g_bPlayerInTrigger[client][triggerIndex])
- {
- ExecuteSoloMovementFromTrigger(client, triggerIndex);
- }
- }
- }
- else
- {
- DebugPrint("Timer_DelayedCheck - %N no cumple condiciones en trigger %d", client, triggerIndex);
- // Mostrar mensaje específico sobre salud si es el problema
- int permanentHealth = GetClientHealth(client);
- int tempHealth = GetPlayerTempHealth(client);
- int totalHealth = permanentHealth + tempHealth;
- if (totalHealth < 45)
- {
- PrintToChat(client, "[\x04MA Error\x01] Necesitas 45 de salud. Tienes: %d", totalHealth);
- }
- // Si no cumple condiciones, programar otro chequeo solo si sigue en el trigger
- if (g_bPlayerInTrigger[client][triggerIndex])
- {
- CreateTimer(0.5, Timer_DelayedCheck, client);
- }
- }
- }
- else
- {
- // Trigger en cooldown, esperar y volver a verificar solo si sigue en el trigger
- float timeLeft = g_fNextActivationTime[triggerIndex] - GetGameTime();
- if (timeLeft > 0 && g_bPlayerInTrigger[client][triggerIndex])
- {
- PrintHintText(client, "Trigger en cooldown: %.1fs", timeLeft);
- CreateTimer(timeLeft + 0.1, Timer_DelayedCheck, client);
- }
- }
- break; // Solo procesar el primer trigger donde está el jugador
- }
- }
- if (!foundTrigger)
- {
- DebugPrint("Timer_DelayedCheck - %N no está en ningún trigger", client);
- }
- return Plugin_Stop;
- }
- // ========================================================================
- // FUNCIONES DE ACTIVACIÓN GRUPAL (MODIFICADAS PARA PERMITIR MÚLTIPLES RUNNERS)
- // ========================================================================
- public void AttemptGroupActivation(int groupNumber, int triggerIdx, int activator)
- {
- DebugPrint("AttemptGroupActivation - Intentando activar el grupo %d, trigger %d, activador %N",
- groupNumber, triggerIdx, activator);
- // ================================================================
- // NUEVO: Verificar si el trigger está en cooldown
- // ================================================================
- if (triggerIdx >= 0 && triggerIdx < MAX_TRIGGERS)
- {
- float currentTime = GetGameTime();
- if (currentTime < g_fNextActivationTime[triggerIdx])
- {
- float timeLeft = g_fNextActivationTime[triggerIdx] - currentTime;
- DebugPrint("AttemptGroupActivation - Trigger %d en cooldown, reprogramando en %.2fs", triggerIdx, timeLeft);
- // Programar reintento después del cooldown
- DataPack pack = new DataPack();
- pack.WriteCell(groupNumber);
- pack.WriteCell(triggerIdx);
- pack.WriteCell(GetClientUserId(activator));
- CreateTimer(timeLeft + 0.1, Timer_RetryActivation, pack);
- return;
- }
- }
- // Obtener todos los triggers del grupo
- ArrayList groupTriggers = GetGroupTriggers(groupNumber);
- DebugPrint("AttemptGroupActivation - El grupo %d tiene %d triggers", groupNumber, groupTriggers.Length);
- if (groupTriggers == null || groupTriggers.Length == 0)
- return;
- // Obtener información del grupo
- ArrayList groupTypes = GetGroupTypes(groupNumber);
- DebugPrint("AttemptGroupActivation - El grupo %d tiene %d tipos:", groupNumber, groupTypes.Length);
- // Contar cuántos tipos únicos hay
- int uniqueTypes = 0;
- bool hasRun = false;
- bool hasBile = false;
- bool hasGL = false;
- bool hasPM = false;
- for (int i = 0; i < groupTypes.Length; i++)
- {
- char type[16];
- groupTypes.GetString(i, type, sizeof(type));
- DebugPrint("AttemptGroupActivation - Tipo %d: %s", i, type);
- if (StrEqual(type, "run")) hasRun = true;
- else if (StrEqual(type, "bile")) hasBile = true;
- else if (StrEqual(type, "gl")) hasGL = true;
- else if (StrEqual(type, "pm")) hasPM = true;
- else if (StrEqual(type, "runpm")) { hasRun = true; hasPM = true; }
- else if (StrEqual(type, "runbile")) { hasRun = true; hasBile = true; }
- else if (StrEqual(type, "rungl")) { hasRun = true; hasGL = true; }
- }
- // Determinar si es grupo múltiple
- int totalTypes = (hasRun ? 1 : 0) + (hasBile ? 1 : 0) + (hasGL ? 1 : 0) + (hasPM ? 1 : 0);
- DebugPrint("AttemptGroupActivation - Total tipos únicos en grupo %d: %d", groupNumber, totalTypes);
- // ================================================================
- // DECLARAR VARIABLES PARA ESTA FUNCIÓN (SOLO UNA VEZ)
- // ================================================================
- ArrayList activatedPlayers = new ArrayList();
- ArrayList activatedFiles = new ArrayList(ByteCountToCells(128));
- // Verificar si el grupo ya está activo (evitar múltiples activaciones)
- char groupKey[16];
- IntToString(groupNumber, groupKey, sizeof(groupKey));
- bool isGroupActive = false;
- if (g_hActiveGroups != null)
- {
- g_hActiveGroups.GetValue(groupKey, isGroupActive);
- }
- if (isGroupActive)
- {
- DebugPrint("AttemptGroupActivation - El grupo %d ya está activo - ignorando", groupNumber);
- delete activatedPlayers;
- delete activatedFiles;
- return;
- }
- // NUEVA VERIFICACIÓN: Comprobar que todos los jugadores siguen en sus triggers
- bool allPlayersStillValid = true;
- ArrayList playersToCheck = new ArrayList();
- // Determinar la capacidad del grupo basada en el booster de mayor capacidad
- int groupCapacity = 1; // Capacidad mínima
- int maxBoosterCapacity = 0;
- char maxBoosterType[16] = "";
- // Calcular capacidad basada en boosters
- if (hasPM)
- {
- int capacity = GetBoosterCapacityNew("PM"); // 32
- if (capacity > maxBoosterCapacity)
- {
- maxBoosterCapacity = capacity;
- strcopy(maxBoosterType, sizeof(maxBoosterType), "PM");
- }
- }
- if (hasBile)
- {
- int capacity = GetBoosterCapacityNew("Bile"); // 5
- if (capacity > maxBoosterCapacity)
- {
- maxBoosterCapacity = capacity;
- strcopy(maxBoosterType, sizeof(maxBoosterType), "Bile");
- }
- }
- if (hasGL)
- {
- int capacity = GetBoosterCapacityNew("GL"); // 1
- if (capacity > maxBoosterCapacity)
- {
- maxBoosterCapacity = capacity;
- strcopy(maxBoosterType, sizeof(maxBoosterType), "GL");
- }
- }
- // Si no hay boosters puros, verificar híbridos
- if (maxBoosterCapacity == 0)
- {
- // Buscar en todos los triggers del grupo para determinar la capacidad
- for (int i = 0; i < groupTriggers.Length; i++)
- {
- int currentTriggerIdx = groupTriggers.Get(i);
- char type[16];
- strcopy(type, sizeof(type), g_sTriggerType[currentTriggerIdx]);
- if (StrEqual(type, "runpm") || StrEqual(type, "runbile") || StrEqual(type, "rungl"))
- {
- // Es un híbrido - obtener su capacidad
- int capacity = MA_GetBoosterCapacity(type);
- if (capacity > maxBoosterCapacity)
- {
- maxBoosterCapacity = capacity;
- strcopy(maxBoosterType, sizeof(maxBoosterType), type);
- }
- }
- }
- }
- // Establecer capacidad del grupo
- groupCapacity = maxBoosterCapacity > 0 ? maxBoosterCapacity : 32; // Default 32 si no hay booster
- DebugPrint("AttemptGroupActivation - Capacidad del grupo %d: %d runners (booster: %s)",
- groupNumber, groupCapacity, maxBoosterType);
- // ================================================================
- // RECOLECTAR JUGADORES PARA VERIFICACIÓN (para grupos de 1-2 tipos)
- // ================================================================
- if (totalTypes < 3)
- {
- for (int i = 0; i < groupTriggers.Length; i++)
- {
- int currentTriggerIdx = groupTriggers.Get(i);
- for (int client = 1; client <= MaxClients; client++)
- {
- if (IsValidSurvivor(client) && g_bPlayerReady[client][currentTriggerIdx])
- {
- if (playersToCheck.FindValue(client) == -1)
- {
- playersToCheck.Push(client);
- DebugPrint("AttemptGroupActivation - Jugador listo: %N en trigger %d", client, currentTriggerIdx);
- }
- }
- }
- }
- }
- // Verificar que todos los jugadores propuestos sigan válidos y en sus triggers
- if (playersToCheck.Length > 0)
- {
- for (int i = 0; i < playersToCheck.Length; i++)
- {
- int client = playersToCheck.Get(i);
- if (!IsValidSurvivor(client))
- {
- DebugPrint("AttemptGroupActivation - %N ya no es válido", client);
- allPlayersStillValid = false;
- break;
- }
- if (g_bClientInMovement[client])
- {
- DebugPrint("AttemptGroupActivation - %N ya está en movimiento", client);
- allPlayersStillValid = false;
- break;
- }
- bool foundInGroupTrigger = false;
- for (int j = 0; j < groupTriggers.Length; j++)
- {
- int currentTriggerIdx = groupTriggers.Get(j);
- if (g_bPlayerInTrigger[client][currentTriggerIdx] && g_bPlayerReady[client][currentTriggerIdx])
- {
- foundInGroupTrigger = true;
- break;
- }
- }
- if (!foundInGroupTrigger)
- {
- DebugPrint("AttemptGroupActivation - %N ya no está en un trigger del grupo %d", client, groupNumber);
- allPlayersStillValid = false;
- break;
- }
- }
- }
- if (!allPlayersStillValid)
- {
- DebugPrint("AttemptGroupActivation - Validación falló - cancelando activación");
- delete playersToCheck;
- delete activatedPlayers;
- delete activatedFiles;
- return;
- }
- // ================================================================
- // VERIFICAR QUE TODOS LOS TIPOS TENGAN JUGADORES LISTOS
- // ================================================================
- if (totalTypes >= 3)
- {
- DebugPrint("AttemptGroupActivation - Verificando %d tipos para grupo %d", totalTypes, groupNumber);
- bool runReady = (hasRun && TypeHasReadyPlayers(groupNumber, "run"));
- bool bileReady = (hasBile && (TypeHasReadyPlayers(groupNumber, "bile") || TypeHasReadyPlayers(groupNumber, "runbile")));
- bool glReady = (hasGL && (TypeHasReadyPlayers(groupNumber, "gl") || TypeHasReadyPlayers(groupNumber, "rungl")));
- bool pmReady = (hasPM && (TypeHasReadyPlayers(groupNumber, "pm") || TypeHasReadyPlayers(groupNumber, "runpm")));
- DebugPrint("AttemptGroupActivation - Estados: Run=%d, Bile=%d, GL=%d, PM=%d",
- runReady, bileReady, glReady, pmReady);
- int readyTypes = 0;
- if (runReady) readyTypes++;
- if (bileReady) readyTypes++;
- if (glReady) readyTypes++;
- if (pmReady) readyTypes++;
- if (readyTypes < totalTypes)
- {
- DebugPrint("AttemptGroupActivation - No todos los tipos del grupo %d tienen jugadores listos (%d/%d)",
- groupNumber, readyTypes, totalTypes);
- if (hasRun && !runReady)
- {
- if (!g_bWaitMessageShown[activator][groupNumber])
- {
- PrintToChat(activator, "[\x04MA Grupo (%d)\x01] Esperando Runner...", groupNumber);
- g_bWaitMessageShown[activator][groupNumber] = true;
- }
- }
- if (hasBile && !bileReady)
- {
- if (!g_bWaitMessageShown[activator][groupNumber])
- {
- PrintToChat(activator, "[\x04MA Grupo (%d)\x01] Esperando Bile...", groupNumber);
- g_bWaitMessageShown[activator][groupNumber] = true;
- }
- }
- if (hasGL && !glReady)
- {
- if (!g_bWaitMessageShown[activator][groupNumber])
- {
- PrintToChat(activator, "[\x04MA Grupo (%d)\x01] Esperando GL...", groupNumber);
- g_bWaitMessageShown[activator][groupNumber] = true;
- }
- }
- if (hasPM && !pmReady)
- {
- if (!g_bWaitMessageShown[activator][groupNumber])
- {
- PrintToChat(activator, "[\x04MA Grupo (%d)\x01] Esperando PM...", groupNumber);
- g_bWaitMessageShown[activator][groupNumber] = true;
- }
- }
- delete playersToCheck;
- delete activatedPlayers;
- delete activatedFiles;
- return;
- }
- DebugPrint("AttemptGroupActivation - TODOS los tipos (%d) tienen jugadores listos", totalTypes);
- }
- else
- {
- if (!AllGroupTypesHaveReadyPlayers(groupNumber))
- {
- DebugPrint("AttemptGroupActivation - No todos los tipos del grupo %d tienen jugadores listos", groupNumber);
- delete playersToCheck;
- delete activatedPlayers;
- delete activatedFiles;
- return;
- }
- }
- // Verificar runners disponibles
- ArrayList allRunners = GetAllRunnersInGroup(groupNumber);
- int availableRunners = allRunners.Length;
- delete allRunners;
- DebugPrint("AttemptGroupActivation - Runners disponibles en grupo %d: %d", groupNumber, availableRunners);
- if (availableRunners == 0 && hasRun)
- {
- DebugPrint("AttemptGroupActivation - No hay runners disponibles en el grupo %d", groupNumber);
- delete playersToCheck;
- delete activatedPlayers;
- delete activatedFiles;
- return;
- }
- DebugPrint("AttemptGroupActivation - TODAS las condiciones cumplidas - ACTIVANDO GRUPO %d", groupNumber);
- g_bWaitMessageShown[activator][groupNumber] = false;
- // Marcar grupo como activo
- if (g_hActiveGroups != null)
- {
- g_hActiveGroups.SetValue(groupKey, true);
- }
- // ================================================================
- // ACTIVAR SEGÚN EL TIPO DE GRUPO
- // ================================================================
- if (totalTypes >= 3)
- {
- DebugPrint("AttemptGroupActivation - Activando grupo MULTIPLE (%d tipos)", totalTypes);
- // 1. PRIMERO: Activar HÍBRIDOS (runpm, runbile, rungl)
- char hybridTypes[][] = {"runpm", "runbile", "rungl"};
- for (int h = 0; h < sizeof(hybridTypes); h++)
- {
- // Obtener TODOS los jugadores en esta posición híbrida
- ArrayList hybridPlayers = GetAllReadyPlayersInHybridPosition(groupNumber, hybridTypes[h]);
- if (hybridPlayers != null && hybridPlayers.Length > 0)
- {
- // Determinar el arma requerida para este híbrido
- char requiredWeapon[8];
- if (StrEqual(hybridTypes[h], "runpm")) strcopy(requiredWeapon, sizeof(requiredWeapon), "pm");
- else if (StrEqual(hybridTypes[h], "runbile")) strcopy(requiredWeapon, sizeof(requiredWeapon), "bile");
- else if (StrEqual(hybridTypes[h], "rungl")) strcopy(requiredWeapon, sizeof(requiredWeapon), "gl");
- // Verificar si al menos uno de los jugadores tiene el arma requerida
- bool hasArmed = false;
- for (int i = 0; i < hybridPlayers.Length; i++)
- {
- int client = hybridPlayers.Get(i);
- if (HasRequiredWeapon(client, requiredWeapon))
- {
- hasArmed = true;
- break;
- }
- }
- // Si no hay ningún jugador con el arma, no activar este híbrido
- if (!hasArmed)
- {
- DebugPrint("AttemptGroupActivation - No hay jugador con %s en posición híbrida %s, saltando",
- requiredWeapon, hybridTypes[h]);
- // Mostrar mensaje de espera (solo una vez)
- if (!g_bWaitMessageShown[activator][groupNumber])
- {
- PrintToChat(activator, "[\x04MA Grupo (%d)\x01] Esperando un jugador con %s en posición híbrida...",
- groupNumber, requiredWeapon);
- g_bWaitMessageShown[activator][groupNumber] = true;
- }
- delete hybridPlayers;
- continue;
- }
- // Activar TODOS los jugadores en la posición híbrida (incluyendo los que no tienen arma)
- for (int i = 0; i < hybridPlayers.Length; i++)
- {
- int client = hybridPlayers.Get(i);
- if (IsPlayerAlreadyInGroupActivation(groupNumber, client))
- {
- if (g_bClientInMovement[client])
- {
- DebugPrint("AttemptGroupActivation - %N ya está en movimiento en el grupo %d - saltando", client, groupNumber);
- continue;
- }
- else
- {
- DebugPrint("AttemptGroupActivation - %N completó movimiento previo, puede unirse al grupo %d de nuevo", client, groupNumber);
- MarkPlayerInGroupActivation(groupNumber, client, false);
- }
- }
- int foundTriggerIdx = FindReadyTriggerForPlayer(client, hybridTypes[h]);
- if (foundTriggerIdx != -1)
- {
- char movementFile[128];
- GetMovementFileForTrigger(foundTriggerIdx, movementFile, sizeof(movementFile));
- if (g_bPlayerReady[client][foundTriggerIdx])
- {
- MarkPlayerInGroupActivation(groupNumber, client, true);
- activatedPlayers.Push(client);
- activatedFiles.PushString(movementFile);
- ExecutePlayerMovementImmediate(client, movementFile, MSG_GROUP_MULTI, groupNumber);
- DebugPrint("AttemptGroupActivation - Híbrido %N (%s) activado para grupo %d",
- client, hybridTypes[h], groupNumber);
- }
- }
- }
- }
- delete hybridPlayers;
- }
- // 2. SEGUNDO: Activar boosters puros
- if (hasBile)
- {
- ArrayList bilePlayers = GetReadyPlayersByType(groupNumber, "bile");
- if (bilePlayers != null && bilePlayers.Length > 0)
- {
- for (int i = 0; i < bilePlayers.Length; i++)
- {
- int client = bilePlayers.Get(i);
- if (IsPlayerAlreadyInGroupActivation(groupNumber, client))
- {
- if (g_bClientInMovement[client])
- {
- DebugPrint("AttemptGroupActivation - %N ya está en movimiento en el grupo %d - saltando", client, groupNumber);
- continue;
- }
- else
- {
- DebugPrint("AttemptGroupActivation - %N completó movimiento previo, puede unirse al grupo %d de nuevo", client, groupNumber);
- MarkPlayerInGroupActivation(groupNumber, client, false);
- }
- }
- int foundTriggerIdx = FindReadyTriggerForPlayer(client, "bile");
- if (foundTriggerIdx != -1)
- {
- char movementFile[128];
- GetMovementFileForTrigger(foundTriggerIdx, movementFile, sizeof(movementFile));
- if (g_bPlayerReady[client][foundTriggerIdx])
- {
- MarkPlayerInGroupActivation(groupNumber, client, true);
- activatedPlayers.Push(client);
- activatedFiles.PushString(movementFile);
- ExecutePlayerMovementImmediate(client, movementFile, MSG_GROUP_MULTI, groupNumber);
- DebugPrint("AttemptGroupActivation - Bile puro %N activado para grupo %d", client, groupNumber);
- break;
- }
- }
- }
- }
- delete bilePlayers;
- }
- if (hasGL)
- {
- ArrayList glPlayers = GetReadyPlayersByType(groupNumber, "gl");
- if (glPlayers != null && glPlayers.Length > 0)
- {
- for (int i = 0; i < glPlayers.Length; i++)
- {
- int client = glPlayers.Get(i);
- if (IsPlayerAlreadyInGroupActivation(groupNumber, client))
- {
- if (g_bClientInMovement[client])
- {
- DebugPrint("AttemptGroupActivation - %N ya está en movimiento en el grupo %d - saltando", client, groupNumber);
- continue;
- }
- else
- {
- DebugPrint("AttemptGroupActivation - %N completó movimiento previo, puede unirse al grupo %d de nuevo", client, groupNumber);
- MarkPlayerInGroupActivation(groupNumber, client, false);
- }
- }
- int foundTriggerIdx = FindReadyTriggerForPlayer(client, "gl");
- if (foundTriggerIdx != -1)
- {
- char movementFile[128];
- GetMovementFileForTrigger(foundTriggerIdx, movementFile, sizeof(movementFile));
- if (g_bPlayerReady[client][foundTriggerIdx])
- {
- MarkPlayerInGroupActivation(groupNumber, client, true);
- activatedPlayers.Push(client);
- activatedFiles.PushString(movementFile);
- ExecutePlayerMovementImmediate(client, movementFile, MSG_GROUP_MULTI, groupNumber);
- DebugPrint("AttemptGroupActivation - GL puro %N activado para grupo %d", client, groupNumber);
- break;
- }
- }
- }
- }
- delete glPlayers;
- }
- if (hasPM)
- {
- ArrayList pmPlayers = GetReadyPlayersByType(groupNumber, "pm");
- if (pmPlayers != null && pmPlayers.Length > 0)
- {
- for (int i = 0; i < pmPlayers.Length; i++)
- {
- int client = pmPlayers.Get(i);
- if (IsPlayerAlreadyInGroupActivation(groupNumber, client))
- {
- if (g_bClientInMovement[client])
- {
- DebugPrint("AttemptGroupActivation - %N ya está en movimiento en el grupo %d - saltando", client, groupNumber);
- continue;
- }
- else
- {
- DebugPrint("AttemptGroupActivation - %N completó movimiento previo, puede unirse al grupo %d de nuevo", client, groupNumber);
- MarkPlayerInGroupActivation(groupNumber, client, false);
- }
- }
- int foundTriggerIdx = FindReadyTriggerForPlayer(client, "pm");
- if (foundTriggerIdx != -1)
- {
- char movementFile[128];
- GetMovementFileForTrigger(foundTriggerIdx, movementFile, sizeof(movementFile));
- if (g_bPlayerReady[client][foundTriggerIdx])
- {
- MarkPlayerInGroupActivation(groupNumber, client, true);
- activatedPlayers.Push(client);
- activatedFiles.PushString(movementFile);
- ExecutePlayerMovementImmediate(client, movementFile, MSG_GROUP_MULTI, groupNumber);
- DebugPrint("AttemptGroupActivation - PM puro %N activado para grupo %d", client, groupNumber);
- break;
- }
- }
- }
- }
- delete pmPlayers;
- }
- // 3. TERCERO: Activar runners puros ADICIONALES
- int runnersActivated = 0;
- for (int i = 0; i < activatedPlayers.Length; i++)
- {
- int client = activatedPlayers.Get(i);
- for (int j = 0; j < groupTriggers.Length; j++)
- {
- int currentTriggerIdx = groupTriggers.Get(j);
- if (g_bPlayerReady[client][currentTriggerIdx])
- {
- char type[16];
- strcopy(type, sizeof(type), g_sTriggerType[currentTriggerIdx]);
- if (StrEqual(type, "run") || StrContains(type, "run") != -1)
- {
- runnersActivated++;
- break;
- }
- }
- }
- }
- DebugPrint("AttemptGroupActivation - Runners ya activados: %d, Capacidad del grupo: %d",
- runnersActivated, groupCapacity);
- if (runnersActivated < groupCapacity)
- {
- ArrayList additionalRunners = GetReadyPlayersByType(groupNumber, "run");
- if (additionalRunners != null && additionalRunners.Length > 0)
- {
- for (int i = 0; i < additionalRunners.Length && runnersActivated < groupCapacity; i++)
- {
- int client = additionalRunners.Get(i);
- if (IsPlayerAlreadyInGroupActivation(groupNumber, client))
- {
- if (g_bClientInMovement[client])
- {
- DebugPrint("AttemptGroupActivation - %N ya está en movimiento en el grupo %d - saltando", client, groupNumber);
- continue;
- }
- else
- {
- DebugPrint("AttemptGroupActivation - %N completó movimiento previo, puede unirse al grupo %d de nuevo", client, groupNumber);
- MarkPlayerInGroupActivation(groupNumber, client, false);
- }
- }
- int foundTriggerIdx = FindReadyTriggerForPlayer(client, "run");
- if (foundTriggerIdx != -1)
- {
- char movementFile[128];
- GetMovementFileForTrigger(foundTriggerIdx, movementFile, sizeof(movementFile));
- if (g_bPlayerReady[client][foundTriggerIdx])
- {
- MarkPlayerInGroupActivation(groupNumber, client, true);
- activatedPlayers.Push(client);
- activatedFiles.PushString(movementFile);
- ExecutePlayerMovementImmediate(client, movementFile, MSG_GROUP_MULTI, groupNumber);
- runnersActivated++;
- DebugPrint("AttemptGroupActivation - Runner adicional %N activado para grupo %d (%d/%d)",
- client, groupNumber, runnersActivated, groupCapacity);
- }
- }
- }
- }
- delete additionalRunners;
- }
- DebugPrint("AttemptGroupActivation - Grupo %d activado: %d jugadores (capacidad: %d)",
- groupNumber, activatedPlayers.Length, groupCapacity);
- }
- else
- {
- // Para grupos de 1-2 tipos, usar ActivateGroupInOrder
- ActivateGroupInOrder(groupNumber, groupCapacity, activatedFiles);
- // Obtener jugadores activados
- for (int i = 1; i <= MaxClients; i++)
- {
- if (IsValidSurvivor(i) && IsPlayerAlreadyInGroupActivation(groupNumber, i))
- {
- activatedPlayers.Push(i);
- }
- }
- }
- // Mostrar resumen de activación
- if (activatedPlayers.Length > 0)
- {
- ShowGroupActivationSummary(groupNumber, activatedPlayers, activatedFiles);
- }
- else
- {
- DebugPrint("AttemptGroupActivation - ¡ADVERTENCIA! Grupo %d activado pero 0 jugadores registrados", groupNumber);
- }
- // Limpiar
- delete activatedPlayers;
- delete activatedFiles;
- delete playersToCheck;
- // Aplicar refire_time grupal
- ApplyGroupRefireTime(groupNumber);
- DebugPrint("AttemptGroupActivation - Grupo %d activado exitosamente", groupNumber);
- }
- /**
- * Obtiene TODOS los jugadores listos para un tipo específico (no solo el primero)
- */
- ArrayList GetAllReadyPlayersForType(int groupNumber, const char[] type)
- {
- ArrayList players = new ArrayList();
- ArrayList groupTriggers = GetGroupTriggers(groupNumber);
- if (groupTriggers == null)
- return players;
- for (int i = 0; i < groupTriggers.Length; i++)
- {
- int triggerIndex = groupTriggers.Get(i);
- // Verificar si este trigger es del tipo buscado
- if (StrEqual(g_sTriggerType[triggerIndex], type))
- {
- // Agregar TODOS los jugadores listos en este trigger
- for (int client = 1; client <= MaxClients; client++)
- {
- if (IsValidSurvivor(client) && g_bPlayerReady[client][triggerIndex])
- {
- // Verificar que el jugador no esté ya en la lista
- if (players.FindValue(client) == -1)
- {
- players.Push(client);
- }
- }
- }
- }
- }
- return players;
- }
- // ========================================================================
- // FUNCIÓN NUEVA: OBTENER CAPACIDAD DEL GRUPO (FIJA)
- // ========================================================================
- int GetGroupBoosterCapacityFixed(int groupNumber)
- {
- ArrayList groupTypes = GetGroupTypes(groupNumber);
- if (groupTypes == null)
- {
- DebugPrint("GetGroupBoosterCapacityFixed - Grupo %d no tiene tipos -> 1 runner", groupNumber);
- return 1;
- }
- DebugPrint("GetGroupBoosterCapacityFixed - Analizando grupo %d:", groupNumber);
- // 1. Verificar si el grupo tiene booster (PM, Bile, GL)
- bool hasBooster = false;
- int boosterCapacity = 1;
- for (int i = 0; i < groupTypes.Length; i++)
- {
- char type[16];
- groupTypes.GetString(i, type, sizeof(type));
- if (StrEqual(type, "pm") || StrEqual(type, "bile") || StrEqual(type, "gl") ||
- StrEqual(type, "runpm") || StrEqual(type, "runbile") || StrEqual(type, "rungl"))
- {
- hasBooster = true;
- boosterCapacity = MA_GetBoosterCapacity(type);
- DebugPrint("GetGroupBoosterCapacityFixed - Encontrado booster %s -> %d runners", type, boosterCapacity);
- break;
- }
- }
- if (hasBooster)
- {
- // 2. Grupo CON booster: capacidad limitada por el booster
- DebugPrint("GetGroupBoosterCapacityFixed - Grupo %d TIENE booster -> %d runners máx", groupNumber, boosterCapacity);
- return boosterCapacity;
- }
- else
- {
- // 3. Grupo SIN booster (solo runners): capacidad máxima 32
- DebugPrint("GetGroupBoosterCapacityFixed - Grupo %d SIN booster -> 32 runners máx", groupNumber);
- return 32;
- }
- }
- // NUEVA FUNCIÓN: Verificar que todos los tipos tengan jugadores DIFERENTES
- bool AllTypesHaveDifferentPlayers(int groupNumber)
- {
- ArrayList playersFound = new ArrayList();
- ArrayList groupTypes = GetGroupTypes(groupNumber);
- if (groupTypes == null || groupTypes.Length == 0)
- {
- delete playersFound;
- return true;
- }
- for (int i = 0; i < groupTypes.Length; i++)
- {
- char type[16];
- groupTypes.GetString(i, type, sizeof(type));
- // Obtener el primer jugador listo de este tipo
- int playerForType = GetFirstReadyPlayerForType(groupNumber, type);
- if (playerForType != -1)
- {
- // Verificar si este jugador ya está en la lista (activando otro rol)
- if (playersFound.FindValue(playerForType) != -1)
- {
- DebugPrint("AllTypesHaveDifferentPlayers - ¡Jugador duplicado! %N activando múltiples roles", playerForType);
- delete playersFound;
- return false;
- }
- playersFound.Push(playerForType);
- }
- }
- delete playersFound;
- return true;
- }
- // NUEVA FUNCIÓN: Obtener el primer jugador listo para un tipo específico
- int GetFirstReadyPlayerForType(int groupNumber, const char[] type)
- {
- ArrayList groupTriggers = GetGroupTriggers(groupNumber);
- if (groupTriggers == null)
- return -1;
- for (int i = 0; i < groupTriggers.Length; i++)
- {
- int triggerIndex = groupTriggers.Get(i);
- // Verificar si este trigger es del tipo buscado
- if (StrEqual(g_sTriggerType[triggerIndex], type))
- {
- // Buscar primer jugador listo en este trigger
- for (int client = 1; client <= MaxClients; client++)
- {
- if (IsValidSurvivor(client) && g_bPlayerReady[client][triggerIndex])
- {
- return client;
- }
- }
- }
- }
- return -1;
- }
- char[] GetTypeDisplayName(const char[] type)
- {
- char displayName[16];
- if (StrEqual(type, "pm")) strcopy(displayName, sizeof(displayName), "PM");
- else if (StrEqual(type, "bile")) strcopy(displayName, sizeof(displayName), "Bile");
- else if (StrEqual(type, "gl")) strcopy(displayName, sizeof(displayName), "GL");
- else if (StrEqual(type, "run")) strcopy(displayName, sizeof(displayName), "Runner");
- else if (StrEqual(type, "runpm")) strcopy(displayName, sizeof(displayName), "Runner+PM");
- else if (StrEqual(type, "runbile")) strcopy(displayName, sizeof(displayName), "Runner+Bile");
- else if (StrEqual(type, "rungl")) strcopy(displayName, sizeof(displayName), "Runner+GL");
- else if (StrEqual(type, "dual")) strcopy(displayName, sizeof(displayName), "Dual");
- else strcopy(displayName, sizeof(displayName), type);
- return displayName;
- }
- int CountTotalReadyPlayers(int groupNumber)
- {
- int count = 0;
- ArrayList groupTriggers = GetGroupTriggers(groupNumber);
- if (groupTriggers == null) return 0;
- for (int i = 0; i < groupTriggers.Length; i++)
- {
- int triggerIndex = groupTriggers.Get(i);
- // Contar jugadores listos en este trigger
- for (int client = 1; client <= MaxClients; client++)
- {
- if (IsValidSurvivor(client) && g_bPlayerReady[client][triggerIndex])
- {
- count++;
- break; // Solo contar una vez por jugador (en caso de múltiples triggers)
- }
- }
- }
- return count;
- }
- bool AllGroupTypesHaveReadyPlayers(int groupNumber)
- {
- DebugPrint("AllGroupTypesHaveReadyPlayers - Verificando grupo %d", groupNumber);
- ArrayList groupTypes = GetGroupTypes(groupNumber);
- if (groupTypes == null || groupTypes.Length == 0)
- {
- DebugPrint("AllGroupTypesHaveReadyPlayers - El grupo %d no tiene tipos", groupNumber);
- return false;
- }
- for (int i = 0; i < groupTypes.Length; i++)
- {
- char type[16];
- groupTypes.GetString(i, type, sizeof(type));
- DebugPrint("AllGroupTypesHaveReadyPlayers - Verificando tipo: %s", type);
- if (!TypeHasReadyPlayers(groupNumber, type))
- {
- DebugPrint("AllGroupTypesHaveReadyPlayers - El tipo %s NO tiene jugadores listos", type);
- return false;
- }
- DebugPrint("AllGroupTypesHaveReadyPlayers - El tipo %s SÍ tiene jugadores listos", type);
- }
- DebugPrint("AllGroupTypesHaveReadyPlayers - TODOS los tipos tienen jugadores listos");
- return true;
- }
- bool TypeHasReadyPlayers(int groupNumber, const char[] type)
- {
- DebugPrint("TypeHasReadyPlayers - Verificando tipo %s en grupo %d", type, groupNumber);
- ArrayList groupTriggers = GetGroupTriggers(groupNumber);
- if (groupTriggers == null)
- return false;
- // Detectar si es un tipo híbrido (runpm, runbile, rungl)
- bool isHybrid = StrEqual(type, "runpm") || StrEqual(type, "runbile") || StrEqual(type, "rungl");
- // Determinar el arma requerida para híbridos
- char requiredWeapon[8] = "";
- if (StrEqual(type, "runpm")) strcopy(requiredWeapon, sizeof(requiredWeapon), "pm");
- else if (StrEqual(type, "runbile")) strcopy(requiredWeapon, sizeof(requiredWeapon), "bile");
- else if (StrEqual(type, "rungl")) strcopy(requiredWeapon, sizeof(requiredWeapon), "gl");
- for (int i = 0; i < groupTriggers.Length; i++)
- {
- int triggerIndex = groupTriggers.Get(i);
- if (StrEqual(g_sTriggerType[triggerIndex], type))
- {
- for (int client = 1; client <= MaxClients; client++)
- {
- if (!IsValidSurvivor(client))
- continue;
- // Si es híbrido, verificar si el jugador está en el trigger y tiene el arma
- if (isHybrid)
- {
- if (g_bPlayerInTrigger[client][triggerIndex] && HasRequiredWeapon(client, requiredWeapon))
- {
- DebugPrint("TypeHasReadyPlayers - Jugador %N con arma %s en trigger %d para tipo %s",
- client, requiredWeapon, triggerIndex, type);
- return true;
- }
- }
- else
- {
- // Para tipos normales (bile, pm, gl, run), usar g_bPlayerReady
- if (g_bPlayerReady[client][triggerIndex])
- {
- DebugPrint("TypeHasReadyPlayers - Jugador %N listo en trigger %d para tipo %s",
- client, triggerIndex, type);
- return true;
- }
- }
- }
- }
- }
- DebugPrint("TypeHasReadyPlayers - Tipo %s NO tiene jugadores listos", type);
- return false;
- }
- int CalculateGroupCapacity(int groupNumber)
- {
- int maxCapacity = 0;
- ArrayList groupTypes = GetGroupTypes(groupNumber);
- for (int i = 0; i < groupTypes.Length; i++)
- {
- char type[16];
- groupTypes.GetString(i, type, sizeof(type));
- int capacity = MA_GetBoosterCapacity(type);
- if (capacity > maxCapacity)
- {
- maxCapacity = capacity;
- }
- }
- return maxCapacity;
- }
- int CountReadyRunners(int groupNumber)
- {
- int count = 0;
- ArrayList groupTriggers = GetGroupTriggers(groupNumber);
- for (int i = 0; i < groupTriggers.Length; i++)
- {
- int triggerIndex = groupTriggers.Get(i);
- // Solo contar triggers de tipo "run" o híbridos
- char type[16];
- strcopy(type, sizeof(type), g_sTriggerType[triggerIndex]);
- if (StrEqual(type, "run") || StrContains(type, "run") != -1)
- {
- // Contar jugadores listos en este trigger
- for (int client = 1; client <= MaxClients; client++)
- {
- if (IsValidSurvivor(client) && g_bPlayerReady[client][triggerIndex])
- {
- count++;
- }
- }
- }
- }
- return count;
- }
- // ========================================================================
- // FUNCIÓN MODIFICADA: ACTIVAR GRUPO CON CAPACIDAD DE BOOSTER COMPLETA
- // ========================================================================
- // ========================================================================
- // FUNCIÓN MODIFICADA: ACTIVAR GRUPO CON CAPACIDAD DE BOOSTER COMPLETA
- // ========================================================================
- public void ActivateGroupInOrder(int groupNumber, int runnerLimit, ArrayList &activatedFiles)
- {
- DebugPrint("ActivateGroupInOrder - Activando grupo %d en orden (NUEVA VERSIÓN CON HÍBRIDOS)", groupNumber);
- if (activatedFiles == null)
- {
- activatedFiles = new ArrayList(ByteCountToCells(128));
- }
- // Obtener todos los tipos en este grupo
- ArrayList groupTypes = GetGroupTypes(groupNumber);
- if (groupTypes == null)
- {
- DebugPrint("ActivateGroupInOrder - ERROR: El grupo %d no tiene tipos", groupNumber);
- return;
- }
- // ================================================================
- // DETECTAR SI EL GRUPO TIENE HÍBRIDOS
- // ================================================================
- bool hasHybrid = false;
- for (int i = 0; i < groupTypes.Length; i++)
- {
- char type[16];
- groupTypes.GetString(i, type, sizeof(type));
- if (StrEqual(type, "runpm") || StrEqual(type, "runbile") || StrEqual(type, "rungl"))
- {
- hasHybrid = true;
- break;
- }
- }
- DebugPrint("ActivateGroupInOrder - Grupo %d tiene híbridos: %s", groupNumber, hasHybrid ? "SÍ" : "NO");
- // Determinar el tipo de booster y su capacidad
- char boosterType[16] = "";
- int boosterCapacity = 0;
- for (int i = 0; i < groupTypes.Length; i++)
- {
- char type[16];
- groupTypes.GetString(i, type, sizeof(type));
- if (StrEqual(type, "pm") || StrEqual(type, "bile") || StrEqual(type, "gl"))
- {
- strcopy(boosterType, sizeof(boosterType), type);
- boosterCapacity = MA_GetBoosterCapacity(type);
- DebugPrint("ActivateGroupInOrder - Booster encontrado: %s, Capacidad: %d runners", boosterType, boosterCapacity);
- break;
- }
- }
- if (strlen(boosterType) == 0)
- {
- DebugPrint("ActivateGroupInOrder - Grupo %d no tiene booster puro", groupNumber);
- // Si no hay booster puro, verificar si hay híbridos
- for (int i = 0; i < groupTypes.Length; i++)
- {
- char type[16];
- groupTypes.GetString(i, type, sizeof(type));
- if (StrEqual(type, "runpm") || StrEqual(type, "runbile") || StrEqual(type, "rungl"))
- {
- strcopy(boosterType, sizeof(boosterType), type);
- boosterCapacity = MA_GetBoosterCapacity(type);
- DebugPrint("ActivateGroupInOrder - Híbrido encontrado: %s, Capacidad: %d runners", boosterType, boosterCapacity);
- break;
- }
- }
- }
- // Si aún no encontramos capacidad, usar capacidad por defecto
- if (boosterCapacity == 0)
- {
- boosterCapacity = 1; // Capacidad por defecto
- DebugPrint("ActivateGroupInOrder - Usando capacidad por defecto: %d runners", boosterCapacity);
- }
- // Array para almacenar jugadores activados
- ArrayList activatedPlayers = new ArrayList();
- int totalRunnersActivated = 0;
- bool boosterActivated = false;
- ArrayList groupTriggers = GetGroupTriggers(groupNumber); // Necesario para buscar triggers
- // ================================================================
- // PASO 1: ACTIVAR HÍBRIDOS PRIMERO (runpm, runbile, rungl)
- // ================================================================
- char hybridTypes[][] = {"runpm", "runbile", "rungl"};
- int hybridsActivated = 0; // Contador de híbridos activados
- for (int h = 0; h < sizeof(hybridTypes) && totalRunnersActivated < boosterCapacity; h++)
- {
- // Obtener TODOS los jugadores en esta posición híbrida
- ArrayList allHybridPlayers = GetAllReadyPlayersInHybridPosition(groupNumber, hybridTypes[h]);
- if (allHybridPlayers != null && allHybridPlayers.Length > 0)
- {
- // Determinar qué arma requiere este híbrido
- char requiredWeapon[8];
- if (StrEqual(hybridTypes[h], "runpm")) strcopy(requiredWeapon, sizeof(requiredWeapon), "pm");
- else if (StrEqual(hybridTypes[h], "runbile")) strcopy(requiredWeapon, sizeof(requiredWeapon), "bile");
- else if (StrEqual(hybridTypes[h], "rungl")) strcopy(requiredWeapon, sizeof(requiredWeapon), "gl");
- else strcopy(requiredWeapon, sizeof(requiredWeapon), "pm");
- // ============================================================
- // VERIFICACIÓN CRÍTICA: ¿Al menos un jugador tiene el arma?
- // ============================================================
- bool hasArmedPlayer = false;
- int armedClient = -1;
- for (int i = 0; i < allHybridPlayers.Length; i++)
- {
- int client = allHybridPlayers.Get(i);
- if (HasRequiredWeapon(client, requiredWeapon))
- {
- hasArmedPlayer = true;
- armedClient = client;
- break;
- }
- }
- if (!hasArmedPlayer)
- {
- DebugPrint("ActivateGroupInOrder - Ningún jugador tiene %s en posición híbrida %s - saltando",
- requiredWeapon, hybridTypes[h]);
- // Mostrar mensaje de espera
- PrintToChatAll("[\x04MA Grupo (%d)\x01] Esperando un jugador con %s en posición híbrida...",
- groupNumber, requiredWeapon);
- delete allHybridPlayers;
- continue; // Saltar este híbrido
- }
- DebugPrint("ActivateGroupInOrder - Jugador %N tiene %s en posición híbrida %s - activando a TODOS",
- armedClient, requiredWeapon, hybridTypes[h]);
- // ============================================================
- // ACTIVAR A TODOS los jugadores en la posición híbrida
- // ============================================================
- for (int i = 0; i < allHybridPlayers.Length && totalRunnersActivated < boosterCapacity; i++)
- {
- int client = allHybridPlayers.Get(i);
- // Verificar que el jugador no esté ya en activación
- if (IsPlayerAlreadyInGroupActivation(groupNumber, client))
- {
- DebugPrint("ActivateGroupInOrder - %N ya está en activación del grupo %d - saltando", client, groupNumber);
- continue;
- }
- // ================================================================
- // NUEVO: Buscar un trigger de este tipo donde el jugador esté
- // Usamos g_bPlayerInTrigger, NO g_bPlayerReady
- // ================================================================
- int triggerIndex = -1;
- for (int j = 0; j < groupTriggers.Length; j++)
- {
- int idx = groupTriggers.Get(j);
- if (StrEqual(g_sTriggerType[idx], hybridTypes[h]) && g_bPlayerInTrigger[client][idx])
- {
- triggerIndex = idx;
- break;
- }
- }
- if (triggerIndex == -1)
- {
- DebugPrint("ActivateGroupInOrder - %N no está en trigger de tipo %s, saltando", client, hybridTypes[h]);
- continue;
- }
- char movementFile[128];
- GetMovementFileForTrigger(triggerIndex, movementFile, sizeof(movementFile));
- // Marcar jugador como en activación
- MarkPlayerInGroupActivation(groupNumber, client, true);
- // Almacenar para mensaje grupal
- activatedPlayers.Push(client);
- activatedFiles.PushString(movementFile);
- // EJECUTAR MOVIMIENTO - TODOS los jugadores en posición híbrida son runners
- ExecutePlayerMovementImmediate(client, movementFile, MSG_GROUP, groupNumber);
- totalRunnersActivated++;
- hybridsActivated++;
- DebugPrint("ActivateGroupInOrder - Híbrido %N activado como runner (%d/%d)",
- client, totalRunnersActivated, boosterCapacity);
- }
- }
- delete allHybridPlayers;
- }
- // ================================================================
- // SI EL GRUPO TIENE HÍBRIDOS Y NO SE ACTIVÓ NINGUNO, NO CONTINUAR
- // ================================================================
- if (hasHybrid && hybridsActivated == 0)
- {
- DebugPrint("ActivateGroupInOrder - Grupo %d tiene híbridos pero no se activó ninguno. No se activan boosters puros.", groupNumber);
- delete activatedPlayers;
- return;
- }
- // ================================================================
- // PASO 2: ACTIVAR RUNNERS PUROS ADICIONALES
- // ================================================================
- if (totalRunnersActivated < boosterCapacity)
- {
- ArrayList runners = GetReadyPlayersByType(groupNumber, "run");
- if (runners != null && runners.Length > 0)
- {
- DebugPrint("ActivateGroupInOrder - Encontrados %d runners puros adicionales", runners.Length);
- for (int i = 0; i < runners.Length && totalRunnersActivated < boosterCapacity; i++)
- {
- int client = runners.Get(i);
- // Verificar que el jugador no esté ya en activación
- if (IsPlayerAlreadyInGroupActivation(groupNumber, client))
- {
- DebugPrint("ActivateGroupInOrder - %N ya está en activación del grupo %d - saltando", client, groupNumber);
- continue;
- }
- int triggerIndex = FindReadyTriggerForPlayer(client, "run");
- if (triggerIndex != -1)
- {
- char movementFile[128];
- GetMovementFileForTrigger(triggerIndex, movementFile, sizeof(movementFile));
- // Marcar jugador como en activación
- MarkPlayerInGroupActivation(groupNumber, client, true);
- // Almacenar para mensaje grupal
- activatedPlayers.Push(client);
- activatedFiles.PushString(movementFile);
- // Ejecutar movimiento del runner
- ExecutePlayerMovementImmediate(client, movementFile, MSG_GROUP, groupNumber);
- totalRunnersActivated++;
- DebugPrint("ActivateGroupInOrder - Runner puro adicional %N activado (%d/%d)",
- client, totalRunnersActivated, boosterCapacity);
- }
- }
- }
- delete runners;
- }
- // ================================================================
- // PASO 3: ACTIVAR BOOSTERS PUROS (pm, bile, gl) - ÚLTIMO
- // ================================================================
- char boosterTypes[][] = {"pm", "bile", "gl"};
- for (int b = 0; b < sizeof(boosterTypes) && !boosterActivated; b++)
- {
- ArrayList boosters = GetReadyPlayersByType(groupNumber, boosterTypes[b]);
- if (boosters != null && boosters.Length > 0)
- {
- DebugPrint("ActivateGroupInOrder - Encontrados %d boosters tipo %s",
- boosters.Length, boosterTypes[b]);
- // Activar solo UN booster (el primero disponible)
- for (int i = 0; i < boosters.Length && !boosterActivated; i++)
- {
- int client = boosters.Get(i);
- // Verificar que el jugador no esté ya en activación
- if (IsPlayerAlreadyInGroupActivation(groupNumber, client))
- {
- DebugPrint("ActivateGroupInOrder - %N ya está en activación del grupo %d - saltando", client, groupNumber);
- continue;
- }
- int triggerIndex = FindReadyTriggerForPlayer(client, boosterTypes[b]);
- if (triggerIndex != -1)
- {
- char movementFile[128];
- GetMovementFileForTrigger(triggerIndex, movementFile, sizeof(movementFile));
- // Marcar jugador como en activación
- MarkPlayerInGroupActivation(groupNumber, client, true);
- // Almacenar para mensaje grupal
- activatedPlayers.Push(client);
- activatedFiles.PushString(movementFile);
- // EJECUTAR MOVIMIENTO DEL BOOSTER - ÚLTIMO
- ExecutePlayerMovementImmediate(client, movementFile, MSG_GROUP, groupNumber);
- boosterActivated = true;
- DebugPrint("ActivateGroupInOrder - Booster %N activado para impulsar %d runners",
- client, totalRunnersActivated);
- }
- }
- }
- delete boosters;
- }
- // Si no se activó ningún booster pero hay runners, mostrar mensaje
- if (!boosterActivated && totalRunnersActivated > 0)
- {
- DebugPrint("ActivateGroupInOrder - ¡ADVERTENCIA! Se activaron %d runners pero NO se activó ningún booster",
- totalRunnersActivated);
- }
- DebugPrint("ActivateGroupInOrder - RESUMEN FINAL: %d runners activados (capacidad: %d), Booster: %s",
- totalRunnersActivated, boosterCapacity, boosterActivated ? "SÍ" : "NO");
- delete activatedPlayers;
- }
- // ========================================================================
- // FUNCIÓN CORREGIDA: OBTENER JUGADORES LISTOS POR TIPO (MEJORADA)
- // ========================================================================
- ArrayList GetReadyPlayersByType(int groupNumber, const char[] type)
- {
- ArrayList players = new ArrayList();
- ArrayList groupTriggers = GetGroupTriggers(groupNumber);
- if (groupTriggers == null)
- return players;
- // Primera pasada: agregar todos los jugadores listos para este tipo
- for (int i = 0; i < groupTriggers.Length; i++)
- {
- int triggerIndex = groupTriggers.Get(i);
- // Verificar si este trigger es del tipo buscado
- if (StrEqual(g_sTriggerType[triggerIndex], type))
- {
- // Agregar jugadores listos en este trigger
- for (int client = 1; client <= MaxClients; client++)
- {
- if (IsValidSurvivor(client) && g_bPlayerReady[client][triggerIndex])
- {
- // Verificar que el jugador no esté ya en la lista
- if (players.FindValue(client) == -1)
- {
- players.Push(client);
- }
- }
- }
- }
- }
- return players;
- }
- // ========================================================================
- // NUEVA FUNCIÓN: OBTENER TODOS LOS JUGADORES EN POSICIÓN HÍBRIDA
- // ========================================================================
- /**
- * Obtiene TODOS los jugadores en una posición híbrida (runpm, runbile, rungl)
- * Siempre y cuando al menos uno de ellos tenga el arma requerida.
- * Esto incluye tanto al que tiene el arma como a los que no la tienen (runners adicionales)
- */
- ArrayList GetAllReadyPlayersInHybridPosition(int groupNumber, const char[] hybridType)
- {
- ArrayList allPlayers = new ArrayList();
- ArrayList groupTriggers = GetGroupTriggers(groupNumber);
- if (groupTriggers == null)
- return allPlayers;
- char requiredWeapon[8];
- if (StrEqual(hybridType, "runpm")) strcopy(requiredWeapon, sizeof(requiredWeapon), "pm");
- else if (StrEqual(hybridType, "runbile")) strcopy(requiredWeapon, sizeof(requiredWeapon), "bile");
- else if (StrEqual(hybridType, "rungl")) strcopy(requiredWeapon, sizeof(requiredWeapon), "gl");
- else
- {
- DebugPrint("GetAllReadyPlayersInHybridPosition - Tipo híbrido desconocido: %s", hybridType);
- return allPlayers;
- }
- bool hasArmed = false;
- ArrayList tempPlayers = new ArrayList();
- for (int i = 0; i < groupTriggers.Length; i++)
- {
- int triggerIndex = groupTriggers.Get(i);
- if (!StrEqual(g_sTriggerType[triggerIndex], hybridType))
- continue;
- int triggerEnt = EntRefToEntIndex(g_iTriggers[triggerIndex]);
- if (triggerEnt == INVALID_ENT_REFERENCE)
- continue;
- // Obtener la caja de colisión del trigger (con un margen de tolerancia)
- float origin[3], mins[3], maxs[3];
- GetEntPropVector(triggerEnt, Prop_Send, "m_vecOrigin", origin);
- GetEntPropVector(triggerEnt, Prop_Send, "m_vecMins", mins);
- GetEntPropVector(triggerEnt, Prop_Send, "m_vecMaxs", maxs);
- // Añadir un margen de 5 unidades para evitar errores de redondeo
- float tolerance = 5.0;
- float worldMin[3], worldMax[3];
- worldMin[0] = origin[0] + mins[0] - tolerance;
- worldMin[1] = origin[1] + mins[1] - tolerance;
- worldMin[2] = origin[2] + mins[2] - tolerance;
- worldMax[0] = origin[0] + maxs[0] + tolerance;
- worldMax[1] = origin[1] + maxs[1] + tolerance;
- worldMax[2] = origin[2] + maxs[2] + tolerance;
- for (int client = 1; client <= MaxClients; client++)
- {
- if (!IsValidSurvivor(client))
- continue;
- // PRIMERO: confiar en g_bPlayerInTrigger (más fiable)
- bool inside = g_bPlayerInTrigger[client][triggerIndex];
- // SI no está registrado, verificar físicamente con tolerancia
- if (!inside)
- {
- float clientPos[3];
- GetClientAbsOrigin(client, clientPos);
- inside = (clientPos[0] >= worldMin[0] && clientPos[0] <= worldMax[0] &&
- clientPos[1] >= worldMin[1] && clientPos[1] <= worldMax[1] &&
- clientPos[2] >= worldMin[2] && clientPos[2] <= worldMax[2]);
- if (inside)
- {
- // Si está físicamente pero no registrado, lo registramos para futuras ocasiones
- g_bPlayerInTrigger[client][triggerIndex] = true;
- g_bPlayerReady[client][triggerIndex] = false;
- g_iPlayerRetryCount[client][triggerIndex] = 0;
- g_bWaitingForWeapon[client][triggerIndex] = false;
- DebugPrint("GetAllReadyPlayersInHybridPosition - Jugador %N detectado físicamente en trigger %d", client, triggerIndex);
- }
- }
- if (!inside)
- continue;
- // Verificar salud y que no esté en movimiento
- if (!CheckPlayerHealthRules(client) || g_bClientInMovement[client])
- continue;
- // Agregar a la lista temporal (evitar duplicados)
- if (tempPlayers.FindValue(client) == -1)
- {
- tempPlayers.Push(client);
- DebugPrint("GetAllReadyPlayersInHybridPosition - Jugador %N agregado (arma: %s)",
- client, HasRequiredWeapon(client, requiredWeapon) ? "SÍ" : "NO");
- }
- // Marcar si este jugador tiene el arma requerida
- if (HasRequiredWeapon(client, requiredWeapon))
- hasArmed = true;
- }
- }
- // Si no hay ningún jugador con el arma, devolver lista vacía
- if (!hasArmed)
- {
- DebugPrint("GetAllReadyPlayersInHybridPosition - No hay jugador con %s en posición híbrida %s", requiredWeapon, hybridType);
- delete tempPlayers;
- return allPlayers;
- }
- // Copiar todos los jugadores al resultado
- for (int i = 0; i < tempPlayers.Length; i++)
- allPlayers.Push(tempPlayers.Get(i));
- delete tempPlayers;
- DebugPrint("GetAllReadyPlayersInHybridPosition - Devolviendo %d jugadores en posición híbrida %s (al menos uno armado)", allPlayers.Length, hybridType);
- return allPlayers;
- }
- // ========================================================================
- // NUEVA FUNCIÓN: OBTENER TODOS LOS RUNNERS DISPONIBLES EN EL GRUPO
- // ========================================================================
- ArrayList GetAllRunnersInGroup(int groupNumber)
- {
- ArrayList allRunners = new ArrayList();
- // Buscar runners puros
- ArrayList pureRunners = GetReadyPlayersByType(groupNumber, "run");
- for (int i = 0; i < pureRunners.Length; i++)
- {
- int client = pureRunners.Get(i);
- if (allRunners.FindValue(client) == -1)
- {
- allRunners.Push(client);
- }
- }
- delete pureRunners;
- // Buscar híbridos (también son runners)
- char hybridTypes[][] = {"runpm", "runbile", "rungl"};
- for (int h = 0; h < sizeof(hybridTypes); h++)
- {
- ArrayList hybrids = GetReadyPlayersByType(groupNumber, hybridTypes[h]);
- for (int i = 0; i < hybrids.Length; i++)
- {
- int client = hybrids.Get(i);
- if (allRunners.FindValue(client) == -1)
- {
- allRunners.Push(client);
- }
- }
- delete hybrids;
- }
- return allRunners;
- }
- // ========================================================================
- // NUEVA FUNCIÓN: VERIFICAR CAPACIDAD DEL GRUPO
- // ========================================================================
- int GetGroupBoosterCapacity(int groupNumber)
- {
- ArrayList groupTypes = GetGroupTypes(groupNumber);
- if (groupTypes == null)
- return 1; // Capacidad por defecto
- int maxCapacity = 1;
- for (int i = 0; i < groupTypes.Length; i++)
- {
- char type[16];
- groupTypes.GetString(i, type, sizeof(type));
- // Verificar si es un booster puro
- if (StrEqual(type, "pm") || StrEqual(type, "bile") || StrEqual(type, "gl"))
- {
- int capacity = MA_GetBoosterCapacity(type);
- if (capacity > maxCapacity)
- {
- maxCapacity = capacity;
- }
- }
- }
- // Si no encontramos booster puro, verificar híbridos
- if (maxCapacity == 1)
- {
- for (int i = 0; i < groupTypes.Length; i++)
- {
- char type[16];
- groupTypes.GetString(i, type, sizeof(type));
- if (StrEqual(type, "runpm") || StrEqual(type, "runbile") || StrEqual(type, "rungl"))
- {
- int capacity = MA_GetBoosterCapacity(type);
- if (capacity > maxCapacity)
- {
- maxCapacity = capacity;
- }
- }
- }
- }
- DebugPrint("GetGroupBoosterCapacity - Grupo %d - Capacidad máxima: %d runners", groupNumber, maxCapacity);
- return maxCapacity;
- }
- // ========================================================================
- // NUEVAS FUNCIONES PARA ANÁLISIS DE ARCHIVOS (IMPLEMENTACIÓN COMPLETA)
- // ========================================================================
- /**
- * Analiza los archivos que se activarán en un grupo
- */
- public bool AnalyzeActivatedFiles(int groupNumber, ArrayList files, GroupAnalysis analysis)
- {
- // Inicializar estructura
- analysis.hasPureRunner = false;
- analysis.pureRunnerCount = 0;
- analysis.hybridPMCount = 0;
- analysis.hybridBileCount = 0;
- analysis.hybridGLCount = 0;
- analysis.boosterPMCount = 0;
- analysis.boosterBileCount = 0;
- analysis.boosterGLCount = 0;
- analysis.uniqueFileNames = new ArrayList(ByteCountToCells(128));
- analysis.uniqueFileDurations = new ArrayList();
- // Analizar cada archivo
- for (int i = 0; i < files.Length; i++)
- {
- char fileName[128];
- files.GetString(i, fileName, sizeof(fileName));
- // Obtener nombre base del archivo
- char baseName[64];
- GetBaseFileName(fileName, baseName, sizeof(baseName));
- // Clasificar por tipo usando la información del archivo
- MovementFileInfo info;
- if (MA_GetMovementFileInfo(fileName, g_hMovementFileInfos, info))
- {
- // Usar if-else if en lugar de switch para evitar problemas de sintaxis en SourcePawn
- if (info.movType == MOVEMENT_RUN)
- {
- analysis.hasPureRunner = true;
- analysis.pureRunnerCount++;
- DebugPrint("AnalyzeActivatedFiles - Archivo %s -> Runner Puro", baseName);
- }
- else if (info.movType == MOVEMENT_RUNPM)
- {
- analysis.hybridPMCount++;
- DebugPrint("AnalyzeActivatedFiles - Archivo %s -> Híbrido PM", baseName);
- }
- else if (info.movType == MOVEMENT_RUNBILE)
- {
- analysis.hybridBileCount++;
- DebugPrint("AnalyzeActivatedFiles - Archivo %s -> Híbrido Bile", baseName);
- }
- else if (info.movType == MOVEMENT_RUNGL)
- {
- analysis.hybridGLCount++;
- DebugPrint("AnalyzeActivatedFiles - Archivo %s -> Híbrido GL", baseName);
- }
- else if (info.movType == MOVEMENT_PM)
- {
- analysis.boosterPMCount++;
- DebugPrint("AnalyzeActivatedFiles - Archivo %s -> Booster PM Puro", baseName);
- }
- else if (info.movType == MOVEMENT_BILE)
- {
- analysis.boosterBileCount++;
- DebugPrint("AnalyzeActivatedFiles - Archivo %s -> Booster Bile Puro", baseName);
- }
- else if (info.movType == MOVEMENT_GL)
- {
- analysis.boosterGLCount++;
- DebugPrint("AnalyzeActivatedFiles - Archivo %s -> Booster GL Puro", baseName);
- }
- else
- {
- DebugPrint("AnalyzeActivatedFiles - Archivo %s -> Tipo desconocido: %d", baseName, info.movType);
- }
- // Agregar a lista única
- analysis.uniqueFileNames.PushString(baseName);
- // Obtener duración
- float duration = 10.0;
- if (g_hMovementDurations != null)
- g_hMovementDurations.GetValue(fileName, duration);
- analysis.uniqueFileDurations.Push(duration);
- }
- else
- {
- // Fallback: clasificar por nombre
- if (StrContains(baseName, "runpm") != -1)
- {
- analysis.hybridPMCount++;
- DebugPrint("AnalyzeActivatedFiles - %s -> Híbrido PM (por nombre)", baseName);
- }
- else if (StrContains(baseName, "runbile") != -1)
- {
- analysis.hybridBileCount++;
- DebugPrint("AnalyzeActivatedFiles - %s -> Híbrido Bile (por nombre)", baseName);
- }
- else if (StrContains(baseName, "rungl") != -1)
- {
- analysis.hybridGLCount++;
- DebugPrint("AnalyzeActivatedFiles - %s -> Híbrido GL (por nombre)", baseName);
- }
- else if (StrContains(baseName, "pm") != -1 && StrContains(baseName, "runpm") == -1)
- {
- analysis.boosterPMCount++;
- DebugPrint("AnalyzeActivatedFiles - %s -> Booster PM Puro (por nombre)", baseName);
- }
- else if (StrContains(baseName, "bile") != -1 && StrContains(baseName, "runbile") == -1)
- {
- analysis.boosterBileCount++;
- DebugPrint("AnalyzeActivatedFiles - %s -> Booster Bile Puro (por nombre)", baseName);
- }
- else if (StrContains(baseName, "gl") != -1 && StrContains(baseName, "rungl") == -1)
- {
- analysis.boosterGLCount++;
- DebugPrint("AnalyzeActivatedFiles - %s -> Booster GL Puro (por nombre)", baseName);
- }
- else if (StrContains(baseName, "run") != -1)
- {
- analysis.hasPureRunner = true;
- analysis.pureRunnerCount++;
- DebugPrint("AnalyzeActivatedFiles - %s -> Runner Puro (por nombre)", baseName);
- }
- analysis.uniqueFileNames.PushString(baseName);
- float duration = 10.0;
- if (g_hMovementDurations != null)
- g_hMovementDurations.GetValue(fileName, duration);
- analysis.uniqueFileDurations.Push(duration);
- }
- }
- // Resumen del análisis
- DebugPrint("AnalyzeActivatedFiles RESUMEN - Grupo %d:", groupNumber);
- DebugPrint(" Runners Puros: %d", analysis.pureRunnerCount);
- DebugPrint(" Híbridos PM: %d", analysis.hybridPMCount);
- DebugPrint(" Híbridos Bile: %d", analysis.hybridBileCount);
- DebugPrint(" Híbridos GL: %d", analysis.hybridGLCount);
- DebugPrint(" Boosters PM: %d", analysis.boosterPMCount);
- DebugPrint(" Boosters Bile: %d", analysis.boosterBileCount);
- DebugPrint(" Boosters GL: %d", analysis.boosterGLCount);
- return true;
- }
- /**
- * Valida si la combinación de archivos es válida
- */
- public bool ValidateGroupComposition(const GroupAnalysis analysis, char[] errorMsg, int errorLen)
- {
- // Calcular totales por tipo
- int totalPM = analysis.hybridPMCount + analysis.boosterPMCount;
- int totalBile = analysis.hybridBileCount + analysis.boosterBileCount;
- int totalGL = analysis.hybridGLCount + analysis.boosterGLCount;
- int totalRunners = analysis.pureRunnerCount + analysis.hybridPMCount + analysis.hybridBileCount + analysis.hybridGLCount;
- int totalBoosters = analysis.boosterPMCount + analysis.boosterBileCount + analysis.boosterGLCount;
- DebugPrint("ValidateGroupComposition - Total PM: %d, Total Bile: %d, Total GL: %d", totalPM, totalBile, totalGL);
- DebugPrint("ValidateGroupComposition - Total Runners: %d, Total Boosters: %d", totalRunners, totalBoosters);
- // REGLA 1: Debe haber al menos 1 runner
- if (totalRunners == 0)
- {
- Format(errorMsg, errorLen, "No hay runners en la fórmula. Se necesita al menos 1 runner puro o híbrido.");
- return false;
- }
- // REGLA 2: Si hay boosters puros, debe haber runner puro o híbrido
- if (totalBoosters > 0 && totalRunners == 0)
- {
- Format(errorMsg, errorLen, "Los boosters puros requieren al menos 1 runner.");
- return false;
- }
- // REGLA 3: Runner puro es opcional si hay híbridos
- // (Ya está implícito en el cálculo de totalRunners)
- // REGLA 4: Validar capacidad según el booster de mayor capacidad
- int maxCapacity = 0;
- if (totalPM > 0)
- {
- int capPM = GetBoosterCapacityNew("PM");
- maxCapacity = (capPM > maxCapacity) ? capPM : maxCapacity;
- }
- if (totalBile > 0)
- {
- int capBile = GetBoosterCapacityNew("Bile");
- maxCapacity = (capBile > maxCapacity) ? capBile : maxCapacity;
- }
- if (totalGL > 0)
- {
- int capGL = GetBoosterCapacityNew("GL");
- maxCapacity = (capGL > maxCapacity) ? capGL : maxCapacity;
- }
- // Si no hay boosters ni híbridos (solo runners puros), capacidad máxima 32
- if (maxCapacity == 0) maxCapacity = 32;
- DebugPrint("ValidateGroupComposition - Capacidad máxima permitida: %d runners", maxCapacity);
- // La validación de capacidad se hace en otra parte del código
- // Aquí solo validamos la composición básica
- return true;
- }
- /**
- * Genera el nombre para mostrar del grupo
- */
- public void GenerateGroupDisplayName(const GroupAnalysis analysis, int playerCount, char[] buffer, int bufferLen)
- {
- // CASO 1: Solo 1 jugador
- if (playerCount == 1)
- {
- strcopy(buffer, bufferLen, "Solo");
- return;
- }
- // Calcular totales por tipo
- int totalPM = analysis.hybridPMCount + analysis.boosterPMCount;
- int totalBile = analysis.hybridBileCount + analysis.boosterBileCount;
- int totalGL = analysis.hybridGLCount + analysis.boosterGLCount;
- // Array para tipos con sus contadores
- char types[3][16];
- int counts[3];
- int typeCount = 0;
- // PM
- if (totalPM > 0)
- {
- strcopy(types[typeCount], 16, "PM");
- counts[typeCount] = totalPM;
- typeCount++;
- }
- // Bile
- if (totalBile > 0)
- {
- strcopy(types[typeCount], 16, "Bile");
- counts[typeCount] = totalBile;
- typeCount++;
- }
- // GL
- if (totalGL > 0)
- {
- strcopy(types[typeCount], 16, "GL");
- counts[typeCount] = totalGL;
- typeCount++;
- }
- // Ordenar por capacidad (PM > Bile > GL)
- for (int i = 0; i < typeCount - 1; i++)
- {
- for (int j = i + 1; j < typeCount; j++)
- {
- int capacityI = GetBoosterCapacityNew(types[i]);
- int capacityJ = GetBoosterCapacityNew(types[j]);
- if (capacityJ > capacityI ||
- (capacityJ == capacityI && StrCompare(types[j], types[i], false) < 0))
- {
- // Intercambiar
- char tempType[16];
- strcopy(tempType, sizeof(tempType), types[i]);
- strcopy(types[i], 16, types[j]);
- strcopy(types[j], 16, tempType);
- int tempCount = counts[i];
- counts[i] = counts[j];
- counts[j] = tempCount;
- }
- }
- }
- // Construir prefijo según cantidad de jugadores
- char prefix[16];
- if (playerCount == 2)
- {
- strcopy(prefix, sizeof(prefix), "Dual");
- }
- else if (playerCount == 3)
- {
- strcopy(prefix, sizeof(prefix), "Triple");
- }
- else if (playerCount == 4)
- {
- strcopy(prefix, sizeof(prefix), "Quad");
- }
- else
- {
- strcopy(prefix, sizeof(prefix), "Grupo");
- }
- // Construir nombre completo
- if (typeCount == 0)
- {
- // Solo runners puros
- Format(buffer, bufferLen, "%s", prefix);
- }
- else
- {
- Format(buffer, bufferLen, "%s-", prefix);
- for (int i = 0; i < typeCount; i++)
- {
- if (i > 0) StrCat(buffer, bufferLen, "/");
- // Mostrar contador solo si hay más de 1 y estamos en Triple o más
- if (counts[i] > 1 && playerCount >= 3)
- {
- char typePart[32];
- Format(typePart, sizeof(typePart), "(%d)%s", counts[i], types[i]);
- StrCat(buffer, bufferLen, typePart);
- }
- else
- {
- StrCat(buffer, bufferLen, types[i]);
- }
- // Limitar a 3 tipos para nombres largos
- if (playerCount == 3 && i >= 2) break;
- }
- }
- DebugPrint("GenerateGroupDisplayName - Generado: %s para %d jugadores", buffer, playerCount);
- }
- /**
- * Obtiene el nombre base del archivo (sin path ni extensión)
- */
- public void GetBaseFileName(const char[] fullPath, char[] baseName, int maxlen)
- {
- // Copiar la cadena completa primero
- strcopy(baseName, maxlen, fullPath);
- // Eliminar extensión .txt si existe
- int len = strlen(baseName);
- if (len > 4 && StrEqual(baseName[len-4], ".txt", false))
- {
- baseName[len-4] = '\0';
- }
- // Eliminar path si existe
- int slashPos = FindCharInString(baseName, '/', true);
- if (slashPos == -1) slashPos = FindCharInString(baseName, '\\', true);
- if (slashPos != -1)
- {
- char temp[128];
- strcopy(temp, sizeof(temp), baseName[slashPos+1]);
- strcopy(baseName, maxlen, temp);
- }
- }
- /**
- * Obtiene la capacidad de un booster (nueva función)
- */
- public int GetBoosterCapacityNew(const char[] boosterType)
- {
- if (StrEqual(boosterType, "PM", false))
- return 32;
- else if (StrEqual(boosterType, "Bile", false))
- return 5;
- else if (StrEqual(boosterType, "GL", false))
- return 1;
- return 1; // Default
- }
- /**
- * Ordena archivos para mostrar en mensaje público
- */
- public void SortFilesForDisplay(ArrayList files, ArrayList durations, ArrayList sortedFiles, ArrayList sortedDurations)
- {
- if (files == null || files.Length == 0)
- return;
- // Crear arrays temporales
- ArrayList tempFiles = new ArrayList(ByteCountToCells(128));
- ArrayList tempDurations = new ArrayList();
- // Copiar datos
- for (int i = 0; i < files.Length; i++)
- {
- char file[128];
- files.GetString(i, file, sizeof(file));
- tempFiles.PushString(file);
- tempDurations.Push(durations.Get(i));
- }
- // Ordenar: primero runners puros, luego híbridos, luego boosters puros
- for (int i = 0; i < tempFiles.Length; i++)
- {
- char file[128];
- tempFiles.GetString(i, file, sizeof(file));
- char baseName[64];
- GetBaseFileName(file, baseName, sizeof(baseName));
- // Determinar tipo
- bool isRunner = (StrContains(baseName, "run") != -1 &&
- StrContains(baseName, "runpm") == -1 &&
- StrContains(baseName, "runbile") == -1 &&
- StrContains(baseName, "rungl") == -1);
- bool isHybrid = (StrContains(baseName, "runpm") != -1 ||
- StrContains(baseName, "runbile") != -1 ||
- StrContains(baseName, "rungl") != -1);
- bool isBooster = (!isRunner && !isHybrid &&
- (StrContains(baseName, "pm") != -1 ||
- StrContains(baseName, "bile") != -1 ||
- StrContains(baseName, "gl") != -1));
- // Agregar en orden
- if (isRunner)
- {
- sortedFiles.PushString(file);
- sortedDurations.Push(tempDurations.Get(i));
- }
- }
- // Agregar híbridos
- for (int i = 0; i < tempFiles.Length; i++)
- {
- char file[128];
- tempFiles.GetString(i, file, sizeof(file));
- char baseName[64];
- GetBaseFileName(file, baseName, sizeof(baseName));
- bool isHybrid = (StrContains(baseName, "runpm") != -1 ||
- StrContains(baseName, "runbile") != -1 ||
- StrContains(baseName, "rungl") != -1);
- if (isHybrid)
- {
- sortedFiles.PushString(file);
- sortedDurations.Push(tempDurations.Get(i));
- }
- }
- // Agregar boosters puros
- for (int i = 0; i < tempFiles.Length; i++)
- {
- char file[128];
- tempFiles.GetString(i, file, sizeof(file));
- char baseName[64];
- GetBaseFileName(file, baseName, sizeof(baseName));
- bool isBooster = (StrContains(baseName, "pm") != -1 && StrContains(baseName, "runpm") == -1) ||
- (StrContains(baseName, "bile") != -1 && StrContains(baseName, "runbile") == -1) ||
- (StrContains(baseName, "gl") != -1 && StrContains(baseName, "rungl") == -1);
- if (isBooster)
- {
- sortedFiles.PushString(file);
- sortedDurations.Push(tempDurations.Get(i));
- }
- }
- delete tempFiles;
- delete tempDurations;
- }
- /**
- * Formatea el mensaje público con archivos y duraciones
- */
- public void FormatPublicMessage(const char[] groupName, int groupNumber, ArrayList files,
- ArrayList durations, char[] buffer, int bufferLen)
- {
- // Base del mensaje
- char base[128];
- // Determinar prefijo basado en cantidad de archivos
- if (files.Length == 1)
- {
- Format(base, sizeof(base), "[\x04MA %s\x01] ", groupName);
- }
- else
- {
- Format(base, sizeof(base), "[\x04MA %s (%d)\x01] ", groupName, groupNumber);
- }
- // Concatenar archivos con duraciones
- char filePart[512];
- filePart[0] = '\0';
- for (int i = 0; i < files.Length; i++)
- {
- char fileName[64];
- files.GetString(i, fileName, sizeof(fileName));
- float duration = durations.Get(i);
- // Obtener nombre de visualización
- char displayName[64];
- GetBaseFileName(fileName, displayName, sizeof(displayName));
- char temp[128];
- Format(temp, sizeof(temp), "%s(%.1fs)", displayName, duration);
- if (i == 0)
- strcopy(filePart, sizeof(filePart), temp);
- else
- Format(filePart, sizeof(filePart), "%s + %s", filePart, temp);
- }
- // Combinar todo
- Format(buffer, bufferLen, "%s%s", base, filePart);
- DebugPrint("FormatPublicMessage - %s", buffer);
- }
- // ========================================================================
- // FUNCIÓN MODIFICADA: ShowGroupActivationSummary
- // ========================================================================
- public void ShowGroupActivationSummary(int groupNumber, ArrayList players, ArrayList files)
- {
- // 1. Validar entrada
- if (players == null || files == null || players.Length == 0 || files.Length == 0)
- return;
- DebugPrint("ShowGroupActivationSummary - Mostrando resumen para grupo %d con %d jugadores y %d archivos",
- groupNumber, players.Length, files.Length);
- // 2. Prevenir mensajes duplicados
- static int lastGroupActivation = 0;
- static float lastActivationTime = 0.0;
- float currentTime = GetGameTime();
- if (lastGroupActivation == groupNumber && (currentTime - lastActivationTime) < 0.5)
- {
- DebugPrint("ShowGroupActivationSummary - Evitando mensaje duplicado para grupo %d", groupNumber);
- return;
- }
- lastGroupActivation = groupNumber;
- lastActivationTime = currentTime;
- // 3. Analizar archivos
- GroupAnalysis analysis;
- if (!AnalyzeActivatedFiles(groupNumber, files, analysis))
- {
- DebugPrint("ShowGroupActivationSummary - No se pudo analizar archivos del grupo %d", groupNumber);
- return;
- }
- // 4. Validar composición
- char errorMsg[128];
- if (!ValidateGroupComposition(analysis, errorMsg, sizeof(errorMsg)))
- {
- // Mostrar error a los jugadores involucrados
- for (int i = 0; i < players.Length; i++)
- {
- int client = players.Get(i);
- if (IsValidSurvivor(client))
- {
- PrintToChat(client, "[\x04Error\x01] %s", errorMsg);
- }
- }
- // Limpiar análisis
- delete analysis.uniqueFileNames;
- delete analysis.uniqueFileDurations;
- return;
- }
- // 5. Generar nombre del grupo
- char groupName[64];
- GenerateGroupDisplayName(analysis, players.Length, groupName, sizeof(groupName));
- DebugPrint("ShowGroupActivationSummary - Nombre generado: %s", groupName);
- // 6. Obtener duraciones de archivos y nombres base (sin "_autor")
- ArrayList durations = new ArrayList();
- ArrayList baseFileNames = new ArrayList(ByteCountToCells(64));
- for (int i = 0; i < files.Length; i++)
- {
- char file[128];
- files.GetString(i, file, sizeof(file));
- // Obtener nombre base sin autor (ej: "7run_Tonblader" -> "7run")
- char baseName[64];
- GetBaseFileNameWithoutAuthor(file, baseName, sizeof(baseName));
- baseFileNames.PushString(baseName);
- // Obtener duración
- float duration = 10.0;
- if (g_hMovementDurations != null)
- g_hMovementDurations.GetValue(file, duration);
- durations.Push(duration);
- }
- // 7. Obtener autores únicos (para mensaje opcional)
- ArrayList uniqueAuthors = new ArrayList(ByteCountToCells(32));
- for (int i = 0; i < files.Length; i++)
- {
- char file[128];
- files.GetString(i, file, sizeof(file));
- // Extraer autor del nombre del archivo
- char author[32];
- if (ExtractAuthorFromFileName(file, author, sizeof(author)))
- {
- if (uniqueAuthors.FindString(author) == -1)
- {
- uniqueAuthors.PushString(author);
- }
- }
- }
- // 8. Mensaje de autores (OPCIONAL - solo si hay autores diferentes)
- if (uniqueAuthors.Length > 0)
- {
- // Construir lista de autores formateada
- char authorsFormatted[256];
- FormatAuthorsList(uniqueAuthors, authorsFormatted, sizeof(authorsFormatted));
- // Mostrar mensaje de autores solo si no está vacío
- if (strlen(authorsFormatted) > 0)
- {
- PrintToChatAll("[\x04MA %s (%d)\x01] Movimientos Creados por %s",
- groupName, groupNumber, authorsFormatted);
- }
- }
- // 9. Mensaje público con archivos y duraciones (para TODOS)
- char publicMessage[512];
- FormatSimplePublicMessage(groupName, groupNumber, baseFileNames, durations, publicMessage, sizeof(publicMessage));
- PrintToChatAll("%s", publicMessage);
- // 10. Mensaje personal a los jugadores involucrados
- char privateMessage[128];
- Format(privateMessage, sizeof(privateMessage), "[\x04MA %s (%d)\x01] (%d) Jugadores en movimiento...",
- groupName, groupNumber, players.Length);
- for (int i = 0; i < players.Length; i++)
- {
- int client = players.Get(i);
- if (IsValidSurvivor(client))
- {
- PrintToChat(client, "%s", privateMessage);
- }
- }
- // 11. Limpiar memoria
- delete durations;
- delete baseFileNames;
- delete uniqueAuthors;
- delete analysis.uniqueFileNames;
- delete analysis.uniqueFileDurations;
- DebugPrint("ShowGroupActivationSummary - Resumen completado para grupo %d", groupNumber);
- }
- // ========================================================================
- // SISTEMA DE REFIRE_TIME MEJORADO - LÓGICA V15
- // ========================================================================
- void ApplyRefireTime(int triggerIndex)
- {
- if (triggerIndex < 0 || triggerIndex >= MAX_TRIGGERS)
- return;
- // Actualizar tiempos de activación
- g_fLastActivation[triggerIndex] = GetGameTime();
- g_fNextActivationTime[triggerIndex] = GetGameTime() + REFIRE_TIME;
- DebugPrint("ApplyRefireTime - Trigger %d - Cooldown activado por %.1f segundos", triggerIndex, REFIRE_TIME);
- // IMPORTANTE: NO resetear el estado de los jugadores que están dentro del trigger
- // Solo resetear los que NO están dentro
- for (int client = 1; client <= MaxClients; client++)
- {
- if (IsValidSurvivor(client))
- {
- // Si el jugador NO está en el trigger, resetearlo completamente
- if (!g_bPlayerInTrigger[client][triggerIndex])
- {
- g_bPlayerReady[client][triggerIndex] = false;
- g_iPlayerRetryCount[client][triggerIndex] = 0;
- g_bWaitingForWeapon[client][triggerIndex] = false;
- // Cancelar timer de reintento si existe
- if (g_hRetryTimer[client][triggerIndex] != null)
- {
- KillTimer(g_hRetryTimer[client][triggerIndex]);
- g_hRetryTimer[client][triggerIndex] = null;
- }
- }
- else
- {
- // El jugador SÍ está en el trigger - mantenerlo pero mostrar mensaje
- PrintHintText(client, "Trigger en cooldown: %.1fs", REFIRE_TIME);
- // Programar verificación para cuando termine el cooldown
- CreateTimer(REFIRE_TIME + 0.1, Timer_CheckTriggerPresence, client);
- }
- }
- }
- }
- void ApplyGroupRefireTime(int groupNumber)
- {
- // Deshabilitar TODOS los triggers del grupo temporalmente
- ArrayList groupTriggers = GetGroupTriggers(groupNumber);
- DebugPrint("ApplyGroupRefireTime - Aplicando refire_time a grupo %d (%d triggers)", groupNumber, groupTriggers.Length);
- for (int i = 0; i < groupTriggers.Length; i++)
- {
- int triggerIndex = groupTriggers.Get(i);
- ApplyRefireTime(triggerIndex);
- }
- // Remover grupo de la lista de activos después del refire_time
- char groupKey[16];
- IntToString(groupNumber, groupKey, sizeof(groupKey));
- CreateTimer(REFIRE_TIME, Timer_ResetGroup, groupNumber);
- }
- public Action Timer_ResetGroup(Handle timer, any groupNumber)
- {
- char groupKey[16];
- IntToString(groupNumber, groupKey, sizeof(groupKey));
- // Remover grupo de la lista de activos
- if (g_hActiveGroups != null)
- {
- g_hActiveGroups.Remove(groupKey);
- }
- // Limpiar jugadores de este grupo
- ClearGroupActivationPlayers(groupNumber);
- DebugPrint("Timer_ResetGroup - Grupo %d re-habilitado después de refire_time", groupNumber);
- return Plugin_Stop;
- }
- // ========================================================================
- // FUNCIÓN CAN TRIGGER ACTIVATE (NUEVA)
- // ========================================================================
- bool CanTriggerActivate(int triggerIndex)
- {
- if (triggerIndex < 0 || triggerIndex >= MAX_TRIGGERS)
- return false;
- float currentTime = GetGameTime();
- // Verificar si ha pasado el refire_time
- if (currentTime < g_fNextActivationTime[triggerIndex])
- {
- return false;
- }
- // Verificar si el trigger está habilitado
- if (!g_bTriggerEnabled[triggerIndex])
- {
- return false;
- }
- return true;
- }
- // ========================================================================
- // FUNCIONES DE TRACKING POR TRIGGER (COMPLEMENTO)
- // ========================================================================
- void AddPlayerToTrigger(int client, int triggerIndex)
- {
- if (triggerIndex < 0 || triggerIndex >= MAX_TRIGGERS) return;
- g_bPlayerInTrigger[client][triggerIndex] = true;
- g_fPlayerLastCheck[client][triggerIndex] = GetGameTime();
- g_iPlayerRetryCount[client][triggerIndex] = 0;
- g_bWaitingForWeapon[client][triggerIndex] = false;
- // Iniciar verificación inmediata (como en V15)
- CreateTimer(0.1, Timer_CheckTriggerPresence, client);
- }
- void RemovePlayerFromTrigger(int client, int triggerIndex)
- {
- if (triggerIndex < 0 || triggerIndex >= MAX_TRIGGERS) return;
- // Limpiar completamente el estado
- g_bPlayerInTrigger[client][triggerIndex] = false;
- g_bPlayerReady[client][triggerIndex] = false;
- g_bWaitingForWeapon[client][triggerIndex] = false;
- // Cancelar todos los timers relacionados
- if (g_hRetryTimer[client][triggerIndex] != null)
- {
- KillTimer(g_hRetryTimer[client][triggerIndex]);
- g_hRetryTimer[client][triggerIndex] = null;
- }
- // Remover de grupos si estaba en uno
- int groupNumber = g_iTriggerGroup[triggerIndex];
- if (groupNumber > 0)
- {
- MarkPlayerInGroupActivation(groupNumber, client, false);
- }
- }
- void RemovePlayerFromAllWaitingLists(int client)
- {
- int userid = GetClientUserId(client);
- // Limpiar mensajes de espera para todos los grupos
- for (int group = 0; group < 100; group++)
- {
- g_fLastWaitMessage[client][group] = 0.0;
- g_bWaitMessageShown[client][group] = false;
- }
- // Remover de g_hWaitingGroups si existe
- if (g_hWaitingGroups != null)
- {
- StringMapSnapshot snapshot = g_hWaitingGroups.Snapshot();
- char groupKey[16];
- for (int i = 0; i < snapshot.Length; i++)
- {
- snapshot.GetKey(i, groupKey, sizeof(groupKey));
- ArrayList waitingList;
- if (g_hWaitingGroups.GetValue(groupKey, waitingList))
- {
- int index = waitingList.FindValue(userid);
- if (index != -1)
- {
- waitingList.Erase(index);
- DebugPrint("RemovePlayerFromAllWaitingLists - %N removido de lista de espera del grupo %s", client, groupKey);
- if (waitingList.Length == 0)
- {
- delete waitingList;
- g_hWaitingGroups.Remove(groupKey);
- }
- }
- }
- }
- delete snapshot;
- }
- }
- void CompletelyCleanPlayerFromTrigger(int client, int triggerIndex)
- {
- if (triggerIndex < 0 || triggerIndex >= MAX_TRIGGERS) return;
- DebugPrint("CompletelyCleanPlayerFromTrigger - Limpiando completamente a %N del trigger %d", client, triggerIndex);
- // 1. Limpiar estados básicos
- g_bPlayerInTrigger[client][triggerIndex] = false;
- g_bPlayerReady[client][triggerIndex] = false;
- g_fPlayerLastCheck[client][triggerIndex] = 0.0;
- g_iPlayerRetryCount[client][triggerIndex] = 0;
- g_bWaitingForWeapon[client][triggerIndex] = false;
- // 2. Cancelar TODOS los timers relacionados
- if (g_hRetryTimer[client][triggerIndex] != null)
- {
- KillTimer(g_hRetryTimer[client][triggerIndex]);
- g_hRetryTimer[client][triggerIndex] = null;
- }
- // 3. Cancelar timer de verificación de presencia si existe
- // (no almacenamos su handle, pero podemos buscar alternativas)
- // 4. Remover de grupos de activación
- int groupNumber = g_iTriggerGroup[triggerIndex];
- if (groupNumber > 0)
- {
- MarkPlayerInGroupActivation(groupNumber, client, false);
- g_bWaitMessageShown[client][groupNumber] = false;
- }
- // 5. Remover de cualquier lista de espera de grupo
- RemovePlayerFromAllWaitingLists(client);
- }
- // ========================================================================
- // FUNCIÓN PARA VERIFICAR JUGADORES DENTRO DE TRIGGERS AL INICIO
- // ========================================================================
- void CheckPlayersInTriggers()
- {
- DebugPrint("CheckPlayersInTriggers - Iniciando verificación de jugadores en triggers...");
- for (int client = 1; client <= MaxClients; client++)
- {
- if (!IsValidSurvivor(client))
- continue;
- float clientPos[3];
- GetClientAbsOrigin(client, clientPos);
- for (int i = 0; i < MAX_TRIGGERS; i++)
- {
- if (!IsValidEntRef(g_iTriggers[i]))
- continue;
- int trigger = EntRefToEntIndex(g_iTriggers[i]);
- if (trigger == INVALID_ENT_REFERENCE)
- continue;
- float origin[3], mins[3], maxs[3];
- GetEntPropVector(trigger, Prop_Send, "m_vecOrigin", origin);
- GetEntPropVector(trigger, Prop_Send, "m_vecMins", mins);
- GetEntPropVector(trigger, Prop_Send, "m_vecMaxs", maxs);
- float worldMin[3], worldMax[3];
- worldMin[0] = origin[0] + mins[0];
- worldMin[1] = origin[1] + mins[1];
- worldMin[2] = origin[2] + mins[2];
- worldMax[0] = origin[0] + maxs[0];
- worldMax[1] = origin[1] + maxs[1];
- worldMax[2] = origin[2] + maxs[2];
- if (clientPos[0] >= worldMin[0] && clientPos[0] <= worldMax[0] &&
- clientPos[1] >= worldMin[1] && clientPos[1] <= worldMax[1] &&
- clientPos[2] >= worldMin[2] && clientPos[2] <= worldMax[2])
- {
- if (!g_bPlayerInTrigger[client][i])
- {
- g_bPlayerInTrigger[client][i] = true;
- g_bPlayerReady[client][i] = false;
- g_iPlayerRetryCount[client][i] = 0;
- g_bWaitingForWeapon[client][i] = false;
- DebugPrint("CheckPlayersInTriggers - Jugador %N agregado manualmente al trigger %d (tipo: %s)",
- client, i, g_sTriggerType[i]);
- CreateTimer(0.2, Timer_DelayedCheck, client);
- }
- }
- // ELIMINA la parte que limpia (else con CompletelyCleanPlayerFromTrigger)
- }
- }
- DebugPrint("CheckPlayersInTriggers - Verificación completada.");
- }
- // ========================================================================
- // ACTUALIZACIÓN PERIÓDICA DE JUGADORES EN TRIGGERS (SOLO AGREGAR)
- // ========================================================================
- void UpdatePlayerTriggers()
- {
- for (int client = 1; client <= MaxClients; client++)
- {
- if (!IsValidSurvivor(client))
- continue;
- float clientPos[3];
- GetClientAbsOrigin(client, clientPos);
- for (int i = 0; i < MAX_TRIGGERS; i++)
- {
- if (!IsValidEntRef(g_iTriggers[i]))
- continue;
- int trigger = EntRefToEntIndex(g_iTriggers[i]);
- if (trigger == INVALID_ENT_REFERENCE)
- continue;
- float origin[3], mins[3], maxs[3];
- GetEntPropVector(trigger, Prop_Send, "m_vecOrigin", origin);
- GetEntPropVector(trigger, Prop_Send, "m_vecMins", mins);
- GetEntPropVector(trigger, Prop_Send, "m_vecMaxs", maxs);
- float worldMin[3], worldMax[3];
- worldMin[0] = origin[0] + mins[0];
- worldMin[1] = origin[1] + mins[1];
- worldMin[2] = origin[2] + mins[2];
- worldMax[0] = origin[0] + maxs[0];
- worldMax[1] = origin[1] + maxs[1];
- worldMax[2] = origin[2] + maxs[2];
- bool inside = (clientPos[0] >= worldMin[0] && clientPos[0] <= worldMax[0] &&
- clientPos[1] >= worldMin[1] && clientPos[1] <= worldMax[1] &&
- clientPos[2] >= worldMin[2] && clientPos[2] <= worldMax[2]);
- // SOLO AGREGAR si está dentro y no está registrado
- if (inside && !g_bPlayerInTrigger[client][i])
- {
- g_bPlayerInTrigger[client][i] = true;
- g_bPlayerReady[client][i] = false;
- g_iPlayerRetryCount[client][i] = 0;
- g_bWaitingForWeapon[client][i] = false;
- DebugPrint("UpdatePlayerTriggers - Jugador %N agregado manualmente al trigger %d (tipo: %s)",
- client, i, g_sTriggerType[i]);
- // Iniciar verificación
- CreateTimer(0.2, Timer_DelayedCheck, client);
- }
- // NOTA: Ya no eliminamos a los que no están dentro, eso lo hace OnEndTouch.
- }
- }
- }
- void MarkPlayerReady(int client, int triggerIndex, bool ready)
- {
- if (triggerIndex < 0 || triggerIndex >= MAX_TRIGGERS) return;
- g_bPlayerReady[client][triggerIndex] = ready;
- g_fPlayerLastCheck[client][triggerIndex] = GetGameTime();
- }
- int FindReadyTriggerForPlayer(int client, const char[] type)
- {
- // Buscar en todos los triggers
- for (int i = 0; i < MAX_TRIGGERS; i++)
- {
- if (g_iTriggerGroup[i] != -1 && StrEqual(g_sTriggerType[i], type))
- {
- if (g_bPlayerReady[client][i])
- {
- return i;
- }
- }
- }
- return -1;
- }
- // ========================================================================
- // SISTEMA DE RETRY AUTOMÁTICO COMPLETO
- // ========================================================================
- public Action Timer_RetryCheck(Handle timer, DataPack pack)
- {
- pack.Reset();
- int userid = pack.ReadCell();
- int triggerIndex = pack.ReadCell();
- delete pack;
- int client = GetClientOfUserId(userid);
- if (!IsValidSurvivor(client))
- return Plugin_Stop;
- // Cancelar referencia al timer
- g_hRetryTimer[client][triggerIndex] = null;
- // Verificar si el jugador sigue en el trigger
- if (!g_bPlayerInTrigger[client][triggerIndex])
- {
- DebugPrint("Timer_RetryCheck - %N ya no está en el trigger %d", client, triggerIndex);
- return Plugin_Stop;
- }
- // Verificar condiciones nuevamente
- if (CheckPlayerConditions(client, triggerIndex))
- {
- DebugPrint("Timer_RetryCheck - %N ahora cumple condiciones en trigger %d", client, triggerIndex);
- MarkPlayerReady(client, triggerIndex, true);
- // Intentar activar
- int groupNumber = g_iTriggerGroup[triggerIndex];
- if (groupNumber > 0)
- {
- AttemptGroupActivation(groupNumber, triggerIndex, client);
- }
- else
- {
- ExecuteSoloMovementFromTrigger(client, triggerIndex);
- }
- }
- else
- {
- // Incrementar contador de reintentos
- g_iPlayerRetryCount[client][triggerIndex]++;
- if (g_iPlayerRetryCount[client][triggerIndex] >= MAX_RETRY_ATTEMPTS)
- {
- DebugPrint("Timer_RetryCheck - %N excedió límite de reintentos en trigger %d", client, triggerIndex);
- PrintToChat(client, "[\x04Error\x01] Demasiados intentos fallidos. Verifica tu arma.");
- RemovePlayerFromTrigger(client, triggerIndex);
- return Plugin_Stop;
- }
- // Programar otro reintento
- DebugPrint("Timer_RetryCheck - %N aún no cumple condiciones, reintentando... (intento %d/%d)",
- client, g_iPlayerRetryCount[client][triggerIndex], MAX_RETRY_ATTEMPTS);
- DataPack retryPack = new DataPack();
- retryPack.WriteCell(userid);
- retryPack.WriteCell(triggerIndex);
- g_hRetryTimer[client][triggerIndex] = CreateTimer(0.5, Timer_RetryCheck, retryPack);
- }
- return Plugin_Stop;
- }
- // ========================================================================
- // TIMER PARA REINTENTAR ACTIVACIÓN DESPUÉS DEL COOLDOWN
- // ========================================================================
- public Action Timer_RetryActivation(Handle timer, DataPack pack)
- {
- pack.Reset();
- int groupNumber = pack.ReadCell();
- int triggerIdx = pack.ReadCell();
- int userid = pack.ReadCell();
- delete pack;
- int activator = GetClientOfUserId(userid);
- if (!IsValidSurvivor(activator))
- return Plugin_Stop;
- DebugPrint("Timer_RetryActivation - Reintentando activación del grupo %d (trigger %d)", groupNumber, triggerIdx);
- AttemptGroupActivation(groupNumber, triggerIdx, activator);
- return Plugin_Stop;
- }
- public Action Timer_UpdateTriggers(Handle timer)
- {
- UpdatePlayerTriggers();
- return Plugin_Continue; // Para que siga ejecutándose
- }
- public Action Timer_CheckTriggers(Handle timer)
- {
- CheckPlayersInTriggers();
- return Plugin_Stop;
- }
- // ========================================================================
- // FUNCIÓN MEJORADA PARA DETENER MOVIMIENTO (CON LIMPIEZA DE GRUPO)
- // ========================================================================
- void ForceStopClientMovement(int client, bool showMessage = true)
- {
- if (client < 1 || client > MaxClients) return;
- if (!g_bClientInMovement[client])
- {
- if (showMessage)
- PrintToChat(client, "[\x04Movimiento\x01] No estás en movimiento");
- return;
- }
- DebugPrint("ForceStopClientMovement - %N deteniendo movimiento manualmente", client);
- // Guardar información del grupo antes de limpiar
- int groupNumber = g_iClientMovementGroup[client];
- char movementFile[128];
- strcopy(movementFile, sizeof(movementFile), g_sClientMovementFile[client]);
- // 1. Limpiar contador visual
- PrintHintText(client, " ");
- // 2. Cancelar TODOS los timers
- if (g_hClientCountdownTimer[client] != null)
- {
- KillTimer(g_hClientCountdownTimer[client]);
- g_hClientCountdownTimer[client] = null;
- }
- if (g_hClientMovementTimer[client] != null)
- {
- KillTimer(g_hClientMovementTimer[client]);
- g_hClientMovementTimer[client] = null;
- }
- // 3. Llamar al ConVar del movement reader
- if (g_hCvarStopPlayer != null)
- {
- char clientStr[12];
- IntToString(client, clientStr, sizeof(clientStr));
- g_hCvarStopPlayer.SetString(clientStr);
- }
- // 4. Limpiar estado interno
- g_bClientInMovement[client] = false;
- g_sClientMovementFile[client][0] = '\0';
- g_sClientMovementFileName[client][0] = '\0';
- g_fClientMovementEndTime[client] = 0.0;
- g_fClientMovementDuration[client] = 0.0;
- // 5. Liberar del grupo si estaba en uno
- if (groupNumber > 0)
- {
- MarkPlayerInGroupActivation(groupNumber, client, false);
- DebugPrint("ForceStopClientMovement - %N liberado del grupo %d", client, groupNumber);
- }
- // 6. Mensaje al jugador
- if (showMessage)
- {
- PrintToChat(client, "[\x04Movimiento\x01] Movimiento detenido");
- }
- }
- // ========================================================================
- // COMANDOS
- // ========================================================================
- public Action Command_ShowModels(int client, int args)
- {
- CreateVisualModels();
- PrintToChat(client, "[\x04Sistema\x01] Modelos de cono mostrados en todos los triggers");
- return Plugin_Handled;
- }
- public Action Command_HideModels(int client, int args)
- {
- RemoveVisualModels();
- PrintToChat(client, "[\x04Sistema\x01] Modelos de cono escondidos");
- return Plugin_Handled;
- }
- public Action Command_Groups(int client, int args)
- {
- PrintToChat(client, "[\x04Grupos\x01] === Estado de Grupos ===");
- PrintToChat(client, "[\x04Grupos\x01] Sistema: %s", g_hCvarGroupsEnable.BoolValue ? "\x04ACTIVADO" : "\x02DESACTIVADO");
- PrintToChat(client, "[\x04Grupos\x01] P/M: %s", g_hCvarGroupPM.BoolValue ? "\x04ACTIVADO" : "\x02DESACTIVADO");
- PrintToChat(client, "[\x04Grupos\x01] Bile: %s", g_hCvarGroupBile.BoolValue ? "\x04ACTIVADO" : "\x02DESACTIVADO");
- PrintToChat(client, "[\x04Grupos\x01] GL: %s", g_hCvarGroupGL.BoolValue ? "\x04ACTIVADO" : "\x02DESACTIVADO");
- PrintToChat(client, "[\x04Grupos\x01] Run: %s", g_hCvarGroupRun.BoolValue ? "\x04ACTIVADO" : "\x02DESACTIVADO");
- PrintToChat(client, "[\x04Grupos\x01] Dual: %s", g_hCvarGroupDual.BoolValue ? "\x04ACTIVADO" : "\x02DESACTIVADO");
- PrintToChat(client, "[\x04Grupos\x01] Uso: !togglegroup <pm|bile|gl|run|dual|all>");
- return Plugin_Handled;
- }
- public Action Command_ToggleGroup(int client, int args)
- {
- if (args < 1)
- {
- PrintToChat(client, "[\x04Uso\x01] !togglegroup <pm|bile|gl|run|dual|all>");
- return Plugin_Handled;
- }
- char group[16];
- GetCmdArg(1, group, sizeof(group));
- if (StrEqual(group, "all", false))
- {
- bool newState = !g_hCvarGroupsEnable.BoolValue;
- g_hCvarGroupsEnable.SetBool(newState);
- PrintToChat(client, "[\x04Grupos\x01] Todos los grupos: %s", newState ? "\x04ACTIVADOS" : "\x02DESACTIVADOS");
- DebugPrint("Command_ToggleGroup - %N cambió todos los grupos a: %s", client, newState ? "ACTIVADOS" : "DESACTIVADOS");
- }
- else if (StrEqual(group, "pm", false))
- {
- bool newState = !g_hCvarGroupPM.BoolValue;
- g_hCvarGroupPM.SetBool(newState);
- PrintToChat(client, "[\x04Grupos\x01] Grupo P/M: %s", newState ? "\x04ACTIVADO" : "\x02DESACTIVADO");
- DebugPrint("Command_ToggleGroup - %N cambió grupo P/M a: %s", client, newState ? "ACTIVADO" : "DESACTIVADO");
- }
- else if (StrEqual(group, "bile", false))
- {
- bool newState = !g_hCvarGroupBile.BoolValue;
- g_hCvarGroupBile.SetBool(newState);
- PrintToChat(client, "[\x04Grupos\x01] Grupo Bile: %s", newState ? "\x04ACTIVADO" : "\x02DESACTIVADO");
- DebugPrint("Command_ToggleGroup - %N cambió grupo Bile a: %s", client, newState ? "ACTIVADO" : "DESACTIVADO");
- }
- else if (StrEqual(group, "gl", false))
- {
- bool newState = !g_hCvarGroupGL.BoolValue;
- g_hCvarGroupGL.SetBool(newState);
- PrintToChat(client, "[\x04Grupos\x01] Grupo GL: %s", newState ? "\x04ACTIVADO" : "\x02DESACTIVADO");
- DebugPrint("Command_ToggleGroup - %N cambió grupo GL a: %s", client, newState ? "ACTIVADO" : "DESACTIVADO");
- }
- else if (StrEqual(group, "run", false))
- {
- bool newState = !g_hCvarGroupRun.BoolValue;
- g_hCvarGroupRun.SetBool(newState);
- PrintToChat(client, "[\x04Grupos\x01] Grupo Run: %s", newState ? "\x04ACTIVADO" : "\x02DESACTIVADO");
- DebugPrint("Command_ToggleGroup - %N cambió grupo Run a: %s", client, newState ? "ACTIVADO" : "DESACTIVADO");
- }
- else if (StrEqual(group, "dual", false))
- {
- bool newState = !g_hCvarGroupDual.BoolValue;
- g_hCvarGroupDual.SetBool(newState);
- PrintToChat(client, "[\x04Grupos\x01] Grupo Dual: %s", newState ? "\x04ACTIVADO" : "\x02DESACTIVADO");
- DebugPrint("Command_ToggleGroup - %N cambió grupo Dual a: %s", client, newState ? "ACTIVADO" : "DESACTIVADO");
- }
- else
- {
- PrintToChat(client, "[\x04Error\x01] Grupo no válido. Usa: pm, bile, gl, run, dual, all");
- return Plugin_Handled;
- }
- if (g_hMovementFiles.Length > 0)
- {
- RecreateTriggers();
- }
- return Plugin_Handled;
- }
- // ========================================================================
- // COMANDOS BÁSICOS
- // ========================================================================
- public Action Command_ForceMovement(int client, int args)
- {
- if (args < 1)
- {
- PrintToChat(client, "[\x04Uso\x01] !move <nombre_archivo>");
- return Plugin_Handled;
- }
- char movementFile[128];
- GetCmdArg(1, movementFile, sizeof(movementFile));
- bool exists;
- if (!g_hFileExistence.GetValue(movementFile, exists) || !exists)
- {
- PrintToChat(client, "[\x04Error\x01] Archivo no encontrado: %s", movementFile);
- return Plugin_Handled;
- }
- MovementFileInfo info;
- bool found = MA_GetMovementFileInfo(movementFile, g_hMovementFileInfos, info);
- if (!found)
- {
- PrintToChat(client, "[\x04Error\x01] No se pudo obtener información del archivo");
- return Plugin_Handled;
- }
- if (info.category == CATEGORY_BOOSTER_PURE || info.category == CATEGORY_RUNNER_GROUP)
- {
- PrintToChat(client, "[\x04Error\x01] Este movimiento requiere grupo. Actívalo desde el trigger.");
- return Plugin_Handled;
- }
- ExecutePlayerMovementImmediate(client, movementFile, MSG_INDIVIDUAL);
- return Plugin_Handled;
- }
- public Action Command_StopMovement(int client, int args)
- {
- if (g_bClientInMovement[client])
- {
- ForceStopClientMovement(client, true);
- PrintToChat(client, "[\x04Movimiento\x01] Movimiento detenido");
- }
- else
- {
- PrintToChat(client, "[\x04Error\x01] No estás ejecutando ningún movimiento");
- }
- return Plugin_Handled;
- }
- public Action Command_StopMe(int client, int args)
- {
- if (!client || !IsClientInGame(client))
- {
- ReplyToCommand(client, "[SM] Este comando solo puede ser usado en el juego.");
- return Plugin_Handled;
- }
- if (!g_bClientInMovement[client])
- {
- PrintToChat(client, "[\x04Movimiento\x01] No estás ejecutando ningún movimiento");
- return Plugin_Handled;
- }
- // Detener el movimiento
- ForceStopClientMovement(client, true);
- return Plugin_Handled;
- }
- public Action Command_ReloadMovements(int client, int args)
- {
- LoadMovementFilesAutomatically();
- PrintToChat(client, "[\x04Sistema\x01] Movimientos recargados");
- return Plugin_Handled;
- }
- public Action Command_ForceDual(int client, int args)
- {
- if (args < 2)
- {
- PrintToChat(client, "[\x04Uso\x01] !forcedual <archivo1> <archivo2> [jugador2]");
- return Plugin_Handled;
- }
- char file1[128], file2[128];
- GetCmdArg(1, file1, sizeof(file1));
- GetCmdArg(2, file2, sizeof(file2));
- int partner = client;
- if (args >= 3)
- {
- char arg[64];
- GetCmdArg(3, arg, sizeof(arg));
- partner = FindTarget(client, arg, true, false);
- }
- if (partner == -1)
- {
- PrintToChat(client, "[\x04Error\x01] Jugador no encontrado");
- return Plugin_Handled;
- }
- // Verificar archivos...
- // Obtener información de los archivos USANDO EL INCLUDE
- MovementFileInfo info1, info2;
- bool found1 = MA_GetMovementFileInfo(file1, g_hMovementFileInfos, info1);
- bool found2 = MA_GetMovementFileInfo(file2, g_hMovementFileInfos, info2);
- if (!found1 || !found2)
- {
- PrintToChat(client, "[\x04Error\x01] No se pudo obtener información de los archivos");
- return Plugin_Handled;
- }
- // Obtener nombres base para mostrar (sin autor)
- char baseName1[64], baseName2[64];
- GetBaseFileNameWithoutAuthor(file1, baseName1, sizeof(baseName1));
- GetBaseFileNameWithoutAuthor(file2, baseName2, sizeof(baseName2));
- // Obtener autores
- char author1[32], author2[32];
- strcopy(author1, sizeof(author1), info1.sAuthor);
- strcopy(author2, sizeof(author2), info2.sAuthor);
- if (strlen(author1) == 0) strcopy(author1, sizeof(author1), "Desconocido");
- if (strlen(author2) == 0) strcopy(author2, sizeof(author2), "Desconocido");
- // Obtener duraciones
- float duration1 = 10.0, duration2 = 10.0;
- if (g_hMovementDurations != null)
- {
- g_hMovementDurations.GetValue(file1, duration1);
- g_hMovementDurations.GetValue(file2, duration2);
- }
- // Mostrar autores (si son diferentes)
- if (!StrEqual(author1, author2))
- {
- PrintToChatAll("[\x04Movimiento\x01] Creado por '\x04%s\x01' y '\x04%s\x01'", author1, author2);
- }
- else if (!StrEqual(author1, "Desconocido"))
- {
- PrintToChatAll("[\x04Movimiento\x01] Creado por '\x04%s\x01'", author1);
- }
- // Determinar el tipo de dual basado en los tipos de movimiento
- char dualType[16] = "Run"; // Valor por defecto
- // Verificar si es un dual con booster
- if (info1.movType == MOVEMENT_PM || info2.movType == MOVEMENT_PM ||
- info1.movType == MOVEMENT_RUNPM || info2.movType == MOVEMENT_RUNPM)
- {
- dualType = "PM";
- }
- else if (info1.movType == MOVEMENT_BILE || info2.movType == MOVEMENT_BILE ||
- info1.movType == MOVEMENT_RUNBILE || info2.movType == MOVEMENT_RUNBILE)
- {
- dualType = "Bile";
- }
- else if (info1.movType == MOVEMENT_GL || info2.movType == MOVEMENT_GL ||
- info1.movType == MOVEMENT_RUNGL || info2.movType == MOVEMENT_RUNGL)
- {
- dualType = "GL";
- }
- // Mostrar mensaje público usando el sistema antiguo
- PrintToChatAll("[\x04M. Dual-%s\x01] %s(%.1fs) + %s(%.1fs)",
- dualType, baseName1, duration1, baseName2, duration2);
- // Mensajes para los jugadores
- PrintToChat(client, "[\x04M. Dual-%s\x01] %N + %N", dualType, client, partner);
- PrintToChat(partner, "[\x04M. Dual-%s\x01] %N + %N", dualType, client, partner);
- // También usar el nuevo sistema de nombres para consistencia
- ArrayList files = new ArrayList(ByteCountToCells(128));
- ArrayList players = new ArrayList();
- files.PushString(file1);
- files.PushString(file2);
- players.Push(client);
- players.Push(partner);
- // Analizar para obtener nombre del grupo
- GroupAnalysis analysis;
- if (AnalyzeActivatedFiles(0, files, analysis))
- {
- char groupName[64];
- GenerateGroupDisplayName(analysis, 2, groupName, sizeof(groupName));
- // Mostrar mensaje con nuevo formato también
- ArrayList durations = new ArrayList();
- durations.Push(duration1);
- durations.Push(duration2);
- ArrayList sortedFiles = new ArrayList(ByteCountToCells(128));
- ArrayList sortedDurations = new ArrayList();
- SortFilesForDisplay(files, durations, sortedFiles, sortedDurations);
- char publicMessage[512];
- FormatPublicMessage(groupName, 0, sortedFiles, sortedDurations, publicMessage, sizeof(publicMessage));
- PrintToChatAll("%s", publicMessage);
- delete durations;
- delete sortedFiles;
- delete sortedDurations;
- }
- // Ejecutar movimientos
- ExecutePlayerMovementImmediate(client, file1, MSG_DUAL);
- ExecutePlayerMovementImmediate(partner, file2, MSG_DUAL);
- delete files;
- delete players;
- return Plugin_Handled;
- }
- // =======================================================================
- // COMMAND LISTENER PARA st_mr_stop (GLOBAL)
- // =======================================================================
- public Action Command_GlobalStop(int client, const char[] command, int argc)
- {
- DebugPrint("Command_GlobalStop - Comando %s detectado - Deteniendo todos los movimientos", command);
- for (int i = 1; i <= MaxClients; i++)
- {
- if (g_bClientInMovement[i])
- {
- ForceStopClientMovement(i);
- }
- }
- if (g_hActiveGroups != null)
- g_hActiveGroups.Clear();
- if (g_hWaitingGroups != null)
- g_hWaitingGroups.Clear();
- // Limpiar control de jugadores por grupo
- if (g_hPlayersInGroupActivation != null)
- {
- g_hPlayersInGroupActivation.Clear();
- }
- return Plugin_Continue;
- }
- // ========================================================================
- // FUNCIONES AUXILIARES ADICIONALES
- // ========================================================================
- void ShowGroupActivationMessages(int groupNumber, ArrayList players, ArrayList movementFiles)
- {
- if (players == null || players.Length == 0) return;
- // Obtener autores únicos
- ArrayList uniqueAuthors = new ArrayList(ByteCountToCells(32));
- char authorsList[128] = "";
- for (int i = 0; i < movementFiles.Length; i++)
- {
- char file[128];
- movementFiles.GetString(i, file, sizeof(file));
- MovementFileInfo info;
- if (MA_GetMovementFileInfo(file, g_hMovementFileInfos, info))
- {
- if (strlen(info.sAuthor) > 0 && uniqueAuthors.FindString(info.sAuthor) == -1)
- {
- uniqueAuthors.PushString(info.sAuthor);
- }
- }
- }
- // Construir lista de autores
- for (int i = 0; i < uniqueAuthors.Length; i++)
- {
- char author[32];
- uniqueAuthors.GetString(i, author, sizeof(author));
- if (i == 0)
- Format(authorsList, sizeof(authorsList), "'\x04%s\x01'", author);
- else if (i == uniqueAuthors.Length - 1)
- Format(authorsList, sizeof(authorsList), "%s y '\x04%s\x01'", authorsList, author);
- else
- Format(authorsList, sizeof(authorsList), "%s, '\x04%s\x01'", authorsList, author);
- }
- delete uniqueAuthors;
- // Mostrar mensaje según cantidad de jugadores
- if (players.Length == 2)
- {
- PrintToChatAll("[\x04Grupo %d\x01] Movimientos creados por %s", groupNumber, authorsList);
- // Mostrar nombres de jugadores a los involucrados
- int client1 = players.Get(0);
- int client2 = players.Get(1);
- if (IsValidSurvivor(client1) && IsValidSurvivor(client2))
- {
- PrintToChat(client1, "[\x04Grupo %d\x01] %N + %N", groupNumber, client1, client2);
- PrintToChat(client2, "[\x04Grupo %d\x01] %N + %N", groupNumber, client1, client2);
- }
- }
- else if (players.Length > 2)
- {
- PrintToChatAll("[\x04Grupo %d\x01] Movimientos creados por %s y otros", groupNumber, authorsList);
- // Mostrar lista de jugadores
- char playerList[256] = "";
- for (int i = 0; i < players.Length; i++)
- {
- int client = players.Get(i);
- if (IsValidSurvivor(client))
- {
- if (i == 0)
- Format(playerList, sizeof(playerList), "%N", client);
- else if (i == players.Length - 1)
- Format(playerList, sizeof(playerList), "%s y %N", playerList, client);
- else
- Format(playerList, sizeof(playerList), "%s, %N", playerList, client);
- }
- }
- // Mostrar a todos los jugadores del grupo
- for (int i = 0; i < players.Length; i++)
- {
- int client = players.Get(i);
- if (IsValidSurvivor(client))
- {
- PrintToChat(client, "[\x04Grupo %d\x01] %s", groupNumber, playerList);
- }
- }
- }
- }
- // Preparación para sistema de aleatoriedad (futuro)
- char[] GetRandomMovementForGroup(int groupNumber)
- {
- char selectedFile[128] = "";
- // Buscar todos los archivos "run" de este grupo
- ArrayList groupRunFiles = new ArrayList(ByteCountToCells(128));
- for (int i = 0; i < g_hMovementFileInfos.Length; i++)
- {
- MovementFileInfo info;
- g_hMovementFileInfos.GetArray(i, info, sizeof(info));
- if (info.iNumber == groupNumber && info.movType == MOVEMENT_RUN)
- {
- groupRunFiles.PushString(info.sFullName);
- }
- }
- if (groupRunFiles.Length > 0)
- {
- // Por ahora: seleccionar el primero (aleatoriedad futura)
- // FUTURO: int randomIndex = GetRandomInt(0, groupRunFiles.Length - 1);
- int randomIndex = 0;
- groupRunFiles.GetString(randomIndex, selectedFile, sizeof(selectedFile));
- }
- delete groupRunFiles;
- return selectedFile;
- }
- // ========================================================================
- // FUNCIÓN PARA DETERMINAR AUTORES Y MOSTRAR MENSAJES (COMPATIBILIDAD)
- // ========================================================================
- void ShowAuthorsAndNames(int groupNumber, ArrayList players, ArrayList files)
- {
- if (players == null || players.Length == 0) return;
- // Usar el nuevo sistema para grupos de 1-3 jugadores
- if (players.Length <= 3)
- {
- ShowGroupActivationSummary(groupNumber, players, files);
- return;
- }
- // Para 4+ jugadores, usar sistema antiguo (compatibilidad)
- ShowGroupActivationMessages(groupNumber, players, files);
- }
- // Función para obtener nombre base sin autor
- public void GetBaseFileNameWithoutAuthor(const char[] fullPath, char[] baseName, int maxlen)
- {
- // Primero obtener el nombre base del archivo
- GetBaseFileName(fullPath, baseName, maxlen);
- // Remover el autor (todo después del último '_')
- int lastUnderscore = FindCharInString(baseName, '_', true);
- if (lastUnderscore != -1)
- {
- baseName[lastUnderscore] = '\0';
- // También remover cualquier guión bajo extra al final
- while (lastUnderscore > 0 && baseName[lastUnderscore-1] == '_')
- {
- baseName[lastUnderscore-1] = '\0';
- lastUnderscore--;
- }
- }
- }
- // Función para extraer autor del nombre del archivo
- public bool ExtractAuthorFromFileName(const char[] fileName, char[] author, int maxlen)
- {
- char temp[128];
- strcopy(temp, sizeof(temp), fileName);
- // Buscar el último "_" para separar nombre y autor
- int underscorePos = FindCharInString(temp, '_', true);
- if (underscorePos != -1)
- {
- strcopy(author, maxlen, temp[underscorePos + 1]);
- return true;
- }
- author[0] = '\0';
- return false;
- }
- // Función para formatear lista de autores
- public void FormatAuthorsList(ArrayList authors, char[] buffer, int maxlen)
- {
- buffer[0] = '\0';
- if (authors == null || authors.Length == 0)
- return;
- // Primero, eliminar duplicados (ya deberían estar eliminados, pero por seguridad)
- ArrayList unique = new ArrayList(ByteCountToCells(32));
- for (int i = 0; i < authors.Length; i++)
- {
- char author[32];
- authors.GetString(i, author, sizeof(author));
- if (unique.FindString(author) == -1)
- {
- unique.PushString(author);
- }
- }
- // Formatear lista
- for (int i = 0; i < unique.Length; i++)
- {
- char author[32];
- unique.GetString(i, author, sizeof(author));
- if (i == 0)
- {
- Format(buffer, maxlen, "'\x04%s\x01'", author);
- }
- else if (i == unique.Length - 1)
- {
- Format(buffer, maxlen, "%s y '\x04%s\x01'", buffer, author);
- }
- else
- {
- Format(buffer, maxlen, "%s, '\x04%s\x01'", buffer, author);
- }
- }
- delete unique;
- }
- // Función para mensaje público simple
- public void FormatSimplePublicMessage(const char[] groupName, int groupNumber, ArrayList baseFileNames,
- ArrayList durations, char[] buffer, int bufferLen)
- {
- // Construir parte de archivos
- char filesPart[512];
- filesPart[0] = '\0';
- for (int i = 0; i < baseFileNames.Length; i++)
- {
- char baseName[64];
- baseFileNames.GetString(i, baseName, sizeof(baseName));
- float duration = durations.Get(i);
- char temp[128];
- Format(temp, sizeof(temp), "%s(%.1fs)", baseName, duration);
- if (i == 0)
- strcopy(filesPart, sizeof(filesPart), temp);
- else
- Format(filesPart, sizeof(filesPart), "%s + %s", filesPart, temp);
- }
- // Combinar todo
- Format(buffer, bufferLen, "[\x04MA %s (%d)\x01] %s", groupName, groupNumber, filesPart);
- DebugPrint("FormatSimplePublicMessage - %s", buffer);
- }
- // ========================================================================
- // FUNCIÓN DE DEPURACIÓN DE ARCHIVOS (OPCIONAL, SOLO SI HAY CVAR DE DEBUG)
- // ========================================================================
- void DebugMovementFilesStructure()
- {
- if (!g_hCvarDebug.BoolValue) return;
- DebugPrint("\n=== ESTRUCTURA DE ARCHIVOS DE MOVIMIENTO ===");
- DebugPrint("Total de archivos cargados: %d", g_hMovementFileInfos.Length);
- for (int i = 0; i < g_hMovementFileInfos.Length; i++)
- {
- MovementFileInfo info;
- g_hMovementFileInfos.GetArray(i, info, sizeof(info));
- char typeName[32];
- switch(info.movType)
- {
- case MOVEMENT_PM: typeName = "PM";
- case MOVEMENT_BILE: typeName = "BILE";
- case MOVEMENT_GL: typeName = "GL";
- case MOVEMENT_RUN: typeName = "RUN";
- case MOVEMENT_DUAL: typeName = "DUAL";
- case MOVEMENT_RUNPM: typeName = "RUNPM";
- case MOVEMENT_RUNBILE: typeName = "RUNBILE";
- case MOVEMENT_RUNGL: typeName = "RUNGL";
- default: typeName = "UNKNOWN";
- }
- char catName[32];
- switch(info.category)
- {
- case CATEGORY_INDIVIDUAL: catName = "INDIVIDUAL";
- case CATEGORY_HYBRID: catName = "HYBRID";
- case CATEGORY_BOOSTER_PURE: catName = "BOOSTER";
- case CATEGORY_RUNNER_GROUP: catName = "RUNNER_GROUP";
- default: catName = "UNKNOWN";
- }
- if (info.bHasLetter)
- {
- DebugPrint("[%d] %s -> Grupo: %d, Letra: %c, Tipo: %s, Categoría: %s",
- i, info.sFullName, info.iNumber, info.sLetter, typeName, catName);
- }
- else
- {
- DebugPrint("[%d] %s -> Grupo: %d, Tipo: %s, Categoría: %s",
- i, info.sFullName, info.iNumber, typeName, catName);
- }
- }
- }
- // ========================================================================
- // FIN DEL PLUGIN - SISTEMA COMPLETO V21R CON NUEVO SISTEMA DE NOMBRES
- // ========================================================================
Advertisement
Add Comment
Please, Sign In to add comment