Index: ps/trunk/build/premake/premake.lua
===================================================================
--- ps/trunk/build/premake/premake.lua (revision 8924)
+++ ps/trunk/build/premake/premake.lua (revision 8925)
@@ -1,1141 +1,1143 @@
addoption("atlas", "Include Atlas scenario editor packages")
addoption("collada", "Include COLLADA packages (requires FCollada library)")
addoption("coverage", "Enable code coverage data collection (GCC only)")
addoption("aoe3ed", "Include AoE3Ed")
addoption("icc", "Use Intel C++ Compiler (Linux only; should use either \"--cc icc\" or --without-pch too, and then set CXX=icpc before calling make)")
addoption("outpath", "Location for generated project files")
addoption("without-tests", "Disable generation of test projects")
addoption("without-pch", "Disable generation and usage of precompiled headers")
addoption("with-system-nvtt", "Search standard paths for nvidia-texture-tools library, instead of using bundled copy")
addoption("bindir", "Directory for executables (typically '/usr/games'); default is to be relocatable")
addoption("datadir", "Directory for data files (typically '/usr/share/games/0ad'); default is ../data/ relative to executable")
addoption("libdir", "Directory for libraries (typically '/usr/lib/games/0ad'); default is ./ relative to executable")
dofile("functions.lua")
dofile("extern_libs.lua")
-- detect CPU architecture (simplistic, currently only supports x86 and amd64)
arch = "x86"
if OS == "windows" then
if os.getenv("PROCESSOR_ARCHITECTURE") == "amd64" or os.getenv("PROCESSOR_ARCHITEW6432") == "amd64" then
arch = "amd64"
end
else
arch = os.getenv("HOSTTYPE")
if arch == "x86_64" then
arch = "amd64"
else
os.execute("gcc -dumpmachine > .gccmachine.tmp")
local f = io.open(".gccmachine.tmp", "r")
local machine = f:read("*line")
f:close()
if string.find(machine, "x86_64") == 1 then
arch = "amd64"
elseif string.find(machine, "i.86") == 1 then
arch = "x86"
else
print("WARNING: Cannot determine architecture from GCC, assuming x86")
end
end
end
-- Set up the Project
project.name = "pyrogenesis"
project.bindir = "../../binaries/system"
project.libdir = "../../binaries/system"
project.debugdir = "../../binaries/system"
if not options["outpath"] then
error("You must specify the 'outpath' parameter")
end
project.path = options["outpath"]
project.configs = { "Debug", "Release", "Testing" }
if OS == "windows" then
project.nasmpath = "../../build/bin/nasm.exe"
project.cxxtestpath = "../../build/bin/cxxtestgen.exe"
has_broken_pch = false
else
project.cxxtestpath = "../../build/bin/cxxtestgen.pl"
if OS == "linux" and arch == "amd64" then
-- Hack for amd64 linux - tell nasm to product 64-bit elf.
project.nasmformat = "elf64"
elseif OS == "macosx" and arch == "amd64" then
project.nasmformat = "macho64"
end
-- GCC bug (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=10591) - PCH breaks anonymous namespaces
-- Fixed in 4.2.0, but we have to disable PCH for earlier versions, else
-- it conflicts annoyingly with wx 2.8 headers.
-- It's too late to do this test by the time we start compiling the PCH file, so
-- do the test in this build script instead (which is kind of ugly - please fix if
-- you have a better idea)
if not options["icc"] then
os.execute("gcc -dumpversion > .gccver.tmp")
local f = io.open(".gccver.tmp", "r")
major, dot, minor = f:read(1, 1, 1)
f:close()
major = 0+major -- coerce to number
minor = 0+minor
has_broken_pch = (major < 4 or (major == 4 and minor < 2))
if has_broken_pch then
print("WARNING: Detected GCC <4.2 -- disabling PCH for Atlas (will increase build times)")
end
end
end
source_root = "../../../source/" -- default for most projects - overridden by local in others
-- Rationale: packages should not have any additional include paths except for
-- those required by external libraries. Instead, we should always write the
-- full relative path, e.g. #include "maths/Vector3d.h". This avoids confusion
-- ("which file is meant?") and avoids enormous include path lists.
-- packages: engine static libs, main exe, atlas, atlas frontends, test.
--------------------------------------------------------------------------------
-- package helper functions
--------------------------------------------------------------------------------
function package_set_target(package_name)
-- Note: On Windows, ".exe" is added on the end, on unices the name is used directly
package.config["Debug" ].target = package_name.."_dbg"
package.config["Testing"].target = package_name.."_test"
package.config["Release"].target = package_name
local obj_dir_prefix = "obj/"..package_name.."_"
package.config["Debug" ].objdir = obj_dir_prefix.."Debug"
package.config["Testing"].objdir = obj_dir_prefix.."Test"
package.config["Release"].objdir = obj_dir_prefix.."Release"
end
function package_set_build_flags()
package.buildflags = { "with-symbols", "no-edit-and-continue" }
if not options["icc"] then
-- adds the -Wall compiler flag
tinsert(package.buildflags, "extra-warnings") -- this causes far too many warnings/remarks on ICC
end
-- PremakeWiki says with-symbols and optimize are automatically set for
-- Debug and Release builds, respectively. doesn't happen though, so do it manually.
package.config["Debug"].defines = { "DEBUG" }
package.config["Testing"].buildflags = { "no-runtime-checks" }
package.config["Testing"].defines = { "TESTING" }
package.config["Release"].buildflags = { "no-runtime-checks", "optimize-speed" }
package.config["Release"].defines = { "NDEBUG", "CONFIG_FINAL=1" }
-- required for the lowlevel library. must be set from all packages that use it, otherwise it assumes it is
-- being used as a DLL (which is currently not the case in 0ad)
tinsert(package.defines, "LIB_STATIC_LINK")
-- various platform-specific build flags
if OS == "windows" then
-- use native wchar_t type (not typedef to unsigned short)
tinsert(package.buildflags, "native-wchar_t")
else -- *nix
if options["icc"] then
tinsert(package.buildoptions, {
"-w1",
-- "-Wabi",
-- "-Wp64", -- complains about OBJECT_TO_JSVAL which is annoying
"-Wpointer-arith",
"-Wreturn-type",
-- "-Wshadow",
"-Wuninitialized",
"-Wunknown-pragmas",
"-Wunused-function",
"-wd1292", -- avoid lots of 'attribute "__nonnull__" ignored'
})
tinsert(package.config["Debug"].buildoptions, {
"-O0", -- ICC defaults to -O2
})
if OS == "macosx" then
tinsert(package.linkoptions, {"-multiply_defined","suppress"})
end
else
tinsert(package.buildoptions, {
-- enable most of the standard warnings
"-Wno-switch", -- enumeration value not handled in switch (this is sometimes useful, but results in lots of noise)
"-Wno-reorder", -- order of initialization list in constructors (lots of noise)
"-Wno-invalid-offsetof", -- offsetof on non-POD types (see comment in renderer/PatchRData.cpp)
"-Wextra",
"-Wno-missing-field-initializers", -- (this is common in external headers we can't fix)
-- add some other useful warnings that need to be enabled explicitly
"-Wunused-parameter",
"-Wredundant-decls", -- (useful for finding some multiply-included header files)
-- "-Wformat=2", -- (useful sometimes, but a bit noisy, so skip it by default)
-- "-Wcast-qual", -- (useful for checking const-correctness, but a bit noisy, so skip it by default)
"-Wnon-virtual-dtor", -- (sometimes noisy but finds real bugs)
"-Wundef", -- (useful for finding macro name typos)
-- enable security features (stack checking etc) that shouldn't have
-- a significant effect on performance and can catch bugs
"-fstack-protector-all",
"-D_FORTIFY_SOURCE=2",
-- always enable strict aliasing (useful in debug builds because of the warnings)
"-fstrict-aliasing",
-- do something (?) so that ccache can handle compilation with PCH enabled
"-fpch-preprocess",
-- enable SSE intrinsics
"-msse",
-- don't omit frame pointers (for now), because performance will be impacted
-- negatively by the way this breaks profilers more than it will be impacted
-- positively by the optimisation
"-fno-omit-frame-pointer",
})
if OS == "linux" then
tinsert(package.linkoptions, {
"-Wl,--no-undefined",
"-Wl,--as-needed",
})
end
if options["coverage"] then
tinsert(package.buildoptions, {"-fprofile-arcs", "-ftest-coverage"})
tinsert(package.links, "gcov")
end
-- To support intrinsics like __sync_bool_compare_and_swap on x86
-- we need to set -march to something that supports them
if arch == "x86" then
tinsert(package.buildoptions, "-march=i686")
end
-- We don't want to require SSE2 everywhere yet, but OS X headers do
-- require it (and Intel Macs always have it) so enable it here
if OS == "macosx" then
tinsert(package.buildoptions, "-msse2")
end
end
tinsert(package.buildoptions, {
-- Hide symbols in dynamic shared objects by default, for efficiency and for equivalence with
-- Windows - they should be exported explicitly with __attribute__ ((visibility ("default")))
"-fvisibility=hidden",
})
-- X11 includes may be installed in one of a gadzillion of three places
-- Famous last words: "You can't include too much! ;-)"
package.includepaths = {
"/usr/X11R6/include/X11",
"/usr/X11R6/include",
"/usr/include/X11"
}
package.libpaths = {
"/usr/X11R6/lib"
}
if options["bindir"] then
tinsert(package.defines, "INSTALLED_BINDIR=" .. options["bindir"])
end
if options["datadir"] then
tinsert(package.defines, "INSTALLED_DATADIR=" .. options["datadir"])
end
if options["libdir"] then
tinsert(package.defines, "INSTALLED_LIBDIR=" .. options["libdir"])
end
if OS == "linux" then
-- To use our local SpiderMonkey library, it needs to be part of the
-- runtime dynamic linker path. Add it with -rpath to make sure it gets found.
if options["libdir"] then
tinsert(package.linkoptions, {"-Wl,-rpath=" .. options["libdir"]})
else
-- Add the executable path:
tinsert(package.linkoptions, {"-Wl,-rpath='$$ORIGIN'"}) -- use Makefile escaping of '$'
end
end
end
end
-- create a package and set the attributes that are common to all packages.
function package_create(package_name, target_type)
-- Note: don't store in local variable. A global variable needs to
-- be set for Premake's use; it is implicitly used in e.g. matchfiles()
package = newpackage()
package.path = project.path
package.language = "c++"
package.name = package_name
package.kind = target_type
package.includepaths = {}
package_set_target(package_name)
package_set_build_flags()
return package
end
-- extra_params: table including zero or more of the following:
-- * no_default_pch: (any type) prevents adding the PCH include dir.
-- see setup_static_lib_package() for explanation of this scheme and rationale.
-- * extra_files: table of filenames (relative to source_root) to add to project
-- * extra_links: table of library names to add to link step
function package_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params)
-- We don't want the VC project to be deeply nested (once for each
-- folder in source_root). Therefore, remove the source_root
-- directory from the filenames (where those
-- names are used by Premake to construct the project tree), but set
-- 'trimprefix' (with Premake altered to recognise that) so the project
-- will still point to the correct filenames.
package.trimprefix = source_root
package.files = sourcesfromdirs(source_root, rel_source_dirs)
-- Put the project-specific PCH directory at the start of the
-- include path, so '#include "precompiled.h"' will look in
-- there first
if not extra_params["no_default_pch"] then
tinsert(package.includepaths, source_root .. "pch/" .. package.name)
end
-- next is source root dir, for absolute (nonrelative) includes
-- (e.g. "lib/precompiled.h")
tinsert(package.includepaths, source_root)
for i,v in pairs(rel_include_dirs) do
tinsert(package.includepaths, source_root .. v)
end
if extra_params["extra_files"] then
for i,v in pairs(extra_params["extra_files"]) do
tinsert(package.files, source_root .. v)
end
end
if extra_params["extra_links"] then
listconcat(package.links, extra_params["extra_links"])
end
end
-- Detect and set up PCH for the current package
function package_setup_pch(pch_dir, header, source)
if OS == "windows" or not options["without-pch"] then
package.pchheader = header -- "precompiled.h"
package.pchsource = source -- "precompiled.cpp"
if pch_dir then
tinsert(package.files, {
pch_dir..header,
pch_dir..source
})
end
for i,v in pairs(project.configs) do
tinsert(package.config[v].defines, "USING_PCH")
end
end
end
--------------------------------------------------------------------------------
-- engine static libraries
--------------------------------------------------------------------------------
-- the engine is split up into several static libraries. this eases separate
-- distribution of those components, reduces dependencies a bit, and can
-- also speed up builds.
-- more to the point, it is necessary to efficiently support a separate
-- test executable that also includes much of the game code.
-- names of all static libs created. automatically added to the
-- main app project later (see explanation at end of this file)
static_lib_names = {}
-- set up one of the static libraries into which the main engine code is split.
-- extra_params: see package_add_contents().
-- note: rel_source_dirs and rel_include_dirs are relative to global source_root.
function setup_static_lib_package (package_name, rel_source_dirs, extern_libs, extra_params)
package_create(package_name, "lib")
package_add_contents(source_root, rel_source_dirs, {}, extra_params)
package_add_extern_libs(extern_libs)
if not extra_params["no_default_link"] then
tinsert(static_lib_names, package_name)
end
if OS == "windows" then
tinsert(package.buildflags, "no-rtti")
end
-- Precompiled Headers
-- rationale: we need one PCH per static lib, since one global header would
-- increase dependencies. To that end, we can either include them as
-- "packagedir/precompiled.h", or add "source/PCH/packagedir" to the
-- include path and put the PCH there. The latter is better because
-- many packages contain several dirs and it's unclear where there the
-- PCH should be stored. This way is also a bit easier to use in that
-- source files always include "precompiled.h".
-- Notes:
-- * Visual Assist manages to use the project include path and can
-- correctly open these files from the IDE.
-- * precompiled.cpp (needed to "Create" the PCH) also goes in
-- the abovementioned dir.
if not extra_params["no_default_pch"] then
pch_dir = source_root.."pch/"..package_name.."/"
package_setup_pch(pch_dir, "precompiled.h", "precompiled.cpp")
end
end
-- this is where the source tree is chopped up into static libs.
-- can be changed very easily; just copy+paste a new setup_static_lib_package,
-- or remove existing ones. static libs are automagically added to
-- main_exe link step.
function setup_all_libs ()
-- relative to global source_root.
local source_dirs = {}
-- names of external libraries used (see libraries_dir comment)
local extern_libs = {}
source_dirs = {
"network",
}
extern_libs = {
"spidermonkey",
"enet",
"boost", -- dragged in via server->simulation.h->random
}
setup_static_lib_package("network", source_dirs, extern_libs, {})
source_dirs = {
"simulation2",
"simulation2/components",
"simulation2/helpers",
"simulation2/scripting",
"simulation2/serialization",
"simulation2/system",
"simulation2/testcomponents",
}
extern_libs = {
"boost",
"opengl",
"spidermonkey",
}
setup_static_lib_package("simulation2", source_dirs, extern_libs, {})
source_dirs = {
"scriptinterface",
}
extern_libs = {
"boost",
"spidermonkey",
"valgrind",
}
setup_static_lib_package("scriptinterface", source_dirs, extern_libs, {})
source_dirs = {
"ps",
"ps/scripting",
"ps/Network",
"ps/GameSetup",
"ps/XML",
"sound",
"scripting",
"maths",
"maths/scripting",
}
extern_libs = {
"spidermonkey",
"sdl", -- key definitions
"libxml2",
"opengl",
"zlib",
"boost",
"enet",
+ "libcurl",
}
setup_static_lib_package("engine", source_dirs, extern_libs, {})
source_dirs = {
"graphics",
"graphics/scripting",
"renderer"
}
extern_libs = {
"nvtt",
"opengl",
"sdl", -- key definitions
"spidermonkey", -- for graphics/scripting
"boost"
}
setup_static_lib_package("graphics", source_dirs, extern_libs, {})
source_dirs = {
"tools/atlas/GameInterface",
"tools/atlas/GameInterface/Handlers"
}
extern_libs = {
"boost",
"sdl", -- key definitions
"opengl",
"spidermonkey"
}
setup_static_lib_package("atlas", source_dirs, extern_libs, {})
source_dirs = {
"gui",
"gui/scripting"
}
if OS == "windows" then -- add JS files to allow VS find-in-solution and VA open-file-in-workspace
tinsert(source_dirs, "../binaries/data/mods/public/gui/test")
end
extern_libs = {
"spidermonkey",
"sdl", -- key definitions
"opengl",
"boost"
}
setup_static_lib_package("gui", source_dirs, extern_libs, {})
source_dirs = {
"lib",
"lib/allocators",
"lib/external_libraries",
"lib/file",
"lib/file/archive",
"lib/file/common",
"lib/file/io",
"lib/file/vfs",
"lib/pch",
"lib/posix",
"lib/res",
"lib/res/graphics",
"lib/res/sound",
"lib/sysdep",
"lib/tex"
}
extern_libs = {
"boost",
"sdl",
"opengl",
"libpng",
"zlib",
"openal",
"vorbis",
"libjpg",
"valgrind",
"cxxtest",
}
-- CPU architecture-specific
if arch == "amd64" then
tinsert(source_dirs, "lib/sysdep/arch/amd64");
tinsert(source_dirs, "lib/sysdep/arch/x86_x64");
else
tinsert(source_dirs, "lib/sysdep/arch/ia32");
tinsert(source_dirs, "lib/sysdep/arch/x86_x64");
end
-- OS-specific
sysdep_dirs = {
linux = { "lib/sysdep/os/linux", "lib/sysdep/os/unix", "lib/sysdep/os/unix/x" },
-- note: RC file must be added to main_exe package.
-- note: don't add "lib/sysdep/os/win/aken.cpp" because that must be compiled with the DDK.
windows = { "lib/sysdep/os/win", "lib/sysdep/os/win/wposix", "lib/sysdep/os/win/whrt" },
macosx = { "lib/sysdep/os/osx", "lib/sysdep/os/unix" },
}
for i,v in pairs(sysdep_dirs[OS]) do
tinsert(source_dirs, v);
end
-- runtime-library-specific
if options["target"] == "gnu" then
tinsert(source_dirs, "lib/sysdep/rtl/gcc");
else
tinsert(source_dirs, "lib/sysdep/rtl/msc");
end
setup_static_lib_package("lowlevel", source_dirs, extern_libs, {})
-- CxxTest mock function support
extern_libs = {
"boost",
"cxxtest",
}
-- 'real' implementations, to be linked against the main executable
setup_static_lib_package("mocks_real", {}, extern_libs, { no_default_link = 1, no_default_pch = 1 })
listconcat(package.files, matchfiles(source_root.."mocks/*.h", source_root.."mocks/*_real.cpp"))
-- 'test' implementations, to be linked against the test executable
setup_static_lib_package("mocks_test", {}, extern_libs, { no_default_link = 1, no_default_pch = 1 })
listconcat(package.files, matchfiles(source_root.."mocks/*.h", source_root.."mocks/*_test.cpp"))
end
--------------------------------------------------------------------------------
-- main EXE
--------------------------------------------------------------------------------
-- used for main EXE as well as test
used_extern_libs = {
"opengl",
"sdl",
"libjpg",
"libpng",
"zlib",
"spidermonkey",
"libxml2",
"openal",
"vorbis",
"boost",
"cxxtest",
"comsuppw",
"enet",
+ "libcurl",
"valgrind",
"nvtt",
}
-- Bundles static libs together with main.cpp and builds game executable.
function setup_main_exe ()
package_create("pyrogenesis", "winexe")
-- For VS2005, tell the linker to use the libraries' .obj files instead of
-- the .lib, to allow incremental linking.
-- (Reduces re-link time from ~20 seconds to ~2 secs)
tinsert(package.buildflags, "use-library-dep-inputs")
local extra_params = {
extra_files = { "main.cpp" },
}
package_add_contents(source_root, {}, {}, extra_params)
package_add_extern_libs(used_extern_libs)
tinsert(package.links, "mocks_real")
-- Platform Specifics
if OS == "windows" then
tinsert(package.files, source_root.."lib/sysdep/os/win/icon.rc")
-- from "lowlevel" static lib; must be added here to be linked in
tinsert(package.files, source_root.."lib/sysdep/os/win/error_dialog.rc")
-- VS2005 generates its own manifest, but earlier ones need us to add it manually
if (options["target"] == "vs2002" or options["target"] == "vs2003") then
tinsert(package.files, source_root.."lib/sysdep/os/win/manifest.rc")
end
tinsert(package.buildflags, "no-rtti")
-- this seems to be required when using vcbuild but not the IDE (?!)
tinsert(package.links, "ws2_32")
package.linkoptions = {
-- wraps main thread in a __try block(see wseh.cpp). replace with mainCRTStartup if that's undesired.
"/ENTRY:wseh_EntryPoint",
-- see wstartup.h
"/INCLUDE:_wstartup_InitAndRegisterShutdown",
-- delay loading of various Windows DLLs (not specific to any of the
-- external libraries; those are handled separately)
"/DELAYLOAD:ws2_32.dll",
-- allow manual unload of delay-loaded DLLs
"/DELAY:UNLOAD"
}
elseif OS == "linux" then
-- Libraries
tinsert(package.links, {
"fam",
-- Utilities
"rt",
-- Dynamic libraries (needed for linking for gold)
"dl",
})
-- Threading support
tinsert(package.buildoptions, "-pthread")
tinsert(package.linkoptions, "-pthread")
-- For debug_resolve_symbol
package.config["Debug"].linkoptions = { "-rdynamic" }
package.config["Testing"].linkoptions = { "-rdynamic" }
elseif OS == "macosx" then
-- Libraries
tinsert(package.links, { -- Utilities
"pthread"
})
end
end
--------------------------------------------------------------------------------
-- atlas
--------------------------------------------------------------------------------
-- setup a typical Atlas component package
-- extra_params: as in package_add_contents; also zero or more of the following:
-- * pch: (any type) set precompiled.h and .cpp as PCH
function setup_atlas_package(package_name, target_type, rel_source_dirs, rel_include_dirs, extern_libs, extra_params)
local source_root = "../../../source/tools/atlas/" .. package_name .. "/"
package_create(package_name, target_type)
-- Don't add the default 'sourceroot/pch/projectname' for finding PCH files
extra_params["no_default_pch"] = 1
package_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params)
package_add_extern_libs(extern_libs)
if extra_params["pch"] then
package_setup_pch(nil, "precompiled.h", "precompiled.cpp");
end
if options["aoe3ed"] then
tinsert(package.defines, "USE_AOE3ED")
end
-- Platform Specifics
if OS == "windows" then
tinsert(package.defines, "_UNICODE")
-- Link to required libraries
package.links = { "winmm", "comctl32", "rpcrt4", "delayimp", "ws2_32" }
-- required to use WinMain() on Windows, otherwise will default to main()
tinsert(package.buildflags, "no-main")
if extra_params["extra_links"] then
listconcat(package.links, extra_params["extra_links"])
end
elseif OS == "linux" then
tinsert(package.buildoptions, "-rdynamic")
tinsert(package.buildoptions, "-fPIC")
tinsert(package.linkoptions, "-fPIC")
tinsert(package.linkoptions, "-rdynamic")
if extra_params["no_unused_warnings"] then
if not options["icc"] then
tinsert(package.buildoptions, "-Wno-unused-parameter")
end
end
end
end
-- build all Atlas component packages
function setup_atlas_packages()
setup_atlas_package("AtlasObject", "lib",
{ -- src
""
},{ -- include
},{ -- extern_libs
"libxml2",
"wxwidgets"
},{ -- extra_params
})
setup_atlas_package("AtlasScript", "lib",
{ -- src
""
},{ -- include
".."
},{ -- extern_libs
"boost",
"spidermonkey",
"valgrind",
"wxwidgets",
},{ -- extra_params
})
setup_atlas_package("wxJS", "lib",
{ -- src
"",
"common",
"ext",
"gui",
"gui/control",
"gui/event",
"gui/misc",
"io",
},{ -- include
},{ -- extern_libs
"spidermonkey",
"wxwidgets"
},{ -- extra_params
pch = (not has_broken_pch),
no_unused_warnings = 1, -- wxJS has far too many and we're never going to fix them, so just ignore them
})
atlas_src = {
"ActorEditor",
"ColourTester",
"CustomControls/Buttons",
"CustomControls/Canvas",
"CustomControls/ColourDialog",
"CustomControls/DraggableListCtrl",
"CustomControls/EditableListCtrl",
"CustomControls/FileHistory",
"CustomControls/HighResTimer",
"CustomControls/SnapSplitterWindow",
"CustomControls/VirtualDirTreeCtrl",
"CustomControls/Windows",
"ErrorReporter",
"General",
"General/VideoRecorder",
"Misc",
"ScenarioEditor",
"ScenarioEditor/Sections/Common",
"ScenarioEditor/Sections/Cinematic",
"ScenarioEditor/Sections/Environment",
"ScenarioEditor/Sections/Map",
"ScenarioEditor/Sections/Object",
"ScenarioEditor/Sections/Terrain",
"ScenarioEditor/Sections/Trigger",
"ScenarioEditor/Tools",
"ScenarioEditor/Tools/Common",
}
atlas_extra_links = {
"AtlasObject",
"AtlasScript",
"wxJS",
}
if options["aoe3ed"] then
tinsert(atlas_src, "ArchiveViewer")
tinsert(atlas_src, "FileConverter")
tinsert(atlas_extra_links, "DatafileIO")
tinsert(atlas_extra_links, "xerces-c")
end
setup_atlas_package("AtlasUI", "dll", atlas_src,
{ -- include
"..",
"CustomControls",
"Misc"
},{ -- extern_libs
"boost",
"comsuppw",
"devil",
--"ffmpeg", -- disabled for now because it causes too many build difficulties
"libxml2",
"sdl", -- key definitions
"spidermonkey",
"wxwidgets",
"x11",
"zlib",
},{ -- extra_params
pch = (not has_broken_pch),
extra_links = atlas_extra_links,
extra_files = { "Misc/atlas.rc" }
})
if options["aoe3ed"] then
setup_atlas_package("DatafileIO", "lib",
{ -- src
"",
"BAR",
"DDT",
"SCN",
"Stream",
"XMB"
},{ -- include
},{ -- extern_libs
"devil",
"xerces",
"zlib"
},{ -- extra_params
pch = 1,
})
end
end
-- Atlas 'frontend' tool-launching packages
function setup_atlas_frontend_package (package_name)
package_create(package_name, "winexe")
package_add_extern_libs({
"spidermonkey",
})
local source_root = "../../../source/tools/atlas/AtlasFrontends/"
package.files = {
source_root..package_name..".cpp",
source_root..package_name..".rc"
}
package.trimprefix = source_root
package.includepaths = { source_root .. ".." }
-- Platform Specifics
if OS == "windows" then
tinsert(package.defines, "_UNICODE")
-- required to use WinMain() on Windows, otherwise will default to main()
tinsert(package.buildflags, "no-main")
else -- Non-Windows, = Unix
if options["aoe3ed"] then
tinsert(package.links, "DatafileIO")
end
tinsert(package.links, "AtlasObject")
end
tinsert(package.links, "AtlasUI")
end
function setup_atlas_frontends()
setup_atlas_frontend_package("ActorEditor")
setup_atlas_frontend_package("ColourTester")
if options["aoe3ed"] then
setup_atlas_frontend_package("ArchiveViewer")
setup_atlas_frontend_package("FileConverter")
end
end
--------------------------------------------------------------------------------
-- collada
--------------------------------------------------------------------------------
function setup_collada_package(package_name, target_type, rel_source_dirs, rel_include_dirs, extern_libs, extra_params)
package_create(package_name, target_type)
-- Don't add the default 'sourceroot/pch/projectname' for finding PCH files
extra_params["no_default_pch"] = 1
package_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params)
package_add_extern_libs(extern_libs)
if extra_params["pch"] then
package_setup_pch(nil, "precompiled.h", "precompiled.cpp");
end
-- Platform Specifics
if OS == "windows" then
-- required to use WinMain() on Windows, otherwise will default to main()
tinsert(package.buildflags, "no-main")
if extra_params["extra_links"] then
listconcat(package.links, extra_params["extra_links"])
end
end
if OS == "linux" then
tinsert(package.defines, "LINUX");
-- FCollada is not aliasing-safe, so disallow dangerous optimisations
-- (TODO: It'd be nice to fix FCollada, but that looks hard)
tinsert(package.buildoptions, "-fno-strict-aliasing")
tinsert(package.buildoptions, "-rdynamic")
tinsert(package.linkoptions, "-rdynamic")
elseif OS == "macosx" then
-- define MACOS-something?
-- On OSX, fcollada uses a few utility functions from coreservices
tinsert(package.linkoptions, "-framework CoreServices")
end
end
-- build all Collada component packages
function setup_collada_packages()
setup_collada_package("Collada", "dll",
{ -- src
"collada"
},{ -- include
},{ -- extern_libs
"fcollada",
"dl",
"libxml2",
},{ -- extra_params
pch = 1,
})
end
--------------------------------------------------------------------------------
-- tests
--------------------------------------------------------------------------------
function get_all_test_files(root, src_files, hdr_files)
-- note: lua doesn't have any directory handling functions at all, ugh.
-- premake's matchrecursive on patterns like *tests*.h doesn't work -
-- apparently it applies them to filenames, not the complete path.
-- our workaround is to enumerate all files and manually filter out the
-- desired */tests/* files. this is a bit slow, but hey.
local all_files = matchrecursive(root .. "*.h")
for i,v in pairs(all_files) do
-- header file in subdirectory test
if string.sub(v, -2) == ".h" and string.find(v, "/tests/") then
-- don't include sysdep tests on the wrong sys
-- don't include Atlas tests unless Atlas is being built
if not (string.find(v, "/sysdep/os/win/") and OS ~= "windows") and
not (string.find(v, "/tools/atlas/") and not options["atlas"])
then
tinsert(hdr_files, v)
-- add the corresponding source file immediately, instead of
-- waiting for it to appear after cxxtestgen. this avoids
-- having to recreate workspace 2x after adding a test.
tinsert(src_files, string.sub(v, 1, -3) .. ".cpp")
end
end
end
end
function setup_tests()
local src_files = {}
local hdr_files = {}
get_all_test_files(source_root, src_files, hdr_files)
package_create("test_gen", "cxxtestgen")
package.files = hdr_files
package.rootfile = source_root .. "test_root.cpp"
package.testoptions = "--have-std"
package.rootoptions = "--have-std"
if OS == "windows" then
package.rootoptions = package.rootoptions .. " --gui=PsTestWrapper --runner=Win32ODSPrinter"
else
package.rootoptions = package.rootoptions .. " --gui=PsTestWrapper --runner=ErrorPrinter"
end
-- precompiled headers - the header is added to all generated .cpp files
-- note that the header isn't actually precompiled here, only #included
-- so that the build stage can use it as a precompiled header.
include = " --include=precompiled.h"
package.rootoptions = package.rootoptions .. include
package.testoptions = package.testoptions .. include
tinsert(package.buildflags, "no-manifest")
package_create("test", "winexe")
links = static_lib_names
tinsert(links, "test_gen")
if options["atlas"] then
tinsert(links, "AtlasObject")
end
extra_params = {
extra_files = { "test_root.cpp", "test_setup.cpp" },
extra_links = links,
}
package_add_contents(source_root, {}, {}, extra_params)
-- note: these are not relative to source_root and therefore can't be included via package_add_contents.
listconcat(package.files, src_files)
package_add_extern_libs(used_extern_libs)
tinsert(package.links, "mocks_test")
if OS == "windows" then
-- from "lowlevel" static lib; must be added here to be linked in
tinsert(package.files, source_root.."lib/sysdep/os/win/error_dialog.rc")
tinsert(package.buildflags, "no-rtti")
-- this seems to be required when using vcbuild but not the IDE (?!)
tinsert(package.links, "ws2_32")
-- see wstartup.h
tinsert(package.linkoptions, "/INCLUDE:_wstartup_InitAndRegisterShutdown")
elseif OS == "linux" then
tinsert(package.links, {
"fam",
-- Utilities
"rt",
-- Dynamic libraries (needed for linking for gold)
"dl",
})
-- Threading support
tinsert(package.buildoptions, "-pthread")
tinsert(package.linkoptions, "-pthread")
-- For debug_resolve_symbol
package.config["Debug"].linkoptions = { "-rdynamic" }
package.config["Testing"].linkoptions = { "-rdynamic" }
tinsert(package.includepaths, source_root .. "pch/test/")
end
package_setup_pch(
source_root .. "pch/test/",
"precompiled.h",
"precompiled.cpp");
tinsert(package.buildflags, "use-library-dep-inputs")
end
-- must come first, so that VC sets it as the default project and therefore
-- allows running via F5 without the "where is the EXE" dialog.
setup_main_exe()
-- save package global variable for later (will be overwritten by setup_all_libs)
main_exe_package = package
setup_all_libs()
-- HACK: add the static libs to the main EXE project. only now (after
-- setup_all_libs has run) are the lib names known. cannot move
-- setup_main_exe to run after setup_all_libs (see comment above).
-- we also don't want to hardcode the names - that would require more
-- work when changing the static lib breakdown.
listconcat(main_exe_package.links, static_lib_names)
if options["atlas"] then
setup_atlas_packages()
setup_atlas_frontends()
end
if options["collada"] then
setup_collada_packages()
end
if not options["without-tests"] then
setup_tests()
end
Index: ps/trunk/source/ps/ConfigDB.cpp
===================================================================
--- ps/trunk/source/ps/ConfigDB.cpp (revision 8924)
+++ ps/trunk/source/ps/ConfigDB.cpp (revision 8925)
@@ -1,418 +1,423 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2011 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 "Pyrogenesis.h"
#include "Parser.h"
#include "ConfigDB.h"
#include "CLogger.h"
#include "Filesystem.h"
#include "scripting/ScriptingHost.h"
#include "scriptinterface/ScriptInterface.h"
#include
typedef std::map TConfigMap;
TConfigMap CConfigDB::m_Map[CFG_LAST];
-CStrW CConfigDB::m_ConfigFile[CFG_LAST];
-bool CConfigDB::m_UseVFS[CFG_LAST];
+VfsPath CConfigDB::m_ConfigFile[CFG_LAST];
#define GET_NS_PRIVATE(cx, obj) (EConfigNamespace)((intptr_t)JS_GetPrivate(cx, obj) >> 1)
namespace ConfigNamespace_JS
{
JSBool GetProperty(JSContext* cx, JSObject* obj, jsid id, jsval* vp)
{
EConfigNamespace cfgNs = GET_NS_PRIVATE(cx, obj);
if (cfgNs < 0 || cfgNs >= CFG_LAST)
return JS_FALSE;
jsval idval;
if (!JS_IdToValue(cx, id, &idval))
return JS_FALSE;
std::string propName;
if (!ScriptInterface::FromJSVal(cx, idval, propName))
return JS_FALSE;
CConfigValue *val = g_ConfigDB.GetValue(cfgNs, propName);
if (val)
{
JSString *js_str = JS_NewStringCopyN(cx, val->m_String.c_str(), val->m_String.size());
*vp = STRING_TO_JSVAL(js_str);
}
return JS_TRUE;
}
JSBool SetProperty(JSContext* cx, JSObject* obj, jsid id, jsval* vp)
{
EConfigNamespace cfgNs = GET_NS_PRIVATE(cx, obj);
if (cfgNs < 0 || cfgNs >= CFG_LAST)
return JS_FALSE;
jsval idval;
if (!JS_IdToValue(cx, id, &idval))
return JS_FALSE;
std::string propName;
if (!ScriptInterface::FromJSVal(cx, idval, propName))
return JS_FALSE;
CConfigValue *val = g_ConfigDB.CreateValue(cfgNs, propName);
if (!ScriptInterface::FromJSVal(cx, *vp, val->m_String))
return JS_FALSE;
return JS_TRUE;
}
JSClass Class = {
"ConfigNamespace", JSCLASS_HAS_PRIVATE,
JS_PropertyStub, JS_PropertyStub,
GetProperty, SetProperty,
JS_EnumerateStub, JS_ResolveStub,
JS_ConvertStub, JS_FinalizeStub
};
JSBool Construct(JSContext* cx, uintN argc, jsval* vp)
{
UNUSED2(argc);
JSObject *newObj = JS_NewObject(cx, &Class, NULL, NULL);
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(newObj));
return JS_TRUE;
}
void SetNamespace(JSContext *cx, JSObject *obj, EConfigNamespace cfgNs)
{
JS_SetPrivate(cx, obj, (void *)((uintptr_t)cfgNs << 1)); // JS requires bottom bit = 0
}
JSBool WriteFile(JSContext* cx, uintN argc, jsval* vp)
{
EConfigNamespace cfgNs = GET_NS_PRIVATE(cx, JS_THIS_OBJECT(cx, vp));
if (cfgNs < 0 || cfgNs >= CFG_LAST)
return JS_FALSE;
- if (argc != 2)
- return JS_FALSE;
-
- bool useVFS;
- if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], useVFS))
+ if (argc != 1)
return JS_FALSE;
std::wstring path;
- if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[1], path))
+ if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], path))
return JS_FALSE;
- bool res = g_ConfigDB.WriteFile(cfgNs, useVFS, path);
+ bool res = g_ConfigDB.WriteFile(cfgNs, path);
JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(res));
return JS_TRUE;
}
JSBool Reload(JSContext* cx, uintN argc, jsval* vp)
{
if (argc != 0)
return JS_FALSE;
EConfigNamespace cfgNs = GET_NS_PRIVATE(cx, JS_THIS_OBJECT(cx, vp));
if (cfgNs < 0 || cfgNs >= CFG_LAST)
return JS_FALSE;
JSBool ret = g_ConfigDB.Reload(cfgNs);
JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(ret));
return JS_TRUE;
}
JSBool SetFile(JSContext* cx, uintN argc, jsval* vp)
{
EConfigNamespace cfgNs = GET_NS_PRIVATE(cx, JS_THIS_OBJECT(cx, vp));
if (cfgNs < 0 || cfgNs >= CFG_LAST)
return JS_FALSE;
- if (argc != 2)
- return JS_FALSE;
-
- bool useVFS;
- if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], useVFS))
+ if (argc != 1)
return JS_FALSE;
std::wstring path;
- if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[1], path))
+ if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], path))
return JS_FALSE;
- g_ConfigDB.SetConfigFile(cfgNs, useVFS, path);
+ g_ConfigDB.SetConfigFile(cfgNs, path);
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
JSFunctionSpec Funcs[] = {
{ "writeFile", WriteFile, 2, 0 },
{ "reload", Reload, 0, 0 },
{ "setFile", SetFile, 2, 0 },
{0}
};
};
namespace ConfigDB_JS
{
JSClass Class = {
"ConfigDB", 0,
JS_PropertyStub, JS_PropertyStub,
JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, JS_ResolveStub,
JS_ConvertStub, JS_FinalizeStub
};
JSPropertySpec Props[] = {
{0}
};
JSFunctionSpec Funcs[] = {
{0}
};
JSBool Construct(JSContext* cx, uintN argc, jsval* vp)
{
UNUSED2(argc);
JSObject *newObj = JS_NewObject(cx, &Class, NULL, NULL);
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(newObj));
int flags=JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT;
#define cfg_ns(_propname, _enum) STMT (\
JSObject *nsobj=g_ScriptingHost.CreateCustomObject("ConfigNamespace"); \
debug_assert(nsobj); \
ConfigNamespace_JS::SetNamespace(cx, nsobj, _enum); \
debug_assert(JS_DefineProperty(cx, newObj, _propname, OBJECT_TO_JSVAL(nsobj), NULL, NULL, flags)); )
cfg_ns("default", CFG_DEFAULT);
cfg_ns("system", CFG_SYSTEM);
cfg_ns("user", CFG_USER);
cfg_ns("mod", CFG_MOD);
#undef cfg_ns
return JS_TRUE;
}
};
CConfigDB::CConfigDB()
{
g_ScriptingHost.DefineCustomObjectType(&ConfigDB_JS::Class, ConfigDB_JS::Construct, 0, ConfigDB_JS::Props, ConfigDB_JS::Funcs, NULL, NULL);
g_ScriptingHost.DefineCustomObjectType(&ConfigNamespace_JS::Class, ConfigNamespace_JS::Construct, 0, NULL, ConfigNamespace_JS::Funcs, NULL, NULL);
JSObject *js_ConfigDB = g_ScriptingHost.CreateCustomObject("ConfigDB");
g_ScriptingHost.SetGlobal("g_ConfigDB", OBJECT_TO_JSVAL(js_ConfigDB));
}
CConfigValue *CConfigDB::GetValue(EConfigNamespace ns, const CStr& name)
{
CConfigValueSet* values = GetValues(ns, name);
if (!values)
return (NULL);
return &((*values)[0]);
}
CConfigValueSet *CConfigDB::GetValues(EConfigNamespace ns, const CStr& name)
{
if (ns < 0 || ns >= CFG_LAST)
{
debug_warn(L"CConfigDB: Invalid ns value");
return NULL;
}
TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name);
if (it != m_Map[CFG_COMMAND].end())
return &(it->second);
for (int search_ns = ns; search_ns >= 0; search_ns--)
{
TConfigMap::iterator it = m_Map[search_ns].find(name);
if (it != m_Map[search_ns].end())
return &(it->second);
}
return NULL;
}
std::vector > CConfigDB::GetValuesWithPrefix(EConfigNamespace ns, const CStr& prefix)
{
std::vector > ret;
if (ns < 0 || ns >= CFG_LAST)
{
debug_warn(L"CConfigDB: Invalid ns value");
return ret;
}
for (TConfigMap::iterator it = m_Map[CFG_COMMAND].begin(); it != m_Map[CFG_COMMAND].end(); ++it)
{
if (boost::algorithm::starts_with(it->first, prefix))
ret.push_back(std::make_pair(it->first, it->second));
}
for (int search_ns = ns; search_ns >= 0; search_ns--)
{
for (TConfigMap::iterator it = m_Map[search_ns].begin(); it != m_Map[search_ns].end(); ++it)
{
if (boost::algorithm::starts_with(it->first, prefix))
ret.push_back(std::make_pair(it->first, it->second));
}
}
return ret;
}
CConfigValue *CConfigDB::CreateValue(EConfigNamespace ns, const CStr& name)
{
if (ns < 0 || ns >= CFG_LAST)
{
debug_warn(L"CConfigDB: Invalid ns value");
return NULL;
}
CConfigValue *ret=GetValue(ns, name);
if (ret) return ret;
TConfigMap::iterator it=m_Map[ns].insert(m_Map[ns].begin(), make_pair(name, CConfigValueSet( 1 )));
return &(it->second[0]);
}
-void CConfigDB::SetConfigFile(EConfigNamespace ns, bool useVFS, const CStrW& path)
+void CConfigDB::SetConfigFile(EConfigNamespace ns, const VfsPath& path)
{
if (ns < 0 || ns >= CFG_LAST)
{
debug_warn(L"CConfigDB: Invalid ns value");
return;
}
m_ConfigFile[ns]=path;
- m_UseVFS[ns]=useVFS;
}
bool CConfigDB::Reload(EConfigNamespace ns)
{
+ if (ns < 0 || ns >= CFG_LAST)
+ {
+ debug_warn(L"CConfigDB: Invalid ns value");
+ return false;
+ }
+
// Set up CParser
CParser parser;
CParserLine parserLine;
parser.InputTaskType("Assignment", "_$ident_=<_[-$arg(_minus)]_$value_,>_[-$arg(_minus)]_$value[[;]$rest]");
parser.InputTaskType("CommentOrBlank", "_[;[$rest]]");
// Open file with VFS
shared_ptr buffer; size_t buflen;
{
// Handle missing files quietly
if (g_VFS->GetFileInfo(m_ConfigFile[ns], NULL) < 0)
{
- LOGMESSAGE(L"Cannot find config file \"%ls\" - ignoring", m_ConfigFile[ns].c_str());
+ LOGMESSAGE(L"Cannot find config file \"%ls\" - ignoring", m_ConfigFile[ns].string().c_str());
return false;
}
else
{
- LOGMESSAGE(L"Loading config file \"%ls\"", m_ConfigFile[ns].c_str());
+ LOGMESSAGE(L"Loading config file \"%ls\"", m_ConfigFile[ns].string().c_str());
LibError ret = g_VFS->LoadFile(m_ConfigFile[ns], buffer, buflen);
if (ret != INFO::OK)
{
- LOGERROR(L"CConfigDB::Reload(): vfs_load for \"%ls\" failed: return was %ld", m_ConfigFile[ns].c_str(), ret);
+ LOGERROR(L"CConfigDB::Reload(): vfs_load for \"%ls\" failed: return was %ld", m_ConfigFile[ns].string().c_str(), ret);
return false;
}
}
}
TConfigMap newMap;
char *filebuf=(char *)buffer.get();
char *filebufend=filebuf+buflen;
// Read file line by line
char *next=filebuf-1;
do
{
char *pos=next+1;
next=(char *)memchr(pos, '\n', filebufend-pos);
if (!next) next=filebufend;
char *lend=next;
if (lend > filebuf && *(lend-1) == '\r') lend--;
// Send line to parser
bool parseOk=parserLine.ParseString(parser, std::string(pos, lend));
// Get name and value from parser
std::string name;
std::string value;
if (parseOk &&
parserLine.GetArgCount()>=2 &&
parserLine.GetArgString(0, name) &&
parserLine.GetArgString(1, value))
{
// Add name and value to the map
size_t argCount = parserLine.GetArgCount();
newMap[name].clear();
for( size_t t = 0; t < argCount; t++ )
{
if( !parserLine.GetArgString( (int)t + 1, value ) )
continue;
CConfigValue argument;
argument.m_String = value;
newMap[name].push_back( argument );
LOGMESSAGE(L"Loaded config string \"%hs\" = \"%hs\"", name.c_str(), value.c_str());
}
}
}
while (next < filebufend);
m_Map[ns].swap(newMap);
return true;
}
-bool CConfigDB::WriteFile(EConfigNamespace ns, bool useVFS, const CStrW& path)
+bool CConfigDB::WriteFile(EConfigNamespace ns)
{
- debug_assert(useVFS);
+ if (ns < 0 || ns >= CFG_LAST)
+ {
+ debug_warn(L"CConfigDB: Invalid ns value");
+ return false;
+ }
+
+ return WriteFile(ns, m_ConfigFile[ns]);
+}
+bool CConfigDB::WriteFile(EConfigNamespace ns, const VfsPath& path)
+{
if (ns < 0 || ns >= CFG_LAST)
{
debug_warn(L"CConfigDB: Invalid ns value");
return false;
}
shared_ptr buf = io_Allocate(1*MiB);
char* pos = (char*)buf.get();
TConfigMap &map=m_Map[ns];
for(TConfigMap::const_iterator it = map.begin(); it != map.end(); ++it)
{
pos += sprintf(pos, "%s = \"%s\"\n", it->first.c_str(), it->second[0].m_String.c_str());
}
const size_t len = pos - (char*)buf.get();
LibError ret = g_VFS->CreateFile(path, buf, len);
if(ret < 0)
{
- LOGERROR(L"CConfigDB::WriteFile(): CreateFile \"%ls\" failed (error: %d)", path.c_str(), (int)ret);
+ LOGERROR(L"CConfigDB::WriteFile(): CreateFile \"%ls\" failed (error: %d)", path.string().c_str(), (int)ret);
return false;
}
return true;
}
Index: ps/trunk/source/ps/GameSetup/Config.cpp
===================================================================
--- ps/trunk/source/ps/GameSetup/Config.cpp (revision 8924)
+++ ps/trunk/source/ps/GameSetup/Config.cpp (revision 8925)
@@ -1,199 +1,186 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2011 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 "ps/ConfigDB.h"
#include "ps/CConsole.h"
#include "ps/GameSetup/CmdLineArgs.h"
#include "lib/timer.h"
#include "lib/utf8.h"
#include "lib/res/sound/snd_mgr.h"
#include "Config.h"
// (these variables are documented in the header.)
CStrW g_CursorName = L"test";
-CStr g_ActiveProfile = "default";
bool g_NoGLS3TC = false;
bool g_NoGLAutoMipmap = false;
bool g_NoGLVBO = false;
bool g_NoGLFramebufferObject = false;
bool g_Shadows = false;
bool g_FancyWater = false;
float g_LodBias = 0.0f;
float g_Gamma = 1.0f;
bool g_EntGraph = false;
CStr g_RenderPath = "default";
int g_xres, g_yres;
bool g_VSync = false;
bool g_Quickstart = false;
bool g_DisableAudio = false;
// flag to switch on drawing terrain overlays
bool g_ShowPathfindingOverlay = false;
// flag to switch on triangulation pathfinding
bool g_TriPathfind = false;
// If non-empty, specified map will be automatically loaded
CStr g_AutostartMap = "";
//----------------------------------------------------------------------------
-// config and profile
+// config
//----------------------------------------------------------------------------
-static void LoadProfile( const CStr& profile )
-{
- VfsPath path = VfsPath(L"config/profiles") / wstring_from_utf8(profile);
-
- VfsPath configFilename = path / L"settings/user.cfg";
- g_ConfigDB.SetConfigFile(CFG_USER, true, configFilename.string().c_str());
- g_ConfigDB.Reload(CFG_USER);
-
- int max_history_lines = 200;
- CFG_GET_USER_VAL("console.history.size", Int, max_history_lines);
- g_Console->UseHistoryFile(path / L"settings/history", max_history_lines);
-}
-
-
// Fill in the globals from the config files.
static void LoadGlobals()
{
- CFG_GET_SYS_VAL("profile", String, g_ActiveProfile);
-
- // Now load the profile before trying to retrieve the values of the rest of these.
-
- LoadProfile( g_ActiveProfile );
-
CFG_GET_USER_VAL("vsync", Bool, g_VSync);
CFG_GET_USER_VAL("nos3tc", Bool, g_NoGLS3TC);
CFG_GET_USER_VAL("noautomipmap", Bool, g_NoGLAutoMipmap);
CFG_GET_USER_VAL("novbo", Bool, g_NoGLVBO);
CFG_GET_USER_VAL("noframebufferobject", Bool, g_NoGLFramebufferObject);
CFG_GET_USER_VAL("shadows", Bool, g_Shadows);
CFG_GET_USER_VAL("fancywater", Bool, g_FancyWater);
CFG_GET_USER_VAL("renderpath", String, g_RenderPath);
CFG_GET_USER_VAL("lodbias", Float, g_LodBias);
float gain = -1.0f;
CFG_GET_USER_VAL("sound.mastergain", Float, gain);
if(gain >= 0.0f)
WARN_ERR(snd_set_master_gain(gain));
}
static void ProcessCommandLineArgs(const CmdLineArgs& args)
{
// TODO: all these options (and the ones processed elsewhere) should
// be documented somewhere for users.
if (args.Has("buildarchive"))
{
// note: VFS init is sure to have been completed by now
// (since CONFIG_Init reads from file); therefore,
// it is safe to call this from here directly.
// vfs_opt_rebuild_main_archive("mods/official/official1.zip", "../logs/trace.txt");
}
// Handle "-conf=key:value" (potentially multiple times)
std::vector conf = args.GetMultiple("conf");
for (size_t i = 0; i < conf.size(); ++i)
{
CStr name_value = conf[i];
if (name_value.Find(':') != -1)
{
CStr name = name_value.BeforeFirst(":");
CStr value = name_value.AfterFirst(":");
g_ConfigDB.CreateValue(CFG_COMMAND, name)->m_String = value;
}
}
if (args.Has("entgraph"))
g_EntGraph = true;
if (args.Has("g"))
{
g_Gamma = (float)atof(args.Get("g"));
if (g_Gamma == 0.0f)
g_Gamma = 1.0f;
}
// if (args.Has("listfiles"))
// trace_enable(true);
if (args.Has("profile"))
g_ConfigDB.CreateValue(CFG_COMMAND, "profile")->m_String = args.Get("profile");
if (args.Has("quickstart"))
{
g_Quickstart = true;
g_DisableAudio = true; // do this for backward-compatibility with user expectations
}
if (args.Has("nosound"))
g_DisableAudio = true;
if (args.Has("shadows"))
g_ConfigDB.CreateValue(CFG_COMMAND, "shadows")->m_String = "true";
if (args.Has("xres"))
g_ConfigDB.CreateValue(CFG_COMMAND, "xres")->m_String = args.Get("xres");
if (args.Has("yres"))
g_ConfigDB.CreateValue(CFG_COMMAND, "yres")->m_String = args.Get("yres");
if (args.Has("vsync"))
g_ConfigDB.CreateValue(CFG_COMMAND, "vsync")->m_String = "true";
}
void CONFIG_Init(const CmdLineArgs& args)
{
TIMER(L"CONFIG_Init");
new CConfigDB;
// Load the global, default config file
- g_ConfigDB.SetConfigFile(CFG_DEFAULT, false, L"config/default.cfg");
+ g_ConfigDB.SetConfigFile(CFG_DEFAULT, L"config/default.cfg");
g_ConfigDB.Reload(CFG_DEFAULT); // 216ms
// Try loading the local system config file (which doesn't exist by
// default) - this is designed as a way of letting developers edit the
// system config without accidentally committing their changes back to SVN.
- g_ConfigDB.SetConfigFile(CFG_SYSTEM, false, L"config/local.cfg");
+ g_ConfigDB.SetConfigFile(CFG_SYSTEM, L"config/local.cfg");
g_ConfigDB.Reload(CFG_SYSTEM);
- g_ConfigDB.SetConfigFile(CFG_MOD, true, L"config/mod.cfg");
+ g_ConfigDB.SetConfigFile(CFG_USER, L"config/user.cfg");
+ g_ConfigDB.Reload(CFG_USER);
+
+ g_ConfigDB.SetConfigFile(CFG_MOD, L"config/mod.cfg");
// No point in reloading mod.cfg here - we haven't mounted mods yet
ProcessCommandLineArgs(args);
+ // Initialise console history file
+ int max_history_lines = 200;
+ CFG_GET_USER_VAL("console.history.size", Int, max_history_lines);
+ g_Console->UseHistoryFile(L"config/console.txt", max_history_lines);
+
// Collect information from system.cfg, the profile file,
// and any command-line overrides to fill in the globals.
LoadGlobals(); // 64ms
}
Index: ps/trunk/source/ps/GameSetup/HWDetect.cpp
===================================================================
--- ps/trunk/source/ps/GameSetup/HWDetect.cpp (revision 8924)
+++ ps/trunk/source/ps/GameSetup/HWDetect.cpp (revision 8925)
@@ -1,103 +1,258 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2011 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 "scripting/ScriptingHost.h"
#include "scriptinterface/ScriptInterface.h"
#include "lib/ogl.h"
+#include "lib/svn_revision.h"
+#include "lib/timer.h"
#include "lib/utf8.h"
#include "lib/sysdep/cpu.h"
#include "lib/sysdep/gfx.h"
#include "lib/sysdep/os_cpu.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
+#include "ps/UserReport.h"
#include "ps/VideoMode.h"
#include "ps/GameSetup/Config.h"
void SetDisableAudio(void* UNUSED(cbdata), bool disabled)
{
g_DisableAudio = disabled;
}
+static void ReportGLLimits(ScriptInterface& scriptInterface, CScriptValRooted settings);
+
void RunHardwareDetection()
{
+ TIMER(L"RunHardwareDetection");
+
ScriptInterface& scriptInterface = g_ScriptingHost.GetScriptInterface();
scriptInterface.RegisterFunction("SetDisableAudio");
// Load the detection script:
const wchar_t* scriptName = L"hwdetect/hwdetect.js";
CVFSFile file;
if (file.Load(g_VFS, scriptName) != PSRETURN_OK)
{
LOGERROR(L"Failed to load hardware detection script");
return;
}
LibError err; // ignore encoding errors
std::wstring code = wstring_from_utf8(file.GetAsString(), &err);
scriptInterface.LoadScript(scriptName, code);
// Collect all the settings we'll pass to the script:
+ // (We'll use this same data for the opt-in online reporting system, so it
+ // includes some fields that aren't directly useful for the hwdetect script)
CScriptValRooted settings;
scriptInterface.Eval("({})", settings);
- scriptInterface.SetProperty(settings.get(), "os_unix", OS_UNIX, false);
- scriptInterface.SetProperty(settings.get(), "os_linux", OS_LINUX, false);
- scriptInterface.SetProperty(settings.get(), "os_macosx", OS_MACOSX, false);
- scriptInterface.SetProperty(settings.get(), "os_win", OS_WIN, false);
-
- scriptInterface.SetProperty(settings.get(), "gfx_card", std::wstring(gfx_card), false);
- scriptInterface.SetProperty(settings.get(), "gfx_drv_ver", std::wstring(gfx_drv_ver), false);
- scriptInterface.SetProperty(settings.get(), "gfx_mem", gfx_mem, false);
-
- scriptInterface.SetProperty(settings.get(), "gl_vendor", std::string((const char*)glGetString(GL_VENDOR)), false);
- scriptInterface.SetProperty(settings.get(), "gl_renderer", std::string((const char*)glGetString(GL_RENDERER)), false);
- scriptInterface.SetProperty(settings.get(), "gl_version", std::string((const char*)glGetString(GL_VERSION)), false);
-
- const char* exts = ogl_ExtensionString();
- if (!exts) exts = "";
- scriptInterface.SetProperty(settings.get(), "gl_extensions", std::string(exts), false);
- scriptInterface.SetProperty(settings.get(), "gl_max_tex_size", (int)ogl_max_tex_size, false);
- scriptInterface.SetProperty(settings.get(), "gl_max_tex_units", (int)ogl_max_tex_units, false);
-
- scriptInterface.SetProperty(settings.get(), "video_xres", g_VideoMode.GetXRes(), false);
- scriptInterface.SetProperty(settings.get(), "video_yres", g_VideoMode.GetYRes(), false);
- scriptInterface.SetProperty(settings.get(), "video_bpp", g_VideoMode.GetBPP(), false);
+ scriptInterface.SetProperty(settings.get(), "os_unix", OS_UNIX);
+ scriptInterface.SetProperty(settings.get(), "os_linux", OS_LINUX);
+ scriptInterface.SetProperty(settings.get(), "os_macosx", OS_MACOSX);
+ scriptInterface.SetProperty(settings.get(), "os_win", OS_WIN);
+
+ scriptInterface.SetProperty(settings.get(), "arch_ia32", ARCH_IA32);
+ scriptInterface.SetProperty(settings.get(), "arch_amd64", ARCH_AMD64);
+
+#ifdef NDEBUG
+ scriptInterface.SetProperty(settings.get(), "build_debug", 0);
+#else
+ scriptInterface.SetProperty(settings.get(), "build_debug", 1);
+#endif
+ scriptInterface.SetProperty(settings.get(), "build_datetime", std::string(__DATE__ " " __TIME__));
+ scriptInterface.SetProperty(settings.get(), "build_revision", std::wstring(svn_revision));
+
+ scriptInterface.SetProperty(settings.get(), "gfx_card", std::wstring(gfx_card));
+ scriptInterface.SetProperty(settings.get(), "gfx_drv_ver", std::wstring(gfx_drv_ver));
+ scriptInterface.SetProperty(settings.get(), "gfx_mem", gfx_mem);
+
+ ReportGLLimits(scriptInterface, settings);
+
+ scriptInterface.SetProperty(settings.get(), "video_xres", g_VideoMode.GetXRes());
+ scriptInterface.SetProperty(settings.get(), "video_yres", g_VideoMode.GetYRes());
+ scriptInterface.SetProperty(settings.get(), "video_bpp", g_VideoMode.GetBPP());
struct utsname un;
uname(&un);
- scriptInterface.SetProperty(settings.get(), "uname_sysname", std::string(un.sysname), false);
- scriptInterface.SetProperty(settings.get(), "uname_release", std::string(un.release), false);
- scriptInterface.SetProperty(settings.get(), "uname_version", std::string(un.version), false);
- scriptInterface.SetProperty(settings.get(), "uname_machine", std::string(un.machine), false);
+ scriptInterface.SetProperty(settings.get(), "uname_sysname", std::string(un.sysname));
+ scriptInterface.SetProperty(settings.get(), "uname_release", std::string(un.release));
+ scriptInterface.SetProperty(settings.get(), "uname_version", std::string(un.version));
+ scriptInterface.SetProperty(settings.get(), "uname_machine", std::string(un.machine));
+
+ scriptInterface.SetProperty(settings.get(), "cpu_identifier", std::string(cpu_IdentifierString()));
+ scriptInterface.SetProperty(settings.get(), "cpu_frequency", os_cpu_ClockFrequency());
- scriptInterface.SetProperty(settings.get(), "cpu_identifier", std::string(cpu_IdentifierString()), false);
- scriptInterface.SetProperty(settings.get(), "cpu_frequency", os_cpu_ClockFrequency(), false);
+ scriptInterface.SetProperty(settings.get(), "ram_total", (int)os_cpu_MemorySize());
+ scriptInterface.SetProperty(settings.get(), "ram_free", (int)os_cpu_MemoryAvailable());
- scriptInterface.SetProperty(settings.get(), "ram_total", (int)os_cpu_MemorySize(), false);
- scriptInterface.SetProperty(settings.get(), "ram_free", (int)os_cpu_MemoryAvailable(), false);
+ // Send the same data to the reporting system
+ g_UserReporter.SubmitReport("hwdetect", 1, scriptInterface.StringifyJSON(settings.get(), false));
// Run the detection script:
scriptInterface.CallFunctionVoid(scriptInterface.GetGlobalObject(), "RunHardwareDetection", settings);
}
+
+
+static void ReportGLLimits(ScriptInterface& scriptInterface, CScriptValRooted settings)
+{
+#define INTEGER(id) do { \
+ GLint i = -1; \
+ glGetIntegerv(GL_##id, &i); \
+ ogl_WarnIfError(); \
+ scriptInterface.SetProperty(settings.get(), "GL_" #id, i); \
+ } while (false)
+
+#define INTEGER2(id) do { \
+ GLint i[2] = { -1, -1 }; \
+ glGetIntegerv(GL_##id, i); \
+ ogl_WarnIfError(); \
+ scriptInterface.SetProperty(settings.get(), "GL_" #id "[0]", i[0]); \
+ scriptInterface.SetProperty(settings.get(), "GL_" #id "[1]", i[1]); \
+ } while (false)
+
+#define FLOAT(id) do { \
+ GLfloat f = std::numeric_limits::quiet_NaN(); \
+ glGetFloatv(GL_##id, &f); \
+ ogl_WarnIfError(); \
+ scriptInterface.SetProperty(settings.get(), "GL_" #id, f); \
+ } while (false)
+
+#define FLOAT2(id) do { \
+ GLfloat f[2] = { std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN() }; \
+ glGetFloatv(GL_##id, f); \
+ ogl_WarnIfError(); \
+ scriptInterface.SetProperty(settings.get(), "GL_" #id "[0]", f[0]); \
+ scriptInterface.SetProperty(settings.get(), "GL_" #id "[1]", f[1]); \
+ } while (false)
+
+#define STRING(id) do { \
+ const char* c = (const char*)glGetString(GL_##id); \
+ ogl_WarnIfError(); \
+ if (!c) c = ""; \
+ scriptInterface.SetProperty(settings.get(), "GL_" #id, std::string(c)); \
+ } while (false)
+
+#define BOOL(id) INTEGER(id)
+
+ // Core OpenGL 1.3:
+ // (We don't bother checking extension strings for anything older than 1.3;
+ // it'll just produce harmless warnings)
+ STRING(VERSION);
+ STRING(VENDOR);
+ STRING(RENDERER);
+ STRING(EXTENSIONS);
+ INTEGER(MAX_LIGHTS);
+ INTEGER(MAX_CLIP_PLANES);
+ if (ogl_HaveExtension("GL_ARB_imaging")) // only in imaging subset
+ INTEGER(MAX_COLOR_MATRIX_STACK_DEPTH);
+ INTEGER(MAX_MODELVIEW_STACK_DEPTH);
+ INTEGER(MAX_PROJECTION_STACK_DEPTH);
+ INTEGER(MAX_TEXTURE_STACK_DEPTH);
+ INTEGER(SUBPIXEL_BITS);
+ INTEGER(MAX_3D_TEXTURE_SIZE);
+ INTEGER(MAX_TEXTURE_SIZE);
+ INTEGER(MAX_CUBE_MAP_TEXTURE_SIZE);
+ INTEGER(MAX_PIXEL_MAP_TABLE);
+ INTEGER(MAX_NAME_STACK_DEPTH);
+ INTEGER(MAX_LIST_NESTING);
+ INTEGER(MAX_EVAL_ORDER);
+ INTEGER2(MAX_VIEWPORT_DIMS);
+ INTEGER(MAX_ATTRIB_STACK_DEPTH);
+ INTEGER(MAX_CLIENT_ATTRIB_STACK_DEPTH);
+ INTEGER(AUX_BUFFERS);
+ BOOL(RGBA_MODE);
+ BOOL(INDEX_MODE);
+ BOOL(DOUBLEBUFFER);
+ BOOL(STEREO);
+ FLOAT2(ALIASED_POINT_SIZE_RANGE);
+ FLOAT2(SMOOTH_POINT_SIZE_RANGE);
+ FLOAT(SMOOTH_POINT_SIZE_GRANULARITY);
+ FLOAT2(ALIASED_LINE_WIDTH_RANGE);
+ FLOAT2(SMOOTH_LINE_WIDTH_RANGE);
+ FLOAT(SMOOTH_LINE_WIDTH_GRANULARITY);
+ // Skip MAX_CONVOLUTION_WIDTH, MAX_CONVOLUTION_HEIGHT since they'd need GetConvolutionParameteriv
+ INTEGER(MAX_ELEMENTS_INDICES);
+ INTEGER(MAX_ELEMENTS_VERTICES);
+ INTEGER(MAX_TEXTURE_UNITS);
+ INTEGER(SAMPLE_BUFFERS);
+ INTEGER(SAMPLES);
+ // TODO: compressed texture formats
+ INTEGER(RED_BITS);
+ INTEGER(GREEN_BITS);
+ INTEGER(BLUE_BITS);
+ INTEGER(ALPHA_BITS);
+ INTEGER(INDEX_BITS);
+ INTEGER(DEPTH_BITS);
+ INTEGER(STENCIL_BITS);
+ INTEGER(ACCUM_RED_BITS);
+ INTEGER(ACCUM_GREEN_BITS);
+ INTEGER(ACCUM_BLUE_BITS);
+ INTEGER(ACCUM_ALPHA_BITS);
+
+ // Core OpenGL 2.0 (treated as extensions):
+
+ if (ogl_HaveExtension("GL_EXT_texture_lod_bias"))
+ FLOAT(MAX_TEXTURE_LOD_BIAS_EXT);
+
+ // Skip GL_ARB_occlusion_query's QUERY_COUNTER_BITS_ARB since it'd need GetQueryiv
+
+ if (ogl_HaveExtension("GL_ARB_shading_language_100"))
+ STRING(SHADING_LANGUAGE_VERSION_ARB);
+
+ if (ogl_HaveExtension("GL_ARB_vertex_shader"))
+ {
+ INTEGER(MAX_VERTEX_ATTRIBS_ARB);
+ INTEGER(MAX_VERTEX_UNIFORM_COMPONENTS_ARB);
+ INTEGER(MAX_VARYING_FLOATS_ARB);
+ INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB);
+ INTEGER(MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB);
+ }
+
+ if (ogl_HaveExtension("GL_ARB_fragment_shader"))
+ INTEGER(MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB);
+
+ if (ogl_HaveExtension("GL_ARB_vertex_shader") || ogl_HaveExtension("GL_ARB_fragment_shader"))
+ {
+ INTEGER(MAX_TEXTURE_IMAGE_UNITS_ARB);
+ INTEGER(MAX_TEXTURE_COORDS_ARB);
+ }
+
+ if (ogl_HaveExtension("GL_ARB_draw_buffers"))
+ INTEGER(MAX_DRAW_BUFFERS_ARB);
+
+ // Other interesting extensions:
+
+ if (ogl_HaveExtension("GL_EXT_framebuffer_object"))
+ {
+ INTEGER(MAX_COLOR_ATTACHMENTS_EXT);
+ INTEGER(MAX_RENDERBUFFER_SIZE_EXT);
+ }
+
+ if (ogl_HaveExtension("GL_EXT_texture_filter_anisotropic"))
+ FLOAT(MAX_TEXTURE_MAX_ANISOTROPY_EXT);
+}
Index: ps/trunk/source/ps/GameSetup/GameSetup.cpp
===================================================================
--- ps/trunk/source/ps/GameSetup/GameSetup.cpp (revision 8924)
+++ ps/trunk/source/ps/GameSetup/GameSetup.cpp (revision 8925)
@@ -1,1053 +1,1061 @@
/* Copyright (C) 2011 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/input.h"
#include "lib/lockfree.h"
#include "lib/ogl.h"
#include "lib/timer.h"
#include "lib/utf8.h"
#include "lib/external_libraries/sdl.h"
#include "lib/file/common/file_stats.h"
#include "lib/res/h_mgr.h"
#include "lib/res/graphics/cursor.h"
#include "lib/res/sound/snd_mgr.h"
#include "lib/sysdep/cursor.h"
#include "lib/sysdep/cpu.h"
#include "lib/sysdep/gfx.h"
#include "lib/tex/tex.h"
#if OS_WIN
#include "lib/sysdep/os/win/wversion.h"
#endif
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/Filesystem.h"
#include "ps/Font.h"
#include "ps/Game.h"
#include "ps/Globals.h"
#include "ps/Hotkey.h"
#include "ps/Joystick.h"
#include "ps/Loader.h"
#include "ps/Overlay.h"
#include "ps/Profile.h"
#include "ps/ProfileViewer.h"
+#include "ps/UserReport.h"
#include "ps/Util.h"
#include "ps/VideoMode.h"
#include "ps/World.h"
#include "graphics/CinemaTrack.h"
#include "graphics/GameView.h"
#include "graphics/LightEnv.h"
#include "graphics/MapReader.h"
#include "graphics/MaterialManager.h"
#include "graphics/ParticleEngine.h"
#include "graphics/TerrainTextureManager.h"
#include "renderer/Renderer.h"
#include "renderer/VertexBufferManager.h"
#include "maths/MathUtil.h"
#include "simulation2/Simulation2.h"
#include "scripting/ScriptingHost.h"
#include "scripting/ScriptGlue.h"
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/ScriptStats.h"
#include "maths/scripting/JSInterface_Vector3D.h"
#include "ps/scripting/JSInterface_Console.h"
#include "gui/GUI.h"
#include "gui/GUIManager.h"
#include "gui/scripting/JSInterface_IGUIObject.h"
#include "gui/scripting/JSInterface_GUITypes.h"
#include "gui/scripting/ScriptFunctions.h"
#include "sound/JSI_Sound.h"
#include "network/NetServer.h"
#include "network/NetClient.h"
#include "ps/Pyrogenesis.h" // psSetLogDir
#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"
#if !(OS_WIN || OS_MACOSX) // assume all other platforms use X11 for wxWidgets
#define MUST_INIT_X11 1
#include
#else
#define MUST_INIT_X11 0
#endif
#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;
static const int SANE_TEX_QUALITY_DEFAULT = 5; // keep in sync with code
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_ScriptingHost.GetScriptInterface().SetGlobal("g_Progress", percent, true);
g_ScriptingHost.GetScriptInterface().SetGlobal("g_LoadDescription", pending_task, true);
g_GUI->SendEventToAll("progress");
}
void Render()
{
ogl_WarnIfError();
CStr skystring = "255 0 255";
CFG_GET_USER_VAL("skycolor", String, skystring);
CColor skycol;
GUI::ParseString(skystring, skycol);
g_Renderer.SetClearColor(skycol.AsSColor4ub());
// start new frame
g_Renderer.BeginFrame();
ogl_WarnIfError();
if (g_Game && g_Game->IsGameStarted())
{
g_Game->GetView()->Render();
}
ogl_WarnIfError();
// set up overlay mode
glPushAttrib(GL_ENABLE_BIT);
glEnable(GL_TEXTURE_2D);
glDisable(GL_CULL_FACE);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
ogl_WarnIfError();
g_Renderer.RenderTextOverlays();
// Temp GUI message GeeTODO
PROFILE_START("render gui");
if(g_DoRenderGui) g_GUI->Draw();
PROFILE_END("render gui");
ogl_WarnIfError();
// Particle Engine Updating
CParticleEngine::GetInstance()->UpdateEmitters();
ogl_WarnIfError();
// Text:
// Use the GL_ALPHA texture as the alpha channel with a flat colouring
glDisable(GL_ALPHA_TEST);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
// Added --
glEnable(GL_TEXTURE_2D);
// -- GL
glLoadIdentity();
PROFILE_START("render console");
g_Console->Render();
PROFILE_END("render console");
ogl_WarnIfError();
PROFILE_START("render logger");
if(g_DoRenderLogger) g_Logger->Render();
PROFILE_END("render logger");
ogl_WarnIfError();
// Profile information
PROFILE_START("render profiling");
g_ProfileViewer.RenderProfile();
PROFILE_END("render profiling");
ogl_WarnIfError();
// Draw the cursor (or set the Windows cursor, on Windows)
if (g_DoRenderCursor)
{
CStrW cursorName = g_CursorName;
if (cursorName.empty())
{
cursor_draw(g_VFS, NULL, g_mouse_x, g_yres-g_mouse_y);
}
else
{
if (cursor_draw(g_VFS, cursorName.c_str(), g_mouse_x, g_yres-g_mouse_y) < 0)
LOGWARNING(L"Failed to draw cursor '%ls'", cursorName.c_str());
}
}
// restore
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glPopAttrib();
g_Renderer.EndFrame();
ogl_WarnIfError();
}
static void RegisterJavascriptInterfaces()
{
// maths
JSI_Vector3D::init();
// graphics
CGameView::ScriptingInit();
// renderer
CRenderer::ScriptingInit();
// sound
JSI_Sound::ScriptingInit();
// ps
JSI_Console::init();
// GUI
CGUI::ScriptingInit();
GuiScriptingInit(g_ScriptingHost.GetScriptInterface());
}
static void InitScripting()
{
TIMER(L"InitScripting");
// Create the scripting host. This needs to be done before the GUI is created.
// [7ms]
new ScriptingHost;
RegisterJavascriptInterfaces();
}
#if 0
// disabled because the file cache doesn't work (http://trac.wildfiregames.com/ticket/611)
static size_t OperatingSystemFootprint()
{
#if OS_WIN
switch(wversion_Number())
{
case WVERSION_2K:
case WVERSION_XP:
return 150;
case WVERSION_XP64:
return 200;
default: // don't warn about newer Windows versions
case WVERSION_VISTA:
return 300;
case WVERSION_7:
return 250;
}
#else
return 200;
#endif
}
static size_t ChooseCacheSize()
{
// (all sizes in MiB)
const size_t total = os_cpu_MemorySize();
const size_t available = os_cpu_MemoryAvailable();
debug_assert(total >= available);
const size_t inUse = total-available;
size_t os = OperatingSystemFootprint();
debug_assert(total >= os/2);
size_t apps = (inUse > os)? (inUse - os) : 0;
size_t game = 300;
size_t cache = 200;
// plenty of memory
if(os + apps + game + cache < total)
{
cache = total - os - apps - game;
}
else // below min-spec
{
// assume kernel+other apps will be swapped out
os = 9*os/10;
apps = 2*apps/3;
game = 3*game/4;
if(os + apps + game + cache < total)
cache = total - os - apps - game;
else
{
cache = 50;
debug_printf(L"Warning: memory size (%d MiB, %d used) is rather low\n", total, available);
}
}
// total data is currently about 500 MiB, and not all of it
// is used at once, so don't use more than that.
// (this also ensures the byte count will fit in size_t and
// avoids using up too much address space)
cache = std::min(cache, (size_t)500);
debug_printf(L"Cache: %d (total: %d; available: %d)\n", cache, total, available);
return cache*MiB;
}
#else
static size_t ChooseCacheSize()
{
return 32*MiB;
}
#endif
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;
}
static void InitVfs(const CmdLineArgs& args)
{
TIMER(L"InitVfs");
const Paths paths(args);
fs::wpath 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;
hooks.display_error = psDisplayError;
app_hooks_update(&hooks);
const size_t cacheSize = ChooseCacheSize();
g_VFS = CreateVfs(cacheSize);
g_VFS->Mount(L"screenshots/", paths.Data()/L"screenshots/");
const fs::wpath readonlyConfig = paths.RData()/L"config/";
g_VFS->Mount(L"config/", readonlyConfig);
if(readonlyConfig != paths.Config())
g_VFS->Mount(L"config/", paths.Config());
g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE); // (adding XMBs to archive speeds up subsequent reads)
std::vector mods = args.GetMultiple("mod");
mods.push_back("public");
if(!args.Has("onlyPublicFiles"))
mods.push_back("internal");
fs::wpath modArchivePath(paths.Cache()/L"mods");
fs::wpath modLoosePath(paths.RData()/L"mods");
for (size_t i = 0; i < mods.size(); ++i)
{
size_t priority = i;
size_t flags = VFS_MOUNT_WATCH|VFS_MOUNT_ARCHIVABLE|VFS_MOUNT_MUST_EXIST;
std::wstring modName (wstring_from_utf8(mods[i]));
g_VFS->Mount(L"", AddSlash(modLoosePath/modName), flags, priority);
g_VFS->Mount(L"", AddSlash(modArchivePath/modName), flags, priority);
}
// 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, CScriptVal initData)
{
{
// console
TIMER(L"ps_console");
g_Console->UpdateScreenSize(g_xres, g_yres);
// Calculate and store the line spacing
CFont font(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;
}
// 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", initData);
return;
}
// GUI uses VFS, so this must come after VFS init.
g_GUI->SwitchPage(gui_page, initData);
// Warn nicely about missing S3TC support
if (!ogl_tex_has_s3tc())
{
#if !(OS_WIN || OS_MACOSX)
bool isMesa = true;
#else
bool isMesa = false;
#endif
g_GUI->GetScriptInterface().CallFunctionVoid(OBJECT_TO_JSVAL(g_GUI->GetScriptObject()), "s3tcWarning", isMesa);
}
}
static void InitInput()
{
SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
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(conInputHandler);
in_add_handler(CProfileViewer::InputThunk);
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);
// 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);
}
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_NOFRAMEBUFFEROBJECT,g_NoGLFramebufferObject);
g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWS,g_Shadows);
g_Renderer.SetOptionBool(CRenderer::OPT_FANCYWATER,g_FancyWater);
g_Renderer.SetRenderPath(CRenderer::GetRenderPathByName(g_RenderPath));
g_Renderer.SetOptionFloat(CRenderer::OPT_LODBIAS, g_LodBias);
// create terrain related stuff
new CTerrainTextureManager;
// create the material manager
new CMaterialManager;
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();
}
static void InitSDL()
{
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);
SDL_EnableUNICODE(1);
}
static void ShutdownSDL()
{
SDL_Quit();
sys_cursor_reset();
}
void EndGame()
{
SAFE_DELETE(g_NetServer);
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_Game);
}
void Shutdown(int UNUSED(flags))
{
EndGame();
ShutdownPs(); // Must delete g_GUI before g_ScriptingHost
in_reset_handlers();
// destroy actor related stuff
TIMER_BEGIN(L"shutdown actor stuff");
delete &g_MaterialManager;
TIMER_END(L"shutdown actor stuff");
// destroy terrain related stuff
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");
tex_codec_unregister_all();
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");
+
TIMER_BEGIN(L"shutdown ScriptingHost");
delete &g_ScriptingHost;
TIMER_END(L"shutdown ScriptingHost");
TIMER_BEGIN(L"shutdown ConfigDB");
delete &g_ConfigDB;
TIMER_END(L"shutdown ConfigDB");
// resource
// first shut down all resource owners, and then the handle manager.
TIMER_BEGIN(L"resource modules");
snd_shutdown();
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_UNIX
void SetDefaultIfLocaleInvalid()
{
// On misconfigured systems with incorrect locale settings, we'll die
// with a C++ exception when some code 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
void SetDefaultIfLocaleInvalid()
{
// 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");
debug_filter_add(L"HRT");
SetDefaultIfLocaleInvalid();
cpu_ConfigureFloatingPoint();
timer_LatchStartTime();
// 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!
}
static bool Autostart(const CmdLineArgs& args);
void Init(const CmdLineArgs& args, int UNUSED(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);
// This must come after VFS init, which sets the current directory
// (required for finding our output log files).
g_Logger = new CLogger;
// 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, 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);
InitScripting(); // before GUI
// g_ConfigDB, command line args, globals
CONFIG_Init(args);
+
+ if (!g_Quickstart)
+ g_UserReporter.Initialize(); // after config
}
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
SDL_WM_SetCaption("0 A.D.", "0 A.D.");
}
// needed by ogl_tex to detect broken gfx card/driver combos,
// but takes a while due to WMI startup, so make it optional.
if(!g_Quickstart)
gfx_detect();
RunHardwareDetection();
tex_codec_register_all();
const int quality = SANE_TEX_QUALITY_DEFAULT; // TODO: set value from config file
SetTextureQuality(quality);
ogl_WarnIfError();
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)
{
// speed up startup by disabling all sound
// (OpenAL init will be skipped).
// must be called before first snd_open.
snd_disable(true);
}
g_GUI = new CGUIManager(g_ScriptingHost.GetScriptInterface());
// (must come after SetVideoMode, since it calls ogl_Init)
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();
if (!Autostart(args))
{
const bool setup_gui = ((flags & INIT_NO_GUI) == 0);
InitPs(setup_gui, L"page_pregame.xml", JSVAL_VOID);
}
}
void RenderGui(bool RenderingState)
{
g_DoRenderGui = RenderingState;
}
void RenderLogger(bool RenderingState)
{
g_DoRenderLogger = RenderingState;
}
void RenderCursor(bool RenderingState)
{
g_DoRenderCursor = RenderingState;
}
static bool Autostart(const CmdLineArgs& args)
{
/*
* Handle various command-line options, for quick testing of various features:
* -autostart=mapname -- single-player
* -autostart=mapname -autostart-playername=Player -autostart-host -autostart-players=2 -- multiplayer host, wait for 2 players
* -autostart=mapname -autostart-playername=Player -autostart-client -autostart-ip=127.0.0.1 -- multiplayer client, connect to 127.0.0.1
*/
CStr autostartMap = args.Get("autostart");
if (autostartMap.empty())
return false;
g_Game = new CGame();
ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
CScriptValRooted attrs;
scriptInterface.Eval("({})", attrs);
scriptInterface.SetProperty(attrs.get(), "mapType", std::string("scenario"), false);
scriptInterface.SetProperty(attrs.get(), "map", std::string(autostartMap), false);
// Set attrs.settings = { AIs: [...] }:
/*
* Handle command-line options for AI:
* -autostart-ai=1:dummybot -autostart-ai=2:dummybot -- adds the dummybot AI to players 1 and 2
*/
CScriptValRooted ais;
scriptInterface.Eval("([])", ais);
if (args.Has("autostart-ai"))
{
std::vector aiArgs = args.GetMultiple("autostart-ai");
for (size_t i = 0; i < aiArgs.size(); ++i)
{
int player = aiArgs[i].BeforeFirst(":").ToInt();
CStr name = aiArgs[i].AfterFirst(":");
scriptInterface.SetPropertyInt(ais.get(), player, std::string(name), false);
}
}
CScriptValRooted settings;
scriptInterface.Eval("({})", settings);
scriptInterface.SetProperty(settings.get(), "AIs", ais, false);
scriptInterface.SetProperty(attrs.get(), "settings", settings, false);
CScriptValRooted mpInitData;
g_GUI->GetScriptInterface().Eval("({isNetworked:true, playerAssignments:{}})", mpInitData);
g_GUI->GetScriptInterface().SetProperty(mpInitData.get(), "attribs",
CScriptVal(g_GUI->GetScriptInterface().CloneValueFromOtherContext(scriptInterface, attrs.get())), false);
if (args.Has("autostart-host"))
{
InitPs(true, L"page_loading.xml", mpInitData.get());
size_t maxPlayers = 2;
if (args.Has("autostart-players"))
maxPlayers = args.Get("autostart-players").ToUInt();
g_NetServer = new CNetServer(maxPlayers);
g_NetServer->UpdateGameAttributes(attrs.get(), scriptInterface);
bool ok = g_NetServer->SetupConnection();
debug_assert(ok);
g_NetClient = new CNetClient(g_Game);
// TODO: player name, etc
g_NetClient->SetupConnection("127.0.0.1");
}
else if (args.Has("autostart-client"))
{
InitPs(true, L"page_loading.xml", mpInitData.get());
g_NetClient = new CNetClient(g_Game);
// TODO: player name, etc
CStr ip = "127.0.0.1";
if (args.Has("autostart-ip"))
ip = args.Get("autostart-ip");
bool ok = g_NetClient->SetupConnection(ip);
debug_assert(ok);
}
else
{
g_Game->SetPlayerID(1);
g_Game->StartGame(attrs);
LDR_NonprogressiveLoad();
PSRETURN ret = g_Game->ReallyStartGame();
debug_assert(ret == PSRETURN_OK);
InitPs(true, L"page_session.xml", JSVAL_VOID);
}
return true;
}
Index: ps/trunk/source/ps/UserReport.cpp
===================================================================
--- ps/trunk/source/ps/UserReport.cpp (nonexistent)
+++ ps/trunk/source/ps/UserReport.cpp (revision 8925)
@@ -0,0 +1,601 @@
+/* Copyright (C) 2011 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 "UserReport.h"
+
+#include "lib/timer.h"
+#include "lib/external_libraries/curl.h"
+#include "lib/external_libraries/sdl.h"
+#include "lib/external_libraries/zlib.h"
+#include "lib/file/archive/stream.h"
+#include "lib/sysdep/sysdep.h"
+#include "ps/ConfigDB.h"
+#include "ps/Filesystem.h"
+#include "ps/ThreadUtil.h"
+
+#define DEBUG_UPLOADS 0
+
+/*
+ * The basic idea is that the game submits reports to us, which we send over
+ * HTTP to a server for storage and analysis.
+ *
+ * We can't use libcurl's asynchronous 'multi' API, because DNS resolution can
+ * be synchronous and slow (which would make the game pause).
+ * So we use the 'easy' API in a background thread.
+ * The main thread submits reports, toggles whether uploading is enabled,
+ * and polls for the current status (typically to display in the GUI);
+ * the worker thread does all of the uploading.
+ *
+ * It'd be nice to extend this in the future to handle things like crash reports.
+ * The game should store the crashlogs (suitably anonymised) in a directory, and
+ * we should detect those files and upload them when we're restarted and online.
+ */
+
+
+/**
+ * Version number stored in config file when the user agrees to the reporting.
+ * Reporting will be disabled if the config value is missing or is less than
+ * this value. If we start reporting a lot more data, we should increase this
+ * value and get the user to re-confirm.
+ */
+static const int REPORTER_VERSION = 1;
+
+/**
+ * Time interval (seconds) at which the worker thread will check its reconnection
+ * timers. (This should be relatively high so the thread doesn't waste much time
+ * continually waking up.)
+ */
+static const double TIMER_CHECK_INTERVAL = 10.0;
+
+/**
+ * Seconds we should wait before reconnecting to the server after a failure.
+ */
+static const double RECONNECT_INVERVAL = 60.0;
+
+CUserReporter g_UserReporter;
+
+struct CUserReport
+{
+ time_t m_Time;
+ std::string m_Type;
+ int m_Version;
+ std::string m_Data;
+};
+
+class CUserReporterWorker
+{
+public:
+ CUserReporterWorker(const std::string& userID, const std::string& url) :
+ m_UserID(userID), m_Enabled(false), m_Shutdown(false), m_Status("disabled"),
+ m_PauseUntilTime(timer_Time()), m_LastUpdateTime(timer_Time())
+ {
+ // Set up libcurl:
+
+ m_Curl = curl_easy_init();
+ debug_assert(m_Curl);
+
+#if DEBUG_UPLOADS
+ curl_easy_setopt(m_Curl, CURLOPT_VERBOSE, 1L);
+#endif
+
+ // Capture error messages
+ curl_easy_setopt(m_Curl, CURLOPT_ERRORBUFFER, m_ErrorBuffer);
+
+ // Disable signal handlers (required for multithreaded applications)
+ curl_easy_setopt(m_Curl, CURLOPT_NOSIGNAL, 1L);
+
+ // To minimise security risks, don't support redirects
+ curl_easy_setopt(m_Curl, CURLOPT_FOLLOWLOCATION, 0L);
+
+ // Set IO callbacks
+ curl_easy_setopt(m_Curl, CURLOPT_WRITEFUNCTION, ReceiveCallback);
+ curl_easy_setopt(m_Curl, CURLOPT_WRITEDATA, this);
+ curl_easy_setopt(m_Curl, CURLOPT_READFUNCTION, SendCallback);
+ curl_easy_setopt(m_Curl, CURLOPT_READDATA, this);
+
+ // Set URL to POST to
+ curl_easy_setopt(m_Curl, CURLOPT_URL, url.c_str());
+ curl_easy_setopt(m_Curl, CURLOPT_POST, 1L);
+
+ // Set up HTTP headers
+ m_Headers = NULL;
+ // Set the UA string
+ std::string ua = "User-Agent: 0ad ";
+ ua += curl_version();
+ ua += " (http://wildfiregames.com/0ad/)";
+ m_Headers = curl_slist_append(m_Headers, ua.c_str());
+ // Override the default application/x-www-form-urlencoded type since we're not using that type
+ m_Headers = curl_slist_append(m_Headers, "Content-Type: application/octet-stream");
+ // Disable the Accept header because it's a waste of a dozen bytes
+ m_Headers = curl_slist_append(m_Headers, "Accept: ");
+ curl_easy_setopt(m_Curl, CURLOPT_HTTPHEADER, m_Headers);
+
+
+ // Set up the worker thread:
+
+ // Use SDL semaphores since OS X doesn't implement sem_init
+ m_WorkerSem = SDL_CreateSemaphore(0);
+ debug_assert(m_WorkerSem);
+
+ int ret = pthread_create(&m_WorkerThread, NULL, &RunThread, this);
+ debug_assert(ret == 0);
+ }
+
+ ~CUserReporterWorker()
+ {
+ // Clean up resources
+
+ SDL_DestroySemaphore(m_WorkerSem);
+
+ curl_slist_free_all(m_Headers);
+ curl_easy_cleanup(m_Curl);
+ }
+
+ /**
+ * Called by main thread, when the online reporting is enabled/disabled.
+ */
+ void SetEnabled(bool enabled)
+ {
+ CScopeLock lock(m_WorkerMutex);
+ if (enabled != m_Enabled)
+ {
+ m_Enabled = enabled;
+
+ // Wake up the worker thread
+ SDL_SemPost(m_WorkerSem);
+ }
+ }
+
+ /**
+ * Called by main thread to request shutdown.
+ * Returns true if we've shut down successfully.
+ * Returns false if shutdown is taking too long (we might be blocked on a
+ * sync network operation) - you mustn't destroy this object, just leak it
+ * and terminate.
+ */
+ bool Shutdown()
+ {
+ {
+ CScopeLock lock(m_WorkerMutex);
+ m_Shutdown = true;
+ }
+
+ // Wake up the worker thread
+ SDL_SemPost(m_WorkerSem);
+
+ // Wait for it to shut down cleanly
+ // TODO: should have a timeout in case of network hangs
+ pthread_join(m_WorkerThread, NULL);
+
+ return true;
+ }
+
+ /**
+ * Called by main thread to determine the current status of the uploader.
+ */
+ std::string GetStatus()
+ {
+ CScopeLock lock(m_WorkerMutex);
+ return m_Status;
+ }
+
+ /**
+ * Called by main thread to add a new report to the queue.
+ */
+ void Submit(const shared_ptr& report)
+ {
+ {
+ CScopeLock lock(m_WorkerMutex);
+ m_ReportQueue.push_back(report);
+ }
+
+ // Wake up the worker thread
+ SDL_SemPost(m_WorkerSem);
+ }
+
+ /**
+ * Called by the main thread every frame, so we can check
+ * retransmission timers.
+ */
+ void Update()
+ {
+ double now = timer_Time();
+ if (now > m_LastUpdateTime + TIMER_CHECK_INTERVAL)
+ {
+ // Wake up the worker thread
+ SDL_SemPost(m_WorkerSem);
+
+ m_LastUpdateTime = now;
+ }
+ }
+
+private:
+ static void* RunThread(void* data)
+ {
+ debug_SetThreadName("CUserReportWorker");
+
+ static_cast(data)->Run();
+
+ return NULL;
+ }
+
+ void Run()
+ {
+ /*
+ * We use a semaphore to let the thread be woken up when it has
+ * work to do. Various actions from the main thread can wake it:
+ * * SetEnabled()
+ * * Shutdown()
+ * * Submit()
+ * * Retransmission timeouts, once every several seconds
+ *
+ * If multiple actions have triggered wakeups, we might respond to
+ * all of those actions after the first wakeup, which is okay (we'll do
+ * nothing during the subsequent wakeups). We should never hang due to
+ * processing fewer actions than wakeups.
+ *
+ * Retransmission timeouts are triggered via the main thread - we can't simply
+ * use SDL_SemWaitTimeout because on Linux it's implemented as an inefficient
+ * busy-wait loop, and we can't use a manual busy-wait with a long delay time
+ * because we'd lose responsiveness. So the main thread pings the worker
+ * occasionally so it can check its timer.
+ */
+
+ // Wait until the main thread wakes us up
+ while (SDL_SemWait(m_WorkerSem) == 0)
+ {
+ // Handle shutdown requests as soon as possible
+ if (GetShutdown())
+ return;
+
+ // If we're not enabled, ignore this wakeup
+ if (!GetEnabled())
+ continue;
+
+ // If we're still pausing due to a failed connection,
+ // go back to sleep again
+ if (timer_Time() < m_PauseUntilTime)
+ continue;
+
+ // We're enabled, so process as many reports as possible
+ while (ProcessReport())
+ {
+ // Handle shutdowns while we were sending the report
+ if (GetShutdown())
+ return;
+ }
+ }
+ }
+
+ bool GetEnabled()
+ {
+ CScopeLock lock(m_WorkerMutex);
+ return m_Enabled;
+ }
+
+ bool GetShutdown()
+ {
+ CScopeLock lock(m_WorkerMutex);
+ return m_Shutdown;
+ }
+
+ void SetStatus(const std::string& status)
+ {
+ CScopeLock lock(m_WorkerMutex);
+ m_Status = status;
+#if DEBUG_UPLOADS
+ debug_printf(L">>> CUserReporterWorker status: %hs\n", status.c_str());
+#endif
+ }
+
+ bool ProcessReport()
+ {
+ shared_ptr report;
+
+ {
+ CScopeLock lock(m_WorkerMutex);
+ if (m_ReportQueue.empty())
+ return false;
+ report = m_ReportQueue.front();
+ m_ReportQueue.pop_front();
+ }
+
+ ConstructRequestData(*report);
+ m_RequestDataOffset = 0;
+ m_ResponseData.clear();
+
+ curl_easy_setopt(m_Curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)m_RequestData.size());
+
+ SetStatus("connecting");
+
+#if DEBUG_UPLOADS
+ TIMER(L"CUserReporterWorker request");
+#endif
+
+ CURLcode err = curl_easy_perform(m_Curl);
+
+#if DEBUG_UPLOADS
+ printf(">>>\n%s\n<<<\n", m_ResponseData.c_str());
+#endif
+
+ if (err == CURLE_OK)
+ {
+ long code = -1;
+ curl_easy_getinfo(m_Curl, CURLINFO_RESPONSE_CODE, &code);
+ SetStatus("completed:" + CStr(code));
+
+ // Check for success code
+ if (code == 200)
+ return true;
+
+ // If the server returns the 410 Gone status, interpret that as meaning
+ // it no longer supports uploads (at least from this version of the game),
+ // so shut down and stop talking to it (to avoid wasting bandwidth)
+ if (code == 410)
+ {
+ CScopeLock lock(m_WorkerMutex);
+ m_Shutdown = true;
+ return false;
+ }
+ }
+ else
+ {
+ SetStatus("failed:" + CStr(err) + ":" + m_ErrorBuffer);
+ }
+
+ // We got an unhandled return code or a connection failure;
+ // push this report back onto the queue and try again after
+ // a long interval
+
+ {
+ CScopeLock lock(m_WorkerMutex);
+ m_ReportQueue.push_front(report);
+ }
+
+ m_PauseUntilTime = timer_Time() + RECONNECT_INVERVAL;
+ return false;
+ }
+
+ void ConstructRequestData(const CUserReport& report)
+ {
+ // Construct the POST request data in the application/x-www-form-urlencoded format
+
+ std::string r;
+
+ r += "user_id=";
+ AppendEscaped(r, m_UserID);
+
+ r += "&time=" + CStr(report.m_Time);
+
+ r += "&type=";
+ AppendEscaped(r, report.m_Type);
+
+ r += "&version=" + CStr(report.m_Version);
+
+ r += "&data=";
+ AppendEscaped(r, report.m_Data);
+
+ // Compress the content with zlib to save bandwidth.
+ // (Note that we send a request with unlabelled compressed data instead
+ // of using Content-Encoding, because Content-Encoding is a mess and causes
+ // problems with servers and breaks Content-Length and this is much easier.)
+ std::string compressed;
+ compressed.resize(compressBound(r.size()));
+ uLongf destLen = compressed.size();
+ int ok = compress((Bytef*)compressed.c_str(), &destLen, (const Bytef*)r.c_str(), r.size());
+ debug_assert(ok == Z_OK);
+ compressed.resize(destLen);
+
+ m_RequestData.swap(compressed);
+ }
+
+ void AppendEscaped(std::string& buffer, const std::string& str)
+ {
+ char* escaped = curl_easy_escape(m_Curl, str.c_str(), str.size());
+ buffer += escaped;
+ curl_free(escaped);
+ }
+
+ static size_t ReceiveCallback(void* buffer, size_t size, size_t nmemb, void* userp)
+ {
+ CUserReporterWorker* self = static_cast(userp);
+
+ if (self->GetShutdown())
+ return 0; // signals an error
+
+ self->m_ResponseData += std::string((char*)buffer, (char*)buffer+size*nmemb);
+
+ return size*nmemb;
+ }
+
+ static size_t SendCallback(char* bufptr, size_t size, size_t nmemb, void* userp)
+ {
+ CUserReporterWorker* self = static_cast(userp);
+
+ if (self->GetShutdown())
+ return CURL_READFUNC_ABORT; // signals an error
+
+ // We can return as much data as available, up to the buffer size
+ size_t amount = std::min(self->m_RequestData.size() - self->m_RequestDataOffset, size*nmemb);
+
+ // ...But restrict to sending a small amount at once, so that we remain
+ // responsive to shutdown requests even if the network is pretty slow
+ amount = std::min((size_t)1024, amount);
+
+ memcpy(bufptr, &self->m_RequestData[self->m_RequestDataOffset], amount);
+ self->m_RequestDataOffset += amount;
+
+ self->SetStatus("sending:" + CStr((float)self->m_RequestDataOffset / self->m_RequestData.size()));
+
+ return amount;
+ }
+
+private:
+ // Thread-related members:
+ pthread_t m_WorkerThread;
+ CMutex m_WorkerMutex;
+ SDL_sem* m_WorkerSem;
+
+ // Shared by main thread and worker thread:
+ // These variables are all protected by m_WorkerMutex
+ std::deque > m_ReportQueue;
+ bool m_Enabled;
+ bool m_Shutdown;
+ std::string m_Status;
+
+ // Initialised in constructor by main thread; otherwise used only by worker thread:
+ std::string m_UserID;
+ CURL* m_Curl;
+ curl_slist* m_Headers;
+ double m_PauseUntilTime;
+
+ // Only used by worker thread:
+ std::string m_ResponseData;
+ std::string m_RequestData;
+ size_t m_RequestDataOffset;
+ char m_ErrorBuffer[CURL_ERROR_SIZE];
+
+ // Only used by main thread:
+ double m_LastUpdateTime;
+};
+
+
+
+CUserReporter::CUserReporter() :
+ m_Worker(NULL)
+{
+}
+
+CUserReporter::~CUserReporter()
+{
+ debug_assert(!m_Worker); // Deinitialize should have been called before shutdown
+}
+
+std::string CUserReporter::LoadUserID()
+{
+ std::string userID;
+
+ // Read the user ID from user.cfg (if there is one)
+ CFG_GET_USER_VAL("userreport.id", String, userID);
+
+ // If we don't have a validly-formatted user ID, generate a new one
+ if (userID.length() != 16)
+ {
+ u8 bytes[8] = {0};
+ sys_generate_random_bytes(bytes, ARRAY_SIZE(bytes));
+ // ignore failures - there's not much we can do about it
+
+ userID = "";
+ for (size_t i = 0; i < ARRAY_SIZE(bytes); ++i)
+ {
+ char hex[3];
+ sprintf_s(hex, ARRAY_SIZE(hex), "%02x", bytes[i]);
+ userID += hex;
+ }
+
+ g_ConfigDB.CreateValue(CFG_USER, "userreport.id")->m_String = userID;
+ g_ConfigDB.WriteFile(CFG_USER);
+ }
+
+ return userID;
+}
+
+bool CUserReporter::IsReportingEnabled()
+{
+ int version = -1;
+ CFG_GET_USER_VAL("userreport.enabledversion", Int, version);
+ return (version >= REPORTER_VERSION);
+}
+
+void CUserReporter::SetReportingEnabled(bool enabled)
+{
+ CStr val(enabled ? REPORTER_VERSION : 0);
+ g_ConfigDB.CreateValue(CFG_USER, "userreport.enabledversion")->m_String = val;
+ g_ConfigDB.WriteFile(CFG_USER);
+
+ if (m_Worker)
+ m_Worker->SetEnabled(enabled);
+}
+
+std::string CUserReporter::GetStatus()
+{
+ if (!m_Worker)
+ return "disabled";
+
+ return m_Worker->GetStatus();
+}
+
+
+void CUserReporter::Initialize()
+{
+ debug_assert(!m_Worker); // must only be called once
+
+ std::string userID = LoadUserID();
+ std::string url;
+ CFG_GET_SYS_VAL("userreport.url", String, url);
+
+ // Initialise everything except Win32 sockets (because our networking
+ // system already inits those)
+ curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_WIN32);
+
+ m_Worker = new CUserReporterWorker(userID, url);
+
+ m_Worker->SetEnabled(IsReportingEnabled());
+}
+
+void CUserReporter::Deinitialize()
+{
+ if (!m_Worker)
+ return;
+
+ if (m_Worker->Shutdown())
+ {
+ // Worker was shut down cleanly
+
+ SAFE_DELETE(m_Worker);
+ curl_global_cleanup();
+ }
+ else
+ {
+ // Worker failed to shut down in a reasonable time
+ // Leak the resources (since that's better than hanging or crashing)
+ m_Worker = NULL;
+ }
+}
+
+void CUserReporter::Update()
+{
+ if (m_Worker)
+ m_Worker->Update();
+}
+
+void CUserReporter::SubmitReport(const char* type, int version, const std::string& data)
+{
+ // If not initialised, discard the report
+ if (!m_Worker)
+ return;
+
+ shared_ptr report(new CUserReport);
+ report->m_Time = time(NULL);
+ report->m_Type = type;
+ report->m_Version = version;
+ report->m_Data = data;
+
+ m_Worker->Submit(report);
+}
Property changes on: ps/trunk/source/ps/UserReport.cpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/source/ps/UserReport.h
===================================================================
--- ps/trunk/source/ps/UserReport.h (nonexistent)
+++ ps/trunk/source/ps/UserReport.h (revision 8925)
@@ -0,0 +1,62 @@
+/* Copyright (C) 2011 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_USERREPORT
+#define INCLUDED_USERREPORT
+
+class CUserReporterWorker;
+
+class CUserReporter
+{
+public:
+ CUserReporter();
+ ~CUserReporter();
+
+ void Initialize();
+ void Deinitialize();
+
+ /**
+ * Must be called frequently (preferably every frame), to update some
+ * internal reconnection timers.
+ */
+ void Update();
+
+ // Functions for the GUI to control the reporting:
+ bool IsReportingEnabled();
+ void SetReportingEnabled(bool enabled);
+ std::string GetStatus();
+
+ /**
+ * Submit a report to be transmitted to the online server.
+ * Nothing will be transmitted until reporting is enabled by the user, so
+ * you don't need to check for that first.
+ * @param type short string identifying the type of data ("hwdetect", "message", etc)
+ * @param version positive integer that should be incremented if the data is changed in
+ * a non-compatible way and the server will have to distinguish old and new formats
+ * @param data the actual data (typically UTF-8-encoded text, or JSON, but could be binary)
+ */
+ void SubmitReport(const char* type, int version, const std::string& data);
+
+private:
+ std::string LoadUserID();
+
+ CUserReporterWorker* m_Worker;
+};
+
+extern CUserReporter g_UserReporter;
+
+#endif // INCLUDED_USERREPORT
Property changes on: ps/trunk/source/ps/UserReport.h
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/source/ps/ConfigDB.h
===================================================================
--- ps/trunk/source/ps/ConfigDB.h (revision 8924)
+++ ps/trunk/source/ps/ConfigDB.h (revision 8925)
@@ -1,171 +1,179 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2011 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 .
*/
/*
CConfigDB - Load, access and store configuration variables
TDD : http://forums.wildfiregames.com/0ad/index.php?showtopic=1125
OVERVIEW:
JavaScript:
All the javascript interfaces are provided through the global object
g_ConfigDB.
g_ConfigDB Properties:
system:
All CFG_SYSTEM values are linked to properties of this object.
a=g_ConfigDB.system.foo; is equivalent to C++ code
g_ConfigDB.GetValue(CFG_SYSTEM, "foo");
mod: Ditto, but linked to CFG_MOD
user: Ditto, but linked to CFG_USER
default: Ditto, but linked to CFG_DEFAULT
g_ConfigDB Functions: None so far
ConfigNamespace Functions: (applicable to the system, mod or user
properties of g_ConfigDB)
boolean WriteFile(boolean useVFS, string path):
JS interface to g_ConfigDB.WriteFile - should work exactly the
same.
boolean Reload() => g_ConfigDB.Reload()
void SetFile() => g_ConfigDB.SetConfigFile()
*/
#ifndef INCLUDED_CONFIGDB
#define INCLUDED_CONFIGDB
#include "Parser.h"
#include "CStr.h"
#include "Singleton.h"
+#include "lib/file/vfs/vfs_path.h"
+
// Namespace priorities: User supersedes mod supersedes system.
// Command-line arguments override everything.
enum EConfigNamespace
{
CFG_DEFAULT,
CFG_SYSTEM,
CFG_MOD,
CFG_USER,
CFG_COMMAND,
CFG_LAST
};
typedef CParserValue CConfigValue;
typedef std::vector CConfigValueSet;
#define g_ConfigDB CConfigDB::GetSingleton()
class CConfigDB: public Singleton
{
static std::map m_Map[];
- static CStrW m_ConfigFile[];
- static bool m_UseVFS[];
+ static VfsPath m_ConfigFile[];
public:
// NOTE: Construct the Singleton Object *after* JavaScript init, so that
// the JS interface can be registered.
CConfigDB();
/**
* Attempt to find a config variable with the given name; will search all
* namespaces from system up to the specified namespace.
*
* Returns a pointer to the config value structure for the variable, or
* NULL if such a variable could not be found
*/
CConfigValue *GetValue(EConfigNamespace ns, const CStr& name);
/**
* Attempt to retrieve a vector of values corresponding to the given setting;
* will search all namespaces from system up to the specified namespace.
*
* Returns a pointer to the vector, or NULL if the setting could not be found.
*/
CConfigValueSet *GetValues(EConfigNamespace ns, const CStr& name);
/**
* Retrieve a vector of values corresponding to settings whose names begin
* with the given prefix;
* will search all namespaces from system up to the specified namespace.
*/
std::vector > GetValuesWithPrefix(EConfigNamespace ns, const CStr& prefix);
/**
* Create a new config value in the specified namespace. If such a
* variable already exists, the old value is returned and the effect is
* exactly the same as that of GetValue()
*
* Returns a pointer to the value of the newly created config variable, or
* that of the already existing config variable.
*/
CConfigValue *CreateValue(EConfigNamespace ns, const CStr& name);
/**
* Set the path to the config file used to populate the specified namespace
* Note that this function does not actually load the config file. Use
* the Reload() method if you want to read the config file at the same time.
*
* 'path': The path to the config file.
- * VFS: relative to VFS root
- * non-VFS: relative to current working directory (binaries/data/)
- * 'useVFS': true if the path is a VFS path, false if it is a real path
*/
- void SetConfigFile(EConfigNamespace ns, bool useVFS, const CStrW& path);
+ void SetConfigFile(EConfigNamespace ns, const VfsPath& path);
/**
* Reload the config file associated with the specified config namespace
* (the last config file path set with SetConfigFile)
*
* Returns:
* true: if the reload succeeded,
* false: if the reload failed
*/
bool Reload(EConfigNamespace);
/**
* Write the current state of the specified config namespace to the file
* specified by 'path'
*
* Returns:
* true: if the config namespace was successfully written to the file
* false: if an error occurred
*/
- bool WriteFile(EConfigNamespace ns, bool useVFS, const CStrW& path);
+ bool WriteFile(EConfigNamespace ns, const VfsPath& path);
+
+ /**
+ * Write the current state of the specified config namespace to the file
+ * it was originally loaded from.
+ *
+ * Returns:
+ * true: if the config namespace was successfully written to the file
+ * false: if an error occurred
+ */
+ bool WriteFile(EConfigNamespace ns);
};
// stores the value of the given key into . this quasi-template
// convenience wrapper on top of CConfigValue::Get* simplifies user code and
// avoids "assignment within condition expression" warnings.
#define CFG_GET_SYS_VAL(name, type, destination)\
STMT(\
CConfigValue* val = g_ConfigDB.GetValue(CFG_SYSTEM, name);\
if(val)\
val->Get##type(destination);\
)
#define CFG_GET_USER_VAL(name, type, destination)\
STMT(\
CConfigValue* val = g_ConfigDB.GetValue(CFG_USER, name);\
if(val)\
val->Get##type(destination);\
)
#endif
Index: ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp
===================================================================
--- ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp (revision 8924)
+++ ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp (revision 8925)
@@ -1,649 +1,649 @@
/* Copyright (C) 2010 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 "JSInterface_IGUIObject.h"
#include "JSInterface_GUITypes.h"
#include "gui/IGUIObject.h"
#include "gui/CGUI.h"
#include "gui/IGUIScrollBar.h"
#include "gui/CList.h"
#include "gui/GUIManager.h"
#include "ps/CLogger.h"
#include "scriptinterface/ScriptInterface.h"
JSClass JSI_IGUIObject::JSI_class = {
"GUIObject", JSCLASS_HAS_PRIVATE,
JS_PropertyStub, JS_PropertyStub,
JSI_IGUIObject::getProperty, JSI_IGUIObject::setProperty,
JS_EnumerateStub, JS_ResolveStub,
JS_ConvertStub, JS_FinalizeStub,
NULL, NULL, NULL, JSI_IGUIObject::construct
};
JSPropertySpec JSI_IGUIObject::JSI_props[] =
{
{ 0 }
};
JSFunctionSpec JSI_IGUIObject::JSI_methods[] =
{
{ "toString", JSI_IGUIObject::toString, 0, 0 },
{ "focus", JSI_IGUIObject::focus, 0, 0 },
{ "blur", JSI_IGUIObject::blur, 0, 0 },
{ 0 }
};
JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval* vp)
{
IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, obj, &JSI_IGUIObject::JSI_class, NULL);
if (!e)
return JS_FALSE;
jsval idval;
if (!JS_IdToValue(cx, id, &idval))
return JS_FALSE;
std::string propName;
if (!ScriptInterface::FromJSVal(cx, idval, propName))
return JS_FALSE;
// Skip some things which are known to be functions rather than properties.
// ("constructor" *must* be here, else it'll try to GetSettingType before
// the private IGUIObject* has been set (and thus crash). The others are
// partly for efficiency, and also to allow correct reporting of attempts to
// access nonexistent properties.)
if (propName == "constructor" ||
propName == "prototype" ||
propName == "toString" ||
propName == "focus" ||
propName == "blur"
)
return JS_TRUE;
// Use onWhatever to access event handlers
if (propName.substr(0, 2) == "on")
{
CStr eventName (CStr(propName.substr(2)).LowerCase());
std::map::iterator it = e->m_ScriptHandlers.find(eventName);
if (it == e->m_ScriptHandlers.end())
*vp = JSVAL_NULL;
else
*vp = OBJECT_TO_JSVAL(*(it->second));
return JS_TRUE;
}
// Handle the "parent" property specially
if (propName == "parent")
{
IGUIObject* parent = e->GetParent();
if (parent)
{
// If the object isn't parentless, return a new object
*vp = OBJECT_TO_JSVAL(parent->GetJSObject());
}
else
{
// Return null if there's no parent
*vp = JSVAL_NULL;
}
return JS_TRUE;
}
// Also handle "name" specially
else if (propName == "name")
{
*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, e->GetName()));
return JS_TRUE;
}
// Handle all other properties
else
{
// Retrieve the setting's type (and make sure it actually exists)
EGUISettingType Type;
if (e->GetSettingType(propName, Type) != PSRETURN_OK)
{
JS_ReportError(cx, "Invalid GUIObject property '%s'", propName.c_str());
return JS_FALSE;
}
// (All the cases are in {...} to avoid scoping problems)
switch (Type)
{
case GUIST_bool:
{
bool value;
GUI::GetSetting(e, propName, value);
*vp = value ? JSVAL_TRUE : JSVAL_FALSE;
break;
}
case GUIST_int:
{
int value;
GUI::GetSetting(e, propName, value);
*vp = INT_TO_JSVAL(value);
break;
}
case GUIST_float:
{
float value;
GUI::GetSetting(e, propName, value);
// Create a garbage-collectable double
return JS_NewNumberValue(cx, value, vp);
}
case GUIST_CColor:
{
CColor colour;
GUI::GetSetting(e, propName, colour);
JSObject* obj = JS_NewObject(cx, &JSI_GUIColor::JSI_class, NULL, NULL);
*vp = OBJECT_TO_JSVAL(obj); // root it
jsval c;
// Attempt to minimise ugliness through macrosity
#define P(x) if (!JS_NewNumberValue(cx, colour.x, &c)) return JS_FALSE; JS_SetProperty(cx, obj, #x, &c)
P(r);
P(g);
P(b);
P(a);
#undef P
break;
}
case GUIST_CClientArea:
{
CClientArea area;
GUI::GetSetting(e, propName, area);
JSObject* obj = JS_NewObject(cx, &JSI_GUISize::JSI_class, NULL, NULL);
*vp = OBJECT_TO_JSVAL(obj); // root it
try
{
#define P(x, y, z) g_ScriptingHost.SetObjectProperty_Double(obj, #z, area.x.y)
P(pixel, left, left);
P(pixel, top, top);
P(pixel, right, right);
P(pixel, bottom, bottom);
P(percent, left, rleft);
P(percent, top, rtop);
P(percent, right, rright);
P(percent, bottom, rbottom);
#undef P
}
catch (PSERROR_Scripting_ConversionFailed)
{
debug_warn(L"Error creating size object!");
break;
}
break;
}
case GUIST_CGUIString:
{
CGUIString value;
GUI::GetSetting(e, propName, value);
- *vp = ScriptInterface::ToJSVal(cx, value.GetRawString());
+ *vp = ScriptInterface::ToJSVal(cx, value.GetOriginalString());
break;
}
case GUIST_CStr:
{
CStr value;
GUI::GetSetting(e, propName, value);
*vp = ScriptInterface::ToJSVal(cx, value);
break;
}
case GUIST_CStrW:
{
CStrW value;
GUI::GetSetting(e, propName, value);
*vp = ScriptInterface::ToJSVal(cx, value);
break;
}
case GUIST_CGUISpriteInstance:
{
CGUISpriteInstance *value;
GUI::GetSettingPointer(e, propName, value);
*vp = ScriptInterface::ToJSVal(cx, value->GetName());
break;
}
case GUIST_EAlign:
{
EAlign value;
GUI::GetSetting(e, propName, value);
CStr word;
switch (value)
{
case EAlign_Left: word = "left"; break;
case EAlign_Right: word = "right"; break;
case EAlign_Center: word = "center"; break;
default: debug_warn(L"Invalid EAlign!"); word = "error"; break;
}
*vp = ScriptInterface::ToJSVal(cx, word);
break;
}
case GUIST_EVAlign:
{
EVAlign value;
GUI::GetSetting(e, propName, value);
CStr word;
switch (value)
{
case EVAlign_Top: word = "top"; break;
case EVAlign_Bottom: word = "bottom"; break;
case EVAlign_Center: word = "center"; break;
default: debug_warn(L"Invalid EVAlign!"); word = "error"; break;
}
*vp = ScriptInterface::ToJSVal(cx, word);
break;
}
case GUIST_CGUIList:
{
CGUIList value;
GUI::GetSetting(e, propName, value);
JSObject *obj = JS_NewArrayObject(cx, 0, NULL);
*vp = OBJECT_TO_JSVAL(obj); // root it
for (size_t i = 0; i < value.m_Items.size(); ++i)
{
- jsval val = ScriptInterface::ToJSVal(cx, value.m_Items[i].GetRawString());
+ jsval val = ScriptInterface::ToJSVal(cx, value.m_Items[i].GetOriginalString());
JS_SetElement(cx, obj, (jsint)i, &val);
}
break;
}
default:
JS_ReportError(cx, "Setting '%s' uses an unimplemented type", propName.c_str());
debug_assert(0);
return JS_FALSE;
}
return JS_TRUE;
}
}
JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, jsval* vp)
{
IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, obj, &JSI_IGUIObject::JSI_class, NULL);
if (!e)
return JS_FALSE;
jsval idval;
if (!JS_IdToValue(cx, id, &idval))
return JS_FALSE;
std::string propName;
if (!ScriptInterface::FromJSVal(cx, idval, propName))
return JS_FALSE;
if (propName == "name")
{
std::string value;
if (!ScriptInterface::FromJSVal(cx, *vp, value))
return JS_FALSE;
e->SetName(value);
return JS_TRUE;
}
// Use onWhatever to set event handlers
if (propName.substr(0, 2) == "on")
{
if (!JSVAL_IS_OBJECT(*vp) || !JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(*vp)))
{
JS_ReportError(cx, "on- event-handlers must be functions");
return JS_FALSE;
}
CStr eventName (CStr(propName.substr(2)).LowerCase());
e->SetScriptHandler(eventName, JSVAL_TO_OBJECT(*vp));
return JS_TRUE;
}
// Retrieve the setting's type (and make sure it actually exists)
EGUISettingType Type;
if (e->GetSettingType(propName, Type) != PSRETURN_OK)
{
JS_ReportError(cx, "Invalid setting '%s'", propName.c_str());
return JS_TRUE;
}
switch (Type)
{
case GUIST_CStr:
{
std::string value;
if (!ScriptInterface::FromJSVal(cx, *vp, value))
return JS_FALSE;
GUI::SetSetting(e, propName, value);
break;
}
case GUIST_CStrW:
{
std::wstring value;
if (!ScriptInterface::FromJSVal(cx, *vp, value))
return JS_FALSE;
GUI::SetSetting(e, propName, value);
break;
}
case GUIST_CGUISpriteInstance:
{
std::string value;
if (!ScriptInterface::FromJSVal(cx, *vp, value))
return JS_FALSE;
GUI::SetSetting(e, propName, CGUISpriteInstance(value));
break;
}
case GUIST_CGUIString:
{
std::wstring value;
if (!ScriptInterface::FromJSVal(cx, *vp, value))
return JS_FALSE;
CGUIString str;
str.SetValue(value);
GUI::SetSetting(e, propName, str);
break;
}
case GUIST_EAlign:
{
std::string value;
if (!ScriptInterface::FromJSVal(cx, *vp, value))
return JS_FALSE;
EAlign a;
if (value == "left") a = EAlign_Left;
else if (value == "right") a = EAlign_Right;
else if (value == "center" || value == "centre") a = EAlign_Center;
else
{
JS_ReportError(cx, "Invalid alignment (should be 'left', 'right' or 'center')");
return JS_FALSE;
}
GUI::SetSetting(e, propName, a);
break;
}
case GUIST_EVAlign:
{
std::string value;
if (!ScriptInterface::FromJSVal(cx, *vp, value))
return JS_FALSE;
EVAlign a;
if (value == "top") a = EVAlign_Top;
else if (value == "bottom") a = EVAlign_Bottom;
else if (value == "center" || value == "centre") a = EVAlign_Center;
else
{
JS_ReportError(cx, "Invalid alignment (should be 'top', 'bottom' or 'center')");
return JS_FALSE;
}
GUI::SetSetting(e, propName, a);
break;
}
case GUIST_int:
{
int32 value;
if (JS_ValueToInt32(cx, *vp, &value) == JS_TRUE)
GUI::SetSetting(e, propName, value);
else
{
JS_ReportError(cx, "Cannot convert value to int");
return JS_FALSE;
}
break;
}
case GUIST_float:
{
jsdouble value;
if (JS_ValueToNumber(cx, *vp, &value) == JS_TRUE)
GUI::SetSetting(e, propName, (float)value);
else
{
JS_ReportError(cx, "Cannot convert value to float");
return JS_FALSE;
}
break;
}
case GUIST_bool:
{
JSBool value;
if (JS_ValueToBoolean(cx, *vp, &value) == JS_TRUE)
GUI::SetSetting(e, propName, value||0); // ||0 to avoid int-to-bool compiler warnings
else
{
JS_ReportError(cx, "Cannot convert value to bool");
return JS_FALSE;
}
break;
}
case GUIST_CClientArea:
{
if (JSVAL_IS_STRING(*vp))
{
std::wstring value;
if (!ScriptInterface::FromJSVal(cx, *vp, value))
return JS_FALSE;
if (e->SetSetting(propName, value) != PSRETURN_OK)
{
JS_ReportError(cx, "Invalid value for setting '%s'", propName.c_str());
return JS_FALSE;
}
}
else if (JSVAL_IS_OBJECT(*vp) && JS_InstanceOf(cx, JSVAL_TO_OBJECT(*vp), &JSI_GUISize::JSI_class, NULL))
{
CClientArea area;
GUI::GetSetting(e, propName, area);
JSObject* obj = JSVAL_TO_OBJECT(*vp);
#define P(x, y, z) area.x.y = (float)g_ScriptingHost.GetObjectProperty_Double(obj, #z)
P(pixel, left, left);
P(pixel, top, top);
P(pixel, right, right);
P(pixel, bottom, bottom);
P(percent, left, rleft);
P(percent, top, rtop);
P(percent, right, rright);
P(percent, bottom, rbottom);
#undef P
GUI::SetSetting(e, propName, area);
}
else
{
JS_ReportError(cx, "Size only accepts strings or GUISize objects");
return JS_FALSE;
}
break;
}
case GUIST_CColor:
{
if (JSVAL_IS_STRING(*vp))
{
std::wstring value;
if (!ScriptInterface::FromJSVal(cx, *vp, value))
return JS_FALSE;
if (e->SetSetting(propName, value) != PSRETURN_OK)
{
JS_ReportError(cx, "Invalid value for setting '%s'", propName.c_str());
return JS_FALSE;
}
}
else if (JSVAL_IS_OBJECT(*vp) && JS_InstanceOf(cx, JSVAL_TO_OBJECT(*vp), &JSI_GUIColor::JSI_class, NULL))
{
CColor colour;
JSObject* obj = JSVAL_TO_OBJECT(*vp);
jsval t; double s;
#define PROP(x) JS_GetProperty(cx, obj, #x, &t); \
JS_ValueToNumber(cx, t, &s); \
colour.x = (float)s
PROP(r); PROP(g); PROP(b); PROP(a);
#undef PROP
GUI::SetSetting(e, propName, colour);
}
else
{
JS_ReportError(cx, "Color only accepts strings or GUIColor objects");
return JS_FALSE;
}
break;
}
case GUIST_CGUIList:
{
JSObject* obj = JSVAL_TO_OBJECT(*vp);
jsuint length;
if (JSVAL_IS_OBJECT(*vp) && JS_GetArrayLength(cx, obj, &length) == JS_TRUE)
{
CGUIList list;
for (int i=0; i<(int)length; ++i)
{
jsval element;
if (! JS_GetElement(cx, obj, i, &element))
{
JS_ReportError(cx, "Failed to get list element");
return JS_FALSE;
}
std::wstring value;
if (!ScriptInterface::FromJSVal(cx, element, value))
return JS_FALSE;
CGUIString str;
str.SetValue(value);
list.m_Items.push_back(str);
}
GUI::SetSetting(e, propName, list);
}
else
{
JS_ReportError(cx, "List only accepts a GUIList object");
return JS_FALSE;
}
break;
}
// TODO Gee: (2004-09-01) EAlign and EVAlign too.
default:
JS_ReportError(cx, "Setting '%s' uses an unimplemented type", propName.c_str());
break;
}
return JS_TRUE;
}
JSBool JSI_IGUIObject::construct(JSContext* cx, uintN argc, jsval* vp)
{
if (argc == 0)
{
JS_ReportError(cx, "GUIObject has no default constructor");
return JS_FALSE;
}
JSObject* obj = JS_NewObject(cx, &JSI_IGUIObject::JSI_class, NULL, NULL);
// Store the IGUIObject in the JS object's 'private' area
IGUIObject* guiObject = (IGUIObject*)JSVAL_TO_PRIVATE(JS_ARGV(cx, vp)[0]);
JS_SetPrivate(cx, obj, guiObject);
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj));
return JS_TRUE;
}
void JSI_IGUIObject::init()
{
g_ScriptingHost.DefineCustomObjectType(&JSI_class, construct, 1, JSI_props, JSI_methods, NULL, NULL);
}
JSBool JSI_IGUIObject::toString(JSContext* cx, uintN argc, jsval* vp)
{
UNUSED2(argc);
IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, JS_THIS_OBJECT(cx, vp), &JSI_IGUIObject::JSI_class, NULL);
if (!e)
return JS_FALSE;
char buffer[256];
snprintf(buffer, 256, "[GUIObject: %s]", e->GetName().c_str());
buffer[255] = 0;
JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, buffer)));
return JS_TRUE;
}
JSBool JSI_IGUIObject::focus(JSContext* cx, uintN argc, jsval* vp)
{
UNUSED2(argc);
IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, JS_THIS_OBJECT(cx, vp), &JSI_IGUIObject::JSI_class, NULL);
if (!e)
return JS_FALSE;
e->GetGUI()->SetFocusedObject(e);
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
JSBool JSI_IGUIObject::blur(JSContext* cx, uintN argc, jsval* vp)
{
UNUSED2(argc);
IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, JS_THIS_OBJECT(cx, vp), &JSI_IGUIObject::JSI_class, NULL);
if (!e)
return JS_FALSE;
e->GetGUI()->SetFocusedObject(NULL);
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
Index: ps/trunk/source/gui/scripting/ScriptFunctions.cpp
===================================================================
--- ps/trunk/source/gui/scripting/ScriptFunctions.cpp (revision 8924)
+++ ps/trunk/source/gui/scripting/ScriptFunctions.cpp (revision 8925)
@@ -1,462 +1,493 @@
/* Copyright (C) 2011 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 "lib/timer.h"
+#include "lib/utf8.h"
#include "lib/sysdep/sysdep.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/Hotkey.h"
#include "ps/Overlay.h"
#include "ps/Pyrogenesis.h"
+#include "ps/UserReport.h"
#include "ps/GameSetup/Atlas.h"
#include "ps/GameSetup/Config.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/helpers/Selection.h"
#include "js/jsapi.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
namespace {
CScriptVal GetActiveGui(void* UNUSED(cbdata))
{
return OBJECT_TO_JSVAL(g_GUI->GetScriptObject());
}
void PushGuiPage(void* UNUSED(cbdata), std::wstring name, CScriptVal initData)
{
g_GUI->PushPage(name, initData);
}
void SwitchGuiPage(void* UNUSED(cbdata), std::wstring name, CScriptVal initData)
{
g_GUI->SwitchPage(name, initData);
}
void PopGuiPage(void* UNUSED(cbdata))
{
g_GUI->PopPage();
}
CScriptVal GuiInterfaceCall(void* cbdata, std::wstring name, CScriptVal data)
{
CGUIManager* guiManager = static_cast (cbdata);
if (!g_Game)
return JSVAL_VOID;
CSimulation2* sim = g_Game->GetSimulation2();
debug_assert(sim);
CmpPtr gui(*sim, SYSTEM_ENTITY);
if (gui.null())
return JSVAL_VOID;
int player = -1;
if (g_Game)
player = g_Game->GetPlayerID();
CScriptValRooted arg (sim->GetScriptInterface().GetContext(), sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), data.get()));
CScriptVal ret (gui->ScriptCall(player, name, arg.get()));
return guiManager->GetScriptInterface().CloneValueFromOtherContext(sim->GetScriptInterface(), ret.get());
}
void PostNetworkCommand(void* cbdata, CScriptVal cmd)
{
CGUIManager* guiManager = static_cast (cbdata);
if (!g_Game)
return;
CSimulation2* sim = g_Game->GetSimulation2();
debug_assert(sim);
CmpPtr queue(*sim, SYSTEM_ENTITY);
if (queue.null())
return;
jsval cmd2 = sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), cmd.get());
queue->PostNetworkCommand(cmd2);
}
std::vector PickEntitiesAtPoint(void* UNUSED(cbdata), int x, int y)
{
return EntitySelection::PickEntitiesAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y, g_Game->GetPlayerID());
}
std::vector PickFriendlyEntitiesInRect(void* UNUSED(cbdata), 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);
}
std::vector PickSimilarFriendlyEntities(void* UNUSED(cbdata), std::string templateName, bool onScreenOnly)
{
return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetPlayerID(), onScreenOnly);
}
CFixedVector3D GetTerrainAtPoint(void* UNUSED(cbdata), 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(void* UNUSED(cbdata), std::wstring name)
{
std::wstring old = g_CursorName;
g_CursorName = name;
return old;
}
int GetPlayerID(void* UNUSED(cbdata))
{
if (g_Game)
return g_Game->GetPlayerID();
return -1;
}
std::wstring GetDefaultPlayerName(void* UNUSED(cbdata))
{
// TODO: this should come from a config file or something
std::wstring name = sys_get_user_name();
if (name.empty())
name = L"anonymous";
return name;
}
void StartNetworkGame(void* UNUSED(cbdata))
{
debug_assert(g_NetServer);
g_NetServer->StartGame();
}
void StartGame(void* cbdata, CScriptVal attribs, int playerID)
{
CGUIManager* guiManager = static_cast (cbdata);
debug_assert(!g_NetServer);
debug_assert(!g_NetClient);
debug_assert(!g_Game);
g_Game = new CGame();
// Convert from GUI script context to sim script context
CSimulation2* sim = g_Game->GetSimulation2();
CScriptValRooted gameAttribs (sim->GetScriptInterface().GetContext(),
sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), attribs.get()));
g_Game->SetPlayerID(playerID);
g_Game->StartGame(gameAttribs);
}
void SetNetworkGameAttributes(void* cbdata, CScriptVal attribs)
{
CGUIManager* guiManager = static_cast (cbdata);
debug_assert(g_NetServer);
g_NetServer->UpdateGameAttributes(attribs, guiManager->GetScriptInterface());
}
void StartNetworkHost(void* cbdata, std::wstring playerName)
{
CGUIManager* guiManager = static_cast (cbdata);
debug_assert(!g_NetClient);
debug_assert(!g_NetServer);
debug_assert(!g_Game);
g_NetServer = new CNetServer();
if (!g_NetServer->SetupConnection())
{
guiManager->GetScriptInterface().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"))
{
guiManager->GetScriptInterface().ReportError("Failed to connect to server");
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_Game);
}
}
void StartNetworkJoin(void* cbdata, std::wstring playerName, std::string serverAddress)
{
CGUIManager* guiManager = static_cast (cbdata);
debug_assert(!g_NetClient);
debug_assert(!g_NetServer);
debug_assert(!g_Game);
g_Game = new CGame();
g_NetClient = new CNetClient(g_Game);
g_NetClient->SetUserName(playerName);
if (!g_NetClient->SetupConnection(serverAddress))
{
guiManager->GetScriptInterface().ReportError("Failed to connect to server");
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_Game);
}
}
void DisconnectNetworkGame(void* UNUSED(cbdata))
{
// TODO: we ought to do async reliable disconnections
SAFE_DELETE(g_NetServer);
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_Game);
}
CScriptVal PollNetworkClient(void* cbdata)
{
CGUIManager* guiManager = static_cast (cbdata);
if (!g_NetClient)
return CScriptVal();
CScriptValRooted poll = g_NetClient->GuiPoll();
// Convert from net client context to GUI script context
return guiManager->GetScriptInterface().CloneValueFromOtherContext(g_NetClient->GetScriptInterface(), poll.get());
}
void AssignNetworkPlayer(void* UNUSED(cbdata), int playerID, std::string guid)
{
debug_assert(g_NetServer);
g_NetServer->AssignPlayer(playerID, guid);
}
void SendNetworkChat(void* UNUSED(cbdata), std::wstring message)
{
debug_assert(g_NetClient);
g_NetClient->SendChatMessage(message);
}
std::vector GetAIs(void* cbdata)
{
CGUIManager* guiManager = static_cast (cbdata);
return ICmpAIManager::GetAIs(guiManager->GetScriptInterface());
}
void OpenURL(void* UNUSED(cbdata), std::string url)
{
sys_open_url(url);
}
void RestartInAtlas(void* UNUSED(cbdata))
{
restart_mainloop_in_atlas();
}
bool AtlasIsAvailable(void* UNUSED(cbdata))
{
return ATLAS_IsAvailable();
}
CScriptVal LoadMapSettings(void* cbdata, std::wstring pathname)
{
CGUIManager* guiManager = static_cast (cbdata);
CMapSummaryReader reader;
if (reader.LoadMap(VfsPath(pathname + L".xml")) != PSRETURN_OK)
return CScriptVal();
return reader.GetMapSettings(guiManager->GetScriptInterface()).get();
}
/**
* Start / stop camera following mode
* @param entityid unit id to follow. If zero, stop following mode
*/
void CameraFollow(void* UNUSED(cbdata), entity_id_t entityid)
{
if (g_Game && g_Game->GetView())
g_Game->GetView()->CameraFollow(entityid, false);
}
void CameraFollowFPS(void* UNUSED(cbdata), entity_id_t entityid)
{
if (g_Game && g_Game->GetView())
g_Game->GetView()->CameraFollow(entityid, true);
}
bool HotkeyIsPressed_(void* UNUSED(cbdata), std::string hotkeyName)
{
return HotkeyIsPressed(hotkeyName);
}
void DisplayErrorDialog(void* UNUSED(cbdata), std::wstring msg)
{
debug_DisplayError(msg.c_str(), DE_NO_DEBUG_INFO, NULL, NULL, NULL, 0, NULL, NULL);
}
+
+bool IsUserReportEnabled(void* UNUSED(cbdata))
+{
+ return g_UserReporter.IsReportingEnabled();
+}
+
+void SetUserReportEnabled(void* UNUSED(cbdata), bool enabled)
+{
+ g_UserReporter.SetReportingEnabled(enabled);
+}
+
+std::string GetUserReportStatus(void* UNUSED(cbdata))
+{
+ return g_UserReporter.GetStatus();
+}
+
+void SubmitUserReport(void* UNUSED(cbdata), std::string type, int version, std::wstring data)
+{
+ g_UserReporter.SubmitReport(type.c_str(), version, utf8_from_wstring(data));
+}
+
+
+
void SetSimRate(void* UNUSED(cbdata), float rate)
{
g_Game->SetSimRate(rate);
}
void SetTurnLength(void* UNUSED(cbdata), 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(void* UNUSED(cbdata), 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(void* UNUSED(cbdata))
{
debug_printf(L"Crashing at user's request.\n");
return *(int*)0;
}
void DebugWarn(void* UNUSED(cbdata))
{
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(void* cbdata)
{
CGUIManager* guiManager = static_cast (cbdata);
double time = timer_Time();
JS_GC(guiManager->GetScriptInterface().GetContext());
time = timer_Time() - time;
g_Console->InsertMessage(L"Garbage collection completed in: %f", time);
}
void DumpSimState(void* UNUSED(cbdata))
{
fs::wpath path (psLogDir()/L"sim_dump.txt");
std::ofstream file (path.external_file_string().c_str(), std::ofstream::out | std::ofstream::trunc);
g_Game->GetSimulation2()->DumpDebugState(file);
}
void EnableTimeWarpRecording(void* UNUSED(cbdata), unsigned int numTurns)
{
g_Game->GetTurnManager()->EnableTimeWarpRecording(numTurns);
}
void RewindTimeWarp(void* UNUSED(cbdata))
{
g_Game->GetTurnManager()->RewindTimeWarp();
}
} // namespace
void GuiScriptingInit(ScriptInterface& scriptInterface)
{
// GUI manager functions:
scriptInterface.RegisterFunction("GetActiveGui");
scriptInterface.RegisterFunction("PushGuiPage");
scriptInterface.RegisterFunction("SwitchGuiPage");
scriptInterface.RegisterFunction("PopGuiPage");
// Simulation<->GUI interface functions:
scriptInterface.RegisterFunction("GuiInterfaceCall");
scriptInterface.RegisterFunction("PostNetworkCommand");
// Entity picking
scriptInterface.RegisterFunction, int, int, &PickEntitiesAtPoint>("PickEntitiesAtPoint");
scriptInterface.RegisterFunction, int, int, int, int, int, &PickFriendlyEntitiesInRect>("PickFriendlyEntitiesInRect");
scriptInterface.RegisterFunction, std::string, bool, &PickSimilarFriendlyEntities>("PickSimilarFriendlyEntities");
scriptInterface.RegisterFunction("GetTerrainAtPoint");
// Network / game setup functions
scriptInterface.RegisterFunction("StartNetworkGame");
scriptInterface.RegisterFunction("StartGame");
scriptInterface.RegisterFunction("StartNetworkHost");
scriptInterface.RegisterFunction("StartNetworkJoin");
scriptInterface.RegisterFunction("DisconnectNetworkGame");
scriptInterface.RegisterFunction("PollNetworkClient");
scriptInterface.RegisterFunction("SetNetworkGameAttributes");
scriptInterface.RegisterFunction("AssignNetworkPlayer");
scriptInterface.RegisterFunction("SendNetworkChat");
scriptInterface.RegisterFunction, &GetAIs>("GetAIs");
// Misc functions
scriptInterface.RegisterFunction("SetCursor");
scriptInterface.RegisterFunction("GetPlayerID");
scriptInterface.RegisterFunction("GetDefaultPlayerName");
scriptInterface.RegisterFunction("OpenURL");
scriptInterface.RegisterFunction("RestartInAtlas");
scriptInterface.RegisterFunction("AtlasIsAvailable");
scriptInterface.RegisterFunction("LoadMapSettings");
scriptInterface.RegisterFunction("CameraFollow");
scriptInterface.RegisterFunction("CameraFollowFPS");
scriptInterface.RegisterFunction("HotkeyIsPressed");
scriptInterface.RegisterFunction("DisplayErrorDialog");
+ // User report functions
+ scriptInterface.RegisterFunction("IsUserReportEnabled");
+ scriptInterface.RegisterFunction("SetUserReportEnabled");
+ scriptInterface.RegisterFunction("GetUserReportStatus");
+ scriptInterface.RegisterFunction("SubmitUserReport");
+
// Development/debugging functions
scriptInterface.RegisterFunction("SetSimRate");
scriptInterface.RegisterFunction("SetTurnLength");
scriptInterface.RegisterFunction("SetCameraTarget");
scriptInterface.RegisterFunction("Crash");
scriptInterface.RegisterFunction("DebugWarn");
scriptInterface.RegisterFunction("ForceGC");
scriptInterface.RegisterFunction("DumpSimState");
scriptInterface.RegisterFunction("EnableTimeWarpRecording");
scriptInterface.RegisterFunction("RewindTimeWarp");
}
Index: ps/trunk/source/gui/GUItext.h
===================================================================
--- ps/trunk/source/gui/GUItext.h (revision 8924)
+++ ps/trunk/source/gui/GUItext.h (revision 8925)
@@ -1,338 +1,348 @@
/* Copyright (C) 2009 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 .
*/
/*
GUI text, handles text stuff
--Overview--
Mainly contains struct SGUIText and friends.
Actual text processing is made in CGUI::GenerateText()
--More info--
Check GUI.h
*/
#ifndef INCLUDED_GUITEXT
#define INCLUDED_GUITEXT
//--------------------------------------------------------
// Includes / Compiler directives
//--------------------------------------------------------
#include
#include "CGUISprite.h"
//--------------------------------------------------------
// Declarations
//--------------------------------------------------------
/**
* An SGUIText object is a parsed string, divided into
* text-rendering components. Each component, being a
* call to the Renderer. For instance, if you by tags
* change the color, then the GUI will have to make
* individual calls saying it want that color on the
* text.
*
* For instance:
* "Hello [b]there[/b] bunny!"
*
* That without word-wrapping would mean 3 components.
* i.e. 3 calls to CRenderer. One drawing "Hello",
* one drawing "there" in bold, and one drawing "bunny!".
*/
struct SGUIText
{
/**
* A sprite call to the CRenderer
*/
struct SSpriteCall
{
SSpriteCall() : m_CellID(0) {}
/**
* Size and position of sprite
*/
CRect m_Area;
/**
* Sprite from global GUI sprite database.
*/
CGUISpriteInstance m_Sprite;
int m_CellID;
/**
* Tooltip text
*/
CStrW m_Tooltip;
/**
* Tooltip style
*/
CStrW m_TooltipStyle;
};
/**
* A text call to the CRenderer
*/
struct STextCall
{
STextCall() :
m_UseCustomColor(false),
m_Bold(false), m_Italic(false), m_Underlined(false),
m_pSpriteCall(NULL) {}
/**
* Position
*/
CPos m_Pos;
/**
* Size
*/
CSize m_Size;
/**
* The string that is suppose to be rendered.
*/
CStrW m_String;
/**
* Use custom color? If true then m_Color is used,
* else the color inputted will be used.
*/
bool m_UseCustomColor;
/**
* Color setup
*/
CColor m_Color;
/**
* Font name
*/
CStrW m_Font;
/**
* Settings
*/
bool m_Bold, m_Italic, m_Underlined;
/**
* *IF* an icon, then this is not NULL.
*/
std::list::pointer m_pSpriteCall;
};
/**
* List of TextCalls, for instance "Hello", "there!"
*/
std::vector m_TextCalls;
/**
* List of sprites, or "icons" that should be rendered
* along with the text.
*/
std::list m_SpriteCalls; // list for consistent mem addresses
// so that we can point to elements.
/**
* Width and height of the whole output, used when setting up
* scrollbars and such.
*/
CSize m_Size;
};
/**
* String class, substitute for CStr, but that parses
* the tags and builds up a list of all text that will
* be different when outputted.
*
* The difference between CGUIString and SGUIText is that
* CGUIString is a string-class that parses the tags
* when the value is set. The SGUIText is just a container
* which stores the positions and settings of all text-calls
* that will have to be made to the Renderer.
*/
class CGUIString
{
public:
/**
* A chunk of text that represents one call to the renderer.
* In other words, all text in one chunk, will be drawn
* exactly with the same settings.
*/
struct TextChunk
{
/**
* A tag looks like this "Hello [B]there[/B] little"
*/
struct Tag
{
/**
* Tag Type
*/
enum TagType
{
TAG_B,
TAG_I,
TAG_FONT,
TAG_SIZE,
TAG_COLOR,
TAG_IMGLEFT,
TAG_IMGRIGHT,
TAG_ICON
};
struct TagAttribute
{
std::string attrib;
std::string value;
};
/**
* Set tag from string
*
* @param tagtype TagType by string, like 'IMG' for [IMG]
* @return True if m_TagType was set.
*/
bool SetTagType(const CStr& tagtype);
/**
* In [B=Hello][/B]
* m_TagType is TAG_B
*/
TagType m_TagType;
/**
* In [B=Hello][/B]
* m_TagValue is 'Hello'
*/
std::string m_TagValue;
/**
* Some tags need an additional attributes
*/
std::vector m_TagAttributes;
};
/**
* m_From and m_To is the range of the string
*/
int m_From, m_To;
/**
* Tags that are present. [A][B]
*/
std::vector m_Tags;
};
/**
* All data generated in GenerateTextCall()
*/
struct SFeedback
{
// Constants
static const int Left=0;
static const int Right=1;
/**
* Reset all member data.
*/
void Reset();
/**
* Image stacks, for left and right floating images.
*/
std::vector m_Images[2]; // left and right
/**
* Text and Sprite Calls.
*/
std::vector m_TextCalls;
std::list m_SpriteCalls; // list for consistent mem addresses
// so that we can point to elements.
/**
* Width and Height *feedback*
*/
CSize m_Size;
/**
* If the word inputted was a new line.
*/
bool m_NewLine;
};
/**
* Set the value, the string will automatically
* be parsed when set.
*/
void SetValue(const CStrW& str);
/**
* Get String, without tags
*/
const CStrW& GetRawString() const { return m_RawString; }
/**
+ * Get String, with tags
+ */
+ const CStrW& GetOriginalString() const { return m_OriginalString; }
+
+ /**
* Generate Text Call from specified range. The range
* must span only within ONE TextChunk though. Otherwise
* it can't be fit into a single Text Call
*
* Notice it won't make it complete, you will have to add
* X/Y values and such.
*
* @param Feedback contains all info that is generated.
* @param DefaultFont Default Font
* @param from From character n,
* @param to to character n.
* @param FirstLine Whether this is the first line of text, to calculate its height correctly
* @param pObject Only for Error outputting, optional! If NULL
* then no Errors will be reported! Useful when you need
* to make several GenerateTextCall in different phases,
* it avoids duplicates.
*/
void GenerateTextCall(SFeedback &Feedback,
const CStr& DefaultFont,
const int &from, const int &to,
const bool FirstLine,
const IGUIObject *pObject=NULL) const;
/**
* Words
*/
std::vector m_Words;
/**
* TextChunks
*/
std::vector m_TextChunks;
private:
/**
* The full raw string. Stripped of tags.
*/
CStrW m_RawString;
+
+ /**
+ * The original string value passed to SetValue.
+ */
+ CStrW m_OriginalString;
};
#endif
Index: ps/trunk/source/gui/GUItext.cpp
===================================================================
--- ps/trunk/source/gui/GUItext.cpp (revision 8924)
+++ ps/trunk/source/gui/GUItext.cpp (revision 8925)
@@ -1,613 +1,615 @@
/* Copyright (C) 2009 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 .
*/
/*
GUI text
*/
#include "precompiled.h"
#include "GUI.h"
#include "GUIManager.h"
#include "ps/CLogger.h"
#include "ps/Parser.h"
#include
#include "ps/Font.h"
static const wchar_t TagStart = '[';
static const wchar_t TagEnd = ']';
void CGUIString::SFeedback::Reset()
{
m_Images[Left].clear();
m_Images[Right].clear();
m_TextCalls.clear();
m_SpriteCalls.clear();
m_Size = CSize();
m_NewLine=false;
}
void CGUIString::GenerateTextCall(SFeedback &Feedback,
const CStr& DefaultFont,
const int &from, const int &to,
const bool FirstLine,
const IGUIObject *pObject) const
{
// Reset width and height, because they will be determined with incrementation
// or comparisons.
Feedback.Reset();
// Check out which text chunk this is within.
//bool match_found = false;
std::vector::const_iterator itTextChunk;
for (itTextChunk=m_TextChunks.begin(); itTextChunk!=m_TextChunks.end(); ++itTextChunk)
{
// - GL - Temp
TextChunk tc = *itTextChunk;
// -- GL
// Get the area that is overlapped by both the TextChunk and
// by the from/to inputted.
int _from, _to;
_from = std::max(from, itTextChunk->m_From);
_to = std::min(to, itTextChunk->m_To);
// If from is larger than to, than they are not overlapping
if (_to == _from && itTextChunk->m_From == itTextChunk->m_To)
{
// These should never be able to have more than one tag.
debug_assert(itTextChunk->m_Tags.size()==1);
// Now do second check
// because icons and images are placed on exactly one position
// in the words-list, it can be counted twice if placed on an
// edge. But there is always only one logical preference that
// we want. This check filters the unwanted.
// it's in the end of one word, and the icon
// should really belong to the beginning of the next one
if (_to == to && to >= 1)
{
if (GetRawString()[to-1] == ' ' ||
GetRawString()[to-1] == '-' ||
GetRawString()[to-1] == '\n')
continue;
}
// This std::string is just a break
if (_from == from && from >= 1)
{
if (GetRawString()[from] == '\n' &&
GetRawString()[from-1] != '\n' &&
GetRawString()[from-1] != ' ' &&
GetRawString()[from-1] != '-')
continue;
}
// Single tags
if (itTextChunk->m_Tags[0].m_TagType == CGUIString::TextChunk::Tag::TAG_IMGLEFT)
{
// Only add the image if the icon exists.
if (g_GUI->IconExists(itTextChunk->m_Tags[0].m_TagValue))
{
Feedback.m_Images[SFeedback::Left].push_back(itTextChunk->m_Tags[0].m_TagValue);
}
else if (pObject)
{
LOGERROR(L"Trying to use an [imgleft]-tag with an undefined icon (\"%hs\").", itTextChunk->m_Tags[0].m_TagValue.c_str());
}
}
else
if (itTextChunk->m_Tags[0].m_TagType == CGUIString::TextChunk::Tag::TAG_IMGRIGHT)
{
// Only add the image if the icon exists.
if (g_GUI->IconExists(itTextChunk->m_Tags[0].m_TagValue))
{
Feedback.m_Images[SFeedback::Right].push_back(itTextChunk->m_Tags[0].m_TagValue);
}
else if (pObject)
{
LOGERROR(L"Trying to use an [imgright]-tag with an undefined icon (\"%hs\").", itTextChunk->m_Tags[0].m_TagValue.c_str());
}
}
else
if (itTextChunk->m_Tags[0].m_TagType == CGUIString::TextChunk::Tag::TAG_ICON)
{
// Only add the image if the icon exists.
if (g_GUI->IconExists(itTextChunk->m_Tags[0].m_TagValue))
{
// We'll need to setup a text-call that will point
// to the icon, this is to be able to iterate
// through the text-calls without having to
// complex the structure virtually for nothing more.
SGUIText::STextCall TextCall;
// Also add it to the sprites being rendered.
SGUIText::SSpriteCall SpriteCall;
// Get Icon from icon database in g_GUI
SGUIIcon icon = g_GUI->GetIcon(itTextChunk->m_Tags[0].m_TagValue);
CSize size = icon.m_Size;
// append width, and make maximum height the height.
Feedback.m_Size.cx += size.cx;
Feedback.m_Size.cy = std::max(Feedback.m_Size.cy, size.cy);
// These are also needed later
TextCall.m_Size = size;
SpriteCall.m_Area = size;
// Handle additional attributes
std::vector::const_iterator att_it;
for(att_it = itTextChunk->m_Tags[0].m_TagAttributes.begin(); att_it != itTextChunk->m_Tags[0].m_TagAttributes.end(); ++att_it)
{
TextChunk::Tag::TagAttribute tagAttrib = (TextChunk::Tag::TagAttribute)(*att_it);
if (tagAttrib.attrib == "displace" && !tagAttrib.value.empty())
{ //Displace the sprite
CSize displacement;
// Parse the value
if (!GUI::ParseString(CStrW(tagAttrib.value), displacement))
LOGERROR(L"Error parsing 'displace' value for tag [ICON]");
else
SpriteCall.m_Area += displacement;
}
else if(tagAttrib.attrib == "tooltip")
{
SpriteCall.m_Tooltip = CStrW(tagAttrib.value);
}
else if(tagAttrib.attrib == "tooltip_style")
{
SpriteCall.m_TooltipStyle = CStrW(tagAttrib.value);
}
}
SpriteCall.m_Sprite = icon.m_SpriteName;
SpriteCall.m_CellID = icon.m_CellID;
// Add sprite call
Feedback.m_SpriteCalls.push_back(SpriteCall);
// Finalize text call
TextCall.m_pSpriteCall = &Feedback.m_SpriteCalls.back();
// Add text call
Feedback.m_TextCalls.push_back(TextCall);
}
else if (pObject)
{
LOGERROR(L"Trying to use an [icon]-tag with an undefined icon (\"%hs\").", itTextChunk->m_Tags[0].m_TagValue.c_str());
}
}
}
else
if (_to > _from && !Feedback.m_NewLine)
{
SGUIText::STextCall TextCall;
// Set defaults
TextCall.m_Font = DefaultFont;
TextCall.m_UseCustomColor = false;
// Extract substd::string from RawString.
TextCall.m_String = GetRawString().substr(_from, _to-_from);
// Go through tags and apply changes.
std::vector::const_iterator it2;
for (it2 = itTextChunk->m_Tags.begin(); it2 != itTextChunk->m_Tags.end(); ++it2)
{
if (it2->m_TagType == CGUIString::TextChunk::Tag::TAG_COLOR)
{
// Set custom color
TextCall.m_UseCustomColor = true;
// Try parsing the color std::string
if (!GUI::ParseString(CStrW(it2->m_TagValue), TextCall.m_Color))
{
if (pObject)
LOGERROR(L"Error parsing the value of a [color]-tag in GUI text when reading object \"%hs\".", pObject->GetPresentableName().c_str());
}
}
else
if (it2->m_TagType == CGUIString::TextChunk::Tag::TAG_FONT)
{
// TODO Gee: (2004-08-15) Check if Font exists?
TextCall.m_Font = CStrW(it2->m_TagValue);
}
}
// Calculate the size of the font
CSize size;
int cx, cy;
CFont font (TextCall.m_Font);
font.CalculateStringSize(TextCall.m_String, cx, cy);
// For anything other than the first line, the line spacing
// needs to be considered rather than just the height of the text
if (! FirstLine)
cy = font.GetLineSpacing();
size.cx = (float)cx;
size.cy = (float)cy;
// Append width, and make maximum height the height.
Feedback.m_Size.cx += size.cx;
Feedback.m_Size.cy = std::max(Feedback.m_Size.cy, size.cy);
// These are also needed later
TextCall.m_Size = size;
if (! TextCall.m_String.empty())
{
if (TextCall.m_String[0] == '\n')
{
Feedback.m_NewLine = true;
}
}
// Add text-chunk
Feedback.m_TextCalls.push_back(TextCall);
}
}
}
bool CGUIString::TextChunk::Tag::SetTagType(const CStr& tagtype)
{
CStr _tagtype = tagtype.UpperCase();
if (_tagtype == CStr("B"))
{
m_TagType = TAG_B;
return true;
}
else
if (_tagtype == CStr("I"))
{
m_TagType = TAG_I;
return true;
}
else
if (_tagtype == CStr("COLOR"))
{
m_TagType = TAG_COLOR;
return true;
}
else
if (_tagtype == CStr("FONT"))
{
m_TagType = TAG_FONT;
return true;
}
else
if (_tagtype == CStr("ICON"))
{
m_TagType = TAG_ICON;
return true;
}
else
if (_tagtype == CStr("IMGLEFT"))
{
m_TagType = TAG_IMGLEFT;
return true;
}
else
if (_tagtype == CStr("IMGRIGHT"))
{
m_TagType = TAG_IMGRIGHT;
return true;
}
return false;
}
void CGUIString::SetValue(const CStrW& str)
{
+ m_OriginalString = str;
+
// clear
m_TextChunks.clear();
m_Words.clear();
m_RawString = CStrW();
// Setup parser
// TODO Gee: (2004-08-16) Create and store this parser object somewhere to save loading time.
// TODO PT: Extended CParserCache so that the above is possible (since it currently only
// likes one-task parsers)
CParser Parser;
// I've added the option of an additional parameter. Only used for icons when writing this.
Parser.InputTaskType("start", "$ident[_=_$value_[$ident_=_$value_]]");
Parser.InputTaskType("end", "/$ident");
long position = 0;
long from=0; // the position in the raw std::string where the last tag ended
long from_nonraw=0; // like from only in position of the REAL std::string, with tags.
long curpos = 0;
// Current Text Chunk
CGUIString::TextChunk CurrentTextChunk;
for (;;position = curpos+1)
{
// Find next TagStart character
curpos = str.Find(position, TagStart);
if (curpos == -1)
{
m_RawString += str.substr(position);
if (from != (long)m_RawString.length())
{
CurrentTextChunk.m_From = from;
CurrentTextChunk.m_To = (int)m_RawString.length();
m_TextChunks.push_back(CurrentTextChunk);
}
break;
}
else
{
// First check if there is another TagStart before a TagEnd,
// in that case it's just a regular TagStart and we can continue.
long pos_left = str.Find(curpos+1, TagStart);
long pos_right = str.Find(curpos+1, TagEnd);
if (pos_right == -1)
{
m_RawString += str.substr(position, curpos-position+1);
continue;
}
else
if (pos_left != -1 && pos_left < pos_right)
{
m_RawString += str.substr(position, pos_left-position);
continue;
}
else
{
m_RawString += str.substr(position, curpos-position);
// Okay we've found a TagStart and TagEnd, positioned
// at pos and pos_right. Now let's extract the
// interior and try parsing.
CStr tagstr (str.substr(curpos+1, pos_right-curpos-1));
CParserLine Line;
Line.ParseString(Parser, (const char*)tagstr);
// Set to true if the tag is just text.
bool justtext = false;
if (Line.m_ParseOK)
{
if (Line.m_TaskTypeName == "start")
{
// The tag
TextChunk::Tag tag;
std::string Str_TagType;
Line.GetArgString(0, Str_TagType);
if (!tag.SetTagType(Str_TagType))
{
justtext = true;
}
else
{
// Check for possible value-std::strings
if (Line.GetArgCount() >= 2)
Line.GetArgString(1, tag.m_TagValue);
//Handle arbitrary number of additional parameters
size_t argn;
for(argn = 2; argn < Line.GetArgCount(); argn += 2)
{
TextChunk::Tag::TagAttribute a;
Line.GetArgString(argn, a.attrib);
Line.GetArgString(argn+1, a.value);
tag.m_TagAttributes.push_back(a);
}
// Finalize last
if (curpos != from_nonraw)
{
CurrentTextChunk.m_From = from;
CurrentTextChunk.m_To = from + curpos - from_nonraw;
m_TextChunks.push_back(CurrentTextChunk);
from = CurrentTextChunk.m_To;
}
from_nonraw = pos_right+1;
// Some tags does not have a closure, and should be
// stored without text. Like a in XML.
if (tag.m_TagType == TextChunk::Tag::TAG_IMGLEFT ||
tag.m_TagType == TextChunk::Tag::TAG_IMGRIGHT ||
tag.m_TagType == TextChunk::Tag::TAG_ICON)
{
// We need to use a fresh text chunk
// because 'tag' should be the *only* tag.
TextChunk FreshTextChunk;
// They does not work with the text system.
FreshTextChunk.m_From = from + pos_right+1 - from_nonraw;
FreshTextChunk.m_To = from + pos_right+1 - from_nonraw;
FreshTextChunk.m_Tags.push_back(tag);
m_TextChunks.push_back(FreshTextChunk);
}
else
{
// Add that tag, but first, erase previous occurences of the
// same tag.
std::vector::iterator it;
for (it = CurrentTextChunk.m_Tags.begin(); it != CurrentTextChunk.m_Tags.end(); ++it)
{
if (it->m_TagType == tag.m_TagType)
{
CurrentTextChunk.m_Tags.erase(it);
break;
}
}
// Add!
CurrentTextChunk.m_Tags.push_back(tag);
}
}
}
else
if (Line.m_TaskTypeName == "end")
{
// The tag
TextChunk::Tag tag;
std::string Str_TagType;
Line.GetArgString(0, Str_TagType);
if (!tag.SetTagType(Str_TagType))
{
justtext = true;
}
else
{
// Finalize the previous chunk
if (curpos != from_nonraw)
{
CurrentTextChunk.m_From = from;
CurrentTextChunk.m_To = from + curpos - from_nonraw;
m_TextChunks.push_back(CurrentTextChunk);
from = CurrentTextChunk.m_To;
}
from_nonraw = pos_right+1;
// Search for the tag, if it's not added, then
// pass it as plain text.
std::vector::iterator it;
for (it = CurrentTextChunk.m_Tags.begin(); it != CurrentTextChunk.m_Tags.end(); ++it)
{
if (it->m_TagType == tag.m_TagType)
{
CurrentTextChunk.m_Tags.erase(it);
break;
}
}
}
}
}
else justtext = true;
if (justtext)
{
// What was within the tags could not be interpreted
// so we'll assume it's just text.
m_RawString += str.substr(curpos, pos_right-curpos+1);
}
curpos = pos_right;
continue;
}
}
}
// Add a delimiter at start and at end, it helps when
// processing later, because we don't have make exceptions for
// those cases.
// We'll sort later.
m_Words.push_back(0);
m_Words.push_back((int)m_RawString.length());
// Space: ' '
for (position=0, curpos=0;;position = curpos+1)
{
// Find the next word-delimiter.
long dl = m_RawString.Find(position, ' ');
if (dl == -1)
break;
curpos = dl;
m_Words.push_back((int)dl+1);
}
// Dash: '-'
for (position=0, curpos=0;;position = curpos+1)
{
// Find the next word-delimiter.
long dl = m_RawString.Find(position, '-');
if (dl == -1)
break;
curpos = dl;
m_Words.push_back((int)dl+1);
}
// New Line: '\n'
for (position=0, curpos=0;;position = curpos+1)
{
// Find the next word-delimiter.
long dl = m_RawString.Find(position, '\n');
if (dl == -1)
break;
curpos = dl;
// Add before and
m_Words.push_back((int)dl);
m_Words.push_back((int)dl+1);
}
sort(m_Words.begin(), m_Words.end());
// Remove duplicates (only if larger than 2)
if (m_Words.size() > 2)
{
std::vector::iterator it;
int last_word = -1;
for (it = m_Words.begin(); it != m_Words.end(); )
{
if (last_word == *it)
{
it = m_Words.erase(it);
}
else
{
last_word = *it;
++it;
}
}
}
#if 0
for (int i=0; i<(int)m_Words.size(); ++i)
{
LOGMESSAGE(L"m_Words[%d] = %d", i, m_Words[i]);
}
for (int i=0; i<(int)m_TextChunks.size(); ++i)
{
LOGMESSAGE(L"m_TextChunk[%d] = [%d,%d]", i, m_TextChunks[i].m_From, m_TextChunks[i].m_To);
for (int j=0; j<(int)m_TextChunks[i].m_Tags.size(); ++j)
{
LOGMESSAGE(L"--Tag: %d \"%hs\"", (int)m_TextChunks[i].m_Tags[j].m_TagType, m_TextChunks[i].m_Tags[j].m_TagValue.c_str());
}
}
#endif
}
Index: ps/trunk/source/scriptinterface/ScriptInterface.h
===================================================================
--- ps/trunk/source/scriptinterface/ScriptInterface.h (revision 8924)
+++ ps/trunk/source/scriptinterface/ScriptInterface.h (revision 8925)
@@ -1,461 +1,461 @@
/* Copyright (C) 2011 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_SCRIPTINTERFACE
#define INCLUDED_SCRIPTINTERFACE
#include
#include
#include
#include "ScriptTypes.h"
#include "ScriptVal.h"
#include "js/jsapi.h"
#include "lib/file/vfs/vfs_path.h"
#include "ps/Profile.h"
#include "ps/utf16string.h"
class AutoGCRooter;
namespace boost { class rand48; }
// Set the maximum number of function arguments that can be handled
// (This should be as small as possible (for compiler efficiency),
// but as large as necessary for all wrapped functions)
#define SCRIPT_INTERFACE_MAX_ARGS 6
#ifdef NDEBUG
#define ENABLE_SCRIPT_PROFILING 0
#else
#define ENABLE_SCRIPT_PROFILING 1
#endif
struct ScriptInterface_impl;
class ScriptRuntime;
/**
* Abstraction around a SpiderMonkey JSContext.
*
* Thread-safety:
* - May be used in non-main threads.
* - Each ScriptInterface must be created, used, and destroyed, all in a single thread
* (it must never be shared between threads).
*/
class ScriptInterface
{
public:
/**
* Returns a runtime, which can used to initialise any number of
* ScriptInterfaces contexts. Values created in one context may be used
* in any other context from the same runtime (but not any other runtime).
* Each runtime should only ever be used on a single thread.
*/
static shared_ptr CreateRuntime();
/**
* Constructor.
* @param nativeScopeName Name of global object that functions (via RegisterFunction) will
* be placed into, as a scoping mechanism; typically "Engine"
*/
ScriptInterface(const char* nativeScopeName, const char* debugName, const shared_ptr& runtime);
~ScriptInterface();
/**
* Shut down the JS system to clean up memory. Must only be called when there
* are no ScriptInterfaces alive.
*/
static void ShutDown();
void SetCallbackData(void* cbdata);
static void* GetCallbackData(JSContext* cx);
JSContext* GetContext() const;
JSRuntime* GetRuntime() const;
void ReplaceNondeterministicFunctions(boost::rand48& rng);
/**
* Call a constructor function, equivalent to JS "new ctor(arg)".
* @return The new object; or JSVAL_VOID on failure, and logs an error message
*/
jsval CallConstructor(jsval ctor, jsval arg);
/**
* Create an object as with CallConstructor except don't actually execute the
* constructor function.
* @return The new object; or JSVAL_VOID on failure, and logs an error message
*/
jsval NewObjectFromConstructor(jsval ctor);
/**
* Call the named property on the given object, with void return type and 0 arguments
*/
bool CallFunctionVoid(jsval val, const char* name);
/**
* Call the named property on the given object, with void return type and 1 argument
*/
template
bool CallFunctionVoid(jsval val, const char* name, const T0& a0);
/**
* Call the named property on the given object, with void return type and 2 arguments
*/
template
bool CallFunctionVoid(jsval val, const char* name, const T0& a0, const T1& a1);
/**
* Call the named property on the given object, with void return type and 3 arguments
*/
template
bool CallFunctionVoid(jsval val, const char* name, const T0& a0, const T1& a1, const T2& a2);
/**
* Call the named property on the given object, with return type R and 0 arguments
*/
template
bool CallFunction(jsval val, const char* name, R& ret);
/**
* Call the named property on the given object, with return type R and 1 argument
*/
template
bool CallFunction(jsval val, const char* name, const T0& a0, R& ret);
/**
* Call the named property on the given object, with return type R and 2 arguments
*/
template
bool CallFunction(jsval val, const char* name, const T0& a0, const T1& a1, R& ret);
/**
* Call the named property on the given object, with return type R and 3 arguments
*/
template
bool CallFunction(jsval val, const char* name, const T0& a0, const T1& a1, const T2& a2, R& ret);
/**
* Call the named property on the given object, with return type R and 4 arguments
*/
template
bool CallFunction(jsval val, const char* name, const T0& a0, const T1& a1, const T2& a2, const T3& a3, R& ret);
jsval GetGlobalObject();
JSClass* GetGlobalClass();
/**
* Set the named property on the global object.
* If @p replace is true, an existing property will be overwritten; otherwise attempts
* to set an already-defined value will fail.
*/
template
bool SetGlobal(const char* name, const T& value, bool replace = false);
/**
* Set the named property on the given object.
* Optionally makes it {ReadOnly, DontDelete, DontEnum}.
*/
template
- bool SetProperty(jsval obj, const char* name, const T& value, bool constant, bool enumerate = true);
+ bool SetProperty(jsval obj, const char* name, const T& value, bool constant = false, bool enumerate = true);
/**
* Set the integer-named property on the given object.
* Optionally makes it {ReadOnly, DontDelete, DontEnum}.
*/
template
- bool SetPropertyInt(jsval obj, int name, const T& value, bool constant, bool enumerate = true);
+ bool SetPropertyInt(jsval obj, int name, const T& value, bool constant = false, bool enumerate = true);
template
bool GetProperty(jsval obj, const char* name, T& out);
bool HasProperty(jsval obj, const char* name);
bool EnumeratePropertyNamesWithPrefix(jsval obj, const char* prefix, std::vector& out);
bool SetPrototype(jsval obj, jsval proto);
bool FreezeObject(jsval obj, bool deep);
bool Eval(const char* code);
template bool Eval(const CHAR* code, T& out);
std::wstring ToString(jsval obj);
/**
* Parse a JSON string. Returns the undefined value on error.
*/
CScriptValRooted ParseJSON(const utf16string& string);
/**
* Parse a UTF-8-encoded JSON string. Returns the undefined value on error.
*/
CScriptValRooted ParseJSON(const std::string& string_utf8);
/**
* Read a JSON file. Returns the undefined value on error.
*/
CScriptValRooted ReadJSONFile(const VfsPath& path);
/**
* Stringify to a JSON string, UTF-8 encoded. Returns an empty string on error.
*/
std::string StringifyJSON(jsval obj, bool indent = true);
/**
* Report the given error message through the JS error reporting mechanism,
* and throw a JS exception. (Callers can check IsPendingException, and must
* return JS_FALSE in that case to propagate the exception.)
*/
void ReportError(const char* msg);
/**
* Load and execute the given script in a new function scope.
* @param filename Name for debugging purposes (not used to load the file)
* @param code JS code to execute
* @return true on successful compilation and execution; false otherwise
*/
bool LoadScript(const std::wstring& filename, const std::wstring& code);
/**
* Load and execute the given script in the global scope.
* @return true on successful compilation and execution; false otherwise
*/
bool LoadGlobalScriptFile(const VfsPath& path);
/**
* Construct a new value (usable in this ScriptInterface's context) by cloning
* a value from a different context.
* Complex values (functions, XML, etc) won't be cloned correctly, but basic
* types and cyclic references should be fine.
*/
jsval CloneValueFromOtherContext(ScriptInterface& otherContext, jsval val);
/**
* Convert a jsval to a C++ type. (This might trigger GC.)
*/
template static bool FromJSVal(JSContext* cx, jsval val, T& ret);
/**
* Convert a C++ type to a jsval. (This might trigger GC. The return
* value must be rooted if you don't want it to be collected.)
*/
template static jsval ToJSVal(JSContext* cx, T const& val);
AutoGCRooter* ReplaceAutoGCRooter(AutoGCRooter* rooter);
/**
* Dump some memory heap debugging information to stderr.
*/
void DumpHeap();
void MaybeGC();
/**
* Structured clones are a way to serialize 'simple' JS values into a buffer
* that can safely be passed between contexts and runtimes and threads.
* A StructuredClone can be stored and read multiple times if desired.
* We wrap them in shared_ptr so memory management is automatic and
* thread-safe.
*/
class StructuredClone
{
NONCOPYABLE(StructuredClone);
public:
StructuredClone();
~StructuredClone();
JSContext* m_Context;
uint64* m_Data;
size_t m_Size;
};
shared_ptr WriteStructuredClone(jsval v);
jsval ReadStructuredClone(const shared_ptr& ptr);
private:
bool CallFunction_(jsval val, const char* name, size_t argc, jsval* argv, jsval& ret);
bool Eval_(const char* code, jsval& ret);
bool Eval_(const wchar_t* code, jsval& ret);
bool SetGlobal_(const char* name, jsval value, bool replace);
bool SetProperty_(jsval obj, const char* name, jsval value, bool readonly, bool enumerate);
bool SetPropertyInt_(jsval obj, int name, jsval value, bool readonly, bool enumerate);
bool GetProperty_(jsval obj, const char* name, jsval& value);
static bool IsExceptionPending(JSContext* cx);
static JSClass* GetClass(JSContext* cx, JSObject* obj);
static void* GetPrivate(JSContext* cx, JSObject* obj);
void Register(const char* name, JSNative fptr, size_t nargs);
std::auto_ptr m;
// The nasty macro/template bits are split into a separate file so you don't have to look at them
public:
#include "NativeWrapperDecls.h"
// This declares:
//
// template
// void RegisterFunction(const char* functionName);
//
// template
// static JSNative call;
//
// template
// static JSNative callMethod;
//
// template
// static size_t nargs();
};
// Implement those declared functions
#include "NativeWrapperDefns.h"
template
bool ScriptInterface::CallFunction(jsval val, const char* name, R& ret)
{
jsval jsRet;
bool ok = CallFunction_(val, name, 0, NULL, jsRet);
if (!ok)
return false;
return FromJSVal(GetContext(), jsRet, ret);
}
template
bool ScriptInterface::CallFunctionVoid(jsval val, const char* name, const T0& a0)
{
jsval jsRet;
jsval argv[1];
argv[0] = ToJSVal(GetContext(), a0);
return CallFunction_(val, name, 1, argv, jsRet);
}
template
bool ScriptInterface::CallFunctionVoid(jsval val, const char* name, const T0& a0, const T1& a1)
{
jsval jsRet;
jsval argv[2];
argv[0] = ToJSVal(GetContext(), a0);
argv[1] = ToJSVal(GetContext(), a1);
return CallFunction_(val, name, 2, argv, jsRet);
}
template
bool ScriptInterface::CallFunctionVoid(jsval val, const char* name, const T0& a0, const T1& a1, const T2& a2)
{
jsval jsRet;
jsval argv[3];
argv[0] = ToJSVal(GetContext(), a0);
argv[1] = ToJSVal(GetContext(), a1);
argv[2] = ToJSVal(GetContext(), a2);
return CallFunction_(val, name, 3, argv, jsRet);
}
template
bool ScriptInterface::CallFunction(jsval val, const char* name, const T0& a0, R& ret)
{
jsval jsRet;
jsval argv[1];
argv[0] = ToJSVal(GetContext(), a0);
bool ok = CallFunction_(val, name, 1, argv, jsRet);
if (!ok)
return false;
return FromJSVal(GetContext(), jsRet, ret);
}
template
bool ScriptInterface::CallFunction(jsval val, const char* name, const T0& a0, const T1& a1, R& ret)
{
jsval jsRet;
jsval argv[2];
argv[0] = ToJSVal(GetContext(), a0);
argv[1] = ToJSVal(GetContext(), a1);
bool ok = CallFunction_(val, name, 2, argv, jsRet);
if (!ok)
return false;
return FromJSVal(GetContext(), jsRet, ret);
}
template
bool ScriptInterface::CallFunction(jsval val, const char* name, const T0& a0, const T1& a1, const T2& a2, R& ret)
{
jsval jsRet;
jsval argv[3];
argv[0] = ToJSVal(GetContext(), a0);
argv[1] = ToJSVal(GetContext(), a1);
argv[2] = ToJSVal(GetContext(), a2);
bool ok = CallFunction_(val, name, 3, argv, jsRet);
if (!ok)
return false;
return FromJSVal(GetContext(), jsRet, ret);
}
template
bool ScriptInterface::CallFunction(jsval val, const char* name, const T0& a0, const T1& a1, const T2& a2, const T3& a3, R& ret)
{
jsval jsRet;
jsval argv[4];
argv[0] = ToJSVal(GetContext(), a0);
argv[1] = ToJSVal(GetContext(), a1);
argv[2] = ToJSVal(GetContext(), a2);
argv[3] = ToJSVal(GetContext(), a3);
bool ok = CallFunction_(val, name, 4, argv, jsRet);
if (!ok)
return false;
return FromJSVal(GetContext(), jsRet, ret);
}
template
bool ScriptInterface::SetGlobal(const char* name, const T& value, bool replace)
{
return SetGlobal_(name, ToJSVal(GetContext(), value), replace);
}
template
bool ScriptInterface::SetProperty(jsval obj, const char* name, const T& value, bool readonly, bool enumerate)
{
return SetProperty_(obj, name, ToJSVal(GetContext(), value), readonly, enumerate);
}
template
bool ScriptInterface::SetPropertyInt(jsval obj, int name, const T& value, bool readonly, bool enumerate)
{
return SetPropertyInt_(obj, name, ToJSVal(GetContext(), value), readonly, enumerate);
}
template
bool ScriptInterface::GetProperty(jsval obj, const char* name, T& out)
{
jsval val;
if (! GetProperty_(obj, name, val))
return false;
return FromJSVal(GetContext(), val, out);
}
template
bool ScriptInterface::Eval(const CHAR* code, T& ret)
{
jsval rval;
if (! Eval_(code, rval))
return false;
return FromJSVal(GetContext(), rval, ret);
}
#endif // INCLUDED_SCRIPTINTERFACE
Index: ps/trunk/source/main.cpp
===================================================================
--- ps/trunk/source/main.cpp (revision 8924)
+++ ps/trunk/source/main.cpp (revision 8925)
@@ -1,506 +1,509 @@
/* Copyright (C) 2010 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 .
*/
/*
This module drives the game when running without Atlas (our integrated
map editor). It receives input and OS messages via SDL and feeds them
into the input dispatcher, where they are passed on to the game GUI and
simulation.
It also contains main(), which either runs the above controller or
that of Atlas depending on commandline parameters.
*/
// not for any PCH effort, but instead for the (common) definitions
// included there.
#define MINIMAL_PCH 2
#include "lib/precompiled.h"
#include "lib/debug.h"
#include "lib/lib_errors.h"
#include "lib/secure_crt.h"
#include "lib/frequency_filter.h"
#include "lib/input.h"
#include "lib/ogl.h"
#include "lib/timer.h"
#include "lib/utf8.h"
#include "lib/external_libraries/sdl.h"
#include "lib/res/sound/snd_mgr.h"
#include "ps/ArchiveBuilder.h"
#include "ps/CConsole.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/Globals.h"
#include "ps/Hotkey.h"
#include "ps/Loader.h"
#include "ps/Profile.h"
#include "ps/Pyrogenesis.h"
#include "ps/Replay.h"
+#include "ps/UserReport.h"
#include "ps/Util.h"
#include "ps/VideoMode.h"
#include "ps/GameSetup/GameSetup.h"
#include "ps/GameSetup/Atlas.h"
#include "ps/GameSetup/Config.h"
#include "ps/GameSetup/CmdLineArgs.h"
#include "ps/GameSetup/Paths.h"
#include "ps/XML/Xeromyces.h"
#include "network/NetClient.h"
#include "network/NetServer.h"
#include "network/NetSession.h"
#include "graphics/Camera.h"
#include "graphics/GameView.h"
#include "graphics/TextureManager.h"
#include "gui/GUIManager.h"
#include "renderer/Renderer.h"
#include "scripting/ScriptingHost.h"
#include "simulation2/Simulation2.h"
extern bool g_GameRestarted;
void kill_mainloop();
// to avoid redundant and/or recursive resizing, we save the new
// size after VIDEORESIZE messages and only update the video mode
// once per frame.
// these values are the latest resize message, and reset to 0 once we've
// updated the video mode
static int g_ResizedW;
static int g_ResizedH;
// main app message handler
static InReaction MainInputHandler(const SDL_Event_* ev)
{
switch(ev->ev.type)
{
case SDL_ACTIVEEVENT:
if (ev->ev.active.state == SDL_APPMOUSEFOCUS)
{
// Tell renderer not to render cursor if mouse focus is lost
// this restores system cursor, until/if focus is regained
if (!ev->ev.active.gain)
RenderCursor(false);
else
RenderCursor(true);
}
break;
case SDL_QUIT:
kill_mainloop();
break;
case SDL_VIDEORESIZE:
g_ResizedW = ev->ev.resize.w;
g_ResizedH = ev->ev.resize.h;
break;
case SDL_HOTKEYDOWN:
std::string hotkey = static_cast(ev->ev.user.data1);
if (hotkey == "exit")
{
kill_mainloop();
return IN_HANDLED;
}
else if (hotkey == "screenshot")
{
WriteScreenshot(L".png");
return IN_HANDLED;
}
else if (hotkey == "bigscreenshot")
{
WriteBigScreenshot(L".bmp", 10);
return IN_HANDLED;
}
else if (hotkey == "togglefullscreen")
{
g_VideoMode.ToggleFullscreen();
return IN_HANDLED;
}
break;
}
return IN_PASS;
}
// dispatch all pending events to the various receivers.
static void PumpEvents()
{
PROFILE( "dispatch events" );
in_dispatch_recorded_events();
SDL_Event_ ev;
while(SDL_PollEvent(&ev.ev))
in_dispatch_event(&ev);
}
// return indication of whether archive is currently being built; this is
// used to prevent reloading during that time (see call site).
static bool ProgressiveBuildArchive()
{
ONCE(g_GUI->SendEventToAll("archivebuildercomplete"));
return false;
#if 0
int ret = vfs_opt_auto_build("../logs/trace.txt", "mods/official/official%02d.zip", "mods/official/mini%02d.zip");
if(ret == INFO::ALL_COMPLETE)
{
// nothing to do; will return false below
}
else if(ret < 0)
DEBUG_DISPLAY_ERROR(L"Archive build failed");
else if(ret == INFO::OK)
g_GUI.SendEventToAll("archivebuildercomplete");
// in progress
else
{
int percent = (int)ret;
g_ScriptingHost.SetGlobal("g_ArchiveBuilderProgress", INT_TO_JSVAL(percent));
g_GUI.SendEventToAll("archivebuilderprogress");
return true;
}
return false;
#endif
}
static int ProgressiveLoad()
{
wchar_t description[100];
int progress_percent;
LibError ret = LDR_ProgressiveLoad(10e-3, description, ARRAY_SIZE(description), &progress_percent);
switch(ret)
{
// no load active => no-op (skip code below)
case INFO::OK:
return 0;
// current task didn't complete. we only care about this insofar as the
// load process is therefore not yet finished.
case ERR::TIMED_OUT:
break;
// just finished loading
case INFO::ALL_COMPLETE:
g_Game->ReallyStartGame();
wcscpy_s(description, ARRAY_SIZE(description), L"Game is starting..");
// LDR_ProgressiveLoad returns L""; set to valid text to
// avoid problems in converting to JSString
break;
// error!
default:
CHECK_ERR(ret);
// can't do this above due to legit ERR::TIMED_OUT
break;
}
GUI_DisplayLoadProgress(progress_percent, description);
return 0;
}
static void RendererIncrementalLoad()
{
const double maxTime = 0.1f;
double startTime = timer_Time();
bool more;
do {
more = g_Renderer.GetTextureManager().MakeProgress();
}
while (more && timer_Time() - startTime < maxTime);
}
static bool quit = false; // break out of main loop
static void Frame()
{
ogl_WarnIfError();
// get elapsed time
const double time = timer_Time();
g_frequencyFilter->Update(time);
// .. old method - "exact" but contains jumps
#if 0
static double last_time;
const double time = timer_Time();
const float TimeSinceLastFrame = (float)(time-last_time);
last_time = time;
ONCE(return); // first call: set last_time and return
// .. new method - filtered and more smooth, but errors may accumulate
#else
const float TimeSinceLastFrame = 1.0 / g_frequencyFilter->SmoothedFrequency();
#endif
debug_assert(TimeSinceLastFrame > 0.0f);
// decide if update/render is necessary
bool need_render = !g_app_minimized;
bool need_update = true;
// If we are not running a multiplayer game, disable updates when the game is
// minimized or out of focus and relinquish the CPU a bit, in order to make
// debugging easier.
if( !g_NetClient && !g_app_has_focus )
{
need_update = false;
// don't use SDL_WaitEvent: don't want the main loop to freeze until app focus is restored
SDL_Delay(10);
}
// TODO: throttling: limit update and render frequency to the minimum.
// this is mostly relevant for "inactive" state, so that other windows
// get enough CPU time, but it's always nice for power+thermal management.
bool is_building_archive; // must come before PROFILE_START's {
PROFILE_START("build archive");
is_building_archive = ProgressiveBuildArchive();
PROFILE_END( "build archive");
// this scans for changed files/directories and reloads them, thus
// allowing hotloading (changes are immediately assimilated in-game).
// must not be done during archive building because it changes the
// archive file each iteration, but keeps it locked; reloading
// would trigger a warning because the file can't be opened.
if(!is_building_archive)
{
PROFILE_START("reload changed files");
ReloadChangedFiles();
PROFILE_END( "reload changed files");
}
PROFILE_START("progressive load");
ProgressiveLoad();
PROFILE_END("progressive load");
PROFILE_START("renderer incremental load");
RendererIncrementalLoad();
PROFILE_END("renderer incremental load");
PROFILE_START("input");
PumpEvents();
PROFILE_END("input");
// if the user quit by closing the window, the GL context will be broken and
// may crash when we call Render() on some drivers, so leave this loop
// before rendering
if (quit)
return;
// respond to pumped resize events
if (g_ResizedW || g_ResizedH)
{
g_VideoMode.ResizeWindow(g_ResizedW, g_ResizedH);
g_ResizedW = g_ResizedH = 0;
}
PROFILE_START("network poll");
if (g_NetClient)
g_NetClient->Poll();
PROFILE_END("network poll");
ogl_WarnIfError();
PROFILE_START("gui tick");
g_GUI->TickObjects();
PROFILE_END("gui tick");
ogl_WarnIfError();
PROFILE_START( "game logic" );
if (g_Game && g_Game->IsGameStarted() && need_update)
{
PROFILE_START( "simulation update" );
g_Game->Update(TimeSinceLastFrame);
PROFILE_END( "simulation update" );
PROFILE_START( "camera update" );
g_Game->GetView()->Update(float(TimeSinceLastFrame));
PROFILE_END( "camera update" );
PROFILE_START( "sound update" );
CCamera* camera = g_Game->GetView()->GetCamera();
CMatrix3D& orientation = camera->m_Orientation;
float* pos = &orientation._data[12];
float* dir = &orientation._data[8];
float* up = &orientation._data[4];
// HACK: otherwise sound effects are L/R flipped. No idea what else
// is going wrong, because the listener and camera are supposed to
// coincide in position and orientation.
float down[3] = { -up[0], -up[1], -up[2] };
if(snd_update(pos, dir, down) < 0)
debug_printf(L"snd_update failed\n");
PROFILE_END( "sound update" );
}
else
{
if(snd_update(0, 0, 0) < 0)
debug_printf(L"snd_update (pos=0 version) failed\n");
}
PROFILE_END( "game logic" );
// Immediately flush any messages produced by simulation code
PROFILE_START("network flush");
if (g_NetClient)
g_NetClient->Flush();
PROFILE_END("network flush");
+ g_UserReporter.Update();
+
PROFILE_START( "update console" );
g_Console->Update(TimeSinceLastFrame);
PROFILE_END( "update console" );
PROFILE_START("render");
ogl_WarnIfError();
if(need_render)
{
Render();
PROFILE_START( "swap buffers" );
SDL_GL_SwapBuffers();
PROFILE_END( "swap buffers" );
}
ogl_WarnIfError();
PROFILE_END("render");
g_Profiler.Frame();
g_GameRestarted = false;
}
static void MainControllerInit()
{
// add additional input handlers only needed by this controller:
// must be registered after gui_handler. Should mayhap even be last.
in_add_handler(MainInputHandler);
}
static void MainControllerShutdown()
{
}
// stop the main loop and trigger orderly shutdown. called from several
// places: the event handler (SDL_QUIT and hotkey) and JS exitProgram.
void kill_mainloop()
{
quit = true;
}
static bool restart_in_atlas = false;
// called by game code to indicate main() should restart in Atlas mode
// instead of terminating
void restart_mainloop_in_atlas()
{
quit = true;
restart_in_atlas = true;
}
// moved into a helper function to ensure args is destroyed before
// exit(), which may result in a memory leak.
static void RunGameOrAtlas(int argc, const char* argv[])
{
CmdLineArgs args(argc, argv);
// We need to initialise libxml2 in the main thread before
// any thread uses it. So initialise it here before we
// might run Atlas.
CXeromyces::Startup();
// run Atlas (if requested via args)
bool ran_atlas = ATLAS_RunIfOnCmdLine(args, false);
// Atlas handles the whole init/shutdown/etc sequence by itself;
// when we get here, it has exited and we're done.
if(ran_atlas)
return;
// run non-visual simulation replay if requested
if (args.Has("replay"))
{
snd_disable(true);
Paths paths(args);
g_VFS = CreateVfs(20 * MiB);
g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE);
g_VFS->Mount(L"", paths.RData()/L"mods/public", VFS_MOUNT_MUST_EXIST);
{
CReplayPlayer replay;
replay.Load(args.Get("replay"));
replay.Replay();
}
g_VFS.reset();
CXeromyces::Terminate();
return;
}
// run in archive-building mode if requested
if (args.Has("archivebuild"))
{
Paths paths(args);
fs::wpath mod = wstring_from_utf8(args.Get("archivebuild"));
fs::wpath zip;
if (args.Has("archivebuild-output"))
zip = wstring_from_utf8(args.Get("archivebuild-output"));
else
zip = mod.leaf()+L".zip";
CArchiveBuilder builder(mod, paths.Cache());
builder.Build(zip);
CXeromyces::Terminate();
return;
}
const double res = timer_Resolution();
g_frequencyFilter = CreateFrequencyFilter(res, 30.0);
// run the game
Init(args, 0);
InitGraphics(args, 0);
MainControllerInit();
while(!quit)
Frame();
Shutdown(0);
ScriptingHost::FinalShutdown(); // this can't go in Shutdown() because that could be called multiple times per process, so stick it here instead
MainControllerShutdown();
if (restart_in_atlas)
{
ATLAS_RunIfOnCmdLine(args, true);
return;
}
// Shut down libxml2 (done here to match the Startup call)
CXeromyces::Terminate();
}
int main(int argc, char* argv[])
{
EarlyInit(); // must come at beginning of main
RunGameOrAtlas(argc, const_cast(argv));
return EXIT_SUCCESS;
}
Index: ps/trunk/source/lib/external_libraries/curl.h
===================================================================
--- ps/trunk/source/lib/external_libraries/curl.h (revision 8924)
+++ ps/trunk/source/lib/external_libraries/curl.h (revision 8925)
@@ -1,48 +1,52 @@
/* Copyright (c) 2011 Wildfire Games
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* bring in libcurl header, with compatibility fixes
*/
#ifndef INCLUDED_CURL
#define INCLUDED_CURL
+#if OS_WIN
+
// curl.h wants to include winsock2.h which causes conflicts.
// provide some required definitions from winsock.h, then pretend
// we already included winsock.h
typedef uintptr_t SOCKET;
struct sockaddr
{
unsigned short sa_family;
char sa_data[14];
};
struct fd_set;
#define _WINSOCKAPI_ // winsock.h include guard
+#endif // OS_WIN
+
#include
#endif // #ifndef INCLUDED_CURL
Index: ps/trunk/binaries/data/mods/public/gui/pregame/styles.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/pregame/styles.xml (revision 8924)
+++ ps/trunk/binaries/data/mods/public/gui/pregame/styles.xml (revision 8925)
@@ -1,10 +1,21 @@
+
+
+
+
+
Index: ps/trunk/binaries/data/mods/public/gui/pregame/mainmenu.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/pregame/mainmenu.js (revision 8924)
+++ ps/trunk/binaries/data/mods/public/gui/pregame/mainmenu.js (revision 8925)
@@ -1,72 +1,138 @@
+var userReportEnabledText; // contains the original version with "$status" placeholder
+
function init()
{
global.curr_music = newRandomSound("music", "menu");
if (global.curr_music)
global.curr_music.loop();
+
+ userReportEnabledText = getGUIObjectByName("userReportEnabledText").caption;
+}
+
+function submitUserReportMessage()
+{
+ var input = getGUIObjectByName("userReportMessageInput");
+ var msg = input.caption;
+ if (msg.length)
+ Engine.SubmitUserReport("message", 1, msg);
+ input.caption = "";
+}
+
+function formatUserReportStatus(status)
+{
+ var d = status.split(/:/, 3);
+
+ if (d[0] == "disabled")
+ return "disabled";
+
+ if (d[0] == "connecting")
+ return "connecting to server";
+
+ if (d[0] == "sending")
+ {
+ var done = d[1];
+ return "uploading (" + Math.floor(100*done) + "%)";
+ }
+
+ if (d[0] == "completed")
+ {
+ var httpCode = d[1];
+ if (httpCode == 200)
+ return "upload succeeded";
+ else
+ return "upload failed (" + httpCode + ")";
+ }
+
+ if (d[0] == "failed")
+ {
+ var errCode = d[1];
+ var errMessage = d[2];
+ return "upload failed (" + errMessage + ")";
+ }
+
+ return "unknown";
+}
+
+function onTick()
+{
+ if (Engine.IsUserReportEnabled())
+ {
+ getGUIObjectByName("userReportDisabled").hidden = true;
+ getGUIObjectByName("userReportEnabled").hidden = false;
+
+ getGUIObjectByName("userReportEnabledText").caption =
+ userReportEnabledText.replace(/\$status/,
+ formatUserReportStatus(Engine.GetUserReportStatus()));
+ }
+ else
+ {
+ getGUIObjectByName("userReportDisabled").hidden = false;
+ getGUIObjectByName("userReportEnabled").hidden = true;
+ }
}
// Helper function that enables the dark background mask, then reveals a given subwindow object.
function openMainMenuSubWindow (windowName)
{
guiUnHide("pgSubWindow");
guiUnHide(windowName);
}
// Helper function that disables the dark background mask, then hides a given subwindow object.
function closeMainMenuSubWindow (windowName)
{
guiHide("pgSubWindow");
guiHide(windowName);
}
// Switch to a given options tab window.
function openOptionsTab(tabName)
{
// Hide the other tabs.
for (i = 1; i <= 3; i++)
{
switch (i)
{
case 1:
var tmpName = "pgOptionsAudio";
break;
case 2:
var tmpName = "pgOptionsVideo";
break;
case 3:
var tmpName = "pgOptionsGame";
break;
default:
break;
}
if (tmpName != tabName)
{
getGUIObjectByName (tmpName + "Window").hidden = true;
getGUIObjectByName (tmpName + "Button").enabled = true;
}
}
// Make given tab visible.
getGUIObjectByName (tabName + "Window").hidden = false;
getGUIObjectByName (tabName + "Button").enabled = false;
}
// Move the credits up the screen.
function updateCredits()
{
// If there are still credit lines to remove, remove them.
if (getNumItems("pgCredits") > 0)
removeItem ("pgCredits", 0);
else
{
// When we've run out of credit,
// Stop the increment timer if it's still active.
cancelInterval();
// Close the credits screen and return.
closeMainMenuSubWindow ("pgCredits");
guiUnHide ("pg");
}
}
Index: ps/trunk/binaries/data/mods/public/gui/pregame/mainmenu.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/pregame/mainmenu.xml (revision 8924)
+++ ps/trunk/binaries/data/mods/public/gui/pregame/mainmenu.xml (revision 8925)
@@ -1,1225 +1,1311 @@
tooltipText
-
+
Manual
Website
IRC
Quit
+
+
+
+
+ [font="serif-bold-16"]Help improve 0 A.D.![/font]
+
+You can automatically send us anonymous feedback that will help us to improve performance and compatibility and to fix bugs.
+
+
+ [font="serif-bold-16"]Enable feedback
+ Engine.SetUserReportEnabled(true);
+
+
+ Technical details
+ Engine.PushGuiPage("page_manual.xml", { "page": "userreport" });
+
+
+
+
+ [font="serif-bold-16"]Thank you for helping improve 0 A.D.![/font]
+
+Anonymous feedback is currently enabled.
+Status: $status.
+
+ If you want to send a message to the developers, you can enter one here:
+
+
+
+
+
+ Send
+ submitUserReportMessage();
+
+
+
+ Disable feedback
+ Engine.SetUserReportEnabled(false);
+
+
+ Technical details
+ Engine.PushGuiPage("page_manual.xml", { "page": "userreport" });
+
+
+
Campaigns
Options
Mute All
Music Volume
0.0)
guiModifyCaption ("pgOptionsAudioMusicGain", -.1, 1);
global.curr_music.setGain (getGUIObjectByName ("pgOptionsAudioMusicGain").caption);
g_ConfigDB.system["sound.mastergain"] = getGUIObjectByName ("pgOptionsAudioMusicGain").caption;
]]>Mute
Sound Volume
0.0)
guiModifyCaption ("pgOptionsAudioSoundGain", -.1, 1);
]]>Mute
Ambient Volume
0.0)
guiModifyCaption ("pgOptionsAudioAmbientGain", -.1, 1);
]]>Mute
Voice Volume
0.0)
guiModifyCaption ("pgOptionsAudioVoiceGain", -.1, 1);
]]>Mute
Resolution
Model Detail
Texture Detail
Animation Detail
Effects Detail
Weather Detail
Water Detail
Terrain Detail
Shadow Detail
Show Blood
System Info
Mouse Settings
Reverse Buttons
Audio
Video
Game
OK
Cancel
History
Index: ps/trunk/binaries/data/mods/public/gui/pregame/sprites.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/pregame/sprites.xml (revision 8924)
+++ ps/trunk/binaries/data/mods/public/gui/pregame/sprites.xml (revision 8925)
@@ -1,177 +1,187 @@
+
+
+
+
+
Index: ps/trunk/binaries/data/mods/public/gui/manual/manual.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/manual/manual.js (revision 8924)
+++ ps/trunk/binaries/data/mods/public/gui/manual/manual.js (revision 8925)
@@ -1,4 +1,4 @@
-function init()
+function init(data)
{
- getGUIObjectByName("mainText").caption = readFile("gui/manual/intro.txt");
+ getGUIObjectByName("mainText").caption = readFile("gui/manual/" + data.page + ".txt");
}
Index: ps/trunk/binaries/data/mods/public/gui/manual/userreport.txt
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/manual/userreport.txt (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/manual/userreport.txt (revision 8925)
@@ -0,0 +1,12 @@
+As a free, open source game, we don't have the resources to test on a wide range of systems, but we want to provide the best quality experience to as many players as possible. When you enable automatic feedback, we'll receive data to help us understand the hardware we should focus on supporting, and to identify performance problems we should fix.
+
+The following data will be sent to our server:
+
+• A random user ID (stored in %APPDATA%\0ad\config\user.cfg on Windows, ~/.config/0ad/user.cfg on Unix), to let us detect repeated reports from the same user.
+• Game version number and basic build settings (optimisation mode, CPU architecture).
+• Hardware details: OS version, graphics driver version, OpenGL capabilities, screen size, CPU type, RAM.
+• Performance details: a snapshot of timing data a few seconds after you start a match or change graphics settings.
+
+The data will only be a few kilobytes each time you run the game, so bandwidth usage is minimal.
+
+We will store the submitted data on our server, and may publish statistics or non-user-identifiable details to help other game developers with similar questions. We will store the IP address that submitted the data, to help detect abuse of the service, but will not publish it.
Property changes on: ps/trunk/binaries/data/mods/public/gui/manual/userreport.txt
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/manual/intro.txt
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/manual/intro.txt (revision 8924)
+++ ps/trunk/binaries/data/mods/public/gui/manual/intro.txt (revision 8925)
@@ -1,93 +1,91 @@
[font="serif-bold-18"]0 A.D. in-game manual
[font="serif-14"]
Thank you for installing 0 A.D.! This page will give a brief overview of the features available in this incomplete, under-development, alpha version of the game.
-[font="serif-12"]Use the "X" button in the top-right corner of this window to close it. Or click the "Close" button below.
-
[font="serif-bold-16"]Graphics settings
[font="serif-14"]You can switch between fullscreen and windowed mode by pressing Alt+Enter. In windowed mode, you can resize the window. If the game runs too slowly, you can change some settings in the configuration file: look for binaries/data/config/default.cfg in the location where the game is installed, which gives instructions for editing, and try disabling the "fancywater" and "shadows" options.
[font="serif-bold-16"]Playing the game
[font="serif-14"]The controls and gameplay should be familiar to players of traditional RTS games. There are currently a lot of missing features and poorly-balanced stats – you will probably have to wait until a beta release for it to work well.
Basic controls:
• Left-click to select units.
• Left-click-and-drag to select groups of units.
• Right-click to order units to the target.
• Arrow keys or WASD keys to move the camera.
• Ctrl + arrow keys, or shift + mouse wheel, to rotate the camera.
• Mouse wheel, or "+" and "-" keys, to zoom.
[font="serif-bold-16"]Modes
[font="serif-14"]The main menu gives access to two game modes:
• [font="serif-bold-14"]Single-player[font="serif-14"] — the game does not have any AI opponents yet, but you can use this to test the game's mechanics.
• [font="serif-bold-14"]Multiplayer[font="serif-14"] — play against human opponents over the internet.
To set up a multiplayer game, one player must select the "Host game" option. The game uses UDP port 20595, so the host must configure their NAT/firewall/etc to allow this. Other players can then select "Join game" and enter the host's IP address.
[font="serif-bold-16"]Game setup
[font="serif-14"]In a multiplayer game, only the host can alter the game setup options.
First, select a map to play on. The "techdemo" maps are designed for testing particular gameplay features and are probably not generally useful.
Next, you can use the drop-down lists in the player list to select who controls which player in the map. This always shows 8 players – anyone who is assigned to a player that doesn't exist in the map, or is not assigned to any player at all, will probably not have a fun time.
When you are ready to start, click the "Start game" button.
[font="serif-bold-16"]Hotkeys:
[font="serif-bold-14"]Always
[font="serif-14"]Alt+F4: Close the game, without confirmation
F9: Show/hide console
Shift + F: Show/hide frame counter (FPS)
F11: Enable/disable real-time performance profiler (toggles through the displays of information)
Shift+F11: Save current profiler data to "logs/profile.txt"
F2: Take screenshot (in .png format, saved to %appdata%/0ad\data\screenshots on Windows)
Shift+F2: Take huge screenshot (6400px*4800px, in .bmp format, saved to %appdata%/0ad\data\screenshots on Windows)
[font="serif-bold-14"]In Game
[font="serif-14"]Alt + Enter: Toggle between fullscreen and windowed
Double Left Click: Select all of your units of the same kind on the screen
Triple Left Click: Select all of your units of the same kind on the entire map
F10: Open/close menu
ESC: Close all dialogs (chat, menu)
Enter/return:Open/send chat
Pause: Pause/unpause the game
Delete: Delete currently selected unit/units/building/buildings
[font="serif-bold-14"]Modify mouse action
[font="serif-14"]Ctrl + Right Click on building: Garrison
Shift + Right Click: Queue the move/build/gather/etc order
Shift + Left click when training unit/s: Add units in batches of five
Shift + Left Click or Left Drag over unit on map: Add unit to selection
Ctrl + Left Click or Left Drag over unit on map: Remove unit from selection
Ctrl + Left Click on unit/group icon with multiple units selected: Deselect
[font="serif-bold-14"]Overlays
[font="serif-14"]G: Hide/show the GUI
Shift+D: Show/hide developer overlay (with developer options)
[font="serif-bold-14"]Camera manipulation
[font="serif-14"]W or [up]: Pan screen up
S or [down]: Pan screen down
A or [left]: Pan screen left
D or [right]: Pan screen right
Ctrl + W or [up]: Rotate camera to look upward
Ctrl + S or [down]: Rotate camera to look downward
Ctrl + A or [left]: Rotate camera clockwise around terrain
Ctrl + D or [right]: Rotate camera anticlockwise around terrain
Q: Rotate camera clockwise around terrain
E: Rotate camera anticlockwise around terrain
Shift + Mouse Wheel Rotate Up: Rotate camera clockwise around terrain
Shift + Mouse Wheel Rotate Down: Rotate camera anticlockwise around terrain
F: Follow the selected unit (move the camera to stop the camera from following the unit/s)
H: Reset camera zoom/orientation
+: Zoom in (keep pressed for continuous zoom)
-: Zoom out (keep pressed for continuous zoom)
Alt+W: Toggle through wireframe modes
Middle Mouse Button or / (ForwardSlash): Keep pressed and move the mouse to pan
[font="serif-bold-14"]During Building Placement
[font="serif-14"][: Rotate building 15 degrees counter-clockwise
]: Rotate building 15 degrees clockwise
\ No newline at end of file
Index: ps/trunk/binaries/data/mods/public/hwdetect/hwdetect.js
===================================================================
--- ps/trunk/binaries/data/mods/public/hwdetect/hwdetect.js (revision 8924)
+++ ps/trunk/binaries/data/mods/public/hwdetect/hwdetect.js (revision 8925)
@@ -1,135 +1,135 @@
/* Copyright (c) 2010 Wildfire Games
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
This script is for adjusting the game's default settings based on the
user's system configuration details.
The game engine itself does some detection of capabilities, so it will
enable certain graphical features only when they are supported.
This script is for the messier task of avoiding performance problems
and driver bugs based on experience of particular system configurations.
*/
function RunDetection(settings)
{
// This function should have no side effects, it should just
// set these output properties:
// List of warning strings to display to the user
// in an ugly GUI dialog box
var dialog_warnings = [];
// List of warning strings to log
var warnings = [];
var disable_audio = undefined;
// TODO: add some mechanism for setting config values
// (overriding default.cfg, but overridden by local.cfg)
// Extract all the settings we might use from the argument:
// (This is less error-prone than referring to "settings.foo" directly
// since typos will be caught)
// OS flags (0 or 1)
var os_unix = settings.os_unix;
var os_linux = settings.os_linux;
var os_macosx = settings.os_macosx;
var os_win = settings.os_win;
// Should avoid using these, since they're disabled in quickstart mode
var gfx_card = settings.gfx_card;
var gfx_drv_ver = settings.gfx_drv_ver;
var gfx_mem = settings.gfx_mem;
// Values from glGetString
- var gl_vendor = settings.gl_vendor;
- var gl_renderer = settings.gl_renderer;
- var gl_version = settings.gl_version;
- var gl_extensions = settings.gl_extensions.split(" "); // split on spaces
+ var GL_VENDOR = settings.GL_VENDOR;
+ var GL_RENDERER = settings.GL_RENDERER;
+ var GL_VERSION = settings.GL_VERSION;
+ var GL_EXTENSIONS = settings.GL_EXTENSIONS.split(" "); // split on spaces
var video_xres = settings.video_xres;
var video_yres = settings.video_yres;
var video_bpp = settings.video_bpp;
var uname_sysname = settings.uname_sysname;
var uname_release = settings.uname_release;
var uname_version = settings.uname_version;
var uname_machine = settings.uname_machine;
var cpu_identifier = settings.cpu_identifier;
var cpu_frequency = settings.cpu_frequency; // -1 if unknown
var ram_total = settings.ram_total; // megabytes
var ram_free = settings.ram_free; // megabytes
// NVIDIA 260.19.* UNIX drivers cause random crashes soon after startup.
// http://www.wildfiregames.com/forum/index.php?showtopic=13668
// Fixed in 260.19.21:
// "Fixed a race condition in OpenGL that could cause crashes with multithreaded applications."
- if (os_unix && gl_version.match(/NVIDIA 260\.19\.(0[0-9]|1[0-9]|20)$/))
+ if (os_unix && GL_VERSION.match(/NVIDIA 260\.19\.(0[0-9]|1[0-9]|20)$/))
{
dialog_warnings.push("You are using 260.19.* series NVIDIA drivers, which may crash the game. Please upgrade to 260.19.21 or later.");
}
// http://trac.wildfiregames.com/ticket/685
if (os_macosx)
{
warnings.push("Audio has been disabled, due to problems with OpenAL on OS X.");
disable_audio = true;
}
return {
"dialog_warnings": dialog_warnings,
"warnings": warnings,
"disable_audio": disable_audio,
};
}
global.RunHardwareDetection = function(settings)
{
//print(uneval(settings)+"\n");
var output = RunDetection(settings);
//print(uneval(output)+"\n");
for (var i = 0; i < output.warnings.length; ++i)
warn(output.warnings[i]);
if (output.dialog_warnings.length)
{
var msg = output.dialog_warnings.join("\n\n");
Engine.DisplayErrorDialog(msg);
}
if (output.disable_audio !== undefined)
Engine.SetDisableAudio(output.disable_audio);
};
Index: ps/trunk/binaries/data/config/default.cfg
===================================================================
--- ps/trunk/binaries/data/config/default.cfg (revision 8924)
+++ ps/trunk/binaries/data/config/default.cfg (revision 8925)
@@ -1,184 +1,183 @@
; 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 / OS X, create: *
; * ~/.config/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
; 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:
fancywater = true
shadows = true
vsync = false
nos3tc = false
noautomipmap = true
novbo = false
noframebufferobject = 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
; vertexshader Use vertex shaders for transform and lighting where possible
; Using 'fixed' instead of 'default' may work around some graphics-related problems,
; but will reduce performance when a modern graphics card is available.
renderpath = default
; Adjusts how OpenGL calculates mipmap level of detail. 0.0f is the default (blurry) value.
; Lower values sharpen/extend, and higher values blur/decrease. Clamped at -3.0 to 3.0.
; -1.0 to -1.5 recommended for good results.
lodbias = 0
-; Profile selection
-
-profile = default
+; Opt-in online user reporting system
+userreport.url = "http://feedback.wildfiregames.com/report/upload/v1/"
; Font mappings:
font.console = console
font.default = palatino12
font.misc = verdana16
; Colour of the sky (in "r g b" format)
skycolor = "0 0 0"
; GENERAL PREFERENCES:
sound.mastergain = 0.5
; Camera control settings
view.scroll.speed = 120.0
view.rotate.x.speed = 1.2
view.rotate.x.min = 20
view.rotate.x.max = 60
view.rotate.x.default = 30
view.rotate.y.speed = 2.0
view.rotate.y.speed.wheel = 0.45
view.rotate.y.default = 0.0
view.drag.speed = 0.5
view.zoom.speed = 256.0
view.zoom.speed.wheel = 32.0
view.zoom.min = 96.0
view.zoom.max = 256.0
view.zoom.default = 192.0
view.pos.smoothness = 0.1
view.zoom.smoothness = 0.4
view.rotate.x.smoothness = 0.5
view.rotate.y.smoothness = 0.3
; 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" ; Exit to desktop
hotkey.leave = Escape ; End current game or Exit
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 = "K" ; Toggle product/company watermark for official screenshots
hotkey.wireframe = "Alt+W" ; Toggle wireframe mode
; > CAMERA SETTINGS
hotkey.camera.reset = "H" ; 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, ForwardSlash ; 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
; > CONSOLE SETTINGS
hotkey.console.toggle = BackQuote, F9 ; Open/close console
hotkey.console.copy = "Ctrl+C" ; Copy from console to clipboard
hotkey.console.paste = "Ctrl+V" ; Paste clipboard to console
; > ENTITY SELECTION
hotkey.selection.add = Shift ; Add units to selection
hotkey.selection.remove = Ctrl ; Remove units from selection
hotkey.selection.group.0 = 0
hotkey.selection.group.1 = 1
hotkey.selection.group.2 = 2
hotkey.selection.group.3 = 3
hotkey.selection.group.4 = 4
hotkey.selection.group.5 = 5
hotkey.selection.group.6 = 6
hotkey.selection.group.7 = 7
hotkey.selection.group.8 = 8
hotkey.selection.group.9 = 9
hotkey.selection.group.add = Shift ; +group: Add units to group
hotkey.selection.group.save = Ctrl ; +group: Save units to group
hotkey.selection.group.snap = Alt ; +group: Check up on group
hotkey.selection.snap = Home ; Centre view on selection
; > SESSION CONTROLS
hotkey.session.kill = Delete ; Destroy selected units
hotkey.session.garrison = Ctrl ; Modifier to garrison when clicking on 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.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
hotkey.timewarp.rewind = Backspace
; > OVERLAY KEYS
hotkey.fps.toggle = "Shift+F" ; Toggle frame counter
hotkey.session.devcommands.toggle = "Shift+D" ; Toggle developer commands panel
hotkey.session.gui.toggle = "G" ; Toggle visibility of session GUI
hotkey.menu.toggle = "F10" ; Toggle in-game menu
; > HOTKEYS ONLY
hotkey.chat = Return ; Toggle chat window
; > PROFILER
hotkey.profile.toggle = "F11" ; Enable/disable real-time profiler
hotkey.profile.save = "Shift+F11" ; Save current profiler data to logs/profile.txt
; 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