Index: ps/trunk/binaries/data/config/default.cfg
===================================================================
--- ps/trunk/binaries/data/config/default.cfg (revision 15676)
+++ ps/trunk/binaries/data/config/default.cfg (revision 15677)
@@ -1,376 +1,379 @@
; Global Configuration Settings
;
; **************************************************************
; * DO NOT EDIT THIS FILE if you want personal customisations: *
; * create a text file called "local.cfg" instead, and copy *
; * the lines from this file that you want to change. *
; * *
; * On Linux, create: *
; * $XDG_CONFIG_HOME/0ad/config/local.cfg *
; * (Note: $XDG_CONFIG_HOME defaults to ~/.config) *
; * *
; * On OS X, create: *
; * ~/Library/Application\ Support/0ad/config/local.cfg *
; * *
; * On Windows, create: *
; * %appdata%\0ad\config\local.cfg *
; * *
; **************************************************************
; Enable/disable windowed mode by default. (Use Alt+Enter to toggle in the game.)
windowed = false
; Show detailed tooltips (Unit stats)
showdetailedtooltips = false
; Enable/disable the splashscreen
splashscreendisable = false
; Splashscreen version (date of last modification). By default, 0 to force splashscreen to appear at first launch.
splashscreenversion = 0
; Pause the game on window focus loss (Only applicable to single player mode)
pauseonfocusloss = true
; Default player name to use in multiplayer
; playername = "anonymous"
; Default server name or IP to use in multiplayer
multiplayerserver = "127.0.0.1"
; Force a particular resolution. (If these are 0, the default is
; to keep the current desktop resolution in fullscreen mode or to
; use 1024x768 in windowed mode.)
xres = 0
yres = 0
; Force a non-standard bit depth (if 0 then use the current desktop bit depth)
bpp = 0
; System settings:
; if false, actors won't be rendered but anything entity will be.
renderactors = true
waterugly=false; Force usage of the fixed pipeline water. This is faster, but really, really ugly.
waterfancyeffects = false
waterrealdepth = true
waterrefraction = true
waterreflection = true
shadowsonwater = false
shadows = true
shadowpcf = true
vsync = false
particles = true
silhouettes = true
showsky = false
nos3tc = false
noautomipmap = true
novbo = false
noframebufferobject = false
; Disable hardware cursors
nohwcursor = false
; Linux only: Set the driconf force_s3tc_enable option at startup,
; for compressed texture support
force_s3tc_enable = true
; Specify the render path. This can be one of:
; default Automatically select one of the below, depending on system capabilities
; fixed Only use OpenGL fixed function pipeline
; shader Use vertex/fragment shaders for transform and lighting where possible
; Using 'fixed' instead of 'default' may work around some graphics-related problems,
; but will reduce performance and features when a modern graphics card is available.
renderpath = default
;;;;; EXPERIMENTAL ;;;;;
; Prefer GLSL shaders over ARB shaders (not recommended). REQUIRES gentangents=true.
preferglsl = false
; Generate tangents for normal and parallax mapping. REQUIRES preferglsl=true.
gentangents = false
; Experimental probably-non-working GPU skinning support; requires preferglsl; use at own risk
gpuskinning = false
; Use smooth LOS interpolation; REQUIRES preferglsl=true.
smoothlos = false
; Use screen-space postprocessing filters (HDR, bloom, DOF, etc). Incompatible with fixed renderpath.
postproc = false
; Quality level of shader effects (set to 10 to display effects)
materialmgr.quality = 0.0
; Maximum distance to display parallax effect. Set to 0 to disable parallax.
materialmgr.PARALLAX_DIST.max = 150
; Maximum distance to display high quality parallax effect.
materialmgr.PARALLAX_HQ_DIST.max = 75
; Maximum distance to display very high quality parallax effect. Set to 30 to enable.
materialmgr.PARALLAX_VHQ_DIST.max = 0
;;;;;;;;;;;;;;;;;;;;;;;;
; Replace alpha-blending with alpha-testing, for performance experiments
forcealphatest = false
; Opt-in online user reporting system
userreport.url = "http://feedback.wildfiregames.com/report/upload/v1/"
; Colour of the sky (in "r g b" format)
skycolor = "0 0 0"
; GENERAL PREFERENCES:
sound.mastergain = 0.9
sound.musicgain = 0.2
sound.ambientgain = 0.6
sound.actiongain = 0.7
sound.uigain = 0.7
; Camera control settings
view.scroll.speed = 120.0
view.scroll.speed.modifier = 1.05 ; Multiplier for changing scroll speed
view.rotate.x.speed = 1.2
view.rotate.x.min = 28.0
view.rotate.x.max = 60.0
view.rotate.x.default = 35.0
view.rotate.y.speed = 2.0
view.rotate.y.speed.wheel = 0.45
view.rotate.y.default = 0.0
view.rotate.speed.modifier = 1.05 ; Multiplier for changing rotation speed
view.drag.speed = 0.5
view.zoom.speed = 256.0
view.zoom.speed.wheel = 32.0
view.zoom.min = 50.0
view.zoom.max = 200.0
view.zoom.default = 120.0
view.zoom.speed.modifier = 1.05 ; Multiplier for changing zoom speed
view.pos.smoothness = 0.1
view.zoom.smoothness = 0.4
view.rotate.x.smoothness = 0.5
view.rotate.y.smoothness = 0.3
view.near = 2.0 ; Near plane distance
view.far = 4096.0 ; Far plane distance
view.fov = 45.0 ; Field of view (degrees), lower is narrow, higher is wide
view.height.smoothness = 0.5
view.height.min = 16
; How close do we have to be to the actual location in order to jump back to the previous one?
camerajump.threshold = 40
; HOTKEY MAPPINGS:
; Each one of the specified keys will trigger the action on the left
; for multiple-key combinations, separate keys with '+' and enclose the entire thing
; in doublequotes.
; See keys.txt for the list of key names.
; > SYSTEM SETTINGS
hotkey.exit = "Alt+F4", "Ctrl+Break", "Super+Q" ; Exit to desktop
hotkey.leave = Escape ; End current game or Exit
hotkey.confirm = Return ; Confirm the current command.
hotkey.pause = Pause ; Pause/unpause game
hotkey.screenshot = F2 ; Take PNG screenshot
hotkey.bigscreenshot = "Shift+F2" ; Take large BMP screenshot
hotkey.togglefullscreen = "Alt+Return" ; Toggle fullscreen/windowed mode
hotkey.screenshot.watermark = "Alt+K" ; Toggle product/company watermark for official screenshots
hotkey.wireframe = "Alt+W" ; Toggle wireframe mode
hotkey.silhouettes = "Alt+S" ; Toggle unit silhouettes
hotkey.showsky = "Alt+Z" ; Toggle sky
; > CAMERA SETTINGS
hotkey.camera.reset = "R" ; Reset camera rotation to default.
hotkey.camera.follow = "F" ; Follow the first unit in the selection
hotkey.camera.zoom.in = Plus, Equals, NumPlus ; Zoom camera in (continuous control)
hotkey.camera.zoom.out = Minus, NumMinus ; Zoom camera out (continuous control)
hotkey.camera.zoom.wheel.in = WheelUp ; Zoom camera in (stepped control)
hotkey.camera.zoom.wheel.out = WheelDown ; Zoom camera out (stepped control)
hotkey.camera.rotate.up = "Ctrl+UpArrow", "Ctrl+W" ; Rotate camera to look upwards
hotkey.camera.rotate.down = "Ctrl+DownArrow", "Ctrl+S" ; Rotate camera to look downwards
hotkey.camera.rotate.cw = "Ctrl+LeftArrow", "Ctrl+A", Q ; Rotate camera clockwise around terrain
hotkey.camera.rotate.ccw = "Ctrl+RightArrow", "Ctrl+D", E ; Rotate camera anticlockwise around terrain
hotkey.camera.rotate.wheel.cw = "Shift+WheelUp", MouseX1 ; Rotate camera clockwise around terrain (stepped control)
hotkey.camera.rotate.wheel.ccw = "Shift+WheelDown", MouseX2 ; Rotate camera anticlockwise around terrain (stepped control)
hotkey.camera.pan = MouseMiddle ; Enable scrolling by moving mouse
hotkey.camera.left = A, LeftArrow ; Scroll or rotate left
hotkey.camera.right = D, RightArrow ; Scroll or rotate right
hotkey.camera.up = W, UpArrow ; Scroll or rotate up/forwards
hotkey.camera.down = S, DownArrow ; Scroll or rotate down/backwards
hotkey.camera.scroll.speed.increase = "Ctrl+Shift+S" ; Increase scroll speed
hotkey.camera.scroll.speed.decrease = "Ctrl+Alt+S" ; Decrease scroll speed
hotkey.camera.rotate.speed.increase = "Ctrl+Shift+R" ; Increase rotation speed
hotkey.camera.rotate.speed.decrease = "Ctrl+Alt+R" ; Decrease rotation speed
hotkey.camera.zoom.speed.increase = "Ctrl+Shift+Z" ; Increase zoom speed
hotkey.camera.zoom.speed.decrease = "Ctrl+Alt+Z" ; Decrease zoom speed
hotkey.camera.jump.1 = F5 ; Jump to position N
hotkey.camera.jump.2 = F6
hotkey.camera.jump.3 = F7
hotkey.camera.jump.4 = F8
;hotkey.camera.jump.5 =
;hotkey.camera.jump.6 =
;hotkey.camera.jump.7 =
;hotkey.camera.jump.8 =
;hotkey.camera.jump.9 =
;hotkey.camera.jump.10 =
hotkey.camera.jump.set.1 = "Ctrl+F5" ; Set jump position N
hotkey.camera.jump.set.2 = "Ctrl+F6"
hotkey.camera.jump.set.3 = "Ctrl+F7"
hotkey.camera.jump.set.4 = "Ctrl+F8"
;hotkey.camera.jump.set.5 =
;hotkey.camera.jump.set.6 =
;hotkey.camera.jump.set.7 =
;hotkey.camera.jump.set.8 =
;hotkey.camera.jump.set.9 =
;hotkey.camera.jump.set.10 =
; > CONSOLE SETTINGS
hotkey.console.toggle = BackQuote, F9 ; Open/close console
; > CLIPBOARD CONTROLS
hotkey.copy = "Ctrl+C" ; Copy to clipboard
hotkey.paste = "Ctrl+V" ; Paste from clipboard
hotkey.cut = "Ctrl+X" ; Cut selected text and copy to the clipboard
; > ENTITY SELECTION
hotkey.selection.add = Shift ; Add units to selection
hotkey.selection.milonly = Alt ; Add only military units to selection
hotkey.selection.remove = Ctrl ; Remove units from selection
hotkey.selection.cancel = Esc ; Un-select all units and cancel building placement
hotkey.selection.idleworker = Period ; Select next idle worker
hotkey.selection.idlewarrior = ForwardSlash ; Select next idle warrior
hotkey.selection.offscreen = Alt ; Include offscreen units in selection
hotkey.selection.group.select.0 = 0
hotkey.selection.group.save.0 = "Ctrl+0"
hotkey.selection.group.add.0 = "Shift+0"
hotkey.selection.group.select.1 = 1
hotkey.selection.group.save.1 = "Ctrl+1"
hotkey.selection.group.add.1 = "Shift+1"
hotkey.selection.group.select.2 = 2
hotkey.selection.group.save.2 = "Ctrl+2"
hotkey.selection.group.add.2 = "Shift+2"
hotkey.selection.group.select.3 = 3
hotkey.selection.group.save.3 = "Ctrl+3"
hotkey.selection.group.add.3 = "Shift+3"
hotkey.selection.group.select.4 = 4
hotkey.selection.group.save.4 = "Ctrl+4"
hotkey.selection.group.add.4 = "Shift+4"
hotkey.selection.group.select.5 = 5
hotkey.selection.group.save.5 = "Ctrl+5"
hotkey.selection.group.add.5 = "Shift+5"
hotkey.selection.group.select.6 = 6
hotkey.selection.group.save.6 = "Ctrl+6"
hotkey.selection.group.add.6 = "Shift+6"
hotkey.selection.group.select.7 = 7
hotkey.selection.group.save.7 = "Ctrl+7"
hotkey.selection.group.add.7 = "Shift+7"
hotkey.selection.group.select.8 = 8
hotkey.selection.group.save.8 = "Ctrl+8"
hotkey.selection.group.add.8 = "Shift+8"
hotkey.selection.group.select.9 = 9
hotkey.selection.group.save.9 = "Ctrl+9"
hotkey.selection.group.add.9 = "Shift+9"
; > SESSION CONTROLS
hotkey.session.kill = Delete ; Destroy selected units
hotkey.session.stop = "H" ; Stop the current action
hotkey.session.attack = "Ctrl+Alt" ; Modifier to force attack instead of another action
hotkey.session.attackmove = Ctrl ; Modifier to attackmove when clicking on a point
hotkey.session.attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when clicking on a point
hotkey.session.garrison = Ctrl ; Modifier to garrison when clicking on building
hotkey.session.autorallypoint = Ctrl ; Modifier to set the rally point on the building itself
hotkey.session.guard = "G" ; Modifier to escort/guard when clicking on unit/building
hotkey.session.queue = Shift ; Modifier to queue unit orders instead of replacing
hotkey.session.batchtrain = Shift ; Modifier to train units in batches
hotkey.session.massbarter = Shift ; Modifier to barter bunch of resources
hotkey.session.masstribute = Shift ; Modifier to tribute bunch of resources
hotkey.session.fulltradeswap = Shift ; Modifier to put the desired trade resource to 100%
hotkey.session.unloadtype = Shift ; Modifier to unload all units of type
hotkey.session.deselectgroup = Ctrl ; Modifier to deselect units when clicking group icon, instead of selecting
hotkey.session.rotate.cw = RightBracket ; Rotate building placement preview clockwise
hotkey.session.rotate.ccw = LeftBracket ; Rotate building placement preview anticlockwise
hotkey.timewarp.fastforward = Space ; If timewarp mode enabled, speed up the game
hotkey.timewarp.rewind = Backspace ; If timewarp mode enabled, go back to earlier point in the game
; > UNIT TRAINING
hotkey.session.queueunit.1 = "Z" ; add first unit type to queue
hotkey.session.queueunit.2 = "X" ; add second unit type to queue
hotkey.session.queueunit.3 = "C" ; add third unit type to queue
hotkey.session.queueunit.4 = "V" ; add fourth unit type to queue
hotkey.session.queueunit.5 = "B" ; add fivth unit type to queue
hotkey.session.queueunit.6 = "N" ; add sixth unit type to queue
hotkey.session.queueunit.7 = "M" ; add seventh unit type to queue
hotkey.session.queueunit.8 = Comma ; add eighth unit type to queue
; > OVERLAY KEYS
hotkey.fps.toggle = "Alt+F" ; Toggle frame counter
hotkey.realtime.toggle = "Alt+T" ; Toggle current display of computer time
hotkey.session.devcommands.toggle = "Alt+D" ; Toggle developer commands panel
hotkey.session.gui.toggle = "Alt+G" ; Toggle visibility of session GUI
hotkey.menu.toggle = "F10" ; Toggle in-game menu
hotkey.timeelapsedcounter.toggle = "F12" ; Toggle time elapsed counter
hotkey.session.showstatusbars = Tab ; Toggle display of status bars
hotkey.session.highlightguarding = PgDn ; Toggle highlight of guarding units
hotkey.session.highlightguarded = PgUp ; Toggle highlight of guarded units
; > HOTKEYS ONLY
hotkey.chat = Return ; Toggle chat window
hotkey.teamchat = "T" ; Toggle chat window in team chat mode
; > GUI TEXTBOX HOTKEYS
hotkey.text.delete.left = "Ctrl+Backspace" ; Delete word to the left of cursor
hotkey.text.delete.right = "Ctrl+Del" ; Delete word to the right of cursor
hotkey.text.move.left = "Ctrl+LeftArrow" ; Move cursor to start of word to the left of cursor
hotkey.text.move.right = "Ctrl+RightArrow" ; Move cursor to start of word to the right of cursor
; > PROFILER
hotkey.profile.toggle = "F11" ; Enable/disable real-time profiler
hotkey.profile.save = "Shift+F11" ; Save current profiler data to logs/profile.txt
hotkey.profile2.enable = "F11" ; Enable HTTP/GPU modes for new profiler
profiler2.http.autoenable = false ; Enable HTTP server output at startup (default off for security/performance)
profiler2.script.enable = false ; Enable Javascript profiling. Needs to be set before startup and can't be changed later. (default off for performance)
profiler2.gpu.autoenable = false ; Enable GPU timing at startup (default off for performance/compatibility)
profiler2.gpu.arb.enable = true ; Allow GL_ARB_timer_query timing mode when available
profiler2.gpu.ext.enable = true ; Allow GL_EXT_timer_query timing mode when available
profiler2.gpu.intel.enable = true ; Allow GL_INTEL_performance_queries timing mode when available
; Developer options
jsdebugger.enable = false ; Enable Javascript debugging. The Debugger is currently broken and can't be enabled until the SpiderMonkey upgrade is done and the debugger is updated for the new API.
tinygettext.debug = false ; Enable Tinygettext debugging information. If true, Tinygettext prints error messages each time that a translation for an English string is not found.
; > QUICKSAVE
hotkey.quicksave = "Shift+F5"
hotkey.quickload = "Shift+F8"
; EXPERIMENTAL: joystick/gamepad settings
joystick.enable = false
joystick.deadzone = 8192
joystick.camera.pan.x = 0
joystick.camera.pan.y = 1
joystick.camera.rotate.x = 3
joystick.camera.rotate.y = 2
joystick.camera.zoom.in = 5
joystick.camera.zoom.out = 4
; SESSION GUI SETTINGS
gui.session.timeelapsedcounter = false ; Show the game duration in the top right corner
gui.session.attacknotificationmessage = true ; Show attack notification messages
gui.session.minimap.pingduration = 50.0 ; The duration for which an entity will be pinged after an attack notification
gui.session.minimap.blinkduration = 1.7 ; The blink duration while pinging
; GENERAL GUI SETTINGS
gui.cursorblinkrate = 0.5 ; Cursor blink rate in seconds (0.0 to disable blinking)
; Multiplayer lobby preferences
lobby.server = "lobby.wildfiregames.com" ; Address of lobby server
lobby.xpartamupp = "wfgbot17" ; Name of the server-side xmpp client that manage games
lobby.chattimestamp = false ; Show time chat message was posted
lobby.room = "arena17" ; Default MUC room to join
lobby.history = 0 ; Number of past messages to display on join
; Overlay Preferences
overlay.fps = "false" ; Show frames per second in top right corner
overlay.realtime = "false" ; Show current system time in top right corner
+
+; MOD SETTINGS
+;mod.enabledmods = "mod public"
Index: ps/trunk/binaries/data/mods/mod/gui/modmod/modmod.js
===================================================================
--- ps/trunk/binaries/data/mods/mod/gui/modmod/modmod.js (nonexistent)
+++ ps/trunk/binaries/data/mods/mod/gui/modmod/modmod.js (revision 15677)
@@ -0,0 +1,494 @@
+/*
+Example contents of g_mods:
+{
+ "foldername1": { // this is the content of the json file in a specific mod
+ name: "unique_shortname", // eg "0ad", "rote"
+ version: "0.0.16",
+ label: "Nice Mod Name", // eg "0 A.D. - Empires Ascendant"
+ type: "content|functionality|mixed/mod-pack",
+ url: "http://wildfregames.com/",
+ description: "",
+ dependencies: [] // (name({<,<=,==,>=,>}version)?)+
+ },
+ "foldername2": {
+ name: "mod2",
+ label: "Mod 2",
+ version: "1.1",
+ type: "content|functionality|mixed/mod-pack",
+ url: "http://play0ad.wfg.com/",
+ description: "",
+ dependencies: []
+ }
+}
+*/
+
+
+var g_mods = {}; // Contains all JSONs as explained in the structure above
+var g_modsEnabled = []; // folder names
+var g_modsAvailable = []; // folder names
+
+const g_sortByOptions = [translate("Name"), translate("Label"), translate("Folder"), translate("Version")];
+const SORT_BY_NAME = 0;
+const SORT_BY_FOLDER = 1;
+const SORT_BY_LABEL = 2;
+const SORT_BY_VERSION = 3;
+
+var g_modTypes = [translate("Type: Any")];
+
+/**
+ * Fetches the mod lists in JSON from the Engine.
+ * Initiates a first creation of the GUI lists.
+ * Enabled mods are read from the Configuration and checked if still available.
+ */
+function init()
+{
+ g_mods = Engine.GetAvailableMods();
+
+ g_modsEnabled = getExistingModsFromConfig();
+ g_modsAvailable = Object.keys(g_mods).filter(function(i) { return g_modsEnabled.indexOf(i) === -1; });
+
+ Engine.GetGUIObjectByName("negateFilter").checked = false;
+ Engine.GetGUIObjectByName("modGenericFilter").caption = translate("Filter");
+ Engine.GetGUIObjectByName("modTypeFilter").selected = 0;
+
+ var sortBy = Engine.GetGUIObjectByName("sortBy");
+ sortBy.list = g_sortByOptions;
+ sortBy.selected = SORT_BY_NAME;
+
+ // sort ascending by default
+ Engine.GetGUIObjectByName("isOrderDescending").checked = false;
+
+ generateModsLists();
+
+ Engine.GetGUIObjectByName("message").caption = translate("Message: Mods Loaded.");
+}
+
+/**
+ * Recreating both the available and enabled mods lists.
+ */
+function generateModsLists()
+{
+ generateModsList('modsAvailableList', g_modsAvailable);
+ generateModsList('modsEnabledList', g_modsEnabled);
+}
+
+function saveMods()
+{
+ // always sort mods before saving
+ sortMods();
+ Engine.ConfigDB_CreateValue("user", "mod.enabledmods", ["mod"].concat(g_modsEnabled).join(" "));
+ Engine.ConfigDB_WriteFile("user", "config/user.cfg");
+}
+
+function startMods()
+{
+ // always sort mods before starting
+ sortMods();
+ Engine.SetMods(["mod"].concat(g_modsEnabled));
+ Engine.RestartEngine();
+}
+
+function getExistingModsFromConfig()
+{
+ var existingMods = [];
+
+ var mods = [];
+ var cfgMods = Engine.ConfigDB_GetValue("user", "mod.enabledmods");
+ if (cfgMods.length > 0)
+ mods = cfgMods.split(/\s+/);
+
+ mods.forEach(function(mod) {
+ if (mod in g_mods)
+ existingMods.push(mod);
+ });
+
+ return existingMods;
+}
+
+/**
+ * (Re-)Generate List of all mods.
+ * @param listObjectName The GUI object's name (e.g. "modsEnabledList", "modsAvailableList")
+ */
+function generateModsList(listObjectName, mods)
+{
+ var sortBy = Engine.GetGUIObjectByName("sortBy");
+ var orderDescending = Engine.GetGUIObjectByName("isOrderDescending");
+ var isDescending = orderDescending && orderDescending.checked;
+
+ // TODO: Sorting mods by dependencies would be nice
+ if (listObjectName != "modsEnabledList")
+ {
+ var idx = -1;
+ if (sortBy)
+ idx = sortBy.selected;
+
+ switch (idx)
+ {
+ default:
+ warn("generateModsList: invalid index '"+idx+"'"); // fall through
+ // sort by unique name alphanumerically by default:
+ case -1:
+ case SORT_BY_NAME:
+ mods.sort(function(a, b)
+ {
+ var ret = compare(g_mods[a].name.toLowerCase(), g_mods[b].name.toLowerCase());
+ return ret * (isDescending ? -1 : 1);
+ });
+ break;
+ case SORT_BY_FOLDER:
+ mods.sort(function(a, b)
+ {
+ return compare(a.toLowerCase(), b.toLowerCase()) * (isDescending ? -1 : 1);
+ });
+ break;
+ case SORT_BY_LABEL:
+ mods.sort(function(a, b)
+ {
+ var ret = compare(g_mods[a].label.toLowerCase(), g_mods[b].label.toLowerCase());
+ return ret * (isDescending ? -1 : 1);
+ });
+ break;
+ case SORT_BY_VERSION:
+ mods.sort(function(a, b)
+ {
+ // TODO reuse actual logic
+ var ret = compare(g_mods[a].version, g_mods[b].version);
+ return ret * (isDescending ? -1 : 1);
+ });
+ break;
+ }
+ }
+
+ var [names, folders, labels, types, urls, versions, dependencies] = [[],[],[],[],[],[],[]];
+ mods.forEach(function(foldername)
+ {
+ var mod = g_mods[foldername];
+ if (mod.type && g_modTypes.indexOf(mod.type) == -1)
+ g_modTypes.push(mod.type);
+
+ if (filterMod(foldername))
+ return;
+
+ names.push(mod.name);
+ folders.push('[color="45 45 45"](' + foldername + ')[/color]');
+
+ labels.push(mod.label || "");
+ types.push(mod.type || "");
+ urls.push(mod.url || "");
+ versions.push(mod.version || "");
+ dependencies.push((mod.dependencies || []).join(" "));
+ });
+
+ // Update the list
+ var obj = Engine.GetGUIObjectByName(listObjectName);
+ obj.list_name = names;
+ obj.list_modFolderName = folders;
+ obj.list_modLabel = labels;
+ obj.list_modType = types;
+ obj.list_modURL = urls;
+ obj.list_modVersion = versions;
+ obj.list_modDependencies = dependencies;
+
+ obj.list = names;
+
+ var modTypeFilter = Engine.GetGUIObjectByName("modTypeFilter");
+ modTypeFilter.list = g_modTypes;
+}
+
+function compare(a, b)
+{
+ return ( (a > b) ? 1 : (b > a) ? -1 : 0 );
+}
+
+function enableMod()
+{
+ var obj = Engine.GetGUIObjectByName("modsAvailableList");
+ var pos = obj.selected;
+ if (pos === -1)
+ return;
+
+ var mod = g_modsAvailable[pos];
+
+ // Move it to the other table
+ // check dependencies, warn about not satisfied dependencies and abort if so:
+ if (!areDependenciesMet(mod))
+ return;
+
+ g_modsEnabled.push(g_modsAvailable.splice(pos, 1)[0]);
+
+ if (pos >= g_modsAvailable.length)
+ pos--;
+ obj.selected = pos;
+
+ generateModsLists();
+}
+
+function disableMod()
+{
+ var obj = Engine.GetGUIObjectByName("modsEnabledList");
+ var pos = obj.selected;
+ if (pos === -1)
+ return;
+
+ var mod = g_modsEnabled[pos];
+
+ g_modsAvailable.push(g_modsEnabled.splice(pos, 1)[0]);
+
+ // Remove mods that required the removed mod and cascade
+ // Sort them, so we know which ones can depend on the removed mod
+ // TODO: Find position where the removed mod would have fit (for now assume idx 0)
+ sortMods();
+ for (var i = 0; i < g_modsEnabled.length; ++i)
+ {
+ if (!areDependenciesMet(g_modsEnabled[i]))
+ {
+ g_modsAvailable.push(g_modsEnabled.splice(i, 1)[0]);
+ --i;
+ }
+ }
+
+ // select the last element even if more than 1 mod has been removed:
+ if (pos > g_modsEnabled.length - 1)
+ pos = g_modsEnabled.length - 1;
+ obj.selected = pos;
+
+ generateModsLists();
+}
+
+function resetFilters()
+{
+ // Reset states of gui objects.
+ Engine.GetGUIObjectByName("modTypeFilter").selected = 0;
+ Engine.GetGUIObjectByName("negateFilter").checked = false;
+ Engine.GetGUIObjectByName("modGenericFilter").caption = "";
+
+ // NOTE: Calling generateModsLists() is not needed as the selection changes and that calls applyFilters()
+}
+
+function applyFilters()
+{
+ Engine.GetGUIObjectByName("modsAvailableList").selected = -1;
+ Engine.GetGUIObjectByName("modsEnabledList").selected = -1;
+ generateModsLists();
+}
+
+/**
+ * Filter a mod based on the status of the filters.
+ *
+ * @param modFolder Mod to be tested.
+ * @return True if mod should not be displayed.
+ */
+function filterMod(modFolder)
+{
+ var mod = g_mods[modFolder];
+
+ var modTypeFilter = Engine.GetGUIObjectByName("modTypeFilter");
+ var genericFilter = Engine.GetGUIObjectByName("modGenericFilter");
+ var negateFilter = Engine.GetGUIObjectByName("negateFilter");
+
+ // TODO: and result of filters together (type && generic)
+
+ // We assume index 0 means display all for any given filter.
+ if (modTypeFilter.selected > 0
+ && (mod.type || "") != modTypeFilter.list[modTypeFilter.selected])
+ return !negateFilter.checked;
+
+ if (genericFilter && genericFilter.caption && genericFilter.caption != "" && genericFilter.caption != "Filter")
+ {
+ var t = genericFilter.caption;
+ if (modFolder.indexOf(t) === -1
+ && mod.name.indexOf(t) === -1
+ && mod.label.indexOf(t) === -1
+ && (mod.type || "").indexOf(t) === -1
+ && mod.url.indexOf(t) === -1
+ && mod.version.indexOf(t) === -1
+ && mod.description.indexOf(t) === -1
+ && mod.dependencies.indexOf(t) === -1)
+ {
+ return !negateFilter.checked;
+ }
+ }
+
+ return negateFilter.checked;
+}
+
+function closePage()
+{
+ Engine.SwitchGuiPage("page_pregame.xml", {});
+}
+
+
+/**
+ * Moves an item in the list @p objectName up or down depending on the value of @p up.
+ */
+function moveCurrItem(objectName, up)
+{
+ // reuse the check for null and if something is selected.
+ if (getCurrItemValue(objectName) == "")
+ return;
+
+ var idx = Engine.GetGUIObjectByName(objectName).selected;
+ if (idx === -1)
+ return;
+
+ var num = getNumItems(objectName);
+ var idx2 = idx + (up ? -1 : 1);
+ if (idx2 < 0 || idx2 >= num)
+ return;
+
+ var tmp = g_modsEnabled[idx];
+ g_modsEnabled[idx] = g_modsEnabled[idx2];
+ g_modsEnabled[idx2] = tmp;
+
+ // Selected object reached the new position.
+ Engine.GetGUIObjectByName(objectName).list = g_modsEnabled;
+ Engine.GetGUIObjectByName(objectName).selected = idx2;
+ generateModsList('modsEnabledList', g_modsEnabled);
+}
+
+function areDependenciesMet(mod)
+{
+ var guiObject = Engine.GetGUIObjectByName("message");
+ for each (var dependency in g_mods[mod].dependencies)
+ {
+ if (isDependencyMet(dependency))
+ continue;
+ guiObject.caption = '[color="250 100 100"]' + translate(sprintf('Dependency not met: %(dep)s', { "dep": dependency })) +'[/color]';
+ return false;
+ }
+
+ guiObject.caption = '[color="100 250 100"]' + translate('All dependencies met') + '[/color]';
+
+ return true;
+}
+
+/**
+ * @param dependency: Either id (unique modJson.name) and version or only the unique mod name.
+ * Concatenated by either "=", ">", "<", ">=", "<=".
+ */
+function isDependencyMet(dependency_idAndVersion, modsEnabled = null)
+{
+ if (!modsEnabled)
+ modsEnabled = g_modsEnabled;
+
+ // Split on {=,<,<=,>,>=} and use the second part as the version number
+ // and whatever we split on as a way to handle that version.
+ var op = dependency_idAndVersion.match(/(<=|>=|<|>|=)/);
+ // Did the dependency contain a version number?
+ if (op)
+ {
+ op = op[0];
+ var dependency_parts = dependency_idAndVersion.split(op);
+ var dependency_version = dependency_parts[1];
+ var dependency_id = dependency_parts[0];
+ }
+ else
+ var dependency_id = dependency_idAndVersion;
+
+ // modsEnabled_key currently is the mod folder name.
+ for each (var modsEnabled_key in modsEnabled)
+ {
+ var modJson = g_mods[modsEnabled_key];
+ if (modJson.name != dependency_id)
+ continue;
+
+ // There could be another mod with a satisfying version
+ if (!op || versionSatisfied(modJson.version, op, dependency_version))
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Returns true if @p version satisfies @p op (<,<=,=,>=,>) @p requirement.
+ * @note @p version and @p requirement are split on '.' and everything after
+ * '-' or '_' is ignored. Only numbers are supported.
+ * @note "5.3" < "5.3.0"
+ */
+function versionSatisfied(version, op, requirement)
+{
+ var reqList = requirement.split(/[-_]/)[0].split(/\./g);
+ var avList = version.split(/[-_]/)[0].split(/\./g);
+
+ var eq = op.indexOf("=") !== -1;
+ var lt = op.indexOf("<") !== -1;
+ var gt = op.indexOf(">") !== -1;
+ if (!(eq || lt || gt))
+ {
+ warn("No valid compare op");
+ return false;
+ }
+
+ var l = Math.min(reqList.length, avList.length);
+ for (var i = 0; i < l; ++i)
+ {
+ // TODO: Handle NaN
+ var diff = +avList[i] - +reqList[i];
+
+ // Early success
+ if (gt && diff > 0)
+ return true;
+ if (lt && diff < 0)
+ return true;
+
+ // Early failure
+ if (gt && diff < 0)
+ return false;
+ if (lt && diff > 0)
+ return false;
+ if (eq && diff !== 0)
+ return false;
+ }
+ // common prefix matches
+ var ldiff = avList.length - reqList.length;
+ if (ldiff === 0)
+ return eq;
+ // NB: 2.3 != 2.3.0
+ if (ldiff < 0)
+ return lt;
+ if (ldiff > 0)
+ return gt;
+
+ // Can't be reached
+ error("version checking code broken");
+ return false;
+}
+
+function sortMods()
+{
+ // store the list of dependencies per mod, but strip the version numbers
+ var deps = {};
+ for (var mod of g_modsEnabled)
+ {
+ deps[mod] = [];
+ if (!g_mods[mod].dependencies)
+ continue;
+ deps[mod] = g_mods[mod].dependencies.map(function(d) { return d.split(/(<=|>=|<|>|=)/)[0]; });
+ }
+ var sortFunction = function(mod1, mod2)
+ {
+ var name1 = g_mods[mod1].name;
+ var name2 = g_mods[mod2].name;
+ if (deps[mod1].indexOf(name2) != -1)
+ return 1;
+ if (deps[mod2].indexOf(name1) != -1)
+ return -1;
+ return 0;
+ }
+ g_modsEnabled.sort(sortFunction);
+ generateModsList("modsEnabledList", g_modsEnabled);
+}
+
+function showModDescription(listObjectName, mod_keys)
+{
+ var listObject = Engine.GetGUIObjectByName(listObjectName);
+ if (listObject.selected == -1)
+ var desc = '[color="255 100 100"]' + translate("No mod has been selected.") + '[/color]';
+ else
+ {
+ var mod_key = mod_keys[listObject.selected];
+ var desc = g_mods[mod_key].description;
+ }
+
+ Engine.GetGUIObjectByName("globalModDescription").caption = desc;
+}
Index: ps/trunk/binaries/data/mods/mod/gui/modmod/modmod.xml
===================================================================
--- ps/trunk/binaries/data/mods/mod/gui/modmod/modmod.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/mod/gui/modmod/modmod.xml (revision 15677)
@@ -0,0 +1,192 @@
+
+
+
+
+
+
+
Index: ps/trunk/binaries/data/mods/mod/gui/modmod/styles.xml
===================================================================
--- ps/trunk/binaries/data/mods/mod/gui/modmod/styles.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/mod/gui/modmod/styles.xml (revision 15677)
@@ -0,0 +1,15 @@
+
+
+
+
+
Index: ps/trunk/binaries/data/mods/mod/gui/page_modmod.xml
===================================================================
--- ps/trunk/binaries/data/mods/mod/gui/page_modmod.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/mod/gui/page_modmod.xml (revision 15677)
@@ -0,0 +1,21 @@
+
+
+ common/modern/setup.xml
+ common/modern/styles.xml
+ common/modern/sprites.xml
+
+ common/setup.xml
+ common/sprite1.xml
+ common/styles.xml
+ common/common_sprites.xml
+ common/common_styles.xml
+ common/init.xml
+
+ pregame/sprites.xml
+ pregame/styles.xml
+
+ modmod/styles.xml
+ modmod/modmod.xml
+
+ common/global.xml
+
Index: ps/trunk/binaries/data/mods/mod/gui/page_pregame.xml
===================================================================
--- ps/trunk/binaries/data/mods/mod/gui/page_pregame.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/mod/gui/page_pregame.xml (revision 15677)
@@ -0,0 +1,4 @@
+
+
+ pregame/mainmenu.xml
+
Index: ps/trunk/binaries/data/mods/mod/gui/pregame/mainmenu.js
===================================================================
--- ps/trunk/binaries/data/mods/mod/gui/pregame/mainmenu.js (nonexistent)
+++ ps/trunk/binaries/data/mods/mod/gui/pregame/mainmenu.js (revision 15677)
@@ -0,0 +1,4 @@
+function init()
+{
+ Engine.SwitchGuiPage("page_modmod.xml", {});
+}
Index: ps/trunk/binaries/data/mods/mod/gui/pregame/mainmenu.xml
===================================================================
--- ps/trunk/binaries/data/mods/mod/gui/pregame/mainmenu.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/mod/gui/pregame/mainmenu.xml (revision 15677)
@@ -0,0 +1,5 @@
+
+
+
+
+
Index: ps/trunk/binaries/data/mods/public/gui/pregame/mainmenu.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/pregame/mainmenu.xml (revision 15676)
+++ ps/trunk/binaries/data/mods/public/gui/pregame/mainmenu.xml (revision 15677)
@@ -1,601 +1,612 @@
onTick();
[font="sans-bold-16"]Help improve 0 A.D.![/font]\nYou can automatically send us anonymous feedback that will help us fix bugs, and improve performance and compatibility.Enable FeedbackEnableUserReport(true);Technical DetailsEngine.PushGuiPage("page_manual.xml", {"page":"manual/userreport", "title":getTechnicalDetails()});[font="sans-bold-16"]Thank you for helping improve 0 A.D.![/font]\n\nAnonymous feedback is currently enabled.\nStatus: $status.Disable FeedbackEnableUserReport(false);Technical DetailsEngine.PushGuiPage("page_manual.xml", {"page":"manual/userreport", "title":getTechnicalDetails()});
closeMenu();
MatchesClick here to start a new single player game.
Engine.SwitchGuiPage("page_gamesetup.xml", { type: "offline" });
CampaignsRelive history through historical military campaigns. [NOT YET IMPLEMENTED]
closeMenu();
Load GameClick here to load a saved game.
closeMenu();
Engine.PushGuiPage("page_loadgame.xml", { type: "offline" });
Join GameJoining an existing multiplayer game.
closeMenu();
// Open Multiplayer connection window with join option.
Engine.PushGuiPage("page_gamesetup_mp.xml", { multiplayerGameType: "join" });
Host GameHost a multiplayer game.\n\nRequires UDP port 20595 to be open.
closeMenu();
// Open Multiplayer connection window with host option.
Engine.PushGuiPage("page_gamesetup_mp.xml", { multiplayerGameType: "host" });
Game LobbyLaunch the multiplayer lobby.
closeMenu();
// Open Multiplayer game lobby.
Engine.PushGuiPage("page_prelobby.xml", []);
if (!Engine.StartXmppClient)
{
this.enabled = false;
this.tooltip = getLobbyDisabledByBuild();
}
OptionsAdjust game settings.
closeMenu();
LanguageChoose the language of the game.Scenario EditorOpen the Atlas Scenario Editor in a new window. You can run this more reliably by starting the game with the command-line argument "-editor".
pressedScenarioEditorButton();
Welcome ScreenShow the Welcome Screen. Useful if you hid it by mistake.
+
+ Mod Selection
+ Select mods to use.
+
+ Engine.SwitchGuiPage("page_modmod.xml", {});
+
+ Learn To PlayOpen the 0 A.D. Game Manual.
closeMenu();
Single PlayerChallenge the computer player to a single player match.
closeMenu();
openMenu("submenuSinglePlayer", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 3);
MultiplayerFight against one or more human players in a multiplayer game.
closeMenu();
openMenu("submenuMultiplayer", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 3);
Tools & OptionsGame options and scenario design tools.
closeMenu();
- openMenu("submenuToolsAndOptions", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 4);
+ openMenu("submenuToolsAndOptions", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 5);
HistoryLearn about the many civilizations featured in 0 A.D.
closeMenu();
ExitExits the game.exitGamePressed();[font="sans-bold-16"]Alpha XVI: PataƱjali[/font]\n\nWARNING: This is an early development version of the game. Many features have not been added yet.\n\nGet involved at: play0ad.comHelp with the Translation!Click to open the 0 A.D. translate page in your browser.WebsiteClick to open play0ad.com in your web browser.ChatClick to open the 0 A.D. IRC chat in your browser. (#0ad on webchat.quakenet.org)Report a BugClick to visit 0 A.D. Trac to report a bug, crash, or error.WILDFIRE GAMES
this.caption = getBuildString();
Index: ps/trunk/binaries/data/mods/public/mod.json
===================================================================
--- ps/trunk/binaries/data/mods/public/mod.json (nonexistent)
+++ ps/trunk/binaries/data/mods/public/mod.json (revision 15677)
@@ -0,0 +1,9 @@
+{
+ "name": "0ad",
+ "version": "0.0.17",
+ "label": "0 A.D. Empires Ascendant",
+ "url": "play0ad.com",
+ "description": "A free, open-source, historical RTS game.",
+ "dependencies": [],
+ "type": "game"
+}
Index: ps/trunk/build/resources/0ad.sh
===================================================================
--- ps/trunk/build/resources/0ad.sh (revision 15676)
+++ ps/trunk/build/resources/0ad.sh (revision 15677)
@@ -1,9 +1,9 @@
#!/bin/sh
pyrogenesis=$(which pyrogenesis 2> /dev/null)
if [ -x "$pyrogenesis" ] ; then
- "$pyrogenesis" "$@"
+ "$pyrogenesis" -mod=public "$@"
else
echo "Error: pyrogenesis not found in ($PATH)"
exit 1
fi
Index: ps/trunk/source/gui/scripting/ScriptFunctions.cpp
===================================================================
--- ps/trunk/source/gui/scripting/ScriptFunctions.cpp (revision 15676)
+++ ps/trunk/source/gui/scripting/ScriptFunctions.cpp (revision 15677)
@@ -1,1047 +1,1049 @@
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "scriptinterface/ScriptInterface.h"
#include "graphics/Camera.h"
#include "graphics/GameView.h"
#include "graphics/MapReader.h"
#include "gui/GUIManager.h"
#include "gui/GUI.h"
#include "gui/IGUIObject.h"
#include "gui/scripting/JSInterface_GUITypes.h"
#include "graphics/scripting/JSInterface_GameView.h"
#include "i18n/L10n.h"
#include "i18n/scripting/JSInterface_L10n.h"
#include "lib/svn_revision.h"
#include "lib/sysdep/sysdep.h"
#include "lib/timer.h"
#include "lib/utf8.h"
#include "lobby/scripting/JSInterface_Lobby.h"
#include "maths/FixedVector3D.h"
#include "network/NetClient.h"
#include "network/NetServer.h"
#include "network/NetTurnManager.h"
#include "ps/CLogger.h"
#include "ps/CConsole.h"
#include "ps/Errors.h"
#include "ps/Game.h"
#include "ps/Globals.h" // g_frequencyFilter
#include "ps/GUID.h"
#include "ps/World.h"
#include "ps/Hotkey.h"
#include "ps/Overlay.h"
#include "ps/ProfileViewer.h"
#include "ps/Pyrogenesis.h"
#include "ps/SavedGame.h"
#include "ps/scripting/JSInterface_ConfigDB.h"
#include "ps/scripting/JSInterface_Console.h"
+#include "ps/scripting/JSInterface_Mod.h"
#include "ps/scripting/JSInterface_VFS.h"
#include "ps/UserReport.h"
#include "ps/GameSetup/Atlas.h"
#include "ps/GameSetup/Config.h"
#include "renderer/scripting/JSInterface_Renderer.h"
#include "tools/atlas/GameInterface/GameLoop.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpAIManager.h"
#include "simulation2/components/ICmpCommandQueue.h"
#include "simulation2/components/ICmpGuiInterface.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpTemplateManager.h"
#include "simulation2/components/ICmpSelectable.h"
#include "simulation2/helpers/Selection.h"
#include "soundmanager/SoundManager.h"
#include "soundmanager/scripting/JSInterface_Sound.h"
/*
* This file defines a set of functions that are available to GUI scripts, to allow
* interaction with the rest of the engine.
* Functions are exposed to scripts within the global object 'Engine', so
* scripts should call "Engine.FunctionName(...)" etc.
*/
extern void restart_mainloop_in_atlas(); // from main.cpp
extern void EndGame();
extern void kill_mainloop();
namespace {
// Note that the initData argument may only contain clonable data.
// Functions aren't supported for example!
// TODO: Use LOGERROR to print a friendly error message when the requirements aren't met instead of failing with debug_warn when cloning.
void PushGuiPage(ScriptInterface::CxPrivate* pCxPrivate, std::wstring name, CScriptVal initData1)
{
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
// TODO: Get Handle parameter directly with SpiderMonkey 31
JS::RootedValue initData(cx, initData1.get());
g_GUI->PushPage(name, pCxPrivate->pScriptInterface->WriteStructuredClone(initData));
}
void SwitchGuiPage(ScriptInterface::CxPrivate* pCxPrivate, std::wstring name, CScriptVal initData1)
{
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
// TODO: Get Handle parameter directly with SpiderMonkey 31
JS::RootedValue initData(cx, initData1.get());
g_GUI->SwitchPage(name, pCxPrivate->pScriptInterface, initData);
}
void PopGuiPage(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
g_GUI->PopPage();
}
// Note that the args argument may only contain clonable data.
// Functions aren't supported for example!
// TODO: Use LOGERROR to print a friendly error message when the requirements aren't met instead of failing with debug_warn when cloning.
void PopGuiPageCB(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal args1)
{
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
// TODO: Get Handle parameter directly with SpiderMonkey 31
JS::RootedValue args(cx, args1.get());
g_GUI->PopPageCB(pCxPrivate->pScriptInterface->WriteStructuredClone(args));
}
CScriptVal GuiInterfaceCall(ScriptInterface::CxPrivate* pCxPrivate, std::wstring name, CScriptVal data1)
{
// TODO: With ESR31 we should be able to take JS::HandleValue directly
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
JS::RootedValue data(cx, data1.get());
if (!g_Game)
return JS::UndefinedValue();
CSimulation2* sim = g_Game->GetSimulation2();
ENSURE(sim);
CmpPtr cmpGuiInterface(*sim, SYSTEM_ENTITY);
if (!cmpGuiInterface)
return JS::UndefinedValue();
int player = g_Game->GetPlayerID();
JSContext* cxSim = sim->GetScriptInterface().GetContext();
JSAutoRequest rqSim(cxSim);
JS::RootedValue arg(cxSim, sim->GetScriptInterface().CloneValueFromOtherContext(*(pCxPrivate->pScriptInterface), data));
JS::RootedValue ret(cxSim, cmpGuiInterface->ScriptCall(player, name, CScriptVal(arg)).get());
return pCxPrivate->pScriptInterface->CloneValueFromOtherContext(sim->GetScriptInterface(), ret);
}
void PostNetworkCommand(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal cmd1)
{
if (!g_Game)
return;
CSimulation2* sim = g_Game->GetSimulation2();
ENSURE(sim);
CmpPtr cmpCommandQueue(*sim, SYSTEM_ENTITY);
if (!cmpCommandQueue)
return;
// TODO: With ESR31 we should be able to take JS::HandleValue directly
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
JS::RootedValue cmd(cx, cmd1.get());
JSContext* cxSim = sim->GetScriptInterface().GetContext();
JSAutoRequest rqSim(cxSim);
JS::RootedValue cmd2(cxSim, sim->GetScriptInterface().CloneValueFromOtherContext(*(pCxPrivate->pScriptInterface), cmd));
cmpCommandQueue->PostNetworkCommand(CScriptVal(cmd2));
}
std::vector PickEntitiesAtPoint(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int x, int y, int range)
{
return EntitySelection::PickEntitiesAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y, g_Game->GetPlayerID(), false, range);
}
std::vector PickFriendlyEntitiesInRect(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int x0, int y0, int x1, int y1, int player)
{
return EntitySelection::PickEntitiesInRect(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x0, y0, x1, y1, player, false);
}
std::vector PickFriendlyEntitiesOnScreen(ScriptInterface::CxPrivate* pCxPrivate, int player)
{
return PickFriendlyEntitiesInRect(pCxPrivate, 0, 0, g_xres, g_yres, player);
}
std::vector PickSimilarFriendlyEntities(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string templateName, bool includeOffScreen, bool matchRank, bool allowFoundations)
{
return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetPlayerID(), includeOffScreen, matchRank, false, allowFoundations);
}
CFixedVector3D GetTerrainAtScreenPoint(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int x, int y)
{
CVector3D pos = g_Game->GetView()->GetCamera()->GetWorldCoordinates(x, y, true);
return CFixedVector3D(fixed::FromFloat(pos.X), fixed::FromFloat(pos.Y), fixed::FromFloat(pos.Z));
}
std::wstring SetCursor(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring name)
{
std::wstring old = g_CursorName;
g_CursorName = name;
return old;
}
int GetPlayerID(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (g_Game)
return g_Game->GetPlayerID();
return -1;
}
void SetPlayerID(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int id)
{
if (g_Game)
g_Game->SetPlayerID(id);
}
CScriptValRooted GetEngineInfo(ScriptInterface::CxPrivate* pCxPrivate)
{
return SavedGames::GetEngineInfo(*(pCxPrivate->pScriptInterface));
}
void StartNetworkGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
ENSURE(g_NetServer);
g_NetServer->StartGame();
}
void StartGame(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal attribs1, int playerID)
{
ENSURE(!g_NetServer);
ENSURE(!g_NetClient);
ENSURE(!g_Game);
g_Game = new CGame();
// TODO: With ESR31 we should be able to take JS::HandleValue directly
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
JS::RootedValue attribs(cx, attribs1.get());
// Convert from GUI script context to sim script context
CSimulation2* sim = g_Game->GetSimulation2();
JSContext* cxSim = sim->GetScriptInterface().GetContext();
JSAutoRequest rqSim(cxSim);
JS::RootedValue gameAttribs(cxSim,
sim->GetScriptInterface().CloneValueFromOtherContext(*(pCxPrivate->pScriptInterface), attribs));
g_Game->SetPlayerID(playerID);
g_Game->StartGame(CScriptValRooted(cxSim, gameAttribs), "");
}
CScriptVal StartSavedGame(ScriptInterface::CxPrivate* pCxPrivate, std::wstring name)
{
// We need to be careful with different compartments and contexts.
// The GUI calls this function from the GUI context and expects the return value in the same context.
// The game we start from here creates another context and expects data in this context.
JSContext* cxGui = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cxGui);
ENSURE(!g_NetServer);
ENSURE(!g_NetClient);
ENSURE(!g_Game);
// Load the saved game data from disk
JS::RootedValue guiContextMetadata(cxGui);
std::string savedState;
Status err = SavedGames::Load(name, *(pCxPrivate->pScriptInterface), &guiContextMetadata, savedState);
if (err < 0)
return JS::UndefinedValue();
g_Game = new CGame();
{
CSimulation2* sim = g_Game->GetSimulation2();
JSContext* cxGame = sim->GetScriptInterface().GetContext();
JSAutoRequest rq(cxGame);
JS::RootedValue gameContextMetadata(cxGame,
sim->GetScriptInterface().CloneValueFromOtherContext(*(pCxPrivate->pScriptInterface), guiContextMetadata));
JS::RootedValue gameInitAttributes(cxGame);
sim->GetScriptInterface().GetProperty(gameContextMetadata, "initAttributes", &gameInitAttributes);
int playerID;
sim->GetScriptInterface().GetProperty(gameContextMetadata, "player", playerID);
// Start the game
g_Game->SetPlayerID(playerID);
g_Game->StartGame(CScriptValRooted(cxGame, gameInitAttributes), savedState);
}
return guiContextMetadata.get();
}
void SaveGame(ScriptInterface::CxPrivate* pCxPrivate, std::wstring filename, std::wstring description, CScriptVal GUIMetadata1)
{
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
// TODO: Get Handle parameter directly with SpiderMonkey 31
JS::RootedValue GUIMetadata(cx, GUIMetadata1.get());
shared_ptr GUIMetadataClone = pCxPrivate->pScriptInterface->WriteStructuredClone(GUIMetadata);
if (SavedGames::Save(filename, description, *g_Game->GetSimulation2(), GUIMetadataClone, g_Game->GetPlayerID()) < 0)
LOGERROR(L"Failed to save game");
}
void SaveGamePrefix(ScriptInterface::CxPrivate* pCxPrivate, std::wstring prefix, std::wstring description, CScriptVal GUIMetadata1)
{
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
// TODO: Get Handle parameter directly with SpiderMonkey 31
JS::RootedValue GUIMetadata(cx, GUIMetadata1.get());
shared_ptr GUIMetadataClone = pCxPrivate->pScriptInterface->WriteStructuredClone(GUIMetadata);
if (SavedGames::SavePrefix(prefix, description, *g_Game->GetSimulation2(), GUIMetadataClone, g_Game->GetPlayerID()) < 0)
LOGERROR(L"Failed to save game");
}
void SetNetworkGameAttributes(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal attribs1)
{
ENSURE(g_NetServer);
// TODO: Get Handle parameter directly with SpiderMonkey 31
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
JS::RootedValue attribs(cx, attribs1.get());
g_NetServer->UpdateGameAttributes(&attribs, *(pCxPrivate->pScriptInterface));
}
void StartNetworkHost(ScriptInterface::CxPrivate* pCxPrivate, std::wstring playerName)
{
ENSURE(!g_NetClient);
ENSURE(!g_NetServer);
ENSURE(!g_Game);
g_NetServer = new CNetServer();
if (!g_NetServer->SetupConnection())
{
pCxPrivate->pScriptInterface->ReportError("Failed to start server");
SAFE_DELETE(g_NetServer);
return;
}
g_Game = new CGame();
g_NetClient = new CNetClient(g_Game);
g_NetClient->SetUserName(playerName);
if (!g_NetClient->SetupConnection("127.0.0.1"))
{
pCxPrivate->pScriptInterface->ReportError("Failed to connect to server");
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_Game);
}
}
void StartNetworkJoin(ScriptInterface::CxPrivate* pCxPrivate, std::wstring playerName, std::string serverAddress)
{
ENSURE(!g_NetClient);
ENSURE(!g_NetServer);
ENSURE(!g_Game);
g_Game = new CGame();
g_NetClient = new CNetClient(g_Game);
g_NetClient->SetUserName(playerName);
if (!g_NetClient->SetupConnection(serverAddress))
{
pCxPrivate->pScriptInterface->ReportError("Failed to connect to server");
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_Game);
}
}
void DisconnectNetworkGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
// TODO: we ought to do async reliable disconnections
SAFE_DELETE(g_NetServer);
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_Game);
}
CScriptVal PollNetworkClient(ScriptInterface::CxPrivate* pCxPrivate)
{
if (!g_NetClient)
return JS::UndefinedValue();
// Convert from net client context to GUI script context
JSContext* cxNet = g_NetClient->GetScriptInterface().GetContext();
JSAutoRequest rqNet(cxNet);
JS::RootedValue pollNet(cxNet, g_NetClient->GuiPoll().get());
return pCxPrivate->pScriptInterface->CloneValueFromOtherContext(g_NetClient->GetScriptInterface(), pollNet);
}
void AssignNetworkPlayer(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int playerID, std::string guid)
{
ENSURE(g_NetServer);
g_NetServer->AssignPlayer(playerID, guid);
}
void SetNetworkPlayerStatus(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string guid, int ready)
{
ENSURE(g_NetServer);
g_NetServer->SetPlayerReady(guid, ready);
}
void ClearAllPlayerReady (ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
ENSURE(g_NetServer);
g_NetServer->ClearAllPlayerReady();
}
void SendNetworkChat(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring message)
{
ENSURE(g_NetClient);
g_NetClient->SendChatMessage(message);
}
void SendNetworkReady(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int message)
{
ENSURE(g_NetClient);
g_NetClient->SendReadyMessage(message);
}
std::vector GetAIs(ScriptInterface::CxPrivate* pCxPrivate)
{
return ICmpAIManager::GetAIs(*(pCxPrivate->pScriptInterface));
}
std::vector GetSavedGames(ScriptInterface::CxPrivate* pCxPrivate)
{
return SavedGames::GetSavedGames(*(pCxPrivate->pScriptInterface));
}
bool DeleteSavedGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring name)
{
return SavedGames::DeleteSavedGame(name);
}
void OpenURL(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string url)
{
sys_open_url(url);
}
std::wstring GetMatchID(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return ps_generate_guid().FromUTF8();
}
void RestartInAtlas(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
restart_mainloop_in_atlas();
}
bool AtlasIsAvailable(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return ATLAS_IsAvailable();
}
bool IsAtlasRunning(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return (g_AtlasGameLoop && g_AtlasGameLoop->running);
}
CScriptVal LoadMapSettings(ScriptInterface::CxPrivate* pCxPrivate, VfsPath pathname)
{
CMapSummaryReader reader;
if (reader.LoadMap(pathname) != PSRETURN_OK)
return CScriptVal();
return reader.GetMapSettings(*(pCxPrivate->pScriptInterface)).get();
}
CScriptVal GetMapSettings(ScriptInterface::CxPrivate* pCxPrivate)
{
if (!g_Game)
return JS::UndefinedValue();
JSContext* cx = g_Game->GetSimulation2()->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
JS::RootedValue mapSettings(cx, g_Game->GetSimulation2()->GetMapSettings().get());
return pCxPrivate->pScriptInterface->CloneValueFromOtherContext(
g_Game->GetSimulation2()->GetScriptInterface(),
mapSettings);
}
/**
* Get the current X coordinate of the camera.
*/
float CameraGetX(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (g_Game && g_Game->GetView())
return g_Game->GetView()->GetCameraX();
return -1;
}
/**
* Get the current Z coordinate of the camera.
*/
float CameraGetZ(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (g_Game && g_Game->GetView())
return g_Game->GetView()->GetCameraZ();
return -1;
}
/**
* Start / stop camera following mode
* @param entityid unit id to follow. If zero, stop following mode
*/
void CameraFollow(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), entity_id_t entityid)
{
if (g_Game && g_Game->GetView())
g_Game->GetView()->CameraFollow(entityid, false);
}
/**
* Start / stop first-person camera following mode
* @param entityid unit id to follow. If zero, stop following mode
*/
void CameraFollowFPS(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), entity_id_t entityid)
{
if (g_Game && g_Game->GetView())
g_Game->GetView()->CameraFollow(entityid, true);
}
/**
* Set the data (position, orientation and zoom) of the camera
*/
void SetCameraData(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), entity_pos_t x, entity_pos_t y, entity_pos_t z, entity_pos_t rotx, entity_pos_t roty, entity_pos_t zoom)
{
// called from JS; must not fail
if(!(g_Game && g_Game->GetWorld() && g_Game->GetView() && g_Game->GetWorld()->GetTerrain()))
return;
CVector3D Pos = CVector3D(x.ToFloat(), y.ToFloat(), z.ToFloat());
float RotX = rotx.ToFloat();
float RotY = roty.ToFloat();
float Zoom = zoom.ToFloat();
g_Game->GetView()->SetCamera(Pos, RotX, RotY, Zoom);
}
/// Move camera to a 2D location
void CameraMoveTo(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), entity_pos_t x, entity_pos_t z)
{
// called from JS; must not fail
if(!(g_Game && g_Game->GetWorld() && g_Game->GetView() && g_Game->GetWorld()->GetTerrain()))
return;
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
CVector3D target;
target.X = x.ToFloat();
target.Z = z.ToFloat();
target.Y = terrain->GetExactGroundLevel(target.X, target.Z);
g_Game->GetView()->MoveCameraTarget(target);
}
entity_id_t GetFollowedEntity(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (g_Game && g_Game->GetView())
return g_Game->GetView()->GetFollowedEntity();
return INVALID_ENTITY;
}
bool HotkeyIsPressed_(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string hotkeyName)
{
return HotkeyIsPressed(hotkeyName);
}
void DisplayErrorDialog(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring msg)
{
debug_DisplayError(msg.c_str(), DE_NO_DEBUG_INFO, NULL, NULL, NULL, 0, NULL, NULL);
}
CScriptVal GetProfilerState(ScriptInterface::CxPrivate* pCxPrivate)
{
return g_ProfileViewer.SaveToJS(*(pCxPrivate->pScriptInterface));
}
bool IsUserReportEnabled(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return g_UserReporter.IsReportingEnabled();
}
void SetUserReportEnabled(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool enabled)
{
g_UserReporter.SetReportingEnabled(enabled);
}
std::string GetUserReportStatus(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return g_UserReporter.GetStatus();
}
void SubmitUserReport(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string type, int version, std::wstring data)
{
g_UserReporter.SubmitReport(type.c_str(), version, utf8_from_wstring(data));
}
void SetSimRate(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float rate)
{
g_Game->SetSimRate(rate);
}
float GetSimRate(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return g_Game->GetSimRate();
}
void SetTurnLength(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int length)
{
if (g_NetServer)
g_NetServer->SetTurnLength(length);
else
LOGERROR(L"Only network host can change turn length");
}
// Focus the game camera on a given position.
void SetCameraTarget(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float x, float y, float z)
{
g_Game->GetView()->ResetCameraTarget(CVector3D(x, y, z));
}
// Deliberately cause the game to crash.
// Currently implemented via access violation (read of address 0).
// Useful for testing the crashlog/stack trace code.
int Crash(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
debug_printf(L"Crashing at user's request.\n");
return *(volatile int*)0;
}
void DebugWarn(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
debug_warn(L"Warning at user's request.");
}
// Force a JS garbage collection cycle to take place immediately.
// Writes an indication of how long this took to the console.
void ForceGC(ScriptInterface::CxPrivate* pCxPrivate)
{
double time = timer_Time();
JS_GC(pCxPrivate->pScriptInterface->GetJSRuntime());
time = timer_Time() - time;
g_Console->InsertMessage(L"Garbage collection completed in: %f", time);
}
void DumpSimState(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
OsPath path = psLogDir()/"sim_dump.txt";
std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
g_Game->GetSimulation2()->DumpDebugState(file);
}
void DumpTerrainMipmap(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
VfsPath filename(L"screenshots/terrainmipmap.png");
g_Game->GetWorld()->GetTerrain()->GetHeightMipmap().DumpToDisk(filename);
OsPath realPath;
g_VFS->GetRealPath(filename, realPath);
LOGMESSAGERENDER(L"Terrain mipmap written to '%ls'", realPath.string().c_str());
}
void EnableTimeWarpRecording(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), unsigned int numTurns)
{
g_Game->GetTurnManager()->EnableTimeWarpRecording(numTurns);
}
void RewindTimeWarp(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
g_Game->GetTurnManager()->RewindTimeWarp();
}
void QuickSave(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
g_Game->GetTurnManager()->QuickSave();
}
void QuickLoad(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
g_Game->GetTurnManager()->QuickLoad();
}
void SetBoundingBoxDebugOverlay(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool enabled)
{
ICmpSelectable::ms_EnableDebugOverlays = enabled;
}
void Script_EndGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
EndGame();
}
// Cause the game to exit gracefully.
// params:
// returns:
// notes:
// - Exit happens after the current main loop iteration ends
// (since this only sets a flag telling it to end)
void ExitProgram(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
kill_mainloop();
}
// Is the game paused?
bool IsPaused(ScriptInterface::CxPrivate* pCxPrivate)
{
if (!g_Game)
{
JS_ReportError(pCxPrivate->pScriptInterface->GetContext(), "Game is not started");
return false;
}
return g_Game->m_Paused;
}
// Pause/unpause the game
void SetPaused(ScriptInterface::CxPrivate* pCxPrivate, bool pause)
{
if (!g_Game)
{
JS_ReportError(pCxPrivate->pScriptInterface->GetContext(), "Game is not started");
return;
}
g_Game->m_Paused = pause;
#if CONFIG2_AUDIO
if ( g_SoundManager )
g_SoundManager->Pause(pause);
#endif
}
// Return the global frames-per-second value.
// params:
// returns: FPS [int]
// notes:
// - This value is recalculated once a frame. We take special care to
// filter it, so it is both accurate and free of jitter.
int GetFps(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
int freq = 0;
if (g_frequencyFilter)
freq = g_frequencyFilter->StableFrequency();
return freq;
}
CScriptVal GetGUIObjectByName(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStr name)
{
- IGUIObject* guiObj = g_GUI->FindObjectByName(name);
- if (guiObj)
- return OBJECT_TO_JSVAL(guiObj->GetJSObject());
- else
- return JSVAL_VOID;
+ IGUIObject* guiObj = g_GUI->FindObjectByName(name);
+ if (guiObj)
+ return OBJECT_TO_JSVAL(guiObj->GetJSObject());
+ else
+ return JSVAL_VOID;
}
// Return the date/time at which the current executable was compiled.
// params: mode OR an integer specifying
// what to display: -1 for "date time (svn revision)", 0 for date, 1 for time, 2 for svn revision
// returns: string with the requested timestamp info
// notes:
// - Displayed on main menu screen; tells non-programmers which auto-build
// they are running. Could also be determined via .EXE file properties,
// but that's a bit more trouble.
// - To be exact, the date/time returned is when scriptglue.cpp was
// last compiled, but the auto-build does full rebuilds.
// - svn revision is generated by calling svnversion and cached in
// lib/svn_revision.cpp. it is useful to know when attempting to
// reproduce bugs (the main EXE and PDB should be temporarily reverted to
// that revision so that they match user-submitted crashdumps).
std::wstring GetBuildTimestamp(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int mode)
{
char buf[200];
if (mode == -1) // Date, time and revision.
{
UDate dateTime = L10n::Instance().ParseDateTime(__DATE__ " " __TIME__, "MMM d yyyy HH:mm:ss", Locale::getUS());
std::string dateTimeString = L10n::Instance().LocalizeDateTime(dateTime, L10n::DateTime, SimpleDateFormat::DATE_TIME);
char svnRevision[32];
sprintf_s(svnRevision, ARRAY_SIZE(svnRevision), "%ls", svn_revision);
if (strcmp(svnRevision, "custom build") == 0)
{
// Translation: First item is a date and time, item between parenthesis is the Subversion revision number of the current build.
sprintf_s(buf, ARRAY_SIZE(buf), L10n::Instance().Translate("%s (custom build)").c_str(), dateTimeString.c_str());
}
else
{
// Translation: First item is a date and time, item between parenthesis is the Subversion revision number of the current build.
sprintf_s(buf, ARRAY_SIZE(buf), L10n::Instance().Translate("%s (%ls)").c_str(), dateTimeString.c_str(), svn_revision);
}
}
else if (mode == 0) // Date.
{
UDate dateTime = L10n::Instance().ParseDateTime(__DATE__, "MMM d yyyy", Locale::getUS());
std::string dateTimeString = L10n::Instance().LocalizeDateTime(dateTime, L10n::Date, SimpleDateFormat::MEDIUM);
sprintf_s(buf, ARRAY_SIZE(buf), "%s", dateTimeString.c_str());
}
else if (mode == 1) // Time.
{
UDate dateTime = L10n::Instance().ParseDateTime(__TIME__, "HH:mm:ss", Locale::getUS());
std::string dateTimeString = L10n::Instance().LocalizeDateTime(dateTime, L10n::Time, SimpleDateFormat::MEDIUM);
sprintf_s(buf, ARRAY_SIZE(buf), "%s", dateTimeString.c_str());
}
else if (mode == 2) // Revision.
{
char svnRevision[32];
sprintf_s(svnRevision, ARRAY_SIZE(svnRevision), "%ls", svn_revision);
if (strcmp(svnRevision, "custom build") == 0)
{
sprintf_s(buf, ARRAY_SIZE(buf), "%s", L10n::Instance().Translate("custom build").c_str());
}
else
{
sprintf_s(buf, ARRAY_SIZE(buf), "%ls", svn_revision);
}
}
return wstring_from_utf8(buf);
}
//-----------------------------------------------------------------------------
// Timer
//-----------------------------------------------------------------------------
// Script profiling functions: Begin timing a piece of code with StartJsTimer(num)
// and stop timing with StopJsTimer(num). The results will be printed to stdout
// when the game exits.
static const size_t MAX_JS_TIMERS = 20;
static TimerUnit js_start_times[MAX_JS_TIMERS];
static TimerUnit js_timer_overhead;
static TimerClient js_timer_clients[MAX_JS_TIMERS];
static wchar_t js_timer_descriptions_buf[MAX_JS_TIMERS * 12]; // depends on MAX_JS_TIMERS and format string below
static void InitJsTimers(ScriptInterface& scriptInterface)
{
wchar_t* pos = js_timer_descriptions_buf;
for(size_t i = 0; i < MAX_JS_TIMERS; i++)
{
const wchar_t* description = pos;
pos += swprintf_s(pos, 12, L"js_timer %d", (int)i)+1;
timer_AddClient(&js_timer_clients[i], description);
}
// call several times to get a good approximation of 'hot' performance.
// note: don't use a separate timer slot to warm up and then judge
// overhead from another: that causes worse results (probably some
// caching effects inside JS, but I don't entirely understand why).
std::wstring calibration_script =
L"Engine.StartXTimer(0);\n" \
L"Engine.StopXTimer (0);\n" \
L"\n";
scriptInterface.LoadGlobalScript("timer_calibration_script", calibration_script);
// slight hack: call LoadGlobalScript twice because we can't average several
// TimerUnit values because there's no operator/. this way is better anyway
// because it hopefully avoids the one-time JS init overhead.
js_timer_clients[0].sum.SetToZero();
scriptInterface.LoadGlobalScript("timer_calibration_script", calibration_script);
js_timer_clients[0].sum.SetToZero();
js_timer_clients[0].num_calls = 0;
}
void StartJsTimer(ScriptInterface::CxPrivate* pCxPrivate, unsigned int slot)
{
ONCE(InitJsTimers(*(pCxPrivate->pScriptInterface)));
if (slot >= MAX_JS_TIMERS)
LOGERROR(L"Exceeded the maximum number of timer slots for scripts!");
js_start_times[slot].SetFromTimer();
}
void StopJsTimer(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), unsigned int slot)
{
if (slot >= MAX_JS_TIMERS)
LOGERROR(L"Exceeded the maximum number of timer slots for scripts!");
TimerUnit now;
now.SetFromTimer();
now.Subtract(js_timer_overhead);
BillingPolicy_Default()(&js_timer_clients[slot], js_start_times[slot], now);
js_start_times[slot].SetToZero();
}
} // namespace
void GuiScriptingInit(ScriptInterface& scriptInterface)
{
JSI_IGUIObject::init(scriptInterface);
JSI_GUITypes::init(scriptInterface);
JSI_GameView::RegisterScriptFunctions(scriptInterface);
JSI_Renderer::RegisterScriptFunctions(scriptInterface);
JSI_Console::RegisterScriptFunctions(scriptInterface);
JSI_ConfigDB::RegisterScriptFunctions(scriptInterface);
+ JSI_Mod::RegisterScriptFunctions(scriptInterface);
JSI_Sound::RegisterScriptFunctions(scriptInterface);
JSI_L10n::RegisterScriptFunctions(scriptInterface);
// VFS (external)
scriptInterface.RegisterFunction("BuildDirEntList");
scriptInterface.RegisterFunction("FileExists");
scriptInterface.RegisterFunction("GetFileMTime");
scriptInterface.RegisterFunction("GetFileSize");
scriptInterface.RegisterFunction("ReadFile");
scriptInterface.RegisterFunction("ReadFileLines");
// GUI manager functions:
scriptInterface.RegisterFunction("PushGuiPage");
scriptInterface.RegisterFunction("SwitchGuiPage");
scriptInterface.RegisterFunction("PopGuiPage");
scriptInterface.RegisterFunction("PopGuiPageCB");
scriptInterface.RegisterFunction("GetGUIObjectByName");
// Simulation<->GUI interface functions:
scriptInterface.RegisterFunction("GuiInterfaceCall");
scriptInterface.RegisterFunction("PostNetworkCommand");
// Entity picking
scriptInterface.RegisterFunction, int, int, int, &PickEntitiesAtPoint>("PickEntitiesAtPoint");
scriptInterface.RegisterFunction, int, int, int, int, int, &PickFriendlyEntitiesInRect>("PickFriendlyEntitiesInRect");
scriptInterface.RegisterFunction, int, &PickFriendlyEntitiesOnScreen>("PickFriendlyEntitiesOnScreen");
scriptInterface.RegisterFunction, std::string, bool, bool, bool, &PickSimilarFriendlyEntities>("PickSimilarFriendlyEntities");
scriptInterface.RegisterFunction("GetTerrainAtScreenPoint");
// Network / game setup functions
scriptInterface.RegisterFunction("StartNetworkGame");
scriptInterface.RegisterFunction("StartGame");
scriptInterface.RegisterFunction("EndGame");
scriptInterface.RegisterFunction("StartNetworkHost");
scriptInterface.RegisterFunction("StartNetworkJoin");
scriptInterface.RegisterFunction("DisconnectNetworkGame");
scriptInterface.RegisterFunction("PollNetworkClient");
scriptInterface.RegisterFunction("SetNetworkGameAttributes");
scriptInterface.RegisterFunction("AssignNetworkPlayer");
scriptInterface.RegisterFunction("SetNetworkPlayerStatus");
scriptInterface.RegisterFunction("ClearAllPlayerReady");
scriptInterface.RegisterFunction("SendNetworkChat");
scriptInterface.RegisterFunction("SendNetworkReady");
scriptInterface.RegisterFunction, &GetAIs>("GetAIs");
scriptInterface.RegisterFunction("GetEngineInfo");
// Saved games
scriptInterface.RegisterFunction("StartSavedGame");
scriptInterface.RegisterFunction, &GetSavedGames>("GetSavedGames");
scriptInterface.RegisterFunction("DeleteSavedGame");
scriptInterface.RegisterFunction("SaveGame");
scriptInterface.RegisterFunction("SaveGamePrefix");
scriptInterface.RegisterFunction("QuickSave");
scriptInterface.RegisterFunction("QuickLoad");
// Misc functions
scriptInterface.RegisterFunction("SetCursor");
scriptInterface.RegisterFunction("GetPlayerID");
scriptInterface.RegisterFunction("SetPlayerID");
scriptInterface.RegisterFunction("OpenURL");
scriptInterface.RegisterFunction("GetMatchID");
scriptInterface.RegisterFunction("RestartInAtlas");
scriptInterface.RegisterFunction("AtlasIsAvailable");
scriptInterface.RegisterFunction("IsAtlasRunning");
scriptInterface.RegisterFunction("LoadMapSettings");
scriptInterface.RegisterFunction("GetMapSettings");
scriptInterface.RegisterFunction("CameraGetX");
scriptInterface.RegisterFunction("CameraGetZ");
scriptInterface.RegisterFunction("CameraFollow");
scriptInterface.RegisterFunction("CameraFollowFPS");
scriptInterface.RegisterFunction("SetCameraData");
scriptInterface.RegisterFunction("CameraMoveTo");
scriptInterface.RegisterFunction("GetFollowedEntity");
scriptInterface.RegisterFunction("HotkeyIsPressed");
scriptInterface.RegisterFunction("DisplayErrorDialog");
scriptInterface.RegisterFunction("GetProfilerState");
scriptInterface.RegisterFunction("Exit");
scriptInterface.RegisterFunction("IsPaused");
scriptInterface.RegisterFunction("SetPaused");
scriptInterface.RegisterFunction("GetFPS");
scriptInterface.RegisterFunction("GetBuildTimestamp");
// User report functions
scriptInterface.RegisterFunction("IsUserReportEnabled");
scriptInterface.RegisterFunction("SetUserReportEnabled");
scriptInterface.RegisterFunction("GetUserReportStatus");
scriptInterface.RegisterFunction("SubmitUserReport");
// Development/debugging functions
scriptInterface.RegisterFunction("StartXTimer");
scriptInterface.RegisterFunction("StopXTimer");
scriptInterface.RegisterFunction("SetSimRate");
scriptInterface.RegisterFunction("GetSimRate");
scriptInterface.RegisterFunction("SetTurnLength");
scriptInterface.RegisterFunction("SetCameraTarget");
scriptInterface.RegisterFunction("Crash");
scriptInterface.RegisterFunction("DebugWarn");
scriptInterface.RegisterFunction("ForceGC");
scriptInterface.RegisterFunction("DumpSimState");
scriptInterface.RegisterFunction("DumpTerrainMipmap");
scriptInterface.RegisterFunction("EnableTimeWarpRecording");
scriptInterface.RegisterFunction("RewindTimeWarp");
scriptInterface.RegisterFunction("SetBoundingBoxDebugOverlay");
// Lobby functions
scriptInterface.RegisterFunction("HasXmppClient");
scriptInterface.RegisterFunction("IsRankedGame");
scriptInterface.RegisterFunction("SetRankedGame");
#if CONFIG2_LOBBY // Allow the lobby to be disabled
scriptInterface.RegisterFunction("StartXmppClient");
scriptInterface.RegisterFunction("StartRegisterXmppClient");
scriptInterface.RegisterFunction("StopXmppClient");
scriptInterface.RegisterFunction("ConnectXmppClient");
scriptInterface.RegisterFunction("DisconnectXmppClient");
scriptInterface.RegisterFunction("SendGetGameList");
scriptInterface.RegisterFunction("SendGetBoardList");
scriptInterface.RegisterFunction("SendGetRatingList");
scriptInterface.RegisterFunction("SendRegisterGame");
scriptInterface.RegisterFunction("SendGameReport");
scriptInterface.RegisterFunction("SendUnregisterGame");
scriptInterface.RegisterFunction("SendChangeStateGame");
scriptInterface.RegisterFunction("GetPlayerList");
scriptInterface.RegisterFunction("GetGameList");
scriptInterface.RegisterFunction("GetBoardList");
scriptInterface.RegisterFunction("LobbyGuiPollMessage");
scriptInterface.RegisterFunction("LobbySendMessage");
scriptInterface.RegisterFunction("LobbySetPlayerPresence");
scriptInterface.RegisterFunction("LobbySetNick");
scriptInterface.RegisterFunction("LobbyGetNick");
scriptInterface.RegisterFunction("LobbyKick");
scriptInterface.RegisterFunction("LobbyBan");
scriptInterface.RegisterFunction("LobbyGetPlayerPresence");
scriptInterface.RegisterFunction("LobbyGetPlayerRole");
scriptInterface.RegisterFunction("EncryptPassword");
scriptInterface.RegisterFunction("LobbyGetRoomSubject");
#endif // CONFIG2_LOBBY
}
Index: ps/trunk/source/ps/GameSetup/GameSetup.cpp
===================================================================
--- ps/trunk/source/ps/GameSetup/GameSetup.cpp (revision 15676)
+++ ps/trunk/source/ps/GameSetup/GameSetup.cpp (revision 15677)
@@ -1,1471 +1,1472 @@
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "lib/app_hooks.h"
#include "lib/config2.h"
#include "lib/input.h"
#include "lib/ogl.h"
#include "lib/timer.h"
#include "lib/external_libraries/libsdl.h"
#include "lib/file/common/file_stats.h"
#include "lib/res/h_mgr.h"
#include "lib/res/graphics/cursor.h"
#include "lib/sysdep/cursor.h"
#include "lib/sysdep/cpu.h"
#include "lib/sysdep/gfx.h"
#include "lib/sysdep/os_cpu.h"
#include "lib/tex/tex.h"
#if OS_WIN
#include "lib/sysdep/os/win/wversion.h"
#endif
#include "graphics/CinemaTrack.h"
#include "graphics/FontMetrics.h"
#include "graphics/GameView.h"
#include "graphics/LightEnv.h"
#include "graphics/MapReader.h"
#include "graphics/MaterialManager.h"
#include "graphics/TerrainTextureManager.h"
#include "gui/GUI.h"
#include "gui/GUIManager.h"
#include "gui/scripting/ScriptFunctions.h"
#include "maths/MathUtil.h"
#include "network/NetServer.h"
#include "network/NetClient.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/GameSetup/Atlas.h"
#include "ps/GameSetup/GameSetup.h"
#include "ps/GameSetup/Paths.h"
#include "ps/GameSetup/Config.h"
#include "ps/GameSetup/CmdLineArgs.h"
#include "ps/GameSetup/HWDetect.h"
#include "ps/Globals.h"
#include "ps/Hotkey.h"
#include "ps/Joystick.h"
#include "ps/Loader.h"
#include "ps/Mod.h"
#include "ps/Overlay.h"
#include "ps/Profile.h"
#include "ps/ProfileViewer.h"
#include "ps/Profiler2.h"
#include "ps/Pyrogenesis.h" // psSetLogDir
#include "ps/scripting/JSInterface_Console.h"
#include "ps/TouchInput.h"
#include "ps/UserReport.h"
#include "ps/Util.h"
#include "ps/VideoMode.h"
#include "ps/World.h"
#include "renderer/Renderer.h"
#include "renderer/VertexBufferManager.h"
#include "renderer/ModelRenderer.h"
#include "scriptinterface/DebuggingServer.h"
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/ScriptStats.h"
#include "simulation2/Simulation2.h"
#include "lobby/IXmppClient.h"
#include "soundmanager/scripting/JSInterface_Sound.h"
#include "soundmanager/ISoundManager.h"
#include "tools/atlas/GameInterface/GameLoop.h"
#include "tools/atlas/GameInterface/View.h"
#if !(OS_WIN || OS_MACOSX || OS_ANDROID) // assume all other platforms use X11 for wxWidgets
#define MUST_INIT_X11 1
#include
#else
#define MUST_INIT_X11 0
#endif
#if OS_WIN
extern void wmi_Shutdown();
#endif
extern void restart_engine();
#include
#include
#include
ERROR_GROUP(System);
ERROR_TYPE(System, SDLInitFailed);
ERROR_TYPE(System, VmodeFailed);
ERROR_TYPE(System, RequiredExtensionsMissing);
bool g_DoRenderGui = true;
bool g_DoRenderLogger = true;
bool g_DoRenderCursor = true;
shared_ptr g_ScriptRuntime;
static const int SANE_TEX_QUALITY_DEFAULT = 5; // keep in sync with code
bool g_InDevelopmentCopy;
bool g_CheckedIfInDevelopmentCopy = false;
static void SetTextureQuality(int quality)
{
int q_flags;
GLint filter;
retry:
// keep this in sync with SANE_TEX_QUALITY_DEFAULT
switch(quality)
{
// worst quality
case 0:
q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP;
filter = GL_NEAREST;
break;
// [perf] add bilinear filtering
case 1:
q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP;
filter = GL_LINEAR;
break;
// [vmem] no longer reduce resolution
case 2:
q_flags = OGL_TEX_HALF_BPP;
filter = GL_LINEAR;
break;
// [vmem] add mipmaps
case 3:
q_flags = OGL_TEX_HALF_BPP;
filter = GL_NEAREST_MIPMAP_LINEAR;
break;
// [perf] better filtering
case 4:
q_flags = OGL_TEX_HALF_BPP;
filter = GL_LINEAR_MIPMAP_LINEAR;
break;
// [vmem] no longer reduce bpp
case SANE_TEX_QUALITY_DEFAULT:
q_flags = OGL_TEX_FULL_QUALITY;
filter = GL_LINEAR_MIPMAP_LINEAR;
break;
// [perf] add anisotropy
case 6:
// TODO: add anisotropic filtering
q_flags = OGL_TEX_FULL_QUALITY;
filter = GL_LINEAR_MIPMAP_LINEAR;
break;
// invalid
default:
debug_warn(L"SetTextureQuality: invalid quality");
quality = SANE_TEX_QUALITY_DEFAULT;
// careful: recursion doesn't work and we don't want to duplicate
// the "sane" default values.
goto retry;
}
ogl_tex_set_defaults(q_flags, filter);
}
//----------------------------------------------------------------------------
// GUI integration
//----------------------------------------------------------------------------
// display progress / description in loading screen
void GUI_DisplayLoadProgress(int percent, const wchar_t* pending_task)
{
g_GUI->GetActiveGUI()->GetScriptInterface()->SetGlobal("g_Progress", percent, true);
g_GUI->GetActiveGUI()->GetScriptInterface()->SetGlobal("g_LoadDescription", pending_task, true);
g_GUI->GetActiveGUI()->SendEventToAll("progress");
}
void Render()
{
PROFILE3("render");
if (g_SoundManager)
g_SoundManager->IdleTask();
ogl_WarnIfError();
g_Profiler2.RecordGPUFrameStart();
ogl_WarnIfError();
// 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 OS_ANDROID
#warning TODO: cursors for Android
#else
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());
#endif
#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();
}
static size_t OperatingSystemFootprint()
{
#if OS_WIN
switch(wversion_Number())
{
case WVERSION_2K:
case WVERSION_XP:
return 150;
case WVERSION_XP64:
return 200;
default: // newer Windows version: assume the worst, and don't warn
case WVERSION_VISTA:
return 300;
case WVERSION_7:
return 250;
}
#else
return 200;
#endif
}
static size_t ChooseCacheSize()
{
// (all sizes in MiB and signed to allow temporarily negative computations)
const ssize_t total = (ssize_t)os_cpu_MemorySize();
// (NB: os_cpu_MemoryAvailable is useless on Linux because free memory
// is marked as "in use" by OS caches.)
const ssize_t os = (ssize_t)OperatingSystemFootprint();
const ssize_t game = 300; // estimated working set
ssize_t cache = 500; // upper bound: total size of our data
// the cache reserves contiguous address space, which is a precious
// resource on 32-bit systems, so don't use too much:
if(ARCH_IA32 || sizeof(void*) == 4)
cache = std::min(cache, (ssize_t)200);
// try to leave over enough memory for the OS and game
cache = std::min(cache, total-os-game);
// always provide at least this much to ensure correct operation
cache = std::max(cache, (ssize_t)64);
debug_printf(L"Cache: %d (total: %d) MiB\n", (int)cache, (int)total);
return size_t(cache)*MiB;
}
ErrorReactionInternal psDisplayError(const wchar_t* UNUSED(text), size_t UNUSED(flags))
{
// If we're fullscreen, then sometimes (at least on some particular drivers on Linux)
// displaying the error dialog hangs the desktop since the dialog box is behind the
// fullscreen window. So we just force the game to windowed mode before displaying the dialog.
// (But only if we're in the main thread, and not if we're being reentrant.)
if (ThreadUtil::IsMainThread())
{
static bool reentering = false;
if (!reentering)
{
reentering = true;
g_VideoMode.SetFullscreen(false);
reentering = false;
}
}
// We don't actually implement the error display here, so return appropriately
return ERI_NOT_IMPLEMENTED;
}
std::vector& GetMods(const CmdLineArgs& args, int flags)
{
const bool init_mods = (flags & INIT_MODS) == INIT_MODS;
const bool add_user = !InDevelopmentCopy() && !args.Has("noUserMod");
if (!init_mods)
{
// Add the user mod if it should be present
if (add_user && (g_modsLoaded.empty() || g_modsLoaded.back() != "user"))
g_modsLoaded.push_back("user");
return g_modsLoaded;
}
g_modsLoaded = args.GetMultiple("mod");
- // TODO: It would be nice to remove this hard-coding of public
+ // TODO: It would be nice to remove this hard-coding of public (remove it once mod is standalone)
g_modsLoaded.insert(g_modsLoaded.begin(), "public");
+ g_modsLoaded.insert(g_modsLoaded.begin(), "mod");
// Add the user mod if not explicitly disabled or we have a dev copy so
// that saved files end up in version control and not in the user mod.
if (add_user)
g_modsLoaded.push_back("user");
return g_modsLoaded;
}
void MountMods(const Paths& paths, const std::vector& mods)
{
OsPath modPath = paths.RData()/"mods";
OsPath modUserPath = paths.UserData()/"mods";
for (size_t i = 0; i < mods.size(); ++i)
{
size_t priority = (i+1)*2; // mods are higher priority than regular mountings, which default to priority 0
size_t userFlags = VFS_MOUNT_WATCH|VFS_MOUNT_ARCHIVABLE|VFS_MOUNT_REPLACEABLE;
size_t baseFlags = userFlags|VFS_MOUNT_MUST_EXIST;
OsPath modName(mods[i]);
if (InDevelopmentCopy())
{
// We are running a dev copy, so only mount mods in the user mod path
// if the mod does not exist in the data path.
if (DirectoryExists(modPath / modName/""))
g_VFS->Mount(L"", modPath / modName/"", baseFlags, priority);
else
g_VFS->Mount(L"", modUserPath / modName/"", userFlags, priority);
}
else
{
g_VFS->Mount(L"", modPath / modName/"", baseFlags, priority);
// Ensure that user modified files are loaded, if they are present
g_VFS->Mount(L"", modUserPath / modName/"", userFlags, priority+1);
}
}
}
static void InitVfs(const CmdLineArgs& args, int flags)
{
TIMER(L"InitVfs");
const bool setup_error = (flags & INIT_HAVE_DISPLAY_ERROR) == 0;
const Paths paths(args);
OsPath logs(paths.Logs());
CreateDirectories(logs, 0700);
psSetLogDir(logs);
// desired location for crashlog is now known. update AppHooks ASAP
// (particularly before the following error-prone operations):
AppHooks hooks = {0};
hooks.bundle_logs = psBundleLogs;
hooks.get_log_dir = psLogDir;
if (setup_error)
hooks.display_error = psDisplayError;
app_hooks_update(&hooks);
const size_t cacheSize = ChooseCacheSize();
g_VFS = CreateVfs(cacheSize);
const OsPath readonlyConfig = paths.RData()/"config"/"";
g_VFS->Mount(L"config/", readonlyConfig);
// Engine localization files.
g_VFS->Mount(L"l10n/", paths.RData()/"l10n"/"");
MountMods(paths, GetMods(args, flags));
// We mount these dirs last as otherwise writing could result in files being placed in a mod's dir.
g_VFS->Mount(L"screenshots/", paths.UserData()/"screenshots"/"");
g_VFS->Mount(L"saves/", paths.UserData()/"saves"/"", VFS_MOUNT_WATCH);
// Mounting with highest priority, so that a mod supplied user.cfg is harmless
g_VFS->Mount(L"config/", readonlyConfig, 0, (size_t)-1);
if(readonlyConfig != paths.Config())
g_VFS->Mount(L"config/", paths.Config(), 0, (size_t)-1);
g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE); // (adding XMBs to archive speeds up subsequent reads)
// note: don't bother with g_VFS->TextRepresentation - directories
// haven't yet been populated and are empty.
}
static void InitPs(bool setup_gui, const CStrW& gui_page, ScriptInterface* srcScriptInterface, JS::HandleValue initData)
{
{
// console
TIMER(L"ps_console");
g_Console->UpdateScreenSize(g_xres, g_yres);
// Calculate and store the line spacing
CFontMetrics font(CStrIntern(CONSOLE_FONT));
g_Console->m_iFontHeight = font.GetLineSpacing();
g_Console->m_iFontWidth = font.GetCharacterWidth(L'C');
g_Console->m_charsPerPage = (size_t)(g_xres / g_Console->m_iFontWidth);
// Offset by an arbitrary amount, to make it fit more nicely
g_Console->m_iFontOffset = 7;
double blinkRate = 0.5;
CFG_GET_VAL("gui.cursorblinkrate", Double, blinkRate);
g_Console->SetCursorBlinkRate(blinkRate);
}
// hotkeys
{
TIMER(L"ps_lang_hotkeys");
LoadHotkeys();
}
if (!setup_gui)
{
// We do actually need *some* kind of GUI loaded, so use the
// (currently empty) Atlas one
g_GUI->SwitchPage(L"page_atlas.xml", srcScriptInterface, initData);
return;
}
// GUI uses VFS, so this must come after VFS init.
g_GUI->SwitchPage(gui_page, srcScriptInterface, initData);
}
static void InitInput()
{
#if !SDL_VERSION_ATLEAST(2, 0, 0)
SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
#endif
g_Joystick.Initialise();
// register input handlers
// This stack is constructed so the first added, will be the last
// one called. This is important, because each of the handlers
// has the potential to block events to go further down
// in the chain. I.e. the last one in the list added, is the
// only handler that can block all messages before they are
// processed.
in_add_handler(game_view_handler);
in_add_handler(CProfileViewer::InputThunk);
in_add_handler(conInputHandler);
in_add_handler(HotkeyInputHandler);
// gui_handler needs to be registered after (i.e. called before!) the
// hotkey handler so that input boxes can be typed in without
// setting off hotkeys.
in_add_handler(gui_handler);
in_add_handler(touch_input_handler);
// must be registered after (called before) the GUI which relies on these globals
in_add_handler(GlobalsInputHandler);
}
static void ShutdownPs()
{
SAFE_DELETE(g_GUI);
SAFE_DELETE(g_Console);
// disable the special Windows cursor, or free textures for OGL cursors
cursor_draw(g_VFS, 0, g_mouse_x, g_yres-g_mouse_y, false);
}
static void InitRenderer()
{
TIMER(L"InitRenderer");
if(g_NoGLS3TC)
ogl_tex_override(OGL_TEX_S3TC, OGL_TEX_DISABLE);
if(g_NoGLAutoMipmap)
ogl_tex_override(OGL_TEX_AUTO_MIPMAP_GEN, OGL_TEX_DISABLE);
// create renderer
new CRenderer;
// set renderer options from command line options - NOVBO must be set before opening the renderer
g_Renderer.SetOptionBool(CRenderer::OPT_NOVBO, g_NoGLVBO);
g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWS, g_Shadows);
g_Renderer.SetOptionBool(CRenderer::OPT_WATERUGLY, g_WaterUgly);
g_Renderer.SetOptionBool(CRenderer::OPT_WATERFANCYEFFECTS, g_WaterFancyEffects);
g_Renderer.SetOptionBool(CRenderer::OPT_WATERREALDEPTH, g_WaterRealDepth);
g_Renderer.SetOptionBool(CRenderer::OPT_WATERREFLECTION, g_WaterReflection);
g_Renderer.SetOptionBool(CRenderer::OPT_WATERREFRACTION, g_WaterRefraction);
g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWSONWATER, g_WaterShadows);
g_Renderer.SetRenderPath(CRenderer::GetRenderPathByName(g_RenderPath));
g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWPCF, g_ShadowPCF);
g_Renderer.SetOptionBool(CRenderer::OPT_PARTICLES, g_Particles);
g_Renderer.SetOptionBool(CRenderer::OPT_SILHOUETTES, g_Silhouettes);
g_Renderer.SetOptionBool(CRenderer::OPT_SHOWSKY, g_ShowSky);
// create terrain related stuff
new CTerrainTextureManager;
g_Renderer.Open(g_xres, g_yres);
// Setup lighting environment. Since the Renderer accesses the
// lighting environment through a pointer, this has to be done before
// the first Frame.
g_Renderer.SetLightEnv(&g_LightEnv);
// I haven't seen the camera affecting GUI rendering and such, but the
// viewport has to be updated according to the video mode
SViewPort vp;
vp.m_X = 0;
vp.m_Y = 0;
vp.m_Width = g_xres;
vp.m_Height = g_yres;
g_Renderer.SetViewport(vp);
ColorActivateFastImpl();
ModelRenderer::Init();
}
static void InitSDL()
{
#if OS_LINUX
// In fullscreen mode when SDL is compiled with DGA support, the mouse
// sensitivity often appears to be unusably wrong (typically too low).
// (This seems to be reported almost exclusively on Ubuntu, but can be
// reproduced on Gentoo after explicitly enabling DGA.)
// Disabling the DGA mouse appears to fix that problem, and doesn't
// have any obvious negative effects.
setenv("SDL_VIDEO_X11_DGAMOUSE", "0", 0);
#endif
if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_NOPARACHUTE) < 0)
{
LOGERROR(L"SDL library initialization failed: %hs", SDL_GetError());
throw PSERROR_System_SDLInitFailed();
}
atexit(SDL_Quit);
#if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_StartTextInput();
#else
SDL_EnableUNICODE(1);
#endif
}
static void ShutdownSDL()
{
SDL_Quit();
sys_cursor_reset();
}
void EndGame()
{
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_NetServer);
SAFE_DELETE(g_Game);
ISoundManager::CloseGame();
}
void Shutdown(int flags)
{
if ((flags & SHUTDOWN_FROM_CONFIG))
goto from_config;
EndGame();
SAFE_DELETE(g_XmppClient);
ShutdownPs();
TIMER_BEGIN(L"shutdown TexMan");
delete &g_TexMan;
TIMER_END(L"shutdown TexMan");
// destroy renderer
TIMER_BEGIN(L"shutdown Renderer");
delete &g_Renderer;
g_VBMan.Shutdown();
TIMER_END(L"shutdown Renderer");
g_Profiler2.ShutdownGPU();
// Free cursors before shutting down SDL, as they may depend on SDL.
cursor_shutdown();
TIMER_BEGIN(L"shutdown SDL");
ShutdownSDL();
TIMER_END(L"shutdown SDL");
g_VideoMode.Shutdown();
TIMER_BEGIN(L"shutdown UserReporter");
g_UserReporter.Deinitialize();
TIMER_END(L"shutdown UserReporter");
// JS debugger temporarily disabled during the SpiderMonkey upgrade (check trac ticket #2348 for details)
//TIMER_BEGIN(L"shutdown DebuggingServer (if active)");
//delete g_DebuggingServer;
//TIMER_END(L"shutdown DebuggingServer (if active)");
from_config:
TIMER_BEGIN(L"shutdown ConfigDB");
delete &g_ConfigDB;
TIMER_END(L"shutdown ConfigDB");
// This is needed to ensure that no callbacks from the JSAPI try to use
// the profiler when it's already destructed
g_ScriptRuntime.reset();
// resource
// first shut down all resource owners, and then the handle manager.
TIMER_BEGIN(L"resource modules");
ISoundManager::SetEnabled(false);
g_VFS.reset();
// this forcibly frees all open handles (thus preventing real leaks),
// and makes further access to h_mgr impossible.
h_mgr_shutdown();
file_stats_dump();
TIMER_END(L"resource modules");
TIMER_BEGIN(L"shutdown misc");
timer_DisplayClientTotals();
CNetHost::Deinitialize();
SAFE_DELETE(g_ScriptStatsTable);
// should be last, since the above use them
SAFE_DELETE(g_Logger);
delete &g_Profiler;
delete &g_ProfileViewer;
TIMER_END(L"shutdown misc");
#if OS_WIN
TIMER_BEGIN(L"shutdown wmi");
wmi_Shutdown();
TIMER_END(L"shutdown wmi");
#endif
}
#if OS_UNIX
static void FixLocales()
{
#if OS_MACOSX || OS_BSD
// OS X requires a UTF-8 locale in LC_CTYPE so that *wprintf can handle
// wide characters. Peculiarly the string "UTF-8" seems to be acceptable
// despite not being a real locale, and it's conveniently language-agnostic,
// so use that.
setlocale(LC_CTYPE, "UTF-8");
#endif
// On misconfigured systems with incorrect locale settings, we'll die
// with a C++ exception when some code (e.g. Boost) tries to use locales.
// To avoid death, we'll detect the problem here and warn the user and
// reset to the default C locale.
// For informing the user of the problem, use the list of env vars that
// glibc setlocale looks at. (LC_ALL is checked first, and LANG last.)
const char* const LocaleEnvVars[] = {
"LC_ALL",
"LC_COLLATE",
"LC_CTYPE",
"LC_MONETARY",
"LC_NUMERIC",
"LC_TIME",
"LC_MESSAGES",
"LANG"
};
try
{
// this constructor is similar to setlocale(LC_ALL, ""),
// but instead of returning NULL, it throws runtime_error
// when the first locale env variable found contains an invalid value
std::locale("");
}
catch (std::runtime_error&)
{
LOGWARNING(L"Invalid locale settings");
for (size_t i = 0; i < ARRAY_SIZE(LocaleEnvVars); i++)
{
if (char* envval = getenv(LocaleEnvVars[i]))
LOGWARNING(L" %hs=\"%hs\"", LocaleEnvVars[i], envval);
else
LOGWARNING(L" %hs=\"(unset)\"", LocaleEnvVars[i]);
}
// We should set LC_ALL since it overrides LANG
if (setenv("LC_ALL", std::locale::classic().name().c_str(), 1))
debug_warn(L"Invalid locale settings, and unable to set LC_ALL env variable.");
else
LOGWARNING(L"Setting LC_ALL env variable to: %hs", getenv("LC_ALL"));
}
}
#else
static void FixLocales()
{
// Do nothing on Windows
}
#endif
void EarlyInit()
{
// If you ever want to catch a particular allocation:
//_CrtSetBreakAlloc(232647);
ThreadUtil::SetMainThread();
debug_SetThreadName("main");
// add all debug_printf "tags" that we are interested in:
debug_filter_add(L"TIMER");
timer_LatchStartTime();
// initialise profiler early so it can profile startup,
// but only after LatchStartTime
g_Profiler2.Initialise();
FixLocales();
// Because we do GL calls from a secondary thread, Xlib needs to
// be told to support multiple threads safely.
// This is needed for Atlas, but we have to call it before any other
// Xlib functions (e.g. the ones used when drawing the main menu
// before launching Atlas)
#if MUST_INIT_X11
int status = XInitThreads();
if (status == 0)
debug_printf(L"Error enabling thread-safety via XInitThreads\n");
#endif
// Initialise the low-quality rand function
srand(time(NULL)); // NOTE: this rand should *not* be used for simulation!
}
bool Autostart(const CmdLineArgs& args);
bool Init(const CmdLineArgs& args, int flags)
{
h_mgr_init();
// Do this as soon as possible, because it chdirs
// and will mess up the error reporting if anything
// crashes before the working directory is set.
InitVfs(args, flags);
// This must come after VFS init, which sets the current directory
// (required for finding our output log files).
g_Logger = new CLogger;
// Workaround until Simulation and AI use their own threads and also their own runtimes
g_ScriptRuntime = ScriptInterface::CreateRuntime(384 * 1024 * 1024);
// Special command-line mode to dump the entity schemas instead of running the game.
// (This must be done after loading VFS etc, but should be done before wasting time
// on anything else.)
if (args.Has("dumpSchema"))
{
CSimulation2 sim(NULL, g_ScriptRuntime, NULL);
sim.LoadDefaultScripts();
std::ofstream f("entity.rng", std::ios_base::out | std::ios_base::trunc);
f << sim.GenerateSchema();
std::cout << "Generated entity.rng\n";
exit(0);
}
// override ah_translate with our i18n code.
AppHooks hooks = {0};
hooks.translate = psTranslate;
hooks.translate_free = psTranslateFree;
app_hooks_update(&hooks);
// Set up the console early, so that debugging
// messages can be logged to it. (The console's size
// and fonts are set later in InitPs())
g_Console = new CConsole();
CNetHost::Initialize();
new CProfileViewer;
new CProfileManager; // before any script code
g_ScriptStatsTable = new CScriptStatsTable;
g_ProfileViewer.AddRootTable(g_ScriptStatsTable);
#if CONFIG2_AUDIO
ISoundManager::CreateSoundManager();
#endif
// g_ConfigDB, command line args, globals
CONFIG_Init(args);
// Check if there are mods specified on the command line,
// or if we already set the mods (~INIT_MODS),
// else check if there are mods that should be loaded specified
// in the config and load those (by aborting init and restarting
// the engine).
if (!args.Has("mod") && (flags & INIT_MODS) == INIT_MODS)
{
CStr modstring;
CFG_GET_VAL("mod.enabledmods", String, modstring);
if (!modstring.empty())
{
std::vector mods;
boost::split(mods, modstring, boost::is_any_of(" "), boost::token_compress_on);
std::swap(g_modsLoaded, mods);
// Abort init and restart
restart_engine();
return false;
}
}
// before scripting
// JS debugger temporarily disabled during the SpiderMonkey upgrade (check trac ticket #2348 for details)
//if (g_JSDebuggerEnabled)
// g_DebuggingServer = new CDebuggingServer();
// Optionally start profiler HTTP output automatically
// (By default it's only enabled by a hotkey, for security/performance)
bool profilerHTTPEnable = false;
CFG_GET_VAL("profiler2.http.autoenable", Bool, profilerHTTPEnable);
if (profilerHTTPEnable)
g_Profiler2.EnableHTTP();
if (!g_Quickstart)
g_UserReporter.Initialize(); // after config
PROFILE2_EVENT("Init finished");
return true;
}
void InitGraphics(const CmdLineArgs& args, int flags)
{
const bool setup_vmode = (flags & INIT_HAVE_VMODE) == 0;
if(setup_vmode)
{
InitSDL();
if (!g_VideoMode.InitSDL())
throw PSERROR_System_VmodeFailed(); // abort startup
#if !SDL_VERSION_ATLEAST(2, 0, 0)
SDL_WM_SetCaption("0 A.D.", "0 A.D.");
#endif
}
RunHardwareDetection();
const int quality = SANE_TEX_QUALITY_DEFAULT; // TODO: set value from config file
SetTextureQuality(quality);
ogl_WarnIfError();
// Optionally start profiler GPU timings automatically
// (By default it's only enabled by a hotkey, for performance/compatibility)
bool profilerGPUEnable = false;
CFG_GET_VAL("profiler2.gpu.autoenable", Bool, profilerGPUEnable);
if (profilerGPUEnable)
g_Profiler2.EnableGPU();
if(!g_Quickstart)
{
WriteSystemInfo();
// note: no longer vfs_display here. it's dog-slow due to unbuffered
// file output and very rarely needed.
}
if(g_DisableAudio)
ISoundManager::SetEnabled(false);
g_GUI = new CGUIManager();
// (must come after SetVideoMode, since it calls ogl_Init)
if (ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", NULL) != 0 // ARB
&& ogl_HaveExtensions(0, "GL_ARB_vertex_shader", "GL_ARB_fragment_shader", NULL) != 0) // GLSL
{
DEBUG_DISPLAY_ERROR(
L"Your graphics card doesn't appear to be fully compatible with OpenGL shaders."
L" In the future, the game will not support pre-shader graphics cards."
L" You are advised to try installing newer drivers and/or upgrade your graphics card."
L" For more information, please see http://www.wildfiregames.com/forum/index.php?showtopic=16734"
);
// TODO: actually quit once fixed function support is dropped
}
const char* missing = ogl_HaveExtensions(0,
"GL_ARB_multitexture",
"GL_EXT_draw_range_elements",
"GL_ARB_texture_env_combine",
"GL_ARB_texture_env_dot3",
NULL);
if(missing)
{
wchar_t buf[500];
swprintf_s(buf, ARRAY_SIZE(buf),
L"The %hs extension doesn't appear to be available on your computer."
L" The game may still work, though - you are welcome to try at your own risk."
L" If not or it doesn't look right, upgrade your graphics card.",
missing
);
DEBUG_DISPLAY_ERROR(buf);
// TODO: i18n
}
if (!ogl_HaveExtension("GL_ARB_texture_env_crossbar"))
{
DEBUG_DISPLAY_ERROR(
L"The GL_ARB_texture_env_crossbar extension doesn't appear to be available on your computer."
L" Shadows are not available and overall graphics quality might suffer."
L" You are advised to try installing newer drivers and/or upgrade your graphics card.");
g_Shadows = false;
}
ogl_WarnIfError();
InitRenderer();
InitInput();
ogl_WarnIfError();
try
{
if (!Autostart(args))
{
const bool setup_gui = ((flags & INIT_NO_GUI) == 0);
// We only want to display the splash screen at startup
shared_ptr scriptInterface = g_GUI->GetScriptInterface();
JSContext* cx = scriptInterface->GetContext();
JSAutoRequest rq(cx);
JS::RootedValue data(cx);
if (g_GUI)
{
scriptInterface->Eval("({})", &data);
scriptInterface->SetProperty(data, "isStartup", true);
}
InitPs(setup_gui, L"page_pregame.xml", g_GUI->GetScriptInterface().get(), data);
}
}
catch (PSERROR_Game_World_MapLoadFailed& e)
{
// Map Loading failed
// Start the engine so we have a GUI
InitPs(true, L"page_pregame.xml", NULL, JS::UndefinedHandleValue);
// Call script function to do the actual work
// (delete game data, switch GUI page, show error, etc.)
CancelLoad(CStr(e.what()).FromUTF8());
}
}
void RenderGui(bool RenderingState)
{
g_DoRenderGui = RenderingState;
}
void RenderLogger(bool RenderingState)
{
g_DoRenderLogger = RenderingState;
}
void RenderCursor(bool RenderingState)
{
g_DoRenderCursor = RenderingState;
}
/**
* Temporarily loads a scenario map and retrieves the "ScriptSettings" JSON
* data from it.
* The scenario map format is used for scenario and skirmish map types (random
* games do not use a "map" (format) but a small JavaScript program which
* creates a map on the fly). It contains a section to initialize the game
* setup screen.
* @param mapPath Absolute path (from VFS root) to the map file to peek in.
* @return ScriptSettings in JSON format extracted from the map.
*/
CStr8 LoadSettingsOfScenarioMap(const VfsPath &mapPath)
{
CXeromyces mapFile;
const char *pathToSettings[] =
{
"Scenario", "ScriptSettings", "" // Path to JSON data in map
};
Status loadResult = mapFile.Load(g_VFS, mapPath);
if (INFO::OK != loadResult)
{
LOGERROR(L"Unable to load map file - maybe path typo?");
throw PSERROR_Game_World_MapLoadFailed("Unable to load map file - maybe path typo?");
}
XMBElement mapElement = mapFile.GetRoot();
// Select the ScriptSettings node in the map file...
for (int i = 0; pathToSettings[i][0]; i++)
{
int childId = mapFile.GetElementID(pathToSettings[i]);
XMBElementList children = mapElement.GetChildNodes();
for (int childIndex = 0; childIndex < children.Count; childIndex++)
{
XMBElement child = children.Item(childIndex);
if (child.GetNodeName() == childId)
{
mapElement = child;
break;
}
}
}
// ... they contain a JSON document to initialize the game setup
// screen
return mapElement.GetText();
}
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)
* -autostart-civ=1:hele -- set player #1 civ to "hele"
*
* 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();
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 (as was
// done by -autostart-random)
// - 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-random");
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 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(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(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")
{
// Initialize general settings from the map data so some values
// (e.g. name of map) are always present, even when autostart is
// less-than-completely configured
// (Omitting this may cause the loading screen to display "Loading (undefined)",
// for example...)
CStr8 mapSettingsJSON = LoadSettingsOfScenarioMap("maps/" + autoStartName + ".xml");
scriptInterface.ParseJSON(mapSettingsJSON, &settings);
mapType = "scenario";
}
else if (mapDirectory == L"skirmishes")
{
// In skirmish mode, the player initialization data is taken from the
// game-setup settings (see CGame::RegisterInit(...)). If some player
// is not initialized (PlayerData[] holding fewer/more entries than
// defined in map), there's a crash.
// To prevent this, we mimic the behavior of the game setup screen by
// retrieving the map settings from the actual map xml...
CStr8 mapSettingsJSON = LoadSettingsOfScenarioMap("maps/" + autoStartName + ".xml");
scriptInterface.ParseJSON(mapSettingsJSON, &settings);
// ...and initialize the playerData array being edited by
// autostart-civ et.al. with the real map data, so sensible values
// are always present:
scriptInterface.GetProperty(settings, "PlayerData", &playerData);
mapType = "skirmish";
}
if (mapType.empty())
{
LOGERROR(L"Unrecognized map type '%ls' detected", mapType.c_str());
throw PSERROR_Game_World_MapLoadFailed("Unrecognized map type.\nConsult GameSetup.cpp for the currently supported types.");
}
scriptInterface.SetProperty(attrs, "mapType", mapType);
scriptInterface.SetProperty(attrs, "map", std::string("maps/" + autoStartName));
scriptInterface.SetProperty(settings, "mapType", mapType);
// Set player data for AIs
// attrs.settings = { PlayerData: [ { AI: ... }, ... ] }:
if (args.Has("autostart-ai"))
{
std::vector aiArgs = args.GetMultiple("autostart-ai");
for (size_t i = 0; i < aiArgs.size(); ++i)
{
// Instead of overwriting existing player data, modify the array
JS::RootedValue player(cx);
if (!scriptInterface.GetPropertyInt(playerData, i, &player) || player.isUndefined())
{
scriptInterface.Eval("({})", &player);
}
int playerID = aiArgs[i].BeforeFirst(":").ToInt();
CStr name = aiArgs[i].AfterFirst(":");
scriptInterface.SetProperty(player, "AI", std::string(name));
scriptInterface.SetProperty(player, "AIDiff", 2);
scriptInterface.SetPropertyInt(playerData, playerID-1, player);
}
}
// Set AI difficulty
if (args.Has("autostart-aidiff"))
{
std::vector civArgs = args.GetMultiple("autostart-aidiff");
for (size_t i = 0; i < civArgs.size(); ++i)
{
// Instead of overwriting existing player data, modify the array
JS::RootedValue player(cx);
if (!scriptInterface.GetPropertyInt(playerData, i, &player) || player.isUndefined())
{
scriptInterface.Eval("({})", &player);
}
int playerID = civArgs[i].BeforeFirst(":").ToInt();
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"))
{
std::vector civArgs = args.GetMultiple("autostart-civ");
for (size_t i = 0; i < civArgs.size(); ++i)
{
// Instead of overwriting existing player data, modify the array
JS::RootedValue player(cx);
if (!scriptInterface.GetPropertyInt(playerData, i, &player) || player.isUndefined())
{
scriptInterface.Eval("({})", &player);
}
int playerID = civArgs[i].BeforeFirst(":").ToInt();
CStr name = civArgs[i].AfterFirst(":");
scriptInterface.SetProperty(player, "Civ", std::string(name));
scriptInterface.SetPropertyInt(playerData, playerID-1, player);
}
}
// 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-players"))
{
maxPlayers = args.Get("autostart-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 = "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(CScriptValRooted(cx, attrs), "");
LDR_NonprogressiveLoad();
PSRETURN ret = g_Game->ReallyStartGame();
ENSURE(ret == PSRETURN_OK);
InitPs(true, L"page_session.xml", NULL, JS::UndefinedHandleValue);
}
return true;
}
void CancelLoad(const CStrW& message)
{
shared_ptr pScriptInterface = g_GUI->GetActiveGUI()->GetScriptInterface();
JSContext* cx = pScriptInterface->GetContext();
JSAutoRequest rq(cx);
JS::RootedValue global(cx, pScriptInterface->GetGlobalObject());
// Cancel loader
LDR_Cancel();
// Call the cancelOnError GUI function, defined in ..gui/common/functions_utility_error.js
// So all GUI pages that load games should include this script
if (g_GUI && g_GUI->HasPages())
{
if (pScriptInterface->HasProperty(global, "cancelOnError" ))
pScriptInterface->CallFunctionVoid(global, "cancelOnError", message);
}
}
bool InDevelopmentCopy()
{
if (!g_CheckedIfInDevelopmentCopy)
{
g_InDevelopmentCopy = (g_VFS->GetFileInfo(L"config/dev.cfg", NULL) == INFO::OK);
g_CheckedIfInDevelopmentCopy = true;
}
return g_InDevelopmentCopy;
}
Index: ps/trunk/source/ps/scripting/JSInterface_Mod.cpp
===================================================================
--- ps/trunk/source/ps/scripting/JSInterface_Mod.cpp (nonexistent)
+++ ps/trunk/source/ps/scripting/JSInterface_Mod.cpp (revision 15677)
@@ -0,0 +1,128 @@
+/* Copyright (C) 2014 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "scriptinterface/ScriptInterface.h"
+#include "scriptinterface/ScriptVal.h"
+
+#include "lib/file/file_system.h"
+#include "lib/file/vfs/vfs.h"
+#include "lib/utf8.h"
+#include "ps/Filesystem.h"
+#include "ps/GameSetup/GameSetup.h"
+#include "ps/GameSetup/Paths.h"
+#include "ps/Mod.h"
+#include "ps/scripting/JSInterface_Mod.h"
+
+#include
+
+extern void restart_engine();
+
+/**
+ * Returns a JS object containing a listing of available mods that
+ * have a modname.json file in their modname folder. The returned
+ * object looks like { modname1: json1, modname2: json2, ... } where
+ * jsonN is the content of the modnameN/modnameN.json file as a JS
+ * object.
+ *
+ * @return JS object with available mods as the keys of the modname.json
+ * properties.
+ */
+CScriptVal JSI_Mod::GetAvailableMods(ScriptInterface::CxPrivate* pCxPrivate)
+{
+ ScriptInterface* scriptInterface = pCxPrivate->pScriptInterface;
+ JSContext* cx = scriptInterface->GetContext();
+ JSAutoRequest rq(cx);
+ JS::RootedObject obj(cx, JS_NewObject(cx, NULL, NULL, NULL));
+
+ const Paths paths(g_args);
+
+ // loop over all possible paths
+ OsPath modPath = paths.RData()/"mods";
+ OsPath modUserPath = paths.UserData()/"mods";
+
+ DirectoryNames modDirs;
+ DirectoryNames modDirsUser;
+
+ GetDirectoryEntries(modPath, NULL, &modDirs);
+ // Sort modDirs so that we can do a fast lookup below
+ std::sort(modDirs.begin(), modDirs.end());
+
+ PIVFS vfs = CreateVfs(1); // No cache needed; TODO but 0 crashes
+
+ for (DirectoryNames::iterator iter = modDirs.begin(); iter != modDirs.end(); ++iter)
+ {
+ vfs->Clear();
+ if (vfs->Mount(L"", modPath / *iter, VFS_MOUNT_MUST_EXIST) < 0)
+ continue;
+
+ CVFSFile modinfo;
+ if (modinfo.Load(vfs, L"mod.json", false) != PSRETURN_OK)
+ continue;
+
+ JS::RootedValue json(cx);
+ scriptInterface->ParseJSON(modinfo.GetAsString(), &json);
+
+ // Valid mod, add it to our structure
+ JS_SetProperty(cx, obj, utf8_from_wstring(iter->string()).c_str(), json.address());
+ }
+
+ GetDirectoryEntries(modUserPath, NULL, &modDirsUser);
+ bool dev = InDevelopmentCopy();
+
+ for (DirectoryNames::iterator iter = modDirsUser.begin(); iter != modDirsUser.end(); ++iter)
+ {
+ // If we are in a dev copy we do not mount mods in the user mod folder that
+ // are already present in the mod folder, thus we skip those here.
+ if (dev && std::binary_search(modDirs.begin(), modDirs.end(), *iter))
+ continue;
+
+ vfs->Clear();
+ if (vfs->Mount(L"", modUserPath / *iter, VFS_MOUNT_MUST_EXIST) < 0)
+ continue;
+
+ CVFSFile modinfo;
+ if (modinfo.Load(vfs, L"mod.json", false) != PSRETURN_OK)
+ continue;
+
+ JS::RootedValue json(cx);
+ scriptInterface->ParseJSON(modinfo.GetAsString(), &json);
+
+ // Valid mod, add it to our structure
+ JS_SetProperty(cx, obj, utf8_from_wstring(iter->string()).c_str(), json.address());
+ }
+
+ return JS::ObjectValue(*obj);
+}
+
+void JSI_Mod::RestartEngine(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
+{
+ restart_engine();
+}
+
+void JSI_Mod::SetMods(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::vector mods)
+{
+ g_modsLoaded = mods;
+}
+
+void JSI_Mod::RegisterScriptFunctions(ScriptInterface& scriptInterface)
+{
+ scriptInterface.RegisterFunction("GetAvailableMods");
+ scriptInterface.RegisterFunction("RestartEngine");
+ scriptInterface.RegisterFunction, &JSI_Mod::SetMods>("SetMods");
+}
Index: ps/trunk/source/ps/scripting/JSInterface_Mod.h
===================================================================
--- ps/trunk/source/ps/scripting/JSInterface_Mod.h (nonexistent)
+++ ps/trunk/source/ps/scripting/JSInterface_Mod.h (revision 15677)
@@ -0,0 +1,32 @@
+/* Copyright (C) 2014 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_JSI_MOD
+#define INCLUDED_JSI_MOD
+
+class ScriptInterface;
+class CScriptVal;
+
+namespace JSI_Mod
+{
+ void RegisterScriptFunctions(ScriptInterface& scriptInterface);
+ CScriptVal GetAvailableMods(ScriptInterface::CxPrivate* pCxPrivate);
+ void RestartEngine(ScriptInterface::CxPrivate* pCxPrivate);
+ void SetMods(ScriptInterface::CxPrivate* pCxPrivate, std::vector mods);
+}
+
+#endif
Index: ps/trunk/source/tools/dist/0ad.nsi
===================================================================
--- ps/trunk/source/tools/dist/0ad.nsi (revision 15676)
+++ ps/trunk/source/tools/dist/0ad.nsi (revision 15677)
@@ -1,202 +1,206 @@
; To generate the installer (on Linux):
; Do an 'svn export' into a directory called e.g. "export-win32"
; makensis -nocd -dcheckoutpath=export-win32 -drevision=1234 -dprefix=0ad-0.1.2-alpha export-win32/source/tools/dist/0ad.nsi
SetCompressor /SOLID lzma
!include "MUI2.nsh"
!include "LogicLib.nsh"
; Control whether to include source code (and component selection screen)
!define INCLUDE_SOURCE 0
;--------------------------------
;General
;Name and file
Name "0 A.D."
OutFile "${PREFIX}-win32.exe"
;Default installation folder
InstallDir "$LOCALAPPDATA\0 A.D. alpha"
; NOTE: we can't use folder names ending in "." because they seemingly get stripped
;Get installation folder from registry if available
InstallDirRegKey HKCU "Software\0 A.D." ""
RequestExecutionLevel user
;--------------------------------
;Variables
Var StartMenuFolder
;--------------------------------
;Interface Settings
!define MUI_WELCOMEFINISHPAGE_BITMAP ${CHECKOUTPATH}\build\resources\installer.bmp
!define MUI_ICON ${CHECKOUTPATH}\build\resources\ps.ico
!define MUI_ABORTWARNING
;--------------------------------
;Pages
!insertmacro MUI_PAGE_WELCOME
!if INCLUDE_SOURCE
!insertmacro MUI_PAGE_COMPONENTS
!endif
!insertmacro MUI_PAGE_DIRECTORY
;Start Menu Folder Page Configuration
!define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKCU"
!define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\0 A.D."
!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
!define MUI_STARTMENUPAGE_DEFAULTFOLDER "0 A.D. alpha"
!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder
!insertmacro MUI_PAGE_INSTFILES
!define MUI_FINISHPAGE_RUN $INSTDIR\binaries\system\pyrogenesis.exe
+ !define MUI_FINISHPAGE_RUN_PARAMETERS "-mod=public"
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
;--------------------------------
;Languages
!insertmacro MUI_LANGUAGE "English"
;--------------------------------
;Installer Sections
Section "!Game and data files" GameSection
SetOutPath "$INSTDIR"
File "${CHECKOUTPATH}\*.txt"
File "${CHECKOUTPATH}\source\tools\openlogsfolder\*.*"
File /r /x "public" /x "dev.cfg" "${CHECKOUTPATH}\binaries"
SetOutPath "$INSTDIR\binaries\data\mods\public"
File "${CHECKOUTPATH}\binaries\data\mods\public\public.zip"
+ SetOutPath "$INSTDIR\binaries\data\mods\mod"
+ File "${CHECKOUTPATH}\binaries\data\mods\mod\mod.zip"
;Store installation folder
WriteRegStr SHCTX "Software\0 A.D." "" $INSTDIR
;Create uninstaller
WriteUninstaller "$INSTDIR\Uninstall.exe"
;Add uninstall information
WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "DisplayName" "0 A.D."
WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "DisplayVersion" "r${REVISION}-alpha"
WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "VersionMajor" 0
WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "VersionMinor" ${REVISION}
WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "Publisher" "Wildfire Games"
WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "DisplayIcon" "$\"$INSTDIR\binaries\system\pyrogenesis.exe$\""
WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "InstallLocation" "$\"$INSTDIR$\""
WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "UninstallString" "$\"$INSTDIR\Uninstall.exe$\""
WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "QuietUninstallString" "$\"$INSTDIR\Uninstall.exe$\" /S"
WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "URLInfoAbout" "http://play0ad.com"
WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "NoModify" 1
WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "NoRepair" 1
!insertmacro MUI_STARTMENU_WRITE_BEGIN Application
;Create shortcuts
CreateDirectory "$SMPROGRAMS\$StartMenuFolder"
SetOutPath "$INSTDIR\binaries\system" ;Set working directory of shortcuts
- CreateShortCut "$SMPROGRAMS\$StartMenuFolder\0 A.D..lnk" "$INSTDIR\binaries\system\pyrogenesis.exe" ""
+ CreateShortCut "$SMPROGRAMS\$StartMenuFolder\0 A.D..lnk" "$INSTDIR\binaries\system\pyrogenesis.exe" "-mod=public"
+ CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Pyrogenesis mod selector.lnk" "$INSTDIR\binaries\system\pyrogenesis.exe" ""
CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Map editor.lnk" "$INSTDIR\binaries\system\pyrogenesis.exe" "-editor" "$INSTDIR\binaries\data\tools\atlas\icons\ScenarioEditor.ico"
SetOutPath "$INSTDIR"
CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Open logs folder.lnk" "$INSTDIR\OpenLogsFolder.bat"
CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" "$INSTDIR\Uninstall.exe"
WriteINIStr "$SMPROGRAMS\$StartMenuFolder\Web site.url" "InternetShortcut" "URL" "http://play0ad.com/"
!insertmacro MUI_STARTMENU_WRITE_END
SectionEnd
!if INCLUDE_SOURCE
Section /o "Source code" SourceSection
SetOutPath "$INSTDIR"
File /r ${CHECKOUTPATH}\source
File /r ${CHECKOUTPATH}\docs
File /r ${CHECKOUTPATH}\build
File /r ${CHECKOUTPATH}\libraries
SectionEnd
!endif
;--------------------------------
;Installer Functions
Function .onInit
ReadRegStr $R0 SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "UninstallString"
StrCmp $R0 "" done
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION \
"0 A.D. is already installed.$\n$\nClick $\"OK$\" to remove the previous version, or $\"Cancel$\" to stop this installation." \
IDOK uninst
Abort
;Run the uninstaller
uninst:
ClearErrors
ExecWait '$R0 _?=$INSTDIR' ;Do not copy the uninstaller to a temp file
done:
FunctionEnd
;--------------------------------
;Descriptions
!if INCLUDE_SOURCE
;Assign descriptions to sections
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${GameSection} "0 A.D. game executable and data."
!insertmacro MUI_DESCRIPTION_TEXT ${SourceSection} "Source code and build tools."
!insertmacro MUI_FUNCTION_DESCRIPTION_END
!endif
;--------------------------------
;Uninstaller Section
Section "Uninstall"
RMDir /r "$INSTDIR\binaries"
!if INCLUDE_SOURCE
RMDir /r "$INSTDIR\source"
RMDir /r "$INSTDIR\docs"
RMDir /r "$INSTDIR\build"
RMDir /r "$INSTDIR\libraries"
!endif
Delete "$INSTDIR\*.txt"
Delete "$INSTDIR\*.bat"
Delete "$INSTDIR\OpenLogsFolder.vbs"
Delete "$INSTDIR\Uninstall.exe"
RMDir "$INSTDIR"
RMDir /r "$LOCALAPPDATA\0ad\cache"
RMDir /r "$LOCALAPPDATA\0ad\logs"
; leave the other directories (screenshots, config files, etc)
!insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder
Delete "$SMPROGRAMS\$StartMenuFolder\Open logs folder.lnk"
Delete "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk"
Delete "$SMPROGRAMS\$StartMenuFolder\Map editor.lnk"
Delete "$SMPROGRAMS\$StartMenuFolder\0 A.D..lnk"
Delete "$SMPROGRAMS\$StartMenuFolder\Web site.url"
RMDir "$SMPROGRAMS\$StartMenuFolder"
DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D."
DeleteRegKey /ifempty SHCTX "Software\0 A.D."
SectionEnd
Index: ps/trunk/source/tools/dist/build.sh
===================================================================
--- ps/trunk/source/tools/dist/build.sh (revision 15676)
+++ ps/trunk/source/tools/dist/build.sh (revision 15677)
@@ -1,63 +1,65 @@
#!/bin/bash
set -ev
# Compiled executable for archive-builder tool
EXE=/mnt/0ad/0ad/binaries/system/pyrogenesis
# Location of clean checkout
SVNWC=/mnt/0ad/0ad/
SVNREV=`svnversion -n ${SVNWC}`
PREFIX=0ad-0.0.XXX-alpha
XZOPTS="-9 -e"
BZ2OPTS="-9"
GZIPOPTS="-9"
GZIP7ZOPTS="-mx=9"
# Export files with appropriate line-endings
rm -rf export-unix
rm -rf export-win32
svn export ${SVNWC} export-unix
svn export --native-eol CRLF ${SVNWC} export-win32
# Only include translations for a subset of languages
find export-{unix,win32}/binaries/data/ -name "*.po" | grep -v '.*/\(ca\|cs\|de\|en_GB\|es\|fr\|gd\|gl\|it\|nl\|pt_PT\|pt_BR\)\.[A-Za-z0-9_.]\+\.po' | xargs rm
# Update the svn_revision, so these builds can be identified
echo L\"${SVNREV}-release\" > export-unix/build/svn_revision/svn_revision.txt
echo L\"${SVNREV}-release\" > export-win32/build/svn_revision/svn_revision.txt
# Package the mod data
# (The platforms differ only in line endings, so just do the Unix one instead of
# generating two needlessly inconsistent packages)
-${EXE} -archivebuild=export-unix/binaries/data/mods/public -archivebuild-output=export-unix/binaries/data/mods/public/public.zip
+${EXE} -mod=mod -archivebuild=export-unix/binaries/data/mods/public -archivebuild-output=export-unix/binaries/data/mods/public/public.zip
cp export-unix/binaries/data/mods/public/public.zip export-win32/binaries/data/mods/public/public.zip
+${EXE} -archivebuild=export-unix/binaries/data/mods/mod -archivebuild-output=export-unix/binaries/data/mods/public/mod/mod.zip
+cp export-unix/binaries/data/mods/mod/mod.zip export-win32/binaries/data/mods/mod/mod.zip
# Collect the relevant files
ln -Tsf export-unix ${PREFIX}
tar cf $PREFIX-unix-build.tar \
--exclude='*.bat' --exclude='*.dll' --exclude='*.exe' --exclude='*.lib' \
--exclude='libraries/source/fcollada/src/FCollada/FColladaTest' \
--exclude='libraries/source/spidermonkey/include-win32-*' \
${PREFIX}/{source,build,libraries/source,binaries/system/readme.txt,binaries/data/l10n,binaries/data/tests,binaries/data/mods/_test.*,*.txt}
tar cf $PREFIX-unix-data.tar \
--exclude='binaries/data/config/dev.cfg' \
- ${PREFIX}/binaries/data/{config,mods/public/public.zip,tools}
+ ${PREFIX}/binaries/data/{config,mods/mod/mod.zip,mods/public/public.zip,tools}
# TODO: ought to include generated docs in here, perhaps?
# Compress
xz -kv ${XZOPTS} $PREFIX-unix-build.tar
xz -kv ${XZOPTS} $PREFIX-unix-data.tar
7z a ${GZIP7ZOPTS} $PREFIX-unix-build.tar.gz $PREFIX-unix-build.tar
7z a ${GZIP7ZOPTS} $PREFIX-unix-data.tar.gz $PREFIX-unix-data.tar
# Create Windows installer
makensis -nocd -dcheckoutpath=export-win32 -drevision=${SVNREV} -dprefix=${PREFIX} export-win32/source/tools/dist/0ad.nsi
# Fix permissions
chmod -f 644 ${PREFIX}-{unix-{build,data}.tar.{xz,bz2,gz},win32.exe}
# Print digests for copying into wiki page
sha1sum ${PREFIX}-{unix-{build,data}.tar.{xz,bz2,gz},win32.exe}