about summary refs log tree commit diff
path: root/src/engine/client/client.cpp
blob: dd7e895d90e938a27757b4ca138022c318be6256 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
#include <baselib/system.h>
#include <baselib/input.h>
#include <baselib/audio.h>
#include <baselib/stream/file.h>

#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <engine/interface.h>

#include <engine/packet.h>
#include <engine/snapshot.h>
#include "ui.h"

#include <engine/compression.h>

#include <engine/versions.h>
#include <engine/config.h>
//#include <engine/network.h>

#include <mastersrv/mastersrv.h>
#include "client.h"

using namespace baselib;

// --- input wrappers ---
static int keyboard_state[2][input::last];
static int keyboard_current = 0;
static int keyboard_first = 1;

void inp_mouse_relative(int *x, int *y) { input::mouse_position(x, y); }
int inp_key_pressed(int key) { return keyboard_state[keyboard_current][key]; }
int inp_key_was_pressed(int key) { return keyboard_state[keyboard_current^1][key]; }
int inp_key_down(int key) { return inp_key_pressed(key)&&!inp_key_was_pressed(key); }
int inp_button_pressed(int button) { return input::pressed(button); }

void inp_update()
{
	if(keyboard_first)
	{
		// make sure to reset
		keyboard_first = 0;
		inp_update();
	}
	
	keyboard_current = keyboard_current^1;
	for(int i = 0; i < input::last; i++)
		keyboard_state[keyboard_current][i] = input::pressed(i);
}

// --- input snapping ---
static int input_data[MAX_INPUT_SIZE] = {0};
static int input_data_size;
static int input_is_changed = 1;
void snap_input(void *data, int size)
{
	if(input_data_size != size || memcmp(input_data, data, size))
		input_is_changed = 1;
	mem_copy(input_data, data, size);
	input_data_size = size;
}

// -- snapshot handling ---
enum
{
	SNAP_INCOMMING=2,
	NUM_SNAPSHOT_TYPES=3,
};

static snapshot_storage snapshots_new;
static int current_tick;
static snapshot *snapshots[NUM_SNAPSHOT_TYPES];
static char snapshot_data[NUM_SNAPSHOT_TYPES][MAX_SNAPSHOT_SIZE];
static int recived_snapshots;
static int64 snapshot_start_time;
static int64 local_start_time;

float client_localtime()
{
	return (time_get()-local_start_time)/(float)(time_freq());
}

void *snap_get_item(int snapid, int index, snap_item *item)
{
	dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid");
	snapshot::item *i = snapshots[snapid]->get_item(index);
	item->type = i->type();
	item->id = i->id();
	return (void *)i->data();
}

int snap_num_items(int snapid)
{
	dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid");
	return snapshots[snapid]->num_items;
}

static void snap_init()
{
	snapshots[SNAP_INCOMMING] = (snapshot*)snapshot_data[0];
	snapshots[SNAP_CURRENT] = (snapshot*)snapshot_data[1];
	snapshots[SNAP_PREV] = (snapshot*)snapshot_data[2];
	mem_zero(snapshot_data, NUM_SNAPSHOT_TYPES*MAX_SNAPSHOT_SIZE);
	recived_snapshots = 0;
}

float client_intratick()
{
	return (time_get() - snapshot_start_time)/(float)(time_freq()/SERVER_TICK_SPEED);
}

int client_tick()
{
	return current_tick;
}

int client_tickspeed()
{
	return SERVER_TICK_SPEED;
}

void *snap_find_item(int snapid, int type, int id)
{
	// TODO: linear search. should be fixed.
	for(int i = 0; i < snapshots[snapid]->num_items; i++)
	{
		snapshot::item *itm = snapshots[snapid]->get_item(i);
		if(itm->type() == type && itm->id() == id)
			return (void *)itm->data();
	}
	return 0x0;
}


int menu_loop(); // TODO: what is this?
static float frametime = 0.0001f;

float client_frametime()
{
	return frametime;
}

static net_client net;

int client_send_msg()
{
	const msg_info *info = msg_get_info();
	NETPACKET packet;
	mem_zero(&packet, sizeof(NETPACKET));
	
	packet.client_id = 0;
	packet.data = info->data;
	packet.data_size = info->size;

	if(info->flags&MSGFLAG_VITAL)	
		packet.flags = PACKETFLAG_VITAL;
	
	net.send(&packet);
	return 0;
}

static struct 
{
	server_info infos[MAX_SERVERS];
	int64 request_times[MAX_SERVERS];
	netaddr4 addresses[MAX_SERVERS];
	int num;
} servers;
static int serverlist_lan = 1;

static netaddr4 master_server;

int client_serverbrowse_getlist(server_info **serverlist)
{
	*serverlist = servers.infos;
	return servers.num;
}

void client_serverbrowse_init()
{
	servers.num = 0;
}

void client_serverbrowse_refresh(int lan)
{
	serverlist_lan = lan;
	
	if(serverlist_lan)
	{
		dbg_msg("client", "broadcasting for servers");
		NETPACKET packet;
		packet.client_id = -1;
		packet.address.ip[0] = 0;
		packet.address.ip[1] = 0;
		packet.address.ip[2] = 0;
		packet.address.ip[3] = 0;
		packet.address.port = 8303;
		packet.flags = PACKETFLAG_CONNLESS;
		packet.data_size = sizeof(SERVERBROWSE_GETINFO);
		packet.data = SERVERBROWSE_GETINFO;
		net.send(&packet);	
		
		// reset the list
		servers.num = 0;		
	}
	else
	{
		dbg_msg("client", "requesting server list");
		NETPACKET packet;
		packet.client_id = -1;
		packet.address = master_server;
		packet.flags = PACKETFLAG_CONNLESS;
		packet.data_size = sizeof(SERVERBROWSE_GETLIST);
		packet.data = SERVERBROWSE_GETLIST;
		net.send(&packet);	
		
		// reset the list
		servers.num = 0;
	}
}

enum
{
	STATE_OFFLINE,
	STATE_CONNECTING,
	STATE_LOADING,
	STATE_ONLINE,
	STATE_BROKEN,
	STATE_QUIT,
};

static netaddr4 server_address;
static const char *server_spam_address=0;

static int state;
static int get_state() { return state; }
static void set_state(int s)
{
	dbg_msg("game", "state change. last=%d current=%d", state, s);
	state = s;
}


void client_connect(const char *server_address_str)
{
	dbg_msg("client", "connecting to '%s'", server_address_str);
	char buf[512];
	strncpy(buf, server_address_str, 512);
	
	const char *port_str = 0;
	for(int k = 0; buf[k]; k++)
	{
		if(buf[k] == ':')
		{
			port_str = &(buf[k+1]);
			buf[k] = 0;
			break;
		}
	}
	
	int port = 8303;
	if(port_str)
		port = atoi(port_str);
		
	if(net_host_lookup(buf, port, &server_address) != 0)
		dbg_msg("client", "could not find the address of %s, connecting to localhost", buf);
	
	net.connect(&server_address);
	set_state(STATE_CONNECTING);	
}

// --- client ---
// TODO: remove this class
void client::send_info()
{
	recived_snapshots = 0;

	msg_pack_start_system(NETMSG_INFO, MSGFLAG_VITAL);
	msg_pack_string(config.player_name, 128);
	msg_pack_string(config.clan_name, 128);
	msg_pack_string(config.password, 128);
	msg_pack_string("myskin", 128);
	msg_pack_end();
	client_send_msg();
}

void client::send_entergame()
{
	msg_pack_start_system(NETMSG_ENTERGAME, MSGFLAG_VITAL);
	msg_pack_end();
	client_send_msg();
}

void client::send_error(const char *error)
{
	/*
		pack(NETMSG_CLIENT_ERROR, "s", error);
	*/
	/*
	packet p(NETMSG_CLIENT_ERROR);
	p.write_str(error);
	send_packet(&p);
	//send_packet(&p);
	//send_packet(&p);
	*/
}	

void client::send_input()
{
	msg_pack_start_system(NETMSG_INPUT, 0);
	msg_pack_int(input_data_size);
	for(int i = 0; i < input_data_size/4; i++)
		msg_pack_int(input_data[i]);
	msg_pack_end();
	client_send_msg();
}

void client::disconnect()
{
	send_error("disconnected");
	net.disconnect("disconnected");
	set_state(STATE_OFFLINE);
	map_unload();
}

bool client::load_data()
{
	debug_font = gfx_load_texture("data/debug_font.png");
	return true;
}

void client::debug_render()
{
	gfx_blend_normal();
	gfx_texture_set(debug_font);
	gfx_mapscreen(0,0,gfx_screenwidth(),gfx_screenheight());
	
	static NETSTATS prev, current;
	static int64 last_snap = 0;
	if(time_get()-last_snap > time_freq()/10)
	{
		last_snap = time_get();
		prev = current;
		net.stats(&current);
	}
	
	char buffer[512];
	sprintf(buffer, "send: %8d recv: %8d",
		(current.send_bytes-prev.send_bytes)*10,
		(current.recv_bytes-prev.recv_bytes)*10);
	gfx_quads_text(10, 10, 16, buffer);
	
}

void client::render()
{
	gfx_clear(0.0f,0.0f,0.0f);
	
	// this should be moved around abit
	// TODO: clean this shit up!
	if(get_state() == STATE_ONLINE)
	{
		modc_render();
		
		// debug render stuff
		debug_render();
		
	}
	else if (get_state() != STATE_CONNECTING && get_state() != STATE_LOADING)
	{
		//netaddr4 server_address;
		int status = modmenu_render();

		if (status == -1)
			set_state(STATE_QUIT);
	}
	else if (get_state() == STATE_CONNECTING || get_state() == STATE_LOADING)
	{
		static int64 start = time_get();
		static int tee_texture;
		static int connecting_texture;
		static bool inited = false;
		
		if (!inited)
		{
			tee_texture = gfx_load_texture("data/gui_tee.png");
			connecting_texture = gfx_load_texture("data/gui/connecting.png");
				
			inited = true;
		}

		gfx_mapscreen(0,0,400.0f,300.0f);

		float t = (time_get() - start) / (double)time_freq();

		float speed = 2*sin(t);

		speed = 1.0f;

		float x = 208 + sin(t*speed) * 32;
		float w = sin(t*speed + 3.149) * 64;

		ui_do_image(tee_texture, x, 95, w, 64);
		ui_do_image(connecting_texture, 88, 150, 256, 64);
	}
}

void client::run(const char *direct_connect_server)
{
	local_start_time = time_get();
	snapshot_part = 0;
	info_request_begin = 0;
	info_request_end = 0;
	
	client_serverbrowse_init();
	
	// init graphics and sound
	if(!gfx_init())
		return;

	snd_init(); // sound is allowed to fail
	
	// load data
	if(!load_data())
		return;

	// init snapshotting
	snap_init();
	
	// init the mod
	modc_init();

	// init menu
	modmenu_init();
	
	// open socket
	net.open(0, 0);
	
	//
	net_host_lookup(config.masterserver, MASTERSERVER_PORT, &master_server);

	// connect to the server if wanted
	if(direct_connect_server)
		client_connect(direct_connect_server);
		
	//int64 inputs_per_second = 50;
	//int64 time_per_input = time_freq()/inputs_per_second;
	int64 game_starttime = time_get();
	int64 last_input = game_starttime;
	
	int64 reporttime = time_get();
	int64 reportinterval = time_freq()*1;
	int frames = 0;
	
	input::set_mouse_mode(input::mode_relative);
	
	while (1)
	{	
		frames++;
		int64 frame_start_time = time_get();

		// send input
		if(get_state() == STATE_ONLINE)
		{
			if(server_spam_address)
				disconnect();
			
			if(input_is_changed || time_get() > last_input+time_freq())
			{
				send_input();
				input_is_changed = 0;
				last_input = time_get();
			}
		}
		
		if(get_state() == STATE_OFFLINE && server_spam_address)
			client_connect(server_spam_address);
		
		// update input
		inp_update();
		
		//
		if(input::pressed(input::f1))
			input::set_mouse_mode(input::mode_absolute);
		if(input::pressed(input::f2))
			input::set_mouse_mode(input::mode_relative);

		// panic button
		if(input::pressed(input::lctrl) && input::pressed('Q'))
			break;
			
		// pump the network
		pump_network();
		
		// update the server browser
		serverbrowse_update();
		
		// render
		render();
		
		// swap the buffers
		gfx_swap();
		
		// check conditions
		if(get_state() == STATE_BROKEN || get_state() == STATE_QUIT)
			break;

		// be nice
		thread_sleep(1);
		
		if(reporttime < time_get())
		{
			dbg_msg("client/report", "fps=%.02f netstate=%d",
				frames/(float)(reportinterval/time_freq()), net.state());
			frames = 0;
			reporttime += reportinterval;
		}
		
		/*if (input::pressed(input::esc))
			if (get_state() == STATE_CONNECTING || get_state() == STATE_ONLINE)
				disconnect();*/

		// update frametime
		frametime = (time_get()-frame_start_time)/(float)time_freq();
	}
	
	modc_shutdown();
	disconnect();

	modmenu_shutdown();
	
	gfx_shutdown();
	snd_shutdown();
}

void client::error(const char *msg)
{
	dbg_msg("game", "error: %s", msg);
	send_error(msg);
	set_state(STATE_BROKEN);
}

void client::serverbrowse_request(int id)
{
	dbg_msg("client", "requesting server info from %d.%d.%d.%d:%d",
		servers.addresses[id].ip[0], servers.addresses[id].ip[1], servers.addresses[id].ip[2],
		servers.addresses[id].ip[3], servers.addresses[id].port);
	NETPACKET packet;
	packet.client_id = -1;
	packet.address = servers.addresses[id];
	packet.flags = PACKETFLAG_CONNLESS;
	packet.data_size = sizeof(SERVERBROWSE_GETINFO);
	packet.data = SERVERBROWSE_GETINFO;
	net.send(&packet);
	servers.request_times[id] = time_get();
}

void client::serverbrowse_update()
{
	int64 timeout = time_freq();
	int64 now = time_get();
	int max_requests = 10;
	
	// timeout old requests
	while(info_request_begin < servers.num && info_request_begin < info_request_end)
	{
		if(now > servers.request_times[info_request_begin]+timeout)
			info_request_begin++;
		else
			break;
	}
	
	// send new requests
	while(info_request_end < servers.num && info_request_end-info_request_begin < max_requests)
	{
		serverbrowse_request(info_request_end);
		info_request_end++;
	}
}

void client::process_packet(NETPACKET *packet)
{
	if(packet->client_id == -1)
	{
		// connectionlesss
		if(packet->data_size >= (int)sizeof(SERVERBROWSE_LIST) &&
			memcmp(packet->data, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)) == 0)
		{
			// server listing
			int size = packet->data_size-sizeof(SERVERBROWSE_LIST);
			mem_copy(servers.addresses, (char*)packet->data+sizeof(SERVERBROWSE_LIST), size);
			servers.num = size/sizeof(NETADDR4);

			info_request_begin = 0;
			info_request_end = 0;

			for(int i = 0; i < servers.num; i++)
			{
				servers.infos[i].num_players = 0;
				servers.infos[i].max_players = 0;
				servers.infos[i].latency = 999;
				sprintf(servers.infos[i].address, "%d.%d.%d.%d:%d",
					servers.addresses[i].ip[0], servers.addresses[i].ip[1], servers.addresses[i].ip[2],
					servers.addresses[i].ip[3], servers.addresses[i].port);
				sprintf(servers.infos[i].name, "%d.%d.%d.%d:%d",
					servers.addresses[i].ip[0], servers.addresses[i].ip[1], servers.addresses[i].ip[2],
					servers.addresses[i].ip[3], servers.addresses[i].port);
			}
		}

		if(packet->data_size >= (int)sizeof(SERVERBROWSE_INFO) &&
			memcmp(packet->data, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0)
		{
			// we got ze info
			data_unpacker unpacker;
			unpacker.reset((unsigned char*)packet->data+sizeof(SERVERBROWSE_INFO), packet->data_size-sizeof(SERVERBROWSE_INFO));
			
			if(serverlist_lan)
			{
				if(servers.num != MAX_SERVERS)
				{
					int i = servers.num;
					strncpy(servers.infos[i].name, unpacker.get_string(), 128);
					strncpy(servers.infos[i].map, unpacker.get_string(), 128);
					servers.infos[i].max_players = unpacker.get_int();
					servers.infos[i].num_players = unpacker.get_int();
					servers.infos[i].latency = 0;
					
					sprintf(servers.infos[i].address, "%d.%d.%d.%d:%d",
						packet->address.ip[0], packet->address.ip[1], packet->address.ip[2],
						packet->address.ip[3], packet->address.port);

					dbg_msg("client", "got server info");
					servers.num++;
					
				}
			}
			else
			{
				for(int i = 0; i < servers.num; i++)
				{
					if(net_addr4_cmp(&servers.addresses[i], &packet->address) == 0)
					{
						strncpy(servers.infos[i].name, unpacker.get_string(), 128);
						strncpy(servers.infos[i].map, unpacker.get_string(), 128);
						servers.infos[i].max_players = unpacker.get_int();
						servers.infos[i].num_players = unpacker.get_int();
						servers.infos[i].latency = ((time_get() - servers.request_times[i])*1000)/time_freq();
						dbg_msg("client", "got server info");
						break;
					}
				}
			}
		}
	}
	else
	{
		
		int sys;
		int msg = msg_unpack_start(packet->data, packet->data_size, &sys);
		if(sys)
		{
			// system message
			if(msg == NETMSG_MAP)
			{
				const char *map = msg_unpack_string();
				dbg_msg("client/network", "connection accepted, map=%s", map);
				set_state(STATE_LOADING);
				
				if(map_load(map))
				{
					modc_entergame();
					send_entergame();
					dbg_msg("client/network", "loading done");
					// now we will wait for two snapshots
					// to finish the connection
				}
				else
				{
					error("failure to load map");
				}
			}
			else if(msg == NETMSG_SNAP || msg == NETMSG_SNAPSMALL || msg == NETMSG_SNAPEMPTY)
			{
				//dbg_msg("client/network", "got snapshot");
				int game_tick = msg_unpack_int();
				int delta_tick = game_tick-msg_unpack_int();
				int num_parts = 1;
				int part = 0;
				int part_size = 0;
				
				if(msg == NETMSG_SNAP)
				{
					num_parts = msg_unpack_int();
					part = msg_unpack_int();
				}
				
				if(msg != NETMSG_SNAPEMPTY)
					part_size = msg_unpack_int();
				
				if(snapshot_part == part)
				{
					// TODO: clean this up abit
					const char *d = (const char *)msg_unpack_raw(part_size);
					mem_copy((char*)snapshots[SNAP_INCOMMING] + part*MAX_SNAPSHOT_PACKSIZE, d, part_size);
					snapshot_part++;
				
					if(snapshot_part == num_parts)
					{
						snapshot *tmp = snapshots[SNAP_PREV];
						snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT];
						snapshots[SNAP_CURRENT] = tmp;
						current_tick = game_tick;

						// decompress snapshot
						void *deltadata = snapshot_empty_delta();
						int deltasize = sizeof(int)*3;

						unsigned char tmpbuffer[MAX_SNAPSHOT_SIZE];
						unsigned char tmpbuffer2[MAX_SNAPSHOT_SIZE];
						if(part_size)
						{
							int compsize = zerobit_decompress(snapshots[SNAP_INCOMMING], part_size, tmpbuffer);
							int intsize = intpack_decompress(tmpbuffer, compsize, tmpbuffer2);
							deltadata = tmpbuffer2;
							deltasize = intsize;
						}

						// find snapshot that we should use as delta 
						static snapshot emptysnap;
						emptysnap.data_size = 0;
						emptysnap.num_items = 0;
						
						snapshot *deltashot = &emptysnap;
						int deltashot_size;

						if(delta_tick >= 0)
						{
							void *delta_data;
							deltashot_size = snapshots_new.get(delta_tick, 0, &delta_data);
							if(deltashot_size >= 0)
							{
								deltashot = (snapshot *)delta_data;
							}
							else
							{
								// TODO: handle this
								dbg_msg("client", "error, couldn't find the delta snapshot");
							}
						}

						int snapsize = snapshot_unpack_delta(deltashot, (snapshot*)snapshots[SNAP_CURRENT], deltadata, deltasize);
						//snapshot *shot = (snapshot *)snapshots[SNAP_CURRENT];

						// purge old snapshots					
						snapshots_new.purge_until(delta_tick);
						snapshots_new.purge_until(game_tick-50); // TODO: change this to server tickrate
						
						// add new
						snapshots_new.add(game_tick, time_get(), snapsize, snapshots[SNAP_CURRENT]);
						
						// apply snapshot, cycle pointers
						recived_snapshots++;
						snapshot_start_time = time_get();
						
						// we got two snapshots until we see us self as connected
						if(recived_snapshots == 2)
						{
							local_start_time = time_get();
							set_state(STATE_ONLINE);
						}
						
						if(recived_snapshots > 2)
							modc_newsnapshot();
						
						snapshot_part = 0;
						
						// ack snapshot
						msg_pack_start_system(NETMSG_SNAPACK, 0);
						msg_pack_int(game_tick);
						msg_pack_end();
						client_send_msg();
					}
				}
				else
				{
					dbg_msg("client", "snapshot reset!");
					snapshot_part = 0;
				}
			}
		}
		else
		{
			// game message
			modc_message(msg);
		}
	}
}

void client::pump_network()
{
	net.update();

	// check for errors		
	if(get_state() != STATE_OFFLINE && net.state() == NETSTATE_OFFLINE)
	{
		// TODO: add message to the user there
		set_state(STATE_OFFLINE);
	}

	//
	if(get_state() == STATE_CONNECTING && net.state() == NETSTATE_ONLINE)
	{
		// we switched to online
		dbg_msg("client", "connected, sending info");
		set_state(STATE_LOADING);
		send_info();
	}
	
	// process packets
	NETPACKET packet;
	while(net.recv(&packet))
		process_packet(&packet);
}

int editor_main(int argc, char **argv);

client main_client;

int main(int argc, char **argv)
{
	dbg_msg("client", "starting...");
	
	config_reset();
	config_load("default.cfg");

	const char *direct_connect_server = 0x0;
	snd_set_master_volume(config.volume / 255.0f);
	bool editor = false;

	// init network, need to be done first so we can do lookups
	net_init();

	// parse arguments
	for(int i = 1; i < argc; i++)
	{
		if(argv[i][0] == '-' && argv[i][1] == 'c' && argv[i][2] == 0 && argc - i > 1)
		{
			// -c SERVER:PORT
			i++;
			direct_connect_server = argv[i];
		}
		else if(argv[i][0] == '-' && argv[i][1] == 's' && argv[i][2] == 0 && argc - i > 1)
		{
			// -s SERVER:PORT
			i++;
			server_spam_address = argv[i];
		}
		else if(argv[i][0] == '-' && argv[i][1] == 'n' && argv[i][2] == 0 && argc - i > 1)
		{
			// -n NAME
			i++;
			config_set_player_name(&config, argv[i]);
		}
		else if(argv[i][0] == '-' && argv[i][1] == 'w' && argv[i][2] == 0)
		{
			// -w
			config.fullscreen = 0;
		}
		
		else if(argv[i][0] == '-' && argv[i][1] == 'e' && argv[i][2] == 0)
		{
			editor = true;
		}
	}
	
	if(editor)
		editor_main(argc, argv);
	else
	{
		// start the client
		main_client.run(direct_connect_server);
	}
	return 0;
}