diff --git a/src/base/tl/string.h b/src/base/tl/string.h
index ced25452..dad73fa3 100644
--- a/src/base/tl/string.h
+++ b/src/base/tl/string.h
@@ -40,7 +40,7 @@ class string_base : private ALLOCATOR
public:
string_base() { reset(); }
string_base(const char *other_str) { copy(other_str, str_length(other_str)); }
- string_base(const char *other_str, int length) { copy(other_str, length); }
+ string_base(const char *other_str, int length) { copy(other_str, length); str[length] = 0; }
string_base(const string_base &other) { reset(); copy(other); }
~string_base() { free(); }
diff --git a/src/base/vmath.h b/src/base/vmath.h
index 0d7a54ad..cbdf1644 100644
--- a/src/base/vmath.h
+++ b/src/base/vmath.h
@@ -16,7 +16,7 @@ class vector2_base
union { T y,v; };
vector2_base() {}
- vector2_base(float nx, float ny)
+ vector2_base(T nx, T ny)
{
x = nx;
y = ny;
diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp
index 65b6abb1..c76c516f 100644
--- a/src/game/client/components/menus.cpp
+++ b/src/game/client/components/menus.cpp
@@ -564,7 +564,15 @@ int CMenus::RenderMenubar(CUIRect r)
NewPage = PAGE_FAVORITES;
}
- Box.VSplitLeft(4.0f*5, 0, &Box);
+ Box.VSplitLeft(10.0f, 0, &Box);
+ Box.VSplitLeft(100.0f, &Button, &Box);
+ static int s_ServerButton=0;
+ if(DoButton_MenuTab(&s_ServerButton, Localize("Server"), m_ActivePage==PAGE_SERVER, &Button, CUI::CORNER_T))
+ {
+ NewPage = PAGE_SERVER;
+ }
+
+ Box.VSplitLeft(10.0f, 0, &Box);
Box.VSplitLeft(100.0f, &Button, &Box);
static int s_DemosButton=0;
if(DoButton_MenuTab(&s_DemosButton, Localize("Demos"), m_ActivePage==PAGE_DEMOS, &Button, CUI::CORNER_T))
@@ -840,6 +848,8 @@ int CMenus::Render()
RenderServerbrowser(MainView);
else if(g_Config.m_UiPage == PAGE_LAN)
RenderServerbrowser(MainView);
+ else if(g_Config.m_UiPage == PAGE_SERVER)
+ ServerCreatorProcess(MainView);
else if(g_Config.m_UiPage == PAGE_DEMOS)
RenderDemoList(MainView);
else if(g_Config.m_UiPage == PAGE_FAVORITES)
diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h
index 8e4b1b83..f53af242 100644
--- a/src/game/client/components/menus.h
+++ b/src/game/client/components/menus.h
@@ -120,6 +120,7 @@ class CMenus : public CComponent
PAGE_INTERNET,
PAGE_LAN,
PAGE_FAVORITES,
+ PAGE_SERVER,
PAGE_DEMOS,
PAGE_SETTINGS,
PAGE_SYSTEM,
@@ -244,6 +245,10 @@ class CMenus : public CComponent
void RenderDemoPlayer(CUIRect MainView);
void RenderDemoList(CUIRect MainView);
+ // found in menus_server.cpp
+ void ServerCreatorInit();
+ void ServerCreatorProcess(CUIRect MainView);
+
// found in menus_ingame.cpp
void RenderGame(CUIRect MainView);
void RenderPlayers(CUIRect MainView);
diff --git a/src/game/client/components/menus_server.cpp b/src/game/client/components/menus_server.cpp
new file mode 100644
index 00000000..cbad30a3
--- /dev/null
+++ b/src/game/client/components/menus_server.cpp
@@ -0,0 +1,238 @@
+
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+
+#include "menus.h"
+
+#if defined(WIN32)
+#include
+#include
+#include
+#include
+
+static HANDLE serverProcess = -1;
+#else
+#include
+#include
+#include
+#include
+#endif
+
+static sorted_array s_maplist;
+static bool atexitRegistered = false;
+
+static int MapScan(const char *pName, int IsDir, int DirType, void *pUser)
+{
+ sorted_array *maplist = (sorted_array *)pUser;
+ int l = str_length(pName);
+ if(l < 4 || IsDir || str_comp(pName+l-4, ".map") != 0)
+ return 0;
+ maplist->add(string(pName, l - 4));
+ return 0;
+}
+
+void CMenus::ServerCreatorInit()
+{
+ if( s_maplist.size() == 0 )
+ {
+ Storage()->ListDirectory(IStorage::TYPE_ALL, "maps", MapScan, &s_maplist);
+ }
+}
+
+static void StopServer()
+{
+#if defined(WIN32)
+ if( serverProcess != -1 )
+ TerminateProcess(serverProcess, 0);
+ serverProcess = -1;
+#elif defined(__ANDROID__)
+ system("$SECURE_STORAGE_DIR/busybox killall ninslash_srv");
+#else
+ system("killall ninslash_srv ninslash_srv_d");
+#endif
+}
+
+static void StartServer(const char *type, const char *map, int bots)
+{
+ char aBuf[4096];
+ str_format(aBuf, sizeof(aBuf),
+ "sv_port 8303\n"
+ "sv_name \"%s\"\n"
+ "sv_gametype %s\n"
+ "sv_map %s\n"
+ "sv_maprotation %s\n"
+ "sv_preferredteamsize %d\n"
+ "sv_scorelimit 0\n"
+ "sv_randomweapons 1\n"
+ "sv_vanillapickups 1\n"
+ "sv_weapondrops 1\n",
+ type, type, map, map, bots);
+
+ FILE *ff = fopen("server.cfg", "wb");
+ if( !ff )
+ return;
+ fwrite(aBuf, str_length(aBuf), 1, ff);
+ fclose(ff);
+
+#if defined(WIN32)
+ serverProcess = (HANDLE) _spawnl(_P_NOWAIT, "ninslash_srv.exe", "ninslash_srv.exe", "-f", "server.cfg", NULL);
+#elif defined(__ANDROID__)
+ system("$SECURE_STORAGE_DIR/ninslash_srv -f server.cfg >/dev/null 2>&1 &");
+#else
+ system("./ninslash_srv_d -f server.cfg || ./ninslash_srv -f server.cfg &");
+#endif
+
+ if( !atexitRegistered )
+ atexit(&StopServer);
+ atexitRegistered = true;
+}
+
+static bool ServerStatus()
+{
+#if defined(WIN32)
+ return serverProcess != -1;
+#elif defined(__ANDROID__)
+ int status = system("$SECURE_STORAGE_DIR/busybox sh -c 'ps | grep ninslash_srv'");
+ return WEXITSTATUS(status) == 0;
+#else
+ int status = system("ps | grep ninslash_srv");
+ return WEXITSTATUS(status) == 0;
+#endif
+}
+
+void CMenus::ServerCreatorProcess(CUIRect MainView)
+{
+ static int s_map = 0;
+ static int s_bots = 5;
+
+ static int64 LastUpdateTime = 0;
+ static bool ServerRunning = false;
+ static bool ServerStarting = false;
+
+ bool ServerStarted = false;
+
+ ServerCreatorInit();
+
+ // background
+ RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_ALL, 20.0f);
+ MainView.Margin(20.0f, &MainView);
+
+ MainView.HSplitTop(10, 0, &MainView);
+ CUIRect MsgBox = MainView;
+ UI()->DoLabelScaled(&MsgBox, Localize("Local server"), 20.0f, 0);
+
+ if (time_get() / time_freq() > LastUpdateTime + 2)
+ {
+ LastUpdateTime = time_get() / time_freq();
+ ServerRunning = ServerStatus();
+ if (ServerRunning && ServerStarting)
+ ServerStarted = true;
+ ServerStarting = false;
+ }
+
+ MainView.HSplitTop(30, 0, &MainView);
+ MsgBox = MainView;
+ UI()->DoLabelScaled(&MsgBox, ServerStarting ? Localize("Server is starting") :
+ ServerRunning ? Localize("Server is running") :
+ Localize("Server stopped"), 20.0f, 0);
+
+ MainView.HSplitTop(50, 0, &MainView);
+
+ CUIRect Button;
+
+ MainView.VSplitLeft(50, 0, &Button);
+ Button.h = 30;
+ Button.w = 200;
+ static int s_StopServerButton = 0;
+ if( ServerRunning && DoButton_Menu(&s_StopServerButton, Localize("Stop server"), 0, &Button))
+ {
+ StopServer();
+ LastUpdateTime = time_get() / time_freq() - 2;
+ }
+
+ static int s_StartDmServerButton = 0;
+ if( !ServerRunning && !ServerStarting && DoButton_Menu(&s_StartDmServerButton, Localize("Start DM server"), 0, &Button))
+ {
+ StartServer("dm", s_maplist[s_map].cstr(), s_bots);
+ LastUpdateTime = time_get() / time_freq(); // We do not actually ping the server, just wait 3 seconds
+ ServerStarting = true;
+ }
+
+ MainView.VSplitLeft(300, 0, &Button);
+ Button.h = 30;
+ Button.w = 200;
+ static int s_StartInfServerButton = 0;
+ if( !ServerRunning && !ServerStarting && DoButton_Menu(&s_StartInfServerButton, Localize("Start INF server"), 0, &Button) )
+ {
+ StartServer("inf", s_maplist[s_map].cstr(), s_bots);
+ LastUpdateTime = time_get() / time_freq(); // We do not actually ping the server, just wait 3 seconds
+ ServerStarting = true;
+ }
+
+ MainView.VSplitLeft(550, 0, &Button);
+ Button.h = 30;
+ Button.w = 200;
+ static int s_StartCtfServerButton = 0;
+ if( !ServerRunning && !ServerStarting && DoButton_Menu(&s_StartCtfServerButton, Localize("Start CTF server"), 0, &Button) )
+ {
+ StartServer("ctf", s_maplist[s_map].cstr(), s_bots);
+ LastUpdateTime = time_get() / time_freq(); // We do not actually ping the server, just wait 3 seconds
+ ServerStarting = true;
+ }
+
+ static int s_JoinServerButton = 0;
+ if(ServerStarted || (ServerRunning && DoButton_Menu(&s_JoinServerButton, Localize("Join server"), 0, &Button)))
+ {
+ strcpy(g_Config.m_UiServerAddress, "127.0.0.1");
+ Client()->Connect(g_Config.m_UiServerAddress);
+ }
+
+ MainView.HSplitTop(60, 0, &MainView);
+
+ MainView.VSplitLeft(50, 0, &MsgBox);
+ MsgBox.w = 100;
+
+ char aBuf[64];
+ str_format(aBuf, sizeof(aBuf), "%s: %i", Localize("Bots"), s_bots);
+ UI()->DoLabelScaled(&MsgBox, aBuf, 20.0f, -1);
+
+ MainView.VSplitLeft(150, 0, &Button);
+ Button.h = 30;
+ Button.w = 500;
+
+ s_bots = (int)(DoScrollbarH(&s_bots, &Button, s_bots/15.0f)*15.0f+0.1f);
+
+ MainView.HSplitTop(60, 0, &MainView);
+
+ static float s_ScrollValue = 0.0f;
+ UiDoListboxStart(&s_ScrollValue, &MainView, 50.0f, Localize("Map"), "", s_maplist.size(), 1, s_map, s_ScrollValue);
+
+ for(int i = 0; i < s_maplist.size(); ++i)
+ {
+ CListboxItem Item = UiDoListboxNextItem(&s_maplist[i], s_map == i);
+ if(Item.m_Visible)
+ {
+ CUIRect Label;
+ //Item.m_Rect.Margin(5.0f, &Item.m_Rect);
+ Item.m_Rect.HSplitTop(10.0f, &Item.m_Rect, &Label);
+ UI()->DoLabelScaled(&Label, s_maplist[i].cstr(), 20.0f, 0);
+ }
+ }
+
+ s_map = UiDoListboxEnd(&s_ScrollValue, 0);
+}