UnrealFightingFramework/Source/UnrealFightingFramework/State/FFState.cpp

250 lines
7.7 KiB
C++

// Unreal Fighting Framework by Kevin Poretti
#include "FFState.h"
// FF includes
#include "FFStateMachineComponent.h"
#include "IFFStateAvatarInterface.h"
#include "IFFStateOwnerInterface.h"
// UE includes
#include "Components/SkeletalMeshComponent.h"
void UFFState::Init(const FFFStateContext& InStateContext)
{
OnInit(InStateContext);
}
bool UFFState::CanTransition(const FFFStateContext& InStateContext)
{
/**
* Check to see if avatar implements StateAvatarInterface
* if so then
* Check if the avatar is in the correct stance to perform this action
* Check if the state is enabled
*
* Check to see if the owner implements the StateOwnerInterface
* Check input conditions if there are any
*
* If at least one input condition is good then we can transition
* so return true otherwise return false
*/
IFFStateAvatarInterface* SAI = Cast<IFFStateAvatarInterface>(InStateContext.Avatar);
if(SAI)
{
if(!(SAI->CheckStance(StanceRequired) && SAI->CheckStateEnabled(StateType)))
{
return false;
}
}
else
{
UE_LOG(LogTemp, Error, TEXT("CanTransition :: Avatar of FFFStateContext does not implement IFFStateAvatarInterface"));
return false;
}
IFFStateOwnerInterface* SOI = Cast<IFFStateOwnerInterface>(InStateContext.Owner);
if(SOI)
{
if(!SOI->CheckInputSequences(InputSequences))
{
return false;
}
}
else
{
UE_LOG(LogTemp, Error, TEXT("CanTransition :: Owner of FFFStateContext does not implement IFFStateOwnerInterface"));
return false;
}
return OnCanTransition(InStateContext) && !bIsFollowupState;
}
void UFFState::Enter(const FFFStateContext& InStateContext)
{
PlayMontage(InStateContext);
if(bDisableMostRecentInputOnEntry)
{
IFFStateOwnerInterface* PC = Cast<IFFStateOwnerInterface>(InStateContext.Owner);
if(PC)
{
PC->DisableMostRecentInput();
}
}
OnEnter(InStateContext);
}
void UFFState::Exit(const FFFStateContext& InStateContext, EFFStateFinishReason StateFinishReason)
{
if(InStateContext.Avatar &&
(bStopMontageOnStateEnd && StateFinishReason == EFFStateFinishReason::SFT_DurationMetOrExceeded || StateFinishReason == EFFStateFinishReason::SFT_Interrupted) ||
(bStopMontageOnMovementModeChange && StateFinishReason == EFFStateFinishReason::SFT_NotInReqMovementMode))
{
StopCurrentAnimMontage(InStateContext);
}
OnExit(InStateContext);
}
void UFFState::Update(float OneFrame, const FFFStateContext& InStateContext)
{
IFFStateOwnerInterface* SOI = Cast<IFFStateOwnerInterface>(InStateContext.Owner);
// TODO: maybe a check/asset is better because I can't think of a reason that not having the Owner implement
// IFFStateOwnerInterface is valid. Although there could be objects that have state that don't necessarily directly
// respond to input and don't have an Owner/controller responsible for them. Or they do have an owner but are associated
// with another Avatar like a character who is owned by a player/player controller.
if(!SOI)
{
UE_LOG(LogTemp, Error, TEXT("Owner of FFFStateContext does not implement IFFStateOwnerInterface"));
return;
}
for(FFFInputEventHandler InputHandler : InputHandlers)
{
if(SOI->CheckInputSequence(InputHandler.RequiredSequence))
{
if(InputHandler.Delegate.ExecuteIfBound(InStateContext))
{
UE_LOG(LogTemp, Error,
TEXT("Trying to execute an input handler delegate on %s but it is not bound"),
*Name.ToString());
}
}
}
OnUpdate(OneFrame, InStateContext);
if(bStateHasDuration && InStateContext.Parent->GetTicksInState() >= StateDuration)
{
Finish(InStateContext, EFFStateFinishReason::SFT_DurationMetOrExceeded);
}
}
void UFFState::Landed(const FHitResult& Hit, const FFFStateContext& InStateContext)
{
OnLanded(Hit, InStateContext);
// TODO: might want to also finish the state here by default and have the subclasses overwrite
// that behavior in the few cases we don't want to finish the state when we land
}
void UFFState::MovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode,
EMovementMode NewMovementMode, uint8 NewCustomMode, const FFFStateContext& InStateContext)
{
OnMovementModeChanged(PrevMovementMode, PreviousCustomMode,
NewMovementMode, NewCustomMode, InStateContext);
}
void UFFState::AttackHit(const FHitResult& HitResult, const FFFStateContext& InStateContext)
{
OnAttackHit(HitResult, InStateContext);
}
void UFFState::HitTaken(const FFFStateContext& InStateContext)
{
OnHitTaken(InStateContext);
}
void UFFState::BlockTaken(const FFFStateContext& InStateContext)
{
OnBlockTaken(InStateContext);
}
void UFFState::Finish(const FFFStateContext& InStateContext, EFFStateFinishReason StateFinishReason)
{
// TODO: I really don't like having to pass this state finish reason into this GoToEntryState
// function, which then passes it to GoToState, which then passes it back to the state through
// it's "Exit" function all so we can stop the current anim montage when we exit from a state if
// the appropriate flags are set. I think having this state finish reason is good but I may want
// to rethink the way we handle logic for ending a state and which class is in charge of handling
// what
if(GetFollowupState(InStateContext) != NAME_None)
{
InStateContext.Parent->GoToState(GetFollowupState(InStateContext), StateFinishReason);
}
else
{
InStateContext.Parent->GoToEntryState(StateFinishReason);
}
}
FName UFFState::GetFollowupState_Implementation(const FFFStateContext& InStateContext)
{
return FollowupState;
}
void UFFState::PlayMontage(const FFFStateContext& InStateContext)
{
// TODO: think of a better way to handle optionally playing montages other than the one set as MontageToPlay
OnPlayMontage(InStateContext);
}
void UFFState::OnInit_Implementation(const FFFStateContext& InStateContext)
{
}
bool UFFState::OnCanTransition_Implementation(const FFFStateContext& InStateContext)
{
return true;
}
// TODO: think of a better way to handle optionally playing montages other than the one set as MontageToPlay
void UFFState::OnPlayMontage_Implementation(const FFFStateContext& InStateContext)
{
if(InStateContext.Avatar)
{
USkeletalMeshComponent* SMC = InStateContext.Avatar->GetComponentByClass<USkeletalMeshComponent>();
if(SMC && MontageToPlay)
{
SMC->GetAnimInstance()->Montage_Play(MontageToPlay);
}
}
}
UWorld* UFFState::GetWorld() const
{
UFFStateMachineComponent* SMC = Cast<UFFStateMachineComponent>(GetOuter());
if(SMC)
{
return SMC->GetWorld();
}
return nullptr;
}
void UFFState::StopCurrentAnimMontage(const FFFStateContext& InStateContext)
{
USkeletalMeshComponent* SMC = InStateContext.Avatar->GetComponentByClass<USkeletalMeshComponent>();
if(SMC)
{
// TODO: Need a system for keeping track what anim montages were played during a state
// so we can stop only those when the state ends (and if the bStopMontageOnStateEnd flag is set)
// ALSO, this will be important for when we need to fast forward an animation when the game rollsback
// and we have to resimulate to the current local tick
// For now just stop all anim montages
UAnimMontage* CurrAnim = SMC->GetAnimInstance()->GetCurrentActiveMontage();
if(CurrAnim)
{
SMC->GetAnimInstance()->Montage_Stop(CurrAnim->BlendOut.GetBlendTime());
}
}
}