diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index 9d4e0d0..12846f4 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -1952,6 +1952,15 @@ void UHyperTwistCoachDashboardWidget::HandleStartPreviewCaseClicked() StartSelectedGuidancePreviewCaseRun(); } +void UHyperTwistCoachDashboardWidget::HandleStartMethodDrillFollowUpClicked() +{ + StartActiveMethodDrillFollowUpRun(DefaultSessionId + TEXT("_method_drill_follow_up")); + SyncRetainedRunRecap(); + SyncSelectedQueueEntry(); + SyncSelectedAttempt(); + UpdateDashboardPresentation(); +} + void UHyperTwistCoachDashboardWidget::HandleCompleteRunClicked() { CompleteActiveTrainingRun(false); @@ -2414,6 +2423,46 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() TEXT("CoachGuidancePreviewCaseDetail"), 2 ); + MethodDrillDiagnosticsHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachMethodDrillDiagnosticsHeader"), + 2 + ); + MethodDrillDiagnosticsStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachMethodDrillDiagnosticsStatus"), + 2 + ); + MethodDrillDiagnosticsDetailTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachMethodDrillDiagnosticsDetail"), + 8 + ); + UHorizontalBox* MethodDrillActionRow = WidgetTree->ConstructWidget( + UHorizontalBox::StaticClass(), + TEXT("CoachDashboardMethodDrillActions") + ); + if (MethodDrillActionRow != nullptr) + { + if (UVerticalBoxSlot* MethodDrillActionRowSlot = RootLayout->AddChildToVerticalBox(MethodDrillActionRow)) + { + MethodDrillActionRowSlot->SetPadding(FMargin(0.0f, 4.0f, 0.0f, 0.0f)); + } + + UTextBlock* StartMethodDrillFollowUpLabelRaw = nullptr; + StartMethodDrillFollowUpButton = HyperTwistCoachDashboardWidgetInternal::AddButton( + WidgetTree, + MethodDrillActionRow, + TEXT("StartMethodDrillFollowUpButton"), + TEXT("StartMethodDrillFollowUpButtonLabel"), + TEXT("Launch Drill Follow-Up"), + StartMethodDrillFollowUpLabelRaw + ); + StartMethodDrillFollowUpButtonLabel = StartMethodDrillFollowUpLabelRaw; + } UHorizontalBox* RunRecapActionRow = WidgetTree->ConstructWidget( UHorizontalBox::StaticClass(), TEXT("CoachDashboardRunRecapActions") @@ -3408,6 +3457,13 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() { StartPreviewCaseButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleStartPreviewCaseClicked); } + if (StartMethodDrillFollowUpButton != nullptr) + { + StartMethodDrillFollowUpButton->OnClicked.AddDynamic( + this, + &UHyperTwistCoachDashboardWidget::HandleStartMethodDrillFollowUpClicked + ); + } if (CompleteRunButton != nullptr) { CompleteRunButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleCompleteRunClicked); @@ -5086,6 +5142,158 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() : (bCanContinueCurrentRun ? (CachedRunSummary.AttemptCount > 0 ? TEXT("Start Next Attempt") : TEXT("Start First Attempt")) : TransitionLaunchLabel)); + const bool bHasMethodDrillRun = CachedMethodDrillRunState.IsStructurallyValid(); + const bool bHasMethodDrillDiagnosticPacket = CachedMethodDrillDiagnosticPacket.IsStructurallyValid(); + const bool bHasMethodDrillFollowUpTemplate = CachedMethodDrillFollowUpTemplate.IsStructurallyValid(); + const bool bHasMethodDrillFollowUpDeck = CachedMethodDrillFollowUpDeck.IsStructurallyValid(); + const bool bMethodDrillLifecycleClosed = + bHasMethodDrillRun + && (CachedMethodDrillRunState.LifecycleState == EHyperTwistTrainingDrillLifecycleState::Completed + || CachedMethodDrillRunState.LifecycleState == EHyperTwistTrainingDrillLifecycleState::Aborted); + const bool bCanLaunchMethodDrillFollowUp = + !bHasLiveAttemptTimer + && !bHasActiveRun + && bMethodDrillLifecycleClosed + && bHasMethodDrillDiagnosticPacket + && bHasMethodDrillFollowUpTemplate + && bHasMethodDrillFollowUpDeck; + const auto BuildCaseIdPreview = [](const TArray& CaseIds) -> FString + { + if (CaseIds.Num() <= 0) + { + return TEXT("none"); + } + + TArray PreviewCaseIds; + const int32 PreviewCount = FMath::Min(CaseIds.Num(), 3); + PreviewCaseIds.Reserve(PreviewCount + 1); + for (int32 CaseIndex = 0; CaseIndex < PreviewCount; ++CaseIndex) + { + PreviewCaseIds.Add(CaseIds[CaseIndex]); + } + if (CaseIds.Num() > PreviewCount) + { + PreviewCaseIds.Add(FString::Printf(TEXT("+%d more"), CaseIds.Num() - PreviewCount)); + } + return FString::Join(PreviewCaseIds, TEXT(", ")); + }; + const FHyperTwistTrainingMethodDrillCaseComparisonPacket* PrimaryMethodDrillCase = + bHasMethodDrillDiagnosticPacket && CachedMethodDrillDiagnosticPacket.CasePackets.Num() > 0 + ? &CachedMethodDrillDiagnosticPacket.CasePackets[0] + : nullptr; + const FString MethodDrillFocusPreview = BuildCaseIdPreview( + CachedMethodDrillDiagnosticPacket.FocusCaseIds); + const FString MethodDrillTemplateLabel = bHasMethodDrillFollowUpTemplate + ? (CachedMethodDrillFollowUpTemplate.Title.IsEmpty() + ? CachedMethodDrillFollowUpTemplate.TemplateId + : CachedMethodDrillFollowUpTemplate.Title) + : TEXT("n/a"); + FString MethodDrillDiagnosticsStatusLine; + if (!bHasMethodDrillDiagnosticPacket) + { + MethodDrillDiagnosticsStatusLine = bHasMethodDrillRun + ? FString::Printf( + TEXT("Method drill: %s is active, but no diagnostic packet is available yet."), + *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName( + CachedMethodDrillRunState.LifecycleState)) + : TEXT("Method drill: no active drill diagnostics are available yet."); + } + else + { + MethodDrillDiagnosticsStatusLine = FString::Printf( + TEXT("Method drill: %s | %s | review %s | focus %d | pending %d | assisted %d | repeat burden %d | deferred %d | follow-up %s"), + CachedMethodDrillDiagnosticPacket.StageLabel.IsEmpty() + ? (CachedMethodDrillDiagnosticPacket.DrillId.IsEmpty() + ? TEXT("drill") + : *CachedMethodDrillDiagnosticPacket.DrillId) + : *CachedMethodDrillDiagnosticPacket.StageLabel, + *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName( + CachedMethodDrillDiagnosticPacket.DiagnosticState), + *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName( + CachedMethodDrillDiagnosticPacket.RecommendedReviewGrade), + CachedMethodDrillDiagnosticPacket.FocusCaseIds.Num(), + CachedMethodDrillDiagnosticPacket.PendingCaseCount, + CachedMethodDrillDiagnosticPacket.AssistedCompletionCount, + CachedMethodDrillDiagnosticPacket.RepeatBurdenCaseCount, + CachedMethodDrillDiagnosticPacket.DeferredCaseCount, + bHasMethodDrillFollowUpTemplate ? TEXT("ready") : TEXT("not ready") + ); + } + FString MethodDrillDiagnosticsDetailLine; + if (!bHasMethodDrillDiagnosticPacket) + { + MethodDrillDiagnosticsDetailLine = + TEXT("Method drill detail: a completed or resumed method-drill run will surface its owned diagnostic packet and follow-up template here."); + } + else + { + MethodDrillDiagnosticsDetailLine = FString::Printf( + TEXT("Lifecycle: %s | mode: %s | visibility: %s | boundary: %s | template: %s | kind: %s | suggested mode: %s | selection: %s | recommended %d | focus: %s%s%s"), + *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName( + CachedMethodDrillDiagnosticPacket.LifecycleState), + *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName( + CachedMethodDrillDiagnosticPacket.DrillMode), + *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName( + CachedMethodDrillDiagnosticPacket.VisibilityPolicy), + CachedMethodDrillDiagnosticPacket.BoundaryReason.IsEmpty() + ? TEXT("n/a") + : *CachedMethodDrillDiagnosticPacket.BoundaryReason, + *MethodDrillTemplateLabel, + bHasMethodDrillFollowUpTemplate + ? *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName( + CachedMethodDrillFollowUpTemplate.TemplateKind) + : TEXT("n/a"), + bHasMethodDrillFollowUpTemplate + ? *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName( + CachedMethodDrillFollowUpTemplate.SuggestedMode) + : TEXT("n/a"), + bHasMethodDrillFollowUpTemplate + ? *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName( + CachedMethodDrillFollowUpTemplate.SuggestedSelectionPolicy) + : TEXT("n/a"), + bHasMethodDrillFollowUpTemplate ? CachedMethodDrillFollowUpTemplate.RecommendedCaseCount : 0, + *MethodDrillFocusPreview, + PrimaryMethodDrillCase != nullptr ? TEXT(" || Top case: ") : TEXT(""), + PrimaryMethodDrillCase != nullptr + ? *FString::Printf( + TEXT("%s [%s | grade %s | attempts %d | repeats %d | reveals %d]"), + *PrimaryMethodDrillCase->CaseId, + *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName( + PrimaryMethodDrillCase->DiagnosticState), + *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName( + PrimaryMethodDrillCase->RecommendedReviewGrade), + PrimaryMethodDrillCase->AttemptCount, + PrimaryMethodDrillCase->RepeatCount, + PrimaryMethodDrillCase->RevealCount) + : TEXT("") + ); + } + FString StartMethodDrillFollowUpLabel = TEXT("Launch Drill Follow-Up"); + if (bHasLiveAttemptTimer) + { + StartMethodDrillFollowUpLabel = TEXT("Drill Follow-Up Busy"); + } + else if (bHasActiveRun) + { + StartMethodDrillFollowUpLabel = TEXT("Clear Run For Drill Follow-Up"); + } + else if (bHasMethodDrillRun && !bMethodDrillLifecycleClosed) + { + StartMethodDrillFollowUpLabel = TEXT("Finish Drill Before Follow-Up"); + } + else if (bHasMethodDrillDiagnosticPacket && CachedMethodDrillDiagnosticPacket.bNeedsFollowUpTemplate + && !bHasMethodDrillFollowUpTemplate) + { + StartMethodDrillFollowUpLabel = TEXT("Follow-Up Template Missing"); + } + else if (bHasMethodDrillFollowUpTemplate && !bHasMethodDrillFollowUpDeck) + { + StartMethodDrillFollowUpLabel = TEXT("No Drill Follow-Up Deck"); + } + else if (!bHasMethodDrillDiagnosticPacket) + { + StartMethodDrillFollowUpLabel = TEXT("No Drill Follow-Up"); + } const bool bHasCurrentRunRecap = bHasActiveRun && (CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Completed || CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Aborted); @@ -6669,6 +6877,26 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() { GuidancePreviewCaseDetailTextBlock->SetText(FText::FromString(GuidancePreviewCaseDetailLine)); } + if (MethodDrillDiagnosticsHeaderTextBlock != nullptr) + { + MethodDrillDiagnosticsHeaderTextBlock->SetText(FText::FromString(TEXT("[Method Drill Diagnostics]"))); + } + if (MethodDrillDiagnosticsStatusTextBlock != nullptr) + { + MethodDrillDiagnosticsStatusTextBlock->SetText(FText::FromString(MethodDrillDiagnosticsStatusLine)); + } + if (MethodDrillDiagnosticsDetailTextBlock != nullptr) + { + MethodDrillDiagnosticsDetailTextBlock->SetText(FText::FromString(MethodDrillDiagnosticsDetailLine)); + } + if (StartMethodDrillFollowUpButtonLabel != nullptr) + { + StartMethodDrillFollowUpButtonLabel->SetText(FText::FromString(StartMethodDrillFollowUpLabel)); + } + if (StartMethodDrillFollowUpButton != nullptr) + { + StartMethodDrillFollowUpButton->SetIsEnabled(bCanLaunchMethodDrillFollowUp); + } if (StartQueuedRunButtonLabel != nullptr) { StartQueuedRunButtonLabel->SetText(FText::FromString(QueuedRunLabel)); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp index b4ad341..1b9f95d 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp @@ -17,10 +17,24 @@ bool UHyperTwistTrainingPanelWidget::HasActiveTrainingRun() const return UHyperTwistTrainingRuntimeLibrary::HasActiveTrainingRun(const_cast(this)); } +bool UHyperTwistTrainingPanelWidget::HasActiveMethodDrillRun() const +{ + return UHyperTwistTrainingRuntimeLibrary::HasActiveMethodDrillRun( + const_cast(this)); +} + void UHyperTwistTrainingPanelWidget::RefreshTrainingState() { CachedRunState = UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingRunState(this); CachedRunSummary = UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingRunSummary(this); + CachedMethodDrillRunState = UHyperTwistTrainingRuntimeLibrary::GetActiveMethodDrillRunState(this); + CachedMethodDrillSummary = UHyperTwistTrainingRuntimeLibrary::GetActiveMethodDrillSummary(this); + CachedMethodDrillDiagnosticPacket = + UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingMethodDrillDiagnosticPacket(this); + CachedMethodDrillFollowUpTemplate = + UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingMethodDrillFollowUpTemplate(this); + CachedMethodDrillFollowUpDeck = + UHyperTwistTrainingRuntimeLibrary::BuildActiveMethodDrillFollowUpDeck(this); CachedCoachSignals = UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachSignals(this); CachedCoachBrief = UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachBrief(this); CachedCoachFollowUpBrief = UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachFollowUpBrief(this); @@ -118,6 +132,14 @@ FHyperTwistTrainingDeck UHyperTwistTrainingPanelWidget::BuildActiveCoachFollowUp return CachedCoachFollowUpDeck; } +FHyperTwistTrainingDeck UHyperTwistTrainingPanelWidget::BuildActiveMethodDrillFollowUpDeck() +{ + CachedMethodDrillFollowUpDeck = + UHyperTwistTrainingRuntimeLibrary::BuildActiveMethodDrillFollowUpDeck(this); + RefreshTrainingState(); + return CachedMethodDrillFollowUpDeck; +} + FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartCoachRecommendedRun( const int32 MaxCases, const FString& StartActionSourceLabel @@ -226,6 +248,17 @@ bool UHyperTwistTrainingPanelWidget::PromoteCoachQueueEntry(const FString& Entry return bSucceeded; } +FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartActiveMethodDrillFollowUpRun( + const FString& SessionId +) +{ + LastStepResult = FHyperTwistTrainingRunStepResult(); + CachedRunState = + UHyperTwistTrainingRuntimeLibrary::StartActiveMethodDrillFollowUpRun(this, SessionId); + RefreshTrainingState(); + return CachedRunState; +} + FHyperTwistTrainingRepositoryRoundTripVerification UHyperTwistTrainingPanelWidget::VerifyTrainingRepositoryRoundTrip() { diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp index 096d436..575c4fd 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp @@ -1054,6 +1054,18 @@ FHyperTwistTrainingDeck UHyperTwistTrainingRuntimeLibrary::BuildActiveCoachFollo return FHyperTwistTrainingDeck(); } +FHyperTwistTrainingDeck UHyperTwistTrainingRuntimeLibrary::BuildActiveMethodDrillFollowUpDeck( + UObject* WorldContextObject +) +{ + if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject)) + { + return TrainingSubsystem->BuildActiveMethodDrillFollowUpDeck(); + } + + return FHyperTwistTrainingDeck(); +} + FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartTrainingRunFromTemplate( UObject* WorldContextObject, const FString& TemplateId, @@ -1069,6 +1081,19 @@ FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartTrainingRunF return FHyperTwistTrainingRunState(); } +FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartActiveMethodDrillFollowUpRun( + UObject* WorldContextObject, + const FString& SessionId +) +{ + if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject)) + { + return TrainingSubsystem->StartActiveMethodDrillFollowUpRun(SessionId); + } + + return FHyperTwistTrainingRunState(); +} + FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartRecommendedReviewRun( UObject* WorldContextObject, const FString& SessionId, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp index e8bd5d7..358922c 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -1555,6 +1555,21 @@ FHyperTwistTrainingDeck UHyperTwistTrainingSubsystem::BuildActiveCoachFollowUpDe return FocusDeck; } +FHyperTwistTrainingDeck UHyperTwistTrainingSubsystem::BuildActiveMethodDrillFollowUpDeck() const +{ + if (!ActiveMethodDrillFollowUpTemplate.IsStructurallyValid()) + { + return FHyperTwistTrainingDeck(); + } + + const FHyperTwistTrainingDeck MaterializedDeck = + UHyperTwistTrainingCatalogLibrary::MaterializeTrainingSessionTemplateDeck( + ActiveMethodDrillFollowUpTemplate, + UHyperTwistTrainingCatalogLibrary::MakePhase3TrainingCatalog() + ); + return MaterializedDeck.IsStructurallyValid() ? MaterializedDeck : FHyperTwistTrainingDeck(); +} + FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartTrainingRunFromTemplate( const FString& TemplateId, const FString& UserId, @@ -1594,6 +1609,33 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartTrainingRunFromTe ); } +FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartActiveMethodDrillFollowUpRun( + const FString& SessionId +) +{ + if (!ActiveMethodDrillRunState.IsStructurallyValid() + || !ActiveMethodDrillFollowUpTemplate.IsStructurallyValid()) + { + return FHyperTwistTrainingRunState(); + } + + const FHyperTwistTrainingDeck FollowUpDeck = BuildActiveMethodDrillFollowUpDeck(); + if (!FollowUpDeck.IsStructurallyValid()) + { + return FHyperTwistTrainingRunState(); + } + + const FString ResolvedSessionId = !SessionId.IsEmpty() + ? SessionId + : FString::Printf(TEXT("method_drill_follow_up_%s"), *ActiveMethodDrillRunState.RunId); + return StartTrainingRunFromDeck( + FollowUpDeck, + ActiveMethodDrillRunState.UserId, + ResolvedSessionId, + ActiveMethodDrillFollowUpTemplate.SuggestedMode + ); +} + FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartRecommendedReviewRun( const FString& SessionId, int32 MaxCases, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h index fa99cbb..963491f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h @@ -597,6 +597,21 @@ protected: UPROPERTY(Transient) TObjectPtr GuidancePreviewCaseDetailTextBlock = nullptr; + UPROPERTY(Transient) + TObjectPtr MethodDrillDiagnosticsHeaderTextBlock = nullptr; + + UPROPERTY(Transient) + TObjectPtr MethodDrillDiagnosticsStatusTextBlock = nullptr; + + UPROPERTY(Transient) + TObjectPtr MethodDrillDiagnosticsDetailTextBlock = nullptr; + + UPROPERTY(Transient) + TObjectPtr StartMethodDrillFollowUpButton = nullptr; + + UPROPERTY(Transient) + TObjectPtr StartMethodDrillFollowUpButtonLabel = nullptr; + UPROPERTY(Transient) TObjectPtr RepeatRunButton = nullptr; @@ -999,6 +1014,9 @@ protected: UFUNCTION() void HandleStartPreviewCaseClicked(); + UFUNCTION() + void HandleStartMethodDrillFollowUpClicked(); + UFUNCTION() void HandleCompleteRunClicked(); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h index d8178a2..2bd8cf8 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h @@ -35,6 +35,21 @@ public: UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training") FHyperTwistTrainingSessionSummary CachedRunSummary; + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Drill") + FHyperTwistTrainingMethodDrillRunState CachedMethodDrillRunState; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Drill") + FHyperTwistTrainingMethodDrillSummary CachedMethodDrillSummary; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Drill") + FHyperTwistTrainingMethodDrillDiagnosticPacket CachedMethodDrillDiagnosticPacket; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Drill") + FHyperTwistTrainingSessionTemplate CachedMethodDrillFollowUpTemplate; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Drill") + FHyperTwistTrainingDeck CachedMethodDrillFollowUpDeck; + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training") FHyperTwistTrainingRunStepResult LastStepResult; @@ -77,6 +92,9 @@ public: UFUNCTION(BlueprintPure, Category = "HyperTwist|Training") bool HasActiveTrainingRun() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill") + bool HasActiveMethodDrillRun() const; + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training") void RefreshTrainingState(); @@ -109,6 +127,9 @@ public: UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingDeck BuildActiveCoachFollowUpDeck(int32 MaxCases); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill") + FHyperTwistTrainingDeck BuildActiveMethodDrillFollowUpDeck(); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingRunState StartCoachRecommendedRun( int32 MaxCases, @@ -151,6 +172,9 @@ public: UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") bool PromoteCoachQueueEntry(const FString& EntryId); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill") + FHyperTwistTrainingRunState StartActiveMethodDrillFollowUpRun(const FString& SessionId); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training") FHyperTwistTrainingRepositoryRoundTripVerification VerifyTrainingRepositoryRoundTrip(); }; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h index 31059fd..3e1b3c2 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h @@ -426,6 +426,9 @@ public: int32 MaxCases ); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject")) + static FHyperTwistTrainingDeck BuildActiveMethodDrillFollowUpDeck(UObject* WorldContextObject); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject")) static FHyperTwistTrainingRunState StartTrainingRunFromTemplate( UObject* WorldContextObject, @@ -434,6 +437,12 @@ public: const FString& SessionId ); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject")) + static FHyperTwistTrainingRunState StartActiveMethodDrillFollowUpRun( + UObject* WorldContextObject, + const FString& SessionId + ); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject")) static FHyperTwistTrainingRunState StartRecommendedReviewRun( UObject* WorldContextObject, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h index fbb06fd..84a3e4d 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h @@ -341,6 +341,9 @@ public: UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingDeck BuildActiveCoachFollowUpDeck(int32 MaxCases) const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill") + FHyperTwistTrainingDeck BuildActiveMethodDrillFollowUpDeck() const; + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training") FHyperTwistTrainingRunState StartTrainingRunFromTemplate( const FString& TemplateId, @@ -348,6 +351,9 @@ public: const FString& SessionId ); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill") + FHyperTwistTrainingRunState StartActiveMethodDrillFollowUpRun(const FString& SessionId); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training") FHyperTwistTrainingRunState StartRecommendedReviewRun( const FString& SessionId,