]> git.localhorst.tv Git - blank.git/blob - src/app/runtime.cpp
75eeec55dd2c3e9c790ad2dfcd510a1c08396ff9
[blank.git] / src / app / runtime.cpp
1 #include "Application.hpp"
2 #include "Environment.hpp"
3 #include "Runtime.hpp"
4
5 #include "init.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"
12
13 #include <cctype>
14 #include <cstdlib>
15 #include <ctime>
16 #include <fstream>
17 #include <iostream>
18 #include <SDL.h>
19
20 using namespace std;
21
22
23 namespace {
24
25 string default_asset_path() {
26         char *base = SDL_GetBasePath();
27         string assets(base);
28         assets += "assets/";
29         SDL_free(base);
30         return assets;
31 }
32
33 string default_save_path() {
34 #ifndef NDEBUG
35         char *base = SDL_GetBasePath();
36         string save(base);
37         save += "saves/";
38         SDL_free(base);
39         return save;
40 #else
41         char *pref = SDL_GetPrefPath("localhorst", "blank");
42         string save(pref);
43         SDL_free(pref);
44         return save;
45 #endif
46 }
47
48 }
49
50 namespace blank {
51
52 void Config::Load(std::istream &is) {
53         TokenStreamReader in(is);
54         std::string name;
55         while (in.HasMore()) {
56                 if (in.Peek().type == Token::STRING) {
57                         in.ReadString(name);
58                 } else {
59                         in.ReadIdentifier(name);
60                 }
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") {
75                         int port;
76                         in.ReadNumber(port);
77                         net.port = port;
78                 } else if (name == "net.cmd_port") {
79                         int port;
80                         in.ReadNumber(port);
81                         net.cmd_port = 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);
96                 }
97                 if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
98                         in.Skip(Token::SEMICOLON);
99                 }
100         }
101 }
102
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;
119 }
120
121
122 HeadlessEnvironment::HeadlessEnvironment(const Config &config)
123 : config(config)
124 , loader(config.asset_path)
125 , counter()
126 , state() {
127
128 }
129
130 string HeadlessEnvironment::Config::GetWorldPath(const string &world_name) const {
131         return save_path + "worlds/" + world_name + '/';
132 }
133
134 string HeadlessEnvironment::Config::GetWorldPath(const string &world_name, const string &host_name) const {
135         return save_path + "cache/" + host_name + '/' + world_name + '/';
136 }
137
138 Environment::Environment(Window &win, const Config &config)
139 : HeadlessEnvironment(config)
140 , assets(loader)
141 , audio()
142 , viewport()
143 , window(win)
144 , keymap()
145 , msg_state(*this) {
146         viewport.Clear();
147         window.Flip();
148         keymap.LoadDefault();
149
150         string keys_path = config.save_path + "keys.conf";
151         if (!is_file(keys_path)) {
152                 std::ofstream file(keys_path);
153                 keymap.Save(file);
154         } else {
155                 std::ifstream file(keys_path);
156                 keymap.Load(file);
157         }
158 }
159
160 void Environment::ShowMessage(const char *msg) {
161         cout << msg << endl;
162         msg_state.SetMessage(msg);
163         state.Push(&msg_state);
164 }
165
166
167 Runtime::Runtime() noexcept
168 : name("blank")
169 , mode(NORMAL)
170 , target(STANDALONE)
171 , n(0)
172 , t(0)
173 , config() {
174
175 }
176
177
178 void Runtime::Initialize(int argc, const char *const *argv) {
179         ReadArgs(argc, argv);
180         if (mode == ERROR) return;
181         ReadPreferences();
182         ReadArgs(argc, argv);
183 }
184
185 void Runtime::ReadArgs(int argc, const char *const *argv) {
186         if (argc <= 0) return;
187         name = argv[0];
188
189         bool options = true;
190         bool error = false;
191
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;
196                         continue;
197                 }
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') {
203                                         // stopper
204                                         options = false;
205                                 } else {
206                                         const char *param = arg + 2;
207                                         // long option
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) {
219                                                 target = STANDALONE;
220                                         } else if (strcmp(param, "server") == 0) {
221                                                 target = SERVER;
222                                         } else if (strcmp(param, "client") == 0) {
223                                                 target = CLIENT;
224                                         } else if (strcmp(param, "asset-path") == 0) {
225                                                 ++i;
226                                                 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
227                                                         cerr << "missing argument to --asset-path" << endl;
228                                                         error = true;
229                                                 } else {
230                                                         config.env.asset_path = argv[i];
231                                                 }
232                                         } else if (strcmp(param, "host") == 0) {
233                                                 ++i;
234                                                 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
235                                                         cerr << "missing argument to --host" << endl;
236                                                         error = true;
237                                                 } else {
238                                                         config.game.net.host = argv[i];
239                                                 }
240                                         } else if (strcmp(param, "port") == 0) {
241                                                 ++i;
242                                                 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
243                                                         cerr << "missing argument to --port" << endl;
244                                                         error = true;
245                                                 } else {
246                                                         config.game.net.port = strtoul(argv[i], nullptr, 10);
247                                                 }
248                                         } else if (strcmp(param, "cmd-port") == 0) {
249                                                 ++i;
250                                                 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
251                                                         cerr << "missing argument to --cmd-port" << endl;
252                                                         error = true;
253                                                 } else {
254                                                         config.game.net.cmd_port = strtoul(argv[i], nullptr, 10);
255                                                 }
256                                         } else if (strcmp(param, "player-name") == 0) {
257                                                 ++i;
258                                                 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
259                                                         cerr << "missing argument to --player-name" << endl;
260                                                         error = true;
261                                                 } else {
262                                                         config.game.player.name = argv[i];
263                                                 }
264                                         } else if (strcmp(param, "save-path") == 0) {
265                                                 ++i;
266                                                 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
267                                                         cerr << "missing argument to --save-path" << endl;
268                                                         error = true;
269                                                 } else {
270                                                         config.env.save_path = argv[i];
271                                                 }
272                                         } else if (strcmp(param, "world-name") == 0) {
273                                                 ++i;
274                                                 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
275                                                         cerr << "missing argument to --world-name" << endl;
276                                                         error = true;
277                                                 } else {
278                                                         config.world.name = argv[i];
279                                                 }
280                                         } else {
281                                                 cerr << "unknown option " << arg << endl;
282                                                 error = true;
283                                         }
284                                 }
285                         } else {
286                                 // short options
287                                 for (int j = 1; arg[j] != '\0'; ++j) {
288                                         switch (arg[j]) {
289                                                 case 'd':
290                                                         config.game.video.dblbuf = false;
291                                                         break;
292                                                 case 'm':
293                                                         ++i;
294                                                         if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
295                                                                 cerr << "missing argument to -m" << endl;
296                                                                 error = true;
297                                                         } else {
298                                                                 config.game.video.msaa = strtoul(argv[i], nullptr, 10);
299                                                         }
300                                                         break;
301                                                 case 'n':
302                                                         ++i;
303                                                         if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
304                                                                 cerr << "missing argument to -n" << endl;
305                                                                 error = true;
306                                                         } else {
307                                                                 n = strtoul(argv[i], nullptr, 10);
308                                                         }
309                                                         break;
310                                                 case 's':
311                                                         ++i;
312                                                         if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
313                                                                 cerr << "missing argument to -s" << endl;
314                                                                 error = true;
315                                                         } else {
316                                                                 config.gen.seed = strtoul(argv[i], nullptr, 10);
317                                                         }
318                                                         break;
319                                                 case 't':
320                                                         ++i;
321                                                         if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
322                                                                 cerr << "missing argument to -t" << endl;
323                                                                 error = true;
324                                                         } else {
325                                                                 t = strtoul(argv[i], nullptr, 10);
326                                                         }
327                                                         break;
328                                                 case '-':
329                                                         // stopper
330                                                         options = false;
331                                                         break;
332                                                 default:
333                                                         cerr << "unknown option " << arg[j] << endl;
334                                                         error = true;
335                                                         break;
336                                         }
337                                 }
338                         }
339                 } else {
340                         cerr << "unable to interpret argument "
341                                 << i << " (" << arg << ")" << endl;
342                         error = true;
343                 }
344         }
345
346         if (error) {
347                 mode = ERROR;
348         } else if (n > 0) {
349                 if (t > 0) {
350                         mode = FIXED_FRAME_LIMIT;
351                 } else {
352                         mode = FRAME_LIMIT;
353                 }
354         } else if (t > 0) {
355                 mode = TIME_LIMIT;
356         } else {
357                 mode = NORMAL;
358         }
359 }
360
361 void Runtime::ReadPreferences() {
362         if (config.env.asset_path.empty()) {
363                 config.env.asset_path = default_asset_path();
364         } else if (
365                 config.env.asset_path[config.env.asset_path.size() - 1] != '/' &&
366                 config.env.asset_path[config.env.asset_path.size() - 1] != '\\'
367         ) {
368                 config.env.asset_path += '/';
369         }
370         if (config.env.save_path.empty()) {
371                 config.env.save_path = default_save_path();
372         } else if (
373                 config.env.save_path[config.env.save_path.size() - 1] != '/' &&
374                 config.env.save_path[config.env.save_path.size() - 1] != '\\'
375         ) {
376                 config.env.save_path += '/';
377         }
378
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);
383         } else {
384                 make_dirs(config.env.save_path);
385                 ofstream file(prefs_path);
386                 config.game.Save(file);
387         }
388 }
389
390 int Runtime::Execute() {
391         if (mode == ERROR) {
392                 return 1;
393         }
394
395         InitHeadless init_headless;
396
397         switch (target) {
398                 default:
399                 case STANDALONE:
400                         RunStandalone();
401                         break;
402                 case SERVER:
403                         RunServer();
404                         break;
405                 case CLIENT:
406                         RunClient();
407                         break;
408         }
409
410         return 0;
411 }
412
413 void Runtime::RunStandalone() {
414         Init init(config.game.video.dblbuf, config.game.video.msaa);
415
416         Environment env(init.window, config.env);
417         env.viewport.VSync(config.game.video.vsync);
418
419         WorldSave save(config.env.GetWorldPath(config.world.name));
420         if (save.Exists()) {
421                 save.Read(config.world);
422                 save.Read(config.gen);
423         } else {
424                 save.Write(config.world);
425                 save.Write(config.gen);
426         }
427
428         Application app(env);
429         standalone::MasterState world_state(env, config.game, config.gen, config.world, save);
430         app.PushState(&world_state);
431         Run(app);
432 }
433
434 void Runtime::RunServer() {
435         HeadlessEnvironment env(config.env);
436
437         WorldSave save(config.env.GetWorldPath(config.world.name));
438         if (save.Exists()) {
439                 save.Read(config.world);
440                 save.Read(config.gen);
441         } else {
442                 save.Write(config.world);
443                 save.Write(config.gen);
444         }
445
446         HeadlessApplication app(env);
447         server::ServerState server_state(env, config.gen, config.world, save, config.game);
448         app.PushState(&server_state);
449         Run(app);
450 }
451
452 void Runtime::RunClient() {
453         Init init(config.game.video.dblbuf, config.game.video.msaa);
454
455         Environment env(init.window, config.env);
456         env.viewport.VSync(config.game.video.vsync);
457
458         Application app(env);
459         client::MasterState client_state(env, config.game, config.world);
460         app.PushState(&client_state);
461         Run(app);
462 }
463
464 void Runtime::Run(HeadlessApplication &app) {
465         switch (mode) {
466                 default:
467                 case NORMAL:
468                         app.Run();
469                         break;
470                 case FRAME_LIMIT:
471                         app.RunN(n);
472                         break;
473                 case TIME_LIMIT:
474                         app.RunT(t);
475                         break;
476                 case FIXED_FRAME_LIMIT:
477                         app.RunS(n, t);
478                         break;
479         }
480 }
481
482 }