KLingo Project Documentation 1.0.0
Unreal Engine 5.6 C++ Project Documentation
로딩중...
검색중...
일치하는것 없음
APlayerControl.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
7#include "APlayerControl.h"
8
9#include "APlayerActor.h"
10#include "IControllable.h"
11#include "UMainWidget.h"
12#include "UQuestInfoWidget.h"
13#include "AWheatly.h"
15
16#include "EnhancedInputSubsystems.h"
17#include "EnhancedInputComponent.h"
18
19#include "InputMappingContext.h"
20#include "InputAction.h"
21
22#include "FComponentHelper.h"
23#include "UBroadcastManager.h"
24#include "UInteractionSystem.h"
25#include "UHookSystem.h"
28#include "ADropper.h"
29#include "ALingoGameState.h"
31#include "EngineUtils.h"
32#include "GameLogging.h"
34#include "MiniOwlBot.h"
35#include "QuestOrderWidget.h"
36#include "TutorialComponent.h"
37#include "UDialogManager.h"
38#include "ULingoGameHelper.h"
39#include "UPopupManager.h"
40#include "UPopup_SpeakQuest.h"
41#include "UPopup_SpeakResult.h"
42#include "UConfigLibrary.h"
43#include "UChatHistorySystem.h"
44#include "UPopup_DailyStudy.h"
45#include "UPopup_History.h"
46#include "UPopup_HowToPlay.h"
47#include "Onepiece/Onepiece.h"
48
49#define IMC_DEFAULT_PATH TEXT("/Game/CustomContents/Input/IMC_Game_Player.IMC_Game_Player")
50#define IA_MOVE_PATH TEXT("/Game/CustomContents/Input/IA_Game_Movement.IA_Game_Movement")
51#define IA_LOOK_PATH TEXT("/Game/CustomContents/Input/IA_Game_LookAround.IA_Game_LookAround")
52#define IA_JUMP_PATH TEXT("/Game/CustomContents/Input/IA_Game_Jump.IA_Game_Jump")
53#define IA_RECORD_PATH TEXT("/Game/CustomContents/Input/IA_Game_Record.IA_Game_Record")
54#define IA_GRAB_PATH TEXT("/Game/CustomContents/Input/IA_Game_Grab.IA_Game_Grab")
55#define IA_INTERACT_PATH TEXT("/Game/CustomContents/Input/IA_Game_Interact.IA_Game_Interact")
56#define IA_RUN_PATH TEXT("/Game/CustomContents/Input/IA_Game_Run.IA_Game_Run")
57#define IA_INFO_PATH TEXT("/Game/CustomContents/Input/IA_Game_Info.IA_Game_Info")
58#define IA_HOOK_PATH TEXT("/Game/CustomContents/Input/IA_Game_Hook.IA_Game_Hook")
59#define IA_CHAT_PATH TEXT("/Game/CustomContents/Input/IA_EnterChat.IA_EnterChat")
60#define IA_HISTORY_PATH TEXT("/Game/CustomContents/Input/IA_Game_History.IA_Game_History")
61#define IA_HOWTOCTRL_PATH TEXT("/Game/CustomContents/Input/IA_Game_HowToCtrl.IA_Game_HowToCtrl")
62#define IA_HOWTOPLAY_PATH TEXT("/Game/CustomContents/Input/IA_Game_HowToPlay.IA_Game_HowToPlay")
63
64
86
88{
89 Super::BeginPlay();
90
91 if (auto LP = GetLocalPlayer())
92 {
93 if (auto SubSystem = LP->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
94 {
95 if (IMC_Default)
96 {
97 SubSystem->ClearAllMappings();
98 SubSystem->AddMappingContext(IMC_Default, 0);
99 }
100 }
101 }
102 if (IsLocalController())
103 {
104 UserInfo = ULingoGameInstanceSubsystem::Get(GetWorld())->GetUserInfo();
106
107 // 맵 로딩 완료 시 로딩 서클 숨김
108 if (ULoadingCircleManager* LoadingManager = ULoadingCircleManager::Get(GetWorld()))
109 LoadingManager->LoadingCircle(false);
110 }
111
112 // 서버에서만 DoorMessage 구독
113 if (HasAuthority())
114 {
115 UBroadcastManager::Get(GetWorld())->OnDoorMessage.AddDynamic(this, &APlayerControl::OnDoorMessage);
116 }
117}
118
120{
121 Super::SetupInputComponent();
122
123 if (UEnhancedInputComponent* EIC = Cast<UEnhancedInputComponent>(InputComponent))
124 {
125 EIC->BindAction(IA_Move, ETriggerEvent::Triggered, this, &APlayerControl::OnMove);
126 EIC->BindAction(IA_Move, ETriggerEvent::Completed, this, &APlayerControl::OnStopMove);
127 EIC->BindAction(IA_Look, ETriggerEvent::Triggered, this, &APlayerControl::OnLook);
128
129 EIC->BindAction(IA_Jump, ETriggerEvent::Started, this, &APlayerControl::OnJump);
130
131 EIC->BindAction(IA_Record, ETriggerEvent::Started, this, &APlayerControl::OnRecordPressed);
132 EIC->BindAction(IA_Record, ETriggerEvent::Completed, this, &APlayerControl::OnRecordReleased);
133
134 EIC->BindAction(IA_Grab, ETriggerEvent::Started, this, &APlayerControl::OnGrab);
135 EIC->BindAction(IA_Grab, ETriggerEvent::Completed, this, &APlayerControl::OnGrabRelease);
136
137 EIC->BindAction(IA_Interact, ETriggerEvent::Started, this, &APlayerControl::OnInteract);
138 EIC->BindAction(IA_Run, ETriggerEvent::Started, this, &APlayerControl::OnRun);
139
140 EIC->BindAction(IA_Info, ETriggerEvent::Started, this, &APlayerControl::OnInfo);
141
142 EIC->BindAction(IA_Hook, ETriggerEvent::Started, this, &APlayerControl::OnHook);
143
144 EIC->BindAction(IA_Chat, ETriggerEvent::Started, this, &APlayerControl::OnChat);
145 EIC->BindAction(IA_History, ETriggerEvent::Started, this, &APlayerControl::OnHistory);
146 EIC->BindAction(IA_HowToCtrl, ETriggerEvent::Started, this, &APlayerControl::OnHowToCtrl);
147 EIC->BindAction(IA_HowToPlay, ETriggerEvent::Started, this, &APlayerControl::OnHowToPlay);
148 }
149}
150
152{
153 UBroadcastManager::Get(GetWorld())->SendUpdateQuestRole(QuestRole);
154}
155
157{
158 APawn* P = GetPawn();
159 if (!P)
160 return nullptr;
161
162 // UObject 기반 UInterface 라면 Cast 가능
163 if (IControllable* C = Cast<IControllable>(P))
164 return C;
165
166 return nullptr;
167}
168
170{
171 const int32 UserId = ULingoGameHelper::GetUserId(GetWorld());
172
173 if (UConfigLibrary::GetUserBool(UserId, TEXT("TutorialCompleted"), false))
174 {
175 PRINTLOG(TEXT("[Tutorial] User %d already completed tutorial"), UserId);
176 return true; // 스킵
177 }
178
179 return false; // 튜토리얼 진행
180}
181
183{
185 {
186 TutorialComponent->StartTutorial();
187 PRINTLOG(TEXT("[Tutorial] Starting tutorial manually"));
188 }
189}
190
192{
193 // 세이브 시스템에 여부 저장
194 const int32 UserId = ULingoGameHelper::GetUserId(GetWorld());
195 UConfigLibrary::SetUserBool(UserId, TEXT("TutorialCompleted"), true);
196}
197
198void APlayerControl::OnMove(const FInputActionValue& Value)
199{
201 C->Cmd_Move(Value.Get<FVector2D>());
202}
203
204void APlayerControl::OnLook(const FInputActionValue& Value)
205{
207 C->Cmd_Look(Value.Get<FVector2D>());
208}
209
210void APlayerControl::OnStopMove(const FInputActionValue& Value)
211{
213 {
214 C->Cmd_StopMove();
215 }
216}
217
218void APlayerControl::OnJump(const FInputActionValue&)
219{
221 C->Cmd_Jump();
222
223 //this->TEST_DropperDropProcess();
224 // this->TEST_AddItemToBoxList();
225}
226
227
228void APlayerControl::OnRun(const FInputActionValue& Value)
229{
231 {
232 C->Cmd_Run();
233 }
234}
235
236void APlayerControl::OnRecordPressed(const FInputActionValue& Value)
237{
239 C->Cmd_RecordStart();
240}
241
242void APlayerControl::OnRecordReleased(const FInputActionValue& Value)
243{
245 C->Cmd_RecordEnd();
246}
247
248void APlayerControl::OnInfo(const FInputActionValue& Value)
249{
251 {
252 C->Cmd_Info();
253 }
254}
255
256void APlayerControl::OnGrab(const FInputActionValue& Value)
257{
259}
260
261void APlayerControl::OnGrabRelease(const FInputActionValue& Value)
262{
264}
265
266void APlayerControl::OnInteract(const FInputActionValue& Value)
267{
268 // 튜토리얼 체크 (로컬 플레이어만, 실제 동작은 서버에서만)
269 APlayerActor* MyPlayer = Cast<APlayerActor>(GetPawn());
270 if (MyPlayer && MyPlayer->InteractionSystem && MyPlayer->InteractionSystem->CurrentTarget)
271 {
272 // Button 타입인 경우 튜토리얼 체크
273 if (MyPlayer->InteractionSystem->CurrentTarget->InteractionType == EInteractionType::Button)
274 {
275 AActor* TargetActor = MyPlayer->InteractionSystem->CurrentTarget->GetOwner();
276 if (UTutorialComponent* Tutorial = FindComponentByClass<UTutorialComponent>())
277 {
278 Tutorial->OnObjectInteracted(TargetActor);
279 }
280 }
281 }
282
284}
285
286void APlayerControl::Server_OnGrab_Implementation()
287{
288 APlayerActor* MyPlayer = Cast<APlayerActor>(GetPawn());
289 if (MyPlayer && MyPlayer->InteractionSystem)
290 {
291 MyPlayer->InteractionSystem->TryPickUp();
292 }
293}
294
295void APlayerControl::Server_OnGrabRelease_Implementation()
296{
297 APlayerActor* MyPlayer = Cast<APlayerActor>(GetPawn());
298 if (MyPlayer && MyPlayer->InteractionSystem)
299 {
300 MyPlayer->InteractionSystem->TryDrop();
301 }
302}
303
304
305void APlayerControl::Server_OnInteract_Implementation()
306{
307 APlayerActor* MyPlayer = Cast<APlayerActor>(GetPawn());
308 if (MyPlayer && MyPlayer->InteractionSystem)
309 {
310 MyPlayer->InteractionSystem->TryInteract();
311 }
312}
313
314void APlayerControl::OnHook(const FInputActionValue& Value)
315{
317}
318
319void APlayerControl::OnChat(const FInputActionValue& Value)
320{
321 // GameAndUI로??
322 FInputModeUIOnly uiInputMode;
323 SetInputMode(uiInputMode);
324
325 APlayerActor* player = Cast<APlayerActor>(GetPawn());
326 player->GetMainWidget()->SetFocusOnChat();
327}
328
329void APlayerControl::OnHistory(const FInputActionValue& Value)
330{
331 if (auto Popup = UPopupManager::ShowPopupAs<UPopup_History>(GetWorld(), EPopupType::History))
332 {
333 Popup->InitPopup();
334 }
335}
336
337
338void APlayerControl::OnHowToCtrl(const FInputActionValue& Value)
339{
341}
342
343
344void APlayerControl::OnHowToPlay(const FInputActionValue& Value)
345{
346 // 현재 진행 중인 퀘스트 타입 확인
347 if (auto PS = GetPlayerState<ALingoPlayerState>())
348 {
349 // Read 퀘스트 진행 중
350 if (PS->bReadQuestIng && !PS->bReadQuestCompleted)
351 {
353 }
354 else if (PS->bListenQuestIng && !PS->bListenQuestCompleted)
355 {
357 }
358 else if (PS->bSpeakQuestIng && !PS->bSpeakQuestCompleted)
359 {
361 }
362 else if (PS->bWriteQuestIng && !PS->bWriteQuestCompleted)
363 {
365 }
366 }
367}
368
370{
371 if (auto Popup = UPopupManager::ShowPopupAs<UPopup_HowToPlay>(GetWorld(), EPopupType::HowToPlay))
372 {
373 TArray<EHowToPlayPageType> PageTypes;
374
375 if ( QuestType == EQuestType::None )
376 {
377 PageTypes.Add(EHowToPlayPageType::Control_P1);
378 PageTypes.Add(EHowToPlayPageType::Control_P2);
379 PageTypes.Add(EHowToPlayPageType::Control_P3);
380 PageTypes.Add(EHowToPlayPageType::Control_P4);
381 PageTypes.Add(EHowToPlayPageType::Control_P5);
382 PageTypes.Add(EHowToPlayPageType::Control_P6);
383 }
384 else if ( QuestType == EQuestType::Read )
385 {
386 PageTypes.Add(EHowToPlayPageType::Read_P1);
387 PageTypes.Add(EHowToPlayPageType::Read_P2);
388 PageTypes.Add(EHowToPlayPageType::Read_P3);
389 PageTypes.Add(EHowToPlayPageType::Read_P4);
390 }
391 else if ( QuestType == EQuestType::Listen )
392 {
393 PageTypes.Add(EHowToPlayPageType::Listen_P1);
394 PageTypes.Add(EHowToPlayPageType::Listen_P2);
395 PageTypes.Add(EHowToPlayPageType::Listen_P3);
396 PageTypes.Add(EHowToPlayPageType::Listen_P4);
397 }
398 else if ( QuestType == EQuestType::Speak )
399 {
400 PageTypes.Add(EHowToPlayPageType::Speak_P1);
401 PageTypes.Add(EHowToPlayPageType::Speak_P2);
402 PageTypes.Add(EHowToPlayPageType::Speak_P3);
403 }
404 else if ( QuestType == EQuestType::Write )
405 {
406 PageTypes.Add(EHowToPlayPageType::Write_P1);
407 PageTypes.Add(EHowToPlayPageType::Write_P2);
408 PageTypes.Add(EHowToPlayPageType::Write_P3);
409 }
410
411 Popup->InitPopup(PageTypes);
412 }
413}
414
415
416void APlayerControl::Server_OnHook_Implementation()
417{
418 APlayerActor* MyPlayer = Cast<APlayerActor>(GetPawn());
419 if (MyPlayer && MyPlayer->HookSystem)
420 {
421 MyPlayer->HookSystem->TryHook();
422 }
423}
424
425void APlayerControl::Server_SetUserInfo_Implementation(const FResponseUserMe& InUserInfo)
426{
427 UserInfo = InUserInfo;
428}
429
431{
432 if (auto World = GetWorld())
433 {
434 for (TActorIterator<ADropper> It(World); It; ++It)
435 {
436 ADropper* Dropper = *It;
437 if (Dropper)
438 {
439 if (Dropper->IsBusy() )
440 {
441 Requester->Client_ToastMessage( TEXT("Dropper is Busy") );
442 return;
443 }
444
445 FLuggageData tmpData;
446 tmpData.word1 = tmpData.word1.GetRandomAnimal();
447 tmpData.word2 = tmpData.word2.GetRandomColor();
448
449 Dropper->SetSpawnData(tmpData);
450 Dropper->SetSpawnClass( LoadClass<AActor>(nullptr, TEXT("/Game/CustomContents/Blueprints/Interactables/BP_Luggage.BP_Luggage_C")));
451 Dropper->RequestSpawn();
452 return;
453 }
454 }
455 }
456}
457
458void APlayerControl::Server_RequestDrop_Implementation()
459{
460 RequestDrop(this);
461}
462
463void APlayerControl::Client_ToastMessage_Implementation(const FString& Message)
464{
465 UDialogManager::Get(GetWorld())->ShowToast(Message);
466}
467
468void APlayerControl::Client_UpdateSpeakQuest_Implementation(int32 StepIndex)
469{
470 ALingoPlayerState* PS = GetPlayerState<ALingoPlayerState>();
471 if (!PS)
472 return;
473
474 if (StepIndex == 0)
475 {
476 // 첫 번째 질문일 경우 MessageBox 표시
477 if (auto Popup = UPopupManager::ShowPopupAs<UPopup_SpeakQuest>(GetWorld(), EPopupType::SpeakQuest))
478 {
479 // MessageBox OK 버튼 클릭 시 질문 표시
480 FOnMsgBoxOkDelegate OnOkDelegate;
481
482 Popup->InitPopup( FOnMsgBoxOkDelegate().CreateLambda([this, StepIndex]()
483 {
484 if (APlayerActor* PlayerActor = Cast<APlayerActor>(GetPawn()))
485 {
486 PlayerActor->PlaySpeakInfo(StepIndex);
487 }
488 UpdateSpeakWidget(StepIndex);
489 }));
490 }
491 }
492 else
493 {
494 if (APlayerActor* PlayerActor = Cast<APlayerActor>(GetPawn()))
495 PlayerActor->PlaySpeakInfo(StepIndex);
496
497 UpdateSpeakWidget(StepIndex);
498 }
499}
500
501void APlayerControl::Client_EndSpeakQuest_Implementation()
502{
503 // Quest 완료 시에는 StepIndex를 -1로 전달하여 Widget을 숨김
505
506 // if (auto PopupManager = UPopupManager::Get(GetWorld()))
507 // PopupManager->ShowMsgBox(TEXT("SpeakQuest"), TEXT("QUEST COMPLETE"), EMsgBoxType::OK, FOnMsgBoxOkDelegate());
509}
510
511void APlayerControl::Client_InteractKiosk_Implementation()
512{
513 APlayerActor* MyPlayer = Cast<APlayerActor>(GetPawn());
514 UInteractableComponent* component = nullptr;
515 if (MyPlayer)
516 {
517 component = MyPlayer->InteractionSystem->CurrentTarget;
518 }
519 if (!component->bCanInteract)
520 return;
521
522 // 델리게이트 브로드캐스트
523 component->OnInteractionTriggered.Broadcast(GetPawn());
524
525 if (HasAuthority())
526 {
527 PRINTLOG( TEXT("InteractableComponent::TriggerInteraction server server"));
528 }
529}
530
531void APlayerControl::Client_RequestSpeakScenario_Implementation(AWheatly* Wheatly)
532{
533 if (!Wheatly)
534 {
535 PRINTLOG(TEXT("[APlayerControl] Client_RequestSpeakScenario: Wheatly is null"));
536 return;
537 }
538
539 // 현재 조종중인 PlayerActor 획득
540 APlayerActor* PlayerActor = Cast<APlayerActor>(GetPawn());
541 if (!PlayerActor)
542 {
543 PRINTLOG(TEXT("[APlayerControl] Client_RequestSpeakScenario: PlayerActor is null"));
544 return;
545 }
546
547 // Client에서 네트워크 요청 수행
548 if (auto KLingoNetwork = UKLingoNetworkSystem::Get(GetWorld()))
549 {
550 // Lambda를 사용하여 응답 처리
551 // this 캡처: APlayerControl의 Server RPC 호출을 위해
552 KLingoNetwork->RequestSpeakScenario( FResponseSpeakScenarioDelegate::CreateLambda(
553 [this, Wheatly](FResponseSpeakScenario& ResponseData, bool bWasSuccessful)
554 {
555 if (bWasSuccessful && Wheatly)
556 {
557 // 성공 시 자신의 Server RPC 호출 (PlayerControl은 Client 소유!)
559 PRINTLOG(TEXT("[APlayerControl] Client successfully received scenario data, syncing to server"));
560 }
561 else
562 {
563 // 실패 시 에러는 이미 ShowNetworkErrorPopup으로 표시됨
564 PRINTLOG(TEXT("[APlayerControl] Client failed to receive scenario data"));
565 }
566 }
567 )
568 );
569 }
570 else
571 {
572 PRINTLOG(TEXT("[APlayerControl] Client_RequestSpeakScenario: Failed to get KLingoNetworkSystem"));
573 }
574}
575
576void APlayerControl::Server_SyncSpeakScenarioData_Implementation(AWheatly* Wheatly, const FResponseSpeakScenario& Data)
577{
578 if (!Wheatly)
579 {
580 PRINTLOG(TEXT("[APlayerControl] Server_SyncSpeakScenarioData: Wheatly is null"));
581 return;
582 }
583
584 // 현재 조종중인 PlayerActor 획득
585 APlayerActor* PlayerActor = Cast<APlayerActor>(GetPawn());
586 if (!PlayerActor)
587 {
588 PRINTLOG(TEXT("[APlayerControl] Server_SyncSpeakScenarioData: PlayerActor is null"));
589 return;
590 }
591
592 // SpeakQuest 진행 상태 설정
593 if (ALingoPlayerState* PS = GetPlayerState<ALingoPlayerState>())
594 {
595 PS->SetSpeakQuestIng(true);
596 }
597
598 // Wheatly에 데이터 전달 (Server에서 실행됨)
599 Wheatly->SyncSpeakScenarioData(PlayerActor, Data);
600
601 PRINTLOG(TEXT("[APlayerControl] Server successfully synced scenario data to Wheatly"));
602}
603
604void APlayerControl::Server_SendDoorMessage_Implementation(int32 InDoorIndex, bool bOpen)
605{
606 if (!HasAuthority())
607 return;
608
609 // Pawn을 EventInstigator로 전달
610 if (APawn* MyPawn = GetPawn())
611 {
612 ANetworkBroadcastActor::Get(GetWorld())->SendDoorMessage(InDoorIndex, bOpen, MyPawn);
613 PRINTLOG(TEXT("[APlayerControl] Server sending DoorMessage: Index=%d, Open=%d"), InDoorIndex, bOpen);
614 }
615}
616
618{
619 if (APlayerActor* PlayerActor = Cast<APlayerActor>(GetPawn()))
620 {
621 if (UMainWidget* MainWidget = PlayerActor->GetMainWidget())
622 {
623 MainWidget->UpdateSpeakWidget(StepIndex);
624 }
625 }
626}
627
629{
630 if ( auto GS = Cast<ALingoGameState>(GetWorld()->GetGameState()) )
631 {
632 if (auto KLingoNetwork = UKLingoNetworkSystem::Get(GetWorld()))
633 {
634 FRequestSpeakResult SpeakRequest;
635 SpeakRequest.room_id = GS->GetRoomId();
636 SpeakRequest.user_id = GetUserId();
637 SpeakRequest.scenario_id = 1;
639 SpeakRequest.state_type = 0;
640 KLingoNetwork->RequestSpeakResult(SpeakRequest,
641 FResponseSpeakResultDelegate::CreateUObject(this, &APlayerControl::OnResponseSpeakResult));
642 }
643 }
644}
645
646void APlayerControl::OnResponseSpeakResult(FResponseSpeakResult& ResponseData, bool bWasSuccessful)
647{
648 if (bWasSuccessful)
649 {
650 if ( auto PS = GetPlayerState<ALingoPlayerState>() )
651 {
652 // PS에 결과 데이터를 저장한다
653 PS->SpeakResult = ResponseData;
654
655 if ( auto Popup = UPopupManager::ShowPopupAs<UPopup_SpeakResult>(GetWorld(), EPopupType::SpeakResult) )
656 Popup->InitPopup(ResponseData);
657 }
658 }
659 else
660 {
661 PRINTLOG(TEXT("[Result] Quest result Failed"));
662 }
663}
664
666{
667 // 테스트 : 맵에 있는 ADropper를 찾아서 ALuggage를 스폰
668 if ( HasAuthority())
669 RequestDrop(this);
670 else
672}
673
675{
676 // 테스트: 랜덤 아이템 추가
677 if (auto BM = UBroadcastManager::Get(GetWorld()))
678 {
679 TArray<FResultStatData> TestItems;
680
681 // 랜덤하게 1~3개의 아이템 생성
682 int32 ItemCount = FMath::RandRange(1, 3);
683 for (int32 i = 0; i < ItemCount; ++i)
684 {
685 FResultStatData Item;
686
687 // 랜덤 위젯 타입 선택
688 int32 RandomType = FMath::RandRange(0, 3);
689 switch (RandomType)
690 {
691 case 0: // Grade
693 Item.GradeTextureType = static_cast<EResourceTextureType>(FMath::RandRange(0, 4)); // Rarity_D ~ Rarity_S
694 Item.TitleText = FText::FromString(TEXT("등급"));
695 break;
696
697 case 1: // Score
699 Item.ScoreValue = FMath::RandRange(100, 9999);
700 Item.TitleText = FText::FromString(TEXT("점수"));
701 break;
702
703 case 2: // Rate
705 Item.RatePercent = FMath::FRandRange(0.0f, 1.0f);
706 Item.TitleText = FText::FromString(TEXT("비율"));
707 break;
708
709 case 3: // Symbol
711 Item.SymbolValue = FString::SanitizeFloat( FMath::FRandRange(0.0f, 1.0f) );
712 Item.TitleText = FText::FromString(TEXT("심볼"));
713 break;
714 }
715
716 // 랜덤 색상 선택
717 Item.ColorType = static_cast<EColorStyleType>(FMath::RandRange(0, 7)); // Green ~ Gray
718
719 TestItems.Add(Item);
720 }
721
722 // BroadcastManager를 통해 전송
723 BM->SendAddItemToBoxList(TestItems);
724 }
725}
726
727void APlayerControl::ServerRPC_SendChat_Implementation(const FText& inMessage)
728{
729 if (auto* GS = GetWorld()->GetGameState<ALingoGameState>())
730 {
731 // PlayerIndex 찾기
732 int32 PlayerIndex = -1;
733 if (ALingoPlayerState* PS = GetPlayerState<ALingoPlayerState>())
734 {
735 PlayerIndex = GS->PlayerArray.IndexOfByKey(PS);
736 }
737
738 // PRINTLOG(TEXT("[SendChat] APlayerControl::ServerRPC_SendChat: %s"), *inMessage.ToString());
739 GS->MulticastRPC_SendChat(UserInfo, inMessage, PlayerIndex);
740 }
741}
742
743void APlayerControl::ServerRPC_SendAIQuestion_Implementation(const FString& Question)
744{
745 // 서버에서만 실행됩니다.
746 if (!HasAuthority())
747 return;
748
749 // AI에게 질문 전송
750 if (UKLingoNetworkSystem* NetworkSystem = UKLingoNetworkSystem::Get(GetWorld()))
751 {
752 FResponseChatAnswersDelegate Delegate;
753 Delegate.BindUObject(this, &APlayerControl::OnChatAnswerReceived);
754
755 // Context는 비워둡니다 (필요시 채팅 히스토리 전달 가능)
756 NetworkSystem->RequestChatQuestion(TEXT(""), Question, Delegate);
757
758 PRINTLOG(TEXT("[AI Chat] Question sent to AI: %s"), *Question);
759 }
760}
761
762void APlayerControl::OnChatAnswerReceived(FResponseChatAnswers& ResponseData, bool bWasSuccessful)
763{
764 if (!bWasSuccessful || ResponseData.answer.IsEmpty())
765 {
766 PRINTLOG(TEXT("[AI Chat] Failed to receive AI response"));
767 return;
768 }
769
770 // AI 응답을 Bot 정보로 채팅에 표시
771 if (auto* GS = GetWorld()->GetGameState<ALingoGameState>())
772 {
773 FText AIAnswer = FText::FromString(ResponseData.answer);
774 // Bot은 PlayerIndex -1 사용
775 GS->MulticastRPC_SendChat(GS->GetBotInfo(), AIAnswer, DefineData::BotID);
776
777 // Chat History 저장
779 ChatHistorySystem->SaveChatHistory(ResponseData.question, ResponseData.answer);
780 }
781
782 // ai tutor 봇에 메시지 표시
783 if (auto playerActor = Cast<APlayerActor>(GetPawn()))
784 {
785 playerActor->GetMiniOwlBot()->UpdateText(ResponseData.answer);
786 }
787}
788
789void APlayerControl::OnDoorMessage(int32 InDoorIndex, bool bInOpen, AActor* EventInstigator)
790{
791 // 서버에서만 실행
792 if (!HasAuthority())
793 return;
794
795 // 문이 열릴 때만 처리
796 if (!bInOpen)
797 return;
798
799 // PlayerState 가져오기
800 ALingoPlayerState* PS = GetPlayerState<ALingoPlayerState>();
801 if (!PS)
802 return;
803
804 // End DoorIndex에 따라 퀘스트 완료 처리
805 if (InDoorIndex == DoorGroup::Step1_End)
806 {
807 // ReadQuest: 협력 퀘스트, 모든 플레이어 완료
809 }
810 else if (InDoorIndex == DoorGroup::Step2_End)
811 {
812 // ListenQuest: 협력 퀘스트, 모든 플레이어 완료
814 }
815 else if (InDoorIndex == DoorGroup::Step3_End)
816 {
817 // SpeakQuest: 개인 퀘스트, EventInstigator 확인
818 if (EventInstigator != nullptr && EventInstigator != GetPawn())
819 return;
820
822 }
823 else if (InDoorIndex == DoorGroup::Step4_End)
824 {
825 // WriteQuest: 개인 퀘스트, EventInstigator 확인
826 if (EventInstigator != nullptr && EventInstigator != GetPawn())
827 return;
828
830 }
831
832 if (InDoorIndex == DoorGroup::Step1_Tutorial)
833 {
835 }
836 else if (InDoorIndex == DoorGroup::Step2_Tutorial)
837 {
839 }
840 else if (InDoorIndex == DoorGroup::Step3_Tutorial)
841 {
842 // 개인 퀘스트: Instigator가 본인인지 확인 후 해당 클라이언트에게만 알림
843 if (EventInstigator == GetPawn())
844 {
846 }
847 }
848 else if (InDoorIndex == DoorGroup::Step3_Tutorial)
849 {
850 // 개인 퀘스트: Instigator가 본인인지 확인 후 해당 클라이언트에게만 알림
851 if (EventInstigator == GetPawn())
852 {
854 }
855 }
856}
857
858// 모든 클라이언트에서 실행될 로직
859void APlayerControl::Multicast_OpenHowToPlay_Implementation(EQuestType InType)
860{
861 // 로컬 플레이어 컨트롤러인지 확인 (UI는 내 화면에만 띄워야 함)
862 if (IsLocalController())
863 {
864 OpenHowToPlay(InType);
865 }
866}
867
868// 특정 클라이언트에서 실행될 로직
869void APlayerControl::Client_OpenHowToPlay_Implementation(EQuestType InType)
870{
871 // Client RPC는 해당 컨트롤러가 소유한 클라이언트에서만 실행됨
872 OpenHowToPlay(InType);
873}
874
876{
877 // PlayerActor 가져오기
878 APlayerActor* PlayerActor = Cast<APlayerActor>(GetPawn());
879 if (!PlayerActor)
880 return;
881
882 // MainWidget 가져오기
883 UMainWidget* MainWidget = PlayerActor->GetMainWidget();
884 if (!MainWidget)
885 return;
886
887 // QuestInfoWidget 가져오기
888 UQuestInfoWidget* QuestWidget = MainWidget->GetQuestInfoWidget();
889 if (!QuestWidget)
890 return;
891
892 // PlayerState 가져오기
893 ALingoPlayerState* PS = GetPlayerState<ALingoPlayerState>();
894 if (!PS)
895 return;
896
897 // 퀘스트 상태 확인 및 위젯 업데이트
898 FString Title;
899 FString Description;
900 bool bShouldShow = false;
901
902 // ReadQuest 상태 확인
903 if (PS->bReadQuestIng && !PS->bReadQuestCompleted)
904 {
905 Title = TEXT("Mission Goal");
906 Description = TEXT("Place the object on the switch to open the gate");
907 bShouldShow = true;
908 }
909 else if (PS->bReadQuestCompleted && !PS->bListenQuestIng)
910 {
911 Title = TEXT("Move Food Court");
912 Description = TEXT("Move Food Court With Friend");
913 bShouldShow = true;
914 }
915 // ListenQuest 상태 확인
916 else if (PS->bListenQuestIng && !PS->bListenQuestCompleted)
917 {
918 Title = TEXT("Move Next Goal");
919 Description = TEXT("Place the object on the switch to open the gate");
920 bShouldShow = true;
921 }
922 else if (PS->bListenQuestCompleted && !PS->bSpeakQuestIng)
923 {
924 Title = TEXT("Move Jugdes");
925 Description = TEXT("Move Judes And Talk");
926 bShouldShow = true;
927 }
928 // SpeakQuest 상태 확인
929 else if (PS->bSpeakQuestIng && !PS->bSpeakQuestCompleted)
930 {
931 Title = TEXT("Press V Key And Talk");
932 Description = TEXT("Answer Judes Question");
933 bShouldShow = true;
934 }
935 else if (PS->bSpeakQuestCompleted && !PS->bWriteQuestIng)
936 {
937 // Speak 종료 후 바로 Write로 이어지는 경우 처리
938 // 잠시 숨김 처리 (또는 다른 메시지 표시)
939 bShouldShow = false;
940 }
941 // WriteQuest 상태 확인
942 else if (PS->bWriteQuestIng && !PS->bWriteQuestCompleted)
943 {
944 Title = TEXT("Move Paper");
945 Description = TEXT("Find Wirte Kiosk And Interaction");
946 bShouldShow = true;
947 }
948 else if (PS->bWriteQuestCompleted)
949 {
950 Title = TEXT("Move End Point");
951 Description = TEXT("Get Evalution");
952 bShouldShow = true;
953 }
954
955 // 위젯 업데이트
956 if (bShouldShow)
957 {
958 QuestWidget->UpdateQuestText(Title, Description);
959 QuestWidget->SetVisibility(ESlateVisibility::Visible);
960 }
961 else
962 {
963 QuestWidget->SetVisibility(ESlateVisibility::Collapsed);
964 }
965}
966
967void APlayerControl::UpdateQuestOrderWidget(const FString& inQuestOrder)
968{
969 // PlayerActor 가져오기
970 APlayerActor* PlayerActor = Cast<APlayerActor>(GetPawn());
971 if (!PlayerActor)
972 return;
973
974 // MainWidget 가져오기
975 UMainWidget* MainWidget = PlayerActor->GetMainWidget();
976 if (!MainWidget)
977 return;
978
979 // QuestInfoWidget 가져오기
980 UQuestOrderWidget* QuestOrderWidget = MainWidget->GetQuestOrderWidget();
981 if (!QuestOrderWidget)
982 return;
983
984 // PlayerState 가져오기
985 ALingoPlayerState* PS = GetPlayerState<ALingoPlayerState>();
986 if (!PS)
987 return;
988
989 // 위젯 업데이트
990 QuestOrderWidget->UpdateQuestOrder(inQuestOrder);
991 QuestOrderWidget->SetVisibility(ESlateVisibility::Visible);
992}
EQuestType
네트워크 복제를 위한 전역 브로드캐스트 Actor
Declares the player-controlled character actor.
#define IMC_DEFAULT_PATH
#define IA_HOWTOCTRL_PATH
#define IA_HISTORY_PATH
#define IA_INFO_PATH
#define IA_INTERACT_PATH
#define IA_JUMP_PATH
#define IA_RECORD_PATH
#define IA_LOOK_PATH
#define IA_HOWTOPLAY_PATH
#define IA_RUN_PATH
#define IA_MOVE_PATH
#define IA_GRAB_PATH
#define IA_CHAT_PATH
#define IA_HOOK_PATH
APlayerControl 선언에 대한 Doxygen 주석을 제공합니다.
EQuestRole
Read 퀘스트에서 플레이어의 역할을 정의합니다.
Definition EQuestRole.h:6
EColorStyleType
FComponentHelper 구조체를 선언합니다.
EResourceTextureType
YiSan 전반에서 사용하는 공용 인터페이스를 선언합니다.
#define PRINTLOG(fmt,...)
Definition GameLogging.h:30
UControllable 클래스를 선언합니다.
Chat 대화 기록을 GConfig를 이용하여 관리하는 컴포넌트입니다.
GConfig 래퍼 설정 관리 시스템
UDialogManager 클래스를 선언합니다.
그래플링 훅 시스템
플레이어의 상호작용 감지 및 처리 시스템
KLingo API 요청을 담당하는 서브시스템을 선언합니다.
ULoadingCircleManager 클래스를 선언합니다.
Chat History를 표시하는 팝업 위젯입니다.
void SetSpawnData(const FLuggageData &InData)
[Luggage용] 스폰 전에 Dropper가 생성될 액터에게 넘겨줄 데이터 등록
Definition ADropper.h:48
void SetSpawnClass(TSubclassOf< AActor > InClass)
스폰 전에 Dropper가 어떤 클래스를 스폰할지 등록
Definition ADropper.h:45
FORCEINLINE bool IsBusy() const
Definition ADropper.h:54
bool RequestSpawn()
스폰 요청 (서버에서만 동작)
Definition ADropper.cpp:49
bool bSpeakQuestIng
SpeakQuest 진행 여부 플래그
FResponseSpeakResult SpeakResult
void SetSpeakQuestCompleted()
SpeakQuest 완료 처리 (서버에서만 호출)
void SetListenQuestCompleted()
ListenQuest 완료 처리 (서버에서만 호출)
bool bWriteQuestCompleted
WriteQuest 완료 여부 플래그
bool bSpeakQuestCompleted
SpeakQuest 완료 여부 플래그
void SetSpeakQuestIng(bool bInProgress)
SpeakQuest 진행 상태 설정 (서버에서만 호출)
bool bWriteQuestIng
WriteQuest 진행 여부 플래그
void SetReadQuestCompleted()
ReadQuest 완료 처리 (서버에서만 호출)
bool bReadQuestIng
ReadQuest 진행 여부 플래그
void SetWriteQuestCompleted()
WriteQuest 완료 처리 (서버에서만 호출)
bool bListenQuestIng
ListenQuest 진행 여부 플래그
bool bListenQuestCompleted
ListenQuest 완료 여부 플래그
bool bReadQuestCompleted
ReadQuest 완료 여부 플래그
static ANetworkBroadcastActor * Get(const UObject *WorldContextObject)
싱글톤 인스턴스 가져오기
void SendDoorMessage(int InDoorIndex, bool bOpen, AActor *EventInstigator)
문 상태 변경 메시지를 네트워크로 전송
Main character driven directly by the player.
TObjectPtr< class UHookSystem > HookSystem
TObjectPtr< class UInteractionSystem > InteractionSystem
class UMainWidget * GetMainWidget() const
메인 위젯을 가져옵니다.
void UpdateSpeakWidget(int32 StepIndex)
class UTutorialComponent * TutorialComponent
void OnResponseSpeakResult(FResponseSpeakResult &ResponseData, bool bWasSuccessful)
void OnRecordReleased(const FInputActionValue &Value)
void OnJump(const FInputActionValue &Value)
int32 GetUserId() const
void UpdateQuestOrderWidget(const FString &inQuestOrder)
TObjectPtr< class UChatHistorySystem > ChatHistorySystem
Chat History 관리 컴포넌트
void UpdateQuestRole(EQuestRole QuestRole)
void OnInteract(const FInputActionValue &Value)
virtual void BeginPlay() override
void Client_ToastMessage(const FString &Message)
void OnGrabRelease(const FInputActionValue &Value)
FResponseUserMe UserInfo
사용자 정보 (레벨 전환에서도 유지됨)
void OnMove(const FInputActionValue &Value)
void OnLook(const FInputActionValue &Value)
void Server_OnHook()
TObjectPtr< class UInputMappingContext > IMC_Default
void OnChat(const FInputActionValue &Value)
void OnChatAnswerReceived(FResponseChatAnswers &ResponseData, bool bWasSuccessful)
AI 응답을 받았을 때 Bot 정보로 채팅에 표시합니다.
void OnRun(const FInputActionValue &Value)
void RequestDrop(APlayerControl *Requester)
void OnInfo(const FInputActionValue &Value)
void Server_RequestDrop()
void Server_OnGrab()
void OpenHowToPlay(EQuestType QuestType)
void UpdateQuestInfoWidget()
퀘스트 상태에 따라 QuestInfoWidget 업데이트
void OnGrab(const FInputActionValue &Value)
void OnHistory(const FInputActionValue &Value)
void TEST_DropperDropProcess()
virtual void SetupInputComponent() override
void OnHook(const FInputActionValue &Value)
void OnHowToCtrl(const FInputActionValue &Value)
void OnRecordPressed(const FInputActionValue &Value)
bool ShouldSkipTutorial() const
void Client_OpenHowToPlay(EQuestType InType)
void Server_SetUserInfo(const FResponseUserMe &InUserInfo)
void Server_OnInteract()
void OnHowToPlay(const FInputActionValue &Value)
void Server_OnGrabRelease()
class IControllable * GetControllable() const
void Multicast_OpenHowToPlay(EQuestType InType)
void Server_SyncSpeakScenarioData(class AWheatly *Wheatly, const struct FResponseSpeakScenario &Data)
Client에서 받은 시나리오 데이터를 Wheatly에 전달 (Server RPC)
void OnStopMove(const FInputActionValue &Value)
void OnDoorMessage(int32 InDoorIndex, bool bInOpen, AActor *EventInstigator)
DoorMessage 이벤트 핸들러
Wheatly NPC 액터
Definition AWheatly.h:31
static bool GetUserBool(int32 UserId, const FString &Key, bool bDefaultValue=false)
유저별 불린 설정 읽기
static void SetUserBool(int32 UserId, const FString &Key, bool bValue, bool bAutoSave=true)
유저별 불린 설정 저장
KLingo 서버와의 HTTP 요청을 중재하는 게임 인스턴스 서브시스템입니다.
static int32 GetUserId(const UObject *WorldContextObject)
static int32 GetStageTypeIndex(const EQuestType QuestType)
전역 로딩 서클의 표시 여부를 관리하는 LocalPlayerSubsystem입니다.
메인 UI 위젯
Definition UMainWidget.h:17
void SetFocusOnChat()
class UQuestOrderWidget * GetQuestOrderWidget() const
Definition UMainWidget.h:62
class UQuestInfoWidget * GetQuestInfoWidget() const
Definition UMainWidget.h:59
void UpdateQuestText(FString InTitle, FString InDescription)
void UpdateQuestOrder(const FString &inQuestOrder)
static const int32 BotID
Definition Onepiece.h:56
static const int32 Step3_End
Definition Onepiece.h:33
static const int32 Step1_Tutorial
Definition Onepiece.h:20
static const int32 Step2_Tutorial
Definition Onepiece.h:28
static const int32 Step1_End
Definition Onepiece.h:25
static const int32 Step4_End
Definition Onepiece.h:37
static const int32 Step2_End
Definition Onepiece.h:29
static const int32 Step3_Tutorial
Definition Onepiece.h:32
static T * LoadAsset(const TCHAR *Path)
FWordInfo word1
시나리오 단어 정보
Definition ADropper.h:18
FWordInfo word2
Definition ADropper.h:21
Chat Answers 응답 구조체입니다.
Speak 시나리오 응답 구조체입니다.
Result Stat 위젯 통합 데이터 구조 위젯 타입, 색상 스타일, 각 타입별 데이터를 통합 관리
float RatePercent
Rate 타입 전용: 퍼센트 값 (0.0 ~ 1.0)
EColorStyleType ColorType
색상 스타일
FText TitleText
타이틀 텍스트
EResultItemWidgetType WidgetType
위젯 타입
EResourceTextureType GradeTextureType
Grade 타입 전용: 텍스처 타입
float ScoreValue
Score 타입 전용: 점수 값
static FWordInfo GetRandomAnimal()
랜덤 동물 선택 - CSV 기반 하드코딩
static FWordInfo GetRandomColor()
랜덤 색상 선택 - CSV 기반 하드코딩