Index: ps/trunk/source/ps/scripting/JSInterface_ConfigDB.h
===================================================================
--- ps/trunk/source/ps/scripting/JSInterface_ConfigDB.h (revision 24214)
+++ ps/trunk/source/ps/scripting/JSInterface_ConfigDB.h (revision 24215)
@@ -1,43 +1,44 @@
/* Copyright (C) 2019 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_CONFIGDB
#define INCLUDED_JSI_CONFIGDB
#include "ps/ConfigDB.h"
#include "scriptinterface/ScriptInterface.h"
#include
namespace JSI_ConfigDB
{
bool IsProtectedConfigName(const std::string& name);
bool GetConfigNamespace(const std::wstring& cfgNsString, EConfigNamespace& cfgNs);
bool HasChanges(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& cfgNsString);
bool SetChanges(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& cfgNsString, bool value);
std::string GetValue(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& cfgNsString, const std::string& name);
bool CreateValue(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& cfgNsString, const std::string& name, const std::string& value);
+ bool CreateValues(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& cfgNsString, const std::string& name, const std::vector& values);
bool RemoveValue(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& cfgNsString, const std::string& name);
bool WriteFile(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& cfgNsString, const Path& path);
bool WriteValueToFile(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& cfgNsString, const std::string& name, const std::string& value, const Path& path);
void CreateAndWriteValueToFile(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& cfgNsString, const std::string& name, const std::string& value, const Path& path);
bool Reload(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& cfgNsString);
bool SetFile(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& cfgNsString, const Path& path);
void RegisterScriptFunctions(const ScriptInterface& scriptInterface);
}
#endif // INCLUDED_JSI_CONFIGDB
Index: ps/trunk/source/gui/Scripting/ScriptFunctions.cpp
===================================================================
--- ps/trunk/source/gui/Scripting/ScriptFunctions.cpp (revision 24214)
+++ ps/trunk/source/gui/Scripting/ScriptFunctions.cpp (revision 24215)
@@ -1,75 +1,77 @@
/* Copyright (C) 2019 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 "ScriptFunctions.h"
#include "graphics/scripting/JSInterface_GameView.h"
#include "gui/Scripting/JSInterface_GUIManager.h"
#include "gui/Scripting/JSInterface_GUISize.h"
#include "gui/Scripting/JSInterface_IGUIObject.h"
#include "i18n/scripting/JSInterface_L10n.h"
#include "lobby/scripting/JSInterface_Lobby.h"
#include "network/scripting/JSInterface_Network.h"
#include "ps/scripting/JSInterface_ConfigDB.h"
#include "ps/scripting/JSInterface_Console.h"
#include "ps/scripting/JSInterface_Debug.h"
#include "ps/scripting/JSInterface_Game.h"
+#include "ps/scripting/JSInterface_Hotkey.h"
#include "ps/scripting/JSInterface_Main.h"
#include "ps/scripting/JSInterface_Mod.h"
#include "ps/scripting/JSInterface_ModIo.h"
#include "ps/scripting/JSInterface_SavedGame.h"
#include "ps/scripting/JSInterface_UserReport.h"
#include "ps/scripting/JSInterface_VFS.h"
#include "ps/scripting/JSInterface_VisualReplay.h"
#include "renderer/scripting/JSInterface_Renderer.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/scripting/JSInterface_Simulation.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.
*/
void GuiScriptingInit(ScriptInterface& scriptInterface)
{
JSI_GUISize::RegisterScriptClass(scriptInterface);
JSI_IGUIObject::RegisterScriptClass(scriptInterface);
JSI_ConfigDB::RegisterScriptFunctions(scriptInterface);
JSI_Console::RegisterScriptFunctions(scriptInterface);
JSI_Debug::RegisterScriptFunctions(scriptInterface);
JSI_GUIManager::RegisterScriptFunctions(scriptInterface);
JSI_Game::RegisterScriptFunctions(scriptInterface);
JSI_GameView::RegisterScriptFunctions(scriptInterface);
+ JSI_Hotkey::RegisterScriptFunctions(scriptInterface);
JSI_L10n::RegisterScriptFunctions(scriptInterface);
JSI_Lobby::RegisterScriptFunctions(scriptInterface);
JSI_Main::RegisterScriptFunctions(scriptInterface);
JSI_Mod::RegisterScriptFunctions(scriptInterface);
JSI_ModIo::RegisterScriptFunctions(scriptInterface);
JSI_Network::RegisterScriptFunctions(scriptInterface);
JSI_Renderer::RegisterScriptFunctions(scriptInterface);
JSI_SavedGame::RegisterScriptFunctions(scriptInterface);
JSI_Simulation::RegisterScriptFunctions(scriptInterface);
JSI_Sound::RegisterScriptFunctions(scriptInterface);
JSI_UserReport::RegisterScriptFunctions(scriptInterface);
JSI_VFS::RegisterScriptFunctions_GUI(scriptInterface);
JSI_VisualReplay::RegisterScriptFunctions(scriptInterface);
}
Index: ps/trunk/binaries/data/config/default.cfg
===================================================================
--- ps/trunk/binaries/data/config/default.cfg (revision 24214)
+++ ps/trunk/binaries/data/config/default.cfg (revision 24215)
@@ -1,530 +1,530 @@
; 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. *
; * *
; * If a setting is part of a section (for instance [hotkey]) *
; * you need to append the section name at the beginning of *
; * your custom line (for instance you need to write *
; * "hotkey.pause = Space" if you want to change the pausing *
; * hotkey to the spacebar). *
; * *
; * 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
; Pause the game on window focus loss (Only applicable to single player mode)
pauseonfocusloss = true
; Persist settings after leaving the game setup screen
persistmatchsettings = 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
; Preferred display (for multidisplay setups, only works with SDL 2.0)
display = 0
; Emulate right-click with Ctrl+Click on Mac mice
macmouse = false
; System settings:
; if false, actors won't be rendered but anything entity will be.
renderactors = true
watereffects=true ; When disabled, 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
shadowquality = 0 ; Shadow map resolution. (-2 - Very Low, -1 - Low, 0 - Medium, 1 - High, 2 - Very High)
; High values can crash the game when using a graphics card with low memory!
shadowpcf = true
shadowsfixed = false ; When enabled shadows are rendered only on the
shadowsfixeddistance = 300.0 ; fixed distance and without swimming effect.
vsync = false
particles = true
fog = true
silhouettes = true
showsky = true
novbo = false
; Disable hardware cursors
nohwcursor = false
; 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. Allows fancier graphical effects.
preferglsl = false
; Experimental probably-non-working GPU skinning support; requires preferglsl; use at own risk
gpuskinning = false
; Use smooth LOS interpolation
smoothlos = false
; Use screen-space postprocessing filters (HDR, bloom, DOF, etc). Incompatible with fixed renderpath.
postproc = false
; Use anti-aliasing techniques.
antialiasing = "disabled"
; Use sharpening techniques.
sharpening = "disabled"
sharpness = 0.3
; Quality level of shader effects (set to 10 to display all effects)
materialmgr.quality = 2.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
; Color of the sky (in "r g b" format)
skycolor = "0 0 0"
[adaptivefps]
session = 60 ; Throttle FPS in running games (prevents 100% CPU workload).
menu = 60 ; Throttle FPS in menus only.
[hotkey]
; Each one of the specified keys will trigger the action on the left
; for multiple-key combinations, separate keys with '+'.
; See keys.txt for the list of key names.
; > SYSTEM SETTINGS
-exit = "Ctrl+Break", "Super+Q" ; Exit to desktop
+exit = "Ctrl+Break", "Super+Q", "Alt+F4" ; Exit to desktop
cancel = Escape ; Close or cancel the current dialog box/popup
confirm = Return ; Confirm the current command
pause = Pause ; Pause/unpause game
screenshot = F2 ; Take PNG screenshot
bigscreenshot = "Shift+F2" ; Take large BMP screenshot
togglefullscreen = "Alt+Return" ; Toggle fullscreen/windowed mode
screenshot.watermark = "Alt+K" ; Toggle product/company watermark for official screenshots
wireframe = "Alt+Shift+W" ; Toggle wireframe mode
silhouettes = "Alt+Shift+S" ; Toggle unit silhouettes
showsky = "Alt+Z" ; Toggle sky
; > DIALOG HOTKEYS
summary = "Ctrl+Tab" ; Toggle in-game summary
lobby = "Alt+L" ; Show the multiplayer lobby in a dialog window.
structree = "Alt+Shift+T" ; Show structure tree
civinfo = "Alt+Shift+H" ; Show civilization info
; > CLIPBOARD CONTROLS
copy = "Ctrl+C" ; Copy to clipboard
paste = "Ctrl+V" ; Paste from clipboard
cut = "Ctrl+X" ; Cut selected text and copy to the clipboard
; > CONSOLE SETTINGS
console.toggle = BackQuote, F9 ; Open/close console
; > OVERLAY KEYS
fps.toggle = "Alt+F" ; Toggle frame counter
realtime.toggle = "Alt+T" ; Toggle current display of computer time
timeelapsedcounter.toggle = "F12" ; Toggle time elapsed counter
ceasefirecounter.toggle = unused ; Toggle ceasefire counter
; > HOTKEYS ONLY
chat = Return ; Toggle chat window
teamchat = "T" ; Toggle chat window in team chat mode
privatechat = "L" ; Toggle chat window and select the previous private chat partner
; > QUICKSAVE
quicksave = "Shift+F5"
quickload = "Shift+F8"
[hotkey.camera]
reset = "R" ; Reset camera rotation to default.
follow = "F" ; Follow the first unit in the selection
rallypointfocus = unused ; Focus the camera on the rally point of the selected building
-zoom.in = Plus, Equals, NumPlus ; Zoom camera in (continuous control)
-zoom.out = Minus, Underscore, NumMinus ; Zoom camera out (continuous control)
+zoom.in = Plus, NumPlus ; Zoom camera in (continuous control)
+zoom.out = Minus, NumMinus ; Zoom camera out (continuous control)
zoom.wheel.in = WheelUp ; Zoom camera in (stepped control)
zoom.wheel.out = WheelDown ; Zoom camera out (stepped control)
rotate.up = "Ctrl+UpArrow", "Ctrl+W" ; Rotate camera to look upwards
rotate.down = "Ctrl+DownArrow", "Ctrl+S" ; Rotate camera to look downwards
rotate.cw = "Ctrl+LeftArrow", "Ctrl+A", Q ; Rotate camera clockwise around terrain
rotate.ccw = "Ctrl+RightArrow", "Ctrl+D", E ; Rotate camera anticlockwise around terrain
rotate.wheel.cw = "Shift+WheelUp", MouseX1 ; Rotate camera clockwise around terrain (stepped control)
rotate.wheel.ccw = "Shift+WheelDown", MouseX2 ; Rotate camera anticlockwise around terrain (stepped control)
pan = MouseMiddle ; Enable scrolling by moving mouse
left = A, LeftArrow ; Scroll or rotate left
right = D, RightArrow ; Scroll or rotate right
up = W, UpArrow ; Scroll or rotate up/forwards
down = S, DownArrow ; Scroll or rotate down/backwards
scroll.speed.increase = "Ctrl+Shift+S" ; Increase scroll speed
scroll.speed.decrease = "Ctrl+Alt+S" ; Decrease scroll speed
rotate.speed.increase = "Ctrl+Shift+R" ; Increase rotation speed
rotate.speed.decrease = "Ctrl+Alt+R" ; Decrease rotation speed
zoom.speed.increase = "Ctrl+Shift+Z" ; Increase zoom speed
zoom.speed.decrease = "Ctrl+Alt+Z" ; Decrease zoom speed
[hotkey.camera.jump]
1 = F5 ; Jump to position N
2 = F6
3 = F7
4 = F8
;5 =
;6 =
;7 =
;8 =
;9 =
;10 =
[hotkey.camera.jump.set]
1 = "Ctrl+F5" ; Set jump position N
2 = "Ctrl+F6"
3 = "Ctrl+F7"
4 = "Ctrl+F8"
;5 =
;6 =
;7 =
;8 =
;9 =
;10 =
[hotkey.profile]
toggle = "F11" ; Enable/disable real-time profiler
save = "Shift+F11" ; Save current profiler data to logs/profile.txt
[hotkey.profile2]
toggle = "Ctrl+F11" ; Enable/disable HTTP/GPU modes for new profiler
[hotkey.selection]
+cancel = Esc ; Un-select all units and cancel building placement
add = Shift ; Add units to selection
militaryonly = Alt ; Add only military units to the selection
nonmilitaryonly = "Alt+Y" ; Add only non-military units to the selection
idleonly = "I" ; Select only idle units
woundedonly = "O" ; Select only wounded units
remove = Ctrl ; Remove units from selection
-cancel = Esc ; Un-select all units and cancel building placement
-idleworker = Period, NumPoint ; Select next idle worker
-idlewarrior = ForwardSlash, NumDivide ; Select next idle warrior
+idleworker = Period, NumDecimal ; Select next idle worker
+idlewarrior = Slash, NumDivide ; Select next idle warrior
idleunit = BackSlash ; Select next idle unit
offscreen = Alt ; Include offscreen units in selection
[hotkey.selection.group.add]
0 = "Shift+0", "Shift+Num0"
1 = "Shift+1", "Shift+Num1"
2 = "Shift+2", "Shift+Num2"
3 = "Shift+3", "Shift+Num3"
4 = "Shift+4", "Shift+Num4"
5 = "Shift+5", "Shift+Num5"
6 = "Shift+6", "Shift+Num6"
7 = "Shift+7", "Shift+Num7"
8 = "Shift+8", "Shift+Num8"
9 = "Shift+9", "Shift+Num9"
[hotkey.selection.group.save]
0 = "Ctrl+0", "Ctrl+Num0"
1 = "Ctrl+1", "Ctrl+Num1"
2 = "Ctrl+2", "Ctrl+Num2"
3 = "Ctrl+3", "Ctrl+Num3"
4 = "Ctrl+4", "Ctrl+Num4"
5 = "Ctrl+5", "Ctrl+Num5"
6 = "Ctrl+6", "Ctrl+Num6"
7 = "Ctrl+7", "Ctrl+Num7"
8 = "Ctrl+8", "Ctrl+Num8"
9 = "Ctrl+9", "Ctrl+Num9"
[hotkey.selection.group.select]
0 = 0, Num0
1 = 1, Num1
2 = 2, Num2
3 = 3, Num3
4 = 4, Num4
5 = 5, Num5
6 = 6, Num6
7 = 7, Num7
8 = 8, Num8
9 = 9, Num9
[hotkey.session]
-kill = Delete ; Destroy selected units
+kill = Delete, Backspace ; Destroy selected units
stop = "H" ; Stop the current action
backtowork = "Y" ; The unit will go back to work
unload = "U" ; Unload garrisoned units when a building/mechanical unit is selected
move = unused ; Modifier to move to a point instead of another action (e.g. gather)
attack = Ctrl ; Modifier to attack instead of another action (e.g. capture)
attackmove = Ctrl ; Modifier to attackmove when clicking on a point
attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when clicking on a point (should contain the attackmove keys)
garrison = Ctrl ; Modifier to garrison when clicking on building
autorallypoint = Ctrl ; Modifier to set the rally point on the building itself
guard = "G" ; Modifier to escort/guard when clicking on unit/building
patrol = "P" ; Modifier to patrol a unit
repair = "J" ; Modifier to repair when clicking on building/mechanical unit
queue = Shift ; Modifier to queue unit orders instead of replacing
orderone = Alt ; Modifier to order only one entity in selection.
batchtrain = Shift ; Modifier to train units in batches
massbarter = Shift ; Modifier to barter bunch of resources
masstribute = Shift ; Modifier to tribute bunch of resources
noconfirmation = Shift ; Do not ask confirmation when deleting a building/unit
fulltradeswap = Shift ; Modifier to put the desired trade resource to 100%
unloadtype = Shift ; Modifier to unload all units of type
deselectgroup = Ctrl ; Modifier to deselect units when clicking group icon, instead of selecting
rotate.cw = RightBracket ; Rotate building placement preview clockwise
rotate.ccw = LeftBracket ; Rotate building placement preview anticlockwise
snaptoedges = Ctrl ; Modifier to align new structures with nearby existing structure
; Overlays
showstatusbars = Tab ; Toggle display of status bars
devcommands.toggle = "Alt+D" ; Toggle developer commands panel
-highlightguarding = PgDn ; Toggle highlight of guarding units
-highlightguarded = PgUp ; Toggle highlight of guarded units
+highlightguarding = PageDown ; Toggle highlight of guarding units
+highlightguarded = PageUp ; Toggle highlight of guarded units
diplomacycolors = "Alt+X" ; Toggle diplomacy colors
toggleattackrange = "Alt+C" ; Toggle display of attack range overlays of selected defensive structures
toggleaurasrange = "Alt+V" ; Toggle display of aura range overlays of selected units and structures
togglehealrange = "Alt+B" ; Toggle display of heal range overlays of selected units
[hotkey.session.gui]
toggle = "Alt+G" ; Toggle visibility of session GUI
menu.toggle = "F10" ; Toggle in-game menu
diplomacy.toggle = "Ctrl+H" ; Toggle in-game diplomacy page
barter.toggle = "Ctrl+B" ; Toggle in-game barter/trade page
objectives.toggle = "Ctrl+O" ; Toggle in-game objectives page
tutorial.toggle = "Ctrl+P" ; Toggle in-game tutorial panel
[hotkey.session.savedgames]
-delete = Delete ; Delete the selected saved game asking confirmation
+delete = Delete, Backspace ; Delete the selected saved game asking confirmation
noconfirmation = Shift ; Do not ask confirmation when deleting a game
[hotkey.session.queueunit] ; > UNIT TRAINING
1 = "Z" ; add first unit type to queue
2 = "X" ; add second unit type to queue
3 = "C" ; add third unit type to queue
4 = "V" ; add fourth unit type to queue
5 = "B" ; add fivth unit type to queue
6 = "N" ; add sixth unit type to queue
7 = "M" ; add seventh unit type to queue
8 = Comma ; add eighth unit type to queue
[hotkey.session.timewarp]
fastforward = Space ; If timewarp mode enabled, speed up the game
-rewind = Backspace ; If timewarp mode enabled, go back to earlier point in the game
+rewind = "Shift+Backspace" ; If timewarp mode enabled, go back to earlier point in the game
[hotkey.tab]
next = "Tab", "Alt+S" ; Show the next tab
prev = "Shift+Tab", "Alt+W" ; Show the previous tab
[hotkey.text] ; > GUI TEXTBOX HOTKEYS
delete.left = "Ctrl+Backspace" ; Delete word to the left of cursor
delete.right = "Ctrl+Del" ; Delete word to the right of cursor
move.left = "Ctrl+LeftArrow" ; Move cursor to start of word to the left of cursor
move.right = "Ctrl+RightArrow" ; Move cursor to start of word to the right of cursor
[gui]
cursorblinkrate = 0.5 ; Cursor blink rate in seconds (0.0 to disable blinking)
scale = 1.0 ; GUI scaling factor, for improved compatibility with 4K displays
[gui.gamesetup]
enabletips = true ; Enable/Disable tips during gamesetup (for newcomers)
assignplayers = everyone ; Whether to assign joining clients to free playerslots. Possible values: everyone, buddies, disabled.
aidifficulty = 3 ; Difficulty level, from 0 (easiest) to 5 (hardest)
aibehavior = "random" ; Default behavior of the AI (random, balanced, aggressive or defensive)
settingsslide = true ; Enable/Disable settings panel slide
[gui.loadingscreen]
progressdescription = false ; Whether to display the progress percent or a textual description
[gui.session]
camerajump.threshold = 40 ; How close do we have to be to the actual location in order to jump back to the previous one?
timeelapsedcounter = false ; Show the game duration in the top right corner
ceasefirecounter = false ; Show the remaining ceasefire time in the top right corner
batchtrainingsize = 5 ; Number of units to be trained per batch by default (when pressing the hotkey)
scrollbatchratio = 1 ; Number of times you have to scroll to increase/decrease the batchsize by 1
woundedunithotkeythreshold = 33 ; The wounded unit hotkey considers the selected units as wounded if their health percentage falls below this number
attackrange = true ; Display attack range overlays of selected defensive structures
aurasrange = true ; Display aura range overlays of selected units and structures
healrange = true ; Display heal range overlays of selected units
rankabovestatusbar = true ; Show rank icons above status bars
experiencestatusbar = true ; Show an experience status bar above each selected unit
respoptooltipsort = 0 ; Sorting players in the resources and population tooltip by value (0 - no sort, -1 - ascending, 1 - descending)
snaptoedges = "disabled" ; Possible values: disabled, enabled.
snaptoedgesdistancethreshold = 15 ; On which distance we don't snap to edges
disjointcontrolgroups = "true" ; Whether control groups are disjoint sets or entities can be in multiple control groups at the same time.
[gui.session.minimap]
blinkduration = 1.7 ; The blink duration while pinging
pingduration = 50.0 ; The duration for which an entity will be pinged after an attack notification
[gui.session.notifications]
attack = true ; Show a chat notification if you are attacked by another player
tribute = true ; Show a chat notification if an ally tributes resources to another team member if teams are locked, and all tributes in observer mode
barter = true ; Show a chat notification to observers when a player bartered resources
phase = completed ; 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. Possible values: none, completed, all.
[gui.splashscreen]
enable = true ; Enable/disable the splashscreen
version = 0 ; Splashscreen version (date of last modification). By default, 0 to force splashscreen to appear at first launch
[gui.session.diplomacycolors]
self = "21 55 149" ; Color of your units when diplomacy colors are enabled
ally = "86 180 31" ; Color of allies when diplomacy colors are enabled
neutral = "231 200 5" ; Color of neutral players when diplomacy colors are enabled
enemy = "150 20 20" ; Color of enemies when diplomacy colors are enabled
[joystick] ; EXPERIMENTAL: joystick/gamepad settings
enable = false
deadzone = 8192
[joystick.camera]
pan.x = 0
pan.y = 1
rotate.x = 3
rotate.y = 2
zoom.in = 5
zoom.out = 4
[chat]
timestamp = true ; Show at which time chat messages have been sent
[chat.session]
extended = true ; Whether to display the chat history
[lobby]
history = 0 ; Number of past messages to display on join
room = "arena24" ; Default MUC room to join
server = "lobby.wildfiregames.com" ; Address of lobby server
tls = true ; Whether to use TLS encryption when connecting to the server.
verify_certificate = false ; Whether to reject connecting to the lobby if the TLS certificate is invalid (TODO: wait for Gloox GnuTLS trust implementation to be fixed)
terms_url = "https://trac.wildfiregames.com/browser/ps/trunk/binaries/data/mods/public/gui/prelobby/common/terms/"; Allows the user to save the text and print the terms
terms_of_service = "0" ; Version (hash) of the Terms of Service that the user has accepted
terms_of_use = "0" ; Version (hash) of the Terms of Use that the user has accepted
privacy_policy = "0" ; Version (hash) of the Privacy Policy that the user has accepted
xpartamupp = "wfgbot24" ; Name of the server-side XMPP-account that manage games
echelon = "echelon24" ; Name of the server-side XMPP-account that manages ratings
buddies = "," ; Comma separated list of playernames that the current user has marked as buddies
rememberpassword = true ; Whether to store the encrypted password in the user config
[lobby.columns]
gamerating = false ; Show the average rating of the participating players in a column of the gamelist
[lobby.stun]
enabled = true ; The STUN protocol allows hosting games without configuring the firewall and router.
; If STUN is disabled, the game relies on direct connection, UPnP and port forwarding.
server = "lobby.wildfiregames.com" ; Address of the STUN server.
port = 3478 ; Port of the STUN server.
delay = 200 ; Duration in milliseconds that is waited between STUN messages.
; Smaller numbers speed up joins but also become less stable.
[mod]
enabledmods = "mod public"
[modio]
public_key = "RWTsHxQMrRq4xwHisyBa2rNQfAedcINzbTT83jeX4/ZcfVxqLfWB4y8w" ; Public key corresponding to the private key valid mods are signed with
disclaimer = "0" ; Version (hash) of the Disclaimer that the user has accepted
[modio.v1]
baseurl = "https://api.mod.io/v1"
api_key = "23df258a71711ea6e4b50893acc1ba55"
name_id = "0ad"
[network]
duplicateplayernames = false ; Rename joining player to "User (2)" if "User" is already connected, otherwise prohibit join.
lateobservers = everyone ; Allow observers to join the game after it started. Possible values: everyone, buddies, disabled.
observerlimit = 8 ; Prevent further observer joins in running games if this limit is reached
gamestarttimeout = 60000 ; Don't disconnect clients timing out in the loading screen and rejoin process before exceeding this timeout.
[overlay]
fps = "false" ; Show frames per second in top right corner
realtime = "false" ; Show current system time in top right corner
netwarnings = "true" ; Show warnings if the network connection is bad
[profiler2]
autoenable = false ; Enable HTTP server output at startup (default off for security/performance)
gpu.arb.enable = true ; Allow GL_ARB_timer_query timing mode when available
gpu.ext.enable = true ; Allow GL_EXT_timer_query timing mode when available
gpu.intel.enable = true ; Allow GL_INTEL_performance_queries timing mode when available
[rlinterface]
address = "127.0.0.1:6000"
[sound]
mastergain = 0.9
musicgain = 0.2
ambientgain = 0.6
actiongain = 0.7
uigain = 0.7
[sound.notify]
nick = true ; Play a sound when someone mentions your name in the lobby or game
gamesetup.join = false ; Play a sound when a new client joins the game setup
[tinygettext]
debug = false ; Print error messages each time a translation for an English string is not found.
[userreport] ; Opt-in online user reporting system
url_upload = "https://feedback.wildfiregames.com/report/upload/v1/" ; URL where UserReports are uploaded to
url_publication = "https://feedback.wildfiregames.com/" ; URL where UserReports were analyzed and published
url_terms = "https://trac.wildfiregames.com/browser/ps/trunk/binaries/data/mods/public/gui/userreport/Terms_and_Conditions.txt"; Allows the user to save the text and print the terms
terms = "0" ; Version (hash) of the UserReporter Terms that the user has accepted
[view] ; Camera control settings
scroll.speed = 120.0
scroll.speed.modifier = 1.05 ; Multiplier for changing scroll speed
rotate.x.speed = 1.2
rotate.x.min = 28.0
rotate.x.max = 60.0
rotate.x.default = 35.0
rotate.y.speed = 2.0
rotate.y.speed.wheel = 0.45
rotate.y.default = 0.0
rotate.speed.modifier = 1.05 ; Multiplier for changing rotation speed
drag.speed = 0.5
zoom.speed = 256.0
zoom.speed.wheel = 32.0
zoom.min = 50.0
zoom.max = 200.0
zoom.default = 120.0
zoom.speed.modifier = 1.05 ; Multiplier for changing zoom speed
pos.smoothness = 0.1
zoom.smoothness = 0.4
rotate.x.smoothness = 0.5
rotate.y.smoothness = 0.3
near = 2.0 ; Near plane distance
far = 4096.0 ; Far plane distance
fov = 45.0 ; Field of view (degrees), lower is narrow, higher is wide
height.smoothness = 0.5
height.min = 16
Index: ps/trunk/source/tools/atlas/GameInterface/Handlers/MiscHandlers.cpp
===================================================================
--- ps/trunk/source/tools/atlas/GameInterface/Handlers/MiscHandlers.cpp (revision 24214)
+++ ps/trunk/source/tools/atlas/GameInterface/Handlers/MiscHandlers.cpp (revision 24215)
@@ -1,218 +1,219 @@
/* Copyright (C) 2020 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 "MessageHandler.h"
#include "../MessagePasserImpl.h"
#include "../GameLoop.h"
#include "../View.h"
#include "graphics/CinemaManager.h"
#include "graphics/GameView.h"
#include "gui/GUIManager.h"
#include "gui/CGUI.h"
#include "lib/external_libraries/libsdl.h"
#include "lib/ogl.h"
#include "lib/sysdep/cpu.h"
#include "maths/MathUtil.h"
#include "ps/Game.h"
#include "ps/Util.h"
#include "ps/GameSetup/Config.h"
#include "ps/GameSetup/GameSetup.h"
#include "renderer/Renderer.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpSoundManager.h"
extern void (*Atlas_GLSwapBuffers)(void* context);
namespace AtlasMessage {
MESSAGEHANDLER(MessageTrace)
{
((MessagePasserImpl*)g_MessagePasser)->SetTrace(msg->enable);
}
MESSAGEHANDLER(Screenshot)
{
if (msg->big)
WriteBigScreenshot(L".bmp", msg->tiles);
else
WriteScreenshot(L".png");
}
QUERYHANDLER(CinemaRecord)
{
const int w = msg->width, h = msg->height;
{
g_Renderer.Resize(w, h);
SViewPort vp = { 0, 0, w, h };
g_Game->GetView()->SetViewport(vp);
}
unsigned char* img = new unsigned char [w*h*3];
unsigned char* temp = new unsigned char[w*3];
int num_frames = msg->framerate * msg->duration;
AtlasView::GetView_Game()->SaveState(L"cinema_record");
// Set it to update the simulation at normal speed
AtlasView::GetView_Game()->SetSpeedMultiplier(1.f);
for (int frame = 0; frame < num_frames; ++frame)
{
AtlasView::GetView_Game()->Update(1.f / msg->framerate);
Render();
Atlas_GLSwapBuffers((void*)g_AtlasGameLoop->glCanvas);
glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, img);
// Swap the rows around, else the image will be upside down
//* // TODO: BGR24 output doesn't need flipping, YUV420 and RGBA32 do
for (int y = 0; y < h/2; ++y)
{
memcpy(temp, &img[y*w*3], w*3);
memcpy(&img[y*w*3], &img[(h-1-y)*w*3], w*3);
memcpy(&img[(h-1-y)*w*3], temp, w*3);
}
//*/
// Call the user-supplied function with this data, so they can
// store it as a video
sCinemaRecordCB cbdata = { img };
msg->cb.Call(cbdata);
}
// Pause the game once we've finished
AtlasView::GetView_Game()->SetSpeedMultiplier(0.f);
AtlasView::GetView_Game()->RestoreState(L"cinema_record");
// TODO: delete the saved state now that we don't need it any more
delete[] img;
delete[] temp;
// Restore viewport
{
g_Renderer.Resize(g_xres, g_yres);
SViewPort vp = { 0, 0, g_xres, g_yres };
g_Game->GetView()->SetViewport(vp);
}
}
QUERYHANDLER(Ping)
{
UNUSED2(msg);
}
MESSAGEHANDLER(SimStopMusic)
{
UNUSED2(msg);
CmpPtr cmpSoundManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
if (cmpSoundManager)
cmpSoundManager->StopMusic();
}
MESSAGEHANDLER(SimStateSave)
{
AtlasView::GetView_Game()->SaveState(*msg->label);
}
MESSAGEHANDLER(SimStateRestore)
{
AtlasView::GetView_Game()->RestoreState(*msg->label);
}
QUERYHANDLER(SimStateDebugDump)
{
msg->dump = AtlasView::GetView_Game()->DumpState(msg->binary);
}
MESSAGEHANDLER(SimPlay)
{
AtlasView::GetView_Game()->SetSpeedMultiplier(msg->speed);
AtlasView::GetView_Game()->SetTesting(msg->simTest);
}
MESSAGEHANDLER(JavaScript)
{
g_GUI->GetActiveGUI()->GetScriptInterface()->LoadGlobalScript(L"Atlas", *msg->command);
}
MESSAGEHANDLER(GuiSwitchPage)
{
g_GUI->SwitchPage(*msg->page, NULL, JS::UndefinedHandleValue);
}
MESSAGEHANDLER(GuiMouseButtonEvent)
{
SDL_Event_ ev = { { 0 } };
ev.ev.type = msg->pressed ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP;
ev.ev.button.button = msg->button;
ev.ev.button.state = msg->pressed ? SDL_PRESSED : SDL_RELEASED;
ev.ev.button.clicks = msg->clicks;
float x, y;
msg->pos->GetScreenSpace(x, y);
ev.ev.button.x = static_cast(Clamp(x, 0, g_xres));
ev.ev.button.y = static_cast(Clamp(y, 0, g_yres));
in_dispatch_event(&ev);
}
MESSAGEHANDLER(GuiMouseMotionEvent)
{
SDL_Event_ ev = { { 0 } };
ev.ev.type = SDL_MOUSEMOTION;
float x, y;
msg->pos->GetScreenSpace(x, y);
ev.ev.motion.x = static_cast(Clamp(x, 0, g_xres));
ev.ev.motion.y = static_cast(Clamp(y, 0, g_yres));
in_dispatch_event(&ev);
}
MESSAGEHANDLER(GuiKeyEvent)
{
SDL_Event_ ev = { { 0 } };
ev.ev.type = msg->pressed ? SDL_KEYDOWN : SDL_KEYUP;
ev.ev.key.keysym.sym = (SDL_Keycode)(int)msg->sdlkey;
+ ev.ev.key.keysym.scancode = SDL_GetScancodeFromKey((SDL_Keycode)(int)msg->sdlkey);
in_dispatch_event(&ev);
}
MESSAGEHANDLER(GuiCharEvent)
{
// Simulate special 'text input' events in the SDL
// This isn't quite compatible with WXWidget's handling,
// so to avoid trouble we only send 'letter-like' ASCII input.
SDL_Event_ ev = { { 0 } };
ev.ev.type = SDL_TEXTEDITING;
ev.ev.text.type = SDL_TEXTEDITING;
ev.ev.text.text[0] = (char)msg->sdlkey;
ev.ev.text.text[1] = (char)0;
in_dispatch_event(&ev);
ev.ev.type = SDL_TEXTINPUT;
ev.ev.text.type = SDL_TEXTINPUT;
ev.ev.text.text[0] = (char)msg->sdlkey;
ev.ev.text.text[1] = (char)0;
in_dispatch_event(&ev);
}
}
Index: ps/trunk/source/ps/ConfigDB.cpp
===================================================================
--- ps/trunk/source/ps/ConfigDB.cpp (revision 24214)
+++ ps/trunk/source/ps/ConfigDB.cpp (revision 24215)
@@ -1,455 +1,467 @@
/* Copyright (C) 2019 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 "ConfigDB.h"
#include
#include "lib/allocators/shared_ptr.h"
#include "lib/file/vfs/vfs_path.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#include "ps/Filesystem.h"
#include
#include
typedef std::map TConfigMap;
TConfigMap CConfigDB::m_Map[CFG_LAST];
VfsPath CConfigDB::m_ConfigFile[CFG_LAST];
bool CConfigDB::m_HasChanges[CFG_LAST];
static std::recursive_mutex cfgdb_mutex;
// These entries will not be printed to logfiles, so that logfiles can be shared without leaking personal or sensitive data
static const std::unordered_set g_UnloggedEntries = {
"lobby.password",
"lobby.buddies",
"userreport.id" // authentication token for GDPR personal data requests
};
#define CHECK_NS(rval)\
do {\
if (ns < 0 || ns >= CFG_LAST)\
{\
debug_warn(L"CConfigDB: Invalid ns value");\
return rval;\
}\
} while (false)
namespace {
template void Get(const CStr& value, T& ret)
{
std::stringstream ss(value);
ss >> ret;
}
template<> void Get<>(const CStr& value, bool& ret)
{
ret = value == "true";
}
template<> void Get<>(const CStr& value, std::string& ret)
{
ret = value;
}
std::string EscapeString(const CStr& str)
{
std::string ret;
for (size_t i = 0; i < str.length(); ++i)
{
if (str[i] == '\\')
ret += "\\\\";
else if (str[i] == '"')
ret += "\\\"";
else
ret += str[i];
}
return ret;
}
} // namespace
#define GETVAL(type)\
void CConfigDB::GetValue(EConfigNamespace ns, const CStr& name, type& value)\
{\
CHECK_NS(;);\
std::lock_guard s(cfgdb_mutex);\
TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name);\
if (it != m_Map[CFG_COMMAND].end())\
{\
Get(it->second[0], value);\
return;\
}\
for (int search_ns = ns; search_ns >= 0; --search_ns)\
{\
it = m_Map[search_ns].find(name);\
if (it != m_Map[search_ns].end())\
{\
Get(it->second[0], value);\
return;\
}\
}\
}
GETVAL(bool)
GETVAL(int)
GETVAL(u32)
GETVAL(float)
GETVAL(double)
GETVAL(std::string)
#undef GETVAL
bool CConfigDB::HasChanges(EConfigNamespace ns) const
{
CHECK_NS(false);
std::lock_guard s(cfgdb_mutex);
return m_HasChanges[ns];
}
void CConfigDB::SetChanges(EConfigNamespace ns, bool value)
{
CHECK_NS(;);
std::lock_guard s(cfgdb_mutex);
m_HasChanges[ns] = value;
}
void CConfigDB::GetValues(EConfigNamespace ns, const CStr& name, CConfigValueSet& values) const
{
CHECK_NS(;);
std::lock_guard s(cfgdb_mutex);
TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name);
if (it != m_Map[CFG_COMMAND].end())
{
values = it->second;
return;
}
for (int search_ns = ns; search_ns >= 0; --search_ns)
{
it = m_Map[search_ns].find(name);
if (it != m_Map[search_ns].end())
{
values = it->second;
return;
}
}
}
EConfigNamespace CConfigDB::GetValueNamespace(EConfigNamespace ns, const CStr& name) const
{
CHECK_NS(CFG_LAST);
std::lock_guard s(cfgdb_mutex);
TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name);
if (it != m_Map[CFG_COMMAND].end())
return CFG_COMMAND;
for (int search_ns = ns; search_ns >= 0; --search_ns)
{
it = m_Map[search_ns].find(name);
if (it != m_Map[search_ns].end())
return (EConfigNamespace)search_ns;
}
return CFG_LAST;
}
std::map CConfigDB::GetValuesWithPrefix(EConfigNamespace ns, const CStr& prefix) const
{
std::lock_guard s(cfgdb_mutex);
std::map ret;
CHECK_NS(ret);
// Loop upwards so that values in later namespaces can override
// values in earlier namespaces
for (int search_ns = 0; search_ns <= ns; ++search_ns)
for (const std::pair& p : m_Map[search_ns])
if (boost::algorithm::starts_with(p.first, prefix))
ret[p.first] = p.second;
for (const std::pair& p : m_Map[CFG_COMMAND])
if (boost::algorithm::starts_with(p.first, prefix))
ret[p.first] = p.second;
return ret;
}
void CConfigDB::SetValueString(EConfigNamespace ns, const CStr& name, const CStr& value)
{
CHECK_NS(;);
std::lock_guard s(cfgdb_mutex);
TConfigMap::iterator it = m_Map[ns].find(name);
if (it == m_Map[ns].end())
it = m_Map[ns].insert(m_Map[ns].begin(), make_pair(name, CConfigValueSet(1)));
it->second[0] = value;
}
void CConfigDB::SetValueBool(EConfigNamespace ns, const CStr& name, const bool value)
{
CStr valueString = value ? "true" : "false";
SetValueString(ns, name, valueString);
}
+void CConfigDB::SetValueList(EConfigNamespace ns, const CStr& name, std::vector values)
+{
+ CHECK_NS(;);
+
+ std::lock_guard s(cfgdb_mutex);
+ TConfigMap::iterator it = m_Map[ns].find(name);
+ if (it == m_Map[ns].end())
+ it = m_Map[ns].insert(m_Map[ns].begin(), make_pair(name, CConfigValueSet(1)));
+
+ it->second = values;
+}
+
void CConfigDB::RemoveValue(EConfigNamespace ns, const CStr& name)
{
CHECK_NS(;);
std::lock_guard s(cfgdb_mutex);
TConfigMap::iterator it = m_Map[ns].find(name);
if (it == m_Map[ns].end())
return;
m_Map[ns].erase(it);
}
void CConfigDB::SetConfigFile(EConfigNamespace ns, const VfsPath& path)
{
CHECK_NS(;);
std::lock_guard s(cfgdb_mutex);
m_ConfigFile[ns] = path;
}
bool CConfigDB::Reload(EConfigNamespace ns)
{
CHECK_NS(false);
std::lock_guard s(cfgdb_mutex);
shared_ptr buffer;
size_t buflen;
{
// Handle missing files quietly
if (g_VFS->GetFileInfo(m_ConfigFile[ns], NULL) < 0)
{
LOGMESSAGE("Cannot find config file \"%s\" - ignoring", m_ConfigFile[ns].string8());
return false;
}
LOGMESSAGE("Loading config file \"%s\"", m_ConfigFile[ns].string8());
Status ret = g_VFS->LoadFile(m_ConfigFile[ns], buffer, buflen);
if (ret != INFO::OK)
{
LOGERROR("CConfigDB::Reload(): vfs_load for \"%s\" failed: return was %lld", m_ConfigFile[ns].string8(), (long long)ret);
return false;
}
}
TConfigMap newMap;
char *filebuf = (char*)buffer.get();
char *filebufend = filebuf+buflen;
bool quoted = false;
CStr header;
CStr name;
CStr value;
int line = 1;
std::vector values;
for (char* pos = filebuf; pos < filebufend; ++pos)
{
switch (*pos)
{
case '\n':
case ';':
break; // We finished parsing this line
case ' ':
case '\r':
case '\t':
continue; // ignore
case '[':
header.clear();
for (++pos; pos < filebufend && *pos != '\n' && *pos != ']'; ++pos)
header.push_back(*pos);
if (pos == filebufend || *pos == '\n')
{
LOGERROR("Config header with missing close tag encountered on line %d in '%s'", line, m_ConfigFile[ns].string8());
header.clear();
++line;
continue;
}
LOGMESSAGE("Found config header '%s'", header.c_str());
header.push_back('.');
while (++pos < filebufend && *pos != '\n' && *pos != ';')
if (*pos != ' ' && *pos != '\r')
{
LOGERROR("Config settings on the same line as a header on line %d in '%s'", line, m_ConfigFile[ns].string8());
break;
}
while (pos < filebufend && *pos != '\n')
++pos;
++line;
continue;
case '=':
// Parse parameters (comma separated, possibly quoted)
for (++pos; pos < filebufend && *pos != '\n' && *pos != ';'; ++pos)
{
switch (*pos)
{
case '"':
quoted = true;
// parse until not quoted anymore
for (++pos; pos < filebufend && *pos != '\n' && *pos != '"'; ++pos)
{
if (*pos == '\\' && ++pos == filebufend)
{
LOGERROR("Escape character at end of input (line %d in '%s')", line, m_ConfigFile[ns].string8());
break;
}
value.push_back(*pos);
}
if (pos < filebufend && *pos == '"')
quoted = false;
else
--pos; // We should terminate the outer loop too
break;
case ' ':
case '\r':
case '\t':
break; // ignore
case ',':
if (!value.empty())
values.push_back(value);
value.clear();
break;
default:
value.push_back(*pos);
break;
}
}
if (quoted) // We ignore the invalid parameter
LOGERROR("Unmatched quote while parsing config file '%s' on line %d", m_ConfigFile[ns].string8(), line);
else if (!value.empty())
values.push_back(value);
value.clear();
quoted = false;
break; // We are either at the end of the line, or we still have a comment to parse
default:
name.push_back(*pos);
continue;
}
// Consume the rest of the line
while (pos < filebufend && *pos != '\n')
++pos;
// Store the setting
if (!name.empty() && !values.empty())
{
CStr key(header + name);
newMap[key] = values;
if (g_UnloggedEntries.find(key) != g_UnloggedEntries.end())
LOGMESSAGE("Loaded config string \"%s\"", key);
else
{
std::string vals;
for (size_t i = 0; i < newMap[key].size() - 1; ++i)
vals += "\"" + EscapeString(newMap[key][i]) + "\", ";
vals += "\"" + EscapeString(newMap[key][values.size()-1]) + "\"";
LOGMESSAGE("Loaded config string \"%s\" = %s", key, vals);
}
}
else if (!name.empty())
LOGERROR("Encountered config setting '%s' without value while parsing '%s' on line %d", name, m_ConfigFile[ns].string8(), line);
name.clear();
values.clear();
++line;
}
if (!name.empty())
LOGERROR("Config file does not have a new line after the last config setting '%s'", name);
m_Map[ns].swap(newMap);
return true;
}
bool CConfigDB::WriteFile(EConfigNamespace ns) const
{
CHECK_NS(false);
std::lock_guard s(cfgdb_mutex);
return WriteFile(ns, m_ConfigFile[ns]);
}
bool CConfigDB::WriteFile(EConfigNamespace ns, const VfsPath& path) const
{
CHECK_NS(false);
std::lock_guard s(cfgdb_mutex);
shared_ptr buf;
AllocateAligned(buf, 1*MiB, maxSectorSize);
char* pos = (char*)buf.get();
for (const std::pair& p : m_Map[ns])
{
size_t i;
pos += sprintf(pos, "%s = ", p.first.c_str());
for (i = 0; i < p.second.size() - 1; ++i)
pos += sprintf(pos, "\"%s\", ", EscapeString(p.second[i]).c_str());
pos += sprintf(pos, "\"%s\"\n", EscapeString(p.second[i]).c_str());
}
const size_t len = pos - (char*)buf.get();
Status ret = g_VFS->CreateFile(path, buf, len);
if (ret < 0)
{
LOGERROR("CConfigDB::WriteFile(): CreateFile \"%s\" failed (error: %d)", path.string8(), (int)ret);
return false;
}
return true;
}
bool CConfigDB::WriteValueToFile(EConfigNamespace ns, const CStr& name, const CStr& value)
{
CHECK_NS(false);
std::lock_guard s(cfgdb_mutex);
return WriteValueToFile(ns, name, value, m_ConfigFile[ns]);
}
bool CConfigDB::WriteValueToFile(EConfigNamespace ns, const CStr& name, const CStr& value, const VfsPath& path)
{
CHECK_NS(false);
std::lock_guard s(cfgdb_mutex);
TConfigMap newMap;
m_Map[ns].swap(newMap);
Reload(ns);
SetValueString(ns, name, value);
bool ret = WriteFile(ns, path);
m_Map[ns].swap(newMap);
return ret;
}
#undef CHECK_NS
Index: ps/trunk/source/ps/Hotkey.cpp
===================================================================
--- ps/trunk/source/ps/Hotkey.cpp (revision 24214)
+++ ps/trunk/source/ps/Hotkey.cpp (revision 24215)
@@ -1,365 +1,344 @@
/* Copyright (C) 2020 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 "Hotkey.h"
#include
+#include "lib/external_libraries/libsdl.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#include "ps/ConfigDB.h"
#include "ps/Globals.h"
#include "ps/KeyName.h"
static bool unified[UNIFIED_LAST - UNIFIED_SHIFT];
-struct SKey
-{
- SDL_Keycode code; // keycode or MOUSE_ or UNIFIED_ value
- bool negated; // whether the key must be pressed (false) or unpressed (true)
-};
-
-// Hotkey data associated with an externally-specified 'primary' keycode
-struct SHotkeyMapping
-{
- CStr name; // name of the hotkey
- bool negated; // whether the primary key must be pressed (false) or unpressed (true)
- std::vector requires; // list of non-primary keys that must also be active
-};
-
-typedef std::vector KeyMapping;
-
-// A mapping of keycodes onto the hotkeys that are associated with that key.
-// (A hotkey triggered by a combination of multiple keys will be in this map
-// multiple times.)
-static std::map g_HotkeyMap;
+std::unordered_map g_HotkeyMap;
+std::unordered_map g_HotkeyStatus;
-// The current pressed status of hotkeys
-std::map g_HotkeyStatus;
+static_assert(std::is_integral::type>::value, "SDL_Scancode is not an integral enum.");
+static_assert(SDL_USEREVENT_ == SDL_USEREVENT, "SDL_USEREVENT_ is not the same type as the real SDL_USEREVENT");
// Look up each key binding in the config file and set the mappings for
// all key combinations that trigger it.
static void LoadConfigBindings()
{
for (const std::pair& configPair : g_ConfigDB.GetValuesWithPrefix(CFG_COMMAND, "hotkey."))
{
std::string hotkeyName = configPair.first.substr(7); // strip the "hotkey." prefix
for (const CStr& hotkey : configPair.second)
{
if (hotkey.LowerCase() == "unused")
continue;
std::vector keyCombination;
// Iterate through multiple-key bindings (e.g. Ctrl+I)
boost::char_separator sep("+");
typedef boost::tokenizer > tokenizer;
tokenizer tok(hotkey, sep);
for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
{
// Attempt decode as key name
- int mapping = FindKeyCode(*it);
- if (!mapping)
- mapping = SDL_GetKeyFromName(it->c_str());
- if (!mapping)
+ SDL_Scancode scancode = FindScancode(it->c_str());
+ if (!scancode)
{
LOGWARNING("Hotkey mapping used invalid key '%s'", hotkey.c_str());
continue;
}
- SKey key = { (SDL_Keycode)mapping, false };
+ SKey key = { scancode, false };
keyCombination.push_back(key);
}
std::vector::iterator itKey, itKey2;
for (itKey = keyCombination.begin(); itKey != keyCombination.end(); ++itKey)
{
SHotkeyMapping bindCode;
bindCode.name = hotkeyName;
bindCode.negated = itKey->negated;
for (itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); ++itKey2)
if (itKey != itKey2) // Push any auxiliary keys
bindCode.requires.push_back(*itKey2);
g_HotkeyMap[itKey->code].push_back(bindCode);
}
}
}
}
void LoadHotkeys()
{
- InitKeyNameMap();
-
LoadConfigBindings();
// Set up the state of the hotkeys given no key is down.
// i.e. find those hotkeys triggered by all negations.
for (const std::pair& p : g_HotkeyMap)
for (const SHotkeyMapping& hotkey : p.second)
{
if (!hotkey.negated)
continue;
bool allNegated = true;
for (const SKey& k : hotkey.requires)
if (!k.negated)
allNegated = false;
if (allNegated)
g_HotkeyStatus[hotkey.name] = true;
}
}
void UnloadHotkeys()
{
g_HotkeyMap.clear();
g_HotkeyStatus.clear();
}
bool isNegated(const SKey& key)
{
// Normal keycodes are below EXTRA_KEYS_BASE
if ((int)key.code < EXTRA_KEYS_BASE && g_keys[key.code] == key.negated)
return false;
// Mouse 'keycodes' are after the modifier keys
else if ((int)key.code < MOUSE_LAST && (int)key.code > MOUSE_BASE && g_mouse_buttons[key.code - MOUSE_BASE] == key.negated)
return false;
// Modifier keycodes are between the normal keys and the mouse 'keys'
- else if ((int)key.code < UNIFIED_LAST && (int)key.code > SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES) && unified[key.code - UNIFIED_SHIFT] == key.negated)
+ else if ((int)key.code < UNIFIED_LAST && (int)key.code > SDL_NUM_SCANCODES && unified[key.code - UNIFIED_SHIFT] == key.negated)
return false;
else
return true;
}
InReaction HotkeyStateChange(const SDL_Event_* ev)
{
if (ev->ev.type == SDL_HOTKEYPRESS)
g_HotkeyStatus[static_cast(ev->ev.user.data1)] = true;
else if (ev->ev.type == SDL_HOTKEYUP)
g_HotkeyStatus[static_cast(ev->ev.user.data1)] = false;
return IN_PASS;
}
InReaction HotkeyInputHandler(const SDL_Event_* ev)
{
- int keycode = 0;
+ int scancode = SDL_SCANCODE_UNKNOWN;
switch(ev->ev.type)
{
case SDL_KEYDOWN:
case SDL_KEYUP:
- keycode = (int)ev->ev.key.keysym.sym;
+ scancode = ev->ev.key.keysym.scancode;
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// Mousewheel events are no longer buttons, but we want to maintain the order
// expected by g_mouse_buttons for compatibility
if (ev->ev.button.button >= SDL_BUTTON_X1)
- keycode = MOUSE_BASE + (int)ev->ev.button.button + 2;
+ scancode = MOUSE_BASE + (int)ev->ev.button.button + 2;
else
- keycode = MOUSE_BASE + (int)ev->ev.button.button;
+ scancode = MOUSE_BASE + (int)ev->ev.button.button;
break;
case SDL_MOUSEWHEEL:
if (ev->ev.wheel.y > 0)
{
- keycode = MOUSE_WHEELUP;
+ scancode = MOUSE_WHEELUP;
break;
}
else if (ev->ev.wheel.y < 0)
{
- keycode = MOUSE_WHEELDOWN;
+ scancode = MOUSE_WHEELDOWN;
break;
}
else if (ev->ev.wheel.x > 0)
{
- keycode = MOUSE_X2;
+ scancode = MOUSE_X2;
break;
}
else if (ev->ev.wheel.x < 0)
{
- keycode = MOUSE_X1;
+ scancode = MOUSE_X1;
break;
}
return IN_PASS;
default:
return IN_PASS;
}
// Somewhat hackish:
// Create phantom 'unified-modifier' events when left- or right- modifier keys are pressed
// Just send them to this handler; don't let the imaginary event codes leak back to real SDL.
SDL_Event_ phantom;
phantom.ev.type = ((ev->ev.type == SDL_KEYDOWN) || (ev->ev.type == SDL_MOUSEBUTTONDOWN)) ? SDL_KEYDOWN : SDL_KEYUP;
if (phantom.ev.type == SDL_KEYDOWN)
phantom.ev.key.repeat = ev->ev.type == SDL_KEYDOWN ? ev->ev.key.repeat : 0;
- if ((keycode == SDLK_LSHIFT) || (keycode == SDLK_RSHIFT))
+ if (scancode == SDL_SCANCODE_LSHIFT || scancode == SDL_SCANCODE_RSHIFT)
{
- phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_SHIFT;
+ phantom.ev.key.keysym.scancode = static_cast(UNIFIED_SHIFT);
unified[0] = (phantom.ev.type == SDL_KEYDOWN);
HotkeyInputHandler(&phantom);
}
- else if ((keycode == SDLK_LCTRL) || (keycode == SDLK_RCTRL))
+ else if (scancode == SDL_SCANCODE_LCTRL || scancode == SDL_SCANCODE_RCTRL)
{
- phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_CTRL;
+ phantom.ev.key.keysym.scancode = static_cast(UNIFIED_CTRL);
unified[1] = (phantom.ev.type == SDL_KEYDOWN);
HotkeyInputHandler(&phantom);
}
- else if ((keycode == SDLK_LALT) || (keycode == SDLK_RALT))
+ else if (scancode == SDL_SCANCODE_LALT || scancode == SDL_SCANCODE_RALT)
{
- phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_ALT;
+ phantom.ev.key.keysym.scancode = static_cast(UNIFIED_ALT);
unified[2] = (phantom.ev.type == SDL_KEYDOWN);
HotkeyInputHandler(&phantom);
}
- else if ((keycode == SDLK_LGUI) || (keycode == SDLK_RGUI))
+ else if (scancode == SDL_SCANCODE_LGUI || scancode == SDL_SCANCODE_RGUI)
{
- phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_SUPER;
+ phantom.ev.key.keysym.scancode = static_cast(UNIFIED_SUPER);
unified[3] = (phantom.ev.type == SDL_KEYDOWN);
HotkeyInputHandler(&phantom);
}
// Check whether we have any hotkeys registered for this particular keycode
- if (g_HotkeyMap.find(keycode) == g_HotkeyMap.end())
+ if (g_HotkeyMap.find(scancode) == g_HotkeyMap.end())
return (IN_PASS);
// Inhibit the dispatch of hotkey events caused by real keys (not fake mouse button
// events) while the console is up.
bool consoleCapture = false;
- if (g_Console && g_Console->IsActive() && keycode < SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES))
+ if (g_Console && g_Console->IsActive() && scancode < SDL_NUM_SCANCODES)
consoleCapture = true;
// Here's an interesting bit:
// If you have an event bound to, say, 'F', and another to, say, 'Ctrl+F', pressing
// 'F' while control is down would normally fire off both.
// To avoid this, set the modifier keys for /all/ events this key would trigger
// (Ctrl, for example, is both group-save and bookmark-save)
// but only send a HotkeyPress/HotkeyDown event for the event with bindings most precisely
// matching the conditions (i.e. the event with the highest number of auxiliary
// keys, providing they're all down)
bool typeKeyDown = ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN ) || (ev->ev.type == SDL_MOUSEWHEEL);
// -- KEYDOWN SECTION --
std::vector closestMapNames;
size_t closestMapMatch = 0;
- for (const SHotkeyMapping& hotkey : g_HotkeyMap[keycode])
+ for (const SHotkeyMapping& hotkey : g_HotkeyMap[scancode])
{
// If a key has been pressed, and this event triggers on its release, skip it.
// Similarly, if the key's been released and the event triggers on a keypress, skip it.
if (hotkey.negated == typeKeyDown)
continue;
// Check for no unpermitted keys
bool accept = true;
for (const SKey& k : hotkey.requires)
{
accept = isNegated(k);
if (!accept)
break;
}
if (accept && !(consoleCapture && hotkey.name != "console.toggle"))
{
// Check if this is an equally precise or more precise match
if (hotkey.requires.size() + 1 >= closestMapMatch)
{
// Check if more precise
if (hotkey.requires.size() + 1 > closestMapMatch)
{
// Throw away the old less-precise matches
closestMapNames.clear();
closestMapMatch = hotkey.requires.size() + 1;
}
closestMapNames.push_back(hotkey.name.c_str());
}
}
}
for (size_t i = 0; i < closestMapNames.size(); ++i)
{
// Send a KeyPress event when a key is pressed initially and on mouseButton and mouseWheel events.
if (ev->ev.type != SDL_KEYDOWN || ev->ev.key.repeat == 0)
{
SDL_Event_ hotkeyPressNotification;
hotkeyPressNotification.ev.type = SDL_HOTKEYPRESS;
hotkeyPressNotification.ev.user.data1 = const_cast(closestMapNames[i]);
in_push_priority_event(&hotkeyPressNotification);
}
// Send a HotkeyDown event on every key, mouseButton and mouseWheel event.
// For keys the event is repeated depending on hardware and OS configured interval.
// On linux, modifier keys (shift, alt, ctrl) are not repeated, see https://github.com/SFML/SFML/issues/122.
SDL_Event_ hotkeyDownNotification;
hotkeyDownNotification.ev.type = SDL_HOTKEYDOWN;
hotkeyDownNotification.ev.user.data1 = const_cast(closestMapNames[i]);
in_push_priority_event(&hotkeyDownNotification);
}
// -- KEYUP SECTION --
- for (const SHotkeyMapping& hotkey : g_HotkeyMap[keycode])
+ for (const SHotkeyMapping& hotkey : g_HotkeyMap[scancode])
{
// If it's a keydown event, won't cause HotKeyUps in anything that doesn't
// use this key negated => skip them
// If it's a keyup event, won't cause HotKeyUps in anything that does use
// this key negated => skip them too.
if (hotkey.negated != typeKeyDown)
continue;
// Check for no unpermitted keys
bool accept = true;
for (const SKey& k : hotkey.requires)
{
accept = isNegated(k);
if (!accept)
break;
}
if (accept)
{
SDL_Event_ hotkeyNotification;
hotkeyNotification.ev.type = SDL_HOTKEYUP;
hotkeyNotification.ev.user.data1 = const_cast(hotkey.name.c_str());
in_push_priority_event(&hotkeyNotification);
}
}
return IN_PASS;
}
bool HotkeyIsPressed(const CStr& keyname)
{
return g_HotkeyStatus[keyname];
}
Index: ps/trunk/source/ps/KeyName.cpp
===================================================================
--- ps/trunk/source/ps/KeyName.cpp (revision 24214)
+++ ps/trunk/source/ps/KeyName.cpp (revision 24215)
@@ -1,238 +1,221 @@
/* Copyright (C) 2015 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 .
*/
// Ooh, a file of keynames. Fun.
#include "precompiled.h"
#include "KeyName.h"
#include "lib/external_libraries/libsdl.h"
#include "ps/CStr.h"
-#include