/*
===========================================================================
Copyright (C) 2023 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code 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 2 of the License,
or (at your option) any later version.

OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include "damagemodel.h"

static const unsigned int DAMAGEMODEL_NOTSOLID = 1;

Event EV_DamageModel_Setup
(
    "_setup",
    EV_DEFAULT,
    NULL,
    NULL,
    "Sets up an object.",
    EV_NORMAL
);
Event EV_DamageModel_KillTrace
(
    "killtrace",
    EV_DEFAULT,
    "vvff",
    "offset direction radius distance",
    "kills all objects along the trace\n"
    "offset - initial offset from origin\n"
    "direction - angular offset orientation for trace\n"
    "radius - thickness of trace\n"
    "distance - how far to trace",
    EV_NORMAL
);
Event EV_DamageModel_SpawnOrientedBoundingBox
(
    "orientedbbox",
    EV_DEFAULT,
    "vvf",
    "mins maxs yawoffset",
    "spawn an oriented bounding box with the given dimensions and an angular offset\n"
    "mins - min dimensions of box\n"
    "maxs - max dimensions of box\n"
    "yawoffset - angular offset orientation of box",
    EV_NORMAL
);
Event EV_DamageModel_KillThread
(
    "killthread",
    EV_DEFAULT,
    "s",
    "thread",
    "Set the thread to execute when this model is killed",
    EV_NORMAL
);

CLASS_DECLARATION(Animate, DamageModel, "DamageModel") {
    {&EV_Damage,                               &DamageModel::Damaged                 },
    {&EV_Killed,                               &DamageModel::Killed                  },
    {&EV_DamageModel_Setup,                    &DamageModel::Setup                   },
    {&EV_DamageModel_KillTrace,                &DamageModel::KillTrace               },
    {&EV_DamageModel_SpawnOrientedBoundingBox, &DamageModel::SpawnOrientedBoundingBox},
    {&EV_DamageModel_KillThread,               &DamageModel::EventSetKillThread      },
    {NULL,                                     NULL                                  }
};

DamageModel::DamageModel()
{
    if (LoadingSavegame) {
        return;
    }

    setSolidType(SOLID_BBOX);
    takedamage     = DAMAGE_YES;
    boundingBoxEnt = NULL;

    health = 50;
    flags |= FL_ROTATEDBOUNDS;

    PostEvent(EV_DamageModel_Setup, EV_POSTSPAWN);
}

DamageModel::~DamageModel()
{
    if (boundingBoxEnt) {
        boundingBoxEnt->PostEvent(EV_Remove, 0);
        boundingBoxEnt = NULL;
    }
}

void DamageModel::Setup(Event *ev)
{
    max_health = health;
    deadflag   = DEAD_NO;

    // set the animation
    NewAnim("idle");

    link();
}

void DamageModel::Damaged(Event *ev)
{
    Event *newev;
    int    damage;
    str    animname;

    newev = new Event(EV_SetAnim);
    newev->AddString("idle");

    damage = ev->GetInteger(2);
    if (damage < health * 0.25) {
        animname = "pain_small";
    } else if (damage < health * 0.66) {
        animname = "pain_medium";
    } else {
        animname = "pain_large";
    }

    switch (ev->GetInteger(9)) {
    case MOD_CRUSH:
    case MOD_CRUSH_EVERY_FRAME:
    case MOD_EXPLOSION:
    case MOD_EXPLODEWALL:
    case MOD_GRENADE:
    case MOD_ROCKET:
    case MOD_VEHICLE:
    case MOD_AAGUN:
        DamageEvent(ev);
        if (damage >= health) {
            return;
        }
        break;
    default:
        break;
    }

    if (!HasAnim(animname)) {
        animname = "pain";
    }

    NewAnim("pain", newev);
}

void DamageModel::Killed(Event *ev)
{
    Entity *inflictor;
    Vector  delta;
    float   yaw;
    int     num;
    str     anim;

    takedamage = DAMAGE_NO;
    deadflag   = DEAD_DEAD;
    setSolidType(SOLID_NOT);

    inflictor = ev->GetEntity(3);
    delta     = origin - inflictor->origin;
    yaw       = AngleSubtract(delta.toYaw(), angles.y);

    num  = (fmod(yaw + 360, 360) + 22.f) / 45.f;
    anim = "death_" + str(num);
    // execute the kill thread
    label.Execute(this);

    if (!HasAnim(anim)) {
        anim = "death";
    }

    if (spawnflags & DAMAGEMODEL_NOTSOLID) {
        NewAnim(anim, EV_BecomeNonSolid);
    } else {
        NewAnim(anim);
    }

    if (killtarget.c_str() && strcmp(killtarget, "")) {
        Entity *ent = NULL;

        // remove all entities with the kill target name
        for (ent = G_FindTarget(NULL, killtarget); ent; ent = G_FindTarget(ent, killtarget)) {
            ent->PostEvent(EV_Remove, 0);
        }
    }
}

void DamageModel::KillTrace(Event *ev)
{
    Vector  offset;
    Vector  direction;
    float   radius;
    float   distance;
    Vector  transformed;
    Vector  forward;
    Vector  localFwd;
    Vector  mins, maxs;
    Vector  end;
    trace_t trace;
    Entity *ent;
    int     i;

    if (spawnflags & DAMAGEMODEL_NOTSOLID) {
        return;
    }

    offset    = ev->GetVector(1);
    direction = ev->GetVector(2);
    radius    = ev->GetFloat(3);
    distance  = ev->GetFloat(4);

    MatrixTransformVector(offset, orientation, transformed);
    transformed += origin;

    direction.AngleVectorsLeft(&forward, NULL, NULL);
    MatrixTransformVector(forward, orientation, localFwd);

    end  = transformed + localFwd * distance;
    mins = Vector(-radius, -radius, -radius);
    maxs = Vector(radius, radius, radius);
    ent  = this;

    for (i = 0; i < 11; i++) {
        float damage;

        if (transformed == end) {
            break;
        }

        trace = G_Trace(transformed, mins, maxs, end, ent, MASK_SOLID, qfalse, "KillTrace");

        if (trace.fraction >= 1 || trace.entityNum == ENTITYNUM_WORLD) {
            break;
        }

        transformed = trace.endpos;
        if (!trace.ent) {
            continue;
        }

        ent = trace.ent->entity;
        if (!ent) {
            continue;
        }

        if (ent->takedamage == DAMAGE_NO) {
            continue;
        }

        if (ent->isSubclassOf(DamageModel)) {
            damage = health * 0.5;
            if (damage < 20) {
                damage = 20;
            }
        } else {
            damage = health + 1;
        }

        ent->Damage(this, this, damage, trace.endpos, localFwd, trace.plane.normal, 0, 0, MOD_CRUSH, HITLOC_GENERAL);
    }
}

void DamageModel::SpawnOrientedBoundingBox(Event *ev)
{
    Vector mins, maxs;
    float  yawoffset;

    if (spawnflags & DAMAGEMODEL_NOTSOLID) {
        return;
    }

    mins      = ev->GetVector(1) * edict->s.scale;
    maxs      = ev->GetVector(2) * edict->s.scale;
    yawoffset = ev->GetFloat(3);

    boundingBoxEnt = new Entity();
    boundingBoxEnt->edict->r.svFlags |= SVF_SENDPVS;
    boundingBoxEnt->edict->s.eFlags |= EF_LINKANGLES;
    boundingBoxEnt->edict->r.contents = CONTENTS_SOLID;
    boundingBoxEnt->setSolidType(SOLID_BBOX);

    boundingBoxEnt->angles   = angles;
    boundingBoxEnt->angles.y = fmod(angles.y + yawoffset, 360);
    boundingBoxEnt->setAngles(boundingBoxEnt->angles);
    boundingBoxEnt->setSize(mins, maxs);
    boundingBoxEnt->setOrigin(origin);
    boundingBoxEnt->DisconnectPaths();
}

void DamageModel::EventSetKillThread(Event *ev)
{
    if (ev->IsFromScript()) {
        label.SetThread(ev->GetValue(1));
    } else {
        label.Set(ev->GetString(1));
    }
}

void DamageModel::Archive(Archiver& arc)
{
    Animate::Archive(arc);

    label.Archive(arc);
    arc.ArchiveSafePointer(&boundingBoxEnt);
}
