From 7a715358a049005a7ee0428b502ce3cccea5aa40 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Tue, 28 Apr 2026 23:38:03 +0200 Subject: [PATCH] Close Roux Bound2 drill recovery packet --- .../HyperTwistContractLibrary.cpp | 42 ++++ .../HyperTwistTrainingLibrary.cpp | 111 ++++++++++ .../HyperTwistTrainingRepositoryLibrary.cpp | 205 ++++++++++++++++++ .../HyperTwistTrainingRuntimeLibrary.cpp | 61 ++++++ .../HyperTwistTrainingSubsystem.cpp | 104 ++++++++- .../HyperTwistContractLibrary.h | 9 + .../HyperTwistTrainingLibrary.h | 14 ++ .../HyperTwistTrainingRepositoryLibrary.h | 26 +++ .../HyperTwistTrainingRuntimeLibrary.h | 29 +++ .../HyperTwistTrainingSubsystem.h | 25 +++ .../HyperTwistTrainingTypes.h | 59 +++++ 11 files changed, 684 insertions(+), 1 deletion(-) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp index 4fe2716..d0aecf5 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp @@ -496,6 +496,48 @@ FHyperTwistTrainingMethodDrillBatch UHyperTwistContractLibrary::MakeSampleMethod ); } +FHyperTwistTrainingMethodDrillDefinition UHyperTwistContractLibrary::MakeSampleFavoriteFirstMethodDrillDefinition() +{ + const FHyperTwistTrainingMethodDrillDefinition BaseDefinition = MakeSampleMethodDrillDefinition(); + if (!BaseDefinition.IsStructurallyValid()) + { + return FHyperTwistTrainingMethodDrillDefinition(); + } + + TArray Favorites; + FHyperTwistTrainingMethodDrillFavoriteEntry FavoriteEntry = MakeSampleMethodDrillFavoriteEntry(); + FavoriteEntry.CaseId = TEXT("roux-block-case-02"); + Favorites.Add(FavoriteEntry); + return UHyperTwistTrainingRepositoryLibrary::BuildDrillDefinitionFavoriteFirst( + BaseDefinition, + Favorites, + 1 + ); +} + +FHyperTwistTrainingMethodDrillBatchReadiness UHyperTwistContractLibrary::MakeSampleMethodDrillBatchReadiness() +{ + return UHyperTwistTrainingRepositoryLibrary::DeriveDrillBatchReadiness( + MakeSampleMethodDrillBatch(), + MakeSampleMethodDrillDefinition() + ); +} + +FHyperTwistTrainingDrillCaseModeCompatibility UHyperTwistContractLibrary::MakeSampleMethodDrillCaseModeCompatibility() +{ + const FHyperTwistTrainingMethodDrillDefinition DrillDefinition = MakeSampleMethodDrillDefinition(); + if (!DrillDefinition.IsStructurallyValid() || DrillDefinition.SourceDeck.Cases.Num() == 0) + { + return FHyperTwistTrainingDrillCaseModeCompatibility(); + } + + return UHyperTwistTrainingLibrary::DeriveDrillCaseModeCompatibility( + DrillDefinition.SourceDeck.Cases[0], + DrillDefinition.DrillMode, + DrillDefinition.VisibilityPolicy + ); +} + FHyperTwistTrainingRunStepResult UHyperTwistContractLibrary::MakeSampleTrainingRunStepResult() { const FHyperTwistTrainingRunState RunState = MakeSampleTrainingRunState(); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingLibrary.cpp index a7f4d98..117b1b6 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingLibrary.cpp @@ -1638,6 +1638,15 @@ FHyperTwistTrainingMethodDrillDefinition UHyperTwistTrainingLibrary::BuildMethod DrillDefinition.SourceDeckId = Deck.DeckId; DrillDefinition.SourceDeck = Deck; + DrillDefinition.SourceDeck.Cases = FilterDrillCasesByModeCompatibility( + Deck.Cases, + DrillMode, + VisibilityPolicy + ); + if (DrillDefinition.SourceDeck.Cases.Num() == 0) + { + return FHyperTwistTrainingMethodDrillDefinition(); + } DrillDefinition.FocusCaseIds = HyperTwistTrainingLibraryInternal::BuildDrillCaseIds(DrillDefinition); DrillDefinition.DrillMode = DrillMode; DrillDefinition.VisibilityPolicy = VisibilityPolicy; @@ -1963,3 +1972,105 @@ FHyperTwistTrainingMethodDrillBatch UHyperTwistTrainingLibrary::BuildMethodDrill DrillBatch.bPinnedFavoritesOnly = bPinnedFavoritesOnly; return DrillBatch; } + +FHyperTwistTrainingDrillCaseModeCompatibility UHyperTwistTrainingLibrary::DeriveDrillCaseModeCompatibility( + const FHyperTwistTrainingCase& TrainingCase, + EHyperTwistTrainingDrillModeKind DrillMode, + EHyperTwistTrainingDrillVisibilityPolicy VisibilityPolicy +) +{ + FHyperTwistTrainingDrillCaseModeCompatibility Result; + Result.CaseId = TrainingCase.CaseId; + Result.DrillMode = DrillMode; + Result.VisibilityPolicy = VisibilityPolicy; + + if (!TrainingCase.IsStructurallyValid()) + { + return Result; + } + + const bool bHasNotation = !TrainingCase.CanonicalNotation.IsEmpty(); + const bool bHasStateRef = !TrainingCase.CanonicalStateRef.IsEmpty(); + const bool bHasScramble = !TrainingCase.ScrambleNotation.IsEmpty(); + const bool bHasPromptLabel = !TrainingCase.PromptLabel.IsEmpty(); + + const EHyperTwistTrainingPromptKind PromptKind = TrainingCase.PromptKind; + + switch (DrillMode) + { + case EHyperTwistTrainingDrillModeKind::Recognition: + Result.bCompatibleWithMode = + bHasStateRef + || bHasScramble + || PromptKind == EHyperTwistTrainingPromptKind::Recognition + || PromptKind == EHyperTwistTrainingPromptKind::State; + break; + + case EHyperTwistTrainingDrillModeKind::Recall: + Result.bCompatibleWithMode = + bHasNotation + || PromptKind == EHyperTwistTrainingPromptKind::Algorithm + || PromptKind == EHyperTwistTrainingPromptKind::Sequence + || PromptKind == EHyperTwistTrainingPromptKind::Memo; + break; + + case EHyperTwistTrainingDrillModeKind::Execution: + Result.bCompatibleWithMode = + bHasNotation + || PromptKind == EHyperTwistTrainingPromptKind::Algorithm + || PromptKind == EHyperTwistTrainingPromptKind::Sequence; + break; + + case EHyperTwistTrainingDrillModeKind::Reconstruction: + Result.bCompatibleWithMode = + bHasStateRef + || PromptKind == EHyperTwistTrainingPromptKind::State + || PromptKind == EHyperTwistTrainingPromptKind::Recognition; + break; + + default: + Result.bCompatibleWithMode = false; + break; + } + + switch (VisibilityPolicy) + { + case EHyperTwistTrainingDrillVisibilityPolicy::FullInformation: + Result.bCompatibleWithVisibility = true; + break; + + case EHyperTwistTrainingDrillVisibilityPolicy::PromptOnly: + Result.bCompatibleWithVisibility = bHasPromptLabel || bHasNotation; + break; + + case EHyperTwistTrainingDrillVisibilityPolicy::MaskedState: + case EHyperTwistTrainingDrillVisibilityPolicy::PartialState: + Result.bCompatibleWithVisibility = bHasStateRef; + break; + + default: + Result.bCompatibleWithVisibility = false; + break; + } + + return Result; +} + +TArray UHyperTwistTrainingLibrary::FilterDrillCasesByModeCompatibility( + const TArray& Cases, + EHyperTwistTrainingDrillModeKind DrillMode, + EHyperTwistTrainingDrillVisibilityPolicy VisibilityPolicy +) +{ + TArray Filtered; + for (const FHyperTwistTrainingCase& TrainingCase : Cases) + { + const FHyperTwistTrainingDrillCaseModeCompatibility Compat = + DeriveDrillCaseModeCompatibility(TrainingCase, DrillMode, VisibilityPolicy); + if (Compat.bCompatibleWithMode && Compat.bCompatibleWithVisibility) + { + Filtered.Add(TrainingCase); + } + } + return Filtered; +} diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp index caf0a0c..d8257f9 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp @@ -2953,6 +2953,211 @@ FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::RemoveM return UpdatedRepositoryState; } +FHyperTwistTrainingMethodDrillBatchReadiness UHyperTwistTrainingRepositoryLibrary::DeriveDrillBatchReadiness( + const FHyperTwistTrainingMethodDrillBatch& DrillBatch, + const FHyperTwistTrainingMethodDrillDefinition& CurrentDefinition +) +{ + FHyperTwistTrainingMethodDrillBatchReadiness Readiness; + Readiness.BatchId = DrillBatch.BatchId; + Readiness.LastUpdatedAtUtc = DrillBatch.LastUpdatedAtUtc; + + if (!DrillBatch.IsStructurallyValid()) + { + return Readiness; + } + + TSet AllowedCaseIds; + for (const FHyperTwistTrainingCase& TrainingCase : CurrentDefinition.SourceDeck.Cases) + { + if (!TrainingCase.CaseId.IsEmpty()) + { + AllowedCaseIds.Add(TrainingCase.CaseId); + } + } + if (CurrentDefinition.FocusCaseIds.Num() > 0) + { + for (const FString& FocusId : CurrentDefinition.FocusCaseIds) + { + AllowedCaseIds.Add(FocusId); + } + } + + bool bFoundStaleCase = false; + + for (const FString& PendingId : DrillBatch.PendingCaseIds) + { + if (!AllowedCaseIds.Contains(PendingId)) + { + bFoundStaleCase = true; + } + } + if (!DrillBatch.CurrentCaseId.IsEmpty() && !AllowedCaseIds.Contains(DrillBatch.CurrentCaseId)) + { + bFoundStaleCase = true; + } + for (const FString& CompletedId : DrillBatch.CompletedCaseIds) + { + if (!AllowedCaseIds.Contains(CompletedId)) + { + bFoundStaleCase = true; + } + } + + Readiness.bIsStale = bFoundStaleCase; + + const int32 PendingCount = DrillBatch.PendingCaseIds.Num(); + const bool bHasActiveCurrent = !DrillBatch.CurrentCaseId.IsEmpty() + && !DrillBatch.CompletedCaseIds.Contains(DrillBatch.CurrentCaseId); + Readiness.RemainingCaseCount = PendingCount + (bHasActiveCurrent ? 1 : 0); + Readiness.CompletedCaseCount = DrillBatch.CompletedCaseIds.Num(); + Readiness.bIsResumable = Readiness.RemainingCaseCount > 0; + + const int32 Total = Readiness.RemainingCaseCount + Readiness.CompletedCaseCount; + Readiness.CompletionFraction = Total > 0 + ? static_cast(Readiness.CompletedCaseCount) / static_cast(Total) + : 0.0f; + + return Readiness; +} + +TArray UHyperTwistTrainingRepositoryLibrary::ListResumableDrillBatches( + const FHyperTwistTrainingRepositoryState& RepositoryState, + const FString& UserId, + const FString& MethodSegmentId +) +{ + TArray AllBatches = + ListMethodDrillBatches(RepositoryState, UserId, MethodSegmentId); + + TArray Resumable; + for (const FHyperTwistTrainingMethodDrillBatch& Batch : AllBatches) + { + const bool bHasPending = Batch.PendingCaseIds.Num() > 0; + const bool bHasActiveCurrent = !Batch.CurrentCaseId.IsEmpty() + && !Batch.CompletedCaseIds.Contains(Batch.CurrentCaseId); + if (bHasPending || bHasActiveCurrent) + { + Resumable.Add(Batch); + } + } + return Resumable; +} + +FHyperTwistTrainingMethodDrillBatch UHyperTwistTrainingRepositoryLibrary::RebuildDrillBatchForResume( + const FHyperTwistTrainingMethodDrillBatch& SavedBatch, + const FHyperTwistTrainingMethodDrillDefinition& CurrentDefinition +) +{ + FHyperTwistTrainingMethodDrillBatch Rebuilt = SavedBatch; + + if (!SavedBatch.IsStructurallyValid()) + { + return Rebuilt; + } + + TSet AllowedCaseIds; + for (const FHyperTwistTrainingCase& TrainingCase : CurrentDefinition.SourceDeck.Cases) + { + if (!TrainingCase.CaseId.IsEmpty()) + { + AllowedCaseIds.Add(TrainingCase.CaseId); + } + } + if (CurrentDefinition.FocusCaseIds.Num() > 0) + { + for (const FString& FocusId : CurrentDefinition.FocusCaseIds) + { + AllowedCaseIds.Add(FocusId); + } + } + + Rebuilt.PendingCaseIds.RemoveAll([&AllowedCaseIds](const FString& CaseId) + { + return !AllowedCaseIds.Contains(CaseId); + }); + + Rebuilt.CompletedCaseIds.RemoveAll([&AllowedCaseIds](const FString& CaseId) + { + return !AllowedCaseIds.Contains(CaseId); + }); + + Rebuilt.Responses.RemoveAll([&AllowedCaseIds](const FHyperTwistTrainingMethodDrillResponseRecord& Record) + { + return !AllowedCaseIds.Contains(Record.CaseId); + }); + + const bool bCurrentStillValid = !Rebuilt.CurrentCaseId.IsEmpty() + && AllowedCaseIds.Contains(Rebuilt.CurrentCaseId); + if (!bCurrentStillValid) + { + if (Rebuilt.PendingCaseIds.Num() > 0) + { + Rebuilt.CurrentCaseId = Rebuilt.PendingCaseIds[0]; + Rebuilt.PendingCaseIds.RemoveAt(0); + } + else + { + Rebuilt.CurrentCaseId = FString(); + } + } + + Rebuilt.LastUpdatedAtUtc = FDateTime::UtcNow().ToIso8601(); + return Rebuilt; +} + +FHyperTwistTrainingMethodDrillDefinition UHyperTwistTrainingRepositoryLibrary::BuildDrillDefinitionFavoriteFirst( + const FHyperTwistTrainingMethodDrillDefinition& BaseDefinition, + const TArray& Favorites, + int32 MaxFavoriteCases +) +{ + FHyperTwistTrainingMethodDrillDefinition Result = BaseDefinition; + + if (!BaseDefinition.IsStructurallyValid()) + { + return Result; + } + + TSet DeckCaseIds; + for (const FHyperTwistTrainingCase& TrainingCase : BaseDefinition.SourceDeck.Cases) + { + if (!TrainingCase.CaseId.IsEmpty()) + { + DeckCaseIds.Add(TrainingCase.CaseId); + } + } + + TArray FavoriteCaseIds; + for (const FHyperTwistTrainingMethodDrillFavoriteEntry& FavoriteEntry : Favorites) + { + if (!FavoriteEntry.CaseId.IsEmpty() && DeckCaseIds.Contains(FavoriteEntry.CaseId)) + { + FavoriteCaseIds.AddUnique(FavoriteEntry.CaseId); + if (MaxFavoriteCases > 0 && FavoriteCaseIds.Num() >= MaxFavoriteCases) + { + break; + } + } + } + + TArray OrderedCaseIds = FavoriteCaseIds; + for (const FHyperTwistTrainingCase& TrainingCase : BaseDefinition.SourceDeck.Cases) + { + if (!TrainingCase.CaseId.IsEmpty() && !OrderedCaseIds.Contains(TrainingCase.CaseId)) + { + OrderedCaseIds.Add(TrainingCase.CaseId); + } + } + + Result.FocusCaseIds = OrderedCaseIds; + if (FavoriteCaseIds.Num() > 0) + { + Result.EntrySource = EHyperTwistTrainingDrillEntrySource::Favorites; + } + return Result; +} + FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::UpsertRunRecord( const FHyperTwistTrainingRepositoryState& RepositoryState, const FHyperTwistTrainingRunRecord& RunRecord diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp index 1f1ef17..27e08fc 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp @@ -514,6 +514,67 @@ TArray UHyperTwistTrainingRuntimeLibrary::G return TArray(); } +TArray UHyperTwistTrainingRuntimeLibrary::GetResumableMethodDrillBatchesForUser( + UObject* WorldContextObject, + const FString& UserId, + const FString& MethodSegmentId +) +{ + if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject)) + { + return TrainingSubsystem->GetResumableMethodDrillBatchesForUser(UserId, MethodSegmentId); + } + + return TArray(); +} + +FHyperTwistTrainingMethodDrillBatchReadiness UHyperTwistTrainingRuntimeLibrary::DeriveMethodDrillBatchReadiness( + UObject* WorldContextObject, + const FHyperTwistTrainingMethodDrillBatch& DrillBatch, + const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition +) +{ + if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject)) + { + return TrainingSubsystem->DeriveMethodDrillBatchReadiness(DrillBatch, DrillDefinition); + } + + return FHyperTwistTrainingMethodDrillBatchReadiness(); +} + +FHyperTwistTrainingMethodDrillBatch UHyperTwistTrainingRuntimeLibrary::RebuildMethodDrillBatchForResume( + UObject* WorldContextObject, + const FHyperTwistTrainingMethodDrillBatch& DrillBatch, + const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition +) +{ + if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject)) + { + return TrainingSubsystem->RebuildMethodDrillBatchForResume(DrillBatch, DrillDefinition); + } + + return FHyperTwistTrainingMethodDrillBatch(); +} + +FHyperTwistTrainingMethodDrillDefinition UHyperTwistTrainingRuntimeLibrary::BuildFavoriteFirstMethodDrillDefinition( + UObject* WorldContextObject, + const FHyperTwistTrainingMethodDrillDefinition& BaseDefinition, + const FString& UserId, + int32 MaxFavoriteCases +) +{ + if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject)) + { + return TrainingSubsystem->BuildFavoriteFirstMethodDrillDefinition( + BaseDefinition, + UserId, + MaxFavoriteCases + ); + } + + return FHyperTwistTrainingMethodDrillDefinition(); +} + TArray UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCaseRecommendations(UObject* WorldContextObject) { if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject)) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp index 7934535..241bf5a 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -863,6 +863,70 @@ TArray UHyperTwistTrainingSubsystem::GetMet ); } +TArray UHyperTwistTrainingSubsystem::GetResumableMethodDrillBatchesForUser( + const FString& UserId, + const FString& MethodSegmentId +) const +{ + return UHyperTwistTrainingRepositoryLibrary::ListResumableDrillBatches( + TrainingRepositoryState, + UserId, + MethodSegmentId + ); +} + +FHyperTwistTrainingMethodDrillBatchReadiness UHyperTwistTrainingSubsystem::DeriveMethodDrillBatchReadiness( + const FHyperTwistTrainingMethodDrillBatch& DrillBatch, + const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition +) const +{ + return UHyperTwistTrainingRepositoryLibrary::DeriveDrillBatchReadiness( + DrillBatch, + DrillDefinition + ); +} + +FHyperTwistTrainingMethodDrillBatch UHyperTwistTrainingSubsystem::RebuildMethodDrillBatchForResume( + const FHyperTwistTrainingMethodDrillBatch& DrillBatch, + const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition +) const +{ + return UHyperTwistTrainingRepositoryLibrary::RebuildDrillBatchForResume( + DrillBatch, + DrillDefinition + ); +} + +FHyperTwistTrainingMethodDrillDefinition UHyperTwistTrainingSubsystem::BuildFavoriteFirstMethodDrillDefinition( + const FHyperTwistTrainingMethodDrillDefinition& BaseDefinition, + const FString& UserId, + int32 MaxFavoriteCases +) const +{ + if (!BaseDefinition.IsStructurallyValid()) + { + return BaseDefinition; + } + + const FString EffectiveUserId = !UserId.IsEmpty() ? UserId : ActiveMethodDrillRunState.UserId; + if (EffectiveUserId.IsEmpty()) + { + return BaseDefinition; + } + + const TArray Favorites = + UHyperTwistTrainingRepositoryLibrary::ListMethodDrillFavorites( + TrainingRepositoryState, + EffectiveUserId, + BaseDefinition.MethodDescriptor.SegmentId + ); + return UHyperTwistTrainingRepositoryLibrary::BuildDrillDefinitionFavoriteFirst( + BaseDefinition, + Favorites, + MaxFavoriteCases + ); +} + TArray UHyperTwistTrainingSubsystem::GetActiveCaseRecommendations() const { return ActiveCaseRecommendations; @@ -2062,11 +2126,49 @@ FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingSubsystem::ResumeMetho const FString& RunId ) { + const FHyperTwistTrainingMethodDrillBatch RebuiltBatch = + UHyperTwistTrainingRepositoryLibrary::RebuildDrillBatchForResume(DrillBatch, DrillDefinition); + if (!RebuiltBatch.IsStructurallyValid()) + { + ActiveMethodDrillRunState = FHyperTwistTrainingMethodDrillRunState(); + bHasActiveMethodDrillRun = false; + RefreshMethodDrillSummary(); + return ActiveMethodDrillRunState; + } + + const FHyperTwistTrainingMethodDrillBatchReadiness Readiness = + UHyperTwistTrainingRepositoryLibrary::DeriveDrillBatchReadiness(RebuiltBatch, DrillDefinition); + TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertMethodDrillBatch( + TrainingRepositoryState, + RebuiltBatch + ); + RefreshRepositoryViews(); + ActiveMethodDrillRunState = UHyperTwistTrainingLibrary::ResumeMethodDrillRunFromBatch( DrillDefinition, - DrillBatch, + RebuiltBatch, RunId ); + if (ActiveMethodDrillRunState.IsStructurallyValid() + && !Readiness.bIsResumable + && ActiveMethodDrillRunState.LifecycleState != EHyperTwistTrainingDrillLifecycleState::Completed + && ActiveMethodDrillRunState.LifecycleState != EHyperTwistTrainingDrillLifecycleState::Aborted) + { + ActiveMethodDrillRunState = UHyperTwistTrainingLibrary::FinishMethodDrillRun( + ActiveMethodDrillRunState, + false + ); + TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertMethodDrillBatch( + TrainingRepositoryState, + UHyperTwistTrainingLibrary::BuildMethodDrillBatch( + ActiveMethodDrillRunState, + RebuiltBatch.BatchId, + RebuiltBatch.Title, + RebuiltBatch.bPinnedFavoritesOnly + ) + ); + RefreshRepositoryViews(); + } bHasActiveMethodDrillRun = ActiveMethodDrillRunState.IsStructurallyValid(); RefreshMethodDrillSummary(); return ActiveMethodDrillRunState; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistBootstrap/HyperTwistContractLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistBootstrap/HyperTwistContractLibrary.h index 85c85ac..ef44154 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistBootstrap/HyperTwistContractLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistBootstrap/HyperTwistContractLibrary.h @@ -83,6 +83,15 @@ public: UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill") static FHyperTwistTrainingMethodDrillBatch MakeSampleMethodDrillBatch(); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill") + static FHyperTwistTrainingMethodDrillDefinition MakeSampleFavoriteFirstMethodDrillDefinition(); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill") + static FHyperTwistTrainingMethodDrillBatchReadiness MakeSampleMethodDrillBatchReadiness(); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill") + static FHyperTwistTrainingDrillCaseModeCompatibility MakeSampleMethodDrillCaseModeCompatibility(); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training") static FHyperTwistTrainingRunStepResult MakeSampleTrainingRunStepResult(); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingLibrary.h index b0b43c7..acf4563 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingLibrary.h @@ -208,4 +208,18 @@ public: const FString& Title, bool bPinnedFavoritesOnly ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill") + static FHyperTwistTrainingDrillCaseModeCompatibility DeriveDrillCaseModeCompatibility( + const FHyperTwistTrainingCase& TrainingCase, + EHyperTwistTrainingDrillModeKind DrillMode, + EHyperTwistTrainingDrillVisibilityPolicy VisibilityPolicy + ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill") + static TArray FilterDrillCasesByModeCompatibility( + const TArray& Cases, + EHyperTwistTrainingDrillModeKind DrillMode, + EHyperTwistTrainingDrillVisibilityPolicy VisibilityPolicy + ); }; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h index da16c98..89d7e53 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h @@ -98,6 +98,32 @@ public: const FString& BatchId ); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Drill") + static FHyperTwistTrainingMethodDrillBatchReadiness DeriveDrillBatchReadiness( + const FHyperTwistTrainingMethodDrillBatch& DrillBatch, + const FHyperTwistTrainingMethodDrillDefinition& CurrentDefinition + ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Drill") + static TArray ListResumableDrillBatches( + const FHyperTwistTrainingRepositoryState& RepositoryState, + const FString& UserId, + const FString& MethodSegmentId + ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Drill") + static FHyperTwistTrainingMethodDrillBatch RebuildDrillBatchForResume( + const FHyperTwistTrainingMethodDrillBatch& SavedBatch, + const FHyperTwistTrainingMethodDrillDefinition& CurrentDefinition + ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Drill") + static FHyperTwistTrainingMethodDrillDefinition BuildDrillDefinitionFavoriteFirst( + const FHyperTwistTrainingMethodDrillDefinition& BaseDefinition, + const TArray& Favorites, + int32 MaxFavoriteCases + ); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository") static FHyperTwistTrainingRepositoryState UpsertRunRecord( const FHyperTwistTrainingRepositoryState& RepositoryState, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h index b189838..d36e4e6 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h @@ -178,6 +178,35 @@ public: const FString& MethodSegmentId ); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject")) + static TArray GetResumableMethodDrillBatchesForUser( + UObject* WorldContextObject, + const FString& UserId, + const FString& MethodSegmentId + ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject")) + static FHyperTwistTrainingMethodDrillBatchReadiness DeriveMethodDrillBatchReadiness( + UObject* WorldContextObject, + const FHyperTwistTrainingMethodDrillBatch& DrillBatch, + const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition + ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject")) + static FHyperTwistTrainingMethodDrillBatch RebuildMethodDrillBatchForResume( + UObject* WorldContextObject, + const FHyperTwistTrainingMethodDrillBatch& DrillBatch, + const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition + ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject")) + static FHyperTwistTrainingMethodDrillDefinition BuildFavoriteFirstMethodDrillDefinition( + UObject* WorldContextObject, + const FHyperTwistTrainingMethodDrillDefinition& BaseDefinition, + const FString& UserId, + int32 MaxFavoriteCases + ); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject")) static TArray GetActiveTrainingCaseRecommendations(UObject* WorldContextObject); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h index e10d919..11a2201 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h @@ -152,6 +152,31 @@ public: const FString& MethodSegmentId ) const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill") + TArray GetResumableMethodDrillBatchesForUser( + const FString& UserId, + const FString& MethodSegmentId + ) const; + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill") + FHyperTwistTrainingMethodDrillBatchReadiness DeriveMethodDrillBatchReadiness( + const FHyperTwistTrainingMethodDrillBatch& DrillBatch, + const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition + ) const; + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill") + FHyperTwistTrainingMethodDrillBatch RebuildMethodDrillBatchForResume( + const FHyperTwistTrainingMethodDrillBatch& DrillBatch, + const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition + ) const; + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill") + FHyperTwistTrainingMethodDrillDefinition BuildFavoriteFirstMethodDrillDefinition( + const FHyperTwistTrainingMethodDrillDefinition& BaseDefinition, + const FString& UserId, + int32 MaxFavoriteCases + ) const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training") TArray GetActiveCaseRecommendations() const; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index c9ddde3..70129b3 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -2401,6 +2401,65 @@ struct FHyperTwistTrainingMethodDrillBatch } }; +USTRUCT(BlueprintType) +struct FHyperTwistTrainingMethodDrillBatchReadiness +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString BatchId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bIsResumable = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bIsStale = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RemainingCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 CompletedCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float CompletionFraction = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LastUpdatedAtUtc; + + bool IsStructurallyValid() const + { + return !BatchId.IsEmpty(); + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingDrillCaseModeCompatibility +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CaseId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCompatibleWithMode = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCompatibleWithVisibility = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingDrillModeKind DrillMode = EHyperTwistTrainingDrillModeKind::Recognition; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingDrillVisibilityPolicy VisibilityPolicy = + EHyperTwistTrainingDrillVisibilityPolicy::FullInformation; + + bool IsStructurallyValid() const + { + return !CaseId.IsEmpty(); + } +}; + USTRUCT(BlueprintType) struct FHyperTwistTrainingLearnerPreset {