about summary refs log tree commit diff
path: root/src/game/client/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/game/client/components')
-rw-r--r--src/game/client/components/hud.cpp3
-rw-r--r--src/game/client/components/mapimages.cpp2
-rw-r--r--src/game/client/components/mapimages.hpp4
-rw-r--r--src/game/client/components/maplayers.cpp2
-rw-r--r--src/game/client/components/menus.cpp92
-rw-r--r--src/game/client/components/menus.hpp39
-rw-r--r--src/game/client/components/menus_browser.cpp4
-rw-r--r--src/game/client/components/menus_demo.cpp390
-rw-r--r--src/game/client/components/menus_ingame.cpp2
-rw-r--r--src/game/client/components/players.cpp4
10 files changed, 500 insertions, 42 deletions
diff --git a/src/game/client/components/hud.cpp b/src/game/client/components/hud.cpp
index 86ce6257..2b1c1663 100644
--- a/src/game/client/components/hud.cpp
+++ b/src/game/client/components/hud.cpp
@@ -305,7 +305,8 @@ void HUD::on_render()
 
 	render_goals();
 	render_fps();
-	render_connectionwarning();
+	if(client_state() != CLIENTSTATE_DEMOPLAYBACK)
+		render_connectionwarning();
 	render_tunewarning();
 	render_teambalancewarning();
 	render_voting();
diff --git a/src/game/client/components/mapimages.cpp b/src/game/client/components/mapimages.cpp
index 9dc2447a..7f839da0 100644
--- a/src/game/client/components/mapimages.cpp
+++ b/src/game/client/components/mapimages.cpp
@@ -8,7 +8,7 @@ MAPIMAGES::MAPIMAGES()
 	count = 0;
 }
 
-void MAPIMAGES::on_reset()
+void MAPIMAGES::on_mapload()
 {
 	// unload all textures
 	for(int i = 0; i < count; i++)
diff --git a/src/game/client/components/mapimages.hpp b/src/game/client/components/mapimages.hpp
index e1e0063d..cba46033 100644
--- a/src/game/client/components/mapimages.hpp
+++ b/src/game/client/components/mapimages.hpp
@@ -9,7 +9,7 @@ public:
 	
 	int get(int index) const { return textures[index]; }
 	int num() const { return count; }
-	
-	virtual void on_reset();
+
+	virtual void on_mapload();
 };
 
diff --git a/src/game/client/components/maplayers.cpp b/src/game/client/components/maplayers.cpp
index e1518036..c6fea413 100644
--- a/src/game/client/components/maplayers.cpp
+++ b/src/game/client/components/maplayers.cpp
@@ -50,7 +50,7 @@ static void envelope_eval(float time_offset, int env, float *channels)
 
 void MAPLAYERS::on_render()
 {
-	if(client_state() != CLIENTSTATE_ONLINE)
+	if(client_state() != CLIENTSTATE_ONLINE && client_state() != CLIENTSTATE_DEMOPLAYBACK)
 		return;
 	
 	vec2 center = gameclient.camera->center;
diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp
index b1043870..f71f0d96 100644
--- a/src/game/client/components/menus.cpp
+++ b/src/game/client/components/menus.cpp
@@ -32,6 +32,11 @@ vec4 MENUS::color_tabbar_active;
 vec4 MENUS::color_tabbar_inactive_ingame;
 vec4 MENUS::color_tabbar_active_ingame;
 
+
+float MENUS::button_height = 25.0f;
+float MENUS::listheader_height = 16.0f;
+float MENUS::fontmod_height = 0.8f;
+
 INPUT_EVENT MENUS::inputevents[MAX_INPUTEVENTS];
 int MENUS::num_inputevents;
 
@@ -84,9 +89,10 @@ MENUS::MENUS()
 	menu_active = true;
 	num_inputevents = 0;
 	
-	last_input = time_get();
+	demos = 0;
+	num_demos = 0;
 	
-	button_height = 25.0f;
+	last_input = time_get();
 }
 
 vec4 MENUS::button_color_mul(const void *id)
@@ -111,22 +117,27 @@ void MENUS::ui_draw_browse_icon(int what, const RECT *r)
 void MENUS::ui_draw_menu_button(const void *id, const char *text, int checked, const RECT *r, const void *extra)
 {
 	ui_draw_rect(r, vec4(1,1,1,0.5f)*button_color_mul(id), CORNER_ALL, 5.0f);
-	ui_do_label(r, text, 18.0f, 0);
+	ui_do_label(r, text, r->h*fontmod_height, 0);
 }
 
 void MENUS::ui_draw_keyselect_button(const void *id, const char *text, int checked, const RECT *r, const void *extra)
 {
 	ui_draw_rect(r, vec4(1,1,1,0.5f)*button_color_mul(id), CORNER_ALL, 5.0f);
-	ui_do_label(r, text, 14.0f, 0);
+	ui_do_label(r, text, r->h*fontmod_height, 0);
 }
 
 void MENUS::ui_draw_menu_tab_button(const void *id, const char *text, int checked, const RECT *r, const void *extra)
 {
+	int corners = CORNER_T;
+	vec4 colormod(1,1,1,1);
+	if(extra)
+		corners = *(int *)extra;
+	
 	if(checked)
-		ui_draw_rect(r, color_tabbar_active, CORNER_T, 10.0f);
+		ui_draw_rect(r, color_tabbar_active, corners, 10.0f);
 	else
-		ui_draw_rect(r, color_tabbar_inactive, CORNER_T, 10.0f);
-	ui_do_label(r, text, 22.0f, 0);
+		ui_draw_rect(r, color_tabbar_inactive, corners, 10.0f);
+	ui_do_label(r, text, r->h*fontmod_height, 0);
 }
 
 
@@ -136,7 +147,7 @@ void MENUS::ui_draw_settings_tab_button(const void *id, const char *text, int ch
 		ui_draw_rect(r, color_tabbar_active, CORNER_R, 10.0f);
 	else
 		ui_draw_rect(r, color_tabbar_inactive, CORNER_R, 10.0f);
-	ui_do_label(r, text, 20.0f, 0);
+	ui_do_label(r, text, r->h*fontmod_height, 0);
 }
 
 void MENUS::ui_draw_grid_header(const void *id, const char *text, int checked, const RECT *r, const void *extra)
@@ -145,7 +156,7 @@ void MENUS::ui_draw_grid_header(const void *id, const char *text, int checked, c
 		ui_draw_rect(r, vec4(1,1,1,0.5f), CORNER_T, 5.0f);
 	RECT t;
 	ui_vsplit_l(r, 5.0f, 0, &t);
-	ui_do_label(&t, text, 14.0f, -1);
+	ui_do_label(&t, text, r->h*fontmod_height, -1);
 }
 
 void MENUS::ui_draw_list_row(const void *id, const char *text, int checked, const RECT *r, const void *extra)
@@ -156,7 +167,7 @@ void MENUS::ui_draw_list_row(const void *id, const char *text, int checked, cons
 		ui_margin(&sr, 1.5f, &sr);
 		ui_draw_rect(&sr, vec4(1,1,1,0.5f), CORNER_ALL, 4.0f);
 	}
-	ui_do_label(r, text, 14.0f, -1);
+	ui_do_label(r, text, r->h*fontmod_height, -1);
 }
 
 void MENUS::ui_draw_checkbox_common(const void *id, const char *text, const char *boxtext, const RECT *r)
@@ -171,8 +182,8 @@ void MENUS::ui_draw_checkbox_common(const void *id, const char *text, const char
 	ui_margin(&c, 2.0f, &c);
 	ui_draw_rect(&c, vec4(1,1,1,0.25f)*button_color_mul(id), CORNER_ALL, 3.0f);
 	c.y += 2;
-	ui_do_label(&c, boxtext, 12.0f, 0);
-	ui_do_label(&t, text, 14.0f, -1);
+	ui_do_label(&c, boxtext, r->h*fontmod_height*0.75f, 0);
+	ui_do_label(&t, text, r->h*fontmod_height*0.8f, -1);
 }
 
 void MENUS::ui_draw_checkbox(const void *id, const char *text, int checked, const RECT *r, const void *extra)
@@ -489,31 +500,43 @@ int MENUS::render_menubar(RECT r)
 			ui_vsplit_l(&box, 30.0f, 0, &box); 
 		}
 
-		ui_vsplit_l(&box, 110.0f, &button, &box);
+		ui_vsplit_l(&box, 100.0f, &button, &box);
 		static int internet_button=0;
-		if (ui_do_button(&internet_button, "Internet", active_page==PAGE_INTERNET, &button, ui_draw_menu_tab_button, 0))
+		int corners = CORNER_TL;
+		if (ui_do_button(&internet_button, "Internet", active_page==PAGE_INTERNET, &button, ui_draw_menu_tab_button, &corners))
 		{
 			client_serverbrowse_refresh(BROWSETYPE_INTERNET);
 			new_page = PAGE_INTERNET;
 		}
 
-		ui_vsplit_l(&box, 4.0f, 0, &box);
-		ui_vsplit_l(&box, 90.0f, &button, &box);
+		//ui_vsplit_l(&box, 4.0f, 0, &box);
+		ui_vsplit_l(&box, 80.0f, &button, &box);
 		static int lan_button=0;
-		if (ui_do_button(&lan_button, "LAN", active_page==PAGE_LAN, &button, ui_draw_menu_tab_button, 0))
+		corners = 0;
+		if (ui_do_button(&lan_button, "LAN", active_page==PAGE_LAN, &button, ui_draw_menu_tab_button, &corners))
 		{
 			client_serverbrowse_refresh(BROWSETYPE_LAN);
 			new_page = PAGE_LAN;
 		}
 
-		ui_vsplit_l(&box, 4.0f, 0, &box);
-		ui_vsplit_l(&box, 120.0f, &button, &box);
+		//ui_vsplit_l(&box, 4.0f, 0, &box);
+		ui_vsplit_l(&box, 110.0f, &button, &box);
 		static int favorites_button=0;
-		if (ui_do_button(&favorites_button, "Favorites", active_page==PAGE_FAVORITES, &button, ui_draw_menu_tab_button, 0))
+		corners = CORNER_TR;
+		if (ui_do_button(&favorites_button, "Favorites", active_page==PAGE_FAVORITES, &button, ui_draw_menu_tab_button, &corners))
 		{
 			client_serverbrowse_refresh(BROWSETYPE_FAVORITES);
 			new_page  = PAGE_FAVORITES;
 		}
+		
+		ui_vsplit_l(&box, 4.0f*5, 0, &box);
+		ui_vsplit_l(&box, 100.0f, &button, &box);
+		static int demos_button=0;
+		if (ui_do_button(&demos_button, "Demos", active_page==PAGE_DEMOS, &button, ui_draw_menu_tab_button, 0))
+		{
+			//client_serverbrowse_refresh(BROWSETYPE_FAVORITES);
+			new_page  = PAGE_DEMOS;
+		}		
 	}
 	else
 	{
@@ -547,13 +570,13 @@ int MENUS::render_menubar(RECT r)
 	ui_vsplit_r(&box, 30.0f, &box, 0);
 	*/
 	
-	ui_vsplit_r(&box, 110.0f, &box, &button);
+	ui_vsplit_r(&box, 90.0f, &box, &button);
 	static int quit_button=0;
 	if (ui_do_button(&quit_button, "Quit", 0, &button, ui_draw_menu_tab_button, 0))
 		popup = POPUP_QUIT;
 
 	ui_vsplit_r(&box, 10.0f, &box, &button);
-	ui_vsplit_r(&box, 110.0f, &box, &button);
+	ui_vsplit_r(&box, 120.0f, &box, &button);
 	static int settings_button=0;
 	if (ui_do_button(&settings_button, "Settings", active_page==PAGE_SETTINGS, &button, ui_draw_menu_tab_button, 0))
 		new_page = PAGE_SETTINGS;
@@ -663,7 +686,7 @@ int MENUS::render()
 	if(popup == POPUP_NONE)
 	{
 		// do tab bar
-		ui_hsplit_t(&screen, 26.0f, &tab_bar, &main_view);
+		ui_hsplit_t(&screen, 24.0f, &tab_bar, &main_view);
 		ui_vmargin(&tab_bar, 20.0f, &tab_bar);
 		render_menubar(tab_bar);
 		
@@ -691,6 +714,8 @@ int MENUS::render()
 			render_serverbrowser(main_view);
 		else if(config.ui_page == PAGE_LAN)
 			render_serverbrowser(main_view);
+		else if(config.ui_page == PAGE_DEMOS)
+			render_demolist(main_view);
 		else if(config.ui_page == PAGE_FAVORITES)
 			render_serverbrowser(main_view);
 		else if(config.ui_page == PAGE_SETTINGS)
@@ -922,7 +947,7 @@ void MENUS::on_statechange(int new_state, int old_state)
 		popup = POPUP_CONNECTING;
 	else if(new_state == CLIENTSTATE_CONNECTING)
 		popup = POPUP_CONNECTING;
-	else if (new_state == CLIENTSTATE_ONLINE)
+	else if (new_state == CLIENTSTATE_ONLINE || new_state == CLIENTSTATE_DEMOPLAYBACK)
 	{
 		popup = POPUP_NONE;
 		menu_active = false;
@@ -931,17 +956,19 @@ void MENUS::on_statechange(int new_state, int old_state)
 
 void MENUS::on_render()
 {
-	if(client_state() != CLIENTSTATE_ONLINE)
+	if(client_state() != CLIENTSTATE_ONLINE && client_state() != CLIENTSTATE_DEMOPLAYBACK)
 		menu_active = true;
+
+	if(client_state() == CLIENTSTATE_DEMOPLAYBACK)
+	{
+		RECT screen = *ui_screen();
+		gfx_mapscreen(screen.x, screen.y, screen.w, screen.h);
+		render_demoplayer(screen);
+	}
 	
 	if(!menu_active)
 		return;
 		
-
-
-	if(inp_key_down('M')) button_height += 1.0f;
-	if(inp_key_down('N')) button_height -= 1.0f;
-		
 	// update colors
 	vec3 rgb = hsl_to_rgb(vec3(config.ui_color_hue/255.0f, config.ui_color_sat/255.0f, config.ui_color_lht/255.0f));
 	gui_color = vec4(rgb.r, rgb.g, rgb.b, config.ui_color_alpha/255.0f);
@@ -1002,8 +1029,8 @@ void MENUS::on_render()
 	}
 	else
 	{
-		//render_background();
-		render();
+		if(client_state() != CLIENTSTATE_DEMOPLAYBACK)
+			render();
 
 		// render cursor
 		gfx_texture_set(data->images[IMAGE_CURSOR].id);
@@ -1024,6 +1051,7 @@ void MENUS::on_render()
 			gfx_text_set_cursor(&cursor, 10, 10, 10, TEXTFLAG_RENDER);
 			gfx_text_ex(&cursor, buf, -1);
 		}
+		
 	}
 
 	num_inputevents = 0;
diff --git a/src/game/client/components/menus.hpp b/src/game/client/components/menus.hpp
index 8aae925f..51090b93 100644
--- a/src/game/client/components/menus.hpp
+++ b/src/game/client/components/menus.hpp
@@ -15,6 +15,9 @@ class MENUS : public COMPONENT
 	
 	static vec4 button_color_mul(const void *id);
 
+
+	static void ui_draw_demoplayer_button(const void *id, const char *text, int checked, const RECT *r, const void *extra);
+
 	static void ui_draw_browse_icon(int what, const RECT *r);
 	static void ui_draw_menu_button(const void *id, const char *text, int checked, const RECT *r, const void *extra);
 	static void ui_draw_keyselect_button(const void *id, const char *text, int checked, const RECT *r, const void *extra);
@@ -33,6 +36,19 @@ class MENUS : public COMPONENT
 	static int ui_do_key_reader(void *id, const RECT *rect, int key);
 	static void ui_do_getbuttons(int start, int stop, RECT view);
 
+	struct LISTBOXITEM
+	{
+		int visible;
+		int selected;
+		RECT rect;
+	};
+	
+	static void ui_do_listbox_start(void *id, const RECT *rect, float row_height, const char *title, int num_items, int selected_index);
+	static LISTBOXITEM ui_do_listbox_nextitem(void *id);
+	static int ui_do_listbox_end();
+	
+	//static void demolist_listdir_callback(const char *name, int is_dir, void *user);
+	//static void demolist_list_callback(const RECT *rect, int index, void *user);
 
 	enum
 	{
@@ -53,6 +69,7 @@ class MENUS : public COMPONENT
 		PAGE_INTERNET,
 		PAGE_LAN,
 		PAGE_FAVORITES,
+		PAGE_DEMOS,
 		PAGE_SETTINGS,
 		PAGE_SYSTEM,
 	};
@@ -71,7 +88,9 @@ class MENUS : public COMPONENT
 	static int num_inputevents;
 	
 	// some settings
-	float button_height;
+	static float button_height;
+	static float listheader_height;
+	static float fontmod_height;
 	
 	// for graphic settings
 	bool need_restart;
@@ -79,6 +98,20 @@ class MENUS : public COMPONENT
 	// for call vote
 	int callvote_selectedplayer;
 	int callvote_selectedmap;
+	
+	// demo
+	struct DEMOITEM
+	{
+		char filename[512];
+		char name[256];
+	};
+	
+	DEMOITEM *demos;
+	int num_demos;
+		
+	void demolist_populate();
+	static void demolist_count_callback(const char *name, int is_dir, void *user);
+	static void demolist_fetch_callback(const char *name, int is_dir, void *user);
 
 	// found in menus.cpp
 	int render();
@@ -87,6 +120,10 @@ class MENUS : public COMPONENT
 	int render_menubar(RECT r);
 	void render_news(RECT main_view);
 	
+	// found in menus_demo.cpp
+	void render_demoplayer(RECT main_view);
+	void render_demolist(RECT main_view);
+	
 	// found in menus_ingame.cpp
 	void render_game(RECT main_view);
 	void render_serverinfo(RECT main_view);
diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp
index a4e730e6..3cba5311 100644
--- a/src/game/client/components/menus_browser.cpp
+++ b/src/game/client/components/menus_browser.cpp
@@ -21,7 +21,7 @@ void MENUS::render_serverbrowser_serverlist(RECT view)
 	RECT headers;
 	RECT status;
 	
-	ui_hsplit_t(&view, 16.0f, &headers, &view);
+	ui_hsplit_t(&view, listheader_height, &headers, &view);
 	ui_hsplit_b(&view, 20.0f, &view, &status);
 	
 	// split of the scrollbar
@@ -634,7 +634,7 @@ void MENUS::render_serverbrowser(RECT main_view)
 		ui_vmargin(&button, 2.0f, &button);
 		//ui_vmargin(&button, 2.0f, &button);
 		static int join_button = 0;
-		if(ui_do_button(&join_button, "Connect", 0, &button, ui_draw_menu_button, 0) || inp_key_down(KEY_ENTER))
+		if(ui_do_button(&join_button, "Connect", 0, &button, ui_draw_menu_button, 0)) // || inp_key_down(KEY_ENTER))
 			client_connect(config.ui_server_address);
 					
 		ui_hsplit_b(&button_box, 5.0f, &button_box, &button);
diff --git a/src/game/client/components/menus_demo.cpp b/src/game/client/components/menus_demo.cpp
new file mode 100644
index 00000000..81717bf7
--- /dev/null
+++ b/src/game/client/components/menus_demo.cpp
@@ -0,0 +1,390 @@
+
+#include <base/math.hpp>
+
+//#include <string.h> // strcmp, strlen, strncpy
+//#include <stdlib.h> // atoi
+
+#include <engine/e_client_interface.h>
+#include <game/client/gc_render.hpp>
+#include <game/client/gameclient.hpp>
+
+//#include <game/generated/g_protocol.hpp>
+//#include <game/generated/gc_data.hpp>
+
+#include <game/client/ui.hpp>
+//#include <game/client/gameclient.hpp>
+//#include <game/client/animstate.hpp>
+
+#include "menus.hpp"
+
+void MENUS::ui_draw_demoplayer_button(const void *id, const char *text, int checked, const RECT *r, const void *extra)
+{
+	ui_draw_rect(r, vec4(1,1,1, checked ? 0.10f : 0.5f)*button_color_mul(id), CORNER_ALL, 5.0f);
+	ui_do_label(r, text, 14.0f, 0);
+}
+
+void MENUS::render_demoplayer(RECT main_view)
+{
+	const DEMOPLAYBACK_INFO *info = client_demoplayer_getinfo();
+	
+	const float seekbar_height = 15.0f;
+	const float buttonbar_height = 20.0f;
+	const float margins = 5.0f;
+	float total_height;
+	
+	if(menu_active)
+		total_height = seekbar_height+buttonbar_height+margins*3;
+	else
+		total_height = seekbar_height+margins*2;
+	
+	ui_hsplit_b(&main_view, total_height, 0, &main_view);
+	ui_vsplit_l(&main_view, 250.0f, 0, &main_view);
+	ui_vsplit_r(&main_view, 250.0f, &main_view, 0);
+	
+	ui_draw_rect(&main_view, color_tabbar_active, CORNER_T, 10.0f);
+		
+	ui_margin(&main_view, 5.0f, &main_view);
+	
+	RECT seekbar, buttonbar;
+	
+	if(menu_active)
+	{
+		ui_hsplit_t(&main_view, seekbar_height, &seekbar, &buttonbar);
+		ui_hsplit_t(&buttonbar, margins, 0, &buttonbar);
+	}
+	else
+		seekbar = main_view;
+
+	// do seekbar
+	{
+		static int seekbar_id = 0;
+		void *id = &seekbar_id;
+		char buffer[128];
+		
+		ui_draw_rect(&seekbar, vec4(0,0,0,0.5f), CORNER_ALL, 5.0f);
+		
+		int current_tick = info->current_tick - info->first_tick;
+		int total_ticks = info->last_tick - info->first_tick;
+		
+		float amount = current_tick/(float)total_ticks;
+		
+		RECT filledbar = seekbar;
+		filledbar.w = 10.0f + (filledbar.w-10.0f)*amount;
+		
+		ui_draw_rect(&filledbar, vec4(1,1,1,0.5f), CORNER_ALL, 5.0f);
+		
+		str_format(buffer, sizeof(buffer), "%d:%02d / %d:%02d",
+			current_tick/SERVER_TICK_SPEED/60, (current_tick/SERVER_TICK_SPEED)%60,
+			total_ticks/SERVER_TICK_SPEED/60, (total_ticks/SERVER_TICK_SPEED)%60);
+		ui_do_label(&seekbar, buffer, seekbar.h*0.70f, 0);
+
+		// do the logic
+	    int inside = ui_mouse_inside(&seekbar);
+			
+		if(ui_active_item() == id)
+		{
+			if(!ui_mouse_button(0))
+				ui_set_active_item(0);
+			else
+			{
+				float amount = (ui_mouse_x()-seekbar.x)/(float)seekbar.w;
+				if(amount > 0 && amount < 1.0f)
+				{
+					gameclient.on_reset();
+					client_demoplayer_setpos(amount);
+				}
+			}
+		}
+		else if(ui_hot_item() == id)
+		{
+			if(ui_mouse_button(0))
+				ui_set_active_item(id);
+		}		
+		
+		if(inside)
+			ui_set_hot_item(id);
+	}	
+	
+
+	if(menu_active)
+	{
+		// do buttons
+		RECT button;
+
+		// pause button
+		ui_vsplit_l(&buttonbar, buttonbar_height, &button, &buttonbar);
+		static int pause_button = 0;
+		if(ui_do_button(&pause_button, "| |", info->paused, &button, ui_draw_demoplayer_button, 0))
+			client_demoplayer_setpause(!info->paused);
+		
+		// play button
+		ui_vsplit_l(&buttonbar, margins, 0, &buttonbar);
+		ui_vsplit_l(&buttonbar, buttonbar_height, &button, &buttonbar);
+		static int play_button = 0;
+		if(ui_do_button(&play_button, ">", !info->paused, &button, ui_draw_demoplayer_button, 0))
+		{
+			client_demoplayer_setpause(0);
+			client_demoplayer_setspeed(1.0f);
+		}
+
+		// slowdown
+		ui_vsplit_l(&buttonbar, margins, 0, &buttonbar);
+		ui_vsplit_l(&buttonbar, buttonbar_height, &button, &buttonbar);
+		static int slowdown_button = 0;
+		if(ui_do_button(&slowdown_button, "<<", 0, &button, ui_draw_demoplayer_button, 0))
+		{
+			if(info->speed > 4.0f) client_demoplayer_setspeed(4.0f);
+			else if(info->speed > 2.0f) client_demoplayer_setspeed(2.0f);
+			else if(info->speed > 1.0f) client_demoplayer_setspeed(1.0f);
+			else if(info->speed > 0.5f) client_demoplayer_setspeed(0.5f);
+			else client_demoplayer_setspeed(0.05f);
+		}
+		
+		// fastforward
+		ui_vsplit_l(&buttonbar, margins, 0, &buttonbar);
+		ui_vsplit_l(&buttonbar, buttonbar_height, &button, &buttonbar);
+		static int fastforward_button = 0;
+		if(ui_do_button(&fastforward_button, ">>", 0, &button, ui_draw_demoplayer_button, 0))
+		{
+			if(info->speed < 0.5f) client_demoplayer_setspeed(0.5f);
+			else if(info->speed < 1.0f) client_demoplayer_setspeed(1.0f);
+			else if(info->speed < 2.0f) client_demoplayer_setspeed(2.0f);
+			else if(info->speed < 4.0f) client_demoplayer_setspeed(4.0f);
+			else client_demoplayer_setspeed(8.0f);
+		}
+
+		// speed meter
+		ui_vsplit_l(&buttonbar, margins*3, 0, &buttonbar);
+		char buffer[64];
+		if(info->speed >= 1.0f)
+			str_format(buffer, sizeof(buffer), "x%.0f", info->speed);
+		else
+			str_format(buffer, sizeof(buffer), "x%.1f", info->speed);
+		ui_do_label(&buttonbar, buffer, button.h*0.7f, -1);
+
+		// exit button
+		ui_vsplit_r(&buttonbar, buttonbar_height*3, &buttonbar, &button);
+		static int exit_button = 0;
+		if(ui_do_button(&exit_button, "Exit", 0, &button, ui_draw_demoplayer_button, 0))
+			client_disconnect();
+	}
+}
+
+static RECT listbox_originalview;
+static RECT listbox_view;
+static float listbox_rowheight;
+static int listbox_itemindex;
+static int listbox_selected_index;
+static int listbox_new_selected;
+
+void MENUS::ui_do_listbox_start(void *id, const RECT *rect, float row_height, const char *title, int num_items, int selected_index)
+{
+	RECT scroll, row;
+	RECT view = *rect;
+	RECT header, footer;
+	
+	// draw header
+	ui_hsplit_t(&view, listheader_height, &header, &view);
+	ui_draw_rect(&header, vec4(1,1,1,0.25f), CORNER_T, 5.0f); 
+	ui_do_label(&header, title, header.h*fontmod_height, 0);
+
+	// draw footers
+	ui_hsplit_b(&view, listheader_height, &view, &footer);
+	ui_draw_rect(&footer, vec4(1,1,1,0.25f), CORNER_B, 5.0f); 
+	ui_vsplit_l(&footer, 10.0f, 0, &footer);
+
+	// background
+	ui_draw_rect(&view, vec4(0,0,0,0.15f), 0, 0);
+
+	// prepare the scroll
+	ui_vsplit_r(&view, 15, &view, &scroll);
+
+	// setup the variables	
+	listbox_originalview = view;
+	listbox_selected_index = selected_index;
+	listbox_new_selected = selected_index;
+	listbox_itemindex = 0;
+	listbox_rowheight = row_height;
+	//int num_servers = client_serverbrowse_sorted_num();
+
+
+	// do the scrollbar
+	ui_hsplit_t(&view, listbox_rowheight, &row, 0);
+
+	int num = (int)(listbox_originalview.h/row.h);
+	static float scrollvalue = 0;
+	ui_hmargin(&scroll, 5.0f, &scroll);
+	scrollvalue = ui_do_scrollbar_v(id, &scroll, scrollvalue);
+
+	int start = (int)(num*scrollvalue);
+	if(start < 0)
+		start = 0;
+	
+	// the list
+	listbox_view = listbox_originalview;
+	ui_vmargin(&listbox_view, 5.0f, &listbox_view);
+	ui_clip_enable(&listbox_view);
+	listbox_view.y -= scrollvalue*num*row.h;	
+}
+
+MENUS::LISTBOXITEM MENUS::ui_do_listbox_nextitem(void *id)
+{
+	RECT row;
+	LISTBOXITEM item = {0};
+	ui_hsplit_t(&listbox_view, listbox_rowheight-2.0f, &row, &listbox_view);
+	ui_hsplit_t(&listbox_view, 2.0f, 0, &listbox_view);
+
+	RECT select_hit_box = row;
+
+	item.visible = 1;
+	if(listbox_selected_index == listbox_itemindex)
+		item.selected = 1;
+	
+	// make sure that only those in view can be selected
+	if(row.y+row.h > listbox_originalview.y)
+	{
+		
+		if(select_hit_box.y < listbox_originalview.y) // clip the selection
+		{
+			select_hit_box.h -= listbox_originalview.y-select_hit_box.y;
+			select_hit_box.y = listbox_originalview.y;
+		}
+		
+		if(ui_do_button(id, "", listbox_selected_index==listbox_itemindex, &select_hit_box, 0, 0))
+			listbox_new_selected = listbox_itemindex;
+	}
+	else
+		item.visible = 0;
+	
+	item.rect = row;
+	
+	// check if we need to do more
+	if(row.y > listbox_originalview.y+listbox_originalview.h)
+		item.visible = 0;
+
+	if(listbox_selected_index==listbox_itemindex)
+	{
+		//selected_index = i;
+		RECT r = row;
+		ui_margin(&r, 1.5f, &r);
+		ui_draw_rect(&r, vec4(1,1,1,0.5f), CORNER_ALL, 4.0f);
+	}	
+
+	listbox_itemindex++;
+
+	ui_vmargin(&item.rect, 5.0f, &item.rect);
+
+	return item;
+}
+
+int MENUS::ui_do_listbox_end()
+{
+	ui_clip_disable();
+	return listbox_new_selected;
+}
+
+/*
+void MENUS::demolist_listdir_callback(const char *name, int is_dir, void *user)
+{
+
+	(*count)++;
+	LISTBOXITEM item = ui_do_listbox_nextitem((void*)(10+*count));
+	if(item.visible)
+		ui_do_label(&item.rect, name, item.rect.h*fontmod_height, -1);
+}
+
+
+	DEMOITEM *demos;
+	int num_demos;
+	*/
+	
+void MENUS::demolist_count_callback(const char *name, int is_dir, void *user)
+{
+	if(is_dir || name[0] == '.')
+		return;
+	(*(int *)user)++;
+}
+
+struct FETCH_CALLBACKINFO
+{
+	MENUS *self;
+	const char *prefix;
+	int count;
+};
+
+void MENUS::demolist_fetch_callback(const char *name, int is_dir, void *user)
+{
+	if(is_dir || name[0] == '.')
+		return;
+			
+	FETCH_CALLBACKINFO *info = (FETCH_CALLBACKINFO *)user;
+	
+	if(info->count == info->self->num_demos)
+		return;
+	
+	str_format(info->self->demos[info->count].filename, sizeof(info->self->demos[info->count].filename), "%s/%s", info->prefix, name);
+	str_copy(info->self->demos[info->count].name, name, sizeof(info->self->demos[info->count].name));
+	info->count++;
+}
+
+
+void MENUS::demolist_populate()
+{
+	if(demos)
+		mem_free(demos);
+	demos = 0;
+	num_demos = 0;
+	
+	char buf[512];
+	str_format(buf, sizeof(buf), "%s/demos", client_user_directory());
+	fs_listdir(buf, demolist_count_callback, &num_demos);
+	fs_listdir("demos", demolist_count_callback, &num_demos);
+	
+	demos = (DEMOITEM *)mem_alloc(sizeof(DEMOITEM)*num_demos, 1);
+	mem_zero(demos, sizeof(DEMOITEM)*num_demos);
+
+	FETCH_CALLBACKINFO info = {this, buf, 0};
+	fs_listdir(buf, demolist_fetch_callback, &info);
+	info.prefix = "demos";
+	fs_listdir("demos", demolist_fetch_callback, &info);
+}
+
+
+void MENUS::render_demolist(RECT main_view)
+{
+	static int inited = 0;
+	if(!inited)
+		demolist_populate();
+	inited = 1;
+	
+	// render background
+	ui_draw_rect(&main_view, color_tabbar_active, CORNER_ALL, 10.0f);
+	ui_margin(&main_view, 10.0f, &main_view);
+	
+	RECT buttonbar;
+	ui_hsplit_b(&main_view, button_height+5.0f, &main_view, &buttonbar);
+	ui_hsplit_t(&buttonbar, 5.0f, 0, &buttonbar);
+	
+	static int selected_item = -1;
+	static int num_items = 0;
+	static int demolist_id = 0;
+	
+	ui_do_listbox_start(&demolist_id, &main_view, 17.0f, "Demos", num_items, selected_item);
+	for(int i = 0; i < num_demos; i++)
+	{
+		LISTBOXITEM item = ui_do_listbox_nextitem((void*)(10+i));
+		if(item.visible)
+			ui_do_label(&item.rect, demos[i].name, item.rect.h*fontmod_height, -1);
+	}
+	selected_item = ui_do_listbox_end();
+	
+	RECT button;
+	ui_vsplit_r(&buttonbar, 120.0f, &buttonbar, &button);
+	static int play_button = 0;
+	if(ui_do_button(&play_button, "Play", 0, &button, ui_draw_menu_button, 0))
+	{
+		if(selected_item >= 0 && selected_item < num_demos)
+			client_demoplayer_play(demos[selected_item].filename);
+	}
+}
+
diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp
index 33b4ffca..cfd92daa 100644
--- a/src/game/client/components/menus_ingame.cpp
+++ b/src/game/client/components/menus_ingame.cpp
@@ -283,7 +283,7 @@ void MENUS::render_servercontrol_map(RECT main_view)
 
 		ui_vmargin(&button, 5.0f, &button);
 		ui_do_label(&button, gameclient.maplist->name(i), 18.0f, -1);
-	}	
+	}
 }
 
 void MENUS::render_servercontrol_kick(RECT main_view)
diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp
index 6bf4221e..02977632 100644
--- a/src/game/client/components/players.cpp
+++ b/src/game/client/components/players.cpp
@@ -132,7 +132,7 @@ void PLAYERS::render_player(
 	gameclient.clients[info.cid].angle = angle;
 	vec2 direction = get_direction((int)(angle*256.0f));
 	
-	if(info.local && config.cl_predict)
+	if(info.local && config.cl_predict && client_state() != CLIENTSTATE_DEMOPLAYBACK)
 	{
 		if(!gameclient.snap.local_character || (gameclient.snap.local_character->health < 0) || (gameclient.snap.gameobj && gameclient.snap.gameobj->game_over))
 		{
@@ -149,6 +149,8 @@ void PLAYERS::render_player(
 	vec2 position = mix(vec2(prev.x, prev.y), vec2(player.x, player.y), intratick);
 	vec2 vel = mix(vec2(prev.vx/256.0f, prev.vy/256.0f), vec2(player.vx/256.0f, player.vy/256.0f), intratick);
 	
+	//dbg_msg("", "%d %d %d %d %f", prev.x, prev.y, player.x, player.y, intratick);
+	
 	gameclient.flow->add(position, vel*100.0f, 10.0f);
 	
 	render_info.got_airjump = player.jumped&2?0:1;