250 lines
7.7 KiB
C++
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());
|
|
}
|
|
}
|
|
}
|