void CGUI::Xeromyces_ReadScript(XMBElement Element, CXeromyces* pFile, boost::unordered_set<VfsPath>& Paths) { // Check for a 'file' parameter CStrW file (Element.GetAttributes().GetNamedItem( pFile->GetAttributeID("file") ).FromUTF8()); // If there is a file specified, open and execute it if (! file.empty()) { Paths.insert(file); try { m_ScriptInterface->LoadGlobalScriptFile(file); } catch (PSERROR_Scripting& e) { LOGERROR(L"GUI: Error executing script %ls: %hs", file.c_str(), e.what()); } } // Execute inline scripts try { CStr code (Element.GetText()); if (! code.empty()) m_ScriptInterface->LoadGlobalScript(L"Some XML file", code.FromUTF8()); } catch (PSERROR_Scripting& e) { LOGERROR(L"GUI: Error executing inline script: %hs", e.what()); } }
void CConsole::LoadHistory() { // note: we don't care if this file doesn't exist or can't be read; // just don't load anything in that case. // do this before LoadFile to avoid an error message if file not found. if (!VfsFileExists(m_sHistoryFile)) return; shared_ptr<u8> buf; size_t buflen; if (g_VFS->LoadFile(m_sHistoryFile, buf, buflen) < 0) return; CStr bytes ((char*)buf.get(), buflen); CStrW str (bytes.FromUTF8()); size_t pos = 0; while (pos != CStrW::npos) { pos = str.find('\n'); if (pos != CStrW::npos) { if (pos > 0) m_deqBufHistory.push_front(str.Left(str[pos-1] == '\r' ? pos - 1 : pos)); str = str.substr(pos + 1); } else if (str.length() > 0) m_deqBufHistory.push_front(str); } }
// Return file contents in a string. Assume file is UTF-8 encoded text. // // contents = readFile(filename); // filename: VFS filename (may include path) CScriptVal JSI_VFS::ReadFile(ScriptInterface::CxPrivate* pCxPrivate, std::wstring filename) { CVFSFile file; if (file.Load(g_VFS, filename) != PSRETURN_OK) return JSVAL_NULL; CStr contents = file.DecodeUTF8(); // assume it's UTF-8 // Fix CRLF line endings. (This function will only ever be used on text files.) contents.Replace("\r\n", "\n"); // Decode as UTF-8 return ScriptInterface::ToJSVal( pCxPrivate->pScriptInterface->GetContext(), contents.FromUTF8() ); }
// Return file contents in a string. Assume file is UTF-8 encoded text. // // contents = readFile(filename); // filename: VFS filename (may include path) JS::Value JSI_VFS::ReadFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filename) { JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); JSAutoRequest rq(cx); CVFSFile file; if (file.Load(g_VFS, filename) != PSRETURN_OK) return JS::NullValue(); CStr contents = file.DecodeUTF8(); // assume it's UTF-8 // Fix CRLF line endings. (This function will only ever be used on text files.) contents.Replace("\r\n", "\n"); // Decode as UTF-8 JS::RootedValue ret(cx); ScriptInterface::ToJSVal(cx, &ret, contents.FromUTF8()); return ret; }
/* * Command line options for autostart (keep synchronized with binaries/system/readme.txt): * * -autostart="TYPEDIR/MAPNAME" enables autostart and sets MAPNAME; TYPEDIR is skirmishes, scenarios, or random * -autostart-ai=PLAYER:AI sets the AI for PLAYER (e.g. 2:petra) * -autostart-aidiff=PLAYER:DIFF sets the DIFFiculty of PLAYER's AI (0: sandbox, 5: very hard) * -autostart-civ=PLAYER:CIV sets PLAYER's civilisation to CIV (skirmish and random maps only) * -autostart-aiseed=AISEED sets the seed used for the AI random generator (default 0, use -1 for random) * Multiplayer: * -autostart-playername=NAME sets local player NAME (default 'anonymous') * -autostart-host sets multiplayer host mode * -autostart-host-players=NUMBER sets NUMBER of human players for multiplayer game (default 2) * -autostart-client=IP sets multiplayer client to join host at given IP address * Random maps only: * -autostart-seed=SEED sets random map SEED value (default 0, use -1 for random) * -autostart-size=TILES sets random map size in TILES (default 192) * -autostart-players=NUMBER sets NUMBER of players on random map (default 2) * * Examples: * 1) "Bob" will host a 2 player game on the Arcadia map: * -autostart="scenarios/Arcadia 02" -autostart-host -autostart-host-players=2 -autostart-playername="Bob" * 2) Load Alpine Lakes random map with random seed, 2 players (Athens and Britons), and player 2 is PetraBot: * -autostart="random/alpine_lakes" -autostart-seed=-1 -autostart-players=2 -autostart-civ=1:athen -autostart-civ=2:brit -autostart-ai=2:petra */ bool Autostart(const CmdLineArgs& args) { CStr autoStartName = args.Get("autostart"); if (autoStartName.empty()) return false; g_Game = new CGame(); ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); JS::RootedValue attrs(cx); scriptInterface.Eval("({})", &attrs); JS::RootedValue settings(cx); scriptInterface.Eval("({})", &settings); JS::RootedValue playerData(cx); scriptInterface.Eval("([])", &playerData); // The directory in front of the actual map name indicates which type // of map is being loaded. Drawback of this approach is the association // of map types and folders is hard-coded, but benefits are: // - No need to pass the map type via command line separately // - Prevents mixing up of scenarios and skirmish maps to some degree Path mapPath = Path(autoStartName); std::wstring mapDirectory = mapPath.Parent().Filename().string(); std::string mapType; if (mapDirectory == L"random") { // Default seed is 0 u32 seed = 0; CStr seedArg = args.Get("autostart-seed"); if (!seedArg.empty()) { // Random seed value if (seedArg == "-1") seed = rand(); else seed = seedArg.ToULong(); } // Random map definition will be loaded from JSON file, so we need to parse it std::wstring scriptPath = L"maps/" + autoStartName.FromUTF8() + L".json"; JS::RootedValue scriptData(cx); scriptInterface.ReadJSONFile(scriptPath, &scriptData); if (!scriptData.isUndefined() && scriptInterface.GetProperty(scriptData, "settings", &settings)) { // JSON loaded ok - copy script name over to game attributes std::wstring scriptFile; scriptInterface.GetProperty(settings, "Script", scriptFile); scriptInterface.SetProperty(attrs, "script", scriptFile); // RMS filename } else { // Problem with JSON file LOGERROR("Autostart: Error reading random map script '%s'", utf8_from_wstring(scriptPath)); throw PSERROR_Game_World_MapLoadFailed("Error reading random map script.\nCheck application log for details."); } // Get optional map size argument (default 192) uint mapSize = 192; if (args.Has("autostart-size")) { CStr size = args.Get("autostart-size"); mapSize = size.ToUInt(); } scriptInterface.SetProperty(settings, "Seed", seed); // Random seed scriptInterface.SetProperty(settings, "Size", mapSize); // Random map size (in patches) // Get optional number of players (default 2) size_t numPlayers = 2; if (args.Has("autostart-players")) { CStr num = args.Get("autostart-players"); numPlayers = num.ToUInt(); } // Set up player data for (size_t i = 0; i < numPlayers; ++i) { JS::RootedValue player(cx); scriptInterface.Eval("({})", &player); // We could load player_defaults.json here, but that would complicate the logic // even more and autostart is only intended for developers anyway scriptInterface.SetProperty(player, "Civ", std::string("athen")); scriptInterface.SetPropertyInt(playerData, i, player); } mapType = "random"; } else if (mapDirectory == L"scenarios" || mapDirectory == L"skirmishes") { // Initialize general settings from the map data so some values // (e.g. name of map) are always present, even when autostart is // partially configured CStr8 mapSettingsJSON = LoadSettingsOfScenarioMap("maps/" + autoStartName + ".xml"); scriptInterface.ParseJSON(mapSettingsJSON, &settings); // Initialize the playerData array being modified by autostart // with the real map data, so sensible values are present: scriptInterface.GetProperty(settings, "PlayerData", &playerData); if (mapDirectory == L"scenarios") mapType = "scenario"; else mapType = "skirmish"; } else { LOGERROR("Autostart: Unrecognized map type '%s'", utf8_from_wstring(mapDirectory)); throw PSERROR_Game_World_MapLoadFailed("Unrecognized map type.\nConsult readme.txt for the currently supported types."); } scriptInterface.SetProperty(attrs, "mapType", mapType); scriptInterface.SetProperty(attrs, "map", std::string("maps/" + autoStartName)); scriptInterface.SetProperty(settings, "mapType", mapType); // Set seed for AIs u32 aiseed = 0; if (args.Has("autostart-aiseed")) { CStr seedArg = args.Get("autostart-aiseed"); if (seedArg == "-1") aiseed = rand(); else aiseed = seedArg.ToULong(); } scriptInterface.SetProperty(settings, "AISeed", aiseed); // Set player data for AIs // attrs.settings = { PlayerData: [ { AI: ... }, ... ] }: if (args.Has("autostart-ai")) { std::vector<CStr> aiArgs = args.GetMultiple("autostart-ai"); for (size_t i = 0; i < aiArgs.size(); ++i) { int playerID = aiArgs[i].BeforeFirst(":").ToInt(); // Instead of overwriting existing player data, modify the array JS::RootedValue player(cx); if (!scriptInterface.GetPropertyInt(playerData, playerID-1, &player) || player.isUndefined()) { if (mapDirectory == L"scenarios" || mapDirectory == L"skirmishes") { // playerID is certainly bigger than this map player number LOGWARNING("Autostart: Invalid player %d in autostart-ai option", playerID); continue; } scriptInterface.Eval("({})", &player); } CStr name = aiArgs[i].AfterFirst(":"); scriptInterface.SetProperty(player, "AI", std::string(name)); scriptInterface.SetProperty(player, "AIDiff", 3); scriptInterface.SetPropertyInt(playerData, playerID-1, player); } } // Set AI difficulty if (args.Has("autostart-aidiff")) { std::vector<CStr> civArgs = args.GetMultiple("autostart-aidiff"); for (size_t i = 0; i < civArgs.size(); ++i) { int playerID = civArgs[i].BeforeFirst(":").ToInt(); // Instead of overwriting existing player data, modify the array JS::RootedValue player(cx); if (!scriptInterface.GetPropertyInt(playerData, playerID-1, &player) || player.isUndefined()) { if (mapDirectory == L"scenarios" || mapDirectory == L"skirmishes") { // playerID is certainly bigger than this map player number LOGWARNING("Autostart: Invalid player %d in autostart-aidiff option", playerID); continue; } scriptInterface.Eval("({})", &player); } int difficulty = civArgs[i].AfterFirst(":").ToInt(); scriptInterface.SetProperty(player, "AIDiff", difficulty); scriptInterface.SetPropertyInt(playerData, playerID-1, player); } } // Set player data for Civs if (args.Has("autostart-civ")) { if (mapDirectory != L"scenarios") { std::vector<CStr> civArgs = args.GetMultiple("autostart-civ"); for (size_t i = 0; i < civArgs.size(); ++i) { int playerID = civArgs[i].BeforeFirst(":").ToInt(); // Instead of overwriting existing player data, modify the array JS::RootedValue player(cx); if (!scriptInterface.GetPropertyInt(playerData, playerID-1, &player) || player.isUndefined()) { if (mapDirectory == L"skirmishes") { // playerID is certainly bigger than this map player number LOGWARNING("Autostart: Invalid player %d in autostart-civ option", playerID); continue; } scriptInterface.Eval("({})", &player); } CStr name = civArgs[i].AfterFirst(":"); scriptInterface.SetProperty(player, "Civ", std::string(name)); scriptInterface.SetPropertyInt(playerData, playerID-1, player); } } else LOGWARNING("Autostart: Option 'autostart-civ' is invalid for scenarios"); } // Add player data to map settings scriptInterface.SetProperty(settings, "PlayerData", playerData); // Add map settings to game attributes scriptInterface.SetProperty(attrs, "settings", settings); JS::RootedValue mpInitData(cx); scriptInterface.Eval("({isNetworked:true, playerAssignments:{}})", &mpInitData); scriptInterface.SetProperty(mpInitData, "attribs", attrs); // Get optional playername CStrW userName = L"anonymous"; if (args.Has("autostart-playername")) { userName = args.Get("autostart-playername").FromUTF8(); } if (args.Has("autostart-host")) { InitPs(true, L"page_loading.xml", &scriptInterface, mpInitData); size_t maxPlayers = 2; if (args.Has("autostart-host-players")) maxPlayers = args.Get("autostart-host-players").ToUInt(); g_NetServer = new CNetServer(maxPlayers); g_NetServer->UpdateGameAttributes(&attrs, scriptInterface); bool ok = g_NetServer->SetupConnection(); ENSURE(ok); g_NetClient = new CNetClient(g_Game); g_NetClient->SetUserName(userName); g_NetClient->SetupConnection("127.0.0.1"); } else if (args.Has("autostart-client")) { InitPs(true, L"page_loading.xml", &scriptInterface, mpInitData); g_NetClient = new CNetClient(g_Game); g_NetClient->SetUserName(userName); CStr ip = args.Get("autostart-client"); if (ip.empty()) ip = "127.0.0.1"; bool ok = g_NetClient->SetupConnection(ip); ENSURE(ok); } else { g_Game->SetPlayerID(1); g_Game->StartGame(&attrs, ""); LDR_NonprogressiveLoad(); PSRETURN ret = g_Game->ReallyStartGame(); ENSURE(ret == PSRETURN_OK); InitPs(true, L"page_session.xml", NULL, JS::UndefinedHandleValue); } return true; }
// Render void CProfileViewer::RenderProfile() { if (!m->profileVisible) return; if (!m->path.size()) { m->profileVisible = false; return; } PROFILE3_GPU("profile viewer"); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); AbstractProfileTable* table = m->path[m->path.size() - 1]; const std::vector<ProfileColumn>& columns = table->GetColumns(); size_t numrows = table->GetNumberRows(); CFont font(L"mono-stroke-10"); font.Bind(); int lineSpacing = font.GetLineSpacing(); // Render background GLint estimate_height; GLint estimate_width; estimate_width = 50; for(size_t i = 0; i < columns.size(); ++i) estimate_width += (GLint)columns[i].width; estimate_height = 3 + (GLint)numrows; if (m->path.size() > 1) estimate_height += 2; estimate_height = lineSpacing*estimate_height; glDisable(GL_TEXTURE_2D); glColor4ub(0,0,0,128); glBegin(GL_QUADS); glVertex2i(estimate_width, g_yres); glVertex2i(0, g_yres); glVertex2i(0, g_yres-estimate_height); glVertex2i(estimate_width, g_yres-estimate_height); glEnd(); glEnable(GL_TEXTURE_2D); // Print table and column titles glPushMatrix(); glTranslatef(2.0f, g_yres - lineSpacing, 0.0f ); glScalef(1.0f, -1.0f, 1.0f); glColor3ub(255, 255, 255); glPushMatrix(); glwprintf(L"%hs", table->GetTitle().c_str()); glPopMatrix(); glTranslatef( 20.0f, lineSpacing, 0.0f ); glPushMatrix(); for(size_t col = 0; col < columns.size(); ++col) { CStr text = columns[col].title; int w, h; font.CalculateStringSize(text.FromUTF8(), w, h); glPushMatrix(); if (col > 0) // right-align all but the first column glTranslatef(columns[col].width - w, 0, 0); glwprintf(L"%hs", text.c_str()); glPopMatrix(); glTranslatef(columns[col].width, 0, 0); } glPopMatrix(); glTranslatef( 0.0f, lineSpacing, 0.0f ); // Print rows int currentExpandId = 1; for(size_t row = 0; row < numrows; ++row) { glPushMatrix(); glDisable(GL_TEXTURE_2D); if (row % 2) glColor4ub(255, 255, 255, 16); else glColor4ub(0, 0, 0, 16); glBegin(GL_QUADS); glVertex2i(-22.f, 2.f); glVertex2i(estimate_width-22.f, 2.f); glVertex2i(estimate_width-22.f, 2.f-lineSpacing); glVertex2i(-22.f, 2.f-lineSpacing); glEnd(); glEnable(GL_TEXTURE_2D); if (table->IsHighlightRow(row)) glColor3ub(255, 128, 128); else glColor3ub(255, 255, 255); if (table->GetChild(row)) { glPushMatrix(); glTranslatef( -15.0f, 0.0f, 0.0f ); glwprintf(L"%d", currentExpandId); glPopMatrix(); currentExpandId++; } for(size_t col = 0; col < columns.size(); ++col) { CStr text = table->GetCellText(row, col); int w, h; font.CalculateStringSize(text.FromUTF8(), w, h); glPushMatrix(); if (col > 0) // right-align all but the first column glTranslatef(columns[col].width - w, 0, 0); glwprintf(L"%hs", text.c_str()); glPopMatrix(); glTranslatef(columns[col].width, 0, 0); } glPopMatrix(); glTranslatef( 0.0f, lineSpacing, 0.0f ); } glColor3ub(255, 255, 255); if (m->path.size() > 1) { glTranslatef( 0.0f, lineSpacing, 0.0f ); glPushMatrix(); glPushMatrix(); glTranslatef( -15.0f, 0.0f, 0.0f ); glwprintf( L"0" ); glPopMatrix(); glwprintf( L"back to parent" ); glPopMatrix(); } glPopMatrix(); glDisable(GL_BLEND); }
void Render() { PROFILE3("render"); #if CONFIG2_AUDIO if (g_SoundManager) g_SoundManager->IdleTask(); #endif ogl_WarnIfError(); g_Profiler2.RecordGPUFrameStart(); ogl_WarnIfError(); CStr skystring = "255 0 255"; CFG_GET_VAL("skycolor", String, skystring); CColor skycol; GUI<CColor>::ParseString(skystring.FromUTF8(), skycol); g_Renderer.SetClearColor(skycol.AsSColor4ub()); // prepare before starting the renderer frame if (g_Game && g_Game->IsGameStarted()) g_Game->GetView()->BeginFrame(); if (g_Game) g_Renderer.SetSimulation(g_Game->GetSimulation2()); // start new frame g_Renderer.BeginFrame(); ogl_WarnIfError(); if (g_Game && g_Game->IsGameStarted()) g_Game->GetView()->Render(); ogl_WarnIfError(); g_Renderer.RenderTextOverlays(); if (g_DoRenderGui) g_GUI->Draw(); ogl_WarnIfError(); // If we're in Atlas game view, render special overlays (e.g. editor bandbox) if (g_AtlasGameLoop && g_AtlasGameLoop->view) { g_AtlasGameLoop->view->DrawOverlays(); ogl_WarnIfError(); } // Text: glDisable(GL_DEPTH_TEST); g_Console->Render(); ogl_WarnIfError(); if (g_DoRenderLogger) g_Logger->Render(); ogl_WarnIfError(); // Profile information g_ProfileViewer.RenderProfile(); ogl_WarnIfError(); // Draw the cursor (or set the Windows cursor, on Windows) if (g_DoRenderCursor) { PROFILE3_GPU("cursor"); CStrW cursorName = g_CursorName; if (cursorName.empty()) { cursor_draw(g_VFS, NULL, g_mouse_x, g_yres-g_mouse_y, false); } else { bool forceGL = false; CFG_GET_VAL("nohwcursor", Bool, forceGL); #if CONFIG2_GLES #warning TODO: implement cursors for GLES #else // set up transform for GL cursor glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); CMatrix3D transform; transform.SetOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); glLoadMatrixf(&transform._11); #endif if (cursor_draw(g_VFS, cursorName.c_str(), g_mouse_x, g_yres-g_mouse_y, forceGL) < 0) LOGWARNING(L"Failed to draw cursor '%ls'", cursorName.c_str()); #if CONFIG2_GLES #warning TODO: implement cursors for GLES #else // restore transform glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); #endif } } glEnable(GL_DEPTH_TEST); g_Renderer.EndFrame(); PROFILE2_ATTR("draw calls: %d", (int)g_Renderer.GetStats().m_DrawCalls); PROFILE2_ATTR("terrain tris: %d", (int)g_Renderer.GetStats().m_TerrainTris); PROFILE2_ATTR("water tris: %d", (int)g_Renderer.GetStats().m_WaterTris); PROFILE2_ATTR("model tris: %d", (int)g_Renderer.GetStats().m_ModelTris); PROFILE2_ATTR("overlay tris: %d", (int)g_Renderer.GetStats().m_OverlayTris); PROFILE2_ATTR("blend splats: %d", (int)g_Renderer.GetStats().m_BlendSplats); PROFILE2_ATTR("particles: %d", (int)g_Renderer.GetStats().m_Particles); ogl_WarnIfError(); g_Profiler2.RecordGPUFrameEnd(); ogl_WarnIfError(); }
bool Autostart(const CmdLineArgs& args) { /* * Handle various command-line options, for quick testing of various features: * -autostart=name -- map name for scenario, or rms name for random map * -autostart-ai=1:dummybot -- adds the dummybot AI to player 1 * -autostart-playername=name -- multiplayer player name * -autostart-host -- multiplayer host mode * -autostart-players=2 -- number of players * -autostart-client -- multiplayer client mode * -autostart-ip=127.0.0.1 -- multiplayer connect to 127.0.0.1 * -autostart-random=104 -- random map, optional seed value = 104 (default is 0, random is -1) * -autostart-size=192 -- random map size in tiles = 192 (default is 192) * * Examples: * -autostart=Acropolis -autostart-host -autostart-players=2 -- Host game on Acropolis map, 2 players * -autostart=latium -autostart-random=-1 -- Start single player game on latium random map, random rng seed */ CStr autoStartName = args.Get("autostart"); #if OS_ANDROID // HACK: currently the most convenient way to test maps on Android; // should find a better solution autoStartName = "Oasis"; #endif if (autoStartName.empty()) { return false; } g_Game = new CGame(); ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); CScriptValRooted attrs; scriptInterface.Eval("({})", attrs); CScriptVal settings; scriptInterface.Eval("({})", settings); CScriptVal playerData; scriptInterface.Eval("([])", playerData); // Set different attributes for random or scenario game if (args.Has("autostart-random")) { CStr seedArg = args.Get("autostart-random"); // Default seed is 0 uint32 seed = 0; if (!seedArg.empty()) { if (seedArg.compare("-1") == 0) { // Random seed value seed = rand(); } else { seed = seedArg.ToULong(); } } // Random map definition will be loaded from JSON file, so we need to parse it std::wstring mapPath = L"maps/random/"; std::wstring scriptPath = mapPath + autoStartName.FromUTF8() + L".json"; CScriptValRooted scriptData = scriptInterface.ReadJSONFile(scriptPath); if (!scriptData.undefined() && scriptInterface.GetProperty(scriptData.get(), "settings", settings)) { // JSON loaded ok - copy script name over to game attributes std::wstring scriptFile; scriptInterface.GetProperty(settings.get(), "Script", scriptFile); scriptInterface.SetProperty(attrs.get(), "script", scriptFile); // RMS filename } else { // Problem with JSON file LOGERROR(L"Error reading random map script '%ls'", scriptPath.c_str()); throw PSERROR_Game_World_MapLoadFailed("Error reading random map script.\nCheck application log for details."); } // Get optional map size argument (default 192) uint mapSize = 192; if (args.Has("autostart-size")) { CStr size = args.Get("autostart-size"); mapSize = size.ToUInt(); } scriptInterface.SetProperty(attrs.get(), "map", std::string(autoStartName)); scriptInterface.SetProperty(attrs.get(), "mapPath", mapPath); scriptInterface.SetProperty(attrs.get(), "mapType", std::string("random")); scriptInterface.SetProperty(settings.get(), "Seed", seed); // Random seed scriptInterface.SetProperty(settings.get(), "Size", mapSize); // Random map size (in patches) // Get optional number of players (default 2) size_t numPlayers = 2; if (args.Has("autostart-players")) { CStr num = args.Get("autostart-players"); numPlayers = num.ToUInt(); } // Set up player data for (size_t i = 0; i < numPlayers; ++i) { CScriptVal player; scriptInterface.Eval("({})", player); // We could load player_defaults.json here, but that would complicate the logic // even more and autostart is only intended for developers anyway scriptInterface.SetProperty(player.get(), "Civ", std::string("athen")); scriptInterface.SetPropertyInt(playerData.get(), i, player); } } else { scriptInterface.SetProperty(attrs.get(), "map", std::string(autoStartName)); scriptInterface.SetProperty(attrs.get(), "mapType", std::string("scenario")); } // Set player data for AIs // attrs.settings = { PlayerData: [ { AI: ... }, ... ] }: if (args.Has("autostart-ai")) { std::vector<CStr> aiArgs = args.GetMultiple("autostart-ai"); for (size_t i = 0; i < aiArgs.size(); ++i) { // Instead of overwriting existing player data, modify the array CScriptVal player; if (!scriptInterface.GetPropertyInt(playerData.get(), i, player) || player.undefined()) { scriptInterface.Eval("({})", player); } int playerID = aiArgs[i].BeforeFirst(":").ToInt(); CStr name = aiArgs[i].AfterFirst(":"); scriptInterface.SetProperty(player.get(), "AI", std::string(name)); scriptInterface.SetPropertyInt(playerData.get(), playerID-1, player); } } // Add player data to map settings scriptInterface.SetProperty(settings.get(), "PlayerData", playerData); // Add map settings to game attributes scriptInterface.SetProperty(attrs.get(), "settings", settings); CScriptVal mpInitData; g_GUI->GetScriptInterface().Eval("({isNetworked:true, playerAssignments:{}})", mpInitData); g_GUI->GetScriptInterface().SetProperty(mpInitData.get(), "attribs", CScriptVal(g_GUI->GetScriptInterface().CloneValueFromOtherContext(scriptInterface, attrs.get()))); // Get optional playername CStrW userName = L"anonymous"; if (args.Has("autostart-playername")) { userName = args.Get("autostart-playername").FromUTF8(); } if (args.Has("autostart-host")) { InitPs(true, L"page_loading.xml", mpInitData.get()); size_t maxPlayers = 2; if (args.Has("autostart-players")) { maxPlayers = args.Get("autostart-players").ToUInt(); } g_NetServer = new CNetServer(maxPlayers); g_NetServer->UpdateGameAttributes(attrs.get(), scriptInterface); bool ok = g_NetServer->SetupConnection(); ENSURE(ok); g_NetClient = new CNetClient(g_Game); g_NetClient->SetUserName(userName); g_NetClient->SetupConnection("127.0.0.1"); } else if (args.Has("autostart-client")) { InitPs(true, L"page_loading.xml", mpInitData.get()); g_NetClient = new CNetClient(g_Game); g_NetClient->SetUserName(userName); CStr ip = "127.0.0.1"; if (args.Has("autostart-ip")) { ip = args.Get("autostart-ip"); } bool ok = g_NetClient->SetupConnection(ip); ENSURE(ok); } else { g_Game->SetPlayerID(1); g_Game->StartGame(attrs, ""); LDR_NonprogressiveLoad(); PSRETURN ret = g_Game->ReallyStartGame(); ENSURE(ret == PSRETURN_OK); InitPs(true, L"page_session.xml", JSVAL_VOID); } return true; }
void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObject* pParent, std::vector<std::pair<CStr, CStr> >& NameSubst, boost::unordered_set<VfsPath>& Paths, u32 nesting_depth) { ENSURE(pParent); XMBAttributeList attributes = Element.GetAttributes(); CStr type(attributes.GetNamedItem(pFile->GetAttributeID("type"))); if (type.empty()) type = "empty"; // Construct object from specified type // henceforth, we need to do a rollback before aborting. // i.e. releasing this object IGUIObject* object = ConstructObject(type); if (!object) { LOGERROR("GUI: Unrecognized object type \"%s\"", type.c_str()); return; } // Cache some IDs for element attribute names, to avoid string comparisons #define ELMT(x) int elmt_##x = pFile->GetElementID(#x) #define ATTR(x) int attr_##x = pFile->GetAttributeID(#x) ELMT(object); ELMT(action); ELMT(repeat); ELMT(translatableAttribute); ELMT(translate); ELMT(attribute); ELMT(keep); ELMT(include); ATTR(style); ATTR(type); ATTR(name); ATTR(hotkey); ATTR(z); ATTR(on); ATTR(file); ATTR(directory); ATTR(id); ATTR(context); // // Read Style and set defaults // // If the setting "style" is set, try loading that setting. // // Always load default (if it's available) first! // CStr argStyle(attributes.GetNamedItem(attr_style)); if (m_Styles.count("default") == 1) object->LoadStyle(*this, "default"); if (!argStyle.empty()) { if (m_Styles.count(argStyle) == 0) LOGERROR("GUI: Trying to use style '%s' that doesn't exist.", argStyle.c_str()); else object->LoadStyle(*this, argStyle); } bool NameSet = false; bool ManuallySetZ = false; CStrW inclusionPath; CStr hotkeyTag; for (XMBAttribute attr : attributes) { // If value is "null", then it is equivalent as never being entered if (CStr(attr.Value) == "null") continue; // Ignore "type" and "style", we've already checked it if (attr.Name == attr_type || attr.Name == attr_style) continue; if (attr.Name == attr_name) { CStr name(attr.Value); for (const std::pair<CStr, CStr>& sub : NameSubst) name.Replace(sub.first, sub.second); object->SetName(name); NameSet = true; continue; } if (attr.Name == attr_hotkey) hotkeyTag = attr.Value; if (attr.Name == attr_z) ManuallySetZ = true; if (object->SetSetting(pFile->GetAttributeString(attr.Name), attr.Value.FromUTF8(), true) != PSRETURN_OK) LOGERROR("GUI: (object: %s) Can't set \"%s\" to \"%s\"", object->GetPresentableName(), pFile->GetAttributeString(attr.Name), attr.Value); } // Check if name isn't set, generate an internal name in that case. if (!NameSet) { object->SetName("__internal(" + CStr::FromInt(m_InternalNameNumber) + ")"); ++m_InternalNameNumber; } if (!hotkeyTag.empty()) m_HotkeyObjects[hotkeyTag].push_back(object); CStrW caption(Element.GetText().FromUTF8()); if (!caption.empty()) object->SetSetting("caption", caption, true); for (XMBElement child : Element.GetChildNodes()) { // Check what name the elements got int element_name = child.GetNodeName(); if (element_name == elmt_object) { // Call this function on the child Xeromyces_ReadObject(child, pFile, object, NameSubst, Paths, nesting_depth); } else if (element_name == elmt_action) { // Scripted <action> element // Check for a 'file' parameter CStrW filename(child.GetAttributes().GetNamedItem(attr_file).FromUTF8()); CStr code; // If there is a file, open it and use it as the code if (!filename.empty()) { Paths.insert(filename); CVFSFile scriptfile; if (scriptfile.Load(g_VFS, filename) != PSRETURN_OK) { LOGERROR("Error opening GUI script action file '%s'", utf8_from_wstring(filename)); throw PSERROR_GUI_JSOpenFailed(); } code = scriptfile.DecodeUTF8(); // assume it's UTF-8 } XMBElementList grandchildren = child.GetChildNodes(); if (!grandchildren.empty()) // The <action> element contains <keep> and <translate> tags. for (XMBElement grandchild : grandchildren) { if (grandchild.GetNodeName() == elmt_translate) code += g_L10n.Translate(grandchild.GetText()); else if (grandchild.GetNodeName() == elmt_keep) code += grandchild.GetText(); } else // It’s pure JavaScript code. // Read the inline code (concatenating to the file code, if both are specified) code += CStr(child.GetText()); CStr action = CStr(child.GetAttributes().GetNamedItem(attr_on)); // We need to set the GUI this object belongs to because RegisterScriptHandler requires an associated GUI. object->SetGUI(this); object->RegisterScriptHandler(action.LowerCase(), code, this); } else if (element_name == elmt_repeat) { Xeromyces_ReadRepeat(child, pFile, object, NameSubst, Paths, nesting_depth); } else if (element_name == elmt_translatableAttribute) { // This is an element in the form “<translatableAttribute id="attributeName">attributeValue</translatableAttribute>”. CStr attributeName(child.GetAttributes().GetNamedItem(attr_id)); // Read the attribute name. if (attributeName.empty()) { LOGERROR("GUI: ‘translatableAttribute’ XML element with empty ‘id’ XML attribute found. (object: %s)", object->GetPresentableName().c_str()); continue; } CStr value(child.GetText()); if (value.empty()) continue; CStr context(child.GetAttributes().GetNamedItem(attr_context)); // Read the context if any. if (!context.empty()) { CStr translatedValue(g_L10n.TranslateWithContext(context, value)); object->SetSetting(attributeName, translatedValue.FromUTF8(), true); } else { CStr translatedValue(g_L10n.Translate(value)); object->SetSetting(attributeName, translatedValue.FromUTF8(), true); } } else if (element_name == elmt_attribute) { // This is an element in the form “<attribute id="attributeName"><keep>Don’t translate this part // </keep><translate>but translate this one.</translate></attribute>”. CStr attributeName(child.GetAttributes().GetNamedItem(attr_id)); // Read the attribute name. if (attributeName.empty()) { LOGERROR("GUI: ‘attribute’ XML element with empty ‘id’ XML attribute found. (object: %s)", object->GetPresentableName().c_str()); continue; } CStr translatedValue; for (XMBElement grandchild : child.GetChildNodes()) { if (grandchild.GetNodeName() == elmt_translate) translatedValue += g_L10n.Translate(grandchild.GetText()); else if (grandchild.GetNodeName() == elmt_keep) translatedValue += grandchild.GetText(); } object->SetSetting(attributeName, translatedValue.FromUTF8(), true); } else if (element_name == elmt_include) { CStrW filename(child.GetAttributes().GetNamedItem(attr_file).FromUTF8()); CStrW directory(child.GetAttributes().GetNamedItem(attr_directory).FromUTF8()); if (!filename.empty()) { if (!directory.empty()) LOGWARNING("GUI: Include element found with file name (%s) and directory name (%s). Only the file will be processed.", utf8_from_wstring(filename), utf8_from_wstring(directory)); Paths.insert(filename); CXeromyces XeroIncluded; if (XeroIncluded.Load(g_VFS, filename, "gui") != PSRETURN_OK) { LOGERROR("GUI: Error reading included XML: '%s'", utf8_from_wstring(filename)); continue; } XMBElement node = XeroIncluded.GetRoot(); if (node.GetNodeName() != XeroIncluded.GetElementID("object")) { LOGERROR("GUI: Error reading included XML: '%s', root element must have be of type 'object'.", utf8_from_wstring(filename)); continue; } if (nesting_depth+1 >= MAX_OBJECT_DEPTH) { LOGERROR("GUI: Too many nested GUI includes. Probably caused by a recursive include attribute. Abort rendering '%s'.", utf8_from_wstring(filename)); continue; } Xeromyces_ReadObject(node, &XeroIncluded, object, NameSubst, Paths, nesting_depth+1); } else if (!directory.empty()) { if (nesting_depth+1 >= MAX_OBJECT_DEPTH) { LOGERROR("GUI: Too many nested GUI includes. Probably caused by a recursive include attribute. Abort rendering '%s'.", utf8_from_wstring(directory)); continue; } VfsPaths pathnames; vfs::GetPathnames(g_VFS, directory, L"*.xml", pathnames); for (const VfsPath& path : pathnames) { // as opposed to loading scripts, don't care if it's loaded before // one might use the same parts of the GUI in different situations Paths.insert(path); CXeromyces XeroIncluded; if (XeroIncluded.Load(g_VFS, path, "gui") != PSRETURN_OK) { LOGERROR("GUI: Error reading included XML: '%s'", path.string8()); continue; } XMBElement node = XeroIncluded.GetRoot(); if (node.GetNodeName() != XeroIncluded.GetElementID("object")) { LOGERROR("GUI: Error reading included XML: '%s', root element must have be of type 'object'.", path.string8()); continue; } Xeromyces_ReadObject(node, &XeroIncluded, object, NameSubst, Paths, nesting_depth+1); } } else LOGERROR("GUI: 'include' XML element must have valid 'file' or 'directory' attribute found. (object %s)", object->GetPresentableName().c_str()); } else { // Try making the object read the tag. if (!object->HandleAdditionalChildren(child, pFile)) LOGERROR("GUI: (object: %s) Reading unknown children for its type", object->GetPresentableName().c_str()); } } if (!ManuallySetZ) { // Set it automatically to 10 plus its parents bool absolute; GUI<bool>::GetSetting(object, "absolute", absolute); if (absolute) // If the object is absolute, we'll have to get the parent's Z buffered, // and add to that! GUI<float>::SetSetting(object, "z", pParent->GetBufferedZ() + 10.f, true); else // If the object is relative, then we'll just store Z as "10" GUI<float>::SetSetting(object, "z", 10.f, true); } try { if (pParent == m_BaseObject) AddObject(object); else pParent->AddChild(object); } catch (PSERROR_GUI& e) { LOGERROR("GUI error: %s", e.what()); } }