GML Trying to understand how to network

Discussion in 'Programming' started by XirmiX, Jul 3, 2017.

  1. XirmiX

    XirmiX Member

    Joined:
    Jul 18, 2016
    Posts:
    365
    I've went through several tutorials, read some things up in the manual, and I just don't get it. Various sources give me various things, and I understand that it depends on the type of server you want, but I just don't understand the various mechanisms behind them.

    I know you need to, at least for me:
    * connect the client to the server
    * have data be transfered between the server and the client (id, player username, latency, fps, keys pressed by a player and probably more)
    * disconnection of the user
    * letting other clients know about each individual other client

    But where do I start? What do I need to do to start to understand it and to understand what I need and where? I know I need a tcp server, and I still don't understand the difference between txp and udp, like... if you connect, you connect, what's the debate here?
     
  2. andev

    andev Member

    Joined:
    Jul 2, 2017
    Posts:
    444
    The difference between TCP and UDP is reliability and speed. TCP is slower but everything arrives in order, UDP is faster but data doesn't arrive in order, and sometimes doesn't arrive at all.

    I know it's not really helpful advice, but you should try reading through those tutorials again! A google search gets me these (which I know are good).
    We can answer your questions but we can't learn it for you!
     
    Last edited: Jul 4, 2017
  3. XirmiX

    XirmiX Member

    Joined:
    Jul 18, 2016
    Posts:
    365
    There's so much that's going on with this, so how am I supposed to remember all of it? I can't, and as a result, while I do understand things on their own, following tutorials, reading up guides doesn't help me. What do I do? Take this tutorial for example:


    ^That so far has been the best tutorial for me and yet it STILL can't help me out.

    I went through with it, and got this and I'm getting errors:

    Client
    ___________________________________________
    ############################################################################################
    FATAL ERROR in
    action number 1
    of Alarm Event for alarm 0
    for object obj_connection:

    Unable to find any instance for object index '3' name 'obj_player'
    at gml_Script_scr_clientMovement (line 3) - buffer_write(send_buffer, buffer_u16, round(obj_player.x));
    ############################################################################################
    --------------------------------------------------------------------------------------------
    stack frame is
    gml_Script_scr_clientMovement (line 3)
    called from - gml_Object_obj_connection_ObjAlarm0_1 (line 1) - scr_clientMovement();

    Server
    ___________________________________________
    ############################################################################################
    FATAL ERROR in
    action number 1
    of Async Event: Networking
    for object obj_connection:

    Variable obj_connection.socket_id(100006, -2147483648) not set before reading it.
    at gml_Script_scr_serverDisconnection (line 4) - with(client_id[? string(socket_id)])
    ############################################################################################
    --------------------------------------------------------------------------------------------
    stack frame is
    gml_Script_scr_serverDisconnection (line 4)
    called from - gml_Object_obj_connection_NetworkingEvent_1 (line 13) - scr_serverDisconnection(async_load [? "socket"]);

    And the thing is, I got through the first tutorial, understanding most things, then going through the second made a lot less sense after, or at least I couldn't put things together to understand them. I tried to keep the code the same as possible while having two projects running instead of all being placed in the same project, as was the case in the tutorial. And this is what happens. I've spent hours and days on this crap. How do you learn this ****?

    I just can't get through it. I understand the principles, I understand, for the most part, how to send and receive buffers, create a server, connect to a server and so forth. But I can't put it all together. And the tutorials out there, start out fine, but end up derailing into just this ball of mess where you just do something so it works, without any explanation of why what is being done is being done and why it's necessary.

    EDIT:
    I will say that you explained TCP and UDP well, however, I already found out about it elsewhere, so my knowledge about it might have already influenced that.
     
    Last edited: Jul 4, 2017
  4. XirmiX

    XirmiX Member

    Joined:
    Jul 18, 2016
    Posts:
    365
    Okay, the questions about the tutorial for me are as follows:
    1) What is "client_map = ds_map_create();" for? What is it and why do I need it?

    2) What is "byte alignment"? See buffer_create in manual for where it's used.

    3) How does the "return" function work? I've searched about it before, but never really comprehended what it does and why it's needed and when it's needed.

    4) How do the keys in async_load do anything? They're just variables, how can they respond to vital sending data? What exactly do they do and how, and why are they needed?

    5) How does a socket work? It's been grinding my gears ever since I've started going through tutorials and the manual. Still don't fully understand it!

    6) From this tutorial, what is "client_id" supposed to be? It's not a created variable anywhere that I can see. First seeing it at 18:24. And no, in the next tutorial, you do not set the client_id anywhere for the client. You can see for yourself here.

    7) "while(true)" <= this appears in the tutorial for some of the scripts. How does this make any sense? While WHAT is true?

    8) "with(oServerClient)" <= what is this and why is it needed in the type of a server system outlined here?

    9)
    if (buffer_tell(buffer) == buffer_get_size(buffer))
    {
    break;
    }

    ^for the above, FIRSTLY, why shouldn't it be "if (buffer_tell(buffer) > buffer_get_size(buffer))" instead? This, to my understanding, is supposed to not send any information if the information in bytes exceeds the byte side of the buffer being sent. But... SECONDLY how is the placement of this if statement appropriate to do so? You do all of the reading and writing and sending within the switch statement, while the if statement is outside of it. How is this supposed to help when it's outside of it?

    10) From the tutorial mentioned before, why can't client_send_movement script code be inside the client_send_message script? Or can it, and it simply isn't because something something something? How would I fit it in there without breaking everything along the way, as splitting this networking system's equivalent into two projects did?

    I Think that's it... Think... Perhaps I should have done this from the beginning... will see if I get any answers, though.
     
  5. FrostyCat

    FrostyCat Member

    Joined:
    Jun 26, 2016
    Posts:
    3,858
    Seeing how the last few multiplayer topics you made have turned out, I've decided that throwing cold water on your aspiration is the most productive action to take.

    It's easy to tell where a programmer is by the questions he/she asks, so I'll start with evaluating yours. Almost every question on post #3 and #4 are about basic GML, only two are specific to networking. That's a big red flag.

    You don't know how to handle "undefined variable" errors. You don't know how to count from zero. You don't understand basic vocabulary. You don't even know basic GML statements. This will all end badly if nobody tells you the hard truth. You are trying to build a skyscraper with no piles in the ground.

    In the defence of the authors of tutorials you read, I want you to quit blaming them for "not being explanatory enough". Multiplayer development is an advanced topic that requires you to have mastered basic topics beforehand --- the jargon, the techniques, the general concepts. They have every right to expect you to know this stuff, and you demonstrated quite clearly that you don't.

    As it stands, you aren't even worth a level 1 on your "programmer scale". Quit rushing ahead without the basics that tutorial authors can reasonably demand of you, and perhaps you would be able understand what they mean and get something done.
     
  6. Tsa05

    Tsa05 Member

    Joined:
    Jun 21, 2016
    Posts:
    539
    You mayyyy wish to shelve the multiplayer game for a bit, and try out a mini project in order to get better acquainted with GameMaker itself.

    For example, can you make a model of a game where there's a few "dumb" computer players wandering around? If you can, then...can you modify that sort of game so that one "controller" object keeps track of each computer player's data instead of each separate computer player object?

    You'll need to be able to do this, in order to make a reasonable network game. I mention it because some of your questions would be answered in this exercise. For example, your questions about ds_map_create(), return, client_id, while, and async are not networking game questions; these are aspects of how any programming within GameMaker works, and you'll need to be entirely familiar with them before you consider a networked game (which will have you employing these things in more complicated ways).

    Some of your questions sound specific to a certain tutorial that I'm not looking at. Can't help you there. But:
    1) A ds_map is a data structure that stores information. You already know how to store a single piece of information, right? health = 100? That kinda thing?
    Sometimes, you need to store a whole set of associated information. That's where a Map comes into play. You've *got* to look up ds_maps in the manual, though. The only way to know how to use them would be for me to describe each gameMaker function that's used with them. The manual already does this, and more thoroughly than I can. Basically, a map lets you store multiple pieces of data paired with names to reference the data, all under a single variable.

    And why do you need it? Many reasons. It's a fundamental part of programming, not just networking games. In the case of a networked game, you might use a ds_map to store all of the information about a player's connection to your server under one variable. Info like IP address, port information, connection status, etc. Or you might use a ds_map to store details about a player or NPC--things like x and y coordinates, status effects, and so on. It's just a way to group info together.

    2) I don't get it. You linked the manual article...what more can I tell ya? If you plan to read and write data in certain-sized chunks that are powers of 2, then...use the alignment that matches that. If you need to read arbitrary amounts of data, then you've got to use 1, which tells it to read one byte at a time.\

    3) Return isn't a function. It's the result of one. You presumably already know that you can "call" a function and pass in data within (parenthesis). That is the data that the function will use to do stuff. Well, return is the answer that gets sent back. If you do something like myNewObject = instance_create(x,y, obj_something).... The variable "myNewObject" now contains a reference to the newly created object. But that's because YYG programmed the instance_create() function to return that information once the object is made.

    Whenever you make a function that computes something, you've got to have it return the answer at the end of the script. Otherwise, the computation performed by your script is basically lost.

    4) Don't worry about how async does it. It just does. For example, when you tell the computer this:
    Code:
    get_string_async("Message", "")
    The computer will display a dialogue box. When the user types something in and presses OK, the box vanishes. What happened to the text they entered?!?!?! Well, the Asynchronous Dialog event gets triggered. Automatically. Don't worry. It happens. So, if you do not put any code into your game that says what to do during the Async Dialog event, then nothing happens. Not because the event failed to trigger, mind you. Nothing happens because you never defined what to do if the event triggers.

    Same with networking. When you send out network traffic and connect to a server and stuff--that's all Async stuff also. Don't worry about how it does its thing. What you need to worry about is what to do when it does its thing. In other words, you'll need to add an Async Networking event to an object somewhere. You'll need to add code that checks the incoming data and decides what to do. Don't worry about how the Async network event happens. It will happen when a networking signal comes through.

    The variables in async_load, are not magical. Hopefully, you understand ds_maps a little by now? If not check the manual about those. Because async_load is simply a DS Map. It's a map containing data about...something that's been loaded. The exact contents of the map depend upon what is being loaded. You want to know how it affects your game? Well, how does the variable "health" affect your game? It doesn't--unless you make it. It's just a piece of data about a status. You can check against it to see whether something should happen.

    In the same way, you can ckeck keys within async_load to determine whether something is done loading, or whatever, and you can then decide what should happen next in your game.

    5) It's magic. Don't worry about this until you're long past this point. You've got to learn about functions, async events, and data structures.

    6) It's a return value from a networking function. Returns aren't so scary now, right?

    7) While is a common programming keyword. It tells the computer to repeat a block of code until a condition evaluates to true. Saying while(true){ } sets up a theoretically infinite loop. "True" is a built-in value that always evaluates to true. So, the block of code will execute continuously.

    This would freeeze your game, normally. But I suspect there's lots of break statements in there, too, right? The break statement causes a loop to end prematurely--before the condition at the top is met. People sometimes use while(true) as a way to set up a repeating process in code. Often it's something like reading through the data in a buffer. There'll be some condition in there that breaks the loop when the end of the buffer is reached.

    8) I'm not looking at the specific example, but the with statement causes code to be applied to some other object. For example, if I wanted an object to set the position of some other object, I can't do:
    Code:
    x = 100;
    y = 200;
    Because that would set *this* object's position. Instead I can do this:
    Code:
    with(obj_otherObject){
         x = 100;
         y = 200;
    }
    This will find ALL objects of type "obj_otherObject" and will cause ALL of them to run that code on themselves.

    9, 10) Specific to the decisions made by the guy making the tutorial. Hopefully, with an understanding of Maps, Async functions, and a few of GameMaker's built-in structure, these will be self-evident!

    Again, I urge you--drop the networking part and try filling in NPCs instead of live players. Control the NPCs from one central controller object that tells them to move around.
    If you can make this work, it's a much shorter step to turning that into a networked game.
     
    Monsi and JackTurbo like this.
  7. XirmiX

    XirmiX Member

    Joined:
    Jul 18, 2016
    Posts:
    365
    @FrostyCat I was kind of expecting/hoping this response from you. Thank you for that, to be honest and... I'm stunned by how polite everyone is here. Normally, I'd have people creating flame wars as a result of me being angry about something on other forums xD

    I'm sure not as experienced as I might have first thought, but I don't think I'm as newbish as you might think. I mean, I can perfectly understand stuff when it comes to physics' forces and collisions... mostly... I get stuck when I need to use SohCahToa because it has not made any sense to me (although I've not got kind of an idea of what it's for, though still no idea how to calculate whatever I want with it).

    Although you are correct in that I don't know all of game maker. I don't know why I would need to in order to make a simple connection and data transfer system, accompanied by drawing out of certain things... like would I really need to know how to mess around with shaders just so I could send data from and between clients through a server? I doubt it.

    And yes, I don't know how to get through "undefined variable" errors... if by that you mean that the error says "variable <unknown variable> has not been defined". If it tells me what the variable is that I need to fix, then most times I can fix it. The times I might not is when the variable IS declared and it still gives it to me, which I can only imagine happening if I call a variable before declaring it, but I don't know how to get through that in a mess of a networking system.

    @Tsa05

    1) I know what a ds_map is. In fact, in one of my threads, I think I mentioned that I understood that it's basically a variable version of an ini file, or like a ds_list that has a list of keys that each represent a value, instead of just values themselves. What I didn't understand was specific to the tutorial; what the ds_map is used for exactly.

    2) Because I didn't understand what "alignment" means. I've never used the word in my entire life. I first thought that it's the number of the cases that you're sending this data to... which now that I think about it doesn't make sense, that's what buffer_read is for... Anyway, so, you briefly go over the fact that it's some form of way of reading the file? I still don't get it. When creating a buffer, what am I aligning exactly?

    3) Call me an idiot, but... I still don't get it. Functions section in the manual goes briefly over "input" and "output" of functions, which I have no idea what that means. Well, I mean, I know what input and output is, but not in this context, I don't. So, what does return do again? :p I swear I'm not being a troll!

    4) Are you sure about that? I think it's better for me to understand it so that I would know why I need it there and be able to use the map correctly using my own head and not just out of me getting used to using it the way I do, without any idea why.

    5) Functions (aside from whatever the hell the "input" and "output" shenanigans are) I do comprehend. Most other programming languages let you use functions to assign them as commands for something to do something. The same kind of goes here, although it's mostly compressed into functions that are aready built-in and... I think I've only once seen a custom-created function in GML and that was from an extension.

    Async events... well, where do I start? And would I really need to know anything beyond the Networking function for what I'm trying to do here (a 2D MMO tank game)? Perhaps if I were to make a web site that holds onto a server list, but other than that, idk.

    Data structures? Wow... I didn't know these even existed... DS Stacks? The manual doesn't explain it well for me. It made me imagine a stack of coins and me being able to "push" or "pop" values held inside a stack, but I have no idea what that means. Same goes for queues, priority queues, . Grids, I think I know what this is. Although, what is the purpose of these? Just use lists and arrays, these data structures seem like niches that could easily be replicated using something much more basic. Will I ever be using most of these when making games? I mean, you can make a grid using lists or arrays anyway, can't you? Speaking of which, what is the difference between an array and a list exactly? They seem like the exact same time.

    6) They still are; what are they?

    7) Well, of course I know what "while" is, you don't need to tell me that, in fact, I've taught some other peeps about "if", "while" and "for" statements. I thought that my explanation would point towards what I'm not understanding there. The whole "while(true)" thing, which I'm still confused by. Why is it necessary in the context that it's presented in? Or, perhaps why would you use such a computer-freezing monstrosity as a forever true while loop?

    8) Again, what you're talking about is what I already understand. What I DON'T understand is why an empty object is refered to there. Why it's being used in the context that it's presented in. And why would this object, that is used by the server, be used by the client as well, in the context that it's presented in. You don't simply share objects between a server and a client, the server's gotta send that **** to the server to allow permission to it, yet, since both client and server are created in one and the same object, it's somehow fine to do so, for some reason. Well, if you took this project and tried to separate the client and the server by both having different projects to be set into, you couldn't, precisely because of this object. Moreover, I get senses that this type of linking, through this object, will subject the game to extremely easy cheating as a result. And I certainly don't want that for my game, where some haxter ruins it for the others and the server needing to be shut down just because some tool thought it would be funny. I want to make sure that the server decides on the parameters of everything in the game, draws everything in the game and simply sends the information to clients, with clients only forwarding themselves, their upgrade and equipment input as well as the keys they press, everything else being held by the server to keep a fairer game. Having clients define where their opponents are on their screen... not a good idea, I think.

    9 and 10) I'm sure I could easily make an NPC with one central object telling them what to do... in fact, I've done that already while trying to help somebody out, where the controllable cube made a whole bunch of other cubes spawn from the global position "0, 0" and move towards it (and destroy themselves once they touch the movable cube). Will talk about more of this below...

    So, what do you guys think I need to teach myself before doing networking? I know it's sockets, though the information on it isn't that well explained to me. Data Structures... well, I understand lists and maps, I just don't understand async's magic. So... anything else I really need to know? Perhaps more on the draw event, though, I generally understand its principles and a lot of the functions that it uses. What I want to know is whether the server can handle all of the data of players and hold onto all of the map data, while it all just gets sent and drawn for the other clients.

    Thing is, I don't know what I need to know before I can start understanding networking fully. It's just these weird anomalies that just don't make sense. I still don't know where in the tutorial linked in this topic the "client_id" is declared or what it's supposed to represent exactly.
     
    Last edited: Jul 5, 2017
  8. The-any-Key

    The-any-Key Member

    Joined:
    Feb 2, 2017
    Posts:
    1,477
    I got a tutorial series here.
     
  9. XirmiX

    XirmiX Member

    Joined:
    Jul 18, 2016
    Posts:
    365
    @The-any-Key huh... I would really want to attempt doing all the networking myself, so I know the ins-and-outs of the stuff in my own game. However, if I really don't make any progress on this in the next few days, I guess GMnet is something I could use as a starter.

    At the moment, I think I'm close to understanding most of the basics of GM networking, but I simply don't know what I need to learn more about to get there.

    EDIT: I think understanding sockets is all I really need right now to progress. I'm guessing sockets are a way for the server to pick certain clients and determine which client to remove the data of when a client is disconnecting. So, how do I do this? I'm in the middle of writing code and this is what I have:

    Server object Create event
    Code:
    server_trype = network_socket_tcp;
    port = 50000;
    max_players = 8;
    server = network_create_server_raw(server_type, port, max_players);
    socket = ds_list_create();
    
    Server object Networking event
    Code:
    type = async_load[? "type"];
    
    switch(type)
    {
        case network_type_connect:
            socket_list = async_load[? "socket"];
            buffer_seek(buffer, buffer_seek_start, 1);
            client_socket_current = buffer_read(buffer, buffer_u8);
            player_username = buffer_read(buffer, buffer_u8);
           
            ds_list_add(socket, client_socket_current);
       
       
        break;
        case network_type_disconnect:
            socket_list = async_load[? "socket"];
           
       
        break;
        case network_type_data:
       
       
        break;
       
    }
    
    
     
    Last edited: Jul 5, 2017
  10. Tsa05

    Tsa05 Member

    Joined:
    Jun 21, 2016
    Posts:
    539
    I believe that the primary reason that you're encountering difficulties with getting clear responses has to do with how you're asking. It might be that you're trying to be light-hearted, casual, or humorous--I'm not sure--but it's confusing things. When you point out a line that says while(true) and ask what it does, we will tell you, in some detail, what it does. It turns out that you already know, though? You're underplaying your current level of programming knowledge, making it difficult to know what you need explained.

    I did, I hope, make it clear about the while loop in my reply, though. It's set up like a continuous loop, and it will break whenever any end condition is met. If you want to know *exactly* why it's set up that way, then...you've got to ask the guy who did it. There's many ways to make something happen in code. But I suspect it's something like this:

    The author has a large block of code that figures out what to do with data. The block of code needs to run many times. It's placed into a loop.
    The loop should run many times, but the exact number of times is not known in advance because it depends upon how much data must be processed.
    There are many different ways in which the loop could "finish" going through the data.

    With this in mind, the author could write this:
    while( (condition1 or condition2) and (condition3 and condition4) or (condition5 r condition6) ).....
    In other words, the author could list every possible circumstance in which the loop should keep running. This would probably make for a long list though. What the author has done instead is to start looping continuously, and to place break statements to make the loop exit whenever it ought to. It's just a stylistic choice.


    Input and Output of a function:
    Are you familiar with the sqrt() function? It computes the square root of a number. So if I do this:
    Code:
    value = sqrt(9);
    value is now equal to 3. I hope this is common ground. This makes sense, right? Value is equal to the square root of 9.

    sqrt() is actually a function, and it has an input and an output. Inputs go in parenthesis. In this example, there's one input, and it's the number 9.
    When you tell GameMaker to do the function sqrt() with the input of 9, it goes off and does some math. When it is done, GameMaker returns with the answer. That's the output of the function. So, in the example above, I'm setting "value" equal to the output of the sqrt() function.

    Most functions have this kind of thing. When you create a new network connection, for example, the output of the function is a lookup number. That lookup number can later be used to check on the status of the network connection. It's really important for functions to output stuff--otherwise we can't use the results to move on.
     
    XirmiX likes this.
  11. XirmiX

    XirmiX Member

    Joined:
    Jul 18, 2016
    Posts:
    365
    @Tsa05 sorry, I didn't notice your reply!

    In terms of while(true), your explanation made a bit more sense, but I still can't comprehend why someone would do this. I think I would need an actual, simple and working example of while(true) in order to understand how to use it. I'm pretty sure I can use a while loop, but only if I set a condition first, otherwise it seems like a complete non-sensical mess to me. The pointing out of me appearing to be trolling, was to confirm that, in case you thought I was trolling, that I wasn't, and that I really did not understand while(true) at all.

    Yes, I understand square root (though I've never known there was a built-in function for it in GML). In the context of that function, it makes perfect sense... but what if the function was something a bit more tricky, say, instance_create? The input essentially is the output, or at least the output isn't really a variable you can define, but an action; an action sent to the engine to spawn an instance of a certain object in a certain area. Also, when using return function, where does it return whatever it returns to? I see the return function being used a lot by scripts, so, does that mean that the value will be returned to the object/script or whatever that called it?

    Thank you for all the help so far! Your explanations do help!
     
  12. Tsa05

    Tsa05 Member

    Joined:
    Jun 21, 2016
    Posts:
    539
    instance_create does actually need a return value, but to make it clear why, I might have to explain a few things about how GameMaker is working beneath the surface.

    Suppose we work with the following code:
    Code:
    instance_create(x, y, obj_something)
    This will create an instance of obj_something at position x, y. Simple enough so far, right?

    There's 3 inputs involved with creating a new object:
    1) the x-position of the new object
    2) the y-position of the new object
    3) the type of object to create

    The output is a lot less obvious, though. You might see the object appear on the screen, right? But that's not the "output." That's just the object, drawing itself. So, what's the output?

    Well, every time that you create an object in GameMaker, it is given a totally unique number (usually something like 100001, 100002, etc). These numbers are chosen at the moment that an object is created. There's not really a way to know ahead of time what an object's number will be, since it all depends on what number GameMaker decides to start with, and on how many other objects have already been created.

    So, what do you do when you want your program to interact with a specific object? ....For example:
    When I click a button on the screen, I want it to cause my player object to move.
    It's just an example, so bear with me. How could I make this kind of thing happen? How can a button (obj_button) change the speed of a player (obj_player)?
    Here's one way:
    Code:
    obj_player.speed = 10;
    If the button executes this code when clicked, the player object's speed will change.

    But there's a problem

    What if I have several players on the screen!? They are ALL instance of obj_player! It's possible to make many copies....
    The code above will only make one player object change it's speed, and I don't even know which one. It depends, basically, on whichever one GameMaker has first on its internal list of objects. That's a really imprecise way of doing things.

    How to make a specific instance of obj_player do what you want:
    You've got to refer to it by it's super-unique number that was assigned when the object was created. That way, GameMaker knows that you want to change the speed of just that specific object, instead of changing the speed of "any one object that happens to be a player object."

    Remember, though, that the unique numbers are assigned spontaneously, as the code runs, so there's no way that you'll know what those numbers are...is there?

    And that's where return values come back to rescue us. It turns out, the output of the instance_create function--in other words, the value that is "returned"--is the unique number for the newly created object.

    Revisiting the code, then:
    Code:
    mySpecialObject = instance_create(x, y, obj_something)
    I can store the returned value of instance_create in a variable. Now, if I ever need to affect that object, specifically, I can do this:
    Code:
    mySpecialObject.speed = 10;
    This is the first step in a larger journey. Consider the following:
    Code:
    player1 = instance_create(x, y, obj_player)
    player2 = instance_create(x+50, y+50, obj_player)
    
    player1.hp = 100;
    player2.hp = 200;
    
    player1.speed = 5;
    player2.speed = 4;
    I can create 2 copies of the same object, and then change certain aspects of each one separately, since I've got the return values.

    Do you see how this will apply to a networking game? The server will send data to each player's computer about the position of all players. Each player's computer can use that data to update a list of objects with new position information. The list of objects will actually be a list of return values from instance_create.
     
  13. XirmiX

    XirmiX Member

    Joined:
    Jul 18, 2016
    Posts:
    365
    @Tsa05 return truly is something weird, but I guess that makes sense. Question is, in that type of scenario you've set out, would I need to use the Return function? Or, more concretely, is this true:
    I'm guessing that there is no way simply for the server to have everything happen in it and then send information to be drawn for the clients and you actually need all of the objects used in the game be for both the server and the client, right? And each of those objects would have instances for all of the players as well as the server, so, if 5 players have connected, those 5 players will need on their client side an object that would represent all of the controllable player instances, and the same goes for the server itself, right?

    Also, is it important to understand while(true) way of doing a while statement, or would I just be fine with the standard method of while(something == true)?
     
  14. Tsa05

    Tsa05 Member

    Joined:
    Jun 21, 2016
    Posts:
    539
    Return always returns to wherever it was called from, yes.

    The server *can* have everything happen in it, but this rarely happens in real multiplayer games. It is inefficient for the server to run the game, with all of the collision code and everything, especially since a server might actually host the data for several games at once! But it comes down to design decisions with your game. I don't know how your game is structured, but I'll make up a simple example, and then describe one way it could be done. I hope that this won't be too vague...

    We have a game consisting of 3 objects: The client-side controller object, the playable player object, and the "dumb" player object. The "dumb" player object is an object that draws a player, but does not have any events set up for responding to player inputs like keyboard presses and stuff. The playable player object does have controls.

    Suppose that at the start of the game, 5 players appear in a room together. Let's say we see them from the top-down.
    Each player is at a separate computer. When the game begins, an object is created in the room, and the object represents that one specific player.
    The player's object responds to keyboard inputs as usual.

    But then, there's a second object--the client-side controller object. This object establishes a connection to the server. It receives a buffer containing specific pieces of information. Maybe something like this:
    (number of players) (ID of receiving player) (x-position of first player) (y-position of first player) (direction of first player) (x-position of second player) (y-position of second player) (direction of second player) ......and so on.

    The controller object checks the first piece of data in the buffer. It's a number. This is the number of connected players. The controller object counts how many "dumb" player type objects exist in the room. There should be 1 less "dumb" player object than the number of players. In other words, if there's 5 players in the game, then there should be 4 "dumb" player objects in the room. And one object that can be controlled by the player.

    Of course, when the game first gets going, there's not any "dumb" player objects yet. Just the one controllable one.
    Soooo, you do a loop. This would be inside of an async event, when a network message is recieved. This is not complete code, but an example of one portion.
    Code:
    var numPlayers = buffer_read( myBuffer, buffer_u8);
    var numDumb = instance_count(obj_dumbPlayer);
    var numToAdd = (numPlayers-1) - numDumb;
    for(var i=0; i<numToAdd; i+=1){
         newObject = instance_create(0,0, obj_dumbPlayer);
    }
    
    Does the code above make sense? I hope so? It's not code that you can use as-is.... It assumes the existence of something called myBuffer, which is a buffer that would come from the server. It also assumes that there's a 1-byte value at the start of the buffer, which says how many players are in the game. But more importantly, do you see how one object could create many "dumb" player objects?

    Networking is almost beside the point--the variable numPlayers could have been set in the Create event, if this was a single player game with NPC players. Instead, it's set by the value from a server. Either way, same result: The controller object makes a set of objects equal to the number needed to make a full set of players.

    So, we know that the player can control their own object...but how do the other objects move around?
    Well, suppose that the server is constantly sending buffers like the example I mentioned. Every time a new one is sent, it contains the current number of players, followed by the x,y,and direction for each object. The controller just needs to use that info. Remember the examples where I was using an object's ID number to set a value for that object? We'll need to do that now...

    And that's where data structures are super handy again. Instead of storing the return from instance_create as a single variable, newObject, which will be overwritten every time a new object is made, you could add the value of newObject to a ds_list.

    Once you have a list of object ID numbers, it's easy:
    Loop through the list, dealing with 1 object at a time
    For the first object in the list, apply the first x coordinate, y coordinate, and direction.
    Move on to the next item in the list and next trio of data pieces read from the buffer.
    Repeat until you're through the list.

    Easy, right? The controller object has a list of "dumb" objects and a buffer full of data groups that need to be applied.

    The one slight hiccup is that the numbering might have to adjust:
    Suppose I'm player number 3, right? In programming, we start counting at zero, so I'm the 4th player--but number 3. Here's the buffer from the example above, using abbreviations:
    [N][P][x0][y0][d0][x1][y1][d1][x2][y2][d2][x3][y3][d3][x4][y4][d4]
    I've hilighted in red the portion of the buffer that would correspond to our player character. The rest of the sets of data would control "dumb" players.

    Now, suppose that the controller object made 4 "dumb" player objects in a list. That looks like this:
    [obj0]
    [obj1]
    [obj2]
    [obj3]

    So, we can just apply x0, y0, d0 from the buffer to obj0 from the list.
    And we can apply x1, y1, d1 from the buffer to obj1 from the list.
    And x2, y2, d2 can apply to obj2.
    But you can't apply x3, y3, d3 to obj3 in the list. Remember, since I'm player #3, that data set in the buffer does not apply anyone in the list.

    I assume that the logic is easy to work around though, right?
    As you look through the list, you just check whether the player number (second piece of data in the buffer) is equal to the current set of data you're reading from the buffer. If so, skip it and move to the next one. :)

    It's a lot of words to say it all out, but the concept is really simple. You have a list of "data sets" (x, y coordinates and other info). You have a list of "dumb" player objects you've created. Apply the data to the objects.

    As for the while()... Do what you understand best. If, later on, it becomes more clear why small changes in style might be more time-saving or whatever, it's easy to change.
     
    IndianaBones and XirmiX like this.
  15. XirmiX

    XirmiX Member

    Joined:
    Jul 18, 2016
    Posts:
    365
    @Tsa05 I'll get back to this tomorrow, and hopefully have something functional by the end of the next day. One last question before I head off for the day is this: in the server, how do make it so that it scrolls through a list (e.g. the socket list) and sends data of one player to all players, except the player it came from? It's the main thing that's been keeping me from setting up some form of connection and it's irritating that I can't figure it out myself. So, lets say, I join a server, and I want the server to send out information (the amount of sockets, ID and ) to the other 4 players. How would I do this? Here's what I have so far (this is the connection case in a switch event, which handles connecting):

    Code:
    //case for the client connecting
        case network_type_connect:
            client_socket_current = async_load[? "socket"];
            //buffer_sent = async_load[? "buffer"];
           
            //Read the buffers sent by the joining client
            buffer_seek(buffer, buffer_seek_start, 1);
            client_id = buffer_read(buffer, buffer_string); //Client's ID number
            player_username = buffer_read(buffer, buffer_string); //Client's username
           
            //Add new player socket to the socket list
            ds_list_add(socket_list, client_socket_current);
           
            var client_amount = ds_list_size(socket_list);
           
            //In the client_data map, create a new key with the ID of the player, which will hold onto the user's username
            //client_data[? string(client_id)] = player_username;
           
            //buffer_write(send_buffer, buffer_string, client_id);
            //buffer_write(send_buffer, buffer_string, player_username);
           
            //If there are now 2 or more users connected, send data about the new client to other clients
            if (ds_list_size(socket_list) <= 2)
            {
                for(i = 0; i < ds_list_size(socket_list); i++)
                {
                    network_send_raw(socket_list.[here I need to refer to any other client that isn't ], send_buffer, 47);
                    network_send_raw(socket_list, send_buffer, 46);
                }
            }
           
       
        break;
    
    The ID, player username and socket amount is probably something the server wants to send to all clients, including the one that joined and as a result sent it to the server itself, so that less complications occur in this scenario. But, since I have this code done prior to creating player objects and "dumb"player objects, well, might as well see it applied for what I have, so I can then use it for other things. If I get the added info for this code, I'm sure I can handle a lot of other stuff much more easily and won't need to create a hundred topics just to understand networking better.

    Oh, and, I'm kind of unsure on calculating the amount of bits I will need to send to players... The ID strings will be 16 characters long, so that's 16 bytes (or so I read, about C++ at least) and the usernames will be up to 30 characters long, which equates to 46 bytes total. Now, there's also the socket list... which I don't know how much will be. The socket will probably be an 8-bit number, so, I could only assume that I would need to send a total amount of 47 bytes, although I'm not too sure.

    Btw, I will take your word for it and have the game run efficiently, as you suggest, by having the game run for each client based on the data of the server and not have the entire game run on the server. I hope this doesn't leeway players to cheat in extremely easy ways within the game. I will have the stats, such as speed, damage, health etc. assigned from the server to the clients, however, for obvious reasons. And sure enough, my game will be top-down... a side-based tank game would just be weird (perhaps not unplayable, but weird), you don't want to remove the left-and-right dimension to allow for the up-and-down, unless it's a platformer.

    Thanks for all of your great help. While I already figured out a lot about this myself, confirmation from someone who knows their stuff is always great.
     
  16. Tsa05

    Tsa05 Member

    Joined:
    Jun 21, 2016
    Posts:
    539
    client_socket_current = async_load[? "socket"];

    It looks like your first line of code in there is looking up the socket of the player who just connected...

    So you should be able to skip over that one by using an If statement within your for loop.


    The code you posted, though, confuses the heck out of me. Network packet sending usually looks like this:
    Code:
    network_send_raw(socket, buffer, size);
    It appears that your code has substituted a socket with a ds_list. This won't work. Or, actually, it will appear to work, then will stop working when least expected.

    Let's review--follow the bouncing data here.
    For starters. This block of code we're looking at is what happens when a client makes a specific type of communication with the server--a "connect" request. So remember, this is the code that happens when someone calls up the server and asks to "connect." Which actually, deep under the hood, means "please create a socket thing, which lets us talk, and then return the ID number of the socket."
    Code:
    client_socket_current = async_load[? "socket"];
    So at this moment in the code, a connect request was received and GameMaker is processing it. The magic of sockets has already happened, and one has been made. The specific ID number of that socket is currently being stored in a special data structure (a "map") called async_load, which GameMaker has created specifically for this purpose. The line of code above looks into the Map, and retrieves the special socket ID number, which we will need for later use. This number is important! We use this number as an input with networking functions, in order to tell them which socket to use under the hood.

    So now the socket ID of the connecting player is stored in client_socket_current.

    Code:
    //Add new player socket to the socket list
    ds_list_add(socket_list, client_socket_current);
    A data structure (a "List") appears! There is a list, already created earlier in your game, called socket_list. It is going to be a list of every single socket ID number that's connected to the server. With this list, you will be able to know how to talk to all connected players, one at a time, by sending data to each socket ID number on the list. As the command says, you're adding a value to socket_list, and the value you add is client_socket_current. As you know, client_socket_current is the most recent player who has requested a connection. So now, the most recently connected player's socket has been added to the list of connected player sockets. Remember, a socket is just a number that refers to an underlying way to talk to a specific client.

    Code:
    if (ds_list_size(socket_list) <= 2)
    I'm not completely sure why this is here. Seems like it should be >=2 instead... But anyways. The important thing is that ds_list_size() is a function that returns how many items are in a list. You provided socket_list as an input to that function. Remember, every time a connection request arrives, the socket that was created by that connection is added to the list. So the total number of socket ID numbers on the list is equal to the total number of connected players.

    So basically, it's just checking how many players have connected so far, and doing something if there's less than or equal to 2 players connected. Do you see why I think this should be >=2?
    Why should the server start sending game data if there's less than 2 players in the game? It seems like instead, the server should send data any time a new connection is made and there's 2 or more players as a result.

    Code:
    for(i = 0; i < ds_list_size(socket_list); i++)
                {
    This establishes a loop. The next instructions are going to be done one time for each connected player.

    Code:
    network_send_raw(socket_list.[here I need to refer to any other client that isn't ], send_buffer, 47);
    And this is where things mess up. Remember, the goal is to send some data (whatever is in send_buffer) one time to each client.
    And we talk to each client how? By referring to their unique socket ID number. Thankfully we stored each of those in a list.

    Your code is trying to use the list itself as though it's a socket. The worst part is, this kinda can work
    ID numbers are just...numbers. Just regular numbers. So they can be used out of context.
    The very first time you make a socket, GameMaker might say "oh, this is the first socket so I'll number it zero."
    Then, you make a ds_list structure. GameMaker says "oh, this is the first list so I'll number it zero."
    Two totally different kinds of "things," both numbered Zero.
    So, when you tell GameMaker to send data using socket number "socket_list", GameMaker knows that socket_list was number 0, and you're using that ID number in a function that requires a socket ID. So, it uses socket number 0. Seems to work! Of course, as soon as you try to send data to a second player (socket number 1), it won't work. Because you're still sending to the socket with the number equal to the value of "socket_list"...and socket_list is a reference to a ds_list. :D Pass-by-Reference (using ID numbers to represent programming structures) can be tricky like that
    Anyways. You don't want to send data so "socket_list." You want to send data to each socket in the list that is called socket_list.
    In order to repeat the data sending multiple times, you do a loop--which we're already inside of. Use the counting variable i to help.
    Code:
    var socket_to_use = ds_list_find_value(socket_list, i);
    network_send_raw(socket_to_use , send_buffer, 47);
    The code above looks up the "i" th socket ID number in the list, and then uses that number in the networking function. This results in send_buffer being sent to the client connected to that socket ID number.

    Since it's a loop, the value of i will increment after sending, then the loop will repeat. So the next client will receive the data. Then the next, then the next...
     
    IndianaBones and XirmiX like this.
  17. XirmiX

    XirmiX Member

    Joined:
    Jul 18, 2016
    Posts:
    365
    @Tsa05 Yes, indeed I meant it to be MORE than or equal to 2. And wow, I never thought to use i as an indicator of which socket to use. So, based on what you've described, this won't work:
    Code:
    network_send_raw(socket_list.i, send_buffer, 47);
    
    Not entirely sure why that wouldn't work.

    Also, have I done the the byte size correctly for this?

    EDIT: So, I've come around to a problem where I can't identify when and where a client should read something. I guess I could have everything read all at once, but that would be inefficient. So, is there any way I could identify when a client should read what from a server? Or, what the buffer id... never mind, I think I answered my own question (an 8-bit integer being sent along with everything else, with it being the first thing to be read, identifying the "buffer number", which would work towards "if" statements on the client side, which would mean that the client reads sent data as necessary). I'm sure there's more efficient methods of doing so, and if so, perhaps you could clue me in on that?
     
    Last edited: Jul 6, 2017
  18. Tsa05

    Tsa05 Member

    Joined:
    Jun 21, 2016
    Posts:
    539
    Code:
    network_send_raw(socket_list.i, send_buffer, 47);
    Will not work because socket_list is a ds_list, and you cannot access values within a list that way. The dot-notation (name.value) can be used to refer to values stored in other objects. You aren't trying to retrieve a value stored in another object--you want a value (a socket ID number) that's stored in the current object, within a list. So you've got to use the GameMaker functions that access data in lists.

    Check out the various ds_list functions here: DS Lists
    It's important that you at least look over the names of the Ds List functions. This will give you an idea of the kinds of things that you can do to a ds list. In this case, since you want to retrieve a value from the list, you use this:
    Code:
    var socket_to_use = ds_list_find_value(socket_list, i);
    The ds_list_find_value() function returns the value stored at a certain position in the list. In the line above, that value is stored in the variable socket_to_use. (Since the socket_list is a list of socket ID numbers, "socket_to_use" is presumably a socket's ID number).
    Code:
    network_send_raw(socket_to_use , send_buffer, 47);
    So when you sent a buffer over the network, you use a socket number.

    Also, for byte size, I have absolutely no idea. Is your buffer 47 bytes? The amount of data you send depends upon the amount of data that you...intended to send. I haven't seen the code that adds data to your buffer, so I don't know what your buffer (called send_buffer) looks like.
    If you want to send the entire buffer, an easy way is to use buffer_get_size() to determine the byte size.
    See here: buffer_get_size
     
    Last edited: Jul 16, 2017
    XirmiX likes this.
  19. XirmiX

    XirmiX Member

    Joined:
    Jul 18, 2016
    Posts:
    365
    A few more things, before I think I'll be able to get a decent connection going:
    1) When I write to a buffer and then send it... that data still stays in the buffer or no? If not, is there a way to clear the buffer after sending it?

    2) Kind of unrelated to networking per se, but it's important for getting username from the client: is it possible to read the name of a file and set it as a string value to a variable? If not, I'll do whatever I need differently.

    3) I'm getting an error for buffer_seek for some reaso. I could only guess it has something to do with me using the "buffer" key from the async_list incorrectly, though I don't think that's the case. Here's my code:
    Client Create event
    Code:
    ///Initializing connection socket
    {
    joined = false;
    playerjoined = false;
    playerleft = false;
    
    message = "";
    
    port = 50000;
    IP = "127.0.0.1";
    socket_plug = network_create_socket(network_socket_tcp);
    send_buffer = buffer_create(256, buffer_fixed, 1);
    network_connect_raw(socket_plug, IP, port);
    client_amount = 0;
    client_data = ds_map_create();
    
    myclient_id = "1234567890123456";//ini_read_string("Profile Data", "password", noone);
    myplayer_username = "Scorpion";
    }
    
    Client Networking event (connection code)
    Code:
    //Sending data upon connection
        case network_type_connect:
        buffer_send = async_load[? "buffer"];
        var buffer_type_write = SEND_USER_ID;
      
        buffer_write(buffer_send, buffer_u8, buffer_type_write);
        buffer_write(buffer_send, buffer_string, myclient_id);
        buffer_write(buffer_send, buffer_string, myplayer_username);
      
        network_send_raw(socket_plug, buffer_send, buffer_sizeof(buffer_send));
      
        joined = true;
      
        break;
    
    Server Create event
    Code:
    //declaring the type of connection (tcp)
    server_type = network_socket_tcp;
    //port number
    port = 50000;
    //max amount of clients that can connect to the server
    max_players = 8;
    //generating a server
    server = network_create_server_raw(server_type, port, max_players);
    
    //ds_list to hold a list of client sockets into
    socket_map = ds_map_create();
    //creating an integer variable, which will define how many players have joined
    client_amount = 0;
    //ds_map in which to hold
    client_data = ds_map_create();
    
    
    //create a buffer with which to send information
    send_buffer = buffer_create(256, buffer_fixed, 1);
    
    Server Networking event (connection code)
    Code:
    //case for the client connecting
        case network_type_connect:
            //Identify the connecting socket
            client_socket_current = async_load[? "socket"];
            //Identify buffer sent
            buffer = async_load[? "buffer"];
          
            //Read the buffers sent by the joining client
            buffer_seek(buffer, buffer_seek_start, 0);
            var buffer_type_read = buffer_read(buffer, buffer_u8); //Read's the initial integer sent, identifying the buffer type
            var client_id = buffer_read(buffer, buffer_string); //Client's ID number
            var player_username = buffer_read(buffer, buffer_string); //Client's username
          
            //Add new player socket to the socket list
            ds_map_add(socket_map, client_socket_current, client_id);
          
            //Updating the amount of players that are connected in the client amount variable
            client_amount = ds_map_size(socket_list);
          
            //In the client_data map, create a new key with the ID of the player, which will hold onto the user's username
            client_data[? string(client_id)] = player_username;
          
            //Defining a temporary variable for the buffer number
            var buffer_type_write = 3;
          
            //writing data to send to buffer
            buffer_write(send_buffer, buffer_u8, buffer_type_write);
            buffer_write(send_buffer, buffer_string, client_id);
            buffer_write(send_buffer, buffer_string, player_username);
            buffer_write(send_buffer, buffer_u8, client_amount);
          
            //If there are now 2 or more users connected, send data about the new client to other clients
            if (ds_map_size(socket_map) >= 2)
            {
                for(i = 0; i < ds_map_size(socket_map); i++)
                {
                    socket_in_use = ds_map_find_value(socket_map, i);
                    network_send_raw(socket_in_use, send_buffer, buffer_sizeof(send_buffer));
                }
            }
        break;
    

    And here's the error:
    FATAL ERROR in
    action number 1
    of Async Event: Networking
    for object obj_connection:

    buffer_seek argument 1 incorrect type (5) expecting a Number (YYGI32)
    at gml_Object_obj_connection_NetworkingEvent_1 (line 16) - buffer_seek(buffer, buffer_seek_start, 0);

    The error occurs on the server side once the client tries to connect.
     
  20. Tsa05

    Tsa05 Member

    Joined:
    Jun 21, 2016
    Posts:
    539
    1) check out the list of functions for Buffers
    You can delete a buffer when you're done with it.

    2) Sure. Strings can basically be whatevs. You can write a string to a buffer. And variables can hold names of things.
    Just bear in mind GameMaker's string format stuff (strings can't have double-quotes unless there's a \ in front of them. Strings that are supposed to have a \ in them have to use \\)

    3) Hmrmrm, weird. I think the error might be referring to the wrong argument. But, anyways. Your error is occurring in the case where you handle Networking events of the following type: network_type_connect
    You know this because that's the case statement that the line is under. Remember that for now--we're dealing with a "connect" type of request.

    A little farther down, you have this:
    buffer = async_load[? "buffer"];
    Code:
    buffer = async_load[? "buffer"];
    So, that line is looking for a key called "buffer" in the Ds_Map called async_map. Remember that async_map was created automagically by GameMaker when a client machine made a connection request. Refer to the manual to determine what kind of data is included in the map:
    Networking Event

    [​IMG]

    Since this is a connecting type of request, the async_load buffer contains the keys "socket" and "succeeded".
    It does not contain the key "buffer."

    Remember, all of the code under the case network_type_connect section will execute whenever a connection request is made, and that's it.
    Basically, the client is saying "Hi there, may I connect?" and that's it. Your code is trying to retrieve the "buffer" of data that the client sent, but all they are doing is saying "hi, may I connect." There is no buffer of data to retrieve.

    So, later, when you try to seek to the start of your buffer....no buffer was actually found earlier. GameMaker can't find the start of no buffer.

    I think what's going on here is that you're trying to write 2 separate pieces of server functionality under the same place. You want the server to accept new connections (adding them to the socket list) and also to process incoming data from connected players.

    Networking works simpler, though, in baby steps.
    "Hi, may I connect?"
    "Sure"
    "Thank you! Here's some data"
    "Cool, here's some data in reply"

    So, you've got a code case set up to handle connect requests...now you just need a separate case to handle data requests.
    Code:
    //case for the client connecting
        case network_type_connect:
            //Identify the connecting socket
            client_socket_current = async_load[? "socket"];
            //Add new player socket to the socket list
            ds_map_add(socket_map, client_socket_current, client_id);
          
            //Updating the amount of players that are connected in the client amount variable
            client_amount = ds_map_size(socket_list);
          
            //In the client_data map, create a new key with the ID of the player, which will hold onto the user's username
            client_data[? string(client_id)] = player_username;
          
            //Defining a temporary variable for the buffer number
            var buffer_type_write = 3;
          
            //writing data to send to buffer
            buffer_write(send_buffer, buffer_u8, buffer_type_write);
            buffer_write(send_buffer, buffer_string, client_id);
            buffer_write(send_buffer, buffer_string, player_username);
            buffer_write(send_buffer, buffer_u8, client_amount);
          
            //If there are now 2 or more users connected, send data about the new client to other clients
            if (ds_map_size(socket_map) >= 2)
            {
                for(i = 0; i < ds_map_size(socket_map); i++)
                {
                    socket_in_use = ds_map_find_value(socket_map, i);
                    network_send_raw(socket_in_use, send_buffer, buffer_sizeof(send_buffer));
                }
            }
        break;
    case network_type_data:
      buffer = async_load[? "buffer"];
    
            //Read the buffers sent by the joining client
            buffer_seek(buffer, buffer_seek_start, 0);
            var buffer_type_read = buffer_read(buffer, buffer_u8); //Read's the initial integer sent, identifying the buffer type
            var client_id = buffer_read(buffer, buffer_string); //Client's ID number
            var player_username = buffer_read(buffer, buffer_string); //Client's username
          
    break;
    Now, I haven't tested that code! Can't guarantee it works, but I wanted to show you the idea. I've simply moved some of your code, that's all.
    Your network_type_connect case is back to what we talked about earlier: it stores the socket of the new player, and tells all players some new info.

    Then I added a new case, network_type_data. This case contains code that will execute if the server receives some other kind of data that is not a basic request to connect. Check the documentation on network events again: Networking Event. It's important to see that there's several different types of networking events that your server could respond to. The main ones are network_type_connect, network_type_disconnect, and network_type_data.

    Does it make sense, why you would not try to get data from a buffer during a connection request? Hopefully that gets you farther along.
     
  21. XirmiX

    XirmiX Member

    Joined:
    Jul 18, 2016
    Posts:
    365
    @Tsa05 I tried it and it still doesn't work, but I know why, and it's because you can't mix-and-match the order of an open function (i.e. recognising the player has joined, then going over to reading buffer data and then going back to connection case to send data received over to other clients). This is the type of error you get:
    FATAL ERROR in
    action number 1
    of Async Event: Networking
    for object obj_connection:

    local variable client_id(100001, -2147483648) not set before reading it.
    at gml_Object_obj_connection_NetworkingEvent_1 (line 14) - ds_map_add(socket_map, client_socket_current, client_id);

    You were on the right track. It's a shame a server can't receive buffers upon a client joining, but I guess that's fine. Either way, I fixed the above error error. Getting farther into things working (well, kind of, text that I want to show up, isn't showing up, but that's irrelevant for this thread), I'm getting an issue with disconnecting. Something to do with the server not being able to read out the player ID as it gets a zero, when a string is expected, which is strange:
    FATAL ERROR in
    action number 1
    of Async Event: Networking
    for object obj_connection:

    buffer_write argument 1 incorrect type (0) expecting a String (YYGS)
    at gml_Object_obj_connection_NetworkingEvent_1 (line 34) - buffer_write(send_buffer, buffer_string, client_id); //only sending ID, because deleting ID key will subsequently delete player username

    Server Networking event (disconnection code)
    Code:
    //case for the client disconnecting
        case network_type_disconnect:
            //Identify the disconnecting socket
            client_socket_current = async_load[? "socket"];
         
            //Contained in a temporary variable, from the socket map, the ID number (a string) of the disconnecting socket
            var client_id = ds_map_read(socket_map, string(client_socket_current));
         
            //Updating the amount of players that are connected in the client amount variable
            //client_amount = ds_map_size(socket_map);
            //A temporary variable, which is needed to send amount of players connected, due to a disconnect, to other clients. Reason for this variable is quite complex...
            var client_amount_tosend = ds_map_size(socket_map) - 1;
         
            var buffer_type_write = 2;
         
            //writing data to send to buffer
            buffer_write(send_buffer, buffer_u8, buffer_type_write);
            buffer_write(send_buffer, buffer_string, client_id); //only sending ID, because deleting ID key will subsequently delete player username
            buffer_write(send_buffer, buffer_u8, client_amount_tosend);
         
            //If there are now 2 or more users connected, send data about the new client to other clients
            if (ds_map_size(socket_map) >= 2)
            {
                for(i = 0; i < ds_map_size(socket_map); i++)
                {
                    socket_in_use = ds_map_find_value(socket_map, i);
                    network_send_raw(socket_in_use, send_buffer, buffer_sizeof(send_buffer));
                }
            }
         
            //In the client_data map, delete from client data the disconnecting client's ID along with their name (ID being the key and username being its string value)
            ds_map_delete(client_data, client_id);
         
            //Remove the disconnecting player socket from the socket list
            ds_map_delete(socket_map, client_socket_current);
         
            //Updating the amount of players that are connected in the client amount variable
            client_amount = ds_list_size(socket_map);
        break;
    
    Yes, the client_id is a temporary variable, but the case hasn't been broken yet, so the variable shouldn't be deleted. Doubt that's what's happening, but it's a possibility.

    Also, I was kind of hoping to get some info on an in-built function that lets me read the name of a file. I can't find anything related to reading of a file's name anywhere in the manual's link you provided.

    As for buffers, I'm still not clear on this; are the buffers cleared when you send them (the case breaks) or don't they? If not, how exactly would I clear them? I don't want to delete a buffer, I want to simply clear it. If it's not possible, and can only be over-written, then there might be some annoying complications, such as unnecessarily filled buffers, needing a bunch of buffers to begin with and not always knowing whether there might be something left over from the previous buffer sent that you don't want to be sent...
     
    Last edited: Jul 7, 2017
  22. Tsa05

    Tsa05 Member

    Joined:
    Jun 21, 2016
    Posts:
    539
    Code:
    buffer_write(send_buffer, buffer_string, client_id)
    The buffer_write() function requires the ID number of the buffer you're writing to, the type of data you want to right, and the actual data.
    You are writing to send_buffer, and you're writing a string, and the data you're writing is client_id. Is client_id a number? If it's a number instead of a string, then that is the cause of your error.

    There's functions to find the names of files... file_find_first() and file_find_next() can do that...but I'm not sure what you're doing with that and what the motivation for reading file names is, so I can't be of much help suggesting a method to try.

    Buffers are not cleared when you send them. You *can* seek to the buffer start position, if you want, and start to overwrite the buffer.
    But you should probably just delete them when you are finished with them. There's not any real penalty or disadvantage with deleting a buffer and creating a new one later.

    Suppose you have a buffer with a bunch of data in it:
    234857093240294391027349830134801273412300

    The buffer above contains 42 bytes of information. That means 42 bytes of memory have been set aside by the computer, for my buffer.
    I could clear the buffer, I suppose, by writing zeroes to it?
    000000000000000000000000000000000000000000
    But, that's still taking up 42 bytes in memory, so it's not actually doing anything useful. I've simply replaced the old data with new data--the amount of memory taken by the buffer is the same.

    If I want to make the buffer smaller, I can use buffer_resize.
    If I want to get rid of all data in the buffer *and* recover all of the memory that it was using, I can use buffer_delete.
     
  23. XirmiX

    XirmiX Member

    Joined:
    Jul 18, 2016
    Posts:
    365
    @Tsa05 The data I'm sending is a string. The "client_id" variable is defined as a key from a ds_map. The key is a socket number, while its value is a string. I'm guessing it returns 0, because it thinks I want to send the key, not the key's value... Or perhaps it's something else; either way I've found the issue and I was using the wrong function. I was using ds_map_read instead of ds_map_find_value. However, I'm still getting a similar error:
    FATAL ERROR in
    action number 1
    of Async Event: Networking
    for object obj_connection:

    buffer_write argument 1 incorrect type (5) expecting a String (YYGS)
    at gml_Object_obj_connection_NetworkingEvent_1 (line 34) - buffer_write(send_buffer, buffer_string, client_id); //only sending ID, because deleting ID key will subsequently delete player username

    I think I'll post down some more code (some parts are indented, because I'm not using them yet, or are just left blank):

    Client Create event code
    Code:
    ///Initializing connection socket
    {
    joined = false;
    playerjoined = false;
    playerleft = false;
    
    message = "";
    
    x_previous = x+1;
    y_previous = y+1;
    x_current = x;
    y_current = y;
    send_data = 0;
    
    port = 50000;
    IP = "127.0.0.1";
    socket_plug = network_create_socket(network_socket_tcp);
    send_buffer = buffer_create(256, buffer_fixed, 1);
    network_connect_raw(socket_plug, IP, port);
    client_amount = 0;
    client_data = ds_map_create();
    
    myclient_id = "1234567890123456";//ini_read_string("Profile Data", "password", noone);
    myplayer_username = "Scorpion";
    }
    
    Client Networking event code
    Code:
    type = async_load[? "type"];
    
    switch(type)
    {
        //Sending data upon connection
        case network_type_connect:
        var buffer_type_write = SEND_USER_ID;
     
        buffer_write(send_buffer, buffer_u8, buffer_type_write);
        buffer_write(send_buffer, buffer_string, myclient_id);
        buffer_write(send_buffer, buffer_string, myplayer_username);
     
        network_send_raw(socket_plug, buffer_send, buffer_sizeof(buffer_send));
     
        message = myplayer_username + " joined";
        joined = true;
     
        break;
     
        //Sending and receiving data
        case network_type_data:
        buffer = async_load[? "buffer"];
     
        switch(send_coordinates)
        {
            case HULL_COORDINATES:
            //buffer_write(send_buffer, )
            break;
        }
     
        buffer_seek(buffer, buffer_seek_start, 0);
     
        //Identify buffer type
        var buffer_type_read = buffer_read(buffer, buffer_u8);
     
        switch(buffer_type_read)
        {
            //if buffer type is 2
            case REMOVE_USER_ID:
                //reads buffers
                client_id = buffer_read(buffer, buffer_string);
                client_amount = buffer_read(buffer, buffer_u8);
            
                var player_name = ds_map_find_value(client_data, client_id);
                message = player_name + " left";
                playerjoined = true;
                //Deletes client ID from the client data list, subsequently, the client username, since client's id is its key
                ds_map_delete(client_data, client_id);
            break;
     
            //if buffer type is 3
            case ADD_USER_ID:
                client_id = buffer_read(buffer, buffer_string);
                player_username = buffer_read(buffer, buffer_string);
                client_amount = buffer_read(buffer, buffer_u8);
            
                client_data[? string(client_id)] = player_username;
            
                var player_name = ds_map_find_value(client_data, client_id);
                message = player_name + " joined";
                playerleft = true;
            break;
        
            //if buffer type is 4
            case 4:
                /*
                var xx = buffer_read(buffer, buffer_f32);
                var yy = buffer_read(buffer, buffer_f32);
                */
            
            
            break;
        
            //if buffer type is 5
            case 5:
            
            break;
        
            //unidentified case
            case UNIDENTIFIED_BUFFER:
                show_error("A buffer received from the server was unidentified.", true);
            break;
        }
        break;
    }
    
    Server Create event code
    Code:
    //declaring the type of connection (tcp)
    server_type = network_socket_tcp;
    //port number
    port = 50000;
    //max amount of clients that can connect to the server
    max_players = 8;
    //generating a server
    server = network_create_server_raw(server_type, port, max_players);
    
    //ds_list to hold a list of client sockets into
    socket_map = ds_map_create();
    //creating an integer variable, which will define how many players have joined
    client_amount = 0;
    //ds_map in which to hold
    client_data = ds_map_create();
    
    
    //create a buffer with which to send information
    send_buffer = buffer_create(256, buffer_fixed, 1);
    
    Server Networking event code
    Code:
    ///Networking
    //??
    type = async_load[? "type"];
    
    //Switch statement, because needs a list of connection types... yeah
    switch(type)
    {
        //case for the client connecting
        case network_type_connect:
            //Identify the connecting socket
            client_socket_current = async_load[? "socket"];
        
            //Updating the amount of players that are connected in the client amount variable
            client_amount = ds_map_size(socket_map);
        break;
    
        //case for the client disconnecting
        case network_type_disconnect:
            //Identify the disconnecting socket
            client_socket_current = async_load[? "socket"];
        
            //Contained in a temporary variable, from the socket map, the ID number (a string) of the disconnecting socket
            var client_id = ds_map_find_value(socket_map, client_socket_current);
        
            //Updating the amount of players that are connected in the client amount variable
            //client_amount = ds_map_size(socket_map);
            //A temporary variable, which is needed to send amount of players connected, due to a disconnect, to other clients. Reason for this variable is quite complex...
            var client_amount_tosend = ds_map_size(socket_map) - 1;
        
            var buffer_type_write = 2;
        
            //writing data to send to buffer
            buffer_write(send_buffer, buffer_u8, buffer_type_write);
            buffer_write(send_buffer, buffer_string, client_id); //only sending ID, because deleting ID key will subsequently delete player username
            buffer_write(send_buffer, buffer_u8, client_amount_tosend);
        
            //If there are now 2 or more users connected, send data about the new client to other clients
            if (ds_map_size(socket_map) >= 2)
            {
                for(i = 0; i < ds_map_size(socket_map); i++)
                {
                    socket_in_use = ds_map_find_value(socket_map, i);
                    network_send_raw(socket_in_use, send_buffer, buffer_sizeof(send_buffer));
                }
            }
        
            //In the client_data map, delete from client data the disconnecting client's ID along with their name (ID being the key and username being its string value)
            ds_map_delete(client_data, client_id);
        
            //Remove the disconnecting player socket from the socket list
            ds_map_delete(socket_map, client_socket_current);
        
            //Updating the amount of players that are connected in the client amount variable
            client_amount = ds_list_size(socket_map);
        break;
    
        //case for receiving and sending data
        case network_type_data:
            //Identify the sender client
            client_socket_current = async_load[? "id"];
            //Identify buffer sent
            buffer = async_load[? "buffer"];
        
            //Read the buffers sent by the joining client
            buffer_seek(buffer, buffer_seek_start, 0);
            var buffer_type_read = buffer_read(buffer, buffer_u8); //Read's the initial integer sent, identifying the buffer type
            switch(buffer_type_read)
            {
                case 1:
                    var client_id = buffer_read(buffer, buffer_string); //Client's ID number
                    var player_username = buffer_read(buffer, buffer_string); //Client's username
                
                    //Add new player socket to the socket list
                    ds_map_add(socket_map, client_socket_current, client_id);
                
                    //In the client_data map, create a new key with the ID of the player, which will hold onto the user's username
                    client_data[? string(client_id)] = player_username;
                
                    //Defining a temporary variable for the buffer number
                    var buffer_type_write = 3;
                
                    //writing data to send to buffer
                    buffer_write(send_buffer, buffer_u8, buffer_type_write);
                    buffer_write(send_buffer, buffer_string, client_id);
                    buffer_write(send_buffer, buffer_string, player_username);
                    buffer_write(send_buffer, buffer_u8, client_amount);
                
                    //If there are now 2 or more users connected, send data about the new client to other clients
                    if (ds_map_size(socket_map) >= 2)
                    {
                        for(i = 0; i < ds_map_size(socket_map); i++)
                        {
                            socket_in_use = ds_map_find_value(socket_map, i);
                            network_send_raw(socket_in_use, send_buffer, buffer_sizeof(send_buffer));
                        }
                    }
                break;
            }
        break;
     
    }
    

    I didn't think I would have so much trouble not getting the server crashed due to a client disconnecting... Lets hope once this is wrapped up that then I can send and receive buffer data to and from clients as needed without any errors I can't fix myself, because I feel like I'm wasting a lot of your and possibly other peoples' precious time :(

    EDIT: my only guess here is that the joining client's ID and username were never received and that you can't write buffers and send data from within network_type_connect either. I'm re-organising my code right now (I will not have edited the code above) to see if that is the case.

    ANOTHER EDIT: Nope, turns out this is not the case and I'm still getting the same error.
     
    Last edited: Jul 7, 2017
  24. Tsa05

    Tsa05 Member

    Joined:
    Jun 21, 2016
    Posts:
    539
    Hmrmmrm, well, something definitely isn't what GameMaker is expecting.

    Debugging time?
    Code:
    //writing data to send to buffer
    show_debug_message("buffer_type_write=" + string(buffer_type_write));
    buffer_write(send_buffer, buffer_u8, buffer_type_write);
    show_debug_message("client_id=" + string(client_id));
    buffer_write(send_buffer, buffer_string, client_id);
    buffer_write(send_buffer, buffer_string, player_username);
    buffer_write(send_buffer, buffer_u8, client_amount);
    Try using the debug messages above, and check the console output (the black output box at the bottom of the gamemaker window, that shows compiling status and stuff) when you run the game. See what your program *thinks* client_id is--that may help track down where things are getting lost.
     
  25. XirmiX

    XirmiX Member

    Joined:
    Jul 18, 2016
    Posts:
    365
    The log doesn't show me anything. Perhaps it's that the server is not even receiving the client_id nor the username. But that doesn't make sense because the code is set up so that the client would send the data and so that the server would read it and then store it.

    Oh and if I launch even this version of the software in debug mode (the client, after the server has been launched ofc), the debugger goes haywire, as I mentioned in some other thread, and stuff is displayed where it's not supposed to be.

    Perhaps what I'm doing has touched upon some form of the engine's own glitches and so I'm getting what I'm getting. Everything else out there is some-what close to mine, but not really in terms of tutorials; RealTutsGML does everything in the way I want my game except that his server and client are one and the same project and that the client IDs are generated in some bizarre way that from what I can gather would crash the server if too many people join and leave over and over again for a long time.

    HeartBeast has all the startups I would need, if the tutorial wasn't for just one client and there are HUGE differences between one client connecting and multiple ones, like having lists, sending data to other clients about you and so forth.

    SlasherXGames tutorial would be perfect for me if it wasn't for the fact that my client is the one who keeps the username and password on them, as well as an ID, which caused me issues upon part 4.

    Well, you get the picture. I'm sure there are GML games that could and have already done what I want to on them, but weirdly, things go haywire for me just because I'm reading a string. Perhaps there's more to reading strings than I thought? According to the manual, a string value is itself with a 0 at the end in terms of bits, or something like that, while text, for instance, isn't, so perhaps I should instead send text? I've yet to comprehend the real differences, however.
     
    Last edited: Jul 10, 2017
  26. Tsa05

    Tsa05 Member

    Joined:
    Jun 21, 2016
    Posts:
    539
    Wait, what do you mean the debugger goes haywire?
    You need to run your game in debug mode in order to debug issues (and the debug print-outs only work in debug mode). If your debugger isn't working, that's got to be addressed first; you'll never get to the bottom of these sorts of data type errors if you can't debug the code. Can you describe the debugger behaviour specifically? It's possible that we can help here on the forum, or narrow down the issue to something for YYG support.
     
  27. XirmiX

    XirmiX Member

    Joined:
    Jul 18, 2016
    Posts:
    365
    As I said, when I launch the server in debug mode, everything is fine, the server is set up and the debugger behaves normally. When I then also launch the client, in debug mode, the debugger closes, along with the server it seems, then opens up the server again, and this time the debugger shown shows code in places where it's not actually from. Essentially there can't be two debugger windows, so I think it might be trying to synchronise and subsequently goes haywire.

    Tried to launch only the server in debug mode, tried only launching the client in debug mode, and it doesn't glitch out when I do so; only when I try to launch both in debug mode does this happen.

    EDIT: Debugging only the server and only the client still doesn't show any messages in their logs, just the usual stuff, like compiling, client joining and leaving and whether or not they are paused for the client, joining and leaving isn't shown of course).
     
  28. Tsa05

    Tsa05 Member

    Joined:
    Jun 21, 2016
    Posts:
    539
    Ok, that makes more sense. The debugger connects over a localhost address.

    But...you are saying that you get:
    Code:
    FATAL ERROR in
    action number 1
    of Async Event: Networking
    for object obj_connection:
    
    buffer_write argument 1 incorrect type (5) expecting a String (YYGS)
    at gml_Object_obj_connection_NetworkingEvent_1 (line 34) - buffer_write(send_buffer, buffer_string, client_id); 
    Right?
    So, wherever that is happening, put a show_debug_message("client_id="+string(client_id)) just before that line.
    Then, run whichever program (client or server) has the error in debug mode. When it crashes, with that error, check the outpur in the console to see what happened.
     
  29. XirmiX

    XirmiX Member

    Joined:
    Jul 18, 2016
    Posts:
    365
    It says the value is "<undefined>" in the debugger (when launching the server in debug mode and client without it) and upon a client disconnecting. The log tells me nothing however. From the debugger, it tells me the value of var client_amount_tosend as well, which surprisingly is -1 instead of 0, since it should keep the amount of players (checking the amount of values in socket_map) and take away 1 upon one of them disconnecting, but adding when they connect. It seems that these variables (which are prevalent in the network_type_disconnect of ):

    Code:
    var client_id = ds_map_find_value(socket_map, client_socket_current);
    
    var client_amount_tosend = ds_map_size(socket_map) - 1;
    
    Can't read values or sizes from ds_maps for some weird reason. Remember, I'm storing socket IDs as the keys within the first ds map and simple integer numbers in the other, so I think it might be something to do with the sockets themselves. As for the size of the list being returned as 0, I have no goddamn clue. These are the lines of code that add values to these maps:

    Code:
    ds_map_add(socket_map, client_socket_current, client_id);
    
    client_data[? string(client_id)] = player_username;
    
    The above lines occur in "class 1" on the server side of the network_type_data, where the server receives and stores client data.

    EDIT: I should probably mention that in Create event, the socket_map is set up as follows:
    Code:
    socket_map = ds_map_create();
    
    But that's standard, isn't it?
     
  30. Tsa05

    Tsa05 Member

    Joined:
    Jun 21, 2016
    Posts:
    539
    Examining this section of your code (from post #23):
    Code:
    switch(type)
    {
        //case for the client connecting
        case network_type_connect:
            //Identify the connecting socket
            client_socket_current = async_load[? "socket"];
        
            //Updating the amount of players that are connected in the client amount variable
            client_amount = ds_map_size(socket_map);
        break;
    
        //case for the client disconnecting
        case network_type_disconnect:
            //Identify the disconnecting socket
            client_socket_current = async_load[? "socket"];
        
            //Contained in a temporary variable, from the socket map, the ID number (a string) of the disconnecting socket
            var client_id = ds_map_find_value(socket_map, client_socket_current);
        
    When a client connects, you find their socket using:
    client_socket_current = async_load[? "socket"];

    You do not, in that code, add client_socket_current to your socket_map.
    Later, when a disconnect occurs, you search for the socket by using:
    var client_id = ds_map_find_value(socket_map, client_socket_current);
    But this will always return undefined as an answer, since there's never anything added to the map, right?
     
  31. XirmiX

    XirmiX Member

    Joined:
    Jul 18, 2016
    Posts:
    365
    BELOW that code I add the map. Essentially, when the client connects, the server updates the amount of players that are in the game... I guess I had forgotten to add a "+1" there, but it's still negative 1 upon the client disconnecting.

    But in terms of the socket itself, when connecting, the client is supposed to send out to the server its ID and its username, which it does when connecting right here:

    //Sending data upon connection
    case network_type_connect:
    var buffer_type_write = SEND_USER_ID;

    buffer_write(send_buffer, buffer_u8, buffer_type_write);
    buffer_write(send_buffer, buffer_string, myclient_id);
    buffer_write(send_buffer, buffer_string, myplayer_username);

    network_send_raw(socket_plug, buffer_send, buffer_sizeof(buffer_send));


    message = myplayer_username + " joined";
    joined = true;

    break;

    And the server is supposed to receive it in network_type_data and store it, which is does as shown below:

    //case for receiving and sending data
    case network_type_data:
    //Identify the sender client
    client_socket_current = async_load[? "id"];
    //Identify buffer sent
    buffer = async_load[? "buffer"];

    //Read the buffers sent by the joining client
    buffer_seek(buffer, buffer_seek_start, 0);
    var buffer_type_read = buffer_read(buffer, buffer_u8); //Read's the initial integer sent, identifying the buffer type
    switch(buffer_type_read)
    {
    case 1:
    var client_id = buffer_read(buffer, buffer_string); //Client's ID number
    var player_username = buffer_read(buffer, buffer_string); //Client's username

    //Add new player socket to the socket list
    ds_map_add(socket_map, client_socket_current, client_id);

    //In the client_data map, create a new key with the ID of the player, which will hold onto the user's username
    client_data[? string(client_id)] = player_username;

    //Defining a temporary variable for the buffer number
    var buffer_type_write = 3;

    //writing data to send to buffer
    buffer_write(send_buffer, buffer_u8, buffer_type_write);
    buffer_write(send_buffer, buffer_string, client_id);
    buffer_write(send_buffer, buffer_string, player_username);
    buffer_write(send_buffer, buffer_u8, client_amount);

    //If there are now 2 or more users connected, send data about the new client to other clients
    if (ds_map_size(socket_map) >= 2)
    {
    for(i = 0; i < ds_map_size(socket_map); i++)
    {
    socket_in_use = ds_map_find_value(socket_map, i);
    network_send_raw(socket_in_use, send_buffer, buffer_sizeof(send_buffer));
    }
    }
    break;
    }
    break;

    So, it doesn't receive information... why? It's supposed to be able to receive information, no?
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice