// DEMOLITION MAN! from the movie by the same name. Plant Tripmines.
// Based on "Vexd Entity demo (TripMine)" by Vexd

/* CVARS - copy and paste to shconfig.cfg

//Demolition Man
demoman_level 6
demoman_radius 300        // radius of blast
demoman_maxdamage 125        // max damage a mine can cause
demoman_maxmines 2        // max ammount of mines that can be placed at once
demoman_minehealth 80        // health of mines (determines how many shots blow them up)

*/

#include <superheromod>

//Max number of mines that can be set using the CVAR
#define MAX_MINES 20

// *** Color and Alpha for Tripline ***

//Colors To Pick From
#define CUSTOM        0
#define RED        1
#define GREEN        2
#define BLUE        3
#define LTBLUE        4
#define YELLOW        5
#define PURPLE        6
#define ORANGE        7

//Number of colors above (must be correct)
#define COLORS_NUM    8    //Do not set higher than 100 or you will have problems

//Special color modes
#define RNDLIST        100    //Picks a random color from the list
#define RNDCOLOR    101    //Picks random RBG values (may cause lag)
#define TEAMCOLOR    102    //Colors them for what team the planter is on

//Color Settings
#define MINE_COLOR LTBLUE
#define MINE_ALPHA 60

//Color definitions
new const LineColors[COLORS_NUM][3] = {
    {150, 150, 150},    //Custom
    {255, 0, 0},        //Red
    {0, 255, 0},        //Green
    {0, 0, 255},        //Blue
    {0, 255, 255},        //Light Blue
    {255, 255, 0},        //Yellow
    {255, 0, 255},        //Purple
    {255, 128, 0}        //Orange
}

//How often does a mine think
#define MINE_THINK 0.05

// GLOBAL VARIABLES
new gHeroID
new const gHeroName[] = "Demolition Man"
new bool:gHasDemolitionMan[SH_MAXSLOTS+1]    //shouldn't really need this
new bool:gBombPlanted
new gPlayerMines[SH_MAXSLOTS+1][MAX_MINES]
new gPlayerMinesCount[SH_MAXSLOTS+1]
new const gSoundActivate[] = "weapons/mine_activate.wav"
new const gSoundCharge[] = "weapons/mine_charge.wav"
new const gSoundDeploy[] = "weapons/mine_deploy.wav"
new const gModelMine[] = "models/v_tripmine.mdl"
new gSpriteBeam, gSpriteFire, gSpriteSmoke
new gPcvarRadius, gPcvarMaxDamage, gPcvarMaxMines, gPcvarMineHealth
//----------------------------------------------------------------------------------------------
public plugin_init()
{
    // Plugin Info
    register_plugin("SUPERHERO Demolition Man", SH_VERSION_STR, "AssKicR/jtp10181")

    register_dictionary("sh_demoman.txt")

    // DO NOT EDIT THIS FILE TO CHANGE CVARS, USE THE SHCONFIG.CFG
    new pcvarLevel = register_cvar("demoman_level", "6")
    gPcvarRadius = register_cvar("demoman_radius", "300")
    gPcvarMaxDamage = register_cvar("demoman_maxdamage", "125")
    gPcvarMaxMines = register_cvar("demoman_maxmines", "2")
    gPcvarMineHealth = register_cvar("demoman_minehealth", "80")

    // FIRE THE EVENT TO CREATE THIS SUPERHERO!
    gHeroID = sh_create_hero(gHeroName, pcvarLevel)
    sh_set_hero_info(gHeroID, "Tripmines", "Unlimited Tripmines - Limited ammount that can be planted at once")
    sh_set_hero_bind(gHeroID)

    register_forward(FM_Think, "mine_think")
}
//----------------------------------------------------------------------------------------------
public plugin_precache()
{
    precache_sound(gSoundActivate)
    precache_sound(gSoundCharge)
    precache_sound(gSoundDeploy)
    precache_model(gModelMine)
    gSpriteBeam = precache_model("sprites/laserbeam.spr")
    gSpriteFire = precache_model("sprites/explode1.spr")
    gSpriteSmoke = precache_model("sprites/steam1.spr")
}
//----------------------------------------------------------------------------------------------
public sh_hero_init(id, heroID, mode)
{
    if ( gHeroID != heroID ) return

    switch(mode) {
        case SH_HERO_ADD: {
            gHasDemolitionMan[id] = true
        }

        case SH_HERO_DROP: {
            remove_mines(id)
            gHasDemolitionMan[id] = false
        }
    }

    sh_debug_message(id, 1, "%s %s", gHeroName, mode ? "ADDED" : "DROPPED")
}
//----------------------------------------------------------------------------------------------
public sh_hero_key(id, heroID, key)
{
    if ( gHeroID != heroID ) return

    if ( key == SH_KEYDOWN ) {
        if ( sh_is_freezetime() || gBombPlanted ) return
        if ( !is_user_alive(id) || !gHasDemolitionMan[id] ) return

        create_mine(id)
    }
}
//----------------------------------------------------------------------------------------------
create_mine(id)
{
    if ( !is_user_alive(id) ) return

    new maxmines = get_pcvar_num(gPcvarMaxMines)

    if ( maxmines > MAX_MINES ) {
        set_pcvar_num(gPcvarMaxMines, MAX_MINES)
        maxmines = MAX_MINES
    }

    if ( gPlayerMinesCount[id] >= maxmines ) {
        sh_sound_deny(id)
        sh_chat_message(id, gHeroID, "[SH] %L", LANG_PLAYER, "SH_DEMOMAN_MMLR", maxmines)
        return
    }

    new Float:vOrigin[3]
    pev(id, pev_origin, vOrigin)

    new Float:vTraceDirection[3], Float:vTraceEnd[3], Float:vTraceResult[3], Float:vNormal[3]

    velocity_by_aim(id, 64, vTraceDirection)
    vTraceEnd[0] = vTraceDirection[0] + vOrigin[0]
    vTraceEnd[1] = vTraceDirection[1] + vOrigin[1]
    vTraceEnd[2] = vTraceDirection[2] + vOrigin[2]

    new Float:fraction, tr = 0
    engfunc(EngFunc_TraceLine, vOrigin, vTraceEnd, 0, id, tr)
    get_tr2(tr, TR_vecEndPos, vTraceResult)
    get_tr2(tr, TR_vecPlaneNormal, vNormal)
    get_tr2(tr, TR_flFraction, fraction)

    if ( fraction >= 1.0 ) {
        sh_sound_deny(id)
        sh_chat_message(id, gHeroID, "[SH] %L", LANG_PLAYER, "SH_DEMOMAN_YMP")
        return
    }

    new slot
    for ( slot = 0; slot < MAX_MINES; slot++ ) {
        if ( gPlayerMines[id][slot] == -1 ) break
    }

    if ( slot >= maxmines || slot >= MAX_MINES ) return

    new NewEnt = engfunc(EngFunc_CreateNamedEntity, engfunc(EngFunc_AllocString, "info_target"))
    if ( !NewEnt ) return

    set_pev(NewEnt, pev_classname, "demoman_tripmine")
    engfunc(EngFunc_SetModel, NewEnt, gModelMine)

    //set_pev(NewEnt, pev_owner, id)
    set_pev(NewEnt, pev_iuser2, id)
    gPlayerMines[id][slot] = NewEnt
    gPlayerMinesCount[id]++

    set_pev(NewEnt, pev_movetype, MOVETYPE_FLY) //5 = movetype_fly, No grav, but collides.
    set_pev(NewEnt, pev_solid, SOLID_NOT)
    set_pev(NewEnt, pev_body, 3)
    set_pev(NewEnt, pev_sequence, 7)    // 7 = TRIPMINE_WORLD
    set_pev(NewEnt, pev_takedamage, DAMAGE_NO)
    set_pev(NewEnt, pev_iuser1, 0)        //0 Will be for inactive.

    new Float:vNewOrigin[3], Float:vEntAngles[3]
    vNewOrigin[0] = vTraceResult[0] + (vNormal[0] * 8.0)
    vNewOrigin[1] = vTraceResult[1] + (vNormal[1] * 8.0)
    vNewOrigin[2] = vTraceResult[2] + (vNormal[2] * 8.0)

    engfunc(EngFunc_SetSize, NewEnt, Float:{-8.0, -8.0, -8.0}, Float:{8.0, 8.0, 8.0})
    engfunc(EngFunc_SetOrigin, NewEnt, vNewOrigin)

    vector_to_angle(vNormal, vEntAngles)
    set_pev(NewEnt, pev_angles, vEntAngles)
    vEntAngles[0] *= -1.0
    vEntAngles[1] *= -1.0
    vEntAngles[2] *= -1.0
    set_pev(NewEnt, pev_v_angle, vEntAngles)

    new Float:vBeamEnd[3], Float:vTracedBeamEnd[3]
    vBeamEnd[0] = vNewOrigin[0] + (vNormal[0] * 8192.0)
    vBeamEnd[1] = vNewOrigin[1] + (vNormal[1] * 8192.0)
    vBeamEnd[2] = vNewOrigin[2] + (vNormal[2] * 8192.0)

    tr = 0
    engfunc(EngFunc_TraceLine, vNewOrigin, vBeamEnd, 1, -1, tr)
    get_tr2(tr, TR_vecEndPos, vTracedBeamEnd)
    set_pev(NewEnt, pev_vuser1, vTracedBeamEnd)

    emit_sound(NewEnt, CHAN_WEAPON, gSoundDeploy, VOL_NORM, ATTN_NORM, 0, PITCH_NORM)
    emit_sound(NewEnt, CHAN_VOICE, gSoundCharge, VOL_NORM, ATTN_NORM, 0, PITCH_NORM)

    set_pev(NewEnt, pev_nextthink, get_gametime() + 3.0)
}
//----------------------------------------------------------------------------------------------
public mine_think(ent)
{
    if ( !pev_valid(ent) ) return FMRES_IGNORED

    static classname[32]
    classname[0] = '^0'
    pev(ent, pev_classname, classname, charsmax(classname))

    if ( !equal(classname, "demoman_tripmine") ) return FMRES_IGNORED

    static Float:vEnd[3], Float:gametime
    pev(ent, pev_vuser1, vEnd)
    gametime = get_gametime()

    switch( pev(ent, pev_iuser1) )
    {
        case 0: {
            //Activate the mine
            set_pev(ent, pev_iuser1, 1) //1 Will be for active.
            set_pev(ent, pev_solid, SOLID_BBOX)
            set_pev(ent, pev_takedamage, DAMAGE_YES)
            set_pev(ent, pev_health, get_pcvar_float(gPcvarMineHealth)+1000.0)

            //Double message makes it darker at first
            laser_line(ent, vEnd, false)
            laser_line(ent, vEnd, false)
            set_pev(ent, pev_fuser1, gametime + 0.9)

            emit_sound(ent, CHAN_VOICE, gSoundActivate, VOL_NORM, ATTN_NORM, 0, PITCH_NORM)
        }

        case 1: {
            static Float:mineHealth
            pev(ent, pev_health, mineHealth)

            if ( mineHealth <= 1000.0 ) {
                detonate_mine(ent, -1)
                return FMRES_IGNORED
            }

            static Float:beamtime
            pev(ent, pev_fuser1, beamtime)
            if ( beamtime <= gametime ) {
                //Should get called every second
                laser_line(ent, vEnd, true)
                set_pev(ent, pev_fuser1, gametime + 0.9)
            }

            static Float:vTrace[3], iHit, tr
            static Float:vOrigin[3]
            pev(ent, pev_origin, vOrigin)
            tr = 0
            engfunc(EngFunc_TraceLine, vOrigin, vEnd, 0, ent, tr)
            get_tr2(tr, TR_vecEndPos, vTrace)
            iHit = get_tr2(tr, TR_pHit)

            if ( is_user_alive(iHit) ) detonate_mine(ent, iHit)
        }
    }

    if ( pev_valid(ent) ) set_pev(ent, pev_nextthink, gametime + MINE_THINK)

    return FMRES_IGNORED
}
//----------------------------------------------------------------------------------------------
detonate_mine(MineID, iHit)
{
    if ( !pev_valid(MineID) ) return

    new Float:vOrigin[3], slot
    pev(MineID, pev_origin, vOrigin)

    new id = pev(MineID, pev_iuser2)

    // clear this from the list of live tripmines
    for ( slot = 0; slot < MAX_MINES; slot++ ) {
        if ( gPlayerMines[id][slot] == MineID ) {
            gPlayerMines[id][slot] = -1
            gPlayerMinesCount[id]--
            break
        }
    }

    if ( iHit == -1 ) {
        sh_chat_message(id, gHeroID, "[SH] %L", LANG_PLAYER, "SH_DEMOMAN_YMH")
    }
    else {
        new name[32]
        get_user_name(iHit, name, charsmax(name))
        sh_chat_message(id, gHeroID, "[SH] %L", LANG_PLAYER, "SH_DEMOMAN_DYM", name)
    }

    //Kill the Beam
    message_begin(MSG_BROADCAST, SVC_TEMPENTITY) //message begin
    write_byte(TE_KILLBEAM)
    write_short(MineID) // entity
    message_end()

    engfunc(EngFunc_RemoveEntity, MineID)

    blow_up(id, vOrigin)
}
//----------------------------------------------------------------------------------------------
blow_up(id, Float:vExplodeAt[3])
{
    new Float:dRatio, Float:distanceBetween, damage
    new Float:dmgRadius = get_pcvar_float(gPcvarRadius)
    new maxDamage = get_pcvar_num(gPcvarMaxDamage)
    new CsTeams:idTeam = cs_get_user_team(id)
    new FFOn = sh_friendlyfire_on()
    new Float:vOrigin[3]

    explode_effect(vExplodeAt)

    // Raise the origin a bit
    vExplodeAt[2] += 37.0

    // Check Players
    new players[SH_MAXSLOTS], playerCount, player
    get_players(players, playerCount, "ah")

    for ( new i = 0; i < playerCount; i++ ) {
        player = players[i]

        pev(player, pev_origin, vOrigin)
        distanceBetween = vector_distance(vExplodeAt, vOrigin)

        if ( distanceBetween <= dmgRadius ) {
            if ( FFOn || player == id || idTeam != cs_get_user_team(player) ) {
                dRatio = distanceBetween / dmgRadius
                damage = maxDamage - floatround(maxDamage * dRatio)
                if ( !damage ) damage = 1    // Incase damage cvar is really low cause something if within the radius 
                sh_extra_damage(player, id, damage, "tripmine", _, SH_DMG_NORM, true, _, vExplodeAt)
            }

            // Add some push and shake effects instead of dmgStun later, and cause it on everyone within radius
        }
    }

    // Check for mines to hurt
    new Float:mineHealth, iCurrent = -1
    while( (iCurrent = engfunc(EngFunc_FindEntityByString, iCurrent, "classname", "demoman_tripmine")) > 0 ) {
        //if ( !pev_valid(iCurrent) ) continue
        pev(iCurrent, pev_origin, vOrigin)
        distanceBetween = vector_distance(vExplodeAt, vOrigin)

        if ( distanceBetween <= dmgRadius ) {
            dRatio = distanceBetween / dmgRadius
            damage = maxDamage - floatround(maxDamage * dRatio)

            // Trim it down a little for the mines
            damage = floatround(damage * 0.70)

            pev(iCurrent, pev_health, mineHealth)
            set_pev(iCurrent, pev_health, (mineHealth - damage))
        }
    }
}
//----------------------------------------------------------------------------------------------
remove_mines(id)
{
    new MineID
    gPlayerMinesCount[id] = 0

    for ( new x = 0; x < MAX_MINES; x++ ) {

        if ( gPlayerMines[id][x] == -1 ) continue

        MineID = gPlayerMines[id][x]
        gPlayerMines[id][x] = -1

        if ( !pev_valid(MineID) ) continue

        //Kill the Beam
        message_begin(MSG_BROADCAST, SVC_TEMPENTITY) //message begin
        write_byte(TE_KILLBEAM)
        write_short(MineID) // entity
        message_end()

        engfunc(EngFunc_RemoveEntity, MineID)
    }
}
//----------------------------------------------------------------------------------------------
laser_line(MineID, Float:vEnd[3], bool:killbeam)
{
    if ( !pev_valid(MineID) ) return

    static colornum, colors[3]
    colornum = MINE_COLOR

    switch(colornum)
    {
        case RNDLIST: {
            colors = LineColors[random_num(0, COLORS_NUM - 1)]
        }

        case RNDCOLOR: {
            while ( colors[0] + colors[1] + colors[2] < 150 ) {
                colors[0] = random_num(30, 255)
                colors[1] = random_num(30, 255)
                colors[2] = random_num(30, 255)
            }
        }

        case TEAMCOLOR: {
            switch ( cs_get_user_team(pev(MineID, pev_iuser2)) )
            {
                case CS_TEAM_T: colors = LineColors[RED]
                case CS_TEAM_CT: colors = LineColors[BLUE]
                default: colors = LineColors[CUSTOM]
            }
        }

        default: {
            if ( colornum < 0 || colornum >= COLORS_NUM ) {
                colornum = 0
            }
            colors = LineColors[colornum]
        }
    }

    //This is a little cleaner but not much
    if ( killbeam ) {
        //Kill the Beam
        message_begin(MSG_BROADCAST, SVC_TEMPENTITY) //message begin
        write_byte(TE_KILLBEAM)
        write_short(MineID) // entity
        message_end()
    }

    message_begin(MSG_BROADCAST, SVC_TEMPENTITY) //message begin
    write_byte(TE_BEAMENTPOINT)
    write_short(MineID)             // start entity
    engfunc(EngFunc_WriteCoord, vEnd[0])    // end position
    engfunc(EngFunc_WriteCoord, vEnd[1])
    engfunc(EngFunc_WriteCoord, vEnd[2])
    write_short(gSpriteBeam)// sprite index
    write_byte(0)        // starting frame
    write_byte(0)        // frame rate in 0.1's
    write_byte(10)        // life in 0.1's
    write_byte(5)        // line width in 0.1's
    write_byte(0)        // noise amplitude in 0.01's
    write_byte(colors[0])    // Red
    write_byte(colors[1])    // Green
    write_byte(colors[2])    // Blue
    write_byte(MINE_ALPHA)    // brightness
    write_byte(0)        // scroll speed in 0.1's
    message_end()
}
//----------------------------------------------------------------------------------------------
explode_effect(Float:vec[3])
{
    //Explosion
    message_begin(MSG_BROADCAST, SVC_TEMPENTITY) //message begin
    write_byte(TE_EXPLOSION)
    engfunc(EngFunc_WriteCoord, vec[0]) // start position
    engfunc(EngFunc_WriteCoord, vec[1])
    engfunc(EngFunc_WriteCoord, vec[2])
    write_short(gSpriteFire) // sprite index
    write_byte(60) // scale in 0.1's
    write_byte(10) // framerate
    write_byte(0) // flags
    message_end()

    //Explosion2
    message_begin(MSG_BROADCAST, SVC_TEMPENTITY) //message begin
    write_byte(TE_EXPLOSION2)
    engfunc(EngFunc_WriteCoord, vec[0]) // start position
    engfunc(EngFunc_WriteCoord, vec[1])
    engfunc(EngFunc_WriteCoord, vec[2])
    write_byte(188) // starting color
    write_byte(10) // num colors
    message_end()

    //Smoke
    message_begin(MSG_BROADCAST, SVC_TEMPENTITY) //message begin
    write_byte(TE_SMOKE)
    engfunc(EngFunc_WriteCoord, vec[0]) // start position
    engfunc(EngFunc_WriteCoord, vec[1])
    engfunc(EngFunc_WriteCoord, vec[2])
    write_short(gSpriteSmoke) // sprite index
    write_byte(20) // scale in 0.1's
    write_byte(10) // framerate
    message_end()
}
//----------------------------------------------------------------------------------------------
public client_connect(id)
{
    remove_mines(id)
}
//----------------------------------------------------------------------------------------------
public client_disconnected(id)
{
    remove_mines(id)
}
//----------------------------------------------------------------------------------------------
public sh_client_spawn(id)
{
    remove_mines(id)
}
//----------------------------------------------------------------------------------------------
public bomb_explode()
{
    gBombPlanted = true

    // Remove all the mines when the bomb blows up to prevent crashes
    static players[SH_MAXSLOTS], playerCount, i
    get_players(players, playerCount, "h")
    for ( i = 0; i < playerCount; i++ ) {
        remove_mines(players[i])
    }
}
//----------------------------------------------------------------------------------------------
public sh_round_start()
{
    gBombPlanted = false
}
//----------------------------------------------------------------------------------------------