diff --git a/README.md b/README.md
index 3acf725b81..44e37b90e4 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,7 @@ That repository served as a great source of code and inspiration to get 'Experim
- **Lua Scripting**: Write your mod in Lua, a powerful and easy-to-learn scripting language.
- **Garry's Mod compatibility**: Use Garry's Mod addons, gamemodes, scripts and binary modules in your mod.
- **Game Content mounting**: Mount content from other Source Engine games, like Half-Life 2, Counter-Strike: Source and more.
+- **Native Android Support**: You can now run experiment-source on your Android phone! (NOTE: This feature is not ready yet)
[» Visit the Developer Portal for our full Goals & Roadmap.](https://experiment-games.github.io/experiment-source/general/goals-and-roadmap/)
@@ -235,3 +236,32 @@ Setup the debugger with the following values:
> - Working Directory: `C:\Program Files %28x86%29\Steam\steamapps\common\Source SDK Base 2013 Multiplayer`
+
+### Installation for Android
+1. Install vs-android from [Source4Android Project](https://github.com/GuestSneezeOSDev/source4android) or [vs-android GitHub repo](https://github.com/gavinpugh/vs-android)
+2. Edit your `server.vcxproj` and `client.vcxproj` and add this between line 5 or 12 or somewhere else. [1](https://github.com/GuestSneezeOSDev/source4android)
+```xml
+
+ $(SolutionDir)bin\$(Configuration)\android\
+
+```
+3. Now convert your `client.vcxproj` and `server.vcxproj` to compile as a shared library (aka `.so`), you can do this by renaming the `.vcxproj` to `.vcitems`
+Heres an example `.vcitems`
+```xml
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ true
+ {0c903c63-3ab9-49bf-b59f-0d0862db414a}
+
+
+
+ %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)
+
+
+
+
+
+
+```
\ No newline at end of file
diff --git a/src/game/client/cdll_client_int.cpp b/src/game/client/cdll_client_int.cpp
index af01f02bf3..110473ebf2 100644
--- a/src/game/client/cdll_client_int.cpp
+++ b/src/game/client/cdll_client_int.cpp
@@ -176,6 +176,8 @@ extern vgui::IInputInternal *g_InputInternal;
#include
#endif
+#include "touch.h"
+
#ifdef PORTAL
#include "PortalRender.h"
#endif
@@ -809,7 +811,8 @@ class CHLClient : public IBaseClientDLL
// Returns true if the disconnect command has been handled by the client
virtual bool DisconnectAttempt( void );
- public:
+ virtual void IN_TouchEvent( int type, int fingerId, int x, int y );
+
void PrecacheMaterial( const char *pMaterialName );
virtual bool IsConnectedUserInfoChangeAllowed( IConVar *pCvar );
@@ -1191,6 +1194,8 @@ int CHLClient::Init( CreateInterfaceFn appSystemFactory,
gHUD.Init();
+ gTouch.Init();
+
g_pClientMode->Init();
if ( !IGameSystem::InitAllSystems() )
@@ -1600,6 +1605,21 @@ int CHLClient::IN_KeyEvent( int eventcode, ButtonCode_t keynum, const char *pszC
return input->KeyEvent( eventcode, keynum, pszCurrentBinding );
}
+void CHLClient::IN_TouchEvent( int type, int fingerId, int x, int y )
+{
+ if ( enginevgui->IsGameUIVisible() )
+ return;
+
+ touch_event_t ev;
+
+ ev.type = type;
+ ev.fingerid = fingerId;
+ ev.x = x;
+ ev.y = y;
+
+ gTouch.ProcessEvent( &ev );
+}
+
void CHLClient::ExtraMouseSample( float frametime, bool active )
{
Assert( C_BaseEntity::IsAbsRecomputationsEnabled() );
diff --git a/src/game/client/client_experiment.vpc b/src/game/client/client_experiment.vpc
index e14115486a..56bc79ac43 100644
--- a/src/game/client/client_experiment.vpc
+++ b/src/game/client/client_experiment.vpc
@@ -176,6 +176,12 @@ $Project "Client (Experiment)"
$File "experiment\ui\experimenttextwindow.cpp"
$File "experiment\ui\experimenttextwindow.h"
+ $Folder "Android"
+ {
+ $File "experiment\ui\touch.cpp"
+ $File "experiment\ui\touch.cpp"
+ }
+
$File "experiment\ui\createmultiplayergamedialog.cpp"
$File "experiment\ui\createmultiplayergamedialog.h"
$File "experiment\ui\createmultiplayergamegameplaypage.cpp"
diff --git a/src/game/client/experiment/ui/touch.cpp b/src/game/client/experiment/ui/touch.cpp
new file mode 100644
index 0000000000..7c10fc47ef
--- /dev/null
+++ b/src/game/client/experiment/ui/touch.cpp
@@ -0,0 +1,1233 @@
+#include "cbase.h"
+#include "convar.h"
+#include
+#include "vgui/IInputInternal.h"
+#include "VGuiMatSurface/IMatSystemSurface.h"
+#include "vgui/ISurface.h"
+#include "touch.h"
+#include "cdll_int.h"
+#include "ienginevgui.h"
+#include "in_buttons.h"
+#include "filesystem.h"
+#include "tier0/icommandline.h"
+#include "vgui_controls/Button.h"
+#include "viewrender.h"
+
+#define STB_RECT_PACK_IMPLEMENTATION
+#include "..\public\stb_rect_pack.h"
+#include
+
+extern ConVar cl_sidespeed;
+extern ConVar cl_forwardspeed;
+extern ConVar cl_upspeed;
+extern ConVar default_fov;
+
+extern IMatSystemSurface* g_pMatSystemSurface;
+
+#ifdef ANDROID
+#define TOUCH_DEFAULT "1"
+#else
+#define TOUCH_DEFAULT "0"
+#endif
+
+extern ConVar sensitivity;
+
+#define TOUCH_DEFAULT_CFG "touch_default.cfg"
+#define MIN_ALPHA_IN_CUTSCENE 20
+
+ConVar touch_enable("touch_enable", TOUCH_DEFAULT, FCVAR_ARCHIVE);
+ConVar touch_draw("touch_draw", "1", FCVAR_ARCHIVE);
+ConVar touch_filter("touch_filter", "0", FCVAR_ARCHIVE);
+ConVar touch_forwardzone("touch_forwardzone", "0.06", FCVAR_ARCHIVE, "forward touch zone");
+ConVar touch_sidezone("touch_sidezone", "0.06", FCVAR_ARCHIVE, "side touch zone");
+ConVar touch_pitch("touch_pitch", "90", FCVAR_ARCHIVE, "touch pitch sensitivity");
+ConVar touch_yaw("touch_yaw", "120", FCVAR_ARCHIVE, "touch yaw sensitivity");
+ConVar touch_config_file("touch_config_file", "touch.cfg", FCVAR_ARCHIVE, "current touch profile file");
+ConVar touch_grid_count("touch_grid_count", "50", FCVAR_ARCHIVE, "touch grid count");
+ConVar touch_grid_enable("touch_grid_enable", "1", FCVAR_ARCHIVE, "enable touch grid");
+ConVar touch_precise_amount("touch_precise_amount", "0.5", FCVAR_ARCHIVE, "sensitivity multiplier for precise-look");
+
+ConVar touch_button_info("touch_button_info", "0", FCVAR_ARCHIVE);
+
+#define boundmax( num, high ) ( (num) < (high) ? (num) : (high) )
+#define boundmin( num, low ) ( (num) >= (low) ? (num) : (low) )
+#define bound( low, num, high ) ( boundmin( boundmax(num, high), low ))
+#define S
+
+extern IVEngineClient* engine;
+
+CTouchControls gTouch;
+static VTouchPanel g_TouchPanel;
+VTouchPanel* touch_panel = &g_TouchPanel;
+
+CTouchPanel::CTouchPanel(vgui::VPANEL parent) : BaseClass(NULL, "TouchPanel")
+{
+ SetParent(parent);
+
+ int w, h;
+ engine->GetScreenSize(w, h);
+ SetBounds(0, 0, w, h);
+
+ SetFgColor(Color(0, 0, 0, 255));
+ SetPaintBackgroundEnabled(false);
+
+ SetKeyBoardInputEnabled(false);
+ SetMouseInputEnabled(false);
+
+ SetVisible(true);
+}
+
+void CTouchPanel::Paint()
+{
+ gTouch.Frame();
+}
+
+void CTouchPanel::OnScreenSizeChanged(int iOldWide, int iOldTall)
+{
+ BaseClass::OnScreenSizeChanged(iOldWide, iOldTall);
+
+ int w, h;
+ w = ScreenWidth();
+ h = ScreenHeight();
+ gTouch.screen_w = ScreenWidth(); gTouch.screen_h = h;
+
+ SetBounds(0, 0, w, h);
+}
+
+void CTouchPanel::ApplySchemeSettings(vgui::IScheme* pScheme)
+{
+ BaseClass::ApplySchemeSettings(pScheme);
+
+ int w, h;
+ w = ScreenWidth();
+ h = ScreenHeight();
+ gTouch.screen_w = ScreenWidth(); gTouch.screen_h = h;
+
+ SetBounds(0, 0, w, h);
+}
+
+CON_COMMAND(touch_addbutton, "add native touch button")
+{
+ rgba_t color;
+ int argc = args.ArgC();
+
+ if (argc >= 12)
+ {
+ float aspect = 1.f;
+ int flags = 0;
+
+ if (argc >= 13)
+ flags = Q_atoi(args[12]);
+ if (argc >= 14)
+ aspect = Q_atof(args[13]);
+
+ color = rgba_t(Q_atoi(args[8]), Q_atoi(args[9]), Q_atoi(args[10]), Q_atoi(args[11]));
+ gTouch.AddButton(args[1], args[2], args[3],
+ Q_atof(args[4]), Q_atof(args[5]),
+ Q_atof(args[6]), Q_atof(args[7]),
+ color, round_aspect, aspect, flags);
+
+ return;
+ }
+
+ if (argc >= 8)
+ {
+ color = rgba_t(255, 255, 255);
+
+ gTouch.AddButton(args[1], args[2], args[3],
+ Q_atof(args[4]), Q_atof(args[5]),
+ Q_atof(args[6]), Q_atof(args[7]),
+ color);
+ return;
+ }
+ if (argc >= 4)
+ {
+ color = rgba_t(255, 255, 255);
+ gTouch.AddButton(args[1], args[2], args[3], 0.4, 0.4, 0.6, 0.6);
+ return;
+ }
+
+ Msg("Usage: touch_addbutton [ [ r g b a ] ]\n");
+}
+
+CON_COMMAND(touch_removebutton, "remove native touch button")
+{
+ if (args.ArgC() > 1)
+ gTouch.RemoveButton(args[1]);
+ else
+ Msg("Usage: touch_removebutton \n");
+}
+
+#if 0
+CON_COMMAND(touch_settexture, "set button texture")
+{
+ if (args.ArgC() >= 3)
+ {
+ gTouch.SetTexture(args[1], args[2]);
+ return;
+ }
+ Msg("Usage: touch_settexture \n");
+}
+#endif
+
+CON_COMMAND(touch_enableedit, "enable button editing mode")
+{
+ gTouch.EnableTouchEdit(true);
+}
+
+CON_COMMAND(touch_disableedit, "disable button editing mode")
+{
+ gTouch.EnableTouchEdit(false);
+}
+
+CON_COMMAND(touch_setcolor, "change button color")
+{
+ if (args.ArgC() >= 6)
+ {
+ rgba_t color(Q_atoi(args[2]), Q_atoi(args[3]), Q_atoi(args[4]), Q_atoi(args[5]));
+ gTouch.SetColor(args[1], color);
+ }
+ else
+ Msg("Usage: touch_setcolor \n");
+}
+
+CON_COMMAND(touch_setcommand, "change button command")
+{
+ if (args.ArgC() >= 3)
+ gTouch.SetCommand(args[1], args[2]);
+ else
+ Msg("Usage: touch_setcommand \n");
+}
+
+CON_COMMAND(touch_setflags, "change button flags")
+{
+ if (args.ArgC() >= 3)
+ gTouch.SetFlags(args[1], Q_atoi(args[2]));
+ else
+ Msg("Usage: touch_setflags \n");
+}
+
+CON_COMMAND(touch_show, "show button")
+{
+ if (args.ArgC() >= 2)
+ gTouch.ShowButton(args[1]);
+ else
+ Msg("Usage: touch_show \n");
+}
+
+CON_COMMAND(touch_hide, "hide button")
+{
+ if (args.ArgC() >= 2)
+ gTouch.HideButton(args[1]);
+ else
+ Msg("Usage: touch_hide \n");
+}
+
+CON_COMMAND(touch_list, "list buttons")
+{
+ gTouch.ListButtons();
+}
+
+CON_COMMAND(touch_removeall, "remove all buttons")
+{
+ gTouch.RemoveButtons();
+}
+
+CON_COMMAND(touch_writeconfig, "save current config")
+{
+ gTouch.WriteConfig();
+}
+
+
+CON_COMMAND(touch_loaddefaults, "generate config from defaults")
+{
+ gTouch.ResetToDefaults();
+}
+
+CON_COMMAND(touch_setgridcolor, "change grid color")
+{
+ if (args.ArgC() >= 5)
+ gTouch.gridcolor = rgba_t(Q_atoi(args[1]), Q_atoi(args[2]), Q_atoi(args[3]), Q_atoi(args[4]));
+ else
+ Msg("Usage: touch_setgridcolor \n");
+}
+
+/*
+CON_COMMAND( touch_roundall, "round all buttons coordinates to grid" )
+{
+
+}
+
+CON_COMMAND( touch_exportconfig, "export config keeping aspect ratio" )
+{
+
+}
+
+CON_COMMAND( touch_reloadconfig, "load config, not saving changes" )
+{
+
+}
+*/
+
+/*
+CON_COMMAND( touch_fade, "start fade animation for selected buttons" )
+{
+
+}
+
+CON_COMMAND( touch_toggleselection, "toggle visibility on selected button in editor" )
+{
+
+}*/
+
+void CTouchControls::GetTouchAccumulators(float* side, float* forward, float* yaw, float* pitch)
+{
+ *forward = this->forward;
+ *side = this->side;
+ *pitch = this->pitch;
+ *yaw = this->yaw;
+ this->yaw = 0.f;
+ this->pitch = 0.f;
+}
+
+void CTouchControls::GetTouchDelta(float yaw, float pitch, float* dx, float* dy)
+{
+ // Apply filtering?
+ if (touch_filter.GetBool())
+ {
+ // Average over last two samples
+ *dx = (yaw + m_flPreviousYaw) * 0.5f;
+ *dy = (pitch + m_flPreviousPitch) * 0.5f;
+ }
+ else
+ {
+ *dx = yaw;
+ *dy = pitch;
+ }
+
+ // Latch previous
+ m_flPreviousYaw = yaw;
+ m_flPreviousPitch = pitch;
+}
+
+void CTouchControls::ResetToDefaults()
+{
+ rgba_t color(255, 255, 255, 155);
+ char buf[MAX_PATH];
+ gridcolor = rgba_t(255, 0, 0, 30);
+
+ RemoveButtons();
+
+ Q_snprintf(buf, sizeof buf, "cfg/%s", TOUCH_DEFAULT_CFG);
+ if (!filesystem->FileExists(buf))
+ {
+ AddButton("look", "", "_look", 0.5, 0, 1, 1, color, 0, 0, 0);
+ AddButton("move", "", "_move", 0, 0, 0.5, 1, color, 0, 0, 0);
+
+ AddButton("use", "vgui/touch/use", "+use", 0.880000, 0.213333, 1.000000, 0.426667, color);
+ AddButton("jump", "vgui/touch/jump", "+jump", 0.880000, 0.462222, 1.000000, 0.675556, color);
+ AddButton("attack", "vgui/touch/shoot", "+attack", 0.760000, 0.583333, 0.880000, 0.796667, color);
+ AddButton("attack2", "vgui/touch/shoot_alt", "+attack2", 0.760000, 0.320000, 0.880000, 0.533333, color);
+ AddButton("duck", "vgui/touch/crouch", "+duck", 0.880000, 0.746667, 1.000000, 0.960000, color);
+ AddButton("tduck", "vgui/touch/tduck", ";+duck", 0.560000, 0.817778, 0.620000, 0.924444, color);
+ AddButton("zoom", "vgui/touch/zoom", "+zoom", 0.680000, 0.00000, 0.760000, 0.142222, color);
+ AddButton("speed", "vgui/touch/speed", "+speed", 0.180000, 0.568889, 0.280000, 0.746667, color);
+ AddButton("loadquick", "vgui/touch/load", "load quick", 0.760000, 0.000000, 0.840000, 0.142222, color);
+ AddButton("savequick", "vgui/touch/save", "save quick", 0.840000, 0.000000, 0.920000, 0.142222, color);
+ AddButton("reload", "vgui/touch/reload", "+reload", 0.000000, 0.320000, 0.120000, 0.533333, color);
+ AddButton("flashlight", "vgui/touch/flash_light_filled", "impulse 100", 0.920000, 0.000000, 1.000000, 0.142222, color);
+ AddButton("invnext", "vgui/touch/next_weap", "invnext", 0.000000, 0.533333, 0.120000, 0.746667, color);
+ AddButton("invprev", "vgui/touch/prev_weap", "invprev", 0.000000, 0.071111, 0.120000, 0.284444, color);
+ AddButton("edit", "vgui/touch/settings", "touch_enableedit", 0.420000, 0.000000, 0.500000, 0.151486, color);
+ AddButton("menu", "vgui/touch/menu", "gameui_activate", 0.000000, 0.00000, 0.080000, 0.142222, color);
+ }
+ else
+ {
+ Q_snprintf(buf, sizeof buf, "exec %s", TOUCH_DEFAULT_CFG);
+ engine->ExecuteClientCmd(buf);
+ }
+
+ WriteConfig();
+}
+
+void CTouchControls::Init()
+{
+ int w, h;
+ engine->GetScreenSize(w, h);
+ screen_w = w; screen_h = h;
+
+ touchTextureID = 0;
+ configchanged = false;
+ config_loaded = false;
+ btns.EnsureCapacity(64);
+ look_finger = move_finger = resize_finger = -1;
+ forward = side = 0.f;
+ pitch = yaw = 0.f;
+ scolor = rgba_t(1, 1, 1, 1);
+ state = state_none;
+ swidth = 1;
+ move_button = edit = selection = NULL;
+ showbuttons = true;
+ clientonly = false;
+ precision = false;
+ mouse_events = 0;
+ move_start_x = move_start_y = 0.0f;
+ m_flPreviousYaw = m_flPreviousPitch = 0.f;
+ gridcolor = rgba_t(255, 0, 0, 30);
+
+ m_bCutScene = false;
+ showtexture = hidetexture = resettexture = closetexture = joytexture = 0;
+ configchanged = false;
+
+ rgba_t color(255, 255, 255, 155);
+
+ AddButton("look", "", "_look", 0.5, 0, 1, 1, color, 0, 0, 0);
+ AddButton("move", "", "_move", 0, 0, 0.5, 1, color, 0, 0, 0);
+
+ AddButton("use", "vgui/touch/use", "+use", 0.880000, 0.213333, 1.000000, 0.426667, color);
+ AddButton("jump", "vgui/touch/jump", "+jump", 0.880000, 0.462222, 1.000000, 0.675556, color);
+ AddButton("attack", "vgui/touch/shoot", "+attack", 0.760000, 0.583333, 0.880000, 0.796667, color);
+ AddButton("attack2", "vgui/touch/shoot_alt", "+attack2", 0.760000, 0.320000, 0.880000, 0.533333, color);
+ AddButton("duck", "vgui/touch/crouch", "+duck", 0.880000, 0.746667, 1.000000, 0.960000, color);
+ AddButton("tduck", "vgui/touch/tduck", ";+duck", 0.560000, 0.817778, 0.620000, 0.924444, color);
+ AddButton("zoom", "vgui/touch/zoom", "+zoom", 0.680000, 0.00000, 0.760000, 0.142222, color);
+ AddButton("speed", "vgui/touch/speed", "+speed", 0.180000, 0.568889, 0.280000, 0.746667, color);
+ AddButton("loadquick", "vgui/touch/load", "load quick", 0.760000, 0.000000, 0.840000, 0.142222, color);
+ AddButton("savequick", "vgui/touch/save", "save quick", 0.840000, 0.000000, 0.920000, 0.142222, color);
+ AddButton("reload", "vgui/touch/reload", "+reload", 0.000000, 0.320000, 0.120000, 0.533333, color);
+ AddButton("flashlight", "vgui/touch/flash_light_filled", "impulse 100", 0.920000, 0.000000, 1.000000, 0.142222, color);
+ AddButton("invnext", "vgui/touch/next_weap", "invnext", 0.000000, 0.533333, 0.120000, 0.746667, color);
+ AddButton("invprev", "vgui/touch/prev_weap", "invprev", 0.000000, 0.071111, 0.120000, 0.284444, color);
+ AddButton("edit", "vgui/touch/settings", "touch_enableedit", 0.420000, 0.000000, 0.500000, 0.151486, color);
+ AddButton("menu", "vgui/touch/menu", "gameui_activate", 0.000000, 0.00000, 0.080000, 0.142222, color);
+
+ char buf[256];
+
+ Q_snprintf(buf, sizeof buf, "cfg/%s", touch_config_file.GetString());
+ if (filesystem->FileExists(buf, "MOD"))
+ {
+ Q_snprintf(buf, sizeof buf, "exec %s\n", touch_config_file.GetString());
+ engine->ExecuteClientCmd(buf);
+ }
+ else
+ ResetToDefaults();
+
+ CTouchTexture* texture = new CTouchTexture;
+ texture->isInAtlas = false;
+ texture->textureID = 0;
+ texture->X0 = 0; texture->X1 = 0; texture->Y0 = 0; texture->Y1 = 0;
+
+ Q_strncpy(texture->szName, "vgui/touch/back", sizeof(texture->szName));
+ textureList.AddToTail(texture);
+
+ CreateAtlasTexture();
+ m_flHideTouch = 0.f;
+
+ initialized = true;
+}
+
+void CTouchControls::LevelInit()
+{
+ m_bCutScene = false;
+ m_AlphaDiff = 0;
+ m_flHideTouch = 0;
+}
+
+int nextPowerOfTwo(int x)
+{
+ if ((x & (x - 1)) == 0)
+ return x;
+
+ int t = 1 << 30;
+ while (x < t) t >>= 1;
+
+ return t << 1;
+}
+
+void CTouchControls::CreateAtlasTexture()
+{
+ char fullFileName[MAX_PATH];
+
+ int atlasSize = 0;
+
+ stbrp_rect* rects = (stbrp_rect*)malloc(textureList.Count() * sizeof(stbrp_rect));
+ memset(rects, 0, sizeof(stbrp_node) * textureList.Count());
+
+ if (touchTextureID)
+ vgui::surface()->DeleteTextureByID(touchTextureID);
+
+ int rectCount = 0;
+
+ for (int i = 0; i < textureList.Count(); i++)
+ {
+ CTouchTexture* t = textureList[i];
+ Q_snprintf(fullFileName, MAX_PATH, "materials/%s.vtf", t->szName);
+
+ FileHandle_t fp;
+ fp = ::filesystem->Open(fullFileName, "rb");
+ if (!fp)
+ {
+ t->textureID = vgui::surface()->CreateNewTextureID();
+ vgui::surface()->DrawSetTextureFile(t->textureID, t->szName, true, false);
+ continue;
+ }
+
+ ::filesystem->Seek(fp, 0, FILESYSTEM_SEEK_TAIL);
+ int srcVTFLength = ::filesystem->Tell(fp);
+ ::filesystem->Seek(fp, 0, FILESYSTEM_SEEK_HEAD);
+
+ CUtlBuffer buf;
+ buf.EnsureCapacity(srcVTFLength);
+ int bytesRead = ::filesystem->Read(buf.Base(), srcVTFLength, fp);
+ ::filesystem->Close(fp);
+
+ buf.SeekGet(CUtlBuffer::SEEK_HEAD, 0); // Need to set these explicitly since ->Read goes straight to memory and skips them.
+ buf.SeekPut(CUtlBuffer::SEEK_HEAD, bytesRead);
+
+ t->vtf = CreateVTFTexture();
+ if (t->vtf->Unserialize(buf))
+ {
+ if (t->vtf->Format() != IMAGE_FORMAT_RGBA8888 && t->vtf->Format() != IMAGE_FORMAT_BGRA8888)
+ {
+ t->textureID = vgui::surface()->CreateNewTextureID();
+ vgui::surface()->DrawSetTextureFile(t->textureID, t->szName, true, false);
+ DestroyVTFTexture(t->vtf);
+ continue;
+ }
+ if (t->vtf->Height() != t->vtf->Width() || (t->vtf->Height() & (t->vtf->Height() - 1)) != 0)
+ Error("%s texture is wrong! Don't use npot textures for touch.");
+
+ t->height = t->vtf->Height();
+ t->width = t->vtf->Width();
+ t->isInAtlas = true;
+
+ atlasSize += t->width * t->height;
+ }
+ else
+ {
+ DestroyVTFTexture(t->vtf);
+ t->textureID = vgui::surface()->CreateNewTextureID();
+ vgui::surface()->DrawSetTextureFile(t->textureID, t->szName, true, false);
+ continue;
+ }
+
+ rects[rectCount].h = t->height;
+ rects[rectCount].w = t->width;
+ rectCount++;
+ }
+
+ if (!textureList.Count() || rectCount == 0)
+ {
+ free(rects);
+ return;
+ }
+
+ int atlasHeight = nextPowerOfTwo(sqrt((double)atlasSize));
+ int sizeInBytes = atlasHeight * atlasHeight * 4;
+ unsigned char* dest = new unsigned char[sizeInBytes];
+ memset(dest, 0, sizeInBytes);
+
+ int nodesCount = atlasHeight * 2;
+ stbrp_node* nodes = (stbrp_node*)malloc(nodesCount * sizeof(stbrp_node));
+ memset(nodes, 0, sizeof(stbrp_node) * nodesCount);
+
+ stbrp_context context;
+ stbrp_init_target(&context, atlasHeight, atlasHeight, nodes, nodesCount);
+ stbrp_pack_rects(&context, rects, rectCount);
+
+ rectCount = 0;
+ for (int i = 0; i < textureList.Count(); i++)
+ {
+ CTouchTexture* t = textureList[i];
+ if (t->textureID)
+ continue;
+
+ t->X0 = rects[rectCount].x / (float)atlasHeight;
+ t->Y0 = rects[rectCount].y / (float)atlasHeight;
+ t->X1 = t->X0 + t->width / (float)atlasHeight;
+ t->Y1 = t->Y0 + t->height / (float)atlasHeight;
+
+ unsigned char* src = t->vtf->ImageData(0, 0, 0);
+ for (int row = 0; row < t->height; row++)
+ {
+ unsigned char* row_dest = dest + (row + rects[rectCount].y) * atlasHeight * 4 + rects[rectCount].x * 4;
+ unsigned char* row_src = src + row * t->height * 4;
+
+ memcpy(row_dest, row_src, t->height * 4);
+ }
+ rectCount++;
+
+ DestroyVTFTexture(t->vtf);
+ }
+
+ touchTextureID = vgui::surface()->CreateNewTextureID(true);
+ vgui::surface()->DrawSetTextureRGBA(touchTextureID, dest, atlasHeight, atlasHeight, 1, true);
+
+ free(nodes);
+ free(rects);
+ delete[] dest;
+}
+
+void CTouchControls::Shutdown()
+{
+ textureList.PurgeAndDeleteElements();
+ btns.PurgeAndDeleteElements();
+}
+
+void CTouchControls::RemoveButtons()
+{
+ btns.PurgeAndDeleteElements();
+}
+
+void CTouchControls::ListButtons()
+{
+ CUtlLinkedList::iterator it;
+ for (it = btns.begin(); it != btns.end(); it++)
+ {
+ CTouchButton* b = *it;
+
+ Msg("%s %s %s %f %f %f %f %d %d %d %d %d\n",
+ b->name, b->texturefile, b->command,
+ b->x1, b->y1, b->x2, b->y2,
+ b->color.r, b->color.g, b->color.b, b->color.a, b->flags);
+ }
+}
+
+void CTouchControls::IN_CheckCoords(float* x1, float* y1, float* x2, float* y2)
+{
+ /// TODO: grid check here
+ if (*x2 - *x1 < GRID_X * 2)
+ *x2 = *x1 + GRID_X * 2;
+ if (*y2 - *y1 < GRID_Y * 2)
+ *y2 = *y1 + GRID_Y * 2;
+ if (*x1 < 0)
+ *x2 -= *x1, * x1 = 0;
+ if (*y1 < 0)
+ *y2 -= *y1, * y1 = 0;
+ if (*y2 > 1)
+ *y1 -= *y2 - 1, * y2 = 1;
+ if (*x2 > 1)
+ *x1 -= *x2 - 1, * x2 = 1;
+
+ if (touch_grid_enable.GetBool())
+ {
+ *x1 = GRID_ROUND_X(*x1);
+ *x2 = GRID_ROUND_X(*x2);
+ *y1 = GRID_ROUND_Y(*y1);
+ *y2 = GRID_ROUND_Y(*y2);
+ }
+}
+
+void CTouchControls::Move(float /*frametime*/, CUserCmd* cmd)
+{
+}
+
+void CTouchControls::IN_Look()
+{
+}
+
+void CTouchControls::Frame()
+{
+ if (!initialized)
+ return;
+
+ C_BasePlayer* pPlayer = C_BasePlayer::GetLocalPlayer();
+
+ if (pPlayer && (pPlayer->GetFlags() & FL_FROZEN || g_pIntroData != NULL))
+ {
+ if (!m_bCutScene)
+ {
+ m_bCutScene = true;
+ m_AlphaDiff = 0;
+ }
+ }
+ else if (!pPlayer)
+ {
+ m_bCutScene = false;
+ m_AlphaDiff = 0;
+ m_flHideTouch = 0;
+ }
+ else
+ m_bCutScene = false;
+
+ if (touch_enable.GetBool() && touch_draw.GetBool() && !enginevgui->IsGameUIVisible()) Paint();
+}
+
+void CTouchControls::Paint()
+{
+ if (!initialized)
+ return;
+
+ CUtlLinkedList::iterator it;
+
+ const rgba_t buttonEditClr = rgba_t(61, 153, 0, 40);
+
+ if (state == state_edit)
+ {
+ vgui::surface()->DrawSetColor(gridcolor.r, gridcolor.g, gridcolor.b, gridcolor.a * 3); // 255, 0, 0, 200 <- default here
+ float x, y;
+
+ for (x = 0.0f; x < 1.0f; x += GRID_X)
+ vgui::surface()->DrawLine(screen_w * x, 0, screen_w * x, screen_h);
+
+ for (y = 0.0f; y < 1.0f; y += GRID_Y)
+ vgui::surface()->DrawLine(0, screen_h * y, screen_w, screen_h * y);
+
+ for (it = btns.begin(); it != btns.end(); it++)
+ {
+ CTouchButton* btn = *it;
+
+ if (!(btn->flags & TOUCH_FL_NOEDIT))
+ {
+ if (touch_button_info.GetInt())
+ {
+ g_pMatSystemSurface->DrawColoredText(2, btn->x1 * screen_w, btn->y1 * screen_h, 255, 255, 255, 255, "N: %s", btn->name); // name
+ g_pMatSystemSurface->DrawColoredText(2, btn->x1 * screen_w, btn->y1 * screen_h + 10, 255, 255, 255, 255, "T: %s", btn->texturefile); // texture
+ g_pMatSystemSurface->DrawColoredText(2, btn->x1 * screen_w, btn->y1 * screen_h + 20, 255, 255, 255, 255, "C: %s", btn->command); // command
+ g_pMatSystemSurface->DrawColoredText(2, btn->x1 * screen_w, btn->y1 * screen_h + 30, 255, 255, 255, 255, "F: %i", btn->flags); // flags
+ g_pMatSystemSurface->DrawColoredText(2, btn->x1 * screen_w, btn->y1 * screen_h + 40, 255, 255, 255, 255, "RGBA: %d %d %d %d", btn->color.r, btn->color.g, btn->color.b, btn->color.a);// color
+ }
+
+ vgui::surface()->DrawSetColor(buttonEditClr.r, buttonEditClr.g, buttonEditClr.b, buttonEditClr.a); // 255, 0, 0, 50 <- default here
+ vgui::surface()->DrawFilledRect(btn->x1 * screen_w, btn->y1 * screen_h, btn->x2 * screen_w, btn->y2 * screen_h);
+ }
+ }
+ }
+
+ CMatRenderContextPtr pRenderContext(g_pMaterialSystem);
+ int meshCount = 0;
+
+ // Draw non-atlas touch textures
+ for (it = btns.begin(); it != btns.end(); it++)
+ {
+ CTouchButton* btn = *it;
+
+ if (btn->texture != NULL && !(btn->flags & TOUCH_FL_HIDE))
+ {
+ CTouchTexture* t = btn->texture;
+
+ if (t->textureID)
+ {
+ m_pMesh = pRenderContext->GetDynamicMesh(true, NULL, NULL, g_pMatSystemSurface->DrawGetTextureMaterial(t->textureID));
+
+ meshBuilder.Begin(m_pMesh, MATERIAL_QUADS, 1);
+
+ int alpha = (btn->color.a > MIN_ALPHA_IN_CUTSCENE) ? max(MIN_ALPHA_IN_CUTSCENE, btn->color.a - m_AlphaDiff) : btn->color.a;
+ rgba_t color(btn->color.r, btn->color.g, btn->color.b, alpha);
+
+ meshBuilder.Position3f(btn->x1 * screen_w, btn->y1 * screen_h, 0);
+ meshBuilder.Color4ubv(color);
+ meshBuilder.TexCoord2f(0, 0, 0);
+ meshBuilder.AdvanceVertexF();
+
+ meshBuilder.Position3f(btn->x2 * screen_w, btn->y1 * screen_h, 0);
+ meshBuilder.Color4ubv(color);
+ meshBuilder.TexCoord2f(0, 1, 0);
+ meshBuilder.AdvanceVertexF();
+
+ meshBuilder.Position3f(btn->x2 * screen_w, btn->y2 * screen_h, 0);
+ meshBuilder.Color4ubv(color);
+ meshBuilder.TexCoord2f(0, 1, 1);
+ meshBuilder.AdvanceVertexF();
+
+ meshBuilder.Position3f(btn->x1 * screen_w, btn->y2 * screen_h, 0);
+ meshBuilder.Color4ubv(color);
+ meshBuilder.TexCoord2f(0, 0, 1);
+ meshBuilder.AdvanceVertexF();
+
+ meshBuilder.End();
+
+ m_pMesh->Draw();
+ }
+ else if (!btn->texture->isInAtlas)
+ CreateAtlasTexture();
+
+ if (!t->textureID)
+ meshCount++;
+ }
+ }
+
+ m_pMesh = pRenderContext->GetDynamicMesh(true, NULL, NULL, g_pMatSystemSurface->DrawGetTextureMaterial(touchTextureID));
+ meshBuilder.Begin(m_pMesh, MATERIAL_QUADS, meshCount);
+
+ for (it = btns.begin(); it != btns.end(); it++)
+ {
+ CTouchButton* btn = *it;
+
+ if (btn->texture != NULL && !(btn->flags & TOUCH_FL_HIDE) && !btn->texture->textureID)
+ {
+ CTouchTexture* t = btn->texture;
+
+ int alpha = (btn->color.a > MIN_ALPHA_IN_CUTSCENE) ? max(MIN_ALPHA_IN_CUTSCENE, btn->color.a - m_AlphaDiff) : btn->color.a;
+ rgba_t color(btn->color.r, btn->color.g, btn->color.b, alpha);
+
+ meshBuilder.Position3f(btn->x1 * screen_w, btn->y1 * screen_h, 0);
+ meshBuilder.Color4ubv(color);
+ meshBuilder.TexCoord2f(0, t->X0, t->Y0);
+ meshBuilder.AdvanceVertexF();
+
+ meshBuilder.Position3f(btn->x2 * screen_w, btn->y1 * screen_h, 0);
+ meshBuilder.Color4ubv(color);
+ meshBuilder.TexCoord2f(0, t->X1, t->Y0);
+ meshBuilder.AdvanceVertexF();
+
+ meshBuilder.Position3f(btn->x2 * screen_w, btn->y2 * screen_h, 0);
+ meshBuilder.Color4ubv(color);
+ meshBuilder.TexCoord2f(0, t->X1, t->Y1);
+ meshBuilder.AdvanceVertexF();
+
+ meshBuilder.Position3f(btn->x1 * screen_w, btn->y2 * screen_h, 0);
+ meshBuilder.Color4ubv(color);
+ meshBuilder.TexCoord2f(0, t->X0, t->Y1);
+ meshBuilder.AdvanceVertexF();
+ }
+ }
+
+ meshBuilder.End();
+ m_pMesh->Draw();
+
+
+ if (m_flHideTouch < gpGlobals->curtime)
+ {
+ if (m_bCutScene && m_AlphaDiff < 255 - MIN_ALPHA_IN_CUTSCENE)
+ m_AlphaDiff++;
+ else if (!m_bCutScene && m_AlphaDiff > 0)
+ m_AlphaDiff--;
+
+ m_flHideTouch = gpGlobals->curtime + 0.002f;
+ }
+}
+
+void CTouchControls::AddButton(const char* name, const char* texturefile, const char* command, float x1, float y1, float x2, float y2, rgba_t color, int round, float aspect, int flags)
+{
+ CTouchButton* btn = new CTouchButton;
+ ETouchButtonType type = touch_command;
+
+ Q_strncpy(btn->name, name, sizeof(btn->name));
+ Q_strncpy(btn->texturefile, texturefile, sizeof(btn->texturefile));
+ Q_strncpy(btn->command, command, sizeof(btn->command));
+
+ if (round)
+ IN_CheckCoords(&x1, &y1, &x2, &y2);
+
+ if (round == round_aspect)
+ y2 = y1 + (x2 - x1) * (((float)screen_w) / screen_h) * aspect;
+
+ btn->x1 = x1;
+ btn->y1 = y1;
+ btn->x2 = x2;
+ btn->y2 = y2;
+ btn->flags = flags;
+
+ //IN_CheckCoords(&btn->x1, &btn->y1, &btn->x2, &btn->y2);
+
+ if (Q_strcmp(command, "_look") == 0)
+ type = touch_look;
+ else if (Q_strcmp(command, "_move") == 0)
+ type = touch_move;
+
+ btn->color = color;
+ btn->type = type;
+ btn->finger = -1;
+
+ if (btn->texturefile[0] == 0)
+ {
+ btn->texture = NULL;
+ btns.AddToTail(btn);
+ return;
+ }
+
+ for (int i = 0; i < textureList.Count(); i++)
+ {
+ if (strcmp(textureList[i]->szName, btn->texturefile) == 0)
+ {
+ btn->texture = textureList[i];
+ btns.AddToTail(btn);
+ return;
+ }
+ }
+
+ CTouchTexture* texture = new CTouchTexture;
+ btn->texture = texture;
+ texture->isInAtlas = false;
+ texture->textureID = 0;
+ texture->X0 = 0; texture->X1 = 0; texture->Y0 = 0; texture->Y1 = 0;
+ Q_strncpy(texture->szName, btn->texturefile, sizeof(btn->texturefile));
+ textureList.AddToTail(texture);
+
+ btns.AddToTail(btn);
+}
+
+void CTouchControls::ShowButton(const char* name)
+{
+ CTouchButton* btn = FindButton(name);
+ if (btn)
+ btn->flags &= ~TOUCH_FL_HIDE;
+}
+
+void CTouchControls::HideButton(const char* name)
+{
+ CTouchButton* btn = FindButton(name);
+ if (btn)
+ btn->flags |= TOUCH_FL_HIDE;
+}
+
+void CTouchControls::SetTexture(const char* name, const char* file)
+{
+ CTouchButton* btn = FindButton(name);
+
+ if (btn)
+ {
+ Q_strncpy(btn->texturefile, file, sizeof(btn->texturefile));
+
+ // btn->textureID = vgui::surface()->CreateNewTextureID();
+ // vgui::surface()->DrawSetTextureFile( btn->textureID, file, true, false);
+ }
+}
+
+void CTouchControls::SetColor(const char* name, rgba_t color)
+{
+ CTouchButton* btn = FindButton(name);
+ if (btn) btn->color = color;
+}
+
+void CTouchControls::SetCommand(const char* name, const char* cmd)
+{
+ CTouchButton* btn = FindButton(name);
+ if (btn) Q_strncpy(btn->command, cmd, sizeof btn->command);
+}
+
+void CTouchControls::SetFlags(const char* name, int flags)
+{
+ CTouchButton* btn = FindButton(name);
+ if (btn) btn->flags = flags;
+}
+
+void CTouchControls::RemoveButton(const char* name)
+{
+ for (int i = 0; i < btns.Count(); i++)
+ {
+ if (Q_strncmp(btns[i]->name, name, sizeof(btns[i]->name)) == 0)
+ btns.Free(i);
+ }
+}
+
+CTouchButton* CTouchControls::FindButton(const char* name)
+{
+ CUtlLinkedList::iterator it;
+ for (it = btns.begin(); it != btns.end(); it++)
+ {
+ CTouchButton* button = *it;
+
+ if (Q_strncmp(button->name, name, sizeof(button->name)) == 0)
+ return button;
+ }
+ return NULL;
+}
+
+void CTouchControls::ProcessEvent(touch_event_t* ev)
+{
+ if (!touch_enable.GetBool())
+ return;
+
+ if (state == state_edit)
+ {
+ EditEvent(ev);
+ return;
+ }
+
+ if (ev->type == IE_FingerMotion)
+ FingerMotion(ev);
+ else
+ FingerPress(ev);
+}
+
+void CTouchControls::EditEvent(touch_event_t* ev)
+{
+ const float x = ev->x;
+ const float y = ev->y;
+
+ //CUtlLinkedList::iterator it;
+
+ if (ev->type == IE_FingerDown)
+ {
+ //for( it = btns.end(); it != btns.begin(); it-- ) unexpected, doesn't work
+ for (int i = btns.Count() - 1; i >= 0; i--)
+ {
+ CTouchButton* btn = btns[i];
+ if (x > btn->x1 && x < btn->x2 && y > btn->y1 && y < btn->y2)
+ {
+ if (btn->flags & TOUCH_FL_HIDE)
+ continue;
+
+ if (btn->flags & TOUCH_FL_NOEDIT)
+ {
+ engine->ClientCmd_Unrestricted(btn->command);
+ continue;
+ }
+
+ if (move_finger == -1)
+ {
+ move_finger = ev->fingerid;
+ selection = btn;
+ break;
+ }
+ else if (resize_finger == -1)
+ {
+ resize_finger = ev->fingerid;
+ }
+ }
+ }
+ }
+ else if (ev->type == IE_FingerUp)
+ {
+ if (ev->fingerid == move_finger)
+ {
+ move_finger = -1;
+ IN_CheckCoords(&selection->x1, &selection->y1, &selection->x2, &selection->y2);
+ selection = nullptr;
+ }
+ else if (ev->fingerid == resize_finger)
+ resize_finger = -1;
+ }
+ else // IE_FingerMotion
+ {
+ if (!selection)
+ return;
+
+ if (move_finger == ev->fingerid)
+ {
+ selection->x1 += ev->dx;
+ selection->x2 += ev->dx;
+ selection->y1 += ev->dy;
+ selection->y2 += ev->dy;
+ }
+ else if (resize_finger == ev->fingerid)
+ {
+ selection->x2 += ev->dx;
+ selection->y2 += ev->dy;
+ }
+ }
+}
+
+
+void CTouchControls::FingerMotion(touch_event_t* ev) // finger in my ass
+{
+ const float x = ev->x;
+ const float y = ev->y;
+
+ float f, s;
+
+ CUtlLinkedList::iterator it;
+ for (it = btns.begin(); it != btns.end(); it++)
+ {
+ CTouchButton* btn = *it;
+ if (btn->finger == ev->fingerid)
+ {
+ if (btn->type == touch_move)
+ {
+ f = (move_start_y - y) / touch_forwardzone.GetFloat();
+ s = (move_start_x - x) / touch_sidezone.GetFloat();
+ forward = bound(-1, f, 1);
+ side = bound(-1, s, 1);
+ }
+ else if (btn->type == touch_look)
+ {
+ yaw += ev->dx;
+ pitch += ev->dy;
+ }
+ }
+ }
+}
+
+void CTouchControls::FingerPress(touch_event_t* ev)
+{
+ const float x = ev->x;
+ const float y = ev->y;
+
+ CUtlLinkedList::iterator it;
+
+ if (ev->type == IE_FingerDown)
+ {
+ for (it = btns.begin(); it != btns.end(); it++)
+ {
+ CTouchButton* btn = *it;
+ if (x > btn->x1 && x < btn->x2 && y > btn->y1 && y < btn->y2)
+ {
+ if (btn->flags & TOUCH_FL_HIDE)
+ continue;
+
+ btn->finger = ev->fingerid;
+ if (btn->type == touch_move)
+ {
+ if (move_finger == -1)
+ {
+ move_start_x = x;
+ move_start_y = y;
+ move_finger = ev->fingerid;
+ }
+ else
+ btn->finger = move_finger;
+ }
+ else if (btn->type == touch_look)
+ {
+ if (look_finger == -1)
+ look_finger = ev->fingerid;
+ else
+ btn->finger = look_finger;
+ }
+ else
+ engine->ClientCmd_Unrestricted(btn->command);
+ }
+ }
+ }
+ else if (ev->type == IE_FingerUp)
+ {
+ for (it = btns.begin(); it != btns.end(); it++)
+ {
+ CTouchButton* btn = *it;
+
+ if (btn->flags & TOUCH_FL_HIDE)
+ continue;
+
+ if (btn->finger == ev->fingerid)
+ {
+ btn->finger = -1;
+
+ if (btn->type == touch_move)
+ {
+ forward = side = 0;
+ move_finger = -1;
+ }
+ else if (btn->type == touch_look)
+ look_finger = -1;
+ else if (btn->command[0] == '+')
+ {
+ char cmd[256];
+
+ Q_snprintf(cmd, sizeof cmd, "%s", btn->command);
+ cmd[0] = '-';
+ engine->ClientCmd_Unrestricted(cmd);
+ }
+ }
+ }
+ }
+}
+
+void CTouchControls::EnableTouchEdit(bool enable)
+{
+ if (enable)
+ {
+ state = state_edit;
+ resize_finger = move_finger = look_finger = wheel_finger = -1;
+ move_button = NULL;
+ configchanged = true;
+ AddButton("close_edit", "vgui/touch/back", "touch_disableedit", 0.020000, 0.800000, 0.100000, 0.977778, rgba_t(255, 255, 255, 255), 0, 1.f, TOUCH_FL_NOEDIT);
+ }
+ else
+ {
+ state = state_none;
+ resize_finger = move_finger = look_finger = wheel_finger = -1;
+ move_button = NULL;
+ configchanged = false;
+ RemoveButton("close_edit");
+ WriteConfig();
+ }
+}
+
+void CTouchControls::WriteConfig()
+{
+ FileHandle_t f;
+ char newconfigfile[128];
+ char oldconfigfile[128];
+ char configfile[128];
+
+#define IsEmpty Count
+ if (btns.IsEmpty() == 0)
+ return;
+
+ if (CommandLine()->FindParm("-nowriteconfig"))
+ return;
+
+ DevMsg("Touch_WriteConfig(): %s\n", touch_config_file.GetString());
+
+ Q_snprintf(newconfigfile, 64, "cfg/%s.new", touch_config_file.GetString());
+ Q_snprintf(oldconfigfile, 64, "cfg/%s.bak", touch_config_file.GetString());
+ Q_snprintf(configfile, 64, "cfg/%s", touch_config_file.GetString());
+
+ f = filesystem->Open(newconfigfile, "w+");
+
+ if (f)
+ {
+ filesystem->FPrintf(f, "//=======================================================================\n");
+ filesystem->FPrintf(f, "//\t\t\ttouchscreen config\n");
+ filesystem->FPrintf(f, "//=======================================================================\n");
+ filesystem->FPrintf(f, "\ntouch_config_file \"%s\"\n", touch_config_file.GetString());
+ filesystem->FPrintf(f, "\n// touch cvars\n");
+ filesystem->FPrintf(f, "\n// sensitivity settings\n");
+ filesystem->FPrintf(f, "touch_pitch \"%f\"\n", touch_pitch.GetFloat());
+ filesystem->FPrintf(f, "touch_yaw \"%f\"\n", touch_yaw.GetFloat());
+ filesystem->FPrintf(f, "touch_forwardzone \"%f\"\n", touch_forwardzone.GetFloat());
+ filesystem->FPrintf(f, "touch_sidezone \"%f\"\n", touch_sidezone.GetFloat());
+ /* filesystem->FPrintf( f, "touch_nonlinear_look \"%d\"\n",touch_nonlinear_look.GetBool() );
+ filesystem->FPrintf( f, "touch_pow_factor \"%f\"\n", touch_pow_factor->value );
+ filesystem->FPrintf( f, "touch_pow_mult \"%f\"\n", touch_pow_mult->value );
+ filesystem->FPrintf( f, "touch_exp_mult \"%f\"\n", touch_exp_mult->value );*/
+ filesystem->FPrintf(f, "\n// grid settings\n");
+ filesystem->FPrintf(f, "touch_grid_count \"%d\"\n", touch_grid_count.GetInt());
+ filesystem->FPrintf(f, "touch_grid_enable \"%d\"\n", touch_grid_enable.GetInt());
+
+ filesystem->FPrintf(f, "touch_setgridcolor \"%d\" \"%d\" \"%d\" \"%d\"\n", gridcolor.r, gridcolor.g, gridcolor.b, gridcolor.a);
+ filesystem->FPrintf(f, "touch_button_info \"%d\"\n", touch_button_info.GetInt());
+ /*
+ filesystem->FPrintf( f, "\n// global overstroke (width, r, g, b, a)\n" );
+ filesystem->FPrintf( f, "touch_set_stroke %d %d %d %d %d\n", touch.swidth, touch.scolor[0], touch.scolor[1], touch.scolor[2], touch.scolor[3] );
+ filesystem->FPrintf( f, "\n// highlight when pressed\n" );
+ filesystem->FPrintf( f, "touch_highlight_r \"%f\"\n", touch_highlight_r->value );
+ filesystem->FPrintf( f, "touch_highlight_g \"%f\"\n", touch_highlight_g->value );
+ filesystem->FPrintf( f, "touch_highlight_b \"%f\"\n", touch_highlight_b->value );
+ filesystem->FPrintf( f, "touch_highlight_a \"%f\"\n", touch_highlight_a->value );
+ filesystem->FPrintf( f, "\n// _joy and _dpad options\n" );
+ filesystem->FPrintf( f, "touch_dpad_radius \"%f\"\n", touch_dpad_radius->value );
+ filesystem->FPrintf( f, "touch_joy_radius \"%f\"\n", touch_joy_radius->value );
+ */
+ filesystem->FPrintf(f, "\n// how much slowdown when Precise Look button pressed\n");
+ filesystem->FPrintf(f, "touch_precise_amount \"%f\"\n", touch_precise_amount.GetFloat());
+ // filesystem->FPrintf( f, "\n// enable/disable move indicator\n" );
+ // filesystem->FPrintf( f, "touch_move_indicator \"%f\"\n", touch_move_indicator );
+
+ filesystem->FPrintf(f, "\n// reset menu state when execing config\n");
+ //filesystem->FPrintf( f, "touch_setclientonly 0\n" );
+ filesystem->FPrintf(f, "\n// touch buttons\n");
+ filesystem->FPrintf(f, "touch_removeall\n");
+
+ CUtlLinkedList::iterator it;
+ for (it = btns.begin(); it != btns.end(); it++)
+ {
+ CTouchButton* b = *it;
+
+ if (b->flags & TOUCH_FL_CLIENT)
+ continue; //skip temporary buttons
+
+ if (b->flags & TOUCH_FL_DEF_SHOW)
+ b->flags &= ~TOUCH_FL_HIDE;
+
+ if (b->flags & TOUCH_FL_DEF_HIDE)
+ b->flags |= TOUCH_FL_HIDE;
+
+ float aspect = (b->y2 - b->y1) / ((b->x2 - b->x1) / (screen_h / screen_w));
+
+ filesystem->FPrintf(f, "touch_addbutton \"%s\" \"%s\" \"%s\" %f %f %f %f %d %d %d %d %d %f\n",
+ b->name, b->texturefile, b->command,
+ b->x1, b->y1, b->x2, b->y2,
+ b->color.r, b->color.g, b->color.b, b->color.a, b->flags, aspect);
+ }
+
+ filesystem->Close(f);
+
+ filesystem->RemoveFile(oldconfigfile);
+ filesystem->RenameFile(configfile, oldconfigfile);
+ filesystem->RenameFile(newconfigfile, configfile);
+ }
+ else DevMsg("Couldn't write %s.\n", configfile);
+}
diff --git a/src/game/client/experiment/ui/touch.h b/src/game/client/experiment/ui/touch.h
new file mode 100644
index 0000000000..b6f278d780
--- /dev/null
+++ b/src/game/client/experiment/ui/touch.h
@@ -0,0 +1,249 @@
+#include "utllinkedlist.h"
+#include "vgui/ISurface.h"
+#include "vgui/VGUI.h"
+#include
+#include "cbase.h"
+#include "kbutton.h"
+#include "usercmd.h"
+
+extern ConVar touch_enable;
+
+#define GRID_COUNT touch_grid_count.GetInt()
+#define GRID_COUNT_X (GRID_COUNT)
+#define GRID_COUNT_Y (GRID_COUNT * screen_h / screen_w)
+#define GRID_X (1.0f/GRID_COUNT_X)
+#define GRID_Y (screen_w/screen_h/GRID_COUNT_X)
+#define GRID_ROUND_X(x) ((float)round( x * GRID_COUNT_X ) / GRID_COUNT_X)
+#define GRID_ROUND_Y(x) ((float)round( x * GRID_COUNT_Y ) / GRID_COUNT_Y)
+
+#define CMD_SIZE 64
+
+#define TOUCH_FL_HIDE (1U << 0)
+#define TOUCH_FL_NOEDIT (1U << 1)
+#define TOUCH_FL_CLIENT (1U << 2)
+#define TOUCH_FL_MP (1U << 3)
+#define TOUCH_FL_SP (1U << 4)
+#define TOUCH_FL_DEF_SHOW (1U << 5)
+#define TOUCH_FL_DEF_HIDE (1U << 6)
+#define TOUCH_FL_DRAW_ADDITIVE (1U << 7)
+#define TOUCH_FL_STROKE (1U << 8)
+#define TOUCH_FL_PRECISION (1U << 9)
+
+enum ETouchButtonType
+{
+ touch_command = 0, // Tap button
+ touch_move, // Like a joystick stick.
+ touch_joy, // Like a joystick stick, centered.
+ touch_dpad, // Only two directions.
+ touch_look, // Like a touchpad.
+ touch_key
+};
+
+enum ETouchState
+{
+ state_none = 0,
+ state_edit,
+ state_edit_move
+};
+
+enum ETouchRound
+{
+ round_none = 0,
+ round_grid,
+ round_aspect
+};
+
+struct rgba_t
+{
+ rgba_t(unsigned char r, unsigned char g, unsigned char b, unsigned char a = 255) : r(r), g(g), b(b), a(a) { }
+ rgba_t() : r(0), g(0), b(0), a(0) { }
+ rgba_t(unsigned char* x) : r(x[0]), g(x[1]), b(x[2]), a(x[3]) { }
+
+ operator unsigned char* () { return &r; }
+
+ unsigned char r, g, b, a;
+};
+
+struct event_clientcmd_t
+{
+ char buf[CMD_SIZE];
+};
+
+struct event_s
+{
+ int type;
+ float x, y, dx, dy;
+ int fingerid;
+} typedef touch_event_t;
+
+
+struct CTouchTexture
+{
+ IVTFTexture* vtf;
+
+ float X0, Y0, X1, Y1; // position in atlas texture
+ int height, width;
+ int textureID;
+ bool isInAtlas;
+ char szName[1024];
+};
+
+class CTouchButton
+{
+public:
+ // Touch button type: tap, stick or slider
+ ETouchButtonType type;
+
+ // Field of button in pixels
+ float x1, y1, x2, y2;
+
+ rgba_t color;
+ char texturefile[256];
+ char command[256];
+ char name[32];
+ int finger;
+ int flags;
+ float fade;
+ float fadespeed;
+ float fadeend;
+ float aspect;
+ CTouchTexture* texture;
+};
+
+class CTouchPanel : public vgui::Panel
+{
+ DECLARE_CLASS_SIMPLE(CTouchPanel, vgui::Panel);
+
+public:
+ CTouchPanel(vgui::VPANEL parent);
+ virtual ~CTouchPanel(void) {};
+ virtual void Paint();
+ virtual void ApplySchemeSettings(vgui::IScheme* pScheme);
+
+protected:
+ MESSAGE_FUNC_INT_INT(OnScreenSizeChanged, "OnScreenSizeChanged", oldwide, oldtall);
+};
+
+abstract_class ITouchPanel
+{
+public:
+ virtual void Create(vgui::VPANEL parent) = 0;
+ virtual void Destroy(void) = 0;
+};
+
+class VTouchPanel : public ITouchPanel
+{
+private:
+ CTouchPanel* touchPanel;
+public:
+ VTouchPanel(void)
+ {
+ touchPanel = NULL;
+ }
+
+ void Create(vgui::VPANEL parent)
+ {
+ touchPanel = new CTouchPanel(parent);
+ }
+
+ void Destroy(void)
+ {
+ if (touchPanel)
+ {
+ touchPanel->SetParent((vgui::Panel*)NULL);
+ touchPanel->MarkForDeletion();
+ touchPanel = NULL;
+ }
+ }
+};
+
+class CTouchControls
+{
+public:
+ void Init();
+ void LevelInit();
+ void Shutdown();
+
+ void Paint();
+ void Frame();
+
+ void AddButton(const char* name, const char* texturefile, const char* command, float x1, float y1, float x2, float y2, rgba_t color = rgba_t(255, 255, 255, 255), int round = 2, float aspect = 1.f, int flags = 0);
+ void RemoveButton(const char* name);
+ void ResetToDefaults();
+ void HideButton(const char* name);
+ void ShowButton(const char* name);
+ void ListButtons();
+ void RemoveButtons();
+
+ CTouchButton* FindButton(const char* name);
+ // bool FindNextButton( const char *name, CTouchButton &button );
+ void SetTexture(const char* name, const char* file);
+ void SetColor(const char* name, rgba_t color);
+ void SetCommand(const char* name, const char* cmd);
+ void SetFlags(const char* name, int flags);
+ void WriteConfig();
+
+ void IN_CheckCoords(float* x1, float* y1, float* x2, float* y2);
+ void InitGrid();
+
+ void Move(float frametime, CUserCmd* cmd);
+ void IN_Look();
+
+ void ProcessEvent(touch_event_t* ev);
+ void FingerPress(touch_event_t* ev);
+ void FingerMotion(touch_event_t* ev);
+ void GetTouchAccumulators(float* forward, float* side, float* yaw, float* pitch);
+ void GetTouchDelta(float yaw, float pitch, float* dx, float* dy);
+ void EditEvent(touch_event_t* ev);
+ void EnableTouchEdit(bool enable);
+ void CreateAtlasTexture();
+
+ CTouchPanel* touchPanel;
+ float screen_h, screen_w;
+ float forward, side, movecount;
+ float yaw, pitch;
+ rgba_t gridcolor;
+
+private:
+ bool initialized = false;
+ ETouchState state;
+ CUtlLinkedList btns;
+ CUtlVector textureList;
+
+ int look_finger, move_finger, wheel_finger;
+ CTouchButton* move_button;
+
+ float move_start_x, move_start_y;
+ float m_flPreviousYaw, m_flPreviousPitch;
+
+ int touchTextureID;
+ IMesh* m_pMesh;
+ CMeshBuilder meshBuilder;
+
+ // editing
+ CTouchButton* edit;
+ CTouchButton* selection;
+ int resize_finger;
+ bool showbuttons;
+ bool clientonly;
+ rgba_t scolor;
+ int swidth;
+ bool precision;
+ // textures
+ int showtexture;
+ int hidetexture;
+ int resettexture;
+ int closetexture;
+ int joytexture; // touch indicator
+ bool configchanged;
+ bool config_loaded;
+ vgui::HFont textfont;
+ int mouse_events;
+
+ bool m_bCutScene;
+ float m_flHideTouch;
+ int m_AlphaDiff;
+};
+
+extern CTouchControls gTouch;
+extern VTouchPanel* touch_panel;
\ No newline at end of file
diff --git a/src/game/client/in_main.cpp b/src/game/client/in_main.cpp
index 313f3c0a95..d6b25e294c 100644
--- a/src/game/client/in_main.cpp
+++ b/src/game/client/in_main.cpp
@@ -41,6 +41,11 @@
#include
#endif
+#include "touch.h"
+#include "ienginevgui.h"
+#include "inputsystem/iinputsystem.h"
+
+
extern ConVar in_joystick;
extern ConVar cam_idealpitch;
extern ConVar cam_idealyaw;
@@ -1122,6 +1127,7 @@ void CInput::ControllerMove( float frametime, CUserCmd *cmd )
}
}
+ gTouch.Move( frametime, cmd );
JoyStickMove( frametime, cmd );
// NVNT if we have a haptic device..
@@ -1366,9 +1372,9 @@ void CInput::CreateMove( int sequence_number, float input_sample_frametime, bool
// Using joystick?
#ifdef SIXENSE
- if ( in_joystick.GetInt() || g_pSixenseInput->IsEnabled() )
+ if ( in_joystick.GetInt() || g_pSixenseInput->IsEnabled() || touch_enable.GetInt() )
#else
- if ( in_joystick.GetInt() )
+ if ( in_joystick.GetInt() || touch_enable.GetInt() )
#endif
{
if ( cmd->forwardmove > 0 )
diff --git a/src/game/client/vgui_int.cpp b/src/game/client/vgui_int.cpp
index a94974efd5..3701434e3e 100644
--- a/src/game/client/vgui_int.cpp
+++ b/src/game/client/vgui_int.cpp
@@ -23,6 +23,7 @@
#include
#include "filesystem.h"
#include "matsys_controls/matsyscontrols.h"
+#include "touch.h"
#ifdef SIXENSE
#include "sixense/in_sixense.h"
@@ -130,6 +131,7 @@ static void VGui_VideoMode_AdjustForModeChange( void )
fps->Destroy();
messagechars->Destroy();
loadingdisc->Destroy();
+ touch_panel->Destroy();
// Recreate our panels.
VPANEL gameToolParent = enginevgui->GetPanel( PANEL_CLIENTDLL_TOOLS );
@@ -143,6 +145,7 @@ static void VGui_VideoMode_AdjustForModeChange( void )
// Debugging or related tool
fps->Create( toolParent );
+ touch_panel->Create( toolParent );
#if defined( TRACK_BLOCKING_IO )
iopanel->Create( gameDLLPanel );
#endif
@@ -215,6 +218,7 @@ void VGui_CreateGlobalPanels( void )
// Debugging or related tool
fps->Create( toolParent );
+ touch_panel->Create( toolParent );
#if defined( TRACK_BLOCKING_IO )
iopanel->Create( gameDLLPanel );
#endif
@@ -244,6 +248,7 @@ void VGui_Shutdown()
iopanel->Destroy();
#endif
fps->Destroy();
+ touch_panel->Destroy();
messagechars->Destroy();
loadingdisc->Destroy();
diff --git a/src/public/cdll_int.h b/src/public/cdll_int.h
index aa39a12386..143522ffa0 100644
--- a/src/public/cdll_int.h
+++ b/src/public/cdll_int.h
@@ -777,6 +777,8 @@ abstract_class IBaseClientDLL
virtual bool DisconnectAttempt( void ) = 0;
virtual bool IsConnectedUserInfoChangeAllowed( IConVar * pCvar ) = 0;
+
+ virtual void IN_TouchEvent( int type, int fingerId, int x, int y ) = 0;
};
#define CLIENT_DLL_INTERFACE_VERSION "VClient017"
diff --git a/src/public/inputsystem/InputEnums.h b/src/public/inputsystem/InputEnums.h
index 4da6866b37..7b025ad360 100644
--- a/src/public/inputsystem/InputEnums.h
+++ b/src/public/inputsystem/InputEnums.h
@@ -79,6 +79,10 @@ enum InputEventType_t
IE_ButtonDoubleClicked, // m_nData contains a ButtonCode_t
IE_AnalogValueChanged, // m_nData contains an AnalogCode_t, m_nData2 contains the value
+ IE_FingerDown, // Touch Support
+ IE_FingerUp, // Touch Support
+ IE_FingerMotion, // Touch Support
+
IE_FirstSystemEvent = 100,
IE_Quit = IE_FirstSystemEvent,
IE_ControllerInserted, // m_nData contains the controller ID
diff --git a/src/public/stb_rect_pack.h b/src/public/stb_rect_pack.h
new file mode 100644
index 0000000000..6a633ce666
--- /dev/null
+++ b/src/public/stb_rect_pack.h
@@ -0,0 +1,623 @@
+// stb_rect_pack.h - v1.01 - public domain - rectangle packing
+// Sean Barrett 2014
+//
+// Useful for e.g. packing rectangular textures into an atlas.
+// Does not do rotation.
+//
+// Before #including,
+//
+// #define STB_RECT_PACK_IMPLEMENTATION
+//
+// in the file that you want to have the implementation.
+//
+// Not necessarily the awesomest packing method, but better than
+// the totally naive one in stb_truetype (which is primarily what
+// this is meant to replace).
+//
+// Has only had a few tests run, may have issues.
+//
+// More docs to come.
+//
+// No memory allocations; uses qsort() and assert() from stdlib.
+// Can override those by defining STBRP_SORT and STBRP_ASSERT.
+//
+// This library currently uses the Skyline Bottom-Left algorithm.
+//
+// Please note: better rectangle packers are welcome! Please
+// implement them to the same API, but with a different init
+// function.
+//
+// Credits
+//
+// Library
+// Sean Barrett
+// Minor features
+// Martins Mozeiko
+// github:IntellectualKitty
+//
+// Bugfixes / warning fixes
+// Jeremy Jaussaud
+// Fabian Giesen
+//
+// Version history:
+//
+// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section
+// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles
+// 0.99 (2019-02-07) warning fixes
+// 0.11 (2017-03-03) return packing success/fail result
+// 0.10 (2016-10-25) remove cast-away-const to avoid warnings
+// 0.09 (2016-08-27) fix compiler warnings
+// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0)
+// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0)
+// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort
+// 0.05: added STBRP_ASSERT to allow replacing assert
+// 0.04: fixed minor bug in STBRP_LARGE_RECTS support
+// 0.01: initial release
+//
+// LICENSE
+//
+// See end of file for license information.
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// INCLUDE SECTION
+//
+
+#ifndef STB_INCLUDE_STB_RECT_PACK_H
+#define STB_INCLUDE_STB_RECT_PACK_H
+
+#define STB_RECT_PACK_VERSION 1
+
+#ifdef STBRP_STATIC
+#define STBRP_DEF static
+#else
+#define STBRP_DEF extern
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct stbrp_context stbrp_context;
+typedef struct stbrp_node stbrp_node;
+typedef struct stbrp_rect stbrp_rect;
+
+typedef int stbrp_coord;
+
+#define STBRP__MAXVAL 0x7fffffff
+// Mostly for internal use, but this is the maximum supported coordinate value.
+
+STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
+// Assign packed locations to rectangles. The rectangles are of type
+// 'stbrp_rect' defined below, stored in the array 'rects', and there
+// are 'num_rects' many of them.
+//
+// Rectangles which are successfully packed have the 'was_packed' flag
+// set to a non-zero value and 'x' and 'y' store the minimum location
+// on each axis (i.e. bottom-left in cartesian coordinates, top-left
+// if you imagine y increasing downwards). Rectangles which do not fit
+// have the 'was_packed' flag set to 0.
+//
+// You should not try to access the 'rects' array from another thread
+// while this function is running, as the function temporarily reorders
+// the array while it executes.
+//
+// To pack into another rectangle, you need to call stbrp_init_target
+// again. To continue packing into the same rectangle, you can call
+// this function again. Calling this multiple times with multiple rect
+// arrays will probably produce worse packing results than calling it
+// a single time with the full rectangle array, but the option is
+// available.
+//
+// The function returns 1 if all of the rectangles were successfully
+// packed and 0 otherwise.
+
+struct stbrp_rect
+{
+ // reserved for your use:
+ int id;
+
+ // input:
+ stbrp_coord w, h;
+
+ // output:
+ stbrp_coord x, y;
+ int was_packed; // non-zero if valid packing
+
+}; // 16 bytes, nominally
+
+
+STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
+// Initialize a rectangle packer to:
+// pack a rectangle that is 'width' by 'height' in dimensions
+// using temporary storage provided by the array 'nodes', which is 'num_nodes' long
+//
+// You must call this function every time you start packing into a new target.
+//
+// There is no "shutdown" function. The 'nodes' memory must stay valid for
+// the following stbrp_pack_rects() call (or calls), but can be freed after
+// the call (or calls) finish.
+//
+// Note: to guarantee best results, either:
+// 1. make sure 'num_nodes' >= 'width'
+// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
+//
+// If you don't do either of the above things, widths will be quantized to multiples
+// of small integers to guarantee the algorithm doesn't run out of temporary storage.
+//
+// If you do #2, then the non-quantized algorithm will be used, but the algorithm
+// may run out of temporary storage and be unable to pack some rectangles.
+
+STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
+// Optionally call this function after init but before doing any packing to
+// change the handling of the out-of-temp-memory scenario, described above.
+// If you call init again, this will be reset to the default (false).
+
+
+STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
+// Optionally select which packing heuristic the library should use. Different
+// heuristics will produce better/worse results for different data sets.
+// If you call init again, this will be reset to the default.
+
+enum
+{
+ STBRP_HEURISTIC_Skyline_default=0,
+ STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
+ STBRP_HEURISTIC_Skyline_BF_sortHeight
+};
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// the details of the following structures don't matter to you, but they must
+// be visible so you can handle the memory allocations for them
+
+struct stbrp_node
+{
+ stbrp_coord x,y;
+ stbrp_node *next;
+};
+
+struct stbrp_context
+{
+ int width;
+ int height;
+ int align;
+ int init_mode;
+ int heuristic;
+ int num_nodes;
+ stbrp_node *active_head;
+ stbrp_node *free_head;
+ stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// IMPLEMENTATION SECTION
+//
+
+#ifdef STB_RECT_PACK_IMPLEMENTATION
+#ifndef STBRP_SORT
+#include
+#define STBRP_SORT qsort
+#endif
+
+#ifndef STBRP_ASSERT
+#include
+#define STBRP_ASSERT assert
+#endif
+
+#ifdef _MSC_VER
+#define STBRP__NOTUSED(v) (void)(v)
+#define STBRP__CDECL __cdecl
+#else
+#define STBRP__NOTUSED(v) (void)sizeof(v)
+#define STBRP__CDECL
+#endif
+
+enum
+{
+ STBRP__INIT_skyline = 1
+};
+
+STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
+{
+ switch (context->init_mode) {
+ case STBRP__INIT_skyline:
+ STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
+ context->heuristic = heuristic;
+ break;
+ default:
+ STBRP_ASSERT(0);
+ }
+}
+
+STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
+{
+ if (allow_out_of_mem)
+ // if it's ok to run out of memory, then don't bother aligning them;
+ // this gives better packing, but may fail due to OOM (even though
+ // the rectangles easily fit). @TODO a smarter approach would be to only
+ // quantize once we've hit OOM, then we could get rid of this parameter.
+ context->align = 1;
+ else {
+ // if it's not ok to run out of memory, then quantize the widths
+ // so that num_nodes is always enough nodes.
+ //
+ // I.e. num_nodes * align >= width
+ // align >= width / num_nodes
+ // align = ceil(width/num_nodes)
+
+ context->align = (context->width + context->num_nodes-1) / context->num_nodes;
+ }
+}
+
+STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
+{
+ int i;
+
+ for (i=0; i < num_nodes-1; ++i)
+ nodes[i].next = &nodes[i+1];
+ nodes[i].next = NULL;
+ context->init_mode = STBRP__INIT_skyline;
+ context->heuristic = STBRP_HEURISTIC_Skyline_default;
+ context->free_head = &nodes[0];
+ context->active_head = &context->extra[0];
+ context->width = width;
+ context->height = height;
+ context->num_nodes = num_nodes;
+ stbrp_setup_allow_out_of_mem(context, 0);
+
+ // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
+ context->extra[0].x = 0;
+ context->extra[0].y = 0;
+ context->extra[0].next = &context->extra[1];
+ context->extra[1].x = (stbrp_coord) width;
+ context->extra[1].y = (1<<30);
+ context->extra[1].next = NULL;
+}
+
+// find minimum y position if it starts at x1
+static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
+{
+ stbrp_node *node = first;
+ int x1 = x0 + width;
+ int min_y, visited_width, waste_area;
+
+ STBRP__NOTUSED(c);
+
+ STBRP_ASSERT(first->x <= x0);
+
+ #if 0
+ // skip in case we're past the node
+ while (node->next->x <= x0)
+ ++node;
+ #else
+ STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
+ #endif
+
+ STBRP_ASSERT(node->x <= x0);
+
+ min_y = 0;
+ waste_area = 0;
+ visited_width = 0;
+ while (node->x < x1) {
+ if (node->y > min_y) {
+ // raise min_y higher.
+ // we've accounted for all waste up to min_y,
+ // but we'll now add more waste for everything we've visted
+ waste_area += visited_width * (node->y - min_y);
+ min_y = node->y;
+ // the first time through, visited_width might be reduced
+ if (node->x < x0)
+ visited_width += node->next->x - x0;
+ else
+ visited_width += node->next->x - node->x;
+ } else {
+ // add waste area
+ int under_width = node->next->x - node->x;
+ if (under_width + visited_width > width)
+ under_width = width - visited_width;
+ waste_area += under_width * (min_y - node->y);
+ visited_width += under_width;
+ }
+ node = node->next;
+ }
+
+ *pwaste = waste_area;
+ return min_y;
+}
+
+typedef struct
+{
+ int x,y;
+ stbrp_node **prev_link;
+} stbrp__findresult;
+
+static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
+{
+ int best_waste = (1<<30), best_x, best_y = (1 << 30);
+ stbrp__findresult fr;
+ stbrp_node **prev, *node, *tail, **best = NULL;
+
+ // align to multiple of c->align
+ width = (width + c->align - 1);
+ width -= width % c->align;
+ STBRP_ASSERT(width % c->align == 0);
+
+ // if it can't possibly fit, bail immediately
+ if (width > c->width || height > c->height) {
+ fr.prev_link = NULL;
+ fr.x = fr.y = 0;
+ return fr;
+ }
+
+ node = c->active_head;
+ prev = &c->active_head;
+ while (node->x + width <= c->width) {
+ int y,waste;
+ y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
+ if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
+ // bottom left
+ if (y < best_y) {
+ best_y = y;
+ best = prev;
+ }
+ } else {
+ // best-fit
+ if (y + height <= c->height) {
+ // can only use it if it first vertically
+ if (y < best_y || (y == best_y && waste < best_waste)) {
+ best_y = y;
+ best_waste = waste;
+ best = prev;
+ }
+ }
+ }
+ prev = &node->next;
+ node = node->next;
+ }
+
+ best_x = (best == NULL) ? 0 : (*best)->x;
+
+ // if doing best-fit (BF), we also have to try aligning right edge to each node position
+ //
+ // e.g, if fitting
+ //
+ // ____________________
+ // |____________________|
+ //
+ // into
+ //
+ // | |
+ // | ____________|
+ // |____________|
+ //
+ // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
+ //
+ // This makes BF take about 2x the time
+
+ if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
+ tail = c->active_head;
+ node = c->active_head;
+ prev = &c->active_head;
+ // find first node that's admissible
+ while (tail->x < width)
+ tail = tail->next;
+ while (tail) {
+ int xpos = tail->x - width;
+ int y,waste;
+ STBRP_ASSERT(xpos >= 0);
+ // find the left position that matches this
+ while (node->next->x <= xpos) {
+ prev = &node->next;
+ node = node->next;
+ }
+ STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
+ y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
+ if (y + height <= c->height) {
+ if (y <= best_y) {
+ if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
+ best_x = xpos;
+ STBRP_ASSERT(y <= best_y);
+ best_y = y;
+ best_waste = waste;
+ best = prev;
+ }
+ }
+ }
+ tail = tail->next;
+ }
+ }
+
+ fr.prev_link = best;
+ fr.x = best_x;
+ fr.y = best_y;
+ return fr;
+}
+
+static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
+{
+ // find best position according to heuristic
+ stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
+ stbrp_node *node, *cur;
+
+ // bail if:
+ // 1. it failed
+ // 2. the best node doesn't fit (we don't always check this)
+ // 3. we're out of memory
+ if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
+ res.prev_link = NULL;
+ return res;
+ }
+
+ // on success, create new node
+ node = context->free_head;
+ node->x = (stbrp_coord) res.x;
+ node->y = (stbrp_coord) (res.y + height);
+
+ context->free_head = node->next;
+
+ // insert the new node into the right starting point, and
+ // let 'cur' point to the remaining nodes needing to be
+ // stiched back in
+
+ cur = *res.prev_link;
+ if (cur->x < res.x) {
+ // preserve the existing one, so start testing with the next one
+ stbrp_node *next = cur->next;
+ cur->next = node;
+ cur = next;
+ } else {
+ *res.prev_link = node;
+ }
+
+ // from here, traverse cur and free the nodes, until we get to one
+ // that shouldn't be freed
+ while (cur->next && cur->next->x <= res.x + width) {
+ stbrp_node *next = cur->next;
+ // move the current node to the free list
+ cur->next = context->free_head;
+ context->free_head = cur;
+ cur = next;
+ }
+
+ // stitch the list back in
+ node->next = cur;
+
+ if (cur->x < res.x + width)
+ cur->x = (stbrp_coord) (res.x + width);
+
+#ifdef _DEBUG
+ cur = context->active_head;
+ while (cur->x < context->width) {
+ STBRP_ASSERT(cur->x < cur->next->x);
+ cur = cur->next;
+ }
+ STBRP_ASSERT(cur->next == NULL);
+
+ {
+ int count=0;
+ cur = context->active_head;
+ while (cur) {
+ cur = cur->next;
+ ++count;
+ }
+ cur = context->free_head;
+ while (cur) {
+ cur = cur->next;
+ ++count;
+ }
+ STBRP_ASSERT(count == context->num_nodes+2);
+ }
+#endif
+
+ return res;
+}
+
+static int STBRP__CDECL rect_height_compare(const void *a, const void *b)
+{
+ const stbrp_rect *p = (const stbrp_rect *) a;
+ const stbrp_rect *q = (const stbrp_rect *) b;
+ if (p->h > q->h)
+ return -1;
+ if (p->h < q->h)
+ return 1;
+ return (p->w > q->w) ? -1 : (p->w < q->w);
+}
+
+static int STBRP__CDECL rect_original_order(const void *a, const void *b)
+{
+ const stbrp_rect *p = (const stbrp_rect *) a;
+ const stbrp_rect *q = (const stbrp_rect *) b;
+ return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
+}
+
+STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
+{
+ int i, all_rects_packed = 1;
+
+ // we use the 'was_packed' field internally to allow sorting/unsorting
+ for (i=0; i < num_rects; ++i) {
+ rects[i].was_packed = i;
+ }
+
+ // sort according to heuristic
+ STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
+
+ for (i=0; i < num_rects; ++i) {
+ if (rects[i].w == 0 || rects[i].h == 0) {
+ rects[i].x = rects[i].y = 0; // empty rect needs no space
+ } else {
+ stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
+ if (fr.prev_link) {
+ rects[i].x = (stbrp_coord) fr.x;
+ rects[i].y = (stbrp_coord) fr.y;
+ } else {
+ rects[i].x = rects[i].y = STBRP__MAXVAL;
+ }
+ }
+ }
+
+ // unsort
+ STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
+
+ // set was_packed flags and all_rects_packed status
+ for (i=0; i < num_rects; ++i) {
+ rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
+ if (!rects[i].was_packed)
+ all_rects_packed = 0;
+ }
+
+ // return the all_rects_packed status
+ return all_rects_packed;
+}
+#endif
+
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+This is free and unencumbered software released into the public domain.
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------------------------------
+*/
diff --git a/src/thirdparty/source4android/src4android.sh b/src/thirdparty/source4android/src4android.sh
new file mode 100644
index 0000000000..46d82b6a24
--- /dev/null
+++ b/src/thirdparty/source4android/src4android.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+# ===============SOURCE4ANDROID===============
+# Source4Android is licensed under MIT License,
+# so if you do plan on using it you must credit GuestSneezePlayZ in your HL2 Mod or Source Engine-based game.
+# Thank you for using this project! And Super Thanks to every contributor in the project!
+# Contributors:
+# GuestSneezeOSDev: Android support and most of the stuff
+# nillerusr: The touch support comes from his repository
+# ===============LICENSE======================
+# MIT License
+#
+#Copyright (c) 2025 Mohamed
+#
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+#
+#The above copyright notice and this permission notice shall be included in all
+#copies or substantial portions of the Software.
+#
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+#SOFTWARE.
+
+(
+SRCDIR = ../..
+echo "Please install vs-android before continuing."
+echo "It can be located here -> http://www.gavpugh.com/downloads/vs-android-0.964.zip"
+sleep 5
+echo "Editing client to support Android."
+${SRCDIR}/game/client/client.vcxproj << EOF
+
+ $(SolutionDir)bin\$(Configuration)\android\
+
+true
+EOF
+echo "Editing client to support Android. Complete"
+echo "Editing server to support Android."
+${SRCDIR}/game/server/server.vcxproj << EOF
+
+ $(SolutionDir)bin\$(Configuration)\android\
+
+true
+EOF
+echo "Editing server to support Android. Complete"
+) 2>&1 | tee -a src4android.log
+# ===============SOURCE4ANDROID LOGS===============
+echo "SOURCE4ANDROID LOGS:"
+cat src4android.log
\ No newline at end of file