gzdoom/src/g_shared/sbar_mugshot.cpp
Randy Heit 63e26df7b2 - Added player MugShotMaxHealth property. Negative values use the player's
max health as the mug shot max health, zero uses 100 as the mug shot max
  health, and positive values used directly as the mug shot max health.


SVN r1757 (trunk)
2009-08-07 04:20:28 +00:00

499 lines
13 KiB
C++

/*
** sbar_mugshot.cpp
**
** Draws customizable mugshots for the status bar.
**
**---------------------------------------------------------------------------
** Copyright 2008 Braden Obrzut
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include "r_defs.h"
#include "r_main.h"
#include "m_random.h"
#include "d_player.h"
#include "d_event.h"
#include "sbar.h"
#include "sbarinfo.h"
#include "templates.h"
#define ST_RAMPAGEDELAY (2*TICRATE)
#define ST_MUCHPAIN 20
TArray<FMugShotState> MugShotStates;
//===========================================================================
//
// FMugShotFrame constructor
//
//===========================================================================
FMugShotFrame::FMugShotFrame()
{
}
//===========================================================================
//
// FMugShotFrame destructor
//
//===========================================================================
FMugShotFrame::~FMugShotFrame()
{
}
//===========================================================================
//
// FMugShotFrame :: GetTexture
//
// Assemble a graphic name with the specified prefix and return the FTexture.
//
//===========================================================================
FTexture *FMugShotFrame::GetTexture(const char *default_face, FPlayerSkin *skin, int random, int level,
int direction, bool uses_levels, bool health2, bool healthspecial, bool directional)
{
int index = !directional ? random % Graphic.Size() : direction;
if ((unsigned int)index > Graphic.Size() - 1)
{
index = Graphic.Size() - 1;
}
FString sprite(skin->face[0] != 0 ? skin->face : default_face, 3);
sprite += Graphic[index];
if (uses_levels) //change the last character to the level
{
if (!health2 && (!healthspecial || index == 1))
{
sprite.LockBuffer()[2 + Graphic[index].Len()] += level;
}
else
{
sprite.LockBuffer()[1 + Graphic[index].Len()] += level;
}
sprite.UnlockBuffer();
}
return TexMan[TexMan.CheckForTexture(sprite, 0, true)];
}
//===========================================================================
//
// MugShotState default constructor
//
//===========================================================================
FMugShotState::FMugShotState()
{
}
//===========================================================================
//
// MugShotState named constructor
//
//===========================================================================
FMugShotState::FMugShotState(FName name)
{
State = name;
bUsesLevels = false;
bHealth2 = false;
bHealthSpecial = false;
bDirectional = false;
bFinished = true;
Random = M_Random();
}
//===========================================================================
//
// MugShotState destructor
//
//===========================================================================
FMugShotState::~FMugShotState()
{
}
//===========================================================================
//
// FMugShotState :: Tick
//
//===========================================================================
void FMugShotState::Tick()
{
if (Time == -1)
{ //When the delay is negative 1, stay on this frame indefinitely.
return;
}
if (Time != 0)
{
Time--;
}
else if (Position < Frames.Size() - 1)
{
Position++;
Time = Frames[Position].Delay;
Random = M_Random();
}
else
{
bFinished = true;
}
}
//===========================================================================
//
// FMugShotState :: Reset
//
//===========================================================================
void FMugShotState::Reset()
{
Time = Frames[0].Delay;
Position = 0;
bFinished = false;
Random = M_Random();
}
//===========================================================================
//
// FindMugShotState
//
//===========================================================================
FMugShotState *FindMugShotState(FName state)
{
for (unsigned int i = 0; i < MugShotStates.Size(); i++)
{
if (MugShotStates[i].State == state)
return &MugShotStates[i];
}
return NULL;
}
//===========================================================================
//
// FindMugShotStateIndex
//
// Used to allow replacements of states
//
//===========================================================================
int FindMugShotStateIndex(FName state)
{
for (unsigned int i = 0; i < MugShotStates.Size(); i++)
{
if (MugShotStates[i].State == state)
return i;
}
return -1;
}
//===========================================================================
//
// FMugShot constructor
//
//===========================================================================
FMugShot::FMugShot()
{
FaceHealth = -1;
bEvilGrin = false;
bNormal = true;
bDamageFaceActive = false;
bOuchActive = false;
CurrentState = NULL;
RampageTimer = 0;
LastDamageAngle = 1;
}
//===========================================================================
//
// FMugShot :: Tick
//
// Do some stuff related to the mug shot that has to be done at 35fps
//
//===========================================================================
void FMugShot::Tick(player_t *player)
{
if (CurrentState != NULL)
{
CurrentState->Tick();
if (CurrentState->bFinished)
{
bNormal = true;
bOuchActive = false;
CurrentState = NULL;
}
}
if ((player->cmd.ucmd.buttons & (BT_ATTACK|BT_ALTATTACK)) && !(player->cheats & (CF_FROZEN | CF_TOTALLYFROZEN)) && player->ReadyWeapon)
{
if (RampageTimer != ST_RAMPAGEDELAY)
{
RampageTimer++;
}
}
else
{
RampageTimer = 0;
}
FaceHealth = player->health;
}
//===========================================================================
//
// FMugShot :: SetState
//
// Sets the mug shot state and resets it if it is not the state we are
// already on. Wait_till_done is basically a priority variable; when set to
// true the state won't change unless the previous state is finished. Reset
// overrides the behavior of only switching when the state is not the one we
// are already on.
// Returns true if the requested state was switched to or is already playing,
// and false if the requested state could not be set.
//
//===========================================================================
bool FMugShot::SetState(const char *state_name, bool wait_till_done, bool reset)
{
// Search for full name.
FMugShotState *state = FindMugShotState(FName(state_name, true));
if (state == NULL)
{
// Search for initial name, if the full one contains a dot.
const char *dot = strchr(state_name, '.');
if (dot != NULL)
{
state = FindMugShotState(FName(state_name, dot - state_name, true));
}
if (state == NULL)
{
// Requested state does not exist, so do nothing.
return false;
}
}
bNormal = false; //Assume we are not setting god or normal for now.
bOuchActive = false;
if (state != CurrentState)
{
if (!wait_till_done || CurrentState == NULL || CurrentState->bFinished)
{
CurrentState = state;
state->Reset();
return true;
}
return false;
}
else if(reset)
{
state->Reset();
return true;
}
return true;
}
//===========================================================================
//
// FMugShot :: UpdateState
//
//===========================================================================
int FMugShot::UpdateState(player_t *player, int stateflags)
{
int i;
angle_t badguyangle;
angle_t diffang;
FString full_state_name;
if (player->health > 0)
{
if (bEvilGrin && !(stateflags & DRAWMUGSHOT_DISABLEGRIN))
{
if (player->bonuscount)
{
SetState("grin", false);
return 0;
}
else if (CurrentState == NULL)
{
bEvilGrin = false;
}
}
if (player->damagecount &&
// Now go in if pain is disabled but we think ouch will be shown (and ouch is not disabled!)
(!(stateflags & DRAWMUGSHOT_DISABLEPAIN) || (((FaceHealth != -1 && FaceHealth - player->health > ST_MUCHPAIN) || bOuchActive) && !(stateflags & DRAWMUGSHOT_DISABLEOUCH))))
{
int damage_angle = 1;
if (player->attacker && player->attacker != player->mo)
{
if (player->mo != NULL)
{
// The next 12 lines are from the Doom statusbar code.
badguyangle = R_PointToAngle2(player->mo->x, player->mo->y, player->attacker->x, player->attacker->y);
if (badguyangle > player->mo->angle)
{
// whether right or left
diffang = badguyangle - player->mo->angle;
i = diffang > ANG180;
}
else
{
// whether left or right
diffang = player->mo->angle - badguyangle;
i = diffang <= ANG180;
} // confusing, aint it?
if (i && diffang >= ANG45)
{
damage_angle = 0;
}
else if (!i && diffang >= ANG45)
{
damage_angle = 2;
}
}
}
bool use_ouch = false;
if (((FaceHealth != -1 && FaceHealth - player->health > ST_MUCHPAIN) || bOuchActive) && !(stateflags & DRAWMUGSHOT_DISABLEOUCH))
{
use_ouch = true;
full_state_name = "ouch.";
}
else
{
full_state_name = "pain.";
}
full_state_name += player->LastDamageType;
if (SetState(full_state_name, false, true))
{
bDamageFaceActive = (CurrentState != NULL);
LastDamageAngle = damage_angle;
bOuchActive = use_ouch;
}
return damage_angle;
}
if (bDamageFaceActive)
{
if (CurrentState == NULL)
{
bDamageFaceActive = false;
}
else
{
bool use_ouch = false;
if (((FaceHealth != -1 && player->health - FaceHealth > ST_MUCHPAIN) || bOuchActive) && !(stateflags & DRAWMUGSHOT_DISABLEOUCH))
{
use_ouch = true;
full_state_name = "ouch.";
}
else
{
full_state_name = "pain.";
}
full_state_name += player->LastDamageType;
if (SetState(full_state_name))
{
bOuchActive = use_ouch;
}
return LastDamageAngle;
}
}
if (RampageTimer == ST_RAMPAGEDELAY && !(stateflags & DRAWMUGSHOT_DISABLERAMPAGE))
{
SetState("rampage", !bNormal); //If we have nothing better to show, use the rampage face.
return 0;
}
if (bNormal)
{
bool good;
if ((player->cheats & CF_GODMODE) || (player->mo != NULL && player->mo->flags2 & MF2_INVULNERABLE))
{
good = SetState((stateflags & DRAWMUGSHOT_ANIMATEDGODMODE) ? "godanimated" : "god");
}
else
{
good = SetState("normal");
}
if (good)
{
bNormal = true; //SetState sets bNormal to false.
}
}
}
else
{
if (!(stateflags & DRAWMUGSHOT_XDEATHFACE) || !(player->cheats & CF_EXTREMELYDEAD))
{
full_state_name = "death.";
}
else
{
full_state_name = "xdeath.";
}
full_state_name += player->LastDamageType;
SetState(full_state_name);
bNormal = true; //Allow the face to return to alive states when the player respawns.
}
return 0;
}
//===========================================================================
//
// FMugShot :: GetFace
//
// Updates the status of the mug shot and returns the current face texture.
//
//===========================================================================
FTexture *FMugShot::GetFace(player_t *player, const char *default_face, int accuracy, int stateflags)
{
int angle = UpdateState(player, stateflags);
int level = 0;
int max = player->mo->MugShotMaxHealth;
if (max < 0)
{
max = player->mo->GetMaxHealth();
}
else if (max == 0)
{
max = 100;
}
while (player->health < (accuracy - 1 - level) * (max / accuracy))
{
level++;
}
if (CurrentState != NULL)
{
FPlayerSkin *skin = &skins[player->morphTics ? player->MorphedPlayerClass : player->userinfo.skin];
return CurrentState->GetCurrentFrameTexture(default_face, skin, level, angle);
}
return NULL;
}