// ----------------------------------------------------------------------
// Spraypaint cans
// ----------------------------------------------------------------------
//
// Super cheap.
//

const KIRI_SPRAY_DISTANCE = 96;
const KIRI_SPRAY_SHAKEANIM_MAXANGLE = 20.0;
const KIRI_SPRAY_MAXPAINT = 50;
const KIRI_SPRAY_MAXPRESSURE = 100;
const KIRI_SPRAY_PRESSUREBUILDSCALE = 20;
const KIRI_SPRAY_PRESSURE_PER_USE = (KIRI_SPRAY_MAXPRESSURE / 2);
const KIRI_SPRAY_PRESSURE_DECAY_RATE = 0.1;

const HDLD_KIRI_SPRAYCAN = "ksp";

enum KiriSprayerStatus
{
    KIRI_SPRAY_WS_PAINT = 1,
    KIRI_SPRAY_WS_PRESSURE  = 2
}

class SnekTechSprayer : HDWeapon
{
    default
    {
		radius 2;
        height 4;
        +SpriteAngle;
        Scale 0.4;

        +hdweapon.fitsinbackpack;
        +weapon.wimpy_weapon;

        +INVENTORY.PERSISTENTPOWER;
        +INVENTORY.INVBAR;

        // inventory.icon "KSPRB0";
        inventory.pickupsound "kiri/spraycan_rattle";
        inventory.pickupmessage "Picked up some spraypaint cans.";

        inventory.amount 1;
        inventory.maxamount 100;

        hdweapon.refid HDLD_KIRI_SPRAYCAN;

        tag "Spraypaint Can";
    }

    states
    {
    spawn:
        KSPR B -1;
        stop;

    ready:
        KSPR A 1 {
            A_WeaponReady(WRF_ALL);
            A_WeaponBusy(false);
        }
        goto readyend;

    altfire:
    reload:
        KSPR A 1 offset(0, 20) {
            A_OverlayPivot(0, 0.5, 1.0);
            A_OverlayRotate(0, 0);

            A_StartSound(
                "kiri/spraycan_rattle",
                CHAN_WEAPON, CHANF_NOSTOP);
        }
        KSPR A 1 offset(0, 40) { A_OverlayRotate(0, sin(level.time * 5.0) * KIRI_SPRAY_SHAKEANIM_MAXANGLE * 1.0/3.0); }
        KSPR A 1 offset(0, 60) { A_OverlayRotate(0, sin(level.time * 5.0) * KIRI_SPRAY_SHAKEANIM_MAXANGLE * 2.0/3.0); }
        KSPR A 1 offset(0, 80) {
            A_OverlayRotate(0, sin(level.time * 5.0) * KIRI_SPRAY_SHAKEANIM_MAXANGLE);

            // Taper off pressure increase amount based on how much
            // pressure is in there already.
            float pressureIncreaseScale = 1.0 - (
                float(invoker.GetPressure()) /
                float(KIRI_SPRAY_MAXPRESSURE));
            pressureIncreaseScale *= pressureIncreaseScale;

            // Add pressure.
            int pressure = invoker.GetPressure();
            pressure +=
                random(0,
                    KIRI_SPRAY_PRESSUREBUILDSCALE
                    * invoker.weaponstatus[KIRI_SPRAY_WS_PAINT]
                    / KIRI_SPRAY_MAXPAINT);

            // Cap pressure amount.
            if(pressure > KIRI_SPRAY_MAXPRESSURE) {
                pressure = KIRI_SPRAY_MAXPRESSURE;
            }

            invoker.SetPressure(pressure);

        }
        KSPR A 1 offset(0, 60) { A_OverlayRotate(0, sin(level.time * 5.0) * KIRI_SPRAY_SHAKEANIM_MAXANGLE * 2.0/3.0); }
        KSPR A 1 offset(0, 40) { A_OverlayRotate(0, sin(level.time * 5.0) * KIRI_SPRAY_SHAKEANIM_MAXANGLE * 1.0/3.0); }
        KSPR A 1 offset(0, 20) { A_OverlayRotate(0, 0); }
        goto ready;

    fire:
        KSPR A 2 {

            if(invoker.GetPressure() < KIRI_SPRAY_PRESSURE_PER_USE) {

                invoker.owner.A_Log("Not enough pressure to spray.", true);

            } else {

                float zOffset = GunHeight() * 0.8;
                FLineTraceData lineTraceData;

                class<SnekTechSprayerPattern> sprayerPatternClass = "SnekTechSprayerPattern";

                // Find the spray pattern class that matches the
                // player's CVar for selected pattern.
                String currentSprayCVar = CVar.GetCVar(
                    "snektech_spraypattern",
                    invoker.owner.player).GetString();
                for(int i = 0; i < AllActorClasses.size(); i++) {
                    class<SnekTechSprayerPattern> thisPatternClass = (class<SnekTechSprayerPattern>)(AllActorClasses[i]);
                    if(thisPatternClass) {
                        if(thisPatternClass.GetClassName() == currentSprayCVar) {
                            sprayerPatternClass = (class<SnekTechSprayerPattern>)(AllActorClasses[i]);
                            break;
                        }
                    }
                }

                // Get the actual decal name out of it.
                let defaultSprayClass = GetDefaultByType(sprayerPatternClass);
                String actualDecalName = defaultSprayClass.decalName;

                // Find a wall to spray on.
                bool traceHit = LineTrace(
                    angle,
                    KIRI_SPRAY_DISTANCE,
                    pitch,
                    flags : TRF_THRUACTORS,
                    offsetz : zOffset,
                    data : lineTraceData);

                if(traceHit && lineTraceData.HitLine) {

                    A_StartSound(
                        "kiri/spraycan_spray",
                        CHAN_WEAPON);

                    let normal = lineTraceData.HitDir;
                    let spawnedThing = Actor.Spawn("SnekTechSprayerDecalSpawner", lineTraceData.HitLocation - normal);
                    SnekTechSprayerDecalSpawner spawner = SnekTechSprayerDecalSpawner(spawnedThing);

                    if(spawner) {
                        spawner.angle = VectorAngle(-normal.x, -normal.y);
                        spawner.actualDecalName = actualDecalName;
                        spawner.master = invoker.owner;

                        invoker.SetPressure(invoker.GetPressure() - KIRI_SPRAY_PRESSURE_PER_USE);
                        invoker.weaponstatus[KIRI_SPRAY_WS_PAINT] -= 1;
                    }

                } else {

                    invoker.owner.A_Log("Not close enough to a wall to spray.", true);

                }
            }
        }
        goto waiting;

    unload:
        KSPR A 2 { invoker.CycleSprayPattern(-1); }
        goto waiting;

    firemode:
        KSPR A 2 { invoker.CycleSprayPattern(1); }
        goto waiting;

    waiting:
        KSPR A 5;
        KSPR A 0 A_Refire("waiting");
        goto ready;

    select0:
        KSPR A 0 offset(0, 120);
        ---- A 1 A_Raise(12);
        wait;

    deselect0:
        KSPR A 0;
        ---- A 1 A_Lower(12);
        wait;

    }

    override bool AddSpareWeapon(actor newowner)
    {
        return AddSpareWeaponRegular(newowner);
    }

    override HDWeapon GetSpareWeapon(actor newowner, bool reverse, bool doselect)
    {
        return GetSpareWeaponRegular(newowner, reverse, doselect);
    }

    // Why do we store pressure as this weird equation?
    //
    // Pressure is stored as a time since the a hypothetical last
    // "fully charged" time, based on the level time and pressure
    // decay rate.
    //
    // This way we can decay pressure without ticking, important for
    // spare weapons and stuff in backpacks.
    int GetPressure() const
    {
        int time = level.TotalTime;
        int lastChargedTime = weaponstatus[KIRI_SPRAY_WS_PRESSURE];
        int pressure = int(
            float(KIRI_SPRAY_MAXPRESSURE) -
            float(time - lastChargedTime) * KIRI_SPRAY_PRESSURE_DECAY_RATE);

        if(pressure > KIRI_SPRAY_MAXPRESSURE) {
            pressure = KIRI_SPRAY_MAXPRESSURE;
        }

        if(pressure < 0) {
            pressure = 0;
        }

        return pressure;
    }

    void SetPressure(int pressure)
    {
        if(pressure > KIRI_SPRAY_MAXPRESSURE) {
            pressure = KIRI_SPRAY_MAXPRESSURE;
        }

        if(pressure < 0) {
            pressure = 0;
        }

        int time = level.TotalTime;
        int lastChargedTime = int(round(
            float(pressure - KIRI_SPRAY_MAXPRESSURE) / float(KIRI_SPRAY_PRESSURE_DECAY_RATE) + time));

        weaponstatus[KIRI_SPRAY_WS_PRESSURE] = lastChargedTime;
    }

    void ResetPressure()
    {
        int resetValue = -int(float(KIRI_SPRAY_MAXPRESSURE) / float(KIRI_SPRAY_PRESSURE_DECAY_RATE));
        weaponstatus[KIRI_SPRAY_WS_PRESSURE] = resetValue;
    }

    override void Consolidate()
    {
        ResetPressure();
    }

	override void InitializeWepStats(bool idfa)
    {
        super.InitializeWepStats(idfa);

        SetPressure(0);

        // Add a little bit of randomness to the amount of paint in
        // the can.
        weaponstatus[KIRI_SPRAY_WS_PAINT] =
            KIRI_SPRAY_MAXPAINT
            - random(0, KIRI_SPRAY_MAXPAINT / 10);
    }

    override void DrawHUDStuff(
        HDStatusBar statusBar, HDWeapon weapon,
        HDPlayerPawn pawn)
    {
        // Current pressure (top bar).
        statusBar.DrawWepNum(
            GetPressure(),
            KIRI_SPRAY_MAXPRESSURE, posy:-10);

        // Total paint remaining (bottom bar).
        statusBar.DrawWepNum(
            weaponstatus[KIRI_SPRAY_WS_PAINT],
            KIRI_SPRAY_MAXPAINT);
    }

    void GetSprayPatternList(Array<String> ret)
    {
        ret.clear();
        for(int i = 0; i < AllActorClasses.Size(); i++) {
            class<Actor> c = AllActorClasses[i];
            if(c is "SnekTechSprayerPattern" && c != "SnekTechSprayerPattern") {
                ret.push(c.GetClassName());
            }
        }
    }

    void CycleSprayPattern(int increment)
    {
        // Find the current entry in the list of patterns.
        String currentPattern = CVar.GetCVar("snektech_spraypattern", owner.player).GetString();
        Array<String> patternList;
        GetSprayPatternList(patternList);
        int currentIndex = patternList.find(currentPattern);

        // Make sure we're actually in the list (CVar might not be in
        // list, due to mods getting shuffled around and whatever).
        if(currentIndex >= patternList.size()) {
            currentIndex = -1;
        }

        // Increment/decrement the index.
        int newIndex = (currentIndex + increment) % patternList.size();
        if(newIndex < 0) {
            newIndex = patternList.size() - 1;
        }

        // Set the new CVar.
        currentPattern = patternList[newIndex];
        // first, update the Actual cvar
        if (owner.PlayerNumber() == consoleplayer)
           CVar.FindCVar("snektech_spraypattern").SetString(currentPattern);
        // then update the per-player copy so the help text is correct
        CVar.GetCVar("snektech_spraypattern", owner.player).SetString(currentPattern);

        // Print it.
        owner.A_Log(
            String.format(
                "Selected spray pattern: (%d/%d) %s",
                newIndex + 1, patternList.size(),
                currentPattern),
            true);

        // Current pattern is referenced in the help text, so reset
        // it.
        A_SetHelpText();
    }

    override double WeaponBulk()
    {
        return 10;
    }

    override String GetHelpText()
    {
        super.GetHelpText();

        int messageIndex = level.time;
        Array<String> messages = {
            "Deface the tyrant's property.",
            "Engage in civil disobedience.",
            "Create a beautiful work of art.",
            "Show the world that you still exist.",
            "Defy the tyrant."
        };

        String currentPattern = CVar.GetCVar("snektech_spraypattern", owner.player).GetString();

        return
            WEPHELP_FIREMODE .. " Cycle pattern (current: "..currentPattern..").\n"..
            WEPHELP_UNLOAD   .. " Cycle pattern (backwards).\n"..
            WEPHELP_ALTFIRE  .. " Shake the can.\n"..
            WEPHELP_FIRE     .. " "..messages[messageIndex % messages.Size()].."\n";
    }
}

class SnekTechSprayerDecalSpawner : Decal
{
    default
    {
        +nogravity;
    }

    states
    {
    spawn:
        TNT1 A -1;
        stop;
    }

    int thisSprayerId;
    String actualDecalName;

    Array<Thinker> decals;

    void SpawnDecal()
    {
        Thinker think;
        array<thinker> seen;

        // find every decal that Isn't ours
        let iter = ThinkerIterator.Create('Thinker', STAT_DECAL);
        while (think = iter.Next()) {
            // BaseDecal isn't exported to zscript so we have to filter this manually
            if (think.GetClassName() == 'BaseDecal') {
                seen.Push(think);
            }
        }

        // SpawnDecal checks arg0 for the decal ID/name.
        // gzdoom passes string arguments as negated string table indices, so
        // we need to cast the decal name to a Name (implicitly adding it to
        // the string table), then to an int (to get the index).
        args[0] = -int(Name(actualDecalName));
        Super.SpawnDecal();

        // then find every decal that we spawned
        iter.Reinit();
        while (think = iter.Next()) {
            if (think.GetClassName() == 'BaseDecal' &&
                seen.Find(think) == seen.Size())
            {
                decals.Push(think);
            }
        }
    }

    override void OnDestroy()
    {
        for (let i = 0; i < decals.Size(); i++) {
            if (decals[i]) {
                decals[i].Destroy();
            }
        }
    }

    // Decal::BeginPlay deletes the actor
    override void BeginPlay() {Actor.BeginPlay();}

    override void PostBeginPlay()
    {
        Super.PostBeginPlay();

        // copy A_SprayDecal z offset
        setz(pos.z + height / 2);

        SpawnDecal();
        if (decals.Size() == 0) {
            Destroy();
            return;
        }

        // Figure out a new ID number.
        ThinkerIterator iter = ThinkerIterator.Create("SnekTechSprayerDecalSpawner");
        SnekTechSprayerDecalSpawner otherSpawner;
        int maxId = 0;
        while(otherSpawner = SnekTechSprayerDecalSpawner(iter.Next())) {

            if(otherSpawner == self) {
                continue;
            }

            // Keep track of the highest ID.
            if(otherSpawner.thisSprayerId > maxId) {
                if(otherSpawner.master == master) {
                    maxId = otherSpawner.thisSprayerId;
                }
            }
        }
        thisSprayerId = maxId + 1;

        // Clear old sprayers.
        iter = ThinkerIterator.Create("SnekTechSprayerDecalSpawner");
        while(otherSpawner = SnekTechSprayerDecalSpawner(iter.Next())) {

            if(otherSpawner == self) {
                continue;
            }

            int maxSpraysPerPlayer =
                CVar.GetCVar("snektech_maxspraysperplayer").GetInt();
            if(otherSpawner.thisSprayerId <= (thisSprayerId - maxSpraysPerPlayer)) {
                if(otherSpawner.master == master) {
                    otherSpawner.Destroy();
                }
            }
        }
    }
}

class SnekTechSprayerPattern : Actor
{
    string decalName;
    property decalName:decalName;

    default {
        SnekTechSprayerPattern.decalName "KiriTestDecal";
    }
}

class SnekSpray_TransPride : SnekTechSprayerPattern
{
    default
    {
        SnekTechSprayerPattern.decalName "SnekSpray_TransPride";
    }
}

class SnekSpray_LesbianPride : SnekTechSprayerPattern
{
    default
    {
        SnekTechSprayerPattern.decalName "SnekSpray_LesbianPride";
    }
}

class SnekSpray_NBPride : SnekTechSprayerPattern
{
    default
    {
        SnekTechSprayerPattern.decalName "SnekSpray_NBPride";
    }
}

class SnekSpray_AcePride : SnekTechSprayerPattern
{
    default
    {
        SnekTechSprayerPattern.decalName "SnekSpray_AcePride";
    }
}

class SnekSpray_ProgressPride : SnekTechSprayerPattern
{
    default
    {
        SnekTechSprayerPattern.decalName "SnekSpray_ProgressPride";
    }
}

class SnekSpray_DemiPride : SnekTechSprayerPattern
{
    default
    {
        SnekTechSprayerPattern.decalName "SnekSpray_DemiPride";
    }
}

class SnekSpray_PanPride : SnekTechSprayerPattern
{
    default
    {
        SnekTechSprayerPattern.decalName "SnekSpray_PanPride";
    }
}

class SnekSpray_SwitchPride : SnekTechSprayerPattern
{
    default
    {
        SnekTechSprayerPattern.decalName "SnekSpray_SwitchPride";
    }
}

class SnekSpray_BiPride : SnekTechSprayerPattern
{
    default
    {
        SnekTechSprayerPattern.decalName "SnekSpray_BiPride";
    }
}

class SnekSpray_GayPride : SnekTechSprayerPattern
{
    default
    {
        SnekTechSprayerPattern.decalName "SnekSpray_GayPride";
    }
}

