/*
 * ============================================================================
 *
 *  Zombie:Reloaded
 *
 *  File:          respawn.inc
 *  Type:          Module
 *  Description:   Players come back to life
 *
 *  Copyright (C) 2009-2013  Greyscale, Richard Helgeby
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * ============================================================================
 */

/**
 * Conditions for respawning players.
 */
enum RespawnCondition
{
    Respawn_Default = -1,       /** Let ZR decide according to its settings. */
    Respawn_Human = 0,          /** Respawn as a human. */
    Respawn_Zombie,             /** Respawn as a zombie. */
    Respawn_ZombieIfSuicide     /** Respawn as a zombie if killed by world damage. */
}

/**
 * Array for storing respawn timer handles per client.
 */
new Handle:tRespawn[MAXPLAYERS + 1];

/**
 * Array for flagging zombies who were killed by world.
 */
new bool:bKilledByWorld[MAXPLAYERS + 1];

/**
 * Client is joining the server.
 */
RespawnClientInit(client)
{
    // Reset timer handle.
    tRespawn[client] = INVALID_HANDLE;

    // Init bKilledByWorld for client.
    bKilledByWorld[client] = false;
}

/**
 * Client is spawning into the game.
 *
 * @param client    The client index.
 */
RespawnOnClientSpawn(client)
{
    // If timer is running, kill it.
    if (tRespawn[client] != INVALID_HANDLE)
    {
        KillTimer(tRespawn[client]);
    }

    // Reset timer handle.
    tRespawn[client] = INVALID_HANDLE;

    // Reset player velocity
    float fResetVelocity[3] = {0.0, 0.0, 0.0};
    ToolsClientVelocity(client, fResetVelocity, true, false);
}

/**
 * Client has been killed.
 *
 * @param client    The client index.
 */
RespawnOnClientDeath(client, attacker, const String:weapon[])
{
    // If client is a zombie, check if they were killed by world.
    if (InfectIsClientInfected(client))
    {
        // Set bKilledByWorld to true if attacker is not a valid client.
        bKilledByWorld[client] = !ZRIsClientValid(attacker);
    }

    // If timer is running, kill it.
    if (tRespawn[client] != INVALID_HANDLE)
    {
        KillTimer(tRespawn[client]);
        tRespawn[client] = INVALID_HANDLE;
    }

    // If player was infected, then stop.
    if (StrEqual(weapon, "zombie_claws_of_death", false))
    {
        return;
    }

    // If respawn is disabled, stop here.
    new bool:respawn = GetConVarBool(g_hCvarsList[CVAR_RESPAWN]);
    if (!respawn)
    {
        return;
    }

    // Start respawn timer.
    new Float:delay = GetConVarFloat(g_hCvarsList[CVAR_RESPAWN_DELAY]);
    tRespawn[client] = CreateTimer(delay, RespawnTimer, client, TIMER_FLAG_NO_MAPCHANGE);
}

/**
 * The round is ending.
 */
RespawnOnRoundEnd()
{
    // x = client index.
    for (new x = 1; x <= MaxClients; x++)
    {
        // If client isn't in-game, then stop.
        if (!IsClientInGame(x))
        {
            continue;
        }

        // If timer isn't currently running, then stop.
        if (tRespawn[x] == INVALID_HANDLE)
        {
            continue;
        }

        // Stop timer.
        KillTimer(tRespawn[x]);

        // Reset timer handle.
        tRespawn[x] = INVALID_HANDLE;
    }
}

/**
 * Spawns a player into the round.
 *
 * @param client            The client index.
 * @param zombie            Respawn as zombie.
 * @param zombieIfSuicide   Respawn as zombie if killed by world damage.
 *
 * @return      True if the player was spawned, false otherwise.
 */
bool:RespawnSpawnClient(client, bool:zombie = false, bool:zombieIfSuicide = false)
{
    // If client isn't in-game, then stop.
    if (!IsClientInGame(client))
    {
        return false;
    }

    // Get respawn condition.
    new RespawnCondition:condition = RespawnToCondition(zombie, zombieIfSuicide);

    // Trigger API forward.
    new Action:result = APIOnClientRespawn(client, condition);

    // Check if respawn should be stopped.
    if (result == Plugin_Handled)
    {
        return false;
    }

    // Restore respawn condition.
    RespawnRestoreCondition(condition, zombie, zombieIfSuicide);

    new bool:ragdollremove = GetConVarBool(g_hCvarsList[CVAR_VEFFECTS_RAGDOLL_REMOVE]);
    if (!ragdollremove)
    {
        // Detatch ragdoll so it's not removed on respawn.
        RagdollResetClientRagdoll(client);
    }

    // Spawn player.
    CS_RespawnPlayer(client);

    // Check if first zombie has spawned.
    if (InfectHasZombieSpawned())
    {
        // Infect if player should spawn as zombie.
        if (zombie)
        {
            InfectHumanToZombie(client);
        }
        // Infect if player committed suicide by world damage.
        else if (zombieIfSuicide && bKilledByWorld[client])
        {
            InfectHumanToZombie(client);
            bKilledByWorld[client] = false;
        }
    }

    // Forward event to modules.
    APIOnClientRespawned(client, condition);

    return true;
}

/**
 * Timer callback, respawns a player.
 *
 * @param timer     The timer handle.
 * @param client    The client index.
 */
public Action:RespawnTimer(Handle:timer, any:client)
{
    // Reset timer handle.
    tRespawn[client] = INVALID_HANDLE;

    // If client isn't in-game, then stop.
    if (!IsClientInGame(client))
    {
        return;
    }

    // If player already is alive, then stop.
    if (IsPlayerAlive(client))
    {
        return;
    }

    // Get client team.
    new team = GetClientTeam(client);

    // If player isn't on a team, then stop.
    if (team != CS_TEAM_T && team != CS_TEAM_CT)
    {
        return;
    }

    // Get whether player should respawn as zombie.
    new bool:zombie = GetConVarBool(g_hCvarsList[CVAR_RESPAWN_TEAM_ZOMBIE]);

    // Get whether zombies died by suicide should respawn as zombies.
    new bool:zombieIfSuicide = GetConVarBool(g_hCvarsList[CVAR_RESPAWN_TEAM_ZOMBIE_WORLD]);

    // Spawn player.
    RespawnSpawnClient(client, zombie, zombieIfSuicide);
}

/**
 * Converts separate conditions into a single condition value.
 */
RespawnCondition:RespawnToCondition(bool:zombie, bool:zombieIfSuicide)
{
    if (zombie)
    {
        return Respawn_Zombie;
    }
    else if (zombieIfSuicide)
    {
        return Respawn_ZombieIfSuicide;
    }

    return Respawn_Human;
}

/**
 * Restores respawn condition to individual variables. If the condition invalid,
 * nothing will be changed.
 *
 * @param condition         Condition to restore.
 * @param zombie            Output.
 * @param zombieIfSucidie   Output.
 */
RespawnRestoreCondition(RespawnCondition:condition, &bool:zombie, &bool:zombieIfSuicide)
{
    switch (condition)
    {
        case Respawn_Human:
        {
            zombie = false;
            zombieIfSuicide = false;
        }
        case Respawn_Zombie:
        {
            zombie = true;
            zombieIfSuicide = false;
        }
        case Respawn_ZombieIfSuicide:
        {
            zombie = false;
            zombieIfSuicide = true;
        }
    }
}
