📜 ⬆️ ⬇️

MMO from scratch. Part 2. Building functionality + Diamond Square algorithm

Hello! In the previous part we dealt with the basic architecture, network and messaging. Increase the functionality now. We make it possible to log in by registering with a session id, which can be used in the future to manage the client during the game. Next, we add a chat, in fact, everything works according to its principle: received a message - sent to the signatories. Let's make it possible to create game rooms, where we will collect players and send them into battle. Synchronize the movement of customers and finally check the shot on the test server. There will be a lot of code, I continue the step by step description so that you can quickly figure it out and reproduce it for your needs. For those who are not familiar with the first part, but want to bring something useful for themselves here and now, I added the implementation of the Diamond Square fractal landscape generation algorithm to the beginning. Happy coding!

Part 1. The overall picture, the assembly of libraries, preparing the client and server for messaging
Part 2. Building game functionality + Diamond Square algorithm



Diamond Square and Unreal Engine Algorithm


Diamond square gives one of the most realistic results. The landscapes resulting from it, as a rule, are called fractal. Different implementations of the algorithm are used in programs such as terragen.
')


The algorithm can be described in five steps:
  1. Initialization of corner points. Assigning heights to them by selecting random numbers.
  2. Finding the middle point, assigning it values, based on the average of the angular, plus a random number.
  3. Finding the middle points for diamonds marked with black dots (in this step, one point of each diamond goes beyond the array).
  4. For each square (there are 4 in this step), repeat step # 2.
  5. Repeat step number 3 for each diamond. In rhombuses that have points on the edge of an array, one of the points goes outside the bounds of the array.



You can find the implementation here:

github.com/VadimDev/Unreal-Engine-diamond-square-algorithm

A few words about how I drew it in the engine. One of the options was to create everything using DrawDebugLine, however, this does not work in a packed game, and besides, a timer with its lifetime is started for each line, which creates an additional load. To draw lines or create Mesh at runtime, you need to create your UPrimitiveComponent, then in it a class derived from FPrimitiveSceneProxy, in which we override the GetDynamicMeshElements function (or DrawDynamicElements, if called and drawn once). This function has access to FPrimitiveDrawInterface, which allows you to draw various primitives. Override FPrimitiveSceneProxy * CreateSceneProxy () in UPrimitiveComponent and return from there an instance of the nested class derived from FPrimitiveSceneProxy. To use a class, you need to create the BP of the actor and assign the component to it.

Algorithm "diamond-square" to build fractal landscapes

For the cause! Registration and Login


In the previous part we dealt with the network and messaging. If there are difficulties or something does not converge, then look at the source of the project that you can get here:

github.com/VadimDev/Spiky-Project

Let's start with the implementation of the interface and functionality, registration form and input. To begin, create their view in the visual UMG editor.

Create a folder Blueprints, in it Widgets. As a basis, I always take HorizontalBox / VerticalBox, in which there can be a ScaleBox with different parameters. As shown, this is the best option for auto-scaling for different screens. The interface has a lot of attachments and in itself is quite complicated. For tests, it is useful to have a temporary widget with a Canvas root, you can add a created widget to it and stretch it while watching for scaling.



We will not create widgets step by step. You need to take them and resources to them from source codes and place in Widgets and ProjectResources.

Now to the logic, we need to bind the interface to the code, we create a class inherited from UserWidget for each widget.

Extend UserWidget for UMG Widgets
docs.unrealengine.com/latest/INT/Programming/Slate
docs.unrealengine.com/latest/INT/Programming/Tutorials/UMG

Open Spiky_Server.Build.cs and add new modules necessary for working with UI:

PrivateDependencyModuleNames.AddRange(new string[] { "UMG", "Slate", "SlateCore" });

Create a UI folder and place the captions and the stub implementations there:

Login, login, server address setting widget and idle screen widget
LoginWidgets
LoginWidgets.h
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "Runtime/UMG/Public/Blueprint/UserWidget.h" #include <string> #include "LoginWidgets.generated.h" class UButton; class UTextBlock; class UEditableTextBox; UCLASS() class SPIKY_CLIENT_API ULoginWidgets : public UUserWidget { GENERATED_BODY() virtual void NativeConstruct() override; bool bMailOk = false; bool bPassOk = false; public: UButton* wSingUpButton = nullptr; UTextBlock* wInfoBlock = nullptr; UEditableTextBox* wMailTextBox = nullptr; UEditableTextBox* wPasswordTextBox = nullptr; UButton* wLoginButton = nullptr; UButton* wSettingsButton = nullptr; UFUNCTION() void SettingsButtonClicked(); UFUNCTION() void SingUpButtonClicked(); UFUNCTION() void LoginButtonClicked(); UFUNCTION() void OnMailTextChanged(const FText & text); UFUNCTION() void OnPasswordTextChanged(const FText & text); FTimerHandle MessageTimerHandle; void HideErrorMessage(); void ShowErrorMessage(FString msg); static std::string mail; static std::string password; }; 


LoginWidgets.cpp
 // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "LoginWidgets.h" std::string ULoginWidgets::mail = ""; std::string ULoginWidgets::password = ""; void ULoginWidgets::NativeConstruct() { Super::NativeConstruct(); } void ULoginWidgets::LoginButtonClicked() { } void ULoginWidgets::SettingsButtonClicked() { } void ULoginWidgets::SingUpButtonClicked() { } void ULoginWidgets::HideErrorMessage() { } void ULoginWidgets::ShowErrorMessage(FString msg) { } void ULoginWidgets::OnMailTextChanged(const FText & text) { } void ULoginWidgets::OnPasswordTextChanged(const FText & text) { } 



RegWidgets
RegWidgets.h
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "Runtime/UMG/Public/Blueprint/UserWidget.h" #include "RegWidgets.generated.h" class UButton; class UImage; class UEditableTextBox; class UTextBlock; class UTexture2D; UCLASS() class SPIKY_CLIENT_API URegWidgets : public UUserWidget { GENERATED_BODY() URegWidgets(const FObjectInitializer& ObjectInitializer); virtual void NativeConstruct() override; public: UButton* wReloadCaptchaButton = nullptr; UImage* wCaptchaImage = nullptr; UImage* wLoginImage = nullptr; UImage* wPassImage = nullptr; UImage* wMailImage = nullptr; UImage* wCaptchaCheckImage = nullptr; UTexture2D* accept_tex = nullptr; UTexture2D* denied_tex = nullptr; UTexture2D* empty_tex = nullptr; UEditableTextBox* wLoginTextBox = nullptr; UEditableTextBox* wPasswordTextBox = nullptr; UEditableTextBox* wMainTextBox = nullptr; UEditableTextBox* wCaptchaTextBox = nullptr; UTextBlock* wInfoBlock = nullptr; UButton* wShowTermsPrivacyButton = nullptr; UButton* wCloseButton = nullptr; UButton* wSingUpButton = nullptr; UFUNCTION() void SingUpButtonClicked(); UFUNCTION() void CloseButtonClicked(); UFUNCTION() void ShowTermPrivacyClicked(); UFUNCTION() void ReloadCaptchaClicked(); UFUNCTION() void OnLoginTextChanged(const FText & text); UFUNCTION() void OnPasswordTextChanged(const FText & text); UFUNCTION() void OnMailTextChanged(const FText & text); UFUNCTION() void OnCaptchaTextChanged(const FText & text); bool bLoginOk = false; bool bPassOk = false; bool bMailOk = false; bool bCaptchaOk = false; }; 


RegWidgets.cpp
 // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "RegWidgets.h" URegWidgets::URegWidgets(const FObjectInitializer & ObjectInitializer) : Super(ObjectInitializer) { } void URegWidgets::NativeConstruct() { Super::NativeConstruct(); } void URegWidgets::CloseButtonClicked() { } void URegWidgets::ShowTermPrivacyClicked() { } void URegWidgets::ReloadCaptchaClicked() { } void URegWidgets::OnLoginTextChanged(const FText & text) { } void URegWidgets::OnPasswordTextChanged(const FText & text) { } void URegWidgets::OnMailTextChanged(const FText & text) { } void URegWidgets::OnCaptchaTextChanged(const FText & text) { } void URegWidgets::SingUpButtonClicked() { } 



SetServerWidgets
SetServerWidgets.h
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "Runtime/UMG/Public/Blueprint/UserWidget.h" #include "SetServerWidgets.generated.h" class UEditableTextBox; UCLASS() class SPIKY_CLIENT_API USetServerWidgets : public UUserWidget { GENERATED_BODY() virtual void NativeConstruct() override; UEditableTextBox* wAddressBox = nullptr; UEditableTextBox* wPortBox = nullptr; public: void SetAddress(); }; 


SetServerWidgets.cpp
 // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "SetServerWidgets.h" #include "SocketObject.h" #include "Runtime/UMG/Public/Components/EditableTextBox.h" void USetServerWidgets::NativeConstruct() { Super::NativeConstruct(); wAddressBox = Cast<UEditableTextBox>(GetWidgetFromName(TEXT("AddressBox"))); wPortBox = Cast<UEditableTextBox>(GetWidgetFromName(TEXT("PortBox"))); // default value uint32 OutIP; USocketObject::tcp_address->GetIp(OutIP); //  ip   FString ip = FString::Printf(TEXT("%d.%d.%d.%d"), 0xff & (OutIP >> 24), 0xff & (OutIP >> 16), 0xff & (OutIP >> 8), 0xff & OutIP); wAddressBox->SetText(FText::FromString(ip)); wPortBox->SetText(FText::FromString(FString::FromInt(USocketObject::tcp_address->GetPort()))); } void USetServerWidgets::SetAddress() { uint32 OutIP; USocketObject::tcp_address->GetIp(OutIP); //  ip   FString oldIP = FString::Printf(TEXT("%d.%d.%d.%d"), 0xff & (OutIP >> 24), 0xff & (OutIP >> 16), 0xff & (OutIP >> 8), 0xff & OutIP); FString oldPort = FString::FromInt(USocketObject::tcp_address->GetPort()); //     FIPv4Address serverIP; FIPv4Address::Parse(wAddressBox->GetText().ToString(), serverIP); int32 serverPort = FCString::Atoi(*(wPortBox->GetText().ToString())); FString newIP = serverIP.ToString(); FString newPort = FString::FromInt(serverPort); GLog->Log(newIP + " " + newPort); //       if (!oldIP.Equals(*newIP, ESearchCase::IgnoreCase) || !oldPort.Equals(*newPort, ESearchCase::IgnoreCase)) { GLog->Log("Address change"); USocketObject::tcp_address->SetIp(serverIP.Value); USocketObject::tcp_address->SetPort(FCString::Atoi(*(wPortBox->GetText().ToString()))); USocketObject::Reconnect(); } } 



SSButtonWidgets
SSButtonWidgets.h
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "Runtime/UMG/Public/Blueprint/UserWidget.h" #include "SSButtonWidgets.generated.h" class UButton; UCLASS() class SPIKY_CLIENT_API USSButtonWidgets : public UUserWidget { GENERATED_BODY() virtual void NativeConstruct() override; UButton* wSettingsButton = nullptr; UFUNCTION() void SettingsButtonClicked(); }; 


SSButtonWidgets.cpp
 // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "SSButtonWidgets.h" #include "Runtime/UMG/Public/Components/Button.h" void USSButtonWidgets::NativeConstruct() { Super::NativeConstruct(); wSettingsButton = Cast<UButton>(GetWidgetFromName(TEXT("SettingsButton"))); wSettingsButton->OnClicked.AddDynamic(this, &USSButtonWidgets::SettingsButtonClicked); } void USSButtonWidgets::SettingsButtonClicked() { } 



WSWidgets
WSWidgets.h
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "Spiky_Client.h" #include "Runtime/UMG/Public/Blueprint/UserWidget.h" #include "Runtime/UMG/Public/Components/Image.h" #include "WSWidgets.generated.h" UCLASS() class SPIKY_CLIENT_API UWSWidgets : public UUserWidget { GENERATED_BODY() virtual void NativeConstruct() override; public: FTimerHandle MessageTimerHandle; bool once = true; UImage * wGear1 = nullptr; UImage * wGear2 = nullptr; FWidgetTransform transform1; FWidgetTransform transform2; void GearsAnim(); }; 


WSWidgets.cpp
 // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "WSWidgets.h" #include "Runtime/Engine/Public/TimerManager.h" void UWSWidgets::NativeConstruct() { Super::NativeConstruct(); if (once) { once = false; GetWorld()->GetTimerManager().SetTimer(MessageTimerHandle, this, &UWSWidgets::GearsAnim, 0.01f, true); } wGear1 = Cast<UImage>(GetWidgetFromName(TEXT("Gear1"))); wGear2 = Cast<UImage>(GetWidgetFromName(TEXT("Gear2"))); } void UWSWidgets::GearsAnim() { transform1.Angle += 1; wGear1->SetRenderTransform(transform1); transform2.Angle -= 1; wGear2->SetRenderTransform(transform2); } 




In the UMG editor, each widget can have a name through which it will be accessible from the code. In the constructor, we find the widget by this name and initialize:

 USetServerWidgets::NativeConstruct() wAddressBox = Cast<UEditableTextBox>(GetWidgetFromName(TEXT("AddressBox"))); wPortBox = Cast<UEditableTextBox>(GetWidgetFromName(TEXT("PortBox"))); 

In the SetServerWidgets widget we get a static address, return it to a normal view. And fill them with the wAddressBox and wPortBox fields:

 USetServerWidgets::SetAddress()       ,                      USocketObject::Reconnect() 

The SSButtonWidgets widget is the only function to show and hide SetServerWidgets always on top of everything else.

To place widgets in layers, we need to create a WidgetsContainer with a single UCanvasPanel element:

Widgetcontainer
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "Runtime/UMG/Public/Blueprint/UserWidget.h" #include "WidgetsContainer.generated.h" class UCanvasPanel; UCLASS() class SPIKY_CLIENT_API UWidgetsContainer : public UUserWidget { GENERATED_BODY() virtual void NativeConstruct() override; public: UCanvasPanel * wCanvas = nullptr; }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "WidgetsContainer.h" #include "Runtime/UMG/Public/Components/CanvasPanel.h" #include "CanvasPanelSlot.h" void UWidgetsContainer::NativeConstruct() { Super::NativeConstruct(); wCanvas = Cast<UCanvasPanel>(GetWidgetFromName(TEXT("Canvas"))); } 


Now open the Unreal Editor, open the WidgetContainer widget that has the default kenvas, assign it the name Canvas so that we can find it in the code (if not already assigned), assign the new parents to the created widgets, go from Designer to Graph, select Edit Class Settings and change the Parent Class to the corresponding name of the C ++ class.

Let's start placing widgets, for this we use the previously created DifferentMix. Add forward-looking ads, a constructor, a set of temporary links to the received instances, the GetWorld () function, through the DifferentMix instance we can also get a link to the current world, changing from GameMode to GameMode, the widgets themselves that are created based on the links, and their slots on Canvas

DifferentMix
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "CoreMinimal.h" #include "UObject/NoExportTypes.h" #include "DifferentMix.generated.h" class UWidgetsContainer; class UCanvasPanelSlot; class URegWidgets; class ULoginWidgets; class USSButtonWidgets; class USetServerWidgets; class UUserWidget; class UWSWidgets; /** * World singleton, stores references to widgets and rare functions */ UCLASS() class SPIKY_CLIENT_API UDifferentMix : public UObject { GENERATED_BODY() UDifferentMix(const FObjectInitializer& ObjectInitializer); UWidgetsContainer* tmpWidgetContainerRef; URegWidgets* tmpRegistrationRef; ULoginWidgets* tmpLoginScreenRef; USSButtonWidgets* tmpServerSettingsButtonRef; USetServerWidgets* tmpServerSettingsRef; UUserWidget* tmpTermsPrivacyRef; UWSWidgets* tmpWaitingScreenRef; public: virtual class UWorld* GetWorld() const override; void Init(); UWidgetsContainer* wWidgetContainer; URegWidgets* wRegistration; ULoginWidgets* wLoginScreen; USSButtonWidgets* wServerSettingsButton; USetServerWidgets* wServerSettings; UUserWidget* wTermsPrivacy; UWSWidgets* wWaitingScreen; UCanvasPanelSlot* registrationSlot; UCanvasPanelSlot* loginScreenSlot; UCanvasPanelSlot* serverSettingsButtonsSlot; UCanvasPanelSlot* serverSettingsSlot; UCanvasPanelSlot* TermsPrivacySlot; UCanvasPanelSlot* waitingScreenSlot; }; 


To create each widget we need access to the current game world, we will initialize DifferentMix to GameMode and save the reference to the world in GameInstance. Add to SpikyGameInstance:

 // .h static UWorld* world; void DifferentMixInit(UWorld* the_world); static UDifferentMix * DifferentMix; 

Create a DifferentMix object and add it to root, this will prevent its being destroyed by the garbage collector, we will call Init to create a set of widgets for us:

 // .cpp UWorld* UClientGameInstance::world = nullptr; UDifferentMix * UClientGameInstance::DifferentMix = nullptr; void USpikyGameInstance::DifferentMixInit(UWorld* the_world) { GLog->Log("DifferentMixInit"); world = the_world; DifferentMix = NewObject<UDifferentMix>(UDifferentMix::StaticClass()); DifferentMix->AddToRoot(); DifferentMix->Init(); } 

Now we need a valid reference to the world, we can’t get it in SpikyGameInstance as it is an object independent of the current world, but the GameMode is perfect, we’ll add DifferentMix initialization to ASpikyGameMode :: BeginPlay ():

 USpikyGameInstance* gameInstance = Cast<USpikyGameInstance>(GetWorld()->GetGameInstance()); gameInstance->DifferentMixInit(GetWorld()); 

Create widgets in UDifferentMix :: Init () and place them on the canvas slot:

 wWidgetContainer = CreateWidget<UWidgetsContainer>(GetWorld(), tmpWidgetContainerRef->GetClass()); wWidgetContainer->AddToViewport(); wRegistration = CreateWidget<URegWidgets>(GetWorld(), tmpRegistrationRef->GetClass()); registrationSlot = Cast<UCanvasPanelSlot(wWidgetContainer->wCanvas->AddChild(wRegistration)); registrationSlot->SetZOrder(0); registrationSlot->SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f)); registrationSlot->SetOffsets(FMargin(0, 0, 0, 0)); wRegistration->SetVisibility(ESlateVisibility::Hidden); 

We create widgets from links, add them to the canvas, stretch them, set the depth of SetZOrder, on top of which it should be and set the initial visibility.

Any new widget in the project is added like this:

  1. UMG interface and CPP parent are created;
  2. In DifferentMix, a preliminary declaration is declared: class URegWidgets;
  3. Link to widget URegWidgets * tmpRegistrationRef;
  4. The WRegWidgets widget itself * wRegistration;
  5. And the Canvas slot: UCanvasPanelSlot * registrationSlot;
  6. After in the constructor, initialize the link
     static ConstructorHelpers::FClassFinder<URegWidgets> RegistrationWidgets(TEXT("WidgetBlueprint'/Game/Blueprints/Widgets/Reg_W.Reg_W_C'")); if (RegistrationWidgets.Class != NULL) { tmpRegistrationRef = RegistrationWidgets.Class->GetDefaultObject<URegWidgets>(); } 

  7. Then in UDifferentMix :: Init () we create the widget and place it in the slot:
     wRegistration = CreateWidget<URegWidgets>(GetWorld(), tmpRegistrationRef->GetClass()); registrationSlot = Cast<UCanvasPanelSlot(wWidgetContainer->wCanvas->AddChild(wRegistration)); registrationSlot->SetZOrder(0); registrationSlot->SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f)); registrationSlot->SetOffsets(FMargin(0, 0, 0, 0)); wRegistration->SetVisibility(ESlateVisibility::Hidden); 

When you start the game, we need to show the LoginScreen, for this we will add two new call features in DifferentMix:

 void HideAllWidgets(); void ShowLoginScreen(); void ShowMouse(); 

Current state of DifferentMix
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "CoreMinimal.h" #include "UObject/NoExportTypes.h" #include "DifferentMix.generated.h" class UWidgetsContainer; class UCanvasPanelSlot; class URegWidgets; class ULoginWidgets; class USSButtonWidgets; class USetServerWidgets; class UUserWidget; class UWSWidgets; /** * World singleton, stores references to widgets and rare functions */ UCLASS() class SPIKY_CLIENT_API UDifferentMix : public UObject { GENERATED_BODY() UDifferentMix(const FObjectInitializer& ObjectInitializer); UWidgetsContainer* tmpWidgetContainerRef; URegWidgets* tmpRegistrationRef; ULoginWidgets* tmpLoginScreenRef; USSButtonWidgets* tmpServerSettingsButtonRef; USetServerWidgets* tmpServerSettingsRef; UUserWidget* tmpTermsPrivacyRef; UWSWidgets* tmpWaitingScreenRef; public: virtual class UWorld* GetWorld() const override; void Init(); UWidgetsContainer* wWidgetContainer; URegWidgets* wRegistration; ULoginWidgets* wLoginScreen; USSButtonWidgets* wServerSettingsButton; USetServerWidgets* wServerSettings; UUserWidget* wTermsPrivacy; UWSWidgets* wWaitingScreen; UCanvasPanelSlot* registrationSlot; UCanvasPanelSlot* loginScreenSlot; UCanvasPanelSlot* serverSettingsButtonsSlot; UCanvasPanelSlot* serverSettingsSlot; UCanvasPanelSlot* TermsPrivacySlot; UCanvasPanelSlot* waitingScreenSlot; void HideAllWidgets(); void ShowLoginScreen(); void ShowMouse(); }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "DifferentMix.h" #include "SpikyGameInstance.h" #include "WidgetsContainer.h" #include "RegWidgets.h" #include "LoginWidgets.h" #include "SSButtonWidgets.h" #include "SetServerWidgets.h" #include "WSWidgets.h" #include "Runtime/UMG/Public/Components/CanvasPanel.h" #include "CanvasPanelSlot.h" #include "Runtime/CoreUObject/Public/UObject/ConstructorHelpers.h" UDifferentMix::UDifferentMix(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { static ConstructorHelpers::FClassFinder<UWidgetsContainer> WidgetContainer(TEXT("WidgetBlueprint'/Game/Blueprints/Widgets/WidgetContainer.WidgetContainer_C'")); if (WidgetContainer.Class != NULL) { tmpWidgetContainerRef = WidgetContainer.Class->GetDefaultObject<UWidgetsContainer>(); } static ConstructorHelpers::FClassFinder<URegWidgets> RegistrationWidgets(TEXT("WidgetBlueprint'/Game/Blueprints/Widgets/Reg_W.Reg_W_C'")); if (RegistrationWidgets.Class != NULL) { tmpRegistrationRef = RegistrationWidgets.Class->GetDefaultObject<URegWidgets>(); } static ConstructorHelpers::FClassFinder<ULoginWidgets> LoginWidgets(TEXT("WidgetBlueprint'/Game/Blueprints/Widgets/Login_W.Login_W_C'")); if (LoginWidgets.Class != NULL) { tmpLoginScreenRef = LoginWidgets.Class->GetDefaultObject<ULoginWidgets>(); } static ConstructorHelpers::FClassFinder<USSButtonWidgets> SetServerButtonWidgets(TEXT("WidgetBlueprint'/Game/Blueprints/Widgets/SSButton_W.SSButton_W_C'")); if (SetServerButtonWidgets.Class != NULL) { tmpServerSettingsButtonRef = SetServerButtonWidgets.Class->GetDefaultObject<USSButtonWidgets>(); } static ConstructorHelpers::FClassFinder<USetServerWidgets> ServerSettingsWidgets(TEXT("WidgetBlueprint'/Game/Blueprints/Widgets/SetServer_W.SetServer_W_C'")); if (ServerSettingsWidgets.Class != NULL) { tmpServerSettingsRef = ServerSettingsWidgets.Class->GetDefaultObject<USetServerWidgets>(); } static ConstructorHelpers::FClassFinder<UUserWidget> TermsPrivacyWidgets(TEXT("WidgetBlueprint'/Game/Blueprints/Widgets/Terms_Privacy_W.Terms_Privacy_W_C'")); if (TermsPrivacyWidgets.Class != NULL) { tmpTermsPrivacyRef = TermsPrivacyWidgets.Class->GetDefaultObject<UUserWidget>(); } static ConstructorHelpers::FClassFinder<UWSWidgets> WaitingScreenWidgets(TEXT("WidgetBlueprint'/Game/Blueprints/Widgets/WS_W.WS_W_C'")); if (WaitingScreenWidgets.Class != NULL) { tmpWaitingScreenRef = WaitingScreenWidgets.Class->GetDefaultObject<UWSWidgets>(); } } class UWorld* UDifferentMix::GetWorld() const { return USpikyGameInstance::world; } void UDifferentMix::Init() { wWidgetContainer = CreateWidget<UWidgetsContainer>(GetWorld(), tmpWidgetContainerRef->GetClass()); wWidgetContainer->AddToViewport(); wRegistration = CreateWidget<URegWidgets>(GetWorld(), tmpRegistrationRef->GetClass()); registrationSlot = Cast<UCanvasPanelSlot>(wWidgetContainer->wCanvas->AddChild(wRegistration)); registrationSlot->SetZOrder(0); registrationSlot->SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f)); registrationSlot->SetOffsets(FMargin(0, 0, 0, 0)); wRegistration->SetVisibility(ESlateVisibility::Hidden); wLoginScreen = CreateWidget<ULoginWidgets>(GetWorld(), tmpLoginScreenRef->GetClass()); loginScreenSlot = Cast<UCanvasPanelSlot>(wWidgetContainer->wCanvas->AddChild(wLoginScreen)); loginScreenSlot->SetZOrder(0); loginScreenSlot->SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f)); loginScreenSlot->SetOffsets(FMargin(0, 0, 0, 0)); wLoginScreen->SetVisibility(ESlateVisibility::Hidden); wServerSettingsButton = CreateWidget<USSButtonWidgets>(GetWorld(), tmpServerSettingsButtonRef->GetClass()); serverSettingsButtonsSlot = Cast<UCanvasPanelSlot>(wWidgetContainer->wCanvas->AddChild(wServerSettingsButton)); serverSettingsButtonsSlot->SetZOrder(3); serverSettingsButtonsSlot->SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f)); serverSettingsButtonsSlot->SetOffsets(FMargin(0, 0, 0, 0)); wServerSettingsButton->SetVisibility(ESlateVisibility::Hidden); wServerSettings = CreateWidget<USetServerWidgets>(GetWorld(), tmpServerSettingsRef->GetClass()); serverSettingsSlot = Cast<UCanvasPanelSlot>(wWidgetContainer->wCanvas->AddChild(wServerSettings)); serverSettingsSlot->SetZOrder(1); serverSettingsSlot->SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f)); serverSettingsSlot->SetOffsets(FMargin(0, 0, 0, 0)); wServerSettings->SetVisibility(ESlateVisibility::Hidden); wTermsPrivacy = CreateWidget<UUserWidget>(GetWorld(), tmpTermsPrivacyRef->GetClass()); TermsPrivacySlot = Cast<UCanvasPanelSlot>(wWidgetContainer->wCanvas->AddChild(wTermsPrivacy)); TermsPrivacySlot->SetZOrder(1); TermsPrivacySlot->SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f)); TermsPrivacySlot->SetOffsets(FMargin(0, 0, 0, 0)); wTermsPrivacy->SetVisibility(ESlateVisibility::Hidden); wWaitingScreen = CreateWidget<UWSWidgets>(GetWorld(), tmpWaitingScreenRef->GetClass()); waitingScreenSlot = Cast<UCanvasPanelSlot>(wWidgetContainer->wCanvas->AddChild(wWaitingScreen)); waitingScreenSlot->SetZOrder(1000); // max waitingScreenSlot->SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f)); waitingScreenSlot->SetOffsets(FMargin(0, 0, 0, 0)); wWaitingScreen->SetVisibility(ESlateVisibility::Hidden); } void UDifferentMix::HideAllWidgets() { for (size_t i = 0; i < wWidgetContainer->wCanvas->GetChildrenCount(); i++) { wWidgetContainer->wCanvas->GetChildAt(i)->SetVisibility(ESlateVisibility::Hidden); } } void UDifferentMix::ShowLoginScreen() { HideAllWidgets(); wLoginScreen->SetVisibility(ESlateVisibility::Visible); wServerSettingsButton->SetVisibility(ESlateVisibility::SelfHitTestInvisible); } void UDifferentMix::ShowMouse() { // show mouse APlayerController* MyController = GetWorld()->GetFirstPlayerController(); MyController->bShowMouseCursor = true; MyController->bEnableClickEvents = true; MyController->bEnableMouseOverEvents = true; } 


Add their call to SpikyGameMode:

 USpikyGameInstance::DifferentMix->ShowLoginScreen(); USpikyGameInstance::DifferentMix->ShowMouse(); 

Current SpikyGameMode Status
 // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "SpikyGameMode.h" #include "SocketObject.h" #include "Runtime/Engine/Classes/Engine/World.h" #include "Protobufs/UtilityModels.pb.h" #include "SpikyGameInstance.h" #include "DifferentMix.h" void ASpikyGameMode::BeginPlay() { Super::BeginPlay(); GLog->Log("AClientGameMode::BeginPlay()"); USpikyGameInstance* gameInstance = Cast<USpikyGameInstance>(GetWorld()->GetGameInstance()); gameInstance->DifferentMixInit(GetWorld()); EnableInput(GetWorld()->GetFirstPlayerController()); //InputComponent->BindAction("Q", IE_Pressed, this, &ASpikyGameMode::TestSendUPDMessage); USpikyGameInstance::DifferentMix->ShowLoginScreen(); USpikyGameInstance::DifferentMix->ShowMouse(); } void ASpikyGameMode::EndPlay(const EEndPlayReason::Type EndPlayReason) { Super::EndPlay(EndPlayReason); GLog->Log("AClientGameMode::EndPlay()"); } void ASpikyGameMode::TestSendUPDMessage() { GLog->Log("send ->>>"); std::shared_ptr<Utility> utility(new Utility); utility->set_alive(true); USocketObject::SendByUDP(utility.get()); } 


Compile and check what happened. When you start the game, the login screen should appear.

Login and Login Screens




Add a response to pressing the server address setting button:

SSButtonWidgets
 // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "SSButtonWidgets.h" #include "Runtime/UMG/Public/Components/Button.h" #include "SpikyGameInstance.h" #include "DifferentMix.h" #include "SetServerWidgets.h" void USSButtonWidgets::NativeConstruct() { Super::NativeConstruct(); wSettingsButton = Cast<UButton>(GetWidgetFromName(TEXT("SettingsButton"))); wSettingsButton->OnClicked.AddDynamic(this, &USSButtonWidgets::SettingsButtonClicked); } void USSButtonWidgets::SettingsButtonClicked() { if (USpikyGameInstance::DifferentMix->wServerSettings->GetVisibility() == ESlateVisibility::Hidden) { USpikyGameInstance::DifferentMix->wServerSettings->SetVisibility(ESlateVisibility::Visible); } else { USpikyGameInstance::DifferentMix->wServerSettings->SetAddress(); USpikyGameInstance::DifferentMix->wServerSettings->SetVisibility(ESlateVisibility::Hidden); } } 


Let's start implementing the registration, for this we need the OpenSSL capabilities. This part is very important, since we will encrypt all the data in the game in the same way. At the stage of entry, registration, we get encryption keys. It works like this: we start typing, the form data is checked for the validity of the characters and then immediately sent to the server to check availability, the server looks for such a login in the database, and returns a validity or error code. When a form is opened, the server sends a captcha, and it saves its value in the map <time, value> all captcha older than 60 seconds are deleted. Entering captcha is checked with typing. Checks are performed by a special InputChecking handler. If all the fields are filled in correctly, then we send the login, meil, captcha to the server in unencrypted form. On the server, we check the presence of required fields, then all the data again, only after that we generate the public key and send it to the client. In our project, I use the Diffie-Hellman algorithm for key exchange. Encryption is performed using the AES-128 algorithm. The Diffie-Hellman algorithm allows two parties to obtain a shared secret key using an unprotected communication channel. The principle of operation can be found here:

Diffie - Hellman Algorithm

But if in a nutshell:

Bob chooses two public numbers (p, g) - for example 75, 2
Alice and Bob choose two secret numbers (a, b) - for example 3, 15

Alice - g ^ a mod p -> 2 ^ 3 mod 75 = 8 (A)
Bob - g ^ b mod p -> 2 ^ 15 mod 75 = 68 (B)

A and B are local secret keys.

Calculates the secret secret key:

Alice - B ^ a mod p, 68 ^ 3 mod 75 = 32 pk
Bob - A ^ b mod p, 8 ^ 15 mod 75 = 32 pk

AES - , .

- AES . , , . :

wiki.openssl.org/index.php/Diffie_Hellman
wiki.openssl.org/index.php/EVP_Symmetric_Encryption_and_Decryption

Crypto Utils. Diffie-Hellman, AES, SHA256 / Base64:

Cryptography
Crypto.h
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #pragma warning(disable:4996) #include "Runtime/CoreUObject/Public/UObject/Object.h" #include <openssl/bn.h> #include <string> #include <google/protobuf/message.h> #include "Crypto.generated.h" struct keys { char * p; char * g; char * pubKey; char * privKey; }; UCLASS() class SPIKY_CLIENT_API UCrypto : public UObject { GENERATED_BODY() public: // DiffieHellman static DH *get_dh(int size); // 512 or 1024 static keys Generate_KeysSet_DH(); static DH * client; static std::string Generate_SecretKey_DH(std::string str); // Base64 static size_t CalcDecodeLength(const char* b64input); static size_t Base64Decode(char* b64message, unsigned char** buffer, size_t* length); static std::string Base64Encode(char *decoded_bytes, size_t decoded_length); // Sha256 static std::string SHA256(const void *data, size_t data_len); // AES_ecb_128 static int AES_ECB_Encrypt(unsigned char *source, int source_len, unsigned char *key, unsigned char *cipher); static int AES_ECB_Decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key, unsigned char *plaintext); static std::string Encrypt(std::string source, std::string key); static std::string Decrypt(std::string cipher, std::string key); static std::string EncryptProto(google::protobuf::Message * message, std::string key); private: static void handleErrors(void); }; 


Crypto.cpp
 // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "Crypto.h" #define _CRT_SECURE_NO_WARNINGS #pragma warning(disable:4267) // Base64, AES #include <string> #include <assert.h> #include <openssl/bio.h> #include <openssl/evp.h> #include <openssl/buffer.h> #include <openssl/err.h> // Sha256 #include <sstream> #include <iomanip> // DH #include <openssl/crypto.h> #include <openssl/dh.h> #include <memory> #include "Config.h" using namespace std; DH * UCrypto::get_dh(int size) { static unsigned char dh512_p[] = { 0xDA, 0x58, 0x3C, 0x16, 0xD9, 0x85, 0x22, 0x89, 0xD0, 0xE4, 0xAF, 0x75, 0x6F, 0x4C, 0xCA, 0x92, 0xDD, 0x4B, 0xE5, 0x33, 0xB8, 0x04, 0xFB, 0x0F, 0xED, 0x94, 0xEF, 0x9C, 0x8A, 0x44, 0x03, 0xED, 0x57, 0x46, 0x50, 0xD3, 0x69, 0x99, 0xDB, 0x29, 0xD7, 0x76, 0x27, 0x6B, 0xA2, 0xD3, 0xD4, 0x12, 0xE2, 0x18, 0xF4, 0xDD, 0x1E, 0x08, 0x4C, 0xF6, 0xD8, 0x00, 0x3E, 0x7C, 0x47, 0x74, 0xE8, 0x33 }; static unsigned char dh1024_p[] = { 0xF4, 0x88, 0xFD, 0x58, 0x4E, 0x49, 0xDB, 0xCD, 0x20, 0xB4, 0x9D, 0xE4, 0x91, 0x07, 0x36, 0x6B, 0x33, 0x6C, 0x38, 0x0D, 0x45, 0x1D, 0x0F, 0x7C, 0x88, 0xB3, 0x1C, 0x7C, 0x5B, 0x2D, 0x8E, 0xF6, 0xF3, 0xC9, 0x23, 0xC0, 0x43, 0xF0, 0xA5, 0x5B, 0x18, 0x8D, 0x8E, 0xBB, 0x55, 0x8C, 0xB8, 0x5D, 0x38, 0xD3, 0x34, 0xFD, 0x7C, 0x17, 0x57, 0x43, 0xA3, 0x1D, 0x18, 0x6C, 0xDE, 0x33, 0x21, 0x2C, 0xB5, 0x2A, 0xFF, 0x3C, 0xE1, 0xB1, 0x29, 0x40, 0x18, 0x11, 0x8D, 0x7C, 0x84, 0xA7, 0x0A, 0x72, 0xD6, 0x86, 0xC4, 0x03, 0x19, 0xC8, 0x07, 0x29, 0x7A, 0xCA, 0x95, 0x0C, 0xD9, 0x96, 0x9F, 0xAB, 0xD0, 0x0A, 0x50, 0x9B, 0x02, 0x46, 0xD3, 0x08, 0x3D, 0x66, 0xA4, 0x5D, 0x41, 0x9F, 0x9C, 0x7C, 0xBD, 0x89, 0x4B, 0x22, 0x19, 0x26, 0xBA, 0xAB, 0xA2, 0x5E, 0xC3, 0x55, 0xE9, 0x2F, 0x78, 0xC7 }; static unsigned char dh_g[] = { 0x02, }; DH *dh; if (size == 512) { if ((dh = DH_new()) == NULL) return(NULL); dh->p = BN_bin2bn(dh512_p, sizeof(dh512_p), NULL); dh->g = BN_bin2bn(dh_g, sizeof(dh_g), NULL); } else { if ((dh = DH_new()) == NULL) return(NULL); dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL); dh->g = BN_bin2bn(dh_g, sizeof(dh_g), NULL); } if ((dh->p == NULL) || (dh->g == NULL)) { DH_free(dh); return(NULL); } return(dh); } //char * UOpenSSLCrypto::private_key_dh = ""; DH * UCrypto::client = get_dh(512); // DH_new(); // <- use pregenegate P/G or generate manualy (cpu heavy task) keys UCrypto::Generate_KeysSet_DH() { //DH_generate_parameters_ex(client, 512, DH_GENERATOR_2, NULL); // generate P/G manualy // if you generate P/G manualy you also must send P/G to server DH_generate_key(client); keys keys_set; keys_set.p = BN_bn2dec(client->p); keys_set.g = BN_bn2dec(client->g); keys_set.pubKey = BN_bn2dec(client->pub_key); keys_set.privKey = BN_bn2dec(client->priv_key); return keys_set; } string UCrypto::Generate_SecretKey_DH(string str) { BIGNUM *pub_bob_key = BN_new(); BN_dec2bn(&pub_bob_key, str.c_str()); unsigned char * dh_secret = (unsigned char*)OPENSSL_malloc(sizeof(unsigned char) * (DH_size(client))); DH_compute_key(dh_secret, pub_bob_key, client); return Base64Encode((char*)dh_secret, sizeof(unsigned char) * (DH_size(client))); } size_t UCrypto::CalcDecodeLength(const char* b64input) { //Calculates the length of a decoded string size_t len = strlen(b64input), padding = 0; if (b64input[len - 1] == '=' && b64input[len - 2] == '=') //last two chars are = padding = 2; else if (b64input[len - 1] == '=') //last char is = padding = 1; return (len * 3) / 4 - padding; } size_t UCrypto::Base64Decode(char* b64message, unsigned char** buffer, size_t* length) { //Decodes a base64 encoded string BIO *bio, *b64; int decodeLen = CalcDecodeLength(b64message); *buffer = (unsigned char*)malloc(decodeLen + 1); (*buffer)[decodeLen] = '\0'; bio = BIO_new_mem_buf(b64message, -1); b64 = BIO_new(BIO_f_base64()); bio = BIO_push(b64, bio); BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); //Do not use newlines to flush buffer *length = BIO_read(bio, *buffer, strlen(b64message)); assert(*length == decodeLen); //length should equal decodeLen, else something went horribly wrong BIO_free_all(bio); return (0); //success } string UCrypto::Base64Encode(char *decoded_bytes, size_t decoded_length) { int x; BIO *bioMem, *b64; BUF_MEM *bufPtr; b64 = BIO_new(BIO_f_base64()); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); bioMem = BIO_new(BIO_s_mem()); b64 = BIO_push(b64, bioMem); BIO_write(b64, decoded_bytes, decoded_length); x = BIO_flush(b64); if (x < 1) { BIO_free_all(b64); return NULL; } BIO_get_mem_ptr(b64, &bufPtr); string buff(bufPtr->data, bufPtr->length); BIO_free_all(b64); return buff; } /* // USAGE EXAMPLE //Encode To Base64 char* base64EncodeOutput, *text = "Hello World"; char* inbase = OpenSSL_Base64::Base64Encode(text, strlen((char*)text)); cout << inbase << endl; //Decode From Base64 unsigned char* base64DecodeOutput; size_t test; OpenSSL_Base64::Base64Decode(inbase, &base64DecodeOutput, &test); cout << base64DecodeOutput << endl; */ string UCrypto::SHA256(const void * data, size_t data_len) { EVP_MD_CTX mdctx; unsigned char md_value[EVP_MAX_MD_SIZE]; unsigned int md_len; EVP_DigestInit(&mdctx, EVP_sha256()); EVP_DigestUpdate(&mdctx, data, (size_t)data_len); EVP_DigestFinal_ex(&mdctx, md_value, &md_len); EVP_MD_CTX_cleanup(&mdctx); std::stringstream s; s.fill('0'); for (size_t i = 0; i < md_len; ++i) s << std::setw(2) << std::hex << (unsigned short)md_value[i]; return s.str(); } int UCrypto::AES_ECB_Encrypt(unsigned char * plaintext, int plaintext_len, unsigned char * key, unsigned char * ciphertext) { EVP_CIPHER_CTX *ctx; int len; int ciphertext_len; /* Create and initialise the context */ ctx = EVP_CIPHER_CTX_new(); if (!ctx) handleErrors(); /* Initialise the encryption operation. IMPORTANT - ensure you use a key size appropriate for your cipher * In this we are using 128 bit AES (ie a 128 bit key). */ if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, NULL)) handleErrors(); /* Provide the message to be encrypted, and obtain the encrypted output. * EVP_EncryptUpdate can be called multiple times if necessary */ if (1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)) handleErrors(); ciphertext_len = len; /* Finalise the encryption. Further ciphertext bytes may be written at this stage. */ if (1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) handleErrors(); ciphertext_len += len; /* Clean up */ EVP_CIPHER_CTX_free(ctx); return ciphertext_len; } int UCrypto::AES_ECB_Decrypt(unsigned char * ciphertext, int ciphertext_len, unsigned char * key, unsigned char * plaintext) { EVP_CIPHER_CTX *ctx; int len; int plaintext_len; /* Create and initialise the context */ ctx = EVP_CIPHER_CTX_new(); if (!ctx) handleErrors(); if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, NULL)) handleErrors(); /* Provide the message to be decrypted, and obtain the plaintext output. * EVP_DecryptUpdate can be called multiple times if necessary */ if (1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) handleErrors(); plaintext_len = len; /* Finalise the decryption. Further plaintext bytes may be written at this stage. */ if (1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) handleErrors(); plaintext_len += len; /* Clean up */ EVP_CIPHER_CTX_free(ctx); return plaintext_len; } std::string UCrypto::Encrypt(string source, string key) { if (Config::bEnableCrypt) { string tmpkey = key.substr(0, 16); unsigned char * key_c = (unsigned char*)strcpy((char*)malloc(tmpkey.length() + 1), tmpkey.c_str()); auto cipher = make_unique<unsigned char[]>(source.length() * 2 + 8); unsigned char * source_c = (unsigned char*)source.c_str(); size_t cipherLen = AES_ECB_Encrypt(source_c, strlen((char*)source_c), key_c, cipher.get()); string cipher_str((char*)cipher.get(), cipherLen); free(key_c); return cipher_str; } else { return source; } } std::string UCrypto::Decrypt(std::string cipher, std::string key) { if (Config::bEnableCrypt) { string tmpkey = key.substr(0, 16); unsigned char * key_c = (unsigned char*)strcpy((char*)malloc(tmpkey.length() + 1), tmpkey.c_str()); auto source = make_unique<unsigned char[]>(cipher.length() * 2); unsigned char * cipher_c = (unsigned char*)cipher.c_str(); size_t decryptLen = AES_ECB_Decrypt(cipher_c, cipher.length(), key_c, source.get()); string decrypt_str((char*)source.get(), decryptLen); free(key_c); return decrypt_str; } else { return cipher; } } std::string UCrypto::EncryptProto(google::protobuf::Message * message, std::string key) { int size = message->ByteSize(); auto proto_arr = make_unique<unsigned char[]>(size); message->SerializeToArray(proto_arr.get(), size); if (Config::bEnableCrypt) { string tmpkey = key.substr(0, 16); unsigned char * key_c = (unsigned char*)strcpy((char*)malloc(tmpkey.length() + 1), tmpkey.c_str()); auto cipher = make_unique<unsigned char[]>(size * 2 + 8); unsigned char * source_c = (unsigned char*)proto_arr.get(); size_t cipherLen = AES_ECB_Encrypt(source_c, size, key_c, cipher.get()); string cipher_str((char*)cipher.get(), cipherLen); free(key_c); return cipher_str; } else { string cipher_str((char*)proto_arr.get(), size); return cipher_str; } } void UCrypto::handleErrors(void) { ERR_print_errors_fp(stderr); abort(); } 



Crypto?

DiffieHellman

static DH *get_dh(int size);

p g 512 1024, p/g, (cpu heavy task), .

static keys Generate_KeysSet_DH();

: p,g, private key, public key.

static DH * client;

DH.

static std::string Generate_SecretKey_DH(std::string str);

, Base64.

Base64

static size_t CalcDecodeLength(const char* b64input);

.

static size_t Base64Decode(char* b64message, unsigned char** buffer, size_t* length);
static std::string Base64Encode(char *decoded_bytes, size_t decoded_length);


/ Base64.

Sha256

static std::string SHA256(const void *data, size_t data_len);

.

AES_ecb_128

static int AES_ECB_Encrypt(unsigned char *source, int source_len, unsigned char *key, unsigned char *cipher);
static int AES_ECB_Decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key, unsigned char *plaintext);
static std::string Encrypt(std::string source, std::string key);
static std::string Decrypt(std::string cipher, std::string key);
static std::string EncryptProto(google::protobuf::Message * message, std::string key);


, , 16 .

static void handleErrors(void);

.

if (Config::bEnableCrypt) .

, .
Java Cryptography Architecture , : Appendix D: Sample Programs Diffie-Hellman Key Exchange between 2 Parties.

, , javax.crypto.*; java.security.*;

com.spiky.server.utils Cryptography:

Cryptography.java
 /* * Copyright (c) 2017, Vadim Petrov - MIT License */ package com.spiky.server.utils; import com.spiky.server.ServerMain; import javax.crypto.*; import javax.crypto.interfaces.DHPrivateKey; import javax.crypto.interfaces.DHPublicKey; import javax.crypto.spec.DHParameterSpec; import javax.crypto.spec.DHPublicKeySpec; import javax.crypto.spec.SecretKeySpec; import java.math.BigInteger; import java.security.*; import java.security.spec.InvalidKeySpecException; import java.util.Base64; public class Cryptography { private String secretKey; private String clientPublicKey; private String clientPrivateKey; private KeyAgreement clientKeyAgree; public String getSecretKey() { return secretKey; } public void setSecretKey(String secretKey) { this.secretKey = secretKey; } public String getClientPublicKey() { return clientPublicKey; } public void DiffieHellman_createKeys() { try { DHParameterSpec dhSkipParamSpec = new DHParameterSpec(P, G); // Alice creates her own DH key pair, using the DH parameters from above KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH"); aliceKpairGen.initialize(dhSkipParamSpec); KeyPair aliceKpair = aliceKpairGen.generateKeyPair(); DHPublicKey dhPub = (DHPublicKey)aliceKpair.getPublic(); clientPublicKey = String.valueOf(dhPub.getY()); DHPrivateKey dhPr = (DHPrivateKey)aliceKpair.getPrivate(); clientPrivateKey = String.valueOf(dhPr.getX()); // Alice creates and initializes her DH KeyAgreement object clientKeyAgree = KeyAgreement.getInstance("DH"); clientKeyAgree.init(aliceKpair.getPrivate()); } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | InvalidKeyException e) { e.printStackTrace(); } } public String DiffieHellman_createSecretKey(String bobPublicKey) { try { DHPublicKeySpec dhPubKeySpecs = new DHPublicKeySpec(new BigInteger(bobPublicKey), P, G); KeyFactory kf = KeyFactory.getInstance("DH"); DHPublicKey bobPubKey = (DHPublicKey) kf.generatePublic(dhPubKeySpecs); clientKeyAgree.doPhase(bobPubKey, true); byte[] aliceSecret = clientKeyAgree.generateSecret(); byte[] encodedBytes = Base64.getEncoder().encode(aliceSecret); String source_key = new String(encodedBytes); return source_key.substring(0, 16); } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException e) { e.printStackTrace(); } return null; } public byte[] Crypt(byte[] source, String key) { if(ServerMain.bEnableCrypto) { try { Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(), "AES"); cipher.init(Cipher.ENCRYPT_MODE, skeySpec); return cipher.doFinal(source); } catch (InvalidKeyException | NoSuchPaddingException | NoSuchAlgorithmException | IllegalBlockSizeException | BadPaddingException e) { e.printStackTrace(); } } else { return source; } return null; } public byte[] Decrypt(byte[] cryptogram, String key) { //System.out.println("ServerMain.bEnableCrypto: " + ServerMain.bEnableCrypto); if(ServerMain.bEnableCrypto) { try { Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(), "AES"); cipher.init(Cipher.DECRYPT_MODE, skeySpec); return cipher.doFinal(cryptogram); } catch (IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) { e.printStackTrace(); } } else { return cryptogram; } return null; } // The 1024 bit Diffie-Hellman modulus values used by SKIP private static final byte dh1024_p[] = { (byte)0xF4, (byte)0x88, (byte)0xFD, (byte)0x58, (byte)0x4E, (byte)0x49, (byte)0xDB, (byte)0xCD, (byte)0x20, (byte)0xB4, (byte)0x9D, (byte)0xE4, (byte)0x91, (byte)0x07, (byte)0x36, (byte)0x6B, (byte)0x33, (byte)0x6C, (byte)0x38, (byte)0x0D, (byte)0x45, (byte)0x1D, (byte)0x0F, (byte)0x7C, (byte)0x88, (byte)0xB3, (byte)0x1C, (byte)0x7C, (byte)0x5B, (byte)0x2D, (byte)0x8E, (byte)0xF6, (byte)0xF3, (byte)0xC9, (byte)0x23, (byte)0xC0, (byte)0x43, (byte)0xF0, (byte)0xA5, (byte)0x5B, (byte)0x18, (byte)0x8D, (byte)0x8E, (byte)0xBB, (byte)0x55, (byte)0x8C, (byte)0xB8, (byte)0x5D, (byte)0x38, (byte)0xD3, (byte)0x34, (byte)0xFD, (byte)0x7C, (byte)0x17, (byte)0x57, (byte)0x43, (byte)0xA3, (byte)0x1D, (byte)0x18, (byte)0x6C, (byte)0xDE, (byte)0x33, (byte)0x21, (byte)0x2C, (byte)0xB5, (byte)0x2A, (byte)0xFF, (byte)0x3C, (byte)0xE1, (byte)0xB1, (byte)0x29, (byte)0x40, (byte)0x18, (byte)0x11, (byte)0x8D, (byte)0x7C, (byte)0x84, (byte)0xA7, (byte)0x0A, (byte)0x72, (byte)0xD6, (byte)0x86, (byte)0xC4, (byte)0x03, (byte)0x19, (byte)0xC8, (byte)0x07, (byte)0x29, (byte)0x7A, (byte)0xCA, (byte)0x95, (byte)0x0C, (byte)0xD9, (byte)0x96, (byte)0x9F, (byte)0xAB, (byte)0xD0, (byte)0x0A, (byte)0x50, (byte)0x9B, (byte)0x02, (byte)0x46, (byte)0xD3, (byte)0x08, (byte)0x3D, (byte)0x66, (byte)0xA4, (byte)0x5D, (byte)0x41, (byte)0x9F, (byte)0x9C, (byte)0x7C, (byte)0xBD, (byte)0x89, (byte)0x4B, (byte)0x22, (byte)0x19, (byte)0x26, (byte)0xBA, (byte)0xAB, (byte)0xA2, (byte)0x5E, (byte)0xC3, (byte)0x55, (byte)0xE9, (byte)0x2F, (byte)0x78, (byte)0xC7 }; private static final byte dh512_p[] = { (byte)0xDA, (byte)0x58, (byte)0x3C, (byte)0x16, (byte)0xD9, (byte)0x85, (byte)0x22, (byte)0x89, (byte)0xD0, (byte)0xE4, (byte)0xAF, (byte)0x75, (byte)0x6F, (byte)0x4C, (byte)0xCA, (byte)0x92, (byte)0xDD, (byte)0x4B, (byte)0xE5, (byte)0x33, (byte)0xB8, (byte)0x04, (byte)0xFB, (byte)0x0F, (byte)0xED, (byte)0x94, (byte)0xEF, (byte)0x9C, (byte)0x8A, (byte)0x44, (byte)0x03, (byte)0xED, (byte)0x57, (byte)0x46, (byte)0x50, (byte)0xD3, (byte)0x69, (byte)0x99, (byte)0xDB, (byte)0x29, (byte)0xD7, (byte)0x76, (byte)0x27, (byte)0x6B, (byte)0xA2, (byte)0xD3, (byte)0xD4, (byte)0x12, (byte)0xE2, (byte)0x18, (byte)0xF4, (byte)0xDD, (byte)0x1E, (byte)0x08, (byte)0x4C, (byte)0xF6, (byte)0xD8, (byte)0x00, (byte)0x3E, (byte)0x7C, (byte)0x47, (byte)0x74, (byte)0xE8, (byte)0x33 }; private static final BigInteger P = new BigInteger(1, dh512_p); private static final BigInteger G = BigInteger.valueOf(2); } 


, enableCrypt = true ServerMain:

 /*    */ public static final boolean bEnableCrypto = Boolean.parseBoolean(configurationBundle.getString("enableCrypt")); 

In order to encrypt something, just select the cipher, set the operation mode, select the type of padding (if the source data is less than the block length) and get an array of encrypted bytes:

 Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(), "AES"); cipher.init(Cipher.ENCRYPT_MODE, skeySpec); return cipher.doFinal(source); 

Here we also use the pre-generated values ​​of p, g. Encryption on both sides is done!

Let's go back to the registration.

After the start of the login set, I want its length to correspond to at least 3 characters, as well it can contain only English letters, numbers, underscores, and dashes. Create a StringCleaner function in DifferentMix that will accept a string and characters allowed in it:

 void UDifferentMix::StringCleaner(std::string & source, const std::string & availableSymbols) { source.erase(std::remove_if(source.begin(), source.end(), [&availableSymbols](const char ch) { if (availableSymbols.find(ch) != std::string::npos) return false; return true; } ), source.end()); } 

In the NativeConstruct constructor, add links to the widgets and delegate events such as changing the text:

 wLoginTextBox = Cast<UEditableTextBox>(GetWidgetFromName(TEXT("LoginTextBox"))); wLoginTextBox->OnTextChanged.AddDynamic(this, &URegWidgets::OnLoginTextChanged); 

URegWidgets::URegWidgets, .

 static ConstructorHelpers::FObjectFinder<UTexture2D> accept_ref(TEXT("Texture2D'/Game/ProjectResources/Images/accept.accept'")); accept_tex = accept_ref.Object; 

:

 void URegWidgets::OnLoginTextChanged(const FText & text)   std::string  DifferentMix->StringCleaner()     (str.length() < 3) SetBrushFromTexture(denied_tex) wInfoBlock->SetText(FText::FromString("Error : Too short login")); return; UMessageEncoder::Send(inputChecking) 

— . RegLogModels, :


Add a message to URegWidgets :: OnLoginTextChanged and send it over the network:

 std::shared_ptr<InputChecking> inputChecking(new InputChecking); inputChecking->set_mail(TCHAR_TO_UTF8(*text.ToString())); UMessageEncoder::Send(inputChecking.get(), false, true); 

Other fields work in a similar way. After clicking on the SingUp button, we check the validity flags of each of the fields (if a field is filled incorrectly, the flag signals this). And if everything is correct, send the login, meil and captcha:

 URegWidgets::SingUpButtonClicked() if (bLoginOk && bPassOk && bMailOk && bCaptchaOk) URegWidgets::CloseButtonClicked() USpikyGameInstance::DifferentMix->ShowLoginScreen(); 

To display the license, the Terms_Privacy_W widget is used, we will not create a parent for it, there is no logic here. Add the ability to display in URegWidgets:

 void URegWidgets::ShowTermPrivacyClicked() { USpikyGameInstance::DifferentMix->wTermsPrivacy->SetVisibility(ESlateVisibility::Visible); } 

Current RegWidgets Status
 // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "RegWidgets.h" #include "Protobufs/RegLogModels.pb.h" #include "MessageEncoder.h" #include "SpikyGameInstance.h" #include "DifferentMix.h" #include "Runtime/CoreUObject/Public/UObject/ConstructorHelpers.h" #include "Runtime/Engine/Classes/Engine/Texture2D.h" #include "Runtime/UMG/Public/Components/Button.h" #include "Runtime/UMG/Public/Components/Image.h" #include "Runtime/UMG/Public/Components/EditableTextBox.h" #include "Runtime/UMG/Public/Components/TextBlock.h" URegWidgets::URegWidgets(const FObjectInitializer & ObjectInitializer) : Super(ObjectInitializer) { static ConstructorHelpers::FObjectFinder<UTexture2D> accept_ref(TEXT("Texture2D'/Game/ProjectResources/Images/accept.accept'")); accept_tex = accept_ref.Object; static ConstructorHelpers::FObjectFinder<UTexture2D> denied_ref(TEXT("Texture2D'/Game/ProjectResources/Images/denied.denied'")); denied_tex = denied_ref.Object; static ConstructorHelpers::FObjectFinder<UTexture2D> empty_ref(TEXT("Texture2D'/Game/ProjectResources/Images/empty.empty'")); empty_tex = empty_ref.Object; } void URegWidgets::NativeConstruct() { Super::NativeConstruct(); wReloadCaptchaButton = Cast<UButton>(GetWidgetFromName(TEXT("ReloadCaptchaButton"))); wReloadCaptchaButton->OnClicked.AddDynamic(this, &URegWidgets::ReloadCaptchaClicked); wCaptchaImage = Cast<UImage>(GetWidgetFromName(TEXT("CaptchaImage"))); wLoginTextBox = Cast<UEditableTextBox>(GetWidgetFromName(TEXT("LoginTextBox"))); wLoginTextBox->OnTextChanged.AddDynamic(this, &URegWidgets::OnLoginTextChanged); wPasswordTextBox = Cast<UEditableTextBox>(GetWidgetFromName(TEXT("PasswordTextBox"))); wPasswordTextBox->OnTextChanged.AddDynamic(this, &URegWidgets::OnPasswordTextChanged); wMainTextBox = Cast<UEditableTextBox>(GetWidgetFromName(TEXT("MailTextBox"))); wMainTextBox->OnTextChanged.AddDynamic(this, &URegWidgets::OnMailTextChanged); wCaptchaTextBox = Cast<UEditableTextBox>(GetWidgetFromName(TEXT("CaptchaTextBox"))); wCaptchaTextBox->OnTextChanged.AddDynamic(this, &URegWidgets::OnCaptchaTextChanged); wLoginImage = Cast<UImage>(GetWidgetFromName(TEXT("LoginImage"))); wPassImage = Cast<UImage>(GetWidgetFromName(TEXT("PasswordImage"))); wMailImage = Cast<UImage>(GetWidgetFromName(TEXT("MailImage"))); wCaptchaCheckImage = Cast<UImage>(GetWidgetFromName(TEXT("CaptchaCheckImage"))); wInfoBlock = Cast<UTextBlock>(GetWidgetFromName(TEXT("InfoBlock"))); wShowTermsPrivacyButton = Cast<UButton>(GetWidgetFromName(TEXT("TermsPrivacy"))); wShowTermsPrivacyButton->OnClicked.AddDynamic(this, &URegWidgets::ShowTermPrivacyClicked); wCloseButton = Cast<UButton>(GetWidgetFromName(TEXT("CloseButton"))); wCloseButton->OnClicked.AddDynamic(this, &URegWidgets::CloseButtonClicked); wSingUpButton = Cast<UButton>(GetWidgetFromName(TEXT("SingUpButton"))); wSingUpButton->OnClicked.AddDynamic(this, &URegWidgets::SingUpButtonClicked); } void URegWidgets::CloseButtonClicked() { USpikyGameInstance::DifferentMix->ShowLoginScreen(); } void URegWidgets::ShowTermPrivacyClicked() { USpikyGameInstance::DifferentMix->wTermsPrivacy->SetVisibility(ESlateVisibility::Visible); } void URegWidgets::ReloadCaptchaClicked() { std::shared_ptr<InputChecking> inputChecking(new InputChecking); inputChecking->set_getcaptcha(true); UMessageEncoder::Send(inputChecking.get(), false, true); wCaptchaTextBox->SetText(FText::FromString("")); bCaptchaOk = false; } void URegWidgets::OnLoginTextChanged(const FText & text) { std::string str(TCHAR_TO_UTF8(*text.ToString())); USpikyGameInstance::DifferentMix->StringCleaner(str, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"); wLoginTextBox->SetText(FText::FromString(str.c_str())); if (str.length() < 3) { wLoginImage->SetBrushFromTexture(denied_tex); wInfoBlock->SetText(FText::FromString("Error : Too short login")); return; } wInfoBlock->SetText(FText::FromString(" ")); wLoginImage->SetBrushFromTexture(empty_tex); std::shared_ptr<InputChecking> inputChecking(new InputChecking); inputChecking->set_login(TCHAR_TO_UTF8(*text.ToString())); UMessageEncoder::Send(inputChecking.get(), false, true); } void URegWidgets::OnPasswordTextChanged(const FText & text) { std::string str(TCHAR_TO_UTF8(*text.ToString())); USpikyGameInstance::DifferentMix->StringCleaner(str, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); wPasswordTextBox->SetText(FText::FromString(str.c_str())); if (str.length() < 4) { bPassOk = false; wPassImage->SetBrushFromTexture(denied_tex); wInfoBlock->SetText(FText::FromString("Error : Too short password")); return; } wInfoBlock->SetText(FText::FromString(" ")); wPassImage->SetBrushFromTexture(accept_tex); bPassOk = true; } void URegWidgets::OnMailTextChanged(const FText & text) { std::string str(TCHAR_TO_UTF8(*text.ToString())); USpikyGameInstance::DifferentMix->StringCleaner(str, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@._-"); wMainTextBox->SetText(FText::FromString(str.c_str())); if (str.length() < 4) { bMailOk = false; wMailImage->SetBrushFromTexture(denied_tex); wInfoBlock->SetText(FText::FromString("Error : Too short mail")); return; } wInfoBlock->SetText(FText::FromString(" ")); wMailImage->SetBrushFromTexture(empty_tex); std::shared_ptr<InputChecking> inputChecking(new InputChecking); inputChecking->set_mail(TCHAR_TO_UTF8(*text.ToString())); UMessageEncoder::Send(inputChecking.get(), false, true); } void URegWidgets::OnCaptchaTextChanged(const FText & text) { std::string captcha_str(TCHAR_TO_UTF8(*wCaptchaTextBox->GetText().ToString())); USpikyGameInstance::DifferentMix->StringCleaner(captcha_str, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); wCaptchaTextBox->SetText(FText::FromString(captcha_str.c_str())); if (captcha_str.length() < 5) { bCaptchaOk = false; wCaptchaCheckImage->SetBrushFromTexture(denied_tex); wInfoBlock->SetText(FText::FromString("Error : Too short captcha")); return; } wInfoBlock->SetText(FText::FromString(" ")); wCaptchaCheckImage->SetBrushFromTexture(empty_tex); std::shared_ptr<InputChecking> inputChecking(new InputChecking); inputChecking->set_captcha(captcha_str); UMessageEncoder::Send(inputChecking.get(), false, true); } void URegWidgets::SingUpButtonClicked() { if (bLoginOk && bPassOk && bMailOk && bCaptchaOk) { USpikyGameInstance::DifferentMix->RunWaitingScreen(); std::shared_ptr<Registration> registration(new Registration); registration->set_login(TCHAR_TO_UTF8(*wLoginTextBox->GetText().ToString())); registration->set_mail(TCHAR_TO_UTF8(*wMainTextBox->GetText().ToString())); registration->set_captcha(TCHAR_TO_UTF8(*wCaptchaTextBox->GetText().ToString())); UMessageEncoder::Send(registration.get(), false, true); } else { wInfoBlock->SetText(FText::FromString("Error : Enter valid login/pass/mail/captcha")); } } 




The process of generating keys, registration may take some time, it would be nice to display a waiting screen during this process. To do this, we use WSWidgets, which will receive two widgets with the image of hexes, and rotate them using the FWidgetTransform and a timer that calls GearsAnim () with a certain frequency:

 void UWSWidgets::GearsAnim() { transform1.Angle += 1; wGear1->SetRenderTransform(transform1); transform2.Angle -= 1; wGear2->SetRenderTransform(transform2); } 

DifferentMix:

 void RunWaitingScreen(); void StopWaitingScreen(); ... void UDifferentMix::RunWaitingScreen() { wWaitingScreen->SetVisibility(ESlateVisibility::Visible); } void UDifferentMix::StopWaitingScreen() { wWaitingScreen->SetVisibility(ESlateVisibility::Hidden); } 



URegWidgets::SingUpButtonClicked , :

 void URegWidgets::SingUpButtonClicked() if (bLoginOk && bPassOk && bMailOk && bCaptchaOk) USpikyGameInstance::DifferentMix->RunWaitingScreen(); 

LoginWidgets , ULoginWidgets::NativeConstruct(), , :

 void ULoginWidgets::HideErrorMessage() { wInfoBlock->SetText(FText::FromString(" ")); } void ULoginWidgets::ShowErrorMessage(FString msg) { wInfoBlock->SetText(FText::FromString(*msg)); GetWorld()->GetTimerManager().ClearTimer(MessageTimerHandle); GetWorld()->GetTimerManager().SetTimer(MessageTimerHandle, this, &ULoginWidgets::HideErrorMessage, 1.2f, false); } 

ULoginWidgets::SingUpButtonClicked() , , , :

 if (USocketObject::bIsConnection) { USpikyGameInstance::DifferentMix->ShowRegistrationScreen(); USpikyGameInstance::DifferentMix->wRegistration->ReloadCaptchaClicked(); } else { ShowErrorMessage("No connection"); } 

UDifferentMix::ShowRegistrationScreen():

 void UDifferentMix::ShowRegistrationScreen() { HideAllWidgets(); wRegistration->SetVisibility(ESlateVisibility::Visible); } 

:

 ULoginWidgets::LoginButtonClicked() if (bMailOk && bPassOk && USocketObject::bIsConnection) RunWaitingScreen(); //    keys_set = UCrypto::Generate_KeysSet_DH(); //    std::shared_ptr<Login> login_proto(new Login); //    login_proto->set_publickey(keys_set.pubKey); UMessageEncoder::Send(login_proto.get(), false, true); 


, LoginWidgets:

LoginWidgets
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "Runtime/UMG/Public/Blueprint/UserWidget.h" #include <string> #include "LoginWidgets.generated.h" class UButton; class UTextBlock; class UEditableTextBox; UCLASS() class SPIKY_CLIENT_API ULoginWidgets : public UUserWidget { GENERATED_BODY() virtual void NativeConstruct() override; bool bMailOk = false; bool bPassOk = false; public: UButton* wSingUpButton = nullptr; UTextBlock* wInfoBlock = nullptr; UEditableTextBox* wMailTextBox = nullptr; UEditableTextBox* wPasswordTextBox = nullptr; UButton* wLoginButton = nullptr; UButton* wSettingsButton = nullptr; UFUNCTION() void SettingsButtonClicked(); UFUNCTION() void SingUpButtonClicked(); UFUNCTION() void LoginButtonClicked(); UFUNCTION() void OnMailTextChanged(const FText & text); UFUNCTION() void OnPasswordTextChanged(const FText & text); FTimerHandle MessageTimerHandle; void HideErrorMessage(); void ShowErrorMessage(FString msg); static std::string mail; static std::string password; }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "LoginWidgets.h" #include "Runtime/UMG/Public/Components/Button.h" #include "Runtime/UMG/Public/Components/TextBlock.h" #include "Runtime/UMG/Public/Components/EditableTextBox.h" #include "Runtime/Engine/Public/TimerManager.h" #include "SocketObject.h" #include "SpikyGameInstance.h" #include "DifferentMix.h" #include "RegWidgets.h" #include "Crypto.h" #include "Protobufs/RegLogModels.pb.h" #include "SetServerWidgets.h" #include "MessageEncoder.h" std::string ULoginWidgets::mail = ""; std::string ULoginWidgets::password = ""; void ULoginWidgets::NativeConstruct() { Super::NativeConstruct(); wSingUpButton = Cast<UButton>(GetWidgetFromName(TEXT("SingUpButton"))); wSingUpButton->OnClicked.AddDynamic(this, &ULoginWidgets::SingUpButtonClicked); wLoginButton = Cast<UButton>(GetWidgetFromName(TEXT("LoginButton"))); wLoginButton->OnClicked.AddDynamic(this, &ULoginWidgets::LoginButtonClicked); wInfoBlock = Cast<UTextBlock>(GetWidgetFromName(TEXT("InfoBlock"))); wMailTextBox = Cast<UEditableTextBox>(GetWidgetFromName(TEXT("MailBox"))); wMailTextBox->OnTextChanged.AddDynamic(this, &ULoginWidgets::OnMailTextChanged); wPasswordTextBox = Cast<UEditableTextBox>(GetWidgetFromName(TEXT("PasswordBox"))); wPasswordTextBox->OnTextChanged.AddDynamic(this, &ULoginWidgets::OnPasswordTextChanged); } void ULoginWidgets::LoginButtonClicked() { if (bMailOk && bPassOk && USocketObject::bIsConnection) { GLog->Log("ULoginWidgets::LoginButtonClicked()"); USpikyGameInstance::DifferentMix->RunWaitingScreen(); keys keys_set = UCrypto::Generate_KeysSet_DH(); std::shared_ptr<Login> login_proto(new Login); login_proto->set_publickey(keys_set.pubKey); UMessageEncoder::Send(login_proto.get(), false, true); } else { if (!USocketObject::bIsConnection) { ShowErrorMessage("No connection"); } else if (!bMailOk && !bPassOk) { ShowErrorMessage("Incorrect mail and password"); } else if (!bMailOk) { ShowErrorMessage("Incorrect mail"); } else if (!bPassOk) { ShowErrorMessage("Incorrect password"); } } } void ULoginWidgets::SettingsButtonClicked() { USpikyGameInstance::DifferentMix->wServerSettings->SetVisibility(ESlateVisibility::Visible); } void ULoginWidgets::SingUpButtonClicked() { if (USocketObject::bIsConnection) { USpikyGameInstance::DifferentMix->ShowRegistrationScreen(); USpikyGameInstance::DifferentMix->wRegistration->ReloadCaptchaClicked(); } else { ShowErrorMessage("No connection"); } } void ULoginWidgets::HideErrorMessage() { wInfoBlock->SetText(FText::FromString(" ")); } void ULoginWidgets::ShowErrorMessage(FString msg) { wInfoBlock->SetText(FText::FromString(*msg)); GetWorld()->GetTimerManager().ClearTimer(MessageTimerHandle); GetWorld()->GetTimerManager().SetTimer(MessageTimerHandle, this, &ULoginWidgets::HideErrorMessage, 1.2f, false); } void ULoginWidgets::OnMailTextChanged(const FText & text) { std::string str(TCHAR_TO_UTF8(*text.ToString())); USpikyGameInstance::DifferentMix->StringCleaner(str, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@._-"); wMailTextBox->SetText(FText::FromString(str.c_str())); if (str.length() < 4) { bMailOk = false; ShowErrorMessage("Too short mail"); return; } HideErrorMessage(); bMailOk = true; mail = str; } void ULoginWidgets::OnPasswordTextChanged(const FText & text) { std::string str(TCHAR_TO_UTF8(*text.ToString())); USpikyGameInstance::DifferentMix->StringCleaner(str, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); wPasswordTextBox->SetText(FText::FromString(str.c_str())); if (str.length() < 4) { bPassOk = false; ShowErrorMessage("Too short password"); return; } HideErrorMessage(); bPassOk = true; password = str; } 


. Patchca . , . , CryptogramWrapper, Wrapper, :

 message CryptogramWrapper { bytes registration = 1; } message Wrapper { Registration registration = 3; } 

DecryptHandler, , , - , , ( ):

 @Override protected void decode(ChannelHandlerContext ctx, MessageModels.Wrapper wrapper, List<Object> list) throws Exception { init(ctx); /*     */ if(wrapper.hasCryptogramWrapper()) { if(wrapper.getCryptogramWrapper().hasField(registration_cw)) { byte[] cryptogram = wrapper.getCryptogramWrapper().getRegistration().toByteArray(); byte[] original = cryptography.Decrypt(cryptogram, cryptography.getSecretKey()); RegistrationLoginModels.Registration registration = RegistrationLoginModels.Registration.parseFrom(original); } else if (wrapper.getCryptogramWrapper().hasField(login_cw)) { byte[] cryptogram = wrapper.getCryptogramWrapper().getLogin().toByteArray(); byte[] original = cryptography.Decrypt(cryptogram, cryptography.getSecretKey()); RegistrationLoginModels.Login login = RegistrationLoginModels.Login.parseFrom(original); } else if(wrapper.getCryptogramWrapper().hasField(mainMenu_cw)) { byte[] cryptogram = wrapper.getCryptogramWrapper().getMainMenu().toByteArray(); byte[] original = cryptography.Decrypt(cryptogram, cryptography.getSecretKey()); MainMenuModels.MainMenu mainMenu = MainMenuModels.MainMenu.parseFrom(original); } else if(wrapper.getCryptogramWrapper().hasField(room_cw)) { byte[] cryptogram = wrapper.getCryptogramWrapper().getRoom().toByteArray(); byte[] original = cryptography.Decrypt(cryptogram, cryptography.getSecretKey()); GameRoomModels.Room room = GameRoomModels.Room.parseFrom(original); } else if(wrapper.getCryptogramWrapper().hasField(gameModels_cw)) { byte[] cryptogram = wrapper.getCryptogramWrapper().getGameModels().toByteArray(); byte[] original = cryptography.Decrypt(cryptogram, cryptography.getSecretKey()); GameModels.GameData gameData = GameModels.GameData.parseFrom(original); } } else if(wrapper.hasInputChecking()) { } else if(wrapper.hasRegistration()) { } else if(wrapper.hasLogin()) { } } 

. findFieldByName:

 ... public static com.google.protobuf.Descriptors.FieldDescriptor registration_cw = MessageModels.CryptogramWrapper.getDefaultInstance().getDescriptorForType().findFieldByName("registration"); ... if(wrapper.getCryptogramWrapper().hasField(registration_cw)) { } 

Utils Descriptors. , , Logics InputChecking.

MySQL Hibernate. Maven:

 <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>[4.2.6,4.2.9]</version> </dependency> 

Utils SessionUtil, :

 Query query = session.createQuery("SELECT login FROM UserModel WHERE login = :str "); 

SessionUtil
 /* * Copyright (c) 2017, Vadim Petrov - MIT License */ package com.spiky.server.utils; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; import org.hibernate.service.ServiceRegistry; import org.hibernate.service.ServiceRegistryBuilder; public class SessionUtil { private final SessionFactory factory; public SessionUtil() { Configuration configuration = new Configuration(); configuration.configure(); ServiceRegistryBuilder srBuilder = new ServiceRegistryBuilder(); srBuilder.applySettings(configuration.getProperties()); ServiceRegistry serviceRegistry = srBuilder.buildServiceRegistry(); factory = configuration.buildSessionFactory(serviceRegistry); } public Session getSession() { return getInstance().factory.openSession(); } private static SessionUtil getInstance() { return new SessionUtil(); } } 


, , dbmodels UserModel:

UserModel
  /* * Copyright (c) 2017, Vadim Petrov - MIT License */ package com.spiky.server.dbmodels; import javax.persistence.*; @Entity public class UserModel { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column String login; @Column String hash; @Column String mail; public String getLogin() { return login; } public void setLogin(String login) { this.login = login; } public String getHash() { return hash; } public void setHashPass(String hash) { this.hash = hash; } public String getMail() { return mail; } public void setMail(String mail) { this.mail = mail; } @Override public String toString() { return "login: \"" + login + "\"\nmail: \"" + mail + "\"\nhash: \"" + hash + "\""; } } 


hibernate.cfg.xml resources:

hibernate.cfg.xml
 <?xml version="1.0"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- Database connection settings --> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">root</property> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/game_db</property> <!-- SQL dialect --> <property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property> <!-- set up connection pool c3p0 for use --> <property name="c3p0.max_size">10</property> <!-- Echo all executed SQL to stdout --> <property name="show_sql">true</property> <!-- update the database schema on startup --> <property name="hibernate.hbm2ddl.auto">update</property> <mapping class="com.spiky.server.dbmodels.UserModel"></mapping> </session-factory> </hibernate-configuration> 


MySQL Workbench .

InputChecking, . , , , Netty AttributeKey.

netty.io/4.0/api/io/netty/util/AttributeKey.html
stackoverflow.com/questions/29596677/replacement-for-attributekey
stackoverflow.com/questions/25932352/using-channel-attributes-in-different-context-handlers

ServerMain :

 public final static AttributeKey<String> SECRETKEY = AttributeKey.valueOf("secret_key"); public final static AttributeKey<String> CHANNEL_OWNER = AttributeKey.valueOf("channel_owner"); public final static AttributeKey<Session> HIBER_SESSION = AttributeKey.valueOf("hiber_session"); public final static AttributeKey<Transaction> HIBER_TRANSACTION = AttributeKey.valueOf("hiber_transaction"); public final static AttributeKey<Cryptography> CRYPTOGRAPHY = AttributeKey.valueOf("hiber_cryptography"); 

DecryptHandler , Init() , :

 private Session session = new SessionUtil().getSession(); private Transaction transaction = session.beginTransaction(); private Cryptography cryptography = new Cryptography(); /*    */ private boolean bInit = false; /*        */ private void init(ChannelHandlerContext ctx) { if(!bInit) { bInit = true; ctx.channel().attr(HIBER_SESSION).set(session); ctx.channel().attr(HIBER_TRANSACTION).set(transaction); ctx.channel().attr(CRYPTOGRAPHY).set(cryptography); } } 

- , ServerMain :

 /*    */ public static Map<Long,String> captchaBank = Collections.synchronizedMap(new HashMap<>()); 

, :

 /*     60  */ private static void captchaCleaner() { long lifetime = 60000; new Thread(() -> { while (true) { try { Thread.sleep(10000); //      10  synchronized (captchaBank) { captchaBank.entrySet().removeIf(e-> System.currentTimeMillis() - e.getKey() > lifetime); } } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } /*   captchaCleaner() */ public static void main(String[] args) { new Thread(ServerMain::run_tcp).start(); //new Thread(ServerMain::run_udp).start(); captchaCleaner(); } 

Registration , . getCaptcha(). patchca , , :

 RegistrationLoginModels.InputChecking inputChecking = RegistrationLoginModels.InputChecking.newBuilder() .setCaptchaData(ByteString.copyFrom(captchaBytes)) .build(); 

Registration
 /* * Copyright (c) 2017, Vadim Petrov - MIT License */ package com.spiky.server.tcp.logics; import com.google.protobuf.ByteString; import com.spiky.server.protomodels.MessageModels; import com.spiky.server.protomodels.RegistrationLoginModels; import org.patchca.background.SingleColorBackgroundFactory; import org.patchca.color.SingleColorFactory; import org.patchca.filter.predefined.CurvesRippleFilterFactory; import org.patchca.service.ConfigurableCaptchaService; import org.patchca.utils.encoder.EncoderHelper; import java.awt.*; import java.io.ByteArrayOutputStream; import java.io.IOException; import static com.spiky.server.ServerMain.captchaBank; public class Registration { public MessageModels.Wrapper getCaptcha() { try { ConfigurableCaptchaService cs = new ConfigurableCaptchaService(); cs.setBackgroundFactory(new SingleColorBackgroundFactory(new Color(0, 0, 0))); cs.setColorFactory(new SingleColorFactory(new Color(255, 255, 255))); cs.setFilterFactory(new CurvesRippleFilterFactory(cs.getColorFactory())); cs.setHeight(100); cs.setWidth(250); ByteArrayOutputStream bos = new ByteArrayOutputStream(); captchaBank.put(System.currentTimeMillis(), EncoderHelper.getChallangeAndWriteImage(cs, "png", bos)); byte[] captchaBytes = bos.toByteArray(); bos.close(); RegistrationLoginModels.InputChecking inputChecking = RegistrationLoginModels.InputChecking.newBuilder() .setCaptchaData(ByteString.copyFrom(captchaBytes)) .build(); return MessageModels.Wrapper.newBuilder().setInputChecking(inputChecking).build(); } catch (IOException e) { e.printStackTrace(); } return null; } } 


InputChecking, , , check() :

 if(type.equals("login")) Query query = session.createQuery("SELECT login FROM UserModel WHERE login = :str "); List users = query.setParameter("str", data).list(); 

, :

 RegistrationLoginModels.InputChecking inputChecking = RegistrationLoginModels.InputChecking.newBuilder() .setLoginCheckStatus(false) // no valid .build(); 

InputChecking
 /* * Copyright (c) 2017, Vadim Petrov - MIT License */ package com.spiky.server.tcp.logics; import com.spiky.server.protomodels.MessageModels; import com.spiky.server.protomodels.RegistrationLoginModels; import io.netty.channel.ChannelHandlerContext; import org.hibernate.Query; import org.hibernate.Session; import java.util.List; import java.util.Map; import static com.spiky.server.ServerMain.HIBER_SESSION; import static com.spiky.server.ServerMain.captchaBank; import static com.spiky.server.utils.Descriptors.*; public class InputChecking { public InputChecking(ChannelHandlerContext ctx, MessageModels.Wrapper wrapper) { Session session = ctx.channel().attr(HIBER_SESSION).get(); if(wrapper.getInputChecking().hasField(getCaptcha_ich)) { ctx.writeAndFlush(new Registration().getCaptcha()); } else if(wrapper.getInputChecking().hasField(login_ich)) { ctx.writeAndFlush(check(session, wrapper.getInputChecking().getLogin(), "login")); } else if(wrapper.getInputChecking().hasField(mail_ich)) { ctx.writeAndFlush(check(session, wrapper.getInputChecking().getMail(), "mail")); } else if(wrapper.getInputChecking().hasField(captcha_ich)) { ctx.writeAndFlush(check(session, wrapper.getInputChecking().getCaptcha(), "captcha")); } } private MessageModels.Wrapper check(Session session, String data, String type) { if(type.equals("login")) { Query query = session.createQuery("SELECT login FROM UserModel WHERE login = :str "); List users = query.setParameter("str", data).list(); if(!users.isEmpty()) { RegistrationLoginModels.InputChecking inputChecking = RegistrationLoginModels.InputChecking.newBuilder() .setLoginCheckStatus(false) // no valid .build(); return MessageModels.Wrapper.newBuilder().setInputChecking(inputChecking).build(); } else { RegistrationLoginModels.InputChecking inputChecking = RegistrationLoginModels.InputChecking.newBuilder() .setLoginCheckStatus(true) // valid .build(); return MessageModels.Wrapper.newBuilder().setInputChecking(inputChecking).build(); } } else if (type.equals("mail")) { Query query = session.createQuery("SELECT mail FROM UserModel WHERE mail = :str "); List mails = query.setParameter("str", data).list(); if(!mails.isEmpty()) { RegistrationLoginModels.InputChecking inputChecking = RegistrationLoginModels.InputChecking.newBuilder() .setMailCheckStatus(false) .build(); return MessageModels.Wrapper.newBuilder().setInputChecking(inputChecking).build(); } else { RegistrationLoginModels.InputChecking inputChecking = RegistrationLoginModels.InputChecking.newBuilder() .setMailCheckStatus(true) .build(); return MessageModels.Wrapper.newBuilder().setInputChecking(inputChecking).build(); } } else if (type.equals("captcha")) { boolean challengeFind = false; synchronized (captchaBank) { for (Map.Entry<Long, String> entry : captchaBank.entrySet()) if (entry.getValue().equals(data)) challengeFind = true; if (challengeFind) { RegistrationLoginModels.InputChecking inputChecking = RegistrationLoginModels.InputChecking.newBuilder() .setCaptchaCheckStatus(true) .build(); return MessageModels.Wrapper.newBuilder().setInputChecking(inputChecking).build(); } else { RegistrationLoginModels.InputChecking inputChecking = RegistrationLoginModels.InputChecking.newBuilder() .setCaptchaCheckStatus(false) .build(); return MessageModels.Wrapper.newBuilder().setInputChecking(inputChecking).build(); } } } return null; } } 


, . DifferentMix:

UTexture2D* CreateTexture(const std::string raw, bool alpha);
 // .h class UTexture2D; UTexture2D* CreateTexture(const std::string raw, bool alpha); ... // .cpp #include "Runtime/ImageWrapper/Public/Interfaces/IImageWrapperModule.h" #include "Runtime/Core/Public/Modules/ModuleManager.h" #include "Runtime/Engine/Classes/Engine/Texture2D.h" UTexture2D* UDifferentMix::CreateTexture(const std::string raw, bool alpha) { int32 num = raw.length(); const uint8_t * byte_array = reinterpret_cast<const uint8_t*>(raw.c_str()); if (num != 0) { IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName(TEXT("ImageWrapper"))); // Note: PNG format. Other formats are supported IImageWrapperPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG); if (ImageWrapper.IsValid() && ImageWrapper->SetCompressed(byte_array, num)) { const TArray<uint8>* UncompressedBGRA = nullptr; TArray<uint8> UncompressedRGBA; if (ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, UncompressedBGRA)) { // Create the UTexture for rendering UncompressedRGBA.AddZeroed(UncompressedBGRA->Num()); for (int i = 0; UncompressedBGRA->Num() > i; i += 4) { UncompressedRGBA[i] = (*UncompressedBGRA)[i + 2]; UncompressedRGBA[i + 1] = (*UncompressedBGRA)[i + 1]; UncompressedRGBA[i + 2] = (*UncompressedBGRA)[i]; UncompressedRGBA[i + 3] = (*UncompressedBGRA)[i + 3]; if (alpha) { if ((UncompressedRGBA[i] + UncompressedRGBA[i + 1] + UncompressedRGBA[i + 2]) < 3) { UncompressedRGBA[i + 3] = 0; } } } UTexture2D* MyTexture = UTexture2D::CreateTransient(ImageWrapper->GetWidth(), ImageWrapper->GetHeight(), PF_R8G8B8A8); // Fill in the source data from the file uint8* TextureData = (uint8*)MyTexture->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE); FMemory::Memcpy(TextureData, UncompressedRGBA.GetData(), UncompressedRGBA.Num()); MyTexture->PlatformData->Mips[0].BulkData.Unlock(); // Update the rendering resource from data. MyTexture->UpdateResource(); return MyTexture; } } } return nullptr; } 


.Build.cs – ImageWrapper. InputChecking Handlers/Logics:

InputChecking
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "Runtime/CoreUObject/Public/UObject/Object.h" #include "InputChecking.generated.h" class InputChecking; UCLASS() class SPIKY_CLIENT_API UInputChecking : public UObject { GENERATED_BODY() public: void Handler(const InputChecking inputChecking); }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "InputChecking.h" #include "Descriptors.h" #include "Runtime/Engine/Classes/Engine/Texture2D.h" #include "SpikyGameInstance.h" #include "DifferentMix.h" #include "RegWidgets.h" #include "Runtime/UMG/Public/Components/Image.h" #include "Protobufs/RegLogModels.pb.h" void UInputChecking::Handler(const InputChecking inputChecking) { if (inputChecking.GetReflection()->HasField(inputChecking, Descriptors::captchaDataField_ich)) { UTexture2D * tex = USpikyGameInstance::DifferentMix->CreateTexture(inputChecking.captchadata(), true); USpikyGameInstance::DifferentMix->wRegistration->wCaptchaImage->SetBrushFromTexture(tex); } else if (inputChecking.GetReflection()->HasField(inputChecking, Descriptors::loginCheckStatus_ich)) { if (inputChecking.logincheckstatus()) { USpikyGameInstance::DifferentMix->wRegistration->wLoginImage->SetBrushFromTexture(USpikyGameInstance::DifferentMix->wRegistration->accept_tex); USpikyGameInstance::DifferentMix->wRegistration->bLoginOk = true; } else { USpikyGameInstance::DifferentMix->wRegistration->wLoginImage->SetBrushFromTexture(USpikyGameInstance::DifferentMix->wRegistration->denied_tex); } } else if (inputChecking.GetReflection()->HasField(inputChecking, Descriptors::mailCheckStatus_ich)) { if (inputChecking.mailcheckstatus()) { USpikyGameInstance::DifferentMix->wRegistration->wMailImage->SetBrushFromTexture(USpikyGameInstance::DifferentMix->wRegistration->accept_tex); USpikyGameInstance::DifferentMix->wRegistration->bMailOk = true; } else { USpikyGameInstance::DifferentMix->wRegistration->wMailImage->SetBrushFromTexture(USpikyGameInstance::DifferentMix->wRegistration->denied_tex); } } else if (inputChecking.GetReflection()->HasField(inputChecking, Descriptors::captchaCheckStatus_ich)) { if (inputChecking.captchacheckstatus()) { USpikyGameInstance::DifferentMix->wRegistration->wCaptchaCheckImage->SetBrushFromTexture(USpikyGameInstance::DifferentMix->wRegistration->accept_tex); USpikyGameInstance::DifferentMix->wRegistration->bCaptchaOk = true; } else { USpikyGameInstance::DifferentMix->wRegistration->bCaptchaOk = false; USpikyGameInstance::DifferentMix->wRegistration->wCaptchaCheckImage->SetBrushFromTexture(USpikyGameInstance::DifferentMix->wRegistration->denied_tex); } } } 


:

 if (inputChecking.GetReflection()->HasField(inputChecking, Descriptors::captchaDataField_ich)) { //   } 

DecryptHandler InputChecking:

 ... else if(wrapper.hasInputChecking()) { new InputChecking(ctx, wrapper); } ... 

EncryptHandler :

 public class EncryptHandler extends MessageToMessageEncoder<MessageModels.Wrapper> { @Override protected void encode(ChannelHandlerContext ctx, MessageModels.Wrapper wrapper, List<Object> list) throws Exception { /*   ,   */ if(!wrapper.hasCryptogramWrapper()) { ctx.writeAndFlush(wrapper); } //   else if(cryptogramWrapper.hasField(registration_cw)) { byte[] cryptogram = cryptography.Crypt(cryptogramWrapper.getRegistration().toByteArray(), secretKey); cryptogramWrapper = MessageModels.CryptogramWrapper.newBuilder().clear() .setRegistration(ByteString.copyFrom(cryptogram)).build(); ctx.writeAndFlush(MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cryptogramWrapper).build()); } } } 

EncryptHandler, :

EncryptHandler
 /* * Copyright (c) 2017, Vadim Petrov - MIT License */ package com.spiky.server.tcp.handlers; import com.google.protobuf.ByteString; import com.spiky.server.protomodels.MessageModels; import com.spiky.server.utils.Cryptography; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageEncoder; import java.util.List; import static com.spiky.server.ServerMain.CRYPTOGRAPHY; import static com.spiky.server.utils.Descriptors.*; public class EncryptHandler extends MessageToMessageEncoder<MessageModels.Wrapper> { @Override protected void encode(ChannelHandlerContext ctx, MessageModels.Wrapper wrapper, List<Object> list) throws Exception { Cryptography cryptography = ctx.channel().attr(CRYPTOGRAPHY).get(); MessageModels.CryptogramWrapper cryptogramWrapper = wrapper.getCryptogramWrapper(); String secretKey = cryptography.getSecretKey(); /*   ,   */ if(!wrapper.hasCryptogramWrapper()) { ctx.writeAndFlush(wrapper); } else if(cryptogramWrapper.hasField(registration_cw)) { byte[] cryptogram = cryptography.Crypt(cryptogramWrapper.getRegistration().toByteArray(), secretKey); cryptogramWrapper = MessageModels.CryptogramWrapper.newBuilder().clear() .setRegistration(ByteString.copyFrom(cryptogram)).build(); ctx.writeAndFlush(MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cryptogramWrapper).build()); } else if (cryptogramWrapper.hasField(login_cw)) { byte[] cryptogram = cryptography.Crypt(cryptogramWrapper.getLogin().toByteArray(), secretKey); cryptogramWrapper = MessageModels.CryptogramWrapper.newBuilder().clear() .setLogin(ByteString.copyFrom(cryptogram)).build(); ctx.writeAndFlush(MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cryptogramWrapper).build()); } else if (cryptogramWrapper.hasField(initialState_cw)) { byte[] cryptogram = cryptography.Crypt(cryptogramWrapper.getInitialState().toByteArray(), secretKey); cryptogramWrapper = MessageModels.CryptogramWrapper.newBuilder().clear() .setInitialState(ByteString.copyFrom(cryptogram)).build(); ctx.writeAndFlush(MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cryptogramWrapper).build()); } else if(cryptogramWrapper.hasField(mainMenu_cw)) { byte[] cryptogram = cryptography.Crypt(cryptogramWrapper.getMainMenu().toByteArray(), secretKey); cryptogramWrapper = MessageModels.CryptogramWrapper.newBuilder().clear() .setMainMenu(ByteString.copyFrom(cryptogram)).build(); ctx.writeAndFlush(MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cryptogramWrapper).build()); } else if(cryptogramWrapper.hasField(room_cw)) { byte[] cryptogram = cryptography.Crypt(cryptogramWrapper.getRoom().toByteArray(), secretKey); cryptogramWrapper = MessageModels.CryptogramWrapper.newBuilder().clear() .setRoom(ByteString.copyFrom(cryptogram)).build(); ctx.writeAndFlush(MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cryptogramWrapper).build()); } else if(cryptogramWrapper.hasField(gameModels_cw)) { byte[] cryptogram = cryptography.Crypt(cryptogramWrapper.getGameModels().toByteArray(), secretKey); cryptogramWrapper = MessageModels.CryptogramWrapper.newBuilder().clear() .setGameModels(ByteString.copyFrom(cryptogram)).build(); ctx.writeAndFlush(MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cryptogramWrapper).build()); } } } 


ServerInitializer :

 pipeline.addLast(new EncryptHandler()); 

MessageEncoder :

MessageEncoder
 // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "MessageEncoder.h" #include "SocketObject.h" #include "Protobufs/MessageModels.pb.h" #include <google/protobuf/io/zero_copy_stream_impl_lite.h> #include <google/protobuf/io/coded_stream.h> #include "Crypto.h" #include "SpikyGameInstance.h" #include "Protobufs/MainMenuModels.pb.h" #include "Protobufs/GameRoomModels.pb.h" #include "Protobufs/GameModels.pb.h " bool UMessageEncoder::Send(google::protobuf::Message * message, bool bCrypt, bool bTCP) { Wrapper wrapper; std::shared_ptr<CryptogramWrapper> cw(new CryptogramWrapper); //     if (bCrypt) { if (message->GetTypeName() == "Registration") { Registration * mes = static_cast<Registration*>(message); std::string cipher = UCrypto::EncryptProto(mes, USpikyGameInstance::secretKey); cw->set_registration(cipher); wrapper.set_allocated_cryptogramwrapper(cw.get()); } else if (message->GetTypeName() == "Login") { Login * mes = static_cast<Login*>(message); std::string cipher = UCrypto::EncryptProto(mes, USpikyGameInstance::secretKey); cw->set_login(cipher); wrapper.set_allocated_cryptogramwrapper(cw.get()); } else if (message->GetTypeName() == "MainMenu") { MainMenu * mes = static_cast<MainMenu*>(message); std::string cipher = UCrypto::EncryptProto(mes, USpikyGameInstance::secretKey); cw->set_mainmenu(cipher); wrapper.set_allocated_cryptogramwrapper(cw.get()); } else if (message->GetTypeName() == "Room") { Room * mes = static_cast<Room*>(message); std::string cipher = UCrypto::EncryptProto(mes, USpikyGameInstance::secretKey); cw->set_room(cipher); wrapper.set_allocated_cryptogramwrapper(cw.get()); } else if (message->GetTypeName() == "GameData") { GameData * mes = static_cast<GameData*>(message); std::string cipher = UCrypto::EncryptProto(mes, USpikyGameInstance::secretKey); cw->set_gamemodels(cipher); wrapper.set_allocated_cryptogramwrapper(cw.get()); } } else { if (message->GetTypeName() == "Utility") { Utility * mes = static_cast<Utility*>(message); wrapper.set_allocated_utility(mes); } else if (message->GetTypeName() == "InputChecking") { InputChecking * mes = static_cast<InputChecking*>(message); wrapper.set_allocated_inputchecking(mes); } else if (message->GetTypeName() == "Registration") { Registration * mes = static_cast<Registration*>(message); wrapper.set_allocated_registration(mes); } else if (message->GetTypeName() == "Login") { Login * mes = static_cast<Login*>(message); wrapper.set_allocated_login(mes); } } size_t size = wrapper.ByteSize() + 5; // include size, varint32 never takes more than 5 bytes uint8_t * buffer = new uint8_t[size]; google::protobuf::io::ArrayOutputStream arr(buffer, size); google::protobuf::io::CodedOutputStream output(&arr); //       buffer output.WriteVarint32(wrapper.ByteSize()); wrapper.SerializeToCodedStream(&output); //     utility if (wrapper.has_utility()) { wrapper.release_utility(); } else if (wrapper.has_inputchecking()) { wrapper.release_inputchecking(); } else if (wrapper.has_registration()) { wrapper.release_registration(); } else if (wrapper.has_login()) { wrapper.release_login(); } else if (wrapper.has_cryptogramwrapper()) { wrapper.release_cryptogramwrapper(); } int32 bytesSent = 0; bool sentState = false; if (bTCP) { //send by tcp sentState = USocketObject::tcp_socket->Send(buffer, output.ByteCount(), bytesSent); } else { //send by udp sentState = USocketObject::udp_socket->SendTo(buffer, output.ByteCount(), bytesSent, *USocketObject::udp_address); } delete[] buffer; return sentState; } 


MessageDecoder :

MessageDecoder
 // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "MessageDecoder.h" #include "Protobufs/MessageModels.pb.h" #include "Crypto.h" #include "Descriptors.h" #include "SpikyGameInstance.h" #include "Protobufs/MainMenuModels.pb.h" #include "Protobufs/GameRoomModels.pb.h" void UMessageDecoder::SendProtoToDecoder(Wrapper * wrapper) { if (wrapper->has_inputchecking()) { } else if (wrapper->has_registration()) { } else if (wrapper->has_login()) { } else if (wrapper->has_cryptogramwrapper()) { if (wrapper->cryptogramwrapper().GetReflection()->HasField(wrapper->cryptogramwrapper(), Descriptors::registration_cw)) { std::string source = UCrypto::Decrypt(wrapper->cryptogramwrapper().registration(), USpikyGameInstance::secretKey); Registration registration; registration.ParseFromArray(source.c_str(), source.length()); } else if (wrapper->cryptogramwrapper().GetReflection()->HasField(wrapper->cryptogramwrapper(), Descriptors::login_cw)) { std::string source = UCrypto::Decrypt(wrapper->cryptogramwrapper().login(), USpikyGameInstance::secretKey); Login login; login.ParseFromArray(source.c_str(), source.length()); } else if (wrapper->cryptogramwrapper().GetReflection()->HasField(wrapper->cryptogramwrapper(), Descriptors::initialState_cw)) { std::string source = UCrypto::Decrypt(wrapper->cryptogramwrapper().initialstate(), USpikyGameInstance::secretKey); InitialState is; is.ParseFromArray(source.c_str(), source.length()); } else if (wrapper->cryptogramwrapper().GetReflection()->HasField(wrapper->cryptogramwrapper(), Descriptors::mainMenu_cw)) { std::string source = UCrypto::Decrypt(wrapper->cryptogramwrapper().mainmenu(), USpikyGameInstance::secretKey); MainMenu mainMenu; mainMenu.ParseFromArray(source.c_str(), source.length()); } else if (wrapper->cryptogramwrapper().GetReflection()->HasField(wrapper->cryptogramwrapper(), Descriptors::room_cw)) { std::string source = UCrypto::Decrypt(wrapper->cryptogramwrapper().room(), USpikyGameInstance::secretKey); Room room; room.ParseFromArray(source.c_str(), source.length()); } else if (wrapper->cryptogramwrapper().GetReflection()->HasField(wrapper->cryptogramwrapper(), Descriptors::gameModels_cw)) { std::string source = UCrypto::Decrypt(wrapper->cryptogramwrapper().gamemodels(), USpikyGameInstance::secretKey); GameData gameData; gameData.ParseFromArray(source.c_str(), source.length()); } } } 


InputChecking:

 if (wrapper->has_inputchecking()) { UInputChecking * inputChecking = NewObject<UInputChecking>(UInputChecking::StaticClass()); inputChecking->Handler(wrapper->inputchecking()); } 

, . , Vadim VaDiM , , .



, Logics Registration Login:

Registration
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "Runtime/CoreUObject/Public/UObject/Object.h" #include "Registration.generated.h" class Registration; UCLASS() class SPIKY_CLIENT_API URegistration : public UObject { GENERATED_BODY() public: void Handler(Registration registration); }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "Registration.h" #include "Protobufs/RegLogModels.pb.h" #include "Descriptors.h" #include "Crypto.h" #include "MessageEncoder.h" #include "SpikyGameInstance.h" #include "DifferentMix.h" #include "RegWidgets.h" #include "Runtime/UMG/Public/Components/TextBlock.h" #include "Runtime/UMG/Public/Components/EditableTextBox.h" void URegistration::Handler(Registration registration) { if (registration.GetReflection()->HasField(registration, Descriptors::publicKey_reg)) { // create public, private key keys keys_set = UCrypto::Generate_KeysSet_DH(); std::shared_ptr<Registration> reg(new Registration); reg->set_publickey(keys_set.pubKey); UMessageEncoder::Send(reg.get(), false, true); // send public key to server USpikyGameInstance::secretKey = UCrypto::Generate_SecretKey_DH(registration.publickey()); } else if (registration.GetReflection()->HasField(registration, Descriptors::stateCode_reg)) { if (registration.statecode() == 0) // error while registration { USpikyGameInstance::DifferentMix->wRegistration->wInfoBlock->SetText(FText::FromString("Unknown error try to enter everything again")); USpikyGameInstance::DifferentMix->wRegistration->wLoginTextBox->SetText(FText::FromString("")); USpikyGameInstance::DifferentMix->wRegistration->wPasswordTextBox->SetText(FText::FromString("")); USpikyGameInstance::DifferentMix->wRegistration->wMainTextBox->SetText(FText::FromString("")); USpikyGameInstance::DifferentMix->wRegistration->wCaptchaTextBox->SetText(FText::FromString("")); USpikyGameInstance::DifferentMix->wRegistration->ReloadCaptchaClicked(); USpikyGameInstance::DifferentMix->StopWaitingScreen(); } else if (registration.statecode() == 1) // successful secret key generation { GLog->Log("Successful secret key generation"); std::shared_ptr<Registration> reg(new Registration); reg->set_login(TCHAR_TO_UTF8(*USpikyGameInstance::DifferentMix->wRegistration->wLoginTextBox->GetText().ToString())); std::string hash_str = TCHAR_TO_UTF8(*USpikyGameInstance::DifferentMix->wRegistration->wPasswordTextBox->GetText().ToString()); reg->set_hash(UCrypto::SHA256(&hash_str, hash_str.length())); reg->set_mail(TCHAR_TO_UTF8(*USpikyGameInstance::DifferentMix->wRegistration->wMainTextBox->GetText().ToString())); UMessageEncoder::Send(reg.get(), true, true); } } } 


Login
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "Runtime/CoreUObject/Public/UObject/Object.h" #include "Login.generated.h" class Login; UCLASS() class SPIKY_CLIENT_API ULogin : public UObject { GENERATED_BODY() public: void Handler(Login login); }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "Login.h" #include "Protobufs/RegLogModels.pb.h" #include "SpikyGameInstance.h" #include "LoginWidgets.h" #include "Descriptors.h" #include "Crypto.h" #include "MessageEncoder.h" #include "DifferentMix.h" #include "Runtime/UMG/Public/Components/TextBlock.h" void ULogin::Handler(Login login) { if (login.GetReflection()->HasField(login, Descriptors::publicKey_log)) { USpikyGameInstance::secretKey = UCrypto::Generate_SecretKey_DH(login.publickey()); std::shared_ptr<Login> login_proto(new Login); login_proto->set_mail(ULoginWidgets::mail); login_proto->set_hash(UCrypto::SHA256(&ULoginWidgets::password, ULoginWidgets::password.length())); UMessageEncoder::Send(login_proto.get(), true, true); } if (login.GetReflection()->HasField(login, Descriptors::stateCode_log)) { USpikyGameInstance::DifferentMix->StopWaitingScreen(); USpikyGameInstance::DifferentMix->wLoginScreen->wInfoBlock->SetText(FText::FromString("Incorrect login or password")); GLog->Log(FString("Session ID: NULL")); GLog->Log(FString("Login: NULL")); } } 


SpikyGameInstance , :

 //.h static std::string secretKey; static std::string sessionId; static std::string userLogin; //.cpp std::string USpikyGameInstance::secretKey = ""; std::string USpikyGameInstance::sessionId = ""; std::string USpikyGameInstance::userLogin = ""; 

Registration Login:

Registration
 /* * Copyright (c) 2017, Vadim Petrov - MIT License */ package com.spiky.server.tcp.logics; import com.google.protobuf.ByteString; import com.spiky.server.dbmodels.UserModel; import com.spiky.server.protomodels.MessageModels; import com.spiky.server.protomodels.RegistrationLoginModels; import com.spiky.server.utils.Cryptography; import io.netty.channel.ChannelHandlerContext; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.Transaction; import org.patchca.background.SingleColorBackgroundFactory; import org.patchca.color.SingleColorFactory; import org.patchca.filter.predefined.CurvesRippleFilterFactory; import org.patchca.service.ConfigurableCaptchaService; import org.patchca.utils.encoder.EncoderHelper; import java.awt.*; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.*; import static com.spiky.server.ServerMain.*; import static com.spiky.server.utils.Descriptors.*; public class Registration { public Registration() {} public Registration(ChannelHandlerContext ctx, RegistrationLoginModels.Registration registration) { if(registration.hasField(publicKey_reg)) { Cryptography cryptography = ctx.channel().attr(CRYPTOGRAPHY).get(); ctx.channel().attr(SECRETKEY).set(cryptography.DiffieHellman_createSecretKey(registration.getPublicKey())); cryptography.setSecretKey(ctx.channel().attr(SECRETKEY).get()); System.out.println("secret key: " + ctx.channel().attr(SECRETKEY).get()); // todo registration = RegistrationLoginModels.Registration.newBuilder().clear().setStateCode(1).build(); ctx.writeAndFlush(MessageModels.Wrapper.newBuilder().setRegistration(registration).build()); } else if(!registration.hasField(login_reg) || !registration.hasField(mail_reg) || !registration.hasField(captcha_reg)) { //     registration = RegistrationLoginModels.Registration.newBuilder() .clear() .setStateCode(0) //    .build(); ctx.writeAndFlush(MessageModels.Wrapper.newBuilder().setRegistration(registration).build()); } else { //     ctx.writeAndFlush(checkAll(ctx, registration)); } } private MessageModels.Wrapper checkAll(ChannelHandlerContext ctx, RegistrationLoginModels.Registration registration) { Session session = ctx.channel().attr(HIBER_SESSION).get(); Query query = session.createQuery("SELECT login, mail FROM UserModel WHERE login = :login OR mail = :mail "); query.setParameter("login", registration.getLogin()); query.setParameter("mail", registration.getMail()); java.util.List<String[]> userdata = query.list(); boolean challengeFind = false; synchronized (captchaBank) { for (Map.Entry<Long, String> entry : captchaBank.entrySet()) if (entry.getValue().equals(registration.getCaptcha())) challengeFind = true; } if(!userdata.isEmpty() || !challengeFind) { if(!userdata.isEmpty()) System.out.println("!userdata.isEmpty()"); if(!challengeFind) System.out.println("!challengeFind"); registration = RegistrationLoginModels.Registration.newBuilder().clear().setStateCode(0).build(); return MessageModels.Wrapper.newBuilder().setRegistration(registration).build(); } else { System.out.println("Registration initialization, send public key.."); Cryptography cryptography = ctx.channel().attr(CRYPTOGRAPHY).get(); cryptography.DiffieHellman_createKeys(); registration = RegistrationLoginModels.Registration.newBuilder().clear().setPublicKey(cryptography.getClientPublicKey()).build(); return MessageModels.Wrapper.newBuilder().setRegistration(registration).build(); } } public MessageModels.Wrapper getCaptcha() { try { ConfigurableCaptchaService cs = new ConfigurableCaptchaService(); cs.setBackgroundFactory(new SingleColorBackgroundFactory(new Color(0, 0, 0))); cs.setColorFactory(new SingleColorFactory(new Color(255, 255, 255))); cs.setFilterFactory(new CurvesRippleFilterFactory(cs.getColorFactory())); cs.setHeight(100); cs.setWidth(250); ByteArrayOutputStream bos = new ByteArrayOutputStream(); captchaBank.put(System.currentTimeMillis(), EncoderHelper.getChallangeAndWriteImage(cs, "png", bos)); byte[] captchaBytes = bos.toByteArray(); bos.close(); RegistrationLoginModels.InputChecking inputChecking = RegistrationLoginModels.InputChecking.newBuilder() .setCaptchaData(ByteString.copyFrom(captchaBytes)) .build(); return MessageModels.Wrapper.newBuilder().setInputChecking(inputChecking).build(); } catch (IOException e) { e.printStackTrace(); } return null; } public void saveUser(ChannelHandlerContext ctx, RegistrationLoginModels.Registration registration) { UserModel user = new UserModel(); user.setLogin(registration.getLogin()); user.setHashPass(registration.getHash()); user.setMail(registration.getMail()); Session session = ctx.channel().attr(HIBER_SESSION).get(); Transaction transaction = ctx.channel().attr(HIBER_TRANSACTION).get(); session.save(user); transaction.commit(); RegistrationLoginModels.InitialState.Builder initialState = RegistrationLoginModels.InitialState.newBuilder() .setSessionId(ctx.channel().id().asShortText()) .setLogin(user.getLogin()); ctx.channel().attr(CHANNEL_OWNER).set(user.getLogin()); /* *//*      *//* roomListUpdateSubscribers.add(ctx.channel()); *//*      *//* for (Map.Entry<String,GameRoom> pair : gameRooms.entrySet()) { *//*          *//* if(!pair.getValue().getGameState()) { GameRoomModels.CreateRoom room = GameRoomModels.CreateRoom.newBuilder() .setRoomName(pair.getValue().getRoomName()) .setMapName(pair.getValue().getMapName()) .setCreator(pair.getValue().getCreator()) .setGameTime(pair.getValue().getGameTime() + " minutes") .setMaxPlayers(pair.getValue().getMaxPlayers() + " players") .build(); initialState.addCreateRoom(room); } } */ MessageModels.CryptogramWrapper cryptogramWrapper = MessageModels.CryptogramWrapper.newBuilder() .setInitialState(ByteString.copyFrom(initialState.build().toByteArray())).build(); ctx.writeAndFlush(MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cryptogramWrapper).build()); } } 


Login
 /* * Copyright (c) 2017, Vadim Petrov - MIT License */ package com.spiky.server.tcp.logics; import com.google.protobuf.ByteString; import com.spiky.server.dbmodels.UserModel; import com.spiky.server.protomodels.MessageModels; import com.spiky.server.protomodels.RegistrationLoginModels; import com.spiky.server.utils.Cryptography; import io.netty.channel.ChannelHandlerContext; import org.hibernate.Query; import org.hibernate.Session; import java.util.List; import java.util.Map; import static com.spiky.server.ServerMain.*; import static com.spiky.server.utils.Descriptors.publicKey_log; public class Login { public Login() {} public Login(ChannelHandlerContext ctx, RegistrationLoginModels.Login login) { if(login.hasField(publicKey_log)) { Cryptography cryptography = ctx.channel().attr(CRYPTOGRAPHY).get(); cryptography.DiffieHellman_createKeys(); ctx.channel().attr(SECRETKEY).set(cryptography.DiffieHellman_createSecretKey(login.getPublicKey())); cryptography.setSecretKey(ctx.channel().attr(SECRETKEY).get()); System.out.println("secret key: " + ctx.channel().attr(SECRETKEY).get()); // todo login = RegistrationLoginModels.Login.newBuilder().clear().setPublicKey(cryptography.getClientPublicKey()).build(); ctx.writeAndFlush(MessageModels.Wrapper.newBuilder().setLogin(login).build()); } } public void hasUser(ChannelHandlerContext ctx, RegistrationLoginModels.Login login) { Session session = ctx.channel().attr(HIBER_SESSION).get(); Query query = session.createSQLQuery("SELECT * FROM UserModel WHERE mail = :mail AND hash = :hash").addEntity(UserModel.class); query.setParameter("mail", login.getMail()); query.setParameter("hash", login.getHash()); List<UserModel> user = query.list(); if(!user.isEmpty()) { ctx.channel().attr(CHANNEL_OWNER).set(user.get(0).getLogin()); /*      */ //roomListUpdateSubscribers.add(ctx.channel()); RegistrationLoginModels.InitialState.Builder initialState = RegistrationLoginModels.InitialState.newBuilder() .setSessionId(ctx.channel().id().asShortText()) .setLogin(user.get(0).getLogin()); /* *//*      *//* for (Map.Entry<String,GameRoom> pair : gameRooms.entrySet()) { *//*          *//* if(!pair.getValue().getGameState()) { GameRoomModels.CreateRoom room = GameRoomModels.CreateRoom.newBuilder() .setRoomName(pair.getValue().getRoomName()) .setMapName(pair.getValue().getMapName()) .setCreator(pair.getValue().getCreator()) .setGameTime(pair.getValue().getGameTime() + " minutes") .setMaxPlayers(pair.getValue().getMaxPlayers() + " players") .build(); initialState.addCreateRoom(room); } } */ MessageModels.CryptogramWrapper cryptogramWrapper = MessageModels.CryptogramWrapper.newBuilder() .setInitialState(ByteString.copyFrom(initialState.build().toByteArray())).build(); ctx.writeAndFlush(MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cryptogramWrapper).build()); } else { RegistrationLoginModels.Login log = RegistrationLoginModels.Login.newBuilder().setStateCode(0).build(); MessageModels.CryptogramWrapper cryptogramWrapper = MessageModels.CryptogramWrapper.newBuilder() .setLogin(ByteString.copyFrom(log.toByteArray())).build(); ctx.writeAndFlush(MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cryptogramWrapper).build()); } } } 


DecryptHandler :

DecryptHandler/decode
 ... if(wrapper.hasCryptogramWrapper()) { if(wrapper.getCryptogramWrapper().hasField(registration_cw)) { byte[] cryptogram = wrapper.getCryptogramWrapper().getRegistration().toByteArray(); byte[] original = cryptography.Decrypt(cryptogram, cryptography.getSecretKey()); RegistrationLoginModels.Registration registration = RegistrationLoginModels.Registration.parseFrom(original); new Registration().saveUser(ctx, registration); } else if (wrapper.getCryptogramWrapper().hasField(login_cw)) { byte[] cryptogram = wrapper.getCryptogramWrapper().getLogin().toByteArray(); byte[] original = cryptography.Decrypt(cryptogram, cryptography.getSecretKey()); RegistrationLoginModels.Login login = RegistrationLoginModels.Login.parseFrom(original); new Login().hasUser(ctx, login); } } else if(wrapper.hasInputChecking()) { new InputChecking(ctx, wrapper); } else if(wrapper.hasRegistration()) { new Registration(ctx, wrapper.getRegistration()); } else if(wrapper.hasLogin()) { new Login(ctx, wrapper.getLogin()); } 


:

UMessageDecoder::SendProtoToDecoder
 ... if (wrapper->has_inputchecking()) { UInputChecking * inputChecking = NewObject<UInputChecking>(UInputChecking::StaticClass()); inputChecking->Handler(wrapper->inputchecking()); } else if (wrapper->has_registration()) { URegistration * registration = NewObject<URegistration>(URegistration::StaticClass()); registration->Handler(wrapper->registration()); } else if (wrapper->has_login()) { ULogin * login = NewObject<ULogin>(ULogin::StaticClass()); login->Handler(wrapper->login()); } else if (wrapper->has_cryptogramwrapper()) { if (wrapper->cryptogramwrapper().GetReflection()->HasField(wrapper->cryptogramwrapper(), Descriptors::registration_cw)) { std::string source = UCrypto::Decrypt(wrapper->cryptogramwrapper().registration(), USpikyGameInstance::secretKey); Registration registration; registration.ParseFromArray(source.c_str(), source.length()); URegistration * reg = NewObject<URegistration>(URegistration::StaticClass()); reg->Handler(registration); } else if (wrapper->cryptogramwrapper().GetReflection()->HasField(wrapper->cryptogramwrapper(), Descriptors::login_cw)) { std::string source = UCrypto::Decrypt(wrapper->cryptogramwrapper().login(), USpikyGameInstance::secretKey); Login login; login.ParseFromArray(source.c_str(), source.length()); ULogin * log = NewObject<ULogin>(ULogin::StaticClass()); log->Handler(login); } } 


.

check in


— , ReloadCaptchaClicked() , InputChecking inputChecking->set_getcaptcha(true) , UMessageEncoder::Send(message, bCrypt, bTCP) , , InputChecking , Wrapper TCP USocketObject::tcp_socket->Send(...) .

DecryptHandler decode. , , InputChecking(ctx, wrapper) , . Registration. InputChecking Registration().getCaptcha() . Map<Long,String> captchaBank , , . ServerMain — captchaCleaner() .

FTCPSocketListeningTh::Run() UMessageDecoder::SendProtoToDecoder . UInputChecking::Handler, « » HasField(inputChecking, Descriptors::captchaDataField_ich) , :

 UTexture2D* tex= USpikyGameInstance::DifferentMix->CreateTexture(inputChecking.captchadata(),true); USpikyGameInstance::DifferentMix->wRegistration->wCaptchaImage->SetBrushFromTexture(tex); 

, , , - . , , .

SingUpButtonClicked(), USpikyGameInstance::DifferentMix->RunWaitingScreen() , Registration Login, Mail, Captcha . , Registration, , checkAll(). HIBER_SESSION . CRYPTOGRAPHY DiffieHellman/createKeys() .

, URegistration::Handler . USpikyGameInstance::secretKey . Registration if(registration.hasField(publicKey_reg)) , ctx.channel().attr(SECRETKEY) . , , . Registration, , UCrypto::SHA256 . UMessageEncoder::Send UCrypto::EncryptProto(mes, USpikyGameInstance::secretKey) . CryptogramWrapper, Wrapper, . Registration saveUser(...) .

Login


, , LoginButtonClicked() DifferentMix->RunWaitingScreen() , . . , . .


. , , .



:

MainMenu_W – ;
ChatMain_W – , MainMenu;
CreateRoom_W – ;

MainMenuWidgets
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "Runtime/UMG/Public/Blueprint/UserWidget.h" #include "MainMenuWidgets.generated.h" class UTextBlock; class UButton; class UScrollBox; class URoomListUnit; UCLASS() class SPIKY_CLIENT_API UMainMenuWidgets : public UUserWidget { GENERATED_BODY() virtual void NativeConstruct() override; public: UButton* wOpenChatButton = nullptr; UTextBlock* wPlayerName = nullptr; UTextBlock* wInfoBlock = nullptr; UButton* wCreateRoomButton = nullptr; UFUNCTION() void OpenChatButtonClicked(); UFUNCTION() void CreateRoomButtonClicked(); FTimerHandle MessageTimerHandle; void HideErrorMessage(); void ShowErrorMessage(FString msg); UScrollBox * wRoomsScrollBox = nullptr; bool bChatOpen = false; bool bCreateRoomOpen = false; void AddRoom(URoomListUnit* room); }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "MainMenuWidgets.h" #include "Runtime/UMG/Public/Components/Button.h" #include "Runtime/UMG/Public/Components/TextBlock.h" #include "Runtime/UMG/Public/Components/ScrollBox.h" #include "Runtime/Engine/Public/TimerManager.h" void UMainMenuWidgets::NativeConstruct() { Super::NativeConstruct(); wOpenChatButton = Cast<UButton>(GetWidgetFromName(TEXT("OpenChatButton"))); wOpenChatButton->OnClicked.AddDynamic(this, &UMainMenuWidgets::OpenChatButtonClicked); wCreateRoomButton = Cast<UButton>(GetWidgetFromName(TEXT("CreateRoomButton"))); wCreateRoomButton->OnClicked.AddDynamic(this, &UMainMenuWidgets::CreateRoomButtonClicked); wPlayerName = Cast<UTextBlock>(GetWidgetFromName(TEXT("PlayerName"))); wInfoBlock = Cast<UTextBlock>(GetWidgetFromName(TEXT("InfoBox"))); wRoomsScrollBox = Cast<UScrollBox>(GetWidgetFromName(TEXT("RoomsScrollBox"))); } void UMainMenuWidgets::OpenChatButtonClicked() { } void UMainMenuWidgets::CreateRoomButtonClicked() { } void UMainMenuWidgets::AddRoom(URoomListUnit* room) { } void UMainMenuWidgets::HideErrorMessage() { wInfoBlock->SetText(FText::FromString(" ")); } void UMainMenuWidgets::ShowErrorMessage(FString msg) { wInfoBlock->SetText(FText::FromString(*msg)); GetWorld()->GetTimerManager().ClearTimer(MessageTimerHandle); GetWorld()->GetTimerManager().SetTimer(MessageTimerHandle, this, &UMainMenuWidgets::HideErrorMessage, 2.f, false); } 


MainMenuChatWidgets
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "Runtime/UMG/Public/Blueprint/UserWidget.h" #include "MainMenuChatWidgets.generated.h" class Chat; class UButton; class UScrollBox; class UMultiLineEditableTextBox; class URichText; UCLASS() class SPIKY_CLIENT_API UMainMenuChatWidgets : public UUserWidget { GENERATED_BODY() virtual void NativeConstruct() override; public: UButton* wEnterButton = nullptr; UFUNCTION() void EnterButtonClicked(); UScrollBox * wChatScrollBox = nullptr; UMultiLineEditableTextBox* wChatTextBox = nullptr; void NewMessage(URichText* richText); void ScrollToEnd(); }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "MainMenuChatWidgets.h" #include "Runtime/UMG/Public/Components/ScrollBox.h" #include "Runtime/UMG/Public/Components/Button.h" #include "Runtime/UMG/Public/Components/MultiLineEditableTextBox.h" void UMainMenuChatWidgets::NativeConstruct() { Super::NativeConstruct(); wEnterButton = Cast<UButton>(GetWidgetFromName(TEXT("EnterButton"))); wEnterButton->OnClicked.AddDynamic(this, &UMainMenuChatWidgets::EnterButtonClicked); wChatScrollBox = Cast<UScrollBox>(GetWidgetFromName(TEXT("ChatScrollBox"))); wChatTextBox = Cast<UMultiLineEditableTextBox>(GetWidgetFromName(TEXT("ChatTextBox"))); } void UMainMenuChatWidgets::EnterButtonClicked() { } void UMainMenuChatWidgets::NewMessage(URichText* richText) { } void UMainMenuChatWidgets::ScrollToEnd() { } 


CreateRoom
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "Runtime/UMG/Public/Blueprint/UserWidget.h" #include "CreateRoomWidgets.generated.h" class UEditableTextBox; class UButton; class UComboBoxString; UCLASS() class SPIKY_CLIENT_API UCreateRoomWidgets : public UUserWidget { GENERATED_BODY() virtual void NativeConstruct() override; public: UEditableTextBox* wRoomNameTextBox = nullptr; UComboBoxString* wMapComboBox = nullptr; UComboBoxString* wGameTimeComboBox = nullptr; UComboBoxString* wPlayersNumberComboBox = nullptr; UButton* wCreateRoomButton = nullptr; UFUNCTION() void CreateRoomButtonClicked(); UFUNCTION() void OnRoomNameTextChanged(const FText & text); bool bRoomNameOk = false; }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "CreateRoomWidgets.h" #include "Runtime/UMG/Public/Components/Button.h" #include "Runtime/UMG/Public/Components/EditableTextBox.h" #include "Runtime/UMG/Public/Components/ComboBoxString.h" void UCreateRoomWidgets::NativeConstruct() { Super::NativeConstruct(); wCreateRoomButton = Cast<UButton>(GetWidgetFromName(TEXT("CreateRoomButton"))); wCreateRoomButton->OnClicked.AddDynamic(this, &UCreateRoomWidgets::CreateRoomButtonClicked); wRoomNameTextBox = Cast<UEditableTextBox>(GetWidgetFromName(TEXT("RoomName"))); wRoomNameTextBox->OnTextChanged.AddDynamic(this, &UCreateRoomWidgets::OnRoomNameTextChanged); wMapComboBox = Cast<UComboBoxString>(GetWidgetFromName(TEXT("MapComboBox"))); wGameTimeComboBox = Cast<UComboBoxString>(GetWidgetFromName(TEXT("TimeComboBox"))); wPlayersNumberComboBox = Cast<UComboBoxString>(GetWidgetFromName(TEXT("PNComboBox"))); } void UCreateRoomWidgets::CreateRoomButtonClicked() { } void UCreateRoomWidgets::OnRoomNameTextChanged(const FText & text) { } 


. DifferentMix:

DifferentMix
 //.h class UMainMenuWidgets; class UMainMenuChatWidgets; class UCreateRoomWidgets; UMainMenuWidgets* tmpMainMenuRef; UMainMenuChatWidgets* tmpMainMenuChatRef; UCreateRoomWidgets* tmpCreateRoomRef; UMainMenuWidgets* wMainMenuWidgets; UMainMenuChatWidgets* wMainMenuChatWidgets; UCreateRoomWidgets* wCreateRoomWidgets; UCanvasPanelSlot* mainMenuSlot; UCanvasPanelSlot* mainMenuChatSlot; UCanvasPanelSlot* createRoomSlot; void ShowMainMenuScreen(); //.cpp #include "MainMenuWidgets.h" #include "MainMenuChatWidgets.h" #include "CreateRoomWidgets.h" UDifferentMix::UDifferentMix(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { static ConstructorHelpers::FClassFinder<UMainMenuWidgets> MainMenuWidgets(TEXT("WidgetBlueprint'/Game/Blueprints/Widgets/MainMenu_W.MainMenu_W_C'")); if (MainMenuWidgets.Class != NULL) { tmpMainMenuRef = MainMenuWidgets.Class->GetDefaultObject<UMainMenuWidgets>(); } static ConstructorHelpers::FClassFinder<UMainMenuChatWidgets> chatMainMenuWidgets(TEXT("WidgetBlueprint'/Game/Blueprints/Widgets/ChatMain_W.ChatMain_W_C'")); if (chatMainMenuWidgets.Class != NULL) { tmpMainMenuChatRef = chatMainMenuWidgets.Class->GetDefaultObject<UMainMenuChatWidgets>(); } static ConstructorHelpers::FClassFinder<UCreateRoomWidgets> createRoomWidgets(TEXT("WidgetBlueprint'/Game/Blueprints/Widgets/CreateRoom_W.CreateRoom_W_C'")); if (createRoomWidgets.Class != NULL) { tmpCreateRoomRef = createRoomWidgets.Class->GetDefaultObject<UCreateRoomWidgets>(); } } void UDifferentMix::Init() { wMainMenuWidgets = CreateWidget<UMainMenuWidgets>(GetWorld(), tmpMainMenuRef->GetClass()); mainMenuSlot = Cast<UCanvasPanelSlot>(wWidgetContainer->wCanvas->AddChild(wMainMenuWidgets)); mainMenuSlot->SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f)); mainMenuSlot->SetOffsets(FMargin(0, 0, 0, 0)); wMainMenuWidgets->SetVisibility(ESlateVisibility::Hidden); wMainMenuChatWidgets = CreateWidget<UMainMenuChatWidgets>(GetWorld(), tmpMainMenuChatRef->GetClass()); mainMenuChatSlot = Cast<UCanvasPanelSlot>(wWidgetContainer->wCanvas->AddChild(wMainMenuChatWidgets)); mainMenuChatSlot->SetZOrder(10); mainMenuChatSlot->SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f)); mainMenuChatSlot->SetOffsets(FMargin(0, 0, 0, 0)); wMainMenuChatWidgets->SetVisibility(ESlateVisibility::Hidden); wCreateRoomWidgets = CreateWidget<UCreateRoomWidgets>(GetWorld(), tmpCreateRoomRef->GetClass()); createRoomSlot = Cast<UCanvasPanelSlot>(wWidgetContainer->wCanvas->AddChild(wCreateRoomWidgets)); createRoomSlot->SetZOrder(1); createRoomSlot->SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f)); createRoomSlot->SetOffsets(FMargin(0, 0, 0, 0)); wCreateRoomWidgets->SetVisibility(ESlateVisibility::Hidden); } void UDifferentMix::ShowMainMenuScreen() { HideAllWidgets(); wMainMenuWidgets->SetVisibility(ESlateVisibility::Visible); } 


, . , id. Logics InitialState, , , , , , . id , .

UMessageDecoder :

 UInitialState * initialState = NewObject<UInitialState>(UInitialState::StaticClass()); initialState->Handler(is); 

, , -- . , , , — , , . 100 CircularArrayList ArrayList, .

UMainMenuWidgets::OpenChatButtonClicked(), - :

UMainMenuWidgets::OpenChatButtonClicked()
 #include "Protobufs/MainMenuModels.pb.h" #include "SpikyGameInstance.h" #include "DifferentMix.h" #include "MainMenuChatWidgets.h" #include "MessageEncoder.h" void UMainMenuWidgets::OpenChatButtonClicked() { if (!bChatOpen) { bChatOpen = true; USpikyGameInstance::DifferentMix->wMainMenuChatWidgets->SetVisibility(ESlateVisibility::SelfHitTestInvisible); std::shared_ptr<Chat> chat(new Chat); chat->set_subscribe(true); std::shared_ptr<MainMenu> mainMenu(new MainMenu); mainMenu->set_allocated_chat(chat.get()); UMessageEncoder::Send(mainMenu.get(), true, true); mainMenu->release_chat(); } else { bChatOpen = false; USpikyGameInstance::DifferentMix->wMainMenuChatWidgets->SetVisibility(ESlateVisibility::Hidden); USpikyGameInstance::DifferentMix->wMainMenuChatWidgets->wChatScrollBox->ClearChildren(); std::shared_ptr<Chat> chat(new Chat); chat->set_subscribe(false); std::shared_ptr<MainMenu> mainMenu(new MainMenu); mainMenu->set_allocated_chat(chat.get()); UMessageEncoder::Send(mainMenu.get(), true, true); mainMenu->release_chat(); } } 


, MainMenu :

MainMenu
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "Runtime/CoreUObject/Public/UObject/Object.h" #include "MainMenu.generated.h" class MainMenu; UCLASS() class SPIKY_CLIENT_API UMainMenu : public UObject { GENERATED_BODY() public: void Handler(MainMenu mainMenu); }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "MainMenu.h" #include "Protobufs/MainMenuModels.pb.h" #include "Descriptors.h" void UMainMenu::Handler(MainMenu mainMenu) { } 


, MainMenu , DecryptHandler:

 else if(wrapper.getCryptogramWrapper().hasField(mainMenu_cw)) { byte[] cryptogram = wrapper.getCryptogramWrapper().getMainMenu().toByteArray(); byte[] original = cryptography.Decrypt(cryptogram, cryptography.getSecretKey()); MainMenuModels.MainMenu mainMenu = MainMenuModels.MainMenu.parseFrom(original); new MainMenu(ctx, mainMenu); } 

MainMenuChat, :

MainMenuChat
 public class MainMenu { public MainMenu(ChannelHandlerContext ctx, MainMenuModels.MainMenu mainMenu) { if(mainMenu.hasField(chat_mm)) { new MainMenuChat(ctx, mainMenu.getChat()); } } } public class MainMenuChat { public MainMenuChat(ChannelHandlerContext ctx, MainMenuModels.Chat chat) { } } 


, , , . Utils:

CircularArrayList
 /* * Copyright (c) 2017, Vadim Petrov - MIT License */ package com.spiky.server.utils; import java.util.ArrayList; public class CircularArrayList<E> extends ArrayList<E> { private ArrayList<E> list; private int maxSize; public CircularArrayList(int size) { list = new ArrayList<E> (size); maxSize = size; } @Override public int size() { return list.size(); } @Override public boolean add (E objectToAdd) { if (list.size () > maxSize) { list.remove(0); list.add(objectToAdd); } else { list.add(objectToAdd); } return true; } @Override public E get(int i) { return list.get(i); } @Override public String toString() { String str = ""; for (E element : list) str+= "[" + element + "]"; return str; } } 


ArrayList, . MainMenuChat:

 /*     */ private static ChannelGroup recipients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); /*   100  */ private static List<MainMenuModels.Chat> syncMessageList = Collections.synchronizedList(new CircularArrayList<MainMenuModels.Chat>(100)); 

Recipients – Netty, , , . MainMenuChat :

 if(chat.hasField(subscribe_chat)) if(chat.getSubscribe()) /*  */ recipients.add(ctx.channel());    100     recipients.remove(ctx.channel()); else                /*    */ recipients.writeAndFlush(wrapper); 

UMainMenu::Handler:

  if (mainMenu.GetReflection()->HasField(mainMenu, Descriptors::chat_mm)) { UChats * chats = NewObject<UChats>(UChats::StaticClass()); chats->Handler(mainMenu.chat(), "mainmenu"); } 

, , , .



. RoomManager – // , /, . GameRoom – , , , . CreateRoomWidgets , , , .

CreateRoom, Room, , Wrapper.

UMainMenuWidgets::CreateRoomButtonClicked():

 #include "CreateRoomWidgets.h" ... void UMainMenuWidgets::CreateRoomButtonClicked() { if (!bCreateRoomOpen) { bCreateRoomOpen = true; USpikyGameInstance::DifferentMix->wCreateRoomWidgets->SetVisibility(ESlateVisibility::SelfHitTestInvisible); } else { bCreateRoomOpen = false; USpikyGameInstance::DifferentMix->wCreateRoomWidgets->SetVisibility(ESlateVisibility::Hidden); } } 




DecryptHandler/decode, RoomManager:

 ... else if(wrapper.getCryptogramWrapper().hasField(room_cw)) { byte[] cryptogram = wrapper.getCryptogramWrapper().getRoom().toByteArray(); byte[] original = cryptography.Decrypt(cryptogram, cryptography.getSecretKey()); GameRoomModels.Room room = GameRoomModels.Room.parseFrom(original); new RoomManager(ctx, room); } ... 

GameRoom, Logics:

GameRoom
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "Runtime/CoreUObject/Public/UObject/Object.h" #include <string> #include "GameRoom.generated.h" class Room; class CreateRoom; class URoomListUnit; class RoomsListUpdate; class SubscribeRoom; class RoomUpdate; UCLASS() class SPIKY_CLIENT_API UGameRoom : public UObject { GENERATED_BODY() public: void Handler(Room room); static URoomListUnit* NewRoom(CreateRoom room); static std::string roomCreator; static std::string roomName; void DeleteRoom(RoomsListUpdate room); void DeleteRoom(std::string name); void Subscribe(SubscribeRoom subscribe); void UpdateRoom(RoomUpdate update); void StartGame(); }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "GameRoom.h" #include "Descriptors.h" #include "Protobufs/GameRoomModels.pb.h" std::string UGameRoom::roomCreator = ""; std::string UGameRoom::roomName = ""; void UGameRoom::Handler(Room room) { if (room.has_createroom()) { } else if (room.has_roomslistupdate()) { } else if (room.has_subscriberoom()) { } else if (room.has_roomupdate()) { } } void UGameRoom::Subscribe(SubscribeRoom subscribe) { } void UGameRoom::UpdateRoom(RoomUpdate update) { } URoomListUnit* UGameRoom::NewRoom(CreateRoom room) { return nullptr; } void UGameRoom::DeleteRoom(RoomsListUpdate update) { } void UGameRoom::DeleteRoom(std::string name) { } void UGameRoom::StartGame() { } 


, RoomManager, if(room.hasCreateRoom()) createRoom(ctx, room.getCreateRoom());

, ( ) , . ServerMain:

 public final static AttributeKey<String> ROOM_OWNER = AttributeKey.valueOf("room_owner"); /*          */ public static ChannelGroup roomListUpdateSubscribers = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); /*     */ public static Map<String,GameRoom> gameRooms = Collections.synchronizedMap(new HashMap<>()); 

Open GameRoom, an instance of this class stores the name of the creator, the name of the map, the name of the room, the time of the game, the number of players, the session id of the creator, the state of the game — that is, the game is running or not; players. Command lists and chat:

GameRoom
 /* * Copyright (c) 2017, Vadim Petrov - MIT License */ package com.spiky.server.tcp.logics; import com.spiky.server.protomodels.GameRoomModels; import com.spiky.server.protomodels.MainMenuModels; import com.spiky.server.utils.CircularArrayList; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.util.concurrent.GlobalEventExecutor; import java.util.*; import static com.spiky.server.ServerMain.CHANNEL_OWNER; public class GameRoom { private String creator; private String mapName; private String roomName; private int gameTime; private int maxPlayers; private String creatorSessionId; private boolean gameState = false; Map<String,Channel> players = Collections.synchronizedMap(new HashMap<>()); Map<String,Channel> team1 = Collections.synchronizedMap(new HashMap<>()); Map<String,Channel> team2 = Collections.synchronizedMap(new HashMap<>()); Map<String,Channel> undistributed = Collections.synchronizedMap(new HashMap<>()); //Map<String,PlayerState> playersState = Collections.synchronizedMap(new HashMap<>()); /*      */ ChannelGroup recipients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); /*   100  */ List<MainMenuModels.Chat> syncMessageList = Collections.synchronizedList(new CircularArrayList<MainMenuModels.Chat>(100)); GameRoom(ChannelHandlerContext ctx, GameRoomModels.CreateRoom roomDescribe) { creator = ctx.channel().attr(CHANNEL_OWNER).get(); /*      ,        id  */ creatorSessionId = ctx.channel().id().asShortText(); roomName = roomDescribe.getRoomName(); mapName = roomDescribe.getMapName(); if(Objects.equals(roomDescribe.getGameTime(), "5 minutes")) { gameTime = 5; } else if(Objects.equals(roomDescribe.getGameTime(), "10 minutes")) { gameTime = 10; } else if(Objects.equals(roomDescribe.getGameTime(), "15 minutes")) { gameTime = 15; } else if(Objects.equals(roomDescribe.getGameTime(), "20 minutes")) { gameTime = 20; } maxPlayers = Integer.parseInt(roomDescribe.getMaxPlayers()); addPlayer(ctx, creator); } void addPlayer(ChannelHandlerContext ctx, String player) { if(players.size() < maxPlayers) { /*      */ players.put(player, ctx.channel()); /*     */ undistributed.put(player, ctx.channel()); /*    */ recipients.add(ctx.channel()); } } public String getCreator() { return creator; } public void setCreator(String creator) { this.creator = creator; } public String getMapName() { return mapName; } public void setMapName(String mapName) { this.mapName = mapName; } public String getRoomName() { return roomName; } public void setRoomName(String roomName) { this.roomName = roomName; } public int getGameTime() { return gameTime; } public void setGameTime(int gameTime) { this.gameTime = gameTime; } public int getMaxPlayers() { return maxPlayers; } public void setMaxPlayers(int maxPlayers) { this.maxPlayers = maxPlayers; } public String getCreatorSessionId() { return creatorSessionId; } public void setCreatorSessionId(String creatorSessionId) { this.creatorSessionId = creatorSessionId; } public boolean getGameState() { return gameState; } public void setGameState(boolean gameState) { this.gameState = gameState; } } 


Roommanager
 /* * Copyright (c) 2017, Vadim Petrov - MIT License */ package com.spiky.server.tcp.logics; import com.google.protobuf.ByteString; import com.spiky.server.protomodels.GameRoomModels; import com.spiky.server.protomodels.MessageModels; import io.netty.channel.ChannelHandlerContext; import static com.spiky.server.ServerMain.*; public class RoomManager { public RoomManager(ChannelHandlerContext ctx, GameRoomModels.Room room) { if(room.hasCreateRoom()) { createRoom(ctx, room.getCreateRoom()); } } private void createRoom(ChannelHandlerContext ctx, GameRoomModels.CreateRoom createRoom) { /*        */ GameRoom gameRoom = new GameRoom(ctx, createRoom); /*       */ if(!gameRooms.containsKey(gameRoom.getCreator())) { gameRooms.put(gameRoom.getCreator(), gameRoom); ctx.channel().attr(ROOM_OWNER).set(ctx.channel().attr(CHANNEL_OWNER).get()); createRoom = createRoom.toBuilder().setRoomName(gameRoom.getRoomName()).setCreator(gameRoom.getCreator()).build(); /*     */ GameRoomModels.Room room = GameRoomModels.Room.newBuilder().setCreateRoom(createRoom).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(room.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); /*      */ roomListUpdateSubscribers.writeAndFlush(wrapper); } } } 


createRoom(ChannelHandlerContext ctx, GameRoomModels.CreateRoom createRoom)
Creates a new room based on incoming data. Checks for the presence of a room with the owner, if the player has already created one room, he can not create a second one at a time. Return the room data to the client and update the list of rooms for everyone (type of response is CreateRoom).

On the client, the message goes to UGameRoom :: Handler:

 if (room.has_createroom()) { USpikyGameInstance::DifferentMix->wMainMenuWidgets->AddRoom(NewRoom(room.createroom())); //      ,         if (room.createroom().creator() == USpikyGameInstance::userLogin) { roomCreator = room.createroom().creator(); roomName = room.createroom().roomname(); USpikyGameInstance::DifferentMix->ShowGameRoom(); } } 

. . UButton , :

 std::shared_ptr<SubscribeRoom> subscribe(new SubscribeRoom); subscribe->set_subscribe(true); subscribe->set_roomname(TCHAR_TO_UTF8(*textBlock->GetText().ToString())); 

RoomListUnit UI:

RoomListUnit
 // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "Runtime/UMG/Public/Components/Button.h" #include "RoomListUnit.generated.h" DECLARE_DYNAMIC_MULTICAST_DELEGATE(FClickDelegate); UCLASS() class SPIKY_CLIENT_API URoomListUnit: public UButton { GENERATED_BODY() public: URoomListUnit(); UPROPERTY() FClickDelegate click; UFUNCTION() void OnClick(); }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "RoomListUnit.h" #include "Protobufs/GameRoomModels.pb.h" #include "Runtime/UMG/Public/Components/HorizontalBox.h" #include "Runtime/UMG/Public/Components/TextBlock.h" #include "MessageEncoder.h" URoomListUnit::URoomListUnit() { OnClicked.AddDynamic(this, &URoomListUnit::OnClick); } void URoomListUnit::OnClick() { std::shared_ptr<Room> room(new Room); UHorizontalBox* horBox = Cast<UHorizontalBox>(GetChildAt(0)); UTextBlock* textBlock = Cast<UTextBlock>(horBox->GetChildAt(0)); std::shared_ptr<SubscribeRoom> subscribe(new SubscribeRoom); subscribe->set_subscribe(true); subscribe->set_roomname(TCHAR_TO_UTF8(*textBlock->GetText().ToString())); room->set_allocated_subscriberoom(subscribe.get()); UMessageEncoder::Send(room.get(), true, true); room->release_subscriberoom(); } 


UMainMenuWidgets::AddRoom(URoomListUnit* room):

 #include "Runtime/UMG/Public/Components/ScrollBoxSlot.h" #include "RoomListUnit.h" void UMainMenuWidgets::AddRoom(URoomListUnit* room) { UScrollBoxSlot* gameRoomSlot = Cast<UScrollBoxSlot>(wRoomsScrollBox->AddChild(room)); gameRoomSlot->SetHorizontalAlignment(HAlign_Fill); } URoomListUnit* UGameRoom::NewRoom(CreateRoom room)   ,               

UGameRoom::NewRoom
 #include "Runtime/UMG/Public/Components/HorizontalBox.h" #include "Runtime/UMG/Public/Components/HorizontalBoxSlot.h" #include "Runtime/UMG/Public/Components/TextBlock.h" #include "Runtime/UMG/Public/Components/ButtonSlot.h" #include "Runtime/UMG/Public/Components/ScrollBox.h" #include "RoomListUnit.h" URoomListUnit* UGameRoom::NewRoom(CreateRoom room) { URoomListUnit* button = NewObject<URoomListUnit>(URoomListUnit::StaticClass()); UHorizontalBox* horBox = NewObject<UHorizontalBox>(UHorizontalBox::StaticClass()); UTextBlock* wRoomName = NewObject<UTextBlock>(UTextBlock::StaticClass()); UTextBlock* wMapName = NewObject<UTextBlock>(UTextBlock::StaticClass()); UTextBlock* wCreator = NewObject<UTextBlock>(UTextBlock::StaticClass()); UTextBlock* wGameTime = NewObject<UTextBlock>(UTextBlock::StaticClass()); UTextBlock* wMaxPlayers = NewObject<UTextBlock>(UTextBlock::StaticClass()); wRoomName->SetText(FText::FromString(TCHAR_TO_UTF8(*FString(room.roomname().c_str())))); wMapName->SetText(FText::FromString(TCHAR_TO_UTF8(*FString(room.mapname().c_str())))); wCreator->SetText(FText::FromString(TCHAR_TO_UTF8(*FString(room.creator().c_str())))); wGameTime->SetText(FText::FromString(TCHAR_TO_UTF8(*FString(room.gametime().c_str())))); wMaxPlayers->SetText(FText::FromString(TCHAR_TO_UTF8(*FString(room.maxplayers().c_str())))); UHorizontalBoxSlot* roomNameSlot = Cast<UHorizontalBoxSlot>(horBox->AddChild(wRoomName)); UHorizontalBoxSlot* mapNameSlot = Cast<UHorizontalBoxSlot>(horBox->AddChild(wMapName)); UHorizontalBoxSlot* creatorSlot = Cast<UHorizontalBoxSlot>(horBox->AddChild(wCreator)); UHorizontalBoxSlot* gameTimeSlot = Cast<UHorizontalBoxSlot>(horBox->AddChild(wGameTime)); UHorizontalBoxSlot* maxPlayersSlot = Cast<UHorizontalBoxSlot>(horBox->AddChild(wMaxPlayers)); roomNameSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); roomNameSlot->SetHorizontalAlignment(HAlign_Center); mapNameSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); mapNameSlot->SetHorizontalAlignment(HAlign_Center); creatorSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); creatorSlot->SetHorizontalAlignment(HAlign_Center); gameTimeSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); gameTimeSlot->SetHorizontalAlignment(HAlign_Center); maxPlayersSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); maxPlayersSlot->SetHorizontalAlignment(HAlign_Center); UButtonSlot* buttonSlot = Cast<UButtonSlot>(button->AddChild(horBox)); buttonSlot->SetHorizontalAlignment(HAlign_Fill); buttonSlot->SetVerticalAlignment(VAlign_Fill); return button; } 


, , , .
Login :

 if(!user.isEmpty()) ctx.channel().attr(CHANNEL_OWNER).set(user.get(0).getLogin()); /*      */ roomListUpdateSubscribers.add(ctx.channel()); 

:

 /*      */ for (Map.Entry<String,GameRoom> pair : gameRooms.entrySet()) { /*          */ if(!pair.getValue().getGameState()) { GameRoomModels.CreateRoom room = GameRoomModels.CreateRoom.newBuilder() .setRoomName(pair.getValue().getRoomName()) .setMapName(pair.getValue().getMapName()) .setCreator(pair.getValue().getCreator()) .setGameTime(pair.getValue().getGameTime() + " minutes") .setMaxPlayers(pair.getValue().getMaxPlayers() + " players") .build(); initialState.addCreateRoom(room); } } 

Registration:

 public void saveUser(ChannelHandlerContext ctx, RegistrationLoginModels.Registration registration) { … ctx.channel().attr(CHANNEL_OWNER).set(user.getLogin()); /*      */ roomListUpdateSubscribers.add(ctx.channel()); /*      */ for (Map.Entry<String,GameRoom> pair : gameRooms.entrySet()) { … } 

. , . , . . , .





, , DiffrentMix :

Changes
 //.h void ShowGameRoom(); UTexture2D* EmptyImage = nullptr; UTexture2D* GreenImage = nullptr; //.cpp //   static ConstructorHelpers::FObjectFinder<UTexture2D> EmptyImageRef(TEXT("Texture2D'/Game/ProjectResources/Images/empty.empty'")); EmptyImage = EmptyImageRef.Object; static ConstructorHelpers::FObjectFinder<UTexture2D> GreenImageRef(TEXT("Texture2D'/Game/ProjectResources/Images/G.G'")); GreenImage = GreenImageRef.Object; void UDifferentMix::ShowGameRoom() { HideAllWidgets(); //  ,   wMainMenuChatWidgets->SetVisibility(ESlateVisibility::Hidden); wMainMenuWidgets->bChatOpen = false; wGameRoomWidgets->SetVisibility(ESlateVisibility::Visible); wGameRoomWidgets->wToFirstTeamButton->SetVisibility(ESlateVisibility::Visible); wGameRoomWidgets->wToSecondTeamButton->SetVisibility(ESlateVisibility::Visible); wGameRoomWidgets->wToUndistributedTeamButton->SetVisibility(ESlateVisibility::Visible); wGameRoomWidgets->wStartButton->SetVisibility(ESlateVisibility::Visible); wGameRoomWidgets->AddPlayer(USpikyGameInstance::userLogin.c_str(), "undistributed"); } 


Add the source code of the widget, to start the list item, the custom button UGameRoomUserUnit. Here we bind the event of pressing the button, change its style and add the player to the list of selected ones, with which we will later move through the lists:

GameRoomUserUnit
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "Runtime/UMG/Public/Components/Button.h" #include <vector> #include "GameRoomUserUnit.generated.h" DECLARE_DYNAMIC_MULTICAST_DELEGATE(FClickDelegateGameRoom); UCLASS() class SPIKY_CLIENT_API UGameRoomUserUnit: public UButton { GENERATED_BODY() public: UGameRoomUserUnit(); UPROPERTY() FClickDelegateGameRoom click; UFUNCTION() void OnClick(); bool bSelect = false; static std::vector<std::pair<FString, FString>> select_players; }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "GameRoomUserUnit.h" #include "Runtime/UMG/Public/Components/TextBlock.h" #include "SpikyGameInstance.h" #include "DifferentMix.h" #include <algorithm> #include "GameRoom.h" std::vector<std::pair<FString, FString>> UGameRoomUserUnit::select_players; UGameRoomUserUnit::UGameRoomUserUnit() { OnClicked.AddDynamic(this, &UGameRoomUserUnit::OnClick); } void UGameRoomUserUnit::OnClick() { //        if (UGameRoom::roomCreator != USpikyGameInstance::userLogin) return; UTextBlock* textBlock = Cast<UTextBlock>(GetChildAt(0)); FSlateBrush greenBackgroundImage; greenBackgroundImage.SetResourceObject((UObject*)(USpikyGameInstance::DifferentMix->GreenImage)); FSlateBrush emptyBackgroundImage; emptyBackgroundImage.SetResourceObject((UObject*)(USpikyGameInstance::DifferentMix->EmptyImage)); FString player_name = textBlock->GetText().ToString();; FString team_name = GetParent()->GetName(); std::pair<FString, FString> item = std::make_pair(player_name, team_name); if (!bSelect) { bSelect = true; WidgetStyle.Pressed = greenBackgroundImage; WidgetStyle.Hovered = greenBackgroundImage; WidgetStyle.Normal = greenBackgroundImage; select_players.push_back(item); } else { bSelect = false; WidgetStyle.Pressed = emptyBackgroundImage; WidgetStyle.Hovered = emptyBackgroundImage; WidgetStyle.Normal = emptyBackgroundImage; auto it = std::find(select_players.begin(), select_players.end(), item); if (it == select_players.end()) { // not in vector } else { auto index = std::distance(select_players.begin(), it); select_players.erase(select_players.begin() + index); } } GLog->Log(FString::FromInt(select_players.size())); } 


GameRoomWidgets
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "Runtime/UMG/Public/Blueprint/UserWidget.h" #include "GameRoomWidgets.generated.h" class UScrollBox; class UButton; class UMultiLineEditableTextBox; class URichText; UCLASS() class SPIKY_CLIENT_API UGameRoomWidgets : public UUserWidget { GENERATED_BODY() virtual void NativeConstruct() override; public: UScrollBox* wUndistributedTeamScrollBox = nullptr; UScrollBox* wFirstTeamScrollBox = nullptr; UScrollBox* wSecondTeamScrollBox = nullptr; UButton* wToFirstTeamButton = nullptr; UButton* wToSecondTeamButton = nullptr; UButton* wToUndistributedTeamButton = nullptr; UButton* wStartButton = nullptr; UButton* wGameInfoButton = nullptr; UButton* wCloseButton = nullptr; UButton* wEnterButton = nullptr; UScrollBox * wChatScrollBox = nullptr; UMultiLineEditableTextBox* wChatTextBox = nullptr; UFUNCTION() void StartButtonClicked(); UFUNCTION() void GameInfoButtonClicked(); UFUNCTION() void ToFirstTeamClicked(); UFUNCTION() void ToSecondTeamClicked(); UFUNCTION() void ToUndistributedClicked(); UFUNCTION() void CloseButtonClicked(); UFUNCTION() void EnterButtonClicked(); void NewMessage(URichText* richText); void ScrollToEnd(); void AddPlayer(FString player, FString team); void RemovePlayer(FString player, FString team); void RemoveAddPlayer(FString player, FString sourceTeam, FString targetTeam); void TeamSelector(FString team); }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "GameRoomWidgets.h" #include "Runtime/UMG/Public/Components/ScrollBox.h" #include "Runtime/UMG/Public/Components/ScrollBoxSlot.h" #include "Runtime/UMG/Public/Components/Button.h" #include "Runtime/UMG/Public/Components/TextBlock.h" #include "Runtime/UMG/Public/Components/MultiLineEditableTextBox.h" #include "GameRoomUserUnit.h" #include "SpikyGameInstance.h" #include "DifferentMix.h" #include "Protobufs/GameRoomModels.pb.h" #include "GameRoom.h" #include "MessageEncoder.h" #include "MainMenuWidgets.h" #include "CreateRoomWidgets.h" #include "RichText.h" #include "Runtime/Engine/Public/TimerManager.h" void UGameRoomWidgets::NativeConstruct() { Super::NativeConstruct(); wFirstTeamScrollBox = Cast<UScrollBox>(GetWidgetFromName(TEXT("team1"))); wSecondTeamScrollBox = Cast<UScrollBox>(GetWidgetFromName(TEXT("team2"))); wUndistributedTeamScrollBox = Cast<UScrollBox>(GetWidgetFromName(TEXT("undistributed"))); wToFirstTeamButton = Cast<UButton>(GetWidgetFromName(TEXT("ToFTButton"))); wToFirstTeamButton->OnClicked.AddDynamic(this, &UGameRoomWidgets::ToFirstTeamClicked); wToSecondTeamButton = Cast<UButton>(GetWidgetFromName(TEXT("ToSTButton"))); wToSecondTeamButton->OnClicked.AddDynamic(this, &UGameRoomWidgets::ToSecondTeamClicked); wToUndistributedTeamButton = Cast<UButton>(GetWidgetFromName(TEXT("ToUndistributedButton"))); wToUndistributedTeamButton->OnClicked.AddDynamic(this, &UGameRoomWidgets::ToUndistributedClicked); wCloseButton = Cast<UButton>(GetWidgetFromName(TEXT("CloseButton"))); wCloseButton->OnClicked.AddDynamic(this, &UGameRoomWidgets::CloseButtonClicked); wEnterButton = Cast<UButton>(GetWidgetFromName(TEXT("EnterButton"))); wEnterButton->OnClicked.AddDynamic(this, &UGameRoomWidgets::EnterButtonClicked); wChatScrollBox = Cast<UScrollBox>(GetWidgetFromName(TEXT("ChatScrollBox"))); wChatTextBox = Cast<UMultiLineEditableTextBox>(GetWidgetFromName(TEXT("ChatTextBox"))); wStartButton = Cast<UButton>(GetWidgetFromName(TEXT("StartButton"))); wStartButton->OnClicked.AddDynamic(this, &UGameRoomWidgets::StartButtonClicked); wGameInfoButton = Cast<UButton>(GetWidgetFromName(TEXT("GameInfoButton"))); wGameInfoButton->OnClicked.AddDynamic(this, &UGameRoomWidgets::GameInfoButtonClicked); } void UGameRoomWidgets::AddPlayer(FString player, FString team) { UGameRoomUserUnit* button = NewObject<UGameRoomUserUnit>(this, UGameRoomUserUnit::StaticClass()); UTextBlock* textBlock = NewObject<UTextBlock>(this, UTextBlock::StaticClass()); textBlock->SetText(FText::FromString(player)); button->AddChild(textBlock); FSlateBrush emptyBackgroundImage; emptyBackgroundImage.SetResourceObject((UObject*)(USpikyGameInstance::DifferentMix->EmptyImage)); button->WidgetStyle.Normal = emptyBackgroundImage; button->WidgetStyle.Hovered = emptyBackgroundImage; button->WidgetStyle.Pressed = emptyBackgroundImage; UScrollBoxSlot* buttonSlot = nullptr; if (team == "team1") { buttonSlot = Cast<UScrollBoxSlot>(wFirstTeamScrollBox->AddChild(button)); } else if (team == "team2") { buttonSlot = Cast<UScrollBoxSlot>(wSecondTeamScrollBox->AddChild(button)); } else if (team == "undistributed") { buttonSlot = Cast<UScrollBoxSlot>(wUndistributedTeamScrollBox->AddChild(button)); } buttonSlot->SetHorizontalAlignment(HAlign_Fill); } void UGameRoomWidgets::RemovePlayer(FString player, FString team) { UScrollBox* targetScrollBox = nullptr; if (team == "team1") { targetScrollBox = wFirstTeamScrollBox; } else if (team == "team2") { targetScrollBox = wSecondTeamScrollBox; } else if (team == "undistributed") { targetScrollBox = wUndistributedTeamScrollBox; } for (size_t i = 0; i < targetScrollBox->GetChildrenCount(); i++) { UGameRoomUserUnit* button = Cast<UGameRoomUserUnit>(targetScrollBox->GetChildAt(i)); UTextBlock* textBlock = Cast<UTextBlock>(button->GetChildAt(0)); if (textBlock->GetText().ToString() == player) targetScrollBox->RemoveChildAt(i); } //     ,   auto it = UGameRoomUserUnit::select_players.begin(); //       while (it != UGameRoomUserUnit::select_players.end()) { if (it->first == player) { it = UGameRoomUserUnit::select_players.erase(it); } else { ++it; } GLog->Log(FString::FromInt(UGameRoomUserUnit::select_players.size())); } } void UGameRoomWidgets::RemoveAddPlayer(FString player, FString sourceTeam, FString targetTeam) { //    ,      UScrollBox * targetScrollBox = nullptr; if (targetTeam == "team1") targetScrollBox = wFirstTeamScrollBox; if (targetTeam == "team2") targetScrollBox = wSecondTeamScrollBox; if (targetTeam == "undistributed") targetScrollBox = wUndistributedTeamScrollBox; for (size_t i = 0; i < targetScrollBox->GetChildrenCount(); i++) { UGameRoomUserUnit * entity = Cast<UGameRoomUserUnit>(targetScrollBox->GetChildAt(i)); FString str = Cast<UTextBlock>(entity->GetChildAt(0))->GetText().ToString(); for (auto const& e : UGameRoomUserUnit::select_players) if (str == e.first) entity->OnClick(); } if (targetTeam == "team1" && sourceTeam != targetTeam) { RemovePlayer(player, sourceTeam); AddPlayer(player, targetTeam); } if (targetTeam == "team2" && sourceTeam != targetTeam) { RemovePlayer(player, sourceTeam); AddPlayer(player, targetTeam); } if (targetTeam == "undistributed" && sourceTeam != targetTeam) { RemovePlayer(player, sourceTeam); AddPlayer(player, targetTeam); } //     ,   auto it = UGameRoomUserUnit::select_players.begin(); while (it != UGameRoomUserUnit::select_players.end()) { if (it->first == player) { it = UGameRoomUserUnit::select_players.erase(it); } else { ++it; } GLog->Log(FString::FromInt(UGameRoomUserUnit::select_players.size())); } } void UGameRoomWidgets::TeamSelector(FString team) { std::string targetTeam = TCHAR_TO_UTF8(*team); //      std::shared_ptr<RoomDescribe> roomDescribe(new RoomDescribe); for (auto s : UGameRoomUserUnit::select_players) { if (s.second == "undistributed") { TeamPlayer* teamPlayer = roomDescribe->add_undistributed(); teamPlayer->set_player_name(TCHAR_TO_UTF8(*s.first)); } else if (s.second == "team1") { TeamPlayer* teamPlayer = roomDescribe->add_team1(); teamPlayer->set_player_name(TCHAR_TO_UTF8(*s.first)); } else if (s.second == "team2") { TeamPlayer* teamPlayer = roomDescribe->add_team2(); teamPlayer->set_player_name(TCHAR_TO_UTF8(*s.first)); } } std::shared_ptr<RoomUpdate> roomUpdate(new RoomUpdate); roomUpdate->set_allocated_roomdescribe(roomDescribe.get()); roomUpdate->set_targetteam(targetTeam); roomUpdate->set_roomname(UGameRoom::roomName); std::shared_ptr<Room> room(new Room); room->set_allocated_roomupdate(roomUpdate.get()); UMessageEncoder::Send(room.get(), true, true); room->release_roomupdate(); roomUpdate->release_roomdescribe(); } void UGameRoomWidgets::StartButtonClicked() { std::shared_ptr<Room> room(new Room); room->set_startgame(true); room->set_roomname(UGameRoom::roomName); UMessageEncoder::Send(room.get(), true, true); } void UGameRoomWidgets::GameInfoButtonClicked() { // TODO } void UGameRoomWidgets::ToFirstTeamClicked() { TeamSelector("team1"); } void UGameRoomWidgets::ToUndistributedClicked() { TeamSelector("undistributed"); } void UGameRoomWidgets::ToSecondTeamClicked() { TeamSelector("team2"); } void UGameRoomWidgets::CloseButtonClicked() { if (UGameRoom::roomCreator != USpikyGameInstance::userLogin) { //       wFirstTeamScrollBox->ClearChildren(); wSecondTeamScrollBox->ClearChildren(); wUndistributedTeamScrollBox->ClearChildren(); wChatScrollBox->ClearChildren(); //      USpikyGameInstance::DifferentMix->ShowMainMenuScreen(); //    std::shared_ptr<SubscribeRoom> unsubscribe(new SubscribeRoom); unsubscribe->set_subscribe(false); unsubscribe->set_roomname(UGameRoom::roomName); std::shared_ptr<Room> room(new Room); room->set_allocated_subscriberoom(unsubscribe.get()); UMessageEncoder::Send(room.get(), true, true); room->release_subscriberoom(); } else { std::shared_ptr<RoomsListUpdate> update(new RoomsListUpdate); update->set_deleteroom(true); std::shared_ptr<Room> room(new Room); room->set_allocated_roomslistupdate(update.get()); UMessageEncoder::Send(room.get(), true, true); room->release_roomslistupdate(); } } void UGameRoomWidgets::EnterButtonClicked() { if (wChatTextBox->GetText().ToString() == "") return; std::shared_ptr<Room> room(new Room); std::shared_ptr<RoomUpdate> roomUpdate(new RoomUpdate); std::shared_ptr<RoomDescribe> roomDescribe(new RoomDescribe); std::shared_ptr<Chat> chat(new Chat); chat->set_text(TCHAR_TO_UTF8(*wChatTextBox->GetText().ToString())); wChatTextBox->SetText(FText::FromString("")); roomDescribe->set_allocated_chat(chat.get()); roomUpdate->set_allocated_roomdescribe(roomDescribe.get()); roomUpdate->set_roomname(UGameRoom::roomName); room->set_allocated_roomupdate(roomUpdate.get()); UMessageEncoder::Send(room.get(), true, true); room->release_roomupdate(); roomUpdate->release_roomdescribe(); roomDescribe->release_chat(); } void UGameRoomWidgets::NewMessage(URichText* richText) { UScrollBoxSlot* richTextSlot = Cast<UScrollBoxSlot>(wChatScrollBox->AddChild(richText)); richTextSlot->SetPadding(FMargin(10, 5, 10, 5)); wChatScrollBox->ScrollToEnd(); FTimerHandle timerHandle; GetWorld()->GetTimerManager().SetTimer(timerHandle, this, &UGameRoomWidgets::ScrollToEnd, .01f, false); } void UGameRoomWidgets::ScrollToEnd() { wChatScrollBox->ScrollToEnd(); } 


There is no widget that could perform moving lists in Unreal, you have to create your own, here is a description of each function:

 void UGameRoomWidgets::NativeConstruct()      void UGameRoomWidgets::AddPlayer(FString player, FString team)            void UGameRoomWidgets::RemovePlayer(FString player, FString team)     void UGameRoomWidgets::RemoveAddPlayer(FString player, FString sourceTeam, FString targetTeam)           void UGameRoomWidgets::TeamSelector(FString team)          RoomDescribe,        void UGameRoomWidgets::StartButtonClicked()                    void UGameRoomWidgets::CloseButtonClicked()                            void UGameRoomWidgets::EnterButtonClicked()     void UGameRoomWidgets::NewMessage(URichText* richText)      void UGameRoomWidgets::ScrollToEnd()      

Now add a new widget to DifferentMix:

GameRoomWidgets in DifferentMix
 .h class UGameRoomWidgets; UGameRoomWidgets* tmpGameRoomRef; UGameRoomWidgets* wGameRoomWidgets; .cpp #include "GameRoomWidgets.h" ... static ConstructorHelpers::FClassFinder<UGameRoomWidgets> gameRoomWidgets(TEXT("WidgetBlueprint'/Game/Blueprints/Widgets/GameRoom_W.GameRoom_W_C'")); if (gameRoomWidgets.Class != NULL) { tmpGameRoomRef = gameRoomWidgets.Class->GetDefaultObject<UGameRoomWidgets>(); } ... wGameRoomWidgets = CreateWidget<UGameRoomWidgets>(GetWorld(), tmpGameRoomRef->GetClass()); gameRoomSlot = Cast<UCanvasPanelSlot>(wWidgetContainer->wCanvas->AddChild(wGameRoomWidgets)); gameRoomSlot->SetZOrder(1); gameRoomSlot->SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f)); gameRoomSlot->SetOffsets(FMargin(0, 0, 0, 0)); wGameRoomWidgets->SetVisibility(ESlateVisibility::Hidden); 


GameRoom GameRoomWidgets . UGameRoom::Handler() USpikyGameInstance::DifferentMix->ShowGameRoom(); . , . UGameRoomWidgets::CloseButtonClicked() :

 std::shared_ptr<RoomsListUpdate> update(new RoomsListUpdate); update->set_deleteroom(true); 

RoomManager :

 else if(room.hasRoomsListUpdate()) { if(room.getRoomsListUpdate().hasField(deleteRoom_room)) { deleteRoom(ctx); } } 

deleteRoom(ChannelHandlerContext ctx), , , . , RoomManager:

 public static void clearRoomData(ChannelHandlerContext ctx)     -   ,             ,                       private static void roomUnsubscribe(ChannelHandlerContext ctx, GameRoomModels.Room room)           ,     

RoomManager
 /* * Copyright (c) 2017, Vadim Petrov - MIT License */ package com.spiky.server.tcp.logics; import com.google.protobuf.ByteString; import com.spiky.server.protomodels.GameRoomModels; import com.spiky.server.protomodels.MessageModels; import io.netty.channel.ChannelHandlerContext; import java.util.Objects; import static com.spiky.server.ServerMain.*; import static com.spiky.server.utils.Descriptors.deleteRoom_room; public class RoomManager { public RoomManager(ChannelHandlerContext ctx, GameRoomModels.Room room) { if(room.hasCreateRoom()) { createRoom(ctx, room.getCreateRoom()); } else if(room.hasRoomsListUpdate()) { if(room.getRoomsListUpdate().hasField(deleteRoom_room)) { deleteRoom(ctx); } } } private void createRoom(ChannelHandlerContext ctx, GameRoomModels.CreateRoom createRoom) { /*        */ GameRoom gameRoom = new GameRoom(ctx, createRoom); /*       */ if(!gameRooms.containsKey(gameRoom.getCreator())) { gameRooms.put(gameRoom.getCreator(), gameRoom); ctx.channel().attr(ROOM_OWNER).set(ctx.channel().attr(CHANNEL_OWNER).get()); createRoom = createRoom.toBuilder().setRoomName(gameRoom.getRoomName()).setCreator(gameRoom.getCreator()).build(); /*     */ GameRoomModels.Room room = GameRoomModels.Room.newBuilder().setCreateRoom(createRoom).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(room.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); /*      */ roomListUpdateSubscribers.writeAndFlush(wrapper); } } public static void deleteRoom(ChannelHandlerContext ctx) { /*      */ String channel_owner = ctx.channel().attr(CHANNEL_OWNER).get(); if(gameRooms.containsKey(channel_owner)) { GameRoom gameRoom = gameRooms.get(channel_owner); /*        */ if(gameRoom.getGameState()) return; /*     */ gameRooms.remove(channel_owner); /*     */ GameRoomModels.RoomsListUpdate update = GameRoomModels.RoomsListUpdate.newBuilder().setRoomOwner(gameRoom.getCreator()).build(); GameRoomModels.Room room = GameRoomModels.Room.newBuilder().setRoomsListUpdate(update).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(room.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); roomListUpdateSubscribers.writeAndFlush(wrapper); } } public static void clearRoomData(ChannelHandlerContext ctx) { String player = ctx.channel().attr(CHANNEL_OWNER).get(); if(gameRooms.containsKey(player)) { GameRoom gameRoom = gameRooms.get(player); System.out.println(gameRoom.getRoomName()); /*    ,     */ if(!gameRoom.getGameState()) { gameRooms.remove(player); /*     */ GameRoomModels.RoomsListUpdate update = GameRoomModels.RoomsListUpdate.newBuilder().setDeleteRoom(true).setRoomName(gameRoom.getRoomName()).build(); GameRoomModels.Room room = GameRoomModels.Room.newBuilder().setRoomsListUpdate(update).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(room.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); roomListUpdateSubscribers.writeAndFlush(wrapper); } } /*  ,            */ else { for(String owner : gameRooms.keySet()) { if (gameRooms.get(owner).players.containsKey(player)) { /*      */ if(!gameRooms.get(owner).getGameState()) { GameRoom gameRoom = gameRooms.get(owner); GameRoomModels.SubscribeRoom.Builder subscribe = GameRoomModels.SubscribeRoom.newBuilder() .setRoomName(gameRoom.getRoomName()); if(gameRoom.team1.containsKey(player)) { subscribe.setTeam("team1"); } else if (gameRoom.team2.containsKey(player)) { subscribe.setTeam("team2"); } else if (gameRoom.undistributed.containsKey(player)) { subscribe.setTeam("undistributed"); } GameRoomModels.Room room = GameRoomModels.Room.newBuilder().setSubscribeRoom(subscribe.build()).build(); roomUnsubscribe(ctx, room); } } } } } private static void roomUnsubscribe(ChannelHandlerContext ctx, GameRoomModels.Room room) { /*   */ for(String owner : gameRooms.keySet()) { if (Objects.equals(gameRooms.get(owner).getRoomName(), room.getSubscribeRoom().getRoomName())) { GameRoom gameRoom = gameRooms.get(owner); String player = ctx.channel().attr(CHANNEL_OWNER).get(); String team = ""; /*           */ if(!gameRoom.getGameState()) { gameRoom.players.remove(player); if(gameRoom.team1.containsKey(player)) { team = "team1"; gameRoom.team1.remove(player); } else if(gameRoom.team2.containsKey(player)) { team = "team2"; gameRoom.team2.remove(player); } else if(gameRoom.undistributed.containsKey(player)) { team = "undistributed"; gameRoom.undistributed.remove(player); } GameRoomModels.SubscribeRoom subscribe = room.getSubscribeRoom().toBuilder().setPlayer(player).setTeam(team).build(); GameRoomModels.Room out = GameRoomModels.Room.newBuilder().setSubscribeRoom(subscribe).build(); System.out.println(out); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(out.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); gameRoom.recipients.writeAndFlush(wrapper); } } } } } 


, Netty , IdleStateHandler(30, 0, 0), timeout , , , . 30 , .

ServerInitializer, :

ServerInitializer
 public class ServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); /*  */ //pipeline.addLast(new LoggingHandler(LogLevel.INFO)); /*   */ // Decoders protobuf pipeline.addLast(new ProtobufVarint32FrameDecoder()); pipeline.addLast(new ProtobufDecoder(MessageModels.Wrapper.getDefaultInstance())); /*   */ // Encoder protobuf pipeline.addLast(new ProtobufVarint32LengthFieldPrepender()); pipeline.addLast(new ProtobufEncoder()); /* The connection is closed when there is no inbound traffic for N seconds */ pipeline.addLast(new IdleStateHandler(30, 0, 0)); /*    */ pipeline.addLast(new EncryptHandler()); /*    */ pipeline.addLast(new DecryptHandler()); } } 


DecryptHandler:

 private void clearPlayerData(ChannelHandlerContext ctx)     . public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception   public void channelUnregistered(ChannelHandlerContext ctx) throws Exception   public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception -   -        30  

DecryptHandler
 /* * Copyright (c) 2017, Vadim Petrov - MIT License */ package com.spiky.server.tcp.handlers; import com.spiky.server.protomodels.GameRoomModels; import com.spiky.server.protomodels.MainMenuModels; import com.spiky.server.protomodels.MessageModels; import com.spiky.server.protomodels.RegistrationLoginModels; import com.spiky.server.tcp.logics.*; import com.spiky.server.utils.Cryptography; import com.spiky.server.utils.SessionUtil; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageDecoder; import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleStateEvent; import org.hibernate.Session; import org.hibernate.Transaction; import java.util.List; import static com.spiky.server.ServerMain.CRYPTOGRAPHY; import static com.spiky.server.ServerMain.HIBER_SESSION; import static com.spiky.server.ServerMain.HIBER_TRANSACTION; import static com.spiky.server.tcp.logics.RoomManager.clearRoomData; import static com.spiky.server.utils.Descriptors.*; public class DecryptHandler extends MessageToMessageDecoder<MessageModels.Wrapper> { private Session session = new SessionUtil().getSession(); private Transaction transaction = session.beginTransaction(); private Cryptography cryptography = new Cryptography(); /*    */ private boolean bInit = false; /*        */ private void init(ChannelHandlerContext ctx) { if(!bInit) { bInit = true; ctx.channel().attr(HIBER_SESSION).set(session); ctx.channel().attr(HIBER_TRANSACTION).set(transaction); ctx.channel().attr(CRYPTOGRAPHY).set(cryptography); } } @Override protected void decode(ChannelHandlerContext ctx, MessageModels.Wrapper wrapper, List<Object> list) throws Exception { init(ctx); /*     */ if(wrapper.hasCryptogramWrapper()) { if(wrapper.getCryptogramWrapper().hasField(registration_cw)) { byte[] cryptogram = wrapper.getCryptogramWrapper().getRegistration().toByteArray(); byte[] original = cryptography.Decrypt(cryptogram, cryptography.getSecretKey()); RegistrationLoginModels.Registration registration = RegistrationLoginModels.Registration.parseFrom(original); new Registration().saveUser(ctx, registration); } else if (wrapper.getCryptogramWrapper().hasField(login_cw)) { byte[] cryptogram = wrapper.getCryptogramWrapper().getLogin().toByteArray(); byte[] original = cryptography.Decrypt(cryptogram, cryptography.getSecretKey()); RegistrationLoginModels.Login login = RegistrationLoginModels.Login.parseFrom(original); new Login().hasUser(ctx, login); } else if(wrapper.getCryptogramWrapper().hasField(mainMenu_cw)) { byte[] cryptogram = wrapper.getCryptogramWrapper().getMainMenu().toByteArray(); byte[] original = cryptography.Decrypt(cryptogram, cryptography.getSecretKey()); MainMenuModels.MainMenu mainMenu = MainMenuModels.MainMenu.parseFrom(original); new MainMenu(ctx, mainMenu); } else if(wrapper.getCryptogramWrapper().hasField(room_cw)) { byte[] cryptogram = wrapper.getCryptogramWrapper().getRoom().toByteArray(); byte[] original = cryptography.Decrypt(cryptogram, cryptography.getSecretKey()); GameRoomModels.Room room = GameRoomModels.Room.parseFrom(original); new RoomManager(ctx, room); } } else if(wrapper.hasInputChecking()) { new InputChecking(ctx, wrapper); } else if(wrapper.hasRegistration()) { new Registration(ctx, wrapper.getRegistration()); } else if(wrapper.hasLogin()) { new Login(ctx, wrapper.getLogin()); } } private void clearPlayerData(ChannelHandlerContext ctx) { if(session.isOpen()) session.close(); clearRoomData(ctx); ctx.close(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); clearPlayerData(ctx); } @Override public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { super.channelUnregistered(ctx); System.out.println("Close connection id: " + ctx.channel().id().asShortText() + " cause ChannelUnregistered"); clearPlayerData(ctx); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof IdleStateEvent) { IdleStateEvent e = (IdleStateEvent) evt; if (e.state() == IdleState.READER_IDLE) { System.out.println("Close connection id: " + ctx.channel().id().asShortText() + " cause READER_IDLE"); clearPlayerData(ctx); } } } } 


:

 /*       */ void UGameRoom::DeleteRoom(RoomsListUpdate update)               /* -      ,       ,       */ void UGameRoom::DeleteRoom(std::string name)                                      

UGameRoom::DeleteRoom
 #include "GameRoomWidgets.h" #include "GameRoomUserUnit.h" #include "CreateRoomWidgets.h" void UGameRoom::DeleteRoom(RoomsListUpdate update) { //     for (size_t i = 0; i < USpikyGameInstance::DifferentMix->wMainMenuWidgets->wRoomsScrollBox->GetChildrenCount(); i++) { URoomListUnit* listUnit = Cast<URoomListUnit>(USpikyGameInstance::DifferentMix->wMainMenuWidgets->wRoomsScrollBox->GetChildAt(i)); UHorizontalBox* horBox = Cast<UHorizontalBox>(listUnit->GetChildAt(0)); UTextBlock* wCreatorName = Cast<UTextBlock>(horBox->GetChildAt(2)); if (wCreatorName->GetText().ToString() == update.roomowner().c_str()) { USpikyGameInstance::DifferentMix->wMainMenuWidgets->wRoomsScrollBox->RemoveChildAt(i); } } //           if (roomCreator == update.roomowner().c_str()) { //       USpikyGameInstance::DifferentMix->wGameRoomWidgets->wFirstTeamScrollBox->ClearChildren(); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wSecondTeamScrollBox->ClearChildren(); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wUndistributedTeamScrollBox->ClearChildren(); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wChatScrollBox->ClearChildren(); UGameRoomUserUnit::select_players.clear(); roomCreator = ""; roomName = ""; USpikyGameInstance::DifferentMix->wMainMenuWidgets->SetVisibility(ESlateVisibility::Visible); USpikyGameInstance::DifferentMix->wCreateRoomWidgets->SetVisibility(ESlateVisibility::Hidden); USpikyGameInstance::DifferentMix->wGameRoomWidgets->SetVisibility(ESlateVisibility::Hidden); } } void UGameRoom::DeleteRoom(std::string name) { //     for (size_t i = 0; i < USpikyGameInstance::DifferentMix->wMainMenuWidgets->wRoomsScrollBox->GetChildrenCount(); i++) { URoomListUnit* listUnit = Cast<URoomListUnit>(USpikyGameInstance::DifferentMix->wMainMenuWidgets->wRoomsScrollBox->GetChildAt(i)); UHorizontalBox* horBox = Cast<UHorizontalBox>(listUnit->GetChildAt(0)); UTextBlock* wCreatorName = Cast<UTextBlock>(horBox->GetChildAt(2)); if (wCreatorName->GetText().ToString() == name.c_str()) { USpikyGameInstance::DifferentMix->wMainMenuWidgets->wRoomsScrollBox->RemoveChildAt(i); } } //       if (roomCreator == name) { //         for (size_t j = 0; j < USpikyGameInstance::DifferentMix->wGameRoomWidgets->wUndistributedTeamScrollBox->GetChildrenCount(); j++) { UGameRoomUserUnit * entity = Cast<UGameRoomUserUnit>(USpikyGameInstance::DifferentMix->wGameRoomWidgets->wUndistributedTeamScrollBox->GetChildAt(j)); FString playerName = Cast<UTextBlock>(entity->GetChildAt(0))->GetText().ToString(); //           if (playerName == USpikyGameInstance::userLogin.c_str() && playerName != roomCreator.c_str()) { //       USpikyGameInstance::DifferentMix->wGameRoomWidgets->wFirstTeamScrollBox->ClearChildren(); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wSecondTeamScrollBox->ClearChildren(); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wUndistributedTeamScrollBox->ClearChildren(); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wChatScrollBox->ClearChildren(); UGameRoomUserUnit::select_players.clear(); roomCreator = ""; roomName = ""; USpikyGameInstance::DifferentMix->wMainMenuWidgets->SetVisibility(ESlateVisibility::Visible); USpikyGameInstance::DifferentMix->wCreateRoomWidgets->SetVisibility(ESlateVisibility::Hidden); USpikyGameInstance::DifferentMix->wGameRoomWidgets->SetVisibility(ESlateVisibility::Hidden); } } } } 


 void UGameRoom::Handler(Room room) { … else if (room.has_roomslistupdate()) { if (!room.startgame()) { //    ,      DeleteRoom(room.roomslistupdate()); } else { DeleteRoom(room.roomslistupdate().roomowner()); } } } 

, , . UGameRoomWidgets::CloseButtonClicked().

URoomListUnit::OnClick() . . RoomManager :

 else if(room.hasSubscribeRoom()) { if(room.getSubscribeRoom().getSubscribe()) { roomSubscribe(ctx, room); } else { roomUnsubscribe(ctx, room); } } 

:

 private void roomSubscribe(ChannelHandlerContext ctx, GameRoomModels.Room room)                    ,               ,  ,          updateRoom(ctx, gameRoom , player); private void updateRoom(ChannelHandlerContext ctx, GameRoom gameRoom, String player)         ,          ChannelMatchers.isNot /*    */ gameRoom.recipients.writeAndFlush(wrapper, ChannelMatchers.isNot(ctx.channel())); 

:

 else if(room.hasRoomUpdate()) { /*    */ if(room.getRoomUpdate().getRoomDescribe().hasChat()) { //chatHandler(ctx, room.getRoomUpdate()); } else { updateRoom(ctx, room.getRoomUpdate()); } } 

:

 private void updateRoom(ChannelHandlerContext ctx, GameRoomModels.RoomUpdate roomUpdate)    ,   ,     

RoomManager
 /* * Copyright (c) 2017, Vadim Petrov - MIT License */ package com.spiky.server.tcp.logics; import com.google.protobuf.ByteString; import com.spiky.server.protomodels.GameRoomModels; import com.spiky.server.protomodels.MainMenuModels; import com.spiky.server.protomodels.MessageModels; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.group.ChannelMatchers; import java.util.HashMap; import java.util.Map; import java.util.Objects; import static com.spiky.server.ServerMain.*; import static com.spiky.server.utils.Descriptors.deleteRoom_room; public class RoomManager { public RoomManager(ChannelHandlerContext ctx, GameRoomModels.Room room) { if(room.hasCreateRoom()) { createRoom(ctx, room.getCreateRoom()); } else if(room.hasRoomsListUpdate()) { if(room.getRoomsListUpdate().hasField(deleteRoom_room)) { deleteRoom(ctx); } } else if(room.hasSubscribeRoom()) { if(room.getSubscribeRoom().getSubscribe()) { roomSubscribe(ctx, room); } else { roomUnsubscribe(ctx, room); } } else if(room.hasRoomUpdate()) { /*    */ if(room.getRoomUpdate().getRoomDescribe().hasChat()) { //chatHandler(ctx, room.getRoomUpdate()); } else { updateRoom(ctx, room.getRoomUpdate()); } } } private void createRoom(ChannelHandlerContext ctx, GameRoomModels.CreateRoom createRoom) { /*        */ GameRoom gameRoom = new GameRoom(ctx, createRoom); /*       */ if(!gameRooms.containsKey(gameRoom.getCreator())) { gameRooms.put(gameRoom.getCreator(), gameRoom); ctx.channel().attr(ROOM_OWNER).set(ctx.channel().attr(CHANNEL_OWNER).get()); createRoom = createRoom.toBuilder().setRoomName(gameRoom.getRoomName()).setCreator(gameRoom.getCreator()).build(); /*     */ GameRoomModels.Room room = GameRoomModels.Room.newBuilder().setCreateRoom(createRoom).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(room.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); /*      */ roomListUpdateSubscribers.writeAndFlush(wrapper); } } public static void deleteRoom(ChannelHandlerContext ctx) { /*      */ String channel_owner = ctx.channel().attr(CHANNEL_OWNER).get(); if(gameRooms.containsKey(channel_owner)) { GameRoom gameRoom = gameRooms.get(channel_owner); /*        */ if(gameRoom.getGameState()) return; /*     */ gameRooms.remove(channel_owner); /*     */ GameRoomModels.RoomsListUpdate update = GameRoomModels.RoomsListUpdate.newBuilder().setRoomOwner(gameRoom.getCreator()).build(); GameRoomModels.Room room = GameRoomModels.Room.newBuilder().setRoomsListUpdate(update).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(room.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); roomListUpdateSubscribers.writeAndFlush(wrapper); } } public static void clearRoomData(ChannelHandlerContext ctx) { String player = ctx.channel().attr(CHANNEL_OWNER).get(); if(gameRooms.containsKey(player)) { GameRoom gameRoom = gameRooms.get(player); System.out.println(gameRoom.getRoomName()); /*    ,     */ if(!gameRoom.getGameState()) { gameRooms.remove(player); /*     */ GameRoomModels.RoomsListUpdate update = GameRoomModels.RoomsListUpdate.newBuilder().setDeleteRoom(true).setRoomOwner(gameRoom.getCreator()).build(); GameRoomModels.Room room = GameRoomModels.Room.newBuilder().setRoomsListUpdate(update).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(room.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); roomListUpdateSubscribers.writeAndFlush(wrapper); } } /*  ,            */ else { for(String owner : gameRooms.keySet()) { if (gameRooms.get(owner).players.containsKey(player)) { /*      */ if(!gameRooms.get(owner).getGameState()) { GameRoom gameRoom = gameRooms.get(owner); GameRoomModels.SubscribeRoom.Builder subscribe = GameRoomModels.SubscribeRoom.newBuilder() .setRoomName(gameRoom.getRoomName()); if(gameRoom.team1.containsKey(player)) { subscribe.setTeam("team1"); } else if (gameRoom.team2.containsKey(player)) { subscribe.setTeam("team2"); } else if (gameRoom.undistributed.containsKey(player)) { subscribe.setTeam("undistributed"); } GameRoomModels.Room room = GameRoomModels.Room.newBuilder().setSubscribeRoom(subscribe.build()).build(); roomUnsubscribe(ctx, room); } } } } } private void roomSubscribe(ChannelHandlerContext ctx, GameRoomModels.Room room) { /*   */ for(String owner : gameRooms.keySet()) { if(Objects.equals(gameRooms.get(owner).getRoomName(), room.getSubscribeRoom().getRoomName())) { GameRoom gameRoom = gameRooms.get(owner); /*       */ if(gameRoom.players.size() > gameRoom.getMaxPlayers()) { GameRoomModels.SubscribeRoom subscribe = GameRoomModels.SubscribeRoom.newBuilder().setStateCode(1).build(); GameRoomModels.Room out = GameRoomModels.Room.newBuilder().setSubscribeRoom(subscribe).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(out.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); ctx.writeAndFlush(wrapper); return; } String player = ctx.channel().attr(CHANNEL_OWNER).get(); /* ,         */ if(!gameRoom.players.containsKey(player)) { gameRoom.addPlayer(ctx, player); } else { GameRoomModels.SubscribeRoom subscribe = GameRoomModels.SubscribeRoom.newBuilder().setStateCode(2).build(); GameRoomModels.Room out = GameRoomModels.Room.newBuilder().setSubscribeRoom(subscribe).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(out.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); ctx.writeAndFlush(wrapper); return; } /*       */ GameRoomModels.RoomDescribe.Builder builder = GameRoomModels.RoomDescribe.newBuilder(); for (String p : gameRoom.undistributed.keySet()) { GameRoomModels.TeamPlayer undistributed = GameRoomModels.TeamPlayer.newBuilder() .setPlayerName(p).build(); builder.addUndistributed(undistributed); } for (String p : gameRoom.team1.keySet()) { GameRoomModels.TeamPlayer team1 = GameRoomModels.TeamPlayer.newBuilder() .setPlayerName(p).build(); builder.addTeam1(team1); } for (String p : gameRoom.team2.keySet()) { GameRoomModels.TeamPlayer team2 = GameRoomModels.TeamPlayer.newBuilder() .setPlayerName(p).build(); builder.addTeam2(team2); } GameRoomModels.RoomDescribe roomDescribe = builder.setRoomName(gameRoom.getRoomName()) .setMapName(gameRoom.getMapName()) .setGameTime(gameRoom.getGameTime() + " minutes") .setMaxPlayers(gameRoom.getMaxPlayers() + " players") .setCreator(gameRoom.getCreator()) .build(); MainMenuModels.Chat.Builder chatBuilder = MainMenuModels.Chat.newBuilder(); /*    100  */ for (int i = 0; i < gameRoom.syncMessageList.size(); i++) { MainMenuModels.ChatMessage message = MainMenuModels.ChatMessage.newBuilder() .setTime(gameRoom.syncMessageList.get(i).getTime()) .setName(gameRoom.syncMessageList.get(i).getName()) .setText(gameRoom.syncMessageList.get(i).getText()) .build(); chatBuilder.addMessages(message); } roomDescribe = roomDescribe.toBuilder().setChat(chatBuilder.build()).build(); System.out.println(roomDescribe); GameRoomModels.SubscribeRoom subscribe = GameRoomModels.SubscribeRoom.newBuilder().setRoomDescribe(roomDescribe).build(); GameRoomModels.Room out = GameRoomModels.Room.newBuilder().setSubscribeRoom(subscribe).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(out.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); ctx.writeAndFlush(wrapper); /*      */ updateRoom(ctx, gameRoom , player); ctx.channel().attr(ROOM_OWNER).set(gameRoom.getCreator()); } } } private void updateRoom(ChannelHandlerContext ctx, GameRoomModels.RoomUpdate roomUpdate) { /*   */ for(String owner : gameRooms.keySet()) { if (Objects.equals(gameRooms.get(owner).getRoomName(), roomUpdate.getRoomName())) { GameRoom gameRoom = gameRooms.get(owner); String channel_owner = ctx.channel().attr(CHANNEL_OWNER).get(); /*        */ if(Objects.equals(channel_owner, gameRoom.getCreator())) { GameRoomModels.RoomDescribe roomDescribe = roomUpdate.getRoomDescribe(); Map<String,Channel> list = new HashMap<>(); /*     */ for (GameRoomModels.TeamPlayer p : roomDescribe.getTeam1List()) { list.put(p.getPlayerName(), gameRoom.team1.get(p.getPlayerName())); gameRoom.team1.remove(p.getPlayerName()); } for (GameRoomModels.TeamPlayer p : roomDescribe.getTeam2List()) { list.put(p.getPlayerName(), gameRoom.team2.get(p.getPlayerName())); gameRoom.team2.remove(p.getPlayerName()); } for (GameRoomModels.TeamPlayer p : roomDescribe.getUndistributedList()) { list.put(p.getPlayerName(), gameRoom.undistributed.get(p.getPlayerName())); gameRoom.undistributed.remove(p.getPlayerName()); } /*    */ if(Objects.equals(roomUpdate.getTargetTeam(), "team1")) { gameRoom.team1.putAll(list); } else if(Objects.equals(roomUpdate.getTargetTeam(), "team2")) { gameRoom.team2.putAll(list); } else if(Objects.equals(roomUpdate.getTargetTeam(), "undistributed")) { gameRoom.undistributed.putAll(list); } /*     */ GameRoomModels.Room out = GameRoomModels.Room.newBuilder().setRoomUpdate(roomUpdate).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(out.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); gameRoom.recipients.writeAndFlush(wrapper); } } } } private void updateRoom(ChannelHandlerContext ctx, GameRoom gameRoom, String player) { /*          */ GameRoomModels.SubscribeRoom subscribe = GameRoomModels.SubscribeRoom.newBuilder().setPlayer(player).build(); GameRoomModels.Room out = GameRoomModels.Room.newBuilder().setSubscribeRoom(subscribe).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(out.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); /*    */ gameRoom.recipients.writeAndFlush(wrapper, ChannelMatchers.isNot(ctx.channel())); } private static void roomUnsubscribe(ChannelHandlerContext ctx, GameRoomModels.Room room) { /*   */ for(String owner : gameRooms.keySet()) { if (Objects.equals(gameRooms.get(owner).getRoomName(), room.getSubscribeRoom().getRoomName())) { GameRoom gameRoom = gameRooms.get(owner); String player = ctx.channel().attr(CHANNEL_OWNER).get(); String team = ""; /*           */ if(!gameRoom.getGameState()) { gameRoom.players.remove(player); if(gameRoom.team1.containsKey(player)) { team = "team1"; gameRoom.team1.remove(player); } else if(gameRoom.team2.containsKey(player)) { team = "team2"; gameRoom.team2.remove(player); } else if(gameRoom.undistributed.containsKey(player)) { team = "undistributed"; gameRoom.undistributed.remove(player); } GameRoomModels.SubscribeRoom subscribe = room.getSubscribeRoom().toBuilder().setPlayer(player).setTeam(team).build(); GameRoomModels.Room out = GameRoomModels.Room.newBuilder().setSubscribeRoom(subscribe).build(); System.out.println(out); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(out.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); gameRoom.recipients.writeAndFlush(wrapper); } } } } } 


UGameRoom::Handler(Room room):

 ... else if (room.has_subscriberoom()) { Subscribe(room.subscriberoom()); } else if (room.has_roomupdate()) { if (room.roomupdate().roomdescribe().GetReflection()->HasField(room.roomupdate().roomdescribe(), Descriptors::chat_room)) { //UChats * chats = NewObject<UChats>(UChats::StaticClass()); //chats->Handler(room.roomupdate().roomdescribe().chat(), "gameroom"); } else { UpdateRoom(room.roomupdate()); } } 

void UGameRoom::Subscribe(SubscribeRoom subscribe), , , . .
UGameRoom::UpdateRoom(RoomUpdate update), - , -.

, . RoomManager chatHandler(ctx, room.getRoomUpdate()); chatHandler():

RoomManager
 /* * Copyright (c) 2017, Vadim Petrov - MIT License */ package com.spiky.server.tcp.logics; import com.google.protobuf.ByteString; import com.spiky.server.protomodels.GameRoomModels; import com.spiky.server.protomodels.MainMenuModels; import com.spiky.server.protomodels.MessageModels; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.group.ChannelMatchers; import java.util.Calendar; import java.util.HashMap; import java.util.Map; import java.util.Objects; import static com.spiky.server.ServerMain.*; import static com.spiky.server.utils.Descriptors.deleteRoom_room; public class RoomManager { public RoomManager(ChannelHandlerContext ctx, GameRoomModels.Room room) { if(room.hasCreateRoom()) { createRoom(ctx, room.getCreateRoom()); } else if(room.hasRoomsListUpdate()) { if(room.getRoomsListUpdate().hasField(deleteRoom_room)) { deleteRoom(ctx); } } else if(room.hasSubscribeRoom()) { if(room.getSubscribeRoom().getSubscribe()) { roomSubscribe(ctx, room); } else { roomUnsubscribe(ctx, room); } } else if(room.hasRoomUpdate()) { /*    */ if(room.getRoomUpdate().getRoomDescribe().hasChat()) { chatHandler(ctx, room.getRoomUpdate()); } else { updateRoom(ctx, room.getRoomUpdate()); } } } private void createRoom(ChannelHandlerContext ctx, GameRoomModels.CreateRoom createRoom) { /*        */ GameRoom gameRoom = new GameRoom(ctx, createRoom); /*       */ if(!gameRooms.containsKey(gameRoom.getCreator())) { gameRooms.put(gameRoom.getCreator(), gameRoom); ctx.channel().attr(ROOM_OWNER).set(ctx.channel().attr(CHANNEL_OWNER).get()); createRoom = createRoom.toBuilder().setRoomName(gameRoom.getRoomName()).setCreator(gameRoom.getCreator()).build(); /*     */ GameRoomModels.Room room = GameRoomModels.Room.newBuilder().setCreateRoom(createRoom).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(room.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); /*      */ roomListUpdateSubscribers.writeAndFlush(wrapper); } } public static void deleteRoom(ChannelHandlerContext ctx) { /*      */ String channel_owner = ctx.channel().attr(CHANNEL_OWNER).get(); if(gameRooms.containsKey(channel_owner)) { GameRoom gameRoom = gameRooms.get(channel_owner); /*        */ if(gameRoom.getGameState()) return; /*     */ gameRooms.remove(channel_owner); /*     */ GameRoomModels.RoomsListUpdate update = GameRoomModels.RoomsListUpdate.newBuilder().setRoomOwner(gameRoom.getCreator()).build(); GameRoomModels.Room room = GameRoomModels.Room.newBuilder().setRoomsListUpdate(update).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(room.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); roomListUpdateSubscribers.writeAndFlush(wrapper); } } public static void clearRoomData(ChannelHandlerContext ctx) { String player = ctx.channel().attr(CHANNEL_OWNER).get(); if(gameRooms.containsKey(player)) { GameRoom gameRoom = gameRooms.get(player); System.out.println(gameRoom.getRoomName()); /*    ,     */ if(!gameRoom.getGameState()) { gameRooms.remove(player); /*     */ GameRoomModels.RoomsListUpdate update = GameRoomModels.RoomsListUpdate.newBuilder().setDeleteRoom(true).setRoomName(gameRoom.getRoomName()).build(); GameRoomModels.Room room = GameRoomModels.Room.newBuilder().setRoomsListUpdate(update).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(room.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); roomListUpdateSubscribers.writeAndFlush(wrapper); } } /*  ,            */ else { for(String owner : gameRooms.keySet()) { if (gameRooms.get(owner).players.containsKey(player)) { /*      */ if(!gameRooms.get(owner).getGameState()) { GameRoom gameRoom = gameRooms.get(owner); GameRoomModels.SubscribeRoom.Builder subscribe = GameRoomModels.SubscribeRoom.newBuilder() .setRoomName(gameRoom.getRoomName()); if(gameRoom.team1.containsKey(player)) { subscribe.setTeam("team1"); } else if (gameRoom.team2.containsKey(player)) { subscribe.setTeam("team2"); } else if (gameRoom.undistributed.containsKey(player)) { subscribe.setTeam("undistributed"); } GameRoomModels.Room room = GameRoomModels.Room.newBuilder().setSubscribeRoom(subscribe.build()).build(); roomUnsubscribe(ctx, room); } } } } } private void roomSubscribe(ChannelHandlerContext ctx, GameRoomModels.Room room) { /*   */ for(String owner : gameRooms.keySet()) { if(Objects.equals(gameRooms.get(owner).getRoomName(), room.getSubscribeRoom().getRoomName())) { GameRoom gameRoom = gameRooms.get(owner); /*       */ if(gameRoom.players.size() > gameRoom.getMaxPlayers()) { GameRoomModels.SubscribeRoom subscribe = GameRoomModels.SubscribeRoom.newBuilder().setStateCode(1).build(); GameRoomModels.Room out = GameRoomModels.Room.newBuilder().setSubscribeRoom(subscribe).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(out.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); ctx.writeAndFlush(wrapper); return; } String player = ctx.channel().attr(CHANNEL_OWNER).get(); /* ,         */ if(!gameRoom.players.containsKey(player)) { gameRoom.addPlayer(ctx, player); } else { GameRoomModels.SubscribeRoom subscribe = GameRoomModels.SubscribeRoom.newBuilder().setStateCode(2).build(); GameRoomModels.Room out = GameRoomModels.Room.newBuilder().setSubscribeRoom(subscribe).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(out.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); ctx.writeAndFlush(wrapper); return; } /*       */ GameRoomModels.RoomDescribe.Builder builder = GameRoomModels.RoomDescribe.newBuilder(); for (String p : gameRoom.undistributed.keySet()) { GameRoomModels.TeamPlayer undistributed = GameRoomModels.TeamPlayer.newBuilder() .setPlayerName(p).build(); builder.addUndistributed(undistributed); } for (String p : gameRoom.team1.keySet()) { GameRoomModels.TeamPlayer team1 = GameRoomModels.TeamPlayer.newBuilder() .setPlayerName(p).build(); builder.addTeam1(team1); } for (String p : gameRoom.team2.keySet()) { GameRoomModels.TeamPlayer team2 = GameRoomModels.TeamPlayer.newBuilder() .setPlayerName(p).build(); builder.addTeam2(team2); } GameRoomModels.RoomDescribe roomDescribe = builder.setRoomName(gameRoom.getRoomName()) .setMapName(gameRoom.getMapName()) .setGameTime(gameRoom.getGameTime() + " minutes") .setMaxPlayers(gameRoom.getMaxPlayers() + " players") .setCreator(gameRoom.getCreator()) .build(); MainMenuModels.Chat.Builder chatBuilder = MainMenuModels.Chat.newBuilder(); /*    100  */ for (int i = 0; i < gameRoom.syncMessageList.size(); i++) { MainMenuModels.ChatMessage message = MainMenuModels.ChatMessage.newBuilder() .setTime(gameRoom.syncMessageList.get(i).getTime()) .setName(gameRoom.syncMessageList.get(i).getName()) .setText(gameRoom.syncMessageList.get(i).getText()) .build(); chatBuilder.addMessages(message); } roomDescribe = roomDescribe.toBuilder().setChat(chatBuilder.build()).build(); System.out.println(roomDescribe); GameRoomModels.SubscribeRoom subscribe = GameRoomModels.SubscribeRoom.newBuilder().setRoomDescribe(roomDescribe).build(); GameRoomModels.Room out = GameRoomModels.Room.newBuilder().setSubscribeRoom(subscribe).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(out.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); ctx.writeAndFlush(wrapper); /*      */ updateRoom(ctx, gameRoom , player); ctx.channel().attr(ROOM_OWNER).set(gameRoom.getCreator()); } } } private void updateRoom(ChannelHandlerContext ctx, GameRoomModels.RoomUpdate roomUpdate) { /*   */ for(String owner : gameRooms.keySet()) { if (Objects.equals(gameRooms.get(owner).getRoomName(), roomUpdate.getRoomName())) { GameRoom gameRoom = gameRooms.get(owner); String channel_owner = ctx.channel().attr(CHANNEL_OWNER).get(); /*        */ if(Objects.equals(channel_owner, gameRoom.getCreator())) { GameRoomModels.RoomDescribe roomDescribe = roomUpdate.getRoomDescribe(); Map<String,Channel> list = new HashMap<>(); /*     */ for (GameRoomModels.TeamPlayer p : roomDescribe.getTeam1List()) { list.put(p.getPlayerName(), gameRoom.team1.get(p.getPlayerName())); gameRoom.team1.remove(p.getPlayerName()); } for (GameRoomModels.TeamPlayer p : roomDescribe.getTeam2List()) { list.put(p.getPlayerName(), gameRoom.team2.get(p.getPlayerName())); gameRoom.team2.remove(p.getPlayerName()); } for (GameRoomModels.TeamPlayer p : roomDescribe.getUndistributedList()) { list.put(p.getPlayerName(), gameRoom.undistributed.get(p.getPlayerName())); gameRoom.undistributed.remove(p.getPlayerName()); } /*    */ if(Objects.equals(roomUpdate.getTargetTeam(), "team1")) { gameRoom.team1.putAll(list); } else if(Objects.equals(roomUpdate.getTargetTeam(), "team2")) { gameRoom.team2.putAll(list); } else if(Objects.equals(roomUpdate.getTargetTeam(), "undistributed")) { gameRoom.undistributed.putAll(list); } /*     */ GameRoomModels.Room out = GameRoomModels.Room.newBuilder().setRoomUpdate(roomUpdate).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(out.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); gameRoom.recipients.writeAndFlush(wrapper); } } } } private void updateRoom(ChannelHandlerContext ctx, GameRoom gameRoom, String player) { /*          */ GameRoomModels.SubscribeRoom subscribe = GameRoomModels.SubscribeRoom.newBuilder().setPlayer(player).build(); GameRoomModels.Room out = GameRoomModels.Room.newBuilder().setSubscribeRoom(subscribe).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(out.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); /*    */ gameRoom.recipients.writeAndFlush(wrapper, ChannelMatchers.isNot(ctx.channel())); } private static void roomUnsubscribe(ChannelHandlerContext ctx, GameRoomModels.Room room) { /*   */ for(String owner : gameRooms.keySet()) { if (Objects.equals(gameRooms.get(owner).getRoomName(), room.getSubscribeRoom().getRoomName())) { GameRoom gameRoom = gameRooms.get(owner); String player = ctx.channel().attr(CHANNEL_OWNER).get(); String team = ""; /*           */ if(!gameRoom.getGameState()) { gameRoom.players.remove(player); if(gameRoom.team1.containsKey(player)) { team = "team1"; gameRoom.team1.remove(player); } else if(gameRoom.team2.containsKey(player)) { team = "team2"; gameRoom.team2.remove(player); } else if(gameRoom.undistributed.containsKey(player)) { team = "undistributed"; gameRoom.undistributed.remove(player); } GameRoomModels.SubscribeRoom subscribe = room.getSubscribeRoom().toBuilder().setPlayer(player).setTeam(team).build(); GameRoomModels.Room out = GameRoomModels.Room.newBuilder().setSubscribeRoom(subscribe).build(); System.out.println(out); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(out.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); gameRoom.recipients.writeAndFlush(wrapper); } } } } private void chatHandler(ChannelHandlerContext ctx, GameRoomModels.RoomUpdate roomUpdate) { /*   */ for (String owner : gameRooms.keySet()) { if (Objects.equals(gameRooms.get(owner).getRoomName(), roomUpdate.getRoomName())) { GameRoom gameRoom = gameRooms.get(owner); String channel_owner = ctx.channel().attr(CHANNEL_OWNER).get(); if (gameRoom.players.containsKey(channel_owner)) { Calendar c = Calendar.getInstance(); long ms = c.get(Calendar.HOUR_OF_DAY) * 60 * 60 * 1000 + c.get(Calendar.MINUTE) * 60 * 1000 + c.get(Calendar.SECOND) * 1000; MainMenuModels.Chat chat = roomUpdate.getRoomDescribe().getChat(); String str = chat.getText(); chat = MainMenuModels.Chat.newBuilder().clear() .setName(ctx.channel().attr(CHANNEL_OWNER).get()) .setText(str) .setTime(ms) .build(); /*     */ gameRoom.syncMessageList.add(chat); GameRoomModels.RoomUpdate update = GameRoomModels.RoomUpdate.newBuilder().setRoomDescribe( GameRoomModels.RoomDescribe.newBuilder().setChat(chat).build()).build(); GameRoomModels.Room room = GameRoomModels.Room.newBuilder().setRoomUpdate(update).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(room.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); gameRoom.recipients.writeAndFlush(wrapper); } } } } } 


UGameRoom::Handler:

 ... else if (room.has_roomupdate()) { if (room.roomupdate().roomdescribe().GetReflection()->HasField(room.roomupdate().roomdescribe(), Descriptors::chat_room)) { UChats * chats = NewObject<UChats>(UChats::StaticClass()); chats->Handler(room.roomupdate().roomdescribe().chat(), "gameroom"); } else { UpdateRoom(room.roomupdate()); } } 

UChats::Handler type == «gameroom», :

GameRoom
 // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "GameRoom.h" #include "Descriptors.h" #include "Protobufs/GameRoomModels.pb.h" #include "SpikyGameInstance.h" #include "DifferentMix.h" #include "MainMenuWidgets.h" #include "Runtime/UMG/Public/Components/HorizontalBox.h" #include "Runtime/UMG/Public/Components/HorizontalBoxSlot.h" #include "Runtime/UMG/Public/Components/TextBlock.h" #include "Runtime/UMG/Public/Components/ButtonSlot.h" #include "Runtime/UMG/Public/Components/ScrollBox.h" #include "RoomListUnit.h" #include "GameRoomWidgets.h" #include "GameRoomUserUnit.h" #include "CreateRoomWidgets.h" std::string UGameRoom::roomCreator = ""; std::string UGameRoom::roomName = ""; void UGameRoom::Handler(Room room) { if (room.has_createroom()) { USpikyGameInstance::DifferentMix->wMainMenuWidgets->AddRoom(NewRoom(room.createroom())); //      ,         if (room.createroom().creator() == USpikyGameInstance::userLogin) { roomCreator = room.createroom().creator(); roomName = room.createroom().roomname(); USpikyGameInstance::DifferentMix->ShowGameRoom(); } } else if (room.has_roomslistupdate()) { if (!room.startgame()) { //    ,      DeleteRoom(room.roomslistupdate()); } else { DeleteRoom(room.roomslistupdate().roomowner()); } } else if (room.has_subscriberoom()) { Subscribe(room.subscriberoom()); } else if (room.has_roomupdate()) { if (room.roomupdate().roomdescribe().GetReflection()->HasField(room.roomupdate().roomdescribe(), Descriptors::chat_room)) { UChats * chats = NewObject<UChats>(UChats::StaticClass()); chats->Handler(room.roomupdate().roomdescribe().chat(), "gameroom"); } else { UpdateRoom(room.roomupdate()); } } } void UGameRoom::Subscribe(SubscribeRoom subscribe) { if (subscribe.statecode() == 1) { GLog->Log("The room is full"); } else if (subscribe.statecode() == 2) { GLog->Log("The player is already in the room"); } else if (subscribe.has_roomdescribe()) { //    roomCreator = subscribe.roomdescribe().creator(); roomName = subscribe.roomdescribe().roomname(); //      USpikyGameInstance::DifferentMix->wGameRoomWidgets->wFirstTeamScrollBox->ClearChildren(); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wSecondTeamScrollBox->ClearChildren(); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wUndistributedTeamScrollBox->ClearChildren(); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wChatScrollBox->ClearChildren(); for (TeamPlayer e : subscribe.roomdescribe().team1()) { USpikyGameInstance::DifferentMix->wGameRoomWidgets->AddPlayer(e.player_name().c_str(), "team1"); } for (TeamPlayer e : subscribe.roomdescribe().team2()) { USpikyGameInstance::DifferentMix->wGameRoomWidgets->AddPlayer(e.player_name().c_str(), "team2"); } for (TeamPlayer e : subscribe.roomdescribe().undistributed()) { USpikyGameInstance::DifferentMix->wGameRoomWidgets->AddPlayer(e.player_name().c_str(), "undistributed"); } UChats * chats = NewObject<UChats>(UChats::StaticClass()); chats->Handler(subscribe.roomdescribe().chat(), "gameroom"); //   USpikyGameInstance::DifferentMix->wMainMenuWidgets->SetVisibility(ESlateVisibility::Hidden); USpikyGameInstance::DifferentMix->wCreateRoomWidgets->SetVisibility(ESlateVisibility::Hidden); USpikyGameInstance::DifferentMix->wMainMenuChatWidgets->SetVisibility(ESlateVisibility::Hidden); USpikyGameInstance::DifferentMix->wMainMenuWidgets->bChatOpen = false; USpikyGameInstance::DifferentMix->wGameRoomWidgets->SetVisibility(ESlateVisibility::Visible); //     USpikyGameInstance::DifferentMix->wGameRoomWidgets->wToFirstTeamButton->SetVisibility(ESlateVisibility::Hidden); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wToSecondTeamButton->SetVisibility(ESlateVisibility::Hidden); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wToUndistributedTeamButton->SetVisibility(ESlateVisibility::Hidden); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wStartButton->SetVisibility(ESlateVisibility::Hidden); } else if (subscribe.GetReflection()->HasField(subscribe, Descriptors::player_sub) && !subscribe.GetReflection()->HasField(subscribe, Descriptors::player_team)) { USpikyGameInstance::DifferentMix->wGameRoomWidgets->AddPlayer(subscribe.player().c_str(), "undistributed"); } else if (!subscribe.subscribe()) { USpikyGameInstance::DifferentMix->wGameRoomWidgets->RemovePlayer(subscribe.player().c_str(), subscribe.team().c_str()); if (USpikyGameInstance::userLogin == subscribe.player()) { USpikyGameInstance::DifferentMix->wGameRoomWidgets->wFirstTeamScrollBox->ClearChildren(); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wSecondTeamScrollBox->ClearChildren(); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wUndistributedTeamScrollBox->ClearChildren(); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wChatScrollBox->ClearChildren(); //  USpikyGameInstance::DifferentMix->ShowMainMenuScreen(); } } } void UGameRoom::UpdateRoom(RoomUpdate update) { for (TeamPlayer e : update.roomdescribe().team1()) { USpikyGameInstance::DifferentMix->wGameRoomWidgets->RemoveAddPlayer(e.player_name().c_str(), "team1", update.targetteam().c_str()); } for (TeamPlayer e : update.roomdescribe().team2()) { USpikyGameInstance::DifferentMix->wGameRoomWidgets->RemoveAddPlayer(e.player_name().c_str(), "team2", update.targetteam().c_str()); } for (TeamPlayer e : update.roomdescribe().undistributed()) { USpikyGameInstance::DifferentMix->wGameRoomWidgets->RemoveAddPlayer(e.player_name().c_str(), "undistributed", update.targetteam().c_str()); } } URoomListUnit* UGameRoom::NewRoom(CreateRoom room) { URoomListUnit* button = NewObject<URoomListUnit>(URoomListUnit::StaticClass()); UHorizontalBox* horBox = NewObject<UHorizontalBox>(UHorizontalBox::StaticClass()); UTextBlock* wRoomName = NewObject<UTextBlock>(UTextBlock::StaticClass()); UTextBlock* wMapName = NewObject<UTextBlock>(UTextBlock::StaticClass()); UTextBlock* wCreator = NewObject<UTextBlock>(UTextBlock::StaticClass()); UTextBlock* wGameTime = NewObject<UTextBlock>(UTextBlock::StaticClass()); UTextBlock* wMaxPlayers = NewObject<UTextBlock>(UTextBlock::StaticClass()); wRoomName->SetText(FText::FromString(TCHAR_TO_UTF8(*FString(room.roomname().c_str())))); wMapName->SetText(FText::FromString(TCHAR_TO_UTF8(*FString(room.mapname().c_str())))); wCreator->SetText(FText::FromString(TCHAR_TO_UTF8(*FString(room.creator().c_str())))); wGameTime->SetText(FText::FromString(TCHAR_TO_UTF8(*FString(room.gametime().c_str())))); wMaxPlayers->SetText(FText::FromString(TCHAR_TO_UTF8(*FString(room.maxplayers().c_str())))); UHorizontalBoxSlot* roomNameSlot = Cast<UHorizontalBoxSlot>(horBox->AddChild(wRoomName)); UHorizontalBoxSlot* mapNameSlot = Cast<UHorizontalBoxSlot>(horBox->AddChild(wMapName)); UHorizontalBoxSlot* creatorSlot = Cast<UHorizontalBoxSlot>(horBox->AddChild(wCreator)); UHorizontalBoxSlot* gameTimeSlot = Cast<UHorizontalBoxSlot>(horBox->AddChild(wGameTime)); UHorizontalBoxSlot* maxPlayersSlot = Cast<UHorizontalBoxSlot>(horBox->AddChild(wMaxPlayers)); roomNameSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); roomNameSlot->SetHorizontalAlignment(HAlign_Center); mapNameSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); mapNameSlot->SetHorizontalAlignment(HAlign_Center); creatorSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); creatorSlot->SetHorizontalAlignment(HAlign_Center); gameTimeSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); gameTimeSlot->SetHorizontalAlignment(HAlign_Center); maxPlayersSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); maxPlayersSlot->SetHorizontalAlignment(HAlign_Center); UButtonSlot* buttonSlot = Cast<UButtonSlot>(button->AddChild(horBox)); buttonSlot->SetHorizontalAlignment(HAlign_Fill); buttonSlot->SetVerticalAlignment(VAlign_Fill); return button; } void UGameRoom::DeleteRoom(RoomsListUpdate update) { //     for (size_t i = 0; i < USpikyGameInstance::DifferentMix->wMainMenuWidgets->wRoomsScrollBox->GetChildrenCount(); i++) { URoomListUnit* listUnit = Cast<URoomListUnit>(USpikyGameInstance::DifferentMix->wMainMenuWidgets->wRoomsScrollBox->GetChildAt(i)); UHorizontalBox* horBox = Cast<UHorizontalBox>(listUnit->GetChildAt(0)); UTextBlock* wCreatorName = Cast<UTextBlock>(horBox->GetChildAt(2)); if (wCreatorName->GetText().ToString() == update.roomowner().c_str()) { USpikyGameInstance::DifferentMix->wMainMenuWidgets->wRoomsScrollBox->RemoveChildAt(i); } } //           if (roomCreator == update.roomowner().c_str()) { //       USpikyGameInstance::DifferentMix->wGameRoomWidgets->wFirstTeamScrollBox->ClearChildren(); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wSecondTeamScrollBox->ClearChildren(); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wUndistributedTeamScrollBox->ClearChildren(); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wChatScrollBox->ClearChildren(); UGameRoomUserUnit::select_players.clear(); roomCreator = ""; roomName = ""; USpikyGameInstance::DifferentMix->wMainMenuWidgets->SetVisibility(ESlateVisibility::Visible); USpikyGameInstance::DifferentMix->wCreateRoomWidgets->SetVisibility(ESlateVisibility::Hidden); USpikyGameInstance::DifferentMix->wGameRoomWidgets->SetVisibility(ESlateVisibility::Hidden); } } void UGameRoom::DeleteRoom(std::string name) { //     for (size_t i = 0; i < USpikyGameInstance::DifferentMix->wMainMenuWidgets->wRoomsScrollBox->GetChildrenCount(); i++) { URoomListUnit* listUnit = Cast<URoomListUnit>(USpikyGameInstance::DifferentMix->wMainMenuWidgets->wRoomsScrollBox->GetChildAt(i)); UHorizontalBox* horBox = Cast<UHorizontalBox>(listUnit->GetChildAt(0)); UTextBlock* wCreatorName = Cast<UTextBlock>(horBox->GetChildAt(2)); if (wCreatorName->GetText().ToString() == name.c_str()) { USpikyGameInstance::DifferentMix->wMainMenuWidgets->wRoomsScrollBox->RemoveChildAt(i); } } //       if (roomCreator == name) { //         for (size_t j = 0; j < USpikyGameInstance::DifferentMix->wGameRoomWidgets->wUndistributedTeamScrollBox->GetChildrenCount(); j++) { UGameRoomUserUnit * entity = Cast<UGameRoomUserUnit>(USpikyGameInstance::DifferentMix->wGameRoomWidgets->wUndistributedTeamScrollBox->GetChildAt(j)); FString playerName = Cast<UTextBlock>(entity->GetChildAt(0))->GetText().ToString(); //           if (playerName == USpikyGameInstance::userLogin.c_str() && playerName != roomCreator.c_str()) { //       USpikyGameInstance::DifferentMix->wGameRoomWidgets->wFirstTeamScrollBox->ClearChildren(); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wSecondTeamScrollBox->ClearChildren(); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wUndistributedTeamScrollBox->ClearChildren(); USpikyGameInstance::DifferentMix->wGameRoomWidgets->wChatScrollBox->ClearChildren(); UGameRoomUserUnit::select_players.clear(); roomCreator = ""; roomName = ""; USpikyGameInstance::DifferentMix->wMainMenuWidgets->SetVisibility(ESlateVisibility::Visible); USpikyGameInstance::DifferentMix->wCreateRoomWidgets->SetVisibility(ESlateVisibility::Hidden); USpikyGameInstance::DifferentMix->wGameRoomWidgets->SetVisibility(ESlateVisibility::Hidden); } } } } void UGameRoom::StartGame() { } 


! windows x64, . , Play:



,
/WindowsNoEditor/Spiky_Client/Saved/Config/WindowsNoEditor — GameUserSettings.ini :

GameUserSettings.ini
 [/Script/Engine.GameUserSettings] bUseVSync=False ResolutionSizeX=683 ResolutionSizeY=384 LastUserConfirmedResolutionSizeX=1280 LastUserConfirmedResolutionSizeY=1024 WindowPosX=-1 WindowPosY=-1 bUseDesktopResolutionForFullscreen=False FullscreenMode=2 LastConfirmedFullscreenMode=2 Version=5 


, , , , , , .

. , , .

Pawns MechActor:

MechActor
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "GameFramework/Actor.h" #include "MechActor.generated.h" UCLASS() class SPIKY_CLIENT_API AMechActor : public AActor { GENERATED_BODY() public: AMechActor(const FObjectInitializer& ObjectInitializer); UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MeshComponent) UStaticMeshComponent* MeshComponent; }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "MechActor.h" #include "Runtime/Engine/Classes/GameFramework/RotatingMovementComponent.h" #include "Runtime/Engine/Classes/Components/StaticMeshComponent.h" AMechActor::AMechActor(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { MeshComponent = ObjectInitializer.CreateDefaultSubobject<UStaticMeshComponent>(this, TEXT("Mesh")); MeshComponent->SetMobility(EComponentMobility::Movable); RootComponent = MeshComponent; URotatingMovementComponent* RotatingMovement = ObjectInitializer.CreateDefaultSubobject<URotatingMovementComponent>(this, TEXT("RotatingMovement")); RotatingMovement->RotationRate = FRotator(0, 80, 0); // speed RotatingMovement->PivotTranslation = FVector(0, 0, 0); // point RotatingMovement->SetUpdatedComponent(GetRootComponent()); } 


 URotatingMovementComponent* RotatingMovement = ObjectInitializer.CreateDefaultSubobject<URotatingMovementComponent>(this,TEXT("RotatingMovement")); RotatingMovement->RotationRate = FRotator(0, 80, 0); // speed RotatingMovement->PivotTranslation = FVector(0, 0, 0); // point RotatingMovement->SetUpdatedComponent(GetRootComponent()); 

MeshComponent RootComponent . Unreal Engine , – UrotatingMovementComponent, .

BP MeshComponent.
Blueprints/Pawns MechActor_BP. scale x,y = 10, DirectionalLight. CameraActor, Auto Player Activation Player 0. . .




, , , .

. 20 .

, , - , , .

, . , , , . , .

start game . :

 void UGameRoomWidgets::StartButtonClicked() { std::shared_ptr<Room> room(new Room); room->set_startgame(true); room->set_roomname(UGameRoom::roomName); UMessageEncoder::Send(room.get(), true, true); } 

RoomManager :

 ... else if(room.hasField(startGame_room)) { startGame(ctx, room.getRoomName()); } ... private void startGame(ChannelHandlerContext ctx, String roomName) {} 

, PlayerState:

PlayerState
 /* * Copyright (c) 2017, Vadim Petrov - MIT License */ package com.spiky.server.tcp.logics; import java.util.Collections; import java.util.Map; import java.util.NavigableMap; import java.util.TreeMap; public class PlayerState { private String team; private NavigableMap<Long,Position> positionByTime = Collections.synchronizedNavigableMap(new TreeMap<>()); private long lastUpdateTime; PlayerState() { startCleaner(); } public class Position { Location location; Rotation rotation; Position(Location location, Rotation rotation) { this.location = location; this.rotation = rotation; } } /*      ! */ private void startCleaner() { new Thread(() -> { long lifetime = 5000; while (true) { try { if(positionByTime.size() <= 0) continue; lastUpdateTime = positionByTime.lastEntry().getKey(); Thread.sleep(5000); /*    5   ,        */ if(positionByTime.lastEntry().getKey() == lastUpdateTime) { long ms = System.currentTimeMillis(); lastUpdateTime = ms; Position old_pos = positionByTime.lastEntry().getValue(); positionByTime.clear(); positionByTime.put(ms, old_pos); } else { positionByTime.entrySet().removeIf(e -> System.currentTimeMillis() - e.getKey() > lifetime); } } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } public class Location { private int x, y, z; public Location(int x, int y, int z) { this.x = x; this.y = y; this.z = z; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public int getZ() { return z; } public void setZ(int z) { this.z = z; } } public class Rotation { private int pitch, roll, yaw; public Rotation(int pitch, int roll, int yaw) { this.pitch = pitch; this.roll = roll; this.yaw = yaw; } public int getPitch() { return pitch; } public void setPitch(int pitch) { this.pitch = pitch; } public int getRoll() { return roll; } public void setRoll(int roll) { this.roll = roll; } public int getYaw() { return yaw; } public void setYaw(int yaw) { this.yaw = yaw; } } Map.Entry<Long, Position> getLastPosition() { return positionByTime.lastEntry(); } void addPosition(long time, Position position) { positionByTime.put(time, position); } public Map.Entry<Long, Position> getClosestMs(long ms) { // todo  ,    2  return positionByTime.lowerEntry(ms); } public String getTeam() { return team; } public void setTeam(String team) { this.team = team; } } 


, . Position, Location Rotation. Position :

 private String team; private NavigableMap<Long,Position> positionByTime = Collections.synchronizedNavigableMap(new TreeMap<>()); private long lastUpdateTime; 

NavigableMap , NavigableMap (lowerEntry()), lastUpdateTime – , , , , , , , — , . . GameRoom playersState, :

 Map<String,PlayerState> playersState = Collections.synchronizedMap(new HashMap<>()); 

 private void startGame(ChannelHandlerContext ctx, String roomName)                                     ,    –     

private void startGame(ChannelHandlerContext ctx, String roomName)
 private void startGame(ChannelHandlerContext ctx, String roomName) { /*   */ for(String owner : gameRooms.keySet()) { if (Objects.equals(gameRooms.get(owner).getRoomName(), roomName)) { GameRoom gameRoom = gameRooms.get(owner); /*   */ if (Objects.equals(gameRoom.getCreator(), ctx.channel().attr(CHANNEL_OWNER).get())) { /*    */ if(gameRoom.getGameState()) return; /*     ,       */ gameRoom.setGameState(true); GameRoomModels.RoomsListUpdate roomsListUpdate = GameRoomModels.RoomsListUpdate.newBuilder().setRoomOwner(gameRoom.getCreator()).build(); GameRoomModels.Room room = GameRoomModels.Room.newBuilder().setStartGame(true).setRoomsListUpdate(roomsListUpdate).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setRoom(ByteString.copyFrom(room.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); /*    ,     */ roomListUpdateSubscribers.writeAndFlush(wrapper); /*        */ for (String p : gameRoom.undistributed.keySet()) { /*    */ if(!Objects.equals(gameRoom.getCreator(), p)) { gameRoom.players.remove(p); gameRoom.recipients.remove(gameRoom.undistributed.get(p)); } } gameRoom.undistributed.clear(); /*        , ,    */ GameModels.GameInitialState initialState = GameModels.GameInitialState.newBuilder().setStartGame(true).build(); Random rand = new Random(); for (String name : gameRoom.players.keySet()) { String team = ""; if(gameRoom.team1.containsKey(name)) { team = "team1"; } else if(gameRoom.team2.containsKey(name)) { team = "team2"; } PlayerState playerState = new PlayerState(); playerState.setTeam(team); GameModels.Player playerProto = GameModels.Player.newBuilder().build(); int randomX = rand.nextInt((2000 - (-2000)) + 1) + (-2000); int randomY = rand.nextInt((2000 - (-2000)) + 1) + (-2000); PlayerState.Location l = playerState.new Location(randomX, randomY, 0); PlayerState.Rotation r = playerState.new Rotation(0, 0, 0); playerState.addPosition(System.currentTimeMillis(), playerState.new Position(l, r)); gameRoom.playersState.put(name, playerState); GameModels.PlayerPosition.Location loc = GameModels.PlayerPosition.Location.newBuilder() .setX(playerState.getLastPosition().getValue().location.getX()) .setY(playerState.getLastPosition().getValue().location.getY()) .setZ(playerState.getLastPosition().getValue().location.getZ()) .build(); GameModels.PlayerPosition.Rotation rot = GameModels.PlayerPosition.Rotation.newBuilder() .setPitch(playerState.getLastPosition().getValue().rotation.getPitch()) .setYaw(playerState.getLastPosition().getValue().rotation.getYaw()) .setRoll(playerState.getLastPosition().getValue().rotation.getRoll()) .build(); GameModels.PlayerPosition playerPosition = GameModels.PlayerPosition.newBuilder().setLoc(loc).setRot(rot).build(); /*  ,  ,  (  ) */ playerProto = playerProto.toBuilder().setPlayerName(name).setTeam(playerState.getTeam()).setPlayerPosition(playerPosition).build(); initialState = initialState.toBuilder().addPlayer(playerProto).build(); } GameModels.GameData gameData = GameModels.GameData.newBuilder().setGameInitialState(initialState).build(); MessageModels.CryptogramWrapper cw2 = MessageModels.CryptogramWrapper.newBuilder().setGameModels(ByteString.copyFrom(gameData.toByteArray())).build(); MessageModels.Wrapper wrapper2 = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw2).build(); for (Channel c : gameRoom.players.values()) c.writeAndFlush(wrapper2); } } } } 


, Character ATPSCharacter : public ACharacter . , :

ATPSCharacter
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "GameFramework/Character.h" #include "TPSCharacter.generated.h" UCLASS() class SPIKY_CLIENT_API ATPSCharacter : public ACharacter { GENERATED_BODY() public: ATPSCharacter(); protected: virtual void BeginPlay() override; public: virtual void Tick(float DeltaTime) override; virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; UFUNCTION() void MoveForward(float Val); UFUNCTION() void MoveRight(float Val); void Fire(); }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "TPSCharacter.h" #include "GameFramework/CharacterMovementComponent.h" #include "Runtime/Engine/Classes/Components/InputComponent.h" #include "Runtime/Engine/Classes/GameFramework/Controller.h" #include "Runtime/Engine/Classes/GameFramework/PlayerController.h" #include "Runtime/Engine/Classes/Kismet/KismetSystemLibrary.h" #include "Protobufs/GameModels.pb.h" #include "MessageEncoder.h" ATPSCharacter::ATPSCharacter() { PrimaryActorTick.bCanEverTick = true; } void ATPSCharacter::BeginPlay() { Super::BeginPlay(); } void ATPSCharacter::Tick(float DeltaTime) { Super::Tick(DeltaTime); } void ATPSCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); PlayerInputComponent->BindAxis("MoveForward", this, &ATPSCharacter::MoveForward); PlayerInputComponent->BindAxis("MoveRight", this, &ATPSCharacter::MoveRight); PlayerInputComponent->BindAxis("Turn", this, &ATPSCharacter::AddControllerYawInput); PlayerInputComponent->BindAxis("LookUp", this, &ATPSCharacter::AddControllerPitchInput); PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &ATPSCharacter::Fire); } void ATPSCharacter::MoveForward(float Value) { if ((Controller != NULL) && (Value != 0.0f)) { // find out which way is forward FRotator Rotation = Controller->GetControlRotation(); // Limit pitch when walking or falling if (GetCharacterMovement()->IsMovingOnGround() || GetCharacterMovement()->IsFalling()) { Rotation.Pitch = 0.0f; } // add movement in that direction const FVector Direction = FRotationMatrix(Rotation).GetScaledAxis(EAxis::X); AddMovementInput(Direction, Value); } } void ATPSCharacter::MoveRight(float Value) { if ((Controller != NULL) && (Value != 0.0f)) { // find out which way is right const FRotator Rotation = Controller->GetControlRotation(); const FVector Direction = FRotationMatrix(Rotation).GetScaledAxis(EAxis::Y); // add movement in that direction AddMovementInput(Direction, Value); } } void ATPSCharacter::Fire() { GLog->Log("FIRE"); APlayerController* controller = Cast<APlayerController>(GetController()); FVector start = controller->PlayerCameraManager->GetCameraLocation(); FVector end = start + controller->PlayerCameraManager->GetActorForwardVector() * 512; FCollisionQueryParams TraceParams(FName(TEXT("Trace"))); TraceParams.bTraceComplex = true; FHitResult HitData(ForceInit); //Trace! GetWorld()->LineTraceSingleByChannel(HitData, start, end, ECC_Pawn, TraceParams); FVector startZ = FVector(start.X, start.Y, start.Z - 10); FVector endZ = FVector(end.X, end.Y, end.Z + 10); UKismetSystemLibrary::DrawDebugLine(this, startZ, endZ, FColor(255, 0, 0), 3.f, 1.f); std::shared_ptr<Shot> shot(new Shot); std::shared_ptr<Shot::Start> shotStart(new Shot::Start); std::shared_ptr<Shot::End> shotEnd(new Shot::End); shotStart->set_x(start.X); shotStart->set_y(start.Y); shotStart->set_z(start.Z); shotEnd->set_x(end.X); shotEnd->set_y(end.Y); shotEnd->set_z(end.Z); shot->set_allocated_start(shotStart.get()); shot->set_allocated_end(shotEnd.get()); if (IsValid(HitData.GetActor())) if (HitData.GetActor()->Tags.Num() > 0) shot->set_requestto(TCHAR_TO_UTF8(*HitData.GetActor()->Tags.Top().ToString())); shot->set_timestamp(USpikyGameInstance::DifferentMix->GetMS()); std::shared_ptr<GameData> gameData(new GameData); gameData->set_allocated_shot(shot.get()); //    ,    ,      UMessageEncoder::Send(gameData.get(), true, true); gameData->release_shot(); shot->release_end(); shot->release_start(); } 


ATPSCharacter::Fire() — , , UKismetSystemLibrary::DrawDebugLine LineTraceSingleByChannel . , , .

Unreal. DifferentMix:

 ... int64 GetMS(); ... int64 UDifferentMix::GetMS() { FDateTime now = FDateTime::UtcNow(); return now.ToUnixTimestamp() * 1000 + now.GetMillisecond(); } 

GameProcess :

GameProcess
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "Runtime/CoreUObject/Public/UObject/Object.h" #include <vector> #include "GameProcess.generated.h" class GameData; class PlayerPosition; UCLASS() class SPIKY_CLIENT_API UGameProcess : public UObject { GENERATED_BODY() public: void Handler(GameData gData); static GameData gameData; void UpdatePositions(PlayerPosition playerPosition); static std::vector<int64> pings; void ComputePing(GameData gData); void ComputeShot(GameData gData); }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "GameProcess.h" #include "Protobufs/GameModels.pb.h" GameData UGameProcess::gameData; std::vector<int64> UGameProcess::pings; void UGameProcess::Handler(GameData gData) { } void UGameProcess::ComputeShot(GameData gData) { } void UGameProcess::UpdatePositions(PlayerPosition playerPosition) { } void UGameProcess::ComputePing(GameData gData) { } 


, GameMode, MapGameMode:

 void AMapGameMode::BeginPlay()            . AActor* AMapGameMode::ChoosePlayerStart_Implementation(AController* Player)   void AMapGameMode::SendLocation()   ,         void AMapGameMode::SendPing()  Ping void AMapGameMode::ComputeFrameRate()  FPS 

, . Blueprints/Pawns
TPSCharacter_BP. TPSCharacter GenericMale. ProjectSettings->Input:



TPSCharacter_BP Use Pawn Control Rotation, :





GameMode :

 static ConstructorHelpers::FClassFinder<ATPSCharacter> PlayerPawnObject(TEXT("Blueprint'/Game/Blueprints/Pawns/TPSCharacter_BP.TPSCharacter_BP_C'")); if (PlayerPawnObject.Class != NULL) { pawnObject = PlayerPawnObject.Class->GetDefaultObject<ATPSCharacter>(); } 

MapGameMode
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "GameFramework/GameModeBase.h" #include "MapGameMode.generated.h" class ATPSCharacter; UCLASS() class SPIKY_CLIENT_API AMapGameMode : public AGameModeBase { GENERATED_BODY() AMapGameMode(const FObjectInitializer& ObjectInitializer); virtual void BeginPlay() override; virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; virtual void Tick(float DeltaTime) override; ATPSCharacter* pawnObject; virtual AActor* ChoosePlayerStart_Implementation(AController* Player) override; FTimerHandle UpdateLocationTimerHandle; void SendLocation(); FVector old_position, position; FRotator old_rotation, rotation; FTimerHandle PingTimerHandle; void SendPing(); FTimerHandle FPSTimerHandle; void ComputeFrameRate(); int32 fps = 0; }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "MapGameMode.h" #include "SpikyGameInstance.h" #include "Runtime/Engine/Classes/Engine/World.h" #include "TPSCharacter.h" #include "Protobufs/GameModels.pb.h" #include "GameProcess.h" #include "DifferentMix.h" #include "MessageEncoder.h" AMapGameMode::AMapGameMode(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { PrimaryActorTick.bCanEverTick = true; } void AMapGameMode::BeginPlay() { Super::BeginPlay(); GLog->Log("AMapGameMode::BeginPlay()"); USpikyGameInstance* gameInstance = Cast<USpikyGameInstance>(GetWorld()->GetGameInstance()); gameInstance->DifferentMixInit(GetWorld()); //       for (::Player p : UGameProcess::gameData.gameinitialstate().player()) { if (p.player_name() != USpikyGameInstance::userLogin) { FVector pos = FVector(p.playerposition().loc().x(), p.playerposition().loc().y(), p.playerposition().loc().z()); auto character = GetWorld()->SpawnActor<ATPSCharacter>(pawnObject->GetClass(), FTransform(pos)); character->Tags.Add(p.player_name().c_str()); } } } void AMapGameMode::EndPlay(const EEndPlayReason::Type EndPlayReason) { Super::EndPlay(EndPlayReason); } void AMapGameMode::Tick(float DeltaTime) { Super::Tick(DeltaTime); } AActor* AMapGameMode::ChoosePlayerStart_Implementation(AController* Player) { FVector pos; for (::Player p : UGameProcess::gameData.gameinitialstate().player()) { if (p.player_name() == USpikyGameInstance::userLogin) { pos = FVector(p.playerposition().loc().x(), p.playerposition().loc().y(), p.playerposition().loc().z()); } } ATPSCharacter* playerCharacter = GetWorld()->SpawnActor<ATPSCharacter>(pawnObject->GetClass(), FTransform(pos)); GetWorld()->GetFirstPlayerController()->Possess(playerCharacter); playerCharacter->Tags.Add(USpikyGameInstance::userLogin.c_str()); return playerCharacter; } void AMapGameMode::SendLocation() { ATPSCharacter* character = Cast<ATPSCharacter>(GetWorld()->GetFirstPlayerController()->GetCharacter()); position = character->GetActorLocation(); rotation = character->GetActorRotation(); //       if (position.Equals(old_position) && rotation.Equals(old_rotation)) return; old_position = position; old_rotation = rotation; std::shared_ptr<PlayerPosition> playerPosition(new PlayerPosition); std::shared_ptr<PlayerPosition::Location> playerLocation(new PlayerPosition::Location); std::shared_ptr<PlayerPosition::Rotation> playerRotation(new PlayerPosition::Rotation); playerLocation->set_x(position.X); playerLocation->set_y(position.Y); playerLocation->set_z(position.Z); playerRotation->set_pitch(rotation.Pitch); playerRotation->set_roll(rotation.Roll); playerRotation->set_yaw(rotation.Yaw); playerPosition->set_allocated_loc(playerLocation.get()); playerPosition->set_allocated_rot(playerRotation.get()); playerPosition->set_timestamp(USpikyGameInstance::DifferentMix->GetMS()); std::shared_ptr<GameData> gameData(new GameData); gameData->set_allocated_playerposition(playerPosition.get()); UMessageEncoder::Send(gameData.get(), true, true); gameData->release_playerposition(); playerPosition->release_rot(); playerPosition->release_loc(); } void AMapGameMode::SendPing() { } void AMapGameMode::ComputeFrameRate() { } 


Map1 ( Github).

UGameProcess::Handler:

 void UGameProcess::Handler(GameData gData) { if (gData.has_gameinitialstate()) { gameData = gData; UGameplayStatics::OpenLevel(USpikyGameInstance::DifferentMix->GetWorld(), "Map1", false, "game= Spiky_Client.MapGameMode"); } } 

If a signal is received, we open Map1 with the installed MapGameMode, which performs logic on placement by starting positions, initializing the interface and sending movement data to the server.

I think we will need a sight, for this we will add a HUD which will place the texture of the sight in the center. Add the SpikyHUD class to the Public / Private root:

Spikyhud
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "GameFramework/HUD.h" #include "Runtime/Engine/Classes/Engine/Texture2D.h" #include "SpikyHUD.generated.h" UCLASS() class SPIKY_CLIENT_API ASpikyHUD : public AHUD { GENERATED_BODY() ASpikyHUD(); private: /** Crosshair asset pointer */ UTexture2D* CrosshairTex; virtual void DrawHUD() override; }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "SpikyHUD.h" #include "Runtime/CoreUObject/Public/UObject/ConstructorHelpers.h" #include "Runtime/Engine/Classes/Engine/Canvas.h" ASpikyHUD::ASpikyHUD() { // Set the crosshair texture static ConstructorHelpers::FObjectFinder<UTexture2D> CrosshairTexObj(TEXT("Texture2D'/Game/ProjectResources/Images/crosshair.crosshair'")); CrosshairTex = CrosshairTexObj.Object; } void ASpikyHUD::DrawHUD() { Super::DrawHUD(); // Draw very simple crosshair // find center of the Canvas const FVector2D Center(Canvas->ClipX * 0.5f, Canvas->ClipY * 0.5f); // offset by half the texture's dimensions so that the center of the texture aligns with the center of the Canvas const FVector2D CrosshairDrawPosition((Center.X - (CrosshairTex->GetSurfaceWidth() * 0.5)), (Center.Y - (CrosshairTex->GetSurfaceHeight() * 0.5f))); // draw the crosshair FCanvasTileItem TileItem(CrosshairDrawPosition, CrosshairTex->Resource, FLinearColor::White); TileItem.BlendMode = SE_BLEND_Translucent; Canvas->DrawItem(TileItem); } 



Update SpikyGameMode in the constructor:

 HUDClass = ASpikyHUD::StaticClass(); 


We will collect and test in order to add another Map1 map to the assembly; we need to point the path to it:



After starting the game, the player sees the placed players more often in the air, their position is not updated, we place them a little higher so that there are no overlaps.

Add a sync move and shots. In SpikyGameMode, create a timer that sends information about the player’s location 20 times per second:

SpikyGameMode
 //.h FTimerHandle UpdateLocationTimerHandle; void SendLocation(); FVector old_position, position; FRotator old_rotation, rotation; //.cpp void AMapGameMode::BeginPlay() { //   30      /10 .1 /20 .05 / 30 .03 GetWorld()->GetTimerManager().SetTimer(UpdateLocationTimerHandle, this, &AMapGameMode::SendLocation, .05f, true); } void AMapGameMode::EndPlay(const EEndPlayReason::Type EndPlayReason) { Super::EndPlay(EndPlayReason); GetWorld()->GetTimerManager().ClearAllTimersForObject(this); } //        -  void AMapGameMode::SendLocation() {...} 


UGameProcess::Handler :

 ... #include "Runtime/CoreUObject/Public/UObject/UObjectIterator.h" ... else if (gData.has_playerposition()) { UpdatePositions(gData.playerposition()); } else if (gData.has_shot()) { ComputeShot(gData); } void UGameProcess::ComputeShot(GameData gData)   ,   void UGameProcess::UpdatePositions(PlayerPosition playerPosition)   - ,     ,   

GameState:

 private void updatePosition(ChannelHandlerContext ctx, GameModels.PlayerPosition playerPosition)        

, , , , , , , :

GameState
 /* * Copyright (c) 2017, Vadim Petrov - MIT License */ package com.spiky.server.tcp.logics; import com.google.protobuf.ByteString; import com.spiky.server.protomodels.GameModels; import com.spiky.server.protomodels.MessageModels; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.group.ChannelMatchers; import java.util.Map; import static com.spiky.server.ServerMain.CHANNEL_OWNER; import static com.spiky.server.ServerMain.ROOM_OWNER; import static com.spiky.server.ServerMain.gameRooms; import static com.spiky.server.utils.Descriptors.requestTo_shot_gd; public class GameState { public GameState(ChannelHandlerContext ctx, GameModels.GameData gameData) { if(gameData.hasPlayerPosition()) { updatePosition(ctx, gameData.getPlayerPosition()); } else if(gameData.hasShot()) { /*   */ GameRoom gameRoom = gameRooms.get(ctx.channel().attr(ROOM_OWNER).get()); /*       ,       */ if(gameData.getShot().hasField(requestTo_shot_gd)) { gameData = gameData.toBuilder().setShot(gameData.getShot().toBuilder().clearTimeStamp().build()).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setGameModels(ByteString.copyFrom(gameData.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); gameRoom.recipients.writeAndFlush(wrapper, ChannelMatchers.isNot(ctx.channel())); } else { } } } private void updatePosition(ChannelHandlerContext ctx, GameModels.PlayerPosition playerPosition) { GameRoom gameRoom = gameRooms.get(ctx.channel().attr(ROOM_OWNER).get()); /*      */ PlayerState state = gameRoom.playersState.get(ctx.channel().attr(CHANNEL_OWNER).get()); PlayerState.Location l = state.new Location(playerPosition.getLoc().getX(), playerPosition.getLoc().getY(), playerPosition.getLoc().getZ()); PlayerState.Rotation r = state.new Rotation(playerPosition.getRot().getPitch(), playerPosition.getRot().getYaw(), playerPosition.getRot().getRoll()); state.addPosition(playerPosition.getTimeStamp(), state.new Position(l, r)); //System.out.println(playerPosition.getTimeStamp()); /*   */ playerPosition = playerPosition.toBuilder().setPlayerName(ctx.channel().attr(CHANNEL_OWNER).get()).build(); GameModels.GameData gameData = GameModels.GameData.newBuilder().setPlayerPosition(playerPosition).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setGameModels(ByteString.copyFrom(gameData.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); gameRoom.recipients.writeAndFlush(wrapper, ChannelMatchers.isNot(ctx.channel())); } } 


. , ping fps. :

PingFpsWidgets
 // Copyright (c) 2017, Vadim Petrov - MIT License #pragma once #include "Runtime/UMG/Public/Blueprint/UserWidget.h" #include "PingFpsWidgets.generated.h" class UTextBlock; UCLASS() class SPIKY_CLIENT_API UPingFpsWidgets : public UUserWidget { GENERATED_BODY() virtual void NativeConstruct() override; public: UTextBlock* wFps = nullptr; UTextBlock* wPing = nullptr; }; // Copyright (c) 2017, Vadim Petrov - MIT License #include "Spiky_Client.h" #include "PingFpsWidgets.h" #include "Runtime/UMG/Public/Components/TextBlock.h" void UPingFpsWidgets::NativeConstruct() { Super::NativeConstruct(); wFps = Cast<UTextBlock>(GetWidgetFromName(TEXT("fps"))); wPing = Cast<UTextBlock>(GetWidgetFromName(TEXT("ping"))); } 


DifferentMix:

PingFpsWidgets DifferentMix
 //.h class UPingFpsWidgets; UPingFpsWidgets* tmpPingFpsRef; UPingFpsWidgets* wPingFpsWidgets; UCanvasPanelSlot* pingFpsSlot; .cpp #include "PingFpsWidgets.h" ... static ConstructorHelpers::FClassFinder<UPingFpsWidgets> pingFpsWidgets(TEXT("WidgetBlueprint'/Game/Blueprints/Widgets/PingFPS_W.PingFPS_W_C'")); if (pingFpsWidgets.Class != NULL) { tmpPingFpsRef = pingFpsWidgets.Class->GetDefaultObject<UPingFpsWidgets>(); } ... wPingFpsWidgets = CreateWidget<UPingFpsWidgets>(GetWorld(), tmpPingFpsRef->GetClass()); pingFpsSlot = Cast<UCanvasPanelSlot>(wWidgetContainer->wCanvas->AddChild(wPingFpsWidgets)); pingFpsSlot->SetZOrder(10); pingFpsSlot->SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f)); pingFpsSlot->SetOffsets(FMargin(0, 0, 0, 0)); wPingFpsWidgets->SetVisibility(ESlateVisibility::Hidden); 


AMapGameMode::BeginPlay():

 #include "PingFpsWidgets.h" ... gameInstance->DifferentMix->wPingFpsWidgets->SetVisibility(ESlateVisibility::Visible); //          GetWorld()->GetTimerManager().SetTimer(PingTimerHandle, this, &AMapGameMode::SendPing, .25f, true); //  frame rate (fps) GetWorld()->GetTimerManager().SetTimer(FPSTimerHandle, this, &AMapGameMode::ComputeFrameRate, 1.f, true); 

, :

 ... #include "Runtime/UMG/Public/Components/TextBlock.h" ... void AMapGameMode::Tick(float DeltaTime) { Super::Tick(DeltaTime); fps++; } void AMapGameMode::SendPing() { std::shared_ptr<Ping> ping(new Ping); ping->set_time(USpikyGameInstance::DifferentMix->GetMS()); std::shared_ptr<GameData> gameData(new GameData); gameData->set_allocated_ping(ping.get()); UMessageEncoder::Send(gameData.get(), true, true); gameData->release_ping(); } void AMapGameMode::ComputeFrameRate() { USpikyGameInstance::DifferentMix->wPingFpsWidgets->wFps->SetText(FText::FromString(FString::FromInt(fps) + " FPS")); fps = 0; } 

. UGameProcess::Handler :

 ... else if (gData.has_ping()) { ComputePing(gData); } ... 


 void UGameProcess::ComputePing(GameData gData)  5 ,  ,  

void UGameProcess::ComputePing(GameData gData)
 void UGameProcess::ComputePing(GameData gData) { int64 ping = USpikyGameInstance::DifferentMix->GetMS() - gData.ping().time(); if (pings.size() < 5) { pings.push_back(ping); } else { int64 avr_ping = 0; for (int64 p : pings) { avr_ping += p; } avr_ping /= 5; pings.clear(); FString str = "Ping: " + FString::FromInt(avr_ping) + " ms"; USpikyGameInstance::DifferentMix->wPingFpsWidgets->wPing->SetText(FText::FromString(str)); } } 


GameState :

 else if(gameData.hasPing()) { MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setGameModels(ByteString.copyFrom(gameData.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); ctx.writeAndFlush(wrapper); } 

!

. : , , — . , .

java socket'.

Unreal VerificationServer. , MessageDecoder-MessageEncoder, protobuf . tcp VerificationServerConnection, :

VerificationServerConnection
 /* * Copyright (c) 2017, Vadim Petrov - MIT License */ package com.spiky.server.tcp; import com.spiky.server.protomodels.GameModels; import com.spiky.server.tcp.logics.GameState; import java.io.*; import java.net.ServerSocket; import java.net.Socket; public class VerificationServerConnection { private OutputStream socketWriter; public VerificationServerConnection(int verificationServerPort) { try { ServerSocket serverSocket = new ServerSocket(verificationServerPort); Socket activeSocket = serverSocket.accept(); InputStream socketReader = activeSocket.getInputStream(); socketWriter = new DataOutputStream(activeSocket.getOutputStream()); //  new Thread(() -> { try { while (true) { byte[] messageByte = new byte[1024]; socketReader.read(messageByte); ByteArrayInputStream input = new ByteArrayInputStream(messageByte); GameModels.GameData gameData = GameModels.GameData.parseDelimitedFrom(input); new GameState(gameData); } } catch (IOException e) { e.printStackTrace(); } }).start(); } catch (IOException e) { e.printStackTrace(); } } public void SendToVerificationServer(GameModels.GameData gameData) { try { ByteArrayOutputStream output = new ByteArrayOutputStream(1024); gameData.writeDelimitedTo(output); byte sendData[] = output.toByteArray(); socketWriter.write(sendData); } catch (IOException e) { e.printStackTrace(); } } } 


Add a new constructor to GameState:

 public GameState(GameModels.GameData gameData) { if(gameData.hasShot()) { System.out.println(gameData); GameRoom gameRoom = gameRooms.get(gameData.getShot().getRoomOwner()); gameData = gameData.toBuilder().setShot(gameData.getShot().toBuilder() .clearTimeStamp() .clearPlayerPosition() .clearRoomOwner().build() ).build(); MessageModels.CryptogramWrapper cw = MessageModels.CryptogramWrapper.newBuilder().setGameModels(ByteString.copyFrom(gameData.toByteArray())).build(); MessageModels.Wrapper wrapper = MessageModels.Wrapper.newBuilder().setCryptogramWrapper(cw).build(); gameRoom.recipients.writeAndFlush(wrapper); } } 

This data comes after checking on the verification server. Add the port of the verification server to the configuration file:

 verificationServerPort = 7682 

And read it in ServerMain:

 private static final int verificationServerPort = Integer.valueOf(configurationBundle.getString("verificationServerPort")); 

Run the server connection:

 /*      */ public static VerificationServerConnection verificationServerConnection; public static void main(String[] args) { new Thread(ServerMain::run_tcp).start(); //new Thread(ServerMain::run_udp).start(); captchaCleaner(); verificationServerConnection = new VerificationServerConnection(verificationServerPort); } 

Update the condition in GameState, if a tag has arrived (each player has a tag with a name, if without a tag, then you just need to show everyone the shot), the player thinks he’s hit, we send it to the test:

 /*       */ Map.Entry<Long, PlayerState.Position> pos = state.getClosestMs(gameData.getShot().getTimeStamp()); /*    */ ServerMain.verificationServerConnection.SendToVerificationServer(gd); 

On the server, right in the decoder, we check:

 UMessageDecoder::SendProtoToDecoder(GameData* gameData)         -         ,      ,         

Next, we can update the status of the player at all. I did not do this, but only returned the result of the check. This is how the hit verification process on the test server looks like:



That's all. Hope the material was helpful.

Interesting articles on the topic and literature


:
MechWarrior Online

DOOM III:
The DOOM III Network Architecture

Valve:
Source Multiplayer Networking

Source: https://habr.com/ru/post/334786/


All Articles