/*
** a_health.cpp
** All health items
**
**---------------------------------------------------------------------------
** Copyright 2000-2016 Randy Heit
** Copyright 2006-2016 Cheistoph Oelckers
** 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 "d_player.h"
#include "a_morph.h"
#include "a_health.h"
#include "serializer.h"
// [BB] New #includes.
#include "cooperative.h"
#include "sv_commands.h"

//---------------------------------------------------------------------------
//
// FUNC P_GiveBody
//
// Returns false if the body isn't needed at all.
//
//---------------------------------------------------------------------------

bool P_GiveBody (AActor *actor, int num, int max)
{
	if (actor->health <= 0 || (actor->player != NULL && actor->player->playerstate == PST_DEAD))
	{ // Do not heal dead things.
		return false;
	}

	// [EP] DummyPlayer shouldn't receive anything.
	if (actor->player == COOP_GetVoodooDollDummyPlayer())
		return false;

	player_t *player = actor->player;

	num = clamp(num, -65536, 65536);	// prevent overflows for bad values
	if (player != NULL)
	{
		// Max is 0 by default, preserving default behavior for P_GiveBody()
		// calls while supporting AHealth.
		if (max <= 0)
		{
			// [BC] Apply the prosperity power.
			if ( player->cheats & CF_PROSPERITY )
				max = deh.MaxSoulsphere + 50;
			// [BC] Add the player's max. health bonus to his max.
			else
				max = static_cast<APlayerPawn*>(actor)->GetMaxHealth() + player->mo->stamina + player->lMaxHealthBonus;
			// [MH] First step in predictable generic morph effects
 			if (player->morphTics)
 			{
				if (player->MorphStyle & MORPH_FULLHEALTH)
				{
					if (!(player->MorphStyle & MORPH_ADDSTAMINA))
					{
						max -= player->mo->stamina;
					}
				}
				else // old health behaviour
				{
					max = MAXMORPHHEALTH;
					if (player->MorphStyle & MORPH_ADDSTAMINA)
					{
						max += player->mo->stamina;
					}
				}
 			}
		}
		// [RH] For Strife: A negative body sets you up with a percentage
		// of your full health.
		if (num < 0)
		{
			num = max * -num / 100;
			if (player->health < num)
			{
				player->health = num;
				actor->health = num;
				return true;
			}
		}
		else if (num > 0)
		{
			if (player->health < max)
			{
				num = int(num * G_SkillProperty(SKILLP_HealthFactor));
				if (num < 1) num = 1;
				player->health += num;
				if (player->health > max)
				{
					player->health = max;
				}
				actor->health = player->health;
				return true;
			}
		}
	}
	else
	{
		// Parameter value for max is ignored on monsters, preserving original
		// behaviour on AHealth as well as on existing calls to P_GiveBody().
		max = actor->SpawnHealth();
		if (num < 0)
		{
			num = max * -num / 100;
			if (actor->health < num)
			{
				actor->health = num;
				return true;
			}
		}
		else if (actor->health < max)
		{
			actor->health += num;
			if (actor->health > max)
			{
				actor->health = max;
			}
			return true;
		}
	}
	return false;
}

DEFINE_ACTION_FUNCTION(AActor, GiveBody)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_INT(num);
	PARAM_INT_DEF(max);

	// [BB] This is handled server-side.
	if ( NETWORK_InClientMode() )
		return 0;

	// [BB]
	const int oldHealth = self->health;

	// [BB] Save return value.
	const bool result = P_GiveBody(self, num, max);

	// [BB] Inform clients
	if ( ( NETWORK_GetState( ) == NETSTATE_SERVER ) && self->player && ( oldHealth != self->health ) )
		SERVERCOMMANDS_SetPlayerHealth( self->player - players );

	ACTION_RETURN_BOOL(result);
}

//===========================================================================
//
// Classes
//
//===========================================================================

IMPLEMENT_CLASS(PClassHealth, false, false)
IMPLEMENT_CLASS(AHealth, false, false)
DEFINE_FIELD(AHealth, PrevHealth)

//===========================================================================
//
// PClassHealth Constructor
//
//===========================================================================

PClassHealth::PClassHealth()
{
	LowHealth = 0;
}

//===========================================================================
//
// PClassHealth :: DeriveData
//
//===========================================================================

void PClassHealth::DeriveData(PClass *newclass)
{
	assert(newclass->IsKindOf(RUNTIME_CLASS(PClassHealth)));
	Super::DeriveData(newclass);
	PClassHealth *newc = static_cast<PClassHealth *>(newclass);
	
	newc->LowHealth = LowHealth;
	newc->LowHealthMessage = LowHealthMessage;
}


//===========================================================================
//
// AHealth :: PickupMessage
//
//===========================================================================
FString AHealth::PickupMessage ()
{
	int threshold = GetClass()->LowHealth;

	if (PrevHealth < threshold)
	{
		FString message = GetClass()->LowHealthMessage;

		if (message.IsNotEmpty())
		{
			return message;
		}
	}
	return Super::PickupMessage();
}

//===========================================================================
//
// AHealth :: TryPickup
//
//===========================================================================

bool AHealth::TryPickup (AActor *&other)
{
	PrevHealth = other->player != NULL ? other->player->health : other->health;

	// P_GiveBody adds one new feature, applied only if it is possible to pick up negative health:
	// Negative values are treated as positive percentages, ie Amount -100 means 100% health, ignoring max amount.
	if (P_GiveBody(other, Amount, MaxAmount))
	{
		GoAwayAndDie();
		return true;
	}
	return false;
}

IMPLEMENT_CLASS(AHealthPickup, false, false)

DEFINE_FIELD(AHealthPickup, autousemode)

//===========================================================================
//
// AHealthPickup :: CreateCopy
//
//===========================================================================

AInventory *AHealthPickup::CreateCopy (AActor *other)
{
	AInventory *copy = Super::CreateCopy (other);
	copy->health = health;
	return copy;
}

//===========================================================================
//
// AHealthPickup :: CreateTossable
//
//===========================================================================

AInventory *AHealthPickup::CreateTossable ()
{
	AInventory *copy = Super::CreateTossable ();
	if (copy != NULL)
	{
		copy->health = health;
	}
	return copy;
}

//===========================================================================
//
// AHealthPickup :: HandlePickup
//
//===========================================================================

bool AHealthPickup::HandlePickup (AInventory *item)
{
	// HealthPickups that are the same type but have different health amounts
	// do not count as the same item.
	if (item->health == health)
	{
		return Super::HandlePickup (item);
	}
	return false;
}

//===========================================================================
//
// AHealthPickup :: Use
//
//===========================================================================

bool AHealthPickup::Use (bool pickup)
{
	return P_GiveBody (Owner, health, 0);
}

//===========================================================================
//
// AHealthPickup :: Serialize
//
//===========================================================================

void AHealthPickup::Serialize(FSerializer &arc)
{
	Super::Serialize(arc);
	auto def = (AHealthPickup*)GetDefault();
	arc("autousemode", autousemode, def->autousemode);
}


// [BC] New definition here for pickups that increase your max. health.
IMPLEMENT_CLASS( AMaxHealth, false, false )

//===========================================================================
//
// AMaxHealth :: TryPickup
//
//===========================================================================

bool AMaxHealth::TryPickup( AActor *&pOther )
{
	LONG		lMax;
	player_t	*pPlayer;

	pPlayer = pOther->player;
	if ( pPlayer != NULL )
	{
		// Increase the player's max. health.
		pPlayer->lMaxHealthBonus += Amount;

		// If it exceeds the maximum amount allowable by this object, cap it. The default
		// is 50.
		if ( pPlayer->lMaxHealthBonus > MaxAmount )
			pPlayer->lMaxHealthBonus = MaxAmount;
	}

	// [BC] The rest of this is based on AHealth::TryPickup. It just has to be different
	// because max. health pickups use the "health" property to determine the maximum health
	// the player's health can be after picking up this object, minus stamina and the player's
	// max. health bonus.

	lMax = health;
	if ( pPlayer != NULL )
	{
		PrevHealth = pPlayer->health;

		// Apply the prosperity power.
		if ( pPlayer->cheats & CF_PROSPERITY )
			lMax = deh.MaxSoulsphere + 50;
		// If a maximum allowable health isn't specified, then use the player's base health,
		// plus any bonuses to his max. health.
		else if ( lMax == 0 )
		{
			// [BC] Add the player's max. health bonus to his max.
			lMax = static_cast<APlayerPawn *>( pOther )->GetMaxHealth( ) + pPlayer->mo->stamina + pPlayer->lMaxHealthBonus;
			if ( pPlayer->morphTics )
				lMax = MAXMORPHHEALTH;
		}
		// Apply max. health bonus to the max. allowable health.
		else
			lMax = lMax + pPlayer->lMaxHealthBonus;

		// The player's health already exceeds his maximum allowable health.
		if ( pPlayer->health >= lMax )
		{
			// You should be able to pick up the Doom health bonus even if
			// you are already full on health.
			if ( ItemFlags & IF_ALWAYSPICKUP )
			{
				GoAwayAndDie( );
				return ( true );
			}

			// We have no use for the object, so don't pick it up.
			return ( false );
		}
		
		// Give the player the health.
		pPlayer->health += Amount;
		if ( pPlayer->health > lMax )
			pPlayer->health = lMax;

		// Make the player's body's health match the player's health.
		pPlayer->mo->health = pPlayer->health;
	}
	else
	{
		PrevHealth = INT_MAX;

		// If we actually received health from the object, or we should always pick it up
		// even if it does no good, destroy the health object.
		if (( P_GiveBody( pOther, Amount )) || ( ItemFlags & IF_ALWAYSPICKUP ))
		{
			GoAwayAndDie( );
			return ( true );
		}

		// Return false because we did not pickup the object.
		return ( false );
	}

	// We picked up the object, so destroy it.
	GoAwayAndDie( );
	return ( true );
}
