aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
-rw-r--r--SConstruct295
m---------deps/openvic-dataloader0
-rw-r--r--scripts/build/cache.py127
-rw-r--r--scripts/build/glob_recursive.py15
-rw-r--r--scripts/build/option_handler.py39
-rw-r--r--src/headless/main.cpp64
-rw-r--r--src/openvic/GameManager.cpp85
-rw-r--r--src/openvic/GameManager.hpp11
-rw-r--r--src/openvic/Simulation.hpp5
-rw-r--r--src/openvic/dataloader/Dataloader.cpp140
-rw-r--r--src/openvic/dataloader/Dataloader.hpp28
-rw-r--r--src/openvic/dataloader/NodeTools.cpp198
-rw-r--r--src/openvic/dataloader/NodeTools.hpp42
-rw-r--r--src/openvic/economy/Good.cpp8
-rw-r--r--src/openvic/economy/Good.hpp8
-rw-r--r--src/openvic/map/Building.cpp4
-rw-r--r--src/openvic/map/Building.hpp4
-rw-r--r--src/openvic/map/Map.cpp7
-rw-r--r--src/openvic/map/Map.hpp4
-rw-r--r--src/openvic/map/Province.cpp10
-rw-r--r--src/openvic/map/Province.hpp6
-rw-r--r--src/openvic/map/Region.cpp2
-rw-r--r--src/openvic/map/Region.hpp2
-rw-r--r--src/openvic/pop/Culture.cpp111
-rw-r--r--src/openvic/pop/Culture.hpp11
-rw-r--r--src/openvic/pop/Pop.cpp67
-rw-r--r--src/openvic/pop/Pop.hpp12
-rw-r--r--src/openvic/pop/Religion.hpp2
-rw-r--r--src/openvic/types/Colour.hpp27
-rw-r--r--src/openvic/types/Date.cpp (renamed from src/openvic/Date.cpp)22
-rw-r--r--src/openvic/types/Date.hpp (renamed from src/openvic/Date.hpp)2
-rw-r--r--src/openvic/types/IdentifierRegistry.cpp (renamed from src/openvic/Types.cpp)2
-rw-r--r--src/openvic/types/IdentifierRegistry.hpp (renamed from src/openvic/Types.hpp)35
-rw-r--r--src/openvic/types/Return.hpp8
-rw-r--r--src/openvic/types/fixed_point/FP.hpp563
-rw-r--r--src/openvic/types/fixed_point/FPLUT.hpp33
-rw-r--r--src/openvic/types/fixed_point/FPLUT_sin_512.hpp58
-rw-r--r--src/openvic/types/fixed_point/FPMath.hpp11
-rw-r--r--src/openvic/types/fixed_point/lut_generator/lut_generator.py39
-rw-r--r--src/openvic/utility/BMP.cpp2
-rw-r--r--src/openvic/utility/BMP.hpp6
-rw-r--r--src/openvic/utility/FloatUtils.hpp11
-rw-r--r--src/openvic/utility/StringUtils.hpp93
44 files changed, 2051 insertions, 171 deletions
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..df89ab5
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "deps/openvic-dataloader"]
+ path = deps/openvic-dataloader
+ url = https://github.com/OpenVicProject/OpenVic-Dataloader
diff --git a/SConstruct b/SConstruct
index 50edb8f..632e97b 100644
--- a/SConstruct
+++ b/SConstruct
@@ -1,20 +1,221 @@
#!/usr/bin/env python
+
+# This file is heavily based on https://github.com/godotengine/godot-cpp/blob/8155f35b29b4b08bc54b2eb0c57e1e9effe9f093/SConstruct
import os
+import platform
import sys
-from glob import glob
-from pathlib import Path
-
-def GlobRecursive(pattern, nodes=['.']):
- import SCons
- results = []
- for node in nodes:
- nnodes = []
- for f in Glob(str(node) + '/*', source=True):
- if type(f) is SCons.Node.FS.Dir:
- nnodes.append(f)
- results += GlobRecursive(pattern, nnodes)
- results += Glob(str(node) + '/' + pattern, source=True)
- return results
+import importlib
+
+import SCons
+
+# Local
+from scripts.build.option_handler import OptionsClass
+from scripts.build.glob_recursive import GlobRecursive
+from scripts.build.cache import show_progress
+
+# Try to detect the host platform automatically.
+# This is used if no `platform` argument is passed
+if sys.platform.startswith("linux"):
+ default_platform = "linux"
+elif sys.platform == "darwin":
+ default_platform = "macos"
+elif sys.platform == "win32" or sys.platform == "msys":
+ default_platform = "windows"
+elif ARGUMENTS.get("platform", ""):
+ default_platform = ARGUMENTS.get("platform")
+else:
+ raise ValueError("Could not detect platform automatically, please specify with platform=<platform>")
+
+is_standalone = SCons.Script.sconscript_reading == 1
+
+BINDIR = "bin"
+TOOLPATH = ["deps/openvic-dataloader/tools"]
+
+try:
+ Import("env")
+ old_env = env
+ env = old_env.Clone()
+except:
+ # Default tools with no platform defaults to gnu toolchain.
+ # We apply platform specific toolchains via our custom tools.
+ env = Environment(tools=["default"], PLATFORM="")
+ old_env = env
+
+env.PrependENVPath("PATH", os.getenv("PATH"))
+
+# Default num_jobs to local cpu count if not user specified.
+# SCons has a peculiarity where user-specified options won't be overridden
+# by SetOption, so we can rely on this to know if we should use our default.
+initial_num_jobs = env.GetOption("num_jobs")
+altered_num_jobs = initial_num_jobs + 1
+env.SetOption("num_jobs", altered_num_jobs)
+if env.GetOption("num_jobs") == altered_num_jobs:
+ cpu_count = os.cpu_count()
+ if cpu_count is None:
+ print("Couldn't auto-detect CPU count to configure build parallelism. Specify it with the -j argument.")
+ else:
+ safer_cpu_count = cpu_count if cpu_count <= 4 else cpu_count - 1
+ print(
+ "Auto-detected %d CPU cores available for build parallelism. Using %d cores by default. You can override it with the -j argument."
+ % (cpu_count, safer_cpu_count)
+ )
+ env.SetOption("num_jobs", safer_cpu_count)
+
+opts = OptionsClass(ARGUMENTS)
+
+platforms = ("linux", "macos", "windows", "android", "ios", "javascript")
+unsupported_known_platforms = ("android", "ios", "javascript")
+opts.Add(
+ EnumVariable(
+ key="platform",
+ help="Target platform",
+ default=env.get("platform", default_platform),
+ allowed_values=platforms,
+ ignorecase=2,
+ )
+)
+
+opts.Add(
+ EnumVariable(
+ key="target",
+ help="Compilation target",
+ default=env.get("target", "template_debug"),
+ allowed_values=("editor", "template_release", "template_debug"),
+ )
+)
+
+opts.Add(BoolVariable(key="build_ovsim_library", help="Build the openvic simulation library.", default=env.get("build_ovsim_library", not is_standalone)))
+opts.Add(
+ EnumVariable(
+ key="precision",
+ help="Set the floating-point precision level",
+ default=env.get("precision", "single"),
+ allowed_values=("single", "double"),
+ )
+)
+
+# Add platform options
+tools = {}
+for pl in set(platforms) - set(unsupported_known_platforms):
+ tool = Tool(pl, toolpath=TOOLPATH)
+ if hasattr(tool, "options"):
+ tool.options(opts)
+ tools[pl] = tool
+
+# CPU architecture options.
+architecture_array = ["", "universal", "x86_32", "x86_64", "arm32", "arm64", "rv64", "ppc32", "ppc64", "wasm32"]
+architecture_aliases = {
+ "x64": "x86_64",
+ "amd64": "x86_64",
+ "armv7": "arm32",
+ "armv8": "arm64",
+ "arm64v8": "arm64",
+ "aarch64": "arm64",
+ "rv": "rv64",
+ "riscv": "rv64",
+ "riscv64": "rv64",
+ "ppcle": "ppc32",
+ "ppc": "ppc32",
+ "ppc64le": "ppc64",
+}
+opts.Add(
+ EnumVariable(
+ key="arch",
+ help="CPU architecture",
+ default=env.get("arch", ""),
+ allowed_values=architecture_array,
+ map=architecture_aliases,
+ )
+)
+
+opts.Add(BoolVariable("build_ovsim_headless", "Build the openvic simulation headless executable", is_standalone))
+
+opts.Add(BoolVariable("compiledb", "Generate compilation DB (`compile_commands.json`) for external tools", False))
+opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False))
+opts.Add(BoolVariable("intermediate_delete", "Enables automatically deleting unassociated intermediate binary files.", True))
+opts.Add(BoolVariable("progress", "Show a progress indicator during compilation", True))
+
+# Targets flags tool (optimizations, debug symbols)
+target_tool = Tool("targets", toolpath=TOOLPATH)
+target_tool.options(opts)
+
+# Custom options and profile flags.
+opts.Make(["custom.py"])
+opts.Finalize(env)
+Help(opts.GenerateHelpText(env))
+
+if env["platform"] in unsupported_known_platforms:
+ print("Unsupported platform: " + env["platform"]+". Only supports " + ", ".join(set(platforms) - set(unsupported_known_platforms)))
+ Exit()
+
+# Process CPU architecture argument.
+if env["arch"] == "":
+ # No architecture specified. Default to arm64 if building for Android,
+ # universal if building for macOS or iOS, wasm32 if building for web,
+ # otherwise default to the host architecture.
+ if env["platform"] in ["macos", "ios"]:
+ env["arch"] = "universal"
+ elif env["platform"] == "android":
+ env["arch"] = "arm64"
+ elif env["platform"] == "javascript":
+ env["arch"] = "wasm32"
+ else:
+ host_machine = platform.machine().lower()
+ if host_machine in architecture_array:
+ env["arch"] = host_machine
+ elif host_machine in architecture_aliases.keys():
+ env["arch"] = architecture_aliases[host_machine]
+ elif "86" in host_machine:
+ # Catches x86, i386, i486, i586, i686, etc.
+ env["arch"] = "x86_32"
+ else:
+ print("Unsupported CPU architecture: " + host_machine)
+ Exit()
+
+tool = Tool(env["platform"], toolpath=TOOLPATH)
+
+if tool is None or not tool.exists(env):
+ raise ValueError("Required toolchain not found for platform " + env["platform"])
+
+tool.generate(env)
+target_tool.generate(env)
+
+print("Building for architecture " + env["arch"] + " on platform " + env["platform"])
+
+# Require C++20
+if env.get("is_msvc", False):
+ env.Append(CXXFLAGS=["/std:c++20"])
+else:
+ env.Append(CXXFLAGS=["-std=c++20"])
+
+if env["precision"] == "double":
+ env.Append(CPPDEFINES=["REAL_T_IS_DOUBLE"])
+
+scons_cache_path = os.environ.get("SCONS_CACHE")
+if scons_cache_path != None:
+ CacheDir(scons_cache_path)
+ print("Scons cache enabled... (path: '" + scons_cache_path + "')")
+
+if env["compiledb"]:
+ # Generating the compilation DB (`compile_commands.json`) requires SCons 4.0.0 or later.
+ from SCons import __version__ as scons_raw_version
+
+ scons_ver = env._get_major_minor_revision(scons_raw_version)
+
+ if scons_ver < (4, 0, 0):
+ print("The `compiledb=yes` option requires SCons 4.0 or later, but your version is %s." % scons_raw_version)
+ Exit(255)
+
+ env.Tool("compilation_db")
+ env.Alias("compiledb", env.CompilationDatabase())
+
+ovdl_env = SConscript("deps/openvic-dataloader/SConstruct")
+
+env.Append(LIBPATH=ovdl_env.openvic_dataloader["LIBPATH"])
+env.Append(LIBS=ovdl_env.openvic_dataloader["LIBS"])
+env.Append(CPPPATH=ovdl_env.openvic_dataloader["INCPATH"])
+
+env.openvic_simulation = {}
# For future reference:
# - CCFLAGS are compilation flags shared between C and C++
@@ -23,23 +224,61 @@ def GlobRecursive(pattern, nodes=['.']):
# - CPPFLAGS are for pre-processor flags
# - CPPDEFINES are for pre-processor defines
# - LINKFLAGS are for linking flags
-env = Environment(
- CPPDEFINES=["OPENVIC_HEADLESS_SIM"]
-)
-
-# Require C++20
-if sys.platform == "win32" or sys.platform == "msys":
- env.Append(CXXFLAGS=["/std:c++20", "/EHsc"])
-else:
- env.Append(CXXFLAGS=["-std=c++20"])
# Tweak this if you want to use different folders, or more folders, to store your source code in.
-paths = ["src"]
-env.Append(CPPPATH=paths)
-sources = GlobRecursive("*.cpp", paths)
+source_path = "src/openvic"
+include_path = "src"
+env.Append(CPPPATH=[[env.Dir(p) for p in [source_path, include_path]]])
+sources = GlobRecursive("*.cpp", [source_path])
+env.simulation_sources = sources
+
+suffix = ".{}.{}".format(env["platform"], env["target"])
+if env.dev_build:
+ suffix += ".dev"
+if env["precision"] == "double":
+ suffix += ".double"
+suffix += "." + env["arch"]
+
+# Expose it when included from another project
+env["suffix"] = suffix
+
+library = None
+env["OBJSUFFIX"] = suffix + env["OBJSUFFIX"]
+library_name = "libopenvic-simulation{}{}".format(suffix, env["LIBSUFFIX"])
+
+if env["build_ovsim_library"]:
+ library = env.StaticLibrary(target=env.File(os.path.join(BINDIR, library_name)), source=sources)
+ Default(library)
+
+ env.Append(LIBPATH=[env.Dir(BINDIR)])
+ env.Append(LIBS=[library_name])
+
+ env.openvic_simulation["LIBPATH"] = env["LIBPATH"]
+ env.openvic_simulation["LIBS"] = env["LIBS"]
+ env.openvic_simulation["INCPATH"] = [env.Dir(include_path)] + ovdl_env.openvic_dataloader["INCPATH"]
+
+headless_program = None
+env["PROGSUFFIX"] = suffix + env["PROGSUFFIX"]
-program = env.Program("headless-sim", sources)
+if env["build_ovsim_headless"]:
+ headless_name = "openvic-simulation"
+ headless_env = env.Clone()
+ headless_path = ["src/headless"]
+ headless_env.Append(CPPDEFINES=["OPENVIC_SIM_HEADLESS"])
+ headless_env.Append(CPPPATH=[headless_env.Dir(headless_path)])
+ headless_env.headless_sources = GlobRecursive("*.cpp", headless_path)
+ if not env["build_ovsim_library"]:
+ headless_env.headless_sources += sources
+ headless_program = headless_env.Program(
+ target=os.path.join(BINDIR, headless_name),
+ source=headless_env.headless_sources,
+ PROGSUFFIX=".headless" + env["PROGSUFFIX"]
+ )
+ Default(headless_program)
-Default(program)
+if "env" in locals():
+ # FIXME: This method mixes both cosmetic progress stuff and cache handling...
+ show_progress(env)
+Return("env")
diff --git a/deps/openvic-dataloader b/deps/openvic-dataloader
new file mode 160000
+Subproject b1f726c79f20f89641d7e091b7abeed48227fad
diff --git a/scripts/build/cache.py b/scripts/build/cache.py
new file mode 100644
index 0000000..d48b0e0
--- /dev/null
+++ b/scripts/build/cache.py
@@ -0,0 +1,127 @@
+# Copied from https://github.com/godotengine/godot/blob/c3b0a92c3cd9a219c1b1776b48c147f1d0602f07/methods.py#L1049-L1172
+def show_progress(env):
+ import os
+ import sys
+ import glob
+ from SCons.Script import Progress, Command, AlwaysBuild
+
+ screen = sys.stdout
+ # Progress reporting is not available in non-TTY environments since it
+ # messes with the output (for example, when writing to a file)
+ show_progress = env["progress"] and sys.stdout.isatty()
+ node_count = 0
+ node_count_max = 0
+ node_count_interval = 1
+ node_count_fname = str(env.Dir("#")) + "/.scons_node_count"
+
+ import time, math
+
+ class cache_progress:
+ # The default is 1 GB cache and 12 hours half life
+ def __init__(self, path=None, limit=1073741824, half_life=43200):
+ self.path = path
+ self.limit = limit
+ self.exponent_scale = math.log(2) / half_life
+ if env["verbose"] and path != None:
+ screen.write(
+ "Current cache limit is {} (used: {})\n".format(
+ self.convert_size(limit), self.convert_size(self.get_size(path))
+ )
+ )
+ self.delete(self.file_list())
+
+ def __call__(self, node, *args, **kw):
+ nonlocal node_count, node_count_max, node_count_interval, node_count_fname, show_progress
+ if show_progress:
+ # Print the progress percentage
+ node_count += node_count_interval
+ if node_count_max > 0 and node_count <= node_count_max:
+ screen.write("\r[%3d%%] " % (node_count * 100 / node_count_max))
+ screen.flush()
+ elif node_count_max > 0 and node_count > node_count_max:
+ screen.write("\r[100%] ")
+ screen.flush()
+ else:
+ screen.write("\r[Initial build] ")
+ screen.flush()
+
+ def delete(self, files):
+ if len(files) == 0:
+ return
+ if env["verbose"]:
+ # Utter something
+ screen.write("\rPurging %d %s from cache...\n" % (len(files), len(files) > 1 and "files" or "file"))
+ [os.remove(f) for f in files]
+
+ def file_list(self):
+ if self.path is None:
+ # Nothing to do
+ return []
+ # Gather a list of (filename, (size, atime)) within the
+ # cache directory
+ file_stat = [(x, os.stat(x)[6:8]) for x in glob.glob(os.path.join(self.path, "*", "*"))]
+ if file_stat == []:
+ # Nothing to do
+ return []
+ # Weight the cache files by size (assumed to be roughly
+ # proportional to the recompilation time) times an exponential
+ # decay since the ctime, and return a list with the entries
+ # (filename, size, weight).
+ current_time = time.time()
+ file_stat = [(x[0], x[1][0], (current_time - x[1][1])) for x in file_stat]
+ # Sort by the most recently accessed files (most sensible to keep) first
+ file_stat.sort(key=lambda x: x[2])
+ # Search for the first entry where the storage limit is
+ # reached
+ sum, mark = 0, None
+ for i, x in enumerate(file_stat):
+ sum += x[1]
+ if sum > self.limit:
+ mark = i
+ break
+ if mark is None:
+ return []
+ else:
+ return [x[0] for x in file_stat[mark:]]
+
+ def convert_size(self, size_bytes):
+ if size_bytes == 0:
+ return "0 bytes"
+ size_name = ("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
+ i = int(math.floor(math.log(size_bytes, 1024)))
+ p = math.pow(1024, i)
+ s = round(size_bytes / p, 2)
+ return "%s %s" % (int(s) if i == 0 else s, size_name[i])
+
+ def get_size(self, start_path="."):
+ total_size = 0
+ for dirpath, dirnames, filenames in os.walk(start_path):
+ for f in filenames:
+ fp = os.path.join(dirpath, f)
+ total_size += os.path.getsize(fp)
+ return total_size
+
+ def progress_finish(target, source, env):
+ nonlocal node_count, progressor
+ try:
+ with open(node_count_fname, "w") as f:
+ f.write("%d\n" % node_count)
+ progressor.delete(progressor.file_list())
+ except Exception:
+ pass
+
+ try:
+ with open(node_count_fname) as f:
+ node_count_max = int(f.readline())
+ except Exception:
+ pass
+
+ cache_directory = os.environ.get("SCONS_CACHE")
+ # Simple cache pruning, attached to SCons' progress callback. Trim the
+ # cache directory to a size not larger than cache_limit.
+ cache_limit = float(os.getenv("SCONS_CACHE_LIMIT", 1024)) * 1024 * 1024
+ progressor = cache_progress(cache_directory, cache_limit)
+ Progress(progressor, interval=node_count_interval)
+
+ progress_finish_command = Command("progress_finish", [], progress_finish)
+ AlwaysBuild(progress_finish_command)
diff --git a/scripts/build/glob_recursive.py b/scripts/build/glob_recursive.py
new file mode 100644
index 0000000..db6eb80
--- /dev/null
+++ b/scripts/build/glob_recursive.py
@@ -0,0 +1,15 @@
+def GlobRecursive(pattern, nodes=['.']):
+ import SCons
+ import glob
+ fs = SCons.Node.FS.get_default_fs()
+ Glob = fs.Glob
+
+ results = []
+ for node in nodes:
+ nnodes = []
+ for f in Glob(str(node) + '/*', source=True):
+ if type(f) is SCons.Node.FS.Dir:
+ nnodes.append(f)
+ results += GlobRecursive(pattern, nnodes)
+ results += Glob(str(node) + '/' + pattern, source=True)
+ return results
diff --git a/scripts/build/option_handler.py b/scripts/build/option_handler.py
new file mode 100644
index 0000000..3cebc1a
--- /dev/null
+++ b/scripts/build/option_handler.py
@@ -0,0 +1,39 @@
+from typing import Tuple, Iterable
+from SCons.Variables import Variables
+
+class OptionsClass:
+ def __init__(self, args):
+ self.opts = None
+ self.opt_list = []
+ self.args = args
+ self.saved_args = args.copy()
+
+ def Add(self, variableOrKey, *argv, **kwarg):
+
+ self.opt_list.append([variableOrKey, argv, kwarg])
+ # Neccessary to have our own build options without errors
+ if isinstance(variableOrKey, str):
+ self.args.pop(variableOrKey, True)
+ else:
+ self.args.pop(variableOrKey[0], True)
+
+ def Make(self, customs : Iterable[str]):
+ self.args = self.saved_args
+ profile = self.args.get("profile", "")
+ if profile:
+ if os.path.isfile(profile):
+ customs.append(profile)
+ elif os.path.isfile(profile + ".py"):
+ customs.append(profile + ".py")
+ self.opts = Variables(customs, self.args)
+ for opt in self.opt_list:
+ if opt[1] == None and opt[2] == None:
+ self.opts.Add(opt[0])
+ else:
+ self.opts.Add(opt[0], *opt[1], **opt[2])
+
+ def Finalize(self, env):
+ self.opts.Update(env)
+
+ def GenerateHelpText(self, env):
+ return self.opts.GenerateHelpText(env)
diff --git a/src/headless/main.cpp b/src/headless/main.cpp
index 70a57a9..69e1e67 100644
--- a/src/headless/main.cpp
+++ b/src/headless/main.cpp
@@ -1,22 +1,56 @@
-#ifdef OPENVIC_HEADLESS_SIM
-#include <iostream>
-#include <string>
-#include "openvic/Simulation.hpp"
-#include "openvic/dataloader/Dataloader.hpp"
+#ifdef OPENVIC_SIM_HEADLESS
+#include <openvic/utility/Logger.hpp>
+#include <openvic/GameManager.hpp>
+#include <openvic/dataloader/Dataloader.hpp>
-int main() {
- std::cout << "HEADLESS SIMULATION" << std::endl;
+using namespace OpenVic;
+static char const* get_program_name(char const* name) {
+ static char const* const missing_name = "<program>";
+ if (name == nullptr) return missing_name;
+ char const* last_separator = name;
+ while (*name != '\0') {
+ if (*name == '/' || *name == '\\') {
+ last_separator = name + 1;
+ }
+ ++name;
+ }
+ if (*last_separator == '\0') return missing_name;
+ return last_separator;
+}
+
+int main(int argc, char const* argv[]) {
+ if (argc < 2) {
+ std::cout << "Usage: " << get_program_name(argc > 0 ? argv[0] : nullptr) << " <base defines dir> [[mod defines dir]+]" << std::endl;
+ std::cout << "Requires defines path(s) as arguments, starting with the base defines and continuing with mods (paths with spaces need to be enclosed in \"quotes\")." << std::endl;
+ return -1;
+ }
+
+ std::cout << "!!! HEADLESS SIMULATION START !!!" << std::endl;
- std::string vic2FolderLocation = "";
- if (vic2FolderLocation.length() <= 0) {
- std::cout << "Path to Victoria 2 folder not specified. Manually specify location: ";
- std::cin >> vic2FolderLocation;
+ Logger::set_info_func([](std::string&& str) { std::cout << str; });
+ Logger::set_error_func([](std::string&& str) { std::cerr << str; });
+
+ GameManager game_manager { []() {
+ Logger::info("State updated");
+ } };
+ Dataloader dataloader;
+
+ std::vector<std::filesystem::path> roots;
+ for (int i = 1; i < argc; ++i) {
+ roots.push_back(argv[i]);
+ }
+ dataloader.set_roots(roots);
+
+ if (dataloader.load_defines(game_manager) != SUCCESS) {
+ Logger::error("Failed to load defines!");
+ }
+ if (game_manager.load_hardcoded_defines() != SUCCESS) {
+ Logger::error("Failed to load hardcoded defines!");
}
- std::filesystem::path path(vic2FolderLocation);
- OpenVic::Simulation sim;
- std::cout << (OpenVic::Dataloader::loadDir(path, sim) ? "Dataloader suceeded" : "Dataloader failed") << std::endl;
+ std::cout << "!!! HEADLESS SIMULATION END !!!" << std::endl;
+ return 0;
}
-#endif \ No newline at end of file
+#endif
diff --git a/src/openvic/GameManager.cpp b/src/openvic/GameManager.cpp
index ce56afc..b4fa8b5 100644
--- a/src/openvic/GameManager.cpp
+++ b/src/openvic/GameManager.cpp
@@ -1,6 +1,6 @@
#include "GameManager.hpp"
-#include "utility/Logger.hpp"
+#include "openvic/utility/Logger.hpp"
using namespace OpenVic;
@@ -51,3 +51,86 @@ return_t GameManager::expand_building(Province::index_t province_index, const st
if (province == nullptr) return FAILURE;
return province->expand_building(building_type_identifier);
}
+
+return_t GameManager::load_hardcoded_defines() {
+ return_t ret = SUCCESS;
+
+ static constexpr colour_t LOW_ALPHA_VALUE = float_to_alpha_value(0.4f);
+ static constexpr colour_t HIGH_ALPHA_VALUE = float_to_alpha_value(0.7f);
+ using mapmode_t = std::pair<std::string, Mapmode::colour_func_t>;
+ const std::vector<mapmode_t> mapmodes {
+ { "mapmode_terrain",
+ [](Map const&, Province const& province) -> colour_t {
+ return LOW_ALPHA_VALUE | (province.is_water() ? 0x4287F5 : 0x0D7017);
+ } },
+ { "mapmode_province",
+ [](Map const&, Province const& province) -> colour_t {
+ return HIGH_ALPHA_VALUE | province.get_colour();
+ } },
+ { "mapmode_region",
+ [](Map const&, Province const& province) -> colour_t {
+ Region const* region = province.get_region();
+ if (region != nullptr) return HIGH_ALPHA_VALUE | region->get_colour();
+ return NULL_COLOUR;
+ } },
+ { "mapmode_index",
+ [](Map const& map, Province const& province) -> colour_t {
+ const colour_t f = fraction_to_colour_byte(province.get_index(), map.get_province_count() + 1);
+ return HIGH_ALPHA_VALUE | (f << 16) | (f << 8) | f;
+ } },
+ { "mapmode_rgo",
+ [](Map const& map, Province const& province) -> colour_t {
+ Good const* rgo = province.get_rgo();
+ if (rgo != nullptr) return HIGH_ALPHA_VALUE | rgo->get_colour();
+ return NULL_COLOUR;
+ } },
+ { "mapmode_infrastructure",
+ [](Map const& map, Province const& province) -> colour_t {
+ Building const* railroad = province.get_building_by_identifier("building_railroad");
+ if (railroad != nullptr) {
+ colour_t val = fraction_to_colour_byte(railroad->get_level(), railroad->get_type().get_max_level() + 1, 0.5f, 1.0f);
+ switch (railroad->get_expansion_state()) {
+ case Building::ExpansionState::CannotExpand: val <<= 16; break;
+ case Building::ExpansionState::CanExpand: break;
+ default: val <<= 8; break;
+ }
+ return HIGH_ALPHA_VALUE | val;
+ }
+ return NULL_COLOUR;
+ } },
+ { "mapmode_population",
+ [](Map const& map, Province const& province) -> colour_t {
+ return HIGH_ALPHA_VALUE | (fraction_to_colour_byte(province.get_total_population(), map.get_highest_province_population() + 1, 0.1f, 1.0f) << 8);
+ } },
+ { "mapmode_culture",
+ // TODO - use std::max_element and maybe change distribution_t to be an ordered std::map
+ [](Map const& map, Province const& province) -> colour_t {
+ distribution_t const& cultures = province.get_culture_distribution();
+ if (!cultures.empty()) {
+ // This breaks if replaced with distribution_t::value_type, something
+ // about operator=(volatile const&) being deleted.
+ std::pair<HasIdentifierAndColour const*, float> culture = *cultures.begin();
+ for (distribution_t::value_type const p : cultures) {
+ if (p.second > culture.second) culture = p;
+ }
+ return HIGH_ALPHA_VALUE | culture.first->get_colour();
+ }
+ return NULL_COLOUR;
+ } }
+ };
+ for (mapmode_t const& mapmode : mapmodes)
+ if (map.add_mapmode(mapmode.first, mapmode.second) != SUCCESS)
+ ret = FAILURE;
+ map.lock_mapmodes();
+
+ using building_type_t = std::tuple<std::string, Building::level_t, Timespan>;
+ const std::vector<building_type_t> building_types {
+ { "building_fort", 4, 8 }, { "building_naval_base", 6, 15 }, { "building_railroad", 5, 10 }
+ };
+ for (building_type_t const& type : building_types)
+ if (building_manager.add_building_type(std::get<0>(type), std::get<1>(type), std::get<2>(type)) != SUCCESS)
+ ret = FAILURE;
+ building_manager.lock_building_types();
+
+ return ret;
+}
diff --git a/src/openvic/GameManager.hpp b/src/openvic/GameManager.hpp
index b06c228..cffa75b 100644
--- a/src/openvic/GameManager.hpp
+++ b/src/openvic/GameManager.hpp
@@ -1,8 +1,8 @@
#pragma once
-#include "GameAdvancementHook.hpp"
-#include "economy/Good.hpp"
-#include "map/Map.hpp"
+#include "openvic/GameAdvancementHook.hpp"
+#include "openvic/economy/Good.hpp"
+#include "openvic/map/Map.hpp"
namespace OpenVic {
struct GameManager {
@@ -31,5 +31,10 @@ namespace OpenVic {
Date const& get_today() const;
return_t expand_building(Province::index_t province_index, const std::string_view building_type_identifier);
+
+ /* Hardcoded data for defining things for which parsing from files has
+ * not been implemented, currently mapmodes and building types.
+ */
+ return_t load_hardcoded_defines();
};
}
diff --git a/src/openvic/Simulation.hpp b/src/openvic/Simulation.hpp
deleted file mode 100644
index b0c2a19..0000000
--- a/src/openvic/Simulation.hpp
+++ /dev/null
@@ -1,5 +0,0 @@
-#pragma once
-
-namespace OpenVic {
- class Simulation {};
-} \ No newline at end of file
diff --git a/src/openvic/dataloader/Dataloader.cpp b/src/openvic/dataloader/Dataloader.cpp
new file mode 100644
index 0000000..90537c9
--- /dev/null
+++ b/src/openvic/dataloader/Dataloader.cpp
@@ -0,0 +1,140 @@
+#include "Dataloader.hpp"
+
+#include "openvic/utility/Logger.hpp"
+#include "openvic//GameManager.hpp"
+#include "openvic/dataloader/NodeTools.hpp"
+
+#include <openvic-dataloader/detail/CallbackOStream.hpp>
+
+using namespace OpenVic;
+using namespace ovdl::v2script;
+
+return_t Dataloader::set_roots(std::vector<std::filesystem::path> new_roots) {
+ if (!roots.empty()) {
+ Logger::error("Overriding existing dataloader roots!");
+ roots.clear();
+ }
+ for (std::reverse_iterator<std::vector<std::filesystem::path>::const_iterator> it = new_roots.crbegin(); it != new_roots.crend(); ++it) {
+ if (std::filesystem::is_directory(*it)) {
+ Logger::info("Adding dataloader root: ", *it);
+ roots.push_back(*it);
+ } else {
+ Logger::error("Invalid dataloader root (must be an existing directory): ", *it);
+ }
+ }
+ if (roots.empty()) {
+ Logger::error("Dataloader has no roots after attempting to add ", new_roots.size());
+ return FAILURE;
+ }
+ return SUCCESS;
+}
+
+std::filesystem::path Dataloader::lookup_file(std::filesystem::path const& path) const {
+ for (std::filesystem::path const& root : roots) {
+ const std::filesystem::path composed = root / path;
+ if (std::filesystem::is_regular_file(composed)) {
+ return composed;
+ }
+ }
+ Logger::error("Lookup for ", path, " failed!");
+ return {};
+}
+
+static bool contains_file_with_name(std::vector<std::filesystem::path> const& paths, std::filesystem::path const& name) {
+ for (std::filesystem::path const& path : paths) {
+ if (path.filename() == name) return true;
+ }
+ return false;
+}
+
+std::vector<std::filesystem::path> Dataloader::lookup_files_in_dir(std::filesystem::path const& path) const {
+ std::vector<std::filesystem::path> ret;
+ for (std::filesystem::path const& root : roots) {
+ const std::filesystem::path composed = root / path;
+ std::error_code ec;
+ for (std::filesystem::directory_entry const& entry : std::filesystem::directory_iterator { composed, ec }) {
+ if (entry.is_regular_file() && !contains_file_with_name(ret, entry.path().filename())) {
+ ret.push_back(entry);
+ }
+ }
+ }
+ return ret;
+}
+
+static Parser parse_defines(std::filesystem::path const& path) {
+ Parser parser;
+ std::string buffer;
+ auto error_log_stream = ovdl::detail::CallbackStream {
+ [](void const* s, std::streamsize n, void* user_data) -> std::streamsize {
+ if (s != nullptr && n > 0 && user_data != nullptr) {
+ static_cast<std::string*>(user_data)->append(static_cast<char const*>(s), n);
+ return n;
+ } else {
+ Logger::error("Invalid input to parser error log callback: ", s, " / ", n, " / ", user_data);
+ return 0;
+ }
+ }, &buffer
+ };
+ parser.set_error_log_to(error_log_stream);
+ parser.load_from_file(path);
+ if (!buffer.empty()) {
+ Logger::error("Parser load errors:\n\n", buffer, "\n");
+ buffer.clear();
+ }
+ if (parser.has_fatal_error() || parser.has_error() || parser.has_warning()) {
+ Logger::error("Parser warnings/errors while loading ", path);
+ return parser;
+ }
+ parser.simple_parse();
+ if (!buffer.empty()) {
+ Logger::error("Parser parse errors:\n\n", buffer, "\n");
+ buffer.clear();
+ }
+ if (parser.has_fatal_error() || parser.has_error() || parser.has_warning()) {
+ Logger::error("Parser warnings/errors while parsing ", path);
+ }
+ return parser;
+}
+
+return_t Dataloader::load_defines(GameManager& game_manager) const {
+ static const std::filesystem::path graphical_culture_type_file = "common/graphicalculturetype.txt";
+ static const std::filesystem::path culture_file = "common/cultures.txt";
+
+ return_t ret = SUCCESS;
+
+ if (game_manager.pop_manager.culture_manager.load_graphical_culture_type_file(parse_defines(
+ lookup_file(graphical_culture_type_file)).get_file_node()) != SUCCESS) {
+ Logger::error("Failed to load graphical culture types!");
+ ret = FAILURE;
+ }
+ if (game_manager.pop_manager.culture_manager.load_culture_file(parse_defines(
+ lookup_file(culture_file)).get_file_node()) != SUCCESS) {
+ Logger::error("Failed to load cultures!");
+ ret = FAILURE;
+ }
+
+ return ret;
+}
+
+static return_t load_pop_history_file(GameManager& game_manager, std::filesystem::path const& path) {
+ return NodeTools::expect_dictionary(parse_defines(path).get_file_node(), [&game_manager](std::string_view province_key, ast::NodeCPtr province_node) -> return_t {
+ Province* province = game_manager.map.get_province_by_identifier(province_key);
+ if (province == nullptr) {
+ Logger::error("Invalid province id: ", province_key);
+ return FAILURE;
+ }
+ return NodeTools::expect_list(province_node, [&game_manager, &province](ast::NodeCPtr pop_node) -> return_t {
+ return game_manager.pop_manager.load_pop_into_province(*province, pop_node);
+ });
+ }, true);
+}
+
+return_t Dataloader::load_pop_history(GameManager& game_manager, std::filesystem::path const& path) const {
+ return_t ret = SUCCESS;
+ for (std::filesystem::path const& file : lookup_files_in_dir(path)) {
+ if (load_pop_history_file(game_manager, file) != SUCCESS) {
+ ret = FAILURE;
+ }
+ }
+ return ret;
+}
diff --git a/src/openvic/dataloader/Dataloader.hpp b/src/openvic/dataloader/Dataloader.hpp
index 062f3d5..b080300 100644
--- a/src/openvic/dataloader/Dataloader.hpp
+++ b/src/openvic/dataloader/Dataloader.hpp
@@ -1,16 +1,24 @@
#pragma once
-#include <filesystem>
-#include "../Simulation.hpp"
+
+#include "openvic/types/Return.hpp"
+#include "openvic-dataloader/v2script/Parser.hpp"
namespace OpenVic {
+ struct GameManager;
+
class Dataloader {
- public:
- enum class LoadingMode {
- DL_COMPATIBILITY
- };
+ std::vector<std::filesystem::path> roots;
+
+ public:
+ Dataloader() = default;
+
+ /* In reverse-load order, so base defines first and final loaded mod last */
+ return_t set_roots(std::vector<std::filesystem::path> new_roots);
+
+ std::filesystem::path lookup_file(std::filesystem::path const& path) const;
+ std::vector<std::filesystem::path> lookup_files_in_dir(std::filesystem::path const& path) const;
- static bool loadDir(std::filesystem::path p, Simulation& sim, LoadingMode loadMode = LoadingMode::DL_COMPATIBILITY) {
- return true;
- }
+ return_t load_defines(GameManager& game_manager) const;
+ return_t load_pop_history(GameManager& game_manager, std::filesystem::path const& path) const;
};
-} \ No newline at end of file
+}
diff --git a/src/openvic/dataloader/NodeTools.cpp b/src/openvic/dataloader/NodeTools.cpp
new file mode 100644
index 0000000..a37892f
--- /dev/null
+++ b/src/openvic/dataloader/NodeTools.cpp
@@ -0,0 +1,198 @@
+#include "NodeTools.hpp"
+
+#include <charconv>
+
+using namespace OpenVic;
+
+template<typename T>
+return_t NodeTools::expect_type(ast::NodeCPtr node, std::function<return_t(T const&)> callback) {
+ if (node != nullptr) {
+ if (node->is_type<T>()) {
+ return callback(ast::cast_node_cptr<T>(node));
+ }
+ Logger::error("Invalid node type ", node->get_type(), " when expecting ", T::get_type_static());
+ } else {
+ Logger::error("Null node when expecting ", T::get_type_static());
+ }
+ return FAILURE;
+}
+
+static return_t identifier_callback_wrapper(std::function<return_t(std::string_view)> callback, std::string_view identifier) {
+ if (!identifier.empty()) {
+ return callback(identifier);
+ }
+ Logger::error("Empty identifier node string");
+ return FAILURE;
+}
+
+return_t NodeTools::expect_identifier(ast::NodeCPtr node, std::function<return_t(std::string_view)> callback) {
+ return expect_type<ast::IdentifierNode>(node, [callback](ast::IdentifierNode const& identifier_node) -> return_t {
+ return identifier_callback_wrapper(callback, identifier_node._name);
+ });
+}
+
+return_t NodeTools::expect_string(ast::NodeCPtr node, std::function<return_t(std::string_view)> callback) {
+ return expect_type<ast::StringNode>(node, [callback](ast::StringNode const& string_node) -> return_t {
+ return callback(string_node._name);
+ });
+}
+
+return_t NodeTools::expect_identifier_or_string(ast::NodeCPtr node, std::function<return_t(std::string_view)> callback) {
+ if (node != nullptr) {
+ if (node->is_type<ast::IdentifierNode>()) {
+ return identifier_callback_wrapper(callback, ast::cast_node_cptr<ast::IdentifierNode>(node)._name);
+ } else if (node->is_type<ast::StringNode>()) {
+ return callback(ast::cast_node_cptr<ast::StringNode>(node)._name);
+ } else {
+ Logger::error("Invalid node type ", node->get_type(), " when expecting ", ast::IdentifierNode::get_type_static(), " or ", ast::StringNode::get_type_static());
+ }
+ } else {
+ Logger::error("Null node when expecting ", ast::IdentifierNode::get_type_static(), " or ", ast::StringNode::get_type_static());
+ }
+ return FAILURE;
+}
+
+return_t NodeTools::expect_bool(ast::NodeCPtr node, std::function<return_t(bool)> callback) {
+ return expect_identifier(node, [callback](std::string_view identifier) -> return_t {
+ if (identifier == "yes") {
+ return callback(true);
+ } else if (identifier == "no") {
+ return callback(false);
+ }
+ Logger::error("Invalid bool identifier text: ", identifier);
+ return FAILURE;
+ });
+}
+
+return_t NodeTools::expect_int(ast::NodeCPtr node, std::function<return_t(int64_t)> callback) {
+ return expect_identifier(node, [callback](std::string_view identifier) -> return_t {
+ int64_t val;
+ char const* const start = identifier.data();
+ char const* const end = start + identifier.size();
+ const std::from_chars_result result = std::from_chars(start, end, val);
+ if (result.ec == std::errc{} && result.ptr == end) {
+ return callback(val);
+ }
+ Logger::error("Invalid int identifier text: ", identifier);
+ return FAILURE;
+ });
+}
+
+return_t NodeTools::expect_uint(ast::NodeCPtr node, std::function<return_t(uint64_t)> callback) {
+ return expect_identifier(node, [callback](std::string_view identifier) -> return_t {
+ uint64_t val;
+ char const* identifier_end = identifier.data() + identifier.size();
+ const std::from_chars_result result = std::from_chars(identifier.data(), identifier_end, val);
+ if (result.ec == std::errc{} && result.ptr == identifier_end) {
+ return callback(val);
+ }
+ Logger::error("Invalid uint identifier text: ", identifier);
+ return FAILURE;
+ });
+}
+
+return_t NodeTools::expect_fixed_point(ast::NodeCPtr node, std::function<return_t(FP)> callback) {
+ return expect_identifier(node, [callback](std::string_view identifier) -> return_t {
+ bool successful = false;
+ FP val = FP::parse(identifier.data(), identifier.length(), &successful);
+ if (successful) {
+ return callback(val);
+ }
+ Logger::error("Invalid fixed point identifier text: ", identifier);
+ return FAILURE;
+ });
+}
+
+return_t NodeTools::expect_colour(ast::NodeCPtr node, std::function<return_t(colour_t)> callback) {
+ colour_t col = NULL_COLOUR;
+ uint32_t components = 0;
+ return_t ret = expect_list(node, std::bind(expect_fixed_point, std::placeholders::_1,
+ [&col, &components](FP val) -> return_t {
+ return_t ret = SUCCESS;
+ if (val < 0 || val > 255) {
+ Logger::error("Invalid colour component: ", val);
+ val = FP::_0();
+ ret = FAILURE;
+ }
+ if (val <= 1) val *= 255;
+ col = (col << 8) | val.to_int32_t();
+ components++;
+ return ret;
+ }), 3);
+ if (components < 3) col <<= 8 * (3 - components);
+ if (callback(col) != SUCCESS) ret = FAILURE;
+ return ret;
+}
+
+return_t NodeTools::expect_date(ast::NodeCPtr node, std::function<return_t(Date)> callback) {
+ return expect_identifier(node, [callback](std::string_view identifier) -> return_t {
+ bool successful = false;
+ Date date = Date::from_string(identifier, &successful);
+ if (successful) {
+ return callback(date);
+ }
+ Logger::error("Invalid date identifier text: ", identifier);
+ return FAILURE;
+ });
+}
+
+return_t NodeTools::expect_assign(ast::NodeCPtr node, std::function<return_t(std::string_view, ast::NodeCPtr)> callback) {
+ return expect_type<ast::AssignNode>(node, [callback](ast::AssignNode const& assign_node) -> return_t {
+ return callback(assign_node._name, assign_node._initializer.get());
+ });
+}
+
+return_t NodeTools::expect_list(ast::NodeCPtr node, std::function<return_t(ast::NodeCPtr)> callback, size_t length, bool file_node) {
+ const std::function<return_t(std::vector<ast::NodeUPtr> const&)> list_func = [length, callback](std::vector<ast::NodeUPtr> const& list) -> return_t {
+ return_t ret = SUCCESS;
+ size_t size = list.size();
+ if (length > 0 && size != length) {
+ Logger::error("List length ", size, " does not match expected length ", length);
+ ret = FAILURE;
+ if (length < size) size = length;
+ }
+ std::for_each(list.begin(), list.begin() + size, [callback, &ret](ast::NodeUPtr const& sub_node) {
+ if (callback(sub_node.get()) != SUCCESS) ret = FAILURE;
+ });
+ return ret;
+ };
+ if (!file_node) {
+ return expect_type<ast::ListNode>(node, [list_func](ast::ListNode const& list_node) -> return_t {
+ return list_func(list_node._statements);
+ });
+ } else {
+ return expect_type<ast::FileNode>(node, [list_func](ast::FileNode const& file_node) -> return_t {
+ return list_func(file_node._statements);
+ });
+ }
+}
+
+return_t NodeTools::expect_dictionary(ast::NodeCPtr node, std::function<return_t(const std::string_view, ast::NodeCPtr)> callback, bool file_node) {
+ return expect_list(node, std::bind(expect_assign, std::placeholders::_1, callback), 0, file_node);
+}
+
+return_t NodeTools::expect_dictionary_keys(ast::NodeCPtr node, dictionary_key_map_t const& keys, bool allow_other_keys, bool file_node) {
+ std::map<std::string, size_t, std::less<void>> key_count;
+ return_t ret = expect_dictionary(node, [keys, allow_other_keys, &key_count](const std::string_view key, ast::NodeCPtr value) -> return_t {
+ const dictionary_key_map_t::const_iterator it = keys.find(key);
+ if (it == keys.end()) {
+ if (allow_other_keys) return SUCCESS;
+ Logger::error("Invalid dictionary key: ", key);
+ return FAILURE;
+ }
+ const size_t count = ++key_count[it->first];
+ dictionary_entry_t const& entry = it->second;
+ if (!entry.can_repeat && count > 1) {
+ Logger::error("Invalid repeat of dictionary key: ", key);
+ return FAILURE;
+ }
+ return entry.callback(value);
+ }, file_node);
+ for (dictionary_key_map_t::value_type const& entry : keys) {
+ if (entry.second.must_appear && key_count.find(entry.first) == key_count.end()) {
+ Logger::error("Mandatory dictionary key not present: ", entry.first);
+ ret = FAILURE;
+ }
+ }
+ return ret;
+}
diff --git a/src/openvic/dataloader/NodeTools.hpp b/src/openvic/dataloader/NodeTools.hpp
new file mode 100644
index 0000000..ca7130c
--- /dev/null
+++ b/src/openvic/dataloader/NodeTools.hpp
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <map>
+
+#include <openvic-dataloader/v2script/AbstractSyntaxTree.hpp>
+
+#include "openvic/types/Colour.hpp"
+#include "openvic/types/Return.hpp"
+#include "openvic/types/Date.hpp"
+#include "openvic/types/fixed_point/FP.hpp"
+
+namespace OpenVic {
+ namespace ast = ovdl::v2script::ast;
+
+ namespace NodeTools {
+
+ template<typename T>
+ return_t expect_type(ast::NodeCPtr node, std::function<return_t(T const&)> callback);
+
+ return_t expect_identifier(ast::NodeCPtr node, std::function<return_t(std::string_view)> callback);
+ return_t expect_string(ast::NodeCPtr node, std::function<return_t(std::string_view)> callback);
+ return_t expect_identifier_or_string(ast::NodeCPtr node, std::function<return_t(std::string_view)> callback);
+ return_t expect_bool(ast::NodeCPtr node, std::function<return_t(bool)> callback);
+ return_t expect_int(ast::NodeCPtr node, std::function<return_t(int64_t)> callback);
+ return_t expect_uint(ast::NodeCPtr node, std::function<return_t(uint64_t)> callback);
+ return_t expect_fixed_point(ast::NodeCPtr node, std::function<return_t(FP)> callback);
+ return_t expect_colour(ast::NodeCPtr node, std::function<return_t(colour_t)> callback);
+ return_t expect_date(ast::NodeCPtr node, std::function<return_t(Date)> callback);
+ return_t expect_assign(ast::NodeCPtr node, std::function<return_t(std::string_view, ast::NodeCPtr)> callback);
+ return_t expect_list(ast::NodeCPtr node, std::function<return_t(ast::NodeCPtr)> callback, size_t length = 0, bool file_node = false);
+ return_t expect_dictionary(ast::NodeCPtr node, std::function<return_t(std::string_view, ast::NodeCPtr)> callback, bool file_node = false);
+
+ static const std::function<return_t(ast::NodeCPtr)> success_callback = [](ast::NodeCPtr) -> return_t { return SUCCESS; };
+
+ struct dictionary_entry_t {
+ bool must_appear, can_repeat;
+ std::function<return_t(ast::NodeCPtr)> callback;
+ };
+ using dictionary_key_map_t = std::map<std::string, dictionary_entry_t, std::less<void>>;
+ return_t expect_dictionary_keys(ast::NodeCPtr node, dictionary_key_map_t const& keys, bool allow_other_keys = false, bool file_node = false);
+ }
+}
diff --git a/src/openvic/economy/Good.cpp b/src/openvic/economy/Good.cpp
index 9e8a7dd..f14c170 100644
--- a/src/openvic/economy/Good.cpp
+++ b/src/openvic/economy/Good.cpp
@@ -20,11 +20,11 @@ std::string const& Good::get_category() const {
return category;
}
-price_t Good::get_base_price() const {
+Good::price_t Good::get_base_price() const {
return base_price;
}
-price_t Good::get_price() const {
+Good::price_t Good::get_price() const {
return price;
}
@@ -44,7 +44,7 @@ void Good::reset_to_defaults() {
GoodManager::GoodManager() : goods { "goods" } {}
return_t GoodManager::add_good(const std::string_view identifier, colour_t colour, const std::string_view category,
- price_t base_price, bool default_available, bool tradeable, bool currency, bool overseas_maintenance) {
+ Good::price_t base_price, bool default_available, bool tradeable, bool currency, bool overseas_maintenance) {
if (identifier.empty()) {
Logger::error("Invalid good identifier - empty!");
return FAILURE;
@@ -57,7 +57,7 @@ return_t GoodManager::add_good(const std::string_view identifier, colour_t colou
Logger::error("Invalid good category for ", identifier, ": empty!");
return FAILURE;
}
- if (base_price <= NULL_PRICE) {
+ if (base_price <= Good::NULL_PRICE) {
Logger::error("Invalid base price for ", identifier, ": ", base_price);
return FAILURE;
}
diff --git a/src/openvic/economy/Good.hpp b/src/openvic/economy/Good.hpp
index cfe185d..5c8e68c 100644
--- a/src/openvic/economy/Good.hpp
+++ b/src/openvic/economy/Good.hpp
@@ -1,6 +1,7 @@
#pragma once
-#include "../Types.hpp"
+#include "openvic/types/IdentifierRegistry.hpp"
+#include "openvic/types/fixed_point/FP.hpp"
namespace OpenVic {
struct GoodManager;
@@ -20,6 +21,9 @@ namespace OpenVic {
struct Good : HasIdentifierAndColour {
friend struct GoodManager;
+ using price_t = FP;
+ static constexpr price_t NULL_PRICE = FP::_0();
+
private:
const std::string category;
const price_t base_price;
@@ -48,7 +52,7 @@ namespace OpenVic {
public:
GoodManager();
- return_t add_good(const std::string_view identifier, colour_t colour, const std::string_view category, price_t base_price,
+ return_t add_good(const std::string_view identifier, colour_t colour, const std::string_view category, Good::price_t base_price,
bool default_available, bool tradeable, bool currency, bool overseas_maintenance);
void lock_goods();
void reset_to_defaults();
diff --git a/src/openvic/map/Building.cpp b/src/openvic/map/Building.cpp
index 73a1886..d2662c4 100644
--- a/src/openvic/map/Building.cpp
+++ b/src/openvic/map/Building.cpp
@@ -2,8 +2,8 @@
#include <cassert>
-#include "../utility/Logger.hpp"
-#include "Province.hpp"
+#include "openvic/utility/Logger.hpp"
+#include "openvic/map/Province.hpp"
using namespace OpenVic;
diff --git a/src/openvic/map/Building.hpp b/src/openvic/map/Building.hpp
index c00932b..2ff9409 100644
--- a/src/openvic/map/Building.hpp
+++ b/src/openvic/map/Building.hpp
@@ -2,8 +2,8 @@
#include <vector>
-#include "../Date.hpp"
-#include "../Types.hpp"
+#include "openvic/types/Date.hpp"
+#include "openvic/types/IdentifierRegistry.hpp"
namespace OpenVic {
diff --git a/src/openvic/map/Map.cpp b/src/openvic/map/Map.cpp
index d5cfdd2..95d94fd 100644
--- a/src/openvic/map/Map.cpp
+++ b/src/openvic/map/Map.cpp
@@ -3,8 +3,8 @@
#include <cassert>
#include <unordered_set>
-#include "../economy/Good.hpp"
-#include "../utility/Logger.hpp"
+#include "openvic/economy/Good.hpp"
+#include "openvic/utility/Logger.hpp"
using namespace OpenVic;
@@ -403,9 +403,6 @@ return_t Map::setup(GoodManager const& good_manager, BuildingManager const& buil
if (!province.is_water() && good_manager.get_good_count() > 0)
province.rgo = good_manager.get_good_by_index(province.get_index() % good_manager.get_good_count());
if (building_manager.generate_province_buildings(province) != SUCCESS) ret = FAILURE;
- // Add some pops to the province (for testing purposes)
- if (!province.is_water())
- pop_manager.generate_test_pops(province);
}
return ret;
}
diff --git a/src/openvic/map/Map.hpp b/src/openvic/map/Map.hpp
index 1b21f5d..26c07c8 100644
--- a/src/openvic/map/Map.hpp
+++ b/src/openvic/map/Map.hpp
@@ -2,7 +2,7 @@
#include <functional>
-#include "Region.hpp"
+#include "openvic/map/Region.hpp"
namespace OpenVic {
@@ -21,6 +21,8 @@ namespace OpenVic {
public:
static const Mapmode ERROR_MAPMODE;
+ Mapmode(Mapmode&&) = default;
+
index_t get_index() const;
colour_t get_colour(Map const& map, Province const& province) const;
};
diff --git a/src/openvic/map/Province.cpp b/src/openvic/map/Province.cpp
index 3de7263..76e478f 100644
--- a/src/openvic/map/Province.cpp
+++ b/src/openvic/map/Province.cpp
@@ -65,11 +65,13 @@ std::string Province::to_string() const {
return stream.str();
}
-void Province::add_pop(Pop&& pop) {
- if (is_water()) {
- Logger::error("Trying to add pop to water province ", get_identifier());
- } else {
+return_t Province::add_pop(Pop&& pop) {
+ if (!is_water()) {
pops.push_back(std::move(pop));
+ return SUCCESS;
+ } else {
+ Logger::error("Trying to add pop to water province ", get_identifier());
+ return FAILURE;
}
}
diff --git a/src/openvic/map/Province.hpp b/src/openvic/map/Province.hpp
index c2a8ac9..3556bcb 100644
--- a/src/openvic/map/Province.hpp
+++ b/src/openvic/map/Province.hpp
@@ -1,7 +1,7 @@
#pragma once
-#include "../pop/Pop.hpp"
-#include "Building.hpp"
+#include "openvic/pop/Pop.hpp"
+#include "openvic/map/Building.hpp"
namespace OpenVic {
struct Map;
@@ -50,7 +50,7 @@ namespace OpenVic {
Good const* get_rgo() const;
std::string to_string() const;
- void add_pop(Pop&& pop);
+ return_t add_pop(Pop&& pop);
void clear_pops();
Pop::pop_size_t get_total_population() const;
distribution_t const& get_pop_type_distribution() const;
diff --git a/src/openvic/map/Region.cpp b/src/openvic/map/Region.cpp
index fc207e3..8ea45f0 100644
--- a/src/openvic/map/Region.cpp
+++ b/src/openvic/map/Region.cpp
@@ -1,7 +1,5 @@
#include "Region.hpp"
-#include <cassert>
-
using namespace OpenVic;
return_t ProvinceSet::add_province(Province* province) {
diff --git a/src/openvic/map/Region.hpp b/src/openvic/map/Region.hpp
index beeb76e..9b5b914 100644
--- a/src/openvic/map/Region.hpp
+++ b/src/openvic/map/Region.hpp
@@ -1,6 +1,6 @@
#pragma once
-#include "Province.hpp"
+#include "openvic/map/Province.hpp"
namespace OpenVic {
diff --git a/src/openvic/pop/Culture.cpp b/src/openvic/pop/Culture.cpp
index f50bb23..3bfb9b9 100644
--- a/src/openvic/pop/Culture.cpp
+++ b/src/openvic/pop/Culture.cpp
@@ -1,15 +1,16 @@
#include "Culture.hpp"
-#include <cassert>
+#include "openvic/dataloader/NodeTools.hpp"
using namespace OpenVic;
GraphicalCultureType::GraphicalCultureType(const std::string_view new_identifier) : HasIdentifier { new_identifier } {}
CultureGroup::CultureGroup(const std::string_view new_identifier,
- GraphicalCultureType const& new_unit_graphical_culture_type)
+ GraphicalCultureType const& new_unit_graphical_culture_type, bool new_is_overseas)
: HasIdentifier { new_identifier },
- unit_graphical_culture_type { new_unit_graphical_culture_type } {}
+ unit_graphical_culture_type { new_unit_graphical_culture_type },
+ is_overseas { new_is_overseas } {}
GraphicalCultureType const& CultureGroup::get_unit_graphical_culture_type() const {
return unit_graphical_culture_type;
@@ -20,8 +21,7 @@ Culture::Culture(const std::string_view new_identifier, colour_t new_colour, Cul
: HasIdentifierAndColour { new_identifier, new_colour, true },
group { new_group },
first_names { new_first_names },
- last_names { new_last_names } {
-}
+ last_names { new_last_names } {}
CultureGroup const& Culture::get_group() const {
return group;
@@ -48,7 +48,7 @@ GraphicalCultureType const* CultureManager::get_graphical_culture_type_by_identi
return graphical_culture_types.get_item_by_identifier(identifier);
}
-return_t CultureManager::add_culture_group(const std::string_view identifier, GraphicalCultureType const* graphical_culture_type) {
+return_t CultureManager::add_culture_group(const std::string_view identifier, GraphicalCultureType const* graphical_culture_type, bool is_overseas) {
if (!graphical_culture_types.is_locked()) {
Logger::error("Cannot register culture groups until graphical culture types are locked!");
return FAILURE;
@@ -61,7 +61,7 @@ return_t CultureManager::add_culture_group(const std::string_view identifier, Gr
Logger::error("Null graphical culture type for ", identifier);
return FAILURE;
}
- return culture_groups.add_item({ identifier, *graphical_culture_type });
+ return culture_groups.add_item({ identifier, *graphical_culture_type, is_overseas });
}
void CultureManager::lock_culture_groups() {
@@ -89,7 +89,6 @@ return_t CultureManager::add_culture(const std::string_view identifier, colour_t
Logger::error("Invalid culture colour for ", identifier, ": ", Culture::colour_to_hex_string(colour));
return FAILURE;
}
- // TODO - name list sanatisation?
return cultures.add_item({ identifier, colour, *group, first_names, last_names });
}
@@ -100,3 +99,99 @@ void CultureManager::lock_cultures() {
Culture const* CultureManager::get_culture_by_identifier(const std::string_view identifier) const {
return cultures.get_item_by_identifier(identifier);
}
+
+return_t CultureManager::load_graphical_culture_type_file(ast::NodeCPtr root) {
+ const return_t ret = NodeTools::expect_list(root, [this](ast::NodeCPtr node) -> return_t {
+ return NodeTools::expect_identifier(node, [this](std::string_view identifier) -> return_t {
+ return add_graphical_culture_type(identifier);
+ });
+ }, 0, true);
+ lock_graphical_culture_types();
+ return ret;
+}
+
+return_t CultureManager::load_culture_file(ast::NodeCPtr root) {
+ if (!graphical_culture_types.is_locked()) {
+ Logger::error("Cannot load culture groups until graphical culture types are locked!");
+ return FAILURE;
+ }
+
+ static const std::string default_unit_graphical_culture_type_identifier = "Generic";
+ GraphicalCultureType const* const default_unit_graphical_culture_type = get_graphical_culture_type_by_identifier(default_unit_graphical_culture_type_identifier);
+ if (default_unit_graphical_culture_type == nullptr) {
+ Logger::error("Failed to find default unit graphical culture type: ", default_unit_graphical_culture_type_identifier);
+ }
+
+ return_t ret = NodeTools::expect_dictionary(root, [this, default_unit_graphical_culture_type](std::string_view key, ast::NodeCPtr value) -> return_t {
+
+ GraphicalCultureType const* unit_graphical_culture_type = default_unit_graphical_culture_type;
+ bool is_overseas = true;
+
+ return_t ret = NodeTools::expect_dictionary_keys(value, {
+ { "leader", { true, false, NodeTools::success_callback } },
+ { "unit", { false, false, [this, &unit_graphical_culture_type](ast::NodeCPtr node) -> return_t {
+ return NodeTools::expect_identifier(node, [this, &unit_graphical_culture_type](std::string_view identifier) -> return_t {
+ unit_graphical_culture_type = get_graphical_culture_type_by_identifier(identifier);
+ if (unit_graphical_culture_type != nullptr) return SUCCESS;
+ Logger::error("Invalid unit graphical culture type: ", identifier);
+ return FAILURE;
+ });
+ } } },
+ { "union", { false, false, NodeTools::success_callback } },
+ { "is_overseas", { false, false, [&is_overseas](ast::NodeCPtr node) -> return_t {
+ return NodeTools::expect_bool(node, [&is_overseas](bool val) -> return_t {
+ is_overseas = val;
+ return SUCCESS;
+ });
+ } } }
+ }, true);
+ if (add_culture_group(key, unit_graphical_culture_type, is_overseas) != SUCCESS) ret = FAILURE;
+ return ret;
+ }, true);
+ lock_culture_groups();
+ if (NodeTools::expect_dictionary(root, [this](std::string_view culture_group_key, ast::NodeCPtr culture_group_value) -> return_t {
+
+ CultureGroup const* culture_group = get_culture_group_by_identifier(culture_group_key);
+
+ return NodeTools::expect_dictionary(culture_group_value, [this, culture_group](std::string_view key, ast::NodeCPtr value) -> return_t {
+ if (key == "leader" || key == "unit" || key == "union" || key == "is_overseas") return SUCCESS;
+
+ colour_t colour = NULL_COLOUR;
+ Culture::name_list_t first_names, last_names;
+
+ static const std::function<return_t(Culture::name_list_t&, ast::NodeCPtr)> read_name_list = [](Culture::name_list_t& names, ast::NodeCPtr node) -> return_t {
+ return NodeTools::expect_list(node, [&names](ast::NodeCPtr val) -> return_t {
+ return NodeTools::expect_identifier_or_string(val, [&names](std::string_view str) -> return_t {
+ if (!str.empty()) {
+ names.push_back(std::string { str });
+ return SUCCESS;
+ }
+ Logger::error("Empty identifier or string");
+ return FAILURE;
+ });
+ });
+ };
+
+ return_t ret = NodeTools::expect_dictionary_keys(value, {
+ { "color", { true, false, [&colour](ast::NodeCPtr node) -> return_t {
+ return NodeTools::expect_colour(node, [&colour](colour_t val) -> return_t {
+ colour = val;
+ return SUCCESS;
+ });
+ } } },
+ { "first_names", { true, false, [&first_names](ast::NodeCPtr node) -> return_t {
+ return read_name_list(first_names, node);
+ } } },
+ { "last_names", { true, false, [&last_names](ast::NodeCPtr node) -> return_t {
+ return read_name_list(last_names, node);
+ } } },
+ { "radicalism", { false, false, NodeTools::success_callback } },
+ { "primary", { false, false, NodeTools::success_callback } }
+ });
+ if (add_culture(key, colour, culture_group, first_names, last_names) != SUCCESS) ret = FAILURE;
+ return ret;
+ });
+ }, true) != SUCCESS) ret = FAILURE;
+ lock_cultures();
+ return ret;
+}
diff --git a/src/openvic/pop/Culture.hpp b/src/openvic/pop/Culture.hpp
index 645e226..7012512 100644
--- a/src/openvic/pop/Culture.hpp
+++ b/src/openvic/pop/Culture.hpp
@@ -1,6 +1,7 @@
#pragma once
-#include "../Types.hpp"
+#include "openvic/types/IdentifierRegistry.hpp"
+#include "openvic/dataloader/NodeTools.hpp"
namespace OpenVic {
@@ -21,10 +22,11 @@ namespace OpenVic {
private:
GraphicalCultureType const& unit_graphical_culture_type;
+ bool is_overseas;
// TODO - leader type, union tag
- CultureGroup(const std::string_view new_identifier, GraphicalCultureType const& new_unit_graphical_culture_type);
+ CultureGroup(const std::string_view new_identifier, GraphicalCultureType const& new_unit_graphical_culture_type, bool new_is_overseas);
public:
CultureGroup(CultureGroup&&) = default;
@@ -63,11 +65,14 @@ namespace OpenVic {
return_t add_graphical_culture_type(const std::string_view identifier);
void lock_graphical_culture_types();
GraphicalCultureType const* get_graphical_culture_type_by_identifier(const std::string_view identifier) const;
- return_t add_culture_group(const std::string_view identifier, GraphicalCultureType const* new_graphical_culture_type);
+ return_t add_culture_group(const std::string_view identifier, GraphicalCultureType const* new_graphical_culture_type, bool is_overseas);
void lock_culture_groups();
CultureGroup const* get_culture_group_by_identifier(const std::string_view identifier) const;
return_t add_culture(const std::string_view identifier, colour_t colour, CultureGroup const* group, Culture::name_list_t const& first_names, Culture::name_list_t const& last_names);
void lock_cultures();
Culture const* get_culture_by_identifier(const std::string_view identifier) const;
+
+ return_t load_graphical_culture_type_file(ast::NodeCPtr node);
+ return_t load_culture_file(ast::NodeCPtr node);
};
}
diff --git a/src/openvic/pop/Pop.cpp b/src/openvic/pop/Pop.cpp
index 81915f6..710d3e0 100644
--- a/src/openvic/pop/Pop.cpp
+++ b/src/openvic/pop/Pop.cpp
@@ -2,8 +2,9 @@
#include <cassert>
-#include "../map/Province.hpp"
-#include "../utility/Logger.hpp"
+#include "openvic/dataloader/NodeTools.hpp"
+#include "openvic/map/Province.hpp"
+#include "openvic/utility/Logger.hpp"
using namespace OpenVic;
@@ -106,18 +107,7 @@ static const std::string test_pop_type_poor = "test_pop_type_poor";
static const std::string test_pop_type_middle = "test_pop_type_middle";
static const std::string test_pop_type_rich = "test_pop_type_rich";
-
PopManager::PopManager() : pop_types { "pop types" } {
- culture_manager.add_graphical_culture_type(test_graphical_culture_type);
- culture_manager.lock_graphical_culture_types();
-
- culture_manager.add_culture_group(test_culture_group, culture_manager.get_graphical_culture_type_by_identifier(test_graphical_culture_type));
- culture_manager.lock_culture_groups();
-
- culture_manager.add_culture(test_culture, 0x0000FF, culture_manager.get_culture_group_by_identifier(test_culture_group),
- { "john" }, { "smith" });
- culture_manager.lock_cultures();
-
religion_manager.add_religion_group(test_religion_group);
religion_manager.lock_religion_groups();
@@ -163,18 +153,45 @@ PopType const* PopManager::get_pop_type_by_identifier(const std::string_view ide
return pop_types.get_item_by_identifier(identifier);
}
-void PopManager::generate_test_pops(Province& province) const {
- if (pop_types.is_locked()) {
- static PopType const& type_poor = *get_pop_type_by_identifier(test_pop_type_poor);
- static PopType const& type_middle = *get_pop_type_by_identifier(test_pop_type_middle);
- static PopType const& type_rich = *get_pop_type_by_identifier(test_pop_type_rich);
- static Culture const& culture = *culture_manager.get_culture_by_identifier(test_culture);
- static Religion const& religion = *religion_manager.get_religion_by_identifier(test_religion);
-
- province.add_pop({ type_poor, culture, religion, static_cast<Pop::pop_size_t>(province.get_index() * province.get_index()) * 100 });
- province.add_pop({ type_middle, culture, religion, static_cast<Pop::pop_size_t>(province.get_index() * province.get_index()) * 50 });
- province.add_pop({ type_rich, culture, religion, static_cast<Pop::pop_size_t>(province.get_index()) * 1000 });
+return_t PopManager::load_pop_into_province(Province& province, ast::NodeCPtr root) const {
+ static PopType const* type = get_pop_type_by_identifier("test_pop_type_poor");
+ Culture const* culture = nullptr;
+ static Religion const* religion = religion_manager.get_religion_by_identifier("test_religion");
+ Pop::pop_size_t size = 0;
+
+ return_t ret = NodeTools::expect_assign(root, [this, &culture, &size](std::string_view, ast::NodeCPtr pop_node) -> return_t {
+ return NodeTools::expect_dictionary_keys(pop_node, {
+ { "culture", { true, false, [this, &culture](ast::NodeCPtr node) -> return_t {
+ return NodeTools::expect_identifier(node, [&culture, this](std::string_view identifier) -> return_t {
+ culture = culture_manager.get_culture_by_identifier(identifier);
+ if (culture != nullptr) return SUCCESS;
+ Logger::error("Invalid pop culture: ", identifier);
+ return FAILURE;
+ });
+ } } },
+ { "religion", { true, false, NodeTools::success_callback } },
+ { "size", { true, false, [&size](ast::NodeCPtr node) -> return_t {
+ return NodeTools::expect_uint(node, [&size](uint64_t val) -> return_t {
+ if (val > 0) {
+ size = val;
+ return SUCCESS;
+ } else {
+ Logger::error("Invalid pop size: ", val, " (setting to 1 instead)");
+ size = 1;
+ return FAILURE;
+ }
+ });
+ } } },
+ { "militancy", { false, false, NodeTools::success_callback } },
+ { "rebel_type", { false, false, NodeTools::success_callback } }
+ });
+ });
+
+ if (type != nullptr && culture != nullptr && religion != nullptr && size > 0) {
+ if (province.add_pop({ *type, *culture, *religion, size }) != SUCCESS) ret = FAILURE;
} else {
- Logger::error("Cannot generate pops before pop types registry is locked!");
+ Logger::error("Some pop arguments are missing: type = ", type, ", culture = ", culture, ", religion = ", religion, ", size = ", size);
+ ret = FAILURE;
}
+ return ret;
}
diff --git a/src/openvic/pop/Pop.hpp b/src/openvic/pop/Pop.hpp
index db9633f..f5a7c4e 100644
--- a/src/openvic/pop/Pop.hpp
+++ b/src/openvic/pop/Pop.hpp
@@ -1,8 +1,7 @@
#pragma once
-#include "../Types.hpp"
-#include "Culture.hpp"
-#include "Religion.hpp"
+#include "openvic/pop/Culture.hpp"
+#include "openvic/pop/Religion.hpp"
namespace OpenVic {
@@ -21,7 +20,7 @@ namespace OpenVic {
PopType const& type;
Culture const& culture;
Religion const& religion;
- pop_size_t size, num_migrated, num_promoted, num_demoted, num_migrated;
+ pop_size_t size, num_promoted, num_demoted, num_migrated;
Pop(PopType const& new_type, Culture const& new_culture, Religion const& new_religion, pop_size_t new_size);
@@ -82,10 +81,11 @@ namespace OpenVic {
struct PopManager {
private:
IdentifierRegistry<PopType> pop_types;
+
+ public:
CultureManager culture_manager;
ReligionManager religion_manager;
- public:
PopManager();
return_t add_pop_type(const std::string_view identifier, colour_t new_colour, PopType::strata_t strata, PopType::sprite_t sprite,
@@ -94,6 +94,6 @@ namespace OpenVic {
void lock_pop_types();
PopType const* get_pop_type_by_identifier(const std::string_view identifier) const;
- void generate_test_pops(Province& province) const;
+ return_t load_pop_into_province(Province& province, ast::NodeCPtr root) const;
};
}
diff --git a/src/openvic/pop/Religion.hpp b/src/openvic/pop/Religion.hpp
index 4eb3e4c..e629977 100644
--- a/src/openvic/pop/Religion.hpp
+++ b/src/openvic/pop/Religion.hpp
@@ -1,6 +1,6 @@
#pragma once
-#include "../Types.hpp"
+#include "openvic/types/IdentifierRegistry.hpp"
namespace OpenVic {
diff --git a/src/openvic/types/Colour.hpp b/src/openvic/types/Colour.hpp
new file mode 100644
index 0000000..fdac40e
--- /dev/null
+++ b/src/openvic/types/Colour.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <algorithm>
+#include <cstdint>
+
+namespace OpenVic {
+ // Represents a 24-bit RGB integer OR a 32-bit ARGB integer
+ using colour_t = uint32_t;
+ /* When colour_t is used as an identifier, NULL_COLOUR is disallowed
+ * and should be reserved as an error value.
+ * When colour_t is used in a purely graphical context, NULL_COLOUR
+ * should be allowed.
+ */
+ static constexpr colour_t NULL_COLOUR = 0, FULL_COLOUR = 0xFF, MAX_COLOUR_RGB = 0xFFFFFF;
+ constexpr colour_t float_to_colour_byte(float f, float min = 0.0f, float max = 1.0f) {
+ return static_cast<colour_t>(std::clamp(min + f * (max - min), min, max) * 255.0f);
+ }
+ constexpr colour_t fraction_to_colour_byte(int n, int d, float min = 0.0f, float max = 1.0f) {
+ return float_to_colour_byte(static_cast<float>(n) / static_cast<float>(d), min, max);
+ }
+ constexpr colour_t float_to_alpha_value(float a) {
+ return float_to_colour_byte(a) << 24;
+ }
+ constexpr float colour_byte_to_float(colour_t colour) {
+ return std::clamp(static_cast<float>(colour) / 255.0f, 0.0f, 1.0f);
+ }
+}
diff --git a/src/openvic/Date.cpp b/src/openvic/types/Date.cpp
index 7c37386..b6e1367 100644
--- a/src/openvic/Date.cpp
+++ b/src/openvic/types/Date.cpp
@@ -5,7 +5,7 @@
#include <cctype>
#include <charconv>
-#include "utility/Logger.hpp"
+#include "openvic/utility/Logger.hpp"
using namespace OpenVic;
@@ -157,7 +157,8 @@ std::ostream& OpenVic::operator<<(std::ostream& out, Date const& date) {
}
// Parsed from string of the form YYYY.MM.DD
-Date Date::from_string(const std::string_view date) {
+Date Date::from_string(const std::string_view date, bool* successful) {
+ if (successful != nullptr) *successful = true;
size_t first_pos = 0;
while (first_pos < date.length() && std::isdigit(date[first_pos])) {
first_pos++;
@@ -165,6 +166,7 @@ Date Date::from_string(const std::string_view date) {
if (first_pos == 0) {
Logger::error("Failed to find year digits in date: ", date);
+ if (successful != nullptr) *successful = false;
return {};
}
@@ -174,6 +176,7 @@ Date Date::from_string(const std::string_view date) {
std::from_chars_result result = std::from_chars(start, end, val);
if (result.ec != std::errc{} || result.ptr != end || val < 0 || val >= 1 << (8 * sizeof(year_t))) {
Logger::error("Failed to read year: ", date);
+ if (successful != nullptr) *successful = false;
return {};
}
year_t year = val;
@@ -187,12 +190,14 @@ Date Date::from_string(const std::string_view date) {
}
if (first_pos == second_pos) {
Logger::error("Failed to find month digits in date: ", date);
+ if (successful != nullptr) *successful = false;
} else {
start = date.data() + first_pos;
end = date.data() + second_pos;
result = std::from_chars(start, end, val);
if (result.ec != std::errc{} || result.ptr != end || val < 1 || val > MONTHS_IN_YEAR) {
Logger::error("Failed to read month: ", date);
+ if (successful != nullptr) *successful = false;
} else {
month = val;
if (second_pos < date.length()) {
@@ -203,24 +208,33 @@ Date Date::from_string(const std::string_view date) {
}
if (second_pos == third_pos) {
Logger::error("Failed to find day digits in date: ", date);
+ if (successful != nullptr) *successful = false;
} else {
start = date.data() + second_pos;
end = date.data() + third_pos;
result = std::from_chars(start, end, val);
if (result.ec != std::errc{} || result.ptr != end || val < 1 || val > DAYS_IN_MONTH[month - 1]) {
Logger::error("Failed to read day: ", date);
+ if (successful != nullptr) *successful = false;
} else {
day = val;
if (third_pos < date.length()) {
Logger::error("Unexpected string \"", date.substr(third_pos), "\" at the end of date ", date);
+ if (successful != nullptr) *successful = false;
}
}
}
- } else Logger::error("Unexpected character \"", date[second_pos], "\" in month of date ", date);
+ } else {
+ Logger::error("Unexpected character \"", date[second_pos], "\" in month of date ", date);
+ if (successful != nullptr) *successful = false;
+ }
}
}
}
- } else Logger::error("Unexpected character \"", date[first_pos], "\" in year of date ", date);
+ } else {
+ Logger::error("Unexpected character \"", date[first_pos], "\" in year of date ", date);
+ if (successful != nullptr) *successful = false;
+ }
}
return _dateToTimespan(year, month, day);
};
diff --git a/src/openvic/Date.hpp b/src/openvic/types/Date.hpp
index 2994523..ca5645c 100644
--- a/src/openvic/Date.hpp
+++ b/src/openvic/types/Date.hpp
@@ -84,7 +84,7 @@ namespace OpenVic {
explicit operator std::string() const;
// Parsed from string of the form YYYY.MM.DD
- static Date from_string(const std::string_view date);
+ static Date from_string(const std::string_view date, bool* successful = nullptr);
};
std::ostream& operator<<(std::ostream& out, Date const& date);
}
diff --git a/src/openvic/Types.cpp b/src/openvic/types/IdentifierRegistry.cpp
index 4902ce3..97a1ff5 100644
--- a/src/openvic/Types.cpp
+++ b/src/openvic/types/IdentifierRegistry.cpp
@@ -1,4 +1,4 @@
-#include "Types.hpp"
+#include "IdentifierRegistry.hpp"
#include <cassert>
#include <iomanip>
diff --git a/src/openvic/Types.hpp b/src/openvic/types/IdentifierRegistry.hpp
index 3e7ac1d..e5ac94b 100644
--- a/src/openvic/Types.hpp
+++ b/src/openvic/types/IdentifierRegistry.hpp
@@ -1,43 +1,14 @@
#pragma once
-#include <algorithm>
-#include <cstdint>
#include <map>
#include <unordered_map>
#include <vector>
-#include "utility/Logger.hpp"
+#include "openvic/types/Colour.hpp"
+#include "openvic/types/Return.hpp"
+#include "openvic/utility/Logger.hpp"
namespace OpenVic {
- // Represents a 24-bit RGB integer OR a 32-bit ARGB integer
- using colour_t = uint32_t;
- /* When colour_t is used as an identifier, NULL_COLOUR is disallowed
- * and should be reserved as an error value.
- * When colour_t is used in a purely graphical context, NULL_COLOUR
- * should be allowed.
- */
- static constexpr colour_t NULL_COLOUR = 0, FULL_COLOUR = 0xFF, MAX_COLOUR_RGB = 0xFFFFFF;
- constexpr colour_t float_to_colour_byte(float f, float min = 0.0f, float max = 1.0f) {
- return static_cast<colour_t>(std::clamp(min + f * (max - min), min, max) * 255.0f);
- }
- constexpr colour_t fraction_to_colour_byte(int n, int d, float min = 0.0f, float max = 1.0f) {
- return float_to_colour_byte(static_cast<float>(n) / static_cast<float>(d), min, max);
- }
- constexpr colour_t float_to_alpha_value(float a) {
- return float_to_colour_byte(a) << 24;
- }
- constexpr float colour_byte_to_float(colour_t colour) {
- return std::clamp(static_cast<float>(colour) / 255.0f, 0.0f, 1.0f);
- }
-
- // TODO: price_t must be changed to a fixed-point numeric type before multiplayer
- using price_t = double;
- static constexpr price_t NULL_PRICE = 0.0;
- using return_t = bool;
-
- // This mirrors godot::Error, where `OK = 0` and `FAILED = 1`.
- static constexpr return_t SUCCESS = false, FAILURE = true;
-
/*
* Base class for objects with a non-empty string identifier,
* uniquely named instances of which can be entered into an
diff --git a/src/openvic/types/Return.hpp b/src/openvic/types/Return.hpp
new file mode 100644
index 0000000..04c3734
--- /dev/null
+++ b/src/openvic/types/Return.hpp
@@ -0,0 +1,8 @@
+#pragma once
+
+namespace OpenVic {
+ using return_t = bool;
+
+ // This mirrors godot::Error, where `OK = 0` and `FAILED = 1`.
+ constexpr return_t SUCCESS = false, FAILURE = true;
+}
diff --git a/src/openvic/types/fixed_point/FP.hpp b/src/openvic/types/fixed_point/FP.hpp
new file mode 100644
index 0000000..8b32906
--- /dev/null
+++ b/src/openvic/types/fixed_point/FP.hpp
@@ -0,0 +1,563 @@
+#pragma once
+
+#include <cstdint>
+#include <cstdlib>
+#include <cerrno>
+#include <cmath>
+#include <limits>
+#include <cstring>
+
+#include "FPLUT.hpp"
+#include "openvic/utility/FloatUtils.hpp"
+#include "openvic/utility/StringUtils.hpp"
+#include "openvic/utility/Logger.hpp"
+
+namespace OpenVic {
+ struct FP {
+ static constexpr size_t SIZE = 8;
+
+ constexpr FP() : value { 0 } {}
+ constexpr FP(int64_t new_value) : value { new_value } {}
+ constexpr FP(int32_t new_value) : value { static_cast<int64_t>(new_value) << FPLUT::PRECISION } {}
+
+ // Trivial destructor
+ ~FP() = default;
+
+ static constexpr FP max() {
+ return std::numeric_limits<int64_t>::max();
+ }
+
+ static constexpr FP min() {
+ return std::numeric_limits<int64_t>::min();
+ }
+
+ static constexpr FP usable_max() {
+ return static_cast<int64_t>(2147483648LL);
+ }
+
+ static constexpr FP usable_min() {
+ return -usable_max();
+ }
+
+ static constexpr FP _0() {
+ return 0;
+ }
+
+ static constexpr FP _1() {
+ return 1;
+ }
+
+ static constexpr FP _2() {
+ return 2;
+ }
+
+ static constexpr FP _3() {
+ return 3;
+ }
+
+ static constexpr FP _4() {
+ return 4;
+ }
+
+ static constexpr FP _5() {
+ return 5;
+ }
+
+ static constexpr FP _6() {
+ return 6;
+ }
+
+ static constexpr FP _7() {
+ return 7;
+ }
+
+ static constexpr FP _8() {
+ return 8;
+ }
+
+ static constexpr FP _9() {
+ return 9;
+ }
+
+ static constexpr FP _10() {
+ return 10;
+ }
+
+ static constexpr FP _50() {
+ return 50;
+ }
+
+ static constexpr FP _100() {
+ return 100;
+ }
+
+ static constexpr FP _200() {
+ return 200;
+ }
+
+ static constexpr FP _0_01() {
+ return _1() / _100();
+ }
+
+ static constexpr FP _0_02() {
+ return _0_01() * 2;
+ }
+
+ static constexpr FP _0_03() {
+ return _0_01() * 3;
+ }
+
+ static constexpr FP _0_04() {
+ return _0_01() * 4;
+ }
+
+ static constexpr FP _0_05() {
+ return _0_01() * 5;
+ }
+
+ static constexpr FP _0_10() {
+ return _1() / 10;
+ }
+
+ static constexpr FP _0_20() {
+ return _0_10() * 2;
+ }
+
+ static constexpr FP _0_25() {
+ return _1() / 4;
+ }
+
+ static constexpr FP _0_33() {
+ return _1() / 3;
+ }
+
+ static constexpr FP _0_50() {
+ return _1() / 2;
+ }
+
+ static constexpr FP _0_75() {
+ return _1() - _0_25();
+ }
+
+ static constexpr FP _0_95() {
+ return _1() - _0_05();
+ }
+
+ static constexpr FP _0_99() {
+ return _1() - _0_01();
+ }
+
+ static constexpr FP _1_01() {
+ return _1() + _0_01();
+ }
+
+ static constexpr FP _1_10() {
+ return _1() + _0_10();
+ }
+
+ static constexpr FP _1_50() {
+ return _1() + _0_50();
+ }
+
+ static constexpr FP minus_one() {
+ return -1;
+ }
+
+ static constexpr FP pi() {
+ return static_cast<int64_t>(205887LL);
+ }
+
+ static constexpr FP pi2() {
+ return pi() * 2;
+ }
+
+ static constexpr FP pi_quarter() {
+ return pi() / 4;
+ }
+
+ static constexpr FP pi_half() {
+ return pi() / 2;
+ }
+
+ static constexpr FP one_div_pi2() {
+ return 1 / pi2();
+ }
+
+ static constexpr FP deg2rad() {
+ return static_cast<int64_t>(1143LL);
+ }
+
+ static constexpr FP rad2deg() {
+ return static_cast<int64_t>(3754936LL);
+ }
+
+ static constexpr FP e() {
+ return static_cast<int64_t>(178145LL);
+ }
+
+ constexpr FP abs() const {
+ return value >= 0 ? value : -value;
+ }
+
+ // Not including sign, so -1.1 -> 0.1
+ constexpr FP get_frac() const {
+ return abs().value & ((1 << FPLUT::PRECISION) - 1);
+ }
+
+ constexpr int64_t to_int64_t() const {
+ return value >> FPLUT::PRECISION;
+ }
+
+ constexpr void set_raw_value(int64_t value) {
+ this->value = value;
+ }
+
+ constexpr int64_t get_raw_value() const {
+ return value;
+ }
+
+ constexpr int32_t to_int32_t() const {
+ return static_cast<int32_t>(value >> FPLUT::PRECISION);
+ }
+
+ constexpr float to_float() const {
+ return value / 65536.0F;
+ }
+
+ constexpr float to_float_rounded() const {
+ return static_cast<float>(FloatUtils::round_to_int64((value / 65536.0F) * 100000.0F)) / 100000.0F;
+ }
+
+ constexpr float to_double() const {
+ return value / 65536.0;
+ }
+
+ constexpr float to_double_rounded() const {
+ return FloatUtils::round_to_int64((value / 65536.0) * 100000.0) / 100000.0;
+ }
+
+ std::string to_string() const {
+ std::string str = std::to_string(to_int64_t()) + ".";
+ FP frac_part = get_frac();
+ do {
+ frac_part.value *= 10;
+ str += std::to_string(frac_part.to_int64_t());
+ frac_part = frac_part.get_frac();
+ } while (frac_part > 0);
+ return str;
+ }
+
+ // Deterministic
+ static constexpr FP parse_raw(int64_t value) {
+ return value;
+ }
+
+ // Deterministic
+ static constexpr FP parse(int64_t value) {
+ return value << FPLUT::PRECISION;
+ }
+
+ // Deterministic
+ static constexpr FP parse(const char* str, const char* end, bool *successful = nullptr) {
+ if (successful != nullptr) *successful = false;
+
+ if (str == nullptr || str >= end) {
+ return _0();
+ }
+
+ bool negative = false;
+
+ if (*str == '-') {
+ negative = true;
+ ++str;
+ if (str == end) return _0();
+ }
+
+ const char* dot_pointer = str;
+ while (*dot_pointer != '.' && ++dot_pointer != end);
+
+ if (dot_pointer == str && dot_pointer + 1 == end) {
+ // Invalid: ".", "+." or "-."
+ return _0();
+ }
+
+ FP result = _0();
+ if (successful != nullptr) *successful = true;
+
+ if (dot_pointer != str) {
+ // Non-empty integer part
+ bool int_successful = false;
+ result += parse_integer(str, dot_pointer, &int_successful);
+ if (!int_successful && successful != nullptr) *successful = false;
+ }
+
+ if (dot_pointer + 1 < end) {
+ // Non-empty fractional part
+ bool frac_successful = false;
+ result += parse_fraction(dot_pointer + 1, end, &frac_successful);
+ if (!frac_successful && successful != nullptr) *successful = false;
+ }
+
+ return negative ? -result : result;
+ }
+
+ static constexpr FP parse(const char* str, size_t length, bool *successful = nullptr) {
+ return parse(str, str + length, successful);
+ }
+
+ // Not Deterministic
+ static constexpr FP parse_unsafe(float value) {
+ return static_cast<int64_t>(value * FPLUT::ONE + 0.5f * (value < 0 ? -1 : 1));
+ }
+
+ // Not Deterministic
+ static FP parse_unsafe(const char* value) {
+ char* endpointer;
+ double double_value = std::strtod(value, &endpointer);
+
+ if (*endpointer != '\0') {
+ Logger::error("Unsafe fixed point parse failed to parse the end of a string: \"", endpointer, "\"");
+ }
+
+ int64_t integer_value = static_cast<long>(double_value * FPLUT::ONE + 0.5 * (double_value < 0 ? -1 : 1));
+
+ return integer_value;
+ }
+
+ constexpr operator int32_t() const {
+ return to_int32_t();
+ }
+
+ constexpr operator int64_t() const {
+ return to_int64_t();
+ }
+
+ constexpr operator float() const {
+ return to_float();
+ }
+
+ constexpr operator double() const {
+ return to_double();
+ }
+
+ operator std::string() const {
+ return to_string();
+ }
+
+ friend std::ostream& operator<<(std::ostream& stream, const FP& obj) {
+ return stream << obj.to_string();
+ }
+
+ constexpr friend FP operator-(const FP& obj) {
+ return -obj.value;
+ }
+
+ constexpr friend FP operator+(const FP& obj) {
+ return +obj.value;
+ }
+
+ constexpr friend FP operator+(const FP& lhs, const FP& rhs) {
+ return lhs.value + rhs.value;
+ }
+
+ constexpr friend FP operator+(const FP& lhs, const int32_t& rhs) {
+ return lhs.value + (static_cast<int64_t>(rhs) << FPLUT::PRECISION);
+ }
+
+ constexpr friend FP operator+(const int32_t& lhs, const FP& rhs) {
+ return (static_cast<int64_t>(lhs) << FPLUT::PRECISION) + rhs.value;
+ }
+
+ constexpr FP operator+=(const FP& obj) {
+ value += obj.value;
+ return *this;
+ }
+
+ constexpr FP operator+=(const int32_t& obj) {
+ value += (static_cast<int64_t>(obj) << FPLUT::PRECISION);
+ return *this;
+ }
+
+ constexpr friend FP operator-(const FP& lhs, const FP& rhs) {
+ return lhs.value - rhs.value;
+ }
+
+ constexpr friend FP operator-(const FP& lhs, const int32_t& rhs) {
+ return lhs.value - (static_cast<int64_t>(rhs) << FPLUT::PRECISION);
+ }
+
+ constexpr friend FP operator-(const int32_t& lhs, const FP& rhs) {
+ return (static_cast<int64_t>(lhs) << FPLUT::PRECISION) - rhs.value;
+ }
+
+ constexpr FP operator-=(const FP& obj) {
+ value -= obj.value;
+ return *this;
+ }
+
+ constexpr FP operator-=(const int32_t& obj) {
+ value -= (static_cast<int64_t>(obj) << FPLUT::PRECISION);
+ return *this;
+ }
+
+ constexpr friend FP operator*(const FP& lhs, const FP& rhs) {
+ return lhs.value * rhs.value >> FPLUT::PRECISION;
+ }
+
+ constexpr friend FP operator*(const FP& lhs, const int32_t& rhs) {
+ return lhs.value * rhs;
+ }
+
+ constexpr friend FP operator*(const int32_t& lhs, const FP& rhs) {
+ return lhs * rhs.value;
+ }
+
+ constexpr FP operator*=(const FP& obj) {
+ value *= obj.value >> FPLUT::PRECISION;
+ return *this;
+ }
+
+ constexpr FP operator*=(const int32_t& obj) {
+ value *= obj;
+ return *this;
+ }
+
+ constexpr friend FP operator/(const FP& lhs, const FP& rhs) {
+ return (lhs.value << FPLUT::PRECISION) / rhs.value;
+ }
+
+ constexpr friend FP operator/(const FP& lhs, const int32_t& rhs) {
+ return lhs.value / rhs;
+ }
+
+ constexpr friend FP operator/(const int32_t& lhs, const FP& rhs) {
+ return (static_cast<int64_t>(lhs) << (2 / FPLUT::PRECISION)) / rhs.value;
+ }
+
+ constexpr FP operator/=(const FP& obj) {
+ value = (value << FPLUT::PRECISION) / obj.value;
+ return *this;
+ }
+
+ constexpr FP operator/=(const int32_t& obj) {
+ value /= obj;
+ return *this;
+ }
+
+ constexpr friend FP operator%(const FP& lhs, const FP& rhs) {
+ return lhs.value % rhs.value;
+ }
+
+ constexpr friend FP operator%(const FP& lhs, const int32_t& rhs) {
+ return lhs.value % (static_cast<int64_t>(rhs) << FPLUT::PRECISION);
+ }
+
+ constexpr friend FP operator%(const int32_t& lhs, const FP& rhs) {
+ return (static_cast<int64_t>(lhs) << FPLUT::PRECISION) % rhs.value;
+ }
+
+ constexpr FP operator%=(const FP& obj) {
+ value %= obj.value;
+ return *this;
+ }
+
+ constexpr FP operator%=(const int32_t& obj) {
+ value %= (static_cast<int64_t>(obj) << FPLUT::PRECISION);
+ return *this;
+ }
+
+ constexpr friend bool operator<(const FP& lhs, const FP& rhs) {
+ return lhs.value < rhs.value;
+ }
+
+ constexpr friend bool operator<(const FP& lhs, const int32_t& rhs) {
+ return lhs.value < static_cast<int64_t>(rhs) << FPLUT::PRECISION;
+ }
+
+ constexpr friend bool operator<(const int32_t& lhs, const FP& rhs) {
+ return static_cast<int64_t>(lhs) << FPLUT::PRECISION < rhs.value;
+ }
+
+ constexpr friend bool operator<=(const FP& lhs, const FP& rhs) {
+ return lhs.value <= rhs.value;
+ }
+
+ constexpr friend bool operator<=(const FP& lhs, const int32_t& rhs) {
+ return lhs.value <= static_cast<int64_t>(rhs) << FPLUT::PRECISION;
+ }
+
+ constexpr friend bool operator<=(const int32_t& lhs, const FP& rhs) {
+ return static_cast<int64_t>(lhs) << FPLUT::PRECISION <= rhs.value;
+ }
+
+ constexpr friend bool operator>(const FP& lhs, const FP& rhs) {
+ return lhs.value > rhs.value;
+ }
+
+ constexpr friend bool operator>(const FP& lhs, const int32_t& rhs) {
+ return lhs.value > static_cast<int64_t>(rhs) << FPLUT::PRECISION;
+ }
+
+ constexpr friend bool operator>(const int32_t& lhs, const FP& rhs) {
+ return static_cast<int64_t>(lhs) << FPLUT::PRECISION > rhs.value;
+ }
+
+ constexpr friend bool operator>=(const FP& lhs, const FP& rhs) {
+ return lhs.value >= rhs.value;
+ }
+
+ constexpr friend bool operator>=(const FP& lhs, const int32_t& rhs) {
+ return lhs.value >= static_cast<int64_t>(rhs) << FPLUT::PRECISION;
+ }
+
+ constexpr friend bool operator>=(const int32_t& lhs, const FP& rhs) {
+ return static_cast<int64_t>(lhs) << FPLUT::PRECISION >= rhs.value;
+ }
+
+ constexpr friend bool operator==(const FP& lhs, const FP& rhs) {
+ return lhs.value == rhs.value;
+ }
+
+ constexpr friend bool operator==(const FP& lhs, const int32_t& rhs) {
+ return lhs.value == static_cast<int64_t>(rhs) << FPLUT::PRECISION;
+ }
+
+ constexpr friend bool operator==(const int32_t& lhs, const FP& rhs) {
+ return static_cast<int64_t>(lhs) << FPLUT::PRECISION == rhs.value;
+ }
+
+ constexpr friend bool operator!=(const FP& lhs, const FP& rhs) {
+ return lhs.value != rhs.value;
+ }
+
+ constexpr friend bool operator!=(const FP& lhs, const int32_t& rhs) {
+ return lhs.value != static_cast<int64_t>(rhs) << FPLUT::PRECISION;
+ }
+
+ constexpr friend bool operator!=(const int32_t& lhs, const FP& rhs) {
+ return static_cast<int64_t>(lhs) << FPLUT::PRECISION != rhs.value;
+ }
+
+
+ private:
+ int64_t value;
+
+ static constexpr FP parse_integer(const char* str, const char* end, bool* successful) {
+ int64_t parsed_value = StringUtils::string_to_int64(str, end, successful, 10);
+ return parse(parsed_value);
+ }
+
+ static constexpr FP parse_fraction(const char* str, const char* end, bool* successful) {
+ constexpr size_t READ_SIZE = 5;
+ if (str + READ_SIZE < end) {
+ Logger::error("Fixed point fraction parse will only use the first ", READ_SIZE,
+ " characters of ", std::string_view { str, static_cast<size_t>(end - str) });
+ end = str + READ_SIZE;
+ }
+ int64_t parsed_value = StringUtils::string_to_int64(str, end, successful, 10);
+ return parse_raw(parsed_value * 65536 / 100000);
+ }
+ };
+
+ static_assert(sizeof(FP) == FP::SIZE, "FP is not 8 bytes");
+}
diff --git a/src/openvic/types/fixed_point/FPLUT.hpp b/src/openvic/types/fixed_point/FPLUT.hpp
new file mode 100644
index 0000000..f9b21ab
--- /dev/null
+++ b/src/openvic/types/fixed_point/FPLUT.hpp
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <array>
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <numbers>
+#include <utility>
+
+#include "FPLUT_sin_512.hpp"
+
+namespace OpenVic::FPLUT {
+ constexpr int32_t PRECISION = 16;
+ constexpr int64_t ONE = 1 << PRECISION;
+
+ // The LUT index is between 0 and 2^16, the index table goes until 512, if we shift by 7 our index will be between 0 and 511
+ constexpr int32_t SHIFT = 16 - 9;
+
+ constexpr int64_t sin(int64_t value) {
+ int sign = 1;
+ if (value < 0) {
+ value = -value;
+ sign = -1;
+ }
+
+ int index = value >> SHIFT;
+ int64_t a = SIN_LUT[index];
+ int64_t b = SIN_LUT[index + 1];
+ int64_t fraction = (value - (index << SHIFT)) << 9;
+ int64_t result = a + (((b - a) * fraction) >> PRECISION);
+ return result * sign;
+ }
+}
diff --git a/src/openvic/types/fixed_point/FPLUT_sin_512.hpp b/src/openvic/types/fixed_point/FPLUT_sin_512.hpp
new file mode 100644
index 0000000..1af0380
--- /dev/null
+++ b/src/openvic/types/fixed_point/FPLUT_sin_512.hpp
@@ -0,0 +1,58 @@
+#pragma once
+
+#include <cstdint>
+
+static constexpr int32_t SIN_LUT[] = {
+ 0, 804, 1608, 2412, 3216, 4019, 4821, 5623, 6424, 7224,
+ 8022, 8820, 9616, 10411, 11204, 11996, 12785, 13573, 14359, 15143,
+ 15924, 16703, 17479, 18253, 19024, 19792, 20557, 21320, 22078, 22834,
+ 23586, 24335, 25080, 25821, 26558, 27291, 28020, 28745, 29466, 30182,
+ 30893, 31600, 32303, 33000, 33692, 34380, 35062, 35738, 36410, 37076,
+ 37736, 38391, 39040, 39683, 40320, 40951, 41576, 42194, 42806, 43412,
+ 44011, 44604, 45190, 45769, 46341, 46906, 47464, 48015, 48559, 49095,
+ 49624, 50146, 50660, 51166, 51665, 52156, 52639, 53114, 53581, 54040,
+ 54491, 54934, 55368, 55794, 56212, 56621, 57022, 57414, 57798, 58172,
+ 58538, 58896, 59244, 59583, 59914, 60235, 60547, 60851, 61145, 61429,
+ 61705, 61971, 62228, 62476, 62714, 62943, 63162, 63372, 63572, 63763,
+ 63944, 64115, 64277, 64429, 64571, 64704, 64827, 64940, 65043, 65137,
+ 65220, 65294, 65358, 65413, 65457, 65492, 65516, 65531, 65536, 65531,
+ 65516, 65492, 65457, 65413, 65358, 65294, 65220, 65137, 65043, 64940,
+ 64827, 64704, 64571, 64429, 64277, 64115, 63944, 63763, 63572, 63372,
+ 63162, 62943, 62714, 62476, 62228, 61971, 61705, 61429, 61145, 60851,
+ 60547, 60235, 59914, 59583, 59244, 58896, 58538, 58172, 57798, 57414,
+ 57022, 56621, 56212, 55794, 55368, 54934, 54491, 54040, 53581, 53114,
+ 52639, 52156, 51665, 51166, 50660, 50146, 49624, 49095, 48559, 48015,
+ 47464, 46906, 46341, 45769, 45190, 44604, 44011, 43412, 42806, 42194,
+ 41576, 40951, 40320, 39683, 39040, 38391, 37736, 37076, 36410, 35738,
+ 35062, 34380, 33692, 33000, 32303, 31600, 30893, 30182, 29466, 28745,
+ 28020, 27291, 26558, 25821, 25080, 24335, 23586, 22834, 22078, 21320,
+ 20557, 19792, 19024, 18253, 17479, 16703, 15924, 15143, 14359, 13573,
+ 12785, 11996, 11204, 10411, 9616, 8820, 8022, 7224, 6424, 5623,
+ 4821, 4019, 3216, 2412, 1608, 804, 0, -804, -1608, -2412,
+ -3216, -4019, -4821, -5623, -6424, -7224, -8022, -8820, -9616, -10411,
+ -11204, -11996, -12785, -13573, -14359, -15143, -15924, -16703, -17479, -18253,
+ -19024, -19792, -20557, -21320, -22078, -22834, -23586, -24335, -25080, -25821,
+ -26558, -27291, -28020, -28745, -29466, -30182, -30893, -31600, -32303, -33000,
+ -33692, -34380, -35062, -35738, -36410, -37076, -37736, -38391, -39040, -39683,
+ -40320, -40951, -41576, -42194, -42806, -43412, -44011, -44604, -45190, -45769,
+ -46341, -46906, -47464, -48015, -48559, -49095, -49624, -50146, -50660, -51166,
+ -51665, -52156, -52639, -53114, -53581, -54040, -54491, -54934, -55368, -55794,
+ -56212, -56621, -57022, -57414, -57798, -58172, -58538, -58896, -59244, -59583,
+ -59914, -60235, -60547, -60851, -61145, -61429, -61705, -61971, -62228, -62476,
+ -62714, -62943, -63162, -63372, -63572, -63763, -63944, -64115, -64277, -64429,
+ -64571, -64704, -64827, -64940, -65043, -65137, -65220, -65294, -65358, -65413,
+ -65457, -65492, -65516, -65531, -65536, -65531, -65516, -65492, -65457, -65413,
+ -65358, -65294, -65220, -65137, -65043, -64940, -64827, -64704, -64571, -64429,
+ -64277, -64115, -63944, -63763, -63572, -63372, -63162, -62943, -62714, -62476,
+ -62228, -61971, -61705, -61429, -61145, -60851, -60547, -60235, -59914, -59583,
+ -59244, -58896, -58538, -58172, -57798, -57414, -57022, -56621, -56212, -55794,
+ -55368, -54934, -54491, -54040, -53581, -53114, -52639, -52156, -51665, -51166,
+ -50660, -50146, -49624, -49095, -48559, -48015, -47464, -46906, -46341, -45769,
+ -45190, -44604, -44011, -43412, -42806, -42194, -41576, -40951, -40320, -39683,
+ -39040, -38391, -37736, -37076, -36410, -35738, -35062, -34380, -33692, -33000,
+ -32303, -31600, -30893, -30182, -29466, -28745, -28020, -27291, -26558, -25821,
+ -25080, -24335, -23586, -22834, -22078, -21320, -20557, -19792, -19024, -18253,
+ -17479, -16703, -15924, -15143, -14359, -13573, -12785, -11996, -11204, -10411,
+ -9616, -8820, -8022, -7224, -6424, -5623, -4821, -4019, -3216, -2412,
+ -1608, -804, 0
+};
diff --git a/src/openvic/types/fixed_point/FPMath.hpp b/src/openvic/types/fixed_point/FPMath.hpp
new file mode 100644
index 0000000..965d63c
--- /dev/null
+++ b/src/openvic/types/fixed_point/FPMath.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "FP.hpp"
+
+namespace OpenVic::FPMath {
+ constexpr FP sin(FP number) {
+ number %= FP::pi2();
+ number *= FP::one_div_pi2();
+ return FPLUT::sin(number.get_raw_value());
+ }
+}
diff --git a/src/openvic/types/fixed_point/lut_generator/lut_generator.py b/src/openvic/types/fixed_point/lut_generator/lut_generator.py
new file mode 100644
index 0000000..5c5ba8b
--- /dev/null
+++ b/src/openvic/types/fixed_point/lut_generator/lut_generator.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+import math
+
+PRECISION = 16
+ONE = 1 << PRECISION
+SIN_VALUE_COUNT = 512
+
+SinLut = []
+
+for i in range(SIN_VALUE_COUNT):
+ angle = 2 * math.pi * i / SIN_VALUE_COUNT
+
+ sin_value = math.sin(angle)
+ moved_sin = sin_value * ONE
+ rounded_sin = int(moved_sin + 0.5) if moved_sin > 0 else int(moved_sin - 0.5)
+ SinLut.append(rounded_sin)
+
+SinLut.append(SinLut[0])
+
+output = [
+"""#pragma once
+
+#include <cstdint>
+
+static constexpr int32_t SIN_LUT[] = {"""
+]
+
+lines = [SinLut[i:i+10] for i in range(0, len(SinLut), 10)]
+
+for line in lines:
+ output.append("\t" + ", ".join(str(value) for value in line) + ",")
+
+output[-1] = output[-1][:-1] # Remove the last comma
+output.append("};\n")
+
+cpp_code = "\n".join(output)
+
+with open(f"FPLUT_sin_{SIN_VALUE_COUNT}.hpp", "w", newline="\n") as file:
+ file.write(cpp_code)
diff --git a/src/openvic/utility/BMP.cpp b/src/openvic/utility/BMP.cpp
index 9a90d68..6859411 100644
--- a/src/openvic/utility/BMP.cpp
+++ b/src/openvic/utility/BMP.cpp
@@ -3,7 +3,7 @@
#include <cstring>
#include <set>
-#include "Logger.hpp"
+#include "openvic/utility/Logger.hpp"
using namespace OpenVic;
diff --git a/src/openvic/utility/BMP.hpp b/src/openvic/utility/BMP.hpp
index c6f5815..50b333b 100644
--- a/src/openvic/utility/BMP.hpp
+++ b/src/openvic/utility/BMP.hpp
@@ -1,6 +1,10 @@
#pragma once
-#include "../Types.hpp"
+#include <cstdio>
+#include <vector>
+
+#include "openvic/types/Colour.hpp"
+#include "openvic/types/Return.hpp"
namespace OpenVic {
class BMP {
diff --git a/src/openvic/utility/FloatUtils.hpp b/src/openvic/utility/FloatUtils.hpp
new file mode 100644
index 0000000..4fc83fd
--- /dev/null
+++ b/src/openvic/utility/FloatUtils.hpp
@@ -0,0 +1,11 @@
+#include <cstdint>
+
+namespace OpenVic::FloatUtils {
+ constexpr int round_to_int(double num) {
+ return (num > 0.0) ? (num + 0.5) : (num - 0.5);
+ }
+
+ constexpr int64_t round_to_int64(double num) {
+ return (num > 0.0) ? (num + 0.5) : (num - 0.5);
+ }
+}
diff --git a/src/openvic/utility/StringUtils.hpp b/src/openvic/utility/StringUtils.hpp
new file mode 100644
index 0000000..72f4038
--- /dev/null
+++ b/src/openvic/utility/StringUtils.hpp
@@ -0,0 +1,93 @@
+#include <cstdint>
+#include <limits>
+
+namespace OpenVic::StringUtils {
+ /* The constexpr function 'string_to_int64' will convert a string into a int64 integer value.
+ * The function takes four parameters: the input string (as a pair of pointers marking the start and
+ * end of the string), a bool pointer for reporting success, and the base for numerical conversion.
+ * The base parameter defaults to 10 (decimal), but it can be any value between 2 and 36. If the base
+ * given is 0, it will be set to 16 if the string starts with "0x" or "0X", otherwise 8 if the string
+ * still starts with "0", otherwise 10. The success bool pointer parameter is used to report whether
+ * or not conversion was successful. It can be nullptr if this information is not needed.
+ */
+ constexpr int64_t string_to_int64(char const* str, const char* end, bool* successful, int base = 10) {
+ if (successful != nullptr) *successful = false;
+
+ // Base value should be between 2 and 36. If it's not, return 0 as an invalid case.
+ if (str == nullptr || end <= str || base < 0 || base == 1 || base > 36)
+ return 0;
+
+ // The result of the conversion will be stored in this variable.
+ int64_t result = 0;
+ // This flag will be set if the number is negative.
+ bool is_negative = false;
+
+ // Check if there is a sign character.
+ if (*str == '+' || *str == '-') {
+ if (*str == '-')
+ is_negative = true;
+ ++str;
+ if (str == end) return 0;
+ }
+
+ // If base is zero, base is determined by the string prefix.
+ if (base == 0) {
+ if (*str == '0') {
+ if (str + 1 != end && (str[1] == 'x' || str[1] == 'X')) {
+ base = 16; // Hexadecimal.
+ str += 2; // Skip '0x' or '0X'
+ if (str == end) return 0;
+ } else {
+ base = 8; // Octal.
+ }
+ } else {
+ base = 10; // Decimal.
+ }
+ } else if (base == 16) {
+ // If base is 16 and string starts with '0x' or '0X', skip these characters.
+ if (*str == '0' && str + 1 != end && (str[1] == 'x' || str[1] == 'X')) {
+ str += 2;
+ if (str == end) return 0;
+ }
+ }
+
+ // Convert the number in the string.
+ for (; str != end; ++str) {
+ int digit;
+ if (*str >= '0' && *str <= '9') {
+ digit = *str - '0'; // Calculate digit value for '0'-'9'.
+ } else if (*str >= 'a' && *str <= 'z') {
+ digit = *str - 'a' + 10; // Calculate digit value for 'a'-'z'.
+ } else if (*str >= 'A' && *str <= 'Z') {
+ digit = *str - 'A' + 10; // Calculate digit value for 'A'-'Z'.
+ } else {
+ break; // Stop conversion if current character is not a digit.
+ }
+
+ if (digit >= base) {
+ break; // Stop conversion if current digit is greater than or equal to the base.
+ }
+
+ // Check for overflow on multiplication
+ if (result > std::numeric_limits<int64_t>::max() / base) {
+ return is_negative ? std::numeric_limits<int64_t>::min() : std::numeric_limits<int64_t>::max();
+ }
+
+ result *= base;
+
+ // Check for overflow on addition
+ if (result > std::numeric_limits<int64_t>::max() - digit) {
+ return is_negative ? std::numeric_limits<int64_t>::min() : std::numeric_limits<int64_t>::max();
+ }
+
+ result += digit;
+ }
+
+ // If successful is not null and the entire string was parsed,
+ // set *successful to true (if not it is already false).
+ if (successful != nullptr && str == end) *successful = true;
+
+ // Return the result. If the number was negative, the result is negated.
+ return is_negative ? -result : result;
+ }
+}