1 #include "Application.hpp"
2 #include "Environment.hpp"
6 #include "../client/MasterState.hpp"
7 #include "../io/filesystem.hpp"
8 #include "../io/TokenStreamReader.hpp"
9 #include "../io/WorldSave.hpp"
10 #include "../server/ServerState.hpp"
11 #include "../standalone/MasterState.hpp"
25 string default_asset_path() {
26 char *base = SDL_GetBasePath();
33 string default_save_path() {
35 char *base = SDL_GetBasePath();
41 char *pref = SDL_GetPrefPath("localhorst", "blank");
52 void Config::Load(std::istream &is) {
53 TokenStreamReader in(is);
55 while (in.HasMore()) {
56 if (in.Peek().type == Token::STRING) {
59 in.ReadIdentifier(name);
61 in.Skip(Token::EQUALS);
62 if (name == "audio.enabled") {
63 in.ReadBoolean(audio.enabled);
64 } else if (name == "input.keyboard") {
65 in.ReadBoolean(input.keyboard);
66 } else if (name == "input.mouse") {
67 in.ReadBoolean(input.mouse);
68 } else if (name == "input.pitch_sensitivity") {
69 in.ReadNumber(input.pitch_sensitivity);
70 } else if (name == "input.yaw_sensitivity") {
71 in.ReadNumber(input.yaw_sensitivity);
72 } else if (name == "net.host") {
73 in.ReadString(net.host);
74 } else if (name == "net.port") {
78 } else if (name == "net.cmd_port") {
82 } else if (name == "player.name") {
83 in.ReadString(player.name);
84 } else if (name == "video.dblbuf") {
85 in.ReadBoolean(video.dblbuf);
86 } else if (name == "video.vsync") {
87 in.ReadBoolean(video.vsync);
88 } else if (name == "video.msaa") {
89 in.ReadNumber(video.msaa);
90 } else if (name == "video.hud") {
91 in.ReadBoolean(video.hud);
92 } else if (name == "video.world") {
93 in.ReadBoolean(video.world);
94 } else if (name == "video.debug") {
95 in.ReadBoolean(video.debug);
97 if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
98 in.Skip(Token::SEMICOLON);
103 void Config::Save(std::ostream &out) {
104 out << "audio.enabled = " << (audio.enabled ? "yes" : "no") << ';' << std::endl;
105 out << "input.keyboard = " << (input.keyboard ? "on" : "off") << ';' << std::endl;
106 out << "input.mouse = " << (input.keyboard ? "on" : "off") << ';' << std::endl;
107 out << "input.pitch_sensitivity = " << input.pitch_sensitivity << ';' << std::endl;
108 out << "input.yaw_sensitivity = " << input.yaw_sensitivity << ';' << std::endl;
109 out << "net.host = \"" << net.host << "\";" << std::endl;
110 out << "net.port = " << net.port << ';' << std::endl;
111 out << "net.cmd_port = " << net.cmd_port << ';' << std::endl;
112 out << "player.name = \"" << player.name << "\";" << std::endl;
113 out << "video.dblbuf = " << (video.dblbuf ? "on" : "off") << ';' << std::endl;
114 out << "video.vsync = " << (video.vsync ? "on" : "off") << ';' << std::endl;
115 out << "video.msaa = " << video.msaa << ';' << std::endl;
116 out << "video.hud = " << (video.hud ? "on" : "off") << ';' << std::endl;
117 out << "video.world = " << (video.world ? "on" : "off") << ';' << std::endl;
118 out << "video.debug = " << (video.debug ? "on" : "off") << ';' << std::endl;
122 HeadlessEnvironment::HeadlessEnvironment(const Config &config)
124 , loader(config.asset_path)
130 string HeadlessEnvironment::Config::GetWorldPath(const string &world_name) const {
131 return save_path + "worlds/" + world_name + '/';
134 string HeadlessEnvironment::Config::GetWorldPath(const string &world_name, const string &host_name) const {
135 return save_path + "cache/" + host_name + '/' + world_name + '/';
138 Environment::Environment(Window &win, const Config &config)
139 : HeadlessEnvironment(config)
148 keymap.LoadDefault();
150 string keys_path = config.save_path + "keys.conf";
151 if (!is_file(keys_path)) {
152 std::ofstream file(keys_path);
155 std::ifstream file(keys_path);
160 void Environment::ShowMessage(const char *msg) {
162 msg_state.SetMessage(msg);
163 state.Push(&msg_state);
167 Runtime::Runtime() noexcept
178 void Runtime::Initialize(int argc, const char *const *argv) {
179 ReadArgs(argc, argv);
180 if (mode == ERROR) return;
182 ReadArgs(argc, argv);
185 void Runtime::ReadArgs(int argc, const char *const *argv) {
186 if (argc <= 0) return;
192 for (int i = 1; i < argc; ++i) {
193 const char *arg = argv[i];
194 if (!arg || arg[0] == '\0') {
195 cerr << "warning: found empty argument at position " << i << endl;
198 if (options && arg[0] == '-') {
199 if (arg[1] == '\0') {
200 cerr << "warning: incomplete option list at position " << i << endl;
201 } else if (arg[1] == '-') {
202 if (arg[2] == '\0') {
206 const char *param = arg + 2;
208 if (strcmp(param, "no-vsync") == 0) {
209 config.game.video.vsync = false;
210 } else if (strcmp(param, "no-keyboard") == 0) {
211 config.game.input.keyboard = false;
212 } else if (strcmp(param, "no-mouse") == 0) {
213 config.game.input.mouse = false;
214 } else if (strcmp(param, "no-hud") == 0) {
215 config.game.video.hud = false;
216 } else if (strcmp(param, "no-audio") == 0) {
217 config.game.audio.enabled = false;
218 } else if (strcmp(param, "standalone") == 0) {
220 } else if (strcmp(param, "server") == 0) {
222 } else if (strcmp(param, "client") == 0) {
224 } else if (strcmp(param, "asset-path") == 0) {
226 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
227 cerr << "missing argument to --asset-path" << endl;
230 config.env.asset_path = argv[i];
232 } else if (strcmp(param, "host") == 0) {
234 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
235 cerr << "missing argument to --host" << endl;
238 config.game.net.host = argv[i];
240 } else if (strcmp(param, "port") == 0) {
242 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
243 cerr << "missing argument to --port" << endl;
246 config.game.net.port = strtoul(argv[i], nullptr, 10);
248 } else if (strcmp(param, "cmd-port") == 0) {
250 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
251 cerr << "missing argument to --cmd-port" << endl;
254 config.game.net.cmd_port = strtoul(argv[i], nullptr, 10);
256 } else if (strcmp(param, "player-name") == 0) {
258 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
259 cerr << "missing argument to --player-name" << endl;
262 config.game.player.name = argv[i];
264 } else if (strcmp(param, "save-path") == 0) {
266 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
267 cerr << "missing argument to --save-path" << endl;
270 config.env.save_path = argv[i];
272 } else if (strcmp(param, "world-name") == 0) {
274 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
275 cerr << "missing argument to --world-name" << endl;
278 config.world.name = argv[i];
281 cerr << "unknown option " << arg << endl;
287 for (int j = 1; arg[j] != '\0'; ++j) {
290 config.game.video.dblbuf = false;
294 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
295 cerr << "missing argument to -m" << endl;
298 config.game.video.msaa = strtoul(argv[i], nullptr, 10);
303 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
304 cerr << "missing argument to -n" << endl;
307 n = strtoul(argv[i], nullptr, 10);
312 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
313 cerr << "missing argument to -s" << endl;
316 config.gen.seed = strtoul(argv[i], nullptr, 10);
321 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
322 cerr << "missing argument to -t" << endl;
325 t = strtoul(argv[i], nullptr, 10);
333 cerr << "unknown option " << arg[j] << endl;
340 cerr << "unable to interpret argument "
341 << i << " (" << arg << ")" << endl;
350 mode = FIXED_FRAME_LIMIT;
360 if (config.env.asset_path.empty()) {
361 config.env.asset_path = default_asset_path();
363 config.env.asset_path.back() != '/' &&
364 config.env.asset_path.back() != '\\'
366 config.env.asset_path += '/';
368 if (config.env.save_path.empty()) {
369 config.env.save_path = default_save_path();
371 config.env.save_path.back() != '/' &&
372 config.env.save_path.back() != '\\'
374 config.env.save_path += '/';
378 void Runtime::ReadPreferences() {
379 string prefs_path = config.env.save_path + "prefs.conf";
380 if (is_file(prefs_path)) {
381 ifstream file(prefs_path);
382 config.game.Load(file);
384 make_dirs(config.env.save_path);
385 ofstream file(prefs_path);
386 config.game.Save(file);
390 int Runtime::Execute() {
395 InitHeadless init_headless;
413 void Runtime::RunStandalone() {
414 Init init(config.game.video.dblbuf, config.game.video.msaa);
416 Environment env(init.window, config.env);
417 env.viewport.VSync(config.game.video.vsync);
419 WorldSave save(config.env.GetWorldPath(config.world.name));
421 save.Read(config.world);
422 save.Read(config.gen);
424 save.Write(config.world);
425 save.Write(config.gen);
428 Application app(env);
429 standalone::MasterState world_state(env, config.game, config.gen, config.world, save);
430 app.PushState(&world_state);
434 void Runtime::RunServer() {
435 HeadlessEnvironment env(config.env);
437 WorldSave save(config.env.GetWorldPath(config.world.name));
439 save.Read(config.world);
440 save.Read(config.gen);
442 save.Write(config.world);
443 save.Write(config.gen);
446 HeadlessApplication app(env);
447 server::ServerState server_state(env, config.gen, config.world, save, config.game);
448 app.PushState(&server_state);
452 void Runtime::RunClient() {
453 Init init(config.game.video.dblbuf, config.game.video.msaa);
455 Environment env(init.window, config.env);
456 env.viewport.VSync(config.game.video.vsync);
458 Application app(env);
459 client::MasterState client_state(env, config.game, config.world);
460 app.PushState(&client_state);
464 void Runtime::Run(HeadlessApplication &app) {
476 case FIXED_FRAME_LIMIT: