/*=============================================================================
    Name    : damage.c
    Purpose : things necessary for showing ship damage

    Created 7/30/1998 by khent
    Copyright Relic Entertainment, Inc.  All rights reserved.
=============================================================================*/

#include <math.h>
#include <string.h>
#include "glinc.h"
#include "spaceobj.h"
#include "etg.h"
#include "trails.h"
#include "univupdate.h"
#include "damage.h"
#include "statscript.h"
#include "debug.h"
#include "mex.h"
#include "memory.h"
#include "randy.h"
#include "singleplayer.h"


#define DMG_WORLDSPACE 1


/*=============================================================================
    Data
=============================================================================*/

sdword maxEffects[DMG_NumberDamageTypes] = {1,2,1};

static real32 DMG_StrikeTrailLimit = 0.6f;
static real32 DMG_StrikeLightLimit = 0.4f;
static real32 DMG_TrailLimit = 0.6f;
static real32 DMG_LightLimit = 0.4f;
static real32 DMG_HeavyLimit = 0.2f;
static real32 DMG_DyingLimit = 0.075f;

scriptEntry DamageTweaks[] =
{
    makeEntry(DMG_StrikeTrailLimit, scriptSetReal32CB),
    makeEntry(DMG_StrikeLightLimit, scriptSetReal32CB),
    makeEntry(DMG_TrailLimit, scriptSetReal32CB),
    makeEntry(DMG_LightLimit, scriptSetReal32CB),
    makeEntry(DMG_HeavyLimit, scriptSetReal32CB),
    makeEntry(DMG_DyingLimit, scriptSetReal32CB),
    endEntry
};


/*=============================================================================
    Code
=============================================================================*/

/*-----------------------------------------------------------------------------
    Name        : dmgStartup
    Description : startup the damage module
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void dmgStartup()
{
    //nothing here
}

/*-----------------------------------------------------------------------------
    Name        : dmgShutdown
    Description : shutdown the damage module
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void dmgShutdown()
{
    //nothing here
}

/*-----------------------------------------------------------------------------
    Name        : dmgCoordSysFromVector
    Description : Constructs a coordinate system from a normal vector
    Inputs      : m - resultant 3x3 matrix
                  axis - characteristic vector
    Outputs     : m - 3x3 matrix to fill
    Return      :
----------------------------------------------------------------------------*/
void dmgCoordSysFromVector(matrix* m, vector* axis)
{
    vector up, LOF, right;

    vecSet(up, 0.0f, 0.0f, 1.0f);
    mexVecToVec(&LOF, axis);
    vecCrossProduct(right, LOF, up);
    vecCrossProduct(up, LOF, right);
    vecNormalize(&LOF);
    vecNormalize(&right);
    vecNormalize(&up);

    matPutVectIntoMatrixCol1(up, *m);
    matPutVectIntoMatrixCol2(right, *m);
    matPutVectIntoMatrixCol3(LOF, *m);
}

/*-----------------------------------------------------------------------------
    Name        : dmgRandomDamageLocation
    Description : select a damage light randomly from the ship, convert it to a
                  coordinate system + position, and return it (in shipspace)
    Inputs      : ship - the Ship
                  location - where the position goes
                  coordsys - where the coordinate system goes
    Outputs     : location, coordsys
    Return      :
----------------------------------------------------------------------------*/
void dmgRandomDamageLocation(Ship* ship, vector* location, matrix* coordsys)
{
    vector* lightNormal;
    vector* lightPosition;
    sdword  index;
    real32 scale = ship->magnitudeSquared;

    if (ship->nDamageLights == 0)
    {
        //no damage lights, use centre of ship
        vecZeroVector(*location);
        *coordsys = IdentityMatrix;
        return;
    }

    //random index
    index = ranRandom(RAN_Damage) % ship->nDamageLights;

    //obtain light data
    lightNormal = &ship->damageLights[index].normal;
    lightPosition = &ship->damageLights[index].position;

    //store location
    //*location = *lightPosition;
    vecScalarMultiply(*location, *lightPosition, scale);

    //turn its heading into a coordinate system
    dmgCoordSysFromVector(coordsys, lightNormal);
    //coordsys->m11 *= scale;
    //coordsys->m22 *= scale;
    //coordsys->m33 *= scale;
}

/*-----------------------------------------------------------------------------
    Name        : dmgCoordSysFromChunk
    Description : extract a coordinate system + offset from a light
    Inputs      : ship - the Ship
                  location, coordsys - resultant coordinate system goes here
                  type - eg. "Eng"
                  name - eg. "Eng0"
    Outputs     : location, coordsys
    Return      :
----------------------------------------------------------------------------*/
void dmgCoordSysFromChunk(Ship* ship, vector* location, matrix* coordsys, char* type, char* name)
{
    ShipStaticInfo* shipStatic;
    MEXChunk* mexChunk;
    vector* LOFChunk;
    vector* posChunk;
    real32 scale = ship->magnitudeSquared;

    shipStatic = ship->staticinfo;
    mexChunk = mexGetChunk(shipStatic->staticheader.pMexData, type, name);

    if (mexChunk == NULL)
    {
        vecZeroVector(*location);
        *coordsys = IdentityMatrix;
        return;
    }

    LOFChunk = &((MEXEngineChunk*)mexChunk)->normal;
    posChunk = &((MEXEngineChunk*)mexChunk)->position;

    //*location = *posChunk;
    vecScalarMultiply(*location, *posChunk, scale);
    dmgCoordSysFromVector(coordsys, LOFChunk);
    //coordsys->m11 *= scale;
    //coordsys->m22 *= scale;
    //coordsys->m33 *= scale;
}

/*-----------------------------------------------------------------------------
    Name        : dmgNumEffects
    Description : returns the number of effects currently playing at given level of damage
    Inputs      : ship - the Ship
                  level - level of damage
    Outputs     :
    Return      : the number of effects
----------------------------------------------------------------------------*/
sdword dmgNumEffects(Ship* ship, sdword level)
{
    sdword i, count;

    for (i = count = 0; i < maxEffects[level]; i++)
    {
        if (ship->showingDamage[level][i] != NULL)
        {
            count++;
        }
    }

    return count;
}

/*-----------------------------------------------------------------------------
    Name        : dmgAddEffectToShip
    Description : try to add an damage effect to a ship
    Inputs      : ship - the Ship
                  effect - the Effect
                  level - level of damage
    Outputs     : ship->showingDamage[level][?] contains the effect, or a fatal error is raised
    Return      :
----------------------------------------------------------------------------*/
void dmgAddEffectToShip(Ship* ship, Effect* effect, sdword level)
{
    sdword i;
    udword uship;

    for (i = 0; i < maxEffects[level]; i++)
    {
        if (ship->showingDamage[level][i] == NULL)
        {
#ifdef khent
#if DMG_VERBOSE >= 1
            dbgMessagef("\n-- damage effect level %d --", level);
#endif //DMG_VERBOSE
#endif //khent
            ship->showingDamage[level][i] = effect;
            return;
        }
    }

    uship = TreatAsUdword(ship);
    dbgFatalf(DBG_Loc, "too many damage effects at level %d, ship %u", level, uship);
}

/*-----------------------------------------------------------------------------
    Name        : dmgPlayEffect
    Description : try to play an effect to show ship damage
    Inputs      : ship - the Ship
                  effect - the Effect (type etglod) to play
                  level - level of damage
    Outputs     : the effect may begin playing if the maximum number of effects
                  are not already playing
    Return      :
----------------------------------------------------------------------------*/
void dmgPlayEffect(Ship* ship, etglod* effect, sdword level)
{
    etgeffectstatic* stat;
    sdword LOD;
    real32 floatDamage, floatSize;
    udword intDamage, intSize;
    matrix coordsys;
    vector location;

    //ASSERT: ship in renderlist

    if (effect == NULL)
    {
        //no effect to play
        return;
    }

    if (dmgNumEffects(ship, level) >= maxEffects[level])
    {
        //already playing the maximum number of effects
        return;
    }

    LOD = ship->currentLOD;
    if (LOD < effect->nLevels)
    {
        stat = effect->level[LOD];
    }
    else
    {
        //no effect to play, so simply return
        return;
    }

    //obtain location & coordsys for the effect from a random light
    switch (level)
    {
    case DMG_Light:
        //engine
        dmgCoordSysFromChunk(ship, &location, &coordsys, "Eng", "Eng0");
        break;

    case DMG_Heavy:
        //random "dmg" light
        dmgRandomDamageLocation(ship, &location, &coordsys);
        break;

    case DMG_Dying:
        //centre
        vecZeroVector(location);
        coordsys = IdentityMatrix;
        break;

    default:
        dbgFatalf(DBG_Loc, "\ndmgPlayEffect level %d unrecognized", level);
    }

    //obtain damage (health ratio)
    floatDamage = ship->health * ship->staticinfo->oneOverMaxHealth;
    intDamage = TreatAsUdword(floatDamage);
    floatSize = ship->staticinfo->staticheader.staticCollInfo.collspheresize;
    if (RGLtype == SWtype)
    {                                                       //smaller damage effects in software
        floatSize *= etgSoftwareScalarDamage;
    }
    intSize = TreatAsUdword(floatSize);

#if ETG_DISABLEABLE
    if (etgEffectsEnabled && etgDamageEffectsEnabled)
#endif
    {
#if DMG_WORLDSPACE
        Effect* effect;
        matrix  finalCoordSys;
        vector  finalLocation;
        hmatrix hmatA, hmatB, hmatC;

        hmatMakeHMatFromMatAndVec(&hmatB, &coordsys, &location);
        hmatMakeHMatFromMatAndVec(&hmatA, &ship->rotinfo.coordsys, &ship->posinfo.position);
        hmatMultiplyHMatByHMat(&hmatC, &hmatA, &hmatB);
        matGetMatFromHMat(&finalCoordSys, &hmatC);
        hmatGetVectFromHMatrixCol4(finalLocation, hmatC);

        effect = etgEffectCreate(stat, ship,
                                 &finalLocation,
                                 &ship->posinfo.velocity,
                                 &finalCoordSys,
                                 ship->magnitudeSquared, EAF_NLips | EAF_Velocity,
                                 2, intDamage, intSize);
#else
        Effect* effect = etgEffectCreate(stat, ship, &location, &ship->posinfo.velocity,
                                         &coordsys, ship->magnitudeSquared, EAF_NLips | EAF_Velocity | EAF_OwnerCoordSys,
                                         2, intDamage, intSize);
#endif
        dmgAddEffectToShip(ship, effect, level);
    }
}

/*-----------------------------------------------------------------------------
    Name        : dmgWipeOutEffect
    Description : delete an effect completely (?)
    Inputs      : effect - the Effect
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void dmgWipeOutEffect(Effect* effect)
{
    etgEffectDelete(effect);
    univDeleteEffect(effect);
}

/*-----------------------------------------------------------------------------
    Name        : dmgForgetEffects
    Description : set all damage effects in a ship to NULL.  NOT TO BE USED TO
                  FREE DAMAGE EFFECTS in a general sense!
    Inputs      : ship - the ship to free effects from
    Outputs     : ship->showingDamage effects are forgotten
    Return      :
----------------------------------------------------------------------------*/
void dmgForgetEffects(Ship* ship)
{
    sdword level, i;

    for (level = DMG_Light; level < DMG_All; level++)
    {
        for (i = 0; i < maxEffects[level]; i++)
        {
            ship->showingDamage[level][i] = NULL;
        }
    }
}

/*-----------------------------------------------------------------------------
    Name        : dmgStopEffect
    Description : stop all effects playing at a particular level of damage
    Inputs      : ship - the Ship
                  level - DMG_All clears all ship damage effects.  DMG_Light,
                  DMG_Heavy, or DMG_Dying clear only that level of damage
    Outputs     : ship->showingDamage[level]* effects are deleted and such
    Return      :
----------------------------------------------------------------------------*/
void dmgStopEffect(Ship* ship, sdword level)
{
    sdword i;

    if (level == DMG_All)
    {
        trailMakeWobbly(ship, FALSE);

        for (level = DMG_Light; level < DMG_All; level++)
        {
            for (i = 0; i < maxEffects[level]; i++)
            {
                if (ship->showingDamage[level][i] != NULL)
                {
                    dmgWipeOutEffect(ship->showingDamage[level][i]);
                    ship->showingDamage[level][i] = NULL;
                }
            }
        }
    }
    else
    {
        for (i = 0; i < maxEffects[level]; i++)
        {
            if (ship->showingDamage[level][i] != NULL)
            {
                dmgWipeOutEffect(ship->showingDamage[level][i]);
                ship->showingDamage[level][i] = NULL;
            }
        }
    }
}

/*-----------------------------------------------------------------------------
    Name        : dmgStopSingleEffect
    Description : search the effect's owner's damage effect list and remove the provided
                  effect if it belongs to the owner
    Inputs      : effect - the Effect
    Outputs     : effect is cleared from the ship's damage effect list, but
                  IS NOT DELETED (a flag is set for the etg to do so in etgDamageDone).
                  DO NOT call this fn directly
    Return      :
----------------------------------------------------------------------------*/
void dmgStopSingleEffect(Effect* effect)
{
    sdword level, j;
    Ship* ship;

    dbgAssert(effect->owner != NULL);

    ship = (Ship*)effect->owner;

    for (level = DMG_Light; level < DMG_All; level++)
    {
        for (j = 0; j < maxEffects[level]; j++)
        {
            if (ship->showingDamage[level][j] == effect)
            {
                ship->showingDamage[level][j] = NULL;
            }
        }
    }
}

/*-----------------------------------------------------------------------------
    Name        : dmgShipThink
    Description : think fn for showing ship damage.  determines whether damage
                  should be shown
    Inputs      : ship - the Ship
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void dmgShipThink(Ship* ship)
{
    ShipStaticInfo* shipstaticinfo;
    ShipSinglePlayerGameInfo* ssinfo;
    ShipClass shipClass;
    real32 maxhealth, health, healthRatio;
    extern bool gShowDamage;

    if (!gShowDamage)
    {
        return;
    }

    if (!univSpaceObjInRenderList((SpaceObj*)ship))
    {
        return;
    }

    ssinfo = ship->shipSinglePlayerGameInfo;
    if (ssinfo != NULL && ssinfo->hsState != HS_INACTIVE)
    {
        return;
    }

    shipstaticinfo = ship->staticinfo;

    shipClass = shipstaticinfo->shipclass;

    maxhealth = shipstaticinfo->maxhealth;
    health = ship->health;
    healthRatio = health / maxhealth;

    switch (shipClass)
    {
    //strike craft
    case CLASS_Corvette:
    case CLASS_Fighter:
        if (healthRatio <= DMG_StrikeTrailLimit)
        {
            trailMakeWobbly(ship, TRUE);
        }
        else
        {
            trailMakeWobbly(ship, FALSE);
        }

        if (healthRatio <= DMG_StrikeLightLimit)
        {
            dmgPlayEffect(ship, etgDamageEffectTable[shipClass][DMG_Light], DMG_Light);
        }
        else
        {
            dmgStopEffect(ship, DMG_Light);
        }

        break;

    //everything else
    default:
        if (healthRatio <= DMG_TrailLimit)
        {
            trailMakeWobbly(ship, TRUE);
        }
        else
        {
            trailMakeWobbly(ship, FALSE);
        }

        if (healthRatio <= DMG_LightLimit)
        {
            dmgPlayEffect(ship, etgDamageEffectTable[shipClass][DMG_Light], DMG_Light);
        }
        else
        {
            dmgStopEffect(ship, DMG_Light);
        }

        if (healthRatio <= DMG_HeavyLimit)
        {
            dmgPlayEffect(ship, etgDamageEffectTable[shipClass][DMG_Heavy], DMG_Heavy);
        }
        else
        {
            dmgStopEffect(ship, DMG_Heavy);
        }

        if (healthRatio <= DMG_DyingLimit)
        {
            dmgPlayEffect(ship, etgDamageEffectTable[shipClass][DMG_Dying], DMG_Dying);
        }
        else
        {
            dmgStopEffect(ship, DMG_Dying);
        }
    }
}

/*-----------------------------------------------------------------------------
    Name        : dmgGetLights
    Description : convert mex chunks containing damage lights into something more
                  easily (and speedily) traversable
    Inputs      : ship - the Ship
    Outputs     : ship->damageLights, etc
    Return      :
----------------------------------------------------------------------------*/
void dmgGetLights(Ship* ship)
{
    vector* lightNormal;
    vector* lightPosition;
    ShipStaticInfo* shipStatic;
    MEXChunk* mexChunk;
    char name[5] = "Dmg0";
    sdword i, numChunks;

    shipStatic = ship->staticinfo;

    //count Dmg chunks
    for (i = numChunks = 0; i < 50; i++)
    {
        name[3] = '0' + i;
        mexChunk = mexGetChunk(shipStatic->staticheader.pMexData, "Dmg", name);
        if (mexChunk == NULL)
        {
            break;
        }
        else
        {
            numChunks++;
        }
    }

    if (numChunks == 0)
    {
        //no chunks, do nothing
        return;
    }

    //allocate space for chunks
    ship->nDamageLights = numChunks;
    ship->damageLights = (DamageLightStatic*)memAlloc(numChunks*sizeof(DamageLightStatic), "dmgLightStatic", NonVolatile);
    dbgAssert(ship->damageLights != NULL);

    //assign light data
    for (i = 0; i < numChunks; i++)
    {
        name[3] = '0' + i;
        mexChunk = mexGetChunk(shipStatic->staticheader.pMexData, "Dmg", name);
        dbgAssert(mexChunk != NULL);    //shouldn't be NULL

        lightNormal = &((MEXEngineChunk*)mexChunk)->normal;
        lightPosition = &((MEXEngineChunk*)mexChunk)->position;

        memcpy(&ship->damageLights[i].position, lightPosition, sizeof(vector));
        memcpy(&ship->damageLights[i].normal, lightNormal, sizeof(vector));
    }
}

/*-----------------------------------------------------------------------------
    Name        : dmgClearLights
    Description : release the memory allocated to a ship's damage lights
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void dmgClearLights(Ship* ship)
{
    if (ship->nDamageLights > 0)
    {
        dbgAssert(ship->damageLights != NULL);
        memFree(ship->damageLights);
        ship->nDamageLights = 0;
    }
    else
    {
        dbgAssert(ship->damageLights == NULL);
    }
}
