multiplayer game development blog

Posted By: ulf

multiplayer game development blog - 04/25/06 07:52

hey there yesterday i started developing the core of our game. in short its a 10 player rpg/rts fast paced action game. i thought because here is almost no live in the multiplayerforum i write a blog about creating the multiplayer core of our game. this might be interesting for some of you to read - at least i hope so. it helps me also keeping track of the project.
be aware, iam not the best coder i just want to cover developing a multiplayer project with a6. maybe this helps the community a bit when i show how i solve some problems.

i hope its okay for the mods, to leave the topic here and that i post each day iam working on. others are invited to give me hints or discuss problems.
if its not okay please tell me so, ill then post the blog at my own site but that would be difficult for others to join the discussion.

okay here we go: yesterday the 24.04. i used locoweeds tutorial to get a start all worked well and seemed logic. then i ran into a strange behaviour. the server code works well but the client never gets the server_says_yes variable wich is triggerd by the on_server function.
i did everything like in locoweeds tutorial until the part where the player entities are created and the players_connected are count.
i was shure i understood all the code.
after some testing i realised that i have to put a sleep(1) bevore the level load, so that the on_server command is properly triggered when the client joins the game. iam wondering why nobody else or locoweed had this problem yet? after i made this little change in the code, everything works well.

Code:

sleep(1); // solves the bug where event_join is skipped when a player joins
level_load(str_map); // load level, must be loaded after connection is set
sleep(1); // make shure level is loaded



today the 25.04. i have planned writing the chat part of the core and add some more debug information. lets see how far i come...

edit: i have successfully implented a chat and some debug information. you can see how far iam here:



however, i found a little thing in the tutorial to fix. when a client joins he doesnt see the right number of people already in the game. so i added this:

Code:

send_var_to(you, number_of_players); // send the number of players ingame to joined client this was not part of locoweeds tut



at the server_called function wich is the on_server function under the ifdef server; to fix it.

next things iam gonna work on are camera and unit selection...
Posted By: ulf

Re: multiplayer game development blog - 04/26/06 21:15

26.04.06 today i implented a working camera. it feels almost like those in warcraft 3.

i also found out about the proc_local function. i was wondering why a client could not use enable_click with his own entity.

i found out that i have to define a proc_local function where i set enable_click for each client. for the server i use the entitys function directly.
its very tricky and i doubt that i have a perfect approach yet. however it works for me. i can select the entities now, a selected entity gets a cross around it like in rts games. it works for all clients.

the next thing iam gonna do is multiple selection and correct placement of the player entities... this will probably take me some days.
Posted By: Penneywize9

Re: multiplayer game development blog - 04/27/06 00:45

This is awesome! keep it up. This will probably be my favourite thread until the development of your game is finished.

Good initiative. Most developers, though willing to help others, rarely go into such detail with every problem they face. This thread is going to be a goldmine of great info for any of us who want to try multiplayer programming.
Posted By: William

Re: multiplayer game development blog - 04/27/06 03:13

Quote:

after some testing i realised that i have to put a sleep(1) bevore the level load, so that the on_server command is properly triggered when the client joins the game. iam wondering why nobody else or locoweed had this problem yet?




I ran across this problem a while back, though I thought it was just me(since I didn't re-create locoweeds level/tut, just used it for reference). I ended up putting the sleep in the on_server function, but I think i'll change that to putting the sleep before the level loading. Thanks for sharing that.

Some Tips:

- Proc_client/proc_local functions do not keep the "you" pointer when creating.

- Make sure your proc_local function isn't running code thats meant only for instances with your clients main entity. Use my.native for this.

- My.client flag and connection variable are your friends, i've used them alot.

- If you want a different interpolation solution for lan games, it's a good idea to create a global variable and differentiate between lan and internet play in your interpolation code. Lan cant have the ent_sendrate set at 0 so might as well reap the benifit and have options for it.

- If your creating a hybrid server you'll have to always keep in mind the server/client combination. Remember, this does not mean the server runs functions from proc_client or proc_local even though it's a hyrbrid. If your doing an rts or any game that requires a group of players before the game starts(not a server running and players come and go as please) then I think you'll need a hybrid server solution. I'm going for both hybrid server and a dedicated server solution in one.

- Checks and balances. Keeping track on whats realisitic when recieving and sending var/skills/arrays; hampers hacking.

- Watch your pointers! I cannot stress this enough, a large degree of my headaches come from pointer problems. Part of this is due to transfering a single-player code base to multiplayer, other part is due to stupidity. You can send pointers via handles to the other entities, but, if a new entity joins the game then it will not have the skills of the other entities. So... you have to resend the pointers.

- Are you sure you want to create a multiplayer game first? Why not create single player than take that wad of code and change it for mulitplayer? Currently i've been converting my single player base to mulitplayer for a little over a month now. I think if I went mulitplayer from day one, at my experience level(in day one), it would have took much much longer to get where I am right now due to time consuming multiplayer testing. Single player testing is not time consuming as you don't have to create multiple instances of your game over and over. If you do go single player first, create very modularized code(for example, for mine I can shut down each weapon, all weapons, chop up movement and still have functional code). This makes the task a bit easier bringing over to multiplayer as you create a base(creation of ents), and go thru your code from there 3-4 functions at a time. Keep in mind i'm speaking from my own experience, I havn't done any MMORPG work so mabye it's best to go multiplayer right from the get-go there.

- For single player I have 8 kart pointers, kart1 for the main player, kart2-kart8 for the A.I. For multiplayer, every kart creates as kart1 to keep all the single player code functional, and on top of that, I have new pointers from mkart1-mkart8 for each multiplayer kart to differentiate them. This allows me to easily transer ammo/health/ect. thru the different kart pointers, and also, keep the single player functions running well while keeping A.I seperate.

- Headeaches, i've still got a couple more weeks of them as I transer the rest of my weapons over to multiplayer, lol... I honestly cant say multiplayer has been any fun past the first week or so.
Posted By: ulf

Re: multiplayer game development blog - 04/27/06 06:46

hey there, thanks for your useful tips! i think its not a good idea to transfer the code. it may work if its well organised like yours but my game is inteded to be multiplayer only.
i already wrote some singleplayergames so i would say i know the engine pretty good to start multiplayer. although iam not a coding guru, i find out stuff due to trial and error and experience.

iam going for a hybrid server, because the player who opens the game should also be able to play. iam at the very start of this whole thing and find it has a very steep learning curve, thats probably why a lot of people stay away from multiplayer in a6. its simply confusing at the beginning. i hope this gets better soon once i get the hang of it...

btw whats your game all about?
Posted By: ulf

Re: multiplayer game development blog - 04/27/06 19:23

27.06. today i was able to implent a functionality like in most mmorpgs or rts games where you can use /w name text or /a text. so in short you can whisper to other players only, talk to your team only or talk to all players on the server.

what i do is check the string with:

if(str_cmpni(str_sendchat,"/a ")) // we got a broadcast incoming -> display it and send to clients

then its like locoweed code works. its all based on some string manipulation. so far it works for /a perfectly. tomorrow ill add /w for whisper and maybe /t for the teamsay.

i planned that i send the messages to all clients like "/w namefrom nameto text" and only display the text if nameto is equal player_name.
so again locoweeds chat skeleton is used here.

i realised that some string functions like str_getnextword and so on would be useful. but right now iam to lazy to write them for the little string manipulation i do
Posted By: ulf

Re: multiplayer game development blog - 04/28/06 11:27

28.06 after some worries with the if and else clauses i finally got the whisper command working. switch/case would have been a real helper here!!

now you can "/w name text" and its displayed only for the player with the right player_name.

iam really happy because teamsay shouldnt be a problem now because it works similar. what i did was to send the chat over the network in the form "/a playerto playerfrom messagetext".

then only display it in the on_server or on_client functions at the "EVENT_STRING" if the playerfrom text was equal to player_name.

heres a little example code of how my probably ugly string manipulation works just to get the idea: (note: its not complete, and uses locoweeds chat example as base)

Code:

// send chat to a special player with "/w playerto text"
if(str_cmpni(str_chatentry,"/w "))
{

txtchatentry.visible = off;

// change text so that it is "you whisper to nameto: messagetext"
str_cpy(str_temp,"you whisper to "); // put players name before entry
str_clip(str_chatentry,3); // clip /a at start
str_cpy(str_whisperto, str_chatentry); // copy first 20 chars to name
temp = str_stri(str_whisperto," "); // find first space after name
str_trunc(str_whisperto, (str_len(str_whisperto)-temp)+1); // trunctate the rest of the name string
str_cat(str_temp,str_whisperto); // copy name to temp
str_cat(str_temp,":"); // copy : to temp
str_clip(str_chatentry,str_len(str_whisperto)); // clip the name from the chatentry
str_cat(str_temp,str_chatentry); // paste chatentry after name
str_cpy(str_sendchat, str_temp); // place the result in sendchat


// if host display already and send to others
if(connection == 3)
{


// move old chat entries one up and display str_sendchat at bottom
update_chat();


// add "/w " and send "/w nameto namefrom messagetext" to clients
str_cpy(str_temp, "/w "); // add /w
str_cat(str_temp, str_whisperto); // add name to whisper to
str_cat(str_temp, " "); // spacer
str_cat(str_temp, player_name); // add sender name
str_cat(str_temp, " "); // spacer
str_cat(str_temp, str_chatentry); // message text left over from top
str_cpy(str_sendchat, str_temp);

send_string(str_sendchat);


}
else // if client send entry to server for processing/manipulation
{
// move old chat entries one up and display str_sendchat at bottom
update_chat();

// add "/w " and send "/w nameto namefrom messagetext" to clients
str_cpy(str_temp, "/w "); // add /w
str_cat(str_temp, str_whisperto); // add name to whisper to
str_cat(str_temp, " "); // spacer
str_cat(str_temp, player_name); // add sender name
str_cat(str_temp, " "); // spacer
str_cat(str_temp, str_chatentry); // message text left over from top
str_cpy(str_sendchat, str_temp);

send_string(str_sendchat);
}

}



and here is a screenshot of it working (notice the chat at the bottom)


Posted By: William

Re: multiplayer game development blog - 04/28/06 11:48

Once you understand exactly where functions run on the client and server by default, and how to manipulate them to run anywhere, multiplayer thinking makes sense. Here is a quick run-down:

- Entities created will create an "instance" of themselves on each computer. Each entity will have the same pointer. A handle that calls up an entity on one computer will call up the same entity on the other computer. There is no problems retrieving "you" pointers from other computers, just place them in a skill, send that skill, and ptr_for_handle them. In a single player game you wouldn't have to worry about this; in a multiplayer environment entities don't know what their instances "you" pointers are ect. You have to send them.

- Entities created will run their action on the server.
In that action you can call proc_client many times, it will start a function for that entity on the client that created the entity. This is needed for individual input.

- Proc_local starts a function for the entity on all computers for its instance. Do not put specialized input in here unless you validate if "my.native == on". Because it's being run on each computer for the same entity. Only call this function once and branch from there. When a new client joins the game only the last proc_local for each entity called will be called again for the new client.

- Even though a hybrid server is both a server and client it will not run proc_local or proc_client functions. Which is rightfully so since the server already started a function for each entity when they were created. Just do the work from those those functions or branches from them.

Just nail down exactly how to manipulate functions to run between the clients and server and you'll have a foothold on mulitplayer. After that is the tougher part, how to go about it? Depending on the size of your project, this can be very tedious. Some questions you'll come across: Collisions? Scans? and the very big one - Syncronization. Create a small movement demo before you go into your real projects. It'll help introduce some issues, but don't become over zealous and lose sight once succesfull in that, I learned that lesson... theres many more problems awaiting and it depends on the genre really.

Quote:


btw whats your game all about?





It's a kart racing game somewhat similar to Mario Kart. Take Mario Kart and innovate many key aspects of it, some more than others, from weapons, level selections, mulitplayer, modes, gameplay, and graphics. While i'm not going to say to much now, i'll have a website up sometime this year. Afterwards i'll release some details here on the forum alongside a couple screenshots, I think most will be surprised.
Posted By: D3D

Re: multiplayer game development blog - 04/28/06 13:56

Ulf:

Thank you for sharing your project and the chat code. I was trying something like that for fun a while back, but it didn't worked (because i'm not advanced) also without some good samples to look at, it's going to become very hard to create multiplayer games for me with GameStudio. Keep it up


All the best,

Dusty
Posted By: ulf

Re: multiplayer game development blog - 04/29/06 17:28

iam not at home this weekend so i wont develop, iam however reading some theory stuff regarding lag, client prediction and so on... ill update the thread tuesday or wednesday again. nice weekend all of you!
Posted By: ulf

Re: multiplayer game development blog - 05/02/06 20:26

02.05. okay just a quick update today, i managed to implent faction as well. so now if a player writes /f text only his faction sees the text. the faction is determined by a -d statement when starting the engine. players can chose their faction within our external starter program.

i also added some small information when a client joins or leaves the game.

right now iam working on a playerlist with display of playername, faction, race.
this gives me some trouble since i cant access the string in player.string1 with the str_ function in the latest public beta.
maybe its just a fault of me, but it maybe a bug too. anyways if i dont find a solution to access this strings ill have to find a workaround.

my plan for the playerlist is to get the name of a joined player when he triggers event_join and add his chosen race after the race selection screen to the list. then i only have to manage to display it on all players...
Posted By: Locoweed

Re: multiplayer game development blog - 05/03/06 00:24

Nice blog ulf,

Should help alot of people out.

To answer your question here.
"after some testing i realised that i have to put a sleep(1) bevore the level load, so that the on_server command is properly triggered when the client joins the game. iam wondering why nobody else or locoweed had this problem yet?"

I also had that problem at one point or another. I think it has been varying somewhat depending on the GStudio version. I am surprised it wasn't still in there ( a sleep() before level_load() ), but it probably started working in version A6.30 without so I took it out. I usually leave those lines in, but commented out with a warning. Either that or it was always working until after A6.30 and I asked Conetic about it then, but I have ran into that problem at sometime. It doesn't hurt to have it there anyway, so it might as well be there no matter what the version.

Also, I like how you are explaining how you are adding whisper, etc. Very good stuff.

Nice blog, keep it up,
Loco

And feel free to use/change anything from the original tutorial to use to help the community, like your own expanded tutorial on what you are doing. I have no problem with that if you want to do something like that.
Posted By: ulf

Re: multiplayer game development blog - 05/04/06 21:07

thanks locoweed unfortunately i have no time writing a tutorial like yours because iam working every minute i find on the project. however because i think its a shame that the mulitplayer forum is deserted i post here and write what iam experiencing.
i dont think iam a good coder good enough to write a tutorial like yours, iam only a average guy with a dreamgame in mind and a little team to help me
iam at the very start of finding out how things work, i wont say ill never write a tutorial but until i do i will need more knowledge.

04.05.06 today i finally got the movement working, iam using a modified version of georges stratego2 movement.

because my camera was facing down 60 degree tilt, i had to calculate the point the mouse is clicking for the unit to move to. georges code is with the camera facing down directly at the map.

i did it with a code snipped i found on the forum. here is it:

Code:

vec_set(temp.x, vector(mouse_pos.x, mouse_pos.y,2000))
vec_for_screen (temp.x, camera);
vec_sub(temp.x, camera.x);
temp.x/=temp.z;
temp.y/=temp.z;
temp.x=camera.x+(temp.x*-1)*camera.z;
temp.y=camera.y+(temp.y*-1)*camera.z;
temp.z=0;



this takes the mouse position and gets the right vector position where the mouse clicks. if you want to use this for terrain you have to trace afterwards because .z of temp is set to zero.

it works quite well except that if you click at a hill, its not 100% correct because it takes the z. coordinate at zero and traces upwards the hill. i havent found a solution for that yet.

then i was able to create the movement, after this i switched my.nosend=on; for the players entities. then i create at every client a local entity for the host player with ent_createlocal. when the host player moves i sent 4 times a second his coordinates to all the clients. with this code:

Code:

// send pos to fake every 4 sec
timer += 16 * time;
if(timer >= 64)
{
vec_set(hostplayerposition, my.x);
send_var(hostplayerposition);
timer = 0;
}



then i directly set this coordinates in the entities action. and it works! it lags like hell but i think i can smooth it out with vec_lerp to interpolate between the last received position and the actual position.
i also have to send updates when the entity stops moving so movement ends for all at the same point.

right now it only works for the host player but with 2 clients connected it consumes an average bps of 40! without nosend and 2 clients updating the host on the clients consumes 400. so sending manually is 10 times better. without other stuff like animation changes and so on. right now it only moves the host.

i guess it will take me some time to smooth this movement on the clients. i also have to find out an effiecient way to create a fake entity at all the clients for each other player, get input from this fake entities and move the real entities on the host pc and send upates to the fakes.
i dont have an idea how this system should be set up. i guess it can be done with lots of entity pointers and updating them when a client leaves or joins. i thought about haveing 10 each on the host and client corresponding to the my.player_number. but iam not shure yet.

i hope you could follow iam a bit confused right now as the code is getting bigger and bigger 2500 lines now. and i have to reorganize it and comment it soon again so that i dont lose the overview.

ill keep you updated what i find out. maybe some of you has an idea about how to efficient create fake entities on the clients and keep them updated?
Posted By: ulf

Re: multiplayer game development blog - 05/05/06 19:11

05.05.06 today i made a big step i think i now have a simple movement code for the server player. his positions are sent to all clients 4 times a second . now i have a very simple movement for the clients. they just use c_move to move to every new received position.

this works quite well only sometimes there are little jerks in the movement. i think i know the cause but havent found a good solution yet. the statemachine of the client switches to mode_stand when the last received position is reached with c_move. now if this position is reach bevore the client receives a new position the player stands still for a very little time. and starts moving then again. this is where the jerk comes from.

i made a little sketch to show you how i do movement.



i sent the new position first when the client joins so he knows where the server stands at. and when the server player stops his movement so both end at almost the same position.

i will now think about a way to prevent the problem that the player stops at the client because he reached the last received position faster than he got a new one.

iam pretty happy already because we tested this over the internet and it works with 30-40 bps average!

nice weekend all of you, ill update the blog once i find out new things. if you have any suggestions for me you are very welcome!
Posted By: Locoweed

Re: multiplayer game development blog - 05/06/06 20:29

Hi ulf,

When I first started trying to get multiplayer to work with 3DGS long ago, I was doing things similar to what you are trying. I still believe client-side movement is the most effiecent and best way to go. The concept you are using is pretty much the same, the technique was a bit different though. I sent force-over-time and updated actual position every once and a while, while you are sending points to go to, and sending position updates sometimes. With a point-and-click movement game, sending points to go to might actually be more efficient, although with a FPS or real time movement game where the player can change his direction at any time, I think the force-over-time method would have to be used.

Anyway, nice work. Good reading.

Later,
Loco
Posted By: Locoweed

Re: multiplayer game development blog - 05/06/06 22:04

Hi again,

I was thinking on your "player moves to next received position and then stops and waits for next received position" concept. I think if you managed it right, the client entity would never have to actually stop and wait for next position update. Just giving you something to think about. How could you use some dplay variables such as dplay_latency and fps on the client to predict better when that client should arrive at next waypoint on his computer, minimizing any noticable wait time?

Loco
Posted By: ulf

Re: multiplayer game development blog - 05/07/06 09:08

hey again loco,

i just typed a 1000words reply and it got lost due to a "invalid form" and me not saving it bevore posting... so ill sum up what i wrote.

in my movement code, the server sents every 4 positions the position of the hostplayer to the client and once a client joins his actual position. this goes like this:

x--y1----y2----y3----y4...

as you can see when the client at x reached position y1 it receives. it has to wait 1/4 second till the position y2 is incoming. so it will just stand there waiting. i cannot change the first initial sent update i think because a client could join at any time thus receiving a position from the host wich is like 1mm next to the starting position.

i was however able to get the animations correctly i used the .z coordinate of the hostplayerposition vector to set it to 1 to indicate when the state switches to mode_stand at the host so the clients do this too after they are very close to the last received position.

now you pointed me in the direction of dplay_latency... i can only guess that you mean i move the clients host entity very very slow to the first received position so that it reaches this position not until it gets the second one and so on...
but i doubt this will solve the problem. imagine the client receives starting position of the host at 0,0 then the next position is 0,0.003 due to the send algorithm i use. so the client will move to 0,0.003 and wait there till the next position comes in. moving such a short distance is probably bad. i think i try to let the client only move when the first position is var away from the actual position... lets see how this works.

this is the update code i use to send positions, its placed in the hosts movement of his entity in the while(1) loop at the state MODE_WALK

Code:

vec_set(hostplayerposition, my.x);

// send pos to fake every 4 sec
timer += 16 * time;
if(timer >= 64)
{


hostplayerposition.z = 0;
send_var(hostplayerposition);
timer = 0;
}



so if iam completely wrong please point me to the right direction master

thanks again!
Posted By: LordRathan

Re: multiplayer game development blog - 05/07/06 11:09

Sorry for hijacking this thread, but maybe both of you, ulf and locoweed, should have a look at this thread in Morbius. Maybe it is interesting for you.

Sorry again. ulf, keep up this amazing thread. It is very helpfull.
Posted By: Locoweed

Re: multiplayer game development blog - 05/07/06 21:34

@ LordRathan,
Get back to you on that.

@ ulf,

"imagine the client receives starting position of the host at 0,0 then the next position is 0,0.003 due to the send algorithm i use. so the client will move to 0,0.003 and wait there till the next position comes in."

How do the enitities move on the client from 0,0 to 0,0.0003? With ent_move() (or c_move()), or do you just place it the directly my.X, my.PAN, etc?

Loco
Posted By: William

Re: multiplayer game development blog - 05/08/06 06:21

Using c_move() isn't a good idea due to collision problems and overshooting because of it. For my game I send the positions using send_rate and then interpolated the positions. The only problem with this is the entities appear a bit behind each other on the clients, but on the server they are all in their right posiitons. This poses a problem in a racing game where on your comp it shows the vehicle behind you, but in reality(on the server) it's a little ahead of you. Mabye this doesn't matter much in other genres as much... I'm thinking of something to tackle this, in the meantime you can change this code to still keep them quite close... on lan you dont notice this(since you update every frame).

Code:
 
function movement_smooth() //collision box
{my.invisible = on; //ent_morph(my, "kart2.mdl");
var rotate[3]; var s_angle[3]; var old_angle[3]; var new_angle[3]; var angle_direct; var oangle_direct; var pan_factor;
var calc_angle[3]; var move[3]; var move_old[3]; var xdirect; var move_factor; var zspeed; var length;

while(1)
{
if(my != decoy_1)&&(!lan)
{
my.passable = on;

vec_set(s_angle.pan, my.server_angle);
rotate.pan = ang(s_angle.pan - my.pan); my.pan += rotate.pan * 0.4 * time;
rotate.tilt = ang(s_angle.tilt - my.tilt); my.tilt += rotate.tilt * 0.4 * time;
rotate.roll = ang(s_angle.roll - my.roll); my.roll += rotate.roll * 0.4 * time;
vec_set(old_angle.pan, my.server_angle);

vec_set(move.x, my.server_origin);
if(move_old.x != move.x){vec_set(length.x, move.x); vec_sub(length.x, my.x); vec_set(temp.x, move.x); vec_sub(temp.x, my.x); vec_to_angle(calc_angle.pan, temp.x);}
my.x += length.x * 0.6 * time;
my.y += length.y * 0.6 * time;
my.z += length.z * 0.6 * time;

if(key_v){vec_set(my.x, my.server_origin);}
if(my.fspeed < 5){vec_set(my.x, my.server_origin);}

vec_set(temp.x, nullvector);
if(my.server_origin != move_old.x){my.fspeed = vec_dist(my.server_origin, move_old.x); my.fspeed /= 2.74; zspeed = move.z - move_old.z; zspeed /= 2.74;}
if(my.tracekart){you = ptr_for_handle(my.tracekart); vec_set(you.x, my.x); if(you.spin == 0){vec_set(you.pan, my.pan);} you.z -= 27;}

vec_set(move_old.x, my.server_origin);
}
if(my != decoy_1)&&(lan)
{
my.passable = on;
vec_set(s_angle.pan, my.server_angle);
rotate.pan = ang(s_angle.pan - my.pan); my.pan += rotate.pan * 0.4 * time;
rotate.tilt = ang(s_angle.tilt - my.tilt); my.tilt += rotate.tilt * 0.4 * time;
rotate.roll = ang(s_angle.roll - my.roll); my.roll += rotate.roll * 0.4 * time;
vec_set(old_angle.pan, my.server_angle);

vec_set(move.x, my.server_origin);
if(move_old.x != move.x){vec_set(length.x, move.x); vec_sub(length.x, my.x); vec_set(temp.x, move.x); vec_sub(temp.x, my.x); vec_to_angle(calc_angle.pan, temp.x);}
my.x += length.x * 0.6 * time;
my.y += length.y * 0.6 * time;
my.z += length.z * 0.6 * time;

if(my.server_origin != move_old.x){my.fspeed = vec_dist(my.server_origin, move_old.x); my.fspeed /= 2.74; zspeed = move.z - move_old.z; zspeed /= 2.74;}
if(my.tracekart){you = ptr_for_handle(my.tracekart); vec_set(you.x, my.x); if(you.spin == 0){vec_set(you.pan, my.pan);} you.z -= 27;}

vec_set(move_old.x, my.server_origin);
}

wait(1);
}
}


Posted By: ulf

Re: multiplayer game development blog - 05/08/06 06:38

loco: i just use c_move to get to the next position, this is the complete code of the fakeentity at the client. hostplayerposition is the vector iam sending.

Code:


function hostplayerfakefunction()
{

var unit_speed[3];
var dire[3];

var currentposition[3];

my.mode = mode_stand;
while(1)
{
//stand
if(my.mode == mode_stand)
{
my.anim_index += time;
ent_Animate(my,"stand",my.anim_index,anm_cycle);

// new different hostposition incoming -> go move there
if(abs(my.x - hostplayerposition.x) + abs(my.y - hostplayerposition.y) > 15)
{
my.mode = mode_walk;
}

}

// walking to hostplayerposition
if(my.mode == mode_walk)
{


while (my.mode == mode_walk ) // stop near the target
{



// always face next position
vec_set(temp, hostplayerposition);
vec_sub (temp, my.x);
vec_to_angle (dire, temp); // rotate the unit towards the target
my.tilt = 0; // we only need the correct "pan" angle, not tilt
unit_speed.x = 1.5; // unit speed

// turn
if(((my.pan < dire.pan + 5) || (my.pan > dire.pan - 5)) && (abs(my.x - hostplayerposition.x) + abs(my.y - hostplayerposition.y) > 10 ))
{
my.pan = my.pan + ang(dire.pan-my.pan) * 1 * time;//i am turning

}

c_move(my, unit_speed, nullvector, ignore_me + ignore_you + IGNORE_CONTENT + ignore_sprites + ignore_passable );


// trace to geht right height to terrain
vecFrom.x =my.x;
vecFrom.y =my.y;
vecFrom.z = 200;

vec_set(vecTo,VecFrom);
vecTo.z = -200;

c_trace(vecFrom,vecTo, IGNORE_ME|IGNORE_PASSABLE|IGNORE_CONTENT|IGNORE_SPRITES|USE_POLYGON);
vec_set(temp_loc,vecTo);
my.z = target.z + 32;

// animate
my.anim_index += 5 * time;
ent_animate(my,"walk",my.anim_index,anm_cycle);


// are we near the last received hostposition? and z is 1
if (abs(my.x - hostplayerposition.x) + abs(my.y - hostplayerposition.y) < 5 && hostplayerposition.z == 1)
{
my.mode = mode_stand;
hostplayerposition.z = 0;
}

wait(1);
}


}
wait(1);
}

}



william: very very much thanks for your contribution, ill have a look at your method later in the afternoon. i guess setting directly doesnt take that much ressources like c_move also... however i assume you are using c_move for the server player? do you?
Posted By: William

Re: multiplayer game development blog - 05/09/06 01:26

Naw, at first I was thinking of sending the clients input key to the server and c_moving it on the server, but I ended up just doing c_move on the client then using similar code for both server and clients. You could do your moveing on the server, but if your in a fast paced game it would be wise to always have some sort of client prediciton in place as well. I believe in locos tutorial he does the movement on the server and uses native A6 update methods to relay the positions back. I think it's best to turn off all A6 automatic updates and do everything yourself using send_rate.
Posted By: Locoweed

Re: multiplayer game development blog - 05/09/06 01:27

Hi,

Ok, what I was suggesting on the clients would be to use dplay_latency to arrive at a percent rate to move on the clients.

You are sending update every 1/4 seconds + the time it takes the message to be recieved by the client from the server.

So, right after every new position list is sent every 1/4 sec, take the current latency on client and try to figure out just how slower the client should move adjusted for the latency. The difference in movement speed should hardly be noticeable and should keep more fluient movement happenening on the client.

The equation for figuring out the percent rate to move at would look something like this:

latencyAdjust = .25/(.25 + (dplay_latency/1000)/2);
(I don't know how your movement works though, so this could be a totally wacky equation, and probably is)

.25 seconds (update rate) / (.25 seconds (update_rate) + (dplay_latency (milliseconds)/1000(now in seconds)/2 (latency is round trip, so divide by 2 to estimate send time));

Now let's say we has a latency of 100 milliseconds, we divide that 1000 to get latency in seconds and then divide that by 2 to get approximate send-to-client time. Which comes out to equal .05 seconds. Now that we have the approximate send delay time, we can try to move the client at a percent rate to compensate for the approximate delay time.

so basically we do .25/(.25+.05) = .83

So we move client at 83% of normal speed on client.

Anyway, just throwing that out there. May not really be a good idea even. Just something to think about.

One thing I did notice is that you are not multiplying your unit_speed.x*time in that client code.

You are using time with pan:

my.pan = my.pan + ang(dire.pan-my.pan) * 1 * time;//i am turning

But not with your x force (which you have as unit_speed.x). I think it would behoove you to add before c_move():

unit_speed.x = unit_speed.x * time;
(could make major difference if server and client are somehow not running same FPS)

If you were trying my crazy idea above it would be:

unit_speed.x = unit_speed.x * latencyAdjust * time;

Anyhow, thats all I know.

Later,
Loco
Posted By: ulf

Re: multiplayer game development blog - 05/09/06 08:01

09.06.

big thanks for both of you!

i am now doing the following: i c_move on the host for the player, later ill use a simple pathfinding from the aum for the player. i think c_move is the way to go since this gives the opportunity of easy collision handling. i dont know if it runs smooth with 50 entities doing c_move at the same time. ill see ...

i then send his position 4 times a second to the client in a variable with send_var.
the client has a local entity created with ent_createlocal and moves this entity with interpolation like william posted his example.
it lags a bit behind but thats totally okay! to fix the problem where i receive a short position and then nothing so the player waits short i just do a little trick.
i continue to move the player to the direction the last position was, its likeley that the next update is somewhere near, so the movement is smooth. even if the next position is around the corner its not such a big problem since interpolation turns smoothly then. and with 4 updates a second its not a big deal, you wont notice the way you walked to far.

i even tested updating only 2 times a second, this still works quite good and i get as low as 28 average bps!

now the only thinks iam still not happy about is that when the host has a high movespeed it looks good on the hosts machine, very smooth.
but on the client its a bit choppy, its okay but you see the difference. maybe this is because i test at one computer? i have to test it with my second pc if this is also the case then...

so to sum up right now it works like this: move player on host computer with c_move, send_var to client 4 times a sec., move client with interpolation directly without c_move.

ill post the code in the near future when its cleaned up and commented.

@william, you say you use ent_rate, so you must send everything in skills? how is your system set up? do you create only one entity for a game entity?

i plan to use fixed pointers for each player, like entity* player1... and update those pointers at the server when a client joins or leaves the game. then i want to use variables and arrays to send stuff.
Posted By: William

Re: multiplayer game development blog - 05/09/06 09:29

Quote:


@william, you say you use ent_rate, so you must send everything in skills? how is your system set up? do you create only one entity for a game entity?




While I can only speak from what i'm doing in my game(mabye it's not best for you). I first create 3 entities on the client(kart body, collision cube, wheels). I then set all the pointers and send to clients ect. Afterwards, I move the collision cube like normal. The kart body then follows that and does it's own suspension stuff normally. The wheels then followed the kart body. I had a problem with bad jittering on the client but with none on the server, oddly, I fixed this by setting the kart bodies position directly in the movement smooth for the collision cube. You'll notice this in this line >

if(my.tracekart){you = ptr_for_handle(my.tracekart); vec_set(you.x, my.x); if(you.spin == 0){vec_set(you.pan, my.pan);} you.z -= 27;}

If I instead set that in a function for the kart body like this:

Code:
function movement_smooth2() //body
{my.invisible = off; var stop_vanish; var old_spin;
while(!my.tracekart){wait(1);}
while(!my.kart_point){wait(1);}

if(my.kart_point == 1){mkart1 = me;}
if(my.kart_point == 2){mkart2 = me;}
if(my.kart_point == 3){mkart3 = me;}
if(my.kart_point == 4){mkart4 = me;}
if(my.kart_point == 5){mkart5 = me;}
if(my.kart_point == 6){mkart6 = me;}
if(my.kart_point == 7){mkart7 = me;}
if(my.kart_point == 8){mkart8 = me;}

while(1)
{
if(my != decoy_2)
{
my.passable = on;

c_scan(my.x,my.pan,vector(360,0,160),IGNORE_ME); //trace for local weapons

if(my.spin == 0){stop_vanish = 0;} //spinout?
if(my.spin == 1) && (stop_vanish == 0){stop_vanish = 1; spinkart();}

//you = ptr_for_handle(my.tracekart);
//vec_set(my.pan, you.pan);
//vec_set(my.x, you.x); my.z -= 27;
}

if(my.spin != old_spin){send_skill(my.spin, 0);} old_spin = my.spin;
wait(1);
}
}



Then there would be bad jittering. I've yet to look into this a little more, I know it sounds a bit weird. Also, by running multiple instances on one computer can add some jittering. You'll find if you split it across multiple computers things run a bit smoother.

As to the set up,


Code:
 if(connection) //Multiplayer
{
if(kart_create == 1){my.pan = kc1.pan;}

if(my.client == 1)
{
proc_client(my, move_player);
proc_local(my,local_functions);
//cs_move();

my.invisible = on;
my.nosend_frame = on; my.nosend_alpha = on; my.nosend_ambient = on; my.nosend_color = on; my.nosend_light = on; my.nosend_uv = on;
my.nosend_origin = on; my.nosend_angles = on; my.nosend_flags = on; my.nosend_scale = on; my.nosend_skin = on; my.nosend_sound = on;
}
if(my.client != 1)
{
proc_local(my,local_functions);
move_player();
my.invisible = on;
my.nosend_frame = on; my.nosend_alpha = on; my.nosend_ambient = on; my.nosend_color = on; my.nosend_light = on; my.nosend_uv = on;
my.nosend_origin = on; my.nosend_angles = on; my.nosend_flags = on; my.nosend_scale = on; my.nosend_skin = on; my.nosend_sound = on;
}
}



Then in while loop on server:

Code:
   if(new_sends != people_connected){client_new += 1*time;}
if(new_sends != people_connected)&&(client_new > 160){client_new = 0; new_sends += 1; send_skill(my.tracekart, send_all);} //this part resends pointers to a new client that just joined game

send_skill(my.server_origin, send_vec + send_all + send_rate);
send_skill(my.server_angle, send_vec + send_all + send_rate);



I send skills for most things. Send positions from clien-server, then relay it back to clients from server. To keep things working for both single player and multiplayer, each kart has a pointer "kart1". That way all the single player functions still read it as a player and not A.I(A.I uses kart2-8). Now, I also number each clients kart indpendantly, this is for multiplayer distinction mkart1 - mkart 8. I do weapons/scans on only one computer at time(for example... if u fire projectile, it will detect people only on yours). If it hits on yours, it tells other computers and proceeds with other functions ect. This may not work with your game, it all depends what your trying to do. I also do checks and balances with movement and weapons ect. to keep cheaters away.


Heres a link that explains some multiplayer concepts that counterstrike uses, pretty good reading -

http://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking

P.S - If you find a good way to keep the entities very close to original positions in the interpolation code, be sure to give me a shout.
Posted By: ulf

Re: multiplayer game development blog - 05/09/06 10:17

thanks william, ill test later how it works with more computers. i noticed you set the nosend flags one by one. are you using some things of the internal a6 sending? you could just send nosend with my.nosend = on; instead of all those single instructions.

in my lan simulation it works quite well with the positions, shure the clients lag a little bit behind but they arrive almost at the same time at the same point.

i did normalize the vector where the player moves to, so that there is constant movement. i dont want this starting slow, moving normal, ending slow kind of movement.

i assume you are already using some kind of input prediction for each client? so that they directly see the results of their inputs? its described in that source link. use almost the same code like on the server to display the actions a client presses directly. then you check from time to time if the client is on a different position like on the server simulation and if this is the case correct the position towards the server position slowly. so at least each client gets the feeling that his inputs are processed directly.

for the other cars you can either live with the little lagtime or use extrapolation/dead reckoning. thats what the standard conitec multiplayer system does i think. it calculates the position where an entity "should" be given the previous movement and predicts it. http://en.wikipedia.org/wiki/Dead_reckoning i remember there are some good gamasutra articles about that but havent found the link right now.

i havent looked in depth into it yet because i can live with that little delay in my game. i will however post the code once i got the movement working like i want.

i start the movement once the position of the server entity is a little bit away from the position on the client and end it when the client is near the server position.
Posted By: William

Re: multiplayer game development blog - 05/09/06 10:59

I set the nosend flags one by one since I had a problem with my.nosend = on; messing up a proc_client command or proc_local, I forget which or what it was exactly, but I remember there being a problem that gave me quite a headache...

Unlike the article in the link, I just do all my movements on the clients, and just set the position on the server. No need for prediction then. The reason they do movement on both the player and the server is due to cheating I think. But, I do some checks and balances against this. The problem with doing 2 methods is that there will need to be some sort of correction in your code. For example, there is possibilities for the servers real positions(based on client input) and the clients predicted positions to be out of snyc. You then have to move the client in snyc back which causes jarring.
Posted By: ulf

Re: multiplayer game development blog - 05/09/06 11:34

ahh okay now its clear for me what you mean. however i wouldnt do it that way even if you do checks at the server. i cant await seeing a demo of your game to test this. i learned in serveral threads that this causes the simulation to diverge and brings big problems.

you should be able to get closer to actual positons at your clients if you extrapolate the position of the other clients a little bit ahead of their current position. if you make this relative to the speed you shouldnt have too big problems. so your client player gets the current position from the server along with the current forces of that object and extrapolates a position ahaed of time that compensates the lag time.
you can even use the dplay_latency variable for that like lokoweed suggested so you should be able to predict almost perfectly where the players are at the server - even if you get the older positions and forces only.
Posted By: William

Re: multiplayer game development blog - 05/09/06 12:12

Quote:

however i wouldnt do it that way even if you do checks at the server. i cant await seeing a demo of your game to test this. i learned in serveral threads that this causes the simulation to diverge and brings big problems.




Hmm.. what do you mean by it causing the simulation to diverge and bring up problems? I don't remember reading much about this. Remember, i'm moving on the client, sending the positions to server, then having the server relay the positions back to the clients. The checks on the server only see if the movement is in a realistic range(to make sure client isn't cheating).

Quote:


you should be able to get closer to actual positons at your clients if you extrapolate the position of the other clients a little bit ahead of their current position.




Yeah, I was thinking of doing something like this. It should work quite well, especially in a racing game since it wouldn't be all that sudden(acceleration over time).
Posted By: ulf

Re: multiplayer game development blog - 05/09/06 12:53

look, you have 2 karts driving next to each other. because of network problems you are not getting updates from your opponents kart. in reality on the server he is ahead of you. but because you didnt get updates he is right next to you and blocks your way to drive right. because all your games driving logic is done locally you cant use a shortcut on the right because its blocked on your machine. whereas in reality on the server you would be able to because your opponent didnt block your way.

what happens if a hacked client just says i am moving there, nothing is there? with all game logic at the server he couldnt because you would do checking for that on the server only. what happens if both of your cars say, i want to move there? who gets the right to move there? the one wich is sending first? this probably gives advantage for people with good internet connection.

so its a bad way sending the results of game logic to the server. for client server architecture a general rule is keep all! the game logic including movement at the server.

just have a look at gamedev.net forums for example there are tons of threads like this. i may give only poor examples because iam not very good explaining things like this in english altough i try my best i hope you understand what i mean?
Posted By: William

Re: multiplayer game development blog - 05/09/06 13:19

I understand what you mean. Though my collisions are done mariokart style(arcade like) and do not hamper the gameplay(or block paths) with the expection of a bounce away from the opposing kart. If this game was very dependant on realistic collisions then this would be a problem. Either way the server still contains the "real" positions of all entities at all times. If you keep the world on the client static to the entity(meaning, things dont affect movement on your comp any different than they do on others) you should be fine. I'd use the server positions and some distance testing to see if there is a collision then initiate the bounce sequence on the clients. So lets say theres a thing on the ground that you run into and spins you out. You'd have the server do the scanning for karts. That way it's fair. But your right about the potential issues... I think i'll have to look into this a bit further yet. Though in the meantime i'll keep to this method and will soon see how it all pans out when I do some more indepth testing(crosses fingers).
Posted By: EpsiloN

Re: multiplayer game development blog - 05/09/06 14:27

Quote:

you should be able to get closer to actual positons at your clients if you extrapolate the position of the other clients a little bit ahead of their current position. if you make this relative to the speed you shouldnt have too big problems. so your client player gets the current position from the server along with the current forces of that object and extrapolates a position ahaed of time that compensates the lag time.




Wont that cause a problem ...
server sends the new calculated possition a little ahead of the client to smooth the movement , the client gets the new possition and at the same moment the connection between the 2 computers freeze and the server stops moving that entity...on the client the entity will move until it reaches the given coordinates wich are incorrect because the server stoped moving that entity , this is possible...right?
Posted By: ulf

Re: multiplayer game development blog - 05/10/06 18:27

10.05.06 i finally got the movement working thanks to william and locoweed for their help.

it works now in my testthing that i can move the host player around and send position to the client. its very smooth and works perfect for me.

now my problem is i have to pack this test into a system where every player can move his own entity by sending coordinates to the server wich then calculates his movement with c_move and sends coordinates back.

right now i need two models, one for event_click and movement and one for the player visual model with animations.

i thought about how to manage this efficient and it gives me headaches. i came to the conclusion that it would be the best to create 2 pointers for each player with ent_create so i can send directly to that entity. the first with the name "player" holds the playernumber and is sensitive to event_disconnect so clients can disconnect. then depending on the playernumber i create an entitypointer depending on the playernumber of the player with the name "thisisplayer1" so i can send directly to that player.

then i create pointers like:

*hosthull
*host
*client1hull
*client1
*client2hull
*client2
.
.
.

and create them locally for each player depending how many players are active in the game.

i use variables like client1wantstomovethere[3]; to let the server know where client1 wants to move. then the server calculates the movement and sends back client1ishererightnow[3]; to all clients 4 times a second.

well right now my code is a little mess, and i have to rewrite lots of it so my system works. i know this might not be very good coding style but at least it should make the code a lot more readable and understandable.

ill let you know when iam done and post the movement code.
Posted By: Locoweed

Re: multiplayer game development blog - 05/11/06 01:39

Nice to see you moving along.
Posted By: ulf

Re: multiplayer game development blog - 05/11/06 18:34

11.05.06 just a quick update today, i got a damn cold today i hope it goes away or even gets better tomorrow. thus i just did some code reorganizing. i renamed all functions and created a lot of local entities like described above.
so far it works great okay you got a delay on the client but its shorter than in warcraft 3. and iam able to move the client as well as the server player.

anyways i found a thing wich i think is a bug. shorevietam is experiencing the same. the average_bps from the F11 debug panel seems to be buggy. it has problems updating. when iam using 2 send_var the average bps keeps climbing up to no stop. with one it stays around 40 but when i use 2 send_vars constantly its getting higher and higher - this cant be. iam not using more instances of a function. and iam only sending 2 vectors 4 times a second. so it should be around 40-50 i guess but its not. it goes unlimited high climbing slowly. iam pretty shure i have no bug in my code.
that shorevietam is experiencing the same in his mplayertests is interesting too.
Posted By: ulf

Re: multiplayer game development blog - 05/12/06 18:38

12.05.06 the cold is a little bit better but it still hurts. we just had egg big icerain here for 5 minutes i never saw such a thing bevore. well back to the game. apart from some code cleaning i thought all day long how to achieve a good entity handling then it came to my mind.
my game should not be able to allow players to join later. i totally forgot that in awe about a6 multiplayerprogramming

so what i plan now is the following. the external serverbrowser assigns each player a playernumber already. it then passes it via the -d command to the engine. the host will generate a unique number for the game he starts and assign it too with -d.

at gamestart each client sends his unique game number to the host and checks if its right. this way only players wich where in the same lobby can be in the same game.

now for the entity handling:

first thing i do is create an entity for each player with ent_create that allows the player to disconnect. this entity is placed 1000 quants under the level and is just there to handle the disconnect AND it stores the playernumber assigned by the external starter.

the next thing iam gonna do is, that i browse through all entites and check for the player numbers at the server. so at the server i create pointers for the server with the names: thisisclient1, thisisclient2... so that iam able to use send_var_to directly to them saving bandwith.
again, i browse through the entitys wich where created with ent_create and assign pointers to them because they exist both on the server and client and are just there to handle disconnects and store the playernumber. those entities have .nosend flag set.

then i only work with local entites from now on.

i create 10 local entities for each client with the pointers host, client1, client2...client9
the same for the host, he also gets this host, client1, client2...client9.


each of those entities has a different function. i hope you could follow

now image the following. the client with clientnumber 1 clicks his local entity2. because this is obviously not his own entity he sends a variable request_health_player2 with his player number set. the host then sends the health of player2 to player 1 who requested it by clicking on his local entity for player2.
the same for movement. imagine the client with number 1 clicks his local client1 entity. because of his playernumber 1 he knows this is his entity. now when he clicked the entity he can click somewhere else and this sends the vector client1_wants_to_move_there[3] to the server.
the server then moves his own local entity client1 with c_move, sending back the position client1_is_at[3] 4 times a second. (later ill add sending only to those who can see client1.


i think this is the best system for my kind of game. everything is done by sending variables triggered by local entites back and forth. game logic is kept completely at the host while clients can only make requests and the host then checks if this is possible or not sending results back to clients.


this is contrary to locoweeds system wich was more dynamic but i dont need the ability for players to join and leave as they want because the game is like a soccer game it goes for ~60 minutes average and you level up during that time. so late joined players wont have a chance anyway. only those who join with the right gameid assigned by the serverbrowser will be able to join and pick a race to play with.

because iam not feeling very fresh in my mind right now i postpone the coding to tomorrow probably doing some other designwork for the game right now.

from cleaning up my code i learned, that its better to comment right away when writing. and deleting unused variables as soon as you realize you dont need it. doing this afterwards is just plain boring work.

well i hope there are no flaws in my system and its possible to manage everything from inventory to fighting and so on in my game. if you dont understand a thing just dare to ask or if you find a logical problem please let me know. thanks!
Posted By: William

Re: multiplayer game development blog - 05/13/06 03:37

I just thought i'd note that your way of handling pointers and local entities for bot hthe server and client will probably make things harder. When you create normally, the server and every client has the same entity created on them. If someone joins after the start, then they wont have these entities on them. But, since your planning for fixed creation thats allright. Then again, it's gonna make testing tough, unless you create a system so everyone joins at same time or have all the initial local entities sitting in the level at start.

Anyways, if you go with one normal ent_create instead of local creation, pointers are much easier, and sending information is easier too(because of the pointers). Since every client and server creates the entity, they will all have the same pointer. With local entities your going to have to manually assign pointers, since every entity would be unique. For example, a local soldier on the client and a local soldier on the server will have different pointers by natural, so your going to have to set up a huge list of unique pointers(for every new unit) and go about asinging them properly. This might also mess up the "you" type pointers for handles ect. Cause no longer will the you entity on your comp be the exact same entity on all the other comps.

For the intitial pointer set up, just do it this way, client1 is the first client connected to the game, and so on... then if you create a guard, create it server/client wide. It has it's own unique pointer which is by default the same on all computers. If you click on that entity locally, tell the server, the server then sends a move command to the clients, then move it on the server and clients. In the end I think messing with properly setting pointers locally is much tougher than making sure you properly disable all the automatic A6 state sending globally. In my game the only entities that send positions are the main karts. Everything past that is all states so to speak. For example, a weapon i've got is similar to green turtle projectile in Mariokart. I have it follow the kart, then I send a variable across when I left click mouse, that variable tells the projectile to move on each client individually, then I do the logic on the client/server, and when you end up removing that weapon, you dont have to do no poitner work, it removes on all comps with only one "ent_remove" command!

P.S - hope the cold gets better, they can be quite nasty sometimes.
Posted By: ulf

Re: multiplayer game development blog - 05/13/06 12:55

thanks for your input. i haven been thinking about this now. i still have some problems understanding.

imagine i create the entity client2 = ent_create(...) at the client2, now the function of this entity runs at the server only the function given by proc_local runs at all clients. fine then i could move the entity on all clients according to the incoming data from the server.

now i have the problem that i need a bounding box for mouse clicks for each entity. that box i just copied with clickentity2.x = client2.x and so on. now how do i for example set this clickentity to match client2 on each client since i dont have different pointers for it.
how would you solve that?

shorevietam suggested setting numbers to the entity and iterating on the server then sending back and creating pointers but i think this is much more work than my initial idea.

also as far as i understood if i use client2 = ent_create(...) at the client, only! the pointer client2 is valid at the client. if i use the pointer client2 at the server its empty, isnt it?

well i dont really see the point in creating the entities with ent_create apart from the advantage that all players see it right away when created and i dont have to handle this with invisble flags.
Posted By: fastlane69

Re: multiplayer game development blog - 05/13/06 19:20

Sorry to bust in on your blog, don't really have anything constructive to say (god knows I'm busy enough with my own code) but I just wanted to give you kudos for what you are doing.

This thread will be invaluable for future generations of 3DGS developers and I commend you for the idea and for following through on it.

Great Job, Ulf!!
Posted By: Locoweed

Re: multiplayer game development blog - 05/13/06 21:35

Oh my, a compliment from fastlane69! I think my heart stopped beating there for a second. It passed. I thought I was going to have to call 911 there.
Posted By: William

Re: multiplayer game development blog - 05/14/06 00:21

Quote:


also as far as i understood if i use client2 = ent_create(...) at the client, only! the pointer client2 is valid at the client. if i use the pointer client2 at the server its empty, isnt it?





Yep, it will be empty on the server. If your doing something like this, just go client2 = my; on for every instance of this entity. However, you can only set this pointer once on each computer, otherwise it wouldn't be unique.

Code:
      you = ent_create("wdynamad.mdl", temp, bombw);

my.weapon = 1;

if(you)
{
you.p_find = handle(my); //Tells the weapon what kart created it
sleep(0.1);
send_skill(you.p_find, 0);
}



This code creates an entity, then stores a handle of the kart that created the enity in a skill, then sends that handle to the server.

Code:
  while(!my.p_find){wait(1);}
send_skill(my.p_find, send_all);



You'd then run this code on the server version of the newly created entity. After this, every instance of this entity will have the handle pointing to the kart that created it. I needed this so that the entity follows the kart that created it on every computer.

Anyways, I thought i'd add a bit more food for thought. I still think using ent_create with no local entities is the best way to go. Though, I dont know if i'm fully following what your trying to do, so i'd reccomend going about it your way, and see if it works. Be sure to report back when you've tried it out.
Posted By: ulf

Re: multiplayer game development blog - 05/15/06 11:39

15.05.06 wow, thanks again william. i didnt know handle() does work in multiplayer as you have ent_createlocal. so i guess the handle works only for entities created with ent_create? anyways good to know it seems to work altough its a little bit unlogic for me.

now i see why having all entities created by ent_createlocal is not a good idea unless you have a fixed number of entities.
its a mess when trying to display whos already joined and who not. not to mention npcs and so on. you have to send a lot of status variables back and forth indicating what happend to wich entity.

i think ill rewrite my entity handling code from scratch keeping the working chat and cleaning all the unneeded parts. ill then go the way with ent_create.

another interesting thing i noticed: we tested the movementcode with 4 updates a second via the internet. it was very smooth in the first test, like it is in lan. the second test a weaker connection was host. the movement sometimes stopped for all entities on the clients machine at the same time.
i guess this is due to a missing/delayed packet of the positionvector. so i have to take care of that in the movementcode too. i think ill just move on slower heading in the direction of the last received position. this will make the movement a bit choppy when it lags but when no data is incoming the game has to stop anyway.

i feel that if i manage to handle the entites right. i have done the hardest part.

my target is to get the 10 players into the game able to walk around like in wc3 with their own avatar. this already worked in my previous system pretty good but with more players joining it becomes a mess handling the entities.

ill keep you updated, thanks for rating this thread and your nice comments. this keeps me motivated on top of my own motivation to realise this game i have in mind!
Posted By: William

Re: multiplayer game development blog - 05/16/06 10:20

Quote:

so i guess the handle works only for entities created with ent_create?




Naw, I'm pretty sure there is no problem using handles and ent_createlocal, but it would only be valid on your computer, as the other comps have no knowledge of the entity.

Your online finds are intresting, thanks for posting them. While I haven't actually done any testing online yet(plan to use the -sim in the future). I'd think your right that this is probably due to the positional updates not reaching their destination in time from the server. There is multiple solutions to this, and it really depends on what your trying to do:

- You can have it so your interpolation of entities on the client are 2 updates behind your servers real position. That way if one update is delayed or lost, there will always be an extra one. This sucks on it's own, cause your entities wont be very near to their real positions.

- If this fails(both updates lost), then extrapolation based on previous updates would be your only choice.

- How weak was your connection for your second test? If your game is planned for dsl and lan only you may be able to get away with these things. Most people with 56k(like me) don't even bother playing UT online ect. Cause it isn't too fun. Also, usually the people who run servers should have the best connection. Player amounts factor in as well.

Since my game uses quite a bit of projectiles. I'm going to have to keep the entities as close to the server position as possible. I think i'll keep it 2 updates back(currently it's only 1 update back), but extrapolate constantly on top of that. Using the angle of the player and the angle to the next update position to help deterine where to extrapolate too. I dont know if this would be possible for all game types, since racing is more constant and predictable than an FPS game.

If your doing an FPS game, I wouldn't reccomend a constant extrapolation, because you'll have great overshooting. Keep things back, but deal with it on the server. No one knows better in an FPS game if the player running is actually behind where he is in reality. You'd still do targeting on the client, but you'd have to back everything up on the server to verify it. Racing games, especially with projectiles, don't have this luxury. If you see someone win a race, when on your screen there behind you, but in reality they crossed the finish line first, you'd be mad... so it's sorta a given to extrapolate. If anyone knows of a good solution for this, it would be great though, cause extrapolating can cause problems.

Heres a couple more Q's to consider - what type of internet connection are you aiming for with your rts/fps hybrid? Was send_unreliable set for your positional skills when you did the online test? Also, mabye try sending more updates to see what happens, only 4 updates a second seems a little low, I think even quake2 have 10 updates a second. Hope to hear the answers sometimes soon, I think this is a good learning experience for both of us.

Quake2 Multiplayer Specs:

http://www.csse.monash.edu.au/~timf/bottim/q2net/q2network-0.03-4.html
Posted By: ulf

Re: multiplayer game development blog - 05/16/06 10:34

hey there, thats what i mean. locally it can not work but for entities created globally this was even new for me.

the game we are making is aimed for dsl primarily but it should run with isdn or even modem as well at the client side. the server will need dsl cause of the high upload value.

the connection the host had was a weak dsl, with some problems. he had 25kb upload though, that should be plenty for the data the game sends(2 vectors 4times a second). download is 75-100kb i think but that should be totally okay. the problem was that sending stucked after some random time every ~10 seconds. for all moving entities - while i was host movement was smooth as expected. i have almost the same connection. this was probably due to some technical issues i dont know if the data was lost or delayed. the outcome was that the entity stopped when it rechead the last known position then after 1/2 second it moved on.

edit: dplay_unreliable was set to off; like standard for this tests. also iam not making an rts/fps. i do a rts/rpg kind of game every player controls an avatar like diablo but there will be lots of npcs walking around and fighting.
thats why i try to keep updates low from the beginning. because in total i think ill have around 50 moving entities at a time average. players will be limited to 10. 5 on each team. each on of those 5 needs data from half of the total entities. so lets say one player needs data of ~30 moving entities average. all that with ent_move on the server (i switched from c_move because its faster) sent to the clients.
Posted By: William

Re: multiplayer game development blog - 05/16/06 10:54

Yeah, that does sound weird. It must be something technical, I don't see how the positional updates would be lost or delayed with this good of a connection.

Your game sounds intresting, always nice when someone does something original. And I dont think you'll have to keep the updates very low for the main players if you keep it at 10. NPC's don't need constant updates or c_move(), they should be handled via pathfinding. All you should do is send an skill indicating that the NPC should move, then on each computer move it via pathfinding sperately. For my projectile weapons, I just send a skill to their instances saying "move", then they move on each computer according to their code seperately without any further multiplayer updates. Another thing, if your game is like diablo, then it won't be as fast paced like Unreal Tournament or something, so this should make multiplayer coding a bit easier in the long run. Of course I dont know the exact details of what your planning, but mabye you can get away with constantly updateing only some core entities.
Posted By: ulf

Re: multiplayer game development blog - 05/18/06 11:59

18.05.06 okay, iam almost done rewriting the system so that every player only uses 1 entity of his own. sending positions is done via skills. ill explain the whole movement system in total once its clean and works good.

a very important thing i came across is that whenever you create entities in a network game you should have sleep(1); set as the first statement in the entities action. if you dont, you will get problems that some clients dont get this entity.

this is what happend to me when i was testing with 4 clients total. some just didnt get updates from others. very strange. a sleep(1) at the beginning of the entities action solved my problem.

i then tested the game with a friend again over the net. it worked good like bevore. i just measured that i have an average up and download of ~0.5 kb per second as host. for comparison if i host a wc3 game for 10 players i have like 5kb download and 5-10kb upload per second.
maybe i can stay below that with my game who knows yet.

my problem now still is the movement at the client. i sent the position to go to to the host. and the host then calculates in the entities server function the movement. so far this works like a charm.
then i sent data back with SEND_RATE in a skill only when the entity is moving at the host.
my problem still is to get a smooth movement out of this at the client. this is what iam working on right now. this code also needs to take into account dropped packets.

what i will do is move to the first received point, then keeping this direction but getting slower constantly until the next position comes in. then ill turn slowly to this new position and increase speed to normal.

i also realised that send_skill is a problem. because it sends to all clients or to the creator only. but for updating others players positions you have to send to all! thats where the problem is. its a waste of bandwith. imagine only 1 out of 10 players see a certain player of those 10. now all 9 others get this data too and cant even display him because of fog of war. so its a waste of bandwith.

the next problem is, that when you sent position updates to all players you enable them to cheat. only thing they would have to do is read the data they get and thus they know where the other players are. or even worse simply remove the fog of war.

thats why i suggested a send_skill_to statement in the future forum.

http://www.coniserver.net/ubbthreads/showflat.php/Cat/0/Number/655449/an/0/page/0#Post655449

please support this idea by posting there if you think its a good one. let me know if you have some possible workarounds in mind. thanks.

edit: i forgot. i think i found a problem? in locoweeds code if i can call it so. he sends the player number to all players once a new player joins in the function running at the server. this is fine so far the only problem is that newer players dont get the player numbers of those players already in the game. maybe iam totally wrong here but i solved this with a ent_next loop in the entities function at the server. so it loops through all entities, checks if this entity has a playernumber and then sends it to all clients. maybe iam totally wrong here and the newer players dont even need the playernumber skill from the ones already in the game. in my case they do because if for example you want to attack another player you have to let the server know wich one you want to attack therefore every client needs to know every other players unique playernumber skill.
Posted By: TWO

Re: multiplayer game development blog - 05/18/06 19:10

Just to say it, the server mentioned above was my pc, with 40-50 kb down and 20 kb up... I really think most kidds that play games have DSL and this is standart yet; But maybe Ulf is cheating and makes our game playable for me,too
Posted By: William

Re: multiplayer game development blog - 05/19/06 01:07

Quote:


what i will do is move to the first received point, then keeping this direction but getting slower constantly until the next position comes in. then ill turn slowly to this new position and increase speed to normal.




This wouldn't work. If you speed up and slow down your entities the player will notice it. I spent a few days myself trying to get this working purely via angles, but that does not work either since your simulation will diverge. That past example I showed you is what you should keep using. Though, keep it 2 updates behind. This will keep it very smooth and accuracte, however, it will always be behind. To counter this, try extrapolating the position based on angle, so it is always a certain distance ahead the interpolated position. While I havn't done the extrapolating with the code yet, I will in a couple days, and see if it works like I think...

Your right about the waste of bandwidth, I havn't thought of this previously, but now that you've mentioned it I posted in your future thread. It would be a good feature to selectively send updates.

Quote:

maybe iam totally wrong here and the newer players dont even need the playernumber skill from the ones already in the game. in my case they do because if for example you want to attack another player you have to let the server know wich one you want to attack therefore every client needs to know every other players unique playernumber skill.




Yes, new players will need the unique id of the past players. I don't remember how locoweeds was doing it, but the way i'm doing it works quite well. Here's how:

New client connected function:


Code:
 
function server_called()
{
//if new player connected
if((event_type == event_join) && (people_connected < 8))
{
ifdef server;
// old_pconnected = people_connected;
people_connected += 1; //another person connected
sleep(2);
send_var(people_connected); //send number of people connected
endif; //ifdef server
}

//if player disconnected
if(event_type == event_leave)
{
ifdef server;
people_connected -= 1; // one less person connected to server
sleep(2);
send_var(people_connected); // send number of people connected
endif;
}
}

on_server = server_called; // server called



Now, on the top of the function for your entity which contains the unique id:

Code:
   if(people_connected == 1){mkart1 = me; my.kart_point = 1;}
if(people_connected == 2){mkart2 = me; my.kart_point = 2;}
if(people_connected == 3){mkart3 = me; my.kart_point = 3;}
if(people_connected == 4){mkart4 = me; my.kart_point = 4;}
if(people_connected == 5){mkart5 = me; my.kart_point = 5;}
if(people_connected == 6){mkart6 = me; my.kart_point = 6;}
if(people_connected == 7){mkart7 = me; my.kart_point = 7;}
if(people_connected == 8){mkart8 = me; my.kart_point = 8;}



In the while loop of your entities on the server.

Code:
   if(new_sends != people_connected){client_new += 1*time;}
if(new_sends != people_connected)&&(client_new > 160){client_new = 0; new_sends += 1; send_skill(my.kart_point, send_all); send_skill(my.tracekart, send_all);send_skill(my.wheelkart, send_all);}



To sum it up, when an entity first connects, the people_connected variable goes up by one. You then send this variable to all existing clients. Now, on the new client that just connected you set it's unique id by the existing value of people_connected. In other words, if people_connected is 2, then the new client entity will be id#2.

Now that you've set this skill, you must have some commands for it to send the skill when a new player joins, otherwise only existing players will contain this skill. So that's why you put that code in the while loop on the server, that detects when a new client joins, then it resends all the important skill information(pointers, id, ect.) to the newly joint entity.

Theres probably many ways to do this... but no matter what your doing, you have to make sure you have a little bit of code in each entity function that will automatically update all the new clients of important skills ect. when they join.
Posted By: ulf

Re: multiplayer game development blog - 05/19/06 07:34

please let me know when you done the extrapolation and how you did it i have just finished the movement again. iam now doing it the way described, when there i currently no new position incoming, the player just keeps moving into the last known direction a little bit slower (not noticable). this works great, only thing what can happen is that sometimes you make a little extra curve but that doesnt really influence anything.

i always move the client player a little bit slower than the real player to make shure he doesnt always reach the position close.
this way the clients are 1/2 second delayed in the internet i would say. a test yesterday evening showed that with this method no lags occur at all in the movement.
they also showed when 2 players are constantly moving with this system. (the server uses send_skill(my._receive_x, SEND_ALL | SEND_VEC | SEND_RATE | SEND_UNRELIABLE); to send the data with dplay_entrate = 4;
i have almost constantly 0.5kb/s upload at my network card. when only one player is moving theres 0.2-0.3 kb/s and if no on moves its around 0.1-0.3 kb/s wich i guess are the keepalive ping messages.

i made a little video to show the movement. this video is taken at the client its very slow due to the recording software so dont worry about that. just notice how the player moves at the client. this was taken in lan.

http://www.ackbytes.de/upload/movement.wmv

(850kb size of movie)

use videolan to watch it, have fun also notice the panel i made can be dragged around and it can be pinned so you dont accidently move it around. almost all hud elements in the game will be moveable this way.
Posted By: William

Re: multiplayer game development blog - 05/19/06 07:53

I took a look at the video. I see your doing something very different than what i'm doing. It looks like you've got things running properly. I assume your using the mouse position and moving the entity directly there? By the looks of things, I dont think you'd ever need to constantly update your players position(if it keeps moving to where mouse clicked). The only position you should send is the mouse position(upon clicking) and send it reliable.

All along I thought you were doing something realtime(user input via keyboard), lol, but I guess multiplayer coding techniques can be very generalized and work across all genres.
Posted By: ulf

Re: multiplayer game development blog - 05/19/06 08:03

yes right now it works this way but in future it will include pathfinding done from the server. shure i could also send only the position of the nodes to go to maybe ill even do that - but for npcs wich will be quite a lot i need this quick response system. because they will have a simple path following not finding technique and they can follow the player short distances i need this system.
Posted By: ulf

Re: multiplayer game development blog - 05/23/06 21:20

23.05.06 just a little update tonight. i was in erfurt the last weekend, it was a very nice visit. lots of old little medieval buildings, a bridge where on both sides are houses so if you walk in the middle you dont notice that its a bridge. was very nice, i can recommend it to you.
then when i came back i finally got the result of the last test i wrote and i passed it! it was hell of a test and i didnt thought it turned out this way but this is very good news for me!

i did a little work on the project today. what i did was fix the spawning. because i set nosend to off. i had to make shure that at the client the other entities are correctly displayed. so i sent the coordinates whenever a new player is created to all clients. this is done with a ent_next while loop inside the players action at the server just bevore the statemachine enters the while(1) loop.

i also implented an idea i had while ago. our starter will generate a unique id and pass it to the server via -d parameter. then all clients have to start their instance of the game with the same unique id in the -d parameter. if they dont they get booted.
this way i assure that only the right players can join the right game from our launcher. at least i think so at the moment

so now to sum up, the chat works, joining/leaving, entity player creating and simple player movement.

the next things iam gonna do are probably some fun ones. i think ill implent the basic stat system and the inventory next. maybe ill even add a little npc and test some fighting system ideas.

theres plenty left to do for me. another thing i gotta to soon is implent the level load for xpresso's outstanding editor ipagED.

ill let you know once i get the next results. oh and i have to keep my promise and post my movement code here. ill comment it a bit better soon and do so.
until then, have a nice week!
Posted By: Garrettwademan

Re: multiplayer game development blog - 05/26/06 02:47

Nice Ulf, I have been having trouble with the movement code because either A) I can't update the clients models on the server and send them to other clients or B) can't figure out what the heck is going on because I don't know the best way to do it. That would be awsome, by the way, nice job dude, I like what your doing, I learned alot so far from all the posts. Keep it up.
Posted By: Garrettwademan

Re: multiplayer game development blog - 05/26/06 14:14

First off, WEEEEEEEEEEEEEEEEE,, I FINALLY GOT MY MOVEMENT CODE WORKING AFTER BEATING MY HEAD INTO A WALL. haha, sorry, I am just a bit excited that I got it working. Ok, off to finish my game.
Posted By: ulf

Re: multiplayer game development blog - 05/29/06 12:05

29.05.06 okay. today as promised the movementcode. please note that this is the code i currently use. it will not work if you copy and paste it to your projects. however you may study it to see how it works for me.

in short, i move the player at the host with ent_move and send positions with send_skill using SEND_RATE 4times a second to the client. the client then interpolates between the positions. the interpolation code is very similar to those william posted in this thread.

the skelleton of the code is similar to locoweeds tutorial.

the project is coming along niceley. right now i tested a simple inventory code so players both the client and host can pick up items dropped by npcs or other players. i do this by first creating an item with ent_create giving it an item_id wich defines wich item it is. then i make it sensitive for click. if its clicked at the client i set the skill ._player_number_wants_to_pick_me_up to the playernumber of the player who clicked it and send this skill to the server. the server then puts this item in the inventory skill slot of the player and sends back that its done to all players!

next things iam gonna do is making this work with movable panels so players can drop items with the mouse once they are in the inventory.

but heres the movement code i promised, have fun with it! note: it may not be perfectly clean or bugfree, please let me know if you find some majore mistakes. thx

Code:


// sets all nosends, because setting my.nosend = on causes problems in 6.4.x
function set_nosend()
{
my.nosend_frame = on; my.nosend_alpha = on; my.nosend_ambient = on; my.nosend_color = on; my.nosend_light = on; my.nosend_uv = on;
my.nosend_origin = on; my.nosend_angles = on; my.nosend_flags = on; my.nosend_scale = on; my.nosend_skin = on; my.nosend_sound = on;
}



// function for player events on the server
function real_player_event()
{

// client disconnected
if (EVENT_TYPE == EVENT_DISCONNECT)
{
// do this to keep the number of players in order
gl_number_of_players -= 1; // decrement number of players

// go through all entities, any player # that was above mine, decrement his player number
you = ent_next(NULL);
while(you != NULL)
{
if(you._player_number > my._player_number)
{
you._player_number -= 1;
send_skill(you._player_number, SEND_ALL);
}
you = ent_next(you);
}

send_var(gl_number_of_players); // let everyone know new number of players
ent_remove(me); // remove ent of player that quit
}
if (EVENT_TYPE == EVENT_CLICK)
{

}
IF (EVENT_TYPE == EVENT_TOUCH) {
MY.LIGHT = ON;
MY.RED = 255;
MY.GREEN = 0;
MY.BLUE = 0;
RETURN;
}
IF (EVENT_TYPE == EVENT_RELEASE) {
MY.LIGHT = OFF;
RETURN;
}



}

// event handler for clients
function real_player_local_event()
{
if (EVENT_TYPE == EVENT_CLICK)
{

}
IF (EVENT_TYPE == EVENT_TOUCH) {
MY.LIGHT = ON;
MY.RED = 255;
MY.GREEN = 0;
MY.BLUE = 0;
RETURN;
}
IF (EVENT_TYPE == EVENT_RELEASE) {
MY.LIGHT = OFF;
RETURN;
}


}

// local entity function for getting click and so on
action real_player_local()
{

my.invisible = on;
sleep(1);


// get faction and send to server but only for the own player instance
if(me == player)
{
my._faction = id_faction;
send_skill(my._faction, 0);
my._race = id_race;
send_skill(my._race, 0);
}

var calc_angle[3];
var move[3];
var move_old[3];
var length; // range from real to target pos
var vecfrom[3]; // temp
var vecto[3]; // temp
var temp_loc[3]; // temp

my.enable_click = on;
my.enable_touch = on;
my.enable_release = on;


my.event = real_player_local_event;

my.passable = on;


// do this to assure players are in mode_stand at start
my._receive_x = my.x;
my._receive_y = my.y;
my._receive_z = 1;
// trace to geht right height to terrain
vecFrom.x =my.x;
vecFrom.y =my.y;
vecFrom.z = 200;

vec_set(vecTo,VecFrom);
vecTo.z = -200;

c_trace(vecFrom,vecTo, IGNORE_ME|IGNORE_PASSABLE|IGNORE_CONTENT|IGNORE_SPRITES|USE_POLYGON);
vec_set(temp_loc,vecTo);
my.z = target.z + 32;

// start animation loop
animate();

// intially set animation to stand, this will be changed below if necessary
my._animation_state = animation_state_STAND;

my.invisible = off;

sleep(1); // fix for bug when players didnt move
my._mode = mode_stand;

while(1)
{
my._prev_anim_state = my._animation_state; // save animation state

//stand
if(my._mode == mode_stand)
{
my._animation_state = animation_state_STAND;

// new different hostposition incoming -> go move there
if(my._receive_z == 0)
{
my._disttotarget = abs(my.x - my._receive_x) + abs(my.y - my._receive_y);
if(my._disttotarget >= 35)
{
my._mode = mode_walk;
}
}
}

// moving
if(my._mode == mode_walk)
{
my._animation_state = animation_state_WALK;

my._disttotarget = abs(my.x - my._receive_x) + abs(my.y - my._receive_y);

if (my._disttotarget >= 12)
{
// interpolate movement
vec_set(move.x, my._receive_x);

if(move_old.x != move.x)
{
vec_set(length.x, move.x);
vec_sub(length.x, my.x);
vec_set(temp.x, move.x);
vec_sub(temp.x, my.x);
vec_to_angle(calc_angle.pan, temp.x);
}

vec_normalize(length, 1);


my.x += length.x * (my._movespeed -0.8 ) * time;
my.y += length.y * (my._movespeed -0.8 ) * time;
vec_set(temp.x, nullvector);

vec_set(move_old.x, my._receive_x);

// turn
if((my.pan < calc_angle.pan + 10) || (my.pan > calc_angle.pan - 10))
{
my.pan = my.pan + ang(calc_angle.pan-my.pan) * 0.5 * time ;//i am turning
}
}
// prevent stopping
if ((my._disttotarget <12) && (my._receive_z == 0))
{
my.x += length.x * (my._movespeed -1 ) * time;
my.y += length.y * (my._movespeed -1 ) * time;
}


// trace to geht right height to terrain
vecFrom.x =my.x;
vecFrom.y =my.y;
vecFrom.z = 200;

vec_set(vecTo,VecFrom);
vecTo.z = -200;

c_trace(vecFrom,vecTo, IGNORE_ME|IGNORE_PASSABLE|IGNORE_CONTENT|IGNORE_SPRITES|USE_POLYGON);
vec_set(temp_loc,vecTo);
my.z = target.z + 32;

// are we near the last received hostposition? and z is 1
if ((my._disttotarget < 12) && (my._receive_z == 1))
{
my._mode = mode_stand;
}

}

wait(1);
}


}


// action for the player is run at the server
action real_player
{


my.invisible = on; // set this so others dont see me until created

var dire[3]; // turn to angle
var vecfrom[3]; // temp
var vecto[3]; // temp
var temp_loc[3]; // temp


sleep(1); // this fixes the problem where some players dont receive data
proc_local(my, real_player_local); // function for client




my.enable_disconnect = on;
my.event = real_player_event; // event function for server

// server gives the players entity his player number
gl_number_of_players += 1; // after entity created increment number of players ingame
send_var(gl_number_of_players); // send this new number to all connected people

my._player_number = gl_number_of_players; // set the player number to the skill


// this code is to solve high latency creation problem
sleep(.3); // this can be left at .3 no matter what
ent_sendnow(my);
sleep(.3); // sleep(3); // high latency solution for now

set_nosend(); // dont send any automatic updates

my.enable_click = on;
my.enable_touch = on;
my.enable_release = on;
my.passable = on;






// set faction and race for host
if(connection == 3 && me == player)
{
my._faction = id_faction;
my._race = id_race;
}

// wait for faction and race if client
while((!my._faction) && (!my._race)) {wait(1);}
send_skill(my._faction, SEND_ALL);

if(my._race == RACE_HUMAN_MALE)
{
my._health = HEALTH_HUMAN;
my._mana = MANA_HUMAN;
my._movespeed = 8;
}

if(my._race == RACE_ORC_MALE)
{
my._health = HEALTH_ORC;
my._mana = MANA_ORC;
my._movespeed = 13;
}


// send entity skills of already ingame players
you = ent_next(NULL);
while(you != NULL)
{
if(you._movespeed)
{
send_skill(you._movespeed, SEND_ALL); // send the movespeed so others can move my entity
}
if(you._player_number)
{
send_skill(you._player_number, SEND_ALL); // send my player number skill to all players
}
if(you._faction)
{
send_skill(you._faction, SEND_ALL); // send my faction id skill to all players
}
if(you._health)
{
send_skill(you._health, SEND_ALL); // send health skill to all players
}
if(you._mana)
{
send_skill(you._mana, SEND_ALL); // send my mana skill to all players
}
if(you._race)
{
send_skill(you._race, SEND_ALL); // send my race skill to all players
}

// send position and mode also for right start positions
send_skill(you.x, SEND_ALL);
send_skill(you.y, SEND_ALL);
send_skill(you._mode, SEND_ALL);


you = ent_next(you);
}



// set initial target coordinates so entity doesnt start moving to nullvector at start
my._target_x = my.x;
my._target_y = my.y;

// start animation loop for host
animate();

my._mode = mode_stand; // starting mode

// intially set animation to stand, this will be changed below if necessary
my._animation_state = animation_state_STAND;


// trace to geht right height to terrain
vecFrom.x = my.x;
vecFrom.y = my.y;
vecFrom.z = 200;

vec_set(vecTo,VecFrom);
vecTo.z = -200;

c_trace(vecFrom,vecTo, IGNORE_ME|IGNORE_PASSABLE|IGNORE_CONTENT|IGNORE_SPRITES|USE_POLYGON|GLIDE);
vec_set(temp_loc,vecTo);
my.z = target.z + 32;

my.invisible = off;
while(1)
{


my._prev_anim_state = my._animation_state; // save animation state
//stand
if(my._mode == mode_stand)
{
my._animation_state = animation_state_STAND;
my._receive_z = 0;
if(abs(my.x - my._target_x) + abs(my.y - my._target_y) > 20)
{
my._mode = mode_walk;
}
}
//move to location stored in my.target_x
if(my._mode == mode_walk)
{
my._animation_state = animation_state_WALK;
my.tilt = 0; // we only need the correct "pan" angle, not tilt


while (abs(my.x - my._target_x) + abs(my.y - my._target_y) > 15 ) // stop near the target
{
// turn
vec_set(temp.x, my._target_x);
vec_sub(temp.x, my.x);
vec_to_angle(dire.pan, temp.x);

if((my.pan < dire.pan + 10) || (my.pan > dire.pan - 10) )
{
my.pan = my.pan + ang(dire.pan-my.pan) * 0.5 * time;//i am turning
}

// move
vec_diff(temp.x, my.x, my._target_x);
vec_normalize(temp.x,- my._movespeed * time);

// ent_move cause its 20x faster
move_mode = glide+ignore_me+ignore_you+ignore_sprites+ignore_passable;
ent_move(nullvector, temp.x);
//c_move(my, nullvector, temp.x, glide|ignore_me|ignore_you|IGNORE_CONTENT|ignore_sprites|ignore_passable );

// trace to geht right height to terrain
vecFrom.x = my.x;
vecFrom.y = my.y;
vecFrom.z = 200;

vec_set(vecTo,VecFrom);
vecTo.z = -200;

c_trace(vecFrom,vecTo, IGNORE_ME|IGNORE_PASSABLE|IGNORE_CONTENT|IGNORE_SPRITES|USE_POLYGON|GLIDE);
vec_set(temp_loc,vecTo);
my.z = target.z + 32;

// send current position to other clients
vec_set(my._receive_x, my.x);
my._receive_z = 0;
send_skill(my._receive_x, SEND_ALL | SEND_VEC | SEND_RATE | SEND_UNRELIABLE);


wait (1);
}

// switch back to standing
my._mode = mode_stand;
vec_set(my._receive_x, my.x);
// z = 1 indicates stop of movement
my._receive_z = 1;
send_skill(my._receive_z, SEND_ALL | SEND_VEC );
wait(1);

}
wait (1);

}

}



// function create_player() create a player at spawnpoint
function create_player()
{

if (id_race == RACE_HUMAN_MALE )
{
player = ent_create(str_cbabe,vector(100,300,0),real_player);
while(!player) {wait(1);}
}
if (id_race == RACE_ORC_MALE )
{
player = ent_create(str_warlock,vector(100,100,0),real_player);
while(!player) {wait(1);}
}


}



// set the race to human
function set_race_human()
{
id_race = RACE_HUMAN_MALE;
cl_race_not_set = FALSE;
}

// set the race to orc
function set_race_orc()
{
id_race = RACE_ORC_MALE;
cl_race_not_set = FALSE;
}

// set the race to woodelv
function set_race_woodelv()
{
id_race = RACE_HUMAN_MALE;
cl_race_not_set = FALSE;
}


Posted By: William

Re: multiplayer game development blog - 05/30/06 04:50

Your code looks good Ulf. Another tip might be to create your multiplayer entities at a position like vec(2000,2000,-5000), and then afterwards when your ready, set them to their proper positions. I noticed that upon creating some entities, you'll bounce into them even if you set them as passable, due to the time needed for it's instances, ect.


Anyways, i've finished converting a unique weapon compared to what I was converting before. I've learned a few things, and have an update to the interpolation code with extrapolation.

Code:
function movement_smooth() //collision box
{
my.invisible = on; my.passable = on;
var rotate[3]; var s_angle[3]; var old_angle[3]; var rpos[3]; var zpos[3]; var pos_add;
var calc_angle[3]; var move[3]; var move_old[3]; var zspeed; var length;

while(1)
{
if(my != decoy_1)&&(!lan)
{
my.passable = on;

vec_set(s_angle.pan, my.server_angle); //iterpolate server angle
rotate.pan = ang(s_angle.pan - my.pan); my.pan += rotate.pan * 0.8 * time;
rotate.tilt = ang(s_angle.tilt - my.tilt); my.tilt += rotate.tilt * 0.8 * time;
rotate.roll = ang(s_angle.roll - my.roll); my.roll += rotate.roll * 0.8 * time;
vec_set(old_angle.pan, my.server_angle);

vec_set(move.x, my.server_origin); //interpolate server position
if(move_old.x != move.x){vec_set(length.x, move.x); vec_sub(length.x, rpos.x); vec_set(temp.x, move.x); vec_sub(temp.x, rpos.x); vec_to_angle(calc_angle.pan, temp.x);}
rpos.x += length.x * 0.6 * time;
rpos.y += length.y * 0.6 * time;
rpos.z += length.z * 0.6 * time;

vec_set(zpos.x, rpos.x);

pos_add = my.fspeed * 4; //extrapolate to original position, fspeed is the speed of entity
if(pos_add > 400){pos_add = 400;} //if(pos_add < 40){pos_add = 0;}
temp.x = -pos_add; temp.y = 0; temp.z = 0;
vec_rotate(temp.x, my.pan); vec_add(zpos.x, temp.x); vec_set(my.x, zpos.x); vec_set(zpos.x, nullvector);

if(key_v){vec_set(my.x, my.server_origin);}
if(my.fspeed < 5){vec_set(my.x, my.server_origin);}

vec_set(temp.x, nullvector);
if(my.server_origin != move_old.x){my.fspeed = vec_dist(my.server_origin, move_old.x); my.fspeed /= 2.74; zspeed = move.z - move_old.z; zspeed /= 2.74;}
if(my.tracekart){you = ptr_for_handle(my.tracekart); vec_set(you.x, my.x); if(you.spin == 0){vec_set(you.pan, my.pan);} you.z -= 27;}

vec_set(move_old.x, my.server_origin);
}
wait(1);
}
}



This actually worked quite well, and now all the karts on each client and server see eachother at their proper positions with smoothness. But, there is one thing I should say. This will most likely only work in a situation where you have acceleration, such as a racing game. Because you extrapolate according to your acceleration. If you put this in a FPS game, the extrapolation would be sudden, and it would look jarred. It needs a certain degree of predictability too, since it extrapolates according to the players angle. If your angle updates are very far behind, or your angle quickly changes, this may not work as well.

But, this is a good solution for racing games, and should fix the problem of 2 players crossing the finish line at same time, and on one computer it shows you win, but on another you lose, and in the end, you win, because on the other computer your position is being interpolated. It also fixs the problems with aiming projectiles.

If your doing an FPS you wont have these problems anyways. Afterall, a character in an FPS moves much slower...

A few other things i've learned:

- If your rotating bones via user input, just send the position similar to sending the server angle.

- Local entities can be used in a dynamic multipalyer environment. I ran into a problem where I needed to create a couple entities everysecond(projectiles). At first I coded it using multiplayer entities(instances), but then I realized the amount of traffic this produced. So I ended up just sending a skill determining if the entities should be fired, and if they did, fire local entities on each computer. Do the events on only one computer, and have local entities for the rest. Of course the problem with this is that the two local entities can become out of sync(delete on one comp, stays on another). So only use this method if you really need it, and if your sure there movement is quite linear... so they'll generally delete at the same time on both comps, ect.
Posted By: ulf

Re: multiplayer game development blog - 05/30/06 06:30

very nice that you let us know william! thanks! as for the creation thats what i noticed too. i have tested this yesterday when i was making an item pickup action. the entity is created shortly after the server and it is created vsibile and as obstacle all the time!
so even if you set them passable at the client function you will run into them. ill do what you suggested i create them somewhere the player doesnt see them and send proper positions once a sleep(1) at the server function is finished.

good to see you advancing with your racing game. when will you release more info or even a demo?
Posted By: William

Re: multiplayer game development blog - 05/30/06 07:30

Quote:


good to see you advancing with your racing game. when will you release more info or even a demo?




I've gotta set up a website first, and decided there is no point doing so until the game is closer to completion... Probably will see something(info, screens, video) sometime in the next 6 months. But nothing is certain... so it may take longer.
Posted By: ulf

Re: multiplayer game development blog - 05/30/06 09:59

30.05.06just did a load test. what i did was place 63 spiders in the level with a simple script from the aum. then i send all positions of them with:

vec_set(my._receive_x, my.x);
my._receive_z = 0;
send_skill(my._receive_x, SEND_ALL | SEND_VEC | SEND_RATE | SEND_UNRELIABLE);

and move them on the client with this data. dplay_entrate was set to 4.

this is what i get with 2 clients connected.



as you can see the kiloByte per second is at around ~10! when only 1 client was connected it was at ~5 wich makes sense.

no my dsl has only 25kb upload and my target is to get 20kb at average during gameplay maximum. so dsl players can host a game.

the problem is in the final game there should be 10 clients. so i would end up haveing 50kb upload with the current code. i probably have to lower the dplay_entrate and see how this works.
shure 60 entities moving at once is worst case but can and will happen in the game.

maybe some of you found this interesting or have suggestions how to lower the send load. this is also why i requested send_skill_to. as players only need to receive the data of the entities they currently see this would in my case mean every player only needs the data of around 30 entities at once thus cutting the upload bandwith in half!

in warcraft 3 with around 80-100 moving entities at once in a game i get like 5kb-7kb average upload as host... i wonder how they do it. i guess they use this system so they only send movement data to the clients who really need it and not to all. like we currently only can do with send_skill ):
Posted By: William

Re: multiplayer game development blog - 05/31/06 00:51

I would think in an RTS game, you'd only send the mouseclick position, and let the local pathfinding do the rest. As to the data for each little entity(health, ammo, ect.), sending to comps that only need it would indeed help. But mabye you can find a way to detract health and the such locally, and still keep it all in snyc. After all, when you delete an entity, it will delete on all comps at the same time. Just make sure when their attacking eachother you display the effects, and mabye a mock up health bar(while only doing the real health work on one comp).
Posted By: Locoweed

Re: multiplayer game development blog - 05/31/06 04:59

ent_sendnow(), something to think about. If you want to get rid of dplay_entrate(), sending every entity every blah blah percent second, consider how much you could lessen traffic by only sending when necessary with ent_sendnow(). Just a thought.

Why send an update for every entity, even when they are not moving or doing anything? Ala dplay_entrate(), when you could just send entity update when really needed? Of course, you would probably still want to send the entity updates at a rate, similar to what dplay_entrate() did, but only when an entity is actively doing something.

Yup, I really said that. Whatever it means.

Commands to consider:

Server:
entity.nosend;
ent_sendnow(entity);

Client:
send_skill();
Posted By: ulf

Re: multiplayer game development blog - 05/31/06 09:16

yep loco thats what i already do, i ONLY send updates once entities start moving. when they stand still i of course dont update. i just use send_skill from the movement part of the statemachine.

however i have currently to send all positions to all clients with send_skill because there is no send_skill_to if there where something like this i could send updates to the clients who really need it because they see this entity moving. but well maybe this will be implented in the future somehow someday far away from now in another infinite galaxy.
Posted By: ulf

Re: multiplayer game development blog - 06/01/06 16:41

01.06.06 iam currently having some problems with the inventory system. right now i can pick up entitys but if i want to drop them in the world iam having a theoretical little problem.

for the server this is no problem but for the client it is. i thought i go like this.

whenever a client wants to drop an item i do the following. i put the itemid in a skill player._want_to_drop and sent this to the server. then i send the position where he wants to drop it in the world also with skills to the server.

there i check if the player really owns this item and if yes he can drop it - create the item. but this is the problem i dont have a good idea how to do this and where. if i do it with an ent_next loop and constantly check for the player._want_to_drop skill i waste a lot of ressources i think....

but well maybe i have to do it this way if i dont have a better idea... maybe some of you have
Posted By: William

Re: multiplayer game development blog - 06/02/06 07:13

Create item on client or server.
Send skill to server.
Server send skill to clients.
Drop item on server and clients.

Now, if you want to check if the client actually has this item to drop, you could always have "iventory boxs". So, every machine knows what is in each space of inventory. This way you dont tell the server and clients "drop item x". Instead, you say "drop inventory box x". It will work if you label each peice of inventory in it's own container... and it's in the same container on each pc(you place it in the container upon creation, the container really just contains a pointer to its item/items). This is quick thinking though, so mabye it won't work(I never worked with an rpg inventory before).
Posted By: ulf

Re: multiplayer game development blog - 06/02/06 13:14

02.06.06 okay i got it working now. what i did was just sent the item wich a player wants to drop to the server. the server checks the players inventory if he really owns the item. then he drops it with ent_create.

i do this in an ent_next loop with a sleep(0.5) (thanks to firoball for this idea). so with sleep the ent_next loop doesnt take too much ressources. so whenever the skill player._want_to_drop is set to a number. the server checks if in the players inventory is this item with the number. then it ent_creates. the item on the ground where the player clicked.

this is saved in player._drop_x and player._drop_y btw.

it works really good! players can now pick up and drop items from the world. the next things i have to do is make this rough prototype better. so it works with more items. and players can change the positions of the items in the inventory. after this i will make a smaller inventory for stackable things like arrows, potions and so on.

then iam ready to make the player equipment system. so he can equip armor and so on. when thats ready i thought i will have to make the stat calculation depending on what the player is currently carrying.


two things i dont like is that you cant supply a function with an argument when using ent_create so ent_create(str_model, nullvector, function(argument)); doesnt work ):

another thing i really fear is that i will run out of entity skills pretty soon... well maybe i can optimize a bit but i really think this will be a big problem to deal with.

anyways i whish you all a nice weekend. laters!
Posted By: Locoweed

Re: multiplayer game development blog - 06/06/06 21:30

Glad to see you continuing to move along.

All of your concerns listed above can be overcome without much problem. Never under estimate the power of compacting data into a string and using send_string() or send_string_to(), decoding the info in the string when it arrives at it's destination. If used right under certain circumstances this can be a very powerful command. Send_var() and send_var_to() on the otherhand are pretty much worthless in alot of situations, especially when it comes to arrays, because you can not send a single variable of the array, it always sends the entire array. Babbling now.

I might explain more about this later if you get to a point where it is really necessary. My first ever attempt at 3DGS muliplayer, long, long ago in a galaxy before 3DGS networking really worked at all, I used this method extensively.

I can just imagine you thinking, "What the heck does this have to do with any problem I just listed in my last few post?" Answer, nothing and everything. If 3DGS doesn't supply a command you need or give you as many skills as you need, etc, you may have to think outside the box a bit in transfering multiplayer data.

Just some precognition stuff,
Loco
Posted By: ulf

Re: multiplayer game development blog - 09/26/06 07:46

hey there, iam sorry for the big delay in this thread. i had enyojed the summer and relaxed a bit. also iam busy completing our game "utz wollewutz 3d" wich will be done in some weeks with a6.

this project here is not canceled, it will be continued - however with another engine. i found out torque is much more suitable for this job. with 3dgs you will have to do so many workarounds and still not get the capabilities the torque network engine gives you right away in terms of networking.
i dont want to wait years for vital a6 multiplayer features.
dont get me wrong, a6 is a very good engine for small singleplayer games - even small multiplayer might be possible.

i dont think ill come back to a6 for the current multiplayer project. after the time of summer i came back to the forums and realised i havent missed much. looks like no interesting projects are under development right now. 1 employee also left the conitec team.

i really hope things get better soon here, its hard to switch the engine after the work you put into working with it.

maybe ill release the code of the project i already have once iam a bit ahead in developing with torque.

greetings
Posted By: PHeMoX

Re: multiplayer game development blog - 09/27/06 18:40

Quote:

i dont think ill come back to a6 for the current multiplayer project. after the time of summer i came back to the forums and realised i havent missed much. looks like no interesting projects are under development right now. 1 employee also left the conitec team.




Uuhhm, yeah, but why would an engine be bad when the activity on the forums is a little less than average and when 1 person left the conitec team? He will be replaced, so don't worry about it ... it may not be as bad as it sounds, eventhough we probably miss Marco's posts and stuff ..

Quote:

looks like no interesting projects are under development right now




You could be one of the guys making an interesting project instead, it shouldn't be a reason to quit using 3dgs in my opinion, but that's up to you. Anyways, good luck with Torgue,

Cheers
Posted By: ulf

Re: multiplayer game development blog - 09/28/06 07:42

well yesterday i bought torque and was surprised with what features it comes. shure it is not so good as a6 with shaders but i dont mind that.

iam not making a6 bad, i really like it for little games and daemonica showed that even big ones are possible!

i just see that torque saved me about 1 year of development time in terms of multiplayer capability. iam pretty shure that a6 will take some years till multiplayer is at the point where it is now with torque. but maybe it will never reach the point? who knows?
i dont want to be the one running against windmills. i want to create a game, i dont want to be the one developing engine features that should be included in an engine.

well i wish you all good luck with your projects. ill release the sourcecode wich includes simple movement, chat with teamsay and whisper and simple item management dropping soon here for you to examine.
greetings
Posted By: ulf

Re: multiplayer game development blog - 02/04/07 23:36

sorry for digging up the old thread, just in case any one is interested in the code i released it as promised now. see this thread here
http://www.coniserver.net/ubbthreads/showflat.php/Cat/0/Number/726234/an/0/page/0#Post726234
or my homepage. greetings.
© 2024 lite-C Forums