// Project Sword & Gun Copyright © 2021 Kevin Poretti

#include "SNGTypes.generated.h"
#pragma once

#define SNG_SURFACE_Default				SurfaceType_Default
#define SNG_SURFACE_Flesh				SurfaceType1
#define SNG_SURFACE_FleshVulnerable		SurfaceType2
#define SNG_SURFACE_Stone				SurfaceType3
#define SNG_SURFACE_Dirt				SurfaceType4
#define SNG_SURFACE_Water				SurfaceType5
#define SNG_SURFACE_Metal				SurfaceType6
#define SNG_SURFACE_Wood				SurfaceType7
#define SNG_SURFACE_Glass				SurfaceType8
#define SNG_SURFACE_Grass				SurfaceType9

/**
 *	Specifies special reactions that can happen when damage is received 
 */
UENUM(BlueprintType)
enum class ESNGReactionType : uint8
{
	/** No special reactions when receiving damage */
	None			UMETA(DisplayName="No Reaction"),
	
	/**
	 *	After being damaged, if the receiver collides with the wall they will stick to the location they impacted the
	 *	wall for some duration
	 */
	WallSplat		UMETA(DisplayName="Wall Splat"),
	
	/**
	 *	After being damaged, if the receiver collides with the wall they will be launched from the location they
	 *	impacted the wall and in the direction of the normal of the wall
	 */
	WallBounce		UMETA(DisplayName="Wall Bounce"),
	
	/**
	 *	After being damaged, if the receiver collides with the ground they will slide along from the location they
	 *	impacted the ground and in the direction they were travelling when they landed
	 */
	GroundSlide		UMETA(DisplayName="Ground Slide"),
	
	/**
	 *	After being damaged, if the receiver collides with the ground they will be launchedfrom the location they
	 *	impacted the ground and in the direction of the normal of the ground
	 */
	GroundBounce	UMETA(DisplayName="Ground Bounce"),
	
	/** Receiver sticks the the location they impacted the ground at regardless of what forces were present when landing */ 
	HardKnockdown	UMETA(DisplayName="Hard Knockdown"),

	ERT_MAX			UMETA(Hidden),
};

/**
 *	Used to specify how a receiver should be launched when damage is received, if applicable
 */
UENUM(BlueprintType)
enum class ESNGLaunchType : uint8
{
	/** Does not launch */
	None					UMETA(DisplayName="Does Not Launch"),
	/** Only launches if the receiver is already in the air */
	OnlyInAir				UMETA(DisplayName="Only In Air"),
	/** Only launches if the receiver is already in the air AND already stunned */
	OnlyInAirAndStunned		UMETA(DisplayName="Only In Air And Stunned"),
	/** Always launches */
	Always					UMETA(DisplayName="Always"),
	
	ELT_MAX					UMETA(Hidden),
};

/**
 *	Specifies how the launch direction should be calculated when damage is received, if applicable
 */
UENUM(BlueprintType)
enum class ESNGLaunchDirectionType : uint8
{
	/** Launch direction should be treated as a world direction */
	Absolute			UMETA(DisplayName="Absolute"),
	/** Receiver is launched in the direction of the causer rotation when the hit occured */  
	UseCauserRotation	UMETA(DisplayName="Use Causer Rotation"),
	/** Receiver is launched in the Look At Rotation between the receiver and the causer */  
	UseLookAtRotation	UMETA(DisplayName="Use Look At Rotation"),
	
	ELDT_MAX			UMETA(Hidden),
};

/**
 *	Specifies how hit stop should apply to the causer and receiver of damage
 */
UENUM(BlueprintType)
enum class ESNGHitstopApplicationType : uint8
{
	/** Not applied */
	None			UMETA(DisplayName="Not Applied"),
	/** Only applies to the damage causer */
	CauserOnly		UMETA(DisplayName="Causer Only"),
	/** Only applies to the damage receiver */
	ReceiverOnly	UMETA(DisplayName="Receiver Only"),
	/** Applies to both damage causer and receiver */ 
	Both			UMETA(DisplayName="Both"),

	EHAT_MAX		UMETA(Hidden), 
};

/**
 *	Hold various parameters of a hit from either a melee of ranged weapon.
 *	
 *	This is passed to damage receivers through the TakeDamage function where it is used to determine how the receiver
 *	should react to the damage event.
 */
USTRUCT(BlueprintType)
struct FSNGHitData
{
	GENERATED_BODY()

	/** Amount of base damage to apply to the damage receiver */
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	float BaseDamage;

	/** How long until the receiver of this hit is able to act again */
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	float StunTime;

	/** How hit stop should be applied for this hit */
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	ESNGHitstopApplicationType HitstopApplicationType;

	/** How long the hit stop should last, if applicable */
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	float StopTime;

	/** How long the hit slow should last, if applicable */
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	float SlowTime;

	/** How long the hit slow should last, if applicable */
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	ESNGReactionType ReactionType;

	/** How the receiver of this hit should be launched */
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	ESNGLaunchType LaunchType;

	/** Magnitude of the force the receiver should be launched at, if applicable */
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	float LaunchForce;

	/** How the launch direction should be calculated when this hit is received, if applicable */
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	ESNGLaunchDirectionType LaunchDirectionType;

	// NOTE(kevin): This should probably be specified as euler angles. It is too cumbersome to tweak this as a unit vector...
	/** Launch direction as a unit vector */
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	FVector LaunchDirection;

	// NOTE(kevin): This doesn't belong here. This isn't really data about the hit this has more to do with whatever action may cause this hit.
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	float TraceRadius;

	FSNGHitData()
		: BaseDamage(0.0)
		, StunTime(0.0)
		, HitstopApplicationType(ESNGHitstopApplicationType::None)
		, StopTime(0.0)
		, SlowTime(0.0)
		, ReactionType(ESNGReactionType::None)
		, LaunchType(ESNGLaunchType::None)
		, LaunchForce(0.0)
		, LaunchDirectionType(ESNGLaunchDirectionType::Absolute)
		, LaunchDirection(FVector::ZeroVector)
		, TraceRadius(5.0)
	{}
};

/**
 *	Damage subclass that handles damage with game specific hit properties.
 *
 *  This is mainly a wrapper class used to pass HitData through Unreal's damage API.
 *  When an event consumer receives this damage event through the TakeDamage function being called, it will take the
 *  HitData properties and put them into our own SNGEvent which will then be consumed by the receiver's StateMachine.
 */
USTRUCT()
struct SWORDNGUN_API FSNGDamageEvent : public FDamageEvent
{
	GENERATED_BODY()

	/** The hit parameters for the action that caused this damage event */
	UPROPERTY()
	FSNGHitData HitData;

	/** The type of the surface that was damaged */
	UPROPERTY()
	TEnumAsByte<EPhysicalSurface> SurfaceType;

	/** Location where the Actor being damaged was hit */
	UPROPERTY()
	FVector ImpactPoint;
	
	/** ID for this class. NOTE this must be unique for all damage events. */
	static const int32 ClassID = 3;

	virtual int32 GetTypeID() const override { return FSNGDamageEvent::ClassID; };
	virtual bool IsOfType(int32 InID) const override { return (FSNGDamageEvent::ClassID == InID) || FDamageEvent::IsOfType(InID); };

	// NOTE(kevin) do we need this??
	/** Simple API for common cases where we are happy to assume a single hit is expected, even though damage event may have multiple hits. */
	virtual void GetBestHitInfo(AActor const* HitActor, AActor const* HitInstigator, struct FHitResult& OutHitInfo, FVector& OutImpulseDir) const override;

	FSNGDamageEvent()
		: HitData()
		, SurfaceType(EPhysicalSurface::SurfaceType_Default)
		, ImpactPoint(FVector::ZeroVector)
	{}
};

UENUM()
enum class EActionState : uint8
{
	AS_Pressed			UMETA(DiplayName="Pressed"),
	AS_Held				UMETA(DiplayName="Held"),
	AS_Released			UMETA(DiplayName="Released"),
	AS_MAX				UMETA(Hidden),
};

/**
 *	Specifies the type of the event.
 */
UENUM(BlueprintType)
enum class ESNGEventType : uint8
{
	/** Used for specifying dummy events */
	None				UMETA(Hidden),
	/** A command to a possessed pawn by a controller */
	CharacterCommand	UMETA(DiplayName="Character Command"),
	/** Damage */
	Damage				UMETA(DiplayName="Damage"),
	
	EET_MAX				UMETA(Hidden),
};

/**
 *	Base struct for different event data structs to inherit from.
 */
USTRUCT(BlueprintType)
struct FSNGEventData
{
	GENERATED_BODY()

	FSNGEventData()
	{}
};

/**
 *	An event triggered by a controller (either through player inputs or AI decisions) which specifies commands for the
 *	possessed pawn to process.  
 */
USTRUCT(BlueprintType)
struct FSNGCharacterCommandData : public FSNGEventData
{
	GENERATED_BODY()

	/** Name of the command*/
	UPROPERTY(BlueprintReadOnly)
	FName CommandName;

	UPROPERTY(BlueprintReadOnly)
	EActionState ActionState;
	
	FSNGCharacterCommandData()
		: CommandName(NAME_None), ActionState(EActionState::AS_Released)
	{
	}
};

/**
 *	An event triggered by a hit from a damage causer which both specifies that a certain character/player was hit and
 *	specifies various parameters for the damage receiver to process.  
 */
USTRUCT(BlueprintType)
struct FSNGDamageData : public FSNGEventData
{
	GENERATED_BODY()

	/** The hit parameters for the action that caused this damage event */ 
	UPROPERTY(BlueprintReadOnly)
	FSNGHitData HitData;

	/**
	 *	The controller that caused this event. In almost all circumstances this is the controller possessing the Actor
	 *	pointed to by the DamageCauser member.
	 */
	UPROPERTY(BlueprintReadOnly)
	AController* EventInstigator;

	/** The actor that caused the damage event to trigger. Will usually be a pawn. */
	UPROPERTY(BlueprintReadOnly)
    AActor* DamageCauser;

	/** The type of the surface that was damaged */
	UPROPERTY(BlueprintReadOnly)
	TEnumAsByte<EPhysicalSurface> SurfaceType;

	/** Location where the Actor being damaged was hit */
	UPROPERTY(BlueprintReadOnly)
	FVector ImpactPoint;
	
	FSNGDamageData()
		: HitData()
		, EventInstigator(nullptr)
		, DamageCauser(nullptr)
		, SurfaceType(EPhysicalSurface::SurfaceType_Default)
		, ImpactPoint(FVector::ZeroVector)
	{}
};

/**
 *	Struct containing information about an event to be processed by an event consumer.
 */
USTRUCT(BlueprintType)
struct FSNGEvent
{
	GENERATED_BODY()

	/** Type of the event. You should read this to determine which member of the FSNGEventData union to access. */
	UPROPERTY(BlueprintReadOnly)
	ESNGEventType EventType;
	
	// FSNGEventData discriminated union
	union
	{
		/** Parameters for commands to be processed by a possessed pawn */
		FSNGCharacterCommandData CommandData;
		/** Parameters for damage to be processed by the damage receiver */
		FSNGDamageData DamageData;
	};
	
	FSNGEvent()
		: EventType(ESNGEventType::None)
		, CommandData()
		, DamageData()
	{
	}
};



