How To Make A Multiplayer Game With Unreal Engine and Amazon GameLift (Part 7 - Client)

Поділитися
Вставка
  • Опубліковано 30 лис 2024

КОМЕНТАРІ •

  • @Flopperam
    @Flopperam  4 роки тому +2

    Hey guys, just realized that I was confused about the "Content-Type: application/json" header that we added to the http requests. I didn't really understand the header fully and got carried away while adding it to almost all the http requests. The header is used in both requests and responses sent to and from the api. In requests, it denotes the encoding format of the content, so if you're not sending data in the request content, then this header is redundant. Unfortunately I add this header in a few of the http requests that don't have any content inside of them (for e.g. the requests to the GetPlayerData and InvalidateTokens api endpoints). Luckily, that won't break anything. The code will still work with or without the extra header. In responses, the header also denotes the encoding format of the content. However, you may have noticed in our Lambda functions, in previous parts of the tutorial, that we don't specify this "Content-Type: application/json" header in our responses. The reason is that by default, the encoding format of json text in request and response content is utf-8. Therefore, we actually did not need this header in any of our requests. Again, the extra code is not harmful, just more lines to type out, so I am sorry about that.

    • @crazyguy7585
      @crazyguy7585 4 роки тому

      bro can u make tutorial on Epic Online Service Which is Online now can u teach how to use SDK. i am big fan of ur work.

    • @Flopperam
      @Flopperam  4 роки тому +2

      Hey, thanks, that is the very next thing on our to-do list!

    • @crazyguy7585
      @crazyguy7585 4 роки тому

      @@Flopperam I Love it bro no tutorial on this subject. :)

  • @Flopperam
    @Flopperam  4 роки тому +1

    Thank you to someone in our discord who pointed this out, but please avoid using the Advanced Sessions plugin in this tutorial because it adds a name parameter to the Options passed to calls to the OpenLevel function, which causes the server to automatically reject the incoming client connection.
    The reason this occurs is that this will happen when you enable an Online Subsystem (OSS) on one of the client or server, but not both. The OSS has to be the same on both the server and the client at all times, so make sure to disable all plugins that enable the Online Subsystem and keep the Online Subsystem to be null on both the client and server to avoid issues when clients are trying to connect to the server.

    • @SilasCodes
      @SilasCodes 4 роки тому +1

      This comment is a life-saver. Consider pinning it!

  • @Flopperam
    @Flopperam  4 роки тому

    At around 5:42 until 10:30, I spent some time disabling a couple of built-in engine VR plugins (SteamVR and OculusVR), but I decided to go back and reenable these plugins. Although disabling the plugins are fine in this case since we don't use VR at all in this tutorial project and disabling plugins can reduce the size of a packaged build, people may use the tutorial project as a template to build their own game so I want to try to leave as many default settings as possible. Of course, I still encourage you guys to experiment with disabling redundant plugins because when creating a new Unreal Engine project, a lot of unused plugins are enabled by default. I just didn't want to assume what anyone watching did or did not need in their project, because VR, Android, iOS, OnlineSubsystem, and other plugins may not be used in this tutorial, but that does not mean they can not be used in conjunction with GameLift and the code/assets in this tutorial project.

  • @twinsassink8745
    @twinsassink8745 4 роки тому +2

    Great Video! Note: Make sure your fleet config is correct spent 4 hours trying to figure out what was wrong with my code and had the fleet config settings wrong the entire time D:

  • @Flopperam
    @Flopperam  4 роки тому

    At 1:58:41, in addition to making sure that the "LatencyInMs" was passed to the GameLift StartMatchmaking function, make sure that the "skill" property in the "PlayerAttributes" array has a property called "N" with a value of 50, since by default, players with no wins nor losses are assigned a default player skill number value of 50 in this tutorial.

  • @WalidtheNoematicsguy
    @WalidtheNoematicsguy 4 роки тому +1

    Thanks brother 👍🏻 any changes I need to make it work for Andriod?

    • @Flopperam
      @Flopperam  4 роки тому +2

      Hey, no changes have to be made for Android!

  • @rewb0rn
    @rewb0rn 2 роки тому +1

    Hey, great tutorial series! I could not get the clients to load the map after a successful matchmaking. I had the "successfully found a match, now connecting" message and the OpenLevel function gets called, but then nothing happened. Turned out I did not have the correct port settings on the fleet and the required inbound ports 7777-7778 were not set. Hope this helps anyone with the same problem.

  • @dimaferox5133
    @dimaferox5133 4 роки тому +1

    Great video, thanks! You are a very strong programmer

  • @C1C3R0_DEV
    @C1C3R0_DEV 4 роки тому

    @flopperam
    What would you suggest as far as iterating on your game once all of this is setup? I really want to be able have the capability for multiple instances playing locally just so I can test without having to push a new server build every time I make a change.
    Do just have a #ifdef or "if editor" bit of logic?

    • @Flopperam
      @Flopperam  4 роки тому

      Hey, so the next part should be the last part involving coding. It will cover features involving the server such as backfill, terminating game sessions so that server processes can be reused for new game sessions, accepting player sessions, etc.. As for testing locally, this gets tough because GameLiftLocal as far as I know does not support matchmaking right now. What you can do for local gamelift testing later is that after you make a game session through the command line, you can start making player sessions for that game session through the command line. You can then hardcode the ip address, port, player session id and player id values passed to OpenLevel. But again, definitely not a scalable solution. I'm not sure if that answered your question.

  • @luiszavaleta6398
    @luiszavaleta6398 3 роки тому

    Hello Chris! Good afternoon
    I am having a problem that I don't understand. After build the project, some things work fine.
    For example, I can login without problems, search for a game and it even shows me the message that a game was found successfully. In gamelift it tells me that a game session was created, but, it doesn't take me to the thirdperson character map ...
    Any idea? Also now the game sessions are in my gamelift fleet but I can't use them. I suppose that I can deactivate it from the cmd

    • @Flopperam
      @Flopperam  3 роки тому +1

      Hey, first, for getting rid of the game sessions. In part 8 we go over how to recycle game sessions. However, in your and other situations, it may be helpful to know how to scale down a fleet's instances to zero. To do that, go to your fleet on the dashboard, then go to the Scaling tab, and scroll down to where it says "Manually adjust the desired instance count to". Set that number to zero. When you want to start testing again, set that number back to 1 once the number of "Active instances" above on that same page becomes zero (may have to refresh the page). However, this will only work in getting rid of your game sessions if you set the "Protection Policy" of your fleet when you created it to "No Protection". So if you didn't do that, edit your fleet to not have any protection for game sessions. This seems tedious but unfortunately, there is no way to get rid of a game session besides through either the server process hosting the game session or through a fleet scale down event, forums.awsgametech.com/t/teminating-rogue-game-sessions/7661
      As for not being able to connect to the third person character map, can you add some log statements in your game client, specifically in the OnPollMatchmakingResponseReceived function to verify that when a match has been found, your client is getting the connection details for that match? Or perhaps in your game client logs already, there's something preventing the client from connecting. You can also check your server logs by remote connecting into the instance to see if your clients are even attempting to connect and maybe the server is rejecting them, ua-cam.com/video/V1fIQOpqa4Y/v-deo.html

  • @Combined8
    @Combined8 4 роки тому +1

    Hey Chris. Thxs so much for the tutorial. GetPlayerData testing in Postman with valid access key gave me "Unauthorized" but in Lamda test with same access key works, any idea why?

    • @Flopperam
      @Flopperam  4 роки тому

      Hey, in API Gateway, can you try deleting an API method, redeploy the API, then recreate that same API method, redeploy the API again, and test your API methods in Postman after that. Please let me know if you still see Unauthorized after trying all of that out.

    • @Combined8
      @Combined8 4 роки тому

      @@Flopperam I still see unauthorized.

    • @Flopperam
      @Flopperam  4 роки тому

      @@Combined8 A couple more things then. First, did you create and enable the custom scope in your Cognito user pool. Second, did you add the custom scope to the list of allowed OAuth scopes when you added the Cognito authorizer to your API method in API Gateway? Note that the last point has to be done for every API method that you add the Cognito authorizer to.

    • @Flopperam
      @Flopperam  4 роки тому

      @@Combined8 Also, make sure for the url to the cognito hosted UI that you are using that the custom scope is in the url.

    • @Combined8
      @Combined8 4 роки тому

      @@Flopperam Yes, I checked those as well. Gonna reply to discord channel.

  • @Uskie
    @Uskie 2 роки тому

    Hey Flopperam!
    Really digging this tutorial series! I've liked every single video so far.
    Sad to say, I've hit a brick wall with this one. I've got as far as having two clients successfully call and get a response for pollmatchmaking, but the number of game sessions on the Fleet is always stuck at zero.
    I've checked my Matchmaker and Ruleset, they both seem to match the tutorial. I did however notice the "Active ticket count" in the Matchmaker always reads 0.
    Can you think of any pointers that might lead me in the direction? Would really appreciate any help!
    Thanks!

    • @Flopperam
      @Flopperam  2 роки тому +1

      Hey, is there a queue attached to your matchmaker? And if so, does that queue have any fleets attached to it with available server processes?

    • @Uskie
      @Uskie 2 роки тому +1

      Thank you for the quick reply!
      Turns out after assigning my Queue to my Fleet, I had since made a new Fleet, so my Queue was pointing to my old, deleted Fleet!
      I fixed my mistake and now EVERYTHING WORKS!
      I'll consider donating to your Patreon!

    • @Flopperam
      @Flopperam  2 роки тому

      Hey Uskie, thank you so much for your kind donation! We are glad the series helped you achieve success in your game! Cant wait to see it

  • @jaytvids
    @jaytvids 2 роки тому

    Hey this tutorial series has been a big help for a project I'm working on. Quick question for you as I'm not really sure how to debug this. I managed to get everything 99% working the other night. I was able to join with both instances and we would spawn in the world. My constructor was missing the pawn declaration though so we would spawn as free-floating cameras. I managed to fix that but now when I go to join on both clients it just gets stuck searching and won't even time out. It's still generating access/refresh tokens, exchanging for ticket ids, etc. I'm even registering polls when searching.

    • @Flopperam
      @Flopperam  2 роки тому +1

      Hey, my guess is that there are no more available server processes for hosting new game sessions in any of the fleets attached to your queue attached to your matchmaker and as a result you're getting matchmaking events of type PotentialMatchCreated, but the queue is timing out and before I made this tutorial series I didn't realize that you can't set the queue timeout to a value too long. Otherwise, the matchmaking will time out before the matchmaker has a chance to send an event of type MatchmakingTimedOut, leaving your clients hanging since a "final state" event notification was ever received. However this may not be the case, you would have to check your fleet, queue, cloud watch logs for the TrackEvents lambda function, thr MatchmakingTickets dynamodb table, and unreal client logs to confirm. This is only my guess as in part 7 we don't recycle server processes for new game sessions. That topic is covered in part 8. Let me know if you need any clarification.

    • @jaytvids
      @jaytvids 2 роки тому +1

      @@Flopperam Tweaked my fleet settings and now everything is working perfectly. Thank you SO much for the help with this Chris! You're the man!!

  • @dotBAIT
    @dotBAIT 3 роки тому

    I cant seem to get my clients to join the game, keeps saying "searching for game" but nothing happens. When I check the tickets the only thing I notice are new "MatchmakingTimeout" types getting created.
    When I look in the CloudWatch I see I get a "Potential Matchmaking Found" but they never join. Im located in Hawaii so the closest region I can use is us-west-2 but my ping is at 90 and sometimes in the 100s. Could this be the reason why I keep timing out?

    • @dotBAIT
      @dotBAIT 3 роки тому

      Upon further looking into the logs, it looks like it times me out immediately then goes into matchmakingsearching then foundpotitalmatch... I have no idea whats going on. Any help would be appreciated.

    • @Flopperam
      @Flopperam  3 роки тому +1

      Issue was resolved on discord. Dynamodb table had to be recreated with a different structure like it was in part 5.

    • @dotBAIT
      @dotBAIT 3 роки тому

      @@Flopperam thought i removed this, thanks for the help again!

  • @Cr4ZyCS2
    @Cr4ZyCS2 4 роки тому +1

    Hello bro , i need some help with unreal , im learning 2 years websites and some programs languages but unreal is special can you help me with your fortnite clone inst working , in visual studio 2019 isnt working i get 2 errors , help me pls

  • @sumit20016
    @sumit20016 4 роки тому

    hello nice tutorial love u for that just a question. i am getting only one accesss token even after 10 seconds i am not getting another access token
    UE_LOG(LogTemp, Warning, TEXT("access token: %s"), *AccessToken);
    //UE_LOG(LogTemp, Warning, TEXT("refresh token: %s"), *RefreshToken);
    GetWorld()->GetTimerManager().SetTimer(RetrieveNewTokensHandle, this, &UGameLiftTutorialGameInstance::RetrieveNewTokens, 1.0f, false, 10.0f);
    can to tell me which files to check where i have done a mistake

    • @Flopperam
      @Flopperam  4 роки тому +1

      Could you check in your GameLiftTutorialGameInsttance.cpp file, specifically in the RetrieveNewTokens and OnRetrieveNewTokensResponseReceived functions, that you are calling SetTimer for the RetrieveNewTokens function? Those functions have to be called periodically in order to get new access tokens.

  • @meronwilfvaisbein7470
    @meronwilfvaisbein7470 4 роки тому

    Hi man. Great job. I have a problem: when I cancel the "matchmaking request" I can't see the "cancelled" in dynamodb matchmaking tickets. It won't show up. "TimedOut" is shown as it should be. I checked cloudwatch and in Track events I can see Cancelled events. What am I missing? Thanks in advance.

    • @Flopperam
      @Flopperam  4 роки тому

      can you check your cloudwatch logs for the StopMatchmaking lambda function in order to make sure that the function is getting called?

  • @unknownmystics7175
    @unknownmystics7175 2 роки тому

    Hey! great tutorial! im at the Join game test and when im pressing the join game button, it is never greyed out and never starts searching. cloudwatch sees the start and end of start matchmaking but never does have any other logs then start end and report.
    My matchmaking dynamo db table is also empty.
    Any help?

    • @unknownmystics7175
      @unknownmystics7175 2 роки тому

      also stop matchmaking has no logs so thats never called

    • @unknownmystics7175
      @unknownmystics7175 2 роки тому

      so i found it is getting stuck right in the onstartmatchmaking responsereciecved function, where would I go from here?

    • @Flopperam
      @Flopperam  2 роки тому

      @@unknownmystics7175 Hey I would suggest checking cloudwatch logs for the StartMatchmaking and TrackEvents lambda functions. Check to see if a matchmaking request was made, and if so, at what state did the request end at? MatchmakingFailed? MatchmakingTimedOut? etc.

    • @unknownmystics7175
      @unknownmystics7175 2 роки тому

      @@Flopperam it looks like my startmatchmaking lambda function wasnt returning results! thank you for sending me down the path forward to pleasing our almighty bezos

  • @bone.maggot
    @bone.maggot 4 роки тому

    hey Chris, my clients get matched perfectly and connected to the server successfully,
    but the ThirdPersonExampleMaps are not opened on any client builds,
    instead, it opened a google's homepage... i suspect that there may be some incorrect settings on my server build. because i cant remember if i set up the server default map correctly as i moved the "Maps" folder out of the default location.
    but how should i confirm my suspection? or if it was the server build,
    how should i save everything if i have not created a server build project seperately.
    thank you.

    • @bone.maggot
      @bone.maggot 4 роки тому

      UPDATE:
      checked log file, it writes:
      [2020.10.23-14.35.09:605][ 95]LogNet: Warning: Travel Failure: [PackageMissing]: /Game/ThirdPersonCPP/Maps/ThirdPersonExampleMap
      [2020.10.23-14.35.09:605][ 95]LogNet: TravelFailure: PackageMissing, Reason for Failure: '/Game/ThirdPersonCPP/Maps/ThirdPersonExampleMap'
      according to the correct path is 'Game/Maps/ThirdPersonExampleMap'
      i guess my doubt was valid? if it was the problem now is how can i repair it.
      seems the only way is to recreate a new project for server build? sorta heavy though...

    • @Flopperam
      @Flopperam  4 роки тому

      @@bone.maggot you are correct. It seems that you will have to fix the map in the editor settings (map and modes section), then repackage the project, reupload the server build, recreate the fleet, and change the fleet destination in the queue to that new fleet.

    • @bone.maggot
      @bone.maggot 4 роки тому +1

      @@Flopperam yeah i followed the steps you mentioned and my program works like a charm now. thank you!

  • @RahulGupta-gz1ex
    @RahulGupta-gz1ex 3 роки тому

    hi, PUBG uses gamelift ? what serivces does PUBG use from AWS ?

    • @Flopperam
      @Flopperam  3 роки тому +1

      Sorry for the late reply, but I'm not sure if the PUBG team uses GameLift, another cloud platform, or manages its own servers.

  • @TalisBarbalho
    @TalisBarbalho 4 роки тому

    Hello! It seems that I'll have at least one question per video. Sorry and thanks for your time in advance.
    I've changed the tutorial to allow a match to be created with a single player. The problem with that is, as soon as the player hits the "join match" button, the match goes from "matchmaking succeeded" to "potential matchmaking created" right away. This causes issues when I'm searching for a match right after because the player never finds a match and thus never travels to the map. I'm half-way done the next video and I'm assuming that I'll be able to fix this problem with backfill.
    Is this the expected behavior assuming these changes? Would you adapt something to make this work like this or should I just wait to get to the backfill part?
    Thank you very much

    • @Flopperam
      @Flopperam  4 роки тому

      Hey, sorry for the late reply, so in the beginning of part 5, we changed the code of the TrackEvents Lambda function for this reason. When FlexMatch publishes event notification messages to an SNS topic, the order in which the messages were published is not guaranteed to be the same order that they are delivered in to the SNS topic. Therefore, scenarios like you just mentioned where a message of type PotentialMatchCreated gets written after a message of type MatchmakingSucceeded can occur. For this reason, we changed the TrackEvents lambda function, the function subscribed to the SNS topic receiving FlexMatch event notifications, to only write final result notifications to DynamoDb in the case of no match acceptance. These include: MatchmakingSucceeded, MatchmakingFailed, MatchmakingCancelled, and MatchmakingTimedOut. You can check out this forum post for more information: forums.awsgametech.com/t/how-reliable-is-the-time-field-in-the-flexmatch-event-notification-messages/8527. Please let me know whether or not this helps in any way!

    • @TalisBarbalho
      @TalisBarbalho 4 роки тому

      @@Flopperam hey! No need to say sorry. I saw that you guys were having power issues. Thank you for taking the time even with this.
      So I probably have something wrong somewhere else, because the table is not keeping the matchmaking accepted in the database. It shows the expired ones, so I think that, because of that I cant join the match. That would make sense. I don't know what I didn't wrong, but I'll try to find out and let you know! Thanks for the help once again

    • @Flopperam
      @Flopperam  4 роки тому +1

      We talked about this on stream, but thought I'd write a reply in case other people come across this issue. Just to clarify, the matchmaking requests are timing out? Or is nothing getting written to the MatchmakingTickets table?

    • @TalisBarbalho
      @TalisBarbalho 4 роки тому

      @@Flopperam Hey! Sorry took me so long to get back to this. Cloudwatch is showing me the event potential matchmake created, but the match is neven being written to the database, nor is created on the fleet. I get the ticket id and everything on code as it should work, but the poll function doesn't work because there's nothing on the database to look for. The fleet has enough instances running to create a match as well, so I'm not sure what else I could have messed up with.

    • @Flopperam
      @Flopperam  4 роки тому

      Can you check if the fleet is assigned correctly to the queue attached to the matchmaker?

  • @ChasseuseUrtem
    @ChasseuseUrtem 4 роки тому

    Hello Flopperam, i have a problem, on onStartMatchmakingResponseReceived, when i create matchmakingTicketId, like this :
    FString matchmakingTicketId = jsonObject->GetStringField("ticketId");
    I have this error in log : LogJson: Error: Json Value of type 'Object' used as a 'String'.
    And my matchmakingTicketId is empty. Did you have an idea please ?

    • @Flopperam
      @Flopperam  4 роки тому

      When testing the StartMatchmaking API endpoint through Postman, do you get a valid response? If so, what does that response look like? Mainly checking for formatting here.

    • @lepetitcplusplus8013
      @lepetitcplusplus8013 4 роки тому

      @@Flopperam ok i change my body to :
      {
      "latencyMap": {
      "eu-central-1": 50
      }
      }
      and my answer is :
      {
      "ticketId": {
      "TicketId": "0030502d-abdb-4fb3-9980-765b636d51ee",
      "ConfigurationName": "SpacelMatchmaker",
      "ConfigurationArn": "arn:aws:gamelift:eu-central-1:866520236906:matchmakingconfiguration/SpacelMatchmaker",
      "Status": "QUEUED",
      "StartTime": "2020-11-16T15:50:02.332Z",
      "Players": [
      {
      "PlayerId": "c01aba04-8c5e-4ede-b8ab-dc760ded6a10",
      "PlayerAttributes": {
      "skill": {
      "N": 50
      }
      },
      "LatencyInMs": {
      "eu-central-1": 50
      }
      }
      ]
      }
      }

    • @lepetitcplusplus8013
      @lepetitcplusplus8013 4 роки тому

      Hum, maybe my problem is that :
      "ticketId": {
      "TicketId": "0030502d-abdb-4fb3-9980-765b636d51ee",
      i roll back the tutorial x)

    • @Flopperam
      @Flopperam  4 роки тому

      @@lepetitcplusplus8013 What does your StartMatchmaking lambda function look like? It seems that you are returning the entire ticket object rather than just the ticketid.

    • @ChasseuseUrtem
      @ChasseuseUrtem 4 роки тому

      @@Flopperam and you right ! I fail my lambda :
      ticketId = data.MatchmakingTicket;
      instead if ticketId = data.MatchmakingTicket.TicketId;
      Thank's for your quiet response ! :)
      PS : i'm not psycko, i have just two youtube chanel x) sorry for this

  • @HS-zk5nn
    @HS-zk5nn 2 роки тому

    Hi Flopperam, I was so close to getting to the end and I transferred my map to the project. Now whenever I press the Matchmaking Button in the Main Menu widget, the button just goes grey for about a couple seconds or so, and then turns back to normal. And in the middle of the screen that used to say "Searching for Game" is now blank and it wont open the new level.
    I checked the log file in aws; the player logs in okay; and a startmatchmaking log is created in aws in cloudwatch. Not sure what happened. I am so close wrapping this up, and it is probably something really silly that I did. can you please help?

    • @Flopperam
      @Flopperam  2 роки тому

      Hey, can you check cloudwatch logs for the TrackEvents lambda function to see if maybe the matchmaking requests are timing out or getting canceled?

    • @HS-zk5nn
      @HS-zk5nn 2 роки тому

      @@Flopperam hmm it looks like TrackEvents isnt getting updated. Not sure why..

    • @Flopperam
      @Flopperam  2 роки тому

      @@HS-zk5nn Double check that the matchmaking configuration specified in your StartMatchmaking function is correct. If it is, then make sure that you have the correct SNS topic set up in that matchmaker and that the TrackEvents lambda function is subscribed to that SNS topic.

    • @HS-zk5nn
      @HS-zk5nn 2 роки тому

      @@Flopperam Hi. I double checked all of them. I think they are all correct. aws changed dynamo db, so you cant see ids there. But when I did the test event (from amazon, not from Join Game), it published to the track events in cloudwatch.

    • @HS-zk5nn
      @HS-zk5nn 2 роки тому

      @@Flopperam so the test works, but the join game still doesnt. sorry if I wasnt clear before

  • @DevLu
    @DevLu 4 роки тому +1

    Good job bro

  • @ahmedhassn8828
    @ahmedhassn8828 4 роки тому

    Hello again, i followed the tutorial till this one, but the two windows can't join one to another, both of them create a ticket id. i copy your VS code to mine, almost every .cpp copied from you with some code removed that doesn't match with my .h files, so why there is no connection !?

    • @Flopperam
      @Flopperam  4 роки тому +1

      Hey, can you make sure that the queue attached to your matchmaker has a destination pointing to your latest fleet?

    • @ahmedhassn8828
      @ahmedhassn8828 4 роки тому

      @@Flopperam attached correctly , the fleet attached to queue, the queue attached to matchmaker, it looks like every thing is fine as videos

    • @Flopperam
      @Flopperam  4 роки тому +1

      @@ahmedhassn8828 In your fleet, is there an instance with at least one available server process for hosting a game session?

    • @ahmedhassn8828
      @ahmedhassn8828 4 роки тому

      @@Flopperam I've 1 active instance, 2 active servers, 0 game sessions, 0 of 0 player sessions, and fleet type is spot. The fleet status is active

    • @Flopperam
      @Flopperam  4 роки тому +1

      @@ahmedhassn8828 Was anything written to the MatchmakingTickets table in DynamoDb?

  • @eng.yousifa.bshara8235
    @eng.yousifa.bshara8235 4 роки тому

    this tutorials work on Android????