Index: ps/trunk/build/premake/premake5.lua =================================================================== --- ps/trunk/build/premake/premake5.lua (revision 23846) +++ ps/trunk/build/premake/premake5.lua (revision 23847) @@ -1,1464 +1,1465 @@ newoption { trigger = "android", description = "Use non-working Android cross-compiling mode" } newoption { trigger = "atlas", description = "Include Atlas scenario editor projects" } newoption { trigger = "coverage", description = "Enable code coverage data collection (GCC only)" } newoption { trigger = "gles", description = "Use non-working OpenGL ES 2.0 mode" } newoption { trigger = "icc", description = "Use Intel C++ Compiler (Linux only; should use either \"--cc icc\" or --without-pch too, and then set CXX=icpc before calling make)" } newoption { trigger = "jenkins-tests", description = "Configure CxxTest to use the XmlPrinter runner which produces Jenkins-compatible output" } newoption { trigger = "minimal-flags", description = "Only set compiler/linker flags that are really needed. Has no effect on Windows builds" } newoption { trigger = "outpath", description = "Location for generated project files" } newoption { trigger = "with-system-mozjs45", description = "Search standard paths for libmozjs45, instead of using bundled copy" } newoption { trigger = "with-system-nvtt", description = "Search standard paths for nvidia-texture-tools library, instead of using bundled copy" } newoption { trigger = "without-audio", description = "Disable use of OpenAL/Ogg/Vorbis APIs" } newoption { trigger = "without-lobby", description = "Disable the use of gloox and the multiplayer lobby" } newoption { trigger = "without-miniupnpc", description = "Disable use of miniupnpc for port forwarding" } newoption { trigger = "without-nvtt", description = "Disable use of NVTT" } newoption { trigger = "without-pch", description = "Disable generation and usage of precompiled headers" } newoption { trigger = "without-tests", description = "Disable generation of test projects" } -- Linux/BSD specific options newoption { trigger = "prefer-local-libs", description = "Prefer locally built libs. Any local libraries used must also be listed within a file within /etc/ld.so.conf.d so the dynamic linker can find them at runtime." } -- OS X specific options newoption { trigger = "macosx-bundle", description = "Enable OSX bundle, the argument is the bundle identifier string (e.g. com.wildfiregames.0ad)" } newoption { trigger = "macosx-version-min", description = "Set minimum required version of the OS X API, the build will possibly fail if an older SDK is used, while newer API functions will be weakly linked (i.e. resolved at runtime)" } newoption { trigger = "sysroot", description = "Set compiler system root path, used for building against a non-system SDK. For example /usr/local becomes SYSROOT/user/local" } -- Windows specific options newoption { trigger = "build-shared-glooxwrapper", description = "Rebuild glooxwrapper DLL for Windows. Requires the same compiler version that gloox was built with" } newoption { trigger = "use-shared-glooxwrapper", description = "Use prebuilt glooxwrapper DLL for Windows" } newoption { trigger = "large-address-aware", description = "Make the executable large address aware. Do not use for development, in order to spot memory issues easily" } -- Install options newoption { trigger = "bindir", description = "Directory for executables (typically '/usr/games'); default is to be relocatable" } newoption { trigger = "datadir", description = "Directory for data files (typically '/usr/share/games/0ad'); default is ../data/ relative to executable" } newoption { trigger = "libdir", description = "Directory for libraries (typically '/usr/lib/games/0ad'); default is ./ relative to executable" } -- Root directory of project checkout relative to this .lua file rootdir = "../.." dofile("extern_libs5.lua") -- detect compiler for non-Windows if os.istarget("macosx") then cc = "clang" elseif os.istarget("linux") and _OPTIONS["icc"] then cc = "icc" elseif not os.istarget("windows") then cc = os.getenv("CC") if cc == nil or cc == "" then local hasgcc = os.execute("which gcc > .gccpath") local f = io.open(".gccpath", "r") local gccpath = f:read("*line") f:close() os.execute("rm .gccpath") if gccpath == nil then cc = "clang" else cc = "gcc" end end end -- detect CPU architecture (simplistic, currently only supports x86, amd64 and ARM) arch = "x86" if _OPTIONS["android"] then arch = "arm" elseif os.istarget("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" or arch == "amd64" then arch = "amd64" else os.execute(cc .. " -dumpmachine > .gccmachine.tmp") local f = io.open(".gccmachine.tmp", "r") local machine = f:read("*line") f:close() if string.find(machine, "x86_64") == 1 or string.find(machine, "amd64") == 1 then arch = "amd64" elseif string.find(machine, "i.86") == 1 then arch = "x86" elseif string.find(machine, "arm") == 1 then arch = "arm" elseif string.find(machine, "aarch64") == 1 then arch = "aarch64" else print("WARNING: Cannot determine architecture from GCC, assuming x86") end end end -- Test whether we need to link libexecinfo. -- This is mostly the case on musl systems, as well as on BSD systems : only glibc provides the -- backtrace symbols we require in the libc, for other libcs we use the libexecinfo library. local link_execinfo = false if os.istarget("bsd") then link_execinfo = true elseif os.istarget("linux") then local _, link_errorCode = os.outputof(cc .. " ./tests/execinfo.c -o /dev/null") if link_errorCode ~= 0 then link_execinfo = true end end -- Set up the Workspace workspace "pyrogenesis" targetdir(rootdir.."/binaries/system") libdirs(rootdir.."/binaries/system") if not _OPTIONS["outpath"] then error("You must specify the 'outpath' parameter") end location(_OPTIONS["outpath"]) configurations { "Release", "Debug" } source_root = rootdir.."/source/" -- default for most projects - overridden by local in others -- Rationale: projects 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. -- projects: engine static libs, main exe, atlas, atlas frontends, test. -------------------------------------------------------------------------------- -- project helper functions -------------------------------------------------------------------------------- function project_set_target(project_name) -- Note: On Windows, ".exe" is added on the end, on unices the name is used directly local obj_dir_prefix = _OPTIONS["outpath"].."/obj/"..project_name.."_" filter "Debug" objdir(obj_dir_prefix.."Debug") targetsuffix("_dbg") filter "Release" objdir(obj_dir_prefix.."Release") filter { } end function project_set_build_flags() editandcontinue "Off" if not _OPTIONS["minimal-flags"] then symbols "On" end if cc ~= "icc" and (os.istarget("windows") or not _OPTIONS["minimal-flags"]) then -- adds the -Wall compiler flag warnings "Extra" -- this causes far too many warnings/remarks on ICC end -- disable Windows debug heap, since it makes malloc/free hugely slower when -- running inside a debugger if os.istarget("windows") then debugenvs { "_NO_DEBUG_HEAP=1" } end filter "Debug" defines { "DEBUG" } filter "Release" if os.istarget("windows") or not _OPTIONS["minimal-flags"] then optimize "Speed" end defines { "NDEBUG", "CONFIG_FINAL=1" } filter { } if _OPTIONS["gles"] then defines { "CONFIG2_GLES=1" } end if _OPTIONS["without-audio"] then defines { "CONFIG2_AUDIO=0" } end if _OPTIONS["without-nvtt"] then defines { "CONFIG2_NVTT=0" } end if _OPTIONS["without-lobby"] then defines { "CONFIG2_LOBBY=0" } end if _OPTIONS["without-miniupnpc"] then defines { "CONFIG2_MINIUPNPC=0" } end -- required for the lowlevel library. must be set from all projects that use it, otherwise it assumes it is -- being used as a DLL (which is currently not the case in 0ad) defines { "LIB_STATIC_LINK" } -- various platform-specific build flags if os.istarget("windows") then flags { "MultiProcessorCompile" } -- use native wchar_t type (not typedef to unsigned short) nativewchar "on" else -- *nix -- TODO, FIXME: This check is incorrect because it means that some additional flags will be added inside the "else" branch if the -- compiler is ICC and minimal-flags is specified (ticket: #2994) if cc == "icc" and not _OPTIONS["minimal-flags"] then 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' } filter "Debug" buildoptions { "-O0" } -- ICC defaults to -O2 filter { } if os.istarget("macosx") then linkoptions { "-multiply_defined","suppress" } end else -- exclude most non-essential build options for minimal-flags if not _OPTIONS["minimal-flags"] then 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", "-U_FORTIFY_SOURCE", -- (avoid redefinition warning if already defined) "-D_FORTIFY_SOURCE=2", -- always enable strict aliasing (useful in debug builds because of the warnings) "-fstrict-aliasing", -- 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 not _OPTIONS["without-pch"] then buildoptions { -- do something (?) so that ccache can handle compilation with PCH enabled -- (ccache 3.1+ also requires CCACHE_SLOPPINESS=time_macros for this to work) "-fpch-preprocess" } end if os.istarget("linux") or os.istarget("bsd") then buildoptions { "-fPIC" } linkoptions { "-Wl,--no-undefined", "-Wl,--as-needed", "-Wl,-z,relro" } end if arch == "x86" then buildoptions { -- To support intrinsics like __sync_bool_compare_and_swap on x86 -- we need to set -march to something that supports them (i686). -- We use pentium3 to also enable other features like mmx and sse, -- while tuning for generic to have good performance on every -- supported CPU. -- Note that all these features are already supported on amd64. "-march=pentium3 -mtune=generic" } end end buildoptions { -- Enable C++11 standard. "-std=c++0x" } if arch == "arm" then -- disable warnings about va_list ABI change and use -- compile-time flags for futher configuration. buildoptions { "-Wno-psabi" } if _OPTIONS["android"] then -- Android uses softfp, so we should too. buildoptions { "-mfloat-abi=softfp" } end end if _OPTIONS["coverage"] then buildoptions { "-fprofile-arcs", "-ftest-coverage" } links { "gcov" } 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.istarget("macosx") then buildoptions { "-msse2" } end -- Check if SDK path should be used if _OPTIONS["sysroot"] then buildoptions { "-isysroot " .. _OPTIONS["sysroot"] } linkoptions { "-Wl,-syslibroot," .. _OPTIONS["sysroot"] } end -- On OS X, sometimes we need to specify the minimum API version to use if _OPTIONS["macosx-version-min"] then buildoptions { "-mmacosx-version-min=" .. _OPTIONS["macosx-version-min"] } -- clang and llvm-gcc look at mmacosx-version-min to determine link target -- and CRT version, and use it to set the macosx_version_min linker flag linkoptions { "-mmacosx-version-min=" .. _OPTIONS["macosx-version-min"] } end -- Check if we're building a bundle if _OPTIONS["macosx-bundle"] then defines { "BUNDLE_IDENTIFIER=" .. _OPTIONS["macosx-bundle"] } end -- On OS X, force using libc++ since it has better C++11 support, -- now required by the game if os.istarget("macosx") then buildoptions { "-stdlib=libc++" } linkoptions { "-stdlib=libc++" } end end 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" } if _OPTIONS["bindir"] then defines { "INSTALLED_BINDIR=" .. _OPTIONS["bindir"] } end if _OPTIONS["datadir"] then defines { "INSTALLED_DATADIR=" .. _OPTIONS["datadir"] } end if _OPTIONS["libdir"] then defines { "INSTALLED_LIBDIR=" .. _OPTIONS["libdir"] } end if os.istarget("linux") or os.istarget("bsd") then if _OPTIONS["prefer-local-libs"] then libdirs { "/usr/local/lib" } end -- To use our local shared libraries, they need to be found in the -- runtime dynamic linker path. Add their path to -rpath. if _OPTIONS["libdir"] then linkoptions {"-Wl,-rpath," .. _OPTIONS["libdir"] } else -- On FreeBSD we need to allow use of $ORIGIN if os.istarget("bsd") then linkoptions { "-Wl,-z,origin" } end -- Adding the executable path and taking care of correct escaping if _ACTION == "gmake" then linkoptions { "-Wl,-rpath,'$$ORIGIN'" } elseif _ACTION == "codeblocks" then linkoptions { "-Wl,-R\\\\$$$ORIGIN" } end end end end end -- create a project and set the attributes that are common to all projects. function project_create(project_name, target_type) project(project_name) language "C++" kind(target_type) filter "action:vs2015" toolset "v140_xp" filter {} filter "action:vs*" buildoptions "/utf-8" filter {} project_set_target(project_name) project_set_build_flags() end -- OSX creates a .app bundle if the project type of the main application is set to "WindowedApp". -- We don't want this because this bundle would be broken (it lacks all the resources and external dependencies, Info.plist etc...) -- Windows opens a console in the background if it's set to ConsoleApp, which is not what we want. -- I didn't check if this setting matters for linux, but WindowedApp works there. function get_main_project_target_type() if _OPTIONS["android"] then return "SharedLib" elseif os.istarget("macosx") then return "ConsoleApp" else return "WindowedApp" end end -- source_root: rel_source_dirs and rel_include_dirs are relative to this directory -- rel_source_dirs: A table of subdirectories. All source files in these directories are added. -- rel_include_dirs: A table of subdirectories to be included. -- extra_params: table including zero or more of the following: -- * no_pch: If specified, no precompiled headers are used for this project. -- * pch_dir: If specified, this directory will be used for precompiled headers instead of the default -- /pch//. -- * 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 project_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) for i,v in pairs(rel_source_dirs) do local prefix = source_root..v.."/" files { prefix.."*.cpp", prefix.."*.h", prefix.."*.inl", prefix.."*.js", prefix.."*.asm", prefix.."*.mm" } end -- Put the project-specific PCH directory at the start of the -- include path, so '#include "precompiled.h"' will look in -- there first local pch_dir if not extra_params["pch_dir"] then pch_dir = source_root .. "pch/" .. project().name .. "/" else pch_dir = extra_params["pch_dir"] end includedirs { pch_dir } -- 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 -- "projectdir/precompiled.h", or add "source/PCH/projectdir" to the -- include path and put the PCH there. The latter is better because -- many projects 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 _OPTIONS["without-pch"] and not extra_params["no_pch"]) then filter "action:vs*" pchheader("precompiled.h") filter "action:xcode*" pchheader("../"..pch_dir.."precompiled.h") filter { "action:not vs*", "action:not xcode*" } pchheader(pch_dir.."precompiled.h") filter {} pchsource(pch_dir.."precompiled.cpp") defines { "CONFIG_ENABLE_PCH=1" } files { pch_dir.."precompiled.h", pch_dir.."precompiled.cpp" } else defines { "CONFIG_ENABLE_PCH=0" } flags { "NoPCH" } end -- next is source root dir, for absolute (nonrelative) includes -- (e.g. "lib/precompiled.h") includedirs { source_root } for i,v in pairs(rel_include_dirs) do includedirs { source_root .. v } end if extra_params["extra_files"] then for i,v in pairs(extra_params["extra_files"]) do -- .rc files are only needed on Windows if path.getextension(v) ~= ".rc" or os.istarget("windows") then files { source_root .. v } end end end if extra_params["extra_links"] then links { extra_params["extra_links"] } end end -- Add command-line options to set up the manifest dependencies for Windows -- (See lib/sysdep/os/win/manifest.cpp) function project_add_manifest() linkoptions { "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='X86' publicKeyToken='6595b64144ccf1df'\"" } 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 = {} static_lib_names_debug = {} static_lib_names_release = {} -- set up one of the static libraries into which the main engine code is split. -- extra_params: -- no_default_link: If specified, linking won't be done by default. -- For the rest of extra_params, see project_add_contents(). -- note: rel_source_dirs and rel_include_dirs are relative to global source_root. function setup_static_lib_project (project_name, rel_source_dirs, extern_libs, extra_params) local target_type = "StaticLib" project_create(project_name, target_type) project_add_contents(source_root, rel_source_dirs, {}, extra_params) project_add_extern_libs(extern_libs, target_type) if not extra_params["no_default_link"] then table.insert(static_lib_names, project_name) end if os.istarget("windows") then rtti "off" elseif os.istarget("macosx") and _OPTIONS["macosx-version-min"] then xcodebuildsettings { MACOSX_DEPLOYMENT_TARGET = _OPTIONS["macosx-version-min"] } end end function setup_third_party_static_lib_project (project_name, rel_source_dirs, extern_libs, extra_params) setup_static_lib_project(project_name, rel_source_dirs, extern_libs, extra_params) includedirs { source_root .. "third_party/" .. project_name .. "/include/" } end function setup_shared_lib_project (project_name, rel_source_dirs, extern_libs, extra_params) local target_type = "SharedLib" project_create(project_name, target_type) project_add_contents(source_root, rel_source_dirs, {}, extra_params) project_add_extern_libs(extern_libs, target_type) if not extra_params["no_default_link"] then table.insert(static_lib_names, project_name) end if os.istarget("windows") then rtti "off" links { "delayimp" } elseif os.istarget("macosx") and _OPTIONS["macosx-version-min"] then xcodebuildsettings { MACOSX_DEPLOYMENT_TARGET = _OPTIONS["macosx-version-min"] } 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_project, -- 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 } if not _OPTIONS["without-miniupnpc"] then table.insert(extern_libs, "miniupnpc") end setup_static_lib_project("network", source_dirs, extern_libs, {}) source_dirs = { "third_party/tinygettext/src", } extern_libs = { "iconv", "boost", } setup_third_party_static_lib_project("tinygettext", source_dirs, extern_libs, { } ) -- it's an external library and we don't want to modify its source to fix warnings, so we just disable them to avoid noise in the compile output filter "action:vs*" buildoptions { "/wd4127", "/wd4309", "/wd4800", "/wd4100", "/wd4996", "/wd4099", "/wd4503" } filter {} if not _OPTIONS["without-lobby"] then source_dirs = { "lobby", "lobby/scripting", "i18n", "third_party/encryption" } extern_libs = { "spidermonkey", "boost", "enet", "gloox", "icu", "iconv", "libsodium", "tinygettext" } setup_static_lib_project("lobby", source_dirs, extern_libs, {}) if _OPTIONS["use-shared-glooxwrapper"] and not _OPTIONS["build-shared-glooxwrapper"] then table.insert(static_lib_names_debug, "glooxwrapper_dbg") table.insert(static_lib_names_release, "glooxwrapper") else source_dirs = { "lobby/glooxwrapper", } extern_libs = { "boost", "gloox", } if _OPTIONS["build-shared-glooxwrapper"] then setup_shared_lib_project("glooxwrapper", source_dirs, extern_libs, {}) else setup_static_lib_project("glooxwrapper", source_dirs, extern_libs, {}) end end else source_dirs = { "lobby/scripting", "third_party/encryption" } extern_libs = { "spidermonkey", "boost", "libsodium" } setup_static_lib_project("lobby", source_dirs, extern_libs, {}) files { source_root.."lobby/Globals.cpp" } end source_dirs = { "simulation2", "simulation2/components", "simulation2/helpers", "simulation2/scripting", "simulation2/serialization", "simulation2/system", "simulation2/testcomponents", } extern_libs = { "boost", "opengl", "spidermonkey", } setup_static_lib_project("simulation2", source_dirs, extern_libs, {}) source_dirs = { "scriptinterface", "scriptinterface/third_party" } extern_libs = { "boost", "spidermonkey", "valgrind", "sdl", } setup_static_lib_project("scriptinterface", source_dirs, extern_libs, {}) source_dirs = { "ps", "ps/scripting", "network/scripting", "ps/GameSetup", "ps/XML", "soundmanager", "soundmanager/data", "soundmanager/items", "soundmanager/scripting", "maths", "maths/scripting", "i18n", "i18n/scripting", "third_party/fmt", } extern_libs = { "spidermonkey", "sdl", -- key definitions "libxml2", "opengl", "zlib", "boost", "enet", "libcurl", "tinygettext", "icu", "iconv", "libsodium", } if not _OPTIONS["without-audio"] then table.insert(extern_libs, "openal") table.insert(extern_libs, "vorbis") end setup_static_lib_project("engine", source_dirs, extern_libs, {}) source_dirs = { "graphics", "graphics/scripting", "renderer", "renderer/scripting", "third_party/mikktspace", "third_party/ogre3d_preprocessor" } extern_libs = { "opengl", "sdl", -- key definitions "spidermonkey", -- for graphics/scripting "boost" } if not _OPTIONS["without-nvtt"] then table.insert(extern_libs, "nvtt") end setup_static_lib_project("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_project("atlas", source_dirs, extern_libs, {}) source_dirs = { "gui", "gui/ObjectTypes", "gui/ObjectBases", "gui/Scripting", "gui/SettingTypes", "i18n" } extern_libs = { "spidermonkey", "sdl", -- key definitions "opengl", "boost", "enet", "tinygettext", "icu", "iconv", } if not _OPTIONS["without-audio"] then table.insert(extern_libs, "openal") end setup_static_lib_project("gui", source_dirs, extern_libs, {}) source_dirs = { "lib", "lib/adts", "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/sysdep", "lib/tex" } extern_libs = { "boost", "sdl", "openal", "opengl", "libpng", "zlib", "valgrind", "cxxtest", } -- CPU architecture-specific if arch == "amd64" then table.insert(source_dirs, "lib/sysdep/arch/amd64"); table.insert(source_dirs, "lib/sysdep/arch/x86_x64"); elseif arch == "x86" then table.insert(source_dirs, "lib/sysdep/arch/ia32"); table.insert(source_dirs, "lib/sysdep/arch/x86_x64"); elseif arch == "arm" then table.insert(source_dirs, "lib/sysdep/arch/arm"); elseif arch == "aarch64" then table.insert(source_dirs, "lib/sysdep/arch/aarch64"); end -- OS-specific sysdep_dirs = { linux = { "lib/sysdep/os/linux", "lib/sysdep/os/unix" }, -- note: RC file must be added to main_exe project. -- 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" }, bsd = { "lib/sysdep/os/bsd", "lib/sysdep/os/unix", "lib/sysdep/os/unix/x" }, } for i,v in pairs(sysdep_dirs[os.target()]) do table.insert(source_dirs, v); end if os.istarget("linux") then if _OPTIONS["android"] then table.insert(source_dirs, "lib/sysdep/os/android") else table.insert(source_dirs, "lib/sysdep/os/unix/x") end end -- On OSX, disable precompiled headers because C++ files and Objective-C++ files are -- mixed in this project. To fix that, we would need per-file basis configuration which -- is not yet supported by the gmake action in premake. We should look into using gmake2. extra_params = {} if os.istarget("macosx") then extra_params = { no_pch = 1 } end -- runtime-library-specific if _ACTION == "vs2015" then table.insert(source_dirs, "lib/sysdep/rtl/msc"); else table.insert(source_dirs, "lib/sysdep/rtl/gcc"); end setup_static_lib_project("lowlevel", source_dirs, extern_libs, extra_params) -- Third-party libraries that are built as part of the main project, -- not built externally and then linked source_dirs = { "third_party/mongoose", } extern_libs = { } setup_static_lib_project("mongoose", source_dirs, extern_libs, { no_pch = 1 }) -- CxxTest mock function support extern_libs = { "boost", "cxxtest", } -- 'real' implementations, to be linked against the main executable -- (files are added manually and not with setup_static_lib_project -- because not all files in the directory are included) setup_static_lib_project("mocks_real", {}, extern_libs, { no_default_link = 1, no_pch = 1 }) files { "mocks/*.h", source_root.."mocks/*_real.cpp" } -- 'test' implementations, to be linked against the test executable setup_static_lib_project("mocks_test", {}, extern_libs, { no_default_link = 1, no_pch = 1 }) files { 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", "libpng", "zlib", "spidermonkey", "libxml2", "boost", "cxxtest", "comsuppw", "enet", "libcurl", "tinygettext", "icu", "iconv", "libsodium", "valgrind", } if not os.istarget("windows") and not _OPTIONS["android"] and not os.istarget("macosx") then -- X11 should only be linked on *nix table.insert(used_extern_libs, "x11") table.insert(used_extern_libs, "xcursor") end if not _OPTIONS["without-audio"] then table.insert(used_extern_libs, "openal") table.insert(used_extern_libs, "vorbis") end if not _OPTIONS["without-nvtt"] then table.insert(used_extern_libs, "nvtt") end if not _OPTIONS["without-lobby"] then table.insert(used_extern_libs, "gloox") end if not _OPTIONS["without-miniupnpc"] then table.insert(used_extern_libs, "miniupnpc") end -- Bundles static libs together with main.cpp and builds game executable. function setup_main_exe () local target_type = get_main_project_target_type() project_create("pyrogenesis", target_type) filter "system:not macosx" linkgroups 'On' filter {} links { "mocks_real" } local extra_params = { extra_files = { "main.cpp" }, no_pch = 1 } project_add_contents(source_root, {}, {}, extra_params) project_add_extern_libs(used_extern_libs, target_type) dependson { "Collada" } -- Platform Specifics if os.istarget("windows") then files { source_root.."lib/sysdep/os/win/icon.rc" } -- from "lowlevel" static lib; must be added here to be linked in files { source_root.."lib/sysdep/os/win/error_dialog.rc" } rtti "off" 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", -- allow manual unload of delay-loaded DLLs "/DELAY:UNLOAD", } -- allow the executable to use more than 2GB of RAM. -- this should not be enabled during development, so that memory issues are easily spotted. if _OPTIONS["large-address-aware"] then linkoptions { "/LARGEADDRESSAWARE" } end -- see manifest.cpp project_add_manifest() elseif os.istarget("linux") or os.istarget("bsd") then if not _OPTIONS["android"] and not (os.getversion().description == "OpenBSD") then links { "rt" } end if _OPTIONS["android"] then -- NDK's STANDALONE-TOOLCHAIN.html says this is required linkoptions { "-Wl,--fix-cortex-a8" } links { "log" } end if link_execinfo then links { "execinfo" } end if os.istarget("linux") or os.getversion().description == "GNU/kFreeBSD" then links { -- Dynamic libraries (needed for linking for gold) "dl", } end -- Threading support buildoptions { "-pthread" } if not _OPTIONS["android"] then linkoptions { "-pthread" } end -- For debug_resolve_symbol filter "Debug" linkoptions { "-rdynamic" } filter { } elseif os.istarget("macosx") then links { "pthread" } links { "ApplicationServices.framework", "Cocoa.framework", "CoreFoundation.framework" } if _OPTIONS["macosx-version-min"] then xcodebuildsettings { MACOSX_DEPLOYMENT_TARGET = _OPTIONS["macosx-version-min"] } end end end -------------------------------------------------------------------------------- -- atlas -------------------------------------------------------------------------------- -- setup a typical Atlas component project -- extra_params, rel_source_dirs and rel_include_dirs: as in project_add_contents; function setup_atlas_project(project_name, target_type, rel_source_dirs, rel_include_dirs, extern_libs, extra_params) local source_root = rootdir.."/source/tools/atlas/" .. project_name .. "/" project_create(project_name, target_type) -- if not specified, the default for atlas pch files is in the project root. if not extra_params["pch_dir"] then extra_params["pch_dir"] = source_root end project_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) project_add_extern_libs(extern_libs, target_type) -- Platform Specifics if os.istarget("windows") then -- Link to required libraries links { "winmm", "comctl32", "rpcrt4", "delayimp", "ws2_32" } elseif os.istarget("linux") or os.istarget("bsd") then buildoptions { "-rdynamic", "-fPIC" } linkoptions { "-fPIC", "-rdynamic" } -- warnings triggered by wxWidgets buildoptions { "-Wno-unused-local-typedefs" } elseif os.istarget("macosx") then -- install_name settings aren't really supported yet by premake, but there are plans for the future. -- we currently use this hack to work around some bugs with wrong install_names. if target_type == "SharedLib" then if _OPTIONS["macosx-bundle"] then -- If we're building a bundle, it will be in ../Frameworks filter "Debug" linkoptions { "-install_name @executable_path/../Frameworks/lib"..project_name.."_dbg.dylib" } filter "Release" linkoptions { "-install_name @executable_path/../Frameworks/lib"..project_name..".dylib" } filter { } else filter "Debug" linkoptions { "-install_name @executable_path/lib"..project_name.."_dbg.dylib" } filter "Release" linkoptions { "-install_name @executable_path/lib"..project_name..".dylib" } filter { } end end end end -- build all Atlas component projects function setup_atlas_projects() setup_atlas_project("AtlasObject", "StaticLib", { -- src ".", "../../../third_party/jsonspirit" },{ -- include "../../../third_party/jsonspirit" },{ -- extern_libs "boost", "iconv", "libxml2" },{ -- extra_params no_pch = 1 }) atlas_src = { "ActorEditor", "CustomControls/Buttons", "CustomControls/Canvas", "CustomControls/ColorDialog", "CustomControls/DraggableListCtrl", "CustomControls/EditableListCtrl", "CustomControls/FileHistory", "CustomControls/HighResTimer", "CustomControls/MapDialog", "CustomControls/SnapSplitterWindow", "CustomControls/VirtualDirTreeCtrl", "CustomControls/Windows", "General", "General/VideoRecorder", "Misc", "ScenarioEditor", "ScenarioEditor/Sections/Common", "ScenarioEditor/Sections/Cinema", "ScenarioEditor/Sections/Environment", "ScenarioEditor/Sections/Map", "ScenarioEditor/Sections/Object", "ScenarioEditor/Sections/Player", "ScenarioEditor/Sections/Terrain", "ScenarioEditor/Tools", "ScenarioEditor/Tools/Common", } atlas_extra_links = { "AtlasObject" } atlas_extern_libs = { "boost", "comsuppw", "iconv", "libxml2", "sdl", -- key definitions "wxwidgets", "zlib", } if not os.istarget("windows") and not os.istarget("macosx") then -- X11 should only be linked on *nix table.insert(atlas_extern_libs, "x11") end setup_atlas_project("AtlasUI", "SharedLib", atlas_src, { -- include "..", "CustomControls", - "Misc" + "Misc", + "../../../third_party/jsonspirit" }, atlas_extern_libs, { -- extra_params pch_dir = rootdir.."/source/tools/atlas/AtlasUI/Misc/", no_pch = false, extra_links = atlas_extra_links, extra_files = { "Misc/atlas.rc" } }) end -- Atlas 'frontend' tool-launching projects function setup_atlas_frontend_project (project_name) local target_type = get_main_project_target_type() project_create(project_name, target_type) local source_root = rootdir.."/source/tools/atlas/AtlasFrontends/" files { source_root..project_name..".cpp" } if os.istarget("windows") then files { source_root..project_name..".rc" } end includedirs { source_root .. ".." } -- Platform Specifics if os.istarget("windows") then -- see manifest.cpp project_add_manifest() else -- Non-Windows, = Unix links { "AtlasObject" } end links { "AtlasUI" } end function setup_atlas_frontends() setup_atlas_frontend_project("ActorEditor") end -------------------------------------------------------------------------------- -- collada -------------------------------------------------------------------------------- function setup_collada_project(project_name, target_type, rel_source_dirs, rel_include_dirs, extern_libs, extra_params) project_create(project_name, target_type) local source_root = source_root.."collada/" extra_params["pch_dir"] = source_root project_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) project_add_extern_libs(extern_libs, target_type) -- Platform Specifics if os.istarget("windows") then characterset "MBCS" elseif os.istarget("linux") then defines { "LINUX" } links { "dl", } -- FCollada is not aliasing-safe, so disallow dangerous optimisations -- (TODO: It'd be nice to fix FCollada, but that looks hard) buildoptions { "-fno-strict-aliasing" } buildoptions { "-rdynamic" } linkoptions { "-rdynamic" } elseif os.istarget("bsd") then if os.getversion().description == "OpenBSD" then links { "c", } end if os.getversion().description == "GNU/kFreeBSD" then links { "dl", } end buildoptions { "-fno-strict-aliasing" } buildoptions { "-rdynamic" } linkoptions { "-rdynamic" } elseif os.istarget("macosx") then -- define MACOS-something? -- install_name settings aren't really supported yet by premake, but there are plans for the future. -- we currently use this hack to work around some bugs with wrong install_names. if target_type == "SharedLib" then if _OPTIONS["macosx-bundle"] then -- If we're building a bundle, it will be in ../Frameworks linkoptions { "-install_name @executable_path/../Frameworks/lib"..project_name..".dylib" } else linkoptions { "-install_name @executable_path/lib"..project_name..".dylib" } end end buildoptions { "-fno-strict-aliasing" } -- On OSX, fcollada uses a few utility functions from coreservices links { "CoreServices.framework" } end end -- build all Collada component projects function setup_collada_projects() setup_collada_project("Collada", "SharedLib", { -- src "." },{ -- include },{ -- extern_libs "fcollada", "iconv", "libxml2" },{ -- extra_params }) end -------------------------------------------------------------------------------- -- tests -------------------------------------------------------------------------------- function setup_tests() local cxxtest = require "cxxtest" if os.istarget("windows") then cxxtest.setpath(rootdir.."/build/bin/cxxtestgen.exe") else cxxtest.setpath(rootdir.."/libraries/source/cxxtest-4.4/bin/cxxtestgen") end local runner = "ErrorPrinter" if _OPTIONS["jenkins-tests"] then runner = "XmlPrinter" end local includefiles = { -- 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. "precompiled.h", -- This is required to build against SDL 2.0.4 on Windows. "lib/external_libraries/libsdl.h", } cxxtest.init(source_root, true, runner, includefiles) local target_type = get_main_project_target_type() project_create("test", target_type) -- Find header files in 'test' subdirectories local all_files = os.matchfiles(source_root .. "**/tests/*.h") local test_files = {} for i,v in pairs(all_files) do -- 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 not os.istarget("windows")) and not (string.find(v, "/tools/atlas/") and not _OPTIONS["atlas"]) and not (string.find(v, "/sysdep/arch/x86_x64/") and ((arch ~= "amd64") or (arch ~= "x86"))) then table.insert(test_files, v) end end cxxtest.configure_project(test_files) filter "system:not macosx" linkgroups 'On' filter {} links { static_lib_names } filter "Debug" links { static_lib_names_debug } filter "Release" links { static_lib_names_release } filter { } links { "mocks_test" } if _OPTIONS["atlas"] then links { "AtlasObject" } end extra_params = { extra_files = { "test_setup.cpp" }, } project_add_contents(source_root, {}, {}, extra_params) project_add_extern_libs(used_extern_libs, target_type) dependson { "Collada" } -- TODO: should fix the duplication between this OS-specific linking -- code, and the similar version in setup_main_exe if os.istarget("windows") then -- from "lowlevel" static lib; must be added here to be linked in files { source_root.."lib/sysdep/os/win/error_dialog.rc" } rtti "off" -- see wstartup.h linkoptions { "/INCLUDE:_wstartup_InitAndRegisterShutdown" } -- Enables console for the TEST project on Windows linkoptions { "/SUBSYSTEM:CONSOLE" } project_add_manifest() elseif os.istarget("linux") or os.istarget("bsd") then if link_execinfo then links { "execinfo" } end if not _OPTIONS["android"] and not (os.getversion().description == "OpenBSD") then links { "rt" } end if _OPTIONS["android"] then -- NDK's STANDALONE-TOOLCHAIN.html says this is required linkoptions { "-Wl,--fix-cortex-a8" } end if os.istarget("linux") or os.getversion().description == "GNU/kFreeBSD" then links { -- Dynamic libraries (needed for linking for gold) "dl", } end -- Threading support buildoptions { "-pthread" } if not _OPTIONS["android"] then linkoptions { "-pthread" } end -- For debug_resolve_symbol filter "Debug" linkoptions { "-rdynamic" } filter { } includedirs { source_root .. "pch/test/" } elseif os.istarget("macosx") and _OPTIONS["macosx-version-min"] then xcodebuildsettings { MACOSX_DEPLOYMENT_TARGET = _OPTIONS["macosx-version-min"] } end 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() setup_all_libs() -- 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. project("pyrogenesis") -- Set the main project active links { static_lib_names } filter "Debug" links { static_lib_names_debug } filter "Release" links { static_lib_names_release } filter { } if _OPTIONS["atlas"] then setup_atlas_projects() setup_atlas_frontends() end setup_collada_projects() if not _OPTIONS["without-tests"] then setup_tests() end Index: ps/trunk/source/simulation2/Simulation2.cpp =================================================================== --- ps/trunk/source/simulation2/Simulation2.cpp (revision 23846) +++ ps/trunk/source/simulation2/Simulation2.cpp (revision 23847) @@ -1,999 +1,1004 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Simulation2.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptRuntime.h" #include "simulation2/MessageTypes.h" #include "simulation2/system/ComponentManager.h" #include "simulation2/system/ParamNode.h" #include "simulation2/system/SimContext.h" #include "simulation2/components/ICmpAIManager.h" #include "simulation2/components/ICmpCommandQueue.h" #include "simulation2/components/ICmpTemplateManager.h" #include "graphics/MapReader.h" #include "graphics/Terrain.h" #include "lib/timer.h" #include "lib/file/vfs/vfs_util.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Loader.h" #include "ps/Profile.h" #include "ps/Pyrogenesis.h" #include "ps/Util.h" #include "ps/XML/Xeromyces.h" #include class CSimulation2Impl { public: CSimulation2Impl(CUnitManager* unitManager, shared_ptr rt, CTerrain* terrain) : m_SimContext(), m_ComponentManager(m_SimContext, rt), m_EnableOOSLog(false), m_EnableSerializationTest(false), m_RejoinTestTurn(-1), m_TestingRejoin(false), m_SecondaryTerrain(nullptr), m_SecondaryContext(nullptr), m_SecondaryComponentManager(nullptr), m_SecondaryLoadedScripts(nullptr), m_MapSettings(rt->m_rt), m_InitAttributes(rt->m_rt) { m_SimContext.m_UnitManager = unitManager; m_SimContext.m_Terrain = terrain; m_ComponentManager.LoadComponentTypes(); RegisterFileReloadFunc(ReloadChangedFileCB, this); // Tests won't have config initialised if (CConfigDB::IsInitialised()) { CFG_GET_VAL("ooslog", m_EnableOOSLog); CFG_GET_VAL("serializationtest", m_EnableSerializationTest); CFG_GET_VAL("rejointest", m_RejoinTestTurn); if (m_RejoinTestTurn < 0) // Handle bogus values of the arg m_RejoinTestTurn = -1; } if (m_EnableOOSLog) { m_OOSLogPath = createDateIndexSubdirectory(psLogDir() / "oos_logs"); debug_printf("Writing ooslogs to %s\n", m_OOSLogPath.string8().c_str()); } } ~CSimulation2Impl() { delete m_SecondaryTerrain; delete m_SecondaryContext; delete m_SecondaryComponentManager; delete m_SecondaryLoadedScripts; UnregisterFileReloadFunc(ReloadChangedFileCB, this); } void ResetState(bool skipScriptedComponents, bool skipAI) { m_DeltaTime = 0.0; m_LastFrameOffset = 0.0f; m_TurnNumber = 0; ResetComponentState(m_ComponentManager, skipScriptedComponents, skipAI); } static void ResetComponentState(CComponentManager& componentManager, bool skipScriptedComponents, bool skipAI) { componentManager.ResetState(); componentManager.InitSystemEntity(); componentManager.AddSystemComponents(skipScriptedComponents, skipAI); } static bool LoadDefaultScripts(CComponentManager& componentManager, std::set* loadedScripts); static bool LoadScripts(CComponentManager& componentManager, std::set* loadedScripts, const VfsPath& path); static bool LoadTriggerScripts(CComponentManager& componentManager, JS::HandleValue mapSettings, std::set* loadedScripts); Status ReloadChangedFile(const VfsPath& path); static Status ReloadChangedFileCB(void* param, const VfsPath& path) { return static_cast(param)->ReloadChangedFile(path); } int ProgressiveLoad(); void Update(int turnLength, const std::vector& commands); static void UpdateComponents(CSimContext& simContext, fixed turnLengthFixed, const std::vector& commands); void Interpolate(float simFrameLength, float frameOffset, float realFrameLength); void DumpState(); CSimContext m_SimContext; CComponentManager m_ComponentManager; double m_DeltaTime; float m_LastFrameOffset; std::string m_StartupScript; JS::PersistentRootedValue m_InitAttributes; JS::PersistentRootedValue m_MapSettings; std::set m_LoadedScripts; uint32_t m_TurnNumber; bool m_EnableOOSLog; OsPath m_OOSLogPath; // Functions and data for the serialization test mode: (see Update() for relevant comments) bool m_EnableSerializationTest; int m_RejoinTestTurn; bool m_TestingRejoin; // Secondary simulation CTerrain* m_SecondaryTerrain; CSimContext* m_SecondaryContext; CComponentManager* m_SecondaryComponentManager; std::set* m_SecondaryLoadedScripts; struct SerializationTestState { std::stringstream state; std::stringstream debug; std::string hash; }; void DumpSerializationTestState(SerializationTestState& state, const OsPath& path, const OsPath::String& suffix); void ReportSerializationFailure( SerializationTestState* primaryStateBefore, SerializationTestState* primaryStateAfter, SerializationTestState* secondaryStateBefore, SerializationTestState* secondaryStateAfter); void InitRNGSeedSimulation(); void InitRNGSeedAI(); static std::vector CloneCommandsFromOtherContext(const ScriptInterface& oldScript, const ScriptInterface& newScript, const std::vector& commands) { JSContext* cxOld = oldScript.GetContext(); JSAutoRequest rqOld(cxOld); std::vector newCommands; newCommands.reserve(commands.size()); for (const SimulationCommand& command : commands) { JSContext* cxNew = newScript.GetContext(); JSAutoRequest rqNew(cxNew); JS::RootedValue tmpCommand(cxNew, newScript.CloneValueFromOtherContext(oldScript, command.data)); newScript.FreezeObject(tmpCommand, true); SimulationCommand cmd(command.player, cxNew, tmpCommand); newCommands.emplace_back(std::move(cmd)); } return newCommands; } }; bool CSimulation2Impl::LoadDefaultScripts(CComponentManager& componentManager, std::set* loadedScripts) { return ( LoadScripts(componentManager, loadedScripts, L"simulation/components/interfaces/") && LoadScripts(componentManager, loadedScripts, L"simulation/helpers/") && LoadScripts(componentManager, loadedScripts, L"simulation/components/") ); } bool CSimulation2Impl::LoadScripts(CComponentManager& componentManager, std::set* loadedScripts, const VfsPath& path) { VfsPaths pathnames; if (vfs::GetPathnames(g_VFS, path, L"*.js", pathnames) < 0) return false; bool ok = true; for (const VfsPath& path : pathnames) { if (loadedScripts) loadedScripts->insert(path); LOGMESSAGE("Loading simulation script '%s'", path.string8()); if (!componentManager.LoadScript(path)) ok = false; } return ok; } bool CSimulation2Impl::LoadTriggerScripts(CComponentManager& componentManager, JS::HandleValue mapSettings, std::set* loadedScripts) { bool ok = true; if (componentManager.GetScriptInterface().HasProperty(mapSettings, "TriggerScripts")) { std::vector scriptNames; componentManager.GetScriptInterface().GetProperty(mapSettings, "TriggerScripts", scriptNames); for (const std::string& triggerScript : scriptNames) { std::string scriptName = "maps/" + triggerScript; if (loadedScripts) { if (loadedScripts->find(scriptName) != loadedScripts->end()) continue; loadedScripts->insert(scriptName); } LOGMESSAGE("Loading trigger script '%s'", scriptName.c_str()); if (!componentManager.LoadScript(scriptName.data())) ok = false; } } return ok; } Status CSimulation2Impl::ReloadChangedFile(const VfsPath& path) { // Ignore if this file wasn't loaded as a script // (TODO: Maybe we ought to load in any new .js files that are created in the right directories) if (m_LoadedScripts.find(path) == m_LoadedScripts.end()) return INFO::OK; // If the file doesn't exist (e.g. it was deleted), don't bother loading it since that'll give an error message. // (Also don't bother trying to 'unload' it from the component manager, because that's not possible) if (!VfsFileExists(path)) return INFO::OK; LOGMESSAGE("Reloading simulation script '%s'", path.string8()); if (!m_ComponentManager.LoadScript(path, true)) return ERR::FAIL; return INFO::OK; } int CSimulation2Impl::ProgressiveLoad() { // yield after this time is reached. balances increased progress bar // smoothness vs. slowing down loading. const double end_time = timer_Time() + 200e-3; int ret; do { bool progressed = false; int total = 0; int progress = 0; CMessageProgressiveLoad msg(&progressed, &total, &progress); m_ComponentManager.BroadcastMessage(msg); if (!progressed || total == 0) return 0; // we have nothing left to load ret = Clamp(100*progress / total, 1, 100); } while (timer_Time() < end_time); return ret; } void CSimulation2Impl::DumpSerializationTestState(SerializationTestState& state, const OsPath& path, const OsPath::String& suffix) { if (!state.hash.empty()) { std::ofstream file (OsString(path / (L"hash." + suffix)).c_str(), std::ofstream::out | std::ofstream::trunc); file << Hexify(state.hash); } if (!state.debug.str().empty()) { std::ofstream file (OsString(path / (L"debug." + suffix)).c_str(), std::ofstream::out | std::ofstream::trunc); file << state.debug.str(); } if (!state.state.str().empty()) { std::ofstream file (OsString(path / (L"state." + suffix)).c_str(), std::ofstream::out | std::ofstream::trunc | std::ofstream::binary); file << state.state.str(); } } void CSimulation2Impl::ReportSerializationFailure( SerializationTestState* primaryStateBefore, SerializationTestState* primaryStateAfter, SerializationTestState* secondaryStateBefore, SerializationTestState* secondaryStateAfter) { const OsPath path = createDateIndexSubdirectory(psLogDir() / "serializationtest"); debug_printf("Writing serializationtest-data to %s\n", path.string8().c_str()); // Clean up obsolete files from previous runs wunlink(path / "hash.before.a"); wunlink(path / "hash.before.b"); wunlink(path / "debug.before.a"); wunlink(path / "debug.before.b"); wunlink(path / "state.before.a"); wunlink(path / "state.before.b"); wunlink(path / "hash.after.a"); wunlink(path / "hash.after.b"); wunlink(path / "debug.after.a"); wunlink(path / "debug.after.b"); wunlink(path / "state.after.a"); wunlink(path / "state.after.b"); if (primaryStateBefore) DumpSerializationTestState(*primaryStateBefore, path, L"before.a"); if (primaryStateAfter) DumpSerializationTestState(*primaryStateAfter, path, L"after.a"); if (secondaryStateBefore) DumpSerializationTestState(*secondaryStateBefore, path, L"before.b"); if (secondaryStateAfter) DumpSerializationTestState(*secondaryStateAfter, path, L"after.b"); debug_warn(L"Serialization test failure"); } void CSimulation2Impl::InitRNGSeedSimulation() { u32 seed = 0; if (!m_ComponentManager.GetScriptInterface().HasProperty(m_MapSettings, "Seed") || !m_ComponentManager.GetScriptInterface().GetProperty(m_MapSettings, "Seed", seed)) LOGWARNING("CSimulation2Impl::InitRNGSeedSimulation: No seed value specified - using %d", seed); m_ComponentManager.SetRNGSeed(seed); } void CSimulation2Impl::InitRNGSeedAI() { u32 seed = 0; if (!m_ComponentManager.GetScriptInterface().HasProperty(m_MapSettings, "AISeed") || !m_ComponentManager.GetScriptInterface().GetProperty(m_MapSettings, "AISeed", seed)) LOGWARNING("CSimulation2Impl::InitRNGSeedAI: No seed value specified - using %d", seed); CmpPtr cmpAIManager(m_SimContext, SYSTEM_ENTITY); if (cmpAIManager) cmpAIManager->SetRNGSeed(seed); } void CSimulation2Impl::Update(int turnLength, const std::vector& commands) { PROFILE3("sim update"); PROFILE2_ATTR("turn %d", (int)m_TurnNumber); fixed turnLengthFixed = fixed::FromInt(turnLength) / 1000; /* * In serialization test mode, we save the original (primary) simulation state before each turn update. * We run the update, then load the saved state into a secondary context. * We serialize that again and compare to the original serialization (to check that * serialize->deserialize->serialize is equivalent to serialize). * Then we run the update on the secondary context, and check that its new serialized * state matches the primary context after the update (to check that the simulation doesn't depend * on anything that's not serialized). * * In rejoin test mode, the secondary simulation is initialized from serialized data at turn N, then both * simulations run independantly while comparing their states each turn. This is way faster than a * complete serialization test and allows us to reproduce OOSes on rejoin. */ const bool serializationTestDebugDump = false; // set true to save human-readable state dumps before an error is detected, for debugging (but slow) const bool serializationTestHash = true; // set true to save and compare hash of state SerializationTestState primaryStateBefore; const ScriptInterface& scriptInterface = m_ComponentManager.GetScriptInterface(); const bool startRejoinTest = (int64_t) m_RejoinTestTurn == m_TurnNumber; if (startRejoinTest) m_TestingRejoin = true; if (m_EnableSerializationTest || m_TestingRejoin) { ENSURE(m_ComponentManager.SerializeState(primaryStateBefore.state)); if (serializationTestDebugDump) ENSURE(m_ComponentManager.DumpDebugState(primaryStateBefore.debug, false)); if (serializationTestHash) ENSURE(m_ComponentManager.ComputeStateHash(primaryStateBefore.hash, false)); } UpdateComponents(m_SimContext, turnLengthFixed, commands); if (m_EnableSerializationTest || startRejoinTest) { if (startRejoinTest) debug_printf("Initializing the secondary simulation\n"); delete m_SecondaryTerrain; m_SecondaryTerrain = new CTerrain(); delete m_SecondaryContext; m_SecondaryContext = new CSimContext(); m_SecondaryContext->m_Terrain = m_SecondaryTerrain; delete m_SecondaryComponentManager; m_SecondaryComponentManager = new CComponentManager(*m_SecondaryContext, scriptInterface.GetRuntime()); m_SecondaryComponentManager->LoadComponentTypes(); delete m_SecondaryLoadedScripts; m_SecondaryLoadedScripts = new std::set(); ENSURE(LoadDefaultScripts(*m_SecondaryComponentManager, m_SecondaryLoadedScripts)); ResetComponentState(*m_SecondaryComponentManager, false, false); // Load the trigger scripts after we have loaded the simulation. { JSContext* cx2 = m_SecondaryComponentManager->GetScriptInterface().GetContext(); JSAutoRequest rq2(cx2); JS::RootedValue mapSettingsCloned(cx2, m_SecondaryComponentManager->GetScriptInterface().CloneValueFromOtherContext( scriptInterface, m_MapSettings)); ENSURE(LoadTriggerScripts(*m_SecondaryComponentManager, mapSettingsCloned, m_SecondaryLoadedScripts)); } // Load the map into the secondary simulation LDR_BeginRegistering(); std::unique_ptr mapReader(new CMapReader); std::string mapType; scriptInterface.GetProperty(m_InitAttributes, "mapType", mapType); if (mapType == "random") { // TODO: support random map scripts debug_warn(L"Serialization test mode does not support random maps"); } else { std::wstring mapFile; scriptInterface.GetProperty(m_InitAttributes, "map", mapFile); VfsPath mapfilename = VfsPath(mapFile).ChangeExtension(L".pmp"); mapReader->LoadMap(mapfilename, scriptInterface.GetJSRuntime(), JS::UndefinedHandleValue, m_SecondaryTerrain, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, m_SecondaryContext, INVALID_PLAYER, true); // throws exception on failure } LDR_EndRegistering(); ENSURE(LDR_NonprogressiveLoad() == INFO::OK); ENSURE(m_SecondaryComponentManager->DeserializeState(primaryStateBefore.state)); } if (m_EnableSerializationTest || m_TestingRejoin) { SerializationTestState secondaryStateBefore; ENSURE(m_SecondaryComponentManager->SerializeState(secondaryStateBefore.state)); if (serializationTestDebugDump) ENSURE(m_SecondaryComponentManager->DumpDebugState(secondaryStateBefore.debug, false)); if (serializationTestHash) ENSURE(m_SecondaryComponentManager->ComputeStateHash(secondaryStateBefore.hash, false)); if (primaryStateBefore.state.str() != secondaryStateBefore.state.str() || primaryStateBefore.hash != secondaryStateBefore.hash) { ReportSerializationFailure(&primaryStateBefore, NULL, &secondaryStateBefore, NULL); } SerializationTestState primaryStateAfter; ENSURE(m_ComponentManager.SerializeState(primaryStateAfter.state)); if (serializationTestHash) ENSURE(m_ComponentManager.ComputeStateHash(primaryStateAfter.hash, false)); UpdateComponents(*m_SecondaryContext, turnLengthFixed, CloneCommandsFromOtherContext(scriptInterface, m_SecondaryComponentManager->GetScriptInterface(), commands)); SerializationTestState secondaryStateAfter; ENSURE(m_SecondaryComponentManager->SerializeState(secondaryStateAfter.state)); if (serializationTestHash) ENSURE(m_SecondaryComponentManager->ComputeStateHash(secondaryStateAfter.hash, false)); if (primaryStateAfter.state.str() != secondaryStateAfter.state.str() || primaryStateAfter.hash != secondaryStateAfter.hash) { // Only do the (slow) dumping now we know we're going to need to report it ENSURE(m_ComponentManager.DumpDebugState(primaryStateAfter.debug, false)); ENSURE(m_SecondaryComponentManager->DumpDebugState(secondaryStateAfter.debug, false)); ReportSerializationFailure(&primaryStateBefore, &primaryStateAfter, &secondaryStateBefore, &secondaryStateAfter); } } // Run the GC occasionally // No delay because a lot of garbage accumulates in one turn and in non-visual replays there are // much more turns in the same time than in normal games. // Every 500 turns we run a shrinking GC, which decommits unused memory and frees all JIT code. // Based on testing, this seems to be a good compromise between memory usage and performance. // Also check the comment about gcPreserveCode in the ScriptInterface code and this forum topic: // http://www.wildfiregames.com/forum/index.php?showtopic=18466&p=300323 // // (TODO: we ought to schedule this for a frame where we're not // running the sim update, to spread the load) if (m_TurnNumber % 500 == 0) scriptInterface.GetRuntime()->ShrinkingGC(); else scriptInterface.GetRuntime()->MaybeIncrementalGC(0.0f); if (m_EnableOOSLog) DumpState(); // Start computing AI for the next turn CmpPtr cmpAIManager(m_SimContext, SYSTEM_ENTITY); if (cmpAIManager) cmpAIManager->StartComputation(); ++m_TurnNumber; } void CSimulation2Impl::UpdateComponents(CSimContext& simContext, fixed turnLengthFixed, const std::vector& commands) { // TODO: the update process is pretty ugly, with lots of messages and dependencies // between different components. Ought to work out a nicer way to do this. CComponentManager& componentManager = simContext.GetComponentManager(); { PROFILE2("Sim - Update Start"); CMessageTurnStart msgTurnStart; componentManager.BroadcastMessage(msgTurnStart); } CmpPtr cmpPathfinder(simContext, SYSTEM_ENTITY); if (cmpPathfinder) { cmpPathfinder->FetchAsyncResultsAndSendMessages(); cmpPathfinder->UpdateGrid(); } // Push AI commands onto the queue before we use them CmpPtr cmpAIManager(simContext, SYSTEM_ENTITY); if (cmpAIManager) cmpAIManager->PushCommands(); CmpPtr cmpCommandQueue(simContext, SYSTEM_ENTITY); if (cmpCommandQueue) cmpCommandQueue->FlushTurn(commands); // Process newly generated move commands so the UI feels snappy if (cmpPathfinder) { cmpPathfinder->StartProcessingMoves(true); cmpPathfinder->FetchAsyncResultsAndSendMessages(); } // Send all the update phases { PROFILE2("Sim - Update"); CMessageUpdate msgUpdate(turnLengthFixed); componentManager.BroadcastMessage(msgUpdate); } { CMessageUpdate_MotionFormation msgUpdate(turnLengthFixed); componentManager.BroadcastMessage(msgUpdate); } // Process move commands for formations (group proxy) if (cmpPathfinder) { cmpPathfinder->StartProcessingMoves(true); cmpPathfinder->FetchAsyncResultsAndSendMessages(); } { PROFILE2("Sim - Motion Unit"); CMessageUpdate_MotionUnit msgUpdate(turnLengthFixed); componentManager.BroadcastMessage(msgUpdate); } { PROFILE2("Sim - Update Final"); CMessageUpdate_Final msgUpdate(turnLengthFixed); componentManager.BroadcastMessage(msgUpdate); } // Clean up any entities destroyed during the simulation update componentManager.FlushDestroyedComponents(); // Process all remaining moves if (cmpPathfinder) cmpPathfinder->StartProcessingMoves(false); } void CSimulation2Impl::Interpolate(float simFrameLength, float frameOffset, float realFrameLength) { PROFILE3("sim interpolate"); m_LastFrameOffset = frameOffset; CMessageInterpolate msg(simFrameLength, frameOffset, realFrameLength); m_ComponentManager.BroadcastMessage(msg); // Clean up any entities destroyed during interpolate (e.g. local corpses) m_ComponentManager.FlushDestroyedComponents(); } void CSimulation2Impl::DumpState() { PROFILE("DumpState"); std::stringstream name;\ name << std::setw(5) << std::setfill('0') << m_TurnNumber << ".txt"; const OsPath path = m_OOSLogPath / name.str(); std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc); if (!DirectoryExists(m_OOSLogPath)) { LOGWARNING("OOS-log directory %s was deleted, creating it again.", m_OOSLogPath.string8().c_str()); CreateDirectories(m_OOSLogPath, 0700); } file << "State hash: " << std::hex; std::string hashRaw; m_ComponentManager.ComputeStateHash(hashRaw, false); for (size_t i = 0; i < hashRaw.size(); ++i) file << std::setfill('0') << std::setw(2) << (int)(unsigned char)hashRaw[i]; file << std::dec << "\n"; file << "\n"; m_ComponentManager.DumpDebugState(file, true); std::ofstream binfile (OsString(path.ChangeExtension(L".dat")).c_str(), std::ofstream::out | std::ofstream::trunc | std::ofstream::binary); m_ComponentManager.SerializeState(binfile); } //////////////////////////////////////////////////////////////// CSimulation2::CSimulation2(CUnitManager* unitManager, shared_ptr rt, CTerrain* terrain) : m(new CSimulation2Impl(unitManager, rt, terrain)) { } CSimulation2::~CSimulation2() { delete m; } // Forward all method calls to the appropriate CSimulation2Impl/CComponentManager methods: void CSimulation2::EnableSerializationTest() { m->m_EnableSerializationTest = true; } void CSimulation2::EnableRejoinTest(int rejoinTestTurn) { m->m_RejoinTestTurn = rejoinTestTurn; } void CSimulation2::EnableOOSLog() { if (m->m_EnableOOSLog) return; m->m_EnableOOSLog = true; m->m_OOSLogPath = createDateIndexSubdirectory(psLogDir() / "oos_logs"); debug_printf("Writing ooslogs to %s\n", m->m_OOSLogPath.string8().c_str()); } entity_id_t CSimulation2::AddEntity(const std::wstring& templateName) { return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity()); } entity_id_t CSimulation2::AddEntity(const std::wstring& templateName, entity_id_t preferredId) { return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity(preferredId)); } entity_id_t CSimulation2::AddLocalEntity(const std::wstring& templateName) { return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewLocalEntity()); } void CSimulation2::DestroyEntity(entity_id_t ent) { m->m_ComponentManager.DestroyComponentsSoon(ent); } void CSimulation2::FlushDestroyedEntities() { m->m_ComponentManager.FlushDestroyedComponents(); } IComponent* CSimulation2::QueryInterface(entity_id_t ent, int iid) const { return m->m_ComponentManager.QueryInterface(ent, iid); } void CSimulation2::PostMessage(entity_id_t ent, const CMessage& msg) const { m->m_ComponentManager.PostMessage(ent, msg); } void CSimulation2::BroadcastMessage(const CMessage& msg) const { m->m_ComponentManager.BroadcastMessage(msg); } CSimulation2::InterfaceList CSimulation2::GetEntitiesWithInterface(int iid) { return m->m_ComponentManager.GetEntitiesWithInterface(iid); } const CSimulation2::InterfaceListUnordered& CSimulation2::GetEntitiesWithInterfaceUnordered(int iid) { return m->m_ComponentManager.GetEntitiesWithInterfaceUnordered(iid); } const CSimContext& CSimulation2::GetSimContext() const { return m->m_SimContext; } ScriptInterface& CSimulation2::GetScriptInterface() const { return m->m_ComponentManager.GetScriptInterface(); } void CSimulation2::PreInitGame() { JSContext* cx = GetScriptInterface().GetContext(); JSAutoRequest rq(cx); JS::RootedValue global(cx, GetScriptInterface().GetGlobalObject()); GetScriptInterface().CallFunctionVoid(global, "PreInitGame"); } void CSimulation2::InitGame() { JSContext* cx = GetScriptInterface().GetContext(); JSAutoRequest rq(cx); JS::RootedValue global(cx, GetScriptInterface().GetGlobalObject()); JS::RootedValue settings(cx); JS::RootedValue tmpInitAttributes(cx, GetInitAttributes()); GetScriptInterface().GetProperty(tmpInitAttributes, "settings", &settings); GetScriptInterface().CallFunctionVoid(global, "InitGame", settings); } void CSimulation2::Update(int turnLength) { std::vector commands; m->Update(turnLength, commands); } void CSimulation2::Update(int turnLength, const std::vector& commands) { m->Update(turnLength, commands); } void CSimulation2::Interpolate(float simFrameLength, float frameOffset, float realFrameLength) { m->Interpolate(simFrameLength, frameOffset, realFrameLength); } void CSimulation2::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling) { PROFILE3("sim submit"); CMessageRenderSubmit msg(collector, frustum, culling); m->m_ComponentManager.BroadcastMessage(msg); } float CSimulation2::GetLastFrameOffset() const { return m->m_LastFrameOffset; } bool CSimulation2::LoadScripts(const VfsPath& path) { return m->LoadScripts(m->m_ComponentManager, &m->m_LoadedScripts, path); } bool CSimulation2::LoadDefaultScripts() { return m->LoadDefaultScripts(m->m_ComponentManager, &m->m_LoadedScripts); } void CSimulation2::SetStartupScript(const std::string& code) { m->m_StartupScript = code; } const std::string& CSimulation2::GetStartupScript() { return m->m_StartupScript; } void CSimulation2::SetInitAttributes(JS::HandleValue attribs) { m->m_InitAttributes = attribs; } JS::Value CSimulation2::GetInitAttributes() { return m->m_InitAttributes.get(); } void CSimulation2::GetInitAttributes(JS::MutableHandleValue ret) { ret.set(m->m_InitAttributes); } void CSimulation2::SetMapSettings(const std::string& settings) { m->m_ComponentManager.GetScriptInterface().ParseJSON(settings, &m->m_MapSettings); } void CSimulation2::SetMapSettings(JS::HandleValue settings) { m->m_MapSettings = settings; m->InitRNGSeedSimulation(); m->InitRNGSeedAI(); } std::string CSimulation2::GetMapSettingsString() { return m->m_ComponentManager.GetScriptInterface().StringifyJSON(&m->m_MapSettings); } void CSimulation2::GetMapSettings(JS::MutableHandleValue ret) { ret.set(m->m_MapSettings); } void CSimulation2::LoadPlayerSettings(bool newPlayers) { JSContext* cx = GetScriptInterface().GetContext(); JSAutoRequest rq(cx); JS::RootedValue global(cx, GetScriptInterface().GetGlobalObject()); GetScriptInterface().CallFunctionVoid(global, "LoadPlayerSettings", m->m_MapSettings, newPlayers); } void CSimulation2::LoadMapSettings() { JSContext* cx = GetScriptInterface().GetContext(); JSAutoRequest rq(cx); JS::RootedValue global(cx, GetScriptInterface().GetGlobalObject()); // Initialize here instead of in Update() GetScriptInterface().CallFunctionVoid(global, "LoadMapSettings", m->m_MapSettings); GetScriptInterface().FreezeObject(m->m_InitAttributes, true); GetScriptInterface().SetGlobal("InitAttributes", m->m_InitAttributes, true, true, true); if (!m->m_StartupScript.empty()) GetScriptInterface().LoadScript(L"map startup script", m->m_StartupScript); // Load the trigger scripts after we have loaded the simulation and the map. m->LoadTriggerScripts(m->m_ComponentManager, m->m_MapSettings, &m->m_LoadedScripts); } int CSimulation2::ProgressiveLoad() { return m->ProgressiveLoad(); } Status CSimulation2::ReloadChangedFile(const VfsPath& path) { return m->ReloadChangedFile(path); } void CSimulation2::ResetState(bool skipScriptedComponents, bool skipAI) { m->ResetState(skipScriptedComponents, skipAI); } bool CSimulation2::ComputeStateHash(std::string& outHash, bool quick) { return m->m_ComponentManager.ComputeStateHash(outHash, quick); } bool CSimulation2::DumpDebugState(std::ostream& stream) { return m->m_ComponentManager.DumpDebugState(stream, true); } bool CSimulation2::SerializeState(std::ostream& stream) { return m->m_ComponentManager.SerializeState(stream); } bool CSimulation2::DeserializeState(std::istream& stream) { // TODO: need to make sure the required SYSTEM_ENTITY components get constructed return m->m_ComponentManager.DeserializeState(stream); } std::string CSimulation2::GenerateSchema() { return m->m_ComponentManager.GenerateSchema(); } static std::vector GetJSONData(const VfsPath& path) { VfsPaths pathnames; Status ret = vfs::GetPathnames(g_VFS, path, L"*.json", pathnames); if (ret != INFO::OK) { // Some error reading directory wchar_t error[200]; LOGERROR("Error reading directory '%s': %s", path.string8(), utf8_from_wstring(StatusDescription(ret, error, ARRAY_SIZE(error)))); return std::vector(); } std::vector data; for (const VfsPath& p : pathnames) { // Load JSON file CVFSFile file; PSRETURN ret = file.Load(g_VFS, p); if (ret != PSRETURN_OK) { LOGERROR("GetJSONData: Failed to load file '%s': %s", p.string8(), GetErrorString(ret)); continue; } data.push_back(file.DecodeUTF8()); // assume it's UTF-8 } return data; } std::vector CSimulation2::GetRMSData() { return GetJSONData(L"maps/random/"); } std::vector CSimulation2::GetCivData() { return GetJSONData(L"simulation/data/civs/"); } +std::vector CSimulation2::GetVictoryConditiondData() +{ + return GetJSONData(L"simulation/data/settings/victory_conditions/"); +} + static std::string ReadJSON(const VfsPath& path) { if (!VfsFileExists(path)) { LOGERROR("File '%s' does not exist", path.string8()); return std::string(); } // Load JSON file CVFSFile file; PSRETURN ret = file.Load(g_VFS, path); if (ret != PSRETURN_OK) { LOGERROR("Failed to load file '%s': %s", path.string8(), GetErrorString(ret)); return std::string(); } return file.DecodeUTF8(); // assume it's UTF-8 } std::string CSimulation2::GetPlayerDefaults() { return ReadJSON(L"simulation/data/settings/player_defaults.json"); } std::string CSimulation2::GetMapSizes() { return ReadJSON(L"simulation/data/settings/map_sizes.json"); } std::string CSimulation2::GetAIData() { const ScriptInterface& scriptInterface = GetScriptInterface(); JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); JS::RootedValue aiData(cx, ICmpAIManager::GetAIs(scriptInterface)); // Build single JSON string with array of AI data JS::RootedValue ais(cx); if (!ScriptInterface::CreateObject(cx, &ais, "AIData", aiData)) return std::string(); return scriptInterface.StringifyJSON(&ais); } Index: ps/trunk/source/simulation2/Simulation2.h =================================================================== --- ps/trunk/source/simulation2/Simulation2.h (revision 23846) +++ ps/trunk/source/simulation2/Simulation2.h (revision 23847) @@ -1,277 +1,284 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_SIMULATION2 #define INCLUDED_SIMULATION2 #include "lib/file/vfs/vfs_path.h" #include "scriptinterface/ScriptVal.h" #include "simulation2/helpers/SimulationCommand.h" #include "simulation2/system/CmpPtr.h" #include "simulation2/system/Components.h" #include #include class CFrustum; class CMessage; class CSimContext; class CSimulation2Impl; class CTerrain; class CUnitManager; class IComponent; class SceneCollector; class ScriptInterface; class ScriptRuntime; /** * Public API for simulation system. * Most code should interact with the simulation only through this API. */ class CSimulation2 { NONCOPYABLE(CSimulation2); public: // TODO: CUnitManager should probably be handled automatically by this // module, but for now we'll have it passed in externally instead CSimulation2(CUnitManager* unitManager, shared_ptr rt, CTerrain* terrain); ~CSimulation2(); void EnableSerializationTest(); void EnableRejoinTest(int rejoinTestTurn); void EnableOOSLog(); /** * Load all scripts in the specified directory (non-recursively), * so they can register new component types and functions. This * should be called immediately after constructing the CSimulation2 object. * @return false on failure */ bool LoadScripts(const VfsPath& path); /** * Call LoadScripts for each of the game's standard simulation script paths. * @return false on failure */ bool LoadDefaultScripts(); /** * Loads the player settings script (called before map is loaded) * @param newPlayers will delete all the existing player entities (if any) and create new ones * (needed for loading maps, but Atlas might want to update existing player data) */ void LoadPlayerSettings(bool newPlayers); /** * Loads the map settings script (called after map is loaded) */ void LoadMapSettings(); /** * Set a startup script, which will get executed before the first turn. */ void SetStartupScript(const std::string& script); /** * Get the current startup script. */ const std::string& GetStartupScript(); /** * Set the attributes identifying the scenario/RMS used to initialise this * simulation. */ void SetInitAttributes(JS::HandleValue settings); /** * Get the data passed to SetInitAttributes. */ JS::Value GetInitAttributes(); void GetInitAttributes(JS::MutableHandleValue ret); /** * Set the initial map settings (as a UTF-8-encoded JSON string), * which will be used to set up the simulation state. * Called from atlas. */ void SetMapSettings(const std::string& settings); /** * Set the initial map settings, which will be used * to set up the simulation state. * Called from MapReader (for all map-types). */ void SetMapSettings(JS::HandleValue settings); /** * Get the current map settings as a UTF-8 JSON string. */ std::string GetMapSettingsString(); /** * Get the current map settings. */ void GetMapSettings(JS::MutableHandleValue ret); /** * RegMemFun incremental loader function. */ int ProgressiveLoad(); /** * Reload any scripts that were loaded from the given filename. * (This is used to implement hotloading.) */ Status ReloadChangedFile(const VfsPath& path); /** * Initialise (or re-initialise) the complete simulation state. * Must be called after LoadScripts, and must be called * before any methods that depend on the simulation state. * @param skipScriptedComponents don't load the scripted system components * (this is intended for use by test cases that don't mount all of VFS) * @param skipAI don't initialise the AI system * (this is intended for use by test cases that don't want all entity * templates loaded automatically) */ void ResetState(bool skipScriptedComponents = false, bool skipAI = false); /** * Replace/destroy some entities (e.g. skirmish replacers) * Called right before InitGame, on CGame instantiation. * (This mustn't be used when e.g. loading saved games, only when starting new ones.) * This calls the PreInitGame function defined in helpers/InitGame.js. */ void PreInitGame(); /** * Initialise a new game, based on some script data. (Called on CGame instantiation) * (This mustn't be used when e.g. loading saved games, only when starting new ones.) * This calls the InitGame function defined in helpers/InitGame.js. */ void InitGame(); void Update(int turnLength); void Update(int turnLength, const std::vector& commands); void Interpolate(float simFrameLength, float frameOffset, float realFrameLength); void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling); /** * Returns the last frame offset passed to Interpolate(), i.e. the offset corresponding * to the currently-rendered scene. */ float GetLastFrameOffset() const; /** * Construct a new entity and add it to the world. * @param templateName see ICmpTemplateManager for syntax * @return the new entity ID, or INVALID_ENTITY on error */ entity_id_t AddEntity(const std::wstring& templateName); entity_id_t AddEntity(const std::wstring& templateName, entity_id_t preferredId); entity_id_t AddLocalEntity(const std::wstring& templateName); /** * Destroys the specified entity, once FlushDestroyedEntities is called. * Has no effect if the entity does not exist, or has already been added to the destruction queue. */ void DestroyEntity(entity_id_t ent); /** * Does the actual destruction of entities from DestroyEntity. * This is called automatically by Update, but should also be called at other * times when an entity might have been deleted and should be removed from * any further processing (e.g. after editor UI message processing) */ void FlushDestroyedEntities(); IComponent* QueryInterface(entity_id_t ent, int iid) const; void PostMessage(entity_id_t ent, const CMessage& msg) const; void BroadcastMessage(const CMessage& msg) const; using InterfaceList = std::vector >; using InterfaceListUnordered = std::unordered_map; /** * Returns a list of components implementing the given interface, and their * associated entities, sorted by entity ID. */ InterfaceList GetEntitiesWithInterface(int iid); /** * Returns a list of components implementing the given interface, and their * associated entities, as an unordered map. */ const InterfaceListUnordered& GetEntitiesWithInterfaceUnordered(int iid); const CSimContext& GetSimContext() const; ScriptInterface& GetScriptInterface() const; bool ComputeStateHash(std::string& outHash, bool quick); bool DumpDebugState(std::ostream& stream); bool SerializeState(std::ostream& stream); bool DeserializeState(std::istream& stream); std::string GenerateSchema(); ///////////////////////////////////////////////////////////////////////////// // Some functions for Atlas UI to be able to access VFS data /** * Get random map script data * * @return vector of strings containing JSON format data */ std::vector GetRMSData(); /** * Get civilization data * * @return vector of strings containing JSON format data */ std::vector GetCivData(); /** + * Get victory condition data + * + * @return vector of strings containing JSON format data + */ + std::vector GetVictoryConditiondData(); + + /** * Get player default data * * @return string containing JSON format data */ std::string GetPlayerDefaults(); /** * Get map sizes data * * @return string containing JSON format data */ std::string GetMapSizes(); /** * Get AI data * * @return string containing JSON format data */ std::string GetAIData(); private: CSimulation2Impl* m; }; #endif // INCLUDED_SIMULATION2 Index: ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp (revision 23846) +++ ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp (revision 23847) @@ -1,658 +1,709 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Map.h" #include "AtlasObject/AtlasObject.h" +#include "AtlasObject/JSONSpiritInclude.h" #include "GameInterface/Messages.h" #include "ScenarioEditor/ScenarioEditor.h" #include "ScenarioEditor/Tools/Common/Tools.h" -#include "wx/busyinfo.h" -#include "wx/filename.h" +#include +#include #define CREATE_CHECKBOX(window, parentSizer, name, description, ID) \ parentSizer->Add(new wxStaticText(window, wxID_ANY, _(name)), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); \ parentSizer->Add(Tooltipped(new wxCheckBox(window, ID, wxEmptyString), _(description))); enum { ID_MapName, ID_MapDescription, ID_MapReveal, ID_MapType, ID_MapPreview, ID_MapTeams, ID_MapKW_Demo, ID_MapKW_Naval, ID_MapKW_New, ID_MapKW_Trigger, - ID_VC_Conquest, - ID_VC_ConquestUnits, - ID_VC_ConquestStructures, - ID_VC_CaptureTheRelic, - ID_VC_Wonder, - ID_VC_Regicide, ID_RandomScript, ID_RandomSize, ID_RandomNomad, ID_RandomSeed, ID_RandomReseed, ID_RandomGenerate, ID_SimPlay, ID_SimFast, ID_SimSlow, ID_SimPause, ID_SimReset, ID_OpenPlayerPanel }; enum { SimInactive, SimPlaying, SimPlayingFast, SimPlayingSlow, SimPaused }; bool IsPlaying(int s) { return (s == SimPlaying || s == SimPlayingFast || s == SimPlayingSlow); } // TODO: Some of these helper things should be moved out of this file // and into shared locations // Helper function for adding tooltips static wxWindow* Tooltipped(wxWindow* window, const wxString& tip) { window->SetToolTip(tip); return window; } // Helper class for storing AtObjs class AtObjClientData : public wxClientData { public: AtObjClientData(const AtObj& obj) : obj(obj) {} virtual ~AtObjClientData() {} AtObj GetValue() { return obj; } private: AtObj obj; }; class MapSettingsControl : public wxPanel { public: MapSettingsControl(wxWindow* parent, ScenarioEditor& scenarioEditor); void CreateWidgets(); void ReadFromEngine(); void SetMapSettings(const AtObj& obj); AtObj UpdateSettingsObject(); private: void SendToEngine(); - void OnConquestChanged(); + void OnVictoryConditionChanged(long controlId); void OnEdit(wxCommandEvent& evt) { + OnVictoryConditionChanged(evt.GetId()); SendToEngine(); - if (evt.GetId() == ID_VC_Conquest) - OnConquestChanged(); } + std::map m_VictoryConditions; std::set m_MapSettingsKeywords; std::set m_MapSettingsVictoryConditions; std::vector m_PlayerCivChoices; Observable& m_MapSettings; DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(MapSettingsControl, wxPanel) EVT_TEXT(ID_MapName, MapSettingsControl::OnEdit) EVT_TEXT(ID_MapDescription, MapSettingsControl::OnEdit) EVT_TEXT(ID_MapPreview, MapSettingsControl::OnEdit) EVT_CHECKBOX(wxID_ANY, MapSettingsControl::OnEdit) EVT_CHOICE(wxID_ANY, MapSettingsControl::OnEdit) END_EVENT_TABLE(); MapSettingsControl::MapSettingsControl(wxWindow* parent, ScenarioEditor& scenarioEditor) : wxPanel(parent, wxID_ANY), m_MapSettings(scenarioEditor.GetMapSettings()) { wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Map settings")); SetSizer(sizer); } void MapSettingsControl::CreateWidgets() { wxSizer* sizer = GetSizer(); ///////////////////////////////////////////////////////////////////////// // Map settings wxBoxSizer* nameSizer = new wxBoxSizer(wxHORIZONTAL); nameSizer->Add(new wxStaticText(this, wxID_ANY, _("Name")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); nameSizer->Add(8, 0); nameSizer->Add(Tooltipped(new wxTextCtrl(this, ID_MapName), _("Displayed name of the map")), wxSizerFlags().Proportion(1)); sizer->Add(nameSizer, wxSizerFlags().Expand()); sizer->Add(0, 2); sizer->Add(new wxStaticText(this, wxID_ANY, _("Description"))); sizer->Add(Tooltipped(new wxTextCtrl(this, ID_MapDescription, wxEmptyString, wxDefaultPosition, wxSize(-1, 100), wxTE_MULTILINE), _("Short description used on the map selection screen")), wxSizerFlags().Expand()); sizer->AddSpacer(5); wxFlexGridSizer* gridSizer = new wxFlexGridSizer(2, 5, 5); gridSizer->AddGrowableCol(1); // TODO: have preview selector tool? gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Preview")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(new wxTextCtrl(this, ID_MapPreview, wxEmptyString), _("Texture used for map preview")), wxSizerFlags().Expand()); CREATE_CHECKBOX(this, gridSizer, "Reveal map", "If checked, players won't need to explore", ID_MapReveal); CREATE_CHECKBOX(this, gridSizer, "Lock teams", "If checked, teams will be locked", ID_MapTeams); sizer->Add(gridSizer, wxSizerFlags().Expand()); sizer->AddSpacer(5); - // TODO: replace by names in binaries/data/mods/public/simulation/data/settings/victory_conditions/ wxStaticBoxSizer* victoryConditionSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Victory Conditions")); wxFlexGridSizer* vcGridSizer = new wxFlexGridSizer(2, 0, 5); vcGridSizer->AddGrowableCol(1); - CREATE_CHECKBOX(this, vcGridSizer, "Conquest", "Select Conquest victory condition", ID_VC_Conquest); - CREATE_CHECKBOX(this, vcGridSizer, "Conquest Units", "Select Conquest Units victory condition", ID_VC_ConquestUnits); - CREATE_CHECKBOX(this, vcGridSizer, "Conquest Structures", "Select Conquest Structures victory condition", ID_VC_ConquestStructures); - CREATE_CHECKBOX(this, vcGridSizer, "Capture the Relic", "Select Capture the Relic victory condition", ID_VC_CaptureTheRelic); - CREATE_CHECKBOX(this, vcGridSizer, "Wonder", "Select Wonder victory condition", ID_VC_Wonder); - CREATE_CHECKBOX(this, vcGridSizer, "Regicide", "Select Regicide victory condition", ID_VC_Regicide); + + AtlasMessage::qGetVictoryConditionData qryVictoryCondition; + qryVictoryCondition.Post(); + std::vector victoryConditionData = *qryVictoryCondition.data; + + for (const std::string& victoryConditionJson : victoryConditionData) + { + AtObj victoryCondition = AtlasObject::LoadFromJSON(victoryConditionJson); + long index = wxWindow::NewControlId(); + wxString title = wxString::FromUTF8(victoryCondition["Data"]["Title"]); + m_VictoryConditions.insert(std::pair(index, victoryCondition)); + CREATE_CHECKBOX(this, vcGridSizer, title.MakeLower(), "Select " + wxString::FromUTF8(victoryCondition["Data"]["Title"]) + " victory condition.", index); + } + victoryConditionSizer->Add(vcGridSizer); sizer->Add(victoryConditionSizer, wxSizerFlags().Expand()); sizer->AddSpacer(5); wxStaticBoxSizer* keywordsSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Keywords")); wxFlexGridSizer* kwGridSizer = new wxFlexGridSizer(4, 5, 15); CREATE_CHECKBOX(this, kwGridSizer, "Demo", "If checked, map will only be visible using filters in game setup", ID_MapKW_Demo); CREATE_CHECKBOX(this, kwGridSizer, "Naval", "If checked, map will only be visible using filters in game setup", ID_MapKW_Naval); CREATE_CHECKBOX(this, kwGridSizer, "New", "If checked, the map will appear in the list of new maps", ID_MapKW_New); CREATE_CHECKBOX(this, kwGridSizer, "Trigger", "If checked, the map will appear in the list of maps with trigger scripts", ID_MapKW_Trigger); keywordsSizer->Add(kwGridSizer); sizer->Add(keywordsSizer, wxSizerFlags().Expand()); } void MapSettingsControl::ReadFromEngine() { AtlasMessage::qGetMapSettings qry; qry.Post(); if (!(*qry.settings).empty()) { // Prevent error if there's no map settings to parse m_MapSettings = AtlasObject::LoadFromJSON(*qry.settings); } // map name wxDynamicCast(FindWindow(ID_MapName), wxTextCtrl)->ChangeValue(wxString::FromUTF8(m_MapSettings["Name"])); // map description wxDynamicCast(FindWindow(ID_MapDescription), wxTextCtrl)->ChangeValue(wxString::FromUTF8(m_MapSettings["Description"])); // map preview wxDynamicCast(FindWindow(ID_MapPreview), wxTextCtrl)->ChangeValue(wxString::FromUTF8(m_MapSettings["Preview"])); // reveal map wxDynamicCast(FindWindow(ID_MapReveal), wxCheckBox)->SetValue(wxString::FromUTF8(m_MapSettings["RevealMap"]) == "true"); // victory conditions m_MapSettingsVictoryConditions.clear(); for (AtIter victoryCondition = m_MapSettings["VictoryConditions"]["item"]; victoryCondition.defined(); ++victoryCondition) m_MapSettingsVictoryConditions.insert(std::string(victoryCondition)); - wxWindow* window; -#define INIT_CHECKBOX(ID, mapSettings, value) \ - window = FindWindow(ID); \ - if (window != nullptr) \ - wxDynamicCast(window, wxCheckBox)->SetValue(mapSettings.count(value) != 0); - - INIT_CHECKBOX(ID_VC_Conquest, m_MapSettingsVictoryConditions, "conquest"); - INIT_CHECKBOX(ID_VC_ConquestUnits, m_MapSettingsVictoryConditions, "conquest_units"); - INIT_CHECKBOX(ID_VC_ConquestStructures, m_MapSettingsVictoryConditions, "conquest_structures"); - INIT_CHECKBOX(ID_VC_CaptureTheRelic, m_MapSettingsVictoryConditions, "capture_the_relic"); - INIT_CHECKBOX(ID_VC_Wonder, m_MapSettingsVictoryConditions, "wonder"); - INIT_CHECKBOX(ID_VC_Regicide, m_MapSettingsVictoryConditions, "regicide"); - OnConquestChanged(); + // Clear Checkboxes before loading data. We don't update victory condition just yet because it might reenable some of the checkboxes. + for (const std::pair& vc : m_VictoryConditions) + { + wxCheckBox* checkBox = wxDynamicCast(FindWindow(vc.first), wxCheckBox); + if (!checkBox) + continue; + + checkBox->SetValue(false); + checkBox->Enable(true); + } + + for (const std::pair& vc : m_VictoryConditions) + { + if (m_MapSettingsVictoryConditions.count(wxString::FromUTF8(vc.second["Data"]["Title"]).Lower().ToStdString()) == 0) + continue; + + wxCheckBox* checkBox = wxDynamicCast(FindWindow(vc.first), wxCheckBox); + if (!checkBox) + continue; + + checkBox->SetValue(true); + OnVictoryConditionChanged(vc.first); + } // lock teams wxDynamicCast(FindWindow(ID_MapTeams), wxCheckBox)->SetValue(wxString::FromUTF8(m_MapSettings["LockTeams"]) == "true"); // keywords { m_MapSettingsKeywords.clear(); for (AtIter keyword = m_MapSettings["Keywords"]["item"]; keyword.defined(); ++keyword) m_MapSettingsKeywords.insert(std::string(keyword)); - + wxWindow* window; + #define INIT_CHECKBOX(ID, mapSettings, value) \ + window = FindWindow(ID); \ + if (window != nullptr) \ + wxDynamicCast(window, wxCheckBox)->SetValue(mapSettings.count(value) != 0); INIT_CHECKBOX(ID_MapKW_Demo, m_MapSettingsKeywords, "demo"); INIT_CHECKBOX(ID_MapKW_Naval, m_MapSettingsKeywords, "naval"); INIT_CHECKBOX(ID_MapKW_New, m_MapSettingsKeywords, "new"); INIT_CHECKBOX(ID_MapKW_Trigger, m_MapSettingsKeywords, "trigger"); + #undef INIT_CHECKBOX } - -#undef INIT_CHECKBOX } void MapSettingsControl::SetMapSettings(const AtObj& obj) { m_MapSettings = obj; m_MapSettings.NotifyObservers(); SendToEngine(); } -// TODO Use the json data for this -void MapSettingsControl::OnConquestChanged() +void MapSettingsControl::OnVictoryConditionChanged(long controlId) { - bool conqestEnabled = wxDynamicCast(FindWindow(ID_VC_Conquest), wxCheckBox)->GetValue(); + AtObj victoryCondition; - wxCheckBox* conquestUnitsCheckbox = wxDynamicCast(FindWindow(ID_VC_ConquestUnits), wxCheckBox); - conquestUnitsCheckbox->Enable(!conqestEnabled); - wxCheckBox* conquestStructuresCheckbox = wxDynamicCast(FindWindow(ID_VC_ConquestStructures), wxCheckBox); - conquestStructuresCheckbox->Enable(!conqestEnabled); - if (conqestEnabled) + for (const std::pair& vc : m_VictoryConditions) { - conquestUnitsCheckbox->SetValue(false); - conquestStructuresCheckbox->SetValue(false); + if(vc.first != controlId) + continue; + + victoryCondition = vc.second; + break; + } + + // ChangeOnChecked and DisabledWhenChecked use file names instead of victory titles so we have to convert them. + + for (AtIter victoryConditionPair = victoryCondition["Data"]["ChangeOnChecked"]; victoryConditionPair.defined(); ++victoryConditionPair) + { + for (const std::pair& vc : m_VictoryConditions) + { + std::string escapedTitle = wxString::FromUTF8(vc.second["Data"]["Title"]).Lower().ToStdString(); + for (std::string::iterator it = escapedTitle.begin(); it != escapedTitle.end(); ++it) { + if (*it == ' ') + *it = '_'; + } + + if (victoryConditionPair[escapedTitle.c_str()].defined()) + { + wxCheckBox* victoryConditionCheckBox = wxDynamicCast(FindWindow(vc.first), wxCheckBox); + victoryConditionCheckBox->SetValue(wxString::FromUTF8(victoryConditionPair[escapedTitle.c_str()]).Lower().ToStdString() == "true"); + break; + } + } + } + + bool controlEnabled = wxDynamicCast(FindWindow(controlId), wxCheckBox)->GetValue(); + for (AtIter victoryConditionTitle = victoryCondition["Data"]["DisabledWhenChecked"]; victoryConditionTitle.defined(); ++victoryConditionTitle) + { + for (const std::pair& vc : m_VictoryConditions) + { + std::string escapedTitle = wxString::FromUTF8(vc.second["Data"]["Title"]).Lower().ToStdString(); + for (std::string::iterator it = escapedTitle.begin(); it != escapedTitle.end(); ++it) { + if (*it == ' ') + *it = '_'; + } + + if (escapedTitle == wxString::FromUTF8(victoryConditionTitle["item"]).ToStdString()) + { + wxCheckBox* victoryConditionCheckBox = wxDynamicCast(FindWindow(vc.first), wxCheckBox); + victoryConditionCheckBox->Enable(!controlEnabled); + victoryConditionCheckBox->SetValue(!controlEnabled); + break; + } + } } } AtObj MapSettingsControl::UpdateSettingsObject() { // map name m_MapSettings.set("Name", wxDynamicCast(FindWindow(ID_MapName), wxTextCtrl)->GetValue().utf8_str()); // map description m_MapSettings.set("Description", wxDynamicCast(FindWindow(ID_MapDescription), wxTextCtrl)->GetValue().utf8_str()); // map preview m_MapSettings.set("Preview", wxDynamicCast(FindWindow(ID_MapPreview), wxTextCtrl)->GetValue().utf8_str()); // reveal map m_MapSettings.setBool("RevealMap", wxDynamicCast(FindWindow(ID_MapReveal), wxCheckBox)->GetValue()); // victory conditions #define INSERT_VICTORY_CONDITION_CHECKBOX(name, ID) \ if (wxDynamicCast(FindWindow(ID), wxCheckBox)->GetValue()) \ m_MapSettingsVictoryConditions.insert(name); \ else \ m_MapSettingsVictoryConditions.erase(name); - INSERT_VICTORY_CONDITION_CHECKBOX("conquest", ID_VC_Conquest); - INSERT_VICTORY_CONDITION_CHECKBOX("conquest_units", ID_VC_ConquestUnits); - INSERT_VICTORY_CONDITION_CHECKBOX("conquest_structures", ID_VC_ConquestStructures); - INSERT_VICTORY_CONDITION_CHECKBOX("capture_the_relic", ID_VC_CaptureTheRelic); - INSERT_VICTORY_CONDITION_CHECKBOX("wonder", ID_VC_Wonder); - INSERT_VICTORY_CONDITION_CHECKBOX("regicide", ID_VC_Regicide); + for (const std::pair& vc : m_VictoryConditions) + INSERT_VICTORY_CONDITION_CHECKBOX(wxString::FromUTF8(vc.second["Data"]["Title"]).Lower().ToStdString(), vc.first) #undef INSERT_VICTORY_CONDITION_CHECKBOX AtObj victoryConditions; victoryConditions.set("@array", ""); for (const std::string& victoryCondition : m_MapSettingsVictoryConditions) victoryConditions.add("item", victoryCondition.c_str()); m_MapSettings.set("VictoryConditions", victoryConditions); // keywords { #define INSERT_KEYWORDS_CHECKBOX(name, ID) \ if (wxDynamicCast(FindWindow(ID), wxCheckBox)->GetValue()) \ m_MapSettingsKeywords.insert(name); \ else \ m_MapSettingsKeywords.erase(name); INSERT_KEYWORDS_CHECKBOX("demo", ID_MapKW_Demo); INSERT_KEYWORDS_CHECKBOX("naval", ID_MapKW_Naval); INSERT_KEYWORDS_CHECKBOX("new", ID_MapKW_New); INSERT_KEYWORDS_CHECKBOX("trigger", ID_MapKW_Trigger); #undef INSERT_KEYWORDS_CHECKBOX AtObj keywords; keywords.set("@array", ""); for (const std::string& keyword : m_MapSettingsKeywords) keywords.add("item", keyword.c_str()); m_MapSettings.set("Keywords", keywords); } // teams locked m_MapSettings.setBool("LockTeams", wxDynamicCast(FindWindow(ID_MapTeams), wxCheckBox)->GetValue()); // default AI RNG seed m_MapSettings.setInt("AISeed", 0); return m_MapSettings; } void MapSettingsControl::SendToEngine() { UpdateSettingsObject(); std::string json = AtlasObject::SaveToJSON(m_MapSettings); // TODO: would be nice if we supported undo for settings changes POST_COMMAND(SetMapSettings, (json)); } MapSidebar::MapSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer) : Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer), m_SimState(SimInactive) { wxSizer* scrollSizer = new wxBoxSizer(wxVERTICAL); wxScrolledWindow* scrolledWindow = new wxScrolledWindow(this); scrolledWindow->SetScrollRate(10, 10); scrolledWindow->SetSizer(scrollSizer); m_MainSizer->Add(scrolledWindow, wxSizerFlags().Expand().Proportion(1)); m_MapSettingsCtrl = new MapSettingsControl(scrolledWindow, m_ScenarioEditor); scrollSizer->Add(m_MapSettingsCtrl, wxSizerFlags().Expand()); { ///////////////////////////////////////////////////////////////////////// // Random map settings wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, scrolledWindow, _("Random map")); scrollSizer->Add(sizer, wxSizerFlags().Expand()); sizer->Add(new wxChoice(scrolledWindow, ID_RandomScript), wxSizerFlags().Expand()); sizer->AddSpacer(5); sizer->Add(new wxButton(scrolledWindow, ID_OpenPlayerPanel, _T("Change players")), wxSizerFlags().Expand()); sizer->AddSpacer(5); wxFlexGridSizer* gridSizer = new wxFlexGridSizer(2, 5, 5); gridSizer->AddGrowableCol(1); wxChoice* sizeChoice = new wxChoice(scrolledWindow, ID_RandomSize); gridSizer->Add(new wxStaticText(scrolledWindow, wxID_ANY, _("Map size")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(sizeChoice, wxSizerFlags().Expand()); CREATE_CHECKBOX(scrolledWindow, gridSizer, "Nomad", "Place only some units instead of starting bases.", ID_RandomNomad); gridSizer->Add(new wxStaticText(scrolledWindow, wxID_ANY, _("Random seed")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); wxBoxSizer* seedSizer = new wxBoxSizer(wxHORIZONTAL); seedSizer->Add(Tooltipped(new wxTextCtrl(scrolledWindow, ID_RandomSeed, _T("0"), wxDefaultPosition, wxDefaultSize, 0, wxTextValidator(wxFILTER_NUMERIC)), _("Seed value for random map")), wxSizerFlags(1).Expand()); seedSizer->Add(Tooltipped(new wxButton(scrolledWindow, ID_RandomReseed, _("R"), wxDefaultPosition, wxSize(40, -1)), _("New random seed"))); gridSizer->Add(seedSizer, wxSizerFlags().Expand()); sizer->Add(gridSizer, wxSizerFlags().Expand()); sizer->AddSpacer(5); sizer->Add(Tooltipped(new wxButton(scrolledWindow, ID_RandomGenerate, _("Generate map")), _("Run selected random map script")), wxSizerFlags().Expand()); } { ///////////////////////////////////////////////////////////////////////// // Simulation buttons wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, scrolledWindow, _("Simulation test")); scrollSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 8)); wxGridSizer* gridSizer = new wxGridSizer(5); gridSizer->Add(Tooltipped(new wxButton(scrolledWindow, ID_SimPlay, _("Play"), wxDefaultPosition, wxSize(48, -1)), _("Run the simulation at normal speed")), wxSizerFlags().Expand()); gridSizer->Add(Tooltipped(new wxButton(scrolledWindow, ID_SimFast, _("Fast"), wxDefaultPosition, wxSize(48, -1)), _("Run the simulation at 8x speed")), wxSizerFlags().Expand()); gridSizer->Add(Tooltipped(new wxButton(scrolledWindow, ID_SimSlow, _("Slow"), wxDefaultPosition, wxSize(48, -1)), _("Run the simulation at 1/8x speed")), wxSizerFlags().Expand()); gridSizer->Add(Tooltipped(new wxButton(scrolledWindow, ID_SimPause, _("Pause"), wxDefaultPosition, wxSize(48, -1)), _("Pause the simulation")), wxSizerFlags().Expand()); gridSizer->Add(Tooltipped(new wxButton(scrolledWindow, ID_SimReset, _("Reset"), wxDefaultPosition, wxSize(48, -1)), _("Reset the editor to initial state")), wxSizerFlags().Expand()); sizer->Add(gridSizer, wxSizerFlags().Expand()); UpdateSimButtons(); } } void MapSidebar::OnCollapse(wxCollapsiblePaneEvent& WXUNUSED(evt)) { Freeze(); // Toggling the collapsing doesn't seem to update the sidebar layout // automatically, so do it explicitly here Layout(); Refresh(); // fixes repaint glitch on Windows Thaw(); } void MapSidebar::OnFirstDisplay() { // We do this here becase messages are used which requires simulation to be init'd m_MapSettingsCtrl->CreateWidgets(); m_MapSettingsCtrl->ReadFromEngine(); // Load the map sizes list AtlasMessage::qGetMapSizes qrySizes; qrySizes.Post(); AtObj sizes = AtlasObject::LoadFromJSON(*qrySizes.sizes); wxChoice* sizeChoice = wxDynamicCast(FindWindow(ID_RandomSize), wxChoice); for (AtIter s = sizes["Data"]["item"]; s.defined(); ++s) sizeChoice->Append(wxString::FromUTF8(s["Name"]), reinterpret_cast((*s["Tiles"]).getLong())); sizeChoice->SetSelection(0); // Load the RMS script list AtlasMessage::qGetRMSData qry; qry.Post(); std::vector scripts = *qry.data; wxChoice* scriptChoice = wxDynamicCast(FindWindow(ID_RandomScript), wxChoice); scriptChoice->Clear(); for (size_t i = 0; i < scripts.size(); ++i) { AtObj data = AtlasObject::LoadFromJSON(scripts[i]); wxString name = wxString::FromUTF8(data["settings"]["Name"]); if (!name.IsEmpty()) scriptChoice->Append(name, new AtObjClientData(*data["settings"])); } scriptChoice->SetSelection(0); Layout(); } void MapSidebar::OnMapReload() { m_MapSettingsCtrl->ReadFromEngine(); // Reset sim test buttons POST_MESSAGE(SimPlay, (0.f, false)); POST_MESSAGE(SimStopMusic, ()); POST_MESSAGE(GuiSwitchPage, (L"page_atlas.xml")); m_SimState = SimInactive; UpdateSimButtons(); } void MapSidebar::UpdateSimButtons() { wxButton* button; button = wxDynamicCast(FindWindow(ID_SimPlay), wxButton); wxCHECK(button, ); button->Enable(m_SimState != SimPlaying); button = wxDynamicCast(FindWindow(ID_SimFast), wxButton); wxCHECK(button, ); button->Enable(m_SimState != SimPlayingFast); button = wxDynamicCast(FindWindow(ID_SimSlow), wxButton); wxCHECK(button, ); button->Enable(m_SimState != SimPlayingSlow); button = wxDynamicCast(FindWindow(ID_SimPause), wxButton); wxCHECK(button, ); button->Enable(IsPlaying(m_SimState)); button = wxDynamicCast(FindWindow(ID_SimReset), wxButton); wxCHECK(button, ); button->Enable(m_SimState != SimInactive); } void MapSidebar::OnSimPlay(wxCommandEvent& event) { float speed = 1.f; int newState = SimPlaying; if (event.GetId() == ID_SimFast) { speed = 8.f; newState = SimPlayingFast; } else if (event.GetId() == ID_SimSlow) { speed = 0.125f; newState = SimPlayingSlow; } if (m_SimState == SimInactive) { // Force update of player settings POST_MESSAGE(LoadPlayerSettings, (false)); POST_MESSAGE(SimStateSave, (L"default")); POST_MESSAGE(GuiSwitchPage, (L"page_session.xml")); POST_MESSAGE(SimPlay, (speed, true)); m_SimState = newState; } else // paused or already playing at a different speed { POST_MESSAGE(SimPlay, (speed, true)); m_SimState = newState; } UpdateSimButtons(); } void MapSidebar::OnSimPause(wxCommandEvent& WXUNUSED(event)) { if (IsPlaying(m_SimState)) { POST_MESSAGE(SimPlay, (0.f, true)); m_SimState = SimPaused; } UpdateSimButtons(); } void MapSidebar::OnSimReset(wxCommandEvent& WXUNUSED(event)) { if (IsPlaying(m_SimState)) { POST_MESSAGE(SimPlay, (0.f, true)); POST_MESSAGE(SimStateRestore, (L"default")); POST_MESSAGE(SimStopMusic, ()); POST_MESSAGE(SimPlay, (0.f, false)); POST_MESSAGE(GuiSwitchPage, (L"page_atlas.xml")); m_SimState = SimInactive; } else if (m_SimState == SimPaused) { POST_MESSAGE(SimPlay, (0.f, true)); POST_MESSAGE(SimStateRestore, (L"default")); POST_MESSAGE(SimStopMusic, ()); POST_MESSAGE(SimPlay, (0.f, false)); POST_MESSAGE(GuiSwitchPage, (L"page_atlas.xml")); m_SimState = SimInactive; } UpdateSimButtons(); } void MapSidebar::OnRandomReseed(wxCommandEvent& WXUNUSED(evt)) { // Pick a shortish randomish value wxString seed; seed << (int)floor((rand() / (float)RAND_MAX) * 10000.f); wxDynamicCast(FindWindow(ID_RandomSeed), wxTextCtrl)->SetValue(seed); } void MapSidebar::OnRandomGenerate(wxCommandEvent& WXUNUSED(evt)) { if (m_ScenarioEditor.DiscardChangesDialog()) return; wxChoice* scriptChoice = wxDynamicCast(FindWindow(ID_RandomScript), wxChoice); if (scriptChoice->GetSelection() < 0) return; // TODO: this settings thing seems a bit of a mess, // since it's mixing data from three different sources AtObj settings = m_MapSettingsCtrl->UpdateSettingsObject(); AtObj scriptSettings = dynamic_cast(scriptChoice->GetClientObject(scriptChoice->GetSelection()))->GetValue(); settings.addOverlay(scriptSettings); wxChoice* sizeChoice = wxDynamicCast(FindWindow(ID_RandomSize), wxChoice); wxString size; size << (intptr_t)sizeChoice->GetClientData(sizeChoice->GetSelection()); settings.setInt("Size", wxAtoi(size)); settings.setBool("Nomad", wxDynamicCast(FindWindow(ID_RandomNomad), wxCheckBox)->GetValue()); settings.setInt("Seed", wxAtoi(wxDynamicCast(FindWindow(ID_RandomSeed), wxTextCtrl)->GetValue())); std::string json = AtlasObject::SaveToJSON(settings); wxBusyInfo busy(_("Generating map")); wxBusyCursor busyc; wxString scriptName = wxString::FromUTF8(settings["Script"]); // Copy the old map settings, so we don't lose them if the map generation fails AtObj oldSettings = settings; AtlasMessage::qGenerateMap qry((std::wstring)scriptName.wc_str(), json); qry.Post(); if (qry.status < 0) { // Display error message and revert to old map settings wxLogError(_("Random map script '%s' failed"), scriptName.c_str()); m_MapSettingsCtrl->SetMapSettings(oldSettings); } m_ScenarioEditor.NotifyOnMapReload(); } void MapSidebar::OnOpenPlayerPanel(wxCommandEvent& WXUNUSED(evt)) { m_ScenarioEditor.SelectPage(_T("PlayerSidebar")); } BEGIN_EVENT_TABLE(MapSidebar, Sidebar) EVT_COLLAPSIBLEPANE_CHANGED(wxID_ANY, MapSidebar::OnCollapse) EVT_BUTTON(ID_SimPlay, MapSidebar::OnSimPlay) EVT_BUTTON(ID_SimFast, MapSidebar::OnSimPlay) EVT_BUTTON(ID_SimSlow, MapSidebar::OnSimPlay) EVT_BUTTON(ID_SimPause, MapSidebar::OnSimPause) EVT_BUTTON(ID_SimReset, MapSidebar::OnSimReset) EVT_BUTTON(ID_RandomReseed, MapSidebar::OnRandomReseed) EVT_BUTTON(ID_RandomGenerate, MapSidebar::OnRandomGenerate) EVT_BUTTON(ID_OpenPlayerPanel, MapSidebar::OnOpenPlayerPanel) END_EVENT_TABLE(); Index: ps/trunk/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp (revision 23846) +++ ps/trunk/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp (revision 23847) @@ -1,372 +1,377 @@ /* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "MessageHandler.h" #include "../GameLoop.h" #include "../CommandProc.h" #include "graphics/GameView.h" #include "graphics/LOSTexture.h" #include "graphics/MapIO.h" #include "graphics/MapWriter.h" #include "graphics/Patch.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" #include "lib/bits.h" #include "lib/file/vfs/vfs_path.h" #include "lib/status.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/Loader.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "scriptinterface/ScriptInterface.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpPlayer.h" #include "simulation2/components/ICmpPlayerManager.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/components/ICmpTerrain.h" namespace { void InitGame() { if (g_Game) { delete g_Game; g_Game = NULL; } g_Game = new CGame(false); // Default to player 1 for playtesting g_Game->SetPlayerID(1); } void StartGame(JS::MutableHandleValue attrs) { g_Game->StartGame(attrs, ""); // TODO: Non progressive load can fail - need a decent way to handle this LDR_NonprogressiveLoad(); // Disable fog-of-war - this must be done before starting the game, // as visual actors cache their visibility state on first render. CmpPtr cmpRangeManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); if (cmpRangeManager) cmpRangeManager->SetLosRevealAll(-1, true); PSRETURN ret = g_Game->ReallyStartGame(); ENSURE(ret == PSRETURN_OK); } } namespace AtlasMessage { QUERYHANDLER(GenerateMap) { try { InitGame(); // Random map const ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); JS::RootedValue settings(cx); scriptInterface.ParseJSON(*msg->settings, &settings); scriptInterface.SetProperty(settings, "mapType", "random"); JS::RootedValue attrs(cx); ScriptInterface::CreateObject( cx, &attrs, "mapType", "random", "script", *msg->filename, "settings", settings); StartGame(&attrs); msg->status = 0; } catch (PSERROR_Game_World_MapLoadFailed&) { // Cancel loading LDR_Cancel(); // Since map generation failed and we don't know why, use the blank map as a fallback InitGame(); const ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); // Set up 8-element array of empty objects to satisfy init JS::RootedValue playerData(cx); ScriptInterface::CreateArray(cx, &playerData); for (int i = 0; i < 8; ++i) { JS::RootedValue player(cx); ScriptInterface::CreateObject(cx, &player); scriptInterface.SetPropertyInt(playerData, i, player); } JS::RootedValue settings(cx); ScriptInterface::CreateObject( cx, &settings, "mapType", "scenario", "PlayerData", playerData); JS::RootedValue attrs(cx); ScriptInterface::CreateObject( cx, &attrs, "mapType", "scenario", "map", "maps/scenarios/_default", "settings", settings); StartGame(&attrs); msg->status = -1; } } MESSAGEHANDLER(LoadMap) { InitGame(); const ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); // Scenario CStrW map = *msg->filename; CStrW mapBase = map.BeforeLast(L".pmp"); // strip the file extension, if any JS::RootedValue attrs(cx); ScriptInterface::CreateObject( cx, &attrs, "mapType", "scenario", "map", mapBase); StartGame(&attrs); } MESSAGEHANDLER(ImportHeightmap) { std::vector heightmap_source; if (LoadHeightmapImageOs(*msg->filename, heightmap_source) != INFO::OK) { LOGERROR("Failed to decode heightmap."); return; } // resize terrain to heightmap size // Notice that the number of tiles/pixels per side of the heightmap image is // one less than the number of vertices per side of the heightmap. CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); const ssize_t newSize = (sqrt(heightmap_source.size()) - 1) / PATCH_SIZE; const ssize_t offset = (newSize - terrain->GetPatchesPerSide()) / 2; terrain->ResizeAndOffset(newSize, offset, offset); // copy heightmap data into map u16* heightmap = g_Game->GetWorld()->GetTerrain()->GetHeightMap(); ENSURE(heightmap_source.size() == (std::size_t) SQR(g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide())); std::copy(heightmap_source.begin(), heightmap_source.end(), heightmap); // update simulation CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY); if (cmpTerrain) cmpTerrain->ReloadTerrain(); g_Game->GetView()->GetLOSTexture().MakeDirty(); } MESSAGEHANDLER(SaveMap) { CMapWriter writer; VfsPath pathname = VfsPath(*msg->filename).ChangeExtension(L".pmp"); writer.SaveMap(pathname, g_Game->GetWorld()->GetTerrain(), g_Renderer.GetWaterManager(), g_Renderer.GetSkyManager(), &g_LightEnv, g_Game->GetView()->GetCamera(), g_Game->GetView()->GetCinema(), &g_Renderer.GetPostprocManager(), g_Game->GetSimulation2()); } QUERYHANDLER(GetMapSettings) { msg->settings = g_Game->GetSimulation2()->GetMapSettingsString(); } BEGIN_COMMAND(SetMapSettings) { std::string m_OldSettings, m_NewSettings; void SetSettings(const std::string& settings) { g_Game->GetSimulation2()->SetMapSettings(settings); } void Do() { m_OldSettings = g_Game->GetSimulation2()->GetMapSettingsString(); m_NewSettings = *msg->settings; SetSettings(m_NewSettings); } // TODO: we need some way to notify the Atlas UI when the settings are changed // externally, otherwise this will have no visible effect void Undo() { // SetSettings(m_OldSettings); } void Redo() { // SetSettings(m_NewSettings); } void MergeIntoPrevious(cSetMapSettings* prev) { prev->m_NewSettings = m_NewSettings; } }; END_COMMAND(SetMapSettings) MESSAGEHANDLER(LoadPlayerSettings) { g_Game->GetSimulation2()->LoadPlayerSettings(msg->newplayers); } QUERYHANDLER(GetMapSizes) { msg->sizes = g_Game->GetSimulation2()->GetMapSizes(); } QUERYHANDLER(GetRMSData) { msg->data = g_Game->GetSimulation2()->GetRMSData(); } BEGIN_COMMAND(ResizeMap) { int m_OldTiles, m_NewTiles; cResizeMap() { } void MakeDirty() { CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY); if (cmpTerrain) cmpTerrain->ReloadTerrain(); // The LOS texture won't normally get updated when running Atlas // (since there's no simulation updates), so explicitly dirty it g_Game->GetView()->GetLOSTexture().MakeDirty(); } void ResizeTerrain(int tiles) { CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); const ssize_t newSize = tiles / PATCH_SIZE; const ssize_t offset = (newSize - terrain->GetPatchesPerSide()) / 2; terrain->ResizeAndOffset(newSize, offset, offset); MakeDirty(); } void Do() { CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY); if (!cmpTerrain) { m_OldTiles = m_NewTiles = 0; } else { m_OldTiles = (int)cmpTerrain->GetTilesPerSide(); m_NewTiles = msg->tiles; } ResizeTerrain(m_NewTiles); } void Undo() { ResizeTerrain(m_OldTiles); } void Redo() { ResizeTerrain(m_NewTiles); } }; END_COMMAND(ResizeMap) QUERYHANDLER(VFSFileExists) { msg->exists = VfsFileExists(*msg->path); } QUERYHANDLER(VFSFileRealPath) { VfsPath pathname(*msg->path); if (pathname.empty()) return; OsPath realPathname; if (g_VFS->GetRealPath(pathname, realPathname) == INFO::OK) msg->realPath = realPathname.string(); } static Status AddToFilenames(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData) { std::vector& filenames = *(std::vector*)cbData; filenames.push_back(pathname.string().c_str()); return INFO::OK; } QUERYHANDLER(GetMapList) { #define GET_FILE_LIST(path, list) \ std::vector list; \ vfs::ForEachFile(g_VFS, path, AddToFilenames, (uintptr_t)&list, L"*.xml", vfs::DIR_RECURSIVE); \ msg->list = list; GET_FILE_LIST(L"maps/scenarios/", scenarioFilenames); GET_FILE_LIST(L"maps/skirmishes/", skirmishFilenames); GET_FILE_LIST(L"maps/tutorials/", tutorialFilenames); #undef GET_FILE_LIST } +QUERYHANDLER(GetVictoryConditionData) +{ + msg->data = g_Game->GetSimulation2()->GetVictoryConditiondData(); +} + } Index: ps/trunk/source/tools/atlas/GameInterface/Messages.h =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/Messages.h (revision 23846) +++ ps/trunk/source/tools/atlas/GameInterface/Messages.h (revision 23847) @@ -1,733 +1,738 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_MESSAGES #define INCLUDED_MESSAGES #ifndef MESSAGES_SKIP_SETUP #include "MessagesSetup.h" #endif #include #include // TODO: organisation, documentation, etc #ifdef _MSC_VER // (can't use MSC_VERSION here since this file is included by Atlas too) #pragma warning(push) #pragma warning(disable: 4003) #endif ////////////////////////////////////////////////////////////////////////// // Initialise some engine code. Must be called before anything else. MESSAGE(Init, ); // Initialise SDL-related code. Must be called before SetCanvas and InitGraphics. MESSAGE(InitSDL, ); // Initialise graphics-related code. Must be called after the first SetCanvas, // and before much else. MESSAGE(InitGraphics, ); // Shut down engine/graphics code. MESSAGE(Shutdown, ); struct eRenderView { enum renderViews { NONE, GAME, ACTOR }; }; MESSAGE(RenderEnable, ((int, view)) // eRenderView ); // SetViewParam: used for hints to the renderer, e.g. to set wireframe mode; // unrecognised param names are ignored MESSAGE(SetViewParamB, ((int, view)) // eRenderView ((std::wstring, name)) ((bool, value)) ); MESSAGE(SetViewParamI, ((int, view)) // eRenderView ((std::wstring, name)) ((int, value)) ); MESSAGE(SetViewParamC, ((int, view)) // eRenderView ((std::wstring, name)) ((Color, value)) ); MESSAGE(SetViewParamS, ((int, view)) // eRenderView ((std::wstring, name)) ((std::wstring, value)) ); MESSAGE(JavaScript, ((std::wstring, command)) ); ////////////////////////////////////////////////////////////////////////// MESSAGE(GuiSwitchPage, ((std::wstring, page)) ); MESSAGE(GuiMouseButtonEvent, ((int, button)) ((bool, pressed)) ((Position, pos)) ((int, clicks)) ); MESSAGE(GuiMouseMotionEvent, ((Position, pos)) ); MESSAGE(GuiKeyEvent, ((int, sdlkey)) // SDLKey code ((int, unichar)) // Unicode character ((bool, pressed)) ); MESSAGE(GuiCharEvent, ((int, sdlkey)) ((int, unichar)) ); ////////////////////////////////////////////////////////////////////////// MESSAGE(SimStopMusic, ); MESSAGE(SimStateSave, ((std::wstring, label)) // named slot to store saved data ); MESSAGE(SimStateRestore, ((std::wstring, label)) // named slot to find saved data ); QUERY(SimStateDebugDump, ((bool, binary)) , ((std::wstring, dump)) ); MESSAGE(SimPlay, ((float, speed)) // 0 for pause, 1 for normal speed ((bool, simTest)) // true if we're in simulation test mode, false otherwise ); ////////////////////////////////////////////////////////////////////////// QUERY(Ping, , ); ////////////////////////////////////////////////////////////////////////// MESSAGE(SetCanvas, ((void*, canvas)) ((int, width)) ((int, height)) ); MESSAGE(ResizeScreen, ((int, width)) ((int, height)) ); ////////////////////////////////////////////////////////////////////////// // Messages for map panel QUERY(GenerateMap, ((std::wstring, filename)) // random map script filename ((std::string, settings)) // map settings as JSON string , ((int, status)) ); MESSAGE(ImportHeightmap, ((std::wstring, filename)) ); MESSAGE(LoadMap, ((std::wstring, filename)) ); MESSAGE(SaveMap, ((std::wstring, filename)) ); QUERY(GetMapList, , ((std::vector, scenarioFilenames)) ((std::vector, skirmishFilenames)) ((std::vector, tutorialFilenames)) ); QUERY(GetMapSettings, , ((std::string, settings)) ); COMMAND(SetMapSettings, MERGE, ((std::string, settings)) ); MESSAGE(LoadPlayerSettings, ((bool, newplayers)) ); QUERY(GetMapSizes, , ((std::string, sizes)) ); QUERY(GetRMSData, , ((std::vector, data)) ); COMMAND(ResizeMap, NOMERGE, ((int, tiles)) ); QUERY(VFSFileExists, ((std::wstring, path)) , ((bool, exists)) ); QUERY(VFSFileRealPath, ((std::wstring, path)) , ((std::wstring, realPath)) ); ////////////////////////////////////////////////////////////////////////// // Messages for player panel QUERY(GetCivData, , ((std::vector, data)) ); +QUERY(GetVictoryConditionData, + , + ((std::vector, data)) + ); + QUERY(GetPlayerDefaults, , ((std::string, defaults)) ); QUERY(GetAIData, , ((std::string, data)) ); ////////////////////////////////////////////////////////////////////////// MESSAGE(RenderStyle, ((bool, wireframe)) ); MESSAGE(MessageTrace, ((bool, enable)) ); MESSAGE(Screenshot, ((bool, big)) ((int, tiles)) // For big screenshots: the final image will be (640*tiles)x(480*tiles) ); #ifndef MESSAGES_SKIP_STRUCTS struct sCinemaRecordCB { unsigned char* buffer; }; SHAREABLE_STRUCT(sCinemaRecordCB); #endif QUERY(CinemaRecord, ((std::wstring, path)) ((int, framerate)) ((float, duration)) ((int, width)) ((int, height)) ((Callback, cb)) , ); ////////////////////////////////////////////////////////////////////////// MESSAGE(Brush, ((int, width)) // number of vertices ((int, height)) ((std::vector, data)) // width*height array ); MESSAGE(BrushPreview, ((bool, enable)) ((Position, pos)) // only used if enable==true ); ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// QUERY(GetTerrainGroups, , // no inputs ((std::vector, groupNames)) ); #ifndef MESSAGES_SKIP_STRUCTS struct sTerrainTexturePreview { Shareable name; Shareable loaded; Shareable imageWidth; Shareable imageHeight; Shareable > imageData; // RGB*width*height }; SHAREABLE_STRUCT(sTerrainTexturePreview); #endif QUERY(GetTerrainGroupPreviews, ((std::wstring, groupName)) ((int, imageWidth)) ((int, imageHeight)) , ((std::vector, previews)) ); QUERY(GetTerrainPassabilityClasses, , // no inputs ((std::vector, classNames)) ); QUERY(GetTerrainTexturePreview, ((std::wstring, name)) ((int, imageWidth)) ((int, imageHeight)) , ((sTerrainTexturePreview, preview)) ); ////////////////////////////////////////////////////////////////////////// #ifndef MESSAGES_SKIP_STRUCTS struct sObjectsListItem { Shareable id; Shareable name; Shareable type; // 0 = entity, 1 = actor }; SHAREABLE_STRUCT(sObjectsListItem); #endif QUERY(GetObjectsList, , // no inputs ((std::vector, objects)) // sorted by .name ); #ifndef MESSAGES_SKIP_STRUCTS struct sObjectSettings { Shareable player; Shareable > selections; // Some settings are immutable and therefore are ignored (and should be left // empty) when passed from the editor to the game: Shareable > > variantGroups; }; SHAREABLE_STRUCT(sObjectSettings); #endif // transform de local entity to a real entity MESSAGE(ObjectPreviewToEntity,); //Query for get selected objects QUERY(GetCurrentSelection, , //No inputs ((std::vector, ids)) ); // Moving Preview(s) object together, default is using the firs element in vector MESSAGE(MoveObjectPreview, ((Position,pos)) ); // Preview object in the game world - creates a temporary unit at the given // position, and removes it when the preview is next changed MESSAGE(ObjectPreview, ((std::wstring, id)) // or empty string => disable ((sObjectSettings, settings)) ((Position, pos)) ((bool, usetarget)) // true => use 'target' for orientation; false => use 'angle' ((Position, target)) ((float, angle)) ((unsigned int, actorseed)) ((bool, cleanObjectPreviews)) ); COMMAND(CreateObject, NOMERGE, ((std::wstring, id)) ((sObjectSettings, settings)) ((Position, pos)) ((bool, usetarget)) // true => use 'target' for orientation; false => use 'angle' ((Position, target)) ((float, angle)) ((unsigned int, actorseed)) ); // Set an actor to be previewed on its own (i.e. without the game world). // (Use RenderEnable to make it visible.) MESSAGE(SetActorViewer, ((std::wstring, id)) ((std::string, animation)) ((int, playerID)) ((float, speed)) ((bool, flushcache)) // true => unload all actor files before starting the preview (because we don't have proper hotloading yet) ); ////////////////////////////////////////////////////////////////////////// QUERY(Exit,,); // no inputs nor outputs ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// struct eScrollConstantDir { enum { FORWARDS, BACKWARDS, LEFT, RIGHT, CLOCKWISE, ANTICLOCKWISE }; }; MESSAGE(ScrollConstant, // set a constant scrolling(/rotation) rate ((int, view)) // eRenderView ((int, dir)) // eScrollConstantDir ((float, speed)) // set speed 0.0f to stop scrolling ); struct eScrollType { enum { FROM, TO }; }; MESSAGE(Scroll, // for scrolling by dragging the mouse FROM somewhere TO elsewhere ((int, view)) // eRenderView ((int, type)) // eScrollType ((Position, pos)) ); MESSAGE(SmoothZoom, ((int, view)) // eRenderView ((float, amount)) ); struct eRotateAroundType { enum { FROM, TO }; }; MESSAGE(RotateAround, ((int, view)) // eRenderView ((int, type)) // eRotateAroundType ((Position, pos)) ); MESSAGE(LookAt, ((int, view)) // eRenderView ((Position, pos)) ((Position, target)) ); MESSAGE(CameraReset, ); QUERY(GetView, , ((sCameraInfo, info)) ); MESSAGE(SetView, ((sCameraInfo, info)) ); ////////////////////////////////////////////////////////////////////////// #ifndef MESSAGES_SKIP_STRUCTS struct sEnvironmentSettings { Shareable watertype; // range 0..1 corresponds to min..max terrain height; out-of-bounds values allowed Shareable waterheight; // range 0..1 corresponds to min..max terrain height; out-of-bounds values allowed Shareable waterwaviness; // range ??? Shareable watermurkiness; // range ??? Shareable windangle; Shareable watercolor; Shareable watertint; Shareable sunrotation; // range -pi..+pi Shareable sunelevation; // range -pi/2 .. +pi/2 // emulate 'HDR' by allowing overly bright suncolor. this is // multiplied on to suncolor after converting to float // (struct Color stores as normal u8, 0..255) Shareable sunoverbrightness; // range 1..3 // support different lighting models ("old" for the version compatible with old scenarios, // "standard" for the new normal model that supports much brighter lighting) Shareable posteffect; Shareable skyset; Shareable suncolor; Shareable terraincolor; Shareable unitcolor; Shareable fogcolor; Shareable fogfactor; Shareable fogmax; Shareable brightness; Shareable contrast; Shareable saturation; Shareable bloom; }; SHAREABLE_STRUCT(sEnvironmentSettings); #endif QUERY(GetEnvironmentSettings, // no inputs , ((sEnvironmentSettings, settings)) ); COMMAND(SetEnvironmentSettings, MERGE, // merge lots of small changes into one undoable command ((sEnvironmentSettings, settings)) ); COMMAND(RecalculateWaterData, NOMERGE, ((float, unused))); COMMAND(PickWaterHeight, NOMERGE, ((Position, screenPos))); QUERY(GetSkySets, // no inputs , ((std::vector, skysets)) ); QUERY(GetPostEffects, // no inputs , ((std::vector, posteffects)) ); ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// COMMAND(AlterElevation, MERGE, ((Position, pos)) ((float, amount)) ); COMMAND(SmoothElevation, MERGE, ((Position, pos)) ((float, amount)) ); COMMAND(FlattenElevation, MERGE, ((Position, pos)) ((float, amount)) ); COMMAND(PikeElevation, MERGE, ((Position, pos)) ((float, amount)) ); struct ePaintTerrainPriority { enum { HIGH, LOW }; }; COMMAND(PaintTerrain, MERGE, ((Position, pos)) ((std::wstring, texture)) ((int, priority)) // ePaintTerrainPriority ); COMMAND(ReplaceTerrain, NOMERGE, ((Position, pos)) ((std::wstring, texture)) ); COMMAND(FillTerrain, NOMERGE, ((Position, pos)) ((std::wstring, texture)) ); QUERY(GetTerrainTexture, ((Position, pos)) , ((std::wstring, texture)) ); ////////////////////////////////////////////////////////////////////////// QUERY(PickObject, ((Position, pos)) ((bool, selectActors)) , ((ObjectID, id)) ((int, offsetx)) // offset of object centre from input position ((int, offsety)) // ); QUERY(PickObjectsInRect, ((Position, start)) ((Position, end)) ((bool, selectActors)) , ((std::vector, ids)) ); QUERY(PickSimilarObjects, ((ObjectID, id)) , ((std::vector, ids)) ); MESSAGE(ResetSelectionColor, ); COMMAND(MoveObjects, MERGE, ((std::vector, ids)) ((ObjectID, pivot)) ((Position, pos)) ); COMMAND(RotateObjectsFromCenterPoint, MERGE, ((std::vector, ids)) ((Position, target)) ((bool, rotateObject)) ); COMMAND(RotateObject, MERGE, ((std::vector, ids)) ((Position, target)) ); COMMAND(DeleteObjects, NOMERGE, ((std::vector, ids)) ); MESSAGE(SetSelectionPreview, ((std::vector, ids)) ); QUERY(GetObjectSettings, ((int, view)) // eRenderView ((ObjectID, id)) , ((sObjectSettings, settings)) ); COMMAND(SetObjectSettings, NOMERGE, ((int, view)) // eRenderView ((ObjectID, id)) ((sObjectSettings, settings)) ); QUERY(GetObjectMapSettings, ((std::vector, ids)) , ((std::wstring, xmldata)) ); QUERY(GetPlayerObjects, ((int, player)) , ((std::vector, ids)) ); MESSAGE(SetBandbox, ((bool, show)) ((int, sx0)) ((int, sy0)) ((int, sx1)) ((int, sy1)) ); ////////////////////////////////////////////////////////////////////////// QUERY(GetCinemaPaths, , // no inputs ((std::vector , paths)) ); QUERY(GetCameraInfo, , ((AtlasMessage::sCameraInfo, info)) ); QUERY(PickPathNode, ((Position, pos)) , ((AtlasMessage::sCinemaPathNode, node)) ); QUERY(PickAxis, ((AtlasMessage::sCinemaPathNode, node)) ((Position, pos)) , ((int, axis)) ); COMMAND(AddPathNode, NOMERGE, ((AtlasMessage::sCinemaPathNode, node)) ); COMMAND(DeletePathNode, NOMERGE, ((AtlasMessage::sCinemaPathNode, node)) ); COMMAND(MovePathNode, NOMERGE, ((AtlasMessage::sCinemaPathNode, node)) ((int, axis)) ((Position, from)) ((Position, to)) ); COMMAND(AddCinemaPath, NOMERGE, ((std::wstring, pathName))); COMMAND(DeleteCinemaPath, NOMERGE, ((std::wstring, pathName))); COMMAND(SetCinemaPaths, NOMERGE, ((std::vector, paths)) ); COMMAND(SetCinemaPathsDrawing, NOMERGE, ((bool, drawPaths))); MESSAGE(CinemaEvent, ((std::wstring, path)) ((int, mode)) ((float, t)) ((bool, drawCurrent)) ((bool, lines)) ); MESSAGE(ClearPathNodePreview,); ////////////////////////////////////////////////////////////////////////// QUERY(GetSelectedObjectsTemplateNames, ((std::vector, ids)) , ((std::vector, names)) ); #ifdef _MSC_VER #pragma warning(pop) #endif #ifndef MESSAGES_SKIP_SETUP #include "MessagesSetup.h" #endif #endif // INCLUDED_MESSAGES