From f6dfd7ae18d52f45bb72ff7d746334e2a555a006 Mon Sep 17 00:00:00 2001 From: Mo <98795422+codeGlutton@users.noreply.github.com> Date: Wed, 23 Sep 2026 02:17:51 +0900 Subject: [PATCH] =?UTF-8?q?fix=20=EC=A0=84=ED=88=AC=20=EC=8B=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=AF=B9=20=EB=B0=9C=EB=8F=99=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=95=88=EC=A0=95=ED=99=94=20=EB=B0=8F=20=EC=A0=84=ED=88=AC=20?= =?UTF-8?q?=EC=8A=B9=EB=A6=AC=20=EB=AF=B8=EC=B2=98=EB=A6=AC=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [SVN-REV]: 463 --- .../Obstacle/Gimmick/GimmickModel.cpp | 17 ++--- Source/P_RD/ObjectModel.cpp | 10 +++ Source/P_RD/ObjectModel.h | 3 + Source/P_RD/SRPGFramework/SRPGAction.cpp | 74 ++++++++++++++----- Source/P_RD/SRPGFramework/SRPGAction.h | 11 +++ Source/P_RD/SRPGFramework/SRPGTurnContext.cpp | 12 ++- Source/P_RD/SRPGFramework/SRPGTurnContext.h | 3 + .../WorldSubsystem/SRPGCombatModel.cpp | 38 +++++++++- .../WorldSubsystem/SRPGCombatModel.h | 10 ++- .../Actor/BoardActor/GimmickModelTests.cpp | 2 + .../Singleton/RunCheckpointTests.cpp | 6 +- 11 files changed, 149 insertions(+), 37 deletions(-) diff --git a/Source/P_RD/Actor/BoardActor/Obstacle/Gimmick/GimmickModel.cpp b/Source/P_RD/Actor/BoardActor/Obstacle/Gimmick/GimmickModel.cpp index 7e8fd3e72..76204cffd 100644 --- a/Source/P_RD/Actor/BoardActor/Obstacle/Gimmick/GimmickModel.cpp +++ b/Source/P_RD/Actor/BoardActor/Obstacle/Gimmick/GimmickModel.cpp @@ -1,4 +1,4 @@ -/*****************************************************************//** +/*****************************************************************//** * @file GimmickModel.cpp * @brief 기믹 공통 모델 구현 파일 * @author 이문환 @@ -58,16 +58,15 @@ bool UGimmickModel::TryTriggerGimmick(const FTileIndex& AimedTileIndex, TSharedP --mRemainingTriggerCount; } + USRPGCombatModel* CombatModel = GetWorldSubsystemModel(this); + checkf(CombatModel != nullptr, TEXT("전투 모델 nullptr")); + TSharedPtr EndHoldBarrier = CombatModel->GetCurrentActionEndHoldBarrier(); + // 장착된 스킬 강제 시전 (기믹은 행동력 개념이 없으므로 소모 검사 없이 시전) FOnEndSkillUI EndCallback; - EndCallback.AddUObject(this, &UGimmickModel::OnGimmickSkillEnd); - - // 배리어를 받았으면, 배리어 포인터를 캡처한 빈 람다를 종료 콜백에 추가 - // 스킬이 끝나면 콜백이 정리되고, 람다가 사라지면서 캡처된 포인터도 함께 해제됨 - if (PresentationBarrier.IsValid() == true) - { - EndCallback.AddLambda([PresentationBarrier](const FActiveSkillContext&, const UStaticSkillData*) {}); - } + EndCallback.AddWeakLambda(this, [this, PresentationBarrier, EndHoldBarrier](const FActiveSkillContext& Context, const UStaticSkillData* SkillData) { + OnGimmickSkillEnd(Context, SkillData); + }); SkillComp->ForcedActivateSkill(GetTileMap(), mTriggerSkillIndex, AimedTileIndex, EndCallback); return true; diff --git a/Source/P_RD/ObjectModel.cpp b/Source/P_RD/ObjectModel.cpp index 82f06c128..54abee75d 100644 --- a/Source/P_RD/ObjectModel.cpp +++ b/Source/P_RD/ObjectModel.cpp @@ -45,3 +45,13 @@ int32 UObjectModel::GetModelId() const { return mModelId; } + +bool UObjectModel::IsInitialized() const +{ + return mIsInitialized; +} + +bool UObjectModel::IsPendingDestroy() const +{ + return mIsPendingDestroy; +} diff --git a/Source/P_RD/ObjectModel.h b/Source/P_RD/ObjectModel.h index 4ed970ff2..b50d0dbc4 100644 --- a/Source/P_RD/ObjectModel.h +++ b/Source/P_RD/ObjectModel.h @@ -50,6 +50,9 @@ class P_RD_API UObjectModel : public UObject IObjectView* GetView() const; int32 GetModelId() const; + bool IsInitialized() const; + bool IsPendingDestroy() const; + protected: TWeakObjectPtr mView; diff --git a/Source/P_RD/SRPGFramework/SRPGAction.cpp b/Source/P_RD/SRPGFramework/SRPGAction.cpp index e79257cba..4cdf7f01e 100644 --- a/Source/P_RD/SRPGFramework/SRPGAction.cpp +++ b/Source/P_RD/SRPGFramework/SRPGAction.cpp @@ -73,31 +73,50 @@ void USRPGAction::EndAction() UE_LOG(LogSRPGCombat, Log, TEXT("액션 종료: %s"), *GetClass()->GetName()); - // 액션 종료 로직 - OnEndAction(); + mEndHoldPresentationBarrier = FPresentationBarrier::Make(FOnFinishPresentation::CreateWeakLambda(this, [this]() { + // 액션 종료 로직 + OnEndAction(); - // 로그 작성 - GetWorldEventLogger(this)->EndActionLog(); - - // 핸들러 등록 해제 - USRPGCommandRouterModel* CommandRouterModel = GetWorldSubsystemModel(this); - checkf(CommandRouterModel != nullptr, TEXT("명령 라우터 모델 nullptr")); - CommandRouterModel->UnregisterCommandHandler(this); + // 로그 작성 + GetWorldEventLogger(this)->EndActionLog(); - // 전투 상태 평가 - USRPGTurnContext* TurnContext = mParent.Get(); - checkf(TurnContext != nullptr, TEXT("턴 객체 nullptr")); - USRPGCombatModel* CombatModel = TurnContext->GetParent(); - checkf(CombatModel != nullptr, TEXT("전투 모델 nullptr")); - CombatModel->EvaluateCombatStates(); + // 핸들러 등록 해제 + USRPGCommandRouterModel* CommandRouterModel = GetWorldSubsystemModel(this); + checkf(CommandRouterModel != nullptr, TEXT("명령 라우터 모델 nullptr")); + CommandRouterModel->UnregisterCommandHandler(this); - // 종료 연출 시작 - TSharedPtr PresentationBarrier = FPresentationBarrier::Make(FOnFinishPresentation::CreateWeakLambda(this, [this]() { + // 전투 상태 평가 USRPGTurnContext* TurnContext = mParent.Get(); - checkf(TurnContext != nullptr, TEXT("이미 제거된 턴에서 Action 종료 명령 오류")); - TurnContext->OnEndCurrentAction(this, mActionResult); + checkf(TurnContext != nullptr, TEXT("턴 객체 nullptr")); + USRPGCombatModel* CombatModel = TurnContext->GetParent(); + checkf(CombatModel != nullptr, TEXT("전투 모델 nullptr")); + CombatModel->EvaluateCombatStates(); + + // 종료 연출 시작 + TSharedPtr PresentationBarrier = FPresentationBarrier::Make(FOnFinishPresentation::CreateWeakLambda(this, [this]() { + + USRPGTurnContext* TurnContext = mParent.Get(); + checkf(TurnContext != nullptr, TEXT("이미 제거된 턴에서 Action 종료 명령 오류")); + TurnContext->OnEndCurrentAction(this, mActionResult); + })); + OnEndActionUI.Broadcast(PresentationBarrier, this, mActionResult); })); - OnEndActionUI.Broadcast(PresentationBarrier, this, mActionResult); + + DecrementEndHoldCount(); +} + +void USRPGAction::IncrementEndHoldCount() +{ + ++mEndHoldCount; +} + +void USRPGAction::DecrementEndHoldCount() +{ + --mEndHoldCount; + if (mEndHoldCount == 0) + { + mEndHoldPresentationBarrier.Reset(); + } } void USRPGAction::TryBeginAction() @@ -179,6 +198,21 @@ void USRPGAction::ReserveInitializeCommand(TInstancedStruct Comman mInitializeCommand = MoveTemp(Command); } +TSharedPtr USRPGAction::GetEndHoldBarrier() +{ + if (mEndHoldCount <= 0) + { + return nullptr; + } + + IncrementEndHoldCount(); + TSharedPtr HoldBarrier = FPresentationBarrier::Make(FOnFinishPresentation::CreateWeakLambda(this, [this]() { + DecrementEndHoldCount(); + })); + + return HoldBarrier; +} + TWeakObjectPtr USRPGAction::GetParent() const { return mParent; diff --git a/Source/P_RD/SRPGFramework/SRPGAction.h b/Source/P_RD/SRPGFramework/SRPGAction.h index 982717546..baebfd194 100644 --- a/Source/P_RD/SRPGFramework/SRPGAction.h +++ b/Source/P_RD/SRPGFramework/SRPGAction.h @@ -41,6 +41,10 @@ class USRPGAction : public UObject, public ISRPGCommandHandler void TickAction(float DeltaTime); void EndAction(); +private: + void IncrementEndHoldCount(); + void DecrementEndHoldCount(); + protected: /** * 액션 상태를 확인하여 조건적으로 실행하는 함수 @@ -77,6 +81,9 @@ class USRPGAction : public UObject, public ISRPGCommandHandler void ReserveInitializeCommand(TInstancedStruct Command); /* 외부 API */ +public: + TSharedPtr GetEndHoldBarrier(); + public: TWeakObjectPtr GetParent() const; UUnitModel* GetInstigator() const; @@ -87,6 +94,10 @@ class USRPGAction : public UObject, public ISRPGCommandHandler FOnBeginActionUI OnBeginActionUI; FOnEndActionUI OnEndActionUI; +protected: + int32 mEndHoldCount = 1; + TSharedPtr mEndHoldPresentationBarrier = nullptr; + protected: UPROPERTY(Category = Parent, VisibleAnywhere, BlueprintReadOnly, meta = (DisplayName = "Parent")) TWeakObjectPtr mParent; diff --git a/Source/P_RD/SRPGFramework/SRPGTurnContext.cpp b/Source/P_RD/SRPGFramework/SRPGTurnContext.cpp index c8588dc10..e918003dc 100644 --- a/Source/P_RD/SRPGFramework/SRPGTurnContext.cpp +++ b/Source/P_RD/SRPGFramework/SRPGTurnContext.cpp @@ -384,7 +384,7 @@ void USRPGTurnContext::OnHandleCommand(ESRPGCommandResult Result) { if (mTurnPhase == ESRPGTurnPhase::TurnPlay && mReservedActions.Num() > mHeadActionIndex) { - USRPGAction* const CurrentAction = mReservedActions[mHeadActionIndex]; + USRPGAction* CurrentAction = mReservedActions[mHeadActionIndex]; CurrentAction->TryBeginAction(); // TryBeginAction의 델리게이트가 새 커맨드를 동기 제출하면 현재 액션이 끝나 head가 이동할 수 있다. @@ -398,6 +398,16 @@ void USRPGTurnContext::OnHandleCommand(ESRPGCommandResult Result) } } +TSharedPtr USRPGTurnContext::GetCurrentActionEndHoldBarrier() const +{ + if (mReservedActions.Num() > mHeadActionIndex) + { + USRPGAction* CurrentAction = mReservedActions[mHeadActionIndex]; + return CurrentAction->GetEndHoldBarrier(); + } + return nullptr; +} + USRPGCombatModel* USRPGTurnContext::GetParent() const { return mParent.Get(); diff --git a/Source/P_RD/SRPGFramework/SRPGTurnContext.h b/Source/P_RD/SRPGFramework/SRPGTurnContext.h index 599e28d2a..212587d3b 100644 --- a/Source/P_RD/SRPGFramework/SRPGTurnContext.h +++ b/Source/P_RD/SRPGFramework/SRPGTurnContext.h @@ -99,6 +99,9 @@ class USRPGTurnContext : public UObject void OnHandleCommand(ESRPGCommandResult Result); /* 외부 API */ +public: + TSharedPtr GetCurrentActionEndHoldBarrier() const; + public: USRPGCombatModel* GetParent() const; UUnitModel* GetOwner() const; diff --git a/Source/P_RD/Singleton/WorldSubsystem/SRPGCombatModel.cpp b/Source/P_RD/Singleton/WorldSubsystem/SRPGCombatModel.cpp index c9cfffe5f..2464d587a 100644 --- a/Source/P_RD/Singleton/WorldSubsystem/SRPGCombatModel.cpp +++ b/Source/P_RD/Singleton/WorldSubsystem/SRPGCombatModel.cpp @@ -63,15 +63,25 @@ void USRPGCombatModel::Serialize(FArchive& Ar) void USRPGCombatModel::Tick(float DeltaTime) { USRPGTurnContext* CurTurnContext = GetCurrentTurnContext(); - if (mCombatPhase == ESRPGCombatRoomPhase::CombatPlay && CurTurnContext != nullptr) + if (mCombatPhase != ESRPGCombatRoomPhase::CombatEnd && CurTurnContext != nullptr) { CurTurnContext->TickTurn(DeltaTime); } } -bool USRPGCombatModel::IsTickable() const +ETickableTickType USRPGCombatModel::GetTickableTickType() const { - return true; + if (IsTemplate() == true || IsInitialized() == false) + { + return ETickableTickType::Never; + } + + return ETickableTickType::Conditional; +} + +bool USRPGCombatModel::IsAllowedToTick() const +{ + return IsInitialized(); } TStatId USRPGCombatModel::GetStatId() const @@ -710,6 +720,16 @@ bool USRPGCombatModel::UnregisterTurn(UUnitModel* Owner, bool IgnoreCurTurn) return true; } +int32 USRPGCombatModel::UnregisterAllTurn(UUnitModel* Owner, bool IncludeCurTurn) +{ + int32 UnregisterCount = 0; + while (UnregisterTurn(Owner, IncludeCurTurn) == true) + { + ++UnregisterCount; + } + return UnregisterCount; +} + bool USRPGCombatModel::EvaluateRound() { // A corrupted or stalled speed configuration must not block the game thread in Shipping. @@ -1038,7 +1058,7 @@ void USRPGCombatModel::UnregisterUnitModel(UUnitModel* UnitModel) /* 턴 제거 */ - UnregisterTurn(UnitModel); + UnregisterAllTurn(UnitModel); /* 제거 */ @@ -1196,6 +1216,16 @@ void USRPGCombatModel::ForcedBeginTurn() CurTurnContext->BeginTurn(); } +TSharedPtr USRPGCombatModel::GetCurrentActionEndHoldBarrier() const +{ + if (HasAnyTurnContext() == true) + { + USRPGTurnContext* CurTurnContext = GetCurrentTurnContext(); + return CurTurnContext->GetCurrentActionEndHoldBarrier(); + } + return nullptr; +} + bool USRPGCombatModel::HasAnyTurnContext() const { return mTurnContextOrders.IsEmpty() == false; diff --git a/Source/P_RD/Singleton/WorldSubsystem/SRPGCombatModel.h b/Source/P_RD/Singleton/WorldSubsystem/SRPGCombatModel.h index fda6a8e6f..3a9bcf016 100644 --- a/Source/P_RD/Singleton/WorldSubsystem/SRPGCombatModel.h +++ b/Source/P_RD/Singleton/WorldSubsystem/SRPGCombatModel.h @@ -105,11 +105,12 @@ class P_RD_API USRPGCombatModel : public UObjectModel, public FTickableGameObjec /* UObjectModel 상속 */ public: void Serialize(FArchive& Ar) override; - + /* FTickableGameObject 상속 */ public: void Tick(float DeltaTime) override; - bool IsTickable() const override; + ETickableTickType GetTickableTickType() const override; + bool IsAllowedToTick() const override final; TStatId GetStatId() const override; /* 생명 주기 함수 */ @@ -186,6 +187,7 @@ class P_RD_API USRPGCombatModel : public UObjectModel, public FTickableGameObjec */ bool UnregisterTurn(USRPGTurnContext* TurnContext, bool IgnoreCurTurn = true); bool UnregisterTurn(UUnitModel* Owner, bool IncludeCurTurn = true); + int32 UnregisterAllTurn(UUnitModel* Owner, bool IncludeCurTurn = true); protected: bool EvaluateRound(); @@ -231,6 +233,10 @@ class P_RD_API USRPGCombatModel : public UObjectModel, public FTickableGameObjec void ForcedBeginTurn(); /* 외부 API 함수 */ + /* 외부 API */ +public: + TSharedPtr GetCurrentActionEndHoldBarrier() const; + public: bool HasAnyTurnContext() const; int32 GetTurnContextCount() const; diff --git a/Source/P_RDTests/Actor/BoardActor/GimmickModelTests.cpp b/Source/P_RDTests/Actor/BoardActor/GimmickModelTests.cpp index b7c1c5c23..bf75b8795 100644 --- a/Source/P_RDTests/Actor/BoardActor/GimmickModelTests.cpp +++ b/Source/P_RDTests/Actor/BoardActor/GimmickModelTests.cpp @@ -722,5 +722,7 @@ bool FPuddleRoundEndEventTests::RunTest(const FString& Parameters) TestEqual(TEXT("[Case3] 유닛A 기절 3"), GetStunCount(UnitA), 3); TestEqual(TEXT("[Case3] 장판C 수명 2 -> 1"), PuddleC->GetRemainingRoundCount(), 1); + CombatModel->Destroy(); + return true; } diff --git a/Source/P_RDTests/Singleton/RunCheckpointTests.cpp b/Source/P_RDTests/Singleton/RunCheckpointTests.cpp index ed185087e..ff464dfbd 100644 --- a/Source/P_RDTests/Singleton/RunCheckpointTests.cpp +++ b/Source/P_RDTests/Singleton/RunCheckpointTests.cpp @@ -1,4 +1,4 @@ -#include "P_RDTests.h" +#include "P_RDTests.h" #include "Misc/AutomationTest.h" #include "Singleton/RunCheckpointTestsHelper.h" #include "SaveGame/SaveCheckpoint.h" @@ -198,6 +198,10 @@ bool FCheckpointStalledRoundGuardTest::RunTest(const FString& Parameters) AddExpectedError(TEXT("Combat speed cannot produce a turn"), EAutomationExpectedErrorFlags::Contains, 1); TestFalse(TEXT("Empty combat returns instead of spinning in Shipping"), Model->EvaluateEmptyRound()); TestEqual(TEXT("Blocked combat reports once"), BlockedCount, 1); + + Model->Destroy(); + Preview->Destroy(); + return true; }