/*
** infodefaults.cpp
** Parses default lists to create default copies of actors
**
**---------------------------------------------------------------------------
** Copyright 1998-2006 Randy Heit
** 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 <assert.h>
#include "actor.h"
#include "info.h"
#include "s_sound.h"
#include "p_conversation.h"
#include "a_pickups.h"
#include "gi.h"
#include "a_artifacts.h"
#include "a_keys.h"
#include "i_system.h"
#include "r_data.h"
#include "w_wad.h"
#include "a_strifeglobal.h"
#include "thingdef.h"

void FActorInfo::BuildDefaults ()
{
	if (Class->Defaults == NULL)
	{
		Class->Defaults = new BYTE[Class->Size];
		if (Class == RUNTIME_CLASS(AActor))
		{
			memset (Class->Defaults, 0, Class->Size);
		}
		else
		{
			PClass *parent;

			parent = Class->ParentClass;
			parent->ActorInfo->BuildDefaults ();
			Class->Meta = parent->Meta;
			assert (Class->Size >= parent->Size);
			memcpy (Class->Defaults, parent->Defaults, parent->Size);
			if (Class->Size > parent->Size)
			{
				memset (Class->Defaults + parent->Size, 0, Class->Size - parent->Size);
			}
			if (parent == RUNTIME_CLASS(AActor) && OwnedStates == NULL)
			{ // Stateless actors that are direct subclasses of AActor
			  // have their spawnstate default to something that won't
			  // immediately destroy them.
				((AActor *)(Class->Defaults))->SpawnState = &AActor::States[0];
			}
		}
		ApplyDefaults (Class->Defaults);
	}
}

static FState *DefaultStates (PClass *type)
{
	FState *states = type->ActorInfo->OwnedStates;

	if (states == NULL)
	{
		do
		{
			type = type->ParentClass;
			states = type->ActorInfo->OwnedStates;
		} while (states == NULL && type != RUNTIME_CLASS(AActor));
	}
	return states;
}

static PClass *sgClass;
static BYTE *sgDefaults;

static void ApplyActorDefault (int defnum, const char *datastr, int dataint)
{
	int datasound = 0;
	FState *datastate = NULL;
	const PClass *datatype;

	if (defnum <= ADEF_LastString)
	{
		if (defnum <= ADEF_Inventory_PickupSound)
		{
			datasound = S_FindSound (datastr);
		}
	}
	else if (defnum > ADEF_LastString && dataint >= 0 && dataint < 255)
	{
		datastate = DefaultStates (sgClass) + dataint;
	}

	// Different casts for the same thing
	AActor *const actor = (AActor *)sgDefaults;
	APowerupGiver *const giver = (APowerupGiver *)sgDefaults;
	AInventory *const item = (AInventory *)sgDefaults;
	ABasicArmorBonus *const armorb = (ABasicArmorBonus *)sgDefaults;
	ABasicArmorPickup *const armorp = (ABasicArmorPickup *)sgDefaults;
	APuzzleItem *const puzzl = (APuzzleItem *)sgDefaults;
	APowerup *const power = (APowerup *)sgDefaults;
	AKey *const key = (AKey *)sgDefaults;
	AWeapon *const weapon = (AWeapon *)sgDefaults;
	ASigil *const sigil = (ASigil *)sgDefaults;
	AAmmo *const ammo = (AAmmo *)sgDefaults;
	APlayerPawn *const player = (APlayerPawn *)sgDefaults;

	switch (defnum)
	{
	case ADEF_Weapon_ProjectileType:
	case ADEF_PowerupGiver_Powerup:
		datatype = PClass::FindClass (datastr);
		if (datatype == NULL)
		{
			I_FatalError ("Unknown class %s in %s's default list", datastr, sgClass->TypeName.GetChars());
		}
		break;

	default:
		datatype = NULL;
	}

	switch (defnum)
	{
	case ADEF_SeeSound:		actor->SeeSound = datasound;	break;
	case ADEF_AttackSound:	actor->AttackSound = datasound;	break;
	case ADEF_PainSound:	actor->PainSound = datasound;	break;
	case ADEF_DeathSound:	actor->DeathSound = datasound;	break;
	case ADEF_ActiveSound:	actor->ActiveSound = datasound;	break;
	case ADEF_UseSound:		actor->UseSound = datasound;	break;
	case ADEF_Tag:
		{
			char name[256];
			int i;

			// Replace underscores with spaces
			i = 0;
			do
			{
				name[i] = datastr[i] == '_' ? ' ' : datastr[i];
			} while (datastr[i++] != 0);
			sgClass->Meta.SetMetaString (AMETA_StrifeName, name);
			break;
		}

	case ADEF_Obituary:
		sgClass->Meta.SetMetaString (AMETA_Obituary, datastr);
		break;

	case ADEF_HitObituary:
		sgClass->Meta.SetMetaString (AMETA_HitObituary, datastr);
		break;

	case ADEF_Inventory_PickupMsg:
		sgClass->Meta.SetMetaString (AIMETA_PickupMessage, datastr);
		break;

	// [BC] New property for the announcer entry to be played when the inventory item is
	// picked up.
	case ADEF_Inventory_PickupAnnouncerEntry:

		sprintf( item->szPickupAnnouncerEntry, "%s", datastr );
		break;

	case ADEF_PowerupGiver_Powerup:
		giver->PowerupType = datatype;
		break;

	case ADEF_Inventory_Icon:
		item->Icon = TexMan.AddPatch (datastr);
		if (item->Icon <= 0)
		{
			item->Icon = TexMan.AddPatch (datastr, ns_sprites);
		}
		break;

	case ADEF_XScale:			actor->xscale = dataint;			break;
	case ADEF_YScale:			actor->yscale = dataint;			break;
	case ADEF_SpawnHealth:		actor->health = dataint;			break;
	case ADEF_ReactionTime:		actor->reactiontime = dataint;		break;
	case ADEF_PainChance:		actor->PainChance = dataint;		break;
	case ADEF_Speed:			actor->Speed = dataint;				break;
	case ADEF_FloatSpeed:		actor->FloatSpeed = dataint;		break;
	case ADEF_Radius:			actor->radius = dataint;			break;
	case ADEF_Height:			actor->height = dataint;			break;
	case ADEF_Mass:				actor->Mass = dataint;				break;
	case ADEF_Damage:			actor->Damage = dataint;			break;
	case ADEF_DamageType:		actor->DamageType = dataint;		break;
	case ADEF_Flags:			actor->flags = dataint;				break;
	case ADEF_Flags2:			actor->flags2 = dataint;			break;
	case ADEF_Flags3:			actor->flags3 = dataint;			break;
	case ADEF_Flags4:			actor->flags4 = dataint;			break;
	case ADEF_Flags5:			actor->flags5 = dataint;			break;
	// [BC]
	case ADEF_FlagsST:			actor->ulSTFlags = dataint;			break;
	case ADEF_FlagsNetwork:		actor->ulNetworkFlags = dataint;	break;
	case ADEF_FlagsSet:			actor->flags |= dataint;			break;
	case ADEF_Flags2Set:		actor->flags2 |= dataint;			break;
	case ADEF_Flags3Set:		actor->flags3 |= dataint;			break;
	case ADEF_Flags4Set:		actor->flags4 |= dataint;			break;
	case ADEF_Flags5Set:		actor->flags5 |= dataint;			break;
	// [BC]
	case ADEF_FlagsSTSet:		actor->ulSTFlags |= dataint;		break;
	case ADEF_FlagsNetworkSet:	actor->ulNetworkFlags |= dataint;	break;
	case ADEF_FlagsClear:		actor->flags &= ~dataint;			break;
	case ADEF_Flags2Clear:		actor->flags2 &= ~dataint;			break;
	case ADEF_Flags3Clear:		actor->flags3 &= ~dataint;			break;
	case ADEF_Flags4Clear:		actor->flags4 &= ~dataint;			break;
	case ADEF_Flags5Clear:		actor->flags5 &= ~dataint;			break;
	// [BC]
	case ADEF_FlagsSTClear:		actor->ulSTFlags &= ~dataint;			break;
	case ADEF_FlagsNetworkClear:	actor->ulNetworkFlags &= ~dataint;	break;
	case ADEF_Alpha:			actor->alpha = dataint;				break;
	case ADEF_RenderStyle:		actor->RenderStyle = dataint;		break;
	case ADEF_RenderFlags:		actor->renderflags = dataint;		break;
	case ADEF_Translation:		actor->Translation = dataint;		break;
	case ADEF_MinMissileChance:	actor->MinMissileChance = dataint;	break;
	case ADEF_MeleeRange:		actor->meleerange = dataint;		break;
	case ADEF_MaxDropOffHeight:	actor->MaxDropOffHeight = dataint;	break;
	case ADEF_MaxStepHeight:	actor->MaxStepHeight = dataint;		break;
	case ADEF_BounceFactor:		actor->bouncefactor = dataint;		break;
	case ADEF_BounceCount:		actor->bouncecount = dataint;		break;
	case ADEF_RDFactor:			sgClass->Meta.SetMetaFixed (AMETA_RDFactor, dataint); break;
	case ADEF_FXFlags:			actor->effects = dataint;			break;

	case ADEF_SpawnState:		actor->SpawnState = datastate;		break;
	case ADEF_SeeState:			actor->SeeState = datastate;		break;
	case ADEF_PainState:		actor->PainState = datastate;		break;
	case ADEF_MeleeState:		actor->MeleeState = datastate;		break;
	case ADEF_MissileState:		actor->MissileState = datastate;	break;
	case ADEF_CrashState:		actor->CrashState = datastate;		break;
	case ADEF_DeathState:		actor->DeathState = datastate;		break;
	case ADEF_XDeathState:		actor->XDeathState = datastate;		break;
	case ADEF_BDeathState:		actor->BDeathState = datastate;		break;
	case ADEF_IDeathState:		actor->IDeathState = datastate;		break;
	case ADEF_EDeathState:		actor->EDeathState = datastate;		break;
	case ADEF_RaiseState:		actor->RaiseState = datastate;		break;
	case ADEF_WoundState:		actor->WoundState = datastate;		break;
	case ADEF_YesState:			actor->YesState = datastate;		break;
	case ADEF_NoState:			actor->NoState = datastate;			break;
	case ADEF_GreetingsState:	actor->GreetingsState = datastate;	break;

	case ADEF_StrifeType:	if (!(gameinfo.flags & GI_SHAREWARE)) StrifeTypes[dataint] = sgClass;	break;
	case ADEF_StrifeTeaserType:
		if ((gameinfo.flags & (GI_SHAREWARE|GI_TEASER2)) == (GI_SHAREWARE))
			StrifeTypes[dataint] = sgClass;
		break;
	case ADEF_StrifeTeaserType2:
		if ((gameinfo.flags & (GI_SHAREWARE|GI_TEASER2)) == (GI_SHAREWARE|GI_TEASER2))
			StrifeTypes[dataint] = sgClass;
		break;


	case ADEF_Inventory_FlagsSet:	item->ItemFlags |= dataint; break;
	case ADEF_Inventory_FlagsClear:	item->ItemFlags &= ~dataint; break;
	case ADEF_Inventory_Amount:		item->Amount = dataint;	break;
	case ADEF_Inventory_RespawnTics:item->RespawnTics = dataint; break;
	case ADEF_Inventory_MaxAmount:	item->MaxAmount = dataint; break;
	case ADEF_Inventory_DefMaxAmount:
		// In Heretic, the maximum number of artifacts of one kind you can carry is 16.
		// In Hexen, it was upped to 25.
		item->MaxAmount = gameinfo.gametype == GAME_Heretic ? 16 : 25;
		break;
	case ADEF_Inventory_PickupSound:item->PickupSound = datasound; break;

	// [BC] Fixed this. This is how thingdef.cpp does SavePercent (and shouldn't it work the same?)
	case ADEF_BasicArmorPickup_SavePercent:		armorp->SavePercent = (fixed_t)( dataint * FRACUNIT / 100.f ); break;
	case ADEF_BasicArmorPickup_SaveAmount:		armorp->SaveAmount = dataint; break;
	case ADEF_BasicArmorBonus_SavePercent:		armorb->SavePercent = dataint; break;
	case ADEF_BasicArmorBonus_SaveAmount:		armorb->SaveAmount = dataint; break;
	case ADEF_BasicArmorBonus_MaxSaveAmount:	armorb->MaxSaveAmount = dataint; break;
	case ADEF_HexenArmor_ArmorAmount:			item->Amount = dataint; break;

	case ADEF_PuzzleItem_Number:	puzzl->PuzzleItemNumber = dataint; break;

	case ADEF_PowerupGiver_EffectTics:giver->EffectTics = dataint; break;

	case ADEF_Powerup_EffectTics:	power->EffectTics = dataint; break;
	case ADEF_Powerup_Color:		power->BlendColor = dataint; break;

	case ADEF_Ammo_BackpackAmount:	ammo->BackpackAmount = dataint; break;
	case ADEF_Ammo_BackpackMaxAmount:ammo->BackpackMaxAmount = dataint; break;
	case ADEF_Ammo_DropAmount:		sgClass->Meta.SetMetaInt (AIMETA_DropAmount, dataint); break;

	case ADEF_Weapon_Flags:			weapon->WeaponFlags = dataint; break;
	case ADEF_Weapon_FlagsSet:		weapon->WeaponFlags |= dataint; break;
	case ADEF_Weapon_UpSound:		weapon->UpSound = datasound; break;
	case ADEF_Weapon_ReadySound:	weapon->ReadySound = datasound; break;
	case ADEF_Weapon_SisterType:	weapon->SisterWeaponType = fuglyname(datastr); break;
	case ADEF_Weapon_ProjectileType:weapon->ProjectileType = datatype; break;
	case ADEF_Weapon_AmmoType1:		weapon->AmmoType1 = fuglyname(datastr); break;
	case ADEF_Weapon_AmmoType2:		weapon->AmmoType2 = fuglyname(datastr); break;
	case ADEF_Weapon_AmmoGive1:		weapon->AmmoGive1 = dataint; break;
	case ADEF_Weapon_AmmoGive2:		weapon->AmmoGive2 = dataint; break;
	// [BC] Also, initialize how much ammo the weapon uses in DM. Unless explicitly
	// specified, the weapon will use the same amount of ammo in single player as it will
	// in deathmatch.
	case ADEF_Weapon_AmmoUse1:		weapon->AmmoUse1 = /*weapon->AmmoUseDM1 = */dataint; break;
	case ADEF_Weapon_AmmoUse2:		weapon->AmmoUse2 = /*weapon->AmmoUseDM2 = */dataint; break;
	// [BC] Allow different ammo amounts for DM/teamgames.
//	case ADEF_Weapon_AmmoUseDM1:	weapon->AmmoUseDM1 = dataint; break;
//	case ADEF_Weapon_AmmoUseDM2:	weapon->AmmoUseDM2 = dataint; break;
	case ADEF_Weapon_Kickback:		weapon->Kickback = dataint; break;
	case ADEF_Weapon_YAdjust:		weapon->YAdjust = (dataint<<8)>>8; break;
	case ADEF_Weapon_SelectionOrder:weapon->SelectionOrder = dataint; break;
	case ADEF_Weapon_UpState:		weapon->UpState = datastate; break;
	case ADEF_Weapon_DownState:		weapon->DownState = datastate; break;
	case ADEF_Weapon_ReadyState:	weapon->ReadyState = datastate; break;
	case ADEF_Weapon_AtkState:		weapon->AtkState = datastate; break;
	case ADEF_Weapon_HoldAtkState:	weapon->HoldAtkState = datastate; break;
	case ADEF_Weapon_AltAtkState:	weapon->AltAtkState = datastate; break;
	case ADEF_Weapon_AltHoldAtkState:weapon->AltHoldAtkState = datastate; break;
	case ADEF_Weapon_FlashState:	weapon->FlashState = datastate; break;
	case ADEF_Sigil_NumPieces:		sigil->NumPieces = dataint; break;

	// [GRB] Player class properties
	case ADEF_PlayerPawn_JumpZ:		player->JumpZ = dataint; break;
	case ADEF_PlayerPawn_ViewHeight: player->ViewHeight = dataint; break;
	case ADEF_PlayerPawn_ForwardMove1: player->ForwardMove1 = dataint; break;
	case ADEF_PlayerPawn_ForwardMove2: player->ForwardMove2 = dataint; break;
	case ADEF_PlayerPawn_SideMove1:	player->SideMove1 = dataint; break;
	case ADEF_PlayerPawn_SideMove2:	player->SideMove2 = dataint; break;
	case ADEF_PlayerPawn_ColorRange: sgClass->Meta.SetMetaInt (APMETA_ColorRange, dataint); break;
	case ADEF_PlayerPawn_CrouchSprite: player->crouchsprite = GetSpriteIndex(datastr);	break;
	case ADEF_PlayerPawn_SpawnMask:	player->SpawnMask = dataint; break;
	case ADEF_PlayerPawn_MorphWeapon: player->MorphWeapon = FName(datastr); break;

	case ADEF_PlayerPawn_DisplayName:
		sgClass->Meta.SetMetaString (APMETA_DisplayName, datastr);
		break;
	case ADEF_PlayerPawn_SoundClass:
		sgClass->Meta.SetMetaString (APMETA_SoundClass, datastr);
		break;
	case ADEF_PlayerPawn_ScoreIcon:
		player->ScoreIcon = TexMan.AddPatch (datastr);
		if (player->ScoreIcon <= 0)
		{
			player->ScoreIcon = TexMan.AddPatch (datastr, ns_sprites);
		}
		break;
	}
}

#ifndef _MSC_VER
void ApplyActorDefault (int defnum, const char *datastr)
{
	ApplyActorDefault (defnum, datastr, 0);
}
void ApplyActorDefault (int defnum, int dataint)
{
	ApplyActorDefault (defnum, 0, dataint);
}
#endif

void FActorInfo::ApplyDefaults (BYTE *defaults)
{
	sgClass = Class;
	sgDefaults = defaults;

#if _MSC_VER
	const BYTE *parser = DefaultList;

	const char *datastr = NULL;
	int dataint = 0;
	int defnum;
	int deftype;

	while ((defnum = *(WORD *)parser) != ADEF_EOL)
	{
		deftype = defnum >> (16-2);
		defnum &= 0x3FFF;
		parser += 2;
		if (defnum <= ADEF_LastString)
		{
			datastr = (const char *)parser;
			parser = (const BYTE *)datastr + strlen (datastr) + 1;
		}
		else
		{
			switch (deftype)
			{
			case 0:		// byte
				dataint = *parser;
				parser++;
				break;

			case 1:		// byte * FRACUNIT;
				dataint = *parser << FRACBITS;
				parser++;
				break;

			case 2:		// word
				dataint = *(WORD *)parser;
				parser += 2;
				break;

			case 3:		// long
				dataint = *(DWORD *)parser;
				parser += 4;
				break;
			}
			defnum &= ~ADEFTYPE_MASK;
		}

		ApplyActorDefault (defnum, datastr, dataint);
	}
#else
	DefaultsConstructor ();
#endif
	// Anything that is CountKill is also a monster, even if it doesn't specify it.
	if (((AActor *)defaults)->flags & MF_COUNTKILL)
	{
		((AActor *)defaults)->flags3 |= MF3_ISMONSTER;
	}
	// Any default projectile in Raven's games produces blood splatter
	if (gameinfo.gametype & GAME_Raven && ((AActor *)defaults)->flags & MF_MISSILE)
	{
		((AActor *)defaults)->flags5 |= MF5_BLOODSPLATTER;
	}
}
