KLingo Project Documentation 1.0.0
Unreal Engine 5.6 C++ Project Documentation
로딩중...
검색중...
일치하는것 없음
FoodHolder.cpp
이 파일의 문서화 페이지로 가기
1// Copyright (c) 2025 Doppleddiggong. All rights reserved. Unauthorized copying, modification, or distribution of this file, via any medium is strictly prohibited. Proprietary and confidential.
2
3
4#include "FoodHolder.h"
5
6#include "ADoor.h"
7#include "ALingoGameState.h"
8#include "Food.h"
9#include "GameLogging.h"
11#include "CityName.h"
12#include "FoodCourtManager.h"
13#include "OrderKiosk.h"
14#include "Popup_Result.h"
15#include "UBroadcastManager.h"
16#include "UPopupManager.h"
17#include "Onepiece/Onepiece.h"
18#include "Components/BoxComponent.h"
19#include "Components/SkeletalMeshComponent.h"
20#include "Kismet/GameplayStatics.h"
21#include "Net/UnrealNetwork.h"
22
24{
25 PrimaryActorTick.bCanEverTick = true;
26
27 // Replication
28 bReplicates = true;
29
30 // Root component
31 USceneComponent* Root = CreateDefaultSubobject<USceneComponent>(TEXT("DefaultSceneRoot"));
32 RootComponent = Root;
33
34 // Mesh component
35 MeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("MeshComponent"));
36 MeshComponent->SetupAttachment(RootComponent);
37
38 // Box collision component
39 BoxCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("BoxCollision"));
40 BoxCollision->SetupAttachment(MeshComponent);
41 BoxCollision->SetGenerateOverlapEvents(true);
42 BoxCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
43 BoxCollision->SetCollisionResponseToAllChannels(ECR_Overlap);
44
45 // HoldPos component
46 HoldPos = CreateDefaultSubobject<USceneComponent>(TEXT("HoldPos"));
47 HoldPos->SetupAttachment(MeshComponent);
48}
49
51{
52 Super::BeginPlay();
53
54 BoxCollision->OnComponentBeginOverlap.AddDynamic(this, &AFoodHolder::OnFoodBoxOverlapBegin);
55
56 // 머티리얼 파라미터 초기화 (비활성화 상태)
58}
59
60void AFoodHolder::Tick(float DeltaTime)
61{
62 Super::Tick(DeltaTime);
63
64 // CurTarget이 유효하고 활성화된 상태라면 회전
66 {
67 FRotator CurrentRotation = CurTarget->GetActorRotation();
68 CurrentRotation.Yaw += RotationSpeed * DeltaTime;
69 CurTarget->SetActorRotation(CurrentRotation);
70 }
71}
72
73void AFoodHolder::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
74{
75 Super::GetLifetimeReplicatedProps(OutLifetimeProps);
76
77 DOREPLIFETIME(AFoodHolder, bIsActivated);
78 DOREPLIFETIME(AFoodHolder, CurTarget);
79 DOREPLIFETIME(AFoodHolder, TryIdx);
80}
81
83{
84 // bIsActivated가 복제될 때 머티리얼 업데이트
86}
87
89{
90 // CurTarget이 복제될 때 클라이언트에서도 충돌 비활성화
91 if (CurTarget)
92 {
93 if (AFood* Food = Cast<AFood>(CurTarget))
94 {
95 if (Food->Mesh)
96 {
97 Food->Mesh->SetSimulatePhysics(false);
98 Food->Mesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
99 }
100 PRINTLOG(TEXT("AFoodHolder::OnRep_CurTarget - Disabled collision for Food on client"));
101 }
102 }
103}
104
105void AFoodHolder::SetAnswerFoodIndex(int32 InAnswerFoodIndex)
106{
107 this->AnswerFoodIndex = InAnswerFoodIndex;
108}
109
111 UPrimitiveComponent* OverlappedComponent,
112 AActor* OtherActor,
113 UPrimitiveComponent* OtherComp,
114 int32 OtherBodyIndex,
115 bool bFromSweep,
116 const FHitResult& SweepResult)
117{
118 if (!OtherActor)
119 return;
120
121 if (!HasAuthority())
122 return;
123
124 if (bIsActivated)
125 return;
126
127 ALingoGameState* GS = Cast<ALingoGameState>(GetWorld()->GetGameState());
128 if (!GS) return;
129
130 TryIdx++;
131
132 // Food인지 확인
133 if (AFood* Food = Cast<AFood>(OtherActor))
134 {
135 // 정답 판정
136 const bool bSuccess = CheckFood(Food);
137
138 // 제출 리스트에 제출 데이터 업데이트
139 FScenarioTargetData TempData;
140 TempData.word1 = Food->CurrentFoodData.word1;
141 TempData.word2 = Food->CurrentFoodData.word2;
142 GS->TryListenAnswerData.target_data.Add(TempData);
143
144 // 블루프린트 이벤트 호출
145 OnActivate(bSuccess);
146
147 if (bSuccess)
148 {
149 UE_LOG(LogTemp, Warning, TEXT("[FoodHolder] Correct"));
150
151 GS->SetAllCompassVisibility(false);
152 GS->SetCompassVisibilityByTag("ListenQuestEnd", true);
153
154 // 정답인 경우
155 FTimerHandle TimerHandle;
156 GetWorldTimerManager().SetTimer(TimerHandle, [this, Food]
157 {
159
160 // 모든 액터의 글씨 없애기
161 AActor* FoodContainerManager = UGameplayStatics::GetActorOfClass(GetWorld(), AFoodCourtManager::StaticClass());
162 if (AFoodCourtManager* FCManager = Cast<AFoodCourtManager>(FoodContainerManager))
163 {
164 FCManager->DisableAllListenAnswersText();
165 }
166
167 // Food 캡슐도 텍스트 없애기
168 Food->CurrentFoodData.word1.name = TEXT("");
169 Food->CurrentFoodData.word2.name = TEXT("");
170
171 if (HasAuthority()) Food->UpdateFoodWidget();
172
173 // 모든 클라이언트에 정답 인덱스와 함께 결과 팝업 표시
175 }, 0.5f, false);
176 }
177 else
178 {
179 UE_LOG(LogTemp, Warning, TEXT("[FoodHolder] Wrong"));
180
181 // 오답인 경우
182 FTimerHandle TimerHandle;
183 GetWorldTimerManager().SetTimer(TimerHandle, [this, Food, GS]
184 {
185 // 오답 리스트에 추가
187
188 // 모든 클라이언트에 오답 메시지 표시
189 Multicast_ShowWrongPopup(Food->CurrentFoodData.word1.name);
190
191 // 컨베이어 투입구 텍스트 원상복구
192 TArray<AActor*> CityNames;
193 UGameplayStatics::GetAllActorsOfClass(GetWorld(), ACityName::StaticClass(), CityNames);
194
195 for (AActor* Actor : CityNames)
196 {
197 if (ACityName* CN = Cast<ACityName>(Actor))
198 {
199 CN->SetDefaultText();
200 }
201 }
202
203 // 투입구 상태 초기화
204 TArray<AActor*> OrderKiosks;
205 UGameplayStatics::GetAllActorsOfClass(GetWorld(), AOrderKiosk::StaticClass(), OrderKiosks);
206 for (auto OrderKiosk : OrderKiosks)
207 {
208 if (AOrderKiosk* Kiosk = Cast<AOrderKiosk>(OrderKiosk))
209 Kiosk->IsOnceStopped = false;
210 }
211
212 // 새 Food 캡슐에 부분 정답 데이터 저장
213 /* Food 또는 City만 정답일 경우 : 정답인 요소만 저장
214 * 둘 다 오답인 경우 : 완전 초기화 */
215 FFoodCapsuleData PrevData = Food->CurrentFoodData;
216 Food->Destroy();
217
218 // 새 FoodContainer 생성
219 AActor* FoodContainerManager = UGameplayStatics::GetActorOfClass(GetWorld(), AFoodCourtManager::StaticClass());
220 if (AFoodCourtManager* FCManager = Cast<AFoodCourtManager>(FoodContainerManager))
221 {
222 FCManager->SpawnFoodContainer();
223
224 FTimerHandle PartialAnswerTimer;
225 GetWorldTimerManager().SetTimer(PartialAnswerTimer, [this, PrevData]
226 {
227 AFood* NewFood = Cast<AFood>(UGameplayStatics::GetActorOfClass(GetWorld(), AFood::StaticClass()));
228 if (!NewFood)
229 {
230 UE_LOG(LogTemp, Error, TEXT("[FoodHolder] NewFood is nullptr!"));
231 return;
232 }
233
234 // 부분정답 판정해서 틀리면 공백으로 되돌리기
235 bool bCityCorrect = false;
236 bool bFoodCorrect = false;
237 CheckPartialAnswer(PrevData, bCityCorrect, bFoodCorrect);
238
239 // City만 정답인 경우
240 if (bCityCorrect && !bFoodCorrect)
241 {
242 UE_LOG(LogTemp, Warning, TEXT("[FoodHolder] City Only Correct - Updating City name widget"));
243 NewFood->CurrentFoodData.word1 = PrevData.word1;
244 NewFood->UpdateFoodWidget();
245 }
246 // Food만 정답인 경우
247 else if (!bCityCorrect && bFoodCorrect)
248 {
249 UE_LOG(LogTemp, Warning, TEXT("[FoodHolder] Food Only Correct - Updating Food mesh"));
250 NewFood->CurrentFoodData.word2 = PrevData.word2;
251 NewFood->UpdateFoodWidget();
252 NewFood->UpdateMesh();
253 }
254 // 둘 다 오답: 기본 빈 상태 유지
255 else if (!bCityCorrect && !bFoodCorrect)
256 {
257 UE_LOG(LogTemp, Warning, TEXT("[FoodHolder] Both Wrong - NewFood remains empty"));
258 }
259 }, 1.0f, false);
260 }
261
262 }, 0.5f, false);
263 }
264 }
265}
266
268{
269 if (!TargetFood) return false;
270
271 // Food의 모든 충돌 비활성화
272 if (TargetFood->Mesh)
273 {
274 TargetFood->Mesh->SetSimulatePhysics(false);
275 TargetFood->Mesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
276 }
277
278 // ListenQuest 정답 인덱스 가져오기
279 ALingoGameState* GS = Cast<ALingoGameState>(GetWorld()->GetGameState());
280 if (!GS) return false;
281
282 const int32 CorrectIdx = GS->GetListenScenarioData().correct_answer_index;
283 const TArray<FScenarioTargetData>& ScenarioData = GS->GetListenScenarioData().target_data;
284
285 FString CorrectCityName = ScenarioData[CorrectIdx].word1.name;
286 FString CorrectFoodName = ScenarioData[CorrectIdx].word2.name;
287
288 if (CorrectFoodName == TargetFood->CurrentFoodData.word2.name
289 && CorrectCityName == TargetFood->CurrentFoodData.word1.name)
290 {
291 // Success: Food를 HoldPos 위치보다 살짝 위에 배치
292 FVector ActivatedLocation = HoldPos->GetComponentLocation();
293 ActivatedLocation.Z += ActivatedHeightOffset;
294 TargetFood->SetActorLocation(ActivatedLocation);
295 TargetFood->SetActorRotation(HoldPos->GetComponentRotation());
296
297 // Activate 상태로 전환
298 bIsActivated = true;
299 CurTarget = TargetFood;
300
301 // 서버에서도 머티리얼 업데이트 (클라이언트는 OnRep_IsActivated에서 호출됨)
303
304 return true;
305 }
306 else
307 {
308 UE_LOG(LogTemp, Warning, TEXT("[CheckFood] Returning FALSE"));
309
310 // Fail: 서버에서 머티리얼 업데이트 (오답)
311 bIsActivated = false;
312 UpdateActivateState(false);
313
314 return false;
315 }
316}
317
318void AFoodHolder::CheckPartialAnswer(const struct FFoodCapsuleData& TargetData, bool& bOutCityCorrect, bool& bOutFoodCorrect)
319{
320 bOutCityCorrect = false;
321 bOutFoodCorrect = false;
322
323 // ListenQuest 정답 인덱스 가져오기
324 ALingoGameState* GS = Cast<ALingoGameState>(GetWorld()->GetGameState());
325 if (!GS) return;
326
327 const int32 CorrectIdx = GS->GetListenScenarioData().correct_answer_index;
328 const TArray<FScenarioTargetData>& ScenarioData = GS->GetListenScenarioData().target_data;
329
330 FString CorrectCityName = ScenarioData[CorrectIdx].word1.name;
331 FString CorrectFoodName = ScenarioData[CorrectIdx].word2.name;
332
333 // 각각 판정
334 bOutCityCorrect = (CorrectCityName == TargetData.word1.name);
335 bOutFoodCorrect = (CorrectFoodName == TargetData.word2.name);
336}
337
339{
340 // 머티리얼 파라미터 설정
341 if (MeshComponent && MeshComponent->GetNumMaterials() > 0)
342 {
343 UMaterialInstanceDynamic* DynamicMaterial = Cast<UMaterialInstanceDynamic>(MeshComponent->GetMaterial(0));
344 if (!DynamicMaterial)
345 DynamicMaterial = MeshComponent->CreateDynamicMaterialInstance(0);
346
347 if (DynamicMaterial)
348 DynamicMaterial->SetScalarParameterValue(FName("Activate"), State ? 1.0f : 0.0f);
349 }
350}
351
357void AFoodHolder::Multicast_ShowResultPopup_Implementation(int32 CorrectAnswerIndex)
358{
359 // 모든 클라이언트에서 로컬 GameState에 정답 인덱스 추가
360 if (ALingoGameState* GS = Cast<ALingoGameState>(GetWorld()->GetGameState()))
361 {
362 // 중복 체크 후 추가
363 if (!GS->WrongListenAnswerList.Contains(CorrectAnswerIndex))
364 {
365 GS->WrongListenAnswerList.Add(CorrectAnswerIndex);
366 PRINTLOG(TEXT("[Multicast_ShowResultPopup] Added correct answer index %d to local GameState"), CorrectAnswerIndex);
367 }
368 }
369
370 // 팝업 표시
371 if (auto Popup = UPopupManager::ShowPopupAs<UPopup_Result>(GetWorld(), EPopupType::Result))
372 {
373 Popup->InitPopup(EQuestType::Listen);
374 }
375}
376
383void AFoodHolder::Multicast_ShowWrongPopup_Implementation(const FString& FoodName)
384{
385 // 모든 클라이언트(호스트 포함)에서 오답 메시지 표시
386 FString Message = FString::Printf(TEXT("Wrong Answer\nThis is not the correct Answer.\n\nFood: %s"),
387 *FoodName);
388
389 if (auto DM = UBroadcastManager::Get(this))
390 DM->SendTutorMessage(FText::FromString(Message));
391}
네트워크 복제를 위한 전역 브로드캐스트 Actor
YiSan 전반에서 사용하는 공용 인터페이스를 선언합니다.
#define PRINTLOG(fmt,...)
Definition GameLogging.h:30
virtual void Tick(float DeltaTime) override
bool CheckFood(class AFood *TargetFood)
Food 검증 함수
TObjectPtr< class AActor > CurTarget
Definition FoodHolder.h:97
float ActivatedHeightOffset
Definition FoodHolder.h:105
TObjectPtr< class UBoxComponent > BoxCollision
Definition FoodHolder.h:79
void Multicast_ShowResultPopup(int32 CorrectAnswerIndex)
[Multicast RPC] 모든 클라이언트에 정답 결과 팝업 표시
virtual void BeginPlay() override
TObjectPtr< class USceneComponent > HoldPos
Definition FoodHolder.h:85
void Multicast_ShowWrongPopup(const FString &FoodName)
[Multicast RPC] 모든 클라이언트에 오답 메시지 표시
void UpdateActivateState(bool State)
void OnFoodBoxOverlapBegin(UPrimitiveComponent *OverlappedComponent, AActor *OtherActor, UPrimitiveComponent *OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult &SweepResult)
BoxCollision Overlap 콜백
int32 TryIdx
Definition FoodHolder.h:93
int32 AnswerFoodIndex
정답 Food 인덱스 (-1이면 모든 Food 허용)
Definition FoodHolder.h:112
TObjectPtr< class USkeletalMeshComponent > MeshComponent
Definition FoodHolder.h:82
void CheckPartialAnswer(const struct FFoodCapsuleData &TargetData, bool &bOutCityCorrect, bool &bOutFoodCorrect)
void OnRep_IsActivated()
float RotationSpeed
Definition FoodHolder.h:108
void OnActivate(bool bSuccess)
virtual void GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > &OutLifetimeProps) const override
bool bIsActivated
Definition FoodHolder.h:101
void OnRep_CurTarget()
void SetAnswerFoodIndex(int32 InAnswerFoodIndex)
정답 Food 인덱스 설정
Definition Food.h:37
void UpdateFoodWidget()
Widget에 음식 이름 업데이트
Definition Food.cpp:100
class UStaticMeshComponent * Mesh
Definition Food.h:56
void UpdateMesh()
음식 메시 업데이트 (DataTable에서 로드)
Definition Food.cpp:123
FFoodCapsuleData CurrentFoodData
Definition Food.h:77
TArray< int32 > WrongListenAnswerList
FORCEINLINE const FResponseListenScenario & GetListenScenarioData() const
FResponseListenScenario TryListenAnswerData
void SetCompassVisibilityByTag(FName Tag, bool bVisible)
void SetAllCompassVisibility(bool bVisible)
static ANetworkBroadcastActor * Get(const UObject *WorldContextObject)
싱글톤 인스턴스 가져오기
void SendDoorMessage(int InDoorIndex, bool bOpen, AActor *EventInstigator)
문 상태 변경 메시지를 네트워크로 전송
static const int32 Step2_End
Definition Onepiece.h:29
FWordInfo word1
시나리오 단어 정보
Definition Food.h:22
FWordInfo word2
Definition Food.h:25
TArray< FScenarioTargetData > target_data
Scenario 타겟 데이터입니다.
FString name