Index: ps/trunk/binaries/data/mods/public/gui/options/options.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/options/options.js (revision 20010)
+++ ps/trunk/binaries/data/mods/public/gui/options/options.js (revision 20011)
@@ -1,409 +1,418 @@
var g_HasCallback = false;
var g_Controls;
function init(data)
{
if (data && data.callback)
g_HasCallback = true;
g_Controls = {};
var options = Engine.ReadJSONFile("gui/options/options.json");
for (let category in options)
{
let lastSize;
for (let i = 0; i < options[category].length; ++i)
{
let option = options[category][i];
if (!option.label || !option.parameters || !option.parameters.config)
continue;
let body = Engine.GetGUIObjectByName(category + "[" + i + "]");
let label = Engine.GetGUIObjectByName(category + "Label[" + i + "]");
let config = option.parameters.config;
g_Controls[config] = {
"control": setupControl(option, i, category),
"label": label,
"type": option.type,
"dependencies": option.dependencies || undefined,
"parameters": option.parameters
};
label.caption = translate(option.label);
label.tooltip = option.tooltip ? translate(option.tooltip) : "";
// Move each element to the correct place.
if (lastSize)
{
let newSize = new GUISize();
newSize.left = lastSize.left;
newSize.rright = lastSize.rright;
newSize.top = lastSize.bottom;
newSize.bottom = newSize.top + 26;
body.size = newSize;
lastSize = newSize;
}
else
lastSize = body.size;
// small right shift of options which depends on another one
for (let opt of options[category])
{
if (!opt.label || !opt.parameters || !opt.parameters.config)
continue;
if (!opt.dependencies || opt.dependencies.indexOf(config) === -1)
continue;
label.caption = " " + label.caption;
break;
}
// Show element.
body.hidden = false;
}
}
updateOptionPanel();
}
/**
* Setup the apropriate control for a given option.
*
* @param option Structure containing the data to setup an option.
* @param prefix Prefix to use when accessing control, for example "generalSetting" when the tickbox name is generalSettingTickbox[i].
*/
function setupControl(option, i, category)
{
var control;
var onUpdate;
var key = option.parameters.config;
switch (option.type)
{
case "boolean":
// More space for the label
let text = Engine.GetGUIObjectByName(category + "Label[" + i + "]");
let size = text.size;
size.rright = 87;
text.size = size;
control = Engine.GetGUIObjectByName(category + "Tickbox[" + i + "]");
let checked;
let keyRenderer;
for (let param in option.parameters)
{
switch (param)
{
case "config":
checked = Engine.ConfigDB_GetValue("user", key) === "true";
break;
case "renderer":
keyRenderer = option.parameters.renderer;
if (!Engine["Renderer_Get" + keyRenderer + "Enabled"])
{
warn("Invalid renderer key " + keyRenderer);
keyRenderer = undefined;
break;
}
if (Engine["Renderer_Get" + keyRenderer + "Enabled"]() !== checked)
{
warn("Incompatible renderer option value for " + keyRenderer);
Engine["Renderer_Set" + keyRenderer + "Enabled"](checked);
}
break;
default:
warn("Unknown option source type '" + param + "'");
}
}
onUpdate = function(key, keyRenderer)
{
return function()
{
if (keyRenderer)
Engine["Renderer_Set" + keyRenderer + "Enabled"](this.checked);
Engine.ConfigDB_CreateValue("user", key, String(this.checked));
Engine.ConfigDB_SetChanges("user", true);
updateOptionPanel();
};
}(key, keyRenderer);
// Load final data to the control element.
control.checked = checked;
control.onPress = onUpdate;
break;
case "slider":
control = Engine.GetGUIObjectByName(category + "Slider[" + i + "]");
let value;
let callbackFunction;
let minvalue;
let maxvalue;
for (let param in option.parameters)
{
switch (param)
{
case "config":
value = +Engine.ConfigDB_GetValue("user", key);
break;
case "function":
if (Engine[option.parameters.function])
callbackFunction = option.parameters.function;
break;
case "min":
minvalue = +option.parameters.min;
break;
case "max":
maxvalue = +option.parameters.max;
break;
default:
warn("Unknown option source type '" + param + "'");
}
}
onUpdate = function(key, callbackFunction, minvalue, maxvalue)
{
return function()
{
this.tooltip =
(option.tooltip ? translate(option.tooltip) + "\n" : "") +
sprintf(translateWithContext("slider number", "Value: %(val)s (min: %(min)s, max: %(max)s)"), {
"val": +this.value.toFixed(2),
"min": +minvalue.toFixed(2),
"max": +maxvalue.toFixed(2)
});
if (+Engine.ConfigDB_GetValue("user", key) === this.value)
return;
Engine.ConfigDB_CreateValue("user", key, this.value);
Engine.ConfigDB_SetChanges("user", true);
if (callbackFunction)
Engine[callbackFunction](+this.value);
updateOptionPanel();
};
}(key, callbackFunction, minvalue, maxvalue);
control.value = value;
control.max_value = maxvalue;
control.min_value = minvalue;
control.onValueChange = onUpdate;
break;
case "number":
case "string":
control = Engine.GetGUIObjectByName(category + "Input[" + i + "]");
let caption;
let functionBody;
let minval;
let maxval;
for (let param in option.parameters)
{
switch (param)
{
case "config":
caption = Engine.ConfigDB_GetValue("user", key);
break;
case "function":
if (Engine[option.parameters.function])
functionBody = option.parameters.function;
break;
case "min":
minval = option.parameters.min;
break;
case "max":
maxval = option.parameters.max;
break;
default:
warn("Unknown option source type '" + param + "'");
}
}
// as the enter key is not necessarily pressed after modifying an entry, we will register the input also
// - when the mouse leave the control (MouseLeave event)
// - or when saving or closing the window (registerChanges function)
// so we must ensure that something has indeed been modified
onUpdate = function(key, functionBody, minval, maxval)
{
return function()
{
if (minval && +minval > +this.caption)
this.caption = minval;
if (maxval && +maxval < +this.caption)
this.caption = maxval;
if (Engine.ConfigDB_GetValue("user", key) === this.caption)
return;
Engine.ConfigDB_CreateValue("user", key, this.caption);
Engine.ConfigDB_SetChanges("user", true);
if (functionBody)
Engine[functionBody](+this.caption);
updateOptionPanel();
};
}(key, functionBody, minval, maxval);
control.caption = caption;
control.onPress = onUpdate;
control.onMouseLeave = onUpdate;
break;
case "dropdown":
+ {
control = Engine.GetGUIObjectByName(category + "Dropdown[" + i + "]");
control.onSelectionChange = function(){}; // just the time to setup the value
let config;
+ let callbackFunction;
for (let param in option.parameters)
{
switch (param)
{
case "config":
config = Engine.ConfigDB_GetValue("user", key);
break;
+ case "function":
+ if (Engine[option.parameters.function])
+ callbackFunction = option.parameters.function;
+ break;
case "list":
control.list = option.parameters.list.map(e => translate(e.label));
let values = option.parameters.list.map(e => e.value);
control.list_data = values;
control.selected = values.map(String).indexOf(config);
break;
default:
warn("Unknown option source type '" + param + "'");
}
}
- onUpdate = function(key)
+ onUpdate = function(key, callbackFunction)
{
return function()
{
Engine.ConfigDB_CreateValue("user", key, this.list_data[this.selected]);
Engine.ConfigDB_SetChanges("user", true);
+ if (callbackFunction)
+ Engine[callbackFunction](this.list_data[this.selected]);
updateOptionPanel();
};
- }(key);
+ }(key, callbackFunction);
control.onSelectionChange = onUpdate;
break;
+ }
default:
warn("Unknown option type " + option.type + ", assuming string.");
control = Engine.GetGUIObjectByName(category + "Input[" + i + "]");
break;
}
control.hidden = false;
if (option.type == "slider")
control.onValueChange();
else
control.tooltip = option.tooltip ? translate(option.tooltip) : "";
return control;
}
function updateOptionPanel()
{
// Update dependencies
for (let item in g_Controls)
{
let control = g_Controls[item];
if (control.type != "boolean" || !control.dependencies)
continue;
for (let dependency of control.dependencies)
{
g_Controls[dependency].control.enabled = control.control.checked;
g_Controls[dependency].label.enabled = control.control.checked;
}
}
// And main buttons
let hasChanges = Engine.ConfigDB_HasChanges("user");
Engine.GetGUIObjectByName("revertChanges").enabled = hasChanges;
Engine.GetGUIObjectByName("saveChanges").enabled = hasChanges;
}
/**
* Register changes of input (text and number) controls
*/
function registerChanges()
{
for (let item in g_Controls)
if (g_Controls[item].type === "number" || g_Controls[item].type === "string")
g_Controls[item].control.onPress();
}
function setDefaults()
{
messageBox(
500, 200,
translate("Resetting the options will erase your saved settings. Do you want to continue?"),
translate("Warning"),
[translate("No"), translate("Yes")],
[null, reallySetDefaults]
);
}
function reallySetDefaults()
{
for (let item in g_Controls)
Engine.ConfigDB_RemoveValue("user", item);
Engine.ConfigDB_WriteFile("user", "config/user.cfg");
revertChanges();
}
function revertChanges()
{
Engine.ConfigDB_Reload("user");
for (let item in g_Controls)
{
let control = g_Controls[item];
// needs to update renderer values (which are all of boolean type)
if (control.parameters.renderer)
{
if (control.type != "boolean")
{
warn("Invalid type option " + control.type + " defined in renderer for " + item + ": will not be reverted");
continue;
}
let checked = Engine.ConfigDB_GetValue("user", item) === "true";
Engine["Renderer_Set" + control.parameters.renderer + "Enabled"](checked);
}
// and the possible function calls (which are of number or string types)
if (control.parameters.function)
{
- if (control.type !== "string" && control.type !== "number" && control.type !== "slider")
+ if (control.type != "string" && control.type != "number" && control.type != "slider" && control.type != "dropdown")
{
warn("Invalid type option " + control.type + " defined with function for " + item + ": will not be reverted");
continue;
}
let caption = Engine.ConfigDB_GetValue("user", item);
Engine[control.parameters.function](+caption);
}
}
Engine.ConfigDB_SetChanges("user", false);
init();
}
function saveChanges()
{
registerChanges();
Engine.ConfigDB_WriteFile("user", "config/user.cfg");
Engine.ConfigDB_SetChanges("user", false);
updateOptionPanel();
}
/**
* Close GUI page and call callbacks if they exist.
**/
function closePage()
{
registerChanges();
if (Engine.ConfigDB_HasChanges("user"))
{
messageBox(
500, 200,
translate("You have unsaved changes, do you want to close this window?"),
translate("Warning"),
[translate("No"), translate("Yes")],
[null, closePageWithoutConfirmation]
);
}
else
closePageWithoutConfirmation();
}
function closePageWithoutConfirmation()
{
if (g_HasCallback)
Engine.PopGuiPageCB();
else
Engine.PopGuiPage();
}
Index: ps/trunk/binaries/data/mods/public/gui/options/options.json
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/options/options.json (revision 20010)
+++ ps/trunk/binaries/data/mods/public/gui/options/options.json (revision 20011)
@@ -1,355 +1,356 @@
{
"generalSetting":
[
{
"type": "string",
"label": "Playername (Single Player)",
"tooltip": "How you want to be addressed in Single Player matches.",
"parameters": { "config": "playername.singleplayer" }
},
{
"type": "string",
"label": "Playername (Multiplayer)",
"tooltip": "How you want to be addressed in Multiplayer matches (except lobby).",
"parameters": { "config": "playername.multiplayer" }
},
{
"type": "boolean",
"label": "Windowed Mode",
"tooltip": "Start 0 A.D. in a window",
"parameters": { "config": "windowed" }
},
{
"type": "boolean",
"label": "Background Pause",
"tooltip": "Pause single player games when window loses focus",
"parameters": { "config": "pauseonfocusloss" }
},
{
"type": "boolean",
"label": "Enable Welcome Screen",
"tooltip": "If you disable it, the welcome screen will still appear once, each time a new version is available. You can always launch it from the main menu.",
"parameters": { "config": "gui.splashscreen.enable" }
},
{
"type": "boolean",
"label": "Enable Game Setting Tips",
"tooltip": "Show tips when setting up a game.",
"parameters": { "config": "gui.gamesetup.enabletips" }
},
{
"type": "boolean",
"label": "Detailed Tooltips",
"tooltip": "Show detailed tooltips for trainable units in unit-producing buildings.",
"parameters": { "config": "showdetailedtooltips" }
},
{
"type": "boolean",
"label": "Network Warnings",
"tooltip": "Show which player has a bad connection in multiplayer games.",
"parameters": { "config": "overlay.netwarnings" }
},
{
"type": "boolean",
"label": "FPS Overlay",
"tooltip": "Show frames per second in top right corner.",
"parameters": { "config": "overlay.fps" }
},
{
"type": "boolean",
"label": "Realtime Overlay",
"tooltip": "Show current system time in top right corner.",
"parameters": { "config": "overlay.realtime" }
},
{
"type": "boolean",
"label": "Gametime Overlay",
"tooltip": "Show current simulation time in top right corner.",
"parameters": { "config": "gui.session.timeelapsedcounter" }
},
{
"type": "boolean",
"label": "Ceasefire Time Overlay",
"tooltip": "Always show the remaining ceasefire time.",
"parameters": { "config": "gui.session.ceasefirecounter" }
},
{
"type": "boolean",
"label": "Persist Match Settings",
"tooltip": "Save and restore match settings for quick reuse when hosting another game",
"parameters": { "config": "persistmatchsettings" }
},
{
"type": "dropdown",
"label": "Assign Players",
"tooltip": "Automatically assign joining clients to free player slots during the match setup.",
"parameters": {
"config": "gui.gamesetup.assignplayers",
"list": [
{ "value": "everyone", "label": "Everyone" },
{ "value": "buddies", "label": "Buddies" },
{ "value": "disabled", "label": "Disabled" }
]
}
},
{
"type": "dropdown",
"label": "Late Observer Joins",
"tooltip": "Allow everybody or buddies only to join the game as observer after it started",
"parameters": {
"config": "network.lateobservers",
"list": [
{ "value": "everyone", "label": "Everyone" },
{ "value": "buddies", "label": "Buddies" },
{ "value": "disabled", "label": "Disabled" }
]
}
},
{
"type": "number",
"label": "Observer Limit",
"tooltip": "Prevent further observers from joining if the limit is reached",
"parameters": { "config": "network.observerlimit", "min": 0, "max": 32 }
},
{
"type": "number",
"label": "Batch Training Size",
"tooltip": "Number of units trained per batch",
"parameters": { "config": "gui.session.batchtrainingsize", "min": 1, "max": 20 }
},
{
"type": "boolean",
"label": "Chat Timestamp",
"tooltip": "Show time that messages are posted in the lobby, gamesetup and ingame chat.",
"parameters": { "config": "chat.timestamp" }
},
{
"type": "boolean",
"label": "Aura Range Visualization",
"tooltip": "Display the range of auras of selected units and structures (can also be toggled in-game with the hotkey).",
"parameters": { "config": "gui.session.aurarange" }
},
{
"type": "boolean",
"label": "Heal Range Visualization",
"tooltip": "Display the healing range of selected units (can also be toggled in-game with the hotkey).",
"parameters": { "config": "gui.session.healrange" }
}
],
"graphicsSetting":
[
{
"type": "boolean",
"label": "Prefer GLSL",
"tooltip": "Use OpenGL 2.0 shaders (recommended)",
"parameters": { "config": "preferglsl", "renderer": "PreferGLSL" }
},
{
"type": "boolean",
"label": "Post Processing",
"tooltip": "Use screen-space postprocessing filters (HDR, Bloom, DOF, etc)",
"parameters": { "config": "postproc", "renderer": "Postproc" }
},
{
"type": "slider",
"label": "Shader Effects",
"tooltip": "Number of shader effects. REQUIRES GAME RESTART",
"parameters": { "config": "materialmgr.quality", "min": 0, "max": 10 }
},
{
"type": "boolean",
"label": "Shadows",
"tooltip": "Enable shadows",
"parameters": { "config": "shadows", "renderer": "Shadows" },
"dependencies": [ "shadowquality", "shadowpcf" ]
},
{
"type": "dropdown",
"label": "Shadow Quality",
- "tooltip": "Shadow map resolution. High values can crash the game when using a graphics card with low memory! REQUIRES GAME RESTART",
+ "tooltip": "Shadow map resolution. High values can crash the game when using a graphics card with low memory!",
"parameters": {
"config": "shadowquality",
+ "function": "Renderer_RecreateShadowMap",
"list": [
{ "value": -2, "label": "Very Low" },
{ "value": -1, "label": "Low" },
{ "value": 0, "label": "Medium" },
{ "value": 1, "label": "High" },
{ "value": 2, "label": "Very High" }
]
}
},
{
"type": "boolean",
"label": "Shadow Filtering",
"tooltip": "Smooth shadows",
"parameters": { "config": "shadowpcf", "renderer": "ShadowPCF" }
},
{
"type": "boolean",
"label": "Unit Silhouettes",
"tooltip": "Show outlines of units behind buildings",
"parameters": { "config": "silhouettes", "renderer": "Silhouettes" }
},
{
"type": "boolean",
"label": "Particles",
"tooltip": "Enable particles",
"parameters": { "config": "particles", "renderer": "Particles" }
},
{
"type": "boolean",
"label": "Water Effects",
"tooltip": "When OFF, use the lowest settings possible to render water. This makes other settings irrelevant.",
"parameters": { "config": "watereffects", "renderer": "WaterEffects" },
"dependencies": [ "waterfancyeffects", "waterrealdepth", "waterreflection", "waterrefraction", "watershadows" ]
},
{
"type": "boolean",
"label": "HQ Water Effects",
"tooltip": "Use higher-quality effects for water, rendering coastal waves, shore foam, and ships trails.",
"parameters": { "config": "waterfancyeffects", "renderer": "WaterFancyEffects" }
},
{
"type": "boolean",
"label": "Real Water Depth",
"tooltip": "Use actual water depth in rendering calculations",
"parameters": { "config": "waterrealdepth", "renderer": "WaterRealDepth" }
},
{
"type": "boolean",
"label": "Water Reflections",
"tooltip": "Allow water to reflect a mirror image",
"parameters": { "config": "waterreflection", "renderer": "WaterReflection" }
},
{
"type": "boolean",
"label": "Water Refraction",
"tooltip": "Use a real water refraction map and not transparency",
"parameters": { "config": "waterrefraction", "renderer": "WaterRefraction" }
},
{
"type": "boolean",
"label": "Shadows on Water",
"tooltip": "Cast shadows on water",
"parameters": { "config": "watershadows", "renderer": "WaterShadows" }
},
{
"type": "boolean",
"label": "Smooth LOS",
"tooltip": "Lift darkness and fog-of-war smoothly",
"parameters": { "config": "smoothlos", "renderer": "SmoothLOS" }
},
{
"type": "boolean",
"label": "Show Sky",
"tooltip": "Render Sky",
"parameters": { "config": "showsky", "renderer": "ShowSky" }
},
{
"type": "boolean",
"label": "VSync",
"tooltip": "Run vertical sync to fix screen tearing. REQUIRES GAME RESTART",
"parameters": { "config": "vsync" }
},
{
"type": "slider",
"label": "FPS Throttling in Menus",
"tooltip": "To save CPU workload, throttle render frequency in all menus. Set to maximum to disable throttling.",
"parameters": { "config": "adaptivefps.menu", "min": 20, "max": 100 }
},
{
"type": "slider",
"label": "FPS Throttling in Games",
"tooltip": "To save CPU workload, throttle render frequency in running games. Set to maximum to disable throttling.",
"parameters": { "config": "adaptivefps.session", "min": 20, "max": 100 }
}
],
"soundSetting":
[
{
"type": "slider",
"label": "Master Volume",
"tooltip": "Master audio gain",
"parameters": { "config": "sound.mastergain", "function": "SetMasterGain", "min": 0, "max": 2 }
},
{
"type": "slider",
"label": "Music Volume",
"tooltip": "In game music gain",
"parameters": { "config": "sound.musicgain", "function": "SetMusicGain", "min": 0, "max": 2 }
},
{
"type": "slider",
"label": "Ambient Volume",
"tooltip": "In game ambient sound gain",
"parameters": { "config": "sound.ambientgain", "function": "SetAmbientGain", "min": 0, "max": 2 }
},
{
"type": "slider",
"label": "Action Volume",
"tooltip": "In game unit action sound gain",
"parameters": { "config": "sound.actiongain", "function": "SetActionGain", "min": 0, "max": 2 }
},
{
"type": "slider",
"label": "UI Volume",
"tooltip": "UI sound gain",
"parameters": { "config": "sound.uigain", "function": "SetUIGain", "min": 0, "max": 2 }
},
{
"type": "boolean",
"label": "Nick Notification",
"tooltip": "Receive audio notification when someone types your nick",
"parameters": { "config": "sound.notify.nick" }
}
],
"lobbySetting":
[
{
"type": "number",
"label": "Chat Backlog",
"tooltip": "Number of backlogged messages to load when joining the lobby",
"parameters": { "config": "lobby.history", "min": "0" }
},
{
"type": "boolean",
"label": "Game Rating Column",
"tooltip": "Show the average rating of the participating players in a column of the gamelist.",
"parameters": { "config": "lobby.columns.gamerating" }
}
],
"notificationSetting":
[
{
"type": "boolean",
"label": "Attack",
"tooltip": "Show a chat notification if you are attacked by another player",
"parameters": { "config": "gui.session.notifications.attack" }
},
{
"type": "boolean",
"label": "Tribute",
"tooltip": "Show a chat notification if an ally tributes resources to another team member if teams are locked, and all tributes in observer mode",
"parameters": { "config": "gui.session.notifications.tribute" }
},
{
"type": "boolean",
"label": "Barter",
"tooltip": "Show a chat notification to observers when a player bartered resources",
"parameters": { "config": "gui.session.notifications.barter" }
},
{
"type": "dropdown",
"label": "Phase",
"tooltip": "Show a chat notification if you or an ally have started, aborted or completed a new phase, and phases of all players in observer mode",
"parameters": {
"config": "gui.session.notifications.phase",
"list": [
{ "value": "none", "label": "Disable" },
{ "value": "completed", "label": "Completed" },
{ "value": "all", "label": "All displayed" }
]
}
}
]
}
Index: ps/trunk/source/renderer/Renderer.cpp
===================================================================
--- ps/trunk/source/renderer/Renderer.cpp (revision 20010)
+++ ps/trunk/source/renderer/Renderer.cpp (revision 20011)
@@ -1,2133 +1,2138 @@
/* Copyright (C) 2017 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 .
*/
/*
* higher level interface on top of OpenGL to render basic objects:
* terrain, models, sprites, particles etc.
*/
#include "precompiled.h"
#include