diff --git a/game/client/touch.cpp b/game/client/touch.cpp index 586f616e..efc258b6 100644 --- a/game/client/touch.cpp +++ b/game/client/touch.cpp @@ -9,6 +9,9 @@ #include "ienginevgui.h" #include "in_buttons.h" #include "base_texture.h" +#include "filesystem.h" +#include "tier0/icommandline.h" +#include "vgui_controls/Button.h" extern ConVar cl_sidespeed; extern ConVar cl_forwardspeed; @@ -129,7 +132,12 @@ CON_COMMAND( touch_settexture, "add native touch button" ) CON_COMMAND( touch_enableedit, "enable button editing mode" ) { - gTouch.EnableTouchEdit(); + gTouch.EnableTouchEdit(true); +} + +CON_COMMAND( touch_disableedit, "disable button editing mode" ) +{ + gTouch.EnableTouchEdit(false); } CON_COMMAND( touch_setcolor, "change button color" ) @@ -161,28 +169,40 @@ CON_COMMAND( touch_setflags, "change button flags" ) 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" ) { -} +} CON_COMMAND( touch_roundall, "round all buttons coordinates to grid" ) { @@ -198,12 +218,9 @@ CON_COMMAND( touch_reloadconfig, "load config, not saving changes" ) { } +*/ -CON_COMMAND( touch_writeconfig, "save current config" ) -{ - -} - +/* CON_COMMAND( touch_fade, "start fade animation for selected buttons" ) { @@ -212,7 +229,7 @@ CON_COMMAND( touch_fade, "start fade animation for selected buttons" ) CON_COMMAND( touch_toggleselection, "toggle visibility on selected button in editor" ) { -} +}*/ void CTouchControls::Init() { @@ -224,9 +241,13 @@ void CTouchControls::Init() vgui::surface()->DrawSetTextureRGBA( base_textureID, base_img_rgba, 120, 96, 0, true ); } - engine->GetScreenSize( screen_w, screen_h ); + int w,h; + engine->GetScreenSize( w, h ); + screen_w = w; screen_h = h; - initialized = true; + Msg("grid_x: %f, grid_y: %x\n", GRID_X, GRID_Y); + configchanged = false; + config_loaded = false; btns.EnsureCapacity( 64 ); look_finger = move_finger = resize_finger = -1; forward = side = 0; @@ -243,10 +264,10 @@ void CTouchControls::Init() showtexture = hidetexture = resettexture = closetexture = joytexture = 0; configchanged = false; - rgba_t color(255, 255, 255, 255); + rgba_t color(255, 255, 255, 155); - AddButton( "_look", "", "", 0.5, 0, 1, 1, color, 0, 0, 0 ); - AddButton( "_move", "", "", 0, 0, 0.5, 1, color, 0, 0, 0 ); + 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 ); @@ -254,7 +275,8 @@ void CTouchControls::Init() 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", "+scoreboard", 0.680000, 0.00000, 0.760000, 0.142222, color ); + AddButton( "zoom", "vgui/touch/zoom", "+zoom", 0.680000, 0.00000, 0.760000, 0.142222, color ); +// AddButton( "score", "vgui/touch/map", "+score", 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 ); @@ -262,8 +284,19 @@ void CTouchControls::Init() 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( "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 ); +// AddButton( "lie", "vgui/touch/lie", "+alt1", 0.580000, 0.568889, 0.680000, 0.746667, color ); + + char buf[256]; + Q_snprintf(buf, sizeof buf, "exec %s\n", touch_config_file.GetString()); + engine->ClientCmd_Unrestricted(buf); + + Q_snprintf(buf, sizeof buf, "cfg/%s", touch_config_file.GetString()); + if( !filesystem->FileExists(buf) ) + WriteConfig(); + + initialized = true; } void CTouchControls::Shutdown( ) @@ -271,6 +304,25 @@ void CTouchControls::Shutdown( ) 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 @@ -328,7 +380,7 @@ void CTouchControls::Frame() vgui::surface()->DrawSetTexture(base_textureID); const int off = 50; - vgui::surface()->DrawTexturedRect( off, off, screen_w*0.3+off, 0.24f*screen_w+off ); + vgui::surface()->DrawTexturedRect( off, off, screen_w*0.3f+off, 0.24f*screen_w+off ); } if( touch_enable.GetBool() && !enginevgui->IsGameUIVisible() ) Paint(); @@ -339,16 +391,36 @@ void CTouchControls::Paint( ) if (!initialized) return; + + if( state == state_edit ) + { + vgui::surface()->DrawSetColor(255, 0, 0, 200); + 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 ); + } + CUtlLinkedList::iterator it; for( it = btns.begin(); it != btns.end(); it++ ) { CTouchButton *btn = *it; - if( btn->type == touch_move || btn->type == touch_look ) - continue; - vgui::surface()->DrawSetColor(255, 255, 255, 155); - vgui::surface()->DrawSetTexture( btn->textureID ); - vgui::surface()->DrawTexturedRect( btn->x1*screen_w, btn->y1*screen_h, btn->x2*screen_w, btn->y2*screen_h ); + if( btn->type != touch_move && btn->type != touch_look && !(btn->flags & TOUCH_FL_HIDE) ) + { + vgui::surface()->DrawSetTexture( btn->textureID ); + vgui::surface()->DrawSetColor(btn->color.r, btn->color.g, btn->color.b, btn->color.a); + vgui::surface()->DrawTexturedRect( btn->x1*screen_w, btn->y1*screen_h, btn->x2*screen_w, btn->y2*screen_h ); + } + + if( state == state_edit && !(btn->flags & TOUCH_FL_NOEDIT) ) + { + vgui::surface()->DrawSetColor(255, 0, 0, 50); + vgui::surface()->DrawFilledRect( btn->x1*screen_w, btn->y1*screen_h, btn->x2*screen_w, btn->y2*screen_h ); + } } } @@ -361,21 +433,23 @@ void CTouchControls::AddButton( const char *name, const char *texturefile, const Q_strncpy( btn->texturefile, texturefile, sizeof(btn->texturefile) ); Q_strncpy( btn->command, command, sizeof(btn->command) ); - IN_CheckCoords(&x1, &y1, &x2, &y2); + if( round ) + IN_CheckCoords(&x1, &y1, &x2, &y2); if( round == round_aspect ) - y2 = y1 + ( x2 - x1 ) * (((float)screen_w)/screen_h); + 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); + //IN_CheckCoords(&btn->x1, &btn->y1, &btn->x2, &btn->y2); - if( Q_strcmp(name, "_look") == 0 ) + if( Q_strcmp(command, "_look") == 0 ) type = touch_look; - else if( Q_strcmp(name, "_move") == 0 ) + else if( Q_strcmp(command, "_move") == 0 ) type = touch_move; btn->color = color; @@ -390,12 +464,16 @@ void CTouchControls::AddButton( const char *name, const char *texturefile, const 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) @@ -448,6 +526,7 @@ CTouchButton *CTouchControls::FindButton( const char *name ) if( Q_strncmp( button->name, name, sizeof(button->name)) == 0 ) return button; } + return NULL; } void CTouchControls::ProcessEvent(touch_event_t *ev) @@ -455,13 +534,96 @@ 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::FingerMotion(touch_event_t *ev) +void CTouchControls::EditEvent(touch_event_t *ev) +{ + float x = ev->x / (float)screen_w; + float y = ev->y / (float)screen_h; + + //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; + dx = x; dy = y; + break; + } + else if( resize_finger == -1 ) + { + resize_finger = ev->fingerid; + dx2 = x; dy2 = y; + } + } + } + } + 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; + dx = dy = 0.f; + } + else if( ev->fingerid == resize_finger ) + resize_finger = -1; + } + else // IE_FingerMotion + { + if( !selection ) + return; + + if( move_finger == ev->fingerid ) + { + selection->x1 += x-dx; + selection->x2 += x-dx; + selection->y1 += y-dy; + selection->y2 += y-dy; + + dx = x; + dy = y; + } + else if( resize_finger == ev->fingerid ) + { + selection->x2 += x-dx2; + selection->y2 += y-dy2; + + dx2 = x; dy2 = y; + } + } +} + + +void CTouchControls::FingerMotion(touch_event_t *ev) // finger in my ass { float x = ev->x / (float)screen_w; float y = ev->y / (float)screen_h; @@ -506,6 +668,9 @@ void CTouchControls::FingerPress(touch_event_t *ev) 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 ) { @@ -539,6 +704,10 @@ void CTouchControls::FingerPress(touch_event_t *ev) 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; @@ -563,10 +732,117 @@ void CTouchControls::FingerPress(touch_event_t *ev) } } -void CTouchControls::EnableTouchEdit() +void CTouchControls::EnableTouchEdit(bool enable) { - state = state_edit; - resize_finger = move_finger = look_finger = wheel_finger = -1; - move_button = NULL; - configchanged = true; + if( enable ) + { + state = state_edit; + resize_finger = move_finger = look_finger = wheel_finger = -1; + move_button = NULL; + configchanged = true; + AddButton( "close_edit", "vgui/touch/exit", "touch_disableedit", 0.010000, 0.837778, 0.080000, 0.980000, 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]; + + if( btns.IsEmpty() ) + 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 ) + { + CTouchButton *button; + 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, "\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/game/client/touch.h b/game/client/touch.h index 1876fa22..d4692a3e 100644 --- a/game/client/touch.h +++ b/game/client/touch.h @@ -1,4 +1,5 @@ #include "utllinkedlist.h" +#include "vgui/ISurface.h" #include "vgui/VGUI.h" #include #include "cbase.h" @@ -11,7 +12,7 @@ extern "C" int getAssets(); #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.0/GRID_COUNT_X) +#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) @@ -157,12 +158,13 @@ public: 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, int flags = 0 ); + 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 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 ); @@ -170,9 +172,10 @@ public: 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 ); @@ -182,11 +185,13 @@ public: void FingerPress( touch_event_t *ev ); void FingerMotion( touch_event_t *ev ); - void EnableTouchEdit(); + void EditEvent( touch_event_t *ev ); + + void EnableTouchEdit(bool enable); CTouchPanel *touchPanel; private: - bool initialized; + bool initialized = false; ETouchState state; CUtlLinkedList btns; @@ -196,7 +201,7 @@ private: CTouchButton *move_button; float move_start_x, move_start_y; - float dx, dy; + float dx, dy, dx2, dy2; // editing CTouchButton *edit; @@ -214,13 +219,14 @@ private: int closetexture; int joytexture; // touch indicator bool configchanged; + bool config_loaded; vgui::HFont textfont; int mouse_events; int base_textureID; bool m_bHaveAssets; - int screen_h, screen_w; + float screen_h, screen_w; }; extern CTouchControls gTouch; diff --git a/game/client/wscript b/game/client/wscript index 3d8e1fe0..9b9a50e4 100755 --- a/game/client/wscript +++ b/game/client/wscript @@ -64,7 +64,7 @@ def build(bld): install_path = bld.env.PREFIX if bld.env.DEST_OS != 'android': - install_path += '/hl2/bin' + install_path += '/'+bld.env.GAMES+'/bin' source = [ 'touch.cpp', 'arch.c' ] diff --git a/game/server/wscript b/game/server/wscript index 92c37a03..a474c2f8 100755 --- a/game/server/wscript +++ b/game/server/wscript @@ -57,7 +57,7 @@ def build(bld): install_path = bld.env.PREFIX if bld.env.DEST_OS != 'android': - install_path += '/hl2/bin' + install_path += '/'+bld.env.GAMES+'/bin' source = game["sources"] includes += game["includes"]