// Copyright Team Lumi. All Rights Reserved. #include "LumiCharacter.h" #include "Engine/LocalPlayer.h" #include "Engine/UserInterfaceSettings.h" #include "Camera/CameraComponent.h" #include "Components/CapsuleComponent.h" #include "GameFramework/SpringArmComponent.h" #include "GameFramework/Controller.h" #include "InputActionValue.h" #include "GameFramework/CharacterMovementComponent.h" #include "WizardingCentral/Utils/OneDollar/UniStrokeDataTable.h" #include "WizardingCentral/Utils/OneDollar/UniStrokeResult.h" DEFINE_LOG_CATEGORY(LogLumiCharacter); // 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(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(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(); static ConstructorHelpers::FObjectFinder UnistrokeTemplatesTable( TEXT("/Game/DataTables/SpellTemplates.SpellTemplates") ); if (UnistrokeTemplatesTable.Succeeded()) { SpellTemplatesTable = UnistrokeTemplatesTable.Object; LoadSpellTemplates(); } static ConstructorHelpers::FClassFinder TrainingWidget( TEXT("/Game/UI/WP_SpellTrainWidget.WP_SpellTrainWidget") ); SpellTrainWidgetClass = TrainingWidget.Class; } 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( LogLumiCharacter, Error, TEXT("ALumiCharacter::BeginPlay() Failed to get LumiController!") ); return; } // Add Spell Overlay if (SpellOverlayClass) { SpellOverlay = CreateWidget(LumiController, SpellOverlayClass); if (SpellOverlay) { SpellOverlay->AddToViewport(); SpellOverlay->SetVisibility(ESlateVisibility::Visible); } } if (SpellTrainWidgetClass) { SpellTrainWidget = CreateWidget(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(LogLumiCharacter, Log, TEXT("Grab Triggered")); } void ALumiCharacter::OnJumpActionStarted(const FInputActionValue& Value) { // Broadcast that the jump has started OnJumpStarted.Broadcast(); UE_LOG(LogLumiCharacter, 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(); if (Controller != nullptr) { // 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 (bIsDrawing) { return; } // input is a Vector2D const FVector2D LookAxisVector = Value.Get(); if (Controller != nullptr) { // add yaw and pitch input to controller AddControllerYawInput(LookAxisVector.X); AddControllerPitchInput(LookAxisVector.Y); } } void ALumiCharacter::OnDefaultStance_Implementation() { UE_LOG(LogLumiCharacter, Log, TEXT("OnDefaultStance_Implementation()")); } void ALumiCharacter::OnMagicStance_Implementation() { UE_LOG(LogLumiCharacter, Log, TEXT("OnMagicStance_Implementation()")); } void ALumiCharacter::OnSpellActionStarted() { bIsDrawing = true; if (SpellSystemState != Training) { SpellSystemState = Casting; } } void ALumiCharacter::OnSpellActionCompleted() { if (SpellSystemState == Casting) { SpellSystemState = bIsTraining ? Training : Recognizing; } bIsDrawing = false; switch (SpellSystemState) { case Recognizing: CastSpell(); break; case Training: ShowTrainWidget(); break; default: // DO NOTHING break; } } void ALumiCharacter::OnSpellPositionActionTriggered(const FInputActionValue& Value) { if (!bIsDrawing) { return; } if (SpellSystemState != Casting) { return; } APlayerController* LumiController = nullptr; if (!GetPlayerController(LumiController)) { UE_LOG( LogLumiCharacter, Error, TEXT("ALumiCharacter::OnSpellPositionActionTriggered() Failed to get LumiController!") ); } // 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::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) { UE_LOG( LogLumiCharacter, Error, TEXT("ALumiCharacter::ShowTrainWidget() SpellTrainWidget is nullptr!") ); return; } UE_LOG( LogLumiCharacter, Warning, TEXT("ALumiCharacter::ShowTrainWidget() SpellTrainWidget is OK!") ); return; } void ALumiCharacter::AddSpellTemplateToTable(const FString& Name) { FUniStrokeDataTable Entry; Entry.Name = Name; Entry.Points = *SpellOverlay->GetSpellPoints(); const FString ContextString = "Spell Templates"; TArray Rows; SpellTemplatesTable->GetAllRows(ContextString, Rows); SpellTemplatesTable->AddRow(FName(*FString::FromInt(Rows.Num() + 1)), Entry); SpellRecognizer->AddTemplate(Name, Entry.Points); HideTrainWidget(); } bool ALumiCharacter::GetPlayerController(APlayerController*& LumiController) const { LumiController = Cast(this->Controller); return LumiController != nullptr; } void ALumiCharacter::LoadSpellTemplates() const { if (SpellTemplatesTable == nullptr) { UE_LOG( LogLumiCharacter, Error, TEXT("ALumiCharacter::LoadSpellTemplates() SpellTemplatesTable is nullptr!") ); return; } const FString ContextString = "Spell Templates"; TArray Rows; SpellTemplatesTable->GetAllRows(ContextString, Rows); for (const FUniStrokeDataTable* const T : Rows) { SpellRecognizer->AddTemplate(T->Name, T->Points); } } void ALumiCharacter::CastSpell() { const TArray* CurrentPoints = SpellOverlay->GetSpellPoints(); const FUniStrokeResult Result = SpellRecognizer->Recognize(*CurrentPoints, false); bool bHasMatch = false; if (Result.Score < 0.8f) { UE_LOG( LogLumiCharacter, Log, TEXT("ALumiCharacter::CastSpell() No match found!") ); } else { bHasMatch = true; UE_LOG( LogLumiCharacter, Log, TEXT("ALumiCharacter::CastSpell() Spell: %s"), *Result.Name ); } // TODO: Handle Spell Casting GEngine->AddOnScreenDebugMessage( -1, 10.0f, FColor::Black, bHasMatch ? "Spell: " + Result.Name : "No Match Found" ); // 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); }