Files
WizardingHub/Source/WizardingCentral/Characters/LumiCharacter.cpp
T
2025-05-15 01:34:38 -04:00

375 lines
9.5 KiB
C++

// Copyright Team Lumi. All Rights Reserved.
#include "LumiCharacter.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Engine/LocalPlayer.h"
#include "Engine/UserInterfaceSettings.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "GameFramework/SpringArmComponent.h"
#include "InputActionValue.h"
#include "WizardingCentral/Utils/OneDollar/UniStrokeDataTable.h"
#include "WizardingCentral/Utils/OneDollar/UniStrokeResult.h"
static const FString SpellTemplatesTablePath = TEXT("/Game/DataTables/SpellTemplates.SpellTemplates");
// Sets default values
ALumiCharacter::ALumiCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
// PrimaryActorTick.bCanEverTick = true;
// Set size for collision capsule
GetCapsuleComponent()->InitCapsuleSize(35.0f, 90.0f);
// Allow double jumping
JumpMaxCount = 2;
// Don't rotate when the controller rotates. Let that just affect the camera.
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
// Create a camera boom (pulls in towards the player if there is a collision)
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 400.0f; // The camera follows at this distance behind the character
CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller
CameraBoom->SocketOffset = DefaultBoomOffset;
// Create a follow camera
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
// Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm
// Spell System
bIsDrawing = false;
SpellRecognizer = new FUniStrokeRecognizer();
if (SpellTemplatesTable == nullptr)
{
SpellTemplatesTable = Cast<UDataTable>(
StaticLoadObject(
UDataTable::StaticClass(),
nullptr,
*SpellTemplatesTablePath
)
);
}
LoadSpellTemplates();
}
ELumiStance ALumiCharacter::GetCurrentStance() const
{
return CurrentStance;
}
// Called when the game starts or when spawned
void ALumiCharacter::BeginPlay()
{
Super::BeginPlay();
APlayerController* LumiController = nullptr;
if (!GetPlayerController(LumiController))
{
UE_LOG(LogTemp, Error, TEXT("ALumiCharacter::BeginPlay() Failed to get LumiController!"));
return;
}
// Add Spell Overlay
if (SpellOverlayClass)
{
SpellOverlay = CreateWidget<USpellOverlay>(LumiController, SpellOverlayClass);
if (SpellOverlay)
{
SpellOverlay->AddToViewport();
SpellOverlay->SetVisibility(ESlateVisibility::Visible);
}
}
if (SpellTrainWidgetClass)
{
SpellTrainWidget = CreateWidget<UUserWidget>(LumiController, SpellTrainWidgetClass);
if (SpellTrainWidget)
{
SpellTrainWidget->AddToViewport();
SpellTrainWidget->SetVisibility(ESlateVisibility::Hidden);
}
}
bIsTraining = false;
SpellSystemState = Idle;
}
/*
// Called every frame
void ALumiCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
*/
bool ALumiCharacter::IsLastJump() const
{
return JumpCurrentCountPreJump == JumpMaxCount - 1;
}
void ALumiCharacter::OnGrabActionTriggered(const FInputActionValue& Value)
{
UE_LOG(LogTemp, Log, TEXT("Grab Triggered"));
}
void ALumiCharacter::OnJumpActionStarted(const FInputActionValue& Value)
{
// Broadcast that the jump has started
OnJumpStarted.Broadcast();
UE_LOG(LogTemp, Log, TEXT("Jump Started"));
Jump();
}
void ALumiCharacter::OnJumpActionOngoing(const FInputActionValue& Value)
{
}
void ALumiCharacter::OnJumpActionCompleted(const FInputActionValue& Value)
{
StopJumping();
}
void ALumiCharacter::OnMoveActionTriggered(const FInputActionValue& Value)
{
// input is a Vector2D
const FVector2D MovementVector = Value.Get<FVector2D>();
if (Controller == nullptr)
return;
// find out which way is forward
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
// get forward vector
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
// get right vector
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
// add movement
AddMovementInput(ForwardDirection, MovementVector.Y);
AddMovementInput(RightDirection, MovementVector.X);
}
void ALumiCharacter::OnLookActionTriggered(const FInputActionValue& Value)
{
if (SpellSystemState == Casting)
return;
// input is a Vector2D
const FVector2D LookAxisVector = Value.Get<FVector2D>();
if (Controller != nullptr)
{
// add yaw and pitch input to controller
AddControllerYawInput(LookAxisVector.X);
AddControllerPitchInput(LookAxisVector.Y);
}
}
void ALumiCharacter::OnDefaultStance_Implementation()
{
UE_LOG(LogTemp, Log, TEXT("OnDefaultStance_Implementation()"));
}
void ALumiCharacter::OnMagicStance_Implementation()
{
UE_LOG(LogTemp, Log, TEXT("OnMagicStance_Implementation()"));
}
void ALumiCharacter::OnSpellActionStarted()
{
if (SpellSystemState != Training)
SpellSystemState = Casting;
}
void ALumiCharacter::OnSpellActionCompleted()
{
if (SpellSystemState == Casting)
SpellSystemState = bIsTraining ? Training : Recognizing;
UE_LOG(LogTemp, Log, TEXT("OnSpellActionCompleted() Current State: %d"), SpellSystemState);
switch (SpellSystemState)
{
case Recognizing:
CastSpell();
break;
case Training:
ShowTrainWidget();
break;
default:
break;
}
}
void ALumiCharacter::OnSpellPositionActionTriggered(const FInputActionValue& Value)
{
if (SpellSystemState != Casting)
return;
APlayerController* LumiController = nullptr;
if (!GetPlayerController(LumiController))
return;
// Get Mouse Position
float MouseX = 0.0f, MouseY = 0.0f;
LumiController->GetMousePosition(MouseX, MouseY);
// Normalize Mouse Position
const FVector2D MousePosition = FVector2D(MouseX, MouseY);
const FVector2D ViewportSize = FVector2D(GEngine->GameViewport->Viewport->GetSizeXY());
const float ViewportScale = GetDefault<UUserInterfaceSettings>(
UUserInterfaceSettings::StaticClass()
)->GetDPIScaleBasedOnSize(FIntPoint(ViewportSize.X, ViewportSize.Y));
const FVector2D ScaledPosition = MousePosition / ViewportScale;
// Add Point to Spell Overlay
SpellOverlay->AddPoint(ScaledPosition);
}
void ALumiCharacter::SetIsTraining(const bool bTraining)
{
this->bIsTraining = bTraining;
// TODO: Remove Debug Message
if (bTraining)
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Black, "Training Mode On");
else
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Black, "Training Mode Off");
}
void ALumiCharacter::HideTrainWidget()
{
if (SpellTrainWidget == nullptr)
return;
SpellOverlay->Clear();
SpellTrainWidget->SetVisibility(ESlateVisibility::Hidden);
SpellSystemState = Idle;
}
void ALumiCharacter::ShowTrainWidget()
{
if (SpellTrainWidget == nullptr)
return;
SpellTrainWidget->SetVisibility(ESlateVisibility::Visible);
}
void ALumiCharacter::AddSpellTemplateToTable(const FString& Name)
{
FUniStrokeDataTable Entry;
Entry.Name = Name;
Entry.Points = *SpellOverlay->GetSpellPoints();
const FString ContextString = "Spell Templates";
TArray<FUniStrokeDataTable*> Rows;
SpellTemplatesTable->GetAllRows<FUniStrokeDataTable>(ContextString, Rows);
SpellTemplatesTable->AddRow(FName(*FString::FromInt(Rows.Num() + 1)), Entry);
SpellRecognizer->AddTemplate(Name, Entry.Points);
HideTrainWidget();
}
void ALumiCharacter::OnSpellCast_Implementation(const FString& SpellName)
{
UE_LOG(
LogTemp,
Log,
TEXT("ALumiCharacter::OnSpellCast_Implementation() Spell: %s"),
*SpellName
);
}
bool ALumiCharacter::GetPlayerController(APlayerController*& LumiController) const
{
LumiController = Cast<APlayerController>(this->Controller);
return LumiController != nullptr;
}
void ALumiCharacter::LoadSpellTemplates() const
{
if (SpellTemplatesTable == nullptr)
return;
const FString ContextString = "Spell Templates";
TArray<FUniStrokeDataTable*> Rows;
SpellTemplatesTable->GetAllRows<FUniStrokeDataTable>(ContextString, Rows);
for (const FUniStrokeDataTable* const T : Rows)
SpellRecognizer->AddTemplate(T->Name, T->Points);
}
void ALumiCharacter::CastSpell()
{
const TArray<FVector2D>* CurrentPoints = SpellOverlay->GetSpellPoints();
const FUniStrokeResult Result = SpellRecognizer->Recognize(*CurrentPoints);
bool bHasMatch = false;
if (Result.Score < 0.8f)
{
UE_LOG(LogTemp, Log, TEXT("ALumiCharacter::CastSpell() No match found!"));
}
else
{
bHasMatch = true;
UE_LOG(LogTemp, Log, TEXT("ALumiCharacter::CastSpell() Spell: %s"), *Result.Name);
OnSpellCast(Result.Name);
}
// TODO: Handle Spell Casting
// Clean up
SpellOverlay->Clear();
SpellSystemState = Idle;
}
ELumiStance ALumiCharacter::SetCurrentStance(const ELumiStance NewStance)
{
const ELumiStance OldStance = CurrentStance;
UCharacterMovementComponent* Movement = GetCharacterMovement();
if (NewStance == ELumiStance::Default)
{
Movement->bOrientRotationToMovement = true;
Movement->bUseControllerDesiredRotation = false;
OnDefaultStance();
}
else if (NewStance == ELumiStance::Magic)
{
Movement->bOrientRotationToMovement = false;
Movement->bUseControllerDesiredRotation = true;
OnMagicStance();
}
CurrentStance = NewStance;
return OldStance;
}
void ALumiCharacter::OnSpellTrainSwitchActionCompleted()
{
SetIsTraining(!this->bIsTraining);
}