From 878ede3080ab2cfb627aca505c397d9765052996 Mon Sep 17 00:00:00 2001 From: Magnus Auvinen Date: Tue, 27 Oct 2009 14:38:53 +0000 Subject: major update with stuff --- src/engine/client/ec_client.c | 2061 ----------------------------------- src/engine/client/ec_client.cpp | 2087 ++++++++++++++++++++++++++++++++++++ src/engine/client/ec_gfx.c | 1020 ------------------ src/engine/client/ec_gfx.cpp | 992 +++++++++++++++++ src/engine/client/ec_gfx_text.c | 668 ------------ src/engine/client/ec_gfx_text.cpp | 669 ++++++++++++ src/engine/client/ec_inp.c | 230 ---- src/engine/client/ec_inp.cpp | 234 ++++ src/engine/client/ec_snd.c | 465 -------- src/engine/client/ec_snd.cpp | 471 ++++++++ src/engine/client/ec_srvbrowse.c | 737 ------------- src/engine/client/ec_srvbrowse.cpp | 736 +++++++++++++ 12 files changed, 5189 insertions(+), 5181 deletions(-) delete mode 100644 src/engine/client/ec_client.c create mode 100644 src/engine/client/ec_client.cpp delete mode 100644 src/engine/client/ec_gfx.c create mode 100644 src/engine/client/ec_gfx.cpp delete mode 100644 src/engine/client/ec_gfx_text.c create mode 100644 src/engine/client/ec_gfx_text.cpp delete mode 100644 src/engine/client/ec_inp.c create mode 100644 src/engine/client/ec_inp.cpp delete mode 100644 src/engine/client/ec_snd.c create mode 100644 src/engine/client/ec_snd.cpp delete mode 100644 src/engine/client/ec_srvbrowse.c create mode 100644 src/engine/client/ec_srvbrowse.cpp (limited to 'src/engine/client') diff --git a/src/engine/client/ec_client.c b/src/engine/client/ec_client.c deleted file mode 100644 index 0c707ccf..00000000 --- a/src/engine/client/ec_client.c +++ /dev/null @@ -1,2061 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include - -const int prediction_margin = 1000/50/2; /* magic network prediction value */ - -/* - Server Time - Client Mirror Time - Client Predicted Time - - Snapshot Latency - Downstream latency - - Prediction Latency - Upstream latency -*/ - -/* network client, must be accessible from other parts like the server browser */ -NETCLIENT *net; - -/* TODO: ugly, fix me */ -extern void client_serverbrowse_set(NETADDR *addr, int request, int token, SERVER_INFO *info); -extern void client_serverbrowse_save(); - -static unsigned snapshot_parts; -static int64 local_start_time; - -static int debug_font; -static float frametime = 0.0001f; -static float frametime_low = 1.0f; -static float frametime_high = 0.0f; -static int frames = 0; -static NETADDR server_address; -static int window_must_refocus = 0; -static int snapcrcerrors = 0; - -static int ack_game_tick = -1; -static int current_recv_tick = 0; -static int rcon_authed = 0; - -/* version-checking */ -static char versionstr[10] = "0"; - -/* pinging */ -static int64 ping_start_time = 0; - -/* */ -static char current_map[256] = {0}; -static int current_map_crc = 0; - -/* */ -static char cmd_connect[256] = {0}; - -/* map download */ -static char mapdownload_filename[256] = {0}; -static char mapdownload_name[256] = {0}; -static IOHANDLE mapdownload_file = 0; -static int mapdownload_chunk = 0; -static int mapdownload_crc = 0; -static int mapdownload_amount = -1; -static int mapdownload_totalsize = -1; - -/* */ -static SERVER_INFO current_server_info = {0}; -static int64 current_server_info_requesttime = -1; /* >= 0 should request, == -1 got info */ - -/* current time */ -static int current_tick = 0; -static float intratick = 0; -static float ticktime = 0; -static int prev_tick = 0; - -/* */ -/*static int predictiontime_pingspikecounter = 0; -static int gametime_pingspikecounter = 0;*/ - -/* predicted time */ -static int current_predtick = 0; -static float predintratick = 0; -static int last_input_timeleft = 0; - -static struct /* TODO: handle input better */ -{ - int data[MAX_INPUT_SIZE]; /* the input data */ - int tick; /* the tick that the input is for */ - int64 predicted_time; /* prediction latency when we sent this input */ - int64 time; -} inputs[200]; -static int current_input = 0; - -enum -{ - GRAPH_MAX=128 -}; - -typedef struct -{ - float min, max; - float values[GRAPH_MAX]; - float colors[GRAPH_MAX][3]; - int index; -} GRAPH; - -static void graph_init(GRAPH *g, float min, float max) -{ - g->min = min; - g->max = max; - g->index = 0; -} - -static void graph_scale_max(GRAPH *g) -{ - int i = 0; - g->max = 0; - for(i = 0; i < GRAPH_MAX; i++) - { - if(g->values[i] > g->max) - g->max = g->values[i]; - } -} - -static void graph_scale_min(GRAPH *g) -{ - int i = 0; - g->min = g->max; - for(i = 0; i < GRAPH_MAX; i++) - { - if(g->values[i] < g->min) - g->min = g->values[i]; - } -} - -static void graph_add(GRAPH *graph, float v, float r, float g, float b) -{ - graph->index = (graph->index+1)&(GRAPH_MAX-1); - graph->values[graph->index] = v; - graph->colors[graph->index][0] = r; - graph->colors[graph->index][1] = g; - graph->colors[graph->index][2] = b; -} - -static void graph_render(GRAPH *g, float x, float y, float w, float h, const char *description) -{ - char buf[32]; - int i; - - gfx_blend_normal(); - - - gfx_texture_set(-1); - - gfx_quads_begin(); - gfx_setcolor(0, 0, 0, 0.75f); - gfx_quads_drawTL(x, y, w, h); - gfx_quads_end(); - - gfx_lines_begin(); - gfx_setcolor(0.95f, 0.95f, 0.95f, 1.00f); - gfx_lines_draw(x, y+h/2, x+w, y+h/2); - gfx_setcolor(0.5f, 0.5f, 0.5f, 0.75f); - gfx_lines_draw(x, y+(h*3)/4, x+w, y+(h*3)/4); - gfx_lines_draw(x, y+h/4, x+w, y+h/4); - for(i = 1; i < GRAPH_MAX; i++) - { - float a0 = (i-1)/(float)GRAPH_MAX; - float a1 = i/(float)GRAPH_MAX; - int i0 = (g->index+i-1)&(GRAPH_MAX-1); - int i1 = (g->index+i)&(GRAPH_MAX-1); - - float v0 = (g->values[i0]-g->min) / (g->max-g->min); - float v1 = (g->values[i1]-g->min) / (g->max-g->min); - - gfx_setcolorvertex(0, g->colors[i0][0], g->colors[i0][1], g->colors[i0][2], 0.75f); - gfx_setcolorvertex(1, g->colors[i1][0], g->colors[i1][1], g->colors[i1][2], 0.75f); - gfx_lines_draw(x+a0*w, y+h-v0*h, x+a1*w, y+h-v1*h); - - } - gfx_lines_end(); - - - gfx_texture_set(debug_font); - gfx_quads_text(x+2, y+h-16, 16, 1,1,1,1, description); - - str_format(buf, sizeof(buf), "%.2f", g->max); - gfx_quads_text(x+w-8*strlen(buf)-8, y+2, 16, 1,1,1,1, buf); - - str_format(buf, sizeof(buf), "%.2f", g->min); - gfx_quads_text(x+w-8*strlen(buf)-8, y+h-16, 16, 1,1,1,1, buf); - -} - -typedef struct -{ - int64 snap; - int64 current; - int64 target; - - int64 rlast; - int64 tlast; - GRAPH graph; - - int spikecounter; - - float adjustspeed[2]; /* 0 = down, 1 = up */ -} SMOOTHTIME; - -static void st_init(SMOOTHTIME *st, int64 target) -{ - st->snap = time_get(); - st->current = target; - st->target = target; - st->adjustspeed[0] = 0.3f; - st->adjustspeed[1] = 0.3f; - graph_init(&st->graph, 0.0f, 0.5f); -} - -static int64 st_get(SMOOTHTIME *st, int64 now) -{ - float adjust_speed, a; - int64 c = st->current + (now - st->snap); - int64 t = st->target + (now - st->snap); - int64 r; - - /* it's faster to adjust upward instead of downward */ - /* we might need to adjust these abit */ - - adjust_speed = st->adjustspeed[0]; - if(t > c) - adjust_speed = st->adjustspeed[1]; - - a = ((now-st->snap)/(float)time_freq()) * adjust_speed; - if(a > 1.0f) - a = 1.0f; - - r = c + (int64)((t-c)*a); - - graph_add(&st->graph, a+0.5f,1,1,1); - - return r; -} - -static void st_update_int(SMOOTHTIME *st, int64 target) -{ - int64 now = time_get(); - st->current = st_get(st, now); - st->snap = now; - st->target = target; -} - -static void st_update(SMOOTHTIME *st, GRAPH *graph, int64 target, int time_left, int adjust_direction) -{ - int update_timer = 1; - - if(time_left < 0) - { - int is_spike = 0; - if(time_left < -50) - { - is_spike = 1; - - st->spikecounter += 5; - if(st->spikecounter > 50) - st->spikecounter = 50; - } - - if(is_spike && st->spikecounter < 15) - { - /* ignore this ping spike */ - update_timer = 0; - graph_add(graph, time_left, 1,1,0); - } - else - { - graph_add(graph, time_left, 1,0,0); - if(st->adjustspeed[adjust_direction] < 30.0f) - st->adjustspeed[adjust_direction] *= 2.0f; - } - } - else - { - if(st->spikecounter) - st->spikecounter--; - - graph_add(graph, time_left, 0,1,0); - - st->adjustspeed[adjust_direction] *= 0.95f; - if(st->adjustspeed[adjust_direction] < 2.0f) - st->adjustspeed[adjust_direction] = 2.0f; - } - - last_input_timeleft = time_left; - - if(update_timer) - st_update_int(st, target); -} - -static SMOOTHTIME game_time; -static SMOOTHTIME predicted_time; - -/* graphs */ -static GRAPH inputtime_margin_graph; -static GRAPH gametime_margin_graph; -static GRAPH fps_graph; - -/* -- snapshot handling --- */ -enum -{ - NUM_SNAPSHOT_TYPES=2 -}; - -/* the game snapshots are modifiable by the game */ -SNAPSTORAGE snapshot_storage; -static SNAPSTORAGE_HOLDER *snapshots[NUM_SNAPSHOT_TYPES] = {0, 0}; - -static int recived_snapshots = 0; -static char snapshot_incomming_data[MAX_SNAPSHOT_SIZE]; - -static SNAPSTORAGE_HOLDER demorec_snapshotholders[NUM_SNAPSHOT_TYPES]; -static char *demorec_snapshotdata[NUM_SNAPSHOT_TYPES][2][MAX_SNAPSHOT_SIZE]; - -/* --- */ - -void *snap_get_item(int snapid, int index, SNAP_ITEM *item) -{ - SNAPSHOT_ITEM *i; - dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); - i = snapshot_get_item(snapshots[snapid]->alt_snap, index); - item->datasize = snapshot_get_item_datasize(snapshots[snapid]->alt_snap, index); - item->type = snapitem_type(i); - item->id = snapitem_id(i); - return (void *)snapitem_data(i); -} - -void snap_invalidate_item(int snapid, int index) -{ - SNAPSHOT_ITEM *i; - dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); - i = snapshot_get_item(snapshots[snapid]->alt_snap, index); - if(i) - { - if((char *)i < (char *)snapshots[snapid]->alt_snap || (char *)i > (char *)snapshots[snapid]->alt_snap + snapshots[snapid]->snap_size) - dbg_msg("ASDFASDFASdf", "ASDFASDFASDF"); - if((char *)i >= (char *)snapshots[snapid]->snap && (char *)i < (char *)snapshots[snapid]->snap + snapshots[snapid]->snap_size) - dbg_msg("ASDFASDFASdf", "ASDFASDFASDF"); - i->type_and_id = -1; - } -} - -void *snap_find_item(int snapid, int type, int id) -{ - /* TODO: linear search. should be fixed. */ - int i; - - if(!snapshots[snapid]) - return 0x0; - - for(i = 0; i < snapshots[snapid]->snap->num_items; i++) - { - SNAPSHOT_ITEM *itm = snapshot_get_item(snapshots[snapid]->alt_snap, i); - if(snapitem_type(itm) == type && snapitem_id(itm) == id) - return (void *)snapitem_data(itm); - } - return 0x0; -} - -int snap_num_items(int snapid) -{ - dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); - if(!snapshots[snapid]) - return 0; - return snapshots[snapid]->snap->num_items; -} - -/* ------ time functions ------ */ -float client_intratick() { return intratick; } -float client_predintratick() { return predintratick; } -float client_ticktime() { return ticktime; } -int client_tick() { return current_tick; } -int client_prevtick() { return prev_tick; } -int client_predtick() { return current_predtick; } -int client_tickspeed() { return SERVER_TICK_SPEED; } -float client_frametime() { return frametime; } -float client_localtime() { return (time_get()-local_start_time)/(float)(time_freq()); } - -/* ----- send functions ----- */ -int client_send_msg() -{ - const MSG_INFO *info = msg_get_info(); - NETCHUNK packet; - - if(!info) - return -1; - - if(client_state() == CLIENTSTATE_OFFLINE) - return 0; - - - mem_zero(&packet, sizeof(NETCHUNK)); - - packet.client_id = 0; - packet.data = info->data; - packet.data_size = info->size; - - if(info->flags&MSGFLAG_VITAL) - packet.flags |= NETSENDFLAG_VITAL; - if(info->flags&MSGFLAG_FLUSH) - packet.flags |= NETSENDFLAG_FLUSH; - - if(info->flags&MSGFLAG_RECORD) - { - if(demorec_isrecording()) - demorec_record_message(packet.data, packet.data_size); - } - - if(!(info->flags&MSGFLAG_NOSEND)) - netclient_send(net, &packet); - return 0; -} - -static void client_send_info() -{ - msg_pack_start_system(NETMSG_INFO, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_string(modc_net_version(), 128); - msg_pack_string(config.player_name, 128); - msg_pack_string(config.clan_name, 128); - msg_pack_string(config.password, 128); - msg_pack_end(); - client_send_msg(); -} - - -static void client_send_entergame() -{ - msg_pack_start_system(NETMSG_ENTERGAME, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_end(); - client_send_msg(); -} - -static void client_send_ready() -{ - msg_pack_start_system(NETMSG_READY, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_end(); - client_send_msg(); -} - -int client_rcon_authed() -{ - return rcon_authed; -} - -void client_rcon_auth(const char *name, const char *password) -{ - msg_pack_start_system(NETMSG_RCON_AUTH, MSGFLAG_VITAL); - msg_pack_string(name, 32); - msg_pack_string(password, 32); - msg_pack_end(); - client_send_msg(); -} - -void client_rcon(const char *cmd) -{ - msg_pack_start_system(NETMSG_RCON_CMD, MSGFLAG_VITAL); - msg_pack_string(cmd, 256); - msg_pack_end(); - client_send_msg(); -} - -int client_connection_problems() -{ - return netclient_gotproblems(net); -} - -void client_direct_input(int *input, int size) -{ - int i; - msg_pack_start_system(NETMSG_INPUT, 0); - msg_pack_int(ack_game_tick); - msg_pack_int(current_predtick); - msg_pack_int(size); - - for(i = 0; i < size/4; i++) - msg_pack_int(input[i]); - - msg_pack_end(); - client_send_msg(); -} - - -static void client_send_input() -{ - int64 now = time_get(); - int i, size; - - if(current_predtick <= 0) - return; - - /* fetch input */ - size = modc_snap_input(inputs[current_input].data); - - if(!size) - return; - - /* pack input */ - msg_pack_start_system(NETMSG_INPUT, MSGFLAG_FLUSH); - msg_pack_int(ack_game_tick); - msg_pack_int(current_predtick); - msg_pack_int(size); - - inputs[current_input].tick = current_predtick; - inputs[current_input].predicted_time = st_get(&predicted_time, now); - inputs[current_input].time = now; - - /* pack it */ - for(i = 0; i < size/4; i++) - msg_pack_int(inputs[current_input].data[i]); - - current_input++; - current_input%=200; - - msg_pack_end(); - client_send_msg(); -} - -const char *client_latestversion() -{ - return versionstr; -} - -/* TODO: OPT: do this alot smarter! */ -int *client_get_input(int tick) -{ - int i; - int best = -1; - for(i = 0; i < 200; i++) - { - if(inputs[i].tick <= tick && (best == -1 || inputs[best].tick < inputs[i].tick)) - best = i; - } - - if(best != -1) - return (int *)inputs[best].data; - return 0; -} - -/* ------ state handling ----- */ -static int state = CLIENTSTATE_OFFLINE; -int client_state() { return state; } -static void client_set_state(int s) -{ - int old = state; - if(config.debug) - dbg_msg("client", "state change. last=%d current=%d", state, s); - state = s; - if(old != s) - modc_statechange(state, old); -} - -/* called when the map is loaded and we should init for a new round */ -static void client_on_enter_game() -{ - /* reset input */ - int i; - for(i = 0; i < 200; i++) - inputs[i].tick = -1; - current_input = 0; - - /* reset snapshots */ - snapshots[SNAP_CURRENT] = 0; - snapshots[SNAP_PREV] = 0; - snapstorage_purge_all(&snapshot_storage); - recived_snapshots = 0; - snapshot_parts = 0; - current_predtick = 0; - current_recv_tick = 0; -} - -void client_entergame() -{ - if(state == CLIENTSTATE_DEMOPLAYBACK) - return; - - /* now we will wait for two snapshots */ - /* to finish the connection */ - client_send_entergame(); - client_on_enter_game(); -} - -void client_connect(const char *server_address_str) -{ - char buf[512]; - const char *port_str = 0; - int k; - int port = 8303; - - client_disconnect(); - - dbg_msg("client", "connecting to '%s'", server_address_str); - - //client_serverinfo_request(); - str_copy(buf, server_address_str, sizeof(buf)); - - for(k = 0; buf[k]; k++) - { - if(buf[k] == ':') - { - port_str = &(buf[k+1]); - buf[k] = 0; - break; - } - } - - if(port_str) - port = atoi(port_str); - - /* TODO: IPv6 support */ - if(net_host_lookup(buf, &server_address, NETTYPE_IPV4) != 0) - dbg_msg("client", "could not find the address of %s, connecting to localhost", buf); - - rcon_authed = 0; - server_address.port = port; - netclient_connect(net, &server_address); - client_set_state(CLIENTSTATE_CONNECTING); - - graph_init(&inputtime_margin_graph, -150.0f, 150.0f); - graph_init(&gametime_margin_graph, -150.0f, 150.0f); -} - -void client_disconnect_with_reason(const char *reason) -{ - /* stop demo playback */ - demorec_playback_stop(); - - /* */ - rcon_authed = 0; - netclient_disconnect(net, reason); - client_set_state(CLIENTSTATE_OFFLINE); - map_unload(); - - /* disable all downloads */ - mapdownload_chunk = 0; - if(mapdownload_file) - io_close(mapdownload_file); - mapdownload_file = 0; - mapdownload_crc = 0; - mapdownload_totalsize = -1; - mapdownload_amount = 0; - - /* clear the current server info */ - mem_zero(¤t_server_info, sizeof(current_server_info)); - mem_zero(&server_address, sizeof(server_address)); - - /* clear snapshots */ - snapshots[SNAP_CURRENT] = 0; - snapshots[SNAP_PREV] = 0; - recived_snapshots = 0; -} - -void client_disconnect() -{ - client_disconnect_with_reason(0); -} - - -void client_serverinfo(SERVER_INFO *serverinfo) -{ - mem_copy(serverinfo, ¤t_server_info, sizeof(current_server_info)); -} - -void client_serverinfo_request() -{ - mem_zero(¤t_server_info, sizeof(current_server_info)); - current_server_info_requesttime = 0; -} - -static int client_load_data() -{ - debug_font = gfx_load_texture("debug_font.png", IMG_AUTO, TEXLOAD_NORESAMPLE); - return 1; -} - -extern int snapshot_data_rate[0xffff]; -extern int snapshot_data_updates[0xffff]; - -const char *modc_getitemname(int type); - -static void client_debug_render() -{ - static NETSTATS prev, current; - static int64 last_snap = 0; - static float frametime_avg = 0; - int64 now = time_get(); - char buffer[512]; - - if(!config.debug) - return; - - gfx_blend_normal(); - gfx_texture_set(debug_font); - gfx_mapscreen(0,0,gfx_screenwidth(),gfx_screenheight()); - - if(time_get()-last_snap > time_freq()) - { - last_snap = time_get(); - prev = current; - net_stats(¤t); - } - - /* - eth = 14 - ip = 20 - udp = 8 - total = 42 - */ - frametime_avg = frametime_avg*0.9f + frametime*0.1f; - str_format(buffer, sizeof(buffer), "ticks: %8d %8d mem %dk %d gfxmem: %dk fps: %3d", - current_tick, current_predtick, - mem_stats()->allocated/1024, - mem_stats()->total_allocations, - gfx_memory_usage()/1024, - (int)(1.0f/frametime_avg)); - gfx_quads_text(2, 2, 16, 1,1,1,1, buffer); - - - { - int send_packets = (current.sent_packets-prev.sent_packets); - int send_bytes = (current.sent_bytes-prev.sent_bytes); - int send_total = send_bytes + send_packets*42; - int recv_packets = (current.recv_packets-prev.recv_packets); - int recv_bytes = (current.recv_bytes-prev.recv_bytes); - int recv_total = recv_bytes + recv_packets*42; - - if(!send_packets) send_packets++; - if(!recv_packets) recv_packets++; - str_format(buffer, sizeof(buffer), "send: %3d %5d+%4d=%5d (%3d kbps) avg: %5d\nrecv: %3d %5d+%4d=%5d (%3d kbps) avg: %5d", - send_packets, send_bytes, send_packets*42, send_total, (send_total*8)/1024, send_bytes/send_packets, - recv_packets, recv_bytes, recv_packets*42, recv_total, (recv_total*8)/1024, recv_bytes/recv_packets); - gfx_quads_text(2, 14, 16, 1,1,1,1, buffer); - } - - /* render rates */ - { - int y = 0; - int i; - for(i = 0; i < 256; i++) - { - if(snapshot_data_rate[i]) - { - str_format(buffer, sizeof(buffer), "%4d %20s: %8d %8d %8d", i, modc_getitemname(i), snapshot_data_rate[i]/8, snapshot_data_updates[i], - (snapshot_data_rate[i]/snapshot_data_updates[i])/8); - gfx_quads_text(2, 100+y*12, 16, 1,1,1,1, buffer); - y++; - } - } - } - - str_format(buffer, sizeof(buffer), "pred: %d ms %3.2f", - (int)((st_get(&predicted_time, now)-st_get(&game_time, now))*1000/(float)time_freq()), - predicted_time.adjustspeed[1]); - gfx_quads_text(2, 70, 16, 1,1,1,1, buffer); - - /* render graphs */ - if(config.dbg_graphs) - { - //gfx_mapscreen(0,0,400.0f,300.0f); - float w = gfx_screenwidth()/4.0f; - float h = gfx_screenheight()/6.0f; - float sp = gfx_screenwidth()/100.0f; - float x = gfx_screenwidth()-w-sp; - - graph_scale_max(&fps_graph); - graph_scale_min(&fps_graph); - graph_render(&fps_graph, x, sp*5, w, h, "FPS"); - graph_render(&inputtime_margin_graph, x, sp*5+h+sp, w, h, "Prediction Margin"); - graph_render(&gametime_margin_graph, x, sp*5+h+sp+h+sp, w, h, "Gametime Margin"); - } -} - -void client_quit() -{ - client_set_state(CLIENTSTATE_QUITING); -} - -const char *client_error_string() -{ - return netclient_error_string(net); -} - -static void client_render() -{ - if(config.gfx_clear) - gfx_clear(1,1,0); - - modc_render(); - client_debug_render(); -} - -static const char *client_load_map(const char *name, const char *filename, int wanted_crc) -{ - static char errormsg[128]; - DATAFILE *df; - int crc; - - client_set_state(CLIENTSTATE_LOADING); - - df = datafile_load(filename); - if(!df) - { - str_format(errormsg, sizeof(errormsg), "map '%s' not found", filename); - return errormsg; - } - - /* get the crc of the map */ - crc = datafile_crc(filename); - if(crc != wanted_crc) - { - datafile_unload(df); - str_format(errormsg, sizeof(errormsg), "map differs from the server. %08x != %08x", crc, wanted_crc); - return errormsg; - } - - // stop demo recording if we loaded a new map - demorec_record_stop(); - - dbg_msg("client", "loaded map '%s'", filename); - recived_snapshots = 0; - map_set(df); - - str_copy(current_map, name, sizeof(current_map)); - current_map_crc = crc; - - return NULL; -} - -static const char *client_load_map_search(const char *mapname, int wanted_crc) -{ - const char *error = 0; - char buf[512]; - dbg_msg("client", "loading map, map=%s wanted crc=%08x", mapname, wanted_crc); - client_set_state(CLIENTSTATE_LOADING); - - /* try the normal maps folder */ - str_format(buf, sizeof(buf), "maps/%s.map", mapname); - error = client_load_map(mapname, buf, wanted_crc); - if(!error) - return error; - - /* try the downloaded maps */ - str_format(buf, sizeof(buf), "downloadedmaps/%s_%8x.map", mapname, wanted_crc); - error = client_load_map(mapname, buf, wanted_crc); - return error; -} - -static int player_score_comp(const void *a, const void *b) -{ - SERVER_INFO_PLAYER *p0 = (SERVER_INFO_PLAYER *)a; - SERVER_INFO_PLAYER *p1 = (SERVER_INFO_PLAYER *)b; - if(p0->score == p1->score) - return 0; - if(p0->score < p1->score) - return 1; - return -1; -} - -static void client_process_packet(NETCHUNK *packet) -{ - if(packet->client_id == -1) - { - /* connectionlesss */ - if(packet->data_size == (int)(sizeof(VERSIONSRV_VERSION) + sizeof(VERSION_DATA)) && - memcmp(packet->data, VERSIONSRV_VERSION, sizeof(VERSIONSRV_VERSION)) == 0) - { - unsigned char *versiondata = (unsigned char*) packet->data + sizeof(VERSIONSRV_VERSION); - int version_match = !memcmp(versiondata, VERSION_DATA, sizeof(VERSION_DATA)); - - dbg_msg("client/version", "version does %s (%d.%d.%d)", - version_match ? "match" : "NOT match", - versiondata[1], versiondata[2], versiondata[3]); - - /* assume version is out of date when version-data doesn't match */ - if (!version_match) - { - sprintf(versionstr, "%d.%d.%d", versiondata[1], versiondata[2], versiondata[3]); - } - } - - if(packet->data_size >= (int)sizeof(SERVERBROWSE_LIST) && - memcmp(packet->data, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)) == 0) - { - int size = packet->data_size-sizeof(SERVERBROWSE_LIST); - int num = size/sizeof(MASTERSRV_ADDR); - MASTERSRV_ADDR *addrs = (MASTERSRV_ADDR *)((char*)packet->data+sizeof(SERVERBROWSE_LIST)); - int i; - - for(i = 0; i < num; i++) - { - NETADDR addr; - - /* convert address */ - mem_zero(&addr, sizeof(addr)); - addr.type = NETTYPE_IPV4; - addr.ip[0] = addrs[i].ip[0]; - addr.ip[1] = addrs[i].ip[1]; - addr.ip[2] = addrs[i].ip[2]; - addr.ip[3] = addrs[i].ip[3]; - addr.port = (addrs[i].port[1]<<8) | addrs[i].port[0]; - - client_serverbrowse_set(&addr, BROWSESET_MASTER_ADD, -1, NULL); - } - } - - { - int packet_type = 0; - if(packet->data_size >= (int)sizeof(SERVERBROWSE_INFO) && memcmp(packet->data, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0) - packet_type = 2; - - if(packet->data_size >= (int)sizeof(SERVERBROWSE_OLD_INFO) && memcmp(packet->data, SERVERBROWSE_OLD_INFO, sizeof(SERVERBROWSE_OLD_INFO)) == 0) - packet_type = 1; - - if(packet_type) - { - /* we got ze info */ - UNPACKER up; - SERVER_INFO info = {0}; - int i; - int token = -1; - - unpacker_reset(&up, (unsigned char*)packet->data+sizeof(SERVERBROWSE_INFO), packet->data_size-sizeof(SERVERBROWSE_INFO)); - if(packet_type >= 2) - token = atol(unpacker_get_string(&up)); - str_copy(info.version, unpacker_get_string(&up), sizeof(info.version)); - str_copy(info.name, unpacker_get_string(&up), sizeof(info.name)); - str_copy(info.map, unpacker_get_string(&up), sizeof(info.map)); - str_copy(info.gametype, unpacker_get_string(&up), sizeof(info.gametype)); - info.flags = atol(unpacker_get_string(&up)); - info.progression = atol(unpacker_get_string(&up)); - info.num_players = atol(unpacker_get_string(&up)); - info.max_players = atol(unpacker_get_string(&up)); - str_format(info.address, sizeof(info.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); - - for(i = 0; i < info.num_players; i++) - { - str_copy(info.players[i].name, unpacker_get_string(&up), sizeof(info.players[i].name)); - info.players[i].score = atol(unpacker_get_string(&up)); - } - - if(!up.error) - { - /* sort players */ - qsort(info.players, info.num_players, sizeof(*info.players), player_score_comp); - - if(net_addr_comp(&server_address, &packet->address) == 0) - { - mem_copy(¤t_server_info, &info, sizeof(current_server_info)); - current_server_info.netaddr = server_address; - current_server_info_requesttime = -1; - } - else - { - if(packet_type == 2) - client_serverbrowse_set(&packet->address, BROWSESET_TOKEN, token, &info); - else - client_serverbrowse_set(&packet->address, BROWSESET_OLD_INTERNET, -1, &info); - } - } - } - } - } - else - { - int sys; - int msg = msg_unpack_start(packet->data, packet->data_size, &sys); - - if(sys) - { - /* system message */ - if(msg == NETMSG_MAP_CHANGE) - { - const char *map = msg_unpack_string(); - int map_crc = msg_unpack_int(); - const char *error = 0; - int i; - - if(msg_unpack_error()) - return; - - for(i = 0; map[i]; i++) /* protect the player from nasty map names */ - { - if(map[i] == '/' || map[i] == '\\') - error = "strange character in map name"; - } - - if(error) - client_disconnect_with_reason(error); - else - { - error = client_load_map_search(map, map_crc); - - if(!error) - { - dbg_msg("client/network", "loading done"); - client_send_ready(); - modc_connected(); - } - else - { - str_format(mapdownload_filename, sizeof(mapdownload_filename), "downloadedmaps/%s_%08x.map", map, map_crc); - - dbg_msg("client/network", "starting to download map to '%s'", mapdownload_filename); - - mapdownload_chunk = 0; - str_copy(mapdownload_name, map, sizeof(mapdownload_name)); - mapdownload_file = engine_openfile(mapdownload_filename, IOFLAG_WRITE); - mapdownload_crc = map_crc; - mapdownload_totalsize = -1; - mapdownload_amount = 0; - - msg_pack_start_system(NETMSG_REQUEST_MAP_DATA, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_int(mapdownload_chunk); - msg_pack_end(); - client_send_msg(); - - if(config.debug) - dbg_msg("client/network", "requested chunk %d", mapdownload_chunk); - } - } - } - else if(msg == NETMSG_MAP_DATA) - { - int last = msg_unpack_int(); - int total_size = msg_unpack_int(); - int size = msg_unpack_int(); - const unsigned char *data = msg_unpack_raw(size); - - /* check fior errors */ - if(msg_unpack_error() || size <= 0 || total_size <= 0 || !mapdownload_file) - return; - - io_write(mapdownload_file, data, size); - - mapdownload_totalsize = total_size; - mapdownload_amount += size; - - if(last) - { - const char *error; - dbg_msg("client/network", "download complete, loading map"); - - io_close(mapdownload_file); - mapdownload_file = 0; - mapdownload_amount = 0; - mapdownload_totalsize = -1; - - /* load map */ - error = client_load_map(mapdownload_name, mapdownload_filename, mapdownload_crc); - if(!error) - { - dbg_msg("client/network", "loading done"); - client_send_ready(); - modc_connected(); - } - else - client_disconnect_with_reason(error); - } - else - { - /* request new chunk */ - mapdownload_chunk++; - msg_pack_start_system(NETMSG_REQUEST_MAP_DATA, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_int(mapdownload_chunk); - msg_pack_end(); - client_send_msg(); - - if(config.debug) - dbg_msg("client/network", "requested chunk %d", mapdownload_chunk); - } - } - else if(msg == NETMSG_PING) - { - msg_pack_start_system(NETMSG_PING_REPLY, 0); - msg_pack_end(); - client_send_msg(); - } - else if(msg == NETMSG_RCON_AUTH_STATUS) - { - int result = msg_unpack_int(); - if(msg_unpack_error() == 0) - rcon_authed = result; - } - else if(msg == NETMSG_RCON_LINE) - { - const char *line = msg_unpack_string(); - if(msg_unpack_error() == 0) - { - /*dbg_msg("remote", "%s", line);*/ - modc_rcon_line(line); - } - } - else if(msg == NETMSG_PING_REPLY) - dbg_msg("client/network", "latency %.2f", (time_get() - ping_start_time)*1000 / (float)time_freq()); - else if(msg == NETMSG_INPUTTIMING) - { - int input_predtick = msg_unpack_int(); - int time_left = msg_unpack_int(); - - /* adjust our prediction time */ - int k; - int64 target = 0; - for(k = 0; k < 200; k++) - { - if(inputs[k].tick == input_predtick) - { - target = inputs[k].predicted_time + (time_get() - inputs[k].time); - target = target - (int64)(((time_left-prediction_margin)/1000.0f)*time_freq()); - //st_update(&predicted_time, ); - break; - } - } - - if(target) - st_update(&predicted_time, &inputtime_margin_graph, target, time_left, 1); - } - else if(msg == NETMSG_SNAP || msg == NETMSG_SNAPSINGLE || msg == NETMSG_SNAPEMPTY) - { - /*dbg_msg("client/network", "got snapshot"); */ - int num_parts = 1; - int part = 0; - int game_tick = msg_unpack_int(); - int delta_tick = game_tick-msg_unpack_int(); - int part_size = 0; - int crc = 0; - int complete_size = 0; - const char *data = 0; - - /* we are not allowed to process snapshot yet */ - if(client_state() < CLIENTSTATE_LOADING) - return; - - if(msg == NETMSG_SNAP) - { - num_parts = msg_unpack_int(); - part = msg_unpack_int(); - } - - if(msg != NETMSG_SNAPEMPTY) - { - crc = msg_unpack_int(); - part_size = msg_unpack_int(); - } - - data = (const char *)msg_unpack_raw(part_size); - - if(msg_unpack_error()) - return; - - if(game_tick >= current_recv_tick) - { - if(game_tick != current_recv_tick) - { - snapshot_parts = 0; - current_recv_tick = game_tick; - } - - /* TODO: clean this up abit */ - mem_copy((char*)snapshot_incomming_data + part*MAX_SNAPSHOT_PACKSIZE, data, part_size); - snapshot_parts |= 1<= 0) - { - int deltashot_size = snapstorage_get(&snapshot_storage, delta_tick, 0, &deltashot, 0); - - if(deltashot_size < 0) - { - /* couldn't find the delta snapshots that the server used */ - /* to compress this snapshot. force the server to resync */ - if(config.debug) - dbg_msg("client", "error, couldn't find the delta snapshot"); - - /* ack snapshot */ - /* TODO: combine this with the input message */ - ack_game_tick = -1; - return; - } - } - - /* decompress snapshot */ - deltadata = snapshot_empty_delta(); - deltasize = sizeof(int)*3; - - if(complete_size) - { - int intsize = intpack_decompress(snapshot_incomming_data, complete_size, tmpbuffer2); - - if(intsize < 0) /* failure during decompression, bail */ - return; - - deltadata = tmpbuffer2; - deltasize = intsize; - } - - /* unpack delta */ - purgetick = delta_tick; - snapsize = snapshot_unpack_delta(deltashot, (SNAPSHOT*)tmpbuffer3, deltadata, deltasize); - if(snapsize < 0) - { - dbg_msg("client", "delta unpack failed!"); - return; - } - - if(msg != NETMSG_SNAPEMPTY && snapshot_crc((SNAPSHOT*)tmpbuffer3) != crc) - { - if(config.debug) - { - dbg_msg("client", "snapshot crc error #%d - tick=%d wantedcrc=%d gotcrc=%d compressed_size=%d delta_tick=%d", - snapcrcerrors, game_tick, crc, snapshot_crc((SNAPSHOT*)tmpbuffer3), complete_size, delta_tick); - } - - snapcrcerrors++; - if(snapcrcerrors > 10) - { - /* to many errors, send reset */ - ack_game_tick = -1; - client_send_input(); - snapcrcerrors = 0; - } - return; - } - else - { - if(snapcrcerrors) - snapcrcerrors--; - } - - /* purge old snapshots */ - purgetick = delta_tick; - if(snapshots[SNAP_PREV] && snapshots[SNAP_PREV]->tick < purgetick) - purgetick = snapshots[SNAP_PREV]->tick; - if(snapshots[SNAP_CURRENT] && snapshots[SNAP_CURRENT]->tick < purgetick) - purgetick = snapshots[SNAP_PREV]->tick; - snapstorage_purge_until(&snapshot_storage, purgetick); - - /* add new */ - snapstorage_add(&snapshot_storage, game_tick, time_get(), snapsize, (SNAPSHOT*)tmpbuffer3, 1); - - /* add snapshot to demo */ - if(demorec_isrecording()) - { - - /* write tick marker */ - /* - DEMOREC_TICKMARKER marker; - marker.tick = game_tick; - swap_endian(&marker, sizeof(int), sizeof(marker)/sizeof(int)); - demorec_record_write("TICK", sizeof(marker), &marker); - demorec_record_write("SNAP", snapsize, tmpbuffer3); - */ - - /* write snapshot */ - demorec_record_snapshot(game_tick, tmpbuffer3, snapsize); - } - - /* apply snapshot, cycle pointers */ - recived_snapshots++; - - current_recv_tick = game_tick; - - /* we got two snapshots until we see us self as connected */ - if(recived_snapshots == 2) - { - /* start at 200ms and work from there */ - st_init(&predicted_time, game_tick*time_freq()/50); - predicted_time.adjustspeed[1] = 1000.0f; - st_init(&game_time, (game_tick-1)*time_freq()/50); - snapshots[SNAP_PREV] = snapshot_storage.first; - snapshots[SNAP_CURRENT] = snapshot_storage.last; - local_start_time = time_get(); - client_set_state(CLIENTSTATE_ONLINE); - } - - /* adjust game time */ - { - int64 now = st_get(&game_time, time_get()); - int64 tickstart = game_tick*time_freq()/50; - int64 time_left = (tickstart-now)*1000 / time_freq(); - /*st_update(&game_time, (game_tick-1)*time_freq()/50);*/ - st_update(&game_time, &gametime_margin_graph, (game_tick-1)*time_freq()/50, time_left, 0); - } - - /* ack snapshot */ - ack_game_tick = game_tick; - } - } - } - } - else - { - /* game message */ - if(demorec_isrecording()) - demorec_record_message(packet->data, packet->data_size); - /* demorec_record_write("MESG", packet->data_size, ); */ - - modc_message(msg); - } - } -} - -int client_mapdownload_amount() { return mapdownload_amount; } -int client_mapdownload_totalsize() { return mapdownload_totalsize; } - -static void client_pump_network() -{ - NETCHUNK packet; - - netclient_update(net); - - if(client_state() != CLIENTSTATE_DEMOPLAYBACK) - { - /* check for errors */ - if(client_state() != CLIENTSTATE_OFFLINE && netclient_state(net) == NETSTATE_OFFLINE) - { - client_set_state(CLIENTSTATE_OFFLINE); - client_disconnect(); - dbg_msg("client", "offline error='%s'", netclient_error_string(net)); - } - - /* */ - if(client_state() == CLIENTSTATE_CONNECTING && netclient_state(net) == NETSTATE_ONLINE) - { - /* we switched to online */ - dbg_msg("client", "connected, sending info"); - client_set_state(CLIENTSTATE_LOADING); - client_send_info(); - } - } - - /* process packets */ - while(netclient_recv(net, &packet)) - client_process_packet(&packet); -} - -static void client_democallback_snapshot(void *data, int size) -{ - /* update ticks, they could have changed */ - const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); - SNAPSTORAGE_HOLDER *temp; - current_tick = info->current_tick; - prev_tick = info->previous_tick; - - /* handle snapshots */ - temp = snapshots[SNAP_PREV]; - snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; - snapshots[SNAP_CURRENT] = temp; - - mem_copy(snapshots[SNAP_CURRENT]->snap, data, size); - mem_copy(snapshots[SNAP_CURRENT]->alt_snap, data, size); - - modc_newsnapshot(); - /*modc_predict();*/ -} - -static void client_democallback_message(void *data, int size) -{ - int sys = 0; - int msg = msg_unpack_start(data, size, &sys); - if(!sys) - modc_message(msg); -} - - -const DEMOPLAYBACK_INFO *client_demoplayer_getinfo() -{ - static DEMOPLAYBACK_INFO ret; - const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); - ret.first_tick = info->first_tick; - ret.last_tick = info->last_tick; - ret.current_tick = info->current_tick; - ret.paused = info->paused; - ret.speed = info->speed; - return &ret; -} - -void client_demoplayer_setpos(float percent) -{ - demorec_playback_set(percent); -} - -void client_demoplayer_setspeed(float speed) -{ - demorec_playback_setspeed(speed); -} - -void client_demoplayer_setpause(int paused) -{ - if(paused) - demorec_playback_pause(); - else - demorec_playback_unpause(); -} - -static void client_update() -{ - if(client_state() == CLIENTSTATE_DEMOPLAYBACK) - { - demorec_playback_update(); - if(demorec_isplaying()) - { - /* update timers */ - const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); - current_tick = info->current_tick; - prev_tick = info->previous_tick; - intratick = info->intratick; - ticktime = info->ticktime; - } - else - { - /* disconnect on error */ - client_disconnect(); - } - } - else if(client_state() != CLIENTSTATE_OFFLINE && recived_snapshots >= 3) - { - /* switch snapshot */ - int repredict = 0; - int64 freq = time_freq(); - int64 now = st_get(&game_time, time_get()); - int64 pred_now = st_get(&predicted_time, time_get()); - - while(1) - { - SNAPSTORAGE_HOLDER *cur = snapshots[SNAP_CURRENT]; - int64 tickstart = (cur->tick)*time_freq()/50; - - if(tickstart < now) - { - SNAPSTORAGE_HOLDER *next = snapshots[SNAP_CURRENT]->next; - if(next) - { - snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; - snapshots[SNAP_CURRENT] = next; - - /* set ticks */ - current_tick = snapshots[SNAP_CURRENT]->tick; - prev_tick = snapshots[SNAP_PREV]->tick; - - if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV]) - { - modc_newsnapshot(); - repredict = 1; - } - } - else - break; - } - else - break; - } - - if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV]) - { - int64 curtick_start = (snapshots[SNAP_CURRENT]->tick)*time_freq()/50; - int64 prevtick_start = (snapshots[SNAP_PREV]->tick)*time_freq()/50; - /*tg_add(&predicted_time_graph, pred_now, 0); */ - int prev_pred_tick = (int)(pred_now*50/time_freq()); - int new_pred_tick = prev_pred_tick+1; - static float last_predintra = 0; - - intratick = (now - prevtick_start) / (float)(curtick_start-prevtick_start); - ticktime = (now - prevtick_start) / (float)freq; /*(float)SERVER_TICK_SPEED);*/ - - curtick_start = new_pred_tick*time_freq()/50; - prevtick_start = prev_pred_tick*time_freq()/50; - predintratick = (pred_now - prevtick_start) / (float)(curtick_start-prevtick_start); - - if(new_pred_tick < snapshots[SNAP_PREV]->tick-SERVER_TICK_SPEED || new_pred_tick > snapshots[SNAP_PREV]->tick+SERVER_TICK_SPEED) - { - dbg_msg("client", "prediction time reset!"); - st_init(&predicted_time, snapshots[SNAP_CURRENT]->tick*time_freq()/50); - } - - if(new_pred_tick > current_predtick) - { - last_predintra = predintratick; - current_predtick = new_pred_tick; - repredict = 1; - - /* send input */ - client_send_input(); - } - - last_predintra = predintratick; - } - - /* only do sane predictions */ - if(repredict) - { - if(current_predtick > current_tick && current_predtick < current_tick+50) - modc_predict(); - } - - /* fetch server info if we don't have it */ - if(client_state() >= CLIENTSTATE_LOADING && - current_server_info_requesttime >= 0 && - time_get() > current_server_info_requesttime) - { - client_serverbrowse_request(&server_address); - current_server_info_requesttime = time_get()+time_freq()*2; - } - } - - /* STRESS TEST: join the server again */ - if(config.dbg_stress) - { - static int64 action_taken = 0; - int64 now = time_get(); - if(client_state() == CLIENTSTATE_OFFLINE) - { - if(now > action_taken+time_freq()*2) - { - dbg_msg("stress", "reconnecting!"); - client_connect(config.dbg_stress_server); - action_taken = now; - } - } - else - { - /*if(now > action_taken+time_freq()*(10+config.dbg_stress)) - { - dbg_msg("stress", "disconnecting!"); - client_disconnect(); - action_taken = now; - }*/ - } - } - - /* pump the network */ - client_pump_network(); - - /* update the maser server registry */ - mastersrv_update(); - - /* update the server browser */ - client_serverbrowse_update(); -} - - -static void client_versionupdate() -{ - static int state = 0; - static HOSTLOOKUP version_serveraddr; - - if(state == 0) - { - engine_hostlookup(&version_serveraddr, config.cl_version_server); - state++; - } - else if(state == 1) - { - if(jobs_status(&version_serveraddr.job) == JOBSTATUS_DONE) - { - NETCHUNK packet; - - mem_zero(&packet, sizeof(NETCHUNK)); - - version_serveraddr.addr.port = VERSIONSRV_PORT; - - packet.client_id = -1; - packet.address = version_serveraddr.addr; - packet.data = VERSIONSRV_GETVERSION; - packet.data_size = sizeof(VERSIONSRV_GETVERSION); - packet.flags = NETSENDFLAG_CONNLESS; - - netclient_send(net, &packet); - state++; - } - } -} - -extern int editor_update_and_render(); -extern void editor_init(); - -static void client_run() -{ - NETADDR bindaddr; - int64 reporttime = time_get(); - int64 reportinterval = time_freq()*1; - - static PERFORMACE_INFO rootscope = {"root", 0}; - perf_start(&rootscope); - - local_start_time = time_get(); - snapshot_parts = 0; - - /* init graphics and sound */ - if(gfx_init() != 0) - return; - - /* start refreshing addresses while we load */ - mastersrv_refresh_addresses(); - - /* init the editor */ - editor_init(); - - /* sound is allowed to fail */ - snd_init(); - - /* load data */ - if(!client_load_data()) - return; - - /* init the mod */ - modc_init(); - dbg_msg("client", "version %s", modc_net_version()); - - /* open socket */ - mem_zero(&bindaddr, sizeof(bindaddr)); - net = netclient_open(bindaddr, 0); - - /* connect to the server if wanted */ - /* - if(config.cl_connect[0] != 0) - client_connect(config.cl_connect); - config.cl_connect[0] = 0; - */ - - /* */ - graph_init(&fps_graph, 0.0f, 200.0f); - - /* never start with the editor */ - config.cl_editor = 0; - - inp_mouse_mode_relative(); - - while (1) - { - static PERFORMACE_INFO rootscope = {"root", 0}; - int64 frame_start_time = time_get(); - frames++; - - perf_start(&rootscope); - - /* */ - client_versionupdate(); - - /* handle pending connects */ - if(cmd_connect[0]) - { - client_connect(cmd_connect); - cmd_connect[0] = 0; - } - - /* update input */ - { - static PERFORMACE_INFO scope = {"inp_update", 0}; - perf_start(&scope); - inp_update(); - perf_end(); - } - - /* update sound */ - { - static PERFORMACE_INFO scope = {"snd_update", 0}; - perf_start(&scope); - snd_update(); - perf_end(); - } - - /* release focus */ - if(!gfx_window_active()) - { - if(window_must_refocus == 0) - inp_mouse_mode_absolute(); - window_must_refocus = 1; - } - else if (config.dbg_focus && inp_key_pressed(KEY_ESCAPE)) - { - inp_mouse_mode_absolute(); - window_must_refocus = 1; - } - - /* refocus */ - if(window_must_refocus && gfx_window_active()) - { - if(window_must_refocus < 3) - { - inp_mouse_mode_absolute(); - window_must_refocus++; - } - - if(inp_key_pressed(KEY_MOUSE_1)) - { - inp_mouse_mode_relative(); - window_must_refocus = 0; - } - } - - /* panic quit button */ - if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_pressed('q')) - break; - - if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('d')) - config.debug ^= 1; - - if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('g')) - config.dbg_graphs ^= 1; - - if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('e')) - { - config.cl_editor = config.cl_editor^1; - inp_mouse_mode_relative(); - } - - /* - if(!gfx_window_open()) - break; - */ - - /* render */ - if(config.cl_editor) - { - client_update(); - editor_update_and_render(); - gfx_swap(); - } - else - { - { - static PERFORMACE_INFO scope = {"client_update", 0}; - perf_start(&scope); - client_update(); - perf_end(); - } - - if(config.dbg_stress) - { - if((frames%10) == 0) - { - client_render(); - gfx_swap(); - } - } - else - { - { - static PERFORMACE_INFO scope = {"client_render", 0}; - perf_start(&scope); - client_render(); - perf_end(); - } - - { - static PERFORMACE_INFO scope = {"gfx_swap", 0}; - perf_start(&scope); - gfx_swap(); - perf_end(); - } - } - } - - perf_end(); - - - /* check conditions */ - if(client_state() == CLIENTSTATE_QUITING) - break; - - /* be nice */ - if(config.dbg_stress) - thread_sleep(5); - else if(config.cl_cpu_throttle || !gfx_window_active()) - thread_sleep(1); - - if(config.dbg_hitch) - { - thread_sleep(config.dbg_hitch); - config.dbg_hitch = 0; - } - - if(reporttime < time_get()) - { - if(0 && config.debug) - { - dbg_msg("client/report", "fps=%.02f (%.02f %.02f) netstate=%d", - frames/(float)(reportinterval/time_freq()), - 1.0f/frametime_high, - 1.0f/frametime_low, - netclient_state(net)); - } - frametime_low = 1; - frametime_high = 0; - frames = 0; - reporttime += reportinterval; - perf_next(); - - if(config.dbg_pref) - perf_dump(&rootscope); - } - - /* update frametime */ - frametime = (time_get()-frame_start_time)/(float)time_freq(); - if(frametime < frametime_low) - frametime_low = frametime; - if(frametime > frametime_high) - frametime_high = frametime; - - graph_add(&fps_graph, 1.0f/frametime, 1,1,1); - } - - modc_shutdown(); - client_disconnect(); - - gfx_shutdown(); - snd_shutdown(); -} - -static void con_connect(void *result, void *user_data) -{ - str_copy(cmd_connect, console_arg_string(result, 0), sizeof(cmd_connect)); -} - -static void con_disconnect(void *result, void *user_data) -{ - client_disconnect(); -} - -static void con_quit(void *result, void *user_data) -{ - client_quit(); -} - -static void con_ping(void *result, void *user_data) -{ - msg_pack_start_system(NETMSG_PING, 0); - msg_pack_end(); - client_send_msg(); - ping_start_time = time_get(); -} - -static void con_screenshot(void *result, void *user_data) -{ - gfx_screenshot(); -} - -static void con_rcon(void *result, void *user_data) -{ - client_rcon(console_arg_string(result, 0)); -} - -static void con_rcon_auth(void *result, void *user_data) -{ - client_rcon_auth("", console_arg_string(result, 0)); -} - -static void con_addfavorite(void *result, void *user_data) -{ - NETADDR addr; - if(net_addr_from_str(&addr, console_arg_string(result, 0)) == 0) - client_serverbrowse_addfavorite(addr); -} - -const char *client_demoplayer_play(const char *filename) -{ - int crc; - const char *error; - client_disconnect(); - netclient_error_string_reset(net); - - /* try to start playback */ - demorec_playback_registercallbacks(client_democallback_snapshot, client_democallback_message); - - if(demorec_playback_load(filename)) - return "error loading demo"; - - /* load map */ - crc = (demorec_playback_info()->header.crc[0]<<24)| - (demorec_playback_info()->header.crc[1]<<16)| - (demorec_playback_info()->header.crc[2]<<8)| - (demorec_playback_info()->header.crc[3]); - error = client_load_map_search(demorec_playback_info()->header.map, crc); - if(error) - { - client_disconnect_with_reason(error); - return error; - } - - modc_connected(); - - /* setup buffers */ - mem_zero(demorec_snapshotdata, sizeof(demorec_snapshotdata)); - - snapshots[SNAP_CURRENT] = &demorec_snapshotholders[SNAP_CURRENT]; - snapshots[SNAP_PREV] = &demorec_snapshotholders[SNAP_PREV]; - - snapshots[SNAP_CURRENT]->snap = (SNAPSHOT *)demorec_snapshotdata[SNAP_CURRENT][0]; - snapshots[SNAP_CURRENT]->alt_snap = (SNAPSHOT *)demorec_snapshotdata[SNAP_CURRENT][1]; - snapshots[SNAP_CURRENT]->snap_size = 0; - snapshots[SNAP_CURRENT]->tick = -1; - - snapshots[SNAP_PREV]->snap = (SNAPSHOT *)demorec_snapshotdata[SNAP_PREV][0]; - snapshots[SNAP_PREV]->alt_snap = (SNAPSHOT *)demorec_snapshotdata[SNAP_PREV][1]; - snapshots[SNAP_PREV]->snap_size = 0; - snapshots[SNAP_PREV]->tick = -1; - - /* enter demo playback state */ - client_set_state(CLIENTSTATE_DEMOPLAYBACK); - - demorec_playback_play(); - modc_entergame(); - - return 0; -} - -static void con_play(void *result, void *user_data) -{ - client_demoplayer_play(console_arg_string(result, 0)); -} - -static void con_record(void *result, void *user_data) -{ - if(state != CLIENTSTATE_ONLINE) - dbg_msg("demorec/record", "client is not online"); - else - { - char filename[512]; - str_format(filename, sizeof(filename), "demos/%s.demo", console_arg_string(result, 0)); - demorec_record_start(filename, modc_net_version(), current_map, current_map_crc, "client"); - } -} - -static void con_stoprecord(void *result, void *user_data) -{ - demorec_record_stop(); -} - -static void con_serverdummy(void *result, void *user_data) -{ - dbg_msg("client", "this command is not available on the client"); -} - -static void client_register_commands() -{ - MACRO_REGISTER_COMMAND("quit", "", CFGFLAG_CLIENT, con_quit, 0x0, "Quit Teeworlds"); - MACRO_REGISTER_COMMAND("exit", "", CFGFLAG_CLIENT, con_quit, 0x0, "Quit Teeworlds"); - MACRO_REGISTER_COMMAND("connect", "s", CFGFLAG_CLIENT, con_connect, 0x0, "Connect to the specified host/ip"); - MACRO_REGISTER_COMMAND("disconnect", "", CFGFLAG_CLIENT, con_disconnect, 0x0, "Disconnect from the server"); - MACRO_REGISTER_COMMAND("ping", "", CFGFLAG_CLIENT, con_ping, 0x0, "Ping the current server"); - MACRO_REGISTER_COMMAND("screenshot", "", CFGFLAG_CLIENT, con_screenshot, 0x0, "Take a screenshot"); - MACRO_REGISTER_COMMAND("rcon", "r", CFGFLAG_CLIENT, con_rcon, 0x0, "Send specified command to rcon"); - MACRO_REGISTER_COMMAND("rcon_auth", "s", CFGFLAG_CLIENT, con_rcon_auth, 0x0, "Authenticate to rcon"); - - MACRO_REGISTER_COMMAND("play", "r", CFGFLAG_CLIENT, con_play, 0x0, "Play the file specified"); - MACRO_REGISTER_COMMAND("record", "s", CFGFLAG_CLIENT, con_record, 0, "Record to the file"); - MACRO_REGISTER_COMMAND("stoprecord", "", CFGFLAG_CLIENT, con_stoprecord, 0, "Stop recording"); - - MACRO_REGISTER_COMMAND("add_favorite", "s", CFGFLAG_CLIENT, con_addfavorite, 0x0, "Add a server as a favorite"); - - /* register server dummy commands for tab completion */ - MACRO_REGISTER_COMMAND("kick", "i", CFGFLAG_SERVER, con_serverdummy, 0, "Kick player with specified id"); - MACRO_REGISTER_COMMAND("ban", "s?i", CFGFLAG_SERVER, con_serverdummy, 0, "Ban player with ip/id for x minutes"); - MACRO_REGISTER_COMMAND("unban", "s", CFGFLAG_SERVER, con_serverdummy, 0, "Unban ip"); - MACRO_REGISTER_COMMAND("bans", "", CFGFLAG_SERVER, con_serverdummy, 0, "Show banlist"); - MACRO_REGISTER_COMMAND("status", "", CFGFLAG_SERVER, con_serverdummy, 0, "List players"); - MACRO_REGISTER_COMMAND("shutdown", "", CFGFLAG_SERVER, con_serverdummy, 0, "Shut down"); - /*MACRO_REGISTER_COMMAND("record", "", CFGFLAG_SERVER, con_serverdummy, 0); - MACRO_REGISTER_COMMAND("stoprecord", "", CFGFLAG_SERVER, con_serverdummy, 0);*/ -} - -void client_save_line(const char *line) -{ - engine_config_write_line(line); -} - -const char *client_user_directory() -{ - static char path[1024] = {0}; - fs_storage_path("Teeworlds", path, sizeof(path)); - return path; -} - -#if defined(CONF_PLATFORM_MACOSX) -int SDL_main(int argc, char **argv) -#else -int main(int argc, char **argv) -#endif -{ - /* init the engine */ - dbg_msg("client", "starting..."); - engine_init("Teeworlds"); - - /* register all console commands */ - client_register_commands(); - modc_console_init(); - - /* parse the command line arguments */ - engine_parse_arguments(argc, argv); - - /* execute config file */ - console_execute_file("settings.cfg"); - - /* run the client*/ - client_run(); - - /* write down the config and quit */ - if(engine_config_write_start() == 0) - { - config_save(); - client_serverbrowse_save(); - modc_save_config(); - engine_config_write_stop(); - } - - return 0; -} diff --git a/src/engine/client/ec_client.cpp b/src/engine/client/ec_client.cpp new file mode 100644 index 00000000..7ba0a2bb --- /dev/null +++ b/src/engine/client/ec_client.cpp @@ -0,0 +1,2087 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include "editor.h" +#include "graphics.h" +#include "client.h" + +static IEditor *m_pEditor = 0; +static IEngineGraphics *m_pGraphics = 0; +IEngineGraphics *Graphics() { return m_pGraphics; } + +static IGameClient *m_pGameClient = 0; + + +class CClient : public IEngine +{ +public: + virtual class IGraphics *Graphics() + { + return m_pGraphics; + } +}; + +static CClient m_Client; + +const int prediction_margin = 1000/50/2; /* magic network prediction value */ + +/* + Server Time + Client Mirror Time + Client Predicted Time + + Snapshot Latency + Downstream latency + + Prediction Latency + Upstream latency +*/ + +/* network client, must be accessible from other parts like the server browser */ +CNetClient m_NetClient; + +/* TODO: ugly, fix me */ +extern void client_serverbrowse_set(NETADDR *addr, int request, int token, SERVER_INFO *info); +extern void client_serverbrowse_save(); + +static unsigned snapshot_parts; +static int64 local_start_time; + +static int debug_font; +static float frametime = 0.0001f; +static float frametime_low = 1.0f; +static float frametime_high = 0.0f; +static int frames = 0; +static NETADDR server_address; +static int window_must_refocus = 0; +static int snapcrcerrors = 0; + +static int ack_game_tick = -1; +static int current_recv_tick = 0; +static int rcon_authed = 0; + +/* version-checking */ +static char versionstr[10] = "0"; + +/* pinging */ +static int64 ping_start_time = 0; + +/* */ +static char current_map[256] = {0}; +static int current_map_crc = 0; + +/* */ +static char cmd_connect[256] = {0}; + +/* map download */ +static char mapdownload_filename[256] = {0}; +static char mapdownload_name[256] = {0}; +static IOHANDLE mapdownload_file = 0; +static int mapdownload_chunk = 0; +static int mapdownload_crc = 0; +static int mapdownload_amount = -1; +static int mapdownload_totalsize = -1; + +/* */ +static SERVER_INFO current_server_info = {0}; +static int64 current_server_info_requesttime = -1; /* >= 0 should request, == -1 got info */ + +/* current time */ +static int current_tick = 0; +static float intratick = 0; +static float ticktime = 0; +static int prev_tick = 0; + +/* */ +/*static int predictiontime_pingspikecounter = 0; +static int gametime_pingspikecounter = 0;*/ + +/* predicted time */ +static int current_predtick = 0; +static float predintratick = 0; +static int last_input_timeleft = 0; + +static struct /* TODO: handle input better */ +{ + int data[MAX_INPUT_SIZE]; /* the input data */ + int tick; /* the tick that the input is for */ + int64 predicted_time; /* prediction latency when we sent this input */ + int64 time; +} inputs[200]; +static int current_input = 0; + +enum +{ + GRAPH_MAX=128 +}; + +typedef struct +{ + float min, max; + float values[GRAPH_MAX]; + float colors[GRAPH_MAX][3]; + int index; +} GRAPH; + +static void graph_init(GRAPH *g, float min, float max) +{ + g->min = min; + g->max = max; + g->index = 0; +} + +static void graph_scale_max(GRAPH *g) +{ + int i = 0; + g->max = 0; + for(i = 0; i < GRAPH_MAX; i++) + { + if(g->values[i] > g->max) + g->max = g->values[i]; + } +} + +static void graph_scale_min(GRAPH *g) +{ + int i = 0; + g->min = g->max; + for(i = 0; i < GRAPH_MAX; i++) + { + if(g->values[i] < g->min) + g->min = g->values[i]; + } +} + +static void graph_add(GRAPH *graph, float v, float r, float g, float b) +{ + graph->index = (graph->index+1)&(GRAPH_MAX-1); + graph->values[graph->index] = v; + graph->colors[graph->index][0] = r; + graph->colors[graph->index][1] = g; + graph->colors[graph->index][2] = b; +} + +static void graph_render(GRAPH *g, float x, float y, float w, float h, const char *description) +{ + char buf[32]; + int i; + + //m_pGraphics->BlendNormal(); + + + Graphics()->TextureSet(-1); + + m_pGraphics->QuadsBegin(); + Graphics()->SetColor(0, 0, 0, 0.75f); + Graphics()->QuadsDrawTL(x, y, w, h); + m_pGraphics->QuadsEnd(); + + Graphics()->LinesBegin(); + Graphics()->SetColor(0.95f, 0.95f, 0.95f, 1.00f); + Graphics()->LinesDraw(x, y+h/2, x+w, y+h/2); + Graphics()->SetColor(0.5f, 0.5f, 0.5f, 0.75f); + Graphics()->LinesDraw(x, y+(h*3)/4, x+w, y+(h*3)/4); + Graphics()->LinesDraw(x, y+h/4, x+w, y+h/4); + for(i = 1; i < GRAPH_MAX; i++) + { + float a0 = (i-1)/(float)GRAPH_MAX; + float a1 = i/(float)GRAPH_MAX; + int i0 = (g->index+i-1)&(GRAPH_MAX-1); + int i1 = (g->index+i)&(GRAPH_MAX-1); + + float v0 = (g->values[i0]-g->min) / (g->max-g->min); + float v1 = (g->values[i1]-g->min) / (g->max-g->min); + + Graphics()->SetColorVertex(0, g->colors[i0][0], g->colors[i0][1], g->colors[i0][2], 0.75f); + Graphics()->SetColorVertex(1, g->colors[i1][0], g->colors[i1][1], g->colors[i1][2], 0.75f); + Graphics()->LinesDraw(x+a0*w, y+h-v0*h, x+a1*w, y+h-v1*h); + + } + Graphics()->LinesEnd(); + + + Graphics()->TextureSet(debug_font); + Graphics()->QuadsText(x+2, y+h-16, 16, 1,1,1,1, description); + + str_format(buf, sizeof(buf), "%.2f", g->max); + Graphics()->QuadsText(x+w-8*strlen(buf)-8, y+2, 16, 1,1,1,1, buf); + + str_format(buf, sizeof(buf), "%.2f", g->min); + Graphics()->QuadsText(x+w-8*strlen(buf)-8, y+h-16, 16, 1,1,1,1, buf); + +} + +typedef struct +{ + int64 snap; + int64 current; + int64 target; + + int64 rlast; + int64 tlast; + GRAPH graph; + + int spikecounter; + + float adjustspeed[2]; /* 0 = down, 1 = up */ +} SMOOTHTIME; + +static void st_init(SMOOTHTIME *st, int64 target) +{ + st->snap = time_get(); + st->current = target; + st->target = target; + st->adjustspeed[0] = 0.3f; + st->adjustspeed[1] = 0.3f; + graph_init(&st->graph, 0.0f, 0.5f); +} + +static int64 st_get(SMOOTHTIME *st, int64 now) +{ + float adjust_speed, a; + int64 c = st->current + (now - st->snap); + int64 t = st->target + (now - st->snap); + int64 r; + + /* it's faster to adjust upward instead of downward */ + /* we might need to adjust these abit */ + + adjust_speed = st->adjustspeed[0]; + if(t > c) + adjust_speed = st->adjustspeed[1]; + + a = ((now-st->snap)/(float)time_freq()) * adjust_speed; + if(a > 1.0f) + a = 1.0f; + + r = c + (int64)((t-c)*a); + + graph_add(&st->graph, a+0.5f,1,1,1); + + return r; +} + +static void st_update_int(SMOOTHTIME *st, int64 target) +{ + int64 now = time_get(); + st->current = st_get(st, now); + st->snap = now; + st->target = target; +} + +static void st_update(SMOOTHTIME *st, GRAPH *graph, int64 target, int time_left, int adjust_direction) +{ + int update_timer = 1; + + if(time_left < 0) + { + int is_spike = 0; + if(time_left < -50) + { + is_spike = 1; + + st->spikecounter += 5; + if(st->spikecounter > 50) + st->spikecounter = 50; + } + + if(is_spike && st->spikecounter < 15) + { + /* ignore this ping spike */ + update_timer = 0; + graph_add(graph, time_left, 1,1,0); + } + else + { + graph_add(graph, time_left, 1,0,0); + if(st->adjustspeed[adjust_direction] < 30.0f) + st->adjustspeed[adjust_direction] *= 2.0f; + } + } + else + { + if(st->spikecounter) + st->spikecounter--; + + graph_add(graph, time_left, 0,1,0); + + st->adjustspeed[adjust_direction] *= 0.95f; + if(st->adjustspeed[adjust_direction] < 2.0f) + st->adjustspeed[adjust_direction] = 2.0f; + } + + last_input_timeleft = time_left; + + if(update_timer) + st_update_int(st, target); +} + +static SMOOTHTIME game_time; +static SMOOTHTIME predicted_time; + +/* graphs */ +static GRAPH inputtime_margin_graph; +static GRAPH gametime_margin_graph; +static GRAPH fps_graph; + +/* -- snapshot handling --- */ +enum +{ + NUM_SNAPSHOT_TYPES=2 +}; + +/* the game snapshots are modifiable by the game */ +CSnapshotStorage snapshot_storage; +static CSnapshotStorage::CHolder *snapshots[NUM_SNAPSHOT_TYPES] = {0, 0}; + +static int recived_snapshots = 0; +static char snapshot_incomming_data[CSnapshot::MAX_SIZE]; + +static CSnapshotStorage::CHolder demorec_snapshotholders[NUM_SNAPSHOT_TYPES]; +static char *demorec_snapshotdata[NUM_SNAPSHOT_TYPES][2][CSnapshot::MAX_SIZE]; + +/* --- */ + +void *snap_get_item(int snapid, int index, SNAP_ITEM *item) +{ + CSnapshotItem *i; + dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); + i = snapshots[snapid]->m_pAltSnap->GetItem(index); + item->datasize = snapshots[snapid]->m_pAltSnap->GetItemSize(index); + item->type = i->Type(); + item->id = i->ID(); + return (void *)i->Data(); +} + +void snap_invalidate_item(int snapid, int index) +{ + CSnapshotItem *i; + dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); + i = snapshots[snapid]->m_pAltSnap->GetItem(index); + if(i) + { + if((char *)i < (char *)snapshots[snapid]->m_pAltSnap || (char *)i > (char *)snapshots[snapid]->m_pAltSnap + snapshots[snapid]->m_SnapSize) + dbg_msg("ASDFASDFASdf", "ASDFASDFASDF"); + if((char *)i >= (char *)snapshots[snapid]->m_pSnap && (char *)i < (char *)snapshots[snapid]->m_pSnap + snapshots[snapid]->m_SnapSize) + dbg_msg("ASDFASDFASdf", "ASDFASDFASDF"); + i->m_TypeAndID = -1; + } +} + +void *snap_find_item(int snapid, int type, int id) +{ + /* TODO: linear search. should be fixed. */ + int i; + + if(!snapshots[snapid]) + return 0x0; + + for(i = 0; i < snapshots[snapid]->m_pSnap->m_NumItems; i++) + { + CSnapshotItem *itm = snapshots[snapid]->m_pAltSnap->GetItem(i); + if(itm->Type() == type && itm->ID() == id) + return (void *)itm->Data(); + } + return 0x0; +} + +int snap_num_items(int snapid) +{ + dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid"); + if(!snapshots[snapid]) + return 0; + return snapshots[snapid]->m_pSnap->m_NumItems; +} + +/* ------ time functions ------ */ +float client_intratick() { return intratick; } +float client_predintratick() { return predintratick; } +float client_ticktime() { return ticktime; } +int client_tick() { return current_tick; } +int client_prevtick() { return prev_tick; } +int client_predtick() { return current_predtick; } +int client_tickspeed() { return SERVER_TICK_SPEED; } +float client_frametime() { return frametime; } +float client_localtime() { return (time_get()-local_start_time)/(float)(time_freq()); } + +/* ----- send functions ----- */ +int client_send_msg() +{ + const MSG_INFO *pInfo = msg_get_info(); + CNetChunk Packet; + + if(!pInfo) + return -1; + + if(client_state() == CLIENTSTATE_OFFLINE) + return 0; + + mem_zero(&Packet, sizeof(CNetChunk)); + + Packet.m_ClientID = 0; + Packet.m_pData = pInfo->data; + Packet.m_DataSize = pInfo->size; + + if(pInfo->flags&MSGFLAG_VITAL) + Packet.m_Flags |= NETSENDFLAG_VITAL; + if(pInfo->flags&MSGFLAG_FLUSH) + Packet.m_Flags |= NETSENDFLAG_FLUSH; + + if(pInfo->flags&MSGFLAG_RECORD) + { + if(demorec_isrecording()) + demorec_record_message(Packet.m_pData, Packet.m_DataSize); + } + + if(!(pInfo->flags&MSGFLAG_NOSEND)) + m_NetClient.Send(&Packet); + return 0; +} + +static void client_send_info() +{ + msg_pack_start_system(NETMSG_INFO, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_string(modc_net_version(), 128); + msg_pack_string(config.player_name, 128); + msg_pack_string(config.clan_name, 128); + msg_pack_string(config.password, 128); + msg_pack_end(); + client_send_msg(); +} + + +static void client_send_entergame() +{ + msg_pack_start_system(NETMSG_ENTERGAME, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_end(); + client_send_msg(); +} + +static void client_send_ready() +{ + msg_pack_start_system(NETMSG_READY, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_end(); + client_send_msg(); +} + +int client_rcon_authed() +{ + return rcon_authed; +} + +void client_rcon_auth(const char *name, const char *password) +{ + msg_pack_start_system(NETMSG_RCON_AUTH, MSGFLAG_VITAL); + msg_pack_string(name, 32); + msg_pack_string(password, 32); + msg_pack_end(); + client_send_msg(); +} + +void client_rcon(const char *cmd) +{ + msg_pack_start_system(NETMSG_RCON_CMD, MSGFLAG_VITAL); + msg_pack_string(cmd, 256); + msg_pack_end(); + client_send_msg(); +} + +int client_connection_problems() +{ + return m_NetClient.GotProblems(); +} + +void client_direct_input(int *input, int size) +{ + int i; + msg_pack_start_system(NETMSG_INPUT, 0); + msg_pack_int(ack_game_tick); + msg_pack_int(current_predtick); + msg_pack_int(size); + + for(i = 0; i < size/4; i++) + msg_pack_int(input[i]); + + msg_pack_end(); + client_send_msg(); +} + + +static void client_send_input() +{ + int64 now = time_get(); + int i, size; + + if(current_predtick <= 0) + return; + + /* fetch input */ + size = modc_snap_input(inputs[current_input].data); + + if(!size) + return; + + /* pack input */ + msg_pack_start_system(NETMSG_INPUT, MSGFLAG_FLUSH); + msg_pack_int(ack_game_tick); + msg_pack_int(current_predtick); + msg_pack_int(size); + + inputs[current_input].tick = current_predtick; + inputs[current_input].predicted_time = st_get(&predicted_time, now); + inputs[current_input].time = now; + + /* pack it */ + for(i = 0; i < size/4; i++) + msg_pack_int(inputs[current_input].data[i]); + + current_input++; + current_input%=200; + + msg_pack_end(); + client_send_msg(); +} + +const char *client_latestversion() +{ + return versionstr; +} + +/* TODO: OPT: do this alot smarter! */ +int *client_get_input(int tick) +{ + int i; + int best = -1; + for(i = 0; i < 200; i++) + { + if(inputs[i].tick <= tick && (best == -1 || inputs[best].tick < inputs[i].tick)) + best = i; + } + + if(best != -1) + return (int *)inputs[best].data; + return 0; +} + +/* ------ state handling ----- */ +static int state = CLIENTSTATE_OFFLINE; +int client_state() { return state; } +static void client_set_state(int s) +{ + int old = state; + if(config.debug) + dbg_msg("client", "state change. last=%d current=%d", state, s); + state = s; + if(old != s) + modc_statechange(state, old); +} + +/* called when the map is loaded and we should init for a new round */ +static void client_on_enter_game() +{ + /* reset input */ + int i; + for(i = 0; i < 200; i++) + inputs[i].tick = -1; + current_input = 0; + + /* reset snapshots */ + snapshots[SNAP_CURRENT] = 0; + snapshots[SNAP_PREV] = 0; + snapshot_storage.PurgeAll(); + recived_snapshots = 0; + snapshot_parts = 0; + current_predtick = 0; + current_recv_tick = 0; +} + +void client_entergame() +{ + if(state == CLIENTSTATE_DEMOPLAYBACK) + return; + + /* now we will wait for two snapshots */ + /* to finish the connection */ + client_send_entergame(); + client_on_enter_game(); +} + +void client_connect(const char *server_address_str) +{ + char buf[512]; + const char *port_str = 0; + int k; + int port = 8303; + + client_disconnect(); + + dbg_msg("client", "connecting to '%s'", server_address_str); + + //client_serverinfo_request(); + str_copy(buf, server_address_str, sizeof(buf)); + + for(k = 0; buf[k]; k++) + { + if(buf[k] == ':') + { + port_str = &(buf[k+1]); + buf[k] = 0; + break; + } + } + + if(port_str) + port = atoi(port_str); + + /* TODO: IPv6 support */ + if(net_host_lookup(buf, &server_address, NETTYPE_IPV4) != 0) + dbg_msg("client", "could not find the address of %s, connecting to localhost", buf); + + rcon_authed = 0; + server_address.port = port; + m_NetClient.Connect(&server_address); + client_set_state(CLIENTSTATE_CONNECTING); + + graph_init(&inputtime_margin_graph, -150.0f, 150.0f); + graph_init(&gametime_margin_graph, -150.0f, 150.0f); +} + +void client_disconnect_with_reason(const char *reason) +{ + /* stop demo playback */ + demorec_playback_stop(); + + /* */ + rcon_authed = 0; + m_NetClient.Disconnect(reason); + client_set_state(CLIENTSTATE_OFFLINE); + map_unload(); + + /* disable all downloads */ + mapdownload_chunk = 0; + if(mapdownload_file) + io_close(mapdownload_file); + mapdownload_file = 0; + mapdownload_crc = 0; + mapdownload_totalsize = -1; + mapdownload_amount = 0; + + /* clear the current server info */ + mem_zero(¤t_server_info, sizeof(current_server_info)); + mem_zero(&server_address, sizeof(server_address)); + + /* clear snapshots */ + snapshots[SNAP_CURRENT] = 0; + snapshots[SNAP_PREV] = 0; + recived_snapshots = 0; +} + +void client_disconnect() +{ + client_disconnect_with_reason(0); +} + + +void client_serverinfo(SERVER_INFO *serverinfo) +{ + mem_copy(serverinfo, ¤t_server_info, sizeof(current_server_info)); +} + +void client_serverinfo_request() +{ + mem_zero(¤t_server_info, sizeof(current_server_info)); + current_server_info_requesttime = 0; +} + +static int client_load_data() +{ + debug_font = Graphics()->LoadTexture("debug_font.png", IMG_AUTO, TEXLOAD_NORESAMPLE); + return 1; +} + +extern int snapshot_data_rate[0xffff]; +extern int snapshot_data_updates[0xffff]; + +const char *modc_getitemname(int type); + +static void client_debug_render() +{ + static NETSTATS prev, current; + static int64 last_snap = 0; + static float frametime_avg = 0; + int64 now = time_get(); + char buffer[512]; + + if(!config.debug) + return; + + //m_pGraphics->BlendNormal(); + Graphics()->TextureSet(debug_font); + Graphics()->MapScreen(0,0,Graphics()->ScreenWidth(),Graphics()->ScreenHeight()); + + if(time_get()-last_snap > time_freq()) + { + last_snap = time_get(); + prev = current; + net_stats(¤t); + } + + /* + eth = 14 + ip = 20 + udp = 8 + total = 42 + */ + frametime_avg = frametime_avg*0.9f + frametime*0.1f; + str_format(buffer, sizeof(buffer), "ticks: %8d %8d mem %dk %d gfxmem: N/A fps: %3d", + current_tick, current_predtick, + mem_stats()->allocated/1024, + mem_stats()->total_allocations, + /*gfx_memory_usage()/1024, */ // TODO: Refactor: Reenable this + (int)(1.0f/frametime_avg)); + Graphics()->QuadsText(2, 2, 16, 1,1,1,1, buffer); + + + { + int send_packets = (current.sent_packets-prev.sent_packets); + int send_bytes = (current.sent_bytes-prev.sent_bytes); + int send_total = send_bytes + send_packets*42; + int recv_packets = (current.recv_packets-prev.recv_packets); + int recv_bytes = (current.recv_bytes-prev.recv_bytes); + int recv_total = recv_bytes + recv_packets*42; + + if(!send_packets) send_packets++; + if(!recv_packets) recv_packets++; + str_format(buffer, sizeof(buffer), "send: %3d %5d+%4d=%5d (%3d kbps) avg: %5d\nrecv: %3d %5d+%4d=%5d (%3d kbps) avg: %5d", + send_packets, send_bytes, send_packets*42, send_total, (send_total*8)/1024, send_bytes/send_packets, + recv_packets, recv_bytes, recv_packets*42, recv_total, (recv_total*8)/1024, recv_bytes/recv_packets); + Graphics()->QuadsText(2, 14, 16, 1,1,1,1, buffer); + } + + /* render rates */ + { + int y = 0; + int i; + for(i = 0; i < 256; i++) + { + if(snapshot_data_rate[i]) + { + str_format(buffer, sizeof(buffer), "%4d %20s: %8d %8d %8d", i, modc_getitemname(i), snapshot_data_rate[i]/8, snapshot_data_updates[i], + (snapshot_data_rate[i]/snapshot_data_updates[i])/8); + Graphics()->QuadsText(2, 100+y*12, 16, 1,1,1,1, buffer); + y++; + } + } + } + + str_format(buffer, sizeof(buffer), "pred: %d ms %3.2f", + (int)((st_get(&predicted_time, now)-st_get(&game_time, now))*1000/(float)time_freq()), + predicted_time.adjustspeed[1]); + Graphics()->QuadsText(2, 70, 16, 1,1,1,1, buffer); + + /* render graphs */ + if(config.dbg_graphs) + { + //Graphics()->MapScreen(0,0,400.0f,300.0f); + float w = Graphics()->ScreenWidth()/4.0f; + float h = Graphics()->ScreenHeight()/6.0f; + float sp = Graphics()->ScreenWidth()/100.0f; + float x = Graphics()->ScreenWidth()-w-sp; + + graph_scale_max(&fps_graph); + graph_scale_min(&fps_graph); + graph_render(&fps_graph, x, sp*5, w, h, "FPS"); + graph_render(&inputtime_margin_graph, x, sp*5+h+sp, w, h, "Prediction Margin"); + graph_render(&gametime_margin_graph, x, sp*5+h+sp+h+sp, w, h, "Gametime Margin"); + } +} + +void client_quit() +{ + client_set_state(CLIENTSTATE_QUITING); +} + +const char *client_error_string() +{ + return m_NetClient.ErrorString(); +} + +static void client_render() +{ + if(config.gfx_clear) + Graphics()->Clear(1,1,0); + + modc_render(); + client_debug_render(); +} + +static const char *client_load_map(const char *name, const char *filename, int wanted_crc) +{ + static char errormsg[128]; + DATAFILE *df; + int crc; + + client_set_state(CLIENTSTATE_LOADING); + + df = datafile_load(filename); + if(!df) + { + str_format(errormsg, sizeof(errormsg), "map '%s' not found", filename); + return errormsg; + } + + /* get the crc of the map */ + crc = datafile_crc(filename); + if(crc != wanted_crc) + { + datafile_unload(df); + str_format(errormsg, sizeof(errormsg), "map differs from the server. %08x != %08x", crc, wanted_crc); + return errormsg; + } + + // stop demo recording if we loaded a new map + demorec_record_stop(); + + dbg_msg("client", "loaded map '%s'", filename); + recived_snapshots = 0; + map_set(df); + + str_copy(current_map, name, sizeof(current_map)); + current_map_crc = crc; + + return NULL; +} + +static const char *client_load_map_search(const char *mapname, int wanted_crc) +{ + const char *error = 0; + char buf[512]; + dbg_msg("client", "loading map, map=%s wanted crc=%08x", mapname, wanted_crc); + client_set_state(CLIENTSTATE_LOADING); + + /* try the normal maps folder */ + str_format(buf, sizeof(buf), "maps/%s.map", mapname); + error = client_load_map(mapname, buf, wanted_crc); + if(!error) + return error; + + /* try the downloaded maps */ + str_format(buf, sizeof(buf), "downloadedmaps/%s_%8x.map", mapname, wanted_crc); + error = client_load_map(mapname, buf, wanted_crc); + return error; +} + +static int player_score_comp(const void *a, const void *b) +{ + SERVER_INFO_PLAYER *p0 = (SERVER_INFO_PLAYER *)a; + SERVER_INFO_PLAYER *p1 = (SERVER_INFO_PLAYER *)b; + if(p0->score == p1->score) + return 0; + if(p0->score < p1->score) + return 1; + return -1; +} + +static void client_process_packet(CNetChunk *pPacket) +{ + if(pPacket->m_ClientID == -1) + { + /* connectionlesss */ + if(pPacket->m_DataSize == (int)(sizeof(VERSIONSRV_VERSION) + sizeof(VERSION_DATA)) && + memcmp(pPacket->m_pData, VERSIONSRV_VERSION, sizeof(VERSIONSRV_VERSION)) == 0) + { + unsigned char *versiondata = (unsigned char*)pPacket->m_pData + sizeof(VERSIONSRV_VERSION); + int version_match = !memcmp(versiondata, VERSION_DATA, sizeof(VERSION_DATA)); + + dbg_msg("client/version", "version does %s (%d.%d.%d)", + version_match ? "match" : "NOT match", + versiondata[1], versiondata[2], versiondata[3]); + + /* assume version is out of date when version-data doesn't match */ + if (!version_match) + { + sprintf(versionstr, "%d.%d.%d", versiondata[1], versiondata[2], versiondata[3]); + } + } + + if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_LIST) && + memcmp(pPacket->m_pData, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)) == 0) + { + int size = pPacket->m_DataSize-sizeof(SERVERBROWSE_LIST); + int num = size/sizeof(MASTERSRV_ADDR); + MASTERSRV_ADDR *addrs = (MASTERSRV_ADDR *)((char*)pPacket->m_pData+sizeof(SERVERBROWSE_LIST)); + int i; + + for(i = 0; i < num; i++) + { + NETADDR addr; + + /* convert address */ + mem_zero(&addr, sizeof(addr)); + addr.type = NETTYPE_IPV4; + addr.ip[0] = addrs[i].ip[0]; + addr.ip[1] = addrs[i].ip[1]; + addr.ip[2] = addrs[i].ip[2]; + addr.ip[3] = addrs[i].ip[3]; + addr.port = (addrs[i].port[1]<<8) | addrs[i].port[0]; + + client_serverbrowse_set(&addr, BROWSESET_MASTER_ADD, -1, NULL); + } + } + + { + int packet_type = 0; + if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_INFO) && memcmp(pPacket->m_pData, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0) + packet_type = 2; + + if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_OLD_INFO) && memcmp(pPacket->m_pData, SERVERBROWSE_OLD_INFO, sizeof(SERVERBROWSE_OLD_INFO)) == 0) + packet_type = 1; + + if(packet_type) + { + /* we got ze info */ + CUnpacker up; + SERVER_INFO info = {0}; + int i; + int token = -1; + + up.Reset((unsigned char*)pPacket->m_pData+sizeof(SERVERBROWSE_INFO), pPacket->m_DataSize-sizeof(SERVERBROWSE_INFO)); + if(packet_type >= 2) + token = atol(up.GetString()); + str_copy(info.version, up.GetString(), sizeof(info.version)); + str_copy(info.name, up.GetString(), sizeof(info.name)); + str_copy(info.map, up.GetString(), sizeof(info.map)); + str_copy(info.gametype, up.GetString(), sizeof(info.gametype)); + info.flags = atol(up.GetString()); + info.progression = atol(up.GetString()); + info.num_players = atol(up.GetString()); + info.max_players = atol(up.GetString()); + str_format(info.address, sizeof(info.address), "%d.%d.%d.%d:%d", + pPacket->m_Address.ip[0], pPacket->m_Address.ip[1], pPacket->m_Address.ip[2], + pPacket->m_Address.ip[3], pPacket->m_Address.port); + + for(i = 0; i < info.num_players; i++) + { + str_copy(info.players[i].name, up.GetString(), sizeof(info.players[i].name)); + info.players[i].score = atol(up.GetString()); + } + + if(!up.Error()) + { + /* sort players */ + qsort(info.players, info.num_players, sizeof(*info.players), player_score_comp); + + if(net_addr_comp(&server_address, &pPacket->m_Address) == 0) + { + mem_copy(¤t_server_info, &info, sizeof(current_server_info)); + current_server_info.netaddr = server_address; + current_server_info_requesttime = -1; + } + else + { + if(packet_type == 2) + client_serverbrowse_set(&pPacket->m_Address, BROWSESET_TOKEN, token, &info); + else + client_serverbrowse_set(&pPacket->m_Address, BROWSESET_OLD_INTERNET, -1, &info); + } + } + } + } + } + else + { + int sys; + int msg = msg_unpack_start(pPacket->m_pData, pPacket->m_DataSize, &sys); + + if(sys) + { + /* system message */ + if(msg == NETMSG_MAP_CHANGE) + { + const char *map = msg_unpack_string(); + int map_crc = msg_unpack_int(); + const char *error = 0; + int i; + + if(msg_unpack_error()) + return; + + for(i = 0; map[i]; i++) /* protect the player from nasty map names */ + { + if(map[i] == '/' || map[i] == '\\') + error = "strange character in map name"; + } + + if(error) + client_disconnect_with_reason(error); + else + { + error = client_load_map_search(map, map_crc); + + if(!error) + { + dbg_msg("client/network", "loading done"); + client_send_ready(); + modc_connected(); + } + else + { + str_format(mapdownload_filename, sizeof(mapdownload_filename), "downloadedmaps/%s_%08x.map", map, map_crc); + + dbg_msg("client/network", "starting to download map to '%s'", mapdownload_filename); + + mapdownload_chunk = 0; + str_copy(mapdownload_name, map, sizeof(mapdownload_name)); + mapdownload_file = engine_openfile(mapdownload_filename, IOFLAG_WRITE); + mapdownload_crc = map_crc; + mapdownload_totalsize = -1; + mapdownload_amount = 0; + + msg_pack_start_system(NETMSG_REQUEST_MAP_DATA, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_int(mapdownload_chunk); + msg_pack_end(); + client_send_msg(); + + if(config.debug) + dbg_msg("client/network", "requested chunk %d", mapdownload_chunk); + } + } + } + else if(msg == NETMSG_MAP_DATA) + { + int last = msg_unpack_int(); + int total_size = msg_unpack_int(); + int size = msg_unpack_int(); + const unsigned char *data = msg_unpack_raw(size); + + /* check fior errors */ + if(msg_unpack_error() || size <= 0 || total_size <= 0 || !mapdownload_file) + return; + + io_write(mapdownload_file, data, size); + + mapdownload_totalsize = total_size; + mapdownload_amount += size; + + if(last) + { + const char *error; + dbg_msg("client/network", "download complete, loading map"); + + io_close(mapdownload_file); + mapdownload_file = 0; + mapdownload_amount = 0; + mapdownload_totalsize = -1; + + /* load map */ + error = client_load_map(mapdownload_name, mapdownload_filename, mapdownload_crc); + if(!error) + { + dbg_msg("client/network", "loading done"); + client_send_ready(); + modc_connected(); + } + else + client_disconnect_with_reason(error); + } + else + { + /* request new chunk */ + mapdownload_chunk++; + msg_pack_start_system(NETMSG_REQUEST_MAP_DATA, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_int(mapdownload_chunk); + msg_pack_end(); + client_send_msg(); + + if(config.debug) + dbg_msg("client/network", "requested chunk %d", mapdownload_chunk); + } + } + else if(msg == NETMSG_PING) + { + msg_pack_start_system(NETMSG_PING_REPLY, 0); + msg_pack_end(); + client_send_msg(); + } + else if(msg == NETMSG_RCON_AUTH_STATUS) + { + int result = msg_unpack_int(); + if(msg_unpack_error() == 0) + rcon_authed = result; + } + else if(msg == NETMSG_RCON_LINE) + { + const char *line = msg_unpack_string(); + if(msg_unpack_error() == 0) + { + /*dbg_msg("remote", "%s", line);*/ + modc_rcon_line(line); + } + } + else if(msg == NETMSG_PING_REPLY) + dbg_msg("client/network", "latency %.2f", (time_get() - ping_start_time)*1000 / (float)time_freq()); + else if(msg == NETMSG_INPUTTIMING) + { + int input_predtick = msg_unpack_int(); + int time_left = msg_unpack_int(); + + /* adjust our prediction time */ + int k; + int64 target = 0; + for(k = 0; k < 200; k++) + { + if(inputs[k].tick == input_predtick) + { + target = inputs[k].predicted_time + (time_get() - inputs[k].time); + target = target - (int64)(((time_left-prediction_margin)/1000.0f)*time_freq()); + //st_update(&predicted_time, ); + break; + } + } + + if(target) + st_update(&predicted_time, &inputtime_margin_graph, target, time_left, 1); + } + else if(msg == NETMSG_SNAP || msg == NETMSG_SNAPSINGLE || msg == NETMSG_SNAPEMPTY) + { + /*dbg_msg("client/network", "got snapshot"); */ + int num_parts = 1; + int part = 0; + int game_tick = msg_unpack_int(); + int delta_tick = game_tick-msg_unpack_int(); + int part_size = 0; + int crc = 0; + int complete_size = 0; + const char *data = 0; + + /* we are not allowed to process snapshot yet */ + if(client_state() < CLIENTSTATE_LOADING) + return; + + if(msg == NETMSG_SNAP) + { + num_parts = msg_unpack_int(); + part = msg_unpack_int(); + } + + if(msg != NETMSG_SNAPEMPTY) + { + crc = msg_unpack_int(); + part_size = msg_unpack_int(); + } + + data = (const char *)msg_unpack_raw(part_size); + + if(msg_unpack_error()) + return; + + if(game_tick >= current_recv_tick) + { + if(game_tick != current_recv_tick) + { + snapshot_parts = 0; + current_recv_tick = game_tick; + } + + /* TODO: clean this up abit */ + mem_copy((char*)snapshot_incomming_data + part*MAX_SNAPSHOT_PACKSIZE, data, part_size); + snapshot_parts |= 1<= 0) + { + int deltashot_size = snapshot_storage.Get(delta_tick, 0, &deltashot, 0); + + if(deltashot_size < 0) + { + /* couldn't find the delta snapshots that the server used */ + /* to compress this snapshot. force the server to resync */ + if(config.debug) + dbg_msg("client", "error, couldn't find the delta snapshot"); + + /* ack snapshot */ + /* TODO: combine this with the input message */ + ack_game_tick = -1; + return; + } + } + + /* decompress snapshot */ + deltadata = CSnapshot::EmptyDelta(); + deltasize = sizeof(int)*3; + + if(complete_size) + { + int intsize = intpack_decompress(snapshot_incomming_data, complete_size, tmpbuffer2); + + if(intsize < 0) /* failure during decompression, bail */ + return; + + deltadata = tmpbuffer2; + deltasize = intsize; + } + + /* unpack delta */ + purgetick = delta_tick; + snapsize = CSnapshot::UnpackDelta(deltashot, (CSnapshot*)tmpbuffer3, deltadata, deltasize); + if(snapsize < 0) + { + dbg_msg("client", "delta unpack failed!"); + return; + } + + if(msg != NETMSG_SNAPEMPTY && ((CSnapshot*)tmpbuffer3)->Crc() != crc) + { + if(config.debug) + { + dbg_msg("client", "snapshot crc error #%d - tick=%d wantedcrc=%d gotcrc=%d compressed_size=%d delta_tick=%d", + snapcrcerrors, game_tick, crc, ((CSnapshot*)tmpbuffer3)->Crc(), complete_size, delta_tick); + } + + snapcrcerrors++; + if(snapcrcerrors > 10) + { + /* to many errors, send reset */ + ack_game_tick = -1; + client_send_input(); + snapcrcerrors = 0; + } + return; + } + else + { + if(snapcrcerrors) + snapcrcerrors--; + } + + /* purge old snapshots */ + purgetick = delta_tick; + if(snapshots[SNAP_PREV] && snapshots[SNAP_PREV]->m_Tick < purgetick) + purgetick = snapshots[SNAP_PREV]->m_Tick; + if(snapshots[SNAP_CURRENT] && snapshots[SNAP_CURRENT]->m_Tick < purgetick) + purgetick = snapshots[SNAP_PREV]->m_Tick; + snapshot_storage.PurgeUntil(purgetick); + + /* add new */ + snapshot_storage.Add(game_tick, time_get(), snapsize, (CSnapshot*)tmpbuffer3, 1); + + /* add snapshot to demo */ + if(demorec_isrecording()) + { + + /* write tick marker */ + /* + DEMOREC_TICKMARKER marker; + marker.tick = game_tick; + swap_endian(&marker, sizeof(int), sizeof(marker)/sizeof(int)); + demorec_record_write("TICK", sizeof(marker), &marker); + demorec_record_write("SNAP", snapsize, tmpbuffer3); + */ + + /* write snapshot */ + demorec_record_snapshot(game_tick, tmpbuffer3, snapsize); + } + + /* apply snapshot, cycle pointers */ + recived_snapshots++; + + current_recv_tick = game_tick; + + /* we got two snapshots until we see us self as connected */ + if(recived_snapshots == 2) + { + /* start at 200ms and work from there */ + st_init(&predicted_time, game_tick*time_freq()/50); + predicted_time.adjustspeed[1] = 1000.0f; + st_init(&game_time, (game_tick-1)*time_freq()/50); + snapshots[SNAP_PREV] = snapshot_storage.m_pFirst; + snapshots[SNAP_CURRENT] = snapshot_storage.m_pLast; + local_start_time = time_get(); + client_set_state(CLIENTSTATE_ONLINE); + } + + /* adjust game time */ + { + int64 now = st_get(&game_time, time_get()); + int64 tickstart = game_tick*time_freq()/50; + int64 time_left = (tickstart-now)*1000 / time_freq(); + /*st_update(&game_time, (game_tick-1)*time_freq()/50);*/ + st_update(&game_time, &gametime_margin_graph, (game_tick-1)*time_freq()/50, time_left, 0); + } + + /* ack snapshot */ + ack_game_tick = game_tick; + } + } + } + } + else + { + /* game message */ + if(demorec_isrecording()) + demorec_record_message(pPacket->m_pData, pPacket->m_DataSize); + /* demorec_record_write("MESG", pPacket->data_size, ); */ + + modc_message(msg); + } + } +} + +int client_mapdownload_amount() { return mapdownload_amount; } +int client_mapdownload_totalsize() { return mapdownload_totalsize; } + +static void client_pump_network() +{ + + m_NetClient.Update(); + + if(client_state() != CLIENTSTATE_DEMOPLAYBACK) + { + /* check for errors */ + if(client_state() != CLIENTSTATE_OFFLINE && m_NetClient.State() == NETSTATE_OFFLINE) + { + client_set_state(CLIENTSTATE_OFFLINE); + client_disconnect(); + dbg_msg("client", "offline error='%s'", m_NetClient.ErrorString()); + } + + /* */ + if(client_state() == CLIENTSTATE_CONNECTING && m_NetClient.State() == NETSTATE_ONLINE) + { + /* we switched to online */ + dbg_msg("client", "connected, sending info"); + client_set_state(CLIENTSTATE_LOADING); + client_send_info(); + } + } + + /* process packets */ + CNetChunk Packet; + while(m_NetClient.Recv(&Packet)) + client_process_packet(&Packet); +} + +static void client_democallback_snapshot(void *pData, int Size) +{ + /* update ticks, they could have changed */ + const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); + CSnapshotStorage::CHolder *temp; + current_tick = info->current_tick; + prev_tick = info->previous_tick; + + /* handle snapshots */ + temp = snapshots[SNAP_PREV]; + snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; + snapshots[SNAP_CURRENT] = temp; + + mem_copy(snapshots[SNAP_CURRENT]->m_pSnap, pData, Size); + mem_copy(snapshots[SNAP_CURRENT]->m_pAltSnap, pData, Size); + + modc_newsnapshot(); + /*modc_predict();*/ +} + +static void client_democallback_message(void *data, int size) +{ + int sys = 0; + int msg = msg_unpack_start(data, size, &sys); + if(!sys) + modc_message(msg); +} + + +const DEMOPLAYBACK_INFO *client_demoplayer_getinfo() +{ + static DEMOPLAYBACK_INFO ret; + const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); + ret.first_tick = info->first_tick; + ret.last_tick = info->last_tick; + ret.current_tick = info->current_tick; + ret.paused = info->paused; + ret.speed = info->speed; + return &ret; +} + +void client_demoplayer_setpos(float percent) +{ + demorec_playback_set(percent); +} + +void client_demoplayer_setspeed(float speed) +{ + demorec_playback_setspeed(speed); +} + +void client_demoplayer_setpause(int paused) +{ + if(paused) + demorec_playback_pause(); + else + demorec_playback_unpause(); +} + +static void client_update() +{ + if(client_state() == CLIENTSTATE_DEMOPLAYBACK) + { + demorec_playback_update(); + if(demorec_isplaying()) + { + /* update timers */ + const DEMOREC_PLAYBACKINFO *info = demorec_playback_info(); + current_tick = info->current_tick; + prev_tick = info->previous_tick; + intratick = info->intratick; + ticktime = info->ticktime; + } + else + { + /* disconnect on error */ + client_disconnect(); + } + } + else if(client_state() != CLIENTSTATE_OFFLINE && recived_snapshots >= 3) + { + /* switch snapshot */ + int repredict = 0; + int64 freq = time_freq(); + int64 now = st_get(&game_time, time_get()); + int64 pred_now = st_get(&predicted_time, time_get()); + + while(1) + { + CSnapshotStorage::CHolder *cur = snapshots[SNAP_CURRENT]; + int64 tickstart = (cur->m_Tick)*time_freq()/50; + + if(tickstart < now) + { + CSnapshotStorage::CHolder *next = snapshots[SNAP_CURRENT]->m_pNext; + if(next) + { + snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; + snapshots[SNAP_CURRENT] = next; + + /* set ticks */ + current_tick = snapshots[SNAP_CURRENT]->m_Tick; + prev_tick = snapshots[SNAP_PREV]->m_Tick; + + if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV]) + { + modc_newsnapshot(); + repredict = 1; + } + } + else + break; + } + else + break; + } + + if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV]) + { + int64 curtick_start = (snapshots[SNAP_CURRENT]->m_Tick)*time_freq()/50; + int64 prevtick_start = (snapshots[SNAP_PREV]->m_Tick)*time_freq()/50; + /*tg_add(&predicted_time_graph, pred_now, 0); */ + int prev_pred_tick = (int)(pred_now*50/time_freq()); + int new_pred_tick = prev_pred_tick+1; + static float last_predintra = 0; + + intratick = (now - prevtick_start) / (float)(curtick_start-prevtick_start); + ticktime = (now - prevtick_start) / (float)freq; /*(float)SERVER_TICK_SPEED);*/ + + curtick_start = new_pred_tick*time_freq()/50; + prevtick_start = prev_pred_tick*time_freq()/50; + predintratick = (pred_now - prevtick_start) / (float)(curtick_start-prevtick_start); + + if(new_pred_tick < snapshots[SNAP_PREV]->m_Tick-SERVER_TICK_SPEED || new_pred_tick > snapshots[SNAP_PREV]->m_Tick+SERVER_TICK_SPEED) + { + dbg_msg("client", "prediction time reset!"); + st_init(&predicted_time, snapshots[SNAP_CURRENT]->m_Tick*time_freq()/50); + } + + if(new_pred_tick > current_predtick) + { + last_predintra = predintratick; + current_predtick = new_pred_tick; + repredict = 1; + + /* send input */ + client_send_input(); + } + + last_predintra = predintratick; + } + + /* only do sane predictions */ + if(repredict) + { + if(current_predtick > current_tick && current_predtick < current_tick+50) + modc_predict(); + } + + /* fetch server info if we don't have it */ + if(client_state() >= CLIENTSTATE_LOADING && + current_server_info_requesttime >= 0 && + time_get() > current_server_info_requesttime) + { + client_serverbrowse_request(&server_address); + current_server_info_requesttime = time_get()+time_freq()*2; + } + } + + /* STRESS TEST: join the server again */ + if(config.dbg_stress) + { + static int64 action_taken = 0; + int64 now = time_get(); + if(client_state() == CLIENTSTATE_OFFLINE) + { + if(now > action_taken+time_freq()*2) + { + dbg_msg("stress", "reconnecting!"); + client_connect(config.dbg_stress_server); + action_taken = now; + } + } + else + { + /*if(now > action_taken+time_freq()*(10+config.dbg_stress)) + { + dbg_msg("stress", "disconnecting!"); + client_disconnect(); + action_taken = now; + }*/ + } + } + + /* pump the network */ + client_pump_network(); + + /* update the maser server registry */ + mastersrv_update(); + + /* update the server browser */ + client_serverbrowse_update(); +} + + +static void client_versionupdate() +{ + static int state = 0; + static HOSTLOOKUP version_serveraddr; + + if(state == 0) + { + engine_hostlookup(&version_serveraddr, config.cl_version_server); + state++; + } + else if(state == 1) + { + if(jobs_status(&version_serveraddr.job) == JOBSTATUS_DONE) + { + CNetChunk Packet; + + mem_zero(&Packet, sizeof(Packet)); + + version_serveraddr.addr.port = VERSIONSRV_PORT; + + Packet.m_ClientID = -1; + Packet.m_Address = version_serveraddr.addr; + Packet.m_pData = VERSIONSRV_GETVERSION; + Packet.m_DataSize = sizeof(VERSIONSRV_GETVERSION); + Packet.m_Flags = NETSENDFLAG_CONNLESS; + + m_NetClient.Send(&Packet); + state++; + } + } +} + +static void client_run() +{ + NETADDR bindaddr; + int64 reporttime = time_get(); + int64 reportinterval = time_freq()*1; + + static PERFORMACE_INFO rootscope = {"root", 0}; + perf_start(&rootscope); + + local_start_time = time_get(); + snapshot_parts = 0; + + /* init graphics and sound */ + m_pGraphics = CreateEngineGraphics(); + if(m_pGraphics->Init() != 0) + return; + + /* start refreshing addresses while we load */ + mastersrv_refresh_addresses(); + + /* init the editor */ + m_pEditor = CreateEditor(); + m_pEditor->Init(m_pGraphics); + + /* sound is allowed to fail */ + snd_init(); + + /* load data */ + if(!client_load_data()) + return; + + /* init the mod */ + m_pGameClient = CreateGameClient(&m_Client); + modc_init(); + dbg_msg("client", "version %s", modc_net_version()); + + /* open socket */ + mem_zero(&bindaddr, sizeof(bindaddr)); + m_NetClient.Open(bindaddr, 0); + + /* connect to the server if wanted */ + /* + if(config.cl_connect[0] != 0) + client_connect(config.cl_connect); + config.cl_connect[0] = 0; + */ + + /* */ + graph_init(&fps_graph, 0.0f, 200.0f); + + /* never start with the editor */ + config.cl_editor = 0; + + inp_mouse_mode_relative(); + + while (1) + { + static PERFORMACE_INFO rootscope = {"root", 0}; + int64 frame_start_time = time_get(); + frames++; + + perf_start(&rootscope); + + /* */ + client_versionupdate(); + + /* handle pending connects */ + if(cmd_connect[0]) + { + client_connect(cmd_connect); + cmd_connect[0] = 0; + } + + /* update input */ + { + static PERFORMACE_INFO scope = {"inp_update", 0}; + perf_start(&scope); + inp_update(); + perf_end(); + } + + /* update sound */ + { + static PERFORMACE_INFO scope = {"snd_update", 0}; + perf_start(&scope); + snd_update(); + perf_end(); + } + + /* release focus */ + if(!Graphics()->WindowActive()) + { + if(window_must_refocus == 0) + inp_mouse_mode_absolute(); + window_must_refocus = 1; + } + else if (config.dbg_focus && inp_key_pressed(KEY_ESCAPE)) + { + inp_mouse_mode_absolute(); + window_must_refocus = 1; + } + + /* refocus */ + if(window_must_refocus && Graphics()->WindowActive()) + { + if(window_must_refocus < 3) + { + inp_mouse_mode_absolute(); + window_must_refocus++; + } + + if(inp_key_pressed(KEY_MOUSE_1)) + { + inp_mouse_mode_relative(); + window_must_refocus = 0; + } + } + + /* panic quit button */ + if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_pressed('q')) + break; + + if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('d')) + config.debug ^= 1; + + if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('g')) + config.dbg_graphs ^= 1; + + if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('e')) + { + config.cl_editor = config.cl_editor^1; + inp_mouse_mode_relative(); + } + + /* + if(!gfx_window_open()) + break; + */ + + /* render */ + if(config.cl_editor) + { + client_update(); + m_pEditor->UpdateAndRender(); + m_pGraphics->Swap(); + } + else + { + { + static PERFORMACE_INFO scope = {"client_update", 0}; + perf_start(&scope); + client_update(); + perf_end(); + } + + if(config.dbg_stress) + { + if((frames%10) == 0) + { + client_render(); + m_pGraphics->Swap(); + } + } + else + { + { + static PERFORMACE_INFO scope = {"client_render", 0}; + perf_start(&scope); + client_render(); + perf_end(); + } + + { + static PERFORMACE_INFO scope = {"gfx_swap", 0}; + perf_start(&scope); + m_pGraphics->Swap(); + perf_end(); + } + } + } + + perf_end(); + + + /* check conditions */ + if(client_state() == CLIENTSTATE_QUITING) + break; + + /* be nice */ + if(config.dbg_stress) + thread_sleep(5); + else if(config.cl_cpu_throttle || !Graphics()->WindowActive()) + thread_sleep(1); + + if(config.dbg_hitch) + { + thread_sleep(config.dbg_hitch); + config.dbg_hitch = 0; + } + + if(reporttime < time_get()) + { + if(0 && config.debug) + { + dbg_msg("client/report", "fps=%.02f (%.02f %.02f) netstate=%d", + frames/(float)(reportinterval/time_freq()), + 1.0f/frametime_high, + 1.0f/frametime_low, + m_NetClient.State()); + } + frametime_low = 1; + frametime_high = 0; + frames = 0; + reporttime += reportinterval; + perf_next(); + + if(config.dbg_pref) + perf_dump(&rootscope); + } + + /* update frametime */ + frametime = (time_get()-frame_start_time)/(float)time_freq(); + if(frametime < frametime_low) + frametime_low = frametime; + if(frametime > frametime_high) + frametime_high = frametime; + + graph_add(&fps_graph, 1.0f/frametime, 1,1,1); + } + + modc_shutdown(); + client_disconnect(); + + m_pGraphics->Shutdown(); + snd_shutdown(); +} + +void gfx_swap() +{ + m_pGraphics->Swap(); +} + +static void con_connect(void *result, void *user_data) +{ + str_copy(cmd_connect, console_arg_string(result, 0), sizeof(cmd_connect)); +} + +static void con_disconnect(void *result, void *user_data) +{ + client_disconnect(); +} + +static void con_quit(void *result, void *user_data) +{ + client_quit(); +} + +static void con_ping(void *result, void *user_data) +{ + msg_pack_start_system(NETMSG_PING, 0); + msg_pack_end(); + client_send_msg(); + ping_start_time = time_get(); +} + +static void con_screenshot(void *result, void *user_data) +{ + Graphics()->TakeScreenshot(); +} + +static void con_rcon(void *result, void *user_data) +{ + client_rcon(console_arg_string(result, 0)); +} + +static void con_rcon_auth(void *result, void *user_data) +{ + client_rcon_auth("", console_arg_string(result, 0)); +} + +static void con_addfavorite(void *result, void *user_data) +{ + NETADDR addr; + if(net_addr_from_str(&addr, console_arg_string(result, 0)) == 0) + client_serverbrowse_addfavorite(addr); +} + +const char *client_demoplayer_play(const char *filename) +{ + int crc; + const char *error; + client_disconnect(); + m_NetClient.ResetErrorString(); + + /* try to start playback */ + demorec_playback_registercallbacks(client_democallback_snapshot, client_democallback_message); + + if(demorec_playback_load(filename)) + return "error loading demo"; + + /* load map */ + crc = (demorec_playback_info()->header.crc[0]<<24)| + (demorec_playback_info()->header.crc[1]<<16)| + (demorec_playback_info()->header.crc[2]<<8)| + (demorec_playback_info()->header.crc[3]); + error = client_load_map_search(demorec_playback_info()->header.map, crc); + if(error) + { + client_disconnect_with_reason(error); + return error; + } + + modc_connected(); + + /* setup buffers */ + mem_zero(demorec_snapshotdata, sizeof(demorec_snapshotdata)); + + snapshots[SNAP_CURRENT] = &demorec_snapshotholders[SNAP_CURRENT]; + snapshots[SNAP_PREV] = &demorec_snapshotholders[SNAP_PREV]; + + snapshots[SNAP_CURRENT]->m_pSnap = (CSnapshot *)demorec_snapshotdata[SNAP_CURRENT][0]; + snapshots[SNAP_CURRENT]->m_pAltSnap = (CSnapshot *)demorec_snapshotdata[SNAP_CURRENT][1]; + snapshots[SNAP_CURRENT]->m_SnapSize = 0; + snapshots[SNAP_CURRENT]->m_Tick = -1; + + snapshots[SNAP_PREV]->m_pSnap = (CSnapshot *)demorec_snapshotdata[SNAP_PREV][0]; + snapshots[SNAP_PREV]->m_pAltSnap = (CSnapshot *)demorec_snapshotdata[SNAP_PREV][1]; + snapshots[SNAP_PREV]->m_SnapSize = 0; + snapshots[SNAP_PREV]->m_Tick = -1; + + /* enter demo playback state */ + client_set_state(CLIENTSTATE_DEMOPLAYBACK); + + demorec_playback_play(); + modc_entergame(); + + return 0; +} + +static void con_play(void *result, void *user_data) +{ + client_demoplayer_play(console_arg_string(result, 0)); +} + +static void con_record(void *result, void *user_data) +{ + if(state != CLIENTSTATE_ONLINE) + dbg_msg("demorec/record", "client is not online"); + else + { + char filename[512]; + str_format(filename, sizeof(filename), "demos/%s.demo", console_arg_string(result, 0)); + demorec_record_start(filename, modc_net_version(), current_map, current_map_crc, "client"); + } +} + +static void con_stoprecord(void *result, void *user_data) +{ + demorec_record_stop(); +} + +static void con_serverdummy(void *result, void *user_data) +{ + dbg_msg("client", "this command is not available on the client"); +} + +static void client_register_commands() +{ + MACRO_REGISTER_COMMAND("quit", "", CFGFLAG_CLIENT, con_quit, 0x0, "Quit Teeworlds"); + MACRO_REGISTER_COMMAND("exit", "", CFGFLAG_CLIENT, con_quit, 0x0, "Quit Teeworlds"); + MACRO_REGISTER_COMMAND("connect", "s", CFGFLAG_CLIENT, con_connect, 0x0, "Connect to the specified host/ip"); + MACRO_REGISTER_COMMAND("disconnect", "", CFGFLAG_CLIENT, con_disconnect, 0x0, "Disconnect from the server"); + MACRO_REGISTER_COMMAND("ping", "", CFGFLAG_CLIENT, con_ping, 0x0, "Ping the current server"); + MACRO_REGISTER_COMMAND("screenshot", "", CFGFLAG_CLIENT, con_screenshot, 0x0, "Take a screenshot"); + MACRO_REGISTER_COMMAND("rcon", "r", CFGFLAG_CLIENT, con_rcon, 0x0, "Send specified command to rcon"); + MACRO_REGISTER_COMMAND("rcon_auth", "s", CFGFLAG_CLIENT, con_rcon_auth, 0x0, "Authenticate to rcon"); + + MACRO_REGISTER_COMMAND("play", "r", CFGFLAG_CLIENT, con_play, 0x0, "Play the file specified"); + MACRO_REGISTER_COMMAND("record", "s", CFGFLAG_CLIENT, con_record, 0, "Record to the file"); + MACRO_REGISTER_COMMAND("stoprecord", "", CFGFLAG_CLIENT, con_stoprecord, 0, "Stop recording"); + + MACRO_REGISTER_COMMAND("add_favorite", "s", CFGFLAG_CLIENT, con_addfavorite, 0x0, "Add a server as a favorite"); + + /* register server dummy commands for tab completion */ + MACRO_REGISTER_COMMAND("kick", "i", CFGFLAG_SERVER, con_serverdummy, 0, "Kick player with specified id"); + MACRO_REGISTER_COMMAND("ban", "s?i", CFGFLAG_SERVER, con_serverdummy, 0, "Ban player with ip/id for x minutes"); + MACRO_REGISTER_COMMAND("unban", "s", CFGFLAG_SERVER, con_serverdummy, 0, "Unban ip"); + MACRO_REGISTER_COMMAND("bans", "", CFGFLAG_SERVER, con_serverdummy, 0, "Show banlist"); + MACRO_REGISTER_COMMAND("status", "", CFGFLAG_SERVER, con_serverdummy, 0, "List players"); + MACRO_REGISTER_COMMAND("shutdown", "", CFGFLAG_SERVER, con_serverdummy, 0, "Shut down"); + /*MACRO_REGISTER_COMMAND("record", "", CFGFLAG_SERVER, con_serverdummy, 0); + MACRO_REGISTER_COMMAND("stoprecord", "", CFGFLAG_SERVER, con_serverdummy, 0);*/ +} + +void client_save_line(const char *line) +{ + engine_config_write_line(line); +} + +const char *client_user_directory() +{ + static char path[1024] = {0}; + fs_storage_path("Teeworlds", path, sizeof(path)); + return path; +} + +#if defined(CONF_PLATFORM_MACOSX) +int SDL_main(int argc, char **argv) +#else +int main(int argc, char **argv) +#endif +{ + /* init the engine */ + dbg_msg("client", "starting..."); + engine_init("Teeworlds"); + + /* register all console commands */ + client_register_commands(); + modc_console_init(); + + /* parse the command line arguments */ + engine_parse_arguments(argc, argv); + + /* execute config file */ + console_execute_file("settings.cfg"); + + /* run the client*/ + client_run(); + + /* write down the config and quit */ + if(engine_config_write_start() == 0) + { + config_save(); + client_serverbrowse_save(); + modc_save_config(); + engine_config_write_stop(); + } + + return 0; +} diff --git a/src/engine/client/ec_gfx.c b/src/engine/client/ec_gfx.c deleted file mode 100644 index 1ea9f407..00000000 --- a/src/engine/client/ec_gfx.c +++ /dev/null @@ -1,1020 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - -#include - -#include "SDL.h" - -#ifdef CONF_FAMILY_WINDOWS - #define WIN32_LEAN_AND_MEAN - #include -#endif - -#ifdef CONF_PLATFORM_MACOSX - #include - #include -#else - #include - #include -#endif - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -/* compressed textures */ -#define GL_COMPRESSED_RGB_ARB 0x84ED -#define GL_COMPRESSED_RGBA_ARB 0x84EE -#define GL_COMPRESSED_ALPHA_ARB 0x84E9 - -#define TEXTURE_MAX_ANISOTROPY_EXT 0x84FE - -enum -{ - DRAWING_QUADS=1, - DRAWING_LINES=2 -}; - -/* */ -typedef struct { float x, y, z; } VEC3; -typedef struct { float u, v; } TEXCOORD; -typedef struct { float r, g, b, a; } COLOR; - -typedef struct -{ - VEC3 pos; - TEXCOORD tex; - COLOR color; -} VERTEX; - -const int vertex_buffer_size = 32*1024; -static VERTEX *vertices = 0; -static int num_vertices = 0; - -static int no_gfx = 0; - -static COLOR color[4]; -static TEXCOORD texture[4]; - -static int do_screenshot = 0; -static int render_enable = 1; - -static int screen_width = -1; -static int screen_height = -1; -static float rotation = 0; -static int drawing = 0; - -static float screen_x0 = 0; -static float screen_y0 = 0; -static float screen_x1 = 0; -static float screen_y1 = 0; - -static int invalid_texture = 0; - -typedef struct -{ - GLuint tex; - int memsize; - int flags; - int next; -} TEXTURE; - -enum -{ - MAX_TEXTURES = 1024*4 -}; - -static TEXTURE textures[MAX_TEXTURES]; -static int first_free_texture; -static int memory_usage = 0; - -static SDL_Surface *screen_surface; - -static const unsigned char null_texture_data[] = { - 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, - 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, - 0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, - 0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, -}; - -static void flush() -{ - if(num_vertices == 0) - return; - - if(no_gfx) - { - num_vertices = 0; - return; - } - - - //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); - //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - glVertexPointer(3, GL_FLOAT, - sizeof(VERTEX), - (char*)vertices); - glTexCoordPointer(2, GL_FLOAT, - sizeof(VERTEX), - (char*)vertices + sizeof(float)*3); - glColorPointer(4, GL_FLOAT, - sizeof(VERTEX), - (char*)vertices + sizeof(float)*5); - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - - if(render_enable) - { - if(drawing == DRAWING_QUADS) - glDrawArrays(GL_QUADS, 0, num_vertices); - else if(drawing == DRAWING_LINES) - glDrawArrays(GL_LINES, 0, num_vertices); - } - - /* Reset pointer */ - num_vertices = 0; -} - -static void add_vertices(int count) -{ - num_vertices += count; - if((num_vertices + count) >= vertex_buffer_size) - flush(); -} - -static int try_init() -{ - const SDL_VideoInfo *info; - int flags = SDL_OPENGL; - - screen_width = config.gfx_screen_width; - screen_height = config.gfx_screen_height; - - info = SDL_GetVideoInfo(); - - /* set flags */ - flags = SDL_OPENGL; - flags |= SDL_GL_DOUBLEBUFFER; - flags |= SDL_HWPALETTE; - if(config.dbg_resizable) - flags |= SDL_RESIZABLE; - - if(info->hw_available) - flags |= SDL_HWSURFACE; - else - flags |= SDL_SWSURFACE; - - if(info->blit_hw) - flags |= SDL_HWACCEL; - - if(config.gfx_fullscreen) - flags |= SDL_FULLSCREEN; - - /* set gl attributes */ - if(config.gfx_fsaa_samples) - { - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, config.gfx_fsaa_samples); - } - else - { - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); - } - - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, config.gfx_vsync); - - /* set caption */ - SDL_WM_SetCaption("Teeworlds", "Teeworlds"); - - /* create window */ - screen_surface = SDL_SetVideoMode(screen_width, screen_height, 0, flags); - if(screen_surface == NULL) - { - dbg_msg("gfx", "unable to set video mode: %s", SDL_GetError()); - return -1; - } - - return 0; -} - -void gfx_font_init(); - -static int gfx_init_window() -{ - if(try_init() == 0) - return 0; - - /* try disabling fsaa */ - while(config.gfx_fsaa_samples) - { - config.gfx_fsaa_samples--; - - if(config.gfx_fsaa_samples) - dbg_msg("gfx", "lowering FSAA to %d and trying again", config.gfx_fsaa_samples); - else - dbg_msg("gfx", "disabling FSAA and trying again"); - - if(try_init() == 0) - return 0; - } - - /* try lowering the resolution */ - if(config.gfx_screen_width != 640 || config.gfx_screen_height != 480) - { - dbg_msg("gfx", "setting resolution to 640x480 and trying again"); - config.gfx_screen_width = 640; - config.gfx_screen_height = 480; - - if(try_init() == 0) - return 0; - } - - dbg_msg("gfx", "out of ideas. failed to init graphics"); - - return -1; -} - -int gfx_init() -{ - int i; - - if(config.dbg_stress) - no_gfx = 1; - - { - int systems = 0; - - if(!no_gfx) - systems |= SDL_INIT_VIDEO; - - if(config.snd_enable) - systems |= SDL_INIT_AUDIO; - - if(config.cl_eventthread) - systems |= SDL_INIT_EVENTTHREAD; - - if(SDL_Init(systems) < 0) - { - dbg_msg("gfx", "unable to init SDL: %s", SDL_GetError()); - return -1; - } - } - - atexit(SDL_Quit); - - if(!no_gfx) - { - #ifdef CONF_FAMILY_WINDOWS - if(!getenv("SDL_VIDEO_WINDOW_POS") && !getenv("SDL_VIDEO_CENTERED")) - putenv("SDL_VIDEO_WINDOW_POS=8,27"); - #endif - - if(gfx_init_window() != 0) - return -1; - } - - /* Init vertices */ - if (vertices) - mem_free(vertices); - vertices = (VERTEX*)mem_alloc(sizeof(VERTEX) * vertex_buffer_size, 1); - num_vertices = 0; - - - /* - dbg_msg("gfx", "OpenGL version %d.%d.%d", context.version_major(), - context.version_minor(), - context.version_rev());*/ - - - /* Set all z to -5.0f */ - for (i = 0; i < vertex_buffer_size; i++) - vertices[i].pos.z = -5.0f; - - /* init textures */ - first_free_texture = 0; - for(i = 0; i < MAX_TEXTURES; i++) - textures[i].next = i+1; - textures[MAX_TEXTURES-1].next = -1; - - if(!no_gfx) - { - SDL_ShowCursor(0); - gfx_mapscreen(0,0,config.gfx_screen_width, config.gfx_screen_height); - - /* set some default settings */ - glEnable(GL_BLEND); - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - glAlphaFunc(GL_GREATER, 0); - glEnable(GL_ALPHA_TEST); - glDepthMask(0); - - - //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } - - /* init input */ - inp_init(); - - /* create null texture, will get id=0 */ - invalid_texture = gfx_load_texture_raw(4,4,IMG_RGBA,null_texture_data,IMG_RGBA,TEXLOAD_NORESAMPLE); - dbg_msg("", "invalid texture id: %d %d", invalid_texture, textures[invalid_texture].tex); - - - /* font init */ - gfx_font_init(); - /* perform some tests */ - /* pixeltest_dotesting(); */ - - /*if(config.dbg_stress) - gfx_minimize();*/ - - return 0; -} - -float gfx_screenaspect() -{ - return gfx_screenwidth()/(float)gfx_screenheight(); -} - -int gfx_window_active() -{ - return SDL_GetAppState()&SDL_APPINPUTFOCUS; -} - -int gfx_window_open() -{ - return SDL_GetAppState()&SDL_APPACTIVE; -} - -VIDEO_MODE fakemodes[] = { - {320,240,8,8,8}, {400,300,8,8,8}, {640,480,8,8,8}, - {720,400,8,8,8}, {768,576,8,8,8}, {800,600,8,8,8}, - {1024,600,8,8,8}, {1024,768,8,8,8}, {1152,864,8,8,8}, - {1280,768,8,8,8}, {1280,800,8,8,8}, {1280,960,8,8,8}, - {1280,1024,8,8,8}, {1368,768,8,8,8}, {1400,1050,8,8,8}, - {1440,900,8,8,8}, {1440,1050,8,8,8}, {1600,1000,8,8,8}, - {1600,1200,8,8,8}, {1680,1050,8,8,8}, {1792,1344,8,8,8}, - {1800,1440,8,8,8}, {1856,1392,8,8,8}, {1920,1080,8,8,8}, - {1920,1200,8,8,8}, {1920,1440,8,8,8}, {1920,2400,8,8,8}, - {2048,1536,8,8,8}, - - {320,240,5,6,5}, {400,300,5,6,5}, {640,480,5,6,5}, - {720,400,5,6,5}, {768,576,5,6,5}, {800,600,5,6,5}, - {1024,600,5,6,5}, {1024,768,5,6,5}, {1152,864,5,6,5}, - {1280,768,5,6,5}, {1280,800,5,6,5}, {1280,960,5,6,5}, - {1280,1024,5,6,5}, {1368,768,5,6,5}, {1400,1050,5,6,5}, - {1440,900,5,6,5}, {1440,1050,5,6,5}, {1600,1000,5,6,5}, - {1600,1200,5,6,5}, {1680,1050,5,6,5}, {1792,1344,5,6,5}, - {1800,1440,5,6,5}, {1856,1392,5,6,5}, {1920,1080,5,6,5}, - {1920,1200,5,6,5}, {1920,1440,5,6,5}, {1920,2400,5,6,5}, - {2048,1536,5,6,5} -}; - -int gfx_get_video_modes(VIDEO_MODE *list, int maxcount) -{ - int num_modes = sizeof(fakemodes)/sizeof(VIDEO_MODE); - SDL_Rect **modes; - - if(config.gfx_display_all_modes) - { - int count = sizeof(fakemodes)/sizeof(VIDEO_MODE); - mem_copy(list, fakemodes, sizeof(fakemodes)); - if(maxcount < count) - count = maxcount; - return count; - } - - /* TODO: fix this code on osx or windows */ - - modes = SDL_ListModes(NULL, SDL_OPENGL|SDL_GL_DOUBLEBUFFER|SDL_FULLSCREEN); - if(modes == NULL) - { - /* no modes */ - num_modes = 0; - } - else if(modes == (SDL_Rect**)-1) - { - /* all modes */ - } - else - { - int i; - num_modes = 0; - for(i = 0; modes[i]; ++i) - { - if(num_modes == maxcount) - break; - list[num_modes].width = modes[i]->w; - list[num_modes].height = modes[i]->h; - list[num_modes].red = 8; - list[num_modes].green = 8; - list[num_modes].blue = 8; - num_modes++; - } - } - - return num_modes; -} - -int gfx_unload_texture(int index) -{ - if(index == invalid_texture) - return 0; - - if(index < 0) - return 0; - - glDeleteTextures(1, &textures[index].tex); - textures[index].next = first_free_texture; - memory_usage -= textures[index].memsize; - first_free_texture = index; - return 0; -} - -void gfx_blend_none() -{ - if(no_gfx) return; - glDisable(GL_BLEND); -} - -void gfx_blend_normal() -{ - if(no_gfx) return; - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); -} - -void gfx_blend_additive() -{ - if(no_gfx) return; - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE); -} - -int gfx_memory_usage() { return memory_usage; } - -static unsigned char sample(int w, int h, const unsigned char *data, int u, int v, int offset) -{ - return (data[(v*w+u)*4+offset]+ - data[(v*w+u+1)*4+offset]+ - data[((v+1)*w+u)*4+offset]+ - data[((v+1)*w+u+1)*4+offset])/4; -} - -int gfx_load_texture_raw(int w, int h, int format, const void *data, int store_format, int flags) -{ - int mipmap = 1; - unsigned char *texdata = (unsigned char *)data; - unsigned char *tmpdata = 0; - int oglformat = 0; - int store_oglformat = 0; - int tex = 0; - - /* don't waste memory on texture if we are stress testing */ - if(config.dbg_stress || no_gfx) - return invalid_texture; - - /* grab texture */ - tex = first_free_texture; - first_free_texture = textures[tex].next; - textures[tex].next = -1; - - /* resample if needed */ - if(!(flags&TEXLOAD_NORESAMPLE) && config.gfx_texture_quality==0) - { - if(w > 16 && h > 16 && format == IMG_RGBA) - { - unsigned char *tmpdata; - int c = 0; - int x, y; - - tmpdata = (unsigned char *)mem_alloc(w*h*4, 1); - - w/=2; - h/=2; - - for(y = 0; y < h; y++) - for(x = 0; x < w; x++, c++) - { - tmpdata[c*4] = sample(w*2, h*2, texdata, x*2,y*2, 0); - tmpdata[c*4+1] = sample(w*2, h*2, texdata, x*2,y*2, 1); - tmpdata[c*4+2] = sample(w*2, h*2, texdata, x*2,y*2, 2); - tmpdata[c*4+3] = sample(w*2, h*2, texdata, x*2,y*2, 3); - } - texdata = tmpdata; - } - } - - oglformat = GL_RGBA; - if(format == IMG_RGB) - oglformat = GL_RGB; - else if(format == IMG_ALPHA) - oglformat = GL_ALPHA; - - /* upload texture */ - if(config.gfx_texture_compression) - { - store_oglformat = GL_COMPRESSED_RGBA_ARB; - if(store_format == IMG_RGB) - store_oglformat = GL_COMPRESSED_RGB_ARB; - else if(store_format == IMG_ALPHA) - store_oglformat = GL_COMPRESSED_ALPHA_ARB; - } - else - { - store_oglformat = GL_RGBA; - if(store_format == IMG_RGB) - store_oglformat = GL_RGB; - else if(store_format == IMG_ALPHA) - store_oglformat = GL_ALPHA; - } - - glGenTextures(1, &textures[tex].tex); - glBindTexture(GL_TEXTURE_2D, textures[tex].tex); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); - gluBuild2DMipmaps(GL_TEXTURE_2D, store_oglformat, w, h, oglformat, GL_UNSIGNED_BYTE, texdata); - - /* calculate memory usage */ - { - int pixel_size = 4; - if(store_format == IMG_RGB) - pixel_size = 3; - else if(store_format == IMG_ALPHA) - pixel_size = 1; - - textures[tex].memsize = w*h*pixel_size; - if(mipmap) - { - while(w > 2 && h > 2) - { - w>>=1; - h>>=1; - textures[tex].memsize += w*h*pixel_size; - } - } - } - - memory_usage += textures[tex].memsize; - mem_free(tmpdata); - return tex; -} - -/* simple uncompressed RGBA loaders */ -int gfx_load_texture(const char *filename, int store_format, int flags) -{ - int l = strlen(filename); - int id; - IMAGE_INFO img; - - if(l < 3) - return -1; - if(gfx_load_png(&img, filename)) - { - if (store_format == IMG_AUTO) - store_format = img.format; - - id = gfx_load_texture_raw(img.width, img.height, img.format, img.data, store_format, flags); - mem_free(img.data); - return id; - } - - return invalid_texture; -} - -int gfx_load_png(IMAGE_INFO *img, const char *filename) -{ - char completefilename[512]; - unsigned char *buffer; - png_t png; - - /* open file for reading */ - png_init(0,0); - - engine_getpath(completefilename, sizeof(completefilename), filename, IOFLAG_READ); - - if(png_open_file(&png, completefilename) != PNG_NO_ERROR) - { - dbg_msg("game/png", "failed to open file. filename='%s'", completefilename); - return 0; - } - - if(png.depth != 8 || (png.color_type != PNG_TRUECOLOR && png.color_type != PNG_TRUECOLOR_ALPHA)) - { - dbg_msg("game/png", "invalid format. filename='%s'", completefilename); - png_close_file(&png); - return 0; - } - - buffer = (unsigned char *)mem_alloc(png.width * png.height * png.bpp, 1); - png_get_data(&png, buffer); - png_close_file(&png); - - img->width = png.width; - img->height = png.height; - if(png.color_type == PNG_TRUECOLOR) - img->format = IMG_RGB; - else if(png.color_type == PNG_TRUECOLOR_ALPHA) - img->format = IMG_RGBA; - img->data = buffer; - return 1; -} - -void gfx_shutdown() -{ - if (vertices) - mem_free(vertices); - - /* TODO: SDL, is this correct? */ - SDL_Quit(); -} - -void gfx_screenshot() -{ - do_screenshot = 1; -} - - -extern int text_render_codepaths_usage[5]; - -void gfx_swap() -{ - /*dbg_msg("", "%d %d %d %d %d", - text_render_codepaths_usage[0], - text_render_codepaths_usage[1], - text_render_codepaths_usage[2], - text_render_codepaths_usage[3], - text_render_codepaths_usage[4]); - - text_render_codepaths_usage[0] = 0; - text_render_codepaths_usage[1] = 0; - text_render_codepaths_usage[2] = 0; - text_render_codepaths_usage[3] = 0; - text_render_codepaths_usage[4] = 0;*/ - - if(do_screenshot) - { - /* find filename */ - char filename[128]; - static int index = 1; - - for(; index < 1000; index++) - { - IOHANDLE io; - str_format(filename, sizeof(filename), "screenshots/screenshot%04d.png", index); - io = engine_openfile(filename, IOFLAG_READ); - if(io) - io_close(io); - else - break; - } - - gfx_screenshot_direct(filename); - - do_screenshot = 0; - } - - { - static PERFORMACE_INFO pscope = {"glfwSwapBuffers", 0}; - perf_start(&pscope); - SDL_GL_SwapBuffers(); - perf_end(); - } - - if(render_enable && config.gfx_finish) - glFinish(); -} - -void gfx_screenshot_direct(const char *filename) -{ - /* fetch image data */ - int y; - int w = screen_width; - int h = screen_height; - unsigned char *pixel_data = (unsigned char *)mem_alloc(w*(h+1)*4, 1); - unsigned char *temp_row = pixel_data+w*h*4; - glReadPixels(0,0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixel_data); - - /* flip the pixel because opengl works from bottom left corner */ - for(y = 0; y < h/2; y++) - { - mem_copy(temp_row, pixel_data+y*w*4, w*4); - mem_copy(pixel_data+y*w*4, pixel_data+(h-y-1)*w*4, w*4); - mem_copy(pixel_data+(h-y-1)*w*4, temp_row,w*4); - } - - /* find filename */ - { - char wholepath[1024]; - png_t png; - - engine_savepath(filename, wholepath, sizeof(wholepath)); - - /* save png */ - dbg_msg("client", "saved screenshot to '%s'", wholepath); - png_open_file_write(&png, wholepath); - png_set_data(&png, w, h, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)pixel_data); - png_close_file(&png); - } - - /* clean up */ - mem_free(pixel_data); -} - -int gfx_screenwidth() -{ - return screen_width; -} - -int gfx_screenheight() -{ - return screen_height; -} - -void gfx_texture_set(int slot) -{ - dbg_assert(drawing == 0, "called gfx_texture_set within begin"); - if(no_gfx) return; - if(slot == -1) - { - glDisable(GL_TEXTURE_2D); - } - else - { - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, textures[slot].tex); - } -} - -void gfx_clear(float r, float g, float b) -{ - if(no_gfx) return; - glClearColor(r,g,b,0.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -} - -void gfx_mapscreen(float tl_x, float tl_y, float br_x, float br_y) -{ - screen_x0 = tl_x; - screen_y0 = tl_y; - screen_x1 = br_x; - screen_y1 = br_y; - if(no_gfx) return; - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(tl_x, br_x, br_y, tl_y, 1.0f, 10.f); -} - -void gfx_getscreen(float *tl_x, float *tl_y, float *br_x, float *br_y) -{ - *tl_x = screen_x0; - *tl_y = screen_y0; - *br_x = screen_x1; - *br_y = screen_y1; -} - -void gfx_quads_begin() -{ - dbg_assert(drawing == 0, "called quads_begin twice"); - drawing = DRAWING_QUADS; - - gfx_quads_setsubset(0,0,1,1); - gfx_quads_setrotation(0); - gfx_setcolor(1,1,1,1); -} - -void gfx_quads_end() -{ - dbg_assert(drawing == DRAWING_QUADS, "called quads_end without begin"); - flush(); - drawing = 0; -} - - -void gfx_quads_setrotation(float angle) -{ - dbg_assert(drawing == DRAWING_QUADS, "called gfx_quads_setrotation without begin"); - rotation = angle; -} - -void gfx_setcolorvertex(int i, float r, float g, float b, float a) -{ - dbg_assert(drawing != 0, "called gfx_quads_setcolorvertex without begin"); - color[i].r = r; - color[i].g = g; - color[i].b = b; - color[i].a = a; -} - -void gfx_setcolor(float r, float g, float b, float a) -{ - dbg_assert(drawing != 0, "called gfx_quads_setcolor without begin"); - gfx_setcolorvertex(0, r, g, b, a); - gfx_setcolorvertex(1, r, g, b, a); - gfx_setcolorvertex(2, r, g, b, a); - gfx_setcolorvertex(3, r, g, b, a); -} - -void gfx_quads_setsubset(float tl_u, float tl_v, float br_u, float br_v) -{ - dbg_assert(drawing == DRAWING_QUADS, "called gfx_quads_setsubset without begin"); - - texture[0].u = tl_u; texture[1].u = br_u; - texture[0].v = tl_v; texture[1].v = tl_v; - - texture[3].u = tl_u; texture[2].u = br_u; - texture[3].v = br_v; texture[2].v = br_v; -} - -void gfx_quads_setsubset_free( - float x0, float y0, float x1, float y1, - float x2, float y2, float x3, float y3) -{ - texture[0].u = x0; texture[0].v = y0; - texture[1].u = x1; texture[1].v = y1; - texture[2].u = x2; texture[2].v = y2; - texture[3].u = x3; texture[3].v = y3; -} - - -static void rotate(VEC3 *center, VEC3 *point) -{ - float x = point->x - center->x; - float y = point->y - center->y; - point->x = x * cosf(rotation) - y * sinf(rotation) + center->x; - point->y = x * sinf(rotation) + y * cosf(rotation) + center->y; -} - -void gfx_quads_draw(float x, float y, float w, float h) -{ - gfx_quads_drawTL(x-w/2, y-h/2,w,h); -} - -void gfx_quads_drawTL(float x, float y, float width, float height) -{ - VEC3 center; - - dbg_assert(drawing == DRAWING_QUADS, "called quads_draw without begin"); - - center.x = x + width/2; - center.y = y + height/2; - center.z = 0; - - vertices[num_vertices].pos.x = x; - vertices[num_vertices].pos.y = y; - vertices[num_vertices].tex = texture[0]; - vertices[num_vertices].color = color[0]; - rotate(¢er, &vertices[num_vertices].pos); - - vertices[num_vertices + 1].pos.x = x+width; - vertices[num_vertices + 1].pos.y = y; - vertices[num_vertices + 1].tex = texture[1]; - vertices[num_vertices + 1].color = color[1]; - rotate(¢er, &vertices[num_vertices + 1].pos); - - vertices[num_vertices + 2].pos.x = x + width; - vertices[num_vertices + 2].pos.y = y+height; - vertices[num_vertices + 2].tex = texture[2]; - vertices[num_vertices + 2].color = color[2]; - rotate(¢er, &vertices[num_vertices + 2].pos); - - vertices[num_vertices + 3].pos.x = x; - vertices[num_vertices + 3].pos.y = y+height; - vertices[num_vertices + 3].tex = texture[3]; - vertices[num_vertices + 3].color = color[3]; - rotate(¢er, &vertices[num_vertices + 3].pos); - - add_vertices(4); -} - -void gfx_quads_draw_freeform( - float x0, float y0, float x1, float y1, - float x2, float y2, float x3, float y3) -{ - dbg_assert(drawing == DRAWING_QUADS, "called quads_draw_freeform without begin"); - - vertices[num_vertices].pos.x = x0; - vertices[num_vertices].pos.y = y0; - vertices[num_vertices].tex = texture[0]; - vertices[num_vertices].color = color[0]; - - vertices[num_vertices + 1].pos.x = x1; - vertices[num_vertices + 1].pos.y = y1; - vertices[num_vertices + 1].tex = texture[1]; - vertices[num_vertices + 1].color = color[1]; - - vertices[num_vertices + 2].pos.x = x3; - vertices[num_vertices + 2].pos.y = y3; - vertices[num_vertices + 2].tex = texture[3]; - vertices[num_vertices + 2].color = color[3]; - - vertices[num_vertices + 3].pos.x = x2; - vertices[num_vertices + 3].pos.y = y2; - vertices[num_vertices + 3].tex = texture[2]; - vertices[num_vertices + 3].color = color[2]; - - add_vertices(4); -} - -void gfx_quads_text(float x, float y, float size, float r, float g, float b, float a, const char *text) -{ - float startx = x; - - gfx_quads_begin(); - gfx_setcolor(r,g,b,a); - - while(*text) - { - char c = *text; - text++; - - if(c == '\n') - { - x = startx; - y += size; - } - else - { - gfx_quads_setsubset( - (c%16)/16.0f, - (c/16)/16.0f, - (c%16)/16.0f+1.0f/16.0f, - (c/16)/16.0f+1.0f/16.0f); - - gfx_quads_drawTL(x,y,size,size); - x += size/2; - } - } - - gfx_quads_end(); -} - -void gfx_lines_begin() -{ - dbg_assert(drawing == 0, "called begin twice"); - drawing = DRAWING_LINES; - gfx_setcolor(1,1,1,1); -} - -void gfx_lines_end() -{ - dbg_assert(drawing == DRAWING_LINES, "called end without begin"); - flush(); - drawing = 0; -} - -void gfx_lines_draw(float x0, float y0, float x1, float y1) -{ - dbg_assert(drawing == DRAWING_LINES, "called draw without begin"); - - vertices[num_vertices].pos.x = x0; - vertices[num_vertices].pos.y = y0; - vertices[num_vertices].tex = texture[0]; - vertices[num_vertices].color = color[0]; - - vertices[num_vertices + 1].pos.x = x1; - vertices[num_vertices + 1].pos.y = y1; - vertices[num_vertices + 1].tex = texture[1]; - vertices[num_vertices + 1].color = color[1]; - - add_vertices(2); -} - -void gfx_clip_enable(int x, int y, int w, int h) -{ - if(no_gfx) return; - glScissor(x, gfx_screenheight()-(y+h), w, h); - glEnable(GL_SCISSOR_TEST); -} - -void gfx_clip_disable() -{ - if(no_gfx) return; - glDisable(GL_SCISSOR_TEST); -} - -void gfx_minimize() -{ - SDL_WM_IconifyWindow(); -} - -void gfx_maximize() -{ - /* TODO: SDL */ -} diff --git a/src/engine/client/ec_gfx.cpp b/src/engine/client/ec_gfx.cpp new file mode 100644 index 00000000..5632581a --- /dev/null +++ b/src/engine/client/ec_gfx.cpp @@ -0,0 +1,992 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ + +#include + +#include "SDL.h" + +#ifdef CONF_FAMILY_WINDOWS + #define WIN32_LEAN_AND_MEAN + #include +#endif + +#ifdef CONF_PLATFORM_MACOSX + #include + #include +#else + #include + #include +#endif + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +/* compressed textures */ +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 + +#define TEXTURE_MAX_ANISOTROPY_EXT 0x84FE + + +void gfx_font_init(); + +VIDEO_MODE fakemodes[] = { + {320,240,8,8,8}, {400,300,8,8,8}, {640,480,8,8,8}, + {720,400,8,8,8}, {768,576,8,8,8}, {800,600,8,8,8}, + {1024,600,8,8,8}, {1024,768,8,8,8}, {1152,864,8,8,8}, + {1280,768,8,8,8}, {1280,800,8,8,8}, {1280,960,8,8,8}, + {1280,1024,8,8,8}, {1368,768,8,8,8}, {1400,1050,8,8,8}, + {1440,900,8,8,8}, {1440,1050,8,8,8}, {1600,1000,8,8,8}, + {1600,1200,8,8,8}, {1680,1050,8,8,8}, {1792,1344,8,8,8}, + {1800,1440,8,8,8}, {1856,1392,8,8,8}, {1920,1080,8,8,8}, + {1920,1200,8,8,8}, {1920,1440,8,8,8}, {1920,2400,8,8,8}, + {2048,1536,8,8,8}, + + {320,240,5,6,5}, {400,300,5,6,5}, {640,480,5,6,5}, + {720,400,5,6,5}, {768,576,5,6,5}, {800,600,5,6,5}, + {1024,600,5,6,5}, {1024,768,5,6,5}, {1152,864,5,6,5}, + {1280,768,5,6,5}, {1280,800,5,6,5}, {1280,960,5,6,5}, + {1280,1024,5,6,5}, {1368,768,5,6,5}, {1400,1050,5,6,5}, + {1440,900,5,6,5}, {1440,1050,5,6,5}, {1600,1000,5,6,5}, + {1600,1200,5,6,5}, {1680,1050,5,6,5}, {1792,1344,5,6,5}, + {1800,1440,5,6,5}, {1856,1392,5,6,5}, {1920,1080,5,6,5}, + {1920,1200,5,6,5}, {1920,1440,5,6,5}, {1920,2400,5,6,5}, + {2048,1536,5,6,5} +}; + +int gfx_get_video_modes(VIDEO_MODE *list, int maxcount) +{ + int num_modes = sizeof(fakemodes)/sizeof(VIDEO_MODE); + SDL_Rect **modes; + + if(config.gfx_display_all_modes) + { + int count = sizeof(fakemodes)/sizeof(VIDEO_MODE); + mem_copy(list, fakemodes, sizeof(fakemodes)); + if(maxcount < count) + count = maxcount; + return count; + } + + /* TODO: fix this code on osx or windows */ + + modes = SDL_ListModes(NULL, SDL_OPENGL|SDL_GL_DOUBLEBUFFER|SDL_FULLSCREEN); + if(modes == NULL) + { + /* no modes */ + num_modes = 0; + } + else if(modes == (SDL_Rect**)-1) + { + /* all modes */ + } + else + { + int i; + num_modes = 0; + for(i = 0; modes[i]; ++i) + { + if(num_modes == maxcount) + break; + list[num_modes].width = modes[i]->w; + list[num_modes].height = modes[i]->h; + list[num_modes].red = 8; + list[num_modes].green = 8; + list[num_modes].blue = 8; + num_modes++; + } + } + + return num_modes; +} + + +#include "graphics.h" + +class CGraphics_OpenGL : public IEngineGraphics +{ +protected: + /* */ + typedef struct { float x, y, z; } CPoint; + typedef struct { float u, v; } CTexCoord; + typedef struct { float r, g, b, a; } CColor; + + typedef struct + { + CPoint m_Pos; + CTexCoord m_Tex; + CColor m_Color; + } CVertex; + + enum + { + MAX_VERTICES = 32*1024, + MAX_TEXTURES = 1024*4, + + DRAWING_QUADS=1, + DRAWING_LINES=2 + }; + + CVertex m_aVertices[MAX_VERTICES]; + int m_NumVertices; + + CColor m_aColor[4]; + CTexCoord m_aTexture[4]; + + bool m_RenderEnable; + + float m_Rotation; + int m_Drawing; + bool m_DoScreenshot; + + float m_ScreenX0; + float m_ScreenY0; + float m_ScreenX1; + float m_ScreenY1; + + int m_InvalidTexture; + + struct CTexture + { + GLuint tex; + int memsize; + int flags; + int next; + }; + + enum + { + + }; + + CTexture m_aTextures[MAX_TEXTURES]; + int m_FirstFreeTexture; + int m_TextureMemoryUsage; + + + void Flush() + { + if(m_NumVertices == 0) + return; + + //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); + //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glVertexPointer(3, GL_FLOAT, + sizeof(CVertex), + (char*)m_aVertices); + glTexCoordPointer(2, GL_FLOAT, + sizeof(CVertex), + (char*)m_aVertices + sizeof(float)*3); + glColorPointer(4, GL_FLOAT, + sizeof(CVertex), + (char*)m_aVertices + sizeof(float)*5); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + if(m_RenderEnable) + { + if(m_Drawing == DRAWING_QUADS) + glDrawArrays(GL_QUADS, 0, m_NumVertices); + else if(m_Drawing == DRAWING_LINES) + glDrawArrays(GL_LINES, 0, m_NumVertices); + } + + /* Reset pointer */ + m_NumVertices = 0; + } + + void AddVertices(int count) + { + m_NumVertices += count; + if((m_NumVertices + count) >= MAX_VERTICES) + Flush(); + } + + void Rotate(CPoint *pCenter, CPoint *pPoint) + { + float x = pPoint->x - pCenter->x; + float y = pPoint->y - pCenter->y; + pPoint->x = x * cosf(m_Rotation) - y * sinf(m_Rotation) + pCenter->x; + pPoint->y = x * sinf(m_Rotation) + y * cosf(m_Rotation) + pCenter->y; + } + + + + + static unsigned char sample(int w, int h, const unsigned char *data, int u, int v, int offset) + { + return (data[(v*w+u)*4+offset]+ + data[(v*w+u+1)*4+offset]+ + data[((v+1)*w+u)*4+offset]+ + data[((v+1)*w+u+1)*4+offset])/4; + } +public: + CGraphics_OpenGL() + { + m_NumVertices = 0; + + m_ScreenX0 = 0; + m_ScreenY0 = 0; + m_ScreenX1 = 0; + m_ScreenY1 = 0; + + m_ScreenWidth = -1; + m_ScreenHeight = -1; + + m_Rotation = 0; + m_Drawing = 0; + m_InvalidTexture = 0; + + m_TextureMemoryUsage = 0; + + m_RenderEnable = true; + m_DoScreenshot = false; + } + + + virtual void ClipEnable(int x, int y, int w, int h) + { + //if(no_gfx) return; + glScissor(x, ScreenHeight()-(y+h), w, h); + glEnable(GL_SCISSOR_TEST); + } + + virtual void ClipDisable() + { + //if(no_gfx) return; + glDisable(GL_SCISSOR_TEST); + } + + + virtual void BlendNone() + { + glDisable(GL_BLEND); + } + + virtual void BlendNormal() + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + virtual void BlendAdditive() + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + } + + //int gfx_memory_usage() { return m_MemoryUsage; } + + virtual void MapScreen(float tl_x, float tl_y, float br_x, float br_y) + { + m_ScreenX0 = tl_x; + m_ScreenY0 = tl_y; + m_ScreenX1 = br_x; + m_ScreenY1 = br_y; + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(tl_x, br_x, br_y, tl_y, 1.0f, 10.f); + } + + virtual void GetScreen(float *tl_x, float *tl_y, float *br_x, float *br_y) + { + *tl_x = m_ScreenX0; + *tl_y = m_ScreenY0; + *br_x = m_ScreenX1; + *br_y = m_ScreenY1; + } + + virtual void LinesBegin() + { + dbg_assert(m_Drawing == 0, "called begin twice"); + m_Drawing = DRAWING_LINES; + SetColor(1,1,1,1); + } + + virtual void LinesEnd() + { + dbg_assert(m_Drawing == DRAWING_LINES, "called end without begin"); + Flush(); + m_Drawing = 0; + } + + virtual void LinesDraw(float x0, float y0, float x1, float y1) + { + dbg_assert(m_Drawing == DRAWING_LINES, "called draw without begin"); + + m_aVertices[m_NumVertices].m_Pos.x = x0; + m_aVertices[m_NumVertices].m_Pos.y = y0; + m_aVertices[m_NumVertices].m_Tex = m_aTexture[0]; + m_aVertices[m_NumVertices].m_Color = m_aColor[0]; + + m_aVertices[m_NumVertices + 1].m_Pos.x = x1; + m_aVertices[m_NumVertices + 1].m_Pos.y = y1; + m_aVertices[m_NumVertices + 1].m_Tex = m_aTexture[1]; + m_aVertices[m_NumVertices + 1].m_Color = m_aColor[1]; + + AddVertices(2); + } + + + + virtual int UnloadTexture(int Index) + { + if(Index == m_InvalidTexture) + return 0; + + if(Index < 0) + return 0; + + glDeleteTextures(1, &m_aTextures[Index].tex); + m_aTextures[Index].next = m_FirstFreeTexture; + m_TextureMemoryUsage -= m_aTextures[Index].memsize; + m_FirstFreeTexture = Index; + return 0; + } + + + virtual int LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags) + { + int mipmap = 1; + unsigned char *texdata = (unsigned char *)pData; + unsigned char *tmpdata = 0; + int oglformat = 0; + int store_oglformat = 0; + int tex = 0; + + /* don't waste memory on texture if we are stress testing */ + if(config.dbg_stress) + return m_InvalidTexture; + + /* grab texture */ + tex = m_FirstFreeTexture; + m_FirstFreeTexture = m_aTextures[tex].next; + m_aTextures[tex].next = -1; + + /* resample if needed */ + if(!(Flags&TEXLOAD_NORESAMPLE) && config.gfx_texture_quality==0) + { + if(Width > 16 && Height > 16 && Format == IMG_RGBA) + { + unsigned char *tmpdata; + int c = 0; + int x, y; + + tmpdata = (unsigned char *)mem_alloc(Width*Height*4, 1); + + Width/=2; + Height/=2; + + for(y = 0; y < Height; y++) + for(x = 0; x < Width; x++, c++) + { + tmpdata[c*4] = sample(Width*2, Height*2, texdata, x*2,y*2, 0); + tmpdata[c*4+1] = sample(Width*2, Height*2, texdata, x*2,y*2, 1); + tmpdata[c*4+2] = sample(Width*2, Height*2, texdata, x*2,y*2, 2); + tmpdata[c*4+3] = sample(Width*2, Height*2, texdata, x*2,y*2, 3); + } + texdata = tmpdata; + } + } + + oglformat = GL_RGBA; + if(Format == IMG_RGB) + oglformat = GL_RGB; + else if(Format == IMG_ALPHA) + oglformat = GL_ALPHA; + + /* upload texture */ + if(config.gfx_texture_compression) + { + store_oglformat = GL_COMPRESSED_RGBA_ARB; + if(StoreFormat == IMG_RGB) + store_oglformat = GL_COMPRESSED_RGB_ARB; + else if(StoreFormat == IMG_ALPHA) + store_oglformat = GL_COMPRESSED_ALPHA_ARB; + } + else + { + store_oglformat = GL_RGBA; + if(StoreFormat == IMG_RGB) + store_oglformat = GL_RGB; + else if(StoreFormat == IMG_ALPHA) + store_oglformat = GL_ALPHA; + } + + glGenTextures(1, &m_aTextures[tex].tex); + glBindTexture(GL_TEXTURE_2D, m_aTextures[tex].tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); + gluBuild2DMipmaps(GL_TEXTURE_2D, store_oglformat, Width, Height, oglformat, GL_UNSIGNED_BYTE, texdata); + + /* calculate memory usage */ + { + int pixel_size = 4; + if(StoreFormat == IMG_RGB) + pixel_size = 3; + else if(StoreFormat == IMG_ALPHA) + pixel_size = 1; + + m_aTextures[tex].memsize = Width*Height*pixel_size; + if(mipmap) + { + while(Width > 2 && Height > 2) + { + Width>>=1; + Height>>=1; + m_aTextures[tex].memsize += Width*Height*pixel_size; + } + } + } + + m_TextureMemoryUsage += m_aTextures[tex].memsize; + mem_free(tmpdata); + return tex; + } + + /* simple uncompressed RGBA loaders */ + virtual int LoadTexture(const char *pFilename, int StoreFormat, int Flags) + { + int l = strlen(pFilename); + int id; + IMAGE_INFO img; + + if(l < 3) + return -1; + if(LoadPNG(&img, pFilename)) + { + if (StoreFormat == IMG_AUTO) + StoreFormat = img.format; + + id = LoadTextureRaw(img.width, img.height, img.format, img.data, StoreFormat, Flags); + mem_free(img.data); + return id; + } + + return m_InvalidTexture; + } + + virtual int LoadPNG(IMAGE_INFO *pImg, const char *pFilename) + { + char aCompleteFilename[512]; + unsigned char *pBuffer; + png_t png; + + /* open file for reading */ + png_init(0,0); + + engine_getpath(aCompleteFilename, sizeof(aCompleteFilename), pFilename, IOFLAG_READ); + + if(png_open_file(&png, aCompleteFilename) != PNG_NO_ERROR) + { + dbg_msg("game/png", "failed to open file. filename='%s'", aCompleteFilename); + return 0; + } + + if(png.depth != 8 || (png.color_type != PNG_TRUECOLOR && png.color_type != PNG_TRUECOLOR_ALPHA)) + { + dbg_msg("game/png", "invalid format. filename='%s'", aCompleteFilename); + png_close_file(&png); + return 0; + } + + pBuffer = (unsigned char *)mem_alloc(png.width * png.height * png.bpp, 1); + png_get_data(&png, pBuffer); + png_close_file(&png); + + pImg->width = png.width; + pImg->height = png.height; + if(png.color_type == PNG_TRUECOLOR) + pImg->format = IMG_RGB; + else if(png.color_type == PNG_TRUECOLOR_ALPHA) + pImg->format = IMG_RGBA; + pImg->data = pBuffer; + return 1; + } + + void ScreenshotDirect(const char *filename) + { + /* fetch image data */ + int y; + int w = m_ScreenWidth; + int h = m_ScreenHeight; + unsigned char *pixel_data = (unsigned char *)mem_alloc(w*(h+1)*4, 1); + unsigned char *temp_row = pixel_data+w*h*4; + glReadPixels(0,0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixel_data); + + /* flip the pixel because opengl works from bottom left corner */ + for(y = 0; y < h/2; y++) + { + mem_copy(temp_row, pixel_data+y*w*4, w*4); + mem_copy(pixel_data+y*w*4, pixel_data+(h-y-1)*w*4, w*4); + mem_copy(pixel_data+(h-y-1)*w*4, temp_row,w*4); + } + + /* find filename */ + { + char wholepath[1024]; + png_t png; + + engine_savepath(filename, wholepath, sizeof(wholepath)); + + /* save png */ + dbg_msg("client", "saved screenshot to '%s'", wholepath); + png_open_file_write(&png, wholepath); + png_set_data(&png, w, h, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)pixel_data); + png_close_file(&png); + } + + /* clean up */ + mem_free(pixel_data); + } + + virtual void TextureSet(int TextureID) + { + dbg_assert(m_Drawing == 0, "called Graphics()->TextureSet within begin"); + if(TextureID == -1) + { + glDisable(GL_TEXTURE_2D); + } + else + { + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, m_aTextures[TextureID].tex); + } + } + + virtual void Clear(float r, float g, float b) + { + glClearColor(r,g,b,0.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + + virtual void QuadsBegin() + { + dbg_assert(m_Drawing == 0, "called quads_begin twice"); + m_Drawing = DRAWING_QUADS; + + QuadsSetSubset(0,0,1,1); + QuadsSetRotation(0); + SetColor(1,1,1,1); + } + + virtual void QuadsEnd() + { + dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_end without begin"); + Flush(); + m_Drawing = 0; + } + + virtual void QuadsSetRotation(float Angle) + { + dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsSetRotation without begin"); + m_Rotation = Angle; + } + + virtual void SetColorVertex(int i, float r, float g, float b, float a) + { + dbg_assert(m_Drawing != 0, "called gfx_quads_setcolorvertex without begin"); + m_aColor[i].r = r; + m_aColor[i].g = g; + m_aColor[i].b = b; + m_aColor[i].a = a; + } + + virtual void SetColor(float r, float g, float b, float a) + { + dbg_assert(m_Drawing != 0, "called gfx_quads_setcolor without begin"); + SetColorVertex(0, r, g, b, a); + SetColorVertex(1, r, g, b, a); + SetColorVertex(2, r, g, b, a); + SetColorVertex(3, r, g, b, a); + } + + virtual void QuadsSetSubset(float tl_u, float tl_v, float br_u, float br_v) + { + dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsSetSubset without begin"); + + m_aTexture[0].u = tl_u; m_aTexture[1].u = br_u; + m_aTexture[0].v = tl_v; m_aTexture[1].v = tl_v; + + m_aTexture[3].u = tl_u; m_aTexture[2].u = br_u; + m_aTexture[3].v = br_v; m_aTexture[2].v = br_v; + } + + virtual void QuadsSetSubsetFree( + float x0, float y0, float x1, float y1, + float x2, float y2, float x3, float y3) + { + m_aTexture[0].u = x0; m_aTexture[0].v = y0; + m_aTexture[1].u = x1; m_aTexture[1].v = y1; + m_aTexture[2].u = x2; m_aTexture[2].v = y2; + m_aTexture[3].u = x3; m_aTexture[3].v = y3; + } + + virtual void QuadsDraw(float x, float y, float w, float h) + { + QuadsDrawTL(x-w/2, y-h/2,w,h); + } + + virtual void QuadsDrawTL(float x, float y, float w, float h) + { + CPoint Center; + + dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_draw without begin"); + + Center.x = x + w/2; + Center.y = y + h/2; + Center.z = 0; + + m_aVertices[m_NumVertices].m_Pos.x = x; + m_aVertices[m_NumVertices].m_Pos.y = y; + m_aVertices[m_NumVertices].m_Tex = m_aTexture[0]; + m_aVertices[m_NumVertices].m_Color = m_aColor[0]; + Rotate(&Center, &m_aVertices[m_NumVertices].m_Pos); + + m_aVertices[m_NumVertices + 1].m_Pos.x = x+w; + m_aVertices[m_NumVertices + 1].m_Pos.y = y; + m_aVertices[m_NumVertices + 1].m_Tex = m_aTexture[1]; + m_aVertices[m_NumVertices + 1].m_Color = m_aColor[1]; + Rotate(&Center, &m_aVertices[m_NumVertices + 1].m_Pos); + + m_aVertices[m_NumVertices + 2].m_Pos.x = x + w; + m_aVertices[m_NumVertices + 2].m_Pos.y = y+h; + m_aVertices[m_NumVertices + 2].m_Tex = m_aTexture[2]; + m_aVertices[m_NumVertices + 2].m_Color = m_aColor[2]; + Rotate(&Center, &m_aVertices[m_NumVertices + 2].m_Pos); + + m_aVertices[m_NumVertices + 3].m_Pos.x = x; + m_aVertices[m_NumVertices + 3].m_Pos.y = y+h; + m_aVertices[m_NumVertices + 3].m_Tex = m_aTexture[3]; + m_aVertices[m_NumVertices + 3].m_Color = m_aColor[3]; + Rotate(&Center, &m_aVertices[m_NumVertices + 3].m_Pos); + + AddVertices(4); + } + + void QuadsDrawFreeform( + float x0, float y0, float x1, float y1, + float x2, float y2, float x3, float y3) + { + dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_draw_freeform without begin"); + + m_aVertices[m_NumVertices].m_Pos.x = x0; + m_aVertices[m_NumVertices].m_Pos.y = y0; + m_aVertices[m_NumVertices].m_Tex = m_aTexture[0]; + m_aVertices[m_NumVertices].m_Color = m_aColor[0]; + + m_aVertices[m_NumVertices + 1].m_Pos.x = x1; + m_aVertices[m_NumVertices + 1].m_Pos.y = y1; + m_aVertices[m_NumVertices + 1].m_Tex = m_aTexture[1]; + m_aVertices[m_NumVertices + 1].m_Color = m_aColor[1]; + + m_aVertices[m_NumVertices + 2].m_Pos.x = x3; + m_aVertices[m_NumVertices + 2].m_Pos.y = y3; + m_aVertices[m_NumVertices + 2].m_Tex = m_aTexture[3]; + m_aVertices[m_NumVertices + 2].m_Color = m_aColor[3]; + + m_aVertices[m_NumVertices + 3].m_Pos.x = x2; + m_aVertices[m_NumVertices + 3].m_Pos.y = y2; + m_aVertices[m_NumVertices + 3].m_Tex = m_aTexture[2]; + m_aVertices[m_NumVertices + 3].m_Color = m_aColor[2]; + + AddVertices(4); + } + + virtual void QuadsText(float x, float y, float Size, float r, float g, float b, float a, const char *pText) + { + float startx = x; + + QuadsBegin(); + SetColor(r,g,b,a); + + while(*pText) + { + char c = *pText; + pText++; + + if(c == '\n') + { + x = startx; + y += Size; + } + else + { + QuadsSetSubset( + (c%16)/16.0f, + (c/16)/16.0f, + (c%16)/16.0f+1.0f/16.0f, + (c/16)/16.0f+1.0f/16.0f); + + QuadsDrawTL(x,y,Size,Size); + x += Size/2; + } + } + + QuadsEnd(); + } + + virtual bool Init() + { + /* Set all z to -5.0f */ + for(int i = 0; i < MAX_VERTICES; i++) + m_aVertices[i].m_Pos.z = -5.0f; + + /* init textures */ + m_FirstFreeTexture = 0; + for(int i = 0; i < MAX_TEXTURES; i++) + m_aTextures[i].next = i+1; + m_aTextures[MAX_TEXTURES-1].next = -1; + + /* set some default settings */ + glEnable(GL_BLEND); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glAlphaFunc(GL_GREATER, 0); + glEnable(GL_ALPHA_TEST); + glDepthMask(0); + + /* create null texture, will get id=0 */ + static const unsigned char aNullTextureData[] = { + 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, + 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, + 0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, + 0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, + }; + + m_InvalidTexture = LoadTextureRaw(4,4,IMG_RGBA,aNullTextureData,IMG_RGBA,TEXLOAD_NORESAMPLE); + dbg_msg("", "invalid texture id: %d %d", m_InvalidTexture, m_aTextures[m_InvalidTexture].tex); + + return true; + } +}; + +class CGraphics_SDL : public CGraphics_OpenGL +{ + SDL_Surface *m_pScreenSurface; + + int TryInit() + { + const SDL_VideoInfo *pInfo; + int Flags = SDL_OPENGL; + + m_ScreenWidth = config.gfx_screen_width; + m_ScreenHeight = config.gfx_screen_height; + + pInfo = SDL_GetVideoInfo(); + + /* set flags */ + Flags = SDL_OPENGL; + Flags |= SDL_GL_DOUBLEBUFFER; + Flags |= SDL_HWPALETTE; + if(config.dbg_resizable) + Flags |= SDL_RESIZABLE; + + if(pInfo->hw_available) + Flags |= SDL_HWSURFACE; + else + Flags |= SDL_SWSURFACE; + + if(pInfo->blit_hw) + Flags |= SDL_HWACCEL; + + if(config.gfx_fullscreen) + Flags |= SDL_FULLSCREEN; + + /* set gl attributes */ + if(config.gfx_fsaa_samples) + { + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, config.gfx_fsaa_samples); + } + else + { + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); + } + + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, config.gfx_vsync); + + /* set caption */ + SDL_WM_SetCaption("Teeworlds", "Teeworlds"); + + /* create window */ + m_pScreenSurface = SDL_SetVideoMode(m_ScreenWidth, m_ScreenHeight, 0, Flags); + if(m_pScreenSurface == NULL) + { + dbg_msg("gfx", "unable to set video mode: %s", SDL_GetError()); + return -1; + } + + return 0; + } + + + int InitWindow() + { + if(TryInit() == 0) + return 0; + + /* try disabling fsaa */ + while(config.gfx_fsaa_samples) + { + config.gfx_fsaa_samples--; + + if(config.gfx_fsaa_samples) + dbg_msg("gfx", "lowering FSAA to %d and trying again", config.gfx_fsaa_samples); + else + dbg_msg("gfx", "disabling FSAA and trying again"); + + if(TryInit() == 0) + return 0; + } + + /* try lowering the resolution */ + if(config.gfx_screen_width != 640 || config.gfx_screen_height != 480) + { + dbg_msg("gfx", "setting resolution to 640x480 and trying again"); + config.gfx_screen_width = 640; + config.gfx_screen_height = 480; + + if(TryInit() == 0) + return 0; + } + + dbg_msg("gfx", "out of ideas. failed to init graphics"); + + return -1; + } + + +public: + CGraphics_SDL() + { + m_pScreenSurface = 0; + } + + virtual bool Init() + { + { + int Systems = SDL_INIT_VIDEO; + + if(config.snd_enable) + Systems |= SDL_INIT_AUDIO; + + if(config.cl_eventthread) + Systems |= SDL_INIT_EVENTTHREAD; + + if(SDL_Init(Systems) < 0) + { + dbg_msg("gfx", "unable to init SDL: %s", SDL_GetError()); + return -1; + } + } + + atexit(SDL_Quit); + + #ifdef CONF_FAMILY_WINDOWS + if(!getenv("SDL_VIDEO_WINDOW_POS") && !getenv("SDL_VIDEO_CENTERED")) + putenv("SDL_VIDEO_WINDOW_POS=8,27"); + #endif + + if(InitWindow() != 0) + return -1; + + SDL_ShowCursor(0); + + CGraphics_OpenGL::Init(); + + MapScreen(0,0,config.gfx_screen_width, config.gfx_screen_height); + + /* init input */ + inp_init(); + + /* font init */ + gfx_font_init(); + + return 0; + } + + virtual void Shutdown() + { + /* TODO: SDL, is this correct? */ + SDL_Quit(); + } + + virtual void Minimize() + { + SDL_WM_IconifyWindow(); + } + + virtual void Maximize() + { + /* TODO: SDL */ + } + + virtual int WindowActive() + { + return SDL_GetAppState()&SDL_APPINPUTFOCUS; + } + + virtual int WindowOpen() + { + return SDL_GetAppState()&SDL_APPACTIVE; + + } + + virtual void TakeScreenshot() + { + m_DoScreenshot = true; + } + + virtual void Swap() + { + if(m_DoScreenshot) + { + /* find filename */ + char filename[128]; + static int index = 1; + + for(; index < 1000; index++) + { + IOHANDLE io; + str_format(filename, sizeof(filename), "screenshots/screenshot%04d.png", index); + io = engine_openfile(filename, IOFLAG_READ); + if(io) + io_close(io); + else + break; + } + + ScreenshotDirect(filename); + m_DoScreenshot = false; + } + + { + static PERFORMACE_INFO pscope = {"glfwSwapBuffers", 0}; + perf_start(&pscope); + SDL_GL_SwapBuffers(); + perf_end(); + } + + if(config.gfx_finish) + glFinish(); + } +}; + +extern IEngineGraphics *CreateEngineGraphics() { return new CGraphics_SDL(); } diff --git a/src/engine/client/ec_gfx_text.c b/src/engine/client/ec_gfx_text.c deleted file mode 100644 index de40e391..00000000 --- a/src/engine/client/ec_gfx_text.c +++ /dev/null @@ -1,668 +0,0 @@ -#include -#include -#include - - -#ifdef CONF_PLATFORM_MACOSX - #include - #include -#else - #include - #include -#endif - -static int word_length(const char *text) -{ - int s = 1; - while(1) - { - if(*text == 0) - return s-1; - if(*text == '\n' || *text == '\t' || *text == ' ') - return s; - text++; - s++; - } -} - -static float text_r=1; -static float text_g=1; -static float text_b=1; -static float text_a=1; - -static struct FONT *default_font = 0; -void gfx_text_set_default_font(struct FONT *font) -{ - default_font = font; -} - - -void gfx_text_set_cursor(TEXT_CURSOR *cursor, float x, float y, float font_size, int flags) -{ - mem_zero(cursor, sizeof(*cursor)); - cursor->font_size = font_size; - cursor->start_x = x; - cursor->start_y = y; - cursor->x = x; - cursor->y = y; - cursor->line_count = 1; - cursor->line_width = -1; - cursor->flags = flags; - cursor->charcount = 0; -} - - -void gfx_text(void *font_set_v, float x, float y, float size, const char *text, int max_width) -{ - TEXT_CURSOR cursor; - gfx_text_set_cursor(&cursor, x, y, size, TEXTFLAG_RENDER); - cursor.line_width = max_width; - gfx_text_ex(&cursor, text, -1); -} - -float gfx_text_width(void *font_set_v, float size, const char *text, int length) -{ - TEXT_CURSOR cursor; - gfx_text_set_cursor(&cursor, 0, 0, size, 0); - gfx_text_ex(&cursor, text, length); - return cursor.x; -} - -void gfx_text_color(float r, float g, float b, float a) -{ - text_r = r; - text_g = g; - text_b = b; - text_a = a; -} - -/* ft2 texture */ -#include -#include FT_FREETYPE_H - -static FT_Library ft_library; - -#define MAX_CHARACTERS 64 - - -/* GL_LUMINANCE can be good for debugging*/ -static int font_texture_format = GL_ALPHA; - - -static int font_sizes[] = {8,9,10,11,12,13,14,15,16,17,18,19,20,36}; -#define NUM_FONT_SIZES (sizeof(font_sizes)/sizeof(int)) - - -typedef struct FONTCHAR -{ - int id; - - /* these values are scaled to the font size */ - /* width * font_size == real_size */ - float width; - float height; - float offset_x; - float offset_y; - float advance_x; - - float uvs[4]; - int64 touch_time; -} FONTCHAR; - -typedef struct FONTSIZEDATA -{ - int font_size; - FT_Face *face; - - unsigned textures[2]; - int texture_width; - int texture_height; - - int num_x_chars; - int num_y_chars; - - int char_max_width; - int char_max_height; - - FONTCHAR characters[MAX_CHARACTERS*MAX_CHARACTERS]; - - int current_character; -} FONTSIZEDATA; - -typedef struct FONT -{ - char filename[128]; - FT_Face ft_face; - FONTSIZEDATA sizes[NUM_FONT_SIZES]; -} FONT; - -static int font_get_index(int pixelsize) -{ - int i; - for(i = 0; i < NUM_FONT_SIZES; i++) - { - if(font_sizes[i] >= pixelsize) - return i; - } - - return NUM_FONT_SIZES-1; -} - -FONT *gfx_font_load(const char *filename) -{ - int i; - FONT *font = mem_alloc(sizeof(FONT), 1); - - mem_zero(font, sizeof(*font)); - str_copy(font->filename, filename, sizeof(font->filename)); - - if(FT_New_Face(ft_library, font->filename, 0, &font->ft_face)) - { - mem_free(font); - return NULL; - } - - for(i = 0; i < NUM_FONT_SIZES; i++) - font->sizes[i].font_size = -1; - - return font; -}; - -void gfx_font_destroy(FONT *font) -{ - mem_free(font); -} - -void gfx_font_init() -{ - FT_Init_FreeType(&ft_library); -} - -static void grow(unsigned char *in, unsigned char *out, int w, int h) -{ - int y, x; - for(y = 0; y < h; y++) - for(x = 0; x < w; x++) - { - int c = in[y*w+x]; - int s_y, s_x; - - for(s_y = -1; s_y <= 1; s_y++) - for(s_x = -1; s_x <= 1; s_x++) - { - int get_x = x+s_x; - int get_y = y+s_y; - if (get_x >= 0 && get_y >= 0 && get_x < w && get_y < h) - { - int index = get_y*w+get_x; - if(in[index] > c) - c = in[index]; - } - } - - out[y*w+x] = c; - } -} - -static void font_init_texture(FONTSIZEDATA *sizedata, int charwidth, int charheight, int xchars, int ychars) -{ - static int font_memory_usage = 0; - int i; - int width = charwidth*xchars; - int height = charheight*ychars; - void *mem = mem_alloc(width*height, 1); - mem_zero(mem, width*height); - - if(sizedata->textures[0] == 0) - glGenTextures(2, sizedata->textures); - else - font_memory_usage -= sizedata->texture_width*sizedata->texture_height*2; - - sizedata->num_x_chars = xchars; - sizedata->num_y_chars = ychars; - sizedata->texture_width = width; - sizedata->texture_height = height; - sizedata->current_character = 0; - - for(i = 0; i < 2; i++) - { - glBindTexture(GL_TEXTURE_2D, sizedata->textures[i]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, font_texture_format, width, height, 0, font_texture_format, GL_UNSIGNED_BYTE, mem); - font_memory_usage += width*height; - } - - dbg_msg("", "font memory usage: %d", font_memory_usage); - - mem_free(mem); -} - -static void font_increase_texture_size(FONTSIZEDATA *sizedata) -{ - if(sizedata->texture_width < sizedata->texture_height) - sizedata->num_x_chars <<= 1; - else - sizedata->num_y_chars <<= 1; - font_init_texture(sizedata, sizedata->char_max_width, sizedata->char_max_height, sizedata->num_x_chars, sizedata->num_y_chars); -} - -static void font_init_index(FONT *font, int index) -{ - int outline_thickness = 1; - FONTSIZEDATA *sizedata = &font->sizes[index]; - - sizedata->font_size = font_sizes[index]; - FT_Set_Pixel_Sizes(font->ft_face, 0, sizedata->font_size); - - if(sizedata->font_size >= 18) - outline_thickness = 2; - - { - unsigned glyph_index; - int charcode; - int max_h = 0; - int max_w = 0; - - charcode = FT_Get_First_Char(font->ft_face, &glyph_index); - while(glyph_index != 0) - { - /* do stuff */ - FT_Load_Glyph(font->ft_face, glyph_index, FT_LOAD_DEFAULT); - - if(font->ft_face->glyph->metrics.width > max_w) max_w = font->ft_face->glyph->metrics.width; - if(font->ft_face->glyph->metrics.height > max_h) max_h = font->ft_face->glyph->metrics.height; - charcode = FT_Get_Next_Char(font->ft_face, charcode, &glyph_index); - } - - max_w = (max_w>>6)+2+outline_thickness*2; - max_h = (max_h>>6)+2+outline_thickness*2; - - for(sizedata->char_max_width = 1; sizedata->char_max_width < max_w; sizedata->char_max_width <<= 1); - for(sizedata->char_max_height = 1; sizedata->char_max_height < max_h; sizedata->char_max_height <<= 1); - } - - //dbg_msg("font", "init size %d, texture size %d %d", font->sizes[index].font_size, w, h); - //FT_New_Face(ft_library, "data/fonts/vera.ttf", 0, &font->ft_face); - font_init_texture(sizedata, sizedata->char_max_width, sizedata->char_max_height, 8, 8); -} - -static FONTSIZEDATA *font_get_size(FONT *font, int pixelsize) -{ - int index = font_get_index(pixelsize); - if(font->sizes[index].font_size != font_sizes[index]) - font_init_index(font, index); - return &font->sizes[index]; -} - - -static void font_upload_glyph(FONTSIZEDATA *sizedata, int texnum, int slot_id, int chr, const void *data) -{ - int x = (slot_id%sizedata->num_x_chars) * (sizedata->texture_width/sizedata->num_x_chars); - int y = (slot_id/sizedata->num_x_chars) * (sizedata->texture_height/sizedata->num_y_chars); - - glBindTexture(GL_TEXTURE_2D, sizedata->textures[texnum]); - glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, - sizedata->texture_width/sizedata->num_x_chars, - sizedata->texture_height/sizedata->num_y_chars, - font_texture_format, GL_UNSIGNED_BYTE, data); -} - -/* 8k of data used for rendering glyphs */ -static unsigned char glyphdata[(4096/64) * (4096/64)]; -static unsigned char glyphdata_outlined[(4096/64) * (4096/64)]; - -static int font_get_slot(FONTSIZEDATA *sizedata) -{ - int char_count = sizedata->num_x_chars*sizedata->num_y_chars; - if(sizedata->current_character < char_count) - { - int i = sizedata->current_character; - sizedata->current_character++; - return i; - } - - /* kick out the oldest */ - /* TODO: remove this linear search */ - { - int oldest = 0; - int i; - for(i = 1; i < char_count; i++) - { - if(sizedata->characters[i].touch_time < sizedata->characters[oldest].touch_time) - oldest = i; - } - - if(time_get()-sizedata->characters[oldest].touch_time < time_freq()) - { - font_increase_texture_size(sizedata); - return font_get_slot(sizedata); - } - - return oldest; - } -} - -static int font_render_glyph(FONT *font, FONTSIZEDATA *sizedata, int chr) -{ - FT_Bitmap *bitmap; - int slot_id = 0; - int slot_w = sizedata->texture_width / sizedata->num_x_chars; - int slot_h = sizedata->texture_height / sizedata->num_y_chars; - int slot_size = slot_w*slot_h; - int outline_thickness = 1; - int x = 1; - int y = 1; - int px, py; - - FT_Set_Pixel_Sizes(font->ft_face, 0, sizedata->font_size); - - if(FT_Load_Char(font->ft_face, chr, FT_LOAD_RENDER|FT_LOAD_NO_BITMAP)) - { - dbg_msg("font", "error loading glyph %d", chr); - return -1; - } - - bitmap = &font->ft_face->glyph->bitmap; - - /* fetch slot */ - slot_id = font_get_slot(sizedata); - if(slot_id < 0) - return -1; - - /* adjust spacing */ - if(sizedata->font_size >= 18) - outline_thickness = 2; - x += outline_thickness; - y += outline_thickness; - - /* prepare glyph data */ - mem_zero(glyphdata, slot_size); - - if(bitmap->pixel_mode == FT_PIXEL_MODE_GRAY) - { - for(py = 0; py < bitmap->rows; py++) - for(px = 0; px < bitmap->width; px++) - glyphdata[(py+y)*slot_w+px+x] = bitmap->buffer[py*bitmap->pitch+px]; - } - else if(bitmap->pixel_mode == FT_PIXEL_MODE_MONO) - { - for(py = 0; py < bitmap->rows; py++) - for(px = 0; px < bitmap->width; px++) - { - if(bitmap->buffer[py*bitmap->pitch+px/8]&(1<<(7-(px%8)))) - glyphdata[(py+y)*slot_w+px+x] = 255; - } - } - - if(0) for(py = 0; py < slot_w; py++) - for(px = 0; px < slot_h; px++) - glyphdata[py*slot_w+px] = 255; - - /* upload the glyph */ - font_upload_glyph(sizedata, 0, slot_id, chr, glyphdata); - - if(outline_thickness == 1) - { - grow(glyphdata, glyphdata_outlined, slot_w, slot_h); - font_upload_glyph(sizedata, 1, slot_id, chr, glyphdata_outlined); - } - else - { - grow(glyphdata, glyphdata_outlined, slot_w, slot_h); - grow(glyphdata_outlined, glyphdata, slot_w, slot_h); - font_upload_glyph(sizedata, 1, slot_id, chr, glyphdata); - } - - /* set char info */ - { - FONTCHAR *fontchr = &sizedata->characters[slot_id]; - float scale = 1.0f/sizedata->font_size; - float uscale = 1.0f/sizedata->texture_width; - float vscale = 1.0f/sizedata->texture_height; - int height = bitmap->rows + outline_thickness*2 + 2; - int width = bitmap->width + outline_thickness*2 + 2; - - fontchr->id = chr; - fontchr->height = height * scale; - fontchr->width = width * scale; - fontchr->offset_x = (font->ft_face->glyph->bitmap_left-1) * scale; - fontchr->offset_y = (sizedata->font_size - font->ft_face->glyph->bitmap_top) * scale; - fontchr->advance_x = (font->ft_face->glyph->advance.x>>6) * scale; - - fontchr->uvs[0] = (slot_id%sizedata->num_x_chars) / (float)(sizedata->num_x_chars); - fontchr->uvs[1] = (slot_id/sizedata->num_x_chars) / (float)(sizedata->num_y_chars); - fontchr->uvs[2] = fontchr->uvs[0] + width*uscale; - fontchr->uvs[3] = fontchr->uvs[1] + height*vscale; - } - - return slot_id; -} - -static FONTCHAR *font_get_char(FONT *font, FONTSIZEDATA *sizedata, int chr) -{ - FONTCHAR *fontchr = NULL; - - /* search for the character */ - /* TODO: remove this linear search */ - int i; - for(i = 0; i < sizedata->current_character; i++) - { - if(sizedata->characters[i].id == chr) - { - fontchr = &sizedata->characters[i]; - break; - } - } - - /* check if we need to render the character */ - if(!fontchr) - { - int index = font_render_glyph(font, sizedata, chr); - if(index >= 0) - fontchr = &sizedata->characters[index]; - } - - /* touch the character */ - /* TODO: don't call time_get here */ - if(fontchr) - fontchr->touch_time = time_get(); - - return fontchr; -} - -/* must only be called from the rendering function as the font must be set to the correct size */ -static void font_render_setup(FONT *font, int size) -{ - FT_Set_Pixel_Sizes(font->ft_face, 0, size); -} - -static float font_kerning(FONT *font, int left, int right) -{ - FT_Vector kerning = {0,0}; - FT_Get_Kerning(font->ft_face, left, right, FT_KERNING_DEFAULT, &kerning); - return (kerning.x>>6); -} - -void gfx_text_ex(TEXT_CURSOR *cursor, const char *text, int length) -{ - FONT *font = cursor->font; - FONTSIZEDATA *sizedata = NULL; - - float screen_x0, screen_y0, screen_x1, screen_y1; - float fake_to_screen_x, fake_to_screen_y; - int actual_x, actual_y; - - int actual_size; - int i; - int got_new_line = 0; - float draw_x, draw_y; - float cursor_x, cursor_y; - const char *end; - - float size = cursor->font_size; - - /* to correct coords, convert to screen coords, round, and convert back */ - gfx_getscreen(&screen_x0, &screen_y0, &screen_x1, &screen_y1); - - fake_to_screen_x = (gfx_screenwidth()/(screen_x1-screen_x0)); - fake_to_screen_y = (gfx_screenheight()/(screen_y1-screen_y0)); - actual_x = cursor->x * fake_to_screen_x; - actual_y = cursor->y * fake_to_screen_y; - - cursor_x = actual_x / fake_to_screen_x; - cursor_y = actual_y / fake_to_screen_y; - - /* same with size */ - actual_size = size * fake_to_screen_y; - size = actual_size / fake_to_screen_y; - - /* fetch font data */ - if(!font) - font = default_font; - - if(!font) - return; - - sizedata = font_get_size(font, actual_size); - font_render_setup(font, actual_size); - - /* set length */ - if(length < 0) - length = strlen(text); - - end = text + length; - - /* if we don't want to render, we can just skip the first outline pass */ - i = 1; - if(cursor->flags&TEXTFLAG_RENDER) - i = 0; - - for(;i < 2; i++) - { - const char *current = (char *)text; - const char *end = current+length; - draw_x = cursor_x; - draw_y = cursor_y; - - if(cursor->flags&TEXTFLAG_RENDER) - { - // TODO: Make this better - glEnable(GL_TEXTURE_2D); - if (i == 0) - glBindTexture(GL_TEXTURE_2D, sizedata->textures[1]); - else - glBindTexture(GL_TEXTURE_2D, sizedata->textures[0]); - - gfx_quads_begin(); - if (i == 0) - gfx_setcolor(0.0f, 0.0f, 0.0f, 0.3f*text_a); - else - gfx_setcolor(text_r, text_g, text_b, text_a); - } - - while(current < end) - { - int new_line = 0; - const char *batch_end = end; - if(cursor->line_width > 0 && !(cursor->flags&TEXTFLAG_STOP_AT_END)) - { - int wlen = word_length((char *)current); - TEXT_CURSOR compare = *cursor; - compare.x = draw_x; - compare.y = draw_y; - compare.flags &= ~TEXTFLAG_RENDER; - compare.line_width = -1; - gfx_text_ex(&compare, text, wlen); - - if(compare.x-draw_x > cursor->line_width) - { - /* word can't be fitted in one line, cut it */ - TEXT_CURSOR cutter = *cursor; - cutter.charcount = 0; - cutter.x = draw_x; - cutter.y = draw_y; - cutter.flags &= ~TEXTFLAG_RENDER; - cutter.flags |= TEXTFLAG_STOP_AT_END; - - gfx_text_ex(&cutter, (const char *)current, wlen); - wlen = cutter.charcount; - new_line = 1; - - if(wlen <= 3) /* if we can't place 3 chars of the word on this line, take the next */ - wlen = 0; - } - else if(compare.x-cursor->start_x > cursor->line_width) - { - new_line = 1; - wlen = 0; - } - - batch_end = current + wlen; - } - - while(current < batch_end) - { - const char *tmp; - float advance = 0; - int character = 0; - int nextcharacter = 0; - FONTCHAR *chr; - - // TODO: UTF-8 decode - character = str_utf8_decode(¤t); - tmp = current; - nextcharacter = str_utf8_decode(&tmp); - - if(character == '\n') - { - draw_x = cursor->start_x; - draw_y += size; - draw_x = (int)(draw_x * fake_to_screen_x) / fake_to_screen_x; /* realign */ - draw_y = (int)(draw_y * fake_to_screen_y) / fake_to_screen_y; - continue; - } - - chr = font_get_char(font, sizedata, character); - - if(chr) - { - if(cursor->flags&TEXTFLAG_RENDER) - { - gfx_quads_setsubset(chr->uvs[0], chr->uvs[1], chr->uvs[2], chr->uvs[3]); - gfx_quads_drawTL(draw_x+chr->offset_x*size, draw_y+chr->offset_y*size, chr->width*size, chr->height*size); - } - - advance = chr->advance_x + font_kerning(font, character, nextcharacter)/size; - } - - if(cursor->flags&TEXTFLAG_STOP_AT_END && draw_x+advance*size-cursor->start_x > cursor->line_width) - { - /* we hit the end of the line, no more to render or count */ - current = end; - break; - } - - draw_x += advance*size; - cursor->charcount++; - } - - if(new_line) - { - draw_x = cursor->start_x; - draw_y += size; - got_new_line = 1; - draw_x = (int)(draw_x * fake_to_screen_x) / fake_to_screen_x; /* realign */ - draw_y = (int)(draw_y * fake_to_screen_y) / fake_to_screen_y; - } - } - - if(cursor->flags&TEXTFLAG_RENDER) - gfx_quads_end(); - } - - cursor->x = draw_x; - - if(got_new_line) - cursor->y = draw_y; -} diff --git a/src/engine/client/ec_gfx_text.cpp b/src/engine/client/ec_gfx_text.cpp new file mode 100644 index 00000000..d17d1bed --- /dev/null +++ b/src/engine/client/ec_gfx_text.cpp @@ -0,0 +1,669 @@ +#include +#include +#include +#include + +extern IEngineGraphics *Graphics(); + +#ifdef CONF_PLATFORM_MACOSX + #include + #include +#else + #include + #include +#endif + +static int word_length(const char *text) +{ + int s = 1; + while(1) + { + if(*text == 0) + return s-1; + if(*text == '\n' || *text == '\t' || *text == ' ') + return s; + text++; + s++; + } +} + +static float text_r=1; +static float text_g=1; +static float text_b=1; +static float text_a=1; + +static struct FONT *default_font = 0; +void gfx_text_set_default_font(struct FONT *font) +{ + default_font = font; +} + + +void gfx_text_set_cursor(TEXT_CURSOR *cursor, float x, float y, float font_size, int flags) +{ + mem_zero(cursor, sizeof(*cursor)); + cursor->font_size = font_size; + cursor->start_x = x; + cursor->start_y = y; + cursor->x = x; + cursor->y = y; + cursor->line_count = 1; + cursor->line_width = -1; + cursor->flags = flags; + cursor->charcount = 0; +} + + +void gfx_text(void *font_set_v, float x, float y, float size, const char *text, int max_width) +{ + TEXT_CURSOR cursor; + gfx_text_set_cursor(&cursor, x, y, size, TEXTFLAG_RENDER); + cursor.line_width = max_width; + gfx_text_ex(&cursor, text, -1); +} + +float gfx_text_width(void *font_set_v, float size, const char *text, int length) +{ + TEXT_CURSOR cursor; + gfx_text_set_cursor(&cursor, 0, 0, size, 0); + gfx_text_ex(&cursor, text, length); + return cursor.x; +} + +void gfx_text_color(float r, float g, float b, float a) +{ + text_r = r; + text_g = g; + text_b = b; + text_a = a; +} + +/* ft2 texture */ +#include +#include FT_FREETYPE_H + +static FT_Library ft_library; + +#define MAX_CHARACTERS 64 + + +/* GL_LUMINANCE can be good for debugging*/ +static int font_texture_format = GL_ALPHA; + + +static int font_sizes[] = {8,9,10,11,12,13,14,15,16,17,18,19,20,36}; +#define NUM_FONT_SIZES (sizeof(font_sizes)/sizeof(int)) + + +typedef struct FONTCHAR +{ + int id; + + /* these values are scaled to the font size */ + /* width * font_size == real_size */ + float width; + float height; + float offset_x; + float offset_y; + float advance_x; + + float uvs[4]; + int64 touch_time; +} FONTCHAR; + +typedef struct FONTSIZEDATA +{ + int font_size; + FT_Face *face; + + unsigned textures[2]; + int texture_width; + int texture_height; + + int num_x_chars; + int num_y_chars; + + int char_max_width; + int char_max_height; + + FONTCHAR characters[MAX_CHARACTERS*MAX_CHARACTERS]; + + int current_character; +} FONTSIZEDATA; + +typedef struct FONT +{ + char filename[128]; + FT_Face ft_face; + FONTSIZEDATA sizes[NUM_FONT_SIZES]; +} FONT; + +static int font_get_index(int pixelsize) +{ + for(unsigned i = 0; i < NUM_FONT_SIZES; i++) + { + if(font_sizes[i] >= pixelsize) + return i; + } + + return NUM_FONT_SIZES-1; +} + +FONT *gfx_font_load(const char *filename) +{ + FONT *font = (FONT *)mem_alloc(sizeof(FONT), 1); + + mem_zero(font, sizeof(*font)); + str_copy(font->filename, filename, sizeof(font->filename)); + + if(FT_New_Face(ft_library, font->filename, 0, &font->ft_face)) + { + mem_free(font); + return NULL; + } + + for(unsigned i = 0; i < NUM_FONT_SIZES; i++) + font->sizes[i].font_size = -1; + + return font; +}; + +void gfx_font_destroy(FONT *font) +{ + mem_free(font); +} + +void gfx_font_init() +{ + FT_Init_FreeType(&ft_library); +} + +static void grow(unsigned char *in, unsigned char *out, int w, int h) +{ + int y, x; + for(y = 0; y < h; y++) + for(x = 0; x < w; x++) + { + int c = in[y*w+x]; + int s_y, s_x; + + for(s_y = -1; s_y <= 1; s_y++) + for(s_x = -1; s_x <= 1; s_x++) + { + int get_x = x+s_x; + int get_y = y+s_y; + if (get_x >= 0 && get_y >= 0 && get_x < w && get_y < h) + { + int index = get_y*w+get_x; + if(in[index] > c) + c = in[index]; + } + } + + out[y*w+x] = c; + } +} + +static void font_init_texture(FONTSIZEDATA *sizedata, int charwidth, int charheight, int xchars, int ychars) +{ + static int font_memory_usage = 0; + int i; + int width = charwidth*xchars; + int height = charheight*ychars; + void *mem = mem_alloc(width*height, 1); + mem_zero(mem, width*height); + + if(sizedata->textures[0] == 0) + glGenTextures(2, sizedata->textures); + else + font_memory_usage -= sizedata->texture_width*sizedata->texture_height*2; + + sizedata->num_x_chars = xchars; + sizedata->num_y_chars = ychars; + sizedata->texture_width = width; + sizedata->texture_height = height; + sizedata->current_character = 0; + + for(i = 0; i < 2; i++) + { + glBindTexture(GL_TEXTURE_2D, sizedata->textures[i]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, font_texture_format, width, height, 0, font_texture_format, GL_UNSIGNED_BYTE, mem); + font_memory_usage += width*height; + } + + dbg_msg("", "font memory usage: %d", font_memory_usage); + + mem_free(mem); +} + +static void font_increase_texture_size(FONTSIZEDATA *sizedata) +{ + if(sizedata->texture_width < sizedata->texture_height) + sizedata->num_x_chars <<= 1; + else + sizedata->num_y_chars <<= 1; + font_init_texture(sizedata, sizedata->char_max_width, sizedata->char_max_height, sizedata->num_x_chars, sizedata->num_y_chars); +} + +static void font_init_index(FONT *font, int index) +{ + int outline_thickness = 1; + FONTSIZEDATA *sizedata = &font->sizes[index]; + + sizedata->font_size = font_sizes[index]; + FT_Set_Pixel_Sizes(font->ft_face, 0, sizedata->font_size); + + if(sizedata->font_size >= 18) + outline_thickness = 2; + + { + unsigned glyph_index; + int charcode; + int max_h = 0; + int max_w = 0; + + charcode = FT_Get_First_Char(font->ft_face, &glyph_index); + while(glyph_index != 0) + { + /* do stuff */ + FT_Load_Glyph(font->ft_face, glyph_index, FT_LOAD_DEFAULT); + + if(font->ft_face->glyph->metrics.width > max_w) max_w = font->ft_face->glyph->metrics.width; + if(font->ft_face->glyph->metrics.height > max_h) max_h = font->ft_face->glyph->metrics.height; + charcode = FT_Get_Next_Char(font->ft_face, charcode, &glyph_index); + } + + max_w = (max_w>>6)+2+outline_thickness*2; + max_h = (max_h>>6)+2+outline_thickness*2; + + for(sizedata->char_max_width = 1; sizedata->char_max_width < max_w; sizedata->char_max_width <<= 1); + for(sizedata->char_max_height = 1; sizedata->char_max_height < max_h; sizedata->char_max_height <<= 1); + } + + //dbg_msg("font", "init size %d, texture size %d %d", font->sizes[index].font_size, w, h); + //FT_New_Face(ft_library, "data/fonts/vera.ttf", 0, &font->ft_face); + font_init_texture(sizedata, sizedata->char_max_width, sizedata->char_max_height, 8, 8); +} + +static FONTSIZEDATA *font_get_size(FONT *font, int pixelsize) +{ + int index = font_get_index(pixelsize); + if(font->sizes[index].font_size != font_sizes[index]) + font_init_index(font, index); + return &font->sizes[index]; +} + + +static void font_upload_glyph(FONTSIZEDATA *sizedata, int texnum, int slot_id, int chr, const void *data) +{ + int x = (slot_id%sizedata->num_x_chars) * (sizedata->texture_width/sizedata->num_x_chars); + int y = (slot_id/sizedata->num_x_chars) * (sizedata->texture_height/sizedata->num_y_chars); + + glBindTexture(GL_TEXTURE_2D, sizedata->textures[texnum]); + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, + sizedata->texture_width/sizedata->num_x_chars, + sizedata->texture_height/sizedata->num_y_chars, + font_texture_format, GL_UNSIGNED_BYTE, data); +} + +/* 8k of data used for rendering glyphs */ +static unsigned char glyphdata[(4096/64) * (4096/64)]; +static unsigned char glyphdata_outlined[(4096/64) * (4096/64)]; + +static int font_get_slot(FONTSIZEDATA *sizedata) +{ + int char_count = sizedata->num_x_chars*sizedata->num_y_chars; + if(sizedata->current_character < char_count) + { + int i = sizedata->current_character; + sizedata->current_character++; + return i; + } + + /* kick out the oldest */ + /* TODO: remove this linear search */ + { + int oldest = 0; + int i; + for(i = 1; i < char_count; i++) + { + if(sizedata->characters[i].touch_time < sizedata->characters[oldest].touch_time) + oldest = i; + } + + if(time_get()-sizedata->characters[oldest].touch_time < time_freq()) + { + font_increase_texture_size(sizedata); + return font_get_slot(sizedata); + } + + return oldest; + } +} + +static int font_render_glyph(FONT *font, FONTSIZEDATA *sizedata, int chr) +{ + FT_Bitmap *bitmap; + int slot_id = 0; + int slot_w = sizedata->texture_width / sizedata->num_x_chars; + int slot_h = sizedata->texture_height / sizedata->num_y_chars; + int slot_size = slot_w*slot_h; + int outline_thickness = 1; + int x = 1; + int y = 1; + int px, py; + + FT_Set_Pixel_Sizes(font->ft_face, 0, sizedata->font_size); + + if(FT_Load_Char(font->ft_face, chr, FT_LOAD_RENDER|FT_LOAD_NO_BITMAP)) + { + dbg_msg("font", "error loading glyph %d", chr); + return -1; + } + + bitmap = &font->ft_face->glyph->bitmap; + + /* fetch slot */ + slot_id = font_get_slot(sizedata); + if(slot_id < 0) + return -1; + + /* adjust spacing */ + if(sizedata->font_size >= 18) + outline_thickness = 2; + x += outline_thickness; + y += outline_thickness; + + /* prepare glyph data */ + mem_zero(glyphdata, slot_size); + + if(bitmap->pixel_mode == FT_PIXEL_MODE_GRAY) + { + for(py = 0; py < bitmap->rows; py++) + for(px = 0; px < bitmap->width; px++) + glyphdata[(py+y)*slot_w+px+x] = bitmap->buffer[py*bitmap->pitch+px]; + } + else if(bitmap->pixel_mode == FT_PIXEL_MODE_MONO) + { + for(py = 0; py < bitmap->rows; py++) + for(px = 0; px < bitmap->width; px++) + { + if(bitmap->buffer[py*bitmap->pitch+px/8]&(1<<(7-(px%8)))) + glyphdata[(py+y)*slot_w+px+x] = 255; + } + } + + if(0) for(py = 0; py < slot_w; py++) + for(px = 0; px < slot_h; px++) + glyphdata[py*slot_w+px] = 255; + + /* upload the glyph */ + font_upload_glyph(sizedata, 0, slot_id, chr, glyphdata); + + if(outline_thickness == 1) + { + grow(glyphdata, glyphdata_outlined, slot_w, slot_h); + font_upload_glyph(sizedata, 1, slot_id, chr, glyphdata_outlined); + } + else + { + grow(glyphdata, glyphdata_outlined, slot_w, slot_h); + grow(glyphdata_outlined, glyphdata, slot_w, slot_h); + font_upload_glyph(sizedata, 1, slot_id, chr, glyphdata); + } + + /* set char info */ + { + FONTCHAR *fontchr = &sizedata->characters[slot_id]; + float scale = 1.0f/sizedata->font_size; + float uscale = 1.0f/sizedata->texture_width; + float vscale = 1.0f/sizedata->texture_height; + int height = bitmap->rows + outline_thickness*2 + 2; + int width = bitmap->width + outline_thickness*2 + 2; + + fontchr->id = chr; + fontchr->height = height * scale; + fontchr->width = width * scale; + fontchr->offset_x = (font->ft_face->glyph->bitmap_left-1) * scale; + fontchr->offset_y = (sizedata->font_size - font->ft_face->glyph->bitmap_top) * scale; + fontchr->advance_x = (font->ft_face->glyph->advance.x>>6) * scale; + + fontchr->uvs[0] = (slot_id%sizedata->num_x_chars) / (float)(sizedata->num_x_chars); + fontchr->uvs[1] = (slot_id/sizedata->num_x_chars) / (float)(sizedata->num_y_chars); + fontchr->uvs[2] = fontchr->uvs[0] + width*uscale; + fontchr->uvs[3] = fontchr->uvs[1] + height*vscale; + } + + return slot_id; +} + +static FONTCHAR *font_get_char(FONT *font, FONTSIZEDATA *sizedata, int chr) +{ + FONTCHAR *fontchr = NULL; + + /* search for the character */ + /* TODO: remove this linear search */ + int i; + for(i = 0; i < sizedata->current_character; i++) + { + if(sizedata->characters[i].id == chr) + { + fontchr = &sizedata->characters[i]; + break; + } + } + + /* check if we need to render the character */ + if(!fontchr) + { + int index = font_render_glyph(font, sizedata, chr); + if(index >= 0) + fontchr = &sizedata->characters[index]; + } + + /* touch the character */ + /* TODO: don't call time_get here */ + if(fontchr) + fontchr->touch_time = time_get(); + + return fontchr; +} + +/* must only be called from the rendering function as the font must be set to the correct size */ +static void font_render_setup(FONT *font, int size) +{ + FT_Set_Pixel_Sizes(font->ft_face, 0, size); +} + +static float font_kerning(FONT *font, int left, int right) +{ + FT_Vector kerning = {0,0}; + FT_Get_Kerning(font->ft_face, left, right, FT_KERNING_DEFAULT, &kerning); + return (kerning.x>>6); +} + + +void gfx_text_ex(TEXT_CURSOR *cursor, const char *text, int length) +{ + FONT *font = cursor->font; + FONTSIZEDATA *sizedata = NULL; + + float screen_x0, screen_y0, screen_x1, screen_y1; + float fake_to_screen_x, fake_to_screen_y; + int actual_x, actual_y; + + int actual_size; + int i; + int got_new_line = 0; + float draw_x, draw_y; + float cursor_x, cursor_y; + const char *end; + + float size = cursor->font_size; + + /* to correct coords, convert to screen coords, round, and convert back */ + Graphics()->GetScreen(&screen_x0, &screen_y0, &screen_x1, &screen_y1); + + fake_to_screen_x = (Graphics()->ScreenWidth()/(screen_x1-screen_x0)); + fake_to_screen_y = (Graphics()->ScreenHeight()/(screen_y1-screen_y0)); + actual_x = cursor->x * fake_to_screen_x; + actual_y = cursor->y * fake_to_screen_y; + + cursor_x = actual_x / fake_to_screen_x; + cursor_y = actual_y / fake_to_screen_y; + + /* same with size */ + actual_size = size * fake_to_screen_y; + size = actual_size / fake_to_screen_y; + + /* fetch font data */ + if(!font) + font = default_font; + + if(!font) + return; + + sizedata = font_get_size(font, actual_size); + font_render_setup(font, actual_size); + + /* set length */ + if(length < 0) + length = strlen(text); + + end = text + length; + + /* if we don't want to render, we can just skip the first outline pass */ + i = 1; + if(cursor->flags&TEXTFLAG_RENDER) + i = 0; + + for(;i < 2; i++) + { + const char *current = (char *)text; + const char *end = current+length; + draw_x = cursor_x; + draw_y = cursor_y; + + if(cursor->flags&TEXTFLAG_RENDER) + { + // TODO: Make this better + glEnable(GL_TEXTURE_2D); + if (i == 0) + glBindTexture(GL_TEXTURE_2D, sizedata->textures[1]); + else + glBindTexture(GL_TEXTURE_2D, sizedata->textures[0]); + + Graphics()->QuadsBegin(); + if (i == 0) + Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.3f*text_a); + else + Graphics()->SetColor(text_r, text_g, text_b, text_a); + } + + while(current < end) + { + int new_line = 0; + const char *batch_end = end; + if(cursor->line_width > 0 && !(cursor->flags&TEXTFLAG_STOP_AT_END)) + { + int wlen = word_length((char *)current); + TEXT_CURSOR compare = *cursor; + compare.x = draw_x; + compare.y = draw_y; + compare.flags &= ~TEXTFLAG_RENDER; + compare.line_width = -1; + gfx_text_ex(&compare, text, wlen); + + if(compare.x-draw_x > cursor->line_width) + { + /* word can't be fitted in one line, cut it */ + TEXT_CURSOR cutter = *cursor; + cutter.charcount = 0; + cutter.x = draw_x; + cutter.y = draw_y; + cutter.flags &= ~TEXTFLAG_RENDER; + cutter.flags |= TEXTFLAG_STOP_AT_END; + + gfx_text_ex(&cutter, (const char *)current, wlen); + wlen = cutter.charcount; + new_line = 1; + + if(wlen <= 3) /* if we can't place 3 chars of the word on this line, take the next */ + wlen = 0; + } + else if(compare.x-cursor->start_x > cursor->line_width) + { + new_line = 1; + wlen = 0; + } + + batch_end = current + wlen; + } + + while(current < batch_end) + { + const char *tmp; + float advance = 0; + int character = 0; + int nextcharacter = 0; + FONTCHAR *chr; + + // TODO: UTF-8 decode + character = str_utf8_decode(¤t); + tmp = current; + nextcharacter = str_utf8_decode(&tmp); + + if(character == '\n') + { + draw_x = cursor->start_x; + draw_y += size; + draw_x = (int)(draw_x * fake_to_screen_x) / fake_to_screen_x; /* realign */ + draw_y = (int)(draw_y * fake_to_screen_y) / fake_to_screen_y; + continue; + } + + chr = font_get_char(font, sizedata, character); + + if(chr) + { + if(cursor->flags&TEXTFLAG_RENDER) + { + Graphics()->QuadsSetSubset(chr->uvs[0], chr->uvs[1], chr->uvs[2], chr->uvs[3]); + Graphics()->QuadsDrawTL(draw_x+chr->offset_x*size, draw_y+chr->offset_y*size, chr->width*size, chr->height*size); + } + + advance = chr->advance_x + font_kerning(font, character, nextcharacter)/size; + } + + if(cursor->flags&TEXTFLAG_STOP_AT_END && draw_x+advance*size-cursor->start_x > cursor->line_width) + { + /* we hit the end of the line, no more to render or count */ + current = end; + break; + } + + draw_x += advance*size; + cursor->charcount++; + } + + if(new_line) + { + draw_x = cursor->start_x; + draw_y += size; + got_new_line = 1; + draw_x = (int)(draw_x * fake_to_screen_x) / fake_to_screen_x; /* realign */ + draw_y = (int)(draw_y * fake_to_screen_y) / fake_to_screen_y; + } + } + + if(cursor->flags&TEXTFLAG_RENDER) + Graphics()->QuadsEnd(); + } + + cursor->x = draw_x; + + if(got_new_line) + cursor->y = draw_y; +} diff --git a/src/engine/client/ec_inp.c b/src/engine/client/ec_inp.c deleted file mode 100644 index 495614d6..00000000 --- a/src/engine/client/ec_inp.c +++ /dev/null @@ -1,230 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include "SDL.h" - -#include -#include -#include - -static struct -{ - unsigned char presses; - unsigned char releases; -} input_count[2][1024] = {{{0}}, {{0}}}; - -static unsigned char input_state[2][1024] = {{0}, {0}}; - -static int input_current = 0; -static int input_grabbed = 0; - -static unsigned int last_release = 0; -static unsigned int release_delta = -1; - -void inp_mouse_relative(int *x, int *y) -{ - int nx = 0, ny = 0; - float sens = config.inp_mousesens/100.0f; - - if(config.inp_grab) - SDL_GetRelativeMouseState(&nx, &ny); - else - { - if(input_grabbed) - { - SDL_GetMouseState(&nx,&ny); - SDL_WarpMouse(gfx_screenwidth()/2,gfx_screenheight()/2); - nx -= gfx_screenwidth()/2; ny -= gfx_screenheight()/2; - } - } - - *x = nx*sens; - *y = ny*sens; -} - -enum -{ - INPUT_BUFFER_SIZE=32 -}; - -static INPUT_EVENT input_events[INPUT_BUFFER_SIZE]; -static int num_events = 0; - -static void add_event(int unicode, int key, int flags) -{ - if(num_events != INPUT_BUFFER_SIZE) - { - input_events[num_events].unicode = unicode; - input_events[num_events].key = key; - input_events[num_events].flags = flags; - num_events++; - } -} - -int inp_num_events() -{ - return num_events; -} - -void inp_clear_events() -{ - num_events = 0; -} - -INPUT_EVENT inp_get_event(int index) -{ - if(index < 0 || index >= num_events) - { - INPUT_EVENT e = {0,0}; - return e; - } - - return input_events[index]; -} - -void inp_init() -{ - SDL_EnableUNICODE(1); - SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); -} - -void inp_mouse_mode_absolute() -{ - SDL_ShowCursor(1); - input_grabbed = 0; - if(config.inp_grab) - SDL_WM_GrabInput(SDL_GRAB_OFF); -} - -void inp_mouse_mode_relative() -{ - SDL_ShowCursor(0); - input_grabbed = 1; - if(config.inp_grab) - SDL_WM_GrabInput(SDL_GRAB_ON); -} - -int inp_mouse_doubleclick() -{ - return release_delta < (time_freq() >> 2); -} - -void inp_clear_key_states() -{ - mem_zero(input_state, sizeof(input_state)); - mem_zero(input_count, sizeof(input_count)); -} - -int inp_key_presses(int key) -{ - return input_count[input_current][key].presses; -} - -int inp_key_releases(int key) -{ - return input_count[input_current][key].releases; -} - -int inp_key_state(int key) -{ - return input_state[input_current][key]; -} - -int inp_key_pressed(int key) { return input_state[input_current][key]; } -int inp_key_was_pressed(int key) { return input_state[input_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_state[input_current][button]; } - -void inp_update() -{ - int i; - - if(input_grabbed && !gfx_window_active()) - inp_mouse_mode_absolute(); - - /*if(!input_grabbed && gfx_window_active()) - inp_mouse_mode_relative();*/ - - /* clear and begin count on the other one */ - input_current^=1; - mem_zero(&input_count[input_current], sizeof(input_count[input_current])); - mem_zero(&input_state[input_current], sizeof(input_state[input_current])); - - { - Uint8 *state = SDL_GetKeyState(&i); - if(i >= KEY_LAST) - i = KEY_LAST-1; - mem_copy(input_state[input_current], state, i); - } - - /* these states must always be updated manually because they are not in the GetKeyState from SDL */ - i = SDL_GetMouseState(NULL, NULL); - if(i&SDL_BUTTON(1)) input_state[input_current][KEY_MOUSE_1] = 1; /* 1 is left */ - if(i&SDL_BUTTON(3)) input_state[input_current][KEY_MOUSE_2] = 1; /* 3 is right */ - if(i&SDL_BUTTON(2)) input_state[input_current][KEY_MOUSE_3] = 1; /* 2 is middle */ - if(i&SDL_BUTTON(4)) input_state[input_current][KEY_MOUSE_4] = 1; - if(i&SDL_BUTTON(5)) input_state[input_current][KEY_MOUSE_5] = 1; - if(i&SDL_BUTTON(6)) input_state[input_current][KEY_MOUSE_6] = 1; - if(i&SDL_BUTTON(7)) input_state[input_current][KEY_MOUSE_7] = 1; - if(i&SDL_BUTTON(8)) input_state[input_current][KEY_MOUSE_8] = 1; - - { - SDL_Event event; - - while(SDL_PollEvent(&event)) - { - int key = -1; - int action = INPFLAG_PRESS; - switch (event.type) - { - /* handle keys */ - case SDL_KEYDOWN: - /*if(event.key.keysym.unicode < 255) */ - add_event(event.key.keysym.unicode, 0, 0); - key = event.key.keysym.sym; - break; - case SDL_KEYUP: - action = INPFLAG_RELEASE; - key = event.key.keysym.sym; - break; - - /* handle mouse buttons */ - case SDL_MOUSEBUTTONUP: - action = INPFLAG_RELEASE; - - if(event.button.button == 1) - { - release_delta = time_get() - last_release; - last_release = time_get(); - } - - /* fall through */ - case SDL_MOUSEBUTTONDOWN: - if(event.button.button == SDL_BUTTON_LEFT) key = KEY_MOUSE_1; - if(event.button.button == SDL_BUTTON_RIGHT) key = KEY_MOUSE_2; - if(event.button.button == SDL_BUTTON_MIDDLE) key = KEY_MOUSE_3; - if(event.button.button == SDL_BUTTON_WHEELUP) key = KEY_MOUSE_WHEEL_UP; - if(event.button.button == SDL_BUTTON_WHEELDOWN) key = KEY_MOUSE_WHEEL_DOWN; - if(event.button.button == 6) key = KEY_MOUSE_6; - if(event.button.button == 7) key = KEY_MOUSE_7; - if(event.button.button == 8) key = KEY_MOUSE_8; - break; - - /* other messages */ - case SDL_QUIT: - /* TODO: cleaner exit */ - exit(0); - break; - } - - /* */ - if(key != -1) - { - input_count[input_current][key].presses++; - if(action == INPFLAG_PRESS) - input_state[input_current][key] = 1; - add_event(0, key, action); - } - - } - } -} diff --git a/src/engine/client/ec_inp.cpp b/src/engine/client/ec_inp.cpp new file mode 100644 index 00000000..cf956471 --- /dev/null +++ b/src/engine/client/ec_inp.cpp @@ -0,0 +1,234 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include "SDL.h" + +#include +#include +#include +#include + +static struct +{ + unsigned char presses; + unsigned char releases; +} input_count[2][1024] = {{{0}}, {{0}}}; + +static unsigned char input_state[2][1024] = {{0}, {0}}; + +static int input_current = 0; +static int input_grabbed = 0; + +static unsigned int last_release = 0; +static unsigned int release_delta = -1; + +// TODO: Refactor: Remove this +extern IEngineGraphics *Graphics(); + +void inp_mouse_relative(int *x, int *y) +{ + int nx = 0, ny = 0; + float sens = config.inp_mousesens/100.0f; + + if(config.inp_grab) + SDL_GetRelativeMouseState(&nx, &ny); + else + { + if(input_grabbed) + { + SDL_GetMouseState(&nx,&ny); + SDL_WarpMouse(Graphics()->ScreenWidth()/2,Graphics()->ScreenHeight()/2); + nx -= Graphics()->ScreenWidth()/2; ny -= Graphics()->ScreenHeight()/2; + } + } + + *x = nx*sens; + *y = ny*sens; +} + +enum +{ + INPUT_BUFFER_SIZE=32 +}; + +static INPUT_EVENT input_events[INPUT_BUFFER_SIZE]; +static int num_events = 0; + +static void add_event(int unicode, int key, int flags) +{ + if(num_events != INPUT_BUFFER_SIZE) + { + input_events[num_events].unicode = unicode; + input_events[num_events].key = key; + input_events[num_events].flags = flags; + num_events++; + } +} + +int inp_num_events() +{ + return num_events; +} + +void inp_clear_events() +{ + num_events = 0; +} + +INPUT_EVENT inp_get_event(int index) +{ + if(index < 0 || index >= num_events) + { + INPUT_EVENT e = {0,0}; + return e; + } + + return input_events[index]; +} + +void inp_init() +{ + SDL_EnableUNICODE(1); + SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); +} + +void inp_mouse_mode_absolute() +{ + SDL_ShowCursor(1); + input_grabbed = 0; + if(config.inp_grab) + SDL_WM_GrabInput(SDL_GRAB_OFF); +} + +void inp_mouse_mode_relative() +{ + SDL_ShowCursor(0); + input_grabbed = 1; + if(config.inp_grab) + SDL_WM_GrabInput(SDL_GRAB_ON); +} + +int inp_mouse_doubleclick() +{ + return release_delta < (time_freq() >> 2); +} + +void inp_clear_key_states() +{ + mem_zero(input_state, sizeof(input_state)); + mem_zero(input_count, sizeof(input_count)); +} + +int inp_key_presses(int key) +{ + return input_count[input_current][key].presses; +} + +int inp_key_releases(int key) +{ + return input_count[input_current][key].releases; +} + +int inp_key_state(int key) +{ + return input_state[input_current][key]; +} + +int inp_key_pressed(int key) { return input_state[input_current][key]; } +int inp_key_was_pressed(int key) { return input_state[input_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_state[input_current][button]; } + +void inp_update() +{ + int i; + + if(input_grabbed && !Graphics()->WindowActive()) + inp_mouse_mode_absolute(); + + /*if(!input_grabbed && Graphics()->WindowActive()) + inp_mouse_mode_relative();*/ + + /* clear and begin count on the other one */ + input_current^=1; + mem_zero(&input_count[input_current], sizeof(input_count[input_current])); + mem_zero(&input_state[input_current], sizeof(input_state[input_current])); + + { + Uint8 *state = SDL_GetKeyState(&i); + if(i >= KEY_LAST) + i = KEY_LAST-1; + mem_copy(input_state[input_current], state, i); + } + + /* these states must always be updated manually because they are not in the GetKeyState from SDL */ + i = SDL_GetMouseState(NULL, NULL); + if(i&SDL_BUTTON(1)) input_state[input_current][KEY_MOUSE_1] = 1; /* 1 is left */ + if(i&SDL_BUTTON(3)) input_state[input_current][KEY_MOUSE_2] = 1; /* 3 is right */ + if(i&SDL_BUTTON(2)) input_state[input_current][KEY_MOUSE_3] = 1; /* 2 is middle */ + if(i&SDL_BUTTON(4)) input_state[input_current][KEY_MOUSE_4] = 1; + if(i&SDL_BUTTON(5)) input_state[input_current][KEY_MOUSE_5] = 1; + if(i&SDL_BUTTON(6)) input_state[input_current][KEY_MOUSE_6] = 1; + if(i&SDL_BUTTON(7)) input_state[input_current][KEY_MOUSE_7] = 1; + if(i&SDL_BUTTON(8)) input_state[input_current][KEY_MOUSE_8] = 1; + + { + SDL_Event event; + + while(SDL_PollEvent(&event)) + { + int key = -1; + int action = INPFLAG_PRESS; + switch (event.type) + { + /* handle keys */ + case SDL_KEYDOWN: + /*if(event.key.keysym.unicode < 255) */ + add_event(event.key.keysym.unicode, 0, 0); + key = event.key.keysym.sym; + break; + case SDL_KEYUP: + action = INPFLAG_RELEASE; + key = event.key.keysym.sym; + break; + + /* handle mouse buttons */ + case SDL_MOUSEBUTTONUP: + action = INPFLAG_RELEASE; + + if(event.button.button == 1) + { + release_delta = time_get() - last_release; + last_release = time_get(); + } + + /* fall through */ + case SDL_MOUSEBUTTONDOWN: + if(event.button.button == SDL_BUTTON_LEFT) key = KEY_MOUSE_1; + if(event.button.button == SDL_BUTTON_RIGHT) key = KEY_MOUSE_2; + if(event.button.button == SDL_BUTTON_MIDDLE) key = KEY_MOUSE_3; + if(event.button.button == SDL_BUTTON_WHEELUP) key = KEY_MOUSE_WHEEL_UP; + if(event.button.button == SDL_BUTTON_WHEELDOWN) key = KEY_MOUSE_WHEEL_DOWN; + if(event.button.button == 6) key = KEY_MOUSE_6; + if(event.button.button == 7) key = KEY_MOUSE_7; + if(event.button.button == 8) key = KEY_MOUSE_8; + break; + + /* other messages */ + case SDL_QUIT: + /* TODO: cleaner exit */ + exit(0); + break; + } + + /* */ + if(key != -1) + { + input_count[input_current][key].presses++; + if(action == INPFLAG_PRESS) + input_state[input_current][key] = 1; + add_event(0, key, action); + } + + } + } +} diff --git a/src/engine/client/ec_snd.c b/src/engine/client/ec_snd.c deleted file mode 100644 index ac41ec59..00000000 --- a/src/engine/client/ec_snd.c +++ /dev/null @@ -1,465 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include -#include -#include - -#include "SDL.h" - -#include -#include -#include -#include - -enum -{ - NUM_SAMPLES = 512, - NUM_VOICES = 64, - NUM_CHANNELS = 16, - - MAX_FRAMES = 1024 -}; - -typedef struct -{ - short *data; - int num_frames; - int rate; - int channels; - int loop_start; - int loop_end; -} SAMPLE; - -typedef struct -{ - int vol; - int pan; -} CHANNEL; - -typedef struct -{ - SAMPLE *snd; - CHANNEL *channel; - int tick; - int vol; /* 0 - 255 */ - int flags; - int x, y; -} VOICE; - -static SAMPLE samples[NUM_SAMPLES] = { {0} }; -static VOICE voices[NUM_VOICES] = { {0} }; -static CHANNEL channels[NUM_CHANNELS] = { {255, 0} }; - -static LOCK sound_lock = 0; -static int sound_enabled = 0; - -static int center_x = 0; -static int center_y = 0; - -static int mixing_rate = 48000; -static volatile int sound_volume = 100; - -static int next_voice = 0; - -void snd_set_channel(int cid, float vol, float pan) -{ - channels[cid].vol = (int)(vol*255.0f); - channels[cid].pan = (int)(pan*255.0f); /* TODO: this is only on and off right now */ -} - -static int play(int cid, int sid, int flags, float x, float y) -{ - int vid = -1; - int i; - - lock_wait(sound_lock); - - /* search for voice */ - for(i = 0; i < NUM_VOICES; i++) - { - int id = (next_voice + i) % NUM_VOICES; - if(!voices[id].snd) - { - vid = id; - next_voice = id+1; - break; - } - } - - /* voice found, use it */ - if(vid != -1) - { - voices[vid].snd = &samples[sid]; - voices[vid].channel = &channels[cid]; - voices[vid].tick = 0; - voices[vid].vol = 255; - voices[vid].flags = flags; - voices[vid].x = (int)x; - voices[vid].y = (int)y; - } - - lock_release(sound_lock); - return vid; -} - -int snd_play_at(int cid, int sid, int flags, float x, float y) -{ - return play(cid, sid, flags|SNDFLAG_POS, x, y); -} - -int snd_play(int cid, int sid, int flags) -{ - return play(cid, sid, flags, 0, 0); -} - -void snd_stop(int vid) -{ - /* TODO: a nice fade out */ - lock_wait(sound_lock); - voices[vid].snd = 0; - lock_release(sound_lock); -} - -/* TODO: there should be a faster way todo this */ -static short int2short(int i) -{ - if(i > 0x7fff) - return 0x7fff; - else if(i < -0x7fff) - return -0x7fff; - return i; -} - -static int iabs(int i) -{ - if(i<0) - return -i; - return i; -} - -static void mix(short *final_out, unsigned frames) -{ - int mix_buffer[MAX_FRAMES*2] = {0}; - int i, s; - int master_vol; - - /* aquire lock while we are mixing */ - lock_wait(sound_lock); - - master_vol = sound_volume; - - for(i = 0; i < NUM_VOICES; i++) - { - if(voices[i].snd) - { - /* mix voice */ - VOICE *v = &voices[i]; - int *out = mix_buffer; - - int step = v->snd->channels; /* setup input sources */ - short *in_l = &v->snd->data[v->tick*step]; - short *in_r = &v->snd->data[v->tick*step+1]; - - int end = v->snd->num_frames-v->tick; - - int rvol = v->channel->vol; - int lvol = v->channel->vol; - - /* make sure that we don't go outside the sound data */ - if(frames < end) - end = frames; - - /* check if we have a mono sound */ - if(v->snd->channels == 1) - in_r = in_l; - - /* volume calculation */ - if(v->flags&SNDFLAG_POS && v->channel->pan) - { - /* TODO: we should respect the channel panning value */ - const int range = 1500; /* magic value, remove */ - int dx = v->x - center_x; - int dy = v->y - center_y; - int dist = sqrt(dx*dx+dy*dy); /* double here. nasty */ - int p = iabs(dx); - if(dist < range) - { - /* panning */ - if(dx > 0) - lvol = ((range-p)*lvol)/range; - else - rvol = ((range-p)*rvol)/range; - - /* falloff */ - lvol = (lvol*(range-dist))/range; - rvol = (rvol*(range-dist))/range; - } - else - { - lvol = 0; - rvol = 0; - } - } - - /* process all frames */ - for(s = 0; s < end; s++) - { - *out++ += (*in_l)*lvol; - *out++ += (*in_r)*rvol; - in_l += step; - in_r += step; - v->tick++; - } - - /* free voice if not used any more */ - if(v->tick == v->snd->num_frames) - v->snd = 0; - - } - } - - - /* release the lock */ - lock_release(sound_lock); - - { - /* clamp accumulated values */ - /* TODO: this seams slow */ - for(i = 0; i < frames; i++) - { - int j = i<<1; - int vl = ((mix_buffer[j]*master_vol)/101)>>8; - int vr = ((mix_buffer[j+1]*master_vol)/101)>>8; - - final_out[j] = int2short(vl); - final_out[j+1] = int2short(vr); - } - } - -#if defined(CONF_ARCH_ENDIAN_BIG) - swap_endian(final_out, sizeof(short), frames * 2); -#endif -} - -static void sdlcallback(void *unused, Uint8 *stream, int len) -{ - mix((short *)stream, len/2/2); -} - -int snd_init() -{ - SDL_AudioSpec format; - - sound_lock = lock_create(); - - if(!config.snd_enable) - return 0; - - mixing_rate = config.snd_rate; - - /* Set 16-bit stereo audio at 22Khz */ - format.freq = config.snd_rate; - format.format = AUDIO_S16; - format.channels = 2; - format.samples = config.snd_buffer_size; - format.callback = sdlcallback; - format.userdata = NULL; - - /* Open the audio device and start playing sound! */ - if(SDL_OpenAudio(&format, NULL) < 0) - { - dbg_msg("client/sound", "unable to open audio: %s", SDL_GetError()); - return -1; - } - else - dbg_msg("client/sound", "sound init successful"); - - SDL_PauseAudio(0); - - sound_enabled = 1; - snd_update(); /* update the volume */ - return 0; -} - -int snd_update() -{ - /* update volume */ - int wanted_volume = config.snd_volume; - - if(!gfx_window_active() && config.snd_nonactive_mute) - wanted_volume = 0; - - if(wanted_volume != sound_volume) - { - lock_wait(sound_lock); - sound_volume = wanted_volume; - lock_release(sound_lock); - } - - return 0; -} - -int snd_shutdown() -{ - SDL_CloseAudio(); - lock_destroy(sound_lock); - return 0; -} - -int snd_alloc_id() -{ - /* TODO: linear search, get rid of it */ - unsigned sid; - for(sid = 0; sid < NUM_SAMPLES; sid++) - { - if(samples[sid].data == 0x0) - return sid; - } - - return -1; -} - -static void rate_convert(int sid) -{ - SAMPLE *snd = &samples[sid]; - int num_frames = 0; - short *new_data = 0; - int i; - - /* make sure that we need to convert this sound */ - if(!snd->data || snd->rate == mixing_rate) - return; - - /* allocate new data */ - num_frames = (int)((snd->num_frames/(float)snd->rate)*mixing_rate); - new_data = mem_alloc(num_frames*snd->channels*sizeof(short), 1); - - for(i = 0; i < num_frames; i++) - { - /* resample TODO: this should be done better, like linear atleast */ - float a = i/(float)num_frames; - int f = (int)(a*snd->num_frames); - if(f >= snd->num_frames) - f = snd->num_frames-1; - - /* set new data */ - if(snd->channels == 1) - new_data[i] = snd->data[f]; - else if(snd->channels == 2) - { - new_data[i*2] = snd->data[f*2]; - new_data[i*2+1] = snd->data[f*2+1]; - } - } - - /* free old data and apply new */ - mem_free(snd->data); - snd->data = new_data; - snd->num_frames = num_frames; -} - - -static IOHANDLE file = NULL; - -static int read_data(void *buffer, int size) -{ - return io_read(file, buffer, size); -} - -int snd_load_wv(const char *filename) -{ - SAMPLE *snd; - int sid = -1; - char error[100]; - WavpackContext *context; - - /* don't waste memory on sound when we are stress testing */ - if(config.dbg_stress) - return -1; - - /* no need to load sound when we are running with no sound */ - if(!sound_enabled) - return 1; - - file = engine_openfile(filename, IOFLAG_READ); /* TODO: use system.h stuff for this */ - if(!file) - { - dbg_msg("sound/wv", "failed to open %s", filename); - return -1; - } - - sid = snd_alloc_id(); - if(sid < 0) - return -1; - snd = &samples[sid]; - - context = WavpackOpenFileInput(read_data, error); - if (context) - { - int samples = WavpackGetNumSamples(context); - int bitspersample = WavpackGetBitsPerSample(context); - unsigned int samplerate = WavpackGetSampleRate(context); - int channels = WavpackGetNumChannels(context); - int *data; - int *src; - short *dst; - int i; - - snd->channels = channels; - snd->rate = samplerate; - - if(snd->channels > 2) - { - dbg_msg("sound/wv", "file is not mono or stereo. filename='%s'", filename); - return -1; - } - - /* - if(snd->rate != 44100) - { - dbg_msg("sound/wv", "file is %d Hz, not 44100 Hz. filename='%s'", snd->rate, filename); - return -1; - }*/ - - if(bitspersample != 16) - { - dbg_msg("sound/wv", "bps is %d, not 16, filname='%s'", bitspersample, filename); - return -1; - } - - data = (int *)mem_alloc(4*samples*channels, 1); - WavpackUnpackSamples(context, data, samples); /* TODO: check return value */ - src = data; - - snd->data = (short *)mem_alloc(2*samples*channels, 1); - dst = snd->data; - - for (i = 0; i < samples*channels; i++) - *dst++ = (short)*src++; - - mem_free(data); - - snd->num_frames = samples; - snd->loop_start = -1; - snd->loop_end = -1; - } - else - { - dbg_msg("sound/wv", "failed to open %s: %s", filename, error); - } - - io_close(file); - file = NULL; - - if(config.debug) - dbg_msg("sound/wv", "loaded %s", filename); - - rate_convert(sid); - return sid; -} - -void snd_set_listener_pos(float x, float y) -{ - center_x = (int)x; - center_y = (int)y; -} diff --git a/src/engine/client/ec_snd.cpp b/src/engine/client/ec_snd.cpp new file mode 100644 index 00000000..3baea982 --- /dev/null +++ b/src/engine/client/ec_snd.cpp @@ -0,0 +1,471 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include +#include +#include + +#include "SDL.h" + +extern "C" { // wavpack + #include +} +#include +#include +#include + +enum +{ + NUM_SAMPLES = 512, + NUM_VOICES = 64, + NUM_CHANNELS = 16, + + MAX_FRAMES = 1024 +}; + +typedef struct +{ + short *data; + int num_frames; + int rate; + int channels; + int loop_start; + int loop_end; +} SAMPLE; + +typedef struct +{ + int vol; + int pan; +} CHANNEL; + +typedef struct +{ + SAMPLE *snd; + CHANNEL *channel; + int tick; + int vol; /* 0 - 255 */ + int flags; + int x, y; +} VOICE; + +static SAMPLE samples[NUM_SAMPLES] = { {0} }; +static VOICE voices[NUM_VOICES] = { {0} }; +static CHANNEL channels[NUM_CHANNELS] = { {255, 0} }; + +static LOCK sound_lock = 0; +static int sound_enabled = 0; + +static int center_x = 0; +static int center_y = 0; + +static int mixing_rate = 48000; +static volatile int sound_volume = 100; + +static int next_voice = 0; + +void snd_set_channel(int cid, float vol, float pan) +{ + channels[cid].vol = (int)(vol*255.0f); + channels[cid].pan = (int)(pan*255.0f); /* TODO: this is only on and off right now */ +} + +static int play(int cid, int sid, int flags, float x, float y) +{ + int vid = -1; + int i; + + lock_wait(sound_lock); + + /* search for voice */ + for(i = 0; i < NUM_VOICES; i++) + { + int id = (next_voice + i) % NUM_VOICES; + if(!voices[id].snd) + { + vid = id; + next_voice = id+1; + break; + } + } + + /* voice found, use it */ + if(vid != -1) + { + voices[vid].snd = &samples[sid]; + voices[vid].channel = &channels[cid]; + voices[vid].tick = 0; + voices[vid].vol = 255; + voices[vid].flags = flags; + voices[vid].x = (int)x; + voices[vid].y = (int)y; + } + + lock_release(sound_lock); + return vid; +} + +int snd_play_at(int cid, int sid, int flags, float x, float y) +{ + return play(cid, sid, flags|SNDFLAG_POS, x, y); +} + +int snd_play(int cid, int sid, int flags) +{ + return play(cid, sid, flags, 0, 0); +} + +void snd_stop(int vid) +{ + /* TODO: a nice fade out */ + lock_wait(sound_lock); + voices[vid].snd = 0; + lock_release(sound_lock); +} + +/* TODO: there should be a faster way todo this */ +static short int2short(int i) +{ + if(i > 0x7fff) + return 0x7fff; + else if(i < -0x7fff) + return -0x7fff; + return i; +} + +static int iabs(int i) +{ + if(i<0) + return -i; + return i; +} + +static void mix(short *final_out, unsigned frames) +{ + int mix_buffer[MAX_FRAMES*2] = {0}; + int master_vol; + + /* aquire lock while we are mixing */ + lock_wait(sound_lock); + + master_vol = sound_volume; + + for(unsigned i = 0; i < NUM_VOICES; i++) + { + if(voices[i].snd) + { + /* mix voice */ + VOICE *v = &voices[i]; + int *out = mix_buffer; + + int step = v->snd->channels; /* setup input sources */ + short *in_l = &v->snd->data[v->tick*step]; + short *in_r = &v->snd->data[v->tick*step+1]; + + unsigned end = v->snd->num_frames-v->tick; + + int rvol = v->channel->vol; + int lvol = v->channel->vol; + + /* make sure that we don't go outside the sound data */ + if(frames < end) + end = frames; + + /* check if we have a mono sound */ + if(v->snd->channels == 1) + in_r = in_l; + + /* volume calculation */ + if(v->flags&SNDFLAG_POS && v->channel->pan) + { + /* TODO: we should respect the channel panning value */ + const int range = 1500; /* magic value, remove */ + int dx = v->x - center_x; + int dy = v->y - center_y; + int dist = sqrt(dx*dx+dy*dy); /* double here. nasty */ + int p = iabs(dx); + if(dist < range) + { + /* panning */ + if(dx > 0) + lvol = ((range-p)*lvol)/range; + else + rvol = ((range-p)*rvol)/range; + + /* falloff */ + lvol = (lvol*(range-dist))/range; + rvol = (rvol*(range-dist))/range; + } + else + { + lvol = 0; + rvol = 0; + } + } + + /* process all frames */ + for(unsigned s = 0; s < end; s++) + { + *out++ += (*in_l)*lvol; + *out++ += (*in_r)*rvol; + in_l += step; + in_r += step; + v->tick++; + } + + /* free voice if not used any more */ + if(v->tick == v->snd->num_frames) + v->snd = 0; + + } + } + + + /* release the lock */ + lock_release(sound_lock); + + { + /* clamp accumulated values */ + /* TODO: this seams slow */ + for(unsigned i = 0; i < frames; i++) + { + int j = i<<1; + int vl = ((mix_buffer[j]*master_vol)/101)>>8; + int vr = ((mix_buffer[j+1]*master_vol)/101)>>8; + + final_out[j] = int2short(vl); + final_out[j+1] = int2short(vr); + } + } + +#if defined(CONF_ARCH_ENDIAN_BIG) + swap_endian(final_out, sizeof(short), frames * 2); +#endif +} + +static void sdlcallback(void *unused, Uint8 *stream, int len) +{ + mix((short *)stream, len/2/2); +} + +int snd_init() +{ + SDL_AudioSpec format; + + sound_lock = lock_create(); + + if(!config.snd_enable) + return 0; + + mixing_rate = config.snd_rate; + + /* Set 16-bit stereo audio at 22Khz */ + format.freq = config.snd_rate; + format.format = AUDIO_S16; + format.channels = 2; + format.samples = config.snd_buffer_size; + format.callback = sdlcallback; + format.userdata = NULL; + + /* Open the audio device and start playing sound! */ + if(SDL_OpenAudio(&format, NULL) < 0) + { + dbg_msg("client/sound", "unable to open audio: %s", SDL_GetError()); + return -1; + } + else + dbg_msg("client/sound", "sound init successful"); + + SDL_PauseAudio(0); + + sound_enabled = 1; + snd_update(); /* update the volume */ + return 0; +} + +// TODO: Refactor: Remove this +extern IEngineGraphics *Graphics(); + + +int snd_update() +{ + /* update volume */ + int wanted_volume = config.snd_volume; + + if(!Graphics()->WindowActive() && config.snd_nonactive_mute) + wanted_volume = 0; + + if(wanted_volume != sound_volume) + { + lock_wait(sound_lock); + sound_volume = wanted_volume; + lock_release(sound_lock); + } + + return 0; +} + +int snd_shutdown() +{ + SDL_CloseAudio(); + lock_destroy(sound_lock); + return 0; +} + +int snd_alloc_id() +{ + /* TODO: linear search, get rid of it */ + unsigned sid; + for(sid = 0; sid < NUM_SAMPLES; sid++) + { + if(samples[sid].data == 0x0) + return sid; + } + + return -1; +} + +static void rate_convert(int sid) +{ + SAMPLE *snd = &samples[sid]; + int num_frames = 0; + short *new_data = 0; + int i; + + /* make sure that we need to convert this sound */ + if(!snd->data || snd->rate == mixing_rate) + return; + + /* allocate new data */ + num_frames = (int)((snd->num_frames/(float)snd->rate)*mixing_rate); + new_data = (short *)mem_alloc(num_frames*snd->channels*sizeof(short), 1); + + for(i = 0; i < num_frames; i++) + { + /* resample TODO: this should be done better, like linear atleast */ + float a = i/(float)num_frames; + int f = (int)(a*snd->num_frames); + if(f >= snd->num_frames) + f = snd->num_frames-1; + + /* set new data */ + if(snd->channels == 1) + new_data[i] = snd->data[f]; + else if(snd->channels == 2) + { + new_data[i*2] = snd->data[f*2]; + new_data[i*2+1] = snd->data[f*2+1]; + } + } + + /* free old data and apply new */ + mem_free(snd->data); + snd->data = new_data; + snd->num_frames = num_frames; +} + + +static IOHANDLE file = NULL; + +static int read_data(void *buffer, int size) +{ + return io_read(file, buffer, size); +} + +int snd_load_wv(const char *filename) +{ + SAMPLE *snd; + int sid = -1; + char error[100]; + WavpackContext *context; + + /* don't waste memory on sound when we are stress testing */ + if(config.dbg_stress) + return -1; + + /* no need to load sound when we are running with no sound */ + if(!sound_enabled) + return 1; + + file = engine_openfile(filename, IOFLAG_READ); /* TODO: use system.h stuff for this */ + if(!file) + { + dbg_msg("sound/wv", "failed to open %s", filename); + return -1; + } + + sid = snd_alloc_id(); + if(sid < 0) + return -1; + snd = &samples[sid]; + + context = WavpackOpenFileInput(read_data, error); + if (context) + { + int samples = WavpackGetNumSamples(context); + int bitspersample = WavpackGetBitsPerSample(context); + unsigned int samplerate = WavpackGetSampleRate(context); + int channels = WavpackGetNumChannels(context); + int *data; + int *src; + short *dst; + int i; + + snd->channels = channels; + snd->rate = samplerate; + + if(snd->channels > 2) + { + dbg_msg("sound/wv", "file is not mono or stereo. filename='%s'", filename); + return -1; + } + + /* + if(snd->rate != 44100) + { + dbg_msg("sound/wv", "file is %d Hz, not 44100 Hz. filename='%s'", snd->rate, filename); + return -1; + }*/ + + if(bitspersample != 16) + { + dbg_msg("sound/wv", "bps is %d, not 16, filname='%s'", bitspersample, filename); + return -1; + } + + data = (int *)mem_alloc(4*samples*channels, 1); + WavpackUnpackSamples(context, data, samples); /* TODO: check return value */ + src = data; + + snd->data = (short *)mem_alloc(2*samples*channels, 1); + dst = snd->data; + + for (i = 0; i < samples*channels; i++) + *dst++ = (short)*src++; + + mem_free(data); + + snd->num_frames = samples; + snd->loop_start = -1; + snd->loop_end = -1; + } + else + { + dbg_msg("sound/wv", "failed to open %s: %s", filename, error); + } + + io_close(file); + file = NULL; + + if(config.debug) + dbg_msg("sound/wv", "loaded %s", filename); + + rate_convert(sid); + return sid; +} + +void snd_set_listener_pos(float x, float y) +{ + center_x = (int)x; + center_y = (int)y; +} diff --git a/src/engine/client/ec_srvbrowse.c b/src/engine/client/ec_srvbrowse.c deleted file mode 100644 index 4a85e778..00000000 --- a/src/engine/client/ec_srvbrowse.c +++ /dev/null @@ -1,737 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -extern NETCLIENT *net; - - -/* ------ server browse ---- */ -/* TODO: move all this to a separate file */ - -typedef struct SERVERENTRY_t SERVERENTRY; -struct SERVERENTRY_t -{ - NETADDR addr; - int64 request_time; - int got_info; - SERVER_INFO info; - - SERVERENTRY *next_ip; /* ip hashed list */ - - SERVERENTRY *prev_req; /* request list */ - SERVERENTRY *next_req; -}; - -static HEAP *serverlist_heap = 0; -static SERVERENTRY **serverlist = 0; -static int *sorted_serverlist = 0; - -enum -{ - MAX_FAVORITES=256 -}; - -static NETADDR favorite_servers[MAX_FAVORITES]; -static int num_favorite_servers = 0; - -static SERVERENTRY *serverlist_ip[256] = {0}; /* ip hash list */ - -static SERVERENTRY *first_req_server = 0; /* request list */ -static SERVERENTRY *last_req_server = 0; -static int num_requests = 0; - -static int need_refresh = 0; - -static int num_sorted_servers = 0; -static int num_sorted_servers_capacity = 0; -static int num_servers = 0; -static int num_server_capacity = 0; - -static int sorthash = 0; -static char filterstring[64] = {0}; -static char filtergametypestring[128] = {0}; - -/* the token is to keep server refresh separated from each other */ -static int current_token = 1; - -static int serverlist_type = 0; -static int64 broadcast_time = 0; - -int client_serverbrowse_lan() { return serverlist_type == BROWSETYPE_LAN; } -int client_serverbrowse_num() { return num_servers; } - -SERVER_INFO *client_serverbrowse_get(int index) -{ - if(index < 0 || index >= num_servers) - return 0; - return &serverlist[index]->info; -} - -int client_serverbrowse_sorted_num() { return num_sorted_servers; } - -SERVER_INFO *client_serverbrowse_sorted_get(int index) -{ - if(index < 0 || index >= num_sorted_servers) - return 0; - return &serverlist[sorted_serverlist[index]]->info; -} - - -int client_serverbrowse_num_requests() -{ - return num_requests; -} - -static int client_serverbrowse_sort_compare_name(const void *ai, const void *bi) -{ - SERVERENTRY *a = serverlist[*(const int*)ai]; - SERVERENTRY *b = serverlist[*(const int*)bi]; - return strcmp(a->info.name, b->info.name); -} - -static int client_serverbrowse_sort_compare_map(const void *ai, const void *bi) -{ - SERVERENTRY *a = serverlist[*(const int*)ai]; - SERVERENTRY *b = serverlist[*(const int*)bi]; - return strcmp(a->info.map, b->info.map); -} - -static int client_serverbrowse_sort_compare_ping(const void *ai, const void *bi) -{ - SERVERENTRY *a = serverlist[*(const int*)ai]; - SERVERENTRY *b = serverlist[*(const int*)bi]; - if(a->info.latency > b->info.latency) return 1; - if(a->info.latency < b->info.latency) return -1; - return 0; -} - -static int client_serverbrowse_sort_compare_gametype(const void *ai, const void *bi) -{ - SERVERENTRY *a = serverlist[*(const int*)ai]; - SERVERENTRY *b = serverlist[*(const int*)bi]; - return strcmp(a->info.gametype, b->info.gametype); -} - -static int client_serverbrowse_sort_compare_progression(const void *ai, const void *bi) -{ - SERVERENTRY *a = serverlist[*(const int*)ai]; - SERVERENTRY *b = serverlist[*(const int*)bi]; - if(a->info.progression > b->info.progression) return 1; - if(a->info.progression < b->info.progression) return -1; - return 0; -} - -static int client_serverbrowse_sort_compare_numplayers(const void *ai, const void *bi) -{ - SERVERENTRY *a = serverlist[*(const int*)ai]; - SERVERENTRY *b = serverlist[*(const int*)bi]; - if(a->info.num_players > b->info.num_players) return 1; - if(a->info.num_players < b->info.num_players) return -1; - return 0; -} - -static void client_serverbrowse_filter() -{ - int i = 0, p = 0; - num_sorted_servers = 0; - - /* allocate the sorted list */ - if(num_sorted_servers_capacity < num_servers) - { - if(sorted_serverlist) - mem_free(sorted_serverlist); - num_sorted_servers_capacity = num_servers; - sorted_serverlist = mem_alloc(num_sorted_servers_capacity*sizeof(int), 1); - } - - /* filter the servers */ - for(i = 0; i < num_servers; i++) - { - int filtered = 0; - - if(config.b_filter_empty && serverlist[i]->info.num_players == 0) - filtered = 1; - else if(config.b_filter_full && serverlist[i]->info.num_players == serverlist[i]->info.max_players) - filtered = 1; - else if(config.b_filter_pw && serverlist[i]->info.flags&SRVFLAG_PASSWORD) - filtered = 1; - else if(config.b_filter_pure && (strcmp(serverlist[i]->info.gametype, "DM") != 0 && strcmp(serverlist[i]->info.gametype, "TDM") != 0 && strcmp(serverlist[i]->info.gametype, "CTF") != 0)) - filtered = 1; - else if(config.b_filter_pure_map && - !(strcmp(serverlist[i]->info.map, "dm1") == 0 || - strcmp(serverlist[i]->info.map, "dm2") == 0 || - strcmp(serverlist[i]->info.map, "dm6") == 0 || - strcmp(serverlist[i]->info.map, "dm7") == 0 || - strcmp(serverlist[i]->info.map, "dm8") == 0 || - strcmp(serverlist[i]->info.map, "dm9") == 0 || - strcmp(serverlist[i]->info.map, "ctf1") == 0 || - strcmp(serverlist[i]->info.map, "ctf2") == 0 || - strcmp(serverlist[i]->info.map, "ctf3") == 0 || - strcmp(serverlist[i]->info.map, "ctf4") == 0 || - strcmp(serverlist[i]->info.map, "ctf5") == 0) - ) - { - filtered = 1; - } - else if(config.b_filter_ping < serverlist[i]->info.latency) - filtered = 1; - else if(config.b_filter_compatversion && strncmp(serverlist[i]->info.version, modc_net_version(), 3) != 0) - filtered = 1; - else - { - if(config.b_filter_string[0] != 0) - { - int matchfound = 0; - - serverlist[i]->info.quicksearch_hit = 0; - - /* match against server name */ - if(str_find_nocase(serverlist[i]->info.name, config.b_filter_string)) - { - matchfound = 1; - serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_SERVERNAME; - } - - /* match against players */ - for(p = 0; p < serverlist[i]->info.num_players; p++) - { - if(str_find_nocase(serverlist[i]->info.players[p].name, config.b_filter_string)) - { - matchfound = 1; - serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_PLAYERNAME; - break; - } - } - - /* match against map */ - if(str_find_nocase(serverlist[i]->info.map, config.b_filter_string)) - { - matchfound = 1; - serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_MAPNAME; - } - - if(!matchfound) - filtered = 1; - } - - if(!filtered && config.b_filter_gametype[0] != 0) - { - /* match against game type */ - if(!str_find_nocase(serverlist[i]->info.gametype, config.b_filter_gametype)) - filtered = 1; - } - } - - if(filtered == 0) - sorted_serverlist[num_sorted_servers++] = i; - } -} - -static int client_serverbrowse_sorthash() -{ - int i = config.b_sort&0xf; - i |= config.b_filter_empty<<4; - i |= config.b_filter_full<<5; - i |= config.b_filter_pw<<6; - i |= config.b_sort_order<<7; - i |= config.b_filter_compatversion<<8; - i |= config.b_filter_pure<<9; - i |= config.b_filter_pure_map<<10; - i |= config.b_filter_ping<<16; - return i; -} - -static void client_serverbrowse_sort() -{ - int i; - - /* create filtered list */ - client_serverbrowse_filter(); - - /* sort */ - if(config.b_sort == BROWSESORT_NAME) - qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_name); - else if(config.b_sort == BROWSESORT_PING) - qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_ping); - else if(config.b_sort == BROWSESORT_MAP) - qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_map); - else if(config.b_sort == BROWSESORT_NUMPLAYERS) - qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_numplayers); - else if(config.b_sort == BROWSESORT_GAMETYPE) - qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_gametype); - else if(config.b_sort == BROWSESORT_PROGRESSION) - qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_progression); - - /* invert the list if requested */ - if(config.b_sort_order) - { - for(i = 0; i < num_sorted_servers/2; i++) - { - int temp = sorted_serverlist[i]; - sorted_serverlist[i] = sorted_serverlist[num_sorted_servers-i-1]; - sorted_serverlist[num_sorted_servers-i-1] = temp; - } - } - - /* set indexes */ - for(i = 0; i < num_sorted_servers; i++) - serverlist[sorted_serverlist[i]]->info.sorted_index = i; - - str_copy(filtergametypestring, config.b_filter_gametype, sizeof(filtergametypestring)); - str_copy(filterstring, config.b_filter_string, sizeof(filterstring)); - sorthash = client_serverbrowse_sorthash(); -} - -static void client_serverbrowse_remove_request(SERVERENTRY *entry) -{ - if(entry->prev_req || entry->next_req || first_req_server == entry) - { - if(entry->prev_req) - entry->prev_req->next_req = entry->next_req; - else - first_req_server = entry->next_req; - - if(entry->next_req) - entry->next_req->prev_req = entry->prev_req; - else - last_req_server = entry->prev_req; - - entry->prev_req = 0; - entry->next_req = 0; - num_requests--; - } -} - -static SERVERENTRY *client_serverbrowse_find(NETADDR *addr) -{ - SERVERENTRY *entry = serverlist_ip[addr->ip[0]]; - - for(; entry; entry = entry->next_ip) - { - if(net_addr_comp(&entry->addr, addr) == 0) - return entry; - } - return (SERVERENTRY*)0; -} - -void client_serverbrowse_queuerequest(SERVERENTRY *entry) -{ - /* add it to the list of servers that we should request info from */ - entry->prev_req = last_req_server; - if(last_req_server) - last_req_server->next_req = entry; - else - first_req_server = entry; - last_req_server = entry; - - num_requests++; -} - -void client_serverbrowse_setinfo(SERVERENTRY *entry, SERVER_INFO *info) -{ - int fav = entry->info.favorite; - entry->info = *info; - entry->info.favorite = fav; - entry->info.netaddr = entry->addr; - - // all these are just for nice compability - if(entry->info.gametype[0] == '0' && entry->info.gametype[1] == 0) - str_copy(entry->info.gametype, "DM", sizeof(entry->info.gametype)); - else if(entry->info.gametype[0] == '1' && entry->info.gametype[1] == 0) - str_copy(entry->info.gametype, "TDM", sizeof(entry->info.gametype)); - else if(entry->info.gametype[0] == '2' && entry->info.gametype[1] == 0) - str_copy(entry->info.gametype, "CTF", sizeof(entry->info.gametype)); - - /*if(!request) - { - entry->info.latency = (time_get()-entry->request_time)*1000/time_freq(); - client_serverbrowse_remove_request(entry); - }*/ - - entry->got_info = 1; - client_serverbrowse_sort(); -} - -SERVERENTRY *client_serverbrowse_add(NETADDR *addr) -{ - int hash = addr->ip[0]; - SERVERENTRY *entry = 0; - int i; - - /* create new entry */ - entry = (SERVERENTRY *)memheap_allocate(serverlist_heap, sizeof(SERVERENTRY)); - mem_zero(entry, sizeof(SERVERENTRY)); - - /* set the info */ - entry->addr = *addr; - entry->info.netaddr = *addr; - - entry->info.latency = 999; - str_format(entry->info.address, sizeof(entry->info.address), "%d.%d.%d.%d:%d", - addr->ip[0], addr->ip[1], addr->ip[2], - addr->ip[3], addr->port); - str_format(entry->info.name, sizeof(entry->info.name), "\255%d.%d.%d.%d:%d", /* the \255 is to make sure that it's sorted last */ - addr->ip[0], addr->ip[1], addr->ip[2], - addr->ip[3], addr->port); - - /*if(serverlist_type == BROWSETYPE_LAN) - entry->info.latency = (time_get()-broadcast_time)*1000/time_freq();*/ - - /* check if it's a favorite */ - for(i = 0; i < num_favorite_servers; i++) - { - if(net_addr_comp(addr, &favorite_servers[i]) == 0) - entry->info.favorite = 1; - } - - /* add to the hash list */ - entry->next_ip = serverlist_ip[hash]; - serverlist_ip[hash] = entry; - - if(num_servers == num_server_capacity) - { - SERVERENTRY **newlist; - num_server_capacity += 100; - newlist = mem_alloc(num_server_capacity*sizeof(SERVERENTRY*), 1); - mem_copy(newlist, serverlist, num_servers*sizeof(SERVERENTRY*)); - mem_free(serverlist); - serverlist = newlist; - } - - /* add to list */ - serverlist[num_servers] = entry; - entry->info.server_index = num_servers; - num_servers++; - - return entry; -} - -void client_serverbrowse_set(NETADDR *addr, int type, int token, SERVER_INFO *info) -{ - SERVERENTRY *entry = 0; - if(type == BROWSESET_MASTER_ADD) - { - if(serverlist_type != BROWSETYPE_INTERNET) - return; - - if(!client_serverbrowse_find(addr)) - { - entry = client_serverbrowse_add(addr); - client_serverbrowse_queuerequest(entry); - } - } - else if(type == BROWSESET_FAV_ADD) - { - if(serverlist_type != BROWSETYPE_FAVORITES) - return; - - if(!client_serverbrowse_find(addr)) - { - entry = client_serverbrowse_add(addr); - client_serverbrowse_queuerequest(entry); - } - } - else if(type == BROWSESET_TOKEN) - { - if(token != current_token) - return; - - entry = client_serverbrowse_find(addr); - if(!entry) - entry = client_serverbrowse_add(addr); - if(entry) - { - client_serverbrowse_setinfo(entry, info); - if(serverlist_type == BROWSETYPE_LAN) - entry->info.latency = (time_get()-broadcast_time)*1000/time_freq(); - else - entry->info.latency = (time_get()-entry->request_time)*1000/time_freq(); - client_serverbrowse_remove_request(entry); - } - } - else if(type == BROWSESET_OLD_INTERNET) - { - entry = client_serverbrowse_find(addr); - if(entry) - { - client_serverbrowse_setinfo(entry, info); - - if(serverlist_type == BROWSETYPE_LAN) - entry->info.latency = (time_get()-broadcast_time)*1000/time_freq(); - else - entry->info.latency = (time_get()-entry->request_time)*1000/time_freq(); - client_serverbrowse_remove_request(entry); - } - } - else if(type == BROWSESET_OLD_LAN) - { - entry = client_serverbrowse_find(addr); - if(entry) - if(!entry) - entry = client_serverbrowse_add(addr); - if(entry) - client_serverbrowse_setinfo(entry, info); - } - - client_serverbrowse_sort(); -} - -void client_serverbrowse_refresh(int type) -{ - /* clear out everything */ - if(serverlist_heap) - memheap_destroy(serverlist_heap); - serverlist_heap = memheap_create(); - num_servers = 0; - num_sorted_servers = 0; - mem_zero(serverlist_ip, sizeof(serverlist_ip)); - first_req_server = 0; - last_req_server = 0; - num_requests = 0; - - /* next token */ - current_token = (current_token+1)&0xff; - - /* */ - serverlist_type = type; - - if(type == BROWSETYPE_LAN) - { - unsigned char buffer[sizeof(SERVERBROWSE_GETINFO)+1]; - NETCHUNK packet; - int i; - - mem_copy(buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)); - buffer[sizeof(SERVERBROWSE_GETINFO)] = current_token; - - packet.client_id = -1; - mem_zero(&packet, sizeof(packet)); - packet.address.ip[0] = 255; - packet.address.ip[1] = 255; - packet.address.ip[2] = 255; - packet.address.ip[3] = 255; - packet.flags = NETSENDFLAG_CONNLESS; - packet.data_size = sizeof(buffer); - packet.data = buffer; - broadcast_time = time_get(); - - for(i = 8303; i <= 8310; i++) - { - packet.address.port = i; - netclient_send(net, &packet); - } - - if(config.debug) - dbg_msg("client", "broadcasting for servers"); - } - else if(type == BROWSETYPE_INTERNET) - need_refresh = 1; - else if(type == BROWSETYPE_FAVORITES) - { - int i; - for(i = 0; i < num_favorite_servers; i++) - client_serverbrowse_set(&favorite_servers[i], BROWSESET_FAV_ADD, -1, 0); - } -} - -static void client_serverbrowse_request_impl(NETADDR *addr, SERVERENTRY *entry) -{ - /*unsigned char buffer[sizeof(SERVERBROWSE_GETINFO)+1];*/ - NETCHUNK p; - - if(config.debug) - { - dbg_msg("client", "requesting server info from %d.%d.%d.%d:%d", - addr->ip[0], addr->ip[1], addr->ip[2], - addr->ip[3], addr->port); - } - - /*mem_copy(buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)); - buffer[sizeof(SERVERBROWSE_GETINFO)] = current_token;*/ - - p.client_id = -1; - p.address = *addr; - p.flags = NETSENDFLAG_CONNLESS; - /*p.data_size = sizeof(buffer); - p.data = buffer; - netclient_send(net, &p);*/ - - /* send old requtest style aswell */ - p.data_size = sizeof(SERVERBROWSE_OLD_GETINFO); - p.data = SERVERBROWSE_OLD_GETINFO; - netclient_send(net, &p); - - if(entry) - entry->request_time = time_get(); -} - -void client_serverbrowse_request(NETADDR *addr) -{ - client_serverbrowse_request_impl(addr, 0); -} - - -void client_serverbrowse_update() -{ - int64 timeout = time_freq(); - int64 now = time_get(); - int count; - SERVERENTRY *entry, *next; - - /* do server list requests */ - if(need_refresh && !mastersrv_refreshing()) - { - NETADDR addr; - NETCHUNK p; - int i; - - need_refresh = 0; - - mem_zero(&p, sizeof(p)); - p.client_id = -1; - p.flags = NETSENDFLAG_CONNLESS; - p.data_size = sizeof(SERVERBROWSE_GETLIST); - p.data = SERVERBROWSE_GETLIST; - - for(i = 0; i < MAX_MASTERSERVERS; i++) - { - addr = mastersrv_get(i); - if(!addr.ip[0] && !addr.ip[1] && !addr.ip[2] && !addr.ip[3]) - continue; - - p.address = addr; - netclient_send(net, &p); - } - - if(config.debug) - dbg_msg("client", "requesting server list"); - } - - /* do timeouts */ - entry = first_req_server; - while(1) - { - if(!entry) /* no more entries */ - break; - - next = entry->next_req; - - if(entry->request_time && entry->request_time+timeout < now) - { - /* timeout */ - client_serverbrowse_remove_request(entry); - num_requests--; - } - - entry = next; - } - - /* do timeouts */ - entry = first_req_server; - count = 0; - while(1) - { - if(!entry) /* no more entries */ - break; - - /* no more then 10 concurrent requests */ - if(count == config.b_max_requests) - break; - - if(entry->request_time == 0) - client_serverbrowse_request_impl(&entry->addr, entry); - - count++; - entry = entry->next_req; - } - - /* check if we need to resort */ - /* TODO: remove the strcmp */ - if(sorthash != client_serverbrowse_sorthash() || strcmp(filterstring, config.b_filter_string) != 0 || strcmp(filtergametypestring, config.b_filter_gametype) != 0) - client_serverbrowse_sort(); -} - - -int client_serverbrowse_isfavorite(NETADDR addr) -{ - /* search for the address */ - int i; - for(i = 0; i < num_favorite_servers; i++) - { - if(net_addr_comp(&addr, &favorite_servers[i]) == 0) - return 1; - } - return 0; -} - -void client_serverbrowse_addfavorite(NETADDR addr) -{ - int i; - SERVERENTRY *entry; - - if(num_favorite_servers == MAX_FAVORITES) - return; - - /* make sure that we don't already have the server in our list */ - for(i = 0; i < num_favorite_servers; i++) - { - if(net_addr_comp(&addr, &favorite_servers[i]) == 0) - return; - } - - /* add the server to the list */ - favorite_servers[num_favorite_servers++] = addr; - entry = client_serverbrowse_find(&addr); - if(entry) - entry->info.favorite = 1; - dbg_msg("", "added fav, %p", entry); -} - -void client_serverbrowse_removefavorite(NETADDR addr) -{ - int i; - SERVERENTRY *entry; - - for(i = 0; i < num_favorite_servers; i++) - { - if(net_addr_comp(&addr, &favorite_servers[i]) == 0) - { - mem_move(&favorite_servers[i], &favorite_servers[i+1], num_favorite_servers-(i+1)); - num_favorite_servers--; - - entry = client_serverbrowse_find(&addr); - if(entry) - entry->info.favorite = 0; - - return; - } - } -} - -void client_serverbrowse_save() -{ - int i; - char addrstr[128]; - char buffer[256]; - for(i = 0; i < num_favorite_servers; i++) - { - net_addr_str(&favorite_servers[i], addrstr, sizeof(addrstr)); - str_format(buffer, sizeof(buffer), "add_favorite %s", addrstr); - engine_config_write_line(buffer); - } -} - - -int client_serverbrowse_refreshingmasters() -{ - return mastersrv_refreshing(); -} diff --git a/src/engine/client/ec_srvbrowse.cpp b/src/engine/client/ec_srvbrowse.cpp new file mode 100644 index 00000000..1b04937a --- /dev/null +++ b/src/engine/client/ec_srvbrowse.cpp @@ -0,0 +1,736 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +extern CNetClient m_NetClient; + + +/* ------ server browse ---- */ +/* TODO: move all this to a separate file */ + +typedef struct SERVERENTRY_t SERVERENTRY; +struct SERVERENTRY_t +{ + NETADDR addr; + int64 request_time; + int got_info; + SERVER_INFO info; + + SERVERENTRY *next_ip; /* ip hashed list */ + + SERVERENTRY *prev_req; /* request list */ + SERVERENTRY *next_req; +}; + +static HEAP *serverlist_heap = 0; +static SERVERENTRY **serverlist = 0; +static int *sorted_serverlist = 0; + +enum +{ + MAX_FAVORITES=256 +}; + +static NETADDR favorite_servers[MAX_FAVORITES]; +static int num_favorite_servers = 0; + +static SERVERENTRY *serverlist_ip[256] = {0}; /* ip hash list */ + +static SERVERENTRY *first_req_server = 0; /* request list */ +static SERVERENTRY *last_req_server = 0; +static int num_requests = 0; + +static int need_refresh = 0; + +static int num_sorted_servers = 0; +static int num_sorted_servers_capacity = 0; +static int num_servers = 0; +static int num_server_capacity = 0; + +static int sorthash = 0; +static char filterstring[64] = {0}; +static char filtergametypestring[128] = {0}; + +/* the token is to keep server refresh separated from each other */ +static int current_token = 1; + +static int serverlist_type = 0; +static int64 broadcast_time = 0; + +int client_serverbrowse_lan() { return serverlist_type == BROWSETYPE_LAN; } +int client_serverbrowse_num() { return num_servers; } + +SERVER_INFO *client_serverbrowse_get(int index) +{ + if(index < 0 || index >= num_servers) + return 0; + return &serverlist[index]->info; +} + +int client_serverbrowse_sorted_num() { return num_sorted_servers; } + +SERVER_INFO *client_serverbrowse_sorted_get(int index) +{ + if(index < 0 || index >= num_sorted_servers) + return 0; + return &serverlist[sorted_serverlist[index]]->info; +} + + +int client_serverbrowse_num_requests() +{ + return num_requests; +} + +static int client_serverbrowse_sort_compare_name(const void *ai, const void *bi) +{ + SERVERENTRY *a = serverlist[*(const int*)ai]; + SERVERENTRY *b = serverlist[*(const int*)bi]; + return strcmp(a->info.name, b->info.name); +} + +static int client_serverbrowse_sort_compare_map(const void *ai, const void *bi) +{ + SERVERENTRY *a = serverlist[*(const int*)ai]; + SERVERENTRY *b = serverlist[*(const int*)bi]; + return strcmp(a->info.map, b->info.map); +} + +static int client_serverbrowse_sort_compare_ping(const void *ai, const void *bi) +{ + SERVERENTRY *a = serverlist[*(const int*)ai]; + SERVERENTRY *b = serverlist[*(const int*)bi]; + if(a->info.latency > b->info.latency) return 1; + if(a->info.latency < b->info.latency) return -1; + return 0; +} + +static int client_serverbrowse_sort_compare_gametype(const void *ai, const void *bi) +{ + SERVERENTRY *a = serverlist[*(const int*)ai]; + SERVERENTRY *b = serverlist[*(const int*)bi]; + return strcmp(a->info.gametype, b->info.gametype); +} + +static int client_serverbrowse_sort_compare_progression(const void *ai, const void *bi) +{ + SERVERENTRY *a = serverlist[*(const int*)ai]; + SERVERENTRY *b = serverlist[*(const int*)bi]; + if(a->info.progression > b->info.progression) return 1; + if(a->info.progression < b->info.progression) return -1; + return 0; +} + +static int client_serverbrowse_sort_compare_numplayers(const void *ai, const void *bi) +{ + SERVERENTRY *a = serverlist[*(const int*)ai]; + SERVERENTRY *b = serverlist[*(const int*)bi]; + if(a->info.num_players > b->info.num_players) return 1; + if(a->info.num_players < b->info.num_players) return -1; + return 0; +} + +static void client_serverbrowse_filter() +{ + int i = 0, p = 0; + num_sorted_servers = 0; + + /* allocate the sorted list */ + if(num_sorted_servers_capacity < num_servers) + { + if(sorted_serverlist) + mem_free(sorted_serverlist); + num_sorted_servers_capacity = num_servers; + sorted_serverlist = (int *)mem_alloc(num_sorted_servers_capacity*sizeof(int), 1); + } + + /* filter the servers */ + for(i = 0; i < num_servers; i++) + { + int filtered = 0; + + if(config.b_filter_empty && serverlist[i]->info.num_players == 0) + filtered = 1; + else if(config.b_filter_full && serverlist[i]->info.num_players == serverlist[i]->info.max_players) + filtered = 1; + else if(config.b_filter_pw && serverlist[i]->info.flags&SRVFLAG_PASSWORD) + filtered = 1; + else if(config.b_filter_pure && (strcmp(serverlist[i]->info.gametype, "DM") != 0 && strcmp(serverlist[i]->info.gametype, "TDM") != 0 && strcmp(serverlist[i]->info.gametype, "CTF") != 0)) + filtered = 1; + else if(config.b_filter_pure_map && + !(strcmp(serverlist[i]->info.map, "dm1") == 0 || + strcmp(serverlist[i]->info.map, "dm2") == 0 || + strcmp(serverlist[i]->info.map, "dm6") == 0 || + strcmp(serverlist[i]->info.map, "dm7") == 0 || + strcmp(serverlist[i]->info.map, "dm8") == 0 || + strcmp(serverlist[i]->info.map, "dm9") == 0 || + strcmp(serverlist[i]->info.map, "ctf1") == 0 || + strcmp(serverlist[i]->info.map, "ctf2") == 0 || + strcmp(serverlist[i]->info.map, "ctf3") == 0 || + strcmp(serverlist[i]->info.map, "ctf4") == 0 || + strcmp(serverlist[i]->info.map, "ctf5") == 0) + ) + { + filtered = 1; + } + else if(config.b_filter_ping < serverlist[i]->info.latency) + filtered = 1; + else if(config.b_filter_compatversion && strncmp(serverlist[i]->info.version, modc_net_version(), 3) != 0) + filtered = 1; + else + { + if(config.b_filter_string[0] != 0) + { + int matchfound = 0; + + serverlist[i]->info.quicksearch_hit = 0; + + /* match against server name */ + if(str_find_nocase(serverlist[i]->info.name, config.b_filter_string)) + { + matchfound = 1; + serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_SERVERNAME; + } + + /* match against players */ + for(p = 0; p < serverlist[i]->info.num_players; p++) + { + if(str_find_nocase(serverlist[i]->info.players[p].name, config.b_filter_string)) + { + matchfound = 1; + serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_PLAYERNAME; + break; + } + } + + /* match against map */ + if(str_find_nocase(serverlist[i]->info.map, config.b_filter_string)) + { + matchfound = 1; + serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_MAPNAME; + } + + if(!matchfound) + filtered = 1; + } + + if(!filtered && config.b_filter_gametype[0] != 0) + { + /* match against game type */ + if(!str_find_nocase(serverlist[i]->info.gametype, config.b_filter_gametype)) + filtered = 1; + } + } + + if(filtered == 0) + sorted_serverlist[num_sorted_servers++] = i; + } +} + +static int client_serverbrowse_sorthash() +{ + int i = config.b_sort&0xf; + i |= config.b_filter_empty<<4; + i |= config.b_filter_full<<5; + i |= config.b_filter_pw<<6; + i |= config.b_sort_order<<7; + i |= config.b_filter_compatversion<<8; + i |= config.b_filter_pure<<9; + i |= config.b_filter_pure_map<<10; + i |= config.b_filter_ping<<16; + return i; +} + +static void client_serverbrowse_sort() +{ + int i; + + /* create filtered list */ + client_serverbrowse_filter(); + + /* sort */ + if(config.b_sort == BROWSESORT_NAME) + qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_name); + else if(config.b_sort == BROWSESORT_PING) + qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_ping); + else if(config.b_sort == BROWSESORT_MAP) + qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_map); + else if(config.b_sort == BROWSESORT_NUMPLAYERS) + qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_numplayers); + else if(config.b_sort == BROWSESORT_GAMETYPE) + qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_gametype); + else if(config.b_sort == BROWSESORT_PROGRESSION) + qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_progression); + + /* invert the list if requested */ + if(config.b_sort_order) + { + for(i = 0; i < num_sorted_servers/2; i++) + { + int temp = sorted_serverlist[i]; + sorted_serverlist[i] = sorted_serverlist[num_sorted_servers-i-1]; + sorted_serverlist[num_sorted_servers-i-1] = temp; + } + } + + /* set indexes */ + for(i = 0; i < num_sorted_servers; i++) + serverlist[sorted_serverlist[i]]->info.sorted_index = i; + + str_copy(filtergametypestring, config.b_filter_gametype, sizeof(filtergametypestring)); + str_copy(filterstring, config.b_filter_string, sizeof(filterstring)); + sorthash = client_serverbrowse_sorthash(); +} + +static void client_serverbrowse_remove_request(SERVERENTRY *entry) +{ + if(entry->prev_req || entry->next_req || first_req_server == entry) + { + if(entry->prev_req) + entry->prev_req->next_req = entry->next_req; + else + first_req_server = entry->next_req; + + if(entry->next_req) + entry->next_req->prev_req = entry->prev_req; + else + last_req_server = entry->prev_req; + + entry->prev_req = 0; + entry->next_req = 0; + num_requests--; + } +} + +static SERVERENTRY *client_serverbrowse_find(NETADDR *addr) +{ + SERVERENTRY *entry = serverlist_ip[addr->ip[0]]; + + for(; entry; entry = entry->next_ip) + { + if(net_addr_comp(&entry->addr, addr) == 0) + return entry; + } + return (SERVERENTRY*)0; +} + +void client_serverbrowse_queuerequest(SERVERENTRY *entry) +{ + /* add it to the list of servers that we should request info from */ + entry->prev_req = last_req_server; + if(last_req_server) + last_req_server->next_req = entry; + else + first_req_server = entry; + last_req_server = entry; + + num_requests++; +} + +void client_serverbrowse_setinfo(SERVERENTRY *entry, SERVER_INFO *info) +{ + int fav = entry->info.favorite; + entry->info = *info; + entry->info.favorite = fav; + entry->info.netaddr = entry->addr; + + // all these are just for nice compability + if(entry->info.gametype[0] == '0' && entry->info.gametype[1] == 0) + str_copy(entry->info.gametype, "DM", sizeof(entry->info.gametype)); + else if(entry->info.gametype[0] == '1' && entry->info.gametype[1] == 0) + str_copy(entry->info.gametype, "TDM", sizeof(entry->info.gametype)); + else if(entry->info.gametype[0] == '2' && entry->info.gametype[1] == 0) + str_copy(entry->info.gametype, "CTF", sizeof(entry->info.gametype)); + + /*if(!request) + { + entry->info.latency = (time_get()-entry->request_time)*1000/time_freq(); + client_serverbrowse_remove_request(entry); + }*/ + + entry->got_info = 1; + client_serverbrowse_sort(); +} + +SERVERENTRY *client_serverbrowse_add(NETADDR *addr) +{ + int hash = addr->ip[0]; + SERVERENTRY *entry = 0; + int i; + + /* create new entry */ + entry = (SERVERENTRY *)memheap_allocate(serverlist_heap, sizeof(SERVERENTRY)); + mem_zero(entry, sizeof(SERVERENTRY)); + + /* set the info */ + entry->addr = *addr; + entry->info.netaddr = *addr; + + entry->info.latency = 999; + str_format(entry->info.address, sizeof(entry->info.address), "%d.%d.%d.%d:%d", + addr->ip[0], addr->ip[1], addr->ip[2], + addr->ip[3], addr->port); + str_format(entry->info.name, sizeof(entry->info.name), "\255%d.%d.%d.%d:%d", /* the \255 is to make sure that it's sorted last */ + addr->ip[0], addr->ip[1], addr->ip[2], + addr->ip[3], addr->port); + + /*if(serverlist_type == BROWSETYPE_LAN) + entry->info.latency = (time_get()-broadcast_time)*1000/time_freq();*/ + + /* check if it's a favorite */ + for(i = 0; i < num_favorite_servers; i++) + { + if(net_addr_comp(addr, &favorite_servers[i]) == 0) + entry->info.favorite = 1; + } + + /* add to the hash list */ + entry->next_ip = serverlist_ip[hash]; + serverlist_ip[hash] = entry; + + if(num_servers == num_server_capacity) + { + SERVERENTRY **newlist; + num_server_capacity += 100; + newlist = (SERVERENTRY **)mem_alloc(num_server_capacity*sizeof(SERVERENTRY*), 1); + mem_copy(newlist, serverlist, num_servers*sizeof(SERVERENTRY*)); + mem_free(serverlist); + serverlist = newlist; + } + + /* add to list */ + serverlist[num_servers] = entry; + entry->info.server_index = num_servers; + num_servers++; + + return entry; +} + +void client_serverbrowse_set(NETADDR *addr, int type, int token, SERVER_INFO *info) +{ + SERVERENTRY *entry = 0; + if(type == BROWSESET_MASTER_ADD) + { + if(serverlist_type != BROWSETYPE_INTERNET) + return; + + if(!client_serverbrowse_find(addr)) + { + entry = client_serverbrowse_add(addr); + client_serverbrowse_queuerequest(entry); + } + } + else if(type == BROWSESET_FAV_ADD) + { + if(serverlist_type != BROWSETYPE_FAVORITES) + return; + + if(!client_serverbrowse_find(addr)) + { + entry = client_serverbrowse_add(addr); + client_serverbrowse_queuerequest(entry); + } + } + else if(type == BROWSESET_TOKEN) + { + if(token != current_token) + return; + + entry = client_serverbrowse_find(addr); + if(!entry) + entry = client_serverbrowse_add(addr); + if(entry) + { + client_serverbrowse_setinfo(entry, info); + if(serverlist_type == BROWSETYPE_LAN) + entry->info.latency = (time_get()-broadcast_time)*1000/time_freq(); + else + entry->info.latency = (time_get()-entry->request_time)*1000/time_freq(); + client_serverbrowse_remove_request(entry); + } + } + else if(type == BROWSESET_OLD_INTERNET) + { + entry = client_serverbrowse_find(addr); + if(entry) + { + client_serverbrowse_setinfo(entry, info); + + if(serverlist_type == BROWSETYPE_LAN) + entry->info.latency = (time_get()-broadcast_time)*1000/time_freq(); + else + entry->info.latency = (time_get()-entry->request_time)*1000/time_freq(); + client_serverbrowse_remove_request(entry); + } + } + else if(type == BROWSESET_OLD_LAN) + { + entry = client_serverbrowse_find(addr); + if(entry) + if(!entry) + entry = client_serverbrowse_add(addr); + if(entry) + client_serverbrowse_setinfo(entry, info); + } + + client_serverbrowse_sort(); +} + +void client_serverbrowse_refresh(int type) +{ + /* clear out everything */ + if(serverlist_heap) + memheap_destroy(serverlist_heap); + serverlist_heap = memheap_create(); + num_servers = 0; + num_sorted_servers = 0; + mem_zero(serverlist_ip, sizeof(serverlist_ip)); + first_req_server = 0; + last_req_server = 0; + num_requests = 0; + + /* next token */ + current_token = (current_token+1)&0xff; + + /* */ + serverlist_type = type; + + if(type == BROWSETYPE_LAN) + { + unsigned char Buffer[sizeof(SERVERBROWSE_GETINFO)+1]; + CNetChunk Packet; + int i; + + mem_copy(Buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)); + Buffer[sizeof(SERVERBROWSE_GETINFO)] = current_token; + + Packet.m_ClientID = -1; + mem_zero(&Packet, sizeof(Packet)); + Packet.m_Address.ip[0] = 255; + Packet.m_Address.ip[1] = 255; + Packet.m_Address.ip[2] = 255; + Packet.m_Address.ip[3] = 255; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = sizeof(Buffer); + Packet.m_pData = Buffer; + broadcast_time = time_get(); + + for(i = 8303; i <= 8310; i++) + { + Packet.m_Address.port = i; + m_NetClient.Send(&Packet); + } + + if(config.debug) + dbg_msg("client", "broadcasting for servers"); + } + else if(type == BROWSETYPE_INTERNET) + need_refresh = 1; + else if(type == BROWSETYPE_FAVORITES) + { + for(int i = 0; i < num_favorite_servers; i++) + client_serverbrowse_set(&favorite_servers[i], BROWSESET_FAV_ADD, -1, 0); + } +} + +static void client_serverbrowse_request_impl(NETADDR *addr, SERVERENTRY *entry) +{ + /*unsigned char buffer[sizeof(SERVERBROWSE_GETINFO)+1];*/ + CNetChunk Packet; + + if(config.debug) + { + dbg_msg("client", "requesting server info from %d.%d.%d.%d:%d", + addr->ip[0], addr->ip[1], addr->ip[2], + addr->ip[3], addr->port); + } + + /*mem_copy(buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)); + buffer[sizeof(SERVERBROWSE_GETINFO)] = current_token;*/ + + Packet.m_ClientID = -1; + Packet.m_Address = *addr; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + /*p.data_size = sizeof(buffer); + p.data = buffer; + netclient_send(net, &p);*/ + + /* send old requtest style aswell */ + Packet.m_DataSize = sizeof(SERVERBROWSE_OLD_GETINFO); + Packet.m_pData = SERVERBROWSE_OLD_GETINFO; + m_NetClient.Send(&Packet); + + if(entry) + entry->request_time = time_get(); +} + +void client_serverbrowse_request(NETADDR *addr) +{ + client_serverbrowse_request_impl(addr, 0); +} + + +void client_serverbrowse_update() +{ + int64 timeout = time_freq(); + int64 now = time_get(); + int count; + SERVERENTRY *entry, *next; + + /* do server list requests */ + if(need_refresh && !mastersrv_refreshing()) + { + NETADDR addr; + CNetChunk Packet; + int i; + + need_refresh = 0; + + mem_zero(&Packet, sizeof(Packet)); + Packet.m_ClientID = -1; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = sizeof(SERVERBROWSE_GETLIST); + Packet.m_pData = SERVERBROWSE_GETLIST; + + for(i = 0; i < MAX_MASTERSERVERS; i++) + { + addr = mastersrv_get(i); + if(!addr.ip[0] && !addr.ip[1] && !addr.ip[2] && !addr.ip[3]) + continue; + + Packet.m_Address = addr; + m_NetClient.Send(&Packet); + } + + if(config.debug) + dbg_msg("client", "requesting server list"); + } + + /* do timeouts */ + entry = first_req_server; + while(1) + { + if(!entry) /* no more entries */ + break; + + next = entry->next_req; + + if(entry->request_time && entry->request_time+timeout < now) + { + /* timeout */ + client_serverbrowse_remove_request(entry); + num_requests--; + } + + entry = next; + } + + /* do timeouts */ + entry = first_req_server; + count = 0; + while(1) + { + if(!entry) /* no more entries */ + break; + + /* no more then 10 concurrent requests */ + if(count == config.b_max_requests) + break; + + if(entry->request_time == 0) + client_serverbrowse_request_impl(&entry->addr, entry); + + count++; + entry = entry->next_req; + } + + /* check if we need to resort */ + /* TODO: remove the strcmp */ + if(sorthash != client_serverbrowse_sorthash() || strcmp(filterstring, config.b_filter_string) != 0 || strcmp(filtergametypestring, config.b_filter_gametype) != 0) + client_serverbrowse_sort(); +} + + +int client_serverbrowse_isfavorite(NETADDR addr) +{ + /* search for the address */ + int i; + for(i = 0; i < num_favorite_servers; i++) + { + if(net_addr_comp(&addr, &favorite_servers[i]) == 0) + return 1; + } + return 0; +} + +void client_serverbrowse_addfavorite(NETADDR addr) +{ + int i; + SERVERENTRY *entry; + + if(num_favorite_servers == MAX_FAVORITES) + return; + + /* make sure that we don't already have the server in our list */ + for(i = 0; i < num_favorite_servers; i++) + { + if(net_addr_comp(&addr, &favorite_servers[i]) == 0) + return; + } + + /* add the server to the list */ + favorite_servers[num_favorite_servers++] = addr; + entry = client_serverbrowse_find(&addr); + if(entry) + entry->info.favorite = 1; + dbg_msg("", "added fav, %p", entry); +} + +void client_serverbrowse_removefavorite(NETADDR addr) +{ + int i; + SERVERENTRY *entry; + + for(i = 0; i < num_favorite_servers; i++) + { + if(net_addr_comp(&addr, &favorite_servers[i]) == 0) + { + mem_move(&favorite_servers[i], &favorite_servers[i+1], num_favorite_servers-(i+1)); + num_favorite_servers--; + + entry = client_serverbrowse_find(&addr); + if(entry) + entry->info.favorite = 0; + + return; + } + } +} + +void client_serverbrowse_save() +{ + int i; + char addrstr[128]; + char buffer[256]; + for(i = 0; i < num_favorite_servers; i++) + { + net_addr_str(&favorite_servers[i], addrstr, sizeof(addrstr)); + str_format(buffer, sizeof(buffer), "add_favorite %s", addrstr); + engine_config_write_line(buffer); + } +} + + +int client_serverbrowse_refreshingmasters() +{ + return mastersrv_refreshing(); +} -- cgit 1.4.1