/**
 *  Command for making player immortal.
 *      Copyright 2022 Anton Tarasenko
 *------------------------------------------------------------------------------
 * This file is part of Acedia.
 *
 * Acedia 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, version 3 of the License, or
 * (at your option) any later version.
 *
 * Acedia 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 Acedia.  If not, see <https://www.gnu.org/licenses/>.
 */
class ACommandGod extends Command;

struct GodStatus
{
    //  Player to whom we grant godhood
    var EPlayer target;
    //  Is `target` only a demigod (can get damaged, but not die)?
    var bool    demigod;
    //  Should `target` be unaffected by attacks momentum?
    var bool    unmovable;
};

var private bool                connectedToSignal;
var private array<GodStatus>    godhoodList;

var private ACommandGod_Announcer announcer;

var private const int TDAMAGE, TMOMENTUM;

protected function Finalizer()
{
    connectedToSignal = false;
    _server.kf.health.OnDamage(self).Disconnect();
    _.memory.Free(announcer);
    super.Finalizer();
}

protected function BuildData(CommandDataBuilder builder)
{
    builder.Group(P("gameplay"));
    builder.Summary(P("Command for making player immortal."));
    builder.RequireTarget();
    builder.Describe(P("Gives targeted players god status, making them"
            @ "invincible."));
    builder.SubCommand(P("list"));
    builder.Describe(P("Reports godhood status of targeted players."));
    builder.SubCommand(P("strip"));
    builder.Describe(P("Strips targeted players from the godhood status."));
    builder.Option(P("demi"));
    builder.Describe(P("This flag makes targeted players \"demigods\" instead -"
            @ "they still cannot die, but they can take any non-lethal"
            @ "damage."));
    builder.Option(P("unmovable"));
    builder.Describe(P("This flag also prevents targeted players from being"
            @ "affected by the momentum trasnferred from damaging attacks."));
    announcer = ACommandGod_Announcer(
        _.memory.Allocate(class'ACommandGod_Announcer'));
}

protected function ExecutedFor(
    EPlayer     target,
    CallData    arguments,
    EPlayer     instigator,
    CommandPermissions permissions
) {
    local GodStatus newGodStatus;

    announcer.Setup(target, instigator, othersConsole);
    if (arguments.subCommandName.IsEmpty())
    {
        newGodStatus.target = target;
        newGodStatus.demigod = arguments.options.HasKey(P("demi"));
        newGodStatus.unmovable = arguments.options.HasKey(P("unmovable"));
        MakeGod(target, newGodStatus);
    }
    else if (arguments.subCommandName.Compare(P("list"))) {
        announcer.AnnounceGodStatus(BorrowGodStatus(target));
    }
    else if (arguments.subCommandName.Compare(P("strip"))) {
        RemoveGod(target);
    }
}

private function ProtectDivines(
    EPawn       target,
    EPawn       instigator,
    HashTable   damageData)
{
    local int       damage;
    local EPlayer   targetedPlayer;
    local GodStatus targetDivinity;

    targetedPlayer = target.GetPlayer();
    targetDivinity = BorrowGodStatus(targetedPlayer);
    _.memory.Free(targetedPlayer);
    if (targetDivinity.target == none) {
        return;
    }
    if (targetDivinity.unmovable) {
        damageData.SetVector(T(TMOMENTUM), Vect(0.0f, 0.0f, 0.0f));
    }
    if (targetDivinity.demiGod)
    {
        damage = damageData.GetInt(T(TDAMAGE));
        damage = Min(damage, target.GetHealth() - 1);
        damageData.SetInt(T(TDAMAGE), damage);
    }
    else {
        damageData.SetInt(T(TDAMAGE), 0);
    }
}

private final function MakeGod(
    EPlayer     target,
    GodStatus   newGodStatus)
{
    local int       godIndex;
    local bool      wasGod;
    local GodStatus oldGodStatus;

    if (target == none) {
        return;
    }
    for (godIndex = 0; godIndex < godhoodList.length; godIndex += 1)
    {
        if (target.SameAs(godhoodList[godIndex].target))
        {
            wasGod = true;
            oldGodStatus = godhoodList[godIndex];
            break;
        }
    }
    if (wasGod)
    {
        if (    newGodStatus.demiGod == oldGodStatus.demiGod
            &&  newGodStatus.unmovable == oldGodStatus.unmovable)
        {
            announcer.AnnounceSameGod(newGodStatus);
        }
        else
        {
            announcer.AnnounceChangedGod(oldGodStatus, newGodStatus);
            godhoodList[godIndex].target.FreeSelf();
            newGodStatus.target.NewRef();
            godhoodList[godIndex] = newGodStatus;
        }
    }
    else {
        announcer.AnnounceNewGod(newGodStatus);
        newGodStatus.target.NewRef();
        godhoodList[godhoodList.length] = newGodStatus;
    }
    UpdateHealthSignalConnection();
}

private final function RemoveGod(EPlayer target)
{
    local int i;

    if (target == none) {
        return;
    }
    for (i = 0; i < godhoodList.length; i += 1)
    {
        if (target.SameAs(godhoodList[i].target))
        {
            announcer.AnnounceRemoveGod(godhoodList[i]);
            godhoodList[i].target.FreeSelf();
            godhoodList.Remove(i, 1);
            UpdateHealthSignalConnection();
            return;
        }
    }
    announcer.AnnounceWasNotGod();
}

private final function GodStatus BorrowGodStatus(EPlayer target)
{
    local int       i;
    local GodStatus emptyStatus;

    if (target == none) {
        return emptyStatus;
    }
    for (i = 0; i < godhoodList.length; i += 1)
    {
        if (target.SameAs(godhoodList[i].target)) {
            return godhoodList[i];
        }
    }
    return emptyStatus;
}

private final function UpdateHealthSignalConnection()
{
    if (connectedToSignal && godhoodList.length <= 0)
    {
        _server.kf.health.OnDamage(self).Disconnect();
        connectedToSignal = false;
    }
    if (!connectedToSignal && godhoodList.length > 0)
    {
        connectedToSignal = true;
        _server.kf.health.OnDamage(self).connect = ProtectDivines;
    }
}

defaultproperties
{
    preferredName = "god"
    TDAMAGE             = 0
    stringConstants(0) = "damage"
    TMOMENTUM           = 1
    stringConstants(1) = "momentum"
}