Index: ps/trunk/binaries/data/mods/mod/gui/gui.rnc =================================================================== --- ps/trunk/binaries/data/mods/mod/gui/gui.rnc (revision 20074) +++ ps/trunk/binaries/data/mods/mod/gui/gui.rnc (revision 20075) @@ -1,283 +1,284 @@ namespace a = "http://relaxng.org/ns/compatibility/annotations/1.0" ## # NOTE: To modify this Relax NG grammar, edit the Relax NG Compact (.rnc) file # and use a converter tool like trang to generate the Relax NG XML (.rng) file ## start = object | objects | setup | sprites | styles ## # Types # ## # xsd:boolean could be used instead of this definition, # though it considers "1" & "0" as valid values. bool = "true" | "false" align = "left" | "center" | "right" valign = "top" | "center" | "bottom" wrapmode = "repeat" | "mirrored_repeat" | "clamp_to_edge" coord = xsd:string { pattern = "-?\d*\.?\d+%?([\+\-]\d*\.?\d+%?)*" } clientarea = list { coord, coord, coord, coord } # color can be a name or "R G B A" format string rgba = list { xsd:integer { minInclusive = "0" maxInclusive = "255" }, xsd:integer { minInclusive = "0" maxInclusive = "255" }, xsd:integer { minInclusive = "0" maxInclusive = "255" }, xsd:integer { minInclusive = "0" maxInclusive = "255" }?} ccolor = rgba | xsd:string { pattern = "[A-Za-z]+" } size = list { xsd:decimal, xsd:decimal } pos = list { xsd:decimal, xsd:decimal } rect = list { xsd:decimal, xsd:decimal, xsd:decimal, xsd:decimal } ## # Defines # ## unique_settings = attribute name { text }?, [ a:defaultValue = "empty" ] attribute type { text }?, attribute style { text }? # This could probably be made more specific/strict # with more information regarding the use/meaning # of these attributes. base_settings = attribute absolute { bool }?& attribute enable { bool }?& attribute ghost { bool }?& attribute hidden { bool }?& attribute size { clientarea }?& attribute z { xsd:decimal }? # Defaults are not put in here, because it ruins the concept of styles. ex_settings = attribute anchor { valign }?& attribute buffer_zone { xsd:decimal }?& attribute buffer_width { xsd:decimal }?& attribute button_width { xsd:decimal }?& attribute checked { bool }?& attribute clip { bool }?& attribute dropdown_size { xsd:decimal }?& attribute dropdown_buffer { xsd:decimal }?& attribute enabled { bool }?& attribute font { text }?& attribute fov_wedge_color { ccolor }?& attribute heading_height { text }?& attribute hotkey { text }?& attribute cell_id { xsd:integer }?& attribute independent { bool }?& attribute input_initvalue_destroyed_at_focus { bool }?& attribute mask { bool }?& attribute mask_char { xsd:string { minLength = "1" maxLength = "1" } }?& attribute max_length { xsd:nonNegativeInteger }?& attribute maxwidth { xsd:decimal }? & attribute multiline { bool }?& attribute offset { pos }?& + attribute readonly { bool }?& attribute scrollbar { bool }?& attribute scrollbar_style { text }?& attribute scroll_bottom { bool }?& attribute scroll_top { bool }?& attribute selected_column { text }?& attribute selected_column_order { text }?& attribute sortable { bool }?& attribute sound_closed { text }?& attribute sound_disabled { text }?& attribute sound_enter { text }?& attribute sound_leave { text }?& attribute sound_opened { text }?& attribute sound_pressed { text }?& attribute sound_selected { text }?& attribute sprite { text }?& attribute sprite2 { text }?& attribute sprite_asc { text }?& attribute sprite_heading { text }?& attribute sprite_bar { text }?& attribute sprite_background { text }?& attribute sprite_desc { text }?& attribute sprite_disabled { text }?& attribute sprite_list { text }?& attribute sprite2_disabled { text }?& attribute sprite_not_sorted { text }?& attribute sprite_over { text }?& attribute sprite2_over { text }?& attribute sprite_pressed { text }?& attribute sprite2_pressed { text }?& attribute sprite_selectarea { text }?& attribute square_side { xsd:decimal }?& attribute textcolor { ccolor }?& attribute textcolor_disabled { ccolor }?& attribute textcolor_over { ccolor }?& attribute textcolor_pressed { ccolor }?& attribute textcolor_selected { ccolor }?& attribute text_align { align }?& attribute text_valign { valign }?& attribute tooltip { text }?& attribute tooltip_style { text }? ## # Objects # ## objects = element objects { (script | object)* } script = element script { text & attribute file { text }? & attribute directory { text }? } object = element object { ((object | action | \attribute | column | \include | item | repeat | translatableAttribute)* | text), unique_settings, base_settings, ex_settings } action = element action { text, attribute on { text }, attribute file { text }? } \attribute = element attribute { (keep | translate)*, attribute id { text } } column = element column { translatableAttribute?, ( attribute id { text }& attribute color { ccolor }?& attribute heading { text }?& attribute width { text }?& attribute hidden { bool }? ) } \include = element include { attribute file { text }| attribute directory { text } } item = element item { text, attribute enabled { bool }? } keep = element keep { text } repeat = element repeat { object+, attribute count { xsd:nonNegativeInteger }, attribute var { text }? } translate = element translate { text } translatableAttribute = element translatableAttribute { text, ( attribute id { text }& attribute comment { text }?& attribute context { text }? ) } ## # Styles # ## styles = element styles { style* } style = element style { attribute name { text }, base_settings, ex_settings } ## # Setup # ## setup = element setup { (icon | scrollbar | tooltip | color)* } scrollbar = element scrollbar { attribute name { text }& attribute width { xsd:decimal }& attribute alwaysshown { bool }?& attribute maximum_bar_size { xsd:decimal }?& attribute minimum_bar_size { xsd:decimal }?& attribute scroll_wheel { bool }?& attribute show_edge_buttons { bool }?& attribute sprite_button_top { text }?& attribute sprite_button_top_pressed { text }?& attribute sprite_button_top_disabled { text }?& attribute sprite_button_top_over { text }?& attribute sprite_button_bottom { text }?& attribute sprite_button_bottom_pressed { text }?& attribute sprite_button_bottom_disabled { text }?& attribute sprite_button_bottom_over { text }?& attribute sprite_bar_vertical { text }?& attribute sprite_bar_vertical_over { text }?& attribute sprite_bar_vertical_pressed { text }?& attribute sprite_back_vertical { text }? } icon = element icon { attribute name { text }& attribute size { size }& attribute sprite { text }& attribute cell_id { text }? } tooltip = element tooltip { attribute name { text }& attribute sprite { text }?& attribute anchor { valign }?& attribute buffer_zone { xsd:decimal }?& attribute font { text }?& attribute maxwidth { xsd:decimal }?& attribute offset { pos }?& attribute textcolor { ccolor }?& attribute delay { xsd:integer }?& attribute use_object { text }?& attribute hide_object { bool }? } color = element color { rgba, attribute name { text } } ## # Sprites # ## sprites = element sprites { sprite* } sprite = element sprite { (effect?, image+), attribute name { text } } image = element image { effect?, ( attribute texture { text }?& attribute size { clientarea }?& attribute texture_size { clientarea }?& attribute real_texture_placement { rect }?& attribute cell_size { size }?& attribute backcolor { ccolor }?& attribute bordercolor { ccolor }?& attribute border { bool }?& attribute z_level { xsd:float }?& attribute fixed_h_aspect_ratio { xsd:decimal }?& attribute round_coordinates { bool }?& attribute wrap_mode { wrapmode }? ) } effect = element effect { attribute add_color { ccolor }?, attribute multiply_color { ccolor }?, attribute grayscale { empty }? } Index: ps/trunk/binaries/data/mods/mod/gui/gui.rng =================================================================== --- ps/trunk/binaries/data/mods/mod/gui/gui.rng (revision 20074) +++ ps/trunk/binaries/data/mods/mod/gui/gui.rng (revision 20075) @@ -1,857 +1,862 @@ true false left center right top center bottom repeat mirrored_repeat clamp_to_edge -?\d*\.?\d+%?([\+\-]\d*\.?\d+%?)* 0 255 0 255 0 255 0 255 [A-Za-z]+ 1 1 + + + + + Index: ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.js (revision 20074) +++ ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.js (revision 20075) @@ -1,356 +1,356 @@ /** * Used for checking replay compatibility. */ const g_EngineInfo = Engine.GetEngineInfo(); /** * Needed for formatPlayerInfo to show the player civs in the details. */ const g_CivData = loadCivData(false, false); /** * Used for creating the mapsize filter. */ const g_MapSizes = prepareForDropdown(g_Settings && g_Settings.MapSizes); /** * All replays found in the directory. */ var g_Replays = []; /** * List of replays after applying the display filter. */ var g_ReplaysFiltered = []; /** * Array of unique usernames of all replays. Used for autocompleting usernames. */ var g_Playernames = []; /** * Sorted list of unique maptitles. Used by mapfilter. */ var g_MapNames = []; /** * Sorted list of the victory conditions occuring in the replays */ var g_VictoryConditions = []; /** * Directory name of the currently selected replay. Used to restore the selection after changing filters. */ var g_SelectedReplayDirectory = ""; /** * Skip duplicate expensive GUI updates before init is complete. */ var g_ReplaysLoaded = false; /** * Initializes globals, loads replays and displays the list. */ function init(data) { if (!g_Settings) { Engine.SwitchGuiPage("page_pregame.xml"); return; } loadReplays(data && data.replaySelectionData, false); if (!g_Replays) { Engine.SwitchGuiPage("page_pregame.xml"); return; } initHotkeyTooltips(); displayReplayList(); } /** * Store the list of replays loaded in C++ in g_Replays. * Check timestamp and compatibility and extract g_Playernames, g_MapNames, g_VictoryConditions. * Restore selected filters and item. * @param replaySelectionData - Currently selected filters and item to be restored after the loading. * @param compareFiles - If true, compares files briefly (which might be slow with optical harddrives), * otherwise blindly trusts the replay cache. */ function loadReplays(replaySelectionData, compareFiles) { g_Replays = Engine.GetReplays(compareFiles); if (!g_Replays) return; g_Playernames = []; for (let replay of g_Replays) { let nonAIPlayers = 0; // Check replay for compatibility replay.isCompatible = isReplayCompatible(replay); sanitizeGameAttributes(replay.attribs); // Extract map names if (g_MapNames.indexOf(replay.attribs.settings.Name) == -1 && replay.attribs.settings.Name != "") g_MapNames.push(replay.attribs.settings.Name); // Extract victory conditions if (replay.attribs.settings.GameType && g_VictoryConditions.indexOf(replay.attribs.settings.GameType) == -1) g_VictoryConditions.push(replay.attribs.settings.GameType); // Extract playernames for (let playerData of replay.attribs.settings.PlayerData) { if (!playerData || playerData.AI) continue; // Remove rating from nick let playername = playerData.Name; let ratingStart = playername.indexOf(" ("); if (ratingStart != -1) playername = playername.substr(0, ratingStart); if (g_Playernames.indexOf(playername) == -1) g_Playernames.push(playername); ++nonAIPlayers; } replay.isMultiplayer = nonAIPlayers > 1; replay.isRated = nonAIPlayers == 2 && replay.attribs.settings.PlayerData.length == 2 && replay.attribs.settings.RatingEnabled; } g_MapNames.sort(); g_VictoryConditions.sort(); // Reload filters (since they depend on g_Replays and its derivatives) initFilters(replaySelectionData && replaySelectionData.filters); // Restore user selection if (replaySelectionData) { if (replaySelectionData.directory) g_SelectedReplayDirectory = replaySelectionData.directory; let replaySelection = Engine.GetGUIObjectByName("replaySelection"); if (replaySelectionData.column) replaySelection.selected_column = replaySelectionData.column; if (replaySelectionData.columnOrder) replaySelection.selected_column_order = replaySelectionData.columnOrder; } g_ReplaysLoaded = true; } /** * We may encounter malformed replays. */ function sanitizeGameAttributes(attribs) { if (!attribs.settings) attribs.settings = {}; if (!attribs.settings.Size) attribs.settings.Size = -1; if (!attribs.settings.Name) attribs.settings.Name = ""; if (!attribs.settings.PlayerData) attribs.settings.PlayerData = []; if (!attribs.settings.PopulationCap) attribs.settings.PopulationCap = 300; if (!attribs.settings.mapType) attribs.settings.mapType = "skirmish"; if (!attribs.settings.GameType) attribs.settings.GameType = "conquest"; // Remove gaia if (attribs.settings.PlayerData.length && attribs.settings.PlayerData[0] == null) attribs.settings.PlayerData.shift(); attribs.settings.PlayerData.forEach((pData, index) => { if (!pData.Name) pData.Name = ""; }); } function initHotkeyTooltips() { Engine.GetGUIObjectByName("playersFilter").tooltip = translate("Filter replays by typing one or more, partial or complete playernames.") + " " + colorizeAutocompleteHotkey(); Engine.GetGUIObjectByName("deleteReplayButton").tooltip = deleteTooltip(); } /** * Filter g_Replays, fill the GUI list with that data and show the description of the current replay. */ function displayReplayList() { if (!g_ReplaysLoaded) return; // Remember previously selected replay var replaySelection = Engine.GetGUIObjectByName("replaySelection"); if (replaySelection.selected != -1) g_SelectedReplayDirectory = g_ReplaysFiltered[replaySelection.selected].directory; filterReplays(); var list = g_ReplaysFiltered.map(replay => { let works = replay.isCompatible; return { "directories": replay.directory, "months": compatibilityColor(getReplayDateTime(replay), works), "popCaps": compatibilityColor(translatePopulationCapacity(replay.attribs.settings.PopulationCap), works), "mapNames": compatibilityColor(getReplayMapName(replay), works), "mapSizes": compatibilityColor(translateMapSize(replay.attribs.settings.Size), works), "durations": compatibilityColor(getReplayDuration(replay), works), "playerNames": compatibilityColor(getReplayPlayernames(replay), works) }; }); if (list.length) list = prepareForDropdown(list); // Push to GUI replaySelection.selected = -1; replaySelection.list_months = list.months || []; replaySelection.list_players = list.playerNames || []; replaySelection.list_mapName = list.mapNames || []; replaySelection.list_mapSize = list.mapSizes || []; replaySelection.list_popCapacity = list.popCaps || []; replaySelection.list_duration = list.durations || []; // Change these last, otherwise crash replaySelection.list = list.directories || []; replaySelection.list_data = list.directories || []; replaySelection.selected = replaySelection.list.findIndex(directory => directory == g_SelectedReplayDirectory); displayReplayDetails(); } /** * Shows preview image, description and player text in the right panel. */ function displayReplayDetails() { let selected = Engine.GetGUIObjectByName("replaySelection").selected; let replaySelected = selected > -1; Engine.GetGUIObjectByName("replayInfo").hidden = !replaySelected; Engine.GetGUIObjectByName("replayInfoEmpty").hidden = replaySelected; Engine.GetGUIObjectByName("startReplayButton").enabled = replaySelected; Engine.GetGUIObjectByName("deleteReplayButton").enabled = replaySelected; Engine.GetGUIObjectByName("replayFilename").hidden = !replaySelected; Engine.GetGUIObjectByName("summaryButton").hidden = true; if (!replaySelected) return; let replay = g_ReplaysFiltered[selected]; Engine.GetGUIObjectByName("sgMapName").caption = translate(replay.attribs.settings.Name); Engine.GetGUIObjectByName("sgMapSize").caption = translateMapSize(replay.attribs.settings.Size); Engine.GetGUIObjectByName("sgMapType").caption = translateMapType(replay.attribs.settings.mapType); Engine.GetGUIObjectByName("sgVictory").caption = translateVictoryCondition(replay.attribs.settings.GameType); Engine.GetGUIObjectByName("sgNbPlayers").caption = sprintf(translate("Players: %(numberOfPlayers)s"), { "numberOfPlayers": replay.attribs.settings.PlayerData.length }); - Engine.GetGUIObjectByName("replayFilename").caption = escapeText(Engine.GetReplayDirectoryName(replay.directory)); + Engine.GetGUIObjectByName("replayFilename").caption = Engine.GetReplayDirectoryName(replay.directory); let metadata = Engine.GetReplayMetadata(replay.directory); Engine.GetGUIObjectByName("sgPlayersNames").caption = formatPlayerInfo( replay.attribs.settings.PlayerData, Engine.GetGUIObjectByName("showSpoiler").checked && metadata && metadata.playerStates && metadata.playerStates.map(pState => pState.state) ); let mapData = getMapDescriptionAndPreview(replay.attribs.settings.mapType, replay.attribs.map); Engine.GetGUIObjectByName("sgMapDescription").caption = mapData.description; Engine.GetGUIObjectByName("summaryButton").hidden = !Engine.HasReplayMetadata(replay.directory); setMapPreviewImage("sgMapPreview", mapData.preview); } /** * Returns a human-readable version of the replay date. */ function getReplayDateTime(replay) { return Engine.FormatMillisecondsIntoDateStringLocal(replay.attribs.timestamp * 1000, translate("yyyy-MM-dd HH:mm")); } /** * Returns a human-readable list of the playernames of that replay. * * @returns {string} */ function getReplayPlayernames(replay) { return replay.attribs.settings.PlayerData.map(pData => pData.Name).join(", "); } /** * Returns the name of the map of the given replay. * * @returns {string} */ function getReplayMapName(replay) { return translate(replay.attribs.settings.Name); } /** * Returns the month of the given replay in the format "yyyy-MM". * * @returns {string} */ function getReplayMonth(replay) { return Engine.FormatMillisecondsIntoDateStringLocal(replay.attribs.timestamp * 1000, translate("yyyy-MM")); } /** * Returns a human-readable version of the time when the replay started. * * @returns {string} */ function getReplayDuration(replay) { return timeToString(replay.duration * 1000); } /** * True if we can start the given replay with the currently loaded mods. */ function isReplayCompatible(replay) { return replayHasSameEngineVersion(replay) && hasSameMods(replay.attribs, g_EngineInfo); } /** * True if we can start the given replay with the currently loaded mods. */ function replayHasSameEngineVersion(replay) { return replay.attribs.engine_version && replay.attribs.engine_version == g_EngineInfo.engine_version; } Index: ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.xml =================================================================== --- ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.xml (revision 20074) +++ ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.xml (revision 20075) @@ -1,270 +1,270 @@