about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--data/languages/swedish.txt127
-rw-r--r--src/base/tl/algorithms.hpp135
-rw-r--r--src/base/tl/allocator.hpp15
-rw-r--r--src/base/tl/array.hpp341
-rw-r--r--src/base/tl/base.hpp15
-rw-r--r--src/base/tl/range.hpp230
-rw-r--r--src/base/tl/sorted_array.hpp31
-rw-r--r--src/base/tl/string.hpp68
-rw-r--r--src/game/client/components/menus_browser.cpp68
-rw-r--r--src/game/client/components/menus_settings.cpp4
-rw-r--r--src/game/client/gameclient.cpp69
-rw-r--r--src/game/localization.cpp10
-rw-r--r--src/game/localization.hpp20
13 files changed, 1075 insertions, 58 deletions
diff --git a/data/languages/swedish.txt b/data/languages/swedish.txt
index c67bfa8f..f96ea220 100644
--- a/data/languages/swedish.txt
+++ b/data/languages/swedish.txt
@@ -7,6 +7,9 @@ Fullscreen
 Loading
 == Laddar
 
+Ping
+== Ping
+
 Next Weapon:
 == Nästa vapen:
 
@@ -19,6 +22,9 @@ Use sounds
 Body
 == Kropp
 
+Ping:
+== Ping:
+
 Show name plates
 == Visa namnskyltar
 
@@ -37,14 +43,20 @@ Ok
 Call Vote
 == Starta omröstning
 
-LAN
-== LAN
+Favorite
+== Favorit
+
+Try again
+== Försök igen
+
+Refresh
+== Uppdatera
 
 FSAA samples
 == FSAA samplingar
 
-Hammer:
-== Hammare:
+No servers match your filter criteria
+== Inga servrar matchar dina filter kriterer
 
 Welcome to Teeworlds
 == Välkommen till Teeworlds
@@ -55,24 +67,42 @@ Voting
 Sat.
 == Mättnad
 
-Try again
-== Försök igen
+LAN
+== LAN
 
 Prev. Weapon:
 == Föregående vapen:
 
+Standard gametype
+== Standard speltyp
+
 Skins
 == Utseende
 
 Quit
 == Avsluta
 
-Controls
-== Kontroller
+Has people playing
+== Har folk som spelar
+
+Version:
+== Version:
 
 Dynamic Camera
 == Dynamisk kamera
 
+Remote Console:
+== Serverkonsol:
+
+Type
+== Typ
+
+Disconnected
+== Frånkopplad
+
+Feet
+== Fötter
+
 Show only supported
 == Visa endast upplösningar som stöds
 
@@ -88,12 +118,27 @@ Game
 Screenshot:
 == Skärmdump
 
+Teeworlds %s is out! Download it at www.teeworlds.com!
+== Teeworld %s är släppt! Ladda ner det på www.teeworlds.com!
+
 Grenade:
 == Granater:
 
+Connect
+== Anslut
+
+Info
+== Info
+
+No password
+== Inget lösenord
+
 Team Chat:
 == Lagchatt:
 
+Refreshing master servers
+== Uppdaterar huvudservrar
+
 Settings
 == Inställningar
 
@@ -103,6 +148,9 @@ Custom colors
 Emoticon:
 == Känsloikon
 
+Server details:
+== Server detaljer:
+
 Shotgun:
 == Hagelgevär:
 
@@ -133,6 +181,9 @@ High Detail
 No
 == Nej
 
+Reset filter
+== Återställ filter
+
 Mute when not active
 == Stäng av ljudet när spelet inte är aktivt
 
@@ -160,29 +211,47 @@ Rifle:
 Switch weapon on pickup
 == Byt vapen vid upplock
 
+Compatible version
+== Kompatibel version
+
+Standard map
+== Standard karta
+
 Fire:
 == Skjuta:
 
+Host address:
+== Serveraddress
+
 Weapon
 == Vapen
 
 Display Modes
 == Skärmlägen
 
+Filter
+== Filter
+
 Move Right:
 == Gå höger
 
+Controls
+== Kontroller
+
 Lht.
 == Ljusstyrka
 
+Server not full
+== Inte full server
+
 Chat:
 == Chatt:
 
-Remote Console:
-== Serverkonsol:
+Quick search:
+== Snabbsök:
 
-Feet
-== Fötter
+%d of %d servers, %d players
+== %d av %d servrar, %d spelare
 
 Demos
 == Demon
@@ -211,6 +280,9 @@ Current
 Hook:
 == Haken:
 
+Map
+== Karta
+
 Texture Compression
 == Texturkompression
 
@@ -229,12 +301,21 @@ Alpha
 Name:
 == Namn:
 
-Disconnected
-== Frånkopplad
+Game types:
+== Speltyper:
+
+Name
+== Namn
 
 You must restart the game for all settings to take effect.
 == Du måste starta om spelet för att ändringarna skall gälla.
 
+Maximum ping
+== Högsta ping
+
+Players
+== Spelare
+
 Downloading map
 == Laddar ner karta
 
@@ -244,12 +325,18 @@ Enter
 Jump:
 == Hoppa:
 
+Game Type:
+== Speltyp:
+
 Password:
 == Lösenord:
 
 Player
 == Spelare
 
+Hammer:
+== Hammare:
+
 Vote Yes:
 == Rösta ja:
 
@@ -261,8 +348,20 @@ Pistol:
 
 ##### needs translation ####
 
+Progression:
+== 
+
 The server is running a non-standard tuning on a pure game mode.
 == 
 
+N/A
+== 
+
+Current version: %s
+== 
+
+No servers found
+== 
+
 ##### old translations ####
 
diff --git a/src/base/tl/algorithms.hpp b/src/base/tl/algorithms.hpp
new file mode 100644
index 00000000..15e8e8a9
--- /dev/null
+++ b/src/base/tl/algorithms.hpp
@@ -0,0 +1,135 @@
+#ifndef TL_FILE_ALGORITHMS_HPP
+#define TL_FILE_ALGORITHMS_HPP
+
+#include "range.hpp"
+
+
+/*
+	insert 4
+	      v
+	1 2 3 4 5 6
+
+*/
+
+
+template<class R, class T>
+R partition_linear(R range, T value)
+{
+	concept_empty::check(range);
+	concept_forwarditeration::check(range);
+	concept_sorted::check(range);
+
+	for(; !range.empty(); range.pop_front())
+	{
+		if(!(range.front() < value))
+			return range;
+	}
+	return range;
+}
+
+
+template<class R, class T>
+R partition_binary(R range, T value)
+{
+	concept_empty::check(range);
+	concept_index::check(range);
+	concept_size::check(range);
+	concept_slice::check(range);
+	concept_sorted::check(range);
+	
+	if(range.empty())
+		return range;
+	if(range.back() < value)
+		return R();
+	
+	while(range.size() > 1)
+	{
+		unsigned pivot = (range.size()-1)/2;
+		if(range.index(pivot) < value)
+			range = range.slice(pivot+1, range.size()-1);
+		else
+			range = range.slice(0, pivot+1);
+	}
+	return range;
+}
+
+template<class R, class T>
+R find_linear(R range, T value)
+{
+	concept_empty::check(range);
+	concept_forwarditeration::check(range);
+	for(; !range.empty(); range.pop_front())
+		if(value < range.front())
+			break;
+	return range;
+}
+
+template<class R, class T>
+R find_binary(R range, T value)
+{
+	range = partition_linear(range, value);
+	if(range.empty()) return range;
+	if(range.front() == value) return range;
+	return R();
+}
+
+
+template<class R>
+void sort_bubble(R range)
+{
+	concept_empty::check(range);
+	concept_forwarditeration::check(range);
+	concept_backwarditeration::check(range);
+	
+	// slow bubblesort :/
+	for(; !range.empty(); range.pop_back())
+	{
+		R section = range;
+		typename R::type *prev = &section.front();
+		section.pop_front();
+		for(; !section.empty(); section.pop_front())
+		{
+			typename R::type *cur = &section.front();
+			if(*cur < *prev)
+				swap(*cur, *prev);
+			prev = cur;
+		}
+	}
+}
+
+/*
+template<class R>
+void sort_quick(R range)
+{
+	concept_index::check(range);
+}*/
+
+
+template<class R>
+void sort(R range)
+{
+	sort_bubble(range);
+}
+
+
+template<class R>
+bool sort_verify(R range)
+{
+	concept_empty::check(range);
+	concept_forwarditeration::check(range);
+	
+	typename R::type *prev = &range.front();
+	range.pop_front();
+	for(; !range.empty(); range.pop_front())
+	{
+		typename R::type *cur = &range.front();
+		
+		if(*cur < *prev)
+			return false;
+		prev = cur;
+	}
+	
+	return true;
+}
+
+#endif // TL_FILE_ALGORITHMS_HPP
diff --git a/src/base/tl/allocator.hpp b/src/base/tl/allocator.hpp
new file mode 100644
index 00000000..3baa1c19
--- /dev/null
+++ b/src/base/tl/allocator.hpp
@@ -0,0 +1,15 @@
+#ifndef TL_FILE_ALLOCATOR_HPP
+#define TL_FILE_ALLOCATOR_HPP
+
+template <class T>
+class allocator_default
+{
+public:
+	static T *alloc() { return new T; }
+	static void free(T *p) { delete p; }
+
+	static T *alloc_array(int size) { return new T [size]; }
+	static void free_array(T *p) { delete [] p; }
+};
+
+#endif // TL_FILE_ALLOCATOR_HPP
diff --git a/src/base/tl/array.hpp b/src/base/tl/array.hpp
new file mode 100644
index 00000000..7fa4ab1b
--- /dev/null
+++ b/src/base/tl/array.hpp
@@ -0,0 +1,341 @@
+#ifndef TL_FILE_ARRAY_HPP
+#define TL_FILE_ARRAY_HPP
+
+#include "range.hpp"
+#include "allocator.hpp"
+
+
+/*
+	Class: array
+		Normal dynamic array class
+	
+	Remarks:
+		- Grows 50% each time it needs to fit new items
+		- Use set_size() if you know how many elements 
+		- Use optimize() to reduce the needed space.
+*/
+template <class T, class ALLOCATOR = allocator_default<T> >
+class array : private ALLOCATOR
+{
+	void init()
+	{
+		list = 0x0;
+		clear();
+	}
+	
+public:
+	typedef plain_range<T> range;
+
+	/*
+		Function: array constructor
+	*/
+	array()
+	{
+		init();
+	}
+	
+	/*
+		Function: array copy constructor
+	*/
+	array(const array &other)
+	{
+		init();
+		set_size(other.size());
+		for(int i = 0; i < size(); i++)
+			(*this)[i] = other[i];
+	}
+
+
+	/*
+		Function: array destructor
+	*/
+	~array()
+	{
+		ALLOCATOR::free_array(list);
+		list = 0x0;
+	}
+
+
+	/*
+		Function: delete_all
+		
+		Remarks:
+			- Invalidates ranges
+	*/
+	void delete_all()
+	{
+		for(int i = 0; i < size(); i++)
+			delete list[i];
+		clear();
+	}
+
+
+	/*
+		Function: clear
+	
+		Remarks:
+			- Invalidates ranges
+	*/
+	void clear()
+	{
+		ALLOCATOR::free_array(list);
+		list_size = 1;
+		list = ALLOCATOR::alloc_array(list_size);
+		num_elements = 0;
+	}
+
+	/*
+		Function: size
+	*/
+	int size() const
+	{
+		return num_elements;
+	}
+
+	/*
+		Function: remove_index_fast
+
+		Remarks:
+			- Invalidates ranges
+	*/
+	void remove_index_fast(int index)
+	{
+		list[index] = list[num_elements-1];
+		set_size(size()-1);
+	}
+
+	/*
+		Function: remove_fast
+
+		Remarks:
+			- Invalidates ranges
+	*/
+	void remove_fast(const T& item)
+	{
+		for(int i = 0; i < size(); i++)
+			if(list[i] == item)
+			{
+				remove_index_fast(i);
+				return;
+			}
+	}
+
+	/*
+		Function: remove_index
+		
+		Remarks:
+			- Invalidates ranges
+	*/
+	void remove_index(int index)
+	{
+		for(int i = index+1; i < num_elements; i++)
+			list[i-1] = list[i];
+		
+		set_size(size()-1);
+	}
+
+	/*
+		Function: remove
+
+		Remarks:
+			- Invalidates ranges
+	*/
+	bool remove(const T& item)
+	{
+		for(int i = 0; i < size(); i++)
+			if(list[i] == item)
+			{
+				remove_index(i);
+				return true;
+			}
+		return false;
+	}
+
+	/*
+		Function: add
+			Adds an item to the array.
+		
+		Arguments:
+			item - Item to add.
+			
+		Remarks:
+			- Invalidates ranges
+			- See remarks about <array> how the array grows.
+	*/
+	int add(const T& item)
+	{
+		incsize();
+		set_size(size()+1);
+		list[num_elements-1] = item;
+		return num_elements-1;
+	}
+
+	/*
+		Function: insert
+			Inserts an item into the array at a specified location.
+			
+		Arguments:
+			item - Item to insert.		
+			r - Range where to insert the item
+
+		Remarks:
+			- Invalidates ranges
+			- See remarks about <array> how the array grows.
+	*/
+	int insert(const T& item, range r)
+	{
+		if(r.empty())
+			return add(item);
+			
+		int index = (int)(&r.front()-list);
+		incsize();
+		set_size(size()+1);
+		
+		for(int i = num_elements-1; i > index; i--)
+			list[i] = list[i-1];
+
+		list[index] = item;
+		
+		return num_elements-1;
+	}
+
+	/*
+		Function: operator[]
+	*/
+	T& operator[] (int index)
+	{
+		return list[index];
+	}
+
+	/*
+		Function: const operator[]
+	*/
+	const T& operator[] (int index) const
+	{
+		return list[index];
+	}
+
+	/*
+		Function: base_ptr
+	*/
+	T *base_ptr()
+	{
+		return list;
+	}
+
+	/*
+		Function: base_ptr
+	*/
+	const T *base_ptr() const
+	{
+		return list;
+	}
+
+	/*
+		Function: set_size
+			Resizes the array to the specified size.
+			
+		Arguments:
+			new_size - The new size for the array.
+	*/
+	void set_size(int new_size)
+	{
+		alloc(new_size);
+		num_elements = new_size;
+	}
+
+	/*
+		Function: hint_size
+			Allocates the number of elements wanted but
+			does not increase the list size.
+			
+		Arguments:
+			hint - Size to allocate.
+			
+		Remarks:
+			- If the hint is smaller then the number of elements, nothing will be done.
+			- Invalidates ranges
+	*/
+	void hint_size(int hint)
+	{
+		if(num_elements < hint)
+			alloc(hint);
+	}
+
+
+	/*
+		Function: optimize
+			Removes unnessasary data, returns how many bytes was earned.
+
+		Remarks:
+			- Invalidates ranges
+	*/
+	int optimize()
+	{
+		int before = memusage();
+		alloc(num_elements);
+		return before - memusage();
+	}
+
+	/*
+		Function: memusage
+			Returns how much memory this dynamic array is using
+	*/
+	int memusage()
+	{
+		return sizeof(array) + sizeof(T)*size;
+	}
+
+	/*
+		Function: operator=(array)
+
+		Remarks:
+			- Invalidates ranges
+	*/
+	array &operator = (const array &other)
+	{
+		set_size(other.size());
+		for(int i = 0; i < size(); i++)
+			(*this)[i] = other[i];
+		return *this;
+	}
+	
+	/*
+		Function: all
+			Returns a range that contains the whole array.
+	*/
+	range all() { return range(list, list+num_elements); }
+protected:
+
+	void incsize()
+	{
+		if(num_elements == list_size)
+		{
+			if(list_size < 2)
+				alloc(list_size+1);
+			else
+				alloc(list_size+list_size/2);
+		}		
+	}
+
+	void alloc(int new_len)
+	{
+		list_size = new_len;
+		T *new_list = ALLOCATOR::alloc_array(list_size);
+		
+		int end = num_elements < list_size ? num_elements : list_size;
+		for(int i = 0; i < end; i++)
+			new_list[i] = list[i];
+		
+		ALLOCATOR::free_array(list);
+
+		num_elements = num_elements < list_size ? num_elements : list_size;
+		list = new_list;
+	}
+
+	T *list;
+	int list_size;
+	int num_elements;
+};
+
+#endif // TL_FILE_ARRAY_HPP
diff --git a/src/base/tl/base.hpp b/src/base/tl/base.hpp
new file mode 100644
index 00000000..238cfaea
--- /dev/null
+++ b/src/base/tl/base.hpp
@@ -0,0 +1,15 @@
+
+#include <base/system.h>
+
+inline void assert(bool statement)
+{
+	dbg_assert(statement, "assert!");
+}
+
+template<class T>
+inline void swap(T &a, T &b)
+{
+	T c = b;
+	b = a;
+	a = c;
+}
diff --git a/src/base/tl/range.hpp b/src/base/tl/range.hpp
new file mode 100644
index 00000000..b55f235e
--- /dev/null
+++ b/src/base/tl/range.hpp
@@ -0,0 +1,230 @@
+#ifndef __RANGE_H
+#define __RANGE_H
+
+/*
+	Group: Range concepts
+*/
+
+/*
+	Concept: concept_empty
+		
+		template<class T>
+		struct range
+		{
+			bool empty() const;
+		};
+*/
+struct concept_empty
+{
+	template<typename T> static void check(T &t) { if(0) t.empty(); };
+};
+
+/*
+	Concept: concept_index
+		
+		template<class T>
+		struct range
+		{
+			T &index(size_t);
+		};
+*/
+struct concept_index
+{
+	template<typename T> static void check(T &t) { if(0) t.index(0); };
+};
+
+/*
+	Concept: concept_size
+		
+		template<class T>
+		struct range
+		{
+			size_t size();
+		};
+*/
+struct concept_size
+{
+	template<typename T> static void check(T &t) { if(0) t.size(); };
+};
+
+/*
+	Concept: concept_slice
+		
+		template<class T>
+		struct range
+		{
+			range slice(size_t start, size_t count);
+		};
+*/
+struct concept_slice
+{
+	template<typename T> static void check(T &t) { if(0) t.slice(0, 0); };
+};
+
+/*
+	Concept: concept_sorted
+		
+		template<class T>
+		struct range
+		{
+			void sorted();
+		};
+*/
+struct concept_sorted
+{
+	template<typename T> static void check(T &t) { if(0) t.sorted(); };
+};
+
+/*
+	Concept: concept_forwarditeration
+		Checks for the front and pop_front methods
+		
+		template<class T>
+		struct range
+		{
+			void pop_front();
+			T &front() const;
+		};		
+*/
+struct concept_forwarditeration
+{
+	template<typename T> static void check(T &t) { if(0) { t.front(); t.pop_front(); } };
+};
+
+/*
+	Concept: concept_backwarditeration
+		Checks for the back and pop_back methods
+		
+		template<class T>
+		struct range
+		{
+			void pop_back();
+			T &back() const;
+		};			
+*/
+struct concept_backwarditeration
+{
+	template<typename T> static void check(T &t) { if(0) { t.back(); t.pop_back(); } };
+};
+
+
+/*
+	Group: Range classes
+*/
+
+
+/*
+	Class: plain_range
+	
+	Concepts:
+		<concept_empty>
+		<concept_index>
+		<concept_slice>
+		<concept_forwardinteration>
+		<concept_backwardinteration>
+*/
+template<class T>
+class plain_range
+{
+public:
+	typedef T type;
+	plain_range()
+	{
+		begin = 0x0;
+		end = 0x0;
+	}
+
+	plain_range(const plain_range &r)
+	{
+		*this = r;
+	}
+		
+	plain_range(T *b, T *e)
+	{
+		begin = b;
+		end = e;
+	}
+	
+	bool empty() const { return begin >= end; }
+	void pop_front() { assert(!empty()); begin++; }
+	void pop_back() { assert(!empty()); end--; }
+	T& front() { assert(!empty()); return *begin; }
+	T& back() { assert(!empty()); return *(end-1); }
+	T& index(unsigned i) { assert(i >= 0 && i < (unsigned)(end-begin)); return begin[i]; }
+	unsigned size() const { return (unsigned)(end-begin); }
+	plain_range slice(unsigned startindex, unsigned endindex)
+	{
+		return plain_range(begin+startindex, begin+endindex);
+	}
+	
+protected:
+	T *begin;
+	T *end;
+};
+
+/*
+	Class: plain_range_sorted
+	
+	Concepts:
+		Same as <plain_range> but with these additions:
+		<concept_sorted>
+*/
+template<class T>
+class plain_range_sorted : public plain_range<T>
+{
+	typedef plain_range<T> parent;
+public:
+	/* sorted concept */
+	void sorted() const { }
+	
+	plain_range_sorted()
+	{}
+
+	plain_range_sorted(const plain_range_sorted &r)
+	{
+		*this = r;
+	}
+		
+	plain_range_sorted(T *b, T *e)
+	: parent(b, e)
+	{}
+	
+	plain_range_sorted slice(unsigned start, unsigned count)
+	{
+		return plain_range_sorted(parent::begin+start, parent::begin+start+count);
+	}
+};
+
+template<class R>
+class reverse_range
+{
+private:
+	reverse_range() {}
+public:
+	typedef typename R::type type;
+	
+	reverse_range(R r)
+	{
+		range = r;
+	}
+	
+	reverse_range(const reverse_range &other) { range = other.range; }
+	
+
+	bool empty() const { return range.empty(); }
+	void pop_front() { range.pop_back(); }
+	void pop_back() { range.pop_front(); }
+	type& front() { return range.back(); }
+	type& back() { return range.front(); }
+	
+	R range;
+};
+
+template<class R> reverse_range<R> reverse(R range) {
+   return reverse_range<R>(range);
+}
+template<class R> R reverse(reverse_range<R> range) {
+   return range.range;
+}
+
+#endif
diff --git a/src/base/tl/sorted_array.hpp b/src/base/tl/sorted_array.hpp
new file mode 100644
index 00000000..bb09aba9
--- /dev/null
+++ b/src/base/tl/sorted_array.hpp
@@ -0,0 +1,31 @@
+#ifndef TL_FILE_SORTED_ARRAY_HPP
+#define TL_FILE_SORTED_ARRAY_HPP
+
+#include "algorithms.hpp"
+#include "array.hpp"
+
+template <class T, class ALLOCATOR = allocator_default<T> >
+class sorted_array : public array<T, ALLOCATOR>
+{
+	typedef array<T, ALLOCATOR> parent;
+	
+	// insert and size is not allowed
+	int insert(const T& item, typename parent::range r) { dbg_break(); return 0; }
+	int set_size(int new_size) { dbg_break(); return 0; }
+		
+public:
+	typedef plain_range_sorted<T> range;
+
+	int add(const T& item)
+	{
+		return parent::insert(item, partition_binary(all(), item));
+	}
+
+	/*
+		Function: all
+			Returns a sorted range that contains the whole array.
+	*/	
+	range all() { return range(parent::list, parent::list+parent::num_elements); }
+};
+
+#endif // TL_FILE_SORTED_ARRAY_HPP
diff --git a/src/base/tl/string.hpp b/src/base/tl/string.hpp
new file mode 100644
index 00000000..2b164091
--- /dev/null
+++ b/src/base/tl/string.hpp
@@ -0,0 +1,68 @@
+#ifndef TL_FILE_STRING_HPP
+#define TL_FILE_STRING_HPP
+
+#include "base.hpp"
+#include "allocator.hpp"
+
+template<class ALLOCATOR >
+class string_base : private ALLOCATOR
+{
+	char *str;
+	int length;
+	
+	void reset()
+	{
+		str = 0; length = 0;
+	}
+	
+	void free()
+	{
+		ALLOCATOR::free_array(str);
+		reset();
+	}	
+	
+	void copy(const char *other_str, int other_length)
+	{
+		length = other_length;
+		str = ALLOCATOR::alloc_array(length+1);
+		mem_copy(str, other_str, length+1);
+	}
+		
+	void copy(const string_base &other)
+	{
+		if(!other.str)
+			return;
+		copy(other.str, other.length);
+	}
+	
+public:
+	string_base() { reset(); }
+	string_base(const char *other_str) { copy(other_str, str_length(other_str)); }
+	string_base(const string_base &other) { reset(); copy(other); }
+	~string_base() { free(); }
+	
+	string_base &operator = (const char *other)
+	{
+		free();
+		if(other)
+			copy(other, str_length(other));
+		return *this;
+	}
+	
+	string_base &operator = (const string_base &other)
+	{
+		free();
+		copy(other);
+		return *this;
+	}
+		
+	bool operator < (const char *other_str) const { return str_comp(str, other_str) < 0; }
+	operator const char *() const { return str; }
+	
+	const char *cstr() const { return str; }
+};
+
+/* normal allocated string */
+typedef string_base<allocator_default<char> > string;
+
+#endif // TL_FILE_STRING_HPP
diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp
index 3d58af93..d86a727c 100644
--- a/src/game/client/components/menus_browser.cpp
+++ b/src/game/client/components/menus_browser.cpp
@@ -58,12 +58,12 @@ void MENUS::render_serverbrowser_serverlist(RECT view)
 		{COL_FLAG_LOCK,	-1,						" ",		-1, 14.0f, 0, {0}, {0}},
 		{COL_FLAG_PURE,	-1,						" ",		-1, 14.0f, 0, {0}, {0}},
 		{COL_FLAG_FAV,	-1,						" ",		-1, 14.0f, 0, {0}, {0}},
-		{COL_NAME,		BROWSESORT_NAME,		"Name",		0, 300.0f, 0, {0}, {0}},
-		{COL_GAMETYPE,	BROWSESORT_GAMETYPE,	"Type",		1, 50.0f, 0, {0}, {0}},
-		{COL_MAP,		BROWSESORT_MAP,			"Map", 		1, 100.0f, 0, {0}, {0}},
-		{COL_PLAYERS,	BROWSESORT_NUMPLAYERS,	"Players",	1, 60.0f, 0, {0}, {0}},
+		{COL_NAME,		BROWSESORT_NAME,		localize("Name"),		0, 300.0f, 0, {0}, {0}},
+		{COL_GAMETYPE,	BROWSESORT_GAMETYPE,	localize("Type"),		1, 50.0f, 0, {0}, {0}},
+		{COL_MAP,		BROWSESORT_MAP,			localize("Map"), 		1, 100.0f, 0, {0}, {0}},
+		{COL_PLAYERS,	BROWSESORT_NUMPLAYERS,	localize("Players"),	1, 60.0f, 0, {0}, {0}},
 		{-1,			-1,						" ",		1, 10.0f, 0, {0}, {0}},
-		{COL_PING,		BROWSESORT_PING,		"Ping",		1, 40.0f, FIXED, {0}, {0}},
+		{COL_PING,		BROWSESORT_PING,		localize("Ping"),		1, 40.0f, FIXED, {0}, {0}},
 	};
 	
 	int num_cols = sizeof(cols)/sizeof(column);
@@ -128,11 +128,11 @@ void MENUS::render_serverbrowser_serverlist(RECT view)
 		msgbox.y += view.h/3;
 		
 		if(active_page == PAGE_INTERNET && client_serverbrowse_refreshingmasters())
-			ui_do_label(&msgbox, "Refreshing master servers", 16.0f, 0);
+			ui_do_label(&msgbox, localize("Refreshing master servers"), 16.0f, 0);
 		else if(!client_serverbrowse_num())
-			ui_do_label(&msgbox, "No servers found", 16.0f, 0);
+			ui_do_label(&msgbox, localize("No servers found"), 16.0f, 0);
 		else if(client_serverbrowse_num() && !num_servers)
-			ui_do_label(&msgbox, "No servers match your filter criteria", 16.0f, 0);
+			ui_do_label(&msgbox, localize("No servers match your filter criteria"), 16.0f, 0);
 	}
 
 	int num = (int)(view.h/cols[0].rect.h);
@@ -330,13 +330,13 @@ void MENUS::render_serverbrowser_serverlist(RECT view)
 	// render quick search
 	RECT quicksearch;
 	ui_vsplit_l(&status, 250.0f, &quicksearch, &status);
-	ui_do_label(&quicksearch, "Quick search: ", 14.0f, -1);
-	ui_vsplit_l(&quicksearch, gfx_text_width(0, 14.0f, "Quick search: ", -1), 0, &quicksearch);
+	ui_do_label(&quicksearch, localize("Quick search:"), 14.0f, -1);
+	ui_vsplit_l(&quicksearch, gfx_text_width(0, 14.0f, localize("Quick search:"), -1), 0, &quicksearch);
 	ui_do_edit_box(&config.b_filter_string, &quicksearch, config.b_filter_string, sizeof(config.b_filter_string), 14.0f);
 	
 	// render status
 	char buf[128];
-	str_format(buf, sizeof(buf), "%d of %d servers, %d players", client_serverbrowse_sorted_num(), client_serverbrowse_num(), num_players);
+	str_format(buf, sizeof(buf), localize("%d of %d servers, %d players"), client_serverbrowse_sorted_num(), client_serverbrowse_num(), num_players);
 	ui_vsplit_r(&status, gfx_text_width(0, 14.0f, buf, -1), 0, &status);
 	ui_do_label(&status, buf, 14.0f, -1);
 }
@@ -353,32 +353,32 @@ void MENUS::render_serverbrowser_filters(RECT view)
 
 	// render filters
 	ui_hsplit_t(&view, 20.0f, &button, &view);
-	if (ui_do_button(&config.b_filter_empty, "Has people playing", config.b_filter_empty, &button, ui_draw_checkbox, 0))
+	if (ui_do_button(&config.b_filter_empty, localize("Has people playing"), config.b_filter_empty, &button, ui_draw_checkbox, 0))
 		config.b_filter_empty ^= 1;
 
 	ui_hsplit_t(&view, 20.0f, &button, &view);
-	if (ui_do_button(&config.b_filter_full, "Server not full", config.b_filter_full, &button, ui_draw_checkbox, 0))
+	if (ui_do_button(&config.b_filter_full, localize("Server not full"), config.b_filter_full, &button, ui_draw_checkbox, 0))
 		config.b_filter_full ^= 1;
 
 	ui_hsplit_t(&view, 20.0f, &button, &view);
-	if (ui_do_button(&config.b_filter_pw, "No password", config.b_filter_pw, &button, ui_draw_checkbox, 0))
+	if (ui_do_button(&config.b_filter_pw, localize("No password"), config.b_filter_pw, &button, ui_draw_checkbox, 0))
 		config.b_filter_pw ^= 1;
 
 	ui_hsplit_t(&view, 20.0f, &button, &view);
-	if (ui_do_button((char *)&config.b_filter_compatversion, "Compatible Version", config.b_filter_compatversion, &button, ui_draw_checkbox, 0))
+	if (ui_do_button((char *)&config.b_filter_compatversion, localize("Compatible version"), config.b_filter_compatversion, &button, ui_draw_checkbox, 0))
 		config.b_filter_compatversion ^= 1;
 	
 	ui_hsplit_t(&view, 20.0f, &button, &view);
-	if (ui_do_button((char *)&config.b_filter_pure, "Standard gametype", config.b_filter_pure, &button, ui_draw_checkbox, 0))
+	if (ui_do_button((char *)&config.b_filter_pure, localize("Standard gametype"), config.b_filter_pure, &button, ui_draw_checkbox, 0))
 		config.b_filter_pure ^= 1;
 
 	ui_hsplit_t(&view, 20.0f, &button, &view);
 	/*ui_vsplit_l(&button, 20.0f, 0, &button);*/
-	if (ui_do_button((char *)&config.b_filter_pure_map, "Standard map", config.b_filter_pure_map, &button, ui_draw_checkbox, 0))
+	if (ui_do_button((char *)&config.b_filter_pure_map, localize("Standard map"), config.b_filter_pure_map, &button, ui_draw_checkbox, 0))
 		config.b_filter_pure_map ^= 1;
 		
 	ui_hsplit_t(&view, 20.0f, &button, &view);
-	ui_do_label(&button, "Game types: ", 14.0f, -1);
+	ui_do_label(&button, localize("Game types:"), 14.0f, -1);
 	ui_vsplit_l(&button, 95.0f, 0, &button);
 	ui_margin(&button, 1.0f, &button);
 	ui_do_edit_box(&config.b_filter_gametype, &button, config.b_filter_gametype, sizeof(config.b_filter_gametype), 14.0f);
@@ -394,12 +394,12 @@ void MENUS::render_serverbrowser_filters(RECT view)
 		ui_do_edit_box(&config.b_filter_ping, &editbox, buf, sizeof(buf), 14.0f);
 		config.b_filter_ping = atoi(buf);
 		
-		ui_do_label(&button, "Maximum ping", 14.0f, -1);
+		ui_do_label(&button, localize("Maximum ping"), 14.0f, -1);
 	}
 	
 	ui_hsplit_b(&view, button_height, &view, &button);
 	static int clear_button = 0;
-	if(ui_do_button(&clear_button, "Reset Filter", 0, &button, ui_draw_menu_button, 0))
+	if(ui_do_button(&clear_button, localize("Reset filter"), 0, &button, ui_draw_menu_button, 0))
 	{
 		config.b_filter_full = 0;
 		config.b_filter_empty = 0;
@@ -431,7 +431,7 @@ void MENUS::render_serverbrowser_serverdetail(RECT view)
 	ui_draw_rect(&server_header, vec4(1,1,1,0.25f), CORNER_T, 4.0f);
 	ui_draw_rect(&server_details, vec4(0,0,0,0.15f), CORNER_B, 4.0f);
 	ui_vsplit_l(&server_header, 8.0f, 0x0, &server_header);
-	ui_do_label(&server_header, "Server Details: ", font_size+2.0f, -1);
+	ui_do_label(&server_header, localize("Server details:"), font_size+2.0f, -1);
 
 	ui_vsplit_l(&server_details, 5.0f, 0x0, &server_details);
 
@@ -440,7 +440,11 @@ void MENUS::render_serverbrowser_serverdetail(RECT view)
 	if (selected_server)
 	{
 		RECT row;
-		static const char *labels[] = { "Version:", "Game Type:", "Progression:", "Ping:" };
+		static LOC_CONSTSTRING labels[] = {
+			localize("Version:"),
+			localize("Game Type:"),
+			localize("Progression:"),
+			localize("Ping:")};
 
 		RECT left_column;
 		RECT right_column;
@@ -450,7 +454,7 @@ void MENUS::render_serverbrowser_serverdetail(RECT view)
 			RECT button;
 			ui_hsplit_b(&server_details, 20.0f, &server_details, &button);
 			static int add_fav_button = 0;
-			if (ui_do_button(&add_fav_button, "Favorite", selected_server->favorite, &button, ui_draw_checkbox, 0))
+			if (ui_do_button(&add_fav_button, localize("Favorite"), selected_server->favorite, &button, ui_draw_checkbox, 0))
 			{
 				if(selected_server->favorite)
 					client_serverbrowse_removefavorite(selected_server->netaddr);
@@ -478,7 +482,7 @@ void MENUS::render_serverbrowser_serverdetail(RECT view)
 		char temp[16];
 
 		if(selected_server->progression < 0)
-			str_format(temp, sizeof(temp), "N/A");
+			str_format(temp, sizeof(temp), localize("N/A"));
 		else
 			str_format(temp, sizeof(temp), "%d%%", selected_server->progression);
 		ui_hsplit_t(&right_column, 15.0f, &row, &right_column);
@@ -497,7 +501,7 @@ void MENUS::render_serverbrowser_serverdetail(RECT view)
 	ui_draw_rect(&server_header, vec4(1,1,1,0.25f), CORNER_T, 4.0f);
 	ui_draw_rect(&server_scoreboard, vec4(0,0,0,0.15f), CORNER_B, 4.0f);
 	ui_vsplit_l(&server_header, 8.0f, 0x0, &server_header);
-	ui_do_label(&server_header, "Scoreboard: ", font_size+2.0f, -1);
+	ui_do_label(&server_header, localize("Scoreboard:"), font_size+2.0f, -1);
 
 	ui_vsplit_l(&server_scoreboard, 5.0f, 0x0, &server_scoreboard);
 
@@ -588,11 +592,11 @@ void MENUS::render_serverbrowser(RECT main_view)
 		ui_vsplit_l(&tabbutton1, 5.0f, 0, &tabbutton1);
 		
 		static int filters_tab = 0;
-		if (ui_do_button(&filters_tab, "Filter", toolbox_page==0, &tabbutton0, ui_draw_menu_tab_button, 0))
+		if (ui_do_button(&filters_tab, localize("Filter"), toolbox_page==0, &tabbutton0, ui_draw_menu_tab_button, 0))
 			toolbox_page = 0;
 			
 		static int info_tab = 0;
-		if (ui_do_button(&info_tab, "Info", toolbox_page==1, &tabbutton1, ui_draw_menu_tab_button, 0))
+		if (ui_do_button(&info_tab, localize("Info"), toolbox_page==1, &tabbutton1, ui_draw_menu_tab_button, 0))
 			toolbox_page = 1;
 	}
 
@@ -613,7 +617,7 @@ void MENUS::render_serverbrowser(RECT main_view)
 		ui_vsplit_r(&status_toolbar, 100.0f, &status_toolbar, &button);
 		ui_vmargin(&button, 2.0f, &button);
 		static int refresh_button = 0;
-		if(ui_do_button(&refresh_button, "Refresh", 0, &button, ui_draw_menu_button, 0))
+		if(ui_do_button(&refresh_button, localize("Refresh"), 0, &button, ui_draw_menu_button, 0))
 		{
 			if(config.ui_page == PAGE_INTERNET)
 				client_serverbrowse_refresh(BROWSETYPE_INTERNET);
@@ -625,9 +629,9 @@ void MENUS::render_serverbrowser(RECT main_view)
 		
 		char buf[512];
 		if(strcmp(client_latestversion(), "0") != 0)
-			str_format(buf, sizeof(buf), "Teeworlds %s is out! Download it at www.teeworlds.com!\nCurrent version: %s", client_latestversion(), GAME_VERSION);
+			str_format(buf, sizeof(buf), localize("Teeworlds %s is out! Download it at www.teeworlds.com!"), client_latestversion());
 		else
-			str_format(buf, sizeof(buf), "Current version: %s", GAME_VERSION);
+			str_format(buf, sizeof(buf), localize("Current version: %s"), GAME_VERSION);
 		ui_do_label(&status_toolbar, buf, 14.0f, -1);
 	}
 	
@@ -643,7 +647,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) || enter_pressed)
+		if(ui_do_button(&join_button, localize("Connect"), 0, &button, ui_draw_menu_button, 0) || enter_pressed)
 		{
 			client_connect(config.ui_server_address);
 			enter_pressed = false;
@@ -653,6 +657,6 @@ void MENUS::render_serverbrowser(RECT main_view)
 		ui_hsplit_b(&button_box, 20.0f, &button_box, &button);
 		ui_do_edit_box(&config.ui_server_address, &button, config.ui_server_address, sizeof(config.ui_server_address), 14.0f);
 		ui_hsplit_b(&button_box, 20.0f, &button_box, &button);
-		ui_do_label(&button, "Host address:", 14.0f, -1);
+		ui_do_label(&button, localize("Host address:"), 14.0f, -1);
 	}
 }
diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp
index 58678b80..60d8d7fc 100644
--- a/src/game/client/components/menus_settings.cpp
+++ b/src/game/client/components/menus_settings.cpp
@@ -254,7 +254,7 @@ typedef struct
 	int keyid;
 } KEYINFO;
 
-KEYINFO keys[] = 
+static KEYINFO keys[] = 
 {
 	// we need to do localize so the scripts can pickup the string
 	{ localize("Move Left:"), "+left", 0},
@@ -286,7 +286,7 @@ void MENUS::ui_do_getbuttons(int start, int stop, RECT view)
 {
 	for (int i = start; i < stop; i++)
 	{
-		KEYINFO key = keys[i];
+		KEYINFO &key = keys[i];
 		RECT button, label;
 		ui_hsplit_t(&view, 20.0f, &button, &view);
 		ui_vsplit_l(&button, 130.0f, &label, &button);
diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp
index 834aa29b..752af7cd 100644
--- a/src/game/client/gameclient.cpp
+++ b/src/game/client/gameclient.cpp
@@ -98,8 +98,77 @@ static void con_serverdummy(void *result, void *user_data)
 	dbg_msg("client", "this command is not available on the client");
 }
 
+#include <base/tl/sorted_array.hpp>
+
 void GAMECLIENT::on_console_init()
 {
+	if(0)
+	{
+		int ints[4] = {0,1,2,3};
+		for(int s = 1; s < 4; s++)
+		{
+			//s = 2;
+			plain_range_sorted<int> test_sorted_range(ints, ints+s);
+			plain_range_sorted<int> res1, res2;
+			
+			//res2 = partition_binary(test_sorted_range, 1);
+		
+			//for(int i = 0; i < 4; i++)
+			//	dbg_assert(partition_linear(test_sorted_range, i).front() == i, "partition linear failed");
+				
+				
+			dbg_msg("", "size %d", s);
+
+			for(int i = -1; i < 5; i++)
+			{
+				res1 = partition_linear(test_sorted_range, i);
+				dbg_msg("", "\tlin %d == %d", i, res1.empty()?-1:res1.front());
+
+				res2 = partition_binary(test_sorted_range, i);
+				dbg_msg("", "\tbin %d == %d", i, res2.empty()?-1:res2.front());
+				//dbg_assert(partition_binary(plain_range_sorted<int>(ints, ints+6), i).front() == i+1, "partition binary failed");
+			}
+		} //*/
+
+		sorted_array<int> test;
+		test.add(4);
+		test.add(1);
+		
+		for(int i = 0; i < 100; i++)
+		{
+			int this_add = rand();
+			test.add(this_add);
+			if(!sort_verify(test.all()))
+			{
+				dbg_msg("", "error inserting %d", this_add);
+				for(sorted_array<int>::range r = test.all(); !r.empty(); r.pop_front())
+					dbg_msg("", "%d", r.front());
+				exit(-1);
+			}
+		}/*
+			
+		
+		test.add(1);
+		test.add(4);
+		test.add(3);
+		test.add(4);
+		test.add(3);
+		test.add(2);
+		//test.insert(1, 1);
+		for(sorted_array<int>::range r = test.all(); !r.empty(); r.pop_front())
+			dbg_msg("", "%d", r.front());
+			*/
+			
+		sort_verify(test.all());
+		/*
+		for(int i = 0; i < 15; i++)
+		{
+			dbg_msg("", "found %d == %d", i, !find_binary(test.all(), i).empty());
+		}*/
+		
+		exit(-1);
+	}
+	
 	// setup pointers
 	binds = &::binds;
 	console = &::console;
diff --git a/src/game/localization.cpp b/src/game/localization.cpp
index 58ec539c..110a0f5e 100644
--- a/src/game/localization.cpp
+++ b/src/game/localization.cpp
@@ -1,5 +1,6 @@
 
 #include "localization.hpp"
+#include <base/tl/algorithms.hpp>
 
 extern "C" {
 #include <engine/e_linereader.h>
@@ -16,7 +17,6 @@ static unsigned str_hash(const char *str)
 const char *localize(const char *str)
 {
 	const char *new_str = localization.find_string(str_hash(str));
-	//dbg_msg("", "no localization for '%s'", str);
 	return new_str ? new_str : str;
 }
 
@@ -86,15 +86,19 @@ bool LOCALIZATIONDATABASE::load(const char *filename)
 		replacement += 3;
 		localization.add_string(line, replacement);
 	}
-		
+	
 	current_version++;
 	return true;
 }
 
 const char *LOCALIZATIONDATABASE::find_string(unsigned hash)
 {
-	array<STRING>::range r = ::find(strings.all(), hash);
+	STRING s;
+	s.hash = hash;
+	sorted_array<STRING>::range r = ::find_binary(strings.all(), s);
 	if(r.empty())
 		return 0;
 	return r.front().replacement;
 }
+
+LOCALIZATIONDATABASE localization;
diff --git a/src/game/localization.hpp b/src/game/localization.hpp
index de6849d4..3f79d687 100644
--- a/src/game/localization.hpp
+++ b/src/game/localization.hpp
@@ -1,4 +1,5 @@
-#include <base/tl/array.hpp>
+#include <base/tl/string.hpp>
+#include <base/tl/sorted_array.hpp>
 
 class LOCALIZATIONDATABASE
 {
@@ -6,13 +7,16 @@ class LOCALIZATIONDATABASE
 	{
 	public:
 		unsigned hash;
-		string replacement;
-		
-		bool operator ==(unsigned h) const { return hash == h; }
 		
+		// TODO: do this as an const char * and put everything on a incremental heap
+		string replacement;
+
+		bool operator <(const STRING &other) const { return hash < other.hash; }
+		bool operator <=(const STRING &other) const { return hash <= other.hash; }
+		bool operator ==(const STRING &other) const { return hash == other.hash; }
 	};
 
-	array<STRING> strings;
+	sorted_array<STRING> strings;
 	int current_version;
 	
 public:
@@ -26,8 +30,7 @@ public:
 	const char *find_string(unsigned hash);
 };
 
-static LOCALIZATIONDATABASE localization;
-
+extern LOCALIZATIONDATABASE localization;
 
 class LOC_CONSTSTRING
 {
@@ -46,3 +49,6 @@ public:
 		return current_str;
 	}
 };
+
+
+extern const char *localize(const char *str);